/* * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #include "Battlefield.h" #include "BattlefieldMgr.h" #include "Battleground.h" #include "BattlegroundPackets.h" #include "CellImpl.h" #include "CreatureTextMgr.h" #include "GameTime.h" #include "GridNotifiers.h" #include "GridNotifiersImpl.h" #include "Group.h" #include "GroupMgr.h" #include "Log.h" #include "Map.h" #include "MiscPackets.h" #include "ObjectAccessor.h" #include "ObjectMgr.h" #include Battlefield::Battlefield(Map* map) { m_Timer = 0; m_IsEnabled = true; m_isActive = false; m_DefenderTeam = TEAM_NEUTRAL; m_TypeId = 0; m_BattleId = 0; m_ZoneId = 0; m_Map = map; m_MapId = map->GetId(); m_MaxPlayer = 0; m_MinPlayer = 0; m_MinLevel = 0; m_BattleTime = 0; m_NoWarBattleTime = 0; m_RestartAfterCrash = 0; m_TimeForAcceptInvite = 20; m_uiKickDontAcceptTimer = 1000; m_uiKickAfkPlayersTimer = 1000; m_StartGroupingTimer = 0; m_StartGrouping = false; } Battlefield::~Battlefield() { for (GraveyardVect::const_iterator itr = m_GraveyardList.begin(); itr != m_GraveyardList.end(); ++itr) delete *itr; } // Called when a player enters the zone void Battlefield::HandlePlayerEnterZone(Player* player, uint32 /*zone*/) { // If battle is started, // If not full of players > invite player to join the war // If full of players > announce to player that BF is full and kick him after a few second if he desn't leave if (IsWarTime()) { if (m_PlayersInWar[player->GetTeamId()].size() + m_InvitedPlayers[player->GetTeamId()].size() < m_MaxPlayer) // Vacant spaces InvitePlayerToWar(player); else // No more vacant places { /// @todo Send a packet to announce it to player m_PlayersWillBeKick[player->GetTeamId()][player->GetGUID()] = GameTime::GetGameTime() + 10; InvitePlayerToQueue(player); } } else { // If time left is < 15 minutes invite player to join queue if (m_Timer <= m_StartGroupingTimer) InvitePlayerToQueue(player); } // Add player in the list of player in zone m_players[player->GetTeamId()].insert(player->GetGUID()); OnPlayerEnterZone(player); } // Called when a player leave the zone void Battlefield::HandlePlayerLeaveZone(Player* player, uint32 /*zone*/) { if (IsWarTime()) { // If the player is participating to the battle if (m_PlayersInWar[player->GetTeamId()].find(player->GetGUID()) != m_PlayersInWar[player->GetTeamId()].end()) { m_PlayersInWar[player->GetTeamId()].erase(player->GetGUID()); if (Group* group = player->GetGroup()) // Remove the player from the raid group group->RemoveMember(player->GetGUID()); OnPlayerLeaveWar(player); } } m_InvitedPlayers[player->GetTeamId()].erase(player->GetGUID()); m_PlayersWillBeKick[player->GetTeamId()].erase(player->GetGUID()); m_players[player->GetTeamId()].erase(player->GetGUID()); SendRemoveWorldStates(player); OnPlayerLeaveZone(player); } bool Battlefield::Update(uint32 diff) { if (m_Timer <= diff) { // Battlefield ends on time if (IsWarTime()) EndBattle(true); else // Time to start a new battle! StartBattle(); } else m_Timer -= diff; // Invite players a few minutes before the battle's beginning if (!IsWarTime() && !m_StartGrouping && m_Timer <= m_StartGroupingTimer) { m_StartGrouping = true; InvitePlayersInZoneToQueue(); OnStartGrouping(); } if (IsWarTime()) { if (m_uiKickAfkPlayersTimer <= diff) { m_uiKickAfkPlayersTimer = 1000; KickAfkPlayers(); } else m_uiKickAfkPlayersTimer -= diff; // Kick players who chose not to accept invitation to the battle if (m_uiKickDontAcceptTimer <= diff) { time_t now = GameTime::GetGameTime(); for (uint8 team = 0; team < PVP_TEAMS_COUNT; ++team) for (PlayerTimerMap::iterator itr = m_InvitedPlayers[team].begin(); itr != m_InvitedPlayers[team].end(); ++itr) if (itr->second <= now) KickPlayerFromBattlefield(itr->first); InvitePlayersInZoneToWar(); for (uint8 team = 0; team < PVP_TEAMS_COUNT; ++team) for (PlayerTimerMap::iterator itr = m_PlayersWillBeKick[team].begin(); itr != m_PlayersWillBeKick[team].end(); ++itr) if (itr->second <= now) KickPlayerFromBattlefield(itr->first); m_uiKickDontAcceptTimer = 1000; } else m_uiKickDontAcceptTimer -= diff; } return false; } void Battlefield::InvitePlayersInZoneToQueue() { for (uint8 team = 0; team < PVP_TEAMS_COUNT; ++team) for (auto itr = m_players[team].begin(); itr != m_players[team].end(); ++itr) if (Player* player = ObjectAccessor::FindPlayer(*itr)) InvitePlayerToQueue(player); } void Battlefield::InvitePlayerToQueue(Player* player) { if (m_PlayersInQueue[player->GetTeamId()].count(player->GetGUID())) return; if (m_PlayersInQueue[player->GetTeamId()].size() <= m_MinPlayer || m_PlayersInQueue[GetOtherTeam(player->GetTeamId())].size() >= m_MinPlayer) PlayerAcceptInviteToQueue(player); } void Battlefield::InvitePlayersInQueueToWar() { for (uint8 team = 0; team < PVP_TEAMS_COUNT; ++team) { for (auto itr = m_PlayersInQueue[team].begin(); itr != m_PlayersInQueue[team].end(); ++itr) { if (Player* player = ObjectAccessor::FindPlayer(*itr)) { if (m_PlayersInWar[player->GetTeamId()].size() + m_InvitedPlayers[player->GetTeamId()].size() < m_MaxPlayer) InvitePlayerToWar(player); else { //Full } } } m_PlayersInQueue[team].clear(); } } void Battlefield::InvitePlayersInZoneToWar() { for (uint8 team = 0; team < PVP_TEAMS_COUNT; ++team) { for (auto itr = m_players[team].begin(); itr != m_players[team].end(); ++itr) { if (Player* player = ObjectAccessor::FindPlayer(*itr)) { if (m_PlayersInWar[player->GetTeamId()].count(player->GetGUID()) || m_InvitedPlayers[player->GetTeamId()].count(player->GetGUID())) continue; if (m_PlayersInWar[player->GetTeamId()].size() + m_InvitedPlayers[player->GetTeamId()].size() < m_MaxPlayer) InvitePlayerToWar(player); else // Battlefield is full of players m_PlayersWillBeKick[player->GetTeamId()][player->GetGUID()] = GameTime::GetGameTime() + 10; } } } } uint64 Battlefield::GetQueueId() const { return MAKE_PAIR64(m_BattleId | 0x20000, 0x1F100000); } void Battlefield::InvitePlayerToWar(Player* player) { if (!player) return; /// @todo needed ? if (player->IsInFlight()) return; if (player->InArena() || player->GetBattleground()) { m_PlayersInQueue[player->GetTeamId()].erase(player->GetGUID()); return; } // If the player does not match minimal level requirements for the battlefield, kick him if (player->GetLevel() < m_MinLevel) { if (m_PlayersWillBeKick[player->GetTeamId()].count(player->GetGUID()) == 0) m_PlayersWillBeKick[player->GetTeamId()][player->GetGUID()] = GameTime::GetGameTime() + 10; return; } // Check if player is not already in war if (m_PlayersInWar[player->GetTeamId()].count(player->GetGUID()) || m_InvitedPlayers[player->GetTeamId()].count(player->GetGUID())) return; m_PlayersWillBeKick[player->GetTeamId()].erase(player->GetGUID()); m_InvitedPlayers[player->GetTeamId()][player->GetGUID()] = GameTime::GetGameTime() + m_TimeForAcceptInvite; PlayerAcceptInviteToWar(player); } void Battlefield::InitStalker(uint32 entry, Position const& pos) { if (Creature* creature = SpawnCreature(entry, pos)) StalkerGuid = creature->GetGUID(); else TC_LOG_ERROR("bg.battlefield", "Battlefield::InitStalker: Could not spawn Stalker (Creature entry {}), zone messages will be unavailable!", entry); } void Battlefield::ProcessEvent(WorldObject* target, uint32 eventId, WorldObject* invoker) { ZoneScript::ProcessEvent(target, eventId, invoker); if (invoker) { if (GameObject* gameobject = invoker->ToGameObject()) { if (gameobject->GetGoType() == GAMEOBJECT_TYPE_CONTROL_ZONE) { if (!ControlZoneHandlers.contains(gameobject->GetEntry())) return; auto controlzone = gameobject->GetGOInfo()->controlZone; BattlefieldControlZoneHandler& handler = *ControlZoneHandlers[invoker->GetEntry()]; if (eventId == controlzone.CaptureEventAlliance) handler.HandleCaptureEventAlliance(gameobject); else if (eventId == controlzone.CaptureEventHorde) handler.HandleCaptureEventHorde(gameobject); else if (eventId == controlzone.ContestedEventAlliance) handler.HandleContestedEventAlliance(gameobject); else if (eventId == controlzone.ContestedEventHorde) handler.HandleContestedEventHorde(gameobject); else if (eventId == controlzone.NeutralEventAlliance) handler.HandleNeutralEventAlliance(gameobject); else if (eventId == controlzone.NeutralEventHorde) handler.HandleNeutralEventHorde(gameobject); else if (eventId == controlzone.ProgressEventAlliance) handler.HandleProgressEventAlliance(gameobject); else if (eventId == controlzone.ProgressEventHorde) handler.HandleProgressEventHorde(gameobject); } } } } void Battlefield::KickAfkPlayers() { for (uint8 team = 0; team < PVP_TEAMS_COUNT; ++team) for (auto itr = m_PlayersInWar[team].begin(); itr != m_PlayersInWar[team].end(); ++itr) if (Player* player = ObjectAccessor::FindPlayer(*itr)) if (player->isAFK()) KickPlayerFromBattlefield(*itr); } void Battlefield::KickPlayerFromBattlefield(ObjectGuid guid) { if (Player* player = ObjectAccessor::FindPlayer(guid)) if (player->GetZoneId() == GetZoneId()) player->TeleportTo(KickPosition); } void Battlefield::StartBattle() { if (m_isActive) return; for (uint8 team = 0; team < PVP_TEAMS_COUNT; ++team) { m_PlayersInWar[team].clear(); m_Groups[team].clear(); } m_Timer = m_BattleTime; m_isActive = true; InvitePlayersInZoneToWar(); InvitePlayersInQueueToWar(); OnBattleStart(); } void Battlefield::EndBattle(bool endByTimer) { if (!m_isActive) return; m_isActive = false; m_StartGrouping = false; if (!endByTimer) SetDefenderTeam(GetAttackerTeam()); // Reset battlefield timer m_Timer = m_NoWarBattleTime; OnBattleEnd(endByTimer); } void Battlefield::DoPlaySoundToAll(uint32 soundID) { BroadcastPacketToWar(WorldPackets::Misc::PlaySound(ObjectGuid::Empty, soundID, 0).Write()); } bool Battlefield::HasPlayer(Player* player) const { return m_players[player->GetTeamId()].find(player->GetGUID()) != m_players[player->GetTeamId()].end(); } // Called in Battlefield::InvitePlayerToQueue void Battlefield::PlayerAcceptInviteToQueue(Player* player) { // Add player in queue m_PlayersInQueue[player->GetTeamId()].insert(player->GetGUID()); } // Called in WorldSession::HandleBfExitRequest void Battlefield::AskToLeaveQueue(Player* player) { // Remove player from queue m_PlayersInQueue[player->GetTeamId()].erase(player->GetGUID()); } // Called in WorldSession::HandleHearthAndResurrect void Battlefield::PlayerAskToLeave(Player* player) { // Player leaving Wintergrasp, teleport to Dalaran. // ToDo: confirm teleport destination. player->TeleportTo(571, 5804.1499f, 624.7710f, 647.7670f, 1.6400f); } // Called in Battlefield::InvitePlayerToWar void Battlefield::PlayerAcceptInviteToWar(Player* player) { if (!IsWarTime()) return; if (AddOrSetPlayerToCorrectBfGroup(player)) { m_PlayersInWar[player->GetTeamId()].insert(player->GetGUID()); m_InvitedPlayers[player->GetTeamId()].erase(player->GetGUID()); if (player->isAFK()) player->ToggleAFK(); OnPlayerJoinWar(player); //for scripting } } void Battlefield::TeamCastSpell(TeamId team, int32 spellId) { if (spellId > 0) { for (auto itr = m_PlayersInWar[team].begin(); itr != m_PlayersInWar[team].end(); ++itr) if (Player* player = ObjectAccessor::FindPlayer(*itr)) player->CastSpell(player, uint32(spellId), true); } else { for (auto itr = m_PlayersInWar[team].begin(); itr != m_PlayersInWar[team].end(); ++itr) if (Player* player = ObjectAccessor::FindPlayer(*itr)) player->RemoveAuraFromStack(uint32(-spellId)); } } void Battlefield::BroadcastPacketToZone(WorldPacket const* data) const { for (uint8 team = 0; team < PVP_TEAMS_COUNT; ++team) for (auto itr = m_players[team].begin(); itr != m_players[team].end(); ++itr) if (Player* player = ObjectAccessor::FindPlayer(*itr)) player->SendDirectMessage(data); } void Battlefield::BroadcastPacketToQueue(WorldPacket const* data) const { for (uint8 team = 0; team < PVP_TEAMS_COUNT; ++team) for (auto itr = m_PlayersInQueue[team].begin(); itr != m_PlayersInQueue[team].end(); ++itr) if (Player* player = ObjectAccessor::FindConnectedPlayer(*itr)) player->SendDirectMessage(data); } void Battlefield::BroadcastPacketToWar(WorldPacket const* data) const { for (uint8 team = 0; team < PVP_TEAMS_COUNT; ++team) for (auto itr = m_PlayersInWar[team].begin(); itr != m_PlayersInWar[team].end(); ++itr) if (Player* player = ObjectAccessor::FindPlayer(*itr)) player->SendDirectMessage(data); } void Battlefield::SendWarning(uint8 id, WorldObject const* target /*= nullptr*/) { if (Creature* stalker = GetCreature(StalkerGuid)) sCreatureTextMgr->SendChat(stalker, id, target); } void Battlefield::RegisterZone(uint32 zoneId) { sBattlefieldMgr->AddZone(zoneId, this); } void Battlefield::HideNpc(Creature* creature) { creature->CombatStop(); creature->SetReactState(REACT_PASSIVE); creature->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE); creature->SetUninteractible(true); creature->DisappearAndDie(); creature->SetVisible(false); } void Battlefield::ShowNpc(Creature* creature, bool aggressive) { creature->SetVisible(true); creature->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE); creature->SetUninteractible(false); if (!creature->IsAlive()) creature->Respawn(true); if (aggressive) creature->SetReactState(REACT_AGGRESSIVE); else { creature->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE); creature->SetReactState(REACT_PASSIVE); } } // **************************************************** // ******************* Group System ******************* // **************************************************** Group* Battlefield::GetFreeBfRaid(TeamId TeamId) { for (auto itr = m_Groups[TeamId].begin(); itr != m_Groups[TeamId].end(); ++itr) if (Group* group = sGroupMgr->GetGroupByGUID(*itr)) if (!group->IsFull()) return group; return nullptr; } Group* Battlefield::GetGroupPlayer(ObjectGuid guid, TeamId TeamId) { for (auto itr = m_Groups[TeamId].begin(); itr != m_Groups[TeamId].end(); ++itr) if (Group* group = sGroupMgr->GetGroupByGUID(*itr)) if (group->IsMember(guid)) return group; return nullptr; } bool Battlefield::AddOrSetPlayerToCorrectBfGroup(Player* player) { if (!player->IsInWorld()) return false; if (Group* group = player->GetGroup()) group->RemoveMember(player->GetGUID()); Group* group = GetFreeBfRaid(player->GetTeamId()); if (!group) { group = new Group; group->SetBattlefieldGroup(this); group->Create(player); sGroupMgr->AddGroup(group); m_Groups[player->GetTeamId()].insert(group->GetGUID()); } else if (group->IsMember(player->GetGUID())) { uint8 subgroup = group->GetMemberGroup(player->GetGUID()); player->SetBattlegroundOrBattlefieldRaid(group, subgroup); } else group->AddMember(player); return true; } //***************End of Group System******************* //***************************************************** //***************Spirit Guide System******************* //***************************************************** //-------------------- //-Battlefield Method- //-------------------- BfGraveyard* Battlefield::GetGraveyardById(uint32 id) const { if (id < m_GraveyardList.size()) { if (BfGraveyard* graveyard = m_GraveyardList.at(id)) return graveyard; else TC_LOG_ERROR("bg.battlefield", "Battlefield::GetGraveyardById Id:{} does not exist.", id); } else TC_LOG_ERROR("bg.battlefield", "Battlefield::GetGraveyardById Id:{} could not be found.", id); return nullptr; } WorldSafeLocsEntry const* Battlefield::GetClosestGraveyard(Player* player) { BfGraveyard* closestGY = nullptr; float maxdist = -1; for (uint8 i = 0; i < m_GraveyardList.size(); i++) { if (m_GraveyardList[i]) { if (m_GraveyardList[i]->GetControlTeamId() != player->GetTeamId()) continue; float dist = m_GraveyardList[i]->GetDistance(player); if (dist < maxdist || maxdist < 0) { closestGY = m_GraveyardList[i]; maxdist = dist; } } } if (closestGY) return sObjectMgr->GetWorldSafeLoc(closestGY->GetGraveyardId()); return nullptr; } // ---------------------- // - BfGraveyard Method - // ---------------------- BfGraveyard::BfGraveyard(Battlefield* battlefield) { m_Bf = battlefield; m_GraveyardId = 0; m_ControlTeam = TEAM_NEUTRAL; } void BfGraveyard::Initialize(TeamId startControl, uint32 graveyardId) { m_ControlTeam = startControl; m_GraveyardId = graveyardId; } void BfGraveyard::SetSpirit(Creature* spirit, TeamId team) { if (!spirit) { TC_LOG_ERROR("bg.battlefield", "BfGraveyard::SetSpirit: Invalid Spirit."); return; } m_SpiritGuide[team] = spirit->GetGUID(); spirit->SetReactState(REACT_PASSIVE); } float BfGraveyard::GetDistance(Player* player) { WorldSafeLocsEntry const* safeLoc = sObjectMgr->GetWorldSafeLoc(m_GraveyardId); return player->GetDistance2d(safeLoc->Loc.GetPositionX(), safeLoc->Loc.GetPositionY()); } // For changing graveyard control void BfGraveyard::GiveControlTo(TeamId team) { // Guide switching // Note: Visiblity changes are made by phasing /*if (m_SpiritGuide[1 - team]) m_SpiritGuide[1 - team]->SetVisible(false); if (m_SpiritGuide[team]) m_SpiritGuide[team]->SetVisible(true);*/ if (Creature* spiritHealer = m_Bf->GetCreature(m_SpiritGuide[team])) spiritHealer->SummonGraveyardTeleporter(); m_ControlTeam = team; } bool BfGraveyard::HasNpc(ObjectGuid guid) { if (!m_SpiritGuide[TEAM_ALLIANCE] || !m_SpiritGuide[TEAM_HORDE]) return false; if (!m_Bf->GetCreature(m_SpiritGuide[TEAM_ALLIANCE]) || !m_Bf->GetCreature(m_SpiritGuide[TEAM_HORDE])) return false; return (m_SpiritGuide[TEAM_ALLIANCE] == guid || m_SpiritGuide[TEAM_HORDE] == guid); } // ******************************************************* // *************** End Spirit Guide system *************** // ******************************************************* // ********************** Misc *************************** // ******************************************************* Creature* Battlefield::SpawnCreature(uint32 entry, Position const& pos) { if (!sObjectMgr->GetCreatureTemplate(entry)) { TC_LOG_ERROR("bg.battlefield", "Battlefield::SpawnCreature: entry {} does not exist.", entry); return nullptr; } Creature* creature = Creature::CreateCreature(entry, m_Map, pos); if (!creature) { TC_LOG_ERROR("bg.battlefield", "Battlefield::SpawnCreature: Can't create creature entry: {}", entry); return nullptr; } creature->SetHomePosition(pos); // Set creature in world m_Map->AddToMap(creature); creature->setActive(true); creature->SetFarVisible(true); return creature; } // Method for spawning gameobject on map GameObject* Battlefield::SpawnGameObject(uint32 entry, Position const& pos, QuaternionData const& rot) { if (!sObjectMgr->GetGameObjectTemplate(entry)) { TC_LOG_ERROR("bg.battlefield", "Battlefield::SpawnGameObject: GameObject template {} not found in database! Battlefield not created!", entry); return nullptr; } // Create gameobject GameObject* go = GameObject::CreateGameObject(entry, m_Map, pos, rot, 255, GO_STATE_READY); if (!go) { TC_LOG_ERROR("bg.battlefield", "Battlefield::SpawnGameObject: Could not create gameobject template {}! Battlefield has not been created!", entry); return nullptr; } // Add to world m_Map->AddToMap(go); go->setActive(true); go->SetFarVisible(true); return go; } Creature* Battlefield::GetCreature(ObjectGuid guid) { if (!m_Map) return nullptr; return m_Map->GetCreature(guid); } GameObject* Battlefield::GetGameObject(ObjectGuid guid) { if (!m_Map) return nullptr; return m_Map->GetGameObject(guid); } // ******************************************************* // ******************* CapturePoint ********************** // ******************************************************* BattlefieldControlZoneHandler::BattlefieldControlZoneHandler(Battlefield* bf) : _battlefield(bf) { } Battlefield* BattlefieldControlZoneHandler::GetBattlefield() { return _battlefield; }