aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/server/scripts/ExilesReach/exiles_reach_script_loader.cpp26
-rw-r--r--src/server/scripts/ExilesReach/zone_exiles_reach.cpp1069
2 files changed, 1095 insertions, 0 deletions
diff --git a/src/server/scripts/ExilesReach/exiles_reach_script_loader.cpp b/src/server/scripts/ExilesReach/exiles_reach_script_loader.cpp
new file mode 100644
index 00000000000..9aba87f489e
--- /dev/null
+++ b/src/server/scripts/ExilesReach/exiles_reach_script_loader.cpp
@@ -0,0 +1,26 @@
+/*
+ * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+// This is where scripts' loading functions should be declared:
+void AddSC_zone_exiles_reach();
+
+// The name of this function should match:
+// void Add${NameOfDirectory}Scripts()
+void AddExilesReachScripts()
+{
+ AddSC_zone_exiles_reach();
+}
diff --git a/src/server/scripts/ExilesReach/zone_exiles_reach.cpp b/src/server/scripts/ExilesReach/zone_exiles_reach.cpp
new file mode 100644
index 00000000000..072861e35b4
--- /dev/null
+++ b/src/server/scripts/ExilesReach/zone_exiles_reach.cpp
@@ -0,0 +1,1069 @@
+/*
+ * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "Conversation.h"
+#include "CreatureAIImpl.h"
+#include "Map.h"
+#include "Object.h"
+#include "Player.h"
+#include "CellImpl.h"
+#include "Containers.h"
+#include "MotionMaster.h"
+#include "ObjectAccessor.h"
+#include "ObjectMgr.h"
+#include "PassiveAI.h"
+#include "ScriptedCreature.h"
+#include "ScriptMgr.h"
+#include "ScriptSystem.h"
+#include "SpellInfo.h"
+#include "SpellScript.h"
+#include "TemporarySummon.h"
+#include "Transport.h"
+
+template<class privateAI, class publicAI>
+CreatureAI* GetPrivatePublicPairAISelector(Creature* creature)
+{
+ if (creature->IsPrivateObject())
+ return new privateAI(creature);
+ return new publicAI(creature);
+}
+
+#define RegisterPrivatePublicCreatureAIPair(scriptName, privateAI, publicAI) new FactoryCreatureScript<CreatureAI, &GetPrivatePublicPairAISelector<privateAI, publicAI>>(scriptName);
+
+static Creature* FindCreatureIgnorePhase(WorldObject const* obj, std::string_view stringId, float range = 100.0f)
+{
+ return obj->FindNearestCreatureWithOptions(range, FindCreatureOptions().SetIgnorePhases(true).SetStringId(stringId));
+}
+
+ // ********************************************
+ // * Scripting in this section occurs on ship *
+ // ********************************************
+
+enum AttentionExilesReachData
+{
+ SPELL_DEBUG_LOOK_RIGHT = 290903
+};
+
+// 290901 - Attention!
+class spell_attention_exiles_reach_tutorial : public AuraScript
+{
+ PrepareAuraScript(spell_attention_exiles_reach_tutorial);
+
+ bool Validate(SpellInfo const* /*spellInfo*/) override
+ {
+ return ValidateSpellInfo({ SPELL_DEBUG_LOOK_RIGHT });
+ }
+
+ void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
+ {
+ GetTarget()->CastSpell(GetTarget(), SPELL_DEBUG_LOOK_RIGHT, true);
+ }
+
+ void Register() override
+ {
+ AfterEffectRemove += AuraEffectRemoveFn(spell_attention_exiles_reach_tutorial::OnRemove, EFFECT_0, SPELL_AURA_MOD_ROOT, AURA_EFFECT_HANDLE_REAL);
+ }
+};
+
+enum WarmingUpData
+{
+ CONVERSATION_WARMING_UP_ACCEPT = 12818,
+ CONVERSATION_WARMING_UP_COMPLETE = 12798,
+};
+
+class BaseQuestWarmingUp : public QuestScript
+{
+public:
+ BaseQuestWarmingUp(const char* name) : QuestScript(name) { }
+
+ void OnQuestStatusChange(Player* player, Quest const* /*quest*/, QuestStatus /*oldStatus*/, QuestStatus newStatus)
+ {
+ if (newStatus == QUEST_STATUS_INCOMPLETE)
+ Conversation::CreateConversation(CONVERSATION_WARMING_UP_ACCEPT, player, *player, player->GetGUID(), nullptr);
+ else if (newStatus == QUEST_STATUS_COMPLETE)
+ Conversation::CreateConversation(CONVERSATION_WARMING_UP_COMPLETE, player, *player, player->GetGUID(), nullptr);
+ }
+};
+
+class q56775_warming_up : public BaseQuestWarmingUp
+{
+public:
+ q56775_warming_up() : BaseQuestWarmingUp("q56775_warming_up") { }
+
+ static constexpr float CLONE_ORIENTATION = 5.124503135681152343f;
+ static constexpr float CLONE_Z_OFFSET = 0.308f;
+
+ void OnQuestStatusChange(Player* player, Quest const* quest, QuestStatus oldStatus, QuestStatus newStatus) override
+ {
+ BaseQuestWarmingUp::OnQuestStatusChange(player, quest, oldStatus, newStatus);
+
+ if (newStatus == QUEST_STATUS_REWARDED)
+ {
+ Creature* garrickLowerDeck = FindCreatureIgnorePhase(player, "q56775_garrick_lower_deck", 5.0f);
+ Creature* garrickUpperDeck = FindCreatureIgnorePhase(player, "q56775_garrick_upper_deck", 75.0f);
+ if (!garrickLowerDeck || !garrickUpperDeck)
+ return;
+
+ Position pos(garrickLowerDeck->GetPositionX(), garrickLowerDeck->GetPositionY(), garrickLowerDeck->GetPositionZ() - CLONE_Z_OFFSET, CLONE_ORIENTATION);
+ garrickUpperDeck->SummonPersonalClone(pos, TEMPSUMMON_MANUAL_DESPAWN, 0s, 0, 0, player);
+ }
+ }
+};
+
+class q59926_warming_up : public BaseQuestWarmingUp
+{
+public:
+ q59926_warming_up() : BaseQuestWarmingUp("q59926_warming_up") { }
+
+ void OnQuestStatusChange(Player* player, Quest const* quest, QuestStatus oldStatus, QuestStatus newStatus) override
+ {
+ BaseQuestWarmingUp::OnQuestStatusChange(player, quest, oldStatus, newStatus);
+
+ if (newStatus == QUEST_STATUS_REWARDED)
+ {
+ Creature* grimaxeLowerDeck = FindCreatureIgnorePhase(player, "q59926_grimaxe_lower_deck", 5.0f);
+ Creature* grimaxeUpperDeck = FindCreatureIgnorePhase(player, "q59926_grimaxe_upper_deck", 75.0f);
+ if (!grimaxeLowerDeck || !grimaxeUpperDeck)
+ return;
+
+ grimaxeUpperDeck->SummonPersonalClone(*grimaxeLowerDeck, TEMPSUMMON_MANUAL_DESPAWN, 0s, 0, 0, player);
+ }
+ }
+};
+
+enum WarmingUpCaptainData
+{
+ NPC_WARLORD_BREKA_GRIMAXE2 = 166824,
+ NPC_WARLORD_BREKA_GRIMAXE3 = 166827,
+ NPC_CAPTAIN_GARRICK = 156280,
+
+ PATH_GARRICK_TO_COLE = 10501450,
+ PATH_GARRICK_TO_UPPER_DECK = 10501451,
+ PATH_GRIMAXE_TO_THROG = 10501900,
+ PATH_GRIMAXE_TO_UPPER_DECK = 10501901,
+
+ EVENT_SHIP_CAPTAIN1_SCRIPT1 = 1,
+ EVENT_SHIP_CAPTAIN1_SCRIPT2,
+ EVENT_SHIP_CAPTAIN1_SCRIPT3,
+
+ SAY_SPAR = 0,
+};
+
+// 156280 - Captain Garrick
+// 166824 - Warlord Breka Grimaxe
+struct npc_ship_captain_warming_up_private : public ScriptedAI
+{
+ npc_ship_captain_warming_up_private(Creature* creature) : ScriptedAI(creature), _pathToSparringPartner(0), _pathToUpperDeck(0) { }
+
+ void InitializeAI() override
+ {
+ me->RemoveNpcFlag(UNIT_NPC_FLAG_QUESTGIVER);
+ }
+
+ void JustAppeared() override
+ {
+ if (me->GetEntry() == NPC_CAPTAIN_GARRICK)
+ {
+ _pathToSparringPartner = PATH_GARRICK_TO_COLE;
+ _pathToUpperDeck = PATH_GARRICK_TO_UPPER_DECK;
+ }
+ else if (me->GetEntry() == NPC_WARLORD_BREKA_GRIMAXE2)
+ {
+ _pathToSparringPartner = PATH_GRIMAXE_TO_THROG;
+ _pathToUpperDeck = PATH_GRIMAXE_TO_UPPER_DECK;
+ }
+
+ _events.ScheduleEvent(EVENT_SHIP_CAPTAIN1_SCRIPT1, 1s);
+ }
+
+ void WaypointPathEnded(uint32 /*nodeId*/, uint32 pathId) override
+ {
+ if (pathId == _pathToSparringPartner)
+ _events.ScheduleEvent(EVENT_SHIP_CAPTAIN1_SCRIPT2, 0s);
+ else if (pathId == _pathToUpperDeck)
+ me->DespawnOrUnsummon();
+ }
+
+ void UpdateAI(uint32 diff) override
+ {
+ _events.Update(diff);
+
+ while (uint32 eventId = _events.ExecuteEvent())
+ {
+ switch (eventId)
+ {
+ case EVENT_SHIP_CAPTAIN1_SCRIPT1:
+ Talk(SAY_SPAR);
+ me->GetMotionMaster()->MovePath(_pathToSparringPartner, false);
+ break;
+ case EVENT_SHIP_CAPTAIN1_SCRIPT2:
+ me->HandleEmoteCommand(EMOTE_ONESHOT_SALUTE);
+ _events.ScheduleEvent(EVENT_SHIP_CAPTAIN1_SCRIPT3, 3s);
+ break;
+ case EVENT_SHIP_CAPTAIN1_SCRIPT3:
+ me->GetMotionMaster()->MovePath(_pathToUpperDeck, false);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+private:
+ EventMap _events;
+ uint32 _pathToSparringPartner;
+ uint32 _pathToUpperDeck;
+};
+
+enum StandYourGroundData
+{
+ ACTOR_ID_ALLIANCE = 68598,
+ ACTOR_ID_HORDE = 75920,
+
+ CONVERSATION_PREFIGHT = 14422,
+ CONVERSATION_AGGRO = 14423,
+ CONVERSATION_JUMP = 14424,
+
+ EQUIPMENT_SWORD = 108493,
+ EQUIPMENT_AXE = 175161,
+
+ EVENT_MOVE_TO_A_POSITION = 1,
+ EVENT_PREFIGHT_CONVERSATION,
+ EVENT_JUMP_BEHIND,
+ EVENT_WALK_BACK,
+
+ PATH_ALLIANCE_SPARING_PARTNER = 10501460,
+ PATH_HORDE_SPARING_PARTNER = 10501870,
+
+ POSITION_SPARPOINT_ADVERTISMENT = 1,
+ POSITION_SPARPOINT_READY = 2,
+
+ TALK_SPARING_COMPLETE = 0,
+
+ NPC_ALLIANCE_SPARING_PARTNER = 157051,
+ NPC_HORDE_SPARING_PARTNER = 166814,
+ NPC_SPAR_POINT_ADVERTISMENT = 174971,
+ NPC_KILL_CREDIT = 155607,
+
+ SPELL_COMBAT_TRAINING_COMPLETE = 303120,
+ SPELL_JUMP_LEFT = 312757,
+ SPELL_JUMP_BEHIND = 312755,
+ SPELL_COMBAT_TRAINING = 323071,
+ SPELL_UPDATE_PHASE_SHIFT = 82238,
+};
+
+// 58209 - Stand Your Ground
+// 59927 - Stand Your Ground
+class quest_stand_your_ground : public QuestScript
+{
+public:
+ quest_stand_your_ground() : QuestScript("quest_stand_your_ground") { }
+
+ void OnQuestStatusChange(Player* player, Quest const* /*quest*/, QuestStatus /*oldStatus*/, QuestStatus newStatus) override
+ {
+ // Remove aura if player drops quest
+ if (newStatus == QUEST_STATUS_NONE)
+ player->CastSpell(player, SPELL_COMBAT_TRAINING_COMPLETE);
+ }
+};
+
+// 303065 - Summon Cole - Combat Training (DNT)
+// 325108 - Summon Throg - Combat Training (DNT)
+class spell_summon_sparring_partner : public SpellScript
+{
+ PrepareSpellScript(spell_summon_sparring_partner);
+
+ void SelectTarget(WorldObject*& target)
+ {
+ Player* caster = GetCaster()->ToPlayer();
+ if (!caster)
+ return;
+
+ Creature* partner = caster->FindNearestCreatureWithOptions(10.0f, FindCreatureOptions().SetIgnorePhases(true).SetStringId(caster->GetTeam() == ALLIANCE ? "q58209_cole" : "q59927_throg"));
+ if (!partner)
+ return;
+
+ target = partner;
+ }
+
+ void Register() override
+ {
+ OnObjectTargetSelect += SpellObjectTargetSelectFn(spell_summon_sparring_partner::SelectTarget, EFFECT_0, TARGET_DEST_NEARBY_ENTRY_OR_DB);
+ }
+};
+
+// 157051 - Alliance Sparring Partner
+// 166814 - Horde Sparring Partner
+struct npc_sparring_partner_exiles_reach : public ScriptedAI
+{
+ npc_sparring_partner_exiles_reach(Creature* creature) : ScriptedAI(creature), _jumped(false), _actorIndex(0), _actorId(0), _path(0) { }
+
+ void JustAppeared() override
+ {
+ if (me->GetEntry() == NPC_ALLIANCE_SPARING_PARTNER)
+ {
+ SetEquipmentSlots(false, EQUIPMENT_SWORD, EQUIP_NO_CHANGE, EQUIP_NO_CHANGE);
+ _path = PATH_ALLIANCE_SPARING_PARTNER;
+ _actorId = ACTOR_ID_ALLIANCE;
+ _actorIndex = 0;
+ }
+ else if (me->GetEntry() == NPC_HORDE_SPARING_PARTNER)
+ {
+ SetEquipmentSlots(false, EQUIPMENT_AXE, EQUIP_NO_CHANGE, EQUIP_NO_CHANGE);
+ _path = PATH_HORDE_SPARING_PARTNER;
+ _actorId = ACTOR_ID_HORDE;
+ _actorIndex = 1;
+ }
+ me->SetUnitFlag(UNIT_FLAG_IMMUNE_TO_PC);
+ _events.ScheduleEvent(EVENT_MOVE_TO_A_POSITION, 1s);
+ }
+
+ void IsSummonedBy(WorldObject* summonerWO) override
+ {
+ Unit* summoner = summonerWO->ToUnit();
+
+ if (!summoner || !summoner->IsPlayer())
+ return;
+
+ _playerGUID = summoner->GetGUID();
+ }
+
+ void EnterEvadeMode(EvadeReason /*why*/) override
+ {
+ if (!me->IsAlive())
+ return;
+
+ me->CombatStop(true);
+ EngagementOver();
+ me->ResetPlayerDamageReq();
+ _events.ScheduleEvent(EVENT_WALK_BACK, 1s);
+ }
+
+ void MovementInform(uint32 uiType, uint32 uiId) override
+ {
+ if (uiType != POINT_MOTION_TYPE)
+ return;
+
+ switch (uiId)
+ {
+ case POSITION_SPARPOINT_ADVERTISMENT:
+ me->SetWalk(true);
+ me->GetMotionMaster()->MovePoint(POSITION_SPARPOINT_READY, me->GetFirstCollisionPosition(2.0f, (float)rand_norm() * static_cast<float>(2 * M_PI)));
+ break;
+ case POSITION_SPARPOINT_READY:
+ if (Player* player = ObjectAccessor::GetPlayer(*me, _playerGUID))
+ me->SetFacingToObject(player);
+ me->RemoveUnitFlag(UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_UNINTERACTIBLE);
+ break;
+ default:
+ break;
+ }
+ }
+
+ void WaypointPathEnded(uint32 /*nodeId*/, uint32 pathId) override
+ {
+ if (pathId != _path)
+ return;
+
+ if (Player* player = ObjectAccessor::GetPlayer(*me, _playerGUID))
+ {
+ me->DespawnOrUnsummon();
+ player->CastSpell(player, SPELL_UPDATE_PHASE_SHIFT);
+ player->CastSpell(player, SPELL_COMBAT_TRAINING_COMPLETE);
+ }
+ }
+
+ void DamageTaken(Unit* /*attacker*/, uint32& damage, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo = nullptr*/) override
+ {
+ if (me->GetHealth() <= damage)
+ {
+ damage = 0;
+ me->SetHealth(1);
+ DoStopAttack();
+ me->SetUnitFlag(UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_UNINTERACTIBLE);
+ _events.CancelEvent(EVENT_JUMP_BEHIND);
+
+ if (Player* player = ObjectAccessor::GetPlayer(*me, _playerGUID))
+ {
+ me->SetFacingToObject(player);
+ Talk(TALK_SPARING_COMPLETE, player);
+ player->CastSpell(player, SPELL_COMBAT_TRAINING);
+ player->KilledMonsterCredit(NPC_KILL_CREDIT);
+ }
+ }
+
+ if (me->HealthBelowPctDamaged(65, damage) && !_jumped)
+ {
+ _jumped = true;
+ DoCastVictim(SPELL_JUMP_LEFT, true);
+ StartPrivateConversation(CONVERSATION_JUMP);
+ _events.ScheduleEvent(EVENT_JUMP_BEHIND, 2s);
+ }
+ }
+
+ void JustEngagedWith(Unit* /*who*/) override
+ {
+ StartPrivateConversation(CONVERSATION_AGGRO);
+ }
+
+ void DamageDealt(Unit* target, uint32& damage, DamageEffectType /*damageType*/) override
+ {
+ if (target->GetHealthPct() < 95)
+ damage = 0;
+ }
+
+ void StartPrivateConversation(uint32 conversationId)
+ {
+ if (Player* player = ObjectAccessor::GetPlayer(*me, _playerGUID))
+ {
+ Conversation* conversation = Conversation::CreateConversation(conversationId, player, *player, player->GetGUID(), nullptr, false);
+ conversation->AddActor(_actorId, _actorIndex, me->GetGUID());
+ conversation->Start();
+ }
+ }
+
+ Creature* GetRandomSparPoint()
+ {
+ std::list<Creature*> sparPoints;
+ GetCreatureListWithEntryInGrid(sparPoints, me, NPC_SPAR_POINT_ADVERTISMENT, 25.0f);
+ Trinity::Containers::RandomResize(sparPoints, 1);
+
+ if (sparPoints.empty()) // should never happen
+ return nullptr;
+
+ return sparPoints.front();
+ }
+
+ void UpdateAI(uint32 diff) override
+ {
+ _events.Update(diff);
+
+ while (uint32 eventId = _events.ExecuteEvent())
+ {
+ switch (eventId)
+ {
+ case EVENT_MOVE_TO_A_POSITION:
+ {
+ if (Creature* sparPoint = GetRandomSparPoint())
+ me->GetMotionMaster()->MovePoint(POSITION_SPARPOINT_ADVERTISMENT, sparPoint->GetPosition());
+
+ _events.ScheduleEvent(EVENT_PREFIGHT_CONVERSATION, 1s);
+ break;
+ }
+ case EVENT_PREFIGHT_CONVERSATION:
+ StartPrivateConversation(CONVERSATION_PREFIGHT);
+ break;
+ case EVENT_JUMP_BEHIND:
+ DoCastVictim(SPELL_JUMP_BEHIND, true);
+ break;
+ case EVENT_WALK_BACK:
+ me->GetMotionMaster()->Clear();
+ me->GetMotionMaster()->MovePath(_path, false);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (!UpdateVictim())
+ return;
+
+ DoMeleeAttackIfReady();
+ }
+private:
+ EventMap _events;
+ bool _jumped;
+ uint8 _actorIndex;
+ uint32 _actorId;
+ uint32 _path;
+ ObjectGuid _playerGUID;
+};
+
+enum FirstMateStandYourGroundData
+{
+ QUEST_STAND_YOUR_GROUND_ALLIANCE = 58209,
+ QUEST_STAND_YOUR_GROUND_HORDE = 59927,
+
+ SPELL_SUMMON_COLE = 303064,
+ SPELL_SUMMON_THROG = 325107,
+};
+
+// 160664 - Private Cole
+// 166583 - Grunt Throg
+struct npc_first_mate_stand_your_ground : public ScriptedAI
+{
+ npc_first_mate_stand_your_ground(Creature* creature) : ScriptedAI(creature) { }
+
+ void OnQuestAccept(Player* player, Quest const* quest) override
+ {
+ if (quest->GetQuestId() == QUEST_STAND_YOUR_GROUND_ALLIANCE)
+ {
+ player->CastSpell(player, SPELL_UPDATE_PHASE_SHIFT);
+ player->CastSpell(player, SPELL_SUMMON_COLE);
+ }
+ else if (quest->GetQuestId() == QUEST_STAND_YOUR_GROUND_HORDE)
+ {
+ player->CastSpell(player, SPELL_UPDATE_PHASE_SHIFT);
+ player->CastSpell(player, SPELL_SUMMON_THROG);
+ }
+ }
+};
+
+struct ActorData
+{
+ std::string_view StringId;
+ Position ActorPosition;
+};
+
+static std::vector<ActorData> const ActorDataMap[2] =
+{
+ // TEAM_ALLIANCE
+ {
+ { "q58208_garrick", { 35.5643f, -1.19837f, 12.1479f, 3.3272014f } },
+ { "q58208_richter", { -1.84858f, -8.38776f, 5.10018f, 1.5184366f } },
+ { "q58208_keela", { -15.3642f, 6.5793f, 5.5026f, 3.1415925f } },
+ { "q58208_bjorn", { 12.8406f, -8.49553f, 4.98031f, 4.8520155f } },
+ { "q58208_austin", { -4.48607f, 9.89729f, 5.07851f, 1.5184366f } },
+ { "q58208_cole", { -13.3396f, 0.702157f, 5.57996f, 0.087266445f } },
+ },
+ // TEAM_HORDE
+ {
+ { "q59928_grimaxe", { 25.5237f, 0.283005f, 26.5455f, 3.3526998f } },
+ { "q59928_throg", { -10.8399f, 11.9039f, 8.88028f, 6.2308254f } },
+ { "q59928_mithdran", { -24.4763f, -4.48273f, 9.13471f, 0.62831855f } },
+ { "q59928_lana", { -5.1971f, -15.0268f, 8.992f, 4.712389f } },
+ { "q59928_bo", { -22.1559f, 5.58041f, 9.09176f, 6.143559f } },
+ { "q59928_jinhake", { -31.9464f, 7.5772f, 10.6408f, 6.0737457f } },
+ }
+};
+
+static std::unordered_map<Races, std::string_view> const ActorPetData =
+{
+ { RACE_HUMAN, "q58208_wolf" },
+ { RACE_DWARF, "q58208_bear" },
+ { RACE_NIGHTELF, "q58208_tiger" },
+ { RACE_GNOME, "q58208_bunny" },
+ { RACE_DRAENEI, "q58208_moth" },
+ { RACE_WORGEN, "q58208_dog" },
+ { RACE_PANDAREN_ALLIANCE, "q58208_turtle" },
+ { RACE_ORC, "q59928_wolf" },
+ { RACE_UNDEAD_PLAYER, "q59928_bat" },
+ { RACE_TAUREN, "q59928_plainstrider" },
+ { RACE_TROLL, "q59928_raptor" },
+ { RACE_GOBLIN, "q59928_scorpion" },
+ { RACE_BLOODELF, "q59928_dragonhawk" },
+ { RACE_PANDAREN_HORDE, "q59928_turtle" }
+};
+
+enum BraceForImpactData
+{
+ QUEST_BRACE_FOR_IMPACT_ALLIANCE = 58208,
+ QUEST_BRACE_FOR_IMPACT_HORDE = 59928,
+};
+
+// 58208 - Brace For Impact
+// 59928 - Brace For Impact
+class quest_brace_for_impact : public QuestScript
+{
+public:
+ quest_brace_for_impact() : QuestScript("quest_brace_for_impact") { }
+
+ void OnQuestStatusChange(Player* player, Quest const* quest, QuestStatus /*oldStatus*/, QuestStatus newStatus) override
+ {
+ if (newStatus != QUEST_STATUS_COMPLETE)
+ return;
+
+ TeamId team = TEAM_NEUTRAL;
+ Position petSpawnPos;
+
+ if (quest->GetQuestId() == QUEST_BRACE_FOR_IMPACT_ALLIANCE)
+ {
+ team = TEAM_ALLIANCE;
+ petSpawnPos = { -1.4492f, 8.06887f, 5.10348f, 2.6005409f };
+ }
+ else if (quest->GetQuestId() == QUEST_BRACE_FOR_IMPACT_HORDE)
+ {
+ team = TEAM_HORDE;
+ petSpawnPos = { -22.8374f, -3.08287f, 9.12613f, 3.857178f };
+ }
+
+ if (team == TEAM_NEUTRAL)
+ return;
+
+ SpawnActors(player, team, petSpawnPos);
+ player->CastSpell(player, SPELL_UPDATE_PHASE_SHIFT);
+ }
+
+ void SpawnActors(Player* player, TeamId team, Position petSpawnPos)
+ {
+ for (ActorData const& actor : ActorDataMap[team])
+ SpawnActor(player, FindCreatureIgnorePhase(player, actor.StringId, 50.0f), actor.ActorPosition);
+
+ SpawnPet(player, petSpawnPos);
+ }
+
+ void SpawnPet(Player* player, Position const& position)
+ {
+ if (player->GetClass() != CLASS_HUNTER)
+ return;
+
+ if (std::string_view const* stringId = Trinity::Containers::MapGetValuePtr(ActorPetData, Races(player->GetRace())))
+ {
+ Creature* pet = FindCreatureIgnorePhase(player, *stringId, 25.0f);
+ if (!pet)
+ return;
+
+ SpawnActor(player, pet, position);
+ }
+ }
+
+ void SpawnActor(Player* player, Creature* creature, Position const& position)
+ {
+ TransportBase const* transport = player->GetDirectTransport();
+
+ if (!transport || !creature)
+ return;
+
+ float x, y, z, o;
+ position.GetPosition(x, y, z, o);
+ transport->CalculatePassengerPosition(x, y, z, &o);
+ creature->SummonPersonalClone({ x, y, z, o }, TEMPSUMMON_MANUAL_DESPAWN, 0s, 0, 0, player);
+ }
+};
+
+enum BraceForImpactCaptainData
+{
+ PATH_GARRICK_FROM_UPPER_DECK = 10505890,
+ PATH_GARRICK_TO_LOWER_DECK = 10505891,
+ PATH_GRIMAXE_FROM_UPPER_DECK = 10501910,
+ PATH_GRIMAXE_TO_LOWER_DECK = 10501911,
+
+ EVENT_SHIP_CAPTAIN2_SCRIPT1 = 1,
+ EVENT_SHIP_CAPTAIN2_SCRIPT2,
+
+ SAY_GET_TO_POSITIONS = 1,
+};
+
+// 156280 - Captain Garrick
+// 166827 - Warlord Breka Grimaxe
+struct npc_ship_captain_brace_for_impact_private : public ScriptedAI
+{
+ npc_ship_captain_brace_for_impact_private(Creature* creature) : ScriptedAI(creature), _pathPreTalk(0), _pathPostTalk(0), _waitTime(0s){ }
+
+ void JustAppeared() override
+ {
+ if (me->GetEntry() == NPC_CAPTAIN_GARRICK)
+ {
+ _pathPreTalk = PATH_GARRICK_FROM_UPPER_DECK;
+ _pathPostTalk = PATH_GARRICK_TO_LOWER_DECK;
+ _waitTime = 0s;
+ }
+ else if (me->GetEntry() == NPC_WARLORD_BREKA_GRIMAXE3)
+ {
+ _pathPreTalk = PATH_GRIMAXE_FROM_UPPER_DECK;
+ _pathPostTalk = PATH_GRIMAXE_TO_LOWER_DECK;
+ _waitTime = 1s;
+ }
+
+ me->RemoveNpcFlag(UNIT_NPC_FLAG_QUESTGIVER);
+ me->GetMotionMaster()->MovePath(_pathPreTalk, false);
+ }
+
+ void WaypointPathEnded(uint32 /*nodeId*/, uint32 pathId) override
+ {
+ if (pathId == _pathPreTalk)
+ _events.ScheduleEvent(EVENT_SHIP_CAPTAIN2_SCRIPT1, _waitTime);
+ else if (pathId == _pathPostTalk)
+ me->DespawnOrUnsummon();
+ }
+
+ void UpdateAI(uint32 diff) override
+ {
+ _events.Update(diff);
+
+ while (uint32 eventId = _events.ExecuteEvent())
+ {
+ switch (eventId)
+ {
+ case EVENT_SHIP_CAPTAIN2_SCRIPT1:
+ Talk(SAY_GET_TO_POSITIONS);
+ _events.ScheduleEvent(EVENT_SHIP_CAPTAIN2_SCRIPT2, 3s);
+ break;
+ case EVENT_SHIP_CAPTAIN2_SCRIPT2:
+ me->GetMotionMaster()->MovePath(_pathPostTalk, false);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+private:
+ EventMap _events;
+ uint32 _pathPreTalk;
+ uint32 _pathPostTalk;
+ Seconds _waitTime;
+};
+
+enum BraceForImpactFirstMateData
+{
+ NPC_PRIVATE_COLE = 160664,
+ NPC_GRUNT_THROG = 166583,
+
+ PATH_COLE_BRACE_FOR_IMPACT = 10501461,
+ PATH_THROG_BRACE_FOR_IMPACT = 10501871,
+
+ EVENT_FIRST_MATE_1 = 1,
+ EVENT_FIRST_MATE_2,
+
+ SAY_STORM = 0,
+};
+
+// 160664 - Private Cole
+// 166583 - Grunt Throg
+struct npc_first_mate_brace_for_impact_private : public ScriptedAI
+{
+ npc_first_mate_brace_for_impact_private(Creature* creature) : ScriptedAI(creature), _path(0) { }
+
+ void JustAppeared() override
+ {
+ if (me->GetEntry() == NPC_PRIVATE_COLE)
+ _path = PATH_COLE_BRACE_FOR_IMPACT;
+ else if (me->GetEntry() == NPC_GRUNT_THROG)
+ _path = PATH_THROG_BRACE_FOR_IMPACT;
+
+ _events.ScheduleEvent(EVENT_FIRST_MATE_1, 3s);
+ }
+
+ void WaypointPathEnded(uint32 /*nodeId*/, uint32 pathId) override
+ {
+ if (_path && pathId == _path)
+ me->DespawnOrUnsummon();
+ }
+
+ void UpdateAI(uint32 diff) override
+ {
+ _events.Update(diff);
+
+ while (uint32 eventId = _events.ExecuteEvent())
+ {
+ switch (eventId)
+ {
+ case EVENT_FIRST_MATE_1:
+ Talk(SAY_STORM);
+ _events.ScheduleEvent(EVENT_FIRST_MATE_2, 4s);
+ break;
+ case EVENT_FIRST_MATE_2:
+ me->GetMotionMaster()->MovePath(_path, false);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+private:
+ EventMap _events;
+ uint32 _path;
+};
+
+enum BraceForImpactCrewData
+{
+ NPC_QUARTERMASTER_RICHTER = 157042,
+ NPC_KEE_LA = 157043,
+ NPC_BJORN_STOUTHANDS = 157044,
+ NPC_AUSTIN_HUXWORTH = 157046,
+
+ NPC_BO = 166585,
+ NPC_MITHDRAN_DAWNTRACKER = 166590,
+ NPC_LANA_JORDAN = 166794,
+ NPC_PROVISONER_JIN_HAKE = 166799,
+
+ PATH_RICHTER_BRACE_FOR_IMPACT = 10501770,
+ PATH_KEE_LA_BRACE_FOR_IMPACT = 10501800,
+ PATH_BJORN_BRACE_FOR_IMPACT = 10501790,
+ PATH_AUSTIN_BRACE_FOR_IMPACT = 10501780,
+
+ PATH_BO_BRACE_FOR_IMPACT = 10502010,
+ PATH_MITHDRAN_BRACE_FOR_IMPACT = 10501990,
+ PATH_LANA_BRACE_FOR_IMPACT = 10501980,
+ PATH_JIN_HAKE_BRACE_FOR_IMPACT = 10502000,
+};
+
+// 157042 - Quartermaster Richter
+// 157043 - Kee-La
+// 157044 - Bjorn Stouthands
+// 157046 - Austin Huxworth
+// 166585 - Bo
+// 166590 - Mithdran Dawntracker
+// 166794 - Lana Jordan
+// 166799 - Provisoner Jin'hake
+struct npc_crew_ship_private : public ScriptedAI
+{
+ npc_crew_ship_private(Creature* creature) : ScriptedAI(creature), _path(0) { }
+
+ uint32 GetPathID()
+ {
+ switch (me->GetEntry())
+ {
+ case NPC_QUARTERMASTER_RICHTER: return PATH_RICHTER_BRACE_FOR_IMPACT;
+ case NPC_KEE_LA: return PATH_KEE_LA_BRACE_FOR_IMPACT;
+ case NPC_BJORN_STOUTHANDS: return PATH_BJORN_BRACE_FOR_IMPACT;
+ case NPC_AUSTIN_HUXWORTH: return PATH_AUSTIN_BRACE_FOR_IMPACT;
+ case NPC_BO: return PATH_BO_BRACE_FOR_IMPACT;
+ case NPC_MITHDRAN_DAWNTRACKER: return PATH_MITHDRAN_BRACE_FOR_IMPACT;
+ case NPC_LANA_JORDAN: return PATH_LANA_BRACE_FOR_IMPACT;
+ case NPC_PROVISONER_JIN_HAKE: return PATH_JIN_HAKE_BRACE_FOR_IMPACT;
+ default: return 0;
+ }
+ }
+
+ void JustAppeared() override
+ {
+ _path = GetPathID();
+ _scheduler.Schedule(Seconds(7), [this](TaskContext)
+ {
+ me->GetMotionMaster()->MovePath(_path, false);
+ });
+ }
+
+ void WaypointPathEnded(uint32 /*nodeId*/, uint32 pathId) override
+ {
+ if (_path && pathId == _path)
+ me->DespawnOrUnsummon();
+ }
+
+ void UpdateAI(uint32 diff) override
+ {
+ _scheduler.Update(diff);
+ }
+
+private:
+ TaskScheduler _scheduler;
+ uint32 _path;
+};
+
+enum BraceForImpactPetData
+{
+ EVENT_PET_SHIP_RUN_TO_POSITION = 1,
+
+ MAP_ALLIANCE_SHIP = 2261,
+ MAP_HORDE_SHIP = 2369,
+
+ PATH_PET_ALLIANCE_SHIP = 10501510,
+ PATH_PET_HORDE_SHIP = 10502020
+};
+
+// 167337 - Mechanical Bunny
+// 167342 - Moth
+// 167343 - Dragonhawk
+// 167344 - Scorpion
+// 167345 - Wolf
+// 167346 - Wolf
+// 167347 - Tiger
+// 167348 - Turtle
+// 167349 - Plainstrider
+// 167350 - Raptor
+// 167351 - Bat
+// 167352 - Dog
+// 167375 - Bear
+struct npc_pet_ship_private : public ScriptedAI
+{
+ npc_pet_ship_private(Creature* creature) : ScriptedAI(creature), _path(0) { }
+
+ void JustAppeared() override
+ {
+ if (!me->GetTransport())
+ return;
+
+ int32 transportMap = me->GetTransport()->GetMapIdForSpawning();
+ if (transportMap == MAP_ALLIANCE_SHIP)
+ _path = PATH_PET_ALLIANCE_SHIP;
+ else if (transportMap == MAP_HORDE_SHIP)
+ _path = PATH_PET_HORDE_SHIP;
+
+ if (_path)
+ _events.ScheduleEvent(EVENT_PET_SHIP_RUN_TO_POSITION, 8s);
+ }
+
+ void WaypointPathEnded(uint32 /*nodeId*/, uint32 /*pathId*/) override
+ {
+ me->DespawnOrUnsummon();
+ }
+
+ void UpdateAI(uint32 diff) override
+ {
+ _events.Update(diff);
+
+ while (uint32 eventId = _events.ExecuteEvent())
+ {
+ if (eventId == EVENT_PET_SHIP_RUN_TO_POSITION)
+ me->GetMotionMaster()->MovePath(_path, false);
+ }
+ }
+
+private:
+ EventMap _events;
+ uint32 _path;
+};
+
+enum ExilesReachShipCrashData
+{
+ MOVIE_ALLIANCE_SHIP_CRASH = 895,
+ MOVIE_HORDE_SHIP_CRASH = 931,
+
+ SPELL_ALLIANCE_SHIP_CRASH = 305446,
+ SPELL_HORDE_SHIP_CRASH = 325133,
+ SPELL_BEGIN_TUTORIAL = 295600,
+};
+
+class player_exiles_reach_ship_crash : public PlayerScript
+{
+public:
+ player_exiles_reach_ship_crash() : PlayerScript("player_exiles_reach_ship_crash") { }
+
+ void OnMovieComplete(Player* player, uint32 movieId) override
+ {
+ switch (movieId)
+ {
+ case MOVIE_ALLIANCE_SHIP_CRASH:
+ player->CastSpell(player, SPELL_ALLIANCE_SHIP_CRASH, true);
+ break;
+ case MOVIE_HORDE_SHIP_CRASH:
+ player->CastSpell(player, SPELL_HORDE_SHIP_CRASH, true);
+ break;
+ default:
+ break;
+ }
+ }
+};
+
+class scene_alliance_and_horde_ship : public SceneScript
+{
+public:
+ scene_alliance_and_horde_ship() : SceneScript("scene_alliance_and_horde_ship") { }
+
+ void StartConvo(Player* player)
+ {
+ // This script is used to send conversation to Captian Garrick and Warlord Grimaxe on movement after entering ship
+ player->CastSpell(player, SPELL_BEGIN_TUTORIAL, true);
+ }
+
+ void OnSceneComplete(Player* player, uint32 /*sceneInstanceID*/, SceneTemplate const* /*sceneTemplate*/) override
+ {
+ StartConvo(player);
+ }
+
+ void OnSceneCancel(Player* player, uint32 /*sceneInstanceID*/, SceneTemplate const* /*sceneTemplate*/) override
+ {
+ StartConvo(player);
+ }
+};
+
+// ***************************************************************
+// * Scripting in this section occurs after teleporting to beach *
+// ***************************************************************
+
+enum KnockedDownExilesReachData
+{
+ SPELL_KNOCKED_DOWN_STUN2 = 344889
+};
+
+// 305445 - Knocked Down!
+class spell_knocked_down_exiles_reach_beach : public AuraScript
+{
+ PrepareAuraScript(spell_knocked_down_exiles_reach_beach);
+
+ bool Validate(SpellInfo const* /*spellInfo*/) override
+ {
+ return ValidateSpellInfo({ SPELL_KNOCKED_DOWN_STUN2 });
+ }
+
+ void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
+ {
+ GetTarget()->CastSpell(nullptr, SPELL_KNOCKED_DOWN_STUN2, true);
+ }
+
+ void Register() override
+ {
+ AfterEffectRemove += AuraEffectRemoveFn(spell_knocked_down_exiles_reach_beach::OnRemove, EFFECT_0, SPELL_AURA_MOD_STUN, AURA_EFFECT_HANDLE_REAL);
+ }
+};
+
+enum ExilesReachShipCrashBeachData
+{
+ SPELL_KNOCKED_DOWN = 305445,
+ SPELL_CRASHED_LANDED_ALLIANCE = 305464,
+ SPELL_CRASHED_LANDED_HORDE = 325136
+};
+
+// Script scene for washed up on beach to cast spells Alliance and Horde
+class scene_alliance_and_horde_crash : public SceneScript
+{
+public:
+ scene_alliance_and_horde_crash() : SceneScript("scene_alliance_and_horde_crash") { }
+
+ void OnSceneTriggerEvent(Player* player, uint32 /*sceneInstanceID*/, SceneTemplate const* /*sceneTemplate*/, std::string const& triggerName) override
+ {
+ if (triggerName == "Begin Knockdown Aura")
+ player->CastSpell(player, SPELL_KNOCKED_DOWN, true);
+ }
+
+ void OnSceneComplete(Player* player, uint32 /*sceneInstanceID*/, SceneTemplate const* /*sceneTemplate*/) override
+ {
+ player->CastSpell(player, player->GetTeam() == ALLIANCE ? SPELL_CRASHED_LANDED_ALLIANCE : SPELL_CRASHED_LANDED_HORDE, true);
+ }
+};
+
+CreatureAI* CaptainGarrickShipAISelector(Creature* creature)
+{
+ if (creature->IsPrivateObject())
+ {
+ if (Player* privateObjectOwner = ObjectAccessor::GetPlayer(*creature, creature->GetPrivateObjectOwner()))
+ {
+ if ((privateObjectOwner->GetTeam() == ALLIANCE && privateObjectOwner->GetQuestStatus(QUEST_BRACE_FOR_IMPACT_ALLIANCE) == QUEST_STATUS_NONE))
+ return new npc_ship_captain_warming_up_private(creature);
+ else
+ return new npc_ship_captain_brace_for_impact_private(creature);
+ }
+ }
+
+ return new NullCreatureAI(creature);
+};
+
+void AddSC_zone_exiles_reach()
+{
+ // Ship
+ RegisterSpellScript(spell_attention_exiles_reach_tutorial);
+ new q59926_warming_up();
+ new q56775_warming_up();
+ new quest_stand_your_ground();
+ RegisterCreatureAI(npc_sparring_partner_exiles_reach);
+ RegisterSpellScript(spell_summon_sparring_partner);
+ new FactoryCreatureScript<CreatureAI, &CaptainGarrickShipAISelector>("npc_captain_garrick_ship");
+ RegisterPrivatePublicCreatureAIPair("npc_warlord_grimaxe_lower_ship", npc_ship_captain_warming_up_private, NullCreatureAI);
+ RegisterPrivatePublicCreatureAIPair("npc_warlord_grimaxe_upper_ship", npc_ship_captain_brace_for_impact_private, NullCreatureAI);
+ RegisterPrivatePublicCreatureAIPair("npc_cole_ship", npc_first_mate_brace_for_impact_private, npc_first_mate_stand_your_ground);
+ RegisterPrivatePublicCreatureAIPair("npc_throg_ship", npc_first_mate_brace_for_impact_private, npc_first_mate_stand_your_ground);
+ RegisterPrivatePublicCreatureAIPair("npc_crew_ship", npc_crew_ship_private, NullCreatureAI);
+ RegisterPrivatePublicCreatureAIPair("npc_pet_ship", npc_pet_ship_private, NullCreatureAI);
+ new quest_brace_for_impact();
+
+ new player_exiles_reach_ship_crash();
+ new scene_alliance_and_horde_ship();
+
+ // Beach
+ RegisterSpellScript(spell_knocked_down_exiles_reach_beach);
+ new scene_alliance_and_horde_crash();
+}