mirror of
https://github.com/TrinityCore/TrinityCore.git
synced 2026-01-15 23:20:36 +01:00
Core/MMaps: Implemented dynamic mmap tile rebuilding for destructible objects
This commit is contained in:
@@ -36,7 +36,7 @@ int CHECK_TREE_PERIOD = 200;
|
||||
} // namespace
|
||||
|
||||
template<> struct PositionTrait< GameObjectModel> {
|
||||
static void getPosition(GameObjectModel const& g, G3D::Vector3& p) { p = g.getPosition(); }
|
||||
static void getPosition(GameObjectModel const& g, G3D::Vector3& p) { p = g.GetPosition(); }
|
||||
};
|
||||
|
||||
template<> struct BoundsTrait< GameObjectModel> {
|
||||
|
||||
@@ -21,15 +21,11 @@
|
||||
#include "Define.h"
|
||||
#include <G3D/AABox.h>
|
||||
#include <G3D/Matrix3.h>
|
||||
#include <G3D/Quat.h>
|
||||
#include <G3D/Ray.h>
|
||||
#include <G3D/Vector3.h>
|
||||
#include <memory>
|
||||
|
||||
namespace G3D
|
||||
{
|
||||
class Quat;
|
||||
}
|
||||
|
||||
namespace VMAP
|
||||
{
|
||||
class WorldModel;
|
||||
@@ -53,25 +49,33 @@ public:
|
||||
virtual bool IsInPhase(PhaseShift const& /*phaseShift*/) const = 0;
|
||||
virtual G3D::Vector3 GetPosition() const = 0;
|
||||
virtual G3D::Quat GetRotation() const = 0;
|
||||
virtual int64 GetPackedRotation() const = 0;
|
||||
virtual float GetScale() const = 0;
|
||||
virtual void DebugVisualizeCorner(G3D::Vector3 const& /*corner*/) const = 0;
|
||||
};
|
||||
|
||||
class TC_COMMON_API GameObjectModel /*, public Intersectable*/
|
||||
{
|
||||
GameObjectModel() : iCollisionEnabled(false), iLosBlockingDisabled(false), iInvScale(0), iScale(0), iModel(nullptr) { }
|
||||
GameObjectModel() : iCollisionEnabled(false), iLosBlockingDisabled(false), iIncludeInNavMesh(false), iInvScale(0), iScale(0), iModel(nullptr) { }
|
||||
public:
|
||||
const G3D::AABox& getBounds() const { return iBound; }
|
||||
|
||||
~GameObjectModel();
|
||||
|
||||
const G3D::Vector3& getPosition() const { return iPos;}
|
||||
uint32 GetDisplayId() const { return owner->GetDisplayId(); }
|
||||
G3D::Vector3 const& GetPosition() const { return iPos; }
|
||||
G3D::Quat GetRotation() const { return owner->GetRotation(); }
|
||||
G3D::Matrix3 const& GetInvRot() const { return iInvRot; }
|
||||
int64 GetPackedRotation() const { return owner->GetPackedRotation(); }
|
||||
float GetScale() const { return iScale; }
|
||||
|
||||
/* Enables/disables collision */
|
||||
void EnableCollision(bool enable) { iCollisionEnabled = enable; }
|
||||
bool IsCollisionEnabled() const { return iCollisionEnabled; }
|
||||
void DisableLosBlocking(bool enable) { iLosBlockingDisabled = enable; }
|
||||
bool IsLosBlockingDisabled() const { return iLosBlockingDisabled; }
|
||||
void IncludeInNavMesh(bool enable) { iIncludeInNavMesh = enable; }
|
||||
bool IsIncludedInNavMesh() const { return iIncludeInNavMesh; }
|
||||
bool IsMapObject() const;
|
||||
uint8 GetNameSetId() const { return owner->GetNameSetId(); }
|
||||
|
||||
@@ -83,11 +87,14 @@ public:
|
||||
|
||||
bool UpdatePosition();
|
||||
|
||||
std::shared_ptr<VMAP::WorldModel const> GetWorldModel() const { return iModel; }
|
||||
|
||||
private:
|
||||
bool initialize(std::unique_ptr<GameObjectModelOwnerBase> modelOwner, std::string const& dataPath);
|
||||
|
||||
bool iCollisionEnabled; ///< Is model ignored in all checks
|
||||
bool iLosBlockingDisabled; ///< Is model ignored during line of sight checks (but is always included in location/height checks)
|
||||
bool iIncludeInNavMesh; ///< Is model included when generating navigation mesh
|
||||
G3D::AABox iBound;
|
||||
G3D::Matrix3 iInvRot;
|
||||
G3D::Vector3 iPos;
|
||||
|
||||
@@ -210,6 +210,13 @@ public:
|
||||
if (Node* node = nodes[cell.x][cell.y].get())
|
||||
node->intersectRay(ray, intersectCallback, max_dist);
|
||||
}
|
||||
|
||||
std::span<T const* const> getObjects(int x, int y) const
|
||||
{
|
||||
if (Node* n = nodes[x][y].get())
|
||||
return n->getObjects();
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
#undef CELL_SIZE
|
||||
|
||||
@@ -50,8 +50,21 @@ namespace MMAP
|
||||
// we have to use single dtNavMeshQuery for every instance, since those are not thread safe
|
||||
NavMeshQuerySet navMeshQueries; // instanceId to query
|
||||
|
||||
static uint32 GetInstanceIdForMeshLookup([[maybe_unused]] uint32 mapId, [[maybe_unused]] uint32 instanceId)
|
||||
static uint32 GetInstanceIdForMeshLookup(uint32 mapId, uint32 instanceId)
|
||||
{
|
||||
switch (mapId)
|
||||
{
|
||||
case 0: case 1: case 571: case 603: case 607: case 609: case 616: case 628: case 631: case 644: case 649: case 720:
|
||||
case 732: case 754: case 755: case 861: case 938: case 940: case 962: case 967: case 1064: case 1076: case 1098:
|
||||
case 1122: case 1126: case 1182: case 1205: case 1220: case 1265: case 1492: case 1523: case 1530: case 1579: case 1676:
|
||||
case 1704: case 1705: case 1706: case 1707: case 1734: case 1756: case 1943: case 2076: case 2118: case 2160: case 2161:
|
||||
case 2187: case 2212: case 2235: case 2237: case 2264: case 2450: case 2512: case 2586: case 2601: case 2654: case 2657:
|
||||
case 2660: case 2669: case 2819: case 2828:
|
||||
return instanceId;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// for maps that won't have dynamic mesh, return 0 to reuse the same mesh across all instances
|
||||
return 0;
|
||||
}
|
||||
@@ -103,6 +116,11 @@ namespace MMAP
|
||||
return itr;
|
||||
}
|
||||
|
||||
bool MMapManager::isRebuildingTilesEnabledOnMap(uint32 mapId)
|
||||
{
|
||||
return MMapData::GetInstanceIdForMeshLookup(mapId, 1) != 0;
|
||||
}
|
||||
|
||||
LoadResult MMapManager::loadMapData(std::string_view basePath, uint32 mapId, uint32 instanceId)
|
||||
{
|
||||
// we already have this map loaded?
|
||||
@@ -441,7 +459,7 @@ namespace MMAP
|
||||
TC_LOG_DEBUG("maps", "MMAP:unloadMapInstance: Unloaded mapId {:04} instanceId {}", instanceMapId, instanceId);
|
||||
}
|
||||
|
||||
dtNavMesh const* MMapManager::GetNavMesh(uint32 mapId, uint32 instanceId)
|
||||
dtNavMesh* MMapManager::GetNavMesh(uint32 mapId, uint32 instanceId)
|
||||
{
|
||||
MMapDataSet::const_iterator itr = GetMMapData(mapId);
|
||||
if (itr == loadedMMaps.end())
|
||||
|
||||
@@ -67,10 +67,13 @@ namespace MMAP
|
||||
|
||||
// the returned [dtNavMeshQuery const*] is NOT threadsafe
|
||||
dtNavMeshQuery const* GetNavMeshQuery(uint32 meshMapId, uint32 instanceMapId, uint32 instanceId);
|
||||
dtNavMesh const* GetNavMesh(uint32 mapId, uint32 instanceId);
|
||||
dtNavMesh* GetNavMesh(uint32 mapId, uint32 instanceId);
|
||||
|
||||
uint32 getLoadedTilesCount() const { return loadedTiles; }
|
||||
uint32 getLoadedMapsCount() const { return uint32(loadedMMaps.size()); }
|
||||
|
||||
static bool isRebuildingTilesEnabledOnMap(uint32 mapId);
|
||||
|
||||
private:
|
||||
LoadResult loadMapData(std::string_view basePath, uint32 mapId, uint32 instanceId);
|
||||
uint32 packTileID(int32 x, int32 y);
|
||||
|
||||
@@ -4014,7 +4014,7 @@ void GameObject::UpdateModel()
|
||||
modelCollisionEnabled = GetGoType() == GAMEOBJECT_TYPE_CHEST ? getLootState() == GO_READY : (GetGoState() == GO_STATE_READY || IsTransport());
|
||||
|
||||
RemoveFlag(GO_FLAG_MAP_OBJECT);
|
||||
m_model = nullptr;
|
||||
std::unique_ptr<GameObjectModel> oldModel = std::exchange(m_model, nullptr);
|
||||
|
||||
CreateModel();
|
||||
if (m_model)
|
||||
@@ -4023,6 +4023,23 @@ void GameObject::UpdateModel()
|
||||
if (modelCollisionEnabled)
|
||||
m_model->EnableCollision(modelCollisionEnabled);
|
||||
}
|
||||
|
||||
switch (GetGoType())
|
||||
{
|
||||
// Only update navmesh when display id changes and not on spawn
|
||||
// default state of destructible buildings is intended to be baked in the mesh produced by mmaps_generator
|
||||
case GAMEOBJECT_TYPE_DESTRUCTIBLE_BUILDING:
|
||||
case GAMEOBJECT_TYPE_TRAPDOOR:
|
||||
case GAMEOBJECT_TYPE_PHASEABLE_MO:
|
||||
case GAMEOBJECT_TYPE_SIEGEABLE_MO:
|
||||
if (m_model)
|
||||
GetMap()->RequestRebuildNavMeshOnGameObjectModelChange(*m_model, GetPhaseShift());
|
||||
else if (oldModel)
|
||||
GetMap()->RequestRebuildNavMeshOnGameObjectModelChange(*oldModel, GetPhaseShift());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool GameObject::IsLootAllowedFor(Player const* player) const
|
||||
@@ -4499,6 +4516,7 @@ public:
|
||||
bool IsInPhase(PhaseShift const& phaseShift) const override { return _owner->GetPhaseShift().CanSee(phaseShift); }
|
||||
G3D::Vector3 GetPosition() const override { return G3D::Vector3(_owner->GetPositionX(), _owner->GetPositionY(), _owner->GetPositionZ()); }
|
||||
G3D::Quat GetRotation() const override { return G3D::Quat(_owner->GetLocalRotation().x, _owner->GetLocalRotation().y, _owner->GetLocalRotation().z, _owner->GetLocalRotation().w); }
|
||||
int64 GetPackedRotation() const override { return _owner->GetPackedLocalRotation(); }
|
||||
float GetScale() const override { return _owner->GetObjectScale(); }
|
||||
void DebugVisualizeCorner(G3D::Vector3 const& corner) const override { _owner->SummonCreature(1, corner.x, corner.y, corner.z, 0, TEMPSUMMON_MANUAL_DESPAWN); }
|
||||
|
||||
@@ -4538,8 +4556,20 @@ void GameObject::CreateModel()
|
||||
if (m_model->IsMapObject())
|
||||
SetFlag(GO_FLAG_MAP_OBJECT);
|
||||
|
||||
if (GetGoType() == GAMEOBJECT_TYPE_DOOR)
|
||||
m_model->DisableLosBlocking(GetGOInfo()->door.NotLOSBlocking);
|
||||
switch (GetGoType())
|
||||
{
|
||||
case GAMEOBJECT_TYPE_DOOR:
|
||||
m_model->DisableLosBlocking(GetGOInfo()->door.NotLOSBlocking);
|
||||
break;
|
||||
case GAMEOBJECT_TYPE_DESTRUCTIBLE_BUILDING:
|
||||
case GAMEOBJECT_TYPE_TRAPDOOR:
|
||||
case GAMEOBJECT_TYPE_PHASEABLE_MO:
|
||||
case GAMEOBJECT_TYPE_SIEGEABLE_MO:
|
||||
m_model->IncludeInNavMesh(true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
381
src/server/game/Maps/DynamicMMapTileBuilder.cpp
Normal file
381
src/server/game/Maps/DynamicMMapTileBuilder.cpp
Normal file
@@ -0,0 +1,381 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "DynamicMMapTileBuilder.h"
|
||||
#include "Containers.h"
|
||||
#include "DB2Stores.h"
|
||||
#include "DeadlineTimer.h"
|
||||
#include "GameObjectModel.h"
|
||||
#include "GameTime.h"
|
||||
#include "Hash.h"
|
||||
#include "IoContext.h"
|
||||
#include "Log.h"
|
||||
#include "MMapDefines.h"
|
||||
#include "MMapManager.h"
|
||||
#include "Map.h"
|
||||
#include "VMapFactory.h"
|
||||
#include "VMapManager.h"
|
||||
#include "World.h"
|
||||
#include "advstd.h"
|
||||
#include <thread>
|
||||
|
||||
namespace
|
||||
{
|
||||
struct TileCacheKeyObject
|
||||
{
|
||||
uint32 DisplayId;
|
||||
int16 Scale;
|
||||
std::array<int16, 3> Position;
|
||||
int64 Rotation;
|
||||
|
||||
friend std::strong_ordering operator<=>(TileCacheKeyObject const&, TileCacheKeyObject const&) = default;
|
||||
friend bool operator==(TileCacheKeyObject const&, TileCacheKeyObject const&) = default;
|
||||
};
|
||||
|
||||
struct TileCacheKey
|
||||
{
|
||||
uint32 TerrainMapId;
|
||||
uint32 X;
|
||||
uint32 Y;
|
||||
std::size_t CachedHash; // computing the hash is expensive - store it
|
||||
std::vector<TileCacheKeyObject> Objects;
|
||||
|
||||
friend bool operator==(TileCacheKey const&, TileCacheKey const&) = default;
|
||||
};
|
||||
}
|
||||
|
||||
template <>
|
||||
struct std::hash<TileCacheKey>
|
||||
{
|
||||
static std::size_t Compute(TileCacheKey const& key) noexcept
|
||||
{
|
||||
size_t hashVal = 0;
|
||||
Trinity::hash_combine(hashVal, key.TerrainMapId);
|
||||
Trinity::hash_combine(hashVal, key.X);
|
||||
Trinity::hash_combine(hashVal, key.Y);
|
||||
for (TileCacheKeyObject const& object : key.Objects)
|
||||
{
|
||||
Trinity::hash_combine(hashVal, object.DisplayId);
|
||||
Trinity::hash_combine(hashVal, object.Scale);
|
||||
Trinity::hash_combine(hashVal, object.Position[0]);
|
||||
Trinity::hash_combine(hashVal, object.Position[1]);
|
||||
Trinity::hash_combine(hashVal, object.Position[2]);
|
||||
Trinity::hash_combine(hashVal, object.Rotation);
|
||||
}
|
||||
return hashVal;
|
||||
}
|
||||
|
||||
std::size_t operator()(TileCacheKey const& key) const noexcept
|
||||
{
|
||||
return key.CachedHash;
|
||||
}
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
std::unique_ptr<VMAP::VMapManager> CreateVMapManager(uint32 mapId)
|
||||
{
|
||||
std::unique_ptr<VMAP::VMapManager> vmgr = std::make_unique<VMAP::VMapManager>();
|
||||
|
||||
do
|
||||
{
|
||||
MapEntry const* mapEntry = sMapStore.AssertEntry(mapId);
|
||||
if (!mapEntry)
|
||||
break;
|
||||
|
||||
vmgr->InitializeThreadUnsafe(mapId, mapEntry->ParentMapID);
|
||||
if (mapEntry->ParentMapID < 0)
|
||||
break;
|
||||
|
||||
mapId = mapEntry->ParentMapID;
|
||||
} while (true);
|
||||
|
||||
VMAP::VMapManager* globalManager = VMAP::VMapFactory::createOrGetVMapManager();
|
||||
vmgr->GetLiquidFlagsPtr = globalManager->GetLiquidFlagsPtr;
|
||||
vmgr->IsVMAPDisabledForPtr = globalManager->IsVMAPDisabledForPtr;
|
||||
vmgr->LoadPathOnlyModels = globalManager->LoadPathOnlyModels;
|
||||
return vmgr;
|
||||
}
|
||||
|
||||
struct TileCache
|
||||
{
|
||||
static TileCache* Instance()
|
||||
{
|
||||
static TileCache tc;
|
||||
return &tc;
|
||||
}
|
||||
|
||||
struct Tile
|
||||
{
|
||||
std::shared_ptr<MMAP::DynamicTileBuilder::AsyncTileResult> Data;
|
||||
TimePoint LastAccessed;
|
||||
};
|
||||
|
||||
static constexpr TimePoint::duration CACHE_CLEANUP_INTERVAL = 5min;
|
||||
static constexpr TimePoint::duration CACHE_MAX_AGE = 30min;
|
||||
|
||||
std::mutex TilesMutex;
|
||||
std::unordered_map<TileCacheKey, Tile> Tiles;
|
||||
|
||||
TileCache() :
|
||||
_taskContext(1),
|
||||
_cacheCleanupTimer(_taskContext, CACHE_CLEANUP_INTERVAL)
|
||||
{
|
||||
MMAP::CreateVMapManager = &CreateVMapManager;
|
||||
|
||||
// init timer
|
||||
OnCacheCleanupTimerTick({});
|
||||
|
||||
// start the worker
|
||||
_builderThread = std::thread([this] { _taskContext.run(); });
|
||||
}
|
||||
|
||||
TileCache(TileCache const&) = delete;
|
||||
TileCache(TileCache&&) = delete;
|
||||
|
||||
TileCache& operator=(TileCache const&) = delete;
|
||||
TileCache& operator=(TileCache&&) = delete;
|
||||
|
||||
~TileCache()
|
||||
{
|
||||
_cacheCleanupTimer.cancel();
|
||||
_builderThread.join();
|
||||
}
|
||||
|
||||
template <typename Task>
|
||||
auto StartTask(Task&& task)
|
||||
{
|
||||
return Trinity::Asio::post(_taskContext, std::forward<Task>(task));
|
||||
}
|
||||
|
||||
private:
|
||||
void OnCacheCleanupTimerTick(boost::system::error_code const& error)
|
||||
{
|
||||
if (error)
|
||||
return;
|
||||
|
||||
TimePoint now = GameTime::Now();
|
||||
RemoveOldCacheEntries(now - CACHE_MAX_AGE);
|
||||
_cacheCleanupTimer.expires_at(now + CACHE_CLEANUP_INTERVAL);
|
||||
_cacheCleanupTimer.async_wait([this](boost::system::error_code const& error) { OnCacheCleanupTimerTick(error); });
|
||||
}
|
||||
|
||||
void RemoveOldCacheEntries(TimePoint oldestPreservedEntryTimestamp)
|
||||
{
|
||||
std::lock_guard lock(TilesMutex);
|
||||
Trinity::Containers::EraseIf(Tiles, [=](std::unordered_map<TileCacheKey, Tile>::value_type const& kv)
|
||||
{
|
||||
return kv.second.LastAccessed < oldestPreservedEntryTimestamp;
|
||||
});
|
||||
}
|
||||
|
||||
Trinity::Asio::IoContext _taskContext;
|
||||
Trinity::Asio::DeadlineTimer _cacheCleanupTimer;
|
||||
std::thread _builderThread;
|
||||
};
|
||||
}
|
||||
|
||||
namespace MMAP
|
||||
{
|
||||
struct DynamicTileBuilder::TileId
|
||||
{
|
||||
uint32 TerrainMapId;
|
||||
uint32 X;
|
||||
uint32 Y;
|
||||
|
||||
friend bool operator==(TileId const&, TileId const&) = default;
|
||||
};
|
||||
|
||||
struct TileBuildRequest
|
||||
{
|
||||
DynamicTileBuilder::TileId Id;
|
||||
std::weak_ptr<DynamicTileBuilder::AsyncTileResult> Result;
|
||||
dtNavMesh* NavMesh;
|
||||
};
|
||||
|
||||
bool InvokeAsyncCallbackIfReady(TileBuildRequest& request)
|
||||
{
|
||||
std::shared_ptr<DynamicTileBuilder::AsyncTileResult> result = request.Result.lock();
|
||||
if (!result)
|
||||
return true; // expired, mark as complete and do nothing
|
||||
|
||||
if (!result->IsReady.load(std::memory_order::acquire))
|
||||
return false;
|
||||
|
||||
TileBuilder::TileResult const& tileResult = result->Result;
|
||||
if (tileResult.data)
|
||||
{
|
||||
dtMeshHeader const* header = reinterpret_cast<dtMeshHeader const*>(tileResult.data.get());
|
||||
|
||||
if (dtTileRef tileRef = request.NavMesh->getTileRefAt(header->x, header->y, 0))
|
||||
{
|
||||
TC_LOG_INFO("maps.mmapgen", "[Map {:04}] [{:02},{:02}]: Swapping new tile", request.Id.TerrainMapId, request.Id.Y, request.Id.X);
|
||||
|
||||
request.NavMesh->removeTile(tileRef, nullptr, nullptr);
|
||||
|
||||
unsigned char* data = static_cast<unsigned char*>(dtAlloc(tileResult.size, DT_ALLOC_PERM));
|
||||
std::memcpy(data, tileResult.data.get(), tileResult.size);
|
||||
|
||||
request.NavMesh->addTile(data, tileResult.size, DT_TILE_FREE_DATA, tileRef, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static constexpr auto SetAsyncCallbackReady = [](DynamicTileBuilder::AsyncTileResult* result)
|
||||
{
|
||||
result->IsReady.store(true, std::memory_order::release);
|
||||
};
|
||||
|
||||
DynamicTileBuilder::DynamicTileBuilder(Map* map, dtNavMesh* navMesh) : TileBuilder(sWorld->GetDataPath(), sWorld->GetDataPath(), {}, {}, false, false, false, nullptr),
|
||||
m_map(map), m_navMesh(navMesh), m_rebuildCheckTimer(1s)
|
||||
{
|
||||
}
|
||||
|
||||
DynamicTileBuilder::~DynamicTileBuilder() = default;
|
||||
|
||||
void DynamicTileBuilder::AddTile(uint32 terrainMapId, uint32 tileX, uint32 tileY)
|
||||
{
|
||||
TileId id = { .TerrainMapId = terrainMapId, .X = tileX, .Y = tileY };
|
||||
if (!advstd::ranges::contains(m_tilesToRebuild, id))
|
||||
m_tilesToRebuild.push_back(id);
|
||||
}
|
||||
|
||||
void DynamicTileBuilder::Update(Milliseconds diff)
|
||||
{
|
||||
m_rebuildCheckTimer.Update(diff);
|
||||
if (m_rebuildCheckTimer.Passed())
|
||||
{
|
||||
for (TileId const& tileId : m_tilesToRebuild)
|
||||
m_tiles.AddCallback({ .Id = tileId, .Result = BuildTile(tileId.TerrainMapId, tileId.X, tileId.Y), .NavMesh = m_navMesh });
|
||||
|
||||
m_tilesToRebuild.clear();
|
||||
m_rebuildCheckTimer.Reset(1s);
|
||||
}
|
||||
|
||||
m_tiles.ProcessReadyCallbacks();
|
||||
}
|
||||
|
||||
std::weak_ptr<DynamicTileBuilder::AsyncTileResult> DynamicTileBuilder::BuildTile(uint32 terrainMapId, uint32 tileX, uint32 tileY)
|
||||
{
|
||||
std::vector<std::shared_ptr<GameObjectModel const>> gameObjectModelReferences; // hold strong refs to models
|
||||
for (GameObjectModel const* gameObjectModel : m_map->GetGameObjectModelsInGrid(tileX, tileY))
|
||||
{
|
||||
if (!gameObjectModel->IsMapObject() || !gameObjectModel->IsIncludedInNavMesh())
|
||||
continue;
|
||||
|
||||
std::shared_ptr<VMAP::WorldModel const> worldModel = gameObjectModel->GetWorldModel();
|
||||
if (!worldModel)
|
||||
continue;
|
||||
|
||||
gameObjectModelReferences.emplace_back(worldModel, gameObjectModel);
|
||||
}
|
||||
|
||||
TileCacheKey cacheKey{ .TerrainMapId = terrainMapId, .X = tileX, .Y = tileY, .CachedHash = 0, .Objects = std::vector<TileCacheKeyObject>(gameObjectModelReferences.size()) };
|
||||
for (std::size_t i = 0; i < gameObjectModelReferences.size(); ++i)
|
||||
{
|
||||
GameObjectModel const* gameObjectModel = gameObjectModelReferences[i].get();
|
||||
TileCacheKeyObject& object = cacheKey.Objects[i];
|
||||
object.DisplayId = gameObjectModel->GetDisplayId();
|
||||
object.Scale = int16(gameObjectModel->GetScale() * 1024.0f);
|
||||
object.Position = [](G3D::Vector3 const& pos) -> std::array<int16, 3>
|
||||
{
|
||||
return { int16(pos.x), int16(pos.y), int16(pos.z) };
|
||||
}(gameObjectModel->GetPosition());
|
||||
object.Rotation = gameObjectModel->GetPackedRotation();
|
||||
}
|
||||
|
||||
// Ensure spawn order is stable after adding/removing gameobjects from the map for hash calculation
|
||||
std::ranges::sort(cacheKey.Objects);
|
||||
|
||||
cacheKey.CachedHash = std::hash<TileCacheKey>::Compute(cacheKey);
|
||||
|
||||
TileCache* tileCache = TileCache::Instance();
|
||||
std::lock_guard lock(tileCache->TilesMutex);
|
||||
auto [itr, isNew] = tileCache->Tiles.try_emplace(std::move(cacheKey));
|
||||
itr->second.LastAccessed = GameTime::Now();
|
||||
if (!isNew)
|
||||
return itr->second.Data;
|
||||
|
||||
itr->second.Data = std::make_shared<AsyncTileResult>();
|
||||
tileCache->StartTask([result = itr->second.Data, hash = cacheKey.CachedHash, selfRef = weak_from_this(), terrainMapId, tileX, tileY, gameObjectModelReferences = std::move(gameObjectModelReferences)]() mutable
|
||||
{
|
||||
auto isReadyGuard = Trinity::make_unique_ptr_with_deleter<SetAsyncCallbackReady>(result.get());
|
||||
|
||||
std::shared_ptr<DynamicTileBuilder> self = selfRef.lock();
|
||||
if (!self)
|
||||
return;
|
||||
|
||||
// get navmesh params
|
||||
dtNavMeshParams params;
|
||||
std::vector<OffMeshData> offMeshConnections;
|
||||
if (MMapManager::parseNavMeshParamsFile(sWorld->GetDataPath(), terrainMapId, ¶ms, &offMeshConnections) != LoadResult::Success)
|
||||
return;
|
||||
|
||||
std::unique_ptr<VMAP::VMapManager> vmapManager = CreateVMapManager(terrainMapId);
|
||||
|
||||
MeshData meshData;
|
||||
|
||||
// get heightmap data
|
||||
self->m_terrainBuilder.loadMap(terrainMapId, tileX, tileY, meshData, vmapManager.get());
|
||||
|
||||
// get model data
|
||||
self->m_terrainBuilder.loadVMap(terrainMapId, tileX, tileY, meshData, vmapManager.get());
|
||||
|
||||
for (std::shared_ptr<GameObjectModel const> const& gameObjectModel : gameObjectModelReferences)
|
||||
{
|
||||
G3D::Vector3 position = gameObjectModel->GetPosition();
|
||||
position.x = -position.x;
|
||||
position.y = -position.y;
|
||||
|
||||
G3D::Matrix3 invRotation = (G3D::Quat(0, 0, 1, 0) * gameObjectModel->GetRotation()).toRotationMatrix().inverse();
|
||||
|
||||
self->m_terrainBuilder.loadVMapModel(gameObjectModel->GetWorldModel().get(), position, invRotation, gameObjectModel->GetScale(),
|
||||
meshData, vmapManager.get());
|
||||
}
|
||||
|
||||
// if there is no data, give up now
|
||||
if (meshData.solidVerts.empty() && meshData.liquidVerts.empty())
|
||||
return;
|
||||
|
||||
// remove unused vertices
|
||||
TerrainBuilder::cleanVertices(meshData.solidVerts, meshData.solidTris);
|
||||
TerrainBuilder::cleanVertices(meshData.liquidVerts, meshData.liquidTris);
|
||||
|
||||
// gather all mesh data for final data check, and bounds calculation
|
||||
std::vector<float> allVerts(meshData.liquidVerts.size() + meshData.solidVerts.size());
|
||||
std::ranges::copy(meshData.liquidVerts, allVerts.begin());
|
||||
std::ranges::copy(meshData.solidVerts, allVerts.begin() + std::ssize(meshData.liquidVerts));
|
||||
|
||||
// get bounds of current tile
|
||||
float bmin[3], bmax[3];
|
||||
getTileBounds(tileX, tileY, allVerts.data(), allVerts.size() / 3, bmin, bmax);
|
||||
|
||||
self->m_terrainBuilder.loadOffMeshConnections(terrainMapId, tileX, tileY, meshData, offMeshConnections);
|
||||
|
||||
// build navmesh tile
|
||||
std::string debugSuffix = Trinity::StringFormat("_{:016X}", hash);
|
||||
|
||||
result->Result = self->buildMoveMapTile(terrainMapId, tileX, tileY, meshData, bmin, bmax, ¶ms);
|
||||
if (self->m_debugOutput && result->Result.data)
|
||||
self->saveMoveMapTileToFile(terrainMapId, tileX, tileY, nullptr, result->Result, debugSuffix);
|
||||
});
|
||||
|
||||
return itr->second.Data;
|
||||
}
|
||||
}
|
||||
69
src/server/game/Maps/DynamicMMapTileBuilder.h
Normal file
69
src/server/game/Maps/DynamicMMapTileBuilder.h
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TRINITYCORE_DYNAMIC_MMAP_TILE_BUILDER_H
|
||||
#define TRINITYCORE_DYNAMIC_MMAP_TILE_BUILDER_H
|
||||
|
||||
#include "AsyncCallbackProcessor.h"
|
||||
#include "TileBuilder.h"
|
||||
#include "Timer.h"
|
||||
#include <atomic>
|
||||
|
||||
class Map;
|
||||
|
||||
namespace MMAP
|
||||
{
|
||||
struct TileBuildRequest;
|
||||
bool InvokeAsyncCallbackIfReady(TileBuildRequest& request);
|
||||
|
||||
class DynamicTileBuilder : public TileBuilder, public std::enable_shared_from_this<DynamicTileBuilder>
|
||||
{
|
||||
public:
|
||||
explicit DynamicTileBuilder(Map* map, dtNavMesh* navMesh);
|
||||
DynamicTileBuilder(DynamicTileBuilder const& other) = delete;
|
||||
DynamicTileBuilder(DynamicTileBuilder&& other) = delete;
|
||||
DynamicTileBuilder& operator=(DynamicTileBuilder const& other) = delete;
|
||||
DynamicTileBuilder& operator=(DynamicTileBuilder&& other) = delete;
|
||||
~DynamicTileBuilder();
|
||||
|
||||
void AddTile(uint32 terrainMapId, uint32 tileX, uint32 tileY);
|
||||
|
||||
void Update(Milliseconds diff);
|
||||
|
||||
struct AsyncTileResult
|
||||
{
|
||||
TileResult Result;
|
||||
std::atomic<bool> IsReady;
|
||||
};
|
||||
|
||||
private:
|
||||
std::weak_ptr<AsyncTileResult> BuildTile(uint32 terrainMapId, uint32 tileX, uint32 tileY);
|
||||
|
||||
Map* m_map;
|
||||
dtNavMesh* m_navMesh;
|
||||
|
||||
struct TileId;
|
||||
std::vector<TileId> m_tilesToRebuild;
|
||||
|
||||
TimeTracker m_rebuildCheckTimer;
|
||||
|
||||
friend TileBuildRequest;
|
||||
AsyncCallbackProcessor<TileBuildRequest> m_tiles;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // TRINITYCORE_DYNAMIC_MMAP_TILE_BUILDER_H
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "DB2Stores.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "DynamicTree.h"
|
||||
#include "DynamicMMapTileBuilder.h"
|
||||
#include "GameObjectModel.h"
|
||||
#include "GameTime.h"
|
||||
#include "GridNotifiers.h"
|
||||
@@ -36,6 +37,7 @@
|
||||
#include "InstanceScenario.h"
|
||||
#include "InstanceScript.h"
|
||||
#include "Log.h"
|
||||
#include "MMapManager.h"
|
||||
#include "MapManager.h"
|
||||
#include "MapUtils.h"
|
||||
#include "Metric.h"
|
||||
@@ -154,6 +156,9 @@ i_scriptLock(false), _respawnTimes(std::make_unique<RespawnListContainer>()), _r
|
||||
|
||||
m_terrain->LoadMMapInstance(GetId(), GetInstanceId());
|
||||
|
||||
if (MMAP::MMapManager::isRebuildingTilesEnabledOnMap(GetId()))
|
||||
m_mmapTileRebuilder = std::make_shared<MMAP::DynamicTileBuilder>(this, MMAP::MMapManager::instance()->GetNavMesh(GetId(), GetInstanceId()));
|
||||
|
||||
_worldStateValues = sWorldStateMgr->GetInitialWorldStatesForMap(this);
|
||||
}
|
||||
|
||||
@@ -648,6 +653,9 @@ void Map::UpdatePlayerZoneStats(uint32 oldZone, uint32 newZone)
|
||||
void Map::Update(uint32 t_diff)
|
||||
{
|
||||
_dynamicTree.update(t_diff);
|
||||
if (m_mmapTileRebuilder)
|
||||
m_mmapTileRebuilder->Update(Milliseconds(t_diff));
|
||||
|
||||
/// update worldsessions for existing players
|
||||
for (m_mapRefIter = m_mapRefManager.begin(); m_mapRefIter != m_mapRefManager.end(); ++m_mapRefIter)
|
||||
{
|
||||
@@ -1764,6 +1772,28 @@ bool Map::getObjectHitPos(PhaseShift const& phaseShift, float x1, float y1, floa
|
||||
return result;
|
||||
}
|
||||
|
||||
void Map::RequestRebuildNavMeshOnGameObjectModelChange(GameObjectModel const& model, PhaseShift const& phaseShift)
|
||||
{
|
||||
if (!m_mmapTileRebuilder)
|
||||
return;
|
||||
|
||||
uint32 terrainMapId = PhasingHandler::GetTerrainMapId(phaseShift, GetId(), m_terrain.get(), model.GetPosition().x, model.GetPosition().y);
|
||||
|
||||
G3D::AABox const& bounds = model.getBounds();
|
||||
|
||||
GridCoord low = Trinity::ComputeGridCoord(bounds.high().x, bounds.high().y);
|
||||
low.x_coord = (MAX_NUMBER_OF_GRIDS - 1) - low.x_coord;
|
||||
low.y_coord = (MAX_NUMBER_OF_GRIDS - 1) - low.y_coord;
|
||||
|
||||
GridCoord high = Trinity::ComputeGridCoord(bounds.low().x, bounds.low().y);
|
||||
high.x_coord = (MAX_NUMBER_OF_GRIDS - 1) - high.x_coord;
|
||||
high.y_coord = (MAX_NUMBER_OF_GRIDS - 1) - high.y_coord;
|
||||
|
||||
for (uint32 x = low.x_coord; x <= high.x_coord; ++x)
|
||||
for (uint32 y = low.y_coord; y <= high.y_coord; ++y)
|
||||
m_mmapTileRebuilder->AddTile(terrainMapId, x, y);
|
||||
}
|
||||
|
||||
TransferAbortParams Map::PlayerCannotEnter(uint32 mapid, Player* player)
|
||||
{
|
||||
MapEntry const* entry = sMapStore.LookupEntry(mapid);
|
||||
|
||||
@@ -83,6 +83,7 @@ enum class ItemContext : uint8;
|
||||
namespace Trinity { struct ObjectUpdater; }
|
||||
namespace Vignettes { struct VignetteData; }
|
||||
namespace VMAP { enum class ModelIgnoreFlags : uint32; }
|
||||
namespace MMAP { class DynamicTileBuilder; }
|
||||
|
||||
enum TransferAbortReason : uint32
|
||||
{
|
||||
@@ -496,12 +497,15 @@ class TC_GAME_API Map : public GridRefManager<NGridType>
|
||||
void RemoveGameObjectModel(GameObjectModel const& model) { _dynamicTree.remove(model); }
|
||||
void InsertGameObjectModel(GameObjectModel const& model) { _dynamicTree.insert(model); }
|
||||
bool ContainsGameObjectModel(GameObjectModel const& model) const { return _dynamicTree.contains(model);}
|
||||
std::span<GameObjectModel const* const> GetGameObjectModelsInGrid(uint32 gx, uint32 gy) const { return _dynamicTree.getModelsInGrid(gx, gy); }
|
||||
float GetGameObjectFloor(PhaseShift const& phaseShift, float x, float y, float z, float maxSearchDist = DEFAULT_HEIGHT_SEARCH) const
|
||||
{
|
||||
return _dynamicTree.getHeight(x, y, z, maxSearchDist, phaseShift);
|
||||
}
|
||||
bool getObjectHitPos(PhaseShift const& phaseShift, float x1, float y1, float z1, float x2, float y2, float z2, float& rx, float &ry, float& rz, float modifyDist);
|
||||
|
||||
void RequestRebuildNavMeshOnGameObjectModelChange(GameObjectModel const& model, PhaseShift const& phaseShift);
|
||||
|
||||
virtual ObjectGuid::LowType GetOwnerGuildId(uint32 /*team*/ = TEAM_OTHER) const { return UI64LIT(0); }
|
||||
/*
|
||||
RESPAWN TIMES
|
||||
@@ -647,6 +651,7 @@ class TC_GAME_API Map : public GridRefManager<NGridType>
|
||||
uint32 m_unloadTimer;
|
||||
float m_VisibleDistance;
|
||||
DynamicMapTree _dynamicTree;
|
||||
std::shared_ptr<MMAP::DynamicTileBuilder> m_mmapTileRebuilder;
|
||||
|
||||
MapRefManager m_mapRefManager;
|
||||
MapRefManager::iterator m_mapRefIter;
|
||||
|
||||
Reference in New Issue
Block a user