mirror of
https://github.com/mod-playerbots/azerothcore-wotlk.git
synced 2026-01-21 12:47:07 +00:00
feat(Core/WorldState): init Scourge Invasion pre-wrath event (#22286)
Co-authored-by: Orozxy <crusadermeile@gmail.com> Co-authored-by: Killerwife <killerwife@gmail.com> Co-authored-by: Ryan Turner <AugustoMendes4426@gmail.com>
This commit is contained in:
6223
data/sql/updates/pending_db_world/rev_1748010106966597790.sql
Normal file
6223
data/sql/updates/pending_db_world/rev_1748010106966597790.sql
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
--
|
||||
-- Creates the reference loot (40110) for Haunted Memento (40110)
|
||||
DELETE FROM `reference_loot_template` WHERE `Entry` = 40110 AND `Item` = 40110;
|
||||
INSERT INTO `reference_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, `GroupId`, `MinCount`, `MaxCount`, `Comment`) VALUES
|
||||
(40110, 40110, 0, 100, 0, 1, 1, 1, 1, 'Scourge Invasion Event - Item: Haunted Memento');
|
||||
|
||||
-- Adds the reference loot (40110) above for the Haunted Memento (40110) to Ghoul Berserker (16141), Skeletal Shocktrooper (16298) and Spectral Soldier (16299)
|
||||
DELETE FROM `creature_loot_template` WHERE `Entry` IN (16141, 16298, 16299) AND `Item` = 40110 AND `Reference` = 40110;
|
||||
INSERT INTO `creature_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, `GroupId`, `MinCount`, `MaxCount`, `Comment`) VALUES
|
||||
(16141, 40110, 40110, 2, 0, 1, 1, 1, 1, 'Reference Loot: Scourge Invasion Event - Item: Haunted Memento'),
|
||||
(16298, 40110, 40110, 2, 0, 1, 1, 1, 1, 'Reference Loot: Scourge Invasion Event - Item: Haunted Memento'),
|
||||
(16299, 40110, 40110, 2, 0, 1, 1, 1, 1, 'Reference Loot: Scourge Invasion Event - Item: Haunted Memento');
|
||||
@@ -573,7 +573,7 @@ bool Condition::Meets(ConditionSourceInfo& sourceInfo)
|
||||
}
|
||||
case CONDITION_WORLD_SCRIPT:
|
||||
{
|
||||
condMeets = sWorldState->IsConditionFulfilled(static_cast<WorldStateCondition>(ConditionValue1), static_cast<WorldStateConditionState>(ConditionValue2));
|
||||
condMeets = sWorldState->IsConditionFulfilled(ConditionValue1, ConditionValue2);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -2511,6 +2511,13 @@ void WorldObject::GetGameObjectListWithEntryInGrid(std::list<GameObject*>& gameo
|
||||
Cell::VisitGridObjects(this, searcher, maxSearchRange);
|
||||
}
|
||||
|
||||
void WorldObject::GetGameObjectListWithEntryInGrid(std::list<GameObject*>& gameobjectList, std::vector<uint32> const& entries, float maxSearchRange) const
|
||||
{
|
||||
Acore::AllGameObjectsMatchingOneEntryInRange check(this, entries, maxSearchRange);
|
||||
Acore::GameObjectListSearcher searcher(this, gameobjectList, check);
|
||||
Cell::VisitGridObjects(this, searcher, maxSearchRange);
|
||||
}
|
||||
|
||||
void WorldObject::GetCreatureListWithEntryInGrid(std::list<Creature*>& creatureList, uint32 entry, float maxSearchRange) const
|
||||
{
|
||||
Acore::AllCreaturesOfEntryInRange check(this, entry, maxSearchRange);
|
||||
@@ -2518,6 +2525,13 @@ void WorldObject::GetCreatureListWithEntryInGrid(std::list<Creature*>& creatureL
|
||||
Cell::VisitGridObjects(this, searcher, maxSearchRange);
|
||||
}
|
||||
|
||||
void WorldObject::GetCreatureListWithEntryInGrid(std::list<Creature*>& creatureList, std::vector<uint32> const& entries, float maxSearchRange) const
|
||||
{
|
||||
Acore::AllCreaturesMatchingOneEntryInRange check(this, entries, maxSearchRange);
|
||||
Acore::CreatureListSearcher searcher(this, creatureList, check);
|
||||
Cell::VisitGridObjects(this, searcher, maxSearchRange);
|
||||
}
|
||||
|
||||
void WorldObject::GetDeadCreatureListInGrid(std::list<Creature*>& creaturedeadList, float maxSearchRange, bool alive /*= false*/) const
|
||||
{
|
||||
Acore::AllDeadCreaturesInRange check(this, maxSearchRange, alive);
|
||||
|
||||
@@ -604,7 +604,9 @@ public:
|
||||
|
||||
[[nodiscard]] Player* SelectNearestPlayer(float distance = 0) const;
|
||||
void GetGameObjectListWithEntryInGrid(std::list<GameObject*>& lList, uint32 uiEntry, float fMaxSearchRange) const;
|
||||
void GetGameObjectListWithEntryInGrid(std::list<GameObject*>& gameobjectList, std::vector<uint32> const& entries, float maxSearchRange) const;
|
||||
void GetCreatureListWithEntryInGrid(std::list<Creature*>& lList, uint32 uiEntry, float fMaxSearchRange) const;
|
||||
void GetCreatureListWithEntryInGrid(std::list<Creature*>& creatureList, std::vector<uint32> const& entries, float maxSearchRange) const;
|
||||
void GetDeadCreatureListInGrid(std::list<Creature*>& lList, float maxSearchRange, bool alive = false) const;
|
||||
|
||||
void DestroyForNearbyPlayers();
|
||||
|
||||
@@ -1447,6 +1447,24 @@ namespace Acore
|
||||
float m_fRange;
|
||||
};
|
||||
|
||||
class AllGameObjectsMatchingOneEntryInRange
|
||||
{
|
||||
public:
|
||||
AllGameObjectsMatchingOneEntryInRange(WorldObject const* object, std::vector<uint32> entries, float maxRange) : m_pObject(object), m_uiEntries(entries), m_fRange(maxRange) {}
|
||||
bool operator()(GameObject *go)
|
||||
{
|
||||
if (std::ranges::any_of( m_uiEntries, [go](uint32 entry) { return go->GetEntry() == entry; }) && m_pObject->IsWithinDist(go, m_fRange, false))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
WorldObject const* m_pObject;
|
||||
std::vector<uint32> m_uiEntries;
|
||||
float m_fRange;
|
||||
};
|
||||
|
||||
class AllCreaturesOfEntryInRange
|
||||
{
|
||||
public:
|
||||
@@ -1465,6 +1483,24 @@ namespace Acore
|
||||
float m_fRange;
|
||||
};
|
||||
|
||||
class AllCreaturesMatchingOneEntryInRange
|
||||
{
|
||||
public:
|
||||
AllCreaturesMatchingOneEntryInRange(WorldObject const* object, std::vector<uint32> entries, float maxRange) : m_pObject(object), m_uiEntries(entries), m_fRange(maxRange) {}
|
||||
bool operator()(Unit* unit)
|
||||
{
|
||||
if (std::ranges::any_of(m_uiEntries, [unit](uint32 entry) { return unit->GetEntry() == entry; }) && m_pObject->IsWithinDist(unit, m_fRange, false))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
WorldObject const* m_pObject;
|
||||
std::vector<uint32> m_uiEntries;
|
||||
float m_fRange;
|
||||
};
|
||||
|
||||
class MostHPMissingGroupInRange
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -31,6 +31,7 @@ enum AreaTableIDs : uint32
|
||||
AREA_ELWYNN_FOREST = 12,
|
||||
AREA_DUROTAR = 14,
|
||||
AREA_DUSTWALLOW_MARSH = 15,
|
||||
AREA_AZSHARA = 16,
|
||||
AREA_THE_BARRENS = 17,
|
||||
AREA_WESTERN_PLAGUELANDS = 28,
|
||||
AREA_STRANGLETHORN_VALE = 33,
|
||||
|
||||
@@ -15,13 +15,18 @@
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "AreaDefines.h"
|
||||
#include "CreatureAIImpl.h"
|
||||
#include "GameEventMgr.h"
|
||||
#include "Log.h"
|
||||
#include "MapMgr.h"
|
||||
#include "Player.h"
|
||||
#include "SharedDefines.h"
|
||||
#include "UnitAI.h"
|
||||
#include "Weather.h"
|
||||
#include "WorldState.h"
|
||||
#include "WorldStateDefines.h"
|
||||
#include <chrono>
|
||||
|
||||
WorldState* WorldState::instance()
|
||||
{
|
||||
@@ -87,12 +92,43 @@ void WorldState::Load()
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SAVE_ID_SCOURGE_INVASION:
|
||||
if (data.size())
|
||||
{
|
||||
try
|
||||
{
|
||||
uint32 state;
|
||||
loadStream >> state;
|
||||
m_siData.m_state = SIState(state);
|
||||
for (TimePoint& m_timer : m_siData.m_timers)
|
||||
{
|
||||
uint64 time;
|
||||
loadStream >> time;
|
||||
m_timer = TimePoint(std::chrono::milliseconds(time));
|
||||
}
|
||||
loadStream >> m_siData.m_battlesWon >> m_siData.m_lastAttackZone;
|
||||
for (unsigned int& i : m_siData.m_remaining)
|
||||
loadStream >> i;
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
LOG_ERROR("scripts", "WorldState::Load: Exception reading ScourgeInvasion data {}", e.what());
|
||||
m_siData.Reset();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
} while (result->NextRow());
|
||||
}
|
||||
StartSunsReachPhase(true);
|
||||
StartSunwellGatePhase();
|
||||
HandleSunsReachSubPhaseTransition(m_sunsReachData.m_subphaseMask, true);
|
||||
|
||||
if (m_siData.m_state == STATE_1_ENABLED)
|
||||
{
|
||||
StartScourgeInvasion(false);
|
||||
HandleDefendedZones();
|
||||
}
|
||||
}
|
||||
|
||||
void WorldState::LoadWorldStates()
|
||||
@@ -156,6 +192,12 @@ void WorldState::Save(WorldStateSaveIds saveId)
|
||||
SaveHelper(expansionData, SAVE_ID_QUEL_DANAS);
|
||||
break;
|
||||
}
|
||||
case SAVE_ID_SCOURGE_INVASION:
|
||||
{
|
||||
std::string siData = m_siData.GetData();
|
||||
SaveHelper(siData, SAVE_ID_SCOURGE_INVASION);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -167,7 +209,7 @@ void WorldState::SaveHelper(std::string& stringToSave, WorldStateSaveIds saveId)
|
||||
CharacterDatabase.Execute("INSERT INTO world_state(Id,Data) VALUES('{}','{}')", saveId, stringToSave.data());
|
||||
}
|
||||
|
||||
bool WorldState::IsConditionFulfilled(WorldStateCondition conditionId, WorldStateConditionState state) const
|
||||
bool WorldState::IsConditionFulfilled(uint32 conditionId, uint32 state) const
|
||||
{
|
||||
switch (conditionId)
|
||||
{
|
||||
@@ -179,6 +221,18 @@ bool WorldState::IsConditionFulfilled(WorldStateCondition conditionId, WorldStat
|
||||
case WORLD_STATE_CONDITION_THE_PURPLE_PRINCESS:
|
||||
case WORLD_STATE_CONDITION_THE_THUNDERCALLER:
|
||||
return _transportStates.at(conditionId) == state;
|
||||
case WORLD_STATE_SCOURGE_INVASION_WINTERSPRING:
|
||||
return GetSIRemaining(SI_REMAINING_WINTERSPRING) > 0;
|
||||
case WORLD_STATE_SCOURGE_INVASION_AZSHARA:
|
||||
return GetSIRemaining(SI_REMAINING_AZSHARA) > 0;
|
||||
case WORLD_STATE_SCOURGE_INVASION_BLASTED_LANDS:
|
||||
return GetSIRemaining(SI_REMAINING_BLASTED_LANDS) > 0;
|
||||
case WORLD_STATE_SCOURGE_INVASION_BURNING_STEPPES:
|
||||
return GetSIRemaining(SI_REMAINING_BURNING_STEPPES) > 0;
|
||||
case WORLD_STATE_SCOURGE_INVASION_TANARIS:
|
||||
return GetSIRemaining(SI_REMAINING_TANARIS) > 0;
|
||||
case WORLD_STATE_SCOURGE_INVASION_EASTERN_PLAGUELANDS:
|
||||
return GetSIRemaining(SI_REMAINING_EASTERN_PLAGUELANDS) > 0;
|
||||
default:
|
||||
LOG_ERROR("scripts", "WorldState::IsConditionFulfilled: Unhandled WorldStateCondition {}", conditionId);
|
||||
return false;
|
||||
@@ -190,6 +244,18 @@ void WorldState::HandleConditionStateChange(WorldStateCondition conditionId, Wor
|
||||
_transportStates[conditionId] = state;
|
||||
}
|
||||
|
||||
Map* WorldState::GetMap(uint32 mapId, Position const& invZone)
|
||||
{
|
||||
Map* map = sMapMgr->FindBaseMap(mapId);
|
||||
if (!map)
|
||||
LOG_ERROR("scripts",
|
||||
"ScourgeInvasionEvent::GetMap found no map with mapId {} , x: {}, y: {}.",
|
||||
mapId,
|
||||
invZone.GetPositionX(),
|
||||
invZone.GetPositionY());
|
||||
return map;
|
||||
}
|
||||
|
||||
void WorldState::HandleExternalEvent(WorldStateEvent eventId, uint32 param)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(_mutex);
|
||||
@@ -245,6 +311,32 @@ void WorldState::Update(uint32 diff)
|
||||
_adalSongOfBattleTimer -= diff;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_siData.m_state != STATE_0_DISABLED)
|
||||
{
|
||||
if (m_siData.m_broadcastTimer <= diff)
|
||||
{
|
||||
m_siData.m_broadcastTimer = 10000;
|
||||
BroadcastSIWorldstates();
|
||||
|
||||
if (m_siData.m_state == STATE_1_ENABLED)
|
||||
{
|
||||
for (auto& zone : m_siData.m_cityAttacks)
|
||||
{
|
||||
if (zone.second.zoneId == AREA_UNDERCITY)
|
||||
StartNewCityAttackIfTime(SI_TIMER_UNDERCITY, zone.second.zoneId);
|
||||
else if (zone.second.zoneId == AREA_STORMWIND_CITY)
|
||||
StartNewCityAttackIfTime(SI_TIMER_STORMWIND, zone.second.zoneId);
|
||||
}
|
||||
|
||||
TimePoint now = std::chrono::steady_clock::now();
|
||||
for (auto& zone : m_siData.m_activeInvasions)
|
||||
HandleActiveZone(GetTimerIdForZone(zone.second.zoneId), zone.second.zoneId, zone.second.remainingNecropoli, now);
|
||||
}
|
||||
}
|
||||
else
|
||||
m_siData.m_broadcastTimer -= diff;
|
||||
}
|
||||
}
|
||||
|
||||
void WorldState::HandlePlayerEnterZone(Player* player, AreaTableIDs zoneId)
|
||||
@@ -939,6 +1031,67 @@ std::string WorldState::GetSunsReachPrintout()
|
||||
return output;
|
||||
}
|
||||
|
||||
std::string WorldState::GetScourgeInvasionPrintout()
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m_siData.m_siMutex);
|
||||
std::string output = "Scourge Invasion Status:\n";
|
||||
|
||||
auto formatState = [this]() -> std::string
|
||||
{
|
||||
switch (m_siData.m_state)
|
||||
{
|
||||
case STATE_0_DISABLED:
|
||||
return "Disabled";
|
||||
case STATE_1_ENABLED:
|
||||
return "Enabled";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
};
|
||||
auto formatRemaining = [this](SIRemaining val, char const* name) { return " " + std::string(name) + ": " + std::to_string(m_siData.m_remaining[val]) + "\n"; };
|
||||
|
||||
output += "State: " + formatState() + " (" + std::to_string(static_cast<uint32>(m_siData.m_state)) + ")\n";
|
||||
output += "Battles Won: " + std::to_string(m_siData.m_battlesWon) + "\n";
|
||||
output += "Last Attack Zone ID: " + std::to_string(m_siData.m_lastAttackZone) + "\n";
|
||||
|
||||
output += "Zone Necropolis Count Remaining:\n";
|
||||
output += formatRemaining(SI_REMAINING_WINTERSPRING, "Winterspring");
|
||||
output += formatRemaining(SI_REMAINING_AZSHARA, "Azshara");
|
||||
output += formatRemaining(SI_REMAINING_BLASTED_LANDS, "Blasted Lands");
|
||||
output += formatRemaining(SI_REMAINING_BURNING_STEPPES, "Burning Steppes");
|
||||
output += formatRemaining(SI_REMAINING_TANARIS, "Tanaris");
|
||||
output += formatRemaining(SI_REMAINING_EASTERN_PLAGUELANDS, "Eastern Plaguelands");
|
||||
|
||||
output += "Zone Timers (time until next event):\n";
|
||||
TimePoint now = std::chrono::steady_clock::now();
|
||||
auto formatTimer = [this](SITimers timerId, char const* name, TimePoint now)
|
||||
{
|
||||
TimePoint tp = m_siData.m_timers[timerId];
|
||||
std::string timerStr;
|
||||
if (tp.time_since_epoch().count() == 0)
|
||||
timerStr = "0 (not set)";
|
||||
else if (tp <= now)
|
||||
timerStr = "Elapsed";
|
||||
else
|
||||
{
|
||||
auto diff = std::chrono::duration_cast<std::chrono::seconds>(tp - now);
|
||||
timerStr = std::to_string(diff.count()) + "s remaining";
|
||||
}
|
||||
return " " + std::string(name) + ": " + timerStr + "\n";
|
||||
};
|
||||
|
||||
output += formatTimer(SI_TIMER_WINTERSPRING, "Winterspring Invasion", now);
|
||||
output += formatTimer(SI_TIMER_AZSHARA, "Azshara Invasion", now);
|
||||
output += formatTimer(SI_TIMER_BLASTED_LANDS, "Blasted Lands Invasion", now);
|
||||
output += formatTimer(SI_TIMER_BURNING_STEPPES, "Burning Steppes Invasion", now);
|
||||
output += formatTimer(SI_TIMER_TANARIS, "Tanaris Invasion", now);
|
||||
output += formatTimer(SI_TIMER_EASTERN_PLAGUELANDS, "Eastern Plaguelands Invasion", now);
|
||||
output += formatTimer(SI_TIMER_STORMWIND, "Stormwind City Attack", now);
|
||||
output += formatTimer(SI_TIMER_UNDERCITY, "Undercity Attack", now);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
std::string SunsReachReclamationData::GetData()
|
||||
{
|
||||
std::string output = std::to_string(m_phase) + " " + std::to_string(m_subphaseMask);
|
||||
@@ -1087,8 +1240,726 @@ uint32 SunsReachReclamationData::GetSunwellGatePercentage(uint32 gate)
|
||||
return percentage < 0 ? 0 : uint32(percentage);
|
||||
}
|
||||
|
||||
void WorldState::SetScourgeInvasionState(SIState state)
|
||||
{
|
||||
SIState oldState = m_siData.m_state;
|
||||
if (oldState == state)
|
||||
return;
|
||||
|
||||
m_siData.m_state = state;
|
||||
if (oldState == STATE_0_DISABLED)
|
||||
StartScourgeInvasion(true);
|
||||
else if (state == STATE_0_DISABLED)
|
||||
StopScourgeInvasion();
|
||||
Save(SAVE_ID_SCOURGE_INVASION);
|
||||
}
|
||||
|
||||
void WorldState::SendScourgeInvasionMail()
|
||||
{
|
||||
QueryResult result = CharacterDatabase.Query("SELECT guid FROM characters WHERE level >= 50");
|
||||
if (result)
|
||||
{
|
||||
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
|
||||
MailDraft draft(MAIL_TEMPLATE_ARGENT_DAWN_NEEDS_YOUR_HELP);
|
||||
uint32 count = 0;
|
||||
do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
ObjectGuid playerGUID = ObjectGuid::Create<HighGuid::Player>(fields[0].Get<uint32>());
|
||||
|
||||
// Add item manually. SendMailTo does not add items for offline players
|
||||
if (Item* item = Item::CreateItem(ITEM_A_LETTER_FROM_THE_KEEPER_OF_THE_ROLLS, 1))
|
||||
{
|
||||
item->SaveToDB(trans);
|
||||
draft.AddItem(item);
|
||||
}
|
||||
|
||||
draft.SendMailTo(trans, MailReceiver(playerGUID.GetCounter()), NPC_ARGENT_EMISSARY, MAIL_CHECK_MASK_HAS_BODY);
|
||||
++count;
|
||||
} while (result->NextRow());
|
||||
CharacterDatabase.CommitTransaction(trans);
|
||||
LOG_INFO("WorldState", "SendScourgeInvasionMail sent to {} characters", count);
|
||||
}
|
||||
}
|
||||
|
||||
void WorldState::StartScourgeInvasion(bool sendMail)
|
||||
{
|
||||
sGameEventMgr->StartEvent(GAME_EVENT_SCOURGE_INVASION, false);
|
||||
|
||||
if (sendMail)
|
||||
SendScourgeInvasionMail();
|
||||
|
||||
BroadcastSIWorldstates();
|
||||
if (m_siData.m_state == STATE_1_ENABLED)
|
||||
{
|
||||
for (auto& zone : m_siData.m_cityAttacks)
|
||||
{
|
||||
if (zone.second.zoneId == AREA_UNDERCITY)
|
||||
StartNewCityAttackIfTime(SI_TIMER_UNDERCITY, zone.second.zoneId);
|
||||
else if (zone.second.zoneId == AREA_STORMWIND_CITY)
|
||||
StartNewCityAttackIfTime(SI_TIMER_STORMWIND, zone.second.zoneId);
|
||||
}
|
||||
|
||||
// randomization of init so that not every invasion starts the same way
|
||||
std::vector<uint32> randomIds;
|
||||
randomIds.reserve(m_siData.m_activeInvasions.size());
|
||||
for (auto const& [zoneId, _] : m_siData.m_activeInvasions)
|
||||
randomIds.push_back(zoneId);
|
||||
Acore::Containers::RandomShuffle(randomIds);
|
||||
for (uint32 id : randomIds)
|
||||
OnEnable(m_siData.m_activeInvasions[id]);
|
||||
}
|
||||
}
|
||||
|
||||
ScourgeInvasionData::ScourgeInvasionData()
|
||||
: m_state(STATE_0_DISABLED), m_battlesWon(0), m_lastAttackZone(0), m_remaining{}, m_broadcastTimer(10000)
|
||||
{
|
||||
m_activeInvasions.emplace(
|
||||
AREA_WINTERSPRING,
|
||||
InvasionZone{
|
||||
.map = MAP_KALIMDOR,
|
||||
.zoneId = AREA_WINTERSPRING,
|
||||
.necropolisCount = 3,
|
||||
.remainingNecropoli = SI_REMAINING_WINTERSPRING,
|
||||
.mouth = { Position{7736.56f, -4033.75f, 696.327f, 5.51524f} }
|
||||
}
|
||||
);
|
||||
m_activeInvasions.emplace(
|
||||
AREA_TANARIS,
|
||||
InvasionZone{
|
||||
.map = MAP_KALIMDOR,
|
||||
.zoneId = AREA_TANARIS,
|
||||
.necropolisCount = 3,
|
||||
.remainingNecropoli = SI_REMAINING_TANARIS,
|
||||
.mouth = { Position{-8352.68f, -3972.68f, 10.0753f, 2.14675f} }
|
||||
}
|
||||
);
|
||||
m_activeInvasions.emplace(
|
||||
AREA_AZSHARA,
|
||||
InvasionZone{
|
||||
.map = MAP_KALIMDOR,
|
||||
.zoneId = AREA_AZSHARA,
|
||||
.necropolisCount = 2,
|
||||
.remainingNecropoli = SI_REMAINING_AZSHARA,
|
||||
.mouth = { Position{3273.75f, -4276.98f, 125.509f, 5.44543f} }
|
||||
}
|
||||
);
|
||||
m_activeInvasions.emplace(
|
||||
AREA_BLASTED_LANDS,
|
||||
InvasionZone{
|
||||
.map = MAP_EASTERN_KINGDOMS,
|
||||
.zoneId = AREA_BLASTED_LANDS,
|
||||
.necropolisCount = 2,
|
||||
.remainingNecropoli = SI_REMAINING_BLASTED_LANDS,
|
||||
.mouth = { Position{-11429.3f, -3327.82f, 7.73628f, 1.0821f} }
|
||||
}
|
||||
);
|
||||
m_activeInvasions.emplace(
|
||||
AREA_EASTERN_PLAGUELANDS,
|
||||
InvasionZone{
|
||||
.map = MAP_EASTERN_KINGDOMS,
|
||||
.zoneId = AREA_EASTERN_PLAGUELANDS,
|
||||
.necropolisCount = 2,
|
||||
.remainingNecropoli = SI_REMAINING_EASTERN_PLAGUELANDS,
|
||||
.mouth = { Position{2014.55f, -4934.52f, 73.9846f, 0.0698132f} }
|
||||
}
|
||||
);
|
||||
m_activeInvasions.emplace(
|
||||
AREA_BURNING_STEPPES,
|
||||
InvasionZone{
|
||||
.map = MAP_EASTERN_KINGDOMS,
|
||||
.zoneId = AREA_BURNING_STEPPES,
|
||||
.necropolisCount = 2,
|
||||
.remainingNecropoli = SI_REMAINING_BURNING_STEPPES,
|
||||
.mouth = { Position{-8229.53f, -1118.11f, 144.012f, 6.17846f} }
|
||||
}
|
||||
);
|
||||
|
||||
m_cityAttacks.emplace(
|
||||
AREA_UNDERCITY,
|
||||
CityAttack{
|
||||
.map = MAP_EASTERN_KINGDOMS,
|
||||
.zoneId = AREA_UNDERCITY,
|
||||
.pallid = {
|
||||
Position{1595.87f, 440.539f, -46.3349f, 2.28207f}, // Royal Quarter
|
||||
Position{1659.2f, 265.988f, -62.1788f, 3.64283f} // Trade Quarter
|
||||
}
|
||||
}
|
||||
);
|
||||
m_cityAttacks.emplace(
|
||||
AREA_STORMWIND_CITY,
|
||||
CityAttack{
|
||||
.map = MAP_EASTERN_KINGDOMS,
|
||||
.zoneId = AREA_STORMWIND_CITY,
|
||||
.pallid = {
|
||||
Position{-8578.15f, 886.382f, 87.3148f, 0.586275f}, // Stormwind Keep
|
||||
Position{-8578.15f, 886.382f, 87.3148f, 0.586275f} // Trade District
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void ScourgeInvasionData::Reset()
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m_siMutex);
|
||||
for (auto& timepoint : m_timers)
|
||||
timepoint = TimePoint();
|
||||
|
||||
m_battlesWon = 0;
|
||||
m_lastAttackZone = 0;
|
||||
m_broadcastTimer = 10000;
|
||||
memset(m_remaining, 0, sizeof(m_remaining));
|
||||
}
|
||||
|
||||
std::string ScourgeInvasionData::GetData()
|
||||
{
|
||||
std::string output = std::to_string(m_state) + " ";
|
||||
for (auto& timer : m_timers)
|
||||
output += std::to_string(timer.time_since_epoch().count()) + " ";
|
||||
output += std::to_string(m_battlesWon) + " " + std::to_string(m_lastAttackZone) + " ";
|
||||
for (auto& remaining : m_remaining)
|
||||
output += std::to_string(remaining) + " ";
|
||||
return output;
|
||||
}
|
||||
|
||||
void WorldState::StopScourgeInvasion()
|
||||
{
|
||||
sGameEventMgr->StopEvent(GAME_EVENT_SCOURGE_INVASION);
|
||||
sGameEventMgr->StopEvent(GAME_EVENT_SCOURGE_INVASION_WINTERSPRING);
|
||||
sGameEventMgr->StopEvent(GAME_EVENT_SCOURGE_INVASION_TANARIS);
|
||||
sGameEventMgr->StopEvent(GAME_EVENT_SCOURGE_INVASION_AZSHARA);
|
||||
sGameEventMgr->StopEvent(GAME_EVENT_SCOURGE_INVASION_BLASTED_LANDS);
|
||||
sGameEventMgr->StopEvent(GAME_EVENT_SCOURGE_INVASION_EASTERN_PLAGUELANDS);
|
||||
sGameEventMgr->StopEvent(GAME_EVENT_SCOURGE_INVASION_BURNING_STEPPES);
|
||||
BroadcastSIWorldstates();
|
||||
m_siData.Reset();
|
||||
|
||||
for (auto& [_, cityData] : m_siData.m_cityAttacks)
|
||||
OnDisable(cityData);
|
||||
|
||||
for (auto& [_, zoneData] : m_siData.m_activeInvasions)
|
||||
OnDisable(zoneData);
|
||||
}
|
||||
|
||||
uint32 WorldState::GetSIRemaining(SIRemaining remaining) const
|
||||
{
|
||||
return m_siData.m_remaining[remaining];
|
||||
}
|
||||
|
||||
uint32 WorldState::GetSIRemainingByZone(uint32 zoneId) const
|
||||
{
|
||||
SIRemaining remainingId;
|
||||
switch (zoneId)
|
||||
{
|
||||
case AREA_WINTERSPRING: remainingId = SI_REMAINING_WINTERSPRING; break;
|
||||
case AREA_AZSHARA: remainingId = SI_REMAINING_AZSHARA; break;
|
||||
case AREA_EASTERN_PLAGUELANDS: remainingId = SI_REMAINING_EASTERN_PLAGUELANDS; break;
|
||||
case AREA_BLASTED_LANDS: remainingId = SI_REMAINING_BLASTED_LANDS; break;
|
||||
case AREA_BURNING_STEPPES: remainingId = SI_REMAINING_BURNING_STEPPES; break;
|
||||
case AREA_TANARIS: remainingId = SI_REMAINING_TANARIS; break;
|
||||
default:
|
||||
LOG_ERROR("WorldState", "GetSIRemainingByZone called with invalid zone ID: {}", zoneId);
|
||||
return 0;
|
||||
}
|
||||
return GetSIRemaining(remainingId);
|
||||
}
|
||||
|
||||
void WorldState::SetSIRemaining(SIRemaining remaining, uint32 value)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m_siData.m_siMutex);
|
||||
m_siData.m_remaining[remaining] = value;
|
||||
Save(SAVE_ID_SCOURGE_INVASION);
|
||||
}
|
||||
|
||||
TimePoint WorldState::GetSITimer(SITimers timer)
|
||||
{
|
||||
return m_siData.m_timers[timer];
|
||||
}
|
||||
|
||||
void WorldState::SetSITimer(SITimers timer, TimePoint timePoint)
|
||||
{
|
||||
m_siData.m_timers[timer] = timePoint;
|
||||
}
|
||||
|
||||
uint32 WorldState::GetBattlesWon()
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m_siData.m_siMutex);
|
||||
return m_siData.m_battlesWon;
|
||||
}
|
||||
|
||||
void WorldState::AddBattlesWon(int32 count)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m_siData.m_siMutex);
|
||||
m_siData.m_battlesWon += count;
|
||||
HandleDefendedZones();
|
||||
Save(SAVE_ID_SCOURGE_INVASION);
|
||||
}
|
||||
|
||||
uint32 WorldState::GetLastAttackZone()
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m_siData.m_siMutex);
|
||||
return m_siData.m_lastAttackZone;
|
||||
}
|
||||
|
||||
void WorldState::SetLastAttackZone(uint32 zoneId)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m_siData.m_siMutex);
|
||||
m_siData.m_lastAttackZone = zoneId;
|
||||
}
|
||||
|
||||
void WorldState::BroadcastSIWorldstates()
|
||||
{
|
||||
uint32 victories = GetBattlesWon();
|
||||
|
||||
uint32 remainingAzshara = GetSIRemaining(SI_REMAINING_AZSHARA);
|
||||
uint32 remainingBlastedLands = GetSIRemaining(SI_REMAINING_BLASTED_LANDS);
|
||||
uint32 remainingBurningSteppes = GetSIRemaining(SI_REMAINING_BURNING_STEPPES);
|
||||
uint32 remainingEasternPlaguelands = GetSIRemaining(SI_REMAINING_EASTERN_PLAGUELANDS);
|
||||
uint32 remainingTanaris = GetSIRemaining(SI_REMAINING_TANARIS);
|
||||
uint32 remainingWinterspring = GetSIRemaining(SI_REMAINING_WINTERSPRING);
|
||||
|
||||
sMapMgr->DoForAllMaps([&](Map* map) -> void
|
||||
{
|
||||
switch (map->GetId())
|
||||
{
|
||||
case MAP_EASTERN_KINGDOMS:
|
||||
case MAP_KALIMDOR:
|
||||
map->DoForAllPlayers([&](Player* pl)
|
||||
{
|
||||
// do not process players which are not in world
|
||||
if (!pl->IsInWorld())
|
||||
return;
|
||||
|
||||
pl->SendUpdateWorldState(WORLD_STATE_SCOURGE_INVASION_AZSHARA, remainingAzshara > 0 ? 1 : 0);
|
||||
pl->SendUpdateWorldState(WORLD_STATE_SCOURGE_INVASION_BLASTED_LANDS, remainingBlastedLands > 0 ? 1 : 0);
|
||||
pl->SendUpdateWorldState(WORLD_STATE_SCOURGE_INVASION_BURNING_STEPPES, remainingBurningSteppes > 0 ? 1 : 0);
|
||||
pl->SendUpdateWorldState(WORLD_STATE_SCOURGE_INVASION_EASTERN_PLAGUELANDS, remainingEasternPlaguelands > 0 ? 1 : 0);
|
||||
pl->SendUpdateWorldState(WORLD_STATE_SCOURGE_INVASION_TANARIS, remainingTanaris > 0 ? 1 : 0);
|
||||
pl->SendUpdateWorldState(WORLD_STATE_SCOURGE_INVASION_WINTERSPRING, remainingWinterspring > 0 ? 1 : 0);
|
||||
|
||||
pl->SendUpdateWorldState(WORLD_STATE_SCOURGE_INVASION_VICTORIES, victories);
|
||||
pl->SendUpdateWorldState(WORLD_STATE_SCOURGE_INVASION_NECROPOLIS_AZSHARA, remainingAzshara);
|
||||
pl->SendUpdateWorldState(WORLD_STATE_SCOURGE_INVASION_NECROPOLIS_BLASTED_LANDS, remainingBlastedLands);
|
||||
pl->SendUpdateWorldState(WORLD_STATE_SCOURGE_INVASION_NECROPOLIS_BURNING_STEPPES, remainingBurningSteppes);
|
||||
pl->SendUpdateWorldState(WORLD_STATE_SCOURGE_INVASION_NECROPOLIS_EASTERN_PLAGUELANDS, remainingEasternPlaguelands);
|
||||
pl->SendUpdateWorldState(WORLD_STATE_SCOURGE_INVASION_NECROPOLIS_TANARIS, remainingTanaris);
|
||||
pl->SendUpdateWorldState(WORLD_STATE_SCOURGE_INVASION_NECROPOLIS_WINTERSPRING, remainingWinterspring);
|
||||
});
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void WorldState::HandleDefendedZones()
|
||||
{
|
||||
if (m_siData.m_battlesWon < 50)
|
||||
{
|
||||
sGameEventMgr->StopEvent(GAME_EVENT_SCOURGE_INVASION_50_INVASIONS);
|
||||
sGameEventMgr->StopEvent(GAME_EVENT_SCOURGE_INVASION_100_INVASIONS);
|
||||
sGameEventMgr->StopEvent(GAME_EVENT_SCOURGE_INVASION_150_INVASIONS);
|
||||
}
|
||||
else if (m_siData.m_battlesWon >= 50 && m_siData.m_battlesWon < 100)
|
||||
sGameEventMgr->StartEvent(GAME_EVENT_SCOURGE_INVASION_50_INVASIONS);
|
||||
else if (m_siData.m_battlesWon >= 100 && m_siData.m_battlesWon < 150)
|
||||
{
|
||||
sGameEventMgr->StopEvent(GAME_EVENT_SCOURGE_INVASION_50_INVASIONS);
|
||||
sGameEventMgr->StartEvent(GAME_EVENT_SCOURGE_INVASION_100_INVASIONS);
|
||||
}
|
||||
else if (m_siData.m_battlesWon >= 150)
|
||||
{
|
||||
sGameEventMgr->StopEvent(GAME_EVENT_SCOURGE_INVASION);
|
||||
sGameEventMgr->StopEvent(GAME_EVENT_SCOURGE_INVASION_50_INVASIONS);
|
||||
sGameEventMgr->StopEvent(GAME_EVENT_SCOURGE_INVASION_100_INVASIONS);
|
||||
sGameEventMgr->StartEvent(GAME_EVENT_SCOURGE_INVASION_INVASIONS_DONE);
|
||||
sGameEventMgr->StartEvent(GAME_EVENT_SCOURGE_INVASION_150_INVASIONS);
|
||||
}
|
||||
}
|
||||
|
||||
void WorldState::StartZoneEvent(SIZoneIds eventId)
|
||||
{
|
||||
switch (eventId)
|
||||
{
|
||||
case SI_ZONE_AZSHARA: StartNewInvasion(AREA_AZSHARA); break;
|
||||
case SI_ZONE_BLASTED_LANDS: StartNewInvasion(AREA_BLASTED_LANDS); break;
|
||||
case SI_ZONE_BURNING_STEPPES: StartNewInvasion(AREA_BURNING_STEPPES); break;
|
||||
case SI_ZONE_EASTERN_PLAGUELANDS: StartNewInvasion(AREA_EASTERN_PLAGUELANDS); break;
|
||||
case SI_ZONE_TANARIS: StartNewInvasion(AREA_TANARIS); break;
|
||||
case SI_ZONE_WINTERSPRING: StartNewInvasion(AREA_WINTERSPRING); break;
|
||||
case SI_ZONE_STORMWIND: StartNewCityAttack(AREA_STORMWIND_CITY); break;
|
||||
case SI_ZONE_UNDERCITY: StartNewCityAttack(AREA_UNDERCITY); break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void WorldState::StartNewInvasionIfTime(uint32 attackTimeVar, uint32 zoneId)
|
||||
{
|
||||
TimePoint now = std::chrono::steady_clock::now();
|
||||
|
||||
// Not yet time
|
||||
if (now < sWorldState->GetSITimer(SITimers(attackTimeVar)))
|
||||
return;
|
||||
|
||||
StartNewInvasion(zoneId);
|
||||
}
|
||||
|
||||
void WorldState::StartNewCityAttackIfTime(uint32 attackTimeVar, uint32 zoneId)
|
||||
{
|
||||
TimePoint now = std::chrono::steady_clock::now();
|
||||
|
||||
// Not yet time
|
||||
if (now < sWorldState->GetSITimer(SITimers(attackTimeVar)))
|
||||
return;
|
||||
|
||||
StartNewCityAttack(zoneId);
|
||||
uint32 cityAttackTimer = urand(CITY_ATTACK_TIMER_MIN, CITY_ATTACK_TIMER_MAX);
|
||||
TimePoint next_attack = now + std::chrono::seconds(cityAttackTimer);
|
||||
sWorldState->SetSITimer(SITimers(attackTimeVar), next_attack);
|
||||
}
|
||||
|
||||
void WorldState::StartNewInvasion(uint32 zoneId)
|
||||
{
|
||||
if (IsActiveZone(zoneId))
|
||||
return;
|
||||
|
||||
// Don't attack same zone as before.
|
||||
if (zoneId == sWorldState->GetLastAttackZone())
|
||||
return;
|
||||
|
||||
// If we have at least one victory and more than 1 active zones stop here.
|
||||
if (GetActiveZones() > 1 && sWorldState->GetBattlesWon() > 0)
|
||||
return;
|
||||
|
||||
LOG_DEBUG("gameevent", "Scourge Invasion Event: Starting new invasion in {}.", zoneId);
|
||||
|
||||
ScourgeInvasionData::InvasionZone& zone = m_siData.m_activeInvasions[zoneId];
|
||||
|
||||
Map* map = GetMap(zone.map, zone.mouth[0]);
|
||||
|
||||
if (!map)
|
||||
{
|
||||
LOG_ERROR("gameevent", "ScourgeInvasionEvent::StartNewInvasion unable to access required map ({}). Retrying next update.", zone.map);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (zoneId)
|
||||
{
|
||||
case AREA_AZSHARA: sGameEventMgr->StartEvent(GAME_EVENT_SCOURGE_INVASION_AZSHARA); break;
|
||||
case AREA_BLASTED_LANDS: sGameEventMgr->StartEvent(GAME_EVENT_SCOURGE_INVASION_BLASTED_LANDS); break;
|
||||
case AREA_BURNING_STEPPES: sGameEventMgr->StartEvent(GAME_EVENT_SCOURGE_INVASION_BURNING_STEPPES); break;
|
||||
case AREA_EASTERN_PLAGUELANDS: sGameEventMgr->StartEvent(GAME_EVENT_SCOURGE_INVASION_EASTERN_PLAGUELANDS); break;
|
||||
case AREA_TANARIS: sGameEventMgr->StartEvent(GAME_EVENT_SCOURGE_INVASION_TANARIS); break;
|
||||
case AREA_WINTERSPRING: sGameEventMgr->StartEvent(GAME_EVENT_SCOURGE_INVASION_WINTERSPRING); break;
|
||||
default:
|
||||
LOG_ERROR("gameevent", "ScourgeInvasionEvent::StartNewInvasion unknown zoneId {}.", zoneId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (map)
|
||||
SummonMouth(map, zone, zone.mouth[0], true);
|
||||
}
|
||||
|
||||
void WorldState::StartNewCityAttack(uint32 zoneId)
|
||||
{
|
||||
LOG_DEBUG("gameevent", "Scourge Invasion Event: Starting new City attack in zone {}.", zoneId);
|
||||
|
||||
ScourgeInvasionData::CityAttack& zone = m_siData.m_cityAttacks[zoneId];
|
||||
|
||||
uint32 SpawnLocationID = urand(0, static_cast<uint32>(zone.pallid.size() - 1));
|
||||
|
||||
Map* map = GetMap(zone.map, zone.pallid[SpawnLocationID]);
|
||||
|
||||
// If any of the required maps are not available we return. Will cause the invasion to be started
|
||||
// on next update instead
|
||||
if (!map)
|
||||
{
|
||||
LOG_ERROR("gameevent", "ScourgeInvasionEvent::StartNewCityAttackIfTime unable to access required map (%{}). Retrying next update.", zone.map);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_siData.m_pendingPallids.find(zoneId) != m_siData.m_pendingPallids.end())
|
||||
return;
|
||||
|
||||
if (map && SummonPallid(map, zone, zone.pallid[SpawnLocationID], SpawnLocationID))
|
||||
LOG_DEBUG("gameevent", "ScourgeInvasionEvent::StartNewCityAttackIfTime pallid spawned in {}.", zone.map);
|
||||
else
|
||||
LOG_DEBUG("gameevent", "ScourgeInvasionEvent::StartNewCityAttackIfTime unable to spawn pallid in {}.", zone.map);
|
||||
}
|
||||
|
||||
bool WorldState::ResumeInvasion(ScourgeInvasionData::InvasionZone& zone)
|
||||
{
|
||||
// Dont have a save variable to know which necropolises had already been destroyed, so we
|
||||
// just summon the same amount, but not necessarily the same necropolises
|
||||
LOG_DEBUG("gameevent", "Scourge Invasion Event: Resuming Scourge invasion in zone {}", zone.zoneId);
|
||||
|
||||
uint32 num_necropolises_remaining = sWorldState->GetSIRemaining(SIRemaining(zone.remainingNecropoli));
|
||||
|
||||
// Just making sure we can access all maps before starting the invasion
|
||||
for (uint32 i = 0; i < num_necropolises_remaining; i++)
|
||||
{
|
||||
if (!GetMap(zone.map, zone.mouth[0]))
|
||||
{
|
||||
LOG_ERROR("gameevent", "ScourgeInvasionEvent::ResumeInvasion map {} not accessible. Retry next update.", zone.map);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Map* mapPtr = GetMap(zone.map, zone.mouth[0]);
|
||||
if (!mapPtr)
|
||||
{
|
||||
LOG_ERROR("gameevent", "ScourgeInvasionEvent::ResumeInvasion failed getting map, even after making sure they were loaded....");
|
||||
return false;
|
||||
}
|
||||
|
||||
SummonMouth(mapPtr, zone, zone.mouth[0], false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WorldState::SummonMouth(Map* map, ScourgeInvasionData::InvasionZone& zone, Position position, bool newInvasion)
|
||||
{
|
||||
AddPendingInvasion(zone.zoneId);
|
||||
// Remove old mouth if required.
|
||||
if (Creature* existingMouth = map->GetCreature(zone.mouthGuid))
|
||||
existingMouth->AddObjectToRemoveList();
|
||||
|
||||
if (Creature* mouth = map->SummonCreature(NPC_HERALD_OF_THE_LICH_KING, position))
|
||||
{
|
||||
mouth->GetAI()->DoAction(EVENT_HERALD_OF_THE_LICH_KING_ZONE_START);
|
||||
sWorldState->SetMouthGuid(zone.zoneId, mouth->GetGUID());
|
||||
if (newInvasion)
|
||||
sWorldState->SetSIRemaining(SIRemaining(zone.remainingNecropoli), zone.necropolisCount);
|
||||
}
|
||||
sWorldState->RemovePendingInvasion(zone.zoneId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
enum PallidHorrorPaths
|
||||
{
|
||||
PATH_STORMWIND_KEEP = 163941,
|
||||
PATH_STORMWIND_TRADE_DISTRICT = 163942,
|
||||
PATH_UNDERCITY_TRADE_QUARTER = 163943,
|
||||
PATH_UNDERCITY_ROYAL_QUARTER = 163944,
|
||||
};
|
||||
|
||||
bool WorldState::SummonPallid(Map* map, ScourgeInvasionData::CityAttack& zone, const Position& position, uint32 spawnLoc)
|
||||
{
|
||||
AddPendingPallid(zone.zoneId);
|
||||
// Remove old pallid if required.
|
||||
uint32 pathID = 0;
|
||||
if (Creature* existingPallid = map->GetCreature(zone.pallidGuid))
|
||||
existingPallid->AddObjectToRemoveList();
|
||||
|
||||
// if (Creature* pallid = map->SummonCreature(RAND(NPC_PALLID_HORROR, NPC_PATCHWORK_TERROR), position))
|
||||
if (Creature* pallid = map->SummonCreature(NPC_PALLID_HORROR, position))
|
||||
{
|
||||
pallid->GetMotionMaster()->Clear(false);
|
||||
if (pallid->GetZoneId() == AREA_UNDERCITY)
|
||||
pathID = spawnLoc == 0 ? PATH_UNDERCITY_ROYAL_QUARTER : PATH_UNDERCITY_TRADE_QUARTER;
|
||||
else
|
||||
pathID = spawnLoc == 0 ? PATH_STORMWIND_KEEP : PATH_STORMWIND_TRADE_DISTRICT;
|
||||
|
||||
pallid->GetMotionMaster()->MovePath(pathID, false);
|
||||
|
||||
sWorldState->SetPallidGuid(zone.zoneId, pallid->GetGUID());
|
||||
}
|
||||
sWorldState->RemovePendingPallid(zone.zoneId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void WorldState::HandleActiveZone(uint32 attackTimeVar, uint32 zoneId, uint32 remainingVar, TimePoint now)
|
||||
{
|
||||
TimePoint timePoint = sWorldState->GetSITimer(SITimers(attackTimeVar));
|
||||
|
||||
ScourgeInvasionData::InvasionZone& zone = m_siData.m_activeInvasions[zoneId];
|
||||
|
||||
Map* map = sMapMgr->FindMap(zone.map, 0);
|
||||
|
||||
if (zone.zoneId != zoneId)
|
||||
return;
|
||||
|
||||
uint32 remaining = GetSIRemaining(SIRemaining(remainingVar));
|
||||
|
||||
// Calculate the next possible attack between ZONE_ATTACK_TIMER_MIN and ZONE_ATTACK_TIMER_MAX.
|
||||
uint32 zoneAttackTimer = urand(ZONE_ATTACK_TIMER_MIN, ZONE_ATTACK_TIMER_MAX);
|
||||
TimePoint next_attack = now + std::chrono::seconds(zoneAttackTimer);
|
||||
uint64 timeToNextAttack = std::chrono::duration_cast<std::chrono::minutes>(next_attack-now).count();
|
||||
|
||||
if (zone.mouthGuid)
|
||||
{
|
||||
// Handles the inactive zone, without a Mouth of Kel'Thuzad summoned (which spawns the whole zone event).
|
||||
Creature* mouth = map->GetCreature(zone.mouthGuid);
|
||||
if (!mouth)
|
||||
sWorldState->SetMouthGuid(zone.zoneId, ObjectGuid()); // delays spawning until next tick
|
||||
// Handles the active zone that has no necropolis left.
|
||||
else if (timePoint < now && remaining == 0)
|
||||
{
|
||||
sWorldState->SetSITimer(SITimers(attackTimeVar), next_attack);
|
||||
sWorldState->AddBattlesWon(1);
|
||||
sWorldState->SetLastAttackZone(zoneId);
|
||||
|
||||
LOG_INFO("gameevent", "[Scourge Invasion Event] The Scourge has been defeated in {}, next attack starting in {} minutes.", zoneId, timeToNextAttack);
|
||||
LOG_DEBUG("gameevent", "[Scourge Invasion Event] {} victories", sWorldState->GetBattlesWon());
|
||||
|
||||
if (mouth)
|
||||
mouth->GetAI()->DoAction(EVENT_HERALD_OF_THE_LICH_KING_ZONE_STOP);
|
||||
else
|
||||
LOG_ERROR("gameevent", "ScourgeInvasionEvent::HandleActiveZone ObjectGuid {} not found.", zone.mouthGuid.ToString());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If more than one zones are alreay being attacked, set the timer again to ZONE_ATTACK_TIMER.
|
||||
if (GetActiveZones() > 1)
|
||||
sWorldState->SetSITimer(SITimers(attackTimeVar), next_attack);
|
||||
|
||||
// Try to start the zone if attackTimeVar is 0.
|
||||
StartNewInvasionIfTime(attackTimeVar, zoneId);
|
||||
}
|
||||
}
|
||||
|
||||
void WorldState::SetPallidGuid(uint32 zoneId, ObjectGuid guid)
|
||||
{
|
||||
m_siData.m_cityAttacks[zoneId].pallidGuid = guid;
|
||||
}
|
||||
|
||||
void WorldState::SetMouthGuid(uint32 zoneId, ObjectGuid guid)
|
||||
{
|
||||
m_siData.m_activeInvasions[zoneId].mouthGuid = guid;
|
||||
}
|
||||
|
||||
void WorldState::AddPendingInvasion(uint32 zoneId)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m_siData.m_siMutex);
|
||||
m_siData.m_pendingInvasions.insert(zoneId);
|
||||
}
|
||||
|
||||
void WorldState::RemovePendingInvasion(uint32 zoneId)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m_siData.m_siMutex);
|
||||
m_siData.m_pendingInvasions.erase(zoneId);
|
||||
}
|
||||
|
||||
void WorldState::AddPendingPallid(uint32 zoneId)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m_siData.m_siMutex);
|
||||
m_siData.m_pendingPallids.insert(zoneId);
|
||||
}
|
||||
|
||||
void WorldState::RemovePendingPallid(uint32 zoneId)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m_siData.m_siMutex);
|
||||
m_siData.m_pendingPallids.erase(zoneId);
|
||||
}
|
||||
|
||||
void WorldState::OnEnable(ScourgeInvasionData::InvasionZone& zone)
|
||||
{
|
||||
// If there were remaining necropolises in the old zone before shutdown, we
|
||||
// restore that zone
|
||||
if (sWorldState->GetSIRemaining(SIRemaining(zone.remainingNecropoli)) > 0)
|
||||
ResumeInvasion(zone);
|
||||
// Otherwise we start a new Invasion
|
||||
else
|
||||
StartNewInvasionIfTime(GetTimerIdForZone(zone.zoneId), zone.zoneId);
|
||||
}
|
||||
|
||||
void WorldState::OnDisable(ScourgeInvasionData::InvasionZone& zone)
|
||||
{
|
||||
if (!zone.mouthGuid)
|
||||
return;
|
||||
|
||||
Map* map = GetMap(zone.map, zone.mouth[0]);
|
||||
|
||||
if (Creature* mouth = map->GetCreature(zone.mouthGuid))
|
||||
mouth->DespawnOrUnsummon();
|
||||
}
|
||||
|
||||
void WorldState::OnDisable(ScourgeInvasionData::CityAttack& zone)
|
||||
{
|
||||
if (!zone.pallidGuid)
|
||||
return;
|
||||
|
||||
Map* map = GetMap(zone.map, zone.pallid[0]);
|
||||
|
||||
if (Creature* pallid = map->GetCreature(zone.pallidGuid))
|
||||
pallid->DespawnOrUnsummon();
|
||||
}
|
||||
|
||||
bool WorldState::IsActiveZone(uint32 /*zoneId*/)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// returns the amount of pending zones and active zones with a mouth creature on the map
|
||||
uint32 WorldState::GetActiveZones()
|
||||
{
|
||||
size_t i = m_siData.m_pendingInvasions.size();
|
||||
for (auto const& [zoneId, invasionData] : m_siData.m_activeInvasions)
|
||||
{
|
||||
Map* map = GetMap(invasionData.map, invasionData.mouth[0]);
|
||||
if (!map)
|
||||
{
|
||||
LOG_ERROR("gameevent", "ScourgeInvasionEvent::GetActiveZones no map for zone {}.", invasionData.map);
|
||||
continue;
|
||||
}
|
||||
|
||||
Creature* mouth = map->GetCreature(invasionData.mouthGuid);
|
||||
if (mouth)
|
||||
i++;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
uint32 WorldState::GetTimerIdForZone(uint32 zoneId)
|
||||
{
|
||||
uint32 attackTime = 0;
|
||||
switch (zoneId)
|
||||
{
|
||||
case AREA_TANARIS: attackTime = SI_TIMER_TANARIS; break;
|
||||
case AREA_BLASTED_LANDS: attackTime = SI_TIMER_BLASTED_LANDS; break;
|
||||
case AREA_EASTERN_PLAGUELANDS: attackTime = SI_TIMER_EASTERN_PLAGUELANDS; break;
|
||||
case AREA_BURNING_STEPPES: attackTime = SI_TIMER_BURNING_STEPPES; break;
|
||||
case AREA_WINTERSPRING: attackTime = SI_TIMER_WINTERSPRING; break;
|
||||
case AREA_AZSHARA: attackTime = SI_TIMER_AZSHARA; break;
|
||||
default:
|
||||
LOG_ERROR("gameevent", "ScourgeInvasionEvent::GetTimerIdForZone unknown zoneId {}.", zoneId);
|
||||
return 0;
|
||||
}
|
||||
return attackTime;
|
||||
}
|
||||
|
||||
void WorldState::FillInitialWorldStates(WorldPackets::WorldState::InitWorldStates& packet, uint32 zoneId, uint32 /*areaId*/)
|
||||
{
|
||||
if (m_siData.m_state != STATE_0_DISABLED) // scourge invasion active - need to send all worldstates
|
||||
{
|
||||
uint32 victories = GetBattlesWon();
|
||||
|
||||
uint32 remainingAzshara = GetSIRemaining(SI_REMAINING_AZSHARA);
|
||||
uint32 remainingBlastedLands = GetSIRemaining(SI_REMAINING_BLASTED_LANDS);
|
||||
uint32 remainingBurningSteppes = GetSIRemaining(SI_REMAINING_BURNING_STEPPES);
|
||||
uint32 remainingEasternPlaguelands = GetSIRemaining(SI_REMAINING_EASTERN_PLAGUELANDS);
|
||||
uint32 remainingTanaris = GetSIRemaining(SI_REMAINING_TANARIS);
|
||||
uint32 remainingWinterspring = GetSIRemaining(SI_REMAINING_WINTERSPRING);
|
||||
|
||||
packet.Worldstates.reserve(13);
|
||||
packet.Worldstates.emplace_back(WORLD_STATE_SCOURGE_INVASION_AZSHARA, remainingAzshara > 0 ? 1 : 0);
|
||||
packet.Worldstates.emplace_back(WORLD_STATE_SCOURGE_INVASION_BLASTED_LANDS, remainingBlastedLands > 0 ? 1 : 0);
|
||||
packet.Worldstates.emplace_back(WORLD_STATE_SCOURGE_INVASION_BURNING_STEPPES, remainingBurningSteppes > 0 ? 1 : 0);
|
||||
packet.Worldstates.emplace_back(WORLD_STATE_SCOURGE_INVASION_EASTERN_PLAGUELANDS, remainingEasternPlaguelands > 0 ? 1 : 0);
|
||||
packet.Worldstates.emplace_back(WORLD_STATE_SCOURGE_INVASION_TANARIS, remainingTanaris > 0 ? 1 : 0);
|
||||
packet.Worldstates.emplace_back(WORLD_STATE_SCOURGE_INVASION_WINTERSPRING, remainingWinterspring > 0 ? 1 : 0);
|
||||
packet.Worldstates.emplace_back(WORLD_STATE_SCOURGE_INVASION_VICTORIES, victories);
|
||||
packet.Worldstates.emplace_back(WORLD_STATE_SCOURGE_INVASION_NECROPOLIS_AZSHARA, remainingAzshara);
|
||||
packet.Worldstates.emplace_back(WORLD_STATE_SCOURGE_INVASION_NECROPOLIS_BLASTED_LANDS, remainingBlastedLands);
|
||||
packet.Worldstates.emplace_back(WORLD_STATE_SCOURGE_INVASION_NECROPOLIS_BURNING_STEPPES, remainingBurningSteppes);
|
||||
packet.Worldstates.emplace_back(WORLD_STATE_SCOURGE_INVASION_NECROPOLIS_EASTERN_PLAGUELANDS, remainingEasternPlaguelands);
|
||||
packet.Worldstates.emplace_back(WORLD_STATE_SCOURGE_INVASION_NECROPOLIS_TANARIS, remainingTanaris);
|
||||
packet.Worldstates.emplace_back(WORLD_STATE_SCOURGE_INVASION_NECROPOLIS_WINTERSPRING, remainingWinterspring);
|
||||
}
|
||||
|
||||
switch (zoneId)
|
||||
{
|
||||
case AREA_ISLE_OF_QUEL_DANAS:
|
||||
@@ -1136,5 +2007,7 @@ void WorldState::FillInitialWorldStates(WorldPackets::WorldState::InitWorldState
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
#include "AreaDefines.h"
|
||||
#include "Player.h"
|
||||
#include "WorldStateDefines.h"
|
||||
#include <atomic>
|
||||
|
||||
enum WorldStateCondition
|
||||
@@ -56,11 +57,33 @@ enum WorldStateSpells
|
||||
|
||||
enum WorldStateSaveIds
|
||||
{
|
||||
SAVE_ID_SCOURGE_INVASION = 3,
|
||||
SAVE_ID_QUEL_DANAS = 20,
|
||||
};
|
||||
|
||||
enum WorldStateGameEvents
|
||||
{
|
||||
// Scourge Invasion
|
||||
GAME_EVENT_SCOURGE_INVASION = 17,
|
||||
GAME_EVENT_SCOURGE_INVASION_WINTERSPRING = 121,
|
||||
GAME_EVENT_SCOURGE_INVASION_TANARIS = 122,
|
||||
GAME_EVENT_SCOURGE_INVASION_AZSHARA = 123,
|
||||
GAME_EVENT_SCOURGE_INVASION_BLASTED_LANDS = 124,
|
||||
GAME_EVENT_SCOURGE_INVASION_EASTERN_PLAGUELANDS = 125,
|
||||
GAME_EVENT_SCOURGE_INVASION_BURNING_STEPPES = 126,
|
||||
GAME_EVENT_SCOURGE_INVASION_50_INVASIONS = 127,
|
||||
GAME_EVENT_SCOURGE_INVASION_100_INVASIONS = 128,
|
||||
GAME_EVENT_SCOURGE_INVASION_150_INVASIONS = 129,
|
||||
GAME_EVENT_SCOURGE_INVASION_INVASIONS_DONE = 130,
|
||||
|
||||
// Zombie infestation
|
||||
GAME_EVENT_ZOMBIE_INFESTATION_PHASE_1 = 110,
|
||||
GAME_EVENT_ZOMBIE_INFESTATION_PHASE_2 = 111,
|
||||
GAME_EVENT_ZOMBIE_INFESTATION_PHASE_3 = 112,
|
||||
GAME_EVENT_ZOMBIE_INFESTATION_PHASE_4 = 113,
|
||||
GAME_EVENT_ZOMBIE_INFESTATION_PHASE_5 = 114,
|
||||
GAME_EVENT_ZOMBIE_INFESTATION_PHASE_6 = 115,
|
||||
|
||||
// Isle phases
|
||||
GAME_EVENT_QUEL_DANAS_PHASE_1 = 101,
|
||||
GAME_EVENT_QUEL_DANAS_PHASE_2_ONLY = 102,
|
||||
@@ -84,6 +107,110 @@ enum WorldStateGameEvents
|
||||
GAME_EVENT_SWP_GATES_PHASE_3 = 119, // All Gates Open
|
||||
};
|
||||
|
||||
enum SIMisc
|
||||
{
|
||||
EVENT_HERALD_OF_THE_LICH_KING_ZONE_START = 7,
|
||||
EVENT_HERALD_OF_THE_LICH_KING_ZONE_STOP = 8,
|
||||
NPC_PALLID_HORROR = 16394,
|
||||
NPC_PATCHWORK_TERROR = 16382,
|
||||
NPC_HERALD_OF_THE_LICH_KING = 16995,
|
||||
ITEM_A_LETTER_FROM_THE_KEEPER_OF_THE_ROLLS = 22723,
|
||||
NPC_ARGENT_EMISSARY = 16285,
|
||||
MAIL_TEMPLATE_ARGENT_DAWN_NEEDS_YOUR_HELP = 171,
|
||||
};
|
||||
|
||||
enum SIState : uint32
|
||||
{
|
||||
STATE_0_DISABLED,
|
||||
STATE_1_ENABLED,
|
||||
SI_STATE_MAX,
|
||||
};
|
||||
|
||||
enum SIZoneIds
|
||||
{
|
||||
SI_ZONE_AZSHARA,
|
||||
SI_ZONE_BLASTED_LANDS,
|
||||
SI_ZONE_BURNING_STEPPES,
|
||||
SI_ZONE_EASTERN_PLAGUELANDS,
|
||||
SI_ZONE_TANARIS,
|
||||
SI_ZONE_WINTERSPRING,
|
||||
SI_ZONE_STORMWIND,
|
||||
SI_ZONE_UNDERCITY
|
||||
};
|
||||
|
||||
enum SITimers
|
||||
{
|
||||
SI_TIMER_AZSHARA,
|
||||
SI_TIMER_BLASTED_LANDS,
|
||||
SI_TIMER_BURNING_STEPPES,
|
||||
SI_TIMER_EASTERN_PLAGUELANDS,
|
||||
SI_TIMER_TANARIS,
|
||||
SI_TIMER_WINTERSPRING,
|
||||
SI_TIMER_STORMWIND,
|
||||
SI_TIMER_UNDERCITY,
|
||||
SI_TIMER_MAX,
|
||||
};
|
||||
|
||||
enum SIRemaining
|
||||
{
|
||||
SI_REMAINING_AZSHARA,
|
||||
SI_REMAINING_BLASTED_LANDS,
|
||||
SI_REMAINING_BURNING_STEPPES,
|
||||
SI_REMAINING_EASTERN_PLAGUELANDS,
|
||||
SI_REMAINING_TANARIS,
|
||||
SI_REMAINING_WINTERSPRING,
|
||||
SI_REMAINING_MAX,
|
||||
};
|
||||
|
||||
enum SICityTimers
|
||||
{
|
||||
// These timers may fail if you set it under 1 minute.
|
||||
ZONE_ATTACK_TIMER_MIN = 60 * 45, // 45 min.
|
||||
ZONE_ATTACK_TIMER_MAX = 60 * 60, // 60 min.
|
||||
CITY_ATTACK_TIMER_MIN = 60 * 45, // 45 min.
|
||||
CITY_ATTACK_TIMER_MAX = 60 * 60, // 60 min.
|
||||
};
|
||||
|
||||
struct ScourgeInvasionData
|
||||
{
|
||||
struct InvasionZone
|
||||
{
|
||||
uint32 map;
|
||||
uint32 zoneId;
|
||||
uint32 necropolisCount;
|
||||
uint32 remainingNecropoli;
|
||||
std::vector<Position> mouth;
|
||||
ObjectGuid mouthGuid {};
|
||||
};
|
||||
|
||||
struct CityAttack
|
||||
{
|
||||
uint32 map;
|
||||
uint32 zoneId;
|
||||
std::vector<Position> pallid;
|
||||
ObjectGuid pallidGuid {};
|
||||
};
|
||||
|
||||
SIState m_state;
|
||||
|
||||
TimePoint m_timers[SI_TIMER_MAX];
|
||||
uint32 m_battlesWon;
|
||||
uint32 m_lastAttackZone;
|
||||
uint32 m_remaining[SI_REMAINING_MAX];
|
||||
uint64 m_broadcastTimer;
|
||||
std::mutex m_siMutex;
|
||||
|
||||
std::set<uint32> m_pendingInvasions;
|
||||
std::set<uint32> m_pendingPallids;
|
||||
std::map<uint32, InvasionZone> m_activeInvasions;
|
||||
std::map<uint32, CityAttack> m_cityAttacks;
|
||||
|
||||
ScourgeInvasionData();
|
||||
|
||||
void Reset();
|
||||
std::string GetData();
|
||||
};
|
||||
|
||||
enum SunsReachPhases
|
||||
{
|
||||
SUNS_REACH_PHASE_1_STAGING_AREA,
|
||||
@@ -166,7 +293,7 @@ class WorldState
|
||||
void SaveHelper(std::string& stringToSave, WorldStateSaveIds saveId);
|
||||
void HandlePlayerEnterZone(Player* player, AreaTableIDs zoneId);
|
||||
void HandlePlayerLeaveZone(Player* player, AreaTableIDs zoneId);
|
||||
bool IsConditionFulfilled(WorldStateCondition conditionId, WorldStateConditionState state = WORLD_STATE_CONDITION_STATE_NONE) const;
|
||||
bool IsConditionFulfilled(uint32 conditionId, uint32 state = WORLD_STATE_CONDITION_STATE_NONE) const;
|
||||
void HandleConditionStateChange(WorldStateCondition conditionId, WorldStateConditionState state);
|
||||
void HandleExternalEvent(WorldStateEvent eventId, uint32 param);
|
||||
void Update(uint32 diff);
|
||||
@@ -195,8 +322,55 @@ class WorldState
|
||||
bool _isMagtheridonHeadSpawnedHorde;
|
||||
bool _isMagtheridonHeadSpawnedAlliance;
|
||||
SunsReachReclamationData m_sunsReachData;
|
||||
std::map<WorldStateCondition, std::atomic<WorldStateConditionState>> _transportStates; // atomic to avoid having to lock
|
||||
std::map<uint32, std::atomic<WorldStateConditionState>> _transportStates; // atomic to avoid having to lock
|
||||
std::mutex _mutex; // all World State operations are threat unsafe
|
||||
|
||||
// Scourge Invasion
|
||||
public:
|
||||
void SetScourgeInvasionState(SIState state);
|
||||
void StartScourgeInvasion(bool sendMail);
|
||||
void StopScourgeInvasion();
|
||||
[[nodiscard]] uint32 GetSIRemaining(SIRemaining remaining) const;
|
||||
[[nodiscard]] uint32 GetSIRemainingByZone(uint32 zoneId) const;
|
||||
void SetSIRemaining(SIRemaining remaining, uint32 value);
|
||||
TimePoint GetSITimer(SITimers timer);
|
||||
void SetSITimer(SITimers timer, TimePoint timePoint);
|
||||
uint32 GetBattlesWon();
|
||||
void AddBattlesWon(int32 count);
|
||||
uint32 GetLastAttackZone();
|
||||
void SetLastAttackZone(uint32 zoneId);
|
||||
void BroadcastSIWorldstates();
|
||||
void HandleDefendedZones();
|
||||
|
||||
std::string GetScourgeInvasionPrintout();
|
||||
void StartZoneEvent(SIZoneIds eventId);
|
||||
void StartNewInvasionIfTime(uint32 attackTimeVar, uint32 zoneId);
|
||||
void StartNewCityAttackIfTime(uint32 attackTimeVar, uint32 zoneId);
|
||||
void StartNewInvasion(uint32 zoneId);
|
||||
void StartNewCityAttack(uint32 zoneId);
|
||||
bool ResumeInvasion(ScourgeInvasionData::InvasionZone& zone);
|
||||
bool SummonMouth(Map* map, ScourgeInvasionData::InvasionZone& zone, Position position, bool newInvasion);
|
||||
bool SummonPallid(Map* map, ScourgeInvasionData::CityAttack& zone, const Position& position, uint32 spawnLoc);
|
||||
void HandleActiveZone(uint32 attackTimeVar, uint32 zoneId, uint32 remainingVar, TimePoint now);
|
||||
|
||||
Map* GetMap(uint32 mapId, Position const& invZone);
|
||||
bool IsActiveZone(uint32 zoneId);
|
||||
uint32 GetActiveZones();
|
||||
uint32 GetTimerIdForZone(uint32 zoneId);
|
||||
|
||||
void SetPallidGuid(uint32 zoneId, ObjectGuid guid);
|
||||
void SetMouthGuid(uint32 zoneId, ObjectGuid guid);
|
||||
void AddPendingInvasion(uint32 zoneId);
|
||||
void RemovePendingInvasion(uint32 zoneId);
|
||||
void AddPendingPallid(uint32 zoneId);
|
||||
void RemovePendingPallid(uint32 zoneId);
|
||||
|
||||
void OnEnable(ScourgeInvasionData::InvasionZone& zone);
|
||||
void OnDisable(ScourgeInvasionData::InvasionZone& zone);
|
||||
void OnDisable(ScourgeInvasionData::CityAttack& zone);
|
||||
private:
|
||||
void SendScourgeInvasionMail();
|
||||
ScourgeInvasionData m_siData;
|
||||
};
|
||||
|
||||
#define sWorldState WorldState::instance()
|
||||
|
||||
@@ -39,9 +39,18 @@ public:
|
||||
{ "gatecounter", HandleSunwellGateCounterCommand, SEC_ADMINISTRATOR, Console::Yes },
|
||||
};
|
||||
|
||||
static ChatCommandTable scourgeInvasionCommandTable =
|
||||
{
|
||||
{ "show", HandleScourgeInvasionCommand, SEC_ADMINISTRATOR, Console::Yes },
|
||||
{ "state", HandleScourgeInvasionStateCommand, SEC_ADMINISTRATOR, Console::Yes },
|
||||
{ "battleswon", HandleScourgeInvasionBattlesWonCommand, SEC_ADMINISTRATOR, Console::Yes },
|
||||
{ "startzone", HandleScourgeInvasionStartZone, SEC_ADMINISTRATOR, Console::Yes },
|
||||
};
|
||||
|
||||
static ChatCommandTable worldStateCommandTable =
|
||||
{
|
||||
{ "sunsreach", sunsreachCommandTable }
|
||||
{ "sunsreach", sunsreachCommandTable },
|
||||
{ "scourgeinvasion", scourgeInvasionCommandTable }
|
||||
};
|
||||
|
||||
static ChatCommandTable commandTable =
|
||||
@@ -118,6 +127,46 @@ public:
|
||||
handler->PSendSysMessage(sWorldState->GetSunsReachPrintout());
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleScourgeInvasionCommand(ChatHandler* handler)
|
||||
{
|
||||
handler->PSendSysMessage(sWorldState->GetScourgeInvasionPrintout());
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleScourgeInvasionStateCommand(ChatHandler* handler, uint32 value)
|
||||
{
|
||||
if (value >= SI_STATE_MAX)
|
||||
{
|
||||
handler->PSendSysMessage("Syntax: .worldstate scourgeinvasion state <value>.");
|
||||
handler->PSendSysMessage("Valid values are: 0 (Disabled), 1 (Enabled).");
|
||||
return true;
|
||||
}
|
||||
sWorldState->SetScourgeInvasionState(SIState(value));
|
||||
handler->PSendSysMessage("Scourge Invasion state set to {}.", value);
|
||||
handler->PSendSysMessage(sWorldState->GetScourgeInvasionPrintout());
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleScourgeInvasionBattlesWonCommand(ChatHandler* /* handler */, int32 value)
|
||||
{
|
||||
sWorldState->AddBattlesWon(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleScourgeInvasionStartZone(ChatHandler* handler, uint32 value)
|
||||
{
|
||||
|
||||
if (value >= SI_TIMER_MAX)
|
||||
{
|
||||
handler->PSendSysMessage("Syntax: .worldstate scourgeinvasion startzone <value>.\nvalid values: 0-7");
|
||||
return true;
|
||||
}
|
||||
sWorldState->StartZoneEvent(SIZoneIds(value));
|
||||
handler->PSendSysMessage("Scourge Invasion event started for zone {}.", value);
|
||||
handler->PSendSysMessage(sWorldState->GetScourgeInvasionPrintout());
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
void AddSC_worldstate_commandscript()
|
||||
|
||||
1129
src/server/scripts/World/scourge_invasion.cpp
Normal file
1129
src/server/scripts/World/scourge_invasion.cpp
Normal file
File diff suppressed because it is too large
Load Diff
427
src/server/scripts/World/scourge_invasion.h
Normal file
427
src/server/scripts/World/scourge_invasion.h
Normal file
@@ -0,0 +1,427 @@
|
||||
/*
|
||||
* This file is part of the AzerothCore 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 Affero General Public License as published by the
|
||||
* Free Software Foundation; either version 3 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 Affero General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SCOURGE_INVASION_H
|
||||
#define SCOURGE_INVASION_H
|
||||
|
||||
enum ScourgeInvasionSpells
|
||||
{
|
||||
SPELL_SPIRIT_PARTICLES_PURPLE = 28126, // Purple Minions Aura.
|
||||
|
||||
// GameObject Necropolis
|
||||
SPELL_SUMMON_NECROPOLIS_CRITTERS = 27866, // Spawns NPCs Necropolis Health and Necropolis.
|
||||
|
||||
// Necropolis Health -> Necropolis
|
||||
SPELL_DESPAWNER_OTHER = 28349, // Casted by the NPC "Necropolis health" after getting hit by,
|
||||
// on the NPC "Necropolis" which destroys itself and the Necropolis Object.
|
||||
|
||||
// Necropolis Health
|
||||
SPELL_ZAP_NECROPOLIS = 28386, // There are always 3 Necrotic Shards spawns per Necropolis. This Spell is castet on the NPC "Necropolis Health" if a Shard dies and does 40 Physical damage.
|
||||
// NPC "Necropolis Health" has 42 health. 42 health / 3 Shards = 14 damage.
|
||||
// We have set the armor value from the NPC "Necropolis Health" to 950 to reduce the damage from 40 to 14.
|
||||
|
||||
// Necropolis -> Proxy
|
||||
SPELL_COMMUNIQUE_TIMER_NECROPOLIS = 28395, // Periodically triggers 28373 Communique, Necropolis-to-Proxies every 15 seconds.
|
||||
SPELL_COMMUNIQUE_NECROPOLIS_TO_PROXIES = 28373, // purple bolt Visual (BIG).
|
||||
|
||||
// Proxy -> Necropolis
|
||||
SPELL_COMMUNIQUE_PROXY_TO_NECROPOLIS = 28367, // Purple bolt Visual (SMALL).
|
||||
|
||||
// Proxy -> Relay
|
||||
SPELL_COMMUNIQUE_PROXY_TO_RELAY = 28366, // purple bolt Visual (BIG).
|
||||
|
||||
// Relay -> Proxy
|
||||
SPELL_COMMUNIQUE_RELAY_TO_PROXY = 28365, // Purple bolt Visual (SMALL).
|
||||
|
||||
// Relay -> Shard
|
||||
SPELL_COMMUNIQUE_RELAY_TO_CAMP = 28326, // Purple bolt Visual (BIG).
|
||||
|
||||
// Shard
|
||||
SPELL_CREATE_CRYSTAL = 28344, // Spawn a Necrotic Shard.
|
||||
SPELL_CREATE_CRYSTAL_CORPSE = 27895, // Summon (Damaged Necrotic Shard).
|
||||
SPELL_CAMP_RECEIVES_COMMUNIQUE = 28449, // Impact Visual.
|
||||
SPELL_COMMUNIQUE_TIMER_CAMP = 28346, // Cast on npc_necrotic_shard on spawn? Periodically triggers 28345 Communique Trigger every 35 seconds.
|
||||
SPELL_COMMUNIQUE_TRIGGER = 28345, // Triggers 28281 SPELL_COMMUNIQUE_CAMP_TO_RELAY via void Spell::EffectDummy.
|
||||
SPELL_DAMAGE_CRYSTAL = 28041, // 100 Damage (Physical). Casted on itself, if 16143 (Shadow of Doom) spawns.
|
||||
SPELL_SOUL_REVIVAL = 28681, // Increases all damage caused by 10%.
|
||||
SPELL_CAMP_TYPE_GHOST_SKELETON = 28197, // Camp Type, tells the NPC "Scourge Invasion Minion, finder" which Camp type the Shard has.
|
||||
SPELL_CAMP_TYPE_GHOST_GHOUL = 28198, // ""
|
||||
SPELL_CAMP_TYPE_GHOUL_SKELETON = 28199, // ""
|
||||
SPELL_MINION_SPAWNER_SMALL = 27887, // Triggers 27885 (Disturb Minion Trap, small) every 5 seconds. Activates up to 3 unknown Objects wich spawns the Minions.
|
||||
SPELL_MINION_SPAWNER_BUTTRESS = 27888, // Triggers 27886 (Disturb Minion Trap, Buttress) every 1 hour. Activates unknown Objects, They may also spawn the Cultists.
|
||||
SPELL_CHOOSE_CAMP_TYPE = 28201, // casted by Necrotic Shard.
|
||||
|
||||
// Shard -> Relay
|
||||
SPELL_COMMUNIQUE_CAMP_TO_RELAY = 28281, // Purple bolt Visual (SMALL)
|
||||
SPELL_COMMUNIQUE_CAMP_TO_RELAY_DEATH = 28351, // Visual when Damaged Necrotic Shard dies.
|
||||
|
||||
// Camp - Minion spawning system
|
||||
SPELL_FIND_CAMP_TYPE = 28203, // casted by Scourge Invasion Minion, finder.
|
||||
|
||||
// Scourge Invasion Minion, spawner, Ghost/Ghoul
|
||||
SPELL_PH_SUMMON_MINION_TRAP_GHOST_GHOUL = 27883,
|
||||
|
||||
// Scourge Invasion Minion, spawner, Ghost/Skeleton
|
||||
SPELL_PH_SUMMON_MINION_TRAP_GHOST_SKELETON = 28186,
|
||||
|
||||
// Scourge Invasion Minion, spawner, Ghoul/Skeleton
|
||||
SPELL_PH_SUMMON_MINION_TRAP_GHOUL_SKELETON = 28187,
|
||||
|
||||
// Minions Spells
|
||||
SPELL_ZAP_CRYSTAL = 28032, // 15 damage to a Necrotic Shard on death.
|
||||
SPELL_MINION_SPAWN_IN = 28234, // Pink Lightning.
|
||||
SPELL_SPIRIT_SPAWN_OUT = 17680, // Makes invisible.
|
||||
SPELL_MINION_DESPAWN_TIMER = 28090, // Triggers 28091 (Despawner, self) every 150 seconds. Triggers 17680 SPELL_SPIRIT_SPAWN_OUT via void Spell::EffectDummy.
|
||||
SPELL_CONTROLLER_TIMER = 28095, // Triggers 28091 (Despawner, self) every 60 seconds for 1 hour. (Unknown who is casting this).
|
||||
SPELL_DESPAWNER_SELF = 28091, // Trigger from Spell above.
|
||||
SPELL_SUMMON_SCOURGE_CONTROLLER = 28092,
|
||||
|
||||
// Minion Abilities
|
||||
SPELL_SCOURGE_STRIKE = 28265, // Pink Lightning (Instakill).
|
||||
SPELL_ENRAGE = 8599, // Used by 16141 (Ghoul Berserker).
|
||||
SPELL_BONE_SHARDS = 17014, // [shortest sniff CD: 16,583 seconds] Used by 16299 (Skeletal Shocktrooper).
|
||||
SPELL_INFECTED_BITE = 7367, // [shortest sniff CD: 13,307 seconds] Used by 16141 (Ghoul Berserker).
|
||||
SPELL_DEMORALIZING_SHOUT = 16244, // [shortest sniff CD: 19,438 seconds] Used by 16298 (Spectral Soldier).
|
||||
SPELL_SUNDER_ARMOR = 21081, // [shortest sniff CD: 6,489 seconds] Used by 16298 (Spectral Soldier).
|
||||
SPELL_SHADOW_WORD_PAIN = 589, // Used by 16438 (Skeletal Trooper).
|
||||
SPELL_DUAL_WIELD = 674, // Used by Skeletal Soldier and Skeletal Shocktrooper.
|
||||
|
||||
// Marks of the Dawn
|
||||
SPELL_CREATE_LESSER_MARK_OF_THE_DAWN = 28319, // Create Lesser Mark of the Dawn.
|
||||
SPELL_CREATE_MARK_OF_THE_DAWN = 28320, // Create Mark of the Dawn.
|
||||
SPELL_CREATE_GREATER_MARK_OF_THE_DAWN = 28321, // Create Greater Mark of the Dawn.
|
||||
|
||||
// Rare Minions
|
||||
SPELL_KNOCKDOWN = 16790, // Used by 14697 (Lumbering Horror).
|
||||
SPELL_TRAMPLE = 5568, // Used by 14697 (Lumbering Horror).
|
||||
SPELL_AURA_OF_FEAR = 28313, // Used by 14697 (Lumbering Horror).
|
||||
SPELL_RIBBON_OF_SOULS = 16243, // [shortest sniff CD: 1,638 seconds] Used by 16379 (Spirit of the Damned).
|
||||
SPELL_PSYCHIC_SCREAM = 22884, // or 26042, used by 16379 (Spirit of the Damned).
|
||||
SPELL_MINION_DESPAWN_TIMER_UNCOMMON = 28292, // Triggers 28091 (Despawner, self) every 10 minutes. Triggers 17680 SPELL_SPIRIT_SPAWN_OUT via void Spell::EffectDummy.
|
||||
SPELL_ARCANE_BOLT = 13748, /* 20720 Used by 16380 (Bone Witch).
|
||||
https://classicdb.ch/?npc=16380#abilities says 13748 but 20720 is the only "Arcane Bolt" whichs requires no mana.
|
||||
Danage is very high, so i guess it has a very long cd.
|
||||
Spell description in the Bestiary is: Hurls a magical bolt at an enemy, inflicting Arcane damage.
|
||||
*/
|
||||
|
||||
// Cultist Engineer
|
||||
SPELL_CREATE_SUMMONER_SHIELD = 28132, // Summon Object - Temporary (181142),
|
||||
// Casted exactly the same time with 28234 (Minion Spawn-in) on spawn.
|
||||
SPELL_BUTTRESS_CHANNEL = 28078, // Channeled by Cultist Engineer on Damaged Necrotic Shard shortly after spawning.
|
||||
SPELL_BUTTRESS_TRAP = 28054, // Unknown.
|
||||
SPELL_KILL_SUMMONER_SUMMON_BOSS = 28250, // Reagents, 1 Necrotic Rune
|
||||
|
||||
// Probably used to spawn Shadow of Doom. Casting sequence (All these [x] spells are being casted the following order within 1-2 seconds):
|
||||
SPELL_PH_KILL_SUMMONER_BUFF = 27852, // [1] Casted by Cultist on Player.
|
||||
SPELL_KILL_SUMMONER_WHO_WILL_SUMMON_BOSS = 27894, // [2] Casted by Player on Cultist.
|
||||
SPELL_QUIET_SUICIDE = 3617, // [3] Instakill, casted exactly same time as 31316 (Summon Boss Buff).
|
||||
SPELL_SUMMON_BOSS_BUFF = 31316, // [4] Summon Boss Buff, casted on Player
|
||||
SPELL_SUMMON_BOSS = 31315, /* [5] Reagents, 8 Necrotic Rune, Summon (Shadow of Doom) for 1 hour.
|
||||
The question is: What happens after this hour if the Shadow of Doom despawns?
|
||||
Do the cultists respawn and channeling again on the damaged shard or
|
||||
Does the Necrotic crystal respawn without Cultists or Shadows of Doom?
|
||||
*/
|
||||
|
||||
// Shadow of Doom
|
||||
SPELL_SPAWN_SMOKE = 10389, // Spawning Visual.
|
||||
SPELL_ZAP_CRYSTAL_CORPSE = 28056, // Casted on Shard if Shadow of Doom dies.
|
||||
SPELL_MINDFLAY = 16568,
|
||||
SPELL_FEAR = 12542,
|
||||
|
||||
// Pallid Horror - Patchwerk Terror (also uses: 28315)
|
||||
SPELL_SUMMON_CRACKED_NECROTIC_CRYSTAL = 28424, // Alliance.
|
||||
SPELL_SUMMON_FAINT_NECROTIC_CRYSTAL = 28699, // Horde.
|
||||
SPELL_DAMAGE_VS_GUARDS = 28364, // [shortest sniff CD: 11 seconds, longest 81 sec] hits 13839 (Royal Dreadguard).
|
||||
|
||||
// Flameshocker (also uses: 28234, 17680)
|
||||
SPELL_FLAMESHOCKERS_TOUCH = 28314, // [shortest sniff CD: 30 seconds]
|
||||
SPELL_FLAMESHOCKERS_REVENGE = 28323, // On death.
|
||||
SPELL_FLAMESHOCKERS_TOUCH2 = 28329, // [shortest sniff CD: 30 seconds]
|
||||
SPELL_FLAMESHOCKER_IMMOLATE_VISUAL = 28330
|
||||
|
||||
/*
|
||||
These spells are not used by any NPCs or GameObjects.
|
||||
The [PH] in the name means it's a placeholder. B often adds that to the names of things they add to the game but haven't finalized.
|
||||
The fact that the [PH] is still there means the quest was never finished. (Google)
|
||||
SPELL_PH_SUMMON_MINION_PARENT_GHOST_GHOUL = 28183,
|
||||
SPELL_PH_SUMMON_MINION_PARENT_GHOST_SKELETON = 28184,
|
||||
SPELL_PH_SUMMON_MINION_PARENT_GHOUL_SKELETON = 28185,
|
||||
SPELL_PH_GET_TOKEN = 27922, // Create Item "Necrotic Rune".
|
||||
SPELL_PH_BUTTRESS_ACTIVATOR = 28086,
|
||||
SPELL_PH_CRYSTAL_CORPSE_DESPAWN = 28020,
|
||||
SPELL_PH_CRYSTAL_CORPSE_TIMER = 28018, // Triggers 28020 ([PH] Crystal Corpse Despawn) after 2 hours.
|
||||
SPELL_PH_CYSTAL_BAZOOKA = 27849,
|
||||
SPELL_PH_SUMMON_BUTTRESS = 28024, // Summon (Cultist Engineer) for 1 hour.
|
||||
SPELL_DND_SUMMON_CRYSTAL_MINION_FINDER = 28227,
|
||||
*/
|
||||
};
|
||||
|
||||
enum ScourgeInvasionNPC
|
||||
{
|
||||
// Visible NPCs
|
||||
NPC_NECROTIC_SHARD = 16136,
|
||||
NPC_DAMAGED_NECROTIC_SHARD = 16172,
|
||||
NPC_CULTIST_ENGINEER = 16230,
|
||||
NPC_SHADOW_OF_DOOM = 16143,
|
||||
|
||||
// Camp Helpers (invisible)
|
||||
NPC_SCOURGE_INVASION_MINION_FINDER = 16356, // Casting 28203 (Find Camp Type).
|
||||
NPC_SCOURGE_INVASION_MINION_SPAWNER_GHOST_GHOUL = 16306,
|
||||
NPC_SCOURGE_INVASION_MINION_SPAWNER_GHOST_SKELETON = 16336,
|
||||
NPC_SCOURGE_INVASION_MINION_SPAWNER_GHOUL_SKELETON = 16338,
|
||||
|
||||
// Necropolis Helpers (invisible)
|
||||
NPC_NECROPOLIS = 16401,
|
||||
NPC_NECROPOLIS_HEALTH = 16421,
|
||||
NPC_NECROPOLIS_PROXY = 16398,
|
||||
NPC_NECROPOLIS_RELAY = 16386,
|
||||
|
||||
// Minions
|
||||
NPC_SKELETAL_SHOCKTROOPER = 16299,
|
||||
NPC_GHOUL_BERSERKER = 16141,
|
||||
NPC_SPECTRAL_SOLDIER = 16298,
|
||||
|
||||
// Rare Minions
|
||||
NPC_LUMBERING_HORROR = 14697,
|
||||
NPC_BONE_WITCH = 16380,
|
||||
NPC_SPIRIT_OF_THE_DAMNED = 16379,
|
||||
|
||||
// 50 Zones cleared
|
||||
NPC_ARGENT_DAWN_INITIATE = 16384,
|
||||
NPC_ARGENT_DAWN_CLERIC = 16435,
|
||||
// 100 Zones cleared
|
||||
NPC_ARGENT_DAWN_PRIEST = 16436,
|
||||
NPC_ARGENT_DAWN_PALADIN = 16395,
|
||||
// 150 Zones cleared
|
||||
NPC_ARGENT_DAWN_CRUSADER = 16433,
|
||||
NPC_ARGENT_DAWN_CHAMPION = 16434,
|
||||
|
||||
// Low level Minions
|
||||
NPC_SKELETAL_TROOPER = 16438,
|
||||
NPC_SPECTRAL_SPIRIT = 16437,
|
||||
NPC_SKELETAL_SOLDIER = 16422,
|
||||
NPC_SPECTRAL_APPARITATION = 16423,
|
||||
|
||||
// Stormwind - Undercity Attacks https://www.youtube.com/watch?v=c0QjLqHVPRU&t=17s
|
||||
// NPC_PALLID_HORROR = 16394,
|
||||
// NPC_PATCHWORK_TERROR = 16382,
|
||||
NPC_CRACKED_NECROTIC_CRYSTAL = 16431,
|
||||
NPC_FAINT_NECROTIC_CRYSTAL = 16531,
|
||||
NPC_FLAMESHOCKER = 16383,
|
||||
NPC_HIGHLORD_BOLVAR_FORDRAGON = 1748,
|
||||
NPC_VARIAN = 29611,
|
||||
NPC_LADY_SYLVANAS_WINDRUNNER = 10181,
|
||||
NPC_VARIMATHRAS = 2425,
|
||||
NPC_ROYAL_DREADGUARD = 13839,
|
||||
NPC_STORMWIND_ROYAL_GUARD = 1756,
|
||||
NPC_UNDERCITY_ELITE_GUARDIAN = 16432,
|
||||
NPC_UNDERCITY_GUARDIAN = 5624,
|
||||
NPC_DEATHGUARD_ELITE = 7980,
|
||||
NPC_STORMWIND_CITY_GUARD = 68,
|
||||
NPC_STORMWIND_ELITE_GUARD = 16396,
|
||||
|
||||
// Citizens
|
||||
NPC_RENATO_GALLINA = 1432,
|
||||
NPC_MICHAEL_GARRETT = 4551,
|
||||
NPC_HANNAH_AKELEY = 4575,
|
||||
NPC_INNKEEPER_NORMAN = 6741,
|
||||
NPC_OFFICER_MALOOF = 15766,
|
||||
NPC_STEPHANIE_TURNER = 6174,
|
||||
NPC_THOMAS_MILLER = 3518,
|
||||
NPC_WILLIAM_MONTAGUE = 4549
|
||||
};
|
||||
|
||||
enum ScourgeInvasionObjects
|
||||
{
|
||||
// Invisible Objects
|
||||
GO_BUTTRESS_TRAP = 181112, // [Guessed] Those objects can't be sniffed and are not available in any database.
|
||||
|
||||
GO_SUMMON_MINION_TRAP_GHOST_GHOUL = 181111, // Object is not in sniffed files or any database such as WoWHead, but spell 28196 (Create Minion Trap: Ghost/Skeleton) should probably summon them.
|
||||
GO_SUMMON_MINION_TRAP_GHOST_SKELETON = 181155, // ""
|
||||
GO_SUMMON_MINION_TRAP_GHOUL_SKELETON = 181156, // ""
|
||||
|
||||
// Visible Objects
|
||||
GO_SUMMON_CIRCLE = 181136,
|
||||
GO_SUMMONER_SHIELD = 181142,
|
||||
|
||||
GO_UNDEAD_FIRE = 181173,
|
||||
GO_UNDEAD_FIRE_AURA = 181174,
|
||||
GO_SKULLPILE_01 = 181191,
|
||||
GO_SKULLPILE_02 = 181192,
|
||||
GO_SKULLPILE_03 = 181193,
|
||||
GO_SKULLPILE_04 = 181194,
|
||||
|
||||
GO_NECROPOLIS_TINY = 181154, // Necropolis (scale 1.0).
|
||||
GO_NECROPOLIS_SMALL = 181373, // Necropolis (scale 1.5).
|
||||
GO_NECROPOLIS_MEDIUM = 181374, // Necropolis (scale 2.0).
|
||||
GO_NECROPOLIS_BIG = 181215, // Necropolis (scale 2.5).
|
||||
GO_NECROPOLIS_HUGE = 181223, // Necropolis (scale 3.5).
|
||||
GO_NECROPOLIS_CITY = 181172, // Necropolis at the Citys (scale 2.5).
|
||||
};
|
||||
|
||||
enum ScourgeInvasionMisc
|
||||
{
|
||||
ITEM_NECROTIC_RUNE = 22484,
|
||||
ACTION_FLAMESHOCKER_SCHEDULE_DESPAWN = 1, // Used by Flameshocker to schedule despawn.
|
||||
};
|
||||
|
||||
enum ScourgeInvasionNPCEvents
|
||||
{
|
||||
EVENT_SHARD_MINION_SPAWNER_SMALL = 1,
|
||||
EVENT_SHARD_MINION_SPAWNER_BUTTRESS = 2,
|
||||
EVENT_SPAWNER_SUMMON_MINION = 3,
|
||||
EVENT_SHARD_FIND_DAMAGED_SHARD = 4,
|
||||
EVENT_CULTIST_CHANNELING = 5,
|
||||
EVENT_HERALD_OF_THE_LICH_KING_YELL = 6,
|
||||
// EVENT_HERALD_OF_THE_LICH_KING_ZONE_START = 7,
|
||||
// EVENT_HERALD_OF_THE_LICH_KING_ZONE_STOP = 8,
|
||||
EVENT_HERALD_OF_THE_LICH_KING_UPDATE = 9,
|
||||
|
||||
// Shadow of Doom Events
|
||||
EVENT_DOOM_MINDFLAY = 20,
|
||||
EVENT_DOOM_FEAR = 21,
|
||||
EVENT_DOOM_START_ATTACK = 22,
|
||||
|
||||
// Rare Events
|
||||
EVENT_RARE_KNOCKDOWN = 31,
|
||||
EVENT_RARE_TRAMPLE = 32,
|
||||
EVENT_RARE_RIBBON_OF_SOULS = 33,
|
||||
|
||||
// Minion Events
|
||||
EVENT_MINION_ENRAGE = 40,
|
||||
EVENT_MINION_BONE_SHARDS = 41,
|
||||
EVENT_MINION_INFECTED_BITE = 42,
|
||||
EVENT_MINION_DAZED = 43,
|
||||
EVENT_MINION_DEMORALIZING_SHOUT = 44,
|
||||
EVENT_MINION_SUNDER_ARMOR = 45,
|
||||
EVENT_MINION_ARCANE_BOLT = 46,
|
||||
EVENT_MINION_PSYCHIC_SCREAM = 47,
|
||||
EVENT_MINION_SCOURGE_STRIKE = 48,
|
||||
EVENT_MINION_SHADOW_WORD_PAIN = 49,
|
||||
EVENT_MINION_FLAMESHOCKERS_TOUCH = 50,
|
||||
EVENT_MINION_FLAMESHOCKERS_DESPAWN = 51,
|
||||
|
||||
// Pallid Horror Events
|
||||
EVENT_PALLID_RANDOM_YELL = 52,
|
||||
EVENT_PALLID_SPELL_DAMAGE_VS_GUARDS = 53,
|
||||
EVENT_SYLVANAS_ANSWER_YELL = 54,
|
||||
EVENT_PALLID_RANDOM_SAY = 55,
|
||||
EVENT_PALLID_SUMMON_FLAMESHOCKER = 56
|
||||
};
|
||||
|
||||
enum ScourgeInvasionQuests
|
||||
{
|
||||
QUEST_UNDER_THE_SHADOW = 9153,
|
||||
QUEST_CRACKED_NECROTIC_CRYSTAL = 9292,
|
||||
QUEST_FAINT_NECROTIC_CRYSTAL = 9310
|
||||
};
|
||||
|
||||
enum ScourgeInvasionTalk
|
||||
{
|
||||
HERALD_OF_THE_LICH_KING_SAY_ATTACK_START = 0,
|
||||
HERALD_OF_THE_LICH_KING_SAY_ATTACK_END = 1,
|
||||
HERALD_OF_THE_LICH_KING_SAY_ATTACK_RANDOM = 2,
|
||||
PALLID_HORROR_SAY_RANDOM_YELL = 0,
|
||||
SHADOW_OF_DOOM_SAY_AGGRO = 0,
|
||||
SYLVANAS_SAY_ATTACK_END = 3,
|
||||
VARIAN_SAY_ATTACK_END = 3
|
||||
};
|
||||
|
||||
enum ScourgeInvasionLang
|
||||
{
|
||||
// Pallid Horror random yelling every 65-300 seconds
|
||||
BCT_PALLID_HORROR_YELL1 = 12329, // What? This not Naxxramas! We not like this place... destroy!
|
||||
BCT_PALLID_HORROR_YELL2 = 12327, // Raaarrrrggghhh! We come for you!
|
||||
BCT_PALLID_HORROR_YELL3 = 12326, // Kel'Thuzad say to tell you... DIE!
|
||||
BCT_PALLID_HORROR_YELL4 = 12342, // Why you run away? We make your corpse into Scourge.
|
||||
BCT_PALLID_HORROR_YELL5 = 12343, // No worry, we find you.
|
||||
BCT_PALLID_HORROR_YELL6 = 12330, // You spare parts! We make more Scourge in necropolis.
|
||||
BCT_PALLID_HORROR_YELL7 = 12328, // Hahaha, your guards no match for Scourge!
|
||||
BCT_PALLID_HORROR_YELL8 = 12325, // We come destroy puny ones!
|
||||
|
||||
// Undercity Guardian
|
||||
BCT_UNDERCITY_GUARDIAN_ROGUES_QUARTER = 12336, // Rogues' Quarter attacked by Scourge! Help!
|
||||
BCT_UNDERCITY_GUARDIAN_MAGIC_QUARTER = 12335, // Scourge attack Magic Quarter!
|
||||
BCT_UNDERCITY_GUARDIAN_TRADE_QUARTER = 12353, // There Scourge outside Trade Quarter!
|
||||
BCT_UNDERCITY_GUARDIAN_SEWERS = 12334, // Scourge in sewers! We need help!
|
||||
|
||||
// Undercity Elite Guardian
|
||||
BCT_UNDERCITY_ELITE_GUARDIAN_1 = 12354, // Scourge inside Trade Quarter! Destroy!
|
||||
|
||||
// Royal Dreadguard
|
||||
BCT_UNDERCITY_ROYAL_DREADGUARD_1 = 12337, // The Scourge are at the entrance to the Royal Quarter! Kill them!!
|
||||
|
||||
// Varimathras
|
||||
BCT_UNDERCITY_VARIMATHRAS_1 = 12333, // Dreadguard, hold your line. Halt the advance of those Scourge!
|
||||
|
||||
// Lady Sylvanas Windrunner
|
||||
BCT_UNDERCITY_SYLVANAS_1 = 12331, // The Scourge attack against my court has been eliminated. You may go about your business.
|
||||
BCT_UNDERCITY_SYLVANAS_2 = 12332, // My Royal Dreadguard, you will deal with this matter as befits your station. That, or you will wish that you had.
|
||||
|
||||
// Citizens
|
||||
BCT_UNDERCITY_RANDOM_1 = 12355, // Scourge spotted nearby!
|
||||
BCT_STORMWIND_RANDOM_1 = 12366, // Scourge spotted nearby! Renato Gallina
|
||||
BCT_UNDERCITY_RANDOM_2 = 12356, // I just saw a Scourge! Kill it!
|
||||
BCT_STORMWIND_RANDOM_2 = 12367, // I just saw a Scourge! Kill it! Thomas Miller
|
||||
BCT_UNDERCITY_RANDOM_3 = 12357, // Did you see that? There's a Scourge over there! Michael Garrett, Hannah Akeley
|
||||
BCT_STORMWIND_RANDOM_3 = 12368, // Did you see that? There's a Scourge over there! Thomas Miller
|
||||
BCT_UNDERCITY_RANDOM_4 = 12359, // There's one of the Scourge, right over there! Innkeeper Norman, Michael Garrett
|
||||
BCT_STORMWIND_RANDOM_4 = 12370, // There's one of the Scourge, right over there!
|
||||
BCT_UNDERCITY_RANDOM_5 = 12357, // Did you see that? There's a Scourge over there! Michael Garrett, Hannah Akeley
|
||||
BCT_STORMWIND_RANDOM_5 = 12368, // Did you see that? There's a Scourge over there! Thomas Miller
|
||||
BCT_UNDERCITY_RANDOM_6 = 12361, // Will these unrelenting Scourge attacks never end? Innkeeper Norman, William Montague
|
||||
BCT_STORMWIND_RANDOM_6 = 12372, // Will these unrelenting Scourge attacks never end?
|
||||
BCT_UNDERCITY_RANDOM_7 = 12360, // This has gone too far. How dare the Scourge attack Undercity! Destroy it before more come! Innkeeper Norman
|
||||
BCT_STORMWIND_RANDOM_7 = 12371, // This has gone too far. How dare the Scourge attack Stormwind! Destroy it before more come! Stephanie Turner
|
||||
BCT_UNDERCITY_RANDOM_8 = 12362, // Destroy the Scourge invader now, before it's too late! Michael Garrett
|
||||
BCT_STORMWIND_RANDOM_8 = 12373, // Destroy the Scourge invader now, before it's too late! Officer Maloof
|
||||
BCT_UNDERCITY_RANDOM_9 = 12358, // How can I get anything done with the Scourge running amok in here?! Innkeeper Norman
|
||||
BCT_STORMWIND_RANDOM_9 = 12369, // How can I get anything done with the Scourge running amok around here?! Stephanie Turner
|
||||
|
||||
// Stormwind City Guard
|
||||
BCT_STORMWIND_CITY_GUARD_1 = 12310, // To arms! Scourge spotted in the Cathedral of Light!
|
||||
BCT_STORMWIND_CITY_GUARD_2 = 12311, // Scourge in the Trade District! Have at them!
|
||||
BCT_STORMWIND_CITY_GUARD_3 = 12315, // Light help us... the Scourge are in the Park!
|
||||
|
||||
// Stormwind Royal Guard
|
||||
BCT_STORMWIND_CITY_GUARD_4 = 12316, // The Scourge are at the castle entrance! For Stormwind! For King Anduin!
|
||||
|
||||
// Highlord Bolvar Fordragon?
|
||||
BCT_STORMWIND_BOLVAR_1 = 12317, // Hold the line! Protect the King at all costs!
|
||||
BCT_STORMWIND_BOLVAR_2 = 12318, // Good work, one and all! The Scourge at the castle have been defeated.
|
||||
|
||||
// Misc
|
||||
BCT_CULTIST_ENGINEER_OPTION = 12112, // Use 8 necrotic runes and disrupt his ritual.
|
||||
BCT_GIVE_MAGIC_ITEM_OPTION = 12302, // Give me one of your magic items.
|
||||
BCT_SHADOW_OF_DOOM_TEXT_0 = 12420, // Our dark master has noticed your trifling, and sends me to bring a message... of doom!
|
||||
BCT_SHADOW_OF_DOOM_TEXT_1 = 12421, // These heroics mean nothing, $c. Your future is sealed and your soul is doomed to servitude!
|
||||
BCT_SHADOW_OF_DOOM_TEXT_2 = 12422, // Your battle here is but the smallest mote of a world wide invasion, whelp! It is time you learned of the powers you face!
|
||||
BCT_SHADOW_OF_DOOM_TEXT_3 = 12243, // You will not stop our deepening shadow, $c. Now... join us! Join the ranks of the Chosen!
|
||||
BCT_HERALD_OF_THE_LICH_KING_ZONE_ATTACK_START_1 = 13121, // Spawn.
|
||||
BCT_HERALD_OF_THE_LICH_KING_ZONE_ATTACK_START_2 = 13125, // Spawn. 53 min between 2-3 in sniffs.
|
||||
BCT_HERALD_OF_THE_LICH_KING_ZONE_ATTACK_ENDS_1 = 13165, // Despawn.
|
||||
BCT_HERALD_OF_THE_LICH_KING_ZONE_ATTACK_ENDS_2 = 13164, // Despawn.
|
||||
BCT_HERALD_OF_THE_LICH_KING_ZONE_ATTACK_ENDS_3 = 13163, // Despawn.
|
||||
BCT_HERALD_OF_THE_LICH_KING_RANDOM_1 = 13126, // Random.
|
||||
BCT_HERALD_OF_THE_LICH_KING_RANDOM_2 = 13124, // Random.
|
||||
BCT_HERALD_OF_THE_LICH_KING_RANDOM_3 = 13122, // 180 seconds between 5-6 in sniffs.
|
||||
BCT_HERALD_OF_THE_LICH_KING_RANDOM_4 = 13123, // Random. 30 min between 8-2 in sniffs.
|
||||
|
||||
BCT_CULTIST_ENGINEER_GOSSIP = 8436, // 12111 - This cultist is in a deep trance...
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -34,6 +34,7 @@ void AddSC_npc_stave_of_ancients();
|
||||
void AddSC_server_mail();
|
||||
void AddSC_transport_zeppelins();
|
||||
void AddSC_suns_reach_reclamation();
|
||||
void AddSC_scourge_invasion();
|
||||
|
||||
// The name of this function should match:
|
||||
// void Add${NameOfDirectory}Scripts()
|
||||
@@ -57,4 +58,5 @@ void AddWorldScripts()
|
||||
AddSC_server_mail();
|
||||
AddSC_transport_zeppelins();
|
||||
AddSC_suns_reach_reclamation();
|
||||
AddSC_scourge_invasion();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user