* Remove unnecessary slang Removes ZOMG! that is used in the src in logging and notes. Completely Unnecessary. Contributes to nothing useful. Inflates source. Not needed at all in any shape or form. Not Developer Lingo either. * Further Clean Up Removed Profanity * sensible grammar correction * More ZOMG! removal
3987 lines
130 KiB
C++
3987 lines
130 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 "Geometry.h"
|
|
#include "GridNotifiers.h"
|
|
#include "GridNotifiersImpl.h"
|
|
#include "Group.h"
|
|
#include "InstanceScript.h"
|
|
#include "LFGMgr.h"
|
|
#include "MapInstanced.h"
|
|
#include "Metric.h"
|
|
#include "Object.h"
|
|
#include "ObjectAccessor.h"
|
|
#include "ObjectGridLoader.h"
|
|
#include "ObjectMgr.h"
|
|
#include "Pet.h"
|
|
#include "ScriptMgr.h"
|
|
#include "Transport.h"
|
|
#include "VMapFactory.h"
|
|
#include "VMapMgr2.h"
|
|
#include "Vehicle.h"
|
|
|
|
#ifdef ELUNA
|
|
#include "LuaEngine.h"
|
|
#endif
|
|
|
|
union u_map_magic
|
|
{
|
|
char asChar[4];
|
|
uint32 asUInt;
|
|
};
|
|
|
|
u_map_magic MapMagic = { {'M', 'A', 'P', 'S'} };
|
|
uint32 MapVersionMagic = 8;
|
|
u_map_magic MapAreaMagic = { {'A', 'R', 'E', 'A'} };
|
|
u_map_magic MapHeightMagic = { {'M', 'H', 'G', 'T'} };
|
|
u_map_magic MapLiquidMagic = { {'M', 'L', 'I', 'Q'} };
|
|
|
|
static uint16 const holetab_h[4] = { 0x1111, 0x2222, 0x4444, 0x8888 };
|
|
static uint16 const holetab_v[4] = { 0x000F, 0x00F0, 0x0F00, 0xF000 };
|
|
|
|
Map::~Map()
|
|
{
|
|
// UnloadAll must be called before deleting the map
|
|
|
|
sScriptMgr->OnDestroyMap(this);
|
|
|
|
while (!i_worldObjects.empty())
|
|
{
|
|
WorldObject* obj = *i_worldObjects.begin();
|
|
ASSERT(obj->IsWorldObject());
|
|
LOG_DEBUG("maps", "Map::~Map: WorldObject TypeId is not a corpse! (%u)", static_cast<uint8>(obj->GetTypeId()));
|
|
//ASSERT(obj->GetTypeId() == TYPEID_CORPSE);
|
|
obj->RemoveFromWorld();
|
|
obj->ResetMap();
|
|
}
|
|
|
|
if (!m_scriptSchedule.empty())
|
|
sScriptMgr->DecreaseScheduledScriptCount(m_scriptSchedule.size());
|
|
|
|
//MMAP::MMapFactory::createOrGetMMapMgr()->unloadMap(GetId());
|
|
MMAP::MMapFactory::createOrGetMMapMgr()->unloadMapInstance(GetId(), i_InstanceId);
|
|
}
|
|
|
|
bool Map::ExistMap(uint32 mapid, int gx, int gy)
|
|
{
|
|
int len = sWorld->GetDataPath().length() + strlen("maps/%03u%02u%02u.map") + 1;
|
|
char* tmp = new char[len];
|
|
snprintf(tmp, len, (char*)(sWorld->GetDataPath() + "maps/%03u%02u%02u.map").c_str(), mapid, gx, gy);
|
|
|
|
bool ret = false;
|
|
FILE* pf = fopen(tmp, "rb");
|
|
|
|
if (!pf)
|
|
LOG_ERROR("maps", "Map file '%s': does not exist!", tmp);
|
|
else
|
|
{
|
|
map_fileheader header;
|
|
if (fread(&header, sizeof(header), 1, pf) == 1)
|
|
{
|
|
if (header.mapMagic != MapMagic.asUInt || header.versionMagic != MapVersionMagic)
|
|
{
|
|
LOG_ERROR("maps", "Map file '%s' is from an incompatible map version (%.*u v%u), %.*s v%u is expected. Please pull your source, recompile tools and recreate maps using the updated mapextractor, then replace your old map files with new files.",
|
|
tmp, 4, header.mapMagic, header.versionMagic, 4, MapMagic.asChar, MapVersionMagic);
|
|
}
|
|
|
|
else
|
|
ret = true;
|
|
}
|
|
fclose(pf);
|
|
}
|
|
delete [] tmp;
|
|
return ret;
|
|
}
|
|
|
|
bool Map::ExistVMap(uint32 mapid, int gx, int gy)
|
|
{
|
|
if (VMAP::IVMapMgr* vmgr = VMAP::VMapFactory::createOrGetVMapMgr())
|
|
{
|
|
if (vmgr->isMapLoadingEnabled())
|
|
{
|
|
bool exists = vmgr->existsMap((sWorld->GetDataPath() + "vmaps").c_str(), mapid, gx, gy);
|
|
if (!exists)
|
|
{
|
|
std::string name = vmgr->getDirFileName(mapid, gx, gy);
|
|
LOG_ERROR("maps", "VMap file '%s' is missing or points to wrong version of vmap file. Redo vmaps with latest version of vmap_assembler.exe.", (sWorld->GetDataPath() + "vmaps/" + name).c_str());
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Map::LoadMMap(int gx, int gy)
|
|
{
|
|
if (!DisableMgr::IsPathfindingEnabled(this)) // pussywizard
|
|
return;
|
|
|
|
int mmapLoadResult = MMAP::MMapFactory::createOrGetMMapMgr()->loadMap(GetId(), gx, gy);
|
|
switch (mmapLoadResult)
|
|
{
|
|
case MMAP::MMAP_LOAD_RESULT_OK:
|
|
LOG_DEBUG("maps", "MMAP loaded name:%s, id:%d, x:%d, y:%d (vmap rep.: x:%d, y:%d)", GetMapName(), GetId(), gx, gy, gx, gy);
|
|
break;
|
|
case MMAP::MMAP_LOAD_RESULT_ERROR:
|
|
LOG_DEBUG("maps", "Could not load MMAP name:%s, id:%d, x:%d, y:%d (vmap rep.: x:%d, y:%d)", GetMapName(), GetId(), gx, gy, gx, gy);
|
|
break;
|
|
case MMAP::MMAP_LOAD_RESULT_IGNORED:
|
|
LOG_DEBUG("maps", "Ignored MMAP name:%s, id:%d, x:%d, y:%d (vmap rep.: x:%d, y:%d)", GetMapName(), GetId(), gx, gy, gx, gy);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Map::LoadVMap(int gx, int gy)
|
|
{
|
|
// x and y are swapped !!
|
|
int vmapLoadResult = VMAP::VMapFactory::createOrGetVMapMgr()->loadMap((sWorld->GetDataPath() + "vmaps").c_str(), GetId(), gx, gy);
|
|
switch (vmapLoadResult)
|
|
{
|
|
case VMAP::VMAP_LOAD_RESULT_OK:
|
|
LOG_DEBUG("maps", "VMAP loaded name:%s, id:%d, x:%d, y:%d (vmap rep.: x:%d, y:%d)", GetMapName(), GetId(), gx, gy, gx, gy);
|
|
break;
|
|
case VMAP::VMAP_LOAD_RESULT_ERROR:
|
|
LOG_DEBUG("maps", "Could not load VMAP name:%s, id:%d, x:%d, y:%d (vmap rep.: x:%d, y:%d)", GetMapName(), GetId(), gx, gy, gx, gy);
|
|
break;
|
|
case VMAP::VMAP_LOAD_RESULT_IGNORED:
|
|
LOG_DEBUG("maps", "Ignored VMAP name:%s, id:%d, x:%d, y:%d (vmap rep.: x:%d, y:%d)", GetMapName(), GetId(), gx, gy, gx, gy);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Map::LoadMap(int gx, int gy, bool reload)
|
|
{
|
|
if (i_InstanceId != 0)
|
|
{
|
|
if (GridMaps[gx][gy])
|
|
return;
|
|
|
|
// load grid map for base map
|
|
m_parentMap->EnsureGridCreated(GridCoord(63 - gx, 63 - gy));
|
|
|
|
GridMaps[gx][gy] = m_parentMap->GridMaps[gx][gy];
|
|
return;
|
|
}
|
|
|
|
if (GridMaps[gx][gy] && !reload)
|
|
return;
|
|
|
|
//map already load, delete it before reloading (Is it necessary? Do we really need the ability the reload maps during runtime?)
|
|
if (GridMaps[gx][gy])
|
|
{
|
|
LOG_DEBUG("maps", "Unloading previously loaded map %u before reloading.", GetId());
|
|
sScriptMgr->OnUnloadGridMap(this, GridMaps[gx][gy], gx, gy);
|
|
|
|
delete (GridMaps[gx][gy]);
|
|
GridMaps[gx][gy] = nullptr;
|
|
}
|
|
|
|
// map file name
|
|
char* tmp = nullptr;
|
|
int len = sWorld->GetDataPath().length() + strlen("maps/%03u%02u%02u.map") + 1;
|
|
tmp = new char[len];
|
|
snprintf(tmp, len, (char*)(sWorld->GetDataPath() + "maps/%03u%02u%02u.map").c_str(), GetId(), gx, gy);
|
|
LOG_DEBUG("maps", "Loading map %s", tmp);
|
|
// loading data
|
|
GridMaps[gx][gy] = new GridMap();
|
|
if (!GridMaps[gx][gy]->loadData(tmp))
|
|
{
|
|
LOG_ERROR("maps", "Error loading map file: \n %s\n", tmp);
|
|
}
|
|
delete [] tmp;
|
|
|
|
sScriptMgr->OnLoadGridMap(this, GridMaps[gx][gy], gx, gy);
|
|
}
|
|
|
|
void Map::LoadMapAndVMap(int gx, int gy)
|
|
{
|
|
LoadMap(gx, gy);
|
|
if (i_InstanceId == 0)
|
|
{
|
|
LoadVMap(gx, gy); // Only load the data for the base map
|
|
LoadMMap(gx, gy);
|
|
}
|
|
}
|
|
|
|
Map::Map(uint32 id, uint32 InstanceId, uint8 SpawnMode, Map* _parent) :
|
|
i_mapEntry(sMapStore.LookupEntry(id)), i_spawnMode(SpawnMode), i_InstanceId(InstanceId),
|
|
m_unloadTimer(0), m_VisibleDistance(DEFAULT_VISIBILITY_DISTANCE),
|
|
_instanceResetPeriod(0), m_activeNonPlayersIter(m_activeNonPlayers.end()),
|
|
_transportsUpdateIter(_transports.end()), i_scriptLock(false), _defaultLight(GetDefaultMapLight(id))
|
|
{
|
|
m_parentMap = (_parent ? _parent : this);
|
|
for (unsigned int idx = 0; idx < MAX_NUMBER_OF_GRIDS; ++idx)
|
|
{
|
|
for (unsigned int j = 0; j < MAX_NUMBER_OF_GRIDS; ++j)
|
|
{
|
|
//z code
|
|
GridMaps[idx][j] = nullptr;
|
|
setNGrid(nullptr, idx, j);
|
|
}
|
|
}
|
|
|
|
//lets initialize visibility distance for map
|
|
Map::InitVisibilityDistance();
|
|
|
|
sScriptMgr->OnCreateMap(this);
|
|
}
|
|
|
|
void Map::InitVisibilityDistance()
|
|
{
|
|
//init visibility for continents
|
|
m_VisibleDistance = World::GetMaxVisibleDistanceOnContinents();
|
|
|
|
switch (GetId())
|
|
{
|
|
case 609: // Scarlet Enclave (DK starting zone)
|
|
m_VisibleDistance = 125.0f;
|
|
break;
|
|
case 25: // 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)
|
|
{
|
|
NGridType* grid = getNGrid(cell.GridX(), cell.GridY());
|
|
if (obj->IsWorldObject())
|
|
grid->GetGridType(cell.CellX(), cell.CellY()).template AddWorldObject<T>(obj);
|
|
else
|
|
grid->GetGridType(cell.CellX(), cell.CellY()).template AddGridObject<T>(obj);
|
|
}
|
|
|
|
template<>
|
|
void Map::AddToGrid(Creature* obj, Cell const& cell)
|
|
{
|
|
NGridType* grid = getNGrid(cell.GridX(), cell.GridY());
|
|
if (obj->IsWorldObject())
|
|
grid->GetGridType(cell.CellX(), cell.CellY()).AddWorldObject(obj);
|
|
else
|
|
grid->GetGridType(cell.CellX(), cell.CellY()).AddGridObject(obj);
|
|
|
|
obj->SetCurrentCell(cell);
|
|
}
|
|
|
|
template<>
|
|
void Map::AddToGrid(GameObject* obj, Cell const& cell)
|
|
{
|
|
NGridType* grid = getNGrid(cell.GridX(), cell.GridY());
|
|
grid->GetGridType(cell.CellX(), cell.CellY()).AddGridObject(obj);
|
|
|
|
obj->SetCurrentCell(cell);
|
|
}
|
|
|
|
template<>
|
|
void Map::AddToGrid(DynamicObject* obj, Cell const& cell)
|
|
{
|
|
NGridType* grid = getNGrid(cell.GridX(), cell.GridY());
|
|
if (obj->IsWorldObject())
|
|
grid->GetGridType(cell.CellX(), cell.CellY()).AddWorldObject(obj);
|
|
else
|
|
grid->GetGridType(cell.CellX(), cell.CellY()).AddGridObject(obj);
|
|
|
|
obj->SetCurrentCell(cell);
|
|
}
|
|
|
|
template<>
|
|
void Map::AddToGrid(Corpse* obj, Cell const& cell)
|
|
{
|
|
NGridType* grid = getNGrid(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->isGridObjectDataLoaded())
|
|
{
|
|
if (obj->IsWorldObject())
|
|
grid->GetGridType(cell.CellX(), cell.CellY()).AddWorldObject(obj);
|
|
else
|
|
grid->GetGridType(cell.CellX(), cell.CellY()).AddGridObject(obj);
|
|
}
|
|
}
|
|
|
|
template<class T>
|
|
void Map::SwitchGridContainers(T* /*obj*/, bool /*on*/)
|
|
{
|
|
}
|
|
|
|
template<>
|
|
void Map::SwitchGridContainers(Creature* obj, bool on)
|
|
{
|
|
ASSERT(!obj->IsPermanentWorldObject());
|
|
CellCoord p = Acore::ComputeCellCoord(obj->GetPositionX(), obj->GetPositionY());
|
|
if (!p.IsCoordValid())
|
|
{
|
|
LOG_ERROR("maps", "Map::SwitchGridContainers: Object %s has invalid coordinates X:%f Y:%f grid cell [%u:%u]",
|
|
obj->GetGUID().ToString().c_str(), obj->GetPositionX(), obj->GetPositionY(), p.x_coord, p.y_coord);
|
|
return;
|
|
}
|
|
|
|
Cell cell(p);
|
|
if (!IsGridLoaded(GridCoord(cell.data.Part.grid_x, cell.data.Part.grid_y)))
|
|
return;
|
|
|
|
LOG_DEBUG("maps", "Switch object %s from grid[%u, %u] %d", obj->GetGUID().ToString().c_str(), cell.GridX(), cell.GridY(), on);
|
|
NGridType* ngrid = getNGrid(cell.GridX(), cell.GridY());
|
|
ASSERT(ngrid != nullptr);
|
|
|
|
GridType& grid = ngrid->GetGridType(cell.CellX(), cell.CellY());
|
|
|
|
obj->RemoveFromGrid(); //This step is not really necessary but we want to do ASSERT in remove/add
|
|
|
|
if (on)
|
|
{
|
|
grid.AddWorldObject(obj);
|
|
AddWorldObject(obj);
|
|
}
|
|
else
|
|
{
|
|
grid.AddGridObject(obj);
|
|
RemoveWorldObject(obj);
|
|
}
|
|
|
|
obj->m_isTempWorldObject = on;
|
|
}
|
|
|
|
template<>
|
|
void Map::SwitchGridContainers(GameObject* obj, bool on)
|
|
{
|
|
ASSERT(!obj->IsPermanentWorldObject());
|
|
CellCoord p = Acore::ComputeCellCoord(obj->GetPositionX(), obj->GetPositionY());
|
|
if (!p.IsCoordValid())
|
|
{
|
|
LOG_ERROR("maps", "Map::SwitchGridContainers: Object %s has invalid coordinates X:%f Y:%f grid cell [%u:%u]",
|
|
obj->GetGUID().ToString().c_str(), obj->GetPositionX(), obj->GetPositionY(), p.x_coord, p.y_coord);
|
|
return;
|
|
}
|
|
|
|
Cell cell(p);
|
|
if (!IsGridLoaded(GridCoord(cell.data.Part.grid_x, cell.data.Part.grid_y)))
|
|
return;
|
|
|
|
//TC_LOG_DEBUG(LOG_FILTER_MAPS, "Switch object %s from grid[%u, %u] %u", obj->GetGUID().ToString().c_str(), cell.data.Part.grid_x, cell.data.Part.grid_y, on);
|
|
NGridType* ngrid = getNGrid(cell.GridX(), cell.GridY());
|
|
ASSERT(ngrid != nullptr);
|
|
|
|
GridType& grid = ngrid->GetGridType(cell.CellX(), cell.CellY());
|
|
|
|
obj->RemoveFromGrid(); //This step is not really necessary but we want to do ASSERT in remove/add
|
|
|
|
if (on)
|
|
{
|
|
grid.AddWorldObject(obj);
|
|
AddWorldObject(obj);
|
|
}
|
|
else
|
|
{
|
|
grid.AddGridObject(obj);
|
|
RemoveWorldObject(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(const GridCoord& p)
|
|
{
|
|
if (getNGrid(p.x_coord, p.y_coord)) // pussywizard
|
|
return;
|
|
std::lock_guard<std::mutex> guard(GridLock);
|
|
EnsureGridCreated_i(p);
|
|
}
|
|
|
|
//Create NGrid so the object can be added to it
|
|
//But object data is not loaded here
|
|
void Map::EnsureGridCreated_i(const GridCoord& p)
|
|
{
|
|
if (!getNGrid(p.x_coord, p.y_coord))
|
|
{
|
|
// pussywizard: moved setNGrid to the end of the function
|
|
NGridType* ngt = new NGridType(p.x_coord * MAX_NUMBER_OF_GRIDS + p.y_coord, p.x_coord, p.y_coord);
|
|
|
|
// build a linkage between this map and NGridType
|
|
buildNGridLinkage(ngt); // pussywizard: getNGrid(x, y) changed to: ngt
|
|
|
|
//z coord
|
|
int gx = (MAX_NUMBER_OF_GRIDS - 1) - p.x_coord;
|
|
int gy = (MAX_NUMBER_OF_GRIDS - 1) - p.y_coord;
|
|
|
|
if (!GridMaps[gx][gy])
|
|
{
|
|
LoadMapAndVMap(gx, gy);
|
|
}
|
|
|
|
// pussywizard: moved here
|
|
setNGrid(ngt, p.x_coord, p.y_coord);
|
|
}
|
|
}
|
|
|
|
//Create NGrid and load the object data in it
|
|
bool Map::EnsureGridLoaded(const Cell& cell)
|
|
{
|
|
EnsureGridCreated(GridCoord(cell.GridX(), cell.GridY()));
|
|
NGridType* grid = getNGrid(cell.GridX(), cell.GridY());
|
|
|
|
ASSERT(grid != nullptr);
|
|
if (!isGridObjectDataLoaded(cell.GridX(), cell.GridY()))
|
|
{
|
|
//if (!isGridObjectDataLoaded(cell.GridX(), cell.GridY()))
|
|
//{
|
|
LOG_DEBUG("maps", "Loading grid[%u, %u] for map %u instance %u", cell.GridX(), cell.GridY(), GetId(), i_InstanceId);
|
|
|
|
setGridObjectDataLoaded(true, cell.GridX(), cell.GridY());
|
|
|
|
ObjectGridLoader loader(*grid, this, cell);
|
|
loader.LoadN();
|
|
|
|
Balance();
|
|
return true;
|
|
//}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void Map::LoadGrid(float x, float y)
|
|
{
|
|
EnsureGridLoaded(Cell(x, y));
|
|
}
|
|
|
|
void Map::LoadAllCells()
|
|
{
|
|
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);
|
|
}
|
|
|
|
bool Map::AddPlayerToMap(Player* player)
|
|
{
|
|
CellCoord cellCoord = Acore::ComputeCellCoord(player->GetPositionX(), player->GetPositionY());
|
|
if (!cellCoord.IsCoordValid())
|
|
{
|
|
LOG_ERROR("maps", "Map::Add: Player (%s) has invalid coordinates X:%f Y:%f grid cell [%u:%u]",
|
|
player->GetGUID().ToString().c_str(), player->GetPositionX(), player->GetPositionY(), cellCoord.x_coord, cellCoord.y_coord);
|
|
return false;
|
|
}
|
|
|
|
Cell cell(cellCoord);
|
|
EnsureGridLoaded(cell);
|
|
AddToGrid(player, cell);
|
|
|
|
// Check if we are adding to correct map
|
|
ASSERT (player->GetMap() == this);
|
|
player->SetMap(this);
|
|
player->AddToWorld();
|
|
|
|
SendInitTransports(player);
|
|
SendInitSelf(player);
|
|
SendZoneDynamicInfo(player);
|
|
|
|
player->m_clientGUIDs.clear();
|
|
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->UpdateObjectVisibility(true);
|
|
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 %s has invalid coordinates X:%f Y:%f grid cell [%u:%u]",
|
|
obj->GetGUID().ToString().c_str(), 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->GetTypeId() == TYPEID_GAMEOBJECT && 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);
|
|
|
|
if (obj->isActiveObject())
|
|
AddToActive(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->GetTypeId() == TYPEID_UNIT)
|
|
if (Vehicle* vehicle = obj->ToCreature()->GetVehicleKit())
|
|
vehicle->Reset();
|
|
return true;
|
|
}
|
|
|
|
template<>
|
|
bool Map::AddToMap(MotionTransport* 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 %s has invalid coordinates X:%f Y:%f grid cell [%u:%u]",
|
|
obj->GetGUID().ToString().c_str(), obj->GetPositionX(), obj->GetPositionY(), cellCoord.x_coord, cellCoord.y_coord);
|
|
return false; //Should delete object
|
|
}
|
|
|
|
Cell cell(cellCoord);
|
|
if (obj->isActiveObject())
|
|
EnsureGridLoaded(cell);
|
|
|
|
obj->AddToWorld();
|
|
|
|
if (obj->isActiveObject())
|
|
AddToActive(obj);
|
|
|
|
_transports.insert(obj);
|
|
|
|
// Broadcast creation to players
|
|
if (!GetPlayers().isEmpty())
|
|
{
|
|
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;
|
|
}
|
|
|
|
bool Map::IsGridLoaded(const GridCoord& p) const
|
|
{
|
|
return (getNGrid(p.x_coord, p.y_coord) && isGridObjectDataLoaded(p.x_coord, p.y_coord));
|
|
}
|
|
|
|
void Map::VisitNearbyCellsOfPlayer(Player* player, TypeContainerVisitor<Acore::ObjectUpdater, GridTypeMapContainer>& gridVisitor,
|
|
TypeContainerVisitor<Acore::ObjectUpdater, WorldTypeMapContainer>& worldVisitor,
|
|
TypeContainerVisitor<Acore::ObjectUpdater, GridTypeMapContainer>& largeGridVisitor,
|
|
TypeContainerVisitor<Acore::ObjectUpdater, WorldTypeMapContainer>& largeWorldVisitor)
|
|
{
|
|
// check for valid position
|
|
if (!player->IsPositionValid())
|
|
return;
|
|
|
|
// check normal grid activation range of the player
|
|
VisitNearbyCellsOf(player, gridVisitor, worldVisitor, largeGridVisitor, largeWorldVisitor);
|
|
|
|
// check maximum visibility distance for large creatures
|
|
CellArea area = Cell::CalculateCellArea(player->GetPositionX(), player->GetPositionY(), MAX_VISIBILITY_DISTANCE);
|
|
|
|
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
|
|
// don't visit the same cell twice
|
|
uint32 cell_id = (y * TOTAL_NUMBER_OF_CELLS_PER_MAP) + x;
|
|
if (isCellMarkedLarge(cell_id))
|
|
continue;
|
|
|
|
markCellLarge(cell_id);
|
|
CellCoord pair(x, y);
|
|
Cell cell(pair);
|
|
|
|
Visit(cell, largeGridVisitor);
|
|
Visit(cell, largeWorldVisitor);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Map::VisitNearbyCellsOf(WorldObject* obj, TypeContainerVisitor<Acore::ObjectUpdater, GridTypeMapContainer>& gridVisitor,
|
|
TypeContainerVisitor<Acore::ObjectUpdater, WorldTypeMapContainer>& worldVisitor,
|
|
TypeContainerVisitor<Acore::ObjectUpdater, GridTypeMapContainer>& largeGridVisitor,
|
|
TypeContainerVisitor<Acore::ObjectUpdater, WorldTypeMapContainer>& largeWorldVisitor)
|
|
{
|
|
// Check for valid position
|
|
if (!obj->IsPositionValid())
|
|
return;
|
|
|
|
if (obj->GetGridActivationRange() <= 0.0f) // pussywizard: gameobjects for example are on active lists, but range is equal to 0 (they just prevent grid unloading)
|
|
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
|
|
// don't visit the same cell twice
|
|
uint32 cell_id = (y * TOTAL_NUMBER_OF_CELLS_PER_MAP) + x;
|
|
if (isCellMarked(cell_id))
|
|
continue;
|
|
|
|
markCell(cell_id);
|
|
CellCoord pair(x, y);
|
|
Cell cell(pair);
|
|
//cell.SetNoCreate(); // in mmaps this is missing
|
|
|
|
Visit(cell, gridVisitor);
|
|
Visit(cell, worldVisitor);
|
|
|
|
if (!isCellMarkedLarge(cell_id))
|
|
{
|
|
markCellLarge(cell_id);
|
|
Visit(cell, largeGridVisitor);
|
|
Visit(cell, largeWorldVisitor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Map::Update(const uint32 t_diff, const uint32 s_diff, bool /*thread*/)
|
|
{
|
|
if (t_diff)
|
|
_dynamicTree.update(t_diff);
|
|
|
|
/// update worldsessions for existing players
|
|
for (m_mapRefIter = m_mapRefMgr.begin(); m_mapRefIter != m_mapRefMgr.end(); ++m_mapRefIter)
|
|
{
|
|
Player* player = m_mapRefIter->GetSource();
|
|
if (player && player->IsInWorld())
|
|
{
|
|
//player->Update(t_diff);
|
|
WorldSession* session = player->GetSession();
|
|
MapSessionFilter updater(session);
|
|
session->Update(s_diff, updater);
|
|
}
|
|
}
|
|
|
|
if (!t_diff)
|
|
{
|
|
for (m_mapRefIter = m_mapRefMgr.begin(); m_mapRefIter != m_mapRefMgr.end(); ++m_mapRefIter)
|
|
{
|
|
Player* player = m_mapRefIter->GetSource();
|
|
|
|
if (!player || !player->IsInWorld())
|
|
continue;
|
|
|
|
// update players at tick
|
|
player->Update(s_diff);
|
|
}
|
|
|
|
HandleDelayedVisibility();
|
|
return;
|
|
}
|
|
|
|
/// update active cells around players and active objects
|
|
resetMarkedCells();
|
|
resetMarkedCellsLarge();
|
|
|
|
Acore::ObjectUpdater updater(t_diff, false);
|
|
|
|
// for creature
|
|
TypeContainerVisitor<Acore::ObjectUpdater, GridTypeMapContainer > grid_object_update(updater);
|
|
// for pets
|
|
TypeContainerVisitor<Acore::ObjectUpdater, WorldTypeMapContainer > world_object_update(updater);
|
|
|
|
// for large creatures
|
|
Acore::ObjectUpdater largeObjectUpdater(t_diff, true);
|
|
TypeContainerVisitor<Acore::ObjectUpdater, GridTypeMapContainer > grid_large_object_update(largeObjectUpdater);
|
|
TypeContainerVisitor<Acore::ObjectUpdater, WorldTypeMapContainer > world_large_object_update(largeObjectUpdater);
|
|
|
|
// pussywizard: container for far creatures in combat with players
|
|
std::vector<Creature*> updateList;
|
|
updateList.reserve(10);
|
|
|
|
// non-player active objects, increasing iterator in the loop in case of object removal
|
|
for (m_activeNonPlayersIter = m_activeNonPlayers.begin(); m_activeNonPlayersIter != m_activeNonPlayers.end();)
|
|
{
|
|
WorldObject* obj = *m_activeNonPlayersIter;
|
|
++m_activeNonPlayersIter;
|
|
|
|
if (!obj || !obj->IsInWorld())
|
|
continue;
|
|
|
|
VisitNearbyCellsOf(obj, grid_object_update, world_object_update, grid_large_object_update, world_large_object_update);
|
|
}
|
|
|
|
// the player iterator is stored in the map object
|
|
// to make sure calls to Map::Remove don't invalidate it
|
|
for (m_mapRefIter = m_mapRefMgr.begin(); m_mapRefIter != m_mapRefMgr.end(); ++m_mapRefIter)
|
|
{
|
|
Player* player = m_mapRefIter->GetSource();
|
|
|
|
if (!player || !player->IsInWorld())
|
|
continue;
|
|
|
|
// update players at tick
|
|
player->Update(s_diff);
|
|
|
|
VisitNearbyCellsOfPlayer(player, grid_object_update, world_object_update, grid_large_object_update, world_large_object_update);
|
|
|
|
// If player is using far sight, visit that object too
|
|
if (WorldObject* viewPoint = player->GetViewpoint())
|
|
{
|
|
if (Creature* viewCreature = viewPoint->ToCreature())
|
|
{
|
|
VisitNearbyCellsOf(viewCreature, grid_object_update, world_object_update, grid_large_object_update, world_large_object_update);
|
|
}
|
|
else if (DynamicObject* viewObject = viewPoint->ToDynObject())
|
|
{
|
|
VisitNearbyCellsOf(viewObject, grid_object_update, world_object_update, grid_large_object_update, world_large_object_update);
|
|
}
|
|
}
|
|
|
|
// handle updates for creatures in combat with player and are more than X yards away
|
|
if (player->IsInCombat())
|
|
{
|
|
updateList.clear();
|
|
float rangeSq = player->GetGridActivationRange() - 1.0f;
|
|
rangeSq = rangeSq * rangeSq;
|
|
HostileReference* ref = player->getHostileRefMgr().getFirst();
|
|
while (ref)
|
|
{
|
|
if (Unit* unit = ref->GetSource()->GetOwner())
|
|
if (Creature* cre = unit->ToCreature())
|
|
if (cre->FindMap() == player->FindMap() && cre->GetExactDist2dSq(player) > rangeSq)
|
|
updateList.push_back(cre);
|
|
ref = ref->next();
|
|
}
|
|
for (std::vector<Creature*>::const_iterator itr = updateList.begin(); itr != updateList.end(); ++itr)
|
|
VisitNearbyCellsOf(*itr, grid_object_update, world_object_update, grid_large_object_update, world_large_object_update);
|
|
}
|
|
}
|
|
|
|
for (_transportsUpdateIter = _transports.begin(); _transportsUpdateIter != _transports.end();) // pussywizard: transports updated after VisitNearbyCellsOf, grids around are loaded, everything ok
|
|
{
|
|
MotionTransport* transport = *_transportsUpdateIter;
|
|
++_transportsUpdateIter;
|
|
|
|
if (!transport->IsInWorld())
|
|
continue;
|
|
|
|
transport->Update(t_diff);
|
|
}
|
|
|
|
SendObjectUpdates();
|
|
|
|
///- Process necessary scripts
|
|
if (!m_scriptSchedule.empty())
|
|
{
|
|
i_scriptLock = true;
|
|
ScriptsProcess();
|
|
i_scriptLock = false;
|
|
}
|
|
|
|
MoveAllCreaturesInMoveList();
|
|
MoveAllGameObjectsInMoveList();
|
|
MoveAllDynamicObjectsInMoveList();
|
|
|
|
HandleDelayedVisibility();
|
|
|
|
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::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)
|
|
{
|
|
player->getHostileRefMgr().deleteReferences(true); // pussywizard: multithreading crashfix
|
|
|
|
bool inWorld = player->IsInWorld();
|
|
player->RemoveFromWorld();
|
|
SendRemoveTransports(player);
|
|
|
|
if (!inWorld) // pussywizard: if was in world, RemoveFromWorld() called DestroyForNearbyPlayers()
|
|
player->DestroyForNearbyPlayers(); // pussywizard: previous player->UpdateObjectVisibility(true)
|
|
|
|
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)
|
|
{
|
|
bool inWorld = obj->IsInWorld() && obj->GetTypeId() >= TYPEID_UNIT && obj->GetTypeId() <= TYPEID_GAMEOBJECT;
|
|
obj->RemoveFromWorld();
|
|
|
|
if (obj->isActiveObject())
|
|
RemoveFromActive(obj);
|
|
|
|
if (!inWorld) // pussywizard: if was in world, RemoveFromWorld() called DestroyForNearbyPlayers()
|
|
obj->DestroyForNearbyPlayers(); // pussywizard: previous player->UpdateObjectVisibility()
|
|
|
|
obj->RemoveFromGrid();
|
|
|
|
obj->ResetMap();
|
|
|
|
if (remove)
|
|
DeleteFromWorld(obj);
|
|
}
|
|
|
|
template<>
|
|
void Map::RemoveFromMap(MotionTransport* obj, bool remove)
|
|
{
|
|
obj->RemoveFromWorld();
|
|
if (obj->isActiveObject())
|
|
RemoveFromActive(obj);
|
|
|
|
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();
|
|
|
|
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());
|
|
|
|
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());
|
|
|
|
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(NGridType& ngrid)
|
|
{
|
|
// pussywizard: UnloadGrid only done when whole map is unloaded, no need to worry about moving npcs between grids, etc.
|
|
|
|
const uint32 x = ngrid.getX();
|
|
const uint32 y = ngrid.getY();
|
|
|
|
{
|
|
ObjectGridCleaner worker;
|
|
TypeContainerVisitor<ObjectGridCleaner, GridTypeMapContainer> visitor(worker);
|
|
ngrid.VisitAllGrids(visitor);
|
|
}
|
|
|
|
RemoveAllObjectsInRemoveList();
|
|
|
|
{
|
|
ObjectGridUnloader worker;
|
|
TypeContainerVisitor<ObjectGridUnloader, GridTypeMapContainer> visitor(worker);
|
|
ngrid.VisitAllGrids(visitor);
|
|
}
|
|
|
|
ASSERT(i_objectsToRemove.empty());
|
|
|
|
delete &ngrid;
|
|
setNGrid(nullptr, x, y);
|
|
|
|
int gx = (MAX_NUMBER_OF_GRIDS - 1) - x;
|
|
int gy = (MAX_NUMBER_OF_GRIDS - 1) - y;
|
|
|
|
if (i_InstanceId == 0)
|
|
{
|
|
if (GridMaps[gx][gy])
|
|
{
|
|
GridMaps[gx][gy]->unloadData();
|
|
delete GridMaps[gx][gy];
|
|
}
|
|
// x and y are swapped
|
|
VMAP::VMapFactory::createOrGetVMapMgr()->unloadMap(GetId(), gx, gy);
|
|
MMAP::MMapFactory::createOrGetMMapMgr()->unloadMap(GetId(), gx, gy);
|
|
}
|
|
|
|
GridMaps[gx][gy] = nullptr;
|
|
|
|
LOG_DEBUG("maps", "Unloading grid[%u, %u] for map %u finished", x, y, 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 %s is still in map %u during unload, this should not happen!", player->GetName().c_str(), 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<NGridType>::iterator i = GridRefMgr<NGridType>::begin(); i != GridRefMgr<NGridType>::end();)
|
|
{
|
|
NGridType& 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();)
|
|
{
|
|
MotionTransport* transport = *itr;
|
|
++itr;
|
|
|
|
transport->RemoveFromWorld();
|
|
delete transport;
|
|
}
|
|
|
|
_transports.clear();
|
|
|
|
for (auto& cellCorpsePair : _corpsesByCell)
|
|
{
|
|
for (Corpse* corpse : cellCorpsePair.second)
|
|
{
|
|
corpse->RemoveFromWorld();
|
|
corpse->ResetMap();
|
|
delete corpse;
|
|
}
|
|
}
|
|
|
|
_corpsesByCell.clear();
|
|
_corpsesByPlayer.clear();
|
|
_corpseBones.clear();
|
|
}
|
|
|
|
// *****************************
|
|
// Grid function
|
|
// *****************************
|
|
GridMap::GridMap()
|
|
{
|
|
_flags = 0;
|
|
// Area data
|
|
_gridArea = 0;
|
|
_areaMap = nullptr;
|
|
// Height level data
|
|
_gridHeight = INVALID_HEIGHT;
|
|
_gridGetHeight = &GridMap::getHeightFromFlat;
|
|
_gridIntHeightMultiplier = 0;
|
|
m_V9 = nullptr;
|
|
m_V8 = nullptr;
|
|
_maxHeight = nullptr;
|
|
_minHeight = nullptr;
|
|
// Liquid data
|
|
_liquidType = 0;
|
|
_liquidOffX = 0;
|
|
_liquidOffY = 0;
|
|
_liquidWidth = 0;
|
|
_liquidHeight = 0;
|
|
_liquidLevel = INVALID_HEIGHT;
|
|
_liquidEntry = nullptr;
|
|
_liquidFlags = nullptr;
|
|
_liquidMap = nullptr;
|
|
_holes = nullptr;
|
|
}
|
|
|
|
GridMap::~GridMap()
|
|
{
|
|
unloadData();
|
|
}
|
|
|
|
bool GridMap::loadData(char* filename)
|
|
{
|
|
// Unload old data if exist
|
|
unloadData();
|
|
|
|
map_fileheader header;
|
|
// Not return error if file not found
|
|
FILE* in = fopen(filename, "rb");
|
|
if (!in)
|
|
return true;
|
|
|
|
if (fread(&header, sizeof(header), 1, in) != 1)
|
|
{
|
|
fclose(in);
|
|
return false;
|
|
}
|
|
|
|
if (header.mapMagic == MapMagic.asUInt && header.versionMagic == MapVersionMagic)
|
|
{
|
|
// loadup area data
|
|
if (header.areaMapOffset && !loadAreaData(in, header.areaMapOffset, header.areaMapSize))
|
|
{
|
|
LOG_ERROR("maps", "Error loading map area data\n");
|
|
fclose(in);
|
|
return false;
|
|
}
|
|
// loadup height data
|
|
if (header.heightMapOffset && !loadHeightData(in, header.heightMapOffset, header.heightMapSize))
|
|
{
|
|
LOG_ERROR("maps", "Error loading map height data\n");
|
|
fclose(in);
|
|
return false;
|
|
}
|
|
// loadup liquid data
|
|
if (header.liquidMapOffset && !loadLiquidData(in, header.liquidMapOffset, header.liquidMapSize))
|
|
{
|
|
LOG_ERROR("maps", "Error loading map liquids data\n");
|
|
fclose(in);
|
|
return false;
|
|
}
|
|
// loadup holes data (if any. check header.holesOffset)
|
|
if (header.holesSize && !loadHolesData(in, header.holesOffset, header.holesSize))
|
|
{
|
|
LOG_ERROR("maps", "Error loading map holes data\n");
|
|
fclose(in);
|
|
return false;
|
|
}
|
|
fclose(in);
|
|
return true;
|
|
}
|
|
LOG_ERROR("maps", "Map file '%s' is from an incompatible clientversion. Please recreate using the mapextractor.", filename);
|
|
fclose(in);
|
|
return false;
|
|
}
|
|
|
|
void GridMap::unloadData()
|
|
{
|
|
delete[] _areaMap;
|
|
delete[] m_V9;
|
|
delete[] m_V8;
|
|
delete[] _maxHeight;
|
|
delete[] _minHeight;
|
|
delete[] _liquidEntry;
|
|
delete[] _liquidFlags;
|
|
delete[] _liquidMap;
|
|
delete[] _holes;
|
|
_areaMap = nullptr;
|
|
m_V9 = nullptr;
|
|
m_V8 = nullptr;
|
|
_maxHeight = nullptr;
|
|
_minHeight = nullptr;
|
|
_liquidEntry = nullptr;
|
|
_liquidFlags = nullptr;
|
|
_liquidMap = nullptr;
|
|
_holes = nullptr;
|
|
_gridGetHeight = &GridMap::getHeightFromFlat;
|
|
}
|
|
|
|
bool GridMap::loadAreaData(FILE* in, uint32 offset, uint32 /*size*/)
|
|
{
|
|
map_areaHeader header;
|
|
fseek(in, offset, SEEK_SET);
|
|
|
|
if (fread(&header, sizeof(header), 1, in) != 1 || header.fourcc != MapAreaMagic.asUInt)
|
|
return false;
|
|
|
|
_gridArea = header.gridArea;
|
|
if (!(header.flags & MAP_AREA_NO_AREA))
|
|
{
|
|
_areaMap = new uint16 [16 * 16];
|
|
if (fread(_areaMap, sizeof(uint16), 16 * 16, in) != 16 * 16)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool GridMap::loadHeightData(FILE* in, uint32 offset, uint32 /*size*/)
|
|
{
|
|
map_heightHeader header;
|
|
fseek(in, offset, SEEK_SET);
|
|
|
|
if (fread(&header, sizeof(header), 1, in) != 1 || header.fourcc != MapHeightMagic.asUInt)
|
|
return false;
|
|
|
|
_gridHeight = header.gridHeight;
|
|
if (!(header.flags & MAP_HEIGHT_NO_HEIGHT))
|
|
{
|
|
if ((header.flags & MAP_HEIGHT_AS_INT16))
|
|
{
|
|
m_uint16_V9 = new uint16 [129 * 129];
|
|
m_uint16_V8 = new uint16 [128 * 128];
|
|
if (fread(m_uint16_V9, sizeof(uint16), 129 * 129, in) != 129 * 129 ||
|
|
fread(m_uint16_V8, sizeof(uint16), 128 * 128, in) != 128 * 128)
|
|
return false;
|
|
_gridIntHeightMultiplier = (header.gridMaxHeight - header.gridHeight) / 65535;
|
|
_gridGetHeight = &GridMap::getHeightFromUint16;
|
|
}
|
|
else if ((header.flags & MAP_HEIGHT_AS_INT8))
|
|
{
|
|
m_uint8_V9 = new uint8 [129 * 129];
|
|
m_uint8_V8 = new uint8 [128 * 128];
|
|
if (fread(m_uint8_V9, sizeof(uint8), 129 * 129, in) != 129 * 129 ||
|
|
fread(m_uint8_V8, sizeof(uint8), 128 * 128, in) != 128 * 128)
|
|
return false;
|
|
_gridIntHeightMultiplier = (header.gridMaxHeight - header.gridHeight) / 255;
|
|
_gridGetHeight = &GridMap::getHeightFromUint8;
|
|
}
|
|
else
|
|
{
|
|
m_V9 = new float [129 * 129];
|
|
m_V8 = new float [128 * 128];
|
|
if (fread(m_V9, sizeof(float), 129 * 129, in) != 129 * 129 ||
|
|
fread(m_V8, sizeof(float), 128 * 128, in) != 128 * 128)
|
|
return false;
|
|
_gridGetHeight = &GridMap::getHeightFromFloat;
|
|
}
|
|
}
|
|
else
|
|
_gridGetHeight = &GridMap::getHeightFromFlat;
|
|
|
|
if (header.flags & MAP_HEIGHT_HAS_FLIGHT_BOUNDS)
|
|
{
|
|
_maxHeight = new int16[3 * 3];
|
|
_minHeight = new int16[3 * 3];
|
|
if (fread(_maxHeight, sizeof(int16), 3 * 3, in) != 3 * 3 ||
|
|
fread(_minHeight, sizeof(int16), 3 * 3, in) != 3 * 3)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool GridMap::loadLiquidData(FILE* in, uint32 offset, uint32 /*size*/)
|
|
{
|
|
map_liquidHeader header;
|
|
fseek(in, offset, SEEK_SET);
|
|
|
|
if (fread(&header, sizeof(header), 1, in) != 1 || header.fourcc != MapLiquidMagic.asUInt)
|
|
return false;
|
|
|
|
_liquidType = header.liquidType;
|
|
_liquidOffX = header.offsetX;
|
|
_liquidOffY = header.offsetY;
|
|
_liquidWidth = header.width;
|
|
_liquidHeight = header.height;
|
|
_liquidLevel = header.liquidLevel;
|
|
|
|
if (!(header.flags & MAP_LIQUID_NO_TYPE))
|
|
{
|
|
_liquidEntry = new uint16[16 * 16];
|
|
if (fread(_liquidEntry, sizeof(uint16), 16 * 16, in) != 16 * 16)
|
|
return false;
|
|
|
|
_liquidFlags = new uint8[16 * 16];
|
|
if (fread(_liquidFlags, sizeof(uint8), 16 * 16, in) != 16 * 16)
|
|
return false;
|
|
}
|
|
if (!(header.flags & MAP_LIQUID_NO_HEIGHT))
|
|
{
|
|
_liquidMap = new float[uint32(_liquidWidth) * uint32(_liquidHeight)];
|
|
if (fread(_liquidMap, sizeof(float), _liquidWidth * _liquidHeight, in) != (uint32(_liquidWidth) * uint32(_liquidHeight)))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool GridMap::loadHolesData(FILE* in, uint32 offset, uint32 /*size*/)
|
|
{
|
|
if (fseek(in, offset, SEEK_SET) != 0)
|
|
return false;
|
|
|
|
_holes = new uint16[16 * 16];
|
|
if (fread(_holes, sizeof(uint16), 16 * 16, in) != 16 * 16)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
uint16 GridMap::getArea(float x, float y) const
|
|
{
|
|
if (!_areaMap)
|
|
return _gridArea;
|
|
|
|
x = 16 * (32 - x / SIZE_OF_GRIDS);
|
|
y = 16 * (32 - y / SIZE_OF_GRIDS);
|
|
int lx = (int)x & 15;
|
|
int ly = (int)y & 15;
|
|
return _areaMap[lx * 16 + ly];
|
|
}
|
|
|
|
float GridMap::getHeightFromFlat(float /*x*/, float /*y*/) const
|
|
{
|
|
return _gridHeight;
|
|
}
|
|
|
|
float GridMap::getHeightFromFloat(float x, float y) const
|
|
{
|
|
if (!m_V8 || !m_V9)
|
|
return _gridHeight;
|
|
|
|
x = MAP_RESOLUTION * (32 - x / SIZE_OF_GRIDS);
|
|
y = MAP_RESOLUTION * (32 - y / SIZE_OF_GRIDS);
|
|
|
|
int x_int = (int)x;
|
|
int y_int = (int)y;
|
|
x -= x_int;
|
|
y -= y_int;
|
|
x_int &= (MAP_RESOLUTION - 1);
|
|
y_int &= (MAP_RESOLUTION - 1);
|
|
|
|
if (isHole(x_int, y_int))
|
|
return INVALID_HEIGHT;
|
|
|
|
// Height stored as: h5 - its v8 grid, h1-h4 - its v9 grid
|
|
// +--------------> X
|
|
// | h1-------h2 Coordinates is:
|
|
// | | \ 1 / | h1 0, 0
|
|
// | | \ / | h2 0, 1
|
|
// | | 2 h5 3 | h3 1, 0
|
|
// | | / \ | h4 1, 1
|
|
// | | / 4 \ | h5 1/2, 1/2
|
|
// | h3-------h4
|
|
// V Y
|
|
// For find height need
|
|
// 1 - detect triangle
|
|
// 2 - solve linear equation from triangle points
|
|
// Calculate coefficients for solve h = a*x + b*y + c
|
|
|
|
float a, b, c;
|
|
// Select triangle:
|
|
if (x + y < 1)
|
|
{
|
|
if (x > y)
|
|
{
|
|
// 1 triangle (h1, h2, h5 points)
|
|
float h1 = m_V9[(x_int) * 129 + y_int];
|
|
float h2 = m_V9[(x_int + 1) * 129 + y_int];
|
|
float h5 = 2 * m_V8[x_int * 128 + y_int];
|
|
a = h2 - h1;
|
|
b = h5 - h1 - h2;
|
|
c = h1;
|
|
}
|
|
else
|
|
{
|
|
// 2 triangle (h1, h3, h5 points)
|
|
float h1 = m_V9[x_int * 129 + y_int ];
|
|
float h3 = m_V9[x_int * 129 + y_int + 1];
|
|
float h5 = 2 * m_V8[x_int * 128 + y_int];
|
|
a = h5 - h1 - h3;
|
|
b = h3 - h1;
|
|
c = h1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (x > y)
|
|
{
|
|
// 3 triangle (h2, h4, h5 points)
|
|
float h2 = m_V9[(x_int + 1) * 129 + y_int ];
|
|
float h4 = m_V9[(x_int + 1) * 129 + y_int + 1];
|
|
float h5 = 2 * m_V8[x_int * 128 + y_int];
|
|
a = h2 + h4 - h5;
|
|
b = h4 - h2;
|
|
c = h5 - h4;
|
|
}
|
|
else
|
|
{
|
|
// 4 triangle (h3, h4, h5 points)
|
|
float h3 = m_V9[(x_int) * 129 + y_int + 1];
|
|
float h4 = m_V9[(x_int + 1) * 129 + y_int + 1];
|
|
float h5 = 2 * m_V8[x_int * 128 + y_int];
|
|
a = h4 - h3;
|
|
b = h3 + h4 - h5;
|
|
c = h5 - h4;
|
|
}
|
|
}
|
|
// Calculate height
|
|
return a * x + b * y + c;
|
|
}
|
|
|
|
float GridMap::getHeightFromUint8(float x, float y) const
|
|
{
|
|
if (!m_uint8_V8 || !m_uint8_V9)
|
|
return _gridHeight;
|
|
|
|
x = MAP_RESOLUTION * (32 - x / SIZE_OF_GRIDS);
|
|
y = MAP_RESOLUTION * (32 - y / SIZE_OF_GRIDS);
|
|
|
|
int x_int = (int)x;
|
|
int y_int = (int)y;
|
|
x -= x_int;
|
|
y -= y_int;
|
|
x_int &= (MAP_RESOLUTION - 1);
|
|
y_int &= (MAP_RESOLUTION - 1);
|
|
|
|
if (isHole(x_int, y_int))
|
|
return INVALID_HEIGHT;
|
|
|
|
int32 a, b, c;
|
|
uint8* V9_h1_ptr = &m_uint8_V9[x_int * 128 + x_int + y_int];
|
|
if (x + y < 1)
|
|
{
|
|
if (x > y)
|
|
{
|
|
// 1 triangle (h1, h2, h5 points)
|
|
int32 h1 = V9_h1_ptr[ 0];
|
|
int32 h2 = V9_h1_ptr[129];
|
|
int32 h5 = 2 * m_uint8_V8[x_int * 128 + y_int];
|
|
a = h2 - h1;
|
|
b = h5 - h1 - h2;
|
|
c = h1;
|
|
}
|
|
else
|
|
{
|
|
// 2 triangle (h1, h3, h5 points)
|
|
int32 h1 = V9_h1_ptr[0];
|
|
int32 h3 = V9_h1_ptr[1];
|
|
int32 h5 = 2 * m_uint8_V8[x_int * 128 + y_int];
|
|
a = h5 - h1 - h3;
|
|
b = h3 - h1;
|
|
c = h1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (x > y)
|
|
{
|
|
// 3 triangle (h2, h4, h5 points)
|
|
int32 h2 = V9_h1_ptr[129];
|
|
int32 h4 = V9_h1_ptr[130];
|
|
int32 h5 = 2 * m_uint8_V8[x_int * 128 + y_int];
|
|
a = h2 + h4 - h5;
|
|
b = h4 - h2;
|
|
c = h5 - h4;
|
|
}
|
|
else
|
|
{
|
|
// 4 triangle (h3, h4, h5 points)
|
|
int32 h3 = V9_h1_ptr[ 1];
|
|
int32 h4 = V9_h1_ptr[130];
|
|
int32 h5 = 2 * m_uint8_V8[x_int * 128 + y_int];
|
|
a = h4 - h3;
|
|
b = h3 + h4 - h5;
|
|
c = h5 - h4;
|
|
}
|
|
}
|
|
// Calculate height
|
|
return (float)((a * x) + (b * y) + c) * _gridIntHeightMultiplier + _gridHeight;
|
|
}
|
|
|
|
float GridMap::getHeightFromUint16(float x, float y) const
|
|
{
|
|
if (!m_uint16_V8 || !m_uint16_V9)
|
|
return _gridHeight;
|
|
|
|
x = MAP_RESOLUTION * (32 - x / SIZE_OF_GRIDS);
|
|
y = MAP_RESOLUTION * (32 - y / SIZE_OF_GRIDS);
|
|
|
|
int x_int = (int)x;
|
|
int y_int = (int)y;
|
|
x -= x_int;
|
|
y -= y_int;
|
|
x_int &= (MAP_RESOLUTION - 1);
|
|
y_int &= (MAP_RESOLUTION - 1);
|
|
|
|
if (isHole(x_int, y_int))
|
|
return INVALID_HEIGHT;
|
|
|
|
int32 a, b, c;
|
|
uint16* V9_h1_ptr = &m_uint16_V9[x_int * 128 + x_int + y_int];
|
|
if (x + y < 1)
|
|
{
|
|
if (x > y)
|
|
{
|
|
// 1 triangle (h1, h2, h5 points)
|
|
int32 h1 = V9_h1_ptr[ 0];
|
|
int32 h2 = V9_h1_ptr[129];
|
|
int32 h5 = 2 * m_uint16_V8[x_int * 128 + y_int];
|
|
a = h2 - h1;
|
|
b = h5 - h1 - h2;
|
|
c = h1;
|
|
}
|
|
else
|
|
{
|
|
// 2 triangle (h1, h3, h5 points)
|
|
int32 h1 = V9_h1_ptr[0];
|
|
int32 h3 = V9_h1_ptr[1];
|
|
int32 h5 = 2 * m_uint16_V8[x_int * 128 + y_int];
|
|
a = h5 - h1 - h3;
|
|
b = h3 - h1;
|
|
c = h1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (x > y)
|
|
{
|
|
// 3 triangle (h2, h4, h5 points)
|
|
int32 h2 = V9_h1_ptr[129];
|
|
int32 h4 = V9_h1_ptr[130];
|
|
int32 h5 = 2 * m_uint16_V8[x_int * 128 + y_int];
|
|
a = h2 + h4 - h5;
|
|
b = h4 - h2;
|
|
c = h5 - h4;
|
|
}
|
|
else
|
|
{
|
|
// 4 triangle (h3, h4, h5 points)
|
|
int32 h3 = V9_h1_ptr[ 1];
|
|
int32 h4 = V9_h1_ptr[130];
|
|
int32 h5 = 2 * m_uint16_V8[x_int * 128 + y_int];
|
|
a = h4 - h3;
|
|
b = h3 + h4 - h5;
|
|
c = h5 - h4;
|
|
}
|
|
}
|
|
// Calculate height
|
|
return (float)((a * x) + (b * y) + c) * _gridIntHeightMultiplier + _gridHeight;
|
|
}
|
|
|
|
bool GridMap::isHole(int row, int col) const
|
|
{
|
|
if (!_holes)
|
|
return false;
|
|
|
|
int cellRow = row / 8; // 8 squares per cell
|
|
int cellCol = col / 8;
|
|
int holeRow = row % 8 / 2;
|
|
int holeCol = (col - (cellCol * 8)) / 2;
|
|
|
|
uint16 hole = _holes[cellRow * 16 + cellCol];
|
|
|
|
return (hole & holetab_h[holeCol] & holetab_v[holeRow]) != 0;
|
|
}
|
|
|
|
float GridMap::getMinHeight(float x, float y) const
|
|
{
|
|
if (!_minHeight)
|
|
return -500.0f;
|
|
|
|
static uint32 const indices[] =
|
|
{
|
|
3, 0, 4,
|
|
0, 1, 4,
|
|
1, 2, 4,
|
|
2, 5, 4,
|
|
5, 8, 4,
|
|
8, 7, 4,
|
|
7, 6, 4,
|
|
6, 3, 4
|
|
};
|
|
|
|
static float const boundGridCoords[] =
|
|
{
|
|
0.0f, 0.0f,
|
|
0.0f, -266.66666f,
|
|
0.0f, -533.33331f,
|
|
-266.66666f, 0.0f,
|
|
-266.66666f, -266.66666f,
|
|
-266.66666f, -533.33331f,
|
|
-533.33331f, 0.0f,
|
|
-533.33331f, -266.66666f,
|
|
-533.33331f, -533.33331f
|
|
};
|
|
|
|
Cell cell(x, y);
|
|
float gx = x - (int32(cell.GridX()) - CENTER_GRID_ID + 1) * SIZE_OF_GRIDS;
|
|
float gy = y - (int32(cell.GridY()) - CENTER_GRID_ID + 1) * SIZE_OF_GRIDS;
|
|
|
|
uint32 quarterIndex = 0;
|
|
if (cell.CellY() < MAX_NUMBER_OF_CELLS / 2)
|
|
{
|
|
if (cell.CellX() < MAX_NUMBER_OF_CELLS / 2)
|
|
{
|
|
quarterIndex = 4 + (gy > gx);
|
|
}
|
|
else
|
|
quarterIndex = 2 + ((-SIZE_OF_GRIDS - gx) > gy);
|
|
}
|
|
else if (cell.CellX() < MAX_NUMBER_OF_CELLS / 2)
|
|
{
|
|
quarterIndex = 6 + ((-SIZE_OF_GRIDS - gx) <= gy);
|
|
}
|
|
else
|
|
quarterIndex = gx > gy;
|
|
|
|
quarterIndex *= 3;
|
|
|
|
return G3D::Plane(
|
|
G3D::Vector3(boundGridCoords[indices[quarterIndex + 0] * 2 + 0], boundGridCoords[indices[quarterIndex + 0] * 2 + 1], _minHeight[indices[quarterIndex + 0]]),
|
|
G3D::Vector3(boundGridCoords[indices[quarterIndex + 1] * 2 + 0], boundGridCoords[indices[quarterIndex + 1] * 2 + 1], _minHeight[indices[quarterIndex + 1]]),
|
|
G3D::Vector3(boundGridCoords[indices[quarterIndex + 2] * 2 + 0], boundGridCoords[indices[quarterIndex + 2] * 2 + 1], _minHeight[indices[quarterIndex + 2]])
|
|
).distance(G3D::Vector3(gx, gy, 0.0f));
|
|
}
|
|
|
|
float GridMap::getLiquidLevel(float x, float y) const
|
|
{
|
|
if (!_liquidMap)
|
|
return _liquidLevel;
|
|
|
|
x = MAP_RESOLUTION * (32 - x / SIZE_OF_GRIDS);
|
|
y = MAP_RESOLUTION * (32 - y / SIZE_OF_GRIDS);
|
|
|
|
int cx_int = ((int)x & (MAP_RESOLUTION - 1)) - _liquidOffY;
|
|
int cy_int = ((int)y & (MAP_RESOLUTION - 1)) - _liquidOffX;
|
|
|
|
if (cx_int < 0 || cx_int >= _liquidHeight)
|
|
return INVALID_HEIGHT;
|
|
if (cy_int < 0 || cy_int >= _liquidWidth)
|
|
return INVALID_HEIGHT;
|
|
|
|
return _liquidMap[cx_int * _liquidWidth + cy_int];
|
|
}
|
|
|
|
// Get water state on map
|
|
inline LiquidData const GridMap::GetLiquidData(float x, float y, float z, float collisionHeight, uint8 ReqLiquidType) const
|
|
{
|
|
LiquidData liquidData;
|
|
|
|
// Check water type (if no water return)
|
|
if (_liquidType || _liquidFlags)
|
|
{
|
|
// Get cell
|
|
float cx = MAP_RESOLUTION * (32 - x / SIZE_OF_GRIDS);
|
|
float cy = MAP_RESOLUTION * (32 - y / SIZE_OF_GRIDS);
|
|
|
|
int x_int = (int) cx & (MAP_RESOLUTION - 1);
|
|
int y_int = (int) cy & (MAP_RESOLUTION - 1);
|
|
|
|
// Check water type in cell
|
|
int idx = (x_int >> 3) * 16 + (y_int >> 3);
|
|
uint8 type = _liquidFlags ? _liquidFlags[idx] : _liquidType;
|
|
uint32 entry = 0;
|
|
if (_liquidEntry)
|
|
{
|
|
if (LiquidTypeEntry const* liquidEntry = sLiquidTypeStore.LookupEntry(_liquidEntry[idx]))
|
|
{
|
|
entry = liquidEntry->Id;
|
|
type &= MAP_LIQUID_TYPE_DARK_WATER;
|
|
uint32 liqTypeIdx = liquidEntry->Type;
|
|
if (entry < 21)
|
|
{
|
|
if (AreaTableEntry const* area = sAreaTableStore.LookupEntry(getArea(x, y)))
|
|
{
|
|
uint32 overrideLiquid = area->LiquidTypeOverride[liquidEntry->Type];
|
|
if (!overrideLiquid && area->zone)
|
|
{
|
|
area = sAreaTableStore.LookupEntry(area->zone);
|
|
if (area)
|
|
overrideLiquid = area->LiquidTypeOverride[liquidEntry->Type];
|
|
}
|
|
|
|
if (LiquidTypeEntry const* liq = sLiquidTypeStore.LookupEntry(overrideLiquid))
|
|
{
|
|
entry = overrideLiquid;
|
|
liqTypeIdx = liq->Type;
|
|
}
|
|
}
|
|
}
|
|
|
|
type |= 1 << liqTypeIdx;
|
|
}
|
|
}
|
|
|
|
// Check req liquid type mask
|
|
if (type != 0 && (!ReqLiquidType || (ReqLiquidType & type) != 0))
|
|
{
|
|
// Check water level:
|
|
// Check water height map
|
|
int lx_int = x_int - _liquidOffY;
|
|
int ly_int = y_int - _liquidOffX;
|
|
if (lx_int >= 0 && lx_int < _liquidHeight && ly_int >= 0 && ly_int < _liquidWidth)
|
|
{
|
|
// Get water level
|
|
float liquid_level = _liquidMap ? _liquidMap[lx_int * _liquidWidth + ly_int] : _liquidLevel;
|
|
// Get ground level (sub 0.2 for fix some errors)
|
|
float ground_level = getHeight(x, y);
|
|
|
|
// Check water level and ground level
|
|
if (liquid_level >= ground_level && z >= ground_level - 2)
|
|
{
|
|
// All ok in water -> store data
|
|
liquidData.Entry = entry;
|
|
liquidData.Flags = type;
|
|
liquidData.Level = liquid_level;
|
|
liquidData.DepthLevel = ground_level;
|
|
|
|
// For speed check as int values
|
|
float delta = liquid_level - z;
|
|
|
|
if (delta > collisionHeight)
|
|
liquidData.Status = LIQUID_MAP_UNDER_WATER;
|
|
else if (delta > 0.2f)
|
|
liquidData.Status = LIQUID_MAP_IN_WATER;
|
|
else if (delta > -0.2f)
|
|
liquidData.Status = LIQUID_MAP_WATER_WALK;
|
|
else
|
|
liquidData.Status = LIQUID_MAP_ABOVE_WATER;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return liquidData;
|
|
}
|
|
|
|
GridMap* Map::GetGrid(float x, float y)
|
|
{
|
|
// half opt method
|
|
int gx = (int)(32 - x / SIZE_OF_GRIDS); //grid x
|
|
int gy = (int)(32 - y / SIZE_OF_GRIDS); //grid y
|
|
|
|
// ensure GridMap is loaded
|
|
EnsureGridCreated(GridCoord(63 - gx, 63 - gy));
|
|
|
|
return GridMaps[gx][gy];
|
|
}
|
|
|
|
float Map::GetWaterOrGroundLevel(uint32 phasemask, float x, float y, float z, float* ground /*= nullptr*/, bool /*swim = false*/, float collisionHeight) const
|
|
{
|
|
if (const_cast<Map*>(this)->GetGrid(x, y))
|
|
{
|
|
// 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);
|
|
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);
|
|
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 || fabs(mapHeight - z) > 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 (GridMap* gmap = const_cast<Map*>(this)->GetGrid(x, y))
|
|
return gmap->getHeight(x, y);
|
|
|
|
return VMAP_INVALID_HEIGHT_VALUE;
|
|
}
|
|
|
|
float Map::GetMinHeight(float x, float y) const
|
|
{
|
|
if (GridMap const* grid = const_cast<Map*>(this)->GetGrid(x, y))
|
|
return grid->getMinHeight(x, y);
|
|
|
|
return -500.0f;
|
|
}
|
|
|
|
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 (GridMap* gmap = const_cast<Map*>(this)->GetGrid(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 (GridMap* gmap = const_cast<Map*>(this)->GetGrid(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: %f ground: %f type: %u", 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() == 530 && 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;
|
|
if (delta > 0.2f)
|
|
liquidData.Status = LIQUID_MAP_IN_WATER;
|
|
if (delta > -0.2f)
|
|
liquidData.Status = LIQUID_MAP_WATER_WALK;
|
|
else
|
|
liquidData.Status = LIQUID_MAP_ABOVE_WATER;
|
|
}
|
|
|
|
if (useGridLiquid)
|
|
{
|
|
if (GridMap* gmap = const_cast<Map*>(this)->GetGrid(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() == 530 && 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)
|
|
{
|
|
GridMap* gmap = GetGrid(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() == 530 && 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.2f)
|
|
data.liquidInfo.Status = LIQUID_MAP_IN_WATER;
|
|
else if (delta > -0.2f)
|
|
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() == 530 && liquidEntry == 2)
|
|
liquidEntry = 15;
|
|
|
|
data.liquidInfo = gridLiquidData;
|
|
data.liquidInfo.Entry = liquidEntry;
|
|
}
|
|
}
|
|
}
|
|
|
|
float Map::GetWaterLevel(float x, float y) const
|
|
{
|
|
if (GridMap* gmap = const_cast<Map*>(this)->GetGrid(x, y))
|
|
return gmap->getLiquidLevel(x, y);
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
bool Map::isInLineOfSight(float x1, float y1, float z1, float x2, float y2, float z2, uint32 phasemask, LineOfSightChecks checks) const
|
|
{
|
|
if ((checks & LINEOFSIGHT_CHECK_VMAP) && !VMAP::VMapFactory::createOrGetVMapMgr()->isInLineOfSight(GetId(), x1, y1, z1, x2, y2, z2))
|
|
return false;
|
|
|
|
if (sWorld->getBoolConfig(CONFIG_CHECK_GOBJECT_LOS) && (checks & LINEOFSIGHT_CHECK_GOBJECT)
|
|
&& !_dynamicTree.isInLineOfSight(x1, y1, z1, x2, y2, z2, phasemask))
|
|
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);
|
|
return (liquidData.Status & MAP_LIQUID_STATUS_SWIMMING) != 0 && HasEnoughWater(searcher, liquidData);
|
|
}
|
|
|
|
bool Map::HasEnoughWater(WorldObject const* searcher, LiquidData const& liquidData) const
|
|
{
|
|
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 %s", player->GetGUID().ToString().c_str());
|
|
|
|
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 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);
|
|
|
|
WorldPacket packet;
|
|
data.BuildPacket(&packet);
|
|
player->GetSession()->SendPacket(&packet);
|
|
}
|
|
|
|
void Map::SendInitTransports(Player* player)
|
|
{
|
|
// 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);
|
|
|
|
WorldPacket packet;
|
|
transData.BuildPacket(&packet);
|
|
player->GetSession()->SendPacket(&packet);
|
|
}
|
|
|
|
void Map::SendRemoveTransports(Player* player)
|
|
{
|
|
// 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);
|
|
|
|
// pussywizard: remove static transports from client
|
|
for (GuidUnorderedSet::const_iterator it = player->m_clientGUIDs.begin(); it != player->m_clientGUIDs.end(); )
|
|
{
|
|
if ((*it).IsTransport())
|
|
{
|
|
transData.AddOutOfRangeGUID(*it);
|
|
it = player->m_clientGUIDs.erase(it);
|
|
}
|
|
else
|
|
++it;
|
|
}
|
|
|
|
WorldPacket packet;
|
|
transData.BuildPacket(&packet);
|
|
player->GetSession()->SendPacket(&packet);
|
|
}
|
|
|
|
inline void Map::setNGrid(NGridType* grid, uint32 x, uint32 y)
|
|
{
|
|
if (x >= MAX_NUMBER_OF_GRIDS || y >= MAX_NUMBER_OF_GRIDS)
|
|
{
|
|
LOG_ERROR("maps", "map::setNGrid() Invalid grid coordinates found: %d, %d!", x, y);
|
|
ABORT();
|
|
}
|
|
i_grids[x][y] = grid;
|
|
}
|
|
|
|
void Map::SendObjectUpdates()
|
|
{
|
|
UpdateDataMapType update_players;
|
|
UpdatePlayerSet player_set;
|
|
|
|
while (!_updateObjects.empty())
|
|
{
|
|
Object* obj = *_updateObjects.begin();
|
|
ASSERT(obj->IsInWorld());
|
|
|
|
_updateObjects.erase(_updateObjects.begin());
|
|
obj->BuildUpdate(update_players, player_set);
|
|
}
|
|
|
|
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->GetSession()->SendPacket(&packet);
|
|
packet.clear(); // clean the string
|
|
}
|
|
}
|
|
|
|
void Map::DelayedUpdate(const uint32 t_diff)
|
|
{
|
|
for (_transportsUpdateIter = _transports.begin(); _transportsUpdateIter != _transports.end();)
|
|
{
|
|
MotionTransport* 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 (%s) added to removing list.", obj->GetGUID().ToString().c_str());
|
|
}
|
|
|
|
void Map::AddObjectToSwitchList(WorldObject* obj, bool on)
|
|
{
|
|
ASSERT(obj->GetMapId() == GetId() && obj->GetInstanceId() == GetInstanceId());
|
|
// i_objectsToSwitch is iterated only in Map::RemoveAllObjectsInRemoveList() and it uses
|
|
// the contained objects only if GetTypeId() == TYPEID_UNIT , so we can return in all other cases
|
|
if (obj->GetTypeId() != TYPEID_UNIT && obj->GetTypeId() != TYPEID_GAMEOBJECT)
|
|
return;
|
|
|
|
std::map<WorldObject*, bool>::iterator itr = i_objectsToSwitch.find(obj);
|
|
if (itr == i_objectsToSwitch.end())
|
|
i_objectsToSwitch.insert(itr, std::make_pair(obj, on));
|
|
else if (itr->second != on)
|
|
i_objectsToSwitch.erase(itr);
|
|
else
|
|
ABORT();
|
|
}
|
|
|
|
void Map::RemoveAllObjectsInRemoveList()
|
|
{
|
|
while (!i_objectsToSwitch.empty())
|
|
{
|
|
std::map<WorldObject*, bool>::iterator itr = i_objectsToSwitch.begin();
|
|
WorldObject* obj = itr->first;
|
|
bool on = itr->second;
|
|
i_objectsToSwitch.erase(itr);
|
|
|
|
if (!obj->IsPermanentWorldObject())
|
|
{
|
|
switch (obj->GetTypeId())
|
|
{
|
|
case TYPEID_UNIT:
|
|
SwitchGridContainers<Creature>(obj->ToCreature(), on);
|
|
break;
|
|
case TYPEID_GAMEOBJECT:
|
|
SwitchGridContainers<GameObject>(obj->ToGameObject(), on);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//LOG_DEBUG("maps", "Object remover 1 check.");
|
|
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 %s that is not in map.", obj->GetGUID().ToString().c_str());
|
|
else
|
|
RemoveFromMap(corpse, true);
|
|
break;
|
|
}
|
|
case TYPEID_DYNAMICOBJECT:
|
|
RemoveFromMap((DynamicObject*)obj, true);
|
|
break;
|
|
case TYPEID_GAMEOBJECT:
|
|
if (MotionTransport* transport = obj->ToGameObject()->ToMotionTransport())
|
|
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: %u) is in grid object remove list, ignored.", obj->GetTypeId());
|
|
break;
|
|
}
|
|
}
|
|
|
|
//LOG_DEBUG("maps", "Object remover 2 check.");
|
|
}
|
|
|
|
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()->GetSession()->SendPacket(data);
|
|
}
|
|
|
|
template<class T>
|
|
void Map::AddToActive(T* obj)
|
|
{
|
|
AddToActiveHelper(obj);
|
|
}
|
|
|
|
template <>
|
|
void Map::AddToActive(Creature* c)
|
|
{
|
|
AddToActiveHelper(c);
|
|
}
|
|
|
|
template<>
|
|
void Map::AddToActive(DynamicObject* d)
|
|
{
|
|
AddToActiveHelper(d);
|
|
}
|
|
|
|
template<>
|
|
void Map::AddToActive(GameObject* d)
|
|
{
|
|
AddToActiveHelper(d);
|
|
}
|
|
|
|
template<class T>
|
|
void Map::RemoveFromActive(T* obj)
|
|
{
|
|
RemoveFromActiveHelper(obj);
|
|
}
|
|
|
|
template <>
|
|
void Map::RemoveFromActive(Creature* c)
|
|
{
|
|
RemoveFromActiveHelper(c);
|
|
}
|
|
|
|
template<>
|
|
void Map::RemoveFromActive(DynamicObject* obj)
|
|
{
|
|
RemoveFromActiveHelper(obj);
|
|
}
|
|
|
|
template<>
|
|
void Map::RemoveFromActive(GameObject* obj)
|
|
{
|
|
RemoveFromActiveHelper(obj);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
/*
|
|
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 %s (%s) already in map %d, %d, %d!",
|
|
player->GetName().c_str(), player->GetGUID().ToString().c_str(), 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 '%u' of map '%s' cannot have more than '%u' players. Player '%s' rejected", GetInstanceId(), GetMapName(), maxPlayers, player->GetName().c_str());
|
|
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 %d spawnmode %d with instance id %d", 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 %s (%s) is permanently bound to instance %d, %d, %d, %d but he is being put into instance %d, %d, %d, %d",
|
|
player->GetName().c_str(), player->GetGUID().ToString().c_str(), playerBind->save->GetMapId(), playerBind->save->GetInstanceId(), playerBind->save->GetDifficulty(),
|
|
playerBind->save->CanReset(), mapSave->GetMapId(), mapSave->GetInstanceId(), mapSave->GetDifficulty(), mapSave->CanReset());
|
|
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(), time(nullptr));
|
|
|
|
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->GetSession()->SendPacket(&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)
|
|
{
|
|
// 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);
|
|
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 != nullptr)
|
|
return;
|
|
#ifdef ELUNA
|
|
bool isElunaAI = false;
|
|
instance_data = sEluna->GetInstanceData(this);
|
|
if (instance_data)
|
|
isElunaAI = true;
|
|
|
|
// if Eluna AI was fetched succesfully we should not call CreateInstanceData nor set the unused scriptID
|
|
if (!isElunaAI)
|
|
{
|
|
#endif
|
|
InstanceTemplate const* mInstance = sObjectMgr->GetInstanceTemplate(GetId());
|
|
if (mInstance)
|
|
{
|
|
i_script_id = mInstance->ScriptId;
|
|
instance_data = sScriptMgr->CreateInstanceScript(this);
|
|
}
|
|
#ifdef ELUNA
|
|
}
|
|
#endif
|
|
|
|
if (!instance_data)
|
|
return;
|
|
|
|
#ifdef ELUNA
|
|
// use mangos behavior if we are dealing with Eluna AI
|
|
// initialize should then be called only if load is false
|
|
if (!isElunaAI || !load)
|
|
#endif
|
|
instance_data->Initialize();
|
|
|
|
if (load)
|
|
{
|
|
instance_data->SetCompletedEncountersMask(completedEncounterMask, false);
|
|
if (data != "")
|
|
instance_data->Load(data.c_str());
|
|
}
|
|
}
|
|
|
|
/*
|
|
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: %s, Entry: %u, InstanceId: %u)!", 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->GetSession()->SendPacket(&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 %s is already in map!", player->GetGUID().ToString().c_str());
|
|
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 _objectsStore.Find<Pet>(guid);
|
|
}
|
|
|
|
Transport* Map::GetTransport(ObjectGuid 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 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 = time(nullptr);
|
|
if (GetInstanceResetPeriod() > 0 && respawnTime - now + 5 >= GetInstanceResetPeriod())
|
|
respawnTime = now + YEAR;
|
|
|
|
_creatureRespawnTimes[spawnId] = respawnTime;
|
|
|
|
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_CREATURE_RESPAWN);
|
|
stmt->setUInt32(0, spawnId);
|
|
stmt->setUInt32(1, uint32(respawnTime));
|
|
stmt->setUInt16(2, GetId());
|
|
stmt->setUInt32(3, GetInstanceId());
|
|
CharacterDatabase.Execute(stmt);
|
|
}
|
|
|
|
void Map::RemoveCreatureRespawnTime(ObjectGuid::LowType spawnId)
|
|
{
|
|
_creatureRespawnTimes.erase(spawnId);
|
|
|
|
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CREATURE_RESPAWN);
|
|
stmt->setUInt32(0, spawnId);
|
|
stmt->setUInt16(1, GetId());
|
|
stmt->setUInt32(2, GetInstanceId());
|
|
CharacterDatabase.Execute(stmt);
|
|
}
|
|
|
|
void Map::SaveGORespawnTime(ObjectGuid::LowType spawnId, time_t& respawnTime)
|
|
{
|
|
if (!respawnTime)
|
|
{
|
|
// Delete only
|
|
RemoveGORespawnTime(spawnId);
|
|
return;
|
|
}
|
|
|
|
time_t now = time(nullptr);
|
|
if (GetInstanceResetPeriod() > 0 && respawnTime - now + 5 >= GetInstanceResetPeriod())
|
|
respawnTime = now + YEAR;
|
|
|
|
_goRespawnTimes[spawnId] = respawnTime;
|
|
|
|
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_GO_RESPAWN);
|
|
stmt->setUInt32(0, spawnId);
|
|
stmt->setUInt32(1, uint32(respawnTime));
|
|
stmt->setUInt16(2, GetId());
|
|
stmt->setUInt32(3, GetInstanceId());
|
|
CharacterDatabase.Execute(stmt);
|
|
}
|
|
|
|
void Map::RemoveGORespawnTime(ObjectGuid::LowType spawnId)
|
|
{
|
|
_goRespawnTimes.erase(spawnId);
|
|
|
|
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GO_RESPAWN);
|
|
stmt->setUInt32(0, spawnId);
|
|
stmt->setUInt16(1, GetId());
|
|
stmt->setUInt32(2, GetInstanceId());
|
|
CharacterDatabase.Execute(stmt);
|
|
}
|
|
|
|
void Map::LoadRespawnTimes()
|
|
{
|
|
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CREATURE_RESPAWNS);
|
|
stmt->setUInt16(0, GetId());
|
|
stmt->setUInt32(1, GetInstanceId());
|
|
if (PreparedQueryResult result = CharacterDatabase.Query(stmt))
|
|
{
|
|
do
|
|
{
|
|
Field* fields = result->Fetch();
|
|
ObjectGuid::LowType lowguid = fields[0].GetUInt32();
|
|
uint32 respawnTime = fields[1].GetUInt32();
|
|
|
|
_creatureRespawnTimes[lowguid] = time_t(respawnTime);
|
|
} while (result->NextRow());
|
|
}
|
|
|
|
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_GO_RESPAWNS);
|
|
stmt->setUInt16(0, GetId());
|
|
stmt->setUInt32(1, GetInstanceId());
|
|
if (PreparedQueryResult result = CharacterDatabase.Query(stmt))
|
|
{
|
|
do
|
|
{
|
|
Field* fields = result->Fetch();
|
|
ObjectGuid::LowType lowguid = fields[0].GetUInt32();
|
|
uint32 respawnTime = fields[1].GetUInt32();
|
|
|
|
_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->setUInt16(0, mapId);
|
|
stmt->setUInt32(1, instanceId);
|
|
CharacterDatabase.Execute(stmt);
|
|
|
|
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GO_RESPAWN_BY_INSTANCE);
|
|
stmt->setUInt16(0, mapId);
|
|
stmt->setUInt32(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.PExecute("INSERT INTO log_encounter VALUES(NOW(), %u, %u, %u, %u, '%s')", GetId(), (uint32)GetDifficulty(), type, creditEntry, playersInfo.c_str());
|
|
}
|
|
|
|
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);
|
|
|
|
_corpsesByCell[corpse->GetCellCoord().GetId()].insert(corpse);
|
|
if (corpse->GetType() != CORPSE_BONES)
|
|
_corpsesByPlayer[corpse->GetOwnerGUID()] = corpse;
|
|
else
|
|
_corpseBones.insert(corpse);
|
|
}
|
|
|
|
void Map::RemoveCorpse(Corpse* corpse)
|
|
{
|
|
ASSERT(corpse);
|
|
|
|
corpse->DestroyForNearbyPlayers();
|
|
if (corpse->IsInGrid())
|
|
RemoveFromMap(corpse, false);
|
|
else
|
|
{
|
|
corpse->RemoveFromWorld();
|
|
corpse->ResetMap();
|
|
}
|
|
|
|
_corpsesByCell[corpse->GetCellCoord().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))) &&
|
|
!IsRemovalGrid(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, ObjectGuid::Empty);
|
|
|
|
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 = time(nullptr);
|
|
|
|
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::SendZoneDynamicInfo(Player* player)
|
|
{
|
|
uint32 zoneId = player->GetZoneId();
|
|
ZoneDynamicInfoMap::const_iterator itr = _zoneDynamicInfo.find(zoneId);
|
|
if (itr == _zoneDynamicInfo.end())
|
|
return;
|
|
|
|
if (uint32 music = itr->second.MusicId)
|
|
{
|
|
WorldPacket data(SMSG_PLAY_MUSIC, 4);
|
|
data << uint32(music);
|
|
player->SendDirectMessage(&data);
|
|
}
|
|
|
|
if (uint32 weather = itr->second.WeatherId)
|
|
{
|
|
WorldPacket data(SMSG_WEATHER, 4 + 4 + 1);
|
|
data << uint32(weather);
|
|
data << float(itr->second.WeatherGrade);
|
|
data << uint8(0);
|
|
player->SendDirectMessage(&data);
|
|
}
|
|
|
|
if (uint32 overrideLight = itr->second.OverrideLightId)
|
|
{
|
|
WorldPacket data(SMSG_OVERRIDE_LIGHT, 4 + 4 + 1);
|
|
data << uint32(_defaultLight);
|
|
data << uint32(overrideLight);
|
|
data << uint32(itr->second.LightFadeInTime);
|
|
player->SendDirectMessage(&data);
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
if (_zoneDynamicInfo.find(zoneId) == _zoneDynamicInfo.end())
|
|
_zoneDynamicInfo.insert(ZoneDynamicInfoMap::value_type(zoneId, ZoneDynamicInfo()));
|
|
|
|
_zoneDynamicInfo[zoneId].MusicId = musicId;
|
|
|
|
Map::PlayerList const& players = GetPlayers();
|
|
if (!players.isEmpty())
|
|
{
|
|
WorldPacket data(SMSG_PLAY_MUSIC, 4);
|
|
data << uint32(musicId);
|
|
|
|
for (Map::PlayerList::const_iterator itr = players.begin(); itr != players.end(); ++itr)
|
|
if (Player* player = itr->GetSource())
|
|
if (player->GetZoneId() == zoneId)
|
|
player->SendDirectMessage(&data);
|
|
}
|
|
}
|
|
|
|
void Map::SetZoneWeather(uint32 zoneId, uint32 weatherId, float weatherGrade)
|
|
{
|
|
if (_zoneDynamicInfo.find(zoneId) == _zoneDynamicInfo.end())
|
|
_zoneDynamicInfo.insert(ZoneDynamicInfoMap::value_type(zoneId, ZoneDynamicInfo()));
|
|
|
|
ZoneDynamicInfo& info = _zoneDynamicInfo[zoneId];
|
|
info.WeatherId = weatherId;
|
|
info.WeatherGrade = weatherGrade;
|
|
Map::PlayerList const& players = GetPlayers();
|
|
|
|
if (!players.isEmpty())
|
|
{
|
|
WorldPacket data(SMSG_WEATHER, 4 + 4 + 1);
|
|
data << uint32(weatherId);
|
|
data << float(weatherGrade);
|
|
data << uint8(0);
|
|
|
|
for (Map::PlayerList::const_iterator itr = players.begin(); itr != players.end(); ++itr)
|
|
if (Player* player = itr->GetSource())
|
|
if (player->GetZoneId() == zoneId)
|
|
player->SendDirectMessage(&data);
|
|
}
|
|
}
|
|
|
|
void Map::SetZoneOverrideLight(uint32 zoneId, uint32 lightId, Milliseconds fadeInTime)
|
|
{
|
|
if (_zoneDynamicInfo.find(zoneId) == _zoneDynamicInfo.end())
|
|
_zoneDynamicInfo.insert(ZoneDynamicInfoMap::value_type(zoneId, ZoneDynamicInfo()));
|
|
|
|
ZoneDynamicInfo& info = _zoneDynamicInfo[zoneId];
|
|
info.OverrideLightId = lightId;
|
|
info.LightFadeInTime = static_cast<uint32>(fadeInTime.count());
|
|
Map::PlayerList const& players = GetPlayers();
|
|
|
|
if (!players.isEmpty())
|
|
{
|
|
WorldPacket data(SMSG_OVERRIDE_LIGHT, 4 + 4 + 4);
|
|
data << uint32(_defaultLight);
|
|
data << uint32(lightId);
|
|
data << uint32(static_cast<uint32>(fadeInTime.count()));
|
|
|
|
for (Map::PlayerList::const_iterator itr = players.begin(); itr != players.end(); ++itr)
|
|
if (Player* player = itr->GetSource())
|
|
if (player->GetZoneId() == zoneId)
|
|
player->SendDirectMessage(&data);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @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(const WorldObject* 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(const WorldObject* 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(const WorldObject* 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;
|
|
}
|
|
|
|
const Unit* 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);
|
|
const Creature* 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(const WorldObject* 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: %f, startY: %f, startZ: %f, destX: %f, destY: %f, destZ: %f", 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);
|
|
|
|
const Unit* 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->setUInt32(0, GetId());
|
|
stmt->setUInt32(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].GetUInt8());
|
|
uint32 guid = fields[16].GetUInt32();
|
|
if (type >= MAX_CORPSE_TYPE || type == CORPSE_BONES)
|
|
{
|
|
LOG_ERROR("maps", "Corpse (guid: %u) have wrong corpse type (%u), 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->setUInt32(0, GetId());
|
|
stmt->setUInt32(1, GetInstanceId());
|
|
CharacterDatabase.Execute(stmt);
|
|
}
|