TrinityCore
Loading...
Searching...
No Matches
TerrainMgr.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "TerrainMgr.h"
19#include "DB2Stores.h"
20#include "DisableMgr.h"
21#include "DynamicTree.h"
22#include "GridMap.h"
23#include "Log.h"
24#include "Memory.h"
25#include "MMapManager.h"
26#include "PhasingHandler.h"
27#include "Random.h"
28#include "Util.h"
29#include "VMapFactory.h"
30#include "VMapManager.h"
31#include "World.h"
32#include <G3D/g3dmath.h>
33
34TerrainInfo::TerrainInfo(uint32 mapId) : _mapId(mapId), _parentTerrain(nullptr), _loadedGrids(), _cleanupTimer(randtime(CleanupInterval / 2, CleanupInterval))
35{
36}
37
43
44char const* TerrainInfo::GetMapName() const
45{
46 return sMapStore.AssertEntry(GetId())->MapName[sWorld->GetDefaultDbcLocale()];
47}
48
50{
51 std::string tileListName = Trinity::StringFormat("{}maps/{:04}.tilelist", sWorld->GetDataPath(), GetId());
52 // tile list is optional
53 if (auto tileList = Trinity::make_unique_ptr_with_deleter<&::fclose>(fopen(tileListName.c_str(), "rb")))
54 {
55 u_map_magic mapMagic = { };
56 uint32 versionMagic = { };
57 uint32 build;
58 char tilesData[MAX_NUMBER_OF_GRIDS * MAX_NUMBER_OF_GRIDS] = { };
59 if (fread(mapMagic.data(), mapMagic.size(), 1, tileList.get()) == 1
60 && mapMagic == MapMagic
61 && fread(&versionMagic, sizeof(versionMagic), 1, tileList.get()) == 1
62 && versionMagic == MapVersionMagic
63 && fread(&build, sizeof(build), 1, tileList.get()) == 1
64 && fread(&tilesData[0], MAX_NUMBER_OF_GRIDS * MAX_NUMBER_OF_GRIDS, 1, tileList.get()) == 1)
65 {
66 _gridFileExists = std::bitset<MAX_NUMBER_OF_GRIDS* MAX_NUMBER_OF_GRIDS>(tilesData, std::size(tilesData));
67 return;
68 }
69 }
70
71 for (int32 gx = 0; gx < MAX_NUMBER_OF_GRIDS; ++gx)
72 for (int32 gy = 0; gy < MAX_NUMBER_OF_GRIDS; ++gy)
73 _gridFileExists[GetBitsetIndex(gx, gy)] = ExistMap(GetId(), gx, gy, false);
74}
75
76bool TerrainInfo::ExistMap(uint32 mapid, int32 gx, int32 gy, bool log /*= true*/)
77{
78 std::string fileName = Trinity::StringFormat("{}maps/{:04}_{:02}_{:02}.map", sWorld->GetDataPath(), mapid, gx, gy);
79
80 bool ret = false;
81 auto file = Trinity::make_unique_ptr_with_deleter<&::fclose>(fopen(fileName.c_str(), "rb"));
82 if (!file)
83 {
84 if (log)
85 {
86 TC_LOG_ERROR("maps", "Map file '{}' does not exist!", fileName);
87 TC_LOG_ERROR("maps", "Please place MAP-files (*.map) in the appropriate directory ({}), or correct the DataDir setting in your worldserver.conf file.", (sWorld->GetDataPath() + "maps/"));
88 }
89 }
90 else
91 {
92 map_fileheader header{ };
93 if (fread(&header, sizeof(header), 1, file.get()) == 1)
94 {
95 if (header.mapMagic != MapMagic || header.versionMagic != MapVersionMagic)
96 {
97 if (log)
98 TC_LOG_ERROR("maps", "Map file '{}' is from an incompatible map version ({} v{}), {} v{} is expected. Please pull your source, recompile tools and recreate maps using the updated mapextractor, then replace your old map files with new files. If you still have problems search on forum for error TCE00018.",
99 fileName, std::string_view(header.mapMagic.data(), 4), header.versionMagic, std::string_view(MapMagic.data(), 4), MapVersionMagic);
100 }
101 else
102 ret = true;
103 }
104 }
105
106 return ret;
107}
108
110{
112 {
113 if (vmgr->isMapLoadingEnabled())
114 {
115 VMAP::LoadResult result = vmgr->existsMap(sWorld->GetDataPath() + "vmaps", mapid, gx, gy);
116 std::string name = VMAP::VMapManager::getDirFileName(mapid, gx, gy);
117 switch (result)
118 {
120 break;
122 TC_LOG_ERROR("maps", "VMap file '{}vmaps/{}' does not exist", sWorld->GetDataPath(), name);
123 TC_LOG_ERROR("maps", "Please place VMAP files (*.vmtree and *.vmtile) in the vmap directory ({}), or correct the DataDir setting in your worldserver.conf file.", (sWorld->GetDataPath() + "vmaps/"));
124 return false;
126 TC_LOG_ERROR("maps", "VMap file '{}vmaps/{}' couldn't be loaded", sWorld->GetDataPath(), name);
127 TC_LOG_ERROR("maps", "This is because the version of the VMap file and the version of this module are different, please re-extract the maps with the tools compiled with this module.");
128 return false;
130 TC_LOG_ERROR("maps", "VMap file '{}vmaps/{}' couldn't be loaded", sWorld->GetDataPath(), name);
131 TC_LOG_ERROR("maps", "This is because VMAP files are corrupted, please re-extract the maps with the tools compiled with this module.");
132 return false;
134 TC_LOG_ERROR("maps", "VMap file '{}vmaps/{}' couldn't be loaded", sWorld->GetDataPath(), name);
135 TC_LOG_ERROR("maps", "This is because VMAP is disabled in config file.");
136 return false;
137 }
138 }
139 }
140
141 return true;
142}
143
145{
146 auto childMapItr = std::ranges::find(_childTerrain, mapId, [](std::shared_ptr<TerrainInfo> const& childTerrain) { return childTerrain->GetId(); });
147 return childMapItr != _childTerrain.end() && (*childMapItr)->_gridFileExists[GetBitsetIndex(gx, gy)];
148}
149
150void TerrainInfo::AddChildTerrain(std::shared_ptr<TerrainInfo> childTerrain)
151{
152 childTerrain->_parentTerrain = this;
153 _childTerrain.emplace_back(std::move(childTerrain));
154}
155
157{
158 if (++_referenceCountFromMap[gx][gy] != 1) // check if already loaded
159 return;
160
161 std::scoped_lock lock(_loadMutex);
162 LoadMapAndVMapImpl(gx, gy);
163}
164
166{
167 LoadMMapInstanceImpl(mapId, instanceId);
168
169 for (std::shared_ptr<TerrainInfo> const& childTerrain : _childTerrain)
170 childTerrain->LoadMMapInstanceImpl(mapId, instanceId);
171}
172
173void TerrainInfo::LoadMMap(uint32 instanceId, int32 gx, int32 gy)
174{
175 LoadMMapImpl(instanceId, gx, gy);
176
177 for (std::shared_ptr<TerrainInfo> const& childTerrain : _childTerrain)
178 childTerrain->LoadMMapImpl(instanceId, gx, gy);
179}
180
182{
183 LoadMap(gx, gy);
184 LoadVMap(gx, gy);
185
186 for (std::shared_ptr<TerrainInfo> const& childTerrain : _childTerrain)
187 childTerrain->LoadMapAndVMapImpl(gx, gy);
188
189 _loadedGrids[gx] |= UI64LIT(1) << gy;
190}
191
193{
194 MMAP::MMapManager::instance()->loadMapInstance(sWorld->GetDataPath(), _mapId, mapId, instanceId);
195}
196
198{
199 if (_gridMap[gx][gy])
200 return;
201
202 if (!_gridFileExists[GetBitsetIndex(gx, gy)])
203 return;
204
205 // map file name
206 std::string fileName = Trinity::StringFormat("{}maps/{:04}_{:02}_{:02}.map", sWorld->GetDataPath(), GetId(), gx, gy);
207 TC_LOG_DEBUG("maps", "Loading map {}", fileName);
208 // loading data
209 std::unique_ptr<GridMap> gridMap = std::make_unique<GridMap>();
210 GridMap::LoadResult gridMapLoadResult = gridMap->loadData(fileName.c_str());
211 if (gridMapLoadResult == GridMap::LoadResult::Ok)
212 _gridMap[gx][gy] = std::move(gridMap);
213 else
214 _gridFileExists[GetBitsetIndex(gx, gy)] = false;
215
216 if (gridMapLoadResult == GridMap::LoadResult::InvalidFile)
217 TC_LOG_ERROR("maps", "Error loading map file: {}", fileName);
218}
219
221{
222 if (!VMAP::VMapFactory::createOrGetVMapManager()->isMapLoadingEnabled())
223 return;
224
225 switch (VMAP::VMapFactory::createOrGetVMapManager()->loadMap(sWorld->GetDataPath() + "vmaps", GetId(), gx, gy))
226 {
228 TC_LOG_DEBUG("maps", "VMAP loaded name:{}, id:{}, x:{}, y:{} (vmap rep.: x:{}, y:{})", GetMapName(), GetId(), gx, gy, gx, gy);
229 break;
232 TC_LOG_ERROR("maps", "Could not load VMAP name:{}, id:{}, x:{}, y:{} (vmap rep.: x:{}, y:{})", GetMapName(), GetId(), gx, gy, gx, gy);
233 break;
235 TC_LOG_DEBUG("maps", "Ignored VMAP name:{}, id:{}, x:{}, y:{} (vmap rep.: x:{}, y:{})", GetMapName(), GetId(), gx, gy, gx, gy);
236 break;
237 default:
238 break;
239 }
240}
241
243{
245 return;
246
247 switch (MMAP::LoadResult mmapLoadResult = MMAP::MMapManager::instance()->loadMap(sWorld->GetDataPath(), GetId(), instanceId, gx, gy))
248 {
250 TC_LOG_DEBUG("mmaps.tiles", "MMAP loaded name:{}, id:{}, x:{}, y:{} (mmap rep.: x:{}, y:{})", GetMapName(), GetId(), gx, gy, gx, gy);
251 break;
253 break;
255 if (_parentTerrain)
256 break; // don't log tile not found errors for child maps
257 [[fallthrough]];
258 default:
259 TC_LOG_WARN("mmaps.tiles", "Could not load MMAP name:{}, id:{}, x:{}, y:{} (mmap rep.: x:{}, y:{}) result: {}", GetMapName(), GetId(), gx, gy, gx, gy, AsUnderlyingType(mmapLoadResult));
260 break;
261 }
262}
263
265{
266 --_referenceCountFromMap[gx][gy];
267 // unload later
268}
269
271{
272 UnloadMMapInstanceImpl(mapId, instanceId);
273
274 for (std::shared_ptr<TerrainInfo> const& childTerrain : _childTerrain)
275 childTerrain->UnloadMMapInstanceImpl(mapId, instanceId);
276}
277
279{
280 _gridMap[gx][gy] = nullptr;
283
284 for (std::shared_ptr<TerrainInfo> const& childTerrain : _childTerrain)
285 childTerrain->UnloadMapImpl(gx, gy);
286
287 _loadedGrids[gx] &= ~(UI64LIT(1) << gy);
288}
289
291{
293}
294
295GridMap* TerrainInfo::GetGrid(uint32 mapId, float x, float y, bool loadIfMissing /*= true*/)
296{
297 // half opt method
298 int32 gx = (int)(CENTER_GRID_ID - x / SIZE_OF_GRIDS); //grid x
299 int32 gy = (int)(CENTER_GRID_ID - y / SIZE_OF_GRIDS); //grid y
300
301 // ensure GridMap is loaded
302 if (!(_loadedGrids[gx] & (UI64LIT(1) << gy)) && loadIfMissing)
303 {
304 std::scoped_lock lock(_loadMutex);
305 LoadMapAndVMapImpl(gx, gy);
306 }
307
308 GridMap* grid = _gridMap[gx][gy].get();
309 if (mapId != GetId())
310 {
311 auto childMapItr = std::ranges::find(_childTerrain, mapId, [](std::shared_ptr<TerrainInfo> const& childTerrain) { return childTerrain->GetId(); });
312 if (childMapItr != _childTerrain.end() && (*childMapItr)->_gridMap[gx][gy])
313 grid = (*childMapItr)->GetGrid(mapId, x, y, false);
314 }
315
316 return grid;
317}
318
320{
321 _cleanupTimer.Update(diff);
322 if (!_cleanupTimer.Passed())
323 return;
324
325 // delete those GridMap objects which have refcount = 0
326 for (int32 x = 0; x < MAX_NUMBER_OF_GRIDS; ++x)
327 if (_loadedGrids[x])
328 for (int32 y = 0; y < MAX_NUMBER_OF_GRIDS; ++y)
329 if ((_loadedGrids[x] & (UI64LIT(1) << y)) && !_referenceCountFromMap[x][y])
330 UnloadMapImpl(x, y);
331
333}
334
335static bool IsInWMOInterior(uint32 mogpFlags)
336{
337 return (mogpFlags & 0x2000) != 0;
338}
339
340void TerrainInfo::GetFullTerrainStatusForPosition(PhaseShift const& phaseShift, uint32 mapId, float x, float y, float z, PositionFullTerrainStatus& data,
341 Optional<map_liquidHeaderTypeFlags> reqLiquidType, float collisionHeight, DynamicMapTree const* dynamicMapTree)
342{
346 VMAP::AreaAndLiquidData* wmoData = nullptr;
347 uint32 terrainMapId = PhasingHandler::GetTerrainMapId(phaseShift, mapId, this, x, y);
348 GridMap* gmap = GetGrid(terrainMapId, x, y);
349 vmgr->getAreaAndLiquidData(terrainMapId, x, y, z, reqLiquidType ? AsUnderlyingType(*reqLiquidType) : Optional<uint8>(), vmapData);
350 if (dynamicMapTree)
351 dynamicMapTree->getAreaAndLiquidData(x, y, z, phaseShift, reqLiquidType ? AsUnderlyingType(*reqLiquidType) : Optional<uint8>(), dynData);
352
353 uint32 gridAreaId = 0;
354 float gridMapHeight = INVALID_HEIGHT;
355 if (gmap)
356 {
357 gridAreaId = gmap->getArea(x, y);
358 gridMapHeight = gmap->getHeight(x, y);
359 }
360
361 bool useGridLiquid = true;
362
363 // floor is the height we are closer to (but only if above)
365 if (gridMapHeight > INVALID_HEIGHT && G3D::fuzzyGe(z, gridMapHeight - GROUND_HEIGHT_TOLERANCE))
366 data.floorZ = gridMapHeight;
367 if (vmapData.floorZ > VMAP_INVALID_HEIGHT &&
368 G3D::fuzzyGe(z, vmapData.floorZ - GROUND_HEIGHT_TOLERANCE) &&
369 (G3D::fuzzyLt(z, gridMapHeight - GROUND_HEIGHT_TOLERANCE) || vmapData.floorZ > gridMapHeight))
370 {
371 data.floorZ = vmapData.floorZ;
372 wmoData = &vmapData;
373 }
374 // NOTE: Objects will not detect a case when a wmo providing area/liquid despawns from under them
375 // but this is fine as these kind of objects are not meant to be spawned and despawned a lot
376 // example: Lich King platform
377 if (dynData.floorZ > VMAP_INVALID_HEIGHT &&
378 G3D::fuzzyGe(z, dynData.floorZ - GROUND_HEIGHT_TOLERANCE) &&
379 (G3D::fuzzyLt(z, gridMapHeight - GROUND_HEIGHT_TOLERANCE) || dynData.floorZ > gridMapHeight) &&
380 (G3D::fuzzyLt(z, vmapData.floorZ - GROUND_HEIGHT_TOLERANCE) || dynData.floorZ > vmapData.floorZ))
381 {
382 data.floorZ = dynData.floorZ;
383 wmoData = &dynData;
384 }
385
386 if (wmoData)
387 {
388 if (wmoData->areaInfo)
389 {
390 // wmo found
391 data.wmoLocation.emplace(wmoData->areaInfo->groupId, wmoData->areaInfo->adtId, wmoData->areaInfo->rootId, wmoData->areaInfo->uniqueId);
392 data.outdoors = (wmoData->areaInfo->mogpFlags & 0x8) != 0;
393
394 if (WMOAreaTableEntry const* wmoEntry = DB2Manager::GetWMOAreaTable(wmoData->areaInfo->rootId, wmoData->areaInfo->adtId, wmoData->areaInfo->groupId, true))
395 {
396 data.areaId = wmoEntry->AreaTableID;
397 if (wmoEntry->HasFlag(WMOAreaTableFlags::ForceOutdoors))
398 data.outdoors = true;
399 else if (wmoEntry->HasFlag(WMOAreaTableFlags::ForceIndoors))
400 data.outdoors = false;
401 }
402
403 if (!data.areaId)
404 data.areaId = gridAreaId;
405
406 useGridLiquid = !IsInWMOInterior(wmoData->areaInfo->mogpFlags);
407 }
408 }
409 else
410 {
411 data.outdoors = true;
412 data.areaId = gridAreaId;
413 if (AreaTableEntry const* areaEntry = sAreaTableStore.LookupEntry(data.areaId))
414 data.outdoors = areaEntry->GetFlags().HasFlag(AreaFlags::ForceOutdoors) || !areaEntry->GetFlags().HasFlag(AreaFlags::ForceIndoors);
415 }
416
417 if (!data.areaId)
418 data.areaId = sMapStore.AssertEntry(GetId())->AreaTableID;
419
420 AreaTableEntry const* areaEntry = sAreaTableStore.LookupEntry(data.areaId);
421
422 // liquid processing
424 if (wmoData && wmoData->liquidInfo && wmoData->liquidInfo->level > wmoData->floorZ)
425 {
426 uint32 liquidType = wmoData->liquidInfo->type;
427 if (GetId() == 530 && liquidType == 2) // gotta love hacks
428 liquidType = 15;
429
430 if (liquidType && liquidType < 21 && areaEntry)
431 {
432 uint32 overrideLiquid = areaEntry->LiquidTypeID[(liquidType - 1) & 3];
433 if (!overrideLiquid && areaEntry->ParentAreaID)
434 {
435 if (AreaTableEntry const* zoneEntry = sAreaTableStore.LookupEntry(areaEntry->ParentAreaID))
436 overrideLiquid = zoneEntry->LiquidTypeID[(liquidType - 1) & 3];
437 }
438
439 if (sLiquidTypeStore.HasRecord(overrideLiquid))
440 liquidType = overrideLiquid;
441 }
442
443 data.liquidInfo.emplace();
444 data.liquidInfo->level = wmoData->liquidInfo->level;
445 data.liquidInfo->depth_level = wmoData->floorZ;
446 data.liquidInfo->entry = liquidType;
448
449 float delta = wmoData->liquidInfo->level - z;
451 if (delta > collisionHeight)
452 status = LIQUID_MAP_UNDER_WATER;
453 else if (delta > 0.0f)
454 status = LIQUID_MAP_IN_WATER;
455 else if (delta > -0.1f)
456 status = LIQUID_MAP_WATER_WALK;
457
458 if (status != LIQUID_MAP_ABOVE_WATER)
459 if (std::fabs(wmoData->floorZ - z) <= GROUND_HEIGHT_TOLERANCE)
460 status |= LIQUID_MAP_OCEAN_FLOOR;
461
462 data.liquidStatus = static_cast<ZLiquidStatus>(status);
463 }
464 // look up liquid data from grid map
465 if (gmap && useGridLiquid)
466 {
467 LiquidData gridMapLiquid;
468 ZLiquidStatus gridMapStatus = gmap->GetLiquidStatus(x, y, z, reqLiquidType, &gridMapLiquid, collisionHeight);
469 if (gridMapStatus != LIQUID_MAP_NO_WATER && (!wmoData || gridMapLiquid.level > wmoData->floorZ))
470 {
471 if (GetId() == 530 && gridMapLiquid.entry == 2)
472 gridMapLiquid.entry = 15;
473 data.liquidInfo = gridMapLiquid;
474 data.liquidStatus = gridMapStatus;
475 }
476 }
477}
478
479ZLiquidStatus TerrainInfo::GetLiquidStatus(PhaseShift const& phaseShift, uint32 mapId, float x, float y, float z, Optional<map_liquidHeaderTypeFlags> ReqLiquidType, LiquidData* data, float collisionHeight)
480{
484 bool useGridLiquid = true;
485 uint32 terrainMapId = PhasingHandler::GetTerrainMapId(phaseShift, mapId, this, x, y);
486 if (vmgr->getAreaAndLiquidData(terrainMapId, x, y, z, ReqLiquidType ? AsUnderlyingType(*ReqLiquidType) : Optional<uint8>(), vmapData) && vmapData.liquidInfo)
487 {
488 useGridLiquid = !vmapData.areaInfo || !IsInWMOInterior(vmapData.areaInfo->mogpFlags);
489 TC_LOG_DEBUG("maps", "GetLiquidStatus(): vmap liquid level: {} ground: {} type: {}", vmapData.liquidInfo->level, vmapData.floorZ, vmapData.liquidInfo->type);
490 // Check water level and ground level
491 if (vmapData.liquidInfo->level > vmapData.floorZ && G3D::fuzzyGe(z, vmapData.floorZ - GROUND_HEIGHT_TOLERANCE))
492 {
493 // All ok in water -> store data
494 if (data)
495 {
496 // hardcoded in client like this
497 if (GetId() == 530 && vmapData.liquidInfo->type == 2)
498 vmapData.liquidInfo->type = 15;
499
500 if (vmapData.liquidInfo->type && vmapData.liquidInfo->type < 21)
501 {
502 if (AreaTableEntry const* area = sAreaTableStore.LookupEntry(GetAreaId(phaseShift, mapId, x, y, z)))
503 {
504 uint32 overrideLiquid = area->LiquidTypeID[(vmapData.liquidInfo->type - 1) & 3];
505 if (!overrideLiquid && area->ParentAreaID)
506 {
507 area = sAreaTableStore.LookupEntry(area->ParentAreaID);
508 if (area)
509 overrideLiquid = area->LiquidTypeID[(vmapData.liquidInfo->type - 1) & 3];
510 }
511
512 if (sLiquidTypeStore.HasRecord(overrideLiquid))
513 vmapData.liquidInfo->type = overrideLiquid;
514 }
515 }
516
517 data->level = vmapData.liquidInfo->level;
518 data->depth_level = vmapData.floorZ;
519
520 data->entry = vmapData.liquidInfo->type;
522 }
523
524 float delta = vmapData.liquidInfo->level - z;
525
526 // Get position delta
528 if (delta > collisionHeight) // Under water
529 status = LIQUID_MAP_UNDER_WATER;
530 else if (delta > 0.0f) // In water
531 status = LIQUID_MAP_IN_WATER;
532 else if (delta > -0.1f) // Walk on water
533 status = LIQUID_MAP_WATER_WALK;
534
535 if (status != LIQUID_MAP_ABOVE_WATER)
536 {
537 if (status != LIQUID_MAP_ABOVE_WATER)
538 if (std::fabs(vmapData.floorZ - z) <= GROUND_HEIGHT_TOLERANCE)
539 status |= LIQUID_MAP_OCEAN_FLOOR;
540
541 return static_cast<ZLiquidStatus>(status);
542 }
543
544 result = LIQUID_MAP_ABOVE_WATER;
545 }
546 }
547
548 if (useGridLiquid)
549 {
550 if (GridMap const* gmap = GetGrid(terrainMapId, x, y))
551 {
552 LiquidData map_data;
553 ZLiquidStatus map_result = gmap->GetLiquidStatus(x, y, z, ReqLiquidType, &map_data, collisionHeight);
554 // Not override LIQUID_MAP_ABOVE_WATER with LIQUID_MAP_NO_WATER:
555 if (map_result != LIQUID_MAP_NO_WATER && (map_data.level > vmapData.floorZ))
556 {
557 if (data)
558 {
559 // hardcoded in client like this
560 if (GetId() == 530 && map_data.entry == 2)
561 map_data.entry = 15;
562
563 *data = map_data;
564 }
565 return map_result;
566 }
567 }
568 }
569 return result;
570}
571
572bool TerrainInfo::GetAreaInfo(PhaseShift const& phaseShift, uint32 mapId, float x, float y, float z, uint32& mogpflags, int32& adtId, int32& rootId, int32& groupId, DynamicMapTree const* dynamicMapTree)
573{
574 float check_z = z;
575 uint32 terrainMapId = PhasingHandler::GetTerrainMapId(phaseShift, mapId, this, x, y);
579
580 bool hasVmapAreaInfo = vmgr->getAreaAndLiquidData(terrainMapId, x, y, z, {}, vdata) && vdata.areaInfo.has_value();
581 bool hasDynamicAreaInfo = dynamicMapTree ? dynamicMapTree->getAreaAndLiquidData(x, y, z, phaseShift, {}, ddata) && ddata.areaInfo.has_value() : false;
582 auto useVmap = [&] { check_z = vdata.floorZ; groupId = vdata.areaInfo->groupId; adtId = vdata.areaInfo->adtId; rootId = vdata.areaInfo->rootId; mogpflags = vdata.areaInfo->mogpFlags; };
583 auto useDyn = [&] { check_z = ddata.floorZ; groupId = ddata.areaInfo->groupId; adtId = ddata.areaInfo->adtId; rootId = ddata.areaInfo->rootId; mogpflags = ddata.areaInfo->mogpFlags; };
584
585 if (hasVmapAreaInfo)
586 {
587 if (hasDynamicAreaInfo && ddata.floorZ > vdata.floorZ)
588 useDyn();
589 else
590 useVmap();
591 }
592 else if (hasDynamicAreaInfo)
593 {
594 useDyn();
595 }
596
597 if (hasVmapAreaInfo || hasDynamicAreaInfo)
598 {
599 // check if there's terrain between player height and object height
600 if (GridMap* gmap = GetGrid(terrainMapId, x, y))
601 {
602 float mapHeight = gmap->getHeight(x, y);
603 // z + 2.0f condition taken from GetHeight(), not sure if it's such a great choice...
604 if (z + 2.0f > mapHeight && mapHeight > check_z)
605 return false;
606 }
607 return true;
608 }
609 return false;
610}
611
612uint32 TerrainInfo::GetAreaId(PhaseShift const& phaseShift, uint32 mapId, float x, float y, float z, DynamicMapTree const* dynamicMapTree)
613{
614 uint32 mogpFlags;
615 int32 adtId, rootId, groupId;
616 float vmapZ = z;
617 bool hasVmapArea = GetAreaInfo(phaseShift, mapId, x, y, vmapZ, mogpFlags, adtId, rootId, groupId, dynamicMapTree);
618
619 uint32 gridAreaId = 0;
620 float gridMapHeight = INVALID_HEIGHT;
621 if (GridMap* gmap = GetGrid(PhasingHandler::GetTerrainMapId(phaseShift, mapId, this, x, y), x, y))
622 {
623 gridAreaId = gmap->getArea(x, y);
624 gridMapHeight = gmap->getHeight(x, y);
625 }
626
627 uint32 areaId = 0;
628
629 // floor is the height we are closer to (but only if above)
630 if (hasVmapArea && G3D::fuzzyGe(z, vmapZ - GROUND_HEIGHT_TOLERANCE) && (G3D::fuzzyLt(z, gridMapHeight - GROUND_HEIGHT_TOLERANCE) || vmapZ > gridMapHeight))
631 {
632 // wmo found
633 if (WMOAreaTableEntry const* wmoEntry = DB2Manager::GetWMOAreaTable(rootId, adtId, groupId, false))
634 areaId = wmoEntry->AreaTableID;
635
636 if (!areaId)
637 areaId = gridAreaId;
638 }
639 else
640 areaId = gridAreaId;
641
642 if (!areaId)
643 areaId = sMapStore.AssertEntry(GetId())->AreaTableID;
644
645 return areaId;
646}
647
648uint32 TerrainInfo::GetZoneId(PhaseShift const& phaseShift, uint32 mapId, float x, float y, float z, DynamicMapTree const* dynamicMapTree)
649{
650 uint32 areaId = GetAreaId(phaseShift, mapId, x, y, z, dynamicMapTree);
651 if (AreaTableEntry const* area = sAreaTableStore.LookupEntry(areaId))
652 if (area->ParentAreaID && area->GetFlags().HasFlag(AreaFlags::IsSubzone))
653 return area->ParentAreaID;
654
655 return areaId;
656}
657
658void TerrainInfo::GetZoneAndAreaId(PhaseShift const& phaseShift, uint32 mapId, uint32& zoneid, uint32& areaid, float x, float y, float z, DynamicMapTree const* dynamicMapTree)
659{
660 areaid = zoneid = GetAreaId(phaseShift, mapId, x, y, z, dynamicMapTree);
661 if (AreaTableEntry const* area = sAreaTableStore.LookupEntry(areaid))
662 if (area->ParentAreaID && area->GetFlags().HasFlag(AreaFlags::IsSubzone))
663 zoneid = area->ParentAreaID;
664}
665
666float TerrainInfo::GetMinHeight(PhaseShift const& phaseShift, uint32 mapId, float x, float y)
667{
668 if (GridMap const* grid = GetGrid(PhasingHandler::GetTerrainMapId(phaseShift, mapId, this, x, y), x, y))
669 return grid->getMinHeight(x, y);
670
671 return -500.0f;
672}
673
674float TerrainInfo::GetGridHeight(PhaseShift const& phaseShift, uint32 mapId, float x, float y)
675{
676 if (GridMap* gmap = GetGrid(PhasingHandler::GetTerrainMapId(phaseShift, mapId, this, x, y), x, y))
677 return gmap->getHeight(x, y);
678
680}
681
682float TerrainInfo::GetStaticHeight(PhaseShift const& phaseShift, uint32 mapId, float x, float y, float z, bool checkVMap /*= true*/, float maxSearchDist /*= DEFAULT_HEIGHT_SEARCH*/)
683{
684 // find raw .map surface under Z coordinates
685 float mapHeight = VMAP_INVALID_HEIGHT_VALUE;
686 uint32 terrainMapId = PhasingHandler::GetTerrainMapId(phaseShift, mapId, this, x, y);
687 float gridHeight = GetGridHeight(phaseShift, mapId, x, y);
688 if (G3D::fuzzyGe(z, gridHeight - GROUND_HEIGHT_TOLERANCE))
689 mapHeight = gridHeight;
690
691 float vmapHeight = VMAP_INVALID_HEIGHT_VALUE;
692 if (checkVMap)
693 {
695 if (vmgr->isHeightCalcEnabled())
696 vmapHeight = vmgr->getHeight(terrainMapId, x, y, z, maxSearchDist);
697 }
698
699 // mapHeight set for any above raw ground Z or <= INVALID_HEIGHT
700 // vmapheight set for any under Z value or <= INVALID_HEIGHT
701 if (vmapHeight > INVALID_HEIGHT)
702 {
703 if (mapHeight > INVALID_HEIGHT)
704 {
705 // we have mapheight and vmapheight and must select more appropriate
706
707 // vmap height above map height
708 // or if the distance of the vmap height is less the land height distance
709 if (vmapHeight > mapHeight || std::fabs(mapHeight - z) > std::fabs(vmapHeight - z))
710 return vmapHeight;
711
712 return mapHeight; // better use .map surface height
713 }
714
715 return vmapHeight; // we have only vmapHeight (if have)
716 }
717
718 return mapHeight; // explicitly use map data
719}
720
721float TerrainInfo::GetWaterLevel(PhaseShift const& phaseShift, uint32 mapId, float x, float y)
722{
723 if (GridMap* gmap = GetGrid(PhasingHandler::GetTerrainMapId(phaseShift, mapId, this, x, y), x, y))
724 return gmap->getLiquidLevel(x, y);
725
726 return 0;
727}
728
729bool TerrainInfo::IsInWater(PhaseShift const& phaseShift, uint32 mapId, float x, float y, float pZ, LiquidData* data)
730{
731 LiquidData liquid_status;
732 LiquidData* liquid_ptr = data ? data : &liquid_status;
733 return (GetLiquidStatus(phaseShift, mapId, x, y, pZ, {}, liquid_ptr) & (LIQUID_MAP_IN_WATER | LIQUID_MAP_UNDER_WATER)) != 0;
734}
735
736bool TerrainInfo::IsUnderWater(PhaseShift const& phaseShift, uint32 mapId, float x, float y, float z)
737{
739}
740
741float TerrainInfo::GetWaterOrGroundLevel(PhaseShift const& phaseShift, uint32 mapId, float x, float y, float z, float* ground /*= nullptr*/, bool /*swim = false*/, float collisionHeight /*= DEFAULT_COLLISION_HEIGHT*/, DynamicMapTree const* dynamicMapTree /*= nullptr*/)
742{
743 if (GetGrid(PhasingHandler::GetTerrainMapId(phaseShift, mapId, this, x, y), x, y))
744 {
745 // we need ground level (including grid height version) for proper return water level in point
746 float ground_z = GetStaticHeight(phaseShift, mapId, x, y, z + Z_OFFSET_FIND_HEIGHT, true, 50.0f);
747 if (dynamicMapTree)
748 ground_z = std::max(ground_z, dynamicMapTree->getHeight(x, y, z + Z_OFFSET_FIND_HEIGHT, 50.0f, phaseShift));
749
750 if (ground)
751 *ground = ground_z;
752
753 LiquidData liquid_status;
754
755 ZLiquidStatus res = GetLiquidStatus(phaseShift, mapId, x, y, ground_z, {}, &liquid_status, collisionHeight);
756 switch (res)
757 {
759 return std::max<float>(liquid_status.level, ground_z);
761 return ground_z;
762 default:
763 return liquid_status.level;
764 }
765 }
766
768}
769
770TerrainMgr::TerrainMgr() = default;
771
772TerrainMgr::~TerrainMgr() = default;
773
775{
776 static TerrainMgr instance;
777 return instance;
778}
779
780void TerrainMgr::InitializeParentMapData(std::unordered_map<uint32, std::vector<uint32>> const& mapData)
781{
782 _parentMapData = mapData;
783}
784
785std::shared_ptr<TerrainInfo> TerrainMgr::LoadTerrain(uint32 mapId)
786{
787 MapEntry const* entry = sMapStore.LookupEntry(mapId);
788 if (!entry)
789 return nullptr;
790
791 while (entry->ParentMapID != -1 || entry->CosmeticParentMapID != -1)
792 {
793 uint32 parentMapId = entry->ParentMapID != -1 ? entry->ParentMapID : entry->CosmeticParentMapID;
794 entry = sMapStore.LookupEntry(parentMapId);
795 if (!entry)
796 break;
797
798 mapId = parentMapId;
799 }
800
801 auto itr = _terrainMaps.find(mapId);
802 if (itr != _terrainMaps.end())
803 if (std::shared_ptr<TerrainInfo> terrain = itr->second.lock())
804 return terrain;
805
806 std::shared_ptr<TerrainInfo> terrainInfo = LoadTerrainImpl(mapId);
807 _terrainMaps[mapId] = terrainInfo;
808 return terrainInfo;
809}
810
812{
813 _terrainMaps.clear();
814}
815
817{
818 // global garbage collection
819 for (auto& [mapId, terrainRef] : _terrainMaps)
820 if (std::shared_ptr<TerrainInfo> terrain = terrainRef.lock())
821 terrain->CleanUpGrids(diff);
822}
823
824uint32 TerrainMgr::GetAreaId(PhaseShift const& phaseShift, uint32 mapid, float x, float y, float z)
825{
826 if (std::shared_ptr<TerrainInfo> t = LoadTerrain(mapid))
827 return t->GetAreaId(phaseShift, mapid, x, y, z);
828 return 0;
829}
830
831uint32 TerrainMgr::GetZoneId(PhaseShift const& phaseShift, uint32 mapid, float x, float y, float z)
832{
833 if (std::shared_ptr<TerrainInfo> t = LoadTerrain(mapid))
834 return t->GetZoneId(phaseShift, mapid, x, y, z);
835 return 0;
836}
837
838void TerrainMgr::GetZoneAndAreaId(PhaseShift const& phaseShift, uint32& zoneid, uint32& areaid, uint32 mapid, float x, float y, float z)
839{
840 if (std::shared_ptr<TerrainInfo> t = LoadTerrain(mapid))
841 t->GetZoneAndAreaId(phaseShift, mapid, zoneid, areaid, x, y, z);
842}
843
844std::shared_ptr<TerrainInfo> TerrainMgr::LoadTerrainImpl(uint32 mapId)
845{
846 std::shared_ptr<TerrainInfo> rootTerrain(new TerrainInfo(mapId)); // intentionally not using make_shared, don't want control block allocated together, will be relying on weak_ptr
847
848 rootTerrain->DiscoverGridMapFiles();
849
850 for (uint32 childMapId : _parentMapData[mapId])
851 rootTerrain->AddChildTerrain(LoadTerrainImpl(childMapId));
852
853 return rootTerrain;
854}
855
856bool TerrainMgr::ExistMapAndVMap(uint32 mapid, float x, float y)
857{
859
860 int32 gx = (MAX_NUMBER_OF_GRIDS - 1) - p.x_coord;
861 int32 gy = (MAX_NUMBER_OF_GRIDS - 1) - p.y_coord;
862
863 return TerrainInfo::ExistMap(mapid, gx, gy) && TerrainInfo::ExistVMap(mapid, gx, gy);
864}
DB2Storage< MapEntry > sMapStore("Map.db2", &MapLoadInfo::Instance)
DB2Storage< LiquidTypeEntry > sLiquidTypeStore("LiquidType.db2", &LiquidTypeLoadInfo::Instance)
DB2Storage< AreaTableEntry > sAreaTableStore("AreaTable.db2", &AreaTableLoadInfo::Instance)
int32_t int32
Definition Define.h:150
#define UI64LIT(N)
Definition Define.h:139
uint32_t uint32
Definition Define.h:154
#define SIZE_OF_GRIDS
Definition GridDefines.h:40
#define MAX_NUMBER_OF_GRIDS
Definition GridDefines.h:38
#define CENTER_GRID_ID
Definition GridDefines.h:41
#define INVALID_HEIGHT
Definition GridDefines.h:61
#define TC_LOG_DEBUG(filterType__, message__,...)
Definition Log.h:181
#define TC_LOG_ERROR(filterType__, message__,...)
Definition Log.h:190
#define TC_LOG_WARN(filterType__, message__,...)
Definition Log.h:187
uint32 const MapVersionMagic
u_map_magic const MapMagic
std::array< char, 4 > u_map_magic
Represents a map magic value of 4 bytes (used in versions)
Definition MapDefines.h:27
ZLiquidStatus
Definition MapDefines.h:133
@ LIQUID_MAP_UNDER_WATER
Definition MapDefines.h:138
@ LIQUID_MAP_OCEAN_FLOOR
Definition MapDefines.h:139
@ LIQUID_MAP_NO_WATER
Definition MapDefines.h:134
@ LIQUID_MAP_IN_WATER
Definition MapDefines.h:137
@ LIQUID_MAP_ABOVE_WATER
Definition MapDefines.h:135
@ LIQUID_MAP_WATER_WALK
Definition MapDefines.h:136
map_liquidHeaderTypeFlags
Definition MapDefines.h:97
std::optional< T > Optional
Optional helper class to wrap optional values within.
Definition Optional.h:25
Milliseconds randtime(Milliseconds min, Milliseconds max)
Definition Random.cpp:62
float const GROUND_HEIGHT_TOLERANCE
constexpr float Z_OFFSET_FIND_HEIGHT
static bool IsInWMOInterior(uint32 mogpFlags)
constexpr std::underlying_type< E >::type AsUnderlyingType(E enumValue)
Definition Util.h:565
#define VMAP_INVALID_HEIGHT_VALUE
Definition VMapManager.h:71
#define VMAP_INVALID_HEIGHT
Definition VMapManager.h:70
static uint32 GetLiquidFlags(uint32 liquidType)
static WMOAreaTableEntry const * GetWMOAreaTable(int32 rootId, int32 adtId, int32 groupId, bool allowGroupFallback)
float getHeight(float x, float y, float z, float maxSearchDist, PhaseShift const &phaseShift) const
bool getAreaAndLiquidData(float x, float y, float z, PhaseShift const &phaseShift, Optional< uint8 > reqLiquidType, VMAP::AreaAndLiquidData &data) const
LoadResult
Definition GridMap.h:87
uint16 getArea(float x, float y) const
Definition GridMap.cpp:292
float getHeight(float x, float y) const
Definition GridMap.h:97
ZLiquidStatus GetLiquidStatus(float x, float y, float z, Optional< map_liquidHeaderTypeFlags > ReqLiquidType, LiquidData *data=nullptr, float collisionHeight=2.03128f) const
Definition GridMap.cpp:599
static MMapManager * instance()
bool loadMapInstance(std::string_view basePath, uint32 meshMapId, uint32 instanceMapId, uint32 instanceId)
void unloadMap(uint32 mapId, int32 x, int32 y)
void unloadMapInstance(uint32 meshMapId, uint32 instanceMapId, uint32 instanceId)
static uint32 GetTerrainMapId(PhaseShift const &phaseShift, uint32 mapId, TerrainInfo const *terrain, float x, float y)
void UnloadMMapInstanceImpl(uint32 mapId, uint32 instanceId)
float GetMinHeight(PhaseShift const &phaseShift, uint32 mapId, float x, float y)
bool GetAreaInfo(PhaseShift const &phaseShift, uint32 mapId, float x, float y, float z, uint32 &mogpflags, int32 &adtId, int32 &rootId, int32 &groupId, DynamicMapTree const *dynamicMapTree=nullptr)
float GetWaterLevel(PhaseShift const &phaseShift, uint32 mapId, float x, float y)
std::atomic< uint16 > _referenceCountFromMap[MAX_NUMBER_OF_GRIDS][MAX_NUMBER_OF_GRIDS]
Definition TerrainMgr.h:115
static constexpr Milliseconds CleanupInterval
Definition TerrainMgr.h:119
void LoadMMap(uint32 instanceId, int32 gx, int32 gy)
void LoadMMapInstance(uint32 mapId, uint32 instanceId)
float GetStaticHeight(PhaseShift const &phaseShift, uint32 mapId, float x, float y, float z, bool checkVMap=true, float maxSearchDist=DEFAULT_HEIGHT_SEARCH)
ZLiquidStatus GetLiquidStatus(PhaseShift const &phaseShift, uint32 mapId, float x, float y, float z, Optional< map_liquidHeaderTypeFlags > ReqLiquidType={}, LiquidData *data=nullptr, float collisionHeight=2.03128f)
bool HasChildTerrainGridFile(uint32 mapId, int32 gx, int32 gy) const
void LoadMap(int32 gx, int32 gy)
bool IsInWater(PhaseShift const &phaseShift, uint32 mapId, float x, float y, float z, LiquidData *data=nullptr)
uint32 GetId() const
Definition TerrainMgr.h:48
void LoadVMap(int32 gx, int32 gy)
std::mutex _loadMutex
Definition TerrainMgr.h:113
TimeTracker _cleanupTimer
Definition TerrainMgr.h:122
void DiscoverGridMapFiles()
void UnloadMapImpl(int32 gx, int32 gy)
void LoadMMapInstanceImpl(uint32 mapId, uint32 instanceId)
uint32 GetAreaId(PhaseShift const &phaseShift, uint32 mapId, float x, float y, float z, DynamicMapTree const *dynamicMapTree=nullptr)
void UnloadMMapInstance(uint32 mapId, uint32 instanceId)
TerrainInfo(uint32 mapId)
std::array< uint64, MAX_NUMBER_OF_GRIDS > _loadedGrids
Definition TerrainMgr.h:116
GridMap * GetGrid(uint32 mapId, float x, float y, bool loadIfMissing=true)
std::bitset< MAX_NUMBER_OF_GRIDS *MAX_NUMBER_OF_GRIDS > _gridFileExists
Definition TerrainMgr.h:117
std::vector< std::shared_ptr< TerrainInfo > > _childTerrain
Definition TerrainMgr.h:111
void GetZoneAndAreaId(PhaseShift const &phaseShift, uint32 mapId, uint32 &zoneid, uint32 &areaid, float x, float y, float z, DynamicMapTree const *dynamicMapTree=nullptr)
static bool ExistVMap(uint32 mapid, int32 gx, int32 gy)
void GetFullTerrainStatusForPosition(PhaseShift const &phaseShift, uint32 mapId, float x, float y, float z, PositionFullTerrainStatus &data, Optional< map_liquidHeaderTypeFlags > reqLiquidType={}, float collisionHeight=2.03128f, DynamicMapTree const *dynamicMapTree=nullptr)
void LoadMapAndVMap(int32 gx, int32 gy)
char const * GetMapName() const
void CleanUpGrids(uint32 diff)
void UnloadMap(int32 gx, int32 gy)
static bool ExistMap(uint32 mapid, int32 gx, int32 gy, bool log=true)
void LoadMapAndVMapImpl(int32 gx, int32 gy)
float GetWaterOrGroundLevel(PhaseShift const &phaseShift, uint32 mapId, float x, float y, float z, float *ground=nullptr, bool swim=false, float collisionHeight=2.03128f, DynamicMapTree const *dynamicMapTree=nullptr)
uint32 _mapId
Definition TerrainMgr.h:108
static constexpr int32 GetBitsetIndex(int32 gx, int32 gy)
Definition TerrainMgr.h:106
TerrainInfo * _parentTerrain
Definition TerrainMgr.h:110
std::unique_ptr< GridMap > _gridMap[MAX_NUMBER_OF_GRIDS][MAX_NUMBER_OF_GRIDS]
Definition TerrainMgr.h:114
uint32 GetZoneId(PhaseShift const &phaseShift, uint32 mapId, float x, float y, float z, DynamicMapTree const *dynamicMapTree=nullptr)
bool IsUnderWater(PhaseShift const &phaseShift, uint32 mapId, float x, float y, float z)
void LoadMMapImpl(uint32 instanceId, int32 gx, int32 gy)
void AddChildTerrain(std::shared_ptr< TerrainInfo > childTerrain)
float GetGridHeight(PhaseShift const &phaseShift, uint32 mapId, float x, float y)
void Update(uint32 diff)
std::shared_ptr< TerrainInfo > LoadTerrainImpl(uint32 mapId)
void InitializeParentMapData(std::unordered_map< uint32, std::vector< uint32 > > const &mapData)
void UnloadAll()
uint32 GetAreaId(PhaseShift const &phaseShift, uint32 mapid, float x, float y, float z)
void GetZoneAndAreaId(PhaseShift const &phaseShift, uint32 &zoneid, uint32 &areaid, uint32 mapid, float x, float y, float z)
static TerrainMgr & Instance()
uint32 GetZoneId(PhaseShift const &phaseShift, uint32 mapid, float x, float y, float z)
static bool ExistMapAndVMap(uint32 mapid, float x, float y)
std::unordered_map< uint32, std::weak_ptr< TerrainInfo > > _terrainMaps
Definition TerrainMgr.h:161
std::shared_ptr< TerrainInfo > LoadTerrain(uint32 mapId)
std::unordered_map< uint32, std::vector< uint32 > > _parentMapData
Definition TerrainMgr.h:164
static VMapManager * createOrGetVMapManager()
bool getAreaAndLiquidData(uint32 mapId, float x, float y, float z, Optional< uint8 > reqLiquidType, AreaAndLiquidData &data) const
static std::string getDirFileName(uint32 mapId, uint32 x, uint32 y)
bool isHeightCalcEnabled() const
void unloadMap(uint32 mapId, uint32 x, uint32 y)
float getHeight(uint32 mapId, float x, float y, float z, float maxSearchDist)
#define sWorld
Definition World.h:916
bool IsPathfindingEnabled(uint32 mapId)
std::string StringFormat(FormatString< Args... > fmt, Args &&... args) noexcept
Default TC string format function.
GridCoord ComputeGridCoord(float x, float y)
std::array< uint16, 4 > LiquidTypeID
uint32 x_coord
uint32 y_coord
uint32 entry
Definition MapDefines.h:148
EnumFlag< map_liquidHeaderTypeFlags > type_flags
Definition MapDefines.h:147
float depth_level
Definition MapDefines.h:150
float level
Definition MapDefines.h:149
int16 CosmeticParentMapID
int16 ParentMapID
Optional< LiquidData > liquidInfo
Definition MapDefines.h:173
Optional< WmoLocation > wmoLocation
Definition MapDefines.h:172
ZLiquidStatus liquidStatus
Definition MapDefines.h:171
void Update(int32 diff)
Definition Timer.h:121
bool Passed() const
Definition Timer.h:131
void Reset(int32 expiry)
Definition Timer.h:136
Optional< AreaInfo > areaInfo
Definition VMapManager.h:95
Optional< LiquidInfo > liquidInfo
Definition VMapManager.h:96