diff options
Diffstat (limited to 'src')
22 files changed, 698 insertions, 291 deletions
diff --git a/src/server/game/AI/CoreAI/UnitAI.cpp b/src/server/game/AI/CoreAI/UnitAI.cpp index 5aa6ea8ea7a..f0d9d34db69 100644 --- a/src/server/game/AI/CoreAI/UnitAI.cpp +++ b/src/server/game/AI/CoreAI/UnitAI.cpp @@ -29,7 +29,15 @@ void UnitAI::AttackStart(Unit* victim) { if (victim && me->Attack(victim, true)) + { + // Clear distracted state on attacking + if (me->HasUnitState(UNIT_STATE_DISTRACTED)) + { + me->ClearUnitState(UNIT_STATE_DISTRACTED); + me->GetMotionMaster()->Clear(); + } me->GetMotionMaster()->MoveChase(victim); + } } void UnitAI::AttackStartCaster(Unit* victim, float dist) diff --git a/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp index 8e2a82dbfd3..7b145268d22 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp +++ b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp @@ -470,6 +470,7 @@ void BossAI::_Reset() if (!me->IsAlive()) return; + me->SetCombatPulseDelay(0); me->ResetLootMode(); events.Reset(); summons.DespawnAll(); @@ -500,6 +501,7 @@ void BossAI::_EnterCombat() instance->SetBossState(_bossId, IN_PROGRESS); } + me->SetCombatPulseDelay(5); me->setActive(true); DoZoneInCombat(); ScheduleTasks(); diff --git a/src/server/game/AI/SmartScripts/SmartAI.cpp b/src/server/game/AI/SmartScripts/SmartAI.cpp index 46122697094..46cf934356d 100644 --- a/src/server/game/AI/SmartScripts/SmartAI.cpp +++ b/src/server/game/AI/SmartScripts/SmartAI.cpp @@ -453,45 +453,15 @@ void SmartAI::MoveInLineOfSight(Unit* who) GetScript()->OnMoveInLineOfSight(who); - if (me->HasReactState(REACT_PASSIVE) || AssistPlayerInCombat(who)) + if (AssistPlayerInCombat(who)) return; - if (!CanAIAttack(who)) - return; - - if (!me->CanStartAttack(who, false)) - return; - - if (me->IsHostileTo(who)) - { - float fAttackRadius = me->GetAttackDistance(who); - if (me->IsWithinDistInMap(who, fAttackRadius) && me->IsWithinLOSInMap(who)) - { - if (!me->GetVictim()) - { - // Clear distracted state on combat - if (me->HasUnitState(UNIT_STATE_DISTRACTED)) - { - me->ClearUnitState(UNIT_STATE_DISTRACTED); - me->GetMotionMaster()->Clear(); - } - - AttackStart(who); - } - else/* if (me->GetMap()->IsDungeon())*/ - { - who->SetInCombatWith(me); - me->AddThreat(who, 0.0f); - } - } - } + CreatureAI::MoveInLineOfSight(who); } bool SmartAI::CanAIAttack(const Unit* /*who*/) const { - if (me->GetReactState() == REACT_PASSIVE) - return false; - return true; + return !(me->HasReactState(REACT_PASSIVE)); } bool SmartAI::AssistPlayerInCombat(Unit* who) diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index 7fe3628f893..c8a258ad386 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -138,7 +138,7 @@ bool ForcedDespawnDelayEvent::Execute(uint64 /*e_time*/, uint32 /*p_time*/) Creature::Creature(bool isWorldObject): Unit(isWorldObject), MapObject(), m_groupLootTimer(0), lootingGroupLowGUID(0), m_PlayerDamageReq(0), m_lootRecipient(), m_lootRecipientGroup(0), _skinner(), _pickpocketLootRestore(0), m_corpseRemoveTime(0), m_respawnTime(0), -m_respawnDelay(300), m_corpseDelay(60), m_respawnradius(0.0f), m_reactState(REACT_AGGRESSIVE), +m_respawnDelay(300), m_corpseDelay(60), m_respawnradius(0.0f), m_combatPulseTime(0), m_combatPulseDelay(0), m_reactState(REACT_AGGRESSIVE), m_defaultMovementType(IDLE_MOTION_TYPE), m_spawnId(0), m_equipmentId(0), m_originalEquipmentId(0), m_AlreadyCallAssistance(false), m_AlreadySearchedAssistance(false), m_regenHealth(true), m_AI_locked(false), m_meleeDamageSchoolMask(SPELL_SCHOOL_MASK_NORMAL), m_originalEntry(0), m_homePosition(), m_transportHomePosition(), m_creatureInfo(NULL), m_creatureData(NULL), m_waypointID(0), m_path_id(0), m_formation(NULL) @@ -539,10 +539,44 @@ void Creature::Update(uint32 diff) LastCharmerGUID.Clear(); } + // if periodic combat pulse is enabled and we are both in combat and in a dungeon, do this now + if (m_combatPulseDelay > 0 && IsInCombat() && GetMap()->IsDungeon()) + { + if (diff > m_combatPulseTime) + m_combatPulseTime = 0; + else + m_combatPulseTime -= diff; + + if (m_combatPulseTime == 0) + { + Map::PlayerList const &players = GetMap()->GetPlayers(); + if (!players.isEmpty()) + for (Map::PlayerList::const_iterator it = players.begin(); it != players.end(); ++it) + { + if (Player* player = it->GetSource()) + { + if (player->IsGameMaster()) + continue; + + if (player->IsAlive() && this->IsHostileTo(player)) + { + if (CanHaveThreatList()) + AddThreat(player, 0.0f); + this->SetInCombatWith(player); + player->SetInCombatWith(this); + } + } + } + + m_combatPulseTime = m_combatPulseDelay * IN_MILLISECONDS; + } + } + if (!IsInEvadeMode() && IsAIEnabled) { // do not allow the AI to be changed during update m_AI_locked = true; + i_AI->UpdateAI(diff); m_AI_locked = false; } diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index 3e381063772..79832b70f52 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -601,6 +601,14 @@ class Creature : public Unit, public GridObject<Creature>, public MapObject float GetRespawnRadius() const { return m_respawnradius; } void SetRespawnRadius(float dist) { m_respawnradius = dist; } + 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) + { + m_combatPulseDelay = delay; + if (m_combatPulseTime == 0 || m_combatPulseTime > delay) + m_combatPulseTime = delay; + } + uint32 m_groupLootTimer; // (msecs)timer used for group loot uint32 lootingGroupLowGUID; // used to find group which is looting corpse @@ -686,6 +694,8 @@ class Creature : public Unit, public GridObject<Creature>, public MapObject uint32 m_respawnDelay; // (secs) delay between corpse disappearance and respawning uint32 m_corpseDelay; // (secs) delay between death and corpse disappearance float m_respawnradius; + uint32 m_combatPulseTime; // (msecs) remaining time for next zone-in-combat pulse + uint32 m_combatPulseDelay; // (secs) how often the creature puts the entire zone in combat (only works in dungeons) ReactStates m_reactState; // for AI, not charmInfo void RegenerateMana(); diff --git a/src/server/game/Handlers/AuctionHouseHandler.cpp b/src/server/game/Handlers/AuctionHouseHandler.cpp index 0293901c9e3..8ecf86680ca 100644 --- a/src/server/game/Handlers/AuctionHouseHandler.cpp +++ b/src/server/game/Handlers/AuctionHouseHandler.cpp @@ -265,6 +265,8 @@ void WorldSession::HandleAuctionSellItem(WorldPacket& recvData) if (!auctioneerData) { TC_LOG_ERROR("misc", "Data for auctioneer not found (%s)", auctioneer.ToString().c_str()); + SendAuctionCommandResult(0, AUCTION_SELL_ITEM, ERR_AUCTION_DATABASE_ERROR); + delete AH; return; } @@ -272,6 +274,8 @@ void WorldSession::HandleAuctionSellItem(WorldPacket& recvData) if (!auctioneerInfo) { TC_LOG_ERROR("misc", "Non existing auctioneer (%s)", auctioneer.ToString().c_str()); + SendAuctionCommandResult(0, AUCTION_SELL_ITEM, ERR_AUCTION_DATABASE_ERROR); + delete AH; return; } diff --git a/src/server/game/Instances/InstanceScript.cpp b/src/server/game/Instances/InstanceScript.cpp index 5d44f3ec696..a8cf42ea49e 100644 --- a/src/server/game/Instances/InstanceScript.cpp +++ b/src/server/game/Instances/InstanceScript.cpp @@ -652,3 +652,25 @@ void InstanceScript::UpdateEncounterState(EncounterCreditType type, uint32 credi } } } + +std::string InstanceScript::GetBossStateName(uint8 state) +{ + // See enum EncounterState in InstanceScript.h + switch (state) + { + case NOT_STARTED: + return "NOT_STARTED"; + case IN_PROGRESS: + return "IN_PROGRESS"; + case FAIL: + return "FAIL"; + case DONE: + return "DONE"; + case SPECIAL: + return "SPECIAL"; + case TO_BE_DECIDED: + return "TO_BE_DECIDED"; + default: + return "INVALID"; + } +} diff --git a/src/server/game/Instances/InstanceScript.h b/src/server/game/Instances/InstanceScript.h index e832d7cdffe..93dafea0413 100644 --- a/src/server/game/Instances/InstanceScript.h +++ b/src/server/game/Instances/InstanceScript.h @@ -220,6 +220,7 @@ class InstanceScript : public ZoneScript virtual bool SetBossState(uint32 id, EncounterState state); EncounterState GetBossState(uint32 id) const { return id < bosses.size() ? bosses[id].state : TO_BE_DECIDED; } + static std::string GetBossStateName(uint8 state); BossBoundaryMap const* GetBossBoundary(uint32 id) const { return id < bosses.size() ? &bosses[id].boundary : NULL; } // Achievement criteria additional requirements check diff --git a/src/server/game/Skills/SkillExtraItems.cpp b/src/server/game/Skills/SkillExtraItems.cpp index 8df9ce86f9a..2c9a2a7bcfd 100644 --- a/src/server/game/Skills/SkillExtraItems.cpp +++ b/src/server/game/Skills/SkillExtraItems.cpp @@ -20,11 +20,98 @@ #include "DatabaseEnv.h" #include "Log.h" #include "Player.h" +#include "ObjectMgr.h" #include <map> // some type definitions // no use putting them in the header file, they're only used in this .cpp +// struct to store information about perfection procs +// one entry per spell +struct SkillPerfectItemEntry +{ + // the spell id of the spell required - it's named "specialization" to conform with SkillExtraItemEntry + uint32 requiredSpecialization; + // perfection proc chance + float perfectCreateChance; + // itemid of the resulting perfect item + uint32 perfectItemType; + + SkillPerfectItemEntry() + : requiredSpecialization(0), perfectCreateChance(0.0f), perfectItemType(0) { } + SkillPerfectItemEntry(uint32 rS, float pCC, uint32 pIT) + : requiredSpecialization(rS), perfectCreateChance(pCC), perfectItemType(pIT) { } +}; + +// map to store perfection info. key = spellId of the creation spell, value is the perfectitementry as specified above +typedef std::map<uint32, SkillPerfectItemEntry> SkillPerfectItemMap; + +SkillPerfectItemMap SkillPerfectItemStore; + +// loads the perfection proc info from DB +void LoadSkillPerfectItemTable() +{ + uint32 oldMSTime = getMSTime(); + + SkillPerfectItemStore.clear(); // reload capability + + // 0 1 2 3 + QueryResult result = WorldDatabase.Query("SELECT spellId, requiredSpecialization, perfectCreateChance, perfectItemType FROM skill_perfect_item_template"); + + if (!result) + { + TC_LOG_ERROR("server.loading", ">> Loaded 0 spell perfection definitions. DB table `skill_perfect_item_template` is empty."); + return; + } + + uint32 count = 0; + + do /* fetch data and run sanity checks */ + { + Field* fields = result->Fetch(); + + uint32 spellId = fields[0].GetUInt32(); + + if (!sSpellMgr->GetSpellInfo(spellId)) + { + TC_LOG_ERROR("sql.sql", "Skill perfection data for spell %u has non-existent spell id in `skill_perfect_item_template`!", spellId); + continue; + } + + uint32 requiredSpecialization = fields[1].GetUInt32(); + if (!sSpellMgr->GetSpellInfo(requiredSpecialization)) + { + TC_LOG_ERROR("sql.sql", "Skill perfection data for spell %u has non-existent required specialization spell id %u in `skill_perfect_item_template`!", spellId, requiredSpecialization); + continue; + } + + float perfectCreateChance = fields[2].GetFloat(); + if (perfectCreateChance <= 0.0f) + { + TC_LOG_ERROR("sql.sql", "Skill perfection data for spell %u has impossibly low proc chance in `skill_perfect_item_template`!", spellId); + continue; + } + + uint32 perfectItemType = fields[3].GetUInt32(); + if (!sObjectMgr->GetItemTemplate(perfectItemType)) + { + TC_LOG_ERROR("sql.sql", "Skill perfection data for spell %u references non-existent perfect item id %u in `skill_perfect_item_template`!", spellId, perfectItemType); + continue; + } + + SkillPerfectItemEntry& skillPerfectItemEntry = SkillPerfectItemStore[spellId]; + + skillPerfectItemEntry.requiredSpecialization = requiredSpecialization; + skillPerfectItemEntry.perfectCreateChance = perfectCreateChance; + skillPerfectItemEntry.perfectItemType = perfectItemType; + + ++count; + } + while (result->NextRow()); + + TC_LOG_INFO("server.loading", ">> Loaded %u spell perfection definitions in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); +} + // struct to store information about extra item creation // one entry for every spell that is able to create an extra item struct SkillExtraItemEntry @@ -112,6 +199,30 @@ void LoadSkillExtraItemTable() TC_LOG_INFO("server.loading", ">> Loaded %u spell specialization definitions in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); } +bool CanCreatePerfectItem(Player* player, uint32 spellId, float &perfectCreateChance, uint32 &perfectItemType) +{ + SkillPerfectItemMap::const_iterator ret = SkillPerfectItemStore.find(spellId); + // no entry in DB means no perfection proc possible + if (ret == SkillPerfectItemStore.end()) + return false; + + SkillPerfectItemEntry const* thisEntry = &ret->second; + // lack of entry means no perfection proc possible + if (!thisEntry) + return false; + + // if you don't have the spell needed, then no procs for you + if (!player->HasSpell(thisEntry->requiredSpecialization)) + return false; + + // set values as appropriate + perfectCreateChance = thisEntry->perfectCreateChance; + perfectItemType = thisEntry->perfectItemType; + + // and tell the caller to start rolling the dice + return true; +} + bool CanCreateExtraItems(Player* player, uint32 spellId, float &additionalChance, uint8 &additionalMax) { // get the info for the specified spell diff --git a/src/server/game/Skills/SkillExtraItems.h b/src/server/game/Skills/SkillExtraItems.h index 0cdfff74ed2..118c49ed00f 100644 --- a/src/server/game/Skills/SkillExtraItems.h +++ b/src/server/game/Skills/SkillExtraItems.h @@ -23,6 +23,10 @@ // predef classes used in functions class Player; +// returns true and sets the appropriate info if the player can create a perfect item with the given spellId +bool CanCreatePerfectItem(Player* player, uint32 spellId, float &perfectCreateChance, uint32 &perfectItemType); +// load perfection proc info from DB +void LoadSkillPerfectItemTable(); // returns true and sets the appropriate info if the player can create extra items with the given spellId bool CanCreateExtraItems(Player* player, uint32 spellId, float &additionalChance, uint8 &additionalMax); // function to load the extra item creation info from DB diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index 10dfbd511df..e587a59858a 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -1571,6 +1571,22 @@ void Spell::DoCreateItem(uint32 /*i*/, uint32 itemtype) if (num_to_add > pProto->GetMaxStackSize()) num_to_add = pProto->GetMaxStackSize(); + /* == gem perfection handling == */ + + // the chance of getting a perfect result + float perfectCreateChance = 0.0f; + // the resulting perfect item if successful + uint32 perfectItemType = itemtype; + // get perfection capability and chance + if (CanCreatePerfectItem(player, m_spellInfo->Id, perfectCreateChance, perfectItemType)) + if (roll_chance_f(perfectCreateChance)) // if the roll succeeds... + newitemid = perfectItemType; // the perfect item replaces the regular one + + /* == gem perfection handling over == */ + + + /* == profession specialization handling == */ + // init items_count to 1, since 1 item will be created regardless of specialization int items_count=1; // the chance to create additional items @@ -1579,15 +1595,16 @@ void Spell::DoCreateItem(uint32 /*i*/, uint32 itemtype) uint8 additionalMaxNum=0; // get the chance and maximum number for creating extra items if (CanCreateExtraItems(player, m_spellInfo->Id, additionalCreateChance, additionalMaxNum)) - { // roll with this chance till we roll not to create or we create the max num while (roll_chance_f(additionalCreateChance) && items_count <= additionalMaxNum) ++items_count; - } // really will be created more items num_to_add *= items_count; + /* == profession specialization handling over == */ + + // can the player store the new item? ItemPosCountVec dest; uint32 no_space = 0; diff --git a/src/server/game/Spells/SpellMgr.cpp b/src/server/game/Spells/SpellMgr.cpp index f27f9220aaa..55d0ad0b4dc 100644 --- a/src/server/game/Spells/SpellMgr.cpp +++ b/src/server/game/Spells/SpellMgr.cpp @@ -3105,11 +3105,13 @@ void SpellMgr::LoadSpellInfoCorrections() case 28796: // Poison Bolt Volly - Faerlina spellInfo->MaxAffectedTargets = 5; break; + case 54835: // Curse of the Plaguebringer - Noth (H) + spellInfo->MaxAffectedTargets = 8; + break; case 40827: // Sinful Beam case 40859: // Sinister Beam case 40860: // Vile Beam case 40861: // Wicked Beam - case 54835: // Curse of the Plaguebringer - Noth (H) case 54098: // Poison Bolt Volly - Faerlina (H) spellInfo->MaxAffectedTargets = 10; break; diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index 85e79168b71..169ff069de4 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -1644,6 +1644,9 @@ void World::SetInitialWorldSettings() TC_LOG_INFO("server.loading", "Loading Skill Extra Item Table..."); LoadSkillExtraItemTable(); + TC_LOG_INFO("server.loading", "Loading Skill Perfection Data Table..."); + LoadSkillPerfectItemTable(); + TC_LOG_INFO("server.loading", "Loading Skill Fishing base level requirements..."); sObjectMgr->LoadFishingBaseSkillLevel(); diff --git a/src/server/scripts/Commands/cs_instance.cpp b/src/server/scripts/Commands/cs_instance.cpp index c960d3f3753..aa7310e3c65 100644 --- a/src/server/scripts/Commands/cs_instance.cpp +++ b/src/server/scripts/Commands/cs_instance.cpp @@ -254,7 +254,8 @@ public: } map->ToInstanceMap()->GetInstanceScript()->SetBossState(encounterId, (EncounterState)state); - handler->PSendSysMessage(LANG_COMMAND_INST_SET_BOSS_STATE, encounterId, state); + std::string stateName = InstanceScript::GetBossStateName(state); + handler->PSendSysMessage(LANG_COMMAND_INST_SET_BOSS_STATE, encounterId, state, stateName); return true; } @@ -318,7 +319,8 @@ public: } uint8 state = map->ToInstanceMap()->GetInstanceScript()->GetBossState(encounterId); - handler->PSendSysMessage(LANG_COMMAND_INST_GET_BOSS_STATE, encounterId, state); + std::string stateName = InstanceScript::GetBossStateName(state); + handler->PSendSysMessage(LANG_COMMAND_INST_GET_BOSS_STATE, encounterId, state, stateName); return true; } }; diff --git a/src/server/scripts/Commands/cs_reload.cpp b/src/server/scripts/Commands/cs_reload.cpp index f4b442de92a..2863d2d4ede 100644 --- a/src/server/scripts/Commands/cs_reload.cpp +++ b/src/server/scripts/Commands/cs_reload.cpp @@ -749,12 +749,21 @@ public: return true; } - static bool HandleReloadSkillExtraItemTemplateCommand(ChatHandler* handler, const char* /*args*/) + static bool HandleReloadSkillPerfectItemTemplateCommand(ChatHandler* handler, const char* /*args*/) + { // latched onto HandleReloadSkillExtraItemTemplateCommand as it's part of that table group (and i don't want to chance all the command IDs) + TC_LOG_INFO("misc", "Re-Loading Skill Perfection Data Table..."); + LoadSkillPerfectItemTable(); + handler->SendGlobalGMSysMessage("DB table `skill_perfect_item_template` (perfect item procs when crafting) reloaded."); + return true; + } + + static bool HandleReloadSkillExtraItemTemplateCommand(ChatHandler* handler, const char* args) { TC_LOG_INFO("misc", "Re-Loading Skill Extra Item Table..."); LoadSkillExtraItemTable(); handler->SendGlobalGMSysMessage("DB table `skill_extra_item_template` (extra item creation when crafting) reloaded."); - return true; + + return HandleReloadSkillPerfectItemTemplateCommand(handler, args); } static bool HandleReloadSkillFishingBaseLevelCommand(ChatHandler* handler, const char* /*args*/) diff --git a/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter1.cpp b/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter1.cpp index 9747d31952b..f2d7f23bdc7 100644 --- a/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter1.cpp +++ b/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter1.cpp @@ -93,11 +93,6 @@ class npc_unworthy_initiate : public CreatureScript public: npc_unworthy_initiate() : CreatureScript("npc_unworthy_initiate") { } - CreatureAI* GetAI(Creature* creature) const override - { - return new npc_unworthy_initiateAI(creature); - } - struct npc_unworthy_initiateAI : public ScriptedAI { npc_unworthy_initiateAI(Creature* creature) : ScriptedAI(creature) @@ -156,7 +151,7 @@ public: me->CastSpell(me, SPELL_DK_INITIATE_VISUAL, true); if (Player* starter = ObjectAccessor::GetPlayer(*me, playerGUID)) - sCreatureTextMgr->SendChat(me, SAY_EVENT_ATTACK, NULL, CHAT_MSG_ADDON, LANG_ADDON, TEXT_RANGE_NORMAL, 0, TEAM_OTHER, false, starter); + Talk(SAY_EVENT_ATTACK, starter); phase = PHASE_TO_ATTACK; } @@ -286,6 +281,11 @@ public: } } }; + + CreatureAI* GetAI(Creature* creature) const override + { + return new npc_unworthy_initiateAI(creature); + } }; class npc_unworthy_initiate_anchor : public CreatureScript @@ -457,6 +457,7 @@ enum Spells_DKI //SPELL_DUEL_TRIGGERED = 52990, SPELL_DUEL_VICTORY = 52994, SPELL_DUEL_FLAG = 52991, + SPELL_GROVEL = 7267, }; enum Says_VBM @@ -494,8 +495,6 @@ public: creature->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC); creature->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_UNK_15); - sCreatureTextMgr->SendChat(creature, SAY_DUEL, NULL, CHAT_MSG_ADDON, LANG_ADDON, TEXT_RANGE_NORMAL, 0, TEAM_OTHER, false, player); - player->CastSpell(creature, SPELL_DUEL, false); player->CastSpell(player, SPELL_DUEL_FLAG, true); } @@ -518,11 +517,6 @@ public: return true; } - CreatureAI* GetAI(Creature* creature) const override - { - return new npc_death_knight_initiateAI(creature); - } - struct npc_death_knight_initiateAI : public CombatAI { npc_death_knight_initiateAI(Creature* creature) : CombatAI(creature) @@ -557,6 +551,7 @@ public: if (!m_bIsDuelInProgress && pSpell->Id == SPELL_DUEL) { m_uiDuelerGUID = pCaster->GetGUID(); + Talk(SAY_DUEL, pCaster); m_bIsDuelInProgress = true; } } @@ -577,7 +572,7 @@ public: pDoneBy->AttackStop(); me->CastSpell(pDoneBy, SPELL_DUEL_VICTORY, true); lose = true; - me->CastSpell(me, 7267, true); + me->CastSpell(me, SPELL_GROVEL, true); me->RestoreFaction(); } } @@ -607,13 +602,13 @@ public: { if (lose) { - if (!me->HasAura(7267)) + if (!me->HasAura(SPELL_GROVEL)) EnterEvadeMode(); return; } else if (me->GetVictim() && me->EnsureVictim()->GetTypeId() == TYPEID_PLAYER && me->EnsureVictim()->HealthBelowPct(10)) { - me->EnsureVictim()->CastSpell(me->GetVictim(), 7267, true); // beg + me->EnsureVictim()->CastSpell(me->GetVictim(), SPELL_GROVEL, true); // beg me->EnsureVictim()->RemoveGameObject(SPELL_DUEL_FLAG, true); EnterEvadeMode(); return; @@ -626,6 +621,10 @@ public: } }; + CreatureAI* GetAI(Creature* creature) const override + { + return new npc_death_knight_initiateAI(creature); + } }; /*###### diff --git a/src/server/scripts/Northrend/Naxxramas/boss_heigan.cpp b/src/server/scripts/Northrend/Naxxramas/boss_heigan.cpp index 3d7c128c8dd..5248c48029c 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_heigan.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_heigan.cpp @@ -21,36 +21,42 @@ #include "naxxramas.h" #include "Player.h" -enum Heigan +enum Spells { - SPELL_DECREPIT_FEVER = 29998, // 25-man: 55011 - SPELL_SPELL_DISRUPTION = 29310, - SPELL_PLAGUE_CLOUD = 29350, - - SAY_AGGRO = 0, - SAY_SLAY = 1, - SAY_TAUNT = 2, - SAY_DEATH = 3 + SPELL_DECREPIT_FEVER = 29998, // 25-man: 55011 + SPELL_SPELL_DISRUPTION = 29310, + SPELL_PLAGUE_CLOUD = 29350, + SPELL_TELEPORT_SELF = 30211, +}; + +enum Yells +{ + SAY_AGGRO = 0, + SAY_SLAY = 1, + SAY_TAUNT = 2, + SAY_DEATH = 3, + + EMOTE_DANCE = 4, + EMOTE_DANCE_END = 5, }; enum Events { - EVENT_NONE, - EVENT_DISRUPT, + EVENT_DISRUPT = 1, EVENT_FEVER, EVENT_ERUPT, - EVENT_PHASE, + EVENT_DANCE, + EVENT_DANCE_END }; enum Phases { PHASE_FIGHT = 1, - PHASE_DANCE, + PHASE_DANCE }; enum Misc { - ACTION_SAFETY_DANCE_FAIL = 1, DATA_SAFETY_DANCE = 19962139 }; @@ -66,39 +72,25 @@ public: struct boss_heiganAI : public BossAI { - boss_heiganAI(Creature* creature) : BossAI(creature, BOSS_HEIGAN) + boss_heiganAI(Creature* creature) : BossAI(creature, BOSS_HEIGAN), eruptSection(0), eruptDirection(false), safetyDance(false) { } + + void Reset() override { - eruptSection = 0; - eruptDirection = false; - safetyDance = false; - phase = PHASE_FIGHT; + me->SetReactState(REACT_AGGRESSIVE); + _Reset(); } - uint32 eruptSection; - bool eruptDirection; - bool safetyDance; - Phases phase; - void KilledUnit(Unit* who) override { - if (!(rand32() % 5)) - Talk(SAY_SLAY); + Talk(SAY_SLAY); + if (who->GetTypeId() == TYPEID_PLAYER) safetyDance = false; } - void SetData(uint32 id, uint32 data) override - { - if (id == DATA_SAFETY_DANCE) - safetyDance = data ? true : false; - } - uint32 GetData(uint32 type) const override { - if (type == DATA_SAFETY_DANCE) - return safetyDance ? 1 : 0; - - return 0; + return (type == DATA_SAFETY_DANCE && safetyDance) ? 1u : 0u; } void JustDied(Unit* /*killer*/) override @@ -111,35 +103,14 @@ public: { _EnterCombat(); Talk(SAY_AGGRO); - EnterPhase(PHASE_FIGHT); - safetyDance = true; - } - - void EnterPhase(Phases newPhase) - { - phase = newPhase; - events.Reset(); + eruptSection = 3; - if (phase == PHASE_FIGHT) - { - events.ScheduleEvent(EVENT_DISRUPT, urand(10000, 25000)); - events.ScheduleEvent(EVENT_FEVER, urand(15000, 20000)); - events.ScheduleEvent(EVENT_PHASE, 90000); - events.ScheduleEvent(EVENT_ERUPT, 15000); - me->GetMotionMaster()->MoveChase(me->GetVictim()); - } - else - { - float x, y, z, o; - me->GetHomePosition(x, y, z, o); - me->NearTeleportTo(x, y, z, o - (float(M_PI) / 2)); - me->GetMotionMaster()->Clear(); - me->GetMotionMaster()->MoveIdle(); - me->SetTarget(ObjectGuid::Empty); - DoCastAOE(SPELL_PLAGUE_CLOUD); - events.ScheduleEvent(EVENT_PHASE, 45000); - events.ScheduleEvent(EVENT_ERUPT, 8000); - } + events.ScheduleEvent(EVENT_DISRUPT, urand(15 * IN_MILLISECONDS, 20 * IN_MILLISECONDS), 0, PHASE_FIGHT); + events.ScheduleEvent(EVENT_FEVER, urand(10 * IN_MILLISECONDS, 20 * IN_MILLISECONDS), 0, PHASE_FIGHT); + events.ScheduleEvent(EVENT_DANCE, 90 * IN_MILLISECONDS, 0, PHASE_FIGHT); + events.ScheduleEvent(EVENT_ERUPT, 15 * IN_MILLISECONDS, 0, PHASE_FIGHT); + + safetyDance = true; } void UpdateAI(uint32 diff) override @@ -155,15 +126,36 @@ public: { case EVENT_DISRUPT: DoCastAOE(SPELL_SPELL_DISRUPTION); - events.ScheduleEvent(EVENT_DISRUPT, urand(5000, 10000)); + events.ScheduleEvent(EVENT_DISRUPT, 11 * IN_MILLISECONDS); break; case EVENT_FEVER: DoCastAOE(SPELL_DECREPIT_FEVER); - events.ScheduleEvent(EVENT_FEVER, urand(20000, 25000)); + events.ScheduleEvent(EVENT_FEVER, urand(20 * IN_MILLISECONDS, 25 * IN_MILLISECONDS)); + break; + case EVENT_DANCE: + events.SetPhase(PHASE_DANCE); + Talk(SAY_TAUNT); + Talk(EMOTE_DANCE); + eruptSection = 3; + me->SetReactState(REACT_PASSIVE); + me->AttackStop(); + me->StopMoving(); + DoCast(SPELL_TELEPORT_SELF); + DoCastAOE(SPELL_PLAGUE_CLOUD); + events.ScheduleEvent(EVENT_DANCE_END, 45 * IN_MILLISECONDS, 0, PHASE_DANCE); + events.ScheduleEvent(EVENT_ERUPT, 10 * IN_MILLISECONDS); break; - case EVENT_PHASE: - /// @todo Add missing texts for both phase switches - EnterPhase(phase == PHASE_FIGHT ? PHASE_DANCE : PHASE_FIGHT); + case EVENT_DANCE_END: + events.SetPhase(PHASE_FIGHT); + Talk(EMOTE_DANCE_END); + eruptSection = 3; + events.ScheduleEvent(EVENT_DISRUPT, urand(10, 25) * IN_MILLISECONDS, 0, PHASE_FIGHT); + events.ScheduleEvent(EVENT_FEVER, urand(15, 20) * IN_MILLISECONDS, 0, PHASE_FIGHT); + events.ScheduleEvent(EVENT_DANCE, 90 * IN_MILLISECONDS, 0, PHASE_FIGHT); + events.ScheduleEvent(EVENT_ERUPT, 15 * IN_MILLISECONDS, 0, PHASE_FIGHT); + me->CastStop(); + me->SetReactState(REACT_AGGRESSIVE); + DoZoneInCombat(); break; case EVENT_ERUPT: instance->SetData(DATA_HEIGAN_ERUPT, eruptSection); @@ -176,13 +168,22 @@ public: eruptDirection ? ++eruptSection : --eruptSection; - events.ScheduleEvent(EVENT_ERUPT, phase == PHASE_FIGHT ? 10000 : 3000); + if (events.IsInPhase(PHASE_DANCE)) + events.ScheduleEvent(EVENT_ERUPT, 3 * IN_MILLISECONDS, 0, PHASE_DANCE); + else + events.ScheduleEvent(EVENT_ERUPT, 10 * IN_MILLISECONDS, 0, PHASE_FIGHT); break; } } DoMeleeAttackIfReady(); } + + private: + uint32 eruptSection; + bool eruptDirection; + + bool safetyDance; // is achievement still possible? (= no player deaths yet) }; }; @@ -205,7 +206,7 @@ class spell_heigan_eruption : public SpellScriptLoader if (GetHitDamage() >= int32(GetHitPlayer()->GetHealth())) if (InstanceScript* instance = caster->GetInstanceScript()) if (Creature* Heigan = ObjectAccessor::GetCreature(*caster, instance->GetGuidData(DATA_HEIGAN))) - Heigan->AI()->SetData(DATA_SAFETY_DANCE, 0); + Heigan->AI()->KilledUnit(GetHitPlayer()); } void Register() override @@ -223,9 +224,7 @@ class spell_heigan_eruption : public SpellScriptLoader class achievement_safety_dance : public AchievementCriteriaScript { public: - achievement_safety_dance() : AchievementCriteriaScript("achievement_safety_dance") - { - } + achievement_safety_dance() : AchievementCriteriaScript("achievement_safety_dance") { } bool OnCheck(Player* /*player*/, Unit* target) override { diff --git a/src/server/scripts/Northrend/Naxxramas/boss_loatheb.cpp b/src/server/scripts/Northrend/Naxxramas/boss_loatheb.cpp index 33fb43b6bbc..494c173f5fc 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_loatheb.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_loatheb.cpp @@ -27,7 +27,10 @@ enum Spells SPELL_WARN_NECROTIC_AURA = 59481, SPELL_SUMMON_SPORE = 29234, SPELL_DEATHBLOOM = 29865, - SPELL_INEVITABLE_DOOM = 29204 + SPELL_INEVITABLE_DOOM = 29204, + SPELL_FUNGAL_CREEP = 29232, + + SPELL_DEATHBLOOM_FINAL_DAMAGE = 55594, }; enum Texts @@ -72,29 +75,35 @@ class boss_loatheb : public CreatureScript void Reset() override { _Reset(); + instance->DoRemoveAurasDueToSpellOnPlayers(SPELL_FUNGAL_CREEP); Initialize(); } void EnterCombat(Unit* /*who*/) override { _EnterCombat(); - events.ScheduleEvent(EVENT_NECROTIC_AURA, 17000); - events.ScheduleEvent(EVENT_DEATHBLOOM, 5000); - events.ScheduleEvent(EVENT_SPORE, IsHeroic() ? 18000 : 36000); - events.ScheduleEvent(EVENT_INEVITABLE_DOOM, 120000); + events.ScheduleEvent(EVENT_NECROTIC_AURA, 17 * IN_MILLISECONDS); + events.ScheduleEvent(EVENT_DEATHBLOOM, 5 * IN_MILLISECONDS); + events.ScheduleEvent(EVENT_SPORE, RAID_MODE(36,18) * IN_MILLISECONDS); + events.ScheduleEvent(EVENT_INEVITABLE_DOOM, 2 * MINUTE * IN_MILLISECONDS); } - void SummonedCreatureDies(Creature* /*summon*/, Unit* /*killer*/) override + void SummonedCreatureDies(Creature* summon, Unit* /*killer*/) override { _sporeLoserData = false; + summon->CastSpell(summon,SPELL_FUNGAL_CREEP,true); } - uint32 GetData(uint32 id) const override + void SummonedCreatureDespawn(Creature* summon) override { - if (id != DATA_ACHIEVEMENT_SPORE_LOSER) - return 0; + summons.Despawn(summon); + if (summon->IsAlive()) + summon->CastSpell(summon,SPELL_FUNGAL_CREEP,true); + } - return uint32(_sporeLoserData); + uint32 GetData(uint32 id) const override + { + return (_sporeLoserData && id == DATA_ACHIEVEMENT_SPORE_LOSER) ? 1u : 0u; } void UpdateAI(uint32 diff) override @@ -111,21 +120,29 @@ class boss_loatheb : public CreatureScript case EVENT_NECROTIC_AURA: DoCastAOE(SPELL_NECROTIC_AURA); DoCast(me, SPELL_WARN_NECROTIC_AURA); - events.ScheduleEvent(EVENT_NECROTIC_AURA, 20000); - events.ScheduleEvent(EVENT_NECROTIC_AURA_FADING, 14000); + events.ScheduleEvent(EVENT_NECROTIC_AURA, 20 * IN_MILLISECONDS); + events.ScheduleEvent(EVENT_NECROTIC_AURA_FADING, 14 * IN_MILLISECONDS); break; case EVENT_DEATHBLOOM: DoCastAOE(SPELL_DEATHBLOOM); - events.ScheduleEvent(EVENT_DEATHBLOOM, 30000); + events.ScheduleEvent(EVENT_DEATHBLOOM, 30 * IN_MILLISECONDS); break; case EVENT_INEVITABLE_DOOM: _doomCounter++; DoCastAOE(SPELL_INEVITABLE_DOOM); - events.ScheduleEvent(EVENT_INEVITABLE_DOOM, std::max(120000 - _doomCounter * 15000, 15000)); // needs to be confirmed + if (_doomCounter > 6) + { + if (_doomCounter & 1) // odd + events.ScheduleEvent(EVENT_INEVITABLE_DOOM, 14 * IN_MILLISECONDS); + else + events.ScheduleEvent(EVENT_INEVITABLE_DOOM, 17 * IN_MILLISECONDS); + } + else + events.ScheduleEvent(EVENT_INEVITABLE_DOOM, 30 * IN_MILLISECONDS); break; case EVENT_SPORE: DoCast(me, SPELL_SUMMON_SPORE, false); - events.ScheduleEvent(EVENT_SPORE, IsHeroic() ? 18000 : 36000); + events.ScheduleEvent(EVENT_SPORE, RAID_MODE(36,18) * IN_MILLISECONDS); break; case EVENT_NECROTIC_AURA_FADING: Talk(SAY_NECROTIC_AURA_FADING); @@ -203,9 +220,46 @@ class spell_loatheb_necrotic_aura_warning : public SpellScriptLoader } }; +class spell_loatheb_deathbloom : public SpellScriptLoader +{ + public: + spell_loatheb_deathbloom() : SpellScriptLoader("spell_loatheb_deathbloom") { } + + class spell_loatheb_deathbloom_AuraScript : public AuraScript + { + PrepareAuraScript(spell_loatheb_deathbloom_AuraScript); + + bool Validate(SpellInfo const* /*spell*/) override + { + if (!sSpellMgr->GetSpellInfo(SPELL_DEATHBLOOM_FINAL_DAMAGE)) + return false; + return true; + } + + void AfterRemove(AuraEffect const* eff, AuraEffectHandleModes /*mode*/) + { + if (GetTargetApplication()->GetRemoveMode() != AURA_REMOVE_BY_EXPIRE) + return; + + GetTarget()->CastSpell(nullptr, SPELL_DEATHBLOOM_FINAL_DAMAGE, true, nullptr, eff, GetCasterGUID()); + } + + void Register() override + { + AfterEffectRemove += AuraEffectRemoveFn(spell_loatheb_deathbloom_AuraScript::AfterRemove, EFFECT_0, SPELL_AURA_PERIODIC_DAMAGE, AURA_EFFECT_HANDLE_REAL); + } + }; + + AuraScript* GetAuraScript() const override + { + return new spell_loatheb_deathbloom_AuraScript(); + } +}; + void AddSC_boss_loatheb() { new boss_loatheb(); new achievement_spore_loser(); new spell_loatheb_necrotic_aura_warning(); + new spell_loatheb_deathbloom(); } diff --git a/src/server/scripts/Northrend/Naxxramas/boss_noth.cpp b/src/server/scripts/Northrend/Naxxramas/boss_noth.cpp index f5e5b287571..810d446111a 100644 --- a/src/server/scripts/Northrend/Naxxramas/boss_noth.cpp +++ b/src/server/scripts/Northrend/Naxxramas/boss_noth.cpp @@ -19,51 +19,58 @@ #include "ScriptedCreature.h" #include "naxxramas.h" -enum Noth +enum Phases { - SAY_AGGRO = 0, - SAY_SUMMON = 1, - SAY_SLAY = 2, - SAY_DEATH = 3, - - SOUND_DEATH = 8848, - - SPELL_CURSE_PLAGUEBRINGER = 29213, // 25-man: 54835 - SPELL_CRIPPLE = 29212, // 25-man: 54814 - SPELL_TELEPORT = 29216, - - NPC_WARRIOR = 16984, - NPC_CHAMPION = 16983, - NPC_GUARDIAN = 16981 + PHASE_NONE, + PHASE_GROUND, + PHASE_BALCONY }; -#define SPELL_BLINK RAND(29208, 29209, 29210, 29211) +enum Events +{ + EVENT_CURSE = 1, // curse of the plaguebringer + EVENT_BLINK, // blink (25m only) + EVENT_WARRIOR, // summon warriors during ground phase + EVENT_BALCONY, // become untargetable and begin balcony phase + EVENT_BALCONY_TELEPORT, // actually teleport to balcony, this is slightly delayed + EVENT_WAVE, // spawn wave during balcony phase + EVENT_GROUND, // end balcony phase and teleport to ground + EVENT_GROUND_ATTACKABLE // become attackable and aggressive again at start of ground phase, once again slightly delayed to prevent motionmaster weirdness +}; -// Teleport position of Noth on his balcony -Position const Teleport = { 2631.370f, -3529.680f, 274.040f, 6.277f }; +enum Talk +{ + SAY_AGGRO = 0, + SAY_SUMMON = 1, + SAY_SLAY = 2, + SAY_DEATH = 3, -#define MAX_SUMMON_POS 5 + EMOTE_SUMMON = 4, // ground phase + EMOTE_SUMMON_WAVE = 5, // balcony phase + EMOTE_TELEPORT_1 = 6, // ground to balcony + EMOTE_TELEPORT_2 = 7 // balcony to ground +}; -Position const SummonPos[MAX_SUMMON_POS] = +enum Spells { - { 2728.12f, -3544.43f, 261.91f, 6.04f }, - { 2729.05f, -3544.47f, 261.91f, 5.58f }, - { 2728.24f, -3465.08f, 264.20f, 3.56f }, - { 2704.11f, -3456.81f, 265.53f, 4.51f }, - { 2663.56f, -3464.43f, 262.66f, 5.20f } + SPELL_CURSE = 29213, // 25-man: 54835 + SPELL_CRIPPLE = 29212, // 25-man: 54814 + + SPELL_TELEPORT = 29216, // ground to balcony + SPELL_TELEPORT_BACK = 29231 // balcony to ground }; -enum Events +enum Adds { - EVENT_NONE, - EVENT_BERSERK, - EVENT_CURSE, - EVENT_BLINK, - EVENT_WARRIOR, - EVENT_BALCONY, - EVENT_WAVE, - EVENT_GROUND + N_WARRIOR_SPELLS = 3, + N_CHAMPION_SPELLS = 6, + N_GUARDIAN_SPELLS = 3 }; +const uint32 SummonWarriorSpells[N_WARRIOR_SPELLS] = { 29247, 29248, 29249 }; +const uint32 SummonChampionSpells[N_CHAMPION_SPELLS] = { 29238, 29255, 29257, 29258, 29262, 29267 }; +const uint32 SummonGuardianSpells[N_GUARDIAN_SPELLS] = { 29239, 29256, 29268 }; + +#define SPELL_BLINK RAND(29208, 29209, 29210, 29211) class boss_noth : public CreatureScript { @@ -72,16 +79,38 @@ public: struct boss_nothAI : public BossAI { - boss_nothAI(Creature* creature) : BossAI(creature, BOSS_NOTH) + boss_nothAI(Creature* creature) : BossAI(creature, BOSS_NOTH), balconyCount(0), justBlinked(false) { - balconyCount = 0; - waveCount = 0; + std::copy(SummonWarriorSpells, SummonWarriorSpells + N_WARRIOR_SPELLS, _SummonWarriorSpells); + std::copy(SummonChampionSpells, SummonChampionSpells + N_CHAMPION_SPELLS, _SummonChampionSpells); + std::copy(SummonGuardianSpells, SummonGuardianSpells + N_GUARDIAN_SPELLS, _SummonGuardianSpells); + + events.SetPhase(PHASE_NONE); + } + + void EnterEvadeMode() override + { + Reset(); // teleport back first + _EnterEvadeMode(); } void Reset() override { - me->SetReactState(REACT_AGGRESSIVE); - me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE); + if (!me->IsAlive()) + return; + + // in case we reset during balcony phase + if (events.IsInPhase(PHASE_BALCONY)) + { + DoCastAOE(SPELL_TELEPORT_BACK); + me->SetReactState(REACT_AGGRESSIVE); + me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_NOT_SELECTABLE); + } + + balconyCount = 0; + events.SetPhase(PHASE_NONE); + justBlinked = false; + _Reset(); } @@ -89,31 +118,44 @@ public: { _EnterCombat(); Talk(SAY_AGGRO); - balconyCount = 0; EnterPhaseGround(); } void EnterPhaseGround() { - me->SetReactState(REACT_AGGRESSIVE); - me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE); + events.SetPhase(PHASE_GROUND); + DoZoneInCombat(); if (me->getThreatManager().isThreatListEmpty()) - EnterEvadeMode(); + Reset(); else { - events.ScheduleEvent(EVENT_BALCONY, 110000); - events.ScheduleEvent(EVENT_CURSE, 10000 + rand32() % 15000); - events.ScheduleEvent(EVENT_WARRIOR, 30000); + uint8 secondsGround; + switch (balconyCount) + { + case 0: + secondsGround = 90; + break; + case 1: + secondsGround = 110; + break; + case 2: + default: + secondsGround = 180; + } + events.ScheduleEvent(EVENT_GROUND_ATTACKABLE, 2 * IN_MILLISECONDS, 0, PHASE_GROUND); + events.ScheduleEvent(EVENT_BALCONY, secondsGround * IN_MILLISECONDS, 0, PHASE_GROUND); + events.ScheduleEvent(EVENT_CURSE, urand(10,25) * IN_MILLISECONDS, 0, PHASE_GROUND); + events.ScheduleEvent(EVENT_WARRIOR, urand(20,30) * IN_MILLISECONDS, 0, PHASE_GROUND); if (GetDifficulty() == RAID_DIFFICULTY_25MAN_NORMAL) - events.ScheduleEvent(EVENT_BLINK, urand(20000, 40000)); + events.ScheduleEvent(EVENT_BLINK, urand(20,30) * IN_MILLISECONDS, 0, PHASE_GROUND); } } - void KilledUnit(Unit* /*victim*/) override + void KilledUnit(Unit* victim) override { - if (!(rand32() % 5)) + if(victim->GetTypeId() == TYPEID_PLAYER) Talk(SAY_SLAY); } @@ -121,7 +163,7 @@ public: { summons.Summon(summon); summon->setActive(true); - summon->AI()->DoZoneInCombat(); + summon->AI()->DoZoneInCombat(nullptr, 250.0f); // specify range to cover entire room - default 50yd is not enough } void JustDied(Unit* /*killer*/) override @@ -130,10 +172,35 @@ public: Talk(SAY_DEATH); } - void SummonUndead(uint32 entry, uint32 num) + void DamageTaken(Unit* /*who*/, uint32& damage) // prevent noth from somehow dying in the balcony phase { - for (uint32 i = 0; i < num; ++i) - me->SummonCreature(entry, SummonPos[rand32() % MAX_SUMMON_POS], TEMPSUMMON_CORPSE_DESPAWN, 60000); + if (!events.IsInPhase(PHASE_BALCONY)) + return; + if (damage < me->GetHealth()) + return; + + me->SetHealth(1u); + damage = 0u; + } + + void HandleSummon(uint32* spellsList, const uint8 nSpells, uint8 num) + { // this ensures we do not spawn two mobs using the same spell (<=> in the same position) if we can help it + while (num) + for (uint8 it = 0; it < nSpells && num; ++it) + { + num--; + uint8 selected = urand(it, nSpells - 1); + DoCastAOE(spellsList[selected]); + if (selected != it) // shuffle the selected into the part of the array that is no longer being searched + std::swap(spellsList[selected], spellsList[it]); + } + } + + void CastSummon(uint8 nWarrior, uint8 nChampion, uint8 nGuardian) + { + HandleSummon(_SummonWarriorSpells, N_WARRIOR_SPELLS, nWarrior); + HandleSummon(_SummonChampionSpells, N_CHAMPION_SPELLS, nChampion); + HandleSummon(_SummonGuardianSpells, N_GUARDIAN_SPELLS, nGuardian); } void UpdateAI(uint32 diff) override @@ -151,72 +218,115 @@ public: switch (eventId) { case EVENT_CURSE: - DoCastAOE(SPELL_CURSE_PLAGUEBRINGER); - events.ScheduleEvent(EVENT_CURSE, urand(50000, 60000)); - return; + { + DoCastAOE(SPELL_CURSE); + events.ScheduleEvent(EVENT_CURSE, urand(50, 70) * IN_MILLISECONDS, 0, PHASE_GROUND); + break; + } case EVENT_WARRIOR: Talk(SAY_SUMMON); - SummonUndead(NPC_WARRIOR, RAID_MODE(2, 3)); - events.ScheduleEvent(EVENT_WARRIOR, 30000); - return; + Talk(EMOTE_SUMMON); + + CastSummon(RAID_MODE(2, 3), 0, 0); + + events.ScheduleEvent(EVENT_WARRIOR, 40 * IN_MILLISECONDS, 0, PHASE_GROUND); + break; case EVENT_BLINK: DoCastAOE(SPELL_CRIPPLE, true); DoCastAOE(SPELL_BLINK); DoResetThreat(); - events.ScheduleEvent(EVENT_BLINK, 40000); - return; + justBlinked = true; + + events.ScheduleEvent(EVENT_BLINK, 40000, 0, PHASE_GROUND); + break; case EVENT_BALCONY: + events.SetPhase(PHASE_BALCONY); me->SetReactState(REACT_PASSIVE); - me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE); + me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_NOT_SELECTABLE); me->AttackStop(); + me->StopMoving(); me->RemoveAllAuras(); - me->NearTeleportTo(Teleport.GetPositionX(), Teleport.GetPositionY(), Teleport.GetPositionZ(), Teleport.GetOrientation()); - events.Reset(); - events.ScheduleEvent(EVENT_WAVE, urand(2000, 5000)); - waveCount = 0; - return; + + events.ScheduleEvent(EVENT_BALCONY_TELEPORT, 3 * IN_MILLISECONDS, 0, PHASE_BALCONY); + events.ScheduleEvent(EVENT_WAVE, urand(5 * IN_MILLISECONDS, 8 * IN_MILLISECONDS), 0, PHASE_BALCONY); + + uint8 secondsBalcony; + switch (balconyCount) + { + case 0: + secondsBalcony = 70; + break; + case 1: + secondsBalcony = 97; + break; + case 2: + default: + secondsBalcony = 120; + break; + } + events.ScheduleEvent(EVENT_GROUND, secondsBalcony * IN_MILLISECONDS, 0, PHASE_BALCONY); + break; + case EVENT_BALCONY_TELEPORT: + Talk(EMOTE_TELEPORT_1); + DoCastAOE(SPELL_TELEPORT); + break; case EVENT_WAVE: - Talk(SAY_SUMMON); + Talk(EMOTE_SUMMON_WAVE); switch (balconyCount) { case 0: - SummonUndead(NPC_CHAMPION, RAID_MODE(2, 4)); + CastSummon(0, RAID_MODE(2, 4), 0); break; case 1: - SummonUndead(NPC_CHAMPION, RAID_MODE(1, 2)); - SummonUndead(NPC_GUARDIAN, RAID_MODE(1, 2)); + CastSummon(0, RAID_MODE(1, 2), RAID_MODE(1, 2)); break; case 2: - SummonUndead(NPC_GUARDIAN, RAID_MODE(2, 4)); + CastSummon(0, 0, RAID_MODE(2, 4)); break; default: - SummonUndead(NPC_CHAMPION, RAID_MODE(5, 10)); - SummonUndead(NPC_GUARDIAN, RAID_MODE(5, 10)); + CastSummon(0, RAID_MODE(5, 10), RAID_MODE(5, 10)); break; } - ++waveCount; - events.ScheduleEvent(waveCount < 2 ? EVENT_WAVE : EVENT_GROUND, urand(30000, 45000)); - return; + events.ScheduleEvent(EVENT_WAVE, urand(30, 45) * IN_MILLISECONDS, 0, PHASE_BALCONY); + break; case EVENT_GROUND: - { ++balconyCount; - float x, y, z, o; - me->GetHomePosition(x, y, z, o); - me->NearTeleportTo(x, y, z, o); - events.ScheduleEvent(EVENT_BALCONY, 110000); + + DoCastAOE(SPELL_TELEPORT_BACK); + Talk(EMOTE_TELEPORT_2); + EnterPhaseGround(); - return; - } + break; + case EVENT_GROUND_ATTACKABLE: + me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_NOT_SELECTABLE); + me->SetReactState(REACT_AGGRESSIVE); + break; } } - if (me->HasReactState(REACT_AGGRESSIVE)) - DoMeleeAttackIfReady(); + if (events.IsInPhase(PHASE_GROUND)) + { + /* workaround for movechase breaking after blinking + without this noth would just stand there unless his current target moves */ + if (justBlinked && me->GetVictim() && !me->IsWithinMeleeRange(me->EnsureVictim())) + { + me->GetMotionMaster()->Clear(); + me->GetMotionMaster()->MoveChase(me->EnsureVictim()); + justBlinked = false; + } + else + DoMeleeAttackIfReady(); + } } private: - uint32 waveCount; uint32 balconyCount; + + bool justBlinked; + + uint32 _SummonWarriorSpells[N_WARRIOR_SPELLS]; + uint32 _SummonChampionSpells[N_CHAMPION_SPELLS]; + uint32 _SummonGuardianSpells[N_GUARDIAN_SPELLS]; }; CreatureAI* GetAI(Creature* creature) const override diff --git a/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp b/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp index 3a431ab3ac0..692e6d8b589 100644 --- a/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp +++ b/src/server/scripts/Northrend/Naxxramas/instance_naxxramas.cpp @@ -78,12 +78,13 @@ ObjectData const objectData[] = { 0, 0, } }; -float const HeiganPos[2] = { 2796.0f, -3707.0f }; +// from P2 teleport spell stored target +float const HeiganPos[2] = { 2793.86f, -3707.38f }; float const HeiganEruptionSlope[3] = { - (-3685.0f - HeiganPos[1]) / (2724.0f - HeiganPos[0]), - (-3647.0f - HeiganPos[1]) / (2749.0f - HeiganPos[0]), - (-3637.0f - HeiganPos[1]) / (2771.0f - HeiganPos[0]) + (-3703.303223f - HeiganPos[1]) / (2777.494141f - HeiganPos[0]), // between right center and far right + (-3696.948242f - HeiganPos[1]) / (2785.624268f - HeiganPos[0]), // between left and right halves + (-3691.880615f - HeiganPos[1]) / (2790.280029f - HeiganPos[0]) // between far left and left center }; // 0 H x @@ -246,7 +247,6 @@ class instance_naxxramas : public InstanceMapScript if (go->GetGOInfo()->displayId == 6785 || go->GetGOInfo()->displayId == 1287) { uint32 section = GetEruptionSection(go->GetPositionX(), go->GetPositionY()); - HeiganEruptionGUID[section].erase(go->GetGUID()); return; } @@ -552,7 +552,7 @@ class instance_naxxramas : public InstanceMapScript // This Function is called in CheckAchievementCriteriaMeet and CheckAchievementCriteriaMeet is called before SetBossState(bossId, DONE), // so to check if all bosses are done the checker must exclude 1 boss, the last done, if there is at most 1 encouter in progress when is // called this function then all bosses are done. The one boss that check is the boss that calls this function, so it is dead. - bool AreAllEncoutersDone() + bool AreAllEncountersDone() { uint32 numBossAlive = 0; for (uint32 i = 0; i < EncounterCount; ++i) @@ -589,7 +589,7 @@ class instance_naxxramas : public InstanceMapScript case 13239: // Loatheb case 13240: // Thaddius case 7617: // Kel'Thuzad - if (AreAllEncoutersDone() && !playerDied) + if (AreAllEncountersDone() && !playerDied) return true; return false; } diff --git a/src/server/scripts/Spells/spell_generic.cpp b/src/server/scripts/Spells/spell_generic.cpp index 0ab7c2fcb54..ca8c9a9ef93 100644 --- a/src/server/scripts/Spells/spell_generic.cpp +++ b/src/server/scripts/Spells/spell_generic.cpp @@ -3424,7 +3424,7 @@ class spell_gen_turkey_marker : public SpellScriptLoader void Register() override { - AfterEffectApply += AuraEffectApplyFn(spell_gen_turkey_marker_AuraScript::OnApply, EFFECT_0, SPELL_AURA_PERIODIC_DUMMY, AURA_EFFECT_HANDLE_REAL); + AfterEffectApply += AuraEffectApplyFn(spell_gen_turkey_marker_AuraScript::OnApply, EFFECT_0, SPELL_AURA_PERIODIC_DUMMY, AURA_EFFECT_HANDLE_REAL_OR_REAPPLY_MASK); OnEffectPeriodic += AuraEffectPeriodicFn(spell_gen_turkey_marker_AuraScript::OnPeriodic, EFFECT_0, SPELL_AURA_PERIODIC_DUMMY); } diff --git a/src/server/scripts/World/npc_professions.cpp b/src/server/scripts/World/npc_professions.cpp index 44411b20aaf..9c05e4e2c08 100644 --- a/src/server/scripts/World/npc_professions.cpp +++ b/src/server/scripts/World/npc_professions.cpp @@ -179,6 +179,52 @@ enum ProfessionSpells }; /*### +# specialization trainers +###*/ +enum SpecializationTrainers +{ + /* Alchemy */ + N_TRAINER_TRANSMUTE = 22427, // Zarevhi + N_TRAINER_ELIXIR = 19052, // Lorokeem + N_TRAINER_POTION = 17909, // Lauranna Thar'well + + /* Blacksmithing */ + N_TRAINER_SMITHOMNI1 = 11145, // Myolor Sunderfury + N_TRAINER_SMITHOMNI2 = 11176, // Krathok Moltenfist + N_TRAINER_WEAPON1 = 11146, // Ironus Coldsteel + N_TRAINER_WEAPON2 = 11178, // Borgosh Corebender + N_TRAINER_ARMOR1 = 5164, // Grumnus Steelshaper + N_TRAINER_ARMOR2 = 11177, // Okothos Ironrager + N_TRAINER_HAMMER = 11191, // Lilith the Lithe + N_TRAINER_AXE = 11192, // Kilram + N_TRAINER_SWORD = 11193, // Seril Scourgebane + + /* Leatherworking */ + N_TRAINER_DRAGON1 = 7866, // Peter Galen + N_TRAINER_DRAGON2 = 7867, // Thorkaf Dragoneye + N_TRAINER_ELEMENTAL1 = 7868, // Sarah Tanner + N_TRAINER_ELEMENTAL2 = 7869, // Brumn Winterhoof + N_TRAINER_TRIBAL1 = 7870, // Caryssia Moonhunter + N_TRAINER_TRIBAL2 = 7871, // Se'Jib + + /* Tailoring */ + N_TRAINER_SPELLFIRE = 22213, // Gidge Spellweaver + N_TRAINER_MOONCLOTH = 22208, // Nasmara Moonsong + N_TRAINER_SHADOWEAVE = 22212, // Andrion Darkspinner +}; + +/*### +# specialization quests +###*/ +enum SpecializationQuests +{ + /* Alchemy */ + Q_MASTER_TRANSMUTE = 10899, + Q_MASTER_ELIXIR = 10902, + Q_MASTER_POTION = 10897, +}; + +/*### # formulas to calculate unlearning cost ###*/ @@ -395,23 +441,23 @@ public: if (player->HasSkill(SKILL_ALCHEMY) && player->GetBaseSkillValue(SKILL_ALCHEMY) >= 350 && player->getLevel() > 67) { - if (player->GetQuestRewardStatus(10899) || player->GetQuestRewardStatus(10902) || player->GetQuestRewardStatus(10897)) + if (player->GetQuestRewardStatus(Q_MASTER_TRANSMUTE) || player->GetQuestRewardStatus(Q_MASTER_ELIXIR) || player->GetQuestRewardStatus(Q_MASTER_POTION)) { switch (creature->GetEntry()) { - case 22427: //Zarevhi + case N_TRAINER_TRANSMUTE: //Zarevhi if (!HasAlchemySpell(player)) player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_LEARN_TRANSMUTE, GOSSIP_SENDER_LEARN, GOSSIP_ACTION_INFO_DEF + 1); if (player->HasSpell(S_TRANSMUTE)) player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_UNLEARN_TRANSMUTE, GOSSIP_SENDER_UNLEARN, GOSSIP_ACTION_INFO_DEF + 4); break; - case 19052: //Lorokeem + case N_TRAINER_ELIXIR: //Lorokeem if (!HasAlchemySpell(player)) player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_LEARN_ELIXIR, GOSSIP_SENDER_LEARN, GOSSIP_ACTION_INFO_DEF + 2); if (player->HasSpell(S_ELIXIR)) player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_UNLEARN_ELIXIR, GOSSIP_SENDER_UNLEARN, GOSSIP_ACTION_INFO_DEF + 5); break; - case 17909: //Lauranna Thar'well + case N_TRAINER_POTION: //Lauranna Thar'well if (!HasAlchemySpell(player)) player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_LEARN_POTION, GOSSIP_SENDER_LEARN, GOSSIP_ACTION_INFO_DEF + 3); if (player->HasSpell(S_POTION)) @@ -464,17 +510,17 @@ public: { switch (creature->GetEntry()) { - case 22427: + case N_TRAINER_TRANSMUTE: player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_LEARN_TRANSMUTE, GOSSIP_SENDER_CHECK, action); //unknown textID () player->SEND_GOSSIP_MENU(player->GetGossipTextId(creature), creature->GetGUID()); break; - case 19052: + case N_TRAINER_ELIXIR: player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_LEARN_ELIXIR, GOSSIP_SENDER_CHECK, action); //unknown textID () player->SEND_GOSSIP_MENU(player->GetGossipTextId(creature), creature->GetGUID()); break; - case 17909: + case N_TRAINER_POTION: player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_LEARN_POTION, GOSSIP_SENDER_CHECK, action); //unknown textID () player->SEND_GOSSIP_MENU(player->GetGossipTextId(creature), creature->GetGUID()); @@ -489,17 +535,17 @@ public: { switch (creature->GetEntry()) { - case 22427: //Zarevhi + case N_TRAINER_TRANSMUTE: player->ADD_GOSSIP_ITEM_EXTENDED(0, GOSSIP_UNLEARN_TRANSMUTE, GOSSIP_SENDER_CHECK, action, BOX_UNLEARN_ALCHEMY_SPEC, DoHighUnlearnCost(player), false); //unknown textID () player->SEND_GOSSIP_MENU(player->GetGossipTextId(creature), creature->GetGUID()); break; - case 19052: //Lorokeem + case N_TRAINER_ELIXIR: player->ADD_GOSSIP_ITEM_EXTENDED(0, GOSSIP_UNLEARN_ELIXIR, GOSSIP_SENDER_CHECK, action, BOX_UNLEARN_ALCHEMY_SPEC, DoHighUnlearnCost(player), false); //unknown textID () player->SEND_GOSSIP_MENU(player->GetGossipTextId(creature), creature->GetGUID()); break; - case 17909: //Lauranna Thar'well + case N_TRAINER_POTION: player->ADD_GOSSIP_ITEM_EXTENDED(0, GOSSIP_UNLEARN_POTION, GOSSIP_SENDER_CHECK, action, BOX_UNLEARN_ALCHEMY_SPEC, DoHighUnlearnCost(player), false); //unknown textID () player->SEND_GOSSIP_MENU(player->GetGossipTextId(creature), creature->GetGUID()); @@ -564,20 +610,20 @@ public: { switch (creatureId) { - case 11145: //Myolor Sunderfury - case 11176: //Krathok Moltenfist + case N_TRAINER_SMITHOMNI1: + case N_TRAINER_SMITHOMNI2: if (!player->HasSpell(S_ARMOR) && !player->HasSpell(S_WEAPON) && player->GetReputationRank(REP_ARMOR) >= REP_FRIENDLY) player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_ARMOR_LEARN, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1); if (!player->HasSpell(S_WEAPON) && !player->HasSpell(S_ARMOR) && player->GetReputationRank(REP_WEAPON) >= REP_FRIENDLY) player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_WEAPON_LEARN, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 2); break; - case 11146: //Ironus Coldsteel - case 11178: //Borgosh Corebender + case N_TRAINER_WEAPON1: + case N_TRAINER_WEAPON2: if (player->HasSpell(S_WEAPON)) player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_WEAPON_UNLEARN, GOSSIP_SENDER_UNLEARN, GOSSIP_ACTION_INFO_DEF + 3); break; - case 5164: //Grumnus Steelshaper - case 11177: //Okothos Ironrager + case N_TRAINER_ARMOR1: + case N_TRAINER_ARMOR2: if (player->HasSpell(S_ARMOR)) player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_ARMOR_UNLEARN, GOSSIP_SENDER_UNLEARN, GOSSIP_ACTION_INFO_DEF + 4); break; @@ -588,19 +634,19 @@ public: { switch (creatureId) { - case 11191: //Lilith the Lithe + case N_TRAINER_HAMMER: if (!HasWeaponSub(player)) player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_LEARN_HAMMER, GOSSIP_SENDER_LEARN, GOSSIP_ACTION_INFO_DEF + 5); if (player->HasSpell(S_HAMMER)) player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_UNLEARN_HAMMER, GOSSIP_SENDER_UNLEARN, GOSSIP_ACTION_INFO_DEF + 8); break; - case 11192: //Kilram + case N_TRAINER_AXE: if (!HasWeaponSub(player)) player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_LEARN_AXE, GOSSIP_SENDER_LEARN, GOSSIP_ACTION_INFO_DEF + 6); if (player->HasSpell(S_AXE)) player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_UNLEARN_AXE, GOSSIP_SENDER_UNLEARN, GOSSIP_ACTION_INFO_DEF + 9); break; - case 11193: //Seril Scourgebane + case N_TRAINER_SWORD: if (!HasWeaponSub(player)) player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_LEARN_SWORD, GOSSIP_SENDER_LEARN, GOSSIP_ACTION_INFO_DEF + 7); if (player->HasSpell(S_SWORD)) @@ -685,17 +731,17 @@ public: { switch (creature->GetEntry()) { - case 11191: + case N_TRAINER_HAMMER: player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_LEARN_HAMMER, GOSSIP_SENDER_CHECK, action); //unknown textID (TALK_HAMMER_LEARN) player->SEND_GOSSIP_MENU(player->GetGossipTextId(creature), creature->GetGUID()); break; - case 11192: + case N_TRAINER_AXE: player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_LEARN_AXE, GOSSIP_SENDER_CHECK, action); //unknown textID (TALK_AXE_LEARN) player->SEND_GOSSIP_MENU(player->GetGossipTextId(creature), creature->GetGUID()); break; - case 11193: + case N_TRAINER_SWORD: player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_LEARN_SWORD, GOSSIP_SENDER_CHECK, action); //unknown textID (TALK_SWORD_LEARN) player->SEND_GOSSIP_MENU(player->GetGossipTextId(creature), creature->GetGUID()); @@ -710,26 +756,26 @@ public: { switch (creature->GetEntry()) { - case 11146: //Ironus Coldsteel - case 11178: //Borgosh Corebender - case 5164: //Grumnus Steelshaper - case 11177: //Okothos Ironrager + case N_TRAINER_WEAPON1: + case N_TRAINER_WEAPON2: + case N_TRAINER_ARMOR1: + case N_TRAINER_ARMOR2: player->ADD_GOSSIP_ITEM_EXTENDED(0, GOSSIP_UNLEARN_SMITH_SPEC, GOSSIP_SENDER_CHECK, action, BOX_UNLEARN_ARMORORWEAPON, DoLowUnlearnCost(player), false); //unknown textID (TALK_UNLEARN_AXEORWEAPON) player->SEND_GOSSIP_MENU(player->GetGossipTextId(creature), creature->GetGUID()); break; - case 11191: + case N_TRAINER_HAMMER: player->ADD_GOSSIP_ITEM_EXTENDED(0, GOSSIP_UNLEARN_HAMMER, GOSSIP_SENDER_CHECK, action, BOX_UNLEARN_WEAPON_SPEC, DoMedUnlearnCost(player), false); //unknown textID (TALK_HAMMER_UNLEARN) player->SEND_GOSSIP_MENU(player->GetGossipTextId(creature), creature->GetGUID()); break; - case 11192: + case N_TRAINER_AXE: player->ADD_GOSSIP_ITEM_EXTENDED(0, GOSSIP_UNLEARN_AXE, GOSSIP_SENDER_CHECK, action, BOX_UNLEARN_WEAPON_SPEC, DoMedUnlearnCost(player), false); //unknown textID (TALK_AXE_UNLEARN) player->SEND_GOSSIP_MENU(player->GetGossipTextId(creature), creature->GetGUID()); break; - case 11193: + case N_TRAINER_SWORD: player->ADD_GOSSIP_ITEM_EXTENDED(0, GOSSIP_UNLEARN_SWORD, GOSSIP_SENDER_CHECK, action, BOX_UNLEARN_WEAPON_SPEC, DoMedUnlearnCost(player), false); //unknown textID (TALK_SWORD_UNLEARN) player->SEND_GOSSIP_MENU(player->GetGossipTextId(creature), creature->GetGUID()); @@ -901,18 +947,18 @@ public: { switch (creature->GetEntry()) { - case 7866: //Peter Galen - case 7867: //Thorkaf Dragoneye + case N_TRAINER_DRAGON1: + case N_TRAINER_DRAGON2: if (player->HasSpell(S_DRAGON)) player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_UNLEARN_DRAGON, GOSSIP_SENDER_UNLEARN, GOSSIP_ACTION_INFO_DEF + 1); break; - case 7868: //Sarah Tanner - case 7869: //Brumn Winterhoof + case N_TRAINER_ELEMENTAL1: + case N_TRAINER_ELEMENTAL2: if (player->HasSpell(S_ELEMENTAL)) player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_UNLEARN_ELEMENTAL, GOSSIP_SENDER_UNLEARN, GOSSIP_ACTION_INFO_DEF + 2); break; - case 7870: //Caryssia Moonhunter - case 7871: //Se'Jib + case N_TRAINER_TRIBAL1: + case N_TRAINER_TRIBAL2: if (player->HasSpell(S_TRIBAL)) player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_UNLEARN_TRIBAL, GOSSIP_SENDER_UNLEARN, GOSSIP_ACTION_INFO_DEF + 3); break; @@ -952,20 +998,20 @@ public: { switch (creature->GetEntry()) { - case 7866: //Peter Galen - case 7867: //Thorkaf Dragoneye + case N_TRAINER_DRAGON1: + case N_TRAINER_DRAGON2: player->ADD_GOSSIP_ITEM_EXTENDED(0, GOSSIP_UNLEARN_DRAGON, GOSSIP_SENDER_CHECK, action, BOX_UNLEARN_LEATHER_SPEC, DoMedUnlearnCost(player), false); //unknown textID () player->SEND_GOSSIP_MENU(player->GetGossipTextId(creature), creature->GetGUID()); break; - case 7868: //Sarah Tanner - case 7869: //Brumn Winterhoof + case N_TRAINER_ELEMENTAL1: + case N_TRAINER_ELEMENTAL2: player->ADD_GOSSIP_ITEM_EXTENDED(0, GOSSIP_UNLEARN_ELEMENTAL, GOSSIP_SENDER_CHECK, action, BOX_UNLEARN_LEATHER_SPEC, DoMedUnlearnCost(player), false); //unknown textID () player->SEND_GOSSIP_MENU(player->GetGossipTextId(creature), creature->GetGUID()); break; - case 7870: //Caryssia Moonhunter - case 7871: //Se'Jib + case N_TRAINER_TRIBAL1: + case N_TRAINER_TRIBAL2: player->ADD_GOSSIP_ITEM_EXTENDED(0, GOSSIP_UNLEARN_TRIBAL, GOSSIP_SENDER_CHECK, action, BOX_UNLEARN_LEATHER_SPEC, DoMedUnlearnCost(player), false); //unknown textID () player->SEND_GOSSIP_MENU(player->GetGossipTextId(creature), creature->GetGUID()); @@ -1027,19 +1073,19 @@ public: { switch (creature->GetEntry()) { - case 22213: //Gidge Spellweaver + case N_TRAINER_SPELLFIRE: if (!HasTailorSpell(player)) player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_LEARN_SPELLFIRE, GOSSIP_SENDER_LEARN, GOSSIP_ACTION_INFO_DEF + 1); if (player->HasSpell(S_SPELLFIRE)) player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_UNLEARN_SPELLFIRE, GOSSIP_SENDER_UNLEARN, GOSSIP_ACTION_INFO_DEF + 4); break; - case 22208: //Nasmara Moonsong + case N_TRAINER_MOONCLOTH: if (!HasTailorSpell(player)) player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_LEARN_MOONCLOTH, GOSSIP_SENDER_LEARN, GOSSIP_ACTION_INFO_DEF + 2); if (player->HasSpell(S_MOONCLOTH)) player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_UNLEARN_MOONCLOTH, GOSSIP_SENDER_UNLEARN, GOSSIP_ACTION_INFO_DEF + 5); break; - case 22212: //Andrion Darkspinner + case N_TRAINER_SHADOWEAVE: if (!HasTailorSpell(player)) player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_LEARN_SHADOWEAVE, GOSSIP_SENDER_LEARN, GOSSIP_ACTION_INFO_DEF + 3); if (player->HasSpell(S_SHADOWEAVE)) @@ -1092,17 +1138,17 @@ public: { switch (creature->GetEntry()) { - case 22213: + case N_TRAINER_SPELLFIRE: player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_LEARN_SPELLFIRE, GOSSIP_SENDER_CHECK, action); //unknown textID () player->SEND_GOSSIP_MENU(player->GetGossipTextId(creature), creature->GetGUID()); break; - case 22208: + case N_TRAINER_MOONCLOTH: player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_LEARN_MOONCLOTH, GOSSIP_SENDER_CHECK, action); //unknown textID () player->SEND_GOSSIP_MENU(player->GetGossipTextId(creature), creature->GetGUID()); break; - case 22212: + case N_TRAINER_SHADOWEAVE: player->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, GOSSIP_LEARN_SHADOWEAVE, GOSSIP_SENDER_CHECK, action); //unknown textID () player->SEND_GOSSIP_MENU(player->GetGossipTextId(creature), creature->GetGUID()); @@ -1117,17 +1163,17 @@ public: { switch (creature->GetEntry()) { - case 22213: //Gidge Spellweaver + case N_TRAINER_SPELLFIRE: player->ADD_GOSSIP_ITEM_EXTENDED(0, GOSSIP_UNLEARN_SPELLFIRE, GOSSIP_SENDER_CHECK, action, BOX_UNLEARN_TAILOR_SPEC, DoHighUnlearnCost(player), false); //unknown textID () player->SEND_GOSSIP_MENU(player->GetGossipTextId(creature), creature->GetGUID()); break; - case 22208: //Nasmara Moonsong + case N_TRAINER_MOONCLOTH: player->ADD_GOSSIP_ITEM_EXTENDED(0, GOSSIP_UNLEARN_MOONCLOTH, GOSSIP_SENDER_CHECK, action, BOX_UNLEARN_TAILOR_SPEC, DoHighUnlearnCost(player), false); //unknown textID () player->SEND_GOSSIP_MENU(player->GetGossipTextId(creature), creature->GetGUID()); break; - case 22212: //Andrion Darkspinner + case N_TRAINER_SHADOWEAVE: player->ADD_GOSSIP_ITEM_EXTENDED(0, GOSSIP_UNLEARN_SHADOWEAVE, GOSSIP_SENDER_CHECK, action, BOX_UNLEARN_TAILOR_SPEC, DoHighUnlearnCost(player), false); //unknown textID () player->SEND_GOSSIP_MENU(player->GetGossipTextId(creature), creature->GetGUID()); |