diff --git a/data/sql/updates/pending_db_world/rev_1779915128643016036.sql b/data/sql/updates/pending_db_world/rev_1779915128643016036.sql new file mode 100644 index 000000000..367827d1d --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1779915128643016036.sql @@ -0,0 +1,7 @@ +-- +DROP TABLE IF EXISTS `spell_cone`; +CREATE TABLE `spell_cone` ( + `ID` INT UNSIGNED NOT NULL COMMENT 'Spell ID', + `ConeDegrees` SMALLINT NOT NULL DEFAULT '60' COMMENT 'Cone angle in degrees', + PRIMARY KEY (`ID`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/data/sql/updates/pending_db_world/rev_1779918387451907765.sql b/data/sql/updates/pending_db_world/rev_1779918387451907765.sql new file mode 100644 index 000000000..1d87151e2 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1779918387451907765.sql @@ -0,0 +1,106 @@ +-- +DELETE FROM `spell_cone` WHERE `ID` > 0; +INSERT INTO `spell_cone` VALUES +(5708, 90), +(8374, 90), +-- (9457, 90), -- Tharnariun's Heal +(11019, 90), +(12882, 90), +(12887, 90), +-- (15529, 359), -- Gout of Flame +(15847, -120), +(16094, 90), +(16099, 90), +(16169, 90), +(16340, 90), +(16350, 90), +(16359, 90), +(16390, 90), +(16396, 120), +(18144, 90), +(18435, 120), +(18500, 120), +(19630, 120), +(19641, 120), +(19813, 180), +(20667, 90), +(20712, 120), +(20716, 90), +(20717, 90), +(21071, 120), +(21099, 120), +(21189, 90), +(21333, 180), +(22479, 90), +(22539, 120), +(22558, 90), +(22559, 90), +(22560, 90), +(22561, 90), +-- (22600, 120), -- Force Reactive Disk +(22642, 90), +-- (22682, 120), -- Shadow Flame +(22833, 120), +(22945, 120), +-- (22993, 120), -- Shadow Flame +-- (23187, 120), -- Frost Burn +-- (23189, 120), -- Frost Burn +-- (23308, 120), -- Incinerate +-- (23309, 120), -- Incinerate +-- (23310, 120), -- Time Lapse +-- (23311, 120), -- Time Lapse +-- (23312, 120), -- Time Lapse +-- (23313, 120), -- Corrosive Acid +-- (23314, 120), -- Corrosive Acid +-- (23315, 120), -- Ignite Flesh +-- (23316, 120), -- Ignite Flesh +(23339, 120), +(23364, -120), +(23461, 120), +(23618, 90), +(23919, 120), +(24192, 120), +(24682, 120), +(24818, 120), +(24821, 45), +(24822, 135), +(24823, 90), +(24835, 180), +(24836, 225), +(24837, 270), +(24838, 315), +(24839, 120), +(24933, 7), +-- (25024, 45), -- Drop Mine +-- (25026, 45), Activate MG Turret +-- (25027, 30), -- Flamethrower +(25029, 30), -- Flamethrower +(25030, 90), -- Shoot Rocket +-- (25031, 45), -- Shoot Missile +-- (25032, 10), -- Machine Gun +-- (25060, 90), -- Updraft +(25145, 90), +(25149, 90), +(25150, 90), +(25322, 90), +(25653, -120), +(25993, 120), +(26025, 180), -- Impale used by 15233 Vekniss Guardian; Knockback AoE, Bleed Cone +-- (26027, 180), -- Knockback +(26029, 5), -- Dark Glare +-- (27766, 90), -- Cannon +(27807, 90), +(28127, 90), +(28157, 45), +(28168, 120), +(28405, 180), +(28438, 180), +(28459, 90), +-- (28460, 90), -- Wail of Souls +-- (28469, 360), -- Ghost Vision +(28741, 120), +(28783, 30), +-- (29998, 180), -- Decrepit Fever +(30043, 90), +(30056, 180), +(30732, 180); diff --git a/data/sql/updates/pending_db_world/rev_1780126869002825319.sql b/data/sql/updates/pending_db_world/rev_1780126869002825319.sql new file mode 100644 index 000000000..6dd57c8f9 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1780126869002825319.sql @@ -0,0 +1,7 @@ +-- +-- 25029 Flamethrower TARGET_UNIT_CONE_ENTRY +-- 25030 Shoot Rocket TARGET_UNIT_CONE_ENTRY +DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 13) AND (`SourceEntry` IN (25029, 25030)) AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` = 31) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 3) AND (`ConditionValue2` = 15328) AND (`ConditionValue3` = 0); +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(13, 5, 25029, 0, 0, 31, 0, 3, 15328, 0, 0, 0, 0, '', 'target must be \'Steam Stonk\''), +(13, 1, 25030, 0, 0, 31, 0, 3, 15328, 0, 0, 0, 0, '', 'target must be \'Steam Stonk\''); diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index a7304d2cd..68f028a95 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -52,6 +52,7 @@ #include "World.h" #include "WorldPacket.h" #include +#include /// @todo: this import is not necessary for compilation and marked as unused by the IDE // however, for some reasons removing it would cause a damn linking issue @@ -1225,9 +1226,28 @@ void Spell::SelectImplicitConeTargets(SpellEffIndex effIndex, SpellImplicitTarge SpellTargetObjectTypes objectType = targetType.GetObjectType(); SpellTargetCheckTypes selectionType = targetType.GetCheckType(); ConditionList* condList = m_spellInfo->Effects[effIndex].ImplicitTargetConditions; - float coneAngle = M_PI / 2; - float radius = m_spellInfo->Effects[effIndex].CalcRadius(m_caster) * m_spellValue->RadiusMod; + float coneAngle = G3D::toRadians(60.0f); + if (SpellCone const* sc = sSpellMgr->GetSpellCone(m_spellInfo->Id)) + coneAngle = G3D::toRadians(static_cast(sc->cone_degrees)); + else + { + switch (targetType.GetTarget()) + { + case TARGET_UNIT_CONE_ENEMY_24: + coneAngle = G3D::toRadians(24.0f); + break; + case TARGET_UNIT_CONE_ENEMY_54: + coneAngle = G3D::toRadians(54.0f); + break; + case TARGET_UNIT_CONE_ENEMY_104: + coneAngle = G3D::toRadians(104.0f); + break; + default: + break; + } + } + float radius = m_spellInfo->Effects[effIndex].CalcRadius(m_caster) * m_spellValue->RadiusMod; if (uint32 containerTypeMask = GetSearcherTypeMask(objectType, condList)) { Acore::WorldObjectSpellConeTargetCheck check(coneAngle, radius, m_caster, m_spellInfo, selectionType, condList); diff --git a/src/server/game/Spells/SpellInfoCorrections.cpp b/src/server/game/Spells/SpellInfoCorrections.cpp index cd00baf9f..ceeb0b96d 100644 --- a/src/server/game/Spells/SpellInfoCorrections.cpp +++ b/src/server/game/Spells/SpellInfoCorrections.cpp @@ -5207,6 +5207,13 @@ void SpellMgr::LoadSpellInfoCorrections() spellInfo->Effects[EFFECT_0].BasePoints = 1; }); + // 26025 Impale + ApplySpellFix({ 26025 }, [](SpellInfo* spellInfo) + { + spellInfo->Effects[EFFECT_1].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CONE_ENEMY_54); + spellInfo->Effects[EFFECT_1].TargetB = SpellImplicitTargetInfo(0); + }); + for (uint32 i = 0; i < GetSpellInfoStoreSize(); ++i) { SpellInfo* spellInfo = mSpellInfoMap[i]; diff --git a/src/server/game/Spells/SpellMgr.cpp b/src/server/game/Spells/SpellMgr.cpp index 82de73802..43c15c686 100644 --- a/src/server/game/Spells/SpellMgr.cpp +++ b/src/server/game/Spells/SpellMgr.cpp @@ -695,6 +695,16 @@ SpellTargetPosition const* SpellMgr::GetSpellTargetPosition(uint32 spell_id, Spe return nullptr; } +SpellCone const* SpellMgr::GetSpellCone(uint32 spell_id) const +{ + spell_id = GetFirstSpellInChain(spell_id); + auto itr = mSpellCones.find(spell_id); + if (itr != mSpellCones.end()) + return &itr->second; + + return nullptr; +} + SpellSpellGroupMapBounds SpellMgr::GetSpellSpellGroupMapBounds(uint32 spell_id) const { spell_id = GetFirstSpellInChain(spell_id); @@ -1580,6 +1590,93 @@ void SpellMgr::LoadSpellTargetPositions() LOG_INFO("server.loading", " "); } +void SpellMgr::LoadSpellCones() +{ + uint32 oldMSTime = getMSTime(); + + mSpellCones.clear(); // need for reload case + + // 0 1 + QueryResult result = WorldDatabase.Query("SELECT ID, ConeDegrees FROM spell_cone"); + + if (!result) + { + LOG_WARN("server.loading", ">> Loaded 0 spell cone definitions. DB table `spell_cone` is empty."); + LOG_INFO("server.loading", " "); + return; + } + + uint32 count = 0; + do + { + Field* fields = result->Fetch(); + + uint32 spellId = fields[0].Get(); + int16 coneDeg = fields[1].Get(); + + SpellInfo const* spellInfo = GetSpellInfo(spellId); + if (!spellInfo) + { + LOG_ERROR("sql.sql", "Spell (ID:{}) listed in `spell_cone` does not exist.", spellId); + continue; + } + + if (coneDeg < -360 || coneDeg > 360) + { + LOG_ERROR("sql.sql", "Spell (Id: {}) cone degrees {} out of range (-360..360).", spellId, coneDeg); + continue; + } + + uint32 firstRankId = GetFirstSpellInChain(spellId); + SpellInfo const* firstSpellInfo = GetSpellInfo(firstRankId); + + SpellInfo const* checkInfo = firstSpellInfo ? firstSpellInfo : spellInfo; + + bool hasCone = false; + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + { + if (checkInfo->Effects[i].TargetA.GetSelectionCategory() == TARGET_SELECT_CATEGORY_CONE || + checkInfo->Effects[i].TargetB.GetSelectionCategory() == TARGET_SELECT_CATEGORY_CONE) + { + hasCone = true; + break; + } + } + + if (!hasCone) + { + LOG_ERROR("sql.sql", "Spell (ID:{}) listed in `spell_cone` does not have a cone implicit target.", spellId); + continue; + } + + if (firstRankId != spellId) + LOG_INFO("server.loading", "Spell (ID:{}) listed in `spell_cone` is not first rank; mapping to first rank {}.", spellId, firstRankId); + + SpellCone sc{}; + sc.cone_degrees = coneDeg; + + // Avoid overwriting an existing first-rank entry with conflicting values + auto itr = mSpellCones.find(firstRankId); + if (itr != mSpellCones.end()) + { + if (itr->second.cone_degrees != sc.cone_degrees) + LOG_ERROR("sql.sql", + "Conflicting `spell_cone` entries for first-rank spell ID {}: {} vs {}. Keeping first value.", + firstRankId, + itr->second.cone_degrees, + sc.cone_degrees); + } + else + { + mSpellCones[firstRankId] = sc; + ++count; + } + } while (result->NextRow()); + + LOG_INFO("server.loading", ">> Loaded {} Spell Cone definitions in {} ms", count, GetMSTimeDiffToNow(oldMSTime)); + LOG_INFO("server.loading", " "); +} + void SpellMgr::LoadSpellGroups() { uint32 oldMSTime = getMSTime(); diff --git a/src/server/game/Spells/SpellMgr.h b/src/server/game/Spells/SpellMgr.h index 32f34b030..f4004c1a9 100644 --- a/src/server/game/Spells/SpellMgr.h +++ b/src/server/game/Spells/SpellMgr.h @@ -393,6 +393,13 @@ struct SpellTargetPosition typedef std::map, SpellTargetPosition> SpellTargetPositionMap; +struct SpellCone +{ + int16_t cone_degrees; +}; + +typedef std::map SpellConeMap; + // Enum with EffectRadiusIndex and their actual radius enum EffectRadiusIndex { @@ -683,6 +690,7 @@ public: // Spell target coordinates [[nodiscard]] SpellTargetPosition const* GetSpellTargetPosition(uint32 spell_id, SpellEffIndex effIndex) const; + [[nodiscard]] SpellCone const* GetSpellCone(uint32 spell_id) const; // Spell Groups table SpellSpellGroupMapBounds GetSpellSpellGroupMapBounds(uint32 spell_id) const; @@ -775,6 +783,7 @@ public: void LoadSpellRequired(); void LoadSpellLearnSkills(); void LoadSpellTargetPositions(); + void LoadSpellCones(); void LoadSpellGroups(); void LoadSpellGroupStackRules(); void LoadSpellProcs(); @@ -807,6 +816,7 @@ private: SpellRequiredMap mSpellReq; SpellLearnSkillMap mSpellLearnSkills; SpellTargetPositionMap mSpellTargetPositions; + SpellConeMap mSpellCones; SpellSpellGroupMap mSpellSpellGroup; SpellGroupSpellMap mSpellGroupSpell; SpellGroupStackMap mSpellGroupStack; diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index 85b3871a3..e1ec20a47 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -682,6 +682,9 @@ void World::SetInitialWorldSettings() LOG_INFO("server.loading", "Loading Spell Target Coordinates..."); sSpellMgr->LoadSpellTargetPositions(); + LOG_INFO("server.loading", "Loading Spell Cone definitions..."); + sSpellMgr->LoadSpellCones(); + LOG_INFO("server.loading", "Loading Enchant Custom Attributes..."); sSpellMgr->LoadEnchantCustomAttr(); diff --git a/src/server/scripts/Commands/cs_reload.cpp b/src/server/scripts/Commands/cs_reload.cpp index ef6cf787a..8808d2bde 100644 --- a/src/server/scripts/Commands/cs_reload.cpp +++ b/src/server/scripts/Commands/cs_reload.cpp @@ -164,6 +164,7 @@ public: { "spell_proc", HandleReloadSpellProcsCommand, rbac::RBAC_PERM_COMMAND_RELOAD_SPELL_PROC, Console::Yes }, { "spell_scripts", HandleReloadSpellScriptsCommand, rbac::RBAC_PERM_COMMAND_RELOAD, Console::Yes }, { "spell_target_position", HandleReloadSpellTargetPositionCommand, rbac::RBAC_PERM_COMMAND_RELOAD_SPELL_TARGET_POSITION, Console::Yes }, + { "spell_cone", HandleReloadSpellConeCommand, rbac::RBAC_PERM_COMMAND_RELOAD_SPELL_TARGET_POSITION, Console::Yes }, { "spell_threats", HandleReloadSpellThreatsCommand, rbac::RBAC_PERM_COMMAND_RELOAD_SPELL_THREATS, Console::Yes }, { "spell_group_stack_rules", HandleReloadSpellGroupStackRulesCommand, rbac::RBAC_PERM_COMMAND_RELOAD_SPELL_GROUP_STACK_RULES, Console::Yes }, { "player_loot_template", HandleReloadLootTemplatesPlayerCommand, rbac::RBAC_PERM_COMMAND_RELOAD, Console::Yes }, @@ -936,6 +937,14 @@ public: return true; } + static bool HandleReloadSpellConeCommand(ChatHandler* handler) + { + LOG_INFO("server.loading", "Reloading Spell cone definitions..."); + sSpellMgr->LoadSpellCones(); + handler->SendGlobalGMSysMessage("DB table `spell_cone` reloaded."); + return true; + } + static bool HandleReloadSpellThreatsCommand(ChatHandler* handler) { LOG_INFO("server.loading", "Reloading Aggro Spells Definitions...");