aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/common/Debugging/Errors.cpp11
-rw-r--r--src/common/Debugging/Errors.h4
-rw-r--r--src/server/database/Database/DatabaseWorkerPool.h2
-rw-r--r--src/server/game/Battlegrounds/BattlegroundQueue.cpp2
-rw-r--r--src/server/game/Chat/Chat.cpp2
-rw-r--r--src/server/game/Conditions/ConditionMgr.cpp4
-rw-r--r--src/server/game/Entities/Creature/Creature.cpp13
-rw-r--r--src/server/game/Entities/Creature/Creature.h2
-rw-r--r--src/server/game/Entities/Item/Item.cpp1
-rw-r--r--src/server/game/Entities/Player/KillRewarder.cpp274
-rw-r--r--src/server/game/Entities/Player/KillRewarder.h59
-rw-r--r--src/server/game/Entities/Player/Player.cpp379
-rw-r--r--src/server/game/Entities/Player/Player.h103
-rw-r--r--src/server/game/Entities/Player/TradeData.cpp139
-rw-r--r--src/server/game/Entities/Player/TradeData.h81
-rw-r--r--src/server/game/Globals/ObjectMgr.cpp24
-rw-r--r--src/server/game/Handlers/TradeHandler.cpp5
-rw-r--r--src/server/game/Movement/MovementGenerators/HomeMovementGenerator.cpp2
-rw-r--r--src/server/game/Spells/Spell.cpp1
-rw-r--r--src/server/game/Spells/SpellHistory.cpp136
-rw-r--r--src/server/game/Spells/SpellHistory.h3
-rw-r--r--src/server/game/World/World.cpp1
-rw-r--r--src/server/game/World/World.h1
-rw-r--r--src/server/scripts/Northrend/IcecrownCitadel/boss_valithria_dreamwalker.cpp4
-rw-r--r--src/server/scripts/Northrend/Naxxramas/boss_thaddius.cpp1368
-rw-r--r--src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp38
-rw-r--r--src/server/scripts/Northrend/Naxxramas/naxxramas.h9
-rw-r--r--src/server/scripts/Spells/spell_dk.cpp10
-rw-r--r--src/server/scripts/World/duel_reset.cpp79
-rw-r--r--src/server/worldserver/worldserver.conf.dist13
30 files changed, 1862 insertions, 908 deletions
diff --git a/src/common/Debugging/Errors.cpp b/src/common/Debugging/Errors.cpp
index 45f130ceb3b..4c7e91a8219 100644
--- a/src/common/Debugging/Errors.cpp
+++ b/src/common/Debugging/Errors.cpp
@@ -59,10 +59,15 @@ void Assert(char const* file, int line, char const* function, char const* messag
exit(1);
}
-void Fatal(char const* file, int line, char const* function, char const* message)
+void Fatal(char const* file, int line, char const* function, char const* message, ...)
{
- fprintf(stderr, "\n%s:%i in %s FATAL ERROR:\n %s\n",
- file, line, function, message);
+ va_list args;
+ va_start(args, message);
+
+ fprintf(stderr, "\n%s:%i in %s FATAL ERROR:\n ", file, line, function);
+ vfprintf(stderr, message, args);
+ fprintf(stderr, "\n");
+ fflush(stderr);
std::this_thread::sleep_for(std::chrono::seconds(10));
*((volatile int*)NULL) = 0;
diff --git a/src/common/Debugging/Errors.h b/src/common/Debugging/Errors.h
index 3ceaf2c328f..9e526933acc 100644
--- a/src/common/Debugging/Errors.h
+++ b/src/common/Debugging/Errors.h
@@ -26,7 +26,7 @@ namespace Trinity
DECLSPEC_NORETURN void Assert(char const* file, int line, char const* function, char const* message) ATTR_NORETURN;
DECLSPEC_NORETURN void Assert(char const* file, int line, char const* function, char const* message, char const* format, ...) ATTR_NORETURN ATTR_PRINTF(5, 6);
- DECLSPEC_NORETURN void Fatal(char const* file, int line, char const* function, char const* message) ATTR_NORETURN;
+ DECLSPEC_NORETURN void Fatal(char const* file, int line, char const* function, char const* message, ...) ATTR_NORETURN ATTR_PRINTF(4, 5);
DECLSPEC_NORETURN void Error(char const* file, int line, char const* function, char const* message) ATTR_NORETURN;
@@ -45,7 +45,7 @@ namespace Trinity
#endif
#define WPAssert(cond, ...) ASSERT_BEGIN do { if (!(cond)) Trinity::Assert(__FILE__, __LINE__, __FUNCTION__, #cond, ##__VA_ARGS__); } while(0) ASSERT_END
-#define WPFatal(cond, msg) ASSERT_BEGIN do { if (!(cond)) Trinity::Fatal(__FILE__, __LINE__, __FUNCTION__, (msg)); } while(0) ASSERT_END
+#define WPFatal(cond, ...) ASSERT_BEGIN do { if (!(cond)) Trinity::Fatal(__FILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__); } while(0) ASSERT_END
#define WPError(cond, msg) ASSERT_BEGIN do { if (!(cond)) Trinity::Error(__FILE__, __LINE__, __FUNCTION__, (msg)); } while(0) ASSERT_END
#define WPWarning(cond, msg) ASSERT_BEGIN do { if (!(cond)) Trinity::Warning(__FILE__, __LINE__, __FUNCTION__, (msg)); } while(0) ASSERT_END
#define WPAbort() ASSERT_BEGIN do { Trinity::Abort(__FILE__, __LINE__, __FUNCTION__); } while(0) ASSERT_END
diff --git a/src/server/database/Database/DatabaseWorkerPool.h b/src/server/database/Database/DatabaseWorkerPool.h
index 32837daf5da..c7b5d8c8fea 100644
--- a/src/server/database/Database/DatabaseWorkerPool.h
+++ b/src/server/database/Database/DatabaseWorkerPool.h
@@ -67,6 +67,8 @@ class DatabaseWorkerPool
WPFatal(mysql_thread_safe(), "Used MySQL library isn't thread-safe.");
WPFatal(mysql_get_client_version() >= MIN_MYSQL_CLIENT_VERSION, "TrinityCore does not support MySQL versions below 5.1");
+ WPFatal(mysql_get_client_version() == MYSQL_VERSION_ID, "Used MySQL library version (%s) does not match the version used to compile TrinityCore (%s).",
+ mysql_get_client_info(), MYSQL_SERVER_VERSION);
}
~DatabaseWorkerPool()
diff --git a/src/server/game/Battlegrounds/BattlegroundQueue.cpp b/src/server/game/Battlegrounds/BattlegroundQueue.cpp
index 87e07e15191..223b71eb8c5 100644
--- a/src/server/game/Battlegrounds/BattlegroundQueue.cpp
+++ b/src/server/game/Battlegrounds/BattlegroundQueue.cpp
@@ -1069,7 +1069,7 @@ bool BGQueueRemoveEvent::Execute(uint64 /*e_time*/, uint32 /*p_time*/)
if (bgQueue.IsPlayerInvited(m_PlayerGuid, m_BgInstanceGUID, m_RemoveTime))
{
// track if player leaves the BG by not clicking enter button
- if (bg->isBattleground() && sWorld->getBoolConfig(CONFIG_BATTLEGROUND_TRACK_DESERTERS) &&
+ if (bg && bg->isBattleground() && sWorld->getBoolConfig(CONFIG_BATTLEGROUND_TRACK_DESERTERS) &&
(bg->GetStatus() == STATUS_IN_PROGRESS || bg->GetStatus() == STATUS_WAIT_JOIN))
{
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_DESERTER_TRACK);
diff --git a/src/server/game/Chat/Chat.cpp b/src/server/game/Chat/Chat.cpp
index 161aefdfc67..c99fe62f11e 100644
--- a/src/server/game/Chat/Chat.cpp
+++ b/src/server/game/Chat/Chat.cpp
@@ -1131,7 +1131,7 @@ void ChatHandler::extractOptFirstArg(char* args, char** arg1, char** arg2)
char* ChatHandler::extractQuotedArg(char* args)
{
- if (!*args)
+ if (!args || !*args)
return NULL;
if (*args == '"')
diff --git a/src/server/game/Conditions/ConditionMgr.cpp b/src/server/game/Conditions/ConditionMgr.cpp
index d9fa125b64c..8e8c640ccf7 100644
--- a/src/server/game/Conditions/ConditionMgr.cpp
+++ b/src/server/game/Conditions/ConditionMgr.cpp
@@ -324,7 +324,7 @@ bool Condition::Meets(ConditionSourceInfo& sourceInfo) const
Unit* unit = object->ToUnit();
if (toUnit && unit)
{
- switch (ConditionValue2)
+ switch (static_cast<RelationType>(ConditionValue2))
{
case RELATION_SELF:
condMeets = unit == toUnit;
@@ -344,6 +344,8 @@ bool Condition::Meets(ConditionSourceInfo& sourceInfo) const
case RELATION_CREATED_BY:
condMeets = unit->GetCreatorGUID() == toUnit->GetGUID();
break;
+ default:
+ break;
}
}
}
diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp
index 6c48cfd650a..9173d44ae86 100644
--- a/src/server/game/Entities/Creature/Creature.cpp
+++ b/src/server/game/Entities/Creature/Creature.cpp
@@ -440,7 +440,7 @@ bool Creature::UpdateEntry(uint32 entry, CreatureData const* data /*= nullptr*/)
ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_ATTACK_ME, true);
}
- LoadCreaturesAddon(true);
+ LoadCreaturesAddon();
UpdateMovementFlags();
return true;
}
@@ -821,8 +821,6 @@ bool Creature::Create(ObjectGuid::LowType guidlow, Map* map, uint32 phaseMask, u
break;
}
- LoadCreaturesAddon();
-
//! Need to be called after LoadCreaturesAddon - MOVEMENTFLAG_HOVER is set there
if (HasUnitMovementFlag(MOVEMENTFLAG_HOVER))
{
@@ -1558,7 +1556,7 @@ void Creature::setDeathState(DeathState s)
if (GetCreatureData() && GetPhaseMask() != GetCreatureData()->phaseMask)
SetPhaseMask(GetCreatureData()->phaseMask, false);
Unit::setDeathState(ALIVE);
- LoadCreaturesAddon(true);
+ LoadCreaturesAddon();
}
}
@@ -2064,7 +2062,7 @@ CreatureAddon const* Creature::GetCreatureAddon() const
}
//creature_addon table
-bool Creature::LoadCreaturesAddon(bool reload)
+bool Creature::LoadCreaturesAddon()
{
CreatureAddon const* cainfo = GetCreatureAddon();
if (!cainfo)
@@ -2130,12 +2128,7 @@ bool Creature::LoadCreaturesAddon(bool reload)
// skip already applied aura
if (HasAura(*itr))
- {
- if (!reload)
- TC_LOG_ERROR("sql.sql", "Creature (GUID: %u Entry: %u) has duplicate aura (spell %u) in `auras` field.", GetSpawnId(), GetEntry(), *itr);
-
continue;
- }
AddAura(*itr, this);
TC_LOG_DEBUG("entities.unit", "Spell: %u added to creature (GUID: %u Entry: %u)", *itr, GetGUID().GetCounter(), GetEntry());
diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h
index df83a2f9c0f..9a41c8570ed 100644
--- a/src/server/game/Entities/Creature/Creature.h
+++ b/src/server/game/Entities/Creature/Creature.h
@@ -432,7 +432,7 @@ class Creature : public Unit, public GridObject<Creature>, public MapObject
void DisappearAndDie();
bool Create(ObjectGuid::LowType guidlow, Map* map, uint32 phaseMask, uint32 entry, float x, float y, float z, float ang, CreatureData const* data = nullptr, uint32 vehId = 0);
- bool LoadCreaturesAddon(bool reload = false);
+ bool LoadCreaturesAddon();
void SelectLevel();
void LoadEquipment(int8 id = 1, bool force = false);
diff --git a/src/server/game/Entities/Item/Item.cpp b/src/server/game/Entities/Item/Item.cpp
index 19cf9beff21..1b7914fd85f 100644
--- a/src/server/game/Entities/Item/Item.cpp
+++ b/src/server/game/Entities/Item/Item.cpp
@@ -28,6 +28,7 @@
#include "ConditionMgr.h"
#include "Player.h"
#include "WorldSession.h"
+#include "TradeData.h"
void AddItemsSetItem(Player* player, Item* item)
{
diff --git a/src/server/game/Entities/Player/KillRewarder.cpp b/src/server/game/Entities/Player/KillRewarder.cpp
new file mode 100644
index 00000000000..ad2f8f641ea
--- /dev/null
+++ b/src/server/game/Entities/Player/KillRewarder.cpp
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/>
+ *
+ * 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 "KillRewarder.h"
+#include "SpellAuraEffects.h"
+#include "Creature.h"
+#include "Formulas.h"
+#include "Group.h"
+#include "Guild.h"
+#include "GuildMgr.h"
+#include "InstanceScript.h"
+#include "Pet.h"
+#include "Player.h"
+
+ // == KillRewarder ====================================================
+ // KillRewarder encapsulates logic of rewarding player upon kill with:
+ // * XP;
+ // * honor;
+ // * reputation;
+ // * kill credit (for quest objectives).
+ // Rewarding is initiated in two cases: when player kills unit in Unit::Kill()
+ // and on battlegrounds in Battleground::RewardXPAtKill().
+ //
+ // Rewarding algorithm is:
+ // 1. Initialize internal variables to default values.
+ // 2. In case when player is in group, initialize variables necessary for group calculations:
+ // 2.1. _count - number of alive group members within reward distance;
+ // 2.2. _sumLevel - sum of levels of alive group members within reward distance;
+ // 2.3. _maxLevel - maximum level of alive group member within reward distance;
+ // 2.4. _maxNotGrayMember - maximum level of alive group member within reward distance,
+ // for whom victim is not gray;
+ // 2.5. _isFullXP - flag identifying that for all group members victim is not gray,
+ // so 100% XP will be rewarded (50% otherwise).
+ // 3. Reward killer (and group, if necessary).
+ // 3.1. If killer is in group, reward group.
+ // 3.1.1. Initialize initial XP amount based on maximum level of group member,
+ // for whom victim is not gray.
+ // 3.1.2. Alter group rate if group is in raid (not for battlegrounds).
+ // 3.1.3. Reward each group member (even dead) within reward distance (see 4. for more details).
+ // 3.2. Reward single killer (not group case).
+ // 3.2.1. Initialize initial XP amount based on killer's level.
+ // 3.2.2. Reward killer (see 4. for more details).
+ // 4. Reward player.
+ // 4.1. Give honor (player must be alive and not on BG).
+ // 4.2. Give XP.
+ // 4.2.1. If player is in group, adjust XP:
+ // * set to 0 if player's level is more than maximum level of not gray member;
+ // * cut XP in half if _isFullXP is false.
+ // 4.2.2. Apply auras modifying rewarded XP.
+ // 4.2.3. Give XP to player.
+ // 4.2.4. If player has pet, reward pet with XP (100% for single player, 50% for group case).
+ // 4.3. Give reputation (player must not be on BG).
+ // 4.4. Give kill credit (player must not be in group, or he must be alive or without corpse).
+ // 5. Credit instance encounter.
+
+KillRewarder::KillRewarder(Player* killer, Unit* victim, bool isBattleGround) :
+ // 1. Initialize internal variables to default values.
+ _killer(killer), _victim(victim), _group(killer->GetGroup()),
+ _groupRate(1.0f), _maxNotGrayMember(nullptr), _count(0), _sumLevel(0), _xp(0),
+ _isFullXP(false), _maxLevel(0), _isBattleGround(isBattleGround), _isPvP(false)
+{
+ // mark the credit as pvp if victim is player
+ if (victim->GetTypeId() == TYPEID_PLAYER)
+ _isPvP = true;
+ // or if its owned by player and its not a vehicle
+ else if (victim->GetCharmerOrOwnerGUID().IsPlayer())
+ _isPvP = !victim->IsVehicle();
+
+ _InitGroupData();
+}
+
+inline void KillRewarder::_InitGroupData()
+{
+ if (_group)
+ {
+ // 2. In case when player is in group, initialize variables necessary for group calculations:
+ for (GroupReference* itr = _group->GetFirstMember(); itr != nullptr; itr = itr->next())
+ if (Player* member = itr->GetSource())
+ if (member->IsAlive() && member->IsAtGroupRewardDistance(_victim))
+ {
+ const uint8 lvl = member->getLevel();
+ // 2.1. _count - number of alive group members within reward distance;
+ ++_count;
+ // 2.2. _sumLevel - sum of levels of alive group members within reward distance;
+ _sumLevel += lvl;
+ // 2.3. _maxLevel - maximum level of alive group member within reward distance;
+ if (_maxLevel < lvl)
+ _maxLevel = lvl;
+ // 2.4. _maxNotGrayMember - maximum level of alive group member within reward distance,
+ // for whom victim is not gray;
+ uint32 grayLevel = Trinity::XP::GetGrayLevel(lvl);
+ if (_victim->getLevel() > grayLevel && (!_maxNotGrayMember || _maxNotGrayMember->getLevel() < lvl))
+ _maxNotGrayMember = member;
+ }
+ // 2.5. _isFullXP - flag identifying that for all group members victim is not gray,
+ // so 100% XP will be rewarded (50% otherwise).
+ _isFullXP = _maxNotGrayMember && (_maxLevel == _maxNotGrayMember->getLevel());
+ }
+ else
+ _count = 1;
+}
+
+inline void KillRewarder::_InitXP(Player* player)
+{
+ // Get initial value of XP for kill.
+ // XP is given:
+ // * on battlegrounds;
+ // * otherwise, not in PvP;
+ // * not if killer is on vehicle.
+ if (_isBattleGround || (!_isPvP && !_killer->GetVehicle()))
+ _xp = Trinity::XP::Gain(player, _victim, _isBattleGround);
+}
+
+inline void KillRewarder::_RewardHonor(Player* player)
+{
+ // Rewarded player must be alive.
+ if (player->IsAlive())
+ player->RewardHonor(_victim, _count, -1, true);
+}
+
+inline void KillRewarder::_RewardXP(Player* player, float rate)
+{
+ uint32 xp(_xp);
+ if (_group)
+ {
+ // 4.2.1. If player is in group, adjust XP:
+ // * set to 0 if player's level is more than maximum level of not gray member;
+ // * cut XP in half if _isFullXP is false.
+ if (_maxNotGrayMember && player->IsAlive() &&
+ _maxNotGrayMember->getLevel() >= player->getLevel())
+ xp = _isFullXP ?
+ uint32(xp * rate) : // Reward FULL XP if all group members are not gray.
+ uint32(xp * rate / 2) + 1; // Reward only HALF of XP if some of group members are gray.
+ else
+ xp = 0;
+ }
+ if (xp)
+ {
+ // 4.2.2. Apply auras modifying rewarded XP (SPELL_AURA_MOD_XP_PCT).
+ for (auto const& aura : player->GetAuraEffectsByType(SPELL_AURA_MOD_XP_PCT))
+ AddPct(xp, aura->GetAmount());
+
+ // 4.2.3. Give XP to player.
+ player->GiveXP(xp, _victim, _groupRate);
+ if (Pet* pet = player->GetPet())
+ // 4.2.4. If player has pet, reward pet with XP (100% for single player, 50% for group case).
+ pet->GivePetXP(_group ? xp / 2 : xp);
+ }
+}
+
+inline void KillRewarder::_RewardReputation(Player* player, float rate)
+{
+ // 4.3. Give reputation (player must not be on BG).
+ // Even dead players and corpses are rewarded.
+ player->RewardReputation(_victim, rate);
+}
+
+inline void KillRewarder::_RewardKillCredit(Player* player)
+{
+ // 4.4. Give kill credit (player must not be in group, or he must be alive or without corpse).
+ if (!_group || player->IsAlive() || !player->GetCorpse())
+ if (Creature* target = _victim->ToCreature())
+ {
+ player->KilledMonster(target->GetCreatureTemplate(), target->GetGUID());
+ player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_KILL_CREATURE_TYPE, target->GetCreatureType(), 1, target);
+ }
+}
+
+void KillRewarder::_RewardPlayer(Player* player, bool isDungeon)
+{
+ // 4. Reward player.
+ if (!_isBattleGround)
+ {
+ // 4.1. Give honor (player must be alive and not on BG).
+ _RewardHonor(player);
+ // 4.1.1 Send player killcredit for quests with PlayerSlain
+ if (_victim->GetTypeId() == TYPEID_PLAYER)
+ player->KilledPlayerCredit();
+ }
+ // Give XP only in PvE or in battlegrounds.
+ // Give reputation and kill credit only in PvE.
+ if (!_isPvP || _isBattleGround)
+ {
+ float const rate = _group ?
+ _groupRate * float(player->getLevel()) / _sumLevel : // Group rate depends on summary level.
+ 1.0f; // Personal rate is 100%.
+ if (_xp)
+ // 4.2. Give XP.
+ _RewardXP(player, rate);
+ if (!_isBattleGround)
+ {
+ // If killer is in dungeon then all members receive full reputation at kill.
+ _RewardReputation(player, isDungeon ? 1.0f : rate);
+ _RewardKillCredit(player);
+ }
+ }
+}
+
+void KillRewarder::_RewardGroup()
+{
+ if (_maxLevel)
+ {
+ if (_maxNotGrayMember)
+ // 3.1.1. Initialize initial XP amount based on maximum level of group member,
+ // for whom victim is not gray.
+ _InitXP(_maxNotGrayMember);
+ // To avoid unnecessary calculations and calls,
+ // proceed only if XP is not ZERO or player is not on battleground
+ // (battleground rewards only XP, that's why).
+ if (!_isBattleGround || _xp)
+ {
+ bool const isDungeon = !_isPvP && sMapStore.LookupEntry(_killer->GetMapId())->IsDungeon();
+ if (!_isBattleGround)
+ {
+ // 3.1.2. Alter group rate if group is in raid (not for battlegrounds).
+ bool const isRaid = !_isPvP && sMapStore.LookupEntry(_killer->GetMapId())->IsRaid() && _group->isRaidGroup();
+ _groupRate = Trinity::XP::xp_in_group_rate(_count, isRaid);
+ }
+
+ // 3.1.3. Reward each group member (even dead or corpse) within reward distance.
+ for (GroupReference* itr = _group->GetFirstMember(); itr != nullptr; itr = itr->next())
+ {
+ if (Player* member = itr->GetSource())
+ {
+ if (member->IsAtGroupRewardDistance(_victim))
+ {
+ _RewardPlayer(member, isDungeon);
+ member->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_SPECIAL_PVP_KILL, 1, 0, _victim);
+ }
+ }
+ }
+ }
+ }
+}
+
+void KillRewarder::Reward()
+{
+ // 3. Reward killer (and group, if necessary).
+ if (_group)
+ // 3.1. If killer is in group, reward group.
+ _RewardGroup();
+ else
+ {
+ // 3.2. Reward single killer (not group case).
+ // 3.2.1. Initialize initial XP amount based on killer's level.
+ _InitXP(_killer);
+ // To avoid unnecessary calculations and calls,
+ // proceed only if XP is not ZERO or player is not on battleground
+ // (battleground rewards only XP, that's why).
+ if (!_isBattleGround || _xp)
+ // 3.2.2. Reward killer.
+ _RewardPlayer(_killer, false);
+ }
+
+ // 5. Credit instance encounter.
+ if (Creature* victim = _victim->ToCreature())
+ if (victim->IsDungeonBoss())
+ if (InstanceScript* instance = _victim->GetInstanceScript())
+ instance->UpdateEncounterState(ENCOUNTER_CREDIT_KILL_CREATURE, _victim->GetEntry(), _victim);
+}
diff --git a/src/server/game/Entities/Player/KillRewarder.h b/src/server/game/Entities/Player/KillRewarder.h
new file mode 100644
index 00000000000..577a8ffea20
--- /dev/null
+++ b/src/server/game/Entities/Player/KillRewarder.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/>
+ *
+ * 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 KillRewarder_h__
+#define KillRewarder_h__
+
+#include "Define.h"
+
+class Player;
+class Unit;
+class Group;
+
+class KillRewarder
+{
+public:
+ KillRewarder(Player* killer, Unit* victim, bool isBattleGround);
+
+ void Reward();
+
+private:
+ void _InitXP(Player* player);
+ void _InitGroupData();
+
+ void _RewardHonor(Player* player);
+ void _RewardXP(Player* player, float rate);
+ void _RewardReputation(Player* player, float rate);
+ void _RewardKillCredit(Player* player);
+ void _RewardPlayer(Player* player, bool isDungeon);
+ void _RewardGroup();
+
+ Player* _killer;
+ Unit* _victim;
+ Group* _group;
+ float _groupRate;
+ Player* _maxNotGrayMember;
+ uint32 _count;
+ uint32 _sumLevel;
+ uint32 _xp;
+ bool _isFullXP;
+ uint8 _maxLevel;
+ bool _isBattleGround;
+ bool _isPvP;
+};
+
+#endif // KillRewarder_h__
diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp
index 3d11e786d52..1a9c0fbce4b 100644
--- a/src/server/game/Entities/Player/Player.cpp
+++ b/src/server/game/Entities/Player/Player.cpp
@@ -48,6 +48,7 @@
#include "GuildMgr.h"
#include "InstanceSaveMgr.h"
#include "InstanceScript.h"
+#include "KillRewarder.h"
#include "LFGMgr.h"
#include "Language.h"
#include "Log.h"
@@ -289,374 +290,6 @@ std::ostringstream& operator<< (std::ostringstream& ss, PlayerTaxi const& taxi)
return ss;
}
-//== TradeData =================================================
-
-TradeData* TradeData::GetTraderData() const
-{
- return m_trader->GetTradeData();
-}
-
-Item* TradeData::GetItem(TradeSlots slot) const
-{
- return m_items[slot] ? m_player->GetItemByGuid(m_items[slot]) : NULL;
-}
-
-bool TradeData::HasItem(ObjectGuid itemGuid) const
-{
- for (uint8 i = 0; i < TRADE_SLOT_COUNT; ++i)
- if (m_items[i] == itemGuid)
- return true;
-
- return false;
-}
-
-TradeSlots TradeData::GetTradeSlotForItem(ObjectGuid itemGuid) const
-{
- for (uint8 i = 0; i < TRADE_SLOT_COUNT; ++i)
- if (m_items[i] == itemGuid)
- return TradeSlots(i);
-
- return TRADE_SLOT_INVALID;
-}
-
-Item* TradeData::GetSpellCastItem() const
-{
- return m_spellCastItem ? m_player->GetItemByGuid(m_spellCastItem) : NULL;
-}
-
-void TradeData::SetItem(TradeSlots slot, Item* item, bool update /*= false*/)
-{
- ObjectGuid itemGuid;
- if (item)
- itemGuid = item->GetGUID();
-
- if (m_items[slot] == itemGuid && !update)
- return;
-
- m_items[slot] = itemGuid;
-
- SetAccepted(false);
- GetTraderData()->SetAccepted(false);
-
- Update();
-
- // need remove possible trader spell applied to changed item
- if (slot == TRADE_SLOT_NONTRADED)
- GetTraderData()->SetSpell(0);
-
- // need remove possible player spell applied (possible move reagent)
- SetSpell(0);
-}
-
-void TradeData::SetSpell(uint32 spell_id, Item* castItem /*= NULL*/)
-{
- ObjectGuid itemGuid = castItem ? castItem->GetGUID() : ObjectGuid::Empty;
-
- if (m_spell == spell_id && m_spellCastItem == itemGuid)
- return;
-
- m_spell = spell_id;
- m_spellCastItem = itemGuid;
-
- SetAccepted(false);
- GetTraderData()->SetAccepted(false);
-
- Update(true); // send spell info to item owner
- Update(false); // send spell info to caster self
-}
-
-void TradeData::SetMoney(uint32 money)
-{
- if (m_money == money)
- return;
-
- if (!m_player->HasEnoughMoney(money))
- {
- TradeStatusInfo info;
- info.Status = TRADE_STATUS_CLOSE_WINDOW;
- info.Result = EQUIP_ERR_NOT_ENOUGH_MONEY;
- m_player->GetSession()->SendTradeStatus(info);
- return;
- }
-
- m_money = money;
-
- SetAccepted(false);
- GetTraderData()->SetAccepted(false);
-
- Update(true);
-}
-
-void TradeData::Update(bool forTarget /*= true*/)
-{
- if (forTarget)
- m_trader->GetSession()->SendUpdateTrade(true); // player state for trader
- else
- m_player->GetSession()->SendUpdateTrade(false); // player state for player
-}
-
-void TradeData::SetAccepted(bool state, bool crosssend /*= false*/)
-{
- m_accepted = state;
-
- if (!state)
- {
- TradeStatusInfo info;
- info.Status = TRADE_STATUS_BACK_TO_TRADE;
- if (crosssend)
- m_trader->GetSession()->SendTradeStatus(info);
- else
- m_player->GetSession()->SendTradeStatus(info);
- }
-}
-
-// == KillRewarder ====================================================
-// KillRewarder incapsulates logic of rewarding player upon kill with:
-// * XP;
-// * honor;
-// * reputation;
-// * kill credit (for quest objectives).
-// Rewarding is initiated in two cases: when player kills unit in Unit::Kill()
-// and on battlegrounds in Battleground::RewardXPAtKill().
-//
-// Rewarding algorithm is:
-// 1. Initialize internal variables to default values.
-// 2. In case when player is in group, initialize variables necessary for group calculations:
-// 2.1. _count - number of alive group members within reward distance;
-// 2.2. _sumLevel - sum of levels of alive group members within reward distance;
-// 2.3. _maxLevel - maximum level of alive group member within reward distance;
-// 2.4. _maxNotGrayMember - maximum level of alive group member within reward distance,
-// for whom victim is not gray;
-// 2.5. _isFullXP - flag identifying that for all group members victim is not gray,
-// so 100% XP will be rewarded (50% otherwise).
-// 3. Reward killer (and group, if necessary).
-// 3.1. If killer is in group, reward group.
-// 3.1.1. Initialize initial XP amount based on maximum level of group member,
-// for whom victim is not gray.
-// 3.1.2. Alter group rate if group is in raid (not for battlegrounds).
-// 3.1.3. Reward each group member (even dead) within reward distance (see 4. for more details).
-// 3.2. Reward single killer (not group case).
-// 3.2.1. Initialize initial XP amount based on killer's level.
-// 3.2.2. Reward killer (see 4. for more details).
-// 4. Reward player.
-// 4.1. Give honor (player must be alive and not on BG).
-// 4.2. Give XP.
-// 4.2.1. If player is in group, adjust XP:
-// * set to 0 if player's level is more than maximum level of not gray member;
-// * cut XP in half if _isFullXP is false.
-// 4.2.2. Apply auras modifying rewarded XP.
-// 4.2.3. Give XP to player.
-// 4.2.4. If player has pet, reward pet with XP (100% for single player, 50% for group case).
-// 4.3. Give reputation (player must not be on BG).
-// 4.4. Give kill credit (player must not be in group, or he must be alive or without corpse).
-// 5. Credit instance encounter.
-KillRewarder::KillRewarder(Player* killer, Unit* victim, bool isBattleGround) :
- // 1. Initialize internal variables to default values.
- _killer(killer), _victim(victim), _group(killer->GetGroup()),
- _groupRate(1.0f), _maxNotGrayMember(NULL), _count(0), _sumLevel(0), _xp(0),
- _isFullXP(false), _maxLevel(0), _isBattleGround(isBattleGround), _isPvP(false)
-{
- // mark the credit as pvp if victim is player
- if (victim->GetTypeId() == TYPEID_PLAYER)
- _isPvP = true;
- // or if its owned by player and its not a vehicle
- else if (victim->GetCharmerOrOwnerGUID().IsPlayer())
- _isPvP = !victim->IsVehicle();
-
- _InitGroupData();
-}
-
-inline void KillRewarder::_InitGroupData()
-{
- if (_group)
- {
- // 2. In case when player is in group, initialize variables necessary for group calculations:
- for (GroupReference* itr = _group->GetFirstMember(); itr != NULL; itr = itr->next())
- if (Player* member = itr->GetSource())
- if (member->IsAlive() && member->IsAtGroupRewardDistance(_victim))
- {
- const uint8 lvl = member->getLevel();
- // 2.1. _count - number of alive group members within reward distance;
- ++_count;
- // 2.2. _sumLevel - sum of levels of alive group members within reward distance;
- _sumLevel += lvl;
- // 2.3. _maxLevel - maximum level of alive group member within reward distance;
- if (_maxLevel < lvl)
- _maxLevel = lvl;
- // 2.4. _maxNotGrayMember - maximum level of alive group member within reward distance,
- // for whom victim is not gray;
- uint32 grayLevel = Trinity::XP::GetGrayLevel(lvl);
- if (_victim->getLevel() > grayLevel && (!_maxNotGrayMember || _maxNotGrayMember->getLevel() < lvl))
- _maxNotGrayMember = member;
- }
- // 2.5. _isFullXP - flag identifying that for all group members victim is not gray,
- // so 100% XP will be rewarded (50% otherwise).
- _isFullXP = _maxNotGrayMember && (_maxLevel == _maxNotGrayMember->getLevel());
- }
- else
- _count = 1;
-}
-
-inline void KillRewarder::_InitXP(Player* player)
-{
- // Get initial value of XP for kill.
- // XP is given:
- // * on battlegrounds;
- // * otherwise, not in PvP;
- // * not if killer is on vehicle.
- if (_isBattleGround || (!_isPvP && !_killer->GetVehicle()))
- _xp = Trinity::XP::Gain(player, _victim, _isBattleGround);
-}
-
-inline void KillRewarder::_RewardHonor(Player* player)
-{
- // Rewarded player must be alive.
- if (player->IsAlive())
- player->RewardHonor(_victim, _count, -1, true);
-}
-
-inline void KillRewarder::_RewardXP(Player* player, float rate)
-{
- uint32 xp(_xp);
- if (_group)
- {
- // 4.2.1. If player is in group, adjust XP:
- // * set to 0 if player's level is more than maximum level of not gray member;
- // * cut XP in half if _isFullXP is false.
- if (_maxNotGrayMember && player->IsAlive() &&
- _maxNotGrayMember->getLevel() >= player->getLevel())
- xp = _isFullXP ?
- uint32(xp * rate) : // Reward FULL XP if all group members are not gray.
- uint32(xp * rate / 2) + 1; // Reward only HALF of XP if some of group members are gray.
- else
- xp = 0;
- }
- 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());
-
- // 4.2.3. Give XP to player.
- player->GiveXP(xp, _victim, _groupRate);
- if (Pet* pet = player->GetPet())
- // 4.2.4. If player has pet, reward pet with XP (100% for single player, 50% for group case).
- pet->GivePetXP(_group ? xp / 2 : xp);
- }
-}
-
-inline void KillRewarder::_RewardReputation(Player* player, float rate)
-{
- // 4.3. Give reputation (player must not be on BG).
- // Even dead players and corpses are rewarded.
- player->RewardReputation(_victim, rate);
-}
-
-inline void KillRewarder::_RewardKillCredit(Player* player)
-{
- // 4.4. Give kill credit (player must not be in group, or he must be alive or without corpse).
- if (!_group || player->IsAlive() || !player->GetCorpse())
- if (Creature* target = _victim->ToCreature())
- {
- player->KilledMonster(target->GetCreatureTemplate(), target->GetGUID());
- player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_KILL_CREATURE_TYPE, target->GetCreatureType(), 1, target);
- }
-}
-
-void KillRewarder::_RewardPlayer(Player* player, bool isDungeon)
-{
- // 4. Reward player.
- if (!_isBattleGround)
- {
- // 4.1. Give honor (player must be alive and not on BG).
- _RewardHonor(player);
- // 4.1.1 Send player killcredit for quests with PlayerSlain
- if (_victim->GetTypeId() == TYPEID_PLAYER)
- player->KilledPlayerCredit();
- }
- // Give XP only in PvE or in battlegrounds.
- // Give reputation and kill credit only in PvE.
- if (!_isPvP || _isBattleGround)
- {
- const float rate = _group ?
- _groupRate * float(player->getLevel()) / _sumLevel : // Group rate depends on summary level.
- 1.0f; // Personal rate is 100%.
- if (_xp)
- // 4.2. Give XP.
- _RewardXP(player, rate);
- if (!_isBattleGround)
- {
- // If killer is in dungeon then all members receive full reputation at kill.
- _RewardReputation(player, isDungeon ? 1.0f : rate);
- _RewardKillCredit(player);
- }
- }
-}
-
-void KillRewarder::_RewardGroup()
-{
- if (_maxLevel)
- {
- if (_maxNotGrayMember)
- // 3.1.1. Initialize initial XP amount based on maximum level of group member,
- // for whom victim is not gray.
- _InitXP(_maxNotGrayMember);
- // To avoid unnecessary calculations and calls,
- // proceed only if XP is not ZERO or player is not on battleground
- // (battleground rewards only XP, that's why).
- if (!_isBattleGround || _xp)
- {
- const bool isDungeon = !_isPvP && sMapStore.LookupEntry(_killer->GetMapId())->IsDungeon();
- if (!_isBattleGround)
- {
- // 3.1.2. Alter group rate if group is in raid (not for battlegrounds).
- const bool isRaid = !_isPvP && sMapStore.LookupEntry(_killer->GetMapId())->IsRaid() && _group->isRaidGroup();
- _groupRate = Trinity::XP::xp_in_group_rate(_count, isRaid);
- }
-
- // 3.1.3. Reward each group member (even dead or corpse) within reward distance.
- for (GroupReference* itr = _group->GetFirstMember(); itr != NULL; itr = itr->next())
- {
- if (Player* member = itr->GetSource())
- {
- if (member->IsAtGroupRewardDistance(_victim))
- {
- _RewardPlayer(member, isDungeon);
- member->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_SPECIAL_PVP_KILL, 1, 0, _victim);
- }
- }
- }
- }
- }
-}
-
-void KillRewarder::Reward()
-{
- // 3. Reward killer (and group, if necessary).
- if (_group)
- // 3.1. If killer is in group, reward group.
- _RewardGroup();
- else
- {
- // 3.2. Reward single killer (not group case).
- // 3.2.1. Initialize initial XP amount based on killer's level.
- _InitXP(_killer);
- // To avoid unnecessary calculations and calls,
- // proceed only if XP is not ZERO or player is not on battleground
- // (battleground rewards only XP, that's why).
- if (!_isBattleGround || _xp)
- // 3.2.2. Reward killer.
- _RewardPlayer(_killer, false);
- }
-
- // 5. Credit instance encounter.
- if (Creature* victim = _victim->ToCreature())
- if (victim->IsDungeonBoss())
- if (InstanceScript* instance = _victim->GetInstanceScript())
- instance->UpdateEncounterState(ENCOUNTER_CREDIT_KILL_CREATURE, _victim->GetEntry(), _victim);
-}
-
Player::Player(WorldSession* session): Unit(true)
{
m_speakTime = 0;
@@ -902,6 +535,8 @@ Player::Player(WorldSession* session): Unit(true)
SetPendingBind(0, 0);
_activeCheats = CHEAT_NONE;
+ healthBeforeDuel = 0;
+ manaBeforeDuel = 0;
m_achievementMgr = new AchievementMgr(this);
m_reputationMgr = new ReputationMgr(this);
}
@@ -15927,8 +15562,9 @@ bool Player::GetQuestRewardStatus(uint32 quest_id) const
if (qInfo->IsSeasonal() && !qInfo->IsRepeatable())
{
uint16 eventId = sGameEventMgr->GetEventIdForQuest(qInfo);
- if (m_seasonalquests.find(eventId) != m_seasonalquests.end())
- return m_seasonalquests.find(eventId)->second.find(quest_id) != m_seasonalquests.find(eventId)->second.end();
+ auto seasonalQuestItr = m_seasonalquests.find(eventId);
+ if (seasonalQuestItr != m_seasonalquests.end())
+ return seasonalQuestItr->second.find(quest_id) != seasonalQuestItr->second.end();
return false;
}
@@ -15956,7 +15592,8 @@ QuestStatus Player::GetQuestStatus(uint32 quest_id) const
if (qInfo->IsSeasonal() && !qInfo->IsRepeatable())
{
uint16 eventId = sGameEventMgr->GetEventIdForQuest(qInfo);
- if (m_seasonalquests.find(eventId) == m_seasonalquests.end() || m_seasonalquests.find(eventId)->second.find(quest_id) == m_seasonalquests.find(eventId)->second.end())
+ auto seasonalQuestItr = m_seasonalquests.find(eventId);
+ if (seasonalQuestItr == m_seasonalquests.end() || seasonalQuestItr->second.find(quest_id) == seasonalQuestItr->second.end())
return QUEST_STATUS_NONE;
}
diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h
index 7c0700fa244..2a9dfc49280 100644
--- a/src/server/game/Entities/Player/Player.h
+++ b/src/server/game/Entities/Player/Player.h
@@ -29,6 +29,7 @@
#include "SpellMgr.h"
#include "SpellHistory.h"
#include "Unit.h"
+#include "TradeData.h"
#include <limits>
#include <string>
@@ -687,14 +688,6 @@ struct ItemPosCount
};
typedef std::vector<ItemPosCount> ItemPosCountVec;
-enum TradeSlots
-{
- TRADE_SLOT_COUNT = 7,
- TRADE_SLOT_TRADED_COUNT = 6,
- TRADE_SLOT_NONTRADED = 6,
- TRADE_SLOT_INVALID = -1
-};
-
enum TransferAbortReason
{
TRANSFER_ABORT_NONE = 0x00,
@@ -1007,88 +1000,6 @@ struct TradeStatusInfo
uint8 Slot;
};
-class TradeData
-{
- public: // constructors
- TradeData(Player* player, Player* trader) :
- m_player(player), m_trader(trader), m_accepted(false), m_acceptProccess(false),
- m_money(0), m_spell(0), m_spellCastItem() { }
-
- Player* GetTrader() const { return m_trader; }
- TradeData* GetTraderData() const;
-
- Item* GetItem(TradeSlots slot) const;
- bool HasItem(ObjectGuid itemGuid) const;
- TradeSlots GetTradeSlotForItem(ObjectGuid itemGuid) const;
- void SetItem(TradeSlots slot, Item* item, bool update = false);
-
- uint32 GetSpell() const { return m_spell; }
- void SetSpell(uint32 spell_id, Item* castItem = NULL);
-
- Item* GetSpellCastItem() const;
- bool HasSpellCastItem() const { return !m_spellCastItem.IsEmpty(); }
-
- uint32 GetMoney() const { return m_money; }
- void SetMoney(uint32 money);
-
- bool IsAccepted() const { return m_accepted; }
- void SetAccepted(bool state, bool crosssend = false);
-
- bool IsInAcceptProcess() const { return m_acceptProccess; }
- void SetInAcceptProcess(bool state) { m_acceptProccess = state; }
-
- private: // internal functions
-
- void Update(bool for_trader = true);
-
- private: // fields
-
- Player* m_player; // Player who own of this TradeData
- Player* m_trader; // Player who trade with m_player
-
- bool m_accepted; // m_player press accept for trade list
- bool m_acceptProccess; // one from player/trader press accept and this processed
-
- uint32 m_money; // m_player place money to trade
-
- uint32 m_spell; // m_player apply spell to non-traded slot item
- ObjectGuid m_spellCastItem; // applied spell cast by item use
-
- ObjectGuid m_items[TRADE_SLOT_COUNT]; // traded items from m_player side including non-traded slot
-};
-
-class KillRewarder
-{
-public:
- KillRewarder(Player* killer, Unit* victim, bool isBattleGround);
-
- void Reward();
-
-private:
- void _InitXP(Player* player);
- void _InitGroupData();
-
- void _RewardHonor(Player* player);
- void _RewardXP(Player* player, float rate);
- void _RewardReputation(Player* player, float rate);
- void _RewardKillCredit(Player* player);
- void _RewardPlayer(Player* player, bool isDungeon);
- void _RewardGroup();
-
- Player* _killer;
- Unit* _victim;
- Group* _group;
- float _groupRate;
- Player* _maxNotGrayMember;
- uint32 _count;
- uint32 _sumLevel;
- uint32 _xp;
- bool _isFullXP;
- uint8 _maxLevel;
- bool _isBattleGround;
- bool _isPvP;
-};
-
class Player : public Unit, public GridObject<Player>
{
friend class WorldSession;
@@ -1325,7 +1236,7 @@ class Player : public Unit, public GridObject<Player>
float GetReputationPriceDiscount(Creature const* creature) const;
- Player* GetTrader() const { return m_trade ? m_trade->GetTrader() : NULL; }
+ Player* GetTrader() const { return m_trade ? m_trade->GetTrader() : nullptr; }
TradeData* GetTradeData() const { return m_trade; }
void TradeCancel(bool sendback);
@@ -1947,6 +1858,12 @@ class Player : public Unit, public GridObject<Player>
void SetHonorPoints(uint32 value);
void SetArenaPoints(uint32 value);
+ // duel health and mana reset methods
+ void SaveHealthBeforeDuel() { healthBeforeDuel = GetHealth(); }
+ void SaveManaBeforeDuel() { manaBeforeDuel = GetPower(POWER_MANA); }
+ void RestoreHealthAfterDuel() { SetHealth(healthBeforeDuel); }
+ void RestoreManaAfterDuel() { SetPower(POWER_MANA, manaBeforeDuel); }
+
//End of PvP System
void SetDrunkValue(uint8 newDrunkValue, uint32 itemId = 0);
@@ -2630,6 +2547,10 @@ class Player : public Unit, public GridObject<Player>
uint32 _activeCheats;
+ // variables to save health and mana before duel and restore them after duel
+ uint32 healthBeforeDuel;
+ uint32 manaBeforeDuel;
+
WorldLocation _corpseLocation;
};
diff --git a/src/server/game/Entities/Player/TradeData.cpp b/src/server/game/Entities/Player/TradeData.cpp
new file mode 100644
index 00000000000..bbbd1c81773
--- /dev/null
+++ b/src/server/game/Entities/Player/TradeData.cpp
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/>
+ *
+ * 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 "TradeData.h"
+#include "Player.h"
+#include "WorldSession.h"
+
+TradeData* TradeData::GetTraderData() const
+{
+ return _trader->GetTradeData();
+}
+
+Item* TradeData::GetItem(TradeSlots slot) const
+{
+ return !_items[slot].IsEmpty() ? _player->GetItemByGuid(_items[slot]) : nullptr;
+}
+
+bool TradeData::HasItem(ObjectGuid itemGuid) const
+{
+ for (uint8 i = 0; i < TRADE_SLOT_COUNT; ++i)
+ if (_items[i] == itemGuid)
+ return true;
+
+ return false;
+}
+
+TradeSlots TradeData::GetTradeSlotForItem(ObjectGuid itemGuid) const
+{
+ for (uint8 i = 0; i < TRADE_SLOT_COUNT; ++i)
+ if (_items[i] == itemGuid)
+ return TradeSlots(i);
+
+ return TRADE_SLOT_INVALID;
+}
+
+Item* TradeData::GetSpellCastItem() const
+{
+ return !_spellCastItem.IsEmpty() ? _player->GetItemByGuid(_spellCastItem) : nullptr;
+}
+
+void TradeData::SetItem(TradeSlots slot, Item* item, bool update /*= false*/)
+{
+ ObjectGuid itemGuid;
+ if (item)
+ itemGuid = item->GetGUID();
+
+ if (_items[slot] == itemGuid && !update)
+ return;
+
+ _items[slot] = itemGuid;
+
+ SetAccepted(false);
+ GetTraderData()->SetAccepted(false);
+
+ Update();
+
+ // need remove possible trader spell applied to changed item
+ if (slot == TRADE_SLOT_NONTRADED)
+ GetTraderData()->SetSpell(0);
+
+ // need remove possible player spell applied (possible move reagent)
+ SetSpell(0);
+}
+
+void TradeData::SetSpell(uint32 spell_id, Item* castItem /*= nullptr*/)
+{
+ ObjectGuid itemGuid = castItem ? castItem->GetGUID() : ObjectGuid::Empty;
+
+ if (_spell == spell_id && _spellCastItem == itemGuid)
+ return;
+
+ _spell = spell_id;
+ _spellCastItem = itemGuid;
+
+ SetAccepted(false);
+ GetTraderData()->SetAccepted(false);
+
+ Update(true); // send spell info to item owner
+ Update(false); // send spell info to caster self
+}
+
+void TradeData::SetMoney(uint32 money)
+{
+ if (_money == money)
+ return;
+
+ if (!_player->HasEnoughMoney(money))
+ {
+ TradeStatusInfo info;
+ info.Status = TRADE_STATUS_CLOSE_WINDOW;
+ info.Result = EQUIP_ERR_NOT_ENOUGH_MONEY;
+ _player->GetSession()->SendTradeStatus(info);
+ return;
+ }
+
+ _money = money;
+
+ SetAccepted(false);
+ GetTraderData()->SetAccepted(false);
+
+ Update(true);
+}
+
+void TradeData::Update(bool forTrader /*= true*/) const
+{
+ if (forTrader)
+ _trader->GetSession()->SendUpdateTrade(true); // player state for trader
+ else
+ _player->GetSession()->SendUpdateTrade(false); // player state for player
+}
+
+void TradeData::SetAccepted(bool state, bool forTrader /*= false*/)
+{
+ _accepted = state;
+
+ if (!state)
+ {
+ TradeStatusInfo info;
+ info.Status = TRADE_STATUS_BACK_TO_TRADE;
+ if (forTrader)
+ _trader->GetSession()->SendTradeStatus(info);
+ else
+ _player->GetSession()->SendTradeStatus(info);
+ }
+}
diff --git a/src/server/game/Entities/Player/TradeData.h b/src/server/game/Entities/Player/TradeData.h
new file mode 100644
index 00000000000..cfaf066bde0
--- /dev/null
+++ b/src/server/game/Entities/Player/TradeData.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/>
+ *
+ * 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 TradeData_h__
+#define TradeData_h__
+
+#include "ObjectGuid.h"
+
+enum TradeSlots
+{
+ TRADE_SLOT_COUNT = 7,
+ TRADE_SLOT_TRADED_COUNT = 6,
+ TRADE_SLOT_NONTRADED = 6,
+ TRADE_SLOT_INVALID = -1
+};
+
+class Item;
+class Player;
+
+class TradeData
+{
+public:
+ TradeData(Player* player, Player* trader) :
+ _player(player), _trader(trader), _accepted(false), _acceptProccess(false),
+ _money(0), _spell(0), _spellCastItem() { }
+
+ Player* GetTrader() const { return _trader; }
+ TradeData* GetTraderData() const;
+
+ Item* GetItem(TradeSlots slot) const;
+ bool HasItem(ObjectGuid itemGuid) const;
+ TradeSlots GetTradeSlotForItem(ObjectGuid itemGuid) const;
+ void SetItem(TradeSlots slot, Item* item, bool update = false);
+
+ uint32 GetSpell() const { return _spell; }
+ void SetSpell(uint32 spell_id, Item* castItem = nullptr);
+
+ Item* GetSpellCastItem() const;
+ bool HasSpellCastItem() const { return !_spellCastItem.IsEmpty(); }
+
+ uint32 GetMoney() const { return _money; }
+ void SetMoney(uint32 money);
+
+ bool IsAccepted() const { return _accepted; }
+ void SetAccepted(bool state, bool forTrader = false);
+
+ bool IsInAcceptProcess() const { return _acceptProccess; }
+ void SetInAcceptProcess(bool state) { _acceptProccess = state; }
+
+private:
+ void Update(bool for_trader = true) const;
+
+ Player* _player; // Player who own of this TradeData
+ Player* _trader; // Player who trade with _player
+
+ bool _accepted; // _player press accept for trade list
+ bool _acceptProccess; // one from player/trader press accept and this processed
+
+ uint32 _money; // _player place money to trade
+
+ uint32 _spell; // _player apply spell to non-traded slot item
+ ObjectGuid _spellCastItem; // applied spell cast by item use
+
+ ObjectGuid _items[TRADE_SLOT_COUNT]; // traded items from _player side including non-traded slot
+};
+
+#endif // TradeData_h__
diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp
index 858cb79c6dc..b2be5c49b91 100644
--- a/src/server/game/Globals/ObjectMgr.cpp
+++ b/src/server/game/Globals/ObjectMgr.cpp
@@ -555,6 +555,12 @@ void ObjectMgr::LoadCreatureTemplateAddons()
if (AdditionalSpellInfo->HasAura(SPELL_AURA_CONTROL_VEHICLE))
TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) has SPELL_AURA_CONTROL_VEHICLE aura %lu defined in `auras` field in `creature_template_addon`.", entry, atoul(*itr));
+ if (std::find(creatureAddon.auras.begin(), creatureAddon.auras.end(), atoul(*itr)) != creatureAddon.auras.end())
+ {
+ TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) has duplicate aura (spell %lu) in `auras` field in `creature_template_addon`.", entry, atoul(*itr));
+ continue;
+ }
+
creatureAddon.auras[i++] = atoul(*itr);
}
@@ -1003,6 +1009,12 @@ void ObjectMgr::LoadCreatureAddons()
if (AdditionalSpellInfo->HasAura(SPELL_AURA_CONTROL_VEHICLE))
TC_LOG_ERROR("sql.sql", "Creature (GUID: %u) has SPELL_AURA_CONTROL_VEHICLE aura %lu defined in `auras` field in `creature_addon`.", guid, atoul(*itr));
+ if (std::find(creatureAddon.auras.begin(), creatureAddon.auras.end(), atoul(*itr)) != creatureAddon.auras.end())
+ {
+ TC_LOG_ERROR("sql.sql", "Creature (GUID: %u) has duplicate aura (spell %lu) in `auras` field in `creature_addon`.", guid, atoul(*itr));
+ continue;
+ }
+
creatureAddon.auras[i++] = atoul(*itr);
}
@@ -2335,6 +2347,12 @@ void ObjectMgr::LoadItemTemplates()
itemTemplate.ContainerSlots = uint32(fields[26].GetUInt8());
itemTemplate.StatsCount = uint32(fields[27].GetUInt8());
+ if (itemTemplate.StatsCount > MAX_ITEM_PROTO_STATS)
+ {
+ TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has too large value in statscount (%u), replace by hardcoded limit (%u).", entry, itemTemplate.StatsCount, MAX_ITEM_PROTO_STATS);
+ itemTemplate.StatsCount = MAX_ITEM_PROTO_STATS;
+ }
+
for (uint8 i = 0; i < itemTemplate.StatsCount; ++i)
{
itemTemplate.ItemStat[i].ItemStatType = uint32(fields[28 + i*2].GetUInt8());
@@ -2582,12 +2600,6 @@ void ObjectMgr::LoadItemTemplates()
itemTemplate.ContainerSlots = MAX_BAG_SIZE;
}
- if (itemTemplate.StatsCount > MAX_ITEM_PROTO_STATS)
- {
- TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has too large value in statscount (%u), replace by hardcoded limit (%u).", entry, itemTemplate.StatsCount, MAX_ITEM_PROTO_STATS);
- itemTemplate.StatsCount = MAX_ITEM_PROTO_STATS;
- }
-
for (uint8 j = 0; j < itemTemplate.StatsCount; ++j)
{
// for ItemStatValue != 0
diff --git a/src/server/game/Handlers/TradeHandler.cpp b/src/server/game/Handlers/TradeHandler.cpp
index fbfd16ae1c6..1bb21971935 100644
--- a/src/server/game/Handlers/TradeHandler.cpp
+++ b/src/server/game/Handlers/TradeHandler.cpp
@@ -28,6 +28,7 @@
#include "SocialMgr.h"
#include "Language.h"
#include "AccountMgr.h"
+#include "TradeData.h"
void WorldSession::SendTradeStatus(TradeStatusInfo const& info)
{
@@ -454,6 +455,8 @@ void WorldSession::HandleAcceptTradeOpcode(WorldPacket& /*recvPacket*/)
SendTradeStatus(myCanCompleteInfo);
my_trade->SetAccepted(false);
his_trade->SetAccepted(false);
+ delete my_spell;
+ delete his_spell;
return;
}
else if (hisCanCompleteInfo.Result != EQUIP_ERR_OK)
@@ -466,6 +469,8 @@ void WorldSession::HandleAcceptTradeOpcode(WorldPacket& /*recvPacket*/)
trader->GetSession()->SendTradeStatus(hisCanCompleteInfo);
my_trade->SetAccepted(false);
his_trade->SetAccepted(false);
+ delete my_spell;
+ delete his_spell;
return;
}
diff --git a/src/server/game/Movement/MovementGenerators/HomeMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/HomeMovementGenerator.cpp
index 4245bffb864..7ab7534199a 100644
--- a/src/server/game/Movement/MovementGenerators/HomeMovementGenerator.cpp
+++ b/src/server/game/Movement/MovementGenerators/HomeMovementGenerator.cpp
@@ -33,7 +33,7 @@ void HomeMovementGenerator<Creature>::DoFinalize(Creature* owner)
{
owner->ClearUnitState(UNIT_STATE_EVADE);
owner->SetWalk(true);
- owner->LoadCreaturesAddon(true);
+ owner->LoadCreaturesAddon();
owner->AI()->JustReachedHome();
}
}
diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp
index 98267346234..a443aab32e6 100644
--- a/src/server/game/Spells/Spell.cpp
+++ b/src/server/game/Spells/Spell.cpp
@@ -53,6 +53,7 @@
#include "SpellHistory.h"
#include "Battlefield.h"
#include "BattlefieldMgr.h"
+#include "TradeData.h"
extern pEffect SpellEffects[TOTAL_SPELL_EFFECTS];
diff --git a/src/server/game/Spells/SpellHistory.cpp b/src/server/game/Spells/SpellHistory.cpp
index d2cf03dc936..ed5c31c25c6 100644
--- a/src/server/game/Spells/SpellHistory.cpp
+++ b/src/server/game/Spells/SpellHistory.cpp
@@ -41,6 +41,7 @@ struct SpellHistory::PersistenceHelper<Player>
if (!sSpellMgr->GetSpellInfo(*spellId))
return false;
+ cooldownEntry->SpellId = *spellId;
cooldownEntry->CooldownEnd = Clock::from_time_t(time_t(fields[2].GetUInt32()));
cooldownEntry->ItemId = fields[1].GetUInt32();
cooldownEntry->CategoryId = fields[3].GetUInt32();
@@ -72,6 +73,7 @@ struct SpellHistory::PersistenceHelper<Pet>
if (!sSpellMgr->GetSpellInfo(*spellId))
return false;
+ cooldownEntry->SpellId = *spellId;
cooldownEntry->CooldownEnd = Clock::from_time_t(time_t(fields[1].GetUInt32()));
cooldownEntry->ItemId = 0;
cooldownEntry->CategoryId = fields[2].GetUInt32();
@@ -280,32 +282,7 @@ void SpellHistory::StartCooldown(SpellInfo const* spellInfo, uint32 itemId, Spel
int32 cooldown = -1;
int32 categoryCooldown = -1;
- // some special item spells without correct cooldown in SpellInfo
- // cooldown information stored in item prototype
- if (itemId)
- {
- if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId))
- {
- for (uint8 idx = 0; idx < MAX_ITEM_PROTO_SPELLS; ++idx)
- {
- if (uint32(proto->Spells[idx].SpellId) == spellInfo->Id)
- {
- categoryId = proto->Spells[idx].SpellCategory;
- cooldown = proto->Spells[idx].SpellCooldown;
- categoryCooldown = proto->Spells[idx].SpellCategoryCooldown;
- break;
- }
- }
- }
- }
-
- // if no cooldown found above then base at DBC data
- if (cooldown < 0 && categoryCooldown < 0)
- {
- categoryId = spellInfo->GetCategory();
- cooldown = spellInfo->RecoveryTime;
- categoryCooldown = spellInfo->CategoryRecoveryTime;
- }
+ GetCooldownDurations(spellInfo, itemId, &cooldown, &categoryId, &categoryCooldown);
Clock::time_point curTime = Clock::now();
Clock::time_point catrecTime;
@@ -381,23 +358,39 @@ void SpellHistory::StartCooldown(SpellInfo const* spellInfo, uint32 itemId, Spel
void SpellHistory::SendCooldownEvent(SpellInfo const* spellInfo, uint32 itemId /*= 0*/, Spell* spell /*= nullptr*/, bool startCooldown /*= true*/)
{
- // start cooldowns at server side, if any
- if (startCooldown)
- StartCooldown(spellInfo, itemId, spell);
-
// Send activate cooldown timer (possible 0) at client side
if (Player* player = GetPlayerOwner())
{
+ uint32 category = spellInfo->GetCategory();
+ GetCooldownDurations(spellInfo, itemId, nullptr, &category, nullptr);
+
+ auto categoryItr = _categoryCooldowns.find(category);
+ if (categoryItr != _categoryCooldowns.end() && categoryItr->second->SpellId != spellInfo->Id)
+ {
+ WorldPacket data(SMSG_COOLDOWN_EVENT, 4 + 8);
+ data << uint32(categoryItr->second->SpellId);
+ data << uint64(_owner->GetGUID());
+ player->SendDirectMessage(&data);
+
+ if (startCooldown)
+ StartCooldown(sSpellMgr->EnsureSpellInfo(categoryItr->second->SpellId), itemId, spell);
+ }
+
WorldPacket data(SMSG_COOLDOWN_EVENT, 4 + 8);
data << uint32(spellInfo->Id);
data << uint64(_owner->GetGUID());
player->SendDirectMessage(&data);
}
+
+ // start cooldowns at server side, if any
+ if (startCooldown)
+ StartCooldown(spellInfo, itemId, spell);
}
void SpellHistory::AddCooldown(uint32 spellId, uint32 itemId, Clock::time_point cooldownEnd, uint32 categoryId, Clock::time_point categoryEnd, bool onHold /*= false*/)
{
CooldownEntry& cooldownEntry = _spellCooldowns[spellId];
+ cooldownEntry.SpellId = spellId;
cooldownEntry.CooldownEnd = cooldownEnd;
cooldownEntry.ItemId = itemId;
cooldownEntry.CategoryId = categoryId;
@@ -478,21 +471,7 @@ bool SpellHistory::HasCooldown(SpellInfo const* spellInfo, uint32 itemId /*= 0*/
return true;
uint32 category = 0;
- if (ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(itemId))
- {
- for (uint32 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i)
- {
- if (uint32(itemTemplate->Spells[i].SpellId) == spellInfo->Id)
- {
- category = itemTemplate->Spells[i].SpellCategory;
- break;
- }
- }
- }
-
- if (!category)
- category = spellInfo->GetCategory();
-
+ GetCooldownDurations(spellInfo, itemId, nullptr, &category, nullptr);
if (!category)
return false;
@@ -651,6 +630,48 @@ void SpellHistory::BuildCooldownPacket(WorldPacket& data, uint8 flags, PacketCoo
}
}
+void SpellHistory::GetCooldownDurations(SpellInfo const* spellInfo, uint32 itemId, int32* cooldown, uint32* categoryId, int32* categoryCooldown)
+{
+ ASSERT(cooldown || categoryId || categoryCooldown);
+ int32 tmpCooldown = -1;
+ uint32 tmpCategoryId = 0;
+ int32 tmpCategoryCooldown = -1;
+
+ // some special item spells without correct cooldown in SpellInfo
+ // cooldown information stored in item prototype
+ if (itemId)
+ {
+ if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId))
+ {
+ for (uint8 idx = 0; idx < MAX_ITEM_PROTO_SPELLS; ++idx)
+ {
+ if (uint32(proto->Spells[idx].SpellId) == spellInfo->Id)
+ {
+ tmpCooldown = proto->Spells[idx].SpellCooldown;
+ tmpCategoryId = proto->Spells[idx].SpellCategory;
+ tmpCategoryCooldown = proto->Spells[idx].SpellCategoryCooldown;
+ break;
+ }
+ }
+ }
+ }
+
+ // if no cooldown found above then base at DBC data
+ if (tmpCooldown < 0 && tmpCategoryCooldown < 0)
+ {
+ tmpCooldown = spellInfo->RecoveryTime;
+ tmpCategoryId = spellInfo->GetCategory();
+ tmpCategoryCooldown = spellInfo->CategoryRecoveryTime;
+ }
+
+ if (cooldown)
+ *cooldown = tmpCooldown;
+ if (categoryId)
+ *categoryId = tmpCategoryId;
+ if (categoryCooldown)
+ *categoryCooldown = tmpCategoryCooldown;
+}
+
void SpellHistory::SaveCooldownStateBeforeDuel()
{
_spellCooldownsBeforeDuel = _spellCooldowns;
@@ -671,19 +692,14 @@ void SpellHistory::RestoreCooldownStateAfterDuel()
_spellCooldownsBeforeDuel[itr->first] = _spellCooldowns[itr->first];
}
- _spellCooldowns = _spellCooldownsBeforeDuel;
-
- // update the client: clear all cooldowns
- std::vector<int32> resetCooldowns;
- resetCooldowns.reserve(_spellCooldowns.size());
-
- for (auto itr = _spellCooldowns.begin(); itr != _spellCooldowns.end(); ++itr)
- resetCooldowns.push_back(itr->first);
-
- if (resetCooldowns.empty())
- return;
-
- SendClearCooldowns(resetCooldowns);
+ // check for spell with onHold active before and during the duel
+ for (auto itr = _spellCooldownsBeforeDuel.begin(); itr != _spellCooldownsBeforeDuel.end(); ++itr)
+ {
+ if (!itr->second.OnHold &&
+ _spellCooldowns.find(itr->first) != _spellCooldowns.end() &&
+ !_spellCooldowns[itr->first].OnHold)
+ _spellCooldowns[itr->first] = _spellCooldownsBeforeDuel[itr->first];
+ }
// update the client: restore old cooldowns
PacketCooldowns cooldowns;
@@ -694,14 +710,14 @@ void SpellHistory::RestoreCooldownStateAfterDuel()
uint32 cooldownDuration = itr->second.CooldownEnd > now ? std::chrono::duration_cast<std::chrono::milliseconds>(itr->second.CooldownEnd - now).count() : 0;
// cooldownDuration must be between 0 and 10 minutes in order to avoid any visual bugs
- if (cooldownDuration == 0 || cooldownDuration > 10 * MINUTE * IN_MILLISECONDS)
+ if (cooldownDuration <= 0 || cooldownDuration > 10 * MINUTE * IN_MILLISECONDS || itr->second.OnHold)
continue;
cooldowns[itr->first] = cooldownDuration;
}
WorldPacket data;
- BuildCooldownPacket(data, SPELL_COOLDOWN_FLAG_NONE, cooldowns);
+ BuildCooldownPacket(data, SPELL_COOLDOWN_FLAG_INCLUDE_EVENT_COOLDOWNS, cooldowns);
player->SendDirectMessage(&data);
}
}
diff --git a/src/server/game/Spells/SpellHistory.h b/src/server/game/Spells/SpellHistory.h
index 6a1da28f08f..db65cd50c3e 100644
--- a/src/server/game/Spells/SpellHistory.h
+++ b/src/server/game/Spells/SpellHistory.h
@@ -38,6 +38,7 @@ public:
struct CooldownEntry
{
+ uint32 SpellId = 0;
Clock::time_point CooldownEnd;
uint32 ItemId = 0;
uint32 CategoryId = 0;
@@ -135,6 +136,8 @@ private:
typedef std::unordered_map<uint32, uint32> PacketCooldowns;
void BuildCooldownPacket(WorldPacket& data, uint8 flags, PacketCooldowns const& cooldowns) const;
+ static void GetCooldownDurations(SpellInfo const* spellInfo, uint32 itemId, int32* cooldown, uint32* categoryId, int32* categoryCooldown);
+
Unit* _owner;
CooldownStorageType _spellCooldowns;
CooldownStorageType _spellCooldownsBeforeDuel;
diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp
index bb4de29ad56..c41caa8f955 100644
--- a/src/server/game/World/World.cpp
+++ b/src/server/game/World/World.cpp
@@ -1183,6 +1183,7 @@ void World::LoadConfigSettings(bool reload)
m_bool_configs[CONFIG_START_ALL_SPELLS] = sConfigMgr->GetBoolDefault("PlayerStart.AllSpells", false);
m_int_configs[CONFIG_HONOR_AFTER_DUEL] = sConfigMgr->GetIntDefault("HonorPointsAfterDuel", 0);
m_bool_configs[CONFIG_RESET_DUEL_COOLDOWNS] = sConfigMgr->GetBoolDefault("ResetDuelCooldowns", false);
+ m_bool_configs[CONFIG_RESET_DUEL_HEALTH_MANA] = sConfigMgr->GetBoolDefault("ResetDuelHealthMana", false);
m_bool_configs[CONFIG_START_ALL_EXPLORED] = sConfigMgr->GetBoolDefault("PlayerStart.MapsExplored", false);
m_bool_configs[CONFIG_START_ALL_REP] = sConfigMgr->GetBoolDefault("PlayerStart.AllReputation", false);
m_bool_configs[CONFIG_ALWAYS_MAXSKILL] = sConfigMgr->GetBoolDefault("AlwaysMaxWeaponSkill", false);
diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h
index e3dc3b55681..133ac3f2386 100644
--- a/src/server/game/World/World.h
+++ b/src/server/game/World/World.h
@@ -164,6 +164,7 @@ enum WorldBoolConfigs
CONFIG_CALCULATE_CREATURE_ZONE_AREA_DATA,
CONFIG_CALCULATE_GAMEOBJECT_ZONE_AREA_DATA,
CONFIG_RESET_DUEL_COOLDOWNS,
+ CONFIG_RESET_DUEL_HEALTH_MANA,
BOOL_CONFIG_VALUE_COUNT
};
diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_valithria_dreamwalker.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_valithria_dreamwalker.cpp
index f9d5a310526..4a76dc667e8 100644
--- a/src/server/scripts/Northrend/IcecrownCitadel/boss_valithria_dreamwalker.cpp
+++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_valithria_dreamwalker.cpp
@@ -309,7 +309,7 @@ class boss_valithria_dreamwalker : public CreatureScript
{
me->SetHealth(_spawnHealth);
me->SetReactState(REACT_PASSIVE);
- me->LoadCreaturesAddon(true);
+ me->LoadCreaturesAddon();
// immune to percent heals
me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_OBS_MOD_HEALTH, true);
me->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_HEAL_PCT, true);
@@ -1072,7 +1072,7 @@ class npc_dream_cloud : public CreatureScript
_events.Reset();
_events.ScheduleEvent(EVENT_CHECK_PLAYER, 1000);
me->SetCorpseDelay(0); // remove corpse immediately
- me->LoadCreaturesAddon(true);
+ me->LoadCreaturesAddon();
}
void UpdateAI(uint32 diff) override
diff --git a/src/server/scripts/Northrend/Naxxramas/boss_thaddius.cpp b/src/server/scripts/Northrend/Naxxramas/boss_thaddius.cpp
index d74fd5a03f8..50077fe9dc1 100644
--- a/src/server/scripts/Northrend/Naxxramas/boss_thaddius.cpp
+++ b/src/server/scripts/Northrend/Naxxramas/boss_thaddius.cpp
@@ -19,48 +19,104 @@
#include "ScriptedCreature.h"
#include "SpellScript.h"
#include "Player.h"
+#include "ObjectGuid.h"
#include "naxxramas.h"
-//Stalagg
-enum StalaggYells
+
+enum Phases
+{
+ PHASE_NOT_ENGAGED = 1,
+ PHASE_PETS,
+ PHASE_TRANSITION,
+ PHASE_THADDIUS,
+ PHASE_RESETTING
+};
+
+enum AIActions
{
- SAY_STAL_AGGRO = 0,
- SAY_STAL_SLAY = 1,
- SAY_STAL_DEATH = 2
+ ACTION_RESET_ENCOUNTER_TIMER = -1, // sent from instance AI
+ ACTION_BEGIN_RESET_ENCOUNTER = 0, // sent from thaddius to pets to trigger despawn and encounter reset
+ ACTION_RESET_ENCOUNTER, // sent from thaddius to pets to trigger respawn and full reset
+ ACTION_FEUGEN_DIED, // sent from respective pet to thaddius to indicate death
+ ACTION_STALAGG_DIED, // ^
+ ACTION_FEUGEN_RESET, // pet to thaddius
+ ACTION_STALAGG_RESET, // ^
+ ACTION_FEUGEN_AGGRO, // pet to thaddius on combat start
+ ACTION_STALAGG_AGGRO, // ^
+ ACTION_FEUGEN_REVIVING_FX, // thaddius to pet when pet reports its death
+ ACTION_STALAGG_REVIVING_FX, // ^
+ ACTION_FEUGEN_REVIVED, // thaddius to pet when pet should revive
+ ACTION_STALAGG_REVIVED, // ^
+ ACTION_TRANSITION, // thaddius to pets when transition starts (coil overload anim)
+ ACTION_TRANSITION_2, // thaddius to pets to make the coils shock him
+ ACTION_TRANSITION_3, // thaddius to pets to disable coil GO after spawn
+
+ ACTION_POLARITY_CROSSED // triggers achievement failure, sent from spellscript
};
-enum StalagSpells
+enum Events
{
- SPELL_POWERSURGE = 28134,
- SPELL_MAGNETIC_PULL = 28338,
- SPELL_STALAGG_TESLA = 28097
+ EVENT_SHIFT = 1, // polarity shift
+ EVENT_SHIFT_TALK, // polarity shift yell (hack? couldn't find any event for cast finish)
+ EVENT_CHAIN, // chain lightning
+ EVENT_BERSERK, // enrage timer
+ EVENT_REVIVE_FEUGEN, // timer until feugen is revived (if stalagg still lives)
+ EVENT_REVIVE_STALAGG, // timer until stalagg is revived (if feugen still lives)
+ EVENT_TRANSITION_1, // timer until overload emote
+ EVENT_TRANSITION_2, // timer until thaddius gets zapped by the coils
+ EVENT_TRANSITION_3, // timer until thaddius engages
+ EVENT_ENABLE_BALL_LIGHTNING // grace period after thaddius aggro after which he starts being a baller (e.g. tossing ball lightning at out of range targets)
};
-//Feugen
-enum FeugenYells
+enum Misc
{
- SAY_FEUG_AGGRO = 0,
- SAY_FEUG_SLAY = 1,
- SAY_FEUG_DEATH = 2
+ MAX_POLARITY_10M = 5,
+ MAX_POLARITY_25M = 13,
+
+ DATA_POLARITY_CROSSED = 1,
+};
+
+// Feugen & Stalagg
+enum PetYells
+{
+ SAY_STALAGG_AGGRO = 0,
+ SAY_STALAGG_SLAY = 1,
+ SAY_STALAGG_DEATH = 2,
+
+ SAY_FEUGEN_AGGRO = 0,
+ SAY_FEUGEN_SLAY = 1,
+ SAY_FEUGEN_DEATH = 2,
+
+ EMOTE_FEIGN_DEATH = 3,
+ EMOTE_FEIGN_REVIVE = 4,
+
+ EMOTE_TESLA_LINK_BREAKS = 0,
+ EMOTE_TESLA_OVERLOAD = 1
};
-enum FeugenSpells
+enum PetSpells
{
- SPELL_STATICFIELD = 28135,
- SPELL_FEUGEN_TESLA = 28109
+ SPELL_STALAGG_POWERSURGE = 28134,
+ //SPELL_STALAGG_TESLA = 28097,
+ SPELL_STALAGG_TESLA_PERIODIC = 28098,
+ SPELL_STALAGG_CHAIN_VISUAL = 28096,
+
+ SPELL_FEUGEN_STATICFIELD = 28135,
+ //SPELL_FEUGEN_TESLA = 28109,
+ SPELL_FEUGEN_TESLA_PERIODIC = 28110,
+ SPELL_FEUGEN_CHAIN_VISUAL = 28111,
+
+ SPELL_MAGNETIC_PULL = 54517,
+ SPELL_MAGNETIC_PULL_EFFECT = 28337,
+
+ SPELL_TESLA_SHOCK = 28099
};
-// Thaddius DoAction
-enum ThaddiusActions
+enum PetMisc
{
- ACTION_FEUGEN_RESET,
- ACTION_FEUGEN_DIED,
- ACTION_STALAGG_RESET,
- ACTION_STALAGG_DIED
+ OVERLOAD_DISTANCE = 28
};
-//generic
-#define C_TESLA_COIL 16218 //the coils (emotes "Tesla Coil overloads!")
//Thaddius
enum ThaddiusYells
@@ -70,34 +126,31 @@ enum ThaddiusYells
SAY_SLAY = 2,
SAY_ELECT = 3,
SAY_DEATH = 4,
- SAY_SCREAM = 5
+ SAY_SCREAM = 5,
+
+ EMOTE_POLARITY_SHIFTED = 6
};
enum ThaddiusSpells
{
- SPELL_POLARITY_SHIFT = 28089,
- SPELL_BALL_LIGHTNING = 28299,
- SPELL_CHAIN_LIGHTNING = 28167,
- SPELL_BERSERK = 27680,
- SPELL_POSITIVE_CHARGE = 28062,
- SPELL_POSITIVE_CHARGE_STACK = 29659,
- SPELL_NEGATIVE_CHARGE = 28085,
- SPELL_NEGATIVE_CHARGE_STACK = 29660,
- SPELL_POSITIVE_POLARITY = 28059,
- SPELL_NEGATIVE_POLARITY = 28084,
-};
+ SPELL_THADDIUS_INACTIVE_VISUAL = 28160,
+ SPELL_THADDIUS_SPARK_VISUAL = 28136,
+ SPELL_SHOCK_VISUAL = 28159,
-enum Events
-{
- EVENT_NONE,
- EVENT_SHIFT,
- EVENT_CHAIN,
- EVENT_BERSERK,
-};
+ SPELL_BALL_LIGHTNING = 28299,
+ SPELL_CHAIN_LIGHTNING = 28167,
+ SPELL_BERSERK = 27680,
-enum Achievement
-{
- DATA_POLARITY_SWITCH = 76047605,
+ // polarity handling
+ SPELL_POLARITY_SHIFT = 28089,
+
+ SPELL_POSITIVE_CHARGE_APPLY = 28059,
+ SPELL_POSITIVE_CHARGE_TICK = 28062,
+ SPELL_POSITIVE_CHARGE_AMP = 29659,
+
+ SPELL_NEGATIVE_CHARGE_APPLY = 28084,
+ SPELL_NEGATIVE_CHARGE_TICK = 28085,
+ SPELL_NEGATIVE_CHARGE_AMP = 29660,
};
class boss_thaddius : public CreatureScript
@@ -112,166 +165,272 @@ public:
struct boss_thaddiusAI : public BossAI
{
- boss_thaddiusAI(Creature* creature) : BossAI(creature, BOSS_THADDIUS)
- {
- // init is a bit tricky because thaddius shall track the life of both adds, but not if there was a wipe
- // and, in particular, if there was a crash after both adds were killed (should not respawn)
-
- // Moreover, the adds may not yet be spawn. So just track down the status if mob is spawn
- // and each mob will send its status at reset (meaning that it is alive)
- checkFeugenAlive = false;
- if (Creature* pFeugen = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_FEUGEN)))
- checkFeugenAlive = pFeugen->IsAlive();
+ public:
+ boss_thaddiusAI(Creature* creature) : BossAI(creature, BOSS_THADDIUS), stalaggAlive(true), feugenAlive(true), ballLightningEnabled(false), shockingEligibility(true)
+ {
+ if (instance->GetBossState(BOSS_THADDIUS) != DONE)
+ {
+ events.SetPhase(PHASE_NOT_ENGAGED);
+ SetCombatMovement(false);
- checkStalaggAlive = false;
- if (Creature* pStalagg = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_STALAGG)))
- checkStalaggAlive = pStalagg->IsAlive();
+ BeginResetEncounter(); // initialize everything properly, and ensure that the coils are loaded by the time we initialize
+ }
+ }
- if (!checkFeugenAlive && !checkStalaggAlive)
+ void KilledUnit(Unit* victim) override
{
- me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_NOT_SELECTABLE | UNIT_FLAG_STUNNED);
- me->SetReactState(REACT_AGGRESSIVE);
+ if (victim->GetTypeId() == TYPEID_PLAYER)
+ Talk(SAY_SLAY);
}
- else
+
+ void Reset() override
{
- me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_NOT_SELECTABLE | UNIT_FLAG_STUNNED);
- me->SetReactState(REACT_PASSIVE);
+ if(events.IsInPhase(PHASE_TRANSITION) || events.IsInPhase(PHASE_THADDIUS))
+ BeginResetEncounter();
}
- polaritySwitch = false;
- uiAddsTimer = 0;
- }
-
- bool checkStalaggAlive;
- bool checkFeugenAlive;
- bool polaritySwitch;
- uint32 uiAddsTimer;
-
- void KilledUnit(Unit* /*victim*/) override
- {
- if (!(rand32() % 5))
- Talk(SAY_SLAY);
- }
-
- void JustDied(Unit* /*killer*/) override
- {
- _JustDied();
- Talk(SAY_DEATH);
- }
+ void JustDied(Unit* /*killer*/) override
+ {
+ _JustDied();
+ me->setActive(false);
+ if (Creature* stalagg = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_STALAGG)))
+ stalagg->setActive(false);
+ if (Creature* feugen = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_FEUGEN)))
+ feugen->setActive(false);
+ Talk(SAY_DEATH);
+ }
- void DoAction(int32 action) override
- {
- switch (action)
+ void DoAction(int32 action) override
{
- case ACTION_FEUGEN_RESET:
- checkFeugenAlive = true;
- break;
- case ACTION_FEUGEN_DIED:
- checkFeugenAlive = false;
- break;
- case ACTION_STALAGG_RESET:
- checkStalaggAlive = true;
- break;
- case ACTION_STALAGG_DIED:
- checkStalaggAlive = false;
- break;
+ switch (action)
+ {
+ case ACTION_RESET_ENCOUNTER_TIMER:
+ if (events.IsInPhase(PHASE_RESETTING))
+ ResetEncounter();
+ break;
+ case ACTION_FEUGEN_RESET:
+ case ACTION_STALAGG_RESET:
+ if (!events.IsInPhase(PHASE_NOT_ENGAGED) && !events.IsInPhase(PHASE_RESETTING))
+ BeginResetEncounter();
+ break;
+ case ACTION_FEUGEN_AGGRO:
+ case ACTION_STALAGG_AGGRO:
+ if (events.IsInPhase(PHASE_RESETTING))
+ return BeginResetEncounter();
+ if (!events.IsInPhase(PHASE_NOT_ENGAGED))
+ return;
+ events.SetPhase(PHASE_PETS);
+
+ shockingEligibility = true;
+
+ if (!instance->CheckRequiredBosses(BOSS_THADDIUS))
+ return BeginResetEncounter();
+ instance->SetBossState(BOSS_THADDIUS, IN_PROGRESS);
+
+ me->setActive(true);
+ if (Creature* stalagg = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_STALAGG)))
+ stalagg->setActive(true);
+ if (Creature* feugen = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_FEUGEN)))
+ feugen->setActive(true);
+ break;
+ case ACTION_FEUGEN_DIED:
+ if (Creature* feugen = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_FEUGEN)))
+ feugen->AI()->DoAction(ACTION_FEUGEN_REVIVING_FX);
+ feugenAlive = false;
+ if (stalaggAlive)
+ events.ScheduleEvent(EVENT_REVIVE_FEUGEN, 5 * IN_MILLISECONDS, 0, PHASE_PETS);
+ else
+ Transition();
+
+ break;
+ case ACTION_STALAGG_DIED:
+ if (Creature* stalagg = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_STALAGG)))
+ stalagg->AI()->DoAction(ACTION_STALAGG_REVIVING_FX);
+ stalaggAlive = false;
+ if (feugenAlive)
+ events.ScheduleEvent(EVENT_REVIVE_STALAGG, 5 * IN_MILLISECONDS, 0, PHASE_PETS);
+ else
+ Transition();
+
+ break;
+
+ case ACTION_POLARITY_CROSSED:
+ shockingEligibility = false;
+ break;
+ default:
+ break;
+ }
}
- if (!checkFeugenAlive && !checkStalaggAlive)
+ uint32 GetData(uint32 id) const override
{
- me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_NOT_SELECTABLE | UNIT_FLAG_STUNNED);
- // REACT_AGGRESSIVE only reset when he takes damage.
- DoZoneInCombat();
+ return (id == DATA_POLARITY_CROSSED && shockingEligibility) ? 1u : 0u;
}
- else
+
+ void Transition() // initiate transition between pet phase and thaddius phase
{
- me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_NOT_SELECTABLE | UNIT_FLAG_STUNNED);
- me->SetReactState(REACT_PASSIVE);
+ events.SetPhase(PHASE_TRANSITION);
+
+ me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE);
+
+ events.ScheduleEvent(EVENT_TRANSITION_1, 10 * IN_MILLISECONDS, 0, PHASE_TRANSITION);
+ events.ScheduleEvent(EVENT_TRANSITION_2, 12 * IN_MILLISECONDS, 0, PHASE_TRANSITION);
+ events.ScheduleEvent(EVENT_TRANSITION_3, 14 * IN_MILLISECONDS, 0, PHASE_TRANSITION);
}
- }
- void EnterCombat(Unit* /*who*/) override
- {
- _EnterCombat();
- Talk(SAY_AGGRO);
- events.ScheduleEvent(EVENT_SHIFT, 30000);
- events.ScheduleEvent(EVENT_CHAIN, urand(10000, 20000));
- events.ScheduleEvent(EVENT_BERSERK, 360000);
- }
+ void BeginResetEncounter()
+ {
+ if (!me->IsAlive())
+ return;
+ if (events.IsInPhase(PHASE_RESETTING))
+ return;
+
+ instance->ProcessEvent(me, EVENT_THADDIUS_BEGIN_RESET);
- void DamageTaken(Unit* /*pDoneBy*/, uint32 & /*uiDamage*/) override
- {
- me->SetReactState(REACT_AGGRESSIVE);
- }
+ instance->SetBossState(BOSS_THADDIUS, NOT_STARTED);
- void SetData(uint32 id, uint32 data) override
- {
- if (id == DATA_POLARITY_SWITCH)
- polaritySwitch = data ? true : false;
- }
+ // remove polarity shift debuffs on reset
+ instance->DoRemoveAurasDueToSpellOnPlayers(SPELL_POSITIVE_CHARGE_APPLY);
+ instance->DoRemoveAurasDueToSpellOnPlayers(SPELL_NEGATIVE_CHARGE_APPLY);
- uint32 GetData(uint32 id) const override
- {
- if (id != DATA_POLARITY_SWITCH)
- return 0;
+ me->DespawnOrUnsummon();
- return uint32(polaritySwitch);
- }
+ me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_NOT_SELECTABLE | UNIT_FLAG_STUNNED);
+ events.SetPhase(PHASE_RESETTING);
+ if (Creature* feugen = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_FEUGEN)))
+ feugen->AI()->DoAction(ACTION_BEGIN_RESET_ENCOUNTER);
+ if (Creature* stalagg = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_STALAGG)))
+ stalagg->AI()->DoAction(ACTION_BEGIN_RESET_ENCOUNTER);
- void UpdateAI(uint32 diff) override
- {
- if (checkFeugenAlive && checkStalaggAlive)
- uiAddsTimer = 0;
+ me->setActive(false);
+ }
+
+ void ResetEncounter()
+ {
+ events.SetPhase(PHASE_NOT_ENGAGED);
+ feugenAlive = true;
+ stalaggAlive = true;
+ me->Respawn(true);
+ me->CastSpell(me, SPELL_THADDIUS_INACTIVE_VISUAL);
+
+ if (Creature* feugen = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_FEUGEN)))
+ feugen->AI()->DoAction(ACTION_RESET_ENCOUNTER);
+ if (Creature* stalagg = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_STALAGG)))
+ stalagg->AI()->DoAction(ACTION_RESET_ENCOUNTER);
+ _Reset();
+ }
- if (checkStalaggAlive != checkFeugenAlive)
+ void UpdateAI(uint32 diff) override
{
- uiAddsTimer += diff;
- if (uiAddsTimer > 5000)
+ if (events.IsInPhase(PHASE_NOT_ENGAGED))
+ return;
+ if (events.IsInPhase(PHASE_THADDIUS) && !UpdateVictim())
+ return;
+
+ events.Update(diff);
+ while (uint32 eventId = events.ExecuteEvent())
{
- if (!checkStalaggAlive)
+ switch (eventId)
{
- if (Creature* pStalagg = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_STALAGG)))
- pStalagg->Respawn();
- }
- else
- {
- if (Creature* pFeugen = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_FEUGEN)))
- pFeugen->Respawn();
+ case EVENT_REVIVE_FEUGEN:
+ feugenAlive = true;
+ if (Creature* feugen = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_FEUGEN)))
+ feugen->AI()->DoAction(ACTION_FEUGEN_REVIVED);
+ break;
+ case EVENT_REVIVE_STALAGG:
+ stalaggAlive = true;
+ if (Creature* stalagg = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_STALAGG)))
+ stalagg->AI()->DoAction(ACTION_STALAGG_REVIVED);
+ break;
+ case EVENT_TRANSITION_1: // tesla coils overload
+ if (Creature* feugen = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_FEUGEN)))
+ feugen->AI()->DoAction(ACTION_TRANSITION);
+ if (Creature* stalagg = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_STALAGG)))
+ stalagg->AI()->DoAction(ACTION_TRANSITION);
+ break;
+ case EVENT_TRANSITION_2: // tesla coils shock thaddius
+ me->CastSpell(me, SPELL_THADDIUS_SPARK_VISUAL, true);
+ if (Creature* feugen = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_FEUGEN)))
+ feugen->AI()->DoAction(ACTION_TRANSITION_2);
+ if (Creature* stalagg = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_STALAGG)))
+ stalagg->AI()->DoAction(ACTION_TRANSITION_2);
+ break;
+ case EVENT_TRANSITION_3: // thaddius becomes active
+ me->CastSpell(me, SPELL_THADDIUS_SPARK_VISUAL, true);
+ ballLightningEnabled = false;
+ me->RemoveAura(SPELL_THADDIUS_INACTIVE_VISUAL);
+ me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_STUNNED);
+ me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC);
+
+ DoZoneInCombat();
+ if (Unit* closest = SelectTarget(SELECT_TARGET_NEAREST, 0, 500.0f))
+ AttackStart(closest);
+ else // if there is no nearest target, then there is no target, meaning we should reset
+ return BeginResetEncounter();
+
+ if (Creature* feugen = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_FEUGEN)))
+ feugen->AI()->DoAction(ACTION_TRANSITION_3);
+ if (Creature* stalagg = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_STALAGG)))
+ stalagg->AI()->DoAction(ACTION_TRANSITION_3);
+
+ events.SetPhase(PHASE_THADDIUS);
+
+ Talk(SAY_AGGRO);
+
+ events.ScheduleEvent(EVENT_ENABLE_BALL_LIGHTNING, 5 * IN_MILLISECONDS, 0, PHASE_THADDIUS);
+ events.ScheduleEvent(EVENT_SHIFT, 10 * IN_MILLISECONDS, 0, PHASE_THADDIUS);
+ events.ScheduleEvent(EVENT_CHAIN, urand(10, 20) * IN_MILLISECONDS, 0, PHASE_THADDIUS);
+ events.ScheduleEvent(EVENT_BERSERK, 6 * MINUTE * IN_MILLISECONDS, 0, PHASE_THADDIUS);
+
+ break;
+ case EVENT_ENABLE_BALL_LIGHTNING:
+ ballLightningEnabled = true;
+ break;
+ case EVENT_SHIFT:
+ me->CastStop(); // shift overrides all other spells
+ DoCastAOE(SPELL_POLARITY_SHIFT);
+ events.ScheduleEvent(EVENT_SHIFT_TALK, 3 * IN_MILLISECONDS, PHASE_THADDIUS);
+ events.ScheduleEvent(EVENT_SHIFT, 30 * IN_MILLISECONDS, PHASE_THADDIUS);
+ break;
+ case EVENT_SHIFT_TALK:
+ Talk(SAY_ELECT);
+ Talk(EMOTE_POLARITY_SHIFTED);
+ break;
+ case EVENT_CHAIN:
+ if (me->FindCurrentSpellBySpellId(SPELL_POLARITY_SHIFT)) // delay until shift is over
+ events.ScheduleEvent(EVENT_CHAIN, 3 * IN_MILLISECONDS, 0, PHASE_THADDIUS);
+ else
+ {
+ me->CastStop();
+ DoCastVictim(SPELL_CHAIN_LIGHTNING);
+ events.ScheduleEvent(EVENT_CHAIN, urand(10, 20) * IN_MILLISECONDS, PHASE_THADDIUS);
+ }
+ break;
+ case EVENT_BERSERK:
+ me->CastStop();
+ DoCast(me, SPELL_BERSERK);
+ break;
+ default:
+ break;
}
}
- }
- if (!UpdateVictim())
- return;
-
- events.Update(diff);
-
- if (me->HasUnitState(UNIT_STATE_CASTING))
- return;
-
- while (uint32 eventId = events.ExecuteEvent())
- {
- switch (eventId)
+ if (events.IsInPhase(PHASE_THADDIUS))
{
- case EVENT_SHIFT:
- DoCastAOE(SPELL_POLARITY_SHIFT);
- events.ScheduleEvent(EVENT_SHIFT, 30000);
- return;
- case EVENT_CHAIN:
- DoCastVictim(SPELL_CHAIN_LIGHTNING);
- events.ScheduleEvent(EVENT_CHAIN, urand(10000, 20000));
- return;
- case EVENT_BERSERK:
- DoCast(me, SPELL_BERSERK);
- return;
+ if (me->IsWithinMeleeRange(me->GetVictim()))
+ DoMeleeAttackIfReady();
+ else
+ if (ballLightningEnabled)
+ if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM))
+ DoCast(target, SPELL_BALL_LIGHTNING);
}
}
- if (events.GetTimer() > 15000 && !me->IsWithinMeleeRange(me->GetVictim()))
- DoCastVictim(SPELL_BALL_LIGHTNING);
- else
- DoMeleeAttackIfReady();
- }
+ private:
+ bool stalaggAlive;
+ bool feugenAlive;
+ bool ballLightningEnabled;
+ bool shockingEligibility;
};
};
@@ -288,88 +447,257 @@ public:
struct npc_stalaggAI : public ScriptedAI
{
- npc_stalaggAI(Creature* creature) : ScriptedAI(creature)
- {
- Initialize();
- instance = creature->GetInstanceScript();
- }
+ public:
+ npc_stalaggAI(Creature* creature) : ScriptedAI(creature), _myCoil(ObjectGuid::Empty), _myCoilGO(ObjectGuid::Empty), isOverloading(false), refreshBeam(false), isFeignDeath(false)
+ {
+ Initialize();
+ instance = creature->GetInstanceScript();
+ }
- void Initialize()
- {
- powerSurgeTimer = urand(20000, 25000);
- magneticPullTimer = 20000;
- }
+ void Initialize()
+ {
+ if (GameObject* coil = myCoilGO())
+ coil->SetGoState(GO_STATE_ACTIVE);
- InstanceScript* instance;
+ // if the encounter reset while feigning death
+ me->SetStandState(UNIT_STAND_STATE_STAND);
+ me->SetReactState(REACT_AGGRESSIVE);
+ me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE);
+ isOverloading = false;
+ isFeignDeath = false;
- uint32 powerSurgeTimer;
- uint32 magneticPullTimer;
+ // force tesla coil state refresh
+ refreshBeam = true;
- void Reset() override
- {
- if (Creature* pThaddius = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_THADDIUS)))
- if (pThaddius->AI())
- pThaddius->AI()->DoAction(ACTION_STALAGG_RESET);
- Initialize();
- }
+ powerSurgeTimer = 10 * IN_MILLISECONDS;
+ }
- void KilledUnit(Unit* /*victim*/) override
- {
- if (!(rand32() % 5))
- Talk(SAY_STAL_SLAY);
- }
+ void Reset() override
+ {
+ if (isFeignDeath || !me->IsAlive())
+ return;
+ if (Creature* thaddius = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_THADDIUS)))
+ thaddius->AI()->DoAction(ACTION_STALAGG_RESET);
+ }
- void EnterCombat(Unit* /*who*/) override
- {
- Talk(SAY_STAL_AGGRO);
- DoCast(SPELL_STALAGG_TESLA);
- }
+ void BeginResetEncounter()
+ {
+ if (GameObject* coil = myCoilGO())
+ coil->SetGoState(GO_STATE_READY);
+ me->DespawnOrUnsummon();
+ me->setActive(false);
+ }
- void JustDied(Unit* /*killer*/) override
- {
- Talk(SAY_STAL_DEATH);
- if (Creature* pThaddius = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_THADDIUS)))
- if (pThaddius->AI())
- pThaddius->AI()->DoAction(ACTION_STALAGG_DIED);
- }
+ void ResetEncounter()
+ {
+ me->Respawn(true);
+ Initialize();
+ }
- void UpdateAI(uint32 uiDiff) override
- {
- if (!UpdateVictim())
- return;
+ void DoAction(int32 action) override
+ {
+ switch (action)
+ {
+ case ACTION_BEGIN_RESET_ENCOUNTER:
+ BeginResetEncounter();
+ break;
+ case ACTION_RESET_ENCOUNTER:
+ ResetEncounter();
+ break;
+ case ACTION_STALAGG_REVIVING_FX:
+ break;
+ case ACTION_STALAGG_REVIVED:
+ if (!isFeignDeath)
+ break;
+
+ me->SetFullHealth();
+ me->SetStandState(UNIT_STAND_STATE_STAND);
+ me->SetReactState(REACT_AGGRESSIVE);
+ me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE);
+ Talk(EMOTE_FEIGN_REVIVE);
+ isFeignDeath = false;
+
+ refreshBeam = true; // force beam refresh
+
+ if (Creature* feugen = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_FEUGEN)))
+ if (feugen->GetVictim())
+ {
+ me->AddThreat(feugen->EnsureVictim(), 0.0f);
+ me->SetInCombatWith(feugen->EnsureVictim());
+ }
+ break;
+ case ACTION_TRANSITION:
+ me->Kill(me); // true death
+ me->DespawnOrUnsummon();
+
+ if (Creature* coil = myCoil())
+ {
+ coil->CastStop();
+ coil->AI()->Talk(EMOTE_TESLA_OVERLOAD);
+ }
+ break;
+ case ACTION_TRANSITION_2:
+ if (Creature* coil = myCoil())
+ if (Creature* thaddius = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_THADDIUS)))
+ coil->CastSpell(thaddius, SPELL_SHOCK_VISUAL);
+ break;
+ case ACTION_TRANSITION_3:
+ if (GameObject* coil = myCoilGO())
+ coil->SetGoState(GO_STATE_READY);
+ break;
+ default:
+ break;
+ }
+ }
+
+ void KilledUnit(Unit* victim) override
+ {
+ if (victim->GetTypeId() == TYPEID_PLAYER)
+ Talk(SAY_STALAGG_SLAY);
+ }
- if (magneticPullTimer <= uiDiff)
+ void EnterCombat(Unit* who) override
{
- if (Creature* pFeugen = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_FEUGEN)))
+ Talk(SAY_STALAGG_AGGRO);
+
+ if (Creature* thaddius = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_THADDIUS)))
+ thaddius->AI()->DoAction(ACTION_STALAGG_AGGRO);
+
+ if (Creature* feugen = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_FEUGEN)))
+ if (!feugen->IsInCombat())
+ {
+ feugen->AddThreat(who, 0.0f);
+ feugen->SetInCombatWith(who);
+ }
+ }
+
+ void DamageTaken(Unit* /*who*/, uint32& damage) override
+ {
+ if (damage < me->GetHealth())
+ return;
+
+ if (isFeignDeath) // don't take damage while feigning death
{
- Unit* pStalaggVictim = me->GetVictim();
- Unit* pFeugenVictim = pFeugen->GetVictim();
+ damage = 0;
+ return;
+ }
+
+ isFeignDeath = true;
+ isOverloading = false;
+
+ Talk(EMOTE_FEIGN_DEATH);
+ if (Creature* thaddius = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_THADDIUS)))
+ thaddius->AI()->DoAction(ACTION_STALAGG_DIED);
+
+ me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE);
+ me->RemoveAllAuras();
+ me->SetReactState(REACT_PASSIVE);
+ me->AttackStop();
+ me->StopMoving();
+ me->SetStandState(UNIT_STAND_STATE_DEAD);
+
+ damage = 0;
- if (pFeugenVictim && pStalaggVictim)
+ // force beam refresh as we just removed auras
+ refreshBeam = true;
+ }
+
+ void SpellHit(Unit* caster, SpellInfo const* spell) override
+ {
+ if (!caster)
+ return;
+ if (spell->Id != SPELL_STALAGG_TESLA_PERIODIC)
+ return;
+ if (!isFeignDeath && me->IsInCombat() && !me->GetHomePosition().IsInDist(me, OVERLOAD_DISTANCE))
+ {
+ if (!isOverloading)
{
- // magnetic pull is not working. So just jump.
+ isOverloading = true;
+ caster->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC);
+ if (Creature* creatureCaster = caster->ToCreature())
+ creatureCaster->AI()->Talk(EMOTE_TESLA_LINK_BREAKS);
+ me->RemoveAura(SPELL_STALAGG_CHAIN_VISUAL);
+ }
+ if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM))
+ {
+ caster->CastStop(SPELL_TESLA_SHOCK);
+ caster->CastSpell(target, SPELL_TESLA_SHOCK,true);
+ }
+ }
+ else if (isOverloading || refreshBeam)
+ {
+ isOverloading = false;
+ refreshBeam = false;
+ caster->CastStop();
+ caster->CastSpell(me, SPELL_STALAGG_CHAIN_VISUAL, true);
+ caster->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC);
+ }
+ }
- // reset aggro to be sure that feugen will not follow the jump
- pFeugen->getThreatManager().modifyThreatPercent(pFeugenVictim, -100);
- pFeugenVictim->JumpTo(me, 0.3f);
+ void UpdateAI(uint32 uiDiff) override
+ {
+ if(!isFeignDeath)
+ if (!UpdateVictim())
+ return;
- me->getThreatManager().modifyThreatPercent(pStalaggVictim, -100);
- pStalaggVictim->JumpTo(pFeugen, 0.3f);
+ if (powerSurgeTimer <= uiDiff)
+ {
+ if (isFeignDeath) // delay until potential revive
+ powerSurgeTimer = 0u;
+ else
+ {
+ DoCast(me, SPELL_STALAGG_POWERSURGE);
+ powerSurgeTimer = urand(25, 30) * IN_MILLISECONDS;
}
}
+ else
+ powerSurgeTimer -= uiDiff;
- magneticPullTimer = 20000;
+ if(!isFeignDeath)
+ DoMeleeAttackIfReady();
}
- else magneticPullTimer -= uiDiff;
- if (powerSurgeTimer <= uiDiff)
+ private:
+ Creature* myCoil()
{
- DoCast(me, SPELL_POWERSURGE);
- powerSurgeTimer = urand(15000, 20000);
- } else powerSurgeTimer -= uiDiff;
+ Creature* coil = nullptr;
+ if (_myCoil)
+ coil = ObjectAccessor::GetCreature(*me, _myCoil);
+ if (!coil)
+ {
+ coil = me->FindNearestCreature(NPC_TESLA, 1000.0f, true);
+ if (coil)
+ {
+ _myCoil = coil->GetGUID();
+ coil->SetReactState(REACT_PASSIVE);
+ }
+ }
+ return coil;
+ }
- DoMeleeAttackIfReady();
- }
+ GameObject* myCoilGO()
+ {
+ GameObject* coil = nullptr;
+ if (_myCoilGO)
+ coil = ObjectAccessor::GetGameObject(*me, _myCoilGO);
+ if (!coil)
+ {
+ coil = me->FindNearestGameObject(GO_CONS_NOX_TESLA_STALAGG, 1000.0f);
+ if (coil)
+ _myCoilGO = coil->GetGUID();
+ }
+ return coil;
+ }
+
+ InstanceScript* instance;
+
+ uint32 powerSurgeTimer;
+
+ ObjectGuid _myCoil;
+ ObjectGuid _myCoilGO;
+ bool isOverloading;
+ bool refreshBeam;
+ bool isFeignDeath;
};
};
@@ -386,142 +714,381 @@ public:
struct npc_feugenAI : public ScriptedAI
{
- npc_feugenAI(Creature* creature) : ScriptedAI(creature)
- {
- Initialize();
- instance = creature->GetInstanceScript();
- }
-
- void Initialize()
- {
- staticFieldTimer = 5000;
- }
+ public:
+ npc_feugenAI(Creature* creature) : ScriptedAI(creature), _myCoil(ObjectGuid::Empty), _myCoilGO(ObjectGuid::Empty), isOverloading(false), refreshBeam(false), isFeignDeath(false)
+ {
+ Initialize();
+ instance = creature->GetInstanceScript();
+ }
- InstanceScript* instance;
+ void Initialize()
+ {
+ if (GameObject* coil = myCoilGO())
+ coil->SetGoState(GO_STATE_ACTIVE);
- uint32 staticFieldTimer;
+ // if the encounter reset while feigning death
+ me->SetStandState(UNIT_STAND_STATE_STAND);
+ me->SetReactState(REACT_AGGRESSIVE);
+ me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE);
+ isOverloading = false;
+ isFeignDeath = false;
- void Reset() override
- {
- if (Creature* pThaddius = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_THADDIUS)))
- if (pThaddius->AI())
- pThaddius->AI()->DoAction(ACTION_FEUGEN_RESET);
- Initialize();
- }
+ // force coil state to refresh
+ refreshBeam = true;
- void KilledUnit(Unit* /*victim*/) override
- {
- if (!(rand32() % 5))
- Talk(SAY_FEUG_SLAY);
- }
+ staticFieldTimer = 6 * IN_MILLISECONDS;
+ magneticPullTimer = 20 * IN_MILLISECONDS;
+ }
- void EnterCombat(Unit* /*who*/) override
- {
- Talk(SAY_FEUG_AGGRO);
- DoCast(SPELL_FEUGEN_TESLA);
- }
+ void Reset() override
+ {
+ if (isFeignDeath || !me->IsAlive())
+ return;
+ if (Creature* thaddius = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_THADDIUS)))
+ thaddius->AI()->DoAction(ACTION_FEUGEN_RESET);
+ }
- void JustDied(Unit* /*killer*/) override
- {
- Talk(SAY_FEUG_DEATH);
- if (Creature* pThaddius = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_THADDIUS)))
- if (pThaddius->AI())
- pThaddius->AI()->DoAction(ACTION_FEUGEN_DIED);
- }
+ void BeginResetEncounter()
+ {
+ if (GameObject* coil = myCoilGO())
+ coil->SetGoState(GO_STATE_READY);
+ me->DespawnOrUnsummon();
+ me->setActive(false);
+ }
- void UpdateAI(uint32 uiDiff) override
- {
- if (!UpdateVictim())
- return;
+ void ResetEncounter()
+ {
+ me->Respawn(true);
+ Initialize();
+ }
- if (staticFieldTimer <= uiDiff)
+ void DoAction(int32 action) override
{
- DoCast(me, SPELL_STATICFIELD);
- staticFieldTimer = 5000;
- } else staticFieldTimer -= uiDiff;
+ switch (action)
+ {
+ case ACTION_BEGIN_RESET_ENCOUNTER:
+ BeginResetEncounter();
+ break;
+ case ACTION_RESET_ENCOUNTER:
+ ResetEncounter();
+ break;
+ case ACTION_FEUGEN_REVIVING_FX:
+ break;
+ case ACTION_FEUGEN_REVIVED:
+ if (!isFeignDeath)
+ break;
+
+ me->SetFullHealth();
+ me->SetStandState(UNIT_STAND_STATE_STAND);
+ me->SetReactState(REACT_AGGRESSIVE);
+ me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE);
+ Talk(EMOTE_FEIGN_REVIVE);
+ isFeignDeath = false;
+
+ refreshBeam = true; // force beam refresh
+
+ if (Creature* stalagg = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_STALAGG)))
+ if (stalagg->GetVictim())
+ {
+ me->AddThreat(stalagg->EnsureVictim(), 0.0f);
+ me->SetInCombatWith(stalagg->EnsureVictim());
+ }
+ staticFieldTimer = 6 * IN_MILLISECONDS;
+ magneticPullTimer = 30 * IN_MILLISECONDS;
+ break;
+ case ACTION_TRANSITION:
+ me->Kill(me); // true death this time around
+ me->DespawnOrUnsummon();
+
+ if (Creature* coil = myCoil())
+ {
+ coil->CastStop();
+ coil->AI()->Talk(EMOTE_TESLA_OVERLOAD);
+ }
+ break;
+ case ACTION_TRANSITION_2:
+ if (Creature* coil = myCoil())
+ if (Creature* thaddius = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_THADDIUS)))
+ coil->CastSpell(thaddius, SPELL_SHOCK_VISUAL);
+ break;
+ case ACTION_TRANSITION_3:
+ if (GameObject* coil = myCoilGO())
+ coil->SetGoState(GO_STATE_READY);
+ default:
+ break;
+ }
+ }
- DoMeleeAttackIfReady();
- }
- };
+ void KilledUnit(Unit* victim) override
+ {
+ if(victim->GetTypeId() == TYPEID_PLAYER)
+ Talk(SAY_FEUGEN_SLAY);
+ }
-};
+ void EnterCombat(Unit* who) override
+ {
+ Talk(SAY_FEUGEN_AGGRO);
-class spell_thaddius_pos_neg_charge : public SpellScriptLoader
-{
- public:
- spell_thaddius_pos_neg_charge() : SpellScriptLoader("spell_thaddius_pos_neg_charge") { }
+ if (Creature* thaddius = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_THADDIUS)))
+ thaddius->AI()->DoAction(ACTION_STALAGG_AGGRO);
- class spell_thaddius_pos_neg_charge_SpellScript : public SpellScript
- {
- PrepareSpellScript(spell_thaddius_pos_neg_charge_SpellScript);
+ if (Creature* stalagg = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_STALAGG)))
+ if (!stalagg->IsInCombat())
+ {
+ stalagg->AddThreat(who, 0.0f);
+ stalagg->SetInCombatWith(who);
+ }
+ }
- bool Validate(SpellInfo const* /*spell*/) override
+ void DamageTaken(Unit* /*who*/, uint32& damage) override
{
- if (!sSpellMgr->GetSpellInfo(SPELL_POSITIVE_CHARGE))
- return false;
- if (!sSpellMgr->GetSpellInfo(SPELL_POSITIVE_CHARGE_STACK))
- return false;
- if (!sSpellMgr->GetSpellInfo(SPELL_NEGATIVE_CHARGE))
- return false;
- if (!sSpellMgr->GetSpellInfo(SPELL_NEGATIVE_CHARGE_STACK))
- return false;
- return true;
+ if (damage < me->GetHealth())
+ return;
+
+ if (isFeignDeath) // don't take damage while feigning death
+ {
+ damage = 0;
+ return;
+ }
+
+ isFeignDeath = true;
+ isOverloading = false;
+
+ Talk(EMOTE_FEIGN_DEATH);
+ if (Creature* thaddius = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_THADDIUS)))
+ thaddius->AI()->DoAction(ACTION_FEUGEN_DIED);
+
+ me->RemoveAllAuras();
+ me->SetReactState(REACT_PASSIVE);
+ me->AttackStop();
+ me->StopMoving();
+ me->SetStandState(UNIT_STAND_STATE_DEAD);
+ me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE);
+
+ damage = 0;
+
+ // force beam refresh as we just removed auras
+ refreshBeam = true;
}
- bool Load() override
+ void SpellHit(Unit* caster, SpellInfo const* spell) override
{
- return GetCaster()->GetTypeId() == TYPEID_UNIT;
+ if (!caster)
+ return;
+ if (spell->Id != SPELL_FEUGEN_TESLA_PERIODIC)
+ return;
+ if (!isFeignDeath && me->IsInCombat() && !me->GetHomePosition().IsInDist(me, OVERLOAD_DISTANCE))
+ {
+ if (!isOverloading)
+ {
+ isOverloading = true;
+ caster->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC);
+ if (Creature* creatureCaster = caster->ToCreature())
+ creatureCaster->AI()->Talk(EMOTE_TESLA_LINK_BREAKS);
+ me->RemoveAura(SPELL_STALAGG_CHAIN_VISUAL);
+ }
+ if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0))
+ {
+ caster->CastStop(SPELL_TESLA_SHOCK);
+ caster->CastSpell(target, SPELL_TESLA_SHOCK,true);
+ }
+ }
+ else if (isOverloading || refreshBeam)
+ {
+ isOverloading = false;
+ refreshBeam = false;
+ caster->CastStop();
+ caster->CastSpell(me, SPELL_FEUGEN_CHAIN_VISUAL, true);
+ caster->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC);
+ }
}
- void HandleTargets(std::list<WorldObject*>& targets)
+ void UpdateAI(uint32 uiDiff) override
{
- uint8 count = 0;
- for (std::list<WorldObject*>::iterator ihit = targets.begin(); ihit != targets.end(); ++ihit)
- if ((*ihit)->GetGUID() != GetCaster()->GetGUID())
- if (Player* target = (*ihit)->ToPlayer())
- if (target->HasAura(GetTriggeringSpell()->Id))
- ++count;
+ if (isFeignDeath)
+ return;
+ if (!UpdateVictim())
+ return;
- if (count)
+ if (magneticPullTimer <= uiDiff)
+ {
+ DoCast(me, SPELL_MAGNETIC_PULL);
+ magneticPullTimer = 20 * IN_MILLISECONDS;
+ }
+ else magneticPullTimer -= uiDiff;
+
+ if (staticFieldTimer <= uiDiff)
{
- uint32 spellId = 0;
+ DoCast(me, SPELL_FEUGEN_STATICFIELD);
+ staticFieldTimer = 6 * IN_MILLISECONDS;
+ }
+ else staticFieldTimer -= uiDiff;
- if (GetSpellInfo()->Id == SPELL_POSITIVE_CHARGE)
- spellId = SPELL_POSITIVE_CHARGE_STACK;
- else // if (GetSpellInfo()->Id == SPELL_NEGATIVE_CHARGE)
- spellId = SPELL_NEGATIVE_CHARGE_STACK;
+ DoMeleeAttackIfReady();
+ }
- GetCaster()->SetAuraStack(spellId, GetCaster(), count);
+ private:
+ Creature* myCoil()
+ {
+ Creature* coil = nullptr;
+ if (_myCoil)
+ coil = ObjectAccessor::GetCreature(*me, _myCoil);
+ if (!coil)
+ {
+ coil = me->FindNearestCreature(NPC_TESLA, 1000.0f, true);
+ if (coil)
+ {
+ _myCoil = coil->GetGUID();
+ coil->SetReactState(REACT_PASSIVE);
+ }
}
+ return coil;
+ }
+
+ GameObject* myCoilGO()
+ {
+ GameObject* coil = nullptr;
+ if (_myCoilGO)
+ coil = ObjectAccessor::GetGameObject(*me, _myCoilGO);
+ if (!coil)
+ {
+ coil = me->FindNearestGameObject(GO_CONS_NOX_TESLA_FEUGEN, 1000.0f);
+ if (coil)
+ _myCoilGO = coil->GetGUID();
+ }
+ return coil;
+ }
+ InstanceScript* instance;
+
+ uint32 magneticPullTimer;
+ uint32 staticFieldTimer;
+
+ ObjectGuid _myCoil;
+ ObjectGuid _myCoilGO;
+
+ bool isOverloading;
+ bool refreshBeam;
+ bool isFeignDeath;
+ };
+};
+
+class npc_tesla : public CreatureScript
+{
+public:
+ npc_tesla() : CreatureScript("npc_tesla") { }
+
+ CreatureAI* GetAI(Creature* creature) const override
+ {
+ return GetInstanceAI<npc_teslaAI>(creature);
+ }
+
+ struct npc_teslaAI : public ScriptedAI
+ {
+ npc_teslaAI(Creature* creature) : ScriptedAI(creature) { }
+
+ void EnterEvadeMode() override { } // never stop casting due to evade
+ void UpdateAI(uint32 /*diff*/) override { } // never do anything unless told
+ void EnterCombat(Unit* /*who*/) override { }
+ void DamageTaken(Unit* /*who*/, uint32& damage) override { damage = 0; } // no, you can't kill it
+ };
+};
+
+class spell_thaddius_polarity_charge : public SpellScriptLoader
+{
+ public:
+ spell_thaddius_polarity_charge() : SpellScriptLoader("spell_thaddius_polarity_charge") { }
+
+ class spell_thaddius_polarity_charge_SpellScript : public SpellScript
+ {
+ PrepareSpellScript(spell_thaddius_polarity_charge_SpellScript);
+
+ bool Validate(SpellInfo const* /*spell*/) override
+ {
+ return (
+ sSpellMgr->GetSpellInfo(SPELL_POLARITY_SHIFT) &&
+ sSpellMgr->GetSpellInfo(SPELL_POSITIVE_CHARGE_APPLY) &&
+ sSpellMgr->GetSpellInfo(SPELL_POSITIVE_CHARGE_TICK) &&
+ sSpellMgr->GetSpellInfo(SPELL_POSITIVE_CHARGE_AMP) &&
+ sSpellMgr->GetSpellInfo(SPELL_NEGATIVE_CHARGE_APPLY) &&
+ sSpellMgr->GetSpellInfo(SPELL_NEGATIVE_CHARGE_TICK) &&
+ sSpellMgr->GetSpellInfo(SPELL_NEGATIVE_CHARGE_AMP)
+ );
}
- void HandleDamage(SpellEffIndex /*effIndex*/)
+ void HandleTargets(std::list<WorldObject*>& targetList)
{
if (!GetTriggeringSpell())
return;
- Unit* target = GetHitUnit();
- Unit* caster = GetCaster();
+ uint32 triggeringId = GetTriggeringSpell()->Id;
+ uint32 ampId;
+ switch (triggeringId)
+ {
+ case SPELL_POSITIVE_CHARGE_APPLY:
+ ampId = SPELL_POSITIVE_CHARGE_AMP;
+ break;
+ case SPELL_NEGATIVE_CHARGE_APPLY:
+ ampId = SPELL_NEGATIVE_CHARGE_AMP;
+ break;
+ default:
+ return;
+ }
- if (target->HasAura(GetTriggeringSpell()->Id))
- SetHitDamage(0);
- else
+ uint8 maxStacks = 0;
+ if (GetCaster())
+ switch (GetCaster()->GetMap()->GetDifficulty())
+ {
+ case RAID_DIFFICULTY_10MAN_NORMAL:
+ maxStacks = MAX_POLARITY_10M;
+ break;
+ case RAID_DIFFICULTY_25MAN_NORMAL:
+ maxStacks = MAX_POLARITY_25M;
+ break;
+ default:
+ break;
+ }
+
+ uint8 stacksCount = 1; // do we get a stack for our own debuff?
+ std::list<WorldObject*>::iterator it = targetList.begin();
+ while(it != targetList.end())
+ {
+ if ((*it)->GetTypeId() != TYPEID_PLAYER)
+ {
+ it = targetList.erase(it);
+ continue;
+ }
+ if ((*it)->ToPlayer()->HasAura(triggeringId))
+ {
+ it = targetList.erase(it);
+ if (stacksCount < maxStacks)
+ stacksCount++;
+ continue;
+ }
+
+ // this guy will get hit - achievement failure trigger
+ if (Creature* thaddius = (*it)->FindNearestCreature(NPC_THADDIUS,200.0f))
+ thaddius->AI()->DoAction(ACTION_POLARITY_CROSSED);
+
+ ++it;
+ }
+
+ if (GetCaster() && GetCaster()->ToPlayer())
{
- if (target->GetTypeId() == TYPEID_PLAYER && caster->IsAIEnabled)
- caster->ToCreature()->AI()->SetData(DATA_POLARITY_SWITCH, 1);
+ if (!GetCaster()->ToPlayer()->HasAura(ampId))
+ GetCaster()->ToPlayer()->AddAura(ampId, GetCaster());
+ GetCaster()->ToPlayer()->SetAuraStack(ampId, GetCaster(), stacksCount);
}
}
void Register() override
{
- OnEffectHitTarget += SpellEffectFn(spell_thaddius_pos_neg_charge_SpellScript::HandleDamage, EFFECT_0, SPELL_EFFECT_SCHOOL_DAMAGE);
- OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_thaddius_pos_neg_charge_SpellScript::HandleTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ALLY);
+ OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_thaddius_polarity_charge_SpellScript::HandleTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ALLY);
}
};
SpellScript* GetSpellScript() const override
{
- return new spell_thaddius_pos_neg_charge_SpellScript();
+ return new spell_thaddius_polarity_charge_SpellScript;
}
};
@@ -536,16 +1103,33 @@ class spell_thaddius_polarity_shift : public SpellScriptLoader
bool Validate(SpellInfo const* /*spell*/) override
{
- if (!sSpellMgr->GetSpellInfo(SPELL_POSITIVE_POLARITY) || !sSpellMgr->GetSpellInfo(SPELL_NEGATIVE_POLARITY))
- return false;
- return true;
+ return (
+ sSpellMgr->GetSpellInfo(SPELL_POLARITY_SHIFT) &&
+ sSpellMgr->GetSpellInfo(SPELL_POSITIVE_CHARGE_APPLY) &&
+ sSpellMgr->GetSpellInfo(SPELL_POSITIVE_CHARGE_TICK) &&
+ sSpellMgr->GetSpellInfo(SPELL_POSITIVE_CHARGE_AMP) &&
+ sSpellMgr->GetSpellInfo(SPELL_NEGATIVE_CHARGE_APPLY) &&
+ sSpellMgr->GetSpellInfo(SPELL_NEGATIVE_CHARGE_TICK) &&
+ sSpellMgr->GetSpellInfo(SPELL_NEGATIVE_CHARGE_AMP)
+ );
}
- void HandleDummy(SpellEffIndex /* effIndex */)
+ void HandleDummy(SpellEffIndex /*effIndex*/)
{
- Unit* caster = GetCaster();
if (Unit* target = GetHitUnit())
- target->CastSpell(target, roll_chance_i(50) ? SPELL_POSITIVE_POLARITY : SPELL_NEGATIVE_POLARITY, true, NULL, NULL, caster->GetGUID());
+ if (target->GetTypeId() == TYPEID_PLAYER)
+ {
+ if (roll_chance_i(50))
+ { // positive
+ target->CastSpell(target, SPELL_POSITIVE_CHARGE_APPLY, true);
+ target->RemoveAura(SPELL_POSITIVE_CHARGE_AMP);
+ }
+ else
+ { // negative
+ target->CastSpell(target, SPELL_NEGATIVE_CHARGE_APPLY, true);
+ target->RemoveAura(SPELL_NEGATIVE_CHARGE_AMP);
+ }
+ }
}
void Register() override
@@ -560,23 +1144,131 @@ class spell_thaddius_polarity_shift : public SpellScriptLoader
}
};
-class achievement_polarity_switch : public AchievementCriteriaScript
+class spell_thaddius_magnetic_pull : public SpellScriptLoader
+{
+ public:
+ spell_thaddius_magnetic_pull() : SpellScriptLoader("spell_thaddius_magnetic_pull") { };
+
+ class spell_thaddius_magnetic_pull_SpellScript : public SpellScript
+ {
+ PrepareSpellScript(spell_thaddius_magnetic_pull_SpellScript);
+
+ bool Validate(SpellInfo const* /*spell*/) override
+ {
+ return sSpellMgr->GetSpellInfo(SPELL_MAGNETIC_PULL) ? true : false;
+ }
+
+ void HandleCast() // only feugen ever casts this according to wowhead data
+ {
+ Unit* feugen = GetCaster();
+ if (!feugen || feugen->GetEntry() != NPC_FEUGEN)
+ return;
+
+ Unit* stalagg = ObjectAccessor::GetCreature(*feugen, feugen->GetInstanceScript()->GetGuidData(DATA_STALAGG));
+ if (!stalagg)
+ return;
+
+ Unit* feugenTank = feugen->GetVictim();
+ Unit* stalaggTank = stalagg->GetVictim();
+
+ if (!feugenTank || !stalaggTank)
+ return;
+
+ ThreatManager& feugenThreat = feugen->getThreatManager();
+ ThreatManager& stalaggThreat = stalagg->getThreatManager();
+
+ if (feugenTank == stalaggTank) // special behavior if the tanks are the same (taken from retail)
+ {
+ float feugenTankThreat = feugenThreat.getThreat(feugenTank);
+ float stalaggTankThreat = stalaggThreat.getThreat(stalaggTank);
+
+ feugenThreat.addThreat(feugenTank, stalaggTankThreat - feugenTankThreat);
+ stalaggThreat.addThreat(stalaggTank, feugenTankThreat - stalaggTankThreat);
+
+ feugen->CastSpell(stalaggTank, SPELL_MAGNETIC_PULL_EFFECT, true);
+ }
+ else // normal case, two tanks
+ {
+ float feugenTankThreat = feugenThreat.getThreat(feugenTank);
+ float feugenOtherThreat = feugenThreat.getThreat(stalaggTank);
+ float stalaggTankThreat = stalaggThreat.getThreat(stalaggTank);
+ float stalaggOtherThreat = stalaggThreat.getThreat(feugenTank);
+
+ // set the two entries in feugen's threat table to be equal to the ones in stalagg's
+ feugenThreat.addThreat(stalaggTank, stalaggTankThreat - feugenOtherThreat);
+ feugenThreat.addThreat(feugenTank, stalaggOtherThreat - feugenTankThreat);
+
+ // set the two entries in stalagg's threat table to be equal to the ones in feugen's
+ stalaggThreat.addThreat(feugenTank, feugenTankThreat - stalaggOtherThreat);
+ stalaggThreat.addThreat(stalaggTank, feugenOtherThreat - stalaggTankThreat);
+
+ // pull the two tanks across
+ feugenTank->CastSpell(stalaggTank, SPELL_MAGNETIC_PULL_EFFECT, true);
+ stalaggTank->CastSpell(feugenTank, SPELL_MAGNETIC_PULL_EFFECT, true);
+
+ // and make both attack their respective new tanks
+ if (feugen->GetAI())
+ feugen->GetAI()->AttackStart(stalaggTank);
+ if (stalagg->GetAI())
+ stalagg->GetAI()->AttackStart(feugenTank);
+ }
+ }
+
+ void Register() override
+ {
+ OnCast += SpellCastFn(spell_thaddius_magnetic_pull_SpellScript::HandleCast);
+ }
+ };
+
+ SpellScript* GetSpellScript() const override
+ {
+ return new spell_thaddius_magnetic_pull_SpellScript();
+ }
+};
+
+class at_thaddius_entrance : public AreaTriggerScript
{
public:
- achievement_polarity_switch() : AchievementCriteriaScript("achievement_polarity_switch") { }
+ at_thaddius_entrance() : AreaTriggerScript("at_thaddius_entrance") { }
+
+ bool OnTrigger(Player* player, AreaTriggerEntry const* /*areaTrigger*/) override
+ {
+ InstanceScript* instance = player->GetInstanceScript();
+ if (!instance || instance->GetData(DATA_HAD_THADDIUS_GREET) || instance->GetBossState(BOSS_THADDIUS) == DONE)
+ return true;
+
+ if (Creature* thaddius = ObjectAccessor::GetCreature(*player, instance->GetGuidData(DATA_THADDIUS)))
+ thaddius->AI()->Talk(SAY_GREET);
+ instance->SetData(DATA_HAD_THADDIUS_GREET, 1u);
+
+ return true;
+ }
+};
+
+class achievement_thaddius_shocking : public AchievementCriteriaScript
+{
+ public:
+ achievement_thaddius_shocking() : AchievementCriteriaScript("achievement_thaddius_shocking") { }
bool OnCheck(Player* /*source*/, Unit* target) override
{
- return target && target->GetAI()->GetData(DATA_POLARITY_SWITCH);
+ return target && target->GetAI() && target->GetAI()->GetData(DATA_POLARITY_CROSSED);
}
};
+
void AddSC_boss_thaddius()
{
new boss_thaddius();
new npc_stalagg();
new npc_feugen();
- new spell_thaddius_pos_neg_charge();
+ new npc_tesla();
+
+ new spell_thaddius_polarity_charge();
new spell_thaddius_polarity_shift();
- new achievement_polarity_switch();
+ new spell_thaddius_magnetic_pull();
+
+ new at_thaddius_entrance();
+
+ new achievement_thaddius_shocking();
}
diff --git a/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp b/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp
index 9b10fab2d62..60999d1d883 100644
--- a/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp
+++ b/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp
@@ -127,6 +127,7 @@ class instance_naxxramas : public InstanceMapScript
AbominationCount = 0;
hadAnubRekhanGreet = false;
hadFaerlinaGreet = false;
+ hadThaddiusGreet = false;
CurrentWingTaunt = SAY_KELTHUZAD_FIRST_WING_TAUNT;
playerDied = 0;
@@ -154,12 +155,12 @@ class instance_naxxramas : public InstanceMapScript
case NPC_SIR:
SirGUID = creature->GetGUID();
break;
- case NPC_THADDIUS:
- ThaddiusGUID = creature->GetGUID();
- break;
case NPC_HEIGAN:
HeiganGUID = creature->GetGUID();
break;
+ case NPC_THADDIUS:
+ ThaddiusGUID = creature->GetGUID();
+ break;
case NPC_FEUGEN:
FeugenGUID = creature->GetGUID();
break;
@@ -187,6 +188,18 @@ class instance_naxxramas : public InstanceMapScript
AddMinion(creature, false);
}
+ void ProcessEvent(WorldObject* source, uint32 eventId) override
+ {
+ switch (eventId)
+ {
+ case EVENT_THADDIUS_BEGIN_RESET:
+ if (!source->ToCreature() || source->ToCreature()->GetEntry() != NPC_THADDIUS)
+ return;
+ events.ScheduleEvent(EVENT_THADDIUS_RESET, 30 * IN_MILLISECONDS);
+ break;
+ }
+ }
+
void OnGameObjectCreate(GameObject* go) override
{
if (go->GetGOInfo()->displayId == 6785 || go->GetGOInfo()->displayId == 1287)
@@ -329,6 +342,9 @@ class instance_naxxramas : public InstanceMapScript
case DATA_HAD_FAERLINA_GREET:
hadFaerlinaGreet = (value == 1u);
break;
+ case DATA_HAD_THADDIUS_GREET:
+ hadThaddiusGreet = (value == 1u);
+ break;
default:
break;
}
@@ -341,9 +357,11 @@ class instance_naxxramas : public InstanceMapScript
case DATA_ABOMINATION_KILLED:
return AbominationCount;
case DATA_HAD_ANUBREKHAN_GREET:
- return (uint32)hadAnubRekhanGreet;
+ return hadAnubRekhanGreet ? 1u : 0u;
case DATA_HAD_FAERLINA_GREET:
- return (uint32)hadFaerlinaGreet;
+ return hadFaerlinaGreet ? 1u : 0u;
+ case DATA_HAD_THADDIUS_GREET:
+ return hadThaddiusGreet ? 1u : 0u;
default:
break;
}
@@ -367,14 +385,14 @@ class instance_naxxramas : public InstanceMapScript
return BaronGUID;
case DATA_SIR:
return SirGUID;
- case DATA_THADDIUS:
- return ThaddiusGUID;
case DATA_HEIGAN:
return HeiganGUID;
case DATA_FEUGEN:
return FeugenGUID;
case DATA_STALAGG:
return StalaggGUID;
+ case DATA_THADDIUS:
+ return ThaddiusGUID;
case DATA_KELTHUZAD:
return KelthuzadGUID;
case DATA_KELTHUZAD_PORTAL01:
@@ -543,6 +561,11 @@ class instance_naxxramas : public InstanceMapScript
kelthuzad->AI()->Talk(SAY_DIALOGUE_SAPPHIRON_KELTHUZAD4);
HandleGameObject(KelthuzadDoorGUID, true);
break;
+ case EVENT_THADDIUS_RESET:
+ if (GetBossState(BOSS_THADDIUS) != DONE)
+ if (Creature* thaddius = instance->GetCreature(ThaddiusGUID))
+ thaddius->AI()->DoAction(-1);
+ break;
default:
break;
}
@@ -657,6 +680,7 @@ class instance_naxxramas : public InstanceMapScript
uint8 AbominationCount;
bool hadAnubRekhanGreet;
bool hadFaerlinaGreet;
+ bool hadThaddiusGreet;
uint8 CurrentWingTaunt;
/* The Immortal / The Undying */
diff --git a/src/server/scripts/Northrend/Naxxramas/naxxramas.h b/src/server/scripts/Northrend/Naxxramas/naxxramas.h
index 6289b707411..9c5a4afba91 100644
--- a/src/server/scripts/Northrend/Naxxramas/naxxramas.h
+++ b/src/server/scripts/Northrend/Naxxramas/naxxramas.h
@@ -47,8 +47,8 @@ enum Data
DATA_GOTHIK_GATE,
DATA_SAPPHIRON_BIRTH,
DATA_HAD_ANUBREKHAN_GREET,
-
DATA_HAD_FAERLINA_GREET,
+ DATA_HAD_THADDIUS_GREET,
DATA_HORSEMEN0,
DATA_HORSEMEN1,
@@ -91,10 +91,11 @@ enum CreaturesIds
NPC_LADY = 16065,
NPC_BARON = 30549,
NPC_SIR = 16063,
- NPC_THADDIUS = 15928,
NPC_HEIGAN = 15936,
+ NPC_THADDIUS = 15928,
NPC_FEUGEN = 15930,
NPC_STALAGG = 15929,
+ NPC_TESLA = 16218,
NPC_SAPPHIRON = 15989,
NPC_KEL_THUZAD = 15990,
NPC_CRYPT_GUARD = 16573,
@@ -174,6 +175,10 @@ enum InstanceEvents
EVENT_DIALOGUE_GOTHIK_KORTHAZZ2,
EVENT_DIALOGUE_GOTHIK_RIVENDARE2,
+ // Thaddius AI requesting timed encounter respawn
+ EVENT_THADDIUS_BEGIN_RESET,
+ EVENT_THADDIUS_RESET,
+
// Dialogue that happens after each wing.
EVENT_KELTHUZAD_WING_TAUNT,
diff --git a/src/server/scripts/Spells/spell_dk.cpp b/src/server/scripts/Spells/spell_dk.cpp
index cd0052c24bc..88271dc7139 100644
--- a/src/server/scripts/Spells/spell_dk.cpp
+++ b/src/server/scripts/Spells/spell_dk.cpp
@@ -1343,6 +1343,15 @@ class spell_dk_raise_dead : public SpellScriptLoader
GetCaster()->CastSpell(targets, spellInfo, NULL, TRIGGERED_FULL_MASK);
}
+ void OverrideCooldown()
+ {
+ // Because the ghoul is summoned by one of triggered spells SendCooldownEvent is not sent for this spell
+ // but the client has locked it by itself so we need some link between this spell and the real spell summoning.
+ // Luckily such link already exists - spell category
+ // This starts infinite category cooldown which can later be used by SendCooldownEvent to send packet for this spell
+ GetCaster()->GetSpellHistory()->StartCooldown(GetSpellInfo(), 0, nullptr, true);
+ }
+
void Register() override
{
OnCheckCast += SpellCheckCastFn(spell_dk_raise_dead_SpellScript::CheckCast);
@@ -1351,6 +1360,7 @@ class spell_dk_raise_dead : public SpellScriptLoader
OnCast += SpellCastFn(spell_dk_raise_dead_SpellScript::ConsumeReagents);
OnEffectHitTarget += SpellEffectFn(spell_dk_raise_dead_SpellScript::HandleRaiseDead, EFFECT_1, SPELL_EFFECT_SCRIPT_EFFECT);
OnEffectHitTarget += SpellEffectFn(spell_dk_raise_dead_SpellScript::HandleRaiseDead, EFFECT_2, SPELL_EFFECT_DUMMY);
+ AfterCast += SpellCastFn(spell_dk_raise_dead_SpellScript::OverrideCooldown);
}
private:
diff --git a/src/server/scripts/World/duel_reset.cpp b/src/server/scripts/World/duel_reset.cpp
index f08469d5bd5..9e720455692 100644
--- a/src/server/scripts/World/duel_reset.cpp
+++ b/src/server/scripts/World/duel_reset.cpp
@@ -17,6 +17,8 @@
#include "ScriptMgr.h"
#include "Player.h"
+#include "Pet.h"
+#include "SpellInfo.h"
class DuelResetScript : public PlayerScript
{
@@ -26,28 +28,89 @@ class DuelResetScript : public PlayerScript
// Called when a duel starts (after 3s countdown)
void OnDuelStart(Player* player1, Player* player2) override
{
+ // Cooldowns reset
if (sWorld->getBoolConfig(CONFIG_RESET_DUEL_COOLDOWNS))
{
player1->GetSpellHistory()->SaveCooldownStateBeforeDuel();
player2->GetSpellHistory()->SaveCooldownStateBeforeDuel();
- player1->RemoveArenaSpellCooldowns(true);
- player2->RemoveArenaSpellCooldowns(true);
+
+ ResetSpellCooldowns(player1);
+ ResetSpellCooldowns(player2);
+ }
+
+ // Health and mana reset
+ if (sWorld->getBoolConfig(CONFIG_RESET_DUEL_HEALTH_MANA))
+ {
+ player1->SaveHealthBeforeDuel();
+ player1->SetHealth(player1->GetMaxHealth());
+
+ player2->SaveHealthBeforeDuel();
+ player2->SetHealth(player2->GetMaxHealth());
+
+ // check if player1 class uses mana
+ if (player1->getPowerType() == POWER_MANA || player1->getClass() == CLASS_DRUID)
+ {
+ player1->SaveManaBeforeDuel();
+ player1->SetPower(POWER_MANA, player1->GetMaxPower(POWER_MANA));
+ }
+
+ // check if player2 class uses mana
+ if (player2->getPowerType() == POWER_MANA || player2->getClass() == CLASS_DRUID)
+ {
+ player2->SaveManaBeforeDuel();
+ player2->SetPower(POWER_MANA, player2->GetMaxPower(POWER_MANA));
+ }
}
}
// Called when a duel ends
- void OnDuelEnd(Player* winner, Player* loser, DuelCompleteType /*type*/) override
+ void OnDuelEnd(Player* winner, Player* loser, DuelCompleteType type) override
{
- if (sWorld->getBoolConfig(CONFIG_RESET_DUEL_COOLDOWNS))
+ // do not reset anything if DUEL_INTERRUPTED or DUEL_FLED
+ if (type == DUEL_WON)
{
- winner->RemoveArenaSpellCooldowns(true);
- loser->RemoveArenaSpellCooldowns(true);
+ // Cooldown restore
+ if (sWorld->getBoolConfig(CONFIG_RESET_DUEL_COOLDOWNS))
+ {
+
+ ResetSpellCooldowns(winner);
+ ResetSpellCooldowns(loser);
- winner->GetSpellHistory()->RestoreCooldownStateAfterDuel();
- loser->GetSpellHistory()->RestoreCooldownStateAfterDuel();
+ winner->GetSpellHistory()->RestoreCooldownStateAfterDuel();
+ loser->GetSpellHistory()->RestoreCooldownStateAfterDuel();
+ }
+
+ // Health and mana restore
+ if (sWorld->getBoolConfig(CONFIG_RESET_DUEL_HEALTH_MANA))
+ {
+ winner->RestoreHealthAfterDuel();
+ loser->RestoreHealthAfterDuel();
+
+ // check if player1 class uses mana
+ if (winner->getPowerType() == POWER_MANA || winner->getClass() == CLASS_DRUID)
+ winner->RestoreManaAfterDuel();
+
+ // check if player2 class uses mana
+ if (loser->getPowerType() == POWER_MANA || loser->getClass() == CLASS_DRUID)
+ loser->RestoreManaAfterDuel();
+ }
}
}
+
+ static void ResetSpellCooldowns(Player* player)
+ {
+ // remove cooldowns on spells that have < 10 min CD and has no onHold
+ player->GetSpellHistory()->ResetCooldowns([](SpellHistory::CooldownStorageType::iterator itr) -> bool
+ {
+ SpellInfo const* spellInfo = sSpellMgr->EnsureSpellInfo(itr->first);
+ return spellInfo->RecoveryTime < 10 * MINUTE * IN_MILLISECONDS && spellInfo->CategoryRecoveryTime < 10 * MINUTE * IN_MILLISECONDS && !itr->second.OnHold;
+ }, true);
+
+ // pet cooldowns
+ if (Pet* pet = player->GetPet())
+ pet->GetSpellHistory()->ResetAllCooldowns();
+ }
};
void AddSC_duel_reset()
diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist
index df2b2e73c1c..8619a864998 100644
--- a/src/server/worldserver/worldserver.conf.dist
+++ b/src/server/worldserver/worldserver.conf.dist
@@ -1142,11 +1142,11 @@ BirthdayTime = 1222964635
# DATABASE_CHARACTER = 2, // Character database
# DATABASE_WORLD = 4, // World database
#
-# Default: 0 - (All Disabled)
+# Default: 7 - (All enabled)
# 4 - (Enable world only)
-# 7 - (All enabled)
+# 0 - (All disabled)
-Updates.EnableDatabases = 0
+Updates.EnableDatabases = 7
#
# Updates.SourcePath
@@ -2640,6 +2640,13 @@ HonorPointsAfterDuel = 0
ResetDuelCooldowns = 0
+# ResetDuelHealthMana
+# Description: Reset health and mana before duel starts and restore them when duel ends.
+# Default: 0 - (Disabled)
+# 1 - (Enabled)
+
+ResetDuelHealthMana = 0
+
#
# AlwaysMaxWeaponSkill
# Description: Players will automatically gain max weapon/defense skill when logging in,