diff --git a/sql/updates/world/master/2025_01_21_00_world.sql b/sql/updates/world/master/2025_01_21_00_world.sql new file mode 100644 index 0000000000000..b3f89ecbbf36f --- /dev/null +++ b/sql/updates/world/master/2025_01_21_00_world.sql @@ -0,0 +1,3 @@ +ALTER TABLE `spell_target_position` DROP PRIMARY KEY; +ALTER TABLE `spell_target_position` ADD `OrderIndex` int NOT NULL DEFAULT '0' AFTER `EffectIndex`; +ALTER TABLE `spell_target_position` ADD PRIMARY KEY(`ID`,`EffectIndex`,`OrderIndex`); diff --git a/src/server/game/Miscellaneous/SharedDefines.h b/src/server/game/Miscellaneous/SharedDefines.h index 81b371aabad36..5f0c06c0dd275 100644 --- a/src/server/game/Miscellaneous/SharedDefines.h +++ b/src/server/game/Miscellaneous/SharedDefines.h @@ -2874,7 +2874,7 @@ enum Targets TARGET_UNIT_PASSENGER_7 = 103, TARGET_UNIT_CONE_CASTER_TO_DEST_ENEMY = 104, TARGET_UNIT_CASTER_AND_PASSENGERS = 105, - TARGET_DEST_CHANNEL_CASTER = 106, + TARGET_DEST_NEARBY_DB = 106, TARGET_DEST_NEARBY_ENTRY_2 = 107, TARGET_GAMEOBJECT_CONE_CASTER_TO_DEST_ENEMY = 108, TARGET_GAMEOBJECT_CONE_CASTER_TO_DEST_ALLY = 109, diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index 62a9279c343f1..4ad83b680c0ab 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -1057,16 +1057,6 @@ void Spell::SelectImplicitChannelTargets(SpellEffectInfo const& spellEffectInfo, TC_LOG_DEBUG("spells", "SPELL: cannot find channel spell destination for spell ID {}, effect {}", m_spellInfo->Id, uint32(spellEffectInfo.EffectIndex)); } break; - case TARGET_DEST_CHANNEL_CASTER: - { - SpellDestination dest(*channeledSpell->GetCaster()); - if (m_spellInfo->HasAttribute(SPELL_ATTR4_USE_FACING_FROM_SPELL)) - dest._position.SetOrientation(spellEffectInfo.PositionFacing); - - CallScriptDestinationTargetSelectHandlers(dest, spellEffectInfo.EffectIndex, targetType); - m_targets.SetDst(dest); - break; - } default: ABORT_MSG("Spell::SelectImplicitChannelTargets: received not implemented target type"); break; @@ -1579,6 +1569,25 @@ void Spell::SelectImplicitCasterDestTargets(SpellEffectInfo const& spellEffectIn if (WorldObject const* summoner = casterSummon->GetSummoner()) dest = SpellDestination(*summoner); break; + case TARGET_DEST_NEARBY_DB: + { + Optional> radiusBounds = spellEffectInfo.CalcRadiusBounds(m_caster, targetIndex, this); + std::vector positionsInRange; + for (auto const& [_, position] : sSpellMgr->GetSpellTargetPositions(m_spellInfo->Id, spellEffectInfo.EffectIndex)) + if (m_caster->GetMapId() == position.GetMapId() && (!radiusBounds || (!m_caster->IsInDist(position, radiusBounds->first) && m_caster->IsInDist(position, radiusBounds->second)))) + positionsInRange.push_back(&position); + + if (positionsInRange.empty()) + { + TC_LOG_DEBUG("spells", "SPELL: unknown target coordinates for spell ID {}", m_spellInfo->Id); + SendCastResult(SPELL_FAILED_NO_VALID_TARGETS); + finish(SPELL_FAILED_NO_VALID_TARGETS); + return; + } + + dest = Trinity::Containers::SelectRandomContainerElement(positionsInRange)->GetPosition(); + break; + } default: { float dist = spellEffectInfo.CalcRadius(m_caster, targetIndex); @@ -1599,7 +1608,7 @@ void Spell::SelectImplicitCasterDestTargets(SpellEffectInfo const& spellEffectIn case TARGET_DEST_CASTER_FRONT_RIGHT: case TARGET_DEST_CASTER_BACK_RIGHT: { - constexpr float DefaultTotemDistance = 3.0f; + static constexpr float DefaultTotemDistance = 3.0f; if (!spellEffectInfo.HasRadius(targetIndex)) dist = DefaultTotemDistance; break; diff --git a/src/server/game/Spells/SpellInfo.cpp b/src/server/game/Spells/SpellInfo.cpp index 8cc089c5ee73a..09092589c5f6c 100644 --- a/src/server/game/Spells/SpellInfo.cpp +++ b/src/server/game/Spells/SpellInfo.cpp @@ -347,7 +347,7 @@ std::array SpellImplic {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT, TARGET_DIR_NONE}, // 103 TARGET_UNIT_PASSENGER_7 {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_DEST, TARGET_SELECT_CATEGORY_CONE, TARGET_CHECK_ENEMY, TARGET_DIR_FRONT}, // 104 TARGET_UNIT_CONE_CASTER_TO_DEST_ENEMY {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_AREA, TARGET_CHECK_DEFAULT, TARGET_DIR_NONE}, // 105 TARGET_UNIT_CASTER_AND_PASSENGERS - {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_CHANNEL, TARGET_CHECK_DEFAULT, TARGET_DIR_NONE}, // 106 TARGET_DEST_CHANNEL_CASTER + {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT, TARGET_DIR_NONE}, // 106 TARGET_DEST_NEARBY_DB {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_NEARBY, TARGET_CHECK_ENTRY, TARGET_DIR_NONE}, // 107 TARGET_DEST_NEARBY_ENTRY_2 {TARGET_OBJECT_TYPE_GOBJ, TARGET_REFERENCE_TYPE_DEST, TARGET_SELECT_CATEGORY_CONE, TARGET_CHECK_ENEMY, TARGET_DIR_FRONT}, // 108 TARGET_GAMEOBJECT_CONE_CASTER_TO_DEST_ENEMY {TARGET_OBJECT_TYPE_GOBJ, TARGET_REFERENCE_TYPE_DEST, TARGET_SELECT_CATEGORY_CONE, TARGET_CHECK_ALLY, TARGET_DIR_FRONT}, // 109 TARGET_GAMEOBJECT_CONE_CASTER_TO_DEST_ALLY @@ -715,6 +715,39 @@ float SpellEffectInfo::CalcRadius(WorldObject* caster /*= nullptr*/, SpellTarget return radius; } +Optional> SpellEffectInfo::CalcRadiusBounds(WorldObject* caster, SpellTargetIndex targetIndex, Spell* spell) const +{ + // TargetA -> TargetARadiusEntry + // TargetB -> TargetBRadiusEntry + // Aura effects have TargetARadiusEntry == TargetBRadiusEntry (mostly) + SpellRadiusEntry const* entry = TargetARadiusEntry; + if (targetIndex == SpellTargetIndex::TargetB && HasRadius(targetIndex)) + entry = TargetBRadiusEntry; + + Optional> bounds; + if (!entry) + return bounds; + + bounds.emplace(entry->RadiusMin, entry->RadiusMax); + + if (caster) + { + if (Player* modOwner = caster->GetSpellModOwner()) + modOwner->ApplySpellMod(_spellInfo, SpellModOp::Radius, bounds->second, spell); + + if (!_spellInfo->HasAttribute(SPELL_ATTR9_NO_MOVEMENT_RADIUS_BONUS)) + { + if (Unit const* casterUnit = caster->ToUnit(); casterUnit && Spell::CanIncreaseRangeByMovement(casterUnit)) + { + bounds->first = std::max(bounds->first - 2.0f, 0.0f); + bounds->second += 2.0f; + } + } + } + + return bounds; +} + uint32 SpellEffectInfo::GetProvidedTargetMask() const { return GetTargetFlagMask(TargetA.GetObjectType()) | GetTargetFlagMask(TargetB.GetObjectType()); diff --git a/src/server/game/Spells/SpellInfo.h b/src/server/game/Spells/SpellInfo.h index f49727dc8003b..1af55b902390e 100644 --- a/src/server/game/Spells/SpellInfo.h +++ b/src/server/game/Spells/SpellInfo.h @@ -266,7 +266,8 @@ class TC_GAME_API SpellEffectInfo float CalcDamageMultiplier(WorldObject* caster, Spell* spell = nullptr) const; bool HasRadius(SpellTargetIndex targetIndex) const; - float CalcRadius(WorldObject* caster = nullptr, SpellTargetIndex targetIndex = SpellTargetIndex::TargetA, Spell* = nullptr) const; + float CalcRadius(WorldObject* caster = nullptr, SpellTargetIndex targetIndex = SpellTargetIndex::TargetA, Spell* spell = nullptr) const; + Optional> CalcRadiusBounds(WorldObject* caster, SpellTargetIndex targetIndex, Spell* spell) const; uint32 GetProvidedTargetMask() const; uint32 GetMissingTargetMask(bool srcSet = false, bool dstSet = false, uint32 mask = 0) const; diff --git a/src/server/game/Spells/SpellMgr.cpp b/src/server/game/Spells/SpellMgr.cpp index 7166e99dc8cf9..14ae672424d0b 100644 --- a/src/server/game/Spells/SpellMgr.cpp +++ b/src/server/game/Spells/SpellMgr.cpp @@ -355,6 +355,11 @@ SpellTargetPosition const* SpellMgr::GetSpellTargetPosition(uint32 spell_id, Spe return nullptr; } +Trinity::IteratorPair SpellMgr::GetSpellTargetPositions(uint32 spell_id, SpellEffIndex effIndex) const +{ + return Trinity::Containers::MapEqualRange(mSpellTargetPositions, { spell_id, effIndex }); +} + SpellSpellGroupMapBounds SpellMgr::GetSpellSpellGroupMapBounds(uint32 spell_id) const { spell_id = GetFirstSpellInChain(spell_id); @@ -1156,15 +1161,14 @@ void SpellMgr::LoadSpellTargetPositions() mSpellTargetPositions.clear(); // need for reload case - // 0 1 2 3 4 5 6 - QueryResult result = WorldDatabase.Query("SELECT ID, EffectIndex, MapID, PositionX, PositionY, PositionZ, Orientation FROM spell_target_position"); + // 0 1 2 3 4 5 6 7 + QueryResult result = WorldDatabase.Query("SELECT ID, EffectIndex, OrderIndex, MapID, PositionX, PositionY, PositionZ, Orientation FROM spell_target_position"); if (!result) { TC_LOG_INFO("server.loading", ">> Loaded 0 spell target coordinates. DB table `spell_target_position` is empty."); return; } - uint32 count = 0; do { Field* fields = result->Fetch(); @@ -1172,7 +1176,7 @@ void SpellMgr::LoadSpellTargetPositions() uint32 spellId = fields[0].GetUInt32(); SpellEffIndex effIndex = SpellEffIndex(fields[1].GetUInt8()); - SpellTargetPosition st(fields[2].GetUInt16(), fields[3].GetFloat(), fields[4].GetFloat(), fields[5].GetFloat()); + SpellTargetPosition st(fields[3].GetUInt16(), fields[4].GetFloat(), fields[5].GetFloat(), fields[6].GetFloat()); MapEntry const* mapEntry = sMapStore.LookupEntry(st.GetMapId()); if (!mapEntry) @@ -1200,61 +1204,62 @@ void SpellMgr::LoadSpellTargetPositions() continue; } - if (!fields[6].IsNull()) - st.SetOrientation(fields[6].GetFloat()); + SpellEffectInfo const& spellEffectInfo = spellInfo->GetEffect(effIndex); + if (!fields[7].IsNull()) + st.SetOrientation(fields[7].GetFloat()); else { // target facing is in degrees for 6484 & 9268... - if (spellInfo->GetEffect(effIndex).PositionFacing > 2 * float(M_PI)) - st.SetOrientation(spellInfo->GetEffect(effIndex).PositionFacing * float(M_PI) / 180); + if (spellEffectInfo.PositionFacing > 2 * float(M_PI)) + st.SetOrientation(spellEffectInfo.PositionFacing * float(M_PI) / 180); else - st.SetOrientation(spellInfo->GetEffect(effIndex).PositionFacing); + st.SetOrientation(spellEffectInfo.PositionFacing); } auto hasTarget = [&](Targets target) { - SpellEffectInfo const& spellEffectInfo = spellInfo->GetEffect(effIndex); return spellEffectInfo.TargetA.GetTarget() == target || spellEffectInfo.TargetB.GetTarget() == target; }; - if (hasTarget(TARGET_DEST_DB) || hasTarget(TARGET_DEST_NEARBY_ENTRY_OR_DB)) - { - std::pair key = std::make_pair(spellId, effIndex); - mSpellTargetPositions[key] = st; - ++count; - } - else + if (!hasTarget(TARGET_DEST_NEARBY_DB)) { - TC_LOG_ERROR("sql.sql", "Spell (Id: {}, effIndex: {}) listed in `spell_target_position` does not have a target TARGET_DEST_DB (17).", spellId, uint32(effIndex)); - continue; + if (!hasTarget(TARGET_DEST_DB) && !hasTarget(TARGET_DEST_NEARBY_ENTRY_OR_DB)) + { + TC_LOG_ERROR("sql.sql", "Spell (Id: {}, effIndex: {}) listed in `spell_target_position` does not have a target TARGET_DEST_DB ({}) or TARGET_DEST_NEARBY_DB ({}) or TARGET_DEST_NEARBY_ENTRY_OR_DB ({}).", + spellId, uint32(effIndex), TARGET_DEST_DB, TARGET_DEST_NEARBY_DB, TARGET_DEST_NEARBY_ENTRY_OR_DB); + continue; + } + if (fields[2].GetInt32() != 0) + { + TC_LOG_ERROR("sql.sql", "Spell (Id: {}, effIndex: {}) listed in `spell_target_position` does not have a target TARGET_DEST_NEARBY_DB ({}) but lists multiple points, only one is allowed.", + spellId, uint32(effIndex), TARGET_DEST_NEARBY_DB); + continue; + } } + mSpellTargetPositions.emplace(std::make_pair(spellId, effIndex), st); + } while (result->NextRow()); /* // Check all spells - for (uint32 i = 1; i < GetSpellInfoStoreSize(); ++i) + for (SpellInfo const& spellInfo : mSpellInfoMap) { - SpellInfo const* spellInfo = GetSpellInfo(i); - if (!spellInfo) + if (spellInfo.Difficulty != DIFFICULTY_NONE) continue; - for (uint8 j = 0; j < MAX_SPELL_EFFECTS; ++j) + for (SpellEffectInfo const& effect : spellInfo.GetEffects()) { - SpellEffectInfo const* effect = spellInfo->GetEffect(j); - if (!effect) - continue; - - if (effect->TargetA.GetTarget() != TARGET_DEST_DB && effect->TargetB.GetTarget() != TARGET_DEST_DB) + if (effect.TargetA.GetTarget() != TARGET_DEST_DB && effect.TargetB.GetTarget() != TARGET_DEST_DB) continue; - if (!GetSpellTargetPosition(i, SpellEffIndex(j))) - TC_LOG_DEBUG("spells", "Spell (Id: {}, EffectIndex: {}) does not have record in `spell_target_position`.", i, j); + if (!GetSpellTargetPosition(spellInfo.Id, effect.EffectIndex)) + TC_LOG_DEBUG("spells", "Spell (Id: {}, EffectIndex: {}) does not have record in `spell_target_position`.", spellInfo.Id, effect.EffectIndex); } } */ - TC_LOG_INFO("server.loading", ">> Loaded {} spell teleport coordinates in {} ms", count, GetMSTimeDiffToNow(oldMSTime)); + TC_LOG_INFO("server.loading", ">> Loaded {} spell teleport coordinates in {} ms", mSpellTargetPositions.size(), GetMSTimeDiffToNow(oldMSTime)); } void SpellMgr::LoadSpellGroups() diff --git a/src/server/game/Spells/SpellMgr.h b/src/server/game/Spells/SpellMgr.h index 9dd886a4fde48..f479e1e2a46ba 100644 --- a/src/server/game/Spells/SpellMgr.h +++ b/src/server/game/Spells/SpellMgr.h @@ -409,7 +409,7 @@ typedef std::unordered_map SpellThreatMap; // coordinates for spells (accessed using SpellMgr functions) using SpellTargetPosition = WorldLocation; -typedef std::map, SpellTargetPosition> SpellTargetPositionMap; +typedef std::multimap, SpellTargetPosition> SpellTargetPositionMap; // Enum with EffectRadiusIndex and their actual radius enum EffectRadiusIndex @@ -729,6 +729,7 @@ class TC_GAME_API SpellMgr // Spell target coordinates SpellTargetPosition const* GetSpellTargetPosition(uint32 spell_id, SpellEffIndex effIndex) const; + Trinity::IteratorPair GetSpellTargetPositions(uint32 spell_id, SpellEffIndex effIndex) const; // Spell Groups table SpellSpellGroupMapBounds GetSpellSpellGroupMapBounds(uint32 spell_id) const;