mirror of
https://github.com/mangosfour/server.git
synced 2025-12-15 10:37:02 +00:00
Implement Nagrand - Halaa Outdoor PvP script
This commit is contained in:
parent
f32d6d31c9
commit
b783b60d35
3 changed files with 741 additions and 0 deletions
|
|
@ -54,6 +54,7 @@ void OutdoorPvPMgr::InitOutdoorPvP()
|
||||||
m_scripts[OPVP_ID_HP] = new OutdoorPvPHP();
|
m_scripts[OPVP_ID_HP] = new OutdoorPvPHP();
|
||||||
m_scripts[OPVP_ID_ZM] = new OutdoorPvPZM();
|
m_scripts[OPVP_ID_ZM] = new OutdoorPvPZM();
|
||||||
m_scripts[OPVP_ID_TF] = new OutdoorPvPTF();
|
m_scripts[OPVP_ID_TF] = new OutdoorPvPTF();
|
||||||
|
m_scripts[OPVP_ID_NA] = new OutdoorPvPNA();
|
||||||
|
|
||||||
sLog.outString();
|
sLog.outString();
|
||||||
sLog.outString(">> Loaded %u Outdoor PvP zones", MAX_OPVP_ID);
|
sLog.outString(">> Loaded %u Outdoor PvP zones", MAX_OPVP_ID);
|
||||||
|
|
|
||||||
|
|
@ -15,3 +15,546 @@
|
||||||
* along with this program; if not, write to the Free Software
|
* along with this program; if not, write to the Free Software
|
||||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "OutdoorPvPNA.h"
|
||||||
|
#include "WorldPacket.h"
|
||||||
|
#include "../World.h"
|
||||||
|
#include "../ObjectMgr.h"
|
||||||
|
#include "../Object.h"
|
||||||
|
#include "../Creature.h"
|
||||||
|
#include "../GameObject.h"
|
||||||
|
#include "../Player.h"
|
||||||
|
|
||||||
|
OutdoorPvPNA::OutdoorPvPNA() : OutdoorPvP(),
|
||||||
|
m_zoneOwner(TEAM_NONE),
|
||||||
|
m_soldiersRespawnTimer(0),
|
||||||
|
m_zoneWorldState(0),
|
||||||
|
m_zoneMapState(WORLD_STATE_NA_HALAA_NEUTRAL),
|
||||||
|
m_guardsLeft(0),
|
||||||
|
m_isUnderSiege(false)
|
||||||
|
{
|
||||||
|
// initially set graveyard owner to neither faction
|
||||||
|
sObjectMgr.SetGraveYardLinkTeam(GRAVEYARD_ID_HALAA, GRAVEYARD_ZONE_ID_HALAA, TEAM_INVALID);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OutdoorPvPNA::FillInitialWorldStates(WorldPacket& data, uint32& count)
|
||||||
|
{
|
||||||
|
if (m_zoneOwner != TEAM_NONE)
|
||||||
|
{
|
||||||
|
FillInitialWorldState(data, count, m_zoneWorldState, WORLD_STATE_ADD);
|
||||||
|
|
||||||
|
// map states
|
||||||
|
for (uint8 i = 0; i < MAX_NA_ROOSTS; ++i)
|
||||||
|
FillInitialWorldState(data, count, m_roostWorldState[i], WORLD_STATE_ADD);
|
||||||
|
}
|
||||||
|
|
||||||
|
FillInitialWorldState(data, count, m_zoneMapState, WORLD_STATE_ADD);
|
||||||
|
FillInitialWorldState(data, count, WORLD_STATE_NA_GUARDS_MAX, MAX_NA_GUARDS);
|
||||||
|
FillInitialWorldState(data, count, WORLD_STATE_NA_GUARDS_LEFT, m_guardsLeft);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OutdoorPvPNA::SendRemoveWorldStates(Player* player)
|
||||||
|
{
|
||||||
|
player->SendUpdateWorldState(m_zoneWorldState, WORLD_STATE_REMOVE);
|
||||||
|
player->SendUpdateWorldState(m_zoneMapState, WORLD_STATE_REMOVE);
|
||||||
|
|
||||||
|
for (uint8 i = 0; i < MAX_NA_ROOSTS; ++i)
|
||||||
|
player->SendUpdateWorldState(m_roostWorldState[i], WORLD_STATE_REMOVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OutdoorPvPNA::HandlePlayerEnterZone(Player* player, bool isMainZone)
|
||||||
|
{
|
||||||
|
OutdoorPvP::HandlePlayerEnterZone(player, isMainZone);
|
||||||
|
|
||||||
|
// remove the buff from the player first because there are some issues at relog
|
||||||
|
player->RemoveAurasDueToSpell(SPELL_STRENGTH_HALAANI);
|
||||||
|
|
||||||
|
// buff the player if same team is controlling the zone
|
||||||
|
if (player->GetTeam() == m_zoneOwner)
|
||||||
|
player->CastSpell(player, SPELL_STRENGTH_HALAANI, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OutdoorPvPNA::HandlePlayerLeaveZone(Player* player, bool isMainZone)
|
||||||
|
{
|
||||||
|
// remove the buff from the player
|
||||||
|
player->RemoveAurasDueToSpell(SPELL_STRENGTH_HALAANI);
|
||||||
|
|
||||||
|
OutdoorPvP::HandlePlayerLeaveZone(player, isMainZone);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OutdoorPvPNA::HandleObjectiveComplete(uint32 eventId, std::list<Player*> players, Team team)
|
||||||
|
{
|
||||||
|
if (eventId == EVENT_HALAA_BANNER_WIN_ALLIANCE || eventId == EVENT_HALAA_BANNER_WIN_HORDE)
|
||||||
|
{
|
||||||
|
for (std::list<Player*>::iterator itr = players.begin(); itr != players.end(); ++itr)
|
||||||
|
{
|
||||||
|
if ((*itr) && (*itr)->GetTeam() == team)
|
||||||
|
(*itr)->KilledMonsterCredit(NPC_HALAA_COMBATANT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cast player spell on opponent kill
|
||||||
|
void OutdoorPvPNA::HandlePlayerKillInsideArea(Player* player, Unit* /*victim*/)
|
||||||
|
{
|
||||||
|
if (GameObject* capturePoint = player->GetMap()->GetGameObject(m_capturePoint))
|
||||||
|
{
|
||||||
|
// check capture point range
|
||||||
|
GameObjectInfo const* info = capturePoint->GetGOInfo();
|
||||||
|
if (info && player->IsWithinDistInMap(capturePoint, info->capturePoint.radius))
|
||||||
|
{
|
||||||
|
// check capture point team
|
||||||
|
if (player->GetTeam() == m_zoneOwner)
|
||||||
|
player->CastSpell(player, player->GetTeam() == ALLIANCE ? SPELL_NAGRAND_TOKEN_ALLIANCE : SPELL_NAGRAND_TOKEN_HORDE, true);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OutdoorPvPNA::HandleCreatureCreate(Creature* creature)
|
||||||
|
{
|
||||||
|
switch (creature->GetEntry())
|
||||||
|
{
|
||||||
|
case NPC_RESEARCHER_KARTOS:
|
||||||
|
case NPC_QUARTERMASTER_DAVIAN:
|
||||||
|
case NPC_MERCHANT_ALDRAAN:
|
||||||
|
case NPC_VENDOR_CENDRII:
|
||||||
|
case NPC_AMMUNITIONER_BANRO:
|
||||||
|
case NPC_RESEARCHER_AMERELDINE:
|
||||||
|
case NPC_QUARTERMASTER_NORELIQE:
|
||||||
|
case NPC_MERCHANT_COREIEL:
|
||||||
|
case NPC_VENDOR_EMBELAR:
|
||||||
|
case NPC_AMMUNITIONER_TASALDAN:
|
||||||
|
m_teamVendors.push_back(creature->GetObjectGuid());
|
||||||
|
break;
|
||||||
|
case NPC_HORDE_HALAANI_GUARD:
|
||||||
|
case NPC_ALLIANCE_HANAANI_GUARD:
|
||||||
|
// prevent updating guard counter on owner take over
|
||||||
|
if (m_guardsLeft == MAX_NA_GUARDS)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (m_guardsLeft == 0)
|
||||||
|
{
|
||||||
|
LockHalaa(creature);
|
||||||
|
|
||||||
|
// update world state
|
||||||
|
SendUpdateWorldState(m_zoneMapState, WORLD_STATE_REMOVE);
|
||||||
|
m_zoneMapState = m_zoneOwner == ALLIANCE ? WORLD_STATE_NA_HALAA_ALLIANCE : WORLD_STATE_NA_HALAA_HORDE;
|
||||||
|
SendUpdateWorldState(m_zoneMapState, WORLD_STATE_ADD);
|
||||||
|
}
|
||||||
|
|
||||||
|
++m_guardsLeft;
|
||||||
|
SendUpdateWorldState(WORLD_STATE_NA_GUARDS_LEFT, m_guardsLeft);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OutdoorPvPNA::HandleCreatureDeath(Creature* creature)
|
||||||
|
{
|
||||||
|
if (creature->GetEntry() != NPC_HORDE_HALAANI_GUARD && creature->GetEntry() != NPC_ALLIANCE_HANAANI_GUARD)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// get the location of the dead guard for future respawn
|
||||||
|
float x, y, z, o;
|
||||||
|
creature->GetRespawnCoord(x, y, z, &o);
|
||||||
|
HalaaSoldiersSpawns location = {x, y, z, o};
|
||||||
|
m_deadSoldiers.push(location);
|
||||||
|
|
||||||
|
// set the respawn timer after the last guard died - 5 min for the first time, or 1 hour if the city is under siege
|
||||||
|
if (!m_soldiersRespawnTimer)
|
||||||
|
m_soldiersRespawnTimer = m_isUnderSiege ? HOUR * IN_MILLISECONDS : 5 * MINUTE * IN_MILLISECONDS;
|
||||||
|
|
||||||
|
// decrease the counter
|
||||||
|
--m_guardsLeft;
|
||||||
|
SendUpdateWorldState(WORLD_STATE_NA_GUARDS_LEFT, m_guardsLeft);
|
||||||
|
|
||||||
|
if (m_guardsLeft == 0)
|
||||||
|
{
|
||||||
|
// set the zone under siege and increase the respawn timer
|
||||||
|
m_isUnderSiege = true;
|
||||||
|
m_soldiersRespawnTimer = HOUR * IN_MILLISECONDS;
|
||||||
|
|
||||||
|
// make capturable
|
||||||
|
UnlockHalaa(creature);
|
||||||
|
|
||||||
|
// update world state
|
||||||
|
SendUpdateWorldState(m_zoneMapState, WORLD_STATE_REMOVE);
|
||||||
|
m_zoneMapState = m_zoneOwner == ALLIANCE ? WORLD_STATE_NA_HALAA_NEUTRAL_A : WORLD_STATE_NA_HALAA_NEUTRAL_H;
|
||||||
|
SendUpdateWorldState(m_zoneMapState, WORLD_STATE_ADD);
|
||||||
|
|
||||||
|
sWorld.SendDefenseMessage(ZONE_ID_NAGRAND, LANG_OPVP_NA_DEFENSELESS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OutdoorPvPNA::HandleGameObjectCreate(GameObject* go)
|
||||||
|
{
|
||||||
|
switch (go->GetEntry())
|
||||||
|
{
|
||||||
|
case GO_HALAA_BANNER:
|
||||||
|
m_capturePoint = go->GetObjectGuid();
|
||||||
|
go->SetGoArtKit(GetBannerArtKit(m_zoneOwner));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GO_WYVERN_ROOST_ALLIANCE_SOUTH:
|
||||||
|
m_roostsAlliance[0] = go->GetObjectGuid();
|
||||||
|
break;
|
||||||
|
case GO_WYVERN_ROOST_ALLIANCE_NORTH:
|
||||||
|
m_roostsAlliance[1] = go->GetObjectGuid();
|
||||||
|
break;
|
||||||
|
case GO_WYVERN_ROOST_ALLIANCE_EAST:
|
||||||
|
m_roostsAlliance[2] = go->GetObjectGuid();
|
||||||
|
break;
|
||||||
|
case GO_WYVERN_ROOST_ALLIANCE_WEST:
|
||||||
|
m_roostsAlliance[3] = go->GetObjectGuid();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GO_BOMB_WAGON_HORDE_SOUTH:
|
||||||
|
m_wagonsHorde[0] = go->GetObjectGuid();
|
||||||
|
break;
|
||||||
|
case GO_BOMB_WAGON_HORDE_NORTH:
|
||||||
|
m_wagonsHorde[1] = go->GetObjectGuid();
|
||||||
|
break;
|
||||||
|
case GO_BOMB_WAGON_HORDE_EAST:
|
||||||
|
m_wagonsHorde[2] = go->GetObjectGuid();
|
||||||
|
break;
|
||||||
|
case GO_BOMB_WAGON_HORDE_WEST:
|
||||||
|
m_wagonsHorde[3] = go->GetObjectGuid();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GO_DESTROYED_ROOST_ALLIANCE_SOUTH:
|
||||||
|
m_roostsBrokenAlliance[0] = go->GetObjectGuid();
|
||||||
|
break;
|
||||||
|
case GO_DESTROYED_ROOST_ALLIANCE_NORTH:
|
||||||
|
m_roostsBrokenAlliance[1] = go->GetObjectGuid();
|
||||||
|
break;
|
||||||
|
case GO_DESTROYED_ROOST_ALLIANCE_EAST:
|
||||||
|
m_roostsBrokenAlliance[2] = go->GetObjectGuid();
|
||||||
|
break;
|
||||||
|
case GO_DESTROYED_ROOST_ALLIANCE_WEST:
|
||||||
|
m_roostsBrokenAlliance[3] = go->GetObjectGuid();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GO_WYVERN_ROOST_HORDE_SOUTH:
|
||||||
|
m_roostsHorde[0] = go->GetObjectGuid();
|
||||||
|
break;
|
||||||
|
case GO_WYVERN_ROOST_HORDE_NORTH:
|
||||||
|
m_roostsHorde[1] = go->GetObjectGuid();
|
||||||
|
break;
|
||||||
|
case GO_WYVERN_ROOST_HORDE_EAST:
|
||||||
|
m_roostsHorde[2] = go->GetObjectGuid();
|
||||||
|
break;
|
||||||
|
case GO_WYVERN_ROOST_HORDE_WEST:
|
||||||
|
m_roostsHorde[3] = go->GetObjectGuid();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GO_BOMB_WAGON_ALLIANCE_SOUTH:
|
||||||
|
m_wagonsAlliance[0] = go->GetObjectGuid();
|
||||||
|
break;
|
||||||
|
case GO_BOMB_WAGON_ALLIANCE_NORTH:
|
||||||
|
m_wagonsAlliance[1] = go->GetObjectGuid();
|
||||||
|
break;
|
||||||
|
case GO_BOMB_WAGON_ALLIANCE_EAST:
|
||||||
|
m_wagonsAlliance[2] = go->GetObjectGuid();
|
||||||
|
break;
|
||||||
|
case GO_BOMB_WAGON_ALLIANCE_WEST:
|
||||||
|
m_wagonsAlliance[3] = go->GetObjectGuid();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GO_DESTROYED_ROOST_HORDE_SOUTH:
|
||||||
|
m_roostsBrokenHorde[0] = go->GetObjectGuid();
|
||||||
|
break;
|
||||||
|
case GO_DESTROYED_ROOST_HORDE_NORTH:
|
||||||
|
m_roostsBrokenHorde[1] = go->GetObjectGuid();
|
||||||
|
break;
|
||||||
|
case GO_DESTROYED_ROOST_HORDE_EAST:
|
||||||
|
m_roostsBrokenHorde[2] = go->GetObjectGuid();
|
||||||
|
break;
|
||||||
|
case GO_DESTROYED_ROOST_HORDE_WEST:
|
||||||
|
m_roostsBrokenHorde[3] = go->GetObjectGuid();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OutdoorPvPNA::UpdateWorldState(uint32 value)
|
||||||
|
{
|
||||||
|
SendUpdateWorldState(m_zoneWorldState, value);
|
||||||
|
SendUpdateWorldState(m_zoneMapState, value);
|
||||||
|
|
||||||
|
UpdateWyvernsWorldState(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OutdoorPvPNA::UpdateWyvernsWorldState(uint32 value)
|
||||||
|
{
|
||||||
|
for (uint8 i = 0; i < MAX_NA_ROOSTS; ++i)
|
||||||
|
SendUpdateWorldState(m_roostWorldState[i], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// process the capture events
|
||||||
|
bool OutdoorPvPNA::HandleEvent(uint32 eventId, GameObject* go)
|
||||||
|
{
|
||||||
|
// If we are not using the Halaa banner return
|
||||||
|
if (go->GetEntry() != GO_HALAA_BANNER)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bool eventHandled = true;
|
||||||
|
|
||||||
|
switch (eventId)
|
||||||
|
{
|
||||||
|
case EVENT_HALAA_BANNER_WIN_ALLIANCE:
|
||||||
|
ProcessCaptureEvent(go, ALLIANCE);
|
||||||
|
eventHandled = false;
|
||||||
|
break;
|
||||||
|
case EVENT_HALAA_BANNER_WIN_HORDE:
|
||||||
|
ProcessCaptureEvent(go, HORDE);
|
||||||
|
eventHandled = false;
|
||||||
|
break;
|
||||||
|
case EVENT_HALAA_BANNER_PROGRESS_ALLIANCE:
|
||||||
|
SetBannerVisual(go, CAPTURE_ARTKIT_ALLIANCE, CAPTURE_ANIM_ALLIANCE);
|
||||||
|
sWorld.SendDefenseMessage(ZONE_ID_NAGRAND, LANG_OPVP_NA_PROGRESS_A);
|
||||||
|
break;
|
||||||
|
case EVENT_HALAA_BANNER_PROGRESS_HORDE:
|
||||||
|
SetBannerVisual(go, CAPTURE_ARTKIT_HORDE, CAPTURE_ANIM_HORDE);
|
||||||
|
sWorld.SendDefenseMessage(ZONE_ID_NAGRAND, LANG_OPVP_NA_PROGRESS_H);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// there are some events which required further DB script
|
||||||
|
return eventHandled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OutdoorPvPNA::ProcessCaptureEvent(GameObject* go, Team team)
|
||||||
|
{
|
||||||
|
BuffTeam(m_zoneOwner, SPELL_STRENGTH_HALAANI, true);
|
||||||
|
|
||||||
|
// update capture point owner
|
||||||
|
m_zoneOwner = team;
|
||||||
|
|
||||||
|
LockHalaa(go);
|
||||||
|
m_guardsLeft = MAX_NA_GUARDS;
|
||||||
|
|
||||||
|
m_isUnderSiege = false;
|
||||||
|
m_soldiersRespawnTimer = 0;
|
||||||
|
|
||||||
|
UpdateWorldState(WORLD_STATE_REMOVE);
|
||||||
|
DespawnVendors(go);
|
||||||
|
sObjectMgr.SetGraveYardLinkTeam(GRAVEYARD_ID_HALAA, GRAVEYARD_ZONE_ID_HALAA, m_zoneOwner);
|
||||||
|
|
||||||
|
if (m_zoneOwner == ALLIANCE)
|
||||||
|
{
|
||||||
|
m_zoneWorldState = WORLD_STATE_NA_GUARDS_ALLIANCE;
|
||||||
|
m_zoneMapState = WORLD_STATE_NA_HALAA_ALLIANCE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_zoneWorldState = WORLD_STATE_NA_GUARDS_HORDE;
|
||||||
|
m_zoneMapState = WORLD_STATE_NA_HALAA_HORDE;
|
||||||
|
}
|
||||||
|
|
||||||
|
HandleFactionObjects(go);
|
||||||
|
UpdateWorldState(WORLD_STATE_ADD);
|
||||||
|
|
||||||
|
SendUpdateWorldState(WORLD_STATE_NA_GUARDS_LEFT, m_guardsLeft);
|
||||||
|
|
||||||
|
BuffTeam(m_zoneOwner, SPELL_STRENGTH_HALAANI);
|
||||||
|
sWorld.SendDefenseMessage(ZONE_ID_NAGRAND, m_zoneOwner == ALLIANCE ? LANG_OPVP_NA_CAPTURE_A: LANG_OPVP_NA_CAPTURE_H);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the gameobjects spawn/despawn depending on the controller faction
|
||||||
|
void OutdoorPvPNA::HandleFactionObjects(const WorldObject* objRef)
|
||||||
|
{
|
||||||
|
if (m_zoneOwner == ALLIANCE)
|
||||||
|
{
|
||||||
|
for (uint8 i = 0; i < MAX_NA_ROOSTS; ++i)
|
||||||
|
{
|
||||||
|
RespawnGO(objRef, m_wagonsHorde[i], false);
|
||||||
|
RespawnGO(objRef, m_roostsBrokenAlliance[i], false);
|
||||||
|
RespawnGO(objRef, m_roostsAlliance[i], false);
|
||||||
|
RespawnGO(objRef, m_wagonsAlliance[i], false);
|
||||||
|
RespawnGO(objRef, m_roostsBrokenHorde[i], true);
|
||||||
|
|
||||||
|
m_roostWorldState[i] = nagrandRoostStatesHordeNeutral[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (uint8 i = 0; i < MAX_NA_ROOSTS; ++i)
|
||||||
|
{
|
||||||
|
RespawnGO(objRef, m_wagonsAlliance[i], false);
|
||||||
|
RespawnGO(objRef, m_roostsBrokenHorde[i], false);
|
||||||
|
RespawnGO(objRef, m_roostsHorde[i], false);
|
||||||
|
RespawnGO(objRef, m_wagonsHorde[i], false);
|
||||||
|
RespawnGO(objRef, m_roostsBrokenAlliance[i], true);
|
||||||
|
|
||||||
|
m_roostWorldState[i] = nagrandRoostStatesAllianceNeutral[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle vendors despawn when the city is captured by the other faction
|
||||||
|
void OutdoorPvPNA::DespawnVendors(const WorldObject* objRef)
|
||||||
|
{
|
||||||
|
// despawn all team vendors
|
||||||
|
for (GuidList::const_iterator itr = m_teamVendors.begin(); itr != m_teamVendors.end(); ++itr)
|
||||||
|
{
|
||||||
|
if (Creature* soldier = objRef->GetMap()->GetCreature(*itr))
|
||||||
|
soldier->ForcedDespawn();
|
||||||
|
}
|
||||||
|
m_teamVendors.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OutdoorPvPNA::HandleGameObjectUse(Player* player, GameObject* go)
|
||||||
|
{
|
||||||
|
if (player->GetTeam() == ALLIANCE)
|
||||||
|
{
|
||||||
|
for (uint8 i = 0; i < MAX_NA_ROOSTS; ++i)
|
||||||
|
{
|
||||||
|
if (go->GetEntry() == nagrandWagonsAlliance[i])
|
||||||
|
{
|
||||||
|
// update roost states
|
||||||
|
UpdateWyvernsWorldState(WORLD_STATE_REMOVE);
|
||||||
|
m_roostWorldState[i] = nagrandRoostStatesHordeNeutral[i];
|
||||||
|
UpdateWyvernsWorldState(WORLD_STATE_ADD);
|
||||||
|
|
||||||
|
// spawn the broken roost and despawn the other one
|
||||||
|
RespawnGO(go, m_roostsHorde[i], false);
|
||||||
|
RespawnGO(go, m_roostsBrokenHorde[i], true);
|
||||||
|
|
||||||
|
// no need to iterate the other roosts
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (go->GetEntry() == nagrandRoostsBrokenAlliance[i])
|
||||||
|
{
|
||||||
|
// update roost states
|
||||||
|
UpdateWyvernsWorldState(WORLD_STATE_REMOVE);
|
||||||
|
m_roostWorldState[i] = nagrandRoostStatesAlliance[i];
|
||||||
|
UpdateWyvernsWorldState(WORLD_STATE_ADD);
|
||||||
|
|
||||||
|
// spawn the repaired one along with the explosive wagon - the broken one despawns by self
|
||||||
|
RespawnGO(go, m_wagonsHorde[i], true);
|
||||||
|
RespawnGO(go, m_roostsAlliance[i], true);
|
||||||
|
|
||||||
|
// no need to iterate the other roosts
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (go->GetEntry() == nagrandRoostsAlliance[i])
|
||||||
|
{
|
||||||
|
// mark player as pvp
|
||||||
|
player->UpdatePvP(true, true);
|
||||||
|
player->SetFlag(PLAYER_FLAGS, PLAYER_FLAGS_IN_PVP);
|
||||||
|
|
||||||
|
// prevent despawning after go use
|
||||||
|
go->SetRespawnTime(0);
|
||||||
|
|
||||||
|
// no need to iterate the other roosts
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (player->GetTeam() == HORDE)
|
||||||
|
{
|
||||||
|
for (uint8 i = 0; i < MAX_NA_ROOSTS; ++i)
|
||||||
|
{
|
||||||
|
if (go->GetEntry() == nagrandWagonsHorde[i])
|
||||||
|
{
|
||||||
|
// update roost states
|
||||||
|
UpdateWyvernsWorldState(WORLD_STATE_REMOVE);
|
||||||
|
m_roostWorldState[i] = nagrandRoostStatesAllianceNeutral[i];
|
||||||
|
UpdateWyvernsWorldState(WORLD_STATE_ADD);
|
||||||
|
|
||||||
|
// spawn the broken roost and despawn the other one
|
||||||
|
RespawnGO(go, m_roostsAlliance[i], false);
|
||||||
|
RespawnGO(go, m_roostsBrokenAlliance[i], true);
|
||||||
|
|
||||||
|
// no need to iterate the other roosts
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (go->GetEntry() == nagrandRoostsBrokenHorde[i])
|
||||||
|
{
|
||||||
|
// update roost states
|
||||||
|
UpdateWyvernsWorldState(WORLD_STATE_REMOVE);
|
||||||
|
m_roostWorldState[i] = nagrandRoostStatesHorde[i];
|
||||||
|
UpdateWyvernsWorldState(WORLD_STATE_ADD);
|
||||||
|
|
||||||
|
// spawn the repaired one along with the explosive wagon - the broken one despawns by self
|
||||||
|
RespawnGO(go, m_wagonsAlliance[i], true);
|
||||||
|
RespawnGO(go, m_roostsHorde[i], true);
|
||||||
|
|
||||||
|
// no need to iterate the other roosts
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (go->GetEntry() == nagrandRoostsHorde[i])
|
||||||
|
{
|
||||||
|
// mark player as pvp
|
||||||
|
player->UpdatePvP(true, true);
|
||||||
|
player->SetFlag(PLAYER_FLAGS, PLAYER_FLAGS_IN_PVP);
|
||||||
|
|
||||||
|
// prevent despawning after go use
|
||||||
|
go->SetRespawnTime(0);
|
||||||
|
|
||||||
|
// no need to iterate the other roosts
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OutdoorPvPNA::Update(uint32 diff)
|
||||||
|
{
|
||||||
|
if (m_soldiersRespawnTimer)
|
||||||
|
{
|
||||||
|
if (m_soldiersRespawnTimer < diff)
|
||||||
|
{
|
||||||
|
RespawnSoldier();
|
||||||
|
|
||||||
|
// if all the guards are respawned, stop the timer, else resume the timer depending on the siege state
|
||||||
|
if (m_guardsLeft == MAX_NA_GUARDS)
|
||||||
|
m_soldiersRespawnTimer = 0;
|
||||||
|
else
|
||||||
|
m_soldiersRespawnTimer = m_isUnderSiege ? HOUR * IN_MILLISECONDS : 5 * MINUTE * IN_MILLISECONDS;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
m_soldiersRespawnTimer -= diff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle soldiers respawn on timer - this will summon a replacement for the dead soldier
|
||||||
|
void OutdoorPvPNA::RespawnSoldier()
|
||||||
|
{
|
||||||
|
for (GuidZoneMap::iterator itr = m_zonePlayers.begin(); itr != m_zonePlayers.end(); ++itr)
|
||||||
|
{
|
||||||
|
// Find player who is in main zone (Nagrand) to get correct map reference
|
||||||
|
if (!itr->second)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (Player* player = sObjectMgr.GetPlayer(itr->first))
|
||||||
|
{
|
||||||
|
// summon a soldier replacement in the order they were set in the deque. delete the element after summon
|
||||||
|
player->SummonCreature(m_zoneOwner == ALLIANCE ? NPC_ALLIANCE_HANAANI_GUARD : NPC_HORDE_HALAANI_GUARD, m_deadSoldiers.front().x, m_deadSoldiers.front().y, m_deadSoldiers.front().z, m_deadSoldiers.front().o, TEMPSUMMON_DEAD_DESPAWN, 0, true);
|
||||||
|
m_deadSoldiers.pop();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock Halaa when captured
|
||||||
|
void OutdoorPvPNA::LockHalaa(const WorldObject* objRef)
|
||||||
|
{
|
||||||
|
if (GameObject* go = objRef->GetMap()->GetGameObject(m_capturePoint))
|
||||||
|
go->SetLootState(GO_JUST_DEACTIVATED);
|
||||||
|
|
||||||
|
sOutdoorPvPMgr.SetCapturePointSlider(m_capturePoint, m_zoneOwner == ALLIANCE ? CAPTURE_SLIDER_ALLIANCE_LOCKED : CAPTURE_SLIDER_HORDE_LOCKED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlock Halaa when all the soldiers are killed
|
||||||
|
void OutdoorPvPNA::UnlockHalaa(const WorldObject* objRef)
|
||||||
|
{
|
||||||
|
if (GameObject* go = objRef->GetMap()->GetGameObject(m_capturePoint))
|
||||||
|
go->SetCapturePointSlider(m_zoneOwner == ALLIANCE ? CAPTURE_SLIDER_ALLIANCE : CAPTURE_SLIDER_HORDE);
|
||||||
|
// no banner visual update needed because it already has the correct one
|
||||||
|
else
|
||||||
|
// if grid is unloaded, resetting the slider value is enough
|
||||||
|
sOutdoorPvPMgr.SetCapturePointSlider(m_capturePoint, m_zoneOwner == ALLIANCE ? CAPTURE_SLIDER_ALLIANCE : CAPTURE_SLIDER_HORDE);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,4 +19,201 @@
|
||||||
#ifndef WORLD_PVP_NA
|
#ifndef WORLD_PVP_NA
|
||||||
#define WORLD_PVP_NA
|
#define WORLD_PVP_NA
|
||||||
|
|
||||||
|
#include "Common.h"
|
||||||
|
#include "OutdoorPvP.h"
|
||||||
|
#include "../Language.h"
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
MAX_NA_GUARDS = 15,
|
||||||
|
MAX_NA_ROOSTS = 4, // roosts for each type and team
|
||||||
|
MAX_FIRE_BOMBS = 10,
|
||||||
|
|
||||||
|
// spells
|
||||||
|
SPELL_STRENGTH_HALAANI = 33795,
|
||||||
|
SPELL_NAGRAND_TOKEN_ALLIANCE = 33005,
|
||||||
|
SPELL_NAGRAND_TOKEN_HORDE = 33004,
|
||||||
|
|
||||||
|
// npcs
|
||||||
|
// quest credit
|
||||||
|
NPC_HALAA_COMBATANT = 24867,
|
||||||
|
|
||||||
|
GRAVEYARD_ID_HALAA = 993,
|
||||||
|
GRAVEYARD_ZONE_ID_HALAA = 3518,
|
||||||
|
|
||||||
|
ITEM_ID_FIRE_BOMB = 24538,
|
||||||
|
|
||||||
|
// gameobjects
|
||||||
|
GO_HALAA_BANNER = 182210,
|
||||||
|
|
||||||
|
// spawned when horde is in control - alliance is attacking
|
||||||
|
GO_WYVERN_ROOST_ALLIANCE_SOUTH = 182267,
|
||||||
|
GO_WYVERN_ROOST_ALLIANCE_WEST = 182280,
|
||||||
|
GO_WYVERN_ROOST_ALLIANCE_NORTH = 182281,
|
||||||
|
GO_WYVERN_ROOST_ALLIANCE_EAST = 182282,
|
||||||
|
|
||||||
|
GO_BOMB_WAGON_HORDE_SOUTH = 182222,
|
||||||
|
GO_BOMB_WAGON_HORDE_WEST = 182272,
|
||||||
|
GO_BOMB_WAGON_HORDE_NORTH = 182273,
|
||||||
|
GO_BOMB_WAGON_HORDE_EAST = 182274,
|
||||||
|
|
||||||
|
GO_DESTROYED_ROOST_ALLIANCE_SOUTH = 182266,
|
||||||
|
GO_DESTROYED_ROOST_ALLIANCE_WEST = 182275,
|
||||||
|
GO_DESTROYED_ROOST_ALLIANCE_NORTH = 182276,
|
||||||
|
GO_DESTROYED_ROOST_ALLIANCE_EAST = 182277,
|
||||||
|
|
||||||
|
// spawned when alliance is in control - horde is attacking
|
||||||
|
GO_WYVERN_ROOST_HORDE_SOUTH = 182301,
|
||||||
|
GO_WYVERN_ROOST_HORDE_WEST = 182302,
|
||||||
|
GO_WYVERN_ROOST_HORDE_NORTH = 182303,
|
||||||
|
GO_WYVERN_ROOST_HORDE_EAST = 182304,
|
||||||
|
|
||||||
|
GO_BOMB_WAGON_ALLIANCE_SOUTH = 182305,
|
||||||
|
GO_BOMB_WAGON_ALLIANCE_WEST = 182306,
|
||||||
|
GO_BOMB_WAGON_ALLIANCE_NORTH = 182307,
|
||||||
|
GO_BOMB_WAGON_ALLIANCE_EAST = 182308,
|
||||||
|
|
||||||
|
GO_DESTROYED_ROOST_HORDE_SOUTH = 182297,
|
||||||
|
GO_DESTROYED_ROOST_HORDE_WEST = 182298,
|
||||||
|
GO_DESTROYED_ROOST_HORDE_NORTH = 182299,
|
||||||
|
GO_DESTROYED_ROOST_HORDE_EAST = 182300,
|
||||||
|
|
||||||
|
// npcs
|
||||||
|
// alliance
|
||||||
|
NPC_RESEARCHER_KARTOS = 18817,
|
||||||
|
NPC_QUARTERMASTER_DAVIAN = 18822,
|
||||||
|
NPC_MERCHANT_ALDRAAN = 21485,
|
||||||
|
NPC_VENDOR_CENDRII = 21487,
|
||||||
|
NPC_AMMUNITIONER_BANRO = 21488,
|
||||||
|
NPC_ALLIANCE_HANAANI_GUARD = 18256,
|
||||||
|
|
||||||
|
// horde
|
||||||
|
NPC_RESEARCHER_AMERELDINE = 18816,
|
||||||
|
NPC_QUARTERMASTER_NORELIQE = 18821,
|
||||||
|
NPC_MERCHANT_COREIEL = 21474,
|
||||||
|
NPC_VENDOR_EMBELAR = 21484,
|
||||||
|
NPC_AMMUNITIONER_TASALDAN = 21483,
|
||||||
|
NPC_HORDE_HALAANI_GUARD = 18192,
|
||||||
|
|
||||||
|
// events
|
||||||
|
EVENT_HALAA_BANNER_WIN_ALLIANCE = 11504,
|
||||||
|
EVENT_HALAA_BANNER_WIN_HORDE = 11503,
|
||||||
|
EVENT_HALAA_BANNER_CONTEST_ALLIANCE = 11559,
|
||||||
|
EVENT_HALAA_BANNER_CONTEST_HORDE = 11558,
|
||||||
|
EVENT_HALAA_BANNER_PROGRESS_ALLIANCE = 11821,
|
||||||
|
EVENT_HALAA_BANNER_PROGRESS_HORDE = 11822,
|
||||||
|
|
||||||
|
// world states
|
||||||
|
WORLD_STATE_NA_GUARDS_HORDE = 2503,
|
||||||
|
WORLD_STATE_NA_GUARDS_ALLIANCE = 2502,
|
||||||
|
WORLD_STATE_NA_GUARDS_MAX = 2493,
|
||||||
|
WORLD_STATE_NA_GUARDS_LEFT = 2491,
|
||||||
|
|
||||||
|
// map states
|
||||||
|
WORLD_STATE_NA_WYVERN_NORTH_NEUTRAL_H = 2762,
|
||||||
|
WORLD_STATE_NA_WYVERN_NORTH_NEUTRAL_A = 2662,
|
||||||
|
WORLD_STATE_NA_WYVERN_NORTH_H = 2663,
|
||||||
|
WORLD_STATE_NA_WYVERN_NORTH_A = 2664,
|
||||||
|
|
||||||
|
WORLD_STATE_NA_WYVERN_SOUTH_NEUTRAL_H = 2760,
|
||||||
|
WORLD_STATE_NA_WYVERN_SOUTH_NEUTRAL_A = 2670,
|
||||||
|
WORLD_STATE_NA_WYVERN_SOUTH_H = 2668,
|
||||||
|
WORLD_STATE_NA_WYVERN_SOUTH_A = 2669,
|
||||||
|
|
||||||
|
WORLD_STATE_NA_WYVERN_WEST_NEUTRAL_H = 2761,
|
||||||
|
WORLD_STATE_NA_WYVERN_WEST_NEUTRAL_A = 2667,
|
||||||
|
WORLD_STATE_NA_WYVERN_WEST_H = 2665,
|
||||||
|
WORLD_STATE_NA_WYVERN_WEST_A = 2666,
|
||||||
|
|
||||||
|
WORLD_STATE_NA_WYVERN_EAST_NEUTRAL_H = 2763,
|
||||||
|
WORLD_STATE_NA_WYVERN_EAST_NEUTRAL_A = 2659,
|
||||||
|
WORLD_STATE_NA_WYVERN_EAST_H = 2660,
|
||||||
|
WORLD_STATE_NA_WYVERN_EAST_A = 2661,
|
||||||
|
|
||||||
|
WORLD_STATE_NA_HALAA_NEUTRAL = 2671,
|
||||||
|
WORLD_STATE_NA_HALAA_NEUTRAL_A = 2676,
|
||||||
|
WORLD_STATE_NA_HALAA_NEUTRAL_H = 2677,
|
||||||
|
WORLD_STATE_NA_HALAA_HORDE = 2672,
|
||||||
|
WORLD_STATE_NA_HALAA_ALLIANCE = 2673,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HalaaSoldiersSpawns
|
||||||
|
{
|
||||||
|
float x, y, z, o;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint32 nagrandRoostsAlliance[MAX_NA_ROOSTS] = {GO_WYVERN_ROOST_ALLIANCE_SOUTH, GO_WYVERN_ROOST_ALLIANCE_NORTH, GO_WYVERN_ROOST_ALLIANCE_EAST, GO_WYVERN_ROOST_ALLIANCE_WEST};
|
||||||
|
static const uint32 nagrandRoostsHorde[MAX_NA_ROOSTS] = {GO_WYVERN_ROOST_HORDE_SOUTH, GO_WYVERN_ROOST_HORDE_NORTH, GO_WYVERN_ROOST_HORDE_EAST, GO_WYVERN_ROOST_HORDE_WEST};
|
||||||
|
static const uint32 nagrandRoostsBrokenAlliance[MAX_NA_ROOSTS] = {GO_DESTROYED_ROOST_ALLIANCE_SOUTH, GO_DESTROYED_ROOST_ALLIANCE_NORTH, GO_DESTROYED_ROOST_ALLIANCE_EAST, GO_DESTROYED_ROOST_ALLIANCE_WEST};
|
||||||
|
static const uint32 nagrandRoostsBrokenHorde[MAX_NA_ROOSTS] = {GO_DESTROYED_ROOST_HORDE_SOUTH, GO_DESTROYED_ROOST_HORDE_NORTH, GO_DESTROYED_ROOST_HORDE_EAST, GO_DESTROYED_ROOST_HORDE_WEST};
|
||||||
|
static const uint32 nagrandWagonsAlliance[MAX_NA_ROOSTS] = {GO_BOMB_WAGON_ALLIANCE_SOUTH, GO_BOMB_WAGON_ALLIANCE_NORTH, GO_BOMB_WAGON_ALLIANCE_EAST, GO_BOMB_WAGON_ALLIANCE_WEST};
|
||||||
|
static const uint32 nagrandWagonsHorde[MAX_NA_ROOSTS] = {GO_BOMB_WAGON_HORDE_SOUTH, GO_BOMB_WAGON_HORDE_NORTH, GO_BOMB_WAGON_HORDE_EAST, GO_BOMB_WAGON_HORDE_WEST};
|
||||||
|
|
||||||
|
static const uint32 nagrandRoostStatesAlliance[MAX_NA_ROOSTS] = {WORLD_STATE_NA_WYVERN_SOUTH_A, WORLD_STATE_NA_WYVERN_NORTH_A, WORLD_STATE_NA_WYVERN_EAST_A, WORLD_STATE_NA_WYVERN_WEST_A};
|
||||||
|
static const uint32 nagrandRoostStatesHorde[MAX_NA_ROOSTS] = {WORLD_STATE_NA_WYVERN_SOUTH_H, WORLD_STATE_NA_WYVERN_NORTH_H, WORLD_STATE_NA_WYVERN_EAST_H, WORLD_STATE_NA_WYVERN_WEST_H};
|
||||||
|
static const uint32 nagrandRoostStatesAllianceNeutral[MAX_NA_ROOSTS] = {WORLD_STATE_NA_WYVERN_SOUTH_NEUTRAL_A, WORLD_STATE_NA_WYVERN_NORTH_NEUTRAL_A, WORLD_STATE_NA_WYVERN_EAST_NEUTRAL_A, WORLD_STATE_NA_WYVERN_WEST_NEUTRAL_A};
|
||||||
|
static const uint32 nagrandRoostStatesHordeNeutral[MAX_NA_ROOSTS] = {WORLD_STATE_NA_WYVERN_SOUTH_NEUTRAL_H, WORLD_STATE_NA_WYVERN_NORTH_NEUTRAL_H, WORLD_STATE_NA_WYVERN_EAST_NEUTRAL_H, WORLD_STATE_NA_WYVERN_WEST_NEUTRAL_H};
|
||||||
|
|
||||||
|
class OutdoorPvPNA : public OutdoorPvP
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
OutdoorPvPNA();
|
||||||
|
|
||||||
|
void HandlePlayerEnterZone(Player* player, bool isMainZone) override;
|
||||||
|
void HandlePlayerLeaveZone(Player* player, bool isMainZone) override;
|
||||||
|
void FillInitialWorldStates(WorldPacket& data, uint32& count) override;
|
||||||
|
void SendRemoveWorldStates(Player* player) override;
|
||||||
|
|
||||||
|
bool HandleEvent(uint32 eventId, GameObject* go) override;
|
||||||
|
void HandleObjectiveComplete(uint32 eventId, std::list<Player*> players, Team team) override;
|
||||||
|
|
||||||
|
void HandleCreatureCreate(Creature* creature) override;
|
||||||
|
void HandleGameObjectCreate(GameObject* go) override;
|
||||||
|
void HandleCreatureDeath(Creature* creature) override;
|
||||||
|
|
||||||
|
void HandlePlayerKillInsideArea(Player* player, Unit* victim) override;
|
||||||
|
bool HandleGameObjectUse(Player* player, GameObject* go) override;
|
||||||
|
void Update(uint32 diff) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// world states handling
|
||||||
|
void UpdateWorldState(uint32 value);
|
||||||
|
void UpdateWyvernsWorldState(uint32 value);
|
||||||
|
|
||||||
|
// process capture events
|
||||||
|
void ProcessCaptureEvent(GameObject* go, Team team);
|
||||||
|
|
||||||
|
// set specific team vendors and objects after capture
|
||||||
|
void DespawnVendors(const WorldObject* objRef);
|
||||||
|
void HandleFactionObjects(const WorldObject* objRef);
|
||||||
|
|
||||||
|
// handle a specific game objects
|
||||||
|
void LockHalaa(const WorldObject* objRef);
|
||||||
|
void UnlockHalaa(const WorldObject* objRef);
|
||||||
|
|
||||||
|
// handle soldier respawn on timer
|
||||||
|
void RespawnSoldier();
|
||||||
|
|
||||||
|
Team m_zoneOwner;
|
||||||
|
uint32 m_soldiersRespawnTimer;
|
||||||
|
uint32 m_zoneWorldState;
|
||||||
|
uint32 m_zoneMapState;
|
||||||
|
uint32 m_roostWorldState[MAX_NA_ROOSTS];
|
||||||
|
uint8 m_guardsLeft;
|
||||||
|
|
||||||
|
bool m_isUnderSiege;
|
||||||
|
|
||||||
|
ObjectGuid m_capturePoint;
|
||||||
|
ObjectGuid m_roostsAlliance[MAX_NA_ROOSTS];
|
||||||
|
ObjectGuid m_roostsHorde[MAX_NA_ROOSTS];
|
||||||
|
ObjectGuid m_roostsBrokenAlliance[MAX_NA_ROOSTS];
|
||||||
|
ObjectGuid m_roostsBrokenHorde[MAX_NA_ROOSTS];
|
||||||
|
ObjectGuid m_wagonsAlliance[MAX_NA_ROOSTS];
|
||||||
|
ObjectGuid m_wagonsHorde[MAX_NA_ROOSTS];
|
||||||
|
|
||||||
|
GuidList m_teamVendors;
|
||||||
|
|
||||||
|
std::queue<HalaaSoldiersSpawns> m_deadSoldiers;
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
Loading…
Add table
Add a link
Reference in a new issue