aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/common/Utilities/Util.h1
-rw-r--r--src/server/game/AI/CreatureAI.h2
-rw-r--r--src/server/game/AI/SmartScripts/SmartScript.cpp25
-rw-r--r--src/server/game/AI/SmartScripts/SmartScriptMgr.cpp15
-rw-r--r--src/server/game/AI/SmartScripts/SmartScriptMgr.h29
-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/Entities/Creature/Creature.cpp60
-rw-r--r--src/server/game/Entities/Creature/Creature.h42
-rw-r--r--src/server/game/Entities/GameObject/GameObject.cpp10
-rw-r--r--src/server/game/Entities/Player/Player.cpp24
-rw-r--r--src/server/game/Entities/Player/Player.h5
-rw-r--r--src/server/game/Entities/Unit/Unit.cpp87
-rw-r--r--src/server/game/Entities/Unit/Unit.h38
-rw-r--r--src/server/game/Groups/Group.cpp2
-rw-r--r--src/server/game/Handlers/MiscHandler.cpp6
-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/InstanceScript.cpp12
-rw-r--r--src/server/game/Instances/InstanceScript.h19
-rw-r--r--src/server/game/Maps/AreaBoundary.cpp2
-rw-r--r--src/server/game/Maps/AreaBoundary.h9
-rw-r--r--src/server/game/Miscellaneous/Formulas.h3
-rw-r--r--src/server/game/Spells/Auras/SpellAuraEffects.cpp7
-rw-r--r--src/server/game/Spells/Auras/SpellAuras.cpp2
-rw-r--r--src/server/game/Spells/Spell.cpp2
-rw-r--r--src/server/game/Spells/SpellEffects.cpp10
-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/SpellMgr.cpp10
-rw-r--r--src/server/scripts/Commands/cs_npc.cpp6
-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/instance_deadmines.cpp2
-rw-r--r--src/server/scripts/EasternKingdoms/Uldaman/uldaman.cpp5
-rw-r--r--src/server/scripts/EasternKingdoms/zone_undercity.cpp54
-rw-r--r--src/server/scripts/EasternKingdoms/zone_western_plaguelands.cpp53
-rw-r--r--src/server/scripts/Kalimdor/WailingCaverns/wailing_caverns.cpp14
-rw-r--r--src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/instance_trial_of_the_crusader.cpp9
-rw-r--r--src/server/scripts/Northrend/IcecrownCitadel/boss_blood_prince_council.cpp1
-rw-r--r--src/server/scripts/Northrend/IcecrownCitadel/boss_sindragosa.cpp9
-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.cpp3
-rw-r--r--src/server/scripts/Northrend/Ulduar/Ulduar/boss_mimiron.cpp5
-rw-r--r--src/server/scripts/Northrend/UtgardeKeep/UtgardeKeep/boss_ingvar_the_plunderer.cpp43
-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_paladin.cpp69
-rw-r--r--src/server/scripts/World/duel_reset.cpp43
-rw-r--r--src/server/scripts/World/npc_professions.cpp106
59 files changed, 1976 insertions, 1094 deletions
diff --git a/src/common/Utilities/Util.h b/src/common/Utilities/Util.h
index f72c1430370..1f3b78a8d56 100644
--- a/src/common/Utilities/Util.h
+++ b/src/common/Utilities/Util.h
@@ -27,6 +27,7 @@
#include <vector>
#include <list>
#include <map>
+#include <ctime>
// Searcher for map of structs
template<typename T, class S> struct Finder
diff --git a/src/server/game/AI/CreatureAI.h b/src/server/game/AI/CreatureAI.h
index 3b7c489e018..f250a79ea25 100644
--- a/src/server/game/AI/CreatureAI.h
+++ b/src/server/game/AI/CreatureAI.h
@@ -80,7 +80,7 @@ class CreatureAI : public UnitAI
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(); }
+ void SetBoundary(CreatureBoundary const* boundary) { _boundary = boundary; me->DoImmediateBoundaryCheck(); }
public:
enum EvadeReason
{
diff --git a/src/server/game/AI/SmartScripts/SmartScript.cpp b/src/server/game/AI/SmartScripts/SmartScript.cpp
index 69a0f684a84..808491495a9 100644
--- a/src/server/game/AI/SmartScripts/SmartScript.cpp
+++ b/src/server/game/AI/SmartScripts/SmartScript.cpp
@@ -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..977847e60c9 100644
--- a/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp
+++ b/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp
@@ -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/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/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp
index 3e67f709816..73b9e0580cf 100644
--- a/src/server/game/Entities/Creature/Creature.cpp
+++ b/src/server/game/Entities/Creature/Creature.cpp
@@ -1292,8 +1292,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)
@@ -1604,13 +1634,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();
}
@@ -2739,7 +2785,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..a2d8dda7a7c 100644
--- a/src/server/game/Entities/Creature/Creature.h
+++ b/src/server/game/Entities/Creature/Creature.h
@@ -39,23 +39,24 @@ 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
};
#define CREATURE_FLAG_EXTRA_DB_ALLOWED (CREATURE_FLAG_EXTRA_INSTANCE_BIND | CREATURE_FLAG_EXTRA_CIVILIAN | \
@@ -63,7 +64,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)
#define CREATURE_REGEN_INTERVAL 2 * IN_MILLISECONDS
@@ -534,7 +535,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 +606,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 +657,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..b030f42bef7 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)
diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp
index 95e76a25763..8ce78f88906 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
diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h
index e7af827e9c7..9b64023698c 100644
--- a/src/server/game/Entities/Player/Player.h
+++ b/src/server/game/Entities/Player/Player.h
@@ -1050,8 +1050,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();
diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp
index 24ddcd358ea..3aed5fde7b3 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)
{
@@ -6365,54 +6387,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 +7099,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);
@@ -14187,8 +14158,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
diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h
index c64667feeca..8572c791d67 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; }
diff --git a/src/server/game/Groups/Group.cpp b/src/server/game/Groups/Group.cpp
index 8ebc71b0146..43159828a3c 100644
--- a/src/server/game/Groups/Group.cpp
+++ b/src/server/game/Groups/Group.cpp
@@ -2274,6 +2274,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/MiscHandler.cpp b/src/server/game/Handlers/MiscHandler.cpp
index 758d5af83f7..6cd8bfc014e 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;
}
}
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/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/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/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/Spells/Auras/SpellAuraEffects.cpp b/src/server/game/Spells/Auras/SpellAuraEffects.cpp
index a4e7f61d83c..6a10d113553 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)
{
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..63fe148dd93 100644
--- a/src/server/game/Spells/Spell.cpp
+++ b/src/server/game/Spells/Spell.cpp
@@ -4701,7 +4701,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;
diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp
index 40bd4e3c263..f961654f279 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);
}
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/SpellMgr.cpp b/src/server/game/Spells/SpellMgr.cpp
index e96ed8c6799..ff8fc4539ff 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;
}
@@ -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)
@@ -3332,6 +3335,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 +3570,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/scripts/Commands/cs_npc.cpp b/src/server/scripts/Commands/cs_npc.cpp
index daf4fe5866a..7580e9a07d3 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 18
EnumName<NPCFlags, int32> const npcFlagTexts[NPCFLAG_COUNT] =
{
@@ -162,7 +162,9 @@ 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)
};
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/instance_deadmines.cpp b/src/server/scripts/EasternKingdoms/Deadmines/instance_deadmines.cpp
index 80391ab2873..6714b243765 100644
--- a/src/server/scripts/EasternKingdoms/Deadmines/instance_deadmines.cpp
+++ b/src/server/scripts/EasternKingdoms/Deadmines/instance_deadmines.cpp
@@ -61,6 +61,8 @@ class instance_deadmines : public InstanceMapScript
SetHeaders(DataHeader);
State = CANNON_NOT_USED;
+ CannonBlast_Timer = 0;
+ PiratesDelay_Timer = 0;
}
ObjectGuid FactoryDoorGUID;
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..c0b4e06cfff 100644
--- a/src/server/scripts/EasternKingdoms/zone_undercity.cpp
+++ b/src/server/scripts/EasternKingdoms/zone_undercity.cpp
@@ -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..b1a00f35bb5 100644
--- a/src/server/scripts/EasternKingdoms/zone_western_plaguelands.cpp
+++ b/src/server/scripts/EasternKingdoms/zone_western_plaguelands.cpp
@@ -41,12 +41,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 +73,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,13 +104,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());
+ 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());
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/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_blood_prince_council.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_blood_prince_council.cpp
index f59701b9c25..b8e7dcc91d5 100644
--- a/src/server/scripts/Northrend/IcecrownCitadel/boss_blood_prince_council.cpp
+++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_blood_prince_council.cpp
@@ -1222,6 +1222,7 @@ class npc_kinetic_bomb : public CreatureScript
_x = 0.f;
_y = 0.f;
_groundZ = 0.f;
+ me->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_KNOCK_BACK, true);
}
void Reset() override
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/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..81f1e071da0 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,204 @@ 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)
+ void OpenGate()
{
- 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
- {
- 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())
- {
- me->HandleEmoteCommand(EMOTE_ONESHOT_SPELL_CAST);
- if (Creature* pRandomDeadTrigger = ObjectAccessor::GetCreature(*me, DeadTriggerGUID[rand32() % POS_DEAD]))
- me->CastSpell(pRandomDeadTrigger, spellId, true);
- }
+ if (_gateIsOpen)
+ return;
+ instance->SetData(DATA_GOTHIK_GATE, GO_STATE_ACTIVE);
+ Talk(EMOTE_GATE_OPENED);
+ _gateIsOpen = true;
+
+ for (ObjectGuid summonGuid : summons)
+ if (Creature* summon = ObjectAccessor::GetCreature(*me, summonGuid))
+ summon->AI()->DoAction(ACTION_GATE_OPENED);
}
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 +558,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 +990,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..fd945db4604 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 false;
+
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..01c4704592f 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
}
diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_mimiron.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_mimiron.cpp
index 820332791c8..4d6aa046d10 100644
--- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_mimiron.cpp
+++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_mimiron.cpp
@@ -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 false;
+
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/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_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/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/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();
}