aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/common/Define.h6
-rw-r--r--src/common/Utilities/Containers.h (renamed from src/server/shared/Containers.h)61
-rw-r--r--src/common/Utilities/Random.cpp83
-rw-r--r--src/common/Utilities/Random.h95
-rw-r--r--src/common/Utilities/Util.cpp57
-rw-r--r--src/common/Utilities/Util.h35
-rw-r--r--src/server/authserver/Main.cpp22
-rw-r--r--src/server/authserver/Server/AuthSession.cpp692
-rw-r--r--src/server/authserver/Server/AuthSession.h60
-rw-r--r--src/server/authserver/authserver.conf.dist7
-rw-r--r--src/server/database/Database/Implementation/CharacterDatabase.cpp12
-rw-r--r--src/server/database/Database/Implementation/CharacterDatabase.h6
-rw-r--r--src/server/database/Database/Implementation/LoginDatabase.cpp15
-rw-r--r--src/server/database/Database/Implementation/LoginDatabase.h7
-rw-r--r--src/server/game/AI/CreatureAI.cpp4
-rw-r--r--src/server/game/AI/CreatureAI.h4
-rw-r--r--src/server/game/AI/ScriptedAI/ScriptedCreature.cpp23
-rw-r--r--src/server/game/AI/ScriptedAI/ScriptedCreature.h3
-rw-r--r--src/server/game/AI/SmartScripts/SmartScript.cpp27
-rw-r--r--src/server/game/AI/SmartScripts/SmartScriptMgr.cpp17
-rw-r--r--src/server/game/AI/SmartScripts/SmartScriptMgr.h29
-rw-r--r--src/server/game/Achievements/AchievementMgr.cpp14
-rw-r--r--src/server/game/AuctionHouse/AuctionHouseMgr.cpp43
-rw-r--r--src/server/game/AuctionHouse/AuctionHouseMgr.h11
-rw-r--r--src/server/game/Battlegrounds/Zones/BattlegroundIC.cpp2
-rw-r--r--src/server/game/Battlegrounds/Zones/BattlegroundIC.h8
-rw-r--r--src/server/game/Battlegrounds/Zones/BattlegroundSA.cpp6
-rw-r--r--src/server/game/Chat/Chat.cpp4
-rw-r--r--src/server/game/Conditions/ConditionMgr.cpp2
-rw-r--r--src/server/game/DataStores/DBCStores.cpp75
-rw-r--r--src/server/game/DataStores/DBCStores.h10
-rw-r--r--src/server/game/DataStores/DBCStructure.h11
-rw-r--r--src/server/game/DataStores/DBCfmt.h3
-rw-r--r--src/server/game/Entities/Creature/Creature.cpp80
-rw-r--r--src/server/game/Entities/Creature/Creature.h43
-rw-r--r--src/server/game/Entities/GameObject/GameObject.cpp13
-rw-r--r--src/server/game/Entities/Player/Player.cpp136
-rw-r--r--src/server/game/Entities/Player/Player.h30
-rw-r--r--src/server/game/Entities/Unit/Unit.cpp124
-rw-r--r--src/server/game/Entities/Unit/Unit.h40
-rw-r--r--src/server/game/Globals/ObjectMgr.cpp10
-rw-r--r--src/server/game/Grids/GridDefines.h2
-rw-r--r--src/server/game/Grids/Notifiers/GridNotifiers.cpp2
-rw-r--r--src/server/game/Groups/Group.cpp5
-rw-r--r--src/server/game/Handlers/AuctionHouseHandler.cpp8
-rw-r--r--src/server/game/Handlers/CalendarHandler.cpp15
-rw-r--r--src/server/game/Handlers/ChannelHandler.cpp2
-rw-r--r--src/server/game/Handlers/MiscHandler.cpp10
-rw-r--r--src/server/game/Handlers/MovementHandler.cpp3
-rw-r--r--src/server/game/Handlers/NPCHandler.cpp43
-rw-r--r--src/server/game/Handlers/QuestHandler.cpp2
-rw-r--r--src/server/game/Handlers/SpellHandler.cpp21
-rw-r--r--src/server/game/Instances/InstanceSaveMgr.cpp102
-rw-r--r--src/server/game/Instances/InstanceSaveMgr.h2
-rw-r--r--src/server/game/Instances/InstanceScript.cpp12
-rw-r--r--src/server/game/Instances/InstanceScript.h19
-rw-r--r--src/server/game/Loot/LootMgr.cpp4
-rw-r--r--src/server/game/Maps/AreaBoundary.cpp2
-rw-r--r--src/server/game/Maps/AreaBoundary.h9
-rw-r--r--src/server/game/Maps/Map.cpp226
-rw-r--r--src/server/game/Maps/Map.h40
-rw-r--r--src/server/game/Maps/MapManager.h14
-rw-r--r--src/server/game/Miscellaneous/Formulas.h3
-rw-r--r--src/server/game/Movement/MotionMaster.cpp6
-rw-r--r--src/server/game/Movement/MotionMaster.h8
-rw-r--r--src/server/game/OutdoorPvP/OutdoorPvP.cpp2
-rw-r--r--src/server/game/Scripting/ScriptMgr.cpp37
-rw-r--r--src/server/game/Scripting/ScriptMgr.h8
-rw-r--r--src/server/game/Spells/Auras/SpellAuraEffects.cpp31
-rw-r--r--src/server/game/Spells/Auras/SpellAuras.cpp2
-rw-r--r--src/server/game/Spells/Spell.cpp13
-rw-r--r--src/server/game/Spells/SpellEffects.cpp26
-rw-r--r--src/server/game/Spells/SpellHistory.cpp13
-rw-r--r--src/server/game/Spells/SpellHistory.h6
-rw-r--r--src/server/game/Spells/SpellInfo.cpp5
-rw-r--r--src/server/game/Spells/SpellInfo.h1
-rw-r--r--src/server/game/Spells/SpellMgr.cpp21
-rw-r--r--src/server/game/Texts/CreatureTextMgr.cpp43
-rw-r--r--src/server/game/World/World.cpp7
-rw-r--r--src/server/game/World/World.h2
-rw-r--r--src/server/scripts/Commands/cs_debug.cpp29
-rw-r--r--src/server/scripts/Commands/cs_go.cpp4
-rw-r--r--src/server/scripts/Commands/cs_group.cpp4
-rw-r--r--src/server/scripts/Commands/cs_instance.cpp4
-rw-r--r--src/server/scripts/Commands/cs_lookup.cpp4
-rw-r--r--src/server/scripts/Commands/cs_misc.cpp38
-rw-r--r--src/server/scripts/Commands/cs_npc.cpp7
-rw-r--r--src/server/scripts/EasternKingdoms/AlteracValley/boss_drekthar.cpp2
-rw-r--r--src/server/scripts/EasternKingdoms/AlteracValley/boss_galvangar.cpp2
-rw-r--r--src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/blackwing_lair.h2
-rw-r--r--src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/instance_blackwing_lair.cpp2
-rw-r--r--src/server/scripts/EasternKingdoms/Deadmines/boss_mr_smite.cpp111
-rw-r--r--src/server/scripts/EasternKingdoms/Deadmines/deadmines.h13
-rw-r--r--src/server/scripts/EasternKingdoms/Deadmines/instance_deadmines.cpp46
-rw-r--r--src/server/scripts/EasternKingdoms/Karazhan/instance_karazhan.cpp55
-rw-r--r--src/server/scripts/EasternKingdoms/Karazhan/karazhan.h16
-rw-r--r--src/server/scripts/EasternKingdoms/MagistersTerrace/instance_magisters_terrace.cpp29
-rw-r--r--src/server/scripts/EasternKingdoms/MagistersTerrace/magisters_terrace.cpp84
-rw-r--r--src/server/scripts/EasternKingdoms/MagistersTerrace/magisters_terrace.h19
-rw-r--r--src/server/scripts/EasternKingdoms/Uldaman/uldaman.cpp5
-rw-r--r--src/server/scripts/EasternKingdoms/zone_undercity.cpp56
-rw-r--r--src/server/scripts/EasternKingdoms/zone_western_plaguelands.cpp111
-rw-r--r--src/server/scripts/Kalimdor/WailingCaverns/wailing_caverns.cpp14
-rw-r--r--src/server/scripts/Kalimdor/zone_ashenvale.cpp34
-rw-r--r--src/server/scripts/Northrend/ChamberOfAspects/RubySanctum/boss_general_zarithrian.cpp4
-rw-r--r--src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_faction_champions.cpp4
-rw-r--r--src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_northrend_beasts.cpp6
-rw-r--r--src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/instance_trial_of_the_crusader.cpp9
-rw-r--r--src/server/scripts/Northrend/IcecrownCitadel/boss_deathbringer_saurfang.cpp2
-rw-r--r--src/server/scripts/Northrend/IcecrownCitadel/boss_professor_putricide.cpp2
-rw-r--r--src/server/scripts/Northrend/IcecrownCitadel/boss_sindragosa.cpp9
-rw-r--r--src/server/scripts/Northrend/IcecrownCitadel/boss_the_lich_king.cpp55
-rw-r--r--src/server/scripts/Northrend/IcecrownCitadel/icecrown_citadel.h12
-rw-r--r--src/server/scripts/Northrend/IcecrownCitadel/icecrown_citadel_teleport.cpp71
-rw-r--r--src/server/scripts/Northrend/Naxxramas/boss_four_horsemen.cpp828
-rw-r--r--src/server/scripts/Northrend/Naxxramas/boss_gothik.cpp1132
-rw-r--r--src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp60
-rw-r--r--src/server/scripts/Northrend/Naxxramas/naxxramas.h7
-rw-r--r--src/server/scripts/Northrend/Ulduar/Ulduar/boss_algalon_the_observer.cpp3
-rw-r--r--src/server/scripts/Northrend/Ulduar/Ulduar/boss_flame_leviathan.cpp7
-rw-r--r--src/server/scripts/Northrend/Ulduar/Ulduar/boss_freya.cpp1
-rw-r--r--src/server/scripts/Northrend/Ulduar/Ulduar/boss_general_vezax.cpp1
-rw-r--r--src/server/scripts/Northrend/Ulduar/Ulduar/boss_mimiron.cpp7
-rw-r--r--src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/boss_ingvar_the_plunderer.cpp43
-rw-r--r--src/server/scripts/Northrend/UtgardeKeep/UtgardePinnacle/boss_skadi.cpp4
-rw-r--r--src/server/scripts/Northrend/isle_of_conquest.cpp14
-rw-r--r--src/server/scripts/Northrend/zone_icecrown.cpp1
-rw-r--r--src/server/scripts/Outland/TempestKeep/Eye/boss_kaelthas.cpp12
-rw-r--r--src/server/scripts/Outland/zone_zangarmarsh.cpp4
-rw-r--r--src/server/scripts/Pet/pet_mage.cpp6
-rw-r--r--src/server/scripts/Spells/spell_druid.cpp32
-rw-r--r--src/server/scripts/Spells/spell_generic.cpp5
-rw-r--r--src/server/scripts/Spells/spell_item.cpp39
-rw-r--r--src/server/scripts/Spells/spell_paladin.cpp69
-rw-r--r--src/server/scripts/Spells/spell_rogue.cpp142
-rw-r--r--src/server/scripts/Spells/spell_shaman.cpp44
-rw-r--r--src/server/scripts/World/duel_reset.cpp43
-rw-r--r--src/server/scripts/World/go_scripts.cpp44
-rw-r--r--src/server/scripts/World/npc_professions.cpp106
-rw-r--r--src/server/scripts/World/npcs_special.cpp358
-rw-r--r--src/server/worldserver/worldserver.conf.dist18
-rw-r--r--src/tools/map_extractor/CMakeLists.txt2
-rw-r--r--src/tools/map_extractor/System.cpp103
-rw-r--r--src/tools/map_extractor/adt.cpp27
-rw-r--r--src/tools/map_extractor/adt.h51
-rw-r--r--src/tools/mmaps_generator/TerrainBuilder.cpp2
146 files changed, 4498 insertions, 2289 deletions
diff --git a/src/common/Define.h b/src/common/Define.h
index df3dd37b503..b34edb6a549 100644
--- a/src/common/Define.h
+++ b/src/common/Define.h
@@ -83,10 +83,16 @@
# define ATTR_NORETURN __attribute__((__noreturn__))
# define ATTR_PRINTF(F, V) __attribute__ ((__format__ (__printf__, F, V)))
# define ATTR_DEPRECATED __attribute__((__deprecated__))
+# define TRINITY_CONSTEXPR constexpr
#else //COMPILER != COMPILER_GNU
# define ATTR_NORETURN
# define ATTR_PRINTF(F, V)
# define ATTR_DEPRECATED
+#if _MSC_VER >= 1900
+# define TRINITY_CONSTEXPR constexpr
+#else
+# define TRINITY_CONSTEXPR
+#endif
#endif //COMPILER == COMPILER_GNU
#define UI64FMTD "%" PRIu64
diff --git a/src/server/shared/Containers.h b/src/common/Utilities/Containers.h
index c2ebbf58a1e..f3e9432ca4c 100644
--- a/src/server/shared/Containers.h
+++ b/src/common/Utilities/Containers.h
@@ -19,10 +19,11 @@
#define TRINITY_CONTAINERS_H
#include "Define.h"
+#include "Random.h"
+#include <algorithm>
+#include <functional>
#include <list>
-
-//! Because circular includes are bad
-extern uint32 urand(uint32 min, uint32 max);
+#include <vector>
namespace Trinity
{
@@ -57,14 +58,64 @@ namespace Trinity
list = listCopy;
}
- /* Select a random element from a container. Note: make sure you explicitly empty check the container */
- template <class C> typename C::value_type const& SelectRandomContainerElement(C const& container)
+ /*
+ * Select a random element from a container.
+ *
+ * Note: container cannot be empty
+ */
+ template <class C>
+ typename C::value_type const& SelectRandomContainerElement(C const& container)
{
typename C::const_iterator it = container.begin();
std::advance(it, urand(0, container.size() - 1));
return *it;
}
+ /*
+ * Select a random element from a container where each element has a different chance to be selected.
+ *
+ * @param container Container to select an element from
+ * @param weights Chances of each element to be selected, must be in the same order as elements in container.
+ * Caller is responsible for checking that sum of all weights is greater than 0.
+ *
+ * Note: container cannot be empty
+ */
+ template <class C>
+ typename C::const_iterator SelectRandomWeightedContainerElement(C const& container, std::vector<double> weights)
+ {
+ Trinity::discrete_distribution_param<uint32> ddParam(weights.begin(), weights.end());
+ std::discrete_distribution<uint32> dd(ddParam);
+ typename C::const_iterator it = container.begin();
+ std::advance(it, dd(SFMTEngine::Instance()));
+ return it;
+ }
+
+ /*
+ * Select a random element from a container where each element has a different chance to be selected.
+ *
+ * @param container Container to select an element from
+ * @param weightExtractor Function retrieving chance of each element in container, expected to take an element of the container and returning a double
+ *
+ * Note: container cannot be empty
+ */
+ template <class C, class Fn>
+ typename C::const_iterator SelectRandomWeightedContainerElement(C const& container, Fn weightExtractor)
+ {
+ std::vector<double> weights;
+ weights.reserve(container.size());
+ double weightSum = 0.0;
+ for (auto itr = container.begin(); itr != container.end(); ++itr)
+ {
+ double weight = weightExtractor(*itr);
+ weights.push_back(weight);
+ weightSum += weight;
+ }
+ if (weightSum <= 0.0)
+ weights.assign(container.size(), 1.0);
+
+ return SelectRandomWeightedContainerElement(container, weights);
+ }
+
/**
* @fn bool Trinity::Containers::Intersects(Iterator first1, Iterator last1, Iterator first2, Iterator last2)
*
diff --git a/src/common/Utilities/Random.cpp b/src/common/Utilities/Random.cpp
new file mode 100644
index 00000000000..cc013110b01
--- /dev/null
+++ b/src/common/Utilities/Random.cpp
@@ -0,0 +1,83 @@
+/*
+ * 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 "Random.h"
+#include "Common.h"
+#include "Errors.h"
+#include "SFMT.h"
+#include <boost/thread/tss.hpp>
+
+static boost::thread_specific_ptr<SFMTRand> sfmtRand;
+
+static SFMTRand* GetRng()
+{
+ SFMTRand* rand = sfmtRand.get();
+
+ if (!rand)
+ {
+ rand = new SFMTRand();
+ sfmtRand.reset(rand);
+ }
+
+ return rand;
+}
+
+int32 irand(int32 min, int32 max)
+{
+ ASSERT(max >= min);
+ return int32(GetRng()->IRandom(min, max));
+}
+
+uint32 urand(uint32 min, uint32 max)
+{
+ ASSERT(max >= min);
+ return GetRng()->URandom(min, max);
+}
+
+uint32 urandms(uint32 min, uint32 max)
+{
+ ASSERT(max >= min);
+ ASSERT(INT_MAX / IN_MILLISECONDS >= max);
+ return GetRng()->URandom(min * IN_MILLISECONDS, max * IN_MILLISECONDS);
+}
+
+float frand(float min, float max)
+{
+ ASSERT(max >= min);
+ return float(GetRng()->Random() * (max - min) + min);
+}
+
+uint32 rand32()
+{
+ return GetRng()->BRandom();
+}
+
+double rand_norm()
+{
+ return GetRng()->Random();
+}
+
+double rand_chance()
+{
+ return GetRng()->Random() * 100.0;
+}
+
+SFMTEngine& SFMTEngine::Instance()
+{
+ static SFMTEngine engine;
+ return engine;
+}
diff --git a/src/common/Utilities/Random.h b/src/common/Utilities/Random.h
new file mode 100644
index 00000000000..5610651a83b
--- /dev/null
+++ b/src/common/Utilities/Random.h
@@ -0,0 +1,95 @@
+/*
+ * 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 Random_h__
+#define Random_h__
+
+#include "Define.h"
+#include <limits>
+#include <random>
+
+/* Return a random number in the range min..max. */
+int32 irand(int32 min, int32 max);
+
+/* Return a random number in the range min..max (inclusive). */
+uint32 urand(uint32 min, uint32 max);
+
+/* Return a random millisecond value between min and max seconds. Functionally equivalent to urand(min*IN_MILLISECONDS, max*IN_MILLISECONDS). */
+uint32 urandms(uint32 min, uint32 max);
+
+/* Return a random number in the range 0 .. UINT32_MAX. */
+uint32 rand32();
+
+/* Return a random number in the range min..max */
+float frand(float min, float max);
+
+/* Return a random double from 0.0 to 1.0 (exclusive). */
+double rand_norm();
+
+/* Return a random double from 0.0 to 100.0 (exclusive). */
+double rand_chance();
+
+/* Return true if a random roll fits in the specified chance (range 0-100). */
+inline bool roll_chance_f(float chance)
+{
+ return chance > rand_chance();
+}
+
+/* Return true if a random roll fits in the specified chance (range 0-100). */
+inline bool roll_chance_i(int chance)
+{
+ return chance > irand(0, 99);
+}
+
+/*
+* SFMT wrapper satisfying UniformRandomNumberGenerator concept for use in <random> algorithms
+*/
+class SFMTEngine
+{
+public:
+ typedef uint32 result_type;
+
+ static TRINITY_CONSTEXPR result_type min() { return std::numeric_limits<result_type>::min(); }
+ static TRINITY_CONSTEXPR result_type max() { return std::numeric_limits<result_type>::max(); }
+ result_type operator()() const { return rand32(); }
+
+ static SFMTEngine& Instance();
+};
+
+// Ugly, horrible, i don't even..., hack for VS2013 to work around missing discrete_distribution(iterator, iterator) constructor
+namespace Trinity
+{
+#if COMPILER == COMPILER_MICROSOFT && _MSC_VER <= 1800
+ template<typename T>
+ struct discrete_distribution_param : public std::discrete_distribution<T>::param_type
+ {
+ typedef typename std::discrete_distribution<T>::param_type base;
+
+ template<typename InIt>
+ discrete_distribution_param(InIt begin, InIt end) : base(_Noinit())
+ {
+ this->_Pvec.assign(begin, end);
+ this->_Init();
+ }
+ };
+#else
+ template<typename T>
+ using discrete_distribution_param = typename std::discrete_distribution<T>::param_type;
+#endif
+}
+
+#endif // Random_h__
diff --git a/src/common/Utilities/Util.cpp b/src/common/Utilities/Util.cpp
index 3eb901ca35d..1360253294f 100644
--- a/src/common/Utilities/Util.cpp
+++ b/src/common/Utilities/Util.cpp
@@ -20,10 +20,8 @@
#include "Common.h"
#include "CompilerDefs.h"
#include "utf8.h"
-#include "SFMT.h"
#include "Errors.h" // for ASSERT
#include <stdarg.h>
-#include <boost/thread/tss.hpp>
#if COMPILER == COMPILER_GNU
#include <sys/socket.h>
@@ -31,61 +29,6 @@
#include <arpa/inet.h>
#endif
-static boost::thread_specific_ptr<SFMTRand> sfmtRand;
-
-static SFMTRand* GetRng()
-{
- SFMTRand* rand = sfmtRand.get();
-
- if (!rand)
- {
- rand = new SFMTRand();
- sfmtRand.reset(rand);
- }
-
- return rand;
-}
-
-int32 irand(int32 min, int32 max)
-{
- ASSERT(max >= min);
- return int32(GetRng()->IRandom(min, max));
-}
-
-uint32 urand(uint32 min, uint32 max)
-{
- ASSERT(max >= min);
- return GetRng()->URandom(min, max);
-}
-
-uint32 urandms(uint32 min, uint32 max)
-{
- ASSERT(max >= min);
- ASSERT(INT_MAX/IN_MILLISECONDS >= max);
- return GetRng()->URandom(min * IN_MILLISECONDS, max * IN_MILLISECONDS);
-}
-
-float frand(float min, float max)
-{
- ASSERT(max >= min);
- return float(GetRng()->Random() * (max - min) + min);
-}
-
-uint32 rand32()
-{
- return GetRng()->BRandom();
-}
-
-double rand_norm()
-{
- return GetRng()->Random();
-}
-
-double rand_chance()
-{
- return GetRng()->Random() * 100.0;
-}
-
Tokenizer::Tokenizer(const std::string &src, const char sep, uint32 vectorReserve)
{
m_str = new char[src.length() + 1];
diff --git a/src/common/Utilities/Util.h b/src/common/Utilities/Util.h
index f72c1430370..ab5cabca8d2 100644
--- a/src/common/Utilities/Util.h
+++ b/src/common/Utilities/Util.h
@@ -21,12 +21,14 @@
#include "Define.h"
#include "Errors.h"
+#include "Random.h"
#include <algorithm>
#include <string>
#include <vector>
#include <list>
#include <map>
+#include <ctime>
// Searcher for map of structs
template<typename T, class S> struct Finder
@@ -76,39 +78,6 @@ std::string secsToTimeString(uint64 timeInSecs, bool shortText = false, bool hou
uint32 TimeStringToSecs(const std::string& timestring);
std::string TimeToTimestampStr(time_t t);
-/* Return a random number in the range min..max. */
-int32 irand(int32 min, int32 max);
-
-/* Return a random number in the range min..max (inclusive). */
-uint32 urand(uint32 min, uint32 max);
-
-/* Return a random millisecond value between min and max seconds. Functionally equivalent to urand(min*IN_MILLISECONDS, max*IN_MILLISECONDS). */
-uint32 urandms(uint32 min, uint32 max);
-
-/* Return a random number in the range 0 .. UINT32_MAX. */
-uint32 rand32();
-
-/* Return a random number in the range min..max */
-float frand(float min, float max);
-
-/* Return a random double from 0.0 to 1.0 (exclusive). */
-double rand_norm();
-
-/* Return a random double from 0.0 to 100.0 (exclusive). */
-double rand_chance();
-
-/* Return true if a random roll fits in the specified chance (range 0-100). */
-inline bool roll_chance_f(float chance)
-{
- return chance > rand_chance();
-}
-
-/* Return true if a random roll fits in the specified chance (range 0-100). */
-inline bool roll_chance_i(int chance)
-{
- return chance > irand(0, 99);
-}
-
inline void ApplyPercentModFloatVar(float& var, float val, bool apply)
{
if (val == -100.0f) // prevent set var to zero
diff --git a/src/server/authserver/Main.cpp b/src/server/authserver/Main.cpp
index 0c812ebd494..0618ec437b6 100644
--- a/src/server/authserver/Main.cpp
+++ b/src/server/authserver/Main.cpp
@@ -68,11 +68,14 @@ bool StartDB();
void StopDB();
void SignalHandler(const boost::system::error_code& error, int signalNumber);
void KeepDatabaseAliveHandler(const boost::system::error_code& error);
+void BanExpiryHandler(boost::system::error_code const& error);
variables_map GetConsoleArguments(int argc, char** argv, std::string& configFile, std::string& configService);
boost::asio::io_service* _ioService;
boost::asio::deadline_timer* _dbPingTimer;
uint32 _dbPingInterval;
+boost::asio::deadline_timer* _banExpiryCheckTimer;
+uint32 _banExpiryCheckInterval;
LoginDatabaseWorkerPool LoginDatabase;
int main(int argc, char** argv)
@@ -169,6 +172,11 @@ int main(int argc, char** argv)
_dbPingTimer->expires_from_now(boost::posix_time::minutes(_dbPingInterval));
_dbPingTimer->async_wait(KeepDatabaseAliveHandler);
+ _banExpiryCheckInterval = sConfigMgr->GetIntDefault("BanExpiryCheckInterval", 60);
+ _banExpiryCheckTimer = new boost::asio::deadline_timer(*_ioService);
+ _banExpiryCheckTimer->expires_from_now(boost::posix_time::seconds(_banExpiryCheckInterval));
+ _banExpiryCheckTimer->async_wait(BanExpiryHandler);
+
#if PLATFORM == PLATFORM_WINDOWS
if (m_ServiceStatus != -1)
{
@@ -181,6 +189,7 @@ int main(int argc, char** argv)
// Start the io service worker loop
_ioService->run();
+ _banExpiryCheckTimer->cancel();
_dbPingTimer->cancel();
sAuthSocketMgr.StopNetwork();
@@ -192,6 +201,7 @@ int main(int argc, char** argv)
signals.cancel();
+ delete _banExpiryCheckTimer;
delete _dbPingTimer;
delete _ioService;
return 0;
@@ -242,6 +252,18 @@ void KeepDatabaseAliveHandler(const boost::system::error_code& error)
}
}
+void BanExpiryHandler(boost::system::error_code const& error)
+{
+ if (!error)
+ {
+ LoginDatabase.Execute(LoginDatabase.GetPreparedStatement(LOGIN_DEL_EXPIRED_IP_BANS));
+ LoginDatabase.Execute(LoginDatabase.GetPreparedStatement(LOGIN_UPD_EXPIRED_ACCOUNT_BANS));
+
+ _banExpiryCheckTimer->expires_from_now(boost::posix_time::seconds(_banExpiryCheckInterval));
+ _banExpiryCheckTimer->async_wait(BanExpiryHandler);
+ }
+}
+
#if PLATFORM == PLATFORM_WINDOWS
void ServiceStatusWatcher(boost::system::error_code const& error)
{
diff --git a/src/server/authserver/Server/AuthSession.cpp b/src/server/authserver/Server/AuthSession.cpp
index 60e9b734b13..519cd1f19f7 100644
--- a/src/server/authserver/Server/AuthSession.cpp
+++ b/src/server/authserver/Server/AuthSession.cpp
@@ -43,12 +43,6 @@ enum eAuthCmd
XFER_CANCEL = 0x34
};
-enum eStatus
-{
- STATUS_CONNECTED = 0,
- STATUS_AUTHED
-};
-
#pragma pack(push, 1)
typedef struct AUTH_LOGON_CHALLENGE_C
@@ -115,11 +109,10 @@ enum class BufferSizes : uint32
SRP_6_S = 0x20,
};
+#define MAX_ACCEPTED_CHALLENGE_SIZE (sizeof(AUTH_LOGON_CHALLENGE_C) + 16)
+
#define AUTH_LOGON_CHALLENGE_INITIAL_SIZE 4
#define REALM_LIST_PACKET_SIZE 5
-#define XFER_ACCEPT_SIZE 1
-#define XFER_RESUME_SIZE 9
-#define XFER_CANCEL_SIZE 1
std::unordered_map<uint8, AuthHandler> AuthSession::InitHandlers()
{
@@ -130,15 +123,98 @@ std::unordered_map<uint8, AuthHandler> AuthSession::InitHandlers()
handlers[AUTH_RECONNECT_CHALLENGE] = { STATUS_CONNECTED, AUTH_LOGON_CHALLENGE_INITIAL_SIZE, &AuthSession::HandleReconnectChallenge };
handlers[AUTH_RECONNECT_PROOF] = { STATUS_CONNECTED, sizeof(AUTH_RECONNECT_PROOF_C), &AuthSession::HandleReconnectProof };
handlers[REALM_LIST] = { STATUS_AUTHED, REALM_LIST_PACKET_SIZE, &AuthSession::HandleRealmList };
- handlers[XFER_ACCEPT] = { STATUS_AUTHED, XFER_ACCEPT_SIZE, &AuthSession::HandleXferAccept };
- handlers[XFER_RESUME] = { STATUS_AUTHED, XFER_RESUME_SIZE, &AuthSession::HandleXferResume };
- handlers[XFER_CANCEL] = { STATUS_AUTHED, XFER_CANCEL_SIZE, &AuthSession::HandleXferCancel };
return handlers;
}
std::unordered_map<uint8, AuthHandler> const Handlers = AuthSession::InitHandlers();
+void AccountInfo::LoadResult(Field* fields)
+{
+ // 0 1 2 3 4 5 6
+ //SELECT a.id, a.username, a.locked, a.lock_country, a.last_ip, a.failed_logins, ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate,
+ // 7 8 9 10 11 12
+ // ab.unbandate = ab.bandate, aa.gmlevel, a.token_key, a.sha_pass_hash, a.v, a.s
+ //FROM account a LEFT JOIN account_access aa ON a.id = aa.id LEFT JOIN account_banned ab ON ab.id = a.id AND ab.active = 1 WHERE a.username = ?
+
+ Id = fields[0].GetUInt32();
+ Login = fields[1].GetString();
+ IsLockedToIP = fields[2].GetBool();
+ LockCountry = fields[3].GetString();
+ LastIP = fields[4].GetString();
+ FailedLogins = fields[5].GetUInt32();
+ IsBanned = fields[6].GetUInt64() != 0;
+ IsPermanenetlyBanned = fields[7].GetUInt64() != 0;
+ SecurityLevel = AccountTypes(fields[8].GetUInt8());
+}
+
+AuthSession::AuthSession(tcp::socket&& socket) : Socket(std::move(socket)),
+_sentChallenge(false), _sentProof(false),
+_status(STATUS_CONNECTED), _build(0), _expversion(0)
+{
+ N.SetHexStr("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7");
+ g.SetDword(7);
+}
+
+void AuthSession::Start()
+{
+ std::string ip_address = GetRemoteIpAddress().to_string();
+ TC_LOG_TRACE("session", "Accepted connection from %s", ip_address.c_str());
+
+ PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_IP_INFO);
+ stmt->setString(0, ip_address);
+ stmt->setUInt32(1, inet_addr(ip_address.c_str()));
+
+ _queryCallback = std::bind(&AuthSession::CheckIpCallback, this, std::placeholders::_1);
+ _queryFuture = LoginDatabase.AsyncQuery(stmt);
+}
+
+bool AuthSession::Update()
+{
+ if (!AuthSocket::Update())
+ return false;
+
+ if (_queryFuture.valid() && _queryFuture.wait_for(std::chrono::seconds(0)) == std::future_status::ready)
+ {
+ auto callback = _queryCallback;
+ _queryCallback = nullptr;
+ callback(_queryFuture.get());
+ }
+
+ return true;
+}
+
+void AuthSession::CheckIpCallback(PreparedQueryResult result)
+{
+ if (result)
+ {
+ bool banned = false;
+ do
+ {
+ Field* fields = result->Fetch();
+ if (fields[0].GetUInt64() != 0)
+ banned = true;
+
+ if (!fields[1].GetString().empty())
+ _ipCountry = fields[1].GetString();
+
+ } while (result->NextRow());
+
+ if (banned)
+ {
+ ByteBuffer pkt;
+ pkt << uint8(AUTH_LOGON_CHALLENGE);
+ pkt << uint8(0x00);
+ pkt << uint8(WOW_FAIL_BANNED);
+ SendPacket(pkt);
+ TC_LOG_DEBUG("session", "[AuthSession::CheckIpCallback] Banned ip '%s:%d' tries to login!", GetRemoteIpAddress().to_string().c_str(), GetRemotePort());
+ return;
+ }
+ }
+
+ AsyncRead();
+}
+
void AuthSession::ReadHandler()
{
MessageBuffer& packet = GetReadBuffer();
@@ -153,6 +229,12 @@ void AuthSession::ReadHandler()
break;
}
+ if (_status != itr->second.status)
+ {
+ CloseSocket();
+ return;
+ }
+
uint16 size = uint16(itr->second.packetSize);
if (packet.GetActiveSize() < size)
break;
@@ -161,12 +243,17 @@ void AuthSession::ReadHandler()
{
sAuthLogonChallenge_C* challenge = reinterpret_cast<sAuthLogonChallenge_C*>(packet.GetReadPointer());
size += challenge->size;
+ if (size > MAX_ACCEPTED_CHALLENGE_SIZE)
+ {
+ CloseSocket();
+ return;
+ }
}
if (packet.GetActiveSize() < size)
break;
- if (!(*this.*Handlers.at(cmd).handler)())
+ if (!(*this.*itr->second.handler)())
{
CloseSocket();
return;
@@ -196,224 +283,203 @@ void AuthSession::SendPacket(ByteBuffer& packet)
bool AuthSession::HandleLogonChallenge()
{
+ if (_sentChallenge)
+ return false;
+
+ _sentChallenge = true;
+
sAuthLogonChallenge_C* challenge = reinterpret_cast<sAuthLogonChallenge_C*>(GetReadBuffer().GetReadPointer());
+ if (challenge->size - (sizeof(sAuthLogonChallenge_C) - AUTH_LOGON_CHALLENGE_INITIAL_SIZE - 1) != challenge->I_len)
+ return false;
- //TC_LOG_DEBUG("server.authserver", "[AuthChallenge] got full packet, %#04x bytes", challenge->size);
- TC_LOG_DEBUG("server.authserver", "[AuthChallenge] name(%d): '%s'", challenge->I_len, challenge->I);
+ std::string login((const char*)challenge->I, challenge->I_len);
+ TC_LOG_DEBUG("server.authserver", "[AuthChallenge] '%s'", login.c_str());
- ByteBuffer pkt;
+ if (_queryCallback)
+ {
+ ByteBuffer pkt;
+ pkt << uint8(AUTH_LOGON_CHALLENGE);
+ pkt << uint8(0x00);
+ pkt << uint8(WOW_FAIL_DB_BUSY);
+ SendPacket(pkt);
+
+ TC_LOG_DEBUG("server.authserver", "[AuthChallenge] %s attempted to log too quick after previous attempt!", login.c_str());
+ return true;
+ }
- _login.assign((const char*)challenge->I, challenge->I_len);
_build = challenge->build;
_expversion = uint8(AuthHelper::IsPostBCAcceptedClientBuild(_build) ? POST_BC_EXP_FLAG : (AuthHelper::IsPreBCAcceptedClientBuild(_build) ? PRE_BC_EXP_FLAG : NO_VALID_EXP_FLAG));
- _os = (const char*)challenge->os;
-
- if (_os.size() > 4)
- return false;
+ std::array<char, 5> os;
+ os.fill('\0');
+ memcpy(os.data(), challenge->os, sizeof(challenge->os));
+ _os = os.data();
// Restore string order as its byte order is reversed
std::reverse(_os.begin(), _os.end());
+ _localizationName.resize(4);
+ for (int i = 0; i < 4; ++i)
+ _localizationName[i] = challenge->country[4 - i - 1];
+
+ // Get the account details from the account table
+ PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_LOGONCHALLENGE);
+ stmt->setString(0, login);
+
+ _queryCallback = std::bind(&AuthSession::LogonChallengeCallback, this, std::placeholders::_1);
+ _queryFuture = LoginDatabase.AsyncQuery(stmt);
+ return true;
+}
+
+void AuthSession::LogonChallengeCallback(PreparedQueryResult result)
+{
+ ByteBuffer pkt;
pkt << uint8(AUTH_LOGON_CHALLENGE);
pkt << uint8(0x00);
- // Verify that this IP is not in the ip_banned table
- LoginDatabase.Execute(LoginDatabase.GetPreparedStatement(LOGIN_DEL_EXPIRED_IP_BANS));
+ if (!result)
+ {
+ pkt << uint8(WOW_FAIL_UNKNOWN_ACCOUNT);
+ SendPacket(pkt);
+ return;
+ }
+
+ Field* fields = result->Fetch();
+
+ _accountInfo.LoadResult(fields);
std::string ipAddress = GetRemoteIpAddress().to_string();
uint16 port = GetRemotePort();
- PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_IP_BANNED);
- stmt->setString(0, ipAddress);
- PreparedQueryResult result = LoginDatabase.Query(stmt);
- if (result)
+ // If the IP is 'locked', check that the player comes indeed from the correct IP address
+ if (_accountInfo.IsLockedToIP)
{
- pkt << uint8(WOW_FAIL_BANNED);
- TC_LOG_DEBUG("server.authserver", "'%s:%d' [AuthChallenge] Banned ip tries to login!", ipAddress.c_str(), port);
+ TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account '%s' is locked to IP - '%s' is logging in from '%s'", _accountInfo.Login.c_str(), _accountInfo.LastIP.c_str(), ipAddress.c_str());
+ if (_accountInfo.LastIP != ipAddress)
+ {
+ pkt << uint8(WOW_FAIL_LOCKED_ENFORCED);
+ SendPacket(pkt);
+ return;
+ }
}
else
{
- // Get the account details from the account table
- // No SQL injection (prepared statement)
- stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_LOGONCHALLENGE);
- stmt->setString(0, _login);
-
- PreparedQueryResult res2 = LoginDatabase.Query(stmt);
- if (res2)
+ TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account '%s' is not locked to ip", _accountInfo.Login.c_str());
+ if (_accountInfo.LockCountry.empty() || _accountInfo.LockCountry == "00")
+ TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account '%s' is not locked to country", _accountInfo.Login.c_str());
+ else if (!_accountInfo.LockCountry.empty() && !_ipCountry.empty())
{
- Field* fields = res2->Fetch();
-
- // If the IP is 'locked', check that the player comes indeed from the correct IP address
- bool locked = false;
- if (fields[2].GetUInt8() == 1) // if ip is locked
- {
- TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account '%s' is locked to IP - '%s'", _login.c_str(), fields[4].GetCString());
- TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Player address is '%s'", ipAddress.c_str());
-
- if (strcmp(fields[4].GetCString(), ipAddress.c_str()) != 0)
- {
- TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account IP differs");
- pkt << uint8(WOW_FAIL_LOCKED_ENFORCED);
- locked = true;
- }
- else
- TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account IP matches");
- }
- else
+ TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account '%s' is locked to country: '%s' Player country is '%s'", _accountInfo.Login.c_str(), _accountInfo.LockCountry.c_str(), _ipCountry.c_str());
+ if (_ipCountry != _accountInfo.LockCountry)
{
- TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account '%s' is not locked to ip", _login.c_str());
- std::string accountCountry = fields[3].GetString();
- if (accountCountry.empty() || accountCountry == "00")
- TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account '%s' is not locked to country", _login.c_str());
- else if (!accountCountry.empty())
- {
- uint32 ip = inet_addr(ipAddress.c_str());
- EndianConvertReverse(ip);
-
- stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_LOGON_COUNTRY);
- stmt->setUInt32(0, ip);
- if (PreparedQueryResult sessionCountryQuery = LoginDatabase.Query(stmt))
- {
- std::string loginCountry = (*sessionCountryQuery)[0].GetString();
- TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account '%s' is locked to country: '%s' Player country is '%s'", _login.c_str(),
- accountCountry.c_str(), loginCountry.c_str());
-
- if (loginCountry != accountCountry)
- {
- TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account country differs.");
- pkt << uint8(WOW_FAIL_UNLOCKABLE_LOCK);
- locked = true;
- }
- else
- TC_LOG_DEBUG("server.authserver", "[AuthChallenge] Account country matches");
- }
- else
- TC_LOG_DEBUG("server.authserver", "[AuthChallenge] IP2NATION Table empty");
- }
+ pkt << uint8(WOW_FAIL_UNLOCKABLE_LOCK);
+ SendPacket(pkt);
+ return;
}
+ }
+ }
- if (!locked)
- {
- //set expired bans to inactive
- LoginDatabase.DirectExecute(LoginDatabase.GetPreparedStatement(LOGIN_UPD_EXPIRED_ACCOUNT_BANS));
-
- // If the account is banned, reject the logon attempt
- stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_ACCOUNT_BANNED);
- stmt->setUInt32(0, fields[1].GetUInt32());
- PreparedQueryResult banresult = LoginDatabase.Query(stmt);
- if (banresult)
- {
- if ((*banresult)[0].GetUInt32() == (*banresult)[1].GetUInt32())
- {
- pkt << uint8(WOW_FAIL_BANNED);
- TC_LOG_DEBUG("server.authserver", "'%s:%d' [AuthChallenge] Banned account %s tried to login!", ipAddress.c_str(),
- port, _login.c_str());
- }
- else
- {
- pkt << uint8(WOW_FAIL_SUSPENDED);
- TC_LOG_DEBUG("server.authserver", "'%s:%d' [AuthChallenge] Temporarily banned account %s tried to login!",
- ipAddress.c_str(), port, _login.c_str());
- }
- }
- else
- {
- // Get the password from the account table, upper it, and make the SRP6 calculation
- std::string rI = fields[0].GetString();
-
- // Don't calculate (v, s) if there are already some in the database
- std::string databaseV = fields[6].GetString();
- std::string databaseS = fields[7].GetString();
-
- TC_LOG_DEBUG("network", "database authentication values: v='%s' s='%s'", databaseV.c_str(), databaseS.c_str());
-
- // multiply with 2 since bytes are stored as hexstring
- if (databaseV.size() != size_t(BufferSizes::SRP_6_V) * 2 || databaseS.size() != size_t(BufferSizes::SRP_6_S) * 2)
- SetVSFields(rI);
- else
- {
- s.SetHexStr(databaseS.c_str());
- v.SetHexStr(databaseV.c_str());
- }
-
- b.SetRand(19 * 8);
- BigNumber gmod = g.ModExp(b, N);
- B = ((v * 3) + gmod) % N;
-
- ASSERT(gmod.GetNumBytes() <= 32);
-
- BigNumber unk3;
- unk3.SetRand(16 * 8);
-
- // Fill the response packet with the result
- if (AuthHelper::IsAcceptedClientBuild(_build))
- pkt << uint8(WOW_SUCCESS);
- else
- pkt << uint8(WOW_FAIL_VERSION_INVALID);
-
- // B may be calculated < 32B so we force minimal length to 32B
- pkt.append(B.AsByteArray(32).get(), 32); // 32 bytes
- pkt << uint8(1);
- pkt.append(g.AsByteArray(1).get(), 1);
- pkt << uint8(32);
- pkt.append(N.AsByteArray(32).get(), 32);
- pkt.append(s.AsByteArray(int32(BufferSizes::SRP_6_S)).get(), size_t(BufferSizes::SRP_6_S)); // 32 bytes
- pkt.append(unk3.AsByteArray(16).get(), 16);
- uint8 securityFlags = 0;
-
- // Check if token is used
- _tokenKey = fields[8].GetString();
- if (!_tokenKey.empty())
- securityFlags = 4;
-
- pkt << uint8(securityFlags); // security flags (0x0...0x04)
-
- if (securityFlags & 0x01) // PIN input
- {
- pkt << uint32(0);
- pkt << uint64(0) << uint64(0); // 16 bytes hash?
- }
-
- if (securityFlags & 0x02) // Matrix input
- {
- pkt << uint8(0);
- pkt << uint8(0);
- pkt << uint8(0);
- pkt << uint8(0);
- pkt << uint64(0);
- }
-
- if (securityFlags & 0x04) // Security token input
- pkt << uint8(1);
-
- uint8 secLevel = fields[5].GetUInt8();
- _accountSecurityLevel = secLevel <= SEC_ADMINISTRATOR ? AccountTypes(secLevel) : SEC_ADMINISTRATOR;
-
- _localizationName.resize(4);
- for (int i = 0; i < 4; ++i)
- _localizationName[i] = challenge->country[4 - i - 1];
-
- TC_LOG_DEBUG("server.authserver", "'%s:%d' [AuthChallenge] account %s is using '%c%c%c%c' locale (%u)",
- ipAddress.c_str(), port, _login.c_str(),
- challenge->country[3], challenge->country[2], challenge->country[1], challenge->country[0],
- GetLocaleByName(_localizationName)
- );
- }
- }
+ // If the account is banned, reject the logon attempt
+ if (_accountInfo.IsBanned)
+ {
+ if (_accountInfo.IsPermanenetlyBanned)
+ {
+ pkt << uint8(WOW_FAIL_BANNED);
+ SendPacket(pkt);
+ TC_LOG_DEBUG("server.authserver", "'%s:%d' [AuthChallenge] Banned account %s tried to login!", ipAddress.c_str(), port, _accountInfo.Login.c_str());
+ return;
+ }
+ else
+ {
+ pkt << uint8(WOW_FAIL_SUSPENDED);
+ SendPacket(pkt);
+ TC_LOG_DEBUG("server.authserver", "'%s:%d' [AuthChallenge] Temporarily banned account %s tried to login!", ipAddress.c_str(), port, _accountInfo.Login.c_str());
+ return;
}
- else //no account
- pkt << uint8(WOW_FAIL_UNKNOWN_ACCOUNT);
}
+ // Get the password from the account table, upper it, and make the SRP6 calculation
+ std::string rI = fields[10].GetString();
+
+ // Don't calculate (v, s) if there are already some in the database
+ std::string databaseV = fields[11].GetString();
+ std::string databaseS = fields[12].GetString();
+
+ TC_LOG_DEBUG("network", "database authentication values: v='%s' s='%s'", databaseV.c_str(), databaseS.c_str());
+
+ // multiply with 2 since bytes are stored as hexstring
+ if (databaseV.size() != size_t(BufferSizes::SRP_6_V) * 2 || databaseS.size() != size_t(BufferSizes::SRP_6_S) * 2)
+ SetVSFields(rI);
+ else
+ {
+ s.SetHexStr(databaseS.c_str());
+ v.SetHexStr(databaseV.c_str());
+ }
+
+ b.SetRand(19 * 8);
+ BigNumber gmod = g.ModExp(b, N);
+ B = ((v * 3) + gmod) % N;
+
+ ASSERT(gmod.GetNumBytes() <= 32);
+
+ BigNumber unk3;
+ unk3.SetRand(16 * 8);
+
+ // Fill the response packet with the result
+ if (AuthHelper::IsAcceptedClientBuild(_build))
+ pkt << uint8(WOW_SUCCESS);
+ else
+ pkt << uint8(WOW_FAIL_VERSION_INVALID);
+
+ // B may be calculated < 32B so we force minimal length to 32B
+ pkt.append(B.AsByteArray(32).get(), 32); // 32 bytes
+ pkt << uint8(1);
+ pkt.append(g.AsByteArray(1).get(), 1);
+ pkt << uint8(32);
+ pkt.append(N.AsByteArray(32).get(), 32);
+ pkt.append(s.AsByteArray(int32(BufferSizes::SRP_6_S)).get(), size_t(BufferSizes::SRP_6_S)); // 32 bytes
+ pkt.append(unk3.AsByteArray(16).get(), 16);
+ uint8 securityFlags = 0;
+
+ // Check if token is used
+ _tokenKey = fields[9].GetString();
+ if (!_tokenKey.empty())
+ securityFlags = 4;
+
+ pkt << uint8(securityFlags); // security flags (0x0...0x04)
+
+ if (securityFlags & 0x01) // PIN input
+ {
+ pkt << uint32(0);
+ pkt << uint64(0) << uint64(0); // 16 bytes hash?
+ }
+
+ if (securityFlags & 0x02) // Matrix input
+ {
+ pkt << uint8(0);
+ pkt << uint8(0);
+ pkt << uint8(0);
+ pkt << uint8(0);
+ pkt << uint64(0);
+ }
+
+ if (securityFlags & 0x04) // Security token input
+ pkt << uint8(1);
+
+ TC_LOG_DEBUG("server.authserver", "'%s:%d' [AuthChallenge] account %s is using '%s' locale (%u)",
+ ipAddress.c_str(), port, _accountInfo.Login.c_str(), _localizationName.c_str(), GetLocaleByName(_localizationName));
+
SendPacket(pkt);
- return true;
}
// Logon Proof command handler
bool AuthSession::HandleLogonProof()
{
-
TC_LOG_DEBUG("server.authserver", "Entering _HandleLogonProof");
+ if (_sentProof)
+ return false;
+
+ _sentProof = true;
+
// Read the packet
sAuthLogonProof_C *logonProof = reinterpret_cast<sAuthLogonProof_C*>(GetReadBuffer().GetReadPointer());
@@ -487,7 +553,7 @@ bool AuthSession::HandleLogonProof()
t3.SetBinary(hash, 20);
sha.Initialize();
- sha.UpdateData(_login);
+ sha.UpdateData(_accountInfo.Login);
sha.Finalize();
uint8 t4[SHA_DIGEST_LENGTH];
memcpy(t4, sha.GetDigest(), SHA_DIGEST_LENGTH);
@@ -503,7 +569,7 @@ bool AuthSession::HandleLogonProof()
// Check if SRP6 results match (password is correct), else send an error
if (!memcmp(M.AsByteArray(sha.GetLength()).get(), logonProof->M1, 20))
{
- TC_LOG_DEBUG("server.authserver", "'%s:%d' User '%s' successfully authenticated", GetRemoteIpAddress().to_string().c_str(), GetRemotePort(), _login.c_str());
+ TC_LOG_DEBUG("server.authserver", "'%s:%d' User '%s' successfully authenticated", GetRemoteIpAddress().to_string().c_str(), GetRemotePort(), _accountInfo.Login.c_str());
// Update the sessionkey, last_ip, last login time and reset number of failed logins in the account table for this account
// No SQL injection (escaped user name) and IP address as received by socket
@@ -514,7 +580,7 @@ bool AuthSession::HandleLogonProof()
stmt->setString(1, GetRemoteIpAddress().to_string().c_str());
stmt->setUInt32(2, GetLocaleByName(_localizationName));
stmt->setString(3, _os);
- stmt->setString(4, _login);
+ stmt->setString(4, _accountInfo.Login);
LoginDatabase.DirectExecute(stmt);
OPENSSL_free((void*)K_hex);
@@ -531,6 +597,7 @@ bool AuthSession::HandleLogonProof()
std::string token(reinterpret_cast<char*>(GetReadBuffer().GetReadPointer() + sizeof(sAuthLogonProof_C) + sizeof(size)), size);
GetReadBuffer().ReadCompleted(sizeof(size) + size);
uint32 validToken = TOTP::GenerateToken(_tokenKey.c_str());
+ _tokenKey.clear();
uint32 incomingToken = atoi(token.c_str());
if (validToken != incomingToken)
{
@@ -571,7 +638,7 @@ bool AuthSession::HandleLogonProof()
}
SendPacket(packet);
- _isAuthenticated = true;
+ _status = STATUS_AUTHED;
}
else
{
@@ -583,7 +650,7 @@ bool AuthSession::HandleLogonProof()
SendPacket(packet);
TC_LOG_DEBUG("server.authserver", "'%s:%d' [AuthChallenge] account %s tried to login with invalid password!",
- GetRemoteIpAddress().to_string().c_str(), GetRemotePort(), _login.c_str());
+ GetRemoteIpAddress().to_string().c_str(), GetRemotePort(), _accountInfo.Login.c_str());
uint32 MaxWrongPassCount = sConfigMgr->GetIntDefault("WrongPass.MaxCount", 0);
@@ -591,7 +658,7 @@ bool AuthSession::HandleLogonProof()
if (sConfigMgr->GetBoolDefault("WrongPass.Logging", false))
{
PreparedStatement* logstmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_FALP_IP_LOGGING);
- logstmt->setString(0, _login);
+ logstmt->setString(0, _accountInfo.Login);
logstmt->setString(1, GetRemoteIpAddress().to_string());
logstmt->setString(2, "Logged on failed AccountLogin due wrong password");
@@ -602,42 +669,33 @@ bool AuthSession::HandleLogonProof()
{
//Increment number of failed logins by one and if it reaches the limit temporarily ban that account or IP
PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_FAILEDLOGINS);
- stmt->setString(0, _login);
+ stmt->setString(0, _accountInfo.Login);
LoginDatabase.Execute(stmt);
- stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_FAILEDLOGINS);
- stmt->setString(0, _login);
-
- if (PreparedQueryResult loginfail = LoginDatabase.Query(stmt))
+ if (_accountInfo.FailedLogins >= MaxWrongPassCount)
{
- uint32 failed_logins = (*loginfail)[1].GetUInt32();
+ uint32 WrongPassBanTime = sConfigMgr->GetIntDefault("WrongPass.BanTime", 600);
+ bool WrongPassBanType = sConfigMgr->GetBoolDefault("WrongPass.BanType", false);
- if (failed_logins >= MaxWrongPassCount)
+ if (WrongPassBanType)
+ {
+ stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_ACCOUNT_AUTO_BANNED);
+ stmt->setUInt32(0, _accountInfo.Id);
+ stmt->setUInt32(1, WrongPassBanTime);
+ LoginDatabase.Execute(stmt);
+
+ TC_LOG_DEBUG("server.authserver", "'%s:%d' [AuthChallenge] account %s got banned for '%u' seconds because it failed to authenticate '%u' times",
+ GetRemoteIpAddress().to_string().c_str(), GetRemotePort(), _accountInfo.Login.c_str(), WrongPassBanTime, _accountInfo.FailedLogins);
+ }
+ else
{
- uint32 WrongPassBanTime = sConfigMgr->GetIntDefault("WrongPass.BanTime", 600);
- bool WrongPassBanType = sConfigMgr->GetBoolDefault("WrongPass.BanType", false);
-
- if (WrongPassBanType)
- {
- uint32 acc_id = (*loginfail)[0].GetUInt32();
- stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_ACCOUNT_AUTO_BANNED);
- stmt->setUInt32(0, acc_id);
- stmt->setUInt32(1, WrongPassBanTime);
- LoginDatabase.Execute(stmt);
-
- TC_LOG_DEBUG("server.authserver", "'%s:%d' [AuthChallenge] account %s got banned for '%u' seconds because it failed to authenticate '%u' times",
- GetRemoteIpAddress().to_string().c_str(), GetRemotePort(), _login.c_str(), WrongPassBanTime, failed_logins);
- }
- else
- {
- stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_IP_AUTO_BANNED);
- stmt->setString(0, GetRemoteIpAddress().to_string());
- stmt->setUInt32(1, WrongPassBanTime);
- LoginDatabase.Execute(stmt);
-
- TC_LOG_DEBUG("server.authserver", "'%s:%d' [AuthChallenge] IP got banned for '%u' seconds because account %s failed to authenticate '%u' times",
- GetRemoteIpAddress().to_string().c_str(), GetRemotePort(), WrongPassBanTime, _login.c_str(), failed_logins);
- }
+ stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_IP_AUTO_BANNED);
+ stmt->setString(0, GetRemoteIpAddress().to_string());
+ stmt->setUInt32(1, WrongPassBanTime);
+ LoginDatabase.Execute(stmt);
+
+ TC_LOG_DEBUG("server.authserver", "'%s:%d' [AuthChallenge] IP got banned for '%u' seconds because account %s failed to authenticate '%u' times",
+ GetRemoteIpAddress().to_string().c_str(), GetRemotePort(), WrongPassBanTime, _accountInfo.Login.c_str(), _accountInfo.FailedLogins);
}
}
}
@@ -648,61 +706,88 @@ bool AuthSession::HandleLogonProof()
bool AuthSession::HandleReconnectChallenge()
{
- TC_LOG_DEBUG("server.authserver", "Entering _HandleReconnectChallenge");
- sAuthLogonChallenge_C* challenge = reinterpret_cast<sAuthLogonChallenge_C*>(GetReadBuffer().GetReadPointer());
+ if (_sentChallenge)
+ return false;
- //TC_LOG_DEBUG("server.authserver", "[AuthChallenge] got full packet, %#04x bytes", challenge->size);
- TC_LOG_DEBUG("server.authserver", "[AuthChallenge] name(%d): '%s'", challenge->I_len, challenge->I);
+ _sentChallenge = true;
- _login.assign((const char*)challenge->I, challenge->I_len);
+ sAuthLogonChallenge_C* challenge = reinterpret_cast<sAuthLogonChallenge_C*>(GetReadBuffer().GetReadPointer());
+ if (challenge->size - (sizeof(sAuthLogonChallenge_C) - AUTH_LOGON_CHALLENGE_INITIAL_SIZE - 1) != challenge->I_len)
+ return false;
- PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_SESSIONKEY);
- stmt->setString(0, _login);
- PreparedQueryResult result = LoginDatabase.Query(stmt);
+ std::string login((const char*)challenge->I, challenge->I_len);
+ TC_LOG_DEBUG("server.authserver", "[ReconnectChallenge] '%s'", login.c_str());
- // Stop if the account is not found
- if (!result)
+ if (_queryCallback)
{
- TC_LOG_ERROR("server.authserver", "'%s:%d' [ERROR] user %s tried to login and we cannot find his session key in the database.",
- GetRemoteIpAddress().to_string().c_str(), GetRemotePort(), _login.c_str());
- return false;
+ ByteBuffer pkt;
+ pkt << uint8(AUTH_RECONNECT_CHALLENGE);
+ pkt << uint8(WOW_FAIL_DB_BUSY);
+ SendPacket(pkt);
+
+ TC_LOG_DEBUG("server.authserver", "[ReconnectChallenge] %s attempted to log too quick after previous attempt!", login.c_str());
+ return true;
}
- // Reinitialize build, expansion and the account securitylevel
_build = challenge->build;
_expversion = uint8(AuthHelper::IsPostBCAcceptedClientBuild(_build) ? POST_BC_EXP_FLAG : (AuthHelper::IsPreBCAcceptedClientBuild(_build) ? PRE_BC_EXP_FLAG : NO_VALID_EXP_FLAG));
- _os = (const char*)challenge->os;
-
- if (_os.size() > 4)
- return false;
+ std::array<char, 5> os;
+ os.fill('\0');
+ memcpy(os.data(), challenge->os, sizeof(challenge->os));
+ _os = os.data();
// Restore string order as its byte order is reversed
std::reverse(_os.begin(), _os.end());
- Field* fields = result->Fetch();
- uint8 secLevel = fields[2].GetUInt8();
- _accountSecurityLevel = secLevel <= SEC_ADMINISTRATOR ? AccountTypes(secLevel) : SEC_ADMINISTRATOR;
+ _localizationName.resize(4);
+ for (int i = 0; i < 4; ++i)
+ _localizationName[i] = challenge->country[4 - i - 1];
- K.SetHexStr((*result)[0].GetCString());
+ // Get the account details from the account table
+ PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_RECONNECTCHALLENGE);
+ stmt->setString(0, login);
+
+ _queryCallback = std::bind(&AuthSession::ReconnectChallengeCallback, this, std::placeholders::_1);
+ _queryFuture = LoginDatabase.AsyncQuery(stmt);
+ return true;
+}
- // Sending response
+void AuthSession::ReconnectChallengeCallback(PreparedQueryResult result)
+{
ByteBuffer pkt;
pkt << uint8(AUTH_RECONNECT_CHALLENGE);
- pkt << uint8(0x00);
+
+ if (!result)
+ {
+ pkt << uint8(WOW_FAIL_UNKNOWN_ACCOUNT);
+ SendPacket(pkt);
+ return;
+ }
+
+ Field* fields = result->Fetch();
+
+ _accountInfo.LoadResult(fields);
+ K.SetHexStr(fields[9].GetCString());
_reconnectProof.SetRand(16 * 8);
- pkt.append(_reconnectProof.AsByteArray(16).get(), 16); // 16 bytes random
+
+ pkt << uint8(WOW_SUCCESS);
+ pkt.append(_reconnectProof.AsByteArray(16).get(), 16); // 16 bytes random
pkt << uint64(0x00) << uint64(0x00); // 16 bytes zeros
SendPacket(pkt);
-
- return true;
}
+
bool AuthSession::HandleReconnectProof()
{
TC_LOG_DEBUG("server.authserver", "Entering _HandleReconnectProof");
+ if (_sentProof)
+ return false;
+
+ _sentProof = true;
+
sAuthReconnectProof_C *reconnectProof = reinterpret_cast<sAuthReconnectProof_C*>(GetReadBuffer().GetReadPointer());
- if (_login.empty() || !_reconnectProof.GetNumBytes() || !K.GetNumBytes())
+ if (_accountInfo.Login.empty() || !_reconnectProof.GetNumBytes() || !K.GetNumBytes())
return false;
BigNumber t1;
@@ -710,7 +795,7 @@ bool AuthSession::HandleReconnectProof()
SHA1Hash sha;
sha.Initialize();
- sha.UpdateData(_login);
+ sha.UpdateData(_accountInfo.Login);
sha.UpdateBigNumbers(&t1, &_reconnectProof, &K, NULL);
sha.Finalize();
@@ -722,13 +807,13 @@ bool AuthSession::HandleReconnectProof()
pkt << uint8(0x00);
pkt << uint16(0x00); // 2 bytes zeros
SendPacket(pkt);
- _isAuthenticated = true;
+ _status = STATUS_AUTHED;
return true;
}
else
{
TC_LOG_ERROR("server.authserver", "'%s:%d' [ERROR] user %s tried to login, but session is invalid.", GetRemoteIpAddress().to_string().c_str(),
- GetRemotePort(), _login.c_str());
+ GetRemotePort(), _accountInfo.Login.c_str());
return false;
}
}
@@ -772,20 +857,31 @@ bool AuthSession::HandleRealmList()
{
TC_LOG_DEBUG("server.authserver", "Entering _HandleRealmList");
- // Get the user id (else close the connection)
- // No SQL injection (prepared statement)
- PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_ACCOUNT_ID_BY_NAME);
- stmt->setString(0, _login);
- PreparedQueryResult result = LoginDatabase.Query(stmt);
- if (!result)
+ if (_queryCallback)
{
- TC_LOG_ERROR("server.authserver", "'%s:%d' [ERROR] user %s tried to login but we cannot find him in the database.", GetRemoteIpAddress().to_string().c_str(),
- GetRemotePort(), _login.c_str());
+ TC_LOG_DEBUG("server.authserver", "[RealmList] %s attempted to get realmlist too quick after previous attempt!", _accountInfo.Login.c_str());
return false;
}
- Field* fields = result->Fetch();
- uint32 id = fields[0].GetUInt32();
+ PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_REALM_CHARACTER_COUNTS);
+ stmt->setUInt32(0, _accountInfo.Id);
+
+ _queryCallback = std::bind(&AuthSession::RealmListCallback, this, std::placeholders::_1);
+ _queryFuture = LoginDatabase.AsyncQuery(stmt);
+ return true;
+}
+
+void AuthSession::RealmListCallback(PreparedQueryResult result)
+{
+ std::map<uint32, uint8> characterCounts;
+ if (result)
+ {
+ do
+ {
+ Field* fields = result->Fetch();
+ characterCounts[fields[0].GetUInt32()] = fields[1].GetUInt8();
+ } while (result->NextRow());
+ }
// Update realm list if need
sRealmList->UpdateIfNeed();
@@ -822,25 +918,17 @@ bool AuthSession::HandleRealmList()
name = ss.str();
}
- uint8 lock = (realm.allowedSecurityLevel > _accountSecurityLevel) ? 1 : 0;
-
- uint8 AmountOfCharacters = 0;
- stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_NUM_CHARS_ON_REALM);
- stmt->setUInt32(0, realm.m_ID);
- stmt->setUInt32(1, id);
- result = LoginDatabase.Query(stmt);
- if (result)
- AmountOfCharacters = (*result)[0].GetUInt8();
+ uint8 lock = (realm.allowedSecurityLevel > _accountInfo.SecurityLevel) ? 1 : 0;
- pkt << realm.icon; // realm type
+ pkt << uint8(realm.icon); // realm type
if (_expversion & POST_BC_EXP_FLAG) // only 2.x and 3.x clients
- pkt << lock; // if 1, then realm locked
+ pkt << uint8(lock); // if 1, then realm locked
pkt << uint8(flag); // RealmFlags
pkt << name;
pkt << boost::lexical_cast<std::string>(GetAddressForClient(realm, GetRemoteIpAddress()));
- pkt << realm.populationLevel;
- pkt << AmountOfCharacters;
- pkt << realm.timezone; // realm category
+ pkt << float(realm.populationLevel);
+ pkt << uint8(characterCounts[realm.m_ID]);
+ pkt << uint8(realm.timezone); // realm category
if (_expversion & POST_BC_EXP_FLAG) // 2.x and 3.x clients
pkt << uint8(realm.m_ID);
else
@@ -882,32 +970,6 @@ bool AuthSession::HandleRealmList()
hdr.append(RealmListSizeBuffer); // append RealmList's size buffer
hdr.append(pkt); // append realms in the realmlist
SendPacket(hdr);
- return true;
-}
-
-// Resume patch transfer
-bool AuthSession::HandleXferResume()
-{
- TC_LOG_DEBUG("server.authserver", "Entering _HandleXferResume");
- //uint8
- //uint64
- return true;
-}
-
-// Cancel patch transfer
-bool AuthSession::HandleXferCancel()
-{
- TC_LOG_DEBUG("server.authserver", "Entering _HandleXferCancel");
- //uint8
- return false;
-}
-
-// Accept patch transfer
-bool AuthSession::HandleXferAccept()
-{
- TC_LOG_DEBUG("server.authserver", "Entering _HandleXferAccept");
- //uint8
- return true;
}
// Make the SRP6 calculation from hash in dB
@@ -940,7 +1002,7 @@ void AuthSession::SetVSFields(const std::string& rI)
PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_VS);
stmt->setString(0, v_hex);
stmt->setString(1, s_hex);
- stmt->setString(2, _login);
+ stmt->setString(2, _accountInfo.Login);
LoginDatabase.Execute(stmt);
OPENSSL_free(v_hex);
diff --git a/src/server/authserver/Server/AuthSession.h b/src/server/authserver/Server/AuthSession.h
index d40e0852b57..1babb7407a9 100644
--- a/src/server/authserver/Server/AuthSession.h
+++ b/src/server/authserver/Server/AuthSession.h
@@ -23,29 +23,48 @@
#include "ByteBuffer.h"
#include "Socket.h"
#include "BigNumber.h"
+#include "Callback.h"
#include <memory>
#include <boost/asio/ip/tcp.hpp>
using boost::asio::ip::tcp;
+class Field;
struct AuthHandler;
+enum AuthStatus
+{
+ STATUS_CONNECTED = 0,
+ STATUS_AUTHED
+};
+
+struct AccountInfo
+{
+ void LoadResult(Field* fields);
+
+ uint32 Id = 0;
+ std::string Login;
+ bool IsLockedToIP = false;
+ std::string LockCountry;
+ std::string LastIP;
+ uint32 FailedLogins = 0;
+ bool IsBanned = false;
+ bool IsPermanenetlyBanned = false;
+ AccountTypes SecurityLevel = SEC_PLAYER;
+ std::string TokenKey;
+};
+
class AuthSession : public Socket<AuthSession>
{
+ typedef Socket<AuthSession> AuthSocket;
+
public:
static std::unordered_map<uint8, AuthHandler> InitHandlers();
- AuthSession(tcp::socket&& socket) : Socket(std::move(socket)),
- _isAuthenticated(false), _build(0), _expversion(0), _accountSecurityLevel(SEC_PLAYER)
- {
- N.SetHexStr("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7");
- g.SetDword(7);
- }
+ AuthSession(tcp::socket&& socket);
- void Start() override
- {
- AsyncRead();
- }
+ void Start() override;
+ bool Update() override;
void SendPacket(ByteBuffer& packet);
@@ -59,10 +78,10 @@ private:
bool HandleReconnectProof();
bool HandleRealmList();
- //data transfer handle for patch
- bool HandleXferResume();
- bool HandleXferCancel();
- bool HandleXferAccept();
+ void CheckIpCallback(PreparedQueryResult result);
+ void LogonChallengeCallback(PreparedQueryResult result);
+ void ReconnectChallengeCallback(PreparedQueryResult result);
+ void RealmListCallback(PreparedQueryResult result);
void SetVSFields(const std::string& rI);
@@ -71,22 +90,27 @@ private:
BigNumber K;
BigNumber _reconnectProof;
- bool _isAuthenticated;
+ bool _sentChallenge;
+ bool _sentProof;
+
+ AuthStatus _status;
+ AccountInfo _accountInfo;
std::string _tokenKey;
- std::string _login;
std::string _localizationName;
std::string _os;
+ std::string _ipCountry;
uint16 _build;
uint8 _expversion;
- AccountTypes _accountSecurityLevel;
+ PreparedQueryResultFuture _queryFuture;
+ std::function<void(PreparedQueryResult)> _queryCallback;
};
#pragma pack(push, 1)
struct AuthHandler
{
- uint32 status;
+ AuthStatus status;
size_t packetSize;
bool (AuthSession::*handler)();
};
diff --git a/src/server/authserver/authserver.conf.dist b/src/server/authserver/authserver.conf.dist
index 604988d62e5..82c3cd47148 100644
--- a/src/server/authserver/authserver.conf.dist
+++ b/src/server/authserver/authserver.conf.dist
@@ -131,6 +131,13 @@ WrongPass.BanType = 0
WrongPass.Logging = 0
#
+# BanExpiryCheckInterval
+# Description: Time (in seconds) between checks for expired bans
+# Default: 60
+
+BanExpiryCheckInterval = 60
+
+#
###################################################################################################
###################################################################################################
diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp
index 140f3bf31c9..ab68aca2a8c 100644
--- a/src/server/database/Database/Implementation/CharacterDatabase.cpp
+++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp
@@ -72,7 +72,7 @@ void CharacterDatabaseConnection::DoPrepareStatements()
"FROM characters WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_GROUP_MEMBER, "SELECT guid FROM group_member WHERE memberGuid = ?", CONNECTION_BOTH);
- PrepareStatement(CHAR_SEL_CHARACTER_INSTANCE, "SELECT id, permanent, map, difficulty, resettime FROM character_instance LEFT JOIN instance ON instance = id WHERE guid = ?", CONNECTION_ASYNC);
+ PrepareStatement(CHAR_SEL_CHARACTER_INSTANCE, "SELECT id, permanent, map, difficulty, extendState, resettime FROM character_instance LEFT JOIN instance ON instance = id WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_AURAS, "SELECT casterGuid, spell, effectMask, recalculateMask, stackCount, amount0, amount1, amount2, "
"base_amount0, base_amount1, base_amount2, maxDuration, remainTime, remainCharges FROM character_aura WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHARACTER_SPELL, "SELECT spell, active, disabled FROM character_spell WHERE guid = ?", CONNECTION_ASYNC);
@@ -405,8 +405,8 @@ void CharacterDatabaseConnection::DoPrepareStatements()
PrepareStatement(CHAR_UPD_WORLDSTATE, "UPDATE worldstates SET value = ? WHERE entry = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_WORLDSTATE, "INSERT INTO worldstates (entry, value) VALUES (?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_INSTANCE_BY_INSTANCE_GUID, "DELETE FROM character_instance WHERE guid = ? AND instance = ?", CONNECTION_ASYNC);
- PrepareStatement(CHAR_UPD_CHAR_INSTANCE, "UPDATE character_instance SET instance = ?, permanent = ? WHERE guid = ? AND instance = ?", CONNECTION_ASYNC);
- PrepareStatement(CHAR_INS_CHAR_INSTANCE, "INSERT INTO character_instance (guid, instance, permanent) VALUES (?, ?, ?)", CONNECTION_ASYNC);
+ PrepareStatement(CHAR_UPD_CHAR_INSTANCE, "UPDATE character_instance SET instance = ?, permanent = ?, extendState = ? WHERE guid = ? AND instance = ?", CONNECTION_ASYNC);
+ PrepareStatement(CHAR_INS_CHAR_INSTANCE, "INSERT INTO character_instance (guid, instance, permanent, extendState) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_GENDER_PLAYERBYTES, "UPDATE characters SET gender = ?, playerBytes = ?, playerBytes2 = ? WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHARACTER_SKILL, "DELETE FROM character_skills WHERE guid = ? AND skill = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_ADD_CHARACTER_SOCIAL_FLAGS, "UPDATE character_social SET flags = flags | ? WHERE guid = ? AND friend = ?", CONNECTION_ASYNC);
@@ -434,6 +434,7 @@ void CharacterDatabaseConnection::DoPrepareStatements()
PrepareStatement(CHAR_SEL_CHAR_CLASS_LVL_AT_LOGIN, "SELECT class, level, at_login, knownTitles FROM characters WHERE guid = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_CHAR_AT_LOGIN_TITLES, "SELECT at_login, knownTitles FROM characters WHERE guid = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_INSTANCE, "SELECT data, completedEncounters FROM instance WHERE map = ? AND id = ?", CONNECTION_SYNCH);
+ PrepareStatement(CHAR_SEL_PERM_BIND_BY_INSTANCE, "SELECT guid FROM character_instance WHERE instance = ? and permanent = 1", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_CHAR_COD_ITEM_MAIL, "SELECT id, messageType, mailTemplateId, sender, subject, body, money, has_items FROM mail WHERE receiver = ? AND has_items <> 0 AND cod <> 0", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_CHAR_SOCIAL, "SELECT DISTINCT guid FROM character_social WHERE friend = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_CHAR_OLD_CHARS, "SELECT guid, deleteInfos_Account FROM characters WHERE deleteDate IS NOT NULL AND deleteDate < ?", CONNECTION_SYNCH);
@@ -470,9 +471,10 @@ void CharacterDatabaseConnection::DoPrepareStatements()
PrepareStatement(CHAR_INS_CHAR_GIFT, "INSERT INTO character_gifts (guid, item_guid, entry, flags) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_INSTANCE_BY_INSTANCE, "DELETE FROM instance WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_INSTANCE_BY_INSTANCE, "DELETE FROM character_instance WHERE instance = ?", CONNECTION_ASYNC);
- PrepareStatement(CHAR_DEL_CHAR_INSTANCE_BY_MAP_DIFF, "DELETE FROM character_instance USING character_instance LEFT JOIN instance ON character_instance.instance = id WHERE map = ? and difficulty = ?", CONNECTION_ASYNC);
+ PrepareStatement(CHAR_DEL_EXPIRED_CHAR_INSTANCE_BY_MAP_DIFF, "DELETE FROM character_instance USING character_instance LEFT JOIN instance ON character_instance.instance = id WHERE (extendState = 0 or permanent = 0) and map = ? and difficulty = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_GROUP_INSTANCE_BY_MAP_DIFF, "DELETE FROM group_instance USING group_instance LEFT JOIN instance ON group_instance.instance = id WHERE map = ? and difficulty = ?", CONNECTION_ASYNC);
- PrepareStatement(CHAR_DEL_INSTANCE_BY_MAP_DIFF, "DELETE FROM instance WHERE map = ? and difficulty = ?", CONNECTION_ASYNC);
+ PrepareStatement(CHAR_DEL_EXPIRED_INSTANCE_BY_MAP_DIFF, "DELETE FROM instance WHERE map = ? and difficulty = ? and (SELECT guid FROM character_instance WHERE extendState != 0 AND instance = id LIMIT 1) IS NULL", CONNECTION_ASYNC);
+ PrepareStatement(CHAR_UPD_EXPIRE_CHAR_INSTANCE_BY_MAP_DIFF, "UPDATE character_instance LEFT JOIN instance ON character_instance.instance = id SET extendState = extendState-1 WHERE map = ? and difficulty = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_MAIL_ITEM_BY_ID, "DELETE FROM mail_items WHERE mail_id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_PETITION, "INSERT INTO petition (ownerguid, petitionguid, name, type) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_PETITION_BY_GUID, "DELETE FROM petition WHERE petitionguid = ?", CONNECTION_ASYNC);
diff --git a/src/server/database/Database/Implementation/CharacterDatabase.h b/src/server/database/Database/Implementation/CharacterDatabase.h
index c0ff9cbbfbe..19d4a609e77 100644
--- a/src/server/database/Database/Implementation/CharacterDatabase.h
+++ b/src/server/database/Database/Implementation/CharacterDatabase.h
@@ -365,6 +365,7 @@ enum CharacterDatabaseStatements
CHAR_SEL_CHAR_CLASS_LVL_AT_LOGIN,
CHAR_SEL_CHAR_AT_LOGIN_TITLES,
CHAR_SEL_INSTANCE,
+ CHAR_SEL_PERM_BIND_BY_INSTANCE,
CHAR_SEL_CHAR_COD_ITEM_MAIL,
CHAR_SEL_CHAR_SOCIAL,
CHAR_SEL_CHAR_OLD_CHARS,
@@ -396,9 +397,10 @@ enum CharacterDatabaseStatements
CHAR_INS_CHAR_GIFT,
CHAR_DEL_INSTANCE_BY_INSTANCE,
CHAR_DEL_CHAR_INSTANCE_BY_INSTANCE,
- CHAR_DEL_CHAR_INSTANCE_BY_MAP_DIFF,
+ CHAR_DEL_EXPIRED_CHAR_INSTANCE_BY_MAP_DIFF,
CHAR_DEL_GROUP_INSTANCE_BY_MAP_DIFF,
- CHAR_DEL_INSTANCE_BY_MAP_DIFF,
+ CHAR_DEL_EXPIRED_INSTANCE_BY_MAP_DIFF,
+ CHAR_UPD_EXPIRE_CHAR_INSTANCE_BY_MAP_DIFF,
CHAR_DEL_MAIL_ITEM_BY_ID,
CHAR_INS_PETITION,
CHAR_DEL_PETITION_BY_GUID,
diff --git a/src/server/database/Database/Implementation/LoginDatabase.cpp b/src/server/database/Database/Implementation/LoginDatabase.cpp
index 66847f0a6a0..2749c08594f 100644
--- a/src/server/database/Database/Implementation/LoginDatabase.cpp
+++ b/src/server/database/Database/Implementation/LoginDatabase.cpp
@@ -24,33 +24,34 @@ void LoginDatabaseConnection::DoPrepareStatements()
PrepareStatement(LOGIN_SEL_REALMLIST, "SELECT id, name, address, localAddress, localSubnetMask, port, icon, flag, timezone, allowedSecurityLevel, population, gamebuild FROM realmlist WHERE flag <> 3 ORDER BY name", CONNECTION_SYNCH);
PrepareStatement(LOGIN_DEL_EXPIRED_IP_BANS, "DELETE FROM ip_banned WHERE unbandate<>bandate AND unbandate<=UNIX_TIMESTAMP()", CONNECTION_ASYNC);
- PrepareStatement(LOGIN_UPD_EXPIRED_ACCOUNT_BANS, "UPDATE account_banned SET active = 0 WHERE active = 1 AND unbandate<>bandate AND unbandate<=UNIX_TIMESTAMP()", CONNECTION_SYNCH);
+ PrepareStatement(LOGIN_UPD_EXPIRED_ACCOUNT_BANS, "UPDATE account_banned SET active = 0 WHERE active = 1 AND unbandate<>bandate AND unbandate<=UNIX_TIMESTAMP()", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_IP_INFO, "(SELECT unbandate > UNIX_TIMESTAMP() OR unbandate = bandate AS banned, NULL as country FROM ip_banned WHERE ip = ?) "
"UNION "
"(SELECT NULL AS banned, country FROM ip2nation WHERE INET_NTOA(ip) = ?)", CONNECTION_ASYNC);
- PrepareStatement(LOGIN_SEL_IP_BANNED, "SELECT * FROM ip_banned WHERE ip = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_INS_IP_AUTO_BANNED, "INSERT INTO ip_banned (ip, bandate, unbandate, bannedby, banreason) VALUES (?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()+?, 'Trinity Auth', 'Failed login autoban')", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_IP_BANNED_ALL, "SELECT ip, bandate, unbandate, bannedby, banreason FROM ip_banned WHERE (bandate = unbandate OR unbandate > UNIX_TIMESTAMP()) ORDER BY unbandate", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_IP_BANNED_BY_IP, "SELECT ip, bandate, unbandate, bannedby, banreason FROM ip_banned WHERE (bandate = unbandate OR unbandate > UNIX_TIMESTAMP()) AND ip LIKE CONCAT('%%', ?, '%%') ORDER BY unbandate", CONNECTION_SYNCH);
- PrepareStatement(LOGIN_SEL_ACCOUNT_BANNED, "SELECT bandate, unbandate FROM account_banned WHERE id = ? AND active = 1", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_ACCOUNT_BANNED_ALL, "SELECT account.id, username FROM account, account_banned WHERE account.id = account_banned.id AND active = 1 GROUP BY account.id", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_ACCOUNT_BANNED_BY_USERNAME, "SELECT account.id, username FROM account, account_banned WHERE account.id = account_banned.id AND active = 1 AND username LIKE CONCAT('%%', ?, '%%') GROUP BY account.id", CONNECTION_SYNCH);
PrepareStatement(LOGIN_INS_ACCOUNT_AUTO_BANNED, "INSERT INTO account_banned VALUES (?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()+?, 'Trinity Auth', 'Failed login autoban', 1)", CONNECTION_ASYNC);
PrepareStatement(LOGIN_DEL_ACCOUNT_BANNED, "DELETE FROM account_banned WHERE id = ?", CONNECTION_ASYNC);
- PrepareStatement(LOGIN_SEL_SESSIONKEY, "SELECT a.sessionkey, a.id, aa.gmlevel FROM account a LEFT JOIN account_access aa ON (a.id = aa.id) WHERE username = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_UPD_VS, "UPDATE account SET v = ?, s = ? WHERE username = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_LOGONPROOF, "UPDATE account SET sessionkey = ?, last_ip = ?, last_login = NOW(), locale = ?, failed_logins = 0, os = ? WHERE username = ?", CONNECTION_SYNCH);
- PrepareStatement(LOGIN_SEL_LOGONCHALLENGE, "SELECT a.sha_pass_hash, a.id, a.locked, a.lock_country, a.last_ip, aa.gmlevel, a.v, a.s, a.token_key FROM account a LEFT JOIN account_access aa ON (a.id = aa.id) WHERE a.username = ?", CONNECTION_SYNCH);
+ PrepareStatement(LOGIN_SEL_LOGONCHALLENGE, "SELECT a.id, UPPER(a.username), a.locked, a.lock_country, a.last_ip, a.failed_logins, ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, "
+ "ab.unbandate = ab.bandate, aa.gmlevel, a.token_key, a.sha_pass_hash, a.v, a.s "
+ "FROM account a LEFT JOIN account_access aa ON a.id = aa.id LEFT JOIN account_banned ab ON ab.id = a.id AND ab.active = 1 WHERE a.username = ?", CONNECTION_ASYNC);
+ PrepareStatement(LOGIN_SEL_RECONNECTCHALLENGE, "SELECT a.id, UPPER(a.username), a.locked, a.lock_country, a.last_ip, a.failed_logins, ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, "
+ "ab.unbandate = ab.bandate, aa.gmlevel, a.sessionKey "
+ "FROM account a LEFT JOIN account_access aa ON a.id = aa.id LEFT JOIN account_banned ab ON ab.id = a.id AND ab.active = 1 WHERE a.username = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_LOGON_COUNTRY, "SELECT country FROM ip2nation WHERE ip < ? ORDER BY ip DESC LIMIT 0,1", CONNECTION_SYNCH);
PrepareStatement(LOGIN_UPD_FAILEDLOGINS, "UPDATE account SET failed_logins = failed_logins + 1 WHERE username = ?", CONNECTION_ASYNC);
- PrepareStatement(LOGIN_SEL_FAILEDLOGINS, "SELECT id, failed_logins FROM account WHERE username = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_ACCOUNT_ID_BY_NAME, "SELECT id FROM account WHERE username = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_ACCOUNT_LIST_BY_NAME, "SELECT id, username FROM account WHERE username = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_ACCOUNT_INFO_BY_NAME, "SELECT a.id, a.sessionkey, a.last_ip, a.locked, a.lock_country, a.expansion, a.mutetime, a.locale, a.recruiter, a.os, aa.gmLevel, "
"ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, r.id FROM account a LEFT JOIN account_access aa ON a.id = aa.id AND aa.RealmID IN (-1, ?) "
"LEFT JOIN account_banned ab ON a.id = ab.id AND ab.active = 1 LEFT JOIN account r ON a.id = r.recruiter WHERE a.username = ? ORDER BY aa.RealmID DESC LIMIT 1", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_ACCOUNT_LIST_BY_EMAIL, "SELECT id, username FROM account WHERE email = ?", CONNECTION_SYNCH);
- PrepareStatement(LOGIN_SEL_NUM_CHARS_ON_REALM, "SELECT numchars FROM realmcharacters WHERE realmid = ? AND acctid= ?", CONNECTION_SYNCH);
+ PrepareStatement(LOGIN_SEL_REALM_CHARACTER_COUNTS, "SELECT realmid, numchars FROM realmcharacters WHERE acctid = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_ACCOUNT_BY_IP, "SELECT id, username FROM account WHERE last_ip = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_ACCOUNT_BY_ID, "SELECT 1 FROM account WHERE id = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_INS_IP_BANNED, "INSERT INTO ip_banned (ip, bandate, unbandate, bannedby, banreason) VALUES (?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()+?, ?, ?)", CONNECTION_ASYNC);
diff --git a/src/server/database/Database/Implementation/LoginDatabase.h b/src/server/database/Database/Implementation/LoginDatabase.h
index 69c2e758551..a3789fa2557 100644
--- a/src/server/database/Database/Implementation/LoginDatabase.h
+++ b/src/server/database/Database/Implementation/LoginDatabase.h
@@ -33,25 +33,22 @@ enum LoginDatabaseStatements
LOGIN_DEL_EXPIRED_IP_BANS,
LOGIN_UPD_EXPIRED_ACCOUNT_BANS,
LOGIN_SEL_IP_INFO,
- LOGIN_SEL_IP_BANNED,
LOGIN_INS_IP_AUTO_BANNED,
- LOGIN_SEL_ACCOUNT_BANNED,
LOGIN_SEL_ACCOUNT_BANNED_ALL,
LOGIN_SEL_ACCOUNT_BANNED_BY_USERNAME,
LOGIN_INS_ACCOUNT_AUTO_BANNED,
LOGIN_DEL_ACCOUNT_BANNED,
- LOGIN_SEL_SESSIONKEY,
LOGIN_UPD_VS,
LOGIN_UPD_LOGONPROOF,
LOGIN_SEL_LOGONCHALLENGE,
+ LOGIN_SEL_RECONNECTCHALLENGE,
LOGIN_SEL_LOGON_COUNTRY,
LOGIN_UPD_FAILEDLOGINS,
- LOGIN_SEL_FAILEDLOGINS,
LOGIN_SEL_ACCOUNT_ID_BY_NAME,
LOGIN_SEL_ACCOUNT_LIST_BY_NAME,
LOGIN_SEL_ACCOUNT_INFO_BY_NAME,
LOGIN_SEL_ACCOUNT_LIST_BY_EMAIL,
- LOGIN_SEL_NUM_CHARS_ON_REALM,
+ LOGIN_SEL_REALM_CHARACTER_COUNTS,
LOGIN_SEL_ACCOUNT_BY_IP,
LOGIN_INS_IP_BANNED,
LOGIN_DEL_IP_NOT_BANNED,
diff --git a/src/server/game/AI/CreatureAI.cpp b/src/server/game/AI/CreatureAI.cpp
index c254a9124c1..352ae635878 100644
--- a/src/server/game/AI/CreatureAI.cpp
+++ b/src/server/game/AI/CreatureAI.cpp
@@ -133,7 +133,7 @@ void CreatureAI::MoveInLineOfSight(Unit* who)
if (me->GetCreatureType() == CREATURE_TYPE_NON_COMBAT_PET) // non-combat pets should just stand there and look good;)
return;
- if (me->CanStartAttack(who, false))
+ if (me->HasReactState(REACT_AGGRESSIVE) && me->CanStartAttack(who, false))
AttackStart(who);
//else if (who->GetVictim() && me->IsFriendlyTo(who)
// && me->IsWithinDistInMap(who, sWorld->getIntConfig(CONFIG_CREATURE_FAMILY_ASSISTANCE_RADIUS))
@@ -350,7 +350,7 @@ int32 CreatureAI::VisualizeBoundary(uint32 duration, Unit* owner, bool fill) con
return boundsWarning ? LANG_CREATURE_MOVEMENT_MAYBE_UNBOUNDED : 0;
}
-bool CreatureAI::CheckBoundary(Position* who) const
+bool CreatureAI::CheckBoundary(Position const* who) const
{
if (!who)
who = me;
diff --git a/src/server/game/AI/CreatureAI.h b/src/server/game/AI/CreatureAI.h
index 3b7c489e018..239fda577a7 100644
--- a/src/server/game/AI/CreatureAI.h
+++ b/src/server/game/AI/CreatureAI.h
@@ -79,8 +79,8 @@ class CreatureAI : public UnitAI
Creature* DoSummon(uint32 entry, WorldObject* obj, float radius = 5.0f, uint32 despawnTime = 30000, TempSummonType summonType = TEMPSUMMON_CORPSE_TIMED_DESPAWN);
Creature* DoSummonFlyer(uint32 entry, WorldObject* obj, float flightZ, float radius = 5.0f, uint32 despawnTime = 30000, TempSummonType summonType = TEMPSUMMON_CORPSE_TIMED_DESPAWN);
- bool CheckBoundary(Position* who = nullptr) const;
- void SetBoundary(CreatureBoundary const* boundary) { _boundary = boundary; CheckInRoom(); }
+ bool CheckBoundary(Position const* who = nullptr) const;
+ void SetBoundary(CreatureBoundary const* boundary) { _boundary = boundary; me->DoImmediateBoundaryCheck(); }
public:
enum EvadeReason
{
diff --git a/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp
index 8a1be33cc2b..2fc87347364 100644
--- a/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp
+++ b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp
@@ -547,6 +547,29 @@ void BossAI::UpdateAI(uint32 diff)
DoMeleeAttackIfReady();
}
+void BossAI::_DespawnAtEvade(uint32 delayToRespawn)
+{
+ if (delayToRespawn < 2)
+ {
+ TC_LOG_ERROR("scripts", "_DespawnAtEvade called with delay of %u seconds, defaulting to 2.", delayToRespawn);
+ delayToRespawn = 2;
+ }
+
+ uint32 corpseDelay = me->GetCorpseDelay();
+ uint32 respawnDelay = me->GetRespawnDelay();
+
+ me->SetCorpseDelay(1);
+ me->SetRespawnDelay(delayToRespawn - 1);
+
+ me->DespawnOrUnsummon();
+
+ me->SetCorpseDelay(corpseDelay);
+ me->SetRespawnDelay(respawnDelay);
+
+ if(instance)
+ instance->SetBossState(_bossId, FAIL);
+}
+
// WorldBossAI - for non-instanced bosses
WorldBossAI::WorldBossAI(Creature* creature) :
diff --git a/src/server/game/AI/ScriptedAI/ScriptedCreature.h b/src/server/game/AI/ScriptedAI/ScriptedCreature.h
index 448ddc7dc73..5452a033a17 100644
--- a/src/server/game/AI/ScriptedAI/ScriptedCreature.h
+++ b/src/server/game/AI/ScriptedAI/ScriptedCreature.h
@@ -360,11 +360,14 @@ class BossAI : public ScriptedAI
void JustDied(Unit* /*killer*/) override { _JustDied(); }
void JustReachedHome() override { _JustReachedHome(); }
+ bool CanAIAttack(Unit const* target) const override { return CheckBoundary(target); }
+
protected:
void _Reset();
void _EnterCombat();
void _JustDied();
void _JustReachedHome() { me->setActive(false); }
+ void _DespawnAtEvade(uint32 delayToRespawn = 30);
void TeleportCheaters();
diff --git a/src/server/game/AI/SmartScripts/SmartScript.cpp b/src/server/game/AI/SmartScripts/SmartScript.cpp
index 69a0f684a84..c08d1508774 100644
--- a/src/server/game/AI/SmartScripts/SmartScript.cpp
+++ b/src/server/game/AI/SmartScripts/SmartScript.cpp
@@ -1998,7 +1998,7 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
if (Creature* creature = (*itr)->ToCreature())
{
creature->GetMotionMaster()->Clear();
- creature->GetMotionMaster()->MoveJump(e.target.x, e.target.y, e.target.z, (float)e.action.jump.speedxy, (float)e.action.jump.speedz);
+ creature->GetMotionMaster()->MoveJump(e.target.x, e.target.y, e.target.z, 0.0f, (float)e.action.jump.speedxy, (float)e.action.jump.speedz); // @todo add optional jump orientation support?
}
}
/// @todo Resume path when reached jump location
@@ -2305,6 +2305,31 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
}
break;
}
+ case SMART_ACTION_RANDOM_SOUND:
+ {
+ std::vector<uint32> sounds;
+ std::copy_if(e.action.randomSound.sounds.begin(), e.action.randomSound.sounds.end(),
+ std::back_inserter(sounds), [](uint32 sound) { return sound != 0; });
+
+ bool onlySelf = e.action.randomSound.onlySelf != 0;
+
+ if (ObjectList* targets = GetTargets(e, unit))
+ {
+ for (WorldObject* const obj : *targets)
+ {
+ if (IsUnit(obj))
+ {
+ uint32 sound = Trinity::Containers::SelectRandomContainerElement(sounds);
+ obj->PlayDirectSound(sound, onlySelf ? obj->ToPlayer() : nullptr);
+ TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_RANDOM_SOUND: target: %s (%s), sound: %u, onlyself: %s",
+ obj->GetName().c_str(), obj->GetGUID().ToString().c_str(), sound, onlySelf ? "true" : "false");
+ }
+ }
+
+ delete targets;
+ break;
+ }
+ }
default:
TC_LOG_ERROR("sql.sql", "SmartScript::ProcessAction: Entry %d SourceType %u, Event %u, Unhandled Action type %u", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType());
break;
diff --git a/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp b/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp
index d56983924b4..ef3357fa6ed 100644
--- a/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp
+++ b/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp
@@ -468,7 +468,7 @@ bool SmartAIMgr::IsEventValid(SmartScriptHolder& e)
TC_LOG_ERROR("sql.sql", "SmartAIMgr: Entry %d SourceType %u Event %u Action %u uses non-existent Map entry %u, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.event.respawn.map);
return false;
}
- if (e.event.respawn.type == SMART_SCRIPT_RESPAWN_CONDITION_AREA && !GetAreaEntryByAreaID(e.event.respawn.area))
+ if (e.event.respawn.type == SMART_SCRIPT_RESPAWN_CONDITION_AREA && !sAreaTableStore.LookupEntry(e.event.respawn.area))
{
TC_LOG_ERROR("sql.sql", "SmartAIMgr: Entry %d SourceType %u Event %u Action %u uses non-existent Area entry %u, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.event.respawn.area);
return false;
@@ -827,6 +827,21 @@ bool SmartAIMgr::IsEventValid(SmartScriptHolder& e)
if (e.action.randomEmote.emote6 && !IsEmoteValid(e, e.action.randomEmote.emote6))
return false;
break;
+ case SMART_ACTION_RANDOM_SOUND:
+ {
+ if (std::all_of(e.action.randomSound.sounds.begin(), e.action.randomSound.sounds.end(), [](uint32 sound) { return sound == 0; }))
+ {
+ TC_LOG_ERROR("sql.sql", "SmartAIMgr: Entry %d SourceType %u Event %u Action %u does not have any non-zero sound",
+ e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType());
+ return false;
+ }
+
+ for (uint32 sound : e.action.randomSound.sounds)
+ if (sound && !IsSoundValid(e, sound))
+ return false;
+
+ break;
+ }
case SMART_ACTION_CAST:
{
if (!IsSpellValid(e, e.action.cast.spell))
diff --git a/src/server/game/AI/SmartScripts/SmartScriptMgr.h b/src/server/game/AI/SmartScripts/SmartScriptMgr.h
index 91ee0a1d1e0..c0ea648462d 100644
--- a/src/server/game/AI/SmartScripts/SmartScriptMgr.h
+++ b/src/server/game/AI/SmartScripts/SmartScriptMgr.h
@@ -43,6 +43,16 @@ struct WayPoint
float z;
};
+enum eSmartAI
+{
+ SMART_EVENT_PARAM_COUNT = 4,
+ SMART_ACTION_PARAM_COUNT = 6,
+ SMART_SUMMON_COUNTER = 0xFFFFFF,
+ SMART_ESCORT_LAST_OOC_POINT = 0xFFFFFF,
+ SMART_RANDOM_POINT = 0xFFFFFE,
+ SMART_ESCORT_TARGETS = 0xFFFFFF
+};
+
enum SMART_EVENT_PHASE
{
SMART_EVENT_PHASE_ALWAYS = 0,
@@ -540,8 +550,9 @@ enum SMART_ACTION
SMART_ACTION_GAME_EVENT_START = 112, // GameEventId
SMART_ACTION_START_CLOSEST_WAYPOINT = 113, // wp1, wp2, wp3, wp4, wp5, wp6, wp7
SMART_ACTION_RISE_UP = 114, // distance
+ SMART_ACTION_RANDOM_SOUND = 115, // soundId1, soundId2, soundId3, soundId4, soundId5, onlySelf
- SMART_ACTION_END = 115
+ SMART_ACTION_END = 116
};
struct SmartAction
@@ -1017,6 +1028,12 @@ struct SmartAction
uint32 wp6;
} closestWaypointFromList;
+ struct
+ {
+ std::array<uint32, SMART_ACTION_PARAM_COUNT - 1> sounds;
+ uint32 onlySelf;
+ } randomSound;
+
//! Note for any new future actions
//! All parameters must have type uint32
@@ -1180,16 +1197,6 @@ struct SmartTarget
};
};
-enum eSmartAI
-{
- SMART_EVENT_PARAM_COUNT = 4,
- SMART_ACTION_PARAM_COUNT = 6,
- SMART_SUMMON_COUNTER = 0xFFFFFF,
- SMART_ESCORT_LAST_OOC_POINT = 0xFFFFFF,
- SMART_RANDOM_POINT = 0xFFFFFE,
- SMART_ESCORT_TARGETS = 0xFFFFFF
-};
-
enum SmartScriptType
{
SMART_SCRIPT_TYPE_CREATURE = 0, //done
diff --git a/src/server/game/Achievements/AchievementMgr.cpp b/src/server/game/Achievements/AchievementMgr.cpp
index 90e61826e35..ac8e0298a44 100644
--- a/src/server/game/Achievements/AchievementMgr.cpp
+++ b/src/server/game/Achievements/AchievementMgr.cpp
@@ -156,7 +156,7 @@ bool AchievementCriteriaData::IsValid(AchievementCriteriaEntry const* criteria)
return true;
}
case ACHIEVEMENT_CRITERIA_DATA_TYPE_S_AREA:
- if (!GetAreaEntryByAreaID(area.id))
+ if (!sAreaTableStore.LookupEntry(area.id))
{
TC_LOG_ERROR("sql.sql", "Table `achievement_criteria_data` (Entry: %u Type: %u) for data type ACHIEVEMENT_CRITERIA_DATA_TYPE_S_AREA (%u) has wrong area id in value1 (%u), ignored.",
criteria->ID, criteria->requiredType, dataType, area.id);
@@ -1905,17 +1905,15 @@ bool AchievementMgr::RequirementsSatisfied(AchievementCriteriaEntry const* achie
bool matchFound = false;
for (int j = 0; j < MAX_WORLD_MAP_OVERLAY_AREA_IDX; ++j)
{
- uint32 area_id = worldOverlayEntry->areatableID[j];
- if (!area_id) // array have 0 only in empty tail
+ AreaTableEntry const* area = sAreaTableStore.LookupEntry(worldOverlayEntry->areatableID[j]);
+ if (!area)
break;
- int32 exploreFlag = GetAreaFlagByAreaID(area_id);
- if (exploreFlag < 0)
+ uint32 playerIndexOffset = uint32(area->exploreFlag) / 32;
+ if (playerIndexOffset >= PLAYER_EXPLORED_ZONES_SIZE)
continue;
- uint32 playerIndexOffset = uint32(exploreFlag) / 32;
- uint32 mask = 1 << (uint32(exploreFlag) % 32);
-
+ uint32 mask = 1 << (uint32(area->exploreFlag) % 32);
if (GetPlayer()->GetUInt32Value(PLAYER_EXPLORED_ZONES_1 + playerIndexOffset) & mask)
{
matchFound = true;
diff --git a/src/server/game/AuctionHouse/AuctionHouseMgr.cpp b/src/server/game/AuctionHouse/AuctionHouseMgr.cpp
index f3c20750069..035d9af4369 100644
--- a/src/server/game/AuctionHouse/AuctionHouseMgr.cpp
+++ b/src/server/game/AuctionHouse/AuctionHouseMgr.cpp
@@ -573,6 +573,14 @@ void AuctionHouseObject::Update()
if (AuctionsMap.empty())
return;
+ // Clear expired throttled players
+ for (PlayerGetAllThrottleMap::const_iterator itr = GetAllThrottleMap.begin(); itr != GetAllThrottleMap.end();)
+ {
+ if (itr->second <= curTime)
+ itr = GetAllThrottleMap.erase(itr);
+ else
+ ++itr;
+ }
SQLTransaction trans = CharacterDatabase.BeginTransaction();
@@ -648,13 +656,40 @@ void AuctionHouseObject::BuildListOwnerItems(WorldPacket& data, Player* player,
void AuctionHouseObject::BuildListAuctionItems(WorldPacket& data, Player* player,
std::wstring const& wsearchedname, uint32 listfrom, uint8 levelmin, uint8 levelmax, uint8 usable,
uint32 inventoryType, uint32 itemClass, uint32 itemSubClass, uint32 quality,
- uint32& count, uint32& totalcount)
+ uint32& count, uint32& totalcount, bool getall)
{
int loc_idx = player->GetSession()->GetSessionDbLocaleIndex();
int locdbc_idx = player->GetSession()->GetSessionDbcLocale();
time_t curTime = sWorld->GetGameTime();
+ PlayerGetAllThrottleMap::const_iterator itr = GetAllThrottleMap.find(player->GetGUID());
+ time_t throttleTime = itr != GetAllThrottleMap.end() ? itr->second : curTime;
+
+ if (getall && throttleTime <= curTime)
+ {
+ for (AuctionEntryMap::const_iterator itr = AuctionsMap.begin(); itr != AuctionsMap.end(); ++itr)
+ {
+ AuctionEntry* Aentry = itr->second;
+ // Skip expired auctions
+ if (Aentry->expire_time < curTime)
+ continue;
+
+ Item* item = sAuctionMgr->GetAItem(Aentry->itemGUIDLow);
+ if (!item)
+ continue;
+
+ ++count;
+ ++totalcount;
+ Aentry->BuildAuctionInfo(data, item);
+
+ if (count >= MAX_GETALL_RETURN)
+ break;
+ }
+ GetAllThrottleMap[player->GetGUID()] = curTime + sWorld->getIntConfig(CONFIG_AUCTION_GETALL_DELAY);
+ return;
+ }
+
for (AuctionEntryMap::const_iterator itr = AuctionsMap.begin(); itr != AuctionsMap.end(); ++itr)
{
AuctionEntry* Aentry = itr->second;
@@ -744,16 +779,16 @@ void AuctionHouseObject::BuildListAuctionItems(WorldPacket& data, Player* player
if (count < 50 && totalcount >= listfrom)
{
++count;
- Aentry->BuildAuctionInfo(data);
+ Aentry->BuildAuctionInfo(data, item);
}
++totalcount;
}
}
//this function inserts to WorldPacket auction's data
-bool AuctionEntry::BuildAuctionInfo(WorldPacket& data) const
+bool AuctionEntry::BuildAuctionInfo(WorldPacket& data, Item* sourceItem) const
{
- Item* item = sAuctionMgr->GetAItem(itemGUIDLow);
+ Item* item = (sourceItem) ? sourceItem : sAuctionMgr->GetAItem(itemGUIDLow);
if (!item)
{
TC_LOG_ERROR("misc", "AuctionEntry::BuildAuctionInfo: Auction %u has a non-existent item: %u", Id, itemGUIDLow);
diff --git a/src/server/game/AuctionHouse/AuctionHouseMgr.h b/src/server/game/AuctionHouse/AuctionHouseMgr.h
index 4a2b79ab170..fe4b9ed07de 100644
--- a/src/server/game/AuctionHouse/AuctionHouseMgr.h
+++ b/src/server/game/AuctionHouse/AuctionHouseMgr.h
@@ -30,6 +30,7 @@ class WorldPacket;
#define MIN_AUCTION_TIME (12*HOUR)
#define MAX_AUCTION_ITEMS 160
+#define MAX_GETALL_RETURN 55000
enum AuctionError
{
@@ -90,7 +91,7 @@ struct AuctionEntry
uint8 GetHouseId() const { return houseId; }
uint32 GetAuctionCut() const;
uint32 GetAuctionOutBid() const;
- bool BuildAuctionInfo(WorldPacket & data) const;
+ bool BuildAuctionInfo(WorldPacket & data, Item* sourceItem = nullptr) const;
void DeleteFromDB(SQLTransaction& trans) const;
void SaveToDB(SQLTransaction& trans) const;
bool LoadFromDB(Field* fields);
@@ -110,6 +111,7 @@ class AuctionHouseObject
}
typedef std::map<uint32, AuctionEntry*> AuctionEntryMap;
+ typedef std::unordered_map<ObjectGuid, time_t> PlayerGetAllThrottleMap;
uint32 Getcount() const { return AuctionsMap.size(); }
@@ -133,10 +135,15 @@ class AuctionHouseObject
void BuildListAuctionItems(WorldPacket& data, Player* player,
std::wstring const& searchedname, uint32 listfrom, uint8 levelmin, uint8 levelmax, uint8 usable,
uint32 inventoryType, uint32 itemClass, uint32 itemSubClass, uint32 quality,
- uint32& count, uint32& totalcount);
+ uint32& count, uint32& totalcount, bool getall = false);
private:
AuctionEntryMap AuctionsMap;
+
+ // Map of throttled players for GetAll, and throttle expiry time
+ // Stored here, rather than player object to maintain persistence after logout
+ PlayerGetAllThrottleMap GetAllThrottleMap;
+
};
class AuctionHouseMgr
diff --git a/src/server/game/Battlegrounds/Zones/BattlegroundIC.cpp b/src/server/game/Battlegrounds/Zones/BattlegroundIC.cpp
index cff02234ab6..52e0deaf86f 100644
--- a/src/server/game/Battlegrounds/Zones/BattlegroundIC.cpp
+++ b/src/server/game/Battlegrounds/Zones/BattlegroundIC.cpp
@@ -365,7 +365,7 @@ bool BattlegroundIC::SetupBattleground()
// setting correct factions for Keep Cannons
for (uint8 i = BG_IC_NPC_KEEP_CANNON_1; i <= BG_IC_NPC_KEEP_CANNON_12; ++i)
GetBGCreature(i)->setFaction(BG_IC_Factions[0]);
- for (uint8 i = BG_IC_NPC_KEEP_CANNON_13; i <= BG_IC_NPC_KEEP_CANNON_25; ++i)
+ for (uint8 i = BG_IC_NPC_KEEP_CANNON_13; i <= BG_IC_NPC_KEEP_CANNON_24; ++i)
GetBGCreature(i)->setFaction(BG_IC_Factions[1]);
// correcting spawn time for keeps bombs
diff --git a/src/server/game/Battlegrounds/Zones/BattlegroundIC.h b/src/server/game/Battlegrounds/Zones/BattlegroundIC.h
index 72e9990b7c1..44666819feb 100644
--- a/src/server/game/Battlegrounds/Zones/BattlegroundIC.h
+++ b/src/server/game/Battlegrounds/Zones/BattlegroundIC.h
@@ -390,7 +390,6 @@ enum BG_IC_NPCs
BG_IC_NPC_KEEP_CANNON_22,
BG_IC_NPC_KEEP_CANNON_23,
BG_IC_NPC_KEEP_CANNON_24,
- BG_IC_NPC_KEEP_CANNON_25,
BG_IC_NPC_SIEGE_ENGINE_A,
BG_IC_NPC_SIEGE_ENGINE_H,
@@ -444,7 +443,7 @@ enum BannersTypes
enum BG_IC_MaxSpawns
{
MAX_NORMAL_GAMEOBJECTS_SPAWNS = BG_IC_GO_DOODAD_ND_WINTERORC_WALL_GATEFX_DOOR03+1,
- MAX_NORMAL_NPCS_SPAWNS = BG_IC_NPC_KEEP_CANNON_25+1,
+ MAX_NORMAL_NPCS_SPAWNS = BG_IC_NPC_KEEP_CANNON_24+1,
MAX_WORKSHOP_SPAWNS = 10,
MAX_DOCKS_SPAWNS = 12,
MAX_SPIRIT_GUIDES_SPAWNS = 7,
@@ -506,9 +505,8 @@ const ICNpc BG_IC_NpcSpawnlocs[MAX_NORMAL_NPCS_SPAWNS] =
{BG_IC_NPC_KEEP_CANNON_20, NPC_KEEP_CANNON, TEAM_HORDE, 1137.72f, -688.517f, 88.4023f, 3.9619f}, // 30
{BG_IC_NPC_KEEP_CANNON_21, NPC_KEEP_CANNON, TEAM_HORDE, 1135.29f, -840.878f, 88.0252f, 2.30383f}, // 31
{BG_IC_NPC_KEEP_CANNON_22, NPC_KEEP_CANNON, TEAM_HORDE, 1144.33f, -833.309f, 87.9268f, 2.14675f}, // 32
- {BG_IC_NPC_KEEP_CANNON_23, NPC_KEEP_CANNON, TEAM_HORDE, 1135.29f, -840.878f, 88.0252f, 2.30383f}, // 33
- {BG_IC_NPC_KEEP_CANNON_24, NPC_KEEP_CANNON, TEAM_HORDE, 1142.59f, -691.946f, 87.9756f, 3.9619f}, // 34
- {BG_IC_NPC_KEEP_CANNON_25, NPC_KEEP_CANNON, TEAM_HORDE, 1166.13f, -858.391f, 87.9653f, 5.63741f} // 35
+ {BG_IC_NPC_KEEP_CANNON_23, NPC_KEEP_CANNON, TEAM_HORDE, 1142.59f, -691.946f, 87.9756f, 3.9619f}, // 33
+ {BG_IC_NPC_KEEP_CANNON_24, NPC_KEEP_CANNON, TEAM_HORDE, 1166.13f, -858.391f, 87.9653f, 5.63741f} // 34
};
const Position BG_IC_WorkshopVehicles[5] =
diff --git a/src/server/game/Battlegrounds/Zones/BattlegroundSA.cpp b/src/server/game/Battlegrounds/Zones/BattlegroundSA.cpp
index 9ab96383ed1..1942ac9d648 100644
--- a/src/server/game/Battlegrounds/Zones/BattlegroundSA.cpp
+++ b/src/server/game/Battlegrounds/Zones/BattlegroundSA.cpp
@@ -389,6 +389,8 @@ void BattlegroundSA::PostUpdateImpl(uint32 diff)
{
if (TotalTime >= BG_SA_ROUNDLENGTH)
{
+ CastSpellOnTeam(SPELL_END_OF_ROUND, ALLIANCE);
+ CastSpellOnTeam(SPELL_END_OF_ROUND, HORDE);
RoundScores[0].winner = Attackers;
RoundScores[0].time = BG_SA_ROUNDLENGTH;
TotalTime = 0;
@@ -401,8 +403,6 @@ void BattlegroundSA::PostUpdateImpl(uint32 diff)
ToggleTimer();
ResetObjs();
GetBgMap()->UpdateAreaDependentAuras();
- CastSpellOnTeam(SPELL_END_OF_ROUND, ALLIANCE);
- CastSpellOnTeam(SPELL_END_OF_ROUND, HORDE);
return;
}
}
@@ -410,6 +410,8 @@ void BattlegroundSA::PostUpdateImpl(uint32 diff)
{
if (TotalTime >= EndRoundTimer)
{
+ CastSpellOnTeam(SPELL_END_OF_ROUND, ALLIANCE);
+ CastSpellOnTeam(SPELL_END_OF_ROUND, HORDE);
RoundScores[1].time = BG_SA_ROUNDLENGTH;
RoundScores[1].winner = (Attackers == TEAM_ALLIANCE) ? TEAM_HORDE : TEAM_ALLIANCE;
if (RoundScores[0].time == RoundScores[1].time)
diff --git a/src/server/game/Chat/Chat.cpp b/src/server/game/Chat/Chat.cpp
index 011b68a2bb3..8fe0810f3b9 100644
--- a/src/server/game/Chat/Chat.cpp
+++ b/src/server/game/Chat/Chat.cpp
@@ -296,11 +296,11 @@ bool ChatHandler::ExecuteCommandInTable(std::vector<ChatCommand> const& table, c
uint32 areaId = player->GetAreaId();
std::string areaName = "Unknown";
std::string zoneName = "Unknown";
- if (AreaTableEntry const* area = GetAreaEntryByAreaID(areaId))
+ if (AreaTableEntry const* area = sAreaTableStore.LookupEntry(areaId))
{
int locale = GetSessionDbcLocale();
areaName = area->area_name[locale];
- if (AreaTableEntry const* zone = GetAreaEntryByAreaID(area->zone))
+ if (AreaTableEntry const* zone = sAreaTableStore.LookupEntry(area->zone))
zoneName = zone->area_name[locale];
}
diff --git a/src/server/game/Conditions/ConditionMgr.cpp b/src/server/game/Conditions/ConditionMgr.cpp
index d5367e919a3..4215a3a5d02 100644
--- a/src/server/game/Conditions/ConditionMgr.cpp
+++ b/src/server/game/Conditions/ConditionMgr.cpp
@@ -1708,7 +1708,7 @@ bool ConditionMgr::isConditionTypeValid(Condition* cond) const
}
case CONDITION_ZONEID:
{
- AreaTableEntry const* areaEntry = GetAreaEntryByAreaID(cond->ConditionValue1);
+ AreaTableEntry const* areaEntry = sAreaTableStore.LookupEntry(cond->ConditionValue1);
if (!areaEntry)
{
TC_LOG_ERROR("sql.sql", "%s Area (%u) does not exist, skipped.", cond->ToString(true).c_str(), cond->ConditionValue1);
diff --git a/src/server/game/DataStores/DBCStores.cpp b/src/server/game/DataStores/DBCStores.cpp
index 5f1de673294..6c51c71fe7b 100644
--- a/src/server/game/DataStores/DBCStores.cpp
+++ b/src/server/game/DataStores/DBCStores.cpp
@@ -51,11 +51,9 @@ struct WMOAreaTableTripple
typedef std::map<WMOAreaTableTripple, WMOAreaTableEntry const*> WMOAreaInfoByTripple;
typedef std::multimap<uint32, CharSectionsEntry const*> CharSectionsMap;
-DBCStorage <AreaTableEntry> sAreaStore(AreaTableEntryfmt);
+DBCStorage <AreaTableEntry> sAreaTableStore(AreaTableEntryfmt);
DBCStorage <AreaGroupEntry> sAreaGroupStore(AreaGroupEntryfmt);
DBCStorage <AreaPOIEntry> sAreaPOIStore(AreaPOIEntryfmt);
-static AreaFlagByAreaID sAreaFlagByAreaID;
-static AreaFlagByMapID sAreaFlagByMapID; // for instances without generated *.map files
static WMOAreaInfoByTripple sWMOAreaInfoByTripple;
@@ -91,6 +89,9 @@ DBCStorage <DurabilityCostsEntry> sDurabilityCostsStore(DurabilityCostsfmt);
DBCStorage <EmotesEntry> sEmotesStore(EmotesEntryfmt);
DBCStorage <EmotesTextEntry> sEmotesTextStore(EmotesTextEntryfmt);
+typedef std::tuple<uint32, uint32, uint32> EmotesTextSoundKey;
+static std::map<EmotesTextSoundKey, EmotesTextSoundEntry const*> sEmotesTextSoundMap;
+DBCStorage <EmotesTextSoundEntry> sEmotesTextSoundStore(EmotesTextSoundEntryfmt);
typedef std::map<uint32, SimpleFactionsList> FactionTeamMap;
static FactionTeamMap sFactionTeamMap;
@@ -283,21 +284,7 @@ void LoadDBCStores(const std::string& dataPath)
StoreProblemList bad_dbc_files;
uint32 availableDbcLocales = 0xFFFFFFFF;
- LoadDBC(availableDbcLocales, bad_dbc_files, sAreaStore, dbcPath, "AreaTable.dbc");
-
- // must be after sAreaStore loading
- for (uint32 i = 0; i < sAreaStore.GetNumRows(); ++i) // areaflag numbered from 0
- {
- if (AreaTableEntry const* area = sAreaStore.LookupEntry(i))
- {
- // fill AreaId->DBC records
- sAreaFlagByAreaID.insert(AreaFlagByAreaID::value_type(uint16(area->ID), area->exploreFlag));
-
- // fill MapId->DBC records (skip sub zones and continents)
- if (area->zone == 0 && area->mapid != 0 && area->mapid != 1 && area->mapid != 530 && area->mapid != 571)
- sAreaFlagByMapID.insert(AreaFlagByMapID::value_type(area->mapid, area->exploreFlag));
- }
- }
+ LoadDBC(availableDbcLocales, bad_dbc_files, sAreaTableStore, dbcPath, "AreaTable.dbc");
LoadDBC(availableDbcLocales, bad_dbc_files, sAchievementStore, dbcPath, "Achievement.dbc", &CustomAchievementfmt, &CustomAchievementIndex);
LoadDBC(availableDbcLocales, bad_dbc_files, sAchievementCriteriaStore, dbcPath, "Achievement_Criteria.dbc");
@@ -338,6 +325,10 @@ void LoadDBCStores(const std::string& dataPath)
LoadDBC(availableDbcLocales, bad_dbc_files, sDurabilityQualityStore, dbcPath, "DurabilityQuality.dbc");
LoadDBC(availableDbcLocales, bad_dbc_files, sEmotesStore, dbcPath, "Emotes.dbc");
LoadDBC(availableDbcLocales, bad_dbc_files, sEmotesTextStore, dbcPath, "EmotesText.dbc");
+ LoadDBC(availableDbcLocales, bad_dbc_files, sEmotesTextSoundStore, dbcPath, "EmotesTextSound.dbc");
+ for (uint32 i = 0; i < sEmotesTextSoundStore.GetNumRows(); ++i)
+ if (EmotesTextSoundEntry const* entry = sEmotesTextSoundStore.LookupEntry(i))
+ sEmotesTextSoundMap[EmotesTextSoundKey(entry->EmotesTextId, entry->RaceId, entry->SexId)] = entry;
LoadDBC(availableDbcLocales, bad_dbc_files, sFactionStore, dbcPath, "Faction.dbc");
for (uint32 i=0; i<sFactionStore.GetNumRows(); ++i)
{
@@ -714,7 +705,7 @@ void LoadDBCStores(const std::string& dataPath)
}
// Check loaded DBC files proper version
- if (!sAreaStore.LookupEntry(3617) || // last area (areaflag) added in 3.3.5a
+ if (!sAreaTableStore.LookupEntry(4987) || // last area added in 3.3.5a
!sCharTitlesStore.LookupEntry(177) || // last char title added in 3.3.5a
!sGemPropertiesStore.LookupEntry(1629) || // last added spell in 3.3.5a
!sItemStore.LookupEntry(56806) || // last gem property added in 3.3.5a
@@ -766,41 +757,13 @@ uint32 GetTalentSpellCost(uint32 spellId)
return 0;
}
-int32 GetAreaFlagByAreaID(uint32 area_id)
-{
- AreaFlagByAreaID::iterator i = sAreaFlagByAreaID.find(area_id);
- if (i == sAreaFlagByAreaID.end())
- return -1;
-
- return i->second;
-}
WMOAreaTableEntry const* GetWMOAreaTableEntryByTripple(int32 rootid, int32 adtid, int32 groupid)
{
WMOAreaInfoByTripple::iterator i = sWMOAreaInfoByTripple.find(WMOAreaTableTripple(rootid, adtid, groupid));
- if (i == sWMOAreaInfoByTripple.end())
- return NULL;
- return i->second;
-}
-
-AreaTableEntry const* GetAreaEntryByAreaID(uint32 area_id)
-{
- int32 areaflag = GetAreaFlagByAreaID(area_id);
- if (areaflag < 0)
+ if (i == sWMOAreaInfoByTripple.end())
return NULL;
-
- return sAreaStore.LookupEntry(areaflag);
-}
-
-AreaTableEntry const* GetAreaEntryByAreaFlagAndMap(uint32 area_flag, uint32 map_id)
-{
- if (area_flag)
- return sAreaStore.LookupEntry(area_flag);
-
- if (MapEntry const* mapEntry = sMapStore.LookupEntry(map_id))
- return GetAreaEntryByAreaID(mapEntry->linked_zone);
-
- return NULL;
+ return i->second;
}
char const* GetRaceName(uint8 race, uint8 locale)
@@ -815,15 +778,6 @@ char const* GetClassName(uint8 class_, uint8 locale)
return classEntry ? classEntry->name[locale] : NULL;
}
-uint32 GetAreaFlagByMapId(uint32 mapid)
-{
- AreaFlagByMapID::iterator i = sAreaFlagByMapID.find(mapid);
- if (i == sAreaFlagByMapID.end())
- return 0;
- else
- return i->second;
-}
-
uint32 GetVirtualMapForMapAndZone(uint32 mapid, uint32 zoneId)
{
if (mapid != 530 && mapid != 571) // speed for most cases
@@ -1060,3 +1014,8 @@ ResponseCodes ValidateName(std::string const& name, LocaleConstant locale)
return CHAR_NAME_SUCCESS;
}
+
+EmotesTextSoundEntry const* FindTextSoundEmoteFor(uint32 emote, uint32 race, uint32 gender)
+{
+ return sEmotesTextSoundMap[EmotesTextSoundKey(emote, race, gender)];
+}
diff --git a/src/server/game/DataStores/DBCStores.h b/src/server/game/DataStores/DBCStores.h
index 41a97382b0b..56ee1f618dd 100644
--- a/src/server/game/DataStores/DBCStores.h
+++ b/src/server/game/DataStores/DBCStores.h
@@ -33,11 +33,6 @@ char* GetPetName(uint32 petfamily, uint32 dbclang);
uint32 GetTalentSpellCost(uint32 spellId);
TalentSpellPos const* GetTalentSpellPos(uint32 spellId);
-int32 GetAreaFlagByAreaID(uint32 area_id); // -1 if not found
-AreaTableEntry const* GetAreaEntryByAreaID(uint32 area_id);
-AreaTableEntry const* GetAreaEntryByAreaFlagAndMap(uint32 area_flag, uint32 map_id);
-uint32 GetAreaFlagByMapId(uint32 mapid);
-
char const* GetRaceName(uint8 race, uint8 locale);
char const* GetClassName(uint8 class_, uint8 locale);
@@ -82,9 +77,11 @@ SkillRaceClassInfoEntry const* GetSkillRaceClassInfo(uint32 skill, uint8 race, u
ResponseCodes ValidateName(std::string const& name, LocaleConstant locale);
+EmotesTextSoundEntry const* FindTextSoundEmoteFor(uint32 emote, uint32 race, uint32 gender);
+
extern DBCStorage <AchievementEntry> sAchievementStore;
extern DBCStorage <AchievementCriteriaEntry> sAchievementCriteriaStore;
-extern DBCStorage <AreaTableEntry> sAreaStore;// recommend access using functions
+extern DBCStorage <AreaTableEntry> sAreaTableStore;
extern DBCStorage <AreaGroupEntry> sAreaGroupStore;
extern DBCStorage <AreaPOIEntry> sAreaPOIStore;
extern DBCStorage <AreaTriggerEntry> sAreaTriggerStore;
@@ -113,6 +110,7 @@ extern DBCStorage <DurabilityCostsEntry> sDurabilityCostsStore;
extern DBCStorage <DurabilityQualityEntry> sDurabilityQualityStore;
extern DBCStorage <EmotesEntry> sEmotesStore;
extern DBCStorage <EmotesTextEntry> sEmotesTextStore;
+extern DBCStorage <EmotesTextSoundEntry> sEmotesTextSoundStore;
extern DBCStorage <FactionEntry> sFactionStore;
extern DBCStorage <FactionTemplateEntry> sFactionTemplateStore;
extern DBCStorage <GameObjectDisplayInfoEntry> sGameObjectDisplayInfoStore;
diff --git a/src/server/game/DataStores/DBCStructure.h b/src/server/game/DataStores/DBCStructure.h
index d1794a0ea90..b5dc4489148 100644
--- a/src/server/game/DataStores/DBCStructure.h
+++ b/src/server/game/DataStores/DBCStructure.h
@@ -509,7 +509,7 @@ struct AreaTableEntry
uint32 ID; // 0
uint32 mapid; // 1
uint32 zone; // 2 if 0 then it's zone, else it's zone id of this area
- uint32 exploreFlag; // 3, main index
+ uint32 exploreFlag; // 3
uint32 flags; // 4, unknown value but 312 for all cities
// 5-9 unused
int32 area_level; // 10
@@ -926,6 +926,15 @@ struct EmotesTextEntry
uint32 textid;
};
+struct EmotesTextSoundEntry
+{
+ uint32 Id; // 0
+ uint32 EmotesTextId; // 1
+ uint32 RaceId; // 2
+ uint32 SexId; // 3, 0 male / 1 female
+ uint32 SoundId; // 4
+};
+
struct FactionEntry
{
uint32 ID; // 0 m_ID
diff --git a/src/server/game/DataStores/DBCfmt.h b/src/server/game/DataStores/DBCfmt.h
index aade6d91d61..c61ec997bc2 100644
--- a/src/server/game/DataStores/DBCfmt.h
+++ b/src/server/game/DataStores/DBCfmt.h
@@ -23,7 +23,7 @@ char const Achievementfmt[] = "niixssssssssssssssssxxxxxxxxxxxxxxxxxxiixixxxxxxx
const std::string CustomAchievementfmt = "pppaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaapapaaaaaaaaaaaaaaaaaapp";
const std::string CustomAchievementIndex = "ID";
char const AchievementCriteriafmt[] = "niiiiiiiixxxxxxxxxxxxxxxxxiiiix";
-char const AreaTableEntryfmt[] = "iiinixxxxxissssssssssssssssxiiiiixxx";
+char const AreaTableEntryfmt[] = "niiiixxxxxissssssssssssssssxiiiiixxx";
char const AreaGroupEntryfmt[] = "niiiiiii";
char const AreaPOIEntryfmt[] = "niiiiiiiiiiifffixixxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxix";
char const AreaTriggerEntryfmt[] = "niffffffff";
@@ -52,6 +52,7 @@ char const DurabilityCostsfmt[] = "niiiiiiiiiiiiiiiiiiiiiiiiiiiii";
char const DurabilityQualityfmt[] = "nf";
char const EmotesEntryfmt[] = "nxxiiix";
char const EmotesTextEntryfmt[] = "nxixxxxxxxxxxxxxxxx";
+char const EmotesTextSoundEntryfmt[] = "niiii";
char const FactionEntryfmt[] = "niiiiiiiiiiiiiiiiiiffixssssssssssssssssxxxxxxxxxxxxxxxxxx";
char const FactionTemplateEntryfmt[] = "niiiiiiiiiiiii";
char const GameObjectDisplayInfofmt[] = "nsxxxxxxxxxxffffffx";
diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp
index 3e67f709816..5d62b740947 100644
--- a/src/server/game/Entities/Creature/Creature.cpp
+++ b/src/server/game/Entities/Creature/Creature.cpp
@@ -369,13 +369,13 @@ bool Creature::InitEntry(uint32 entry, CreatureData const* data /*= nullptr*/)
SetByteValue(UNIT_FIELD_BYTES_0, 2, minfo->gender);
// Load creature equipment
- if (!data || data->equipmentId == 0)
- LoadEquipment(); // use default equipment (if available)
- else if (data && data->equipmentId != 0) // override, 0 means no equipment
+ if (data && data->equipmentId != 0)
{
m_originalEquipmentId = data->equipmentId;
LoadEquipment(data->equipmentId);
}
+ else
+ LoadEquipment(0, true);
SetName(normalInfo->Name); // at normal entry always
@@ -905,6 +905,12 @@ bool Creature::Create(ObjectGuid::LowType guidlow, Map* map, uint32 phaseMask, u
if (GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_IGNORE_PATHFINDING)
AddUnitState(UNIT_STATE_IGNORE_PATHFINDING);
+ if (GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_IMMUNITY_KNOCKBACK)
+ {
+ ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_KNOCK_BACK, true);
+ ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_KNOCK_BACK_DEST, true);
+ }
+
return true;
}
@@ -1292,8 +1298,38 @@ bool Creature::CreateFromProto(ObjectGuid::LowType guidlow, uint32 entry, Creatu
return true;
}
-bool Creature::LoadCreatureFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap)
+bool Creature::LoadCreatureFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap, bool allowDuplicate)
{
+ if (!allowDuplicate)
+ {
+ // If an alive instance of this spawnId is already found, skip creation
+ // If only dead instance(s) exist, despawn them and spawn a new (maybe also dead) version
+ const auto creatureBounds = map->GetCreatureBySpawnIdStore().equal_range(spawnId);
+ std::vector <Creature*> despawnList;
+
+ if (creatureBounds.first != creatureBounds.second)
+ {
+ for (auto itr = creatureBounds.first; itr != creatureBounds.second; ++itr)
+ {
+ if (itr->second->IsAlive())
+ {
+ TC_LOG_DEBUG("maps", "Would have spawned %u but %s already exists", spawnId, creatureBounds.first->second->GetGUID().ToString().c_str());
+ return false;
+ }
+ else
+ {
+ despawnList.push_back(itr->second);
+ TC_LOG_DEBUG("maps", "Despawned dead instance of spawn %u (%s)", spawnId, itr->second->GetGUID().ToString().c_str());
+ }
+ }
+
+ for (Creature* despawnCreature : despawnList)
+ {
+ despawnCreature->AddObjectToRemoveList();
+ }
+ }
+ }
+
CreatureData const* data = sObjectMgr->GetCreatureData(spawnId);
if (!data)
@@ -1373,6 +1409,7 @@ void Creature::LoadEquipment(int8 id, bool force /*= true*/)
SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + i, 0);
m_equipmentId = 0;
}
+
return;
}
@@ -1381,7 +1418,7 @@ void Creature::LoadEquipment(int8 id, bool force /*= true*/)
return;
m_equipmentId = id;
- for (uint8 i = 0; i < 3; ++i)
+ for (uint8 i = 0; i < MAX_EQUIPMENT_ITEMS; ++i)
SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + i, einfo->ItemEntry[i]);
}
@@ -1604,13 +1641,29 @@ void Creature::setDeathState(DeathState s)
UpdateMovementFlags();
- CreatureTemplate const* cinfo = GetCreatureTemplate();
- SetUInt32Value(UNIT_NPC_FLAGS, cinfo->npcflag);
ClearUnitState(uint32(UNIT_STATE_ALL_STATE & ~UNIT_STATE_IGNORE_PATHFINDING));
- SetMeleeDamageSchool(SpellSchools(cinfo->dmgschool));
+
+ if (!IsPet())
+ {
+ CreatureData const* creatureData = GetCreatureData();
+ CreatureTemplate const* cinfo = GetCreatureTemplate();
+
+ uint32 npcflag, unit_flags, dynamicflags;
+ ObjectMgr::ChooseCreatureFlags(cinfo, npcflag, unit_flags, dynamicflags, creatureData);
+
+ SetUInt32Value(UNIT_NPC_FLAGS, npcflag);
+ SetUInt32Value(UNIT_FIELD_FLAGS, unit_flags);
+ SetUInt32Value(UNIT_DYNAMIC_FLAGS, dynamicflags);
+
+ RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IN_COMBAT);
+
+ SetMeleeDamageSchool(SpellSchools(cinfo->dmgschool));
+
+ if (creatureData && GetPhaseMask() != creatureData->phaseMask)
+ SetPhaseMask(creatureData->phaseMask, false);
+ }
+
Motion_Initialize();
- if (GetCreatureData() && GetPhaseMask() != GetCreatureData()->phaseMask)
- SetPhaseMask(GetCreatureData()->phaseMask, false);
Unit::setDeathState(ALIVE);
LoadCreaturesAddon();
}
@@ -2096,6 +2149,11 @@ bool Creature::CanCreatureAttack(Unit const* victim, bool /*force*/) const
if (GetMap()->IsDungeon())
return true;
+ // if the mob is actively being damaged, do not reset due to distance unless it's a world boss
+ if (!isWorldBoss())
+ if (time(NULL) - GetLastDamagedTime() <= MAX_AGGRO_RESET_TIME)
+ return true;
+
//Use AttackDistance in distance check if threat radius is lower. This prevents creature bounce in and out of combat every update tick.
float dist = std::max(GetAttackDistance(victim), sWorld->getFloatConfig(CONFIG_THREAT_RADIUS)) + m_CombatDistance;
@@ -2739,7 +2797,7 @@ bool Creature::FocusTarget(Spell const* focusSpell, WorldObject const* target)
}
}
if (shouldDelay)
- shouldDelay = (!focusSpell->IsTriggered() && !focusSpell->GetCastTime());
+ shouldDelay = !(focusSpell->IsTriggered() || focusSpell->GetCastTime() || focusSpell->GetSpellInfo()->IsChanneled());
}
}
diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h
index b0318ed33e2..49bd854ef2f 100644
--- a/src/server/game/Entities/Creature/Creature.h
+++ b/src/server/game/Entities/Creature/Creature.h
@@ -39,23 +39,25 @@ class WorldSession;
enum CreatureFlagsExtra
{
- CREATURE_FLAG_EXTRA_INSTANCE_BIND = 0x00000001, // creature kill bind instance with killer and killer's group
- CREATURE_FLAG_EXTRA_CIVILIAN = 0x00000002, // not aggro (ignore faction/reputation hostility)
- CREATURE_FLAG_EXTRA_NO_PARRY = 0x00000004, // creature can't parry
- CREATURE_FLAG_EXTRA_NO_PARRY_HASTEN = 0x00000008, // creature can't counter-attack at parry
- CREATURE_FLAG_EXTRA_NO_BLOCK = 0x00000010, // creature can't block
- CREATURE_FLAG_EXTRA_NO_CRUSH = 0x00000020, // creature can't do crush attacks
- CREATURE_FLAG_EXTRA_NO_XP_AT_KILL = 0x00000040, // creature kill not provide XP
- CREATURE_FLAG_EXTRA_TRIGGER = 0x00000080, // trigger creature
- CREATURE_FLAG_EXTRA_NO_TAUNT = 0x00000100, // creature is immune to taunt auras and effect attack me
- CREATURE_FLAG_EXTRA_WORLDEVENT = 0x00004000, // custom flag for world event creatures (left room for merging)
- CREATURE_FLAG_EXTRA_GUARD = 0x00008000, // Creature is guard
- CREATURE_FLAG_EXTRA_NO_CRIT = 0x00020000, // creature can't do critical strikes
- CREATURE_FLAG_EXTRA_NO_SKILLGAIN = 0x00040000, // creature won't increase weapon skills
- CREATURE_FLAG_EXTRA_TAUNT_DIMINISH = 0x00080000, // Taunt is a subject to diminishing returns on this creautre
- CREATURE_FLAG_EXTRA_ALL_DIMINISH = 0x00100000, // Creature is subject to all diminishing returns as player are
- CREATURE_FLAG_EXTRA_DUNGEON_BOSS = 0x10000000, // creature is a dungeon boss (SET DYNAMICALLY, DO NOT ADD IN DB)
- CREATURE_FLAG_EXTRA_IGNORE_PATHFINDING = 0x20000000 // creature ignore pathfinding
+ CREATURE_FLAG_EXTRA_INSTANCE_BIND = 0x00000001, // creature kill bind instance with killer and killer's group
+ CREATURE_FLAG_EXTRA_CIVILIAN = 0x00000002, // not aggro (ignore faction/reputation hostility)
+ CREATURE_FLAG_EXTRA_NO_PARRY = 0x00000004, // creature can't parry
+ CREATURE_FLAG_EXTRA_NO_PARRY_HASTEN = 0x00000008, // creature can't counter-attack at parry
+ CREATURE_FLAG_EXTRA_NO_BLOCK = 0x00000010, // creature can't block
+ CREATURE_FLAG_EXTRA_NO_CRUSH = 0x00000020, // creature can't do crush attacks
+ CREATURE_FLAG_EXTRA_NO_XP_AT_KILL = 0x00000040, // creature kill not provide XP
+ CREATURE_FLAG_EXTRA_TRIGGER = 0x00000080, // trigger creature
+ CREATURE_FLAG_EXTRA_NO_TAUNT = 0x00000100, // creature is immune to taunt auras and effect attack me
+ CREATURE_FLAG_EXTRA_WORLDEVENT = 0x00004000, // custom flag for world event creatures (left room for merging)
+ CREATURE_FLAG_EXTRA_GUARD = 0x00008000, // Creature is guard
+ CREATURE_FLAG_EXTRA_NO_CRIT = 0x00020000, // creature can't do critical strikes
+ CREATURE_FLAG_EXTRA_NO_SKILLGAIN = 0x00040000, // creature won't increase weapon skills
+ CREATURE_FLAG_EXTRA_TAUNT_DIMINISH = 0x00080000, // Taunt is a subject to diminishing returns on this creautre
+ CREATURE_FLAG_EXTRA_ALL_DIMINISH = 0x00100000, // creature is subject to all diminishing returns as player are
+ CREATURE_FLAG_EXTRA_NO_PLAYER_DAMAGE_REQ = 0x00200000, // creature does not need to take player damage for kill credit
+ CREATURE_FLAG_EXTRA_DUNGEON_BOSS = 0x10000000, // creature is a dungeon boss (SET DYNAMICALLY, DO NOT ADD IN DB)
+ CREATURE_FLAG_EXTRA_IGNORE_PATHFINDING = 0x20000000, // creature ignore pathfinding
+ CREATURE_FLAG_EXTRA_IMMUNITY_KNOCKBACK = 0x40000000 // creature is immune to knockback effects
};
#define CREATURE_FLAG_EXTRA_DB_ALLOWED (CREATURE_FLAG_EXTRA_INSTANCE_BIND | CREATURE_FLAG_EXTRA_CIVILIAN | \
@@ -63,7 +65,7 @@ enum CreatureFlagsExtra
CREATURE_FLAG_EXTRA_NO_CRUSH | CREATURE_FLAG_EXTRA_NO_XP_AT_KILL | CREATURE_FLAG_EXTRA_TRIGGER | \
CREATURE_FLAG_EXTRA_NO_TAUNT | CREATURE_FLAG_EXTRA_WORLDEVENT | CREATURE_FLAG_EXTRA_NO_CRIT | \
CREATURE_FLAG_EXTRA_NO_SKILLGAIN | CREATURE_FLAG_EXTRA_TAUNT_DIMINISH | CREATURE_FLAG_EXTRA_ALL_DIMINISH | \
- CREATURE_FLAG_EXTRA_GUARD | CREATURE_FLAG_EXTRA_IGNORE_PATHFINDING)
+ CREATURE_FLAG_EXTRA_GUARD | CREATURE_FLAG_EXTRA_IGNORE_PATHFINDING | CREATURE_FLAG_EXTRA_NO_PLAYER_DAMAGE_REQ | CREATURE_FLAG_EXTRA_IMMUNITY_KNOCKBACK)
#define CREATURE_REGEN_INTERVAL 2 * IN_MILLISECONDS
@@ -534,7 +536,7 @@ class Creature : public Unit, public GridObject<Creature>, public MapObject
void setDeathState(DeathState s) override; // override virtual Unit::setDeathState
bool LoadFromDB(ObjectGuid::LowType spawnId, Map* map) { return LoadCreatureFromDB(spawnId, map, false); }
- bool LoadCreatureFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap = true);
+ bool LoadCreatureFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap = true, bool allowDuplicate = false);
void SaveToDB();
// overriden in Pet
virtual void SaveToDB(uint32 mapid, uint8 spawnMask, uint32 phaseMask);
@@ -605,6 +607,7 @@ class Creature : public Unit, public GridObject<Creature>, public MapObject
float GetRespawnRadius() const { return m_respawnradius; }
void SetRespawnRadius(float dist) { m_respawnradius = dist; }
+ void DoImmediateBoundaryCheck() { m_boundaryCheckTime = 0; }
uint32 GetCombatPulseDelay() const { return m_combatPulseDelay; }
void SetCombatPulseDelay(uint32 delay) // (secs) interval at which the creature pulses the entire zone into combat (only works in dungeons)
{
@@ -655,7 +658,7 @@ class Creature : public Unit, public GridObject<Creature>, public MapObject
void SetDisableReputationGain(bool disable) { DisableReputationGain = disable; }
bool IsReputationGainDisabled() const { return DisableReputationGain; }
- bool IsDamageEnoughForLootingAndReward() const { return m_PlayerDamageReq == 0; }
+ bool IsDamageEnoughForLootingAndReward() const { return (m_creatureInfo->flags_extra & CREATURE_FLAG_EXTRA_NO_PLAYER_DAMAGE_REQ) || (m_PlayerDamageReq == 0); }
void LowerPlayerDamageReq(uint32 unDamage);
void ResetPlayerDamageReq() { m_PlayerDamageReq = GetHealth() / 2; }
uint32 m_PlayerDamageReq;
diff --git a/src/server/game/Entities/GameObject/GameObject.cpp b/src/server/game/Entities/GameObject/GameObject.cpp
index 6f0b9f89e44..1acfeacbf83 100644
--- a/src/server/game/Entities/GameObject/GameObject.cpp
+++ b/src/server/game/Entities/GameObject/GameObject.cpp
@@ -1353,7 +1353,15 @@ void GameObject::Use(Unit* user)
break;
}
- player->KillCreditGO(info->entry, GetGUID());
+ if (Group* group = player->GetGroup())
+ {
+ for (GroupReference const* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
+ if (Player* member = itr->GetSource())
+ if (member->IsAtGroupRewardDistance(this))
+ member->KillCreditGO(info->entry, GetGUID());
+ }
+ else
+ player->KillCreditGO(info->entry, GetGUID());
}
if (uint32 trapEntry = info->goober.linkedTrapId)
@@ -1391,7 +1399,10 @@ void GameObject::Use(Unit* user)
player->SendCinematicStart(info->camera.cinematicId);
if (info->camera.eventID)
+ {
GetMap()->ScriptsStart(sEventScripts, info->camera.eventID, player, this);
+ EventInform(info->camera.eventID, user);
+ }
return;
}
diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp
index 95e76a25763..464c3125ce4 100644
--- a/src/server/game/Entities/Player/Player.cpp
+++ b/src/server/game/Entities/Player/Player.cpp
@@ -2406,7 +2406,7 @@ bool Player::CanInteractWithQuestGiver(Object* questGiver)
return false;
}
-Creature* Player::GetNPCIfCanInteractWith(ObjectGuid guid, uint32 npcflagmask)
+Creature* Player::GetNPCIfCanInteractWith(ObjectGuid const& guid, uint32 npcflagmask)
{
// unit checks
if (!guid)
@@ -2450,7 +2450,21 @@ Creature* Player::GetNPCIfCanInteractWith(ObjectGuid guid, uint32 npcflagmask)
return creature;
}
-GameObject* Player::GetGameObjectIfCanInteractWith(ObjectGuid guid, GameobjectTypes type) const
+GameObject* Player::GetGameObjectIfCanInteractWith(ObjectGuid const& guid) const
+{
+ if (GameObject* go = GetMap()->GetGameObject(guid))
+ {
+ if (go->IsWithinDistInMap(this, go->GetInteractionDistance()))
+ return go;
+
+ TC_LOG_DEBUG("maps", "GetGameObjectIfCanInteractWith: GameObject '%s' [GUID: %u] is too far away from player %s [GUID: %u] to be used by him (distance=%f, maximal %f is allowed)", go->GetGOInfo()->name.c_str(),
+ go->GetGUID().GetCounter(), GetName().c_str(), GetGUID().GetCounter(), go->GetDistance(this), go->GetInteractionDistance());
+ }
+
+ return nullptr;
+}
+
+GameObject* Player::GetGameObjectIfCanInteractWith(ObjectGuid const& guid, GameobjectTypes type) const
{
if (GameObject* go = GetMap()->GetGameObject(guid))
{
@@ -2459,12 +2473,12 @@ GameObject* Player::GetGameObjectIfCanInteractWith(ObjectGuid guid, GameobjectTy
if (go->IsWithinDistInMap(this, go->GetInteractionDistance()))
return go;
- TC_LOG_DEBUG("maps", "GetGameObjectIfCanInteractWith: GameObject '%s' [GUID: %u] is too far away from player %s [GUID: %u] to be used by him (distance=%f, maximal 10 is allowed)", go->GetGOInfo()->name.c_str(),
- go->GetGUID().GetCounter(), GetName().c_str(), GetGUID().GetCounter(), go->GetDistance(this));
+ TC_LOG_DEBUG("maps", "GetGameObjectIfCanInteractWith: GameObject '%s' [GUID: %u] is too far away from player %s [GUID: %u] to be used by him (distance=%f, maximal %f is allowed)", go->GetGOInfo()->name.c_str(),
+ go->GetGUID().GetCounter(), GetName().c_str(), GetGUID().GetCounter(), go->GetDistance(this), go->GetInteractionDistance());
}
}
- return NULL;
+ return nullptr;
}
bool Player::IsUnderWater() const
@@ -4668,6 +4682,7 @@ void Player::ResurrectPlayer(float restore_percent, bool applySickness)
// remove death flag + set aura
SetByteValue(UNIT_FIELD_BYTES_1, 3, 0x00);
+ RemoveFlag(PLAYER_FLAGS, PLAYER_FLAGS_IS_OUT_OF_BOUNDS);
// This must be called always even on Players with race != RACE_NIGHTELF in case of faction change
RemoveAurasDueToSpell(20584); // RACE_NIGHTELF speed bonuses
@@ -5088,10 +5103,10 @@ void Player::RepopAtGraveyard()
// note: this can be called also when the player is alive
// for example from WorldSession::HandleMovementOpcodes
- AreaTableEntry const* zone = GetAreaEntryByAreaID(GetAreaId());
+ AreaTableEntry const* zone = sAreaTableStore.LookupEntry(GetAreaId());
// Such zones are considered unreachable as a ghost and the player must be automatically revived
- if ((!IsAlive() && zone && zone->flags & AREA_FLAG_NEED_FLY) || GetTransport() || GetPositionZ() < -500.0f)
+ if ((!IsAlive() && zone && zone->flags & AREA_FLAG_NEED_FLY) || GetTransport() || GetPositionZ() < GetMap()->GetMinHeight(GetPositionX(), GetPositionY()))
{
ResurrectPlayer(0.5f);
SpawnCorpseBones();
@@ -5128,8 +5143,10 @@ void Player::RepopAtGraveyard()
GetSession()->SendPacket(&data);
}
}
- else if (GetPositionZ() < -500.0f)
+ else if (GetPositionZ() < GetMap()->GetMinHeight(GetPositionX(), GetPositionY()))
TeleportTo(m_homebindMapId, m_homebindX, m_homebindY, m_homebindZ, GetOrientation());
+
+ RemoveFlag(PLAYER_FLAGS, PLAYER_FLAGS_IS_OUT_OF_BOUNDS);
}
bool Player::CanJoinConstantChannelInZone(ChatChannelsEntry const* channel, AreaTableEntry const* zone)
@@ -5174,7 +5191,7 @@ void Player::UpdateLocalChannels(uint32 newZone)
if (GetSession()->PlayerLoading() && !IsBeingTeleportedFar())
return; // The client handles it automatically after loading, but not after teleporting
- AreaTableEntry const* current_zone = GetAreaEntryByAreaID(newZone);
+ AreaTableEntry const* current_zone = sAreaTableStore.LookupEntry(newZone);
if (!current_zone)
return;
@@ -6415,22 +6432,32 @@ void Player::CheckAreaExploreAndOutdoor()
return;
bool isOutdoor;
- uint16 areaFlag = GetBaseMap()->GetAreaFlag(GetPositionX(), GetPositionY(), GetPositionZ(), &isOutdoor);
+ uint32 areaId = GetBaseMap()->GetAreaId(GetPositionX(), GetPositionY(), GetPositionZ(), &isOutdoor);
+ AreaTableEntry const* areaEntry = sAreaTableStore.LookupEntry(areaId);
if (sWorld->getBoolConfig(CONFIG_VMAP_INDOOR_CHECK) && !isOutdoor)
RemoveAurasWithAttribute(SPELL_ATTR0_OUTDOORS_ONLY);
- if (areaFlag == 0xffff)
+ if (!areaId)
+ return;
+
+ if (!areaEntry)
+ {
+ TC_LOG_ERROR("entities.player", "Player '%s' (%s) discovered unknown area (x: %f y: %f z: %f map: %u)",
+ GetName().c_str(), GetGUID().ToString().c_str(), GetPositionX(), GetPositionY(), GetPositionZ(), GetMapId());
return;
- int offset = areaFlag / 32;
+ }
+
+ uint32 offset = areaEntry->exploreFlag / 32;
if (offset >= PLAYER_EXPLORED_ZONES_SIZE)
{
- TC_LOG_ERROR("entities.player", "Wrong area flag %u in map data for (X: %f Y: %f) point to field PLAYER_EXPLORED_ZONES_1 + %u ( %u must be < %u ).", areaFlag, GetPositionX(), GetPositionY(), offset, offset, PLAYER_EXPLORED_ZONES_SIZE);
+ TC_LOG_ERROR("entities.player", "Player::CheckAreaExploreAndOutdoor: Wrong area flag %u in map data for (X: %f Y: %f) point to field PLAYER_EXPLORED_ZONES_1 + %u ( %u must be < %u ).",
+ areaEntry->exploreFlag, GetPositionX(), GetPositionY(), offset, offset, PLAYER_EXPLORED_ZONES_SIZE);
return;
}
- uint32 val = (uint32)(1 << (areaFlag % 32));
+ uint32 val = (uint32)(1 << (areaEntry->exploreFlag % 32));
uint32 currFields = GetUInt32Value(PLAYER_EXPLORED_ZONES_1 + offset);
if (!(currFields & val))
@@ -6439,19 +6466,11 @@ void Player::CheckAreaExploreAndOutdoor()
UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_EXPLORE_AREA);
- AreaTableEntry const* areaEntry = GetAreaEntryByAreaFlagAndMap(areaFlag, GetMapId());
- if (!areaEntry)
- {
- TC_LOG_ERROR("entities.player", "Player %u discovered unknown area (x: %f y: %f z: %f map: %u", GetGUID().GetCounter(), GetPositionX(), GetPositionY(), GetPositionZ(), GetMapId());
- return;
- }
-
if (areaEntry->area_level > 0)
{
- uint32 area = areaEntry->ID;
if (getLevel() >= sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL))
{
- SendExplorationExperience(area, 0);
+ SendExplorationExperience(areaId, 0);
}
else
{
@@ -6475,9 +6494,9 @@ void Player::CheckAreaExploreAndOutdoor()
}
GiveXP(XP, NULL);
- SendExplorationExperience(area, XP);
+ SendExplorationExperience(areaId, XP);
}
- TC_LOG_DEBUG("entities.player", "Player %u discovered a new area: %u", GetGUID().GetCounter(), area);
+ TC_LOG_DEBUG("entities.player", "Player '%s' (%s) discovered a new area: %u", GetName().c_str(),GetGUID().ToString().c_str(), areaId);
}
}
}
@@ -7047,7 +7066,7 @@ void Player::UpdateArea(uint32 newArea)
// so apply them accordingly
m_areaUpdateId = newArea;
- AreaTableEntry const* area = GetAreaEntryByAreaID(newArea);
+ AreaTableEntry const* area = sAreaTableStore.LookupEntry(newArea);
pvpInfo.IsInFFAPvPArea = area && (area->flags & AREA_FLAG_ARENA);
UpdatePvPState(true);
@@ -7095,7 +7114,7 @@ void Player::UpdateZone(uint32 newZone, uint32 newArea)
// zone changed, so area changed as well, update it
UpdateArea(newArea);
- AreaTableEntry const* zone = GetAreaEntryByAreaID(newZone);
+ AreaTableEntry const* zone = sAreaTableStore.LookupEntry(newZone);
if (!zone)
return;
@@ -15456,7 +15475,7 @@ bool Player::SatisfyQuestDay(Quest const* qInfo, bool msg)
if (qInfo->IsDFQuest())
{
- if (!m_DFQuests.empty())
+ if (m_DFQuests.find(qInfo->GetQuestId()) != m_DFQuests.end())
return false;
return true;
@@ -18356,8 +18375,9 @@ void Player::_LoadBoundInstances(PreparedQueryResult result)
uint32 mapId = fields[2].GetUInt16();
uint32 instanceId = fields[0].GetUInt32();
uint8 difficulty = fields[3].GetUInt8();
+ BindExtensionState extendState = BindExtensionState(fields[4].GetUInt8());
- time_t resetTime = time_t(fields[4].GetUInt32());
+ time_t resetTime = time_t(fields[5].GetUInt32());
// the resettime for normal instances is only saved when the InstanceSave is unloaded
// so the value read from the DB may be wrong here but only if the InstanceSave is loaded
// and in that case it is not used
@@ -18406,13 +18426,13 @@ void Player::_LoadBoundInstances(PreparedQueryResult result)
// since non permanent binds are always solo bind, they can always be reset
if (InstanceSave* save = sInstanceSaveMgr->AddInstanceSave(mapId, instanceId, Difficulty(difficulty), resetTime, !perm, true))
- BindToInstance(save, perm, true);
+ BindToInstance(save, perm, extendState, true);
}
while (result->NextRow());
}
}
-InstancePlayerBind* Player::GetBoundInstance(uint32 mapid, Difficulty difficulty)
+InstancePlayerBind* Player::GetBoundInstance(uint32 mapid, Difficulty difficulty, bool withExpired)
{
// some instances only have one difficulty
MapDifficulty const* mapDiff = GetDownscaledMapDifficultyData(mapid, difficulty);
@@ -18421,9 +18441,9 @@ InstancePlayerBind* Player::GetBoundInstance(uint32 mapid, Difficulty difficulty
BoundInstancesMap::iterator itr = m_boundInstances[difficulty].find(mapid);
if (itr != m_boundInstances[difficulty].end())
- return &itr->second;
- else
- return NULL;
+ if (itr->second.extendState || withExpired)
+ return &itr->second;
+ return nullptr;
}
InstanceSave* Player::GetInstanceSave(uint32 mapid, bool raid)
@@ -18466,24 +18486,32 @@ void Player::UnbindInstance(BoundInstancesMap::iterator &itr, Difficulty difficu
}
}
-InstancePlayerBind* Player::BindToInstance(InstanceSave* save, bool permanent, bool load)
+InstancePlayerBind* Player::BindToInstance(InstanceSave* save, bool permanent, BindExtensionState extendState, bool load)
{
if (save)
{
InstancePlayerBind& bind = m_boundInstances[save->GetDifficulty()][save->GetMapId()];
+ if (extendState == EXTEND_STATE_KEEP) // special flag, keep the player's current extend state when updating for new boss down
+ {
+ if (save == bind.save)
+ extendState = bind.extendState;
+ else
+ extendState = EXTEND_STATE_NORMAL;
+ }
if (!load)
{
if (bind.save)
{
// update the save when the group kills a boss
- if (permanent != bind.perm || save != bind.save)
+ if (permanent != bind.perm || save != bind.save || extendState != bind.extendState)
{
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_INSTANCE);
stmt->setUInt32(0, save->GetInstanceId());
stmt->setBool(1, permanent);
- stmt->setUInt32(2, GetGUID().GetCounter());
- stmt->setUInt32(3, bind.save->GetInstanceId());
+ stmt->setUInt8(2, extendState);
+ stmt->setUInt32(3, GetGUID().GetCounter());
+ stmt->setUInt32(4, bind.save->GetInstanceId());
CharacterDatabase.Execute(stmt);
}
@@ -18495,6 +18523,7 @@ InstancePlayerBind* Player::BindToInstance(InstanceSave* save, bool permanent, b
stmt->setUInt32(0, GetGUID().GetCounter());
stmt->setUInt32(1, save->GetInstanceId());
stmt->setBool(2, permanent);
+ stmt->setUInt8(3, extendState);
CharacterDatabase.Execute(stmt);
}
@@ -18512,9 +18541,10 @@ InstancePlayerBind* Player::BindToInstance(InstanceSave* save, bool permanent, b
bind.save = save;
bind.perm = permanent;
+ bind.extendState = extendState;
if (!load)
TC_LOG_DEBUG("maps", "Player::BindToInstance: %s(%d) is now bound to map %d, instance %d, difficulty %d", GetName().c_str(), GetGUID().GetCounter(), save->GetMapId(), save->GetInstanceId(), save->GetDifficulty());
- sScriptMgr->OnPlayerBindToInstance(this, save->GetDifficulty(), save->GetMapId(), permanent);
+ sScriptMgr->OnPlayerBindToInstance(this, save->GetDifficulty(), save->GetMapId(), permanent, uint8(extendState));
return &bind;
}
@@ -18532,7 +18562,7 @@ void Player::BindToInstance()
GetSession()->SendPacket(&data);
if (!IsGameMaster())
{
- BindToInstance(mapSave, true);
+ BindToInstance(mapSave, true, EXTEND_STATE_KEEP);
GetSession()->SendCalendarRaidLockout(mapSave, true);
}
}
@@ -18558,15 +18588,19 @@ void Player::SendRaidInfo()
{
for (BoundInstancesMap::iterator itr = m_boundInstances[i].begin(); itr != m_boundInstances[i].end(); ++itr)
{
- if (itr->second.perm)
- {
- InstanceSave* save = itr->second.save;
- data << uint32(save->GetMapId()); // map id
- data << uint32(save->GetDifficulty()); // difficulty
- data << uint64(save->GetInstanceId()); // instance id
- data << uint8(1); // expired = 0
- data << uint8(0); // extended = 1
- data << uint32(save->GetResetTime() - now); // reset time
+ InstancePlayerBind const& bind = itr->second;
+ if (bind.perm)
+ {
+ InstanceSave* save = bind.save;
+ data << uint32(save->GetMapId()); // map id
+ data << uint32(save->GetDifficulty()); // difficulty
+ data << uint64(save->GetInstanceId()); // instance id
+ data << uint8(bind.extendState != EXTEND_STATE_EXPIRED); // expired = 0
+ data << uint8(bind.extendState == EXTEND_STATE_EXTENDED); // extended = 1
+ time_t nextReset = save->GetResetTime();
+ if (bind.extendState == EXTEND_STATE_EXTENDED)
+ nextReset = sInstanceSaveMgr->GetSubsequentResetTime(save->GetMapId(), save->GetDifficulty(), save->GetResetTime());
+ data << uint32(nextReset - now); // reset time
++counter;
}
}
@@ -26140,11 +26174,11 @@ std::string Player::GetMapAreaAndZoneString()
uint32 areaId = GetAreaId();
std::string areaName = "Unknown";
std::string zoneName = "Unknown";
- if (AreaTableEntry const* area = GetAreaEntryByAreaID(areaId))
+ if (AreaTableEntry const* area = sAreaTableStore.LookupEntry(areaId))
{
int locale = GetSession()->GetSessionDbcLocale();
areaName = area->area_name[locale];
- if (AreaTableEntry const* zone = GetAreaEntryByAreaID(area->zone))
+ if (AreaTableEntry const* zone = sAreaTableStore.LookupEntry(area->zone))
zoneName = zone->area_name[locale];
}
diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h
index e7af827e9c7..2388cf9d0c7 100644
--- a/src/server/game/Entities/Player/Player.h
+++ b/src/server/game/Entities/Player/Player.h
@@ -834,14 +834,27 @@ enum PlayerDelayedOperations
// Maximum money amount : 2^31 - 1
extern uint32 const MAX_MONEY_AMOUNT;
+enum BindExtensionState
+{
+ EXTEND_STATE_EXPIRED = 0,
+ EXTEND_STATE_NORMAL = 1,
+ EXTEND_STATE_EXTENDED = 2,
+ EXTEND_STATE_KEEP = 255 // special state: keep current save type
+};
struct InstancePlayerBind
{
InstanceSave* save;
- bool perm;
/* permanent PlayerInstanceBinds are created in Raid/Heroic instances for players
- that aren't already permanently bound when they are inside when a boss is killed
- or when they enter an instance that the group leader is permanently bound to. */
- InstancePlayerBind() : save(NULL), perm(false) { }
+ that aren't already permanently bound when they are inside when a boss is killed
+ or when they enter an instance that the group leader is permanently bound to. */
+ bool perm;
+ /* extend state listing:
+ EXPIRED - doesn't affect anything unless manually re-extended by player
+ NORMAL - standard state
+ EXTENDED - won't be promoted to EXPIRED at next reset period, will instead be promoted to NORMAL */
+ BindExtensionState extendState;
+
+ InstancePlayerBind() : save(NULL), perm(false), extendState(EXTEND_STATE_NORMAL) { }
};
struct AccessRequirement
@@ -1050,8 +1063,9 @@ class Player : public Unit, public GridObject<Player>
void SendInstanceResetWarning(uint32 mapid, Difficulty difficulty, uint32 time, bool welcome);
bool CanInteractWithQuestGiver(Object* questGiver);
- Creature* GetNPCIfCanInteractWith(ObjectGuid guid, uint32 npcflagmask);
- GameObject* GetGameObjectIfCanInteractWith(ObjectGuid guid, GameobjectTypes type) const;
+ Creature* GetNPCIfCanInteractWith(ObjectGuid const& guid, uint32 npcflagmask);
+ GameObject* GetGameObjectIfCanInteractWith(ObjectGuid const& guid) const;
+ GameObject* GetGameObjectIfCanInteractWith(ObjectGuid const& guid, GameobjectTypes type) const;
void ToggleAFK();
void ToggleDND();
@@ -2114,12 +2128,12 @@ class Player : public Unit, public GridObject<Player>
bool m_InstanceValid;
// permanent binds and solo binds by difficulty
BoundInstancesMap m_boundInstances[MAX_DIFFICULTY];
- InstancePlayerBind* GetBoundInstance(uint32 mapid, Difficulty difficulty);
+ InstancePlayerBind* GetBoundInstance(uint32 mapid, Difficulty difficulty, bool withExpired = false);
BoundInstancesMap& GetBoundInstances(Difficulty difficulty) { return m_boundInstances[difficulty]; }
InstanceSave* GetInstanceSave(uint32 mapid, bool raid);
void UnbindInstance(uint32 mapid, Difficulty difficulty, bool unload = false);
void UnbindInstance(BoundInstancesMap::iterator &itr, Difficulty difficulty, bool unload = false);
- InstancePlayerBind* BindToInstance(InstanceSave* save, bool permanent, bool load = false);
+ InstancePlayerBind* BindToInstance(InstanceSave* save, bool permanent, BindExtensionState extendState = EXTEND_STATE_NORMAL, bool load = false);
void BindToInstance();
void SetPendingBind(uint32 instanceId, uint32 bindTimer);
bool HasPendingBind() const { return _pendingBindId > 0; }
diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp
index 24ddcd358ea..c21301c27b2 100644
--- a/src/server/game/Entities/Unit/Unit.cpp
+++ b/src/server/game/Entities/Unit/Unit.cpp
@@ -157,6 +157,28 @@ ProcEventInfo::ProcEventInfo(Unit* actor, Unit* actionTarget, Unit* procTarget,
_damageInfo(damageInfo), _healInfo(healInfo)
{ }
+SpellInfo const* ProcEventInfo::GetSpellInfo() const
+{
+ if (_spell)
+ return _spell->GetSpellInfo();
+ if (_damageInfo)
+ return _damageInfo->GetSpellInfo();
+ if (_healInfo)
+ return _healInfo->GetSpellInfo();
+ return nullptr;
+}
+
+SpellSchoolMask ProcEventInfo::GetSchoolMask() const
+{
+ if (_spell)
+ return _spell->GetSpellInfo()->GetSchoolMask();
+ if (_damageInfo)
+ return _damageInfo->GetSchoolMask();
+ if (_healInfo)
+ return _healInfo->GetSchoolMask();
+ return SPELL_SCHOOL_MASK_NONE;
+}
+
Unit::Unit(bool isWorldObject) :
WorldObject(isWorldObject), m_movedPlayer(NULL), m_lastSanctuaryTime(0),
IsAIEnabled(false), NeedChangeAI(false), LastCharmerGUID(),
@@ -1670,6 +1692,9 @@ void Unit::CalcAbsorbResist(Unit* victim, SpellSchoolMask schoolMask, DamageEffe
RoundToInterval(auraAbsorbMod, 0.0f, 100.0f);
+ int32 absorbIgnoringDamage = CalculatePct(dmgInfo.GetDamage(), auraAbsorbMod);
+ dmgInfo.ModifyDamage(-absorbIgnoringDamage);
+
// We're going to call functions which can modify content of the list during iteration over it's elements
// Let's copy the list so we can prevent iterator invalidation
AuraEffectList vSchoolAbsorbCopy(victim->GetAuraEffectsByType(SPELL_AURA_SCHOOL_ABSORB));
@@ -1702,9 +1727,6 @@ void Unit::CalcAbsorbResist(Unit* victim, SpellSchoolMask schoolMask, DamageEffe
if (defaultPrevented)
continue;
- // Apply absorb mod auras
- AddPct(currentAbsorb, -auraAbsorbMod);
-
// absorb must be smaller than the damage itself
currentAbsorb = RoundToInterval(currentAbsorb, 0, int32(dmgInfo.GetDamage()));
@@ -1753,8 +1775,6 @@ void Unit::CalcAbsorbResist(Unit* victim, SpellSchoolMask schoolMask, DamageEffe
if (defaultPrevented)
continue;
- AddPct(currentAbsorb, -auraAbsorbMod);
-
// absorb must be smaller than the damage itself
currentAbsorb = RoundToInterval(currentAbsorb, 0, int32(dmgInfo.GetDamage()));
@@ -1783,6 +1803,8 @@ void Unit::CalcAbsorbResist(Unit* victim, SpellSchoolMask schoolMask, DamageEffe
}
}
+ dmgInfo.ModifyDamage(absorbIgnoringDamage);
+
// split damage auras - only when not damaging self
if (victim != this)
{
@@ -4333,13 +4355,10 @@ void Unit::GetDispellableAuraList(Unit* caster, uint32 dispelMask, DispelCharges
if (aura->GetSpellInfo()->GetDispelMask() & dispelMask)
{
- if (aura->GetSpellInfo()->Dispel == DISPEL_MAGIC)
- {
- // do not remove positive auras if friendly target
- // negative auras if non-friendly target
- if (aurApp->IsPositive() == IsFriendlyTo(caster))
- continue;
- }
+ // do not remove positive auras if friendly target
+ // negative auras if non-friendly target
+ if (aurApp->IsPositive() == IsFriendlyTo(caster))
+ continue;
// The charges / stack amounts don't count towards the total number of auras that can be dispelled.
// Ie: A dispel on a target with 5 stacks of Winters Chill and a Polymorph has 1 / (1 + 1) -> 50% chance to dispell
@@ -6365,54 +6384,6 @@ bool Unit::HandleDummyAuraProc(Unit* victim, uint32 damage, AuraEffect* triggere
}
case SPELLFAMILY_PALADIN:
{
- // Light's Beacon - Beacon of Light
- if (dummySpell->Id == 53651)
- {
- if (!victim || !procSpell)
- return false;
- triggered_spell_id = 0;
- Unit* beaconTarget = NULL;
- if (GetTypeId() != TYPEID_PLAYER)
- {
- beaconTarget = triggeredByAura->GetBase()->GetCaster();
- if (!beaconTarget || beaconTarget == this || !(beaconTarget->GetAura(53563, victim->GetGUID())))
- return false;
- basepoints0 = int32(damage);
- triggered_spell_id = procSpell->IsRankOf(sSpellMgr->GetSpellInfo(635)) ? 53652 : 53654;
- }
- else
- { // Check Party/Raid Group
- if (Group* group = ToPlayer()->GetGroup())
- {
- for (GroupReference* itr = group->GetFirstMember(); itr != NULL; itr = itr->next())
- {
- if (Player* member = itr->GetSource())
- {
- // check if it was heal by paladin which cast this beacon of light
- if (member->GetAura(53563, victim->GetGUID()))
- {
- // do not proc when target of beacon of light is healed
- if (member == this)
- return false;
-
- beaconTarget = member;
- basepoints0 = int32(damage);
- triggered_spell_id = procSpell->IsRankOf(sSpellMgr->GetSpellInfo(635)) ? 53652 : 53654;
- break;
- }
- }
- }
- }
- }
-
- if (triggered_spell_id && beaconTarget)
- {
- victim->CastCustomSpell(beaconTarget, triggered_spell_id, &basepoints0, NULL, NULL, true);
- return true;
- }
-
- return false;
- }
// Judgements of the Wise
if (dummySpell->SpellIconID == 3017)
{
@@ -7125,9 +7096,6 @@ bool Unit::HandleDummyAuraProc(Unit* victim, uint32 damage, AuraEffect* triggere
float chance = 100.0f / procSpell->Effects[effIndex].ChainTarget;
if (!roll_chance_f(chance))
return false;
-
- // Remove cooldown (Chain Lightning - has Category Recovery time)
- GetSpellHistory()->ResetCooldown(spellId);
}
CastSpell(victim, spellId, true, castItem, triggeredByAura);
@@ -8357,8 +8325,6 @@ bool Unit::HandleProcTriggerSpell(Unit* victim, uint32 damage, AuraEffect* trigg
case 52914:
case 52915:
case 52910:
- // Honor Among Thieves
- case 52916:
{
target = triggeredByAura->GetBase()->GetCaster();
if (!target)
@@ -12779,7 +12745,7 @@ Unit* Creature::SelectVictim()
}
}
else
- return NULL;
+ return nullptr;
if (target && _IsTargetAcceptable(target) && CanCreatureAttack(target))
{
@@ -12788,14 +12754,6 @@ Unit* Creature::SelectVictim()
return target;
}
- // Case where mob is being kited.
- // Mob may not be in range to attack or may have dropped target. In any case,
- // don't evade if damage received within the last 10 seconds
- // Does not apply to world bosses to prevent kiting to cities
- if (!isWorldBoss() && !GetInstanceId())
- if (time(NULL) - GetLastDamagedTime() <= MAX_AGGRO_RESET_TIME)
- return target;
-
// last case when creature must not go to evade mode:
// it in combat but attacker not make any damage and not enter to aggro radius to have record in threat list
// for example at owner command to pet attack some far away creature
@@ -12804,12 +12762,12 @@ Unit* Creature::SelectVictim()
{
if ((*itr) && !CanCreatureAttack(*itr) && (*itr)->GetTypeId() != TYPEID_PLAYER
&& !(*itr)->ToCreature()->HasUnitTypeMask(UNIT_MASK_CONTROLABLE_GUARDIAN))
- return NULL;
+ return nullptr;
}
/// @todo a vehicle may eat some mob, so mob should not evade
if (GetVehicle())
- return NULL;
+ return nullptr;
// search nearby enemy before enter evade mode
if (HasReactState(REACT_AGGRESSIVE))
@@ -12827,17 +12785,17 @@ Unit* Creature::SelectVictim()
{
if ((*itr)->GetBase()->IsPermanent())
{
- AI()->EnterEvadeMode();
+ AI()->EnterEvadeMode(CreatureAI::EVADE_REASON_OTHER);
break;
}
}
- return NULL;
+ return nullptr;
}
// enter in evade mode in other case
AI()->EnterEvadeMode(CreatureAI::EVADE_REASON_NO_HOSTILES);
- return NULL;
+ return nullptr;
}
//======================================================================
@@ -14187,8 +14145,8 @@ void Unit::ProcDamageAndSpellFor(bool isVictim, Unit* target, uint32 procFlag, u
Unit* actionTarget = !isVictim ? target : this;
DamageInfo damageInfo = DamageInfo(actor, actionTarget, damage, procSpell, procSpell ? SpellSchoolMask(procSpell->SchoolMask) : SPELL_SCHOOL_MASK_NORMAL, SPELL_DIRECT_DAMAGE);
- HealInfo healInfo = HealInfo(damage);
- ProcEventInfo eventInfo = ProcEventInfo(actor, actionTarget, target, procFlag, 0, 0, procExtra, NULL, &damageInfo, &healInfo);
+ HealInfo healInfo = HealInfo(actor, actionTarget, damage, procSpell, procSpell ? SpellSchoolMask(procSpell->SchoolMask) : SPELL_SCHOOL_MASK_NORMAL);
+ ProcEventInfo eventInfo = ProcEventInfo(actor, actionTarget, target, procFlag, 0, 0, procExtra, nullptr, &damageInfo, &healInfo);
ProcTriggeredList procTriggered;
// Fill procTriggered list
@@ -16898,12 +16856,12 @@ void Unit::JumpTo(float speedXY, float speedZ, bool forward)
}
}
-void Unit::JumpTo(WorldObject* obj, float speedZ)
+void Unit::JumpTo(WorldObject* obj, float speedZ, bool withOrientation)
{
float x, y, z;
obj->GetContactPoint(this, x, y, z);
float speedXY = GetExactDist2d(x, y) * 10.0f / speedZ;
- GetMotionMaster()->MoveJump(x, y, z, speedXY, speedZ);
+ GetMotionMaster()->MoveJump(x, y, z, GetAngle(obj), speedXY, speedZ, EVENT_JUMP, withOrientation);
}
bool Unit::HandleSpellClick(Unit* clicker, int8 seatId)
diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h
index c64667feeca..4565160dc93 100644
--- a/src/server/game/Entities/Unit/Unit.h
+++ b/src/server/game/Entities/Unit/Unit.h
@@ -863,22 +863,30 @@ public:
class HealInfo
{
private:
- uint32 m_heal;
- uint32 m_absorb;
+ Unit* const _healer;
+ Unit* const _target;
+ uint32 _heal;
+ uint32 _absorb;
+ SpellInfo const* const _spellInfo;
+ SpellSchoolMask const _schoolMask;
+
public:
- explicit HealInfo(uint32 heal)
- : m_heal(heal)
- {
- m_absorb = 0;
- }
+ explicit HealInfo(Unit* healer, Unit* target, uint32 heal, SpellInfo const* spellInfo, SpellSchoolMask schoolMask)
+ : _healer(healer), _target(target), _heal(heal), _absorb(0), _spellInfo(spellInfo), _schoolMask(schoolMask) { }
+
void AbsorbHeal(uint32 amount)
{
amount = std::min(amount, GetHeal());
- m_absorb += amount;
- m_heal -= amount;
+ _absorb += amount;
+ _heal -= amount;
}
- uint32 GetHeal() const { return m_heal; }
+ Unit* GetHealer() const { return _healer; }
+ Unit* GetTarget() const { return _target; }
+ uint32 GetHeal() const { return _heal; }
+ uint32 GetAbsorb() const { return _absorb; }
+ SpellInfo const* GetSpellInfo() const { return _spellInfo; };
+ SpellSchoolMask GetSchoolMask() const { return _schoolMask; };
};
class ProcEventInfo
@@ -897,14 +905,8 @@ public:
uint32 GetSpellPhaseMask() const { return _spellPhaseMask; }
uint32 GetHitMask() const { return _hitMask; }
- SpellInfo const* GetSpellInfo() const { return NULL; }
- SpellInfo const* EnsureSpellInfo() const
- {
- SpellInfo const* spellInfo = GetSpellInfo();
- ASSERT(spellInfo);
- return spellInfo;
- }
- SpellSchoolMask GetSchoolMask() const { return SPELL_SCHOOL_MASK_NONE; }
+ SpellInfo const* GetSpellInfo() const;
+ SpellSchoolMask GetSchoolMask() const;
DamageInfo* GetDamageInfo() const { return _damageInfo; }
HealInfo* GetHealInfo() const { return _healInfo; }
@@ -1565,7 +1567,7 @@ class Unit : public WorldObject
void KnockbackFrom(float x, float y, float speedXY, float speedZ);
void JumpTo(float speedXY, float speedZ, bool forward = true);
- void JumpTo(WorldObject* obj, float speedZ);
+ void JumpTo(WorldObject* obj, float speedZ, bool withOrientation = false);
void MonsterMoveWithSpeed(float x, float y, float z, float speed, bool generatePath = false, bool forceDestination = false);
//void SetFacing(float ori, WorldObject* obj = NULL);
diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp
index d2064092a65..f45634e9684 100644
--- a/src/server/game/Globals/ObjectMgr.cpp
+++ b/src/server/game/Globals/ObjectMgr.cpp
@@ -1756,7 +1756,7 @@ void ObjectMgr::LoadCreatures()
if (!ok)
continue;
- // -1 random, 0 no equipment,
+ // -1 random, 0 no equipment
if (data.equipmentId != 0)
{
if (!GetEquipmentInfo(data.id, data.equipmentId))
@@ -2773,7 +2773,7 @@ void ObjectMgr::LoadItemTemplates()
itemTemplate.ItemSet = 0;
}
- if (itemTemplate.Area && !GetAreaEntryByAreaID(itemTemplate.Area))
+ if (itemTemplate.Area && !sAreaTableStore.LookupEntry(itemTemplate.Area))
TC_LOG_ERROR("sql.sql", "Item (Entry: %u) has wrong Area (%u)", entry, itemTemplate.Area);
if (itemTemplate.Map && !sMapStore.LookupEntry(itemTemplate.Map))
@@ -4143,7 +4143,7 @@ void ObjectMgr::LoadQuests()
// client quest log visual (area case)
if (qinfo->ZoneOrSort > 0)
{
- if (!GetAreaEntryByAreaID(qinfo->ZoneOrSort))
+ if (!sAreaTableStore.LookupEntry(qinfo->ZoneOrSort))
{
TC_LOG_ERROR("sql.sql", "Quest %u has `ZoneOrSort` = %u (zone case) but zone with this id does not exist.",
qinfo->GetQuestId(), qinfo->ZoneOrSort);
@@ -5956,7 +5956,7 @@ void ObjectMgr::LoadGraveyardZones()
continue;
}
- AreaTableEntry const* areaEntry = GetAreaEntryByAreaID(zoneId);
+ AreaTableEntry const* areaEntry = sAreaTableStore.LookupEntry(zoneId);
if (!areaEntry)
{
TC_LOG_ERROR("sql.sql", "Table `game_graveyard_zone` has a record for not existing zone id (%u), skipped.", zoneId);
@@ -7796,7 +7796,7 @@ void ObjectMgr::LoadFishingBaseSkillLevel()
uint32 entry = fields[0].GetUInt32();
int32 skill = fields[1].GetInt16();
- AreaTableEntry const* fArea = GetAreaEntryByAreaID(entry);
+ AreaTableEntry const* fArea = sAreaTableStore.LookupEntry(entry);
if (!fArea)
{
TC_LOG_ERROR("sql.sql", "AreaId %u defined in `skill_fishing_base_level` does not exist", entry);
diff --git a/src/server/game/Grids/GridDefines.h b/src/server/game/Grids/GridDefines.h
index 162c39b951b..24c9100b222 100644
--- a/src/server/game/Grids/GridDefines.h
+++ b/src/server/game/Grids/GridDefines.h
@@ -226,7 +226,7 @@ namespace Trinity
inline bool IsValidMapCoord(float x, float y, float z)
{
- return IsValidMapCoord(x, y) && std::isfinite(z);
+ return IsValidMapCoord(x, y) && IsValidMapCoord(z);
}
inline bool IsValidMapCoord(float x, float y, float z, float o)
diff --git a/src/server/game/Grids/Notifiers/GridNotifiers.cpp b/src/server/game/Grids/Notifiers/GridNotifiers.cpp
index c48d1947eec..2cdbdca4e4f 100644
--- a/src/server/game/Grids/Notifiers/GridNotifiers.cpp
+++ b/src/server/game/Grids/Notifiers/GridNotifiers.cpp
@@ -131,7 +131,7 @@ inline void CreatureUnitRelocationWorker(Creature* c, Unit* u)
if (!u->IsAlive() || !c->IsAlive() || c == u || u->IsInFlight())
return;
- if (c->HasReactState(REACT_AGGRESSIVE) && !c->HasUnitState(UNIT_STATE_SIGHTLESS))
+ if (!c->HasUnitState(UNIT_STATE_SIGHTLESS))
{
if (c->IsAIEnabled && c->CanSeeOrDetect(u, false, true))
c->AI()->MoveInLineOfSight_Safe(u);
diff --git a/src/server/game/Groups/Group.cpp b/src/server/game/Groups/Group.cpp
index 8ebc71b0146..99c5d610e64 100644
--- a/src/server/game/Groups/Group.cpp
+++ b/src/server/game/Groups/Group.cpp
@@ -687,7 +687,8 @@ void Group::ConvertLeaderInstancesToGroup(Player* player, Group* group, bool swi
for (Player::BoundInstancesMap::iterator itr = player->m_boundInstances[i].begin(); itr != player->m_boundInstances[i].end();)
{
if (!switchLeader || !group->GetBoundInstance(itr->second.save->GetDifficulty(), itr->first))
- group->BindToInstance(itr->second.save, itr->second.perm, false);
+ if (itr->second.extendState) // not expired
+ group->BindToInstance(itr->second.save, itr->second.perm, false);
// permanent binds are not removed
if (switchLeader && !itr->second.perm)
@@ -2274,6 +2275,8 @@ LootMethod Group::GetLootMethod() const
ObjectGuid Group::GetLooterGuid() const
{
+ if (GetLootMethod() == FREE_FOR_ALL)
+ return ObjectGuid::Empty;
return m_looterGuid;
}
diff --git a/src/server/game/Handlers/AuctionHouseHandler.cpp b/src/server/game/Handlers/AuctionHouseHandler.cpp
index f23888cab03..efe0526baae 100644
--- a/src/server/game/Handlers/AuctionHouseHandler.cpp
+++ b/src/server/game/Handlers/AuctionHouseHandler.cpp
@@ -706,7 +706,7 @@ void WorldSession::HandleAuctionListItems(WorldPacket& recvData)
TC_LOG_DEBUG("network", "WORLD: Received CMSG_AUCTION_LIST_ITEMS");
std::string searchedname;
- uint8 levelmin, levelmax, usable;
+ uint8 levelmin, levelmax, usable, getAll;
uint32 listfrom, auctionSlotID, auctionMainCategory, auctionSubCategory, quality;
ObjectGuid guid;
@@ -718,7 +718,7 @@ void WorldSession::HandleAuctionListItems(WorldPacket& recvData)
recvData >> auctionSlotID >> auctionMainCategory >> auctionSubCategory;
recvData >> quality >> usable;
- recvData.read_skip<uint8>(); // unk
+ recvData >> getAll;
// this block looks like it uses some lame byte packing or similar...
uint8 unkCnt;
@@ -760,11 +760,11 @@ void WorldSession::HandleAuctionListItems(WorldPacket& recvData)
auctionHouse->BuildListAuctionItems(data, _player,
wsearchedname, listfrom, levelmin, levelmax, usable,
auctionSlotID, auctionMainCategory, auctionSubCategory, quality,
- count, totalcount);
+ count, totalcount, (getAll != 0 && sWorld->getIntConfig(CONFIG_AUCTION_GETALL_DELAY) != 0));
data.put<uint32>(0, count);
data << (uint32) totalcount;
- data << (uint32) 300; // unk 2.3.0 const?
+ data << (uint32) sWorld->getIntConfig(CONFIG_AUCTION_SEARCH_DELAY);
SendPacket(&data);
}
diff --git a/src/server/game/Handlers/CalendarHandler.cpp b/src/server/game/Handlers/CalendarHandler.cpp
index 8bd7086fc1b..540eeba0752 100644
--- a/src/server/game/Handlers/CalendarHandler.cpp
+++ b/src/server/game/Handlers/CalendarHandler.cpp
@@ -695,6 +695,21 @@ void WorldSession::HandleSetSavedInstanceExtend(WorldPacket& recvData)
recvData >> mapId >> difficulty>> toggleExtend;
TC_LOG_DEBUG("network", "CMSG_SET_SAVED_INSTANCE_EXTEND - MapId: %u, Difficulty: %u, ToggleExtend: %s", mapId, difficulty, toggleExtend ? "On" : "Off");
+ if (Player* player = GetPlayer())
+ {
+ InstancePlayerBind* instanceBind = player->GetBoundInstance(mapId, Difficulty(difficulty), toggleExtend == 1); // include expired instances if we are toggling extend on
+ if (!instanceBind || !instanceBind->save || !instanceBind->perm)
+ return;
+
+ BindExtensionState newState;
+ if (!toggleExtend || instanceBind->extendState == EXTEND_STATE_EXPIRED)
+ newState = EXTEND_STATE_NORMAL;
+ else
+ newState = EXTEND_STATE_EXTENDED;
+
+ player->BindToInstance(instanceBind->save, true, newState, false);
+ }
+
/*
InstancePlayerBind* instanceBind = _player->GetBoundInstance(mapId, Difficulty(difficulty));
if (!instanceBind || !instanceBind->save)
diff --git a/src/server/game/Handlers/ChannelHandler.cpp b/src/server/game/Handlers/ChannelHandler.cpp
index 976860e8cc0..9285f4247b2 100644
--- a/src/server/game/Handlers/ChannelHandler.cpp
+++ b/src/server/game/Handlers/ChannelHandler.cpp
@@ -39,7 +39,7 @@ void WorldSession::HandleJoinChannel(WorldPacket& recvPacket)
if (!channel)
return;
- AreaTableEntry const* zone = GetAreaEntryByAreaID(GetPlayer()->GetZoneId());
+ AreaTableEntry const* zone = sAreaTableStore.LookupEntry(GetPlayer()->GetZoneId());
if (!zone || !GetPlayer()->CanJoinConstantChannelInZone(channel, zone))
return;
}
diff --git a/src/server/game/Handlers/MiscHandler.cpp b/src/server/game/Handlers/MiscHandler.cpp
index 758d5af83f7..5f5a66e7b20 100644
--- a/src/server/game/Handlers/MiscHandler.cpp
+++ b/src/server/game/Handlers/MiscHandler.cpp
@@ -105,7 +105,7 @@ void WorldSession::HandleGossipSelectOptionOpcode(WorldPacket& recvData)
GameObject* go = NULL;
if (guid.IsCreatureOrVehicle())
{
- unit = GetPlayer()->GetNPCIfCanInteractWith(guid, UNIT_NPC_FLAG_NONE);
+ unit = GetPlayer()->GetNPCIfCanInteractWith(guid, UNIT_NPC_FLAG_GOSSIP);
if (!unit)
{
TC_LOG_DEBUG("network", "WORLD: HandleGossipSelectOptionOpcode - %s not found or you can't interact with him.", guid.ToString().c_str());
@@ -114,10 +114,10 @@ void WorldSession::HandleGossipSelectOptionOpcode(WorldPacket& recvData)
}
else if (guid.IsGameObject())
{
- go = _player->GetMap()->GetGameObject(guid);
+ go = _player->GetGameObjectIfCanInteractWith(guid);
if (!go)
{
- TC_LOG_DEBUG("network", "WORLD: HandleGossipSelectOptionOpcode - %s not found.", guid.ToString().c_str());
+ TC_LOG_DEBUG("network", "WORLD: HandleGossipSelectOptionOpcode - %s not found or you can't interact with it.", guid.ToString().c_str());
return;
}
}
@@ -319,7 +319,7 @@ void WorldSession::HandleWhoOpcode(WorldPacket& recvData)
continue;
std::string aname;
- if (AreaTableEntry const* areaEntry = GetAreaEntryByAreaID(pzoneid))
+ if (AreaTableEntry const* areaEntry = sAreaTableStore.LookupEntry(pzoneid))
aname = areaEntry->area_name[GetSessionDbcLocale()];
bool s_show = true;
@@ -1753,7 +1753,7 @@ void WorldSession::HandleHearthAndResurrect(WorldPacket& /*recvData*/)
return;
}
- AreaTableEntry const* atEntry = GetAreaEntryByAreaID(_player->GetAreaId());
+ AreaTableEntry const* atEntry = sAreaTableStore.LookupEntry(_player->GetAreaId());
if (!atEntry || !(atEntry->flags & AREA_FLAG_WINTERGRASP_2))
return;
diff --git a/src/server/game/Handlers/MovementHandler.cpp b/src/server/game/Handlers/MovementHandler.cpp
index eea5c62fbd1..02702fc5622 100644
--- a/src/server/game/Handlers/MovementHandler.cpp
+++ b/src/server/game/Handlers/MovementHandler.cpp
@@ -386,7 +386,7 @@ void WorldSession::HandleMovementOpcodes(WorldPacket& recvData)
plrMover->UpdateFallInformationIfNeed(movementInfo, opcode);
- if (movementInfo.pos.GetPositionZ() < -500.0f)
+ if (movementInfo.pos.GetPositionZ() < plrMover->GetMap()->GetMinHeight(movementInfo.pos.GetPositionX(), movementInfo.pos.GetPositionY()))
{
if (!(plrMover->GetBattleground() && plrMover->GetBattleground()->HandlePlayerUnderMap(_player)))
{
@@ -395,6 +395,7 @@ void WorldSession::HandleMovementOpcodes(WorldPacket& recvData)
/// @todo discard movement packets after the player is rooted
if (plrMover->IsAlive())
{
+ plrMover->SetFlag(PLAYER_FLAGS, PLAYER_FLAGS_IS_OUT_OF_BOUNDS);
plrMover->EnvironmentalDamage(DAMAGE_FALL_TO_VOID, GetPlayer()->GetMaxHealth());
// player can be alive if GM/etc
// change the death state to CORPSE to prevent the death timer from
diff --git a/src/server/game/Handlers/NPCHandler.cpp b/src/server/game/Handlers/NPCHandler.cpp
index f272cd4a034..1e00c25a0c3 100644
--- a/src/server/game/Handlers/NPCHandler.cpp
+++ b/src/server/game/Handlers/NPCHandler.cpp
@@ -292,7 +292,7 @@ void WorldSession::HandleGossipHelloOpcode(WorldPacket& recvData)
ObjectGuid guid;
recvData >> guid;
- Creature* unit = GetPlayer()->GetNPCIfCanInteractWith(guid, UNIT_NPC_FLAG_NONE);
+ Creature* unit = GetPlayer()->GetNPCIfCanInteractWith(guid, UNIT_NPC_FLAG_GOSSIP);
if (!unit)
{
TC_LOG_DEBUG("network", "WORLD: HandleGossipHelloOpcode - %s not found or you can not interact with him.", guid.ToString().c_str());
@@ -334,47 +334,6 @@ void WorldSession::HandleGossipHelloOpcode(WorldPacket& recvData)
unit->AI()->sGossipHello(_player);
}
-/*void WorldSession::HandleGossipSelectOptionOpcode(WorldPacket& recvData)
-{
- TC_LOG_DEBUG("network", "WORLD: CMSG_GOSSIP_SELECT_OPTION");
-
- uint32 option;
- uint32 unk;
- uint64 guid;
- std::string code = "";
-
- recvData >> guid >> unk >> option;
-
- if (_player->PlayerTalkClass->GossipOptionCoded(option))
- {
- TC_LOG_DEBUG("network", "reading string");
- recvData >> code;
- TC_LOG_DEBUG("network", "string read: %s", code.c_str());
- }
-
- Creature* unit = GetPlayer()->GetNPCIfCanInteractWith(guid, UNIT_NPC_FLAG_NONE);
- if (!unit)
- {
- TC_LOG_DEBUG("network", "WORLD: HandleGossipSelectOptionOpcode - Unit (GUID: %u) not found or you can't interact with him.", uint32(GUID_LOPART(guid)));
- return;
- }
-
- // remove fake death
- if (GetPlayer()->HasUnitState(UNIT_STATE_DIED))
- GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH);
-
- if (!code.empty())
- {
- if (!Script->GossipSelectWithCode(_player, unit, _player->PlayerTalkClass->GossipOptionSender (option), _player->PlayerTalkClass->GossipOptionAction(option), code.c_str()))
- unit->OnGossipSelect (_player, option);
- }
- else
- {
- if (!Script->OnGossipSelect (_player, unit, _player->PlayerTalkClass->GossipOptionSender (option), _player->PlayerTalkClass->GossipOptionAction (option)))
- unit->OnGossipSelect (_player, option);
- }
-}*/
-
void WorldSession::HandleSpiritHealerActivateOpcode(WorldPacket& recvData)
{
TC_LOG_DEBUG("network", "WORLD: CMSG_SPIRIT_HEALER_ACTIVATE");
diff --git a/src/server/game/Handlers/QuestHandler.cpp b/src/server/game/Handlers/QuestHandler.cpp
index 002adc19a86..a7db18deddb 100644
--- a/src/server/game/Handlers/QuestHandler.cpp
+++ b/src/server/game/Handlers/QuestHandler.cpp
@@ -75,7 +75,7 @@ void WorldSession::HandleQuestgiverHelloOpcode(WorldPacket& recvData)
TC_LOG_DEBUG("network", "WORLD: Received CMSG_QUESTGIVER_HELLO %s", guid.ToString().c_str());
- Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(guid, UNIT_NPC_FLAG_NONE);
+ Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(guid, UNIT_NPC_FLAG_QUESTGIVER);
if (!creature)
{
TC_LOG_DEBUG("network", "WORLD: HandleQuestgiverHelloOpcode - %s not found or you can't interact with him.",
diff --git a/src/server/game/Handlers/SpellHandler.cpp b/src/server/game/Handlers/SpellHandler.cpp
index 0943d9db26a..6be1fd30ae3 100644
--- a/src/server/game/Handlers/SpellHandler.cpp
+++ b/src/server/game/Handlers/SpellHandler.cpp
@@ -268,11 +268,8 @@ void WorldSession::HandleGameObjectUseOpcode(WorldPacket& recvData)
TC_LOG_DEBUG("network", "WORLD: Recvd CMSG_GAMEOBJ_USE Message [%s]", guid.ToString().c_str());
- if (GameObject* obj = GetPlayer()->GetMap()->GetGameObject(guid))
+ if (GameObject* obj = GetPlayer()->GetGameObjectIfCanInteractWith(guid))
{
- if (!obj->IsWithinDistInMap(GetPlayer(), obj->GetInteractionDistance()))
- return;
-
// ignore for remote control state
if (GetPlayer()->m_mover != GetPlayer())
if (!(GetPlayer()->IsOnVehicle(GetPlayer()->m_mover) || GetPlayer()->IsMounted()) && !obj->GetGOInfo()->IsUsableMounted())
@@ -293,17 +290,13 @@ void WorldSession::HandleGameobjectReportUse(WorldPacket& recvPacket)
if (_player->m_mover != _player)
return;
- GameObject* go = GetPlayer()->GetMap()->GetGameObject(guid);
- if (!go)
- return;
-
- if (!go->IsWithinDistInMap(_player, INTERACTION_DISTANCE))
- return;
-
- if (go->AI()->GossipHello(_player))
- return;
+ if (GameObject* go = GetPlayer()->GetGameObjectIfCanInteractWith(guid))
+ {
+ if (go->AI()->GossipHello(_player))
+ return;
- _player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_USE_GAMEOBJECT, go->GetEntry());
+ _player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_USE_GAMEOBJECT, go->GetEntry());
+ }
}
void WorldSession::HandleCastSpellOpcode(WorldPacket& recvPacket)
diff --git a/src/server/game/Instances/InstanceSaveMgr.cpp b/src/server/game/Instances/InstanceSaveMgr.cpp
index 9a64335e6f0..3538262ed35 100644
--- a/src/server/game/Instances/InstanceSaveMgr.cpp
+++ b/src/server/game/Instances/InstanceSaveMgr.cpp
@@ -439,6 +439,23 @@ void InstanceSaveManager::LoadResetTimes()
}
}
+time_t InstanceSaveManager::GetSubsequentResetTime(uint32 mapid, Difficulty difficulty, time_t resetTime) const
+{
+ MapDifficulty const* mapDiff = GetMapDifficultyData(mapid, difficulty);
+ if (!mapDiff || !mapDiff->resetTime)
+ {
+ TC_LOG_ERROR("misc", "InstanceSaveManager::GetSubsequentResetTime: not valid difficulty or no reset delay for map %u", mapid);
+ return 0;
+ }
+
+ time_t diff = sWorld->getIntConfig(CONFIG_INSTANCE_RESET_TIME_HOUR) * HOUR;
+ time_t period = uint32(((mapDiff->resetTime * sWorld->getRate(RATE_INSTANCE_RESET_TIME)) / DAY) * DAY);
+ if (period < DAY)
+ period = DAY;
+
+ return ((resetTime + MINUTE) / DAY * DAY) + period + diff;
+}
+
void InstanceSaveManager::ScheduleReset(bool add, time_t time, InstResetEvent event)
{
if (!add)
@@ -476,6 +493,17 @@ void InstanceSaveManager::ScheduleReset(bool add, time_t time, InstResetEvent ev
m_resetTimeQueue.insert(std::pair<time_t, InstResetEvent>(time, event));
}
+void InstanceSaveManager::ForceGlobalReset(uint32 mapId, Difficulty difficulty)
+{
+ if (!GetDownscaledMapDifficultyData(mapId, difficulty))
+ return;
+ // remove currently scheduled reset times
+ ScheduleReset(false, 0, InstResetEvent(1, mapId, difficulty, 0));
+ ScheduleReset(false, 0, InstResetEvent(4, mapId, difficulty, 0));
+ // force global reset on the instance
+ _ResetOrWarnAll(mapId, difficulty, false, time(nullptr));
+}
+
void InstanceSaveManager::Update()
{
time_t now = time(NULL);
@@ -516,10 +544,26 @@ void InstanceSaveManager::_ResetSave(InstanceSaveHashMap::iterator &itr)
// do not allow UnbindInstance to automatically unload the InstanceSaves
lock_instLists = true;
+ bool shouldDelete = true;
InstanceSave::PlayerListType &pList = itr->second->m_playerList;
- while (!pList.empty())
+ std::vector<Player*> temp; // list of expired binds that should be unbound
+ for (Player* player : pList)
+ {
+ if (InstancePlayerBind* bind = player->GetBoundInstance(itr->second->GetMapId(), itr->second->GetDifficulty()))
+ {
+ ASSERT(bind->save == itr->second);
+ if (bind->perm && bind->extendState) // permanent and not already expired
+ {
+ // actual promotion in DB already happened in caller
+ bind->extendState = bind->extendState == EXTEND_STATE_EXTENDED ? EXTEND_STATE_NORMAL : EXTEND_STATE_EXPIRED;
+ shouldDelete = false;
+ continue;
+ }
+ }
+ temp.push_back(player);
+ }
+ for (Player* player : temp)
{
- Player* player = *(pList.begin());
player->UnbindInstance(itr->second->GetMapId(), itr->second->GetDifficulty(), true);
}
@@ -530,8 +574,13 @@ void InstanceSaveManager::_ResetSave(InstanceSaveHashMap::iterator &itr)
group->UnbindInstance(itr->second->GetMapId(), itr->second->GetDifficulty(), true);
}
- delete itr->second;
- m_instanceSaveById.erase(itr++);
+ if (shouldDelete)
+ {
+ delete itr->second;
+ itr = m_instanceSaveById.erase(itr);
+ }
+ else
+ ++itr;
lock_instLists = false;
}
@@ -572,31 +621,21 @@ void InstanceSaveManager::_ResetOrWarnAll(uint32 mapid, Difficulty difficulty, b
MapEntry const* mapEntry = sMapStore.LookupEntry(mapid);
if (!mapEntry->Instanceable())
return;
+ TC_LOG_DEBUG("misc", "InstanceSaveManager::ResetOrWarnAll: Processing map %s (%u) on difficulty %u (warn? %u)", mapEntry->name[0], mapid, uint8(difficulty), warn);
time_t now = time(NULL);
if (!warn)
{
- MapDifficulty const* mapDiff = GetMapDifficultyData(mapid, difficulty);
- if (!mapDiff || !mapDiff->resetTime)
- {
- TC_LOG_ERROR("misc", "InstanceSaveManager::ResetOrWarnAll: not valid difficulty or no reset delay for map %d", mapid);
+ // calculate the next reset time
+ time_t next_reset = GetSubsequentResetTime(mapid, difficulty, resetTime);
+ if (!next_reset)
return;
- }
- // remove all binds to instances of the given map
- for (InstanceSaveHashMap::iterator itr = m_instanceSaveById.begin(); itr != m_instanceSaveById.end();)
- {
- if (itr->second->GetMapId() == mapid && itr->second->GetDifficulty() == difficulty)
- _ResetSave(itr);
- else
- ++itr;
- }
-
- // delete them from the DB, even if not loaded
+ // delete/promote instance binds from the DB, even if not loaded
SQLTransaction trans = CharacterDatabase.BeginTransaction();
- PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INSTANCE_BY_MAP_DIFF);
+ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_EXPIRED_CHAR_INSTANCE_BY_MAP_DIFF);
stmt->setUInt16(0, uint16(mapid));
stmt->setUInt8(1, uint8(difficulty));
trans->Append(stmt);
@@ -606,21 +645,26 @@ void InstanceSaveManager::_ResetOrWarnAll(uint32 mapid, Difficulty difficulty, b
stmt->setUInt8(1, uint8(difficulty));
trans->Append(stmt);
- stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_INSTANCE_BY_MAP_DIFF);
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_EXPIRED_INSTANCE_BY_MAP_DIFF);
stmt->setUInt16(0, uint16(mapid));
stmt->setUInt8(1, uint8(difficulty));
trans->Append(stmt);
- CharacterDatabase.CommitTransaction(trans);
-
- // calculate the next reset time
- uint32 diff = sWorld->getIntConfig(CONFIG_INSTANCE_RESET_TIME_HOUR) * HOUR;
+ stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_EXPIRE_CHAR_INSTANCE_BY_MAP_DIFF);
+ stmt->setUInt16(0, uint16(mapid));
+ stmt->setUInt8(1, uint8(difficulty));
+ trans->Append(stmt);
- uint32 period = uint32(((mapDiff->resetTime * sWorld->getRate(RATE_INSTANCE_RESET_TIME))/DAY) * DAY);
- if (period < DAY)
- period = DAY;
+ CharacterDatabase.CommitTransaction(trans);
- uint32 next_reset = uint32(((resetTime + MINUTE) / DAY * DAY) + period + diff);
+ // promote loaded binds to instances of the given map
+ for (InstanceSaveHashMap::iterator itr = m_instanceSaveById.begin(); itr != m_instanceSaveById.end();)
+ {
+ if (itr->second->GetMapId() == mapid && itr->second->GetDifficulty() == difficulty)
+ _ResetSave(itr);
+ else
+ ++itr;
+ }
SetResetTimeFor(mapid, difficulty, next_reset);
ScheduleReset(true, time_t(next_reset-3600), InstResetEvent(1, mapid, difficulty, 0));
diff --git a/src/server/game/Instances/InstanceSaveMgr.h b/src/server/game/Instances/InstanceSaveMgr.h
index e3d8175cbc4..d2b3237b3cf 100644
--- a/src/server/game/Instances/InstanceSaveMgr.h
+++ b/src/server/game/Instances/InstanceSaveMgr.h
@@ -190,6 +190,7 @@ class InstanceSaveManager
ResetTimeByMapDifficultyMap::const_iterator itr = m_resetTimeByMapDifficulty.find(MAKE_PAIR32(mapid, d));
return itr != m_resetTimeByMapDifficulty.end() ? itr->second : 0;
}
+ time_t GetSubsequentResetTime(uint32 mapid, Difficulty difficulty, time_t resetTime) const;
// Use this on startup when initializing reset times
void InitializeResetTimeFor(uint32 mapid, Difficulty d, time_t t)
@@ -210,6 +211,7 @@ class InstanceSaveManager
return m_resetTimeByMapDifficulty;
}
void ScheduleReset(bool add, time_t time, InstResetEvent event);
+ void ForceGlobalReset(uint32 mapId, Difficulty difficulty);
void Update();
diff --git a/src/server/game/Instances/InstanceScript.cpp b/src/server/game/Instances/InstanceScript.cpp
index 20f513323c1..1575b50098f 100644
--- a/src/server/game/Instances/InstanceScript.cpp
+++ b/src/server/game/Instances/InstanceScript.cpp
@@ -30,6 +30,12 @@
#include "WorldSession.h"
#include "Opcodes.h"
+BossBoundaryData::~BossBoundaryData()
+{
+ for (const_iterator it = begin(); it != end(); ++it)
+ delete it->Boundary;
+}
+
void InstanceScript::SaveToDB()
{
std::string data = GetSaveData();
@@ -98,9 +104,9 @@ void InstanceScript::SetHeaders(std::string const& dataHeaders)
void InstanceScript::LoadBossBoundaries(const BossBoundaryData& data)
{
- for (BossBoundaryEntry entry : data)
- if (entry.bossId < bosses.size())
- bosses[entry.bossId].boundary.insert(entry.boundary);
+ for (BossBoundaryEntry const& entry : data)
+ if (entry.BossId < bosses.size())
+ bosses[entry.BossId].boundary.insert(entry.Boundary);
}
void InstanceScript::LoadMinionData(const MinionData* data)
diff --git a/src/server/game/Instances/InstanceScript.h b/src/server/game/Instances/InstanceScript.h
index 8285eaa7346..ce83061e162 100644
--- a/src/server/game/Instances/InstanceScript.h
+++ b/src/server/game/Instances/InstanceScript.h
@@ -75,10 +75,23 @@ struct DoorData
struct BossBoundaryEntry
{
- uint32 const bossId;
- AreaBoundary const* const boundary;
+ uint32 BossId;
+ AreaBoundary const* Boundary;
+};
+
+struct BossBoundaryData
+{
+ typedef std::vector<BossBoundaryEntry> StorageType;
+ typedef StorageType::const_iterator const_iterator;
+
+ BossBoundaryData(std::initializer_list<BossBoundaryEntry> data) : _data(data) { }
+ ~BossBoundaryData();
+ const_iterator begin() const { return _data.begin(); }
+ const_iterator end() const { return _data.end(); }
+
+ private:
+ StorageType _data;
};
-typedef std::list<BossBoundaryEntry> BossBoundaryData;
struct MinionData
{
diff --git a/src/server/game/Loot/LootMgr.cpp b/src/server/game/Loot/LootMgr.cpp
index 530bcd2902c..19dc210ea5b 100644
--- a/src/server/game/Loot/LootMgr.cpp
+++ b/src/server/game/Loot/LootMgr.cpp
@@ -1580,8 +1580,8 @@ void LoadLootTemplates_Fishing()
uint32 count = LootTemplates_Fishing.LoadAndCollectLootIds(lootIdSet);
// remove real entries and check existence loot
- for (uint32 i = 1; i < sAreaStore.GetNumRows(); ++i)
- if (AreaTableEntry const* areaEntry = sAreaStore.LookupEntry(i))
+ for (uint32 i = 1; i < sAreaTableStore.GetNumRows(); ++i)
+ if (AreaTableEntry const* areaEntry = sAreaTableStore.LookupEntry(i))
if (lootIdSet.find(areaEntry->ID) != lootIdSet.end())
lootIdSet.erase(areaEntry->ID);
diff --git a/src/server/game/Maps/AreaBoundary.cpp b/src/server/game/Maps/AreaBoundary.cpp
index 837a9959041..e09b252179e 100644
--- a/src/server/game/Maps/AreaBoundary.cpp
+++ b/src/server/game/Maps/AreaBoundary.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015-2016 TrinityCore <http://www.trinitycore.org/>
+ * Copyright (C) 2008-2016 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
diff --git a/src/server/game/Maps/AreaBoundary.h b/src/server/game/Maps/AreaBoundary.h
index 24a00962359..a134b783ca6 100644
--- a/src/server/game/Maps/AreaBoundary.h
+++ b/src/server/game/Maps/AreaBoundary.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015-2016 TrinityCore <http://www.trinitycore.org/>
+ * Copyright (C) 2008-2016 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
@@ -32,15 +32,16 @@ class AreaBoundary
BOUNDARY_PARALLELOGRAM,
BOUNDARY_Z_RANGE,
};
+ virtual ~AreaBoundary() { }
BoundaryType GetBoundaryType() const { return m_boundaryType; }
bool IsWithinBoundary(const Position* pos) const { return (IsWithinBoundaryArea(pos) != m_isInvertedBoundary); }
struct DoublePosition : Position
{
double d_positionX, d_positionY, d_positionZ;
- DoublePosition(double x = 0, double y = 0, double z = 0, float o = 0)
- : Position((float)x, (float)y, (float)z, o), d_positionX(x), d_positionY(y), d_positionZ(z) { }
- DoublePosition(float x = 0, float y = 0, float z = 0, float o = 0)
+ DoublePosition(double x = 0.0, double y = 0.0, double z = 0.0, float o = 0.0f)
+ : Position(x, y, z, o), d_positionX(x), d_positionY(y), d_positionZ(z) { }
+ DoublePosition(float x, float y = 0.0f, float z = 0.0f, float o = 0.0f)
: Position(x, y, z, o), d_positionX(x), d_positionY(y), d_positionZ(z) { }
DoublePosition(const Position& pos)
: DoublePosition(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation()) { }
diff --git a/src/server/game/Maps/Map.cpp b/src/server/game/Maps/Map.cpp
index a2cb84359f2..ccc599b6de8 100644
--- a/src/server/game/Maps/Map.cpp
+++ b/src/server/game/Maps/Map.cpp
@@ -37,7 +37,7 @@
#include "VMapFactory.h"
u_map_magic MapMagic = { {'M','A','P','S'} };
-u_map_magic MapVersionMagic = { {'v','1','.','3'} };
+u_map_magic MapVersionMagic = { {'v','1','.','8'} };
u_map_magic MapAreaMagic = { {'A','R','E','A'} };
u_map_magic MapHeightMagic = { {'M','H','G','T'} };
u_map_magic MapLiquidMagic = { {'M','L','I','Q'} };
@@ -1644,13 +1644,15 @@ GridMap::GridMap()
_flags = 0;
// Area data
_gridArea = 0;
- _areaMap = NULL;
+ _areaMap = nullptr;
// Height level data
_gridHeight = INVALID_HEIGHT;
_gridGetHeight = &GridMap::getHeightFromFlat;
_gridIntHeightMultiplier = 0;
- m_V9 = NULL;
- m_V8 = NULL;
+ m_V9 = nullptr;
+ m_V8 = nullptr;
+ _maxHeight = nullptr;
+ _minHeight = nullptr;
// Liquid data
_liquidType = 0;
_liquidOffX = 0;
@@ -1658,9 +1660,9 @@ GridMap::GridMap()
_liquidWidth = 0;
_liquidHeight = 0;
_liquidLevel = INVALID_HEIGHT;
- _liquidEntry = NULL;
- _liquidFlags = NULL;
- _liquidMap = NULL;
+ _liquidEntry = nullptr;
+ _liquidFlags = nullptr;
+ _liquidMap = nullptr;
}
GridMap::~GridMap()
@@ -1723,15 +1725,19 @@ void GridMap::unloadData()
delete[] _areaMap;
delete[] m_V9;
delete[] m_V8;
+ delete[] _maxHeight;
+ delete[] _minHeight;
delete[] _liquidEntry;
delete[] _liquidFlags;
delete[] _liquidMap;
- _areaMap = NULL;
- m_V9 = NULL;
- m_V8 = NULL;
- _liquidEntry = NULL;
- _liquidFlags = NULL;
- _liquidMap = NULL;
+ _areaMap = nullptr;
+ m_V9 = nullptr;
+ m_V8 = nullptr;
+ _maxHeight = nullptr;
+ _minHeight = nullptr;
+ _liquidEntry = nullptr;
+ _liquidFlags = nullptr;
+ _liquidMap = nullptr;
_gridGetHeight = &GridMap::getHeightFromFlat;
}
@@ -1746,7 +1752,7 @@ bool GridMap::loadAreaData(FILE* in, uint32 offset, uint32 /*size*/)
_gridArea = header.gridArea;
if (!(header.flags & MAP_AREA_NO_AREA))
{
- _areaMap = new uint16 [16*16];
+ _areaMap = new uint16[16 * 16];
if (fread(_areaMap, sizeof(uint16), 16*16, in) != 16*16)
return false;
}
@@ -1796,6 +1802,16 @@ bool GridMap::loadHeightData(FILE* in, uint32 offset, uint32 /*size*/)
}
else
_gridGetHeight = &GridMap::getHeightFromFlat;
+
+ if (header.flags & MAP_HEIGHT_HAS_FLIGHT_BOUNDS)
+ {
+ _maxHeight = new int16[3 * 3];
+ _minHeight = new int16[3 * 3];
+ if (fread(_maxHeight, sizeof(int16), 3 * 3, in) != 3 * 3 ||
+ fread(_minHeight, sizeof(int16), 3 * 3, in) != 3 * 3)
+ return false;
+ }
+
return true;
}
@@ -2066,6 +2082,66 @@ float GridMap::getHeightFromUint16(float x, float y) const
return (float)((a * x) + (b * y) + c)*_gridIntHeightMultiplier + _gridHeight;
}
+float GridMap::getMinHeight(float x, float y) const
+{
+ if (!_minHeight)
+ return -500.0f;
+
+ static uint32 const indices[] =
+ {
+ 3, 0, 4,
+ 0, 1, 4,
+ 1, 2, 4,
+ 2, 5, 4,
+ 5, 8, 4,
+ 8, 7, 4,
+ 7, 6, 4,
+ 6, 3, 4
+ };
+
+ static float const boundGridCoords[] =
+ {
+ 0.0f, 0.0f,
+ 0.0f, -266.66666f,
+ 0.0f, -533.33331f,
+ -266.66666f, 0.0f,
+ -266.66666f, -266.66666f,
+ -266.66666f, -533.33331f,
+ -533.33331f, 0.0f,
+ -533.33331f, -266.66666f,
+ -533.33331f, -533.33331f
+ };
+
+ Cell cell(x, y);
+ float gx = x - (int32(cell.GridX()) - CENTER_GRID_ID + 1) * SIZE_OF_GRIDS;
+ float gy = y - (int32(cell.GridY()) - CENTER_GRID_ID + 1) * SIZE_OF_GRIDS;
+
+ uint32 quarterIndex = 0;
+ if (cell.CellY() < MAX_NUMBER_OF_CELLS / 2)
+ {
+ if (cell.CellX() < MAX_NUMBER_OF_CELLS / 2)
+ {
+ quarterIndex = 4 + (gy > gx);
+ }
+ else
+ quarterIndex = 2 + ((-SIZE_OF_GRIDS - gx) > gy);
+ }
+ else if (cell.CellX() < MAX_NUMBER_OF_CELLS / 2)
+ {
+ quarterIndex = 6 + ((-SIZE_OF_GRIDS - gx) <= gy);
+ }
+ else
+ quarterIndex = gx > gy;
+
+ quarterIndex *= 3;
+
+ return G3D::Plane(
+ G3D::Vector3(boundGridCoords[indices[quarterIndex + 0] * 2 + 0], boundGridCoords[indices[quarterIndex + 0] * 2 + 1], _minHeight[indices[quarterIndex + 0]]),
+ G3D::Vector3(boundGridCoords[indices[quarterIndex + 1] * 2 + 0], boundGridCoords[indices[quarterIndex + 1] * 2 + 1], _minHeight[indices[quarterIndex + 1]]),
+ G3D::Vector3(boundGridCoords[indices[quarterIndex + 2] * 2 + 0], boundGridCoords[indices[quarterIndex + 2] * 2 + 1], _minHeight[indices[quarterIndex + 2]])
+ ).distance(G3D::Vector3(gx, gy, 0.0f));
+}
+
float GridMap::getLiquidLevel(float x, float y) const
{
if (!_liquidMap)
@@ -2125,12 +2201,12 @@ inline ZLiquidStatus GridMap::getLiquidStatus(float x, float y, float z, uint8 R
uint32 liqTypeIdx = liquidEntry->Type;
if (entry < 21)
{
- if (AreaTableEntry const* area = GetAreaEntryByAreaFlagAndMap(getArea(x, y), MAPID_INVALID))
+ if (AreaTableEntry const* area = sAreaTableStore.LookupEntry(getArea(x, y)))
{
uint32 overrideLiquid = area->LiquidTypeOverride[liquidEntry->Type];
if (!overrideLiquid && area->zone)
{
- area = GetAreaEntryByAreaID(area->zone);
+ area = sAreaTableStore.LookupEntry(area->zone);
if (area)
overrideLiquid = area->LiquidTypeOverride[liquidEntry->Type];
}
@@ -2266,6 +2342,14 @@ float Map::GetHeight(float x, float y, float z, bool checkVMap /*= true*/, float
return mapHeight; // explicitly use map data
}
+float Map::GetMinHeight(float x, float y) const
+{
+ if (GridMap const* grid = const_cast<Map*>(this)->GetGrid(x, y))
+ return grid->getMinHeight(x, y);
+
+ return -500.0f;
+}
+
inline bool IsOutdoorWMO(uint32 mogpFlags, int32 /*adtId*/, int32 /*rootId*/, int32 /*groupId*/, WMOAreaTableEntry const* wmoEntry, AreaTableEntry const* atEntry)
{
bool outdoor = true;
@@ -2304,7 +2388,7 @@ bool Map::IsOutdoors(float x, float y, float z) const
if (wmoEntry)
{
TC_LOG_DEBUG("maps", "Got WMOAreaTableEntry! flag %u, areaid %u", wmoEntry->Flags, wmoEntry->areaId);
- atEntry = GetAreaEntryByAreaID(wmoEntry->areaId);
+ atEntry = sAreaTableStore.LookupEntry(wmoEntry->areaId);
}
return IsOutdoorWMO(mogpFlags, adtId, rootId, groupId, wmoEntry, atEntry);
}
@@ -2328,7 +2412,7 @@ bool Map::GetAreaInfo(float x, float y, float z, uint32 &flags, int32 &adtId, in
return false;
}
-uint16 Map::GetAreaFlag(float x, float y, float z, bool *isOutdoors) const
+uint32 Map::GetAreaId(float x, float y, float z, bool *isOutdoors) const
{
uint32 mogpFlags;
int32 adtId, rootId, groupId;
@@ -2341,20 +2425,21 @@ uint16 Map::GetAreaFlag(float x, float y, float z, bool *isOutdoors) const
haveAreaInfo = true;
wmoEntry = GetWMOAreaTableEntryByTripple(rootId, adtId, groupId);
if (wmoEntry)
- atEntry = GetAreaEntryByAreaID(wmoEntry->areaId);
+ atEntry = sAreaTableStore.LookupEntry(wmoEntry->areaId);
}
- uint16 areaflag;
+ uint32 areaId = 0;
if (atEntry)
- areaflag = atEntry->exploreFlag;
+ areaId = atEntry->ID;
else
{
if (GridMap* gmap = const_cast<Map*>(this)->GetGrid(x, y))
- areaflag = gmap->getArea(x, y);
+ areaId = gmap->getArea(x, y);
+
// this used while not all *.map files generated (instances)
- else
- areaflag = GetAreaFlagByMapId(i_mapEntry->MapID);
+ if (!areaId)
+ areaId = i_mapEntry->linked_zone;
}
if (isOutdoors)
@@ -2364,8 +2449,31 @@ uint16 Map::GetAreaFlag(float x, float y, float z, bool *isOutdoors) const
else
*isOutdoors = true;
}
- return areaflag;
- }
+ return areaId;
+}
+
+uint32 Map::GetAreaId(float x, float y, float z) const
+{
+ return GetAreaId(x, y, z, nullptr);
+}
+
+uint32 Map::GetZoneId(float x, float y, float z) const
+{
+ uint32 areaId = GetAreaId(x, y, z);
+ if (AreaTableEntry const* area = sAreaTableStore.LookupEntry(areaId))
+ if (area->zone)
+ return area->zone;
+
+ return areaId;
+}
+
+void Map::GetZoneAndAreaId(uint32& zoneid, uint32& areaid, float x, float y, float z) const
+{
+ areaid = zoneid = GetAreaId(x, y, z);
+ if (AreaTableEntry const* area = sAreaTableStore.LookupEntry(areaid))
+ if (area->zone)
+ zoneid = area->zone;
+}
uint8 Map::GetTerrainType(float x, float y) const
{
@@ -2401,12 +2509,12 @@ ZLiquidStatus Map::getLiquidStatus(float x, float y, float z, uint8 ReqLiquidTyp
if (liquid_type && liquid_type < 21)
{
- if (AreaTableEntry const* area = GetAreaEntryByAreaFlagAndMap(GetAreaFlag(x, y, z), GetId()))
+ if (AreaTableEntry const* area = sAreaTableStore.LookupEntry(GetAreaId(x, y, z)))
{
uint32 overrideLiquid = area->LiquidTypeOverride[liquidFlagType];
if (!overrideLiquid && area->zone)
{
- area = GetAreaEntryByAreaID(area->zone);
+ area = sAreaTableStore.LookupEntry(area->zone);
if (area)
overrideLiquid = area->LiquidTypeOverride[liquidFlagType];
}
@@ -2468,34 +2576,6 @@ float Map::GetWaterLevel(float x, float y) const
return 0;
}
-uint32 Map::GetAreaIdByAreaFlag(uint16 areaflag, uint32 map_id)
-{
- AreaTableEntry const* entry = GetAreaEntryByAreaFlagAndMap(areaflag, map_id);
-
- if (entry)
- return entry->ID;
- else
- return 0;
-}
-
-uint32 Map::GetZoneIdByAreaFlag(uint16 areaflag, uint32 map_id)
-{
- AreaTableEntry const* entry = GetAreaEntryByAreaFlagAndMap(areaflag, map_id);
-
- if (entry)
- return (entry->zone != 0) ? entry->zone : entry->ID;
- else
- return 0;
-}
-
-void Map::GetZoneAndAreaIdByAreaFlag(uint32& zoneid, uint32& areaid, uint16 areaflag, uint32 map_id)
-{
- AreaTableEntry const* entry = GetAreaEntryByAreaFlagAndMap(areaflag, map_id);
-
- areaid = entry ? entry->ID : 0;
- zoneid = entry ? ((entry->zone != 0) ? entry->zone : entry->ID) : 0;
-}
-
bool Map::isInLineOfSight(float x1, float y1, float z1, float x2, float y2, float z2, uint32 phasemask) const
{
return VMAP::VMapFactory::createOrGetVMapManager()->isInLineOfSight(GetId(), x1, y1, z1, x2, y2, z2)
@@ -3178,22 +3258,37 @@ bool InstanceMap::Reset(uint8 method)
}
else
{
+ bool doUnload = true;
if (method == INSTANCE_RESET_GLOBAL)
+ {
// set the homebind timer for players inside (1 minute)
for (MapRefManager::iterator itr = m_mapRefManager.begin(); itr != m_mapRefManager.end(); ++itr)
- itr->GetSource()->m_InstanceValid = false;
+ {
+ InstancePlayerBind* bind = itr->GetSource()->GetBoundInstance(GetId(), GetDifficulty());
+ if (bind && bind->extendState && bind->save->GetInstanceId() == GetInstanceId())
+ doUnload = false;
+ else
+ itr->GetSource()->m_InstanceValid = false;
+ }
+
+ if (doUnload && HasPermBoundPlayers()) // check if any unloaded players have a nonexpired save to this
+ doUnload = false;
+ }
- // the unload timer is not started
- // instead the map will unload immediately after the players have left
- m_unloadWhenEmpty = true;
- m_resetAfterUnload = true;
+ if (doUnload)
+ {
+ // the unload timer is not started
+ // instead the map will unload immediately after the players have left
+ m_unloadWhenEmpty = true;
+ m_resetAfterUnload = true;
+ }
}
}
else
{
// unloaded at next update
m_unloadTimer = MIN_UNLOAD_DELAY;
- m_resetAfterUnload = true;
+ m_resetAfterUnload = !(method == INSTANCE_RESET_GLOBAL && HasPermBoundPlayers());
}
return m_mapRefManager.isEmpty();
@@ -3274,6 +3369,13 @@ MapDifficulty const* Map::GetMapDifficulty() const
return GetMapDifficultyData(GetId(), GetDifficulty());
}
+bool InstanceMap::HasPermBoundPlayers() const
+{
+ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PERM_BIND_BY_INSTANCE);
+ stmt->setUInt16(0,GetInstanceId());
+ return !!CharacterDatabase.Query(stmt);
+}
+
uint32 InstanceMap::GetMaxPlayers() const
{
MapDifficulty const* mapDiff = GetMapDifficulty();
diff --git a/src/server/game/Maps/Map.h b/src/server/game/Maps/Map.h
index 01db38d9c30..bc2bf72f271 100644
--- a/src/server/game/Maps/Map.h
+++ b/src/server/game/Maps/Map.h
@@ -98,9 +98,10 @@ struct map_areaHeader
uint16 gridArea;
};
-#define MAP_HEIGHT_NO_HEIGHT 0x0001
-#define MAP_HEIGHT_AS_INT16 0x0002
-#define MAP_HEIGHT_AS_INT8 0x0004
+#define MAP_HEIGHT_NO_HEIGHT 0x0001
+#define MAP_HEIGHT_AS_INT16 0x0002
+#define MAP_HEIGHT_AS_INT8 0x0004
+#define MAP_HEIGHT_HAS_FLIGHT_BOUNDS 0x0008
struct map_heightHeader
{
@@ -166,6 +167,8 @@ class GridMap
uint16* m_uint16_V8;
uint8* m_uint8_V8;
};
+ int16* _maxHeight;
+ int16* _minHeight;
// Height level data
float _gridHeight;
float _gridIntHeightMultiplier;
@@ -206,6 +209,7 @@ public:
uint16 getArea(float x, float y) const;
inline float getHeight(float x, float y) const {return (this->*_gridGetHeight)(x, y);}
+ float getMinHeight(float x, float y) const;
float getLiquidLevel(float x, float y) const;
uint8 getTerrainType(float x, float y) const;
ZLiquidStatus getLiquidStatus(float x, float y, float z, uint8 ReqLiquidType, LiquidData* data = 0);
@@ -326,11 +330,15 @@ class Map : public GridRefManager<NGridType>
// some calls like isInWater should not use vmaps due to processor power
// can return INVALID_HEIGHT if under z+2 z coord not found height
float GetHeight(float x, float y, float z, bool checkVMap = true, float maxSearchDist = DEFAULT_HEIGHT_SEARCH) const;
+ float GetMinHeight(float x, float y) const;
ZLiquidStatus getLiquidStatus(float x, float y, float z, uint8 ReqLiquidType, LiquidData* data = nullptr) const;
- uint16 GetAreaFlag(float x, float y, float z, bool *isOutdoors=nullptr) const;
- bool GetAreaInfo(float x, float y, float z, uint32 &mogpflags, int32 &adtId, int32 &rootId, int32 &groupId) const;
+ uint32 GetAreaId(float x, float y, float z, bool *isOutdoors) const;
+ bool GetAreaInfo(float x, float y, float z, uint32& mogpflags, int32& adtId, int32& rootId, int32& groupId) const;
+ uint32 GetAreaId(float x, float y, float z) const;
+ uint32 GetZoneId(float x, float y, float z) const;
+ void GetZoneAndAreaId(uint32& zoneid, uint32& areaid, float x, float y, float z) const;
bool IsOutdoors(float x, float y, float z) const;
@@ -339,25 +347,6 @@ class Map : public GridRefManager<NGridType>
bool IsInWater(float x, float y, float z, LiquidData* data = nullptr) const;
bool IsUnderWater(float x, float y, float z) const;
- static uint32 GetAreaIdByAreaFlag(uint16 areaflag, uint32 map_id);
- static uint32 GetZoneIdByAreaFlag(uint16 areaflag, uint32 map_id);
- static void GetZoneAndAreaIdByAreaFlag(uint32& zoneid, uint32& areaid, uint16 areaflag, uint32 map_id);
-
- uint32 GetAreaId(float x, float y, float z) const
- {
- return GetAreaIdByAreaFlag(GetAreaFlag(x, y, z), GetId());
- }
-
- uint32 GetZoneId(float x, float y, float z) const
- {
- return GetZoneIdByAreaFlag(GetAreaFlag(x, y, z), GetId());
- }
-
- void GetZoneAndAreaId(uint32& zoneid, uint32& areaid, float x, float y, float z) const
- {
- GetZoneAndAreaIdByAreaFlag(zoneid, areaid, GetAreaFlag(x, y, z), GetId());
- }
-
void MoveAllCreaturesInMoveList();
void MoveAllGameObjectsInMoveList();
void MoveAllDynamicObjectsInMoveList();
@@ -782,6 +771,9 @@ class InstanceMap : public Map
void SendResetWarnings(uint32 timeLeft) const;
void SetResetSchedule(bool on);
+ /* this checks if any players have a permanent bind (included reactivatable expired binds) to the instance ID
+ it needs a DB query, so use sparingly */
+ bool HasPermBoundPlayers() const;
uint32 GetMaxPlayers() const;
uint32 GetMaxResetDelay() const;
diff --git a/src/server/game/Maps/MapManager.h b/src/server/game/Maps/MapManager.h
index 51bb418bdf5..7f9621593d4 100644
--- a/src/server/game/Maps/MapManager.h
+++ b/src/server/game/Maps/MapManager.h
@@ -42,22 +42,20 @@ class MapManager
Map* CreateMap(uint32 mapId, Player* player, uint32 loginInstanceId=0);
Map* FindMap(uint32 mapId, uint32 instanceId) const;
- uint16 GetAreaFlag(uint32 mapid, float x, float y, float z) const
- {
- Map const* m = const_cast<MapManager*>(this)->CreateBaseMap(mapid);
- return m->GetAreaFlag(x, y, z);
- }
uint32 GetAreaId(uint32 mapid, float x, float y, float z) const
{
- return Map::GetAreaIdByAreaFlag(GetAreaFlag(mapid, x, y, z), mapid);
+ Map const* m = const_cast<MapManager*>(this)->CreateBaseMap(mapid);
+ return m->GetAreaId(x, y, z);
}
uint32 GetZoneId(uint32 mapid, float x, float y, float z) const
{
- return Map::GetZoneIdByAreaFlag(GetAreaFlag(mapid, x, y, z), mapid);
+ Map const* m = const_cast<MapManager*>(this)->CreateBaseMap(mapid);
+ return m->GetZoneId(x, y, z);
}
void GetZoneAndAreaId(uint32& zoneid, uint32& areaid, uint32 mapid, float x, float y, float z)
{
- Map::GetZoneAndAreaIdByAreaFlag(zoneid, areaid, GetAreaFlag(mapid, x, y, z), mapid);
+ Map const* m = const_cast<MapManager*>(this)->CreateBaseMap(mapid);
+ m->GetZoneAndAreaId(zoneid, areaid, x, y, z);
}
void Initialize(void);
diff --git a/src/server/game/Miscellaneous/Formulas.h b/src/server/game/Miscellaneous/Formulas.h
index 52b80ce92ab..b5c6f37b14e 100644
--- a/src/server/game/Miscellaneous/Formulas.h
+++ b/src/server/game/Miscellaneous/Formulas.h
@@ -185,6 +185,9 @@ namespace Trinity
}
xpMod *= isBattleGround ? sWorld->getRate(RATE_XP_BG_KILL) : sWorld->getRate(RATE_XP_KILL);
+ if (creature && creature->m_PlayerDamageReq) // if players dealt less than 50% of the damage and were credited anyway (due to CREATURE_FLAG_EXTRA_NO_PLAYER_DAMAGE_REQ), scale XP gained appropriately (linear scaling)
+ xpMod *= 1.0f - 2.0f*creature->m_PlayerDamageReq / creature->GetMaxHealth();
+
gain = uint32(gain * xpMod);
}
diff --git a/src/server/game/Movement/MotionMaster.cpp b/src/server/game/Movement/MotionMaster.cpp
index 3fcae1398f9..05948b987ad 100644
--- a/src/server/game/Movement/MotionMaster.cpp
+++ b/src/server/game/Movement/MotionMaster.cpp
@@ -366,10 +366,10 @@ void MotionMaster::MoveJumpTo(float angle, float speedXY, float speedZ)
float moveTimeHalf = speedZ / Movement::gravity;
float dist = 2 * moveTimeHalf * speedXY;
_owner->GetClosePoint(x, y, z, _owner->GetObjectSize(), dist, angle);
- MoveJump(x, y, z, speedXY, speedZ);
+ MoveJump(x, y, z, 0.0f, speedXY, speedZ);
}
-void MotionMaster::MoveJump(float x, float y, float z, float speedXY, float speedZ, uint32 id)
+void MotionMaster::MoveJump(float x, float y, float z, float o, float speedXY, float speedZ, uint32 id, bool hasOrientation /* = false*/)
{
TC_LOG_DEBUG("misc", "Unit (GUID: %u) jump to point (X: %f Y: %f Z: %f)", _owner->GetGUID().GetCounter(), x, y, z);
if (speedXY <= 0.1f)
@@ -382,6 +382,8 @@ void MotionMaster::MoveJump(float x, float y, float z, float speedXY, float spee
init.MoveTo(x, y, z, false);
init.SetParabolic(max_height, 0);
init.SetVelocity(speedXY);
+ if (hasOrientation)
+ init.SetFacing(o);
init.Launch();
Mutate(new EffectMovementGenerator(id), MOTION_SLOT_CONTROLLED);
}
diff --git a/src/server/game/Movement/MotionMaster.h b/src/server/game/Movement/MotionMaster.h
index ce0f1f7836e..76ae12986d5 100644
--- a/src/server/game/Movement/MotionMaster.h
+++ b/src/server/game/Movement/MotionMaster.h
@@ -181,9 +181,11 @@ class MotionMaster //: private std::stack<MovementGenerator *>
void MoveCharge(PathGenerator const& path, float speed = SPEED_CHARGE);
void MoveKnockbackFrom(float srcX, float srcY, float speedXY, float speedZ);
void MoveJumpTo(float angle, float speedXY, float speedZ);
- void MoveJump(Position const& pos, float speedXY, float speedZ, uint32 id = EVENT_JUMP)
- { MoveJump(pos.m_positionX, pos.m_positionY, pos.m_positionZ, speedXY, speedZ, id); }
- void MoveJump(float x, float y, float z, float speedXY, float speedZ, uint32 id = EVENT_JUMP);
+ void MoveJump(Position const& pos, float speedXY, float speedZ, uint32 id = EVENT_JUMP, bool hasOrientation = false)
+ {
+ MoveJump(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation(), speedXY, speedZ, id, hasOrientation);
+ }
+ void MoveJump(float x, float y, float z, float o, float speedXY, float speedZ, uint32 id = EVENT_JUMP, bool hasOrientation = false);
void MoveCirclePath(float x, float y, float z, float radius, bool clockwise, uint8 stepCount);
void MoveSmoothPath(uint32 pointId, G3D::Vector3 const* pathPoints, size_t pathSize, bool walk);
void MoveFall(uint32 id = 0);
diff --git a/src/server/game/OutdoorPvP/OutdoorPvP.cpp b/src/server/game/OutdoorPvP/OutdoorPvP.cpp
index 868cba9a5b9..d329ab27de9 100644
--- a/src/server/game/OutdoorPvP/OutdoorPvP.cpp
+++ b/src/server/game/OutdoorPvP/OutdoorPvP.cpp
@@ -687,7 +687,7 @@ void OutdoorPvP::BroadcastWorker(Worker& _worker, uint32 zoneId)
void OutdoorPvP::SetMapFromZone(uint32 zone)
{
- AreaTableEntry const* areaTable = GetAreaEntryByAreaID(zone);
+ AreaTableEntry const* areaTable = sAreaTableStore.LookupEntry(zone);
ASSERT(areaTable);
Map* map = sMapMgr->CreateBaseMap(areaTable->mapid);
ASSERT(!map->Instanceable());
diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp
index 3fa16cf4517..4b26ba96c9b 100644
--- a/src/server/game/Scripting/ScriptMgr.cpp
+++ b/src/server/game/Scripting/ScriptMgr.cpp
@@ -37,7 +37,6 @@
// namespace
// {
- UnusedScriptContainer UnusedScripts;
UnusedScriptNamesContainer UnusedScriptNames;
// }
@@ -107,8 +106,9 @@ class ScriptRegistry
// The actual list of scripts. This will be accessed concurrently, so it must not be modified
// after server startup.
static ScriptMap ScriptPointerList;
+ static std::vector<TScript*> Scripts;
- static void AddScript(TScript* const script)
+ static void AddScript(TScript* const script, bool addToDeleteContainer = true)
{
ASSERT(script);
@@ -126,6 +126,8 @@ class ScriptRegistry
}
AddScript(is_script_database_bound<TScript>{}, script);
+ if (addToDeleteContainer)
+ Scripts.push_back(script);
}
// Gets a script by its ID (assigned by ObjectMgr).
@@ -186,11 +188,6 @@ class ScriptRegistry
{
// The script uses a script name from database, but isn't assigned to anything.
TC_LOG_ERROR("sql.sql", "Script named '%s' does not have a script name assigned in database.", script->GetName().c_str());
-
- // Avoid calling "delete script;" because we are currently in the script constructor
- // In a valid scenario this will not happen because every script has a name assigned in the database
- UnusedScripts.push_back(script);
- return;
}
}
@@ -210,6 +207,7 @@ class ScriptRegistry
#define SCR_REG_MAP(T) ScriptRegistry<T>::ScriptMap
#define SCR_REG_ITR(T) ScriptRegistry<T>::ScriptMapIterator
#define SCR_REG_LST(T) ScriptRegistry<T>::ScriptPointerList
+#define SCR_REG_VEC(T) ScriptRegistry<T>::Scripts
// Utility macros for looping over scripts.
#define FOR_SCRIPTS(T, C, E) \
@@ -266,17 +264,15 @@ void ScriptMgr::Initialize()
}
#endif
- UnloadUnusedScripts();
-
TC_LOG_INFO("server.loading", ">> Loaded %u C++ scripts in %u ms", GetScriptCount(), GetMSTimeDiffToNow(oldMSTime));
}
void ScriptMgr::Unload()
{
#define SCR_CLEAR(T) \
- for (SCR_REG_ITR(T) itr = SCR_REG_LST(T).begin(); itr != SCR_REG_LST(T).end(); ++itr) \
- delete itr->second; \
- SCR_REG_LST(T).clear();
+ for (T* scr : SCR_REG_VEC(T)) \
+ delete scr; \
+ SCR_REG_VEC(T).clear();
// Clear scripts for every script type.
SCR_CLEAR(SpellScriptLoader);
@@ -308,19 +304,10 @@ void ScriptMgr::Unload()
#undef SCR_CLEAR
- UnloadUnusedScripts();
-
delete[] SpellSummary;
delete[] UnitAI::AISpellInfo;
}
-void ScriptMgr::UnloadUnusedScripts()
-{
- for (size_t i = 0; i < UnusedScripts.size(); ++i)
- delete UnusedScripts[i];
- UnusedScripts.clear();
-}
-
void ScriptMgr::LoadDatabase()
{
sScriptSystemMgr->LoadScriptWaypoints();
@@ -1368,9 +1355,9 @@ void ScriptMgr::OnPlayerSave(Player* player)
FOREACH_SCRIPT(PlayerScript)->OnSave(player);
}
-void ScriptMgr::OnPlayerBindToInstance(Player* player, Difficulty difficulty, uint32 mapid, bool permanent)
+void ScriptMgr::OnPlayerBindToInstance(Player* player, Difficulty difficulty, uint32 mapid, bool permanent, uint8 extendState)
{
- FOREACH_SCRIPT(PlayerScript)->OnBindToInstance(player, difficulty, mapid, permanent);
+ FOREACH_SCRIPT(PlayerScript)->OnBindToInstance(player, difficulty, mapid, permanent, extendState);
}
void ScriptMgr::OnPlayerUpdateZone(Player* player, uint32 newZone, uint32 newArea)
@@ -1555,8 +1542,7 @@ FormulaScript::FormulaScript(const char* name)
UnitScript::UnitScript(const char* name, bool addToScripts)
: ScriptObject(name)
{
- if (addToScripts)
- ScriptRegistry<UnitScript>::AddScript(this);
+ ScriptRegistry<UnitScript>::AddScript(this, addToScripts);
}
WorldMapScript::WorldMapScript(const char* name, uint32 mapId)
@@ -1696,6 +1682,7 @@ GroupScript::GroupScript(const char* name)
// Instantiate static members of ScriptRegistry.
template<class TScript> std::map<uint32, TScript*> ScriptRegistry<TScript>::ScriptPointerList;
+template<class TScript> std::vector<TScript*> ScriptRegistry<TScript>::Scripts;
template<class TScript> uint32 ScriptRegistry<TScript>::_scriptIdCounter = 0;
// Specialize for each script type class like so:
diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h
index 5dfc0be688a..8a00305b4da 100644
--- a/src/server/game/Scripting/ScriptMgr.h
+++ b/src/server/game/Scripting/ScriptMgr.h
@@ -734,7 +734,7 @@ class PlayerScript : public UnitScript
virtual void OnSave(Player* /*player*/) { }
// Called when a player is bound to an instance
- virtual void OnBindToInstance(Player* /*player*/, Difficulty /*difficulty*/, uint32 /*mapId*/, bool /*permanent*/) { }
+ virtual void OnBindToInstance(Player* /*player*/, Difficulty /*difficulty*/, uint32 /*mapId*/, bool /*permanent*/, uint8 /*extendState*/) { }
// Called when a player switches to a new zone
virtual void OnUpdateZone(Player* /*player*/, uint32 /*newZone*/, uint32 /*newArea*/) { }
@@ -843,10 +843,7 @@ class GroupScript : public ScriptObject
// namespace
// {
- typedef std::vector<ScriptObject*> UnusedScriptContainer;
typedef std::list<std::string> UnusedScriptNamesContainer;
-
- extern UnusedScriptContainer UnusedScripts;
extern UnusedScriptNamesContainer UnusedScriptNames;
// }
@@ -878,7 +875,6 @@ class ScriptMgr
public: /* Unloading */
void Unload();
- void UnloadUnusedScripts();
public: /* SpellScriptLoader */
@@ -1054,7 +1050,7 @@ class ScriptMgr
void OnPlayerDelete(ObjectGuid guid, uint32 accountId);
void OnPlayerFailedDelete(ObjectGuid guid, uint32 accountId);
void OnPlayerSave(Player* player);
- void OnPlayerBindToInstance(Player* player, Difficulty difficulty, uint32 mapid, bool permanent);
+ void OnPlayerBindToInstance(Player* player, Difficulty difficulty, uint32 mapid, bool permanent, uint8 extendState);
void OnPlayerUpdateZone(Player* player, uint32 newZone, uint32 newArea);
void OnQuestStatusChange(Player* player, uint32 questId, QuestStatus status);
diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.cpp b/src/server/game/Spells/Auras/SpellAuraEffects.cpp
index a4e7f61d83c..207908c6d51 100644
--- a/src/server/game/Spells/Auras/SpellAuraEffects.cpp
+++ b/src/server/game/Spells/Auras/SpellAuraEffects.cpp
@@ -1103,18 +1103,11 @@ void AuraEffect::HandleShapeshiftBoosts(Unit* target, bool apply) const
if (apply)
{
- // Remove cooldown of spells triggered on stance change - they may share cooldown with stance spell
if (spellId)
- {
- target->GetSpellHistory()->ResetCooldown(spellId);
target->CastSpell(target, spellId, true, NULL, this);
- }
if (spellId2)
- {
- target->GetSpellHistory()->ResetCooldown(spellId2);
target->CastSpell(target, spellId2, true, NULL, this);
- }
if (target->GetTypeId() == TYPEID_PLAYER)
{
@@ -4690,11 +4683,6 @@ void AuraEffect::HandleAuraDummy(AuraApplication const* aurApp, uint8 mode, bool
if (target->GetTypeId() == TYPEID_PLAYER)
target->ToPlayer()->RemoveAmmo(); // not use ammo and not allow use
break;
- case 52916: // Honor Among Thieves
- if (target->GetTypeId() == TYPEID_PLAYER)
- if (Unit* spellTarget = ObjectAccessor::GetUnit(*target, target->ToPlayer()->GetComboTarget()))
- target->CastSpell(spellTarget, 51699, true);
- break;
case 71563:
if (Aura* newAura = target->AddAura(71564, target))
newAura->SetStackAmount(newAura->GetSpellInfo()->StackAmount);
@@ -5626,25 +5614,6 @@ void AuraEffect::HandlePeriodicTriggerSpellAuraTick(Unit* target, Unit* caster)
target->RemoveAurasDueToSpell(28820);
return;
}
- // Totemic Mastery (Skyshatter Regalia (Shaman Tier 6) - bonus)
- case 38443:
- {
- bool all = true;
- for (int i = SUMMON_SLOT_TOTEM; i < MAX_TOTEM_SLOT; ++i)
- {
- if (!target->m_SummonSlot[i])
- {
- all = false;
- break;
- }
- }
-
- if (all)
- target->CastSpell(target, 38437, true, NULL, this);
- else
- target->RemoveAurasDueToSpell(38437);
- return;
- }
}
break;
}
diff --git a/src/server/game/Spells/Auras/SpellAuras.cpp b/src/server/game/Spells/Auras/SpellAuras.cpp
index 1b7e0f1ea0d..1ca5df6b327 100644
--- a/src/server/game/Spells/Auras/SpellAuras.cpp
+++ b/src/server/game/Spells/Auras/SpellAuras.cpp
@@ -1487,7 +1487,7 @@ void Aura::HandleAuraSpecificMods(AuraApplication const* aurApp, Unit* caster, b
{
// This additional check is needed to add a minimal delay before cooldown in in effect
// to allow all bubbles broken by a single damage source proc mana return
- if (caster->GetSpellHistory()->GetRemainingCooldown(aura->GetSpellInfo()) <= 11)
+ if (caster->GetSpellHistory()->GetRemainingCooldown(aura->GetSpellInfo()) <= 11 * IN_MILLISECONDS)
break;
}
else // and add if needed
diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp
index 84bff4a69d6..23e2f144ff2 100644
--- a/src/server/game/Spells/Spell.cpp
+++ b/src/server/game/Spells/Spell.cpp
@@ -2429,7 +2429,7 @@ void Spell::DoAllEffectOnTarget(TargetInfo* target)
if (missInfo != SPELL_MISS_EVADE && !m_caster->IsFriendlyTo(unit) && (!m_spellInfo->IsPositive() || m_spellInfo->HasEffect(SPELL_EFFECT_DISPEL)))
{
- m_caster->CombatStart(unit, !m_spellInfo->HasAttribute(SPELL_ATTR3_NO_INITIAL_AGGRO));
+ m_caster->CombatStart(unit, m_spellInfo->HasInitialAggro());
if (!unit->IsStandState())
unit->SetStandState(UNIT_STAND_STATE_STAND);
@@ -2535,7 +2535,7 @@ SpellMissInfo Spell::DoSpellHitOnUnit(Unit* unit, uint32 effectMask, bool scaleA
if (m_caster->GetTypeId() == TYPEID_PLAYER)
m_caster->ToPlayer()->UpdatePvP(true);
}
- if (unit->IsInCombat() && !m_spellInfo->HasAttribute(SPELL_ATTR3_NO_INITIAL_AGGRO))
+ if (unit->IsInCombat() && m_spellInfo->HasInitialAggro())
{
m_caster->SetInCombatState(unit->GetCombatTimer() > 0, unit);
unit->getHostileRefManager().threatAssist(m_caster, 0.0f);
@@ -4611,8 +4611,7 @@ void Spell::HandleThreatSpells()
if (m_UniqueTargetInfo.empty())
return;
- if (m_spellInfo->HasAttribute(SPELL_ATTR1_NO_THREAT) ||
- m_spellInfo->HasAttribute(SPELL_ATTR3_NO_INITIAL_AGGRO))
+ if (!m_spellInfo->HasInitialAggro())
return;
float threat = 0.0f;
@@ -4623,7 +4622,7 @@ void Spell::HandleThreatSpells()
threat += threatEntry->flatMod;
}
- else if (m_spellInfo->HasAttribute(SPELL_ATTR0_CU_NO_INITIAL_THREAT) == 0)
+ else if (!m_spellInfo->HasAttribute(SPELL_ATTR0_CU_NO_INITIAL_THREAT))
threat += m_spellInfo->SpellLevel;
// past this point only multiplicative effects occur
@@ -4701,7 +4700,7 @@ SpellCastResult Spell::CheckCast(bool strict)
return SPELL_FAILED_NOT_READY;
}
- if (!m_caster->GetSpellHistory()->IsReady(m_spellInfo, m_castItemEntry))
+ if (!m_caster->GetSpellHistory()->IsReady(m_spellInfo, m_castItemEntry, IsIgnoringCooldowns()))
{
if (m_triggeredByAuraSpell)
return SPELL_FAILED_DONT_REPORT;
@@ -5477,7 +5476,7 @@ SpellCastResult Spell::CheckCast(bool strict)
if (m_originalCaster && m_originalCaster->GetTypeId() == TYPEID_PLAYER && m_originalCaster->IsAlive())
{
Battlefield* Bf = sBattlefieldMgr->GetBattlefieldToZoneId(m_originalCaster->GetZoneId());
- if (AreaTableEntry const* area = GetAreaEntryByAreaID(m_originalCaster->GetAreaId()))
+ if (AreaTableEntry const* area = sAreaTableStore.LookupEntry(m_originalCaster->GetAreaId()))
if (area->flags & AREA_FLAG_NO_FLY_ZONE || (Bf && !Bf->CanFlyIn()))
return (_triggeredCastFlags & TRIGGERED_DONT_REPORT_CAST_ERROR) ? SPELL_FAILED_DONT_REPORT : SPELL_FAILED_NOT_HERE;
}
diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp
index 40bd4e3c263..9535ca291eb 100644
--- a/src/server/game/Spells/SpellEffects.cpp
+++ b/src/server/game/Spells/SpellEffects.cpp
@@ -877,11 +877,6 @@ void Spell::EffectTriggerSpell(SpellEffIndex effIndex)
values.AddSpellMod(SPELLVALUE_BASE_POINT2, damage);
}
- // Remove spell cooldown (not category) if spell triggering spell with cooldown and same category
- if (m_caster->GetTypeId() == TYPEID_PLAYER && m_spellInfo->CategoryRecoveryTime && spellInfo->CategoryRecoveryTime
- && m_spellInfo->GetCategory() == spellInfo->GetCategory())
- m_caster->GetSpellHistory()->ResetCooldown(spellInfo->Id);
-
// original caster guid only for GO cast
m_caster->CastSpell(targets, spellInfo, &values, TRIGGERED_FULL_MASK, NULL, NULL, m_originalCasterGUID);
}
@@ -930,11 +925,6 @@ void Spell::EffectTriggerMissileSpell(SpellEffIndex effIndex)
values.AddSpellMod(SPELLVALUE_BASE_POINT2, damage);
}
- // Remove spell cooldown (not category) if spell triggering spell with cooldown and same category
- if (m_caster->GetTypeId() == TYPEID_PLAYER && m_spellInfo->CategoryRecoveryTime && spellInfo->CategoryRecoveryTime
- && m_spellInfo->GetCategory() == spellInfo->GetCategory())
- m_caster->GetSpellHistory()->ResetCooldown(spellInfo->Id);
-
// original caster guid only for GO cast
m_caster->CastSpell(targets, spellInfo, &values, TRIGGERED_FULL_MASK, NULL, NULL, m_originalCasterGUID);
}
@@ -1031,7 +1021,7 @@ void Spell::EffectJump(SpellEffIndex effIndex)
float speedXY, speedZ;
CalculateJumpSpeeds(effIndex, m_caster->GetExactDist2d(x, y), speedXY, speedZ);
- m_caster->GetMotionMaster()->MoveJump(x, y, z, speedXY, speedZ);
+ m_caster->GetMotionMaster()->MoveJump(x, y, z, 0.0f, speedXY, speedZ, EVENT_JUMP, false);
}
void Spell::EffectJumpDest(SpellEffIndex effIndex)
@@ -1045,13 +1035,9 @@ void Spell::EffectJumpDest(SpellEffIndex effIndex)
if (!m_targets.HasDst())
return;
- // Init dest coordinates
- float x, y, z;
- destTarget->GetPosition(x, y, z);
-
float speedXY, speedZ;
- CalculateJumpSpeeds(effIndex, m_caster->GetExactDist2d(x, y), speedXY, speedZ);
- m_caster->GetMotionMaster()->MoveJump(x, y, z, speedXY, speedZ);
+ CalculateJumpSpeeds(effIndex, m_caster->GetExactDist2d(destTarget), speedXY, speedZ);
+ m_caster->GetMotionMaster()->MoveJump(*destTarget, speedXY, speedZ, EVENT_JUMP, true);
}
void Spell::CalculateJumpSpeeds(uint8 i, float dist, float & speedXY, float & speedZ)
@@ -4147,14 +4133,14 @@ void Spell::EffectDuel(SpellEffIndex effIndex)
return;
// Players can only fight a duel in zones with this flag
- AreaTableEntry const* casterAreaEntry = GetAreaEntryByAreaID(caster->GetAreaId());
+ AreaTableEntry const* casterAreaEntry = sAreaTableStore.LookupEntry(caster->GetAreaId());
if (casterAreaEntry && !(casterAreaEntry->flags & AREA_FLAG_ALLOW_DUELS))
{
SendCastResult(SPELL_FAILED_NO_DUELING); // Dueling isn't allowed here
return;
}
- AreaTableEntry const* targetAreaEntry = GetAreaEntryByAreaID(target->GetAreaId());
+ AreaTableEntry const* targetAreaEntry = sAreaTableStore.LookupEntry(target->GetAreaId());
if (targetAreaEntry && !(targetAreaEntry->flags & AREA_FLAG_ALLOW_DUELS))
{
SendCastResult(SPELL_FAILED_NO_DUELING); // Dueling isn't allowed here
@@ -4913,7 +4899,7 @@ void Spell::EffectPullTowards(SpellEffIndex effIndex)
float speedXY = float(m_spellInfo->Effects[effIndex].MiscValue) * 0.1f;
float speedZ = unitTarget->GetDistance(pos) / speedXY * 0.5f * Movement::gravity;
- unitTarget->GetMotionMaster()->MoveJump(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), speedXY, speedZ);
+ unitTarget->GetMotionMaster()->MoveJump(pos, speedXY, speedZ);
}
void Spell::EffectDispelMechanic(SpellEffIndex effIndex)
diff --git a/src/server/game/Spells/SpellHistory.cpp b/src/server/game/Spells/SpellHistory.cpp
index ac08f681933..adf5fc47c77 100644
--- a/src/server/game/Spells/SpellHistory.cpp
+++ b/src/server/game/Spells/SpellHistory.cpp
@@ -182,13 +182,13 @@ void SpellHistory::HandleCooldowns(SpellInfo const* spellInfo, uint32 itemID, Sp
StartCooldown(spellInfo, itemID, spell);
}
-bool SpellHistory::IsReady(SpellInfo const* spellInfo, uint32 itemId /*= 0*/) const
+bool SpellHistory::IsReady(SpellInfo const* spellInfo, uint32 itemId /*= 0*/, bool ignoreCategoryCooldown /*= false*/) const
{
if (spellInfo->PreventionType == SPELL_PREVENTION_TYPE_SILENCE)
if (IsSchoolLocked(spellInfo->GetSchoolMask()))
return false;
- if (HasCooldown(spellInfo->Id, itemId))
+ if (HasCooldown(spellInfo->Id, itemId, ignoreCategoryCooldown))
return false;
return true;
@@ -465,11 +465,14 @@ void SpellHistory::ResetAllCooldowns()
_spellCooldowns.clear();
}
-bool SpellHistory::HasCooldown(SpellInfo const* spellInfo, uint32 itemId /*= 0*/) const
+bool SpellHistory::HasCooldown(SpellInfo const* spellInfo, uint32 itemId /*= 0*/, bool ignoreCategoryCooldown /*= false*/) const
{
if (_spellCooldowns.count(spellInfo->Id) != 0)
return true;
+ if (ignoreCategoryCooldown)
+ return false;
+
uint32 category = 0;
GetCooldownDurations(spellInfo, itemId, nullptr, &category, nullptr);
if (!category)
@@ -478,9 +481,9 @@ bool SpellHistory::HasCooldown(SpellInfo const* spellInfo, uint32 itemId /*= 0*/
return _categoryCooldowns.count(category) != 0;
}
-bool SpellHistory::HasCooldown(uint32 spellId, uint32 itemId /*= 0*/) const
+bool SpellHistory::HasCooldown(uint32 spellId, uint32 itemId /*= 0*/, bool ignoreCategoryCooldown /*= false*/) const
{
- return HasCooldown(sSpellMgr->EnsureSpellInfo(spellId), itemId);
+ return HasCooldown(sSpellMgr->EnsureSpellInfo(spellId), itemId, ignoreCategoryCooldown);
}
uint32 SpellHistory::GetRemainingCooldown(SpellInfo const* spellInfo) const
diff --git a/src/server/game/Spells/SpellHistory.h b/src/server/game/Spells/SpellHistory.h
index 00c790a670d..f0a53fe130d 100644
--- a/src/server/game/Spells/SpellHistory.h
+++ b/src/server/game/Spells/SpellHistory.h
@@ -62,7 +62,7 @@ public:
void HandleCooldowns(SpellInfo const* spellInfo, Item const* item, Spell* spell = nullptr);
void HandleCooldowns(SpellInfo const* spellInfo, uint32 itemID, Spell* spell = nullptr);
- bool IsReady(SpellInfo const* spellInfo, uint32 itemId = 0) const;
+ bool IsReady(SpellInfo const* spellInfo, uint32 itemId = 0, bool ignoreCategoryCooldown = false) const;
template<class OwnerType>
void WritePacket(WorldPacket& packet) const;
@@ -105,8 +105,8 @@ public:
}
void ResetAllCooldowns();
- bool HasCooldown(SpellInfo const* spellInfo, uint32 itemId = 0) const;
- bool HasCooldown(uint32 spellId, uint32 itemId = 0) const;
+ bool HasCooldown(SpellInfo const* spellInfo, uint32 itemId = 0, bool ignoreCategoryCooldown = false) const;
+ bool HasCooldown(uint32 spellId, uint32 itemId = 0, bool ignoreCategoryCooldown = false) const;
uint32 GetRemainingCooldown(SpellInfo const* spellInfo) const;
// School lockouts
diff --git a/src/server/game/Spells/SpellInfo.cpp b/src/server/game/Spells/SpellInfo.cpp
index 699e4857ee0..069c794ca8b 100644
--- a/src/server/game/Spells/SpellInfo.cpp
+++ b/src/server/game/Spells/SpellInfo.cpp
@@ -1180,6 +1180,11 @@ bool SpellInfo::IsAutoRepeatRangedSpell() const
return HasAttribute(SPELL_ATTR2_AUTOREPEAT_FLAG);
}
+bool SpellInfo::HasInitialAggro() const
+{
+ return !(HasAttribute(SPELL_ATTR1_NO_THREAT) || HasAttribute(SPELL_ATTR3_NO_INITIAL_AGGRO));
+}
+
bool SpellInfo::IsAffectedBySpellMods() const
{
return !HasAttribute(SPELL_ATTR3_NO_DONE_BONUS);
diff --git a/src/server/game/Spells/SpellInfo.h b/src/server/game/Spells/SpellInfo.h
index dbbab5fb30b..ba658c885fa 100644
--- a/src/server/game/Spells/SpellInfo.h
+++ b/src/server/game/Spells/SpellInfo.h
@@ -420,6 +420,7 @@ public:
bool IsBreakingStealth() const;
bool IsRangedWeaponSpell() const;
bool IsAutoRepeatRangedSpell() const;
+ bool HasInitialAggro() const;
bool IsAffectedBySpellMods() const;
bool IsAffectedBySpellMod(SpellModifier const* mod) const;
diff --git a/src/server/game/Spells/SpellMgr.cpp b/src/server/game/Spells/SpellMgr.cpp
index e96ed8c6799..f5bb1c920fe 100644
--- a/src/server/game/Spells/SpellMgr.cpp
+++ b/src/server/game/Spells/SpellMgr.cpp
@@ -969,10 +969,12 @@ bool SpellMgr::CanSpellTriggerProcOnEvent(SpellProcEntry const& procEntry, ProcE
// check spell family name/flags (if set) for spells
if (eventInfo.GetTypeMask() & (PERIODIC_PROC_FLAG_MASK | SPELL_PROC_FLAG_MASK | PROC_FLAG_DONE_TRAP_ACTIVATION))
{
- if (procEntry.spellFamilyName && eventInfo.GetSpellInfo() && (procEntry.spellFamilyName != eventInfo.EnsureSpellInfo()->SpellFamilyName))
+ SpellInfo const* eventSpellInfo = eventInfo.GetSpellInfo();
+
+ if (procEntry.spellFamilyName && eventSpellInfo && (procEntry.spellFamilyName != eventSpellInfo->SpellFamilyName))
return false;
- if (procEntry.spellFamilyMask && eventInfo.GetSpellInfo() && !(procEntry.spellFamilyMask & eventInfo.EnsureSpellInfo()->SpellFamilyFlags))
+ if (procEntry.spellFamilyMask && eventSpellInfo && !(procEntry.spellFamilyMask & eventSpellInfo->SpellFamilyFlags))
return false;
}
@@ -1157,7 +1159,7 @@ bool SpellArea::IsFitToRequirements(Player const* player, uint32 newZone, uint32
if (!player)
return false;
- AreaTableEntry const* pArea = GetAreaEntryByAreaID(player->GetAreaId());
+ AreaTableEntry const* pArea = sAreaTableStore.LookupEntry(player->GetAreaId());
if (!(pArea && pArea->flags & AREA_FLAG_NO_FLY_ZONE))
return false;
if (!player->HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED) && !player->HasAuraType(SPELL_AURA_FLY))
@@ -2616,7 +2618,7 @@ void SpellMgr::LoadSpellAreas()
}
}
- if (spellArea.areaId && !GetAreaEntryByAreaID(spellArea.areaId))
+ if (spellArea.areaId && !sAreaTableStore.LookupEntry(spellArea.areaId))
{
TC_LOG_ERROR("sql.sql", "Spell %u listed in `spell_area` have wrong area (%u) requirement", spell, spellArea.areaId);
continue;
@@ -2942,6 +2944,7 @@ void SpellMgr::LoadSpellInfoCorrections()
case 53096: // Quetz'lun's Judgment
case 70743: // AoD Special
case 70614: // AoD Special - Vegard
+ case 4020: // Safirdrang's Chill
spellInfo->MaxAffectedTargets = 1;
break;
case 42436: // Drink! (Brewfest)
@@ -2984,13 +2987,6 @@ void SpellMgr::LoadSpellInfoCorrections()
case 36350: // They Must Burn Bomb Aura (self)
spellInfo->Effects[EFFECT_0].TriggerSpell = 36325; // They Must Burn Bomb Drop (DND)
break;
- case 49838: // Stop Time
- case 69438: // Sample Satisfaction
- case 69445: // Perfume Spritz
- case 69489: // Chocolate Sample
- case 69563: // Cologne Spritz
- spellInfo->AttributesEx3 |= SPELL_ATTR3_NO_INITIAL_AGGRO;
- break;
case 61407: // Energize Cores
case 62136: // Energize Cores
case 54069: // Energize Cores
@@ -3332,6 +3328,8 @@ void SpellMgr::LoadSpellInfoCorrections()
spellInfo->Attributes |= SPELL_ATTR0_PASSIVE;
break;
case 17364: // Stormstrike
+ case 48278: // Paralyze
+ case 53651: // Light's Beacon
spellInfo->AttributesEx3 |= SPELL_ATTR3_STACK_FOR_DIFF_CASTERS;
break;
case 51798: // Brewfest - Relay Race - Intro - Quest Complete
@@ -3565,7 +3563,6 @@ void SpellMgr::LoadSpellInfoCorrections()
spellInfo->AreaGroupId = 0; // originally, these require area 4522, which is... outside of Icecrown Citadel
break;
case 70602: // Corruption
- case 48278: // Paralyze
spellInfo->AttributesEx3 |= SPELL_ATTR3_STACK_FOR_DIFF_CASTERS;
break;
case 70715: // Column of Frost (visual marker)
diff --git a/src/server/game/Texts/CreatureTextMgr.cpp b/src/server/game/Texts/CreatureTextMgr.cpp
index 8d3ee939e2c..499f0c9cbf0 100644
--- a/src/server/game/Texts/CreatureTextMgr.cpp
+++ b/src/server/game/Texts/CreatureTextMgr.cpp
@@ -191,7 +191,6 @@ void CreatureTextMgr::LoadCreatureTextLocales()
} while (result->NextRow());
TC_LOG_INFO("server.loading", ">> Loaded %u creature localized texts in %u ms", textCount, GetMSTimeDiffToNow(oldMSTime));
-
}
uint32 CreatureTextMgr::SendChat(Creature* source, uint8 textGroup, WorldObject const* whisperTarget /*= nullptr*/, ChatMsg msgType /*= CHAT_MSG_ADDON*/, Language language /*= LANG_ADDON*/, CreatureTextRange range /*= TEXT_RANGE_NORMAL*/, uint32 sound /*= 0*/, Team team /*= TEAM_OTHER*/, bool gmOnly /*= false*/, Player* srcPlr /*= nullptr*/)
@@ -228,42 +227,10 @@ uint32 CreatureTextMgr::SendChat(Creature* source, uint8 textGroup, WorldObject
tempGroup = textGroupContainer;
}
- uint8 count = 0;
- float lastChance = -1;
- bool isEqualChanced = true;
-
- float totalChance = 0;
-
- for (CreatureTextGroup::const_iterator iter = tempGroup.begin(); iter != tempGroup.end(); ++iter)
+ auto iter = Trinity::Containers::SelectRandomWeightedContainerElement(tempGroup, [](CreatureTextEntry const& t) -> double
{
- if (lastChance >= 0 && lastChance != iter->probability)
- isEqualChanced = false;
-
- lastChance = iter->probability;
- totalChance += iter->probability;
- ++count;
- }
-
- int32 offset = -1;
- if (!isEqualChanced)
- {
- for (CreatureTextGroup::const_iterator iter = tempGroup.begin(); iter != tempGroup.end(); ++iter)
- {
- uint32 chance = uint32(iter->probability);
- uint32 r = urand(0, 100);
- ++offset;
- if (r <= chance)
- break;
- }
- }
-
- uint32 pos = 0;
- if (isEqualChanced || offset < 0)
- pos = urand(0, count - 1);
- else if (offset >= 0)
- pos = offset;
-
- CreatureTextGroup::const_iterator iter = tempGroup.begin() + pos;
+ return t.probability;
+ });
ChatMsg finalType = (msgType == CHAT_MSG_ADDON) ? iter->type : msgType;
Language finalLang = (language == LANG_ADDON) ? iter->lang : language;
@@ -293,9 +260,7 @@ uint32 CreatureTextMgr::SendChat(Creature* source, uint8 textGroup, WorldObject
SendChatPacket(finalSource, builder, finalType, whisperTarget, range, team, gmOnly);
}
- if (isEqualChanced || totalChance == 100.0f)
- SetRepeatId(source, textGroup, iter->id);
-
+ SetRepeatId(source, textGroup, iter->id);
return iter->duration;
}
diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp
index 09cc9180a57..289a4d47666 100644
--- a/src/server/game/World/World.cpp
+++ b/src/server/game/World/World.cpp
@@ -575,6 +575,13 @@ void World::LoadConfigSettings(bool reload)
m_bool_configs[CONFIG_ADDON_CHANNEL] = sConfigMgr->GetBoolDefault("AddonChannel", true);
m_bool_configs[CONFIG_CLEAN_CHARACTER_DB] = sConfigMgr->GetBoolDefault("CleanCharacterDB", false);
m_int_configs[CONFIG_PERSISTENT_CHARACTER_CLEAN_FLAGS] = sConfigMgr->GetIntDefault("PersistentCharacterCleanFlags", 0);
+ m_int_configs[CONFIG_AUCTION_GETALL_DELAY] = sConfigMgr->GetIntDefault("Auction.GetAllScanDelay", 900);
+ m_int_configs[CONFIG_AUCTION_SEARCH_DELAY] = sConfigMgr->GetIntDefault("Auction.SearchDelay", 300);
+ if (m_int_configs[CONFIG_AUCTION_SEARCH_DELAY] < 100 || m_int_configs[CONFIG_AUCTION_SEARCH_DELAY] > 10000)
+ {
+ TC_LOG_ERROR("server.loading", "Auction.SearchDelay (%i) must be between 100 and 10000. Using default of 300ms", m_int_configs[CONFIG_AUCTION_SEARCH_DELAY]);
+ m_int_configs[CONFIG_AUCTION_SEARCH_DELAY] = 300;
+ }
m_int_configs[CONFIG_CHAT_CHANNEL_LEVEL_REQ] = sConfigMgr->GetIntDefault("ChatLevelReq.Channel", 1);
m_int_configs[CONFIG_CHAT_WHISPER_LEVEL_REQ] = sConfigMgr->GetIntDefault("ChatLevelReq.Whisper", 1);
m_int_configs[CONFIG_CHAT_SAY_LEVEL_REQ] = sConfigMgr->GetIntDefault("ChatLevelReq.Say", 1);
diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h
index a624fd6e6a7..00b244c9efb 100644
--- a/src/server/game/World/World.h
+++ b/src/server/game/World/World.h
@@ -356,6 +356,8 @@ enum WorldIntConfigs
CONFIG_CHARTER_COST_ARENA_5v5,
CONFIG_NO_GRAY_AGGRO_ABOVE,
CONFIG_NO_GRAY_AGGRO_BELOW,
+ CONFIG_AUCTION_GETALL_DELAY,
+ CONFIG_AUCTION_SEARCH_DELAY,
INT_CONFIG_VALUE_COUNT
};
diff --git a/src/server/scripts/Commands/cs_debug.cpp b/src/server/scripts/Commands/cs_debug.cpp
index 9902b83ff63..b937fc4e0a4 100644
--- a/src/server/scripts/Commands/cs_debug.cpp
+++ b/src/server/scripts/Commands/cs_debug.cpp
@@ -93,7 +93,8 @@ public:
{ "moveflags", rbac::RBAC_PERM_COMMAND_DEBUG_MOVEFLAGS, false, &HandleDebugMoveflagsCommand, "" },
{ "transport", rbac::RBAC_PERM_COMMAND_DEBUG_TRANSPORT, false, &HandleDebugTransportCommand, "" },
{ "loadcells", rbac::RBAC_PERM_COMMAND_DEBUG_LOADCELLS, false, &HandleDebugLoadCellsCommand, "" },
- { "boundary", rbac::RBAC_PERM_COMMAND_DEBUG_BOUNDARY, false, &HandleDebugBoundaryCommand, "" }
+ { "boundary", rbac::RBAC_PERM_COMMAND_DEBUG_BOUNDARY, false, &HandleDebugBoundaryCommand, "" },
+ { "raidreset", rbac::RBAC_PERM_COMMAND_INSTANCE_UNBIND, false, &HandleDebugRaidResetCommand, "" }
};
static std::vector<ChatCommand> commandTable =
{
@@ -1442,6 +1443,32 @@ public:
return true;
}
+
+ static bool HandleDebugRaidResetCommand(ChatHandler* /*handler*/, char const* args)
+ {
+ char* map_str = args ? strtok((char*)args, " ") : nullptr;
+ char* difficulty_str = args ? strtok(nullptr, " ") : nullptr;
+
+ int32 map = map_str ? atoi(map_str) : -1;
+ if (map <= 0)
+ return false;
+ MapEntry const* mEntry = sMapStore.LookupEntry(map);
+ if (!mEntry || !mEntry->IsRaid())
+ return false;
+ int32 difficulty = difficulty_str ? atoi(difficulty_str) : -1;
+ if (difficulty >= MAX_RAID_DIFFICULTY || difficulty < -1)
+ return false;
+
+ if (difficulty == -1)
+ for (uint8 diff = 0; diff < MAX_RAID_DIFFICULTY; ++diff)
+ {
+ if (GetMapDifficultyData(mEntry->MapID, Difficulty(diff)))
+ sInstanceSaveMgr->ForceGlobalReset(mEntry->MapID, Difficulty(diff));
+ }
+ else
+ sInstanceSaveMgr->ForceGlobalReset(mEntry->MapID, Difficulty(difficulty));
+ return true;
+ }
};
void AddSC_debug_commandscript()
diff --git a/src/server/scripts/Commands/cs_go.cpp b/src/server/scripts/Commands/cs_go.cpp
index b7acfb85f50..039af61c010 100644
--- a/src/server/scripts/Commands/cs_go.cpp
+++ b/src/server/scripts/Commands/cs_go.cpp
@@ -424,7 +424,7 @@ public:
uint32 areaId = id ? (uint32)atoi(id) : player->GetZoneId();
- AreaTableEntry const* areaEntry = GetAreaEntryByAreaID(areaId);
+ AreaTableEntry const* areaEntry = sAreaTableStore.LookupEntry(areaId);
if (x < 0 || x > 100 || y < 0 || y > 100 || !areaEntry)
{
@@ -434,7 +434,7 @@ public:
}
// update to parent zone if exist (client map show only zones without parents)
- AreaTableEntry const* zoneEntry = areaEntry->zone ? GetAreaEntryByAreaID(areaEntry->zone) : areaEntry;
+ AreaTableEntry const* zoneEntry = areaEntry->zone ? sAreaTableStore.LookupEntry(areaEntry->zone) : areaEntry;
ASSERT(zoneEntry);
Map const* map = sMapMgr->CreateBaseMap(zoneEntry->mapid);
diff --git a/src/server/scripts/Commands/cs_group.cpp b/src/server/scripts/Commands/cs_group.cpp
index 5e8952ce881..9f2bc86f9c0 100644
--- a/src/server/scripts/Commands/cs_group.cpp
+++ b/src/server/scripts/Commands/cs_group.cpp
@@ -348,10 +348,10 @@ public:
phase = (!p->IsGameMaster() ? p->GetPhaseMask() : -1);
uint32 locale = handler->GetSessionDbcLocale();
- AreaTableEntry const* area = GetAreaEntryByAreaID(p->GetAreaId());
+ AreaTableEntry const* area = sAreaTableStore.LookupEntry(p->GetAreaId());
if (area)
{
- AreaTableEntry const* zone = GetAreaEntryByAreaID(area->zone);
+ AreaTableEntry const* zone = sAreaTableStore.LookupEntry(area->zone);
if (zone)
zoneName = zone->area_name[locale];
}
diff --git a/src/server/scripts/Commands/cs_instance.cpp b/src/server/scripts/Commands/cs_instance.cpp
index a53d1f00b54..d0325a317db 100644
--- a/src/server/scripts/Commands/cs_instance.cpp
+++ b/src/server/scripts/Commands/cs_instance.cpp
@@ -82,7 +82,7 @@ public:
{
InstanceSave* save = itr->second.save;
std::string timeleft = GetTimeString(save->GetResetTime() - time(NULL));
- handler->PSendSysMessage(LANG_COMMAND_LIST_BIND_INFO, itr->first, save->GetInstanceId(), itr->second.perm ? "yes" : "no", save->GetDifficulty(), save->CanReset() ? "yes" : "no", timeleft.c_str());
+ handler->PSendSysMessage(LANG_COMMAND_LIST_BIND_INFO, itr->first, save->GetInstanceId(), itr->second.perm ? "yes" : "no", itr->second.extendState == EXTEND_STATE_EXPIRED ? "expired" : itr->second.extendState == EXTEND_STATE_EXTENDED ? "yes" : "no", save->GetDifficulty(), save->CanReset() ? "yes" : "no", timeleft.c_str());
counter++;
}
}
@@ -98,7 +98,7 @@ public:
{
InstanceSave* save = itr->second.save;
std::string timeleft = GetTimeString(save->GetResetTime() - time(NULL));
- handler->PSendSysMessage(LANG_COMMAND_LIST_BIND_INFO, itr->first, save->GetInstanceId(), itr->second.perm ? "yes" : "no", save->GetDifficulty(), save->CanReset() ? "yes" : "no", timeleft.c_str());
+ handler->PSendSysMessage(LANG_COMMAND_LIST_BIND_INFO, itr->first, save->GetInstanceId(), itr->second.perm ? "yes" : "no", "-", save->GetDifficulty(), save->CanReset() ? "yes" : "no", timeleft.c_str());
counter++;
}
}
diff --git a/src/server/scripts/Commands/cs_lookup.cpp b/src/server/scripts/Commands/cs_lookup.cpp
index 4e749d33fcf..61e6acfb4d8 100644
--- a/src/server/scripts/Commands/cs_lookup.cpp
+++ b/src/server/scripts/Commands/cs_lookup.cpp
@@ -97,9 +97,9 @@ public:
wstrToLower(wNamePart);
// Search in AreaTable.dbc
- for (uint32 areaflag = 0; areaflag < sAreaStore.GetNumRows(); ++areaflag)
+ for (uint32 i = 0; i < sAreaTableStore.GetNumRows(); ++i)
{
- AreaTableEntry const* areaEntry = sAreaStore.LookupEntry(areaflag);
+ AreaTableEntry const* areaEntry = sAreaTableStore.LookupEntry(i);
if (areaEntry)
{
int locale = handler->GetSessionDbcLocale();
diff --git a/src/server/scripts/Commands/cs_misc.cpp b/src/server/scripts/Commands/cs_misc.cpp
index 6ae509af443..c70246f7fb5 100644
--- a/src/server/scripts/Commands/cs_misc.cpp
+++ b/src/server/scripts/Commands/cs_misc.cpp
@@ -198,8 +198,8 @@ public:
uint32 mapId = object->GetMapId();
MapEntry const* mapEntry = sMapStore.LookupEntry(mapId);
- AreaTableEntry const* zoneEntry = GetAreaEntryByAreaID(zoneId);
- AreaTableEntry const* areaEntry = GetAreaEntryByAreaID(areaId);
+ AreaTableEntry const* zoneEntry = sAreaTableStore.LookupEntry(zoneId);
+ AreaTableEntry const* areaEntry = sAreaTableStore.LookupEntry(areaId);
float zoneX = object->GetPositionX();
float zoneY = object->GetPositionY();
@@ -961,7 +961,7 @@ public:
uint32 zoneId = player->GetZoneId();
- AreaTableEntry const* areaEntry = GetAreaEntryByAreaID(zoneId);
+ AreaTableEntry const* areaEntry = sAreaTableStore.LookupEntry(zoneId);
if (!areaEntry || areaEntry->zone !=0)
{
handler->PSendSysMessage(LANG_COMMAND_GRAVEYARDWRONGZONE, graveyardId, zoneId);
@@ -1052,17 +1052,23 @@ public:
return false;
}
- int32 area = GetAreaFlagByAreaID(atoi((char*)args));
- int32 offset = area / 32;
+ AreaTableEntry const* area = sAreaTableStore.LookupEntry(atoi(args));
+ if (!area)
+ {
+ handler->SendSysMessage(LANG_BAD_VALUE);
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
- if (area<0 || offset >= PLAYER_EXPLORED_ZONES_SIZE)
+ int32 offset = area->exploreFlag / 32;
+ if (offset >= PLAYER_EXPLORED_ZONES_SIZE)
{
handler->SendSysMessage(LANG_BAD_VALUE);
handler->SetSentErrorMessage(true);
return false;
}
- uint32 val = uint32((1 << (area % 32)));
+ uint32 val = uint32((1 << (area->exploreFlag % 32)));
uint32 currFields = playerTarget->GetUInt32Value(PLAYER_EXPLORED_ZONES_1 + offset);
playerTarget->SetUInt32Value(PLAYER_EXPLORED_ZONES_1 + offset, uint32((currFields | val)));
@@ -1083,17 +1089,23 @@ public:
return false;
}
- int32 area = GetAreaFlagByAreaID(atoi((char*)args));
- int32 offset = area / 32;
+ AreaTableEntry const* area = sAreaTableStore.LookupEntry(atoi(args));
+ if (!area)
+ {
+ handler->SendSysMessage(LANG_BAD_VALUE);
+ handler->SetSentErrorMessage(true);
+ return false;
+ }
- if (area < 0 || offset >= PLAYER_EXPLORED_ZONES_SIZE)
+ int32 offset = area->exploreFlag / 32;
+ if (offset >= PLAYER_EXPLORED_ZONES_SIZE)
{
handler->SendSysMessage(LANG_BAD_VALUE);
handler->SetSentErrorMessage(true);
return false;
}
- uint32 val = uint32((1 << (area % 32)));
+ uint32 val = uint32((1 << (area->exploreFlag % 32)));
uint32 currFields = playerTarget->GetUInt32Value(PLAYER_EXPLORED_ZONES_1 + offset);
playerTarget->SetUInt32Value(PLAYER_EXPLORED_ZONES_1 + offset, uint32((currFields ^ val)));
@@ -1737,12 +1749,12 @@ public:
// Position data
MapEntry const* map = sMapStore.LookupEntry(mapId);
- AreaTableEntry const* area = GetAreaEntryByAreaID(areaId);
+ AreaTableEntry const* area = sAreaTableStore.LookupEntry(areaId);
if (area)
{
areaName = area->area_name[locale];
- AreaTableEntry const* zone = GetAreaEntryByAreaID(area->zone);
+ AreaTableEntry const* zone = sAreaTableStore.LookupEntry(area->zone);
if (zone)
zoneName = zone->area_name[locale];
}
diff --git a/src/server/scripts/Commands/cs_npc.cpp b/src/server/scripts/Commands/cs_npc.cpp
index daf4fe5866a..fbd199b99db 100644
--- a/src/server/scripts/Commands/cs_npc.cpp
+++ b/src/server/scripts/Commands/cs_npc.cpp
@@ -43,7 +43,7 @@ struct EnumName
#define CREATE_NAMED_ENUM(VALUE) { VALUE, STRINGIZE(VALUE) }
#define NPCFLAG_COUNT 24
-#define FLAGS_EXTRA_COUNT 16
+#define FLAGS_EXTRA_COUNT 19
EnumName<NPCFlags, int32> const npcFlagTexts[NPCFLAG_COUNT] =
{
@@ -162,7 +162,10 @@ EnumName<CreatureFlagsExtra> const flagsExtra[FLAGS_EXTRA_COUNT] =
CREATE_NAMED_ENUM(CREATURE_FLAG_EXTRA_NO_SKILLGAIN),
CREATE_NAMED_ENUM(CREATURE_FLAG_EXTRA_TAUNT_DIMINISH),
CREATE_NAMED_ENUM(CREATURE_FLAG_EXTRA_ALL_DIMINISH),
- CREATE_NAMED_ENUM(CREATURE_FLAG_EXTRA_DUNGEON_BOSS)
+ CREATE_NAMED_ENUM(CREATURE_FLAG_EXTRA_NO_PLAYER_DAMAGE_REQ),
+ CREATE_NAMED_ENUM(CREATURE_FLAG_EXTRA_DUNGEON_BOSS),
+ CREATE_NAMED_ENUM(CREATURE_FLAG_EXTRA_IGNORE_PATHFINDING),
+ CREATE_NAMED_ENUM(CREATURE_FLAG_EXTRA_IMMUNITY_KNOCKBACK)
};
class npc_commandscript : public CommandScript
diff --git a/src/server/scripts/EasternKingdoms/AlteracValley/boss_drekthar.cpp b/src/server/scripts/EasternKingdoms/AlteracValley/boss_drekthar.cpp
index 269e115189a..d5ed2421efc 100644
--- a/src/server/scripts/EasternKingdoms/AlteracValley/boss_drekthar.cpp
+++ b/src/server/scripts/EasternKingdoms/AlteracValley/boss_drekthar.cpp
@@ -77,7 +77,7 @@ public:
Talk(SAY_RESPAWN);
}
- bool CheckInRoom()
+ bool CheckInRoom() override
{
if (me->GetDistance2d(me->GetHomePosition().GetPositionX(), me->GetHomePosition().GetPositionY()) > 50)
{
diff --git a/src/server/scripts/EasternKingdoms/AlteracValley/boss_galvangar.cpp b/src/server/scripts/EasternKingdoms/AlteracValley/boss_galvangar.cpp
index 93583364ef6..238942c15f6 100644
--- a/src/server/scripts/EasternKingdoms/AlteracValley/boss_galvangar.cpp
+++ b/src/server/scripts/EasternKingdoms/AlteracValley/boss_galvangar.cpp
@@ -78,7 +78,7 @@ public:
Talk(SAY_BUFF);
}
- bool CheckInRoom()
+ bool CheckInRoom() override
{
if (me->GetDistance2d(me->GetHomePosition().GetPositionX(), me->GetHomePosition().GetPositionY()) > 50)
{
diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/blackwing_lair.h b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/blackwing_lair.h
index 39c9d9a79a7..9fce132ea7e 100644
--- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/blackwing_lair.h
+++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/blackwing_lair.h
@@ -59,7 +59,7 @@ enum CreatureIds
enum GameObjectIds
{
GO_BLACK_DRAGON_EGG = 177807,
- GO_BOSSGATE01 = 175946,
+ GO_PORTCULLIS = 176965,
GO_DRAKE_RIDER_PORTCULLIS = 175185,
GO_ALTERAC_VALLEY_GATE = 180424,
GO_GATE = 185483,
diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/instance_blackwing_lair.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/instance_blackwing_lair.cpp
index 83b2b45851c..a4241b16f8d 100644
--- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/instance_blackwing_lair.cpp
+++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackwingLair/instance_blackwing_lair.cpp
@@ -22,7 +22,7 @@
DoorData const doorData[] =
{
- { GO_BOSSGATE01, DATA_RAZORGORE_THE_UNTAMED, DOOR_TYPE_PASSAGE },
+ { GO_PORTCULLIS, DATA_RAZORGORE_THE_UNTAMED, DOOR_TYPE_PASSAGE },
{ GO_DRAKE_RIDER_PORTCULLIS, DATA_VAELASTRAZ_THE_CORRUPT, DOOR_TYPE_PASSAGE },
{ GO_ALTERAC_VALLEY_GATE, DATA_BROODLORD_LASHLAYER, DOOR_TYPE_PASSAGE },
{ GO_GATE, DATA_FIREMAW, DOOR_TYPE_PASSAGE },
diff --git a/src/server/scripts/EasternKingdoms/Deadmines/boss_mr_smite.cpp b/src/server/scripts/EasternKingdoms/Deadmines/boss_mr_smite.cpp
index 541ddc0e1c8..8f246ab9bf0 100644
--- a/src/server/scripts/EasternKingdoms/Deadmines/boss_mr_smite.cpp
+++ b/src/server/scripts/EasternKingdoms/Deadmines/boss_mr_smite.cpp
@@ -25,17 +25,25 @@ EndScriptData */
#include "ScriptedCreature.h"
#include "deadmines.h"
-enum Spels
+enum Spells
{
SPELL_TRASH = 3391,
SPELL_SMITE_STOMP = 6432,
SPELL_SMITE_SLAM = 6435,
- SPELL_NIMBLE_REFLEXES = 6264,
+ SPELL_NIMBLE_REFLEXES = 6264
+};
+enum Equips
+{
EQUIP_SWORD = 5191,
- EQUIP_MACE = 7230,
+ EQUIP_AXE = 5196,
+ EQUIP_MACE = 7230
+};
- SAY_AGGRO = 0,
+enum Texts
+{
+ SAY_PHASE_1 = 2,
+ SAY_PHASE_2 = 3
};
class boss_mr_smite : public CreatureScript
@@ -66,6 +74,8 @@ public:
uiPhase = 0;
uiTimer = 0;
+
+ uiIsMoving = false;
}
InstanceScript* instance;
@@ -79,16 +89,19 @@ public:
uint32 uiPhase;
uint32 uiTimer;
+ bool uiIsMoving;
+
void Reset() override
{
Initialize();
SetEquipmentSlots(false, EQUIP_SWORD, EQUIP_UNEQUIP, EQUIP_NO_CHANGE);
+ me->SetStandState(UNIT_STAND_STATE_STAND);
+ me->SetReactState(REACT_AGGRESSIVE);
}
void EnterCombat(Unit* /*who*/) override
{
- Talk(SAY_AGGRO);
}
bool bCheckChances()
@@ -105,38 +118,52 @@ public:
if (!UpdateVictim())
return;
- /*START ACID-AI*/
- if (uiTrashTimer <= uiDiff)
+ if (!uiIsMoving) // halt abilities in between phases
{
- if (bCheckChances())
- DoCast(me, SPELL_TRASH);
- uiTrashTimer = urand(6000, 15500);
- } else uiTrashTimer -= uiDiff;
+ if (uiTrashTimer <= uiDiff)
+ {
+ if (bCheckChances())
+ DoCast(me, SPELL_TRASH);
+ uiTrashTimer = urand(6000, 15500);
+ }
+ else uiTrashTimer -= uiDiff;
- if (uiSlamTimer <= uiDiff)
- {
- if (bCheckChances())
- DoCastVictim(SPELL_SMITE_SLAM);
- uiSlamTimer = 11000;
- } else uiSlamTimer -= uiDiff;
+ if (uiSlamTimer <= uiDiff)
+ {
+ if (bCheckChances())
+ DoCastVictim(SPELL_SMITE_SLAM);
+ uiSlamTimer = 11000;
+ }
+ else uiSlamTimer -= uiDiff;
- if (uiNimbleReflexesTimer <= uiDiff)
- {
- if (bCheckChances())
- DoCast(me, SPELL_NIMBLE_REFLEXES);
- uiNimbleReflexesTimer = urand(27300, 60100);
- } else uiNimbleReflexesTimer -= uiDiff;
- /*END ACID-AI*/
+ if (uiNimbleReflexesTimer <= uiDiff)
+ {
+ if (bCheckChances())
+ DoCast(me, SPELL_NIMBLE_REFLEXES);
+ uiNimbleReflexesTimer = urand(27300, 60100);
+ }
+ else uiNimbleReflexesTimer -= uiDiff;
+ }
if ((uiHealth == 0 && !HealthAbovePct(66)) || (uiHealth == 1 && !HealthAbovePct(33)))
{
++uiHealth;
DoCastAOE(SPELL_SMITE_STOMP, false);
SetCombatMovement(false);
- if (GameObject* go = ObjectAccessor::GetGameObject(*me, instance->GetGuidData(DATA_SMITE_CHEST)))
+ me->AttackStop();
+ me->InterruptNonMeleeSpells(false);
+ me->SetReactState(REACT_PASSIVE);
+ uiTimer = 2500;
+ uiPhase = 1;
+
+ switch (uiHealth)
{
- me->GetMotionMaster()->Clear();
- me->GetMotionMaster()->MovePoint(1, go->GetPositionX() - 3.0f, go->GetPositionY(), go->GetPositionZ());
+ case 1:
+ Talk(SAY_PHASE_1);
+ break;
+ case 2:
+ Talk(SAY_PHASE_2);
+ break;
}
}
@@ -147,21 +174,36 @@ public:
switch (uiPhase)
{
case 1:
- me->HandleEmoteCommand(EMOTE_STATE_KNEEL); //dosen't work?
- uiTimer = 1000;
- uiPhase = 2;
+ {
+ if (uiIsMoving)
+ break;
+
+ if (GameObject* go = ObjectAccessor::GetGameObject(*me, instance->GetGuidData(DATA_SMITE_CHEST)))
+ {
+ me->GetMotionMaster()->Clear();
+ me->GetMotionMaster()->MovePoint(1, go->GetPositionX() - 1.5f, go->GetPositionY() + 1.4f, go->GetPositionZ());
+ uiIsMoving = true;
+ }
break;
+ }
case 2:
if (uiHealth == 1)
- SetEquipmentSlots(false, EQUIP_SWORD, EQUIP_SWORD, EQUIP_NO_CHANGE);
+ SetEquipmentSlots(false, EQUIP_AXE, EQUIP_AXE, EQUIP_NO_CHANGE);
else
SetEquipmentSlots(false, EQUIP_MACE, EQUIP_UNEQUIP, EQUIP_NO_CHANGE);
uiTimer = 500;
uiPhase = 3;
break;
case 3:
+ me->SetStandState(UNIT_STAND_STATE_STAND);
+ uiTimer = 750;
+ uiPhase = 4;
+ break;
+ case 4:
+ me->SetReactState(REACT_AGGRESSIVE);
SetCombatMovement(true);
me->GetMotionMaster()->MoveChase(me->GetVictim(), me->m_CombatDistance);
+ uiIsMoving = false;
uiPhase = 0;
break;
}
@@ -176,8 +218,11 @@ public:
if (uiType != POINT_MOTION_TYPE)
return;
- uiTimer = 1500;
- uiPhase = 1;
+ me->SetFacingTo(5.47f);
+ me->SetStandState(UNIT_STAND_STATE_KNEEL);
+
+ uiTimer = 2000;
+ uiPhase = 2;
}
};
};
diff --git a/src/server/scripts/EasternKingdoms/Deadmines/deadmines.h b/src/server/scripts/EasternKingdoms/Deadmines/deadmines.h
index dff4243617e..01ebabb160e 100644
--- a/src/server/scripts/EasternKingdoms/Deadmines/deadmines.h
+++ b/src/server/scripts/EasternKingdoms/Deadmines/deadmines.h
@@ -26,6 +26,7 @@ enum CannonState
CANNON_GUNPOWDER_USED,
CANNON_BLAST_INITIATED,
PIRATES_ATTACK,
+ SMITE_ALARMED,
EVENT_DONE
};
@@ -48,4 +49,16 @@ enum GameObjects
GO_DOOR_LEVER = 101833,
GO_MR_SMITE_CHEST = 144111
};
+
+enum CreaturesIds
+{
+ NPC_MR_SMITE = 646
+};
+
+enum InstanceTexts
+{
+ SAY_ALARM1 = 0,
+ SAY_ALARM2 = 1
+};
+
#endif
diff --git a/src/server/scripts/EasternKingdoms/Deadmines/instance_deadmines.cpp b/src/server/scripts/EasternKingdoms/Deadmines/instance_deadmines.cpp
index 80391ab2873..b827fdf7e8b 100644
--- a/src/server/scripts/EasternKingdoms/Deadmines/instance_deadmines.cpp
+++ b/src/server/scripts/EasternKingdoms/Deadmines/instance_deadmines.cpp
@@ -32,18 +32,14 @@ EndScriptData */
enum Sounds
{
SOUND_CANNONFIRE = 1400,
- SOUND_DESTROYDOOR = 3079,
- SOUND_MR_SMITE_ALARM1 = 5775,
- SOUND_MR_SMITE_ALARM2 = 5777
+ SOUND_DESTROYDOOR = 3079
};
-#define SAY_MR_SMITE_ALARM1 "You there, check out that noise!"
-#define SAY_MR_SMITE_ALARM2 "We're under attack! A vast, ye swabs! Repel the invaders!"
-
enum Misc
{
DATA_CANNON_BLAST_TIMER = 3000,
- DATA_PIRATES_DELAY_TIMER = 1000
+ DATA_PIRATES_DELAY_TIMER = 1000,
+ DATA_SMITE_ALARM_DELAY_TIMER = 5000
};
class instance_deadmines : public InstanceMapScript
@@ -61,6 +57,8 @@ class instance_deadmines : public InstanceMapScript
SetHeaders(DataHeader);
State = CANNON_NOT_USED;
+ CannonBlast_Timer = 0;
+ PiratesDelay_Timer = 0;
}
ObjectGuid FactoryDoorGUID;
@@ -70,10 +68,12 @@ class instance_deadmines : public InstanceMapScript
ObjectGuid DefiasPirate1GUID;
ObjectGuid DefiasPirate2GUID;
ObjectGuid DefiasCompanionGUID;
+ ObjectGuid MrSmiteGUID;
uint32 State;
uint32 CannonBlast_Timer;
uint32 PiratesDelay_Timer;
+ uint32 SmiteAlarmDelay_Timer;
ObjectGuid uiSmiteChestGUID;
virtual void Update(uint32 diff) override
@@ -89,22 +89,20 @@ class instance_deadmines : public InstanceMapScript
{
case CANNON_GUNPOWDER_USED:
CannonBlast_Timer = DATA_CANNON_BLAST_TIMER;
- // it's a hack - Mr. Smite should do that but his too far away
- //pIronCladDoor->SetName("Mr. Smite");
- //pIronCladDoor->MonsterYell(SAY_MR_SMITE_ALARM1, LANG_UNIVERSAL, NULL);
- pIronCladDoor->PlayDirectSound(SOUND_MR_SMITE_ALARM1);
State = CANNON_BLAST_INITIATED;
break;
case CANNON_BLAST_INITIATED:
PiratesDelay_Timer = DATA_PIRATES_DELAY_TIMER;
+ SmiteAlarmDelay_Timer = DATA_SMITE_ALARM_DELAY_TIMER;
if (CannonBlast_Timer <= diff)
{
SummonCreatures();
ShootCannon();
BlastOutDoor();
LeverStucked();
- //pIronCladDoor->MonsterYell(SAY_MR_SMITE_ALARM2, LANG_UNIVERSAL, NULL);
- pIronCladDoor->PlayDirectSound(SOUND_MR_SMITE_ALARM2);
+ instance->LoadGrid(-22.8f, -797.24f); // Loads Mr. Smite's grid.
+ if (Creature* smite = instance->GetCreature(MrSmiteGUID)) // goes off when door blows up
+ smite->AI()->Talk(SAY_ALARM1);
State = PIRATES_ATTACK;
} else CannonBlast_Timer -= diff;
break;
@@ -112,9 +110,17 @@ class instance_deadmines : public InstanceMapScript
if (PiratesDelay_Timer <= diff)
{
MoveCreaturesInside();
- State = EVENT_DONE;
+ State = SMITE_ALARMED;
} else PiratesDelay_Timer -= diff;
break;
+ case SMITE_ALARMED:
+ if (SmiteAlarmDelay_Timer <= diff)
+ {
+ if (Creature* smite = instance->GetCreature(MrSmiteGUID))
+ smite->AI()->Talk(SAY_ALARM2);
+ State = EVENT_DONE;
+ } else SmiteAlarmDelay_Timer -= diff;
+ break;
}
}
@@ -178,6 +184,18 @@ class instance_deadmines : public InstanceMapScript
pDoorLever->SetUInt32Value(GAMEOBJECT_FLAGS, 4);
}
+ void OnCreatureCreate(Creature* creature) override
+ {
+ switch (creature->GetEntry())
+ {
+ case NPC_MR_SMITE:
+ MrSmiteGUID = creature->GetGUID();
+ break;
+ default:
+ break;
+ }
+ }
+
void OnGameObjectCreate(GameObject* go) override
{
switch (go->GetEntry())
diff --git a/src/server/scripts/EasternKingdoms/Karazhan/instance_karazhan.cpp b/src/server/scripts/EasternKingdoms/Karazhan/instance_karazhan.cpp
index b23b9e645d0..b725f421c8f 100644
--- a/src/server/scripts/EasternKingdoms/Karazhan/instance_karazhan.cpp
+++ b/src/server/scripts/EasternKingdoms/Karazhan/instance_karazhan.cpp
@@ -44,6 +44,13 @@ EndScriptData */
11 - Nightbane
*/
+const Position OptionalSpawn[] =
+{
+ { -10960.981445f, -1940.138428f, 46.178097f, 4.12f }, // Hyakiss the Lurker
+ { -10899.903320f, -2085.573730f, 49.474449f, 1.38f }, // Rokad the Ravager
+ { -10945.769531f, -2040.153320f, 49.474438f, 0.077f } // Shadikith the Glider
+};
+
class instance_karazhan : public InstanceMapScript
{
public:
@@ -64,6 +71,7 @@ public:
// 1 - OZ, 2 - HOOD, 3 - RAJ, this never gets altered.
m_uiOperaEvent = urand(1, 3);
m_uiOzDeathCount = 0;
+ OptionalBossCount = 0;
}
uint32 m_auiEncounter[MAX_ENCOUNTER];
@@ -71,6 +79,7 @@ public:
uint32 m_uiOperaEvent;
uint32 m_uiOzDeathCount;
+ uint32 OptionalBossCount;
ObjectGuid m_uiCurtainGUID;
ObjectGuid m_uiStageDoorLeftGUID;
@@ -107,6 +116,29 @@ public:
}
}
+ void OnUnitDeath(Unit* unit) override
+ {
+ Creature* creature = unit->ToCreature();
+ if (!creature)
+ return;
+
+ switch (creature->GetEntry())
+ {
+ case NPC_COLDMIST_WIDOW:
+ case NPC_COLDMIST_STALKER:
+ case NPC_SHADOWBAT:
+ case NPC_VAMPIRIC_SHADOWBAT:
+ case NPC_GREATER_SHADOWBAT:
+ case NPC_PHASE_HOUND:
+ case NPC_DREADBEAST:
+ case NPC_SHADOWBEAST:
+ SetData(TYPE_OPTIONAL_BOSS, NOT_STARTED);
+ break;
+ default:
+ break;
+ }
+ }
+
void SetData(uint32 type, uint32 uiData) override
{
switch (type)
@@ -118,7 +150,28 @@ public:
m_auiEncounter[1] = uiData;
break;
case TYPE_MAIDEN: m_auiEncounter[2] = uiData; break;
- case TYPE_OPTIONAL_BOSS: m_auiEncounter[3] = uiData; break;
+ case TYPE_OPTIONAL_BOSS:
+ m_auiEncounter[3] = uiData;
+ if (uiData == NOT_STARTED)
+ {
+ ++OptionalBossCount;
+ if (OptionalBossCount == 50)
+ {
+ switch (urand(0, 2))
+ {
+ case 0:
+ instance->SummonCreature(NPC_HYAKISS_THE_LURKER, OptionalSpawn[0]);
+ break;
+ case 1:
+ instance->SummonCreature(NPC_ROKAD_THE_RAVAGER, OptionalSpawn[1]);
+ break;
+ case 2:
+ instance->SummonCreature(NPC_SHADIKITH_THE_GLIDER, OptionalSpawn[2]);
+ break;
+ }
+ }
+ }
+ break;
case TYPE_OPERA:
m_auiEncounter[4] = uiData;
if (uiData == DONE)
diff --git a/src/server/scripts/EasternKingdoms/Karazhan/karazhan.h b/src/server/scripts/EasternKingdoms/Karazhan/karazhan.h
index db68484c4a8..4d86492257c 100644
--- a/src/server/scripts/EasternKingdoms/Karazhan/karazhan.h
+++ b/src/server/scripts/EasternKingdoms/Karazhan/karazhan.h
@@ -64,4 +64,20 @@ enum OperaEvents
EVENT_RAJ = 3
};
+enum MiscCreatures
+{
+ NPC_HYAKISS_THE_LURKER = 16179,
+ NPC_ROKAD_THE_RAVAGER = 16181,
+ NPC_SHADIKITH_THE_GLIDER = 16180,
+
+ // Trash
+ NPC_COLDMIST_WIDOW = 16171,
+ NPC_COLDMIST_STALKER = 16170,
+ NPC_SHADOWBAT = 16173,
+ NPC_VAMPIRIC_SHADOWBAT = 16175,
+ NPC_GREATER_SHADOWBAT = 16174,
+ NPC_PHASE_HOUND = 16178,
+ NPC_DREADBEAST = 16177,
+ NPC_SHADOWBEAST = 16176
+};
#endif
diff --git a/src/server/scripts/EasternKingdoms/MagistersTerrace/instance_magisters_terrace.cpp b/src/server/scripts/EasternKingdoms/MagistersTerrace/instance_magisters_terrace.cpp
index b48f0edec25..6386bb50e1a 100644
--- a/src/server/scripts/EasternKingdoms/MagistersTerrace/instance_magisters_terrace.cpp
+++ b/src/server/scripts/EasternKingdoms/MagistersTerrace/instance_magisters_terrace.cpp
@@ -16,8 +16,10 @@
*/
#include "ScriptMgr.h"
+#include "ScriptedCreature.h"
#include "InstanceScript.h"
#include "magisters_terrace.h"
+#include "EventMap.h"
/*
0 - Selin Fireheart
@@ -36,6 +38,8 @@ DoorData const doorData[] =
{ 0, 0, DOOR_TYPE_ROOM } // END
};
+Position const KalecgosSpawnPos = { 164.3747f, -397.1197f, 2.151798f, 1.66219f };
+
class instance_magisters_terrace : public InstanceMapScript
{
public:
@@ -93,6 +97,10 @@ class instance_magisters_terrace : public InstanceMapScript
case NPC_DELRISSA:
DelrissaGUID = creature->GetGUID();
break;
+ case NPC_KALECGOS:
+ case NPC_HUMAN_KALECGOS:
+ KalecgosGUID = creature->GetGUID();
+ break;
default:
break;
}
@@ -139,6 +147,25 @@ class instance_magisters_terrace : public InstanceMapScript
}
}
+ void ProcessEvent(WorldObject* obj, uint32 eventId) override
+ {
+ if (eventId == EVENT_SPAWN_KALECGOS)
+ if (!ObjectAccessor::GetCreature(*obj, KalecgosGUID) && Events.Empty())
+ Events.ScheduleEvent(EVENT_SPAWN_KALECGOS, Minutes(1));
+ }
+
+ void Update(uint32 diff) override
+ {
+ Events.Update(diff);
+
+ if (Events.ExecuteEvent() == EVENT_SPAWN_KALECGOS)
+ if (Creature* kalecgos = instance->SummonCreature(NPC_KALECGOS, KalecgosSpawnPos))
+ {
+ kalecgos->GetMotionMaster()->MovePath(PATH_KALECGOS_FLIGHT, false);
+ kalecgos->AI()->Talk(SAY_KALECGOS_SPAWN);
+ }
+ }
+
bool SetBossState(uint32 type, EncounterState state) override
{
if (!InstanceScript::SetBossState(type, state))
@@ -177,10 +204,12 @@ class instance_magisters_terrace : public InstanceMapScript
}
protected:
+ EventMap Events;
ObjectGuid SelinGUID;
ObjectGuid DelrissaGUID;
ObjectGuid KaelStatue[2];
ObjectGuid EscapeOrbGUID;
+ ObjectGuid KalecgosGUID;
uint32 DelrissaDeathCount;
};
diff --git a/src/server/scripts/EasternKingdoms/MagistersTerrace/magisters_terrace.cpp b/src/server/scripts/EasternKingdoms/MagistersTerrace/magisters_terrace.cpp
index e216a024468..5b90ac8ccf4 100644
--- a/src/server/scripts/EasternKingdoms/MagistersTerrace/magisters_terrace.cpp
+++ b/src/server/scripts/EasternKingdoms/MagistersTerrace/magisters_terrace.cpp
@@ -31,7 +31,8 @@ EndContentData */
#include "ScriptedCreature.h"
#include "ScriptedGossip.h"
#include "Player.h"
-#include "SpellInfo.h"
+#include "magisters_terrace.h"
+#include "EventMap.h"
/*######
## npc_kalecgos
@@ -39,30 +40,29 @@ EndContentData */
enum Spells
{
- SPELL_TRANSFORM_TO_KAEL = 44670,
+ SPELL_KALECGOS_TRANSFORM = 44670,
+ SPELL_TRANSFORM_VISUAL = 24085,
+ SPELL_CAMERA_SHAKE = 44762,
SPELL_ORB_KILL_CREDIT = 46307
};
-enum Creatures
+enum MovementPoints
{
- NPC_KAEL = 24848 //human form entry
+ POINT_ID_PREPARE_LANDING = 6
};
-enum Misc
+enum EventIds
{
- POINT_ID_LAND = 1
+ EVENT_KALECGOS_TRANSFORM = 1,
+ EVENT_KALECGOS_LANDING = 2
};
-const float afKaelLandPoint[] = {225.045f, -276.236f, -5.434f};
-
#define GOSSIP_ITEM_KAEL_1 "Who are you?"
#define GOSSIP_ITEM_KAEL_2 "What can we do to assist you?"
#define GOSSIP_ITEM_KAEL_3 "What brings you to the Sunwell?"
#define GOSSIP_ITEM_KAEL_4 "You're not alone here?"
#define GOSSIP_ITEM_KAEL_5 "What would Kil'jaeden want with a mortal woman?"
-// This is friendly keal that appear after used Orb.
-// If we assume DB handle summon, summon appear somewhere outside the platform where Orb is
class npc_kalecgos : public CreatureScript
{
public:
@@ -115,52 +115,46 @@ public:
struct npc_kalecgosAI : public ScriptedAI
{
- npc_kalecgosAI(Creature* creature) : ScriptedAI(creature)
- {
- Initialize();
- }
+ npc_kalecgosAI(Creature* creature) : ScriptedAI(creature) { }
- void Initialize()
+ void MovementInform(uint32 type, uint32 pointId) override
{
- m_uiTransformTimer = 0;
- }
-
- uint32 m_uiTransformTimer;
-
- void Reset() override
- {
- Initialize();
-
- // we must assume he appear as dragon somewhere outside the platform of orb, and then move directly to here
- if (me->GetEntry() != NPC_KAEL)
- me->GetMotionMaster()->MovePoint(POINT_ID_LAND, afKaelLandPoint[0], afKaelLandPoint[1], afKaelLandPoint[2]);
- }
-
- void MovementInform(uint32 uiType, uint32 uiPointId) override
- {
- if (uiType != POINT_MOTION_TYPE)
+ if (type != WAYPOINT_MOTION_TYPE)
return;
- if (uiPointId == POINT_ID_LAND)
- m_uiTransformTimer = MINUTE*IN_MILLISECONDS;
+ if (pointId == POINT_ID_PREPARE_LANDING)
+ {
+ me->HandleEmoteCommand(EMOTE_ONESHOT_LAND);
+ me->SetDisableGravity(false);
+ me->SetHover(false);
+ events.ScheduleEvent(EVENT_KALECGOS_LANDING, Seconds(2));
+ }
}
- void UpdateAI(uint32 uiDiff) override
+ void UpdateAI(uint32 diff) override
{
- if (m_uiTransformTimer)
+ events.Update(diff);
+
+ switch (events.ExecuteEvent())
{
- if (m_uiTransformTimer <= uiDiff)
- {
+ case EVENT_KALECGOS_LANDING:
+ DoCastAOE(SPELL_CAMERA_SHAKE);
+ me->SetObjectScale(0.6f);
+ events.ScheduleEvent(EVENT_KALECGOS_TRANSFORM, Seconds(1));
+ break;
+ case EVENT_KALECGOS_TRANSFORM:
DoCast(me, SPELL_ORB_KILL_CREDIT, true);
-
- // Transform and update entry, now ready for quest/read gossip
- DoCast(me, SPELL_TRANSFORM_TO_KAEL, false);
- me->UpdateEntry(NPC_KAEL);
-
- m_uiTransformTimer = 0;
- } else m_uiTransformTimer -= uiDiff;
+ DoCast(me, SPELL_TRANSFORM_VISUAL, false);
+ DoCast(me, SPELL_KALECGOS_TRANSFORM, false);
+ me->UpdateEntry(NPC_HUMAN_KALECGOS);
+ break;
+ default:
+ break;
}
}
+
+ private:
+ EventMap events;
};
};
diff --git a/src/server/scripts/EasternKingdoms/MagistersTerrace/magisters_terrace.h b/src/server/scripts/EasternKingdoms/MagistersTerrace/magisters_terrace.h
index 917ad0eb50b..05718dfc1dd 100644
--- a/src/server/scripts/EasternKingdoms/MagistersTerrace/magisters_terrace.h
+++ b/src/server/scripts/EasternKingdoms/MagistersTerrace/magisters_terrace.h
@@ -42,7 +42,9 @@ enum CreatureIds
{
NPC_SELIN = 24723,
NPC_DELRISSA = 24560,
- NPC_FEL_CRYSTAL = 24722
+ NPC_FEL_CRYSTAL = 24722,
+ NPC_KALECGOS = 24844,
+ NPC_HUMAN_KALECGOS = 24848
};
enum GameObjectIds
@@ -57,4 +59,19 @@ enum GameObjectIds
GO_ESCAPE_ORB = 188173
};
+enum InstanceEventIds
+{
+ EVENT_SPAWN_KALECGOS = 16547
+};
+
+enum InstanceText
+{
+ SAY_KALECGOS_SPAWN = 0
+};
+
+enum MovementData
+{
+ PATH_KALECGOS_FLIGHT = 248440
+};
+
#endif
diff --git a/src/server/scripts/EasternKingdoms/Uldaman/uldaman.cpp b/src/server/scripts/EasternKingdoms/Uldaman/uldaman.cpp
index 622c8f0de01..447dbcd67f9 100644
--- a/src/server/scripts/EasternKingdoms/Uldaman/uldaman.cpp
+++ b/src/server/scripts/EasternKingdoms/Uldaman/uldaman.cpp
@@ -132,7 +132,10 @@ public:
## at_map_chamber
######*/
-#define QUEST_HIDDEN_CHAMBER 2240
+enum MapChamber
+{
+ QUEST_HIDDEN_CHAMBER = 2240
+};
class AreaTrigger_at_map_chamber : public AreaTriggerScript
{
diff --git a/src/server/scripts/EasternKingdoms/zone_undercity.cpp b/src/server/scripts/EasternKingdoms/zone_undercity.cpp
index 43143748661..99556587e65 100644
--- a/src/server/scripts/EasternKingdoms/zone_undercity.cpp
+++ b/src/server/scripts/EasternKingdoms/zone_undercity.cpp
@@ -157,7 +157,7 @@ public:
{
if (Creature* target = ObjectAccessor::GetCreature(*summoned, targetGUID))
{
- target->GetMotionMaster()->MoveJump(target->GetPositionX(), target->GetPositionY(), me->GetPositionZ() + 15.0f, 0);
+ target->GetMotionMaster()->MoveJump(target->GetPositionX(), target->GetPositionY(), me->GetPositionZ() + 15.0f, me->GetOrientation(), 0);
target->SetPosition(target->GetPositionX(), target->GetPositionY(), me->GetPositionZ()+15.0f, 0.0f);
summoned->CastSpell(target, SPELL_RIBBON_OF_SOULS, false);
}
@@ -317,59 +317,6 @@ public:
};
/*######
-## npc_parqual_fintallas
-######*/
-
-enum ParqualFintallas
-{
- SPELL_MARK_OF_SHAME = 6767
-};
-
-#define GOSSIP_HPF1 "Gul'dan"
-#define GOSSIP_HPF2 "Kel'Thuzad"
-#define GOSSIP_HPF3 "Ner'zhul"
-
-class npc_parqual_fintallas : public CreatureScript
-{
-public:
- npc_parqual_fintallas() : CreatureScript("npc_parqual_fintallas") { }
-
- bool OnGossipSelect(Player* player, Creature* creature, uint32 /*sender*/, uint32 action) override
- {
- player->PlayerTalkClass->ClearMenus();
- if (action == GOSSIP_ACTION_INFO_DEF+1)
- {
- player->CLOSE_GOSSIP_MENU();
- creature->CastSpell(player, SPELL_MARK_OF_SHAME, false);
- }
- if (action == GOSSIP_ACTION_INFO_DEF+2)
- {
- player->CLOSE_GOSSIP_MENU();
- player->AreaExploredOrEventHappens(6628);
- }
- return true;
- }
-
- bool OnGossipHello(Player* player, Creature* creature) override
- {
- if (creature->IsQuestGiver())
- player->PrepareQuestMenu(creature->GetGUID());
-
- if (player->GetQuestStatus(6628) == QUEST_STATUS_INCOMPLETE && !player->HasAura(SPELL_MARK_OF_SHAME))
- {
- player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_HPF1, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF+1);
- player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_HPF2, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF+1);
- player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_HPF3, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF+2);
- player->SEND_GOSSIP_MENU(5822, creature->GetGUID());
- }
- else
- player->SEND_GOSSIP_MENU(5821, creature->GetGUID());
-
- return true;
- }
-};
-
-/*######
## AddSC
######*/
@@ -377,5 +324,4 @@ void AddSC_undercity()
{
new npc_lady_sylvanas_windrunner();
new npc_highborne_lamenter();
- new npc_parqual_fintallas();
}
diff --git a/src/server/scripts/EasternKingdoms/zone_western_plaguelands.cpp b/src/server/scripts/EasternKingdoms/zone_western_plaguelands.cpp
index 6cf86c3069f..a01b93a140b 100644
--- a/src/server/scripts/EasternKingdoms/zone_western_plaguelands.cpp
+++ b/src/server/scripts/EasternKingdoms/zone_western_plaguelands.cpp
@@ -25,7 +25,6 @@ EndScriptData */
/* ContentData
npcs_dithers_and_arbington
-npc_myranda_the_hag
npc_the_scourge_cauldron
npc_andorhal_tower
EndContentData */
@@ -33,7 +32,6 @@ EndContentData */
#include "ScriptMgr.h"
#include "ScriptedCreature.h"
#include "ScriptedGossip.h"
-#include "ScriptedEscortAI.h"
#include "Player.h"
#include "WorldSession.h"
@@ -41,12 +39,23 @@ EndContentData */
## npcs_dithers_and_arbington
######*/
-#define GOSSIP_HDA1 "What does the Felstone Field Cauldron need?"
-#define GOSSIP_HDA2 "What does the Dalson's Tears Cauldron need?"
-#define GOSSIP_HDA3 "What does the Writhing Haunt Cauldron need?"
-#define GOSSIP_HDA4 "What does the Gahrron's Withering Cauldron need?"
-
-#define GOSSIP_SDA1 "Thanks, i need a Vitreous Focuser"
+enum DithersAndArbington
+{
+ GOSSIP_ITEM_ID_FELSTONE_FIELD = 0,
+ GOSSIP_ITEM_ID_DALSON_S_TEARS = 1,
+ GOSSIP_ITEM_ID_WRITHING_HAUNT = 2,
+ GOSSIP_ITEM_ID_GAHRRON_S_WITH = 3,
+ GOSSIP_MENU_ID_LETS_GET_TO_WORK = 3223,
+ GOSSIP_MENU_ID_VITREOUS_FOCUSER = 3229,
+ NPC_TEXT_OSSEOUS_AGITATORS = 3980,
+ NPC_TEXT_SOMATIC_INTENSIFIERS_1 = 3981,
+ NPC_TEXT_SOMATIC_INTENSIFIERS_2 = 3982,
+ NPC_TEXT_ECTOPLASMIC_RESONATORS = 3983,
+ NPC_TEXT_LET_S_GET_TO_WORK = 3985,
+ QUEST_MISSION_ACCOMPLISHED_H = 5237,
+ QUEST_MISSION_ACCOMPLISHED_A = 5238,
+ CREATE_ITEM_VITREOUS_FOCUSER = 17529
+};
class npcs_dithers_and_arbington : public CreatureScript
{
@@ -62,24 +71,24 @@ public:
player->GetSession()->SendListInventory(creature->GetGUID());
break;
case GOSSIP_ACTION_INFO_DEF+1:
- player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_SDA1, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF+5);
- player->SEND_GOSSIP_MENU(3980, creature->GetGUID());
+ player->ADD_GOSSIP_ITEM_DB(GOSSIP_MENU_ID_VITREOUS_FOCUSER, 0, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF+5);
+ player->SEND_GOSSIP_MENU(NPC_TEXT_OSSEOUS_AGITATORS, creature->GetGUID());
break;
case GOSSIP_ACTION_INFO_DEF+2:
- player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_SDA1, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF+5);
- player->SEND_GOSSIP_MENU(3981, creature->GetGUID());
+ player->ADD_GOSSIP_ITEM_DB(GOSSIP_MENU_ID_VITREOUS_FOCUSER, 0, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF+5);
+ player->SEND_GOSSIP_MENU(NPC_TEXT_SOMATIC_INTENSIFIERS_1, creature->GetGUID());
break;
case GOSSIP_ACTION_INFO_DEF+3:
- player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_SDA1, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF+5);
- player->SEND_GOSSIP_MENU(3982, creature->GetGUID());
+ player->ADD_GOSSIP_ITEM_DB(GOSSIP_MENU_ID_VITREOUS_FOCUSER, 0, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF+5);
+ player->SEND_GOSSIP_MENU(NPC_TEXT_SOMATIC_INTENSIFIERS_2, creature->GetGUID());
break;
case GOSSIP_ACTION_INFO_DEF+4:
- player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_SDA1, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF+5);
- player->SEND_GOSSIP_MENU(3983, creature->GetGUID());
+ player->ADD_GOSSIP_ITEM_DB(GOSSIP_MENU_ID_VITREOUS_FOCUSER, 0, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF+5);
+ player->SEND_GOSSIP_MENU(NPC_TEXT_ECTOPLASMIC_RESONATORS, creature->GetGUID());
break;
case GOSSIP_ACTION_INFO_DEF+5:
player->CLOSE_GOSSIP_MENU();
- creature->CastSpell(player, 17529, false);
+ creature->CastSpell(player, CREATE_ITEM_VITREOUS_FOCUSER, false);
break;
}
return true;
@@ -93,61 +102,13 @@ public:
if (creature->IsVendor())
player->ADD_GOSSIP_ITEM(GOSSIP_ICON_VENDOR, GOSSIP_TEXT_BROWSE_GOODS, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_TRADE);
- if (player->GetQuestRewardStatus(5237) || player->GetQuestRewardStatus(5238))
+ if (player->GetQuestRewardStatus(QUEST_MISSION_ACCOMPLISHED_H) || player->GetQuestRewardStatus(QUEST_MISSION_ACCOMPLISHED_A))
{
- player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_HDA1, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF+1);
- player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_HDA2, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF+2);
- player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_HDA3, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF+3);
- player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_HDA4, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF+4);
- player->SEND_GOSSIP_MENU(3985, creature->GetGUID());
- }
- else
- player->SEND_GOSSIP_MENU(player->GetGossipTextId(creature), creature->GetGUID());
-
- return true;
- }
-};
-
-/*######
-## npc_myranda_the_hag
-######*/
-
-enum Myranda
-{
- ILLUSION_GOSSIP = 4773,
- QUEST_SUBTERFUGE = 5862,
- QUEST_IN_DREAMS = 5944,
- SPELL_SCARLET_ILLUSION = 17961
-};
-
-class npc_myranda_the_hag : public CreatureScript
-{
-public:
- npc_myranda_the_hag() : CreatureScript("npc_myranda_the_hag") { }
-
- bool OnGossipSelect(Player* player, Creature* /*creature*/, uint32 /*sender*/, uint32 action) override
- {
- player->PlayerTalkClass->ClearMenus();
- if (action == GOSSIP_ACTION_INFO_DEF + 1)
- {
- player->CLOSE_GOSSIP_MENU();
- player->CastSpell(player, SPELL_SCARLET_ILLUSION, false);
- }
- return true;
- }
-
- bool OnGossipHello(Player* player, Creature* creature) override
- {
- if (creature->IsQuestGiver())
- player->PrepareQuestMenu(creature->GetGUID());
-
- if (player->GetQuestStatus(QUEST_SUBTERFUGE) == QUEST_STATUS_COMPLETE &&
- player->GetQuestStatus(QUEST_IN_DREAMS) != QUEST_STATUS_COMPLETE &&
- !player->HasAura(SPELL_SCARLET_ILLUSION))
- {
- player->ADD_GOSSIP_ITEM_DB(Player::GetDefaultGossipMenuForSource(creature), 0, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1);
- player->SEND_GOSSIP_MENU(ILLUSION_GOSSIP, creature->GetGUID());
- return true;
+ player->ADD_GOSSIP_ITEM_DB(GOSSIP_MENU_ID_LETS_GET_TO_WORK, GOSSIP_ITEM_ID_FELSTONE_FIELD, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF+1);
+ player->ADD_GOSSIP_ITEM_DB(GOSSIP_MENU_ID_LETS_GET_TO_WORK, GOSSIP_ITEM_ID_DALSON_S_TEARS, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF+2);
+ player->ADD_GOSSIP_ITEM_DB(GOSSIP_MENU_ID_LETS_GET_TO_WORK, GOSSIP_ITEM_ID_WRITHING_HAUNT, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF+3);
+ player->ADD_GOSSIP_ITEM_DB(GOSSIP_MENU_ID_LETS_GET_TO_WORK, GOSSIP_ITEM_ID_GAHRRON_S_WITH, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF+4);
+ player->SEND_GOSSIP_MENU(NPC_TEXT_LET_S_GET_TO_WORK, creature->GetGUID());
}
else
player->SEND_GOSSIP_MENU(player->GetGossipTextId(creature), creature->GetGUID());
@@ -184,7 +145,7 @@ public:
me->DealDamage(me, me->GetHealth(), NULL, DIRECT_DAMAGE, SPELL_SCHOOL_MASK_NORMAL, NULL, false);
//override any database `spawntimesecs` to prevent duplicated summons
uint32 rTime = me->GetRespawnDelay();
- if (rTime<600)
+ if (rTime < 600)
me->SetRespawnDelay(600);
}
@@ -263,7 +224,6 @@ public:
}
void MoveInLineOfSight(Unit* who) override
-
{
if (!who || who->GetTypeId() != TYPEID_PLAYER)
return;
@@ -275,14 +235,9 @@ public:
};
};
-/*######
-##
-######*/
-
void AddSC_western_plaguelands()
{
new npcs_dithers_and_arbington();
- new npc_myranda_the_hag();
new npc_the_scourge_cauldron();
new npc_andorhal_tower();
}
diff --git a/src/server/scripts/Kalimdor/WailingCaverns/wailing_caverns.cpp b/src/server/scripts/Kalimdor/WailingCaverns/wailing_caverns.cpp
index 63c56e29414..29a754d5895 100644
--- a/src/server/scripts/Kalimdor/WailingCaverns/wailing_caverns.cpp
+++ b/src/server/scripts/Kalimdor/WailingCaverns/wailing_caverns.cpp
@@ -58,6 +58,10 @@ enum Enums
SAY_FAREWELL = 5,
SAY_ATTACKED = 11,
+ GOSSIP_OPTION_LET_EVENT_BEGIN = 201,
+ NPC_TEXT_NARALEX_SLEEPS_AGAIN = 698,
+ NPC_TEXT_FANGLORDS_ARE_DEAD = 699,
+
SPELL_MARK_OF_THE_WILD_RANK_2 = 5232,
SPELL_SERPENTINE_CLEANSING = 6270,
SPELL_NARALEXS_AWAKENING = 6271,
@@ -70,10 +74,6 @@ enum Enums
NPC_MUTANUS_THE_DEVOURER = 3654,
};
-#define GOSSIP_ID_START_1 698 //Naralex sleeps again!
-#define GOSSIP_ID_START_2 699 //The fanglords are dead!
-#define GOSSIP_ITEM_NARALEX "Let the event begin!"
-
class npc_disciple_of_naralex : public CreatureScript
{
public:
@@ -116,8 +116,8 @@ public:
if ((instance->GetData(TYPE_LORD_COBRAHN) == DONE) && (instance->GetData(TYPE_LORD_PYTHAS) == DONE) &&
(instance->GetData(TYPE_LADY_ANACONDRA) == DONE) && (instance->GetData(TYPE_LORD_SERPENTIS) == DONE))
{
- player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_ITEM_NARALEX, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1);
- player->SEND_GOSSIP_MENU(GOSSIP_ID_START_2, creature->GetGUID());
+ player->ADD_GOSSIP_ITEM_DB(GOSSIP_OPTION_LET_EVENT_BEGIN, 0, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1);
+ player->SEND_GOSSIP_MENU(NPC_TEXT_FANGLORDS_ARE_DEAD, creature->GetGUID());
if (!instance->GetData(TYPE_NARALEX_YELLED))
{
@@ -127,7 +127,7 @@ public:
}
else
{
- player->SEND_GOSSIP_MENU(GOSSIP_ID_START_1, creature->GetGUID());
+ player->SEND_GOSSIP_MENU(NPC_TEXT_NARALEX_SLEEPS_AGAIN, creature->GetGUID());
}
}
return true;
diff --git a/src/server/scripts/Kalimdor/zone_ashenvale.cpp b/src/server/scripts/Kalimdor/zone_ashenvale.cpp
index 52a83c02a8a..50730507b8f 100644
--- a/src/server/scripts/Kalimdor/zone_ashenvale.cpp
+++ b/src/server/scripts/Kalimdor/zone_ashenvale.cpp
@@ -31,6 +31,7 @@ EndContentData */
#include "ScriptedCreature.h"
#include "ScriptedEscortAI.h"
#include "Player.h"
+#include "SpellScript.h"
/*####
# npc_ruul_snowhoof
@@ -344,9 +345,42 @@ class go_naga_brazier : public GameObjectScript
}
};
+enum KingoftheFoulwealdMisc
+{
+ GO_BANNER = 178205
+};
+
+class spell_destroy_karangs_banner : public SpellScriptLoader
+{
+ public:
+ spell_destroy_karangs_banner() : SpellScriptLoader("spell_destroy_karangs_banner") { }
+
+ class spell_destroy_karangs_banner_SpellScript : public SpellScript
+ {
+ PrepareSpellScript(spell_destroy_karangs_banner_SpellScript);
+
+ void HandleAfterCast()
+ {
+ if (GameObject* banner = GetCaster()->FindNearestGameObject(GO_BANNER, GetSpellInfo()->GetMaxRange(true)))
+ banner->Delete();
+ }
+
+ void Register() override
+ {
+ AfterCast += SpellCastFn(spell_destroy_karangs_banner_SpellScript::HandleAfterCast);
+ }
+ };
+
+ SpellScript* GetSpellScript() const override
+ {
+ return new spell_destroy_karangs_banner_SpellScript();
+ }
+};
+
void AddSC_ashenvale()
{
new npc_ruul_snowhoof();
new npc_muglash();
new go_naga_brazier();
+ new spell_destroy_karangs_banner();
}
diff --git a/src/server/scripts/Northrend/ChamberOfAspects/RubySanctum/boss_general_zarithrian.cpp b/src/server/scripts/Northrend/ChamberOfAspects/RubySanctum/boss_general_zarithrian.cpp
index fb5642c6f3b..84d7d92acfe 100644
--- a/src/server/scripts/Northrend/ChamberOfAspects/RubySanctum/boss_general_zarithrian.cpp
+++ b/src/server/scripts/Northrend/ChamberOfAspects/RubySanctum/boss_general_zarithrian.cpp
@@ -135,9 +135,9 @@ class boss_general_zarithrian : public CreatureScript
Talk(SAY_KILL);
}
- bool CanAIAttack(Unit const* /*target*/) const override
+ bool CanAIAttack(Unit const* target) const override
{
- return (instance->GetBossState(DATA_SAVIANA_RAGEFIRE) == DONE && instance->GetBossState(DATA_BALTHARUS_THE_WARBORN) == DONE);
+ return (instance->GetBossState(DATA_SAVIANA_RAGEFIRE) == DONE && instance->GetBossState(DATA_BALTHARUS_THE_WARBORN) == DONE && BossAI::CanAIAttack(target));
}
void UpdateAI(uint32 diff) override
diff --git a/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_faction_champions.cpp b/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_faction_champions.cpp
index 1e860fb6ec5..6c1b516c7de 100644
--- a/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_faction_champions.cpp
+++ b/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_faction_champions.cpp
@@ -461,13 +461,13 @@ class boss_toc_champion_controller : public CreatureScript
if (playerTeam == ALLIANCE)
{
temp->SetHomePosition(vChampionJumpTarget[pos].GetPositionX(), vChampionJumpTarget[pos].GetPositionY(), vChampionJumpTarget[pos].GetPositionZ(), 0);
- temp->GetMotionMaster()->MoveJump(vChampionJumpTarget[pos].GetPositionX(), vChampionJumpTarget[pos].GetPositionY(), vChampionJumpTarget[pos].GetPositionZ(), 20.0f, 20.0f);
+ temp->GetMotionMaster()->MoveJump(vChampionJumpTarget[pos], 20.0f, 20.0f);
temp->SetOrientation(0);
}
else
{
temp->SetHomePosition((ToCCommonLoc[1].GetPositionX()*2)-vChampionJumpTarget[pos].GetPositionX(), vChampionJumpTarget[pos].GetPositionY(), vChampionJumpTarget[pos].GetPositionZ(), 3);
- temp->GetMotionMaster()->MoveJump((ToCCommonLoc[1].GetPositionX()*2)-vChampionJumpTarget[pos].GetPositionX(), vChampionJumpTarget[pos].GetPositionY(), vChampionJumpTarget[pos].GetPositionZ(), 20.0f, 20.0f);
+ temp->GetMotionMaster()->MoveJump((ToCCommonLoc[1].GetPositionX() * 2) - vChampionJumpTarget[pos].GetPositionX(), vChampionJumpTarget[pos].GetPositionY(), vChampionJumpTarget[pos].GetPositionZ(), vChampionJumpTarget[pos].GetOrientation(), 20.0f, 20.0f);
temp->SetOrientation(3);
}
}
diff --git a/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_northrend_beasts.cpp b/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_northrend_beasts.cpp
index 83daa6e35a2..e3720503d0a 100644
--- a/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_northrend_beasts.cpp
+++ b/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/boss_northrend_beasts.cpp
@@ -390,7 +390,7 @@ class npc_snobold_vassal : public CreatureScript
else if (Unit* target2 = SelectTarget(SELECT_TARGET_RANDOM, 0, 0.0f, true))
{
_targetGUID = target2->GetGUID();
- me->GetMotionMaster()->MoveJump(target2->GetPositionX(), target2->GetPositionY(), target2->GetPositionZ(), 15.0f, 15.0f);
+ me->GetMotionMaster()->MoveJump(*target2, 15.0f, 15.0f);
}
}
}
@@ -981,7 +981,7 @@ class boss_icehowl : public CreatureScript
events.ScheduleEvent(EVENT_WHIRL, urand(15*IN_MILLISECONDS, 30*IN_MILLISECONDS));
return;
case EVENT_MASSIVE_CRASH:
- me->GetMotionMaster()->MoveJump(ToCCommonLoc[1].GetPositionX(), ToCCommonLoc[1].GetPositionY(), ToCCommonLoc[1].GetPositionZ(), 20.0f, 20.0f, 0); // 1: Middle of the room
+ me->GetMotionMaster()->MoveJump(ToCCommonLoc[1], 20.0f, 20.0f, 0); // 1: Middle of the room
SetCombatMovement(false);
me->AttackStop();
_stage = 7; //Invalid (Do nothing more than move)
@@ -1034,7 +1034,7 @@ class boss_icehowl : public CreatureScript
_trampleTargetY = target->GetPositionY();
_trampleTargetZ = target->GetPositionZ();
// 2: Hop Backwards
- me->GetMotionMaster()->MoveJump(2*me->GetPositionX() - _trampleTargetX, 2*me->GetPositionY() - _trampleTargetY, me->GetPositionZ(), 30.0f, 20.0f, 0);
+ me->GetMotionMaster()->MoveJump(2*me->GetPositionX() - _trampleTargetX, 2*me->GetPositionY() - _trampleTargetY, me->GetPositionZ(), me->GetOrientation(), 30.0f, 20.0f, 0);
_stage = 7; //Invalid (Do nothing more than move)
}
else
diff --git a/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/instance_trial_of_the_crusader.cpp b/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/instance_trial_of_the_crusader.cpp
index e5ac2676ad0..b1a0f0217c4 100644
--- a/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/instance_trial_of_the_crusader.cpp
+++ b/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/instance_trial_of_the_crusader.cpp
@@ -22,12 +22,11 @@
#include "Player.h"
#include "TemporarySummon.h"
-AreaBoundary const* const mainBoundary = new CircleBoundary(Position(563.26f, 139.6f), 75.0);
BossBoundaryData const boundaries = {
- { BOSS_BEASTS, mainBoundary },
- { BOSS_JARAXXUS, mainBoundary },
- { BOSS_CRUSADERS, mainBoundary },
- { BOSS_VALKIRIES, mainBoundary },
+ { BOSS_BEASTS, new CircleBoundary(Position(563.26f, 139.6f), 75.0) },
+ { BOSS_JARAXXUS, new CircleBoundary(Position(563.26f, 139.6f), 75.0) },
+ { BOSS_CRUSADERS, new CircleBoundary(Position(563.26f, 139.6f), 75.0) },
+ { BOSS_VALKIRIES, new CircleBoundary(Position(563.26f, 139.6f), 75.0) },
{ BOSS_ANUBARAK, new EllipseBoundary(Position(746.0f, 135.0f), 100.0, 75.0) }
};
diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_deathbringer_saurfang.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_deathbringer_saurfang.cpp
index 7e9083115b8..3ea0de31764 100644
--- a/src/server/scripts/Northrend/IcecrownCitadel/boss_deathbringer_saurfang.cpp
+++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_deathbringer_saurfang.cpp
@@ -598,7 +598,7 @@ class boss_deathbringer_saurfang : public CreatureScript
if (target->GetTransport())
return false;
- return true;
+ return BossAI::CanAIAttack(target);
}
static uint32 const FightWonValue;
diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_professor_putricide.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_professor_putricide.cpp
index 0f721148b72..2db9d206a00 100644
--- a/src/server/scripts/Northrend/IcecrownCitadel/boss_professor_putricide.cpp
+++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_professor_putricide.cpp
@@ -317,14 +317,12 @@ class boss_professor_putricide : public CreatureScript
// no possible aura seen in sniff adding the aurastate
summon->ModifyAuraState(AURA_STATE_UNKNOWN22, true);
summon->CastSpell(summon, SPELL_GASEOUS_BLOAT_PROC, true);
- summon->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_KNOCK_BACK, true);
summon->SetReactState(REACT_PASSIVE);
break;
case NPC_VOLATILE_OOZE:
// no possible aura seen in sniff adding the aurastate
summon->ModifyAuraState(AURA_STATE_UNKNOWN19, true);
summon->CastSpell(summon, SPELL_OOZE_ERUPTION_SEARCH_PERIODIC, true);
- summon->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_KNOCK_BACK, true);
summon->SetReactState(REACT_PASSIVE);
break;
case NPC_CHOKING_GAS_BOMB:
diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_sindragosa.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_sindragosa.cpp
index 9bdbce81dbf..ac094588d35 100644
--- a/src/server/scripts/Northrend/IcecrownCitadel/boss_sindragosa.cpp
+++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_sindragosa.cpp
@@ -265,7 +265,7 @@ class boss_sindragosa : public CreatureScript
{
if (!instance->CheckRequiredBosses(DATA_SINDRAGOSA, victim->ToPlayer()))
{
- EnterEvadeMode();
+ EnterEvadeMode(EVADE_REASON_SEQUENCE_BREAK);
instance->DoCastSpellOnPlayers(LIGHT_S_HAMMER_TELEPORT);
return;
}
@@ -276,6 +276,13 @@ class boss_sindragosa : public CreatureScript
Talk(SAY_AGGRO);
}
+ void EnterEvadeMode(EvadeReason why) override
+ {
+ if (_isInAirPhase && why == EVADE_REASON_BOUNDARY)
+ return;
+ BossAI::EnterEvadeMode(why);
+ }
+
void JustReachedHome() override
{
BossAI::JustReachedHome();
diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_the_lich_king.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_the_lich_king.cpp
index 755bbd759e7..9fa624aaad3 100644
--- a/src/server/scripts/Northrend/IcecrownCitadel/boss_the_lich_king.cpp
+++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_the_lich_king.cpp
@@ -285,6 +285,7 @@ enum Phases
#define PHASE_TWO_THREE (events.IsInPhase(PHASE_TWO) ? PHASE_TWO : PHASE_THREE)
Position const CenterPosition = {503.6282f, -2124.655f, 840.8569f, 0.0f};
+Position const TirionSpawn = {505.2118f, -2124.353f, 840.9403f, 3.141593f};
Position const TirionIntro = {489.2970f, -2124.840f, 840.8569f, 0.0f};
Position const TirionCharge = {482.9019f, -2124.479f, 840.8570f, 0.0f};
Position const LichKingIntro[3] =
@@ -514,13 +515,33 @@ class boss_the_lich_king : public CreatureScript
_vileSpiritExplosions = 0;
}
- void Reset() override
+ void InitializeAI() override
+ {
+ SetupEncounter();
+ }
+
+ void JustRespawned() override
+ {
+ SetupEncounter();
+ }
+
+ void SetupEncounter()
{
_Reset();
me->SetReactState(REACT_PASSIVE);
events.SetPhase(PHASE_INTRO);
Initialize();
SetEquipmentSlots(true);
+
+ // Reset The Frozen Throne gameobjects
+ FrozenThroneResetWorker reset;
+ Trinity::GameObjectWorker<FrozenThroneResetWorker> worker(me, reset);
+ me->VisitNearbyGridObject(333.0f, worker);
+
+ // Reset any light override
+ me->GetMap()->SetZoneOverrideLight(AREA_THE_FROZEN_THRONE, 0, 5000);
+
+ me->SummonCreature(NPC_HIGHLORD_TIRION_FORDRING_LK, TirionSpawn, TEMPSUMMON_MANUAL_DESPAWN);
}
void JustDied(Unit* /*killer*/) override
@@ -556,40 +577,21 @@ class boss_the_lich_king : public CreatureScript
events.ScheduleEvent(EVENT_SHADOW_TRAP, 15500, 0, PHASE_ONE);
}
- void JustReachedHome() override
- {
- _JustReachedHome();
- instance->SetBossState(DATA_THE_LICH_KING, NOT_STARTED);
-
- // Reset The Frozen Throne gameobjects
- FrozenThroneResetWorker reset;
- Trinity::GameObjectWorker<FrozenThroneResetWorker> worker(me, reset);
- me->VisitNearbyGridObject(333.0f, worker);
-
- // Restore Tirion's gossip only after The Lich King fully resets to prevent
- // restarting the encounter while LK still runs back to spawn point
- if (Creature* tirion = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_HIGHLORD_TIRION_FORDRING)))
- tirion->SetFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_GOSSIP);
-
- // Reset any light override
- me->GetMap()->SetZoneOverrideLight(AREA_THE_FROZEN_THRONE, 0, 5000);
- }
-
bool CanAIAttack(Unit const* target) const override
{
// The Lich King must not select targets in frostmourne room if he killed everyone outside
- return !target->HasAura(SPELL_IN_FROSTMOURNE_ROOM);
+ return !target->HasAura(SPELL_IN_FROSTMOURNE_ROOM) && BossAI::CanAIAttack(target);
}
- void EnterEvadeMode(EvadeReason why) override
+ void EnterEvadeMode(EvadeReason /*why*/) override
{
- instance->SetBossState(DATA_THE_LICH_KING, FAIL);
- BossAI::EnterEvadeMode(why);
if (Creature* tirion = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_HIGHLORD_TIRION_FORDRING)))
- tirion->AI()->EnterEvadeMode();
+ tirion->DespawnOrUnsummon();
DoCastAOE(SPELL_KILL_FROSTMOURNE_PLAYERS);
EntryCheckPredicate pred(NPC_STRANGULATE_VEHICLE);
summons.DoAction(ACTION_TELEPORT_BACK, pred);
+ summons.DespawnAll();
+ _DespawnAtEvade();
}
void KilledUnit(Unit* victim) override
@@ -770,6 +772,8 @@ class boss_the_lich_king : public CreatureScript
case NPC_STRANGULATE_VEHICLE:
summons.Summon(summon);
return;
+ case NPC_HIGHLORD_TIRION_FORDRING_LK:
+ return;
default:
break;
}
@@ -1153,6 +1157,7 @@ class npc_tirion_fordring_tft : public CreatureScript
_events.Reset();
if (_instance->GetBossState(DATA_THE_LICH_KING) == DONE)
me->RemoveFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_GOSSIP);
+ me->LoadEquipment(1);
}
void MovementInform(uint32 type, uint32 id) override
diff --git a/src/server/scripts/Northrend/IcecrownCitadel/icecrown_citadel.h b/src/server/scripts/Northrend/IcecrownCitadel/icecrown_citadel.h
index 2af8e7f3643..5a5b7aefc02 100644
--- a/src/server/scripts/Northrend/IcecrownCitadel/icecrown_citadel.h
+++ b/src/server/scripts/Northrend/IcecrownCitadel/icecrown_citadel.h
@@ -115,7 +115,7 @@ enum DataTypes
DATA_ARTHAS_PLATFORM = 38,
DATA_TERENAS_MENETHIL = 39,
DATA_ENEMY_GUNSHIP = 40,
- DATA_UPPERSPIRE_TELE_ACT = 41,
+ DATA_UPPERSPIRE_TELE_ACT = 41, /// also used by conditions
DATA_BLOOD_QUEEN_LANA_THEL_COUNCIL = 42
};
@@ -569,14 +569,10 @@ class spell_trigger_spell_from_caster : public SpellScriptLoader
TriggerCastFlags _triggerFlags;
};
-template<class AI>
-CreatureAI* GetIcecrownCitadelAI(Creature* creature)
+template<class AI, class T>
+inline AI* GetIcecrownCitadelAI(T* obj)
{
- if (InstanceMap* instance = creature->GetMap()->ToInstanceMap())
- if (instance->GetInstanceScript())
- if (instance->GetScriptId() == sObjectMgr->GetScriptId(ICCScriptName))
- return new AI(creature);
- return NULL;
+ return GetInstanceAI<AI>(obj, ICCScriptName);
}
#endif // ICECROWN_CITADEL_H_
diff --git a/src/server/scripts/Northrend/IcecrownCitadel/icecrown_citadel_teleport.cpp b/src/server/scripts/Northrend/IcecrownCitadel/icecrown_citadel_teleport.cpp
index 7723a37094c..8b5e03203e1 100644
--- a/src/server/scripts/Northrend/IcecrownCitadel/icecrown_citadel_teleport.cpp
+++ b/src/server/scripts/Northrend/IcecrownCitadel/icecrown_citadel_teleport.cpp
@@ -15,6 +15,7 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include "GameObjectAI.h"
#include "InstanceScript.h"
#include "Player.h"
#include "ScriptedGossip.h"
@@ -22,57 +23,55 @@
#include "Spell.h"
#include "icecrown_citadel.h"
-#define GOSSIP_SENDER_ICC_PORT 631
+static std::vector<uint32> const TeleportSpells =
+{
+ LIGHT_S_HAMMER_TELEPORT, // 0
+ ORATORY_OF_THE_DAMNED_TELEPORT, // 1
+ 0, // 2
+ RAMPART_OF_SKULLS_TELEPORT, // 3
+ DEATHBRINGER_S_RISE_TELEPORT, // 4
+ UPPER_SPIRE_TELEPORT, // 5
+ SINDRAGOSA_S_LAIR_TELEPORT // 6
+};
class icecrown_citadel_teleport : public GameObjectScript
{
+ static_assert(DATA_UPPERSPIRE_TELE_ACT == 41, "icecrown_citadel.h DATA_UPPERSPIRE_TELE_ACT set to value != 41, gossip condition of the teleporters won't work as intended.");
+
public:
icecrown_citadel_teleport() : GameObjectScript("icecrown_citadel_teleport") { }
- bool OnGossipHello(Player* player, GameObject* go) override
+ struct icecrown_citadel_teleportAI : public GameObjectAI
{
- if (InstanceScript* instance = go->GetInstanceScript())
+ icecrown_citadel_teleportAI(GameObject* go) : GameObjectAI(go)
{
- if (instance->GetBossState(DATA_LORD_MARROWGAR) == DONE)
- {
- if (go->GetEntry() != GO_SCOURGE_TRANSPORTER_LIGHTSHAMMER)
- player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "Teleport to Light's Hammer.", GOSSIP_SENDER_ICC_PORT, LIGHT_S_HAMMER_TELEPORT);
- if (go->GetEntry() != GO_SCOURGE_TRANSPORTER_ORATORY)
- player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "Teleport to the Oratory of the Damned.", GOSSIP_SENDER_ICC_PORT, ORATORY_OF_THE_DAMNED_TELEPORT);
- }
- if (instance->GetBossState(DATA_LADY_DEATHWHISPER) == DONE && go->GetEntry() != GO_SCOURGE_TRANSPORTER_RAMPART)
- player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "Teleport to the Rampart of Skulls.", GOSSIP_SENDER_ICC_PORT, RAMPART_OF_SKULLS_TELEPORT);
- if (instance->GetBossState(DATA_ICECROWN_GUNSHIP_BATTLE) == DONE && go->GetEntry() != GO_SCOURGE_TRANSPORTER_DEATHBRINGER)
- player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "Teleport to the Deathbringer's Rise.", GOSSIP_SENDER_ICC_PORT, DEATHBRINGER_S_RISE_TELEPORT);
- if (instance->GetData(DATA_UPPERSPIRE_TELE_ACT) == DONE && go->GetEntry() != GO_SCOURGE_TRANSPORTER_UPPERSPIRE)
- player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "Teleport to the Upper Spire.", GOSSIP_SENDER_ICC_PORT, UPPER_SPIRE_TELEPORT);
- /// @todo Gauntlet event before Sindragosa
- if (instance->GetBossState(DATA_VALITHRIA_DREAMWALKER) == DONE && go->GetEntry() != GO_SCOURGE_TRANSPORTER_SINDRAGOSA)
- player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, "Teleport to Sindragosa's Lair", GOSSIP_SENDER_ICC_PORT, SINDRAGOSA_S_LAIR_TELEPORT);
}
- player->SEND_GOSSIP_MENU(player->GetGossipTextId(go), go->GetGUID());
- return true;
- }
+ bool GossipSelect(Player* player, uint32 /*menuId*/, uint32 gossipListId) override
+ {
+ if (gossipListId >= TeleportSpells.size())
+ return false;
- bool OnGossipSelect(Player* player, GameObject* /*go*/, uint32 sender, uint32 action) override
- {
- player->PlayerTalkClass->ClearMenus();
- player->CLOSE_GOSSIP_MENU();
- SpellInfo const* spell = sSpellMgr->GetSpellInfo(action);
- if (!spell)
- return false;
+ player->PlayerTalkClass->ClearMenus();
+ player->CLOSE_GOSSIP_MENU();
+ SpellInfo const* spell = sSpellMgr->GetSpellInfo(TeleportSpells[gossipListId]);
+ if (!spell)
+ return false;
- if (player->IsInCombat())
- {
- Spell::SendCastResult(player, spell, 0, SPELL_FAILED_AFFECTING_COMBAT);
- return true;
- }
+ if (player->IsInCombat())
+ {
+ Spell::SendCastResult(player, spell, 0, SPELL_FAILED_AFFECTING_COMBAT);
+ return true;
+ }
- if (sender == GOSSIP_SENDER_ICC_PORT)
player->CastSpell(player, spell, true);
+ return true;
+ }
+ };
- return true;
+ GameObjectAI* GetAI(GameObject* go) const override
+ {
+ return GetIcecrownCitadelAI<icecrown_citadel_teleportAI>(go);
}
};
diff --git a/src/server/scripts/Northrend/Naxxramas/boss_four_horsemen.cpp b/src/server/scripts/Northrend/Naxxramas/boss_four_horsemen.cpp
index 3e7de89edd9..a7a89f44d81 100644
--- a/src/server/scripts/Northrend/Naxxramas/boss_four_horsemen.cpp
+++ b/src/server/scripts/Northrend/Naxxramas/boss_four_horsemen.cpp
@@ -15,382 +15,681 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include "Player.h"
#include "ScriptMgr.h"
#include "ScriptedCreature.h"
#include "SpellScript.h"
#include "SpellAuraEffects.h"
#include "naxxramas.h"
-enum Horsemen
+enum Horseman
{
- HORSEMEN_THANE,
- HORSEMEN_LADY,
- HORSEMEN_BARON,
- HORSEMEN_SIR,
+ THANE = DATA_THANE,
+ LADY = DATA_LADY,
+ BARON = DATA_BARON,
+ SIR = DATA_SIR,
};
+static const std::vector<Horseman> horsemen = { THANE, LADY, BARON, SIR }; // for iterating
enum Spells
{
- SPELL_MARK_DAMAGE = 28836
+ /* all */
+ SPELL_MARK_DAMAGE = 28836,
+ SPELL_BERSERK = 26662,
+ SPELL_ENCOUNTER_CREDIT = 59450,
+
+ /* baron */
+ SPELL_BARON_MARK = 28834,
+ SPELL_UNHOLY_SHADOW = 28882,
+
+ /* thane */
+ SPELL_THANE_MARK = 28832,
+ SPELL_METEOR = 28884,
+
+ /* lady */
+ SPELL_SHADOW_BOLT = 57374,
+ SPELL_LADY_MARK = 28833,
+ SPELL_VOID_ZONE = 28863,
+ SPELL_UNYIELDING_PAIN = 57381,
+
+ /* sir */
+ SPELL_HOLY_BOLT = 57376,
+ SPELL_SIR_MARK = 28835,
+ SPELL_HOLY_WRATH = 28883,
+ SPELL_CONDEMNATION = 57377
};
-enum Events
+enum Actions
{
- EVENT_NONE,
- EVENT_MARK,
- EVENT_CAST,
- EVENT_BERSERK,
+ ACTION_BEGIN_MOVEMENT = 1,
+ ACTION_BEGIN_FIGHTING
+};
+
+enum HorsemenData
+{
+ DATA_HORSEMEN_IS_TIMED_KILL = Data::DATA_HORSEMEN_CHECK_ACHIEVEMENT_CREDIT, // inherit from naxxramas.h - this needs to be the first entry to ensure that there are no conflicts
+ DATA_MOVEMENT_FINISHED,
+ DATA_DEATH_TIME
};
-const Position WaypointPositions[12] =
+enum Events
{
- // Thane waypoints
- {2542.3f, -2984.1f, 241.49f, 5.362f},
- {2547.6f, -2999.4f, 241.34f, 5.049f},
- {2542.9f, -3015.0f, 241.35f, 4.654f},
- // Lady waypoints
- {2498.3f, -2961.8f, 241.28f, 3.267f},
- {2487.7f, -2959.2f, 241.28f, 2.890f},
- {2469.4f, -2947.6f, 241.28f, 2.576f},
- // Baron waypoints
- {2553.8f, -2968.4f, 241.33f, 5.757f},
- {2564.3f, -2972.5f, 241.33f, 5.890f},
- {2583.9f, -2971.67f, 241.35f, 0.008f},
- // Sir waypoints
- {2534.5f, -2921.7f, 241.53f, 1.363f},
- {2523.5f, -2902.8f, 241.28f, 2.095f},
- {2517.8f, -2896.6f, 241.28f, 2.315f},
+ /* all */
+ EVENT_BERSERK = 1,
+ EVENT_MARK,
+
+ /* rivendare */
+ EVENT_UNHOLYSHADOW,
+
+ /* thane */
+ EVENT_METEOR,
+
+ /* lady */
+ EVENT_VOIDZONE,
+
+ /* sir */
+ EVENT_HOLYWRATH
};
-const uint32 NPC_HORSEMEN[] = {16064, 16065, 30549, 16063};
-const uint32 SPELL_MARK[] = {28832, 28833, 28834, 28835};
-#define SPELL_PRIMARY(i) RAID_MODE(SPELL_PRIMARY_N[i], SPELL_PRIMARY_H[i])
-const uint32 SPELL_PRIMARY_N[] = {28884, 28863, 28882, 28883};
-const uint32 SPELL_PRIMARY_H[] = {57467, 57463, 57369, 57466};
-#define SPELL_SECONDARY(i) RAID_MODE(SPELL_SECONDARY_N[i], SPELL_SECONDARY_H[i])
-const uint32 SPELL_SECONDARY_N[]= {0, 57374, 0, 57376};
-const uint32 SPELL_SECONDARY_H[]= {0, 57464, 0, 57465};
-const uint32 SPELL_PUNISH[] = {0, 57381, 0, 57377};
-#define SPELL_BERSERK 26662
-
-enum FourHorsemen
+enum Yells
{
SAY_AGGRO = 0,
- SAY_TAUNT = 1,
SAY_SPECIAL = 2,
SAY_SLAY = 3,
- SAY_DEATH = 4
-};
+ SAY_DEATH = 4,
-class boss_four_horsemen : public CreatureScript
-{
-public:
- boss_four_horsemen() : CreatureScript("boss_four_horsemen") { }
+ EMOTE_RAGECAST = 7
+};
- CreatureAI* GetAI(Creature* creature) const override
- {
- return GetInstanceAI<boss_four_horsemenAI>(creature);
- }
+static const Position baronPath[3] = { { 2552.427f, -2969.737f, 241.3021f },{ 2566.759f, -2972.535f, 241.3217f },{ 2584.32f, -2971.96f, 241.3489f } };
+static const Position thanePath[3] = { { 2540.095f, -2983.192f, 241.3344f },{ 2546.005f, -2999.826f, 241.3665f },{ 2542.697f, -3014.055f, 241.3371f } };
+static const Position ladyPath[3] = { { 2507.94f, -2961.444f, 242.4557f },{ 2488.763f, -2960.007f, 241.2757f },{ 2468.26f, -2947.499f, 241.2753f } };
+static const Position sirPath[3] = { { 2533.141f, -2922.14f, 241.2764f },{ 2525.254f, -2905.907f, 241.2761f },{ 2517.636f, -2897.253f, 241.2758f } };
- struct boss_four_horsemenAI : public BossAI
- {
- boss_four_horsemenAI(Creature* creature) : BossAI(creature, BOSS_HORSEMEN)
+struct boss_four_horsemen_baseAI : public BossAI
+{
+ public:
+ Creature* getHorsemanHandle(Horseman who) const
{
- Initialize();
- id = Horsemen(0);
- for (uint8 i = 0; i < 4; ++i)
- if (me->GetEntry() == NPC_HORSEMEN[i])
- id = Horsemen(i);
- caster = (id == HORSEMEN_LADY || id == HORSEMEN_SIR);
+ if (_which == who)
+ return me;
+ else
+ return ObjectAccessor::GetCreature(*me, instance->GetGuidData(who));
}
-
- void Initialize()
+ boss_four_horsemen_baseAI(Creature* creature, Horseman which, Position const* initialPath) :
+ BossAI(creature, BOSS_HORSEMEN), _which(which), _initialPath(initialPath), _myMovementFinished(false), _nextMovement(0), _timeDied(0), _ourMovementFinished(false)
{
- uiEventStarterGUID.Clear();
- nextWP = 0;
- punishTimer = 2000;
- nextMovementStarted = false;
- movementCompleted = false;
- movementStarted = false;
- encounterActionAttack = false;
- encounterActionReset = false;
- doDelayPunish = false;
+ if (!me->IsAlive() && instance->GetBossState(BOSS_HORSEMEN) != DONE)
+ me->SetRespawnTime(10);
}
- Horsemen id;
- ObjectGuid uiEventStarterGUID;
- uint8 nextWP;
- uint32 punishTimer;
- bool caster;
- bool nextMovementStarted;
- bool movementCompleted;
- bool movementStarted;
- bool encounterActionAttack;
- bool encounterActionReset;
- bool doDelayPunish;
-
- void Reset() override
+ uint32 GetData(uint32 type) const override
{
- if (!encounterActionReset)
- DoEncounteraction(NULL, false, true, false);
-
- instance->SetData(DATA_HORSEMEN0 + id, NOT_STARTED);
-
- me->SetReactState(REACT_AGGRESSIVE);
- Initialize();
- _Reset();
+ switch (type)
+ {
+ case DATA_MOVEMENT_FINISHED:
+ return _myMovementFinished ? 1 : 0;
+ case DATA_DEATH_TIME:
+ return _timeDied;
+ case DATA_HORSEMEN_IS_TIMED_KILL:
+ {
+ uint32 minTime = 0, maxTime = 0;
+ for (Horseman boss : horsemen)
+ if (Creature* cBoss = getHorsemanHandle(boss))
+ {
+ uint32 deathTime = cBoss->AI()->GetData(DATA_DEATH_TIME);
+ if (!deathTime)
+ {
+ TC_LOG_WARN("scripts", "FourHorsemenAI: Checking for achievement credit but horseman %s is reporting not dead", cBoss->GetName().c_str());
+ return 0;
+ }
+ if (!minTime || deathTime < minTime)
+ minTime = deathTime;
+ if (!maxTime || deathTime > maxTime)
+ maxTime = deathTime;
+ }
+ else
+ {
+ TC_LOG_WARN("scripts", "FourHorsemenAI: Checking for achievement credit but horseman with id %u is not present", uint32(boss));
+ return 0;
+ }
+ return (getMSTimeDiff(minTime, maxTime) <= 15 * IN_MILLISECONDS) ? 1 : 0;
+ }
+ default:
+ return 0;
+ }
}
- bool DoEncounteraction(Unit* who, bool attack, bool reset, bool checkAllDead)
+ void DoAction(int32 action) override
{
- Creature* Thane = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_THANE));
- Creature* Lady = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_LADY));
- Creature* Baron = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_BARON));
- Creature* Sir = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_SIR));
+ switch (action)
+ {
+ case ACTION_BEGIN_MOVEMENT:
+ me->GetMotionMaster()->MovePoint(1, _initialPath[0], true);
+ break;
+ case ACTION_BEGIN_FIGHTING:
+ if (_ourMovementFinished)
+ break;
+ me->SetCombatPulseDelay(5);
+ BeginFighting();
+ _ourMovementFinished = true;
+ break;
+ }
+ }
- if (Thane && Lady && Baron && Sir)
+ void CheckIsMovementFinished()
+ {
+ for (Horseman boss : horsemen)
{
- if (attack && who)
+ if (Creature* cBoss = getHorsemanHandle(boss))
{
- ENSURE_AI(boss_four_horsemen::boss_four_horsemenAI, Thane->AI())->encounterActionAttack = true;
- ENSURE_AI(boss_four_horsemen::boss_four_horsemenAI, Lady->AI())->encounterActionAttack = true;
- ENSURE_AI(boss_four_horsemen::boss_four_horsemenAI, Baron->AI())->encounterActionAttack = true;
- ENSURE_AI(boss_four_horsemen::boss_four_horsemenAI, Sir->AI())->encounterActionAttack = true;
-
- ENSURE_AI(boss_four_horsemen::boss_four_horsemenAI, Thane->AI())->AttackStart(who);
- ENSURE_AI(boss_four_horsemen::boss_four_horsemenAI, Lady->AI())->AttackStart(who);
- ENSURE_AI(boss_four_horsemen::boss_four_horsemenAI, Baron->AI())->AttackStart(who);
- ENSURE_AI(boss_four_horsemen::boss_four_horsemenAI, Sir->AI())->AttackStart(who);
+ if (cBoss->IsAlive() && !cBoss->AI()->GetData(DATA_MOVEMENT_FINISHED))
+ return;
}
-
- if (reset)
+ else
{
- if (instance->GetBossState(BOSS_HORSEMEN) != NOT_STARTED)
- {
- if (!Thane->IsAlive())
- Thane->Respawn();
-
- if (!Lady->IsAlive())
- Lady->Respawn();
+ TC_LOG_WARN("scripts", "FourHorsemenAI: Checking if movement is finished but horseman with id %u is not present", uint32(boss));
+ ResetEncounter();
+ return;
+ }
+ }
- if (!Baron->IsAlive())
- Baron->Respawn();
+ for (Horseman boss : horsemen)
+ if (Creature* cBoss = getHorsemanHandle(boss))
+ cBoss->AI()->DoAction(ACTION_BEGIN_FIGHTING);
+ }
- if (!Sir->IsAlive())
- Sir->Respawn();
+ void BeginEncounter()
+ {
+ if (instance->GetBossState(BOSS_HORSEMEN) == IN_PROGRESS)
+ return;
+ if (!instance->CheckRequiredBosses(BOSS_HORSEMEN))
+ {
+ ResetEncounter();
+ return;
+ }
+ instance->SetBossState(BOSS_HORSEMEN, IN_PROGRESS);
+ Map::PlayerList const &players = me->GetMap()->GetPlayers();
+ if (players.isEmpty()) // sanity check
+ ResetEncounter();
- ENSURE_AI(boss_four_horsemen::boss_four_horsemenAI, Thane->AI())->encounterActionReset = true;
- ENSURE_AI(boss_four_horsemen::boss_four_horsemenAI, Lady->AI())->encounterActionReset = true;
- ENSURE_AI(boss_four_horsemen::boss_four_horsemenAI, Baron->AI())->encounterActionReset = true;
- ENSURE_AI(boss_four_horsemen::boss_four_horsemenAI, Sir->AI())->encounterActionReset = true;
+ for (Horseman boss : horsemen)
+ {
+ if (Creature* cBoss = getHorsemanHandle(boss))
+ {
+ if (!cBoss->IsAlive())
+ {
+ ResetEncounter();
+ return;
+ }
+ cBoss->SetReactState(REACT_PASSIVE);
+ cBoss->AttackStop(); // clear initial target that was set on enter combat
+ cBoss->setActive(true);
- ENSURE_AI(boss_four_horsemen::boss_four_horsemenAI, Thane->AI())->EnterEvadeMode();
- ENSURE_AI(boss_four_horsemen::boss_four_horsemenAI, Lady->AI())->EnterEvadeMode();
- ENSURE_AI(boss_four_horsemen::boss_four_horsemenAI, Baron->AI())->EnterEvadeMode();
- ENSURE_AI(boss_four_horsemen::boss_four_horsemenAI, Sir->AI())->EnterEvadeMode();
+ for (Map::PlayerList::const_iterator it = players.begin(); it != players.end(); ++it)
+ {
+ if (Player* player = it->GetSource())
+ {
+ if (player->IsGameMaster())
+ continue;
+
+ if (player->IsAlive())
+ {
+ cBoss->AddThreat(player, 0.0f);
+ cBoss->SetInCombatWith(player);
+ player->SetInCombatWith(cBoss);
+ }
+ }
}
- }
- if (checkAllDead)
- return !Thane->IsAlive() && !Lady->IsAlive() && !Baron->IsAlive() && !Sir->IsAlive();
+ /* Why do the Four Horsemen run to opposite corners of the room when engaged? *
+ * They saw all the mobs leading up to them being AoE'd down and made a judgment call. */
+ cBoss->AI()->DoAction(ACTION_BEGIN_MOVEMENT);
+ }
+ else
+ {
+ TC_LOG_WARN("scripts", "FourHorsemenAI: Encounter starting but horseman with id %u is not present", uint32(boss));
+ ResetEncounter();
+ return;
+ }
}
- return false;
}
- void BeginFourHorsemenMovement()
+ void ResetEncounter()
{
- movementStarted = true;
- me->SetReactState(REACT_PASSIVE);
- me->SetWalk(false);
- me->SetSpeed(MOVE_RUN, me->GetSpeedRate(MOVE_RUN), true);
-
- switch (id)
+ if (instance->GetBossState(BOSS_HORSEMEN) == NOT_STARTED || instance->GetBossState(BOSS_HORSEMEN) == DONE)
+ return;
+ instance->SetBossState(BOSS_HORSEMEN, NOT_STARTED);
+ for (Horseman boss : horsemen)
{
- case HORSEMEN_THANE:
- me->GetMotionMaster()->MovePoint(0, WaypointPositions[0]);
- break;
- case HORSEMEN_LADY:
- me->GetMotionMaster()->MovePoint(3, WaypointPositions[3]);
- break;
- case HORSEMEN_BARON:
- me->GetMotionMaster()->MovePoint(6, WaypointPositions[6]);
- break;
- case HORSEMEN_SIR:
- me->GetMotionMaster()->MovePoint(9, WaypointPositions[9]);
- break;
+ if (Creature* cBoss = getHorsemanHandle(boss))
+ {
+ cBoss->DespawnOrUnsummon(0);
+ cBoss->SetRespawnTime(15);
+ }
+ else
+ {
+ TC_LOG_WARN("scripts", "FourHorsemenAI: Encounter resetting but horseman with id %u is not present", uint32(boss));
+ }
}
}
- void MovementInform(uint32 type, uint32 point) override
+ void EncounterCleared()
{
- if (type != POINT_MOTION_TYPE)
+ if (instance->GetBossState(BOSS_HORSEMEN) == DONE)
return;
+ instance->SetBossState(BOSS_HORSEMEN, DONE);
+ //instance->DoUpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET, SPELL_ENCOUNTER_CREDIT);
+ DoCastAOE(SPELL_ENCOUNTER_CREDIT, true);
+ }
- if (point == 2 || point == 5 || point == 8 || point == 11)
- {
- movementCompleted = true;
- me->SetReactState(REACT_AGGRESSIVE);
+ void EnterCombat(Unit* /*who*/) override
+ {
+ if (instance->GetBossState(BOSS_HORSEMEN) != NOT_STARTED) // another horseman already did it
+ return;
+ Talk(SAY_AGGRO);
+ BeginEncounter();
+ }
+
+ void EnterEvadeMode(EvadeReason /*why*/) override
+ {
+ ResetEncounter();
+ }
+
+ void Reset() override
+ {
+ if (!me->IsAlive())
+ return;
+ _myMovementFinished = false;
+ _nextMovement = 0;
+ _timeDied = 0;
+ _ourMovementFinished = false;
+ me->SetReactState(REACT_AGGRESSIVE);
+ SetCombatMovement(false);
+ me->SetCombatPulseDelay(0);
+ me->ResetLootMode();
+ events.Reset();
+ summons.DespawnAll();
+ }
+
+ void KilledUnit(Unit* victim) override
+ {
+ if (victim->GetTypeId() == TYPEID_PLAYER)
+ Talk(SAY_SLAY, victim);
+ }
- Unit* eventStarter = ObjectAccessor::GetUnit(*me, uiEventStarterGUID);
+ void JustDied(Unit* /*killer*/) override
+ {
+ if (instance->GetBossState(BOSS_HORSEMEN) != IN_PROGRESS) // necessary in case a horseman gets one-shot
+ {
+ BeginEncounter();
+ return;
+ }
- if (eventStarter && me->IsValidAttackTarget(eventStarter))
- AttackStart(eventStarter);
- else if (!UpdateVictim())
+ Talk(SAY_DEATH);
+ _timeDied = getMSTime();
+ for (Horseman boss : horsemen)
+ {
+ if (Creature* cBoss = getHorsemanHandle(boss))
{
- EnterEvadeMode();
- return;
+ if (cBoss->IsAlive())
+ {
+ // in case a horseman dies while moving (unlikely but possible especially in non-335 branch)
+ CheckIsMovementFinished();
+ return;
+ }
}
-
- if (caster)
+ else
{
- me->GetMotionMaster()->Clear();
- me->GetMotionMaster()->MoveIdle();
+ TC_LOG_WARN("scripts", "FourHorsemenAI: %s died but horseman with id %u is not present", me->GetName().c_str(), uint32(boss));
+ ResetEncounter();
}
-
- return;
}
- nextMovementStarted = false;
- nextWP = point + 1;
+ EncounterCleared();
}
- // switch to "who" if nearer than current target.
- void SelectNearestTarget(Unit* who)
+ void MovementInform(uint32 type, uint32 i) override
{
- if (me->GetVictim() && me->GetDistanceOrder(who, me->GetVictim()) && me->IsValidAttackTarget(who))
+ if (type != POINT_MOTION_TYPE)
+ return;
+ if (i < 3)
+ _nextMovement = i; // delay to next updateai to prevent it from instantly expiring
+ else
{
- me->getThreatManager().modifyThreatPercent(me->GetVictim(), -100);
- me->AddThreat(who, 1000000.0f);
+ _myMovementFinished = true;
+ CheckIsMovementFinished();
}
}
- void MoveInLineOfSight(Unit* who) override
-
+ void UpdateAI(uint32 diff) override
{
- BossAI::MoveInLineOfSight(who);
- if (caster)
- SelectNearestTarget(who);
+ if (_nextMovement)
+ {
+ me->GetMotionMaster()->MovePoint(_nextMovement + 1, _initialPath[_nextMovement], true);
+ _nextMovement = 0;
+ }
+ _UpdateAI(diff);
}
- void AttackStart(Unit* who) override
+ virtual void BeginFighting() = 0;
+ virtual void _UpdateAI(uint32 /*diff*/) = 0;
+
+ private:
+ const Horseman _which;
+ const Position* _initialPath;
+ bool _myMovementFinished;
+ uint8 _nextMovement;
+ uint32 _timeDied;
+ protected:
+ bool _ourMovementFinished;
+};
+
+class boss_four_horsemen_baron : public CreatureScript
+{
+ public:
+ boss_four_horsemen_baron() : CreatureScript("boss_four_horsemen_baron") { }
+
+ struct boss_four_horsemen_baronAI : public boss_four_horsemen_baseAI
{
- if (!movementCompleted && !movementStarted)
+ boss_four_horsemen_baronAI(Creature* creature) : boss_four_horsemen_baseAI(creature, BARON, baronPath) { }
+ void BeginFighting() override
{
- uiEventStarterGUID = who->GetGUID();
- BeginFourHorsemenMovement();
+ SetCombatMovement(true);
+ me->SetReactState(REACT_AGGRESSIVE);
+ ThreatManager& threat = me->getThreatManager();
+ if (threat.isThreatListEmpty())
+ {
+ if (Unit* nearest = me->SelectNearestPlayer(5000.0f))
+ {
+ me->AddThreat(nearest, 1.0f);
+ AttackStart(nearest);
+ }
+ else
+ ResetEncounter();
+ }
+ else
+ AttackStart(threat.getHostilTarget());
- if (!encounterActionAttack)
- DoEncounteraction(who, true, false, false);
+ events.ScheduleEvent(EVENT_BERSERK, 10 * MINUTE * IN_MILLISECONDS);
+ events.ScheduleEvent(EVENT_MARK, 24 * IN_MILLISECONDS);
+ events.ScheduleEvent(EVENT_UNHOLYSHADOW, urandms(3,7));
}
- else if (movementCompleted && movementStarted)
+
+ void _UpdateAI(uint32 diff) override
{
- if (caster)
- me->Attack(who, false);
- else
- BossAI::AttackStart(who);
+ if (!_ourMovementFinished || !UpdateVictim())
+ return;
+ events.Update(diff);
+
+ while (uint32 eventId = events.ExecuteEvent())
+ {
+ switch (eventId)
+ {
+ case EVENT_BERSERK:
+ DoCastAOE(SPELL_BERSERK, true);
+ break;
+ case EVENT_MARK:
+ DoCastAOE(SPELL_BARON_MARK, true);
+ events.ScheduleEvent(EVENT_MARK, 12 * IN_MILLISECONDS);
+ break;
+ case EVENT_UNHOLYSHADOW:
+ DoCastVictim(SPELL_UNHOLY_SHADOW);
+ events.ScheduleEvent(EVENT_UNHOLYSHADOW, urandms(10,30));
+ break;
+ }
+ }
+
+ if (me->HasUnitState(UNIT_STATE_CASTING))
+ return;
+ DoMeleeAttackIfReady();
}
- }
- void KilledUnit(Unit* /*victim*/) override
+ void SpellHitTarget(Unit* /*target*/, SpellInfo const* spell) override
+ {
+ if (spell->Id == SPELL_UNHOLY_SHADOW)
+ Talk(SAY_SPECIAL);
+ }
+ };
+
+ CreatureAI* GetAI(Creature* creature) const override
{
- if (!(rand32() % 5))
- Talk(SAY_SLAY);
+ return GetInstanceAI<boss_four_horsemen_baronAI>(creature);
}
+};
- void JustDied(Unit* /*killer*/) override
- {
- events.Reset();
- summons.DespawnAll();
+class boss_four_horsemen_thane : public CreatureScript
+{
+ public:
+ boss_four_horsemen_thane() : CreatureScript("boss_four_horsemen_thane") { }
- instance->SetData(DATA_HORSEMEN0 + id, DONE);
+ struct boss_four_horsemen_thaneAI : public boss_four_horsemen_baseAI
+ {
+ boss_four_horsemen_thaneAI(Creature* creature) : boss_four_horsemen_baseAI(creature, THANE, thanePath), _shouldSay(true) { }
+ void BeginFighting() override
+ {
+ SetCombatMovement(true);
+ me->SetReactState(REACT_AGGRESSIVE);
+ ThreatManager& threat = me->getThreatManager();
+ if (threat.isThreatListEmpty())
+ {
+ if (Unit* nearest = me->SelectNearestPlayer(5000.0f))
+ {
+ me->AddThreat(nearest, 1.0f);
+ AttackStart(nearest);
+ }
+ else
+ ResetEncounter();
+ }
+ else
+ AttackStart(threat.getHostilTarget());
- if (DoEncounteraction(NULL, false, false, true))
+ events.ScheduleEvent(EVENT_BERSERK, 10 * MINUTE * IN_MILLISECONDS);
+ events.ScheduleEvent(EVENT_MARK, 24 * IN_MILLISECONDS);
+ events.ScheduleEvent(EVENT_METEOR, urandms(10,15));
+ }
+ void _UpdateAI(uint32 diff) override
{
- instance->SetBossState(BOSS_HORSEMEN, DONE);
- instance->SaveToDB();
+ if (!_ourMovementFinished || !UpdateVictim())
+ return;
+ events.Update(diff);
+
+ while (uint32 eventId = events.ExecuteEvent())
+ {
+ switch (eventId)
+ {
+ case EVENT_BERSERK:
+ DoCastAOE(SPELL_BERSERK, true);
+ break;
+ case EVENT_MARK:
+ DoCastAOE(SPELL_THANE_MARK, true);
+ events.ScheduleEvent(EVENT_MARK, 12 * IN_MILLISECONDS);
+ break;
+ case EVENT_METEOR:
+ if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 20.0f, true))
+ {
+ DoCast(target, SPELL_METEOR);
+ _shouldSay = true;
+ }
+ events.ScheduleEvent(EVENT_METEOR, urandms(13,17));
+ break;
+ }
+ }
- // Achievements related to the 4-horsemen are given through spell 59450 which does not exist.
- // There is thus no way it can be given by casting the spell on the players.
- instance->DoUpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_BE_SPELL_TARGET, 59450);
+ if (me->HasUnitState(UNIT_STATE_CASTING))
+ return;
+ DoMeleeAttackIfReady();
}
- Talk(SAY_DEATH);
- }
+ void SpellHitTarget(Unit* /*target*/, SpellInfo const* spell) override
+ {
+ if (_shouldSay && spell->Id == SPELL_METEOR)
+ {
+ Talk(SAY_SPECIAL);
+ _shouldSay = false;
+ }
+ }
- void EnterCombat(Unit* /*who*/) override
- {
- _EnterCombat();
- Talk(SAY_AGGRO);
+ private:
+ bool _shouldSay; // throttle to make sure we only talk on first target hit by meteor
+ };
- events.ScheduleEvent(EVENT_MARK, 15000);
- events.ScheduleEvent(EVENT_CAST, 20000 + rand32() % 5000);
- events.ScheduleEvent(EVENT_BERSERK, 15*100*1000);
+ CreatureAI* GetAI(Creature* creature) const override
+ {
+ return GetInstanceAI<boss_four_horsemen_thaneAI>(creature);
}
+};
- void UpdateAI(uint32 diff) override
+class boss_four_horsemen_lady : public CreatureScript
+{
+ public:
+ boss_four_horsemen_lady() : CreatureScript("boss_four_horsemen_lady") { }
+
+ struct boss_four_horsemen_ladyAI : public boss_four_horsemen_baseAI
{
- if (nextWP && movementStarted && !movementCompleted && !nextMovementStarted)
+ boss_four_horsemen_ladyAI(Creature* creature) : boss_four_horsemen_baseAI(creature, LADY, ladyPath) { }
+ void BeginFighting() override
{
- nextMovementStarted = true;
- me->GetMotionMaster()->MovePoint(nextWP, WaypointPositions[nextWP]);
+ events.ScheduleEvent(EVENT_BERSERK, 10 * MINUTE * IN_MILLISECONDS);
+ events.ScheduleEvent(EVENT_MARK, 24 * IN_MILLISECONDS);
+ events.ScheduleEvent(EVENT_VOIDZONE, urandms(5,10));
}
- if (!UpdateVictim() || !CheckInRoom() || !movementCompleted)
- return;
+ void _UpdateAI(uint32 diff) override
+ {
+ if (!me->IsInCombat())
+ return;
+ if (!_ourMovementFinished)
+ return;
+ if (me->getThreatManager().isThreatListEmpty())
+ {
+ EnterEvadeMode(EVADE_REASON_NO_HOSTILES);
+ return;
+ }
- events.Update(diff);
+ events.Update(diff);
- if (me->HasUnitState(UNIT_STATE_CASTING))
- return;
+ while (uint32 eventId = events.ExecuteEvent())
+ {
+ switch (eventId)
+ {
+ case EVENT_BERSERK:
+ DoCastAOE(SPELL_BERSERK, true);
+ break;
+ case EVENT_MARK:
+ DoCastAOE(SPELL_LADY_MARK, true);
+ events.ScheduleEvent(EVENT_MARK, 15 * IN_MILLISECONDS);
+ break;
+ case EVENT_VOIDZONE:
+ if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 45.0f, true))
+ {
+ DoCast(target, SPELL_VOID_ZONE, true);
+ Talk(SAY_SPECIAL);
+ }
+ events.ScheduleEvent(EVENT_VOIDZONE, urandms(12, 18));
+ break;
+ }
+ }
+
+ if (me->HasUnitState(UNIT_STATE_CASTING))
+ return;
+
+ if (Unit* target = SelectTarget(SELECT_TARGET_NEAREST, 0, 45.0f, true))
+ DoCast(target, SPELL_SHADOW_BOLT);
+ else
+ {
+ DoCastAOE(SPELL_UNYIELDING_PAIN);
+ Talk(EMOTE_RAGECAST);
+ }
+ }
+ };
+
+ CreatureAI* GetAI(Creature* creature) const override
+ {
+ return GetInstanceAI<boss_four_horsemen_ladyAI>(creature);
+ }
+};
+
+class boss_four_horsemen_sir : public CreatureScript
+{
+ public:
+ boss_four_horsemen_sir() : CreatureScript("boss_four_horsemen_sir") { }
- while (uint32 eventId = events.ExecuteEvent())
+ struct boss_four_horsemen_sirAI : public boss_four_horsemen_baseAI
+ {
+ boss_four_horsemen_sirAI(Creature* creature) : boss_four_horsemen_baseAI(creature, SIR, sirPath), _shouldSay(true) { }
+ void BeginFighting() override
{
- switch (eventId)
+ events.ScheduleEvent(EVENT_BERSERK, 10 * MINUTE * IN_MILLISECONDS);
+ events.ScheduleEvent(EVENT_MARK, 24 * IN_MILLISECONDS);
+ events.ScheduleEvent(EVENT_HOLYWRATH, urandms(13,18));
+ }
+
+ void _UpdateAI(uint32 diff) override
+ {
+ if (!me->IsInCombat())
+ return;
+ if (!_ourMovementFinished)
+ return;
+ if (me->getThreatManager().isThreatListEmpty())
{
- case EVENT_MARK:
- if (!(rand32() % 5))
- Talk(SAY_SPECIAL);
- DoCastAOE(SPELL_MARK[id]);
- events.ScheduleEvent(EVENT_MARK, 15000);
- break;
- case EVENT_CAST:
- if (!(rand32() % 5))
- Talk(SAY_TAUNT);
+ EnterEvadeMode(EVADE_REASON_NO_HOSTILES);
+ return;
+ }
- if (caster)
- {
- if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 45.0f, true))
- DoCast(target, SPELL_PRIMARY(id));
- }
- else
- DoCastVictim(SPELL_PRIMARY(id));
+ events.Update(diff);
- events.ScheduleEvent(EVENT_CAST, 15000);
- break;
- case EVENT_BERSERK:
- Talk(SAY_SPECIAL);
- DoCast(me, EVENT_BERSERK);
- break;
+ while (uint32 eventId = events.ExecuteEvent())
+ {
+ switch (eventId)
+ {
+ case EVENT_BERSERK:
+ DoCastAOE(SPELL_BERSERK, true);
+ break;
+ case EVENT_MARK:
+ DoCastAOE(SPELL_SIR_MARK, true);
+ events.ScheduleEvent(EVENT_MARK, 15 * IN_MILLISECONDS);
+ break;
+ case EVENT_HOLYWRATH:
+ if (Unit* target = SelectTarget(SELECT_TARGET_NEAREST, 0, 45.0f, true))
+ {
+ DoCast(target, SPELL_HOLY_WRATH, true);
+ _shouldSay = true;
+ }
+ events.ScheduleEvent(EVENT_HOLYWRATH, urandms(10,18));
+ break;
+ }
+ }
+
+ if (me->HasUnitState(UNIT_STATE_CASTING))
+ return;
+
+ if (Unit* target = SelectTarget(SELECT_TARGET_NEAREST, 0, 45.0f, true))
+ DoCast(target, SPELL_HOLY_BOLT);
+ else
+ {
+ DoCastAOE(SPELL_CONDEMNATION);
+ Talk(EMOTE_RAGECAST);
}
}
- if (punishTimer <= diff)
+ void SpellHitTarget(Unit* /*target*/, SpellInfo const* spell) override
{
- if (doDelayPunish)
+ if (_shouldSay && spell->Id == SPELL_HOLY_WRATH)
{
- DoCastAOE(SPELL_PUNISH[id], true);
- doDelayPunish = false;
+ Talk(SAY_SPECIAL);
+ _shouldSay = false;
}
- punishTimer = 2000;
- } else punishTimer -= diff;
+ }
- if (!caster)
- DoMeleeAttackIfReady();
- else if ((!DoSpellAttackIfReady(SPELL_SECONDARY(id)) || !me->IsWithinLOSInMap(me->GetVictim())) && movementCompleted && !doDelayPunish)
- doDelayPunish = true;
- }
- };
+ private:
+ bool _shouldSay; // throttle to make sure we only talk on first target hit by holy wrath
+ };
+ CreatureAI* GetAI(Creature* creature) const override
+ {
+ return GetInstanceAI<boss_four_horsemen_sirAI>(creature);
+ }
};
class spell_four_horsemen_mark : public SpellScriptLoader
@@ -450,6 +749,9 @@ class spell_four_horsemen_mark : public SpellScriptLoader
void AddSC_boss_four_horsemen()
{
- new boss_four_horsemen();
+ new boss_four_horsemen_baron();
+ new boss_four_horsemen_thane();
+ new boss_four_horsemen_lady();
+ new boss_four_horsemen_sir();
new spell_four_horsemen_mark();
}
diff --git a/src/server/scripts/Northrend/Naxxramas/boss_gothik.cpp b/src/server/scripts/Northrend/Naxxramas/boss_gothik.cpp
index c9684cf10cf..be12894ebea 100644
--- a/src/server/scripts/Northrend/Naxxramas/boss_gothik.cpp
+++ b/src/server/scripts/Northrend/Naxxramas/boss_gothik.cpp
@@ -20,30 +20,70 @@
#include "SpellScript.h"
#include "GridNotifiers.h"
#include "CombatAI.h"
+#include "AreaBoundary.h"
#include "naxxramas.h"
+/* Constants */
enum Yells
{
- SAY_SPEECH = 0,
- SAY_KILL = 1,
- SAY_DEATH = 2,
- SAY_TELEPORT = 3
+ SAY_INTRO_1 = 0,
+ SAY_INTRO_2 = 1,
+ SAY_INTRO_3 = 2,
+ SAY_INTRO_4 = 3,
+ SAY_PHASE_TWO = 4,
+ SAY_DEATH = 5,
+ SAY_KILL = 6,
+
+ EMOTE_PHASE_TWO = 7,
+ EMOTE_GATE_OPENED = 8
};
-//Gothik
enum Spells
{
+ /* living trainee spells */
+ SPELL_DEATH_PLAGUE = 55604,
+
+ /* living knight spells */
+ SPELL_SHADOW_MARK = 27825,
+
+ /* living rider spells */
+ SPELL_SHADOW_BOLT_VOLLEY = 27831,
+
+ /* spectral trainee spells */
+ SPELL_ARCANE_EXPLOSION = 27989,
+
+ /* spectral knight spells */
+ SPELL_WHIRLWIND = 56408,
+
+ /* spectral rider spells */
+ SPELL_DRAIN_LIFE = 27994,
+ SPELL_UNHOLY_FRENZY = 55648,
+
+ /* spectral horse spells */
+ SPELL_STOMP = 27993,
+
+ /* gothik phase two spells */
SPELL_HARVEST_SOUL = 28679,
SPELL_SHADOW_BOLT = 29317,
- SPELL_INFORM_LIVE_TRAINEE = 27892,
- SPELL_INFORM_LIVE_KNIGHT = 27928,
- SPELL_INFORM_LIVE_RIDER = 27935,
- SPELL_INFORM_DEAD_TRAINEE = 27915,
- SPELL_INFORM_DEAD_KNIGHT = 27931,
- SPELL_INFORM_DEAD_RIDER = 27937,
-
- SPELL_SHADOW_MARK = 27825
+
+ /* visual spells */
+ SPELL_ANCHOR_1_TRAINEE = 27892,
+ SPELL_ANCHOR_1_DK = 27928,
+ SPELL_ANCHOR_1_RIDER = 27935,
+
+ SPELL_ANCHOR_2_TRAINEE = 27893,
+ SPELL_ANCHOR_2_DK = 27929,
+ SPELL_ANCHOR_2_RIDER = 27936,
+
+ SPELL_SKULLS_TRAINEE = 27915,
+ SPELL_SKULLS_DK = 27931,
+ SPELL_SKULLS_RIDER = 27937,
+
+ /* teleport spells */
+ SPELL_TELEPORT_DEAD = 28025,
+ SPELL_TELEPORT_LIVE = 28026
};
+#define SPELLHELPER_UNHOLY_FRENZY RAID_MODE<uint32>(SPELL_UNHOLY_FRENZY,27995)
enum Creatures
{
@@ -53,108 +93,205 @@ enum Creatures
NPC_DEAD_TRAINEE = 16127,
NPC_DEAD_KNIGHT = 16148,
NPC_DEAD_RIDER = 16150,
- NPC_DEAD_HORSE = 16149
+ NPC_DEAD_HORSE = 16149,
+
+ NPC_TRIGGER = 16137
};
-struct Waves { uint32 entry, time, mode; };
-// wave setups are not the same in heroic and normal difficulty,
-// mode is 0 only normal, 1 both and 2 only heroic
-// but this is handled in DoGothikSummon function
-const Waves waves[] =
+enum Phases
{
- {NPC_LIVE_TRAINEE, 20000, 1},
- {NPC_LIVE_TRAINEE, 20000, 1},
- {NPC_LIVE_TRAINEE, 10000, 1},
- {NPC_LIVE_KNIGHT, 10000, 1},
- {NPC_LIVE_TRAINEE, 15000, 1},
- {NPC_LIVE_KNIGHT, 5000, 1},
- {NPC_LIVE_TRAINEE, 20000, 1},
- {NPC_LIVE_TRAINEE, 0, 1},
- {NPC_LIVE_KNIGHT, 10000, 1},
- {NPC_LIVE_TRAINEE, 10000, 2},
- {NPC_LIVE_RIDER, 10000, 0},
- {NPC_LIVE_RIDER, 5000, 2},
- {NPC_LIVE_TRAINEE, 5000, 0},
- {NPC_LIVE_TRAINEE, 15000, 2},
- {NPC_LIVE_KNIGHT, 15000, 0},
- {NPC_LIVE_TRAINEE, 0, 0},
- {NPC_LIVE_RIDER, 10000, 1},
- {NPC_LIVE_KNIGHT, 10000, 1},
- {NPC_LIVE_TRAINEE, 10000, 0},
- {NPC_LIVE_RIDER, 10000, 2},
- {NPC_LIVE_TRAINEE, 0, 2},
- {NPC_LIVE_RIDER, 5000, 1},
- {NPC_LIVE_TRAINEE, 0, 2},
- {NPC_LIVE_KNIGHT, 5000, 1},
- {NPC_LIVE_RIDER, 0, 2},
- {NPC_LIVE_TRAINEE, 20000, 1},
- {NPC_LIVE_RIDER, 0, 1},
- {NPC_LIVE_KNIGHT, 0, 1},
- {NPC_LIVE_TRAINEE, 25000, 2},
- {NPC_LIVE_TRAINEE, 15000, 0},
- {NPC_LIVE_TRAINEE, 25000, 0},
- {0, 0, 1},
+ PHASE_ONE = 1,
+ PHASE_TWO = 2
};
-#define POS_Y_GATE -3360.78f
-#define POS_Y_WEST -3285.0f
-#define POS_Y_EAST -3434.0f
-#define POS_X_NORTH 2750.49f
-#define POS_X_SOUTH 2633.84f
-
-#define IN_LIVE_SIDE(who) (who->GetPositionY() < POS_Y_GATE)
-
enum Events
{
- EVENT_NONE,
+ EVENT_INTRO_2 = 1,
+ EVENT_INTRO_3,
+ EVENT_INTRO_4,
+ EVENT_PHASE_TWO,
EVENT_SUMMON,
+ EVENT_DOORS_UNLOCK,
+ EVENT_TELEPORT,
EVENT_HARVEST,
EVENT_BOLT,
- EVENT_TELEPORT
-};
-enum Pos
-{
- POS_LIVE = 6,
- POS_DEAD = 5
+ EVENT_RESUME_ATTACK
};
-const Position PosSummonLive[POS_LIVE] =
+enum Actions
{
- {2669.7f, -3428.76f, 268.56f, 1.6f},
- {2692.1f, -3428.76f, 268.56f, 1.6f},
- {2714.4f, -3428.76f, 268.56f, 1.6f},
- {2669.7f, -3431.67f, 268.56f, 1.6f},
- {2692.1f, -3431.67f, 268.56f, 1.6f},
- {2714.4f, -3431.67f, 268.56f, 1.6f},
+ ACTION_GATE_OPENED = 1,
+ ACTION_MINION_EVADE,
+ ACTION_ACQUIRE_TARGET
};
-const Position PosSummonDead[POS_DEAD] =
+
+/* Room side checking logic */
+static AreaBoundary* const livingSide = new RectangleBoundary(2633.84f, 2750.49f, -3434.0f, -3360.78f);
+static AreaBoundary* const deadSide = new RectangleBoundary(2633.84f, 2750.49f, -3360.78f, -3285.0f);
+enum Side
{
- {2725.1f, -3310.0f, 268.85f, 3.4f},
- {2699.3f, -3322.8f, 268.60f, 3.3f},
- {2733.1f, -3348.5f, 268.84f, 3.1f},
- {2682.8f, -3304.2f, 268.85f, 3.9f},
- {2664.8f, -3340.7f, 268.23f, 3.7f},
+ SIDE_NONE = 0,
+ SIDE_LIVING,
+ SIDE_DEAD
};
+inline static Side GetSide(Position const* who)
+{
+ if (livingSide->IsWithinBoundary(who))
+ return SIDE_LIVING;
+ if (deadSide->IsWithinBoundary(who))
+ return SIDE_DEAD;
+ return SIDE_NONE;
+}
+inline static bool IsOnSameSide(Position const* who, Position const* other)
+{
+ return (GetSide(who) == GetSide(other));
+}
+static Player* FindEligibleTarget(Creature const* me, bool isGateOpen)
+{
+ Map::PlayerList const& players = me->GetMap()->GetPlayers();
+ for (Map::PlayerList::const_iterator it = players.begin(); it != players.end(); ++it)
+ {
+ Player* player = it->GetSource();
+ if (player && (isGateOpen || IsOnSameSide(me, player)) && me->CanSeeOrDetect(player) && me->IsValidAttackTarget(player) && player->isInAccessiblePlaceFor(me))
+ {
+ return player;
+ }
+ }
-float const PosGroundLiveSide[4] = {2691.2f, -3387.0f, 267.68f, 1.52f};
-float const PosGroundDeadSide[4] = {2693.5f, -3334.6f, 267.68f, 4.67f};
-float const PosPlatform[4] = {2640.5f, -3360.6f, 285.26f, 0.0f};
+ return nullptr;
+}
-// Predicate function to check that the r efzr unit is NOT on the same side as the source.
-struct NotOnSameSide : public std::unary_function<Unit*, bool>
-{
- NotOnSameSide(Unit* source) : _onLiveSide(IN_LIVE_SIDE(source)) { }
- bool operator() (Unit const* target)
+/* Wave data */
+typedef std::pair<uint32, uint8> GothikWaveEntry; // (npcEntry, npcCount)
+typedef std::set<GothikWaveEntry> GothikWave;
+typedef std::pair<GothikWave, uint8> GothikWaveInfo; // (wave, secondsToNext)
+typedef std::vector<GothikWaveInfo> GothikWaveData;
+const GothikWaveData waves10 =
+{
{
- return (_onLiveSide != IN_LIVE_SIDE(target));
- }
+ {{NPC_LIVE_TRAINEE, 2}},
+ 20},
+ {
+ {{NPC_LIVE_TRAINEE, 2}},
+ 20},
+ {
+ {{NPC_LIVE_TRAINEE, 2}},
+ 10},
+ {
+ {{NPC_LIVE_KNIGHT, 1}},
+ 10},
+ {
+ {{NPC_LIVE_TRAINEE, 2}},
+ 15},
+ {
+ {{NPC_LIVE_KNIGHT, 1}},
+ 5},
+ {
+ {{NPC_LIVE_TRAINEE, 2}},
+ 20},
+ {
+ {{NPC_LIVE_TRAINEE, 2}, {NPC_LIVE_KNIGHT, 1}},
+ 10},
+ {
+ {{NPC_LIVE_RIDER, 1}},
+ 10},
+ {
+ {{NPC_LIVE_TRAINEE, 2}},
+ 5},
+ {
+ {{NPC_LIVE_KNIGHT, 1}},
+ 15},
+ {
+ {{NPC_LIVE_TRAINEE, 2}, {NPC_LIVE_RIDER, 1}},
+ 10},
+ {
+ {{NPC_LIVE_KNIGHT, 2}},
+ 10},
+ {
+ {{NPC_LIVE_TRAINEE, 2}},
+ 10},
+ {
+ {{NPC_LIVE_RIDER, 1}},
+ 5},
+ {
+ {{NPC_LIVE_KNIGHT, 1}},
+ 5},
+ {
+ {{NPC_LIVE_TRAINEE, 2}},
+ 20},
+ {
+ {{NPC_LIVE_RIDER, 1}, {NPC_LIVE_KNIGHT, 1}, {NPC_LIVE_TRAINEE, 2}},
+ 15},
+ {
+ {{NPC_LIVE_TRAINEE, 2}},
+ 0}
+};
- private:
- bool _onLiveSide;
+const GothikWaveData waves25 =
+{
+ {
+ {{NPC_LIVE_TRAINEE, 3}},
+ 20},
+ {
+ {{NPC_LIVE_TRAINEE, 3}},
+ 20},
+ {
+ {{NPC_LIVE_TRAINEE, 3}},
+ 10},
+ {
+ {{NPC_LIVE_KNIGHT, 2}},
+ 10},
+ {
+ {{NPC_LIVE_TRAINEE, 3}},
+ 15},
+ {
+ {{NPC_LIVE_KNIGHT, 2}},
+ 5},
+ {
+ {{NPC_LIVE_TRAINEE, 3}},
+ 20},
+ {
+ {{NPC_LIVE_TRAINEE, 3}, {NPC_LIVE_KNIGHT, 2}},
+ 10},
+ {
+ {{NPC_LIVE_TRAINEE, 3}},
+ 10},
+ {
+ {{NPC_LIVE_RIDER, 1}},
+ 5},
+ {
+ {{NPC_LIVE_TRAINEE, 3}},
+ 15},
+ {
+ {{NPC_LIVE_RIDER, 1}},
+ 10},
+ {
+ {{NPC_LIVE_KNIGHT, 2}},
+ 10},
+ {
+ {{NPC_LIVE_RIDER, 1}},
+ 10},
+ {
+ {{NPC_LIVE_RIDER, 1}, {NPC_LIVE_TRAINEE, 3}},
+ 5},
+ {
+ {{NPC_LIVE_KNIGHT, 1}, {NPC_LIVE_TRAINEE, 3}},
+ 5},
+ {
+ {{NPC_LIVE_RIDER, 1}, {NPC_LIVE_TRAINEE, 3}},
+ 20},
+ {
+ {{NPC_LIVE_RIDER, 1}, {NPC_LIVE_KNIGHT, 2}, {NPC_LIVE_TRAINEE, 3}},
+ 0}
};
+
+// GUID of first trigger NPC (used as offset for guid checks)
+// 0-1 are living side soul triggers, 2-3 are spectral side soul triggers, 4 is living rider spawn trigger, 5-7 are living other spawn trigger, 8-12 are skull pile triggers
+const uint32 CGUID_TRIGGER = 127618;
+/* Creature AI */
class boss_gothik : public CreatureScript
{
public:
@@ -165,29 +302,18 @@ class boss_gothik : public CreatureScript
boss_gothikAI(Creature* creature) : BossAI(creature, BOSS_GOTHIK)
{
Initialize();
- waveCount = 0;
}
void Initialize()
{
- mergedSides = false;
- phaseTwo = false;
- thirtyPercentReached = false;
+ _waveCount = 0;
+ _gateCanOpen = false;
+ _gateIsOpen = true;
+ _lastTeleportDead = false;
}
- uint32 waveCount;
- bool mergedSides;
- bool phaseTwo;
- bool thirtyPercentReached;
-
- GuidVector LiveTriggerGUID;
- GuidVector DeadTriggerGUID;
-
void Reset() override
{
- LiveTriggerGUID.clear();
- DeadTriggerGUID.clear();
-
me->SetReactState(REACT_PASSIVE);
instance->SetData(DATA_GOTHIK_GATE, GO_STATE_ACTIVE);
_Reset();
@@ -196,43 +322,29 @@ class boss_gothik : public CreatureScript
void EnterCombat(Unit* /*who*/) override
{
- for (uint32 i = 0; i < POS_LIVE; ++i)
- if (Creature* trigger = DoSummon(WORLD_TRIGGER, PosSummonLive[i]))
- LiveTriggerGUID.push_back(trigger->GetGUID());
- for (uint32 i = 0; i < POS_DEAD; ++i)
- if (Creature* trigger = DoSummon(WORLD_TRIGGER, PosSummonDead[i]))
- DeadTriggerGUID.push_back(trigger->GetGUID());
-
- if (LiveTriggerGUID.size() < POS_LIVE || DeadTriggerGUID.size() < POS_DEAD)
- {
- TC_LOG_ERROR("scripts", "Script Gothik: cannot summon triggers!");
- EnterEvadeMode();
- return;
- }
-
_EnterCombat();
- waveCount = 0;
- events.ScheduleEvent(EVENT_SUMMON, 30000);
- DoTeleportTo(PosPlatform);
- Talk(SAY_SPEECH);
+ events.SetPhase(PHASE_ONE);
+ events.ScheduleEvent(EVENT_SUMMON, 25 * IN_MILLISECONDS, 0, PHASE_ONE);
+ events.ScheduleEvent(EVENT_DOORS_UNLOCK, 205 * IN_MILLISECONDS, 0, PHASE_ONE);
+ events.ScheduleEvent(EVENT_PHASE_TWO, 270 * IN_MILLISECONDS, 0, PHASE_ONE);
+ Talk(SAY_INTRO_1);
+ events.ScheduleEvent(EVENT_INTRO_2, 4 * IN_MILLISECONDS);
+ events.ScheduleEvent(EVENT_INTRO_3, 9 * IN_MILLISECONDS);
+ events.ScheduleEvent(EVENT_INTRO_4, 14 * IN_MILLISECONDS);
instance->SetData(DATA_GOTHIK_GATE, GO_STATE_READY);
+ _gateIsOpen = false;
}
void JustSummoned(Creature* summon) override
{
- if (summon->GetEntry() == WORLD_TRIGGER)
- summon->setActive(true);
- else if (!mergedSides)
+ summons.Summon(summon);
+ if (me->IsInCombat())
{
- summon->AI()->DoAction(me->HasReactState(REACT_PASSIVE) ? 1 : 0);
- summon->AI()->EnterEvadeMode();
+ summon->AI()->DoAction(_gateIsOpen ? ACTION_GATE_OPENED : ACTION_ACQUIRE_TARGET);
+ summon->SetCombatPulseDelay(5);
}
else
- {
- summon->AI()->DoAction(0);
- summon->AI()->DoZoneInCombat();
- }
- summons.Summon(summon);
+ summon->DespawnOrUnsummon();
}
void SummonedCreatureDespawn(Creature* summon) override
@@ -240,256 +352,208 @@ class boss_gothik : public CreatureScript
summons.Despawn(summon);
}
- void KilledUnit(Unit* /*victim*/) override
+ void KilledUnit(Unit* victim) override
{
- if (!(rand32() % 5))
+ if (victim && victim->GetTypeId() == TYPEID_PLAYER)
Talk(SAY_KILL);
}
void JustDied(Unit* /*killer*/) override
{
- LiveTriggerGUID.clear();
- DeadTriggerGUID.clear();
_JustDied();
Talk(SAY_DEATH);
instance->SetData(DATA_GOTHIK_GATE, GO_STATE_ACTIVE);
+ _gateIsOpen = false;
}
- void DoGothikSummon(uint32 entry)
- {
- if (GetDifficulty() == RAID_DIFFICULTY_25MAN_NORMAL)
- {
- switch (entry)
- {
- case NPC_LIVE_TRAINEE:
- {
- if (Creature* liveTrigger = ObjectAccessor::GetCreature(*me, LiveTriggerGUID[0]))
- DoSummon(NPC_LIVE_TRAINEE, liveTrigger, 1);
- if (Creature* liveTrigger1 = ObjectAccessor::GetCreature(*me, LiveTriggerGUID[1]))
- DoSummon(NPC_LIVE_TRAINEE, liveTrigger1, 1);
- if (Creature* liveTrigger2 = ObjectAccessor::GetCreature(*me, LiveTriggerGUID[2]))
- DoSummon(NPC_LIVE_TRAINEE, liveTrigger2, 1);
- break;
- }
- case NPC_LIVE_KNIGHT:
- {
- if (Creature* liveTrigger3 = ObjectAccessor::GetCreature(*me, LiveTriggerGUID[3]))
- DoSummon(NPC_LIVE_KNIGHT, liveTrigger3, 1);
- if (Creature* liveTrigger5 = ObjectAccessor::GetCreature(*me, LiveTriggerGUID[5]))
- DoSummon(NPC_LIVE_KNIGHT, liveTrigger5, 1);
- break;
- }
- case NPC_LIVE_RIDER:
- {
- if (Creature* liveTrigger4 = ObjectAccessor::GetCreature(*me, LiveTriggerGUID[4]))
- DoSummon(NPC_LIVE_RIDER, liveTrigger4, 1);
- break;
- }
- }
- }
- else
- {
- switch (entry)
- {
- case NPC_LIVE_TRAINEE:
- {
- if (Creature* liveTrigger = ObjectAccessor::GetCreature(*me, LiveTriggerGUID[4]))
- DoSummon(NPC_LIVE_TRAINEE, liveTrigger, 1);
- if (Creature* liveTrigger2 = ObjectAccessor::GetCreature(*me, LiveTriggerGUID[4]))
- DoSummon(NPC_LIVE_TRAINEE, liveTrigger2, 1);
- break;
- }
- case NPC_LIVE_KNIGHT:
- {
- if (Creature* liveTrigger5 = ObjectAccessor::GetCreature(*me, LiveTriggerGUID[4]))
- DoSummon(NPC_LIVE_KNIGHT, liveTrigger5, 1);
- break;
- }
- case NPC_LIVE_RIDER:
- {
- if (Creature* liveTrigger4 = ObjectAccessor::GetCreature(*me, LiveTriggerGUID[4]))
- DoSummon(NPC_LIVE_RIDER, liveTrigger4, 1);
- break;
- }
- }
- }
- }
-
- bool CheckGroupSplitted()
- {
- bool checklife = false;
- bool checkdead = false;
- Map::PlayerList const &PlayerList = me->GetMap()->GetPlayers();
- for (Map::PlayerList::const_iterator i = PlayerList.begin(); i != PlayerList.end(); ++i)
- {
- if (i->GetSource() && i->GetSource()->IsAlive() &&
- i->GetSource()->GetPositionX() <= POS_X_NORTH &&
- i->GetSource()->GetPositionX() >= POS_X_SOUTH &&
- i->GetSource()->GetPositionY() <= POS_Y_GATE &&
- i->GetSource()->GetPositionY() >= POS_Y_EAST)
- {
- checklife = true;
- }
- else if (i->GetSource() && i->GetSource()->IsAlive() &&
- i->GetSource()->GetPositionX() <= POS_X_NORTH &&
- i->GetSource()->GetPositionX() >= POS_X_SOUTH &&
- i->GetSource()->GetPositionY() >= POS_Y_GATE &&
- i->GetSource()->GetPositionY() <= POS_Y_WEST)
- {
- checkdead = true;
- }
-
- if (checklife && checkdead)
- return true;
- }
-
- return false;
- }
-
- void SpellHit(Unit* /*caster*/, SpellInfo const* spell) override
+ void OpenGate()
{
- uint32 spellId = 0;
- switch (spell->Id)
- {
- case SPELL_INFORM_LIVE_TRAINEE: spellId = SPELL_INFORM_DEAD_TRAINEE; break;
- case SPELL_INFORM_LIVE_KNIGHT: spellId = SPELL_INFORM_DEAD_KNIGHT; break;
- case SPELL_INFORM_LIVE_RIDER: spellId = SPELL_INFORM_DEAD_RIDER; break;
- }
- if (spellId && me->IsInCombat())
+ if (_gateIsOpen)
+ return;
+ instance->SetData(DATA_GOTHIK_GATE, GO_STATE_ACTIVE);
+ Talk(EMOTE_GATE_OPENED);
+ _gateIsOpen = true;
+
+ for (ObjectGuid summonGuid : summons)
{
- me->HandleEmoteCommand(EMOTE_ONESHOT_SPELL_CAST);
- if (Creature* pRandomDeadTrigger = ObjectAccessor::GetCreature(*me, DeadTriggerGUID[rand32() % POS_DEAD]))
- me->CastSpell(pRandomDeadTrigger, spellId, true);
+ if (Creature* summon = ObjectAccessor::GetCreature(*me, summonGuid))
+ summon->AI()->DoAction(ACTION_GATE_OPENED);
+ if (summons.empty()) // ACTION_GATE_OPENED may cause an evade, despawning summons and invalidating our iterator
+ break;
}
}
void DamageTaken(Unit* /*who*/, uint32& damage) override
{
- if (!phaseTwo)
+ if (!events.IsInPhase(PHASE_TWO))
damage = 0;
}
- void SpellHitTarget(Unit* target, SpellInfo const* spell) override
+ void DoAction(int32 action) override
{
- if (!me->IsInCombat())
- return;
-
- switch (spell->Id)
+ switch (action)
{
- case SPELL_INFORM_DEAD_TRAINEE:
- DoSummon(NPC_DEAD_TRAINEE, target, 0);
- break;
- case SPELL_INFORM_DEAD_KNIGHT:
- DoSummon(NPC_DEAD_KNIGHT, target, 0);
- break;
- case SPELL_INFORM_DEAD_RIDER:
- DoSummon(NPC_DEAD_RIDER, target, 1.0f);
- DoSummon(NPC_DEAD_HORSE, target, 1.0f);
+ case ACTION_MINION_EVADE:
+ if (_gateIsOpen || me->getThreatManager().isThreatListEmpty())
+ return EnterEvadeMode(EVADE_REASON_NO_HOSTILES);
+ if (_gateCanOpen)
+ OpenGate();
break;
}
}
+ void EnterEvadeMode(EvadeReason why) override
+ {
+ BossAI::EnterEvadeMode(why);
+ Position const& home = me->GetHomePosition();
+ me->NearTeleportTo(home.GetPositionX(), home.GetPositionY(), home.GetPositionZ(), home.GetOrientation());
+ }
+
void UpdateAI(uint32 diff) override
{
if (!UpdateVictim())
return;
- events.Update(diff);
-
- if (!thirtyPercentReached && HealthBelowPct(30) && phaseTwo)
+ if (me->HasReactState(REACT_AGGRESSIVE) && !_gateIsOpen && !IsOnSameSide(me, me->GetVictim()))
{
- thirtyPercentReached = true;
- instance->SetData(DATA_GOTHIK_GATE, GO_STATE_ACTIVE);
+ // NBD: this should only happen in practice if there is nobody left alive on our side (we should open gate)
+ // thus we only do a cursory check to make sure (edge cases?)
+ if (Player* newTarget = FindEligibleTarget(me, _gateIsOpen))
+ {
+ me->getThreatManager().resetAllAggro();
+ me->AddThreat(newTarget, 1.0f);
+ AttackStart(newTarget);
+ }
+ else
+ OpenGate();
}
- if (me->HasUnitState(UNIT_STATE_CASTING))
- return;
+ events.Update(diff);
+
+ if (!_gateIsOpen && HealthBelowPct(30) && events.IsInPhase(PHASE_TWO))
+ OpenGate();
while (uint32 eventId = events.ExecuteEvent())
{
switch (eventId)
{
case EVENT_SUMMON:
- if (waves[waveCount].entry)
+ {
+ if (RAID_MODE(waves10,waves25).size() <= _waveCount) // bounds check
{
- if ((waves[waveCount].mode == 2) && (GetDifficulty() == RAID_DIFFICULTY_25MAN_NORMAL))
- DoGothikSummon(waves[waveCount].entry);
- else if ((waves[waveCount].mode == 0) && (GetDifficulty() == RAID_DIFFICULTY_10MAN_NORMAL))
- DoGothikSummon(waves[waveCount].entry);
- else if (waves[waveCount].mode == 1)
- DoGothikSummon(waves[waveCount].entry);
-
- // if group is not splitted, open gate and merge both sides at ~ 2 minutes (wave 11)
- if (waveCount == 11)
+ TC_LOG_INFO("scripts", "GothikAI: Wave count %d is out of range for difficulty %d.", _waveCount, GetDifficulty());
+ break;
+ }
+
+ std::list<Creature*> triggers;
+ me->GetCreatureListWithEntryInGrid(triggers, NPC_TRIGGER, 150.0f);
+ for (GothikWaveEntry entry : RAID_MODE(waves10, waves25)[_waveCount].first)
+ for (uint8 i = 0; i < entry.second; ++i)
{
- if (!CheckGroupSplitted())
+ // GUID layout is as follows:
+ // CGUID+4: center (back of platform) - primary rider spawn
+ // CGUID+5: north (back of platform) - primary knight spawn
+ // CGUID+6: center (front of platform) - second spawn
+ // CGUID+7: south (front of platform) - primary trainee spawn
+ uint32 targetDBGuid;
+ switch (entry.first)
{
- instance->SetData(DATA_GOTHIK_GATE, GO_STATE_ACTIVE);
- DummyEntryCheckPredicate pred;
- summons.DoAction(0, pred); //! Magic numbers fail
- summons.DoZoneInCombat();
- mergedSides = true;
+ case NPC_LIVE_RIDER: // only spawns from center (back) > north
+ targetDBGuid = (CGUID_TRIGGER + 4) + (i % 2);
+ break;
+ case NPC_LIVE_KNIGHT: // spawns north > center (front) > south
+ targetDBGuid = (CGUID_TRIGGER + 5) + (i % 3);
+ break;
+ case NPC_LIVE_TRAINEE: // spawns south > center (front) > north
+ targetDBGuid = (CGUID_TRIGGER + 7) - (i % 3);
+ break;
+ default:
+ targetDBGuid = 0;
}
+
+ for (Creature* trigger : triggers)
+ if (trigger && trigger->GetSpawnId() == targetDBGuid)
+ {
+ DoSummon(entry.first, trigger, 1.0f, 15 * IN_MILLISECONDS, TEMPSUMMON_CORPSE_TIMED_DESPAWN);
+ break;
+ }
}
- if (waves[waveCount].mode == 1)
- events.ScheduleEvent(EVENT_SUMMON, waves[waveCount].time);
- else if ((waves[waveCount].mode == 2) && (GetDifficulty() == RAID_DIFFICULTY_25MAN_NORMAL))
- events.ScheduleEvent(EVENT_SUMMON, waves[waveCount].time);
- else if ((waves[waveCount].mode == 0) && (GetDifficulty() == RAID_DIFFICULTY_10MAN_NORMAL))
- events.ScheduleEvent(EVENT_SUMMON, waves[waveCount].time);
- else
- events.ScheduleEvent(EVENT_SUMMON, 0);
+ if (uint8 timeToNext = RAID_MODE(waves10, waves25)[_waveCount].second)
+ events.ScheduleEvent(EVENT_SUMMON, timeToNext * IN_MILLISECONDS, 0, PHASE_ONE);
- ++waveCount;
- }
- else
- {
- phaseTwo = true;
- Talk(SAY_TELEPORT);
- DoTeleportTo(PosGroundLiveSide);
- me->SetReactState(REACT_AGGRESSIVE);
- DummyEntryCheckPredicate pred;
- summons.DoAction(0, pred); //! Magic numbers fail
- summons.DoZoneInCombat();
- events.ScheduleEvent(EVENT_BOLT, 1000);
- events.ScheduleEvent(EVENT_HARVEST, urand(3000, 15000));
- events.ScheduleEvent(EVENT_TELEPORT, 20000);
- }
+ ++_waveCount;
break;
- case EVENT_BOLT:
- DoCastVictim(SPELL_SHADOW_BOLT);
- events.ScheduleEvent(EVENT_BOLT, 1000);
+ }
+ case EVENT_DOORS_UNLOCK:
+ _gateCanOpen = true;
+ for (ObjectGuid summonGuid : summons)
+ if (Creature* summon = ObjectAccessor::GetCreature(*me, summonGuid))
+ if (summon->IsAlive() && (!summon->IsInCombat() || summon->IsInEvadeMode()))
+ {
+ OpenGate();
+ break;
+ }
break;
- case EVENT_HARVEST:
- DoCastVictim(SPELL_HARVEST_SOUL, true);
- events.ScheduleEvent(EVENT_HARVEST, urand(20000, 25000));
+ case EVENT_PHASE_TWO:
+ events.SetPhase(PHASE_TWO);
+ events.ScheduleEvent(EVENT_TELEPORT, 20 * IN_MILLISECONDS, 0, PHASE_TWO);
+ events.ScheduleEvent(EVENT_HARVEST, 15 * IN_MILLISECONDS, 0, PHASE_TWO);
+ events.ScheduleEvent(EVENT_RESUME_ATTACK, 2 * IN_MILLISECONDS, 0, PHASE_TWO);
+ Talk(SAY_PHASE_TWO);
+ Talk(EMOTE_PHASE_TWO);
+ me->SetReactState(REACT_PASSIVE);
+ me->getThreatManager().resetAllAggro();
+ DoCastAOE(SPELL_TELEPORT_LIVE);
break;
case EVENT_TELEPORT:
- if (!thirtyPercentReached)
+ if (!HealthBelowPct(30))
{
+ me->CastStop();
me->AttackStop();
- if (IN_LIVE_SIDE(me))
- DoTeleportTo(PosGroundDeadSide);
- else
- DoTeleportTo(PosGroundLiveSide);
-
- me->getThreatManager().resetAggro(NotOnSameSide(me));
- if (Unit* target = SelectTarget(SELECT_TARGET_NEAREST, 0))
- {
- me->getThreatManager().addThreat(target, 100.0f);
- AttackStart(target);
- }
-
- events.ScheduleEvent(EVENT_TELEPORT, 20000);
+ me->StopMoving();
+ me->SetReactState(REACT_PASSIVE);
+ me->getThreatManager().resetAllAggro();
+ DoCastAOE(_lastTeleportDead ? SPELL_TELEPORT_LIVE : SPELL_TELEPORT_DEAD);
+ _lastTeleportDead = !_lastTeleportDead;
+
+ events.CancelEvent(EVENT_BOLT);
+ events.ScheduleEvent(EVENT_TELEPORT, 20 * IN_MILLISECONDS, 0, PHASE_TWO);
+ events.ScheduleEvent(EVENT_RESUME_ATTACK, 2 * IN_MILLISECONDS, 0, PHASE_TWO);
}
break;
+
+ case EVENT_HARVEST:
+ DoCastAOE(SPELL_HARVEST_SOUL, true); // triggered allows this to go "through" shadow bolt
+ events.ScheduleEvent(EVENT_HARVEST, 15 * IN_MILLISECONDS, 0, PHASE_TWO);
+ break;
+ case EVENT_RESUME_ATTACK:
+ me->SetReactState(REACT_AGGRESSIVE);
+ events.ScheduleEvent(EVENT_BOLT, 0, 0, PHASE_TWO);
+ // return to the start of this method so victim side etc is re-evaluated
+ return UpdateAI(0u); // tail recursion for efficiency
+ case EVENT_BOLT:
+ DoCastVictim(SPELL_SHADOW_BOLT);
+ events.ScheduleEvent(EVENT_BOLT, 1 * IN_MILLISECONDS, 0, PHASE_TWO);
+ break;
+ case EVENT_INTRO_2:
+ Talk(SAY_INTRO_2);
+ break;
+ case EVENT_INTRO_3:
+ Talk(SAY_INTRO_3);
+ break;
+ case EVENT_INTRO_4:
+ Talk(SAY_INTRO_4);
+ break;
}
}
-
- if (!phaseTwo)
- DoMeleeAttackIfReady();
}
+
+ private:
+ uint32 _waveCount;
+ bool _gateCanOpen;
+ bool _gateIsOpen;
+ bool _lastTeleportDead;
};
CreatureAI* GetAI(Creature* creature) const override
@@ -498,86 +562,407 @@ class boss_gothik : public CreatureScript
}
};
-class npc_gothik_minion : public CreatureScript
+struct npc_gothik_minion_baseAI : public ScriptedAI
{
public:
- npc_gothik_minion() : CreatureScript("npc_gothik_minion") { }
+ npc_gothik_minion_baseAI(Creature* creature, uint32 deathNotify=0) : ScriptedAI(creature), _deathNotify(deathNotify), _gateIsOpen(false) { }
+
+ void JustDied(Unit* /*killer*/) override
+ {
+ if (_deathNotify)
+ DoCastAOE(_deathNotify, true);
+ }
+
+ inline bool isOnSameSide(Unit const* who) const
+ {
+ return IsOnSameSide(me, who);
+ }
- struct npc_gothik_minionAI : public CombatAI
+ void DamageTaken(Unit* attacker, uint32 &damage) override
+ { // do not allow minions to take damage before the gate is opened
+ if (!_gateIsOpen && !isOnSameSide(attacker))
+ damage = 0;
+ }
+
+ void DoAction(int32 action) override
{
- npc_gothik_minionAI(Creature* creature) : CombatAI(creature)
+ switch (action)
{
- liveSide = IN_LIVE_SIDE(me);
- gateClose = false;
+ case ACTION_GATE_OPENED:
+ _gateIsOpen = true;
+ // intentional missing break
+ case ACTION_ACQUIRE_TARGET:
+ if (Player* target = FindEligibleTarget(me, _gateIsOpen))
+ {
+ me->AddThreat(target, 1.0f);
+ AttackStart(target);
+ }
+ else
+ EnterEvadeMode(EVADE_REASON_NO_HOSTILES);
+ break;
+ }
+ }
+
+ void EnterEvadeMode(EvadeReason why) override
+ {
+ ScriptedAI::EnterEvadeMode(why);
+
+ if (InstanceScript* instance = me->GetInstanceScript())
+ if (Creature* gothik = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_GOTHIK)))
+ gothik->AI()->DoAction(ACTION_MINION_EVADE);
+ }
+
+ void UpdateAI(uint32 diff) override
+ {
+ if (!UpdateVictim())
+ return;
+
+ if (!_gateIsOpen && !isOnSameSide(me->GetVictim()))
+ { // reset threat, then try to find someone on same side as us to attack
+ if (Player* newTarget = FindEligibleTarget(me, _gateIsOpen))
+ {
+ me->RemoveAurasByType(SPELL_AURA_MOD_TAUNT);
+ me->getThreatManager().resetAllAggro();
+ me->AddThreat(newTarget, 1.0f);
+ AttackStart(newTarget);
+ }
+ else
+ EnterEvadeMode(EVADE_REASON_NO_HOSTILES);
}
- bool liveSide;
- bool gateClose;
+ _UpdateAI(diff);
+ }
+
+ virtual void _UpdateAI(uint32 diff) { ScriptedAI::UpdateAI(diff); };
- bool isOnSameSide(Unit const* who) const
+ private:
+ uint32 _deathNotify;
+ bool _gateIsOpen;
+};
+
+class npc_gothik_minion_livingtrainee : public CreatureScript
+{
+ public:
+ npc_gothik_minion_livingtrainee() : CreatureScript("npc_gothik_minion_livingtrainee") { }
+
+ struct npc_gothik_minion_livingtraineeAI : public npc_gothik_minion_baseAI
+ {
+ npc_gothik_minion_livingtraineeAI(Creature* creature) : npc_gothik_minion_baseAI(creature, SPELL_ANCHOR_1_TRAINEE), _deathPlagueTimer(urandms(5,20)) { }
+
+ void _UpdateAI(uint32 diff)
{
- return (liveSide == IN_LIVE_SIDE(who));
+ if (diff < _deathPlagueTimer)
+ _deathPlagueTimer -= diff;
+ else
+ {
+ DoCastAOE(SPELL_DEATH_PLAGUE);
+ _deathPlagueTimer = urandms(5, 20);
+ }
+ DoMeleeAttackIfReady();
}
+ uint32 _deathPlagueTimer;
+ };
+
+ CreatureAI* GetAI(Creature* creature) const override
+ {
+ return GetInstanceAI<npc_gothik_minion_livingtraineeAI>(creature);
+ }
+};
+
+class npc_gothik_minion_livingknight : public CreatureScript
+{
+ public:
+ npc_gothik_minion_livingknight() : CreatureScript("npc_gothik_minion_livingknight") { }
- void DoAction(int32 param) override
+ struct npc_gothik_minion_livingknightAI : public npc_gothik_minion_baseAI
+ {
+ npc_gothik_minion_livingknightAI(Creature* creature) : npc_gothik_minion_baseAI(creature, SPELL_ANCHOR_1_DK), _whirlwindTimer(urandms(5,10)) { }
+
+ void _UpdateAI(uint32 diff)
{
- gateClose = param != 0;
+ if (diff < _whirlwindTimer)
+ _whirlwindTimer -= diff;
+ else
+ {
+ DoCastAOE(SPELL_SHADOW_MARK);
+ _whirlwindTimer = urandms(15, 20);
+ }
+ DoMeleeAttackIfReady();
}
+ uint32 _whirlwindTimer;
+ };
- void DamageTaken(Unit* attacker, uint32 &damage) override
+ CreatureAI* GetAI(Creature* creature) const override
+ {
+ return GetInstanceAI<npc_gothik_minion_livingknightAI>(creature);
+ }
+};
+
+class npc_gothik_minion_livingrider : public CreatureScript
+{
+ public:
+ npc_gothik_minion_livingrider() : CreatureScript("npc_gothik_minion_livingrider") { }
+
+ struct npc_gothik_minion_livingriderAI : public npc_gothik_minion_baseAI
+ {
+ npc_gothik_minion_livingriderAI(Creature* creature) : npc_gothik_minion_baseAI(creature, SPELL_ANCHOR_1_RIDER), _boltVolleyTimer(urandms(5,10)) { }
+
+ void _UpdateAI(uint32 diff)
{
- if (gateClose && !isOnSameSide(attacker))
- damage = 0;
+ if (diff < _boltVolleyTimer)
+ _boltVolleyTimer -= diff;
+ else
+ {
+ DoCastAOE(SPELL_SHADOW_BOLT_VOLLEY);
+ _boltVolleyTimer = urandms(10, 15);
+ }
+ if (!me->HasUnitState(UNIT_STATE_CASTING))
+ DoMeleeAttackIfReady();
}
+ uint32 _boltVolleyTimer;
+ };
- void JustDied(Unit* /*killer*/) override
+ CreatureAI* GetAI(Creature* creature) const override
+ {
+ return GetInstanceAI<npc_gothik_minion_livingriderAI>(creature);
+ }
+};
+
+class npc_gothik_minion_spectraltrainee : public CreatureScript
+{
+ public:
+ npc_gothik_minion_spectraltrainee() : CreatureScript("npc_gothik_minion_spectraltrainee") { }
+
+ struct npc_gothik_minion_spectraltraineeAI : public npc_gothik_minion_baseAI
+ {
+ npc_gothik_minion_spectraltraineeAI(Creature* creature) : npc_gothik_minion_baseAI(creature), _explosionTimer(2 * IN_MILLISECONDS) { }
+
+ void _UpdateAI(uint32 diff)
+ {
+ if (diff < _explosionTimer)
+ _explosionTimer -= diff;
+ else
{
- if (me->IsSummon())
- if (Unit* owner = me->ToTempSummon()->GetSummoner())
- CombatAI::JustDied(owner);
+ DoCastAOE(SPELL_ARCANE_EXPLOSION);
+ _explosionTimer = 2 * IN_MILLISECONDS;
}
+ DoMeleeAttackIfReady();
+ }
+ uint32 _explosionTimer;
+ };
- void EnterEvadeMode(EvadeReason why) override
+ CreatureAI* GetAI(Creature* creature) const override
+ {
+ return GetInstanceAI<npc_gothik_minion_spectraltraineeAI>(creature);
+ }
+};
+
+class npc_gothik_minion_spectralknight : public CreatureScript
+{
+ public:
+ npc_gothik_minion_spectralknight() : CreatureScript("npc_gothik_minion_spectralknight") { }
+
+ struct npc_gothik_minion_spectralknightAI : public npc_gothik_minion_baseAI
+ {
+ npc_gothik_minion_spectralknightAI(Creature* creature) : npc_gothik_minion_baseAI(creature), _whirlwindTimer(urandms(15,25)) { }
+
+ void _UpdateAI(uint32 diff)
+ {
+ if (diff < _whirlwindTimer)
+ _whirlwindTimer -= diff;
+ else
{
- if (!gateClose)
- {
- CombatAI::EnterEvadeMode(why);
- return;
- }
+ DoCastAOE(SPELL_WHIRLWIND);
+ _whirlwindTimer = urandms(20, 25);
+ }
+ DoMeleeAttackIfReady();
+ }
+ uint32 _whirlwindTimer;
+ };
- if (!_EnterEvadeMode())
- return;
+ CreatureAI* GetAI(Creature* creature) const override
+ {
+ return GetInstanceAI<npc_gothik_minion_spectralknightAI>(creature);
+ }
+};
+
+class npc_gothik_minion_spectralrider : public CreatureScript
+{
+ public:
+ npc_gothik_minion_spectralrider() : CreatureScript("npc_gothik_minion_spectralrider") { }
+
+ struct npc_gothik_minion_spectralriderAI : public npc_gothik_minion_baseAI
+ {
+ npc_gothik_minion_spectralriderAI(Creature* creature) : npc_gothik_minion_baseAI(creature), _frenzyTimer(urandms(2,5)), _drainTimer(urandms(8,12)) { }
- Map::PlayerList const &PlayerList = me->GetMap()->GetPlayers();
- for (Map::PlayerList::const_iterator i = PlayerList.begin(); i != PlayerList.end(); ++i)
+ void _UpdateAI(uint32 diff)
+ {
+ if (diff < _frenzyTimer)
+ _frenzyTimer -= diff;
+ else if (me->HasUnitState(UNIT_STATE_CASTING))
+ _frenzyTimer = 0;
+ else
+ { // target priority: knight > other rider > horse > gothik
+ std::list<Creature*> potentialTargets = DoFindFriendlyMissingBuff(30.0, SPELLHELPER_UNHOLY_FRENZY);
+ Creature *knightTarget = nullptr, *riderTarget = nullptr, *horseTarget = nullptr, *gothikTarget = nullptr;
+ for (Creature* pTarget : potentialTargets)
{
- if (i->GetSource() && i->GetSource()->IsAlive() && isOnSameSide(i->GetSource()))
+ switch (pTarget->GetEntry())
{
- AttackStart(i->GetSource());
- return;
+ case NPC_DEAD_KNIGHT:
+ knightTarget = pTarget;
+ break;
+ case NPC_DEAD_RIDER:
+ riderTarget = pTarget;
+ break;
+ case NPC_DEAD_HORSE:
+ horseTarget = pTarget;
+ break;
+ case NPC_GOTHIK:
+ gothikTarget = pTarget;
+ break;
}
+ if (knightTarget)
+ break;
}
+ Creature* target = knightTarget ? knightTarget : riderTarget ? riderTarget : horseTarget ? horseTarget : gothikTarget ? gothikTarget : nullptr;
+ if (target)
+ DoCast(target, SPELL_UNHOLY_FRENZY);
+ _frenzyTimer = 20 * IN_MILLISECONDS;
+ }
- me->GetMotionMaster()->MoveIdle();
- Reset();
+ if (diff < _drainTimer)
+ _drainTimer -= diff;
+ else
+ {
+ DoCastVictim(SPELL_DRAIN_LIFE);
+ _drainTimer = urandms(10,15);
}
- void UpdateAI(uint32 diff) override
+ if (!me->HasUnitState(UNIT_STATE_CASTING))
+ DoMeleeAttackIfReady();
+ }
+ uint32 _frenzyTimer, _drainTimer;
+ };
+
+ CreatureAI* GetAI(Creature* creature) const override
+ {
+ return GetInstanceAI<npc_gothik_minion_spectralriderAI>(creature);
+ }
+};
+
+class npc_gothik_minion_spectralhorse : public CreatureScript
+{
+ public:
+ npc_gothik_minion_spectralhorse() : CreatureScript("npc_gothik_minion_spectralhorse") { }
+
+ struct npc_gothik_minion_spectralhorseAI : public npc_gothik_minion_baseAI
+ {
+ npc_gothik_minion_spectralhorseAI(Creature* creature) : npc_gothik_minion_baseAI(creature), _stompTimer(urandms(10,15)) { }
+
+ void _UpdateAI(uint32 diff)
+ {
+ if (diff < _stompTimer)
+ _stompTimer -= diff;
+ else
{
- if (gateClose && (!isOnSameSide(me) || (me->GetVictim() && !isOnSameSide(me->GetVictim()))))
- {
- EnterEvadeMode(EVADE_REASON_OTHER);
- return;
- }
+ DoCastAOE(SPELL_STOMP);
+ _stompTimer = urandms(14, 18);
+ }
+ DoMeleeAttackIfReady();
+ }
+ uint32 _stompTimer;
+ };
- CombatAI::UpdateAI(diff);
+ CreatureAI* GetAI(Creature* creature) const override
+ {
+ return GetInstanceAI<npc_gothik_minion_spectralhorseAI>(creature);
+ }
+};
+
+class npc_gothik_trigger : public CreatureScript
+{
+public:
+ npc_gothik_trigger() : CreatureScript("npc_gothik_trigger") { }
+
+ CreatureAI* GetAI(Creature* creature) const override
+ {
+ return GetInstanceAI<npc_gothik_triggerAI>(creature);
+ }
+
+ struct npc_gothik_triggerAI : public ScriptedAI
+ {
+ npc_gothik_triggerAI(Creature* creature) : ScriptedAI(creature) { creature->SetDisableGravity(true); }
+
+ void EnterEvadeMode(EvadeReason /*why*/) override { }
+ void UpdateAI(uint32 /*diff*/) override { }
+ void EnterCombat(Unit* /*who*/) override { }
+ void DamageTaken(Unit* /*who*/, uint32& damage) override { damage = 0; }
+
+ Creature* SelectRandomSkullPile()
+ {
+ std::list<Creature*> triggers;
+ me->GetCreatureListWithEntryInGrid(triggers, NPC_TRIGGER, 150.0f);
+ uint32 targetDBGuid = CGUID_TRIGGER + urand(8, 12); // CGUID+8 to CGUID+12 are the triggers for the skull piles on dead side
+ for (Creature* trigger : triggers)
+ if (trigger && trigger->GetSpawnId() == targetDBGuid)
+ return trigger;
+
+ return nullptr;
+ }
+ void SpellHit(Unit* /*caster*/, SpellInfo const* spell) override
+ {
+ if (!spell)
+ return;
+
+ switch (spell->Id)
+ {
+ case SPELL_ANCHOR_1_TRAINEE:
+ DoCastAOE(SPELL_ANCHOR_2_TRAINEE, true);
+ break;
+ case SPELL_ANCHOR_1_DK:
+ DoCastAOE(SPELL_ANCHOR_2_DK, true);
+ break;
+ case SPELL_ANCHOR_1_RIDER:
+ DoCastAOE(SPELL_ANCHOR_2_RIDER, true);
+ break;
+ case SPELL_ANCHOR_2_TRAINEE:
+ if (Creature* target = SelectRandomSkullPile())
+ DoCast(target, SPELL_SKULLS_TRAINEE, true);
+ break;
+ case SPELL_ANCHOR_2_DK:
+ if (Creature* target = SelectRandomSkullPile())
+ DoCast(target, SPELL_SKULLS_DK, true);
+ break;
+ case SPELL_ANCHOR_2_RIDER:
+ if (Creature* target = SelectRandomSkullPile())
+ DoCast(target, SPELL_SKULLS_RIDER, true);
+ break;
+ case SPELL_SKULLS_TRAINEE:
+ DoSummon(NPC_DEAD_TRAINEE, me, 0.0f, 15 * IN_MILLISECONDS, TEMPSUMMON_CORPSE_TIMED_DESPAWN);
+ break;
+ case SPELL_SKULLS_DK:
+ DoSummon(NPC_DEAD_KNIGHT, me, 0.0f, 15 * IN_MILLISECONDS, TEMPSUMMON_CORPSE_TIMED_DESPAWN);
+ break;
+ case SPELL_SKULLS_RIDER:
+ DoSummon(NPC_DEAD_RIDER, me, 0.0f, 15 * IN_MILLISECONDS, TEMPSUMMON_CORPSE_TIMED_DESPAWN);
+ DoSummon(NPC_DEAD_HORSE, me, 0.0f, 15 * IN_MILLISECONDS, TEMPSUMMON_CORPSE_TIMED_DESPAWN);
+ break;
}
- };
+ }
- CreatureAI* GetAI(Creature* creature) const override
+ // dead side summons are "owned" by gothik
+ void JustSummoned(Creature* summon) override
+ {
+ if (Creature* gothik = ObjectAccessor::GetCreature(*me, me->GetInstanceScript()->GetGuidData(DATA_GOTHIK)))
+ gothik->AI()->JustSummoned(summon);
+ }
+ void SummonedCreatureDespawn(Creature* summon) override
{
- return new npc_gothik_minionAI(creature);
+ if (Creature* gothik = ObjectAccessor::GetCreature(*me, me->GetInstanceScript()->GetGuidData(DATA_GOTHIK)))
+ gothik->AI()->SummonedCreatureDespawn(summon);
}
+ };
};
class spell_gothik_shadow_bolt_volley : public SpellScriptLoader
@@ -609,6 +994,13 @@ class spell_gothik_shadow_bolt_volley : public SpellScriptLoader
void AddSC_boss_gothik()
{
new boss_gothik();
- new npc_gothik_minion();
+ new npc_gothik_minion_livingtrainee();
+ new npc_gothik_minion_livingknight();
+ new npc_gothik_minion_livingrider();
+ new npc_gothik_minion_spectraltrainee();
+ new npc_gothik_minion_spectralknight();
+ new npc_gothik_minion_spectralrider();
+ new npc_gothik_minion_spectralhorse();
+ new npc_gothik_trigger();
new spell_gothik_shadow_bolt_volley();
}
diff --git a/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp b/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp
index 916bc3d0438..c8a4eb7fbc8 100644
--- a/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp
+++ b/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp
@@ -91,15 +91,6 @@ DoorData const doorData[] =
{ 0, 0, DOOR_TYPE_ROOM }
};
-MinionData const minionData[] =
-{
- { NPC_SIR, BOSS_HORSEMEN },
- { NPC_THANE, BOSS_HORSEMEN },
- { NPC_LADY, BOSS_HORSEMEN },
- { NPC_BARON, BOSS_HORSEMEN },
- { 0, 0, }
-};
-
ObjectData const objectData[] =
{
{ GO_NAXX_PORTAL_ARACHNID, DATA_NAXX_PORTAL_ARACHNID },
@@ -152,11 +143,8 @@ class instance_naxxramas : public InstanceMapScript
SetBossNumber(EncounterCount);
LoadBossBoundaries(boundaries);
LoadDoorData(doorData);
- LoadMinionData(minionData);
LoadObjectData(nullptr, objectData);
- minHorsemenDiedTime = 0;
- maxHorsemenDiedTime = 0;
AbominationCount = 0;
hadAnubRekhanGreet = false;
hadFaerlinaGreet = false;
@@ -179,6 +167,9 @@ class instance_naxxramas : public InstanceMapScript
case NPC_RAZUVIOUS:
RazuviousGUID = creature->GetGUID();
break;
+ case NPC_GOTHIK:
+ GothikGUID = creature->GetGUID();
+ break;
case NPC_THANE:
ThaneGUID = creature->GetGUID();
break;
@@ -215,13 +206,6 @@ class instance_naxxramas : public InstanceMapScript
default:
break;
}
-
- AddMinion(creature, true);
- }
-
- void OnCreatureRemove(Creature* creature) override
- {
- AddMinion(creature, false);
}
void ProcessEvent(WorldObject* /*source*/, uint32 eventId) override
@@ -351,25 +335,6 @@ class instance_naxxramas : public InstanceMapScript
if (GameObject* gate = instance->GetGameObject(GothikGateGUID))
gate->SetGoState(GOState(value));
break;
- case DATA_HORSEMEN0:
- case DATA_HORSEMEN1:
- case DATA_HORSEMEN2:
- case DATA_HORSEMEN3:
- if (value == NOT_STARTED)
- {
- minHorsemenDiedTime = 0;
- maxHorsemenDiedTime = 0;
- }
- else if (value == DONE)
- {
- time_t now = time(NULL);
-
- if (minHorsemenDiedTime == 0)
- minHorsemenDiedTime = now;
-
- maxHorsemenDiedTime = now;
- }
- break;
case DATA_ABOMINATION_KILLED:
AbominationCount = value;
break;
@@ -416,6 +381,8 @@ class instance_naxxramas : public InstanceMapScript
return FaerlinaGUID;
case DATA_RAZUVIOUS:
return RazuviousGUID;
+ case DATA_GOTHIK:
+ return GothikGUID;
case DATA_THANE:
return ThaneGUID;
case DATA_LADY:
@@ -648,13 +615,13 @@ class instance_naxxramas : public InstanceMapScript
{
switch (criteria_id)
{
- case 7600: // Criteria for achievement 2176: And They Would All Go Down Together 15sec of each other 10-man
- if (Difficulty(instance->GetSpawnMode()) == RAID_DIFFICULTY_10MAN_NORMAL && (maxHorsemenDiedTime - minHorsemenDiedTime) < 15)
- return true;
- return false;
- case 7601: // Criteria for achievement 2177: And They Would All Go Down Together 15sec of each other 25-man
- if (Difficulty(instance->GetSpawnMode()) == RAID_DIFFICULTY_25MAN_NORMAL && (maxHorsemenDiedTime - minHorsemenDiedTime) < 15)
- return true;
+ // And They Would All Go Down Together (kill 4HM within 15sec of each other)
+ case 7600: // 25-man
+ case 7601: // 10-man
+ if (criteria_id + instance->GetSpawnMode() == 7601)
+ return false;
+ if (Creature* baron = instance->GetCreature(BaronGUID)) // it doesn't matter which one we use, really
+ return (baron->AI()->GetData(DATA_HORSEMEN_CHECK_ACHIEVEMENT_CREDIT) == 1u);
return false;
// Difficulty checks are done on DB.
// Criteria for achievement 2186: The Immortal (25-man)
@@ -693,6 +660,7 @@ class instance_naxxramas : public InstanceMapScript
// Instructor Razuvious
ObjectGuid RazuviousGUID;
// Gothik the Harvester
+ ObjectGuid GothikGUID;
ObjectGuid GothikGateGUID;
// The Four Horsemen
ObjectGuid ThaneGUID;
@@ -700,8 +668,6 @@ class instance_naxxramas : public InstanceMapScript
ObjectGuid BaronGUID;
ObjectGuid SirGUID;
ObjectGuid HorsemenChestGUID;
- time_t minHorsemenDiedTime;
- time_t maxHorsemenDiedTime;
/* The Construct Quarter */
// Thaddius
diff --git a/src/server/scripts/Northrend/Naxxramas/naxxramas.h b/src/server/scripts/Northrend/Naxxramas/naxxramas.h
index c14ff31bb94..c0caa86e93f 100644
--- a/src/server/scripts/Northrend/Naxxramas/naxxramas.h
+++ b/src/server/scripts/Northrend/Naxxramas/naxxramas.h
@@ -50,10 +50,7 @@ enum Data
DATA_HAD_FAERLINA_GREET,
DATA_HAD_THADDIUS_GREET,
- DATA_HORSEMEN0,
- DATA_HORSEMEN1,
- DATA_HORSEMEN2,
- DATA_HORSEMEN3,
+ DATA_HORSEMEN_CHECK_ACHIEVEMENT_CREDIT,
DATA_ABOMINATION_KILLED,
DATA_NAXX_PORTAL_ARACHNID,
@@ -67,6 +64,7 @@ enum Data64
DATA_ANUBREKHAN,
DATA_FAERLINA,
DATA_RAZUVIOUS,
+ DATA_GOTHIK,
DATA_THANE,
DATA_LADY,
DATA_BARON,
@@ -89,6 +87,7 @@ enum CreaturesIds
NPC_ANUBREKHAN = 15956,
NPC_FAERLINA = 15953,
NPC_RAZUVIOUS = 16061,
+ NPC_GOTHIK = 16060,
NPC_THANE = 16064,
NPC_LADY = 16065,
NPC_BARON = 30549,
diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_algalon_the_observer.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_algalon_the_observer.cpp
index dbb00fa252e..89868fc7bf2 100644
--- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_algalon_the_observer.cpp
+++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_algalon_the_observer.cpp
@@ -979,6 +979,9 @@ class go_celestial_planetarium_access : public GameObjectScript
bool GossipHello(Player* player) override
{
+ if (go->HasFlag(GAMEOBJECT_FLAGS, GO_FLAG_IN_USE))
+ return true;
+
bool hasKey = true;
if (LockEntry const* lock = sLockStore.LookupEntry(go->GetGOInfo()->goober.lockId))
{
diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_flame_leviathan.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_flame_leviathan.cpp
index c77f5b2bce3..6dfc3bf01e7 100644
--- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_flame_leviathan.cpp
+++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_flame_leviathan.cpp
@@ -1186,6 +1186,7 @@ class npc_brann_bronzebeard_ulduar_intro : public CreatureScript
{
if (menuId == GOSSIP_MENU_BRANN_BRONZEBEARD && gossipListId == GOSSIP_OPTION_BRANN_BRONZEBEARD)
{
+ me->RemoveFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_GOSSIP);
player->PlayerTalkClass->SendCloseGossip();
if (Creature* loreKeeper = _instance->GetCreature(DATA_LORE_KEEPER_OF_NORGANNON))
loreKeeper->RemoveFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_GOSSIP);
@@ -1238,6 +1239,7 @@ class npc_lorekeeper : public CreatureScript
{
if (menuId == GOSSIP_MENU_LORE_KEEPER && gossipListId == GOSSIP_OPTION_LORE_KEEPER)
{
+ me->RemoveFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_GOSSIP);
player->PlayerTalkClass->SendCloseGossip();
_instance->instance->LoadGrid(364, -16); // make sure leviathan is loaded
@@ -1250,6 +1252,7 @@ class npc_lorekeeper : public CreatureScript
{
if (Creature* brann = _instance->GetCreature(DATA_BRANN_BRONZEBEARD_INTRO))
{
+ brann->RemoveFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_GOSSIP);
delorah->GetMotionMaster()->MovePoint(0, brann->GetPositionX() - 4, brann->GetPositionY(), brann->GetPositionZ());
/// @todo delorah->AI()->Talk(xxxx, brann->GetGUID()); when reached at branz
}
@@ -1765,9 +1768,7 @@ class spell_vehicle_throw_passenger : public SpellScriptLoader
else
{
passenger->ExitVehicle();
- float x, y, z;
- targets.GetDstPos()->GetPosition(x, y, z);
- passenger->GetMotionMaster()->MoveJump(x, y, z, targets.GetSpeedXY(), targets.GetSpeedZ());
+ passenger->GetMotionMaster()->MoveJump(*targets.GetDstPos(), targets.GetSpeedXY(), targets.GetSpeedZ());
}
}
}
diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_freya.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_freya.cpp
index df5877d9220..05beacca638 100644
--- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_freya.cpp
+++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_freya.cpp
@@ -222,7 +222,6 @@ class npc_iron_roots : public CreatureScript
{
SetCombatMovement(false);
- me->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_KNOCK_BACK, true);
me->ApplySpellImmune(0, IMMUNITY_ID, 49560, true); // Death Grip
me->setFaction(14);
me->SetReactState(REACT_PASSIVE);
diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_general_vezax.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_general_vezax.cpp
index 385f7d6a69d..09d95b34521 100644
--- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_general_vezax.cpp
+++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_general_vezax.cpp
@@ -380,7 +380,6 @@ class npc_saronite_vapors : public CreatureScript
{
Talk(EMOTE_VAPORS);
instance = me->GetInstanceScript();
- me->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_KNOCK_BACK, true);
me->ApplySpellImmune(0, IMMUNITY_ID, 49560, true); // Death Grip jump effect
me->SetReactState(REACT_PASSIVE);
}
diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_mimiron.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_mimiron.cpp
index 820332791c8..e694433c614 100644
--- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_mimiron.cpp
+++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_mimiron.cpp
@@ -444,7 +444,7 @@ class boss_mimiron : public CreatureScript
DoCastAOE(SPELL_DESPAWN_ASSAULT_BOTS);
me->ExitVehicle();
// ExitVehicle() offset position is not implemented, so we make up for that with MoveJump()...
- me->GetMotionMaster()->MoveJump(me->GetPositionX() + (10.f * std::cos(me->GetOrientation())), me->GetPositionY() + (10.f * std::sin(me->GetOrientation())), me->GetPositionZ(), 10.f, 5.f);
+ me->GetMotionMaster()->MoveJump(me->GetPositionX() + (10.f * std::cos(me->GetOrientation())), me->GetPositionY() + (10.f * std::sin(me->GetOrientation())), me->GetPositionZ(), me->GetOrientation(), 10.f, 5.f);
events.ScheduleEvent(EVENT_OUTTRO_1, 7000);
}
@@ -1634,8 +1634,11 @@ class go_mimiron_hardmode_button : public GameObjectScript
public:
go_mimiron_hardmode_button() : GameObjectScript("go_mimiron_hardmode_button") { }
- bool OnGossipHello(Player* /*player*/, GameObject* go)
+ bool OnGossipHello(Player* /*player*/, GameObject* go) override
{
+ if (go->HasFlag(GAMEOBJECT_FLAGS, GO_FLAG_NOT_SELECTABLE))
+ return true;
+
InstanceScript* instance = go->GetInstanceScript();
if (!instance)
return false;
diff --git a/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/boss_ingvar_the_plunderer.cpp b/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/boss_ingvar_the_plunderer.cpp
index b72bcbecdac..86a4a9caf3a 100644
--- a/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/boss_ingvar_the_plunderer.cpp
+++ b/src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/boss_ingvar_the_plunderer.cpp
@@ -110,30 +110,24 @@ class boss_ingvar_the_plunderer : public CreatureScript
{
if (me->GetEntry() != NPC_INGVAR)
me->UpdateEntry(NPC_INGVAR);
-
me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_NOT_SELECTABLE);
_Reset();
- events.SetPhase(PHASE_HUMAN);
-
- events.ScheduleEvent(EVENT_CLEAVE, urand(6, 12)*IN_MILLISECONDS, 0, PHASE_HUMAN);
- events.ScheduleEvent(EVENT_STAGGERING_ROAR, urand(18, 21)*IN_MILLISECONDS, 0, PHASE_HUMAN);
- events.ScheduleEvent(EVENT_ENRAGE, urand(7, 14)*IN_MILLISECONDS, 0, PHASE_HUMAN);
- events.ScheduleEvent(EVENT_SMASH, urand(12, 17)*IN_MILLISECONDS, 0, PHASE_HUMAN);
}
void DamageTaken(Unit* /*doneBy*/, uint32& damage) override
{
if (damage >= me->GetHealth() && events.IsInPhase(PHASE_HUMAN))
{
+ events.SetPhase(PHASE_EVENT);
+ events.ScheduleEvent(EVENT_SUMMON_BANSHEE, 3 * IN_MILLISECONDS, 0, PHASE_EVENT);
+
me->RemoveAllAuras();
+ me->StopMoving();
DoCast(me, SPELL_INGVAR_FEIGN_DEATH, true);
me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_NOT_SELECTABLE);
- events.SetPhase(PHASE_EVENT);
- events.ScheduleEvent(EVENT_SUMMON_BANSHEE, 3 * IN_MILLISECONDS, 0, PHASE_EVENT);
-
Talk(SAY_DEATH);
}
@@ -152,13 +146,28 @@ class boss_ingvar_the_plunderer : public CreatureScript
me->RemoveAura(SPELL_INGVAR_FEIGN_DEATH);
DoCast(me, SPELL_INGVAR_TRANSFORM, true);
me->UpdateEntry(NPC_INGVAR_UNDEAD);
- events.ScheduleEvent(EVENT_JUST_TRANSFORMED, 2 * IN_MILLISECONDS, 0, PHASE_EVENT);
+ events.ScheduleEvent(EVENT_JUST_TRANSFORMED, IN_MILLISECONDS / 2, 0, PHASE_EVENT);
}
void EnterCombat(Unit* /*who*/) override
{
+ if (events.IsInPhase(PHASE_EVENT) || events.IsInPhase(PHASE_UNDEAD)) // ingvar gets multiple EnterCombat calls
+ return;
_EnterCombat();
+
Talk(SAY_AGGRO);
+ events.SetPhase(PHASE_HUMAN);
+ events.ScheduleEvent(EVENT_CLEAVE, urand(6, 12)*IN_MILLISECONDS, 0, PHASE_HUMAN);
+ events.ScheduleEvent(EVENT_STAGGERING_ROAR, urand(18, 21)*IN_MILLISECONDS, 0, PHASE_HUMAN);
+ events.ScheduleEvent(EVENT_ENRAGE, urand(7, 14)*IN_MILLISECONDS, 0, PHASE_HUMAN);
+ events.ScheduleEvent(EVENT_SMASH, urand(12, 17)*IN_MILLISECONDS, 0, PHASE_HUMAN);
+ }
+
+ void AttackStart(Unit* who) override
+ {
+ if (events.IsInPhase(PHASE_EVENT)) // prevent ingvar from beginning to attack/chase during transition
+ return;
+ BossAI::AttackStart(who);
}
void JustDied(Unit* /*killer*/) override
@@ -171,7 +180,7 @@ class boss_ingvar_the_plunderer : public CreatureScript
{
events.SetPhase(PHASE_UNDEAD);
events.ScheduleEvent(EVENT_DARK_SMASH, urand(14, 18)*IN_MILLISECONDS, 0, PHASE_UNDEAD);
- events.ScheduleEvent(EVENT_DREADFUL_ROAR, urand(18, 22)*IN_MILLISECONDS, 0, PHASE_UNDEAD);
+ events.ScheduleEvent(EVENT_DREADFUL_ROAR, 0, 0, PHASE_UNDEAD);
events.ScheduleEvent(EVENT_WOE_STRIKE, urand(10, 14)*IN_MILLISECONDS, 0, PHASE_UNDEAD);
events.ScheduleEvent(EVENT_SHADOW_AXE, 30*IN_MILLISECONDS, 0, PHASE_UNDEAD);
}
@@ -214,9 +223,17 @@ class boss_ingvar_the_plunderer : public CreatureScript
events.ScheduleEvent(EVENT_SMASH, urand(12, 16)*IN_MILLISECONDS, 0, PHASE_HUMAN);
break;
case EVENT_JUST_TRANSFORMED:
+ ScheduleSecondPhase();
me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_NOT_SELECTABLE);
+ if (Unit* target = me->getThreatManager().getHostilTarget())
+ AttackStart(target);
+ else
+ {
+ EnterEvadeMode(EVADE_REASON_NO_HOSTILES);
+ return;
+ }
+ Talk(SAY_AGGRO);
DoZoneInCombat();
- ScheduleSecondPhase();
return;
case EVENT_SUMMON_BANSHEE:
DoCast(me, SPELL_SUMMON_BANSHEE);
diff --git a/src/server/scripts/Northrend/UtgardeKeep/UtgardePinnacle/boss_skadi.cpp b/src/server/scripts/Northrend/UtgardeKeep/UtgardePinnacle/boss_skadi.cpp
index 77b376bf7d5..d7b65093898 100644
--- a/src/server/scripts/Northrend/UtgardeKeep/UtgardePinnacle/boss_skadi.cpp
+++ b/src/server/scripts/Northrend/UtgardeKeep/UtgardePinnacle/boss_skadi.cpp
@@ -238,7 +238,7 @@ public:
me->SetInCombatWithZone();
instance->SetBossState(DATA_SKADI_THE_RUTHLESS, IN_PROGRESS);
instance->DoStartTimedAchievement(ACHIEVEMENT_TIMED_TYPE_EVENT, ACHIEV_TIMED_START_EVENT);
- me->GetMotionMaster()->MoveJump(Location[0].GetPositionX(), Location[0].GetPositionY(), Location[0].GetPositionZ(), 5.0f, 10.0f);
+ me->GetMotionMaster()->MoveJump(Location[0], 5.0f, 10.0f);
me->SetWalk(false);
m_uiMountTimer = 1000;
Summons.DespawnEntry(NPC_GRAUF);
@@ -289,7 +289,7 @@ public:
pGrauf->GetMotionMaster()->MoveFall();
pGrauf->HandleEmoteCommand(EMOTE_ONESHOT_FLYDEATH);
}
- me->GetMotionMaster()->MoveJump(Location[4].GetPositionX(), Location[4].GetPositionY(), Location[4].GetPositionZ(), 5.0f, 10.0f);
+ me->GetMotionMaster()->MoveJump(Location[4], 5.0f, 10.0f);
me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE | UNIT_FLAG_NON_ATTACKABLE);
Talk(SAY_DRAKE_DEATH);
m_uiCrushTimer = 8000;
diff --git a/src/server/scripts/Northrend/isle_of_conquest.cpp b/src/server/scripts/Northrend/isle_of_conquest.cpp
index 14d862eca39..68121e940c9 100644
--- a/src/server/scripts/Northrend/isle_of_conquest.cpp
+++ b/src/server/scripts/Northrend/isle_of_conquest.cpp
@@ -206,7 +206,7 @@ class spell_ioc_parachute_ic : public SpellScriptLoader
class StartLaunchEvent : public BasicEvent
{
public:
- StartLaunchEvent(float x, float y, float z, ObjectGuid::LowType lowGuid) : _x(x), _y(y), _z(z), _lowGuid(lowGuid)
+ StartLaunchEvent(Position const& pos, ObjectGuid::LowType lowGuid) : _pos(pos), _lowGuid(lowGuid)
{
}
@@ -218,15 +218,15 @@ class StartLaunchEvent : public BasicEvent
player->AddAura(SPELL_LAUNCH_NO_FALLING_DAMAGE, player); // prevents falling damage
float speedZ = 10.0f;
- float dist = player->GetExactDist2d(_x, _y);
+ float dist = player->GetExactDist2d(&_pos);
player->ExitVehicle();
- player->GetMotionMaster()->MoveJump(_x, _y, _z, dist, speedZ);
+ player->GetMotionMaster()->MoveJump(_pos, dist, speedZ, EVENT_JUMP, true);
return true;
}
private:
- float _x, _y, _z;
+ Position _pos;
ObjectGuid::LowType _lowGuid;
};
@@ -244,11 +244,7 @@ class spell_ioc_launch : public SpellScriptLoader
if (!GetCaster()->ToCreature() || !GetExplTargetDest())
return;
- float x, y, z;
- x = GetExplTargetDest()->GetPositionX();
- y = GetExplTargetDest()->GetPositionY();
- z = GetExplTargetDest()->GetPositionZ();
- GetCaster()->ToCreature()->m_Events.AddEvent(new StartLaunchEvent(x, y, z, GetHitPlayer()->GetGUID().GetCounter()), GetCaster()->ToCreature()->m_Events.CalculateTime(2500));
+ GetCaster()->ToCreature()->m_Events.AddEvent(new StartLaunchEvent(*GetExplTargetDest(), GetHitPlayer()->GetGUID().GetCounter()), GetCaster()->ToCreature()->m_Events.CalculateTime(2500));
}
void Register() override
diff --git a/src/server/scripts/Northrend/zone_icecrown.cpp b/src/server/scripts/Northrend/zone_icecrown.cpp
index 88217cb384e..1e020edd10a 100644
--- a/src/server/scripts/Northrend/zone_icecrown.cpp
+++ b/src/server/scripts/Northrend/zone_icecrown.cpp
@@ -213,7 +213,6 @@ class npc_tournament_training_dummy : public CreatureScript
void Reset() override
{
me->SetControlled(true, UNIT_STATE_STUNNED);
- me->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_KNOCK_BACK, true);
Initialize();
// Cast Defend spells to max stack size
diff --git a/src/server/scripts/Outland/TempestKeep/Eye/boss_kaelthas.cpp b/src/server/scripts/Outland/TempestKeep/Eye/boss_kaelthas.cpp
index e5812390bd2..be27932e6b4 100644
--- a/src/server/scripts/Outland/TempestKeep/Eye/boss_kaelthas.cpp
+++ b/src/server/scripts/Outland/TempestKeep/Eye/boss_kaelthas.cpp
@@ -144,6 +144,7 @@ enum Spells
// Thaladred the Darkener spells
SPELL_PSYCHIC_BLOW = 10689,
SPELL_SILENCE = 30225,
+ SPELL_REND = 36965,
// Lord Sanguinar spells
SPELL_BELLOWING_ROAR = 40636,
// Grand Astromancer Capernian spells
@@ -881,11 +882,13 @@ class boss_thaladred_the_darkener : public CreatureScript
{
Gaze_Timer = 100;
Silence_Timer = 20000;
+ Rend_Timer = 4000;
PsychicBlow_Timer = 10000;
}
uint32 Gaze_Timer;
uint32 Silence_Timer;
+ uint32 Rend_Timer;
uint32 PsychicBlow_Timer;
void Reset() override
@@ -939,6 +942,15 @@ class boss_thaladred_the_darkener : public CreatureScript
else
Silence_Timer -= diff;
+ //Rend_Timer
+ if (Rend_Timer <= diff)
+ {
+ DoCastVictim(SPELL_REND);
+ Rend_Timer = 4000;
+ }
+ else
+ Rend_Timer -= diff;
+
//PsychicBlow_Timer
if (PsychicBlow_Timer <= diff)
{
diff --git a/src/server/scripts/Outland/zone_zangarmarsh.cpp b/src/server/scripts/Outland/zone_zangarmarsh.cpp
index 48bbb7bad4a..6f38cce0e5b 100644
--- a/src/server/scripts/Outland/zone_zangarmarsh.cpp
+++ b/src/server/scripts/Outland/zone_zangarmarsh.cpp
@@ -48,8 +48,6 @@ EndContentData */
enum AshyenAndKeleth
{
- GOSSIP_REWARD_BLESS = 0,
-
NPC_ASHYEN = 17900,
NPC_KELETH = 17901,
@@ -117,7 +115,6 @@ public:
if (spell)
{
creature->CastSpell(player, spell, true);
- creature->AI()->Talk(GOSSIP_REWARD_BLESS);
}
}
@@ -145,7 +142,6 @@ public:
if (spell)
{
creature->CastSpell(player, spell, true);
- creature->AI()->Talk(GOSSIP_REWARD_BLESS);
}
}
player->CLOSE_GOSSIP_MENU();
diff --git a/src/server/scripts/Pet/pet_mage.cpp b/src/server/scripts/Pet/pet_mage.cpp
index fee47aa1fa2..37584bda2ae 100644
--- a/src/server/scripts/Pet/pet_mage.cpp
+++ b/src/server/scripts/Pet/pet_mage.cpp
@@ -178,6 +178,9 @@ class npc_pet_mage_mirror_image : public CreatureScript
void UpdateAI(uint32 diff) override
{
Unit* owner = me->GetCharmerOrOwner();
+ if (!owner)
+ return;
+
Unit* target = owner->getAttackerForHelper();
events.Update(diff);
@@ -192,9 +195,6 @@ class npc_pet_mage_mirror_image : public CreatureScript
if (me->HasUnitState(UNIT_STATE_CASTING))
return;
- if (!owner)
- return;
-
// assign target if image doesnt have any or the target is not actual
if (!target || me->GetVictim() != target)
{
diff --git a/src/server/scripts/Spells/spell_druid.cpp b/src/server/scripts/Spells/spell_druid.cpp
index 7b853c36075..c088ae07e66 100644
--- a/src/server/scripts/Spells/spell_druid.cpp
+++ b/src/server/scripts/Spells/spell_druid.cpp
@@ -991,6 +991,37 @@ class spell_dru_swift_flight_passive : public SpellScriptLoader
}
};
+// -33943 - Flight Form
+class spell_dru_flight_form : public SpellScriptLoader
+{
+ public:
+ spell_dru_flight_form() : SpellScriptLoader("spell_dru_flight_form") { }
+
+ class spell_dru_flight_form_SpellScript : public SpellScript
+ {
+ PrepareSpellScript(spell_dru_flight_form_SpellScript);
+
+ SpellCastResult CheckCast()
+ {
+ Unit* caster = GetCaster();
+ if (caster->IsInDisallowedMountForm())
+ return SPELL_FAILED_NOT_SHAPESHIFT;
+
+ return SPELL_CAST_OK;
+ }
+
+ void Register() override
+ {
+ OnCheckCast += SpellCheckCastFn(spell_dru_flight_form_SpellScript::CheckCast);
+ }
+ };
+
+ SpellScript* GetSpellScript() const override
+ {
+ return new spell_dru_flight_form_SpellScript();
+ }
+};
+
// -5217 - Tiger's Fury
class spell_dru_tiger_s_fury : public SpellScriptLoader
{
@@ -1196,6 +1227,7 @@ void AddSC_druid_spell_scripts()
new spell_dru_starfall_dummy();
new spell_dru_survival_instincts();
new spell_dru_swift_flight_passive();
+ new spell_dru_flight_form();
new spell_dru_tiger_s_fury();
new spell_dru_typhoon();
new spell_dru_t10_restoration_4p_bonus();
diff --git a/src/server/scripts/Spells/spell_generic.cpp b/src/server/scripts/Spells/spell_generic.cpp
index 5c3ee1d7f4a..abde43ef952 100644
--- a/src/server/scripts/Spells/spell_generic.cpp
+++ b/src/server/scripts/Spells/spell_generic.cpp
@@ -1930,10 +1930,7 @@ class spell_gen_mount : public SpellScriptLoader
if (map == 530 || (map == 571 && target->HasSpell(SPELL_COLD_WEATHER_FLYING)))
canFly = true;
- float x, y, z;
- target->GetPosition(x, y, z);
- uint32 areaFlag = target->GetBaseMap()->GetAreaFlag(x, y, z);
- AreaTableEntry const* area = sAreaStore.LookupEntry(areaFlag);
+ AreaTableEntry const* area = sAreaTableStore.LookupEntry(target->GetAreaId());
if (!area || (canFly && (area->flags & AREA_FLAG_NO_FLY_ZONE)))
canFly = false;
diff --git a/src/server/scripts/Spells/spell_item.cpp b/src/server/scripts/Spells/spell_item.cpp
index 9475da91a77..0abff255e4b 100644
--- a/src/server/scripts/Spells/spell_item.cpp
+++ b/src/server/scripts/Spells/spell_item.cpp
@@ -29,6 +29,7 @@
#include "SpellAuraEffects.h"
#include "SkillDiscovery.h"
#include "Battleground.h"
+#include "DBCStores.h"
// Generic script for handling item dummy effects which trigger another spell.
class spell_item_trigger_spell : public SpellScriptLoader
@@ -2630,6 +2631,43 @@ public:
}
};
+class spell_item_toy_train_set_pulse : public SpellScriptLoader
+{
+public:
+ spell_item_toy_train_set_pulse() : SpellScriptLoader("spell_item_toy_train_set_pulse") { }
+
+ class spell_item_toy_train_set_pulse_SpellScript : public SpellScript
+ {
+ PrepareSpellScript(spell_item_toy_train_set_pulse_SpellScript);
+
+ void HandleDummy(SpellEffIndex /*index*/)
+ {
+ if (Player* target = GetHitUnit()->ToPlayer())
+ {
+ target->HandleEmoteCommand(EMOTE_ONESHOT_TRAIN);
+ if (EmotesTextSoundEntry const* soundEntry = FindTextSoundEmoteFor(TEXT_EMOTE_TRAIN, target->getRace(), target->getGender()))
+ target->PlayDistanceSound(soundEntry->SoundId);
+ }
+ }
+
+ void HandleTargets(std::list<WorldObject*>& targetList)
+ {
+ targetList.remove_if([](WorldObject const* obj) { return obj->GetTypeId() != TYPEID_PLAYER; });
+ }
+
+ void Register() override
+ {
+ OnEffectHitTarget += SpellEffectFn(spell_item_toy_train_set_pulse_SpellScript::HandleDummy, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
+ OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_item_toy_train_set_pulse_SpellScript::HandleTargets, EFFECT_ALL, TARGET_UNIT_SRC_AREA_ALLY);
+ }
+ };
+
+ SpellScript* GetSpellScript() const override
+ {
+ return new spell_item_toy_train_set_pulse_SpellScript();
+ }
+};
+
void AddSC_item_spell_scripts()
{
// 23074 Arcanite Dragonling
@@ -2698,4 +2736,5 @@ void AddSC_item_spell_scripts()
new spell_item_chicken_cover();
new spell_item_muisek_vessel();
new spell_item_greatmothers_soulcatcher();
+ new spell_item_toy_train_set_pulse();
}
diff --git a/src/server/scripts/Spells/spell_paladin.cpp b/src/server/scripts/Spells/spell_paladin.cpp
index 9f6b4947328..8bd4b3eb070 100644
--- a/src/server/scripts/Spells/spell_paladin.cpp
+++ b/src/server/scripts/Spells/spell_paladin.cpp
@@ -43,6 +43,12 @@ enum PaladinSpells
SPELL_PALADIN_BLESSING_OF_LOWER_CITY_PRIEST = 37880,
SPELL_PALADIN_BLESSING_OF_LOWER_CITY_SHAMAN = 37881,
+ SPELL_PALADIN_BEACON_OF_LIGHT = 53563,
+ SPELL_PALADIN_BEACON_OF_LIGHT_HEAL_1 = 53652,
+ SPELL_PALADIN_BEACON_OF_LIGHT_HEAL_2 = 53653,
+ SPELL_PALADIN_BEACON_OF_LIGHT_HEAL_3 = 53654,
+ SPELL_PALADIN_HOLY_LIGHT = 635,
+
SPELL_PALADIN_DIVINE_STORM = 53385,
SPELL_PALADIN_DIVINE_STORM_DUMMY = 54171,
SPELL_PALADIN_DIVINE_STORM_HEAL = 54172,
@@ -1153,6 +1159,68 @@ class spell_pal_lay_on_hands : public SpellScriptLoader
}
};
+// 53651 - Light's Beacon - Beacon of Light
+class spell_pal_light_s_beacon : public SpellScriptLoader
+{
+ public:
+ spell_pal_light_s_beacon() : SpellScriptLoader("spell_pal_light_s_beacon") { }
+
+ class spell_pal_light_s_beacon_AuraScript : public AuraScript
+ {
+ PrepareAuraScript(spell_pal_light_s_beacon_AuraScript);
+
+ bool Validate(SpellInfo const* /*spellInfo*/) override
+ {
+ if (!sSpellMgr->GetSpellInfo(SPELL_PALADIN_BEACON_OF_LIGHT)
+ || !sSpellMgr->GetSpellInfo(SPELL_PALADIN_BEACON_OF_LIGHT_HEAL_1)
+ || !sSpellMgr->GetSpellInfo(SPELL_PALADIN_BEACON_OF_LIGHT_HEAL_2)
+ || !sSpellMgr->GetSpellInfo(SPELL_PALADIN_BEACON_OF_LIGHT_HEAL_3)
+ || !sSpellMgr->GetSpellInfo(SPELL_PALADIN_HOLY_LIGHT))
+ return false;
+ return true;
+ }
+
+ bool CheckProc(ProcEventInfo& eventInfo)
+ {
+ if (GetTarget()->HasAura(SPELL_PALADIN_BEACON_OF_LIGHT, eventInfo.GetActor()->GetGUID()))
+ return false;
+ return true;
+ }
+
+ void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo)
+ {
+ PreventDefaultAction();
+
+ SpellInfo const* procSpell = eventInfo.GetSpellInfo();
+ if (!procSpell)
+ return;
+
+ uint32 healSpellId = procSpell->IsRankOf(sSpellMgr->EnsureSpellInfo(SPELL_PALADIN_HOLY_LIGHT)) ? SPELL_PALADIN_BEACON_OF_LIGHT_HEAL_1 : SPELL_PALADIN_BEACON_OF_LIGHT_HEAL_3;
+ uint32 heal = CalculatePct(eventInfo.GetHealInfo()->GetHeal(), aurEff->GetAmount());
+
+ Unit* beaconTarget = GetCaster();
+ if (!beaconTarget || !beaconTarget->HasAura(SPELL_PALADIN_BEACON_OF_LIGHT, eventInfo.GetActor()->GetGUID()))
+ return;
+
+ /// @todo: caster must be the healed unit to perform distance checks correctly
+ /// but that will break animation on clientside
+ /// caster in spell packets must be the healing unit
+ eventInfo.GetActor()->CastCustomSpell(healSpellId, SPELLVALUE_BASE_POINT0, heal, beaconTarget, true);
+ }
+
+ void Register() override
+ {
+ DoCheckProc += AuraCheckProcFn(spell_pal_light_s_beacon_AuraScript::CheckProc);
+ OnEffectProc += AuraEffectProcFn(spell_pal_light_s_beacon_AuraScript::HandleProc, EFFECT_0, SPELL_AURA_DUMMY);
+ }
+ };
+
+ AuraScript* GetAuraScript() const override
+ {
+ return new spell_pal_light_s_beacon_AuraScript();
+ }
+};
+
// 31789 - Righteous Defense
class spell_pal_righteous_defense : public SpellScriptLoader
{
@@ -1338,6 +1406,7 @@ void AddSC_paladin_spell_scripts()
new spell_pal_judgement("spell_pal_judgement_of_wisdom", SPELL_PALADIN_JUDGEMENT_OF_WISDOM);
new spell_pal_judgement_of_command();
new spell_pal_lay_on_hands();
+ new spell_pal_light_s_beacon();
new spell_pal_righteous_defense();
new spell_pal_sacred_shield();
new spell_pal_seal_of_righteousness();
diff --git a/src/server/scripts/Spells/spell_rogue.cpp b/src/server/scripts/Spells/spell_rogue.cpp
index 3ca2db40d65..9b577d4e140 100644
--- a/src/server/scripts/Spells/spell_rogue.cpp
+++ b/src/server/scripts/Spells/spell_rogue.cpp
@@ -41,6 +41,9 @@ enum RogueSpells
SPELL_ROGUE_SHIV_TRIGGERED = 5940,
SPELL_ROGUE_TRICKS_OF_THE_TRADE_DMG_BOOST = 57933,
SPELL_ROGUE_TRICKS_OF_THE_TRADE_PROC = 59628,
+ SPELL_ROGUE_HONOR_AMONG_THIEVES = 51698,
+ SPELL_ROGUE_HONOR_AMONG_THIEVES_PROC = 52916,
+ SPELL_ROGUE_HONOR_AMONG_THIEVES_2 = 51699
};
// 13877, 33735, (check 51211, 65956) - Blade Flurry
@@ -703,6 +706,143 @@ class spell_rog_tricks_of_the_trade_proc : public SpellScriptLoader
}
};
+// 51698,51700,51701 - Honor Among Thieves
+class spell_rog_honor_among_thieves : public SpellScriptLoader
+{
+public:
+ spell_rog_honor_among_thieves() : SpellScriptLoader("spell_rog_honor_among_thieves") { }
+
+ class spell_rog_honor_among_thieves_AuraScript : public AuraScript
+ {
+ PrepareAuraScript(spell_rog_honor_among_thieves_AuraScript);
+
+ bool CheckProc(ProcEventInfo& /*eventInfo*/)
+ {
+ Unit* caster = GetCaster();
+ if (!caster)
+ return false;
+
+ if (!caster->GetSpellHistory()->HasCooldown(GetSpellInfo()->Effects[EFFECT_0].TriggerSpell))
+ return true;
+
+ return false;
+ }
+
+ void HandleProc(AuraEffect const* aurEff, ProcEventInfo& /*eventInfo*/)
+ {
+ PreventDefaultAction();
+
+ Unit* caster = GetCaster();
+ if (!caster)
+ return;
+
+ Unit* target = GetTarget();
+ target->CastSpell(target, GetSpellInfo()->Effects[EFFECT_0].TriggerSpell, TriggerCastFlags(TRIGGERED_FULL_MASK & ~TRIGGERED_IGNORE_SPELL_AND_CATEGORY_CD), nullptr, aurEff, caster->GetGUID());
+ }
+
+ void Register() override
+ {
+ DoCheckProc += AuraCheckProcFn(spell_rog_honor_among_thieves_AuraScript::CheckProc);
+ OnEffectProc += AuraEffectProcFn(spell_rog_honor_among_thieves_AuraScript::HandleProc, EFFECT_0, SPELL_AURA_PROC_TRIGGER_SPELL);
+ }
+ };
+
+ AuraScript* GetAuraScript() const override
+ {
+ return new spell_rog_honor_among_thieves_AuraScript();
+ }
+};
+
+// 52916 - Honor Among Thieves (Proc)
+class spell_rog_honor_among_thieves_proc : public SpellScriptLoader
+{
+public:
+ spell_rog_honor_among_thieves_proc() : SpellScriptLoader("spell_rog_honor_among_thieves_proc") { }
+
+ class spell_rog_honor_among_thieves_proc_SpellScript : public SpellScript
+ {
+ PrepareSpellScript(spell_rog_honor_among_thieves_proc_SpellScript);
+
+ bool Validate(SpellInfo const* /*spellInfo*/) override
+ {
+ if (!sSpellMgr->GetSpellInfo(SPELL_ROGUE_HONOR_AMONG_THIEVES_PROC))
+ return false;
+
+ return true;
+ }
+
+ void FilterTargets(std::list<WorldObject*>& targets)
+ {
+ targets.clear();
+
+ Unit* target = GetOriginalCaster();
+ if (!target)
+ return;
+
+ targets.push_back(target);
+ }
+
+ void HandleBeforeHit()
+ {
+ Unit* target = GetHitUnit();
+ if (!target)
+ return;
+
+ /*
+ * The applied aura has a duration of 8 seconds
+ * This prevents new applications while its active
+ * Removing it on each new proc enables the application from different sources (different grouped players)
+ * and on new procs after the source cooldown is finished (1 second)
+ */
+ if (target->HasAura(GetSpellInfo()->Id))
+ target->RemoveAura(GetSpellInfo()->Id);
+ }
+
+ void TriggerCooldown()
+ {
+ Unit* target = GetHitUnit();
+ if (!target)
+ return;
+
+ target->GetSpellHistory()->AddCooldown(GetSpellInfo()->Id, 0, std::chrono::seconds(1));
+ }
+
+ void Register() override
+ {
+ OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_rog_honor_among_thieves_proc_SpellScript::FilterTargets, EFFECT_0, TARGET_UNIT_CASTER_AREA_PARTY);
+ BeforeHit += SpellHitFn(spell_rog_honor_among_thieves_proc_SpellScript::HandleBeforeHit);
+ AfterHit += SpellHitFn(spell_rog_honor_among_thieves_proc_SpellScript::TriggerCooldown);
+ }
+ };
+
+ SpellScript* GetSpellScript() const override
+ {
+ return new spell_rog_honor_among_thieves_proc_SpellScript();
+ }
+
+ class spell_rog_honor_among_thieves_proc_AuraScript : public AuraScript
+ {
+ PrepareAuraScript(spell_rog_honor_among_thieves_proc_AuraScript);
+
+ void HandleEffectApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
+ {
+ if (Player* player = GetTarget()->ToPlayer())
+ if (Unit* spellTarget = ObjectAccessor::GetUnit(*player, player->GetTarget()))
+ player->CastSpell(spellTarget, SPELL_ROGUE_HONOR_AMONG_THIEVES_2, true);
+ }
+
+ void Register() override
+ {
+ AfterEffectApply += AuraEffectApplyFn(spell_rog_honor_among_thieves_proc_AuraScript::HandleEffectApply, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
+ }
+ };
+
+ AuraScript* GetAuraScript() const override
+ {
+ return new spell_rog_honor_among_thieves_proc_AuraScript();
+ }
+};
+
void AddSC_rogue_spell_scripts()
{
new spell_rog_blade_flurry();
@@ -716,4 +856,6 @@ void AddSC_rogue_spell_scripts()
new spell_rog_shiv();
new spell_rog_tricks_of_the_trade();
new spell_rog_tricks_of_the_trade_proc();
+ new spell_rog_honor_among_thieves();
+ new spell_rog_honor_among_thieves_proc();
}
diff --git a/src/server/scripts/Spells/spell_shaman.cpp b/src/server/scripts/Spells/spell_shaman.cpp
index c8b0264995b..41e72b1388b 100644
--- a/src/server/scripts/Spells/spell_shaman.cpp
+++ b/src/server/scripts/Spells/spell_shaman.cpp
@@ -55,7 +55,8 @@ enum ShamanSpells
SPELL_SHAMAN_TOTEM_EARTHBIND_EARTHGRAB = 64695,
SPELL_SHAMAN_TOTEM_EARTHBIND_TOTEM = 6474,
SPELL_SHAMAN_TOTEM_EARTHEN_POWER = 59566,
- SPELL_SHAMAN_TOTEM_HEALING_STREAM_HEAL = 52042
+ SPELL_SHAMAN_TOTEM_HEALING_STREAM_HEAL = 52042,
+ SPELL_SHAMAN_TOTEMIC_MASTERY = 38437
};
enum ShamanSpellIcons
@@ -1025,6 +1026,46 @@ class spell_sha_thunderstorm : public SpellScriptLoader
}
};
+// 38443 - Totemic Mastery (Tier 6 - 2P)
+class spell_sha_totemic_mastery : public SpellScriptLoader
+{
+public:
+ spell_sha_totemic_mastery() : SpellScriptLoader("spell_sha_totemic_mastery") { }
+
+ class spell_sha_totemic_mastery_AuraScript : public AuraScript
+ {
+ PrepareAuraScript(spell_sha_totemic_mastery_AuraScript);
+
+ bool Validate(SpellInfo const* /*spellInfo*/) override
+ {
+ if (!sSpellMgr->GetSpellInfo(SPELL_SHAMAN_TOTEMIC_MASTERY))
+ return false;
+ return true;
+ }
+
+ void HandleDummy(AuraEffect const* /*aurEff*/)
+ {
+ Unit* target = GetTarget();
+ for (uint8 i = SUMMON_SLOT_TOTEM; i < MAX_TOTEM_SLOT; ++i)
+ if (!target->m_SummonSlot[i])
+ return;
+
+ target->CastSpell(target, SPELL_SHAMAN_TOTEMIC_MASTERY, true);
+ PreventDefaultAction();
+ }
+
+ void Register() override
+ {
+ OnEffectPeriodic += AuraEffectPeriodicFn(spell_sha_totemic_mastery_AuraScript::HandleDummy, EFFECT_0, SPELL_AURA_PERIODIC_TRIGGER_SPELL);
+ }
+ };
+
+ AuraScript* GetAuraScript() const override
+ {
+ return new spell_sha_totemic_mastery_AuraScript();
+ }
+};
+
void AddSC_shaman_spell_scripts()
{
new spell_sha_ancestral_awakening_proc();
@@ -1048,4 +1089,5 @@ void AddSC_shaman_spell_scripts()
new spell_sha_mana_tide_totem();
new spell_sha_sentry_totem();
new spell_sha_thunderstorm();
+ new spell_sha_totemic_mastery();
}
diff --git a/src/server/scripts/World/duel_reset.cpp b/src/server/scripts/World/duel_reset.cpp
index c5b53368bc8..3c46255a1bf 100644
--- a/src/server/scripts/World/duel_reset.cpp
+++ b/src/server/scripts/World/duel_reset.cpp
@@ -34,9 +34,8 @@ class DuelResetScript : public PlayerScript
player1->GetSpellHistory()->SaveCooldownStateBeforeDuel();
player2->GetSpellHistory()->SaveCooldownStateBeforeDuel();
-
- ResetSpellCooldowns(player1);
- ResetSpellCooldowns(player2);
+ ResetSpellCooldowns(player1, true);
+ ResetSpellCooldowns(player2, true);
}
// Health and mana reset
@@ -73,9 +72,8 @@ class DuelResetScript : public PlayerScript
// Cooldown restore
if (sWorld->getBoolConfig(CONFIG_RESET_DUEL_COOLDOWNS))
{
-
- ResetSpellCooldowns(winner);
- ResetSpellCooldowns(loser);
+ ResetSpellCooldowns(winner, false);
+ ResetSpellCooldowns(loser, false);
winner->GetSpellHistory()->RestoreCooldownStateAfterDuel();
loser->GetSpellHistory()->RestoreCooldownStateAfterDuel();
@@ -98,14 +96,35 @@ class DuelResetScript : public PlayerScript
}
}
- static void ResetSpellCooldowns(Player* player)
+ static void ResetSpellCooldowns(Player* player, bool onStartDuel)
{
- // remove cooldowns on spells that have < 10 min CD and has no onHold
- player->GetSpellHistory()->ResetCooldowns([](SpellHistory::CooldownStorageType::iterator itr) -> bool
+ if (onStartDuel)
{
- SpellInfo const* spellInfo = sSpellMgr->EnsureSpellInfo(itr->first);
- return spellInfo->RecoveryTime < 10 * MINUTE * IN_MILLISECONDS && spellInfo->CategoryRecoveryTime < 10 * MINUTE * IN_MILLISECONDS && !itr->second.OnHold;
- }, true);
+ // remove cooldowns on spells that have < 10 min CD > 30 sec and has no onHold
+ player->GetSpellHistory()->ResetCooldowns([](SpellHistory::CooldownStorageType::iterator itr) -> bool
+ {
+ SpellHistory::Clock::time_point now = SpellHistory::Clock::now();
+ uint32 cooldownDuration = itr->second.CooldownEnd > now ? std::chrono::duration_cast<std::chrono::milliseconds>(itr->second.CooldownEnd - now).count() : 0;
+ SpellInfo const* spellInfo = sSpellMgr->EnsureSpellInfo(itr->first);
+ return spellInfo->RecoveryTime < 10 * MINUTE * IN_MILLISECONDS
+ && spellInfo->CategoryRecoveryTime < 10 * MINUTE * IN_MILLISECONDS
+ && !itr->second.OnHold
+ && cooldownDuration > 0
+ && ( spellInfo->RecoveryTime - cooldownDuration ) > (MINUTE / 2) * IN_MILLISECONDS
+ && ( spellInfo->CategoryRecoveryTime - cooldownDuration ) > (MINUTE / 2) * IN_MILLISECONDS;
+ }, true);
+ }
+ else
+ {
+ // 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())
diff --git a/src/server/scripts/World/go_scripts.cpp b/src/server/scripts/World/go_scripts.cpp
index ef4a2b0e32f..b90839f50c5 100644
--- a/src/server/scripts/World/go_scripts.cpp
+++ b/src/server/scripts/World/go_scripts.cpp
@@ -41,6 +41,7 @@ go_tadpole_cage
go_amberpine_outhouse
go_hive_pod
go_veil_skith_cage
+go_toy_train_set
EndContentData */
#include "ScriptMgr.h"
@@ -1196,6 +1197,48 @@ public:
}
};
+
+enum ToyTrainSpells
+{
+ SPELL_TOY_TRAIN_PULSE = 61551,
+};
+
+class go_toy_train_set : public GameObjectScript
+{
+ public:
+ go_toy_train_set() : GameObjectScript("go_toy_train_set") { }
+
+ struct go_toy_train_setAI : public GameObjectAI
+ {
+ go_toy_train_setAI(GameObject* go) : GameObjectAI(go), _pulseTimer(3 * IN_MILLISECONDS) { }
+
+ void UpdateAI(uint32 diff) override
+ {
+ if (diff < _pulseTimer)
+ _pulseTimer -= diff;
+ else
+ {
+ go->CastSpell(nullptr, SPELL_TOY_TRAIN_PULSE, true);
+ _pulseTimer = 6 * IN_MILLISECONDS;
+ }
+ }
+
+ // triggered on wrecker'd
+ void DoAction(int32 /*action*/) override
+ {
+ go->Delete();
+ }
+
+ private:
+ uint32 _pulseTimer;
+ };
+
+ GameObjectAI* GetAI(GameObject* go) const override
+ {
+ return new go_toy_train_setAI(go);
+ }
+};
+
void AddSC_go_scripts()
{
new go_cat_figurine();
@@ -1231,4 +1274,5 @@ void AddSC_go_scripts()
new go_veil_skith_cage();
new go_frostblade_shrine();
new go_midsummer_bonfire();
+ new go_toy_train_set();
}
diff --git a/src/server/scripts/World/npc_professions.cpp b/src/server/scripts/World/npc_professions.cpp
index 4dca55d562d..867ebafe32b 100644
--- a/src/server/scripts/World/npc_professions.cpp
+++ b/src/server/scripts/World/npc_professions.cpp
@@ -18,27 +18,26 @@
/* ScriptData
SDName: Npc_Professions
-SD%Complete: 80
-SDComment: Provides learn/unlearn/relearn-options for professions. Not supported: Unlearn engineering, re-learn engineering, re-learn leatherworking.
-SDCategory: NPCs
+SD%Complete: 100
+SDComment: Provides learn/unlearn/relearn-options for professions.
+SDCategory: NPCs/GOBs
EndScriptData */
#include "ScriptMgr.h"
#include "ScriptedCreature.h"
#include "ScriptedGossip.h"
+#include "GameObjectAI.h"
#include "Player.h"
#include "SpellInfo.h"
#include "WorldSession.h"
/*
A few notes for future developement:
-- A full implementation of gossip for GO's is required. They must have the same scripting capabilities as creatures. Basically,
-there is no difference here (except that default text is chosen with `gameobject_template`.`data3` (for GO type2, different dataN for a few others)
- It's possible blacksmithing still require some tweaks and adjustments due to the way we _have_ to use reputation.
*/
/*###
-# to be removed from here (->ncp_text). This is data for database projects.
+# to be removed from here (->npc_text). This is data for database projects.
###*/
#define TALK_MUST_UNLEARN_WEAPON "You must forget your weapon type specialty before I can help you. Go to Everlook in Winterspring and seek help there."
@@ -153,6 +152,9 @@ enum ProfessionSpells
S_LEARN_GOBLIN = 20221,
S_LEARN_GNOMISH = 20220,
+ S_UNLEARN_GOBLIN = 68334,
+ S_UNLEARN_GNOMISH = 68333,
+
S_SPELLFIRE = 26797,
S_MOONCLOTH = 26798,
S_SHADOWEAVE = 26801,
@@ -376,6 +378,27 @@ void ProfessionUnlearnSpells(Player* player, uint32 type)
player->RemoveSpell(36075); // Wildfeather Leggings
player->RemoveSpell(36078); // Living Crystal Breastplate
break;
+ case S_UNLEARN_GOBLIN: // S_UNLEARN_GOBLIN
+ player->RemoveSpell(30565); // Foreman's Enchanted Helmet
+ player->RemoveSpell(30566); // Foreman's Reinforced Helmet
+ player->RemoveSpell(30563); // Goblin Rocket Launcher
+ player->RemoveSpell(56514); // Global Thermal Sapper Charge
+ player->RemoveSpell(36954); // Dimensional Ripper - Area 52
+ player->RemoveSpell(23486); // Dimensional Ripper - Everlook
+ player->RemoveSpell(23078); // Goblin Jumper Cables XL
+ player->RemoveSpell(72952); // Shatter Rounds
+ break;
+ case S_UNLEARN_GNOMISH: // S_UNLEARN_GNOMISH
+ player->RemoveSpell(30575); // Gnomish Battle Goggles
+ player->RemoveSpell(30574); // Gnomish Power Goggles
+ player->RemoveSpell(56473); // Gnomish X-Ray Specs
+ player->RemoveSpell(30569); // Gnomish Poultryizer
+ player->RemoveSpell(30563); // Ultrasafe Transporter - Toshley's Station
+ player->RemoveSpell(23489); // Ultrasafe Transporter - Gadgetzan
+ player->RemoveSpell(23129); // World Enlarger
+ player->RemoveSpell(23096); // Gnomish Alarm-o-Bot
+ player->RemoveSpell(72953); // Iceblade Arrow
+ break;
case S_UNLEARN_SPELLFIRE: // S_UNLEARN_SPELLFIRE
player->RemoveSpell(26752); // Spellfire Belt
player->RemoveSpell(26753); // Spellfire Gloves
@@ -923,6 +946,76 @@ public:
}
};
+// Object ID - 177226
+// Book "Soothsaying for dummies"
+enum SoothsayingForDummies
+{
+ GOSSIP_ID = 7058,
+
+ // Engineering
+ OPTION_UNLEARN_GNOMISH = 0,
+ OPTION_UNLEARN_GOBLIN = 1,
+ OPTION_LEARN_GNOMISH = 2,
+ OPTION_LEARN_GOBLIN = 3,
+
+ // Leatherworking
+ OPTION_LEARN_DRAGONSCALE = 4,
+ OPTION_LEARN_ELEMENTAL = 5,
+ OPTION_LEARN_TRIBAL = 6
+};
+
+class go_soothsaying_for_dummies : public GameObjectScript
+{
+ public:
+ go_soothsaying_for_dummies() : GameObjectScript("go_soothsaying_for_dummies") { }
+
+ struct go_soothsaying_for_dummiesAI : public GameObjectAI
+ {
+ go_soothsaying_for_dummiesAI(GameObject* go) : GameObjectAI(go) { }
+
+ bool GossipSelect(Player* player, uint32 menuId, uint32 gossipListId) override
+ {
+ if (menuId != GOSSIP_ID)
+ return false;
+
+ switch (gossipListId)
+ {
+ case OPTION_UNLEARN_GNOMISH:
+ ProcessUnlearnAction(player, nullptr, S_UNLEARN_GNOMISH, 0, 0); // cost is handled by gossip code
+ break;
+ case OPTION_UNLEARN_GOBLIN:
+ ProcessUnlearnAction(player, nullptr, S_UNLEARN_GOBLIN, 0, 0);
+ break;
+ case OPTION_LEARN_GNOMISH:
+ player->CastSpell(player, S_LEARN_GNOMISH, true);
+ break;
+ case OPTION_LEARN_GOBLIN:
+ player->CastSpell(player, S_LEARN_GOBLIN, true);
+ break;
+ case OPTION_LEARN_DRAGONSCALE:
+ player->CastSpell(player, S_LEARN_DRAGON, true);
+ break;
+ case OPTION_LEARN_ELEMENTAL:
+ player->CastSpell(player, S_LEARN_ELEMENTAL, true);
+ break;
+ case OPTION_LEARN_TRIBAL:
+ player->CastSpell(player, S_LEARN_TRIBAL, true);
+ break;
+ default:
+ return false;
+ }
+
+ player->CLOSE_GOSSIP_MENU();
+ return true; // prevent further processing
+ }
+ };
+
+ GameObjectAI* GetAI(GameObject* go) const override
+ {
+ return new go_soothsaying_for_dummiesAI(go);
+ }
+};
+
/*###
# start menues leatherworking
###*/
@@ -1212,6 +1305,7 @@ void AddSC_npc_professions()
new npc_prof_alchemy();
new npc_prof_blacksmith();
new npc_engineering_tele_trinket();
+ new go_soothsaying_for_dummies();
new npc_prof_leather();
new npc_prof_tailor();
}
diff --git a/src/server/scripts/World/npcs_special.cpp b/src/server/scripts/World/npcs_special.cpp
index 16b95e555bb..80b4fac4333 100644
--- a/src/server/scripts/World/npcs_special.cpp
+++ b/src/server/scripts/World/npcs_special.cpp
@@ -38,6 +38,8 @@ npc_snake_trap_serpents 80% AI for snakes that summoned by Snake Trap
npc_shadowfiend 100% restore 5% of owner's mana when shadowfiend die from damage
npc_locksmith 75% list of keys needs to be confirmed
npc_firework 100% NPC's summoned by rockets and rocket clusters, for making them cast visual
+npc_train_wrecker 100% Wind-Up Train Wrecker that kills train set
+npc_egbert 100% Egbert run's around
EndContentData */
#include "ScriptMgr.h"
@@ -56,6 +58,7 @@ EndContentData */
#include "SpellHistory.h"
#include "SpellAuras.h"
#include "Pet.h"
+#include "PetAI.h"
#include "CreatureTextMgr.h"
#include "SmartAI.h"
@@ -519,6 +522,67 @@ public:
};
/*######
+## npc_torch_tossing_target_bunny_controller
+######*/
+
+enum TorchTossingTarget
+{
+ NPC_TORCH_TOSSING_TARGET_BUNNY = 25535,
+ SPELL_TARGET_INDICATOR = 45723
+};
+
+class npc_torch_tossing_target_bunny_controller : public CreatureScript
+{
+public:
+ npc_torch_tossing_target_bunny_controller() : CreatureScript("npc_torch_tossing_target_bunny_controller") { }
+
+ struct npc_torch_tossing_target_bunny_controllerAI : public ScriptedAI
+ {
+ npc_torch_tossing_target_bunny_controllerAI(Creature* creature) : ScriptedAI(creature)
+ {
+ _targetTimer = 3000;
+ }
+
+ ObjectGuid DoSearchForTargets(ObjectGuid lastTargetGUID)
+ {
+ std::list<Creature*> targets;
+ me->GetCreatureListWithEntryInGrid(targets, NPC_TORCH_TOSSING_TARGET_BUNNY, 60.0f);
+ targets.remove_if([lastTargetGUID](Creature* creature) { return creature->GetGUID() == lastTargetGUID; });
+
+ if (!targets.empty())
+ {
+ _lastTargetGUID = Trinity::Containers::SelectRandomContainerElement(targets)->GetGUID();
+
+ return _lastTargetGUID;
+ }
+ return ObjectGuid::Empty;
+ }
+
+ void UpdateAI(uint32 diff) override
+ {
+ if (_targetTimer < diff)
+ {
+ if (Unit* target = ObjectAccessor::GetUnit(*me, DoSearchForTargets(_lastTargetGUID)))
+ target->CastSpell(target, SPELL_TARGET_INDICATOR, true);
+
+ _targetTimer = 3000;
+ }
+ else
+ _targetTimer -= diff;
+ }
+
+ private:
+ uint32 _targetTimer;
+ ObjectGuid _lastTargetGUID;
+ };
+
+ CreatureAI* GetAI(Creature* creature) const override
+ {
+ return new npc_torch_tossing_target_bunny_controllerAI(creature);
+ }
+};
+
+/*######
## Triage quest
######*/
@@ -1422,7 +1486,6 @@ public:
void Reset() override
{
me->SetControlled(true, UNIT_STATE_STUNNED);//disable rotate
- me->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_KNOCK_BACK, true);//imune to knock aways like blast wave
_events.Reset();
_damageTimes.clear();
@@ -2130,7 +2193,7 @@ public:
// Check if we are near Elune'ara lake south, if so try to summon Omen or a minion
if (me->GetZoneId() == ZONE_MOONGLADE)
{
- if (!me->FindNearestCreature(NPC_OMEN, 100.0f, false) && me->GetDistance2d(omenSummonPos.GetPositionX(), omenSummonPos.GetPositionY()) <= 100.0f)
+ if (!me->FindNearestCreature(NPC_OMEN, 100.0f) && me->GetDistance2d(omenSummonPos.GetPositionX(), omenSummonPos.GetPositionY()) <= 100.0f)
{
switch (urand(0, 9))
{
@@ -2387,12 +2450,300 @@ class npc_stable_master : public CreatureScript
}
};
+enum TrainWrecker
+{
+ GO_TOY_TRAIN = 193963,
+ SPELL_TOY_TRAIN_PULSE = 61551,
+ SPELL_WRECK_TRAIN = 62943,
+ ACTION_WRECKED = 1,
+ EVENT_DO_JUMP = 1,
+ EVENT_DO_FACING = 2,
+ EVENT_DO_WRECK = 3,
+ EVENT_DO_DANCE = 4,
+ MOVEID_CHASE = 1,
+ MOVEID_JUMP = 2
+};
+class npc_train_wrecker : public CreatureScript
+{
+ public:
+ npc_train_wrecker() : CreatureScript("npc_train_wrecker") { }
+
+ struct npc_train_wreckerAI : public NullCreatureAI
+ {
+ npc_train_wreckerAI(Creature* creature) : NullCreatureAI(creature), _isSearching(true), _nextAction(0), _timer(1 * IN_MILLISECONDS) { }
+
+ GameObject* VerifyTarget() const
+ {
+ if (GameObject* target = ObjectAccessor::GetGameObject(*me, _target))
+ return target;
+ me->HandleEmoteCommand(EMOTE_ONESHOT_RUDE);
+ me->DespawnOrUnsummon(3 * IN_MILLISECONDS);
+ return nullptr;
+ }
+
+ void UpdateAI(uint32 diff) override
+ {
+ if (_isSearching)
+ {
+ if (diff < _timer)
+ _timer -= diff;
+ else
+ {
+ if (GameObject* target = me->FindNearestGameObject(GO_TOY_TRAIN, 15.0f))
+ {
+ _isSearching = false;
+ _target = target->GetGUID();
+ me->SetWalk(true);
+ me->GetMotionMaster()->MovePoint(MOVEID_CHASE, target->GetNearPosition(3.0f, target->GetAngle(me)));
+ }
+ else
+ _timer = 3 * IN_MILLISECONDS;
+ }
+ }
+ else
+ {
+ switch (_nextAction)
+ {
+ case EVENT_DO_JUMP:
+ if (GameObject* target = VerifyTarget())
+ me->GetMotionMaster()->MoveJump(*target, 5.0, 10.0, MOVEID_JUMP);
+ _nextAction = 0;
+ break;
+ case EVENT_DO_FACING:
+ if (GameObject* target = VerifyTarget())
+ {
+ me->SetFacingTo(target->GetOrientation());
+ me->HandleEmoteCommand(EMOTE_ONESHOT_ATTACK1H);
+ _timer = 1.5 * IN_MILLISECONDS;
+ _nextAction = EVENT_DO_WRECK;
+ }
+ else
+ _nextAction = 0;
+ break;
+ case EVENT_DO_WRECK:
+ if (diff < _timer)
+ {
+ _timer -= diff;
+ break;
+ }
+ if (GameObject* target = VerifyTarget())
+ {
+ me->CastSpell(target, SPELL_WRECK_TRAIN, false);
+ target->AI()->DoAction(ACTION_WRECKED);
+ _timer = 2 * IN_MILLISECONDS;
+ _nextAction = EVENT_DO_DANCE;
+ }
+ else
+ _nextAction = 0;
+ break;
+ case EVENT_DO_DANCE:
+ if (diff < _timer)
+ {
+ _timer -= diff;
+ break;
+ }
+ me->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_DANCE);
+ me->DespawnOrUnsummon(5 * IN_MILLISECONDS);
+ _nextAction = 0;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ void MovementInform(uint32 /*type*/, uint32 id) override
+ {
+ if (id == MOVEID_CHASE)
+ _nextAction = EVENT_DO_JUMP;
+ else if (id == MOVEID_JUMP)
+ _nextAction = EVENT_DO_FACING;
+ }
+
+ private:
+ bool _isSearching;
+ uint8 _nextAction;
+ uint32 _timer;
+ ObjectGuid _target;
+ };
+
+ CreatureAI* GetAI(Creature* creature) const override
+ {
+ return new npc_train_wreckerAI(creature);
+ }
+};
+
+enum EgbertMisc
+{
+ SPELL_EGBERT = 40669,
+ EVENT_RETURN = 3
+};
+
+class npc_egbert : public CreatureScript
+{
+public:
+ npc_egbert() : CreatureScript("npc_egbert") {}
+
+ struct npc_egbertAI : public NullCreatureAI
+ {
+ npc_egbertAI(Creature* creature) : NullCreatureAI(creature)
+ {
+ if (Unit* owner = me->GetCharmerOrOwner())
+ if (owner->GetMap()->GetEntry()->addon > 1)
+ me->SetCanFly(true);
+ }
+
+ void Reset() override
+ {
+ _events.Reset();
+ if (Unit* owner = me->GetCharmerOrOwner())
+ me->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, me->GetFollowAngle());
+ }
+
+ void EnterEvadeMode(EvadeReason why) override
+ {
+ if (!_EnterEvadeMode(why))
+ return;
+
+ Reset();
+ }
+
+ void UpdateAI(uint32 diff) override
+ {
+ _events.Update(diff);
+
+ if (Unit* owner = me->GetCharmerOrOwner())
+ {
+ if (!me->IsWithinDist(owner, 40.f))
+ {
+ me->RemoveAura(SPELL_EGBERT);
+ me->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, me->GetFollowAngle());
+ }
+ }
+
+ if (me->HasAura(SPELL_EGBERT))
+ _events.ScheduleEvent(EVENT_RETURN, urandms(5, 20));
+
+ while (uint32 eventId = _events.ExecuteEvent())
+ {
+ switch (eventId)
+ {
+ case EVENT_RETURN:
+ me->RemoveAura(SPELL_EGBERT);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ private:
+ EventMap _events;
+ };
+
+ CreatureAI* GetAI(Creature* creature) const
+ {
+ return new npc_egbertAI(creature);
+ }
+};
+
+enum PandarenMonkMisc
+{
+ SPELL_PANDAREN_MONK = 69800,
+ EVENT_FOCUS = 1,
+ EVENT_EMOTE = 2,
+ EVENT_FOLLOW = 3,
+ EVENT_DRINK = 4
+};
+
+class npc_pandaren_monk : public CreatureScript
+{
+public:
+ npc_pandaren_monk() : CreatureScript("npc_pandaren_monk") {}
+
+ struct npc_pandaren_monkAI : public NullCreatureAI
+ {
+ npc_pandaren_monkAI(Creature* creature) : NullCreatureAI(creature) { }
+
+ void Reset() override
+ {
+ _events.Reset();
+ _events.ScheduleEvent(EVENT_FOCUS, 1000);
+ }
+
+ void EnterEvadeMode(EvadeReason why) override
+ {
+ if (!_EnterEvadeMode(why))
+ return;
+
+ Reset();
+ }
+
+ void ReceiveEmote(Player* /*player*/, uint32 emote) override
+ {
+ me->InterruptSpell(CURRENT_CHANNELED_SPELL);
+ me->StopMoving();
+
+ switch (emote)
+ {
+ case TEXT_EMOTE_BOW:
+ _events.ScheduleEvent(EVENT_FOCUS, 1000);
+ break;
+ case TEXT_EMOTE_DRINK:
+ _events.ScheduleEvent(EVENT_DRINK, 1000);
+ break;
+ }
+ }
+
+ void UpdateAI(uint32 diff) override
+ {
+ _events.Update(diff);
+
+ if (Unit* owner = me->GetCharmerOrOwner())
+ if (!me->IsWithinDist(owner, 30.f))
+ me->InterruptSpell(CURRENT_CHANNELED_SPELL);
+
+ while (uint32 eventId = _events.ExecuteEvent())
+ {
+ switch (eventId)
+ {
+ case EVENT_FOCUS:
+ if (Unit* owner = me->GetCharmerOrOwner())
+ me->SetFacingToObject(owner);
+ _events.ScheduleEvent(EVENT_EMOTE, 1000);
+ break;
+ case EVENT_EMOTE:
+ me->HandleEmoteCommand(EMOTE_ONESHOT_BOW);
+ _events.ScheduleEvent(EVENT_FOLLOW, 1000);
+ break;
+ case EVENT_FOLLOW:
+ if (Unit* owner = me->GetCharmerOrOwner())
+ me->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE);
+ break;
+ case EVENT_DRINK:
+ me->CastSpell(me, SPELL_PANDAREN_MONK, false);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ private:
+ EventMap _events;
+ };
+
+ CreatureAI* GetAI(Creature* creature) const
+ {
+ return new npc_pandaren_monkAI(creature);
+ }
+};
+
void AddSC_npcs_special()
{
new npc_air_force_bots();
new npc_lunaclaw_spirit();
new npc_chicken_cluck();
new npc_dancing_flames();
+ new npc_torch_tossing_target_bunny_controller();
new npc_doctor();
new npc_injured_patient();
new npc_garments_of_quests();
@@ -2410,4 +2761,7 @@ void AddSC_npcs_special()
new npc_spring_rabbit();
new npc_imp_in_a_ball();
new npc_stable_master();
+ new npc_train_wrecker();
+ new npc_egbert();
+ new npc_pandaren_monk();
}
diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist
index 5b07dde22bb..217995cb88a 100644
--- a/src/server/worldserver/worldserver.conf.dist
+++ b/src/server/worldserver/worldserver.conf.dist
@@ -406,6 +406,24 @@ CleanCharacterDB = 0
PersistentCharacterCleanFlags = 0
#
+# Auction.GetAllScanDelay
+# Description: Sets the minimum time in seconds, a single player character can perform a getall scan.
+# The value is only held in memory so a server restart will clear it.
+# Setting this to zero, will disable GetAll functions completely.
+# Default: 900 - (GetAll scan limited to once every 15mins per player character)
+
+Auction.GetAllScanDelay = 900
+
+#
+# Auction.SearchDelay
+# Description: Sets the minimum time in milliseconds (seconds x 1000), that the client must wait between
+# auction search operations. This can be increased if somehow Auction House activity is causing
+# too much load.
+# Default: 300 - (Time delay between auction searches set to 0.3secs)
+
+Auction.SearchDelay = 300
+
+#
###################################################################################################
###################################################################################################
diff --git a/src/tools/map_extractor/CMakeLists.txt b/src/tools/map_extractor/CMakeLists.txt
index e86ef6aedcb..d0f3e42cef8 100644
--- a/src/tools/map_extractor/CMakeLists.txt
+++ b/src/tools/map_extractor/CMakeLists.txt
@@ -14,6 +14,7 @@ file(GLOB_RECURSE mapextractor_SRCS *.cpp *.h)
set(include_Dirs
${CMAKE_SOURCE_DIR}
${CMAKE_SOURCE_DIR}/dep/cppformat
+ ${CMAKE_SOURCE_DIR}/dep/g3dlite/include
${CMAKE_SOURCE_DIR}/dep/libmpq
${CMAKE_SOURCE_DIR}/src/common
${CMAKE_SOURCE_DIR}/src/common/Utilities
@@ -37,6 +38,7 @@ add_executable(mapextractor
target_link_libraries(mapextractor
common
format
+ g3dlib
mpq
${BZIP2_LIBRARIES}
${ZLIB_LIBRARIES}
diff --git a/src/tools/map_extractor/System.cpp b/src/tools/map_extractor/System.cpp
index 5d1c31ba2dc..f3a761fd437 100644
--- a/src/tools/map_extractor/System.cpp
+++ b/src/tools/map_extractor/System.cpp
@@ -31,6 +31,7 @@
#include "adt.h"
#include "wdt.h"
+#include <G3D/Plane.h>
#include <boost/filesystem.hpp>
extern ArchiveSet gOpenArchives;
@@ -42,12 +43,10 @@ typedef struct
} map_id;
map_id *map_ids;
-uint16 *areas;
uint16 *LiqType;
#define MAX_PATH_LENGTH 128
char output_path[MAX_PATH_LENGTH] = ".";
char input_path[MAX_PATH_LENGTH] = ".";
-uint32 maxAreaId = 0;
// **************************************************
// Extractor options
@@ -231,30 +230,6 @@ uint32 ReadMapDBC()
return map_count;
}
-void ReadAreaTableDBC()
-{
- printf("Read AreaTable.dbc file...");
- DBCFile dbc("DBFilesClient\\AreaTable.dbc");
-
- if(!dbc.open())
- {
- printf("Fatal error: Invalid AreaTable.dbc file format!\n");
- exit(1);
- }
-
- size_t area_count = dbc.getRecordCount();
- size_t maxid = dbc.getMaxId();
- areas = new uint16[maxid + 1];
- memset(areas, 0xff, (maxid + 1) * sizeof(uint16));
-
- for(uint32 x = 0; x < area_count; ++x)
- areas[dbc.getRecord(x).getUInt(0)] = dbc.getRecord(x).getUInt(3);
-
- maxAreaId = dbc.getMaxId();
-
- printf("Done! (" SZFMTD " areas loaded)\n", area_count);
-}
-
void ReadLiquidTypeTableDBC()
{
printf("Read LiquidType.dbc file...");
@@ -282,7 +257,7 @@ void ReadLiquidTypeTableDBC()
// Map file format data
static char const* MAP_MAGIC = "MAPS";
-static char const* MAP_VERSION_MAGIC = "v1.3";
+static char const* MAP_VERSION_MAGIC = "v1.8";
static char const* MAP_AREA_MAGIC = "AREA";
static char const* MAP_HEIGHT_MAGIC = "MHGT";
static char const* MAP_LIQUID_MAGIC = "MLIQ";
@@ -311,9 +286,10 @@ struct map_areaHeader
uint16 gridArea;
};
-#define MAP_HEIGHT_NO_HEIGHT 0x0001
-#define MAP_HEIGHT_AS_INT16 0x0002
-#define MAP_HEIGHT_AS_INT8 0x0004
+#define MAP_HEIGHT_NO_HEIGHT 0x0001
+#define MAP_HEIGHT_AS_INT16 0x0002
+#define MAP_HEIGHT_AS_INT8 0x0004
+#define MAP_HEIGHT_HAS_FLIGHT_BOUNDS 0x0008
struct map_heightHeader
{
@@ -358,7 +334,7 @@ float selectUInt16StepStore(float maxDiff)
return 65535 / maxDiff;
}
// Temporary grid data store
-uint16 area_flags[ADT_CELLS_PER_GRID][ADT_CELLS_PER_GRID];
+uint16 area_ids[ADT_CELLS_PER_GRID][ADT_CELLS_PER_GRID];
float V8[ADT_GRID_SIZE][ADT_GRID_SIZE];
float V9[ADT_GRID_SIZE+1][ADT_GRID_SIZE+1];
@@ -372,6 +348,9 @@ uint8 liquid_flags[ADT_CELLS_PER_GRID][ADT_CELLS_PER_GRID];
bool liquid_show[ADT_GRID_SIZE][ADT_GRID_SIZE];
float liquid_height[ADT_GRID_SIZE+1][ADT_GRID_SIZE+1];
+int16 flight_box_max[3][3];
+int16 flight_box_min[3][3];
+
bool ConvertADT(std::string const& inputPath, std::string const& outputPath, int /*cell_y*/, int /*cell_x*/, uint32 build)
{
ADT_file adt;
@@ -397,34 +376,20 @@ bool ConvertADT(std::string const& inputPath, std::string const& outputPath, int
map.buildMagic = build;
// Get area flags data
- for (int i=0;i<ADT_CELLS_PER_GRID;i++)
- {
- for(int j=0;j<ADT_CELLS_PER_GRID;j++)
- {
- adt_MCNK * cell = cells->getMCNK(i,j);
- uint32 areaid = cell->areaid;
- if(areaid && areaid <= maxAreaId)
- {
- if(areas[areaid] != 0xffff)
- {
- area_flags[i][j] = areas[areaid];
- continue;
- }
- printf("File: %s\nCan't find area flag for areaid %u [%d, %d].\n", inputPath.c_str(), areaid, cell->ix, cell->iy);
- }
- area_flags[i][j] = 0xffff;
- }
- }
+ for (int i = 0; i < ADT_CELLS_PER_GRID; i++)
+ for (int j = 0; j < ADT_CELLS_PER_GRID; j++)
+ area_ids[i][j] = cells->getMCNK(i, j)->areaid;
+
//============================================
// Try pack area data
//============================================
bool fullAreaData = false;
- uint32 areaflag = area_flags[0][0];
- for (int y=0;y<ADT_CELLS_PER_GRID;y++)
+ uint32 areaId = area_ids[0][0];
+ for (int y = 0; y < ADT_CELLS_PER_GRID; ++y)
{
- for(int x=0;x<ADT_CELLS_PER_GRID;x++)
+ for (int x = 0; x < ADT_CELLS_PER_GRID; ++x)
{
- if(area_flags[y][x]!=areaflag)
+ if (area_ids[y][x] != areaId)
{
fullAreaData = true;
break;
@@ -441,12 +406,12 @@ bool ConvertADT(std::string const& inputPath, std::string const& outputPath, int
if (fullAreaData)
{
areaHeader.gridArea = 0;
- map.areaMapSize+=sizeof(area_flags);
+ map.areaMapSize += sizeof(area_ids);
}
else
{
areaHeader.flags |= MAP_AREA_NO_AREA;
- areaHeader.gridArea = static_cast<uint16>(areaflag);
+ areaHeader.gridArea = static_cast<uint16>(areaId);
}
//
@@ -561,6 +526,14 @@ bool ConvertADT(std::string const& inputPath, std::string const& outputPath, int
maxHeight = CONF_use_minHeight;
}
+ bool hasFlightBox = false;
+ if (adt_MFBO* mfbo = adt.a_grid->getMFBO())
+ {
+ memcpy(flight_box_max, &mfbo->max, sizeof(flight_box_max));
+ memcpy(flight_box_min, &mfbo->min, sizeof(flight_box_min));
+ hasFlightBox = true;
+ }
+
map.heightMapOffset = map.areaMapOffset + map.areaMapSize;
map.heightMapSize = sizeof(map_heightHeader);
@@ -577,6 +550,12 @@ bool ConvertADT(std::string const& inputPath, std::string const& outputPath, int
if (CONF_allow_float_to_int && (maxHeight - minHeight) < CONF_flat_height_delta_limit)
heightHeader.flags |= MAP_HEIGHT_NO_HEIGHT;
+ if (hasFlightBox)
+ {
+ heightHeader.flags |= MAP_HEIGHT_HAS_FLIGHT_BOUNDS;
+ map.heightMapSize += sizeof(flight_box_max) + sizeof(flight_box_min);
+ }
+
// Try store as packed in uint16 or uint8 values
if (!(heightHeader.flags & MAP_HEIGHT_NO_HEIGHT))
{
@@ -875,8 +854,8 @@ bool ConvertADT(std::string const& inputPath, std::string const& outputPath, int
outFile.write(reinterpret_cast<const char*>(&map), sizeof(map));
// Store area data
outFile.write(reinterpret_cast<const char*>(&areaHeader), sizeof(areaHeader));
- if (!(areaHeader.flags&MAP_AREA_NO_AREA))
- outFile.write(reinterpret_cast<const char*>(area_flags), sizeof(area_flags));
+ if (!(areaHeader.flags & MAP_AREA_NO_AREA))
+ outFile.write(reinterpret_cast<const char*>(area_ids), sizeof(area_ids));
// Store height data
outFile.write(reinterpret_cast<const char*>(&heightHeader), sizeof(heightHeader));
@@ -899,6 +878,12 @@ bool ConvertADT(std::string const& inputPath, std::string const& outputPath, int
}
}
+ if (heightHeader.flags & MAP_HEIGHT_HAS_FLIGHT_BOUNDS)
+ {
+ outFile.write(reinterpret_cast<char*>(flight_box_max), sizeof(flight_box_max));
+ outFile.write(reinterpret_cast<char*>(flight_box_min), sizeof(flight_box_min));
+ }
+
// Store liquid data if need
if (map.liquidMapOffset)
{
@@ -935,7 +920,6 @@ void ExtractMapsFromMpq(uint32 build)
uint32 map_count = ReadMapDBC();
- ReadAreaTableDBC();
ReadLiquidTypeTableDBC();
std::string path = output_path;
@@ -972,8 +956,7 @@ void ExtractMapsFromMpq(uint32 build)
}
}
printf("\n");
- delete [] areas;
- delete [] map_ids;
+ delete[] map_ids;
}
bool ExtractFile( char const* mpq_name, std::string const& filename )
diff --git a/src/tools/map_extractor/adt.cpp b/src/tools/map_extractor/adt.cpp
index f8e6e469ff0..e97b40475d0 100644
--- a/src/tools/map_extractor/adt.cpp
+++ b/src/tools/map_extractor/adt.cpp
@@ -21,15 +21,16 @@
#include "adt.h"
// Helper
-int holetab_h[4] = {0x1111, 0x2222, 0x4444, 0x8888};
-int holetab_v[4] = {0x000F, 0x00F0, 0x0F00, 0xF000};
+int holetab_h[4] = { 0x1111, 0x2222, 0x4444, 0x8888 };
+int holetab_v[4] = { 0x000F, 0x00F0, 0x0F00, 0xF000 };
-u_map_fcc MHDRMagic = { {'R','D','H','M'} };
-u_map_fcc MCINMagic = { {'N','I','C','M'} };
-u_map_fcc MH2OMagic = { {'O','2','H','M'} };
-u_map_fcc MCNKMagic = { {'K','N','C','M'} };
-u_map_fcc MCVTMagic = { {'T','V','C','M'} };
-u_map_fcc MCLQMagic = { {'Q','L','C','M'} };
+u_map_fcc MHDRMagic = { { 'R','D','H','M' } };
+u_map_fcc MCINMagic = { { 'N','I','C','M' } };
+u_map_fcc MH2OMagic = { { 'O','2','H','M' } };
+u_map_fcc MCNKMagic = { { 'K','N','C','M' } };
+u_map_fcc MCVTMagic = { { 'T','V','C','M' } };
+u_map_fcc MCLQMagic = { { 'Q','L','C','M' } };
+u_map_fcc MFBOMagic = { { 'O','B','F','M' } };
bool isHole(int holes, int i, int j)
{
@@ -81,7 +82,7 @@ bool adt_MHDR::prepareLoadedData()
if (fcc != MHDRMagic.fcc)
return false;
- if (size!=sizeof(adt_MHDR)-8)
+ if (size != sizeof(adt_MHDR) - 8)
return false;
// Check and prepare MCIN
@@ -92,6 +93,9 @@ bool adt_MHDR::prepareLoadedData()
if (offsMH2O && !getMH2O()->prepareLoadedData())
return false;
+ if (offsMFBO && flags & 1 && !getMFBO()->prepareLoadedData())
+ return false;
+
return true;
}
@@ -154,3 +158,8 @@ bool adt_MCLQ::prepareLoadedData()
return true;
}
+
+bool adt_MFBO::prepareLoadedData()
+{
+ return fcc == MFBOMagic.fcc;
+}
diff --git a/src/tools/map_extractor/adt.h b/src/tools/map_extractor/adt.h
index 7b3dc07ae84..30389939f38 100644
--- a/src/tools/map_extractor/adt.h
+++ b/src/tools/map_extractor/adt.h
@@ -263,6 +263,28 @@ public:
};
//
+// Adt file min/max height chunk
+//
+class adt_MFBO
+{
+ union
+ {
+ uint32 fcc;
+ char fcc_txt[4];
+ };
+public:
+ uint32 size;
+ struct plane
+ {
+ int16 coords[9];
+ };
+ plane max;
+ plane min;
+
+ bool prepareLoadedData();
+};
+
+//
// Adt file header chunk
//
class adt_MHDR
@@ -274,12 +296,12 @@ class adt_MHDR
public:
uint32 size;
- uint32 pad;
+ uint32 flags;
uint32 offsMCIN; // MCIN
- uint32 offsTex; // MTEX
- uint32 offsModels; // MMDX
- uint32 offsModelsIds; // MMID
- uint32 offsMapObejcts; // MWMO
+ uint32 offsTex; // MTEX
+ uint32 offsModels; // MMDX
+ uint32 offsModelsIds; // MMID
+ uint32 offsMapObejcts; // MWMO
uint32 offsMapObejctsIds; // MWID
uint32 offsDoodsDef; // MDDF
uint32 offsObjectsDef; // MODF
@@ -291,9 +313,22 @@ public:
uint32 data4;
uint32 data5;
bool prepareLoadedData();
- adt_MCIN *getMCIN(){ return (adt_MCIN *)((uint8 *)&pad+offsMCIN);}
- adt_MH2O *getMH2O(){ return offsMH2O ? (adt_MH2O *)((uint8 *)&pad+offsMH2O) : 0;}
-
+ adt_MCIN* getMCIN()
+ {
+ return reinterpret_cast<adt_MCIN*>(reinterpret_cast<uint8*>(&flags) + offsMCIN);
+ }
+ adt_MH2O* getMH2O()
+ {
+ if (offsMH2O)
+ return reinterpret_cast<adt_MH2O*>(reinterpret_cast<uint8*>(&flags) + offsMH2O);
+ return nullptr;
+ }
+ adt_MFBO* getMFBO()
+ {
+ if (flags & 1 && offsMFBO)
+ return reinterpret_cast<adt_MFBO*>(reinterpret_cast<uint8*>(&flags) + offsMFBO);
+ return nullptr;
+ }
};
class ADT_file : public FileLoader{
diff --git a/src/tools/mmaps_generator/TerrainBuilder.cpp b/src/tools/mmaps_generator/TerrainBuilder.cpp
index 02f3fb1cf4d..69b1ffcb062 100644
--- a/src/tools/mmaps_generator/TerrainBuilder.cpp
+++ b/src/tools/mmaps_generator/TerrainBuilder.cpp
@@ -80,7 +80,7 @@ struct map_liquidHeader
namespace MMAP
{
- char const* MAP_VERSION_MAGIC = "v1.3";
+ char const* MAP_VERSION_MAGIC = "v1.8";
TerrainBuilder::TerrainBuilder(bool skipLiquid) : m_skipLiquid (skipLiquid){ }
TerrainBuilder::~TerrainBuilder() { }