feat(Core/WorldState): improved WorldState scripting (#20141)

* implement world state script

based on
https://github.com/cmangos/mangos-wotlk/commit/0b87ca9d9edf2d4752a3c0f8e9cf85c6689ca861

Co-authored-by: killerwife <killerwife@gmail.com>

* refactor to use onleave and onenter instead of spell_area

allows players to right click remove adal's buff

* add MapMgr.h

* refactor: use condition enum instead of uint32, prefix WORLD_STATE where needed

* remove lock from WorldState::Update

sWorldState->Update() is only called from World::Update

* remove unsafe SmartAI action of setWorldState

---------

Co-authored-by: killerwife <killerwife@gmail.com>
This commit is contained in:
Jelle Meeus
2024-11-12 16:15:27 +01:00
committed by GitHub
parent e80b0ce8b8
commit 3565e4a9eb
20 changed files with 785 additions and 37 deletions
@@ -35,6 +35,7 @@
#include "SmartAI.h"
#include "SpellMgr.h"
#include "Vehicle.h"
#include "WorldState.h"
/// @todo: this import is not necessary for compilation and marked as unused by the IDE
// however, for some reasons removing it would cause a damn linking issue
@@ -3269,6 +3270,11 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
break;
}
case SMART_ACTION_WORLD_SCRIPT:
{
sWorldState->HandleExternalEvent(static_cast<WorldStateEvent>(e.action.worldStateScript.eventId), e.action.worldStateScript.param);
break;
}
default:
LOG_ERROR("sql.sql", "SmartScript::ProcessAction: Entry {} SourceType {}, Event {}, Unhandled Action type {}", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType());
break;
@@ -777,6 +777,7 @@ bool SmartAIMgr::CheckUnusedActionParams(SmartScriptHolder const& e)
case SMART_ACTION_MOVEMENT_STOP: return NO_PARAMS;
case SMART_ACTION_MOVEMENT_PAUSE: return sizeof(SmartAction::move);
case SMART_ACTION_MOVEMENT_RESUME: return sizeof(SmartAction::move);
case SMART_ACTION_WORLD_SCRIPT: return sizeof(SmartAction::worldStateScript);
default:
LOG_WARN("sql.sql", "SmartAIMgr: entryorguid {} source_type {} id {} action_type {} is using an action with no unused params specified in SmartAIMgr::CheckUnusedActionParams(), please report this.",
e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType());
@@ -1960,6 +1961,7 @@ bool SmartAIMgr::IsEventValid(SmartScriptHolder& e)
case SMART_ACTION_MOVEMENT_STOP:
case SMART_ACTION_MOVEMENT_PAUSE:
case SMART_ACTION_MOVEMENT_RESUME:
case SMART_ACTION_WORLD_SCRIPT:
break;
default:
LOG_ERROR("sql.sql", "SmartAIMgr: Not handled action_type({}), event_type({}), Entry {} SourceType {} Event {}, skipped.", e.GetActionType(), e.GetEventType(), e.entryOrGuid, e.GetScriptType(), e.event_id);
@@ -727,8 +727,9 @@ enum SMART_ACTION
SMART_ACTION_MOVEMENT_STOP = 234, //
SMART_ACTION_MOVEMENT_PAUSE = 235, // timer
SMART_ACTION_MOVEMENT_RESUME = 236, // timerOverride
SMART_ACTION_WORLD_SCRIPT = 237, // eventId, param
SMART_ACTION_AC_END = 237, // placeholder
SMART_ACTION_AC_END = 238, // placeholder
};
enum class SmartActionSummonCreatureFlags
@@ -1472,6 +1473,12 @@ struct SmartAction
{
uint32 timer;
} move;
struct
{
uint32 eventId;
uint32 param;
} worldStateScript;
//! Note for any new future actions
//! All parameters must have type uint32
@@ -28,6 +28,7 @@
#include "Spell.h"
#include "SpellAuras.h"
#include "SpellMgr.h"
#include "WorldState.h"
// Checks if object meets the condition
// Can have CONDITION_SOURCE_TYPE_NONE && !mReferenceId if called from a special event (ie: eventAI)
@@ -570,6 +571,11 @@ bool Condition::Meets(ConditionSourceInfo& sourceInfo)
condMeets = unit->IsCharmed();
break;
}
case CONDITION_WORLD_SCRIPT:
{
condMeets = sWorldState->IsConditionFulfilled(static_cast<WorldStateCondition>(ConditionValue1), static_cast<WorldStateConditionState>(ConditionValue2));
break;
}
default:
condMeets = false;
break;
@@ -770,6 +776,9 @@ uint32 Condition::GetSearcherTypeMaskForCondition()
case CONDITION_CHARMED:
mask |= GRID_MAP_TYPE_MASK_CREATURE | GRID_MAP_TYPE_MASK_PLAYER;
break;
case CONDITION_WORLD_SCRIPT:
mask |= GRID_MAP_TYPE_MASK_ALL;
break;
default:
ASSERT(false && "Condition::GetSearcherTypeMaskForCondition - missing condition handling!");
break;
+3 -1
View File
@@ -86,7 +86,9 @@ enum ConditionTypes
CONDITION_AC_START = 100,
CONDITION_QUEST_SATISFY_EXCLUSIVE = 101, // quest_id 0 0 true if satisfied exclusive group
CONDITION_HAS_AURA_TYPE = 102, // aura_type 0 0 true if has aura type
CONDITION_AC_END = 103 // placeholder
CONDITION_WORLD_SCRIPT = 103, // conditionId state 0 true if WorldState::IsConditionFulfilled returns true
CONDITION_AC_END = 104 // placeholder
};
/*! Documentation on implementing a new ConditionSourceType:
@@ -83,6 +83,7 @@
#include "World.h"
#include "WorldPacket.h"
#include "WorldSession.h"
#include "WorldState.h"
#include <cmath>
/// @todo: this import is not necessary for compilation and marked as unused by the IDE
@@ -1722,6 +1723,7 @@ void Player::RemoveFromWorld()
m_session->DoLootRelease(lguid);
sOutdoorPvPMgr->HandlePlayerLeaveZone(this, m_zoneUpdateId);
sBattlefieldMgr->HandlePlayerLeaveZone(this, m_zoneUpdateId);
sWorldState->HandlePlayerLeaveZone(this, static_cast<WorldStateZoneId>(m_zoneUpdateId));
}
// Remove items from world before self - player must be found in Item::RemoveFromObjectUpdate
@@ -38,6 +38,7 @@
#include "Vehicle.h"
#include "Weather.h"
#include "WeatherMgr.h"
#include "WorldState.h"
#include "WorldStatePackets.h"
/// @todo: this import is not necessary for compilation and marked as unused by the IDE
@@ -1215,6 +1216,8 @@ void Player::UpdateZone(uint32 newZone, uint32 newArea)
{
sOutdoorPvPMgr->HandlePlayerLeaveZone(this, m_zoneUpdateId);
sOutdoorPvPMgr->HandlePlayerEnterZone(this, newZone);
sWorldState->HandlePlayerLeaveZone(this, static_cast<WorldStateZoneId>(m_zoneUpdateId));
sWorldState->HandlePlayerEnterZone(this, static_cast<WorldStateZoneId>(newZone));
sBattlefieldMgr->HandlePlayerLeaveZone(this, m_zoneUpdateId);
sBattlefieldMgr->HandlePlayerEnterZone(this, newZone);
SendInitWorldStates(newZone,
+2
View File
@@ -180,6 +180,7 @@ enum eScriptFlags
// Playsound flags
SF_PLAYSOUND_TARGET_PLAYER = 0x1,
SF_PLAYSOUND_DISTANCE_SOUND = 0x2,
SF_PLAYSOUND_DISTANCE_RADIUS = 0x4,
// Orientation flags
SF_ORIENTATION_FACE_TARGET = 0x1,
@@ -307,6 +308,7 @@ struct ScriptInfo
{
uint32 SoundID; // datalong
uint32 Flags; // datalong2
int32 Radius; // dataint
} Playsound;
struct // SCRIPT_COMMAND_CREATE_ITEM (17)
+4 -2
View File
@@ -730,8 +730,10 @@ void Map::ScriptsProcess()
break;
}
// Playsound.Flags bitmask: 0/2=without/with distance dependent
if (step.script->Playsound.Flags & SF_PLAYSOUND_DISTANCE_SOUND)
// Playsound.Flags bitmask: 0/2/4=without/with distance dependent/radius
if (step.script->Playsound.Flags & SF_PLAYSOUND_DISTANCE_RADIUS)
object->PlayRadiusSound(step.script->Playsound.SoundID, step.script->Playsound.Radius);
else if (step.script->Playsound.Flags & SF_PLAYSOUND_DISTANCE_SOUND)
object->PlayDistanceSound(step.script->Playsound.SoundID, player);
else
object->PlayDirectSound(step.script->Playsound.SoundID, player);
+2
View File
@@ -49,6 +49,7 @@
#include "World.h"
#include "WorldPacket.h"
#include "WorldSocket.h"
#include "WorldState.h"
#include <zlib.h>
#include "BanMgr.h"
@@ -617,6 +618,7 @@ void WorldSession::LogoutPlayer(bool save)
_player->RepopAtGraveyard();
sOutdoorPvPMgr->HandlePlayerLeaveZone(_player, _player->GetZoneId());
sWorldState->HandlePlayerLeaveZone(_player, static_cast<WorldStateZoneId>(_player->GetZoneId()));
// pussywizard: remove from battleground queues on logout
for (int i = 0; i < PLAYER_MAX_BATTLEGROUND_QUEUES; ++i)
@@ -1389,18 +1389,6 @@ void SpellMgr::LoadSpellInfoCorrections()
spellInfo->RangeEntry = sSpellRangeStore.LookupEntry(13); // 50000yd
});
// A'dal's Song of Battle
ApplySpellFix({ 39953 }, [](SpellInfo* spellInfo)
{
spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(TARGET_SRC_CASTER);
spellInfo->Effects[EFFECT_1].TargetA = SpellImplicitTargetInfo(TARGET_SRC_CASTER);
spellInfo->Effects[EFFECT_2].TargetA = SpellImplicitTargetInfo(TARGET_SRC_CASTER);
spellInfo->Effects[EFFECT_0].TargetB = SpellImplicitTargetInfo(TARGET_UNIT_SRC_AREA_ALLY);
spellInfo->Effects[EFFECT_1].TargetB = SpellImplicitTargetInfo(TARGET_UNIT_SRC_AREA_ALLY);
spellInfo->Effects[EFFECT_2].TargetB = SpellImplicitTargetInfo(TARGET_UNIT_SRC_AREA_ALLY);
spellInfo->DurationEntry = sSpellDurationStore.LookupEntry(367); // 2 Hours
});
ApplySpellFix({
57607, // WintergraspCatapult - Spell Plague Barrel - EffectRadiusIndex
57619, // WintergraspDemolisher - Spell Hourl Boulder - EffectRadiusIndex
+6
View File
@@ -92,6 +92,7 @@
#include "WhoListCacheMgr.h"
#include "WorldPacket.h"
#include "WorldSession.h"
#include "WorldState.h"
#include <boost/asio/ip/address.hpp>
#include <cmath>
@@ -2411,6 +2412,11 @@ void World::Update(uint32 diff)
sOutdoorPvPMgr->Update(diff);
}
{
METRIC_TIMER("world_update_time", METRIC_TAG("type", "Update worldstate"));
sWorldState->Update(diff);
}
{
METRIC_TIMER("world_update_time", METRIC_TAG("type", "Update battlefields"));
sBattlefieldMgr->Update(diff);
+293
View File
@@ -0,0 +1,293 @@
/*
* 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/>.
*/
#include "MapMgr.h"
#include "Player.h"
#include "SharedDefines.h"
#include "WorldState.h"
WorldState* WorldState::instance()
{
static WorldState instance;
return &instance;
}
WorldState::WorldState() : _isMagtheridonHeadSpawnedHorde(false), _isMagtheridonHeadSpawnedAlliance(false)
{
_transportStates[WORLD_STATE_CONDITION_THE_IRON_EAGLE] = WORLD_STATE_CONDITION_STATE_NONE;
_transportStates[WORLD_STATE_CONDITION_THE_PURPLE_PRINCESS] = WORLD_STATE_CONDITION_STATE_NONE;
_transportStates[WORLD_STATE_CONDITION_THE_THUNDERCALLER] = WORLD_STATE_CONDITION_STATE_NONE;
}
WorldState::~WorldState()
{
}
bool WorldState::IsConditionFulfilled(WorldStateCondition conditionId, WorldStateConditionState state) const
{
switch (conditionId)
{
case WORLD_STATE_CONDITION_TROLLBANES_COMMAND:
return _isMagtheridonHeadSpawnedAlliance;
case WORLD_STATE_CONDITION_NAZGRELS_FAVOR:
return _isMagtheridonHeadSpawnedHorde;
case WORLD_STATE_CONDITION_THE_IRON_EAGLE:
case WORLD_STATE_CONDITION_THE_PURPLE_PRINCESS:
case WORLD_STATE_CONDITION_THE_THUNDERCALLER:
return _transportStates.at(conditionId) == state;
default:
LOG_ERROR("scripts", "WorldState::IsConditionFulfilled: Unhandled WorldStateCondition {}", conditionId);
return false;
}
}
void WorldState::HandleConditionStateChange(WorldStateCondition conditionId, WorldStateConditionState state)
{
_transportStates[conditionId] = state;
}
void WorldState::HandleExternalEvent(WorldStateEvent eventId, uint32 param)
{
std::lock_guard<std::mutex> guard(_mutex);
switch (eventId)
{
case WORLD_STATE_CUSTOM_EVENT_ON_ADALS_SONG_OF_BATTLE:
if (!_adalSongOfBattleTimer)
{
_adalSongOfBattleTimer = 120 * MINUTE * IN_MILLISECONDS;
BuffAdalsSongOfBattle();
}
break;
case WORLD_STATE_CUSTOM_EVENT_ON_MAGTHERIDON_HEAD_SPAWN:
if (param == TEAM_ALLIANCE)
{
_isMagtheridonHeadSpawnedAlliance = true;
BuffMagtheridonTeam(TEAM_ALLIANCE);
}
else
{
_isMagtheridonHeadSpawnedHorde = true;
BuffMagtheridonTeam(TEAM_HORDE);
}
break;
case WORLD_STATE_CUSTOM_EVENT_ON_MAGTHERIDON_HEAD_DESPAWN:
if (param == TEAM_ALLIANCE)
{
_isMagtheridonHeadSpawnedAlliance = false;
DispelMagtheridonTeam(TEAM_ALLIANCE);
}
else
{
_isMagtheridonHeadSpawnedHorde = false;
DispelMagtheridonTeam(TEAM_HORDE);
}
break;
default:
break;
}
}
void WorldState::Update(uint32 diff)
{
if (_adalSongOfBattleTimer)
{
if (_adalSongOfBattleTimer <= diff)
{
_adalSongOfBattleTimer = 0;
DispelAdalsSongOfBattle();
}
else
{
_adalSongOfBattleTimer -= diff;
}
}
}
void WorldState::HandlePlayerEnterZone(Player* player, WorldStateZoneId zoneId)
{
std::lock_guard<std::mutex> guard(_mutex);
switch (zoneId)
{
case ZONEID_SHATTRATH:
case ZONEID_BOTANICA:
case ZONEID_MECHANAR:
case ZONEID_ARCATRAZ:
if (_adalSongOfBattleTimer)
player->CastSpell(player, SPELL_ADAL_SONG_OF_BATTLE, true);
break;
case ZONEID_HELLFIRE_PENINSULA:
case ZONEID_HELLFIRE_RAMPARTS:
case ZONEID_HELLFIRE_CITADEL:
case ZONEID_BLOOD_FURNACE:
case ZONEID_SHATTERED_HALLS:
case ZONEID_MAGTHERIDON_LAIR:
if (_isMagtheridonHeadSpawnedAlliance && player->GetTeamId() == TEAM_ALLIANCE)
player->CastSpell(player, SPELL_TROLLBANES_COMMAND, true);
else if (_isMagtheridonHeadSpawnedHorde && player->GetTeamId() == TEAM_HORDE)
player->CastSpell(player, SPELL_NAZGRELS_FAVOR, true);
break;
default:
break;
}
};
void WorldState::HandlePlayerLeaveZone(Player* player, WorldStateZoneId zoneId)
{
std::lock_guard<std::mutex> guard(_mutex);
switch (zoneId)
{
case ZONEID_SHATTRATH:
case ZONEID_BOTANICA:
case ZONEID_MECHANAR:
case ZONEID_ARCATRAZ:
if (!_adalSongOfBattleTimer)
player->RemoveAurasDueToSpell(SPELL_ADAL_SONG_OF_BATTLE);
break;
case ZONEID_HELLFIRE_PENINSULA:
case ZONEID_HELLFIRE_RAMPARTS:
case ZONEID_HELLFIRE_CITADEL:
case ZONEID_BLOOD_FURNACE:
case ZONEID_SHATTERED_HALLS:
case ZONEID_MAGTHERIDON_LAIR:
if (player->GetTeamId() == TEAM_ALLIANCE)
player->RemoveAurasDueToSpell(SPELL_TROLLBANES_COMMAND);
else if (player->GetTeamId() == TEAM_HORDE)
player->RemoveAurasDueToSpell(SPELL_NAZGRELS_FAVOR);
break;
default:
break;
}
};
void WorldState::BuffMagtheridonTeam(TeamId team)
{
sMapMgr->DoForAllMaps([&](Map* map) -> void
{
switch (map->GetId())
{
case 530: // Outland
map->DoForAllPlayers([&](Player* player)
{
if (player->GetZoneId() == ZONEID_HELLFIRE_PENINSULA && player->GetTeamId() == TEAM_ALLIANCE && team == TEAM_ALLIANCE)
player->CastSpell(player, SPELL_TROLLBANES_COMMAND, true);
else if (player->GetZoneId() == ZONEID_HELLFIRE_PENINSULA && player->GetTeamId() == TEAM_HORDE && team == TEAM_HORDE)
player->CastSpell(player, SPELL_NAZGRELS_FAVOR, true);
});
break;
case 540: // The Shattered Halls
case 542: // The Blood Furnace
case 543: // Ramparts
case 544: // Magtheridon's Lair
map->DoForAllPlayers([&](Player* player)
{
if (player->GetTeamId() == TEAM_ALLIANCE && team == TEAM_ALLIANCE)
player->CastSpell(player, SPELL_TROLLBANES_COMMAND, true);
else if (player->GetTeamId() == TEAM_HORDE && team == TEAM_HORDE)
player->CastSpell(player, SPELL_NAZGRELS_FAVOR, true);
});
break;
default:
break;
}
});
}
void WorldState::DispelMagtheridonTeam(TeamId team)
{
sMapMgr->DoForAllMaps([&](Map* map) -> void
{
switch (map->GetId())
{
case 530: // Outland
map->DoForAllPlayers([&](Player* player)
{
if (player->GetZoneId() == ZONEID_HELLFIRE_PENINSULA && player->GetTeamId() == TEAM_ALLIANCE && team == TEAM_ALLIANCE)
player->RemoveAurasDueToSpell(SPELL_TROLLBANES_COMMAND);
else if (player->GetZoneId() == ZONEID_HELLFIRE_PENINSULA && player->GetTeamId() == TEAM_HORDE && team == TEAM_HORDE)
player->RemoveAurasDueToSpell(SPELL_NAZGRELS_FAVOR);
});
break;
case 540: // The Shattered Halls
case 542: // The Blood Furnace
case 543: // Ramparts
case 544: // Magtheridon's Lair
map->DoForAllPlayers([&](Player* player)
{
if (player->GetTeamId() == TEAM_ALLIANCE && team == TEAM_ALLIANCE)
player->RemoveAurasDueToSpell(SPELL_TROLLBANES_COMMAND);
else if (player->GetTeamId() == TEAM_HORDE && team == TEAM_HORDE)
player->RemoveAurasDueToSpell(SPELL_NAZGRELS_FAVOR);
});
break;
default:
break;
}
});
}
void WorldState::BuffAdalsSongOfBattle()
{
sMapMgr->DoForAllMaps([&](Map* map) -> void
{
switch (map->GetId())
{
case 530: // Outland
map->DoForAllPlayers([&](Player* player)
{
if (player->GetZoneId() == ZONEID_SHATTRATH)
player->CastSpell(player, SPELL_ADAL_SONG_OF_BATTLE, true);
});
break;
case 552: // Arcatraz
case 553: // Botanica
case 554: // Mechanar
map->DoForAllPlayers([&](Player* player)
{
player->CastSpell(player, SPELL_ADAL_SONG_OF_BATTLE, true);
});
break;
default:
break;
}
});
}
void WorldState::DispelAdalsSongOfBattle()
{
sMapMgr->DoForAllMaps([&](Map* map) -> void
{
switch (map->GetId())
{
case 530: // Outland
map->DoForAllPlayers([&](Player* player)
{
if (player->GetZoneId() == ZONEID_SHATTRATH)
player->RemoveAurasDueToSpell(SPELL_ADAL_SONG_OF_BATTLE);
});
break;
case 552: // Arcatraz
case 553: // Botanica
case 554: // Mechanar
map->DoForAllPlayers([&](Player* player)
{
player->RemoveAurasDueToSpell(SPELL_ADAL_SONG_OF_BATTLE);
});
break;
default:
break;
}
});
}
+95
View File
@@ -0,0 +1,95 @@
/*
* 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 WORLD_STATE_H
#define WORLD_STATE_H
#include "Player.h"
#include <atomic>
enum WorldStateCondition
{
WORLD_STATE_CONDITION_TROLLBANES_COMMAND = 39911,
WORLD_STATE_CONDITION_NAZGRELS_FAVOR = 39913,
// Zeppelins
WORLD_STATE_CONDITION_THE_THUNDERCALLER = 164871,
WORLD_STATE_CONDITION_THE_IRON_EAGLE = 175080,
WORLD_STATE_CONDITION_THE_PURPLE_PRINCESS = 176495,
};
enum WorldStateConditionState
{
WORLD_STATE_CONDITION_STATE_NONE = 0,
};
enum WorldStateEvent
{
WORLD_STATE_CUSTOM_EVENT_ON_ADALS_SONG_OF_BATTLE = 39953,
WORLD_STATE_CUSTOM_EVENT_ON_MAGTHERIDON_HEAD_SPAWN = 184640,
WORLD_STATE_CUSTOM_EVENT_ON_MAGTHERIDON_HEAD_DESPAWN = 184641,
};
enum WorldStateZoneId
{
ZONEID_SHATTRATH = 3703,
ZONEID_BOTANICA = 3847,
ZONEID_ARCATRAZ = 3848,
ZONEID_MECHANAR = 3849,
ZONEID_HELLFIRE_PENINSULA = 3483,
ZONEID_HELLFIRE_RAMPARTS = 3562,
ZONEID_HELLFIRE_CITADEL = 3563,
ZONEID_BLOOD_FURNACE = 3713,
ZONEID_SHATTERED_HALLS = 3714,
ZONEID_MAGTHERIDON_LAIR = 3836,
};
enum WorldStateSpells
{
SPELL_ADAL_SONG_OF_BATTLE = 39953,
SPELL_TROLLBANES_COMMAND = 39911,
SPELL_NAZGRELS_FAVOR = 39913,
};
// Intended for implementing server wide scripts, note: all behaviour must be safeguarded towards multithreading
class WorldState
{
public:
WorldState();
virtual ~WorldState();
static WorldState* instance();
void HandlePlayerEnterZone(Player* player, WorldStateZoneId zoneId);
void HandlePlayerLeaveZone(Player* player, WorldStateZoneId zoneId);
bool IsConditionFulfilled(WorldStateCondition conditionId, WorldStateConditionState state = WORLD_STATE_CONDITION_STATE_NONE) const;
void HandleConditionStateChange(WorldStateCondition conditionId, WorldStateConditionState state);
void HandleExternalEvent(WorldStateEvent eventId, uint32 param);
void Update(uint32 diff);
private:
void BuffAdalsSongOfBattle();
void DispelAdalsSongOfBattle();
uint32 _adalSongOfBattleTimer;
void BuffMagtheridonTeam(TeamId team);
void DispelMagtheridonTeam(TeamId team);
bool _isMagtheridonHeadSpawnedHorde;
bool _isMagtheridonHeadSpawnedAlliance;
std::map<WorldStateCondition, std::atomic<WorldStateConditionState>> _transportStates; // atomic to avoid having to lock
std::mutex _mutex; // all World State operations are threat unsafe
};
#define sWorldState WorldState::instance()
#endif
@@ -24,6 +24,7 @@
#include "ScriptedGossip.h"
#include "SpellScript.h"
#include "SpellScriptLoader.h"
#include "WorldState.h"
enum q10935Exorcism
{
@@ -602,6 +603,26 @@ public:
}
};
struct go_magtheridons_head : public GameObjectAI
{
go_magtheridons_head(GameObject* gameObject) : GameObjectAI(gameObject) { }
void InitializeAI() override
{
me->SetGoState(GO_STATE_ACTIVE_ALTERNATIVE); // spawn head on spike
me->SetGameObjectFlag(GO_FLAG_NOT_SELECTABLE);
sWorldState->HandleExternalEvent(WORLD_STATE_CUSTOM_EVENT_ON_MAGTHERIDON_HEAD_SPAWN, me->GetPositionX() > 0.f ? TEAM_HORDE : TEAM_ALLIANCE);
}
void OnStateChanged(uint32 state, Unit* /*unit*/) override
{
if (state == GO_JUST_DEACTIVATED)
{
sWorldState->HandleExternalEvent(WORLD_STATE_CUSTOM_EVENT_ON_MAGTHERIDON_HEAD_DESPAWN, me->GetPositionX() > 0.f ? TEAM_HORDE : TEAM_ALLIANCE);
}
}
};
void AddSC_hellfire_peninsula()
{
// Ours
@@ -615,4 +636,5 @@ void AddSC_hellfire_peninsula()
new go_beacon();
RegisterCreatureAI(npc_magister_aledis);
RegisterGameObjectAI(go_magtheridons_head);
}
@@ -554,26 +554,6 @@ class spell_gen_rallying_cry_of_the_dragonslayer : public SpellScript
}
};
// 39953 - A'dal's Song of Battle
class spell_gen_adals_song_of_battle : public SpellScript
{
PrepareSpellScript(spell_gen_adals_song_of_battle);
void SelectTarget(std::list<WorldObject*>& targets)
{
targets.clear();
Map::PlayerList const& pList = GetCaster()->GetMap()->GetPlayers();
for (Map::PlayerList::const_iterator itr = pList.begin(); itr != pList.end(); ++itr)
if (itr->GetSource()->GetZoneId() == 3703 /*Shattrath*/)
targets.push_back(itr->GetSource());
}
void Register() override
{
OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_gen_adals_song_of_battle::SelectTarget, EFFECT_ALL, TARGET_UNIT_SRC_AREA_ALLY);
}
};
/* 15366 - Songflower Serenade
22888 - Rallying Cry of the Dragonslayer */
class spell_gen_disabled_above_63 : public AuraScript
@@ -5336,7 +5316,6 @@ void AddSC_generic_spell_scripts()
RegisterSpellScript(spell_pet_hit_expertise_scalling);
RegisterSpellScript(spell_gen_grow_flower_patch);
RegisterSpellScript(spell_gen_rallying_cry_of_the_dragonslayer);
RegisterSpellScript(spell_gen_adals_song_of_battle);
RegisterSpellScript(spell_gen_disabled_above_63);
RegisterSpellScript(spell_gen_black_magic_enchant);
RegisterSpellScript(spell_gen_area_aura_select_players);
@@ -0,0 +1,68 @@
/*
* 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/>.
*/
enum ZeppelinEvent
{
EVENT_UC_FROM_GROMGOL_ARRIVAL = 15312,
EVENT_GROMGOL_FROM_UC_ARRIVAL = 15314,
EVENT_OG_FROM_UC_ARRIVAL = 15318,
EVENT_UC_FROM_OG_ARRIVAL = 15320,
EVENT_OG_FROM_GROMGOL_ARRIVAL = 15322,
EVENT_GROMGOL_FROM_OG_ARRIVAL = 15324,
EVENT_WK_ARRIVAL = 15431,
EVENT_VL_FROM_UC_ARRIVAL = 19126,
EVENT_UC_FROM_VL_ARRIVAL = 19127,
EVENT_OG_FROM_BT_ARRIVAL = 19137,
EVENT_BT_FROM_OG_ARRIVAL = 19139,
EVENT_OG_FROM_TB_ARRIVAL = 21868,
EVENT_TB_FROM_OG_ARRIVAL = 21870,
EVENT_OG_TO_GROMGOL_DEPARTURE = 15323,
EVENT_GROMGOL_TO_OG_DEPARTURE = 15325,
EVENT_OG_TO_UC_DEPARTURE = 15319,
EVENT_UC_TO_OG_DEPARTURE = 15321,
EVENT_UC_TO_GROMGOL_DEPARTURE = 15313,
EVENT_GROMGOL_TO_UC_DEPARTURE = 15315,
};
enum ZeppelinMaster
{
NPC_NEZRAZ = 3149,
NPC_HINDENBURG = 3150,
NPC_FREZZA = 9564,
NPC_ZAPETTA = 9566,
NPC_SNURK_BUCKSQUICK = 12136,
NPC_SQUIBBY_OVERSPECK = 12137,
NPC_HARROWMEISER = 23823,
NPC_GREEB_RAMROCKET = 26537,
NPC_NARGO_SCREWBORE = 26538,
NPC_MEEFI_FARTHROTTLE = 26539,
NPC_DRENK_SPANNERSPARK = 26540,
NPC_ZELLI_HOTNOZZLE = 34765,
NPC_KRENDLE_BIGPOCKETS = 34766,
};
const float SEARCH_RANGE_ZEPPELIN_MASTER = 32.0f;
enum ZeppelinPassenger
{
// The Thundercaller
NPC_SKY_CAPTAIN_CLOUDKICKER = 25077,
NPC_CHIEF_OFFICER_COPPERNUT = 25070,
// The Purple Princess
NPC_SKY_CAPTAIN_CABLELAMP = 25105,
NPC_WATCHER_UMJIN = 25107,
};
@@ -0,0 +1,109 @@
/*
* 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/>.
*/
#include "GameObjectAI.h"
#include "GameObjectScript.h"
#include "WorldState.h"
#include "transport_zeppelin.h"
// 175080 The Iron Eagle - Grom'gol to Orgrimmar
struct go_transport_the_iron_eagle : GameObjectAI
{
go_transport_the_iron_eagle(GameObject *object) : GameObjectAI(object) { };
void EventInform(uint32 eventId) override
{
sWorldState->HandleConditionStateChange(WORLD_STATE_CONDITION_THE_IRON_EAGLE, static_cast<WorldStateConditionState>(eventId));
switch (eventId)
{
case EVENT_GROMGOL_FROM_OG_ARRIVAL:
if (Creature *creature = me->FindNearestCreature(NPC_NEZRAZ, SEARCH_RANGE_ZEPPELIN_MASTER))
creature->AI()->Talk(0);
break;
case EVENT_OG_FROM_GROMGOL_ARRIVAL:
if (Creature *creature = me->FindNearestCreature(NPC_SNURK_BUCKSQUICK, 150.0f))
creature->AI()->Talk(0);
break;
default:
return;
}
}
};
// 164871 The Thundercaller - Undercity to Orgrimmar
struct go_transport_the_thundercaller : GameObjectAI
{
go_transport_the_thundercaller(GameObject *object) : GameObjectAI(object) { };
void EventInform(uint32 eventId) override
{
sWorldState->HandleConditionStateChange(WORLD_STATE_CONDITION_THE_THUNDERCALLER, static_cast<WorldStateConditionState>(eventId));
switch (eventId)
{
case EVENT_OG_FROM_UC_ARRIVAL:
if (Creature *creature = me->FindNearestCreature(NPC_FREZZA, SEARCH_RANGE_ZEPPELIN_MASTER))
creature->AI()->Talk(0);
break;
case EVENT_UC_FROM_OG_ARRIVAL:
if (Creature *creature = me->FindNearestCreature(NPC_ZAPETTA, SEARCH_RANGE_ZEPPELIN_MASTER))
creature->AI()->Talk(0);
break;
case EVENT_OG_TO_UC_DEPARTURE:
break;
case EVENT_UC_TO_OG_DEPARTURE:
if (Creature *creature = me->FindNearestCreature(NPC_ZAPETTA, SEARCH_RANGE_ZEPPELIN_MASTER))
creature->AI()->Talk(1);
break;
default:
return;
}
}
};
// 176495 The Purple Princess - Grom'Gol to Undercity
struct go_transport_the_purple_princess : GameObjectAI
{
go_transport_the_purple_princess(GameObject *object) : GameObjectAI(object) { };
void EventInform(uint32 eventId) override
{
sWorldState->HandleConditionStateChange(WORLD_STATE_CONDITION_THE_PURPLE_PRINCESS, static_cast<WorldStateConditionState>(eventId));
switch (eventId)
{
case EVENT_GROMGOL_FROM_UC_ARRIVAL:
if (Creature *creature = me->FindNearestCreature(NPC_SQUIBBY_OVERSPECK, SEARCH_RANGE_ZEPPELIN_MASTER))
creature->AI()->Talk(0);
break;
case EVENT_UC_FROM_GROMGOL_ARRIVAL:
if (Creature *creature = me->FindNearestCreature(NPC_HINDENBURG, SEARCH_RANGE_ZEPPELIN_MASTER))
creature->AI()->Talk(0);
break;
case EVENT_UC_TO_GROMGOL_DEPARTURE:
case EVENT_GROMGOL_TO_UC_DEPARTURE:
break;
default:
return;
}
}
};
void AddSC_transport_zeppelins()
{
RegisterGameObjectAI(go_transport_the_iron_eagle);
RegisterGameObjectAI(go_transport_the_thundercaller);
RegisterGameObjectAI(go_transport_the_purple_princess);
}
@@ -32,6 +32,7 @@ void AddSC_action_ip_logger(); // location: scripts\World\action_ip_logger.cpp
void AddSC_player_scripts();
void AddSC_npc_stave_of_ancients();
void AddSC_server_mail();
void AddSC_transport_zeppelins();
// The name of this function should match:
// void Add${NameOfDirectory}Scripts()
@@ -53,4 +54,5 @@ void AddWorldScripts()
AddSC_player_scripts();
AddSC_npc_stave_of_ancients();
AddSC_server_mail();
AddSC_transport_zeppelins();
}