mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2024-11-21 21:17:25 +00:00
Move Navmesh code away from dPhysics (#701)
This commit is contained in:
parent
a0aa8b2854
commit
9ee219ea42
@ -14,7 +14,7 @@ string(REPLACE "\n" ";" variables ${variables})
|
|||||||
foreach(variable ${variables})
|
foreach(variable ${variables})
|
||||||
# If the string contains a #, skip it
|
# If the string contains a #, skip it
|
||||||
if(NOT "${variable}" MATCHES "#")
|
if(NOT "${variable}" MATCHES "#")
|
||||||
|
|
||||||
# Split the variable into name and value
|
# Split the variable into name and value
|
||||||
string(REPLACE "=" ";" variable ${variable})
|
string(REPLACE "=" ";" variable ${variable})
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ set(PROJECT_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT
|
|||||||
message(STATUS "Version: ${PROJECT_VERSION}")
|
message(STATUS "Version: ${PROJECT_VERSION}")
|
||||||
|
|
||||||
# Compiler flags:
|
# Compiler flags:
|
||||||
# Disabled deprecated warnings as the MySQL includes have deprecated code in them.
|
# Disabled deprecated warnings as the MySQL includes have deprecated code in them.
|
||||||
# Disabled misleading indentation as DL_LinkedList from RakNet has a weird indent.
|
# Disabled misleading indentation as DL_LinkedList from RakNet has a weird indent.
|
||||||
# Disabled no-register
|
# Disabled no-register
|
||||||
# Disabled unknown pragmas because Linux doesn't understand Windows pragmas.
|
# Disabled unknown pragmas because Linux doesn't understand Windows pragmas.
|
||||||
@ -118,17 +118,18 @@ endforeach()
|
|||||||
set(INCLUDED_DIRECTORIES
|
set(INCLUDED_DIRECTORIES
|
||||||
"dCommon"
|
"dCommon"
|
||||||
"dChatFilter"
|
"dChatFilter"
|
||||||
"dGame"
|
"dGame"
|
||||||
"dGame/dBehaviors"
|
"dGame/dBehaviors"
|
||||||
"dGame/dComponents"
|
"dGame/dComponents"
|
||||||
"dGame/dGameMessages"
|
"dGame/dGameMessages"
|
||||||
"dGame/dInventory"
|
"dGame/dInventory"
|
||||||
"dGame/dMission"
|
"dGame/dMission"
|
||||||
"dGame/dEntity"
|
"dGame/dEntity"
|
||||||
"dGame/dUtilities"
|
"dGame/dUtilities"
|
||||||
"dPhysics"
|
"dPhysics"
|
||||||
"dZoneManager"
|
"dNavigation"
|
||||||
"dDatabase"
|
"dZoneManager"
|
||||||
|
"dDatabase"
|
||||||
"dDatabase/Tables"
|
"dDatabase/Tables"
|
||||||
"dNet"
|
"dNet"
|
||||||
"dScripts"
|
"dScripts"
|
||||||
@ -192,7 +193,7 @@ file(
|
|||||||
file(
|
file(
|
||||||
GLOB HEADERS_DGAME
|
GLOB HEADERS_DGAME
|
||||||
LIST_DIRECTORIES false
|
LIST_DIRECTORIES false
|
||||||
${PROJECT_SOURCE_DIR}/dGame/Entity.h
|
${PROJECT_SOURCE_DIR}/dGame/Entity.h
|
||||||
${PROJECT_SOURCE_DIR}/dGame/dGameMessages/GameMessages.h
|
${PROJECT_SOURCE_DIR}/dGame/dGameMessages/GameMessages.h
|
||||||
${PROJECT_SOURCE_DIR}/dGame/EntityManager.h
|
${PROJECT_SOURCE_DIR}/dGame/EntityManager.h
|
||||||
${PROJECT_SOURCE_DIR}/dScripts/CppScripts.h
|
${PROJECT_SOURCE_DIR}/dScripts/CppScripts.h
|
||||||
@ -206,6 +207,7 @@ add_subdirectory(dNet)
|
|||||||
add_subdirectory(dScripts) # Add for dGame to use
|
add_subdirectory(dScripts) # Add for dGame to use
|
||||||
add_subdirectory(dGame)
|
add_subdirectory(dGame)
|
||||||
add_subdirectory(dZoneManager)
|
add_subdirectory(dZoneManager)
|
||||||
|
add_subdirectory(dNavigation)
|
||||||
add_subdirectory(dPhysics)
|
add_subdirectory(dPhysics)
|
||||||
|
|
||||||
# Create a list of common libraries shared between all binaries
|
# Create a list of common libraries shared between all binaries
|
||||||
@ -230,18 +232,18 @@ add_subdirectory(dMasterServer) # Add MasterServer last so it can rely on the ot
|
|||||||
|
|
||||||
# Add our precompiled headers
|
# Add our precompiled headers
|
||||||
target_precompile_headers(
|
target_precompile_headers(
|
||||||
dGame PRIVATE
|
dGame PRIVATE
|
||||||
${HEADERS_DGAME}
|
${HEADERS_DGAME}
|
||||||
)
|
)
|
||||||
|
|
||||||
target_precompile_headers(
|
target_precompile_headers(
|
||||||
dZoneManager PRIVATE
|
dZoneManager PRIVATE
|
||||||
${HEADERS_DZONEMANAGER}
|
${HEADERS_DZONEMANAGER}
|
||||||
)
|
)
|
||||||
|
|
||||||
# Need to specify to use the CXX compiler language here or else we get errors including <string>.
|
# Need to specify to use the CXX compiler language here or else we get errors including <string>.
|
||||||
target_precompile_headers(
|
target_precompile_headers(
|
||||||
dDatabase PRIVATE
|
dDatabase PRIVATE
|
||||||
"$<$<COMPILE_LANGUAGE:CXX>:${HEADERS_DDATABASE}>"
|
"$<$<COMPILE_LANGUAGE:CXX>:${HEADERS_DDATABASE}>"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -253,4 +255,4 @@ target_precompile_headers(
|
|||||||
target_precompile_headers(
|
target_precompile_headers(
|
||||||
tinyxml2 PRIVATE
|
tinyxml2 PRIVATE
|
||||||
"$<$<COMPILE_LANGUAGE:CXX>:${PROJECT_SOURCE_DIR}/thirdparty/tinyxml2/tinyxml2.h>"
|
"$<$<COMPILE_LANGUAGE:CXX>:${PROJECT_SOURCE_DIR}/thirdparty/tinyxml2/tinyxml2.h>"
|
||||||
)
|
)
|
||||||
|
@ -654,7 +654,7 @@ void BaseCombatAIComponent::Wander() {
|
|||||||
auto destination = m_StartPosition + delta;
|
auto destination = m_StartPosition + delta;
|
||||||
|
|
||||||
if (dpWorld::Instance().IsLoaded()) {
|
if (dpWorld::Instance().IsLoaded()) {
|
||||||
destination.y = dpWorld::Instance().GetHeightAtPoint(destination);
|
destination.y = dpWorld::Instance().GetNavMesh()->GetHeightAtPoint(destination);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Vector3::DistanceSquared(destination, m_MovementAI->GetCurrentPosition()) < 2 * 2) {
|
if (Vector3::DistanceSquared(destination, m_MovementAI->GetCurrentPosition()) < 2 * 2) {
|
||||||
|
@ -196,7 +196,7 @@ NiPoint3 MovementAIComponent::ApproximateLocation() const {
|
|||||||
NiPoint3 approximation = NiPoint3(x, y, z);
|
NiPoint3 approximation = NiPoint3(x, y, z);
|
||||||
|
|
||||||
if (dpWorld::Instance().IsLoaded()) {
|
if (dpWorld::Instance().IsLoaded()) {
|
||||||
approximation.y = dpWorld::Instance().GetHeightAtPoint(approximation);
|
approximation.y = dpWorld::Instance().GetNavMesh()->GetHeightAtPoint(approximation);
|
||||||
}
|
}
|
||||||
|
|
||||||
return approximation;
|
return approximation;
|
||||||
@ -208,7 +208,7 @@ bool MovementAIComponent::Warp(const NiPoint3& point) {
|
|||||||
NiPoint3 destination = point;
|
NiPoint3 destination = point;
|
||||||
|
|
||||||
if (dpWorld::Instance().IsLoaded()) {
|
if (dpWorld::Instance().IsLoaded()) {
|
||||||
destination.y = dpWorld::Instance().GetHeightAtPoint(point);
|
destination.y = dpWorld::Instance().GetNavMesh()->GetHeightAtPoint(point);
|
||||||
|
|
||||||
if (std::abs(destination.y - point.y) > 3) {
|
if (std::abs(destination.y - point.y) > 3) {
|
||||||
return false;
|
return false;
|
||||||
@ -387,7 +387,7 @@ void MovementAIComponent::SetDestination(const NiPoint3& value) {
|
|||||||
std::vector<NiPoint3> computedPath;
|
std::vector<NiPoint3> computedPath;
|
||||||
|
|
||||||
if (dpWorld::Instance().IsLoaded()) {
|
if (dpWorld::Instance().IsLoaded()) {
|
||||||
computedPath = dpWorld::Instance().GetPath(GetCurrentPosition(), value, m_Info.wanderSpeed);
|
computedPath = dpWorld::Instance().GetNavMesh()->GetPath(GetCurrentPosition(), value, m_Info.wanderSpeed);
|
||||||
} else {
|
} else {
|
||||||
// Than take 10 points between the current position and the destination and make that the path
|
// Than take 10 points between the current position and the destination and make that the path
|
||||||
|
|
||||||
@ -416,7 +416,7 @@ void MovementAIComponent::SetDestination(const NiPoint3& value) {
|
|||||||
// Simply path
|
// Simply path
|
||||||
for (auto point : computedPath) {
|
for (auto point : computedPath) {
|
||||||
if (dpWorld::Instance().IsLoaded()) {
|
if (dpWorld::Instance().IsLoaded()) {
|
||||||
point.y = dpWorld::Instance().GetHeightAtPoint(point);
|
point.y = dpWorld::Instance().GetNavMesh()->GetHeightAtPoint(point);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_CurrentPath.push_back(point);
|
m_CurrentPath.push_back(point);
|
||||||
|
@ -267,14 +267,14 @@ void PetComponent::OnUse(Entity* originator) {
|
|||||||
if (dpWorld::Instance().IsLoaded()) {
|
if (dpWorld::Instance().IsLoaded()) {
|
||||||
NiPoint3 attempt = petPosition + forward * interactionDistance;
|
NiPoint3 attempt = petPosition + forward * interactionDistance;
|
||||||
|
|
||||||
float y = dpWorld::Instance().GetHeightAtPoint(attempt);
|
float y = dpWorld::Instance().GetNavMesh()->GetHeightAtPoint(attempt);
|
||||||
|
|
||||||
while (std::abs(y - petPosition.y) > 4 && interactionDistance > 10) {
|
while (std::abs(y - petPosition.y) > 4 && interactionDistance > 10) {
|
||||||
const NiPoint3 forward = m_Parent->GetRotation().GetForwardVector();
|
const NiPoint3 forward = m_Parent->GetRotation().GetForwardVector();
|
||||||
|
|
||||||
attempt = originatorPosition + forward * interactionDistance;
|
attempt = originatorPosition + forward * interactionDistance;
|
||||||
|
|
||||||
y = dpWorld::Instance().GetHeightAtPoint(attempt);
|
y = dpWorld::Instance().GetNavMesh()->GetHeightAtPoint(attempt);
|
||||||
|
|
||||||
interactionDistance -= 0.5f;
|
interactionDistance -= 0.5f;
|
||||||
}
|
}
|
||||||
@ -819,7 +819,7 @@ void PetComponent::Wander() {
|
|||||||
auto destination = m_StartPosition + delta;
|
auto destination = m_StartPosition + delta;
|
||||||
|
|
||||||
if (dpWorld::Instance().IsLoaded()) {
|
if (dpWorld::Instance().IsLoaded()) {
|
||||||
destination.y = dpWorld::Instance().GetHeightAtPoint(destination);
|
destination.y = dpWorld::Instance().GetNavMesh()->GetHeightAtPoint(destination);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Vector3::DistanceSquared(destination, m_MovementAI->GetCurrentPosition()) < 2 * 2) {
|
if (Vector3::DistanceSquared(destination, m_MovementAI->GetCurrentPosition()) < 2 * 2) {
|
||||||
|
@ -733,7 +733,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
|
|||||||
auto control = static_cast<ControllablePhysicsComponent*>(entity->GetComponent(COMPONENT_TYPE_CONTROLLABLE_PHYSICS));
|
auto control = static_cast<ControllablePhysicsComponent*>(entity->GetComponent(COMPONENT_TYPE_CONTROLLABLE_PHYSICS));
|
||||||
if (!control) return;
|
if (!control) return;
|
||||||
|
|
||||||
float y = dpWorld::Instance().GetHeightAtPoint(control->GetPosition());
|
float y = dpWorld::Instance().GetNavMesh()->GetHeightAtPoint(control->GetPosition());
|
||||||
std::u16string msg = u"Navmesh height: " + (GeneralUtils::to_u16string(y));
|
std::u16string msg = u"Navmesh height: " + (GeneralUtils::to_u16string(y));
|
||||||
ChatPackets::SendSystemMessage(sysAddr, msg);
|
ChatPackets::SendSystemMessage(sysAddr, msg);
|
||||||
}
|
}
|
||||||
|
4
dNavigation/CMakeLists.txt
Normal file
4
dNavigation/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
set(DNAVIGATION_SOURCES "dNavMesh.cpp")
|
||||||
|
|
||||||
|
add_library(dNavigation STATIC ${DNAVIGATION_SOURCES})
|
||||||
|
target_link_libraries(dNavigation detour recast)
|
25
dNavigation/DetourExtensions.h
Normal file
25
dNavigation/DetourExtensions.h
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Recast.h"
|
||||||
|
#include "DetourCommon.h"
|
||||||
|
#include "DetourNavMesh.h"
|
||||||
|
#include "DetourNavMeshBuilder.h"
|
||||||
|
#include "DetourNavMeshQuery.h"
|
||||||
|
|
||||||
|
static const int NAVMESHSET_MAGIC = 'M' << 24 | 'S' << 16 | 'E' << 8 | 'T'; // char[4] of 'MSET'
|
||||||
|
static const int NAVMESHSET_VERSION = 1;
|
||||||
|
|
||||||
|
struct NavMeshSetHeader {
|
||||||
|
int magic;
|
||||||
|
int version;
|
||||||
|
int numTiles;
|
||||||
|
dtNavMeshParams params;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct NavMeshTileHeader {
|
||||||
|
dtTileRef tileRef;
|
||||||
|
int dataSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const int MAX_POLYS = 256;
|
||||||
|
static const int MAX_SMOOTH = 2048;
|
228
dNavigation/dNavMesh.cpp
Normal file
228
dNavigation/dNavMesh.cpp
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
#include "dNavMesh.h"
|
||||||
|
|
||||||
|
#include "Game.h"
|
||||||
|
#include "dLogger.h"
|
||||||
|
#include "dPlatforms.h"
|
||||||
|
#include "NiPoint3.h"
|
||||||
|
#include "BinaryIO.h"
|
||||||
|
|
||||||
|
dNavMesh::dNavMesh(uint32_t zoneId) {
|
||||||
|
m_ZoneId = zoneId;
|
||||||
|
|
||||||
|
this->LoadNavmesh();
|
||||||
|
|
||||||
|
if (m_NavMesh) {
|
||||||
|
m_NavQuery = dtAllocNavMeshQuery();
|
||||||
|
m_NavQuery->init(m_NavMesh, 2048);
|
||||||
|
|
||||||
|
Game::logger->Log("dNavMesh", "Navmesh loaded successfully!");
|
||||||
|
} else {
|
||||||
|
Game::logger->Log("dNavMesh", "Navmesh loading failed (This may be intended).");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dNavMesh::~dNavMesh() {
|
||||||
|
// Clean up Recast information
|
||||||
|
|
||||||
|
rcFreeHeightField(m_Solid);
|
||||||
|
rcFreeCompactHeightfield(m_CHF);
|
||||||
|
rcFreeContourSet(m_CSet);
|
||||||
|
rcFreePolyMesh(m_PMesh);
|
||||||
|
rcFreePolyMeshDetail(m_PMDMesh);
|
||||||
|
dtFreeNavMesh(m_NavMesh);
|
||||||
|
dtFreeNavMeshQuery(m_NavQuery);
|
||||||
|
|
||||||
|
if (m_Ctx) delete m_Ctx;
|
||||||
|
if (m_Triareas) delete[] m_Triareas;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void dNavMesh::LoadNavmesh() {
|
||||||
|
|
||||||
|
std::string path = "./res/maps/navmeshes/" + std::to_string(m_ZoneId) + ".bin";
|
||||||
|
|
||||||
|
if (!BinaryIO::DoesFileExist(path)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE* fp;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
fopen_s(&fp, path.c_str(), "rb");
|
||||||
|
#elif __APPLE__
|
||||||
|
// macOS has 64bit file IO by default
|
||||||
|
fp = fopen(path.c_str(), "rb");
|
||||||
|
#else
|
||||||
|
fp = fopen64(path.c_str(), "rb");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!fp) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read header.
|
||||||
|
NavMeshSetHeader header;
|
||||||
|
size_t readLen = fread(&header, sizeof(NavMeshSetHeader), 1, fp);
|
||||||
|
if (readLen != 1) {
|
||||||
|
fclose(fp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header.magic != NAVMESHSET_MAGIC) {
|
||||||
|
fclose(fp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header.version != NAVMESHSET_VERSION) {
|
||||||
|
fclose(fp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dtNavMesh* mesh = dtAllocNavMesh();
|
||||||
|
if (!mesh) {
|
||||||
|
fclose(fp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dtStatus status = mesh->init(&header.params);
|
||||||
|
if (dtStatusFailed(status)) {
|
||||||
|
fclose(fp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read tiles.
|
||||||
|
for (int i = 0; i < header.numTiles; ++i) {
|
||||||
|
NavMeshTileHeader tileHeader;
|
||||||
|
readLen = fread(&tileHeader, sizeof(tileHeader), 1, fp);
|
||||||
|
if (readLen != 1) return;
|
||||||
|
|
||||||
|
if (!tileHeader.tileRef || !tileHeader.dataSize)
|
||||||
|
break;
|
||||||
|
|
||||||
|
unsigned char* data = (unsigned char*)dtAlloc(tileHeader.dataSize, DT_ALLOC_PERM);
|
||||||
|
if (!data) break;
|
||||||
|
memset(data, 0, tileHeader.dataSize);
|
||||||
|
readLen = fread(data, tileHeader.dataSize, 1, fp);
|
||||||
|
if (readLen != 1) return;
|
||||||
|
|
||||||
|
mesh->addTile(data, tileHeader.dataSize, DT_TILE_FREE_DATA, tileHeader.tileRef, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
m_NavMesh = mesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
float dNavMesh::GetHeightAtPoint(const NiPoint3& location) {
|
||||||
|
if (m_NavMesh == nullptr) {
|
||||||
|
return location.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
float toReturn = 0.0f;
|
||||||
|
float pos[3];
|
||||||
|
pos[0] = location.x;
|
||||||
|
pos[1] = location.y;
|
||||||
|
pos[2] = location.z;
|
||||||
|
|
||||||
|
dtPolyRef nearestRef = 0;
|
||||||
|
float polyPickExt[3] = { 32.0f, 32.0f, 32.0f };
|
||||||
|
dtQueryFilter filter{};
|
||||||
|
|
||||||
|
m_NavQuery->findNearestPoly(pos, polyPickExt, &filter, &nearestRef, 0);
|
||||||
|
m_NavQuery->getPolyHeight(nearestRef, pos, &toReturn);
|
||||||
|
|
||||||
|
if (toReturn == 0.0f) {
|
||||||
|
toReturn = location.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
return toReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<NiPoint3> dNavMesh::GetPath(const NiPoint3& startPos, const NiPoint3& endPos, float speed) {
|
||||||
|
std::vector<NiPoint3> path;
|
||||||
|
|
||||||
|
// Allows for non-navmesh maps (like new custom maps) to have "basic" enemies.
|
||||||
|
if (m_NavMesh == nullptr) {
|
||||||
|
// How many points to generate between start/end?
|
||||||
|
// Note: not actually 100% accurate due to rounding, but worst case it causes them to go a tiny bit faster
|
||||||
|
// than their speed value would normally allow at the end.
|
||||||
|
int numPoints = startPos.Distance(startPos, endPos) / speed;
|
||||||
|
|
||||||
|
path.push_back(startPos); //insert the start pos
|
||||||
|
|
||||||
|
// Linearly interpolate between these two points:
|
||||||
|
for (int i = 0; i < numPoints; i++) {
|
||||||
|
NiPoint3 newPoint{ startPos };
|
||||||
|
|
||||||
|
newPoint.x += speed;
|
||||||
|
newPoint.y = newPoint.y + (((endPos.y - startPos.y) / (endPos.x - startPos.x)) * (newPoint.x - startPos.x));
|
||||||
|
|
||||||
|
path.push_back(newPoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
path.push_back(endPos); //finally insert our end pos
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
float sPos[3];
|
||||||
|
float ePos[3];
|
||||||
|
sPos[0] = startPos.x;
|
||||||
|
sPos[1] = startPos.y;
|
||||||
|
sPos[2] = startPos.z;
|
||||||
|
|
||||||
|
ePos[0] = endPos.x;
|
||||||
|
ePos[1] = endPos.y;
|
||||||
|
ePos[2] = endPos.z;
|
||||||
|
|
||||||
|
dtStatus pathFindStatus;
|
||||||
|
dtPolyRef startRef;
|
||||||
|
dtPolyRef endRef;
|
||||||
|
float polyPickExt[3] = { 32.0f, 32.0f, 32.0f };
|
||||||
|
dtQueryFilter filter{};
|
||||||
|
|
||||||
|
//Find our start poly
|
||||||
|
m_NavQuery->findNearestPoly(sPos, polyPickExt, &filter, &startRef, 0);
|
||||||
|
|
||||||
|
//Find our end poly
|
||||||
|
m_NavQuery->findNearestPoly(ePos, polyPickExt, &filter, &endRef, 0);
|
||||||
|
|
||||||
|
pathFindStatus = DT_FAILURE;
|
||||||
|
int m_nstraightPath = 0;
|
||||||
|
int m_npolys = 0;
|
||||||
|
dtPolyRef m_polys[MAX_POLYS];
|
||||||
|
float m_straightPath[MAX_POLYS * 3];
|
||||||
|
unsigned char m_straightPathFlags[MAX_POLYS];
|
||||||
|
dtPolyRef m_straightPathPolys[MAX_POLYS];
|
||||||
|
int m_straightPathOptions = 0;
|
||||||
|
|
||||||
|
if (startRef && endRef) {
|
||||||
|
m_NavQuery->findPath(startRef, endRef, sPos, ePos, &filter, m_polys, &m_npolys, MAX_POLYS);
|
||||||
|
|
||||||
|
if (m_npolys) {
|
||||||
|
// In case of partial path, make sure the end point is clamped to the last polygon.
|
||||||
|
float epos[3];
|
||||||
|
dtVcopy(epos, ePos);
|
||||||
|
|
||||||
|
if (m_polys[m_npolys - 1] != endRef) {
|
||||||
|
m_NavQuery->closestPointOnPoly(m_polys[m_npolys - 1], ePos, epos, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_NavQuery->findStraightPath(sPos, epos, m_polys, m_npolys,
|
||||||
|
m_straightPath, m_straightPathFlags,
|
||||||
|
m_straightPathPolys, &m_nstraightPath, MAX_POLYS, m_straightPathOptions);
|
||||||
|
|
||||||
|
// At this point we have our path. Copy it to the path store
|
||||||
|
int nIndex = 0;
|
||||||
|
for (int nVert = 0; nVert < m_nstraightPath; nVert++) {
|
||||||
|
NiPoint3 newPoint{ m_straightPath[nIndex++], m_straightPath[nIndex++], m_straightPath[nIndex++] };
|
||||||
|
path.push_back(newPoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m_npolys = 0;
|
||||||
|
m_nstraightPath = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
38
dNavigation/dNavMesh.h
Normal file
38
dNavigation/dNavMesh.h
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "DetourExtensions.h"
|
||||||
|
|
||||||
|
class NiPoint3;
|
||||||
|
|
||||||
|
class dNavMesh {
|
||||||
|
public:
|
||||||
|
dNavMesh(uint32_t zoneId);
|
||||||
|
~dNavMesh();
|
||||||
|
|
||||||
|
float GetHeightAtPoint(const NiPoint3& location);
|
||||||
|
std::vector<NiPoint3> GetPath(const NiPoint3& startPos, const NiPoint3& endPos, float speed = 10.0f);
|
||||||
|
private:
|
||||||
|
void LoadNavmesh();
|
||||||
|
|
||||||
|
uint32_t m_ZoneId;
|
||||||
|
|
||||||
|
uint8_t* m_Triareas = nullptr;
|
||||||
|
rcHeightfield* m_Solid = nullptr;
|
||||||
|
rcCompactHeightfield* m_CHF = nullptr;
|
||||||
|
rcContourSet* m_CSet = nullptr;
|
||||||
|
rcPolyMesh* m_PMesh = nullptr;
|
||||||
|
rcConfig m_Config;
|
||||||
|
rcPolyMeshDetail* m_PMDMesh = nullptr;
|
||||||
|
|
||||||
|
class InputGeom* m_Geometry = nullptr;
|
||||||
|
class dtNavMesh* m_NavMesh = nullptr;
|
||||||
|
class dtNavMeshQuery* m_NavQuery = nullptr;
|
||||||
|
uint8_t m_NavMeshDrawFlags;
|
||||||
|
rcContext* m_Ctx = nullptr;
|
||||||
|
};
|
@ -21,12 +21,9 @@ void dpWorld::Initialize(unsigned int zoneID) {
|
|||||||
m_Grid = new dpGrid(phys_sp_tilecount, phys_sp_tilesize);
|
m_Grid = new dpGrid(phys_sp_tilecount, phys_sp_tilesize);
|
||||||
}
|
}
|
||||||
|
|
||||||
Game::logger->Log("dpWorld", "Physics world initialized!");
|
m_NavMesh = new dNavMesh(zoneID);
|
||||||
|
|
||||||
if (ShouldLoadNavmesh(zoneID)) {
|
Game::logger->Log("dpWorld", "Physics world initialized!");
|
||||||
if (LoadNavmeshByZoneID(zoneID)) Game::logger->Log("dpWorld", "Loaded navmesh!");
|
|
||||||
else Game::logger->Log("dpWorld", "Error(s) occurred during navmesh load.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dpWorld::~dpWorld() {
|
dpWorld::~dpWorld() {
|
||||||
@ -35,7 +32,9 @@ dpWorld::~dpWorld() {
|
|||||||
m_Grid = nullptr;
|
m_Grid = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
RecastCleanup();
|
m_NavMesh->~dNavMesh();
|
||||||
|
delete m_NavMesh;
|
||||||
|
m_NavMesh = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void dpWorld::StepWorld(float deltaTime) {
|
void dpWorld::StepWorld(float deltaTime) {
|
||||||
@ -98,256 +97,20 @@ void dpWorld::RemoveEntity(dpEntity* entity) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void dpWorld::RecastCleanup() {
|
|
||||||
if (m_triareas) delete[] m_triareas;
|
|
||||||
m_triareas = 0;
|
|
||||||
|
|
||||||
rcFreeHeightField(m_solid);
|
|
||||||
m_solid = 0;
|
|
||||||
rcFreeCompactHeightfield(m_chf);
|
|
||||||
m_chf = 0;
|
|
||||||
rcFreeContourSet(m_cset);
|
|
||||||
m_cset = 0;
|
|
||||||
rcFreePolyMesh(m_pmesh);
|
|
||||||
m_pmesh = 0;
|
|
||||||
rcFreePolyMeshDetail(m_dmesh);
|
|
||||||
m_dmesh = 0;
|
|
||||||
dtFreeNavMesh(m_navMesh);
|
|
||||||
m_navMesh = 0;
|
|
||||||
|
|
||||||
dtFreeNavMeshQuery(m_navQuery);
|
|
||||||
m_navQuery = 0;
|
|
||||||
|
|
||||||
if (m_ctx) delete m_ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool dpWorld::LoadNavmeshByZoneID(unsigned int zoneID) {
|
|
||||||
std::string path = "./res/maps/navmeshes/" + std::to_string(zoneID) + ".bin";
|
|
||||||
m_navMesh = LoadNavmesh(path.c_str());
|
|
||||||
|
|
||||||
if (m_navMesh) { m_navQuery = dtAllocNavMeshQuery(); m_navQuery->init(m_navMesh, 2048); } else return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
dtNavMesh* dpWorld::LoadNavmesh(const char* path) {
|
|
||||||
FILE* fp;
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
fopen_s(&fp, path, "rb");
|
|
||||||
#elif __APPLE__
|
|
||||||
// macOS has 64bit file IO by default
|
|
||||||
fp = fopen(path, "rb");
|
|
||||||
#else
|
|
||||||
fp = fopen64(path, "rb");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (!fp) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read header.
|
|
||||||
NavMeshSetHeader header;
|
|
||||||
size_t readLen = fread(&header, sizeof(NavMeshSetHeader), 1, fp);
|
|
||||||
if (readLen != 1) {
|
|
||||||
fclose(fp);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (header.magic != NAVMESHSET_MAGIC) {
|
|
||||||
fclose(fp);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (header.version != NAVMESHSET_VERSION) {
|
|
||||||
fclose(fp);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
dtNavMesh* mesh = dtAllocNavMesh();
|
|
||||||
if (!mesh) {
|
|
||||||
fclose(fp);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
dtStatus status = mesh->init(&header.params);
|
|
||||||
if (dtStatusFailed(status)) {
|
|
||||||
fclose(fp);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read tiles.
|
|
||||||
for (int i = 0; i < header.numTiles; ++i) {
|
|
||||||
NavMeshTileHeader tileHeader;
|
|
||||||
readLen = fread(&tileHeader, sizeof(tileHeader), 1, fp);
|
|
||||||
if (readLen != 1)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
if (!tileHeader.tileRef || !tileHeader.dataSize)
|
|
||||||
break;
|
|
||||||
|
|
||||||
unsigned char* data = (unsigned char*)dtAlloc(tileHeader.dataSize, DT_ALLOC_PERM);
|
|
||||||
if (!data) break;
|
|
||||||
memset(data, 0, tileHeader.dataSize);
|
|
||||||
readLen = fread(data, tileHeader.dataSize, 1, fp);
|
|
||||||
if (readLen != 1)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
mesh->addTile(data, tileHeader.dataSize, DT_TILE_FREE_DATA, tileHeader.tileRef, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
fclose(fp);
|
|
||||||
|
|
||||||
return mesh;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool dpWorld::ShouldLoadNavmesh(unsigned int zoneID) {
|
|
||||||
return true; //We use default paths now. Might re-tool this function later.
|
|
||||||
|
|
||||||
//TODO: Add to this list as the navmesh folder grows.
|
|
||||||
switch (zoneID) {
|
|
||||||
case 1100:
|
|
||||||
case 1150:
|
|
||||||
case 1151:
|
|
||||||
case 1200:
|
|
||||||
case 1201:
|
|
||||||
case 1300:
|
|
||||||
case 1400:
|
|
||||||
case 1603:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool dpWorld::ShouldUseSP(unsigned int zoneID) {
|
bool dpWorld::ShouldUseSP(unsigned int zoneID) {
|
||||||
//TODO: Add to this list as needed. Only large maps should be added as tiling likely makes little difference on small maps.
|
// TODO: Add to this list as needed.
|
||||||
|
// Only large maps should be added as tiling likely makes little difference on small maps.
|
||||||
|
|
||||||
switch (zoneID) {
|
switch (zoneID) {
|
||||||
case 1100: //Avant Gardens
|
case 1100: // Avant Gardens
|
||||||
case 1200: //Nimbus Station
|
case 1200: // Nimbus Station
|
||||||
case 1300: //Gnarled Forest
|
case 1300: // Gnarled Forest
|
||||||
case 1400: //Forbidden Valley
|
case 1400: // Forbidden Valley
|
||||||
case 1800: //Crux Prime
|
case 1800: // Crux Prime
|
||||||
case 1900: //Nexus Tower
|
case 1900: // Nexus Tower
|
||||||
case 2000: //Ninjago
|
case 2000: // Ninjago
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
float dpWorld::GetHeightAtPoint(const NiPoint3& location) {
|
|
||||||
if (m_navMesh == nullptr) {
|
|
||||||
return location.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
float toReturn = 0.0f;
|
|
||||||
float pos[3];
|
|
||||||
pos[0] = location.x;
|
|
||||||
pos[1] = location.y;
|
|
||||||
pos[2] = location.z;
|
|
||||||
|
|
||||||
dtPolyRef nearestRef = 0;
|
|
||||||
float polyPickExt[3] = { 32.0f, 32.0f, 32.0f };
|
|
||||||
dtQueryFilter filter{};
|
|
||||||
|
|
||||||
m_navQuery->findNearestPoly(pos, polyPickExt, &filter, &nearestRef, 0);
|
|
||||||
m_navQuery->getPolyHeight(nearestRef, pos, &toReturn);
|
|
||||||
|
|
||||||
if (toReturn == 0.0f) {
|
|
||||||
toReturn = location.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
return toReturn;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<NiPoint3> dpWorld::GetPath(const NiPoint3& startPos, const NiPoint3& endPos, float speed) {
|
|
||||||
std::vector<NiPoint3> path;
|
|
||||||
|
|
||||||
//allows for non-navmesh maps (like new custom maps) to have "basic" enemies.
|
|
||||||
if (m_navMesh == nullptr) {
|
|
||||||
//how many points to generate between start/end?
|
|
||||||
//note: not actually 100% accurate due to rounding, but worst case it causes them to go a tiny bit faster
|
|
||||||
//than their speed value would normally allow at the end.
|
|
||||||
int numPoints = startPos.Distance(startPos, endPos) / speed;
|
|
||||||
|
|
||||||
path.push_back(startPos); //insert the start pos
|
|
||||||
|
|
||||||
//Linearly interpolate between these two points:
|
|
||||||
for (int i = 0; i < numPoints; i++) {
|
|
||||||
NiPoint3 newPoint{ startPos };
|
|
||||||
|
|
||||||
newPoint.x += speed;
|
|
||||||
newPoint.y = newPoint.y + (((endPos.y - startPos.y) / (endPos.x - startPos.x)) * (newPoint.x - startPos.x));
|
|
||||||
|
|
||||||
path.push_back(newPoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
path.push_back(endPos); //finally insert our end pos
|
|
||||||
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
float sPos[3];
|
|
||||||
float ePos[3];
|
|
||||||
sPos[0] = startPos.x;
|
|
||||||
sPos[1] = startPos.y;
|
|
||||||
sPos[2] = startPos.z;
|
|
||||||
|
|
||||||
ePos[0] = endPos.x;
|
|
||||||
ePos[1] = endPos.y;
|
|
||||||
ePos[2] = endPos.z;
|
|
||||||
|
|
||||||
dtStatus pathFindStatus;
|
|
||||||
dtPolyRef startRef;
|
|
||||||
dtPolyRef endRef;
|
|
||||||
float polyPickExt[3] = { 32.0f, 32.0f, 32.0f };
|
|
||||||
dtQueryFilter filter{};
|
|
||||||
|
|
||||||
//Find our start poly
|
|
||||||
m_navQuery->findNearestPoly(sPos, polyPickExt, &filter, &startRef, 0);
|
|
||||||
|
|
||||||
//Find our end poly
|
|
||||||
m_navQuery->findNearestPoly(ePos, polyPickExt, &filter, &endRef, 0);
|
|
||||||
|
|
||||||
pathFindStatus = DT_FAILURE;
|
|
||||||
int m_nstraightPath = 0;
|
|
||||||
int m_npolys = 0;
|
|
||||||
dtPolyRef m_polys[MAX_POLYS];
|
|
||||||
float m_straightPath[MAX_POLYS * 3];
|
|
||||||
unsigned char m_straightPathFlags[MAX_POLYS];
|
|
||||||
dtPolyRef m_straightPathPolys[MAX_POLYS];
|
|
||||||
int m_straightPathOptions = 0;
|
|
||||||
|
|
||||||
if (startRef && endRef) {
|
|
||||||
m_navQuery->findPath(startRef, endRef, sPos, ePos, &filter, m_polys, &m_npolys, MAX_POLYS);
|
|
||||||
|
|
||||||
if (m_npolys) {
|
|
||||||
// In case of partial path, make sure the end point is clamped to the last polygon.
|
|
||||||
float epos[3];
|
|
||||||
dtVcopy(epos, ePos);
|
|
||||||
if (m_polys[m_npolys - 1] != endRef)
|
|
||||||
m_navQuery->closestPointOnPoly(m_polys[m_npolys - 1], ePos, epos, 0);
|
|
||||||
|
|
||||||
m_navQuery->findStraightPath(sPos, epos, m_polys, m_npolys,
|
|
||||||
m_straightPath, m_straightPathFlags,
|
|
||||||
m_straightPathPolys, &m_nstraightPath, MAX_POLYS, m_straightPathOptions);
|
|
||||||
|
|
||||||
// At this point we have our path. Copy it to the path store
|
|
||||||
int nIndex = 0;
|
|
||||||
for (int nVert = 0; nVert < m_nstraightPath; nVert++) {
|
|
||||||
/*m_PathStore[nPathSlot].PosX[nVert] = StraightPath[nIndex++];
|
|
||||||
m_PathStore[nPathSlot].PosY[nVert] = StraightPath[nIndex++];
|
|
||||||
m_PathStore[nPathSlot].PosZ[nVert] = StraightPath[nIndex++];*/
|
|
||||||
|
|
||||||
NiPoint3 newPoint{ m_straightPath[nIndex++], m_straightPath[nIndex++], m_straightPath[nIndex++] };
|
|
||||||
path.push_back(newPoint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
m_npolys = 0;
|
|
||||||
m_nstraightPath = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Singleton.h"
|
#include "Singleton.h"
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
//Navmesh includes:
|
//Navmesh includes:
|
||||||
#include "Recast.h"
|
#include "Recast.h"
|
||||||
@ -11,23 +11,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
static const int NAVMESHSET_MAGIC = 'M' << 24 | 'S' << 16 | 'E' << 8 | 'T'; //'MSET';
|
#include "dNavMesh.h"
|
||||||
static const int NAVMESHSET_VERSION = 1;
|
|
||||||
|
|
||||||
struct NavMeshSetHeader {
|
|
||||||
int magic;
|
|
||||||
int version;
|
|
||||||
int numTiles;
|
|
||||||
dtNavMeshParams params;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct NavMeshTileHeader {
|
|
||||||
dtTileRef tileRef;
|
|
||||||
int dataSize;
|
|
||||||
};
|
|
||||||
|
|
||||||
static const int MAX_POLYS = 256;
|
|
||||||
static const int MAX_SMOOTH = 2048;
|
|
||||||
|
|
||||||
class NiPoint3;
|
class NiPoint3;
|
||||||
class dpEntity;
|
class dpEntity;
|
||||||
@ -38,22 +22,17 @@ public:
|
|||||||
void Initialize(unsigned int zoneID);
|
void Initialize(unsigned int zoneID);
|
||||||
|
|
||||||
~dpWorld();
|
~dpWorld();
|
||||||
void RecastCleanup();
|
|
||||||
|
|
||||||
bool LoadNavmeshByZoneID(unsigned int zoneID);
|
|
||||||
dtNavMesh* LoadNavmesh(const char* path);
|
|
||||||
bool ShouldLoadNavmesh(unsigned int zoneID);
|
|
||||||
bool ShouldUseSP(unsigned int zoneID);
|
bool ShouldUseSP(unsigned int zoneID);
|
||||||
|
bool IsLoaded() const { return m_NavMesh != nullptr; }
|
||||||
float GetHeightAtPoint(const NiPoint3& location);
|
|
||||||
std::vector<NiPoint3> GetPath(const NiPoint3& startPos, const NiPoint3& endPos, float speed = 10.0f);
|
|
||||||
bool IsLoaded() const { return m_navMesh != nullptr; }
|
|
||||||
|
|
||||||
void StepWorld(float deltaTime);
|
void StepWorld(float deltaTime);
|
||||||
|
|
||||||
void AddEntity(dpEntity* entity);
|
void AddEntity(dpEntity* entity);
|
||||||
void RemoveEntity(dpEntity* entity);
|
void RemoveEntity(dpEntity* entity);
|
||||||
|
|
||||||
|
dNavMesh* GetNavMesh() { return m_NavMesh; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
dpGrid* m_Grid;
|
dpGrid* m_Grid;
|
||||||
bool phys_spatial_partitioning = 1;
|
bool phys_spatial_partitioning = 1;
|
||||||
@ -63,18 +42,5 @@ private:
|
|||||||
std::vector<dpEntity*> m_StaticEntities;
|
std::vector<dpEntity*> m_StaticEntities;
|
||||||
std::vector<dpEntity*> m_DynamicEntites;
|
std::vector<dpEntity*> m_DynamicEntites;
|
||||||
|
|
||||||
//Navmesh stuffs:
|
dNavMesh* m_NavMesh;
|
||||||
unsigned char* m_triareas;
|
|
||||||
rcHeightfield* m_solid;
|
|
||||||
rcCompactHeightfield* m_chf;
|
|
||||||
rcContourSet* m_cset;
|
|
||||||
rcPolyMesh* m_pmesh;
|
|
||||||
rcConfig m_cfg;
|
|
||||||
rcPolyMeshDetail* m_dmesh;
|
|
||||||
|
|
||||||
class InputGeom* m_geom;
|
|
||||||
class dtNavMesh* m_navMesh;
|
|
||||||
class dtNavMeshQuery* m_navQuery;
|
|
||||||
unsigned char m_navMeshDrawFlags;
|
|
||||||
rcContext* m_ctx;
|
|
||||||
};
|
};
|
||||||
|
@ -17,7 +17,7 @@ void BaseEnemyMech::OnDie(Entity* self, Entity* killer) {
|
|||||||
ControllablePhysicsComponent* controlPhys = static_cast<ControllablePhysicsComponent*>(self->GetComponent(COMPONENT_TYPE_CONTROLLABLE_PHYSICS));
|
ControllablePhysicsComponent* controlPhys = static_cast<ControllablePhysicsComponent*>(self->GetComponent(COMPONENT_TYPE_CONTROLLABLE_PHYSICS));
|
||||||
if (!controlPhys) return;
|
if (!controlPhys) return;
|
||||||
|
|
||||||
NiPoint3 newLoc = { controlPhys->GetPosition().x, dpWorld::Instance().GetHeightAtPoint(controlPhys->GetPosition()), controlPhys->GetPosition().z };
|
NiPoint3 newLoc = { controlPhys->GetPosition().x, dpWorld::Instance().GetNavMesh()->GetHeightAtPoint(controlPhys->GetPosition()), controlPhys->GetPosition().z };
|
||||||
|
|
||||||
EntityInfo info = EntityInfo();
|
EntityInfo info = EntityInfo();
|
||||||
std::vector<LDFBaseData*> cfg;
|
std::vector<LDFBaseData*> cfg;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
set(DWORLDSERVER_SOURCES "ObjectIDManager.cpp"
|
set(DWORLDSERVER_SOURCES "ObjectIDManager.cpp"
|
||||||
"PerformanceManager.cpp"
|
"PerformanceManager.cpp"
|
||||||
"WorldServer.cpp")
|
"WorldServer.cpp")
|
||||||
|
|
||||||
add_executable(WorldServer ${DWORLDSERVER_SOURCES})
|
add_executable(WorldServer ${DWORLDSERVER_SOURCES})
|
||||||
target_link_libraries(WorldServer ${COMMON_LIBRARIES} dChatFilter dGame dZoneManager dPhysics detour recast tinyxml2)
|
target_link_libraries(WorldServer ${COMMON_LIBRARIES} dChatFilter dGame dZoneManager dPhysics detour recast tinyxml2 dNavigation)
|
||||||
|
Loading…
Reference in New Issue
Block a user