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:
Jelle Meeus
2025-07-12 20:54:18 +02:00
committed by GitHub
parent 68bac6ee0c
commit 8e083fbde3
13 changed files with 8947 additions and 5 deletions

View File

@@ -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:

View File

@@ -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);

View File

@@ -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();

View File

@@ -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:

View File

@@ -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,

View File

@@ -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;
}
}

View File

@@ -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()