summaryrefslogtreecommitdiff
path: root/src/server
diff options
context:
space:
mode:
Diffstat (limited to 'src/server')
-rw-r--r--src/server/apps/CMakeLists.txt19
-rw-r--r--src/server/apps/authserver/authserver.conf.dist18
-rw-r--r--src/server/apps/worldserver/Main.cpp4
-rw-r--r--src/server/apps/worldserver/worldserver.conf.dist45
-rw-r--r--src/server/database/Database/Implementation/WorldDatabase.cpp3
-rw-r--r--src/server/database/Database/Implementation/WorldDatabase.h1
-rw-r--r--src/server/database/Updater/DBUpdater.cpp18
-rw-r--r--src/server/game/AI/CoreAI/GuardAI.cpp1
-rw-r--r--src/server/game/AI/ScriptedAI/ScriptedCreature.cpp20
-rw-r--r--src/server/game/AI/ScriptedAI/ScriptedCreature.h1
-rw-r--r--src/server/game/AI/SmartScripts/SmartAI.cpp70
-rw-r--r--src/server/game/AI/SmartScripts/SmartAI.h6
-rw-r--r--src/server/game/AI/SmartScripts/SmartScript.cpp27
-rw-r--r--src/server/game/AI/SmartScripts/SmartScript.h2
-rw-r--r--src/server/game/AI/SmartScripts/SmartScriptMgr.cpp8
-rw-r--r--src/server/game/AI/SmartScripts/SmartScriptMgr.h31
-rw-r--r--src/server/game/Battlegrounds/Battleground.cpp27
-rw-r--r--src/server/game/Battlegrounds/Zones/BattlegroundEY.cpp4
-rw-r--r--src/server/game/Conditions/ConditionMgr.cpp34
-rw-r--r--src/server/game/Conditions/ConditionMgr.h3
-rw-r--r--src/server/game/Conditions/DisableMgr.cpp51
-rw-r--r--src/server/game/DungeonFinding/LFG.h8
-rw-r--r--src/server/game/DungeonFinding/LFGMgr.cpp38
-rw-r--r--src/server/game/DungeonFinding/LFGMgr.h4
-rw-r--r--src/server/game/Entities/Creature/Creature.cpp93
-rw-r--r--src/server/game/Entities/Creature/Creature.h5
-rw-r--r--src/server/game/Entities/Creature/CreatureData.h37
-rw-r--r--src/server/game/Entities/Creature/CreatureGroups.cpp9
-rw-r--r--src/server/game/Entities/Creature/CreatureGroups.h1
-rw-r--r--src/server/game/Entities/Creature/Trainer.cpp262
-rw-r--r--src/server/game/Entities/Creature/Trainer.h97
-rw-r--r--src/server/game/Entities/Item/Item.h7
-rw-r--r--src/server/game/Entities/Item/ItemTemplate.h8
-rw-r--r--src/server/game/Entities/Object/Object.cpp7
-rw-r--r--src/server/game/Entities/Object/Object.h1
-rw-r--r--src/server/game/Entities/Pet/Pet.cpp32
-rw-r--r--src/server/game/Entities/Player/KillRewarder.cpp5
-rw-r--r--src/server/game/Entities/Player/Player.cpp521
-rw-r--r--src/server/game/Entities/Player/Player.h44
-rw-r--r--src/server/game/Entities/Player/PlayerGossip.cpp17
-rw-r--r--src/server/game/Entities/Player/PlayerQuest.cpp4
-rw-r--r--src/server/game/Entities/Player/PlayerStorage.cpp107
-rw-r--r--src/server/game/Entities/Player/PlayerUpdates.cpp25
-rw-r--r--src/server/game/Entities/Unit/StatSystem.cpp134
-rw-r--r--src/server/game/Entities/Unit/Unit.cpp1045
-rw-r--r--src/server/game/Entities/Unit/Unit.h72
-rw-r--r--src/server/game/Events/GameEventMgr.cpp140
-rw-r--r--src/server/game/Events/HolidayDateCalculator.cpp582
-rw-r--r--src/server/game/Events/HolidayDateCalculator.h112
-rw-r--r--src/server/game/Globals/ObjectMgr.cpp334
-rw-r--r--src/server/game/Globals/ObjectMgr.h18
-rw-r--r--src/server/game/Handlers/ItemHandler.cpp4
-rw-r--r--src/server/game/Handlers/MiscHandler.cpp3
-rw-r--r--src/server/game/Handlers/MovementHandler.cpp68
-rw-r--r--src/server/game/Handlers/NPCHandler.cpp186
-rw-r--r--src/server/game/Handlers/PetHandler.cpp28
-rw-r--r--src/server/game/Handlers/SkillHandler.cpp2
-rw-r--r--src/server/game/Handlers/VehicleHandler.cpp5
-rw-r--r--src/server/game/Maps/AreaDefines.h1
-rw-r--r--src/server/game/Miscellaneous/Language.h7
-rw-r--r--src/server/game/Movement/MotionMaster.cpp14
-rw-r--r--src/server/game/Movement/MotionMaster.h16
-rw-r--r--src/server/game/Movement/MovementGenerators/PointMovementGenerator.cpp15
-rw-r--r--src/server/game/Movement/MovementGenerators/PointMovementGenerator.h6
-rw-r--r--src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp12
-rw-r--r--src/server/game/Movement/Spline/MoveSplineInit.h12
-rw-r--r--src/server/game/OutdoorPvP/OutdoorPvP.cpp5
-rw-r--r--src/server/game/Reputation/ReputationMgr.cpp2
-rw-r--r--src/server/game/Scripting/ScriptDefines/AllSpellScript.cpp10
-rw-r--r--src/server/game/Scripting/ScriptDefines/AllSpellScript.h6
-rw-r--r--src/server/game/Scripting/ScriptDefines/PlayerScript.cpp11
-rw-r--r--src/server/game/Scripting/ScriptDefines/PlayerScript.h8
-rw-r--r--src/server/game/Scripting/ScriptDefines/UnitScript.cpp15
-rw-r--r--src/server/game/Scripting/ScriptDefines/UnitScript.h9
-rw-r--r--src/server/game/Scripting/ScriptMgr.h10
-rw-r--r--src/server/game/Server/Packets/AllPackets.h1
-rw-r--r--src/server/game/Server/Packets/NPCPackets.cpp69
-rw-r--r--src/server/game/Server/Packets/NPCPackets.h103
-rw-r--r--src/server/game/Server/WorldSession.cpp2
-rw-r--r--src/server/game/Server/WorldSession.h13
-rw-r--r--src/server/game/Spells/Auras/SpellAuraEffects.cpp407
-rw-r--r--src/server/game/Spells/Auras/SpellAuraEffects.h5
-rw-r--r--src/server/game/Spells/Auras/SpellAuras.cpp128
-rw-r--r--src/server/game/Spells/Auras/SpellAuras.h3
-rw-r--r--src/server/game/Spells/Spell.cpp96
-rw-r--r--src/server/game/Spells/Spell.h3
-rw-r--r--src/server/game/Spells/SpellEffects.cpp36
-rw-r--r--src/server/game/Spells/SpellInfo.cpp179
-rw-r--r--src/server/game/Spells/SpellInfo.h10
-rw-r--r--src/server/game/Spells/SpellInfoCorrections.cpp41
-rw-r--r--src/server/game/Spells/SpellMgr.cpp385
-rw-r--r--src/server/game/Spells/SpellMgr.h105
-rw-r--r--src/server/game/World/World.cpp9
-rw-r--r--src/server/game/World/WorldConfig.cpp4
-rw-r--r--src/server/game/World/WorldConfig.h2
-rw-r--r--src/server/scripts/Commands/cs_npc.cpp15
-rw-r--r--src/server/scripts/Commands/cs_pooltools.cpp419
-rw-r--r--src/server/scripts/Commands/cs_quest.cpp48
-rw-r--r--src/server/scripts/Commands/cs_reload.cpp16
-rw-r--r--src/server/scripts/Commands/cs_script_loader.cpp2
-rw-r--r--src/server/scripts/EasternKingdoms/Karazhan/karazhan.cpp25
-rw-r--r--src/server/scripts/EasternKingdoms/ZulGurub/boss_arlokk.cpp10
-rw-r--r--src/server/scripts/EasternKingdoms/ZulGurub/boss_marli.cpp10
-rw-r--r--src/server/scripts/EasternKingdoms/zone_elwynn_forest.cpp18
-rw-r--r--src/server/scripts/Kalimdor/CavernsOfTime/CullingOfStratholme/culling_of_stratholme.cpp2
-rw-r--r--src/server/scripts/Northrend/AzjolNerub/AzjolNerub/azjol_nerub.h15
-rw-r--r--src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_anubarak.cpp90
-rw-r--r--src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_hadronox.cpp702
-rw-r--r--src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_krikthir_the_gatewatcher.cpp128
-rw-r--r--src/server/scripts/Northrend/AzjolNerub/AzjolNerub/instance_azjol_nerub.cpp61
-rw-r--r--src/server/scripts/Northrend/AzjolNerub/ahnkahet/ahnkahet.h1
-rw-r--r--src/server/scripts/Northrend/AzjolNerub/ahnkahet/boss_jedoga_shadowseeker.cpp38
-rw-r--r--src/server/scripts/Northrend/ChamberOfAspects/ObsidianSanctum/boss_sartharion.cpp43
-rw-r--r--src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_faction_champions.cpp2
-rw-r--r--src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_twin_valkyr.cpp2
-rw-r--r--src/server/scripts/Northrend/DraktharonKeep/boss_novos.cpp158
-rw-r--r--src/server/scripts/Northrend/DraktharonKeep/boss_tharon_ja.cpp36
-rw-r--r--src/server/scripts/Northrend/DraktharonKeep/boss_trollgore.cpp10
-rw-r--r--src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.cpp6
-rw-r--r--src/server/scripts/Northrend/Gundrak/boss_eck.cpp145
-rw-r--r--src/server/scripts/Northrend/Gundrak/boss_gal_darah.cpp6
-rw-r--r--src/server/scripts/Northrend/IcecrownCitadel/boss_the_lich_king.cpp3
-rw-r--r--src/server/scripts/Northrend/Naxxramas/boss_faerlina.cpp6
-rw-r--r--src/server/scripts/Northrend/Naxxramas/boss_four_horsemen.cpp17
-rw-r--r--src/server/scripts/Northrend/Naxxramas/boss_gothik.cpp3
-rw-r--r--src/server/scripts/Northrend/Naxxramas/boss_heigan.cpp248
-rw-r--r--src/server/scripts/Northrend/Naxxramas/boss_thaddius.cpp27
-rw-r--r--src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp57
-rw-r--r--src/server/scripts/Northrend/Naxxramas/naxxramas.h30
-rw-r--r--src/server/scripts/Northrend/Nexus/EyeOfEternity/boss_malygos.cpp18
-rw-r--r--src/server/scripts/Northrend/Nexus/Nexus/boss_anomalus.cpp45
-rw-r--r--src/server/scripts/Northrend/Nexus/Oculus/instance_oculus.cpp16
-rw-r--r--src/server/scripts/Northrend/Ulduar/HallsOfStone/boss_sjonnir.cpp357
-rw-r--r--src/server/scripts/Northrend/Ulduar/HallsOfStone/brann_bronzebeard.cpp231
-rw-r--r--src/server/scripts/Northrend/Ulduar/HallsOfStone/halls_of_stone.h1
-rw-r--r--src/server/scripts/Northrend/Ulduar/HallsOfStone/instance_halls_of_stone.cpp20
-rw-r--r--src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/utgarde_keep.cpp64
-rw-r--r--src/server/scripts/Northrend/VioletHold/violet_hold.cpp5
-rw-r--r--src/server/scripts/Northrend/VioletHold/violet_hold.h3
-rw-r--r--src/server/scripts/Northrend/zone_borean_tundra.cpp199
-rw-r--r--src/server/scripts/Northrend/zone_crystalsong_forest.cpp135
-rw-r--r--src/server/scripts/Northrend/zone_dalaran.cpp6
-rw-r--r--src/server/scripts/Northrend/zone_dragonblight.cpp6
-rw-r--r--src/server/scripts/Northrend/zone_howling_fjord.cpp237
-rw-r--r--src/server/scripts/Northrend/zone_icecrown.cpp126
-rw-r--r--src/server/scripts/Northrend/zone_storm_peaks.cpp929
-rw-r--r--src/server/scripts/OutdoorPvP/OutdoorPvPGH.cpp17
-rw-r--r--src/server/scripts/OutdoorPvP/OutdoorPvPGH.h5
-rw-r--r--src/server/scripts/OutdoorPvP/OutdoorPvPNA.cpp4
-rw-r--r--src/server/scripts/Outland/Auchindoun/SethekkHalls/boss_anzu.cpp5
-rw-r--r--src/server/scripts/Outland/Auchindoun/SethekkHalls/boss_talon_king_ikiss.cpp6
-rw-r--r--src/server/scripts/Outland/TempestKeep/Eye/boss_astromancer.cpp4
-rw-r--r--src/server/scripts/Outland/zone_terokkar_forest.cpp41
-rw-r--r--src/server/scripts/Pet/pet_generic.cpp11
-rw-r--r--src/server/scripts/Pet/pet_hunter.cpp2
-rw-r--r--src/server/scripts/Spells/spell_dk.cpp24
-rw-r--r--src/server/scripts/Spells/spell_druid.cpp11
-rw-r--r--src/server/scripts/Spells/spell_generic.cpp33
-rw-r--r--src/server/scripts/Spells/spell_paladin.cpp25
-rw-r--r--src/server/scripts/Spells/spell_quest.cpp23
-rw-r--r--src/server/scripts/Spells/spell_warlock.cpp117
-rw-r--r--src/server/scripts/World/achievement_scripts.cpp18
-rw-r--r--src/server/scripts/World/go_scripts.cpp20
-rw-r--r--src/server/scripts/World/npc_professions.cpp8
-rw-r--r--src/server/scripts/World/npcs_special.cpp59
-rw-r--r--src/server/shared/DataStores/DBCStructure.h9
-rw-r--r--src/server/shared/Network/AsyncAcceptor.h52
-rw-r--r--src/server/shared/Network/Socket.h6
-rw-r--r--src/server/shared/Network/SocketMgr.h4
-rw-r--r--src/server/shared/SharedDefines.h10
170 files changed, 6978 insertions, 4551 deletions
diff --git a/src/server/apps/CMakeLists.txt b/src/server/apps/CMakeLists.txt
index a8e32a733d..188e830931 100644
--- a/src/server/apps/CMakeLists.txt
+++ b/src/server/apps/CMakeLists.txt
@@ -178,6 +178,25 @@ foreach(APPLICATION_NAME ${APPLICATIONS_BUILD_LIST})
# Install config
CopyApplicationConfig(${APP_PROJECT_NAME} ${APPLICATION_NAME})
+# Copy config merger tool (only once, after all app targets, if enabled)
+
+if(TOOL_CONFIG_MERGER)
+ if(WIN32)
+ if("${CMAKE_MAKE_PROGRAM}" MATCHES "MSBuild")
+ foreach(cfg IN ITEMS Debug Release RelWithDebInfo MinSizeRel)
+ file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/bin/${cfg}/configs")
+ file(COPY "${CMAKE_SOURCE_DIR}/apps/config-merger/python/" DESTINATION "${CMAKE_BINARY_DIR}/bin/${cfg}/configs")
+ endforeach()
+ elseif(MINGW)
+ file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/bin/configs")
+ file(COPY "${CMAKE_SOURCE_DIR}/apps/config-merger/python/" DESTINATION "${CMAKE_BINARY_DIR}/bin/configs")
+ endif()
+ elseif(UNIX)
+ file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/bin/configs")
+ file(COPY "${CMAKE_SOURCE_DIR}/apps/config-merger/python/" DESTINATION "${CMAKE_BINARY_DIR}/bin/configs")
+ endif()
+endif()
+
if (UNIX)
install(TARGETS ${APP_PROJECT_NAME} DESTINATION bin)
elseif (WIN32)
diff --git a/src/server/apps/authserver/authserver.conf.dist b/src/server/apps/authserver/authserver.conf.dist
index 13f2f7063d..6e3deff058 100644
--- a/src/server/apps/authserver/authserver.conf.dist
+++ b/src/server/apps/authserver/authserver.conf.dist
@@ -12,6 +12,7 @@
# CRYPTOGRAPHY
# UPDATE SETTINGS
# LOGGING SYSTEM SETTINGS
+# NETWORK
#
###################################################################################################
@@ -440,3 +441,20 @@ Logger.root=4,Console Auth
#
###################################################################################################
+
+###################################################################################################
+# NETWORK
+#
+# Network.UseSocketActivation
+# Description: [LINUX ONLY FEATURE] Enable systemd socket activation support for the authserver.
+# When enabled and the process is started by systemd socket activation,
+# the server will use the socket passed by systemd instead of
+# creating and binding its own listening socket. Disabled by default.
+#
+# Example: 1 - (Enabled)
+# Default: 0 - (Disabled)
+
+Network.UseSocketActivation = 0
+
+#
+###################################################################################################
diff --git a/src/server/apps/worldserver/Main.cpp b/src/server/apps/worldserver/Main.cpp
index d1c10131b1..511c17e367 100644
--- a/src/server/apps/worldserver/Main.cpp
+++ b/src/server/apps/worldserver/Main.cpp
@@ -48,6 +48,7 @@
#include "SecretMgr.h"
#include "SharedDefines.h"
#include "SteadyTimer.h"
+#include "Systemd.h"
#include "World.h"
#include "WorldSessionMgr.h"
#include "WorldSocket.h"
@@ -406,7 +407,8 @@ int main(int argc, char** argv)
sScriptMgr->OnShutdown();
// set server offline
- LoginDatabase.DirectExecute("UPDATE realmlist SET flag = flag | {} WHERE id = '{}'", REALM_FLAG_OFFLINE, realm.Id.Realm);
+ if (!sConfigMgr->GetOption<bool>("Network.UseSocketActivation", false))
+ LoginDatabase.DirectExecute("UPDATE realmlist SET flag = flag | {} WHERE id = '{}'", REALM_FLAG_OFFLINE, realm.Id.Realm);
LOG_INFO("server.worldserver", "Halting process...");
diff --git a/src/server/apps/worldserver/worldserver.conf.dist b/src/server/apps/worldserver/worldserver.conf.dist
index 07945f07f3..c6305069d3 100644
--- a/src/server/apps/worldserver/worldserver.conf.dist
+++ b/src/server/apps/worldserver/worldserver.conf.dist
@@ -53,6 +53,7 @@
# CHARTER
# GUILD
# FFAPVP
+# OUTDOORPVP
# WINTERGRASP
# BATTLEGROUND
# ARENA
@@ -398,6 +399,20 @@ Network.TcpNodelay = 1
Network.EnableProxyProtocol = 0
#
+# Network.UseSocketActivation
+# Description: [LINUX ONLY FEATURE] Enable systemd socket activation support for the worldserver.
+# When enabled and the process is started by systemd socket activation,
+# the server will use the socket passed by systemd instead of
+# creating and binding its own listening socket. Disabled by default.
+#
+# When enabled the realm is not automatically set as offline on shutdown.
+#
+# Example: 1 - (Enabled)
+# Default: 0 - (Disabled)
+
+Network.UseSocketActivation = 0
+
+#
###################################################################################################
###################################################################################################
@@ -666,6 +681,7 @@ Appender.Server=2,5,0,Server.log,w
# Appender.GM=2,5,15,gm_%s.log
Appender.Errors=2,2,0,Errors.log,w
# Appender.DB=3,5,0
+# Appender.Dev=2,5,0,Dev.log,a
# Logger config values: Given a logger "name"
# Logger.name
@@ -790,7 +806,7 @@ Logger.spells.scripts=2,Console Errors
#Logger.spells.effect=4,Console Server
#Logger.spells.scripts=4,Console Server
#Logger.spells=4,Console Server
-#Logger.sql.dev=4,Console Server
+#Logger.sql.dev=4,Console Server Dev
#Logger.sql.driver=4,Console Server
#Logger.vehicles=4,Console Server
#Logger.warden=4,Console Server
@@ -2780,8 +2796,7 @@ QuestPOI.Enabled = 1
# Description: Level difference between player and quest level at which quests are
# considered low-level and are not shown via exclamation mark (!) at quest
# givers.
-# Default: 4 - (Enabled, Hide quests that have 4 levels less than the character)
-# -1 - (Disabled, Show all available quest marks)
+# Default: 4 - (Enabled, Hide quests that are more than 4 levels lower than the character)
Quests.LowLevelHideDiff = 4
@@ -2790,8 +2805,7 @@ Quests.LowLevelHideDiff = 4
# Description: Level difference between player and quest level at which quests are
# considered high-level and are not shown via exclamation mark (!) at quest
# givers.
-# Default: 7 - (Enabled, Hide quests that have 7 levels more than the character)
-# -1 - (Disabled, Show all available quest marks)
+# Default: 7 - (Enabled, Hide quests that are more than 7 levels higher than the character)
Quests.HighLevelHideDiff = 7
@@ -3362,6 +3376,15 @@ DungeonAccessRequirements.LFGLevelDBCOverride = 0
DungeonFinder.CastDeserter = 1
#
+# DungeonFinder.AllowCompleted
+#
+# Description: Controls whether completed heroic dungeons are excluded from LFG queue.
+# 0 - (Classic WLK mode: Dungeons completed by any group member today are excluded (daily lockout enforced))
+# Default: 1 - (Blizzlike: All dungeons are available for queue, even if already completed)
+
+DungeonFinder.AllowCompleted = 1
+
+#
###################################################################################################
###################################################################################################
@@ -3492,6 +3515,18 @@ FFAPvPTimer = 30
###################################################################################################
###################################################################################################
+# OUTDOORPVP
+#
+# OutdoorPvPCaptureRate
+# Description: Specify rate multiplier for outdoor PvP capture points. (e.g. Eastern Plaguelands, Hellfire Peninsula)
+# Default: 1.0
+
+OutdoorPvPCaptureRate = 1.0
+
+#
+###################################################################################################
+
+###################################################################################################
# WINTERGRASP
#
# Wintergrasp.Enable
diff --git a/src/server/database/Database/Implementation/WorldDatabase.cpp b/src/server/database/Database/Implementation/WorldDatabase.cpp
index fbae5ab12c..ea97d9a8c4 100644
--- a/src/server/database/Database/Implementation/WorldDatabase.cpp
+++ b/src/server/database/Database/Implementation/WorldDatabase.cpp
@@ -77,7 +77,7 @@ void WorldDatabaseConnection::DoPrepareStatements()
PrepareStatement(WORLD_SEL_WAYPOINT_SCRIPT_ID_BY_GUID, "SELECT id FROM waypoint_scripts WHERE guid = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_DEL_CREATURE, "DELETE FROM creature WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(WORLD_SEL_COMMANDS, "SELECT name, security, help FROM command", CONNECTION_SYNCH);
- PrepareStatement(WORLD_SEL_CREATURE_TEMPLATE, "SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, name, subname, IconName, gossip_menu_id, minlevel, maxlevel, exp, faction, npcflag, speed_walk, speed_run, speed_swim, speed_flight, detection_range, scale, `rank`, dmgschool, DamageModifier, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, dynamicflags, family, trainer_type, trainer_spell, trainer_class, trainer_race, type, type_flags, lootid, pickpocketloot, skinloot, PetSpellDataId, VehicleId, mingold, maxgold, AIName, MovementType, ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, ctm.Chase, ctm.Random, ctm.InteractionPauseTimer, HoverHeight, HealthModifier, ManaModifier, ArmorModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, mechanic_immune_mask, spell_school_immune_mask, flags_extra, ScriptName FROM creature_template ct LEFT JOIN creature_template_movement ctm ON ct.entry = ctm.CreatureId WHERE entry = ?", CONNECTION_SYNCH);
+ PrepareStatement(WORLD_SEL_CREATURE_TEMPLATE, "SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, name, subname, IconName, gossip_menu_id, minlevel, maxlevel, exp, faction, npcflag, speed_walk, speed_run, speed_swim, speed_flight, detection_range, scale, `rank`, dmgschool, DamageModifier, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, dynamicflags, family, type, type_flags, lootid, pickpocketloot, skinloot, PetSpellDataId, VehicleId, mingold, maxgold, AIName, MovementType, ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, ctm.Chase, ctm.Random, ctm.InteractionPauseTimer, HoverHeight, HealthModifier, ManaModifier, ArmorModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, mechanic_immune_mask, spell_school_immune_mask, flags_extra, ScriptName FROM creature_template ct LEFT JOIN creature_template_movement ctm ON ct.entry = ctm.CreatureId WHERE entry = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_WAYPOINT_SCRIPT_BY_ID, "SELECT guid, delay, command, datalong, datalong2, dataint, x, y, z, o FROM waypoint_scripts WHERE id = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_ITEM_TEMPLATE_BY_NAME, "SELECT entry FROM item_template WHERE name = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_CREATURE_BY_ID, "SELECT guid FROM creature WHERE id1 = ? OR id2 = ? OR id3 = ?", CONNECTION_SYNCH);
@@ -98,7 +98,6 @@ void WorldDatabaseConnection::DoPrepareStatements()
PrepareStatement(WORLD_SEL_GAME_EVENT_BATTLEGROUND_DATA, "SELECT eventEntry, bgflag FROM game_event_battleground_holiday", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_GAME_EVENT_POOL_DATA, "SELECT pool_template.entry, game_event_pool.eventEntry FROM pool_template JOIN game_event_pool ON pool_template.entry = game_event_pool.pool_entry", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_GAME_EVENT_ARENA_SEASON, "SELECT eventEntry FROM game_event_arena_seasons WHERE season = ?", CONNECTION_SYNCH);
- PrepareStatement(WORLD_SEL_GAME_EVENT_HOLIDAY_DATES, "SELECT id, date_id, date_value, holiday_duration FROM holiday_dates", CONNECTION_SYNCH);
PrepareStatement(WORLD_DEL_GAME_EVENT_CREATURE, "DELETE FROM game_event_creature WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(WORLD_DEL_GAME_EVENT_MODEL_EQUIP, "DELETE FROM game_event_model_equip WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(WORLD_SEL_GAME_EVENT_NPC_VENDOR, "SELECT eventEntry, guid, item, maxcount, incrtime, ExtendedCost FROM game_event_npc_vendor ORDER BY guid, slot ASC", CONNECTION_SYNCH);
diff --git a/src/server/database/Database/Implementation/WorldDatabase.h b/src/server/database/Database/Implementation/WorldDatabase.h
index e6db156be3..d80c854ead 100644
--- a/src/server/database/Database/Implementation/WorldDatabase.h
+++ b/src/server/database/Database/Implementation/WorldDatabase.h
@@ -104,7 +104,6 @@ enum WorldDatabaseStatements : uint32
WORLD_SEL_GAME_EVENT_BATTLEGROUND_DATA,
WORLD_SEL_GAME_EVENT_POOL_DATA,
WORLD_SEL_GAME_EVENT_ARENA_SEASON,
- WORLD_SEL_GAME_EVENT_HOLIDAY_DATES,
WORLD_DEL_GAME_EVENT_CREATURE,
WORLD_DEL_GAME_EVENT_MODEL_EQUIP,
WORLD_SEL_GAME_EVENT_NPC_VENDOR,
diff --git a/src/server/database/Updater/DBUpdater.cpp b/src/server/database/Updater/DBUpdater.cpp
index 0d4e45123b..4e06beb48c 100644
--- a/src/server/database/Updater/DBUpdater.cpp
+++ b/src/server/database/Updater/DBUpdater.cpp
@@ -27,6 +27,7 @@
#include <filesystem>
#include <fstream>
#include <iostream>
+#include <vector>
std::string DBUpdaterUtil::GetCorrectedMySQLExecutable()
{
@@ -396,16 +397,23 @@ bool DBUpdater<T>::Populate(DatabaseWorkerPool<T>& pool)
return false;
}
- for (std::filesystem::directory_iterator itr(DirPath); itr != DirItr; ++itr)
+ std::vector<std::filesystem::path> sqlFiles;
+
+ for (const auto &entry : std::filesystem::directory_iterator(DirPath))
{
- if (itr->path().extension() != ".sql")
- continue;
+ if (entry.path().extension() == ".sql")
+ sqlFiles.push_back(entry.path());
+ }
- LOG_INFO("sql.updates", ">> Applying \'{}\'...", itr->path().filename().generic_string());
+ std::sort(sqlFiles.begin(), sqlFiles.end());
+
+ for (const auto &file : sqlFiles)
+ {
+ LOG_INFO("sql.updates", ">> Applying \'{}\'...", file.filename().generic_string());
try
{
- ApplyFile(pool, itr->path());
+ ApplyFile(pool, file);
}
catch (UpdateException&)
{
diff --git a/src/server/game/AI/CoreAI/GuardAI.cpp b/src/server/game/AI/CoreAI/GuardAI.cpp
index 588e93ea6c..d55dc741e1 100644
--- a/src/server/game/AI/CoreAI/GuardAI.cpp
+++ b/src/server/game/AI/CoreAI/GuardAI.cpp
@@ -33,7 +33,6 @@ GuardAI::GuardAI(Creature* creature) : ScriptedAI(creature)
void GuardAI::Reset()
{
ScriptedAI::Reset();
- me->CastSpell(me, 18950 /*SPELL_INVISIBILITY_AND_STEALTH_DETECTION*/, true);
}
void GuardAI::EnterEvadeMode(EvadeReason /*why*/)
diff --git a/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp
index 762dc6e12e..522b17e41e 100644
--- a/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp
+++ b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp
@@ -15,12 +15,13 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#include "ScriptedCreature.h"
#include "Cell.h"
#include "CellImpl.h"
+#include "Containers.h"
#include "GameTime.h"
#include "GridNotifiers.h"
#include "ObjectMgr.h"
+#include "ScriptedCreature.h"
#include "Spell.h"
#include "TemporarySummon.h"
@@ -141,6 +142,23 @@ Creature* SummonList::GetCreatureWithEntry(uint32 entry) const
return nullptr;
}
+Creature* SummonList::GetRandomCreatureWithEntry(uint32 entry) const
+{
+ std::vector<ObjectGuid> candidates;
+ candidates.reserve(storage_.size());
+
+ for (auto const guid : storage_)
+ if (Creature* summon = ObjectAccessor::GetCreature(*me, guid))
+ if (summon->GetEntry() == entry)
+ candidates.push_back(guid);
+
+ if (candidates.empty())
+ return nullptr;
+
+ ObjectGuid randomGuid = Acore::Containers::SelectRandomContainerElement(candidates);
+ return ObjectAccessor::GetCreature(*me, randomGuid);
+}
+
bool SummonList::IsAnyCreatureAlive() const
{
for (auto const& guid : storage_)
diff --git a/src/server/game/AI/ScriptedAI/ScriptedCreature.h b/src/server/game/AI/ScriptedAI/ScriptedCreature.h
index 4aceb3fae5..47f8cf30c4 100644
--- a/src/server/game/AI/ScriptedAI/ScriptedCreature.h
+++ b/src/server/game/AI/ScriptedAI/ScriptedCreature.h
@@ -157,6 +157,7 @@ public:
uint32 GetEntryCount(uint32 entry) const;
void Respawn();
Creature* GetCreatureWithEntry(uint32 entry) const;
+ Creature* GetRandomCreatureWithEntry(uint32 entry) const;
private:
Creature* me;
diff --git a/src/server/game/AI/SmartScripts/SmartAI.cpp b/src/server/game/AI/SmartScripts/SmartAI.cpp
index f888403491..570f86c53d 100644
--- a/src/server/game/AI/SmartScripts/SmartAI.cpp
+++ b/src/server/game/AI/SmartScripts/SmartAI.cpp
@@ -81,6 +81,7 @@ SmartAI::SmartAI(Creature* c) : CreatureAI(c)
_currentRangeMode = false;
_attackDistance = 0.f;
+ _mainSpellId = 0;
}
bool SmartAI::IsAIControlled() const
@@ -858,7 +859,7 @@ void SmartAI::AttackStart(Unit* who)
return;
}
- if (who && me->Attack(who, me->IsWithinMeleeRange(who)))
+ if (who && me->Attack(who, me->IsWithinMeleeRange(who) || _currentRangeMode))
{
if (!me->HasUnitState(UNIT_STATE_NO_COMBAT_MOVEMENT))
{
@@ -870,7 +871,7 @@ void SmartAI::AttackStart(Unit* who)
me->GetMotionMaster()->Clear(false);
}
- me->GetMotionMaster()->MoveChase(who);
+ me->GetMotionMaster()->MoveChase(who, _attackDistance);
}
}
}
@@ -941,6 +942,35 @@ void SmartAI::PassengerBoarded(Unit* who, int8 seatId, bool apply)
void SmartAI::InitializeAI()
{
GetScript()->OnInitialize(me);
+
+ for (SmartScriptHolder const& event : GetScript()->GetEvents())
+ {
+ if (event.GetActionType() != SMART_ACTION_CAST)
+ continue;
+
+ if (!(event.action.cast.castFlags & SMARTCAST_MAIN_SPELL))
+ continue;
+
+ SetMainSpell(event.action.cast.spell);
+ break;
+ }
+
+ // Fallback: use first SMARTCAST_COMBAT_MOVE if no MAIN_SPELL found
+ if (!_currentRangeMode)
+ {
+ for (SmartScriptHolder const& event : GetScript()->GetEvents())
+ {
+ if (event.GetActionType() != SMART_ACTION_CAST)
+ continue;
+
+ if (!(event.action.cast.castFlags & SMARTCAST_COMBAT_MOVE))
+ continue;
+
+ SetMainSpell(event.action.cast.spell);
+ break;
+ }
+ }
+
if (!me->isDead())
{
mJustReset = true;
@@ -1083,6 +1113,21 @@ void SmartAI::SetCurrentRangeMode(bool on, float range)
me->GetMotionMaster()->MoveChase(victim, _attackDistance);
}
+void SmartAI::SetMainSpell(uint32 spellId)
+{
+ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
+ if (!spellInfo)
+ return;
+
+ float maxRange = spellInfo->GetMaxRange(false);
+ if (maxRange <= NOMINAL_MELEE_RANGE)
+ return;
+
+ _mainSpellId = spellId;
+ _attackDistance = std::max(maxRange - NOMINAL_MELEE_RANGE, 0.0f);
+ _currentRangeMode = true;
+}
+
void SmartAI::DistanceYourself(float range)
{
Unit* victim = me->GetVictim();
@@ -1179,6 +1224,27 @@ void SmartAI::DistancingEnded()
_pendingDistancing = 0.f;
}
+bool SmartAI::IsMainSpellPrevented(SpellInfo const* spellInfo) const
+{
+ if (me->HasSpellCooldown(spellInfo->Id))
+ return true;
+
+ if (spellInfo->PreventionType == SPELL_PREVENTION_TYPE_SILENCE && me->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SILENCED))
+ return true;
+ if (spellInfo->PreventionType == SPELL_PREVENTION_TYPE_PACIFY && me->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PACIFIED))
+ return true;
+
+ return false;
+}
+
+void SmartAI::OnSpellCastFinished(SpellInfo const* spell, SpellFinishReason reason)
+{
+ CreatureAI::OnSpellCastFinished(spell, reason);
+ if (reason == SPELL_FINISHED_CANCELED && _mainSpellId == spell->Id)
+ if (_currentRangeMode && IsMainSpellPrevented(spell))
+ SetCurrentRangeMode(false);
+}
+
void SmartGameObjectAI::SummonedCreatureDies(Creature* summon, Unit* /*killer*/)
{
GetScript()->ProcessEventsFor(SMART_EVENT_SUMMONED_UNIT_DIES, summon);
diff --git a/src/server/game/AI/SmartScripts/SmartAI.h b/src/server/game/AI/SmartScripts/SmartAI.h
index d6b957cc00..466c17020b 100644
--- a/src/server/game/AI/SmartScripts/SmartAI.h
+++ b/src/server/game/AI/SmartScripts/SmartAI.h
@@ -67,6 +67,7 @@ public:
void SetAutoAttack(bool on) { mCanAutoAttack = on; }
void SetCombatMovement(bool on, bool stopOrStartMovement);
void SetCurrentRangeMode(bool on, float range = 0.f);
+ void SetMainSpell(uint32 spellId);
void DistanceYourself(float range);
void SetFollow(Unit* target, float dist = 0.0f, float angle = 0.0f, uint32 credit = 0, uint32 end = 0, uint32 creditType = 0, bool aliveState = true);
void StopFollow(bool complete);
@@ -213,6 +214,10 @@ public:
void DistancingEnded() override;
+ bool IsMainSpellPrevented(SpellInfo const* spellInfo) const;
+
+ void OnSpellCastFinished(SpellInfo const* spell, SpellFinishReason reason) override;
+
private:
bool mIsCharmed;
uint32 mFollowCreditType;
@@ -264,6 +269,7 @@ private:
bool _currentRangeMode;
float _attackDistance;
float _pendingDistancing;
+ uint32 _mainSpellId;
};
class SmartGameObjectAI : public GameObjectAI
diff --git a/src/server/game/AI/SmartScripts/SmartScript.cpp b/src/server/game/AI/SmartScripts/SmartScript.cpp
index a9b42edeb4..d1da816d52 100644
--- a/src/server/game/AI/SmartScripts/SmartScript.cpp
+++ b/src/server/game/AI/SmartScripts/SmartScript.cpp
@@ -707,7 +707,6 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
continue;
}
- // Let us not try to cast spell if we know it is going to fail anyway. Stick to chasing and continue.
if (distanceToTarget > spellMaxRange && isWithinLOSInMap)
{
failedSpellCast = true;
@@ -745,12 +744,9 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
if (e.action.cast.castFlags & SMARTCAST_COMBAT_MOVE)
{
- // If cast flag SMARTCAST_COMBAT_MOVE is set combat movement will not be allowed unless target is outside spell range, out of mana, or LOS.
- if (result == SPELL_FAILED_OUT_OF_RANGE || result == SPELL_CAST_OK)
- // if we are just out of range, we only chase until we are back in spell range.
+ if (result == SPELL_FAILED_OUT_OF_RANGE)
CAST_AI(SmartAI, me->AI())->SetCurrentRangeMode(true, std::max(spellMaxRange - NOMINAL_MELEE_RANGE, 0.0f));
- else // move into melee on any other fail
- // if spell fail for any other reason, we chase to melee range, or stay where we are if spellcast was successful.
+ else if (result != SPELL_CAST_OK)
CAST_AI(SmartAI, me->AI())->SetCurrentRangeMode(false, 0.f);
}
@@ -2696,6 +2692,8 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
{
if (me && me->FindMap())
me->FindMap()->LoadGrid(e.target.x, e.target.y);
+ else if (go && go->FindMap())
+ go->FindMap()->LoadGrid(e.target.x, e.target.y);
break;
}
case SMART_ACTION_PLAYER_TALK:
@@ -3291,6 +3289,13 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
}
break;
}
+ case SMART_ACTION_SET_ANIM_TIER:
+ {
+ for (WorldObject* target : targets)
+ if (IsUnit(target))
+ target->ToUnit()->SetAnimTier(AnimTier(e.action.animTier.animTier));
+ break;
+ }
default:
LOG_ERROR("sql.sql", "SmartScript::ProcessAction: Entry {} SourceType {}, Event {}, Unhandled Action type {}", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType());
break;
@@ -4502,14 +4507,7 @@ void SmartScript::ProcessEvent(SmartScriptHolder& e, Unit* unit, uint32 var0, ui
if (!IsInPhase(e.event.eventPhaseChange.phasemask))
return;
- WorldObject* templastInvoker = GetLastInvoker();
- if (!templastInvoker)
- return;
-
- if (!IsUnit(templastInvoker))
- return;
-
- ProcessAction(e, templastInvoker->ToUnit());
+ ProcessAction(e);
break;
}
case SMART_EVENT_GAME_EVENT_START:
@@ -4538,6 +4536,7 @@ void SmartScript::ProcessEvent(SmartScriptHolder& e, Unit* unit, uint32 var0, ui
{
if (e.event.doAction.eventId != var0)
return;
+ RecalcTimer(e, e.event.doAction.cooldownMin, e.event.doAction.cooldownMax);
ProcessAction(e, unit, var0);
break;
}
diff --git a/src/server/game/AI/SmartScripts/SmartScript.h b/src/server/game/AI/SmartScripts/SmartScript.h
index e0f32438c8..250fdd775a 100644
--- a/src/server/game/AI/SmartScripts/SmartScript.h
+++ b/src/server/game/AI/SmartScripts/SmartScript.h
@@ -207,6 +207,8 @@ public:
void AddCreatureSummon(ObjectGuid const& guid);
void RemoveCreatureSummon(ObjectGuid const& guid);
+ SmartAIEventList const& GetEvents() const { return mEvents; }
+
private:
void IncPhase(uint32 p);
void DecPhase(uint32 p);
diff --git a/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp b/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp
index c4dab89b91..aef9617812 100644
--- a/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp
+++ b/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp
@@ -884,6 +884,7 @@ bool SmartAIMgr::CheckUnusedActionParams(SmartScriptHolder const& e)
case SMART_ACTION_MOVEMENT_RESUME: return sizeof(SmartAction::move);
case SMART_ACTION_WORLD_SCRIPT: return sizeof(SmartAction::worldStateScript);
case SMART_ACTION_DISABLE_REWARD: return sizeof(SmartAction::reward);
+ case SMART_ACTION_SET_ANIM_TIER: return sizeof(SmartAction::animTier);
case SMART_ACTION_DISMOUNT: return NO_PARAMS;
default:
LOG_WARN("sql.sql", "SmartAIMgr: entryorguid {} source_type {} id {} action_type {} is using an action with no unused params specified in SmartAIMgr::CheckUnusedActionParams(), please report this.",
@@ -1897,6 +1898,13 @@ bool SmartAIMgr::IsEventValid(SmartScriptHolder& e)
}
break;
}
+ case SMART_ACTION_SET_ANIM_TIER:
+ if (e.action.animTier.animTier >= uint32(AnimTier::Max))
+ {
+ LOG_ERROR("sql.sql", "SmartAIMgr: Entry {} SourceType {} Event {} Action {} uses invalid animtier %u, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.action.animTier.animTier);
+ return false;
+ }
+ break;
case SMART_ACTION_AUTO_ATTACK:
return IsSAIBoolValid(e, e.action.autoAttack.attack);
case SMART_ACTION_ALLOW_COMBAT_MOVEMENT:
diff --git a/src/server/game/AI/SmartScripts/SmartScriptMgr.h b/src/server/game/AI/SmartScripts/SmartScriptMgr.h
index 5e8e5e9e0c..d128196039 100644
--- a/src/server/game/AI/SmartScripts/SmartScriptMgr.h
+++ b/src/server/game/AI/SmartScripts/SmartScriptMgr.h
@@ -427,6 +427,8 @@ struct SmartEvent
struct
{
uint32 eventId;
+ uint32 cooldownMin;
+ uint32 cooldownMax;
} doAction;
struct
@@ -721,8 +723,9 @@ enum SMART_ACTION
SMART_ACTION_MOVEMENT_RESUME = 236, // timerOverride
SMART_ACTION_WORLD_SCRIPT = 237, // eventId, param
SMART_ACTION_DISABLE_REWARD = 238, // reputation 0/1, loot 0/1
+ SMART_ACTION_SET_ANIM_TIER = 239, // animtier
- SMART_ACTION_AC_END = 239, // placeholder
+ SMART_ACTION_AC_END = 240, // placeholder
};
enum class SmartActionSummonCreatureFlags
@@ -1502,6 +1505,11 @@ struct SmartAction
SAIBool reputation;
SAIBool loot;
} reward;
+
+ struct
+ {
+ uint32 animTier;
+ } animTier;
};
};
@@ -1932,16 +1940,17 @@ enum SmartEventFlags
enum SmartCastFlags
{
- SMARTCAST_INTERRUPT_PREVIOUS = 0x001, // Interrupt any spell casting
- SMARTCAST_TRIGGERED = 0x002, // Triggered (this makes spell cost zero mana and have no cast time)
- //CAST_FORCE_CAST = 0x004, // Forces cast even if creature is out of mana or out of range
- //CAST_NO_MELEE_IF_OOM = 0x008, // Prevents creature from entering melee if out of mana or out of range
- //CAST_FORCE_TARGET_SELF = 0x010, // Forces the target to cast this spell on itself
- SMARTCAST_AURA_NOT_PRESENT = 0x020, // Only casts the spell if the target does not have an aura from the spell
- SMARTCAST_COMBAT_MOVE = 0x040, // Prevents combat movement if cast successful. Allows movement on range, OOM, LOS
- SMARTCAST_THREATLIST_NOT_SINGLE = 0x080, // Only cast if the source's threatlist is higher than one. This includes pets (see Skeram's True Fulfillment)
- SMARTCAST_TARGET_POWER_MANA = 0x100, // Only cast if the target has power type mana (e.g. Mana Drain)
- SMARTCAST_ENABLE_COMBAT_MOVE_ON_LOS = 0x200,
+ SMARTCAST_INTERRUPT_PREVIOUS = 0x001, // Interrupt any spell casting
+ SMARTCAST_TRIGGERED = 0x002, // Triggered (this makes spell cost zero mana and have no cast time)
+ //CAST_FORCE_CAST = 0x004, // Forces cast even if creature is out of mana or out of range
+ //CAST_NO_MELEE_IF_OOM = 0x008, // Prevents creature from entering melee if out of mana or out of range
+ //CAST_FORCE_TARGET_SELF = 0x010, // Forces the target to cast this spell on itself
+ SMARTCAST_AURA_NOT_PRESENT = 0x020, // Only casts the spell if the target does not have an aura from the spell
+ SMARTCAST_COMBAT_MOVE = 0x040, // Prevents combat movement if cast successful. Allows movement on range, OOM, LOS
+ SMARTCAST_THREATLIST_NOT_SINGLE = 0x080, // Only cast if the source's threatlist is higher than one. This includes pets (see Skeram's True Fulfillment)
+ SMARTCAST_TARGET_POWER_MANA = 0x100, // Only cast if the target has power type mana (e.g. Mana Drain)
+ SMARTCAST_ENABLE_COMBAT_MOVE_ON_LOS = 0x200, // Allows combat movement when not in line of sight
+ SMARTCAST_MAIN_SPELL = 0x400, // Sets this spell's max range as the creature's chase distance on spawn
};
enum SmartFollowType
diff --git a/src/server/game/Battlegrounds/Battleground.cpp b/src/server/game/Battlegrounds/Battleground.cpp
index 70b8a90670..326caf426f 100644
--- a/src/server/game/Battlegrounds/Battleground.cpp
+++ b/src/server/game/Battlegrounds/Battleground.cpp
@@ -920,6 +920,33 @@ void Battleground::EndBattleground(PvPTeamId winnerTeamId)
if (!player->GetRandomWinner())
player->SetRandomWinner(true);
+
+ // Achievement 908 / 909 "Call to Arms!"
+ switch (GetBgTypeID(true))
+ {
+ case BATTLEGROUND_AB:
+ // Call to Arms: Arathi Basin
+ player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST, 11335);
+ player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST, 11339);
+ break;
+ case BATTLEGROUND_AV:
+ // Call to Arms: Alterac Valley
+ player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST, 11336);
+ player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST, 11340);
+ break;
+ case BATTLEGROUND_EY:
+ // Call to Arms: Eye of the Storm
+ player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST, 11337);
+ player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST, 11341);
+ break;
+ case BATTLEGROUND_WS:
+ // Call to Arms: Warsong Gulch
+ player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST, 11338);
+ player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST, 11342);
+ break;
+ default:
+ break;
+ }
}
player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_WIN_BG, player->GetMapId());
diff --git a/src/server/game/Battlegrounds/Zones/BattlegroundEY.cpp b/src/server/game/Battlegrounds/Zones/BattlegroundEY.cpp
index 68d057900b..da6bfb2898 100644
--- a/src/server/game/Battlegrounds/Zones/BattlegroundEY.cpp
+++ b/src/server/game/Battlegrounds/Zones/BattlegroundEY.cpp
@@ -583,8 +583,8 @@ void BattlegroundEY::FillInitialWorldStates(WorldPackets::WorldState::InitWorldS
packet.Worldstates.emplace_back(WORLD_STATE_BATTLEGROUND_EY_DRAENEI_RUINS_HORDE_CONTROL, _capturePointInfo[POINT_DRAENEI_RUINS].IsUnderControl(TEAM_HORDE));
packet.Worldstates.emplace_back(WORLD_STATE_BATTLEGROUND_EY_DRAENEI_RUINS_ALLIANCE_CONTROL, _capturePointInfo[POINT_DRAENEI_RUINS].IsUnderControl(TEAM_ALLIANCE));
packet.Worldstates.emplace_back(WORLD_STATE_BATTLEGROUND_EY_DRAENEI_RUINS_UNCONTROL, _capturePointInfo[POINT_DRAENEI_RUINS].IsUncontrolled());
- packet.Worldstates.emplace_back(WORLD_STATE_BATTLEGROUND_EY_MAGE_TOWER_ALLIANCE_CONTROL, _capturePointInfo[POINT_MAGE_TOWER].IsUnderControl(TEAM_HORDE));
- packet.Worldstates.emplace_back(WORLD_STATE_BATTLEGROUND_EY_MAGE_TOWER_HORDE_CONTROL, _capturePointInfo[POINT_MAGE_TOWER].IsUnderControl(TEAM_ALLIANCE));
+ packet.Worldstates.emplace_back(WORLD_STATE_BATTLEGROUND_EY_MAGE_TOWER_ALLIANCE_CONTROL, _capturePointInfo[POINT_MAGE_TOWER].IsUnderControl(TEAM_ALLIANCE));
+ packet.Worldstates.emplace_back(WORLD_STATE_BATTLEGROUND_EY_MAGE_TOWER_HORDE_CONTROL, _capturePointInfo[POINT_MAGE_TOWER].IsUnderControl(TEAM_HORDE));
packet.Worldstates.emplace_back(WORLD_STATE_BATTLEGROUND_EY_MAGE_TOWER_UNCONTROL, _capturePointInfo[POINT_MAGE_TOWER].IsUncontrolled());
packet.Worldstates.emplace_back(WORLD_STATE_BATTLEGROUND_EY_FEL_REAVER_HORDE_CONTROL, _capturePointInfo[POINT_FEL_REAVER].IsUnderControl(TEAM_HORDE));
packet.Worldstates.emplace_back(WORLD_STATE_BATTLEGROUND_EY_FEL_REAVER_ALLIANCE_CONTROL, _capturePointInfo[POINT_FEL_REAVER].IsUnderControl(TEAM_ALLIANCE));
diff --git a/src/server/game/Conditions/ConditionMgr.cpp b/src/server/game/Conditions/ConditionMgr.cpp
index 1d1bbec23f..add31c1b18 100644
--- a/src/server/game/Conditions/ConditionMgr.cpp
+++ b/src/server/game/Conditions/ConditionMgr.cpp
@@ -542,6 +542,23 @@ bool Condition::Meets(ConditionSourceInfo& sourceInfo)
condMeets = object->GetMap()->GetDifficulty() == ConditionValue1;
break;
}
+ case CONDITION_RANDOM_DUNGEON:
+ {
+ if (Unit* unit = object->ToUnit())
+ {
+ if (Player* player = unit->GetCharmerOrOwnerPlayerOrPlayerItself())
+ {
+ if (sLFGMgr->selectedRandomLfgDungeon(player->GetGUID()))
+ {
+ if (!ConditionValue1)
+ condMeets = true;
+ else if (Map* map = player->GetMap())
+ condMeets = map->GetDifficulty() == Difficulty(ConditionValue2);
+ }
+ }
+ }
+ break;
+ }
case CONDITION_PET_TYPE:
{
if (Unit* unit = object->ToUnit())
@@ -786,6 +803,9 @@ uint32 Condition::GetSearcherTypeMaskForCondition()
case CONDITION_CHARMED:
mask |= GRID_MAP_TYPE_MASK_CREATURE | GRID_MAP_TYPE_MASK_PLAYER;
break;
+ case CONDITION_RANDOM_DUNGEON:
+ mask |= GRID_MAP_TYPE_MASK_PLAYER;
+ break;
case CONDITION_WORLD_SCRIPT:
mask |= GRID_MAP_TYPE_MASK_ALL;
break;
@@ -2466,6 +2486,20 @@ bool ConditionMgr::isConditionTypeValid(Condition* cond)
return false;
}
break;
+ case CONDITION_RANDOM_DUNGEON:
+ if (cond->ConditionValue1 > 1)
+ {
+ LOG_ERROR("sql.sql", "RandomDungeon condition has useless data in value1 ({}).", cond->ConditionValue1);
+ return false;
+ }
+ if (cond->ConditionValue2 >= MAX_DIFFICULTY)
+ {
+ LOG_ERROR("sql.sql", "RandomDungeon condition has invalid difficulty in value2 ({}).", cond->ConditionValue1);
+ return false;
+ }
+ if (cond->ConditionValue3)
+ LOG_ERROR("sql.sql", "RandomDungeon condition has useless data in value3 ({}).", cond->ConditionValue3);
+ break;
case CONDITION_PET_TYPE:
if (cond->ConditionValue1 >= (1 << MAX_PET_TYPE))
{
diff --git a/src/server/game/Conditions/ConditionMgr.h b/src/server/game/Conditions/ConditionMgr.h
index d8a29e4d1d..c472451a3f 100644
--- a/src/server/game/Conditions/ConditionMgr.h
+++ b/src/server/game/Conditions/ConditionMgr.h
@@ -88,8 +88,9 @@ enum ConditionTypes
CONDITION_HAS_AURA_TYPE = 102, // aura_type 0 0 true if has aura type
CONDITION_WORLD_SCRIPT = 103, // conditionId state 0 true if WorldState::IsConditionFulfilled returns true
CONDITION_AI_DATA = 104, // dataId value 0 true if AI::GetData returns value
+ CONDITION_RANDOM_DUNGEON = 105, // difficulty (0 = any) 0 0 true if player is queued for a random dungeon via RDF (param1 = Difficulty)
- CONDITION_AC_END = 105 // placeholder
+ CONDITION_AC_END = 106 // placeholder
};
/*! Documentation on implementing a new ConditionSourceType:
diff --git a/src/server/game/Conditions/DisableMgr.cpp b/src/server/game/Conditions/DisableMgr.cpp
index 234ddfe5a2..c02c7c6819 100644
--- a/src/server/game/Conditions/DisableMgr.cpp
+++ b/src/server/game/Conditions/DisableMgr.cpp
@@ -388,30 +388,39 @@ bool DisableMgr::IsDisabledFor(DisableType type, uint32 entry, Unit const* unit,
}
case DISABLE_TYPE_MAP:
case DISABLE_TYPE_LFG_MAP:
- if (Player const* player = unit->ToPlayer())
+ {
+ MapEntry const* mapEntry = sMapStore.LookupEntry(entry);
+ if (!mapEntry)
+ return false;
+
+ if (!mapEntry->IsDungeon())
+ return mapEntry->map_type == MAP_COMMON;
+
+ uint8 disabledModes = itr->second.flags;
+
+ Difficulty targetDifficulty;
+ if (unit && unit->IsPlayer())
+ targetDifficulty = unit->ToPlayer()->GetDifficulty(mapEntry->IsRaid());
+ else
+ targetDifficulty = Difficulty(flags);
+
+ GetDownscaledMapDifficultyData(entry, targetDifficulty);
+
+ switch (targetDifficulty)
{
- MapEntry const* mapEntry = sMapStore.LookupEntry(entry);
- if (mapEntry->IsDungeon())
- {
- uint8 disabledModes = itr->second.flags;
- Difficulty targetDifficulty = player->GetDifficulty(mapEntry->IsRaid());
- GetDownscaledMapDifficultyData(entry, targetDifficulty);
- switch (targetDifficulty)
- {
- case DUNGEON_DIFFICULTY_NORMAL:
- return disabledModes & DUNGEON_STATUSFLAG_NORMAL;
- case DUNGEON_DIFFICULTY_HEROIC:
- return disabledModes & DUNGEON_STATUSFLAG_HEROIC;
- case RAID_DIFFICULTY_10MAN_HEROIC:
- return disabledModes & RAID_STATUSFLAG_10MAN_HEROIC;
- case RAID_DIFFICULTY_25MAN_HEROIC:
- return disabledModes & RAID_STATUSFLAG_25MAN_HEROIC;
- }
- }
- else if (mapEntry->map_type == MAP_COMMON)
- return true;
+ case DUNGEON_DIFFICULTY_NORMAL:
+ return disabledModes & DUNGEON_STATUSFLAG_NORMAL;
+ case DUNGEON_DIFFICULTY_HEROIC:
+ return disabledModes & DUNGEON_STATUSFLAG_HEROIC;
+ case RAID_DIFFICULTY_10MAN_HEROIC:
+ return disabledModes & RAID_STATUSFLAG_10MAN_HEROIC;
+ case RAID_DIFFICULTY_25MAN_HEROIC:
+ return disabledModes & RAID_STATUSFLAG_25MAN_HEROIC;
+ default:
+ return false;
}
return false;
+ }
case DISABLE_TYPE_VMAP:
return flags & itr->second.flags;
case DISABLE_TYPE_QUEST:
diff --git a/src/server/game/DungeonFinding/LFG.h b/src/server/game/DungeonFinding/LFG.h
index 4ba61eefef..260f29f089 100644
--- a/src/server/game/DungeonFinding/LFG.h
+++ b/src/server/game/DungeonFinding/LFG.h
@@ -100,6 +100,14 @@ namespace lfg
LFG_ANSWER_AGREE = 1
};
+ enum LfgRandomDungeonIds : uint32
+ {
+ RANDOM_DUNGEON_NORMAL_TBC = 259,
+ RANDOM_DUNGEON_HEROIC_TBC = 260,
+ RANDOM_DUNGEON_NORMAL_WOTLK = 261,
+ RANDOM_DUNGEON_HEROIC_WOTLK = 262
+ };
+
class Lfg5Guids;
typedef std::list<Lfg5Guids> Lfg5GuidsList;
diff --git a/src/server/game/DungeonFinding/LFGMgr.cpp b/src/server/game/DungeonFinding/LFGMgr.cpp
index b88d04c65e..9e097ab9b5 100644
--- a/src/server/game/DungeonFinding/LFGMgr.cpp
+++ b/src/server/game/DungeonFinding/LFGMgr.cpp
@@ -408,11 +408,10 @@ namespace lfg
DungeonProgressionRequirements const* ar = sObjectMgr->GetAccessRequirement(dungeon->map, Difficulty(dungeon->difficulty));
uint32 lockData = 0;
+
if (dungeon->expansion > expansion || (onlySeasonalBosses && !dungeon->seasonal))
lockData = LFG_LOCKSTATUS_INSUFFICIENT_EXPANSION;
- else if (sDisableMgr->IsDisabledFor(DISABLE_TYPE_MAP, dungeon->map, player))
- lockData = LFG_LOCKSTATUS_RAID_LOCKED;
- else if (sDisableMgr->IsDisabledFor(DISABLE_TYPE_LFG_MAP, dungeon->map, player))
+ else if (IsDungeonDisabled(dungeon->map, dungeon->difficulty))
lockData = LFG_LOCKSTATUS_RAID_LOCKED;
else if (dungeon->difficulty > DUNGEON_DIFFICULTY_NORMAL && (!mapEntry || !mapEntry->IsRaid()) && sInstanceSaveMgr->PlayerIsPermBoundToInstance(player->GetGUID(), dungeon->map, Difficulty(dungeon->difficulty)))
lockData = LFG_LOCKSTATUS_RAID_LOCKED;
@@ -688,7 +687,7 @@ namespace lfg
// xinef: dont check compatibile dungeons for already running group (bind problems)
if (!isContinue)
{
- GetCompatibleDungeons(dungeons, players, joinData.lockmap);
+ GetCompatibleDungeons(dungeons, players, joinData.lockmap, rDungeonId);
if (dungeons.empty())
joinData.result = grp ? LFG_JOIN_PARTY_NOT_MEET_REQS : LFG_JOIN_NOT_MEET_REQS;
}
@@ -1484,8 +1483,9 @@ namespace lfg
@param[in, out] dungeons Dungeons to check restrictions
@param[in] players Set of players to check their dungeon restrictions
@param[out] lockMap Map of players Lock status info of given dungeons (Empty if dungeons is not empty)
+ @param[in] randomDungeonId Random dungeon ID (0 for non-random selections), used to filter disabled maps
*/
- void LFGMgr::GetCompatibleDungeons(LfgDungeonSet& dungeons, LfgGuidSet const& players, LfgLockPartyMap& lockMap)
+ void LFGMgr::GetCompatibleDungeons(LfgDungeonSet& dungeons, LfgGuidSet const& players, LfgLockPartyMap& lockMap, uint32 randomDungeonId)
{
lockMap.clear();
for (LfgGuidSet::const_iterator it = players.begin(); it != players.end() && !dungeons.empty(); ++it)
@@ -1496,6 +1496,13 @@ namespace lfg
{
uint32 dungeonId = (it2->first & 0x00FFFFFF); // Compare dungeon ids
+ LFGDungeonData const* dungeon = GetLFGDungeon(dungeonId);
+
+ uint8 difficultyFlag = (randomDungeonId == RANDOM_DUNGEON_NORMAL_TBC || randomDungeonId == RANDOM_DUNGEON_NORMAL_WOTLK) ? 0 : 1;
+
+ if (dungeon && !IsDungeonDisabled(dungeon->map, (Difficulty)difficultyFlag) && it2->second == LFG_LOCKSTATUS_RAID_LOCKED && randomDungeonId && sWorld->getBoolConfig(CONFIG_LFG_ALLOW_COMPLETED))
+ continue;
+
LfgDungeonSet::iterator itDungeon = dungeons.find(dungeonId);
if (itDungeon != dungeons.end())
{
@@ -1621,6 +1628,7 @@ namespace lfg
}
ObjectGuid oldGroupGUID;
+ bool hasRandomLfgMember = proposal.group.IsEmpty();
for (LfgGuidList::const_iterator it = players.begin(); it != players.end(); ++it)
{
ObjectGuid pguid = (*it);
@@ -1637,8 +1645,16 @@ namespace lfg
SetState(grp->GetGUID(), LFG_STATE_PROPOSAL);
}
+ if (auto const proposalPlayer = proposal.players.find(pguid); proposalPlayer != proposal.players.end())
+ {
+ if (!hasRandomLfgMember && (proposalPlayer->second.group.IsEmpty() || proposalPlayer->second.group != proposal.group))
+ hasRandomLfgMember = true;
+ }
+ else
+ hasRandomLfgMember = true;
+
// Xinef: Apply Random Buff
- if (grp && !grp->IsLfgWithBuff())
+ if (grp && !grp->IsLfgWithBuff() && hasRandomLfgMember)
{
if (!group || group->GetGUID() != oldGroupGUID)
grp->AddLfgBuffFlag();
@@ -1762,10 +1778,8 @@ namespace lfg
else
{
// RDF removes all binds to that map
- if (randomDungeon && !sInstanceSaveMgr->PlayerIsPermBoundToInstance(player->GetGUID(), dungeon->map, player->GetDungeonDifficulty()))
- {
+ if (randomDungeon)
sInstanceSaveMgr->PlayerUnbindInstance(player->GetGUID(), dungeon->map, player->GetDungeonDifficulty(), true);
- }
}
playersTeleported.push_back(player);
@@ -2819,4 +2833,10 @@ namespace lfg
return randomDungeons;
}
+ bool LFGMgr::IsDungeonDisabled(uint32 mapId, Difficulty difficulty) const
+ {
+ return sDisableMgr->IsDisabledFor(DISABLE_TYPE_MAP, mapId, nullptr, difficulty) ||
+ sDisableMgr->IsDisabledFor(DISABLE_TYPE_LFG_MAP, mapId, nullptr);
+ }
+
} // namespace lfg
diff --git a/src/server/game/DungeonFinding/LFGMgr.h b/src/server/game/DungeonFinding/LFGMgr.h
index 426f0c679a..c96ae4f2a4 100644
--- a/src/server/game/DungeonFinding/LFGMgr.h
+++ b/src/server/game/DungeonFinding/LFGMgr.h
@@ -518,6 +518,8 @@ namespace lfg
LfgUpdateData GetLfgStatus(ObjectGuid guid);
/// Checks if Seasonal dungeon is active
bool IsSeasonActive(uint32 dungeonId);
+ /// Checks if given dungeon map is disabled
+ bool IsDungeonDisabled(uint32 mapId, Difficulty difficulty) const;
/// Gets the random dungeon reward corresponding to given dungeon and player level
LfgReward const* GetRandomDungeonReward(uint32 dungeon, uint8 level);
/// Returns all random and seasonal dungeons for given level and expansion
@@ -589,7 +591,7 @@ namespace lfg
void DecreaseKicksLeft(ObjectGuid guid);
void SetState(ObjectGuid guid, LfgState state);
void SetCanOverrideRBState(ObjectGuid guid, bool val);
- void GetCompatibleDungeons(LfgDungeonSet& dungeons, LfgGuidSet const& players, LfgLockPartyMap& lockMap);
+ void GetCompatibleDungeons(LfgDungeonSet& dungeons, LfgGuidSet const& players, LfgLockPartyMap& lockMap, uint32 randomDungeonId = 0);
void _SaveToDB(ObjectGuid guid);
LFGDungeonData const* GetLFGDungeon(uint32 id);
diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp
index 370e4c07df..4a8a98354a 100644
--- a/src/server/game/Entities/Creature/Creature.cpp
+++ b/src/server/game/Entities/Creature/Creature.cpp
@@ -38,6 +38,7 @@
#include "PoolMgr.h"
#include "ScriptMgr.h"
#include "ScriptedGossip.h"
+#include "SpellAuraDefines.h"
#include "SpellAuraEffects.h"
#include "SpellMgr.h"
#include "TemporarySummon.h"
@@ -80,15 +81,6 @@ std::string CreatureMovementData::ToString() const
return str.str();
}
-TrainerSpell const* TrainerSpellData::Find(uint32 spell_id) const
-{
- TrainerSpellMap::const_iterator itr = spellList.find(spell_id);
- if (itr != spellList.end())
- return &itr->second;
-
- return nullptr;
-}
-
bool VendorItemData::RemoveItem(uint32 item_id)
{
bool found = false;
@@ -601,13 +593,13 @@ bool Creature::UpdateEntry(uint32 Entry, const CreatureData* data, bool changele
SetMeleeDamageSchool(SpellSchools(cInfo->dmgschool));
CreatureBaseStats const* stats = sObjectMgr->GetCreatureBaseStats(GetLevel(), cInfo->unit_class);
float armor = stats->GenerateArmor(cInfo);
- SetModifierValue(UNIT_MOD_ARMOR, BASE_VALUE, armor);
- SetModifierValue(UNIT_MOD_RESISTANCE_HOLY, BASE_VALUE, float(cInfo->resistance[SPELL_SCHOOL_HOLY]));
- SetModifierValue(UNIT_MOD_RESISTANCE_FIRE, BASE_VALUE, float(cInfo->resistance[SPELL_SCHOOL_FIRE]));
- SetModifierValue(UNIT_MOD_RESISTANCE_NATURE, BASE_VALUE, float(cInfo->resistance[SPELL_SCHOOL_NATURE]));
- SetModifierValue(UNIT_MOD_RESISTANCE_FROST, BASE_VALUE, float(cInfo->resistance[SPELL_SCHOOL_FROST]));
- SetModifierValue(UNIT_MOD_RESISTANCE_SHADOW, BASE_VALUE, float(cInfo->resistance[SPELL_SCHOOL_SHADOW]));
- SetModifierValue(UNIT_MOD_RESISTANCE_ARCANE, BASE_VALUE, float(cInfo->resistance[SPELL_SCHOOL_ARCANE]));
+ SetStatFlatModifier(UNIT_MOD_ARMOR, BASE_VALUE, armor);
+ SetStatFlatModifier(UNIT_MOD_RESISTANCE_HOLY, BASE_VALUE, float(cInfo->resistance[SPELL_SCHOOL_HOLY]));
+ SetStatFlatModifier(UNIT_MOD_RESISTANCE_FIRE, BASE_VALUE, float(cInfo->resistance[SPELL_SCHOOL_FIRE]));
+ SetStatFlatModifier(UNIT_MOD_RESISTANCE_NATURE, BASE_VALUE, float(cInfo->resistance[SPELL_SCHOOL_NATURE]));
+ SetStatFlatModifier(UNIT_MOD_RESISTANCE_FROST, BASE_VALUE, float(cInfo->resistance[SPELL_SCHOOL_FROST]));
+ SetStatFlatModifier(UNIT_MOD_RESISTANCE_SHADOW, BASE_VALUE, float(cInfo->resistance[SPELL_SCHOOL_SHADOW]));
+ SetStatFlatModifier(UNIT_MOD_RESISTANCE_ARCANE, BASE_VALUE, float(cInfo->resistance[SPELL_SCHOOL_ARCANE]));
SetCanModifyStats(true);
UpdateAllStats();
@@ -1017,10 +1009,7 @@ void Creature::Regenerate(Powers power)
}
// Apply modifiers (if any).
- AuraEffectList const& ModPowerRegenPCTAuras = GetAuraEffectsByType(SPELL_AURA_MOD_POWER_REGEN_PERCENT);
- for (AuraEffectList::const_iterator i = ModPowerRegenPCTAuras.begin(); i != ModPowerRegenPCTAuras.end(); ++i)
- if (Powers((*i)->GetMiscValue()) == power)
- AddPct(addvalue, (*i)->GetAmount());
+ addvalue *= GetTotalAuraMultiplierByMiscValue(SPELL_AURA_MOD_POWER_REGEN_PERCENT, power);
addvalue += GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_POWER_REGEN, power) * (power == POWER_FOCUS ? PET_FOCUS_REGEN_INTERVAL.count() : CREATURE_REGEN_INTERVAL) / (5 * IN_MILLISECONDS);
@@ -1056,9 +1045,7 @@ void Creature::RegenerateHealth()
}
// Apply modifiers (if any).
- AuraEffectList const& ModPowerRegenPCTAuras = GetAuraEffectsByType(SPELL_AURA_MOD_HEALTH_REGEN_PERCENT);
- for (AuraEffectList::const_iterator i = ModPowerRegenPCTAuras.begin(); i != ModPowerRegenPCTAuras.end(); ++i)
- AddPct(addvalue, (*i)->GetAmount());
+ addvalue *= GetTotalAuraMultiplier(SPELL_AURA_MOD_HEALTH_REGEN_PERCENT);
addvalue += GetTotalAuraModifier(SPELL_AURA_MOD_REGEN) * CREATURE_REGEN_INTERVAL / (5 * IN_MILLISECONDS);
@@ -1268,52 +1255,13 @@ bool Creature::isCanInteractWithBattleMaster(Player* player, bool msg) const
return true;
}
-bool Creature::isCanTrainingAndResetTalentsOf(Player* player) const
+bool Creature::CanResetTalents(Player* player) const
{
- return player->GetLevel() >= 10
- && GetCreatureTemplate()->trainer_type == TRAINER_TYPE_CLASS
- && player->IsClass((Classes)GetCreatureTemplate()->trainer_class, CLASS_CONTEXT_CLASS_TRAINER);
-}
-
-bool Creature::IsValidTrainerForPlayer(Player* player, uint32* npcFlags /*= nullptr*/) const
-{
- if (!IsTrainer())
- {
+ Trainer::Trainer const* trainer = sObjectMgr->GetTrainer(GetEntry());
+ if (!trainer)
return false;
- }
-
- switch (m_creatureInfo->trainer_type)
- {
- case TRAINER_TYPE_CLASS:
- case TRAINER_TYPE_PETS:
- if (m_creatureInfo->trainer_class && !player->IsClass((Classes)m_creatureInfo->trainer_class, CLASS_CONTEXT_CLASS_TRAINER))
- {
- if (npcFlags)
- *npcFlags &= ~UNIT_NPC_FLAG_TRAINER_CLASS;
- return false;
- }
- break;
- case TRAINER_TYPE_MOUNTS:
- if (m_creatureInfo->trainer_race && m_creatureInfo->trainer_race != player->getRace())
- {
- return false;
- }
- break;
- case TRAINER_TYPE_TRADESKILLS:
- if (m_creatureInfo->trainer_spell && !player->HasSpell(m_creatureInfo->trainer_spell))
- {
- if (npcFlags)
- *npcFlags &= ~UNIT_NPC_FLAG_TRAINER_PROFESSION;
-
- return false;
- }
- break;
- default:
- break;
- }
-
- return true;
+ return player->GetLevel() >= 10 && trainer->IsTrainerValidForPlayer(player);
}
Player* Creature::GetLootRecipient() const
@@ -1558,8 +1506,8 @@ void Creature::SelectLevel(bool changelevel)
/// @todo: set UNIT_FIELD_POWER*, for some creature class case (energy, etc)
- SetModifierValue(UNIT_MOD_HEALTH, BASE_VALUE, (float)health);
- SetModifierValue(UNIT_MOD_MANA, BASE_VALUE, (float)mana);
+ SetStatFlatModifier(UNIT_MOD_HEALTH, BASE_VALUE, (float)health);
+ SetStatFlatModifier(UNIT_MOD_MANA, BASE_VALUE, (float)mana);
// damage
@@ -1577,8 +1525,8 @@ void Creature::SelectLevel(bool changelevel)
SetBaseWeaponDamage(RANGED_ATTACK, MINDAMAGE, weaponBaseMinDamage);
SetBaseWeaponDamage(RANGED_ATTACK, MAXDAMAGE, weaponBaseMaxDamage);
- SetModifierValue(UNIT_MOD_ATTACK_POWER, BASE_VALUE, stats->AttackPower);
- SetModifierValue(UNIT_MOD_ATTACK_POWER_RANGED, BASE_VALUE, stats->RangedAttackPower);
+ SetStatFlatModifier(UNIT_MOD_ATTACK_POWER, BASE_VALUE, stats->AttackPower);
+ SetStatFlatModifier(UNIT_MOD_ATTACK_POWER_RANGED, BASE_VALUE, stats->RangedAttackPower);
sScriptMgr->OnCreatureSelectLevel(cInfo, this);
}
@@ -3147,11 +3095,6 @@ uint32 Creature::UpdateVendorItemCurrentCount(VendorItem const* vItem, uint32 us
return vCount->count;
}
-TrainerSpellData const* Creature::GetTrainerSpells() const
-{
- return sObjectMgr->GetNpcTrainerSpells(GetEntry());
-}
-
// overwrite WorldObject function for proper name localization
std::string const& Creature::GetNameForLocaleIdx(LocaleConstant loc_idx) const
{
diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h
index 11db152ac4..67d07b26bd 100644
--- a/src/server/game/Entities/Creature/Creature.h
+++ b/src/server/game/Entities/Creature/Creature.h
@@ -103,8 +103,7 @@ public:
///// @todo RENAME THIS!!!!!
bool isCanInteractWithBattleMaster(Player* player, bool msg) const;
- bool isCanTrainingAndResetTalentsOf(Player* player) const;
- [[nodiscard]] bool IsValidTrainerForPlayer(Player* player, uint32* npcFlags = nullptr) const;
+ bool CanResetTalents(Player* player) const;
bool CanCreatureAttack(Unit const* victim, bool skipDistCheck = false) const;
void LoadSpellTemplateImmunity();
bool IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell = nullptr) override;
@@ -203,8 +202,6 @@ public:
uint32 GetVendorItemCurrentCount(VendorItem const* vItem);
uint32 UpdateVendorItemCurrentCount(VendorItem const* vItem, uint32 used_count);
- [[nodiscard]] TrainerSpellData const* GetTrainerSpells() const;
-
[[nodiscard]] CreatureTemplate const* GetCreatureTemplate() const { return m_creatureInfo; }
[[nodiscard]] CreatureData const* GetCreatureData() const { return m_creatureData; }
void SetDetectionDistance(float dist){ m_detectionDistance = dist; }
diff --git a/src/server/game/Entities/Creature/CreatureData.h b/src/server/game/Entities/Creature/CreatureData.h
index 2c8b4eb22a..f1f3d6f02e 100644
--- a/src/server/game/Entities/Creature/CreatureData.h
+++ b/src/server/game/Entities/Creature/CreatureData.h
@@ -215,10 +215,6 @@ struct CreatureTemplate
uint32 unit_flags2; // enum UnitFlags2 mask values
uint32 dynamicflags;
uint32 family; // enum CreatureFamily values (optional)
- uint32 trainer_type;
- uint32 trainer_spell;
- uint32 trainer_class;
- uint32 trainer_race;
uint32 type; // enum CreatureType values
uint32 type_flags; // enum CreatureTypeFlags mask values
uint32 lootid;
@@ -503,39 +499,6 @@ struct VendorItemCount
typedef std::list<VendorItemCount> VendorItemCounts;
-struct TrainerSpell
-{
- TrainerSpell()
- {
- for (unsigned int & i : learnedSpell)
- i = 0;
- }
-
- uint32 spell{0};
- uint32 spellCost{0};
- uint32 reqSkill{0};
- uint32 reqSkillValue{0};
- uint32 reqLevel{0};
- uint32 learnedSpell[3];
- uint32 reqSpell{0};
-
- // helpers
- [[nodiscard]] bool IsCastable() const { return learnedSpell[0] != spell; }
-};
-
-typedef std::unordered_map<uint32 /*spellid*/, TrainerSpell> TrainerSpellMap;
-
-struct TrainerSpellData
-{
- TrainerSpellData() = default;
- ~TrainerSpellData() { spellList.clear(); }
-
- TrainerSpellMap spellList;
- uint32 trainerType{0}; // trainer type based at trainer spells, can be different from creature_template value.
- // req. for correct show non-prof. trainers like weaponmaster, allowed values 0 and 2.
- [[nodiscard]] TrainerSpell const* Find(uint32 spell_id) const;
-};
-
struct CreatureSpellCooldown
{
CreatureSpellCooldown() = default;
diff --git a/src/server/game/Entities/Creature/CreatureGroups.cpp b/src/server/game/Entities/Creature/CreatureGroups.cpp
index 8afb585e09..b26ef4d9a3 100644
--- a/src/server/game/Entities/Creature/CreatureGroups.cpp
+++ b/src/server/game/Entities/Creature/CreatureGroups.cpp
@@ -417,6 +417,15 @@ void CreatureGroup::LeaderMoveTo(float x, float y, float z, uint32 move_type)
}
}
+void CreatureGroup::DespawnFormation(Milliseconds timeToDespawn /*=0ms*/, Seconds forcedRespawnTimer /*=0s*/)
+{
+ for (auto const& itr : m_members)
+ {
+ if (itr.first)
+ itr.first->DespawnOrUnsummon(timeToDespawn, forcedRespawnTimer);
+ }
+}
+
void CreatureGroup::RespawnFormation(bool force)
{
for (auto const& itr : m_members)
diff --git a/src/server/game/Entities/Creature/CreatureGroups.h b/src/server/game/Entities/Creature/CreatureGroups.h
index 3e52ad00ed..0ce3a7c90d 100644
--- a/src/server/game/Entities/Creature/CreatureGroups.h
+++ b/src/server/game/Entities/Creature/CreatureGroups.h
@@ -112,6 +112,7 @@ public:
void MemberEngagingTarget(Creature* member, Unit* target);
Unit* GetNewTargetForMember(Creature* member);
void MemberEvaded(Creature* member);
+ void DespawnFormation(Milliseconds timeToDespawn = 0ms, Seconds forcedRespawnTimer = 0s);
void RespawnFormation(bool force = false);
[[nodiscard]] bool IsFormationInCombat();
[[nodiscard]] bool IsAnyMemberAlive(bool ignoreLeader = false);
diff --git a/src/server/game/Entities/Creature/Trainer.cpp b/src/server/game/Entities/Creature/Trainer.cpp
new file mode 100644
index 0000000000..c9d3d193c4
--- /dev/null
+++ b/src/server/game/Entities/Creature/Trainer.cpp
@@ -0,0 +1,262 @@
+/*
+ * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by the
+ * Free Software Foundation; either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "Trainer.h"
+#include "Creature.h"
+#include "NPCPackets.h"
+#include "Player.h"
+#include "SpellInfo.h"
+#include "SpellMgr.h"
+
+namespace Trainer
+{
+ bool Spell::IsCastable() const
+ {
+ return sSpellMgr->AssertSpellInfo(SpellId)->HasEffect(SPELL_EFFECT_LEARN_SPELL);
+ }
+
+ Trainer::Trainer(uint32 trainerId, Type type, uint32 requirement, std::string greeting, std::vector<Spell> spells) : _trainerId(trainerId), _type(type), _requirement(requirement), _spells(std::move(spells))
+ {
+ _greeting[DEFAULT_LOCALE] = std::move(greeting);
+ }
+
+ void Trainer::SendSpells(Creature* npc, Player* player, LocaleConstant locale) const
+ {
+ float reputationDiscount = player->GetReputationPriceDiscount(npc);
+
+ WorldPackets::NPC::TrainerList trainerList;
+ trainerList.TrainerGUID = npc->GetGUID();
+ trainerList.TrainerType = AsUnderlyingType(_type);
+ trainerList.Greeting = GetGreeting(locale);
+ trainerList.Spells.reserve(_spells.size());
+ for (Spell const& trainerSpell : _spells)
+ {
+ if (!player->IsSpellFitByClassAndRace(trainerSpell.SpellId))
+ continue;
+
+ SpellInfo const* trainerSpellInfo = sSpellMgr->AssertSpellInfo(trainerSpell.SpellId);
+
+ bool primaryProfessionFirstRank = false;
+ for (SpellEffectInfo const& spellEffectInfo : trainerSpellInfo->GetEffects())
+ {
+ if (!spellEffectInfo.IsEffect(SPELL_EFFECT_LEARN_SPELL))
+ continue;
+
+ SpellInfo const* learnedSpellInfo = sSpellMgr->GetSpellInfo(spellEffectInfo.TriggerSpell);
+ if (learnedSpellInfo && learnedSpellInfo->IsPrimaryProfessionFirstRank())
+ primaryProfessionFirstRank = true;
+ }
+
+ trainerList.Spells.emplace_back();
+ WorldPackets::NPC::TrainerListSpell& trainerListSpell = trainerList.Spells.back();
+ trainerListSpell.SpellID = trainerSpell.SpellId;
+ trainerListSpell.Usable = AsUnderlyingType(GetSpellState(player, &trainerSpell));
+ trainerListSpell.MoneyCost = int32(trainerSpell.MoneyCost * reputationDiscount);
+ trainerListSpell.PointCost[0] = 0; // spells don't cost talent points
+ trainerListSpell.PointCost[1] = (primaryProfessionFirstRank ? 1 : 0);
+ trainerListSpell.ReqLevel = trainerSpell.ReqLevel;
+ trainerListSpell.ReqSkillLine = trainerSpell.ReqSkillLine;
+ trainerListSpell.ReqSkillRank = trainerSpell.ReqSkillRank;
+ std::copy(trainerSpell.ReqAbility.begin(), trainerSpell.ReqAbility.end(), trainerListSpell.ReqAbility.begin());
+ }
+
+ player->SendDirectMessage(trainerList.Write());
+ }
+
+ void Trainer::TeachSpell(Creature* npc, Player* player, uint32 spellId)
+ {
+ if (!IsTrainerValidForPlayer(player))
+ return;
+
+ Spell const* trainerSpell = GetSpell(spellId);
+ if (!trainerSpell)
+ {
+ SendTeachFailure(npc, player, spellId, FailReason::Unavailable);
+ return;
+ }
+
+ if (!CanTeachSpell(player, trainerSpell))
+ {
+ SendTeachFailure(npc, player, spellId, FailReason::NotEnoughSkill);
+ return;
+ }
+
+ float reputationDiscount = player->GetReputationPriceDiscount(npc);
+ int32 moneyCost = int32(trainerSpell->MoneyCost * reputationDiscount);
+ if (!player->HasEnoughMoney(moneyCost))
+ {
+ SendTeachFailure(npc, player, spellId, FailReason::NotEnoughMoney);
+ return;
+ }
+
+ player->ModifyMoney(-moneyCost);
+
+ npc->SendPlaySpellVisual(179); // 53 SpellCastDirected
+ npc->SendPlaySpellImpact(player->GetGUID(), 362); // 113 EmoteSalute
+
+ // learn explicitly or cast explicitly
+ if (trainerSpell->IsCastable())
+ player->CastSpell(player, trainerSpell->SpellId, true);
+ else
+ player->learnSpell(trainerSpell->SpellId, false);
+
+ SendTeachSucceeded(npc, player, spellId);
+ }
+
+ Spell const* Trainer::GetSpell(uint32 spellId) const
+ {
+ auto itr = std::find_if(_spells.begin(), _spells.end(), [spellId](Spell const& trainerSpell)
+ {
+ return trainerSpell.SpellId == spellId;
+ });
+
+ if (itr != _spells.end())
+ return &(*itr);
+
+ return nullptr;
+ }
+
+ bool Trainer::CanTeachSpell(Player const* player, Spell const* trainerSpell)
+ {
+ SpellState state = GetSpellState(player, trainerSpell);
+ if (state != SpellState::Available)
+ return false;
+
+ SpellInfo const* trainerSpellInfo = sSpellMgr->AssertSpellInfo(trainerSpell->SpellId);
+
+ for (SpellEffectInfo const& spellEffectInfo : trainerSpellInfo->GetEffects())
+ {
+ if (!spellEffectInfo.IsEffect(SPELL_EFFECT_LEARN_SPELL))
+ continue;
+
+ SpellInfo const* learnedSpellInfo = sSpellMgr->GetSpellInfo(spellEffectInfo.TriggerSpell);
+ if (learnedSpellInfo && learnedSpellInfo->IsPrimaryProfessionFirstRank() && !player->GetFreePrimaryProfessionPoints())
+ return false;
+ }
+
+ return true;
+ }
+
+ SpellState Trainer::GetSpellState(Player const* player, Spell const* trainerSpell) const
+ {
+ if (player->HasSpell(trainerSpell->SpellId))
+ return SpellState::Known;
+
+ // check race/class requirement
+ if (!player->IsSpellFitByClassAndRace(trainerSpell->SpellId))
+ return SpellState::Unavailable;
+
+ // check skill requirement
+ if (trainerSpell->ReqSkillLine && player->GetBaseSkillValue(trainerSpell->ReqSkillLine) < trainerSpell->ReqSkillRank)
+ return SpellState::Unavailable;
+
+ for (int32 reqAbility : trainerSpell->ReqAbility)
+ if (reqAbility && !player->HasSpell(reqAbility))
+ return SpellState::Unavailable;
+
+ // check level requirement
+ if (player->GetLevel() < trainerSpell->ReqLevel)
+ return SpellState::Unavailable;
+
+ // check ranks
+ bool hasLearnSpellEffect = false;
+ bool knowsAllLearnedSpells = true;
+ for (SpellEffectInfo const& spellEffectInfo : sSpellMgr->AssertSpellInfo(trainerSpell->SpellId)->GetEffects())
+ {
+ if (!spellEffectInfo.IsEffect(SPELL_EFFECT_LEARN_SPELL))
+ continue;
+
+ hasLearnSpellEffect = true;
+ if (!player->HasSpell(spellEffectInfo.TriggerSpell))
+ knowsAllLearnedSpells = false;
+
+ if (uint32 previousRankSpellId = sSpellMgr->GetPrevSpellInChain(spellEffectInfo.TriggerSpell))
+ if (!player->HasSpell(previousRankSpellId))
+ return SpellState::Unavailable;
+ }
+
+ if (!hasLearnSpellEffect)
+ {
+ if (uint32 previousRankSpellId = sSpellMgr->GetPrevSpellInChain(trainerSpell->SpellId))
+ if (!player->HasSpell(previousRankSpellId))
+ return SpellState::Unavailable;
+ }
+ else if (knowsAllLearnedSpells)
+ return SpellState::Known;
+
+ // check additional spell requirement
+ for (auto const& requirePair : sSpellMgr->GetSpellsRequiredForSpellBounds(trainerSpell->SpellId))
+ if (!player->HasSpell(requirePair.second))
+ return SpellState::Unavailable;
+
+ return SpellState::Available;
+ }
+
+ bool Trainer::IsTrainerValidForPlayer(Player const* player) const
+ {
+ if (!GetTrainerRequirement())
+ return true;
+
+ switch (GetTrainerType())
+ {
+ case Type::Class:
+ case Type::Pet:
+ // check class for class trainers
+ return player->getClass() == GetTrainerRequirement();
+ case Type::Mount:
+ // check race for mount trainers
+ return player->getRace() == GetTrainerRequirement();
+ case Type::Tradeskill:
+ // check spell for profession trainers
+ return player->HasSpell(GetTrainerRequirement());
+ default:
+ break;
+ }
+
+ return true;
+ }
+
+ void Trainer::SendTeachFailure(Creature const* npc, Player const* player, uint32 spellId, FailReason reason) const
+ {
+ WorldPackets::NPC::TrainerBuyFailed trainerBuyFailed;
+ trainerBuyFailed.TrainerGUID = npc->GetGUID();
+ trainerBuyFailed.SpellID = spellId;
+ trainerBuyFailed.TrainerFailedReason = AsUnderlyingType(reason);
+ player->SendDirectMessage(trainerBuyFailed.Write());
+ }
+
+ void Trainer::SendTeachSucceeded(Creature const* npc, Player const* player, uint32 spellId) const
+ {
+ WorldPackets::NPC::TrainerBuySucceeded trainerBuySucceeded;
+ trainerBuySucceeded.TrainerGUID = npc->GetGUID();
+ trainerBuySucceeded.SpellID = spellId;
+ player->SendDirectMessage(trainerBuySucceeded.Write());
+ }
+
+ std::string const& Trainer::GetGreeting(LocaleConstant locale) const
+ {
+ if (_greeting[locale].empty())
+ return _greeting[DEFAULT_LOCALE];
+
+ return _greeting[locale];
+ }
+
+ void Trainer::AddGreetingLocale(LocaleConstant locale, std::string greeting)
+ {
+ _greeting[locale] = std::move(greeting);
+ }
+}
diff --git a/src/server/game/Entities/Creature/Trainer.h b/src/server/game/Entities/Creature/Trainer.h
new file mode 100644
index 0000000000..66de1154da
--- /dev/null
+++ b/src/server/game/Entities/Creature/Trainer.h
@@ -0,0 +1,97 @@
+/*
+ * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by the
+ * Free Software Foundation; either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef Trainer_h__
+#define Trainer_h__
+
+#include "Common.h"
+#include <array>
+#include <vector>
+
+class Creature;
+class ObjectMgr;
+class Player;
+
+namespace Trainer
+{
+ enum class Type : uint32
+ {
+ Class = 0,
+ Mount = 1,
+ Tradeskill = 2,
+ Pet = 3
+ };
+
+ enum class SpellState : uint8
+ {
+ Available = 0,
+ Unavailable = 1,
+ Known = 2
+ };
+
+ enum class FailReason : uint32
+ {
+ Unavailable = 0,
+ NotEnoughMoney = 1,
+ NotEnoughSkill = 2
+ };
+
+ struct AC_GAME_API Spell
+ {
+ uint32 SpellId = 0;
+ uint32 MoneyCost = 0;
+ uint32 ReqSkillLine = 0;
+ uint32 ReqSkillRank = 0;
+ std::array<uint32, 3> ReqAbility = { };
+ uint8 ReqLevel = 0;
+
+ [[nodiscard]] bool IsCastable() const;
+ };
+
+ class AC_GAME_API Trainer
+ {
+ public:
+ Trainer(uint32 trainerId, Type type, uint32 requirement, std::string greeting, std::vector<Spell> spells);
+
+ [[nodiscard]] Spell const* GetSpell(uint32 spellId) const;
+ [[nodiscard]] std::vector<Spell> const& GetSpells() const { return _spells; }
+ void SendSpells(Creature* npc, Player* player, LocaleConstant locale) const;
+ bool CanTeachSpell(Player const* player, Spell const* trainerSpell);
+ void TeachSpell(Creature* npc, Player* player, uint32 spellId);
+
+ [[nodiscard]] Type GetTrainerType() const { return _type; }
+ [[nodiscard]] uint32 GetTrainerRequirement() const { return _requirement; }
+ bool IsTrainerValidForPlayer(Player const* player) const;
+
+ private:
+ SpellState GetSpellState(Player const* player, Spell const* trainerSpell) const;
+ void SendTeachFailure(Creature const* npc, Player const* player, uint32 spellId, FailReason reason) const;
+ void SendTeachSucceeded(Creature const* npc, Player const* player, uint32 spellId) const;
+ [[nodiscard]] std::string const& GetGreeting(LocaleConstant locale) const;
+
+ friend ObjectMgr;
+ void AddGreetingLocale(LocaleConstant locale, std::string greeting);
+
+ uint32 _trainerId;
+ Type _type;
+ uint32 _requirement;
+ std::vector<Spell> _spells;
+ std::array<std::string, TOTAL_LOCALES> _greeting;
+ };
+}
+
+#endif // Trainer_h__
diff --git a/src/server/game/Entities/Item/Item.h b/src/server/game/Entities/Item/Item.h
index 51289594b9..9f467fad79 100644
--- a/src/server/game/Entities/Item/Item.h
+++ b/src/server/game/Entities/Item/Item.h
@@ -272,6 +272,13 @@ public:
[[nodiscard]] uint32 GetCount() const { return GetUInt32Value(ITEM_FIELD_STACK_COUNT); }
void SetCount(uint32 value) { SetUInt32Value(ITEM_FIELD_STACK_COUNT, value); }
[[nodiscard]] uint32 GetMaxStackCount() const { return GetTemplate()->GetMaxStackSize(); }
+ void GetOnEquipSpellIDs(std::vector<uint32>& spellEquipID) const
+ {
+ if (ItemTemplate const* proto = GetTemplate())
+ proto->GetOnEquipSpellIDs(spellEquipID);
+ else
+ spellEquipID.clear();
+ };
// Checks if this item has sockets, whether built-in or added by an upgrade.
[[nodiscard]] bool HasSocket() const;
[[nodiscard]] uint8 GetGemCountWithID(uint32 GemID) const;
diff --git a/src/server/game/Entities/Item/ItemTemplate.h b/src/server/game/Entities/Item/ItemTemplate.h
index b41616c112..b073cb1078 100644
--- a/src/server/game/Entities/Item/ItemTemplate.h
+++ b/src/server/game/Entities/Item/ItemTemplate.h
@@ -729,6 +729,14 @@ struct ItemTemplate
return (Stackable == 2147483647 || Stackable <= 0) ? uint32(0x7FFFFFFF - 1) : uint32(Stackable);
}
+ void GetOnEquipSpellIDs(std::vector<uint32>& spellEquipID) const
+ {
+ spellEquipID.clear();
+ for (auto const& spell : Spells)
+ if (spell.SpellId && spell.SpellTrigger == ITEM_SPELLTRIGGER_ON_EQUIP)
+ spellEquipID.push_back(spell.SpellId);
+ }
+
[[nodiscard]] float getDPS() const
{
if (Delay == 0)
diff --git a/src/server/game/Entities/Object/Object.cpp b/src/server/game/Entities/Object/Object.cpp
index 3d86fd0599..73ced58747 100644
--- a/src/server/game/Entities/Object/Object.cpp
+++ b/src/server/game/Entities/Object/Object.cpp
@@ -815,13 +815,6 @@ void Object::ApplyModSignedFloatValue(uint16 index, float val, bool apply)
SetFloatValue(index, cur);
}
-void Object::ApplyPercentModFloatValue(uint16 index, float val, bool apply)
-{
- float value = GetFloatValue(index);
- ApplyPercentModFloatVar(value, val, apply);
- SetFloatValue(index, value);
-}
-
void Object::ApplyModPositiveFloatValue(uint16 index, float val, bool apply)
{
float cur = GetFloatValue(index);
diff --git a/src/server/game/Entities/Object/Object.h b/src/server/game/Entities/Object/Object.h
index 9e84a3732a..02d38a2f5a 100644
--- a/src/server/game/Entities/Object/Object.h
+++ b/src/server/game/Entities/Object/Object.h
@@ -165,7 +165,6 @@ public:
void ApplyModUInt64Value(uint16 index, int32 val, bool apply);
void ApplyModPositiveFloatValue(uint16 index, float val, bool apply);
void ApplyModSignedFloatValue(uint16 index, float val, bool apply);
- void ApplyPercentModFloatValue(uint16 index, float val, bool apply);
void SetFlag(uint16 index, uint32 newFlag);
void RemoveFlag(uint16 index, uint32 oldFlag);
diff --git a/src/server/game/Entities/Pet/Pet.cpp b/src/server/game/Entities/Pet/Pet.cpp
index 4cb7d3d1b1..55ebf72afd 100644
--- a/src/server/game/Entities/Pet/Pet.cpp
+++ b/src/server/game/Entities/Pet/Pet.cpp
@@ -1075,7 +1075,7 @@ bool Guardian::InitStatsForLevel(uint8 petlevel)
SetMeleeDamageSchool(SpellSchools(cinfo->dmgschool));
}
- SetModifierValue(UNIT_MOD_ARMOR, BASE_VALUE, float(petlevel * 50));
+ SetStatFlatModifier(UNIT_MOD_ARMOR, BASE_VALUE, float(petlevel * 50));
uint32 attackTime = BASE_ATTACK_TIME;
if (!owner->IsClass(CLASS_HUNTER, CLASS_CONTEXT_PET) && cinfo->BaseAttackTime >= 1000)
@@ -1094,7 +1094,7 @@ bool Guardian::InitStatsForLevel(uint8 petlevel)
// xinef: hunter pets should not inherit template resistances
if (!IsHunterPet())
for (uint8 i = SPELL_SCHOOL_HOLY; i < MAX_SPELL_SCHOOL; ++i)
- SetModifierValue(UnitMods(UNIT_MOD_RESISTANCE_START + i), BASE_VALUE, float(cinfo->resistance[i]));
+ SetStatFlatModifier(UnitMods(UNIT_MOD_RESISTANCE_START + i), BASE_VALUE, float(cinfo->resistance[i]));
//health, mana, armor and resistance
PetLevelInfo const* pInfo = sObjectMgr->GetPetLevelInfo(creature_ID, petlevel);
@@ -1111,15 +1111,15 @@ bool Guardian::InitStatsForLevel(uint8 petlevel)
}
SetCreateHealth(pInfo->health*factorHealth);
- SetModifierValue(UNIT_MOD_HEALTH, BASE_VALUE, (float)pInfo->health);
+ SetStatFlatModifier(UNIT_MOD_HEALTH, BASE_VALUE, (float)pInfo->health);
if (petType != HUNTER_PET) //hunter pet use focus
{
SetCreateMana(pInfo->mana);
- SetModifierValue(UNIT_MOD_MANA, BASE_VALUE, (float)pInfo->mana);
+ SetStatFlatModifier(UNIT_MOD_MANA, BASE_VALUE, (float)pInfo->mana);
}
if (pInfo->armor > 0)
- SetModifierValue(UNIT_MOD_ARMOR, BASE_VALUE, float(pInfo->armor));
+ SetStatFlatModifier(UNIT_MOD_ARMOR, BASE_VALUE, float(pInfo->armor));
for (uint8 stat = 0; stat < MAX_STATS; ++stat)
SetCreateStat(Stats(stat), float(pInfo->stats[stat]));
@@ -1138,9 +1138,9 @@ bool Guardian::InitStatsForLevel(uint8 petlevel)
}
SetCreateHealth(std::max<uint32>(1, stats->BaseHealth[cinfo->expansion]*factorHealth));
- SetModifierValue(UNIT_MOD_HEALTH, BASE_VALUE, GetCreateHealth());
+ SetStatFlatModifier(UNIT_MOD_HEALTH, BASE_VALUE, GetCreateHealth());
SetCreateMana(stats->BaseMana * factorMana);
- SetModifierValue(UNIT_MOD_MANA, BASE_VALUE, GetCreateMana());
+ SetStatFlatModifier(UNIT_MOD_MANA, BASE_VALUE, GetCreateMana());
// xinef: added some multipliers so debuffs can affect pets in any way...
SetCreateStat(STAT_STRENGTH, 22);
@@ -1174,24 +1174,6 @@ bool Guardian::InitStatsForLevel(uint8 petlevel)
switch (GetEntry())
{
- case NPC_FELGUARD:
- {
- // xinef: Glyph of Felguard, so ugly im crying... no appropriate spell
- if (AuraEffect* aurEff = owner->GetAuraEffectDummy(SPELL_GLYPH_OF_FELGUARD))
- {
- HandleStatModifier(UNIT_MOD_ATTACK_POWER, TOTAL_PCT, aurEff->GetAmount(), true);
- }
-
- break;
- }
- case NPC_VOIDWALKER:
- {
- if (AuraEffect* aurEff = owner->GetAuraEffectDummy(SPELL_GLYPH_OF_VOIDWALKER))
- {
- HandleStatModifier(UNIT_MOD_STAT_STAMINA, TOTAL_PCT, aurEff->GetAmount(), true);
- }
- break;
- }
case NPC_WATER_ELEMENTAL_PERM:
{
AddAura(SPELL_PET_AVOIDANCE, this);
diff --git a/src/server/game/Entities/Player/KillRewarder.cpp b/src/server/game/Entities/Player/KillRewarder.cpp
index dce44da0f5..78d24b4dbc 100644
--- a/src/server/game/Entities/Player/KillRewarder.cpp
+++ b/src/server/game/Entities/Player/KillRewarder.cpp
@@ -21,6 +21,7 @@
#include "Pet.h"
#include "Player.h"
#include "ScriptMgr.h"
+#include "SpellAuraDefines.h"
#include "SpellAuraEffects.h"
// KillRewarder incapsulates logic of rewarding player upon kill with:
@@ -162,9 +163,7 @@ void KillRewarder::_RewardXP(Player* player, float rate)
if (xp)
{
// 4.2.2. Apply auras modifying rewarded XP (SPELL_AURA_MOD_XP_PCT).
- Unit::AuraEffectList const& auras = player->GetAuraEffectsByType(SPELL_AURA_MOD_XP_PCT);
- for (Unit::AuraEffectList::const_iterator i = auras.begin(); i != auras.end(); ++i)
- AddPct(xp, (*i)->GetAmount());
+ xp *= player->GetTotalAuraMultiplier(SPELL_AURA_MOD_XP_PCT);
// 4.2.3. Give XP to player.
sScriptMgr->OnPlayerGiveXP(player, xp, _victim, PlayerXPSource::XPSOURCE_KILL);
diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp
index 111392968a..ea1dbf9f53 100644
--- a/src/server/game/Entities/Player/Player.cpp
+++ b/src/server/game/Entities/Player/Player.cpp
@@ -69,14 +69,17 @@
#include "Realm.h"
#include "ReputationMgr.h"
#include "ScriptMgr.h"
+#include "SharedDefines.h"
#include "SocialMgr.h"
#include "Spell.h"
+#include "SpellAuraDefines.h"
#include "SpellAuraEffects.h"
#include "SpellAuras.h"
#include "SpellMgr.h"
#include "StringConvert.h"
#include "TicketMgr.h"
#include "Tokenize.h"
+#include "Trainer.h"
#include "Transport.h"
#include "Unit.h"
#include "UpdateData.h"
@@ -312,8 +315,8 @@ Player::Player(WorldSession* session): Unit(), m_mover(this)
for (uint8 i = 0; i < BASEMOD_END; ++i)
{
- m_auraBaseMod[i][FLAT_MOD] = 0.0f;
- m_auraBaseMod[i][PCT_MOD] = 1.0f;
+ m_auraBaseFlatMod[i] = 0.0f;
+ m_auraBasePctMod[i] = 1.0f;
}
for (uint8 i = 0; i < MAX_COMBAT_RATING; i++)
@@ -420,6 +423,8 @@ Player::Player(WorldSession* session): Unit(), m_mover(this)
sScriptMgr->OnConstructPlayer(this);
_expectingChangeTransport = false;
+ _pendingFlightChangeCounter = 0;
+ _mapChangeOrderCounter = 0;
}
Player::~Player()
@@ -829,9 +834,7 @@ int32 Player::getMaxTimer(MirrorTimerType timer)
if (!IsAlive() || HasWaterBreathingAura() || GetSession()->GetSecurity() >= AccountTypes(sWorld->getIntConfig(CONFIG_DISABLE_BREATHING)))
return DISABLED_MIRROR_TIMER;
int32 UnderWaterTime = sWorld->getIntConfig(CONFIG_WATER_BREATH_TIMER);
- AuraEffectList const& mModWaterBreathing = GetAuraEffectsByType(SPELL_AURA_MOD_WATER_BREATHING);
- for (AuraEffectList::const_iterator i = mModWaterBreathing.begin(); i != mModWaterBreathing.end(); ++i)
- AddPct(UnderWaterTime, (*i)->GetAmount());
+ UnderWaterTime *= GetTotalAuraMultiplier(SPELL_AURA_MOD_WATER_BREATHING);
return UnderWaterTime;
}
case FIRE_TIMER:
@@ -1920,10 +1923,7 @@ void Player::Regenerate(Powers power)
// Mana regen calculated in Player::UpdateManaRegen(), energy regen calculated in Player::UpdateEnergyRegen()
if (power != POWER_MANA && power != POWER_ENERGY)
{
- AuraEffectList const& ModPowerRegenPCTAuras = GetAuraEffectsByType(SPELL_AURA_MOD_POWER_REGEN_PERCENT);
- for (AuraEffectList::const_iterator i = ModPowerRegenPCTAuras.begin(); i != ModPowerRegenPCTAuras.end(); ++i)
- if (Powers((*i)->GetMiscValue()) == power)
- AddPct(addvalue, (*i)->GetAmount());
+ addvalue *= GetTotalAuraMultiplierByMiscValue(SPELL_AURA_MOD_POWER_REGEN_PERCENT, power);
// Butchery requires combat for this effect
if (power != POWER_RUNIC_POWER || IsInCombat())
@@ -2006,11 +2006,7 @@ void Player::RegenerateHealth()
addvalue *= 1.33f;
}
- AuraEffectList const& mModHealthRegenPct = GetAuraEffectsByType(SPELL_AURA_MOD_HEALTH_REGEN_PERCENT);
- for (AuraEffectList::const_iterator i = mModHealthRegenPct.begin(); i != mModHealthRegenPct.end(); ++i)
- {
- AddPct(addvalue, (*i)->GetAmount());
- }
+ addvalue *= GetTotalAuraMultiplier(SPELL_AURA_MOD_HEALTH_REGEN_PERCENT);
if (!IsInCombat())
{
@@ -2120,11 +2116,6 @@ Creature* Player::GetNPCIfCanInteractWith(ObjectGuid const& guid, uint32 npcflag
if (!creature->IsWithinDistInMap(this, INTERACTION_DISTANCE))
return nullptr;
- // pussywizard: many npcs have missing conditions for class training and rogue trainer can for eg. train dual wield to a shaman :/ too many to change in sql and watch in the future
- // pussywizard: this function is not used when talking, but when already taking action (buy spell, reset talents, show spell list)
- if (npcflagmask & (UNIT_NPC_FLAG_TRAINER | UNIT_NPC_FLAG_TRAINER_CLASS) && creature->GetCreatureTemplate()->trainer_type == TRAINER_TYPE_CLASS && !IsClass((Classes)creature->GetCreatureTemplate()->trainer_class, CLASS_CONTEXT_CLASS_TRAINER))
- return nullptr;
-
return creature;
}
@@ -2484,7 +2475,6 @@ void Player::GiveLevel(uint8 level)
m_Played_time[PLAYED_TIME_LEVEL] = 0; // Level Played Time reset
_ApplyAllLevelScaleItemMods(false);
- _RemoveAllAuraStatMods();
SetLevel(level);
@@ -2507,7 +2497,6 @@ void Player::GiveLevel(uint8 level)
UpdateSkillsToMaxSkillsForLevel();
_ApplyAllLevelScaleItemMods(true);
- _ApplyAllAuraStatMods();
if (!isDead())
{
@@ -2664,14 +2653,14 @@ void Player::InitStatsForLevel(bool reapplyMods)
// set armor (resistance 0) to original value (create_agility*2)
SetArmor(int32(m_createStats[STAT_AGILITY] * 2));
- SetResistanceBuffMods(SpellSchools(0), true, 0.0f);
- SetResistanceBuffMods(SpellSchools(0), false, 0.0f);
+ SetFloatValue(UNIT_FIELD_RESISTANCEBUFFMODSPOSITIVE + AsUnderlyingType(SPELL_SCHOOL_NORMAL), 0.0f);
+ SetFloatValue(UNIT_FIELD_RESISTANCEBUFFMODSNEGATIVE + AsUnderlyingType(SPELL_SCHOOL_NORMAL), 0.0f);
// set other resistance to original value (0)
for (uint8 i = 1; i < MAX_SPELL_SCHOOL; ++i)
{
SetResistance(SpellSchools(i), 0);
- SetResistanceBuffMods(SpellSchools(i), true, 0.0f);
- SetResistanceBuffMods(SpellSchools(i), false, 0.0f);
+ SetFloatValue(UNIT_FIELD_RESISTANCEBUFFMODSPOSITIVE + i, 0.0f);
+ SetFloatValue(UNIT_FIELD_RESISTANCEBUFFMODSNEGATIVE + i, 0.0f);
}
SetUInt32Value(PLAYER_FIELD_MOD_TARGET_RESISTANCE, 0);
@@ -3906,74 +3895,6 @@ bool Player::HasActiveSpell(uint32 spell) const
return (itr != m_spells.end() && itr->second->State != PLAYERSPELL_REMOVED && itr->second->Active && itr->second->IsInSpec(m_activeSpec));
}
-TrainerSpellState Player::GetTrainerSpellState(TrainerSpell const* trainer_spell) const
-{
- if (!trainer_spell)
- return TRAINER_SPELL_RED;
-
- bool hasSpell = true;
- for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
- {
- if (!trainer_spell->learnedSpell[i])
- continue;
-
- if (!HasSpell(trainer_spell->learnedSpell[i]))
- {
- hasSpell = false;
- break;
- }
- }
- // known spell
- if (hasSpell)
- return TRAINER_SPELL_GRAY;
-
- // check skill requirement
- if (trainer_spell->reqSkill && GetBaseSkillValue(trainer_spell->reqSkill) < trainer_spell->reqSkillValue)
- return TRAINER_SPELL_RED;
-
- // check level requirement
- if (GetLevel() < trainer_spell->reqLevel)
- return TRAINER_SPELL_RED;
-
- for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
- {
- if (!trainer_spell->learnedSpell[i])
- continue;
-
- // check race/class requirement
- if (!IsSpellFitByClassAndRace(trainer_spell->learnedSpell[i]))
- return TRAINER_SPELL_RED;
-
- if (uint32 prevSpell = sSpellMgr->GetPrevSpellInChain(trainer_spell->learnedSpell[i]))
- {
- // check prev.rank requirement
- if (prevSpell && !HasSpell(prevSpell))
- return TRAINER_SPELL_RED;
- }
-
- SpellsRequiringSpellMapBounds spellsRequired = sSpellMgr->GetSpellsRequiredForSpellBounds(trainer_spell->learnedSpell[i]);
- for (SpellsRequiringSpellMap::const_iterator itr = spellsRequired.first; itr != spellsRequired.second; ++itr)
- {
- // check additional spell requirement
- if (!HasSpell(itr->second))
- return TRAINER_SPELL_RED;
- }
- }
-
- // check primary prof. limit
- // first rank of primary profession spell when there are no proffesions avalible is disabled
- for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
- {
- if (!trainer_spell->learnedSpell[i])
- continue;
- SpellInfo const* learnedSpellInfo = sSpellMgr->GetSpellInfo(trainer_spell->learnedSpell[i]);
- if (learnedSpellInfo && learnedSpellInfo->IsPrimaryProfessionFirstRank() && (GetFreePrimaryProfessionPoints() == 0))
- return TRAINER_SPELL_GREEN_DISABLED;
- }
-
- return TRAINER_SPELL_GREEN;
-}
-
/**
* Deletes a character from the database
*
@@ -5010,24 +4931,107 @@ void Player::ClearChannelWatch()
(*itr)->RemoveWatching(this);
}
-void Player::HandleBaseModValue(BaseModGroup modGroup, BaseModType modType, float amount, bool apply)
+void Player::HandleBaseModFlatValue(BaseModGroup modGroup, float amount, bool apply)
{
if (modGroup >= BASEMOD_END)
{
- LOG_ERROR("entities.player", "ERROR in HandleBaseModValue(): non existed BaseModGroup!");
+ LOG_ERROR("entities.player", "Player::HandleBaseModFlatValue: Invalid BaseModGroup/BaseModType ({}/{}) for player '{}' ({})",
+ modGroup, FLAT_MOD, GetName(), GetGUID().ToString());
return;
}
- switch (modType)
+ m_auraBaseFlatMod[modGroup] += apply ? amount : -amount;
+ UpdateBaseModGroup(modGroup);
+}
+
+void Player::ApplyBaseModPctValue(BaseModGroup modGroup, float pct)
+{
+ if (modGroup >= BASEMOD_END)
{
- case FLAT_MOD:
- m_auraBaseMod[modGroup][modType] += apply ? amount : -amount;
+ LOG_ERROR("entities.player", "Player::ApplyBaseModPctValue: Invalid BaseModGroup/BaseModType ({}/{}) for player '{}' ({})",
+ modGroup, PCT_MOD, GetName(), GetGUID().ToString());
+ return;
+ }
+
+ m_auraBasePctMod[modGroup] += CalculatePct(1.0f, pct);
+ UpdateBaseModGroup(modGroup);
+}
+
+void Player::SetBaseModFlatValue(BaseModGroup modGroup, float val)
+{
+ if (m_auraBaseFlatMod[modGroup] == val)
+ return;
+
+ m_auraBaseFlatMod[modGroup] = val;
+ UpdateBaseModGroup(modGroup);
+}
+
+void Player::SetBaseModPctValue(BaseModGroup modGroup, float val)
+{
+ if (m_auraBasePctMod[modGroup] == val)
+ return;
+
+ m_auraBasePctMod[modGroup] = val;
+ UpdateBaseModGroup(modGroup);
+}
+
+void Player::UpdateDamageDoneMods(WeaponAttackType attackType, int32 skipEnchantSlot /*= -1*/)
+{
+ Unit::UpdateDamageDoneMods(attackType, skipEnchantSlot);
+
+ UnitMods unitMod;
+ switch (attackType)
+ {
+ case BASE_ATTACK:
+ unitMod = UNIT_MOD_DAMAGE_MAINHAND;
break;
- case PCT_MOD:
- ApplyPercentModFloatVar(m_auraBaseMod[modGroup][modType], amount, apply);
+ case OFF_ATTACK:
+ unitMod = UNIT_MOD_DAMAGE_OFFHAND;
break;
+ case RANGED_ATTACK:
+ unitMod = UNIT_MOD_DAMAGE_RANGED;
+ break;
+ default:
+ ABORT();
+ break;
+ }
+
+ float amount = 0.0f;
+ Item* item = GetWeaponForAttack(attackType, true);
+ if (!item)
+ return;
+
+ for (uint8 slot = 0; slot < MAX_ENCHANTMENT_SLOT; ++slot)
+ {
+ if (skipEnchantSlot == slot)
+ continue;
+
+ SpellItemEnchantmentEntry const* enchantmentEntry = sSpellItemEnchantmentStore.LookupEntry(item->GetEnchantmentId(EnchantmentSlot(slot)));
+ if (!enchantmentEntry)
+ continue;
+
+ for (uint8 i = 0; i < MAX_SPELL_ITEM_ENCHANTMENT_EFFECTS; ++i)
+ {
+ switch (enchantmentEntry->type[i])
+ {
+ case ITEM_ENCHANTMENT_TYPE_DAMAGE:
+ amount += enchantmentEntry->amount[i];
+ break;
+ case ITEM_ENCHANTMENT_TYPE_TOTEM:
+ if (IsClass(CLASS_SHAMAN, CLASS_CONTEXT_ABILITY))
+ amount += enchantmentEntry->amount[i] * item->GetTemplate()->Delay / 1000.0f;
+ break;
+ default:
+ break;
+ }
+ }
}
+ HandleStatFlatModifier(unitMod, TOTAL_VALUE, amount, true);
+}
+
+void Player::UpdateBaseModGroup(BaseModGroup modGroup)
+{
if (!CanModifyStats())
return;
@@ -5058,10 +5062,7 @@ float Player::GetBaseModValue(BaseModGroup modGroup, BaseModType modType) const
return 0.0f;
}
- if (modType == PCT_MOD && m_auraBaseMod[modGroup][PCT_MOD] <= 0.0f)
- return 0.0f;
-
- return m_auraBaseMod[modGroup][modType];
+ return (modType == FLAT_MOD ? m_auraBaseFlatMod[modGroup] : m_auraBasePctMod[modGroup]);
}
float Player::GetTotalBaseModValue(BaseModGroup modGroup) const
@@ -5072,15 +5073,15 @@ float Player::GetTotalBaseModValue(BaseModGroup modGroup) const
return 0.0f;
}
- if (m_auraBaseMod[modGroup][PCT_MOD] <= 0.0f)
+ if (m_auraBasePctMod[modGroup] <= 0.0f)
return 0.0f;
- return m_auraBaseMod[modGroup][FLAT_MOD] * m_auraBaseMod[modGroup][PCT_MOD];
+ return m_auraBaseFlatMod[modGroup] * m_auraBasePctMod[modGroup];
}
uint32 Player::GetShieldBlockValue() const
{
- float value = (m_auraBaseMod[SHIELD_BLOCK_VALUE][FLAT_MOD] + GetStat(STAT_STRENGTH) * 0.5f - 10) * m_auraBaseMod[SHIELD_BLOCK_VALUE][PCT_MOD];
+ float value = (m_auraBaseFlatMod[SHIELD_BLOCK_VALUE] + GetStat(STAT_STRENGTH) * 0.5f - 10) * m_auraBasePctMod[SHIELD_BLOCK_VALUE];
value = (value < 0) ? 0 : value;
@@ -5149,7 +5150,7 @@ void Player::GetDodgeFromAgility(float& diminishing, float& nondiminishing)
return;
/// @todo: research if talents/effects that increase total agility by x% should increase non-diminishing part
- float base_agility = GetCreateStat(STAT_AGILITY) * m_auraModifiersGroup[UNIT_MOD_STAT_START + static_cast<uint16>(STAT_AGILITY)][BASE_PCT];
+ float base_agility = GetCreateStat(STAT_AGILITY) * GetPctModifierValue(UnitMods(UNIT_MOD_STAT_START + AsUnderlyingType(STAT_AGILITY)), BASE_PCT);
float bonus_agility = GetStat(STAT_AGILITY) - base_agility;
// calculate diminishing (green in char screen) and non-diminishing (white) contribution
@@ -6090,7 +6091,7 @@ bool Player::RewardHonor(Unit* uVictim, uint32 groupsize, int32 honor, bool awar
*/
ObjectGuid victim_guid;
- uint32 victim_rank = 0;
+ int32 victim_rank = 0;
// need call before fields update to have chance move yesterday data to appropriate fields before today data change.
UpdateHonorFields();
@@ -6123,28 +6124,11 @@ bool Player::RewardHonor(Unit* uVictim, uint32 groupsize, int32 honor, bool awar
if (v_level <= k_grey)
return false;
- // PLAYER_CHOSEN_TITLE VALUES DESCRIPTION
- // [0] Just name
- // [1..14] Alliance honor titles and player name
- // [15..28] Horde honor titles and player name
- // [29..38] Other title and player name
- // [39+] Nothing
- uint32 victim_title = victim->GetUInt32Value(PLAYER_CHOSEN_TITLE);
- uint32 killer_title = 0;
- sScriptMgr->OnPlayerVictimRewardBefore(this, victim, killer_title, victim_title);
- // Get Killer titles, CharTitlesEntry::bit_index
- // Ranks:
- // title[1..14] -> rank[5..18]
- // title[15..28] -> rank[5..18]
- // title[other] -> 0
- if (victim_title == 0)
- victim_guid.Clear(); // Don't show HK: <rank> message, only log.
- else if (victim_title < 15)
- victim_rank = victim_title + 4;
- else if (victim_title < 29)
- victim_rank = victim_title - 14 + 4;
- else
- victim_guid.Clear(); // Don't show HK: <rank> message, only log.
+ victim_rank = victim->GetByteValue(PLAYER_FIELD_BYTES, PLAYER_FIELD_BYTES_OFFSET_LIFETIME_MAX_PVP_RANK);
+
+ uint32 killer_title = GetUInt32Value(PLAYER_CHOSEN_TITLE);
+
+ sScriptMgr->OnPlayerVictimRewardBefore(this, victim, killer_title, victim_rank);
honor_f = std::ceil(Acore::Honor::hk_honor_at_level_f(k_level) * (v_level - k_grey) / (k_level - k_grey));
@@ -6572,20 +6556,22 @@ void Player::_ApplyItemMods(Item* item, uint8 slot, bool apply)
LOG_DEBUG("entities.player", "applying mods for item {} ", item->GetGUID().ToString());
- WeaponAttackType attacktype = Player::GetAttackBySlot(slot);
-
if (item->HasSocket()) //only (un)equipping of items with sockets can influence metagems, so no need to waste time with normal items
CorrectMetaGemEnchants(slot, apply);
- if (attacktype < MAX_ATTACK)
- _ApplyWeaponDependentAuraMods(item, attacktype, apply);
-
_ApplyItemBonuses(proto, slot, apply);
if (slot == EQUIPMENT_SLOT_RANGED)
_ApplyAmmoBonuses();
ApplyItemEquipSpell(item, apply);
+
+ ApplyItemDependentAuras(item, apply);
+
+ WeaponAttackType const attackType = Player::GetAttackBySlot(slot);
+ if (attackType != MAX_ATTACK)
+ UpdateWeaponDependentAuras(attackType);
+
ApplyEnchantment(item, apply);
LOG_DEBUG("entities.player.items", "_ApplyItemMods complete.");
@@ -6656,30 +6642,30 @@ void Player::_ApplyItemBonuses(ItemTemplate const* proto, uint8 slot, bool apply
switch (statType)
{
case ITEM_MOD_MANA:
- HandleStatModifier(UNIT_MOD_MANA, BASE_VALUE, float(val), apply);
+ HandleStatFlatModifier(UNIT_MOD_MANA, BASE_VALUE, float(val), apply);
break;
case ITEM_MOD_HEALTH: // modify HP
- HandleStatModifier(UNIT_MOD_HEALTH, BASE_VALUE, float(val), apply);
+ HandleStatFlatModifier(UNIT_MOD_HEALTH, BASE_VALUE, float(val), apply);
break;
case ITEM_MOD_AGILITY: // modify agility
- HandleStatModifier(UNIT_MOD_STAT_AGILITY, BASE_VALUE, float(val), apply);
- ApplyStatBuffMod(STAT_AGILITY, float(val), apply);
+ HandleStatFlatModifier(UNIT_MOD_STAT_AGILITY, BASE_VALUE, float(val), apply);
+ UpdateStatBuffMod(STAT_AGILITY);
break;
case ITEM_MOD_STRENGTH: //modify strength
- HandleStatModifier(UNIT_MOD_STAT_STRENGTH, BASE_VALUE, float(val), apply);
- ApplyStatBuffMod(STAT_STRENGTH, float(val), apply);
+ HandleStatFlatModifier(UNIT_MOD_STAT_STRENGTH, BASE_VALUE, float(val), apply);
+ UpdateStatBuffMod(STAT_STRENGTH);
break;
case ITEM_MOD_INTELLECT: //modify intellect
- HandleStatModifier(UNIT_MOD_STAT_INTELLECT, BASE_VALUE, float(val), apply);
- ApplyStatBuffMod(STAT_INTELLECT, float(val), apply);
+ HandleStatFlatModifier(UNIT_MOD_STAT_INTELLECT, BASE_VALUE, float(val), apply);
+ UpdateStatBuffMod(STAT_INTELLECT);
break;
case ITEM_MOD_SPIRIT: //modify spirit
- HandleStatModifier(UNIT_MOD_STAT_SPIRIT, BASE_VALUE, float(val), apply);
- ApplyStatBuffMod(STAT_SPIRIT, float(val), apply);
+ HandleStatFlatModifier(UNIT_MOD_STAT_SPIRIT, BASE_VALUE, float(val), apply);
+ UpdateStatBuffMod(STAT_SPIRIT);
break;
case ITEM_MOD_STAMINA: //modify stamina
- HandleStatModifier(UNIT_MOD_STAT_STAMINA, BASE_VALUE, float(val), apply);
- ApplyStatBuffMod(STAT_STAMINA, float(val), apply);
+ HandleStatFlatModifier(UNIT_MOD_STAT_STAMINA, BASE_VALUE, float(val), apply);
+ UpdateStatBuffMod(STAT_STAMINA);
break;
case ITEM_MOD_DEFENSE_SKILL_RATING:
ApplyRatingMod(CR_DEFENSE_SKILL, int32(val), apply);
@@ -6768,11 +6754,11 @@ void Player::_ApplyItemBonuses(ItemTemplate const* proto, uint8 slot, bool apply
ApplyRatingMod(CR_EXPERTISE, int32(val), apply);
break;
case ITEM_MOD_ATTACK_POWER:
- HandleStatModifier(UNIT_MOD_ATTACK_POWER, TOTAL_VALUE, float(val), apply);
- HandleStatModifier(UNIT_MOD_ATTACK_POWER_RANGED, TOTAL_VALUE, float(val), apply);
+ HandleStatFlatModifier(UNIT_MOD_ATTACK_POWER, TOTAL_VALUE, float(val), apply);
+ HandleStatFlatModifier(UNIT_MOD_ATTACK_POWER_RANGED, TOTAL_VALUE, float(val), apply);
break;
case ITEM_MOD_RANGED_ATTACK_POWER:
- HandleStatModifier(UNIT_MOD_ATTACK_POWER_RANGED, TOTAL_VALUE, float(val), apply);
+ HandleStatFlatModifier(UNIT_MOD_ATTACK_POWER_RANGED, TOTAL_VALUE, float(val), apply);
break;
// case ITEM_MOD_FERAL_ATTACK_POWER:
// ApplyFeralAPBonus(int32(val), apply);
@@ -6793,7 +6779,7 @@ void Player::_ApplyItemBonuses(ItemTemplate const* proto, uint8 slot, bool apply
ApplySpellPenetrationBonus(val, apply);
break;
case ITEM_MOD_BLOCK_VALUE:
- HandleBaseModValue(SHIELD_BLOCK_VALUE, FLAT_MOD, float(val), apply);
+ HandleBaseModFlatValue(SHIELD_BLOCK_VALUE, float(val), apply);
break;
/// @deprecated item mods
case ITEM_MOD_SPELL_HEALING_DONE:
@@ -6823,7 +6809,7 @@ void Player::_ApplyItemBonuses(ItemTemplate const* proto, uint8 slot, bool apply
if (armor)
{
- UnitModifierType modType = TOTAL_VALUE;
+ UnitModifierFlatType modType = TOTAL_VALUE;
if (proto->Class == ITEM_CLASS_ARMOR)
{
switch (proto->SubClass)
@@ -6837,33 +6823,33 @@ void Player::_ApplyItemBonuses(ItemTemplate const* proto, uint8 slot, bool apply
break;
}
}
- HandleStatModifier(UNIT_MOD_ARMOR, modType, float(armor), apply);
+ HandleStatFlatModifier(UNIT_MOD_ARMOR, modType, float(armor), apply);
}
// Add armor bonus from ArmorDamageModifier if > 0
if (proto->ArmorDamageModifier > 0 && sScriptMgr->OnPlayerCanArmorDamageModifier(this))
- HandleStatModifier(UNIT_MOD_ARMOR, TOTAL_VALUE, float(proto->ArmorDamageModifier), apply);
+ HandleStatFlatModifier(UNIT_MOD_ARMOR, TOTAL_VALUE, float(proto->ArmorDamageModifier), apply);
if (proto->Block)
- HandleBaseModValue(SHIELD_BLOCK_VALUE, FLAT_MOD, float(proto->Block), apply);
+ HandleBaseModFlatValue(SHIELD_BLOCK_VALUE, float(proto->Block), apply);
if (proto->HolyRes)
- HandleStatModifier(UNIT_MOD_RESISTANCE_HOLY, BASE_VALUE, float(proto->HolyRes), apply);
+ HandleStatFlatModifier(UNIT_MOD_RESISTANCE_HOLY, BASE_VALUE, float(proto->HolyRes), apply);
if (proto->FireRes)
- HandleStatModifier(UNIT_MOD_RESISTANCE_FIRE, BASE_VALUE, float(proto->FireRes), apply);
+ HandleStatFlatModifier(UNIT_MOD_RESISTANCE_FIRE, BASE_VALUE, float(proto->FireRes), apply);
if (proto->NatureRes)
- HandleStatModifier(UNIT_MOD_RESISTANCE_NATURE, BASE_VALUE, float(proto->NatureRes), apply);
+ HandleStatFlatModifier(UNIT_MOD_RESISTANCE_NATURE, BASE_VALUE, float(proto->NatureRes), apply);
if (proto->FrostRes)
- HandleStatModifier(UNIT_MOD_RESISTANCE_FROST, BASE_VALUE, float(proto->FrostRes), apply);
+ HandleStatFlatModifier(UNIT_MOD_RESISTANCE_FROST, BASE_VALUE, float(proto->FrostRes), apply);
if (proto->ShadowRes)
- HandleStatModifier(UNIT_MOD_RESISTANCE_SHADOW, BASE_VALUE, float(proto->ShadowRes), apply);
+ HandleStatFlatModifier(UNIT_MOD_RESISTANCE_SHADOW, BASE_VALUE, float(proto->ShadowRes), apply);
if (proto->ArcaneRes)
- HandleStatModifier(UNIT_MOD_RESISTANCE_ARCANE, BASE_VALUE, float(proto->ArcaneRes), apply);
+ HandleStatFlatModifier(UNIT_MOD_RESISTANCE_ARCANE, BASE_VALUE, float(proto->ArcaneRes), apply);
WeaponAttackType attType = Player::GetAttackBySlot(slot);
if (attType != MAX_ATTACK)
@@ -7034,118 +7020,108 @@ void Player::UpdateItemObtainSpells(Item* item, uint8 bag, uint8 slot)
ApplyItemObtainSpells(item, true);
}
-SpellSchoolMask Player::GetMeleeDamageSchoolMask(WeaponAttackType attackType /*= BASE_ATTACK*/, uint8 damageIndex /*= 0*/) const
+// this one rechecks weapon auras and stores them in BaseModGroup container
+// needed for things like axe specialization applying only to axe weapons in case of dual-wield
+void Player::UpdateWeaponDependentCritAuras(WeaponAttackType attackType)
{
- if (Item const* weapon = GetWeaponForAttack(attackType, true))
+ BaseModGroup modGroup;
+ switch (attackType)
{
- return SpellSchoolMask(1 << weapon->GetTemplate()->Damage[damageIndex].DamageType);
+ case BASE_ATTACK:
+ modGroup = CRIT_PERCENTAGE;
+ break;
+ case OFF_ATTACK:
+ modGroup = OFFHAND_CRIT_PERCENTAGE;
+ break;
+ case RANGED_ATTACK:
+ modGroup = RANGED_CRIT_PERCENTAGE;
+ break;
+ default:
+ ABORT();
+ break;
}
- return SPELL_SCHOOL_MASK_NORMAL;
+ float amount = 0.0f;
+ amount += GetTotalAuraModifier(SPELL_AURA_MOD_WEAPON_CRIT_PERCENT, std::bind(&Unit::CheckAttackFitToAuraRequirement, this, attackType, std::placeholders::_1));
+
+ // these auras don't have item requirement (only Combat Expertise in 3.3.5a)
+ amount += GetTotalAuraModifier(SPELL_AURA_MOD_CRIT_PCT);
+
+ SetBaseModFlatValue(modGroup, amount);
}
-void Player::_ApplyWeaponDependentAuraMods(Item* item, WeaponAttackType attackType, bool apply)
+void Player::UpdateAllWeaponDependentCritAuras()
{
- AuraEffectList const& auraCritList = GetAuraEffectsByType(SPELL_AURA_MOD_WEAPON_CRIT_PERCENT);
- for (AuraEffectList::const_iterator itr = auraCritList.begin(); itr != auraCritList.end(); ++itr)
- _ApplyWeaponDependentAuraCritMod(item, attackType, *itr, apply);
-
- AuraEffectList const& auraDamageFlatList = GetAuraEffectsByType(SPELL_AURA_MOD_DAMAGE_DONE);
- for (AuraEffectList::const_iterator itr = auraDamageFlatList.begin(); itr != auraDamageFlatList.end(); ++itr)
- _ApplyWeaponDependentAuraDamageMod(item, attackType, *itr, apply);
+ for (uint8 i = BASE_ATTACK; i < MAX_ATTACK; ++i)
+ UpdateWeaponDependentCritAuras(WeaponAttackType(i));
+}
- AuraEffectList const& auraDamagePctList = GetAuraEffectsByType(SPELL_AURA_MOD_DAMAGE_PERCENT_DONE);
- for (AuraEffectList::const_iterator itr = auraDamagePctList.begin(); itr != auraDamagePctList.end(); ++itr)
- _ApplyWeaponDependentAuraDamageMod(item, attackType, *itr, apply);
+void Player::UpdateWeaponDependentAuras(WeaponAttackType attackType)
+{
+ UpdateWeaponDependentCritAuras(attackType);
+ UpdateDamageDoneMods(attackType);
+ UpdateDamagePctDoneMods(attackType);
}
-void Player::_ApplyWeaponDependentAuraCritMod(Item* item, WeaponAttackType attackType, AuraEffect const* aura, bool apply)
+void Player::ApplyItemDependentAuras(Item* item, bool apply)
{
- // don't apply mod if item is broken or cannot be used
- if (item->IsBroken() || !CanUseAttackType(attackType))
- return;
+ if (apply)
+ {
+ for (auto [spellId, playerSpell]: GetSpellMap())
+ {
+ if (playerSpell->State == PLAYERSPELL_REMOVED)
+ continue;
- // generic not weapon specific case processes in aura code
- if (aura->GetSpellInfo()->EquippedItemClass == -1)
- return;
+ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
+ if (!spellInfo || !spellInfo->IsPassive() || spellInfo->EquippedItemClass < 0)
+ continue;
- if (!sScriptMgr->OnPlayerCanApplyWeaponDependentAuraDamageMod(this, item, attackType, aura, apply))
- return;
+ if (!HasAura(spellId) && HasItemFitToSpellRequirements(spellInfo))
+ AddAura(spellId, this); // no SMSG_SPELL_GO in sniff found
+ }
- BaseModGroup mod = BASEMOD_END;
- switch (attackType)
- {
- case BASE_ATTACK:
- mod = CRIT_PERCENTAGE;
- break;
- case OFF_ATTACK:
- mod = OFFHAND_CRIT_PERCENTAGE;
- break;
- case RANGED_ATTACK:
- mod = RANGED_CRIT_PERCENTAGE;
- break;
- default:
- return;
- }
+ // Check talents (they are stored separately from regular spells)
+ for (auto [spellId, playerTalent] : GetTalentMap())
+ {
+ if (playerTalent->State == PLAYERSPELL_REMOVED)
+ continue;
+
+ if (!(playerTalent->IsInSpec(GetActiveSpec())))
+ continue;
- if (item->IsFitToSpellRequirements(aura->GetSpellInfo()))
- HandleBaseModValue(mod, FLAT_MOD, float (aura->GetAmount()), apply);
+ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
+ if (!spellInfo || !spellInfo->IsPassive() || spellInfo->EquippedItemClass < 0)
+ continue;
+
+ if (!HasAura(spellId) && HasItemFitToSpellRequirements(spellInfo))
+ AddAura(spellId, this);
+ }
+ }
+ else
+ RemoveItemDependentAurasAndCasts(item);
}
-void Player::_ApplyWeaponDependentAuraDamageMod(Item* item, WeaponAttackType attackType, AuraEffect const* aura, bool apply)
+bool Player::CheckAttackFitToAuraRequirement(WeaponAttackType attackType, AuraEffect const* aurEff) const
{
- // don't apply mod if item is broken or cannot be used
- if (item->IsBroken() || !CanUseAttackType(attackType))
- return;
-
- // ignore spell mods for not wands
- if ((aura->GetMiscValue() & SPELL_SCHOOL_MASK_NORMAL) == 0 && (getClassMask() & CLASSMASK_WAND_USERS) == 0)
- return;
+ SpellInfo const* spellInfo = aurEff->GetSpellInfo();
+ if (spellInfo->EquippedItemClass == -1)
+ return true;
- // generic not weapon specific case processes in aura code
- if (aura->GetSpellInfo()->EquippedItemClass == -1)
- return;
+ Item* item = GetWeaponForAttack(attackType, true);
+ if (!item || !item->IsFitToSpellRequirements(spellInfo))
+ return false;
- UnitMods unitMod = UNIT_MOD_END;
- switch (attackType)
- {
- case BASE_ATTACK:
- unitMod = UNIT_MOD_DAMAGE_MAINHAND;
- break;
- case OFF_ATTACK:
- unitMod = UNIT_MOD_DAMAGE_OFFHAND;
- break;
- case RANGED_ATTACK:
- unitMod = UNIT_MOD_DAMAGE_RANGED;
- break;
- default:
- return;
- }
+ return true;
+}
- UnitModifierType unitModType = TOTAL_VALUE;
- switch (aura->GetAuraType())
+SpellSchoolMask Player::GetMeleeDamageSchoolMask(WeaponAttackType attackType /*= BASE_ATTACK*/, uint8 damageIndex /*= 0*/) const
+{
+ if (Item const* weapon = GetWeaponForAttack(attackType, true))
{
- case SPELL_AURA_MOD_DAMAGE_DONE:
- unitModType = TOTAL_VALUE;
- break;
- case SPELL_AURA_MOD_DAMAGE_PERCENT_DONE:
- unitModType = TOTAL_PCT;
- break;
- default:
- return;
+ return SpellSchoolMask(1 << weapon->GetTemplate()->Damage[damageIndex].DamageType);
}
- if (item->IsFitToSpellRequirements(aura->GetSpellInfo()))
- {
- HandleStatModifier(unitMod, unitModType, float(aura->GetAmount()), apply);
- if (unitModType == TOTAL_VALUE)
- {
- if (aura->GetAmount() > 0)
- ApplyModUInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_POS, aura->GetAmount(), apply);
- else
- ApplyModUInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_NEG, aura->GetAmount(), apply);
- }
- }
+ return SPELL_SCHOOL_MASK_NORMAL;
}
void Player::ApplyItemEquipSpell(Item* item, bool apply, bool form_change)
@@ -7462,12 +7438,6 @@ void Player::CastItemUseSpell(Item* item, SpellCastTargets const& targets, uint8
continue;
}
- if (!spellInfo->CheckElixirStacking(this))
- {
- Spell::SendCastResult(this, spellInfo, cast_count, SPELL_FAILED_AURA_BOUNCED);
- continue;
- }
-
Spell* spell = new Spell(this, spellInfo, (count > 0) ? TRIGGERED_FULL_MASK : TRIGGERED_NONE);
spell->m_CastItem = item;
spell->m_cast_count = cast_count; // set count of casts
@@ -7576,10 +7546,7 @@ void Player::_RemoveAllItemMods()
if (!proto)
continue;
- WeaponAttackType attacktype = Player::GetAttackBySlot(i);
- if (attacktype < MAX_ATTACK)
- _ApplyWeaponDependentAuraMods(m_items[i], attacktype, false);
-
+ ApplyItemDependentAuras(m_items[i], false);
_ApplyItemBonuses(proto, i, false);
if (i == EQUIPMENT_SLOT_RANGED)
@@ -7605,12 +7572,13 @@ void Player::_ApplyAllItemMods()
if (!proto)
continue;
- WeaponAttackType attacktype = Player::GetAttackBySlot(i);
- if (attacktype < MAX_ATTACK)
- _ApplyWeaponDependentAuraMods(m_items[i], attacktype, true);
-
+ ApplyItemDependentAuras(m_items[i], true);
_ApplyItemBonuses(proto, i, true);
+ WeaponAttackType const attackType = Player::GetAttackBySlot(i);
+ if (attackType != MAX_ATTACK)
+ UpdateWeaponDependentAuras(attackType);
+
if (i == EQUIPMENT_SLOT_RANGED)
_ApplyAmmoBonuses();
}
@@ -7835,7 +7803,9 @@ void Player::SendLoot(ObjectGuid guid, LootType loot_type)
// And permit out of range GO with no owner in case fishing hole
if (!go || (loot_type != LOOT_FISHINGHOLE && ((loot_type != LOOT_FISHING && loot_type != LOOT_FISHING_JUNK) || go->GetOwnerGUID() != GetGUID()) && !go->IsWithinDistInMap(this)) || (loot_type == LOOT_CORPSE && go->GetRespawnTime() && go->isSpawnedByDefault()))
{
- go->ForceValuesUpdateAtIndex(GAMEOBJECT_BYTES_1);
+ if (go)
+ go->ForceValuesUpdateAtIndex(GAMEOBJECT_BYTES_1);
+
SendLootRelease(guid);
return;
}
@@ -11983,6 +11953,9 @@ void Player::learnQuestRewardedSpells(Quest const* quest)
if (!found)
return;
+ if (!SatisfyQuestSkill(quest, false))
+ return;
+
CastSpell(this, spellId, true);
}
@@ -12581,9 +12554,9 @@ void Player::RemoveItemDependentAurasAndCasts(Item* pItem)
{
Aura* aura = itr->second;
- // skip passive (passive item dependent spells work in another way) and not self applied auras
+ // skip not self applied auras
SpellInfo const* spellInfo = aura->GetSpellInfo();
- if (aura->IsPassive() || aura->GetCasterGUID() != GetGUID())
+ if (aura->GetCasterGUID() != GetGUID())
{
++itr;
continue;
@@ -14379,6 +14352,18 @@ bool Player::CanSeeVendor(Creature const* creature) const
return true;
}
+bool Player::CanSeeTrainer(Creature const* creature) const
+{
+ if (!creature->HasNpcFlag(UNIT_NPC_FLAG_TRAINER))
+ return true;
+
+ if (auto trainer = sObjectMgr->GetTrainer(creature->GetEntry()))
+ if (!trainer || !trainer->IsTrainerValidForPlayer(this))
+ return false;
+
+ return true;
+}
+
void Player::BuildPlayerTalentsInfoData(WorldPacket* data)
{
*data << uint32(GetFreeTalentPoints()); // unspentTalentPoints
@@ -15746,7 +15731,7 @@ void Player::RefundItem(Item* item)
ItemPosCountVec dest;
InventoryResult msg = CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, itemid, count);
ASSERT(msg == EQUIP_ERR_OK); /// Already checked before
- Item* it = StoreNewItem(dest, itemid, true);
+ Item* it = StoreNewItem(dest, itemid, true, true);
SendNewItem(it, count, true, false, true);
}
}
diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h
index 1603a4f687..9496eb950c 100644
--- a/src/server/game/Entities/Player/Player.h
+++ b/src/server/game/Entities/Player/Player.h
@@ -210,14 +210,6 @@ struct SpellCooldown
typedef std::map<uint32, SpellCooldown> SpellCooldowns;
typedef std::unordered_map<uint32 /*instanceId*/, time_t/*releaseTime*/> InstanceTimeMap;
-enum TrainerSpellState
-{
- TRAINER_SPELL_GREEN = 0,
- TRAINER_SPELL_RED = 1,
- TRAINER_SPELL_GRAY = 2,
- TRAINER_SPELL_GREEN_DISABLED = 10 // custom value, not send to client: formally green but learn not allowed
-};
-
enum ActionButtonUpdateState
{
ACTIONBUTTON_UNCHANGED = 0,
@@ -1303,8 +1295,8 @@ public:
InventoryResult CanUseItem(ItemTemplate const* pItem) const;
[[nodiscard]] InventoryResult CanUseAmmo(uint32 item) const;
InventoryResult CanRollForItemInLFG(ItemTemplate const* item, WorldObject const* lootedObject) const;
- Item* StoreNewItem(ItemPosCountVec const& pos, uint32 item, bool update, int32 randomPropertyId = 0);
- Item* StoreNewItem(ItemPosCountVec const& pos, uint32 item, bool update, int32 randomPropertyId, AllowedLooterSet& allowedLooters);
+ Item* StoreNewItem(ItemPosCountVec const& pos, uint32 item, bool update, int32 randomPropertyId = 0, bool refund = false);
+ Item* StoreNewItem(ItemPosCountVec const& pos, uint32 item, bool update, int32 randomPropertyId, AllowedLooterSet& allowedLooters, bool refund = false);
Item* StoreItem(ItemPosCountVec const& pos, Item* pItem, bool update);
Item* EquipNewItem(uint16 pos, uint32 item, bool update);
Item* EquipItem(uint16 pos, Item* pItem, bool update);
@@ -1334,7 +1326,7 @@ public:
{
return StoreItem(dest, pItem, update);
}
- void RemoveItem(uint8 bag, uint8 slot, bool update, bool swap = false);
+ void RemoveItem(uint8 bag, uint8 slot, bool update);
void MoveItemFromInventory(uint8 bag, uint8 slot, bool update);
// in trade, auction, guild bank, mail....
void MoveItemToInventory(ItemPosCountVec const& dest, Item* pItem, bool update, bool in_characterInventoryDB = false);
@@ -1684,7 +1676,6 @@ public:
void SendRemoveControlBar();
[[nodiscard]] bool HasSpell(uint32 spell) const override;
[[nodiscard]] bool HasActiveSpell(uint32 spell) const; // show in spellbook
- TrainerSpellState GetTrainerSpellState(TrainerSpell const* trainer_spell) const;
[[nodiscard]] bool IsSpellFitByClassAndRace(uint32 spell_id) const;
bool IsNeedCastPassiveSpellAtLearn(SpellInfo const* spellInfo) const;
@@ -2187,11 +2178,19 @@ public:
[[nodiscard]] bool CanTameExoticPets() const { return IsGameMaster() || HasAuraType(SPELL_AURA_ALLOW_TAME_PET_TYPE); }
void SetRegularAttackTime();
- void SetBaseModValue(BaseModGroup modGroup, BaseModType modType, float value) { m_auraBaseMod[modGroup][modType] = value; }
- void HandleBaseModValue(BaseModGroup modGroup, BaseModType modType, float amount, bool apply);
+
+ void HandleBaseModFlatValue(BaseModGroup modGroup, float amount, bool apply);
+ void ApplyBaseModPctValue(BaseModGroup modGroup, float pct);
+
+ void SetBaseModFlatValue(BaseModGroup modGroup, float val);
+ void SetBaseModPctValue(BaseModGroup modGroup, float val);
+
+ void UpdateDamageDoneMods(WeaponAttackType attackType, int32 skipEnchantSlot = -1) override;
+ void UpdateBaseModGroup(BaseModGroup modGroup);
+
[[nodiscard]] float GetBaseModValue(BaseModGroup modGroup, BaseModType modType) const;
[[nodiscard]] float GetTotalBaseModValue(BaseModGroup modGroup) const;
- [[nodiscard]] float GetTotalPercentageModValue(BaseModGroup modGroup) const { return m_auraBaseMod[modGroup][FLAT_MOD] + m_auraBaseMod[modGroup][PCT_MOD]; }
+
void _ApplyAllStatBonuses();
void _RemoveAllStatBonuses();
@@ -2203,9 +2202,13 @@ public:
SpellSchoolMask GetMeleeDamageSchoolMask(WeaponAttackType attackType = BASE_ATTACK, uint8 damageIndex = 0) const override;
- void _ApplyWeaponDependentAuraMods(Item* item, WeaponAttackType attackType, bool apply);
- void _ApplyWeaponDependentAuraCritMod(Item* item, WeaponAttackType attackType, AuraEffect const* aura, bool apply);
- void _ApplyWeaponDependentAuraDamageMod(Item* item, WeaponAttackType attackType, AuraEffect const* aura, bool apply);
+ void UpdateWeaponDependentAuras(WeaponAttackType attackType);
+ void ApplyItemDependentAuras(Item* item, bool apply);
+
+ bool CheckAttackFitToAuraRequirement(WeaponAttackType attackType, AuraEffect const* aurEff) const override;
+
+ void UpdateWeaponDependentCritAuras(WeaponAttackType attackType);
+ void UpdateAllWeaponDependentCritAuras();
void _ApplyItemMods(Item* item, uint8 slot, bool apply);
void _RemoveAllItemMods();
@@ -2548,6 +2551,8 @@ public:
//bool isActiveObject() const { return true; }
bool CanSeeSpellClickOn(Creature const* creature) const;
[[nodiscard]] bool CanSeeVendor(Creature const* creature) const;
+ [[nodiscard]] bool CanSeeTrainer(Creature const* creature) const;
+
private:
[[nodiscard]] bool AnyVendorOptionAvailable(uint32 menuId, Creature const* creature) const;
public:
@@ -2829,7 +2834,8 @@ protected:
ActionButtonList m_actionButtons;
- float m_auraBaseMod[BASEMOD_END][MOD_END];
+ float m_auraBaseFlatMod[BASEMOD_END];
+ float m_auraBasePctMod[BASEMOD_END];
int32 m_baseRatingValue[MAX_COMBAT_RATING];
uint32 m_baseSpellPower;
uint32 m_baseSpellDamage;
diff --git a/src/server/game/Entities/Player/PlayerGossip.cpp b/src/server/game/Entities/Player/PlayerGossip.cpp
index 806a8c9afa..77317f78ae 100644
--- a/src/server/game/Entities/Player/PlayerGossip.cpp
+++ b/src/server/game/Entities/Player/PlayerGossip.cpp
@@ -90,15 +90,15 @@ void Player::PrepareGossipMenu(WorldObject* source, uint32 menuId /*= 0*/, bool
}
case GOSSIP_OPTION_LEARNDUALSPEC:
case GOSSIP_OPTION_DUALSPEC_INFO:
- if (!(GetSpecsCount() == 1 && creature->isCanTrainingAndResetTalentsOf(this) && !(GetLevel() < sWorld->getIntConfig(CONFIG_MIN_DUALSPEC_LEVEL))))
+ if (!(GetSpecsCount() == 1 && creature->CanResetTalents(this) && !(GetLevel() < sWorld->getIntConfig(CONFIG_MIN_DUALSPEC_LEVEL))))
canTalk = false;
break;
case GOSSIP_OPTION_UNLEARNTALENTS:
- if (!creature->isCanTrainingAndResetTalentsOf(this))
+ if (!creature->CanResetTalents(this))
canTalk = false;
break;
case GOSSIP_OPTION_UNLEARNPETTALENTS:
- if (!GetPet() || GetPet()->getPetType() != HUNTER_PET || GetPet()->m_spells.size() <= 1 || creature->GetCreatureTemplate()->trainer_type != TRAINER_TYPE_PETS || creature->GetCreatureTemplate()->trainer_class != CLASS_HUNTER)
+ if (!GetPet() || GetPet()->getPetType() != HUNTER_PET || GetPet()->m_spells.size() <= 1 || !creature->CanResetTalents(this))
canTalk = false;
break;
case GOSSIP_OPTION_TAXIVENDOR:
@@ -117,11 +117,16 @@ void Player::PrepareGossipMenu(WorldObject* source, uint32 menuId /*= 0*/, bool
canTalk = false;
break;
case GOSSIP_OPTION_TRAINER:
- if (!creature->IsValidTrainerForPlayer(this))
+ {
+ Trainer::Trainer const* trainer = sObjectMgr->GetTrainer(creature->GetEntry());
+ if (!trainer || !trainer->IsTrainerValidForPlayer(this))
{
+ LOG_ERROR("sql.sql", "GOSSIP_OPTION_TRAINER:: Player {} (GUID: {}) requested wrong gossip menu: {} at Creature: {} (Entry: {})",
+ GetName(), GetGUID().GetCounter(), menu->GetGossipMenu().GetMenuId(), creature->GetName(), creature->GetEntry());
canTalk = false;
}
- break;
+ }
+ [[fallthrough]];
case GOSSIP_OPTION_GOSSIP:
if (creature->isVendorWithIconSpeak())
{
@@ -328,7 +333,7 @@ void Player::OnGossipSelect(WorldObject* source, uint32 gossipListId, uint32 men
GetSession()->SendStablePet(guid);
break;
case GOSSIP_OPTION_TRAINER:
- GetSession()->SendTrainerList(guid);
+ GetSession()->SendTrainerList(source->ToCreature());
break;
case GOSSIP_OPTION_LEARNDUALSPEC:
if (GetSpecsCount() == 1 && GetLevel() >= sWorld->getIntConfig(CONFIG_MIN_DUALSPEC_LEVEL))
diff --git a/src/server/game/Entities/Player/PlayerQuest.cpp b/src/server/game/Entities/Player/PlayerQuest.cpp
index 554622bba6..a2a99a4bd9 100644
--- a/src/server/game/Entities/Player/PlayerQuest.cpp
+++ b/src/server/game/Entities/Player/PlayerQuest.cpp
@@ -1405,9 +1405,7 @@ uint32 Player::CalculateQuestRewardXP(Quest const* quest)
uint32 xp = uint32(quest->XPValue(GetLevel()) * GetQuestRate(quest->IsDFQuest()));
// handle SPELL_AURA_MOD_XP_QUEST_PCT auras
- Unit::AuraEffectList const& ModXPPctAuras = GetAuraEffectsByType(SPELL_AURA_MOD_XP_QUEST_PCT);
- for (Unit::AuraEffectList::const_iterator i = ModXPPctAuras.begin(); i != ModXPPctAuras.end(); ++i)
- AddPct(xp, (*i)->GetAmount());
+ xp *= GetTotalAuraMultiplier(SPELL_AURA_MOD_XP_QUEST_PCT);
return xp;
}
diff --git a/src/server/game/Entities/Player/PlayerStorage.cpp b/src/server/game/Entities/Player/PlayerStorage.cpp
index bad5c36824..e8117aa58e 100644
--- a/src/server/game/Entities/Player/PlayerStorage.cpp
+++ b/src/server/game/Entities/Player/PlayerStorage.cpp
@@ -60,6 +60,7 @@
#include "StringConvert.h"
#include "Tokenize.h"
#include "Transport.h"
+#include "Unit.h"
#include "UpdateFieldFlags.h"
#include "Util.h"
#include "World.h"
@@ -2527,14 +2528,14 @@ void Player::RemoveAmmo()
UpdateDamagePhysical(RANGED_ATTACK);
}
-Item* Player::StoreNewItem(ItemPosCountVec const& dest, uint32 item, bool update, int32 randomPropertyId)
+Item* Player::StoreNewItem(ItemPosCountVec const& dest, uint32 item, bool update, int32 randomPropertyId, bool refund)
{
AllowedLooterSet allowedLooters;
- return StoreNewItem(dest, item, update, randomPropertyId, allowedLooters);
+ return StoreNewItem(dest, item, update, randomPropertyId, allowedLooters, refund);
}
// Return stored item (if stored to stack, it can diff. from pItem). And pItem ca be deleted in this case.
-Item* Player::StoreNewItem(ItemPosCountVec const& dest, uint32 item, bool update, int32 randomPropertyId, AllowedLooterSet& allowedLooters)
+Item* Player::StoreNewItem(ItemPosCountVec const& dest, uint32 item, bool update, int32 randomPropertyId, AllowedLooterSet& allowedLooters, bool refund)
{
uint32 count = 0;
for (ItemPosCountVec::const_iterator itr = dest.begin(); itr != dest.end(); ++itr)
@@ -2549,8 +2550,13 @@ Item* Player::StoreNewItem(ItemPosCountVec const& dest, uint32 item, bool update
AdditionalSavingAddMask(ADDITIONAL_SAVING_INVENTORY_AND_GOLD);
ItemAddedQuestCheck(item, count);
- UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_RECEIVE_EPIC_ITEM, item, count);
- UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_OWN_ITEM, item, count);
+
+ if (!refund)
+ { // Don't update counter criteria for refunded items (primarily currencies)
+ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_RECEIVE_EPIC_ITEM, item, count);
+ UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_OWN_ITEM, item, count);
+ }
+
pItem = StoreItem(dest, pItem, update);
if (allowedLooters.size() > 1 && pItem->GetTemplate()->GetMaxStackSize() == 1 && pItem->IsSoulBound() && sWorld->getBoolConfig(CONFIG_SET_BOP_ITEM_TRADEABLE))
@@ -2896,7 +2902,7 @@ void Player::VisualizeItem(uint8 slot, Item* pItem)
pItem->SetState(ITEM_CHANGED, this);
}
-void Player::RemoveItem(uint8 bag, uint8 slot, bool update, bool swap)
+void Player::RemoveItem(uint8 bag, uint8 slot, bool update)
{
// note: removeitem does not actually change the item
// it only takes the item out of storage temporarily
@@ -2931,12 +2937,6 @@ void Player::RemoveItem(uint8 bag, uint8 slot, bool update, bool swap)
// remove item dependent auras and casts (only weapon and armor slots)
if (slot < INVENTORY_SLOT_BAG_END && slot < EQUIPMENT_SLOT_END)
{
- // Xinef: Ensure that this function is called for places with swap=true
- if (!swap)
- {
- RemoveItemDependentAurasAndCasts(pItem);
- }
-
// remove held enchantments, update expertise
if (slot == EQUIPMENT_SLOT_MAINHAND)
{
@@ -3071,9 +3071,6 @@ void Player::DestroyItem(uint8 bag, uint8 slot, bool update)
if (slot < EQUIPMENT_SLOT_END)
{
- // remove item dependent auras and casts (only weapon and armor slots)
- RemoveItemDependentAurasAndCasts(pItem);
-
// update expertise and armor penetration - passive auras may need it
switch (slot)
{
@@ -3543,6 +3540,8 @@ void Player::SwapItem(uint16 src, uint16 dst)
Item* pSrcItem = GetItemByPos(srcbag, srcslot);
Item* pDstItem = GetItemByPos(dstbag, dstslot);
+ bool isUnequipingItem = false;
+
if (!pSrcItem)
return;
@@ -3573,6 +3572,7 @@ void Player::SwapItem(uint16 src, uint16 dst)
SendEquipError(msg, pSrcItem, pDstItem);
return;
}
+ isUnequipingItem = true;
}
// anti-wpe
@@ -3673,6 +3673,9 @@ void Player::SwapItem(uint16 src, uint16 dst)
AutoUnequipOffhandIfNeed();
}
+ if (isUnequipingItem)
+ sScriptMgr->OnPlayerUnequip(this, pSrcItem);
+
return;
}
@@ -3865,8 +3868,8 @@ void Player::SwapItem(uint16 src, uint16 dst)
}
// now do moves, remove...
- RemoveItem(dstbag, dstslot, false, true);
- RemoveItem(srcbag, srcslot, false, true);
+ RemoveItem(dstbag, dstslot, false);
+ RemoveItem(srcbag, srcslot, false);
// add to dest
if (IsInventoryPos(dst))
@@ -4355,13 +4358,12 @@ void Player::ApplyEnchantment(Item* item, EnchantmentSlot slot, bool apply, bool
// processed in Player::CastItemCombatSpell
break;
case ITEM_ENCHANTMENT_TYPE_DAMAGE:
- if (item->GetSlot() == EQUIPMENT_SLOT_MAINHAND)
- HandleStatModifier(UNIT_MOD_DAMAGE_MAINHAND, TOTAL_VALUE, float(enchant_amount), apply);
- else if (item->GetSlot() == EQUIPMENT_SLOT_OFFHAND)
- HandleStatModifier(UNIT_MOD_DAMAGE_OFFHAND, TOTAL_VALUE, float(enchant_amount), apply);
- else if (item->GetSlot() == EQUIPMENT_SLOT_RANGED)
- HandleStatModifier(UNIT_MOD_DAMAGE_RANGED, TOTAL_VALUE, float(enchant_amount), apply);
+ {
+ WeaponAttackType const attackType = Player::GetAttackBySlot(item->GetSlot());
+ if (attackType != MAX_ATTACK)
+ UpdateDamageDoneMods(attackType, apply ? -1 : slot);
break;
+ }
case ITEM_ENCHANTMENT_TYPE_EQUIP_SPELL:
if (enchant_spell_id)
{
@@ -4412,7 +4414,7 @@ void Player::ApplyEnchantment(Item* item, EnchantmentSlot slot, bool apply, bool
}
}
- HandleStatModifier(UnitMods(UNIT_MOD_RESISTANCE_START + enchant_spell_id), TOTAL_VALUE, float(enchant_amount), apply);
+ HandleStatFlatModifier(UnitMods(UNIT_MOD_RESISTANCE_START + enchant_spell_id), TOTAL_VALUE, float(enchant_amount), apply);
break;
case ITEM_ENCHANTMENT_TYPE_STAT:
{
@@ -4439,36 +4441,36 @@ void Player::ApplyEnchantment(Item* item, EnchantmentSlot slot, bool apply, bool
{
case ITEM_MOD_MANA:
LOG_DEBUG("entities.player.items", "+ {} MANA", enchant_amount);
- HandleStatModifier(UNIT_MOD_MANA, BASE_VALUE, float(enchant_amount), apply);
+ HandleStatFlatModifier(UNIT_MOD_MANA, BASE_VALUE, float(enchant_amount), apply);
break;
case ITEM_MOD_HEALTH:
LOG_DEBUG("entities.player.items", "+ {} HEALTH", enchant_amount);
- HandleStatModifier(UNIT_MOD_HEALTH, BASE_VALUE, float(enchant_amount), apply);
+ HandleStatFlatModifier(UNIT_MOD_HEALTH, BASE_VALUE, float(enchant_amount), apply);
break;
case ITEM_MOD_AGILITY:
LOG_DEBUG("entities.player.items", "+ {} AGILITY", enchant_amount);
- HandleStatModifier(UNIT_MOD_STAT_AGILITY, TOTAL_VALUE, float(enchant_amount), apply);
- ApplyStatBuffMod(STAT_AGILITY, (float)enchant_amount, apply);
+ HandleStatFlatModifier(UNIT_MOD_STAT_AGILITY, TOTAL_VALUE, float(enchant_amount), apply);
+ UpdateStatBuffMod(STAT_AGILITY);
break;
case ITEM_MOD_STRENGTH:
LOG_DEBUG("entities.player.items", "+ {} STRENGTH", enchant_amount);
- HandleStatModifier(UNIT_MOD_STAT_STRENGTH, TOTAL_VALUE, float(enchant_amount), apply);
- ApplyStatBuffMod(STAT_STRENGTH, (float)enchant_amount, apply);
+ HandleStatFlatModifier(UNIT_MOD_STAT_STRENGTH, TOTAL_VALUE, float(enchant_amount), apply);
+ UpdateStatBuffMod(STAT_STRENGTH);
break;
case ITEM_MOD_INTELLECT:
LOG_DEBUG("entities.player.items", "+ {} INTELLECT", enchant_amount);
- HandleStatModifier(UNIT_MOD_STAT_INTELLECT, TOTAL_VALUE, float(enchant_amount), apply);
- ApplyStatBuffMod(STAT_INTELLECT, (float)enchant_amount, apply);
+ HandleStatFlatModifier(UNIT_MOD_STAT_INTELLECT, TOTAL_VALUE, float(enchant_amount), apply);
+ UpdateStatBuffMod(STAT_INTELLECT);
break;
case ITEM_MOD_SPIRIT:
LOG_DEBUG("entities.player.items", "+ {} SPIRIT", enchant_amount);
- HandleStatModifier(UNIT_MOD_STAT_SPIRIT, TOTAL_VALUE, float(enchant_amount), apply);
- ApplyStatBuffMod(STAT_SPIRIT, (float)enchant_amount, apply);
+ HandleStatFlatModifier(UNIT_MOD_STAT_SPIRIT, TOTAL_VALUE, float(enchant_amount), apply);
+ UpdateStatBuffMod(STAT_SPIRIT);
break;
case ITEM_MOD_STAMINA:
LOG_DEBUG("entities.player.items", "+ {} STAMINA", enchant_amount);
- HandleStatModifier(UNIT_MOD_STAT_STAMINA, TOTAL_VALUE, float(enchant_amount), apply);
- ApplyStatBuffMod(STAT_STAMINA, (float)enchant_amount, apply);
+ HandleStatFlatModifier(UNIT_MOD_STAT_STAMINA, TOTAL_VALUE, float(enchant_amount), apply);
+ UpdateStatBuffMod(STAT_STAMINA);
break;
case ITEM_MOD_DEFENSE_SKILL_RATING:
ApplyRatingMod(CR_DEFENSE_SKILL, enchant_amount, apply);
@@ -4579,12 +4581,12 @@ void Player::ApplyEnchantment(Item* item, EnchantmentSlot slot, bool apply, bool
LOG_DEBUG("entities.player.items", "+ {} EXPERTISE", enchant_amount);
break;
case ITEM_MOD_ATTACK_POWER:
- HandleStatModifier(UNIT_MOD_ATTACK_POWER, TOTAL_VALUE, float(enchant_amount), apply);
- HandleStatModifier(UNIT_MOD_ATTACK_POWER_RANGED, TOTAL_VALUE, float(enchant_amount), apply);
+ HandleStatFlatModifier(UNIT_MOD_ATTACK_POWER, TOTAL_VALUE, float(enchant_amount), apply);
+ HandleStatFlatModifier(UNIT_MOD_ATTACK_POWER_RANGED, TOTAL_VALUE, float(enchant_amount), apply);
LOG_DEBUG("entities.player.items", "+ {} ATTACK_POWER", enchant_amount);
break;
case ITEM_MOD_RANGED_ATTACK_POWER:
- HandleStatModifier(UNIT_MOD_ATTACK_POWER_RANGED, TOTAL_VALUE, float(enchant_amount), apply);
+ HandleStatFlatModifier(UNIT_MOD_ATTACK_POWER_RANGED, TOTAL_VALUE, float(enchant_amount), apply);
LOG_DEBUG("entities.player.items", "+ {} RANGED_ATTACK_POWER", enchant_amount);
break;
// case ITEM_MOD_FERAL_ATTACK_POWER:
@@ -4612,7 +4614,7 @@ void Player::ApplyEnchantment(Item* item, EnchantmentSlot slot, bool apply, bool
LOG_DEBUG("entities.player.items", "+ {} SPELL_PENETRATION", enchant_amount);
break;
case ITEM_MOD_BLOCK_VALUE:
- HandleBaseModValue(SHIELD_BLOCK_VALUE, FLAT_MOD, float(enchant_amount), apply);
+ HandleBaseModFlatValue(SHIELD_BLOCK_VALUE, float(enchant_amount), apply);
LOG_DEBUG("entities.player.items", "+ {} BLOCK_VALUE", enchant_amount);
break;
/// @deprecated item mods
@@ -4631,20 +4633,9 @@ void Player::ApplyEnchantment(Item* item, EnchantmentSlot slot, bool apply, bool
}
case ITEM_ENCHANTMENT_TYPE_TOTEM: // Shaman Rockbiter Weapon
{
- if (IsClass(CLASS_SHAMAN, CLASS_CONTEXT_ABILITY))
- {
- float addValue = 0.0f;
- if (item->GetSlot() == EQUIPMENT_SLOT_MAINHAND)
- {
- addValue = float(enchant_amount * item->GetTemplate()->Delay / 1000.0f);
- HandleStatModifier(UNIT_MOD_DAMAGE_MAINHAND, TOTAL_VALUE, addValue, apply);
- }
- else if (item->GetSlot() == EQUIPMENT_SLOT_OFFHAND)
- {
- addValue = float(enchant_amount * item->GetTemplate()->Delay / 1000.0f);
- HandleStatModifier(UNIT_MOD_DAMAGE_OFFHAND, TOTAL_VALUE, addValue, apply);
- }
- }
+ WeaponAttackType const attackType = Player::GetAttackBySlot(item->GetSlot());
+ if (attackType != MAX_ATTACK)
+ UpdateDamageDoneMods(attackType);
break;
}
case ITEM_ENCHANTMENT_TYPE_USE_SPELL:
@@ -6308,7 +6299,7 @@ void Player::_LoadQuestStatus(PreparedQueryResult result)
++slot;
}
- LOG_DEBUG("entities.player.loading", "Quest status is {{}} for quest {{}} for player ({})", questStatusData.Status, quest_id, GetGUID().ToString());
+ LOG_DEBUG("entities.player.loading", "Quest status is ({}) for quest ({}) for player ({})", questStatusData.Status, quest_id, GetGUID().ToString());
}
} while (result->NextRow());
}
@@ -6419,7 +6410,7 @@ void Player::_LoadWeeklyQuestStatus(PreparedQueryResult result)
continue;
m_weeklyquests.insert(quest_id);
- LOG_DEBUG("entities.player.loading", "Weekly quest {{}} cooldown for player ({})", quest_id, GetGUID().ToString());
+ LOG_DEBUG("entities.player.loading", "Weekly quest ({}) cooldown for player ({})", quest_id, GetGUID().ToString());
} while (result->NextRow());
}
@@ -6442,7 +6433,7 @@ void Player::_LoadSeasonalQuestStatus(PreparedQueryResult result)
continue;
m_seasonalquests[event_id].insert(quest_id);
- LOG_DEBUG("entities.player.loading", "Seasonal quest {{}} cooldown for player ({})", quest_id, GetGUID().ToString());
+ LOG_DEBUG("entities.player.loading", "Seasonal quest ({}) cooldown for player ({})", quest_id, GetGUID().ToString());
} while (result->NextRow());
}
@@ -6464,7 +6455,7 @@ void Player::_LoadMonthlyQuestStatus(PreparedQueryResult result)
continue;
m_monthlyquests.insert(quest_id);
- LOG_DEBUG("entities.player.loading", "Monthly quest {{}} cooldown for player ({})", quest_id, GetGUID().ToString());
+ LOG_DEBUG("entities.player.loading", "Monthly quest ({}) cooldown for player ({})", quest_id, GetGUID().ToString());
} while (result->NextRow());
}
diff --git a/src/server/game/Entities/Player/PlayerUpdates.cpp b/src/server/game/Entities/Player/PlayerUpdates.cpp
index 2913f66238..469131a65c 100644
--- a/src/server/game/Entities/Player/PlayerUpdates.cpp
+++ b/src/server/game/Entities/Player/PlayerUpdates.cpp
@@ -204,6 +204,9 @@ void Player::Update(uint32 p_time)
// do attack
AttackerStateUpdate(victim, BASE_ATTACK);
resetAttackTimer(BASE_ATTACK);
+
+ // Blizzlike: Reset ranged swing timer when performing melee attack
+ resetAttackTimer(RANGED_ATTACK);
}
}
@@ -223,6 +226,9 @@ void Player::Update(uint32 p_time)
// do attack
AttackerStateUpdate(victim, OFF_ATTACK);
resetAttackTimer(OFF_ATTACK);
+
+ // Blizzlike: Reset ranged swing timer when performing melee attack
+ resetAttackTimer(RANGED_ATTACK);
}
}
@@ -313,6 +319,23 @@ void Player::Update(uint32 p_time)
{
m_regenTimer += p_time;
RegenerateAll();
+
+ // Apply buffs from items with Apply on Equip trigger if they are not present.
+ for (uint8 i = 0; i < INVENTORY_SLOT_BAG_END; ++i)
+ {
+ if (!m_items[i])
+ continue;
+
+ std::vector<uint32> spellIDs;
+ m_items[i]->GetOnEquipSpellIDs(spellIDs);
+ bool apply = false;
+ for (uint32 spellID : spellIDs)
+ if (!apply && !HasAura(spellID))
+ apply = true;
+
+ if (apply)
+ ApplyItemEquipSpell(m_items[i], true, false);
+ }
}
if (m_deathState == DeathState::JustDied)
@@ -701,7 +724,7 @@ void Player::UpdateRating(CombatRating cr)
void Player::UpdateAllRatings()
{
- for (int cr = 0; cr < MAX_COMBAT_RATING; ++cr)
+ for (uint8 cr = 0; cr < MAX_COMBAT_RATING; ++cr)
UpdateRating(CombatRating(cr));
}
diff --git a/src/server/game/Entities/Unit/StatSystem.cpp b/src/server/game/Entities/Unit/StatSystem.cpp
index 9009bc7d73..24c025ac6d 100644
--- a/src/server/game/Entities/Unit/StatSystem.cpp
+++ b/src/server/game/Entities/Unit/StatSystem.cpp
@@ -157,7 +157,7 @@ bool Player::UpdateStats(Stats stat)
mask |= (*i)->GetMiscValue();
if (mask)
{
- for (uint32 rating = 0; rating < MAX_COMBAT_RATING; ++rating)
+ for (uint8 rating = 0; rating < MAX_COMBAT_RATING; ++rating)
if (mask & (1 << rating))
ApplyRatingMod(CombatRating(rating), 0, true);
}
@@ -204,7 +204,7 @@ void Player::UpdateSpellDamageAndHealingBonus()
bool Player::UpdateAllStats()
{
- for (int8 i = STAT_STRENGTH; i < MAX_STATS; ++i)
+ for (uint8 i = STAT_STRENGTH; i < MAX_STATS; ++i)
{
float value = GetTotalStatValue(Stats(i));
SetStat(Stats(i), int32(value));
@@ -247,9 +247,9 @@ void Player::UpdateResistances(uint32 school)
float value = 0.0f;
UnitMods unitMod = UnitMods(UNIT_MOD_RESISTANCE_START + school);
- value = GetModifierValue(unitMod, BASE_VALUE);
- value *= GetModifierValue(unitMod, BASE_PCT);
- value += GetModifierValue(unitMod, TOTAL_VALUE);
+ value = GetFlatModifierValue(unitMod, BASE_VALUE);
+ value *= GetPctModifierValue(unitMod, BASE_PCT);
+ value += GetFlatModifierValue(unitMod, TOTAL_VALUE);
AuraEffectList const& mResbyIntellect = GetAuraEffectsByType(SPELL_AURA_MOD_RESISTANCE_OF_STAT_PERCENT);
for(AuraEffectList::const_iterator i = mResbyIntellect.begin(); i != mResbyIntellect.end(); ++i)
@@ -258,7 +258,7 @@ void Player::UpdateResistances(uint32 school)
value += int32(GetStat(Stats((*i)->GetMiscValueB())) * (*i)->GetAmount() / 100.0f);
}
- value *= GetModifierValue(unitMod, TOTAL_PCT);
+ value *= GetPctModifierValue(unitMod, TOTAL_PCT);
SetResistance(SpellSchools(school), int32(value));
}
@@ -270,10 +270,10 @@ void Player::UpdateArmor()
{
UnitMods unitMod = UNIT_MOD_ARMOR;
- float value = GetModifierValue(unitMod, BASE_VALUE); // base armor (from items)
- value *= GetModifierValue(unitMod, BASE_PCT); // armor percent from items
- value += GetStat(STAT_AGILITY) * 2.0f; // armor bonus from stats
- value += GetModifierValue(unitMod, TOTAL_VALUE);
+ float value = GetFlatModifierValue(unitMod, BASE_VALUE); // base armor (from items)
+ value *= GetPctModifierValue(unitMod, BASE_PCT); // armor percent from items
+ value += GetStat(STAT_AGILITY) * 2.0f; // armor bonus from stats
+ value += GetFlatModifierValue(unitMod, TOTAL_VALUE);
//add dynamic flat mods
AuraEffectList const& mResbyIntellect = GetAuraEffectsByType(SPELL_AURA_MOD_RESISTANCE_OF_STAT_PERCENT);
@@ -283,7 +283,7 @@ void Player::UpdateArmor()
value += CalculatePct(GetStat(Stats((*i)->GetMiscValueB())), (*i)->GetAmount());
}
- value *= GetModifierValue(unitMod, TOTAL_PCT);
+ value *= GetPctModifierValue(unitMod, TOTAL_PCT);
SetArmor(int32(value));
@@ -314,10 +314,10 @@ void Player::UpdateMaxHealth()
{
UnitMods unitMod = UNIT_MOD_HEALTH;
- float value = GetModifierValue(unitMod, BASE_VALUE) + GetCreateHealth();
- value *= GetModifierValue(unitMod, BASE_PCT);
- value += GetModifierValue(unitMod, TOTAL_VALUE) + GetHealthBonusFromStamina();
- value *= GetModifierValue(unitMod, TOTAL_PCT);
+ float value = GetFlatModifierValue(unitMod, BASE_VALUE) + GetCreateHealth();
+ value *= GetPctModifierValue(unitMod, BASE_PCT);
+ value += GetFlatModifierValue(unitMod, TOTAL_VALUE) + GetHealthBonusFromStamina();
+ value *= GetPctModifierValue(unitMod, TOTAL_PCT);
sScriptMgr->OnPlayerAfterUpdateMaxHealth(this, value);
SetMaxHealth((uint32)value);
@@ -329,10 +329,10 @@ void Player::UpdateMaxPower(Powers power)
float bonusPower = (power == POWER_MANA && GetCreatePowers(power) > 0) ? GetManaBonusFromIntellect() : 0;
- float value = GetModifierValue(unitMod, BASE_VALUE) + GetCreatePowers(power);
- value *= GetModifierValue(unitMod, BASE_PCT);
- value += GetModifierValue(unitMod, TOTAL_VALUE) + bonusPower;
- value *= GetModifierValue(unitMod, TOTAL_PCT);
+ float value = GetFlatModifierValue(unitMod, BASE_VALUE) + GetCreatePowers(power);
+ value *= GetPctModifierValue(unitMod, BASE_PCT);
+ value += GetFlatModifierValue(unitMod, TOTAL_VALUE) + bonusPower;
+ value *= GetPctModifierValue(unitMod, TOTAL_PCT);
sScriptMgr->OnPlayerAfterUpdateMaxPower(this, power, value);
SetMaxPower(power, uint32(value));
@@ -487,10 +487,10 @@ void Player::UpdateAttackPowerAndDamage(bool ranged)
}
}
- SetModifierValue(unitMod, BASE_VALUE, val2);
+ SetStatFlatModifier(unitMod, BASE_VALUE, val2);
- float base_attPower = GetModifierValue(unitMod, BASE_VALUE) * GetModifierValue(unitMod, BASE_PCT);
- float attPowerMod = GetModifierValue(unitMod, TOTAL_VALUE);
+ float base_attPower = GetFlatModifierValue(unitMod, BASE_VALUE) * GetPctModifierValue(unitMod, BASE_PCT);
+ float attPowerMod = GetFlatModifierValue(unitMod, TOTAL_VALUE);
//add dynamic flat mods
if (ranged)
@@ -514,7 +514,7 @@ void Player::UpdateAttackPowerAndDamage(bool ranged)
attPowerMod += int32(GetArmor() / (*iter)->GetAmount());
}
- float attPowerMultiplier = GetModifierValue(unitMod, TOTAL_PCT) - 1.0f;
+ float attPowerMultiplier = GetPctModifierValue(unitMod, TOTAL_PCT) - 1.0f;
sScriptMgr->OnPlayerAfterUpdateAttackPowerAndDamage(this, level, base_attPower, attPowerMod, attPowerMultiplier, ranged);
SetInt32Value(index, (uint32)base_attPower); //UNIT_FIELD_(RANGED)_ATTACK_POWER field
@@ -576,10 +576,10 @@ void Player::CalculateMinMaxDamage(WeaponAttackType attType, bool normalized, bo
float attackSpeedMod = GetAPMultiplier(attType, normalized);
- float baseValue = GetModifierValue(unitMod, BASE_VALUE) + GetTotalAttackPowerValue(attType) / 14.0f * attackSpeedMod;
- float basePct = GetModifierValue(unitMod, BASE_PCT);
- float totalValue = GetModifierValue(unitMod, TOTAL_VALUE);
- float totalPct = addTotalPct ? GetModifierValue(unitMod, TOTAL_PCT) : 1.0f;
+ float baseValue = GetFlatModifierValue(unitMod, BASE_VALUE) + GetTotalAttackPowerValue(attType) / 14.0f * attackSpeedMod;
+ float basePct = GetPctModifierValue(unitMod, BASE_PCT);
+ float totalValue = GetFlatModifierValue(unitMod, TOTAL_VALUE);
+ float totalPct = addTotalPct ? GetPctModifierValue(unitMod, TOTAL_PCT) : 1.0f;
float weaponMinDamage = GetWeaponDamageRange(attType, MINDAMAGE);
float weaponMaxDamage = GetWeaponDamageRange(attType, MAXDAMAGE);
@@ -681,7 +681,8 @@ void Player::UpdateCritPercentage(WeaponAttackType attType)
break;
}
- float value = GetTotalPercentageModValue(modGroup) + GetRatingBonusValue(cr);
+ // flat = bonus from crit auras, pct = bonus from agility, combat rating = mods from items
+ float value = GetBaseModValue(modGroup, FLAT_MOD) + GetBaseModValue(modGroup, PCT_MOD) + GetRatingBonusValue(cr);
// Modify crit from weapon skill and maximized defense skill of same level victim difference
value += (int32(GetWeaponSkillValue(attType)) - int32(GetMaxSkillValueForLevel())) * 0.04f;
@@ -698,9 +699,9 @@ void Player::UpdateAllCritPercentages()
{
float value = GetMeleeCritFromAgility();
- SetBaseModValue(CRIT_PERCENTAGE, PCT_MOD, value);
- SetBaseModValue(OFFHAND_CRIT_PERCENTAGE, PCT_MOD, value);
- SetBaseModValue(RANGED_CRIT_PERCENTAGE, PCT_MOD, value);
+ SetBaseModPctValue(CRIT_PERCENTAGE, value);
+ SetBaseModPctValue(OFFHAND_CRIT_PERCENTAGE, value);
+ SetBaseModPctValue(RANGED_CRIT_PERCENTAGE, value);
UpdateCritPercentage(BASE_ATTACK);
UpdateCritPercentage(OFF_ATTACK);
@@ -849,7 +850,7 @@ void Player::UpdateSpellCritChance(uint32 school)
// Crit from Intellect
crit += GetSpellCritFromIntellect();
// Increase crit from SPELL_AURA_MOD_SPELL_CRIT_CHANCE
- crit += GetTotalAuraModifierAreaExclusive(SPELL_AURA_MOD_SPELL_CRIT_CHANCE);
+ crit += GetTotalAuraModifier(SPELL_AURA_MOD_SPELL_CRIT_CHANCE);
// Increase crit from SPELL_AURA_MOD_CRIT_PCT
crit += GetTotalAuraModifier(SPELL_AURA_MOD_CRIT_PCT);
// Increase crit by school from SPELL_AURA_MOD_SPELL_CRIT_CHANCE_SCHOOL
@@ -900,16 +901,11 @@ void Player::UpdateExpertise(WeaponAttackType attack)
Item* weapon = GetWeaponForAttack(attack, true);
- AuraEffectList const& expAuras = GetAuraEffectsByType(SPELL_AURA_MOD_EXPERTISE);
- for (AuraEffectList::const_iterator itr = expAuras.begin(); itr != expAuras.end(); ++itr)
+ expertise += GetTotalAuraModifier(SPELL_AURA_MOD_EXPERTISE, [weapon](AuraEffect const* aurEff)
{
- // item neutral spell
- if ((*itr)->GetSpellInfo()->EquippedItemClass == -1)
- expertise += (*itr)->GetAmount();
- // item dependent spell
- else if (weapon && weapon->IsFitToSpellRequirements((*itr)->GetSpellInfo()))
- expertise += (*itr)->GetAmount();
- }
+ return aurEff->GetSpellInfo()->EquippedItemClass == -1 || // item neutral spell
+ (weapon && weapon->IsFitToSpellRequirements(aurEff->GetSpellInfo())); // item dependent spell
+ });
if (expertise < 0)
expertise = 0;
@@ -1100,9 +1096,9 @@ void Creature::UpdateAttackPowerAndDamage(bool ranged)
indexMulti = UNIT_FIELD_RANGED_ATTACK_POWER_MULTIPLIER;
}
- float baseAttackPower = GetModifierValue(unitMod, BASE_VALUE) * GetModifierValue(unitMod, BASE_PCT);
- float attackPowerMod = GetModifierValue(unitMod, TOTAL_VALUE);
- float attackPowerMultiplier = GetModifierValue(unitMod, TOTAL_PCT) - 1.0f;
+ float baseAttackPower = GetFlatModifierValue(unitMod, BASE_VALUE) * GetPctModifierValue(unitMod, BASE_PCT);
+ float attackPowerMod = GetFlatModifierValue(unitMod, TOTAL_VALUE);
+ float attackPowerMultiplier = GetPctModifierValue(unitMod, TOTAL_PCT) - 1.0f;
SetInt32Value(index, uint32(baseAttackPower)); // UNIT_FIELD_(RANGED)_ATTACK_POWER
SetInt32Value(indexMod, uint32(attackPowerMod)); // UNIT_FIELD_(RANGED)_ATTACK_POWER_MODS
@@ -1166,10 +1162,10 @@ void Creature::CalculateMinMaxDamage(WeaponAttackType attType, bool normalized,
float attackPower = GetTotalAttackPowerValue(attType);
float attackSpeedMulti = GetAPMultiplier(attType, normalized);
- float baseValue = GetModifierValue(unitMod, BASE_VALUE) + (attackPower / 14.0f) * variance;
- float basePct = GetModifierValue(unitMod, BASE_PCT) * attackSpeedMulti;
- float totalValue = GetModifierValue(unitMod, TOTAL_VALUE);
- float totalPct = addTotalPct ? GetModifierValue(unitMod, TOTAL_PCT) : 1.0f;
+ float baseValue = GetFlatModifierValue(unitMod, BASE_VALUE) + (attackPower / 14.0f) * variance;
+ float basePct = GetPctModifierValue(unitMod, BASE_PCT) * attackSpeedMulti;
+ float totalValue = GetFlatModifierValue(unitMod, TOTAL_VALUE);
+ float totalPct = addTotalPct ? GetPctModifierValue(unitMod, TOTAL_PCT) : 1.0f;
float dmgMultiplier = GetCreatureTemplate()->DamageModifier; // = DamageModifier * _GetDamageMod(rank);
minDamage = ((weaponMinDamage + baseValue) * dmgMultiplier * basePct + totalValue) * totalPct;
@@ -1233,11 +1229,11 @@ bool Guardian::UpdateAllStats()
void Guardian::UpdateArmor()
{
- float value = GetModifierValue(UNIT_MOD_ARMOR, BASE_VALUE);
- value *= GetModifierValue(UNIT_MOD_ARMOR, BASE_PCT);
+ float value = GetFlatModifierValue(UNIT_MOD_ARMOR, BASE_VALUE);
+ value *= GetPctModifierValue(UNIT_MOD_ARMOR, BASE_PCT);
value += std::max<float>(GetStat(STAT_AGILITY) - GetCreateStat(STAT_AGILITY), 0.0f) * 2.0f;
- value += GetModifierValue(UNIT_MOD_ARMOR, TOTAL_VALUE);
- value *= GetModifierValue(UNIT_MOD_ARMOR, TOTAL_PCT);
+ value += GetFlatModifierValue(UNIT_MOD_ARMOR, TOTAL_VALUE);
+ value *= GetPctModifierValue(UNIT_MOD_ARMOR, TOTAL_PCT);
SetArmor(int32(value));
}
@@ -1278,10 +1274,10 @@ void Guardian::UpdateMaxHealth()
break;
}
- float value = GetModifierValue(unitMod, BASE_VALUE);// xinef: Do NOT add base health TWICE + GetCreateHealth();
- value *= GetModifierValue(unitMod, BASE_PCT);
- value += GetModifierValue(unitMod, TOTAL_VALUE) + stamina * multiplicator;
- value *= GetModifierValue(unitMod, TOTAL_PCT);
+ float value = GetFlatModifierValue(unitMod, BASE_VALUE);// xinef: Do NOT add base health TWICE + GetCreateHealth();
+ value *= GetPctModifierValue(unitMod, BASE_PCT);
+ value += GetFlatModifierValue(unitMod, TOTAL_VALUE) + stamina * multiplicator;
+ value *= GetPctModifierValue(unitMod, TOTAL_PCT);
SetMaxHealth((uint32)value);
}
@@ -1311,11 +1307,11 @@ void Guardian::UpdateMaxPower(Powers power)
break;
}
- // xinef: Do NOT add base mana TWICE
- float value = GetModifierValue(unitMod, BASE_VALUE) + (power != POWER_MANA ? GetCreatePowers(power) : 0);
- value *= GetModifierValue(unitMod, BASE_PCT);
- value += GetModifierValue(unitMod, TOTAL_VALUE) + addValue * multiplicator;
- value *= GetModifierValue(unitMod, TOTAL_PCT);
+ // Do NOT add base mana TWICE
+ float value = GetFlatModifierValue(unitMod, BASE_VALUE) + (power != POWER_MANA ? GetCreatePowers(power) : 0);
+ value *= GetPctModifierValue(unitMod, BASE_PCT);
+ value += GetFlatModifierValue(unitMod, TOTAL_VALUE) + addValue * multiplicator;
+ value *= GetPctModifierValue(unitMod, TOTAL_PCT);
SetMaxPower(power, uint32(value));
}
@@ -1335,12 +1331,12 @@ void Guardian::UpdateAttackPowerAndDamage(bool ranged)
else
val = 2 * GetStat(STAT_STRENGTH) - 20.0f;
- SetModifierValue(unitMod, BASE_VALUE, val);
+ SetStatFlatModifier(unitMod, BASE_VALUE, val);
//in BASE_VALUE of UNIT_MOD_ATTACK_POWER for creatures we store data of meleeattackpower field in DB
- float base_attPower = GetModifierValue(unitMod, BASE_VALUE) * GetModifierValue(unitMod, BASE_PCT);
- float attPowerMod = GetModifierValue(unitMod, TOTAL_VALUE);
- float attPowerMultiplier = GetModifierValue(unitMod, TOTAL_PCT) - 1.0f;
+ float base_attPower = GetFlatModifierValue(unitMod, BASE_VALUE) * GetPctModifierValue(unitMod, BASE_PCT);
+ float attPowerMod = GetFlatModifierValue(unitMod, TOTAL_VALUE);
+ float attPowerMultiplier = GetPctModifierValue(unitMod, TOTAL_PCT) - 1.0f;
//UNIT_FIELD_(RANGED)_ATTACK_POWER field
SetInt32Value(UNIT_FIELD_ATTACK_POWER, (int32)base_attPower);
@@ -1362,10 +1358,10 @@ void Guardian::UpdateDamagePhysical(WeaponAttackType attType)
float att_speed = float(GetAttackTime(BASE_ATTACK)) / 1000.0f;
- float base_value = GetModifierValue(unitMod, BASE_VALUE) + GetTotalAttackPowerValue(attType) / 14.0f * att_speed;
- float base_pct = GetModifierValue(unitMod, BASE_PCT);
- float total_value = GetModifierValue(unitMod, TOTAL_VALUE);
- float total_pct = GetModifierValue(unitMod, TOTAL_PCT);
+ float base_value = GetFlatModifierValue(unitMod, BASE_VALUE) + GetTotalAttackPowerValue(attType) / 14.0f * att_speed;
+ float base_pct = GetPctModifierValue(unitMod, BASE_PCT);
+ float total_value = GetFlatModifierValue(unitMod, TOTAL_VALUE);
+ float total_pct = GetPctModifierValue(unitMod, TOTAL_PCT);
float weapon_mindamage = GetWeaponDamageRange(BASE_ATTACK, MINDAMAGE);
float weapon_maxdamage = GetWeaponDamageRange(BASE_ATTACK, MAXDAMAGE);
diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp
index 31da399db5..615732f59b 100644
--- a/src/server/game/Entities/Unit/Unit.cpp
+++ b/src/server/game/Entities/Unit/Unit.cpp
@@ -16,7 +16,6 @@
*/
#include "Unit.h"
-#include "AccountMgr.h"
#include "AreaDefines.h"
#include "ArenaSpectator.h"
#include "Battlefield.h"
@@ -35,6 +34,7 @@
#include "CreatureGroups.h"
#include "DisableMgr.h"
#include "DynamicVisibility.h"
+#include "Errors.h"
#include "GameObjectAI.h"
#include "GameTime.h"
#include "GridNotifiersImpl.h"
@@ -54,19 +54,19 @@
#include "Player.h"
#include "ReputationMgr.h"
#include "ScriptMgr.h"
-#include "SmartAI.h"
+#include "SharedDefines.h"
#include "Spell.h"
+#include "SpellAuraDefines.h"
#include "SpellAuraEffects.h"
#include "SpellAuras.h"
#include "SpellInfo.h"
#include "SpellMgr.h"
-#include "TargetedMovementGenerator.h"
#include "TemporarySummon.h"
-#include "Tokenize.h"
#include "Totem.h"
#include "TotemAI.h"
#include "Transport.h"
#include "UpdateFieldFlags.h"
+#include "UpdateFields.h"
#include "Util.h"
#include "Vehicle.h"
#include "World.h"
@@ -268,13 +268,13 @@ Unit::Unit() : WorldObject(),
for (uint8 i = 0; i < UNIT_MOD_END; ++i)
{
- m_auraModifiersGroup[i][BASE_VALUE] = 0.0f;
- m_auraModifiersGroup[i][BASE_PCT] = 1.0f;
- m_auraModifiersGroup[i][TOTAL_VALUE] = 0.0f;
- m_auraModifiersGroup[i][TOTAL_PCT] = 1.0f;
+ m_auraFlatModifiersGroup[i][BASE_VALUE] = 0.0f;
+ m_auraFlatModifiersGroup[i][TOTAL_VALUE] = 0.0f;
+ m_auraPctModifiersGroup[i][BASE_PCT] = 1.0f;
+ m_auraPctModifiersGroup[i][TOTAL_PCT] = 1.0f;
}
// implement 50% base damage from offhand
- m_auraModifiersGroup[UNIT_MOD_DAMAGE_OFFHAND][TOTAL_PCT] = 0.5f;
+ m_auraPctModifiersGroup[UNIT_MOD_DAMAGE_OFFHAND][TOTAL_PCT] = 0.5f;
for (uint8 i = 0; i < MAX_ATTACK; ++i)
{
@@ -596,7 +596,7 @@ void Unit::UpdateSplineMovement(uint32 t_diff)
DisableSpline();
if (movespline->HasAnimation() && IsCreature() && IsAlive())
- SetByteValue(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_ANIM_TIER, movespline->GetAnimationType());
+ SetAnimTier(AnimTier(movespline->GetAnimationType()));
}
// pussywizard: update always! not every 400ms, because movement generators need the actual position
@@ -628,7 +628,7 @@ void Unit::UpdateSplinePosition()
//if (HasUnitState(UNIT_STATE_CANNOT_TURN))
// loc.orientation = GetOrientation();
- if (IsPlayer())
+ if (IsPlayer() || IsPet())
UpdatePosition(loc.x, loc.y, loc.z, loc.orientation);
else
ToCreature()->SetPosition(loc.x, loc.y, loc.z, loc.orientation);
@@ -2096,22 +2096,22 @@ uint32 Unit::CalcArmorReducedDamage(Unit const* attacker, Unit const* victim, co
if (attacker->IsPlayer())
{
float bonusPct = 0;
- AuraEffectList const& armorPenAuras = attacker->GetAuraEffectsByType(SPELL_AURA_MOD_ARMOR_PENETRATION_PCT);
- for (AuraEffectList::const_iterator itr = armorPenAuras.begin(); itr != armorPenAuras.end(); ++itr)
+ bonusPct += attacker->GetTotalAuraModifier(SPELL_AURA_MOD_ARMOR_PENETRATION_PCT, [spellInfo,attacker](AuraEffect const* aurEff)
{
- if ((*itr)->GetSpellInfo()->EquippedItemClass == -1)
+ if (aurEff->GetSpellInfo()->EquippedItemClass == -1)
{
- if (!spellInfo || (*itr)->IsAffectedOnSpell(spellInfo) || (*itr)->GetMiscValue() & spellInfo->GetSchoolMask())
- bonusPct += (*itr)->GetAmount();
- else if (!(*itr)->GetMiscValue() && !(*itr)->HasSpellClassMask())
- bonusPct += (*itr)->GetAmount();
+ if (!spellInfo || aurEff->IsAffectedOnSpell(spellInfo) || aurEff->GetMiscValue() & spellInfo->GetSchoolMask())
+ return true;
+ else if (!aurEff->GetMiscValue() && !aurEff->HasSpellClassMask())
+ return true;
}
else
{
- if (attacker->ToPlayer()->HasItemFitToSpellRequirements((*itr)->GetSpellInfo()))
- bonusPct += (*itr)->GetAmount();
+ if (attacker->ToPlayer()->HasItemFitToSpellRequirements(aurEff->GetSpellInfo()))
+ return true;
}
- }
+ return false;
+ });
float maxArmorPen = 0;
if (victim->GetLevel() < 60)
@@ -3534,10 +3534,8 @@ SpellMissInfo Unit::SpellHitResult(Unit* victim, SpellInfo const* spell, bool Ca
if (CanReflect)
{
int32 reflectchance = victim->GetTotalAuraModifier(SPELL_AURA_REFLECT_SPELLS);
- Unit::AuraEffectList const& mReflectSpellsSchool = victim->GetAuraEffectsByType(SPELL_AURA_REFLECT_SPELLS_SCHOOL);
- for (Unit::AuraEffectList::const_iterator i = mReflectSpellsSchool.begin(); i != mReflectSpellsSchool.end(); ++i)
- if ((*i)->GetMiscValue() & spell->GetSchoolMask())
- reflectchance += (*i)->GetAmount();
+ reflectchance += victim->GetTotalAuraModifierByMiscMask(SPELL_AURA_REFLECT_SPELLS_SCHOOL, spell->GetSchoolMask());
+
if (reflectchance > 0 && roll_chance_i(reflectchance))
{
// Start triggers for remove charges if need (trigger only for victim, and mark as active spell)
@@ -3611,14 +3609,7 @@ SpellMissInfo Unit::SpellHitResult(Unit* victim, Spell const* spell, bool CanRef
if (CanReflect)
{
int32 reflectchance = victim->GetTotalAuraModifier(SPELL_AURA_REFLECT_SPELLS);
- Unit::AuraEffectList const& mReflectSpellsSchool = victim->GetAuraEffectsByType(SPELL_AURA_REFLECT_SPELLS_SCHOOL);
- for (Unit::AuraEffectList::const_iterator i = mReflectSpellsSchool.begin(); i != mReflectSpellsSchool.end(); ++i)
- {
- if ((*i)->GetMiscValue() & spell->GetSpellSchoolMask())
- {
- reflectchance += (*i)->GetAmount();
- }
- }
+ reflectchance += victim->GetTotalAuraModifierByMiscMask(SPELL_AURA_REFLECT_SPELLS_SCHOOL, spellInfo->GetSchoolMask());
if (reflectchance > 0 && roll_chance_i(reflectchance))
{
@@ -3799,14 +3790,10 @@ float Unit::GetUnitCriticalChance(WeaponAttackType attackType, Unit const* victi
else
crit += victim->GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_MELEE_CRIT_CHANCE);
- AuraEffectList const& mTotalAuraList = victim->GetAuraEffectsByType(SPELL_AURA_MOD_CRIT_CHANCE_FOR_CASTER);
- for (AuraEffectList::const_iterator i = mTotalAuraList.begin(); i != mTotalAuraList.end(); ++i)
+ crit += victim->GetTotalAuraModifier(SPELL_AURA_MOD_CRIT_CHANCE_FOR_CASTER, [this](AuraEffect const* aurEff)
{
- if (GetGUID() != (*i)->GetCasterGUID())
- continue;
-
- crit += (*i)->GetAmount();
- }
+ return GetGUID() == aurEff->GetCasterGUID();
+ });
// reduce crit chance from Rating for players
if (attackType != RANGED_ATTACK)
@@ -3990,6 +3977,10 @@ void Unit::_UpdateAutoRepeatSpell()
// Reset attack
resetAttackTimer(RANGED_ATTACK);
+
+ // Blizzlike: Reset melee swing timers when performing ranged attack
+ resetAttackTimer(BASE_ATTACK);
+ resetAttackTimer(OFF_ATTACK);
}
}
@@ -4420,16 +4411,85 @@ void Unit::DeMorph()
SetDisplayId(GetNativeDisplayId());
}
+int32 Unit::GetHighestExclusiveSameEffectSpellGroupValue(AuraEffect const* aurEff, AuraType auraType, bool checkMiscValue /*= false*/, int32 miscValue /*= 0*/) const
+{
+ int32 val = 0;
+ SpellSpellGroupMapBounds spellGroup = sSpellMgr->GetSpellSpellGroupMapBounds(aurEff->GetSpellInfo()->GetFirstRankSpell()->Id);
+ for (SpellSpellGroupMap::const_iterator itr = spellGroup.first; itr != spellGroup.second ; ++itr)
+ {
+ if (sSpellMgr->GetSpellGroupStackRule(itr->second) == SPELL_GROUP_STACK_RULE_EXCLUSIVE_SAME_EFFECT)
+ {
+ AuraEffectList const& auraEffList = GetAuraEffectsByType(auraType);
+ for (AuraEffectList::const_iterator auraItr = auraEffList.begin(); auraItr != auraEffList.end(); ++auraItr)
+ {
+ if (aurEff != (*auraItr) && (!checkMiscValue || (*auraItr)->GetMiscValue() == miscValue) &&
+ sSpellMgr->IsSpellMemberOfSpellGroup((*auraItr)->GetSpellInfo()->Id, itr->second))
+ {
+ // absolute value only
+ if (abs(val) < abs((*auraItr)->GetAmount()))
+ val = (*auraItr)->GetAmount();
+ }
+ }
+ }
+ }
+ return val;
+}
+
+bool Unit::IsHighestExclusiveAura(Aura const* aura, bool removeOtherAuraApplications /*= false*/)
+{
+ for (uint32 i = 0 ; i < MAX_SPELL_EFFECTS; ++i)
+ if (AuraEffect const* aurEff = aura->GetEffect(i))
+ if (!IsHighestExclusiveAuraEffect(aura->GetSpellInfo(), aurEff->GetAuraType(), aurEff->GetAmount(), aura->GetEffectMask(), removeOtherAuraApplications))
+ return false;
+
+ return true;
+}
+
+bool Unit::IsHighestExclusiveAuraEffect(SpellInfo const* spellInfo, AuraType auraType, int32 effectAmount, uint8 auraEffectMask, bool removeOtherAuraApplications /*= false*/)
+{
+ AuraEffectList const& auras = GetAuraEffectsByType(auraType);
+ for (Unit::AuraEffectList::const_iterator itr = auras.begin(); itr != auras.end();)
+ {
+ AuraEffect const* existingAurEff = (*itr);
+ ++itr;
+
+ if (sSpellMgr->CheckSpellGroupStackRules(spellInfo, existingAurEff->GetSpellInfo()) == SPELL_GROUP_STACK_RULE_EXCLUSIVE_HIGHEST)
+ {
+ int32 diff = abs(effectAmount) - abs(existingAurEff->GetAmount());
+ if (!diff)
+ for (int32 i = 0; i < MAX_SPELL_EFFECTS; ++i)
+ diff += int32((auraEffectMask & (1 << i)) >> i) - int32((existingAurEff->GetBase()->GetEffectMask() & (1 << i)) >> i);
+
+ if (diff > 0)
+ {
+ Aura const* base = existingAurEff->GetBase();
+ // no removing of area auras from the original owner, as that completely cancels them
+ if (removeOtherAuraApplications && (!base->IsArea() || base->GetOwner() != this))
+ {
+ if (AuraApplication* aurApp = existingAurEff->GetBase()->GetApplicationOfTarget(GetGUID()))
+ {
+ bool hasMoreThanOneEffect = base->HasMoreThanOneEffectForType(auraType);
+ uint32 removedAuras = m_removedAurasCount;
+ RemoveAura(aurApp);
+ if (hasMoreThanOneEffect || m_removedAurasCount > removedAuras)
+ itr = auras.begin();
+ }
+ }
+ }
+ else if (diff < 0)
+ return false;
+ }
+ }
+
+ return true;
+}
+
Aura* Unit::_TryStackingOrRefreshingExistingAura(SpellInfo const* newAura, uint8 effMask, Unit* caster, int32* baseAmount /*= nullptr*/, Item* castItem /*= nullptr*/, ObjectGuid casterGUID /*= ObjectGuid::Empty*/, bool periodicReset /*= false*/)
{
ASSERT(casterGUID || caster);
if (!casterGUID)
casterGUID = caster->GetGUID();
- // Xinef: Hax for mixology, best solution qq
- if (sSpellMgr->GetSpellGroup(newAura->Id) == 1)
- return nullptr;
-
// passive and Incanter's Absorption and auras with different type can stack with themselves any number of times
if (!newAura->IsMultiSlotAura())
{
@@ -4485,7 +4545,7 @@ void Unit::_AddAura(UnitAura* aura, Unit* caster)
ASSERT(!m_cleanupDone);
m_ownedAuras.insert(AuraMap::value_type(aura->GetId(), aura));
- _RemoveNoStackAurasDueToAura(aura);
+ _RemoveNoStackAurasDueToAura(aura, true);
if (aura->IsRemoved())
return;
@@ -4575,7 +4635,7 @@ void Unit::_ApplyAura(AuraApplication* aurApp, uint8 effMask)
{
Aura* aura = aurApp->GetBase();
- _RemoveNoStackAurasDueToAura(aura);
+ _RemoveNoStackAurasDueToAura(aura, false);
if (aurApp->GetRemoveMode())
return;
@@ -4733,7 +4793,7 @@ void Unit::_UnapplyAura(AuraApplication* aurApp, AuraRemoveMode removeMode)
ABORT();
}
-void Unit::_RemoveNoStackAurasDueToAura(Aura* aura)
+void Unit::_RemoveNoStackAurasDueToAura(Aura* aura, bool owned)
{
//SpellInfo const* spellProto = aura->GetSpellInfo();
@@ -4743,23 +4803,17 @@ void Unit::_RemoveNoStackAurasDueToAura(Aura* aura)
// if (spellProto->IsPassiveStackableWithRanks())
// return;
- bool remove = false;
- for (AuraApplicationMap::iterator i = m_appliedAuras.begin(); i != m_appliedAuras.end(); ++i)
+ ASSERT(aura);
+ if (!IsHighestExclusiveAura(aura))
{
- if (remove)
- {
- remove = false;
- i = m_appliedAuras.begin();
- }
-
- if (aura->CanStackWith(i->second->GetBase(), true))
- continue;
-
- RemoveAura(i, AURA_REMOVE_BY_DEFAULT);
- if (i == m_appliedAuras.end())
- break;
- remove = true;
+ aura->Remove();
+ return;
}
+
+ if (owned)
+ RemoveOwnedAuras([aura](Aura const* ownedAura) { return !aura->CanStackWith(ownedAura); });
+ else
+ RemoveAppliedAuras([aura](AuraApplication const* appliedAura) { return !aura->CanStackWith(appliedAura->GetBase()); });
}
void Unit::_RegisterAuraEffect(AuraEffect* aurEff, bool apply)
@@ -5987,232 +6041,309 @@ uint32 Unit::GetDoTsByCaster(ObjectGuid casterGUID) const
return dots;
}
-int32 Unit::GetTotalAuraModifierAreaExclusive(AuraType auratype) const
+int32 Unit::GetTotalAuraModifier(AuraType auraType, std::function<bool(AuraEffect const*)> const& predicate) const
{
+ AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auraType);
+ if (mTotalAuraList.empty())
+ return 0;
+
+ std::map<SpellGroup, int32> sameEffectSpellGroup;
int32 modifier = 0;
- int32 areaModifier = 0;
- AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auratype);
- for (AuraEffectList::const_iterator i = mTotalAuraList.begin(); i != mTotalAuraList.end(); ++i)
+ for (AuraEffect const* aurEff : mTotalAuraList)
{
- if ((*i)->GetSpellInfo()->HasAreaAuraEffect())
+ if (predicate(aurEff))
{
- if (areaModifier < (*i)->GetAmount())
- areaModifier = (*i)->GetAmount();
+ // Check if the Aura Effect has a the Same Effect Stack Rule and if so, use the highest amount of that SpellGroup
+ // If the Aura Effect does not have this Stack Rule, it returns false so we can add to the multiplier as usual
+ if (!sSpellMgr->AddSameEffectStackRuleSpellGroups(aurEff->GetSpellInfo(), static_cast<uint32>(auraType), aurEff->GetAmount(), sameEffectSpellGroup))
+ modifier += aurEff->GetAmount();
}
- else
- modifier += (*i)->GetAmount();
}
- return modifier + areaModifier;
-}
-
-int32 Unit::GetTotalAuraModifier(AuraType auratype) const
-{
- AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auratype);
- if (mTotalAuraList.empty())
- return 0;
-
- int32 modifier = 0;
-
- for (AuraEffectList::const_iterator i = mTotalAuraList.begin(); i != mTotalAuraList.end(); ++i)
- modifier += (*i)->GetAmount();
+ // Add the highest of the Same Effect Stack Rule SpellGroups to the accumulator
+ for (auto const& [_, amount] : sameEffectSpellGroup)
+ modifier += amount;
return modifier;
}
-float Unit::GetTotalAuraMultiplier(AuraType auratype) const
+float Unit::GetTotalAuraMultiplier(AuraType auraType, std::function<bool(AuraEffect const*)> const& predicate) const
{
+ AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auraType);
+ if (mTotalAuraList.empty())
+ return 1.0f;
+
+ std::map<SpellGroup, int32> sameEffectSpellGroup;
float multiplier = 1.0f;
- AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auratype);
- for (AuraEffectList::const_iterator i = mTotalAuraList.begin(); i != mTotalAuraList.end(); ++i)
- AddPct(multiplier, (*i)->GetAmount());
+ for (AuraEffect const* aurEff : mTotalAuraList)
+ {
+ if (predicate(aurEff))
+ {
+ // Check if the Aura Effect has a the Same Effect Stack Rule and if so, use the highest amount of that SpellGroup
+ // If the Aura Effect does not have this Stack Rule, it returns false so we can add to the multiplier as usual
+ if (!sSpellMgr->AddSameEffectStackRuleSpellGroups(aurEff->GetSpellInfo(), static_cast<uint32>(auraType), aurEff->GetAmount(), sameEffectSpellGroup))
+ AddPct(multiplier, aurEff->GetAmount());
+ }
+ }
+
+ // Add the highest of the Same Effect Stack Rule SpellGroups to the multiplier
+ for (auto itr = sameEffectSpellGroup.begin(); itr != sameEffectSpellGroup.end(); ++itr)
+ AddPct(multiplier, itr->second);
return multiplier;
}
-int32 Unit::GetMaxPositiveAuraModifier(AuraType auratype)
+int32 Unit::GetMaxPositiveAuraModifier(AuraType auraType, std::function<bool(AuraEffect const*)> const& predicate) const
{
- int32 modifier = 0;
+ AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auraType);
+ if (mTotalAuraList.empty())
+ return 0;
- AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auratype);
- for (AuraEffectList::const_iterator i = mTotalAuraList.begin(); i != mTotalAuraList.end(); ++i)
+ int32 modifier = 0;
+ for (AuraEffect const* aurEff : mTotalAuraList)
{
- if ((*i)->GetAmount() > modifier)
- modifier = (*i)->GetAmount();
+ if (predicate(aurEff))
+ modifier = std::max(modifier, aurEff->GetAmount());
}
return modifier;
}
-int32 Unit::GetMaxNegativeAuraModifier(AuraType auratype) const
+int32 Unit::GetMaxNegativeAuraModifier(AuraType auraType, std::function<bool(AuraEffect const*)> const& predicate) const
{
- int32 modifier = 0;
-
- AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auratype);
- for (AuraEffectList::const_iterator i = mTotalAuraList.begin(); i != mTotalAuraList.end(); ++i)
- if ((*i)->GetAmount() < modifier)
- modifier = (*i)->GetAmount();
-
- return modifier;
-}
+ AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auraType);
+ if (mTotalAuraList.empty())
+ return 0;
-int32 Unit::GetTotalAuraModifierByMiscMask(AuraType auratype, uint32 misc_mask) const
-{
int32 modifier = 0;
-
- AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auratype);
- for (AuraEffectList::const_iterator i = mTotalAuraList.begin(); i != mTotalAuraList.end(); ++i)
+ for (AuraEffect const* aurEff : mTotalAuraList)
{
- if ((*i)->GetMiscValue()& misc_mask)
- modifier += (*i)->GetAmount();
+ if (predicate(aurEff))
+ modifier = std::min(modifier, aurEff->GetAmount());
}
+
return modifier;
}
-float Unit::GetTotalAuraMultiplierByMiscMask(AuraType auratype, uint32 misc_mask) const
+int32 Unit::GetTotalAuraModifier(AuraType auraType) const
{
- float multiplier = 1.0f;
+ return GetTotalAuraModifier(auraType, [](AuraEffect const* /*aurEff*/) { return true; });
+}
- AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auratype);
- for (AuraEffectList::const_iterator i = mTotalAuraList.begin(); i != mTotalAuraList.end(); ++i)
- if (((*i)->GetMiscValue() & misc_mask))
- AddPct(multiplier, (*i)->GetAmount());
+float Unit::GetTotalAuraMultiplier(AuraType auraType) const
+{
+ return GetTotalAuraMultiplier(auraType, [](AuraEffect const* /*aurEff*/) { return true; });
+}
- return multiplier;
+int32 Unit::GetMaxPositiveAuraModifier(AuraType auraType) const
+{
+ return GetMaxPositiveAuraModifier(auraType, [](AuraEffect const* /*aurEff*/) { return true; });
}
-int32 Unit::GetMaxPositiveAuraModifierByMiscMask(AuraType auratype, uint32 misc_mask, const AuraEffect* except) const
+int32 Unit::GetMaxNegativeAuraModifier(AuraType auraType) const
{
- int32 modifier = 0;
+ return GetMaxNegativeAuraModifier(auraType, [](AuraEffect const* /*aurEff*/) { return true; });
+}
- AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auratype);
- for (AuraEffectList::const_iterator i = mTotalAuraList.begin(); i != mTotalAuraList.end(); ++i)
+int32 Unit::GetTotalAuraModifierByMiscMask(AuraType auraType, uint32 miscMask) const
+{
+ return GetTotalAuraModifier(auraType, [miscMask](AuraEffect const* aurEff) -> bool
{
- if (except != (*i) && (*i)->GetMiscValue()& misc_mask && (*i)->GetAmount() > modifier)
- modifier = (*i)->GetAmount();
- }
-
- return modifier;
+ if ((aurEff->GetMiscValue() & miscMask) != 0)
+ return true;
+ return false;
+ });
}
-int32 Unit::GetMaxNegativeAuraModifierByMiscMask(AuraType auratype, uint32 misc_mask) const
+float Unit::GetTotalAuraMultiplierByMiscMask(AuraType auraType, uint32 miscMask) const
{
- int32 modifier = 0;
-
- AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auratype);
- for (AuraEffectList::const_iterator i = mTotalAuraList.begin(); i != mTotalAuraList.end(); ++i)
+ return GetTotalAuraMultiplier(auraType, [miscMask](AuraEffect const* aurEff) -> bool
{
- if ((*i)->GetMiscValue()& misc_mask && (*i)->GetAmount() < modifier)
- modifier = (*i)->GetAmount();
- }
-
- return modifier;
+ if ((aurEff->GetMiscValue() & miscMask) != 0)
+ return true;
+ return false;
+ });
}
-int32 Unit::GetTotalAuraModifierByMiscValue(AuraType auratype, int32 misc_value) const
+int32 Unit::GetMaxPositiveAuraModifierByMiscMask(AuraType auraType, uint32 miscMask, AuraEffect const* except /*= nullptr*/) const
{
- int32 modifier = 0;
-
- AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auratype);
- for (AuraEffectList::const_iterator i = mTotalAuraList.begin(); i != mTotalAuraList.end(); ++i)
- if ((*i)->GetMiscValue() == misc_value)
- modifier += (*i)->GetAmount();
-
- return modifier;
+ return GetMaxPositiveAuraModifier(auraType, [miscMask, except](AuraEffect const* aurEff) -> bool
+ {
+ if (except != aurEff && (aurEff->GetMiscValue() & miscMask) != 0)
+ return true;
+ return false;
+ });
}
-float Unit::GetTotalAuraMultiplierByMiscValue(AuraType auratype, int32 misc_value) const
+int32 Unit::GetMaxNegativeAuraModifierByMiscMask(AuraType auraType, uint32 miscMask) const
{
- float multiplier = 1.0f;
-
- AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auratype);
- for (AuraEffectList::const_iterator i = mTotalAuraList.begin(); i != mTotalAuraList.end(); ++i)
- if ((*i)->GetMiscValue() == misc_value)
- AddPct(multiplier, (*i)->GetAmount());
-
- return multiplier;
+ return GetMaxNegativeAuraModifier(auraType, [miscMask](AuraEffect const* aurEff) -> bool
+ {
+ if ((aurEff->GetMiscValue() & miscMask) != 0)
+ return true;
+ return false;
+ });
}
-int32 Unit::GetMaxPositiveAuraModifierByMiscValue(AuraType auratype, int32 misc_value) const
+int32 Unit::GetTotalAuraModifierByMiscValue(AuraType auraType, int32 miscValue) const
{
- int32 modifier = 0;
-
- AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auratype);
- for (AuraEffectList::const_iterator i = mTotalAuraList.begin(); i != mTotalAuraList.end(); ++i)
+ return GetTotalAuraModifier(auraType, [miscValue](AuraEffect const* aurEff) -> bool
{
- if ((*i)->GetMiscValue() == misc_value && (*i)->GetAmount() > modifier)
- modifier = (*i)->GetAmount();
- }
+ if (aurEff->GetMiscValue() == miscValue)
+ return true;
+ return false;
+ });
+}
- return modifier;
+float Unit::GetTotalAuraMultiplierByMiscValue(AuraType auraType, int32 miscValue) const
+{
+ return GetTotalAuraMultiplier(auraType, [miscValue](AuraEffect const* aurEff) -> bool
+ {
+ if (aurEff->GetMiscValue() == miscValue)
+ return true;
+ return false;
+ });
}
-int32 Unit::GetMaxNegativeAuraModifierByMiscValue(AuraType auratype, int32 misc_value) const
+int32 Unit::GetMaxPositiveAuraModifierByMiscValue(AuraType auraType, int32 miscValue) const
{
- int32 modifier = 0;
+ return GetMaxPositiveAuraModifier(auraType, [miscValue](AuraEffect const* aurEff) -> bool
+ {
+ if (aurEff->GetMiscValue() == miscValue)
+ return true;
+ return false;
+ });
+}
- AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auratype);
- for (AuraEffectList::const_iterator i = mTotalAuraList.begin(); i != mTotalAuraList.end(); ++i)
+int32 Unit::GetMaxNegativeAuraModifierByMiscValue(AuraType auraType, int32 miscValue) const
+{
+ return GetMaxNegativeAuraModifier(auraType, [miscValue](AuraEffect const* aurEff) -> bool
{
- if ((*i)->GetMiscValue() == misc_value && (*i)->GetAmount() < modifier)
- modifier = (*i)->GetAmount();
- }
+ if (aurEff->GetMiscValue() == miscValue)
+ return true;
+ return false;
+ });
+}
- return modifier;
+int32 Unit::GetTotalAuraModifierByAffectMask(AuraType auraType, SpellInfo const* affectedSpell) const
+{
+ return GetTotalAuraModifier(auraType, [affectedSpell](AuraEffect const* aurEff) -> bool
+ {
+ if (aurEff->IsAffectedOnSpell(affectedSpell))
+ return true;
+ return false;
+ });
}
-int32 Unit::GetTotalAuraModifierByAffectMask(AuraType auratype, SpellInfo const* affectedSpell) const
+float Unit::GetTotalAuraMultiplierByAffectMask(AuraType auraType, SpellInfo const* affectedSpell) const
{
- int32 modifier = 0;
+ return GetTotalAuraMultiplier(auraType, [affectedSpell](AuraEffect const* aurEff) -> bool
+ {
+ if (aurEff->IsAffectedOnSpell(affectedSpell))
+ return true;
+ return false;
+ });
+}
- AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auratype);
- for (AuraEffectList::const_iterator i = mTotalAuraList.begin(); i != mTotalAuraList.end(); ++i)
- if ((*i)->IsAffectedOnSpell(affectedSpell))
- modifier += (*i)->GetAmount();
+int32 Unit::GetMaxPositiveAuraModifierByAffectMask(AuraType auraType, SpellInfo const* affectedSpell) const
+{
+ return GetMaxPositiveAuraModifier(auraType, [affectedSpell](AuraEffect const* aurEff) -> bool
+ {
+ if (aurEff->IsAffectedOnSpell(affectedSpell))
+ return true;
+ return false;
+ });
+}
- return modifier;
+int32 Unit::GetMaxNegativeAuraModifierByAffectMask(AuraType auraType, SpellInfo const* affectedSpell) const
+{
+ return GetMaxNegativeAuraModifier(auraType, [affectedSpell](AuraEffect const* aurEff) -> bool
+ {
+ if (aurEff->IsAffectedOnSpell(affectedSpell))
+ return true;
+ return false;
+ });
}
-float Unit::GetTotalAuraMultiplierByAffectMask(AuraType auratype, SpellInfo const* affectedSpell) const
+void Unit::UpdateResistanceBuffModsMod(SpellSchools school)
{
- float multiplier = 1.0f;
+ float modPos = 0.0f;
+ float modNeg = 0.0f;
- AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auratype);
- for (AuraEffectList::const_iterator i = mTotalAuraList.begin(); i != mTotalAuraList.end(); ++i)
- if ((*i)->IsAffectedOnSpell(affectedSpell))
- AddPct(multiplier, (*i)->GetAmount());
+ // these auras are always positive
+ modPos = GetMaxPositiveAuraModifierByMiscMask(SPELL_AURA_MOD_RESISTANCE_EXCLUSIVE, 1 << school);
+ modPos += GetTotalAuraModifier(SPELL_AURA_MOD_RESISTANCE, [school](AuraEffect const* aurEff) -> bool
+ {
+ if ((aurEff->GetMiscValue() & (1 << school)) && aurEff->GetAmount() > 0)
+ return true;
+ return false;
+ });
- return multiplier;
+ modNeg = GetTotalAuraModifier(SPELL_AURA_MOD_RESISTANCE, [school](AuraEffect const* aurEff) -> bool
+ {
+ if ((aurEff->GetMiscValue() & (1 << school)) && aurEff->GetAmount() < 0)
+ return true;
+ return false;
+ });
+
+ float factor = GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_RESISTANCE_PCT, 1 << school);
+ modPos *= factor;
+ modNeg *= factor;
+
+ SetFloatValue(UNIT_FIELD_RESISTANCEBUFFMODSPOSITIVE + AsUnderlyingType(school), modPos);
+ SetFloatValue(UNIT_FIELD_RESISTANCEBUFFMODSNEGATIVE + AsUnderlyingType(school), modNeg);
}
-int32 Unit::GetMaxPositiveAuraModifierByAffectMask(AuraType auratype, SpellInfo const* affectedSpell) const
+void Unit::UpdateStatBuffMod(Stats stat)
{
- int32 modifier = 0;
+ float modPos = 0.0f;
+ float modNeg = 0.0f;
+ float factor = 0.0f;
- AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auratype);
- for (AuraEffectList::const_iterator i = mTotalAuraList.begin(); i != mTotalAuraList.end(); ++i)
+ UnitMods const unitMod = static_cast<UnitMods>(UNIT_MOD_STAT_START + AsUnderlyingType(stat));
+
+ // includes value from items and enchantments
+ float modValue = GetFlatModifierValue(unitMod, BASE_VALUE);
+ if (modValue > 0.f)
+ modPos += modValue;
+ else
+ modNeg += modValue;
+
+ modPos += GetTotalAuraModifier(SPELL_AURA_MOD_STAT, [stat](AuraEffect const* aurEff) -> bool
{
- if ((*i)->IsAffectedOnSpell(affectedSpell) && (*i)->GetAmount() > modifier)
- modifier = (*i)->GetAmount();
- }
+ if ((aurEff->GetMiscValue() < 0 || aurEff->GetMiscValue() == stat) && aurEff->GetAmount() > 0)
+ return true;
+ return false;
+ });
- return modifier;
-}
+ modNeg += GetTotalAuraModifier(SPELL_AURA_MOD_STAT, [stat](AuraEffect const* aurEff) -> bool
+ {
+ if ((aurEff->GetMiscValue() < 0 || aurEff->GetMiscValue() == stat) && aurEff->GetAmount() < 0)
+ return true;
+ return false;
+ });
-int32 Unit::GetMaxNegativeAuraModifierByAffectMask(AuraType auratype, SpellInfo const* affectedSpell) const
-{
- int32 modifier = 0;
+ factor = GetTotalAuraMultiplier(SPELL_AURA_MOD_PERCENT_STAT, [stat](AuraEffect const* aurEff) -> bool
+ {
+ if (aurEff->GetMiscValue() == -1 || aurEff->GetMiscValue() == stat)
+ return true;
+ return false;
+ });
- AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(auratype);
- for (AuraEffectList::const_iterator i = mTotalAuraList.begin(); i != mTotalAuraList.end(); ++i)
+ factor *= GetTotalAuraMultiplier(SPELL_AURA_MOD_TOTAL_STAT_PERCENTAGE, [stat](AuraEffect const* aurEff) -> bool
{
- if ((*i)->IsAffectedOnSpell(affectedSpell) && (*i)->GetAmount() < modifier)
- modifier = (*i)->GetAmount();
- }
+ if (aurEff->GetMiscValue() == -1 || aurEff->GetMiscValue() == stat)
+ return true;
+ return false;
+ });
- return modifier;
+ modPos *= factor;
+ modNeg *= factor;
+
+ SetFloatValue(UNIT_FIELD_POSSTAT0 + AsUnderlyingType(stat), modPos);
+ SetFloatValue(UNIT_FIELD_NEGSTAT0 + AsUnderlyingType(stat), modNeg);
}
void Unit::_RegisterDynObject(DynamicObject* dynObj)
@@ -11387,44 +11518,41 @@ float Unit::SpellPctDamageModsDone(Unit* victim, SpellInfo const* spellProto, Da
// Done total percent damage auras
float DoneTotalMod = 1.0f;
- AuraEffectList const& mModDamagePercentDone = GetAuraEffectsByType(SPELL_AURA_MOD_DAMAGE_PERCENT_DONE);
- for (AuraEffectList::const_iterator i = mModDamagePercentDone.begin(); i != mModDamagePercentDone.end(); ++i)
+ DoneTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_PERCENT_DONE, [spellProto, this, damagetype](AuraEffect const* aurEff)
{
// prevent apply mods from weapon specific case to non weapon specific spells (Example: thunder clap and two-handed weapon specialization)
- if (spellProto->EquippedItemClass == -1 && (*i)->GetSpellInfo()->EquippedItemClass != -1 &&
- !(*i)->GetSpellInfo()->HasAttribute(SPELL_ATTR5_AURA_AFFECTS_NOT_JUST_REQ_EQUIPPED_ITEM) && (*i)->GetMiscValue() == SPELL_SCHOOL_MASK_NORMAL)
+ if (spellProto->EquippedItemClass == -1 && aurEff->GetSpellInfo()->EquippedItemClass != -1 &&
+ !aurEff->GetSpellInfo()->HasAttribute(SPELL_ATTR5_AURA_AFFECTS_NOT_JUST_REQ_EQUIPPED_ITEM) && aurEff->GetMiscValue() == SPELL_SCHOOL_MASK_NORMAL)
{
- continue;
- }
-
- if (!spellProto->ValidateAttribute6SpellDamageMods(this, *i, damagetype == DOT))
- continue;
+ return false;
+ }
- if (!sScriptMgr->IsNeedModSpellDamagePercent(this, *i, DoneTotalMod, spellProto))
- continue;
+ if (!spellProto->ValidateAttribute6SpellDamageMods(this, aurEff, damagetype == DOT))
+ return false;
- if ((*i)->GetMiscValue() & spellProto->GetSchoolMask())
+ if (aurEff->GetMiscValue() & spellProto->GetSchoolMask())
{
- if ((*i)->GetSpellInfo()->EquippedItemClass == -1)
- AddPct(DoneTotalMod, (*i)->GetAmount());
- else if (!(*i)->GetSpellInfo()->HasAttribute(SPELL_ATTR5_AURA_AFFECTS_NOT_JUST_REQ_EQUIPPED_ITEM) && ((*i)->GetSpellInfo()->EquippedItemSubClassMask == 0))
- AddPct(DoneTotalMod, (*i)->GetAmount());
- else if (ToPlayer() && ToPlayer()->HasItemFitToSpellRequirements((*i)->GetSpellInfo()))
- AddPct(DoneTotalMod, (*i)->GetAmount());
+ if (aurEff->GetSpellInfo()->EquippedItemClass == -1)
+ return true;
+ else if (!aurEff->GetSpellInfo()->HasAttribute(SPELL_ATTR5_AURA_AFFECTS_NOT_JUST_REQ_EQUIPPED_ITEM) && (aurEff->GetSpellInfo()->EquippedItemSubClassMask == 0))
+ return true;
+ else if (ToPlayer() && ToPlayer()->HasItemFitToSpellRequirements(aurEff->GetSpellInfo()))
+ return true;
}
- }
+ return false;
+ });
uint32 creatureTypeMask = victim->GetCreatureTypeMask();
- AuraEffectList const& mDamageDoneVersus = GetAuraEffectsByType(SPELL_AURA_MOD_DAMAGE_DONE_VERSUS);
- for (AuraEffectList::const_iterator i = mDamageDoneVersus.begin(); i != mDamageDoneVersus.end(); ++i)
- if ((creatureTypeMask & uint32((*i)->GetMiscValue())) && spellProto->ValidateAttribute6SpellDamageMods(this, *i, damagetype == DOT))
- AddPct(DoneTotalMod, (*i)->GetAmount());
+ DoneTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_DONE_VERSUS, [creatureTypeMask, spellProto, damagetype, this](AuraEffect const* aurEff)
+ {
+ return creatureTypeMask & aurEff->GetMiscValue() && spellProto->ValidateAttribute6SpellDamageMods(this, aurEff, damagetype == DOT);
+ });
// bonus against aurastate
- AuraEffectList const& mDamageDoneVersusAurastate = GetAuraEffectsByType(SPELL_AURA_MOD_DAMAGE_DONE_VERSUS_AURASTATE);
- for (AuraEffectList::const_iterator i = mDamageDoneVersusAurastate.begin(); i != mDamageDoneVersusAurastate.end(); ++i)
- if (victim->HasAuraState(AuraStateType((*i)->GetMiscValue())) && spellProto->ValidateAttribute6SpellDamageMods(this, *i, damagetype == DOT))
- AddPct(DoneTotalMod, (*i)->GetAmount());
+ DoneTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_DONE_VERSUS_AURASTATE, [victim, spellProto, damagetype, this](AuraEffect const* aurEff)
+ {
+ return victim->HasAuraState(AuraStateType(aurEff->GetMiscValue())) && spellProto->ValidateAttribute6SpellDamageMods(this, aurEff, damagetype == DOT);
+ });
// done scripted mod (take it from owner)
Unit* owner = GetOwner() ? GetOwner() : this;
@@ -11876,22 +12004,19 @@ uint32 Unit::SpellDamageBonusTaken(Unit* caster, SpellInfo const* spellProto, ui
// from positive and negative SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN
// multiplicative bonus, for example Dispersion + Shadowform (0.10*0.85=0.085)
- AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN);
- for (AuraEffectList::const_iterator i = mTotalAuraList.begin(); i != mTotalAuraList.end(); ++i)
- if (((*i)->GetMiscValue() & spellProto->GetSchoolMask()))
- if (spellProto->ValidateAttribute6SpellDamageMods(caster, *i, damagetype == DOT))
- AddPct(TakenTotalMod, (*i)->GetAmount());
+ TakenTotalMod *= GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN, spellProto->GetSchoolMask());
TakenTotalMod = processDummyAuras(TakenTotalMod);
// From caster spells
if (caster)
{
- AuraEffectList const& mOwnerTaken = GetAuraEffectsByType(SPELL_AURA_MOD_DAMAGE_FROM_CASTER);
- for (AuraEffectList::const_iterator i = mOwnerTaken.begin(); i != mOwnerTaken.end(); ++i)
- if ((*i)->GetCasterGUID() == caster->GetGUID() && (*i)->IsAffectedOnSpell(spellProto))
- if (spellProto->ValidateAttribute6SpellDamageMods(caster, *i, damagetype == DOT))
- AddPct(TakenTotalMod, (*i)->GetAmount());
+ TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_FROM_CASTER, [caster, spellProto](AuraEffect const* aurEff) -> bool
+ {
+ if (aurEff->GetCasterGUID() == caster->GetGUID() && aurEff->IsAffectedOnSpell(spellProto))
+ return true;
+ return false;
+ });
}
if (uint32 mechanicMask = spellProto->GetAllEffectsMechanicMask())
@@ -12014,16 +12139,14 @@ float Unit::processDummyAuras(float TakenTotalMod) const
int32 Unit::SpellBaseDamageBonusDone(SpellSchoolMask schoolMask)
{
- int32 DoneAdvertisedBenefit = 0;
-
- AuraEffectList const& mDamageDone = GetAuraEffectsByType(SPELL_AURA_MOD_DAMAGE_DONE);
- for (AuraEffectList::const_iterator i = mDamageDone.begin(); i != mDamageDone.end(); ++i)
- if (((*i)->GetMiscValue() & schoolMask) != 0 &&
- (*i)->GetSpellInfo()->EquippedItemClass == -1 &&
+ int32 DoneAdvertisedBenefit = GetTotalAuraModifier(SPELL_AURA_MOD_DAMAGE_DONE, [schoolMask](AuraEffect const* aurEff)
+ {
+ return aurEff->GetMiscValue() & schoolMask &&
// -1 == any item class (not wand then)
- (*i)->GetSpellInfo()->EquippedItemInventoryTypeMask == 0)
- // 0 == any inventory type (not wand then)
- DoneAdvertisedBenefit += (*i)->GetAmount();
+ aurEff->GetSpellInfo()->EquippedItemClass == -1 &&
+ // 0 == any inventory type (not wand then)
+ aurEff->GetSpellInfo()->EquippedItemInventoryTypeMask == 0;
+ });
if (IsPlayer())
{
@@ -12043,31 +12166,20 @@ int32 Unit::SpellBaseDamageBonusDone(SpellSchoolMask schoolMask)
}
}
// ... and attack power
- AuraEffectList const& mDamageDonebyAP = GetAuraEffectsByType(SPELL_AURA_MOD_SPELL_DAMAGE_OF_ATTACK_POWER);
- for (AuraEffectList::const_iterator i = mDamageDonebyAP.begin(); i != mDamageDonebyAP.end(); ++i)
- if ((*i)->GetMiscValue() & schoolMask)
- DoneAdvertisedBenefit += int32(CalculatePct(GetTotalAttackPowerValue(BASE_ATTACK), (*i)->GetAmount()));
+ DoneAdvertisedBenefit += int32(CalculatePct(GetTotalAttackPowerValue(BASE_ATTACK), GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_SPELL_DAMAGE_OF_ATTACK_POWER, schoolMask)));
}
return DoneAdvertisedBenefit;
}
int32 Unit::SpellBaseDamageBonusTaken(SpellSchoolMask schoolMask, bool isDoT)
{
- int32 TakenAdvertisedBenefit = 0;
-
- AuraEffectList const& mDamageTaken = GetAuraEffectsByType(SPELL_AURA_MOD_DAMAGE_TAKEN);
- for (AuraEffectList::const_iterator i = mDamageTaken.begin(); i != mDamageTaken.end(); ++i)
- if (((*i)->GetMiscValue() & schoolMask) != 0)
- {
+ return GetTotalAuraModifier(SPELL_AURA_MOD_DAMAGE_TAKEN, [schoolMask, isDoT](AuraEffect const* aurEff)
+ {
+ return (aurEff->GetMiscValue() & schoolMask) != 0
// Xinef: if we have DoT damage type and aura has charges, check if it affects DoTs
// Xinef: required for hemorrhage & rupture / garrote
- if (isDoT && (*i)->GetBase()->IsUsingCharges() && !((*i)->GetSpellInfo()->ProcFlags & PROC_FLAG_TAKEN_PERIODIC))
- continue;
-
- TakenAdvertisedBenefit += (*i)->GetAmount();
- }
-
- return TakenAdvertisedBenefit;
+ && !(isDoT && aurEff->GetBase()->IsUsingCharges() && aurEff->GetSpellInfo()->ProcFlags & PROC_FLAG_TAKEN_PERIODIC);
+ });
}
float Unit::SpellDoneCritChance(Unit const* /*victim*/, SpellInfo const* spellProto, SpellSchoolMask schoolMask, WeaponAttackType attackType, bool skipEffectCheck) const
@@ -12338,14 +12450,10 @@ float Unit::SpellTakenCritChance(Unit const* caster, SpellInfo const* spellProto
if (caster)
{
- AuraEffectList const& mTotalAuraList = GetAuraEffectsByType(SPELL_AURA_MOD_CRIT_CHANCE_FOR_CASTER);
- for (AuraEffectList::const_iterator i = mTotalAuraList.begin(); i != mTotalAuraList.end(); ++i)
+ crit_chance += GetTotalAuraModifier(SPELL_AURA_MOD_CRIT_CHANCE_FOR_CASTER, [caster](AuraEffect const* aurEff)
{
- if (caster->GetGUID() != (*i)->GetCasterGUID())
- continue;
-
- crit_chance += (*i)->GetAmount();
- }
+ return caster->GetGUID() == aurEff->GetCasterGUID();
+ });
}
// Modify critical chance by victim SPELL_AURA_MOD_ATTACKER_SPELL_AND_WEAPON_CRIT_CHANCE
@@ -12458,14 +12566,7 @@ float Unit::SpellPctHealingModsDone(Unit* victim, SpellInfo const* spellProto, D
float DoneTotalMod = 1.0f;
// Healing done percent
- AuraEffectList const& mHealingDonePct = GetAuraEffectsByType(SPELL_AURA_MOD_HEALING_DONE_PERCENT);
- for (auto const& auraEff : mHealingDonePct)
- {
- if (!sScriptMgr->IsNeedModHealPercent(this, auraEff, DoneTotalMod, spellProto))
- continue;
-
- AddPct(DoneTotalMod, auraEff->GetAmount());
- }
+ DoneTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_HEALING_DONE_PERCENT);
// done scripted mod (take it from owner)
Unit* owner = GetOwner() ? GetOwner() : this;
@@ -12631,6 +12732,8 @@ uint32 Unit::SpellHealingBonusDone(Unit* victim, SpellInfo const* spellProto, ui
case SPELL_AURA_PERIODIC_HEALTH_FUNNEL:
DoneTotal = 0;
break;
+ default:
+ break;
}
if (spellProto->Effects[i].Effect == SPELL_EFFECT_HEALTH_LEECH)
DoneTotal = 0;
@@ -12672,7 +12775,7 @@ uint32 Unit::SpellHealingBonusTaken(Unit* caster, SpellInfo const* spellProto, u
int32 TakenTotal = 0;
// Taken fixed damage bonus auras
- int32 TakenAdvertisedBenefit = SpellBaseHealingBonusTaken(spellProto->GetSchoolMask());
+ int32 TakenAdvertisedBenefit = GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_HEALING, spellProto->GetSchoolMask());
// Nourish cast, glyph of nourish
if (spellProto->SpellFamilyName == SPELLFAMILY_DRUID && spellProto->SpellFamilyFlags[1] & 0x2000000 && caster)
@@ -12746,10 +12849,10 @@ uint32 Unit::SpellHealingBonusTaken(Unit* caster, SpellInfo const* spellProto, u
if (caster)
{
- AuraEffectList const& mHealingGet = GetAuraEffectsByType(SPELL_AURA_MOD_HEALING_RECEIVED);
- for (AuraEffectList::const_iterator i = mHealingGet.begin(); i != mHealingGet.end(); ++i)
- if (caster->GetGUID() == (*i)->GetCasterGUID() && (*i)->IsAffectedOnSpell(spellProto))
- AddPct(TakenTotalMod, (*i)->GetAmount());
+ TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_HEALING_RECEIVED, [caster, spellProto](AuraEffect const* aurEff)
+ {
+ return caster->GetGUID() == aurEff->GetCasterGUID() && aurEff->IsAffectedOnSpell(spellProto);
+ });
}
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
@@ -12761,6 +12864,8 @@ uint32 Unit::SpellHealingBonusTaken(Unit* caster, SpellInfo const* spellProto, u
case SPELL_AURA_PERIODIC_HEALTH_FUNNEL:
TakenTotal = 0;
break;
+ default:
+ break;
}
if (spellProto->Effects[i].Effect == SPELL_EFFECT_HEALTH_LEECH)
TakenTotal = 0;
@@ -12782,10 +12887,10 @@ int32 Unit::SpellBaseHealingBonusDone(SpellSchoolMask schoolMask)
{
int32 AdvertisedBenefit = 0;
- AuraEffectList const& mHealingDone = GetAuraEffectsByType(SPELL_AURA_MOD_HEALING_DONE);
- for (AuraEffectList::const_iterator i = mHealingDone.begin(); i != mHealingDone.end(); ++i)
- if (!(*i)->GetMiscValue() || ((*i)->GetMiscValue() & schoolMask) != 0)
- AdvertisedBenefit += (*i)->GetAmount();
+ AdvertisedBenefit += GetTotalAuraModifier(SPELL_AURA_MOD_HEALING_DONE, [schoolMask](AuraEffect const* aurEff)
+ {
+ return !aurEff->GetMiscValue() || (aurEff->GetMiscValue() & schoolMask) != 0;
+ });
// Healing bonus of spirit, intellect and strength
if (IsPlayer())
@@ -12804,26 +12909,11 @@ int32 Unit::SpellBaseHealingBonusDone(SpellSchoolMask schoolMask)
}
// ... and attack power
- AuraEffectList const& mHealingDonebyAP = GetAuraEffectsByType(SPELL_AURA_MOD_SPELL_HEALING_OF_ATTACK_POWER);
- for (AuraEffectList::const_iterator i = mHealingDonebyAP.begin(); i != mHealingDonebyAP.end(); ++i)
- if ((*i)->GetMiscValue() & schoolMask)
- AdvertisedBenefit += int32(CalculatePct(GetTotalAttackPowerValue(BASE_ATTACK), (*i)->GetAmount()));
+ AdvertisedBenefit += int32(CalculatePct(GetTotalAttackPowerValue(BASE_ATTACK), GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_SPELL_HEALING_OF_ATTACK_POWER, schoolMask)));
}
return AdvertisedBenefit;
}
-int32 Unit::SpellBaseHealingBonusTaken(SpellSchoolMask schoolMask)
-{
- int32 AdvertisedBenefit = 0;
-
- AuraEffectList const& mDamageTaken = GetAuraEffectsByType(SPELL_AURA_MOD_HEALING);
- for (AuraEffectList::const_iterator i = mDamageTaken.begin(); i != mDamageTaken.end(); ++i)
- if (((*i)->GetMiscValue() & schoolMask) != 0)
- AdvertisedBenefit += (*i)->GetAmount();
-
- return AdvertisedBenefit;
-}
-
bool Unit::IsImmunedToDamage(SpellSchoolMask meleeSchoolMask) const
{
if (meleeSchoolMask == SPELL_SCHOOL_MASK_NONE)
@@ -13172,10 +13262,7 @@ uint32 Unit::MeleeDamageBonusDone(Unit* victim, uint32 pdamage, WeaponAttackType
int32 DoneFlatBenefit = 0;
// ..done
- AuraEffectList const& mDamageDoneCreature = GetAuraEffectsByType(SPELL_AURA_MOD_DAMAGE_DONE_CREATURE);
- for (AuraEffectList::const_iterator i = mDamageDoneCreature.begin(); i != mDamageDoneCreature.end(); ++i)
- if (creatureTypeMask & uint32((*i)->GetMiscValue()))
- DoneFlatBenefit += (*i)->GetAmount();
+ DoneFlatBenefit += GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_DAMAGE_DONE_CREATURE, creatureTypeMask);
// ..done
// SPELL_AURA_MOD_DAMAGE_DONE included in weapon damage
@@ -13188,20 +13275,14 @@ uint32 Unit::MeleeDamageBonusDone(Unit* victim, uint32 pdamage, WeaponAttackType
APbonus += victim->GetTotalAuraModifier(SPELL_AURA_RANGED_ATTACK_POWER_ATTACKER_BONUS);
// ..done (base at attack power and creature type)
- AuraEffectList const& mCreatureAttackPower = GetAuraEffectsByType(SPELL_AURA_MOD_RANGED_ATTACK_POWER_VERSUS);
- for (AuraEffectList::const_iterator i = mCreatureAttackPower.begin(); i != mCreatureAttackPower.end(); ++i)
- if (creatureTypeMask & uint32((*i)->GetMiscValue()))
- APbonus += (*i)->GetAmount();
+ APbonus += GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_RANGED_ATTACK_POWER_VERSUS, creatureTypeMask);
}
else
{
APbonus += victim->GetTotalAuraModifier(SPELL_AURA_MELEE_ATTACK_POWER_ATTACKER_BONUS);
// ..done (base at attack power and creature type)
- AuraEffectList const& mCreatureAttackPower = GetAuraEffectsByType(SPELL_AURA_MOD_MELEE_ATTACK_POWER_VERSUS);
- for (AuraEffectList::const_iterator i = mCreatureAttackPower.begin(); i != mCreatureAttackPower.end(); ++i)
- if (creatureTypeMask & uint32((*i)->GetMiscValue()))
- APbonus += (*i)->GetAmount();
+ APbonus += GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_MELEE_ATTACK_POWER_VERSUS, creatureTypeMask);
}
if (APbonus != 0) // Can be negative
@@ -13224,37 +13305,34 @@ uint32 Unit::MeleeDamageBonusDone(Unit* victim, uint32 pdamage, WeaponAttackType
if (!(damageSchoolMask & SPELL_SCHOOL_MASK_NORMAL))
{
// Some spells don't benefit from pct done mods
- AuraEffectList const& mModDamagePercentDone = GetAuraEffectsByType(SPELL_AURA_MOD_DAMAGE_PERCENT_DONE);
- for (AuraEffectList::const_iterator i = mModDamagePercentDone.begin(); i != mModDamagePercentDone.end(); ++i)
- {
- if (!spellProto || (spellProto->ValidateAttribute6SpellDamageMods(this, *i, false) &&
- sScriptMgr->IsNeedModMeleeDamagePercent(this, *i, DoneTotalMod, spellProto)))
+ DoneTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_PERCENT_DONE, [spellProto, this, damageSchoolMask](AuraEffect const* aurEff)
+ {
+ if (!spellProto || (spellProto->ValidateAttribute6SpellDamageMods(this, aurEff, false)))
{
- if (((*i)->GetMiscValue() & damageSchoolMask))
+ if ((aurEff->GetMiscValue() & damageSchoolMask))
{
- if ((*i)->GetSpellInfo()->EquippedItemClass == -1)
- AddPct(DoneTotalMod, (*i)->GetAmount());
- else if (!(*i)->GetSpellInfo()->HasAttribute(SPELL_ATTR5_AURA_AFFECTS_NOT_JUST_REQ_EQUIPPED_ITEM) && ((*i)->GetSpellInfo()->EquippedItemSubClassMask == 0))
- AddPct(DoneTotalMod, (*i)->GetAmount());
- else if (ToPlayer() && ToPlayer()->HasItemFitToSpellRequirements((*i)->GetSpellInfo()))
- AddPct(DoneTotalMod, (*i)->GetAmount());
+ if (aurEff->GetSpellInfo()->EquippedItemClass == -1)
+ return true;
+ else if (!aurEff->GetSpellInfo()->HasAttribute(SPELL_ATTR5_AURA_AFFECTS_NOT_JUST_REQ_EQUIPPED_ITEM) && (aurEff->GetSpellInfo()->EquippedItemSubClassMask == 0))
+ return true;
+ else if (ToPlayer() && ToPlayer()->HasItemFitToSpellRequirements(aurEff->GetSpellInfo()))
+ return true;
}
}
- }
+ return false;
+ });
}
- AuraEffectList const& mDamageDoneVersus = GetAuraEffectsByType(SPELL_AURA_MOD_DAMAGE_DONE_VERSUS);
- for (AuraEffectList::const_iterator i = mDamageDoneVersus.begin(); i != mDamageDoneVersus.end(); ++i)
- if (creatureTypeMask & uint32((*i)->GetMiscValue()))
- if (!spellProto || spellProto->ValidateAttribute6SpellDamageMods(this, *i, false))
- AddPct(DoneTotalMod, (*i)->GetAmount());
+ DoneTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_DONE_VERSUS, [creatureTypeMask, spellProto, this](AuraEffect const* aurEff)
+ {
+ return (creatureTypeMask & aurEff->GetMiscValue() && (!spellProto || spellProto->ValidateAttribute6SpellDamageMods(this, aurEff, false)));
+ });
// bonus against aurastate
- AuraEffectList const& mDamageDoneVersusAurastate = GetAuraEffectsByType(SPELL_AURA_MOD_DAMAGE_DONE_VERSUS_AURASTATE);
- for (AuraEffectList::const_iterator i = mDamageDoneVersusAurastate.begin(); i != mDamageDoneVersusAurastate.end(); ++i)
- if (victim->HasAuraState(AuraStateType((*i)->GetMiscValue())))
- if (!spellProto || spellProto->ValidateAttribute6SpellDamageMods(this, *i, false))
- AddPct(DoneTotalMod, (*i)->GetAmount());
+ DoneTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_DONE_VERSUS_AURASTATE, [victim, spellProto, this](AuraEffect const* aurEff)
+ {
+ return (victim->HasAuraState(AuraStateType(aurEff->GetMiscValue())) && (!spellProto || spellProto->ValidateAttribute6SpellDamageMods(this, aurEff, false)));
+ });
// done scripted mod (take it from owner)
Unit* owner = GetOwner() ? GetOwner() : this;
@@ -13361,10 +13439,7 @@ uint32 Unit::MeleeDamageBonusTaken(Unit* attacker, uint32 pdamage, WeaponAttackT
int32 TakenFlatBenefit = 0;
// ..taken
- AuraEffectList const& mDamageTaken = GetAuraEffectsByType(SPELL_AURA_MOD_DAMAGE_TAKEN);
- for (AuraEffectList::const_iterator i = mDamageTaken.begin(); i != mDamageTaken.end(); ++i)
- if ((*i)->GetMiscValue() & damageSchoolMask)
- TakenFlatBenefit += (*i)->GetAmount();
+ TakenFlatBenefit += GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_DAMAGE_TAKEN, damageSchoolMask);
if (attType != RANGED_ATTACK)
TakenFlatBenefit += GetTotalAuraModifier(SPELL_AURA_MOD_MELEE_DAMAGE_TAKEN);
@@ -13380,10 +13455,10 @@ uint32 Unit::MeleeDamageBonusTaken(Unit* attacker, uint32 pdamage, WeaponAttackT
if (spellProto)
{
// From caster spells
- AuraEffectList const& mOwnerTaken = GetAuraEffectsByType(SPELL_AURA_MOD_DAMAGE_FROM_CASTER);
- for (AuraEffectList::const_iterator i = mOwnerTaken.begin(); i != mOwnerTaken.end(); ++i)
- if ((*i)->GetCasterGUID() == attacker->GetGUID() && (*i)->IsAffectedOnSpell(spellProto))
- AddPct(TakenTotalMod, (*i)->GetAmount());
+ TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_FROM_CASTER, [attacker, spellProto](AuraEffect const* aurEff)
+ {
+ return attacker->GetGUID() == aurEff->GetCasterGUID() && aurEff->IsAffectedOnSpell(spellProto);
+ });
// Mod damage from spell mechanic
uint32 mechanicMask = spellProto->GetAllEffectsMechanicMask();
@@ -13394,10 +13469,12 @@ uint32 Unit::MeleeDamageBonusTaken(Unit* attacker, uint32 pdamage, WeaponAttackT
if (mechanicMask)
{
- AuraEffectList const& mDamageDoneMechanic = GetAuraEffectsByType(SPELL_AURA_MOD_MECHANIC_DAMAGE_TAKEN_PERCENT);
- for (AuraEffectList::const_iterator i = mDamageDoneMechanic.begin(); i != mDamageDoneMechanic.end(); ++i)
- if (mechanicMask & uint32(1 << ((*i)->GetMiscValue())))
- AddPct(TakenTotalMod, (*i)->GetAmount());
+ TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_MECHANIC_DAMAGE_TAKEN_PERCENT, [mechanicMask](AuraEffect const* aurEff) -> bool
+ {
+ if (mechanicMask & uint32(1 << (aurEff->GetMiscValue())))
+ return true;
+ return false;
+ });
}
}
@@ -13414,15 +13491,11 @@ uint32 Unit::MeleeDamageBonusTaken(Unit* attacker, uint32 pdamage, WeaponAttackT
if (attType != RANGED_ATTACK)
{
- AuraEffectList const& mModMeleeDamageTakenPercent = GetAuraEffectsByType(SPELL_AURA_MOD_MELEE_DAMAGE_TAKEN_PCT);
- for (AuraEffectList::const_iterator i = mModMeleeDamageTakenPercent.begin(); i != mModMeleeDamageTakenPercent.end(); ++i)
- AddPct(TakenTotalMod, (*i)->GetAmount());
+ TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_MELEE_DAMAGE_TAKEN_PCT);
}
else
{
- AuraEffectList const& mModRangedDamageTakenPercent = GetAuraEffectsByType(SPELL_AURA_MOD_RANGED_DAMAGE_TAKEN_PCT);
- for (AuraEffectList::const_iterator i = mModRangedDamageTakenPercent.begin(); i != mModRangedDamageTakenPercent.end(); ++i)
- AddPct(TakenTotalMod, (*i)->GetAmount());
+ TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_RANGED_DAMAGE_TAKEN_PCT);
}
// No positive taken bonus, custom attr
@@ -14021,7 +14094,22 @@ bool Unit::_IsValidAttackTarget(Unit const* target, SpellInfo const* bySpell, Wo
if (repThisToTarget > REP_NEUTRAL
|| (repTargetToThis = target->GetReactionTo(this)) > REP_NEUTRAL)
- return false;
+ {
+ // contested guards can attack contested PvP players even though players may be friendly
+ if (!target->IsControlledByPlayer())
+ return false;
+
+ bool isContestedGuard = false;
+ if (FactionTemplateEntry const* entry = GetFactionTemplateEntry())
+ isContestedGuard = entry->factionFlags & FACTION_TEMPLATE_FLAG_ATTACK_PVP_ACTIVE_PLAYERS;
+
+ bool isContestedPvp = false;
+ if (Player const* player = target->GetCharmerOrOwnerPlayerOrPlayerItself())
+ isContestedPvp = player->HasPlayerFlag(PLAYER_FLAGS_CONTESTED_PVP);
+
+ if (!isContestedGuard && !isContestedPvp)
+ return false;
+ }
// Not all neutral creatures can be attacked (even some unfriendly faction does not react aggresive to you, like Sporaggar)
if (repThisToTarget == REP_NEUTRAL &&
@@ -14272,15 +14360,6 @@ int32 Unit::ModifyPower(Powers power, int32 dVal, bool withPowerUpdate /*= true*
return gain;
}
-// returns negative amount on power reduction
-int32 Unit::ModifyPowerPct(Powers power, float pct, bool apply)
-{
- float amount = (float)GetMaxPower(power);
- ApplyPercentModFloatVar(amount, pct, apply);
-
- return ModifyPower(power, (int32)amount - (int32)GetMaxPower(power));
-}
-
bool Unit::IsAlwaysVisibleFor(WorldObject const* seer) const
{
if (WorldObject::IsAlwaysVisibleFor(seer))
@@ -14632,7 +14711,7 @@ void Unit::setDeathState(DeathState s, bool despawn)
}
else if (s == DeathState::JustRespawned)
{
- RemoveFlag (UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE); // clear skinnable for creature and player (at battleground)
+ RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE); // clear skinnable for creature and player (at battleground)
}
}
@@ -15209,6 +15288,11 @@ float Unit::GetSpellMinRangeForTarget(Unit const* target, SpellInfo const* spell
return spellInfo->GetMinRange(!IsHostileTo(target));
}
+void Unit::SetAnimTier(AnimTier animTier)
+{
+ SetByteValue(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_ANIM_TIER, uint8(animTier));
+}
+
uint32 Unit::GetCreatureType() const
{
if (IsPlayer())
@@ -15230,30 +15314,100 @@ uint32 Unit::GetCreatureType() const
######## ########
#######################################*/
-bool Unit::HandleStatModifier(UnitMods unitMod, UnitModifierType modifierType, float amount, bool apply)
+bool Unit::HandleStatFlatModifier(UnitMods unitMod, UnitModifierFlatType modifierType, float amount, bool apply)
{
- if (unitMod >= UNIT_MOD_END || modifierType >= MODIFIER_TYPE_END)
+ if (unitMod >= UNIT_MOD_END || modifierType >= MODIFIER_TYPE_FLAT_END)
{
LOG_ERROR("entities.unit", "ERROR in HandleStatModifier(): non-existing UnitMods or wrong UnitModifierType!");
return false;
}
+ if (!amount)
+ return false;
+
switch (modifierType)
{
case BASE_VALUE:
case TOTAL_VALUE:
- m_auraModifiersGroup[unitMod][modifierType] += apply ? amount : -amount;
+ m_auraFlatModifiersGroup[unitMod][modifierType] += apply ? amount : -amount;
+ break;
+ default:
break;
+ }
+
+ UpdateUnitMod(unitMod);
+ return true;
+}
+
+// Usage outside of AuraEffect Handlers is discouraged as the value will be lost when auras change. Use an Aura instead.
+void Unit::ApplyStatPctModifier(UnitMods unitMod, UnitModifierPctType modifierType, float pct)
+{
+ if (unitMod >= UNIT_MOD_END || modifierType >= MODIFIER_TYPE_PCT_END)
+ {
+ LOG_ERROR("entities.unit", "ERROR in ApplyStatPctModifier(): non-existing UnitMods or wrong UnitModifierType!");
+ return;
+ }
+
+ if (!pct)
+ return;
+
+ switch (modifierType)
+ {
case BASE_PCT:
case TOTAL_PCT:
- ApplyPercentModFloatVar(m_auraModifiersGroup[unitMod][modifierType], amount, apply);
+ AddPct(m_auraPctModifiersGroup[unitMod][modifierType], pct);
break;
default:
break;
}
- if (!CanModifyStats())
- return false;
+ UpdateUnitMod(unitMod);
+}
+
+void Unit::SetStatFlatModifier(UnitMods unitMod, UnitModifierFlatType modifierType, float val)
+{
+ if (m_auraFlatModifiersGroup[unitMod][modifierType] == val)
+ return;
+
+ m_auraFlatModifiersGroup[unitMod][modifierType] = val;
+ UpdateUnitMod(unitMod);
+}
+
+void Unit::SetStatPctModifier(UnitMods unitMod, UnitModifierPctType modifierType, float val)
+{
+ if (m_auraPctModifiersGroup[unitMod][modifierType] == val)
+ return;
+
+ m_auraPctModifiersGroup[unitMod][modifierType] = val;
+ UpdateUnitMod(unitMod);
+}
+
+float Unit::GetFlatModifierValue(UnitMods unitMod, UnitModifierFlatType modifierType) const
+{
+ if (unitMod >= UNIT_MOD_END || modifierType >= MODIFIER_TYPE_FLAT_END)
+ {
+ LOG_ERROR("entities.unit", "attempt to access non-existing modifier value from UnitMods!");
+ return 0.0f;
+ }
+
+ return m_auraFlatModifiersGroup[unitMod][modifierType];
+}
+
+float Unit::GetPctModifierValue(UnitMods unitMod, UnitModifierPctType modifierType) const
+{
+ if (unitMod >= UNIT_MOD_END || modifierType >= MODIFIER_TYPE_PCT_END)
+ {
+ LOG_ERROR("entities.unit", "attempt to access non-existing modifier value from UnitMods!");
+ return 0.0f;
+ }
+
+ return m_auraPctModifiersGroup[unitMod][modifierType];
+}
+
+void Unit::UpdateUnitMod(UnitMods unitMod)
+{
+ if (!CanModifyStats())
+ return;
switch (unitMod)
{
@@ -15311,36 +15465,100 @@ bool Unit::HandleStatModifier(UnitMods unitMod, UnitModifierType modifierType, f
default:
break;
}
+}
- return true;
+void Unit::UpdateDamageDoneMods(WeaponAttackType attackType, int32 /*skipEnchantSlot = -1*/)
+{
+ UnitMods unitMod;
+ switch (attackType)
+ {
+ case BASE_ATTACK:
+ unitMod = UNIT_MOD_DAMAGE_MAINHAND;
+ break;
+ case OFF_ATTACK:
+ unitMod = UNIT_MOD_DAMAGE_OFFHAND;
+ break;
+ case RANGED_ATTACK:
+ unitMod = UNIT_MOD_DAMAGE_RANGED;
+ break;
+ default:
+ ABORT();
+ break;
+ }
+
+ float amount = GetTotalAuraModifier(SPELL_AURA_MOD_DAMAGE_DONE, [&](AuraEffect const* aurEff) -> bool
+ {
+ if (!(aurEff->GetMiscValue() & SPELL_SCHOOL_MASK_NORMAL))
+ return false;
+
+ return CheckAttackFitToAuraRequirement(attackType, aurEff);
+ });
+
+ SetStatFlatModifier(unitMod, TOTAL_VALUE, amount);
+}
+
+void Unit::UpdateAllDamageDoneMods()
+{
+ for (uint8 i = BASE_ATTACK; i < MAX_ATTACK; ++i)
+ UpdateDamageDoneMods(WeaponAttackType(i));
}
-float Unit::GetModifierValue(UnitMods unitMod, UnitModifierType modifierType) const
+void Unit::UpdateDamagePctDoneMods(WeaponAttackType attackType)
{
- if (unitMod >= UNIT_MOD_END || modifierType >= MODIFIER_TYPE_END)
+ float factor;
+ UnitMods unitMod;
+ switch (attackType)
{
- LOG_ERROR("entities.unit", "attempt to access non-existing modifier value from UnitMods!");
- return 0.0f;
+ case BASE_ATTACK:
+ factor = 1.0f;
+ unitMod = UNIT_MOD_DAMAGE_MAINHAND;
+ break;
+ case OFF_ATTACK:
+ // off hand has 50% penalty
+ factor = 0.5f;
+ unitMod = UNIT_MOD_DAMAGE_OFFHAND;
+ break;
+ case RANGED_ATTACK:
+ factor = 1.0f;
+ unitMod = UNIT_MOD_DAMAGE_RANGED;
+ break;
+ default:
+ ABORT();
+ break;
}
- if (modifierType == TOTAL_PCT && m_auraModifiersGroup[unitMod][modifierType] <= 0.0f)
- return 0.0f;
+ factor *= GetTotalAuraMultiplier(SPELL_AURA_MOD_DAMAGE_PERCENT_DONE, [attackType, this](AuraEffect const* aurEff) -> bool
+ {
+ if (!(aurEff->GetMiscValue() & SPELL_SCHOOL_MASK_NORMAL))
+ return false;
+
+ return CheckAttackFitToAuraRequirement(attackType, aurEff);
+ });
+
+ if (attackType == OFF_ATTACK)
+ factor *= GetTotalAuraMultiplier(SPELL_AURA_MOD_OFFHAND_DAMAGE_PCT, std::bind(&Unit::CheckAttackFitToAuraRequirement, this, attackType, std::placeholders::_1));
- return m_auraModifiersGroup[unitMod][modifierType];
+ SetStatPctModifier(unitMod, TOTAL_PCT, factor);
+}
+
+void Unit::UpdateAllDamagePctDoneMods()
+{
+ for (uint8 i = BASE_ATTACK; i < MAX_ATTACK; ++i)
+ UpdateDamagePctDoneMods(WeaponAttackType(i));
}
float Unit::GetTotalStatValue(Stats stat, float additionalValue) const
{
UnitMods unitMod = UnitMods(static_cast<uint16>(UNIT_MOD_STAT_START) + stat);
- if (m_auraModifiersGroup[unitMod][TOTAL_PCT] <= 0.0f)
+ if (GetPctModifierValue(unitMod, TOTAL_PCT) <= 0.0f)
return 0.0f;
// value = ((base_value * base_pct) + total_value) * total_pct
- float value = m_auraModifiersGroup[unitMod][BASE_VALUE] + GetCreateStat(stat);
- value *= m_auraModifiersGroup[unitMod][BASE_PCT];
- value += m_auraModifiersGroup[unitMod][TOTAL_VALUE] + additionalValue;
- value *= m_auraModifiersGroup[unitMod][TOTAL_PCT];
+ float value = GetFlatModifierValue(unitMod, BASE_VALUE) + GetCreateStat(stat);
+ value *= GetPctModifierValue(unitMod, BASE_PCT);
+ value += GetFlatModifierValue(unitMod, TOTAL_VALUE) + additionalValue;
+ value *= GetPctModifierValue(unitMod, TOTAL_PCT);
return value;
}
@@ -15353,25 +15571,16 @@ float Unit::GetTotalAuraModValue(UnitMods unitMod) const
return 0.0f;
}
- if (m_auraModifiersGroup[unitMod][TOTAL_PCT] <= 0.0f)
+ if (GetPctModifierValue(unitMod, TOTAL_PCT) <= 0.0f)
return 0.0f;
- float value = m_auraModifiersGroup[unitMod][BASE_VALUE];
- value *= m_auraModifiersGroup[unitMod][BASE_PCT];
- value += m_auraModifiersGroup[unitMod][TOTAL_VALUE];
- value *= m_auraModifiersGroup[unitMod][TOTAL_PCT];
-
+ float value = GetFlatModifierValue(unitMod, BASE_VALUE);
+ value *= GetPctModifierValue(unitMod, BASE_PCT);
+ value += GetFlatModifierValue(unitMod, TOTAL_VALUE);
+ value *= GetPctModifierValue(unitMod, TOTAL_PCT);
return value;
}
-void Unit::ApplyStatPercentBuffMod(Stats stat, float val, bool apply)
-{
- if (val == -100.0f) // prevent set var to zero
- val = -99.99f;
- float var = GetStat(stat) * val / 100.0f;
- ApplyModSignedFloatValue((var > 0 ? static_cast<uint16>(UNIT_FIELD_POSSTAT0) + stat : static_cast<uint16>(UNIT_FIELD_NEGSTAT0) + stat), var, apply);
-}
-
SpellSchools Unit::GetSpellSchoolByAuraGroup(UnitMods unitMod) const
{
SpellSchools school = SPELL_SCHOOL_NORMAL;
@@ -17078,28 +17287,40 @@ Unit* Unit::SelectNearbyNoTotemTarget(Unit* exclude, float dist) const
return Acore::Containers::SelectRandomContainerElement(targets);
}
+void ApplyPercentModFloatVar(float& var, float val, bool apply)
+{
+ var *= (apply ? (100.0f + val) / 100.0f : 100.0f / (100.0f + val));
+}
+
void Unit::ApplyAttackTimePercentMod(WeaponAttackType att, float val, bool apply)
{
+ float amount = GetFloatValue(UNIT_FIELD_BASEATTACKTIME + AsUnderlyingType(att));
+
float remainingTimePct = std::max((float)m_attackTimer[att], 0.0f) / (GetAttackTime(att) * m_modAttackSpeedPct[att]);
- if (val > 0)
+ if (val > 0.f)
{
ApplyPercentModFloatVar(m_modAttackSpeedPct[att], val, !apply);
- ApplyPercentModFloatValue(static_cast<uint16>(UNIT_FIELD_BASEATTACKTIME) + att, val, !apply);
+ ApplyPercentModFloatVar(amount, val, !apply);
}
else
{
ApplyPercentModFloatVar(m_modAttackSpeedPct[att], -val, apply);
- ApplyPercentModFloatValue(static_cast<uint16>(UNIT_FIELD_BASEATTACKTIME) + att, -val, apply);
+ ApplyPercentModFloatVar(amount, -val, apply);
}
+ SetFloatValue(UNIT_FIELD_BASEATTACKTIME + AsUnderlyingType(att), amount);
m_attackTimer[att] = uint32(GetAttackTime(att) * m_modAttackSpeedPct[att] * remainingTimePct);
}
void Unit::ApplyCastTimePercentMod(float val, bool apply)
{
- if (val > 0)
- ApplyPercentModFloatValue(UNIT_MOD_CAST_SPEED, val, !apply);
+ float amount = GetFloatValue(UNIT_MOD_CAST_SPEED);
+
+ if (val > 0.f)
+ ApplyPercentModFloatVar(amount, val, !apply);
else
- ApplyPercentModFloatValue(UNIT_MOD_CAST_SPEED, -val, apply);
+ ApplyPercentModFloatVar(amount, -val, apply);
+
+ SetFloatValue(UNIT_MOD_CAST_SPEED, amount);
}
uint32 Unit::GetCastingTimeForBonus(SpellInfo const* spellProto, DamageEffectType damagetype, uint32 CastingTime) const
@@ -18388,7 +18609,7 @@ bool Unit::SetCharmedBy(Unit* charmer, CharmType type, AuraApplication const* au
}
// dismount players when charmed
- if (IsPlayer())
+ if (IsPlayer() && type != CHARM_TYPE_POSSESS)
RemoveAurasByType(SPELL_AURA_MOUNTED);
if (charmer->IsPlayer())
@@ -20736,7 +20957,7 @@ void Unit::PatchValuesUpdate(ByteBuffer& valuesUpdateBuf, BuildValuesCachePosPoi
appendValue &= ~UNIT_NPC_FLAG_VENDOR_MASK;
}
- if (!creature->IsValidTrainerForPlayer(target, &appendValue))
+ if (!target->CanSeeTrainer(creature))
appendValue &= ~UNIT_NPC_FLAG_TRAINER;
valuesUpdateBuf.put(posPointers.UnitNPCFlagsPos, appendValue);
diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h
index c335f7e0ed..1590c2721f 100644
--- a/src/server/game/Entities/Unit/Unit.h
+++ b/src/server/game/Entities/Unit/Unit.h
@@ -123,13 +123,18 @@ enum HitInfo
HITINFO_FAKE_DAMAGE = 0x01000000 // enables damage animation even if no damage done, set only if no damage
};
-enum UnitModifierType
+enum UnitModifierFlatType
{
BASE_VALUE = 0,
- BASE_PCT = 1,
- TOTAL_VALUE = 2,
- TOTAL_PCT = 3,
- MODIFIER_TYPE_END = 4
+ TOTAL_VALUE = 1,
+ MODIFIER_TYPE_FLAT_END = 3
+};
+
+enum UnitModifierPctType
+{
+ BASE_PCT = 0,
+ TOTAL_PCT = 1,
+ MODIFIER_TYPE_PCT_END = 2
};
enum WeaponDamageRange
@@ -214,7 +219,7 @@ enum WeaponAttackType : uint8
MAX_ATTACK
};
-enum CombatRating
+enum CombatRating : uint8
{
CR_WEAPON_SKILL = 0,
CR_DEFENSE_SKILL = 1,
@@ -769,6 +774,8 @@ public:
inline bool IsCrowdControlled() const { return HasFlag(UNIT_FIELD_FLAGS, (UNIT_FLAG_CONFUSED | UNIT_FLAG_FLEEING | UNIT_FLAG_STUNNED)); }
inline bool IsImmobilizedState() const { return HasUnitState(UNIT_STATE_ROOT | UNIT_STATE_STUNNED); }
+ void SetAnimTier(AnimTier animTier);
+
/*********************************************************/
/*** UNIT TYPES, CLASSES, RACES... ***/
/*********************************************************/
@@ -1050,16 +1057,32 @@ public:
for (uint8 i = STAT_STRENGTH; i < MAX_STATS; ++i) SetFloatValue(static_cast<uint16>(UNIT_FIELD_NEGSTAT0) + i, 0);
}
- bool HandleStatModifier(UnitMods unitMod, UnitModifierType modifierType, float amount, bool apply);
- void SetModifierValue(UnitMods unitMod, UnitModifierType modifierType, float value) { m_auraModifiersGroup[unitMod][modifierType] = value; }
- [[nodiscard]] float GetModifierValue(UnitMods unitMod, UnitModifierType modifierType) const;
+ bool HandleStatFlatModifier(UnitMods unitMod, UnitModifierFlatType modifierType, float amount, bool apply);
+ void ApplyStatPctModifier(UnitMods unitMod, UnitModifierPctType modifierType, float amount);
+
+ void SetStatFlatModifier(UnitMods unitMod, UnitModifierFlatType modifierType, float val);
+ void SetStatPctModifier(UnitMods unitMod, UnitModifierPctType modifierType, float val);
+
+ [[nodiscard]] float GetFlatModifierValue(UnitMods unitMod, UnitModifierFlatType modifierType) const;
+ [[nodiscard]] float GetPctModifierValue(UnitMods unitMod, UnitModifierPctType modifierType) const;
+
+ void UpdateUnitMod(UnitMods unitMod);
+
+ // only players have item requirements
+ [[nodiscard]] virtual bool CheckAttackFitToAuraRequirement(WeaponAttackType /*attackType*/, AuraEffect const* /*aurEff*/) const { return true; }
+
+ virtual void UpdateDamageDoneMods(WeaponAttackType attackType, int32 skipEnchantSlot = -1);
+ void UpdateAllDamageDoneMods();
+
+ void UpdateDamagePctDoneMods(WeaponAttackType attackType);
+ void UpdateAllDamagePctDoneMods();
+
[[nodiscard]] float GetTotalStatValue(Stats stat, float additionalValue = 0.0f) const;
void SetCanModifyStats(bool modifyStats) { m_canModifyStats = modifyStats; }
[[nodiscard]] bool CanModifyStats() const { return m_canModifyStats; }
- void ApplyStatBuffMod(Stats stat, float val, bool apply) { ApplyModSignedFloatValue((val > 0 ? static_cast<uint16>(UNIT_FIELD_POSSTAT0) + stat : static_cast<uint16>(UNIT_FIELD_NEGSTAT0) + stat), val, apply); }
- void ApplyStatPercentBuffMod(Stats stat, float val, bool apply);
+ void UpdateStatBuffMod(Stats stat);
// Unit level methods
[[nodiscard]] uint8 GetLevel() const { return uint8(GetUInt32Value(UNIT_FIELD_LEVEL)); }
@@ -1104,7 +1127,6 @@ public:
void SetMaxPower(Powers power, uint32 val);
int32 ModifyPower(Powers power, int32 val, bool withPowerUpdate = true);
- int32 ModifyPowerPct(Powers power, float pct, bool apply = true);
void RewardRage(uint32 damage, uint32 weaponSpeedHitFactor, bool attacker);
@@ -1159,13 +1181,9 @@ public:
[[nodiscard]] uint32 GetResistance(SpellSchoolMask mask) const;
[[nodiscard]] uint32 GetResistance(SpellSchools school) const { return GetUInt32Value(static_cast<uint16>(UNIT_FIELD_RESISTANCES) + school); }
static float GetEffectiveResistChance(Unit const* owner, SpellSchoolMask schoolMask, Unit const* victim);
- [[nodiscard]] float GetResistanceBuffMods(SpellSchools school, bool positive) const { return GetFloatValue(positive ? static_cast<uint16>(UNIT_FIELD_RESISTANCEBUFFMODSPOSITIVE) + school : static_cast<uint16>(UNIT_FIELD_RESISTANCEBUFFMODSNEGATIVE) + + school); }
void SetResistance(SpellSchools school, int32 val) { SetStatInt32Value(static_cast<uint16>(UNIT_FIELD_RESISTANCES) + school, val); }
- void SetResistanceBuffMods(SpellSchools school, bool positive, float val) { SetFloatValue(positive ? static_cast<uint16>(UNIT_FIELD_RESISTANCEBUFFMODSPOSITIVE) + school : static_cast<uint16>(UNIT_FIELD_RESISTANCEBUFFMODSNEGATIVE) + + school, val); }
-
- void ApplyResistanceBuffModsMod(SpellSchools school, bool positive, float val, bool apply) { ApplyModSignedFloatValue(positive ? static_cast<uint16>(UNIT_FIELD_RESISTANCEBUFFMODSPOSITIVE) + school : static_cast<uint16>(UNIT_FIELD_RESISTANCEBUFFMODSNEGATIVE) + + school, val, apply); }
- void ApplyResistanceBuffModsPercentMod(SpellSchools school, bool positive, float val, bool apply) { ApplyPercentModFloatValue(positive ? static_cast<uint16>(UNIT_FIELD_RESISTANCEBUFFMODSPOSITIVE) + school : static_cast<uint16>(UNIT_FIELD_RESISTANCEBUFFMODSNEGATIVE) + + school, val, apply); }
+ void UpdateResistanceBuffModsMod(SpellSchools school);
//////////// Need triage ////////////////
uint16 GetMaxSkillValueForLevel(Unit const* target = nullptr) const { return (target ? getLevelForTarget(target) : GetLevel()) * 5; }
@@ -1319,6 +1337,10 @@ public:
void SetAuraStack(uint32 spellId, Unit* target, uint32 stack);
+ int32 GetHighestExclusiveSameEffectSpellGroupValue(AuraEffect const* aurEff, AuraType auraType, bool checkMiscValue = false, int32 miscValue = 0) const;
+ bool IsHighestExclusiveAura(Aura const* aura, bool removeOtherAuraApplications = false);
+ bool IsHighestExclusiveAuraEffect(SpellInfo const* spellInfo, AuraType auraType, int32 effectAmount, uint8 auraEffectMask, bool removeOtherAuraApplications = false);
+
// aura apply/remove helpers - you should better not use these
Aura* _TryStackingOrRefreshingExistingAura(SpellInfo const* newAura, uint8 effMask, Unit* caster, int32* baseAmount = nullptr, Item* castItem = nullptr, ObjectGuid casterGUID = ObjectGuid::Empty, bool periodicReset = false);
void _AddAura(UnitAura* aura, Unit* caster);
@@ -1328,7 +1350,7 @@ public:
void _UnapplyAura(AuraApplicationMap::iterator& i, AuraRemoveMode removeMode);
void _UnapplyAura(AuraApplication* aurApp, AuraRemoveMode removeMode);
void _RemoveNoStackAuraApplicationsDueToAura(Aura* aura);
- void _RemoveNoStackAurasDueToAura(Aura* aura);
+ void _RemoveNoStackAurasDueToAura(Aura* aura, bool owned);
bool _IsNoStackAuraDueToAura(Aura* appliedAura, Aura* existingAura) const;
void _RegisterAuraEffect(AuraEffect* aurEff, bool apply);
@@ -1474,15 +1496,19 @@ public:
uint32 GetDiseasesByCaster(ObjectGuid casterGUID, uint8 mode = 0);
[[nodiscard]] uint32 GetDoTsByCaster(ObjectGuid casterGUID) const;
- [[nodiscard]] int32 GetTotalAuraModifierAreaExclusive(AuraType auratype) const;
[[nodiscard]] int32 GetTotalAuraModifier(AuraType auratype) const;
[[nodiscard]] float GetTotalAuraMultiplier(AuraType auratype) const;
- int32 GetMaxPositiveAuraModifier(AuraType auratype);
+ [[nodiscard]] int32 GetMaxPositiveAuraModifier(AuraType auratype) const;
[[nodiscard]] int32 GetMaxNegativeAuraModifier(AuraType auratype) const;
+ [[nodiscard]] int32 GetTotalAuraModifier(AuraType auratype, std::function<bool(AuraEffect const*)> const& predicate) const;
+ [[nodiscard]] float GetTotalAuraMultiplier(AuraType auraType, std::function<bool(AuraEffect const*)> const& predicate) const;
+ [[nodiscard]] int32 GetMaxPositiveAuraModifier(AuraType auraType, std::function<bool(AuraEffect const*)> const& predicate) const;
+ [[nodiscard]] int32 GetMaxNegativeAuraModifier(AuraType auraType, std::function<bool(AuraEffect const*)> const& predicate) const;
+
[[nodiscard]] int32 GetTotalAuraModifierByMiscMask(AuraType auratype, uint32 misc_mask) const;
[[nodiscard]] float GetTotalAuraMultiplierByMiscMask(AuraType auratype, uint32 misc_mask) const;
- int32 GetMaxPositiveAuraModifierByMiscMask(AuraType auratype, uint32 misc_mask, const AuraEffect* except = nullptr) const;
+ [[nodiscard]] int32 GetMaxPositiveAuraModifierByMiscMask(AuraType auratype, uint32 misc_mask, const AuraEffect* except = nullptr) const;
[[nodiscard]] int32 GetMaxNegativeAuraModifierByMiscMask(AuraType auratype, uint32 misc_mask) const;
[[nodiscard]] int32 GetTotalAuraModifierByMiscValue(AuraType auratype, int32 misc_value) const;
@@ -1581,7 +1607,6 @@ public:
int32 HealBySpell(HealInfo& healInfo, bool critical = false);
int32 SpellBaseHealingBonusDone(SpellSchoolMask schoolMask);
- int32 SpellBaseHealingBonusTaken(SpellSchoolMask schoolMask);
float SpellPctHealingModsDone(Unit* victim, SpellInfo const* spellProto, DamageEffectType damagetype);
uint32 SpellHealingBonusDone(Unit* victim, SpellInfo const* spellProto, uint32 healamount, DamageEffectType damagetype, uint8 effIndex, float TotalMod = 0.0f, uint32 stack = 1);
uint32 SpellHealingBonusTaken(Unit* caster, SpellInfo const* spellProto, uint32 healamount, DamageEffectType damagetype, uint32 stack = 1);
@@ -2132,7 +2157,8 @@ protected:
AuraStateAurasMap m_auraStateAuras; // Used for improve performance of aura state checks on aura apply/remove
uint32 m_interruptMask;
- float m_auraModifiersGroup[UNIT_MOD_END][MODIFIER_TYPE_END];
+ float m_auraFlatModifiersGroup[UNIT_MOD_END][MODIFIER_TYPE_FLAT_END];
+ float m_auraPctModifiersGroup[UNIT_MOD_END][MODIFIER_TYPE_PCT_END];
float m_weaponDamage[MAX_ATTACK][MAX_WEAPON_DAMAGE_RANGE][MAX_ITEM_PROTO_DAMAGES];
bool m_canModifyStats;
VisibleAuraMap m_visibleAuras;
diff --git a/src/server/game/Events/GameEventMgr.cpp b/src/server/game/Events/GameEventMgr.cpp
index 509d5235ed..b4e6919af5 100644
--- a/src/server/game/Events/GameEventMgr.cpp
+++ b/src/server/game/Events/GameEventMgr.cpp
@@ -21,6 +21,7 @@
#include "DisableMgr.h"
#include "GameObjectAI.h"
#include "GameTime.h"
+#include "HolidayDateCalculator.h"
#include "Language.h"
#include "Log.h"
#include "MapMgr.h"
@@ -34,7 +35,7 @@
#include "WorldSessionMgr.h"
#include "WorldState.h"
#include "WorldStatePackets.h"
-#include <time.h>
+#include <chrono>
GameEventMgr* GameEventMgr::instance()
{
@@ -1070,51 +1071,126 @@ void GameEventMgr::LoadFromDB()
void GameEventMgr::LoadHolidayDates()
{
- uint32 oldMSTime = getMSTime();
-
- WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_SEL_GAME_EVENT_HOLIDAY_DATES);
- PreparedQueryResult result = WorldDatabase.Query(stmt);
-
- if (!result)
- {
- LOG_WARN("server.loading", ">> Loaded 0 holiday dates. DB table `holiday_dates` is empty.");
- return;
- }
-
- uint32 count = 0;
- do
- {
- Field* fields = result->Fetch();
-
- uint32 holidayId = fields[0].Get<uint32>();
- HolidaysEntry* entry = const_cast<HolidaysEntry*>(sHolidaysStore.LookupEntry(holidayId));
+ uint32 const oldMSTime = getMSTime();
+ uint32 dynamicCount = 0;
+ uint32 dbCount = 0;
+
+ // Step 1: Generate dynamic holiday dates based on current year
+ std::chrono::system_clock::time_point const now = std::chrono::system_clock::now();
+ std::time_t const nowTime = std::chrono::system_clock::to_time_t(now);
+ std::tm localTime = {};
+#ifdef _WIN32
+ localtime_s(&localTime, &nowTime);
+#else
+ localtime_r(&nowTime, &localTime);
+#endif
+ int const currentYear = localTime.tm_year + 1900;
+
+ for (auto const& rule : HolidayDateCalculator::GetHolidayRules())
+ {
+ HolidaysEntry* entry = const_cast<HolidaysEntry*>(sHolidaysStore.LookupEntry(rule.holidayId));
if (!entry)
{
- LOG_ERROR("sql.sql", "holiday_dates entry has invalid holiday id {}.", holidayId);
+ LOG_INFO("server.loading", ">> Holiday {} not found in DBC - cannot set dynamic dates", rule.holidayId);
continue;
}
- uint8 dateId = fields[1].Get<uint8>();
- if (dateId >= MAX_HOLIDAY_DATES)
+ // Special handling for Darkmoon Faire - needs multiple dates per year (4 occurrences)
+ if (rule.type == HolidayCalculationType::DARKMOON_FAIRE)
{
- LOG_ERROR("sql.sql", "holiday_dates entry has out of range date_id {}.", dateId);
+ int const locationOffset = rule.month;
+ std::vector<uint32_t> const dates = HolidayDateCalculator::GetDarkmoonFaireDates(locationOffset, currentYear - 1, 4, rule.offset);
+
+ uint8 dateId = 0;
+ for (auto const& packedDate : dates)
+ {
+ if (dateId >= MAX_HOLIDAY_DATES)
+ break;
+
+ entry->Date[dateId++] = packedDate;
+ ++dynamicCount;
+ }
+
+ // Darkmoon Faire lasts 7 days (168 hours) - set Duration if not already set
+ if (!entry->Duration[0])
+ entry->Duration[0] = 168; // 7 days in hours
+
+ auto itr = std::lower_bound(ModifiedHolidays.begin(), ModifiedHolidays.end(), entry->Id);
+ if (itr == ModifiedHolidays.end() || *itr != entry->Id)
+ ModifiedHolidays.insert(itr, entry->Id);
+
continue;
}
- entry->Date[dateId] = fields[2].Get<uint32>();
- if (uint32 duration = fields[3].Get<uint32>())
- entry->Duration[0] = duration;
+ // Generate dates for current year + 2 ahead (year capped at 2030 due to 5-bit client limitation)
+ for (int yearOffset = -1; yearOffset <= 2; ++yearOffset)
+ {
+ int const year = currentYear + yearOffset;
+ if (year > 2030)
+ break;
+
+ uint8 const dateId = static_cast<uint8>(yearOffset + 1);
+ if (dateId >= MAX_HOLIDAY_DATES)
+ break;
+
+ uint32_t const packedDate = HolidayDateCalculator::GetPackedHolidayDate(rule.holidayId, year);
+ entry->Date[dateId] = packedDate;
+
+ // Debug: decode and log the date
+ std::tm const date = HolidayDateCalculator::UnpackDate(packedDate);
+ LOG_DEBUG("server.loading", ">> Holiday {} Date[{}] = {}-{:02d}-{:02d}",
+ rule.holidayId, dateId, date.tm_year + 1900, date.tm_mon + 1, date.tm_mday);
+
+ ++dynamicCount;
+ }
auto itr = std::lower_bound(ModifiedHolidays.begin(), ModifiedHolidays.end(), entry->Id);
if (itr == ModifiedHolidays.end() || *itr != entry->Id)
- {
ModifiedHolidays.insert(itr, entry->Id);
- }
+ }
- ++count;
- } while (result->NextRow());
+ // Step 2: Check game_event.start_time for overrides (allows custom servers to override calculated dates)
+ // Only use as override if start_time year >= current year (ignore old static dates)
+ QueryResult result = WorldDatabase.Query("SELECT holiday, UNIX_TIMESTAMP(start_time) FROM game_event WHERE holiday != 0 AND start_time > '2000-12-31'");
+
+ if (result)
+ {
+ do
+ {
+ Field* fields = result->Fetch();
+
+ uint32 const holidayId = fields[0].Get<uint32>();
+ HolidaysEntry* entry = const_cast<HolidaysEntry*>(sHolidaysStore.LookupEntry(holidayId));
+ if (!entry)
+ continue;
+
+ if (fields[1].IsNull())
+ continue;
+
+ time_t const startTime = fields[1].Get<uint64>();
+ if (startTime == 0)
+ continue;
+
+ std::tm const timeInfo = Acore::Time::TimeBreakdown(startTime);
+
+ int const year = timeInfo.tm_year + 1900;
+ // Only override if start_time is current year or later (ignore old static dates)
+ if (year < currentYear || year > 2030)
+ continue;
+
+ // Pack the date in WoW format and override Date[0]
+ uint32_t const yearOffset = static_cast<uint32_t>(year - 2000);
+ uint32_t const month = static_cast<uint32_t>(timeInfo.tm_mon);
+ uint32_t const day = static_cast<uint32_t>(timeInfo.tm_mday - 1);
+ uint32_t const weekday = static_cast<uint32_t>(timeInfo.tm_wday);
+ entry->Date[0] = (yearOffset << 24) | (month << 20) | (day << 14) | (weekday << 11);
+
+ ++dbCount;
+ } while (result->NextRow());
+ }
- LOG_INFO("server.loading", ">> Loaded {} Holiday Dates in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
+ LOG_INFO("server.loading", ">> Loaded {} Holiday Dates ({} dynamic, {} game_event overrides) in {} ms",
+ dynamicCount + dbCount, dynamicCount, dbCount, GetMSTimeDiffToNow(oldMSTime));
}
uint32 GameEventMgr::GetNPCFlag(Creature* cr)
@@ -1926,7 +2002,7 @@ void GameEventMgr::SetHolidayEventTime(GameEventData& event)
}
else
{
- // date is due and not a singleDate event, try with next DBC date (modified by holiday_dates)
+ // date is due and not a singleDate event, try with next DBC date (dynamically calculated or overridden by game_event.start_time)
// if none is found we don't modify start date and use the one in game_event
}
}
diff --git a/src/server/game/Events/HolidayDateCalculator.cpp b/src/server/game/Events/HolidayDateCalculator.cpp
new file mode 100644
index 0000000000..8bb7f39e8c
--- /dev/null
+++ b/src/server/game/Events/HolidayDateCalculator.cpp
@@ -0,0 +1,582 @@
+/*
+ * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "HolidayDateCalculator.h"
+#include "SharedDefines.h"
+#include <cmath>
+
+// Constants for astronomical calculations
+constexpr double PI = 3.14159265358979323846;
+constexpr double DEG_TO_RAD = PI / 180.0;
+
+// Helper: sin/cos in degrees
+inline double sind(double deg) { return std::sin(deg * DEG_TO_RAD); }
+
+// Static holiday rules configuration
+static const std::vector<HolidayRule> HolidayRules = {
+ // Lunar Festival: Chinese New Year - 1 day (event starts day before CNY)
+ { HOLIDAY_LUNAR_FESTIVAL, HolidayCalculationType::LUNAR_NEW_YEAR, 0, 0, 0, -1 },
+
+ // Love is in the Air: First Monday on or after Feb 3
+ { HOLIDAY_LOVE_IS_IN_THE_AIR, HolidayCalculationType::WEEKDAY_ON_OR_AFTER, 2, 3, static_cast<int>(Weekday::MONDAY), 0 },
+
+ // Noblegarden: Day after Easter Sunday (Easter + 1 day)
+ { HOLIDAY_NOBLEGARDEN, HolidayCalculationType::EASTER_OFFSET, 0, 0, 0, 1 },
+
+ // Children's Week: First Monday on or after Apr 25 (Monday closest to May 1)
+ { HOLIDAY_CHILDRENS_WEEK, HolidayCalculationType::WEEKDAY_ON_OR_AFTER, 4, 25, static_cast<int>(Weekday::MONDAY), 0 },
+
+ // Midsummer Fire Festival: Fixed Jun 21
+ { HOLIDAY_FIRE_FESTIVAL, HolidayCalculationType::FIXED_DATE, 6, 21, 0, 0 },
+
+ // Fireworks Spectacular: Fixed Jul 4
+ { HOLIDAY_FIREWORKS_SPECTACULAR, HolidayCalculationType::FIXED_DATE, 7, 4, 0, 0 },
+
+ // Pirates' Day: Fixed Sep 19
+ { HOLIDAY_PIRATES_DAY, HolidayCalculationType::FIXED_DATE, 9, 19, 0, 0 },
+
+ // Brewfest: Oktoberfest rule - first Saturday on/after Sept 15, minus 7 for holidayStage offset
+ { HOLIDAY_BREWFEST, HolidayCalculationType::WEEKDAY_ON_OR_AFTER, 9, 15, static_cast<int>(Weekday::SATURDAY), -7 },
+
+ // Harvest Festival: 2 days before autumn equinox (Sept 20-21)
+ { HOLIDAY_HARVEST_FESTIVAL, HolidayCalculationType::AUTUMN_EQUINOX, 0, 0, 0, -2 },
+
+ // Hallow's End: Fixed Oct 18
+ { HOLIDAY_HALLOWS_END, HolidayCalculationType::FIXED_DATE, 10, 18, 0, 0 },
+
+ // Day of the Dead: Fixed Nov 1
+ { HOLIDAY_DAY_OF_DEAD, HolidayCalculationType::FIXED_DATE, 11, 1, 0, 0 },
+
+ // Pilgrim's Bounty: Sunday before Thanksgiving (4th Thursday - 4 days)
+ { HOLIDAY_PILGRIMS_BOUNTY, HolidayCalculationType::NTH_WEEKDAY, 11, 4, static_cast<int>(Weekday::THURSDAY), -4 },
+
+ // Winter Veil: 6 days before winter solstice (Dec 15-16)
+ { HOLIDAY_FEAST_OF_WINTER_VEIL, HolidayCalculationType::WINTER_SOLSTICE, 0, 0, 0, -6 },
+
+ // Darkmoon Faire: First Sunday of months matching (month % 3 == locationOffset)
+ // Rotates monthly: Mulgore (Jan) -> Terokkar (Feb) -> Elwynn (Mar) -> repeat
+ // rule.month stores the location offset
+ // rule.offset is -2 (building phase starts Friday, 2 days before faire opens on Sunday)
+ { HOLIDAY_DARKMOON_FAIRE_ELWYNN, HolidayCalculationType::DARKMOON_FAIRE, 0, 0, 0, -2 }, // Mar, Jun, Sep, Dec
+ { HOLIDAY_DARKMOON_FAIRE_THUNDER, HolidayCalculationType::DARKMOON_FAIRE, 1, 0, 0, -2 }, // Jan, Apr, Jul, Oct
+ { HOLIDAY_DARKMOON_FAIRE_SHATTRATH, HolidayCalculationType::DARKMOON_FAIRE, 2, 0, 0, -2 } // Feb, May, Aug, Nov
+};
+
+const std::vector<HolidayRule>& HolidayDateCalculator::GetHolidayRules()
+{
+ return HolidayRules;
+}
+
+std::tm HolidayDateCalculator::CalculateEasterSunday(int year)
+{
+ // Anonymous Gregorian algorithm (Computus)
+ // Reference: https://en.wikipedia.org/wiki/Date_of_Easter#Anonymous_Gregorian_algorithm
+ int const a = year % 19;
+ int const b = year / 100;
+ int const c = year % 100;
+ int const d = b / 4;
+ int const e = b % 4;
+ int const f = (b + 8) / 25;
+ int const g = (b - f + 1) / 3;
+ int const h = (19 * a + b - d - g + 15) % 30;
+ int const i = c / 4;
+ int const k = c % 4;
+ int const l = (32 + 2 * e + 2 * i - h - k) % 7;
+ int const m = (a + 11 * h + 22 * l) / 451;
+ int const month = (h + l - 7 * m + 114) / 31;
+ int const day = ((h + l - 7 * m + 114) % 31) + 1;
+
+ std::tm result = {};
+ result.tm_year = year - 1900;
+ result.tm_mon = month - 1;
+ result.tm_mday = day;
+ mktime(&result); // Normalize and fill in other fields
+
+ return result;
+}
+
+std::tm HolidayDateCalculator::CalculateNthWeekday(int year, int month, Weekday weekday, int n)
+{
+ // Start with first day of the month
+ std::tm date = {};
+ date.tm_year = year - 1900;
+ date.tm_mon = month - 1;
+ date.tm_mday = 1;
+ mktime(&date);
+
+ // Find first occurrence of the target weekday
+ int const daysUntilWeekday = (static_cast<int>(weekday) - date.tm_wday + 7) % 7;
+ date.tm_mday = 1 + daysUntilWeekday;
+
+ // Move to nth occurrence
+ date.tm_mday += (n - 1) * 7;
+
+ mktime(&date); // Normalize (handles month overflow)
+ return date;
+}
+
+std::tm HolidayDateCalculator::CalculateWeekdayOnOrAfter(int year, int month, int day, Weekday weekday)
+{
+ // Start with the specified date
+ std::tm date = {};
+ date.tm_year = year - 1900;
+ date.tm_mon = month - 1;
+ date.tm_mday = day;
+ mktime(&date);
+
+ // Find days until the target weekday (0 if already on that day)
+ int const daysUntilWeekday = (static_cast<int>(weekday) - date.tm_wday + 7) % 7;
+ date.tm_mday += daysUntilWeekday;
+
+ mktime(&date); // Normalize
+ return date;
+}
+
+// ============================================================================
+// LUNAR NEW YEAR CALCULATION
+// Based on Jean Meeus "Astronomical Algorithms" (1991), Chapter 49
+// Reference: https://celestialprogramming.com/moonphases.html
+// Chinese New Year = new moon falling between January 21 and February 20
+// ============================================================================
+
+double HolidayDateCalculator::DateToJulianDay(int year, int month, double day)
+{
+ if (month <= 2)
+ {
+ year -= 1;
+ month += 12;
+ }
+ int const A = year / 100;
+ int const B = 2 - A + (A / 4);
+ return std::floor(365.25 * (year + 4716)) + std::floor(30.6001 * (month + 1)) + day + B - 1524.5;
+}
+
+void HolidayDateCalculator::JulianDayToDate(double jd, int& year, int& month, int& day)
+{
+ jd += 0.5;
+ int const Z = static_cast<int>(jd);
+ int A = Z;
+ if (Z >= 2299161)
+ {
+ int const alpha = static_cast<int>((Z - 1867216.25) / 36524.25);
+ A = Z + 1 + alpha - (alpha / 4);
+ }
+ int const B = A + 1524;
+ int const C = static_cast<int>((B - 122.1) / 365.25);
+ int const D = static_cast<int>(365.25 * C);
+ int const E = static_cast<int>((B - D) / 30.6001);
+
+ day = B - D - static_cast<int>(30.6001 * E);
+ month = (E < 14) ? E - 1 : E - 13;
+ year = (month > 2) ? C - 4716 : C - 4715;
+}
+
+double HolidayDateCalculator::CalculateNewMoon(double k)
+{
+ // Meeus "Astronomical Algorithms" Chapter 49
+ double const T = k / 1236.85;
+ double const T2 = T * T;
+ double const T3 = T2 * T;
+ double const T4 = T3 * T;
+
+ // Mean phase (Eq 49.1)
+ double const JDE = 2451550.09766 + 29.530588861 * k + 0.00015437 * T2
+ - 0.000000150 * T3 + 0.00000000073 * T4;
+
+ // Eccentricity correction
+ double const E = 1.0 - 0.002516 * T - 0.0000074 * T2;
+ double const E2 = E * E;
+
+ // Sun's mean anomaly (Eq 49.4)
+ double const M = 2.5534 + 29.10535670 * k - 0.0000014 * T2 - 0.00000011 * T3;
+
+ // Moon's mean anomaly (Eq 49.5)
+ double const MPrime = 201.5643 + 385.81693528 * k + 0.0107582 * T2
+ + 0.00001238 * T3 - 0.000000058 * T4;
+
+ // Moon's argument of latitude (Eq 49.6)
+ double const F = 160.7108 + 390.67050284 * k - 0.0016118 * T2
+ - 0.00000227 * T3 + 0.000000011 * T4;
+
+ // Longitude of ascending node (Eq 49.7)
+ double const Omega = 124.7746 - 1.56375588 * k + 0.0020672 * T2 + 0.00000215 * T3;
+
+ // New Moon corrections (Table 49.A)
+ double correction =
+ - 0.40720 * sind(MPrime)
+ + 0.17241 * E * sind(M)
+ + 0.01608 * sind(2 * MPrime)
+ + 0.01039 * sind(2 * F)
+ + 0.00739 * E * sind(MPrime - M)
+ - 0.00514 * E * sind(MPrime + M)
+ + 0.00208 * E2 * sind(2 * M)
+ - 0.00111 * sind(MPrime - 2 * F)
+ - 0.00057 * sind(MPrime + 2 * F)
+ + 0.00056 * E * sind(2 * MPrime + M)
+ - 0.00042 * sind(3 * MPrime)
+ + 0.00042 * E * sind(M + 2 * F)
+ + 0.00038 * E * sind(M - 2 * F)
+ - 0.00024 * E * sind(2 * MPrime - M)
+ - 0.00017 * sind(Omega);
+
+ // Additional planetary corrections (Table 49.B)
+ double const A1 = 299.77 + 0.107408 * k - 0.009173 * T2;
+ double const A2 = 251.88 + 0.016321 * k;
+ double const A3 = 251.83 + 26.651886 * k;
+ double const A4 = 349.42 + 36.412478 * k;
+ double const A5 = 84.66 + 18.206239 * k;
+ double const A6 = 141.74 + 53.303771 * k;
+ double const A7 = 207.14 + 2.453732 * k;
+ double const A8 = 154.84 + 7.306860 * k;
+ double const A9 = 34.52 + 27.261239 * k;
+ double const A10 = 207.19 + 0.121824 * k;
+ double const A11 = 291.34 + 1.844379 * k;
+ double const A12 = 161.72 + 24.198154 * k;
+ double const A13 = 239.56 + 25.513099 * k;
+ double const A14 = 331.55 + 3.592518 * k;
+
+ correction += 0.000325 * sind(A1) + 0.000165 * sind(A2) + 0.000164 * sind(A3)
+ + 0.000126 * sind(A4) + 0.000110 * sind(A5) + 0.000062 * sind(A6)
+ + 0.000060 * sind(A7) + 0.000056 * sind(A8) + 0.000047 * sind(A9)
+ + 0.000042 * sind(A10) + 0.000040 * sind(A11) + 0.000037 * sind(A12)
+ + 0.000035 * sind(A13) + 0.000023 * sind(A14);
+
+ return JDE + correction;
+}
+
+std::tm HolidayDateCalculator::CalculateLunarNewYear(int year)
+{
+ // Chinese New Year always falls on the new moon between Jan 21 and Feb 20
+ double const jan21JD = DateToJulianDay(year, 1, 21.0);
+ double const feb21JD = DateToJulianDay(year, 2, 21.0);
+
+ // Approximate lunation number k for January of target year
+ double const approxK = (year - 2000.0) * 12.3685;
+ double const k = std::floor(approxK);
+
+ // Search for the new moon in the valid range
+ for (int i = -2; i <= 2; ++i)
+ {
+ double const nmJDE = CalculateNewMoon(k + i);
+
+ // Convert TT (Terrestrial Time) to UT (approximate DeltaT ~70s for 2020s)
+ double nmJD = nmJDE - 70.0 / 86400.0;
+
+ // Add 8 hours for China Standard Time (UTC+8)
+ nmJD += 8.0 / 24.0;
+
+ if (nmJD >= jan21JD && nmJD < feb21JD)
+ {
+ int cnyYear, cnyMonth, cnyDay;
+ JulianDayToDate(nmJD, cnyYear, cnyMonth, cnyDay);
+
+ std::tm result = {};
+ result.tm_year = cnyYear - 1900;
+ result.tm_mon = cnyMonth - 1;
+ result.tm_mday = cnyDay;
+ mktime(&result);
+ return result;
+ }
+ }
+
+ // Fallback (should never happen for years 2000-2031)
+ std::tm fallback = {};
+ fallback.tm_year = year - 1900;
+ fallback.tm_mon = 0; // January
+ fallback.tm_mday = 25;
+ mktime(&fallback);
+ return fallback;
+}
+
+// ============================================================================
+// AUTUMN EQUINOX CALCULATION
+// Based on Jean Meeus "Astronomical Algorithms" (1991), Chapter 27
+// Reference: https://en.wikipedia.org/wiki/Equinox#Calculation
+// ============================================================================
+
+std::tm HolidayDateCalculator::CalculateAutumnEquinox(int year)
+{
+ // Meeus algorithm for mean September equinox (Table 27.C)
+ // Valid for years 2000-3000
+ double const Y = (year - 2000.0) / 1000.0;
+ double const Y2 = Y * Y;
+ double const Y3 = Y2 * Y;
+ double const Y4 = Y3 * Y;
+
+ // Mean equinox JDE0 (Eq 27.1 for September equinox after 2000)
+ double const JDE0 = 2451810.21715 + 365242.01767 * Y - 0.11575 * Y2
+ + 0.00337 * Y3 + 0.00078 * Y4;
+
+ // Periodic terms for correction (Table 27.B)
+ double const T = (JDE0 - 2451545.0) / 36525.0;
+ double const W = 35999.373 * T - 2.47;
+ double const deltaLambda = 1.0 + 0.0334 * std::cos(W * DEG_TO_RAD)
+ + 0.0007 * std::cos(2.0 * W * DEG_TO_RAD);
+
+ // Simplified correction (sum of periodic terms from Table 27.C)
+ // Using first few significant terms
+ double const S = 485 * std::cos((324.96 + 1934.136 * T) * DEG_TO_RAD)
+ + 203 * std::cos((337.23 + 32964.467 * T) * DEG_TO_RAD)
+ + 199 * std::cos((342.08 + 20.186 * T) * DEG_TO_RAD)
+ + 182 * std::cos((27.85 + 445267.112 * T) * DEG_TO_RAD)
+ + 156 * std::cos((73.14 + 45036.886 * T) * DEG_TO_RAD)
+ + 136 * std::cos((171.52 + 22518.443 * T) * DEG_TO_RAD)
+ + 77 * std::cos((222.54 + 65928.934 * T) * DEG_TO_RAD)
+ + 74 * std::cos((296.72 + 3034.906 * T) * DEG_TO_RAD)
+ + 70 * std::cos((243.58 + 9037.513 * T) * DEG_TO_RAD)
+ + 58 * std::cos((119.81 + 33718.147 * T) * DEG_TO_RAD)
+ + 52 * std::cos((297.17 + 150.678 * T) * DEG_TO_RAD)
+ + 50 * std::cos((21.02 + 2281.226 * T) * DEG_TO_RAD);
+
+ double const JDE = JDE0 + (0.00001 * S) / deltaLambda;
+
+ // Convert JDE to calendar date
+ int eqYear;
+ int eqMonth;
+ int eqDay;
+ JulianDayToDate(JDE, eqYear, eqMonth, eqDay);
+
+ std::tm result = {};
+ result.tm_year = eqYear - 1900;
+ result.tm_mon = eqMonth - 1;
+ result.tm_mday = eqDay;
+ mktime(&result);
+ return result;
+}
+
+// ============================================================================
+// WINTER SOLSTICE CALCULATION
+// Based on Jean Meeus "Astronomical Algorithms" (1991), Chapter 27
+// ============================================================================
+
+std::tm HolidayDateCalculator::CalculateWinterSolstice(int year)
+{
+ // Meeus algorithm for mean December solstice (Table 27.C)
+ // Valid for years 2000-3000
+ double const Y = (year - 2000.0) / 1000.0;
+ double const Y2 = Y * Y;
+ double const Y3 = Y2 * Y;
+ double const Y4 = Y3 * Y;
+
+ // Mean solstice JDE0 (Eq 27.1 for December solstice after 2000)
+ double const JDE0 = 2451900.05952 + 365242.74049 * Y - 0.06223 * Y2
+ - 0.00823 * Y3 + 0.00032 * Y4;
+
+ // Periodic terms for correction (Table 27.B)
+ double const T = (JDE0 - 2451545.0) / 36525.0;
+ double const W = 35999.373 * T - 2.47;
+ double const deltaLambda = 1.0 + 0.0334 * std::cos(W * DEG_TO_RAD)
+ + 0.0007 * std::cos(2.0 * W * DEG_TO_RAD);
+
+ // Simplified correction (sum of periodic terms from Table 27.C)
+ double const S = 485 * std::cos((324.96 + 1934.136 * T) * DEG_TO_RAD)
+ + 203 * std::cos((337.23 + 32964.467 * T) * DEG_TO_RAD)
+ + 199 * std::cos((342.08 + 20.186 * T) * DEG_TO_RAD)
+ + 182 * std::cos((27.85 + 445267.112 * T) * DEG_TO_RAD)
+ + 156 * std::cos((73.14 + 45036.886 * T) * DEG_TO_RAD)
+ + 136 * std::cos((171.52 + 22518.443 * T) * DEG_TO_RAD)
+ + 77 * std::cos((222.54 + 65928.934 * T) * DEG_TO_RAD)
+ + 74 * std::cos((296.72 + 3034.906 * T) * DEG_TO_RAD)
+ + 70 * std::cos((243.58 + 9037.513 * T) * DEG_TO_RAD)
+ + 58 * std::cos((119.81 + 33718.147 * T) * DEG_TO_RAD)
+ + 52 * std::cos((297.17 + 150.678 * T) * DEG_TO_RAD)
+ + 50 * std::cos((21.02 + 2281.226 * T) * DEG_TO_RAD);
+
+ double const JDE = JDE0 + (0.00001 * S) / deltaLambda;
+
+ // Convert JDE to calendar date
+ int solYear;
+ int solMonth;
+ int solDay;
+ JulianDayToDate(JDE, solYear, solMonth, solDay);
+
+ std::tm result = {};
+ result.tm_year = solYear - 1900;
+ result.tm_mon = solMonth - 1;
+ result.tm_mday = solDay;
+ mktime(&result);
+ return result;
+}
+
+std::tm HolidayDateCalculator::CalculateHolidayDate(const HolidayRule& rule, int year)
+{
+ std::tm result = {};
+
+ switch (rule.type)
+ {
+ case HolidayCalculationType::FIXED_DATE:
+ {
+ result.tm_year = year - 1900;
+ result.tm_mon = rule.month - 1;
+ result.tm_mday = rule.day;
+ mktime(&result);
+ break;
+ }
+ case HolidayCalculationType::NTH_WEEKDAY:
+ {
+ result = CalculateNthWeekday(year, rule.month, static_cast<Weekday>(rule.weekday), rule.day);
+ if (rule.offset != 0)
+ {
+ result.tm_mday += rule.offset;
+ mktime(&result); // Normalize
+ }
+ break;
+ }
+ case HolidayCalculationType::EASTER_OFFSET:
+ {
+ result = CalculateEasterSunday(year);
+ result.tm_mday += rule.offset;
+ mktime(&result); // Normalize
+ break;
+ }
+ case HolidayCalculationType::LUNAR_NEW_YEAR:
+ {
+ result = CalculateLunarNewYear(year);
+ if (rule.offset != 0)
+ {
+ result.tm_mday += rule.offset;
+ mktime(&result); // Normalize
+ }
+ break;
+ }
+ case HolidayCalculationType::WEEKDAY_ON_OR_AFTER:
+ {
+ result = CalculateWeekdayOnOrAfter(year, rule.month, rule.day, static_cast<Weekday>(rule.weekday));
+ if (rule.offset != 0)
+ {
+ result.tm_mday += rule.offset;
+ mktime(&result); // Normalize
+ }
+ break;
+ }
+ case HolidayCalculationType::AUTUMN_EQUINOX:
+ {
+ result = CalculateAutumnEquinox(year);
+ if (rule.offset != 0)
+ {
+ result.tm_mday += rule.offset;
+ mktime(&result); // Normalize
+ }
+ break;
+ }
+ case HolidayCalculationType::WINTER_SOLSTICE:
+ {
+ result = CalculateWinterSolstice(year);
+ if (rule.offset != 0)
+ {
+ result.tm_mday += rule.offset;
+ mktime(&result); // Normalize
+ }
+ break;
+ }
+ case HolidayCalculationType::DARKMOON_FAIRE:
+ {
+ // Return first occurrence for the year
+ // rule.month contains the location offset (0, 1, or 2)
+ int const locationOffset = rule.month;
+
+ // Find first month in the year where month % 3 == locationOffset
+ for (int month = 1; month <= 12; ++month)
+ {
+ if (month % 3 == locationOffset)
+ {
+ result = CalculateNthWeekday(year, month, Weekday::SUNDAY, 1);
+ break;
+ }
+ }
+ break;
+ }
+ }
+
+ return result;
+}
+
+uint32_t HolidayDateCalculator::PackDate(const std::tm& date)
+{
+ // WoW packed date format (same as ByteBuffer::AppendPackedTime):
+ // bits 24-28: year offset from 2000 (5 bits = 0-31, valid years 2000-2031)
+ // bits 20-23: month (0-indexed)
+ // bits 14-19: day (0-indexed)
+ // bits 11-13: weekday (0=Sunday, 6=Saturday - POSIX tm_wday)
+ // bits 6-10: hour
+ // bits 0-5: minute
+ int const year = date.tm_year + 1900;
+ // Client uses 5-bit year offset from 2000, so years before 2000 clamp to 0.
+ // If client is patched to support earlier years, update this logic.
+ uint32_t const yearOffset = (year < 2000) ? 0 : static_cast<uint32_t>(year - 2000);
+ uint32_t const month = static_cast<uint32_t>(date.tm_mon); // Already 0-indexed
+ uint32_t const day = static_cast<uint32_t>(date.tm_mday - 1); // Convert to 0-indexed
+ uint32_t const weekday = static_cast<uint32_t>(date.tm_wday); // 0=Sunday, 6=Saturday
+
+ return (yearOffset << 24) | (month << 20) | (day << 14) | (weekday << 11);
+}
+
+std::tm HolidayDateCalculator::UnpackDate(uint32_t packed)
+{
+ std::tm result = {};
+ result.tm_year = static_cast<int>(((packed >> 24) & 0x1F) + 2000 - 1900);
+ result.tm_mon = static_cast<int>((packed >> 20) & 0xF);
+ result.tm_mday = static_cast<int>(((packed >> 14) & 0x3F) + 1);
+ result.tm_wday = static_cast<int>((packed >> 11) & 0x7);
+ result.tm_hour = static_cast<int>((packed >> 6) & 0x1F);
+ result.tm_min = static_cast<int>(packed & 0x3F);
+ mktime(&result); // Normalize and fill in tm_yday, tm_isdst
+ return result;
+}
+
+uint32_t HolidayDateCalculator::GetPackedHolidayDate(uint32_t holidayId, int year)
+{
+ for (auto const& rule : HolidayRules)
+ {
+ if (rule.holidayId == holidayId)
+ {
+ std::tm const date = CalculateHolidayDate(rule, year);
+ return PackDate(date);
+ }
+ }
+ return 0; // Holiday not found
+}
+
+std::vector<uint32_t> HolidayDateCalculator::GetDarkmoonFaireDates(int locationOffset, int startYear, int numYears, int dayOffset)
+{
+ std::vector<uint32_t> dates;
+
+ // Darkmoon Faire is first Sunday of months where (month % 3) == locationOffset
+ // locationOffset 0: Mar, Jun, Sep, Dec - Elwynn (Alliance)
+ // locationOffset 1: Jan, Apr, Jul, Oct - Mulgore (Horde)
+ // locationOffset 2: Feb, May, Aug, Nov - Terokkar (Outland)
+
+ for (int year = startYear; year < startYear + numYears && year <= 2030; ++year)
+ {
+ for (int month = 1; month <= 12; ++month)
+ {
+ if (month % 3 == locationOffset)
+ {
+ // Calculate first Sunday of this month, then apply day offset
+ std::tm date = CalculateNthWeekday(year, month, Weekday::SUNDAY, 1);
+ if (dayOffset != 0)
+ {
+ date.tm_mday += dayOffset;
+ mktime(&date); // Normalize
+ }
+ dates.push_back(PackDate(date));
+ }
+ }
+ }
+
+ return dates;
+}
diff --git a/src/server/game/Events/HolidayDateCalculator.h b/src/server/game/Events/HolidayDateCalculator.h
new file mode 100644
index 0000000000..7ba3eae4f2
--- /dev/null
+++ b/src/server/game/Events/HolidayDateCalculator.h
@@ -0,0 +1,112 @@
+/*
+ * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef ACORE_HOLIDAY_DATE_CALCULATOR_H
+#define ACORE_HOLIDAY_DATE_CALCULATOR_H
+
+#include <cstdint>
+#include <ctime>
+#include <vector>
+
+enum class HolidayCalculationType
+{
+ FIXED_DATE, // Same month/day every year (e.g., Dec 25)
+ NTH_WEEKDAY, // Nth weekday of month (e.g., 4th Thursday of Nov)
+ EASTER_OFFSET, // Days relative to Easter Sunday
+ LUNAR_NEW_YEAR, // Chinese New Year (new moon between Jan 21 - Feb 20)
+ WEEKDAY_ON_OR_AFTER, // First weekday on or after a date (e.g., first Monday on or after Feb 3)
+ AUTUMN_EQUINOX, // Days relative to autumn equinox (offset in days)
+ WINTER_SOLSTICE, // Days relative to winter solstice (offset in days)
+ DARKMOON_FAIRE // First Sunday of months matching (month % 3 == locationOffset)
+};
+
+enum class Weekday
+{
+ SUNDAY = 0,
+ MONDAY,
+ TUESDAY,
+ WEDNESDAY,
+ THURSDAY,
+ FRIDAY,
+ SATURDAY
+};
+
+struct HolidayRule
+{
+ uint32_t holidayId;
+ HolidayCalculationType type;
+ int month; // 1-12
+ int day; // For FIXED_DATE: day of month. For NTH_WEEKDAY: which occurrence (1-5)
+ int weekday; // For NTH_WEEKDAY: 0=Sunday through 6=Saturday
+ int offset; // For EASTER_OFFSET: days after Easter (can be negative)
+};
+
+class HolidayDateCalculator
+{
+public:
+ // Calculate Easter Sunday for a given year (Computus algorithm)
+ static std::tm CalculateEasterSunday(int year);
+
+ // Calculate Nth weekday of a month (e.g., 4th Thursday of November)
+ static std::tm CalculateNthWeekday(int year, int month, Weekday weekday, int n);
+
+ // Calculate first weekday on or after a specific date (e.g., first Monday on or after Feb 3)
+ static std::tm CalculateWeekdayOnOrAfter(int year, int month, int day, Weekday weekday);
+
+ // Calculate Chinese New Year (Lunar New Year) using astronomical algorithm
+ // Based on Jean Meeus "Astronomical Algorithms" - finds new moon between Jan 21 - Feb 20
+ static std::tm CalculateLunarNewYear(int year);
+
+ // Calculate Autumn Equinox using astronomical algorithm
+ // Based on Jean Meeus "Astronomical Algorithms" Chapter 27
+ static std::tm CalculateAutumnEquinox(int year);
+
+ // Calculate Winter Solstice using astronomical algorithm
+ // Based on Jean Meeus "Astronomical Algorithms" Chapter 27
+ static std::tm CalculateWinterSolstice(int year);
+
+ // Calculate holiday start date for a given year
+ static std::tm CalculateHolidayDate(const HolidayRule& rule, int year);
+
+ // Convert std::tm to WoW's packed date format
+ static uint32_t PackDate(const std::tm& date);
+
+ // Convert WoW's packed date format to std::tm
+ static std::tm UnpackDate(uint32_t packed);
+
+ // Get all holiday rules
+ static const std::vector<HolidayRule>& GetHolidayRules();
+
+ // Calculate date for a specific holiday ID and year
+ static uint32_t GetPackedHolidayDate(uint32_t holidayId, int year);
+
+ // Calculate Darkmoon Faire dates for a location over a year range
+ // locationOffset: 0=Elwynn (months 3,6,9,12), 1=Mulgore (months 1,4,7,10), 2=Terokkar (months 2,5,8,11)
+ // dayOffset: days to offset from first Sunday (e.g., -2 for Friday start)
+ // Returns packed dates for all occurrences in the year range
+ static std::vector<uint32_t> GetDarkmoonFaireDates(int locationOffset, int startYear, int numYears, int dayOffset = 0);
+
+private:
+ // Julian Date conversions for lunar calculations
+ static double DateToJulianDay(int year, int month, double day);
+ static void JulianDayToDate(double jd, int& year, int& month, int& day);
+
+ // Calculate new moon Julian Date for lunation k (Meeus algorithm)
+ static double CalculateNewMoon(double k);
+};
+
+#endif // ACORE_HOLIDAY_DATE_CALCULATOR_H
diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp
index f22606ec27..9e9cf67270 100644
--- a/src/server/game/Globals/ObjectMgr.cpp
+++ b/src/server/game/Globals/ObjectMgr.cpp
@@ -342,8 +342,6 @@ ObjectMgr::~ObjectMgr()
for (CacheVendorItemContainer::iterator itr = _cacheVendorItemStore.begin(); itr != _cacheVendorItemStore.end(); ++itr)
itr->second.Clear();
- _cacheTrainerSpellStore.clear();
-
for (DungeonEncounterContainer::iterator itr = _dungeonEncounterStore.begin(); itr != _dungeonEncounterStore.end(); ++itr)
for (DungeonEncounterList::iterator encounterItr = itr->second.begin(); encounterItr != itr->second.end(); ++encounterItr)
delete *encounterItr;
@@ -523,19 +521,19 @@ void ObjectMgr::LoadCreatureTemplates()
{
uint32 oldMSTime = getMSTime();
-// 0 1 2 3 4 5 6 7 8
+// 0 1 2 3 4 5 6 7 8
QueryResult result = WorldDatabase.Query("SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, name, subname, IconName, "
// 9 10 11 12 13 14 15 16 17 18 19 20 21 22
"gossip_menu_id, minlevel, maxlevel, exp, faction, npcflag, speed_walk, speed_run, speed_swim, speed_flight, detection_range, scale, `rank`, dmgschool, "
-// 23 24 25 26 27 28 29 30 31 32 33 34
- "DamageModifier, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, dynamicflags, family, trainer_type, trainer_spell, "
-// 35 36 37 38 39 40 41
- "trainer_class, trainer_race, type, type_flags, lootid, pickpocketloot, skinloot, "
-// 42 43 44 45 46 47 48 49 50 51
+// 23 24 25 26 27 28 29 30 31 32
+ "DamageModifier, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, dynamicflags, family, "
+// 33 34 35 36 37
+ "type, type_flags, lootid, pickpocketloot, skinloot, "
+// 38 39 40 41 42 43 44 45 46 47
"PetSpellDataId, VehicleId, mingold, maxgold, AIName, MovementType, ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, "
-// 52 53 54 55 56 57 58 59 60 61 62 63
+// 48 49 50 51 52 53 54 55 56 57 58 59
"ctm.Chase, ctm.Random, ctm.InteractionPauseTimer, HoverHeight, HealthModifier, ManaModifier, ArmorModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, mechanic_immune_mask, "
-// 64 65 66
+// 60 61 62
"spell_school_immune_mask, flags_extra, ScriptName "
"FROM creature_template ct LEFT JOIN creature_template_movement ctm ON ct.entry = ctm.CreatureId ORDER BY entry DESC;");
@@ -635,15 +633,11 @@ void ObjectMgr::LoadCreatureTemplate(Field* fields, bool triggerHook)
creatureTemplate.unit_flags2 = fields[30].Get<uint32>();
creatureTemplate.dynamicflags = fields[31].Get<uint32>();
creatureTemplate.family = uint32(fields[32].Get<uint8>());
- creatureTemplate.trainer_type = uint32(fields[33].Get<uint8>());
- creatureTemplate.trainer_spell = fields[34].Get<uint32>();
- creatureTemplate.trainer_class = uint32(fields[35].Get<uint8>());
- creatureTemplate.trainer_race = uint32(fields[36].Get<uint8>());
- creatureTemplate.type = uint32(fields[37].Get<uint8>());
- creatureTemplate.type_flags = fields[38].Get<uint32>();
- creatureTemplate.lootid = fields[39].Get<uint32>();
- creatureTemplate.pickpocketLootId = fields[40].Get<uint32>();
- creatureTemplate.SkinLootId = fields[41].Get<uint32>();
+ creatureTemplate.type = uint32(fields[33].Get<uint8>());
+ creatureTemplate.type_flags = fields[34].Get<uint32>();
+ creatureTemplate.lootid = fields[35].Get<uint32>();
+ creatureTemplate.pickpocketLootId = fields[36].Get<uint32>();
+ creatureTemplate.SkinLootId = fields[37].Get<uint32>();
for (uint8 i = SPELL_SCHOOL_HOLY; i < MAX_SPELL_SCHOOL; ++i)
{
@@ -655,49 +649,49 @@ void ObjectMgr::LoadCreatureTemplate(Field* fields, bool triggerHook)
creatureTemplate.spells[i] = 0;
}
- creatureTemplate.PetSpellDataId = fields[42].Get<uint32>();
- creatureTemplate.VehicleId = fields[43].Get<uint32>();
- creatureTemplate.mingold = fields[44].Get<uint32>();
- creatureTemplate.maxgold = fields[45].Get<uint32>();
- creatureTemplate.AIName = fields[46].Get<std::string>(); // stopped here, fix it
- creatureTemplate.MovementType = uint32(fields[47].Get<uint8>());
- if (!fields[48].IsNull())
+ creatureTemplate.PetSpellDataId = fields[38].Get<uint32>();
+ creatureTemplate.VehicleId = fields[39].Get<uint32>();
+ creatureTemplate.mingold = fields[40].Get<uint32>();
+ creatureTemplate.maxgold = fields[41].Get<uint32>();
+ creatureTemplate.AIName = fields[42].Get<std::string>();
+ creatureTemplate.MovementType = uint32(fields[43].Get<uint8>());
+ if (!fields[44].IsNull())
{
- creatureTemplate.Movement.Ground = static_cast<CreatureGroundMovementType>(fields[48].Get<uint8>());
+ creatureTemplate.Movement.Ground = static_cast<CreatureGroundMovementType>(fields[44].Get<uint8>());
}
- creatureTemplate.Movement.Swim = fields[49].Get<bool>();
- if (!fields[50].IsNull())
+ creatureTemplate.Movement.Swim = fields[45].Get<bool>();
+ if (!fields[46].IsNull())
{
- creatureTemplate.Movement.Flight = static_cast<CreatureFlightMovementType>(fields[50].Get<uint8>());
+ creatureTemplate.Movement.Flight = static_cast<CreatureFlightMovementType>(fields[46].Get<uint8>());
}
- creatureTemplate.Movement.Rooted = fields[51].Get<bool>();
- if (!fields[52].IsNull())
+ creatureTemplate.Movement.Rooted = fields[47].Get<bool>();
+ if (!fields[48].IsNull())
{
- creatureTemplate.Movement.Chase = static_cast<CreatureChaseMovementType>(fields[52].Get<uint8>());
+ creatureTemplate.Movement.Chase = static_cast<CreatureChaseMovementType>(fields[48].Get<uint8>());
}
- if (!fields[53].IsNull())
+ if (!fields[49].IsNull())
{
- creatureTemplate.Movement.Random = static_cast<CreatureRandomMovementType>(fields[53].Get<uint8>());
+ creatureTemplate.Movement.Random = static_cast<CreatureRandomMovementType>(fields[49].Get<uint8>());
}
- if (!fields[54].IsNull())
+ if (!fields[50].IsNull())
{
- creatureTemplate.Movement.InteractionPauseTimer = fields[54].Get<uint32>();
+ creatureTemplate.Movement.InteractionPauseTimer = fields[50].Get<uint32>();
}
- creatureTemplate.HoverHeight = fields[55].Get<float>();
- creatureTemplate.ModHealth = fields[56].Get<float>();
- creatureTemplate.ModMana = fields[57].Get<float>();
- creatureTemplate.ModArmor = fields[58].Get<float>();
- creatureTemplate.ModExperience = fields[59].Get<float>();
- creatureTemplate.RacialLeader = fields[60].Get<bool>();
- creatureTemplate.movementId = fields[61].Get<uint32>();
- creatureTemplate.RegenHealth = fields[62].Get<bool>();
- creatureTemplate.MechanicImmuneMask = fields[63].Get<uint32>();
- creatureTemplate.SpellSchoolImmuneMask = fields[64].Get<uint8>();
- creatureTemplate.flags_extra = fields[65].Get<uint32>();
- creatureTemplate.ScriptID = GetScriptId(fields[66].Get<std::string>());
+ creatureTemplate.HoverHeight = fields[51].Get<float>();
+ creatureTemplate.ModHealth = fields[52].Get<float>();
+ creatureTemplate.ModMana = fields[53].Get<float>();
+ creatureTemplate.ModArmor = fields[54].Get<float>();
+ creatureTemplate.ModExperience = fields[55].Get<float>();
+ creatureTemplate.RacialLeader = fields[56].Get<bool>();
+ creatureTemplate.movementId = fields[57].Get<uint32>();
+ creatureTemplate.RegenHealth = fields[58].Get<bool>();
+ creatureTemplate.MechanicImmuneMask = fields[59].Get<uint32>();
+ creatureTemplate.SpellSchoolImmuneMask = fields[60].Get<uint8>();
+ creatureTemplate.flags_extra = fields[61].Get<uint32>();
+ creatureTemplate.ScriptID = GetScriptId(fields[62].Get<std::string>());
// useful if the creature template load is being triggered from outside this class
if (triggerHook)
@@ -1037,30 +1031,6 @@ void ObjectMgr::CheckCreatureTemplate(CreatureTemplate const* cInfo)
cInfo->Entry, cInfo->family, diff + 1, cInfo->DifficultyEntry[diff], difficultyInfo->family);
}
- if (cInfo->trainer_class != difficultyInfo->trainer_class)
- {
- LOG_ERROR("sql.sql", "Creature (Entry: {}) has different `trainer_class` in difficulty {} mode (Entry: {}).", cInfo->Entry, diff + 1, cInfo->DifficultyEntry[diff]);
- continue;
- }
-
- if (cInfo->trainer_race != difficultyInfo->trainer_race)
- {
- LOG_ERROR("sql.sql", "Creature (Entry: {}) has different `trainer_race` in difficulty {} mode (Entry: {}).", cInfo->Entry, diff + 1, cInfo->DifficultyEntry[diff]);
- continue;
- }
-
- if (cInfo->trainer_type != difficultyInfo->trainer_type)
- {
- LOG_ERROR("sql.sql", "Creature (Entry: {}) has different `trainer_type` in difficulty {} mode (Entry: {}).", cInfo->Entry, diff + 1, cInfo->DifficultyEntry[diff]);
- continue;
- }
-
- if (cInfo->trainer_spell != difficultyInfo->trainer_spell)
- {
- LOG_ERROR("sql.sql", "Creature (Entry: {}) has different `trainer_spell` in difficulty {} mode (Entry: {}).", cInfo->Entry, diff + 1, cInfo->DifficultyEntry[diff]);
- continue;
- }
-
if (cInfo->type != difficultyInfo->type)
{
LOG_ERROR("sql.sql", "Creature (Entry: {}, type {}) has different `type` in difficulty {} mode (Entry: {}, type {}).",
@@ -1157,9 +1127,6 @@ void ObjectMgr::CheckCreatureTemplate(CreatureTemplate const* cInfo)
if (cInfo->RangeAttackTime == 0)
const_cast<CreatureTemplate*>(cInfo)->RangeAttackTime = BASE_ATTACK_TIME;
- if ((cInfo->npcflag & UNIT_NPC_FLAG_TRAINER) && cInfo->trainer_type >= MAX_TRAINER_TYPE)
- LOG_ERROR("sql.sql", "Creature (Entry: {}) has wrong trainer type {}.", cInfo->Entry, cInfo->trainer_type);
-
if (cInfo->speed_walk == 0.0f)
{
LOG_ERROR("sql.sql", "Creature (Entry: {}) has wrong value ({}) in speed_walk, set to 1.", cInfo->Entry, cInfo->speed_walk);
@@ -3205,7 +3172,10 @@ void ObjectMgr::LoadItemTemplates()
{
case ITEM_MOD_SPELL_HEALING_DONE:
case ITEM_MOD_SPELL_DAMAGE_DONE:
- LOG_WARN("sql.sql", "Item (Entry: {}) has deprecated stat_type{} ({})", entry, j + 1, itemTemplate.ItemStat[j].ItemStatType);
+ // Skip warning for specific items: 13113 (Feathermoon Headdress - Blizzard oversight), 34967 (test item)
+ if (entry != 13113 && entry != 34967)
+ LOG_WARN("sql.sql", "Item (Entry: {}) has deprecated stat_type{} ({})", entry, j + 1, itemTemplate.ItemStat[j].ItemStatType);
+
break;
default:
break;
@@ -9310,130 +9280,158 @@ void ObjectMgr::LoadMailLevelRewards()
LOG_INFO("server.loading", " ");
}
-void ObjectMgr::AddSpellToTrainer(uint32 entry, uint32 spell, uint32 spellCost, uint32 reqSkill, uint32 reqSkillValue, uint32 reqLevel, uint32 reqSpell)
+void ObjectMgr::LoadTrainers()
{
- if (entry >= ACORE_TRAINER_START_REF)
- return;
+ uint32 oldMSTime = getMSTime();
- CreatureTemplate const* cInfo = GetCreatureTemplate(entry);
- if (!cInfo)
- {
- LOG_ERROR("sql.sql", "Table `npc_trainer` contains an entry for a non-existing creature template (Entry: {}), ignoring", entry);
- return;
- }
+ // For reload case
+ _trainers.clear();
- if (!(cInfo->npcflag & UNIT_NPC_FLAG_TRAINER))
+ std::unordered_map<int32, std::vector<Trainer::Spell>> spellsByTrainer;
+ if (QueryResult trainerSpellsResult = WorldDatabase.Query("SELECT TrainerId, SpellId, MoneyCost, ReqSkillLine, ReqSkillRank, ReqAbility1, ReqAbility2, ReqAbility3, ReqLevel FROM trainer_spell"))
{
- LOG_ERROR("sql.sql", "Table `npc_trainer` contains an entry for a creature template (Entry: {}) without trainer flag, ignoring", entry);
- return;
- }
+ do
+ {
+ Field* fields = trainerSpellsResult->Fetch();
- SpellInfo const* spellinfo = sSpellMgr->GetSpellInfo(spell);
- if (!spellinfo)
- {
- LOG_ERROR("sql.sql", "Table `npc_trainer` contains an entry (Entry: {}) for a non-existing spell (Spell: {}), ignoring", entry, spell);
- return;
- }
+ Trainer::Spell spell;
+ uint32 trainerId = fields[0].Get<uint32>();
+ spell.SpellId = fields[1].Get<uint32>();
+ spell.MoneyCost = fields[2].Get<uint32>();
+ spell.ReqSkillLine = fields[3].Get<uint32>();
+ spell.ReqSkillRank = fields[4].Get<uint32>();
+ spell.ReqAbility[0] = fields[5].Get<uint32>();
+ spell.ReqAbility[1] = fields[6].Get<uint32>();
+ spell.ReqAbility[2] = fields[7].Get<uint32>();
+ spell.ReqLevel = fields[8].Get<uint8>();
- if (!SpellMgr::ComputeIsSpellValid(spellinfo))
- {
- LOG_ERROR("sql.sql", "Table `npc_trainer` contains an entry (Entry: {}) for a broken spell (Spell: {}), ignoring", entry, spell);
- return;
- }
+ SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spell.SpellId);
+ if (!spellInfo)
+ {
+ LOG_ERROR("sql.sql", "Table `trainer_spell` references non-existing spell (SpellId: {}) for TrainerId {}, ignoring", spell.SpellId, trainerId);
+ continue;
+ }
- if (GetTalentSpellCost(spell))
- {
- LOG_ERROR("sql.sql", "Table `npc_trainer` contains an entry (Entry: {}) for a non-existing spell (Spell: {}) which is a talent, ignoring", entry, spell);
- return;
- }
+ if (GetTalentSpellCost(spell.SpellId))
+ {
+ LOG_ERROR("sql.sql", "Table `trainer_spell` references non-existing spell (SpellId: {}) which is a talent, for TrainerId {}, ignoring", spell.SpellId, trainerId);
+ continue;
+ }
- if (reqSpell && !sSpellMgr->GetSpellInfo(reqSpell))
- {
- LOG_ERROR("sql.sql", "Table `npc_trainer` contains an entry (Entry: {}) for a non-existing reqSpell (Spell: {}), ignoring", entry, reqSpell);
- return;
- }
+ if (spell.ReqSkillLine && !sSkillLineStore.LookupEntry(spell.ReqSkillLine))
+ {
+ LOG_ERROR("sql.sql", "Table `trainer_spell` references non-existing skill (ReqSkillLine: {}) for TrainerId {} and SpellId {}, ignoring",
+ spell.ReqSkillLine, spell.SpellId, trainerId);
+ continue;
+ }
- TrainerSpellData& data = _cacheTrainerSpellStore[entry];
+ bool allReqValid = true;
+ for (std::size_t i = 0; i < spell.ReqAbility.size(); ++i)
+ {
+ uint32 requiredSpell = spell.ReqAbility[i];
+ if (requiredSpell && !sSpellMgr->GetSpellInfo(requiredSpell))
+ {
+ LOG_ERROR("sql.sql", "Table `trainer_spell` references non-existing spell (ReqAbility {} : {}) for TrainerId {} and SpellId {}, ignoring",
+ i + 1, requiredSpell, trainerId, spell.SpellId);
+ allReqValid = false;
+ }
+ }
- TrainerSpell& trainerSpell = data.spellList[spell];
- trainerSpell.spell = spell;
- trainerSpell.spellCost = spellCost;
- trainerSpell.reqSkill = reqSkill;
- trainerSpell.reqSkillValue = reqSkillValue;
- trainerSpell.reqLevel = reqLevel;
- trainerSpell.reqSpell = reqSpell;
+ if (!allReqValid)
+ continue;
- if (!trainerSpell.reqLevel)
- trainerSpell.reqLevel = spellinfo->SpellLevel;
+ spellsByTrainer[trainerId].push_back(spell);
+ } while (trainerSpellsResult->NextRow());
+ }
- // calculate learned spell for profession case when stored cast-spell
- trainerSpell.learnedSpell[0] = spell;
- for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
+ if (QueryResult trainersResult = WorldDatabase.Query("SELECT Id, Type, Requirement, Greeting FROM trainer"))
{
- if (spellinfo->Effects[i].Effect != SPELL_EFFECT_LEARN_SPELL)
- continue;
- if (trainerSpell.learnedSpell[0] == spell)
- trainerSpell.learnedSpell[0] = 0;
- // player must be able to cast spell on himself
- if (spellinfo->Effects[i].TargetA.GetTarget() != 0 && spellinfo->Effects[i].TargetA.GetTarget() != TARGET_UNIT_TARGET_ALLY
- && spellinfo->Effects[i].TargetA.GetTarget() != TARGET_UNIT_TARGET_ANY && spellinfo->Effects[i].TargetA.GetTarget() != TARGET_UNIT_CASTER)
+ do
{
- LOG_ERROR("sql.sql", "Table `npc_trainer` has spell {} for trainer entry {} with learn effect which has incorrect target type, ignoring learn effect!", spell, entry);
- continue;
- }
+ Field* fields = trainersResult->Fetch();
- trainerSpell.learnedSpell[i] = spellinfo->Effects[i].TriggerSpell;
+ uint32 trainerId = fields[0].Get<uint32>();
+ Trainer::Type trainerType = Trainer::Type(fields[1].Get<uint8>());
+ uint32 requirement = fields[2].Get<uint32>();
+ std::string greeting = fields[3].Get<std::string>();
+ std::vector<Trainer::Spell> spells;
+ auto spellsItr = spellsByTrainer.find(trainerId);
+ if (spellsItr != spellsByTrainer.end())
+ {
+ spells = std::move(spellsItr->second);
+ spellsByTrainer.erase(spellsItr);
+ }
- if (trainerSpell.learnedSpell[i])
+ _trainers.emplace(std::piecewise_construct, std::forward_as_tuple(trainerId), std::forward_as_tuple(trainerId, trainerType, requirement, std::move(greeting), std::move(spells)));
+ } while (trainersResult->NextRow());
+ }
+
+ for (auto const& unusedSpells : spellsByTrainer)
+ {
+ for (Trainer::Spell const& unusedSpell : unusedSpells.second)
{
- SpellInfo const* learnedSpellInfo = sSpellMgr->GetSpellInfo(trainerSpell.learnedSpell[i]);
- if (learnedSpellInfo && learnedSpellInfo->IsProfession())
- data.trainerType = 2;
+ LOG_ERROR("sql.sql", "Table `trainer_spell` references non-existing trainer (TrainerId: {}) for SpellId {}, ignoring", unusedSpells.first, unusedSpell.SpellId);
}
}
- return;
+ if (QueryResult trainerLocalesResult = WorldDatabase.Query("SELECT Id, locale, Greeting_lang FROM trainer_locale"))
+ {
+ do
+ {
+ Field* fields = trainerLocalesResult->Fetch();
+ uint32 trainerId = fields[0].Get<uint32>();
+ std::string localeName = fields[1].Get<std::string>();
+
+ LocaleConstant locale = GetLocaleByName(localeName);
+ if (locale == LOCALE_enUS)
+ continue;
+
+ if (Trainer::Trainer* trainer = Acore::Containers::MapGetValuePtr(_trainers, trainerId))
+ trainer->AddGreetingLocale(locale, fields[2].Get<std::string>());
+ else
+ LOG_ERROR("sql.sql", "Table `trainer_locale` references non-existing trainer (TrainerId: {}) for locale %s, ignoring",
+ trainerId, localeName.c_str());
+ } while (trainerLocalesResult->NextRow());
+ }
+
+ LOG_INFO("server.loading", ">> Loaded {} Trainers in {} ms", _trainers.size(), GetMSTimeDiffToNow(oldMSTime));
}
-void ObjectMgr::LoadTrainerSpell()
+void ObjectMgr::LoadCreatureDefaultTrainers()
{
uint32 oldMSTime = getMSTime();
- // For reload case
- _cacheTrainerSpellStore.clear();
-
- QueryResult result = WorldDatabase.Query("SELECT b.ID, a.SpellID, a.MoneyCost, a.ReqSkillLine, a.ReqSkillRank, a.ReqLevel, a.ReqSpell FROM npc_trainer AS a "
- "INNER JOIN npc_trainer AS b ON a.ID = -(b.SpellID) "
- "UNION SELECT * FROM npc_trainer WHERE SpellID > 0");
+ _creatureDefaultTrainers.clear();
- if (!result)
+ if (QueryResult result = WorldDatabase.Query("SELECT CreatureId, TrainerId FROM creature_default_trainer"))
{
- LOG_WARN("server.loading", ">> Loaded 0 Trainers. DB table `npc_trainer` is empty!");
- LOG_INFO("server.loading", " ");
- return;
- }
+ do
+ {
+ Field* fields = result->Fetch();
+ uint32 creatureId = fields[0].Get<uint32>();
+ uint32 trainerId = fields[1].Get<uint32>();
- uint32 count = 0;
+ if (!GetCreatureTemplate(creatureId))
+ {
+ LOG_ERROR("sql.sql", "Table `creature_default_trainer` references non-existing creature template (CreatureId: %u), ignoring", creatureId);
+ continue;
+ }
- do
- {
- Field* fields = result->Fetch();
+ _creatureDefaultTrainers[creatureId] = trainerId;
- uint32 entry = fields[0].Get<uint32>();
- uint32 spell = fields[1].Get<uint32>();
- uint32 spellCost = fields[2].Get<uint32>();
- uint32 reqSkill = fields[3].Get<uint16>();
- uint32 reqSkillValue = fields[4].Get<uint16>();
- uint32 reqLevel = fields[5].Get<uint8>();
- uint32 reqSpell = fields[6].Get<uint32>();
+ } while (result->NextRow());
+ }
- AddSpellToTrainer(entry, spell, spellCost, reqSkill, reqSkillValue, reqLevel, reqSpell);
+ LOG_INFO("server.loading", ">> Loaded {} default trainers in {} ms", _creatureDefaultTrainers.size(), GetMSTimeDiffToNow(oldMSTime));
+}
- ++count;
- } while (result->NextRow());
+Trainer::Trainer* ObjectMgr::GetTrainer(uint32 creatureId)
+{
+ auto itr = _creatureDefaultTrainers.find(creatureId);
+ if (itr != _creatureDefaultTrainers.end())
+ return Acore::Containers::MapGetValuePtr(_trainers, itr->second);
- LOG_INFO("server.loading", ">> Loaded {} Trainers in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
- LOG_INFO("server.loading", " ");
+ return nullptr;
}
int ObjectMgr::LoadReferenceVendor(int32 vendor, int32 item, std::set<uint32>* skip_vendors)
diff --git a/src/server/game/Globals/ObjectMgr.h b/src/server/game/Globals/ObjectMgr.h
index 173447ce8c..36028c0e34 100644
--- a/src/server/game/Globals/ObjectMgr.h
+++ b/src/server/game/Globals/ObjectMgr.h
@@ -33,6 +33,7 @@
#include "ObjectDefines.h"
#include "QuestDef.h"
#include "TemporarySummon.h"
+#include "Trainer.h"
#include "VehicleDefines.h"
#include <functional>
#include <limits>
@@ -664,7 +665,6 @@ typedef std::unordered_map<uint32, QuestPOIVector> QuestPOIContainer;
typedef std::map<std::pair<uint32, uint8>, QuestGreeting> QuestGreetingContainer;
typedef std::unordered_map<uint32, VendorItemData> CacheVendorItemContainer;
-typedef std::unordered_map<uint32, TrainerSpellData> CacheTrainerSpellContainer;
typedef std::vector<uint32> CreatureCustomIDsContainer;
@@ -1106,8 +1106,8 @@ public:
void LoadGossipMenuItems();
void LoadVendors();
- void LoadTrainerSpell();
- void AddSpellToTrainer(uint32 entry, uint32 spell, uint32 spellCost, uint32 reqSkill, uint32 reqSkillValue, uint32 reqLevel, uint32 reqSpell);
+ void LoadTrainers();
+ void LoadCreatureDefaultTrainers();
std::string GeneratePetName(uint32 entry);
std::string GeneratePetNameLocale(uint32 entry, LocaleConstant locale);
@@ -1364,14 +1364,7 @@ public:
bool AddGameTele(GameTele& data);
bool DeleteGameTele(std::string_view name);
- [[nodiscard]] TrainerSpellData const* GetNpcTrainerSpells(uint32 entry) const
- {
- CacheTrainerSpellContainer::const_iterator iter = _cacheTrainerSpellStore.find(entry);
- if (iter == _cacheTrainerSpellStore.end())
- return nullptr;
-
- return &iter->second;
- }
+ Trainer::Trainer* GetTrainer(uint32 creatureId);
[[nodiscard]] VendorItemData const* GetNpcVendorItemList(uint32 entry) const
{
@@ -1617,7 +1610,8 @@ private:
PointOfInterestLocaleContainer _pointOfInterestLocaleStore;
CacheVendorItemContainer _cacheVendorItemStore;
- CacheTrainerSpellContainer _cacheTrainerSpellStore;
+ std::unordered_map<uint32, Trainer::Trainer> _trainers;
+ std::unordered_map<uint32, uint32> _creatureDefaultTrainers;
std::set<uint32> _difficultyEntries[MAX_DIFFICULTY - 1]; // already loaded difficulty 1 value in creatures, used in CheckCreatureTemplate
std::set<uint32> _hasDifficultyEntries[MAX_DIFFICULTY - 1]; // already loaded creatures with difficulty 1 values, used in CheckCreatureTemplate
diff --git a/src/server/game/Handlers/ItemHandler.cpp b/src/server/game/Handlers/ItemHandler.cpp
index aef8767541..9fc5643d0a 100644
--- a/src/server/game/Handlers/ItemHandler.cpp
+++ b/src/server/game/Handlers/ItemHandler.cpp
@@ -256,8 +256,8 @@ void WorldSession::HandleAutoEquipItemOpcode(WorldPackets::Item::AutoEquipItem&
}
// now do moves, remove...
- _player->RemoveItem(dstbag, dstslot, true, true);
- _player->RemoveItem(packet.SourceBag, packet.SourceSlot, true, true);
+ _player->RemoveItem(dstbag, dstslot, true);
+ _player->RemoveItem(packet.SourceBag, packet.SourceSlot, true);
// add to dest
_player->EquipItem(dest, pSrcItem, true);
diff --git a/src/server/game/Handlers/MiscHandler.cpp b/src/server/game/Handlers/MiscHandler.cpp
index fd5167cbe1..c6485d813c 100644
--- a/src/server/game/Handlers/MiscHandler.cpp
+++ b/src/server/game/Handlers/MiscHandler.cpp
@@ -656,7 +656,8 @@ void WorldSession::HandleReclaimCorpseOpcode(WorldPacket& recv_data)
if (time_t(corpse->GetGhostTime() + _player->GetCorpseReclaimDelay(corpse->GetType() == CORPSE_RESURRECTABLE_PVP)) > time_t(GameTime::GetGameTime().count()))
return;
- if (!corpse->IsWithinDistInMap(_player, CORPSE_RECLAIM_RADIUS, true))
+ // skip phase check
+ if (!corpse->IsInMap(_player) || !corpse->IsWithinDist(_player, CORPSE_RECLAIM_RADIUS, true))
return;
// resurrect
diff --git a/src/server/game/Handlers/MovementHandler.cpp b/src/server/game/Handlers/MovementHandler.cpp
index 8e7e6c1f35..22bd4a0126 100644
--- a/src/server/game/Handlers/MovementHandler.cpp
+++ b/src/server/game/Handlers/MovementHandler.cpp
@@ -15,6 +15,7 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include "AreaDefines.h"
#include "ArenaSpectator.h"
#include "Battleground.h"
#include "BattlegroundMgr.h"
@@ -299,6 +300,8 @@ void WorldSession::HandleMoveTeleportAck(WorldPacket& recvData)
plMover->UpdatePosition(dest, true);
+ plMover->SetFallInformation(GameTime::GetGameTime().count(), dest.GetPositionZ());
+
// xinef: teleport pets if they are not unsummoned
if (Pet* pet = plMover->GetPet())
{
@@ -415,33 +418,31 @@ void WorldSession::HandleMoverRelocation(MovementInfo& movementInfo, Unit* mover
if (mover->m_movementInfo.HasMovementFlag(MOVEMENTFLAG_ONTRANSPORT))
{
- // if we boarded a transport, add us to it
- if (Player* plrMover = mover->ToPlayer())
+ // if we boarded a transport, add us to it (generalized for both players and creatures)
+ if (!mover->GetTransport())
{
- if (!plrMover->GetTransport())
+ if (Transport* transport = mover->GetMap()->GetTransport(movementInfo.transport.guid))
{
- if (Transport* transport = plrMover->GetMap()->GetTransport(movementInfo.transport.guid))
- {
- plrMover->m_transport = transport;
- transport->AddPassenger(plrMover);
- }
+ mover->SetTransport(transport);
+ transport->AddPassenger(mover);
}
- else if (plrMover->GetTransport()->GetGUID() != movementInfo.transport.guid)
+ }
+ else if (mover->GetTransport()->GetGUID() != movementInfo.transport.guid)
+ {
+ // Switching transports
+ bool foundNewTransport = false;
+ mover->GetTransport()->RemovePassenger(mover);
+ if (Transport* transport = mover->GetMap()->GetTransport(movementInfo.transport.guid))
{
- bool foundNewTransport = false;
- plrMover->m_transport->RemovePassenger(plrMover);
- if (Transport* transport = plrMover->GetMap()->GetTransport(movementInfo.transport.guid))
- {
- foundNewTransport = true;
- plrMover->m_transport = transport;
- transport->AddPassenger(plrMover);
- }
+ foundNewTransport = true;
+ mover->SetTransport(transport);
+ transport->AddPassenger(mover);
+ }
- if (!foundNewTransport)
- {
- plrMover->m_transport = nullptr;
- movementInfo.transport.Reset();
- }
+ if (!foundNewTransport)
+ {
+ mover->SetTransport(nullptr);
+ movementInfo.transport.Reset();
}
}
@@ -449,23 +450,20 @@ void WorldSession::HandleMoverRelocation(MovementInfo& movementInfo, Unit* mover
{
GameObject* go = mover->GetMap()->GetGameObject(movementInfo.transport.guid);
if (!go || go->GetGoType() != GAMEOBJECT_TYPE_TRANSPORT)
- {
movementInfo.RemoveMovementFlag(MOVEMENTFLAG_ONTRANSPORT);
- }
}
}
- else if (mover->IsPlayer())
+ else
{
- if (Player* plrMover = mover->ToPlayer())
+ // if we were on a transport, leave (handles both players and creatures)
+ if (Transport* transport = mover->GetTransport())
{
- if (plrMover->GetTransport()) // if we were on a transport, leave
- {
- sScriptMgr->AnticheatSetUnderACKmount(plrMover); // just for safe
+ if (mover->IsPlayer())
+ sScriptMgr->AnticheatSetUnderACKmount(mover->ToPlayer()); // just for safe
- plrMover->m_transport->RemovePassenger(plrMover);
- plrMover->m_transport = nullptr;
- movementInfo.transport.Reset();
- }
+ transport->RemovePassenger(mover);
+ mover->SetTransport(nullptr);
+ movementInfo.transport.Reset();
}
}
@@ -493,6 +491,10 @@ void WorldSession::HandleMoverRelocation(MovementInfo& movementInfo, Unit* mover
{
if (plrMover->IsAlive())
{
+ // The Oculus under map case is handled by areatrigger (5001) and should not kill the player
+ if (plrMover->GetMapId() == MAP_THE_OCULUS)
+ return;
+
plrMover->SetPlayerFlag(PLAYER_FLAGS_IS_OUT_OF_BOUNDS);
plrMover->EnvironmentalDamage(DAMAGE_FALL_TO_VOID, GetPlayer()->GetMaxHealth());
// player can be alive if GM
diff --git a/src/server/game/Handlers/NPCHandler.cpp b/src/server/game/Handlers/NPCHandler.cpp
index 66b7d01af1..d89a925393 100644
--- a/src/server/game/Handlers/NPCHandler.cpp
+++ b/src/server/game/Handlers/NPCHandler.cpp
@@ -21,6 +21,7 @@
#include "DatabaseEnv.h"
#include "GameGraveyard.h"
#include "Language.h"
+#include "NPCPackets.h"
#include "ObjectMgr.h"
#include "Opcodes.h"
#include "Pet.h"
@@ -29,6 +30,7 @@
#include "ScriptMgr.h"
#include "SpellInfo.h"
#include "SpellMgr.h"
+#include "Trainer.h"
#include "WorldPacket.h"
#include "WorldSession.h"
#include <cmath>
@@ -76,150 +78,48 @@ void WorldSession::SendShowMailBox(ObjectGuid guid)
SendPacket(&data);
}
-void WorldSession::HandleTrainerListOpcode(WorldPacket& recvData)
+void WorldSession::HandleTrainerListOpcode(WorldPackets::NPC::Hello& packet)
{
- ObjectGuid guid;
-
- recvData >> guid;
- SendTrainerList(guid);
-}
-
-void WorldSession::SendTrainerList(ObjectGuid guid)
-{
- std::string str = GetAcoreString(LANG_NPC_TAINER_HELLO);
- SendTrainerList(guid, str);
-}
-
-void WorldSession::SendTrainerList(ObjectGuid guid, const std::string& strTitle)
-{
- LOG_DEBUG("network", "WORLD: SendTrainerList");
-
- Creature* unit = GetPlayer()->GetNPCIfCanInteractWith(guid, UNIT_NPC_FLAG_TRAINER);
- if (!unit)
+ Creature* npc = GetPlayer()->GetNPCIfCanInteractWith(packet.Unit, UNIT_NPC_FLAG_TRAINER);
+ if (!npc)
{
- LOG_DEBUG("network", "WORLD: SendTrainerList - Unit ({}) not found or you can not interact with him.", guid.ToString());
+ LOG_DEBUG("network", "WorldSession: SendTrainerList - {} not found or you can not interact with him.", packet.Unit.ToString().c_str());
return;
}
+ SendTrainerList(npc);
+}
+
+void WorldSession::SendTrainerList(Creature* npc)
+{
// remove fake death
if (GetPlayer()->HasUnitState(UNIT_STATE_DIED))
GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH);
- CreatureTemplate const* ci = unit->GetCreatureTemplate();
-
- if (!ci)
+ Trainer::Trainer const* trainer = sObjectMgr->GetTrainer(npc->GetEntry());
+ if (!trainer)
{
- LOG_DEBUG("network", "WORLD: SendTrainerList - ({}) NO CREATUREINFO!", guid.ToString());
+ LOG_DEBUG("network", "WorldSession: SendTrainerList - trainer spells not found for {}", npc->GetGUID().ToString().c_str());
return;
}
- TrainerSpellData const* trainer_spells = unit->GetTrainerSpells();
- if (!trainer_spells)
+ if (!trainer->IsTrainerValidForPlayer(_player))
{
- LOG_DEBUG("network", "WORLD: SendTrainerList - Training spells not found for creature ({})", guid.ToString());
+ LOG_DEBUG("network", "WorldSession: SendTrainerList - trainer {} not valid for player {}", npc->GetGUID().ToString().c_str(), GetPlayerInfo().c_str());
return;
}
- WorldPacket data(SMSG_TRAINER_LIST, 8 + 4 + 4 + trainer_spells->spellList.size() * 38 + strTitle.size() + 1);
- data << guid;
- data << uint32(trainer_spells->trainerType);
-
- std::size_t count_pos = data.wpos();
- data << uint32(trainer_spells->spellList.size());
-
- // reputation discount
- float fDiscountMod = _player->GetReputationPriceDiscount(unit);
- bool can_learn_primary_prof = GetPlayer()->GetFreePrimaryProfessionPoints() > 0;
-
- uint32 count = 0;
- for (TrainerSpellMap::const_iterator itr = trainer_spells->spellList.begin(); itr != trainer_spells->spellList.end(); ++itr)
- {
- TrainerSpell const* tSpell = &itr->second;
-
- bool valid = true;
- bool primary_prof_first_rank = false;
- for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
- {
- if (!tSpell->learnedSpell[i])
- continue;
- if (!_player->IsSpellFitByClassAndRace(tSpell->learnedSpell[i]))
- {
- valid = false;
- break;
- }
- SpellInfo const* learnedSpellInfo = sSpellMgr->GetSpellInfo(tSpell->learnedSpell[i]);
- if (learnedSpellInfo && learnedSpellInfo->IsPrimaryProfessionFirstRank())
- primary_prof_first_rank = true;
- }
-
- if (!valid)
- continue;
-
- if (tSpell->reqSpell && !_player->HasSpell(tSpell->reqSpell))
- {
- continue;
- }
-
- TrainerSpellState state = _player->GetTrainerSpellState(tSpell);
-
- data << uint32(tSpell->spell); // learned spell (or cast-spell in profession case)
- data << uint8(state == TRAINER_SPELL_GREEN_DISABLED ? TRAINER_SPELL_GREEN : state);
- data << uint32(std::floor(tSpell->spellCost * fDiscountMod));
-
- data << uint32(primary_prof_first_rank && can_learn_primary_prof ? 1 : 0);
- // primary prof. learn confirmation dialog
- data << uint32(primary_prof_first_rank ? 1 : 0); // must be equal prev. field to have learn button in enabled state
- data << uint8(tSpell->reqLevel);
- data << uint32(tSpell->reqSkill);
- data << uint32(tSpell->reqSkillValue);
- //prev + req or req + 0
- uint8 maxReq = 0;
- for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
- {
- if (!tSpell->learnedSpell[i])
- continue;
- if (uint32 prevSpellId = sSpellMgr->GetPrevSpellInChain(tSpell->learnedSpell[i]))
- {
- data << uint32(prevSpellId);
- ++maxReq;
- }
- if (maxReq == 3)
- break;
- SpellsRequiringSpellMapBounds spellsRequired = sSpellMgr->GetSpellsRequiredForSpellBounds(tSpell->learnedSpell[i]);
- for (SpellsRequiringSpellMap::const_iterator itr2 = spellsRequired.first; itr2 != spellsRequired.second && maxReq < 3; ++itr2)
- {
- data << uint32(itr2->second);
- ++maxReq;
- }
- if (maxReq == 3)
- break;
- }
- while (maxReq < 3)
- {
- data << uint32(0);
- ++maxReq;
- }
-
- ++count;
- }
-
- data << strTitle;
-
- data.put<uint32>(count_pos, count);
- SendPacket(&data);
+ trainer->SendSpells(npc, _player, GetSessionDbLocaleIndex());
}
-void WorldSession::HandleTrainerBuySpellOpcode(WorldPacket& recvData)
+void WorldSession::HandleTrainerBuySpellOpcode(WorldPackets::NPC::TrainerBuySpell& packet)
{
- ObjectGuid guid;
- uint32 spellId = 0;
-
- recvData >> guid >> spellId;
+ LOG_DEBUG("network", "WORLD: Received CMSG_TRAINER_BUY_SPELL {}, learn spell id is: {}", packet.TrainerGUID.ToString().c_str(), packet.SpellID);
- Creature* unit = GetPlayer()->GetNPCIfCanInteractWith(guid, UNIT_NPC_FLAG_TRAINER);
- if (!unit)
+ Creature* npc = GetPlayer()->GetNPCIfCanInteractWith(packet.TrainerGUID, UNIT_NPC_FLAG_TRAINER);
+ if (!npc)
{
- LOG_DEBUG("network", "WORLD: HandleTrainerBuySpellOpcode - Unit ({}) not found or you can not interact with him.", guid.ToString());
+ LOG_DEBUG("network", "WORLD: HandleTrainerBuySpellOpcode - {} not found or you can not interact with him.", packet.TrainerGUID.ToString().c_str());
return;
}
@@ -227,47 +127,11 @@ void WorldSession::HandleTrainerBuySpellOpcode(WorldPacket& recvData)
if (GetPlayer()->HasUnitState(UNIT_STATE_DIED))
GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH);
- // check present spell in trainer spell list
- TrainerSpellData const* trainer_spells = unit->GetTrainerSpells();
- if (!trainer_spells)
- return;
-
- // not found, cheat?
- TrainerSpell const* trainer_spell = trainer_spells->Find(spellId);
- if (!trainer_spell)
- return;
-
- if (trainer_spell->reqSpell && !_player->HasSpell(trainer_spell->reqSpell))
- {
- return;
- }
-
- // can't be learn, cheat? Or double learn with lags...
- if (_player->GetTrainerSpellState(trainer_spell) != TRAINER_SPELL_GREEN)
- return;
-
- // apply reputation discount
- uint32 nSpellCost = uint32(std::floor(trainer_spell->spellCost * _player->GetReputationPriceDiscount(unit)));
-
- // check money requirement
- if (!_player->HasEnoughMoney(nSpellCost))
+ Trainer::Trainer* trainer = sObjectMgr->GetTrainer(npc->GetEntry());
+ if (!trainer)
return;
- _player->ModifyMoney(-int32(nSpellCost));
-
- unit->SendPlaySpellVisual(179); // 53 SpellCastDirected
- unit->SendPlaySpellImpact(_player->GetGUID(), 362); // 113 EmoteSalute
-
- // learn explicitly or cast explicitly
- if (trainer_spell->IsCastable())
- _player->CastSpell(_player, trainer_spell->spell, true);
- else
- _player->learnSpell(spellId);
-
- WorldPacket data(SMSG_TRAINER_BUY_SUCCEEDED, 12);
- data << guid;
- data << uint32(spellId); // should be same as in packet from client
- SendPacket(&data);
+ trainer->TeachSpell(npc, _player, packet.SpellID);
}
void WorldSession::HandleGossipHelloOpcode(WorldPacket& recvData)
diff --git a/src/server/game/Handlers/PetHandler.cpp b/src/server/game/Handlers/PetHandler.cpp
index d445be9ed7..3df8d365a2 100644
--- a/src/server/game/Handlers/PetHandler.cpp
+++ b/src/server/game/Handlers/PetHandler.cpp
@@ -231,19 +231,20 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe
if (!creaturePet->CanCreatureAttack(TargetUnit))
return;
- // Not let attack through obstructions
- bool checkLos = !sDisableMgr->IsPathfindingEnabled(pet->GetMap()) ||
- (TargetUnit->IsCreature() && (TargetUnit->ToCreature()->isWorldBoss() || TargetUnit->ToCreature()->IsDungeonBoss()));
+ // Don't bother if pet is already attacking target
+ if (pet->GetVictim() == TargetUnit && pet->GetCharmInfo()->IsCommandAttack())
+ return;
- if (checkLos && !pet->IsWithinLOSInMap(TargetUnit))
- {
- WorldPacket data(SMSG_CAST_FAILED, 1 + 4 + 1);
- data << uint8(0);
- data << uint32(7389);
- data << uint8(SPELL_FAILED_LINE_OF_SIGHT);
- SendPacket(&data);
+ // Check line of sight either from pet or owner depending if pet is charmed
+ Unit* seer = pet;
+ if (Unit* owner = pet->GetOwner())
+ if (owner->IsPlayer() && owner->ToPlayer()->GetCharm() != pet && owner->ToPlayer()->GetVehicleBase() != pet)
+ if (sDisableMgr->IsPathfindingEnabled(pet->GetMap()))
+ seer = owner;
+
+ // Fail on LoS
+ if (seer && !seer->IsWithinLOSInMap(TargetUnit, VMAP::ModelIgnoreFlags::M2))
return;
- }
pet->ClearUnitState(UNIT_STATE_FOLLOW);
// This is true if pet has no target or has target but targets differs.
@@ -428,7 +429,7 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe
// This is true if pet has no target or has target but targets differs.
if (pet->GetVictim() != unit_target)
{
- if (pet->ToCreature()->IsAIEnabled)
+ if (pet->ToCreature()->IsAIEnabled && pet->IsWithinLOSInMap(unit_target, VMAP::ModelIgnoreFlags::M2))
pet->ToCreature()->AI()->AttackStart(unit_target);
}
}
@@ -510,7 +511,8 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe
charmInfo->SetIsCommandFollow(false);
charmInfo->SetIsReturning(false);
- pet->ToCreature()->AI()->AttackStart(TargetUnit);
+ if (pet->IsWithinLOSInMap(TargetUnit, VMAP::ModelIgnoreFlags::M2))
+ pet->ToCreature()->AI()->AttackStart(TargetUnit);
if (pet->IsPet() && pet->ToPet()->getPetType() == SUMMON_PET && pet != TargetUnit && roll_chance_i(10))
pet->SendPetActionSound(PET_ACTION_SPECIAL_SPELL);
diff --git a/src/server/game/Handlers/SkillHandler.cpp b/src/server/game/Handlers/SkillHandler.cpp
index 0cc383454b..29a6cbdcbb 100644
--- a/src/server/game/Handlers/SkillHandler.cpp
+++ b/src/server/game/Handlers/SkillHandler.cpp
@@ -68,7 +68,7 @@ void WorldSession::HandleTalentWipeConfirmOpcode(WorldPacket& recvData)
return;
}
- if (!unit->isCanTrainingAndResetTalentsOf(_player))
+ if (!unit->CanResetTalents(_player))
return;
// remove fake death
diff --git a/src/server/game/Handlers/VehicleHandler.cpp b/src/server/game/Handlers/VehicleHandler.cpp
index 28b5a83a3c..4df63ad67f 100644
--- a/src/server/game/Handlers/VehicleHandler.cpp
+++ b/src/server/game/Handlers/VehicleHandler.cpp
@@ -50,7 +50,10 @@ void WorldSession::HandleDismissControlledVehicle(WorldPacket& recvData)
mi.guid = guid;
ReadMovementInfo(recvData, &mi);
- _player->m_mover->m_movementInfo = mi;
+ if (_player->m_mover->IsRooted()) // for some reason client sends it without it even if rooted
+ mi.AddMovementFlag(MOVEMENTFLAG_ROOT);
+
+ ProcessMovementInfo(mi, _player->m_mover, _player->m_mover->ToPlayer(), recvData);
_player->ExitVehicle();
}
diff --git a/src/server/game/Maps/AreaDefines.h b/src/server/game/Maps/AreaDefines.h
index 1730cd09d4..be844f3c98 100644
--- a/src/server/game/Maps/AreaDefines.h
+++ b/src/server/game/Maps/AreaDefines.h
@@ -96,6 +96,7 @@ enum AreaTableIDs : uint32
AREA_EVERSONG_WOODS = 3430,
AREA_GHOSTLANDS = 3433,
AREA_HELLFIRE_PENINSULA = 3483,
+ AREA_SILVERMOON_CITY = 3487,
AREA_NAGRAND = 3518,
AREA_TEROKKAR_FOREST = 3519,
AREA_SHADOWMOON_VALLEY = 3520,
diff --git a/src/server/game/Miscellaneous/Language.h b/src/server/game/Miscellaneous/Language.h
index 66db5fd998..7f972a0a9c 100644
--- a/src/server/game/Miscellaneous/Language.h
+++ b/src/server/game/Miscellaneous/Language.h
@@ -671,6 +671,8 @@ enum AcoreStrings
LANG_REWARDED = 602,
+ LANG_NPC_DO_ACTION = 603,
+
// 603-704 - free
LANG_WAIT_BEFORE_SPEAKING = 705,
@@ -1157,7 +1159,10 @@ enum AcoreStrings
LANG_CMD_NO_DOOR_FOUND = 5086,
LANG_CMD_DOOR_OPENED = 5087,
- // Room for more strings 5088-9999
+ LANG_CMD_QUEST_STATUS = 5088,
+ LANG_CMD_QUEST_UNAVAILABLE = 5089,
+
+ // Room for more strings 5090-9999
// Level requirement notifications
LANG_SAY_REQ = 6604,
diff --git a/src/server/game/Movement/MotionMaster.cpp b/src/server/game/Movement/MotionMaster.cpp
index bf4df28aad..8e4f91c16c 100644
--- a/src/server/game/Movement/MotionMaster.cpp
+++ b/src/server/game/Movement/MotionMaster.cpp
@@ -471,7 +471,7 @@ void MotionMaster::MoveFollow(Unit* target, float dist, float angle, MovementSlo
*
* For transition movement between the ground and the air, use MoveLand or MoveTakeoff instead.
*/
-void MotionMaster::MovePoint(uint32 id, float x, float y, float z, ForcedMovement forcedMovement, float speed, float orientation, bool generatePath, bool forceDestination, MovementSlot slot)
+void MotionMaster::MovePoint(uint32 id, float x, float y, float z, ForcedMovement forcedMovement, float speed, float orientation, bool generatePath, bool forceDestination, MovementSlot slot, std::optional<AnimTier> animTier)
{
if (_owner->HasUnitFlag(UNIT_FLAG_DISABLE_MOVE))
return;
@@ -479,12 +479,12 @@ void MotionMaster::MovePoint(uint32 id, float x, float y, float z, ForcedMovemen
if (_owner->IsPlayer())
{
LOG_DEBUG("movement.motionmaster", "Player ({}) targeted point (Id: {} X: {} Y: {} Z: {})", _owner->GetGUID().ToString(), id, x, y, z);
- Mutate(new PointMovementGenerator<Player>(id, x, y, z, forcedMovement, speed, orientation, nullptr, generatePath, forceDestination), slot);
+ Mutate(new PointMovementGenerator<Player>(id, x, y, z, forcedMovement, speed, orientation, nullptr, generatePath, forceDestination, animTier), slot);
}
else
{
LOG_DEBUG("movement.motionmaster", "Creature ({}) targeted point (ID: {} X: {} Y: {} Z: {})", _owner->GetGUID().ToString(), id, x, y, z);
- Mutate(new PointMovementGenerator<Creature>(id, x, y, z, forcedMovement, speed, orientation, nullptr, generatePath, forceDestination), slot);
+ Mutate(new PointMovementGenerator<Creature>(id, x, y, z, forcedMovement, speed, orientation, nullptr, generatePath, forceDestination, animTier), slot);
}
}
@@ -556,7 +556,7 @@ void MotionMaster::MoveLand(uint32 id, Position const& pos, float speed /* = 0.0
init.SetVelocity(speed);
}
- init.SetAnimation(Movement::ToGround);
+ init.SetAnimation(AnimTier::Ground);
Mutate(new EffectMovementGenerator(init, id), MOTION_SLOT_ACTIVE);
}
@@ -590,7 +590,7 @@ void MotionMaster::MoveTakeoff(uint32 id, Position const& pos, float speed /* =
init.SetVelocity(speed);
if (!skipAnimation)
- init.SetAnimation(Movement::ToFly);
+ init.SetAnimation(AnimTier::Hover);
Mutate(new EffectMovementGenerator(init, id), MOTION_SLOT_ACTIVE);
}
@@ -727,12 +727,12 @@ void MotionMaster::MoveCharge(float x, float y, float z, float speed, uint32 id,
if (_owner->IsPlayer())
{
LOG_DEBUG("movement.motionmaster", "Player ({}) charge point (X: {} Y: {} Z: {})", _owner->GetGUID().ToString(), x, y, z);
- Mutate(new PointMovementGenerator<Player>(id, x, y, z, FORCED_MOVEMENT_NONE, speed, orientation, path, generatePath, generatePath, targetGUID), MOTION_SLOT_CONTROLLED);
+ Mutate(new PointMovementGenerator<Player>(id, x, y, z, FORCED_MOVEMENT_NONE, speed, orientation, path, generatePath, generatePath, std::nullopt, targetGUID), MOTION_SLOT_CONTROLLED);
}
else
{
LOG_DEBUG("movement.motionmaster", "Creature ({}) charge point (X: {} Y: {} Z: {})", _owner->GetGUID().ToString(), x, y, z);
- Mutate(new PointMovementGenerator<Creature>(id, x, y, z, FORCED_MOVEMENT_NONE, speed, orientation, path, generatePath, generatePath, targetGUID), MOTION_SLOT_CONTROLLED);
+ Mutate(new PointMovementGenerator<Creature>(id, x, y, z, FORCED_MOVEMENT_NONE, speed, orientation, path, generatePath, generatePath, std::nullopt, targetGUID), MOTION_SLOT_CONTROLLED);
}
}
diff --git a/src/server/game/Movement/MotionMaster.h b/src/server/game/Movement/MotionMaster.h
index 637e43a91b..4ac140260f 100644
--- a/src/server/game/Movement/MotionMaster.h
+++ b/src/server/game/Movement/MotionMaster.h
@@ -95,6 +95,16 @@ enum class PathSource
SMART_WAYPOINT_MGR = 1,
};
+enum class AnimTier : uint8
+{
+ Ground = 0,
+ Swim = 1,
+ Hover = 2,
+ Fly = 3,
+ Submerged = 4,
+ Max
+};
+
struct ChaseRange
{
ChaseRange(float range);
@@ -225,9 +235,9 @@ public:
void MoveForwards(Unit* target, float dist);
void MoveConfused();
void MoveFleeing(Unit* enemy, uint32 time = 0);
- void MovePoint(uint32 id, const Position& pos, ForcedMovement forcedMovement = FORCED_MOVEMENT_NONE, float speed = 0.f, bool generatePath = true, bool forceDestination = true)
- { MovePoint(id, pos.m_positionX, pos.m_positionY, pos.m_positionZ, forcedMovement, speed, pos.GetOrientation(), generatePath, forceDestination, MOTION_SLOT_ACTIVE); }
- void MovePoint(uint32 id, float x, float y, float z, ForcedMovement forcedMovement = FORCED_MOVEMENT_NONE, float speed = 0.f, float orientation = 0.0f, bool generatePath = true, bool forceDestination = true, MovementSlot slot = MOTION_SLOT_ACTIVE);
+ void MovePoint(uint32 id, const Position& pos, ForcedMovement forcedMovement = FORCED_MOVEMENT_NONE, float speed = 0.f, bool generatePath = true, bool forceDestination = true, std::optional<AnimTier> animTier = std::nullopt)
+ { MovePoint(id, pos.m_positionX, pos.m_positionY, pos.m_positionZ, forcedMovement, speed, pos.GetOrientation(), generatePath, forceDestination, MOTION_SLOT_ACTIVE, animTier); }
+ void MovePoint(uint32 id, float x, float y, float z, ForcedMovement forcedMovement = FORCED_MOVEMENT_NONE, float speed = 0.f, float orientation = 0.0f, bool generatePath = true, bool forceDestination = true, MovementSlot slot = MOTION_SLOT_ACTIVE, std::optional<AnimTier> animTier = std::nullopt);
void MoveSplinePath(Movement::PointsArray* path, ForcedMovement forcedMovement = FORCED_MOVEMENT_NONE);
void MovePath(uint32 path_id, ForcedMovement forcedMovement = FORCED_MOVEMENT_NONE, PathSource pathSource = PathSource::WAYPOINT_MGR);
diff --git a/src/server/game/Movement/MovementGenerators/PointMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/PointMovementGenerator.cpp
index 53425ce741..66ba79be59 100644
--- a/src/server/game/Movement/MovementGenerators/PointMovementGenerator.cpp
+++ b/src/server/game/Movement/MovementGenerators/PointMovementGenerator.cpp
@@ -94,6 +94,9 @@ void PointMovementGenerator<T>::DoInitialize(T* unit)
init.SetFacing(i_orientation);
}
+ if (_animTier)
+ init.SetAnimation(*_animTier);
+
init.Launch();
}
@@ -152,6 +155,9 @@ bool PointMovementGenerator<T>::DoUpdate(T* unit, uint32 /*diff*/)
else if (_forcedMovement == FORCED_MOVEMENT_RUN)
init.SetWalk(false);
+ if (_animTier)
+ init.SetAnimation(*_animTier);
+
if (i_orientation > 0.0f)
{
init.SetFacing(i_orientation);
@@ -210,9 +216,14 @@ template <> void PointMovementGenerator<Creature>::MovementInform(Creature* unit
if (Unit* summoner = unit->GetCharmerOrOwner())
{
if (UnitAI* AI = summoner->GetAI())
- {
AI->SummonMovementInform(unit, POINT_MOTION_TYPE, id);
- }
+ }
+ else
+ {
+ if (TempSummon* tempSummon = unit->ToTempSummon())
+ if (Unit* summoner = tempSummon->GetSummonerUnit())
+ if (UnitAI* AI = summoner->GetAI())
+ AI->SummonMovementInform(unit, POINT_MOTION_TYPE, id);
}
}
diff --git a/src/server/game/Movement/MovementGenerators/PointMovementGenerator.h b/src/server/game/Movement/MovementGenerators/PointMovementGenerator.h
index 6dc16eb4eb..b4b782bfd1 100644
--- a/src/server/game/Movement/MovementGenerators/PointMovementGenerator.h
+++ b/src/server/game/Movement/MovementGenerators/PointMovementGenerator.h
@@ -21,15 +21,16 @@
#include "Creature.h"
#include "MovementGenerator.h"
#include "MoveSplineInit.h"
+#include <optional>
template<class T>
class PointMovementGenerator : public MovementGeneratorMedium< T, PointMovementGenerator<T> >
{
public:
PointMovementGenerator(uint32 _id, float _x, float _y, float _z, ForcedMovement forcedMovement, float _speed = 0.0f, float orientation = 0.0f, const Movement::PointsArray* _path = nullptr,
- bool generatePath = false, bool forceDestination = false, ObjectGuid chargeTargetGUID = ObjectGuid::Empty, bool reverseOrientation = false, ObjectGuid facingTargetGuid = ObjectGuid())
+ bool generatePath = false, bool forceDestination = false, std::optional<AnimTier> animTier = std::nullopt, ObjectGuid chargeTargetGUID = ObjectGuid::Empty, bool reverseOrientation = false, ObjectGuid facingTargetGuid = ObjectGuid())
: id(_id), i_x(_x), i_y(_y), i_z(_z), speed(_speed), i_orientation(orientation), _generatePath(generatePath), _forceDestination(forceDestination), _reverseOrientation(reverseOrientation),
- _chargeTargetGUID(chargeTargetGUID), _forcedMovement(forcedMovement), _facingTargetGuid(facingTargetGuid)
+ _chargeTargetGUID(chargeTargetGUID), _forcedMovement(forcedMovement), _facingTargetGuid(facingTargetGuid), _animTier(animTier)
{
if (_path)
m_precomputedPath = *_path;
@@ -60,6 +61,7 @@ private:
ObjectGuid _chargeTargetGUID;
ForcedMovement _forcedMovement;
ObjectGuid _facingTargetGuid;
+ std::optional<AnimTier> _animTier;
};
class AssistanceMovementGenerator : public PointMovementGenerator<Creature>
diff --git a/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp
index 47d72798e3..7c13fb982e 100644
--- a/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp
+++ b/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp
@@ -204,10 +204,10 @@ bool WaypointMovementGenerator<Creature>::StartMove(Creature* creature)
switch (node.move_type)
{
case WAYPOINT_MOVE_TYPE_LAND:
- init.SetAnimation(Movement::ToGround);
+ init.SetAnimation(AnimTier::Ground);
break;
case WAYPOINT_MOVE_TYPE_TAKEOFF:
- init.SetAnimation(Movement::ToFly);
+ init.SetAnimation(AnimTier::Hover);
break;
case WAYPOINT_MOVE_TYPE_RUN:
init.SetWalk(false);
@@ -276,22 +276,14 @@ void WaypointMovementGenerator<Creature>::MovementInform(Creature* creature)
if (Unit* owner = creature->GetCharmerOrOwner())
{
if (UnitAI* AI = owner->GetAI())
- {
AI->SummonMovementInform(creature, WAYPOINT_MOTION_TYPE, i_currentNode);
- }
}
else
{
if (TempSummon* tempSummon = creature->ToTempSummon())
- {
if (Unit* owner = tempSummon->GetSummonerUnit())
- {
if (UnitAI* AI = owner->GetAI())
- {
AI->SummonMovementInform(creature, WAYPOINT_MOTION_TYPE, i_currentNode);
- }
- }
- }
}
}
diff --git a/src/server/game/Movement/Spline/MoveSplineInit.h b/src/server/game/Movement/Spline/MoveSplineInit.h
index 5be1ebe26b..e0761129c9 100644
--- a/src/server/game/Movement/Spline/MoveSplineInit.h
+++ b/src/server/game/Movement/Spline/MoveSplineInit.h
@@ -29,14 +29,6 @@ namespace Movement
// xinef: moved declaration here so it can be accessed out of MoveSplineInit.cpp
UnitMoveType SelectSpeedType(uint32 moveFlags);
- enum AnimType
- {
- ToGround = 0, // 460 = ToGround, index of AnimationData.dbc
- FlyToFly = 1, // 461 = FlyToFly?
- ToFly = 2, // 458 = ToFly
- FlyToGround = 3 // 463 = FlyToGround
- };
-
// Transforms coordinates from global to transport offsets
class TransportPathTransform
{
@@ -89,7 +81,7 @@ namespace Movement
/* Plays animation after movement done
* can't be combined with parabolic movement
*/
- void SetAnimation(AnimType anim);
+ void SetAnimation(AnimTier anim);
/* Adds final facing animation
* sets unit's facing to specified point/angle after all path done
@@ -191,7 +183,7 @@ namespace Movement
args.flags.EnableParabolic();
}
- inline void MoveSplineInit::SetAnimation(AnimType anim)
+ inline void MoveSplineInit::SetAnimation(AnimTier anim)
{
args.time_perc = 0.f;
args.flags.EnableAnimation((uint8)anim);
diff --git a/src/server/game/OutdoorPvP/OutdoorPvP.cpp b/src/server/game/OutdoorPvP/OutdoorPvP.cpp
index 9ca84858d7..88e210cfc6 100644
--- a/src/server/game/OutdoorPvP/OutdoorPvP.cpp
+++ b/src/server/game/OutdoorPvP/OutdoorPvP.cpp
@@ -25,6 +25,7 @@
#include "ObjectMgr.h"
#include "OutdoorPvPMgr.h"
#include "WorldPacket.h"
+#include "World.h"
OPvPCapturePoint::OPvPCapturePoint(OutdoorPvP* pvp) :
_pvp(pvp)
@@ -349,12 +350,12 @@ bool OPvPCapturePoint::Update(uint32 diff)
}
// get the difference of numbers
- float factDiff = ((float)_activePlayers[0].size() - (float)_activePlayers[1].size()) * float(diff) / OUTDOORPVP_OBJECTIVE_UPDATE_INTERVAL;
+ float factDiff = (((float)_activePlayers[0].size() - (float)_activePlayers[1].size()) * float(diff) / OUTDOORPVP_OBJECTIVE_UPDATE_INTERVAL) * sWorld->getFloatConfig(CONFIG_OUTDOOR_PVP_CAPTURE_RATE);
if (factDiff == 0.f)
return false;
TeamId ChallengerId = TEAM_NEUTRAL;
- float maxDiff = _maxSpeed * float(diff);
+ float maxDiff = (_maxSpeed * float(diff)) * sWorld->getFloatConfig(CONFIG_OUTDOOR_PVP_CAPTURE_RATE);
if (factDiff < 0.f)
{
diff --git a/src/server/game/Reputation/ReputationMgr.cpp b/src/server/game/Reputation/ReputationMgr.cpp
index c9061158cd..a02c3b604a 100644
--- a/src/server/game/Reputation/ReputationMgr.cpp
+++ b/src/server/game/Reputation/ReputationMgr.cpp
@@ -432,7 +432,7 @@ bool ReputationMgr::SetOneFactionReputation(FactionEntry const* factionEntry, fl
if (new_rank <= REP_HOSTILE)
SetAtWar(&itr->second, true);
- if (old_rank == REP_HOSTILE && new_rank >= REP_UNFRIENDLY && factionEntry->CanBeSetAtWar())
+ if (old_rank <= REP_HOSTILE && new_rank >= REP_UNFRIENDLY && factionEntry->CanBeSetAtWar())
SetAtWar(&itr->second, false);
if (new_rank > old_rank)
diff --git a/src/server/game/Scripting/ScriptDefines/AllSpellScript.cpp b/src/server/game/Scripting/ScriptDefines/AllSpellScript.cpp
index 3f15648935..d47939fe0b 100644
--- a/src/server/game/Scripting/ScriptDefines/AllSpellScript.cpp
+++ b/src/server/game/Scripting/ScriptDefines/AllSpellScript.cpp
@@ -24,16 +24,6 @@ void ScriptMgr::OnCalcMaxDuration(Aura const* aura, int32& maxDuration)
CALL_ENABLED_HOOKS(AllSpellScript, ALLSPELLHOOK_ON_CALC_MAX_DURATION, script->OnCalcMaxDuration(aura, maxDuration));
}
-bool ScriptMgr::CanModAuraEffectDamageDone(AuraEffect const* auraEff, Unit* target, AuraApplication const* aurApp, uint8 mode, bool apply)
-{
- CALL_ENABLED_BOOLEAN_HOOKS(AllSpellScript, ALLSPELLHOOK_CAN_MOD_AURA_EFFECT_DAMAGE_DONE, !script->CanModAuraEffectDamageDone(auraEff, target, aurApp, mode, apply));
-}
-
-bool ScriptMgr::CanModAuraEffectModDamagePercentDone(AuraEffect const* auraEff, Unit* target, AuraApplication const* aurApp, uint8 mode, bool apply)
-{
- CALL_ENABLED_BOOLEAN_HOOKS(AllSpellScript, ALLSPELLHOOK_CAN_MOD_AURA_EFFECT_MOD_DAMAGE_PERCENT_DONE, !script->CanModAuraEffectModDamagePercentDone(auraEff, target, aurApp, mode, apply));
-}
-
void ScriptMgr::OnSpellCheckCast(Spell* spell, bool strict, SpellCastResult& res)
{
CALL_ENABLED_HOOKS(AllSpellScript, ALLSPELLHOOK_ON_SPELL_CHECK_CAST, script->OnSpellCheckCast(spell, strict, res));
diff --git a/src/server/game/Scripting/ScriptDefines/AllSpellScript.h b/src/server/game/Scripting/ScriptDefines/AllSpellScript.h
index 7536f8cf5b..df069bcb11 100644
--- a/src/server/game/Scripting/ScriptDefines/AllSpellScript.h
+++ b/src/server/game/Scripting/ScriptDefines/AllSpellScript.h
@@ -24,8 +24,6 @@
enum AllSpellHook
{
ALLSPELLHOOK_ON_CALC_MAX_DURATION,
- ALLSPELLHOOK_CAN_MOD_AURA_EFFECT_DAMAGE_DONE,
- ALLSPELLHOOK_CAN_MOD_AURA_EFFECT_MOD_DAMAGE_PERCENT_DONE,
ALLSPELLHOOK_ON_SPELL_CHECK_CAST,
ALLSPELLHOOK_CAN_PREPARE,
ALLSPELLHOOK_CAN_SCALING_EVERYTHING,
@@ -56,10 +54,6 @@ public:
// Calculate max duration in applying aura
virtual void OnCalcMaxDuration(Aura const* /*aura*/, int32& /*maxDuration*/) { }
- [[nodiscard]] virtual bool CanModAuraEffectDamageDone(AuraEffect const* /*auraEff*/, Unit* /*target*/, AuraApplication const* /*aurApp*/, uint8 /*mode*/, bool /*apply*/) { return true; }
-
- [[nodiscard]] virtual bool CanModAuraEffectModDamagePercentDone(AuraEffect const* /*auraEff*/, Unit* /*target*/, AuraApplication const* /*aurApp*/, uint8 /*mode*/, bool /*apply*/) { return true; }
-
virtual void OnSpellCheckCast(Spell* /*spell*/, bool /*strict*/, SpellCastResult& /*res*/) { }
[[nodiscard]] virtual bool CanPrepare(Spell* /*spell*/, SpellCastTargets const* /*targets*/, AuraEffect const* /*triggeredByAura*/) { return true; }
diff --git a/src/server/game/Scripting/ScriptDefines/PlayerScript.cpp b/src/server/game/Scripting/ScriptDefines/PlayerScript.cpp
index b032688930..62439d2840 100644
--- a/src/server/game/Scripting/ScriptDefines/PlayerScript.cpp
+++ b/src/server/game/Scripting/ScriptDefines/PlayerScript.cpp
@@ -329,6 +329,11 @@ void ScriptMgr::OnPlayerEquip(Player* player, Item* it, uint8 bag, uint8 slot, b
CALL_ENABLED_HOOKS(PlayerScript, PLAYERHOOK_ON_EQUIP, script->OnPlayerEquip(player, it, bag, slot, update));
}
+void ScriptMgr::OnPlayerUnequip(Player* player, Item* it)
+{
+ CALL_ENABLED_HOOKS(PlayerScript, PLAYERHOOK_ON_UNEQUIP_ITEM, script->OnPlayerUnequip(player, it));
+}
+
void ScriptMgr::OnPlayerJoinBG(Player* player)
{
CALL_ENABLED_HOOKS(PlayerScript, PLAYERHOOK_ON_PLAYER_JOIN_BG, script->OnPlayerJoinBG(player));
@@ -594,12 +599,12 @@ bool ScriptMgr::OnPlayerCanAreaExploreAndOutdoor(Player* player)
CALL_ENABLED_BOOLEAN_HOOKS(PlayerScript, PLAYERHOOK_CAN_AREA_EXPLORE_AND_OUTDOOR, !script->OnPlayerCanAreaExploreAndOutdoor(player));
}
-void ScriptMgr::OnPlayerVictimRewardBefore(Player* player, Player* victim, uint32& killer_title, uint32& victim_title)
+void ScriptMgr::OnPlayerVictimRewardBefore(Player* player, Player* victim, uint32& killer_title, int32& victim_rank)
{
- CALL_ENABLED_HOOKS(PlayerScript, PLAYERHOOK_ON_VICTIM_REWARD_BEFORE, script->OnPlayerVictimRewardBefore(player, victim, killer_title, victim_title));
+ CALL_ENABLED_HOOKS(PlayerScript, PLAYERHOOK_ON_VICTIM_REWARD_BEFORE, script->OnPlayerVictimRewardBefore(player, victim, killer_title, victim_rank));
}
-void ScriptMgr::OnPlayerVictimRewardAfter(Player* player, Player* victim, uint32& killer_title, uint32& victim_rank, float& honor_f)
+void ScriptMgr::OnPlayerVictimRewardAfter(Player* player, Player* victim, uint32& killer_title, int32& victim_rank, float& honor_f)
{
CALL_ENABLED_HOOKS(PlayerScript, PLAYERHOOK_ON_VICTIM_REWARD_AFTER, script->OnPlayerVictimRewardAfter(player, victim, killer_title, victim_rank, honor_f));
}
diff --git a/src/server/game/Scripting/ScriptDefines/PlayerScript.h b/src/server/game/Scripting/ScriptDefines/PlayerScript.h
index 3b6dfe7284..5ee56e7ac6 100644
--- a/src/server/game/Scripting/ScriptDefines/PlayerScript.h
+++ b/src/server/game/Scripting/ScriptDefines/PlayerScript.h
@@ -88,6 +88,7 @@ enum PlayerHook
PLAYERHOOK_ON_AFTER_SET_VISIBLE_ITEM_SLOT,
PLAYERHOOK_ON_AFTER_MOVE_ITEM_FROM_INVENTORY,
PLAYERHOOK_ON_EQUIP,
+ PLAYERHOOK_ON_UNEQUIP_ITEM,
PLAYERHOOK_ON_PLAYER_JOIN_BG,
PLAYERHOOK_ON_PLAYER_JOIN_ARENA,
PLAYERHOOK_GET_CUSTOM_GET_ARENA_TEAM_ID,
@@ -395,6 +396,9 @@ public:
// After an item has been equipped
virtual void OnPlayerEquip(Player* /*player*/, Item* /*it*/, uint8 /*bag*/, uint8 /*slot*/, bool /*update*/) { }
+ // After an item has been unequipped
+ virtual void OnPlayerUnequip(Player* /*player*/, Item* /*it*/) { }
+
// After player enters queue for BG
virtual void OnPlayerJoinBG(Player* /*player*/) { }
@@ -531,9 +535,9 @@ public:
[[nodiscard]] virtual bool OnPlayerCanAreaExploreAndOutdoor(Player* /*player*/) { return true; }
- virtual void OnPlayerVictimRewardBefore(Player* /*player*/, Player* /*victim*/, uint32& /*killer_title*/, uint32& /*victim_title*/) { }
+ virtual void OnPlayerVictimRewardBefore(Player* /*player*/, Player* /*victim*/, uint32& /*killer_title*/, int32& /*victim_rank*/) { }
- virtual void OnPlayerVictimRewardAfter(Player* /*player*/, Player* /*victim*/, uint32& /*killer_title*/, uint32& /*victim_rank*/, float& /*honor_f*/) { }
+ virtual void OnPlayerVictimRewardAfter(Player* /*player*/, Player* /*victim*/, uint32& /*killer_title*/, int32& /*victim_rank*/, float& /*honor_f*/) { }
virtual void OnPlayerCustomScalingStatValueBefore(Player* /*player*/, ItemTemplate const* /*proto*/, uint8 /*slot*/, bool /*apply*/, uint32& /*CustomScalingStatValue*/) { }
diff --git a/src/server/game/Scripting/ScriptDefines/UnitScript.cpp b/src/server/game/Scripting/ScriptDefines/UnitScript.cpp
index 2975ec4f1a..c36cfcd25a 100644
--- a/src/server/game/Scripting/ScriptDefines/UnitScript.cpp
+++ b/src/server/game/Scripting/ScriptDefines/UnitScript.cpp
@@ -84,21 +84,6 @@ bool ScriptMgr::IfNormalReaction(Unit const* unit, Unit const* target, Reputatio
CALL_ENABLED_BOOLEAN_HOOKS(UnitScript, UNITHOOK_IF_NORMAL_REACTION, !script->IfNormalReaction(unit, target, repRank));
}
-bool ScriptMgr::IsNeedModSpellDamagePercent(Unit const* unit, AuraEffect* auraEff, float& doneTotalMod, SpellInfo const* spellProto)
-{
- CALL_ENABLED_BOOLEAN_HOOKS(UnitScript, UNITHOOK_IS_NEEDMOD_SPELL_DAMAGE_PERCENT, !script->IsNeedModSpellDamagePercent(unit, auraEff, doneTotalMod, spellProto));
-}
-
-bool ScriptMgr::IsNeedModMeleeDamagePercent(Unit const* unit, AuraEffect* auraEff, float& doneTotalMod, SpellInfo const* spellProto)
-{
- CALL_ENABLED_BOOLEAN_HOOKS(UnitScript, UNITHOOK_IS_NEEDMOD_MELEE_DAMAGE_PERCENT, !script->IsNeedModMeleeDamagePercent(unit, auraEff, doneTotalMod, spellProto));
-}
-
-bool ScriptMgr::IsNeedModHealPercent(Unit const* unit, AuraEffect* auraEff, float& doneTotalMod, SpellInfo const* spellProto)
-{
- CALL_ENABLED_BOOLEAN_HOOKS(UnitScript, UNITHOOK_IS_NEEDMOD_HEAL_PERCENT, !script->IsNeedModHealPercent(unit, auraEff, doneTotalMod, spellProto));
-}
-
bool ScriptMgr::CanSetPhaseMask(Unit const* unit, uint32 newPhaseMask, bool update)
{
CALL_ENABLED_BOOLEAN_HOOKS(UnitScript, UNITHOOK_CAN_SET_PHASE_MASK, !script->CanSetPhaseMask(unit, newPhaseMask, update));
diff --git a/src/server/game/Scripting/ScriptDefines/UnitScript.h b/src/server/game/Scripting/ScriptDefines/UnitScript.h
index 26c4e2b13d..046f9cc117 100644
--- a/src/server/game/Scripting/ScriptDefines/UnitScript.h
+++ b/src/server/game/Scripting/ScriptDefines/UnitScript.h
@@ -33,9 +33,6 @@ enum UnitHook
UNITHOOK_ON_AURA_APPLY,
UNITHOOK_ON_AURA_REMOVE,
UNITHOOK_IF_NORMAL_REACTION,
- UNITHOOK_IS_NEEDMOD_SPELL_DAMAGE_PERCENT,
- UNITHOOK_IS_NEEDMOD_MELEE_DAMAGE_PERCENT,
- UNITHOOK_IS_NEEDMOD_HEAL_PERCENT,
UNITHOOK_CAN_SET_PHASE_MASK,
UNITHOOK_IS_CUSTOM_BUILD_VALUES_UPDATE,
UNITHOOK_SHOULD_TRACK_VALUES_UPDATE_POS_BY_INDEX,
@@ -89,12 +86,6 @@ public:
[[nodiscard]] virtual bool IfNormalReaction(Unit const* /*unit*/, Unit const* /*target*/, ReputationRank& /*repRank*/) { return true; }
- [[nodiscard]] virtual bool IsNeedModSpellDamagePercent(Unit const* /*unit*/, AuraEffect* /*auraEff*/, float& /*doneTotalMod*/, SpellInfo const* /*spellProto*/) { return true; }
-
- [[nodiscard]] virtual bool IsNeedModMeleeDamagePercent(Unit const* /*unit*/, AuraEffect* /*auraEff*/, float& /*doneTotalMod*/, SpellInfo const* /*spellProto*/) { return true; }
-
- [[nodiscard]] virtual bool IsNeedModHealPercent(Unit const* /*unit*/, AuraEffect* /*auraEff*/, float& /*doneTotalMod*/, SpellInfo const* /*spellProto*/) { return true; }
-
[[nodiscard]] virtual bool CanSetPhaseMask(Unit const* /*unit*/, uint32 /*newPhaseMask*/, bool /*update*/) { return true; }
[[nodiscard]] virtual bool IsCustomBuildValuesUpdate(Unit const* /*unit*/, uint8 /*updateType*/, ByteBuffer& /*fieldBuffer*/, Player const* /*target*/, uint16 /*index*/) { return false; }
diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h
index 6a9a0914d3..0dadd18f63 100644
--- a/src/server/game/Scripting/ScriptMgr.h
+++ b/src/server/game/Scripting/ScriptMgr.h
@@ -351,6 +351,7 @@ public: /* PlayerScript */
void OnPlayerAfterSetVisibleItemSlot(Player* player, uint8 slot, Item* item);
void OnPlayerAfterMoveItemFromInventory(Player* player, Item* it, uint8 bag, uint8 slot, bool update);
void OnPlayerEquip(Player* player, Item* it, uint8 bag, uint8 slot, bool update);
+ void OnPlayerUnequip(Player* player, Item* it);
void OnPlayerJoinBG(Player* player);
void OnPlayerJoinArena(Player* player);
void OnPlayerGetMaxPersonalArenaRatingRequirement(Player const* player, uint32 minSlot, uint32& maxArenaRating) const;
@@ -402,8 +403,8 @@ public: /* PlayerScript */
void OnPlayerUpdateCraftingSkill(Player* player, SkillLineAbilityEntry const* skill, uint32 currentLevel, uint32& gain);
bool OnPlayerUpdateFishingSkill(Player* player, int32 skill, int32 zone_skill, int32 chance, int32 roll);
bool OnPlayerCanAreaExploreAndOutdoor(Player* player);
- void OnPlayerVictimRewardBefore(Player* player, Player* victim, uint32& killer_title, uint32& victim_title);
- void OnPlayerVictimRewardAfter(Player* player, Player* victim, uint32& killer_title, uint32& victim_rank, float& honor_f);
+ void OnPlayerVictimRewardBefore(Player* player, Player* victim, uint32& killer_title, int32& victim_rank);
+ void OnPlayerVictimRewardAfter(Player* player, Player* victim, uint32& killer_title, int32& victim_rank, float& honor_f);
void OnPlayerCustomScalingStatValueBefore(Player* player, ItemTemplate const* proto, uint8 slot, bool apply, uint32& CustomScalingStatValue);
void OnPlayerCustomScalingStatValue(Player* player, ItemTemplate const* proto, uint32& statType, int32& val, uint8 itemProtoStatNumber, uint32 ScalingStatValue, ScalingStatValuesEntry const* ssv);
void OnPlayerApplyItemModsBefore(Player* player, uint8 slot, bool apply, uint8 itemProtoStatNumber, uint32 statType, int32& val);
@@ -546,9 +547,6 @@ public: /* UnitScript */
void OnAuraApply(Unit* /*unit*/, Aura* /*aura*/);
void OnAuraRemove(Unit* unit, AuraApplication* aurApp, AuraRemoveMode mode);
bool IfNormalReaction(Unit const* unit, Unit const* target, ReputationRank& repRank);
- bool IsNeedModSpellDamagePercent(Unit const* unit, AuraEffect* auraEff, float& doneTotalMod, SpellInfo const* spellProto);
- bool IsNeedModMeleeDamagePercent(Unit const* unit, AuraEffect* auraEff, float& doneTotalMod, SpellInfo const* spellProto);
- bool IsNeedModHealPercent(Unit const* unit, AuraEffect* auraEff, float& doneTotalMod, SpellInfo const* spellProto);
bool CanSetPhaseMask(Unit const* unit, uint32 newPhaseMask, bool update);
bool IsCustomBuildValuesUpdate(Unit const* unit, uint8 updateType, ByteBuffer& fieldBuffer, Player const* target, uint16 index);
bool ShouldTrackValuesUpdatePosByIndex(Unit const* unit, uint8 updateType, uint16 index);
@@ -606,8 +604,6 @@ public: /* Arena Team Script */
public: /* SpellSC */
void OnCalcMaxDuration(Aura const* aura, int32& maxDuration);
- bool CanModAuraEffectDamageDone(AuraEffect const* auraEff, Unit* target, AuraApplication const* aurApp, uint8 mode, bool apply);
- bool CanModAuraEffectModDamagePercentDone(AuraEffect const* auraEff, Unit* target, AuraApplication const* aurApp, uint8 mode, bool apply);
void OnSpellCheckCast(Spell* spell, bool strict, SpellCastResult& res);
bool CanPrepare(Spell* spell, SpellCastTargets const* targets, AuraEffect const* triggeredByAura);
bool CanScalingEverything(Spell* spell);
diff --git a/src/server/game/Server/Packets/AllPackets.h b/src/server/game/Server/Packets/AllPackets.h
index e0cbdf7d1c..0f27748360 100644
--- a/src/server/game/Server/Packets/AllPackets.h
+++ b/src/server/game/Server/Packets/AllPackets.h
@@ -28,6 +28,7 @@
#include "ItemPackets.h"
#include "LFGPackets.h"
#include "MiscPackets.h"
+#include "NPCPackets.h"
#include "PetPackets.h"
#include "QueryPackets.h"
#include "TotemPackets.h"
diff --git a/src/server/game/Server/Packets/NPCPackets.cpp b/src/server/game/Server/Packets/NPCPackets.cpp
new file mode 100644
index 0000000000..95e2249aab
--- /dev/null
+++ b/src/server/game/Server/Packets/NPCPackets.cpp
@@ -0,0 +1,69 @@
+/*
+ * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by the
+ * Free Software Foundation; either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "NPCPackets.h"
+
+void WorldPackets::NPC::Hello::Read()
+{
+ _worldPacket >> Unit;
+}
+
+WorldPacket const* WorldPackets::NPC::TrainerList::Write()
+{
+ _worldPacket << TrainerGUID;
+ _worldPacket << int32(TrainerType);
+
+ _worldPacket << int32(Spells.size());
+ for (TrainerListSpell const& spell : Spells)
+ {
+ _worldPacket << int32(spell.SpellID);
+ _worldPacket << uint8(spell.Usable);
+ _worldPacket << int32(spell.MoneyCost);
+ _worldPacket.append(spell.PointCost.data(), spell.PointCost.size());
+ _worldPacket << uint8(spell.ReqLevel);
+ _worldPacket << int32(spell.ReqSkillLine);
+ _worldPacket << int32(spell.ReqSkillRank);
+ _worldPacket.append(spell.ReqAbility.data(), spell.ReqAbility.size());
+ }
+
+ _worldPacket << Greeting;
+
+ return &_worldPacket;
+}
+
+void WorldPackets::NPC::TrainerBuySpell::Read()
+{
+ _worldPacket >> TrainerGUID;
+ _worldPacket >> SpellID;
+}
+
+WorldPacket const* WorldPackets::NPC::TrainerBuyFailed::Write()
+{
+ _worldPacket << TrainerGUID;
+ _worldPacket << int32(SpellID);
+ _worldPacket << int32(TrainerFailedReason);
+
+ return &_worldPacket;
+}
+
+WorldPacket const* WorldPackets::NPC::TrainerBuySucceeded::Write()
+{
+ _worldPacket << TrainerGUID;
+ _worldPacket << int32(SpellID);
+
+ return &_worldPacket;
+}
diff --git a/src/server/game/Server/Packets/NPCPackets.h b/src/server/game/Server/Packets/NPCPackets.h
new file mode 100644
index 0000000000..2fbe30e13e
--- /dev/null
+++ b/src/server/game/Server/Packets/NPCPackets.h
@@ -0,0 +1,103 @@
+/*
+ * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by the
+ * Free Software Foundation; either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NPCPackets_h__
+#define NPCPackets_h__
+
+#include "Packet.h"
+#include "ObjectGuid.h"
+#include <array>
+
+namespace WorldPackets::NPC
+{
+ // CMSG_BANKER_ACTIVATE
+ // CMSG_BATTLEMASTER_HELLO
+ // CMSG_BINDER_ACTIVATE
+ // CMSG_GOSSIP_HELLO
+ // CMSG_LIST_INVENTORY
+ // CMSG_TRAINER_LIST
+ class Hello final : public ClientPacket
+ {
+ public:
+ explicit Hello(WorldPacket&& packet) : ClientPacket(std::move(packet)) { }
+
+ void Read() override;
+
+ ObjectGuid Unit;
+ };
+
+ struct TrainerListSpell
+ {
+ int32 SpellID = 0;
+ uint8 Usable = 0;
+ int32 MoneyCost = 0;
+ std::array<int32, 2> PointCost = { }; // compared with PLAYER_CHARACTER_POINTS in Lua
+ uint8 ReqLevel = 0;
+ int32 ReqSkillLine = 0;
+ int32 ReqSkillRank = 0;
+ std::array<int32, 3> ReqAbility = { };
+ };
+
+ class TrainerList final : public ServerPacket
+ {
+ public:
+ TrainerList() : ServerPacket(SMSG_TRAINER_LIST) { }
+
+ WorldPacket const* Write() override;
+
+ ObjectGuid TrainerGUID;
+ int32 TrainerType = 0;
+ std::vector<TrainerListSpell> Spells;
+ std::string Greeting;
+ };
+
+ class TrainerBuySpell final : public ClientPacket
+ {
+ public:
+ explicit TrainerBuySpell(WorldPacket&& packet) : ClientPacket(CMSG_TRAINER_BUY_SPELL, std::move(packet)) { }
+
+ void Read() override;
+
+ ObjectGuid TrainerGUID;
+ int32 SpellID = 0;
+ };
+
+ class TrainerBuyFailed final : public ServerPacket
+ {
+ public:
+ TrainerBuyFailed() : ServerPacket(SMSG_TRAINER_BUY_FAILED, 8 + 4 + 4) { }
+
+ WorldPacket const* Write() override;
+
+ ObjectGuid TrainerGUID;
+ int32 SpellID = 0;
+ int32 TrainerFailedReason = 0;
+ };
+
+ class TrainerBuySucceeded final : public ServerPacket
+ {
+ public:
+ TrainerBuySucceeded() : ServerPacket(SMSG_TRAINER_BUY_SUCCEEDED, 8 + 4) { }
+
+ WorldPacket const* Write() override;
+
+ ObjectGuid TrainerGUID;
+ int32 SpellID = 0;
+ };
+}
+
+#endif // NPCPackets_h__
diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp
index 04ff5ba9b1..254328b809 100644
--- a/src/server/game/Server/WorldSession.cpp
+++ b/src/server/game/Server/WorldSession.cpp
@@ -125,7 +125,7 @@ WorldSession::WorldSession(uint32 id, std::string&& name, uint32 accountFlags, s
m_playerLogout(false),
m_playerRecentlyLogout(false),
m_playerSave(false),
- m_sessionDbcLocale(sWorld->GetDefaultDbcLocale()),
+ m_sessionDbcLocale(sWorld->GetAvailableDbcLocale(locale)),
m_sessionDbLocaleIndex(locale),
m_latency(0),
m_TutorialsChanged(false),
diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h
index c12fb7df0e..6a1c554ca2 100644
--- a/src/server/game/Server/WorldSession.h
+++ b/src/server/game/Server/WorldSession.h
@@ -198,6 +198,12 @@ namespace WorldPackets
class ArenaTeam;
class CalendarComplain;
}
+
+ namespace NPC
+ {
+ class Hello;
+ class TrainerBuySpell;
+ }
}
enum AccountDataType
@@ -478,8 +484,7 @@ public:
//void SendTestCreatureQueryOpcode(uint32 entry, ObjectGuid guid, uint32 testvalue);
void SendNameQueryOpcode(ObjectGuid guid);
- void SendTrainerList(ObjectGuid guid);
- void SendTrainerList(ObjectGuid guid, std::string const& strTitle);
+ void SendTrainerList(Creature* npc);
void SendListInventory(ObjectGuid guid, uint32 vendorEntry = 0);
void SendShowBank(ObjectGuid guid);
bool CanOpenMailBox(ObjectGuid guid);
@@ -785,8 +790,8 @@ public: // opcodes handlers
void SendActivateTaxiReply(ActivateTaxiReply reply);
void HandleTabardVendorActivateOpcode(WorldPacket& recvPacket);
- void HandleTrainerListOpcode(WorldPacket& recvPacket);
- void HandleTrainerBuySpellOpcode(WorldPacket& recvPacket);
+ void HandleTrainerListOpcode(WorldPackets::NPC::Hello& packet);
+ void HandleTrainerBuySpellOpcode(WorldPackets::NPC::TrainerBuySpell& packet);
void HandlePetitionShowListOpcode(WorldPacket& recvPacket);
void HandleGossipHelloOpcode(WorldPacket& recvPacket);
void HandleGossipSelectOptionOpcode(WorldPacket& recvPacket);
diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.cpp b/src/server/game/Spells/Auras/SpellAuraEffects.cpp
index 06d3166e0a..f0d54d7e0d 100644
--- a/src/server/game/Spells/Auras/SpellAuraEffects.cpp
+++ b/src/server/game/Spells/Auras/SpellAuraEffects.cpp
@@ -212,7 +212,7 @@ pAuraEffectHandler AuraEffectHandler[TOTAL_AURAS] =
&AuraEffect::HandleModStateImmunityMask, //147 SPELL_AURA_MECHANIC_IMMUNITY_MASK
&AuraEffect::HandleAuraRetainComboPoints, //148 SPELL_AURA_RETAIN_COMBO_POINTS
&AuraEffect::HandleNoImmediateEffect, //149 SPELL_AURA_REDUCE_PUSHBACK
- &AuraEffect::HandleShieldBlockValue, //150 SPELL_AURA_MOD_SHIELD_BLOCKVALUE_PCT
+ &AuraEffect::HandleShieldBlockValuePercent, //150 SPELL_AURA_MOD_SHIELD_BLOCKVALUE_PCT
&AuraEffect::HandleAuraTrackStealthed, //151 SPELL_AURA_TRACK_STEALTHED
&AuraEffect::HandleNoImmediateEffect, //152 SPELL_AURA_MOD_DETECTED_RANGE implemented in Creature::GetAggroRange
&AuraEffect::HandleNoImmediateEffect, //153 SPELL_AURA_SPLIT_DAMAGE_FLAT
@@ -393,7 +393,6 @@ AuraEffect::AuraEffect(Aura* base, uint8 effIndex, int32* baseAmount, Unit* cast
m_amount = CalculateAmount(caster);
m_casterLevel = caster ? caster->GetLevel() : 0;
m_applyResilience = caster && caster->CanApplyResilience();
- m_auraGroup = sSpellMgr->GetSpellGroup(GetId());
CalculateSpellMod();
@@ -727,9 +726,12 @@ void AuraEffect::ChangeAmount(int32 newAmount, bool mark, bool onStackOrReapply)
std::list<AuraApplication*> effectApplications;
GetApplicationList(effectApplications);
- for (std::list<AuraApplication*>::const_iterator apptItr = effectApplications.begin(); apptItr != effectApplications.end(); ++apptItr)
- if ((*apptItr)->HasEffect(GetEffIndex()))
- HandleEffect(*apptItr, handleMask, false);
+ for (AuraApplication* aurApp : effectApplications)
+ if (aurApp->HasEffect(GetEffIndex()))
+ {
+ aurApp->GetTarget()->_RegisterAuraEffect(this, false);
+ HandleEffect(aurApp, handleMask, false);
+ }
if (handleMask & AURA_EFFECT_HANDLE_CHANGE_AMOUNT)
{
@@ -740,9 +742,15 @@ void AuraEffect::ChangeAmount(int32 newAmount, bool mark, bool onStackOrReapply)
CalculateSpellMod();
}
- for (std::list<AuraApplication*>::const_iterator apptItr = effectApplications.begin(); apptItr != effectApplications.end(); ++apptItr)
- if ((*apptItr)->HasEffect(GetEffIndex()))
- HandleEffect(*apptItr, handleMask, true);
+ for (AuraApplication* aurApp : effectApplications)
+ if (aurApp->HasEffect(GetEffIndex()))
+ {
+ if (aurApp->GetRemoveMode() != AURA_REMOVE_NONE)
+ continue;
+
+ aurApp->GetTarget()->_RegisterAuraEffect(this, true);
+ HandleEffect(aurApp, handleMask, true);
+ }
}
void AuraEffect::HandleEffect(AuraApplication* aurApp, uint8 mode, bool apply)
@@ -3000,14 +3008,17 @@ void AuraEffect::HandleAuraModDisarm(AuraApplication const* aurApp, uint8 mode,
// Handle damage modification, shapeshifted druids are not affected
if (target->IsPlayer() && (!target->IsInFeralForm() || target->GetShapeshiftForm() == FORM_GHOSTWOLF))
{
- if (Item* pItem = target->ToPlayer()->GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
+ Player* player = target->ToPlayer();
+ if (Item* pItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
{
- WeaponAttackType attacktype = Player::GetAttackBySlot(slot);
+ WeaponAttackType attackType = Player::GetAttackBySlot(slot);
- if (attacktype < MAX_ATTACK)
+ player->ApplyItemDependentAuras(pItem, !apply);
+ if (attackType < MAX_ATTACK)
{
- target->ToPlayer()->_ApplyWeaponDamage(slot, pItem->GetTemplate(), nullptr, !apply);
- target->ToPlayer()->_ApplyWeaponDependentAuraMods(pItem, attacktype, !apply);
+ player->_ApplyWeaponDamage(slot, pItem->GetTemplate(), nullptr, !apply);
+ if (!apply) // apply case already handled on item dependent aura removal (if any)
+ player->UpdateWeaponDependentAuras(attackType);
}
}
}
@@ -3439,9 +3450,17 @@ void AuraEffect::HandleModThreat(AuraApplication const* aurApp, uint8 mode, bool
return;
Unit* target = aurApp->GetTarget();
- for (int8 i = 0; i < MAX_SPELL_SCHOOL; ++i)
+ for (uint8 i = 0; i < MAX_SPELL_SCHOOL; ++i)
if (GetMiscValue() & (1 << i))
- ApplyPercentModFloatVar(target->m_threatModifier[i], float(GetAmount()), apply);
+ {
+ if (apply)
+ AddPct(target->m_threatModifier[i], GetAmount());
+ else
+ {
+ float amount = target->GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_THREAT, 1 << i);
+ target->m_threatModifier[i] = amount;
+ }
+ }
}
void AuraEffect::HandleAuraModTotalThreat(AuraApplication const* aurApp, uint8 mode, bool apply) const
@@ -4318,7 +4337,7 @@ void AuraEffect::HandleAuraModResistanceExclusive(AuraApplication const* aurApp,
Unit* target = aurApp->GetTarget();
- for (int8 x = SPELL_SCHOOL_NORMAL; x < MAX_SPELL_SCHOOL; x++)
+ for (uint8 x = SPELL_SCHOOL_NORMAL; x < MAX_SPELL_SCHOOL; x++)
{
if (GetMiscValue() & int32(1 << x))
{
@@ -4326,9 +4345,9 @@ void AuraEffect::HandleAuraModResistanceExclusive(AuraApplication const* aurApp,
if (amount < GetAmount())
{
float value = float(GetAmount() - amount);
- target->HandleStatModifier(UnitMods(UNIT_MOD_RESISTANCE_START + x), BASE_VALUE, value, apply);
- if (target->IsPlayer())
- target->ApplyResistanceBuffModsMod(SpellSchools(x), aurApp->IsPositive(), value, apply);
+ target->HandleStatFlatModifier(UnitMods(UNIT_MOD_RESISTANCE_START + x), BASE_VALUE, value, apply);
+ if (target->IsPlayer() || target->IsPet())
+ target->UpdateResistanceBuffModsMod(SpellSchools(x));
}
}
}
@@ -4345,9 +4364,9 @@ void AuraEffect::HandleAuraModResistance(AuraApplication const* aurApp, uint8 mo
{
if (GetMiscValue() & int32(1 << x))
{
- target->HandleStatModifier(UnitMods(UNIT_MOD_RESISTANCE_START + x), TOTAL_VALUE, float(GetAmount()), apply);
+ target->HandleStatFlatModifier(UnitMods(UNIT_MOD_RESISTANCE_START + x), TOTAL_VALUE, float(GetAmount()), apply);
if (target->IsPlayer() || target->IsPet())
- target->ApplyResistanceBuffModsMod(SpellSchools(x), GetAmount() > 0, (float)GetAmount(), apply);
+ target->UpdateResistanceBuffModsMod(SpellSchools(x));
}
}
}
@@ -4358,32 +4377,39 @@ void AuraEffect::HandleAuraModBaseResistancePCT(AuraApplication const* aurApp, u
return;
Unit* target = aurApp->GetTarget();
- for (int8 x = SPELL_SCHOOL_NORMAL; x < MAX_SPELL_SCHOOL; x++)
+ for (uint8 x = SPELL_SCHOOL_NORMAL; x < MAX_SPELL_SCHOOL; x++)
{
if (GetMiscValue() & int32(1 << x))
{
- target->HandleStatModifier(UnitMods(UNIT_MOD_RESISTANCE_START + x), BASE_PCT, float(GetAmount()), apply);
+ if (apply)
+ target->ApplyStatPctModifier(UnitMods(UNIT_MOD_RESISTANCE_START + x), BASE_PCT, float(GetAmount()));
+ else
+ {
+ float amount = target->GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_BASE_RESISTANCE_PCT, 1 << x);
+ target->SetStatPctModifier(UnitMods(UNIT_MOD_RESISTANCE_START + x), BASE_PCT, amount);
+ }
}
}
}
-void AuraEffect::HandleModResistancePercent(AuraApplication const* aurApp, uint8 mode, bool apply) const
+void AuraEffect::HandleModResistancePercent(AuraApplication const* aurApp, uint8 mode, bool /*apply*/) const
{
if (!(mode & (AURA_EFFECT_HANDLE_CHANGE_AMOUNT_MASK | AURA_EFFECT_HANDLE_STAT)))
return;
Unit* target = aurApp->GetTarget();
- for (int8 i = SPELL_SCHOOL_NORMAL; i < MAX_SPELL_SCHOOL; i++)
+ for (uint8 i = SPELL_SCHOOL_NORMAL; i < MAX_SPELL_SCHOOL; i++)
{
if (GetMiscValue() & int32(1 << i))
{
- target->HandleStatModifier(UnitMods(UNIT_MOD_RESISTANCE_START + i), TOTAL_PCT, float(GetAmount()), apply);
+ float amount = target->GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_RESISTANCE_PCT, 1 << i);
+ if (target->GetPctModifierValue(UnitMods(UNIT_MOD_RESISTANCE_START + i), TOTAL_PCT) == amount)
+ continue;
+
+ target->SetStatPctModifier(UnitMods(UNIT_MOD_RESISTANCE_START + i), TOTAL_PCT, amount);
if (target->IsPlayer() || target->IsPet())
- {
- target->ApplyResistanceBuffModsPercentMod(SpellSchools(i), true, (float)GetAmount(), apply);
- target->ApplyResistanceBuffModsPercentMod(SpellSchools(i), false, (float)GetAmount(), apply);
- }
+ target->UpdateResistanceBuffModsMod(SpellSchools(i));
}
}
}
@@ -4398,7 +4424,7 @@ void AuraEffect::HandleModBaseResistance(AuraApplication const* aurApp, uint8 mo
{
if (GetMiscValue() & (1 << i))
{
- target->HandleStatModifier(UnitMods(UNIT_MOD_RESISTANCE_START + i), TOTAL_VALUE, float(GetAmount()), apply);
+ target->HandleStatFlatModifier(UnitMods(UNIT_MOD_RESISTANCE_START + i), TOTAL_VALUE, float(GetAmount()), apply);
}
}
}
@@ -4430,23 +4456,28 @@ void AuraEffect::HandleAuraModStat(AuraApplication const* aurApp, uint8 mode, bo
if (!(mode & (AURA_EFFECT_HANDLE_CHANGE_AMOUNT_MASK | AURA_EFFECT_HANDLE_STAT)))
return;
- Unit* target = aurApp->GetTarget();
-
if (GetMiscValue() < -2 || GetMiscValue() > 4)
{
LOG_ERROR("spells.aura.effect", "WARNING: Spell {} effect {} has an unsupported misc value ({}) for SPELL_AURA_MOD_STAT ", GetId(), GetEffIndex(), GetMiscValue());
return;
}
+ Unit* target = aurApp->GetTarget();
+ int32 spellGroupVal = target->GetHighestExclusiveSameEffectSpellGroupValue(this, SPELL_AURA_MOD_STAT, true, GetMiscValue());
+ if (std::abs(spellGroupVal) >= std::abs(GetAmount()))
+ return;
+
for (int32 i = STAT_STRENGTH; i < MAX_STATS; i++)
{
// -1 or -2 is all stats (misc < -2 checked in function beginning)
if (GetMiscValue() < 0 || GetMiscValue() == i)
{
- //target->ApplyStatMod(Stats(i), m_amount, apply);
- target->HandleStatModifier(UnitMods(UNIT_MOD_STAT_START + i), TOTAL_VALUE, float(GetAmount()), apply);
+ if (spellGroupVal)
+ target->HandleStatFlatModifier(UnitMods(UNIT_MOD_STAT_START + i), TOTAL_VALUE, float(GetAmount()), !apply);
+
+ target->HandleStatFlatModifier(UnitMods(UNIT_MOD_STAT_START + i), TOTAL_VALUE, float(GetAmount()), apply);
if (target->IsPlayer() || target->IsPet())
- target->ApplyStatBuffMod(Stats(i), (float)GetAmount(), apply);
+ target->UpdateStatBuffMod(Stats(i));
}
}
}
@@ -4470,8 +4501,16 @@ void AuraEffect::HandleModPercentStat(AuraApplication const* aurApp, uint8 mode,
for (int32 i = STAT_STRENGTH; i < MAX_STATS; ++i)
{
- if (GetMiscValue() == i || GetMiscValue() == -1)
- target->HandleStatModifier(UnitMods(UNIT_MOD_STAT_START + i), BASE_PCT, float(m_amount), apply);
+ if (apply)
+ target->ApplyStatPctModifier(UnitMods(UNIT_MOD_STAT_START + i), BASE_PCT, float(GetAmount()));
+ else
+ {
+ float amount = target->GetTotalAuraMultiplier(SPELL_AURA_MOD_PERCENT_STAT, [i](AuraEffect const* aurEff)
+ {
+ return (aurEff->GetMiscValue() == i || aurEff->GetMiscValue() == -1);
+ });
+ target->SetStatPctModifier(UnitMods(UNIT_MOD_STAT_START + i), BASE_PCT, amount);
+ }
}
}
@@ -4549,7 +4588,7 @@ void AuraEffect::HandleModHealingDone(AuraApplication const* aurApp, uint8 mode,
target->ToPlayer()->UpdateSpellDamageAndHealingBonus();
}
-void AuraEffect::HandleModTotalPercentStat(AuraApplication const* aurApp, uint8 mode, bool apply) const
+void AuraEffect::HandleModTotalPercentStat(AuraApplication const* aurApp, uint8 mode, bool /*apply*/) const
{
if (!(mode & (AURA_EFFECT_HANDLE_CHANGE_AMOUNT_MASK | AURA_EFFECT_HANDLE_STAT)))
return;
@@ -4565,39 +4604,22 @@ void AuraEffect::HandleModTotalPercentStat(AuraApplication const* aurApp, uint8
// save current health state
float healthPct = target->GetHealthPct();
bool alive = target->IsAlive();
- float value = GetAmount();
-
- if (GetId() == 67480) // xinef: hack fix for blessing of sanctuary stats stack with blessing of kings...
- {
- if (value) // not turned off
- value = 10.0f;
- for (int32 i = STAT_STRENGTH; i < MAX_STATS; i++)
- {
- if (i == STAT_STRENGTH || i == STAT_STAMINA)
- {
- if (apply && (target->IsPlayer() || target->IsPet()))
- target->ApplyStatPercentBuffMod(Stats(i), value, apply);
-
- target->HandleStatModifier(UnitMods(UNIT_MOD_STAT_START + i), TOTAL_PCT, value, apply);
-
- if (!apply && (target->IsPlayer() || target->IsPet()))
- target->ApplyStatPercentBuffMod(Stats(i), value, apply);
- }
- }
- return;
- }
for (int32 i = STAT_STRENGTH; i < MAX_STATS; i++)
{
if (GetMiscValue() == i || GetMiscValue() == -1)
{
- if (apply && (target->IsPlayer() || target->IsPet()))
- target->ApplyStatPercentBuffMod(Stats(i), value, apply);
+ float amount = target->GetTotalAuraMultiplier(SPELL_AURA_MOD_TOTAL_STAT_PERCENTAGE, [i](AuraEffect const* aurEff)
+ {
+ return (aurEff->GetMiscValue() == i || aurEff->GetMiscValue() == -1);
+ });
- target->HandleStatModifier(UnitMods(UNIT_MOD_STAT_START + i), TOTAL_PCT, value, apply);
+ if (target->GetPctModifierValue(UnitMods(UNIT_MOD_STAT_START + i), TOTAL_PCT) == amount)
+ continue;
- if (!apply && (target->IsPlayer() || target->IsPet()))
- target->ApplyStatPercentBuffMod(Stats(i), value, apply);
+ target->SetStatPctModifier(UnitMods(UNIT_MOD_STAT_START + i), TOTAL_PCT, amount);
+ if (target->IsPlayer() || target->IsPet())
+ target->UpdateStatBuffMod(Stats(i));
}
}
@@ -4693,7 +4715,7 @@ void AuraEffect::HandleAuraModIncreaseHealth(AuraApplication const* aurApp, uint
if (apply)
{
- target->HandleStatModifier(UNIT_MOD_HEALTH, TOTAL_VALUE, float(GetAmount()), apply);
+ target->HandleStatFlatModifier(UNIT_MOD_HEALTH, TOTAL_VALUE, float(GetAmount()), apply);
target->ModifyHealth(GetAmount());
}
else
@@ -4702,7 +4724,7 @@ void AuraEffect::HandleAuraModIncreaseHealth(AuraApplication const* aurApp, uint
target->ModifyHealth(-GetAmount());
else
target->SetHealth(1);
- target->HandleStatModifier(UNIT_MOD_HEALTH, TOTAL_VALUE, float(GetAmount()), apply);
+ target->HandleStatFlatModifier(UNIT_MOD_HEALTH, TOTAL_VALUE, float(GetAmount()), apply);
}
}
@@ -4716,7 +4738,7 @@ void AuraEffect::HandleAuraModIncreaseMaxHealth(AuraApplication const* aurApp, u
uint32 oldhealth = target->GetHealth();
double healthPercentage = (double)oldhealth / (double)target->GetMaxHealth();
- target->HandleStatModifier(UNIT_MOD_HEALTH, TOTAL_VALUE, float(GetAmount()), apply);
+ target->HandleStatFlatModifier(UNIT_MOD_HEALTH, TOTAL_VALUE, float(GetAmount()), apply);
// refresh percentage
if (oldhealth > 0)
@@ -4746,7 +4768,7 @@ void AuraEffect::HandleAuraModIncreaseEnergy(AuraApplication const* aurApp, uint
UnitMods unitMod = UnitMods(static_cast<uint16>(UNIT_MOD_POWER_START) + PowerType);
- target->HandleStatModifier(unitMod, TOTAL_VALUE, float(GetAmount()), apply);
+ target->HandleStatFlatModifier(unitMod, TOTAL_VALUE, float(GetAmount()), apply);
}
void AuraEffect::HandleAuraModIncreaseEnergyPercent(AuraApplication const* aurApp, uint8 mode, bool apply) const
@@ -4765,17 +4787,16 @@ void AuraEffect::HandleAuraModIncreaseEnergyPercent(AuraApplication const* aurAp
// return;
UnitMods unitMod = UnitMods(static_cast<uint16>(UNIT_MOD_POWER_START) + PowerType);
- float amount = float(GetAmount());
if (apply)
{
- target->HandleStatModifier(unitMod, TOTAL_PCT, amount, apply);
- target->ModifyPowerPct(PowerType, amount, apply);
+ float amount = float(GetAmount());
+ target->ApplyStatPctModifier(unitMod, TOTAL_PCT, amount);
}
else
{
- target->ModifyPowerPct(PowerType, amount, apply);
- target->HandleStatModifier(unitMod, TOTAL_PCT, amount, apply);
+ float amount = target->GetTotalAuraMultiplierByMiscValue(SPELL_AURA_MOD_INCREASE_ENERGY_PERCENT, GetMiscValue());
+ target->SetStatPctModifier(unitMod, TOTAL_PCT, amount);
}
}
@@ -4788,7 +4809,14 @@ void AuraEffect::HandleAuraModIncreaseHealthPercent(AuraApplication const* aurAp
// Unit will keep hp% after MaxHealth being modified if unit is alive.
float percent = target->GetHealthPct();
- target->HandleStatModifier(UNIT_MOD_HEALTH, TOTAL_PCT, float(GetAmount()), apply);
+
+ if (apply)
+ target->ApplyStatPctModifier(UNIT_MOD_HEALTH, TOTAL_PCT, float(GetAmount()));
+ else
+ {
+ float amount = target->GetTotalAuraMultiplier(SPELL_AURA_MOD_INCREASE_HEALTH_PERCENT);
+ target->SetStatPctModifier(UNIT_MOD_HEALTH, TOTAL_PCT, amount);
+ }
// Xinef: pct was rounded down and could "kill" creature by setting its health to 0 making npc zombie
if (target->IsAlive())
@@ -4803,7 +4831,13 @@ void AuraEffect::HandleAuraIncreaseBaseHealthPercent(AuraApplication const* aurA
Unit* target = aurApp->GetTarget();
- target->HandleStatModifier(UNIT_MOD_HEALTH, BASE_PCT, float(GetAmount()), apply);
+ if (apply)
+ target->ApplyStatPctModifier(UNIT_MOD_HEALTH, BASE_PCT, float(GetAmount()));
+ else
+ {
+ float amount = target->GetTotalAuraMultiplier(SPELL_AURA_MOD_BASE_HEALTH_PCT);
+ target->SetStatPctModifier(UNIT_MOD_HEALTH, BASE_PCT, amount);
+ }
}
/********************************/
@@ -4857,34 +4891,17 @@ void AuraEffect::HandleAuraModRegenInterrupt(AuraApplication const* aurApp, uint
HandleModManaRegen(aurApp, mode, apply);
}
-void AuraEffect::HandleAuraModWeaponCritPercent(AuraApplication const* aurApp, uint8 mode, bool apply) const
+void AuraEffect::HandleAuraModWeaponCritPercent(AuraApplication const* aurApp, uint8 mode, bool /*apply*/) const
{
if (!(mode & (AURA_EFFECT_HANDLE_CHANGE_AMOUNT_MASK | AURA_EFFECT_HANDLE_STAT)))
return;
- Unit* target = aurApp->GetTarget();
+ Player* target = aurApp->GetTarget()->ToPlayer();
- if (!target->IsPlayer())
+ if (!target)
return;
- for (int i = 0; i < MAX_ATTACK; ++i)
- if (Item* pItem = target->ToPlayer()->GetWeaponForAttack(WeaponAttackType(i), true))
- target->ToPlayer()->_ApplyWeaponDependentAuraCritMod(pItem, WeaponAttackType(i), this, apply);
-
- // mods must be applied base at equipped weapon class and subclass comparison
- // with spell->EquippedItemClass and EquippedItemSubClassMask and EquippedItemInventoryTypeMask
- // GetMiscValue() comparison with item generated damage types
-
- if (GetSpellInfo()->EquippedItemClass == -1)
- {
- target->ToPlayer()->HandleBaseModValue(CRIT_PERCENTAGE, FLAT_MOD, float (GetAmount()), apply);
- target->ToPlayer()->HandleBaseModValue(OFFHAND_CRIT_PERCENTAGE, FLAT_MOD, float (GetAmount()), apply);
- target->ToPlayer()->HandleBaseModValue(RANGED_CRIT_PERCENTAGE, FLAT_MOD, float (GetAmount()), apply);
- }
- else
- {
- // done in Player::_ApplyWeaponDependentAuraMods
- }
+ target->UpdateAllWeaponDependentCritAuras();
}
void AuraEffect::HandleModHitChance(AuraApplication const* aurApp, uint8 mode, bool apply) const
@@ -4960,9 +4977,7 @@ void AuraEffect::HandleAuraModCritPct(AuraApplication const* aurApp, uint8 mode,
return;
}
- target->ToPlayer()->HandleBaseModValue(CRIT_PERCENTAGE, FLAT_MOD, float (GetAmount()), apply);
- target->ToPlayer()->HandleBaseModValue(OFFHAND_CRIT_PERCENTAGE, FLAT_MOD, float (GetAmount()), apply);
- target->ToPlayer()->HandleBaseModValue(RANGED_CRIT_PERCENTAGE, FLAT_MOD, float (GetAmount()), apply);
+ target->ToPlayer()->UpdateAllWeaponDependentCritAuras();
// included in Player::UpdateSpellCritChance calculation
target->ToPlayer()->UpdateAllSpellCritChances();
@@ -4986,6 +5001,13 @@ void AuraEffect::HandleModCastingSpeed(AuraApplication const* aurApp, uint8 mode
return;
}
+ int32 spellGroupVal = target->GetHighestExclusiveSameEffectSpellGroupValue(this, GetAuraType());
+ if (std::abs(spellGroupVal) >= std::abs(GetAmount()))
+ return;
+
+ if (spellGroupVal)
+ target->ApplyCastTimePercentMod(float(spellGroupVal), !apply);
+
target->ApplyCastTimePercentMod((float)GetAmount(), apply);
}
@@ -5007,6 +5029,17 @@ void AuraEffect::HandleModCombatSpeedPct(AuraApplication const* aurApp, uint8 mo
return;
Unit* target = aurApp->GetTarget();
+ int32 spellGroupVal = target->GetHighestExclusiveSameEffectSpellGroupValue(this, SPELL_AURA_MELEE_SLOW);
+ if (std::abs(spellGroupVal) >= std::abs(GetAmount()))
+ return;
+
+ if (spellGroupVal)
+ {
+ target->ApplyCastTimePercentMod(float(spellGroupVal), !apply);
+ target->ApplyAttackTimePercentMod(BASE_ATTACK, float(spellGroupVal), !apply);
+ target->ApplyAttackTimePercentMod(OFF_ATTACK, float(spellGroupVal), !apply);
+ target->ApplyAttackTimePercentMod(RANGED_ATTACK, float(spellGroupVal), !apply);
+ }
target->ApplyCastTimePercentMod(float(GetAmount()), apply);
target->ApplyAttackTimePercentMod(BASE_ATTACK, float(GetAmount()), apply);
@@ -5031,7 +5064,15 @@ void AuraEffect::HandleModMeleeSpeedPct(AuraApplication const* aurApp, uint8 mod
return;
Unit* target = aurApp->GetTarget();
+ int32 spellGroupVal = target->GetHighestExclusiveSameEffectSpellGroupValue(this, SPELL_AURA_MOD_MELEE_HASTE);
+ if (std::abs(spellGroupVal) >= std::abs(GetAmount()))
+ return;
+ if (spellGroupVal)
+ {
+ target->ApplyAttackTimePercentMod(BASE_ATTACK, float(spellGroupVal), !apply);
+ target->ApplyAttackTimePercentMod(OFF_ATTACK, float(spellGroupVal), !apply);
+ }
target->ApplyAttackTimePercentMod(BASE_ATTACK, float(GetAmount()), apply);
target->ApplyAttackTimePercentMod(OFF_ATTACK, float(GetAmount()), apply);
}
@@ -5073,7 +5114,7 @@ void AuraEffect::HandleModRating(AuraApplication const* aurApp, uint8 mode, bool
if (!target->IsPlayer())
return;
- for (uint32 rating = 0; rating < MAX_COMBAT_RATING; ++rating)
+ for (uint8 rating = 0; rating < MAX_COMBAT_RATING; ++rating)
if (GetMiscValue() & (1 << rating))
target->ToPlayer()->ApplyRatingMod(CombatRating(rating), GetAmount(), apply);
}
@@ -5089,7 +5130,7 @@ void AuraEffect::HandleModRatingFromStat(AuraApplication const* aurApp, uint8 mo
return;
// Just recalculate ratings
- for (uint32 rating = 0; rating < MAX_COMBAT_RATING; ++rating)
+ for (uint8 rating = 0; rating < MAX_COMBAT_RATING; ++rating)
if (GetMiscValue() & (1 << rating))
target->ToPlayer()->ApplyRatingMod(CombatRating(rating), 0, apply);
}
@@ -5105,7 +5146,7 @@ void AuraEffect::HandleAuraModAttackPower(AuraApplication const* aurApp, uint8 m
Unit* target = aurApp->GetTarget();
- target->HandleStatModifier(UNIT_MOD_ATTACK_POWER, TOTAL_VALUE, float(GetAmount()), apply);
+ target->HandleStatFlatModifier(UNIT_MOD_ATTACK_POWER, TOTAL_VALUE, float(GetAmount()), apply);
}
void AuraEffect::HandleAuraModRangedAttackPower(AuraApplication const* aurApp, uint8 mode, bool apply) const
@@ -5118,7 +5159,7 @@ void AuraEffect::HandleAuraModRangedAttackPower(AuraApplication const* aurApp, u
if ((target->getClassMask() & CLASSMASK_WAND_USERS) != 0)
return;
- target->HandleStatModifier(UNIT_MOD_ATTACK_POWER_RANGED, TOTAL_VALUE, float(GetAmount()), apply);
+ target->HandleStatFlatModifier(UNIT_MOD_ATTACK_POWER_RANGED, TOTAL_VALUE, float(GetAmount()), apply);
}
void AuraEffect::HandleAuraModAttackPowerPercent(AuraApplication const* aurApp, uint8 mode, bool apply) const
@@ -5129,7 +5170,13 @@ void AuraEffect::HandleAuraModAttackPowerPercent(AuraApplication const* aurApp,
Unit* target = aurApp->GetTarget();
//UNIT_FIELD_ATTACK_POWER_MULTIPLIER = multiplier - 1
- target->HandleStatModifier(UNIT_MOD_ATTACK_POWER, TOTAL_PCT, float(GetAmount()), apply);
+ if (apply)
+ target->ApplyStatPctModifier(UNIT_MOD_ATTACK_POWER, TOTAL_PCT, float(GetAmount()));
+ else
+ {
+ float amount = target->GetTotalAuraMultiplier(SPELL_AURA_MOD_ATTACK_POWER_PCT);
+ target->SetStatPctModifier(UNIT_MOD_ATTACK_POWER, TOTAL_PCT, amount);
+ }
}
void AuraEffect::HandleAuraModRangedAttackPowerPercent(AuraApplication const* aurApp, uint8 mode, bool apply) const
@@ -5143,7 +5190,13 @@ void AuraEffect::HandleAuraModRangedAttackPowerPercent(AuraApplication const* au
return;
//UNIT_FIELD_RANGED_ATTACK_POWER_MULTIPLIER = multiplier - 1
- target->HandleStatModifier(UNIT_MOD_ATTACK_POWER_RANGED, TOTAL_PCT, float(GetAmount()), apply);
+ if (apply)
+ target->ApplyStatPctModifier(UNIT_MOD_ATTACK_POWER_RANGED, TOTAL_PCT, float(GetAmount()));
+ else
+ {
+ float amount = target->GetTotalAuraMultiplier(SPELL_AURA_MOD_RANGED_ATTACK_POWER_PCT);
+ target->SetStatPctModifier(UNIT_MOD_ATTACK_POWER_RANGED, TOTAL_PCT, amount);
+ }
}
void AuraEffect::HandleAuraModRangedAttackPowerOfStatPercent(AuraApplication const* aurApp, uint8 mode, bool /*apply*/) const
@@ -5184,141 +5237,89 @@ void AuraEffect::HandleModDamageDone(AuraApplication const* aurApp, uint8 mode,
Unit* target = aurApp->GetTarget();
- // apply item specific bonuses for already equipped weapon
+ if ((GetMiscValue() & SPELL_SCHOOL_MASK_NORMAL) != 0)
+ target->UpdateAllDamageDoneMods();
+
+ // Magic damage modifiers implemented in Unit::SpellBaseDamageBonus
+ // This information for client side use only
if (target->IsPlayer())
{
- for (int i = 0; i < MAX_ATTACK; ++i)
- if (Item* pItem = target->ToPlayer()->GetWeaponForAttack(WeaponAttackType(i), true))
- target->ToPlayer()->_ApplyWeaponDependentAuraDamageMod(pItem, WeaponAttackType(i), this, apply);
- }
- // GetMiscValue() is bitmask of spell schools
- // 1 (0-bit) - normal school damage (SPELL_SCHOOL_MASK_NORMAL)
- // 126 - full bitmask all magic damages (SPELL_SCHOOL_MASK_MAGIC) including wands
- // 127 - full bitmask any damages
- //
- // mods must be applied base at equipped weapon class and subclass comparison
- // with spell->EquippedItemClass and EquippedItemSubClassMask and EquippedItemInventoryTypeMask
- // GetMiscValue() comparison with item generated damage types
+ uint16 baseField = GetAmount() >= 0 ? PLAYER_FIELD_MOD_DAMAGE_DONE_POS : PLAYER_FIELD_MOD_DAMAGE_DONE_NEG;
+ for (uint16 i = 0; i < MAX_SPELL_SCHOOL; ++i)
+ if (GetMiscValue() & (1 << i))
+ target->ApplyModUInt32Value(baseField + i, GetAmount(), apply);
- if ((GetMiscValue() & SPELL_SCHOOL_MASK_NORMAL) != 0 && sScriptMgr->CanModAuraEffectDamageDone(this, target, aurApp, mode, apply))
- {
- // apply generic physical damage bonuses including wand case
- if (GetSpellInfo()->EquippedItemClass == -1 || !target->IsPlayer())
- {
- target->HandleStatModifier(UNIT_MOD_DAMAGE_MAINHAND, TOTAL_VALUE, float(GetAmount()), apply);
- target->HandleStatModifier(UNIT_MOD_DAMAGE_OFFHAND, TOTAL_VALUE, float(GetAmount()), apply);
- target->HandleStatModifier(UNIT_MOD_DAMAGE_RANGED, TOTAL_VALUE, float(GetAmount()), apply);
-
- if (target->IsPlayer())
- {
- if (GetAmount() > 0)
- target->ApplyModInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_POS, GetAmount(), apply);
- else
- target->ApplyModInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_NEG, GetAmount(), apply);
- }
- }
- else
- {
- // done in Player::_ApplyWeaponDependentAuraMods
- }
+ if (Guardian* pet = target->ToPlayer()->GetGuardianPet())
+ pet->UpdateAttackPowerAndDamage();
}
+}
- // Skip non magic case for Speedup
- if ((GetMiscValue() & SPELL_SCHOOL_MASK_MAGIC) == 0)
+void AuraEffect::HandleModDamagePercentDone(AuraApplication const* aurApp, uint8 mode, bool /*apply*/) const
+{
+ if (!(mode & (AURA_EFFECT_HANDLE_CHANGE_AMOUNT_MASK | AURA_EFFECT_HANDLE_STAT)))
return;
- if (GetSpellInfo()->EquippedItemClass != -1 || GetSpellInfo()->EquippedItemInventoryTypeMask != 0)
- {
- // wand magic case (skip generic to all item spell bonuses)
- // done in Player::_ApplyWeaponDependentAuraMods
-
- // Skip item specific requirements for not wand magic damage
+ Unit* target = aurApp->GetTarget();
+ if (!target)
return;
- }
- // Magic damage modifiers implemented in Unit::SpellDamageBonus
- // This information for client side use only
+ if ((GetMiscValue() & SPELL_SCHOOL_MASK_NORMAL))
+ target->UpdateAllDamagePctDoneMods();
+
if (target->IsPlayer())
{
- if (GetAmount() > 0)
+ for (uint8 i = 0; i < MAX_SPELL_SCHOOL; ++i)
{
- for (uint32 i = SPELL_SCHOOL_HOLY; i < MAX_SPELL_SCHOOL; i++)
+ if (GetMiscValue() & (1 << i))
{
- if ((GetMiscValue() & (1 << i)) != 0)
- target->ApplyModInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_POS + i, GetAmount(), apply);
+ // only aura type modifying PLAYER_FIELD_MOD_DAMAGE_DONE_PCT
+ float amount = target->GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_DAMAGE_PERCENT_DONE, 1 << i);
+ target->SetFloatValue(PLAYER_FIELD_MOD_DAMAGE_DONE_PCT + i, amount);
}
}
- else
- {
- for (uint32 i = SPELL_SCHOOL_HOLY; i < MAX_SPELL_SCHOOL; i++)
- {
- if ((GetMiscValue() & (1 << i)) != 0)
- target->ApplyModInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_NEG + i, GetAmount(), apply);
- }
- }
- if (Guardian* pet = target->ToPlayer()->GetGuardianPet())
- pet->UpdateAttackPowerAndDamage();
}
}
-void AuraEffect::HandleModDamagePercentDone(AuraApplication const* aurApp, uint8 mode, bool apply) const
+void AuraEffect::HandleModOffhandDamagePercent(AuraApplication const* aurApp, uint8 mode, bool /*apply*/) const
{
if (!(mode & (AURA_EFFECT_HANDLE_CHANGE_AMOUNT_MASK | AURA_EFFECT_HANDLE_STAT)))
return;
Unit* target = aurApp->GetTarget();
- if (!target)
- return;
-
- if (!sScriptMgr->CanModAuraEffectModDamagePercentDone(this, target, aurApp, mode, apply))
- return;
- if (target->IsPlayer())
- {
- for (int i = 0; i < MAX_ATTACK; ++i)
- if (Item* item = target->ToPlayer()->GetWeaponForAttack(WeaponAttackType(i), false))
- target->ToPlayer()->_ApplyWeaponDependentAuraDamageMod(item, WeaponAttackType(i), this, apply);
- }
-
- if ((GetMiscValue() & SPELL_SCHOOL_MASK_NORMAL) && (GetSpellInfo()->EquippedItemClass == -1 || !target->IsPlayer()))
- {
- target->HandleStatModifier(UNIT_MOD_DAMAGE_MAINHAND, TOTAL_PCT, float(GetAmount()), apply);
- target->HandleStatModifier(UNIT_MOD_DAMAGE_OFFHAND, TOTAL_PCT, float(GetAmount()), apply);
- target->HandleStatModifier(UNIT_MOD_DAMAGE_RANGED, TOTAL_PCT, float(GetAmount()), apply);
-
- if (target->IsPlayer())
- target->ToPlayer()->ApplyPercentModFloatValue(PLAYER_FIELD_MOD_DAMAGE_DONE_PCT, float (GetAmount()), apply);
- }
- else
- {
- // done in Player::_ApplyWeaponDependentAuraMods for SPELL_SCHOOL_MASK_NORMAL && EquippedItemClass != -1 and also for wand case
- }
+ // also handles spell group stacks
+ target->UpdateDamagePctDoneMods(OFF_ATTACK);
}
-void AuraEffect::HandleModOffhandDamagePercent(AuraApplication const* aurApp, uint8 mode, bool apply) const
+void AuraEffect::HandleShieldBlockValue(AuraApplication const* aurApp, uint8 mode, bool apply) const
{
if (!(mode & (AURA_EFFECT_HANDLE_CHANGE_AMOUNT_MASK | AURA_EFFECT_HANDLE_STAT)))
return;
- Unit* target = aurApp->GetTarget();
+ Player* target = aurApp->GetTarget()->ToPlayer();
+ if (!target)
+ return;
- target->HandleStatModifier(UNIT_MOD_DAMAGE_OFFHAND, TOTAL_PCT, float(GetAmount()), apply);
+ target->HandleBaseModFlatValue(SHIELD_BLOCK_VALUE, float(GetAmount()), apply);
}
-void AuraEffect::HandleShieldBlockValue(AuraApplication const* aurApp, uint8 mode, bool apply) const
+void AuraEffect::HandleShieldBlockValuePercent(AuraApplication const* aurApp, uint8 mode, bool apply) const
{
if (!(mode & (AURA_EFFECT_HANDLE_CHANGE_AMOUNT_MASK | AURA_EFFECT_HANDLE_STAT)))
return;
- Unit* target = aurApp->GetTarget();
-
- BaseModType modType = FLAT_MOD;
- if (GetAuraType() == SPELL_AURA_MOD_SHIELD_BLOCKVALUE_PCT)
- modType = PCT_MOD;
+ Player* target = aurApp->GetTarget()->ToPlayer();
+ if (!target)
+ return;
- if (target->IsPlayer())
- target->ToPlayer()->HandleBaseModValue(SHIELD_BLOCK_VALUE, modType, float(GetAmount()), apply);
+ if (apply)
+ target->ApplyBaseModPctValue(SHIELD_BLOCK_VALUE, float(GetAmount()));
+ else
+ {
+ float amount = target->GetTotalAuraMultiplier(SPELL_AURA_MOD_SHIELD_BLOCKVALUE_PCT);
+ target->SetBaseModPctValue(SHIELD_BLOCK_VALUE, amount);
+ }
}
/********************************/
@@ -6350,7 +6351,7 @@ void AuraEffect::HandlePeriodicTriggerSpellAuraTick(Unit* target, Unit* caster)
case 27808:
if (caster)
{
- caster->CastCustomSpell(29879, SPELLVALUE_BASE_POINT0, int32(target->CountPctFromMaxHealth(21)), target, true, nullptr, this);
+ caster->CastSpell(target, 29879, true, nullptr, this);
if (GetTickNumber() == 1)
caster->CastSpell(target, 27808, true);
}
diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.h b/src/server/game/Spells/Auras/SpellAuraEffects.h
index 73640626bc..8349c83fc2 100644
--- a/src/server/game/Spells/Auras/SpellAuraEffects.h
+++ b/src/server/game/Spells/Auras/SpellAuraEffects.h
@@ -112,8 +112,6 @@ public:
float GetPctMods() const { return m_pctMods; }
void SetPctMods(float pctMods) { m_pctMods = pctMods; }
- // xinef: stacking
- uint32 GetAuraGroup() const { return m_auraGroup; }
int32 GetOldAmount() const { return m_oldAmount; }
void SetOldAmount(int32 amount) { m_oldAmount = amount; }
void SetEnabled(bool enabled) { m_isAuraEnabled = enabled; }
@@ -131,8 +129,6 @@ private:
float m_critChance;
float m_pctMods;
- // xinef: stacking
- uint32 m_auraGroup;
int32 m_oldAmount;
bool m_isAuraEnabled;
// xinef: channel information for channel triggering
@@ -299,6 +295,7 @@ public:
void HandleModDamagePercentDone(AuraApplication const* aurApp, uint8 mode, bool apply) const;
void HandleModOffhandDamagePercent(AuraApplication const* aurApp, uint8 mode, bool apply) const;
void HandleShieldBlockValue(AuraApplication const* aurApp, uint8 mode, bool apply) const;
+ void HandleShieldBlockValuePercent(AuraApplication const* aurApp, uint8 mode, bool apply) const;
// power cost
void HandleModPowerCostPCT(AuraApplication const* aurApp, uint8 mode, bool apply) const;
void HandleModPowerCost(AuraApplication const* aurApp, uint8 mode, bool apply) const;
diff --git a/src/server/game/Spells/Auras/SpellAuras.cpp b/src/server/game/Spells/Auras/SpellAuras.cpp
index 7d6260e24e..0b3b35d59e 100644
--- a/src/server/game/Spells/Auras/SpellAuras.cpp
+++ b/src/server/game/Spells/Auras/SpellAuras.cpp
@@ -181,67 +181,6 @@ void AuraApplication::_HandleEffect(uint8 effIndex, bool apply)
// Remove all triggered by aura spells vs unlimited duration
aurEff->CleanupTriggeredSpells(GetTarget());
}
-
- // Stacking!
- if (uint32 groupId = aurEff->GetAuraGroup())
- {
- SpellGroupStackFlags sFlag = sSpellMgr->GetGroupStackFlags(groupId);
- if (!aurEff->IsPeriodic() && (sFlag & SPELL_GROUP_STACK_FLAG_EFFECT_EXCLUSIVE))
- {
- AuraApplication* strongestApp = apply ? this : nullptr;
- AuraEffect* strongestEff = apply ? aurEff : nullptr;
- int32 amount = apply ? std::abs(aurEff->GetAmount()) : 0;
- Unit* target = GetTarget();
- Unit::AuraEffectList const& auraList = target->GetAuraEffectsByType(aurEff->GetAuraType());
- for (Unit::AuraEffectList::const_iterator iter = auraList.begin(); iter != auraList.end(); ++iter)
- {
- if ((*iter)->GetAuraGroup() != groupId || (*iter) == strongestEff || (*iter)->GetBase()->IsRemoved())
- continue;
-
- // xinef: skip different misc values
- if (aurEff->GetAuraType() != SPELL_AURA_230 /*SPELL_AURA_MOD_INCREASE_HEALTH_2*/ && aurEff->GetAuraType() != SPELL_AURA_MOD_CASTING_SPEED_NOT_STACK &&
- aurEff->GetMiscValue() != (*iter)->GetMiscValue())
- continue;
-
- // xinef: should not happen
- AuraApplication* aurApp = (*iter)->GetBase()->GetApplicationOfTarget(target->GetGUID());
- if (!aurApp)
- continue;
-
- if (amount < std::abs((*iter)->GetForcedAmount()))
- {
- // xinef: if we have strongest aura and it is active, turn it off
- // xinef: otherwise just save new aura;
- if (strongestApp && strongestEff && strongestApp->IsActive(strongestEff->GetEffIndex()))
- {
- strongestEff->HandleEffect(strongestApp, AURA_EFFECT_HANDLE_CHANGE_AMOUNT, false);
- if (!strongestEff->GetSpellInfo()->HasAreaAuraEffect())
- strongestEff->SetEnabled(false);
- strongestApp->SetDisableMask(strongestEff->GetEffIndex());
- }
- strongestApp = aurApp;
- strongestEff = (*iter);
- amount = std::abs((*iter)->GetAmount());
- }
- // xinef: itered aura is weaker, deactivate if active
- else if (aurApp->IsActive((*iter)->GetEffIndex()))
- {
- (*iter)->HandleEffect(aurApp, AURA_EFFECT_HANDLE_CHANGE_AMOUNT, false);
- if (!(*iter)->GetSpellInfo()->HasAreaAuraEffect())
- (*iter)->SetEnabled(false);
- aurApp->SetDisableMask((*iter)->GetEffIndex());
- }
- }
-
- // xinef: if we have new strongest aura, and it is not active
- if (strongestApp && strongestEff && !strongestApp->IsActive(strongestEff->GetEffIndex()))
- {
- strongestApp->RemoveDisableMask(strongestEff->GetEffIndex());
- strongestEff->SetEnabled(true);
- strongestEff->HandleEffect(strongestApp, AURA_EFFECT_HANDLE_CHANGE_AMOUNT, true);
- }
- }
- }
SetNeedClientUpdate();
}
@@ -661,6 +600,9 @@ void Aura::UpdateTargetMap(Unit* caster, bool apply)
if (!itr->second || itr->first->IsImmunedToSpell(GetSpellInfo()) || !CanBeAppliedOn(itr->first))
addUnit = false;
+ if (addUnit && !itr->first->IsHighestExclusiveAura(this, true))
+ addUnit = false;
+
if (addUnit)
{
// persistent area aura does not hit flying targets
@@ -684,7 +626,7 @@ void Aura::UpdateTargetMap(Unit* caster, bool apply)
for (Unit::AuraApplicationMap::iterator iter = itr->first->GetAppliedAuras().begin(); iter != itr->first->GetAppliedAuras().end(); ++iter)
{
Aura const* aura = iter->second->GetBase();
- if (!CanStackWith(aura, false))
+ if (!CanStackWith(aura))
{
addUnit = false;
break;
@@ -1069,6 +1011,16 @@ void Aura::RefreshSpellMods()
player->RestoreAllSpellMods(0, this);
}
+bool Aura::HasMoreThanOneEffectForType(AuraType auraType) const
+{
+ uint32 count = 0;
+ for (SpellEffectInfo const& spellEffectInfo : GetSpellInfo()->GetEffects())
+ if (HasEffect(spellEffectInfo.EffectIndex) && spellEffectInfo.ApplyAuraName == auraType)
+ ++count;
+
+ return count > 1;
+}
+
bool Aura::IsArea() const
{
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
@@ -1589,7 +1541,7 @@ void Aura::HandleAuraSpecificMods(AuraApplication const* aurApp, Unit* caster, b
// Alchemy: Mixology
if (caster && caster->HasAura(53042) && caster->IsPlayer() && !caster->ToPlayer()->GetSession()->PlayerLoading())
{
- if (sSpellMgr->GetSpellGroup(GetId()) == 1) /*Elixirs*/
+ if (sSpellMgr->IsSpellMemberOfSpellGroup(GetId(), SPELL_GROUP_ELIXIR_BATTLE) || sSpellMgr->IsSpellMemberOfSpellGroup(GetId(), SPELL_GROUP_ELIXIR_GUARDIAN))
{
if (caster->HasSpell(GetSpellInfo()->Effects[EFFECT_0].TriggerSpell))
{
@@ -2018,7 +1970,7 @@ bool Aura::IsAuraStronger(Aura const* newAura) const
return false;
}
-bool Aura::CanStackWith(Aura const* existingAura, bool remove) const
+bool Aura::CanStackWith(Aura const* existingAura) const
{
// Can stack with self
if (this == existingAura)
@@ -2056,47 +2008,19 @@ bool Aura::CanStackWith(Aura const* existingAura, bool remove) const
return false;
// check spell group stack rules
- // xinef: this assures us that both spells are in same group!
- SpellGroupStackFlags stackFlags = sSpellMgr->CheckSpellGroupStackRules(m_spellInfo, existingSpellInfo, remove, IsArea());
- if (stackFlags)
+ switch (sSpellMgr->CheckSpellGroupStackRules(m_spellInfo, existingSpellInfo))
{
- // xinef: same caster rule is bounded by spellfamily
- if (sameCaster && m_spellInfo->SpellFamilyName == existingSpellInfo->SpellFamilyName &&
- (stackFlags & SPELL_GROUP_STACK_FLAG_NOT_SAME_CASTER))
+ case SPELL_GROUP_STACK_RULE_EXCLUSIVE:
+ case SPELL_GROUP_STACK_RULE_EXCLUSIVE_HIGHEST: // if it reaches this point, existing aura is lower/equal
return false;
-
- // xinef: normal exclusive stacking, remove if auras are equal by effects
- if (stackFlags & SPELL_GROUP_STACK_FLAG_EXCLUSIVE)
- {
- if (GetSpellInfo()->IsAuraEffectEqual(existingSpellInfo) || GetSpellInfo()->IsRankOf(existingSpellInfo))
- {
- if (remove)
- return IsAuraStronger(existingAura);
- else
- return existingAura->IsAuraStronger(this);
- }
- }
-
- // xinef: check priority before effect mask
- SpellGroupSpecialFlags thisAuraFlag = sSpellMgr->GetSpellGroupSpecialFlags(GetId());
- SpellGroupSpecialFlags existingAuraFlag = sSpellMgr->GetSpellGroupSpecialFlags(existingSpellInfo->Id);
- if (thisAuraFlag >= SPELL_GROUP_SPECIAL_FLAG_PRIORITY1 && thisAuraFlag <= SPELL_GROUP_SPECIAL_FLAG_PRIORITY4 &&
- existingAuraFlag >= SPELL_GROUP_SPECIAL_FLAG_PRIORITY1 && existingAuraFlag <= SPELL_GROUP_SPECIAL_FLAG_PRIORITY4)
- {
- if (thisAuraFlag < existingAuraFlag)
- {
+ case SPELL_GROUP_STACK_RULE_EXCLUSIVE_FROM_SAME_CASTER:
+ if (sameCaster)
return false;
- }
- }
-
- // xinef: forced strongest aura in group by flag
- if (stackFlags & SPELL_GROUP_STACK_FLAG_FORCED_STRONGEST)
- return !remove;
- if (stackFlags & SPELL_GROUP_STACK_FLAG_FORCED_WEAKEST)
- return remove;
-
- // xinef: forced return, handle all cases using available flags!
- return !(stackFlags & SPELL_GROUP_STACK_FLAG_NEVER_STACK);
+ break;
+ case SPELL_GROUP_STACK_RULE_DEFAULT:
+ case SPELL_GROUP_STACK_RULE_EXCLUSIVE_SAME_EFFECT:
+ default:
+ break;
}
if (m_spellInfo->SpellFamilyName != existingSpellInfo->SpellFamilyName)
diff --git a/src/server/game/Spells/Auras/SpellAuras.h b/src/server/game/Spells/Auras/SpellAuras.h
index 7b7ab603ec..8731299f8b 100644
--- a/src/server/game/Spells/Auras/SpellAuras.h
+++ b/src/server/game/Spells/Auras/SpellAuras.h
@@ -153,6 +153,7 @@ public:
uint8 GetCasterLevel() const { return m_casterLevel; }
+ bool HasMoreThanOneEffectForType(AuraType auraType) const;
bool IsArea() const;
bool IsPassive() const;
bool IsDeathPersistent() const;
@@ -188,7 +189,7 @@ public:
void HandleAuraSpecificMods(AuraApplication const* aurApp, Unit* caster, bool apply, bool onReapply);
bool CanBeAppliedOn(Unit* target);
bool CheckAreaTarget(Unit* target);
- bool CanStackWith(Aura const* checkAura, bool remove) const;
+ bool CanStackWith(Aura const* existingAura) const;
bool IsAuraStronger(Aura const* newAura) const;
// Proc system
diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp
index aad6a4f2d6..608b9614b8 100644
--- a/src/server/game/Spells/Spell.cpp
+++ b/src/server/game/Spells/Spell.cpp
@@ -19,7 +19,6 @@
#include "ArenaSpectator.h"
#include "BattlefieldMgr.h"
#include "Battleground.h"
-#include "BattlegroundIC.h"
#include "CharmInfo.h"
#include "CellImpl.h"
#include "Common.h"
@@ -33,7 +32,6 @@
#include "InstanceScript.h"
#include "Log.h"
#include "LootMgr.h"
-#include "MapMgr.h"
#include "ObjectAccessor.h"
#include "ObjectMgr.h"
#include "Opcodes.h"
@@ -41,6 +39,7 @@
#include "Player.h"
#include "ScriptMgr.h"
#include "SharedDefines.h"
+#include "SpellAuraDefines.h"
#include "SpellAuraEffects.h"
#include "SpellInfo.h"
#include "SpellMgr.h"
@@ -643,6 +642,8 @@ Spell::Spell(Unit* caster, SpellInfo const* info, TriggerCastFlags triggerFlags,
gameObjTarget = nullptr;
destTarget = nullptr;
damage = 0;
+ m_reflectionTarget = nullptr;
+ m_reflectionTargetGuid.Clear();
effectHandleMode = SPELL_EFFECT_HANDLE_LAUNCH;
m_diminishLevel = DIMINISHING_LEVEL_1;
m_diminishGroup = DIMINISHING_NONE;
@@ -1238,11 +1239,7 @@ void Spell::SelectImplicitConeTargets(SpellEffIndex effIndex, SpellImplicitTarge
// Other special target selection goes here
if (uint32 maxTargets = m_spellValue->MaxAffectedTargets)
{
- Unit::AuraEffectList const& Auras = m_caster->GetAuraEffectsByType(SPELL_AURA_MOD_MAX_AFFECTED_TARGETS);
- for (Unit::AuraEffectList::const_iterator j = Auras.begin(); j != Auras.end(); ++j)
- if ((*j)->IsAffectedOnSpell(m_spellInfo))
- maxTargets += (*j)->GetAmount();
-
+ maxTargets += m_caster->GetTotalAuraModifierByAffectMask(SPELL_AURA_MOD_MAX_AFFECTED_TARGETS, m_spellInfo);
Acore::Containers::RandomResize(targets, maxTargets);
}
@@ -1325,11 +1322,7 @@ void Spell::SelectImplicitAreaTargets(SpellEffIndex effIndex, SpellImplicitTarge
// Other special target selection goes here
if (uint32 maxTargets = m_spellValue->MaxAffectedTargets)
{
- Unit::AuraEffectList const& Auras = m_caster->GetAuraEffectsByType(SPELL_AURA_MOD_MAX_AFFECTED_TARGETS);
- for (Unit::AuraEffectList::const_iterator j = Auras.begin(); j != Auras.end(); ++j)
- if ((*j)->IsAffectedOnSpell(m_spellInfo))
- maxTargets += (*j)->GetAmount();
-
+ maxTargets += m_caster->GetTotalAuraModifierByAffectMask(SPELL_AURA_MOD_MAX_AFFECTED_TARGETS, m_spellInfo);
Acore::Containers::RandomResize(targets, maxTargets);
}
@@ -2591,6 +2584,7 @@ void Spell::DoAllEffectOnTarget(TargetInfo* target)
//Spells with this flag cannot trigger if effect is casted on self
bool canEffectTrigger = !m_spellInfo->HasAttribute(SPELL_ATTR3_SUPPRESS_CASTER_PROCS) && unitTarget->CanProc() && (CanExecuteTriggersOnHit(mask) || missInfo == SPELL_MISS_IMMUNE2);
bool reflectedSpell = missInfo == SPELL_MISS_REFLECT;
+ Unit* reflectionSource = nullptr;
Unit* spellHitTarget = nullptr;
if (missInfo == SPELL_MISS_NONE) // In case spell hit target, do all effect on that target
@@ -2602,6 +2596,7 @@ void Spell::DoAllEffectOnTarget(TargetInfo* target)
{
spellHitTarget = m_caster;
unitTarget = m_caster;
+ reflectionSource = effectUnit;
if (m_caster->IsCreature())
m_caster->ToCreature()->LowerPlayerDamageReq(target->damage);
}
@@ -2609,7 +2604,24 @@ void Spell::DoAllEffectOnTarget(TargetInfo* target)
if (spellHitTarget)
{
+ if (reflectionSource)
+ {
+ m_reflectionTarget = reflectionSource;
+ m_reflectionTargetGuid = reflectionSource->GetGUID();
+ m_reflectionTargetPosition.Relocate(reflectionSource);
+ }
+ else
+ {
+ m_reflectionTarget = nullptr;
+ m_reflectionTargetGuid.Clear();
+ m_reflectionTargetPosition = Position();
+ }
+
SpellMissInfo missInfo2 = DoSpellHitOnUnit(spellHitTarget, mask, target->scaleAura);
+
+ m_reflectionTarget = nullptr;
+ m_reflectionTargetGuid.Clear();
+ m_reflectionTargetPosition = Position();
if (missInfo2 != SPELL_MISS_NONE)
{
if (missInfo2 != SPELL_MISS_MISS)
@@ -6055,6 +6067,8 @@ SpellCastResult Spell::CheckCast(bool strict)
}
}
+ uint8 approximateAuraEffectMask = 0;
+ uint8 nonAuraEffectMask = 0;
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
{
// for effects of spells that have only one target
@@ -6540,6 +6554,11 @@ SpellCastResult Spell::CheckCast(bool strict)
default:
break;
}
+
+ if (m_spellInfo->Effects[i].IsAura())
+ approximateAuraEffectMask |= 1 << i;
+ else if (m_spellInfo->Effects[i].IsEffect())
+ nonAuraEffectMask |= 1 << i;
}
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
@@ -6588,8 +6607,13 @@ SpellCastResult Spell::CheckCast(bool strict)
if (target->IsCreature() && target->ToCreature()->IsVehicle())
return SPELL_FAILED_BAD_IMPLICIT_TARGETS;
+ // Allow SPELL_AURA_MOD_POSSESS to work on mounted players,
+ // but keep the old restriction for everything else.
if (target->IsMounted())
- return SPELL_FAILED_CANT_BE_CHARMED;
+ {
+ if (!(target->IsPlayer() && m_spellInfo->Effects[i].ApplyAuraName == SPELL_AURA_MOD_POSSESS))
+ return SPELL_FAILED_CANT_BE_CHARMED;
+ }
if (target->GetCharmerGUID())
return SPELL_FAILED_CHARMED;
@@ -6697,6 +6721,13 @@ SpellCastResult Spell::CheckCast(bool strict)
default:
break;
}
+
+ // check if target already has the same type, but more powerful aura
+ if (!nonAuraEffectMask && (approximateAuraEffectMask & (1 << i)) && !m_spellInfo->IsTargetingArea())
+ if (Unit* target = m_targets.GetUnitTarget())
+ if (!target->IsHighestExclusiveAuraEffect(m_spellInfo, AuraType(m_spellInfo->Effects[i].ApplyAuraName),
+ m_spellInfo->Effects[i].CalcValue(m_caster, &m_spellValue->EffectBasePoints[i]), approximateAuraEffectMask, false))
+ return SPELL_FAILED_AURA_BOUNCED;
}
// check trade slot case (last, for allow catch any another cast problems)
@@ -6947,27 +6978,36 @@ bool Spell::CanAutoCast(Unit* target)
{
ObjectGuid targetguid = target->GetGUID();
- for (uint32 j = 0; j < MAX_SPELL_EFFECTS; ++j)
+ for (SpellEffectInfo const& spellEffectInfo : m_spellInfo->GetEffects())
{
- if (m_spellInfo->Effects[j].Effect == SPELL_EFFECT_APPLY_AURA)
+ if (!spellEffectInfo.IsAura())
+ continue;
+
+ AuraType const& auraType = spellEffectInfo.ApplyAuraName;
+ Unit::AuraEffectList const& auras = target->GetAuraEffectsByType(auraType);
+ for (Unit::AuraEffectList::const_iterator auraIt = auras.begin(); auraIt != auras.end(); ++auraIt)
{
- if (m_spellInfo->StackAmount <= 1)
+ if (GetSpellInfo()->Id == (*auraIt)->GetSpellInfo()->Id)
+ return false;
+
+ switch (sSpellMgr->CheckSpellGroupStackRules(GetSpellInfo(), (*auraIt)->GetSpellInfo()))
{
- if (target->HasAuraEffect(m_spellInfo->Id, j))
+ case SPELL_GROUP_STACK_RULE_EXCLUSIVE:
return false;
- }
- else
- {
- if (AuraEffect* aureff = target->GetAuraEffect(m_spellInfo->Id, j))
- if (aureff->GetBase()->GetStackAmount() >= m_spellInfo->StackAmount)
+ case SPELL_GROUP_STACK_RULE_EXCLUSIVE_FROM_SAME_CASTER:
+ if (GetCaster() == (*auraIt)->GetCaster())
return false;
+ break;
+ case SPELL_GROUP_STACK_RULE_EXCLUSIVE_SAME_EFFECT: // this one has further checks, but i don't think they're necessary for autocast logic
+ case SPELL_GROUP_STACK_RULE_EXCLUSIVE_HIGHEST:
+ if (abs(spellEffectInfo.BasePoints) <= abs((*auraIt)->GetAmount()))
+ return false;
+ break;
+ case SPELL_GROUP_STACK_RULE_DEFAULT:
+ default:
+ break;
}
}
- else if (m_spellInfo->Effects[j].IsAreaAuraEffect())
- {
- if (target->HasAuraEffect(m_spellInfo->Id, j))
- return false;
- }
}
SpellCastResult result = CheckPetCast(target);
@@ -7884,7 +7924,7 @@ bool Spell::CheckEffectTarget(Unit const* target, uint32 eff) const
case SPELL_AURA_AOE_CHARM:
if (target->IsCreature() && target->IsVehicle())
return false;
- if (target->IsMounted())
+ if (target->IsMounted() && m_spellInfo->Effects[eff].ApplyAuraName != SPELL_AURA_MOD_POSSESS)
return false;
if (target->GetCharmerGUID())
return false;
diff --git a/src/server/game/Spells/Spell.h b/src/server/game/Spells/Spell.h
index 331d48997a..0f8cde5d37 100644
--- a/src/server/game/Spells/Spell.h
+++ b/src/server/game/Spells/Spell.h
@@ -666,6 +666,9 @@ public:
WorldLocation* destTarget;
int32 damage;
SpellEffectHandleMode effectHandleMode;
+ Unit* m_reflectionTarget;
+ ObjectGuid m_reflectionTargetGuid;
+ Position m_reflectionTargetPosition;
// used in effects handlers
Aura* m_spellAura;
diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp
index 8bb4c793ef..d725b682ec 100644
--- a/src/server/game/Spells/SpellEffects.cpp
+++ b/src/server/game/Spells/SpellEffects.cpp
@@ -1952,13 +1952,10 @@ void Spell::EffectEnergize(SpellEffIndex effIndex)
Unit::AuraApplicationMap& Auras = unitTarget->GetAppliedAuras();
for (Unit::AuraApplicationMap::iterator itr = Auras.begin(); itr != Auras.end(); ++itr)
{
- SpellGroupSpecialFlags sFlag = sSpellMgr->GetSpellGroupSpecialFlags(itr->second->GetBase()->GetId());
- if (!guardianFound)
- if (sFlag & SPELL_GROUP_SPECIAL_FLAG_ELIXIR_GUARDIAN)
- guardianFound = true;
- if (!battleFound)
- if (sFlag & SPELL_GROUP_SPECIAL_FLAG_ELIXIR_BATTLE)
- battleFound = true;
+ if (!guardianFound && sSpellMgr->IsSpellMemberOfSpellGroup(itr->second->GetBase()->GetId(), SPELL_GROUP_ELIXIR_GUARDIAN))
+ guardianFound = true;
+ if (!battleFound && sSpellMgr->IsSpellMemberOfSpellGroup(itr->second->GetBase()->GetId(), SPELL_GROUP_ELIXIR_BATTLE))
+ battleFound = true;
if (battleFound && guardianFound)
break;
}
@@ -1966,9 +1963,9 @@ void Spell::EffectEnergize(SpellEffIndex effIndex)
// get all available elixirs by mask and spell level
std::set<uint32> availableElixirs;
if (!guardianFound)
- sSpellMgr->GetSetOfSpellsInSpellGroupWithFlag(1, SPELL_GROUP_SPECIAL_FLAG_ELIXIR_GUARDIAN, availableElixirs);
+ sSpellMgr->GetSetOfSpellsInSpellGroup(SPELL_GROUP_ELIXIR_GUARDIAN, availableElixirs);
if (!battleFound)
- sSpellMgr->GetSetOfSpellsInSpellGroupWithFlag(1, SPELL_GROUP_SPECIAL_FLAG_ELIXIR_BATTLE, availableElixirs);
+ sSpellMgr->GetSetOfSpellsInSpellGroup(SPELL_GROUP_ELIXIR_BATTLE, availableElixirs);
for (std::set<uint32>::iterator itr = availableElixirs.begin(); itr != availableElixirs.end();)
{
SpellInfo const* spellInfo = sSpellMgr->AssertSpellInfo(*itr);
@@ -3598,7 +3595,7 @@ void Spell::EffectWeaponDmg(SpellEffIndex effIndex)
unitMod = UNIT_MOD_DAMAGE_RANGED;
break;
}
- float weapon_total_pct = m_caster->GetModifierValue(unitMod, TOTAL_PCT);
+ float weapon_total_pct = m_caster->GetPctModifierValue(unitMod, TOTAL_PCT);
fixed_bonus = int32(fixed_bonus * weapon_total_pct);
spell_bonus = int32(spell_bonus * weapon_total_pct);
}
@@ -5013,7 +5010,24 @@ void Spell::EffectKnockBack(SpellEffIndex effIndex)
return;
float x, y;
- if (m_spellInfo->Effects[effIndex].Effect == SPELL_EFFECT_KNOCK_BACK_DEST)
+ Unit* reflectionSource = m_reflectionTarget;
+
+ if (!reflectionSource && !m_reflectionTargetGuid.IsEmpty())
+ {
+ if (Unit* resolvedSource = ObjectAccessor::GetUnit(*m_caster, m_reflectionTargetGuid))
+ reflectionSource = resolvedSource;
+ }
+
+ if (reflectionSource)
+ {
+ reflectionSource->GetPosition(x, y);
+ }
+ else if (!m_reflectionTargetGuid.IsEmpty())
+ {
+ x = m_reflectionTargetPosition.GetPositionX();
+ y = m_reflectionTargetPosition.GetPositionY();
+ }
+ else if (m_spellInfo->Effects[effIndex].Effect == SPELL_EFFECT_KNOCK_BACK_DEST)
{
if (m_targets.HasDst())
destTarget->GetPosition(x, y);
diff --git a/src/server/game/Spells/SpellInfo.cpp b/src/server/game/Spells/SpellInfo.cpp
index 2a54900431..74b89e99bd 100644
--- a/src/server/game/Spells/SpellInfo.cpp
+++ b/src/server/game/Spells/SpellInfo.cpp
@@ -326,9 +326,9 @@ std::array<SpellImplicitTargetInfo::StaticData, TOTAL_SPELL_TARGETS> SpellImplic
SpellEffectInfo::SpellEffectInfo(SpellEntry const* spellEntry, SpellInfo const* spellInfo, uint8 effIndex)
{
_spellInfo = spellInfo;
- _effIndex = effIndex;
+ EffectIndex = effIndex;
Effect = spellEntry->Effect[effIndex];
- ApplyAuraName = spellEntry->EffectApplyAuraName[effIndex];
+ ApplyAuraName = AuraType(spellEntry->EffectApplyAuraName[effIndex]);
Amplitude = spellEntry->EffectAmplitude[effIndex];
DieSides = spellEntry->EffectDieSides[effIndex];
RealPointsPerLevel = spellEntry->EffectRealPointsPerLevel[effIndex];
@@ -456,7 +456,7 @@ int32 SpellEffectInfo::CalcValue(Unit const* caster, int32 const* bp, Unit const
value += PointsPerComboPoint * comboPoints;
}
- value = caster->ApplyEffectModifiers(_spellInfo, _effIndex, value);
+ value = caster->ApplyEffectModifiers(_spellInfo, EffectIndex, value);
// amount multiplication based on caster's level
if (!caster->IsControlledByPlayer() &&
@@ -501,7 +501,7 @@ int32 SpellEffectInfo::CalcValue(Unit const* caster, int32 const* bp, Unit const
break;
}
- if ((sSpellMgr->GetSpellInfo(_spellInfo->Effects[_effIndex].TriggerSpell) && sSpellMgr->GetSpellInfo(_spellInfo->Effects[_effIndex].TriggerSpell)->HasAttribute(SPELL_ATTR0_SCALES_WITH_CREATURE_LEVEL)) && _spellInfo->HasAttribute(SPELL_ATTR0_SCALES_WITH_CREATURE_LEVEL))
+ if ((sSpellMgr->GetSpellInfo(_spellInfo->Effects[EffectIndex].TriggerSpell) && sSpellMgr->GetSpellInfo(_spellInfo->Effects[EffectIndex].TriggerSpell)->HasAttribute(SPELL_ATTR0_SCALES_WITH_CREATURE_LEVEL)) && _spellInfo->HasAttribute(SPELL_ATTR0_SCALES_WITH_CREATURE_LEVEL))
canEffectScale = false;
if (canEffectScale)
@@ -1579,122 +1579,6 @@ SpellCastResult SpellInfo::CheckLocation(uint32 map_id, uint32 zone_id, uint32 a
return SPELL_CAST_OK;
}
-
-bool SpellInfo::IsStrongerAuraActive(Unit const* caster, Unit const* target) const
-{
- if (!target)
- return false;
-
- // xinef: check spell group
- uint32 groupId = sSpellMgr->GetSpellGroup(Id);
- if (!groupId)
- return false;
-
- SpellGroupSpecialFlags sFlag = sSpellMgr->GetSpellGroupSpecialFlags(Id);
- if (sFlag & SPELL_GROUP_SPECIAL_FLAG_SKIP_STRONGER_CHECK)
- return false;
-
- for (uint8 i = EFFECT_0; i < MAX_SPELL_EFFECTS; ++i)
- {
- // xinef: Skip Empty effects
- if (!Effects[i].IsEffect())
- continue;
-
- // xinef: if non-aura effect is preset - return false
- if (!Effects[i].IsAura())
- return false;
-
- // xinef: aura is periodic - return false
- if (Effects[i].Amplitude)
- return false;
-
- // xinef: exclude dummy auras
- if (Effects[i].ApplyAuraName == SPELL_AURA_DUMMY)
- return false;
- }
-
- for (uint8 i = EFFECT_0; i < MAX_SPELL_EFFECTS; ++i)
- {
- // xinef: skip non-aura efects
- if (!Effects[i].IsAura())
- return false;
-
- Unit::AuraEffectList const& auraList = target->GetAuraEffectsByType((AuraType)Effects[i].ApplyAuraName);
- for (Unit::AuraEffectList::const_iterator iter = auraList.begin(); iter != auraList.end(); ++iter)
- {
- // xinef: aura is not groupped or in different group
- uint32 auraGroup = (*iter)->GetAuraGroup();
- if (!auraGroup || auraGroup != groupId)
- continue;
-
- if (IsRankOf((*iter)->GetSpellInfo()) && (sFlag & SPELL_GROUP_SPECIAL_FLAG_SKIP_STRONGER_SAME_SPELL))
- {
- continue;
- }
-
- // xinef: check priority before effect mask
- if (sFlag >= SPELL_GROUP_SPECIAL_FLAG_PRIORITY1 && sFlag <= SPELL_GROUP_SPECIAL_FLAG_PRIORITY4)
- {
- SpellGroupSpecialFlags sFlagCurr = sSpellMgr->GetSpellGroupSpecialFlags((*iter)->GetId());
- if (sFlagCurr >= SPELL_GROUP_SPECIAL_FLAG_PRIORITY1 && sFlagCurr <= SPELL_GROUP_SPECIAL_FLAG_PRIORITY4 && sFlagCurr < sFlag)
- {
- return true;
- }
- }
-
- // xinef: check aura effect equal auras only, some auras have different effects on different ranks - check rank also
- if (!IsAuraEffectEqual((*iter)->GetSpellInfo()) && !IsRankOf((*iter)->GetSpellInfo()))
- continue;
-
- // xinef: misc value mismatches
- // xinef: commented, checked above
- //if (Effects[i].MiscValue != (*iter)->GetMiscValue())
- // continue;
-
- // xinef: should not happen, or effect is not active - stronger one is present
- AuraApplication* aurApp = (*iter)->GetBase()->GetApplicationOfTarget(target->GetGUID());
- if (!aurApp || !aurApp->IsActive((*iter)->GetEffIndex()))
- continue;
-
- // xinef: assume that all spells are either positive or negative, otherwise they should not be in one group
- // xinef: take custom values into account
-
- int32 basePoints = Effects[i].BasePoints;
- int32 duration = GetMaxDuration();
-
- // xinef: should have the same id, can be different if spell is triggered
- // xinef: have to fix spell mods for triggered spell, turn off current spellmodtakingspell for preparing and restore after
- if (Player const* player = caster->GetSpellModOwner())
- if (player->m_spellModTakingSpell && player->m_spellModTakingSpell->m_spellInfo->Id == Id)
- basePoints = player->m_spellModTakingSpell->GetSpellValue()->EffectBasePoints[i];
-
- int32 curValue = std::abs(Effects[i].CalcValue(caster, &basePoints));
- int32 auraValue = (sFlag & SPELL_GROUP_SPECIAL_FLAG_BASE_AMOUNT_CHECK) ?
- std::abs((*iter)->GetSpellInfo()->Effects[(*iter)->GetEffIndex()].CalcValue((*iter)->GetCaster())) :
- std::abs((*iter)->GetAmount());
-
- // xinef: for same spells, divide amount by stack amount
- if (Id == (*iter)->GetId())
- auraValue /= (*iter)->GetBase()->GetStackAmount();
-
- if (curValue < auraValue)
- return true;
-
- // xinef: little hack, if current spell is the same as aura spell, asume it is not stronger
- // xinef: if values are the same, duration mods should be taken into account but they are almost always passive
- if (curValue == auraValue)
- {
- if (Id == (*iter)->GetId())
- continue;
- if (!(*iter)->GetBase()->IsPassive() && duration < (*iter)->GetBase()->GetDuration())
- return true;
- }
- }
- }
-
- return false;
-}
-
bool SpellInfo::IsAuraEffectEqual(SpellInfo const* otherSpellInfo) const
{
uint8 matchCount = 0;
@@ -1938,10 +1822,6 @@ SpellCastResult SpellInfo::CheckTarget(Unit const* caster, WorldObject const* ta
if (HasEffect(SPELL_EFFECT_SELF_RESURRECT) || HasEffect(SPELL_EFFECT_RESURRECT) || HasEffect(SPELL_EFFECT_RESURRECT_NEW))
return SPELL_FAILED_TARGET_CANNOT_BE_RESURRECTED;
- // xinef: check if stronger aura is active
- if (IsStrongerAuraActive(caster, unitTarget))
- return SPELL_FAILED_AURA_BOUNCED;
-
return SPELL_CAST_OK;
}
@@ -2316,6 +2196,8 @@ SpellSpecificType SpellInfo::LoadSpellSpecific() const
case SPELL_AURA_TRACK_RESOURCES:
case SPELL_AURA_TRACK_STEALTHED:
return SPELL_SPECIFIC_TRACKER;
+ default:
+ break;
}
}
}
@@ -2399,6 +2281,8 @@ uint32 SpellInfo::GetMaxTicks() const
if (Effects[x].Amplitude != 0)
return DotDuration / Effects[x].Amplitude;
break;
+ default:
+ break;
}
}
@@ -2889,50 +2773,3 @@ void SpellInfo::_UnloadImplicitTargetConditionLists()
delete cur;
}
}
-
-bool SpellInfo::CheckElixirStacking(Unit const* caster) const
-{
- if (!caster)
- {
- return true;
- }
-
- // xinef: check spell group
- uint32 groupId = sSpellMgr->GetSpellGroup(Id);
- if (groupId != SPELL_GROUP_GUARDIAN_AND_BATTLE_ELIXIRS)
- {
- return true;
- }
-
- SpellGroupSpecialFlags sFlag = sSpellMgr->GetSpellGroupSpecialFlags(Id);
- for (uint8 i = EFFECT_0; i < MAX_SPELL_EFFECTS; ++i)
- {
- if (!Effects[i].IsAura())
- {
- continue;
- }
-
- Unit::AuraApplicationMap const& Auras = caster->GetAppliedAuras();
- for (Unit::AuraApplicationMap::const_iterator itr = Auras.begin(); itr != Auras.end(); ++itr)
- {
- // xinef: aura is not groupped or in different group
- uint32 auraGroup = sSpellMgr->GetSpellGroup(itr->first);
- if (auraGroup != groupId)
- {
- continue;
- }
-
- // Cannot apply guardian/battle elixir if flask is present
- if (sFlag == SPELL_GROUP_SPECIAL_FLAG_ELIXIR_BATTLE || sFlag == SPELL_GROUP_SPECIAL_FLAG_ELIXIR_GUARDIAN)
- {
- SpellGroupSpecialFlags sAuraFlag = sSpellMgr->GetSpellGroupSpecialFlags(itr->first);
- if ((sAuraFlag & SPELL_GROUP_SPECIAL_FLAG_FLASK) == SPELL_GROUP_SPECIAL_FLAG_FLASK)
- {
- return false;
- }
- }
- }
- }
-
- return true;
-}
diff --git a/src/server/game/Spells/SpellInfo.h b/src/server/game/Spells/SpellInfo.h
index eba1ad9013..50b6d88d40 100644
--- a/src/server/game/Spells/SpellInfo.h
+++ b/src/server/game/Spells/SpellInfo.h
@@ -248,10 +248,10 @@ private:
class SpellEffectInfo
{
SpellInfo const* _spellInfo;
- uint8 _effIndex;
public:
+ uint8 EffectIndex;
uint32 Effect;
- uint32 ApplyAuraName;
+ AuraType ApplyAuraName;
uint32 Amplitude;
int32 DieSides;
float RealPointsPerLevel;
@@ -272,7 +272,7 @@ public:
flag96 SpellClassMask;
std::list<Condition*>* ImplicitTargetConditions;
- SpellEffectInfo() : _spellInfo(nullptr), _effIndex(0), Effect(0), ApplyAuraName(0), Amplitude(0), DieSides(0),
+ SpellEffectInfo() : _spellInfo(nullptr), EffectIndex(0), Effect(0), ApplyAuraName(SPELL_AURA_NONE), Amplitude(0), DieSides(0),
RealPointsPerLevel(0), BasePoints(0), PointsPerComboPoint(0), ValueMultiplier(0), DamageMultiplier(0),
BonusMultiplier(0), MiscValue(0), MiscValueB(0), Mechanic(MECHANIC_NONE), RadiusEntry(nullptr), ChainTarget(0),
ItemType(0), TriggerSpell(0), ImplicitTargetConditions(nullptr) {}
@@ -482,8 +482,6 @@ public:
SpellCastResult CheckExplicitTarget(Unit const* caster, WorldObject const* target, Item const* itemTarget = nullptr) const;
bool CheckTargetCreatureType(Unit const* target) const;
- // xinef: aura stacking
- bool IsStrongerAuraActive(Unit const* caster, Unit const* target) const;
bool IsAuraEffectEqual(SpellInfo const* otherSpellInfo) const;
bool ValidateAttribute6SpellDamageMods(Unit const* caster, const AuraEffect* auraEffect, bool isDot) const;
@@ -539,8 +537,6 @@ public:
// unloading helpers
void _UnloadImplicitTargetConditionLists();
- bool CheckElixirStacking(Unit const* caster) const;
-
private:
std::array<SpellEffectInfo, MAX_SPELL_EFFECTS>& _GetEffects() { return Effects; }
SpellEffectInfo& _GetEffect(SpellEffIndex index) { ASSERT(index < Effects.size()); return Effects[index]; }
diff --git a/src/server/game/Spells/SpellInfoCorrections.cpp b/src/server/game/Spells/SpellInfoCorrections.cpp
index 623c43f1e1..c5dd83f018 100644
--- a/src/server/game/Spells/SpellInfoCorrections.cpp
+++ b/src/server/game/Spells/SpellInfoCorrections.cpp
@@ -570,14 +570,6 @@ void SpellMgr::LoadSpellInfoCorrections()
spellInfo->AttributesEx3 |= SPELL_ATTR3_SUPPRESS_CASTER_PROCS;
});
- // Blessing of sanctuary stats
- ApplySpellFix({ 67480 }, [](SpellInfo* spellInfo)
- {
- spellInfo->Effects[EFFECT_0].MiscValue = -1;
- spellInfo->SpellFamilyName = SPELLFAMILY_UNK1; // allows stacking
- spellInfo->Effects[EFFECT_1].ApplyAuraName = SPELL_AURA_DUMMY; // just a marker
- });
-
ApplySpellFix({
6940, // Hand of Sacrifice
64205 // Divine Sacrifice
@@ -5131,6 +5123,13 @@ void SpellMgr::LoadSpellInfoCorrections()
spellInfo->ChannelInterruptFlags &= ~AURA_INTERRUPT_FLAG_TURNING;
});
+ // Summon Scourged Captive
+ ApplySpellFix({ 51597 }, [](SpellInfo* spellInfo)
+ {
+ spellInfo->Effects[EFFECT_0].BasePoints = 1;
+ spellInfo->Effects[EFFECT_0].DieSides = 0;
+ });
+
// The Green Tower
ApplySpellFix({ 18097 }, [](SpellInfo* spellInfo)
{
@@ -5163,6 +5162,24 @@ void SpellMgr::LoadSpellInfoCorrections()
spellInfo->ProcCharges = 1;
});
+ ApplySpellFix({
+ 56917, // To Icecrown Airship - Teleport to Airship (A)
+ 57417, // To Icecrown Airship - Teleport to Airship (H)
+ }, [](SpellInfo* spellInfo)
+ {
+ spellInfo->RangeEntry = sSpellRangeStore.LookupEntry(6); // 100 yards
+ });
+
+ ApplySpellFix({
+ 60586, // Mighty Spear Thrust
+ 60776, // Claw Swipe
+ 60881, // Fatal Strike
+ 60864, // Jaws of Death
+ }, [](SpellInfo* spellInfo)
+ {
+ spellInfo->AttributesEx4 |= SPELL_ATTR4_IGNORE_DAMAGE_TAKEN_MODIFIERS;
+ });
+
for (uint32 i = 0; i < GetSpellInfoStoreSize(); ++i)
{
SpellInfo* spellInfo = mSpellInfoMap[i];
@@ -5284,14 +5301,6 @@ void SpellMgr::LoadSpellInfoCorrections()
factionTemplateEntry = const_cast<FactionTemplateEntry*>(sFactionTemplateStore.LookupEntry(1921)); // The Taunka
factionTemplateEntry->hostileMask |= 8;
- // Remove 1 from guards friendly mask, making able to attack players
- factionTemplateEntry = const_cast<FactionTemplateEntry*>(sFactionTemplateStore.LookupEntry(1857)); // Area 52 Bruiser
- factionTemplateEntry->friendlyMask &= ~1;
- factionTemplateEntry = const_cast<FactionTemplateEntry*>(sFactionTemplateStore.LookupEntry(1806)); // Netherstorm Agent
- factionTemplateEntry->friendlyMask &= ~1;
- factionTemplateEntry = const_cast<FactionTemplateEntry*>(sFactionTemplateStore.LookupEntry(1812)); // K3 Bruiser
- factionTemplateEntry->friendlyMask &= ~1;
-
// Remove vehicles attr, making accessories selectable
VehicleSeatEntry* vse = const_cast<VehicleSeatEntry*>(sVehicleSeatStore.LookupEntry(4689)); // Siege Engine, Accessory
vse->m_flags &= ~VEHICLE_SEAT_FLAG_PASSENGER_NOT_SELECTABLE;
diff --git a/src/server/game/Spells/SpellMgr.cpp b/src/server/game/Spells/SpellMgr.cpp
index f424be399a..49c6558c81 100644
--- a/src/server/game/Spells/SpellMgr.cpp
+++ b/src/server/game/Spells/SpellMgr.cpp
@@ -18,12 +18,9 @@
#include "SpellMgr.h"
#include "BattlefieldMgr.h"
#include "BattlegroundIC.h"
-#include "BattlegroundMgr.h"
#include "Chat.h"
#include "DBCStores.h"
-#include "GameGraveyard.h"
#include "InstanceScript.h"
-#include "MapMgr.h"
#include "ObjectMgr.h"
#include "Player.h"
#include "ScriptMgr.h"
@@ -196,7 +193,7 @@ DiminishingGroup GetDiminishingReturnsGroupForSpell(SpellInfo const* spellproto,
{
// Storm, Earth and Fire - Earthgrab
if (spellproto->SpellFamilyFlags[2] & 0x4000)
- return DIMINISHING_NONE;
+ return DIMINISHING_CONTROLLED_ROOT;
break;
}
case SPELLFAMILY_DEATHKNIGHT:
@@ -605,9 +602,9 @@ uint32 SpellMgr::GetSpellWithRank(uint32 spell_id, uint32 rank, bool strict) con
return spell_id;
}
-SpellRequiredMapBounds SpellMgr::GetSpellsRequiredForSpellBounds(uint32 spell_id) const
+Acore::IteratorPair<SpellRequiredMap::const_iterator> SpellMgr::GetSpellsRequiredForSpellBounds(uint32 spell_id) const
{
- return mSpellReq.equal_range(spell_id);
+ return Acore::Containers::MapEqualRange(mSpellReq, spell_id);
}
SpellsRequiringSpellMapBounds SpellMgr::GetSpellsRequiringSpellBounds(uint32 spell_id) const
@@ -648,82 +645,143 @@ SpellTargetPosition const* SpellMgr::GetSpellTargetPosition(uint32 spell_id, Spe
return nullptr;
}
-SpellGroupStackFlags SpellMgr::GetGroupStackFlags(uint32 groupid) const
+SpellSpellGroupMapBounds SpellMgr::GetSpellSpellGroupMapBounds(uint32 spell_id) const
{
- SpellGroupStackMap::const_iterator itr = mSpellGroupStackMap.find(groupid);
- if (itr != mSpellGroupStackMap.end())
- return itr->second;
-
- return SPELL_GROUP_STACK_FLAG_NONE;
+ spell_id = GetFirstSpellInChain(spell_id);
+ return mSpellSpellGroup.equal_range(spell_id);
}
-uint32 SpellMgr::GetSpellGroup(uint32 spell_id) const
+bool SpellMgr::IsSpellMemberOfSpellGroup(uint32 spell_id, SpellGroup group_id) const
{
- uint32 first_rank = GetFirstSpellInChain(spell_id);
- SpellGroupMap::const_iterator itr = mSpellGroupMap.find(first_rank);
- if (itr != mSpellGroupMap.end())
- return itr->second.groupId;
-
- return 0;
+ SpellSpellGroupMapBounds spellGroup = GetSpellSpellGroupMapBounds(spell_id);
+ for (SpellSpellGroupMap::const_iterator itr = spellGroup.first; itr != spellGroup.second; ++itr)
+ {
+ if (itr->second == group_id)
+ return true;
+ }
+ return false;
}
-SpellGroupSpecialFlags SpellMgr::GetSpellGroupSpecialFlags(uint32 spell_id) const
+SpellGroupSpellMapBounds SpellMgr::GetSpellGroupSpellMapBounds(SpellGroup group_id) const
{
- uint32 first_rank = GetFirstSpellInChain(spell_id);
- SpellGroupMap::const_iterator itr = mSpellGroupMap.find(first_rank);
- if (itr != mSpellGroupMap.end())
- return itr->second.specialFlags;
-
- return SPELL_GROUP_SPECIAL_FLAG_NONE;
+ return mSpellGroupSpell.equal_range(group_id);
}
-SpellGroupStackFlags SpellMgr::CheckSpellGroupStackRules(SpellInfo const* spellInfo1, SpellInfo const* spellInfo2, bool remove, bool areaAura) const
+void SpellMgr::GetSetOfSpellsInSpellGroup(SpellGroup group_id, std::set<uint32>& foundSpells) const
{
- uint32 spellid_1 = spellInfo1->GetFirstRankSpell()->Id;
- uint32 spellid_2 = spellInfo2->GetFirstRankSpell()->Id;
-
- uint32 groupId = GetSpellGroup(spellid_1);
+ std::set<SpellGroup> usedGroups;
+ GetSetOfSpellsInSpellGroup(group_id, foundSpells, usedGroups);
+}
- SpellGroupSpecialFlags flag1 = GetSpellGroupSpecialFlags(spellid_1);
+void SpellMgr::GetSetOfSpellsInSpellGroup(SpellGroup group_id, std::set<uint32>& foundSpells, std::set<SpellGroup>& usedGroups) const
+{
+ if (usedGroups.find(group_id) != usedGroups.end())
+ return;
+ usedGroups.insert(group_id);
- // xinef: dunno why i added this
- if (spellid_1 == spellid_2 && remove && !areaAura)
+ SpellGroupSpellMapBounds groupSpell = GetSpellGroupSpellMapBounds(group_id);
+ for (SpellGroupSpellMap::const_iterator itr = groupSpell.first; itr != groupSpell.second; ++itr)
{
- if (flag1 & SPELL_GROUP_SPECIAL_FLAG_SAME_SPELL_CHECK)
+ if (itr->second < 0)
{
- return SPELL_GROUP_STACK_FLAG_EXCLUSIVE;
+ SpellGroup currGroup = (SpellGroup)abs(itr->second);
+ GetSetOfSpellsInSpellGroup(currGroup, foundSpells, usedGroups);
+ }
+ else
+ {
+ foundSpells.insert(itr->second);
}
-
- return SPELL_GROUP_STACK_FLAG_NONE;
}
+}
- if (groupId > 0 && groupId == GetSpellGroup(spellid_2))
+bool SpellMgr::AddSameEffectStackRuleSpellGroups(SpellInfo const* spellInfo, uint32 auraType, int32 amount, std::map<SpellGroup, int32>& groups) const
+{
+ uint32 spellId = spellInfo->GetFirstRankSpell()->Id;
+ auto spellGroupBounds = GetSpellSpellGroupMapBounds(spellId);
+ // Find group with SPELL_GROUP_STACK_RULE_EXCLUSIVE_SAME_EFFECT if it belongs to one
+ for (auto itr = spellGroupBounds.first; itr != spellGroupBounds.second; ++itr)
{
- SpellGroupSpecialFlags flag2 = GetSpellGroupSpecialFlags(spellid_2);
- SpellGroupStackFlags additionFlag = SPELL_GROUP_STACK_FLAG_NONE;
- // xinef: first flags are used for elixir stacking rules
- if (flag1 & SPELL_GROUP_SPECIAL_FLAG_STACK_EXCLUSIVE_MAX && flag2 & SPELL_GROUP_SPECIAL_FLAG_STACK_EXCLUSIVE_MAX)
+ SpellGroup group = itr->second;
+ auto found = mSpellSameEffectStack.find(group);
+ if (found != mSpellSameEffectStack.end())
{
- if (flag1 & flag2)
- return SPELL_GROUP_STACK_FLAG_NEVER_STACK;
+ // check auraTypes
+ if (!found->second.count(auraType))
+ continue;
+
+ // Put the highest amount in the map
+ auto groupItr = groups.find(group);
+ if (groupItr == groups.end())
+ groups.emplace(group, amount);
+ else
+ {
+ int32 curr_amount = groups[group];
+ // Take absolute value because this also counts for the highest negative aura
+ if (std::abs(curr_amount) < std::abs(amount))
+ groupItr->second = amount;
+ }
+ // return because a spell should be in only one SPELL_GROUP_STACK_RULE_EXCLUSIVE_SAME_EFFECT group per auraType
+ return true;
}
- // xinef: check only flag1 (new spell)
- else if (flag1 & SPELL_GROUP_SPECIAL_FLAG_FORCED_STRONGEST)
- additionFlag = SPELL_GROUP_STACK_FLAG_FORCED_STRONGEST;
- else if (flag2 & SPELL_GROUP_SPECIAL_FLAG_FORCED_STRONGEST)
- additionFlag = SPELL_GROUP_STACK_FLAG_FORCED_WEAKEST;
+ }
+ // Not in a SPELL_GROUP_STACK_RULE_EXCLUSIVE_SAME_EFFECT group, so return false
+ return false;
+}
+
+SpellGroupStackRule SpellMgr::CheckSpellGroupStackRules(SpellInfo const* spellInfo1, SpellInfo const* spellInfo2) const
+{
+ ASSERT(spellInfo1);
+ ASSERT(spellInfo2);
+
+ uint32 spell_id1 = spellInfo1->GetFirstRankSpell()->Id;
+ uint32 spell_id2 = spellInfo2->GetFirstRankSpell()->Id;
- return SpellGroupStackFlags(GetGroupStackFlags(groupId) | additionFlag);
+ // find SpellGroups which are common for both spells
+ SpellSpellGroupMapBounds spellGroup1 = GetSpellSpellGroupMapBounds(spell_id1);
+ std::set<SpellGroup> groups;
+ for (SpellSpellGroupMap::const_iterator itr = spellGroup1.first; itr != spellGroup1.second; ++itr)
+ {
+ if (IsSpellMemberOfSpellGroup(spell_id2, itr->second))
+ {
+ bool add = true;
+ SpellGroupSpellMapBounds groupSpell = GetSpellGroupSpellMapBounds(itr->second);
+ for (SpellGroupSpellMap::const_iterator itr2 = groupSpell.first; itr2 != groupSpell.second; ++itr2)
+ {
+ if (itr2->second < 0)
+ {
+ SpellGroup currGroup = (SpellGroup)abs(itr2->second);
+ if (IsSpellMemberOfSpellGroup(spell_id1, currGroup) && IsSpellMemberOfSpellGroup(spell_id2, currGroup))
+ {
+ add = false;
+ break;
+ }
+ }
+ }
+ if (add)
+ groups.insert(itr->second);
+ }
}
- return SPELL_GROUP_STACK_FLAG_NONE;
+ SpellGroupStackRule rule = SPELL_GROUP_STACK_RULE_DEFAULT;
+
+ for (std::set<SpellGroup>::iterator itr = groups.begin(); itr!= groups.end(); ++itr)
+ {
+ SpellGroupStackMap::const_iterator found = mSpellGroupStack.find(*itr);
+ if (found != mSpellGroupStack.end())
+ rule = found->second;
+ if (rule)
+ break;
+ }
+ return rule;
}
-void SpellMgr::GetSetOfSpellsInSpellGroupWithFlag(uint32 group_id, SpellGroupSpecialFlags flag, std::set<uint32>& availableElixirs) const
+SpellGroupStackRule SpellMgr::GetSpellGroupStackRule(SpellGroup group) const
{
- for (SpellGroupMap::const_iterator itr = mSpellGroupMap.begin(); itr != mSpellGroupMap.end(); ++itr)
- if (itr->second.groupId == group_id && itr->second.specialFlags == flag)
- availableElixirs.insert(itr->first); // insert spell id
+ SpellGroupStackMap::const_iterator itr = mSpellGroupStack.find(group);
+ if (itr != mSpellGroupStack.end())
+ return itr->second;
+
+ return SPELL_GROUP_STACK_RULE_DEFAULT;
}
SpellProcEventEntry const* SpellMgr::GetSpellProcEvent(uint32 spellId) const
@@ -1627,10 +1685,11 @@ void SpellMgr::LoadSpellGroups()
{
uint32 oldMSTime = getMSTime();
- mSpellGroupMap.clear(); // need for reload case
+ mSpellSpellGroup.clear(); // need for reload case
+ mSpellGroupSpell.clear();
- // 0 1 2
- QueryResult result = WorldDatabase.Query("SELECT id, spell_id, special_flag FROM spell_group");
+ // 0 1
+ QueryResult result = WorldDatabase.Query("SELECT id, spell_id FROM spell_group");
if (!result)
{
LOG_WARN("server.loading", ">> Loaded 0 spell group definitions. DB table `spell_group` is empty.");
@@ -1638,48 +1697,68 @@ void SpellMgr::LoadSpellGroups()
return;
}
+ std::set<uint32> groups;
uint32 count = 0;
do
{
Field* fields = result->Fetch();
uint32 group_id = fields[0].Get<uint32>();
- int32 spell_id = fields[1].Get<uint32>();
- SpellGroupSpecialFlags specialFlag = (SpellGroupSpecialFlags)fields[2].Get<uint32>();
- SpellInfo const* spellInfo = GetSpellInfo(spell_id);
-
- if (!spellInfo)
+ if (group_id <= SPELL_GROUP_DB_RANGE_MIN && group_id >= SPELL_GROUP_CORE_RANGE_MAX)
{
- LOG_ERROR("sql.sql", "Spell {} listed in `spell_group` does not exist", spell_id);
- continue;
- }
- else if (spellInfo->GetRank() > 1)
- {
- LOG_ERROR("sql.sql", "Spell {} listed in `spell_group` is not first rank of spell", spell_id);
+ LOG_ERROR("sql.sql", "SpellGroup id {} listed in `spell_group` is in core range, but is not defined in core!", group_id);
continue;
}
+ int32 spell_id = fields[1].Get<int32>();
+
+ groups.insert(group_id);
+ mSpellGroupSpell.emplace(SpellGroup(group_id), spell_id);
- if (mSpellGroupMap.find(spell_id) != mSpellGroupMap.end())
+ } while (result->NextRow());
+
+ for (auto itr = mSpellGroupSpell.begin(); itr!= mSpellGroupSpell.end();)
+ {
+ if (itr->second < 0)
{
- LOG_ERROR("sql.sql", "Spell {} listed in `spell_group` has more than one group", spell_id);
- continue;
+ if (groups.find(abs(itr->second)) == groups.end())
+ {
+ LOG_ERROR("sql.sql", "SpellGroup id {} listed in `spell_group` does not exist", abs(itr->second));
+ itr = mSpellGroupSpell.erase(itr);
+ }
+ else
+ ++itr;
}
-
- if (specialFlag >= SPELL_GROUP_SPECIAL_FLAG_MAX)
+ else
{
- LOG_ERROR("sql.sql", "Spell {} listed in `spell_group` has invalid special flag!", spell_id);
- continue;
+ SpellInfo const* spellInfo = GetSpellInfo(itr->second);
+ if (!spellInfo)
+ {
+ LOG_ERROR("sql.sql", "Spell {} listed in `spell_group` does not exist", itr->second);
+ itr = mSpellGroupSpell.erase(itr);
+ }
+ else if (spellInfo->GetRank() > 1)
+ {
+ LOG_ERROR("sql.sql", "Spell {} listed in `spell_group` is not first rank of spell.", itr->second);
+ itr = mSpellGroupSpell.erase(itr);
+ }
+ else
+ ++itr;
}
+ }
- SpellStackInfo ssi;
- ssi.groupId = group_id;
- ssi.specialFlags = specialFlag;
- mSpellGroupMap[spell_id] = ssi;
+ for (auto groupItr = groups.begin(); groupItr != groups.end(); ++groupItr)
+ {
+ std::set<uint32> spells;
+ GetSetOfSpellsInSpellGroup(SpellGroup(*groupItr), spells);
- ++count;
- } while (result->NextRow());
+ for (auto spellItr = spells.begin(); spellItr != spells.end(); ++spellItr)
+ {
+ ++count;
+ mSpellSpellGroup.emplace(*spellItr, SpellGroup(*groupItr));
+ }
+ }
- LOG_INFO("server.loading", ">> Loaded {} Spell Group Definitions in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
+ LOG_INFO("server.loading", ">> Loaded {} spell group Definitions in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
LOG_INFO("server.loading", " ");
}
@@ -1687,7 +1766,10 @@ void SpellMgr::LoadSpellGroupStackRules()
{
uint32 oldMSTime = getMSTime();
- mSpellGroupStackMap.clear(); // need for reload case
+ mSpellGroupStack.clear(); // need for reload case
+ mSpellSameEffectStack.clear();
+
+ std::vector<uint32> sameEffectGroups;
// 0 1
QueryResult result = WorldDatabase.Query("SELECT group_id, stack_rule FROM spell_group_stack_rules");
@@ -1705,32 +1787,132 @@ void SpellMgr::LoadSpellGroupStackRules()
uint32 group_id = fields[0].Get<uint32>();
uint8 stack_rule = fields[1].Get<int8>();
- if (stack_rule >= SPELL_GROUP_STACK_FLAG_MAX)
+ if (stack_rule >= SPELL_GROUP_STACK_RULE_MAX)
{
- LOG_ERROR("sql.sql", "SpellGroupStackRule {} listed in `spell_group_stack_rules` does not exist", stack_rule);
+ LOG_ERROR("sql.sql", "SpellGroupStackRule {} listed in `spell_group_stack_rules` does not exist.", stack_rule);
continue;
}
- bool present = false;
- for (SpellGroupMap::const_iterator itr = mSpellGroupMap.begin(); itr != mSpellGroupMap.end(); ++itr)
- if (itr->second.groupId == group_id)
- {
- present = true;
- break;
- }
-
- if (!present)
+ auto bounds = GetSpellGroupSpellMapBounds((SpellGroup)group_id);
+ if (bounds.first == bounds.second)
{
- LOG_ERROR("sql.sql", "SpellGroup id {} listed in `spell_group_stack_rules` does not exist", group_id);
+ LOG_ERROR("sql.sql", "SpellGroup id {} listed in `spell_group_stack_rules` does not exist.", group_id);
continue;
}
- mSpellGroupStackMap[group_id] = (SpellGroupStackFlags)stack_rule;
+ mSpellGroupStack.emplace(SpellGroup(group_id), SpellGroupStackRule(stack_rule));
+
+ // different container for same effect stack rules, need to check effect types
+ if (stack_rule == SPELL_GROUP_STACK_RULE_EXCLUSIVE_SAME_EFFECT)
+ sameEffectGroups.push_back(group_id);
++count;
} while (result->NextRow());
- LOG_INFO("server.loading", ">> Loaded {} Spell Group Stack Rules in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
+ LOG_INFO("server.loading", ">> Loaded {} spell group stack rules in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
+ LOG_INFO("server.loading", " ");
+
+ count = 0;
+ oldMSTime = getMSTime();
+
+ for (uint32 group_id : sameEffectGroups)
+ {
+ std::set<uint32> spellIds;
+ GetSetOfSpellsInSpellGroup(SpellGroup(group_id), spellIds);
+
+ std::unordered_set<uint32> auraTypes;
+
+ // we have to 'guess' what effect this group corresponds to
+ {
+ std::unordered_multiset<uint32 /*auraName*/> frequencyContainer;
+
+ // only waylay for the moment (shared group)
+ std::vector<std::vector<uint32 /*auraName*/>> const SubGroups =
+ {
+ { SPELL_AURA_MOD_MELEE_HASTE, SPELL_AURA_MOD_MELEE_RANGED_HASTE, SPELL_AURA_MOD_RANGED_HASTE }
+ };
+
+ for (uint32 spellId : spellIds)
+ {
+ SpellInfo const* spellInfo = AssertSpellInfo(spellId);
+ for (SpellEffectInfo const& spellEffectInfo : spellInfo->GetEffects())
+ {
+ if (!spellEffectInfo.IsAura())
+ continue;
+
+ uint32 auraName = spellEffectInfo.ApplyAuraName;
+ for (std::vector<uint32> const& subGroup : SubGroups)
+ {
+ if (std::find(subGroup.begin(), subGroup.end(), auraName) != subGroup.end())
+ {
+ // count as first aura
+ auraName = subGroup.front();
+ break;
+ }
+ }
+
+ frequencyContainer.insert(auraName);
+ }
+ }
+
+ uint32 auraType = 0;
+ size_t auraTypeCount = 0;
+ for (uint32 auraName : frequencyContainer)
+ {
+ size_t currentCount = frequencyContainer.count(auraName);
+ if (currentCount > auraTypeCount)
+ {
+ auraType = auraName;
+ auraTypeCount = currentCount;
+ }
+ }
+
+ for (std::vector<uint32> const& subGroup : SubGroups)
+ {
+ if (auraType == subGroup.front())
+ {
+ auraTypes.insert(subGroup.begin(), subGroup.end());
+ break;
+ }
+ }
+
+ if (auraTypes.empty())
+ auraTypes.insert(auraType);
+ }
+
+ // re-check spells against guessed group
+ for (uint32 spellId : spellIds)
+ {
+ SpellInfo const* spellInfo = AssertSpellInfo(spellId);
+
+ bool found = false;
+ while (spellInfo)
+ {
+ for (uint32 auraType : auraTypes)
+ {
+ if (spellInfo->HasAura(AuraType(auraType)))
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (found)
+ break;
+
+ spellInfo = spellInfo->GetNextRankSpell();
+ }
+
+ // not found either, log error
+ if (!found)
+ LOG_ERROR("sql.sql", "SpellId {} listed in `spell_group` with stack rule 3 does not share aura assigned for group {}", spellId, group_id);
+ }
+
+ mSpellSameEffectStack[SpellGroup(group_id)] = auraTypes;
+ ++count;
+ }
+
+ LOG_INFO("server.loading", ">> Loaded {} SPELL_GROUP_STACK_RULE_EXCLUSIVE_SAME_EFFECT stack rules in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
LOG_INFO("server.loading", " ");
}
@@ -2920,6 +3102,8 @@ void SpellMgr::LoadSpellInfoCustomAttributes()
case SPELL_AURA_WATER_BREATHING:
spellInfo->AttributesCu |= SPELL_ATTR0_CU_NO_INITIAL_THREAT;
break;
+ default:
+ break;
}
switch (spellInfo->Effects[j].ApplyAuraName)
@@ -3494,6 +3678,9 @@ void SpellMgr::LoadSpellInfoCustomAttributes()
if (triggerSpell->AttributesCu & SPELL_ATTR0_CU_BINARY_SPELL)
allNonBinary = false;
}
+ break;
+ default:
+ break;
}
}
}
diff --git a/src/server/game/Spells/SpellMgr.h b/src/server/game/Spells/SpellMgr.h
index feb1e4f7ec..a451404b68 100644
--- a/src/server/game/Spells/SpellMgr.h
+++ b/src/server/game/Spells/SpellMgr.h
@@ -20,8 +20,8 @@
// For static or at-server-startup loaded spell data
-#include "Common.h"
#include "Log.h"
+#include "IteratorPair.h"
#include "SharedDefines.h"
#include "Unit.h"
@@ -330,56 +330,49 @@ struct SpellBonusEntry
typedef std::unordered_map<uint32, SpellBonusEntry> SpellBonusMap;
-enum SpellGroupSpecialFlags
+enum SpellGroup
{
- SPELL_GROUP_SPECIAL_FLAG_NONE = 0x000,
- SPELL_GROUP_SPECIAL_FLAG_ELIXIR_BATTLE = 0x001,
- SPELL_GROUP_SPECIAL_FLAG_ELIXIR_GUARDIAN = 0x002,
- SPELL_GROUP_SPECIAL_FLAG_ELIXIR_UNSTABLE = 0x004,
- SPELL_GROUP_SPECIAL_FLAG_ELIXIR_SHATTRATH = 0x008,
- SPELL_GROUP_SPECIAL_FLAG_STACK_EXCLUSIVE_MAX = 0x00F,
- SPELL_GROUP_SPECIAL_FLAG_FORCED_STRONGEST = 0x010, // xinef: specially helpful flag if some spells have different auras, but only one should be present
- SPELL_GROUP_SPECIAL_FLAG_SKIP_STRONGER_CHECK = 0x020,
- SPELL_GROUP_SPECIAL_FLAG_BASE_AMOUNT_CHECK = 0x040,
- SPELL_GROUP_SPECIAL_FLAG_PRIORITY1 = 0x100,
- SPELL_GROUP_SPECIAL_FLAG_PRIORITY2 = 0x200,
- SPELL_GROUP_SPECIAL_FLAG_PRIORITY3 = 0x400,
- SPELL_GROUP_SPECIAL_FLAG_PRIORITY4 = 0x800,
- SPELL_GROUP_SPECIAL_FLAG_SAME_SPELL_CHECK = 0x1000,
- SPELL_GROUP_SPECIAL_FLAG_SKIP_STRONGER_SAME_SPELL = 0x2000,
- SPELL_GROUP_SPECIAL_FLAG_MAX = 0x4000,
-
- SPELL_GROUP_SPECIAL_FLAG_FLASK = SPELL_GROUP_SPECIAL_FLAG_ELIXIR_BATTLE | SPELL_GROUP_SPECIAL_FLAG_ELIXIR_GUARDIAN
+ SPELL_GROUP_NONE = 0,
+ SPELL_GROUP_ELIXIR_BATTLE = 1,
+ SPELL_GROUP_ELIXIR_GUARDIAN = 2,
+ SPELL_GROUP_CORE_RANGE_MAX = 3
};
-enum SpellGroupStackFlags
+namespace std
{
- SPELL_GROUP_STACK_FLAG_NONE = 0x00,
- SPELL_GROUP_STACK_FLAG_EXCLUSIVE = 0x01,
- SPELL_GROUP_STACK_FLAG_NOT_SAME_CASTER = 0x02,
- SPELL_GROUP_STACK_FLAG_FLAGGED = 0x04, // xinef: just a marker
- SPELL_GROUP_STACK_FLAG_NEVER_STACK = 0x08,
- SPELL_GROUP_STACK_FLAG_EFFECT_EXCLUSIVE = 0x10,
- SPELL_GROUP_STACK_FLAG_MAX = 0x20,
-
- // Internal use
- SPELL_GROUP_STACK_FLAG_FORCED_STRONGEST = 0x100,
- SPELL_GROUP_STACK_FLAG_FORCED_WEAKEST = 0x200,
-};
+ template<>
+ struct hash<SpellGroup>
+ {
+ size_t operator()(SpellGroup const& group) const
+ {
+ return hash<uint32>()(uint32(group));
+ }
+ };
+}
-enum SpellGroupIDs
-{
- SPELL_GROUP_GUARDIAN_AND_BATTLE_ELIXIRS = 1
-};
+#define SPELL_GROUP_DB_RANGE_MIN 1000
-struct SpellStackInfo
+// spell_id, group_id
+typedef std::unordered_multimap<uint32, SpellGroup> SpellSpellGroupMap;
+typedef std::pair<SpellSpellGroupMap::const_iterator, SpellSpellGroupMap::const_iterator> SpellSpellGroupMapBounds;
+
+// group_id, spell_id
+typedef std::unordered_multimap<SpellGroup, int32> SpellGroupSpellMap;
+typedef std::pair<SpellGroupSpellMap::const_iterator, SpellGroupSpellMap::const_iterator> SpellGroupSpellMapBounds;
+
+enum SpellGroupStackRule
{
- uint32 groupId;
- SpellGroupSpecialFlags specialFlags;
+ SPELL_GROUP_STACK_RULE_DEFAULT,
+ SPELL_GROUP_STACK_RULE_EXCLUSIVE,
+ SPELL_GROUP_STACK_RULE_EXCLUSIVE_FROM_SAME_CASTER,
+ SPELL_GROUP_STACK_RULE_EXCLUSIVE_SAME_EFFECT,
+ SPELL_GROUP_STACK_RULE_EXCLUSIVE_HIGHEST,
+ SPELL_GROUP_STACK_RULE_MAX
};
-// spell_id, group_id
-typedef std::map<uint32, SpellStackInfo> SpellGroupMap;
-typedef std::map<uint32, SpellGroupStackFlags> SpellGroupStackMap;
+
+typedef std::unordered_map<SpellGroup, SpellGroupStackRule> SpellGroupStackMap;
+
+typedef std::unordered_map<SpellGroup, std::unordered_set<uint32 /*auraName*/>> SameEffectStackMap;
struct SpellThreatEntry
{
@@ -669,7 +662,7 @@ public:
[[nodiscard]] uint32 GetSpellWithRank(uint32 spell_id, uint32 rank, bool strict = false) const;
// Spell Required table
- [[nodiscard]] SpellRequiredMapBounds GetSpellsRequiredForSpellBounds(uint32 spell_id) const;
+ [[nodiscard]] Acore::IteratorPair<SpellRequiredMap::const_iterator>GetSpellsRequiredForSpellBounds(uint32 spell_id) const;
[[nodiscard]] SpellsRequiringSpellMapBounds GetSpellsRequiringSpellBounds(uint32 spell_id) const;
[[nodiscard]] bool IsSpellRequiringSpell(uint32 spellid, uint32 req_spellid) const;
@@ -679,12 +672,18 @@ public:
// Spell target coordinates
[[nodiscard]] SpellTargetPosition const* GetSpellTargetPosition(uint32 spell_id, SpellEffIndex effIndex) const;
- // Spell Groups
- [[nodiscard]] uint32 GetSpellGroup(uint32 spellid) const;
- [[nodiscard]] SpellGroupSpecialFlags GetSpellGroupSpecialFlags(uint32 spell_id) const;
- [[nodiscard]] SpellGroupStackFlags GetGroupStackFlags(uint32 groupid) const;
- SpellGroupStackFlags CheckSpellGroupStackRules(SpellInfo const* spellInfo1, SpellInfo const* spellInfo2, bool remove, bool areaAura) const;
- void GetSetOfSpellsInSpellGroupWithFlag(uint32 group_id, SpellGroupSpecialFlags flag, std::set<uint32>& availableElixirs) const;
+ // Spell Groups table
+ SpellSpellGroupMapBounds GetSpellSpellGroupMapBounds(uint32 spell_id) const;
+ bool IsSpellMemberOfSpellGroup(uint32 spell_id, SpellGroup group_id) const;
+
+ SpellGroupSpellMapBounds GetSpellGroupSpellMapBounds(SpellGroup group_id) const;
+ void GetSetOfSpellsInSpellGroup(SpellGroup group_id, std::set<uint32>& foundSpells) const;
+ void GetSetOfSpellsInSpellGroup(SpellGroup group_id, std::set<uint32>& foundSpells, std::set<SpellGroup>& usedGroups) const;
+
+ // Spell Group Stack Rules table
+ bool AddSameEffectStackRuleSpellGroups(SpellInfo const* spellInfo, uint32 auraType, int32 amount, std::map<SpellGroup, int32>& groups) const;
+ SpellGroupStackRule CheckSpellGroupStackRules(SpellInfo const* spellInfo1, SpellInfo const* spellInfo2) const;
+ SpellGroupStackRule GetSpellGroupStackRule(SpellGroup group_id) const;
// Spell proc event table
[[nodiscard]] SpellProcEventEntry const* GetSpellProcEvent(uint32 spellId) const;
@@ -798,8 +797,10 @@ private:
SpellRequiredMap mSpellReq;
SpellLearnSkillMap mSpellLearnSkills;
SpellTargetPositionMap mSpellTargetPositions;
- SpellGroupMap mSpellGroupMap;
- SpellGroupStackMap mSpellGroupStackMap;
+ SpellSpellGroupMap mSpellSpellGroup;
+ SpellGroupSpellMap mSpellGroupSpell;
+ SpellGroupStackMap mSpellGroupStack;
+ SameEffectStackMap mSpellSameEffectStack;
SpellProcEventMap mSpellProcEventMap;
SpellProcMap mSpellProcMap;
SpellBonusMap mSpellBonusMap;
diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp
index 83adc9e63d..e1de134a21 100644
--- a/src/server/game/World/World.cpp
+++ b/src/server/game/World/World.cpp
@@ -766,6 +766,12 @@ void World::SetInitialWorldSettings()
LOG_INFO("server.loading", "Loading GameTeleports...");
sObjectMgr->LoadGameTele();
+ LOG_INFO("server.loading", "Loading Trainers..."); // must be after LoadCreatureTemplates
+ sObjectMgr->LoadTrainers();
+
+ LOG_INFO("server.loading", "Loading Creature default trainers...");
+ sObjectMgr->LoadCreatureDefaultTrainers();
+
LOG_INFO("server.loading", "Loading Gossip Menu...");
sObjectMgr->LoadGossipMenu();
@@ -775,9 +781,6 @@ void World::SetInitialWorldSettings()
LOG_INFO("server.loading", "Loading Vendors...");
sObjectMgr->LoadVendors(); // must be after load CreatureTemplate and ItemTemplate
- LOG_INFO("server.loading", "Loading Trainers...");
- sObjectMgr->LoadTrainerSpell(); // must be after load CreatureTemplate
-
LOG_INFO("server.loading", "Loading Waypoints...");
sWaypointMgr->Load();
diff --git a/src/server/game/World/WorldConfig.cpp b/src/server/game/World/WorldConfig.cpp
index 761b8dd7be..1936df5107 100644
--- a/src/server/game/World/WorldConfig.cpp
+++ b/src/server/game/World/WorldConfig.cpp
@@ -488,6 +488,8 @@ void WorldConfig::BuildConfigCache()
SetConfigValue<uint32>(CONFIG_FFA_PVP_TIMER, "FFAPvPTimer", 30);
+ SetConfigValue<float>(CONFIG_OUTDOOR_PVP_CAPTURE_RATE, "OutdoorPvPCaptureRate", 1.0f);
+
SetConfigValue<uint32>(CONFIG_LOOT_NEED_BEFORE_GREED_ILVL_RESTRICTION, "LootNeedBeforeGreedILvlRestriction", 70);
SetConfigValue<bool>(CONFIG_PLAYER_SETTINGS_ENABLED, "EnablePlayerSettings", 0);
@@ -555,8 +557,8 @@ void WorldConfig::BuildConfigCache()
// Dungeon finder
SetConfigValue<uint32>(CONFIG_LFG_OPTIONSMASK, "DungeonFinder.OptionsMask", 5);
-
SetConfigValue<bool>(CONFIG_LFG_CAST_DESERTER, "DungeonFinder.CastDeserter", true);
+ SetConfigValue<bool>(CONFIG_LFG_ALLOW_COMPLETED, "DungeonFinder.AllowCompleted", true);
// DBC_ItemAttributes
SetConfigValue<bool>(CONFIG_DBC_ENFORCE_ITEM_ATTRIBUTES, "DBC.EnforceItemAttributes", true);
diff --git a/src/server/game/World/WorldConfig.h b/src/server/game/World/WorldConfig.h
index 540fd8324c..21efea51ba 100644
--- a/src/server/game/World/WorldConfig.h
+++ b/src/server/game/World/WorldConfig.h
@@ -94,6 +94,7 @@ enum ServerConfigs
CONFIG_ALLOW_TICKETS,
CONFIG_DELETE_CHARACTER_TICKET_TRACE,
CONFIG_LFG_CAST_DESERTER,
+ CONFIG_LFG_ALLOW_COMPLETED,
CONFIG_DBC_ENFORCE_ITEM_ATTRIBUTES,
CONFIG_PRESERVE_CUSTOM_CHANNELS,
CONFIG_PDUMP_NO_PATHS,
@@ -371,6 +372,7 @@ enum ServerConfigs
CONFIG_NPC_EVADE_IF_NOT_REACHABLE,
CONFIG_NPC_REGEN_TIME_IF_NOT_REACHABLE_IN_RAID,
CONFIG_FFA_PVP_TIMER,
+ CONFIG_OUTDOOR_PVP_CAPTURE_RATE,
CONFIG_LOOT_NEED_BEFORE_GREED_ILVL_RESTRICTION,
CONFIG_LFG_MAX_KICK_COUNT,
CONFIG_LFG_KICK_PREVENTION_TIMER,
diff --git a/src/server/scripts/Commands/cs_npc.cpp b/src/server/scripts/Commands/cs_npc.cpp
index 09cc7dd39a..66c50537ec 100644
--- a/src/server/scripts/Commands/cs_npc.cpp
+++ b/src/server/scripts/Commands/cs_npc.cpp
@@ -188,6 +188,7 @@ public:
{ "whisper", HandleNpcWhisperCommand, SEC_GAMEMASTER, Console::No },
{ "yell", HandleNpcYellCommand, SEC_GAMEMASTER, Console::No },
{ "tame", HandleNpcTameCommand, SEC_GAMEMASTER, Console::No },
+ { "do", HandleNpcDoActionCommand, SEC_GAMEMASTER, Console::No },
{ "add", npcAddCommandTable },
{ "delete", npcDeleteCommandTable },
{ "follow", npcFollowCommandTable },
@@ -1212,6 +1213,20 @@ public:
return true;
}
+ static bool HandleNpcDoActionCommand(ChatHandler* handler, uint32 actionId)
+ {
+ Creature* creature = handler->getSelectedCreature();
+ if (!creature)
+ {
+ handler->SendErrorMessage(LANG_SELECT_CREATURE);
+ return false;
+ }
+
+ creature->AI()->DoAction(actionId);
+ handler->PSendSysMessage(LANG_NPC_DO_ACTION, creature->GetGUID().ToString(), creature->GetEntry(), creature->GetName(), actionId);
+ return true;
+ }
+
static bool HandleNpcAddFormationCommand(ChatHandler* handler, ObjectGuid::LowType leaderGUID)
{
Creature* creature = handler->getSelectedCreature();
diff --git a/src/server/scripts/Commands/cs_pooltools.cpp b/src/server/scripts/Commands/cs_pooltools.cpp
new file mode 100644
index 0000000000..4bb4d448fd
--- /dev/null
+++ b/src/server/scripts/Commands/cs_pooltools.cpp
@@ -0,0 +1,419 @@
+/*
+ * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "Cell.h"
+#include "CellImpl.h"
+#include "Chat.h"
+#include "CommandScript.h"
+#include "GameObject.h"
+#include "GridNotifiers.h"
+#include "GridNotifiersImpl.h"
+#include "MapMgr.h"
+#include "Player.h"
+#include "ScriptMgr.h"
+#include "Tokenize.h"
+
+using namespace Acore::ChatCommands;
+
+struct PoolTemplateItem
+{
+ uint32 Entry;
+ uint32 Chance;
+};
+
+// Represents one "spawn point" which might contain multiple GUIDs
+struct NodeGroup
+{
+ float X = 0.0f;
+ float Y = 0.0f;
+ float Z = 0.0f;
+ std::vector<std::pair<uint32, uint32>> FoundObjects;
+};
+
+struct PoolSession
+{
+ std::string ZoneName;
+ std::vector<PoolTemplateItem> CurrentTemplate;
+ std::vector<NodeGroup> CapturedGroups;
+};
+
+static std::map<ObjectGuid, PoolSession> PoolSessions;
+
+class pooltools_commandscript : public CommandScript
+{
+public:
+ pooltools_commandscript() : CommandScript("pooltools_commandscript") {}
+
+ ChatCommandTable GetCommands() const override
+ {
+ static ChatCommandTable poolToolsCommandTable =
+ {
+ { "start", HandlePoolStart, SEC_ADMINISTRATOR, Console::No },
+ { "def", HandlePoolDef, SEC_ADMINISTRATOR, Console::No },
+ { "add", HandlePoolAdd, SEC_ADMINISTRATOR, Console::No },
+ { "remove", HandlePoolRemove, SEC_ADMINISTRATOR, Console::No },
+ { "end", HandlePoolEnd, SEC_ADMINISTRATOR, Console::No },
+ { "clear", HandlePoolClear, SEC_ADMINISTRATOR, Console::No }
+ };
+
+ static ChatCommandTable commandTable =
+ {
+ { "pooltools", poolToolsCommandTable }
+ };
+ return commandTable;
+ }
+
+ static bool HandlePoolStart(ChatHandler* handler, std::string description)
+ {
+ ObjectGuid const playerGuid = handler->GetPlayer()->GetGUID();
+
+ if (PoolSessions.find(playerGuid) != PoolSessions.end())
+ {
+ handler->SendErrorMessage("Session already active. Use .pooltools clear first.");
+ return false;
+ }
+
+ PoolSession session;
+ session.ZoneName = description;
+
+ PoolSessions[playerGuid] = session;
+
+ handler->PSendSysMessage("|cff00ff00Pool Session Started.|r Description: {}", description);
+ return true;
+ }
+
+ static bool HandlePoolDef(ChatHandler* handler, Tail args)
+ {
+ ObjectGuid playerGuid = handler->GetPlayer()->GetGUID();
+ if (PoolSessions.find(playerGuid) == PoolSessions.end())
+ {
+ handler->SendErrorMessage("No active session.");
+ return false;
+ }
+
+ std::vector<std::string_view> tokens = Acore::Tokenize(args, ' ', false);
+
+ if (tokens.empty() || tokens.size() % 2 != 0)
+ {
+ handler->SendErrorMessage("Invalid syntax. Usage: .pooltools def [ID] [Chance] [ID] [Chance]...");
+ return false;
+ }
+
+ std::vector<PoolTemplateItem> newTemplate;
+ for (size_t i = 0; i < tokens.size(); i += 2)
+ {
+ uint32 const entry = Acore::StringTo<uint32>(tokens[i]).value_or(0);
+ uint32 const chance = Acore::StringTo<uint32>(tokens[i + 1]).value_or(0);
+
+ if (entry == 0)
+ continue;
+
+ newTemplate.push_back({ entry, chance });
+ }
+
+ PoolSessions[playerGuid].CurrentTemplate = newTemplate;
+ handler->PSendSysMessage("Template Defined ({} items).", newTemplate.size());
+ return true;
+ }
+
+ static bool HandlePoolAdd(ChatHandler* handler, Optional<float> radiusArg)
+ {
+ ObjectGuid playerGuid = handler->GetPlayer()->GetGUID();
+ if (PoolSessions.find(playerGuid) == PoolSessions.end())
+ return false;
+
+ PoolSession& session = PoolSessions[playerGuid];
+ if (session.CurrentTemplate.empty())
+ {
+ handler->SendErrorMessage("Define a template first with .pooltools def");
+ return false;
+ }
+
+ Player* player = handler->GetPlayer();
+ float const radius = radiusArg.value_or(5.0f);
+
+ float searchX = player->GetPositionX();
+ float searchY = player->GetPositionY();
+ float searchZ = player->GetPositionZ();
+
+ GameObject* target = handler->GetNearbyGameObject();
+ if (radius <= 10.0f && target)
+ {
+ searchX = target->GetPositionX();
+ searchY = target->GetPositionY();
+ searchZ = target->GetPositionZ();
+ }
+
+ std::list<GameObject*> nearbyGOs;
+ Acore::GameObjectInRangeCheck check(searchX, searchY, searchZ, radius);
+ Acore::GameObjectListSearcher<Acore::GameObjectInRangeCheck> searcher(player, nearbyGOs, check);
+ Cell::VisitObjects(player, searcher, radius);
+
+ int addedCount = 0;
+ int newGroupsCount = 0;
+
+ for (GameObject* go : nearbyGOs)
+ {
+ if (go->GetDistance(searchX, searchY, searchZ) > radius)
+ continue;
+
+ bool isTemplateMatch = false;
+ for (auto const& tpl : session.CurrentTemplate)
+ {
+ if (go->GetEntry() == tpl.Entry)
+ {
+ isTemplateMatch = true;
+ break;
+ }
+ }
+ if (!isTemplateMatch)
+ continue;
+
+ uint32 const spawnId = go->GetSpawnId();
+
+ bool alreadyCaptured = false;
+ for (auto const& group : session.CapturedGroups)
+ {
+ for (auto const& obj : group.FoundObjects)
+ {
+ if (obj.second == spawnId)
+ {
+ alreadyCaptured = true;
+ break;
+ }
+ }
+ if (alreadyCaptured)
+ break;
+ }
+ if (alreadyCaptured)
+ continue;
+
+ // Clustering
+ NodeGroup* existingGroup = nullptr;
+ for (auto& group : session.CapturedGroups)
+ {
+ if (go->GetDistance(group.X, group.Y, group.Z) < 0.1f)
+ {
+ existingGroup = &group;
+ break;
+ }
+ }
+
+ if (existingGroup)
+ {
+ existingGroup->FoundObjects.push_back({ go->GetEntry(), spawnId });
+ addedCount++;
+ }
+ else
+ {
+ NodeGroup newGroup;
+ newGroup.X = go->GetPositionX();
+ newGroup.Y = go->GetPositionY();
+ newGroup.Z = go->GetPositionZ();
+ newGroup.FoundObjects.push_back({ go->GetEntry(), spawnId });
+
+ session.CapturedGroups.push_back(newGroup);
+ newGroupsCount++;
+ addedCount++;
+ }
+ }
+
+ if (addedCount == 0)
+ {
+ handler->SendErrorMessage("No new matching objects found in {}y radius.", radius);
+ return false;
+ }
+
+ handler->PSendSysMessage("|cff00ff00Scan Complete.|r Added {} objects into {} new groups.", addedCount, newGroupsCount);
+
+ if (!session.CapturedGroups.empty())
+ {
+ NodeGroup& lastGroup = session.CapturedGroups.back();
+ for (auto& p : lastGroup.FoundObjects)
+ {
+ handler->PSendSysMessage(" - Entry {} (GUID: {})", p.first, p.second);
+ }
+ }
+ return true;
+ }
+
+ static bool HandlePoolRemove(ChatHandler* handler)
+ {
+ ObjectGuid const playerGuid = handler->GetPlayer()->GetGUID();
+ if (PoolSessions.find(playerGuid) == PoolSessions.end())
+ {
+ handler->SendErrorMessage("No active session.");
+ return false;
+ }
+
+ PoolSession& session = PoolSessions[playerGuid];
+
+ if (session.CapturedGroups.empty())
+ {
+ handler->SendErrorMessage("No groups captured.");
+ return false;
+ }
+
+ NodeGroup removed = session.CapturedGroups.back();
+ session.CapturedGroups.pop_back();
+
+ handler->PSendSysMessage("|cffff0000Undo Successful.|r Removed last group containing {} objects.", removed.FoundObjects.size());
+ return true;
+ }
+
+ static bool HandlePoolEnd(ChatHandler* handler)
+ {
+ ObjectGuid playerGuid = handler->GetPlayer()->GetGUID();
+ auto it = PoolSessions.find(playerGuid);
+ if (it == PoolSessions.end())
+ return false;
+
+ PoolSession& session = it->second; // Use reference from iterator
+
+ auto EscapeSQL = [](std::string_view input) -> std::string {
+ std::string safe;
+ safe.reserve(input.size());
+ for (char c : input)
+ {
+ if (c == '\'')
+ safe += "\\'";
+ else
+ safe += c;
+ }
+ return safe;
+ };
+
+ bool const complexPool = (session.CurrentTemplate.size() > 1);
+
+ // SQL Variables and Header
+ LOG_DEBUG("sql.dev", "-- Pool Dump: {}", session.ZoneName);
+ LOG_DEBUG("sql.dev", "SET @mother_pool := @mother_pool+1;");
+
+ if (complexPool)
+ LOG_DEBUG("sql.dev", "SET @pool_node := @pool_node+1;");
+
+ LOG_DEBUG("sql.dev", "SET @max_limit := {};", (session.CapturedGroups.size() + 3) / 4);
+
+ // DELETEs section
+ if (!session.CapturedGroups.empty())
+ {
+ LOG_DEBUG("sql.dev", "-- Cleanup specific object links");
+ LOG_DEBUG("sql.dev", "DELETE FROM `pool_gameobject` WHERE `guid` IN (");
+
+ std::vector<std::string> guidList;
+ for (auto const& group : session.CapturedGroups)
+ for (auto const& obj : group.FoundObjects)
+ guidList.push_back(std::to_string(obj.second));
+
+ LOG_DEBUG("sql.dev", fmt::format("{}", fmt::join(guidList, ", ")));
+ LOG_DEBUG("sql.dev", ");\n");
+ }
+
+ LOG_DEBUG("sql.dev", "DELETE FROM `pool_template` WHERE `entry`=@mother_pool;");
+ LOG_DEBUG("sql.dev", "INSERT INTO `pool_template` (`entry`, `max_limit`, `description`) VALUES (@mother_pool, @max_limit, '{} - Mother Pool');", EscapeSQL(session.ZoneName));
+
+ int groupCounter = 0;
+
+ // We can buffer the simple bulk inserts here
+ std::vector<std::string> bulkInserts;
+
+ for (auto const& group : session.CapturedGroups)
+ {
+ groupCounter++;
+
+ // Generate Description
+ std::set<std::string> uniqueNames;
+ for (auto const& obj : group.FoundObjects)
+ {
+ GameObjectTemplate const* goInfo = sObjectMgr->GetGameObjectTemplate(obj.first);
+ uniqueNames.insert(goInfo ? goInfo->name : std::to_string(obj.first));
+ }
+
+ std::string groupDesc = fmt::format("{}", fmt::join(uniqueNames, " / "));
+ std::string safeGroupDesc = EscapeSQL(groupDesc);
+
+ // Simple pooling
+ if (!complexPool)
+ {
+ for (auto const& obj : group.FoundObjects)
+ {
+ float chance = 0.0f;
+ for (auto const& tpl : session.CurrentTemplate)
+ if (tpl.Entry == obj.first)
+ chance = (float)tpl.Chance;
+
+ bulkInserts.push_back(fmt::format("({}, @mother_pool, {}, '{} - {}')",
+ obj.second, chance, EscapeSQL(session.ZoneName), safeGroupDesc));
+ }
+ }
+ // Pool_pool integration
+ else
+ {
+ LOG_DEBUG("sql.dev", "-- Group {}", groupCounter);
+ LOG_DEBUG("sql.dev", "SET @pool_node := @pool_node + 1;");
+
+ // Create the Sub-Pool Node
+ LOG_DEBUG("sql.dev", "INSERT INTO `pool_template` (`entry`, `max_limit`, `description`) VALUES (@pool_node, 1, '{} - Node {}');", EscapeSQL(session.ZoneName), groupCounter);
+
+ // Link Node to Mother Pool
+ LOG_DEBUG("sql.dev", "INSERT INTO `pool_pool` (`pool_id`, `mother_pool`, `chance`, `description`) VALUES (@pool_node, @mother_pool, 0, '{} - {}');", EscapeSQL(session.ZoneName), safeGroupDesc);
+
+ // Link Objects to Sub-Pool Node
+ LOG_DEBUG("sql.dev", "INSERT INTO `pool_gameobject` (`guid`, `pool_entry`, `chance`, `description`) VALUES");
+
+ std::vector<std::string> nodeInserts;
+ for (auto const& obj : group.FoundObjects)
+ {
+ GameObjectTemplate const* goInfo = sObjectMgr->GetGameObjectTemplate(obj.first);
+ std::string objName = goInfo ? goInfo->name : "Unknown";
+
+ float chance = 0.0f;
+ for (auto const& tpl : session.CurrentTemplate)
+ if (tpl.Entry == obj.first) chance = (float)tpl.Chance;
+
+ nodeInserts.push_back(fmt::format("({}, @pool_node, {}, '{} - {}')",
+ obj.second, chance, EscapeSQL(session.ZoneName), EscapeSQL(objName)));
+ }
+ LOG_DEBUG("sql.dev", "{};", fmt::join(nodeInserts, ",\n"));
+ }
+ }
+
+ if (!complexPool && !bulkInserts.empty())
+ {
+ LOG_DEBUG("sql.dev", "INSERT INTO `pool_gameobject` (`guid`, `pool_entry`, `chance`, `description`) VALUES");
+ LOG_DEBUG("sql.dev", "{};", fmt::join(bulkInserts, ",\n"));
+ }
+
+ handler->PSendSysMessage("Dumped {} groups.", groupCounter);
+
+ // Cleanup
+ PoolSessions.erase(it);
+ return true;
+ }
+
+ static bool HandlePoolClear(ChatHandler* handler)
+ {
+ PoolSessions.erase(handler->GetPlayer()->GetGUID());
+ handler->PSendSysMessage("Session cleared.");
+ return true;
+ }
+};
+
+void AddSC_pooltools_commandscript()
+{
+ new pooltools_commandscript();
+}
diff --git a/src/server/scripts/Commands/cs_quest.cpp b/src/server/scripts/Commands/cs_quest.cpp
index c2ae75cb6a..9730b32210 100644
--- a/src/server/scripts/Commands/cs_quest.cpp
+++ b/src/server/scripts/Commands/cs_quest.cpp
@@ -37,6 +37,7 @@ public:
{ "complete", HandleQuestComplete, SEC_GAMEMASTER, Console::Yes },
{ "remove", HandleQuestRemove, SEC_GAMEMASTER, Console::Yes },
{ "reward", HandleQuestReward, SEC_GAMEMASTER, Console::Yes },
+ { "status", HandleQuestStatus, SEC_GAMEMASTER, Console::Yes },
};
static ChatCommandTable commandTable =
{
@@ -724,6 +725,53 @@ public:
handler->SetSentErrorMessage(false);
return true;
}
+
+ static bool HandleQuestStatus(ChatHandler* handler, Quest const* quest, Optional<PlayerIdentifier> playerTarget)
+ {
+ if (!playerTarget)
+ playerTarget = PlayerIdentifier::FromTargetOrSelf(handler);
+
+ if (!playerTarget)
+ {
+ handler->SendErrorMessage(LANG_PLAYER_NOT_FOUND);
+ return false;
+ }
+
+ uint32 entry = quest->GetQuestId();
+ std::string status;
+ if (Player* player = playerTarget->GetConnectedPlayer())
+ {
+ QuestStatus qs = player->GetQuestStatus(entry);
+ switch (qs)
+ {
+ case QUEST_STATUS_NONE:
+ status = "Not Taken";
+ break;
+ case QUEST_STATUS_COMPLETE:
+ status = "Complete";
+ break;
+ case QUEST_STATUS_INCOMPLETE:
+ status = "Incomplete";
+ break;
+ case QUEST_STATUS_FAILED:
+ status = "Failed";
+ break;
+ case QUEST_STATUS_REWARDED:
+ status = "Rewarded";
+ break;
+ default:
+ status = "Unknown";
+ break;
+ }
+
+ handler->PSendSysMessage(LANG_CMD_QUEST_STATUS, quest->GetTitle(), entry, status);
+
+ if (!player->CanTakeQuest(quest, true))
+ handler->PSendSysMessage(LANG_CMD_QUEST_UNAVAILABLE, entry, status);
+ }
+
+ return true;
+ }
};
void AddSC_quest_commandscript()
diff --git a/src/server/scripts/Commands/cs_reload.cpp b/src/server/scripts/Commands/cs_reload.cpp
index 4a58d73b92..03ce1d1854 100644
--- a/src/server/scripts/Commands/cs_reload.cpp
+++ b/src/server/scripts/Commands/cs_reload.cpp
@@ -129,7 +129,7 @@ public:
{ "mail_server_template", HandleReloadMailServerTemplateCommand, SEC_ADMINISTRATOR, Console::Yes },
{ "milling_loot_template", HandleReloadLootTemplatesMillingCommand, SEC_ADMINISTRATOR, Console::Yes },
{ "npc_spellclick_spells", HandleReloadSpellClickSpellsCommand, SEC_ADMINISTRATOR, Console::Yes },
- { "npc_trainer", HandleReloadNpcTrainerCommand, SEC_ADMINISTRATOR, Console::Yes },
+ { "trainer", HandleReloadTrainerCommand, SEC_ADMINISTRATOR, Console::Yes },
{ "npc_vendor", HandleReloadNpcVendorCommand, SEC_ADMINISTRATOR, Console::Yes },
{ "game_event_npc_vendor", HandleReloadGameEventNPCVendorCommand, SEC_ADMINISTRATOR, Console::Yes },
{ "page_text", HandleReloadPageTextsCommand, SEC_ADMINISTRATOR, Console::Yes },
@@ -254,7 +254,7 @@ public:
static bool HandleReloadAllNpcCommand(ChatHandler* handler)
{
- HandleReloadNpcTrainerCommand(handler);
+ HandleReloadTrainerCommand(handler);
HandleReloadNpcVendorCommand(handler);
HandleReloadPointsOfInterestCommand(handler);
HandleReloadSpellClickSpellsCommand(handler);
@@ -746,11 +746,15 @@ public:
return true;
}
- static bool HandleReloadNpcTrainerCommand(ChatHandler* handler)
+ static bool HandleReloadTrainerCommand(ChatHandler* handler)
{
- LOG_INFO("server.loading", "Reloading `npc_trainer` Table!");
- sObjectMgr->LoadTrainerSpell();
- handler->SendGlobalGMSysMessage("DB table `npc_trainer` reloaded.");
+ LOG_INFO("server.loading", "Reloading `trainer` Tables!");
+ sObjectMgr->LoadTrainers();
+ sObjectMgr->LoadCreatureDefaultTrainers();
+ handler->SendGlobalGMSysMessage("DB table `trainer` reloaded.");
+ handler->SendGlobalGMSysMessage("DB table `trainer_locale` reloaded.");
+ handler->SendGlobalGMSysMessage("DB table `trainer_spell` reloaded.");
+ handler->SendGlobalGMSysMessage("DB table `creature_default_trainer` reloaded.");
return true;
}
diff --git a/src/server/scripts/Commands/cs_script_loader.cpp b/src/server/scripts/Commands/cs_script_loader.cpp
index d07ab959f1..7e667a66cc 100644
--- a/src/server/scripts/Commands/cs_script_loader.cpp
+++ b/src/server/scripts/Commands/cs_script_loader.cpp
@@ -49,6 +49,7 @@ void AddSC_modify_commandscript();
void AddSC_npc_commandscript();
void AddSC_pet_commandscript();
void AddSC_player_commandscript();
+void AddSC_pooltools_commandscript();
void AddSC_quest_commandscript();
void AddSC_reload_commandscript();
void AddSC_reset_commandscript();
@@ -101,6 +102,7 @@ void AddCommandsScripts()
AddSC_npc_commandscript();
AddSC_pet_commandscript();
AddSC_player_commandscript();
+ AddSC_pooltools_commandscript();
AddSC_quest_commandscript();
AddSC_reload_commandscript();
AddSC_reset_commandscript();
diff --git a/src/server/scripts/EasternKingdoms/Karazhan/karazhan.cpp b/src/server/scripts/EasternKingdoms/Karazhan/karazhan.cpp
index bfe5e6db10..484a0ada74 100644
--- a/src/server/scripts/EasternKingdoms/Karazhan/karazhan.cpp
+++ b/src/server/scripts/EasternKingdoms/Karazhan/karazhan.cpp
@@ -218,29 +218,22 @@ public:
switch (m_uiEventId)
{
case EVENT_OZ:
- if (OzDialogue[count].textid)
- text = OzDialogue[count].textid;
- if (OzDialogue[count].timer)
- TalkTimer = OzDialogue[count].timer;
+ text = OzDialogue[count].textid;
+ TalkTimer = OzDialogue[count].timer;
break;
-
case EVENT_HOOD:
- if (HoodDialogue[count].textid)
- text = HoodDialogue[count].textid;
- if (HoodDialogue[count].timer)
- TalkTimer = HoodDialogue[count].timer;
+ text = HoodDialogue[count].textid;
+ TalkTimer = HoodDialogue[count].timer;
break;
-
case EVENT_RAJ:
- if (RAJDialogue[count].textid)
- text = RAJDialogue[count].textid;
- if (RAJDialogue[count].timer)
- TalkTimer = RAJDialogue[count].timer;
+ text = RAJDialogue[count].textid;
+ TalkTimer = RAJDialogue[count].timer;
break;
+ default:
+ return;
}
- if (text)
- CreatureAI::Talk(text);
+ CreatureAI::Talk(text);
}
void PrepareEncounter()
diff --git a/src/server/scripts/EasternKingdoms/ZulGurub/boss_arlokk.cpp b/src/server/scripts/EasternKingdoms/ZulGurub/boss_arlokk.cpp
index 584e05f6f2..83275a1793 100644
--- a/src/server/scripts/EasternKingdoms/ZulGurub/boss_arlokk.cpp
+++ b/src/server/scripts/EasternKingdoms/ZulGurub/boss_arlokk.cpp
@@ -78,6 +78,10 @@ Position const PosMoveOnSpawn[1] =
{ -11561.9f, -1627.868f, 41.29941f, 0.0f }
};
+// hack
+float const DamageIncrease = 35.0f;
+float const DamageDecrease = 100.f / (1.f + DamageIncrease / 100.f) - 100.f;
+
class boss_arlokk : public CreatureScript
{
public:
@@ -90,7 +94,7 @@ public:
void Reset() override
{
if (events.IsInPhase(PHASE_TWO))
- me->HandleStatModifier(UNIT_MOD_DAMAGE_MAINHAND, TOTAL_PCT, 35.0f, false); // hack
+ me->ApplyStatPctModifier(UNIT_MOD_DAMAGE_MAINHAND, TOTAL_PCT, DamageDecrease); // hack
_Reset();
_summonCountA = 0;
_summonCountB = 0;
@@ -253,7 +257,7 @@ public:
events.ScheduleEvent(EVENT_RAVAGE, 10s, 14s, 0, PHASE_TWO);
events.ScheduleEvent(EVENT_TRANSFORM_BACK, 30s, 40s, 0, PHASE_TWO);
events.SetPhase(PHASE_TWO);
- me->HandleStatModifier(UNIT_MOD_DAMAGE_MAINHAND, TOTAL_PCT, 35.0f, true); // hack
+ me->ApplyStatPctModifier(UNIT_MOD_DAMAGE_MAINHAND, TOTAL_PCT, DamageIncrease); // hack
break;
case EVENT_RAVAGE:
DoCastVictim(SPELL_RAVAGE, true);
@@ -265,7 +269,7 @@ public:
DoCast(me, SPELL_VANISH_VISUAL);
me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 0, uint32(WEAPON_DAGGER));
me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 1, uint32(WEAPON_DAGGER));
- me->HandleStatModifier(UNIT_MOD_DAMAGE_MAINHAND, TOTAL_PCT, 35.0f, false); // hack
+ me->ApplyStatPctModifier(UNIT_MOD_DAMAGE_MAINHAND, TOTAL_PCT, DamageDecrease); // hack
events.ScheduleEvent(EVENT_SHADOW_WORD_PAIN, 4s, 7s, 0, PHASE_ONE);
events.ScheduleEvent(EVENT_GOUGE, 12s, 15s, 0, PHASE_ONE);
events.ScheduleEvent(EVENT_TRANSFORM, 30s, 0, PHASE_ONE);
diff --git a/src/server/scripts/EasternKingdoms/ZulGurub/boss_marli.cpp b/src/server/scripts/EasternKingdoms/ZulGurub/boss_marli.cpp
index d453d5e2f7..b350b680e5 100644
--- a/src/server/scripts/EasternKingdoms/ZulGurub/boss_marli.cpp
+++ b/src/server/scripts/EasternKingdoms/ZulGurub/boss_marli.cpp
@@ -73,6 +73,10 @@ enum Misc
GO_SPIDER_EGGS = 179985,
};
+// hack
+float const DamageIncrease = 35.0f;
+float const DamageDecrease = 100.f / (1.f + DamageIncrease / 100.f) - 100.f;
+
// High Priestess Mar'li (14510)
struct boss_marli : public BossAI
{
@@ -84,7 +88,7 @@ public:
if (_phase == PHASE_SPIDER)
{
me->RemoveAura(SPELL_SPIDER_FORM);
- me->HandleStatModifier(UNIT_MOD_DAMAGE_MAINHAND, TOTAL_PCT, 35.0f, false);
+ me->ApplyStatPctModifier(UNIT_MOD_DAMAGE_MAINHAND, TOTAL_PCT, DamageDecrease);
_phase = PHASE_TROLL;
}
@@ -143,7 +147,7 @@ private:
me->RemoveAura(SPELL_SPIDER_FORM);
DoCastSelf(SPELL_TRANSFORM_BACK, true);
Talk(SAY_TRANSFORM_BACK);
- me->HandleStatModifier(UNIT_MOD_DAMAGE_MAINHAND, TOTAL_PCT, 35.0f, false);
+ me->ApplyStatPctModifier(UNIT_MOD_DAMAGE_MAINHAND, TOTAL_PCT, DamageDecrease);
scheduler.CancelGroup(PHASE_SPIDER);
}
@@ -186,7 +190,7 @@ private:
Talk(SAY_TRANSFORM);
DoCastSelf(SPELL_SPIDER_FORM, true);
- me->HandleStatModifier(UNIT_MOD_DAMAGE_MAINHAND, TOTAL_PCT, 35.0f, true);
+ me->ApplyStatPctModifier(UNIT_MOD_DAMAGE_MAINHAND, TOTAL_PCT, DamageIncrease);
scheduler.Schedule(5s, PHASE_SPIDER, [this](TaskContext context)
{
diff --git a/src/server/scripts/EasternKingdoms/zone_elwynn_forest.cpp b/src/server/scripts/EasternKingdoms/zone_elwynn_forest.cpp
index 383704d7ff..1fff7357c2 100644
--- a/src/server/scripts/EasternKingdoms/zone_elwynn_forest.cpp
+++ b/src/server/scripts/EasternKingdoms/zone_elwynn_forest.cpp
@@ -429,17 +429,17 @@ struct npc_eastvale_peasent : public ScriptedAI
switch (_path)
{
case PATH_PEASENT_0:
- me->PlayDirectSound(SOUND_PEASENT_GREETING_1);
+ me->PlayDistanceSound(SOUND_PEASENT_GREETING_1);
_events.ScheduleEvent(EVENT_TALKTORAELEN2, 2s);
break;
case PATH_PEASENT_1:
case PATH_PEASENT_3:
- me->PlayDirectSound(SOUND_PEASENT_GREETING_3);
+ me->PlayDistanceSound(SOUND_PEASENT_GREETING_3);
_events.ScheduleEvent(EVENT_RAELENTALK, 2s);
break;
case PATH_PEASENT_2:
case PATH_PEASENT_4:
- me->PlayDirectSound(SOUND_PEASENT_GREETING_2);
+ me->PlayDistanceSound(SOUND_PEASENT_GREETING_2);
_events.ScheduleEvent(EVENT_RAELENTALK, 2s);
break;
}
@@ -451,7 +451,7 @@ struct npc_eastvale_peasent : public ScriptedAI
}
break;
case EVENT_TALKTORAELEN2:
- me->PlayDirectSound(SOUND_PEASENT_GREETING_2);
+ me->PlayDistanceSound(SOUND_PEASENT_GREETING_2);
_events.ScheduleEvent(EVENT_RAELENTALK, 2s);
break;
case EVENT_RAELENTALK:
@@ -466,27 +466,27 @@ struct npc_eastvale_peasent : public ScriptedAI
switch (_path)
{
case PATH_PEASENT_0:
- me->PlayDirectSound(SOUND_PEASENT_LEAVING_1);
+ me->PlayDistanceSound(SOUND_PEASENT_LEAVING_1);
_events.ScheduleEvent(EVENT_PATHBACK, 2s);
break;
case PATH_PEASENT_1:
case PATH_PEASENT_3:
- me->PlayDirectSound(SOUND_PEASENT_LEAVING_4);
+ me->PlayDistanceSound(SOUND_PEASENT_LEAVING_4);
_events.ScheduleEvent(EVENT_TALKTORAELEN4, 2s);
break;
case PATH_PEASENT_2:
- me->PlayDirectSound(SOUND_PEASENT_LEAVING_3);
+ me->PlayDistanceSound(SOUND_PEASENT_LEAVING_3);
_events.ScheduleEvent(EVENT_PATHBACK, 2s);
break;
case PATH_PEASENT_4:
- me->PlayDirectSound(SOUND_PEASENT_LEAVING_2);
+ me->PlayDistanceSound(SOUND_PEASENT_LEAVING_2);
_events.ScheduleEvent(EVENT_PATHBACK, 2s);
break;
}
}
break;
case EVENT_TALKTORAELEN4:
- me->PlayDirectSound(SOUND_PEASENT_LEAVING_5);
+ me->PlayDistanceSound(SOUND_PEASENT_LEAVING_5);
_events.ScheduleEvent(EVENT_PATHBACK, 2s);
break;
case EVENT_PATHBACK:
diff --git a/src/server/scripts/Kalimdor/CavernsOfTime/CullingOfStratholme/culling_of_stratholme.cpp b/src/server/scripts/Kalimdor/CavernsOfTime/CullingOfStratholme/culling_of_stratholme.cpp
index 5db46cd4c2..bd36b5db76 100644
--- a/src/server/scripts/Kalimdor/CavernsOfTime/CullingOfStratholme/culling_of_stratholme.cpp
+++ b/src/server/scripts/Kalimdor/CavernsOfTime/CullingOfStratholme/culling_of_stratholme.cpp
@@ -1297,7 +1297,7 @@ void npc_arthas::npc_arthasAI::ReorderInstance(uint32 data)
if (data >= COS_PROGRESS_KILLED_EPOCH)
if (pInstance)
if (GameObject* pGate = pInstance->instance->GetGameObject(pInstance->GetGuidData(DATA_SHKAF_GATE)))
- pGate->SetGoState(GO_STATE_READY);
+ pGate->SetGoState(GO_STATE_ACTIVE);
pInstance->SetData(DATA_SHOW_INFINITE_TIMER, 1);
}
diff --git a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/azjol_nerub.h b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/azjol_nerub.h
index e5c5ef9e42..7dbf96aff1 100644
--- a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/azjol_nerub.h
+++ b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/azjol_nerub.h
@@ -29,7 +29,11 @@ enum ANData
DATA_KRIKTHIR = 0,
DATA_HADRONOX = 1,
DATA_ANUBARAK = 2,
- MAX_ENCOUNTERS = 3
+ MAX_ENCOUNTERS = 3,
+
+ DATA_GASHRA = 4,
+ DATA_NARJIL = 5,
+ DATA_SILTHIK = 6
};
enum ANIds
@@ -37,12 +41,16 @@ enum ANIds
NPC_WATCHER_NARJIL = 28729,
NPC_WATCHER_GASHRA = 28730,
NPC_WATCHER_SILTHIK = 28731,
+ NPC_ANUBAR_SKIRMISHER = 28734,
+ NPC_ANUBAR_SHADOWCASTER = 28733,
+ NPC_ANUBAR_WARRIOR = 28732,
NPC_SKITTERING_SWARMER = 28735,
NPC_SKITTERING_INFECTIOR = 28736,
NPC_KRIKTHIR_THE_GATEWATCHER = 28684,
NPC_HADRONOX = 28921,
NPC_ANUBARAK = 29120,
+ NPC_WORLD_TRIGGER_LAOI = 23472,
NPC_ANUB_AR_CHAMPION = 29062,
NPC_ANUB_AR_NECROMANCER = 29063,
NPC_ANUB_AR_CRYPTFIEND = 29064,
@@ -55,6 +63,11 @@ enum ANIds
SPELL_WEB_WRAP_TRIGGER = 52087
};
+enum ANActions
+{
+ ACTION_MINION_DIED = 2,
+};
+
template <class AI, class T>
inline AI* GetAzjolNerubAI(T* obj)
{
diff --git a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_anubarak.cpp b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_anubarak.cpp
index dfddd2b715..640bb9c98a 100644
--- a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_anubarak.cpp
+++ b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_anubarak.cpp
@@ -75,15 +75,14 @@ enum Events
enum CreatureIds
{
- NPC_WORLD_TRIGGER = 22515,
NPC_ANUBAR_GUARDIAN = 29216,
NPC_ANUBAR_VENOMANCER = 29217,
};
-enum Phases : uint8
+enum Groups : uint8
{
- PHASE_EMERGED = 1,
- PHASE_SUBMERGED
+ GROUP_EMERGED = 1,
+ GROUP_SUBMERGED
};
enum SubPhase : uint8
@@ -117,7 +116,7 @@ struct boss_anub_arak : public BossAI
_submergePhase = SUBMERGE_NONE;
ScheduleHealthCheckEvent({ 75, 50, 25 }, [&]{
- events.Reset();
+ events.CancelEventGroup(GROUP_EMERGED);
Talk(SAY_SUBMERGE);
DoCastSelf(SPELL_CLEAR_ALL_DEBUFFS, true);
DoCastSelf(SPELL_SUBMERGE, false);
@@ -133,75 +132,74 @@ struct boss_anub_arak : public BossAI
DoCastSelf(SPELL_IMPALE_PERIODIC, true);
++_submergePhase;
- events.Reset();
ScheduleSubmerged();
}
}
void ScheduleEmerged()
{
- events.SetPhase(PHASE_EMERGED);
- events.ScheduleEvent(EVENT_CARRION_BEETLES, 6500ms, 0, PHASE_EMERGED);
- events.ScheduleEvent(EVENT_LEECHING_SWARM, 20s, 0, PHASE_EMERGED);
- events.ScheduleEvent(EVENT_POUND, 15s, 0, PHASE_EMERGED);
+ events.CancelEventGroup(GROUP_SUBMERGED);
+ events.ScheduleEvent(EVENT_CARRION_BEETLES, 6500ms, GROUP_EMERGED);
+ events.ScheduleEvent(EVENT_LEECHING_SWARM, 20s, GROUP_EMERGED);
+ events.ScheduleEvent(EVENT_POUND, 15s, GROUP_EMERGED);
};
void ScheduleSubmerged()
{
- events.SetPhase(PHASE_SUBMERGED);
- events.ScheduleEvent(EVENT_EMERGE, 60s, 0, PHASE_SUBMERGED);
+ events.CancelEventGroup(GROUP_EMERGED);
+ events.ScheduleEvent(EVENT_EMERGE, 60s, GROUP_SUBMERGED);
switch (_submergePhase)
{
case SUBMERGE_75:
- events.ScheduleEvent(EVENT_SUMMON_GUARDIAN, 4s, 0, PHASE_SUBMERGED);
+ events.ScheduleEvent(EVENT_SUMMON_GUARDIAN, 4s, GROUP_SUBMERGED);
if (IsHeroic())
- events.ScheduleEvent(EVENT_SUMMON_GUARDIAN, 7s, 0, PHASE_SUBMERGED);
+ events.ScheduleEvent(EVENT_SUMMON_GUARDIAN, 7s, GROUP_SUBMERGED);
_remainingLargeSummonsBeforeEmerge = IsHeroic() ? 2 : 1;
- events.ScheduleEvent(EVENT_SUMMON_ASSASSINS, 4s, 0, PHASE_SUBMERGED);
- events.ScheduleEvent(EVENT_SUMMON_ASSASSINS, 24s, 0, PHASE_SUBMERGED);
- events.ScheduleEvent(EVENT_SUMMON_ASSASSINS, 44s, 0, PHASE_SUBMERGED);
+ events.ScheduleEvent(EVENT_SUMMON_ASSASSINS, 4s, GROUP_SUBMERGED);
+ events.ScheduleEvent(EVENT_SUMMON_ASSASSINS, 24s, GROUP_SUBMERGED);
+ events.ScheduleEvent(EVENT_SUMMON_ASSASSINS, 44s, GROUP_SUBMERGED);
break;
case SUBMERGE_50:
- events.ScheduleEvent(EVENT_SUMMON_GUARDIAN, 4s, 0, PHASE_SUBMERGED);
+ events.ScheduleEvent(EVENT_SUMMON_GUARDIAN, 4s, GROUP_SUBMERGED);
if (IsHeroic())
- events.ScheduleEvent(EVENT_SUMMON_GUARDIAN, 7s, 0, PHASE_SUBMERGED);
+ events.ScheduleEvent(EVENT_SUMMON_GUARDIAN, 7s, GROUP_SUBMERGED);
- events.ScheduleEvent(EVENT_SUMMON_VENOMANCER, 24s, 0, PHASE_SUBMERGED);
+ events.ScheduleEvent(EVENT_SUMMON_VENOMANCER, 24s, GROUP_SUBMERGED);
if (IsHeroic())
- events.ScheduleEvent(EVENT_SUMMON_VENOMANCER, 29s, 0, PHASE_SUBMERGED);
+ events.ScheduleEvent(EVENT_SUMMON_VENOMANCER, 29s, GROUP_SUBMERGED);
_remainingLargeSummonsBeforeEmerge = IsHeroic() ? 4 : 2;
- events.ScheduleEvent(EVENT_SUMMON_ASSASSINS, 4s, 0, PHASE_SUBMERGED);
- events.ScheduleEvent(EVENT_SUMMON_ASSASSINS, 24s, 0, PHASE_SUBMERGED);
- events.ScheduleEvent(EVENT_SUMMON_ASSASSINS, 44s, 0, PHASE_SUBMERGED);
+ events.ScheduleEvent(EVENT_SUMMON_ASSASSINS, 4s, GROUP_SUBMERGED);
+ events.ScheduleEvent(EVENT_SUMMON_ASSASSINS, 24s, GROUP_SUBMERGED);
+ events.ScheduleEvent(EVENT_SUMMON_ASSASSINS, 44s, GROUP_SUBMERGED);
break;
case SUBMERGE_25:
- events.ScheduleEvent(EVENT_SUMMON_GUARDIAN, 4s, 0, PHASE_SUBMERGED);
+ events.ScheduleEvent(EVENT_SUMMON_GUARDIAN, 4s, GROUP_SUBMERGED);
if (IsHeroic())
- events.ScheduleEvent(EVENT_SUMMON_GUARDIAN, 7s, 0, PHASE_SUBMERGED);
+ events.ScheduleEvent(EVENT_SUMMON_GUARDIAN, 7s, GROUP_SUBMERGED);
- events.ScheduleEvent(EVENT_SUMMON_VENOMANCER, 24s, 0, PHASE_SUBMERGED);
+ events.ScheduleEvent(EVENT_SUMMON_VENOMANCER, 24s, GROUP_SUBMERGED);
if (IsHeroic())
- events.ScheduleEvent(EVENT_SUMMON_VENOMANCER, 29s, 0, PHASE_SUBMERGED);
+ events.ScheduleEvent(EVENT_SUMMON_VENOMANCER, 29s, GROUP_SUBMERGED);
_remainingLargeSummonsBeforeEmerge = IsHeroic() ? 4 : 2;
- events.ScheduleEvent(EVENT_SUMMON_ASSASSINS, 4s, 0, PHASE_SUBMERGED);
- events.ScheduleEvent(EVENT_SUMMON_DARTER, 4s, 0, PHASE_SUBMERGED);
+ events.ScheduleEvent(EVENT_SUMMON_ASSASSINS, 4s, GROUP_SUBMERGED);
+ events.ScheduleEvent(EVENT_SUMMON_DARTER, 4s, GROUP_SUBMERGED);
- events.ScheduleEvent(EVENT_SUMMON_DARTER, 12s, 0, PHASE_SUBMERGED);
+ events.ScheduleEvent(EVENT_SUMMON_DARTER, 12s, GROUP_SUBMERGED);
- events.ScheduleEvent(EVENT_SUMMON_ASSASSINS, 24s, 0, PHASE_SUBMERGED);
- events.ScheduleEvent(EVENT_SUMMON_DARTER, 26s, 0, PHASE_SUBMERGED);
+ events.ScheduleEvent(EVENT_SUMMON_ASSASSINS, 24s, GROUP_SUBMERGED);
+ events.ScheduleEvent(EVENT_SUMMON_DARTER, 26s, GROUP_SUBMERGED);
- events.ScheduleEvent(EVENT_SUMMON_DARTER, 32s, 0, PHASE_SUBMERGED);
+ events.ScheduleEvent(EVENT_SUMMON_DARTER, 32s, GROUP_SUBMERGED);
- events.ScheduleEvent(EVENT_SUMMON_ASSASSINS, 44s, 0, PHASE_SUBMERGED);
- events.ScheduleEvent(EVENT_SUMMON_DARTER, 45s, 0, PHASE_SUBMERGED);
+ events.ScheduleEvent(EVENT_SUMMON_ASSASSINS, 44s, GROUP_SUBMERGED);
+ events.ScheduleEvent(EVENT_SUMMON_DARTER, 45s, GROUP_SUBMERGED);
break;
default:
break;
@@ -213,9 +211,8 @@ struct boss_anub_arak : public BossAI
Talk(SAY_AGGRO);
instance->DoStartTimedAchievement(ACHIEVEMENT_TIMED_TYPE_EVENT, ACHIEV_TIMED_START_EVENT);
- events.SetPhase(PHASE_EMERGED);
- events.ScheduleEvent(EVENT_CLOSE_DOORS, 5s, 0, PHASE_EMERGED);
ScheduleEmerged();
+ events.ScheduleEvent(EVENT_CLOSE_DOORS, 5s);
// set up world triggers
std::list<TempSummon*> summoned;
@@ -285,9 +282,8 @@ struct boss_anub_arak : public BossAI
--_remainingLargeSummonsBeforeEmerge;
if (_remainingLargeSummonsBeforeEmerge == 0)
{
- events.Reset();
- events.SetPhase(PHASE_SUBMERGED);
- events.ScheduleEvent(EVENT_EMERGE, 5s, 0, PHASE_SUBMERGED);
+ me->RemoveAurasDueToSpell(SPELL_IMPALE_PERIODIC);
+ events.RescheduleEvent(EVENT_EMERGE, 5s, GROUP_SUBMERGED);
}
break;
}
@@ -319,12 +315,12 @@ struct boss_anub_arak : public BossAI
break;
case EVENT_CARRION_BEETLES:
DoCastSelf(SPELL_CARRION_BEETLES);
- events.ScheduleEvent(EVENT_CARRION_BEETLES, 25s, 0, PHASE_EMERGED);
+ events.ScheduleEvent(EVENT_CARRION_BEETLES, 25s, GROUP_EMERGED);
break;
case EVENT_LEECHING_SWARM:
Talk(SAY_LOCUST);
DoCastSelf(SPELL_LEECHING_SWARM);
- events.ScheduleEvent(EVENT_LEECHING_SWARM, 20s, 0, PHASE_EMERGED);
+ events.ScheduleEvent(EVENT_LEECHING_SWARM, 20s, GROUP_EMERGED);
break;
case EVENT_POUND:
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 10.0f))
@@ -332,10 +328,10 @@ struct boss_anub_arak : public BossAI
DoCastSelf(SPELL_SELF_ROOT, true);
me->DisableRotate(true);
me->SendMovementFlagUpdate();
- events.ScheduleEvent(EVENT_ENABLE_ROTATE, 3300ms, 0, PHASE_EMERGED);
+ events.ScheduleEvent(EVENT_ENABLE_ROTATE, 3300ms, GROUP_EMERGED);
DoCast(target, SPELL_POUND);
}
- events.ScheduleEvent(EVENT_POUND, 18s, 0, PHASE_EMERGED);
+ events.ScheduleEvent(EVENT_POUND, 18s, GROUP_EMERGED);
break;
case EVENT_ENABLE_ROTATE:
me->RemoveAurasDueToSpell(SPELL_SELF_ROOT);
@@ -376,10 +372,10 @@ struct boss_anub_arak : public BossAI
void DamageTaken(Unit* attacker, uint32& damage, DamageEffectType damagetype, SpellSchoolMask damageSchoolMask) override
{
+ BossAI::DamageTaken(attacker, damage, damagetype, damageSchoolMask);
+
if (me->HasAura(SPELL_SUBMERGE) && damage >= me->GetHealth())
damage = me->GetHealth() - 1;
-
- BossAI::DamageTaken(attacker, damage, damagetype, damageSchoolMask);
}
private:
diff --git a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_hadronox.cpp b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_hadronox.cpp
index c4bc94518b..2264c6e3d8 100644
--- a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_hadronox.cpp
+++ b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_hadronox.cpp
@@ -26,19 +26,39 @@
enum Spells
{
+ // World Trigger
SPELL_SUMMON_ANUBAR_CHAMPION = 53064,
SPELL_SUMMON_ANUBAR_CRYPT_FIEND = 53065,
SPELL_SUMMON_ANUBAR_NECROMANCER = 53066,
+ SPELL_SUMMON_ANUBAR_CHAMPION_PERIODIC = 53035,
+ SPELL_SUMMON_ANUBAR_NECROMANCER_PERIODIC = 53036,
+ SPELL_SUMMON_ANUBAR_CRYPT_FIEND_PERIODIC = 53037,
+
+ // Hadronox
SPELL_WEB_FRONT_DOORS = 53177,
SPELL_WEB_SIDE_DOORS = 53185,
SPELL_ACID_CLOUD = 53400,
SPELL_LEECH_POISON = 53030,
SPELL_LEECH_POISON_HEAL = 53800,
- SPELL_WEB_GRAB = 57731,
+ SPELL_WEB_GRAB = 53406,
SPELL_PIERCE_ARMOR = 53418,
+ // Anub'ar Crusher
SPELL_SMASH = 53318,
- SPELL_FRENZY = 53801
+ SPELL_FRENZY = 53801,
+
+ // Anub'ar Champion
+ SPELL_REND = 59343,
+ SPELL_PUMMEL = 59344,
+
+ // Anub'ar Crypt Guard
+ SPELL_CRUSHING_WEBS = 59347,
+ SPELL_INFECTED_WOUND = 59348,
+
+ // Anub'ar Necromancer
+ SPELL_SHADOW_BOLT = 53333,
+ SPELL_ANIMATE_BONES_1 = 53334,
+ SPELL_ANIMATE_BONES_2 = 53336,
};
enum Events
@@ -54,274 +74,533 @@ enum Events
EVENT_HADRONOX_SUMMON = 9,
EVENT_CRUSHER_SMASH = 20,
- EVENT_CHECK_HEALTH = 21
+ EVENT_CHECK_HEALTH = 21,
+ EVENT_CHECK_EVADE = 22,
+
+ // Anub'ar Champion
+ EVENT_REND,
+ EVENT_PUMMEL,
+
+ // Anub'ar Crypt Guard
+ EVENT_CRUSHING_WEBS,
+ EVENT_INFECTED_WOUND,
+
+ // Anub'ar Necromancer
+ EVENT_SHADOW_BOLT,
+ EVENT_ANIMATE_BONES
};
-enum Misc
+enum NPCs
{
- NPC_ANUB_AR_CRUSHER = 28922,
+ NPC_ANUB_AR_CRUSHER = 28922,
+ NPC_ANUB_AR_CHAMPION_PACK = 29117,
+ NPC_ANUB_AR_CRYPT_FIEND_PACK = 29118,
+ NPC_ANUB_AR_NECROMANCER_PACK = 29119,
+};
- SAY_CRUSHER_AGGRO = 0,
- SAY_CRUSHER_EMOTE = 1,
- SAY_HADRONOX_EMOTE = 0,
+enum SummonGroups : uint32
+{
+ SUMMON_GROUP_CRUSHER_NONE = 0,
+ SUMMON_GROUP_CRUSHER_1 = 1,
+ SUMMON_GROUP_CRUSHER_2 = 2,
+ SUMMON_GROUP_CRUSHER_3 = 3,
+ SUMMON_GROUP_WORLD_TRIGGERS = 4,
+};
- ACTION_DESPAWN_ADDS = 1,
- ACTION_START_EVENT = 2
+enum Data
+{
+ DATA_CRUSHER_PACK_ID = 1,
};
-const Position hadronoxSteps[4] =
+enum Misc
{
- {607.9f, 512.8f, 695.3f, 0.0f},
- {611.67f, 564.11f, 720.0f, 0.0f},
- {576.1f, 580.0f, 727.5f, 0.0f},
- {534.87f, 554.0f, 733.0f, 0.0f}
+ SAY_CRUSHER_AGGRO = 1,
+ SAY_CRUSHER_EMOTE = 2,
+ SAY_HADRONOX_EMOTE = 0,
+
+ ACTION_CRUSHER_ENGAGED = 1,
+ ACTION_CRUSHER_DIED = 2,
+ ACTION_PACK_WALK = 3,
};
-class boss_hadronox : public CreatureScript
+static const std::array<Position, 3> hadronoxSteps =
+{{
+ { 562.191f, 514.068f, 696.50710f },
+ { 615.802f, 517.418f, 695.68066f },
+ { 530.420f, 560.003f, 733.22473f },
+}};
+
+struct boss_hadronox : public BossAI
{
-public:
- boss_hadronox() : CreatureScript("boss_hadronox") { }
+ explicit boss_hadronox(Creature* creature) : BossAI(creature, DATA_HADRONOX), _crushersLeft(0), _doorsWebbed(false), _lastPlayerCombatState(false) { }
- struct boss_hadronoxAI : public BossAI
+ void Reset() override
{
- boss_hadronoxAI(Creature* creature) : BossAI(creature, DATA_HADRONOX)
- {
- }
+ BossAI::Reset();
+ SummonCrusherPack(SUMMON_GROUP_CRUSHER_1);
+ me->SummonCreatureGroup(SUMMON_GROUP_WORLD_TRIGGERS);
+ _doorsWebbed = false;
+ _lastPlayerCombatState = false;
+ }
- void Reset() override
+ void SummonedCreatureEvade(Creature* summon) override
+ {
+ switch (summon->GetEntry())
{
- summons.DoAction(ACTION_DESPAWN_ADDS);
- BossAI::Reset();
- me->SummonCreature(NPC_ANUB_AR_CRUSHER, 542.9f, 519.5f, 741.24f, 2.14f);
+ case NPC_ANUB_AR_CRUSHER:
+ case NPC_ANUB_AR_CHAMPION_PACK:
+ case NPC_ANUB_AR_CRYPT_FIEND_PACK:
+ case NPC_ANUB_AR_NECROMANCER_PACK:
+ EnterEvadeMode(EVADE_REASON_OTHER);
+ break;
+ default:
+ break;
}
+ }
- void DoAction(int32 param) override
+ void DoAction(int32 param) override
+ {
+ if (param == ACTION_CRUSHER_DIED)
+ --_crushersLeft;
+ else if (param == ACTION_CRUSHER_ENGAGED)
{
- if (param == ACTION_START_EVENT)
- {
- instance->SetBossState(DATA_HADRONOX, IN_PROGRESS);
- me->setActive(true);
- events.ScheduleEvent(EVENT_HADRONOX_MOVE1, 20s);
- events.ScheduleEvent(EVENT_HADRONOX_MOVE2, 40s);
- events.ScheduleEvent(EVENT_HADRONOX_MOVE3, 60s);
- events.ScheduleEvent(EVENT_HADRONOX_MOVE4, 80s);
- }
+ if (instance->GetBossState(DATA_HADRONOX) == IN_PROGRESS)
+ return;
+ instance->SetBossState(DATA_HADRONOX, IN_PROGRESS);
+ events.ScheduleEvent(EVENT_CHECK_EVADE, 5s);
+ SummonCrusherPack(SUMMON_GROUP_CRUSHER_2);
+ SummonCrusherPack(SUMMON_GROUP_CRUSHER_3);
+ events.ScheduleEvent(EVENT_HADRONOX_MOVE1, 0s);
+ events.ScheduleEvent(EVENT_HADRONOX_MOVE2, 45s);
+ events.ScheduleEvent(EVENT_HADRONOX_MOVE3, 90s);
}
+ }
- uint32 GetData(uint32 data) const override
+ void SummonCrusherPack(const SummonGroups group)
+ {
+ std::list<TempSummon*> summoned;
+ me->SummonCreatureGroup(group, &summoned);
+ _crushersLeft = summoned.size();
+ for (TempSummon* summon : summoned)
{
- if (data == me->GetEntry())
- return !me->isActiveObject() || events.HasTimeUntilEvent(EVENT_HADRONOX_MOVE4) ? 1 : 0;
- return 0;
+ summon->AI()->SetData(DATA_CRUSHER_PACK_ID, group);
+ summon->AI()->DoAction(ACTION_PACK_WALK);
}
+ }
+
+ void JustSummoned(Creature* summon) override
+ {
+ summons.Summon(summon);
- void JustSummoned(Creature* summon) override
+ switch (summon->GetEntry())
{
- summons.Summon(summon);
-
- // Xinef: cannot use pathfinding...
- if (summon->GetDistance(477.0f, 618.0f, 771.0f) < 5.0f)
- summon->GetMotionMaster()->MoveWaypoint(3000012, false);
- else if (summon->GetDistance(583.0f, 617.0f, 771.0f) < 5.0f)
- summon->GetMotionMaster()->MoveWaypoint(3000013, false);
- else if (summon->GetDistance(581.0f, 608.5f, 739.0f) < 5.0f)
- summon->GetMotionMaster()->MoveWaypoint(3000014, false);
+ case NPC_WORLD_TRIGGER_LAOI:
+ summon->AddAura(SPELL_SUMMON_ANUBAR_CHAMPION_PERIODIC, summon);
+ summon->AddAura(SPELL_SUMMON_ANUBAR_NECROMANCER_PERIODIC, summon);
+ summon->AddAura(SPELL_SUMMON_ANUBAR_CRYPT_FIEND_PERIODIC, summon);
+ break;
+ case NPC_ANUB_AR_CHAMPION:
+ case NPC_ANUB_AR_NECROMANCER:
+ case NPC_ANUB_AR_CRYPTFIEND:
+ // Xinef: cannot use pathfinding...
+ if (summon->GetDistance(477.0f, 618.0f, 771.0f) < 5.0f)
+ summon->GetMotionMaster()->MoveWaypoint(3000012, false);
+ else if (summon->GetDistance(583.0f, 617.0f, 771.0f) < 5.0f)
+ summon->GetMotionMaster()->MoveWaypoint(3000013, false);
+ else if (summon->GetDistance(581.0f, 608.5f, 739.0f) < 5.0f)
+ summon->GetMotionMaster()->MoveWaypoint(3000014, false);
+ break;
+ default:
+ break;
}
+ }
- void KilledUnit(Unit* victim) override
- {
- if (!me->IsAlive() || !victim->HasAura(SPELL_LEECH_POISON))
- return;
+ void KilledUnit(Unit* victim) override
+ {
+ if (!me->IsAlive() || !victim->HasAura(SPELL_LEECH_POISON))
+ return;
- me->ModifyHealth(int32(me->CountPctFromMaxHealth(10)));
- }
+ me->ModifyHealth(static_cast<int32>(me->CountPctFromMaxHealth(10)));
+ }
- void JustDied(Unit* killer) override
- {
- BossAI::JustDied(killer);
- }
+ void JustEngagedWith(Unit* /*who*/) override
+ {
+ me->setActive(true);
+ events.RescheduleEvent(EVENT_HADRONOX_ACID, 10s);
+ events.RescheduleEvent(EVENT_HADRONOX_LEECH, 4s);
+ events.RescheduleEvent(EVENT_HADRONOX_PIERCE, 1s);
+ events.RescheduleEvent(EVENT_HADRONOX_GRAB, 15s);
+ events.RescheduleEvent(EVENT_CHECK_EVADE, 1s);
+ }
- void JustEngagedWith(Unit*) override
+ bool IsInCombatWithPlayer() const
+ {
+ return std::ranges::any_of(me->GetThreatMgr().GetThreatList(), [](auto const& ref) {
+ return ref->getTarget()->IsControlledByPlayer();
+ });
+ }
+
+ void DamageTaken(Unit* who, uint32& damage, DamageEffectType /*damageType*/, SpellSchoolMask /*damageSchoolMask*/) override
+ {
+ if ((!who || !who->IsControlledByPlayer()) && me->HealthBelowPct(70))
{
- events.RescheduleEvent(EVENT_HADRONOX_ACID, 10s);
- events.RescheduleEvent(EVENT_HADRONOX_LEECH, 4s);
- events.RescheduleEvent(EVENT_HADRONOX_PIERCE, 1s);
- events.RescheduleEvent(EVENT_HADRONOX_GRAB, 15s);
+ if (me->HealthBelowPctDamaged(5, damage))
+ damage = 0;
+ else
+ damage *= (me->GetHealthPct() - 5.0f) / 65.0f;
}
+ }
- bool AnyPlayerValid() const
- {
- Map::PlayerList const& playerList = me->GetMap()->GetPlayers();
- for(Map::PlayerList::const_iterator itr = playerList.begin(); itr != playerList.end(); ++itr)
- if (me->GetDistance(itr->GetSource()) < 130.0f && itr->GetSource()->IsAlive() && !itr->GetSource()->IsGameMaster() && me->CanCreatureAttack(itr->GetSource()))
- return true;
+ void UpdateAI(uint32 diff) override
+ {
+ events.Update(diff);
- return false;
- }
+ if (!UpdateVictim())
+ return;
+
+ if (me->HasUnitState(UNIT_STATE_CASTING))
+ return;
- void DamageTaken(Unit* who, uint32& damage, DamageEffectType /*damageType*/, SpellSchoolMask /*damageSchoolMask*/) override
+ switch (uint32 eventId = events.ExecuteEvent())
{
- if ((!who || !who->IsControlledByPlayer()) && me->HealthBelowPct(70))
- {
- if (me->HealthBelowPctDamaged(5, damage))
+ case EVENT_HADRONOX_PIERCE:
+ DoCastVictim(SPELL_PIERCE_ARMOR);
+ events.Repeat(8s);
+ break;
+ case EVENT_HADRONOX_ACID:
+ if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100, false))
+ DoCast(target, SPELL_ACID_CLOUD);
+ events.Repeat(25s);
+ break;
+ case EVENT_HADRONOX_LEECH:
+ DoCastSelf(SPELL_LEECH_POISON);
+ events.Repeat(12s);
+ break;
+ case EVENT_HADRONOX_GRAB:
+ DoCastSelf(SPELL_WEB_GRAB);
+ events.Repeat(25s);
+ break;
+ case EVENT_HADRONOX_MOVE3:
+ if (_crushersLeft > 0)
{
- damage = 0;
+ events.Repeat(2s);
+ break;
}
- else
+ [[fallthrough]];
+ case EVENT_HADRONOX_MOVE1:
+ case EVENT_HADRONOX_MOVE2:
+ Talk(SAY_HADRONOX_EMOTE);
+ me->SetReactState(REACT_PASSIVE);
+ me->GetMotionMaster()->Clear();
+ me->AttackStop();
+ me->GetMotionMaster()->MovePoint(eventId, hadronoxSteps[eventId - 1]);
+ break;
+ case EVENT_CHECK_EVADE:
+ if (IsInCombatWithPlayer() != _lastPlayerCombatState)
{
- damage *= (me->GetHealthPct() - 5.0f) / 65.0f;
+ _lastPlayerCombatState = !_lastPlayerCombatState;
+ if (_lastPlayerCombatState)
+ {
+ me->SetReactState(REACT_AGGRESSIVE);
+ if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == POINT_MOTION_TYPE)
+ me->GetMotionMaster()->Clear();
+ }
+ else
+ EnterEvadeMode(EVADE_REASON_NO_HOSTILES);
}
- }
+ events.Repeat(1s);
+ break;
+ default:
+ break;
}
- void UpdateAI(uint32 diff) override
+ DoMeleeAttackIfReady();
+ }
+
+ void MovementInform(uint32 movementType, uint32 pointId) override
+ {
+ if (movementType != POINT_MOTION_TYPE)
+ return;
+
+ me->SetReactState(REACT_AGGRESSIVE);
+
+ if (pointId == EVENT_HADRONOX_MOVE3)
{
- if (!UpdateVictim())
- return;
+ DoCastSelf(SPELL_WEB_FRONT_DOORS, true);
+ _doorsWebbed = true;
+ }
+ }
- events.Update(diff);
- if (me->HasUnitState(UNIT_STATE_CASTING))
- return;
+ uint32 GetData(uint32 data) const override
+ {
+ if (data == me->GetEntry()) // 'Hadronox Denied' achievement
+ return _doorsWebbed ? 0 : 1;
+ return 0;
+ }
- switch (uint32 eventId = events.ExecuteEvent())
+private:
+ uint8 _crushersLeft;
+ bool _doorsWebbed;
+ bool _lastPlayerCombatState;
+};
+
+struct npc_hadronox_crusherPackAI : public ScriptedAI
+{
+ npc_hadronox_crusherPackAI(Creature* creature, Position const* positions) : ScriptedAI(creature), _instance(creature->GetInstanceScript()), _positions(positions), _myPack(SUMMON_GROUP_CRUSHER_NONE), _doFacing(false) { }
+
+ virtual void DoEngagedWith() = 0;
+ virtual void DoEvent(uint32 /*eventId*/) = 0;
+
+ void DoAction(int32 action) override
+ {
+ if (action == ACTION_PACK_WALK)
+ {
+ switch (_myPack)
{
- case EVENT_HADRONOX_PIERCE:
- me->CastSpell(me->GetVictim(), SPELL_PIERCE_ARMOR, false);
- events.ScheduleEvent(EVENT_HADRONOX_PIERCE, 8s);
- break;
- case EVENT_HADRONOX_ACID:
- if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100, false))
- me->CastSpell(target, SPELL_ACID_CLOUD, false);
- events.ScheduleEvent(EVENT_HADRONOX_ACID, 25s);
- break;
- case EVENT_HADRONOX_LEECH:
- me->CastSpell(me, SPELL_LEECH_POISON, false);
- events.ScheduleEvent(EVENT_HADRONOX_LEECH, 12s);
+ case SUMMON_GROUP_CRUSHER_1:
+ case SUMMON_GROUP_CRUSHER_2:
+ case SUMMON_GROUP_CRUSHER_3:
+ me->GetMotionMaster()->MovePoint(ACTION_PACK_WALK, _positions[_myPack - SUMMON_GROUP_CRUSHER_1]);
break;
- case EVENT_HADRONOX_GRAB:
- me->CastSpell(me, SPELL_WEB_GRAB, false);
- events.ScheduleEvent(EVENT_HADRONOX_GRAB, 25s);
- break;
- case EVENT_HADRONOX_MOVE4:
- me->CastSpell(me, SPELL_WEB_FRONT_DOORS, true);
- [[fallthrough]]; /// @todo: Not sure whether the fallthrough was a mistake (forgetting a break) or intended. This should be double-checked.
- case EVENT_HADRONOX_MOVE1:
- case EVENT_HADRONOX_MOVE2:
- case EVENT_HADRONOX_MOVE3:
- Talk(SAY_HADRONOX_EMOTE);
- me->GetMotionMaster()->MoveCharge(hadronoxSteps[eventId - 1].GetPositionX(), hadronoxSteps[eventId - 1].GetPositionY(), hadronoxSteps[eventId - 1].GetPositionZ(), 10.0f, 0, nullptr, true);
+ default:
break;
}
+ }
+ }
+
+ void MovementInform(uint32 type, uint32 id) override
+ {
+ if (type == POINT_MOTION_TYPE && id == ACTION_PACK_WALK)
+ _doFacing = true;
+ }
+
+ uint32 GetData(uint32 data) const override
+ {
+ if (data == DATA_CRUSHER_PACK_ID)
+ return _myPack;
+ return 0;
+ }
+
+ void SetData(uint32 data, uint32 value) override
+ {
+ if (data == DATA_CRUSHER_PACK_ID)
+ {
+ _myPack = SummonGroups(value);
+ me->SetReactState(_myPack ? REACT_PASSIVE : REACT_AGGRESSIVE);
+ }
+ }
+
+ void JustEngagedWith(Unit* who) override
+ {
+ if (me->HasReactState(REACT_PASSIVE))
+ {
+ std::list<Creature*> creatures;
- DoMeleeAttackIfReady();
+ me->GetCreatureListWithEntryInGrid(creatures, { NPC_ANUB_AR_CRUSHER, NPC_ANUB_AR_CHAMPION_PACK, NPC_ANUB_AR_NECROMANCER_PACK, NPC_ANUB_AR_CRYPT_FIEND_PACK }, 40.0f);
+ for (Creature* creature : creatures)
+ if (creature->AI()->GetData(DATA_CRUSHER_PACK_ID) == _myPack)
+ {
+ creature->SetReactState(REACT_AGGRESSIVE);
+ creature->AI()->AttackStart(who);
+ }
}
+ DoEngagedWith();
+ ScriptedAI::JustEngagedWith(who);
+ }
- bool CheckEvadeIfOutOfCombatArea() const override
+ void MoveInLineOfSight(Unit* who) override
+ {
+ if (!me->HasReactState(REACT_PASSIVE))
{
- return me->isActiveObject() && !AnyPlayerValid();
+ ScriptedAI::MoveInLineOfSight(who);
+ return;
}
- };
- CreatureAI* GetAI(Creature* creature) const override
+ if (me->CanStartAttack(who) && me->IsWithinDistInMap(who, me->GetAttackDistance(who) + me->m_CombatDistance))
+ JustEngagedWith(who);
+ }
+
+ void UpdateAI(uint32 diff) override
{
- return GetAzjolNerubAI<boss_hadronoxAI>(creature);
+ if (_doFacing)
+ {
+ _doFacing = false;
+ me->SetFacingTo(_positions[_myPack - SUMMON_GROUP_CRUSHER_1].GetOrientation());
+ }
+
+ if (!UpdateVictim())
+ return;
+
+ events.Update(diff);
+
+ while (uint32 eventId = events.ExecuteEvent())
+ DoEvent(eventId);
+
+ DoMeleeAttackIfReady();
}
+
+protected:
+ InstanceScript* const _instance;
+ Position const* const _positions;
+ SummonGroups _myPack;
+ bool _doFacing;
};
-class npc_anub_ar_crusher : public CreatureScript
+static const Position crusherWaypoints[] =
{
-public:
- npc_anub_ar_crusher() : CreatureScript("npc_anub_ar_crusher") { }
+ { 529.6913f, 547.1257f, 731.9155f, 4.799650f },
+ { 517.51f , 561.439f , 734.0306f, 4.520403f },
+ { 543.414f , 551.728f , 732.0522f, 3.996804f }
+};
+
+struct npc_anub_ar_crusher : public npc_hadronox_crusherPackAI
+{
+ explicit npc_anub_ar_crusher(Creature* creature) : npc_hadronox_crusherPackAI(creature, crusherWaypoints), _hadFrenzy(false) { }
- struct npc_anub_ar_crusherAI : public ScriptedAI
+ void DoEngagedWith() override
{
- npc_anub_ar_crusherAI(Creature* c) : ScriptedAI(c), summons(me) {}
+ events.ScheduleEvent(EVENT_CRUSHER_SMASH, 8s, 12s);
- EventMap events;
- SummonList summons;
+ if (_myPack != SUMMON_GROUP_CRUSHER_1)
+ return;
- void Reset() override
- {
- summons.DespawnAll();
- events.Reset();
+ if (_instance->GetBossState(DATA_HADRONOX) == IN_PROGRESS)
+ return;
- if (me->ToTempSummon())
- if (Unit* summoner = me->ToTempSummon()->GetSummonerUnit())
- if (summoner->GetEntry() == me->GetEntry())
- {
- me->CastSpell(me, RAND(SPELL_SUMMON_ANUBAR_CHAMPION, SPELL_SUMMON_ANUBAR_CRYPT_FIEND, SPELL_SUMMON_ANUBAR_NECROMANCER), true);
- me->CastSpell(me, RAND(SPELL_SUMMON_ANUBAR_CHAMPION, SPELL_SUMMON_ANUBAR_CRYPT_FIEND, SPELL_SUMMON_ANUBAR_NECROMANCER), true);
- }
- }
+ if (Creature* hadronox = _instance->GetCreature(DATA_HADRONOX))
+ hadronox->AI()->DoAction(ACTION_CRUSHER_ENGAGED);
+
+ Talk(SAY_CRUSHER_AGGRO);
+ }
+
+ void DamageTaken(Unit* /*who*/, uint32& damage, DamageEffectType /*damageType*/, SpellSchoolMask /*damageSchoolMask*/) override
+ {
+ if (_hadFrenzy || !me->HealthBelowPctDamaged(25, damage))
+ return;
+ _hadFrenzy = true;
+ Talk(SAY_CRUSHER_EMOTE);
+ DoCastSelf(SPELL_FRENZY);
+ }
- void JustSummoned(Creature* summon) override
+ void DoEvent(uint32 eventId) override
+ {
+ if (eventId == EVENT_CRUSHER_SMASH)
{
- if (summon->GetEntry() != me->GetEntry())
- {
- summon->GetMotionMaster()->MovePoint(0, *me, FORCED_MOVEMENT_NONE, 0.f, false);
- summon->GetMotionMaster()->MoveFollow(me, 0.1f, 0.0f + M_PI * 0.3f * summons.size());
- }
- summons.Summon(summon);
+ DoCastVictim(SPELL_SMASH);
+ events.Repeat(13s, 21s);
}
+ }
- void DoAction(int32 param) override
+ void JustDied(Unit* killer) override
+ {
+ if (Creature* hadronox = _instance->GetCreature(DATA_HADRONOX))
+ hadronox->AI()->DoAction(ACTION_CRUSHER_DIED);
+ ScriptedAI::JustDied(killer);
+ }
+
+private:
+ bool _hadFrenzy;
+};
+
+static const Position championWaypoints[] =
+{
+ { 539.2076f, 549.7539f, 732.8668f, 4.55531f },
+ { 527.3098f, 559.5197f, 732.9407f, 4.742493f },
+ { }
+};
+struct npc_anub_ar_crusher_champion : public npc_hadronox_crusherPackAI
+{
+ explicit npc_anub_ar_crusher_champion(Creature* creature) : npc_hadronox_crusherPackAI(creature, championWaypoints) { }
+
+ void DoEvent(uint32 eventId) override
+ {
+ switch (eventId)
{
- if (param == ACTION_DESPAWN_ADDS)
- {
- summons.DoAction(ACTION_DESPAWN_ADDS);
- summons.DespawnAll();
- }
+ case EVENT_REND:
+ DoCastVictim(SPELL_REND);
+ events.Repeat(12s, 16s);
+ break;
+ case EVENT_PUMMEL:
+ DoCastVictim(SPELL_PUMMEL);
+ events.Repeat(12s, 17s);
+ break;
+ default:
+ break;
}
+ }
- void JustEngagedWith(Unit*) override
- {
- if (me->ToTempSummon())
- if (Unit* summoner = me->ToTempSummon()->GetSummonerUnit())
- if (summoner->GetEntry() != me->GetEntry())
- {
- summoner->GetAI()->DoAction(ACTION_START_EVENT);
- me->SummonCreature(NPC_ANUB_AR_CRUSHER, 519.58f, 573.73f, 734.30f, 4.50f);
- me->SummonCreature(NPC_ANUB_AR_CRUSHER, 539.38f, 573.25f, 732.20f, 4.738f);
- Talk(SAY_CRUSHER_AGGRO);
- }
+ void DoEngagedWith() override
+ {
+ events.ScheduleEvent(EVENT_REND, 4s, 8s);
+ events.ScheduleEvent(EVENT_PUMMEL, 15s, 19s);
+ }
+};
- events.ScheduleEvent(EVENT_CRUSHER_SMASH, 8s, 0, 0);
- events.ScheduleEvent(EVENT_CHECK_HEALTH, 1s);
- }
+static const Position cryptFiendWaypoints[] =
+{
+ { 520.3911f, 548.7895f, 732.0118f, 5.0091f },
+ { },
+ { 550.9611f, 545.1674f, 731.9031f, 3.996804f }
+};
+struct npc_anub_ar_crusher_crypt_fiend : public npc_hadronox_crusherPackAI
+{
+ explicit npc_anub_ar_crusher_crypt_fiend(Creature* creature) : npc_hadronox_crusherPackAI(creature, cryptFiendWaypoints) { }
- void UpdateAI(uint32 diff) override
+ void DoEvent(uint32 eventId) override
+ {
+ switch (eventId)
{
- if (!UpdateVictim())
- return;
+ case EVENT_CRUSHING_WEBS:
+ DoCastVictim(SPELL_CRUSHING_WEBS);
+ events.Repeat(12s, 16s);
+ break;
+ case EVENT_INFECTED_WOUND:
+ DoCastVictim(SPELL_INFECTED_WOUND);
+ events.Repeat(16s, 25s);
+ break;
+ default:
+ break;
+ }
+ }
- events.Update(diff);
- if (me->HasUnitState(UNIT_STATE_CASTING))
- return;
+ void DoEngagedWith() override
+ {
+ events.ScheduleEvent(EVENT_CRUSHING_WEBS, 4s, 8s);
+ events.ScheduleEvent(EVENT_INFECTED_WOUND, 15s, 19s);
+ }
+};
- switch (events.ExecuteEvent())
- {
- case EVENT_CRUSHER_SMASH:
- me->CastSpell(me->GetVictim(), SPELL_SMASH, false);
- events.ScheduleEvent(EVENT_CRUSHER_SMASH, 15s);
- break;
- case EVENT_CHECK_HEALTH:
- if (me->HealthBelowPct(30))
- {
- Talk(SAY_CRUSHER_EMOTE);
- me->CastSpell(me, SPELL_FRENZY, false);
- break;
- }
- events.ScheduleEvent(EVENT_CHECK_HEALTH, 1s);
- break;
- }
+static const Position necromancerWaypoints[] =
+{
+ { },
+ { 507.6937f, 563.3471f, 734.8986f, 4.520403f },
+ { 535.1049f, 552.8961f, 732.8441f, 3.996804f },
+};
+struct npc_anub_ar_crusher_necromancer : public npc_hadronox_crusherPackAI
+{
+ explicit npc_anub_ar_crusher_necromancer(Creature* creature) : npc_hadronox_crusherPackAI(creature, necromancerWaypoints) { }
- DoMeleeAttackIfReady();
+ void DoEvent(uint32 eventId) override
+ {
+ switch (eventId)
+ {
+ case EVENT_SHADOW_BOLT:
+ DoCastVictim(SPELL_SHADOW_BOLT);
+ events.Repeat(2s, 5s);
+ break;
+ case EVENT_ANIMATE_BONES:
+ DoCastVictim(RAND(SPELL_ANIMATE_BONES_2, SPELL_ANIMATE_BONES_1));
+ events.Repeat(35s, 50s);
+ break;
+ default:
+ break;
}
- };
+ }
- CreatureAI* GetAI(Creature* creature) const override
+ void DoEngagedWith() override
{
- return GetAzjolNerubAI<npc_anub_ar_crusherAI>(creature);
+ events.ScheduleEvent(EVENT_SHADOW_BOLT, 2s, 4s);
+ events.ScheduleEvent(EVENT_ANIMATE_BONES, 37s, 45s);
}
};
@@ -330,7 +609,7 @@ class spell_hadronox_summon_periodic_aura : public AuraScript
PrepareAuraScript(spell_hadronox_summon_periodic_aura);
public:
- spell_hadronox_summon_periodic_aura(uint32 delay, uint32 spellEntry) : _delay(delay), _spellEntry(spellEntry) { }
+ spell_hadronox_summon_periodic_aura(int32 delay, uint32 spellEntry) : _delay(delay), _spellEntry(spellEntry) { }
bool Validate(SpellInfo const* /*spellInfo*/) override
{
@@ -342,7 +621,7 @@ public:
PreventDefaultAction();
Unit* owner = GetUnitOwner();
if (InstanceScript* instance = owner->GetInstanceScript())
- if (!instance->IsBossDone(DATA_HADRONOX))
+ if (!instance->IsBossDone(DATA_HADRONOX) != NOT_STARTED)
{
if (!owner->HasAura(SPELL_WEB_FRONT_DOORS))
owner->CastSpell(owner, _spellEntry, true);
@@ -363,7 +642,7 @@ public:
}
private:
- uint32 _delay;
+ int32 _delay;
uint32 _spellEntry;
};
@@ -389,12 +668,29 @@ class spell_hadronox_leech_poison_aura : public AuraScript
}
};
-class achievement_hadronox_denied : public AchievementCriteriaScript
+class spell_hadronox_web_grab : public SpellScript
{
-public:
- achievement_hadronox_denied() : AchievementCriteriaScript("achievement_hadronox_denied")
+ PrepareSpellScript(spell_hadronox_web_grab);
+
+ // hack to avoid pulling Anub'ar Crusher through the floor and causing Hadronox to evade
+ void FilterTargets(std::list<WorldObject*>& targets)
+ {
+ targets.remove_if([&](WorldObject* target) -> bool
+ {
+ return target->GetEntry() == NPC_ANUB_AR_CRUSHER;
+ });
+ }
+
+ void Register() override
{
+ OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_hadronox_web_grab::FilterTargets, EFFECT_ALL, TARGET_UNIT_SRC_AREA_ENEMY);
}
+};
+
+class achievement_hadronox_denied : public AchievementCriteriaScript
+{
+public:
+ achievement_hadronox_denied() : AchievementCriteriaScript("achievement_hadronox_denied") { }
bool OnCheck(Player* /*player*/, Unit* target, uint32 /*criteria_id*/) override
{
@@ -407,11 +703,15 @@ public:
void AddSC_boss_hadronox()
{
- new boss_hadronox();
- new npc_anub_ar_crusher();
- RegisterSpellScriptWithArgs(spell_hadronox_summon_periodic_aura, "spell_hadronox_summon_periodic_champion_aura", 15000, SPELL_SUMMON_ANUBAR_CHAMPION);
- RegisterSpellScriptWithArgs(spell_hadronox_summon_periodic_aura, "spell_hadronox_summon_periodic_necromancer_aura", 10000, SPELL_SUMMON_ANUBAR_NECROMANCER);
- RegisterSpellScriptWithArgs(spell_hadronox_summon_periodic_aura, "spell_hadronox_summon_periodic_crypt_fiend_aura", 5000, SPELL_SUMMON_ANUBAR_CRYPT_FIEND);
+ RegisterAzjolNerubCreatureAI(boss_hadronox);
+ RegisterAzjolNerubCreatureAI(npc_anub_ar_crusher);
+ RegisterAzjolNerubCreatureAI(npc_anub_ar_crusher_champion);
+ RegisterAzjolNerubCreatureAI(npc_anub_ar_crusher_crypt_fiend);
+ RegisterAzjolNerubCreatureAI(npc_anub_ar_crusher_necromancer);
+ RegisterSpellScriptWithArgs(spell_hadronox_summon_periodic_aura, "spell_hadronox_summon_periodic_champion_aura", 15'000, SPELL_SUMMON_ANUBAR_CHAMPION);
+ RegisterSpellScriptWithArgs(spell_hadronox_summon_periodic_aura, "spell_hadronox_summon_periodic_necromancer_aura", 10'000, SPELL_SUMMON_ANUBAR_NECROMANCER);
+ RegisterSpellScriptWithArgs(spell_hadronox_summon_periodic_aura, "spell_hadronox_summon_periodic_crypt_fiend_aura", 5'000, SPELL_SUMMON_ANUBAR_CRYPT_FIEND);
RegisterSpellScript(spell_hadronox_leech_poison_aura);
+ RegisterSpellScript(spell_hadronox_web_grab);
new achievement_hadronox_denied();
}
diff --git a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_krikthir_the_gatewatcher.cpp b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_krikthir_the_gatewatcher.cpp
index 1664742ea2..2e0eac307b 100644
--- a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_krikthir_the_gatewatcher.cpp
+++ b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_krikthir_the_gatewatcher.cpp
@@ -16,8 +16,10 @@
*/
#include "AchievementCriteriaScript.h"
+#include "CreatureGroups.h"
#include "CreatureScript.h"
#include "ScriptedCreature.h"
+#include "SpellInfo.h"
#include "azjol_nerub.h"
enum Spells
@@ -29,13 +31,6 @@ enum Spells
SPELL_FRENZY = 28747
};
-enum Npcs
-{
- NPC_WARRIOR = 28732,
- NPC_SKIRMISHER = 28734,
- NPC_SHADOWCASTER = 28733
-};
-
enum Yells
{
SAY_AGGRO = 0,
@@ -49,7 +44,8 @@ enum Yells
enum MiscActions
{
ACTION_MINION_ENGAGED = 1,
- GROUP_SWARM = 1
+ GROUP_SWARM = 1,
+ GROUP_WATCHERS = 2
};
class boss_krik_thir : public CreatureScript
@@ -75,16 +71,6 @@ public:
{
BossAI::Reset();
- me->SummonCreature(NPC_WATCHER_NARJIL, 511.8f, 666.493f, 776.278f, 0.977f, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 30000);
- me->SummonCreature(NPC_SHADOWCASTER, 511.63f, 672.44f, 775.71f, 0.90f, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 30000);
- me->SummonCreature(NPC_WARRIOR, 506.75f, 670.7f, 776.24f, 0.92f, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 30000);
- me->SummonCreature(NPC_WATCHER_GASHRA, 526.66f, 663.605f, 775.805f, 1.23f, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 30000);
- me->SummonCreature(NPC_SKIRMISHER, 522.91f, 660.18f, 776.19f, 1.28f, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 30000);
- me->SummonCreature(NPC_WARRIOR, 528.14f, 659.72f, 776.14f, 1.37f, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 30000);
- me->SummonCreature(NPC_WATCHER_SILTHIK, 543.826f, 665.123f, 776.245f, 1.55f, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 30000);
- me->SummonCreature(NPC_SKIRMISHER, 547.5f, 669.96f, 776.1f, 2.3f, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 30000);
- me->SummonCreature(NPC_SHADOWCASTER, 548.64f, 664.27f, 776.74f, 1.77f, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 30000);
-
ScheduleHealthCheckEvent(25, [&] {
DoCastSelf(SPELL_FRENZY, true);
@@ -100,6 +86,21 @@ public:
_canTalk = true;
_minionInCombat = false;
+ _firstCall = true;
+ _minionsEngaged = 0;
+
+ if (me->IsInEvadeMode())
+ return;
+
+ Creature* narjil = instance->GetCreature(DATA_NARJIL);
+ Creature* gashra = instance->GetCreature(DATA_GASHRA);
+ Creature* silthik = instance->GetCreature(DATA_SILTHIK);
+
+ for (Creature* watcher : { narjil, gashra, silthik })
+ {
+ if (watcher && watcher->GetFormation())
+ watcher->GetFormation()->RespawnFormation(true);
+ }
}
void MoveInLineOfSight(Unit* who) override
@@ -116,28 +117,72 @@ public:
void DoAction(int32 actionId) override
{
+ if (actionId == ACTION_MINION_ENGAGED)
+ ++_minionsEngaged;
+
if (actionId == ACTION_MINION_ENGAGED && !_minionInCombat)
{
_minionInCombat = true;
- for (Seconds const& timer : { 10s, 40s, 70s })
- {
- me->m_Events.AddEventAtOffset([this] {
- me->CastCustomSpell(SPELL_SUBBOSS_AGGRO_TRIGGER, SPELLVALUE_MAX_TARGETS, 1, me, true);
- Talk(SAY_SEND_GROUP);
- }, timer);
- }
+ Talk(SAY_SEND_GROUP, 10s);
+
+ for (Seconds const& timer : { 60s, 120s })
+ CallWatcher(timer);
me->m_Events.AddEventAtOffset([this] {
me->SetInCombatWithZone();
- }, 100s);
+ }, IsHeroic() ? 200s : 180s);
+ }
+ else if (actionId == ACTION_MINION_DIED)
+ {
+ me->m_Events.CancelEventGroup(GROUP_WATCHERS);
+
+ // Check if any of the watchers is alive
+ if (!me->FindNearestCreature(NPC_WATCHER_SILTHIK, 100.0f) &&
+ !me->FindNearestCreature(NPC_WATCHER_NARJIL, 100.0f) &&
+ !me->FindNearestCreature(NPC_WATCHER_GASHRA, 100.0f))
+ return;
+
+ me->m_Events.AddEventAtOffset([this] {
+ SummonWatcher();
+ }, 5s, GROUP_WATCHERS);
+
+ // Schedule the next (10s + 60s)
+ CallWatcher(70s);
}
}
+ void CallWatcher(Seconds timer)
+ {
+ me->m_Events.AddEventAtOffset([this] {
+ _firstCall = false;
+ Talk(SAY_SEND_GROUP);
+ SummonWatcher();
+ }, timer, GROUP_WATCHERS);
+ }
+
+ void SummonWatcher()
+ {
+ me->m_Events.AddEventAtOffset([this] {
+ me->CastCustomSpell(SPELL_SUBBOSS_AGGRO_TRIGGER, SPELLVALUE_MAX_TARGETS, 1, me, true);
+ _firstCall = false;
+ }, 5s, GROUP_WATCHERS);
+ }
+
uint32 GetData(uint32 data) const override
{
if (data == me->GetEntry())
- return summons.HasEntry(NPC_WATCHER_NARJIL) && summons.HasEntry(NPC_WATCHER_GASHRA) && summons.HasEntry(NPC_WATCHER_SILTHIK);
+ {
+ Creature* narjil = instance->GetCreature(DATA_NARJIL);
+ Creature* gashra = instance->GetCreature(DATA_GASHRA);
+ Creature* silthik = instance->GetCreature(DATA_SILTHIK);
+
+ if (!narjil || !gashra || !silthik)
+ return false;
+
+ return narjil->IsAlive() && gashra->IsAlive() && silthik->IsAlive();
+ }
+
return 0;
}
@@ -166,7 +211,26 @@ public:
DoCastRandomTarget(SPELL_CURSE_OF_FATIGUE);
}, 27s, 35s);
- summons.DoZoneInCombat();
+ if (Creature* narjil = instance->GetCreature(DATA_NARJIL))
+ narjil->SetInCombatWithZone();
+
+ if (Creature* gashra = instance->GetCreature(DATA_GASHRA))
+ gashra->SetInCombatWithZone();
+
+ if (Creature* silthik = instance->GetCreature(DATA_SILTHIK))
+ silthik->SetInCombatWithZone();
+ }
+
+ void SpellHitTarget(Unit* target, SpellInfo const* spellInfo) override
+ {
+ if (spellInfo->Id == SPELL_SUBBOSS_AGGRO_TRIGGER)
+ {
+ if (_minionsEngaged == 2 && _firstCall)
+ return;
+
+ if (Creature* creature = target->ToCreature())
+ creature->SetInCombatWithZone();
+ }
}
void JustDied(Unit* killer) override
@@ -187,12 +251,6 @@ public:
}
}
- void JustSummoned(Creature* summon) override
- {
- summon->SetNoCallAssistance(true);
- summons.Summon(summon);
- }
-
void SummonedCreatureDies(Creature* summon, Unit*) override
{
summons.Despawn(summon);
@@ -202,6 +260,8 @@ public:
bool _initTalk;
bool _canTalk;
bool _minionInCombat;
+ uint8 _minionsEngaged;
+ bool _firstCall;
[[nodiscard]] bool IsInFrenzy() const { return me->HasAura(SPELL_FRENZY); }
};
diff --git a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/instance_azjol_nerub.cpp b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/instance_azjol_nerub.cpp
index 3f055ed5dc..67d9750400 100644
--- a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/instance_azjol_nerub.cpp
+++ b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/instance_azjol_nerub.cpp
@@ -16,6 +16,7 @@
*/
#include "AreaBoundary.h"
+#include "CreatureGroups.h"
#include "CreatureScript.h"
#include "InstanceMapScript.h"
#include "ScriptedCreature.h"
@@ -37,6 +38,9 @@ ObjectData const creatureData[] =
{ NPC_KRIKTHIR_THE_GATEWATCHER, DATA_KRIKTHIR },
{ NPC_HADRONOX, DATA_HADRONOX },
{ NPC_ANUBARAK, DATA_ANUBARAK },
+ { NPC_WATCHER_GASHRA, DATA_GASHRA },
+ { NPC_WATCHER_NARJIL, DATA_NARJIL },
+ { NPC_WATCHER_SILTHIK, DATA_SILTHIK },
{ 0, 0 }
};
@@ -47,6 +51,7 @@ ObjectData const summonData[] =
{ NPC_ANUB_AR_CHAMPION, DATA_HADRONOX },
{ NPC_ANUB_AR_NECROMANCER, DATA_HADRONOX },
{ NPC_ANUB_AR_CRYPTFIEND, DATA_HADRONOX },
+ { NPC_WORLD_TRIGGER_LAOI, DATA_HADRONOX },
{ 0, 0 }
};
@@ -76,9 +81,59 @@ public:
void OnCreatureEvade(Creature* creature) override
{
- if (creature->EntryEquals(NPC_WATCHER_NARJIL, NPC_WATCHER_GASHRA, NPC_WATCHER_SILTHIK))
- if (Creature* krikthir = GetCreature(DATA_KRIKTHIR))
- krikthir->AI()->EnterEvadeMode();
+ switch (creature->GetEntry())
+ {
+ case NPC_WATCHER_NARJIL:
+ case NPC_WATCHER_GASHRA:
+ case NPC_WATCHER_SILTHIK:
+ if (Creature* krikthir = GetCreature(DATA_KRIKTHIR))
+ krikthir->AI()->EnterEvadeMode();
+ break;
+ case NPC_ANUBAR_SHADOWCASTER:
+ case NPC_ANUBAR_SKIRMISHER:
+ case NPC_ANUBAR_WARRIOR:
+ if (CreatureGroup* formation = creature->GetFormation())
+ if (Creature* leader = formation->GetLeader())
+ if (leader->EntryEquals(NPC_WATCHER_GASHRA, NPC_WATCHER_NARJIL, NPC_WATCHER_SILTHIK))
+ if (Creature* krikthir = GetCreature(DATA_KRIKTHIR))
+ krikthir->AI()->EnterEvadeMode();
+ break;
+ case NPC_KRIKTHIR_THE_GATEWATCHER:
+ if (Creature* narjil = GetCreature(DATA_NARJIL))
+ if (CreatureGroup* formation = narjil->GetFormation())
+ formation->DespawnFormation(0s, 20s);
+
+ if (Creature* gashra = GetCreature(DATA_GASHRA))
+ if (CreatureGroup* formation = gashra->GetFormation())
+ formation->DespawnFormation(0s, 20s);
+
+ if (Creature* silthik = GetCreature(DATA_SILTHIK))
+ if (CreatureGroup* formation = silthik->GetFormation())
+ formation->DespawnFormation(0s, 20s);
+ break;
+ default:
+ break;
+ }
+ }
+
+ void OnUnitDeath(Unit* unit) override
+ {
+ if (unit->EntryEquals(NPC_WATCHER_GASHRA, NPC_WATCHER_NARJIL, NPC_WATCHER_SILTHIK, NPC_ANUBAR_SHADOWCASTER, NPC_ANUBAR_SKIRMISHER, NPC_ANUBAR_WARRIOR))
+ {
+ if (Creature* creature = unit->ToCreature())
+ {
+ ObjectGuid creatureGuid = creature->GetGUID();
+ scheduler.CancelAll();
+ scheduler.Schedule(1s, [this, creatureGuid](TaskContext /*context*/)
+ {
+ if (Creature* creature = instance->GetCreature(creatureGuid))
+ if (CreatureGroup* formation = creature->GetFormation())
+ if (!formation->IsAnyMemberAlive())
+ if (Creature* krikthir = GetCreature(DATA_KRIKTHIR))
+ krikthir->AI()->DoAction(ACTION_MINION_DIED);
+ });
+ }
+ }
}
};
diff --git a/src/server/scripts/Northrend/AzjolNerub/ahnkahet/ahnkahet.h b/src/server/scripts/Northrend/AzjolNerub/ahnkahet/ahnkahet.h
index 8b1dd67366..c2ddd3b7e2 100644
--- a/src/server/scripts/Northrend/AzjolNerub/ahnkahet/ahnkahet.h
+++ b/src/server/scripts/Northrend/AzjolNerub/ahnkahet/ahnkahet.h
@@ -44,6 +44,7 @@ enum AhnKahetCreatures
NPC_AMANITAR = 30258,
// Teldaram and Jedoga encounter related
NPC_JEDOGA_CONTROLLER = 30181,
+ NPC_TWILIGHT_WORSHIPPER = 30111
};
enum AhnkahetSpells
diff --git a/src/server/scripts/Northrend/AzjolNerub/ahnkahet/boss_jedoga_shadowseeker.cpp b/src/server/scripts/Northrend/AzjolNerub/ahnkahet/boss_jedoga_shadowseeker.cpp
index adc26fcc97..d780776064 100644
--- a/src/server/scripts/Northrend/AzjolNerub/ahnkahet/boss_jedoga_shadowseeker.cpp
+++ b/src/server/scripts/Northrend/AzjolNerub/ahnkahet/boss_jedoga_shadowseeker.cpp
@@ -16,7 +16,6 @@
*/
#include "AchievementCriteriaScript.h"
-#include "Containers.h"
#include "CreatureScript.h"
#include "ObjectAccessor.h"
#include "ScriptedCreature.h"
@@ -90,6 +89,7 @@ enum SummonGroups
{
SUMMON_GROUP_OOC = 0,
SUMMON_GROUP_OOC_TRIGGERS = 1,
+ SUMMON_GROUP_IC_WORSHIPPERS = 2
};
enum Points
@@ -208,7 +208,7 @@ struct boss_jedoga_shadowseeker : public BossAI
}
}
- sacraficeTarget_GUID.Clear();
+ sacrificeTargetGUID.Clear();
sayPreachTimer = 120000;
ritualTriggered = false;
volunteerWork = true;
@@ -217,7 +217,7 @@ struct boss_jedoga_shadowseeker : public BossAI
void JustSummoned(Creature* summon) override
{
- if (summon->GetEntry() == NPC_JEDOGA_CONTROLLER)
+ if (summon->EntryEquals(NPC_JEDOGA_CONTROLLER, NPC_TWILIGHT_WORSHIPPER))
{
summons.Summon(summon);
}
@@ -267,12 +267,12 @@ struct boss_jedoga_shadowseeker : public BossAI
}
case NPC_TWILIGHT_VOLUNTEER:
{
- if (sacraficeTarget_GUID && summon->GetGUID() != sacraficeTarget_GUID)
+ if (sacrificeTargetGUID && summon->GetGUID() != sacrificeTargetGUID)
{
break;
}
- if (killer != me && killer->GetGUID() != sacraficeTarget_GUID)
+ if (killer != me && killer->GetGUID() != sacrificeTargetGUID)
{
volunteerWork = false;
}
@@ -314,7 +314,7 @@ struct boss_jedoga_shadowseeker : public BossAI
{
if (action == ACTION_SACRAFICE)
{
- if (Creature* target = ObjectAccessor::GetCreature(*me, sacraficeTarget_GUID))
+ if (Creature* target = ObjectAccessor::GetCreature(*me, sacrificeTargetGUID))
{
Unit::Kill(me, target);
}
@@ -326,6 +326,17 @@ struct boss_jedoga_shadowseeker : public BossAI
_JustEngagedWith();
Talk(SAY_AGGRO);
ReschedulleCombatEvents();
+
+ std::list<TempSummon*> tempSummons;
+ me->SummonCreatureGroup(SUMMON_GROUP_IC_WORSHIPPERS, &tempSummons);
+ if (!tempSummons.empty())
+ {
+ for (TempSummon* summon : tempSummons)
+ {
+ if (summon)
+ summon->SetStandState(UNIT_STAND_STATE_KNEEL);
+ }
+ }
}
void KilledUnit(Unit* who) override
@@ -383,9 +394,9 @@ struct boss_jedoga_shadowseeker : public BossAI
me->SetFacingTo(5.66f);
if (!summons.empty())
{
- sacraficeTarget_GUID = Acore::Containers::SelectRandomContainerElement(summons);
- if (ObjectAccessor::GetCreature(*me, sacraficeTarget_GUID))
+ if (Creature* creature = summons.GetRandomCreatureWithEntry(NPC_TWILIGHT_VOLUNTEER))
{
+ sacrificeTargetGUID = creature->GetGUID();
events.ScheduleEvent(EVENT_JEDGA_START_RITUAL, 3s, 0, PHASE_RITUAL);
}
// Something failed, let players continue but do not grant achievement
@@ -506,15 +517,16 @@ struct boss_jedoga_shadowseeker : public BossAI
}
case EVENT_JEDGA_START_RITUAL:
{
- sacraficeTarget_GUID = Acore::Containers::SelectRandomContainerElement(summons);
- if (Creature* volunteer = ObjectAccessor::GetCreature(*me, sacraficeTarget_GUID))
+ if (Creature* creature = summons.GetRandomCreatureWithEntry(NPC_TWILIGHT_VOLUNTEER))
{
+ sacrificeTargetGUID = creature->GetGUID();
Talk(SAY_SACRIFICE_1);
- sacraficeTarget_GUID = volunteer->GetGUID();
- volunteer->AI()->DoAction(ACTION_RITUAL_BEGIN);
+ creature->AI()->DoAction(ACTION_RITUAL_BEGIN);
}
break;
}
+ default:
+ break;
}
}
@@ -534,7 +546,7 @@ struct boss_jedoga_shadowseeker : public BossAI
private:
GuidList oocSummons;
GuidList oocTriggers;
- ObjectGuid sacraficeTarget_GUID;
+ ObjectGuid sacrificeTargetGUID;
uint32 sayPreachTimer;
bool combatSummonsSummoned;
bool ritualTriggered;
diff --git a/src/server/scripts/Northrend/ChamberOfAspects/ObsidianSanctum/boss_sartharion.cpp b/src/server/scripts/Northrend/ChamberOfAspects/ObsidianSanctum/boss_sartharion.cpp
index 4802010e16..a45be95fd4 100644
--- a/src/server/scripts/Northrend/ChamberOfAspects/ObsidianSanctum/boss_sartharion.cpp
+++ b/src/server/scripts/Northrend/ChamberOfAspects/ObsidianSanctum/boss_sartharion.cpp
@@ -139,8 +139,8 @@ enum Misc
LAVA_RIGHT_SIDE = 1,
// Counters
- MAX_LEFT_LAVA_TSUNAMIS = 3,
- MAX_RIGHT_LAVA_TSUNAMIS = 2,
+ MAX_LEFT_LAVA_TSUNAMIS = 9,
+ MAX_RIGHT_LAVA_TSUNAMIS = 6,
MAX_DRAGONS = 3,
MAX_AREA_TRIGGER_COUNT = 2,
MAX_CYCLONE_COUNT = 5,
@@ -224,7 +224,7 @@ const Position AreaTriggerSummonPos[MAX_AREA_TRIGGER_COUNT] =
{ 3242.84f, 553.979f, 58.8272f, 0.0f },
};
-const float SartharionBoundary[MAX_BOUNDARY_POSITIONS] =
+float const SartharionBoundary[MAX_BOUNDARY_POSITIONS] =
{
3218.86f, // South X
3275.69f, // North X
@@ -232,6 +232,19 @@ const float SartharionBoundary[MAX_BOUNDARY_POSITIONS] =
572.4f // West Y
};
+float const FlameTsunamiLeftOffsets[MAX_LEFT_LAVA_TSUNAMIS] =
+{
+ 476.0f, 484.0f, 492.0f,
+ 524.0f, 532.0f, 540.0f,
+ 572.0f, 580.0f, 588.0f
+};
+
+float const FlameTsunamiRightOffsets[MAX_RIGHT_LAVA_TSUNAMIS] =
+{
+ 500.0f, 508.0f, 516.0f,
+ 548.0f, 556.0f, 564.0f
+};
+
const Position bigIslandMiddlePos = { 3242.822754f, 477.279816f, 57.430473f };
const uint32 dragons[MAX_DRAGONS] = { DATA_TENEBRON, DATA_VESPERON, DATA_SHADRON };
@@ -620,15 +633,18 @@ public:
{
summons.RemoveNotExisting();
Talk(WHISPER_LAVA_CHURN);
- extraEvents.ScheduleEvent(EVENT_SARTHARION_START_LAVA, 2s);
- extraEvents.ScheduleEvent(EVENT_SARTHARION_FINISH_LAVA, 9s);
+ extraEvents.ScheduleEvent(EVENT_SARTHARION_START_LAVA, 3600ms);
+ extraEvents.ScheduleEvent(EVENT_SARTHARION_FINISH_LAVA, 11s);
// Send wave from left
if (lastLavaSide == LAVA_RIGHT_SIDE)
{
for (uint8 i = 0; i < MAX_LEFT_LAVA_TSUNAMIS; ++i)
{
- me->SummonCreature(NPC_FLAME_TSUNAMI, 3208.44f, 580.0f - (i * 50.0f), 55.8f, 0.0f, TEMPSUMMON_TIMED_DESPAWN, 12000);
+ Creature* tsunami = me->SummonCreature(NPC_FLAME_TSUNAMI, 3211.0f, FlameTsunamiLeftOffsets[i], 57.083332f, 0.0f, TEMPSUMMON_TIMED_DESPAWN, 13500);
+
+ if (((i - 2) % 3 == 0) && tsunami) // If center of wave
+ tsunami->CastSpell(tsunami, SPELL_FLAME_TSUNAMI_VISUAL, true);
}
lastLavaSide = LAVA_LEFT_SIDE;
@@ -638,7 +654,10 @@ public:
{
for (uint8 i = 0; i < MAX_RIGHT_LAVA_TSUNAMIS; ++i)
{
- me->SummonCreature(NPC_FLAME_TSUNAMI, 3283.44f, 555.0f - (i * 50.0f), 55.8f, 3.14f, TEMPSUMMON_TIMED_DESPAWN, 12000);
+ Creature* tsunami = me->SummonCreature(NPC_FLAME_TSUNAMI, 3286.0f, FlameTsunamiRightOffsets[i], 57.083332f, 3.14f, TEMPSUMMON_TIMED_DESPAWN, 13500);
+
+ if (((i - 2) % 3 == 0) && tsunami) // If center of wave
+ tsunami->CastSpell(tsunami, SPELL_FLAME_TSUNAMI_VISUAL, true);
}
lastLavaSide = LAVA_RIGHT_SIDE;
@@ -648,24 +667,22 @@ public:
void SendLavaWaves(bool start)
{
if (summons.empty())
- {
return;
- }
for (ObjectGuid const& guid : summons)
{
Creature* tsunami = ObjectAccessor::GetCreature(*me, guid);
if (!tsunami || tsunami->GetEntry() != NPC_FLAME_TSUNAMI)
- {
continue;
- }
- if (start)
+ if (start) // Movement possibly simplified from official, ideally reevaluate in the future.
{
- tsunami->GetMotionMaster()->MovePoint(0, ((tsunami->GetPositionX() < 3250.0f) ? 3283.44f : 3208.44f), tsunami->GetPositionY(), tsunami->GetPositionZ());
+ tsunami->CastSpell(tsunami, SPELL_FLAME_TSUNAMI_DAMAGE_AURA, true);
+ tsunami->GetMotionMaster()->MovePoint(0, ((tsunami->GetPositionX() < 3250.0f) ? 3286.0f : 3211.0f), tsunami->GetPositionY(), tsunami->GetPositionZ());
}
else
{
+ tsunami->RemoveAura(SPELL_FLAME_TSUNAMI_DAMAGE_AURA);
tsunami->SetObjectScale(0.1f);
}
}
diff --git a/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_faction_champions.cpp b/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_faction_champions.cpp
index 319fc1f83e..d27d43b388 100644
--- a/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_faction_champions.cpp
+++ b/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_faction_champions.cpp
@@ -2037,7 +2037,7 @@ public:
npc_toc_enh_shamanAI(Creature* pCreature) : boss_faction_championsAI(pCreature, AI_MELEE)
{
SetEquipmentSlots(false, 51803, 48013, EQUIP_NO_CHANGE);
- me->SetModifierValue(UNIT_MOD_DAMAGE_OFFHAND, TOTAL_PCT, 1.0f);
+ me->SetStatPctModifier(UNIT_MOD_DAMAGE_OFFHAND, TOTAL_PCT, 1.0f);
me->UpdateDamagePhysical(OFF_ATTACK);
events.Reset();
diff --git a/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_twin_valkyr.cpp b/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_twin_valkyr.cpp
index eb26cad02b..2ffe2f7dd5 100644
--- a/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_twin_valkyr.cpp
+++ b/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_twin_valkyr.cpp
@@ -109,7 +109,7 @@ struct boss_twin_valkyrAI : public ScriptedAI
{
pInstance = pCreature->GetInstanceScript();
me->SetReactState(REACT_PASSIVE);
- me->SetModifierValue(UNIT_MOD_DAMAGE_OFFHAND, TOTAL_PCT, 1.0f);
+ me->SetStatPctModifier(UNIT_MOD_DAMAGE_OFFHAND, TOTAL_PCT, 1.0f);
me->UpdateDamagePhysical(OFF_ATTACK);
LastSynchroHP = (int32)me->GetMaxHealth();
SpecialMask = 0;
diff --git a/src/server/scripts/Northrend/DraktharonKeep/boss_novos.cpp b/src/server/scripts/Northrend/DraktharonKeep/boss_novos.cpp
index 277e951045..36f8b555c4 100644
--- a/src/server/scripts/Northrend/DraktharonKeep/boss_novos.cpp
+++ b/src/server/scripts/Northrend/DraktharonKeep/boss_novos.cpp
@@ -47,7 +47,7 @@ enum Spells
SPELL_COPY_OF_SUMMON_MINIONS = 59933,
SPELL_BLIZZARD = 49034,
SPELL_FROSTBOLT = 49037,
- SPELL_TOUCH_OF_MISERY = 50090
+ SPELL_WRATH_OF_MISERY = 50089
};
enum Misc
@@ -56,14 +56,7 @@ enum Misc
NPC_CRYSTAL_HANDLER = 26627,
NPC_SUMMON_CRYSTAL_HANDLER_TARGET = 27583,
- EVENT_SUMMON_FETID_TROLL = 1,
- EVENT_SUMMON_SHADOWCASTER = 2,
- EVENT_SUMMON_HULKING_CORPSE = 3,
- EVENT_SUMMON_CRYSTAL_HANDLER = 4,
- EVENT_CAST_OFFENSIVE_SPELL = 5,
- EVENT_KILL_TALK = 6,
- EVENT_CHECK_PHASE = 7,
- EVENT_SPELL_SUMMON_MINIONS = 8,
+ EVENT_KILL_TALK = 1,
ROOM_RIGHT = 0,
ROOM_LEFT = 1,
@@ -80,9 +73,7 @@ std::unordered_map<uint32, std::tuple <uint32, Position>> const npcSummon =
// 26631
struct boss_novos : public BossAI
{
- boss_novos(Creature* creature) : BossAI(creature, DATA_NOVOS)
- {
- }
+ boss_novos(Creature* creature) : BossAI(creature, DATA_NOVOS) { }
void Reset() override
{
@@ -120,16 +111,73 @@ struct boss_novos : public BossAI
{
Talk(SAY_AGGRO);
BossAI::JustEngagedWith(who);
+ scheduler.ClearValidator();
+
+ ScheduleTimedEvent(3s, [&] {
+ if (Creature* trigger = summons.GetCreatureWithEntry(NPC_CRYSTAL_CHANNEL_TARGET))
+ trigger->CastSpell(trigger, SPELL_SUMMON_FETID_TROLL_CORPSE, true, nullptr, nullptr, me->GetGUID());
+ }, 3s);
+
+ ScheduleTimedEvent(9s, [&] {
+ if (Creature* trigger = summons.GetCreatureWithEntry(NPC_CRYSTAL_CHANNEL_TARGET))
+ trigger->CastSpell(trigger, SPELL_SUMMON_RISEN_SHADOWCASTER, true, nullptr, nullptr, me->GetGUID());
+ }, 10s);
+
+ ScheduleTimedEvent(30s, [&] {
+ if (Creature* trigger = summons.GetCreatureWithEntry(NPC_CRYSTAL_CHANNEL_TARGET))
+ trigger->CastSpell(trigger, SPELL_SUMMON_HULKING_CORPSE, true, nullptr, nullptr, me->GetGUID());
+ }, 30s);
+
+ scheduler.Schedule(70s, [this](TaskContext context) {
+ if (me->HasAura(SPELL_BEAM_CHANNEL))
+ {
+ context.Repeat(2s);
+ return;
+ }
+
+ scheduler.CancelAll();
+
+ me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE|UNIT_FLAG_NOT_SELECTABLE);
+ me->InterruptNonMeleeSpells(false);
+
+ scheduler.SetValidator([this] {
+ return !me->HasUnitState(UNIT_STATE_CASTING);
+ });
+
+ ScheduleTimedEvent(5s, 10s, [&] {
+ DoCastRandomTarget(SPELL_BLIZZARD);
+ }, 12s, 25s);
+
+ ScheduleTimedEvent(5s, 10s, [&] {
+ DoCastRandomTarget(SPELL_WRATH_OF_MISERY);
+ }, 8s, 16s);
+
+ if (IsHeroic())
+ {
+ ScheduleTimedEvent(10s, [&] {
+ DoCastAOE(SPELL_SUMMON_MINIONS);
+ }, 37s, 55s);
+ }
+ });
+
+ for (Seconds timer : { 16s, 32s, 48s, 64s })
+ {
+ me->m_Events.AddEventAtOffset([&] {
+ Talk(SAY_SUMMONING_ADDS);
+ Talk(EMOTE_SUMMONING_ADDS);
+ if (Creature* target = ObjectAccessor::GetCreature(*me, _stage ? _summonTargetLeftGUID : _summonTargetRightGUID))
+ target->CastSpell(target, SPELL_SUMMON_CRYSTAL_HANDLER, true, nullptr, nullptr, me->GetGUID());
+ _stage = _stage ? 0 : 1;
+ }, timer);
+ }
- events.ScheduleEvent(EVENT_SUMMON_FETID_TROLL, 3s);
- events.ScheduleEvent(EVENT_SUMMON_SHADOWCASTER, 9s);
- events.ScheduleEvent(EVENT_SUMMON_HULKING_CORPSE, 30s);
- events.ScheduleEvent(EVENT_SUMMON_CRYSTAL_HANDLER, 20s);
- events.ScheduleEvent(EVENT_CHECK_PHASE, 80s);
+ me->SetGuidValue(UNIT_FIELD_TARGET, ObjectGuid::Empty);
+ me->RemoveAllAuras();
+ me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE);
- me->CastSpell(me, SPELL_ARCANE_BLAST, true);
- me->CastSpell(me, SPELL_ARCANE_FIELD, true);
- me->CastSpell(me, SPELL_DESPAWN_CRYSTAL_HANDLER, true);
+ DoCastSelf(SPELL_ARCANE_BLAST, true);
+ DoCastSelf(SPELL_ARCANE_FIELD, true);
+ DoCastSelf(SPELL_DESPAWN_CRYSTAL_HANDLER, true);
for (auto& itr : npcSummon)
{
@@ -147,11 +195,6 @@ struct boss_novos : public BossAI
break;
}
}
-
- me->SetGuidValue(UNIT_FIELD_TARGET, ObjectGuid::Empty);
- me->RemoveAllAuras();
- me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE);
- me->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE);
}
void JustDied(Unit* killer) override
@@ -182,6 +225,9 @@ struct boss_novos : public BossAI
if (summon->GetEntry() == NPC_FETID_TROLL_CORPSE)
summon->GetMotionMaster()->MovePoint(1, -373.56f, -770.86f, 28.59f);
+
+ if (summon->EntryEquals(NPC_CRYSTAL_HANDLER))
+ summon->SetInCombatWithZone();
}
// Phase 2
else if (summon->GetEntry() != NPC_CRYSTAL_CHANNEL_TARGET)
@@ -211,65 +257,11 @@ struct boss_novos : public BossAI
if (!UpdateVictim())
return;
+ scheduler.Update(diff);
events.Update(diff);
- switch (events.ExecuteEvent())
- {
- case EVENT_SUMMON_FETID_TROLL:
- if (Creature* trigger = summons.GetCreatureWithEntry(NPC_CRYSTAL_CHANNEL_TARGET))
- trigger->CastSpell(trigger, SPELL_SUMMON_FETID_TROLL_CORPSE, true, nullptr, nullptr, me->GetGUID());
- events.ScheduleEvent(EVENT_SUMMON_FETID_TROLL, 3s);
- break;
- case EVENT_SUMMON_HULKING_CORPSE:
- if (Creature* trigger = summons.GetCreatureWithEntry(NPC_CRYSTAL_CHANNEL_TARGET))
- trigger->CastSpell(trigger, SPELL_SUMMON_HULKING_CORPSE, true, nullptr, nullptr, me->GetGUID());
- events.ScheduleEvent(EVENT_SUMMON_HULKING_CORPSE, 30s);
- break;
- case EVENT_SUMMON_SHADOWCASTER:
- if (Creature* trigger = summons.GetCreatureWithEntry(NPC_CRYSTAL_CHANNEL_TARGET))
- trigger->CastSpell(trigger, SPELL_SUMMON_RISEN_SHADOWCASTER, true, nullptr, nullptr, me->GetGUID());
- events.ScheduleEvent(EVENT_SUMMON_SHADOWCASTER, 10s);
- break;
- case EVENT_SUMMON_CRYSTAL_HANDLER:
- if (_crystalCounter++ < 4)
- {
- Talk(SAY_SUMMONING_ADDS);
- Talk(EMOTE_SUMMONING_ADDS);
- if (Creature* target = ObjectAccessor::GetCreature(*me, _stage ? _summonTargetLeftGUID : _summonTargetRightGUID))
- target->CastSpell(target, SPELL_SUMMON_CRYSTAL_HANDLER, true, nullptr, nullptr, me->GetGUID());
- _stage = _stage ? 0 : 1;
- events.ScheduleEvent(EVENT_SUMMON_CRYSTAL_HANDLER, 20s);
- }
- break;
- case EVENT_CHECK_PHASE:
- if (me->HasAura(SPELL_BEAM_CHANNEL))
- {
- events.ScheduleEvent(EVENT_CHECK_PHASE, 2s);
- break;
- }
- events.Reset();
- events.ScheduleEvent(EVENT_CAST_OFFENSIVE_SPELL, 3s);
- events.ScheduleEvent(EVENT_SPELL_SUMMON_MINIONS, 10s);
- me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE);
- me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE);
- me->InterruptNonMeleeSpells(false);
- break;
- case EVENT_CAST_OFFENSIVE_SPELL:
- if (!me->HasUnitState(UNIT_STATE_CASTING))
- if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100, true))
- me->CastSpell(target, RAND(SPELL_BLIZZARD, SPELL_FROSTBOLT, SPELL_TOUCH_OF_MISERY), false);
-
- events.ScheduleEvent(EVENT_CAST_OFFENSIVE_SPELL, 500ms);
- break;
- case EVENT_SPELL_SUMMON_MINIONS:
- if (me->HasUnitState(UNIT_STATE_CASTING))
- {
- me->CastSpell(me, SPELL_SUMMON_MINIONS, false);
- events.ScheduleEvent(EVENT_SPELL_SUMMON_MINIONS, 15s);
- break;
- }
- events.ScheduleEvent(EVENT_SPELL_SUMMON_MINIONS, 500ms);
- break;
- }
+
+ if (!me->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE))
+ DoSpellAttackIfReady(SPELL_FROSTBOLT);
}
bool CheckEvadeIfOutOfCombatArea() const override
diff --git a/src/server/scripts/Northrend/DraktharonKeep/boss_tharon_ja.cpp b/src/server/scripts/Northrend/DraktharonKeep/boss_tharon_ja.cpp
index 78e10f8074..1a240ce158 100644
--- a/src/server/scripts/Northrend/DraktharonKeep/boss_tharon_ja.cpp
+++ b/src/server/scripts/Northrend/DraktharonKeep/boss_tharon_ja.cpp
@@ -59,7 +59,6 @@ enum Misc
EVENT_SPELL_EYE_BEAM = 4,
EVENT_SPELL_LIGHTNING_BREATH = 5,
EVENT_SPELL_POISON_CLOUD = 6,
- EVENT_SPELL_TURN_FLESH = 7,
EVENT_TURN_FLESH_REAL = 9,
EVENT_TURN_BONES_REAL = 10,
EVENT_KILL_TALK = 11
@@ -77,9 +76,7 @@ public:
struct boss_tharon_jaAI : public BossAI
{
- boss_tharon_jaAI(Creature* creature) : BossAI(creature, DATA_THARON_JA)
- {
- }
+ boss_tharon_jaAI(Creature* creature) : BossAI(creature, DATA_THARON_JA) { }
void Reset() override
{
@@ -88,15 +85,23 @@ public:
me->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_KNOCK_BACK_DEST, true);
me->SetDisplayId(me->GetNativeDisplayId());
me->CastSpell(me, SPELL_CLEAR_GIFT, true);
+
+ ScheduleHealthCheckEvent(55, [&] {
+ Talk(SAY_FLESH);
+ me->GetThreatMgr().ResetAllThreat();
+ me->CastSpell((Unit*)nullptr, SPELL_TURN_FLESH, false);
+
+ events.Reset();
+ events.ScheduleEvent(EVENT_TURN_FLESH_REAL, 3s);
+ }, false);
}
void JustEngagedWith(Unit* who) override
{
Talk(SAY_AGGRO);
BossAI::JustEngagedWith(who);
- events.ScheduleEvent(EVENT_SPELL_CURSE_OF_LIFE, 5s);
- events.ScheduleEvent(EVENT_SPELL_SHADOW_VOLLEY, 8s, 10s);
- events.ScheduleEvent(EVENT_SPELL_TURN_FLESH, 1s);
+ events.ScheduleEvent(EVENT_SPELL_CURSE_OF_LIFE, 13s, 24s);
+ events.ScheduleEvent(EVENT_SPELL_SHADOW_VOLLEY, 6s, 29s);
}
void KilledUnit(Unit* /*victim*/) override
@@ -144,24 +149,11 @@ public:
{
case EVENT_SPELL_CURSE_OF_LIFE:
DoCastRandomTarget(SPELL_CURSE_OF_LIFE, 0, 30.0f, false);
- events.ScheduleEvent(EVENT_SPELL_CURSE_OF_LIFE, 13s);
+ events.ScheduleEvent(EVENT_SPELL_CURSE_OF_LIFE, 11s, 28s);
break;
case EVENT_SPELL_SHADOW_VOLLEY:
DoCastAOE(SPELL_SHADOW_VOLLEY);
- events.ScheduleEvent(EVENT_SPELL_SHADOW_VOLLEY, 9s);
- break;
- case EVENT_SPELL_TURN_FLESH:
- if (me->HealthBelowPct(50))
- {
- Talk(SAY_FLESH);
- me->GetThreatMgr().ResetAllThreat();
- me->CastSpell((Unit*)nullptr, SPELL_TURN_FLESH, false);
-
- events.Reset();
- events.ScheduleEvent(EVENT_TURN_FLESH_REAL, 3s);
- return;
- }
- events.ScheduleEvent(EVENT_SPELL_TURN_FLESH, 1s);
+ events.ScheduleEvent(EVENT_SPELL_SHADOW_VOLLEY, 6s, 29s);
break;
case EVENT_TURN_FLESH_REAL:
DoCastSelf(SPELL_DUMMY, true);
diff --git a/src/server/scripts/Northrend/DraktharonKeep/boss_trollgore.cpp b/src/server/scripts/Northrend/DraktharonKeep/boss_trollgore.cpp
index c75c8966b8..8380f1caae 100644
--- a/src/server/scripts/Northrend/DraktharonKeep/boss_trollgore.cpp
+++ b/src/server/scripts/Northrend/DraktharonKeep/boss_trollgore.cpp
@@ -130,6 +130,12 @@ public:
if (!UpdateVictim())
return;
+ if (!CheckInRoom())
+ {
+ EnterEvadeMode(EVADE_REASON_BOUNDARY);
+ return;
+ }
+
events.Update(diff);
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
@@ -159,9 +165,9 @@ public:
DoMeleeAttackIfReady();
}
- bool CheckEvadeIfOutOfCombatArea() const override
+ bool CheckInRoom() override
{
- return me->GetHomePosition().GetExactDist2d(me) > 60.0f;
+ return (me->GetPositionY() >= -700.0f && me->GetPositionY() <= -628.0f);
}
private:
diff --git a/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.cpp b/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.cpp
index 9d44e3b25a..43547fcf0c 100644
--- a/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.cpp
+++ b/src/server/scripts/Northrend/FrozenHalls/HallsOfReflection/halls_of_reflection.cpp
@@ -1950,14 +1950,14 @@ public:
currentStopPoint = 0;
events.Reset();
}
- void DoAction(int32 actionId) override
+ void DoAction(int32 actionId) override
{
switch (actionId)
{
case ACTION_START_INTRO:
events.ScheduleEvent(EVENT_LK_SAY_AGGRO, 0ms);
- events.ScheduleEvent(EVENT_LK_BATTLE_1, 2s +500ms);
+ events.ScheduleEvent(EVENT_LK_BATTLE_1, 2s + 500ms);
events.ScheduleEvent(EVENT_LK_BATTLE_2, 3s);
events.ScheduleEvent(me->GetEntry() == NPC_JAINA_PART2 ? EVENT_JAINA_IMMOBILIZE_LK : EVENT_SYLVANAS_IMMOBILIZE_JUMP, 9s);
break;
@@ -1989,7 +1989,7 @@ public:
path.push_back(G3D::Vector3(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ()));
for (uint8 i = WP_STOP[currentStopPoint - 1] + (currentStopPoint == 1 ? 0 : 1); i <= WP_STOP[currentStopPoint]; ++i)
path.push_back(G3D::Vector3(PathWaypoints[i].GetPositionX(), PathWaypoints[i].GetPositionY(), PathWaypoints[i].GetPositionZ()));
- me->GetMotionMaster()->MoveSplinePath(&path);
+ me->GetMotionMaster()->MoveSplinePath(&path, FORCED_MOVEMENT_RUN);
}
void MovementInform(uint32 type, uint32 /*id*/) override
diff --git a/src/server/scripts/Northrend/Gundrak/boss_eck.cpp b/src/server/scripts/Northrend/Gundrak/boss_eck.cpp
index 9e7a6bbc97..c842079265 100644
--- a/src/server/scripts/Northrend/Gundrak/boss_eck.cpp
+++ b/src/server/scripts/Northrend/Gundrak/boss_eck.cpp
@@ -33,120 +33,69 @@ enum Misc
{
POINT_START = 0,
EVENT_ECK_BERSERK = 1,
- EVENT_ECK_BITE = 2,
- EVENT_ECK_SPIT = 3,
- EVENT_ECK_SPRING = 4,
- EVENT_ECK_HEALTH = 5
+ EVENT_ECK_CRAZED_EMOTE = 2,
+ EMOTE_CRAZED = 1
};
-class boss_eck : public CreatureScript
-{
-public:
- boss_eck() : CreatureScript("boss_eck") { }
+Position const EckHomePosition = { 1642.712f, 934.646f, 107.205f, 0.767f };
+Position const EckCombatStartPosition = { 1638.55f, 919.76f, 104.95f, 0.00f };
- CreatureAI* GetAI(Creature* creature) const override
+struct boss_eck : public BossAI
+{
+ boss_eck(Creature* creature) : BossAI(creature, DATA_ECK_THE_FEROCIOUS)
{
- return GetGundrakAI<boss_eckAI>(creature);
+ scheduler.SetValidator([this]
+ {
+ return !me->HasUnitState(UNIT_STATE_CASTING);
+ });
}
- struct boss_eckAI : public BossAI
+ void InitializeAI() override
{
- boss_eckAI(Creature* creature) : BossAI(creature, DATA_ECK_THE_FEROCIOUS)
- {
- }
-
- void InitializeAI() override
- {
- BossAI::InitializeAI();
- me->GetMotionMaster()->MovePoint(POINT_START, 1638.55f, 919.76f, 104.95f, FORCED_MOVEMENT_NONE, 0.f, 0.f, false);
- me->SetHomePosition(1642.712f, 934.646f, 107.205f, 0.767f);
- me->SetReactState(REACT_PASSIVE);
- }
-
- void MovementInform(uint32 type, uint32 id) override
- {
- if (type == POINT_MOTION_TYPE && id == POINT_START)
- {
- me->CastSpell(me, SPELL_ECK_SPRING_INIT, true);
- me->SetReactState(REACT_AGGRESSIVE);
- }
- }
-
- void SpellHitTarget(Unit* target, SpellInfo const* spell) override
- {
- if (spell->Id == SPELL_ECK_SPRING)
- {
- me->GetThreatMgr().ResetAllThreat();
- me->AddThreat(target, 1.0f);
- }
- }
-
- void Reset() override
- {
- BossAI::Reset();
- }
+ BossAI::InitializeAI();
+ me->GetMotionMaster()->MovePoint(POINT_START, EckCombatStartPosition, FORCED_MOVEMENT_NONE, 0.f, false);
+ me->SetHomePosition(EckHomePosition);
+ me->SetReactState(REACT_PASSIVE);
+ }
- void JustEngagedWith(Unit* who) override
+ void MovementInform(uint32 type, uint32 id) override
+ {
+ if (type == POINT_MOTION_TYPE && id == POINT_START)
{
- BossAI::JustEngagedWith(who);
- events.ScheduleEvent(EVENT_ECK_BERSERK, 60s, 90s);
- events.ScheduleEvent(EVENT_ECK_BITE, 5s);
- events.ScheduleEvent(EVENT_ECK_SPIT, 10s, 37s);
- events.ScheduleEvent(EVENT_ECK_SPRING, 10s, 24s);
+ me->CastSpell(me, SPELL_ECK_SPRING_INIT, true);
+ me->SetReactState(REACT_AGGRESSIVE);
}
+ }
- void JustDied(Unit* killer) override
+ void SpellHitTarget(Unit* target, SpellInfo const* spell) override
+ {
+ if (spell->Id == SPELL_ECK_SPRING)
{
- BossAI::JustDied(killer);
+ me->GetThreatMgr().ResetAllThreat();
+ me->AddThreat(target, 1.0f);
}
+ }
- void UpdateAI(uint32 diff) override
- {
- if (!UpdateVictim())
- return;
-
- events.Update(diff);
- if (me->HasUnitState(UNIT_STATE_CASTING))
- return;
-
- switch (events.ExecuteEvent())
- {
- case EVENT_ECK_HEALTH:
- if (me->HealthBelowPct(21))
- {
- events.CancelEvent(EVENT_ECK_BERSERK);
- me->CastSpell(me, SPELL_ECK_BERSERK, false);
- break;
- }
- events.ScheduleEvent(EVENT_ECK_HEALTH, 1s);
- break;
- case EVENT_ECK_BERSERK:
- me->CastSpell(me, SPELL_ECK_BERSERK, false);
- events.CancelEvent(EVENT_ECK_HEALTH);
- break;
- case EVENT_ECK_BITE:
- me->CastSpell(me->GetVictim(), SPELL_ECK_BITE, false);
- events.ScheduleEvent(EVENT_ECK_BITE, 8s, 12s);
- break;
- case EVENT_ECK_SPIT:
- me->CastSpell(me->GetVictim(), SPELL_ECK_SPIT, false);
- events.ScheduleEvent(EVENT_ECK_SPIT, 11s, 24s);
- break;
- case EVENT_ECK_SPRING:
- if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 30.0f, true, false))
- {
- me->CastSpell(target, SPELL_ECK_SPRING, false);
- }
- events.ScheduleEvent(EVENT_ECK_SPRING, 10s, 24s);
- break;
- }
-
- DoMeleeAttackIfReady();
- }
- };
+ void JustEngagedWith(Unit* who) override
+ {
+ BossAI::JustEngagedWith(who);
+ ScheduleUniqueTimedEvent(77s, [&] {
+ Talk(EMOTE_CRAZED);
+ }, EVENT_ECK_CRAZED_EMOTE);
+ ScheduleTimedEvent(5s, [&] {
+ DoCastVictim(SPELL_ECK_BITE);
+ }, 8s, 12s);
+ ScheduleTimedEvent(10s, 37s, [&] {
+ DoCastVictim(SPELL_ECK_SPIT);
+ }, 8s, 12s);
+ ScheduleTimedEvent(10s, 24s, [&] {
+ DoCastRandomTarget(SPELL_ECK_SPRING, 0, 30.0f, true);
+ }, 10s, 24s);
+ ScheduleEnrageTimer(SPELL_ECK_BERSERK, 90s);
+ }
};
void AddSC_boss_eck()
{
- new boss_eck();
+ RegisterGundrakCreatureAI(boss_eck);
}
diff --git a/src/server/scripts/Northrend/Gundrak/boss_gal_darah.cpp b/src/server/scripts/Northrend/Gundrak/boss_gal_darah.cpp
index 362edec287..611fb8aa9c 100644
--- a/src/server/scripts/Northrend/Gundrak/boss_gal_darah.cpp
+++ b/src/server/scripts/Northrend/Gundrak/boss_gal_darah.cpp
@@ -43,8 +43,8 @@ enum Yells
SAY_SLAY = 1,
SAY_DEATH = 2,
SAY_SUMMON_RHINO = 3,
- SAY_TRANSFORM_1 = 4,
- SAY_TRANSFORM_2 = 5
+ SAY_TRANSFORM = 4,
+ SAY_IMPALE = 5
};
enum Events
@@ -78,6 +78,7 @@ struct boss_gal_darah : public BossAI
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 1, 100.0f, true))
{
DoCast(target, SPELL_IMPALING_CHARGE);
+ Talk(SAY_IMPALE, target);
impaledList.insert(target->GetGUID());
}
}, 16s, 17s);
@@ -123,6 +124,7 @@ struct boss_gal_darah : public BossAI
me->m_Events.AddEventAtOffset([&] {
scheduler.CancelAll();
DoCastSelf(SPELL_TRANSFORM_TO_RHINO);
+ Talk(SAY_TRANSFORM);
}, 32s);
}
diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_the_lich_king.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_the_lich_king.cpp
index 862bd3d5c6..cf0c22a64e 100644
--- a/src/server/scripts/Northrend/IcecrownCitadel/boss_the_lich_king.cpp
+++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_the_lich_king.cpp
@@ -141,6 +141,7 @@ enum Spells
SPELL_SOUL_REAPER = 69409, // instant
SPELL_SOUL_REAPER_BUFF = 69410,
SPELL_SUMMON_VALKYR = 69037, // instant
+ SPELL_SUMMON_VALKYR_PERIODIC = 74361,
SPELL_WINGS_OF_THE_DAMNED = 74352,
SPELL_VALKYR_TARGET_SEARCH = 69030,
SPELL_HARVEST_SOUL_VALKYR = 68985, // vehicle aura used by Val'kyr Shadowguard and Strangulate Vehicle
@@ -1136,7 +1137,7 @@ public:
{
me->GetMap()->SetZoneMusic(AREA_THE_FROZEN_THRONE, MUSIC_SPECIAL);
Talk(SAY_LK_SUMMON_VALKYR);
- me->CastSpell((Unit*)nullptr, SPELL_SUMMON_VALKYR, false);
+ DoCastSelf(IsHeroic() ? SPELL_SUMMON_VALKYR_PERIODIC : SPELL_SUMMON_VALKYR);
events.ScheduleEvent(EVENT_SUMMON_VALKYR, 45s, EVENT_GROUP_ABILITIES);
// schedule a defile (or reschedule it) if next defile event
diff --git a/src/server/scripts/Northrend/Naxxramas/boss_faerlina.cpp b/src/server/scripts/Northrend/Naxxramas/boss_faerlina.cpp
index d6ddf9c64c..7694661d87 100644
--- a/src/server/scripts/Northrend/Naxxramas/boss_faerlina.cpp
+++ b/src/server/scripts/Northrend/Naxxramas/boss_faerlina.cpp
@@ -88,10 +88,12 @@ public:
void JustEngagedWith(Unit* who) override
{
BossAI::JustEngagedWith(who);
- me->CallForHelp(VISIBLE_RANGE);
- summons.DoZoneInCombat();
Talk(SAY_AGGRO);
+ scheduler.Schedule(1200ms, [this](TaskContext /*context*/) {
+ this->summons.DoZoneInCombat();
+ });
+
ScheduleTimedEvent(7s, 15s, [&]{
if (!me->HasAura(SPELL_WIDOWS_EMBRACE))
DoCastVictim(SPELL_POISON_BOLT_VOLLEY);
diff --git a/src/server/scripts/Northrend/Naxxramas/boss_four_horsemen.cpp b/src/server/scripts/Northrend/Naxxramas/boss_four_horsemen.cpp
index 693253cbb9..d73579c25b 100644
--- a/src/server/scripts/Northrend/Naxxramas/boss_four_horsemen.cpp
+++ b/src/server/scripts/Northrend/Naxxramas/boss_four_horsemen.cpp
@@ -165,14 +165,16 @@ public:
me->GetMotionMaster()->MovePoint(currentWaypoint, WaypointPositions[currentWaypoint]);
}
- bool IsInRoom()
+ void EnterEvadeMode(EvadeReason why) override
{
- if (me->GetExactDist(2535.1f, -2968.7f, 241.3f) > 100.0f)
+ if (why == EVADE_REASON_BOUNDARY)
{
- EnterEvadeMode();
- return false;
+ instance->GetCreature(DATA_BARON_RIVENDARE_BOSS)->AI()->EnterEvadeMode(EVADE_REASON_OTHER);
+ instance->GetCreature(DATA_LADY_BLAUMEUX_BOSS)->AI()->EnterEvadeMode(EVADE_REASON_OTHER);
+ instance->GetCreature(DATA_SIR_ZELIEK_BOSS)->AI()->EnterEvadeMode(EVADE_REASON_OTHER);
+ instance->GetCreature(DATA_THANE_KORTHAZZ_BOSS)->AI()->EnterEvadeMode(EVADE_REASON_OTHER);
}
- return true;
+ BossAI::EnterEvadeMode();
}
void Reset() override
@@ -208,7 +210,7 @@ public:
me->SetInCombatWithZone();
if (!UpdateVictim())
{
- EnterEvadeMode();
+ EnterEvadeMode(EVADE_REASON_NO_HOSTILES);
return;
}
if (me->GetEntry() == NPC_LADY_BLAUMEUX || me->GetEntry() == NPC_SIR_ZELIEK)
@@ -278,9 +280,6 @@ public:
currentWaypoint = 0;
}
- if (!IsInRoom())
- return;
-
if (movementPhase < MOVE_PHASE_FINISHED || !UpdateVictim())
return;
diff --git a/src/server/scripts/Northrend/Naxxramas/boss_gothik.cpp b/src/server/scripts/Northrend/Naxxramas/boss_gothik.cpp
index 23eb083d22..651db1e52d 100644
--- a/src/server/scripts/Northrend/Naxxramas/boss_gothik.cpp
+++ b/src/server/scripts/Northrend/Naxxramas/boss_gothik.cpp
@@ -282,7 +282,6 @@ public:
summon->AI()->AttackStart(target);
summon->SetInCombatWithZone();
summon->SetReactState(REACT_AGGRESSIVE);
- summon->CallForHelp(150.0f);
}
}
}
@@ -501,7 +500,7 @@ public:
events.Reset();
}
- void JustEngagedWith(Unit* /*who*/) override
+ void JustEngagedWith(Unit* /*who*/) override
{
switch (me->GetEntry())
{
diff --git a/src/server/scripts/Northrend/Naxxramas/boss_heigan.cpp b/src/server/scripts/Northrend/Naxxramas/boss_heigan.cpp
index b7a3822d51..e4d642000d 100644
--- a/src/server/scripts/Northrend/Naxxramas/boss_heigan.cpp
+++ b/src/server/scripts/Northrend/Naxxramas/boss_heigan.cpp
@@ -55,174 +55,142 @@ enum Misc
PHASE_FAST_DANCE = 1
};
-class boss_heigan : public CreatureScript
+float const heiganFastDanceFaceDirection = 2.40f;
+
+struct boss_heigan : public BossAI
{
-public:
- boss_heigan() : CreatureScript("boss_heigan") { }
+ boss_heigan(Creature* creature) : BossAI(creature, BOSS_HEIGAN) { }
- CreatureAI* GetAI(Creature* pCreature) const override
+ void Reset() override
{
- return GetNaxxramasAI<boss_heiganAI>(pCreature);
+ BossAI::Reset();
+ _currentPhase = 0;
+ _currentSection = 3;
+ _moveRight = true;
+ _eruptionScheduler.CancelAll();
}
- struct boss_heiganAI : public BossAI
+ void KilledUnit(Unit* who) override
{
- explicit boss_heiganAI(Creature* c) : BossAI(c, BOSS_HEIGAN)
- {}
-
- EventMap events;
- uint8 currentPhase{};
- uint8 currentSection{};
- bool moveRight{};
+ if (!who->IsPlayer())
+ return;
- void Reset() override
- {
- BossAI::Reset();
- events.Reset();
- currentPhase = 0;
- currentSection = 3;
- moveRight = true;
- }
+ Talk(SAY_SLAY);
+ instance->StorePersistentData(PERSISTENT_DATA_IMMORTAL_FAIL, 1);
+ }
- void KilledUnit(Unit* who) override
- {
- if (!who->IsPlayer())
- return;
+ void JustDied(Unit* killer) override
+ {
+ _eruptionScheduler.CancelAll();
+ BossAI::JustDied(killer);
+ Talk(EMOTE_DEATH);
+ }
- Talk(SAY_SLAY);
- instance->StorePersistentData(PERSISTENT_DATA_IMMORTAL_FAIL, 1);
- }
+ void JustEngagedWith(Unit* who) override
+ {
+ BossAI::JustEngagedWith(who);
+ me->SetInCombatWithZone();
+ Talk(SAY_AGGRO);
+ StartFightPhase(PHASE_SLOW_DANCE);
+ }
- void JustDied(Unit* killer) override
+ void StartFightPhase(uint8 phase)
+ {
+ _currentSection = 3;
+ _currentPhase = phase;
+ scheduler.CancelAll();
+ _eruptionScheduler.CancelAll();
+ if (phase == PHASE_SLOW_DANCE)
{
- BossAI::JustDied(killer);
- Talk(EMOTE_DEATH);
+ me->CastStop();
+ me->SetReactState(REACT_AGGRESSIVE);
+ DoZoneInCombat();
+ ScheduleTimedEvent(12s, 15s, [&] {
+ DoCastSelf(SPELL_SPELL_DISRUPTION);
+ }, 10s);
+ ScheduleTimedEvent(17s, [&] {
+ DoCastSelf(SPELL_DECREPIT_FEVER);
+ }, 22s, 25s);
+ _eruptionScheduler.Schedule(15s, [this](TaskContext context){
+ instance->SetData(DATA_HEIGAN_ERUPTION, _currentSection);
+ if (_currentSection == 3)
+ _moveRight = false;
+ else if (_currentSection == 0)
+ _moveRight = true;
+
+ _moveRight ? _currentSection++ : _currentSection--;
+ Talk(SAY_TAUNT);
+ context.Repeat(10s);
+ }).Schedule(90s, [this](TaskContext /*context*/) {
+ StartFightPhase(PHASE_FAST_DANCE);
+ });
}
-
- void JustEngagedWith(Unit* who) override
+ else // if (phase == PHASE_FAST_DANCE)
{
- BossAI::JustEngagedWith(who);
- me->SetInCombatWithZone();
- Talk(SAY_AGGRO);
- StartFightPhase(PHASE_SLOW_DANCE);
+ Talk(EMOTE_DANCE);
+ Talk(SAY_DANCE);
+ me->AttackStop();
+ me->StopMoving();
+ me->SetReactState(REACT_PASSIVE);
+ me->CastSpell(me, SPELL_TELEPORT_SELF, false);
+ me->SetFacingTo(heiganFastDanceFaceDirection);
+ scheduler.Schedule(1s, [this](TaskContext /*context*/) {
+ DoCastSelf(SPELL_PLAGUE_CLOUD);
+ });
+ _eruptionScheduler.Schedule(7s, [this](TaskContext context){
+ instance->SetData(DATA_HEIGAN_ERUPTION, _currentSection);
+ if (_currentSection == 3)
+ _moveRight = false;
+ else if (_currentSection == 0)
+ _moveRight = true;
+
+ _moveRight ? _currentSection++ : _currentSection--;
+ context.Repeat(4s);
+ }).Schedule(45s, [this](TaskContext /*context*/) {
+ StartFightPhase(PHASE_SLOW_DANCE);
+ Talk(EMOTE_DANCE_END); // avoid play the emote on aggro
+ });
}
+ ScheduleTimedEvent(5s, [&] {
+ CheckSafetyDance();
+ }, 5s);
+ }
- void StartFightPhase(uint8 phase)
+ void UpdateAI(uint32 diff) override
+ {
+ if (!UpdateVictim())
{
- currentSection = 3;
- currentPhase = phase;
- events.Reset();
- if (phase == PHASE_SLOW_DANCE)
- {
- me->CastStop();
- me->SetReactState(REACT_AGGRESSIVE);
- DoZoneInCombat();
- events.ScheduleEvent(EVENT_DISRUPTION, 12s, 15s);
- events.ScheduleEvent(EVENT_DECEPIT_FEVER, 17s);
- events.ScheduleEvent(EVENT_ERUPT_SECTION, 15s);
- events.ScheduleEvent(EVENT_SWITCH_PHASE, 90s);
- }
- else // if (phase == PHASE_FAST_DANCE)
- {
- Talk(EMOTE_DANCE);
- Talk(SAY_DANCE);
- me->AttackStop();
- me->StopMoving();
- me->SetReactState(REACT_PASSIVE);
- me->CastSpell(me, SPELL_TELEPORT_SELF, false);
- me->SetFacingTo(2.40f);
- events.ScheduleEvent(EVENT_PLAGUE_CLOUD, 1s);
- events.ScheduleEvent(EVENT_ERUPT_SECTION, 7s);
- events.ScheduleEvent(EVENT_SWITCH_PHASE, 45s);
- }
- events.ScheduleEvent(EVENT_SAFETY_DANCE, 5s);
+ return;
}
- bool IsInRoom(Unit* who)
- {
- if (who->GetPositionX() > 2826 || who->GetPositionX() < 2723 || who->GetPositionY() > -3641 || who->GetPositionY() < -3736)
- {
- if (who->GetGUID() == me->GetGUID())
- EnterEvadeMode();
+ _eruptionScheduler.Update(diff);
- return false;
- }
- return true;
- }
+ BossAI::UpdateAI(diff);
+ }
- void UpdateAI(uint32 diff) override
+ void CheckSafetyDance()
+ {
+ if (Map* map = me->GetMap())
{
- if (!IsInRoom(me))
- return;
-
- if (!UpdateVictim())
- return;
-
- events.Update(diff);
-
- switch (events.ExecuteEvent())
+ map->DoForAllPlayers([&](Player* p)
{
- case EVENT_DISRUPTION:
- me->CastSpell(me, SPELL_SPELL_DISRUPTION, false);
- events.Repeat(10s);
- break;
- case EVENT_DECEPIT_FEVER:
- me->CastSpell(me, SPELL_DECREPIT_FEVER, false);
- events.Repeat(22s, 25s);
- break;
- case EVENT_PLAGUE_CLOUD:
- me->CastSpell(me, SPELL_PLAGUE_CLOUD, false);
- break;
- case EVENT_SWITCH_PHASE:
- if (currentPhase == PHASE_SLOW_DANCE)
- {
- StartFightPhase(PHASE_FAST_DANCE);
- }
- else
- {
- StartFightPhase(PHASE_SLOW_DANCE);
- Talk(EMOTE_DANCE_END); // avoid play the emote on aggro
- }
- break;
- case EVENT_ERUPT_SECTION:
- {
- instance->SetData(DATA_HEIGAN_ERUPTION, currentSection);
- if (currentSection == 3)
- moveRight = false;
- else if (currentSection == 0)
- moveRight = true;
-
- moveRight ? currentSection++ : currentSection--;
-
- if (currentPhase == PHASE_SLOW_DANCE)
- Talk(SAY_TAUNT);
-
- events.Repeat(currentPhase == PHASE_SLOW_DANCE ? 10s : 4s);
- break;
- }
- case EVENT_SAFETY_DANCE:
+ if (IsInBoundary(p) && !p->IsAlive())
{
- Map::PlayerList const& pList = me->GetMap()->GetPlayers();
- for (auto const& itr : pList)
- {
- if (IsInRoom(itr.GetSource()) && !itr.GetSource()->IsAlive())
- {
- instance->SetData(DATA_DANCE_FAIL, 0);
- instance->StorePersistentData(PERSISTENT_DATA_IMMORTAL_FAIL, 1);
- return;
- }
- }
- events.Repeat(5s);
+ instance->SetData(DATA_DANCE_FAIL, 0);
+ instance->StorePersistentData(PERSISTENT_DATA_IMMORTAL_FAIL, 1);
return;
}
- }
-
- DoMeleeAttackIfReady();
+ });
}
- };
+ }
+private:
+ uint8 _currentPhase{};
+ uint8 _currentSection{};
+ bool _moveRight{true};
+ TaskScheduler _eruptionScheduler;
};
void AddSC_boss_heigan()
{
- new boss_heigan();
+ RegisterNaxxramasCreatureAI(boss_heigan);
}
diff --git a/src/server/scripts/Northrend/Naxxramas/boss_thaddius.cpp b/src/server/scripts/Northrend/Naxxramas/boss_thaddius.cpp
index efe7c6956f..61c609fe45 100644
--- a/src/server/scripts/Northrend/Naxxramas/boss_thaddius.cpp
+++ b/src/server/scripts/Northrend/Naxxramas/boss_thaddius.cpp
@@ -357,8 +357,11 @@ public:
explicit boss_thaddius_summonAI(Creature* c) : ScriptedAI(c)
{
overload = false;
+ instance = c->GetInstanceScript();
+ SetBoundary(instance->GetBossBoundary(BOSS_THADDIUS));
}
+ InstanceScript* instance;
EventMap events;
uint32 pullTimer{};
uint32 visualTimer{};
@@ -383,6 +386,10 @@ public:
void EnterEvadeMode(EvadeReason why) override
{
me->SetControlled(false, UNIT_STATE_STUNNED);
+
+ if (why == EVADE_REASON_BOUNDARY)
+ instance->GetCreature(DATA_THADDIUS_BOSS)->AI()->EnterEvadeMode(EVADE_REASON_BOUNDARY);
+
ScriptedAI::EnterEvadeMode(why);
}
@@ -683,22 +690,18 @@ public:
};
};
-class at_thaddius_entrance : public AreaTriggerScript
+class at_thaddius_entrance : public OnlyOnceAreaTriggerScript
{
public:
- at_thaddius_entrance() : AreaTriggerScript("at_thaddius_entrance") { }
+ at_thaddius_entrance() : OnlyOnceAreaTriggerScript("at_thaddius_entrance") { }
- bool OnTrigger(Player* player, AreaTrigger const* /*areaTrigger*/) override
+ bool _OnTrigger(Player* player, const AreaTrigger* /*trigger*/) override
{
- InstanceScript* instance = player->GetInstanceScript();
- if (!instance || instance->GetData(DATA_THADDIUS_INTRO) || instance->GetBossState(BOSS_THADDIUS) == DONE)
- return true;
-
- if (Creature* thaddius = instance->GetCreature(DATA_THADDIUS_BOSS))
- thaddius->AI()->Talk(SAY_GREET);
-
- instance->SetData(DATA_THADDIUS_INTRO, 1);
- return true;
+ if (InstanceScript* instance = player->GetInstanceScript())
+ if (instance->GetBossState(BOSS_THADDIUS) != DONE)
+ if (Creature* thaddius = instance->GetCreature(DATA_THADDIUS_BOSS))
+ thaddius->AI()->Talk(SAY_GREET);
+ return false;
}
};
diff --git a/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp b/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp
index 77af6636bd..9014646366 100644
--- a/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp
+++ b/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp
@@ -25,6 +25,40 @@
#include "Player.h"
#include "naxxramas.h"
+BossBoundaryData const boundaries =
+{
+ /* Arachnid Quarter */
+ { BOSS_ANUB, new CircleBoundary(Position(3273.376709f, -3475.876709f), Position(3195.668213f, -3475.930176f)) },
+ { BOSS_FAERLINA, new RectangleBoundary(3315.0f, 3402.0f, -3727.0f, -3590.0f) },
+ { BOSS_FAERLINA, new CircleBoundary(Position(3372.68f, -3648.2f), Position(3316.0f, -3704.26f)) },
+ { BOSS_MAEXXNA, new CircleBoundary(Position(3502.2587f, -3892.1697f), Position(3418.7422f, -3840.271f)) },
+
+ /* Plague Quarter */
+ { BOSS_NOTH, new RectangleBoundary(2618.0f, 2754.0f, -3557.43f, -3450.0f) },
+ { BOSS_HEIGAN, new CircleBoundary(Position(2772.57f, -3685.28f), 56.0f) },
+ { BOSS_HEIGAN, new RectangleBoundary(2723.0f, 2826.0f, -3736.0f, -3641.0f) },
+ { BOSS_LOATHEB, new CircleBoundary(Position(2909.0f, -3997.41f), 57.0f) },
+
+ /* Military Quarter */
+ { BOSS_RAZUVIOUS, new ZRangeBoundary(260.0f, 287.0f) }, // will not chase onto the upper floor
+ { BOSS_GOTHIK, new RectangleBoundary(2627.0f, 2764.0f, -3440.0f, -3275.0f) },
+ { BOSS_HORSEMAN, new ParallelogramBoundary(Position(2646.0f, -2959.0f), Position(2529.0f, -3075.0f), Position(2506.0f, -2854.0f)) },
+
+ /* Construct Quarter */
+ { BOSS_PATCHWERK, new CircleBoundary(Position(3204.0f, -3241.4f), 240.0f) },
+ { BOSS_PATCHWERK, new CircleBoundary(Position(3130.8576f, -3210.36f), Position(3085.37f, -3219.85f), true) }, // entrance slime circle blocker
+ { BOSS_GROBBULUS, new CircleBoundary(Position(3204.0f, -3241.4f), 240.0f) },
+ { BOSS_GROBBULUS, new RectangleBoundary(3295.0f, 3340.0f, -3254.2f, -3230.18f, true) }, // entrance door blocker
+ { BOSS_GLUTH, new CircleBoundary(Position(3293.0f, -3142.0f), 80.0) },
+ { BOSS_GLUTH, new ParallelogramBoundary(Position(3401.0f, -3149.0f), Position(3261.0f, -3028.0f), Position(3320.0f, -3267.0f)) },
+ { BOSS_GLUTH, new ZRangeBoundary(285.0f, 310.0f) },
+ { BOSS_THADDIUS, new ParallelogramBoundary(Position(3478.3f, -3070.0f), Position(3370.0f, -2961.5f), Position(3580.0f, -2961.5f)) },
+
+ /* Frostwyrm Lair */
+ { BOSS_SAPPHIRON, new CircleBoundary(Position(3517.627f, -5255.5f), 110.0) },
+ { BOSS_KELTHUZAD, new CircleBoundary(Position(3716.0f, -5107.0f), 85.0) }
+};
+
struct LivingPoisonData
{
Position Start {};
@@ -109,6 +143,7 @@ static ObjectData const creatureData[]
{ NPC_THADDIUS, DATA_THADDIUS_BOSS },
{ NPC_RAZUVIOUS, DATA_RAZUVIOUS_BOSS },
{ NPC_GOTHIK, DATA_GOTHIK_BOSS },
+ { NPC_HEIGAN, DATA_HEIGAN_BOSS },
{ NPC_BARON_RIVENDARE, DATA_BARON_RIVENDARE_BOSS },
{ NPC_SIR_ZELIEK, DATA_SIR_ZELIEK_BOSS },
{ NPC_LADY_BLAUMEUX, DATA_LADY_BLAUMEUX_BOSS },
@@ -146,6 +181,7 @@ public:
SetPersistentDataCount(PERSISTENT_DATA_COUNT);
LoadDoorData(doorData);
LoadObjectData(creatureData, gameObjectData);
+ LoadBossBoundaries(boundaries);
// GameObjects
for (auto& i : _heiganEruption)
@@ -158,6 +194,7 @@ public:
_events.Reset();
_currentWingTaunt = SAY_FIRST_WING_TAUNT;
_horsemanLoaded = 0;
+ _thaddiusScreamsScheduled = false;
// Achievements
_abominationsKilled = 0;
@@ -233,7 +270,11 @@ public:
{
InstanceScript::OnPlayerEnter(player);
- _events.ScheduleEvent(EVENT_THADDIUS_SCREAMS, 2min, 2min + 30s);
+ if (!_thaddiusScreamsScheduled)
+ {
+ _thaddiusScreamsScheduled = true;
+ _events.ScheduleEvent(EVENT_THADDIUS_SCREAMS, 2min, 2min + 30s);
+ }
}
void OnCreatureCreate(Creature* creature) override
@@ -547,12 +588,17 @@ public:
}
case DONE:
{
+ if (!horsemanKilled) // if no horsemen are found, assume wing is cleared
+ {
+ ActivateWingPortal(DATA_HORSEMAN_PORTAL);
+ break;
+ }
+
_events.RescheduleEvent(EVENT_AND_THEY_WOULD_ALL_GO_DOWN_TOGETHER, 15s);
if (horsemanKilled != HorsemanCount)
return false;
- // all horsemans are killed
if (Creature* cr = GetCreature(DATA_BARON_RIVENDARE_BOSS))
cr->CastSpell(cr, SPELL_THE_FOUR_HORSEMAN_CREDIT, true);
@@ -614,7 +660,7 @@ public:
break;
instance->PlayDirectSoundToMap(SOUND_SCREAM + urand(0, 3));
- return _events.ScheduleEvent(EVENT_THADDIUS_SCREAMS, 2min, 2min + 30s);
+ return _events.ScheduleEvent(EVENT_THADDIUS_SCREAMS, 5min, 10min);
}
case EVENT_AND_THEY_WOULD_ALL_GO_DOWN_TOGETHER:
_horsemanAchievement = false;
@@ -646,7 +692,9 @@ public:
return CreatureTalk(DATA_BARON_RIVENDARE_BOSS, SAY_HORSEMEN_DIALOG2);
case EVENT_FROSTWYRM_WATERFALL_DOOR:
SetGoState(DATA_SAPPHIRON_GATE, GO_STATE_ACTIVE);
- return _events.ScheduleEvent(EVENT_KELTHUZAD_LICH_KING_TALK1, 5s);
+ if (GetBossState(BOSS_KELTHUZAD) != DONE)
+ _events.ScheduleEvent(EVENT_KELTHUZAD_LICH_KING_TALK1, 5s);
+ break;
case EVENT_KELTHUZAD_LICH_KING_TALK1:
CreatureTalk(DATA_KELTHUZAD_BOSS, SAY_SAPP_DIALOG1);
return _events.ScheduleEvent(EVENT_KELTHUZAD_LICH_KING_TALK2, 10s);
@@ -675,6 +723,7 @@ private:
EventMap _events;
uint8 _currentWingTaunt;
uint8 _horsemanLoaded;
+ bool _thaddiusScreamsScheduled;
// GameObjects
std::set<GameObject*> _heiganEruption[HeiganEruptSectionCount];
diff --git a/src/server/scripts/Northrend/Naxxramas/naxxramas.h b/src/server/scripts/Northrend/Naxxramas/naxxramas.h
index 6f47c4fa1c..c28bc2ee4f 100644
--- a/src/server/scripts/Northrend/Naxxramas/naxxramas.h
+++ b/src/server/scripts/Northrend/Naxxramas/naxxramas.h
@@ -50,13 +50,14 @@ enum NaxxramasData
DATA_THADDIUS_BOSS = 103,
DATA_RAZUVIOUS_BOSS = 104,
DATA_GOTHIK_BOSS = 105,
- DATA_BARON_RIVENDARE_BOSS = 106,
- DATA_SIR_ZELIEK_BOSS = 107,
- DATA_LADY_BLAUMEUX_BOSS = 108,
- DATA_THANE_KORTHAZZ_BOSS = 109,
- DATA_SAPPHIRON_BOSS = 110,
- DATA_KELTHUZAD_BOSS = 111,
- DATA_LICH_KING_BOSS = 112,
+ DATA_HEIGAN_BOSS = 106,
+ DATA_BARON_RIVENDARE_BOSS = 107,
+ DATA_SIR_ZELIEK_BOSS = 108,
+ DATA_LADY_BLAUMEUX_BOSS = 109,
+ DATA_THANE_KORTHAZZ_BOSS = 110,
+ DATA_SAPPHIRON_BOSS = 111,
+ DATA_KELTHUZAD_BOSS = 112,
+ DATA_LICH_KING_BOSS = 113,
DATA_LOATHEB_PORTAL = 200,
DATA_MAEXXNA_PORTAL = 201,
@@ -75,17 +76,15 @@ enum NaxxramasData
DATA_DANCE_FAIL = 301,
DATA_SPORE_KILLED = 302,
DATA_FRENZY_REMOVED = 303,
- DATA_THADDIUS_INTRO = 304,
- DATA_CHARGES_CROSSED = 305,
- DATA_HUNDRED_CLUB = 306,
- DATA_ABOMINATION_KILLED = 307,
+ DATA_CHARGES_CROSSED = 304,
+ DATA_HUNDRED_CLUB = 305,
+ DATA_ABOMINATION_KILLED = 306,
};
enum NaxxramasPersistentData
{
- PERSISTENT_DATA_THADDIUS_INTRO = 0,
- PERSISTENT_DATA_KELTHUZAD_DIALOG = 1,
- PERSISTENT_DATA_IMMORTAL_FAIL = 2,
+ PERSISTENT_DATA_KELTHUZAD_DIALOG = 0,
+ PERSISTENT_DATA_IMMORTAL_FAIL = 1,
PERSISTENT_DATA_COUNT
};
@@ -169,6 +168,9 @@ enum NaxxramasCreatureId
// Gothik
NPC_GOTHIK = 16060,
+ // Heigan the Unclean
+ NPC_HEIGAN = 15936,
+
// Four horseman
NPC_BARON_RIVENDARE = 30549,
NPC_SIR_ZELIEK = 16063,
diff --git a/src/server/scripts/Northrend/Nexus/EyeOfEternity/boss_malygos.cpp b/src/server/scripts/Northrend/Nexus/EyeOfEternity/boss_malygos.cpp
index 3d77b31d5e..bdf1ba1187 100644
--- a/src/server/scripts/Northrend/Nexus/EyeOfEternity/boss_malygos.cpp
+++ b/src/server/scripts/Northrend/Nexus/EyeOfEternity/boss_malygos.cpp
@@ -234,6 +234,8 @@ public:
me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_PACIFIED);
me->RemoveUnitFlag(UNIT_FLAG_DISABLE_MOVE);
+ me->SetAnimTier(AnimTier::Fly);
+
if (pInstance)
{
pInstance->SetData(DATA_ENCOUNTER_STATUS, NOT_STARTED);
@@ -277,12 +279,6 @@ public:
case MI_POINT_SURGE_OF_POWER_CENTER:
events.RescheduleEvent(EVENT_SURGE_OF_POWER_WARNING, 0ms, 1);
break;
- }
- }
- else if (type == EFFECT_MOTION_TYPE)
- {
- switch (id)
- {
case MI_POINT_INTRO_LAND:
me->SetDisableGravity(false);
events.RescheduleEvent(EVENT_START_FIGHT, 0ms, 1);
@@ -401,7 +397,7 @@ public:
}
case EVENT_INTRO_LAND:
{
- me->GetMotionMaster()->MoveLand(MI_POINT_INTRO_LAND, me->GetPositionX(), me->GetPositionY(), CenterPos.GetPositionZ(), 7.0f);
+ me->GetMotionMaster()->MovePoint(MI_POINT_INTRO_LAND, me->GetPositionX(), me->GetPositionY(), CenterPos.GetPositionZ(), FORCED_MOVEMENT_RUN, 0.f, 0.f, true, true, MOTION_SLOT_ACTIVE, AnimTier::Ground);
break;
}
case EVENT_START_FIGHT:
@@ -462,7 +458,7 @@ public:
me->GetMotionMaster()->MoveIdle();
me->StopMoving();
me->SetDisableGravity(true);
- me->GetMotionMaster()->MoveTakeoff(MI_POINT_VORTEX_TAKEOFF, me->GetPositionX(), me->GetPositionY(), CenterPos.GetPositionZ() + 20.0f, 7.0f);
+ me->GetMotionMaster()->MovePoint(MI_POINT_VORTEX_TAKEOFF, me->GetPositionX(), me->GetPositionY(), CenterPos.GetPositionZ() + 20.0f, FORCED_MOVEMENT_RUN, 0.f, 0.f, true, true, MOTION_SLOT_ACTIVE, AnimTier::Fly);
events.DelayEvents(25s, 1); // don't delay berserk (group 0)
}
@@ -539,7 +535,7 @@ public:
break;
}
case EVENT_VORTEX_LAND_0:
- me->GetMotionMaster()->MoveLand(MI_POINT_VORTEX_LAND, CenterPos, 7.0f);
+ me->GetMotionMaster()->MovePoint(MI_POINT_VORTEX_LAND, CenterPos, FORCED_MOVEMENT_RUN, 0.f, true, true, AnimTier::Ground);
break;
case EVENT_VORTEX_LAND_1:
@@ -573,7 +569,7 @@ public:
me->GetMotionMaster()->MoveIdle();
me->DisableSpline();
me->SetDisableGravity(true);
- me->GetMotionMaster()->MoveTakeoff(MI_POINT_CENTER_AIR_PH_2, me->GetPositionX(), me->GetPositionY(), me->GetPositionZ() + 32.0f, 7.0f);
+ me->GetMotionMaster()->MovePoint(MI_POINT_CENTER_AIR_PH_2, me->GetPositionX(), me->GetPositionY(), me->GetPositionZ() + 32.0f, FORCED_MOVEMENT_RUN, 0.f, 0.f, true, true, MOTION_SLOT_ACTIVE, AnimTier::Fly);
events.RescheduleEvent(EVENT_START_PHASE_2_MOVE_TO_SIDE, 22s + 500ms, 1);
break;
}
@@ -699,7 +695,7 @@ public:
case EVENT_MOVE_TO_PHASE_3_POSITION:
{
me->SendMeleeAttackStop(me->GetVictim());
- me->GetMotionMaster()->MoveTakeoff(MI_POINT_PH_3_FIGHT_POSITION, CenterPos.GetPositionX(), CenterPos.GetPositionY(), CenterPos.GetPositionZ() - 5.0f, me->GetSpeed(MOVE_RUN));
+ me->GetMotionMaster()->MovePoint(MI_POINT_PH_3_FIGHT_POSITION, CenterPos.GetPositionX(), CenterPos.GetPositionY(), CenterPos.GetPositionZ() - 5.0f, FORCED_MOVEMENT_RUN, 0.f, 0.f, true, true, MOTION_SLOT_ACTIVE, AnimTier::Fly);
me->GetThreatMgr().ClearAllThreat(); // players on vehicle are unattackable -> leads to EnterEvadeMode() because target is not acceptable!
diff --git a/src/server/scripts/Northrend/Nexus/Nexus/boss_anomalus.cpp b/src/server/scripts/Northrend/Nexus/Nexus/boss_anomalus.cpp
index a029169021..f64ba0ac5e 100644
--- a/src/server/scripts/Northrend/Nexus/Nexus/boss_anomalus.cpp
+++ b/src/server/scripts/Northrend/Nexus/Nexus/boss_anomalus.cpp
@@ -43,10 +43,8 @@ enum Yells
enum Events
{
EVENT_ANOMALUS_SPARK = 1,
- EVENT_ANOMALUS_HEALTH = 2,
- EVENT_ANOMALUS_ARCANE_ATTRACTION = 3,
- EVENT_ANOMALUS_SPAWN_RIFT = 4,
- EVENT_ANOMALUS_SPAWN_RIFT_EMPOWERED = 5
+ EVENT_ANOMALUS_ARCANE_ATTRACTION = 2,
+ EVENT_ANOMALUS_SPAWN_RIFT = 3
};
class ChargeRifts : public BasicEvent
@@ -71,6 +69,7 @@ struct boss_anomalus : public BossAI
{
boss_anomalus(Creature* creature) : BossAI(creature, DATA_ANOMALUS_EVENT) { }
+ bool _empowered;
bool achievement;
uint16 activeRifts;
@@ -78,6 +77,7 @@ struct boss_anomalus : public BossAI
{
BossAI::Reset();
achievement = true;
+ _empowered = true;
me->CastSpell(me, SPELL_CLOSE_RIFTS, true);
}
@@ -115,7 +115,6 @@ struct boss_anomalus : public BossAI
activeRifts = 0;
events.ScheduleEvent(EVENT_ANOMALUS_SPARK, 5s);
- events.ScheduleEvent(EVENT_ANOMALUS_HEALTH, 1s);
events.ScheduleEvent(EVENT_ANOMALUS_SPAWN_RIFT, IsHeroic() ? 15s : 25s);
if (IsHeroic())
events.ScheduleEvent(EVENT_ANOMALUS_ARCANE_ATTRACTION, 8s);
@@ -133,7 +132,9 @@ struct boss_anomalus : public BossAI
if (!UpdateVictim())
return;
- events.Update(diff);
+ if (!me->HasAura(SPELL_RIFT_SHIELD))
+ events.Update(diff);
+
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
@@ -143,16 +144,6 @@ struct boss_anomalus : public BossAI
me->CastSpell(me->GetVictim(), SPELL_SPARK, false);
events.ScheduleEvent(EVENT_ANOMALUS_SPARK, 5s);
break;
- case EVENT_ANOMALUS_HEALTH:
- if (me->HealthBelowPct(51))
- {
- //First time we reach 51%, the next rift going to be empowered following timings.
- events.CancelEvent(EVENT_ANOMALUS_SPAWN_RIFT);
- events.ScheduleEvent(EVENT_ANOMALUS_SPAWN_RIFT_EMPOWERED, 1s);
- break;
- }
- events.ScheduleEvent(EVENT_ANOMALUS_HEALTH, 1s);
- break;
case EVENT_ANOMALUS_ARCANE_ATTRACTION:
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 50.0f, true))
me->CastSpell(target, SPELL_ARCANE_ATTRACTION, false);
@@ -162,18 +153,18 @@ struct boss_anomalus : public BossAI
Talk(SAY_RIFT);
Talk(EMOTE_RIFT);
me->CastSpell(me, SPELL_CREATE_RIFT, false);
- //Once we hit 51% hp mark, after each rift we spawn an empowered
- events.ScheduleEvent(me->HealthBelowPct(51) ? EVENT_ANOMALUS_SPAWN_RIFT_EMPOWERED : EVENT_ANOMALUS_SPAWN_RIFT, IsHeroic() ? 15s : 25s);
- break;
- case EVENT_ANOMALUS_SPAWN_RIFT_EMPOWERED:
- Talk(SAY_RIFT);
- Talk(EMOTE_RIFT);
- me->CastSpell(me, SPELL_CREATE_RIFT, false);
- me->CastSpell(me, SPELL_RIFT_SHIELD, true);
- me->m_Events.AddEventAtOffset(new ChargeRifts(me), 1s);
- events.DelayEvents(46s);
- //As we just spawned an empowered spawn a normal one
+ //Once we hit 51% hp mark, alternate between empowered and normal rifts
+ if (me->HealthBelowPct(51))
+ {
+ if (_empowered)
+ {
+ me->CastSpell(me, SPELL_RIFT_SHIELD, true);
+ me->m_Events.AddEventAtOffset(new ChargeRifts(me), 1s);
+ }
+ _empowered = !_empowered;
+ }
+
events.ScheduleEvent(EVENT_ANOMALUS_SPAWN_RIFT, IsHeroic() ? 15s : 25s);
break;
}
diff --git a/src/server/scripts/Northrend/Nexus/Oculus/instance_oculus.cpp b/src/server/scripts/Northrend/Nexus/Oculus/instance_oculus.cpp
index 25a253a648..fcf59ae2ed 100644
--- a/src/server/scripts/Northrend/Nexus/Oculus/instance_oculus.cpp
+++ b/src/server/scripts/Northrend/Nexus/Oculus/instance_oculus.cpp
@@ -63,23 +63,27 @@ public:
memset(&m_auiEncounter, 0, sizeof(m_auiEncounter));
}
- void OnCreatureCreate(Creature* pCreature) override
+ void OnCreatureCreate(Creature* creature) override
{
- switch (pCreature->GetEntry())
+ switch (creature->GetEntry())
{
case NPC_DRAKOS:
- uiDrakosGUID = pCreature->GetGUID();
+ uiDrakosGUID = creature->GetGUID();
break;
case NPC_VAROS:
- uiVarosGUID = pCreature->GetGUID();
+ uiVarosGUID = creature->GetGUID();
break;
case NPC_UROM:
- uiUromGUID = pCreature->GetGUID();
+ uiUromGUID = creature->GetGUID();
break;
case NPC_EREGOS:
- uiEregosGUID = pCreature->GetGUID();
+ uiEregosGUID = creature->GetGUID();
break;
}
+
+ if (sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_GROUP))
+ if (creature->EntryEquals(NPC_AMBER_DRAKE, NPC_EMERALD_DRAKE, NPC_RUBY_DRAKE))
+ creature->SetFaction(FACTION_FRIENDLY); // Friendly faction to allow interaction from both factions
}
void OnGameObjectCreate(GameObject* pGo) override
diff --git a/src/server/scripts/Northrend/Ulduar/HallsOfStone/boss_sjonnir.cpp b/src/server/scripts/Northrend/Ulduar/HallsOfStone/boss_sjonnir.cpp
index fd998bd7dd..d4b72fcb36 100644
--- a/src/server/scripts/Northrend/Ulduar/HallsOfStone/boss_sjonnir.cpp
+++ b/src/server/scripts/Northrend/Ulduar/HallsOfStone/boss_sjonnir.cpp
@@ -28,6 +28,7 @@ enum Spells
SPELL_LIGHTNING_SHIELD = 50831,
SPELL_STATIC_CHARGE = 50834,
SPELL_LIGHTNING_RING = 50840,
+ SPELL_LIGHTNING_RING_5S = 51849,
// IRON SLUDGE
SPELL_TOXIC_VOLLEY = 50838,
@@ -41,7 +42,6 @@ enum Spells
enum Npc
{
- NPC_IRON_SLUDGE = 28165, // if 2 ooze then spawn 1 iron_sludge
NPC_DWARFES_FRIENDLY = 27980, //after fix the machine by Brann
NPC_OOZE = 27981, //spawn after killing dwarf
NPC_FORGED_IRON_DWARF = 27982,
@@ -57,15 +57,6 @@ enum Yells
enum Events
{
- // SJONNIR
- EVENT_SHIELD = 1,
- EVENT_CHAIN_LIGHTNING = 2,
- EVENT_STATIC_CHARGE = 3,
- EVENT_LIGHTNING_RING = 4,
- EVENT_CHECK_HEALTH = 5,
- EVENT_SUMMON = 6,
- EVENT_SUMMON_SPEACH = 7,
-
// TRASH
EVENT_MALFORMED_OOZE_CHECK = 10,
EVENT_TOXIC_VOLLEY = 11,
@@ -73,8 +64,11 @@ enum Events
EVENT_FORGED_LIGHTNING_TETHER = 13,
};
-enum Misc
+enum SjonnirMisc
{
+ GROUP_SUMMONS = 1,
+ GROUP_LIGHTNING_RING = 2,
+
POS_GEN_RIGHT = 0,
POS_GEN_LEFT = 1,
POS_ROOM_CENTER = 2,
@@ -83,13 +77,6 @@ enum Misc
ACTION_SLUG_KILLED = 1,
};
-enum SummonPhases
-{
- PHASE_SUMMON_UNFRIENDLY_DWARFES = 0,
- PHASE_SUMMON_OOZE = 1,
- PHASE_SUMMON_FRIENDLY_DWARFES = 2,
-};
-
static Position RoomPosition[] =
{
{1293.0f, 610.0f, 199.3f, 0.0f},
@@ -107,75 +94,135 @@ public:
return GetHallsOfStoneAI<boss_sjonnirAI>(pCreature);
}
- struct boss_sjonnirAI : public ScriptedAI
+ struct boss_sjonnirAI : public BossAI
{
- boss_sjonnirAI(Creature* c) : ScriptedAI(c), summons(me)
- {
- pInstance = c->GetInstanceScript();
- }
+ boss_sjonnirAI(Creature* c) : BossAI(c, BOSS_SJONNIR) { }
- InstanceScript* pInstance;
- EventMap events;
- SummonList summons;
-
- uint8 SummonPhase;
uint8 SlugeCount;
void Reset() override
{
- events.Reset();
- summons.DespawnAll();
-
+ _Reset();
+ scheduler.ClearValidator();
SlugeCount = 0;
- SummonPhase = PHASE_SUMMON_UNFRIENDLY_DWARFES;
+ instance->SetData(DATA_SJONNIR_ACHIEVEMENT, false);
- if (pInstance)
+ if (instance->GetData(BOSS_TRIBUNAL_OF_AGES) == DONE)
{
- pInstance->SetData(BOSS_SJONNIR, NOT_STARTED);
- pInstance->SetData(DATA_SJONNIR_ACHIEVEMENT, false);
+ if (GameObject* console = me->GetMap()->GetGameObject(instance->GetGuidData(GO_SJONNIR_CONSOLE)))
+ console->SetGoState(GO_STATE_READY);
- me->SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE);
- if (pInstance->GetData(BOSS_TRIBUNAL_OF_AGES) == DONE)
+ if (Creature* brann = ObjectAccessor::GetCreature(*me, instance->GetGuidData(NPC_BRANN)))
{
- me->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE);
+ brann->setDeathState(DeathState::JustDied);
+ brann->Respawn();
+ brann->AI()->DoAction(ACTION_SJONNIR_WIPE_START);
+ }
+ }
+
+ ScheduleHealthCheckEvent(75, [&] {
+ scheduler.CancelGroup(GROUP_SUMMONS);
+ scheduler.Schedule(1s, GROUP_SUMMONS, [&](TaskContext context) {
+ uint8 Pos = urand(POS_GEN_RIGHT, POS_GEN_LEFT);
+ me->SummonCreature(NPC_FORGED_IRON_TROGG, RoomPosition[Pos].GetPositionX(), RoomPosition[Pos].GetPositionY(), RoomPosition[Pos].GetPositionZ(), 0, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 20000);
+ ActivatePipe(Pos);
+ context.Repeat(5s, 7s);
+ });
+ });
+
+ ScheduleHealthCheckEvent(50, [&] {
+ if (Creature* brann = ObjectAccessor::GetCreature(*me, instance->GetGuidData(NPC_BRANN)))
+ brann->AI()->Talk(SAY_BRANN_SPAWN_OOZE);
+
+ scheduler.CancelGroup(GROUP_SUMMONS);
+ scheduler.Schedule(3s, GROUP_SUMMONS, [&](TaskContext context) {
+ uint8 pos = urand(POS_GEN_RIGHT, POS_GEN_LEFT);
+ if (Creature* ooze = me->SummonCreature(NPC_OOZE, RoomPosition[pos], TEMPSUMMON_CORPSE_TIMED_DESPAWN, 20000))
+ {
+ ActivatePipe(pos);
+ ooze->GetMotionMaster()->MovePoint(0, RoomPosition[POS_ROOM_CENTER].GetPositionX(), RoomPosition[POS_ROOM_CENTER].GetPositionY(), RoomPosition[POS_ROOM_CENTER].GetPositionZ());
+ ooze->SetReactState(REACT_PASSIVE);
+ ooze->SetWalk(true);
+ }
+
+ context.Repeat();
+ });
+ });
- if (GameObject* console = me->GetMap()->GetGameObject( pInstance->GetGuidData(GO_SJONNIR_CONSOLE)))
- console->SetGoState(GO_STATE_READY);
+ ScheduleHealthCheckEvent(25, [&] {
+ if (Creature* brann = ObjectAccessor::GetCreature(*me, instance->GetGuidData(NPC_BRANN)))
+ brann->AI()->Talk(SAY_BRANN_SPAWN_EARTHEN);
- if (Creature* brann = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(NPC_BRANN)))
+ scheduler.CancelGroup(GROUP_SUMMONS);
+ scheduler.Schedule(1s, GROUP_SUMMONS, [&](TaskContext context) {
+ for (int i = 0; i < 3; i++)
{
- brann->setDeathState(DeathState::JustDied);
- brann->Respawn();
- brann->AI()->DoAction(ACTION_SJONNIR_WIPE_START);
+ uint8 pos = urand(POS_GEN_RIGHT, POS_GEN_LEFT);
+ if (Creature* dwarf = me->SummonCreature(NPC_DWARFES_FRIENDLY, RoomPosition[pos], TEMPSUMMON_CORPSE_TIMED_DESPAWN, 20000))
+ {
+ if (Player* plr = SelectTargetFromPlayerList(100.0f))
+ dwarf->SetFaction(plr->GetFaction());
+
+ ActivatePipe(pos);
+ dwarf->AI()->AttackStart(me);
+ }
}
- }
- }
+
+ context.Repeat(10s, 20s);
+ });
+ });
+
+ ScheduleHealthCheckEvent(20, [&] {
+ scheduler.CancelGroup(GROUP_LIGHTNING_RING);
+ DoCastSelf(SPELL_FRENZY, true);
+
+ ScheduleTimedEvent(1s, [&] {
+ DoCastSelf(SPELL_LIGHTNING_RING_5S);
+ }, 11s);
+ });
+ }
+
+ void ScheduleTasks() override
+ {
+ ScheduleTimedEvent(14s, 19s, [&] {
+ DoCastSelf(SPELL_LIGHTNING_SHIELD);
+ }, 14s, 19s);
+
+ ScheduleTimedEvent(6s, 12s, [&] {
+ DoCastVictim(SPELL_CHAIN_LIGHTNING);
+ }, 6s, 12s);
+
+ ScheduleTimedEvent(24s, [&] {
+ DoCastRandomTarget(SPELL_STATIC_CHARGE, 0, 50.0f);
+ }, 20s);
+
+ scheduler.Schedule(30s, GROUP_LIGHTNING_RING, [&](TaskContext context) {
+ DoCastAOE(SPELL_LIGHTNING_RING);
+ context.Repeat(40s);
+ });
+
+ if (Creature* brann = ObjectAccessor::GetCreature(*me, instance->GetGuidData(NPC_BRANN)))
+ brann->AI()->Talk(SAY_BRANN_SPAWN_TROGG, 20s);
+
+ scheduler.Schedule(5s, GROUP_SUMMONS, [&](TaskContext context) {
+ uint8 pos = urand(POS_GEN_RIGHT, POS_GEN_LEFT);
+ me->SummonCreature(NPC_FORGED_IRON_DWARF, RoomPosition[pos], TEMPSUMMON_CORPSE_TIMED_DESPAWN, 20000);
+ ActivatePipe(pos);
+ context.Repeat(30s);
+ });
}
void JustEngagedWith(Unit* /*who*/) override
{
+ _JustEngagedWith();
Talk(SAY_AGGRO);
- events.ScheduleEvent(EVENT_CHECK_HEALTH, 1s);
- events.ScheduleEvent(EVENT_SHIELD, 14s, 19s);
- events.ScheduleEvent(EVENT_CHAIN_LIGHTNING, 6s, 12s);
- events.ScheduleEvent(EVENT_STATIC_CHARGE, 24s);
- events.ScheduleEvent(EVENT_LIGHTNING_RING, 25s, 31s);
- events.ScheduleEvent(EVENT_SUMMON, 20s);
- events.ScheduleEvent(EVENT_SUMMON, 21s + 500ms);
- events.ScheduleEvent(EVENT_SUMMON_SPEACH, 20s);
+ if (GameObject* doors = me->GetMap()->GetGameObject(instance->GetGuidData(GO_SJONNIR_DOOR)))
+ doors->SetGoState(GO_STATE_READY);
- if (pInstance)
- {
- pInstance->SetData(BOSS_SJONNIR, IN_PROGRESS);
-
- if (GameObject* doors = me->GetMap()->GetGameObject(pInstance->GetGuidData(GO_SJONNIR_DOOR)))
- doors->SetGoState(GO_STATE_READY);
-
- if (pInstance->GetData(BOSS_TRIBUNAL_OF_AGES) == DONE)
- if (Creature* brann = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(NPC_BRANN)))
- brann->AI()->DoAction(ACTION_START_SJONNIR_FIGHT);
- }
+ if (instance->GetData(BOSS_TRIBUNAL_OF_AGES) == DONE)
+ if (Creature* brann = ObjectAccessor::GetCreature(*me, instance->GetGuidData(NPC_BRANN)))
+ brann->AI()->DoAction(ACTION_START_SJONNIR_FIGHT);
}
void DoAction(int32 param) override
@@ -183,144 +230,20 @@ public:
if (param == ACTION_SLUG_KILLED)
{
SlugeCount++;
- if (SlugeCount >= 5 && pInstance)
- pInstance->SetData(DATA_SJONNIR_ACHIEVEMENT, true);
- }
- }
-
- void UpdateAI(uint32 diff) override
- {
- if (!UpdateVictim())
- return;
-
- events.Update(diff);
- if (me->HasUnitState(UNIT_STATE_CASTING))
- return;
-
- switch (events.ExecuteEvent())
- {
- case EVENT_CHECK_HEALTH:
- {
- if (SummonPhase == PHASE_SUMMON_UNFRIENDLY_DWARFES && HealthBelowPct(50))
- {
- SummonPhase = PHASE_SUMMON_OOZE;
- events.CancelEvent(EVENT_SUMMON);
- events.ScheduleEvent(EVENT_SUMMON, 0ms);
- events.ScheduleEvent(EVENT_SUMMON, 1500ms);
-
- if (pInstance)
- if (Creature* brann = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(NPC_BRANN)))
- {
- brann->AI()->Talk(SAY_BRANN_SPAWN_OOZE);
- }
- }
-
- if (HealthBelowPct(20))
- {
- if (Creature* brann = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(NPC_BRANN)))
- {
- brann->AI()->Talk(SAY_BRANN_SPAWN_EARTHEN);
- }
- SummonPhase = PHASE_SUMMON_FRIENDLY_DWARFES;
- me->CastSpell(me, SPELL_FRENZY, false);
-
- events.CancelEvent(EVENT_SUMMON);
- events.ScheduleEvent(EVENT_SUMMON, 0ms);
- break;
- }
-
- events.Repeat(1s);
- break;
- }
- case EVENT_SHIELD:
- {
- me->CastSpell(me, SPELL_LIGHTNING_SHIELD, false);
- events.Repeat(14s, 19s);
- break;
- }
- case EVENT_CHAIN_LIGHTNING:
- {
- if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 50.0f, true, 0))
- me->CastSpell(target, SPELL_CHAIN_LIGHTNING, false);
-
- events.Repeat(6s, 12s);
- break;
- }
- case EVENT_STATIC_CHARGE:
- {
- if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 50.0f, true, 0))
- me->CastSpell(target, SPELL_STATIC_CHARGE, false);
-
- events.Repeat(20s);
- break;
- }
- case EVENT_LIGHTNING_RING:
- {
- me->CastSpell(me, SPELL_LIGHTNING_RING, false);
- events.Repeat(25s, 31s);
- events.DelayEvents(10s); // Channel duration
- break;
- }
- case EVENT_SUMMON_SPEACH:
- {
- if (Creature* brann = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(NPC_BRANN)))
- {
- brann->AI()->Talk(SAY_BRANN_SPAWN_TROGG);
- }
-
- break;
- }
- case EVENT_SUMMON:
- {
- switch (SummonPhase)
- {
- case PHASE_SUMMON_UNFRIENDLY_DWARFES:
- {
- SummonDwarfes(false);
- events.Repeat(20s);
- break;
- }
- case PHASE_SUMMON_OOZE:
- {
- for (uint8 i = POS_GEN_RIGHT; i <= POS_GEN_LEFT; i++)
- {
- if (Creature* ooze = me->SummonCreature(NPC_OOZE, RoomPosition[i].GetPositionX(), RoomPosition[i].GetPositionY(), RoomPosition[i].GetPositionZ(), 0, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 20000))
- {
- ActivatePipe(i);
- ooze->GetMotionMaster()->MovePoint(0, RoomPosition[POS_ROOM_CENTER].GetPositionX(), RoomPosition[POS_ROOM_CENTER].GetPositionY(), RoomPosition[POS_ROOM_CENTER].GetPositionZ());
- summons.Summon(ooze);
- }
- }
- events.Repeat(10s);
- break;
- }
- case PHASE_SUMMON_FRIENDLY_DWARFES:
- {
- SummonDwarfes(true);
- break;
- }
- }
- break;
- }
+ if (SlugeCount >= 5)
+ instance->SetData(DATA_SJONNIR_ACHIEVEMENT, true);
}
-
- DoMeleeAttackIfReady();
}
void JustDied(Unit* /*killer*/) override
{
Talk(SAY_DEATH);
+ _JustDied();
+ if (GameObject* sd = me->GetMap()->GetGameObject(instance->GetGuidData(GO_SJONNIR_DOOR)))
+ sd->SetGoState(GO_STATE_ACTIVE);
- summons.DespawnAll();
- if (pInstance)
- {
- pInstance->SetData(BOSS_SJONNIR, DONE);
- if (GameObject* sd = me->GetMap()->GetGameObject(pInstance->GetGuidData(GO_SJONNIR_DOOR)))
- sd->SetGoState(GO_STATE_ACTIVE);
-
- if (Creature* brann = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(NPC_BRANN)))
- brann->AI()->DoAction(ACTION_SJONNIR_DEAD);
- }
+ if (Creature* brann = ObjectAccessor::GetCreature(*me, instance->GetGuidData(NPC_BRANN)))
+ brann->AI()->DoAction(ACTION_SJONNIR_DEAD);
}
void KilledUnit(Unit* /*victim*/) override
@@ -333,42 +256,8 @@ public:
void ActivatePipe(uint8 side)
{
- if (pInstance)
- if (GameObject* pipe = me->GetMap()->GetGameObject(pInstance->GetGuidData(side == POS_GEN_RIGHT ? GO_RIGHT_PIPE : GO_LEFT_PIPE)))
- pipe->SendCustomAnim(0);
- }
-
- void SummonDwarfes(bool friendly)
- {
- if (friendly)
- {
- for (int i = 0; i < 3; i++)
- {
- uint8 Pos = urand(POS_GEN_RIGHT, POS_GEN_LEFT);
- if (Creature* dwarf = me->SummonCreature(NPC_DWARFES_FRIENDLY, RoomPosition[Pos].GetPositionX(), RoomPosition[Pos].GetPositionY(), RoomPosition[Pos].GetPositionZ(), 0, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 20000))
- {
- if (Player* plr = SelectTargetFromPlayerList(100.0f))
- dwarf->SetFaction(plr->GetFaction());
-
- ActivatePipe(Pos);
- dwarf->AI()->AttackStart(me);
- summons.Summon(dwarf);
- }
- }
- }
- else
- {
- for (int i = 0; i < 2; i++)
- {
- uint8 Pos = urand(POS_GEN_RIGHT, POS_GEN_LEFT);
- if (Creature* dwarf = me->SummonCreature(urand(0, 1) ? NPC_FORGED_IRON_TROGG : NPC_FORGED_IRON_DWARF, RoomPosition[Pos].GetPositionX(), RoomPosition[Pos].GetPositionY(), RoomPosition[Pos].GetPositionZ(), 0, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 20000))
- {
- ActivatePipe(Pos);
- dwarf->SetInCombatWithZone();
- summons.Summon(dwarf);
- }
- }
- }
+ if (GameObject* pipe = me->GetMap()->GetGameObject(instance->GetGuidData(side == POS_GEN_RIGHT ? GO_RIGHT_PIPE : GO_LEFT_PIPE)))
+ pipe->SendCustomAnim(0);
}
};
};
@@ -423,8 +312,8 @@ public:
}
void JustDied(Unit* /*killer*/) override
{
- if (InstanceScript* pInstance = me->GetInstanceScript())
- if (Creature* sjonnir = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(NPC_SJONNIR)))
+ if (InstanceScript* instance = me->GetInstanceScript())
+ if (Creature* sjonnir = instance->GetCreature(BOSS_SJONNIR))
sjonnir->AI()->DoAction(ACTION_SLUG_KILLED);
}
void UpdateAI(uint32 diff) override
diff --git a/src/server/scripts/Northrend/Ulduar/HallsOfStone/brann_bronzebeard.cpp b/src/server/scripts/Northrend/Ulduar/HallsOfStone/brann_bronzebeard.cpp
index 31b4a18b37..e24adc5ac1 100644
--- a/src/server/scripts/Northrend/Ulduar/HallsOfStone/brann_bronzebeard.cpp
+++ b/src/server/scripts/Northrend/Ulduar/HallsOfStone/brann_bronzebeard.cpp
@@ -53,17 +53,8 @@ enum Misc
SPELL_DARK_MATTER = 51012,
SPELL_SEARING_GAZE = 51136,
- // DARK RUNE PROTECTOR
- SPELL_DRP_CHARGE = 22120,
- SPELL_DRP_CLEAVE = 42724,
-
- // DARK RUNE STORMCALLER
- SPELL_DRS_LIGHTING_BOLT = 12167,
- SPELL_DRS_SHADOW_WORD_PAIN = 15654,
-
- // IRON GOLEM CUSTODIAN
- SPELL_IGC_CRUSH_ARMOR = 33661,
- SPELL_IGC_GROUND_SMASH = 12734,
+ // Serverside
+ SPELL_TRIBUNAL_CREDIT_MARKER = 59046,
// QUESTS
QUEST_HALLS_OF_STONE = 13207,
@@ -85,29 +76,17 @@ enum events
EVENT_DARK_MATTER_START = 11,
EVENT_DARK_MATTER_END = 12,
- // DARK RUNE PROTECTOR
- EVENT_DRP_CHARGE = 13,
- EVENT_DRP_CLEAVE = 14,
-
- // DARK RUNE STORMCALLER
- EVENT_DRS_LIGHTNING_BOLD = 15,
- EVENT_DRS_SHADOW_WORD_PAIN = 16,
-
- // IRON GOLEM CUSTODIAN
- EVENT_IGC_CRUSH = 17,
- EVENT_IGC_GROUND_SMASH = 18,
-
- EVENT_TRIBUNAL_END = 19,
- EVENT_BREEN_WAITING = 20,
- EVENT_TALK_FACE_CHANGE = 21,
- EVENT_SKY_ROOM_FLOOR_CHANGE = 22,
+ EVENT_TRIBUNAL_END = 13,
+ EVENT_BREEN_WAITING = 14,
+ EVENT_TALK_FACE_CHANGE = 15,
+ EVENT_SKY_ROOM_FLOOR_CHANGE = 16,
//BRANN AND SJONNIR
- EVENT_GO_TO_SJONNIR = 23,
- EVENT_DOOR_OPEN = 24,
- EVENT_RESUME_ESCORT = 25,
- EVENT_SJONNIR_END_BRANN_YELL = 26,
- EVENT_SJONNIR_END_BRANN_LAST_YELL = 27,
+ EVENT_GO_TO_SJONNIR = 17,
+ EVENT_DOOR_OPEN = 18,
+ EVENT_RESUME_ESCORT = 19,
+ EVENT_SJONNIR_END_BRANN_YELL = 20,
+ EVENT_SJONNIR_END_BRANN_LAST_YELL = 21,
};
struct Yells
@@ -413,6 +392,7 @@ public:
DoCast(me, 58506, false);
me->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_STATE_READY_UNARMED);
me->SendMovementFlagUpdate();
+ me->SetImmuneToAll(true);
break;
case ACTION_START_SJONNIR_FIGHT:
SetEscortPaused(false);
@@ -548,14 +528,9 @@ public:
if (!plr)
return; //no target
- float speed = 10.0f;
- float tooFarAwaySpeed = me->GetDistance(plr->GetPositionX(), plr->GetPositionY(), plr->GetPositionZ()) / (5000.0f * 0.001f);
- if (speed < tooFarAwaySpeed)
- speed = tooFarAwaySpeed;
-
- darkMatterTarget->GetMotionMaster()->MovePoint(0, plr->GetPositionX(), plr->GetPositionY(), plr->GetPositionZ(), FORCED_MOVEMENT_NONE, speed);
+ darkMatterTarget->GetMotionMaster()->MovePoint(0, plr->GetPositionX(), plr->GetPositionY(), plr->GetPositionZ());
- if (darkMatterTarget->GetDistance(plr) < 15.0f)
+ if (darkMatterTarget->GetDistance(plr) < 5.0f)
{
events.RescheduleEvent(EVENT_DARK_MATTER_END, 3s);
}
@@ -665,10 +640,9 @@ public:
{
pInstance->SetData(BOSS_TRIBUNAL_OF_AGES, DONE);
pInstance->SetData(BRANN_BRONZEBEARD, 3);
- me->CastSpell(me, 59046, true); // credit
+ me->CastSpell(me, SPELL_TRIBUNAL_CREDIT_MARKER, true); // credit
}
- me->ReplaceAllNpcFlags(UNIT_NPC_FLAG_GOSSIP | UNIT_NPC_FLAG_QUESTGIVER);
me->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_STATE_STAND);
me->SendMovementFlagUpdate();
@@ -779,7 +753,7 @@ public:
if (cr)
{
cr->AI()->AttackStart(me);
- cr->AddThreat(me, 100.0f);
+ cr->AddThreat(me, 0.0f);
cr->SetInCombatWithZone();
}
}
@@ -875,6 +849,7 @@ void brann_bronzebeard::brann_bronzebeardAI::WaypointReached(uint32 id)
//Tribunal end, stand in the middle of the sky room
case 17:
SetEscortPaused(true);
+ me->ReplaceAllNpcFlags(UNIT_NPC_FLAG_GOSSIP | UNIT_NPC_FLAG_QUESTGIVER);
me->SetOrientation(3.91672f);
me->SendMovementFlagUpdate();
break;
@@ -886,8 +861,6 @@ void brann_bronzebeard::brann_bronzebeardAI::WaypointReached(uint32 id)
{
pInstance->SetData(BRANN_BRONZEBEARD, 4);
me->ReplaceAllNpcFlags(UNIT_NPC_FLAG_GOSSIP | UNIT_NPC_FLAG_QUESTGIVER);
- if (Creature* cr = ObjectAccessor::GetCreature(*me, pInstance->GetGuidData(NPC_SJONNIR)))
- cr->RemoveUnitFlag(UNIT_FLAG_NOT_SELECTABLE);
me->SetOrientation(3.132660f);
DoCast(me, 58506, false);
me->SendMovementFlagUpdate();
@@ -938,173 +911,6 @@ void brann_bronzebeard::brann_bronzebeardAI::WaypointReached(uint32 id)
}
}
-class dark_rune_protectors : public CreatureScript
-{
-public:
- dark_rune_protectors() : CreatureScript("dark_rune_protectors") { }
-
- CreatureAI* GetAI(Creature* creature) const override
- {
- return new dark_rune_protectorsAI (creature);
- }
-
- struct dark_rune_protectorsAI : public ScriptedAI
- {
- dark_rune_protectorsAI(Creature* c) : ScriptedAI(c) { }
-
- EventMap events;
- void Reset() override
- {
- events.Reset();
- }
-
- void JustEngagedWith(Unit*) override
- {
- events.ScheduleEvent(EVENT_DRP_CLEAVE, 7s);
- }
-
- void UpdateAI(uint32 diff) override
- {
- if (!UpdateVictim())
- return;
-
- events.Update(diff);
- if (me->HasUnitState(UNIT_STATE_CASTING))
- return;
-
- switch (events.ExecuteEvent())
- {
- case EVENT_DRP_CLEAVE:
- {
- me->CastSpell(me->GetVictim(), SPELL_DRP_CLEAVE, false);
- events.Repeat(7s);
- break;
- }
- }
-
- if (Unit* victim = me->GetVictim())
- {
- if (!me->IsWithinMeleeRange(victim) && !me->HasUnitState(UNIT_STATE_CHARGING))
- {
- me->CastSpell(victim, SPELL_DRP_CHARGE, false);
- }
- }
-
- DoMeleeAttackIfReady();
- }
- };
-};
-
-class dark_rune_stormcaller : public CreatureScript
-{
-public:
- dark_rune_stormcaller() : CreatureScript("dark_rune_stormcaller") { }
-
- CreatureAI* GetAI(Creature* creature) const override
- {
- return new dark_rune_stormcallerAI (creature);
- }
-
- struct dark_rune_stormcallerAI : public ScriptedAI
- {
- dark_rune_stormcallerAI(Creature* c) : ScriptedAI(c) { }
-
- EventMap events;
- void Reset() override
- {
- events.Reset();
- }
-
- void JustEngagedWith(Unit*) override
- {
- events.ScheduleEvent(EVENT_DRS_LIGHTNING_BOLD, 5s);
- events.ScheduleEvent(EVENT_DRS_SHADOW_WORD_PAIN, 12s);
- }
-
- void UpdateAI(uint32 diff) override
- {
- if (!UpdateVictim())
- return;
-
- events.Update(diff);
- if (me->HasUnitState(UNIT_STATE_CASTING))
- return;
-
- switch (events.ExecuteEvent())
- {
- case EVENT_DRS_LIGHTNING_BOLD:
- {
- me->CastSpell(me->GetVictim(), SPELL_DRS_LIGHTING_BOLT, false);
- events.Repeat(5s);
- break;
- }
- case EVENT_DRS_SHADOW_WORD_PAIN:
- {
- me->CastSpell(me->GetVictim(), SPELL_DRS_SHADOW_WORD_PAIN, false);
- events.Repeat(12s);
- break;
- }
- }
-
- DoMeleeAttackIfReady();
- }
- };
-};
-
-class iron_golem_custodian : public CreatureScript
-{
-public:
- iron_golem_custodian() : CreatureScript("iron_golem_custodian") { }
-
- CreatureAI* GetAI(Creature* creature) const override
- {
- return new iron_golem_custodianAI (creature);
- }
-
- struct iron_golem_custodianAI : public ScriptedAI
- {
- iron_golem_custodianAI(Creature* c) : ScriptedAI(c) { }
- EventMap events;
- void Reset() override
- {
- events.Reset();
- }
-
- void JustEngagedWith(Unit*) override
- {
- events.ScheduleEvent(EVENT_IGC_CRUSH, 6s);
- events.ScheduleEvent(EVENT_IGC_GROUND_SMASH, 20s);
- }
- void UpdateAI(uint32 diff) override
- {
- if (!UpdateVictim())
- return;
-
- events.Update(diff);
- if (me->HasUnitState(UNIT_STATE_CASTING))
- return;
-
- switch (events.ExecuteEvent())
- {
- case EVENT_IGC_CRUSH:
- {
- me->CastSpell(me->GetVictim(), SPELL_IGC_CRUSH_ARMOR, false);
- events.Repeat(6s);
- break;
- }
- case EVENT_IGC_GROUND_SMASH:
- {
- me->CastSpell(me->GetVictim(), SPELL_IGC_GROUND_SMASH, false);
- events.Repeat(20s, 40s);
- break;
- }
- }
-
- DoMeleeAttackIfReady();
- }
- };
-};
-
class spell_hos_dark_matter : public AuraScript
{
PrepareAuraScript(spell_hos_dark_matter);
@@ -1145,9 +951,6 @@ class spell_hos_dark_matter_size : public SpellScript
void AddSC_brann_bronzebeard()
{
new brann_bronzebeard();
- new dark_rune_protectors();
- new dark_rune_stormcaller();
- new iron_golem_custodian();
RegisterSpellScript(spell_hos_dark_matter);
RegisterSpellScript(spell_hos_dark_matter_size);
}
diff --git a/src/server/scripts/Northrend/Ulduar/HallsOfStone/halls_of_stone.h b/src/server/scripts/Northrend/Ulduar/HallsOfStone/halls_of_stone.h
index 9039c0eba9..d45d3009bc 100644
--- a/src/server/scripts/Northrend/Ulduar/HallsOfStone/halls_of_stone.h
+++ b/src/server/scripts/Northrend/Ulduar/HallsOfStone/halls_of_stone.h
@@ -78,6 +78,7 @@ enum npcs
NPC_ABEDNEUM = 30899,
NPC_SJONNIR = 27978,
NPC_BRANN = 28070,
+ NPC_IRON_SLUDGE = 28165,
ACTION_START_ESCORT_EVENT = 0,
ACTION_START_TRIBUNAL = 1,
diff --git a/src/server/scripts/Northrend/Ulduar/HallsOfStone/instance_halls_of_stone.cpp b/src/server/scripts/Northrend/Ulduar/HallsOfStone/instance_halls_of_stone.cpp
index 521c1ba610..c6c8b65ddd 100644
--- a/src/server/scripts/Northrend/Ulduar/HallsOfStone/instance_halls_of_stone.cpp
+++ b/src/server/scripts/Northrend/Ulduar/HallsOfStone/instance_halls_of_stone.cpp
@@ -20,6 +20,18 @@
#include "ScriptedCreature.h"
#include "halls_of_stone.h"
+ObjectData const summonData[] =
+{
+ { NPC_IRON_SLUDGE, BOSS_SJONNIR },
+ { 0, 0 }
+};
+
+ObjectData const creatureData[] =
+{
+ { NPC_SJONNIR, BOSS_SJONNIR },
+ { 0, 0 }
+};
+
class instance_halls_of_stone : public InstanceMapScript
{
public:
@@ -57,6 +69,9 @@ public:
void Initialize() override
{
SetHeaders(DataHeader);
+ SetBossNumber(MAX_ENCOUNTER);
+ LoadObjectData(creatureData, nullptr);
+ LoadSummonData(summonData);
memset(&Encounter, 0, sizeof(Encounter));
brannAchievement = false;
@@ -121,13 +136,12 @@ public:
{
switch (creature->GetEntry())
{
- case NPC_SJONNIR:
- SjonnirGUID = creature->GetGUID();
- break;
case NPC_BRANN:
BrannGUID = creature->GetGUID();
break;
}
+
+ InstanceScript::OnCreatureCreate(creature);
}
ObjectGuid GetGuidData(uint32 id) const override
diff --git a/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/utgarde_keep.cpp b/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/utgarde_keep.cpp
index 09ee369913..1a7cb30300 100644
--- a/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/utgarde_keep.cpp
+++ b/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/utgarde_keep.cpp
@@ -82,30 +82,33 @@ struct npc_dragonflayer_forge_master : public ScriptedAI
enum EnslavedProtoDrake
{
- TYPE_PROTODRAKE_AT = 28,
- DATA_PROTODRAKE_MOVE = 6,
-
- PATH_PROTODRAKE = 125946,
+ SPELL_REND = 43931,
+ SPELL_FLAME_BREATH = 50653,
+ SPELL_KNOCK_AWAY = 49722,
EVENT_REND = 1,
EVENT_FLAME_BREATH = 2,
EVENT_KNOCKAWAY = 3,
+ // Special
+ EVENT_PRE_LAND = 4,
+ EVENT_LAND = 5,
- SPELL_REND = 43931,
- SPELL_FLAME_BREATH = 50653,
- SPELL_KNOCK_AWAY = 49722,
-
- POINT_LAST = 6,
+ // Special
+ TYPE_PROTODRAKE_AT = 28,
+ DATA_PROTODRAKE_MOVE = 6,
+ POINT_TAKE_OFF = 1,
+ POINT_PRE_LAND = 2,
+ POINT_LAND = 3,
};
-const Position protodrakeCheckPos = {206.24f, -190.28f, 200.11f, 0.f};
+const Position protodrakeCheckPos{206.24f, -190.28f, 200.11f, 0.f};
+const Position protodrakeTakeOffPos{209.1206f, -187.86578f, 215.00346f};
+const Position protodrakePreLandPos{230.80234f, -164.99632f, 196.74878f};
+const Position protodrakeLandPos{241.2079f, -163.06265f, 193.47125f};
struct npc_enslaved_proto_drake : public ScriptedAI
{
- npc_enslaved_proto_drake(Creature* creature) : ScriptedAI(creature)
- {
- _setData = false;
- }
+ explicit npc_enslaved_proto_drake(Creature* creature) : ScriptedAI(creature) { }
void Reset() override
{
@@ -113,22 +116,35 @@ struct npc_enslaved_proto_drake : public ScriptedAI
_events.ScheduleEvent(EVENT_REND, 2s, 3s);
_events.ScheduleEvent(EVENT_FLAME_BREATH, 5500ms, 7s);
_events.ScheduleEvent(EVENT_KNOCKAWAY, 3500ms, 6s);
+ scheduler.CancelAll();
}
void MovementInform(uint32 type, uint32 id) override
{
- if (type == WAYPOINT_MOTION_TYPE && id == POINT_LAST)
+ if (type == EFFECT_MOTION_TYPE && id == POINT_TAKE_OFF)
{
+ ScheduleUniqueTimedEvent(500ms, [&]
+ {
+ me->GetMotionMaster()->MovePoint(POINT_PRE_LAND, protodrakePreLandPos);
+ }, EVENT_PRE_LAND);
+ }
+
+ if (type == POINT_MOTION_TYPE && id == POINT_PRE_LAND)
+ {
+ ScheduleUniqueTimedEvent(0s, [&]
+ {
+ me->GetMotionMaster()->MovePoint(POINT_LAND, protodrakeLandPos);
+ }, EVENT_LAND);
+ }
+
+ if (type == POINT_MOTION_TYPE && id == POINT_LAND)
+ {
+ me->SetFacingTo(0.25f);
me->SetHomePosition(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ(), 0.25f);
if (Vehicle* v = me->GetVehicleKit())
if (Unit* p = v->GetPassenger(0))
if (Creature* rider = p->ToCreature())
rider->SetHomePosition(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ(), 0.25f);
-
- me->SetCanFly(false);
- me->SetDisableGravity(false);
- me->SetFacingTo(0.25f);
- me->SetImmuneToAll(false);
}
}
@@ -137,14 +153,14 @@ struct npc_enslaved_proto_drake : public ScriptedAI
if (type == TYPE_PROTODRAKE_AT && data == DATA_PROTODRAKE_MOVE && !_setData && me->IsAlive() && me->GetDistance(protodrakeCheckPos) < 10.0f)
{
_setData = true;
- me->SetCanFly(true);
- me->SetDisableGravity(true);
- me->GetMotionMaster()->MoveWaypoint(PATH_PROTODRAKE, false);
+ me->GetMotionMaster()->MoveTakeoff(POINT_TAKE_OFF, protodrakeTakeOffPos, 8.0f);
}
}
void UpdateAI(uint32 diff) override
{
+ scheduler.Update(diff);
+
if (!UpdateVictim())
return;
@@ -178,7 +194,7 @@ struct npc_enslaved_proto_drake : public ScriptedAI
}
private:
- bool _setData;
+ bool _setData{false};
EventMap _events;
};
diff --git a/src/server/scripts/Northrend/VioletHold/violet_hold.cpp b/src/server/scripts/Northrend/VioletHold/violet_hold.cpp
index 29398075ad..4d1bc0924f 100644
--- a/src/server/scripts/Northrend/VioletHold/violet_hold.cpp
+++ b/src/server/scripts/Northrend/VioletHold/violet_hold.cpp
@@ -182,7 +182,7 @@ public:
case EVENT_SUMMON_KEEPER_OR_GUARDIAN:
bKorG = true;
spawned = true;
- if (Creature* c = DoSummon(RAND(NPC_PORTAL_GUARDIAN, NPC_PORTAL_KEEPER), me, 2.0f, 0, TEMPSUMMON_DEAD_DESPAWN))
+ if (Creature* c = DoSummon(RAND(NPC_PORTAL_GUARDIAN, NPC_PORTAL_KEEPER_1, NPC_PORTAL_KEEPER_2), me, 2.0f, 0, TEMPSUMMON_DEAD_DESPAWN))
me->CastSpell(c, SPELL_PORTAL_CHANNEL, false);
events.RescheduleEvent(EVENT_SUMMON_KEEPER_TRASH, 20s);
break;
@@ -220,11 +220,12 @@ public:
if (pInstance)
for (SummonList::iterator itr = listOfMobs.begin(); itr != listOfMobs.end(); ++itr)
if (Creature* c = pInstance->instance->GetCreature(*itr))
- if (c->IsAlive() && (c->GetEntry() == NPC_PORTAL_GUARDIAN || c->GetEntry() == NPC_PORTAL_KEEPER))
+ if (c->IsAlive() && c->EntryEquals(NPC_PORTAL_GUARDIAN, NPC_PORTAL_KEEPER_1, NPC_PORTAL_KEEPER_2))
{
me->CastSpell(c, SPELL_PORTAL_CHANNEL, false);
return;
}
+
Unit::Kill(me, me, false);
}
}
diff --git a/src/server/scripts/Northrend/VioletHold/violet_hold.h b/src/server/scripts/Northrend/VioletHold/violet_hold.h
index 229319087a..a21142afd7 100644
--- a/src/server/scripts/Northrend/VioletHold/violet_hold.h
+++ b/src/server/scripts/Northrend/VioletHold/violet_hold.h
@@ -47,7 +47,8 @@ enum Creatures
NPC_CYANIGOSA = 31134,
NPC_PORTAL_GUARDIAN = 30660,
- NPC_PORTAL_KEEPER = 30695,
+ NPC_PORTAL_KEEPER_1 = 30695,
+ NPC_PORTAL_KEEPER_2 = 30893,
NPC_AZURE_INVADER_1 = 30661,
NPC_AZURE_INVADER_2 = 30961,
NPC_AZURE_SPELLBREAKER_1 = 30662,
diff --git a/src/server/scripts/Northrend/zone_borean_tundra.cpp b/src/server/scripts/Northrend/zone_borean_tundra.cpp
index c4e412fb5c..ec6a55f172 100644
--- a/src/server/scripts/Northrend/zone_borean_tundra.cpp
+++ b/src/server/scripts/Northrend/zone_borean_tundra.cpp
@@ -562,201 +562,13 @@ private:
Position _fezzix;
};
-/*######
-## Quest 11590: Abduction
-######*/
-
-// NPC 25316: Beryl Sorcerer
-enum BerylSorcerer
+// Spell 45625: - Arcane Chains: Character Force Cast
+enum ArcaneChains
{
- EVENT_FROSTBOLT = 1,
- EVENT_ARCANE_CHAINS = 2,
- NPC_LIBRARIAN_DONATHAN = 25262,
- NPC_CAPTURED_BERLY_SORCERER = 25474,
- SPELL_FROSTBOLT = 9672,
- SPELL_ARCANE_CHAINS = 45611,
SPELL_ARCANE_CHAINS_CHARACTER_FORCE_CAST = 45625,
- SPELL_ARCANE_CHAINS_SUMMON_CHAINED_MAGE_HUNTER = 45626,
- SPELL_COSMETIC_ENSLAVE_CHAINS_SELF = 45631,
- SPELL_ARCANE_CHAINS_CHANNEL_II = 45735
-};
-
-class npc_beryl_sorcerer : public CreatureScript
-{
-public:
- npc_beryl_sorcerer() : CreatureScript("npc_beryl_sorcerer") { }
-
-struct npc_beryl_sorcererAI : public CreatureAI
- {
- npc_beryl_sorcererAI(Creature* creature) : CreatureAI(creature)
- {
- Initialize();
- }
-
- void Initialize()
- {
- _playerGUID.Clear();
- _chainsCast = false;
- }
-
- void Reset() override
- {
- me->SetReactState(REACT_AGGRESSIVE);
- Initialize();
- }
-
- void JustEngagedWith(Unit* who) override
- {
- if (me->IsValidAttackTarget(who))
- {
- AttackStart(who);
- }
-
- _events.ScheduleEvent(EVENT_FROSTBOLT, 3s, 4s);
- }
-
- void SpellHit(Unit* unit, SpellInfo const* spell) override
- {
- if (spell->Id == SPELL_ARCANE_CHAINS && !_chainsCast)
- {
- if (Player* player = unit->ToPlayer())
- {
- _playerGUID = player->GetGUID();
- _chainsCast = true;
- _events.ScheduleEvent(EVENT_ARCANE_CHAINS, 4s);
- }
- }
- }
-
- void UpdateAI(uint32 diff) override
- {
- if (!UpdateVictim())
- {
- return;
- }
-
- _events.Update(diff);
-
- if (uint32 eventId = _events.ExecuteEvent())
- {
- switch (eventId)
- {
- case EVENT_FROSTBOLT:
- DoCastVictim(SPELL_FROSTBOLT);
- _events.ScheduleEvent(EVENT_FROSTBOLT, 3s, 4s);
- break;
- case EVENT_ARCANE_CHAINS:
- if (me->HasAura(SPELL_ARCANE_CHAINS))
- {
- if (Player* player = ObjectAccessor::GetPlayer(*me, _playerGUID))
- {
- me->CastSpell(player, SPELL_ARCANE_CHAINS_CHARACTER_FORCE_CAST, TriggerCastFlags(TRIGGERED_FULL_MASK & ~TRIGGERED_IGNORE_AURA_INTERRUPT_FLAGS & ~TRIGGERED_IGNORE_CAST_ITEM));
- player->KilledMonsterCredit(NPC_CAPTURED_BERLY_SORCERER);
- me->DisappearAndDie();
- }
- }
- break;
- }
- }
- DoMeleeAttackIfReady();
- }
-
- private:
- EventMap _events;
- ObjectGuid _playerGUID;
- bool _chainsCast;
- };
-
- CreatureAI* GetAI(Creature* creature) const override
- {
- return new npc_beryl_sorcererAI(creature);
- }
-};
-
-// NPC 25474: Captured Beryl Sorcerer
-enum CapturedBerylSorcerer
-{
- EVENT_ADD_ARCANE_CHAINS = 1,
- EVENT_FOLLOW_PLAYER = 2
+ SPELL_ARCANE_CHAINS_SUMMON_CHAINED_MAGE_HUNTER = 45626
};
-class npc_captured_beryl_sorcerer : public CreatureScript
-{
-public:
- npc_captured_beryl_sorcerer() : CreatureScript("npc_captured_beryl_sorcerer") {}
-
- struct npc_captured_beryl_sorcererAI : public FollowerAI
- {
- npc_captured_beryl_sorcererAI(Creature* creature) : FollowerAI(creature)
- {
- Initialize();
- }
-
- void Initialize()
- {
- me->SetReactState(REACT_PASSIVE);
- me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE);
- _events.ScheduleEvent(EVENT_ADD_ARCANE_CHAINS, 0ms);
- }
-
- void Reset() override
- {
- Initialize();
- }
-
- void UpdateAI(uint32 diff) override
- {
- _events.Update(diff);
-
- if (uint32 eventId = _events.ExecuteEvent())
- {
- switch (eventId)
- {
- case EVENT_ADD_ARCANE_CHAINS:
- if (TempSummon* tempSummon = me->ToTempSummon())
- {
- if (Unit* summoner = tempSummon->GetSummonerUnit())
- {
- summoner->CastSpell(summoner, SPELL_ARCANE_CHAINS_CHANNEL_II, TriggerCastFlags(TRIGGERED_FULL_MASK & ~TRIGGERED_IGNORE_AURA_INTERRUPT_FLAGS & ~TRIGGERED_IGNORE_CAST_ITEM & ~TRIGGERED_IGNORE_POWER_AND_REAGENT_COST & ~TRIGGERED_IGNORE_GCD));
- _events.ScheduleEvent(EVENT_FOLLOW_PLAYER, 1s);
- }
- }
- break;
-
- case EVENT_FOLLOW_PLAYER:
- if (TempSummon* tempSummon = me->ToTempSummon())
- {
- if (Player* summoner = tempSummon->GetSummonerUnit()->ToPlayer())
- {
- StartFollow(summoner);
- }
- }
- break;
- }
- }
- }
-
- void MoveInLineOfSight(Unit* who) override
- {
- FollowerAI::MoveInLineOfSight(who);
-
- if (who->GetEntry() == NPC_LIBRARIAN_DONATHAN && me->IsWithinDistInMap(who, INTERACTION_DISTANCE))
- {
- SetFollowComplete();
- me->DespawnOrUnsummon();
- }
- }
- private:
- EventMap _events;
- };
-
- CreatureAI* GetAI(Creature* creature) const override
- {
- return new npc_captured_beryl_sorcererAI(creature);
- }
-};
-
-// Spell 45625: - Arcane Chains: Character Force Cast
class spell_arcane_chains_character_force_cast : public SpellScript
{
PrepareSpellScript(spell_arcane_chains_character_force_cast);
@@ -782,7 +594,8 @@ class spell_arcane_chains_character_force_cast : public SpellScript
######*/
enum ImprisionedBerylSorcerer
{
- SPELL_NEURAL_NEEDLE = 45634,
+ SPELL_NEURAL_NEEDLE = 45634,
+ SPELL_COSMETIC_ENSLAVE_CHAINS_SELF = 45631,
NPC_IMPRISONED_BERYL_SORCERER = 25478,
@@ -2222,8 +2035,6 @@ void AddSC_borean_tundra()
new npc_iruk();
new npc_nerubar_victim();
new npc_lurgglbr();
- new npc_beryl_sorcerer();
- new npc_captured_beryl_sorcerer();
RegisterSpellScript(spell_arcane_chains_character_force_cast);
new npc_imprisoned_beryl_sorcerer();
new npc_mootoo_the_younger();
diff --git a/src/server/scripts/Northrend/zone_crystalsong_forest.cpp b/src/server/scripts/Northrend/zone_crystalsong_forest.cpp
index bdf394cad9..645d3c0224 100644
--- a/src/server/scripts/Northrend/zone_crystalsong_forest.cpp
+++ b/src/server/scripts/Northrend/zone_crystalsong_forest.cpp
@@ -15,52 +15,100 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include "CombatAI.h"
#include "CreatureScript.h"
-#include "PassiveAI.h"
#include "Player.h"
#include "ScriptedCreature.h"
-#include "SmartScriptMgr.h"
+#include "SpellAuras.h"
+#include "SpellScript.h"
#include "Transport.h"
#include "Vehicle.h"
enum ePreparationsForWar
{
- NPC_HAMMERHEAD = 30585,
- NPC_CLOUDBUSTER = 30470,
- TRANSPORT_ORGRIMS_HAMMER = 192241,
- TRANSPORT_THE_SKYBREAKER = 192242
+ NPC_CLOUDBUSTER = 30470,
+ NPC_HAMMERHEAD = 30585,
+ TRANSPORT_ORGRIMS_HAMMER = 192241,
+ TRANSPORT_THE_SKYBREAKER = 192242,
+ SEAT_PLAYER = 0,
+ SPELL_FLIGHT = 48602,
+ SPELL_TO_ICECROWN_PLAYER_AURA_DISMOUNT_A = 56904,
+ SPELL_TO_ICECROWN_PLAYER_AURA_DISMOUNT_H = 57419,
+ SPELL_TO_ICECROWN_AIRSHIP_PLAYER_AURA_TELEPORT_TO_DALARAN = 57460,
+ SPELL_TO_ICECROWN_AIRSHIP_FROST_WYRM_WAITING_TO_SUMMON_AURA = 57498,
+ POINT_END = 16,
+ SPELL_TO_ICECROWN_AIRSHIP_AURA_DISMOUNT_RESPONSE = 56921, // unhandled - vehicle casts 50630 on self
+ SPELL_EJECT_ALL_PASSENGERS = 50630,
+ SPELL_TO_ICECROWN_AIRSHIP_TELEPORT_TO_AIRSHIP_A_FORCE_PLAYER_TO_CAST = 57554,
+ SPELL_TO_ICECROWN_AIRSHIP_TELEPORT_TO_AIRSHIP_H_FORCE_PLAYER_TO_CAST = 57556,
+ SPELL_TO_ICECROWN_AIRSHIP_TELEPORT_TO_AIRSHIP_A = 56917,
+ SPELL_TO_ICECROWN_AIRSHIP_TELEPORT_TO_AIRSHIP_H = 57417,
};
-struct npc_preparations_for_war_vehicle : public NullCreatureAI
+struct npc_preparations_for_war_vehicle : public VehicleAI
{
- npc_preparations_for_war_vehicle(Creature* creature) : NullCreatureAI(creature) { }
-
- uint8 pointId;
- uint32 searchForShipTimer;
- uint32 transportEntry;
+ explicit npc_preparations_for_war_vehicle(Creature* creature) : VehicleAI(creature), searchForShipTimer(0), transportEntry(me->GetEntry() == NPC_CLOUDBUSTER ? TRANSPORT_THE_SKYBREAKER : TRANSPORT_ORGRIMS_HAMMER)
+ {
+ if (transportEntry == TRANSPORT_THE_SKYBREAKER)
+ {
+ // 30476 - [DND] Icecrown Flight To Airship Bunny (A)
+ passenger_x = 31.41805;
+ passenger_y = 0.126893;
+ passenger_z = 41.69821;
+ }
+ else // TRANSPORT_ORGRIMS_HAMMER
+ {
+ // 30588 - [DND] Icecrown Flight To Airship Bunny (H)
+ passenger_x = -18.10283;
+ passenger_y = -0.042108;
+ passenger_z = 45.31725;
+ }
+ }
- void InitializeAI() override
+ void PassengerBoarded(Unit* who, int8 /*seatId*/, bool apply) override
{
- me->GetMotionMaster()->MovePath(me->GetEntry(), FORCED_MOVEMENT_NONE, PathSource::SMART_WAYPOINT_MGR);
+ if (apply)
+ {
+ DoCastSelf(SPELL_TO_ICECROWN_AIRSHIP_PLAYER_AURA_TELEPORT_TO_DALARAN, true);
+ DoCastSelf(SPELL_FLIGHT, true);
+ DoCastSelf(me->GetEntry() == NPC_CLOUDBUSTER ? SPELL_TO_ICECROWN_PLAYER_AURA_DISMOUNT_A : SPELL_TO_ICECROWN_PLAYER_AURA_DISMOUNT_H , true);
+ DoCastSelf(SPELL_TO_ICECROWN_AIRSHIP_FROST_WYRM_WAITING_TO_SUMMON_AURA, true);
+ me->GetMotionMaster()->MovePath(me->GetEntry(), FORCED_MOVEMENT_NONE, PathSource::SMART_WAYPOINT_MGR);
+ }
+ else
+ who->RemoveAurasDueToSpell(VEHICLE_SPELL_PARACHUTE); // maybe vehicle / seat flag should be responsible for parachute gain?
+ }
- NullCreatureAI::InitializeAI();
- pointId = 0;
- searchForShipTimer = 0;
- transportEntry = (me->GetEntry() == NPC_HAMMERHEAD ? TRANSPORT_ORGRIMS_HAMMER : TRANSPORT_THE_SKYBREAKER);
+ void MovementInform(uint32 type, uint32 id) override
+ {
+ if (type == ESCORT_MOTION_TYPE && id == POINT_END)
+ searchForShipTimer = 3000;
}
- void MovementInform(uint32 type, uint32 /*id*/) override
+ void SpellHit(Unit* /*caster*/, SpellInfo const* spell) override
{
- if (type == ESCORT_MOTION_TYPE)
- if (++pointId == 17) // path size
- searchForShipTimer = 3000;
+ switch (spell->Id)
+ {
+ case SPELL_TO_ICECROWN_AIRSHIP_AURA_DISMOUNT_RESPONSE:
+ break;
+ case SPELL_TO_ICECROWN_AIRSHIP_TELEPORT_TO_AIRSHIP_A_FORCE_PLAYER_TO_CAST:
+ case SPELL_TO_ICECROWN_AIRSHIP_TELEPORT_TO_AIRSHIP_H_FORCE_PLAYER_TO_CAST:
+ {
+ uint32 teleportSpell = (spell->Id == SPELL_TO_ICECROWN_AIRSHIP_TELEPORT_TO_AIRSHIP_A_FORCE_PLAYER_TO_CAST)
+ ? SPELL_TO_ICECROWN_AIRSHIP_TELEPORT_TO_AIRSHIP_A
+ : SPELL_TO_ICECROWN_AIRSHIP_TELEPORT_TO_AIRSHIP_H;
+ DoCastSelf(teleportSpell, true); // hack: cast on self to avoid visual glitch on player when ejecting and teleporting on transport
+ DoCastSelf(SPELL_EJECT_ALL_PASSENGERS, true);
+ me->DespawnOrUnsummon(0s);
+ break;
+ }
+ default:
+ break;
+ }
}
void UpdateAI(uint32 diff) override
{
- // horde 7.55f, -0.09, 34.44, 3.13, +20
- // ally 45.18f, 0.03, 40.09, 3.14 +5
-
if (searchForShipTimer)
{
searchForShipTimer += diff;
@@ -68,37 +116,14 @@ struct npc_preparations_for_war_vehicle : public NullCreatureAI
{
searchForShipTimer = 1;
TransportsContainer const& transports = me->GetMap()->GetAllTransports();
- for (TransportsContainer::const_iterator itr = transports.begin(); itr != transports.end(); ++itr)
+ for (auto const transport : transports)
{
- if ((*itr)->GetEntry() == transportEntry)
+ if (transport->GetEntry() == transportEntry)
{
- float x, y, z;
- if (transportEntry == TRANSPORT_ORGRIMS_HAMMER)
- {
- x = 7.55f;
- y = -0.09f;
- z = 54.44f;
- }
- else
- {
- x = 45.18f;
- y = 0.03f;
- z = 45.09f;
- }
+ float x = passenger_x, y = passenger_y, z = passenger_z;
+ transport->CalculatePassengerPosition(x, y, z);
- (*itr)->CalculatePassengerPosition(x, y, z);
-
- if (me->GetDistance2d(x, y) < 10.0f)
- {
- me->DespawnOrUnsummon(1s);
- if (Vehicle* vehicle = me->GetVehicleKit())
- if (Unit* passenger = vehicle->GetPassenger(0))
- {
- passenger->NearTeleportTo(x, y, z - (transportEntry == TRANSPORT_ORGRIMS_HAMMER ? 19.0f : 4.0f), M_PI);
- passenger->RemoveAurasDueToSpell(VEHICLE_SPELL_PARACHUTE); // maybe vehicle / seat flag should be responsible for parachute gain?
- }
- }
- else
+ if (me->GetDistance2d(x, y) > 20.0f) // dismount trigger (56905, 57420) range is 30
me->GetMotionMaster()->MovePoint(0, x, y, z, FORCED_MOVEMENT_NONE, 0.f, 0.f, false, false);
break;
}
@@ -106,6 +131,10 @@ struct npc_preparations_for_war_vehicle : public NullCreatureAI
}
}
}
+private:
+ float passenger_x, passenger_y, passenger_z;
+ uint32 searchForShipTimer;
+ uint32 transportEntry;
};
/*******************************************************
diff --git a/src/server/scripts/Northrend/zone_dalaran.cpp b/src/server/scripts/Northrend/zone_dalaran.cpp
index ca887acbe8..7c5b9fba41 100644
--- a/src/server/scripts/Northrend/zone_dalaran.cpp
+++ b/src/server/scripts/Northrend/zone_dalaran.cpp
@@ -428,7 +428,7 @@ public:
struct npc_mageguard_dalaranAI : public ScriptedAI
{
- npc_mageguard_dalaranAI(Creature* creature) : ScriptedAI(creature)
+ explicit npc_mageguard_dalaranAI(Creature* creature) : ScriptedAI(creature)
{
creature->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE);
creature->ApplySpellImmune(0, IMMUNITY_DAMAGE, SPELL_SCHOOL_NORMAL, true);
@@ -449,6 +449,9 @@ public:
if (!me->IsWithinDist(who, 5.0f, false))
return;
+ if (who->IsCreature() && who->GetCreatureType() == CREATURE_TYPE_NON_COMBAT_PET)
+ return;
+
Player* player = who->GetCharmerOrOwnerPlayerOrPlayerItself();
if (!player || player->IsGameMaster() || player->IsBeingTeleported() || (player->GetPositionZ() > 670 && player->GetVehicle()) ||
@@ -484,7 +487,6 @@ public:
break;
}
me->SetOrientation(me->GetHomePosition().GetOrientation());
- return;
}
void UpdateAI(uint32 /*diff*/) override {}
diff --git a/src/server/scripts/Northrend/zone_dragonblight.cpp b/src/server/scripts/Northrend/zone_dragonblight.cpp
index ade76cf5fd..4b0238f3d7 100644
--- a/src/server/scripts/Northrend/zone_dragonblight.cpp
+++ b/src/server/scripts/Northrend/zone_dragonblight.cpp
@@ -789,7 +789,11 @@ class spell_q12237_rescue_villager : public SpellScript
result = SPELL_FAILED_CUSTOM_ERROR;
}
- if (!GetCaster()->FindNearestCreature(NPC_HELPLESS_VILLAGER_A, 5.0f) && !GetCaster()->FindNearestCreature(NPC_HELPLESS_VILLAGER_B, 5.0f))
+ std::list<Creature*> villagers;
+ GetCaster()->GetCreatureListWithEntryInGrid(villagers, { NPC_HELPLESS_VILLAGER_A, NPC_HELPLESS_VILLAGER_B }, 5.0f);
+ villagers.remove_if([](Creature* c) { return !c->IsAlive() || c->HasAura(SPELL_RIDE_VEHICLE); });
+
+ if (villagers.empty())
{
extension = SPELL_CUSTOM_ERROR_MUST_BE_NEAR_HELPLESS_VILLAGER;
result = SPELL_FAILED_CUSTOM_ERROR;
diff --git a/src/server/scripts/Northrend/zone_howling_fjord.cpp b/src/server/scripts/Northrend/zone_howling_fjord.cpp
index fc28598ec2..928a0a71e8 100644
--- a/src/server/scripts/Northrend/zone_howling_fjord.cpp
+++ b/src/server/scripts/Northrend/zone_howling_fjord.cpp
@@ -66,109 +66,6 @@ public:
}
};
-// The cleansing
-enum TurmoilTexts
-{
- SAY_TURMOIL_0 = 0,
- SAY_TURMOIL_1 = 1,
- SAY_TURMOIL_HALF_HP = 2,
- SAY_TURMOIL_DEATH = 3,
-};
-
-class npc_your_inner_turmoil : public CreatureScript
-{
-public:
- npc_your_inner_turmoil() : CreatureScript("npc_your_inner_turmoil") { }
-
- struct npc_your_inner_turmoilAI : public ScriptedAI
- {
- npc_your_inner_turmoilAI(Creature* creature) : ScriptedAI(creature) {}
-
- uint32 timer;
- short phase;
- bool health50;
-
- void Reset() override
- {
- timer = 0;
- phase = 0;
- health50 = false;
- }
-
- void UpdateAI(uint32 diff) override
- {
- if (timer >= 6000 && phase < 2)
- {
- phase++;
- setphase(phase);
- timer = 0;
- }
-
- timer += diff;
-
- DoMeleeAttackIfReady();
- }
-
- void DamageTaken(Unit*, uint32& /*damage*/, DamageEffectType /*damagetype*/, SpellSchoolMask /*damageSchoolMask*/) override
- {
- if (HealthBelowPct(50) && !health50)
- {
- if (TempSummon const* tempSummon = me->ToTempSummon())
- {
- if (WorldObject* summoner = tempSummon->GetSummonerUnit())
- {
- Talk(SAY_TURMOIL_HALF_HP, summoner);
- }
- }
-
- health50 = true;
- }
- }
-
- void JustDied(Unit* /*killer*/) override
- {
- if (TempSummon const* tempSummon = me->ToTempSummon())
- {
- if (WorldObject* summoner = tempSummon->GetSummonerUnit())
- {
- Talk(SAY_TURMOIL_DEATH, summoner);
- }
- }
- }
-
- void setphase(short newPhase)
- {
- Unit* summoner = me->ToTempSummon() ? me->ToTempSummon()->GetSummonerUnit() : nullptr;
- if (!summoner || !summoner->IsPlayer())
- return;
-
- switch (newPhase)
- {
- case 1:
- Talk(SAY_TURMOIL_0, summoner->ToPlayer());
- return;
- case 2:
- {
- Talk(SAY_TURMOIL_1, summoner->ToPlayer());
- me->SetLevel(summoner->GetLevel());
- me->SetFaction(FACTION_MONSTER);
- if (me->GetExactDist(summoner) < 50.0f)
- {
- me->UpdatePosition(summoner->GetPositionX(), summoner->GetPositionY(), summoner->GetPositionZ(), 0.0f, true);
- summoner->CastSpell(me, 50218, true); // clone caster
- AttackStart(summoner);
- }
- }
- }
- }
- };
-
- CreatureAI* GetAI(Creature* creature) const override
- {
- return new npc_your_inner_turmoilAI(creature);
- }
-};
-
/*######
## npc_apothecary_hanes
######*/
@@ -457,13 +354,145 @@ class spell_hawk_hunting : public SpellScript
}
};
+/*######
+## Quest 11317, 11322: The Cleansing
+######*/
+
+enum TheCleansing
+{
+ SPELL_CLEANSING_SOUL = 43351,
+ SPELL_SUMMON_INNER_TURMOIL = 50167,
+ SPELL_RECENT_MEDITATION = 61720,
+ SPELL_MIRROR_IMAGE_AURA = 50218,
+
+ QUEST_THE_CLEANSING_H = 11317,
+ QUEST_THE_CLEANSING_A = 11322
+};
+
+// 43365 - The Cleansing: Shrine Cast
+class spell_the_cleansing_shrine_cast : public SpellScript
+{
+ PrepareSpellScript(spell_the_cleansing_shrine_cast);
+
+ bool Validate(SpellInfo const* /*spellInfo*/) override
+ {
+ return ValidateSpellInfo({ SPELL_RECENT_MEDITATION, SPELL_CLEANSING_SOUL }) &&
+ sObjectMgr->GetQuestTemplate(QUEST_THE_CLEANSING_H) &&
+ sObjectMgr->GetQuestTemplate(QUEST_THE_CLEANSING_A);
+ }
+
+ SpellCastResult CheckCast()
+ {
+ // Error is correct for quest check but may be not correct for aura and this may be a wrong place to send error
+ if (Player* target = GetExplTargetUnit()->ToPlayer())
+ {
+ if (target->HasAura(SPELL_RECENT_MEDITATION) || (!(target->GetQuestStatus(QUEST_THE_CLEANSING_H) == QUEST_STATUS_INCOMPLETE ||
+ target->GetQuestStatus(QUEST_THE_CLEANSING_A) == QUEST_STATUS_INCOMPLETE)))
+ {
+ Spell::SendCastResult(target, GetSpellInfo(), 0, SPELL_FAILED_FIZZLE);
+ return SPELL_FAILED_FIZZLE;
+ }
+ }
+ return SPELL_CAST_OK;
+ }
+
+ void HandleScript(SpellEffIndex /*effIndex*/)
+ {
+ GetHitUnit()->CastSpell(GetHitUnit(), SPELL_CLEANSING_SOUL, true);
+ }
+
+ void Register() override
+ {
+ OnCheckCast += SpellCheckCastFn(spell_the_cleansing_shrine_cast::CheckCast);
+ OnEffectHitTarget += SpellEffectFn(spell_the_cleansing_shrine_cast::HandleScript, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
+ }
+};
+
+// 43351 - Cleansing Soul
+class spell_the_cleansing_cleansing_soul : public AuraScript
+{
+ PrepareAuraScript(spell_the_cleansing_cleansing_soul);
+
+ bool Validate(SpellInfo const* /*spellInfo*/) override
+ {
+ return ValidateSpellInfo({ SPELL_SUMMON_INNER_TURMOIL, SPELL_RECENT_MEDITATION });
+ }
+
+ void AfterApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
+ {
+ GetTarget()->SetStandState(UNIT_STAND_STATE_SIT);
+ }
+
+ void AfterRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
+ {
+ Unit* target = GetTarget();
+ target->SetStandState(UNIT_STAND_STATE_STAND);
+ target->CastSpell(target, SPELL_SUMMON_INNER_TURMOIL, true);
+ target->CastSpell(target, SPELL_RECENT_MEDITATION, true);
+ }
+
+ void Register() override
+ {
+ AfterEffectApply += AuraEffectApplyFn(spell_the_cleansing_cleansing_soul::AfterApply, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
+ AfterEffectRemove += AuraEffectRemoveFn(spell_the_cleansing_cleansing_soul::AfterRemove, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
+ }
+};
+
+// 50217 - The Cleansing: Script Effect Player Cast Mirror Image
+class spell_the_cleansing_mirror_image_script_effect : public SpellScript
+{
+ PrepareSpellScript(spell_the_cleansing_mirror_image_script_effect);
+
+ bool Validate(SpellInfo const* /*spellInfo*/) override
+ {
+ return ValidateSpellInfo({ SPELL_MIRROR_IMAGE_AURA });
+ }
+
+ void HandleScript(SpellEffIndex /*effIndex*/)
+ {
+ GetHitUnit()->CastSpell(GetHitUnit(), SPELL_MIRROR_IMAGE_AURA, true);
+ }
+
+ void Register() override
+ {
+ OnEffectHitTarget += SpellEffectFn(spell_the_cleansing_mirror_image_script_effect::HandleScript, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
+ }
+};
+
+// 50238 - The Cleansing: Your Inner Turmoil's On Death Cast on Master
+class spell_the_cleansing_on_death_cast_on_master : public SpellScript
+{
+ PrepareSpellScript(spell_the_cleansing_on_death_cast_on_master);
+
+ bool Validate(SpellInfo const* spellInfo) override
+ {
+ return ValidateSpellInfo({ uint32(spellInfo->GetEffect(EFFECT_0).CalcValue()) });
+ }
+
+ void HandleScript(SpellEffIndex /*effIndex*/)
+ {
+ if (Unit* caster = GetCaster())
+ if (TempSummon* casterSummon = caster->ToTempSummon())
+ if (Unit* summoner = casterSummon->GetSummonerUnit())
+ summoner->CastSpell(summoner, GetSpellInfo()->Effects[EFFECT_0].CalcValue(), true);
+ }
+
+ void Register() override
+ {
+ OnEffectHit += SpellEffectFn(spell_the_cleansing_on_death_cast_on_master::HandleScript, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
+ }
+};
+
void AddSC_howling_fjord()
{
new npc_attracted_reef_bull();
- new npc_your_inner_turmoil();
new npc_apothecary_hanes();
new npc_plaguehound_tracker();
new npc_razael_and_lyana();
RegisterCreatureAI(npc_rodin_lightning_enabler);
RegisterSpellScript(spell_hawk_hunting);
+ RegisterSpellScript(spell_the_cleansing_shrine_cast);
+ RegisterSpellScript(spell_the_cleansing_cleansing_soul);
+ RegisterSpellScript(spell_the_cleansing_mirror_image_script_effect);
+ RegisterSpellScript(spell_the_cleansing_on_death_cast_on_master);
}
diff --git a/src/server/scripts/Northrend/zone_icecrown.cpp b/src/server/scripts/Northrend/zone_icecrown.cpp
index 6d1b804b98..06cbe92252 100644
--- a/src/server/scripts/Northrend/zone_icecrown.cpp
+++ b/src/server/scripts/Northrend/zone_icecrown.cpp
@@ -81,6 +81,7 @@ enum valhalas
EVENT_VALHALAS_SECOND = 2,
EVENT_VALHALAS_THIRD = 3,
EVENT_VALHALAS_CHECK_PLAYER = 4,
+ EVENT_VALHALAS_THIRD_2 = 5,
// Fallen Heroes
NPC_ELDRETH = 31195,
@@ -111,7 +112,6 @@ public:
EventMap events;
SummonList summons;
ObjectGuid playerGUID;
- ObjectGuid playerGUID2;
uint32 currentQuest;
void Reset() override
@@ -119,13 +119,18 @@ public:
ResetData();
}
+ void JustReachedHome() override
+ {
+ me->SetNpcFlag(UNIT_NPC_FLAG_GOSSIP | UNIT_NPC_FLAG_QUESTGIVER);
+ }
+
void ResetData()
{
events.Reset();
summons.DespawnAll();
playerGUID.Clear();
currentQuest = 0;
- me->SetNpcFlag(UNIT_NPC_FLAG_QUESTGIVER);
+ me->SetNpcFlag(UNIT_NPC_FLAG_QUESTGIVER | UNIT_NPC_FLAG_GOSSIP);
}
void JustSummoned(Creature* creature) override
@@ -170,63 +175,16 @@ public:
{
events.ScheduleEvent(EVENT_VALHALAS_FIRST, 6s);
events.ScheduleEvent(EVENT_VALHALAS_CHECK_PLAYER, 30s);
+ me->RemoveNpcFlag(UNIT_NPC_FLAG_GOSSIP | UNIT_NPC_FLAG_QUESTGIVER);
currentQuest = questId;
playerGUID = guid;
}
- void CheckSummons()
+ void EndBattle()
{
- bool allow = true;
- for (ObjectGuid const& guid : summons)
- if (Creature* cr = ObjectAccessor::GetCreature(*me, guid))
- if (cr->IsAlive())
- allow = false;
-
- if (allow)
- {
- uint32 quest = currentQuest;
- if (Player* player = ObjectAccessor::GetPlayer(*me, playerGUID))
- {
- switch (quest)
- {
- case QUEST_BFV_FALLEN_HEROES:
- me->Yell("$N has defeated the fallen heroes of Valhalas battles past. This is only a beginning, but it will suffice.", LANG_UNIVERSAL, ObjectAccessor::GetPlayer(*me, playerGUID));
- break;
- case QUEST_BFV_DARK_MASTER:
- me->Yell("Khit'rix the Dark Master has been defeated by $N and his band of companions. Let the next challenge be issued!", LANG_UNIVERSAL, ObjectAccessor::GetPlayer(*me, playerGUID));
- break;
- case QUEST_BFV_SIGRID:
- me->Yell("$N has defeated Sigrid Iceborn for a second time. Well, this time he did it with the help of his friends, but a win is a win!", LANG_UNIVERSAL, ObjectAccessor::GetPlayer(*me, playerGUID));
- break;
- case QUEST_BFV_CARNAGE:
- me->Yell("The horror known as Carnage is no more. Could it be that $N is truly worthy of battle in Valhalas? We shall see.", LANG_UNIVERSAL, ObjectAccessor::GetPlayer(*me, playerGUID));
- break;
- case QUEST_BFV_THANE:
- me->Yell("Thane Banahogg the Deathblow has fallen to $N and his fighting companions. He has but one challenge ahead of him. Who will it be?", LANG_UNIVERSAL, ObjectAccessor::GetPlayer(*me, playerGUID));
- break;
- case QUEST_BFV_FINAL:
- me->Yell("The unthinkable has happened... $N has slain Prince Sandoval!", LANG_UNIVERSAL, ObjectAccessor::GetPlayer(*me, playerGUID));
- break;
- }
- player->GroupEventHappens(quest, player);
- }
- playerGUID2 = playerGUID;
- EnterEvadeMode();
- if (quest == QUEST_BFV_FINAL)
- events.ScheduleEvent(EVENT_VALHALAS_THIRD, 7s);
- }
- else
- {
- uint32 quest = currentQuest;
- if (Player* player = ObjectAccessor::GetPlayer(*me, playerGUID))
- {
- if (!player->HasQuest(quest))
- {
- ResetData();
- return;
- }
- }
- }
+ ResetData();
+ me->RemoveNpcFlag(UNIT_NPC_FLAG_GOSSIP | UNIT_NPC_FLAG_QUESTGIVER);
+ me->GetMotionMaster()->MoveTargetedHome();
}
void UpdateAI(uint32 diff) override
@@ -296,29 +254,65 @@ public:
}
case EVENT_VALHALAS_THIRD:
{
- me->Yell("In defeating him, he and his fighting companions have proven themselves worthy of battle in this most sacred place of vrykul honor.", LANG_UNIVERSAL, ObjectAccessor::GetPlayer(*me, playerGUID));
- events.ScheduleEvent(EVENT_VALHALAS_THIRD + 2, 7s);
+ if (Player* player = ObjectAccessor::GetPlayer(*me, playerGUID))
+ me->Yell("In defeating him, he and his fighting companions have proven themselves worthy of battle in this most sacred place of vrykul honor.", LANG_UNIVERSAL, player);
+ events.ScheduleEvent(EVENT_VALHALAS_THIRD_2, 7s);
break;
}
- case EVENT_VALHALAS_THIRD+2:
+ case EVENT_VALHALAS_THIRD_2:
{
- me->Yell("ALL HAIL $N, CHAMPION OF VALHALAS! ", LANG_UNIVERSAL, ObjectAccessor::GetPlayer(*me, playerGUID2));
+ if (Player* player = ObjectAccessor::GetPlayer(*me, playerGUID))
+ me->Yell("ALL HAIL $N, CHAMPION OF VALHALAS! ", LANG_UNIVERSAL, player);
+ EndBattle();
break;
}
case EVENT_VALHALAS_CHECK_PLAYER:
{
- bool fail = true;
- if (Player* player = ObjectAccessor::GetPlayer(*me, playerGUID))
- if (me->GetDistance(player) < 100.0f)
+ Player* player = ObjectAccessor::GetPlayer(*me, playerGUID);
+ if (!player || me->GetDistance(player) >= 100.0f)
+ {
+ EndBattle();
+ break;
+ }
+
+ if (summons.IsAnyCreatureAlive())
+ {
+ if (!player->HasQuest(currentQuest))
+ EndBattle();
+ else
+ events.Repeat(5s);
+ }
+ else
+ {
+ switch (currentQuest)
{
- fail = false;
- CheckSummons();
+ case QUEST_BFV_FALLEN_HEROES:
+ me->Yell("$N has defeated the fallen heroes of Valhalas battles past. This is only a beginning, but it will suffice.", LANG_UNIVERSAL, player);
+ break;
+ case QUEST_BFV_DARK_MASTER:
+ me->Yell("Khit'rix the Dark Master has been defeated by $N and his band of companions. Let the next challenge be issued!", LANG_UNIVERSAL, player);
+ break;
+ case QUEST_BFV_SIGRID:
+ me->Yell("$N has defeated Sigrid Iceborn for a second time. Well, this time he did it with the help of his friends, but a win is a win!", LANG_UNIVERSAL, player);
+ break;
+ case QUEST_BFV_CARNAGE:
+ me->Yell("The horror known as Carnage is no more. Could it be that $N is truly worthy of battle in Valhalas? We shall see.", LANG_UNIVERSAL, player);
+ break;
+ case QUEST_BFV_THANE:
+ me->Yell("Thane Banahogg the Deathblow has fallen to $N and his fighting companions. He has but one challenge ahead of him. Who will it be?", LANG_UNIVERSAL, player);
+ break;
+ case QUEST_BFV_FINAL:
+ me->Yell("The unthinkable has happened... $N has slain Prince Sandoval!", LANG_UNIVERSAL, player);
+ break;
}
- if (fail)
- EnterEvadeMode();
+ player->GroupEventHappens(currentQuest, player);
- events.Repeat(5s);
+ if (currentQuest == QUEST_BFV_FINAL)
+ events.ScheduleEvent(EVENT_VALHALAS_THIRD, 7s);
+ else
+ EndBattle();
+ }
break;
}
}
diff --git a/src/server/scripts/Northrend/zone_storm_peaks.cpp b/src/server/scripts/Northrend/zone_storm_peaks.cpp
index e940f83539..2bdf9dba9f 100644
--- a/src/server/scripts/Northrend/zone_storm_peaks.cpp
+++ b/src/server/scripts/Northrend/zone_storm_peaks.cpp
@@ -315,300 +315,6 @@ public:
}
};
-enum eWildWyrm
-{
- SPELL_FIGHT_WYRM_BASE = 56673,
- SPELL_FIGHT_WYRM_NEXT = 60863,
- SPELL_SPEAR_OF_HODIR = 56671,
- SPELL_DODGE_CLAWS = 56704,
- SPELL_WYRM_GRIP = 56689,
- SPELL_GRAB_ON = 60533,
- SPELL_THRUST_SPEAR = 56690,
- SPELL_MIGHTY_SPEAR_THRUST = 60586,
- SPELL_FATAL_STRIKE = 60587,
- SPELL_PRY_JAWS_OPEN = 56706,
- SPELL_JAWS_OF_DEATH = 56692,
-};
-
-class npc_wild_wyrm : public CreatureScript
-{
-public:
- npc_wild_wyrm() : CreatureScript("npc_wild_wyrm") { }
-
- struct npc_wild_wyrmAI : public ScriptedAI
- {
- npc_wild_wyrmAI(Creature* creature) : ScriptedAI(creature) {}
-
- ObjectGuid playerGUID;
- uint32 checkTimer;
- uint32 announceAttackTimer;
- uint32 attackTimer;
- bool setCharm;
- bool switching;
- bool startPath;
-
- void EnterEvadeMode(EvadeReason why) override
- {
- if (switching || me->HasControlVehicleAura())
- return;
- ScriptedAI::EnterEvadeMode(why);
- }
-
- void Reset() override
- {
- me->SetRegeneratingHealth(true);
- me->SetSpeed(MOVE_RUN, 1.14f, true); // ZOMG!
- setCharm = false;
- switching = false;
- startPath = false;
- checkTimer = 0;
- playerGUID.Clear();
- attackTimer = 0;
- announceAttackTimer = 0;
- me->AddUnitState(UNIT_STATE_NO_ENVIRONMENT_UPD);
- }
-
- void PassengerBoarded(Unit*, int8, bool apply) override
- {
- if (!apply && me->IsAlive() && me->HasAura(SPELL_WYRM_GRIP))
- me->RemoveAurasDueToSpell(SPELL_WYRM_GRIP);
- }
-
- void MovementInform(uint32 type, uint32 pointId) override
- {
- if (type == POINT_MOTION_TYPE && pointId == 1 && !me->GetCharmerGUID())
- {
- if (Player* player = GetValidPlayer())
- {
- checkTimer = 1;
- me->SetFullHealth();
- player->CastSpell(me, SPELL_FIGHT_WYRM_BASE, true);
- me->CastSpell(me, SPELL_WYRM_GRIP, true);
- me->SetRegeneratingHealth(false);
- }
- }
- else if (type == ESCORT_MOTION_TYPE && me->movespline->Finalized())
- startPath = true;
- else if (type == EFFECT_MOTION_TYPE && pointId == me->GetEntry())
- me->KillSelf();
- }
-
- void DamageTaken(Unit* who, uint32& damage, DamageEffectType, SpellSchoolMask) override
- {
- if (who != me)
- {
- damage = 0;
- if (!GetValidPlayer())
- setCharm = true; // will enter evade on next update
- }
- }
-
- void AttackStart(Unit*) override { }
- void MoveInLineOfSight(Unit* /*who*/) override { }
-
- void OnCharmed(bool apply) override
- {
- if (apply)
- setCharm = true;
- }
-
- void SpellHit(Unit* caster, SpellInfo const* spellInfo) override
- {
- if (!playerGUID && spellInfo->Id == SPELL_SPEAR_OF_HODIR)
- {
- me->GetMotionMaster()->MovePoint(1, caster->GetPositionX(), caster->GetPositionY(), caster->GetPositionZ() + 12.0f);
- playerGUID = caster->GetGUID();
- }
- else if (spellInfo->Id == SPELL_GRAB_ON)
- {
- if (Aura* aura = me->GetAura(SPELL_WYRM_GRIP))
- aura->ModStackAmount(10);
- }
- else if (spellInfo->Id == SPELL_THRUST_SPEAR)
- {
- if (Aura* aura = me->GetAura(SPELL_WYRM_GRIP))
- aura->ModStackAmount(-5);
- }
- else if (spellInfo->Id == SPELL_MIGHTY_SPEAR_THRUST)
- {
- if (Aura* aura = me->GetAura(SPELL_WYRM_GRIP))
- aura->ModStackAmount(-15);
- }
- else if (spellInfo->Id == SPELL_FATAL_STRIKE)
- {
- if (roll_chance_i(me->GetAuraCount(SPELL_PRY_JAWS_OPEN) * 10))
- {
- if (Player* player = GetValidPlayer())
- {
- player->KilledMonsterCredit(30415);
- player->RemoveAurasDueToSpell(SPELL_JAWS_OF_DEATH);
- }
- me->SetStandState(UNIT_STAND_STATE_DEAD);
- me->GetMotionMaster()->MoveFall(me->GetEntry());
- }
- else
- Talk(2);
- }
- }
-
- Player* GetValidPlayer()
- {
- Player* charmer = ObjectAccessor::GetPlayer(*me, playerGUID);
- if (charmer && charmer->IsAlive() && me->GetDistance(charmer) < 20.0f)
- return charmer;
- return nullptr;
- }
-
- void UpdateAI(uint32 diff) override
- {
- if (startPath)
- {
- startPath = false;
- me->GetMotionMaster()->MovePath(me->GetWaypointPath(), FORCED_MOVEMENT_NONE, PathSource::WAYPOINT_MGR);
- }
- if (setCharm)
- {
- setCharm = false;
-
- if (Player* charmer = GetValidPlayer())
- {
- me->SetFaction(FACTION_MONSTER_2);
- charmer->SetClientControl(me, 0, true);
-
- me->SetSpeed(MOVE_RUN, 2.0f, true);
- startPath = true;
- }
- else
- {
- me->RemoveAllAuras();
- EnterEvadeMode(EVADE_REASON_OTHER);
- }
- return;
- }
-
- if (!checkTimer)
- return;
-
- if (checkTimer < 10000)
- {
- checkTimer += diff;
- if (checkTimer >= 2000)
- {
- checkTimer = 1;
- if (me->HealthBelowPct(25))
- {
- if (Player* player = GetValidPlayer())
- {
- Talk(3);
- switching = true;
- me->RemoveAllAuras();
- me->CastSpell(me, SPELL_JAWS_OF_DEATH, true);
- player->CastSpell(me, SPELL_FIGHT_WYRM_NEXT, true);
- checkTimer = 10000;
- return;
- }
- else
- {
- me->RemoveAllAuras();
- EnterEvadeMode(EVADE_REASON_OTHER);
- return;
- }
- }
- }
- }
- else if (checkTimer < 20000)
- {
- checkTimer += diff;
- if (checkTimer >= 13000)
- {
- switching = false;
- checkTimer = 20000;
- }
- }
- else if (checkTimer < 30000)
- {
- checkTimer += diff;
- if (checkTimer >= 22000)
- {
- checkTimer = 20000;
- Player* player = GetValidPlayer();
- if (!player)
- {
- me->RemoveAllAuras();
- EnterEvadeMode(EVADE_REASON_OTHER);
- }
- }
- return;
- }
-
- announceAttackTimer += diff;
- if (announceAttackTimer >= 7000)
- {
- announceAttackTimer = urand(0, 3000);
- Talk(0);
- attackTimer = 1;
- }
- if (attackTimer)
- {
- attackTimer += diff;
- if (attackTimer > 2000)
- {
- attackTimer = 0;
- Player* player = ObjectAccessor::GetPlayer(*me, playerGUID);
- if (player && player->HasAura(SPELL_DODGE_CLAWS))
- Talk(1);
- else if (player)
- me->AttackerStateUpdate(player);
- else
- {
- me->RemoveAllAuras();
- EnterEvadeMode(EVADE_REASON_OTHER);
- }
- }
- }
- }
- };
-
- CreatureAI* GetAI(Creature* creature) const override
- {
- return new npc_wild_wyrmAI(creature);
- }
-};
-
-class spell_q13003_thursting_hodirs_spear_aura : public AuraScript
-{
- PrepareAuraScript(spell_q13003_thursting_hodirs_spear_aura);
-
- void OnApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
- {
- ModStackAmount(60);
- }
-
- void AfterRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
- {
- if (Creature* creature = GetUnitOwner()->ToCreature())
- {
- if (!creature->IsInEvadeMode())
- {
- creature->RemoveAllAuras();
- creature->AI()->EnterEvadeMode();
- }
- }
- }
-
- void HandlePeriodic(AuraEffect const* /* aurEff */)
- {
- ModStackAmount(-1);
- }
-
- void Register() override
- {
- OnEffectPeriodic += AuraEffectPeriodicFn(spell_q13003_thursting_hodirs_spear_aura::HandlePeriodic, EFFECT_0, SPELL_AURA_PERIODIC_DUMMY);
- OnEffectApply += AuraEffectApplyFn(spell_q13003_thursting_hodirs_spear_aura::OnApply, EFFECT_0, SPELL_AURA_PERIODIC_DUMMY, AURA_EFFECT_HANDLE_REAL);
- AfterEffectRemove += AuraEffectRemoveFn(spell_q13003_thursting_hodirs_spear_aura::AfterRemove, EFFECT_0, SPELL_AURA_PERIODIC_DUMMY, AURA_EFFECT_HANDLE_REAL);
- }
-};
-
enum q13007IronColossus
{
SPELL_JORMUNGAR_SUBMERGE = 56504,
@@ -706,7 +412,7 @@ public:
switch (action)
{
case GOSSIP_ACTION_TRAIN:
- player->GetSession()->SendTrainerList(creature->GetGUID());
+ player->GetSession()->SendTrainerList(creature);
break;
case GOSSIP_ACTION_TRADE:
player->GetSession()->SendListInventory(creature->GetGUID());
@@ -915,72 +621,40 @@ public:
}
};
-class npc_hyldsmeet_protodrake : public CreatureScript
+enum HyldsmeetProtoDrake
{
- enum NPCs
- {
- NPC_HYLDSMEET_DRAKERIDER = 29694
- };
+ NPC_HYLDSMEET_DRAKERIDER = 29694
+};
-public:
- npc_hyldsmeet_protodrake() : CreatureScript("npc_hyldsmeet_protodrake") { }
+struct npc_hyldsmeet_protodrake : public CreatureAI
+{
+ explicit npc_hyldsmeet_protodrake(Creature* creature) : CreatureAI(creature), _accessoryRespawnTimer(0) { }
- class npc_hyldsmeet_protodrakeAI : public CreatureAI
+ void PassengerBoarded(Unit* who, int8 /*seat*/, bool apply) override
{
- public:
- npc_hyldsmeet_protodrakeAI(Creature* creature) : CreatureAI(creature), _accessoryRespawnTimer(0), _vehicleKit(creature->GetVehicleKit()) { }
-
- void PassengerBoarded(Unit* who, int8 /*seat*/, bool apply) override
- {
- if (apply)
- {
- class DelayedTransportPositionOffsets : public BasicEvent
- {
- public:
- DelayedTransportPositionOffsets(Unit* owner) : _owner(owner) { }
-
- bool Execute(uint64 /*eventTime*/, uint32 /*updateTime*/) override
- {
- _owner->m_movementInfo.transport.pos.Relocate(-3.5f, 0.f, -0.2f, 0.f);
- return true;
- }
-
- private:
- Unit* _owner;
- };
-
- if (who->IsPlayer())
- who->m_Events.AddEventAtOffset(new DelayedTransportPositionOffsets(who), 500ms);
-
- return;
- }
+ if (apply)
+ return;
- if (who->GetEntry() == NPC_HYLDSMEET_DRAKERIDER)
- _accessoryRespawnTimer = 5 * MINUTE * IN_MILLISECONDS;
- }
+ if (who->GetEntry() == NPC_HYLDSMEET_DRAKERIDER)
+ _accessoryRespawnTimer = 5 * MINUTE * IN_MILLISECONDS;
+ }
- void UpdateAI(uint32 diff) override
+ void UpdateAI(uint32 diff) override
+ {
+ //! We need to manually reinstall accessories because the vehicle itself is friendly to players,
+ //! so EnterEvadeMode is never triggered. The accessory on the other hand is hostile and killable.
+ Vehicle* vehicleKit = me->GetVehicleKit();
+ if (_accessoryRespawnTimer && _accessoryRespawnTimer <= diff && vehicleKit)
{
- //! We need to manually reinstall accessories because the vehicle itself is friendly to players,
- //! so EnterEvadeMode is never triggered. The accessory on the other hand is hostile and killable.
- if (_accessoryRespawnTimer && _accessoryRespawnTimer <= diff && _vehicleKit)
- {
- _vehicleKit->InstallAllAccessories(true);
- _accessoryRespawnTimer = 0;
- }
- else
- _accessoryRespawnTimer -= diff;
+ vehicleKit->InstallAllAccessories(true);
+ _accessoryRespawnTimer = 0;
}
-
- private:
- uint32 _accessoryRespawnTimer;
- Vehicle* _vehicleKit;
- };
-
- CreatureAI* GetAI(Creature* creature) const override
- {
- return new npc_hyldsmeet_protodrakeAI(creature);
+ else
+ _accessoryRespawnTimer -= diff;
}
+
+private:
+ uint32 _accessoryRespawnTimer;
};
enum CloseRift
@@ -1166,21 +840,566 @@ class spell_feed_stormcrest_eagle : public SpellScript
}
};
+enum MammothExplosion
+{
+ SPELL_MAMMOTH_EXPL_1 = 54627,
+ SPELL_MAMMOTH_EXPL_2 = 54628,
+ SPELL_MAMMOTH_EXPL_3 = 54623,
+ SPELL_MAIN_MAMMOTH_MEAT = 57444
+};
+
+// 54581 - Mammoth Explosion Spell Spawner
+class spell_mammoth_explosion : public SpellScript
+{
+ PrepareSpellScript(spell_mammoth_explosion);
+
+ bool Validate(SpellInfo const* /*spellInfo*/) override
+ {
+ return ValidateSpellInfo({ SPELL_MAMMOTH_EXPL_1, SPELL_MAMMOTH_EXPL_2, SPELL_MAMMOTH_EXPL_3, SPELL_MAIN_MAMMOTH_MEAT });
+ }
+
+ void HandleOnEffectHit(SpellEffIndex /*effIndex*/)
+ {
+ if (Unit* target = GetHitUnit())
+ {
+ for (uint32 spellId : { SPELL_MAMMOTH_EXPL_1, SPELL_MAMMOTH_EXPL_2, SPELL_MAMMOTH_EXPL_3 })
+ target->CastSpell(GetHitUnit(), spellId, true);
+
+ target->CastSpell(GetHitUnit(), SPELL_MAIN_MAMMOTH_MEAT, true);
+
+ target->SetVisible(false);
+ }
+ }
+
+ void Register() override
+ {
+ OnEffectHitTarget += SpellEffectFn(spell_mammoth_explosion::HandleOnEffectHit, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
+ }
+};
+
+/*#####
+# Quest 13003 Thrusting Hodir's Spear
+#####*/
+
+enum WildWyrm
+{
+ // Phase 1
+ SPELL_PLAYER_MOUNT_WYRM = 56672,
+ SPELL_FIGHT_WYRM = 56673,
+ SPELL_SPEAR_OF_HODIR = 56671,
+ SPELL_GRIP = 56689,
+ SPELL_GRAB_ON = 60533,
+ SPELL_DODGE_CLAWS = 56704,
+ SPELL_THRUST_SPEAR = 56690,
+ SPELL_MIGHTY_SPEAR_THRUST = 60586,
+ SPELL_CLAW_SWIPE_PERIODIC = 60689,
+ SPELL_CLAW_SWIPE_DAMAGE = 60776,
+ SPELL_FULL_HEAL_MANA = 32432,
+ SPELL_LOW_HEALTH_TRIGGER = 60596,
+
+ // Phase 2
+ SPELL_EJECT_PASSENGER_1 = 60603,
+ SPELL_PRY_JAWS_OPEN = 56706,
+ SPELL_FATAL_STRIKE = 60587,
+ SPELL_FATAL_STRIKE_DAMAGE = 60881,
+ SPELL_JAWS_OF_DEATH_PERIODIC = 56692,
+ SPELL_FLY_STATE_VISUAL = 60865,
+
+ // Dead phase
+ SPELL_WYRM_KILL_CREDIT = 56703,
+ SPELL_FALLING_DRAGON_FEIGN_DEATH = 55795,
+ SPELL_EJECT_ALL_PASSENGERS = 50630,
+
+ SAY_SWIPE = 0,
+ SAY_DODGED = 1,
+ SAY_PHASE_2 = 2,
+ SAY_GRIP_WARN = 3,
+ SAY_STRIKE_MISS = 4,
+
+ ACTION_CLAW_SWIPE_WARN = 1,
+ ACTION_CLAW_SWIPE_DODGE = 2,
+ ACTION_GRIP_FAILING = 3,
+ ACTION_GRIP_LOST = 4,
+ ACTION_FATAL_STRIKE_MISS = 5,
+
+ POINT_START_FIGHT = 1,
+ POINT_FALL = 2,
+
+ SEAT_INITIAL = 0,
+ SEAT_MOUTH = 1,
+
+ PHASE_INITIAL = 0,
+ PHASE_MOUTH = 1,
+ PHASE_DEAD = 2,
+ PHASE_MAX = 3
+};
+
+constexpr uint8 ControllableSpellsCount = 4;
+constexpr uint32 WyrmControlSpells[PHASE_MAX][ControllableSpellsCount] =
+{
+ { SPELL_GRAB_ON, SPELL_DODGE_CLAWS, SPELL_THRUST_SPEAR, SPELL_MIGHTY_SPEAR_THRUST },
+ { SPELL_PRY_JAWS_OPEN, 0, SPELL_FATAL_STRIKE, 0 },
+ { 0, 0, 0, 0 }
+};
+
+struct npc_wild_wyrm : public VehicleAI
+{
+ explicit npc_wild_wyrm(Creature* creature) : VehicleAI(creature) { }
+
+ void InitSpellsForPhase()
+ {
+ ASSERT(_phase < PHASE_MAX);
+ for (uint8 i = 0; i < ControllableSpellsCount; ++i)
+ me->m_spells[i] = WyrmControlSpells[_phase][i];
+ }
+
+ void Reset() override
+ {
+ _phase = PHASE_INITIAL;
+
+ _playerGuid.Clear();
+ scheduler.CancelAll();
+
+ InitSpellsForPhase();
+
+ me->SetImmuneToPC(false);
+ }
+
+ void DoAction(int32 action) override
+ {
+ Player* player = ObjectAccessor::GetPlayer(*me, _playerGuid);
+ if (!player)
+ return;
+
+ switch (action)
+ {
+ case ACTION_CLAW_SWIPE_WARN:
+ Talk(SAY_SWIPE, player);
+ break;
+ case ACTION_CLAW_SWIPE_DODGE:
+ Talk(SAY_DODGED, player);
+ break;
+ case ACTION_GRIP_FAILING:
+ Talk(SAY_GRIP_WARN, player);
+ break;
+ case ACTION_GRIP_LOST:
+ DoCastAOE(SPELL_EJECT_PASSENGER_1, true);
+ break;
+ case ACTION_FATAL_STRIKE_MISS:
+ Talk(SAY_STRIKE_MISS, player);
+ break;
+ default:
+ break;
+ }
+ }
+
+ void SpellHit(Unit* caster, SpellInfo const* spellInfo) override
+ {
+ if (!_playerGuid.IsEmpty() || spellInfo->Id != SPELL_SPEAR_OF_HODIR)
+ return;
+
+ _playerGuid = caster->GetGUID();
+ DoCastAOE(SPELL_FULL_HEAL_MANA, true);
+ me->SetImmuneToPC(true);
+
+ me->GetMotionMaster()->MovePoint(POINT_START_FIGHT, *caster);
+ }
+
+ void MovementInform(uint32 type, uint32 id) override
+ {
+ if (type != POINT_MOTION_TYPE && type != EFFECT_MOTION_TYPE)
+ return;
+
+ switch (id)
+ {
+ case POINT_START_FIGHT:
+ {
+ Player* player = ObjectAccessor::GetPlayer(*me, _playerGuid);
+ if (!player)
+ return;
+
+ DoCast(player, SPELL_PLAYER_MOUNT_WYRM);
+ me->GetMotionMaster()->Clear();
+ break;
+ }
+ case POINT_FALL:
+ DoCastAOE(SPELL_EJECT_ALL_PASSENGERS);
+ me->KillSelf();
+ break;
+ default:
+ break;
+ }
+ }
+
+ void DamageTaken(Unit* /*attacker*/, uint32& damage, DamageEffectType /*damagetype*/, SpellSchoolMask /*damageSchoolMask*/) override
+ {
+ if (damage >= me->GetHealth())
+ {
+ damage = me->GetHealth() - 1;
+
+ if (_phase == PHASE_DEAD)
+ return;
+
+ _phase = PHASE_DEAD;
+ scheduler.CancelAll().Async([this]
+ {
+ InitSpellsForPhase();
+
+ if (Player* player = ObjectAccessor::GetPlayer(*me, _playerGuid))
+ player->VehicleSpellInitialize();
+
+ DoCastAOE(SPELL_WYRM_KILL_CREDIT);
+ DoCastAOE(SPELL_FALLING_DRAGON_FEIGN_DEATH);
+
+ me->RemoveAurasDueToSpell(SPELL_JAWS_OF_DEATH_PERIODIC);
+ me->RemoveAurasDueToSpell(SPELL_PRY_JAWS_OPEN);
+
+ me->ReplaceAllNpcFlags(UNIT_NPC_FLAG_NONE);
+
+ me->GetMotionMaster()->MoveFall(POINT_FALL, true);
+ });
+ }
+ }
+
+ void PassengerBoarded(Unit* passenger, int8 seatId, bool apply) override
+ {
+ if (!apply)
+ {
+ // The player is automatically moved from SEAT_INITIAL to SEAT_MOUTH during phase transition.
+ // Only evade if player exits during the respective active phase.
+ bool initialPhaseExit = (seatId == SEAT_INITIAL && _phase == PHASE_INITIAL);
+ bool mouthPhaseExit = (seatId == SEAT_MOUTH && _phase == PHASE_MOUTH);
+ if (initialPhaseExit || mouthPhaseExit)
+ EnterEvadeMode(EVADE_REASON_NO_HOSTILES);
+ return;
+ }
+
+ if (passenger->GetGUID() != _playerGuid)
+ return;
+
+ if (seatId == SEAT_INITIAL)
+ {
+ me->CastCustomSpell(SPELL_GRIP, SPELLVALUE_AURA_STACK, 50);
+ DoCastAOE(SPELL_CLAW_SWIPE_PERIODIC, true);
+
+ scheduler.Async([this]
+ {
+ me->GetMotionMaster()->MoveWaypoint(me->GetWaypointPath(), true);
+ }).Schedule(500ms, [this](TaskContext context)
+ {
+ if (_phase == PHASE_MOUTH)
+ return;
+
+ if (me->HealthBelowPct(25))
+ {
+ _phase = PHASE_MOUTH;
+ context.Async([this]
+ {
+ InitSpellsForPhase();
+ DoCastAOE(SPELL_LOW_HEALTH_TRIGGER, true);
+ me->RemoveAurasDueToSpell(SPELL_CLAW_SWIPE_PERIODIC);
+ me->RemoveAurasDueToSpell(SPELL_GRIP);
+
+ if (Player* player = ObjectAccessor::GetPlayer(*me, _playerGuid))
+ Talk(SAY_PHASE_2, player);
+
+ DoCastAOE(SPELL_EJECT_PASSENGER_1, true);
+ DoCastAOE(SPELL_JAWS_OF_DEATH_PERIODIC);
+ DoCastAOE(SPELL_FLY_STATE_VISUAL);
+ });
+ return;
+ }
+
+ context.Repeat();
+ });
+ }
+ }
+
+ void UpdateAI(uint32 diff) override
+ {
+ if (!_playerGuid)
+ {
+ if (UpdateVictim())
+ DoMeleeAttackIfReady();
+ return;
+ }
+
+ scheduler.Update(diff);
+ }
+
+private:
+ uint8 _phase{PHASE_INITIAL};
+ ObjectGuid _playerGuid;
+};
+
+// 56689 - Grip
+class spell_grip : public AuraScript
+{
+ PrepareAuraScript(spell_grip);
+
+ void HandlePeriodic(AuraEffect const* /*aurEff*/)
+ {
+ ++_tickNumber;
+
+ // each 15 ticks stack reduction increases by 2 (increases by 1 at each 7th and 15th tick)
+ // except for the first 15 ticks that remove 1 stack each
+ uint32 const period = ((_tickNumber - 1) % 15) + 1;
+ uint32 const sequence = (_tickNumber - 1) / 15;
+
+ uint32 stacksToRemove;
+ if (sequence == 0)
+ stacksToRemove = 1;
+ else
+ {
+ stacksToRemove = sequence * 2;
+ if (period > 7)
+ ++stacksToRemove;
+ }
+
+ // while we could do ModStackAmount(-stacksToRemove), this is how it's done in sniffs :)
+ for (uint32 i = 0; i < stacksToRemove; ++i)
+ ModStackAmount(-1, AURA_REMOVE_BY_EXPIRE);
+
+ if (GetStackAmount() < 15 && !_warning)
+ {
+ _warning = true;
+ GetTarget()->GetAI()->DoAction(ACTION_GRIP_FAILING);
+ }
+ else if (GetStackAmount() > 30)
+ _warning = false;
+ }
+
+ void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
+ {
+ if (GetTargetApplication()->GetRemoveMode() != AURA_REMOVE_BY_EXPIRE)
+ return;
+
+ GetTarget()->GetAI()->DoAction(ACTION_GRIP_LOST);
+ }
+
+ void Register() override
+ {
+ OnEffectPeriodic += AuraEffectPeriodicFn(spell_grip::HandlePeriodic, EFFECT_0, SPELL_AURA_PERIODIC_DUMMY);
+
+ AfterEffectRemove += AuraEffectRemoveFn(spell_grip::OnRemove, EFFECT_0, SPELL_AURA_PERIODIC_DUMMY, AURA_EFFECT_HANDLE_REAL);
+ }
+
+ // tick number in the AuraEffect gets reset each time we stack the aura, so keep track of it locally
+ uint32 _tickNumber = 0;
+
+ bool _warning = false;
+};
+
+// 60533 - Grab On
+class spell_grab_on : public SpellScript
+{
+ PrepareSpellScript(spell_grab_on);
+
+ void HandleScript(SpellEffIndex /*effIndex*/)
+ {
+ if (Aura* grip = GetCaster()->GetAura(SPELL_GRIP, GetCaster()->GetGUID()))
+ grip->ModStackAmount(GetEffectValue(), AURA_REMOVE_BY_DEFAULT, false);
+ }
+
+ void Register() override
+ {
+ OnEffectHitTarget += SpellEffectFn(spell_grab_on::HandleScript, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
+ }
+};
+
+// 56690 - Thrust Spear
+// 60586 - Mighty Spear Thrust
+class spell_loosen_grip : public SpellScript
+{
+ PrepareSpellScript(spell_loosen_grip);
+
+public:
+ explicit spell_loosen_grip(int8 stacksToLose) : SpellScript(), _stacksToLose(stacksToLose) { }
+private:
+ int32 _stacksToLose;
+
+ void HandleScript(SpellEffIndex /*effIndex*/)
+ {
+ if (Aura* grip = GetCaster()->GetAura(SPELL_GRIP))
+ grip->ModStackAmount(-_stacksToLose, AURA_REMOVE_BY_EXPIRE);
+ }
+
+ void Register() override
+ {
+ OnEffectHitTarget += SpellEffectFn(spell_loosen_grip::HandleScript, EFFECT_1, SPELL_EFFECT_SCRIPT_EFFECT);
+ }
+};
+
+// 60596 - Low Health Trigger
+class spell_low_health_trigger : public SpellScript
+{
+ PrepareSpellScript(spell_low_health_trigger);
+
+ bool Validate(SpellInfo const* spellInfo) override
+ {
+ return ValidateSpellInfo({ static_cast<uint32>(spellInfo->GetEffect(EFFECT_0).CalcValue()) });
+ }
+
+ void HandleScript(SpellEffIndex /*effIndex*/)
+ {
+ GetHitUnit()->CastSpell(static_cast<Unit*>(nullptr), GetEffectValue(), true);
+ }
+
+ void Register() override
+ {
+ OnEffectHitTarget += SpellEffectFn(spell_low_health_trigger::HandleScript, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
+ }
+};
+
+// 60776 - Claw Swipe
+// 60864 - Jaws of Death
+class spell_jaws_of_death_claw_swipe_pct_damage : public SpellScript
+{
+ PrepareSpellScript(spell_jaws_of_death_claw_swipe_pct_damage);
+
+ void HandleDamage(SpellEffIndex /*effIndex*/)
+ {
+ SetEffectValue(static_cast<int32>(GetHitUnit()->CountPctFromMaxHealth(GetEffectValue())));
+ }
+
+ void Register() override
+ {
+ OnEffectLaunchTarget += SpellEffectFn(spell_jaws_of_death_claw_swipe_pct_damage::HandleDamage, EFFECT_0, SPELL_EFFECT_SCHOOL_DAMAGE);
+ }
+};
+
+// 56705 - Claw Swipe
+class spell_claw_swipe_check : public AuraScript
+{
+ PrepareAuraScript(spell_claw_swipe_check);
+
+ void OnApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
+ {
+ GetTarget()->GetAI()->DoAction(ACTION_CLAW_SWIPE_WARN);
+ }
+
+ void OnRemove(AuraEffect const* aurEff, AuraEffectHandleModes /*mode*/)
+ {
+ if (Vehicle* vehicle = GetTarget()->GetVehicleKit())
+ {
+ if (Unit* player = vehicle->GetPassenger(SEAT_INITIAL))
+ {
+ if (player->HasAura(SPELL_DODGE_CLAWS))
+ {
+ GetTarget()->GetAI()->DoAction(ACTION_CLAW_SWIPE_DODGE);
+ return;
+ }
+ }
+ }
+
+ GetTarget()->CastSpell(nullptr, aurEff->GetAmount());
+ }
+
+ void Register() override
+ {
+ AfterEffectApply += AuraEffectApplyFn(spell_claw_swipe_check::OnApply, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
+ AfterEffectRemove += AuraEffectApplyFn(spell_claw_swipe_check::OnRemove, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
+ }
+};
+
+// 60587 - Fatal Strike
+class spell_fatal_strike : public SpellScript
+{
+ PrepareSpellScript(spell_fatal_strike);
+
+ bool Validate(SpellInfo const* /*spellInfo*/) override
+ {
+ return ValidateSpellInfo({ SPELL_FATAL_STRIKE_DAMAGE });
+ }
+
+ void HandleDummy(SpellEffIndex /*effIndex*/)
+ {
+ int32 chance = 0;
+ if (AuraEffect const* aurEff = GetCaster()->GetAuraEffect(SPELL_PRY_JAWS_OPEN, EFFECT_0))
+ chance = aurEff->GetAmount();
+
+ if (!roll_chance_i(chance))
+ {
+ GetCaster()->GetAI()->DoAction(ACTION_FATAL_STRIKE_MISS);
+ return;
+ }
+
+ GetCaster()->CastSpell(static_cast<Unit*>(nullptr), SPELL_FATAL_STRIKE_DAMAGE, true);
+ }
+
+ void Register() override
+ {
+ OnEffectHitTarget += SpellEffectFn(spell_fatal_strike::HandleDummy, EFFECT_0, SPELL_EFFECT_DUMMY);
+ }
+};
+
+// 56672 - Player Mount Wyrm
+class spell_player_mount_wyrm : public AuraScript
+{
+ PrepareAuraScript(spell_player_mount_wyrm);
+
+ bool Validate(SpellInfo const* /*spellInfo*/) override
+ {
+ return ValidateSpellInfo({ SPELL_FIGHT_WYRM });
+ }
+
+ void HandleDummy(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
+ {
+ GetTarget()->CastSpell(static_cast<Unit*>(nullptr), SPELL_FIGHT_WYRM, true);
+ }
+
+ void Register() override
+ {
+ AfterEffectRemove += AuraEffectApplyFn(spell_player_mount_wyrm::HandleDummy, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
+ }
+};
+
+// 60603 - Eject Passenger 1
+class spell_eject_passenger_wild_wyrm : public SpellScript
+{
+ PrepareSpellScript(spell_eject_passenger_wild_wyrm);
+
+ bool Validate(SpellInfo const* /*spellInfo*/) override
+ {
+ return ValidateSpellInfo({ SPELL_FIGHT_WYRM });
+ }
+
+ void HandleScript(SpellEffIndex /*effIndex*/)
+ {
+ GetHitUnit()->RemoveAurasDueToSpell(SPELL_FIGHT_WYRM);
+ }
+
+ void Register() override
+ {
+ OnEffectHitTarget += SpellEffectFn(spell_eject_passenger_wild_wyrm::HandleScript, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
+ }
+};
+
void AddSC_storm_peaks()
{
RegisterCreatureAI(npc_frosthound);
new npc_iron_watcher();
new npc_time_lost_proto_drake();
- new npc_wild_wyrm();
- RegisterSpellScript(spell_q13003_thursting_hodirs_spear_aura);
RegisterSpellScript(spell_q13007_iron_colossus);
new npc_roxi_ramrocket();
new npc_brunnhildar_prisoner();
new npc_freed_protodrake();
new npc_icefang();
- new npc_hyldsmeet_protodrake();
+ RegisterCreatureAI(npc_hyldsmeet_protodrake);
RegisterSpellScript(spell_close_rift_aura);
new npc_vehicle_d16_propelled_delivery();
RegisterSpellScript(spell_q12823_remove_collapsing_cave_aura);
RegisterSpellScript(spell_feed_stormcrest_eagle);
+ RegisterSpellScript(spell_mammoth_explosion);
+ RegisterCreatureAI(npc_wild_wyrm);
+ RegisterSpellScript(spell_grip);
+ RegisterSpellScript(spell_grab_on);
+ RegisterSpellScriptWithArgs(spell_loosen_grip, "spell_thrust_spear", 5);
+ RegisterSpellScriptWithArgs(spell_loosen_grip, "spell_mighty_spear_thrust", 15);
+ RegisterSpellScript(spell_low_health_trigger);
+ RegisterSpellScript(spell_jaws_of_death_claw_swipe_pct_damage);
+ RegisterSpellScript(spell_claw_swipe_check);
+ RegisterSpellScript(spell_fatal_strike);
+ RegisterSpellScript(spell_player_mount_wyrm);
+ RegisterSpellScript(spell_eject_passenger_wild_wyrm);
}
diff --git a/src/server/scripts/OutdoorPvP/OutdoorPvPGH.cpp b/src/server/scripts/OutdoorPvP/OutdoorPvPGH.cpp
index d3e56186bb..7e3f118133 100644
--- a/src/server/scripts/OutdoorPvP/OutdoorPvPGH.cpp
+++ b/src/server/scripts/OutdoorPvP/OutdoorPvPGH.cpp
@@ -45,6 +45,23 @@ void OutdoorPvPGH::SendRemoveWorldStates(Player* player)
player->SendUpdateWorldState(WORLD_STATE_OPVP_GH_UI_SLIDER_N, 0);
}
+void OutdoorPvPGH::HandleKill(Player* killer, Unit* killed)
+{
+ if (!killed->IsPlayer())
+ return;
+
+ if (!killer->isHonorOrXPTarget(killed))
+ return;
+
+ if (killer->GetTeamId() == TEAM_ALLIANCE)
+ if (killer->GetQuestStatus(GH_QUEST_KICK_EM_WHILE_THEYRE_DOWN) == QUEST_STATUS_INCOMPLETE)
+ killer->KilledMonsterCredit(GH_CREATURE_QUEST_BUNNY);
+
+ if (killer->GetTeamId() == TEAM_HORDE)
+ if (killer->GetQuestStatus(GH_QUEST_KEEP_EM_ON_THEIR_HEELS) == QUEST_STATUS_INCOMPLETE)
+ killer->KilledMonsterCredit(GH_CREATURE_QUEST_BUNNY);
+}
+
OPvPCapturePointGH::OPvPCapturePointGH(OutdoorPvP* pvp) : OPvPCapturePoint(pvp)
{
SetCapturePointData(189310, MAP_NORTHREND, 2483.68f, -1873.6f, 10.6877f, -0.104719f, 0.0f, 0.0f, 0.0f, 1.0f);
diff --git a/src/server/scripts/OutdoorPvP/OutdoorPvPGH.h b/src/server/scripts/OutdoorPvP/OutdoorPvPGH.h
index 6b74f97b47..40d41ecae3 100644
--- a/src/server/scripts/OutdoorPvP/OutdoorPvPGH.h
+++ b/src/server/scripts/OutdoorPvP/OutdoorPvPGH.h
@@ -26,6 +26,10 @@ enum OutdoorPvPGHenum
GH_HORDE_DEFENSE_EVENT = 66,
GH_ZONE = 394,
+
+ GH_QUEST_KEEP_EM_ON_THEIR_HEELS = 12284,
+ GH_QUEST_KICK_EM_WHILE_THEYRE_DOWN = 12289,
+ GH_CREATURE_QUEST_BUNNY = 27453,
};
class Unit;
@@ -38,6 +42,7 @@ public:
OutdoorPvPGH();
bool SetupOutdoorPvP() override;
void SendRemoveWorldStates(Player* player) override;
+ void HandleKill(Player* killer, Unit* killed) override;
};
class OPvPCapturePointGH : public OPvPCapturePoint
diff --git a/src/server/scripts/OutdoorPvP/OutdoorPvPNA.cpp b/src/server/scripts/OutdoorPvP/OutdoorPvPNA.cpp
index e5d69d84b7..1fe133ef04 100644
--- a/src/server/scripts/OutdoorPvP/OutdoorPvPNA.cpp
+++ b/src/server/scripts/OutdoorPvP/OutdoorPvPNA.cpp
@@ -673,11 +673,11 @@ bool OPvPCapturePointNA::Update(uint32 diff)
m_RespawnTimer -= diff;
// get the difference of numbers
- float factDiff = ((float)_activePlayers[0].size() - (float)_activePlayers[1].size()) * diff / OUTDOORPVP_OBJECTIVE_UPDATE_INTERVAL;
+ float factDiff = (((float)_activePlayers[0].size() - (float)_activePlayers[1].size()) * diff / OUTDOORPVP_OBJECTIVE_UPDATE_INTERVAL) * sWorld->getFloatConfig(CONFIG_OUTDOOR_PVP_CAPTURE_RATE);
if (!factDiff)
return false;
- float maxDiff = _maxSpeed * diff;
+ float maxDiff = _maxSpeed * diff * sWorld->getFloatConfig(CONFIG_OUTDOOR_PVP_CAPTURE_RATE);
if (factDiff < 0)
{
diff --git a/src/server/scripts/Outland/Auchindoun/SethekkHalls/boss_anzu.cpp b/src/server/scripts/Outland/Auchindoun/SethekkHalls/boss_anzu.cpp
index e557b9eb8f..c201d5a0ad 100644
--- a/src/server/scripts/Outland/Auchindoun/SethekkHalls/boss_anzu.cpp
+++ b/src/server/scripts/Outland/Auchindoun/SethekkHalls/boss_anzu.cpp
@@ -139,7 +139,10 @@ struct boss_anzu : public BossAI
me->CastSpell(me, SPELL_BANISH_SELF, true);
for (uint8 i = 0; i < 5; ++i)
{
- me->SummonCreature(NPC_BROOD_OF_ANZU, me->GetPositionX() + 20 * cos((float)i), me->GetPositionY() + 20 * std::sin((float)i), me->GetPositionZ() + 25.0f, 0.0f, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 30000);
+ Position spawnPos = me->GetNearPosition(20.0f, (float)i);
+ spawnPos.m_positionZ += 25.0f;
+ spawnPos.m_orientation = 0.0f;
+ me->SummonCreature(NPC_BROOD_OF_ANZU, spawnPos, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 30000);
}
}
diff --git a/src/server/scripts/Outland/Auchindoun/SethekkHalls/boss_talon_king_ikiss.cpp b/src/server/scripts/Outland/Auchindoun/SethekkHalls/boss_talon_king_ikiss.cpp
index 6ba67f7297..639f88bd28 100644
--- a/src/server/scripts/Outland/Auchindoun/SethekkHalls/boss_talon_king_ikiss.cpp
+++ b/src/server/scripts/Outland/Auchindoun/SethekkHalls/boss_talon_king_ikiss.cpp
@@ -76,12 +76,6 @@ struct boss_talon_king_ikiss : public BossAI
});
}
- /// @todo: remove this once pets stop going through doors.
- bool CanAIAttack(Unit const* /*victim*/) const override
- {
- return _spoken;
- }
-
void MoveInLineOfSight(Unit* who) override
{
if (!_spoken && who->IsPlayer())
diff --git a/src/server/scripts/Outland/TempestKeep/Eye/boss_astromancer.cpp b/src/server/scripts/Outland/TempestKeep/Eye/boss_astromancer.cpp
index 6cc9c8ae3a..94e300720c 100644
--- a/src/server/scripts/Outland/TempestKeep/Eye/boss_astromancer.cpp
+++ b/src/server/scripts/Outland/TempestKeep/Eye/boss_astromancer.cpp
@@ -271,12 +271,12 @@ class spell_astromancer_solarian_transform : public AuraScript
void OnApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
{
- GetUnitOwner()->HandleStatModifier(UnitMods(UNIT_MOD_ARMOR), TOTAL_PCT, 400.0f, true);
+ GetUnitOwner()->ApplyStatPctModifier(UNIT_MOD_ARMOR, TOTAL_PCT, 400.0f);
}
void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
{
- GetUnitOwner()->HandleStatModifier(UnitMods(UNIT_MOD_ARMOR), TOTAL_PCT, 400.0f, false);
+ GetUnitOwner()->ApplyStatPctModifier(UnitMods(UNIT_MOD_ARMOR), TOTAL_PCT, -80.0f);
}
void Register() override
diff --git a/src/server/scripts/Outland/zone_terokkar_forest.cpp b/src/server/scripts/Outland/zone_terokkar_forest.cpp
index 5ffc172d1e..63f623cd5b 100644
--- a/src/server/scripts/Outland/zone_terokkar_forest.cpp
+++ b/src/server/scripts/Outland/zone_terokkar_forest.cpp
@@ -573,6 +573,46 @@ private:
};
/*######
+## go_ancient_skull_pile
+######*/
+
+enum AncientSkullPile
+{
+ ITEM_TIME_LOST_OFFERING = 32720,
+ SPELL_SUMMON_TEROKK = 41004,
+
+ GOSSIP_MENU_ANCIENT_SKULL_PILE = 8687,
+ GOSSIP_MENU_TEXT_ANCIENT_SKULL_PILE = 11058
+};
+
+class go_ancient_skull_pile : public GameObjectScript
+{
+public:
+ go_ancient_skull_pile() : GameObjectScript("go_ancient_skull_pile") {}
+
+ bool OnGossipSelect(Player* player, GameObject* go, uint32 sender, uint32 /*action*/) override
+ {
+ ClearGossipMenuFor(player);
+
+ if (sender == GOSSIP_SENDER_MAIN)
+ {
+ CloseGossipMenuFor(player);
+ if (player->HasItemCount(ITEM_TIME_LOST_OFFERING, 1))
+ go->DespawnOrUnsummon();
+ player->CastSpell(player, SPELL_SUMMON_TEROKK);
+ }
+ return true;
+ }
+
+ bool OnGossipHello(Player* player, GameObject* go) override
+ {
+ AddGossipItemFor(player, GOSSIP_MENU_ANCIENT_SKULL_PILE, 0, GOSSIP_SENDER_MAIN, 0);
+ SendGossipMenuFor(player, GOSSIP_MENU_TEXT_ANCIENT_SKULL_PILE, go->GetGUID());
+ return true;
+ }
+};
+
+/*######
## npc_slim
######*/
@@ -621,5 +661,6 @@ void AddSC_terokkar_forest()
new npc_unkor_the_ruthless();
new npc_isla_starmane();
new go_skull_pile();
+ new go_ancient_skull_pile();
new npc_slim();
}
diff --git a/src/server/scripts/Pet/pet_generic.cpp b/src/server/scripts/Pet/pet_generic.cpp
index 725f28c6cb..c3a3965d30 100644
--- a/src/server/scripts/Pet/pet_generic.cpp
+++ b/src/server/scripts/Pet/pet_generic.cpp
@@ -821,6 +821,16 @@ struct npc_pet_darting_hatchling : public NullCreatureAI
}
};
+struct npc_pet_proto_drake_whelp : public NullCreatureAI
+{
+ npc_pet_proto_drake_whelp(Creature* c) : NullCreatureAI(c) { }
+
+ void Reset() override
+ {
+ me->SetAnimTier(AnimTier::Fly);
+ }
+};
+
void AddSC_generic_pet_scripts()
{
RegisterCreatureAI(npc_pet_gen_soul_trader_beacon);
@@ -836,4 +846,5 @@ void AddSC_generic_pet_scripts()
RegisterCreatureAI(npc_pet_gen_fetch_ball);
RegisterCreatureAI(npc_pet_gen_moth);
RegisterCreatureAI(npc_pet_darting_hatchling);
+ RegisterCreatureAI(npc_pet_proto_drake_whelp);
}
diff --git a/src/server/scripts/Pet/pet_hunter.cpp b/src/server/scripts/Pet/pet_hunter.cpp
index 3a6d979506..b82180e666 100644
--- a/src/server/scripts/Pet/pet_hunter.cpp
+++ b/src/server/scripts/Pet/pet_hunter.cpp
@@ -99,7 +99,7 @@ struct npc_pet_hunter_snake_trap : public ScriptedAI
uint32 health = uint32(107 * (me->GetLevel() - 40) * 0.025f);
me->SetCreateHealth(health);
- me->SetModifierValue(UNIT_MOD_HEALTH, BASE_VALUE, (float)health);
+ me->SetStatFlatModifier(UNIT_MOD_HEALTH, BASE_VALUE, (float)health);
me->SetMaxHealth(health);
//Add delta to make them not all hit the same time
diff --git a/src/server/scripts/Spells/spell_dk.cpp b/src/server/scripts/Spells/spell_dk.cpp
index ebbbeb520d..36c849bc7b 100644
--- a/src/server/scripts/Spells/spell_dk.cpp
+++ b/src/server/scripts/Spells/spell_dk.cpp
@@ -152,7 +152,7 @@ class spell_dk_raise_ally : public SpellScript
SpellCastResult CheckCast()
{
- Player* unitTarget = GetHitPlayer();
+ Unit* unitTarget = GetExplTargetUnit();
if (!unitTarget)
return SPELL_FAILED_BAD_TARGETS;
@@ -174,8 +174,8 @@ class spell_dk_raise_ally : public SpellScript
if (pInfo) // exist in DB
{
ghoul->SetCreateHealth(pInfo->health);
- ghoul->SetModifierValue(UNIT_MOD_HEALTH, BASE_VALUE, pInfo->health);
- ghoul->SetModifierValue(UNIT_MOD_ARMOR, BASE_VALUE, float(pInfo->armor));
+ ghoul->SetStatFlatModifier(UNIT_MOD_HEALTH, BASE_VALUE, pInfo->health);
+ ghoul->SetStatFlatModifier(UNIT_MOD_ARMOR, BASE_VALUE, float(pInfo->armor));
for (uint8 stat = 0; stat < MAX_STATS; ++stat)
ghoul->SetCreateStat(Stats(stat), float(pInfo->stats[stat]));
}
@@ -194,9 +194,9 @@ class spell_dk_raise_ally : public SpellScript
// DK Ghoul haste refresh
float val = (GetCaster()->m_modAttackSpeedPct[BASE_ATTACK] - 1.0f) * 100.0f;
+ val = 2000.0f * (100.0f + val) / 100.0f;
ghoul->m_modAttackSpeedPct[BASE_ATTACK] = GetCaster()->m_modAttackSpeedPct[BASE_ATTACK];
- ghoul->SetFloatValue(UNIT_FIELD_BASEATTACKTIME, 2000.0f);
- ghoul->ApplyPercentModFloatValue(UNIT_FIELD_BASEATTACKTIME, val, true); // we want to reduce attack time
+ ghoul->SetFloatValue(UNIT_FIELD_BASEATTACKTIME, val);
// Strength + Stamina
for (uint8 i = STAT_STRENGTH; i <= STAT_STAMINA; ++i)
@@ -223,20 +223,20 @@ class spell_dk_raise_ally : public SpellScript
value = float(GetCaster()->GetStat(stat)) * mod;
value = ghoul->GetTotalStatValue(stat, value);
ghoul->SetStat(stat, int32(value));
- ghoul->ApplyStatBuffMod(stat, value, true);
+ ghoul->UpdateStatBuffMod(stat);
}
// Attack Power
- ghoul->SetModifierValue(UNIT_MOD_ATTACK_POWER, BASE_VALUE, 589 + ghoul->GetStat(STAT_STRENGTH) + ghoul->GetStat(STAT_AGILITY));
- ghoul->SetInt32Value(UNIT_FIELD_ATTACK_POWER, (int32)ghoul->GetModifierValue(UNIT_MOD_ATTACK_POWER, BASE_VALUE) * ghoul->GetModifierValue(UNIT_MOD_ATTACK_POWER, BASE_PCT));
- ghoul->SetInt32Value(UNIT_FIELD_ATTACK_POWER_MODS, (int32)ghoul->GetModifierValue(UNIT_MOD_ATTACK_POWER, TOTAL_VALUE));
- ghoul->SetFloatValue(UNIT_FIELD_ATTACK_POWER_MULTIPLIER, ghoul->GetModifierValue(UNIT_MOD_ATTACK_POWER, TOTAL_PCT) - 1.0f);
+ ghoul->SetStatFlatModifier(UNIT_MOD_ATTACK_POWER, BASE_VALUE, 589 + ghoul->GetStat(STAT_STRENGTH) + ghoul->GetStat(STAT_AGILITY));
+ ghoul->SetInt32Value(UNIT_FIELD_ATTACK_POWER, (int32)ghoul->GetFlatModifierValue(UNIT_MOD_ATTACK_POWER, BASE_VALUE) * ghoul->GetPctModifierValue(UNIT_MOD_ATTACK_POWER, BASE_PCT));
+ ghoul->SetInt32Value(UNIT_FIELD_ATTACK_POWER_MODS, (int32)ghoul->GetFlatModifierValue(UNIT_MOD_ATTACK_POWER, TOTAL_VALUE));
+ ghoul->SetFloatValue(UNIT_FIELD_ATTACK_POWER_MULTIPLIER, ghoul->GetPctModifierValue(UNIT_MOD_ATTACK_POWER, TOTAL_PCT) - 1.0f);
// Health
- ghoul->SetModifierValue(UNIT_MOD_HEALTH, TOTAL_VALUE, (ghoul->GetStat(STAT_STAMINA) - ghoul->GetCreateStat(STAT_STAMINA)) * 10.0f);
+ ghoul->SetStatFlatModifier(UNIT_MOD_HEALTH, TOTAL_VALUE, (ghoul->GetStat(STAT_STAMINA) - ghoul->GetCreateStat(STAT_STAMINA)) * 10.0f);
// Power Energy
- ghoul->SetModifierValue(UnitMods(UNIT_MOD_POWER_START + static_cast<uint8>(POWER_ENERGY)), BASE_VALUE, ghoul->GetCreatePowers(POWER_ENERGY));
+ ghoul->SetStatFlatModifier(UnitMods(UNIT_MOD_POWER_START + static_cast<uint8>(POWER_ENERGY)), BASE_VALUE, ghoul->GetCreatePowers(POWER_ENERGY));
ghoul->UpdateAllStats();
ghoul->SetFullHealth();
diff --git a/src/server/scripts/Spells/spell_druid.cpp b/src/server/scripts/Spells/spell_druid.cpp
index c6f90e153e..85a12610da 100644
--- a/src/server/scripts/Spells/spell_druid.cpp
+++ b/src/server/scripts/Spells/spell_druid.cpp
@@ -443,15 +443,16 @@ class spell_dru_enrage : public AuraScript
void RecalculateBaseArmor()
{
+ // Recalculate modifies the list while we're iterating through it, so let's copy it instead
Unit::AuraEffectList const& auras = GetTarget()->GetAuraEffectsByType(SPELL_AURA_MOD_BASE_RESISTANCE_PCT);
- for (Unit::AuraEffectList::const_iterator i = auras.begin(); i != auras.end(); ++i)
+ std::vector<AuraEffect*> aurEffs(auras.begin(), auras.end());
+
+ for (AuraEffect* aurEff : aurEffs)
{
- SpellInfo const* spellInfo = (*i)->GetSpellInfo();
+ SpellInfo const* spellInfo = aurEff->GetSpellInfo();
// Dire- / Bear Form (Passive)
if (spellInfo->SpellFamilyName == SPELLFAMILY_DRUID && spellInfo->SpellFamilyFlags.HasFlag(0x0, 0x0, 0x2))
- {
- (*i)->RecalculateAmount();
- }
+ aurEff->RecalculateAmount();
}
}
diff --git a/src/server/scripts/Spells/spell_generic.cpp b/src/server/scripts/Spells/spell_generic.cpp
index 68ef5e5540..f5ff6e9680 100644
--- a/src/server/scripts/Spells/spell_generic.cpp
+++ b/src/server/scripts/Spells/spell_generic.cpp
@@ -2028,7 +2028,8 @@ class spell_gen_animal_blood : public AuraScript
return;
if (Unit* owner = GetUnitOwner())
- owner->CastSpell(owner, SPELL_SPAWN_BLOOD_POOL, true);
+ if (owner->IsInWater())
+ owner->CastSpell(owner, SPELL_SPAWN_BLOOD_POOL, true);
}
void Register() override
@@ -3658,7 +3659,6 @@ class spell_gen_ds_flush_knockback : public SpellScript
56698 - Shadow Blast (spell_gen_default_count_pct_from_max_hp)
59102 - Shadow Blast (spell_gen_default_count_pct_from_max_hp)
60532 - Heart Explosion Effects (spell_gen_default_count_pct_from_max_hp)
- 60864 - Jaws of Death (spell_gen_default_count_pct_from_max_hp)
38441 - Cataclysmic Bolt (spell_gen_50pct_count_pct_from_max_hp)
66316, 67100, 67101, 67102 - Spinning Pain Spike (spell_gen_50pct_count_pct_from_max_hp)
33711/38794 - Murmur's Touch
@@ -4423,7 +4423,9 @@ class spell_gen_eject_all_passengers : public SpellScript
}
};
-/* 62539 - Eject Passenger 2
+/* 49259 - Despawn Driver
+ 49261 - Dismount Passenger
+ 62539 - Eject Passenger 2
64614 - Eject Passenger 4
64629 - Eject Passenger 1
64630 - Eject Passenger 2
@@ -5671,6 +5673,29 @@ class spell_gen_bm_on : public SpellScript
}
};
+class spell_gen_whisper_to_controller : public SpellScript
+{
+ PrepareSpellScript(spell_gen_whisper_to_controller);
+
+ bool Validate(SpellInfo const* spellInfo) override
+ {
+ return sObjectMgr->GetBroadcastText(uint32(spellInfo->GetEffect(EFFECT_0).CalcValue()));
+ }
+
+ void HandleScript(SpellEffIndex /*effIndex*/)
+ {
+ if (Unit* caster = GetCaster())
+ if (TempSummon* casterSummon = caster->ToTempSummon())
+ if (Player* target = casterSummon->GetSummonerUnit()->ToPlayer())
+ casterSummon->Unit::Whisper(uint32(GetEffectValue()), target, false);
+ }
+
+ void Register() override
+ {
+ OnEffectHit += SpellEffectFn(spell_gen_whisper_to_controller::HandleScript, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
+ }
+};
+
void AddSC_generic_spell_scripts()
{
RegisterSpellScript(spell_silithyst);
@@ -5775,6 +5800,7 @@ void AddSC_generic_spell_scripts()
RegisterSpellScriptWithArgs(spell_gen_count_pct_from_max_hp, "spell_gen_default_count_pct_from_max_hp");
RegisterSpellScriptWithArgs(spell_gen_count_pct_from_max_hp, "spell_gen_10pct_count_pct_from_max_hp", 10);
RegisterSpellScriptWithArgs(spell_gen_count_pct_from_max_hp, "spell_gen_50pct_count_pct_from_max_hp", 50);
+ RegisterSpellScriptWithArgs(spell_gen_count_pct_from_max_hp, "spell_gen_26pct_count_pct_from_max_hp", 26);
RegisterSpellScript(spell_gen_despawn_self);
RegisterSpellScript(spell_gen_bandage);
RegisterSpellScript(spell_gen_paralytic_poison);
@@ -5844,4 +5870,5 @@ void AddSC_generic_spell_scripts()
RegisterSpellScript(spell_gen_invis_on);
RegisterSpellScript(spell_gen_bm_on);
RegisterSpellScript(spell_gen_bm_off);
+ RegisterSpellScript(spell_gen_whisper_to_controller);
}
diff --git a/src/server/scripts/Spells/spell_paladin.cpp b/src/server/scripts/Spells/spell_paladin.cpp
index f9cdd76231..0acf87cb21 100644
--- a/src/server/scripts/Spells/spell_paladin.cpp
+++ b/src/server/scripts/Spells/spell_paladin.cpp
@@ -473,11 +473,7 @@ class spell_pal_blessing_of_sanctuary : public AuraScript
{
Unit* target = GetTarget();
if (Unit* caster = GetCaster())
- {
- // xinef: hack
- int32 value = 9;
- caster->CastCustomSpell(target, SPELL_PALADIN_BLESSING_OF_SANCTUARY_BUFF, &value, &value, 0, true);
- }
+ caster->CastSpell(target, SPELL_PALADIN_BLESSING_OF_SANCTUARY_BUFF, true);
}
void HandleEffectRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
@@ -965,6 +961,24 @@ class spell_pal_lay_on_hands : public SpellScript
return true;
}
+ void HandleMaxHealthHeal(SpellEffIndex /*effIndex*/)
+ {
+ Unit* caster = GetCaster();
+ Unit* target = GetExplTargetUnit();
+
+ if (!target || !caster)
+ return;
+
+ uint32 baseHeal = caster->GetMaxHealth();
+ uint32 modifiedHeal = target->SpellHealingBonusTaken(caster, GetSpellInfo(), baseHeal, HEAL);
+
+ // EffectHealMaxHealth() ignores healing modifiers, so we pre-apply the
+ // difference here; this delta will be added on top of the raw heal.
+ int64 healAdjustment = int64(modifiedHeal) - int64(baseHeal);
+
+ SetHitHeal(healAdjustment);
+ }
+
SpellCastResult CheckCast()
{
Unit* caster = GetCaster();
@@ -1004,6 +1018,7 @@ class spell_pal_lay_on_hands : public SpellScript
{
OnCheckCast += SpellCheckCastFn(spell_pal_lay_on_hands::CheckCast);
AfterHit += SpellHitFn(spell_pal_lay_on_hands::HandleScript);
+ OnEffectHitTarget += SpellEffectFn(spell_pal_lay_on_hands::HandleMaxHealthHeal, EFFECT_0, SPELL_EFFECT_HEAL_MAX_HEALTH);
}
int32 _manaAmount;
diff --git a/src/server/scripts/Spells/spell_quest.cpp b/src/server/scripts/Spells/spell_quest.cpp
index 870cc73850..4e1b563333 100644
--- a/src/server/scripts/Spells/spell_quest.cpp
+++ b/src/server/scripts/Spells/spell_quest.cpp
@@ -247,28 +247,6 @@ class spell_q10525_vision_guide : public AuraScript
}
};
-class spell_q11322_q11317_the_cleansing : public AuraScript
-{
- PrepareAuraScript(spell_q11322_q11317_the_cleansing)
-
- void HandleEffectApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
- {
- Unit* ar = GetCaster();
- if (ar && ar->ToPlayer())
- {
- if (ar->ToPlayer()->GetQuestStatus(11317) == QUEST_STATUS_INCOMPLETE || ar->ToPlayer()->GetQuestStatus(11322) == QUEST_STATUS_INCOMPLETE)
- ar->SummonCreature(27959, 3032.0f, -5095.0f, 723.0f, 0.0f, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 60000);
-
- ar->SetStandState(UNIT_STAND_STATE_SIT);
- }
- }
-
- void Register() override
- {
- OnEffectApply += AuraEffectApplyFn(spell_q11322_q11317_the_cleansing::HandleEffectApply, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
- }
-};
-
class spell_q10714_on_spirits_wings : public SpellScript
{
PrepareSpellScript(spell_q10714_on_spirits_wings);
@@ -2499,7 +2477,6 @@ void AddSC_quest_spell_scripts()
RegisterSpellScript(spell_q12014_steady_as_a_rock);
RegisterSpellAndAuraScriptPair(spell_q11026_a11051_banish_the_demons, spell_q11026_a11051_banish_the_demons_aura);
RegisterSpellScript(spell_q10525_vision_guide);
- RegisterSpellScript(spell_q11322_q11317_the_cleansing);
RegisterSpellScript(spell_q10714_on_spirits_wings);
RegisterSpellScript(spell_q10720_the_smallest_creature);
RegisterSpellScript(spell_q13086_last_line_of_defence);
diff --git a/src/server/scripts/Spells/spell_warlock.cpp b/src/server/scripts/Spells/spell_warlock.cpp
index d6a6a3d8cf..e484b2b333 100644
--- a/src/server/scripts/Spells/spell_warlock.cpp
+++ b/src/server/scripts/Spells/spell_warlock.cpp
@@ -18,6 +18,7 @@
#include "AreaDefines.h"
#include "CreatureScript.h"
#include "Pet.h"
+#include "PetDefines.h"
#include "Player.h"
#include "SpellAuraEffects.h"
#include "SpellInfo.h"
@@ -25,6 +26,8 @@
#include "SpellScript.h"
#include "SpellScriptLoader.h"
#include "TemporarySummon.h"
+#include "Unit.h"
+#include "Util.h"
/*
* Scripts for spells with SPELLFAMILY_WARLOCK and SPELLFAMILY_GENERIC spells used by warlock players.
* Ordered alphabetically using scriptname.
@@ -73,6 +76,7 @@ enum WarlockSpells
SPELL_WARLOCK_EYE_OF_KILROGG_FLY = 58083,
SPELL_WARLOCK_PET_VOID_STAR_TALISMAN = 37386, // Void Star Talisman
SPELL_WARLOCK_DEMONIC_PACT_PROC = 48090,
+ SPELL_WARLOCK_GLYPH_OF_VOIDWALKER = 56247,
};
enum WarlockSpellIcons
@@ -292,7 +296,7 @@ class spell_warl_generic_scaling : public AuraScript
void CalculateResistanceAmount(AuraEffect const* aurEff, int32& amount, bool& /*canBeRecalculated*/)
{
- // xinef: pet inherits 40% of resistance from owner and 35% of armor
+ // pet inherits 40% of resistance from owner and 35% of armor
if (Unit* owner = GetUnitOwner()->GetOwner())
{
SpellSchoolMask schoolMask = SpellSchoolMask(aurEff->GetSpellInfo()->Effects[aurEff->GetEffIndex()].MiscValue);
@@ -308,7 +312,7 @@ class spell_warl_generic_scaling : public AuraScript
void CalculateStatAmount(AuraEffect const* aurEff, int32& amount, bool& /*canBeRecalculated*/)
{
- // xinef: by default warlock pet inherits 75% of stamina and 30% of intellect
+ // by default warlock pet inherits 75% of stamina and 30% of intellect
if (Unit* owner = GetUnitOwner()->GetOwner())
{
Stats stat = Stats(aurEff->GetSpellInfo()->Effects[aurEff->GetEffIndex()].MiscValue);
@@ -317,21 +321,33 @@ class spell_warl_generic_scaling : public AuraScript
}
}
- void CalculateAPAmount(AuraEffect const* /*aurEff*/, int32& amount, bool& /*canBeRecalculated*/)
+ void CalculateAPAmount(AuraEffect const* aurEff, int32& amount, bool& /*canBeRecalculated*/)
{
- // xinef: by default warlock pet inherits 57% of max(SP FIRE, SP SHADOW) as AP
- if (Unit* owner = GetUnitOwner()->GetOwner())
+ if (Unit* pet = GetUnitOwner())
{
- int32 fire = owner->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_FIRE);
- int32 shadow = owner->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_SHADOW);
- int32 maximum = (fire > shadow) ? fire : shadow;
- amount = CalculatePct(std::max<int32>(0, maximum), 57);
+ // by default warlock pet inherits 57% of max(SP FIRE, SP SHADOW) as AP
+ if (Unit* owner = pet->GetOwner())
+ {
+ int32 fire = owner->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_FIRE);
+ int32 shadow = owner->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_SHADOW);
+ int32 maximum = (fire > shadow) ? fire : shadow;
+ amount = CalculatePct(std::max<int32>(0, maximum), 57);
+
+ // Glyph of felguard, 99% sure this is a HACK
+ if (pet->GetEntry() == NPC_FELGUARD)
+ {
+ if (AuraEffect* glyph = owner->GetAuraEffect(SPELL_GLYPH_OF_FELGUARD, EFFECT_0))
+ {
+ amount += CalculatePct(pet->GetTotalAuraModValue(UNIT_MOD_ATTACK_POWER) - aurEff->GetAmount() + amount, glyph->GetAmount());
+ }
+ }
+ }
}
}
void CalculateSPAmount(AuraEffect const* /*aurEff*/, int32& amount, bool& /*canBeRecalculated*/)
{
- // xinef: by default warlock pet inherits 15% of max(SP FIRE, SP SHADOW) as SP
+ // by default warlock pet inherits 15% of max(SP FIRE, SP SHADOW) as SP
if (Unit* owner = GetUnitOwner()->GetOwner())
{
int32 fire = owner->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_FIRE);
@@ -339,7 +355,7 @@ class spell_warl_generic_scaling : public AuraScript
int32 maximum = (fire > shadow) ? fire : shadow;
amount = CalculatePct(std::max<int32>(0, maximum), 15);
- // xinef: Update appropriate player field
+ // Update appropriate player field
if (owner->IsPlayer())
owner->SetUInt32Value(PLAYER_PET_SPELL_POWER, (uint32)amount);
}
@@ -1370,81 +1386,27 @@ class spell_warl_shadowburn : public AuraScript
}
};
-class spell_warl_glyph_of_felguard : public AuraScript
+class spell_warl_voidwalker_pet_passive : public AuraScript
{
- PrepareAuraScript(spell_warl_glyph_of_felguard);
+ PrepareAuraScript(spell_warl_voidwalker_pet_passive);
- void HandleApply(AuraEffect const* aurEff, AuraEffectHandleModes /*mode*/)
- {
- if (Player* player = GetCaster()->ToPlayer())
- {
- if (Pet* pet = player->GetPet())
- {
- if (pet->GetEntry() == NPC_FELGUARD)
- {
- pet->HandleStatModifier(UNIT_MOD_ATTACK_POWER, TOTAL_PCT, aurEff->GetAmount(), true);
- }
- }
- }
- }
-
- void HandleRemove(AuraEffect const* aurEff, AuraEffectHandleModes /*mode*/)
- {
- if (Player* player = GetCaster()->ToPlayer())
- {
- if (Pet* pet = player->GetPet())
- {
- if (pet->GetEntry() == NPC_FELGUARD)
- {
- pet->HandleStatModifier(UNIT_MOD_ATTACK_POWER, TOTAL_PCT, aurEff->GetAmount(), false);
- }
- }
- }
- }
-
- void Register() override
- {
- OnEffectApply += AuraEffectApplyFn(spell_warl_glyph_of_felguard::HandleApply, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
- OnEffectRemove += AuraEffectRemoveFn(spell_warl_glyph_of_felguard::HandleRemove, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
- }
-};
-
-class spell_warl_glyph_of_voidwalker : public AuraScript
-{
- PrepareAuraScript(spell_warl_glyph_of_voidwalker);
-
- void HandleApply(AuraEffect const* aurEff, AuraEffectHandleModes /*mode*/)
+ bool Validate(SpellInfo const* /*spellInfo*/) override
{
- if (Player* player = GetCaster()->ToPlayer())
- {
- if (Pet* pet = player->GetPet())
- {
- if (pet->GetEntry() == NPC_VOIDWALKER)
- {
- pet->HandleStatModifier(UNIT_MOD_STAT_STAMINA, TOTAL_PCT, aurEff->GetAmount(), true);
- }
- }
- }
+ return ValidateSpellInfo({ SPELL_WARLOCK_GLYPH_OF_VOIDWALKER });
}
- void HandleRemove(AuraEffect const* aurEff, AuraEffectHandleModes /*mode*/)
+ void CalculateAmount(AuraEffect const* /* aurEff */, int32& amount, bool& /*canBeRecalculated*/)
{
- if (Player* player = GetCaster()->ToPlayer())
- {
- if (Pet* pet = player->GetPet())
- {
- if (pet->GetEntry() == NPC_VOIDWALKER)
- {
- pet->HandleStatModifier(UNIT_MOD_STAT_STAMINA, TOTAL_PCT, aurEff->GetAmount(), false);
- }
- }
- }
+ if (Unit* pet = GetUnitOwner())
+ if (pet->IsPet())
+ if (Unit* owner = pet->ToPet()->GetOwner())
+ if (AuraEffect* aurEff = owner->GetAuraEffect(SPELL_WARLOCK_GLYPH_OF_VOIDWALKER, EFFECT_0))
+ amount += aurEff->GetAmount();
}
void Register() override
{
- OnEffectApply += AuraEffectApplyFn(spell_warl_glyph_of_voidwalker::HandleApply, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
- OnEffectRemove += AuraEffectRemoveFn(spell_warl_glyph_of_voidwalker::HandleRemove, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
+ DoEffectCalcAmount += AuraEffectCalcAmountFn(spell_warl_voidwalker_pet_passive::CalculateAmount, EFFECT_0, SPELL_AURA_MOD_TOTAL_STAT_PERCENTAGE);
}
};
@@ -1529,7 +1491,6 @@ void AddSC_warlock_spell_scripts()
RegisterSpellScript(spell_warl_unstable_affliction);
RegisterSpellScript(spell_warl_drain_soul);
RegisterSpellScript(spell_warl_shadowburn);
- RegisterSpellScript(spell_warl_glyph_of_felguard);
- RegisterSpellScript(spell_warl_glyph_of_voidwalker);
+ RegisterSpellScript(spell_warl_voidwalker_pet_passive);
RegisterSpellScript(spell_warl_demonic_pact_aura);
}
diff --git a/src/server/scripts/World/achievement_scripts.cpp b/src/server/scripts/World/achievement_scripts.cpp
index eebd7cac89..2d7eed6cdb 100644
--- a/src/server/scripts/World/achievement_scripts.cpp
+++ b/src/server/scripts/World/achievement_scripts.cpp
@@ -265,6 +265,23 @@ class achievement_flirt_with_disaster_perf_check : public AchievementCriteriaScr
}
};
+enum FaLaLaLaOgrila
+{
+ SPELL_FRESH_HOLLY = 44824,
+ SPELL_PRESERVED_HOLLY = 62061,
+};
+
+class achievement_fa_la_la_la_ogrila : public AchievementCriteriaScript
+{
+public:
+ achievement_fa_la_la_la_ogrila() : AchievementCriteriaScript("achievement_fa_la_la_la_ogrila") {}
+
+ bool OnCheck(Player* player, Unit* /*target*/, uint32 /*criteria_id*/) override
+ {
+ return player->HasAura(SPELL_FRESH_HOLLY) || player->HasAura(SPELL_PRESERVED_HOLLY);
+ }
+};
+
void AddSC_achievement_scripts()
{
new achievement_resilient_victory();
@@ -285,4 +302,5 @@ void AddSC_achievement_scripts()
new achievement_not_even_a_scratch();
new achievement_killed_exp_or_honor_target();
new achievement_flirt_with_disaster_perf_check();
+ new achievement_fa_la_la_la_ogrila();
}
diff --git a/src/server/scripts/World/go_scripts.cpp b/src/server/scripts/World/go_scripts.cpp
index fa258e37bd..9242761b43 100644
--- a/src/server/scripts/World/go_scripts.cpp
+++ b/src/server/scripts/World/go_scripts.cpp
@@ -41,25 +41,6 @@ public:
}
};
-class go_mistwhisper_treasure : public GameObjectScript
-{
-public:
- go_mistwhisper_treasure() : GameObjectScript("go_mistwhisper_treasure") { }
-
- bool OnGossipHello(Player* pPlayer, GameObject* go) override
- {
- if (!go->FindNearestCreature(28105, 30.0f)) // Tartek
- {
- if (Creature* cr = go->SummonCreature(28105, 6708.7f, 5115.45f, -18.3f, 0.7f, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 30000))
- {
- cr->Yell("My treasure! You no steal from Tartek, dumb big-tongue traitor thing. Tartek and nasty dragon going to kill you! You so dumb.", LANG_UNIVERSAL);
- cr->AI()->AttackStart(pPlayer);
- }
- }
- return false;
- }
-};
-
class go_witherbark_totem_bundle : public GameObjectScript
{
public:
@@ -1896,7 +1877,6 @@ public:
void AddSC_go_scripts()
{
new go_seer_of_zebhalak();
- new go_mistwhisper_treasure();
new go_witherbark_totem_bundle();
new go_arena_ready_marker();
new go_ethereum_prison();
diff --git a/src/server/scripts/World/npc_professions.cpp b/src/server/scripts/World/npc_professions.cpp
index afda87fdaf..b0d57fa759 100644
--- a/src/server/scripts/World/npc_professions.cpp
+++ b/src/server/scripts/World/npc_professions.cpp
@@ -514,7 +514,7 @@ public:
player->GetSession()->SendListInventory(creature->GetGUID());
break;
case GOSSIP_ACTION_TRAIN:
- player->GetSession()->SendTrainerList(creature->GetGUID());
+ player->GetSession()->SendTrainerList(creature);
break;
//Learn Alchemy
case GOSSIP_ACTION_INFO_DEF + 1:
@@ -707,7 +707,7 @@ public:
player->GetSession()->SendListInventory(creature->GetGUID());
break;
case GOSSIP_ACTION_TRAIN:
- player->GetSession()->SendTrainerList(creature->GetGUID());
+ player->GetSession()->SendTrainerList(creature);
break;
//Learn Armor/Weapon
case GOSSIP_ACTION_INFO_DEF + 1:
@@ -1019,7 +1019,7 @@ public:
switch (action)
{
case GOSSIP_ACTION_TRAIN:
- player->GetSession()->SendTrainerList(creature->GetGUID());
+ player->GetSession()->SendTrainerList(creature);
break;
case GOSSIP_MENU_UNLEARN_CONFIRM_DRAGONSCALE:
AddGossipItemFor(player, GOSSIP_MENU_UNLEARN_CONFIRM_DRAGONSCALE, GOSSIP_MENU_OPTION_CONFIRM_UNLEARN_DRAGONSCALE, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1, DoMedUnlearnCost(player));
@@ -1121,7 +1121,7 @@ public:
player->GetSession()->SendListInventory(creature->GetGUID());
break;
case GOSSIP_ACTION_TRAIN:
- player->GetSession()->SendTrainerList(creature->GetGUID());
+ player->GetSession()->SendTrainerList(creature);
break;
//Learn Tailor
case GOSSIP_ACTION_INFO_DEF + 1:
diff --git a/src/server/scripts/World/npcs_special.cpp b/src/server/scripts/World/npcs_special.cpp
index 23b46b039d..fa120635bd 100644
--- a/src/server/scripts/World/npcs_special.cpp
+++ b/src/server/scripts/World/npcs_special.cpp
@@ -563,10 +563,6 @@ public:
if (!SpawnAssoc)
return;
- // check if they're hostile
- if (!(me->IsHostileTo(who) || who->IsHostileTo(me)))
- return;
-
if (me->IsValidAttackTarget(who))
{
Player* playerTarget = who->ToPlayer();
@@ -2683,6 +2679,60 @@ struct npc_controller : public PossessedAI
}
};
+enum TravelerMammothVendor
+{
+ SAY_DISMISS = 0,
+};
+
+struct npc_traveler_mammoth_vendor : public ScriptedAI
+{
+ npc_traveler_mammoth_vendor(Creature* creature) : ScriptedAI(creature) { }
+
+ bool _hasEjected = false;
+ ObjectGuid _playerGuid;
+
+ void Reset() override
+ {
+ _hasEjected = false;
+ _playerGuid.Clear();
+
+ if (sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_GROUP) && !me->GetMap()->IsBattlegroundOrArena())
+ me->SetFaction(FACTION_FRIENDLY);
+
+ me->m_Events.KillAllEvents(false);
+ }
+
+ void UpdateAI(uint32 /*diff*/) override
+ {
+ if (_playerGuid.IsEmpty() && me->GetVehicle())
+ {
+ _playerGuid = me->GetVehicleBase()->GetGUID();
+ }
+ if (!me->GetVehicle() && !_hasEjected)
+ {
+ _hasEjected = true;
+
+ me->m_Events.AddEventAtOffset([this] {
+ if (Unit* driver = ObjectAccessor::GetUnit(*me, _playerGuid))
+ me->SetFacingToObject(driver);
+ }, 2500ms);
+
+ me->m_Events.AddEventAtOffset([this] {
+ me->RemoveNpcFlag(UNIT_NPC_FLAG_GOSSIP | UNIT_NPC_FLAG_VENDOR_MASK);
+ }, 3300ms);
+
+ me->m_Events.AddEventAtOffset([this] {
+ Talk(SAY_DISMISS);
+ me->HandleEmoteCommand(EMOTE_ONESHOT_TALK);
+ }, 4100ms);
+
+ me->m_Events.AddEventAtOffset([this] {
+ me->DespawnOrUnsummon();
+ }, 10200ms);
+ }
+ }
+};
+
void AddSC_npcs_special()
{
new npc_elder_clearwater();
@@ -2709,4 +2759,5 @@ void AddSC_npcs_special()
RegisterCreatureAI(npc_arcanite_dragonling);
RegisterCreatureAI(npc_crashin_thrashin_robot);
RegisterCreatureAI(npc_controller);
+ RegisterCreatureAI(npc_traveler_mammoth_vendor);
}
diff --git a/src/server/shared/DataStores/DBCStructure.h b/src/server/shared/DataStores/DBCStructure.h
index 59ebe9b57f..4e7e8ca2cd 100644
--- a/src/server/shared/DataStores/DBCStructure.h
+++ b/src/server/shared/DataStores/DBCStructure.h
@@ -1208,8 +1208,7 @@ struct ItemRandomPropertiesEntry
{
uint32 ID; // 0
//char const* InternalName; // 1
- std::array<uint32, MAX_ITEM_ENCHANTMENT_EFFECTS> Enchantment; // 2-4
- //std::array<uint32, 2> UnusedEnchantment; // 5-6
+ std::array<uint32, MAX_ITEM_ENCHANTMENT_EFFECTS> Enchantment; // 2-6
std::array<char const*, 16> Name; // 7-22
//uint32 Name_lang_mask; // 23
};
@@ -1220,10 +1219,8 @@ struct ItemRandomSuffixEntry
std::array<char const*, 16> Name; // 1-16
//uint32 Name_lang_mask; // 17
//char const* InternalName; // 18
- std::array<uint32, MAX_ITEM_ENCHANTMENT_EFFECTS> Enchantment; // 19-21
- //std::array<uint32, 2> UnusedEnchantment; // 22-23
- std::array<uint32, MAX_ITEM_ENCHANTMENT_EFFECTS> AllocationPct; // 24-26
- //std::array<uint32, 2> UnusedAllocationPct; // 27-28
+ std::array<uint32, MAX_ITEM_ENCHANTMENT_EFFECTS> Enchantment; // 19-23
+ std::array<uint32, MAX_ITEM_ENCHANTMENT_EFFECTS> AllocationPct; // 24-28
};
#define MAX_ITEM_SET_ITEMS 10
diff --git a/src/server/shared/Network/AsyncAcceptor.h b/src/server/shared/Network/AsyncAcceptor.h
index f91c2ca37e..71c58ed937 100644
--- a/src/server/shared/Network/AsyncAcceptor.h
+++ b/src/server/shared/Network/AsyncAcceptor.h
@@ -20,6 +20,7 @@
#include "IpAddress.h"
#include "Log.h"
+#include "Systemd.h"
#include <atomic>
#include <boost/asio/ip/tcp.hpp>
#include <functional>
@@ -33,10 +34,20 @@ class AsyncAcceptor
public:
typedef void(*AcceptCallback)(tcp::socket&& newSocket, uint32 threadIndex);
- AsyncAcceptor(Acore::Asio::IoContext& ioContext, std::string const& bindIp, uint16 port) :
+ AsyncAcceptor(Acore::Asio::IoContext& ioContext, std::string const& bindIp, uint16 port, bool supportSocketActivation = false) :
_acceptor(ioContext), _endpoint(Acore::Net::make_address(bindIp), port),
- _socket(ioContext), _closed(false), _socketFactory([this](){ return DefaultSocketFactory(); })
+ _socket(ioContext), _closed(false), _socketFactory([this](){ return DefaultSocketFactory(); }),
+ _supportSocketActivation(supportSocketActivation)
{
+ int const listen_fd = get_listen_fd();
+ if (_supportSocketActivation && listen_fd > 0)
+ {
+ LOG_DEBUG("network", "Using socket from systemd socket activation");
+ boost::system::error_code errorCode;
+ _acceptor.assign(boost::asio::ip::tcp::v4(), listen_fd, errorCode);
+ if (errorCode)
+ LOG_WARN("network", "Failed to assign socket {}", errorCode.message());
+ }
}
template<class T>
@@ -72,27 +83,31 @@ public:
bool Bind()
{
boost::system::error_code errorCode;
- _acceptor.open(_endpoint.protocol(), errorCode);
- if (errorCode)
+ // with socket activation the acceptor is already open and bound
+ if (!_acceptor.is_open())
{
- LOG_INFO("network", "Failed to open acceptor {}", errorCode.message());
- return false;
- }
+ _acceptor.open(_endpoint.protocol(), errorCode);
+ if (errorCode)
+ {
+ LOG_INFO("network", "Failed to open acceptor {}", errorCode.message());
+ return false;
+ }
#if AC_PLATFORM != AC_PLATFORM_WINDOWS
- _acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true), errorCode);
- if (errorCode)
- {
- LOG_INFO("network", "Failed to set reuse_address option on acceptor {}", errorCode.message());
- return false;
- }
+ _acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true), errorCode);
+ if (errorCode)
+ {
+ LOG_INFO("network", "Failed to set reuse_address option on acceptor {}", errorCode.message());
+ return false;
+ }
#endif
- _acceptor.bind(_endpoint, errorCode);
- if (errorCode)
- {
- LOG_INFO("network", "Could not bind to {}:{} {}", _endpoint.address().to_string(), _endpoint.port(), errorCode.message());
- return false;
+ _acceptor.bind(_endpoint, errorCode);
+ if (errorCode)
+ {
+ LOG_INFO("network", "Could not bind to {}:{} {}", _endpoint.address().to_string(), _endpoint.port(), errorCode.message());
+ return false;
+ }
}
_acceptor.listen(ACORE_MAX_LISTEN_CONNECTIONS, errorCode);
@@ -124,6 +139,7 @@ private:
tcp::socket _socket;
std::atomic<bool> _closed;
std::function<std::pair<tcp::socket*, uint32>()> _socketFactory;
+ bool _supportSocketActivation;
};
template<class T>
diff --git a/src/server/shared/Network/Socket.h b/src/server/shared/Network/Socket.h
index c4f62807cd..c4ff85c301 100644
--- a/src/server/shared/Network/Socket.h
+++ b/src/server/shared/Network/Socket.h
@@ -188,8 +188,10 @@ protected:
_socket.async_write_some(boost::asio::buffer(buffer.GetReadPointer(), buffer.GetActiveSize()), std::bind(&Socket<T>::WriteHandler,
this->shared_from_this(), std::placeholders::_1, std::placeholders::_2));
#else
- _socket.async_write_some(boost::asio::null_buffers(), std::bind(&Socket<T>::WriteHandlerWrapper,
- this->shared_from_this(), std::placeholders::_1, std::placeholders::_2));
+ _socket.async_wait(tcp::socket::wait_write, [self = this->shared_from_this()](boost::system::error_code error)
+ {
+ self->WriteHandlerWrapper(error, 0);
+ });
#endif
return false;
}
diff --git a/src/server/shared/Network/SocketMgr.h b/src/server/shared/Network/SocketMgr.h
index dc0c5e6f5f..085e4e2380 100644
--- a/src/server/shared/Network/SocketMgr.h
+++ b/src/server/shared/Network/SocketMgr.h
@@ -19,6 +19,7 @@
#define SocketMgr_h__
#include "AsyncAcceptor.h"
+#include "Config.h"
#include "Errors.h"
#include "NetworkThread.h"
#include <boost/asio/ip/tcp.hpp>
@@ -42,7 +43,8 @@ public:
std::unique_ptr<AsyncAcceptor> acceptor;
try
{
- acceptor = std::make_unique<AsyncAcceptor>(ioContext, bindIp, port);
+ bool supportSocketActivation = sConfigMgr->GetOption<bool>("Network.UseSocketActivation", false);
+ acceptor = std::make_unique<AsyncAcceptor>(ioContext, bindIp, port, supportSocketActivation);
}
catch (boost::system::system_error const& err)
{
diff --git a/src/server/shared/SharedDefines.h b/src/server/shared/SharedDefines.h
index bfa5a699bb..b5a979dead 100644
--- a/src/server/shared/SharedDefines.h
+++ b/src/server/shared/SharedDefines.h
@@ -2623,16 +2623,6 @@ enum LockType
LOCKTYPE_OPEN_FROM_VEHICLE = 21
};
-enum TrainerType // this is important type for npcs!
-{
- TRAINER_TYPE_CLASS = 0,
- TRAINER_TYPE_MOUNTS = 1, // on blizz it's 2
- TRAINER_TYPE_TRADESKILLS = 2,
- TRAINER_TYPE_PETS = 3
-};
-
-#define MAX_TRAINER_TYPE 4
-
// CreatureType.dbc
enum CreatureType
{