/*
* 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 .
*/
/* ScriptData
SDName: Winterspring
SD%Complete: Almost Completely Emptied
SDComment: Quest Support 4901
SDCategory: Winterspring
EndScriptData */
/* ContentData
npc_ranshalla
go_elune_fire
EndContentData */
#include "ScriptMgr.h"
#include "GameObject.h"
#include "GameObjectAI.h"
#include "MotionMaster.h"
#include "ObjectAccessor.h"
#include "Player.h"
#include "ScriptedEscortAI.h"
#include "TemporarySummon.h"
enum Says
{
// Escort texts
SAY_QUEST_START = 0,
SAY_ENTER_OWL_THICKET = 1,
SAY_REACH_TORCH = 2,
SAY_AFTER_TORCH = 3,
SAY_REACH_ALTAR_1 = 4,
SAY_REACH_ALTAR_2 = 5,
// After lighting the altar cinematic
SAY_RANSHALLA_ALTAR_1 = 6,
SAY_RANSHALLA_ALTAR_2 = 7,
SAY_PRIESTESS_ALTAR_3 = 8,
SAY_PRIESTESS_ALTAR_4 = 9,
SAY_RANSHALLA_ALTAR_5 = 10,
SAY_RANSHALLA_ALTAR_6 = 11,
SAY_PRIESTESS_ALTAR_7 = 12,
SAY_PRIESTESS_ALTAR_8 = 13,
SAY_PRIESTESS_ALTAR_9 = 14,
SAY_PRIESTESS_ALTAR_10 = 15,
SAY_PRIESTESS_ALTAR_11 = 16,
SAY_PRIESTESS_ALTAR_12 = 17,
SAY_PRIESTESS_ALTAR_13 = 18,
SAY_PRIESTESS_ALTAR_14 = 19,
SAY_VOICE_ALTAR_15 = 20,
SAY_PRIESTESS_ALTAR_16 = 21,
SAY_PRIESTESS_ALTAR_17 = 22,
SAY_PRIESTESS_ALTAR_18 = 23,
SAY_PRIESTESS_ALTAR_19 = 24,
SAY_PRIESTESS_ALTAR_20 = 25,
SAY_PRIESTESS_ALTAR_21 = 26,
SAY_RANSHALLA_END_1 = 27,
SAY_RANSHALLA_END_2 = 28,
EMOTE_CHANT_SPELL = 29,
};
enum Spells
{
SPELL_LIGHT_TORCH = 18953, // channeled spell by Ranshalla while waiting for the torches / altar
};
enum NPCs
{
NPC_RANSHALLA = 10300,
NPC_PRIESTESS_ELUNE = 12116,
NPC_VOICE_ELUNE = 12152,
NPC_GUARDIAN_ELUNE = 12140,
};
enum GOs
{
GO_ELUNE_ALTAR = 177404,
GO_ELUNE_FIRE = 177417,
GO_ELUNE_GEM = 177414, // is respawned in script
GO_ELUNE_LIGHT = 177415, // are respawned in script
};
enum Quests
{
QUEST_GUARDIANS_ALTAR = 4901,
};
enum Dummies
{
NPC_PRIESTESS_DATA_1 = -1, // dummy member for the first priestess (right)
NPC_PRIESTESS_DATA_2 = -2, // dummy member for the second priestess (left)
DATA_MOVE_PRIESTESS = -3, // dummy member to check the priestess movement
DATA_EVENT_END = -4, // dummy member to indicate the event end
EVENT_RESUME = 1, // trigger rest of event
};
// DialogueHelper (imported from SD)
struct DialogueEntry
{
int32 TextEntry; ///< To be said text entry
int32 SayerEntry; ///< Entry of the mob who should say
uint32 SayTimer; ///< Time delay until next text of array is said (0 stops)
};
class DialogueHelper
{
public:
// The array MUST be terminated by {0, 0, 0}
DialogueHelper(DialogueEntry const* dialogueArray) :
_dialogueArray(dialogueArray),
_currentEntry(nullptr),
_actionTimer(0)
{ }
// The array MUST be terminated by {0, 0, 0, 0, 0}
/// Function to initialize the dialogue helper for instances. If not used with instances, GetSpeakerByEntry MUST be overwritten to obtain the speakers
/// Set if take first entries or second entries
void StartNextDialogueText(int32 textEntry)
{
// Find textEntry
bool found = false;
for (DialogueEntry const* entry = _dialogueArray; entry->TextEntry; ++entry)
{
if (entry->TextEntry == textEntry)
{
_currentEntry = entry;
found = true;
break;
}
}
if (!found)
return;
DoNextDialogueStep();
}
void DialogueUpdate(uint32 diff)
{
if (_actionTimer)
{
if (_actionTimer <= diff)
DoNextDialogueStep();
else
_actionTimer -= diff;
}
}
protected:
/// Will be called when a dialogue step was done
virtual void JustDidDialogueStep(int32 /*entry*/) { }
/// Will be called to get a speaker, MUST be implemented if not used in instances
virtual Creature* GetSpeakerByEntry(int32 /*entry*/) { return nullptr; }
private:
void DoNextDialogueStep()
{
// Last Dialogue Entry done?
if (!_currentEntry || !_currentEntry->TextEntry)
{
_actionTimer = 0;
return;
}
// Get Text, SpeakerEntry and Timer
int32 textEntry = _currentEntry->TextEntry;
uint32 sayerEntry = _currentEntry->SayerEntry;
_actionTimer = _currentEntry->SayTimer;
// Simulate Case
if (sayerEntry && textEntry >= 0)
{
// Use Speaker if directly provided
if (Creature* speaker = GetSpeakerByEntry(sayerEntry))
speaker->AI()->Talk(textEntry);
}
JustDidDialogueStep(_currentEntry->TextEntry);
// Increment position
++_currentEntry;
}
DialogueEntry const* _dialogueArray;
DialogueEntry const* _currentEntry;
uint32 _actionTimer;
};
const DialogueEntry introDialogue[] =
{
{SAY_REACH_ALTAR_1, NPC_RANSHALLA, 2000},
{SAY_REACH_ALTAR_2, NPC_RANSHALLA, 3000},
{NPC_RANSHALLA, 0, 0}, // start the altar channeling
{SAY_PRIESTESS_ALTAR_3, NPC_PRIESTESS_DATA_2, 1000},
{SAY_PRIESTESS_ALTAR_4, NPC_PRIESTESS_DATA_1, 4000},
{SAY_RANSHALLA_ALTAR_5, NPC_RANSHALLA, 4000},
{SAY_RANSHALLA_ALTAR_6, NPC_RANSHALLA, 4000}, // start the escort here
{SAY_PRIESTESS_ALTAR_7, NPC_PRIESTESS_DATA_2, 4000},
{SAY_PRIESTESS_ALTAR_8, NPC_PRIESTESS_DATA_2, 5000}, // show the gem
{GO_ELUNE_GEM, 0, 5000},
{SAY_PRIESTESS_ALTAR_9, NPC_PRIESTESS_DATA_1, 4000}, // move priestess 1 near me
{NPC_PRIESTESS_DATA_1, 0, 3000},
{SAY_PRIESTESS_ALTAR_10, NPC_PRIESTESS_DATA_1, 5000},
{SAY_PRIESTESS_ALTAR_11, NPC_PRIESTESS_DATA_1, 4000},
{SAY_PRIESTESS_ALTAR_12, NPC_PRIESTESS_DATA_1, 5000},
{SAY_PRIESTESS_ALTAR_13, NPC_PRIESTESS_DATA_1, 8000}, // summon voice and guard of elune
{NPC_VOICE_ELUNE, 0, 12000},
{SAY_VOICE_ALTAR_15, NPC_VOICE_ELUNE, 5000}, // move priestess 2 near me
{NPC_PRIESTESS_DATA_2, 0, 3000},
{SAY_PRIESTESS_ALTAR_16, NPC_PRIESTESS_DATA_2, 4000},
{SAY_PRIESTESS_ALTAR_17, NPC_PRIESTESS_DATA_2, 6000},
{SAY_PRIESTESS_ALTAR_18, NPC_PRIESTESS_DATA_1, 5000},
{SAY_PRIESTESS_ALTAR_19, NPC_PRIESTESS_DATA_1, 3000}, // move the owlbeast
{NPC_GUARDIAN_ELUNE, 0, 2000},
{SAY_PRIESTESS_ALTAR_20, NPC_PRIESTESS_DATA_1, 4000}, // move the first priestess up
{SAY_PRIESTESS_ALTAR_21, NPC_PRIESTESS_DATA_2, 10000}, // move second priestess up
{DATA_MOVE_PRIESTESS, 0, 6000}, // despawn the gem
{DATA_EVENT_END, 0, 2000}, // turn towards the player
{SAY_RANSHALLA_END_2, NPC_RANSHALLA, 0},
{0, 0, 0},
};
static Position wingThicketLocations[] =
{
{5515.98f, -4903.43f, 846.30f, 4.58f}, // 0 right priestess summon loc
{5501.94f, -4920.20f, 848.69f, 6.15f}, // 1 left priestess summon loc
{5497.35f, -4906.49f, 850.83f, 2.76f}, // 2 guard of elune summon loc
{5518.38f, -4913.47f, 845.57f, 0.00f}, // 3 right priestess move loc
{5510.36f, -4921.17f, 846.33f, 0.00f}, // 4 left priestess move loc
{5511.31f, -4913.82f, 847.17f, 0.00f}, // 5 guard of elune move loc
{5518.51f, -4917.56f, 845.23f, 0.00f}, // 6 right priestess second move loc
{5514.40f, -4921.16f, 845.49f, 0.00f} // 7 left priestess second move loc
};
/*#####
# npc_ranshalla
#####*/
static constexpr uint32 PATH_ESCORT_RANSHALLA = 82402;
class npc_ranshalla : public CreatureScript
{
public:
npc_ranshalla() : CreatureScript("npc_ranshalla") { }
struct npc_ranshallaAI : public EscortAI, private DialogueHelper
{
npc_ranshallaAI(Creature* creature) : EscortAI(creature), DialogueHelper(introDialogue)
{
Initialize();
}
void Initialize()
{
_delayTimer = 0;
}
uint32 _delayTimer;
ObjectGuid _firstPriestessGUID;
ObjectGuid _secondPriestessGUID;
ObjectGuid _guardEluneGUID;
ObjectGuid _voiceEluneGUID;
ObjectGuid _altarGUID;
void Reset() override
{
Initialize();
}
// Called when the player activates the torch / altar
void DoContinueEscort(bool isAltarWaypoint = false)
{
me->InterruptNonMeleeSpells(false);
if (isAltarWaypoint)
Talk(SAY_RANSHALLA_ALTAR_1);
else
Talk(SAY_AFTER_TORCH);
_delayTimer = 2000;
}
// Called when Ranshalla starts to channel on a torch / altar
void DoChannelTorchSpell(bool isAltarWaypoint = false)
{
// Check if we are using the fire or the altar and remove the no_interact flag
if (isAltarWaypoint)
{
if (GameObject* go = GetClosestGameObjectWithEntry(me, GO_ELUNE_ALTAR, 10.0f))
{
go->RemoveFlag(GO_FLAG_NOT_SELECTABLE);
me->SetFacingToObject(go);
_altarGUID = go->GetGUID();
}
}
else if (GameObject* go = GetClosestGameObjectWithEntry(me, GO_ELUNE_FIRE, 10.0f))
go->RemoveFlag(GO_FLAG_NOT_SELECTABLE);
// Yell and set escort to pause
Talk(SAY_REACH_TORCH);
Talk(EMOTE_CHANT_SPELL);
SetEscortPaused(true);
DoCast(me, SPELL_LIGHT_TORCH);
}
void DoSummonPriestess()
{
// Summon 2 Elune priestess and make each of them move to a different spot
if (Creature* priestess = me->SummonCreature(NPC_PRIESTESS_ELUNE, wingThicketLocations[0], TEMPSUMMON_CORPSE_DESPAWN, 0s))
{
priestess->GetMotionMaster()->MovePoint(0, wingThicketLocations[3]);
_firstPriestessGUID = priestess->GetGUID();
}
if (Creature* priestess = me->SummonCreature(NPC_PRIESTESS_ELUNE, wingThicketLocations[1], TEMPSUMMON_CORPSE_DESPAWN, 0s))
{
// Left priestess should have a distinct move point because she is the one who starts the dialogue at point reach
priestess->GetMotionMaster()->MovePoint(1, wingThicketLocations[4]);
_secondPriestessGUID = priestess->GetGUID();
}
}
void SummonedMovementInform(Creature* summoned, uint32 type, uint32 pointId)
{
if (type != POINT_MOTION_TYPE || summoned->GetEntry() != NPC_PRIESTESS_ELUNE || pointId != 1)
return;
// Start the dialogue when the priestess reach the altar (they should both reach the point in the same time)
StartNextDialogueText(SAY_PRIESTESS_ALTAR_3);
}
void WaypointReached(uint32 pointId, uint32 /*pathId*/) override
{
switch (pointId)
{
case 3:
Talk(SAY_ENTER_OWL_THICKET);
break;
case 10: // Cavern 1
case 15: // Cavern 2
case 20: // Cavern 3
case 25: // Cavern 4
case 36: // Cavern 5
DoChannelTorchSpell();
break;
case 39:
StartNextDialogueText(SAY_REACH_ALTAR_1);
SetEscortPaused(true);
break;
case 41:
{
// Search for all nearest lights and respawn them
std::list eluneLights;
GetGameObjectListWithEntryInGrid(eluneLights, me, GO_ELUNE_LIGHT, 20.0f);
for (std::list::const_iterator itr = eluneLights.begin(); itr != eluneLights.end(); ++itr)
{
if ((*itr)->isSpawned())
continue;
(*itr)->SetRespawnTime(115);
(*itr)->Refresh();
}
if (GameObject* altar = ObjectAccessor::GetGameObject(*me ,_altarGUID))
me->SetFacingToObject(altar);
break;
}
case 42:
// Summon the 2 priestess
SetEscortPaused(true);
DoSummonPriestess();
Talk(SAY_RANSHALLA_ALTAR_2);
events.ScheduleEvent(EVENT_RESUME, 2s);
break;
case 44:
// Stop the escort and turn towards the altar
SetEscortPaused(true);
if (GameObject* altar = ObjectAccessor::GetGameObject(*me, _altarGUID))
me->SetFacingToObject(altar);
break;
}
}
void JustDidDialogueStep(int32 entry) override
{
switch (entry)
{
case NPC_RANSHALLA:
// Start the altar channeling
DoChannelTorchSpell(true);
break;
case SAY_RANSHALLA_ALTAR_6:
SetEscortPaused(false);
break;
case SAY_PRIESTESS_ALTAR_8:
// make the gem respawn
if (GameObject* gem = GetClosestGameObjectWithEntry(me, GO_ELUNE_GEM, 10.0f, false))
{
if (gem->isSpawned())
break;
gem->SetRespawnTime(90);
gem->Refresh();
}
break;
case SAY_PRIESTESS_ALTAR_9:
// move near the escort npc
if (Creature* priestess = ObjectAccessor::GetCreature(*me, _firstPriestessGUID))
priestess->GetMotionMaster()->MovePoint(0, wingThicketLocations[6]);
break;
case SAY_PRIESTESS_ALTAR_13:
// summon the Guardian of Elune
if (Creature* guard = me->SummonCreature(NPC_GUARDIAN_ELUNE, wingThicketLocations[2], TEMPSUMMON_CORPSE_DESPAWN, 0s))
{
guard->GetMotionMaster()->MovePoint(0, wingThicketLocations[5]);
_guardEluneGUID = guard->GetGUID();
}
// summon the Voice of Elune
if (GameObject* altar = ObjectAccessor::GetGameObject(*me, _altarGUID))
{
if (Creature* voice = me->SummonCreature(NPC_VOICE_ELUNE, *altar, TEMPSUMMON_TIMED_DESPAWN, 30s))
_voiceEluneGUID = voice->GetGUID();
}
break;
case SAY_VOICE_ALTAR_15:
// move near the escort npc and continue dialogue
if (Creature* priestess = ObjectAccessor::GetCreature(*me, _secondPriestessGUID))
{
priestess->AI()->Talk(SAY_PRIESTESS_ALTAR_14);
priestess->GetMotionMaster()->MovePoint(0, wingThicketLocations[7]);
}
break;
case SAY_PRIESTESS_ALTAR_19:
// make the voice of elune leave
if (Creature* guard = ObjectAccessor::GetCreature(*me, _guardEluneGUID))
{
guard->GetMotionMaster()->MovePoint(0, wingThicketLocations[2]);
guard->DespawnOrUnsummon(4s);
}
break;
case SAY_PRIESTESS_ALTAR_20:
// make the first priestess leave
if (Creature* priestess = ObjectAccessor::GetCreature(*me, _firstPriestessGUID))
{
priestess->GetMotionMaster()->MovePoint(0, wingThicketLocations[0]);
priestess->DespawnOrUnsummon(4s);
}
break;
case SAY_PRIESTESS_ALTAR_21:
// make the second priestess leave
if (Creature* priestess = ObjectAccessor::GetCreature(*me, _secondPriestessGUID))
{
priestess->GetMotionMaster()->MovePoint(0, wingThicketLocations[1]);
priestess->DespawnOrUnsummon(4s);
}
break;
case DATA_EVENT_END:
// Turn towards the player
if (Player* player = GetPlayerForEscort())
{
me->SetFacingToObject(player);
Talk(SAY_RANSHALLA_END_1, player);
}
break;
case SAY_RANSHALLA_END_2:
// Turn towards the altar and kneel - quest complete
if (GameObject* altar = ObjectAccessor::GetGameObject(*me, _altarGUID))
{
me->SetFacingToObject(altar);
altar->ResetDoorOrButton();
}
me->SetStandState(UNIT_STAND_STATE_KNEEL);
if (Player* player = GetPlayerForEscort())
{
player->GroupEventHappens(QUEST_GUARDIANS_ALTAR, me);
Talk(SAY_RANSHALLA_END_2, player);
}
me->DespawnOrUnsummon(4s);
break;
}
}
Creature* GetSpeakerByEntry(int32 entry) override
{
switch (entry)
{
case NPC_RANSHALLA:
return me;
case NPC_VOICE_ELUNE:
return ObjectAccessor::GetCreature(*me, _voiceEluneGUID);
case NPC_PRIESTESS_DATA_1:
return ObjectAccessor::GetCreature(*me, _firstPriestessGUID);
case NPC_PRIESTESS_DATA_2:
return ObjectAccessor::GetCreature(*me, _secondPriestessGUID);
default:
return nullptr;
}
}
void UpdateEscortAI(uint32 diff) override
{
DialogueUpdate(diff);
if (_delayTimer)
{
if (_delayTimer <= diff)
{
SetEscortPaused(false);
_delayTimer = 0;
}
else
_delayTimer -= diff;
}
events.Update(diff);
if (events.ExecuteEvent() == EVENT_RESUME)
StartNextDialogueText(SAY_PRIESTESS_ALTAR_3);
EscortAI::UpdateEscortAI(diff);
}
void OnQuestAccept(Player* player, Quest const* quest) override
{
if (quest->GetQuestId() == QUEST_GUARDIANS_ALTAR)
{
Talk(SAY_QUEST_START);
me->SetFaction(FACTION_ESCORTEE_A_NEUTRAL_PASSIVE);
LoadPath(PATH_ESCORT_RANSHALLA);
Start(false, player->GetGUID(), quest);
}
}
private:
EventMap events;
};
CreatureAI* GetAI(Creature* creature) const override
{
return new npc_ranshallaAI(creature);
}
};
/*#####
# go_elune_fire
#####*/
class go_elune_fire : public GameObjectScript
{
public:
go_elune_fire() : GameObjectScript("go_elune_fire") { }
struct go_elune_fireAI : public GameObjectAI
{
go_elune_fireAI(GameObject* go) : GameObjectAI(go) { }
bool OnGossipHello(Player* /*player*/) override
{
// Check if we are using the torches or the altar
bool isAltar = false;
if (me->GetEntry() == GO_ELUNE_ALTAR)
isAltar = true;
if (Creature* ranshalla = GetClosestCreatureWithEntry(me, NPC_RANSHALLA, 10.0f))
{
if (npc_ranshalla::npc_ranshallaAI* escortAI = dynamic_cast(ranshalla->AI()))
escortAI->DoContinueEscort(isAltar);
}
me->SetFlag(GO_FLAG_NOT_SELECTABLE);
return false;
}
};
GameObjectAI* GetAI(GameObject* go) const override
{
return new go_elune_fireAI(go);
}
};
void AddSC_winterspring()
{
new npc_ranshalla();
new go_elune_fire();
}