mirror of
https://github.com/azerothcore/azerothcore-wotlk.git
synced 2025-11-10 12:24:22 +08:00
3195 lines
104 KiB
C++
3195 lines
104 KiB
C++
/*
|
|
* 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 "Map.h"
|
|
#include "Battleground.h"
|
|
#include "CellImpl.h"
|
|
#include "Chat.h"
|
|
#include "DisableMgr.h"
|
|
#include "DynamicTree.h"
|
|
#include "GameTime.h"
|
|
#include "Geometry.h"
|
|
#include "GridNotifiers.h"
|
|
#include "Group.h"
|
|
#include "InstanceScript.h"
|
|
#include "IVMapMgr.h"
|
|
#include "LFGMgr.h"
|
|
#include "MapGrid.h"
|
|
#include "MapInstanced.h"
|
|
#include "Metric.h"
|
|
#include "MiscPackets.h"
|
|
#include "MMapFactory.h"
|
|
#include "Object.h"
|
|
#include "ObjectAccessor.h"
|
|
#include "ObjectMgr.h"
|
|
#include "Pet.h"
|
|
#include "ScriptMgr.h"
|
|
#include "Transport.h"
|
|
#include "VMapFactory.h"
|
|
#include "Vehicle.h"
|
|
#include "VMapMgr2.h"
|
|
#include "Weather.h"
|
|
#include "WeatherMgr.h"
|
|
|
|
#define MAP_INVALID_ZONE 0xFFFFFFFF
|
|
|
|
ZoneDynamicInfo::ZoneDynamicInfo() : MusicId(0), DefaultWeather(nullptr), WeatherId(WEATHER_STATE_FINE),
|
|
WeatherGrade(0.0f), OverrideLightId(0), LightFadeInTime(0) { }
|
|
|
|
Map::~Map()
|
|
{
|
|
// UnloadAll must be called before deleting the map
|
|
|
|
// Kill all scheduled events without executing them, since the map and its objects are being destroyed.
|
|
// This prevents events from running on invalid or deleted objects during map destruction.
|
|
Events.KillAllEvents(false);
|
|
|
|
sScriptMgr->OnDestroyMap(this);
|
|
|
|
if (!m_scriptSchedule.empty())
|
|
sScriptMgr->DecreaseScheduledScriptCount(m_scriptSchedule.size());
|
|
|
|
MMAP::MMapFactory::createOrGetMMapMgr()->unloadMapInstance(GetId(), i_InstanceId);
|
|
}
|
|
|
|
Map::Map(uint32 id, uint32 InstanceId, uint8 SpawnMode, Map* _parent) :
|
|
_mapGridManager(this), i_mapEntry(sMapStore.LookupEntry(id)), i_spawnMode(SpawnMode), i_InstanceId(InstanceId),
|
|
m_unloadTimer(0), m_VisibleDistance(DEFAULT_VISIBILITY_DISTANCE), _instanceResetPeriod(0),
|
|
_transportsUpdateIter(_transports.end()), i_scriptLock(false), _defaultLight(GetDefaultMapLight(id))
|
|
{
|
|
m_parentMap = (_parent ? _parent : this);
|
|
|
|
_zonePlayerCountMap.clear();
|
|
_updatableObjectListRecheckTimer.SetInterval(UPDATABLE_OBJECT_LIST_RECHECK_TIMER);
|
|
|
|
//lets initialize visibility distance for map
|
|
Map::InitVisibilityDistance();
|
|
|
|
_weatherUpdateTimer.SetInterval(1 * IN_MILLISECONDS);
|
|
_corpseUpdateTimer.SetInterval(20 * MINUTE * IN_MILLISECONDS);
|
|
}
|
|
|
|
// Hook called after map is created AND after added to map list
|
|
void Map::OnCreateMap()
|
|
{
|
|
// Instances load all grids by default (both base map and child maps)
|
|
if (GetInstanceId())
|
|
LoadAllGrids();
|
|
|
|
sScriptMgr->OnCreateMap(this);
|
|
}
|
|
|
|
void Map::InitVisibilityDistance()
|
|
{
|
|
//init visibility for continents
|
|
m_VisibleDistance = World::GetMaxVisibleDistanceOnContinents();
|
|
|
|
switch (GetId())
|
|
{
|
|
case MAP_EBON_HOLD: // Scarlet Enclave (DK starting zone)
|
|
m_VisibleDistance = 125.0f;
|
|
break;
|
|
case MAP_SCOTT_TEST: // (box map)
|
|
m_VisibleDistance = 200.0f;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Template specialization of utility methods
|
|
template<class T>
|
|
void Map::AddToGrid(T* obj, Cell const& cell)
|
|
{
|
|
MapGridType* grid = GetMapGrid(cell.GridX(), cell.GridY());
|
|
grid->AddGridObject<T>(cell.CellX(), cell.CellY(), obj);
|
|
|
|
obj->SetCurrentCell(cell);
|
|
}
|
|
|
|
template<>
|
|
void Map::AddToGrid(Creature* obj, Cell const& cell)
|
|
{
|
|
MapGridType* grid = GetMapGrid(cell.GridX(), cell.GridY());
|
|
grid->AddGridObject(cell.CellX(), cell.CellY(), obj);
|
|
if (obj->IsFarVisible())
|
|
grid->AddFarVisibleObject(cell.CellX(), cell.CellY(), obj);
|
|
|
|
obj->SetCurrentCell(cell);
|
|
}
|
|
|
|
template<>
|
|
void Map::AddToGrid(GameObject* obj, Cell const& cell)
|
|
{
|
|
MapGridType* grid = GetMapGrid(cell.GridX(), cell.GridY());
|
|
grid->AddGridObject(cell.CellX(), cell.CellY(), obj);
|
|
if (obj->IsFarVisible())
|
|
grid->AddFarVisibleObject(cell.CellX(), cell.CellY(), obj);
|
|
|
|
obj->SetCurrentCell(cell);
|
|
}
|
|
|
|
template<>
|
|
void Map::AddToGrid(Player* obj, Cell const& cell)
|
|
{
|
|
MapGridType* grid = GetMapGrid(cell.GridX(), cell.GridY());
|
|
grid->AddGridObject(cell.CellX(), cell.CellY(), obj);
|
|
}
|
|
|
|
template<>
|
|
void Map::AddToGrid(Corpse* obj, Cell const& cell)
|
|
{
|
|
MapGridType* grid = GetMapGrid(cell.GridX(), cell.GridY());
|
|
// Corpses are a special object type - they can be added to grid via a call to AddToMap
|
|
// or loaded through ObjectGridLoader.
|
|
// Both corpses loaded from database and these freshly generated by Player::CreateCoprse are added to _corpsesByCell
|
|
// ObjectGridLoader loads all corpses from _corpsesByCell even if they were already added to grid before it was loaded
|
|
// so we need to explicitly check it here (Map::AddToGrid is only called from Player::BuildPlayerRepop, not from ObjectGridLoader)
|
|
// to avoid failing an assertion in GridObject::AddToGrid
|
|
if (grid->IsObjectDataLoaded())
|
|
grid->AddGridObject(cell.CellX(), cell.CellY(), obj);
|
|
}
|
|
|
|
template<class T>
|
|
void Map::DeleteFromWorld(T* obj)
|
|
{
|
|
// Note: In case resurrectable corpse and pet its removed from global lists in own destructor
|
|
delete obj;
|
|
}
|
|
|
|
template<>
|
|
void Map::DeleteFromWorld(Player* player)
|
|
{
|
|
ObjectAccessor::RemoveObject(player);
|
|
|
|
RemoveUpdateObject(player); //TODO: I do not know why we need this, it should be removed in ~Object anyway
|
|
delete player;
|
|
}
|
|
|
|
void Map::EnsureGridCreated(GridCoord const& gridCoord)
|
|
{
|
|
_mapGridManager.CreateGrid(gridCoord.x_coord, gridCoord.y_coord);
|
|
}
|
|
|
|
bool Map::EnsureGridLoaded(Cell const& cell)
|
|
{
|
|
EnsureGridCreated(GridCoord(cell.GridX(), cell.GridY()));
|
|
|
|
if (_mapGridManager.LoadGrid(cell.GridX(), cell.GridY()))
|
|
{
|
|
Balance();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
MapGridType* Map::GetMapGrid(uint16 const x, uint16 const y)
|
|
{
|
|
return _mapGridManager.GetGrid(x, y);
|
|
}
|
|
|
|
bool Map::IsGridLoaded(GridCoord const& gridCoord) const
|
|
{
|
|
return _mapGridManager.IsGridLoaded(gridCoord.x_coord, gridCoord.y_coord);
|
|
}
|
|
|
|
bool Map::IsGridCreated(GridCoord const& gridCoord) const
|
|
{
|
|
return _mapGridManager.IsGridCreated(gridCoord.x_coord, gridCoord.y_coord);
|
|
}
|
|
|
|
void Map::LoadGrid(float x, float y)
|
|
{
|
|
EnsureGridLoaded(Cell(x, y));
|
|
}
|
|
|
|
void Map::LoadAllGrids()
|
|
{
|
|
for (uint32 cellX = 0; cellX < TOTAL_NUMBER_OF_CELLS_PER_MAP; cellX++)
|
|
for (uint32 cellY = 0; cellY < TOTAL_NUMBER_OF_CELLS_PER_MAP; cellY++)
|
|
LoadGrid((cellX + 0.5f - CENTER_GRID_CELL_ID) * SIZE_OF_GRID_CELL, (cellY + 0.5f - CENTER_GRID_CELL_ID) * SIZE_OF_GRID_CELL);
|
|
}
|
|
|
|
void Map::LoadGridsInRange(Position const& center, float radius)
|
|
{
|
|
if (_mapGridManager.IsGridsFullyLoaded())
|
|
return;
|
|
|
|
float const x = center.GetPositionX();
|
|
float const y = center.GetPositionY();
|
|
|
|
CellCoord cellCoord(Acore::ComputeCellCoord(x, y));
|
|
if (!cellCoord.IsCoordValid())
|
|
return;
|
|
|
|
if (radius > SIZE_OF_GRIDS)
|
|
radius = SIZE_OF_GRIDS;
|
|
|
|
CellArea area = Cell::CalculateCellArea(x, y, radius);
|
|
if (!area)
|
|
return;
|
|
|
|
for (uint32 x = area.low_bound.x_coord; x <= area.high_bound.x_coord; ++x)
|
|
{
|
|
for (uint32 y = area.low_bound.y_coord; y <= area.high_bound.y_coord; ++y)
|
|
{
|
|
CellCoord cellCoord(x, y);
|
|
Cell cell(cellCoord);
|
|
EnsureGridLoaded(cell);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Map::AddPlayerToMap(Player* player)
|
|
{
|
|
CellCoord cellCoord = Acore::ComputeCellCoord(player->GetPositionX(), player->GetPositionY());
|
|
if (!cellCoord.IsCoordValid())
|
|
{
|
|
LOG_ERROR("maps", "Map::Add: Player ({}) has invalid coordinates X:{} Y:{} grid cell [{}:{}]",
|
|
player->GetGUID().ToString(), player->GetPositionX(), player->GetPositionY(), cellCoord.x_coord, cellCoord.y_coord);
|
|
return false;
|
|
}
|
|
|
|
Cell cell(cellCoord);
|
|
LoadGridsInRange(*player, MAX_VISIBILITY_DISTANCE);
|
|
AddToGrid(player, cell);
|
|
|
|
// Check if we are adding to correct map
|
|
ASSERT (player->GetMap() == this);
|
|
player->SetMap(this);
|
|
player->AddToWorld();
|
|
|
|
SendInitTransports(player);
|
|
SendInitSelf(player);
|
|
|
|
player->UpdateObjectVisibility(false);
|
|
|
|
if (player->IsAlive())
|
|
ConvertCorpseToBones(player->GetGUID());
|
|
|
|
sScriptMgr->OnPlayerEnterMap(this, player);
|
|
return true;
|
|
}
|
|
|
|
template<class T>
|
|
void Map::InitializeObject(T* /*obj*/)
|
|
{
|
|
}
|
|
|
|
template<>
|
|
void Map::InitializeObject(Creature* /*obj*/)
|
|
{
|
|
//obj->_moveState = MAP_OBJECT_CELL_MOVE_NONE;
|
|
}
|
|
|
|
template<>
|
|
void Map::InitializeObject(GameObject* /*obj*/)
|
|
{
|
|
//obj->_moveState = MAP_OBJECT_CELL_MOVE_NONE;
|
|
}
|
|
|
|
template<class T>
|
|
bool Map::AddToMap(T* obj, bool checkTransport)
|
|
{
|
|
//TODO: Needs clean up. An object should not be added to map twice.
|
|
if (obj->IsInWorld())
|
|
{
|
|
ASSERT(obj->IsInGrid());
|
|
obj->UpdateObjectVisibilityOnCreate();
|
|
return true;
|
|
}
|
|
|
|
CellCoord cellCoord = Acore::ComputeCellCoord(obj->GetPositionX(), obj->GetPositionY());
|
|
//It will create many problems (including crashes) if an object is not added to grid after creation
|
|
//The correct way to fix it is to make AddToMap return false and delete the object if it is not added to grid
|
|
//But now AddToMap is used in too many places, I will just see how many ASSERT failures it will cause
|
|
ASSERT(cellCoord.IsCoordValid());
|
|
if (!cellCoord.IsCoordValid())
|
|
{
|
|
LOG_ERROR("maps", "Map::AddToMap: Object {} has invalid coordinates X:{} Y:{} grid cell [{}:{}]",
|
|
obj->GetGUID().ToString(), obj->GetPositionX(), obj->GetPositionY(), cellCoord.x_coord, cellCoord.y_coord);
|
|
return false; //Should delete object
|
|
}
|
|
|
|
Cell cell(cellCoord);
|
|
if (obj->isActiveObject())
|
|
EnsureGridLoaded(cell);
|
|
else
|
|
EnsureGridCreated(GridCoord(cell.GridX(), cell.GridY()));
|
|
|
|
AddToGrid(obj, cell);
|
|
|
|
//Must already be set before AddToMap. Usually during obj->Create.
|
|
//obj->SetMap(this);
|
|
obj->AddToWorld();
|
|
|
|
if (checkTransport)
|
|
if (!(obj->IsGameObject() && obj->ToGameObject()->IsTransport())) // dont add transport to transport ;d
|
|
if (Transport* transport = GetTransportForPos(obj->GetPhaseMask(), obj->GetPositionX(), obj->GetPositionY(), obj->GetPositionZ(), obj))
|
|
transport->AddPassenger(obj, true);
|
|
|
|
InitializeObject(obj);
|
|
|
|
//something, such as vehicle, needs to be update immediately
|
|
//also, trigger needs to cast spell, if not update, cannot see visual
|
|
obj->UpdateObjectVisibility(true);
|
|
|
|
// Xinef: little hack for vehicles, accessories have to be added after visibility update so they wont fall off the vehicle, moved from Creature::AIM_Initialize
|
|
// Initialize vehicle, this is done only for summoned npcs, DB creatures are handled by grid loaders
|
|
if (obj->IsCreature())
|
|
if (Vehicle* vehicle = obj->ToCreature()->GetVehicleKit())
|
|
vehicle->Reset();
|
|
return true;
|
|
}
|
|
|
|
template<>
|
|
bool Map::AddToMap(Transport* obj, bool /*checkTransport*/)
|
|
{
|
|
//TODO: Needs clean up. An object should not be added to map twice.
|
|
if (obj->IsInWorld())
|
|
return true;
|
|
|
|
CellCoord cellCoord = Acore::ComputeCellCoord(obj->GetPositionX(), obj->GetPositionY());
|
|
if (!cellCoord.IsCoordValid())
|
|
{
|
|
LOG_ERROR("maps", "Map::Add: Object {} has invalid coordinates X:{} Y:{} grid cell [{}:{}]",
|
|
obj->GetGUID().ToString(), obj->GetPositionX(), obj->GetPositionY(), cellCoord.x_coord, cellCoord.y_coord);
|
|
return false; //Should delete object
|
|
}
|
|
|
|
Cell cell(cellCoord);
|
|
EnsureGridLoaded(cell);
|
|
|
|
obj->AddToWorld();
|
|
|
|
_transports.insert(obj);
|
|
|
|
// Broadcast creation to players
|
|
for (Map::PlayerList::const_iterator itr = GetPlayers().begin(); itr != GetPlayers().end(); ++itr)
|
|
{
|
|
if (itr->GetSource()->GetTransport() != obj)
|
|
{
|
|
UpdateData data;
|
|
obj->BuildCreateUpdateBlockForPlayer(&data, itr->GetSource());
|
|
WorldPacket packet;
|
|
data.BuildPacket(packet);
|
|
itr->GetSource()->SendDirectMessage(&packet);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Map::MarkNearbyCellsOf(WorldObject* obj)
|
|
{
|
|
// Check for valid position
|
|
if (!obj->IsPositionValid())
|
|
return;
|
|
|
|
// Update mobs/objects in ALL visible cells around object!
|
|
CellArea area = Cell::CalculateCellArea(obj->GetPositionX(), obj->GetPositionY(), obj->GetGridActivationRange());
|
|
for (uint32 x = area.low_bound.x_coord; x <= area.high_bound.x_coord; ++x)
|
|
{
|
|
for (uint32 y = area.low_bound.y_coord; y <= area.high_bound.y_coord; ++y)
|
|
{
|
|
// marked cells are those that have been visited
|
|
uint32 cell_id = (y * TOTAL_NUMBER_OF_CELLS_PER_MAP) + x;
|
|
markCell(cell_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Map::UpdatePlayerZoneStats(uint32 oldZone, uint32 newZone)
|
|
{
|
|
// Nothing to do if no change
|
|
if (oldZone == newZone)
|
|
return;
|
|
|
|
if (oldZone != MAP_INVALID_ZONE)
|
|
{
|
|
uint32& oldZoneCount = _zonePlayerCountMap[oldZone];
|
|
if (oldZoneCount)
|
|
--oldZoneCount;
|
|
}
|
|
|
|
if (newZone != MAP_INVALID_ZONE)
|
|
++_zonePlayerCountMap[newZone];
|
|
}
|
|
|
|
void Map::Update(const uint32 t_diff, const uint32 s_diff, bool /*thread*/)
|
|
{
|
|
if (t_diff)
|
|
_dynamicTree.update(t_diff);
|
|
|
|
// Update world sessions and players
|
|
for (m_mapRefIter = m_mapRefMgr.begin(); m_mapRefIter != m_mapRefMgr.end(); ++m_mapRefIter)
|
|
{
|
|
Player* player = m_mapRefIter->GetSource();
|
|
if (player && player->IsInWorld())
|
|
{
|
|
// Update session
|
|
WorldSession* session = player->GetSession();
|
|
MapSessionFilter updater(session);
|
|
session->Update(s_diff, updater);
|
|
|
|
// update players at tick
|
|
if (!t_diff)
|
|
player->Update(s_diff);
|
|
}
|
|
}
|
|
|
|
Events.Update(t_diff);
|
|
|
|
if (!t_diff)
|
|
{
|
|
HandleDelayedVisibility();
|
|
return;
|
|
}
|
|
|
|
_updatableObjectListRecheckTimer.Update(t_diff);
|
|
resetMarkedCells();
|
|
|
|
// Update players
|
|
for (m_mapRefIter = m_mapRefMgr.begin(); m_mapRefIter != m_mapRefMgr.end(); ++m_mapRefIter)
|
|
{
|
|
Player* player = m_mapRefIter->GetSource();
|
|
|
|
if (!player || !player->IsInWorld())
|
|
continue;
|
|
|
|
player->Update(s_diff);
|
|
|
|
if (_updatableObjectListRecheckTimer.Passed())
|
|
{
|
|
MarkNearbyCellsOf(player);
|
|
|
|
// If player is using far sight, update viewpoint
|
|
if (WorldObject* viewPoint = player->GetViewpoint())
|
|
{
|
|
if (Creature* viewCreature = viewPoint->ToCreature())
|
|
MarkNearbyCellsOf(viewCreature);
|
|
else if (DynamicObject* viewObject = viewPoint->ToDynObject())
|
|
MarkNearbyCellsOf(viewObject);
|
|
}
|
|
}
|
|
}
|
|
|
|
UpdateNonPlayerObjects(t_diff);
|
|
|
|
SendObjectUpdates();
|
|
|
|
///- Process necessary scripts
|
|
if (!m_scriptSchedule.empty())
|
|
{
|
|
i_scriptLock = true;
|
|
ScriptsProcess();
|
|
i_scriptLock = false;
|
|
}
|
|
|
|
MoveAllCreaturesInMoveList();
|
|
MoveAllGameObjectsInMoveList();
|
|
MoveAllDynamicObjectsInMoveList();
|
|
|
|
HandleDelayedVisibility();
|
|
|
|
UpdateWeather(t_diff);
|
|
UpdateExpiredCorpses(t_diff);
|
|
|
|
sScriptMgr->OnMapUpdate(this, t_diff);
|
|
|
|
METRIC_VALUE("map_creatures", uint64(GetObjectsStore().Size<Creature>()),
|
|
METRIC_TAG("map_id", std::to_string(GetId())),
|
|
METRIC_TAG("map_instanceid", std::to_string(GetInstanceId())));
|
|
|
|
METRIC_VALUE("map_gameobjects", uint64(GetObjectsStore().Size<GameObject>()),
|
|
METRIC_TAG("map_id", std::to_string(GetId())),
|
|
METRIC_TAG("map_instanceid", std::to_string(GetInstanceId())));
|
|
}
|
|
|
|
void Map::UpdateNonPlayerObjects(uint32 const diff)
|
|
{
|
|
for (WorldObject* obj : _pendingAddUpdatableObjectList)
|
|
_AddObjectToUpdateList(obj);
|
|
_pendingAddUpdatableObjectList.clear();
|
|
|
|
if (_updatableObjectListRecheckTimer.Passed())
|
|
{
|
|
for (uint32 i = 0; i < _updatableObjectList.size();)
|
|
{
|
|
WorldObject* obj = _updatableObjectList[i];
|
|
if (!obj->IsInWorld())
|
|
{
|
|
++i;
|
|
continue;
|
|
}
|
|
|
|
obj->Update(diff);
|
|
|
|
if (!obj->IsUpdateNeeded())
|
|
{
|
|
_RemoveObjectFromUpdateList(obj);
|
|
// Intentional no iteration here, obj is swapped with last element in
|
|
// _updatableObjectList so next loop will update that object at the same index
|
|
}
|
|
else
|
|
++i;
|
|
}
|
|
_updatableObjectListRecheckTimer.Reset();
|
|
}
|
|
else
|
|
{
|
|
for (uint32 i = 0; i < _updatableObjectList.size(); ++i)
|
|
{
|
|
WorldObject* obj = _updatableObjectList[i];
|
|
if (!obj->IsInWorld())
|
|
continue;
|
|
|
|
obj->Update(diff);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Map::AddObjectToPendingUpdateList(WorldObject* obj)
|
|
{
|
|
if (!obj->CanBeAddedToMapUpdateList())
|
|
return;
|
|
|
|
UpdatableMapObject* mapUpdatableObject = dynamic_cast<UpdatableMapObject*>(obj);
|
|
if (mapUpdatableObject->GetUpdateState() != UpdatableMapObject::UpdateState::NotUpdating)
|
|
return;
|
|
|
|
_pendingAddUpdatableObjectList.insert(obj);
|
|
mapUpdatableObject->SetUpdateState(UpdatableMapObject::UpdateState::PendingAdd);
|
|
}
|
|
|
|
// Internal use only
|
|
void Map::_AddObjectToUpdateList(WorldObject* obj)
|
|
{
|
|
UpdatableMapObject* mapUpdatableObject = dynamic_cast<UpdatableMapObject*>(obj);
|
|
ASSERT(mapUpdatableObject && mapUpdatableObject->GetUpdateState() == UpdatableMapObject::UpdateState::PendingAdd);
|
|
|
|
mapUpdatableObject->SetUpdateState(UpdatableMapObject::UpdateState::Updating);
|
|
mapUpdatableObject->SetMapUpdateListOffset(_updatableObjectList.size());
|
|
_updatableObjectList.push_back(obj);
|
|
}
|
|
|
|
// Internal use only
|
|
void Map::_RemoveObjectFromUpdateList(WorldObject* obj)
|
|
{
|
|
UpdatableMapObject* mapUpdatableObject = dynamic_cast<UpdatableMapObject*>(obj);
|
|
ASSERT(mapUpdatableObject && mapUpdatableObject->GetUpdateState() == UpdatableMapObject::UpdateState::Updating);
|
|
|
|
if (obj != _updatableObjectList.back())
|
|
{
|
|
dynamic_cast<UpdatableMapObject*>(_updatableObjectList.back())->SetMapUpdateListOffset(mapUpdatableObject->GetMapUpdateListOffset());
|
|
std::swap(_updatableObjectList[mapUpdatableObject->GetMapUpdateListOffset()], _updatableObjectList.back());
|
|
}
|
|
|
|
_updatableObjectList.pop_back();
|
|
mapUpdatableObject->SetUpdateState(UpdatableMapObject::UpdateState::NotUpdating);
|
|
}
|
|
|
|
void Map::RemoveObjectFromMapUpdateList(WorldObject* obj)
|
|
{
|
|
if (!obj->CanBeAddedToMapUpdateList())
|
|
return;
|
|
|
|
UpdatableMapObject* mapUpdatableObject = dynamic_cast<UpdatableMapObject*>(obj);
|
|
if (mapUpdatableObject->GetUpdateState() == UpdatableMapObject::UpdateState::PendingAdd)
|
|
_pendingAddUpdatableObjectList.erase(obj);
|
|
else if (mapUpdatableObject->GetUpdateState() == UpdatableMapObject::UpdateState::Updating)
|
|
_RemoveObjectFromUpdateList(obj);
|
|
}
|
|
|
|
// Used in VisibilityDistanceType::Large and VisibilityDistanceType::Gigantic
|
|
void Map::AddWorldObjectToFarVisibleMap(WorldObject* obj)
|
|
{
|
|
if (Creature* creature = obj->ToCreature())
|
|
{
|
|
if (!creature->IsInGrid())
|
|
return;
|
|
|
|
Cell curr_cell = creature->GetCurrentCell();
|
|
MapGridType* grid = GetMapGrid(curr_cell.GridX(), curr_cell.GridY());
|
|
grid->AddFarVisibleObject(curr_cell.CellX(), curr_cell.CellY(), creature);
|
|
}
|
|
else if (GameObject* go = obj->ToGameObject())
|
|
{
|
|
if (!go->IsInGrid())
|
|
return;
|
|
|
|
Cell curr_cell = go->GetCurrentCell();
|
|
MapGridType* grid = GetMapGrid(curr_cell.GridX(), curr_cell.GridY());
|
|
grid->AddFarVisibleObject(curr_cell.CellX(), curr_cell.CellY(), go);
|
|
}
|
|
}
|
|
|
|
void Map::RemoveWorldObjectFromFarVisibleMap(WorldObject* obj)
|
|
{
|
|
if (Creature* creature = obj->ToCreature())
|
|
{
|
|
Cell curr_cell = creature->GetCurrentCell();
|
|
MapGridType* grid = GetMapGrid(curr_cell.GridX(), curr_cell.GridY());
|
|
grid->RemoveFarVisibleObject(curr_cell.CellX(), curr_cell.CellY(), creature);
|
|
}
|
|
else if (GameObject* go = obj->ToGameObject())
|
|
{
|
|
Cell curr_cell = go->GetCurrentCell();
|
|
MapGridType* grid = GetMapGrid(curr_cell.GridX(), curr_cell.GridY());
|
|
grid->RemoveFarVisibleObject(curr_cell.CellX(), curr_cell.CellY(), go);
|
|
}
|
|
}
|
|
|
|
// Used in VisibilityDistanceType::Infinite
|
|
void Map::AddWorldObjectToZoneWideVisibleMap(uint32 zoneId, WorldObject* obj)
|
|
{
|
|
_zoneWideVisibleWorldObjectsMap[zoneId].insert(obj);
|
|
}
|
|
|
|
void Map::RemoveWorldObjectFromZoneWideVisibleMap(uint32 zoneId, WorldObject* obj)
|
|
{
|
|
ZoneWideVisibleWorldObjectsMap::iterator itr = _zoneWideVisibleWorldObjectsMap.find(zoneId);
|
|
if (itr == _zoneWideVisibleWorldObjectsMap.end())
|
|
return;
|
|
|
|
itr->second.erase(obj);
|
|
}
|
|
|
|
ZoneWideVisibleWorldObjectsSet const* Map::GetZoneWideVisibleWorldObjectsForZone(uint32 zoneId) const
|
|
{
|
|
ZoneWideVisibleWorldObjectsMap::const_iterator itr = _zoneWideVisibleWorldObjectsMap.find(zoneId);
|
|
if (itr == _zoneWideVisibleWorldObjectsMap.end())
|
|
return nullptr;
|
|
|
|
return &itr->second;
|
|
}
|
|
|
|
void Map::HandleDelayedVisibility()
|
|
{
|
|
if (i_objectsForDelayedVisibility.empty())
|
|
return;
|
|
for (std::unordered_set<Unit*>::iterator itr = i_objectsForDelayedVisibility.begin(); itr != i_objectsForDelayedVisibility.end(); ++itr)
|
|
(*itr)->ExecuteDelayedUnitRelocationEvent();
|
|
i_objectsForDelayedVisibility.clear();
|
|
}
|
|
|
|
struct ResetNotifier
|
|
{
|
|
template<class T>inline void resetNotify(GridRefMgr<T>& m)
|
|
{
|
|
for (typename GridRefMgr<T>::iterator iter = m.begin(); iter != m.end(); ++iter)
|
|
iter->GetSource()->ResetAllNotifies();
|
|
}
|
|
template<class T> void Visit(GridRefMgr<T>&) {}
|
|
void Visit(CreatureMapType& m) { resetNotify<Creature>(m);}
|
|
void Visit(PlayerMapType& m) { resetNotify<Player>(m);}
|
|
};
|
|
|
|
void Map::RemovePlayerFromMap(Player* player, bool remove)
|
|
{
|
|
UpdatePlayerZoneStats(player->GetZoneId(), MAP_INVALID_ZONE);
|
|
|
|
player->getHostileRefMgr().deleteReferences(true); // pussywizard: multithreading crashfix
|
|
|
|
player->RemoveFromWorld();
|
|
SendRemoveTransports(player);
|
|
|
|
if (player->IsInGrid())
|
|
player->RemoveFromGrid();
|
|
else
|
|
ASSERT(remove); //maybe deleted in logoutplayer when player is not in a map
|
|
|
|
sScriptMgr->OnPlayerLeaveMap(this, player);
|
|
if (remove)
|
|
{
|
|
DeleteFromWorld(player);
|
|
}
|
|
}
|
|
|
|
void Map::AfterPlayerUnlinkFromMap()
|
|
{
|
|
}
|
|
|
|
template<class T>
|
|
void Map::RemoveFromMap(T* obj, bool remove)
|
|
{
|
|
obj->RemoveFromWorld();
|
|
|
|
obj->RemoveFromGrid();
|
|
|
|
obj->ResetMap();
|
|
|
|
if (remove)
|
|
{
|
|
RemoveObjectFromMapUpdateList(obj);
|
|
DeleteFromWorld(obj);
|
|
}
|
|
}
|
|
|
|
template<>
|
|
void Map::RemoveFromMap(Transport* obj, bool remove)
|
|
{
|
|
obj->RemoveFromWorld();
|
|
|
|
Map::PlayerList const& players = GetPlayers();
|
|
if (!players.IsEmpty())
|
|
{
|
|
UpdateData data;
|
|
obj->BuildOutOfRangeUpdateBlock(&data);
|
|
WorldPacket packet;
|
|
data.BuildPacket(packet);
|
|
for (Map::PlayerList::const_iterator itr = players.begin(); itr != players.end(); ++itr)
|
|
if (itr->GetSource()->GetTransport() != obj)
|
|
itr->GetSource()->SendDirectMessage(&packet);
|
|
}
|
|
|
|
if (_transportsUpdateIter != _transports.end())
|
|
{
|
|
TransportsContainer::iterator itr = _transports.find(obj);
|
|
if (itr == _transports.end())
|
|
return;
|
|
if (itr == _transportsUpdateIter)
|
|
++_transportsUpdateIter;
|
|
_transports.erase(itr);
|
|
}
|
|
else
|
|
_transports.erase(obj);
|
|
|
|
obj->ResetMap();
|
|
|
|
RemoveObjectFromMapUpdateList(obj);
|
|
|
|
if (remove)
|
|
{
|
|
// if option set then object already saved at this moment
|
|
if (!sWorld->getBoolConfig(CONFIG_SAVE_RESPAWN_TIME_IMMEDIATELY))
|
|
obj->SaveRespawnTime();
|
|
DeleteFromWorld(obj);
|
|
}
|
|
}
|
|
|
|
void Map::PlayerRelocation(Player* player, float x, float y, float z, float o)
|
|
{
|
|
Cell old_cell(player->GetPositionX(), player->GetPositionY());
|
|
Cell new_cell(x, y);
|
|
|
|
if (old_cell.DiffGrid(new_cell) || old_cell.DiffCell(new_cell))
|
|
{
|
|
player->RemoveFromGrid();
|
|
|
|
if (old_cell.DiffGrid(new_cell))
|
|
EnsureGridLoaded(new_cell);
|
|
|
|
AddToGrid(player, new_cell);
|
|
}
|
|
|
|
player->Relocate(x, y, z, o);
|
|
if (player->IsVehicle())
|
|
player->GetVehicleKit()->RelocatePassengers();
|
|
player->UpdatePositionData();
|
|
player->UpdateObjectVisibility(false);
|
|
}
|
|
|
|
void Map::CreatureRelocation(Creature* creature, float x, float y, float z, float o)
|
|
{
|
|
Cell old_cell = creature->GetCurrentCell();
|
|
Cell new_cell(x, y);
|
|
|
|
if (old_cell.DiffGrid(new_cell) || old_cell.DiffCell(new_cell))
|
|
{
|
|
if (old_cell.DiffGrid(new_cell))
|
|
EnsureGridLoaded(new_cell);
|
|
|
|
AddCreatureToMoveList(creature);
|
|
}
|
|
else
|
|
RemoveCreatureFromMoveList(creature);
|
|
|
|
creature->Relocate(x, y, z, o);
|
|
if (creature->IsVehicle())
|
|
creature->GetVehicleKit()->RelocatePassengers();
|
|
creature->UpdatePositionData();
|
|
creature->UpdateObjectVisibility(false);
|
|
}
|
|
|
|
void Map::GameObjectRelocation(GameObject* go, float x, float y, float z, float o)
|
|
{
|
|
Cell old_cell = go->GetCurrentCell();
|
|
Cell new_cell(x, y);
|
|
|
|
if (old_cell.DiffGrid(new_cell) || old_cell.DiffCell(new_cell))
|
|
{
|
|
if (old_cell.DiffGrid(new_cell))
|
|
EnsureGridLoaded(new_cell);
|
|
|
|
AddGameObjectToMoveList(go);
|
|
}
|
|
else
|
|
RemoveGameObjectFromMoveList(go);
|
|
|
|
go->Relocate(x, y, z, o);
|
|
go->UpdateModelPosition();
|
|
go->SetPositionDataUpdate();
|
|
go->UpdateObjectVisibility(false);
|
|
}
|
|
|
|
void Map::DynamicObjectRelocation(DynamicObject* dynObj, float x, float y, float z, float o)
|
|
{
|
|
Cell old_cell = dynObj->GetCurrentCell();
|
|
Cell new_cell(x, y);
|
|
|
|
if (old_cell.DiffGrid(new_cell) || old_cell.DiffCell(new_cell))
|
|
{
|
|
if (old_cell.DiffGrid(new_cell))
|
|
EnsureGridLoaded(new_cell);
|
|
|
|
AddDynamicObjectToMoveList(dynObj);
|
|
}
|
|
else
|
|
RemoveDynamicObjectFromMoveList(dynObj);
|
|
|
|
dynObj->Relocate(x, y, z, o);
|
|
dynObj->SetPositionDataUpdate();
|
|
dynObj->UpdateObjectVisibility(false);
|
|
}
|
|
|
|
void Map::AddCreatureToMoveList(Creature* c)
|
|
{
|
|
if (c->_moveState == MAP_OBJECT_CELL_MOVE_NONE)
|
|
_creaturesToMove.push_back(c);
|
|
c->_moveState = MAP_OBJECT_CELL_MOVE_ACTIVE;
|
|
}
|
|
|
|
void Map::RemoveCreatureFromMoveList(Creature* c)
|
|
{
|
|
if (c->_moveState == MAP_OBJECT_CELL_MOVE_ACTIVE)
|
|
c->_moveState = MAP_OBJECT_CELL_MOVE_INACTIVE;
|
|
}
|
|
|
|
void Map::AddGameObjectToMoveList(GameObject* go)
|
|
{
|
|
if (go->_moveState == MAP_OBJECT_CELL_MOVE_NONE)
|
|
_gameObjectsToMove.push_back(go);
|
|
go->_moveState = MAP_OBJECT_CELL_MOVE_ACTIVE;
|
|
}
|
|
|
|
void Map::RemoveGameObjectFromMoveList(GameObject* go)
|
|
{
|
|
if (go->_moveState == MAP_OBJECT_CELL_MOVE_ACTIVE)
|
|
go->_moveState = MAP_OBJECT_CELL_MOVE_INACTIVE;
|
|
}
|
|
|
|
void Map::AddDynamicObjectToMoveList(DynamicObject* dynObj)
|
|
{
|
|
if (dynObj->_moveState == MAP_OBJECT_CELL_MOVE_NONE)
|
|
_dynamicObjectsToMove.push_back(dynObj);
|
|
dynObj->_moveState = MAP_OBJECT_CELL_MOVE_ACTIVE;
|
|
}
|
|
|
|
void Map::RemoveDynamicObjectFromMoveList(DynamicObject* dynObj)
|
|
{
|
|
if (dynObj->_moveState == MAP_OBJECT_CELL_MOVE_ACTIVE)
|
|
dynObj->_moveState = MAP_OBJECT_CELL_MOVE_INACTIVE;
|
|
}
|
|
|
|
void Map::MoveAllCreaturesInMoveList()
|
|
{
|
|
for (std::vector<Creature*>::iterator itr = _creaturesToMove.begin(); itr != _creaturesToMove.end(); ++itr)
|
|
{
|
|
Creature* c = *itr;
|
|
if (c->FindMap() != this)
|
|
continue;
|
|
|
|
if (c->_moveState != MAP_OBJECT_CELL_MOVE_ACTIVE)
|
|
{
|
|
c->_moveState = MAP_OBJECT_CELL_MOVE_NONE;
|
|
continue;
|
|
}
|
|
|
|
c->_moveState = MAP_OBJECT_CELL_MOVE_NONE;
|
|
if (!c->IsInWorld())
|
|
continue;
|
|
|
|
Cell const& old_cell = c->GetCurrentCell();
|
|
Cell new_cell(c->GetPositionX(), c->GetPositionY());
|
|
if (c->IsFarVisible())
|
|
{
|
|
// Removes via GetCurrentCell, added back in AddToGrid
|
|
RemoveWorldObjectFromFarVisibleMap(c);
|
|
}
|
|
|
|
c->RemoveFromGrid();
|
|
if (old_cell.DiffGrid(new_cell))
|
|
EnsureGridLoaded(new_cell);
|
|
AddToGrid(c, new_cell);
|
|
}
|
|
_creaturesToMove.clear();
|
|
}
|
|
|
|
void Map::MoveAllGameObjectsInMoveList()
|
|
{
|
|
for (std::vector<GameObject*>::iterator itr = _gameObjectsToMove.begin(); itr != _gameObjectsToMove.end(); ++itr)
|
|
{
|
|
GameObject* go = *itr;
|
|
if (go->FindMap() != this)
|
|
continue;
|
|
|
|
if (go->_moveState != MAP_OBJECT_CELL_MOVE_ACTIVE)
|
|
{
|
|
go->_moveState = MAP_OBJECT_CELL_MOVE_NONE;
|
|
continue;
|
|
}
|
|
|
|
go->_moveState = MAP_OBJECT_CELL_MOVE_NONE;
|
|
if (!go->IsInWorld())
|
|
continue;
|
|
|
|
Cell const& old_cell = go->GetCurrentCell();
|
|
Cell new_cell(go->GetPositionX(), go->GetPositionY());
|
|
|
|
if (go->IsFarVisible())
|
|
{
|
|
// Removes via GetCurrentCell, added back in AddToGrid
|
|
RemoveWorldObjectFromFarVisibleMap(go);
|
|
}
|
|
|
|
go->RemoveFromGrid();
|
|
if (old_cell.DiffGrid(new_cell))
|
|
EnsureGridLoaded(new_cell);
|
|
AddToGrid(go, new_cell);
|
|
}
|
|
_gameObjectsToMove.clear();
|
|
}
|
|
|
|
void Map::MoveAllDynamicObjectsInMoveList()
|
|
{
|
|
for (std::vector<DynamicObject*>::iterator itr = _dynamicObjectsToMove.begin(); itr != _dynamicObjectsToMove.end(); ++itr)
|
|
{
|
|
DynamicObject* dynObj = *itr;
|
|
if (dynObj->FindMap() != this)
|
|
continue;
|
|
|
|
if (dynObj->_moveState != MAP_OBJECT_CELL_MOVE_ACTIVE)
|
|
{
|
|
dynObj->_moveState = MAP_OBJECT_CELL_MOVE_NONE;
|
|
continue;
|
|
}
|
|
|
|
dynObj->_moveState = MAP_OBJECT_CELL_MOVE_NONE;
|
|
if (!dynObj->IsInWorld())
|
|
continue;
|
|
|
|
Cell const& old_cell = dynObj->GetCurrentCell();
|
|
Cell new_cell(dynObj->GetPositionX(), dynObj->GetPositionY());
|
|
|
|
dynObj->RemoveFromGrid();
|
|
if (old_cell.DiffGrid(new_cell))
|
|
EnsureGridLoaded(new_cell);
|
|
AddToGrid(dynObj, new_cell);
|
|
}
|
|
_dynamicObjectsToMove.clear();
|
|
}
|
|
|
|
bool Map::UnloadGrid(MapGridType& grid)
|
|
{
|
|
_mapGridManager.UnloadGrid(grid.GetX(), grid.GetY());
|
|
|
|
ASSERT(i_objectsToRemove.empty());
|
|
LOG_DEBUG("maps", "Unloading grid[{}, {}] for map {} finished", grid.GetX(), grid.GetY(), GetId());
|
|
return true;
|
|
}
|
|
|
|
void Map::RemoveAllPlayers()
|
|
{
|
|
if (HavePlayers())
|
|
{
|
|
for (MapRefMgr::iterator itr = m_mapRefMgr.begin(); itr != m_mapRefMgr.end(); ++itr)
|
|
{
|
|
Player* player = itr->GetSource();
|
|
if (!player->IsBeingTeleportedFar())
|
|
{
|
|
// this is happening for bg
|
|
LOG_ERROR("maps", "Map::UnloadAll: player {} is still in map {} during unload, this should not happen!", player->GetName(), GetId());
|
|
player->TeleportTo(player->m_homebindMapId, player->m_homebindX, player->m_homebindY, player->m_homebindZ, player->GetOrientation());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Map::UnloadAll()
|
|
{
|
|
// clear all delayed moves, useless anyway do this moves before map unload.
|
|
_creaturesToMove.clear();
|
|
_gameObjectsToMove.clear();
|
|
|
|
for (GridRefMgr<MapGridType>::iterator i = GridRefMgr<MapGridType>::begin(); i != GridRefMgr<MapGridType>::end();)
|
|
{
|
|
MapGridType& grid(*i->GetSource());
|
|
++i;
|
|
UnloadGrid(grid); // deletes the grid and removes it from the GridRefMgr
|
|
}
|
|
|
|
// pussywizard: crashfix, some npc can be left on transport (not a default passenger)
|
|
if (!AllTransportsEmpty())
|
|
AllTransportsRemovePassengers();
|
|
|
|
for (TransportsContainer::iterator itr = _transports.begin(); itr != _transports.end();)
|
|
{
|
|
Transport* transport = *itr;
|
|
++itr;
|
|
|
|
RemoveFromMap<Transport>(transport, true);
|
|
}
|
|
|
|
_transports.clear();
|
|
|
|
for (auto& cellCorpsePair : _corpsesByGrid)
|
|
{
|
|
for (Corpse* corpse : cellCorpsePair.second)
|
|
{
|
|
corpse->RemoveFromWorld();
|
|
corpse->ResetMap();
|
|
delete corpse;
|
|
}
|
|
}
|
|
|
|
_corpsesByGrid.clear();
|
|
_corpsesByPlayer.clear();
|
|
_corpseBones.clear();
|
|
}
|
|
|
|
std::shared_ptr<GridTerrainData> Map::GetGridTerrainDataSharedPtr(GridCoord const& gridCoord)
|
|
{
|
|
// ensure GridMap is created
|
|
EnsureGridCreated(gridCoord);
|
|
return _mapGridManager.GetGrid(gridCoord.x_coord, gridCoord.y_coord)->GetTerrainDataSharedPtr();
|
|
}
|
|
|
|
GridTerrainData* Map::GetGridTerrainData(GridCoord const& gridCoord)
|
|
{
|
|
if (!MapGridManager::IsValidGridCoordinates(gridCoord.x_coord, gridCoord.y_coord))
|
|
return nullptr;
|
|
|
|
// ensure GridMap is created
|
|
EnsureGridCreated(gridCoord);
|
|
return _mapGridManager.GetGrid(gridCoord.x_coord, gridCoord.y_coord)->GetTerrainData();
|
|
}
|
|
|
|
GridTerrainData* Map::GetGridTerrainData(float x, float y)
|
|
{
|
|
GridCoord const gridCoord = Acore::ComputeGridCoord(x, y);
|
|
return GetGridTerrainData(gridCoord);
|
|
}
|
|
|
|
float Map::GetWaterOrGroundLevel(uint32 phasemask, float x, float y, float z, float* ground /*= nullptr*/, bool /*swim = false*/, float collisionHeight) const
|
|
{
|
|
// we need ground level (including grid height version) for proper return water level in point
|
|
float ground_z = GetHeight(phasemask, x, y, z + Z_OFFSET_FIND_HEIGHT, true, 50.0f);
|
|
if (ground)
|
|
*ground = ground_z;
|
|
|
|
LiquidData const& liquidData = const_cast<Map*>(this)->GetLiquidData(phasemask, x, y, ground_z, collisionHeight, MAP_ALL_LIQUIDS);
|
|
switch (liquidData.Status)
|
|
{
|
|
case LIQUID_MAP_ABOVE_WATER:
|
|
return std::max<float>(liquidData.Level, ground_z);
|
|
case LIQUID_MAP_NO_WATER:
|
|
return ground_z;
|
|
default:
|
|
return liquidData.Level;
|
|
}
|
|
|
|
return VMAP_INVALID_HEIGHT_VALUE;
|
|
}
|
|
|
|
Transport* Map::GetTransportForPos(uint32 phase, float x, float y, float z, WorldObject* worldobject)
|
|
{
|
|
G3D::Vector3 v(x, y, z + 2.0f);
|
|
G3D::Ray r(v, G3D::Vector3(0, 0, -1));
|
|
for (TransportsContainer::const_iterator itr = _transports.begin(); itr != _transports.end(); ++itr)
|
|
if ((*itr)->IsInWorld() && (*itr)->GetExactDistSq(x, y, z) < 75.0f * 75.0f && (*itr)->m_model)
|
|
{
|
|
float dist = 30.0f;
|
|
bool hit = (*itr)->m_model->intersectRay(r, dist, false, phase, VMAP::ModelIgnoreFlags::Nothing);
|
|
if (hit)
|
|
return *itr;
|
|
}
|
|
|
|
if (worldobject)
|
|
if (GameObject* staticTrans = worldobject->FindNearestGameObjectOfType(GAMEOBJECT_TYPE_TRANSPORT, 75.0f))
|
|
if (staticTrans->m_model)
|
|
{
|
|
float dist = 10.0f;
|
|
bool hit = staticTrans->m_model->intersectRay(r, dist, false, phase, VMAP::ModelIgnoreFlags::Nothing);
|
|
if (hit)
|
|
if (GetHeight(phase, x, y, z, true, 30.0f) < (v.z - dist + 1.0f))
|
|
return staticTrans->ToTransport();
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
float Map::GetHeight(float x, float y, float z, bool checkVMap /*= true*/, float maxSearchDist /*= DEFAULT_HEIGHT_SEARCH*/) const
|
|
{
|
|
// find raw .map surface under Z coordinates
|
|
float mapHeight = VMAP_INVALID_HEIGHT_VALUE;
|
|
float gridHeight = GetGridHeight(x, y);
|
|
if (G3D::fuzzyGe(z, gridHeight - GROUND_HEIGHT_TOLERANCE))
|
|
mapHeight = gridHeight;
|
|
|
|
float vmapHeight = VMAP_INVALID_HEIGHT_VALUE;
|
|
if (checkVMap)
|
|
{
|
|
VMAP::IVMapMgr* vmgr = VMAP::VMapFactory::createOrGetVMapMgr();
|
|
vmapHeight = vmgr->getHeight(GetId(), x, y, z, maxSearchDist); // look from a bit higher pos to find the floor
|
|
}
|
|
|
|
// mapHeight set for any above raw ground Z or <= INVALID_HEIGHT
|
|
// vmapheight set for any under Z value or <= INVALID_HEIGHT
|
|
if (vmapHeight > INVALID_HEIGHT)
|
|
{
|
|
if (mapHeight > INVALID_HEIGHT)
|
|
{
|
|
// we have mapheight and vmapheight and must select more appropriate
|
|
|
|
// we are already under the surface or vmap height above map heigt
|
|
// or if the distance of the vmap height is less the land height distance
|
|
if (vmapHeight > mapHeight || std::fabs(mapHeight - z) > std::fabs(vmapHeight - z))
|
|
return vmapHeight;
|
|
else
|
|
return mapHeight; // better use .map surface height
|
|
}
|
|
else
|
|
return vmapHeight; // we have only vmapHeight (if have)
|
|
}
|
|
|
|
return mapHeight; // explicitly use map data
|
|
}
|
|
|
|
float Map::GetGridHeight(float x, float y) const
|
|
{
|
|
if (GridTerrainData* gmap = const_cast<Map*>(this)->GetGridTerrainData(x, y))
|
|
return gmap->getHeight(x, y);
|
|
|
|
return INVALID_HEIGHT;
|
|
}
|
|
|
|
float Map::GetMinHeight(float x, float y) const
|
|
{
|
|
if (GridTerrainData const* grid = const_cast<Map*>(this)->GetGridTerrainData(x, y))
|
|
return grid->getMinHeight(x, y);
|
|
|
|
return MIN_HEIGHT;
|
|
}
|
|
|
|
static inline bool IsInWMOInterior(uint32 mogpFlags)
|
|
{
|
|
return (mogpFlags & 0x2000) != 0;
|
|
}
|
|
|
|
bool Map::GetAreaInfo(uint32 phaseMask, float x, float y, float z, uint32& flags, int32& adtId, int32& rootId, int32& groupId) const
|
|
{
|
|
float vmap_z = z;
|
|
float dynamic_z = z;
|
|
float check_z = z;
|
|
VMAP::IVMapMgr* vmgr = VMAP::VMapFactory::createOrGetVMapMgr();
|
|
uint32 vflags;
|
|
int32 vadtId;
|
|
int32 vrootId;
|
|
int32 vgroupId;
|
|
uint32 dflags;
|
|
int32 dadtId;
|
|
int32 drootId;
|
|
int32 dgroupId;
|
|
|
|
bool hasVmapAreaInfo = vmgr->GetAreaInfo(GetId(), x, y, vmap_z, vflags, vadtId, vrootId, vgroupId);
|
|
bool hasDynamicAreaInfo = _dynamicTree.GetAreaInfo(x, y, dynamic_z, phaseMask, dflags, dadtId, drootId, dgroupId);
|
|
auto useVmap = [&]() { check_z = vmap_z; flags = vflags; adtId = vadtId; rootId = vrootId; groupId = vgroupId; };
|
|
auto useDyn = [&]() { check_z = dynamic_z; flags = dflags; adtId = dadtId; rootId = drootId; groupId = dgroupId; };
|
|
|
|
if (hasVmapAreaInfo)
|
|
{
|
|
if (hasDynamicAreaInfo && dynamic_z > vmap_z)
|
|
useDyn();
|
|
else
|
|
useVmap();
|
|
}
|
|
else if (hasDynamicAreaInfo)
|
|
{
|
|
useDyn();
|
|
}
|
|
|
|
if (hasVmapAreaInfo || hasDynamicAreaInfo)
|
|
{
|
|
// check if there's terrain between player height and object height
|
|
if (GridTerrainData* gmap = const_cast<Map*>(this)->GetGridTerrainData(x, y))
|
|
{
|
|
float mapHeight = gmap->getHeight(x, y);
|
|
// z + 2.0f condition taken from GetHeight(), not sure if it's such a great choice...
|
|
if (z + 2.0f > mapHeight && mapHeight > check_z)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
uint32 Map::GetAreaId(uint32 phaseMask, float x, float y, float z) const
|
|
{
|
|
uint32 mogpFlags;
|
|
int32 adtId, rootId, groupId;
|
|
float vmapZ = z;
|
|
bool hasVmapArea = GetAreaInfo(phaseMask, x, y, vmapZ, mogpFlags, adtId, rootId, groupId);
|
|
|
|
uint32 gridAreaId = 0;
|
|
float gridMapHeight = INVALID_HEIGHT;
|
|
if (GridTerrainData* gmap = const_cast<Map*>(this)->GetGridTerrainData(x, y))
|
|
{
|
|
gridAreaId = gmap->getArea(x, y);
|
|
gridMapHeight = gmap->getHeight(x, y);
|
|
}
|
|
|
|
uint16 areaId = 0;
|
|
|
|
// floor is the height we are closer to (but only if above)
|
|
if (hasVmapArea && G3D::fuzzyGe(z, vmapZ - GROUND_HEIGHT_TOLERANCE) && (G3D::fuzzyLt(z, gridMapHeight - GROUND_HEIGHT_TOLERANCE) || vmapZ > gridMapHeight))
|
|
{
|
|
// wmo found
|
|
if (WMOAreaTableEntry const* wmoEntry = GetWMOAreaTableEntryByTripple(rootId, adtId, groupId))
|
|
areaId = wmoEntry->areaId;
|
|
|
|
if (!areaId)
|
|
areaId = gridAreaId;
|
|
}
|
|
else
|
|
areaId = gridAreaId;
|
|
|
|
if (!areaId)
|
|
areaId = i_mapEntry->linked_zone;
|
|
|
|
return areaId;
|
|
}
|
|
|
|
uint32 Map::GetZoneId(uint32 phaseMask, float x, float y, float z) const
|
|
{
|
|
uint32 areaId = GetAreaId(phaseMask, x, y, z);
|
|
if (AreaTableEntry const* area = sAreaTableStore.LookupEntry(areaId))
|
|
if (area->zone)
|
|
return area->zone;
|
|
|
|
return areaId;
|
|
}
|
|
|
|
void Map::GetZoneAndAreaId(uint32 phaseMask, uint32& zoneid, uint32& areaid, float x, float y, float z) const
|
|
{
|
|
areaid = zoneid = GetAreaId(phaseMask, x, y, z);
|
|
if (AreaTableEntry const* area = sAreaTableStore.LookupEntry(areaid))
|
|
if (area->zone)
|
|
zoneid = area->zone;
|
|
}
|
|
|
|
LiquidData const Map::GetLiquidData(uint32 phaseMask, float x, float y, float z, float collisionHeight, uint8 ReqLiquidType)
|
|
{
|
|
LiquidData liquidData;
|
|
|
|
VMAP::IVMapMgr* vmgr = VMAP::VMapFactory::createOrGetVMapMgr();
|
|
float liquid_level = INVALID_HEIGHT;
|
|
float ground_level = INVALID_HEIGHT;
|
|
uint32 liquid_type = 0;
|
|
uint32 mogpFlags = 0;
|
|
bool useGridLiquid = true;
|
|
if (vmgr->GetLiquidLevel(GetId(), x, y, z, ReqLiquidType, liquid_level, ground_level, liquid_type, mogpFlags))
|
|
{
|
|
useGridLiquid = !IsInWMOInterior(mogpFlags);
|
|
LOG_DEBUG("maps", "GetLiquidStatus(): vmap liquid level: {} ground: {} type: {}", liquid_level, ground_level, liquid_type);
|
|
// Check water level and ground level
|
|
if (liquid_level > ground_level && G3D::fuzzyGe(z, ground_level - GROUND_HEIGHT_TOLERANCE))
|
|
{
|
|
// hardcoded in client like this
|
|
if (GetId() == MAP_OUTLAND && liquid_type == 2)
|
|
liquid_type = 15;
|
|
|
|
uint32 liquidFlagType = 0;
|
|
if (LiquidTypeEntry const* liq = sLiquidTypeStore.LookupEntry(liquid_type))
|
|
liquidFlagType = liq->Type;
|
|
|
|
if (liquid_type && liquid_type < 21)
|
|
{
|
|
if (AreaTableEntry const* area = sAreaTableStore.LookupEntry(GetAreaId(phaseMask, x, y, z)))
|
|
{
|
|
uint32 overrideLiquid = area->LiquidTypeOverride[liquidFlagType];
|
|
if (!overrideLiquid && area->zone)
|
|
{
|
|
area = sAreaTableStore.LookupEntry(area->zone);
|
|
if (area)
|
|
overrideLiquid = area->LiquidTypeOverride[liquidFlagType];
|
|
}
|
|
|
|
if (LiquidTypeEntry const* liq = sLiquidTypeStore.LookupEntry(overrideLiquid))
|
|
{
|
|
liquid_type = overrideLiquid;
|
|
liquidFlagType = liq->Type;
|
|
}
|
|
}
|
|
}
|
|
|
|
liquidData.Level = liquid_level;
|
|
liquidData.DepthLevel = ground_level;
|
|
liquidData.Entry = liquid_type;
|
|
liquidData.Flags = 1 << liquidFlagType;
|
|
}
|
|
|
|
float delta = liquid_level - z;
|
|
|
|
// Get position delta
|
|
if (delta > collisionHeight)
|
|
liquidData.Status = LIQUID_MAP_UNDER_WATER;
|
|
else if (delta > 0.0f)
|
|
liquidData.Status = LIQUID_MAP_IN_WATER;
|
|
else if (delta > -0.1f)
|
|
liquidData.Status = LIQUID_MAP_WATER_WALK;
|
|
else
|
|
liquidData.Status = LIQUID_MAP_ABOVE_WATER;
|
|
}
|
|
|
|
if (useGridLiquid)
|
|
{
|
|
if (GridTerrainData* gmap = const_cast<Map*>(this)->GetGridTerrainData(x, y))
|
|
{
|
|
LiquidData const& map_data = gmap->GetLiquidData(x, y, z, collisionHeight, ReqLiquidType);
|
|
// Not override LIQUID_MAP_ABOVE_WATER with LIQUID_MAP_NO_WATER:
|
|
if (map_data.Status != LIQUID_MAP_NO_WATER && (map_data.Level > ground_level))
|
|
{
|
|
// hardcoded in client like this
|
|
uint32 liquidEntry = map_data.Entry;
|
|
if (GetId() == MAP_OUTLAND && liquidEntry == 2)
|
|
liquidEntry = 15;
|
|
|
|
liquidData = map_data;
|
|
liquidData.Entry = liquidEntry;
|
|
}
|
|
}
|
|
}
|
|
|
|
return liquidData;
|
|
}
|
|
|
|
void Map::GetFullTerrainStatusForPosition(uint32 /*phaseMask*/, float x, float y, float z, float collisionHeight, PositionFullTerrainStatus& data, uint8 reqLiquidType)
|
|
{
|
|
GridTerrainData* gmap = GetGridTerrainData(x, y);
|
|
|
|
VMAP::IVMapMgr* vmgr = VMAP::VMapFactory::createOrGetVMapMgr();
|
|
VMAP::AreaAndLiquidData vmapData;
|
|
// VMAP::AreaAndLiquidData dynData;
|
|
VMAP::AreaAndLiquidData* wmoData = nullptr;
|
|
vmgr->GetAreaAndLiquidData(GetId(), x, y, z, reqLiquidType, vmapData);
|
|
// _dynamicTree.GetAreaAndLiquidData(x, y, z, phaseMask, reqLiquidType, dynData);
|
|
|
|
uint32 gridAreaId = 0;
|
|
float gridMapHeight = INVALID_HEIGHT;
|
|
if (gmap)
|
|
{
|
|
gridAreaId = gmap->getArea(x, y);
|
|
gridMapHeight = gmap->getHeight(x, y);
|
|
}
|
|
|
|
bool useGridLiquid = true;
|
|
|
|
// floor is the height we are closer to (but only if above)
|
|
data.floorZ = VMAP_INVALID_HEIGHT;
|
|
if (gridMapHeight > INVALID_HEIGHT && G3D::fuzzyGe(z, gridMapHeight - GROUND_HEIGHT_TOLERANCE))
|
|
data.floorZ = gridMapHeight;
|
|
|
|
if (vmapData.floorZ > VMAP_INVALID_HEIGHT && G3D::fuzzyGe(z, vmapData.floorZ - GROUND_HEIGHT_TOLERANCE) &&
|
|
(G3D::fuzzyLt(z, gridMapHeight - GROUND_HEIGHT_TOLERANCE) || vmapData.floorZ > gridMapHeight))
|
|
{
|
|
data.floorZ = vmapData.floorZ;
|
|
wmoData = &vmapData;
|
|
}
|
|
|
|
// NOTE: Objects will not detect a case when a wmo providing area/liquid despawns from under them
|
|
// but this is fine as these kind of objects are not meant to be spawned and despawned a lot
|
|
// example: Lich King platform
|
|
/*
|
|
if (dynData.floorZ > VMAP_INVALID_HEIGHT && G3D::fuzzyGe(z, dynData.floorZ - GROUND_HEIGHT_TOLERANCE) &&
|
|
(G3D::fuzzyLt(z, gridMapHeight - GROUND_HEIGHT_TOLERANCE) || dynData.floorZ > gridMapHeight) &&
|
|
(G3D::fuzzyLt(z, vmapData.floorZ - GROUND_HEIGHT_TOLERANCE) || dynData.floorZ > vmapData.floorZ))
|
|
{
|
|
data.floorZ = dynData.floorZ;
|
|
wmoData = &dynData;
|
|
}
|
|
*/
|
|
|
|
if (wmoData)
|
|
{
|
|
if (wmoData->areaInfo)
|
|
{
|
|
// wmo found
|
|
WMOAreaTableEntry const* wmoEntry = GetWMOAreaTableEntryByTripple(wmoData->areaInfo->rootId, wmoData->areaInfo->adtId, wmoData->areaInfo->groupId);
|
|
data.outdoors = (wmoData->areaInfo->mogpFlags & 0x8) != 0;
|
|
if (wmoEntry)
|
|
{
|
|
data.areaId = wmoEntry->areaId;
|
|
if (wmoEntry->Flags & 4)
|
|
data.outdoors = true;
|
|
else if (wmoEntry->Flags & 2)
|
|
data.outdoors = false;
|
|
}
|
|
|
|
if (!data.areaId)
|
|
data.areaId = gridAreaId;
|
|
|
|
useGridLiquid = !IsInWMOInterior(wmoData->areaInfo->mogpFlags);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
data.outdoors = true;
|
|
data.areaId = gridAreaId;
|
|
if (AreaTableEntry const* areaEntry = sAreaTableStore.LookupEntry(data.areaId))
|
|
data.outdoors = (areaEntry->flags & (AREA_FLAG_INSIDE | AREA_FLAG_OUTSIDE)) != AREA_FLAG_INSIDE;
|
|
}
|
|
|
|
if (!data.areaId)
|
|
data.areaId = i_mapEntry->linked_zone;
|
|
|
|
AreaTableEntry const* areaEntry = sAreaTableStore.LookupEntry(data.areaId);
|
|
|
|
// liquid processing
|
|
if (wmoData && wmoData->liquidInfo && wmoData->liquidInfo->level > wmoData->floorZ)
|
|
{
|
|
uint32 liquidType = wmoData->liquidInfo->type;
|
|
if (GetId() == MAP_OUTLAND && liquidType == 2) // gotta love blizzard hacks
|
|
liquidType = 15;
|
|
|
|
uint32 liquidFlagType = 0;
|
|
if (LiquidTypeEntry const* liquidData = sLiquidTypeStore.LookupEntry(liquidType))
|
|
liquidFlagType = liquidData->Type;
|
|
|
|
if (liquidType && liquidType < 21 && areaEntry)
|
|
{
|
|
uint32 overrideLiquid = areaEntry->LiquidTypeOverride[liquidFlagType];
|
|
if (!overrideLiquid && areaEntry->zone)
|
|
{
|
|
AreaTableEntry const* zoneEntry = sAreaTableStore.LookupEntry(areaEntry->zone);
|
|
if (zoneEntry)
|
|
overrideLiquid = zoneEntry->LiquidTypeOverride[liquidFlagType];
|
|
}
|
|
|
|
if (LiquidTypeEntry const* overrideData = sLiquidTypeStore.LookupEntry(overrideLiquid))
|
|
{
|
|
liquidType = overrideLiquid;
|
|
liquidFlagType = overrideData->Type;
|
|
}
|
|
}
|
|
|
|
data.liquidInfo.Level = wmoData->liquidInfo->level;
|
|
data.liquidInfo.DepthLevel = wmoData->floorZ;
|
|
data.liquidInfo.Entry = liquidType;
|
|
data.liquidInfo.Flags = 1 << liquidFlagType;
|
|
|
|
// Get position delta
|
|
float delta = wmoData->liquidInfo->level - z;
|
|
|
|
if (delta > collisionHeight)
|
|
data.liquidInfo.Status = LIQUID_MAP_UNDER_WATER;
|
|
else if (delta > 0.0f)
|
|
data.liquidInfo.Status = LIQUID_MAP_IN_WATER;
|
|
else if (delta > -0.1f)
|
|
data.liquidInfo.Status = LIQUID_MAP_WATER_WALK;
|
|
else
|
|
data.liquidInfo.Status = LIQUID_MAP_ABOVE_WATER;
|
|
}
|
|
|
|
// look up liquid data from grid map
|
|
if (gmap && useGridLiquid)
|
|
{
|
|
LiquidData const& gridLiquidData = gmap->GetLiquidData(x, y, z, collisionHeight, reqLiquidType);
|
|
if (gridLiquidData.Status != LIQUID_MAP_NO_WATER && (!wmoData || gridLiquidData.Level > wmoData->floorZ))
|
|
{
|
|
uint32 liquidEntry = gridLiquidData.Entry;
|
|
if (GetId() == MAP_OUTLAND && liquidEntry == 2)
|
|
liquidEntry = 15;
|
|
|
|
data.liquidInfo = gridLiquidData;
|
|
data.liquidInfo.Entry = liquidEntry;
|
|
}
|
|
}
|
|
}
|
|
|
|
float Map::GetWaterLevel(float x, float y) const
|
|
{
|
|
if (GridTerrainData* gmap = const_cast<Map*>(this)->GetGridTerrainData(x, y))
|
|
return gmap->getLiquidLevel(x, y);
|
|
|
|
return INVALID_HEIGHT;
|
|
}
|
|
|
|
bool Map::isInLineOfSight(float x1, float y1, float z1, float x2, float y2, float z2, uint32 phasemask, LineOfSightChecks checks, VMAP::ModelIgnoreFlags ignoreFlags) const
|
|
{
|
|
if (!sWorld->getBoolConfig(CONFIG_VMAP_BLIZZLIKE_PVP_LOS))
|
|
{
|
|
if (IsBattlegroundOrArena())
|
|
{
|
|
ignoreFlags = VMAP::ModelIgnoreFlags::Nothing;
|
|
}
|
|
}
|
|
|
|
if (!sWorld->getBoolConfig(CONFIG_VMAP_BLIZZLIKE_LOS_OPEN_WORLD))
|
|
{
|
|
if (IsWorldMap())
|
|
{
|
|
ignoreFlags = VMAP::ModelIgnoreFlags::Nothing;
|
|
}
|
|
}
|
|
|
|
if ((checks & LINEOFSIGHT_CHECK_VMAP) && !VMAP::VMapFactory::createOrGetVMapMgr()->isInLineOfSight(GetId(), x1, y1, z1, x2, y2, z2, ignoreFlags))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (sWorld->getBoolConfig(CONFIG_CHECK_GOBJECT_LOS) && (checks & LINEOFSIGHT_CHECK_GOBJECT_ALL))
|
|
{
|
|
ignoreFlags = VMAP::ModelIgnoreFlags::Nothing;
|
|
if (!(checks & LINEOFSIGHT_CHECK_GOBJECT_M2))
|
|
{
|
|
ignoreFlags = VMAP::ModelIgnoreFlags::M2;
|
|
}
|
|
|
|
if (!_dynamicTree.isInLineOfSight(x1, y1, z1, x2, y2, z2, phasemask, ignoreFlags))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Map::GetObjectHitPos(uint32 phasemask, float x1, float y1, float z1, float x2, float y2, float z2, float& rx, float& ry, float& rz, float modifyDist)
|
|
{
|
|
G3D::Vector3 startPos(x1, y1, z1);
|
|
G3D::Vector3 dstPos(x2, y2, z2);
|
|
|
|
G3D::Vector3 resultPos;
|
|
bool result = _dynamicTree.GetObjectHitPos(phasemask, startPos, dstPos, resultPos, modifyDist);
|
|
|
|
rx = resultPos.x;
|
|
ry = resultPos.y;
|
|
rz = resultPos.z;
|
|
return result;
|
|
}
|
|
|
|
float Map::GetHeight(uint32 phasemask, float x, float y, float z, bool vmap/*=true*/, float maxSearchDist /*= DEFAULT_HEIGHT_SEARCH*/) const
|
|
{
|
|
float h1, h2;
|
|
h1 = GetHeight(x, y, z, vmap, maxSearchDist);
|
|
h2 = _dynamicTree.getHeight(x, y, z, maxSearchDist, phasemask);
|
|
return std::max<float>(h1, h2);
|
|
}
|
|
|
|
bool Map::IsInWater(uint32 phaseMask, float x, float y, float pZ, float collisionHeight) const
|
|
{
|
|
LiquidData const& liquidData = const_cast<Map*>(this)->GetLiquidData(phaseMask, x, y, pZ, collisionHeight, MAP_ALL_LIQUIDS);
|
|
return (liquidData.Status & MAP_LIQUID_STATUS_SWIMMING) != 0;
|
|
}
|
|
|
|
bool Map::IsUnderWater(uint32 phaseMask, float x, float y, float z, float collisionHeight) const
|
|
{
|
|
LiquidData const& liquidData = const_cast<Map*>(this)->GetLiquidData(phaseMask, x, y, z, collisionHeight, MAP_LIQUID_TYPE_WATER | MAP_LIQUID_TYPE_OCEAN);
|
|
return liquidData.Status == LIQUID_MAP_UNDER_WATER;
|
|
}
|
|
|
|
bool Map::HasEnoughWater(WorldObject const* searcher, float x, float y, float z) const
|
|
{
|
|
LiquidData const& liquidData = const_cast<Map*>(this)->GetLiquidData(
|
|
searcher->GetPhaseMask(), x, y, z, searcher->GetCollisionHeight(), MAP_ALL_LIQUIDS);
|
|
|
|
if ((liquidData.Status & MAP_LIQUID_STATUS_SWIMMING) == 0)
|
|
return false;
|
|
|
|
float minHeightInWater = searcher->GetMinHeightInWater();
|
|
return liquidData.Level > INVALID_HEIGHT &&
|
|
liquidData.Level > liquidData.DepthLevel &&
|
|
liquidData.Level - liquidData.DepthLevel >= minHeightInWater;
|
|
}
|
|
|
|
char const* Map::GetMapName() const
|
|
{
|
|
return i_mapEntry ? i_mapEntry->name[sWorld->GetDefaultDbcLocale()] : "UNNAMEDMAP\x0";
|
|
}
|
|
|
|
void Map::SendInitSelf(Player* player)
|
|
{
|
|
LOG_DEBUG("maps", "Creating player data for himself {}", player->GetGUID().ToString());
|
|
|
|
WorldPacket packet;
|
|
UpdateData data;
|
|
|
|
// attach to player data current transport data
|
|
if (Transport* transport = player->GetTransport())
|
|
transport->BuildCreateUpdateBlockForPlayer(&data, player);
|
|
|
|
// build data for self presence in world at own client (one time for map)
|
|
player->BuildCreateUpdateBlockForPlayer(&data, player);
|
|
|
|
// build and send self update packet before sending to player his own auras
|
|
data.BuildPacket(packet);
|
|
player->SendDirectMessage(&packet);
|
|
|
|
// send to player his own auras (this is needed here for timely initialization of some fields on client)
|
|
player->GetAurasForTarget(player, true);
|
|
|
|
// clean buffers for further work
|
|
packet.clear();
|
|
data.Clear();
|
|
|
|
// build other passengers at transport also (they always visible and marked as visible and will not send at visibility update at add to map
|
|
if (Transport* transport = player->GetTransport())
|
|
for (Transport::PassengerSet::const_iterator itr = transport->GetPassengers().begin(); itr != transport->GetPassengers().end(); ++itr)
|
|
if (player != (*itr) && player->HaveAtClient(*itr))
|
|
(*itr)->BuildCreateUpdateBlockForPlayer(&data, player);
|
|
|
|
data.BuildPacket(packet);
|
|
player->SendDirectMessage(&packet);
|
|
}
|
|
|
|
void Map::UpdateExpiredCorpses(uint32 const diff)
|
|
{
|
|
_corpseUpdateTimer.Update(diff);
|
|
if (!_corpseUpdateTimer.Passed())
|
|
return;
|
|
|
|
RemoveOldCorpses();
|
|
|
|
_corpseUpdateTimer.Reset();
|
|
}
|
|
|
|
void Map::SendInitTransports(Player* player)
|
|
{
|
|
if (_transports.empty())
|
|
return;
|
|
|
|
// Hack to send out transports
|
|
UpdateData transData;
|
|
for (TransportsContainer::const_iterator itr = _transports.begin(); itr != _transports.end(); ++itr)
|
|
if (*itr != player->GetTransport())
|
|
(*itr)->BuildCreateUpdateBlockForPlayer(&transData, player);
|
|
|
|
if (!transData.HasData())
|
|
return;
|
|
|
|
WorldPacket packet;
|
|
transData.BuildPacket(packet);
|
|
player->SendDirectMessage(&packet);
|
|
}
|
|
|
|
void Map::SendRemoveTransports(Player* player)
|
|
{
|
|
if (_transports.empty())
|
|
return;
|
|
|
|
// Hack to send out transports
|
|
UpdateData transData;
|
|
for (TransportsContainer::const_iterator itr = _transports.begin(); itr != _transports.end(); ++itr)
|
|
if (*itr != player->GetTransport())
|
|
(*itr)->BuildOutOfRangeUpdateBlock(&transData);
|
|
|
|
if (!transData.HasData())
|
|
return;
|
|
|
|
WorldPacket packet;
|
|
transData.BuildPacket(packet);
|
|
player->SendDirectMessage(&packet);
|
|
}
|
|
|
|
void Map::SendObjectUpdates()
|
|
{
|
|
UpdateDataMapType update_players;
|
|
|
|
while (!_updateObjects.empty())
|
|
{
|
|
Object* obj = *_updateObjects.begin();
|
|
ASSERT(obj->IsInWorld());
|
|
|
|
_updateObjects.erase(_updateObjects.begin());
|
|
obj->BuildUpdate(update_players);
|
|
}
|
|
|
|
WorldPacket packet; // here we allocate a std::vector with a size of 0x10000
|
|
for (UpdateDataMapType::iterator iter = update_players.begin(); iter != update_players.end(); ++iter)
|
|
{
|
|
iter->second.BuildPacket(packet);
|
|
iter->first->SendDirectMessage(&packet);
|
|
packet.clear(); // clean the string
|
|
}
|
|
}
|
|
|
|
uint32 Map::ApplyDynamicModeRespawnScaling(WorldObject const* obj, uint32 respawnDelay) const
|
|
{
|
|
ASSERT(obj->GetMap() == this);
|
|
|
|
float rate = sWorld->getFloatConfig(obj->IsGameObject() ? CONFIG_RESPAWN_DYNAMICRATE_GAMEOBJECT : CONFIG_RESPAWN_DYNAMICRATE_CREATURE);
|
|
|
|
if (rate == 1.0f)
|
|
return respawnDelay;
|
|
|
|
// No instanced maps (dungeons, battlegrounds, arenas etc.)
|
|
if (obj->GetMap()->Instanceable())
|
|
return respawnDelay;
|
|
|
|
// No quest givers or world bosses
|
|
if (Creature const* creature = obj->ToCreature())
|
|
if (creature->IsQuestGiver() || creature->isWorldBoss()
|
|
|| (creature->GetCreatureTemplate()->rank == CREATURE_ELITE_RARE)
|
|
|| (creature->GetCreatureTemplate()->rank == CREATURE_ELITE_RAREELITE))
|
|
return respawnDelay;
|
|
|
|
auto it = _zonePlayerCountMap.find(obj->GetZoneId());
|
|
if (it == _zonePlayerCountMap.end())
|
|
return respawnDelay;
|
|
uint32 const playerCount = it->second;
|
|
if (!playerCount)
|
|
return respawnDelay;
|
|
double const adjustFactor = rate / playerCount;
|
|
if (adjustFactor >= 1.0) // nothing to do here
|
|
return respawnDelay;
|
|
uint32 const timeMinimum = sWorld->getIntConfig(obj->IsGameObject() ? CONFIG_RESPAWN_DYNAMICMINIMUM_GAMEOBJECT : CONFIG_RESPAWN_DYNAMICMINIMUM_CREATURE);
|
|
if (respawnDelay <= timeMinimum)
|
|
return respawnDelay;
|
|
|
|
return std::max<uint32>(std::ceil(respawnDelay * adjustFactor), timeMinimum);
|
|
}
|
|
|
|
void Map::DelayedUpdate(const uint32 t_diff)
|
|
{
|
|
for (_transportsUpdateIter = _transports.begin(); _transportsUpdateIter != _transports.end();)
|
|
{
|
|
Transport* transport = *_transportsUpdateIter;
|
|
++_transportsUpdateIter;
|
|
|
|
if (!transport->IsInWorld())
|
|
continue;
|
|
|
|
transport->DelayedUpdate(t_diff);
|
|
}
|
|
|
|
RemoveAllObjectsInRemoveList();
|
|
}
|
|
|
|
void Map::AddObjectToRemoveList(WorldObject* obj)
|
|
{
|
|
ASSERT(obj->GetMapId() == GetId() && obj->GetInstanceId() == GetInstanceId());
|
|
|
|
obj->CleanupsBeforeDelete(false); // remove or simplify at least cross referenced links
|
|
|
|
i_objectsToRemove.insert(obj);
|
|
//LOG_DEBUG("maps", "Object ({}) added to removing list.", obj->GetGUID().ToString());
|
|
}
|
|
|
|
void Map::RemoveAllObjectsInRemoveList()
|
|
{
|
|
while (!i_objectsToRemove.empty())
|
|
{
|
|
std::unordered_set<WorldObject*>::iterator itr = i_objectsToRemove.begin();
|
|
WorldObject* obj = *itr;
|
|
i_objectsToRemove.erase(itr);
|
|
|
|
switch (obj->GetTypeId())
|
|
{
|
|
case TYPEID_CORPSE:
|
|
{
|
|
Corpse* corpse = ObjectAccessor::GetCorpse(*obj, obj->GetGUID());
|
|
if (!corpse)
|
|
LOG_ERROR("maps", "Tried to delete corpse/bones {} that is not in map.", obj->GetGUID().ToString());
|
|
else
|
|
RemoveFromMap(corpse, true);
|
|
break;
|
|
}
|
|
case TYPEID_DYNAMICOBJECT:
|
|
RemoveFromMap((DynamicObject*)obj, true);
|
|
break;
|
|
case TYPEID_GAMEOBJECT:
|
|
if (Transport* transport = obj->ToGameObject()->ToTransport())
|
|
RemoveFromMap(transport, true);
|
|
else
|
|
RemoveFromMap(obj->ToGameObject(), true);
|
|
break;
|
|
case TYPEID_UNIT:
|
|
// in case triggered sequence some spell can continue casting after prev CleanupsBeforeDelete call
|
|
// make sure that like sources auras/etc removed before destructor start
|
|
obj->ToCreature()->CleanupsBeforeDelete();
|
|
RemoveFromMap(obj->ToCreature(), true);
|
|
break;
|
|
default:
|
|
LOG_ERROR("maps", "Non-grid object (TypeId: {}) is in grid object remove list, ignored.", obj->GetTypeId());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32 Map::GetPlayersCountExceptGMs() const
|
|
{
|
|
uint32 count = 0;
|
|
for (MapRefMgr::const_iterator itr = m_mapRefMgr.begin(); itr != m_mapRefMgr.end(); ++itr)
|
|
if (!itr->GetSource()->IsGameMaster())
|
|
++count;
|
|
return count;
|
|
}
|
|
|
|
void Map::SendToPlayers(WorldPacket const* data) const
|
|
{
|
|
for (MapRefMgr::const_iterator itr = m_mapRefMgr.begin(); itr != m_mapRefMgr.end(); ++itr)
|
|
itr->GetSource()->SendDirectMessage(data);
|
|
}
|
|
|
|
template bool Map::AddToMap(Corpse*, bool);
|
|
template bool Map::AddToMap(Creature*, bool);
|
|
template bool Map::AddToMap(GameObject*, bool);
|
|
template bool Map::AddToMap(DynamicObject*, bool);
|
|
|
|
template void Map::RemoveFromMap(Corpse*, bool);
|
|
template void Map::RemoveFromMap(Creature*, bool);
|
|
template void Map::RemoveFromMap(GameObject*, bool);
|
|
template void Map::RemoveFromMap(DynamicObject*, bool);
|
|
|
|
/* ******* Dungeon Instance Maps ******* */
|
|
|
|
InstanceMap::InstanceMap(uint32 id, uint32 InstanceId, uint8 SpawnMode, Map* _parent)
|
|
: Map(id, InstanceId, SpawnMode, _parent),
|
|
m_resetAfterUnload(false), m_unloadWhenEmpty(false),
|
|
instance_data(nullptr), i_script_id(0)
|
|
{
|
|
//lets initialize visibility distance for dungeons
|
|
InstanceMap::InitVisibilityDistance();
|
|
|
|
// the timer is started by default, and stopped when the first player joins
|
|
// this make sure it gets unloaded if for some reason no player joins
|
|
m_unloadTimer = std::max(sWorld->getIntConfig(CONFIG_INSTANCE_UNLOAD_DELAY), (uint32)MIN_UNLOAD_DELAY);
|
|
|
|
// pussywizard:
|
|
if (IsRaid())
|
|
if (time_t resetTime = sInstanceSaveMgr->GetResetTimeFor(id, Difficulty(SpawnMode)))
|
|
if (time_t extendedResetTime = sInstanceSaveMgr->GetExtendedResetTimeFor(id, Difficulty(SpawnMode)))
|
|
_instanceResetPeriod = extendedResetTime - resetTime;
|
|
}
|
|
|
|
InstanceMap::~InstanceMap()
|
|
{
|
|
delete instance_data;
|
|
instance_data = nullptr;
|
|
sInstanceSaveMgr->DeleteInstanceSaveIfNeeded(GetInstanceId(), true);
|
|
}
|
|
|
|
void InstanceMap::InitVisibilityDistance()
|
|
{
|
|
//init visibility distance for instances
|
|
m_VisibleDistance = World::GetMaxVisibleDistanceInInstances();
|
|
|
|
// pussywizard: this CAN NOT exceed MAX_VISIBILITY_DISTANCE
|
|
switch (GetId())
|
|
{
|
|
case 429: // Dire Maul
|
|
case 550: // The Eye
|
|
case 578: // The Nexus: The Oculus
|
|
m_VisibleDistance = 175.0f;
|
|
break;
|
|
case 649: // Trial of the Crusader
|
|
case 650: // Trial of the Champion
|
|
case 595: // Culling of Startholme
|
|
case 658: // Pit of Saron
|
|
m_VisibleDistance = 150.0f;
|
|
break;
|
|
case 615: // Obsidian Sanctum
|
|
case 616: // Eye of Eternity
|
|
case 603: // Ulduar
|
|
case 668: // Halls of Reflection
|
|
case 631: // Icecrown Citadel
|
|
case 724: // Ruby Sanctum
|
|
m_VisibleDistance = 200.0f;
|
|
break;
|
|
case 531: // Ahn'Qiraj Temple
|
|
m_VisibleDistance = 300.0f;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
Do map specific checks to see if the player can enter
|
|
*/
|
|
Map::EnterState InstanceMap::CannotEnter(Player* player, bool loginCheck)
|
|
{
|
|
if (!loginCheck && player->GetMapRef().getTarget() == this)
|
|
{
|
|
LOG_ERROR("maps", "InstanceMap::CanEnter - player {} ({}) already in map {}, {}, {}!",
|
|
player->GetName(), player->GetGUID().ToString(), GetId(), GetInstanceId(), GetSpawnMode());
|
|
|
|
return CANNOT_ENTER_ALREADY_IN_MAP;
|
|
}
|
|
|
|
// allow GM's to enter
|
|
if (player->IsGameMaster())
|
|
return Map::CannotEnter(player, loginCheck);
|
|
|
|
// cannot enter if the instance is full (player cap), GMs don't count
|
|
uint32 maxPlayers = GetMaxPlayers();
|
|
if (GetPlayersCountExceptGMs() >= (loginCheck ? maxPlayers + 1 : maxPlayers))
|
|
{
|
|
LOG_DEBUG("maps", "MAP: Instance '{}' of map '{}' cannot have more than '{}' players. Player '{}' rejected", GetInstanceId(), GetMapName(), maxPlayers, player->GetName());
|
|
player->SendTransferAborted(GetId(), TRANSFER_ABORT_MAX_PLAYERS);
|
|
return CANNOT_ENTER_MAX_PLAYERS;
|
|
}
|
|
|
|
// cannot enter while an encounter is in progress on raids
|
|
bool checkProgress = (IsRaid() || GetId() == 668 /*HoR*/);
|
|
if (checkProgress && GetInstanceScript() && GetInstanceScript()->IsEncounterInProgress())
|
|
{
|
|
player->SendTransferAborted(GetId(), TRANSFER_ABORT_ZONE_IN_COMBAT);
|
|
return CANNOT_ENTER_ZONE_IN_COMBAT;
|
|
}
|
|
|
|
// xinef: dont allow LFG Group to enter other instance that is selected
|
|
if (Group* group = player->GetGroup())
|
|
if (group->isLFGGroup())
|
|
if (!sLFGMgr->inLfgDungeonMap(group->GetGUID(), GetId(), GetDifficulty()))
|
|
{
|
|
player->SendTransferAborted(GetId(), TRANSFER_ABORT_MAP_NOT_ALLOWED);
|
|
return CANNOT_ENTER_UNSPECIFIED_REASON;
|
|
}
|
|
|
|
// cannot enter if instance is in use by another party/soloer that have a permanent save in the same instance id
|
|
PlayerList const& playerList = GetPlayers();
|
|
if (!playerList.IsEmpty())
|
|
for (PlayerList::const_iterator i = playerList.begin(); i != playerList.end(); ++i)
|
|
if (Player* iPlayer = i->GetSource())
|
|
{
|
|
if (iPlayer == player) // login case, player already added to map
|
|
continue;
|
|
if (iPlayer->IsGameMaster()) // bypass GMs
|
|
continue;
|
|
if (!player->GetGroup()) // player has not group and there is someone inside, deny entry
|
|
{
|
|
player->SendTransferAborted(GetId(), TRANSFER_ABORT_MAX_PLAYERS);
|
|
return CANNOT_ENTER_INSTANCE_BIND_MISMATCH;
|
|
}
|
|
// player inside instance has no group or his groups is different to entering player's one, deny entry
|
|
if (!iPlayer->GetGroup() || iPlayer->GetGroup() != player->GetGroup())
|
|
{
|
|
player->SendTransferAborted(GetId(), TRANSFER_ABORT_MAX_PLAYERS);
|
|
return CANNOT_ENTER_INSTANCE_BIND_MISMATCH;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return Map::CannotEnter(player, loginCheck);
|
|
}
|
|
|
|
/*
|
|
Do map specific checks and add the player to the map if successful.
|
|
*/
|
|
bool InstanceMap::AddPlayerToMap(Player* player)
|
|
{
|
|
if (m_resetAfterUnload) // this instance has been reset, it's not meant to be used anymore
|
|
return false;
|
|
|
|
if (IsDungeon())
|
|
{
|
|
Group* group = player->GetGroup();
|
|
|
|
// get an instance save for the map
|
|
InstanceSave* mapSave = sInstanceSaveMgr->GetInstanceSave(GetInstanceId());
|
|
if (!mapSave)
|
|
{
|
|
LOG_ERROR("maps", "InstanceMap::Add: InstanceSave does not exist for map {} spawnmode {} with instance id {}", GetId(), GetSpawnMode(), GetInstanceId());
|
|
return false;
|
|
}
|
|
|
|
// check for existing instance binds
|
|
InstancePlayerBind* playerBind = sInstanceSaveMgr->PlayerGetBoundInstance(player->GetGUID(), GetId(), Difficulty(GetSpawnMode()));
|
|
if (playerBind && playerBind->perm)
|
|
{
|
|
if (playerBind->save != mapSave)
|
|
{
|
|
LOG_ERROR("maps", "InstanceMap::Add: player {} ({}) is permanently bound to instance {}, {}, {}, {} but he is being put into instance {}, {}, {}, {}",
|
|
player->GetName(), player->GetGUID().ToString(), playerBind->save->GetMapId(), playerBind->save->GetInstanceId(), playerBind->save->GetDifficulty(),
|
|
playerBind->save->CanReset(), mapSave->GetMapId(), mapSave->GetInstanceId(), mapSave->GetDifficulty(), mapSave->CanReset());
|
|
return false;
|
|
}
|
|
}
|
|
else if (player->GetSession()->PlayerLoading() && playerBind && playerBind->save != mapSave)
|
|
{
|
|
// Prevent "Convert to Raid" exploit to reset instances
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
playerBind = sInstanceSaveMgr->PlayerBindToInstance(player->GetGUID(), mapSave, false, player);
|
|
// pussywizard: bind lider also if not yet bound
|
|
if (Group* g = player->GetGroup())
|
|
if (g->GetLeaderGUID() != player->GetGUID())
|
|
if (!sInstanceSaveMgr->PlayerGetBoundInstance(g->GetLeaderGUID(), mapSave->GetMapId(), mapSave->GetDifficulty()))
|
|
{
|
|
sInstanceSaveMgr->PlayerCreateBoundInstancesMaps(g->GetLeaderGUID());
|
|
sInstanceSaveMgr->PlayerBindToInstance(g->GetLeaderGUID(), mapSave, false, ObjectAccessor::FindConnectedPlayer(g->GetLeaderGUID()));
|
|
}
|
|
}
|
|
|
|
// increase current instances (hourly limit)
|
|
// xinef: specific instances are still limited
|
|
if (!group || !group->isLFGGroup() || !group->IsLfgRandomInstance())
|
|
player->AddInstanceEnterTime(GetInstanceId(), GameTime::GetGameTime().count());
|
|
|
|
if (!playerBind->perm && !mapSave->CanReset() && group && !group->isLFGGroup() && !group->IsLfgRandomInstance())
|
|
{
|
|
WorldPacket data(SMSG_INSTANCE_LOCK_WARNING_QUERY, 9);
|
|
data << uint32(60000);
|
|
data << uint32(instance_data ? instance_data->GetCompletedEncounterMask() : 0);
|
|
data << uint8(0);
|
|
player->SendDirectMessage(&data);
|
|
player->SetPendingBind(mapSave->GetInstanceId(), 60000);
|
|
}
|
|
}
|
|
|
|
// initialize unload state
|
|
m_unloadTimer = 0;
|
|
m_resetAfterUnload = false;
|
|
m_unloadWhenEmpty = false;
|
|
|
|
// this will acquire the same mutex so it cannot be in the previous block
|
|
Map::AddPlayerToMap(player);
|
|
|
|
if (instance_data)
|
|
instance_data->OnPlayerEnter(player);
|
|
|
|
return true;
|
|
}
|
|
|
|
void InstanceMap::Update(const uint32 t_diff, const uint32 s_diff, bool /*thread*/)
|
|
{
|
|
Map::Update(t_diff, s_diff);
|
|
|
|
if (t_diff)
|
|
if (instance_data)
|
|
instance_data->Update(t_diff);
|
|
}
|
|
|
|
void InstanceMap::RemovePlayerFromMap(Player* player, bool remove)
|
|
{
|
|
if (instance_data)
|
|
instance_data->OnPlayerLeave(player);
|
|
// pussywizard: moved m_unloadTimer to InstanceMap::AfterPlayerUnlinkFromMap(), in this function if 2 players run out at the same time the instance won't close
|
|
//if (!m_unloadTimer && m_mapRefMgr.getSize() == 1)
|
|
// m_unloadTimer = m_unloadWhenEmpty ? MIN_UNLOAD_DELAY : std::max(sWorld->getIntConfig(CONFIG_INSTANCE_UNLOAD_DELAY), (uint32)MIN_UNLOAD_DELAY);
|
|
Map::RemovePlayerFromMap(player, remove);
|
|
|
|
// If remove == true - player already deleted.
|
|
if (!remove)
|
|
player->SetPendingBind(0, 0);
|
|
}
|
|
|
|
void InstanceMap::AfterPlayerUnlinkFromMap()
|
|
{
|
|
if (!m_unloadTimer && !HavePlayers())
|
|
m_unloadTimer = m_unloadWhenEmpty ? MIN_UNLOAD_DELAY : std::max(sWorld->getIntConfig(CONFIG_INSTANCE_UNLOAD_DELAY), (uint32)MIN_UNLOAD_DELAY);
|
|
Map::AfterPlayerUnlinkFromMap();
|
|
}
|
|
|
|
void InstanceMap::CreateInstanceScript(bool load, std::string data, uint32 completedEncounterMask)
|
|
{
|
|
if (instance_data)
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool isOtherAI = false;
|
|
|
|
sScriptMgr->OnBeforeCreateInstanceScript(this, &instance_data, load, data, completedEncounterMask);
|
|
|
|
if (instance_data)
|
|
isOtherAI = true;
|
|
|
|
// if ALE AI was fetched succesfully we should not call CreateInstanceData nor set the unused scriptID
|
|
if (!isOtherAI)
|
|
{
|
|
InstanceTemplate const* mInstance = sObjectMgr->GetInstanceTemplate(GetId());
|
|
if (mInstance)
|
|
{
|
|
i_script_id = mInstance->ScriptId;
|
|
instance_data = sScriptMgr->CreateInstanceScript(this);
|
|
}
|
|
}
|
|
|
|
if (!instance_data)
|
|
return;
|
|
|
|
// use mangos behavior if we are dealing with ALE AI
|
|
// initialize should then be called only if load is false
|
|
if (!isOtherAI || !load)
|
|
{
|
|
instance_data->Initialize();
|
|
}
|
|
|
|
if (load)
|
|
{
|
|
instance_data->SetCompletedEncountersMask(completedEncounterMask, false);
|
|
if (data != "")
|
|
instance_data->Load(data.c_str());
|
|
}
|
|
|
|
instance_data->LoadInstanceSavedGameobjectStateData();
|
|
}
|
|
|
|
/*
|
|
Returns true if there are no players in the instance
|
|
*/
|
|
bool InstanceMap::Reset(uint8 method, GuidList* globalResetSkipList)
|
|
{
|
|
if (method == INSTANCE_RESET_GLOBAL)
|
|
{
|
|
// pussywizard: teleport out immediately
|
|
for (MapRefMgr::iterator itr = m_mapRefMgr.begin(); itr != m_mapRefMgr.end(); ++itr)
|
|
{
|
|
// teleport players that are no longer bound (can be still bound if extended id)
|
|
if (!globalResetSkipList || std::find(globalResetSkipList->begin(), globalResetSkipList->end(), itr->GetSource()->GetGUID()) == globalResetSkipList->end())
|
|
itr->GetSource()->RepopAtGraveyard();
|
|
}
|
|
|
|
// reset map only if noone is bound
|
|
if (!globalResetSkipList || globalResetSkipList->empty())
|
|
{
|
|
// pussywizard: setting both m_unloadWhenEmpty and m_unloadTimer intended, in case RepopAtGraveyard failed
|
|
if (HavePlayers())
|
|
m_unloadWhenEmpty = true;
|
|
m_unloadTimer = MIN_UNLOAD_DELAY;
|
|
m_resetAfterUnload = true;
|
|
}
|
|
|
|
return m_mapRefMgr.IsEmpty();
|
|
}
|
|
|
|
if (HavePlayers())
|
|
{
|
|
if (method == INSTANCE_RESET_ALL || method == INSTANCE_RESET_CHANGE_DIFFICULTY)
|
|
{
|
|
for (MapRefMgr::iterator itr = m_mapRefMgr.begin(); itr != m_mapRefMgr.end(); ++itr)
|
|
itr->GetSource()->SendResetFailedNotify(GetId());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_unloadTimer = MIN_UNLOAD_DELAY;
|
|
m_resetAfterUnload = true;
|
|
}
|
|
|
|
return m_mapRefMgr.IsEmpty();
|
|
}
|
|
|
|
std::string const& InstanceMap::GetScriptName() const
|
|
{
|
|
return sObjectMgr->GetScriptName(i_script_id);
|
|
}
|
|
|
|
void InstanceMap::PermBindAllPlayers()
|
|
{
|
|
if (!IsDungeon())
|
|
return;
|
|
|
|
InstanceSave* save = sInstanceSaveMgr->GetInstanceSave(GetInstanceId());
|
|
if (!save)
|
|
{
|
|
LOG_ERROR("maps", "Cannot bind players because no instance save is available for instance map (Name: {}, Entry: {}, InstanceId: {})!", GetMapName(), GetId(), GetInstanceId());
|
|
return;
|
|
}
|
|
|
|
Player* player;
|
|
Group* group;
|
|
// group members outside the instance group don't get bound
|
|
for (MapRefMgr::iterator itr = m_mapRefMgr.begin(); itr != m_mapRefMgr.end(); ++itr)
|
|
{
|
|
player = itr->GetSource();
|
|
group = player->GetGroup();
|
|
|
|
// players inside an instance cannot be bound to other instances
|
|
// some players may already be permanently bound, in this case nothing happens
|
|
InstancePlayerBind* bind = sInstanceSaveMgr->PlayerGetBoundInstance(player->GetGUID(), save->GetMapId(), save->GetDifficulty());
|
|
|
|
if (!bind || !bind->perm)
|
|
{
|
|
WorldPacket data(SMSG_INSTANCE_SAVE_CREATED, 4);
|
|
data << uint32(0);
|
|
player->SendDirectMessage(&data);
|
|
sInstanceSaveMgr->PlayerBindToInstance(player->GetGUID(), save, true, player);
|
|
}
|
|
|
|
// Xinef: Difficulty change prevention
|
|
if (group)
|
|
group->SetDifficultyChangePrevention(DIFFICULTY_PREVENTION_CHANGE_BOSS_KILLED);
|
|
}
|
|
}
|
|
|
|
void InstanceMap::UnloadAll()
|
|
{
|
|
ASSERT(!HavePlayers());
|
|
|
|
if (m_resetAfterUnload)
|
|
{
|
|
DeleteRespawnTimes();
|
|
DeleteCorpseData();
|
|
}
|
|
|
|
Map::UnloadAll();
|
|
}
|
|
|
|
void InstanceMap::SendResetWarnings(uint32 timeLeft) const
|
|
{
|
|
for (MapRefMgr::const_iterator itr = m_mapRefMgr.begin(); itr != m_mapRefMgr.end(); ++itr)
|
|
itr->GetSource()->SendInstanceResetWarning(GetId(), itr->GetSource()->GetDifficulty(IsRaid()), timeLeft, false);
|
|
}
|
|
|
|
MapDifficulty const* Map::GetMapDifficulty() const
|
|
{
|
|
return GetMapDifficultyData(GetId(), GetDifficulty());
|
|
}
|
|
|
|
uint32 InstanceMap::GetMaxPlayers() const
|
|
{
|
|
MapDifficulty const* mapDiff = GetMapDifficulty();
|
|
if (mapDiff && mapDiff->maxPlayers)
|
|
return mapDiff->maxPlayers;
|
|
|
|
return GetEntry()->maxPlayers;
|
|
}
|
|
|
|
uint32 InstanceMap::GetMaxResetDelay() const
|
|
{
|
|
MapDifficulty const* mapDiff = GetMapDifficulty();
|
|
return mapDiff ? mapDiff->resetTime : 0;
|
|
}
|
|
|
|
/* ******* Battleground Instance Maps ******* */
|
|
|
|
BattlegroundMap::BattlegroundMap(uint32 id, uint32 InstanceId, Map* _parent, uint8 spawnMode)
|
|
: Map(id, InstanceId, spawnMode, _parent), m_bg(nullptr)
|
|
{
|
|
//lets initialize visibility distance for BG/Arenas
|
|
BattlegroundMap::InitVisibilityDistance();
|
|
}
|
|
|
|
BattlegroundMap::~BattlegroundMap()
|
|
{
|
|
if (m_bg)
|
|
{
|
|
//unlink to prevent crash, always unlink all pointer reference before destruction
|
|
m_bg->SetBgMap(nullptr);
|
|
m_bg = nullptr;
|
|
}
|
|
}
|
|
|
|
void BattlegroundMap::InitVisibilityDistance()
|
|
{
|
|
//init visibility distance for BG/Arenas
|
|
m_VisibleDistance = World::GetMaxVisibleDistanceInBGArenas();
|
|
|
|
if (IsBattleArena()) // pussywizard: start with 30yd visibility range on arenas to ensure players can't get informations about the opponents in any way
|
|
m_VisibleDistance = 30.0f;
|
|
}
|
|
|
|
Map::EnterState BattlegroundMap::CannotEnter(Player* player, bool loginCheck)
|
|
{
|
|
if (!loginCheck && player->GetMapRef().getTarget() == this)
|
|
{
|
|
LOG_ERROR("maps", "BGMap::CanEnter - player {} is already in map!", player->GetGUID().ToString());
|
|
ABORT();
|
|
return CANNOT_ENTER_ALREADY_IN_MAP;
|
|
}
|
|
|
|
if (player->GetBattlegroundId() != GetInstanceId())
|
|
return CANNOT_ENTER_INSTANCE_BIND_MISMATCH;
|
|
|
|
// pussywizard: no need to check player limit here, invitations are limited by Battleground::GetFreeSlotsForTeam
|
|
|
|
return Map::CannotEnter(player, loginCheck);
|
|
}
|
|
|
|
bool BattlegroundMap::AddPlayerToMap(Player* player)
|
|
{
|
|
player->m_InstanceValid = true;
|
|
if (IsBattleArena())
|
|
player->CastSpell(player, 100102, true);
|
|
return Map::AddPlayerToMap(player);
|
|
}
|
|
|
|
void BattlegroundMap::RemovePlayerFromMap(Player* player, bool remove)
|
|
{
|
|
if (Battleground* bg = GetBG())
|
|
{
|
|
bg->RemovePlayerAtLeave(player);
|
|
if (IsBattleArena())
|
|
bg->RemoveSpectator(player);
|
|
}
|
|
if (IsBattleArena())
|
|
player->RemoveAura(100102);
|
|
Map::RemovePlayerFromMap(player, remove);
|
|
}
|
|
|
|
void BattlegroundMap::SetUnload()
|
|
{
|
|
m_unloadTimer = MIN_UNLOAD_DELAY;
|
|
}
|
|
|
|
void BattlegroundMap::RemoveAllPlayers()
|
|
{
|
|
if (HavePlayers())
|
|
for (MapRefMgr::iterator itr = m_mapRefMgr.begin(); itr != m_mapRefMgr.end(); ++itr)
|
|
if (Player* player = itr->GetSource())
|
|
if (!player->IsBeingTeleportedFar())
|
|
player->TeleportTo(player->GetEntryPoint());
|
|
}
|
|
|
|
Corpse* Map::GetCorpse(ObjectGuid const& guid)
|
|
{
|
|
return _objectsStore.Find<Corpse>(guid);
|
|
}
|
|
|
|
Creature* Map::GetCreature(ObjectGuid const& guid)
|
|
{
|
|
return _objectsStore.Find<Creature>(guid);
|
|
}
|
|
|
|
GameObject* Map::GetGameObject(ObjectGuid const& guid)
|
|
{
|
|
return _objectsStore.Find<GameObject>(guid);
|
|
}
|
|
|
|
Pet* Map::GetPet(ObjectGuid const& guid)
|
|
{
|
|
return dynamic_cast<Pet*>(_objectsStore.Find<Creature>(guid));
|
|
}
|
|
|
|
Transport* Map::GetTransport(ObjectGuid const& guid)
|
|
{
|
|
if (guid.GetHigh() != HighGuid::Mo_Transport && guid.GetHigh() != HighGuid::Transport)
|
|
return nullptr;
|
|
|
|
GameObject* go = GetGameObject(guid);
|
|
return go ? go->ToTransport() : nullptr;
|
|
}
|
|
|
|
DynamicObject* Map::GetDynamicObject(ObjectGuid const& guid)
|
|
{
|
|
return _objectsStore.Find<DynamicObject>(guid);
|
|
}
|
|
|
|
void Map::UpdateIteratorBack(Player* player)
|
|
{
|
|
if (m_mapRefIter == player->GetMapRef())
|
|
m_mapRefIter = m_mapRefIter->nocheck_prev();
|
|
}
|
|
|
|
void Map::SaveCreatureRespawnTime(ObjectGuid::LowType spawnId, time_t& respawnTime)
|
|
{
|
|
if (!respawnTime)
|
|
{
|
|
// Delete only
|
|
RemoveCreatureRespawnTime(spawnId);
|
|
return;
|
|
}
|
|
|
|
time_t now = GameTime::GetGameTime().count();
|
|
if (GetInstanceResetPeriod() > 0 && respawnTime - now + 5 >= GetInstanceResetPeriod())
|
|
respawnTime = now + YEAR;
|
|
|
|
_creatureRespawnTimes[spawnId] = respawnTime;
|
|
|
|
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_CREATURE_RESPAWN);
|
|
stmt->SetData(0, spawnId);
|
|
stmt->SetData(1, uint32(respawnTime));
|
|
stmt->SetData(2, GetId());
|
|
stmt->SetData(3, GetInstanceId());
|
|
CharacterDatabase.Execute(stmt);
|
|
}
|
|
|
|
void Map::RemoveCreatureRespawnTime(ObjectGuid::LowType spawnId)
|
|
{
|
|
_creatureRespawnTimes.erase(spawnId);
|
|
|
|
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CREATURE_RESPAWN);
|
|
stmt->SetData(0, spawnId);
|
|
stmt->SetData(1, GetId());
|
|
stmt->SetData(2, GetInstanceId());
|
|
CharacterDatabase.Execute(stmt);
|
|
}
|
|
|
|
void Map::SaveGORespawnTime(ObjectGuid::LowType spawnId, time_t& respawnTime)
|
|
{
|
|
if (!respawnTime)
|
|
{
|
|
// Delete only
|
|
RemoveGORespawnTime(spawnId);
|
|
return;
|
|
}
|
|
|
|
time_t now = GameTime::GetGameTime().count();
|
|
if (GetInstanceResetPeriod() > 0 && respawnTime - now + 5 >= GetInstanceResetPeriod())
|
|
respawnTime = now + YEAR;
|
|
|
|
_goRespawnTimes[spawnId] = respawnTime;
|
|
|
|
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_GO_RESPAWN);
|
|
stmt->SetData(0, spawnId);
|
|
stmt->SetData(1, uint32(respawnTime));
|
|
stmt->SetData(2, GetId());
|
|
stmt->SetData(3, GetInstanceId());
|
|
CharacterDatabase.Execute(stmt);
|
|
}
|
|
|
|
void Map::RemoveGORespawnTime(ObjectGuid::LowType spawnId)
|
|
{
|
|
_goRespawnTimes.erase(spawnId);
|
|
|
|
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GO_RESPAWN);
|
|
stmt->SetData(0, spawnId);
|
|
stmt->SetData(1, GetId());
|
|
stmt->SetData(2, GetInstanceId());
|
|
CharacterDatabase.Execute(stmt);
|
|
}
|
|
|
|
void Map::LoadRespawnTimes()
|
|
{
|
|
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CREATURE_RESPAWNS);
|
|
stmt->SetData(0, GetId());
|
|
stmt->SetData(1, GetInstanceId());
|
|
if (PreparedQueryResult result = CharacterDatabase.Query(stmt))
|
|
{
|
|
do
|
|
{
|
|
Field* fields = result->Fetch();
|
|
ObjectGuid::LowType lowguid = fields[0].Get<uint32>();
|
|
uint32 respawnTime = fields[1].Get<uint32>();
|
|
|
|
_creatureRespawnTimes[lowguid] = time_t(respawnTime);
|
|
} while (result->NextRow());
|
|
}
|
|
|
|
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_GO_RESPAWNS);
|
|
stmt->SetData(0, GetId());
|
|
stmt->SetData(1, GetInstanceId());
|
|
if (PreparedQueryResult result = CharacterDatabase.Query(stmt))
|
|
{
|
|
do
|
|
{
|
|
Field* fields = result->Fetch();
|
|
ObjectGuid::LowType lowguid = fields[0].Get<uint32>();
|
|
uint32 respawnTime = fields[1].Get<uint32>();
|
|
|
|
_goRespawnTimes[lowguid] = time_t(respawnTime);
|
|
} while (result->NextRow());
|
|
}
|
|
}
|
|
|
|
void Map::DeleteRespawnTimes()
|
|
{
|
|
_creatureRespawnTimes.clear();
|
|
_goRespawnTimes.clear();
|
|
|
|
DeleteRespawnTimesInDB(GetId(), GetInstanceId());
|
|
}
|
|
|
|
void Map::DeleteRespawnTimesInDB(uint16 mapId, uint32 instanceId)
|
|
{
|
|
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CREATURE_RESPAWN_BY_INSTANCE);
|
|
stmt->SetData(0, mapId);
|
|
stmt->SetData(1, instanceId);
|
|
CharacterDatabase.Execute(stmt);
|
|
|
|
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GO_RESPAWN_BY_INSTANCE);
|
|
stmt->SetData(0, mapId);
|
|
stmt->SetData(1, instanceId);
|
|
CharacterDatabase.Execute(stmt);
|
|
}
|
|
|
|
void Map::UpdateEncounterState(EncounterCreditType type, uint32 creditEntry, Unit* source)
|
|
{
|
|
Difficulty difficulty_fixed = (IsSharedDifficultyMap(GetId()) ? Difficulty(GetDifficulty() % 2) : GetDifficulty());
|
|
DungeonEncounterList const* encounters;
|
|
// 631 : ICC - 724 : Ruby Sanctum --- For heroic difficulties, for some reason, we don't have an encounter list, so we get the encounter list from normal diff. We shouldn't change difficulty_fixed variable.
|
|
if ((GetId() == 631 || GetId() == 724) && IsHeroic())
|
|
{
|
|
encounters = sObjectMgr->GetDungeonEncounterList(GetId(), !Is25ManRaid() ? RAID_DIFFICULTY_10MAN_NORMAL : RAID_DIFFICULTY_25MAN_NORMAL);
|
|
}
|
|
else
|
|
{
|
|
encounters = sObjectMgr->GetDungeonEncounterList(GetId(), difficulty_fixed);
|
|
}
|
|
|
|
if (!encounters)
|
|
{
|
|
return;
|
|
}
|
|
|
|
uint32 dungeonId = 0;
|
|
bool updated = false;
|
|
|
|
for (DungeonEncounterList::const_iterator itr = encounters->begin(); itr != encounters->end(); ++itr)
|
|
{
|
|
DungeonEncounter const* encounter = *itr;
|
|
if (encounter->creditType == type && encounter->creditEntry == creditEntry)
|
|
{
|
|
if (source)
|
|
if (InstanceScript* instanceScript = source->GetInstanceScript())
|
|
{
|
|
uint32 prevMask = instanceScript->GetCompletedEncounterMask();
|
|
instanceScript->SetCompletedEncountersMask((1 << encounter->dbcEntry->encounterIndex) | instanceScript->GetCompletedEncounterMask(), true);
|
|
if (prevMask != instanceScript->GetCompletedEncounterMask())
|
|
updated = true;
|
|
}
|
|
|
|
if (encounter->lastEncounterDungeon)
|
|
{
|
|
dungeonId = encounter->lastEncounterDungeon;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// pussywizard:
|
|
LogEncounterFinished(type, creditEntry);
|
|
|
|
sScriptMgr->OnAfterUpdateEncounterState(this, type, creditEntry, source, difficulty_fixed, encounters, dungeonId, updated);
|
|
|
|
if (dungeonId)
|
|
{
|
|
Map::PlayerList const& players = GetPlayers();
|
|
for (Map::PlayerList::const_iterator i = players.begin(); i != players.end(); ++i)
|
|
{
|
|
if (Player* player = i->GetSource())
|
|
if (Group* grp = player->GetGroup())
|
|
if (grp->isLFGGroup())
|
|
{
|
|
sLFGMgr->FinishDungeon(grp->GetGUID(), dungeonId, this);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Map::LogEncounterFinished(EncounterCreditType type, uint32 creditEntry)
|
|
{
|
|
if (!IsRaid() || !GetEntry() || GetEntry()->Expansion() < 2) // only for wotlk raids, because logs take up tons of mysql memory
|
|
return;
|
|
InstanceMap* map = ToInstanceMap();
|
|
if (!map)
|
|
return;
|
|
std::string playersInfo;
|
|
char buffer[16384], buffer2[255];
|
|
Map::PlayerList const& pl = map->GetPlayers();
|
|
for (Map::PlayerList::const_iterator itr = pl.begin(); itr != pl.end(); ++itr)
|
|
if (Player* p = itr->GetSource())
|
|
{
|
|
std::string auraStr;
|
|
const Unit::AuraApplicationMap& a = p->GetAppliedAuras();
|
|
for (auto iterator = a.begin(); iterator != a.end(); ++iterator)
|
|
{
|
|
snprintf(buffer2, 255, "%u(%u) ", iterator->first, iterator->second->GetEffectMask());
|
|
auraStr += buffer2;
|
|
}
|
|
|
|
snprintf(buffer, 16384, "%s (%s, acc: %u, ip: %s, guild: %u), xyz: (%.1f, %.1f, %.1f), auras: %s\n",
|
|
p->GetName().c_str(), p->GetGUID().ToString().c_str(), p->GetSession()->GetAccountId(), p->GetSession()->GetRemoteAddress().c_str(), p->GetGuildId(), p->GetPositionX(), p->GetPositionY(), p->GetPositionZ(), auraStr.c_str());
|
|
playersInfo += buffer;
|
|
}
|
|
CleanStringForMysqlQuery(playersInfo);
|
|
CharacterDatabase.Execute("INSERT INTO log_encounter VALUES(NOW(), {}, {}, {}, {}, '{}')", GetId(), (uint32)GetDifficulty(), type, creditEntry, playersInfo);
|
|
}
|
|
|
|
bool Map::AllTransportsEmpty() const
|
|
{
|
|
for (TransportsContainer::const_iterator itr = _transports.begin(); itr != _transports.end(); ++itr)
|
|
if (!(*itr)->GetPassengers().empty())
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void Map::AllTransportsRemovePassengers()
|
|
{
|
|
for (TransportsContainer::const_iterator itr = _transports.begin(); itr != _transports.end(); ++itr)
|
|
while (!(*itr)->GetPassengers().empty())
|
|
(*itr)->RemovePassenger(*((*itr)->GetPassengers().begin()), true);
|
|
}
|
|
|
|
time_t Map::GetLinkedRespawnTime(ObjectGuid guid) const
|
|
{
|
|
ObjectGuid linkedGuid = sObjectMgr->GetLinkedRespawnGuid(guid);
|
|
switch (linkedGuid.GetHigh())
|
|
{
|
|
case HighGuid::Unit:
|
|
return GetCreatureRespawnTime(linkedGuid.GetCounter());
|
|
case HighGuid::GameObject:
|
|
return GetGORespawnTime(linkedGuid.GetCounter());
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return time_t(0);
|
|
}
|
|
|
|
void Map::AddCorpse(Corpse* corpse)
|
|
{
|
|
corpse->SetMap(this);
|
|
|
|
GridCoord const gridCoord = Acore::ComputeGridCoord(corpse->GetPositionX(), corpse->GetPositionY());
|
|
_corpsesByGrid[gridCoord.GetId()].insert(corpse);
|
|
if (corpse->GetType() != CORPSE_BONES)
|
|
_corpsesByPlayer[corpse->GetOwnerGUID()] = corpse;
|
|
else
|
|
_corpseBones.insert(corpse);
|
|
}
|
|
|
|
void Map::RemoveCorpse(Corpse* corpse)
|
|
{
|
|
ASSERT(corpse);
|
|
GridCoord const gridCoord = Acore::ComputeGridCoord(corpse->GetPositionX(), corpse->GetPositionY());
|
|
|
|
corpse->DestroyForVisiblePlayers();
|
|
if (corpse->IsInGrid())
|
|
RemoveFromMap(corpse, false);
|
|
else
|
|
{
|
|
corpse->RemoveFromWorld();
|
|
corpse->ResetMap();
|
|
}
|
|
|
|
_corpsesByGrid[gridCoord.GetId()].erase(corpse);
|
|
if (corpse->GetType() != CORPSE_BONES)
|
|
_corpsesByPlayer.erase(corpse->GetOwnerGUID());
|
|
else
|
|
_corpseBones.erase(corpse);
|
|
}
|
|
|
|
Corpse* Map::ConvertCorpseToBones(ObjectGuid const& ownerGuid, bool insignia /*= false*/)
|
|
{
|
|
Corpse* corpse = GetCorpseByPlayer(ownerGuid);
|
|
if (!corpse)
|
|
return nullptr;
|
|
|
|
RemoveCorpse(corpse);
|
|
|
|
// remove corpse from DB
|
|
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
|
|
corpse->DeleteFromDB(trans);
|
|
CharacterDatabase.CommitTransaction(trans);
|
|
|
|
Corpse* bones = NULL;
|
|
|
|
// create the bones only if the map and the grid is loaded at the corpse's location
|
|
// ignore bones creating option in case insignia
|
|
if ((insignia ||
|
|
(IsBattlegroundOrArena() ? sWorld->getBoolConfig(CONFIG_DEATH_BONES_BG_OR_ARENA) : sWorld->getBoolConfig(CONFIG_DEATH_BONES_WORLD))) &&
|
|
IsGridLoaded(corpse->GetPositionX(), corpse->GetPositionY()))
|
|
{
|
|
// Create bones, don't change Corpse
|
|
bones = new Corpse();
|
|
bones->Create(corpse->GetGUID().GetCounter());
|
|
|
|
for (uint8 i = OBJECT_FIELD_TYPE + 1; i < CORPSE_END; ++i) // don't overwrite guid and object type
|
|
bones->SetUInt32Value(i, corpse->GetUInt32Value(i));
|
|
|
|
bones->SetCellCoord(corpse->GetCellCoord());
|
|
bones->Relocate(corpse->GetPositionX(), corpse->GetPositionY(), corpse->GetPositionZ(), corpse->GetOrientation());
|
|
bones->SetPhaseMask(corpse->GetPhaseMask(), false);
|
|
|
|
bones->SetUInt32Value(CORPSE_FIELD_FLAGS, CORPSE_FLAG_UNK2 | CORPSE_FLAG_BONES);
|
|
bones->SetGuidValue(CORPSE_FIELD_OWNER, corpse->GetOwnerGUID());
|
|
|
|
for (uint8 i = 0; i < EQUIPMENT_SLOT_END; ++i)
|
|
if (corpse->GetUInt32Value(CORPSE_FIELD_ITEM + i))
|
|
bones->SetUInt32Value(CORPSE_FIELD_ITEM + i, 0);
|
|
|
|
AddCorpse(bones);
|
|
|
|
bones->UpdatePositionData();
|
|
|
|
// add bones in grid store if grid loaded where corpse placed
|
|
AddToMap(bones);
|
|
}
|
|
|
|
// all references to the corpse should be removed at this point
|
|
delete corpse;
|
|
|
|
return bones;
|
|
}
|
|
|
|
void Map::RemoveOldCorpses()
|
|
{
|
|
time_t now = GameTime::GetGameTime().count();
|
|
|
|
std::vector<ObjectGuid> corpses;
|
|
corpses.reserve(_corpsesByPlayer.size());
|
|
|
|
for (auto const& p : _corpsesByPlayer)
|
|
if (p.second->IsExpired(now))
|
|
corpses.push_back(p.first);
|
|
|
|
for (ObjectGuid const& ownerGuid : corpses)
|
|
ConvertCorpseToBones(ownerGuid);
|
|
|
|
std::vector<Corpse*> expiredBones;
|
|
for (Corpse* bones : _corpseBones)
|
|
if (bones->IsExpired(now))
|
|
expiredBones.push_back(bones);
|
|
|
|
for (Corpse* bones : expiredBones)
|
|
{
|
|
RemoveCorpse(bones);
|
|
delete bones;
|
|
}
|
|
}
|
|
|
|
void Map::ScheduleCreatureRespawn(ObjectGuid creatureGuid, Milliseconds respawnTimer, Position pos)
|
|
{
|
|
Events.AddEventAtOffset([this, creatureGuid, pos]()
|
|
{
|
|
if (Creature* creature = GetCreature(creatureGuid))
|
|
creature->Respawn();
|
|
else
|
|
SummonCreature(creatureGuid.GetEntry(), pos);
|
|
}, respawnTimer);
|
|
}
|
|
|
|
/// Send a packet to all players (or players selected team) in the zone (except self if mentioned)
|
|
bool Map::SendZoneMessage(uint32 zone, WorldPacket const* packet, WorldSession const* self, TeamId teamId) const
|
|
{
|
|
bool foundPlayerToSend = false;
|
|
|
|
for (MapReference const& ref : GetPlayers())
|
|
{
|
|
Player* player = ref.GetSource();
|
|
if (player->IsInWorld() &&
|
|
player->GetZoneId() == zone &&
|
|
player->GetSession() != self &&
|
|
(teamId == TEAM_NEUTRAL || player->GetTeamId() == teamId))
|
|
{
|
|
player->SendDirectMessage(packet);
|
|
foundPlayerToSend = true;
|
|
}
|
|
}
|
|
|
|
return foundPlayerToSend;
|
|
}
|
|
|
|
/// Send a System Message to all players in the zone (except self if mentioned)
|
|
void Map::SendZoneText(uint32 zoneId, char const* text, WorldSession const* self, TeamId teamId) const
|
|
{
|
|
WorldPacket data;
|
|
ChatHandler::BuildChatPacket(data, CHAT_MSG_SYSTEM, LANG_UNIVERSAL, nullptr, nullptr, text);
|
|
SendZoneMessage(zoneId, &data, self, teamId);
|
|
}
|
|
|
|
void Map::SendZoneDynamicInfo(uint32 zoneId, Player* player) const
|
|
{
|
|
ZoneDynamicInfoMap::const_iterator itr = _zoneDynamicInfo.find(zoneId);
|
|
if (itr == _zoneDynamicInfo.end())
|
|
return;
|
|
|
|
if (uint32 music = itr->second.MusicId)
|
|
player->SendDirectMessage(WorldPackets::Misc::PlayMusic(music).Write());
|
|
|
|
SendZoneWeather(itr->second, player);
|
|
|
|
if (uint32 overrideLight = itr->second.OverrideLightId)
|
|
{
|
|
WorldPacket data(SMSG_OVERRIDE_LIGHT, 4 + 4 + 4);
|
|
data << uint32(_defaultLight);
|
|
data << uint32(overrideLight);
|
|
data << uint32(itr->second.LightFadeInTime);
|
|
player->SendDirectMessage(&data);
|
|
}
|
|
}
|
|
|
|
void Map::SendZoneWeather(uint32 zoneId, Player* player) const
|
|
{
|
|
ZoneDynamicInfoMap::const_iterator itr = _zoneDynamicInfo.find(zoneId);
|
|
if (itr == _zoneDynamicInfo.end())
|
|
return;
|
|
|
|
SendZoneWeather(itr->second, player);
|
|
}
|
|
|
|
void Map::SendZoneWeather(ZoneDynamicInfo const& zoneDynamicInfo, Player* player) const
|
|
{
|
|
if (WeatherState weatherId = zoneDynamicInfo.WeatherId)
|
|
{
|
|
WorldPackets::Misc::Weather weather(weatherId, zoneDynamicInfo.WeatherGrade);
|
|
player->SendDirectMessage(weather.Write());
|
|
}
|
|
else if (zoneDynamicInfo.DefaultWeather)
|
|
zoneDynamicInfo.DefaultWeather->SendWeatherUpdateToPlayer(player);
|
|
else
|
|
Weather::SendFineWeatherUpdateToPlayer(player);
|
|
}
|
|
|
|
void Map::UpdateWeather(uint32 const diff)
|
|
{
|
|
_weatherUpdateTimer.Update(diff);
|
|
if (!_weatherUpdateTimer.Passed())
|
|
return;
|
|
|
|
for (auto&& zoneInfo : _zoneDynamicInfo)
|
|
if (zoneInfo.second.DefaultWeather && !zoneInfo.second.DefaultWeather->Update(_weatherUpdateTimer.GetInterval()))
|
|
zoneInfo.second.DefaultWeather.reset();
|
|
|
|
_weatherUpdateTimer.Reset();
|
|
}
|
|
|
|
void Map::PlayDirectSoundToMap(uint32 soundId, uint32 zoneId)
|
|
{
|
|
Map::PlayerList const& players = GetPlayers();
|
|
if (!players.IsEmpty())
|
|
{
|
|
WorldPacket data(SMSG_PLAY_SOUND, 4);
|
|
data << uint32(soundId);
|
|
|
|
for (Map::PlayerList::const_iterator itr = players.begin(); itr != players.end(); ++itr)
|
|
if (Player* player = itr->GetSource())
|
|
if (!zoneId || player->GetZoneId() == zoneId)
|
|
player->SendDirectMessage(&data);
|
|
}
|
|
}
|
|
|
|
void Map::SetZoneMusic(uint32 zoneId, uint32 musicId)
|
|
{
|
|
_zoneDynamicInfo[zoneId].MusicId = musicId;
|
|
|
|
WorldPackets::Misc::PlayMusic playMusic(musicId);
|
|
SendZoneMessage(zoneId, WorldPackets::Misc::PlayMusic(musicId).Write());
|
|
}
|
|
|
|
Weather* Map::GetOrGenerateZoneDefaultWeather(uint32 zoneId)
|
|
{
|
|
WeatherData const* weatherData = WeatherMgr::GetWeatherData(zoneId);
|
|
if (!weatherData)
|
|
return nullptr;
|
|
|
|
ZoneDynamicInfo& info = _zoneDynamicInfo[zoneId];
|
|
|
|
if (!info.DefaultWeather)
|
|
{
|
|
info.DefaultWeather = std::make_unique<Weather>(this, zoneId, weatherData);
|
|
info.DefaultWeather->ReGenerate();
|
|
info.DefaultWeather->UpdateWeather();
|
|
}
|
|
|
|
return info.DefaultWeather.get();
|
|
}
|
|
|
|
void Map::SetZoneWeather(uint32 zoneId, WeatherState weatherId, float weatherGrade)
|
|
{
|
|
ZoneDynamicInfo& info = _zoneDynamicInfo[zoneId];
|
|
info.WeatherId = weatherId;
|
|
info.WeatherGrade = weatherGrade;
|
|
|
|
SendZoneMessage(zoneId, WorldPackets::Misc::Weather(weatherId, weatherGrade).Write());
|
|
}
|
|
|
|
void Map::SetZoneOverrideLight(uint32 zoneId, uint32 lightId, Milliseconds fadeInTime)
|
|
{
|
|
ZoneDynamicInfo& info = _zoneDynamicInfo[zoneId];
|
|
info.OverrideLightId = lightId;
|
|
info.LightFadeInTime = static_cast<uint32>(fadeInTime.count());
|
|
|
|
WorldPacket data(SMSG_OVERRIDE_LIGHT, 4 + 4 + 4);
|
|
data << uint32(_defaultLight);
|
|
data << uint32(lightId);
|
|
data << uint32(static_cast<uint32>(fadeInTime.count()));
|
|
SendZoneMessage(zoneId, &data);
|
|
}
|
|
|
|
void Map::DoForAllPlayers(std::function<void(Player*)> exec)
|
|
{
|
|
for (auto const& it : GetPlayers())
|
|
{
|
|
if (Player* player = it.GetSource())
|
|
{
|
|
exec(player);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Check if a given source can reach a specific point following a path
|
|
* and normalize the coords. Use this method for long paths, otherwise use the
|
|
* overloaded method with the start coords when you need to do a quick check on small segments
|
|
*
|
|
*/
|
|
bool Map::CanReachPositionAndGetValidCoords(WorldObject const* source, PathGenerator *path, float &destX, float &destY, float &destZ, bool failOnCollision, bool failOnSlopes) const
|
|
{
|
|
G3D::Vector3 prevPath = path->GetStartPosition();
|
|
for (auto& vector : path->GetPath())
|
|
{
|
|
float x = vector.x;
|
|
float y = vector.y;
|
|
float z = vector.z;
|
|
|
|
if (!CanReachPositionAndGetValidCoords(source, prevPath.x, prevPath.y, prevPath.z, x, y, z, failOnCollision, failOnSlopes))
|
|
{
|
|
destX = x;
|
|
destY = y;
|
|
destZ = z;
|
|
return false;
|
|
}
|
|
|
|
prevPath = vector;
|
|
}
|
|
|
|
destX = prevPath.x;
|
|
destY = prevPath.y;
|
|
destZ = prevPath.z;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief validate the new destination and set reachable coords
|
|
* Check if a given unit can reach a specific point on a segment
|
|
* and set the correct dest coords
|
|
* NOTE: use this method with small segments.
|
|
*
|
|
* @param failOnCollision if true, the methods will return false when a collision occurs
|
|
* @param failOnSlopes if true, the methods will return false when a non walkable slope is found
|
|
*
|
|
* @return true if the destination is valid, false otherwise
|
|
*
|
|
**/
|
|
|
|
bool Map::CanReachPositionAndGetValidCoords(WorldObject const* source, float& destX, float& destY, float& destZ, bool failOnCollision, bool failOnSlopes) const
|
|
{
|
|
return CanReachPositionAndGetValidCoords(source, source->GetPositionX(), source->GetPositionY(), source->GetPositionZ(), destX, destY, destZ, failOnCollision, failOnSlopes);
|
|
}
|
|
|
|
bool Map::CanReachPositionAndGetValidCoords(WorldObject const* source, float startX, float startY, float startZ, float &destX, float &destY, float &destZ, bool failOnCollision, bool failOnSlopes) const
|
|
{
|
|
if (!CheckCollisionAndGetValidCoords(source, startX, startY, startZ, destX, destY, destZ, failOnCollision))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
Unit const* unit = source->ToUnit();
|
|
// if it's not an unit (Object) then we do not have to continue
|
|
// with walkable checks
|
|
if (!unit)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Walkable checks
|
|
*/
|
|
bool isWaterNext = HasEnoughWater(unit, destX, destY, destZ);
|
|
Creature const* creature = unit->ToCreature();
|
|
bool cannotEnterWater = isWaterNext && (creature && !creature->CanEnterWater());
|
|
bool cannotWalkOrFly = !isWaterNext && !source->ToPlayer() && !unit->CanFly() && (creature && !creature->CanWalk());
|
|
if (cannotEnterWater || cannotWalkOrFly ||
|
|
(failOnSlopes && !PathGenerator::IsWalkableClimb(startX, startY, startZ, destX, destY, destZ, source->GetCollisionHeight())))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief validate the new destination and set coords
|
|
* Check if a given unit can face collisions in a specific segment
|
|
*
|
|
* @return true if the destination is valid, false otherwise
|
|
*
|
|
**/
|
|
bool Map::CheckCollisionAndGetValidCoords(WorldObject const* source, float startX, float startY, float startZ, float &destX, float &destY, float &destZ, bool failOnCollision) const
|
|
{
|
|
// Prevent invalid coordinates here, position is unchanged
|
|
if (!Acore::IsValidMapCoord(startX, startY, startZ) || !Acore::IsValidMapCoord(destX, destY, destZ))
|
|
{
|
|
LOG_FATAL("maps", "Map::CheckCollisionAndGetValidCoords invalid coordinates startX: {}, startY: {}, startZ: {}, destX: {}, destY: {}, destZ: {}", startX, startY, startZ, destX, destY, destZ);
|
|
return false;
|
|
}
|
|
|
|
bool isWaterNext = IsInWater(source->GetPhaseMask(), destX, destY, destZ, source->GetCollisionHeight());
|
|
|
|
PathGenerator path(source);
|
|
|
|
// Use a detour raycast to get our first collision point
|
|
path.SetUseRaycast(true);
|
|
bool result = path.CalculatePath(startX, startY, startZ, destX, destY, destZ, false);
|
|
|
|
Unit const* unit = source->ToUnit();
|
|
bool notOnGround = path.GetPathType() & PATHFIND_NOT_USING_PATH
|
|
|| isWaterNext || (unit && unit->IsFlying());
|
|
|
|
// Check for valid path types before we proceed
|
|
if (!result || (!notOnGround && path.GetPathType() & ~(PATHFIND_NORMAL | PATHFIND_SHORTCUT | PATHFIND_INCOMPLETE | PATHFIND_FARFROMPOLY_END)))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
G3D::Vector3 endPos = path.GetPath().back();
|
|
destX = endPos.x;
|
|
destY = endPos.y;
|
|
destZ = endPos.z;
|
|
|
|
// collision check
|
|
bool collided = false;
|
|
|
|
// check static LOS
|
|
float halfHeight = source->GetCollisionHeight() * 0.5f;
|
|
|
|
// Unit is not on the ground, check for potential collision via vmaps
|
|
if (notOnGround)
|
|
{
|
|
bool col = VMAP::VMapFactory::createOrGetVMapMgr()->GetObjectHitPos(source->GetMapId(),
|
|
startX, startY, startZ + halfHeight,
|
|
destX, destY, destZ + halfHeight,
|
|
destX, destY, destZ, -CONTACT_DISTANCE);
|
|
|
|
destZ -= halfHeight;
|
|
|
|
// Collided with static LOS object, move back to collision point
|
|
if (col)
|
|
{
|
|
collided = true;
|
|
}
|
|
}
|
|
|
|
// check dynamic collision
|
|
bool col = source->GetMap()->GetObjectHitPos(source->GetPhaseMask(),
|
|
startX, startY, startZ + halfHeight,
|
|
destX, destY, destZ + halfHeight,
|
|
destX, destY, destZ, -CONTACT_DISTANCE);
|
|
|
|
destZ -= halfHeight;
|
|
|
|
// Collided with a gameobject, move back to collision point
|
|
if (col)
|
|
{
|
|
collided = true;
|
|
}
|
|
|
|
float groundZ = VMAP_INVALID_HEIGHT_VALUE;
|
|
source->UpdateAllowedPositionZ(destX, destY, destZ, &groundZ);
|
|
|
|
// position has no ground under it (or is too far away)
|
|
if (groundZ <= INVALID_HEIGHT && unit && !unit->CanFly())
|
|
{
|
|
// fall back to gridHeight if any
|
|
float gridHeight = GetGridHeight(destX, destY);
|
|
if (gridHeight > INVALID_HEIGHT)
|
|
{
|
|
destZ = gridHeight + unit->GetHoverHeight();
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return !failOnCollision || !collided;
|
|
}
|
|
|
|
void Map::LoadCorpseData()
|
|
{
|
|
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CORPSES);
|
|
stmt->SetData(0, GetId());
|
|
stmt->SetData(1, GetInstanceId());
|
|
|
|
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
|
// SELECT posX, posY, posZ, orientation, mapId, displayId, itemCache, bytes1, bytes2, guildId, flags, dynFlags, time, corpseType, instanceId, phaseMask, guid FROM corpse WHERE mapId = ? AND instanceId = ?
|
|
PreparedQueryResult result = CharacterDatabase.Query(stmt);
|
|
if (!result)
|
|
return;
|
|
|
|
do
|
|
{
|
|
Field* fields = result->Fetch();
|
|
CorpseType type = CorpseType(fields[13].Get<uint8>());
|
|
uint32 guid = fields[16].Get<uint32>();
|
|
if (type >= MAX_CORPSE_TYPE || type == CORPSE_BONES)
|
|
{
|
|
LOG_ERROR("maps", "Corpse (guid: {}) have wrong corpse type ({}), not loading.", guid, type);
|
|
continue;
|
|
}
|
|
|
|
Corpse* corpse = new Corpse(type);
|
|
|
|
if (!corpse->LoadCorpseFromDB(GenerateLowGuid<HighGuid::Corpse>(), fields))
|
|
{
|
|
delete corpse;
|
|
continue;
|
|
}
|
|
|
|
AddCorpse(corpse);
|
|
|
|
corpse->UpdatePositionData();
|
|
} while (result->NextRow());
|
|
}
|
|
|
|
void Map::DeleteCorpseData()
|
|
{
|
|
// DELETE FROM corpse WHERE mapId = ? AND instanceId = ?
|
|
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CORPSES_FROM_MAP);
|
|
stmt->SetData(0, GetId());
|
|
stmt->SetData(1, GetInstanceId());
|
|
CharacterDatabase.Execute(stmt);
|
|
}
|
|
|
|
std::string Map::GetDebugInfo() const
|
|
{
|
|
std::stringstream sstr;
|
|
sstr << std::boolalpha
|
|
<< "Id: " << GetId() << " InstanceId: " << GetInstanceId() << " Difficulty: " << std::to_string(GetDifficulty())
|
|
<< " HasPlayers: " << HavePlayers();
|
|
return sstr.str();
|
|
}
|
|
|
|
uint32 Map::GetCreatedGridsCount()
|
|
{
|
|
return _mapGridManager.GetCreatedGridsCount();
|
|
}
|
|
|
|
uint32 Map::GetLoadedGridsCount()
|
|
{
|
|
return _mapGridManager.GetLoadedGridsCount();
|
|
}
|
|
|
|
uint32 Map::GetCreatedCellsInGridCount(uint16 const x, uint16 const y)
|
|
{
|
|
return _mapGridManager.GetCreatedCellsInGridCount(x, y);
|
|
}
|
|
|
|
uint32 Map::GetCreatedCellsInMapCount()
|
|
{
|
|
return _mapGridManager.GetCreatedCellsInMapCount();
|
|
}
|
|
|
|
std::string InstanceMap::GetDebugInfo() const
|
|
{
|
|
std::stringstream sstr;
|
|
sstr << Map::GetDebugInfo() << "\n"
|
|
<< std::boolalpha
|
|
<< "ScriptId: " << GetScriptId() << " ScriptName: " << GetScriptName();
|
|
return sstr.str();
|
|
}
|