diff options
Diffstat (limited to 'src/server')
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 { |
