diff --git a/AmeisenNavigation.Exporter/AmeisenNavigation.Exporter.vcxproj b/AmeisenNavigation.Exporter/AmeisenNavigation.Exporter.vcxproj
index 37f7bbb..bc1623b 100644
--- a/AmeisenNavigation.Exporter/AmeisenNavigation.Exporter.vcxproj
+++ b/AmeisenNavigation.Exporter/AmeisenNavigation.Exporter.vcxproj
@@ -101,13 +101,14 @@
WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
true
stdcpplatest
- stdc17
+ Default
true
+ MultiThreadedDebugDLL
Console
true
- StormLibRUS.lib;%(AdditionalDependencies)
+ StormLibDUS.lib;%(AdditionalDependencies)
@@ -119,8 +120,10 @@
WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
true
stdcpplatest
- stdc17
+ Default
true
+ MaxSpeed
+ MultiThreadedDLL
Console
@@ -137,13 +140,14 @@
_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
true
stdcpplatest
- stdc17
+ Default
true
+ MultiThreadedDebugDLL
Console
true
- StormLibRUS.lib;%(AdditionalDependencies)
+ StormLibDUS.lib;%(AdditionalDependencies)
@@ -155,8 +159,10 @@
NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
true
stdcpplatest
- stdc17
+ Default
true
+ MaxSpeed
+ MultiThreadedDLL
Console
@@ -170,7 +176,16 @@
+
+
+
+
+
+
+
+
+
diff --git a/AmeisenNavigation.Exporter/AmeisenNavigation.Exporter.vcxproj.filters b/AmeisenNavigation.Exporter/AmeisenNavigation.Exporter.vcxproj.filters
index b5ab3a5..1d197e4 100644
--- a/AmeisenNavigation.Exporter/AmeisenNavigation.Exporter.vcxproj.filters
+++ b/AmeisenNavigation.Exporter/AmeisenNavigation.Exporter.vcxproj.filters
@@ -23,5 +23,32 @@
Headerdateien
+
+ Headerdateien
+
+
+ Headerdateien
+
+
+ Headerdateien
+
+
+ Headerdateien
+
+
+ Headerdateien
+
+
+ Headerdateien
+
+
+ Headerdateien
+
+
+ Headerdateien
+
+
+ Headerdateien
+
\ No newline at end of file
diff --git a/AmeisenNavigation.Exporter/src/Dbc/DbcFile.hpp b/AmeisenNavigation.Exporter/src/Dbc/DbcFile.hpp
new file mode 100644
index 0000000..dce525c
--- /dev/null
+++ b/AmeisenNavigation.Exporter/src/Dbc/DbcFile.hpp
@@ -0,0 +1,45 @@
+#pragma once
+
+struct DbcHeader
+{
+ char magic[4];
+ unsigned int recordCount;
+ unsigned int fieldCount;
+ unsigned int recordSize;
+ unsigned int stringTableSize;
+};
+
+class DbcFile
+{
+ unsigned char* Data;
+
+public:
+ DbcFile(unsigned char* data) noexcept
+ : Data(data)
+ {}
+
+ ~DbcFile() noexcept
+ {
+ if (Data) free(Data);
+ }
+
+ inline DbcHeader* GetHeader() const noexcept { return reinterpret_cast(Data); }
+ constexpr inline unsigned char* GetData() const noexcept { return Data + sizeof(DbcHeader); }
+
+ inline unsigned int GetRecordCount() const noexcept { return GetHeader()->recordCount; }
+ inline unsigned int GetFieldCount() const noexcept { return GetHeader()->fieldCount; }
+ inline unsigned int GetRecordSize() const noexcept { return GetHeader()->recordSize; }
+ inline unsigned int GetStringTableSize() const noexcept { return GetHeader()->stringTableSize; }
+ inline unsigned char* GetStringTable() const noexcept { return GetData() + (GetRecordSize() * GetRecordCount()); }
+
+ template
+ constexpr inline T Read(unsigned int id, unsigned int field) noexcept
+ {
+ return *((T*)(GetData() + (id * GetRecordSize() + (field * 4))));
+ }
+
+ inline const char* ReadString(unsigned int id, unsigned int field) noexcept
+ {
+ return reinterpret_cast(GetStringTable() + Read(id, field));
+ }
+};
diff --git a/AmeisenNavigation.Exporter/src/Main.cpp b/AmeisenNavigation.Exporter/src/Main.cpp
index e1be7d5..47057cc 100644
--- a/AmeisenNavigation.Exporter/src/Main.cpp
+++ b/AmeisenNavigation.Exporter/src/Main.cpp
@@ -1,62 +1,107 @@
#include "Main.hpp"
-int main()
+int main() noexcept
{
- const std::string mpqFilter{ ".mpq" };
- std::vector mpqFiles{};
- std::vector> dbcFiles{};
+ MpqManager mpqManager(GAME_DIR);
- for (const auto& entry : std::filesystem::recursive_directory_iterator("C:\\Spiele\\World of Warcraft 3.3.5a\\Data\\"))
+ unsigned char* mapDbcData = nullptr;
+ unsigned int mapDbcSize = 0;
+ std::vector> maps;
+
+ if (mpqManager.GetFileContent("DBFilesClient\\Map.dbc", mapDbcData, mapDbcSize))
{
- if (entry.is_regular_file())
- {
- const auto& path = entry.path();
- const auto& ext = path.extension().string();
+ DbcFile mapDbc(mapDbcData);
- if (std::equal(mpqFilter.begin(), mpqFilter.end(), ext.begin(), [](char a, char b) { return std::tolower(a) == std::tolower(b); }))
- {
- mpqFiles.push_back(path);
- }
+ for (unsigned int i = 0u; i < mapDbc.GetRecordCount(); ++i)
+ {
+ maps.push_back(std::make_pair(mapDbc.Read(i, 0u), mapDbc.ReadString(i, 1u)));
}
}
- std::sort(mpqFiles.begin(), mpqFiles.end(), NaturalCompare);
+ unsigned char* liquidTypeDbcData = nullptr;
+ unsigned int liquidTypeDbcSize = 0;
+ std::unordered_map liquidTypes;
- for (const auto& mpqFile : mpqFiles)
+ if (mpqManager.GetFileContent("DBFilesClient\\LiquidType.dbc", liquidTypeDbcData, liquidTypeDbcSize))
{
- std::cout << mpqFile << std::endl;
- continue;
+ DbcFile liquidTypeDbc(liquidTypeDbcData);
- if (void* mpq; SFileOpenArchive(mpqFile.c_str(), 0, MPQ_OPEN_READ_ONLY, &mpq))
+ for (unsigned int i = 0u; i < liquidTypeDbc.GetRecordCount(); ++i)
{
- SFILE_FIND_DATA findData{};
- void* fileFind = SFileFindFirstFile(mpq, "*.dbc", &findData, nullptr);
+ liquidTypes[liquidTypeDbc.Read(i, 0u)] = static_cast(liquidTypeDbc.Read(i, 3u));
+ }
+ }
+
+ // #pragma omp parallel for schedule(dynamic)
+ for (int mapIndex = 0; mapIndex < 1; ++mapIndex) // maps.size()
+ {
+ const auto& [id, name] = maps[mapIndex];
+ const auto mapsPath = std::format("World\\Maps\\{}\\{}", name, name);
+
+ const auto wdtPath = std::format("{}.wdt", mapsPath);
+ unsigned char* wdtData = nullptr;
+ unsigned int wdtSize = 0;
+
+ if (mpqManager.GetFileContent(wdtPath.c_str(), wdtData, wdtSize))
+ {
+ Wdt wdt(wdtData);
+
+ std::vector verts;
+ std::vector tris;
- while (fileFind)
+ for (int i = 0; i < 1; ++i) // WDT_MAP_SIZE * WDT_MAP_SIZE
{
- dbcFiles.push_back(std::make_pair(findData, mpqFile));
+ const auto x = 48; // i % WDT_MAP_SIZE;
+ const auto y = 32; // i / WDT_MAP_SIZE;
- if (!SFileFindNextFile(fileFind, &findData))
+ if (wdt.Main()->adt[x][y].exists)
{
- break;
+ const auto adtPath = std::format("{}_{}_{}.adt", mapsPath, y, x);
+ unsigned char* adtData = nullptr;
+ unsigned int adtSize = 0;
+
+ std::cout << "[Maps] " << name << ": ADT(" << x << ", " << y << ") " << adtPath << std::endl;
+
+ if (mpqManager.GetFileContent(adtPath.c_str(), adtData, adtSize))
+ {
+ Adt adt(adtData);
+
+ for (int a = 0; a < ADT_CELLS_PER_GRID * ADT_CELLS_PER_GRID; ++a)
+ {
+ const int cx = a / ADT_CELLS_PER_GRID;
+ const int cy = a % ADT_CELLS_PER_GRID;
+
+ adt.GetTerrainVertsAndTris(cx, cy, verts, tris);
+ adt.GetLiquidVertsAndTris(cx, cy, verts, tris);
+ }
+ }
}
}
- SFileFindClose(fileFind);
- SFileCloseArchive(mpq);
- }
- else
- {
- std::cerr << "-> Failed to open MPQ file: " << mpqFile << std::endl;
+ ExportDebugObjFile(verts, tris);
}
}
- std::sort(dbcFiles.begin(), dbcFiles.end(), [](const auto& a, const auto& b) { return a.second > b.second; });
+ return 0;
+}
- for (const auto& dbcFile : dbcFiles)
+void ExportDebugObjFile(const std::vector& vertexes, const std::vector& tris) noexcept
+{
+ std::cout << "Exporting OBJ file" << std::endl;
+ std::fstream objFstream;
+ objFstream << std::fixed << std::showpoint;
+ objFstream << std::setprecision(8);
+ objFstream.open("C:\\Users\\Jannis\\source\\repos\\recastnavigation\\RecastDemo\\Bin\\Meshes\\debug.obj", std::fstream::out);
+
+ for (const auto& v3 : vertexes)
{
- std::cout << dbcFile.first.szPlainName << std::endl;
+ objFstream << "v " << v3.y << " " << v3.z << " " << v3.x << "\n";
}
- return 0;
-}
\ No newline at end of file
+ for (const auto& tri : tris)
+ {
+ objFstream << "f " << tri.a << " " << tri.b << " " << tri.c << "\n";
+ }
+
+ objFstream.close();
+}
diff --git a/AmeisenNavigation.Exporter/src/Main.hpp b/AmeisenNavigation.Exporter/src/Main.hpp
index 496a2a3..d7e5efe 100644
--- a/AmeisenNavigation.Exporter/src/Main.hpp
+++ b/AmeisenNavigation.Exporter/src/Main.hpp
@@ -1,17 +1,20 @@
#pragma once
-#include
#include
+#include
+#include
#include
+#include
#include
-#define STORMLIB_NO_AUTO_LINK
-#include
+#include "Utils/Vector3.hpp"
+#include "Utils/Tri.hpp"
+#include "Dbc/DbcFile.hpp"
+#include "Mpq/MpqManager.hpp"
+#include "Wow/Adt.hpp"
+#include "Wow/LiquidType.hpp"
+#include "Wow/Wdt.hpp"
-#include
-#pragma comment(lib, "Shlwapi.lib")
+constexpr auto GAME_DIR = "C:\\Spiele\\World of Warcraft 3.3.5a\\Data\\";
-inline auto NaturalCompare(const std::filesystem::path& path1, const std::filesystem::path& path2)
-{
- return StrCmpLogicalW(path1.wstring().c_str(), path2.wstring().c_str());
-}
+void ExportDebugObjFile(const std::vector& vertexes, const std::vector& tris) noexcept;
diff --git a/AmeisenNavigation.Exporter/src/Mpq/FileSort.hpp b/AmeisenNavigation.Exporter/src/Mpq/FileSort.hpp
new file mode 100644
index 0000000..0f2aa11
--- /dev/null
+++ b/AmeisenNavigation.Exporter/src/Mpq/FileSort.hpp
@@ -0,0 +1,73 @@
+#pragma once
+
+#include
+#include
+
+inline std::pair ExtractNumber(const std::wstring& s)
+{
+ std::wstring prefix;
+ int number = 0;
+ bool foundDigit = false;
+
+ for (const wchar_t& c : s)
+ {
+ if (std::iswdigit(c))
+ {
+ prefix += L' ';
+ foundDigit = true;
+ number = number * 10 + (c - L'0');
+ }
+ else
+ {
+ if (foundDigit)
+ {
+ break;
+ }
+
+ prefix += c;
+ }
+ }
+
+ return { prefix, number };
+}
+
+inline int GetPathDepth(const std::filesystem::path& path, int depth = 0)
+{
+ if (path.has_parent_path())
+ {
+ const auto parent = path.parent_path();
+
+ if (parent != path)
+ {
+ return GetPathDepth(parent, depth + 1);
+ }
+ }
+
+ return depth;
+}
+
+///
+/// Try to sort the MPQ files in the order wow loads them.
+///
+inline bool NaturalCompare(const std::filesystem::directory_entry& a, const std::filesystem::directory_entry& b)
+{
+ if (GetPathDepth(a.path()) > GetPathDepth(b.path()))
+ {
+ return true;
+ }
+
+ auto [prefixA, numberA] = ExtractNumber(a.path().filename().wstring());
+ auto [prefixB, numberB] = ExtractNumber(b.path().filename().wstring());
+
+ if (numberA == 0 && numberB != 0)
+ {
+ return true;
+ }
+
+ if (prefixA != prefixB)
+ {
+ return prefixA < prefixB;
+ }
+
+ return numberA < numberB;
+}
diff --git a/AmeisenNavigation.Exporter/src/Mpq/MpqManager.hpp b/AmeisenNavigation.Exporter/src/Mpq/MpqManager.hpp
new file mode 100644
index 0000000..94bdf29
--- /dev/null
+++ b/AmeisenNavigation.Exporter/src/Mpq/MpqManager.hpp
@@ -0,0 +1,118 @@
+#pragma once
+
+#include
+#include
+#include
+
+#define STORMLIB_NO_AUTO_LINK
+#include
+
+#include "FileSort.hpp"
+
+class MpqManager
+{
+ const char* GameDir;
+ std::vector Mpqs;
+
+public:
+ MpqManager(const char* gameDir) noexcept
+ : GameDir(gameDir),
+ Mpqs()
+ {
+ const std::string mpqFilter{ ".mpq" };
+
+ std::vector mpqFiles;
+
+ for (const auto& p : std::filesystem::recursive_directory_iterator(gameDir))
+ {
+ if (p.is_regular_file())
+ {
+ const auto& ext = p.path().extension().string();
+
+ if (std::ranges::equal(mpqFilter, ext, [](char a, char b) { return std::tolower(a) == std::tolower(b); }))
+ {
+ mpqFiles.emplace_back(p);
+ }
+ }
+ }
+
+ std::ranges::sort(mpqFiles, NaturalCompare);
+ Mpqs.resize(mpqFiles.size());
+
+#pragma omp parallel for schedule(dynamic)
+ for (int i = 0; i < mpqFiles.size(); ++i)
+ {
+ const auto& path = mpqFiles[i].path();
+
+ if (void* mpq; SFileOpenArchive(path.c_str(), 0, MPQ_OPEN_READ_ONLY, &mpq))
+ {
+#pragma omp critical
+ {
+ std::cout << "[MPQManager] (" << i << ") Loaded: 0x" << std::hex << mpq << std::dec << " " << path.filename() << std::endl;
+ }
+
+ Mpqs[i] = mpq;
+ }
+ }
+ }
+
+ ~MpqManager() noexcept
+ {
+ for (void* mpq : Mpqs)
+ {
+ SFileCloseArchive(mpq);
+ }
+ }
+
+ inline bool GetFile(const char* name, SFILE_FIND_DATA& resultFindData, void*& mpqHanle) noexcept
+ {
+ bool result = false;
+ SFILE_FIND_DATA findData{ 0 };
+
+ for (void* mpq : Mpqs)
+ {
+ if (void* fileFind = SFileFindFirstFile(mpq, name, &findData, nullptr))
+ {
+ do
+ {
+ resultFindData = findData;
+ mpqHanle = mpq;
+ result = true;
+ } while (SFileFindNextFile(fileFind, &findData));
+
+ SFileFindClose(fileFind);
+ }
+ }
+
+ return result;
+ }
+
+ inline bool GetFileContent(const char* name, unsigned char*& buffer, unsigned int& bufferSize) noexcept
+ {
+ void* mpq{};
+ SFILE_FIND_DATA findData{};
+
+ if (GetFile(name, findData, mpq))
+ {
+ if (HANDLE hFile{}; SFileOpenFileEx(mpq, findData.cFileName, 0, &hFile))
+ {
+ bufferSize = findData.dwFileSize;
+
+ if (bufferSize > 0)
+ {
+ buffer = new unsigned char[bufferSize];
+
+ if (SFileReadFile(hFile, buffer, bufferSize, 0, 0))
+ {
+ SFileCloseFile(hFile);
+ return true;
+ }
+ }
+
+ SFileCloseFile(hFile);
+ }
+ }
+
+ return false;
+ }
+};
diff --git a/AmeisenNavigation.Exporter/src/Utils/Tri.hpp b/AmeisenNavigation.Exporter/src/Utils/Tri.hpp
new file mode 100644
index 0000000..e692945
--- /dev/null
+++ b/AmeisenNavigation.Exporter/src/Utils/Tri.hpp
@@ -0,0 +1,23 @@
+#pragma once
+
+struct Tri
+{
+ union
+ {
+ struct
+ {
+ int a;
+ int b;
+ int c;
+ };
+ int points[3];
+ };
+
+ Tri() noexcept
+ : points{ 0, 0, 0 }
+ {}
+
+ Tri(int a, int b, int c) noexcept
+ : points{ a, b, c }
+ {}
+};
diff --git a/AmeisenNavigation.Exporter/src/Utils/Vector3.hpp b/AmeisenNavigation.Exporter/src/Utils/Vector3.hpp
new file mode 100644
index 0000000..2a852a4
--- /dev/null
+++ b/AmeisenNavigation.Exporter/src/Utils/Vector3.hpp
@@ -0,0 +1,32 @@
+#pragma once
+
+#pragma once
+
+struct Vector3
+{
+ union
+ {
+ struct
+ {
+ float x;
+ float y;
+ float z;
+ };
+ float pos[3];
+ };
+
+ Vector3() noexcept
+ : pos{ 0.0f, 0.0f, 0.0f }
+ {}
+
+ Vector3(float* position) noexcept
+ : pos{ position[0], position[1], position[2] }
+ {}
+
+ Vector3(float x, float y, float z) noexcept
+ : pos{ x, y, z }
+ {}
+
+ constexpr inline operator float* () noexcept { return pos; }
+ constexpr inline operator const float* () const noexcept { return pos; }
+};
diff --git a/AmeisenNavigation.Exporter/src/Wow/Adt.hpp b/AmeisenNavigation.Exporter/src/Wow/Adt.hpp
new file mode 100644
index 0000000..22e7a96
--- /dev/null
+++ b/AmeisenNavigation.Exporter/src/Wow/Adt.hpp
@@ -0,0 +1,262 @@
+#pragma once
+
+#include "Mver.hpp"
+
+constexpr auto TILESIZE = 533.33333f;
+constexpr auto CHUNKSIZE = TILESIZE / 16.0f;
+constexpr auto UNITSIZE = CHUNKSIZE / 8.0f;
+constexpr auto HALFUNITSIZE = UNITSIZE / 2.0f;
+
+constexpr auto ADT_CELLS_PER_GRID = 16;
+constexpr auto ADT_CELL_SIZE = 8;
+constexpr auto ADT_GRID_SIZE = ADT_CELLS_PER_GRID * ADT_CELL_SIZE;
+
+struct MCIN
+{
+ unsigned char magic[4];
+ unsigned int size;
+
+ struct
+ {
+ unsigned int offsetMcnk;
+ unsigned int size;
+ unsigned int flags;
+ unsigned int asyncId;
+ } cells[ADT_CELLS_PER_GRID][ADT_CELLS_PER_GRID];
+};
+
+enum class AdtLiquidVertexFormat : unsigned short
+{
+ HeightDepth = 0,
+ HeightTextureCoord = 1,
+ Depth = 2,
+};
+
+struct AdtLiquid
+{
+ unsigned short type;
+ AdtLiquidVertexFormat vertexFormat;
+ float minHeightLevel;
+ float maxHeightLevel;
+ unsigned char offsetX;
+ unsigned char offsetY;
+ unsigned char width;
+ unsigned char height;
+ unsigned int offsetExistsBitmap;
+ unsigned int offsetVertexData;
+};
+
+struct AdtLiquidAttributes
+{
+ unsigned long long fishable;
+ unsigned long long deep;
+};
+
+class MH2O
+{
+public:
+ unsigned char magic[4];
+ unsigned int size;
+
+ struct
+ {
+ unsigned int offsetInstances;
+ unsigned int used;
+ unsigned int offsetAttributes;
+ } liquid[ADT_CELLS_PER_GRID][ADT_CELLS_PER_GRID];
+
+ inline AdtLiquid* GetInstance(unsigned int x, unsigned int y) noexcept
+ {
+ if (liquid[x][y].used && liquid[x][y].offsetInstances)
+ {
+ return reinterpret_cast(reinterpret_cast(this) + 8 + liquid[x][y].offsetInstances);
+ }
+
+ return nullptr;
+ }
+
+ inline AdtLiquidAttributes* GetAttributes(unsigned int x, unsigned int y) noexcept
+ {
+ if (liquid[x][y].used)
+ {
+ if (liquid[x][y].offsetAttributes)
+ {
+ return reinterpret_cast(reinterpret_cast(this) + 8 + liquid[x][y].offsetAttributes);
+ }
+
+ static AdtLiquidAttributes all{ 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF };
+ return &all;
+ }
+
+ return nullptr;
+ }
+
+ inline unsigned long long GetExistsBitmap(AdtLiquid* h) noexcept
+ {
+ return h->offsetExistsBitmap ? *reinterpret_cast(reinterpret_cast(this) + 8 + h->offsetExistsBitmap)
+ : 0xFFFFFFFFFFFFFFFFuLL;
+ }
+};
+
+struct MCVT
+{
+ unsigned char magic[4];
+ unsigned int size;
+ float heightMap[(9 * 9) + (8 * 8)];
+};
+
+struct MCNK
+{
+ unsigned char magic[4];
+ unsigned int size;
+ unsigned int flags;
+ unsigned int ix;
+ unsigned int iy;
+ unsigned int nLayers;
+ unsigned int nDoodadRefs;
+ unsigned int offsMcvt;
+ unsigned int offsMcnr;
+ unsigned int offsMcly;
+ unsigned int offsMcrf;
+ unsigned int offsMcal;
+ unsigned int sizeMcal;
+ unsigned int offsMcsh;
+ unsigned int sizeMcsh;
+ unsigned int areaid;
+ unsigned int nMapObjRefs;
+ unsigned int holes;
+ unsigned short s[2];
+ unsigned int data[3];
+ unsigned int predTex;
+ unsigned int nEffectDoodad;
+ unsigned int offsMCcse;
+ unsigned int nSndEmitters;
+ unsigned int offsMclq;
+ unsigned int sizeMclq;
+ float x;
+ float y;
+ float z;
+ unsigned int offsMccv;
+ unsigned int props;
+ unsigned int effectId;
+};
+
+struct MHDR
+{
+ unsigned char magic[4];
+ unsigned int size;
+ unsigned int flags;
+ unsigned int offsetMcin;
+ unsigned int offsetMtex;
+ unsigned int offsetMmdx;
+ unsigned int offsetMmid;
+ unsigned int offsetMwmo;
+ unsigned int offsetMwid;
+ unsigned int offsetMddf;
+ unsigned int offsetModf;
+ unsigned int offsetMfbo;
+ unsigned int offsetMh2o;
+ unsigned int data[5];
+};
+
+class Adt
+{
+ unsigned char* Data;
+
+public:
+
+ Adt(unsigned char* data) noexcept
+ : Data(data)
+ {}
+
+ ~Adt() noexcept
+ {
+ if (Data) free(Data);
+ }
+
+ inline MVER* Mver() noexcept { return reinterpret_cast(Data); };
+ inline MHDR* Mhdr() noexcept { return reinterpret_cast(Data + sizeof(MVER)); };
+
+ inline MCIN* Mcin() noexcept
+ {
+ unsigned int offset = Mhdr()->offsetMcin;
+ return offset ? reinterpret_cast(Data + sizeof(MVER) + 8 + offset) : nullptr;
+ };
+
+ inline MH2O* Mh2o() noexcept
+ {
+ unsigned int offset = Mhdr()->offsetMh2o;
+ return offset ? reinterpret_cast(Data + sizeof(MVER) + 8 + offset) : nullptr;
+ };
+
+ inline MCNK* Mcnk(unsigned int x, unsigned int y) noexcept
+ {
+ unsigned int offset = Mcin()->cells[x][y].offsetMcnk;
+ return offset ? reinterpret_cast(Data + offset) : nullptr;
+ };
+
+ inline MCVT* Mcvt(MCNK* mcnk) noexcept
+ {
+ unsigned int offset = mcnk->offsMcvt;
+ return offset ? reinterpret_cast(reinterpret_cast(mcnk) + offset) : nullptr;
+ };
+
+ inline void GetTerrainVertsAndTris(unsigned int x, unsigned int y, std::vector& vertexes, std::vector& tris) noexcept
+ {
+ if (MCNK* mcnk = Mcnk(x, y))
+ {
+ // heightMap index, 0 - 144
+ int mcvtIndex = 0;
+
+ for (int j = 0; j < 17; ++j) // 17 total row count (9 + 8)
+ {
+ // how many units are in this row (this alternates 9, 8, 9, 8, ..., 9)
+ const int unitCount = j % 2 ? 8 : 9;
+
+ for (int i = 0; i < unitCount; ++i)
+ {
+ // chunk offset - unit offset
+ Vector3 v3{ mcnk->x - (j * HALFUNITSIZE), mcnk->y - (i * UNITSIZE), mcnk->z };
+
+ // add the heightMap offset if there is one
+ if (MCVT* mcvt = Mcvt(mcnk))
+ {
+ v3.z += mcvt->heightMap[mcvtIndex];
+ }
+
+ mcvtIndex++;
+
+ vertexes.emplace_back(v3);
+
+ if (unitCount == 8)
+ {
+ // shift the inner row by HALFUNITSIZE
+ v3.y -= HALFUNITSIZE;
+
+ // check for holes in the inner grid, if there is no hole, generate tris
+ if (!mcnk->holes || !((mcnk->holes & 0x0000FFFFu) & ((1 << (i / 2)) << ((j / 4) << 2))))
+ {
+ int vertexCount = vertexes.size();
+ tris.emplace_back(Tri{ vertexCount - 9, vertexCount, vertexCount - 8 }); // Top
+ tris.emplace_back(Tri{ vertexCount + 9, vertexCount, vertexCount + 8 }); // Bottom
+ tris.emplace_back(Tri{ vertexCount - 8, vertexCount, vertexCount + 9 }); // Right
+ tris.emplace_back(Tri{ vertexCount + 8, vertexCount, vertexCount - 9 }); // Left
+ }
+ }
+ }
+ }
+ }
+ }
+
+ inline void GetLiquidVertsAndTris(unsigned int x, unsigned int y, std::vector& vertexes, std::vector& tris) noexcept
+ {
+ if (MH2O* mh2o = Mh2o())
+ {
+ if (AdtLiquid* liquid = mh2o->GetInstance(x, y))
+ {
+ AdtLiquidAttributes* attributes = mh2o->GetAttributes(x, y);
+ unsigned long long existBitmap = mh2o->GetExistsBitmap(liquid);
+ }
+ }
+ }
+};
diff --git a/AmeisenNavigation.Exporter/src/Wow/LiquidType.hpp b/AmeisenNavigation.Exporter/src/Wow/LiquidType.hpp
new file mode 100644
index 0000000..d58659c
--- /dev/null
+++ b/AmeisenNavigation.Exporter/src/Wow/LiquidType.hpp
@@ -0,0 +1,9 @@
+#pragma once
+
+enum class LiquidType : char
+{
+ WATER = 0,
+ OCEAN = 1,
+ MAGMA = 2,
+ SLIME = 3
+};
diff --git a/AmeisenNavigation.Exporter/src/Wow/Mver.hpp b/AmeisenNavigation.Exporter/src/Wow/Mver.hpp
new file mode 100644
index 0000000..92f0752
--- /dev/null
+++ b/AmeisenNavigation.Exporter/src/Wow/Mver.hpp
@@ -0,0 +1,8 @@
+#pragma once
+
+struct MVER
+{
+ unsigned char magic[4];
+ unsigned int size;
+ unsigned int version;
+};
diff --git a/AmeisenNavigation.Exporter/src/Wow/Wdt.hpp b/AmeisenNavigation.Exporter/src/Wow/Wdt.hpp
new file mode 100644
index 0000000..75c67f0
--- /dev/null
+++ b/AmeisenNavigation.Exporter/src/Wow/Wdt.hpp
@@ -0,0 +1,44 @@
+#pragma once
+
+#include "Mver.hpp"
+
+constexpr auto WDT_MAP_SIZE = 64;
+
+struct MPHD
+{
+ unsigned char magic[4];
+ unsigned int size;
+ unsigned int data[8];
+};
+
+struct MAIN
+{
+ unsigned char magic[4];
+ unsigned int size;
+
+ struct AdtData
+ {
+ unsigned int exists;
+ unsigned int data;
+ } adt[64][64];
+};
+
+class Wdt
+{
+ unsigned char* Data;
+
+public:
+
+ Wdt(unsigned char* data) noexcept
+ : Data(data)
+ {}
+
+ ~Wdt() noexcept
+ {
+ if (Data) free(Data);
+ }
+
+ inline MVER* Mver() noexcept { return reinterpret_cast(Data); };
+ inline MPHD* Mphd() noexcept { return reinterpret_cast(Data + sizeof(MVER)); };
+ inline MAIN* Main() noexcept { return reinterpret_cast(Data + sizeof(MVER) + sizeof(MPHD)); };
+};
diff --git a/AmeisenNavigation.Server/src/Main.cpp b/AmeisenNavigation.Server/src/Main.cpp
index 49087f3..a37a507 100644
--- a/AmeisenNavigation.Server/src/Main.cpp
+++ b/AmeisenNavigation.Server/src/Main.cpp
@@ -100,6 +100,8 @@ int __cdecl main(int argc, const char* argv[])
Server->AddCallback(static_cast(MessageType::EXPLORE_POLY), ExplorePolyCallback);
+ Server->AddCallback(static_cast(MessageType::CONFIGURE_FILTER), ConfigureFilterCallback);
+
LogS("Starting server on: ", Config->ip, ":", std::to_string(Config->port));
Server->Run();
@@ -277,3 +279,22 @@ void ExplorePolyCallback(ClientHandler* handler, char type, const void* data, in
path.pointCount = 0;
pathMisc.pointCount = 0;
}
+
+void ConfigureFilterCallback(ClientHandler* handler, char type, const void* data, int size) noexcept
+{
+ bool result = true;
+ const ConfigureFilterData request = *reinterpret_cast(data);
+
+ AmeisenNavClient* client = Nav->GetClient(handler->GetId());
+ client->ResetQueryFilter();
+
+ const FilterConfig* filterConfigs = &request.firstFilterConfig;
+
+ for (size_t i = 0; i < request.filterConfigCount; ++i)
+ {
+ client->ConfigureQueryFilter(filterConfigs[i].areaId, filterConfigs[i].cost);
+ }
+
+ client->UpdateQueryFilter(request.state);
+ handler->SendData(type, &result, sizeof(bool));
+}
diff --git a/AmeisenNavigation.Server/src/Main.hpp b/AmeisenNavigation.Server/src/Main.hpp
index a80a3dc..3963e2a 100644
--- a/AmeisenNavigation.Server/src/Main.hpp
+++ b/AmeisenNavigation.Server/src/Main.hpp
@@ -11,7 +11,7 @@
#include
#include
-constexpr auto AMEISENNAV_VERSION = "1.8.3.2";
+constexpr auto AMEISENNAV_VERSION = "1.8.4.0";
enum class MessageType
{
@@ -22,6 +22,7 @@ enum class MessageType
CAST_RAY, // Cast a movement ray to test for obstacles
RANDOM_PATH, // Generate a straight path where the nodes get offsetted by a random value
EXPLORE_POLY, // Generate a route to explore the polygon (W.I.P)
+ CONFIGURE_FILTER, // Cpnfigure the clients dtQueryFilter area costs
};
enum class PathType
@@ -79,6 +80,19 @@ struct ExplorePolyData
Vector3 firstPolyPoint;
};
+struct FilterConfig
+{
+ char areaId;
+ float cost;
+};
+
+struct ConfigureFilterData
+{
+ ClientState state;
+ int filterConfigCount;
+ FilterConfig firstFilterConfig;
+};
+
inline AnTcpServer* Server = nullptr;
inline AmeisenNavigation* Nav = nullptr;
inline AmeisenNavConfig* Config = nullptr;
@@ -101,6 +115,8 @@ void RandomPointAroundCallback(ClientHandler* handler, char type, const void* da
void ExplorePolyCallback(ClientHandler* handler, char type, const void* data, int size) noexcept;
+void ConfigureFilterCallback(ClientHandler* handler, char type, const void* data, int size) noexcept;
+
inline void HandlePathFlagsAndSendData(ClientHandler* handler, int mapId, int flags, Path& path, Path& smoothPath, char type, PathType pathType) noexcept
{
Path* pathToSend = &path;
diff --git a/AmeisenNavigation.Tester/MainWindow.xaml b/AmeisenNavigation.Tester/MainWindow.xaml
index cac3dd3..2d5ce85 100644
--- a/AmeisenNavigation.Tester/MainWindow.xaml
+++ b/AmeisenNavigation.Tester/MainWindow.xaml
@@ -7,10 +7,10 @@
mc:Ignorable="d"
Title="AmeisenNavigation Tester" Height="450" Width="900" MinHeight="450" MinWidth="900" Loaded="Window_Loaded">
-
+
-
+
@@ -30,7 +30,20 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/AmeisenNavigation.Tester/MainWindow.xaml.cs b/AmeisenNavigation.Tester/MainWindow.xaml.cs
index c359868..13d3103 100644
--- a/AmeisenNavigation.Tester/MainWindow.xaml.cs
+++ b/AmeisenNavigation.Tester/MainWindow.xaml.cs
@@ -1,11 +1,14 @@
using AnTCP.Client;
using System;
+using System.Collections;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Numerics;
+using System.Runtime.InteropServices.JavaScript;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
@@ -14,12 +17,14 @@ namespace AmeisenNavigation.Tester
{
public enum MessageType
{
- PATH,
- MOVE_ALONG_SURFACE,
- RANDOM_POINT,
- RANDOM_POINT_AROUND,
- CAST_RAY,
- RANDOM_PATH,
+ PATH, // Generate a simple straight path
+ MOVE_ALONG_SURFACE, // Move an entity by small deltas using pathfinding (usefull to prevent falling off edges...)
+ RANDOM_POINT, // Get a random point on the mesh
+ RANDOM_POINT_AROUND, // Get a random point on the mesh in a circle
+ CAST_RAY, // Cast a movement ray to test for obstacles
+ RANDOM_PATH, // Generate a straight path where the nodes get offsetted by a random value
+ EXPLORE_POLY, // Generate a route to explore the polygon (W.I.P)
+ CONFIGURE_FILTER, // Cpnfigure the clients dtQueryFilter area costs
};
public enum PathType
@@ -210,19 +215,21 @@ private void Run(int flags, PathType type)
Vector3 start = new(sX, sY, sZ);
Vector3 end = new(eX, eY, eZ);
+ Stopwatch sw = Stopwatch.StartNew();
IEnumerable path = type switch
{
PathType.STRAIGHT => GetPath(MessageType.PATH, 0, start, end, flags),
PathType.RANDOM => GetPath(MessageType.RANDOM_PATH, 0, start, end, flags),
_ => throw new NotImplementedException(),
};
+ sw.Stop();
if (path == null || !path.Any())
{
return;
}
- UpdateViews(path);
+ UpdateViews(path, sw.Elapsed);
float minX = float.MaxValue;
float maxX = float.MinValue;
@@ -266,10 +273,12 @@ private void Run(int flags, PathType type)
ImgCanvas.Visibility = Visibility.Visible;
}
}
- private void UpdateViews(IEnumerable path)
+
+ private void UpdateViews(IEnumerable path, TimeSpan duration)
{
PointList.ItemsSource = path;
lblPointCount.Content = $"Points: {path.Count()}";
+ lblTime.Content = duration;
Vector3 last = default;
float distance = 0.0f;
@@ -307,5 +316,78 @@ private void TypeFlags_Click(object sender, RoutedEventArgs e)
{
GetPathAndDraw();
}
+
+ private void SldCostWater_ValueChanged(object sender, RoutedPropertyChangedEventArgs e)
+ {
+ float v = MathF.Round((float)sldCostWater.Value, 1);
+ if (lblCostWater != null) lblCostWater.Content = $"Water: {v,1}";
+ UpdateFilterConfig();
+ }
+
+ private void SldCostGround_ValueChanged(object sender, RoutedPropertyChangedEventArgs e)
+ {
+ float v = MathF.Round((float)sldCostGround.Value, 1);
+ if (lblCostGround != null) lblCostGround.Content = $"Ground: {v,1}";
+ UpdateFilterConfig();
+ }
+
+ private void SldCostBadLiquid_ValueChanged(object sender, RoutedPropertyChangedEventArgs e)
+ {
+ float v = MathF.Round((float)sldCostBadLiquid.Value, 2);
+ if (lblCostBadLiquid != null) lblCostBadLiquid.Content = $"Magma/Slime: {v,1}";
+ UpdateFilterConfig();
+ }
+
+ struct FilterConfig
+ {
+ public char State;
+ public int Count = 3;
+ public char GroundArea = (char)1;
+ public float GroundCost;
+ public char WaterArea = (char)4;
+ public float WaterAreaCost;
+ public char BadLiquidArea = (char)8;
+ public float BadLiquidCost;
+
+ public FilterConfig() { }
+ }
+
+ private void RbClientState_Click(object sender, RoutedEventArgs e)
+ {
+ UpdateFilterConfig();
+ }
+
+ private void UpdateFilterConfig()
+ {
+ try
+ {
+ char state = (char)0;
+
+ if (rbClientStateNormalAlly.IsChecked == true)
+ {
+ state = (char)1;
+ }
+ else if (rbClientStateNormalHorde.IsChecked == true)
+ {
+ state = (char)2;
+ }
+ else if (rbClientStateDead.IsChecked == true)
+ {
+ state = (char)3;
+ }
+
+ if (Client != null && Client.IsConnected)
+ {
+ bool result = Client.Send((byte)MessageType.CONFIGURE_FILTER, new FilterConfig()
+ {
+ State = state,
+ GroundCost = (float)sldCostGround.Value,
+ WaterAreaCost = (float)sldCostWater.Value,
+ BadLiquidCost = (float)sldCostBadLiquid.Value,
+ }).As();
+ }
+ }
+ catch { }
+ }
}
}
\ No newline at end of file
diff --git a/AmeisenNavigation/AmeisenNavigation.vcxproj b/AmeisenNavigation/AmeisenNavigation.vcxproj
index 086d1db..4b2152c 100644
--- a/AmeisenNavigation/AmeisenNavigation.vcxproj
+++ b/AmeisenNavigation/AmeisenNavigation.vcxproj
@@ -195,6 +195,8 @@
+
+
diff --git a/AmeisenNavigation/AmeisenNavigation.vcxproj.filters b/AmeisenNavigation/AmeisenNavigation.vcxproj.filters
index fdfc688..43bf59b 100644
--- a/AmeisenNavigation/AmeisenNavigation.vcxproj.filters
+++ b/AmeisenNavigation/AmeisenNavigation.vcxproj.filters
@@ -15,6 +15,8 @@
+
+
diff --git a/AmeisenNavigation/src/AmeisenNavigation.cpp b/AmeisenNavigation/src/AmeisenNavigation.cpp
index 711721e..599265f 100644
--- a/AmeisenNavigation/src/AmeisenNavigation.cpp
+++ b/AmeisenNavigation/src/AmeisenNavigation.cpp
@@ -1,11 +1,11 @@
#include "AmeisenNavigation.hpp"
-void AmeisenNavigation::NewClient(size_t clientId, MmapFormat version) noexcept
+void AmeisenNavigation::NewClient(size_t clientId, MmapFormat format) noexcept
{
if (IsValidClient(clientId)) { return; }
ANAV_DEBUG_ONLY(std::cout << ">> New Client: " << clientId << std::endl);
- Clients[clientId] = new AmeisenNavClient(clientId, version, MaxPolyPath);
+ Clients[clientId] = new AmeisenNavClient(clientId, format, ClientState::NORMAL, FilterProvider, MaxPolyPath);
}
void AmeisenNavigation::FreeClient(size_t clientId) noexcept
@@ -17,6 +17,11 @@ void AmeisenNavigation::FreeClient(size_t clientId) noexcept
ANAV_DEBUG_ONLY(std::cout << ">> Freed Client: " << clientId << std::endl);
}
+AmeisenNavClient* AmeisenNavigation::GetClient(size_t clientId) noexcept
+{
+ return IsValidClient(clientId) ? Clients[clientId]: nullptr;
+}
+
bool AmeisenNavigation::GetPath(size_t clientId, int mapId, const Vector3& startPosition, const Vector3& endPosition, Path& path) noexcept
{
AmeisenNavClient* client;
@@ -27,11 +32,7 @@ bool AmeisenNavigation::GetPath(size_t clientId, int mapId, const Vector3& start
if (CalculateNormalPath(query, client->QueryFilter(), client->GetPolyPathBuffer(), client->GetPolyPathBufferSize(), startPosition, endPosition, path))
{
- for (auto& point : path)
- {
- point.ToWowCoords();
- }
-
+ path.ToWowCoords();
return true;
}
@@ -55,12 +56,12 @@ bool AmeisenNavigation::GetRandomPath(size_t clientId, int mapId, const Vector3&
if (i > 0 && i < path.pointCount - 1)
{
dtPolyRef randomRef;
- dtStatus randomPointStatus = client->GetNavmeshQuery(mapId)->findRandomPointAroundCircle
+ dtStatus randomPointStatus = query->findRandomPointAroundCircle
(
polyPathBuffer[i],
path[i],
maxRandomDistance,
- &client->QueryFilter(),
+ client->QueryFilter(),
GetRandomFloat,
&randomRef,
path[i]
@@ -89,19 +90,19 @@ bool AmeisenNavigation::MoveAlongSurface(size_t clientId, int mapId, const Vecto
if (!TryGetClientAndQuery(clientId, mapId, client, query)) { return false; }
ANAV_DEBUG_ONLY(std::cout << ">> [" << clientId << "] MoveAlongSurface (" << mapId << ") " << PRINT_VEC3(startPosition) << " -> " << PRINT_VEC3(endPosition) << std::endl);
- if (PolyPosition start; dtStatusSucceed(GetNearestPoly(query, &client->QueryFilter(), startPosition, start)))
+ if (PolyPosition start; dtStatusSucceed(GetNearestPoly(query, client->QueryFilter(), startPosition, start)))
{
Vector3 rdEnd;
endPosition.CopyToRDCoords(rdEnd);
int visitedCount = 0;
- dtPolyRef visited[16]{};
+ dtPolyRef visited[8];
dtStatus moveAlongSurfaceStatus = query->moveAlongSurface
(
start.poly,
start.pos,
rdEnd,
- &client->QueryFilter(),
+ client->QueryFilter(),
positionToGoTo,
visited,
&visitedCount,
@@ -132,9 +133,9 @@ bool AmeisenNavigation::GetRandomPoint(size_t clientId, int mapId, Vector3& posi
ANAV_DEBUG_ONLY(std::cout << ">> [" << clientId << "] GetRandomPoint (" << mapId << ")" << std::endl);
dtPolyRef polyRef;
- dtStatus findRandomPointStatus = client->GetNavmeshQuery(mapId)->findRandomPoint
+ dtStatus findRandomPointStatus = query->findRandomPoint
(
- &client->QueryFilter(),
+ client->QueryFilter(),
GetRandomFloat,
&polyRef,
position
@@ -159,15 +160,15 @@ bool AmeisenNavigation::GetRandomPointAround(size_t clientId, int mapId, const V
if (!TryGetClientAndQuery(clientId, mapId, client, query)) { return false; }
ANAV_DEBUG_ONLY(std::cout << ">> [" << clientId << "] GetRandomPointAround (" << mapId << ") startPosition: " << PRINT_VEC3(startPosition) << " radius: " << radius << std::endl);
- if (PolyPosition start; dtStatusSucceed(GetNearestPoly(query, &client->QueryFilter(), startPosition, start)))
+ if (PolyPosition start; dtStatusSucceed(GetNearestPoly(query, client->QueryFilter(), startPosition, start)))
{
dtPolyRef polyRef;
- dtStatus findRandomPointAroundStatus = client->GetNavmeshQuery(mapId)->findRandomPointAroundCircle
+ dtStatus findRandomPointAroundStatus = query->findRandomPointAroundCircle
(
start.poly,
start.pos,
radius,
- &client->QueryFilter(),
+ client->QueryFilter(),
GetRandomFloat,
&polyRef,
position
@@ -196,7 +197,7 @@ bool AmeisenNavigation::CastMovementRay(size_t clientId, int mapId, const Vector
if (!TryGetClientAndQuery(clientId, mapId, client, query)) { return false; }
ANAV_DEBUG_ONLY(std::cout << ">> [" << clientId << "] CastMovementRay (" << mapId << ") " << PRINT_VEC3(startPosition) << " -> " << PRINT_VEC3(endPosition) << std::endl);
- if (PolyPosition start; dtStatusSucceed(GetNearestPoly(query, &client->QueryFilter(), startPosition, start)))
+ if (PolyPosition start; dtStatusSucceed(GetNearestPoly(query, client->QueryFilter(), startPosition, start)))
{
Vector3 rdEnd;
endPosition.CopyToRDCoords(rdEnd);
@@ -206,7 +207,7 @@ bool AmeisenNavigation::CastMovementRay(size_t clientId, int mapId, const Vector
start.poly,
start.pos,
rdEnd,
- &client->QueryFilter(),
+ client->QueryFilter(),
0,
raycastHit
);
@@ -259,19 +260,18 @@ bool AmeisenNavigation::PostProcessClosestPointOnPoly(size_t clientId, int mapId
if (!TryGetClientAndQuery(clientId, mapId, client, query)) { return false; }
ANAV_DEBUG_ONLY(std::cout << ">> [" << clientId << "] PostProcessClosestPointOnPoly (" << mapId << ") " << std::endl);
- for (const auto& point : input)
+ for (int i = 0; i < input.pointCount; ++i)
{
- if (PolyPosition start; dtStatusSucceed(GetNearestPoly(query, &client->QueryFilter(), point, start)))
+ if (PolyPosition start; dtStatusSucceed(GetNearestPoly(query, client->QueryFilter(), input.points[i], start)))
{
Vector3 closest;
bool posOverPoly = false;
- dtStatus closestPointOnPolyStatus = query->closestPointOnPoly(start.poly, start.pos, closest, &posOverPoly);
-
- if (dtStatusSucceed(closestPointOnPolyStatus))
+ if (dtStatusSucceed(query->closestPointOnPoly(start.poly, start.pos, closest, &posOverPoly)))
{
closest.ToWowCoords();
- InsertVector3(output.points, output.pointCount, &closest);
+ output.Append(&closest);
+ if (output.IsFull()) break;
}
}
}
@@ -287,19 +287,31 @@ bool AmeisenNavigation::PostProcessMoveAlongSurface(size_t clientId, int mapId,
if (!TryGetClientAndQuery(clientId, mapId, client, query)) { return false; }
ANAV_DEBUG_ONLY(std::cout << ">> [" << clientId << "] PostProcessMoveAlongSurface (" << mapId << ") " << std::endl);
- InsertVector3(output.points, output.pointCount, &input.points[0]);
-
Vector3 lastPos = input.points[0];
+ output.TryAppend(&lastPos);
+ const float maxDistance = 25.0f;
for (int i = 1; i < input.pointCount; ++i)
{
- if (PolyPosition start; dtStatusSucceed(GetNearestPoly(query, &client->QueryFilter(), lastPos, start)))
+ if (PolyPosition start; dtStatusSucceed(GetNearestPoly(query, client->QueryFilter(), lastPos, start)))
{
Vector3 rdEnd;
input.points[i].CopyToRDCoords(rdEnd);
+ float distance = dtVdist(start.pos, rdEnd);
+
+ if (distance > maxDistance)
+ {
+ Vector3 velocity;
+ dtVsub(velocity, rdEnd, start.pos);
+ dtVnormalize(velocity);
+ dtVscale(velocity, velocity, distance / std::ceilf(distance / maxDistance));
+ dtVadd(rdEnd, start.pos, velocity);
+ i--;
+ }
+
int visitedCount = 0;
- dtPolyRef visited[16]{};
+ dtPolyRef visited[8];
Vector3 positionToGoTo;
dtStatus moveAlongSurfaceStatus = query->moveAlongSurface
@@ -307,7 +319,7 @@ bool AmeisenNavigation::PostProcessMoveAlongSurface(size_t clientId, int mapId,
start.poly,
start.pos,
rdEnd,
- &client->QueryFilter(),
+ client->QueryFilter(),
positionToGoTo,
visited,
&visitedCount,
@@ -317,13 +329,14 @@ bool AmeisenNavigation::PostProcessMoveAlongSurface(size_t clientId, int mapId,
if (dtStatusSucceed(moveAlongSurfaceStatus))
{
positionToGoTo.ToWowCoords();
- InsertVector3(output.points, output.pointCount, &positionToGoTo);
+ output.Append(&positionToGoTo);
lastPos = positionToGoTo;
+
+ if (output.IsFull()) break;
}
}
}
- InsertVector3(output.points, output.pointCount, &input.points[input.pointCount - 1]);
return true;
}
@@ -395,7 +408,7 @@ bool AmeisenNavigation::LoadMmaps(MmapFormat& mmapFormat, int mapId) noexcept
}
#pragma omp parallel for schedule(dynamic)
- for (int i = 1; i <= 64 * 64; ++i)
+ for (int i = 0; i < 64 * 64; ++i)
{
const auto x = i / 64;
const auto y = i % 64;
@@ -455,7 +468,7 @@ bool AmeisenNavigation::TryGetClientAndQuery(size_t clientId, int mapId, Ameisen
if (!IsValidClient(clientId)) { return false; }
client = Clients.at(clientId);
- auto format = client->GetMmapFormat();
+ auto& format = client->GetMmapFormat();
// we already have a query
if (query = client->GetNavmeshQuery(mapId)) { return true; }
@@ -501,7 +514,7 @@ bool AmeisenNavigation::TryGetClientAndQuery(size_t clientId, int mapId, Ameisen
bool AmeisenNavigation::CalculateNormalPath
(
dtNavMeshQuery* query,
- dtQueryFilter& filter,
+ dtQueryFilter* filter,
dtPolyRef* polyPathBuffer,
int maxPolyPathCount,
const Vector3& startPosition,
@@ -510,9 +523,9 @@ bool AmeisenNavigation::CalculateNormalPath
dtPolyRef* visited
) noexcept
{
- if (PolyPosition start; dtStatusSucceed(GetNearestPoly(query, &filter, startPosition, start)))
+ if (PolyPosition start; dtStatusSucceed(GetNearestPoly(query, filter, startPosition, start)))
{
- if (PolyPosition end; dtStatusSucceed(GetNearestPoly(query, &filter, endPosition, end)))
+ if (PolyPosition end; dtStatusSucceed(GetNearestPoly(query, filter, endPosition, end)))
{
int polyPathCount = 0;
dtStatus polyPathStatus = query->findPath
@@ -521,7 +534,7 @@ bool AmeisenNavigation::CalculateNormalPath
end.poly,
start.pos,
end.pos,
- &filter,
+ filter,
polyPathBuffer,
&polyPathCount,
maxPolyPathCount
diff --git a/AmeisenNavigation/src/AmeisenNavigation.hpp b/AmeisenNavigation/src/AmeisenNavigation.hpp
index 147612d..0b4992d 100644
--- a/AmeisenNavigation/src/AmeisenNavigation.hpp
+++ b/AmeisenNavigation/src/AmeisenNavigation.hpp
@@ -59,6 +59,7 @@ class AmeisenNavigation
std::unordered_map>> NavMeshMap;
std::unordered_map Clients;
+ QueryFilterProvider* FilterProvider;
std::map> MmapFormatPatterns
{
@@ -77,11 +78,14 @@ class AmeisenNavigation
MaxPolyPath(maxPolyPath),
MaxSearchNodes(maxSearchNodes),
NavMeshMap(),
- Clients()
+ Clients(),
+ FilterProvider(new QueryFilterProvider())
{}
~AmeisenNavigation()
{
+ delete FilterProvider;
+
for (const auto& client : Clients)
{
if (client.second)
@@ -111,15 +115,22 @@ class AmeisenNavigation
/// Call this to register a new client.
///
/// Unique id for the client.
- /// Version of the client.
- void NewClient(size_t clientId, MmapFormat version) noexcept;
+ /// MMAP format of the client.
+ void NewClient(size_t clientId, MmapFormat format) noexcept;
///
/// Call this to free a client.
///
- /// Uinique id of the client.
+ /// Unique id of the client.
void FreeClient(size_t clientId) noexcept;
+ ///
+ /// Get a client by its id, returns nullptr if client id is unknown.
+ ///
+ /// Unique id of the client.
+ /// Client if found.
+ AmeisenNavClient* GetClient(size_t clientId) noexcept;
+
///
/// Set a custom MMAP filename format. Patterns need to be in std::format style.
///
@@ -313,7 +324,7 @@ class AmeisenNavigation
///
/// Used by the GetPath and GetRandomPath methods to generate a path.
///
- bool CalculateNormalPath(dtNavMeshQuery* query, dtQueryFilter& filter, dtPolyRef* polyPathBuffer, int maxPolyPathCount, const Vector3& startPosition, const Vector3& endPosition, Path& path, dtPolyRef* visited = nullptr) noexcept;
+ bool CalculateNormalPath(dtNavMeshQuery* query, dtQueryFilter* filter, dtPolyRef* polyPathBuffer, int maxPolyPathCount, const Vector3& startPosition, const Vector3& endPosition, Path& path, dtPolyRef* visited = nullptr) noexcept;
///
/// Used to detect the mmap file format.
diff --git a/AmeisenNavigation/src/Clients/AmeisenNavClient.hpp b/AmeisenNavigation/src/Clients/AmeisenNavClient.hpp
index 48ddc08..8ceb34c 100644
--- a/AmeisenNavigation/src/Clients/AmeisenNavClient.hpp
+++ b/AmeisenNavigation/src/Clients/AmeisenNavClient.hpp
@@ -1,18 +1,23 @@
#pragma once
-#include "../../recastnavigation/Detour/Include/DetourCommon.h"
-#include "../../recastnavigation/Detour/Include/DetourNavMeshQuery.h"
+#include "../../../recastnavigation/Detour/Include/DetourCommon.h"
+#include "../../../recastnavigation/Detour/Include/DetourNavMeshQuery.h"
-#include "Mmap/MmapFormat.hpp"
+#include "ClientState.hpp"
+#include "QueryFilterProvider.hpp"
-#include "335a/NavArea335a.hpp"
-#include "548/NavArea548.hpp"
+#include "../Mmap/MmapFormat.hpp"
class AmeisenNavClient
{
size_t Id;
MmapFormat Format;
- dtQueryFilter Filter;
+ ClientState State;
+ QueryFilterProvider* FilterProvider;
+
+ // set per client area costs, to prioritize water movement for example
+ dtQueryFilter* CustomFilter;
+ std::unordered_map FilterCustomizations;
// Holds a dtNavMeshQuery for every map
std::unordered_map NavMeshQuery;
@@ -27,31 +32,20 @@ class AmeisenNavClient
///
/// Id of the client.
/// MMAP format to use.
+ /// Current ClientState.
+ /// QueryFilterProvider to use.
/// Size of the polypath buffer for recast and detour.
- AmeisenNavClient(size_t id, MmapFormat mmapFormat, int polyPathBufferSize = 512) noexcept
+ AmeisenNavClient(size_t id, MmapFormat mmapFormat, ClientState state, QueryFilterProvider* filterProvider, int polyPathBufferSize = 512) noexcept
: Id(id),
Format(mmapFormat),
+ State(state),
+ FilterProvider(filterProvider),
+ CustomFilter(nullptr),
+ FilterCustomizations(),
NavMeshQuery(),
PolyPathBufferSize(polyPathBufferSize),
- PolyPathBuffer(nullptr),
- Filter()
- {
- switch (mmapFormat)
- {
- case MmapFormat::TC335A:
- Filter.setIncludeFlags(static_cast(NavArea335a::GROUND) | static_cast(NavArea335a::WATER));
- Filter.setExcludeFlags(static_cast(NavArea335a::EMPTY) | static_cast(NavArea335a::GROUND_STEEP) | static_cast(NavArea335a::MAGMA_SLIME));
- break;
-
- case MmapFormat::SF548:
- Filter.setIncludeFlags(static_cast(NavArea548::GROUND) | static_cast(NavArea548::WATER));
- Filter.setExcludeFlags(static_cast(NavArea548::EMPTY) | static_cast(NavArea548::MAGMA) | static_cast(NavArea548::SLIME));
- break;
-
- default:
- break;
- }
- }
+ PolyPathBuffer(nullptr)
+ {}
~AmeisenNavClient() noexcept
{
@@ -61,20 +55,45 @@ class AmeisenNavClient
}
if (PolyPathBuffer) delete[] PolyPathBuffer;
+ if (CustomFilter) delete CustomFilter;
}
AmeisenNavClient(const AmeisenNavClient&) = delete;
AmeisenNavClient& operator=(const AmeisenNavClient&) = delete;
constexpr inline size_t GetId() const noexcept { return Id; }
-
constexpr inline MmapFormat& GetMmapFormat() noexcept { return Format; }
+ constexpr inline ClientState& GetClientState() noexcept { return State; }
- constexpr inline dtQueryFilter& QueryFilter() noexcept { return Filter; }
+ inline dtQueryFilter* QueryFilter() noexcept { return CustomFilter ? CustomFilter : FilterProvider->Get(Format, State); }
inline dtNavMeshQuery* GetNavmeshQuery(int mapId) noexcept { return NavMeshQuery[mapId]; }
inline void SetNavmeshQuery(int mapId, dtNavMeshQuery* query) noexcept { NavMeshQuery[mapId] = query; }
constexpr inline int GetPolyPathBufferSize() const noexcept { return PolyPathBufferSize; }
constexpr inline dtPolyRef* GetPolyPathBuffer() noexcept { return PolyPathBuffer ? PolyPathBuffer : PolyPathBuffer = new dtPolyRef[PolyPathBufferSize]; }
-};
\ No newline at end of file
+
+ inline void ResetQueryFilter() noexcept { FilterCustomizations.clear(); }
+ inline void ConfigureQueryFilter(char areaId, float cost) noexcept { FilterCustomizations[areaId] = cost; }
+
+ inline void UpdateQueryFilter(ClientState state) noexcept
+ {
+ State = state;
+
+ if (!FilterCustomizations.empty())
+ {
+ const auto baseFilter = FilterProvider->Get(Format, State);
+ dtQueryFilter* newFilter = new dtQueryFilter(*baseFilter);
+
+ for (const auto& [areaId, cost] : FilterCustomizations)
+ {
+ newFilter->setAreaCost(areaId, cost);
+ }
+
+ dtQueryFilter* oldFilter = CustomFilter;
+ CustomFilter = newFilter;
+
+ if (oldFilter && CustomFilter != oldFilter) delete oldFilter;
+ }
+ }
+};
diff --git a/AmeisenNavigation/src/Clients/ClientState.hpp b/AmeisenNavigation/src/Clients/ClientState.hpp
new file mode 100644
index 0000000..4c699aa
--- /dev/null
+++ b/AmeisenNavigation/src/Clients/ClientState.hpp
@@ -0,0 +1,11 @@
+#pragma once
+
+enum class ClientState : char
+{
+ NORMAL,
+ // Avoid territories of opposite faction
+ NORMAL_ALLIANCE,
+ NORMAL_HORDE,
+ // Allow movement through all type of bad liquids
+ DEAD,
+};
diff --git a/AmeisenNavigation/src/Clients/QueryFilterProvider.hpp b/AmeisenNavigation/src/Clients/QueryFilterProvider.hpp
new file mode 100644
index 0000000..61b9eb3
--- /dev/null
+++ b/AmeisenNavigation/src/Clients/QueryFilterProvider.hpp
@@ -0,0 +1,109 @@
+#pragma once
+
+#include
+
+#include "../../../recastnavigation/Detour/Include/DetourNavMeshQuery.h"
+
+#include "ClientState.hpp"
+
+#include "335a/NavArea335a.hpp"
+#include "548/NavArea548.hpp"
+
+#include "../Mmap/MmapFormat.hpp"
+
+///
+/// Helper to provide default dtQueryFilter's. Clients may use their own filters to optimize movement,
+/// prefer moving through water or whatever.
+///
+class QueryFilterProvider
+{
+ std::unordered_map > Filters;
+
+public:
+ QueryFilterProvider(float waterCost = 1.3f, float badLiquidCost = 4.0f) noexcept
+ : Filters{}
+ {
+ for (const auto format : { MmapFormat::TC335A, MmapFormat::SF548 })
+ {
+ Filters[format][ClientState::NORMAL] = new dtQueryFilter();
+ Filters[format][ClientState::NORMAL_ALLIANCE] = new dtQueryFilter();
+ Filters[format][ClientState::NORMAL_HORDE] = new dtQueryFilter();
+ Filters[format][ClientState::DEAD] = new dtQueryFilter();
+ }
+
+ Tc335aConfig(waterCost, badLiquidCost);
+ Sf548Config(waterCost, badLiquidCost);
+ }
+
+ inline void Tc335aConfig(float waterCost, float badLiquidCost) noexcept
+ {
+ char includeFlags = static_cast(NavArea335a::GROUND)
+ | static_cast(NavArea335a::WATER)
+ | static_cast(NavArea335a::MAGMA_SLIME);
+
+ char excludeFlags = static_cast(NavArea335a::EMPTY)
+ | static_cast(NavArea335a::GROUND_STEEP);
+
+ Filters[MmapFormat::TC335A][ClientState::NORMAL]->setIncludeFlags(includeFlags);
+ Filters[MmapFormat::TC335A][ClientState::NORMAL]->setExcludeFlags(excludeFlags);
+ Filters[MmapFormat::TC335A][ClientState::NORMAL]->setAreaCost(static_cast(NavArea335a::WATER), waterCost);
+ Filters[MmapFormat::TC335A][ClientState::NORMAL]->setAreaCost(static_cast(NavArea335a::MAGMA_SLIME), badLiquidCost);
+
+ Filters[MmapFormat::TC335A][ClientState::NORMAL_ALLIANCE]->setIncludeFlags(includeFlags);
+ Filters[MmapFormat::TC335A][ClientState::NORMAL_ALLIANCE]->setExcludeFlags(excludeFlags);
+ Filters[MmapFormat::TC335A][ClientState::NORMAL_ALLIANCE]->setAreaCost(static_cast(NavArea335a::WATER), waterCost);
+ Filters[MmapFormat::TC335A][ClientState::NORMAL_ALLIANCE]->setAreaCost(static_cast(NavArea335a::MAGMA_SLIME), badLiquidCost);
+
+ Filters[MmapFormat::TC335A][ClientState::NORMAL_HORDE]->setIncludeFlags(includeFlags);
+ Filters[MmapFormat::TC335A][ClientState::NORMAL_HORDE]->setExcludeFlags(excludeFlags);
+ Filters[MmapFormat::TC335A][ClientState::NORMAL_HORDE]->setAreaCost(static_cast(NavArea335a::WATER), waterCost);
+ Filters[MmapFormat::TC335A][ClientState::NORMAL_HORDE]->setAreaCost(static_cast(NavArea335a::MAGMA_SLIME), badLiquidCost);
+
+ Filters[MmapFormat::TC335A][ClientState::DEAD]->setIncludeFlags(includeFlags);
+ Filters[MmapFormat::TC335A][ClientState::DEAD]->setExcludeFlags(excludeFlags);
+ }
+
+ inline void Sf548Config(float waterCost, float badLiquidCost) noexcept
+ {
+ char includeFlags = static_cast(NavArea548::GROUND)
+ | static_cast(NavArea548::WATER)
+ | static_cast(NavArea548::MAGMA)
+ | static_cast(NavArea548::SLIME);
+
+ char excludeFlags = static_cast(NavArea548::EMPTY);
+
+ Filters[MmapFormat::SF548][ClientState::NORMAL]->setIncludeFlags(includeFlags);
+ Filters[MmapFormat::SF548][ClientState::NORMAL]->setExcludeFlags(excludeFlags);
+ Filters[MmapFormat::SF548][ClientState::NORMAL]->setAreaCost(static_cast(NavArea548::WATER), waterCost);
+ Filters[MmapFormat::SF548][ClientState::NORMAL]->setAreaCost(static_cast(NavArea548::MAGMA), badLiquidCost);
+ Filters[MmapFormat::SF548][ClientState::NORMAL]->setAreaCost(static_cast(NavArea548::SLIME), badLiquidCost);
+
+ Filters[MmapFormat::SF548][ClientState::NORMAL_ALLIANCE]->setIncludeFlags(includeFlags);
+ Filters[MmapFormat::SF548][ClientState::NORMAL_ALLIANCE]->setExcludeFlags(excludeFlags);
+ Filters[MmapFormat::SF548][ClientState::NORMAL_ALLIANCE]->setAreaCost(static_cast(NavArea548::WATER), waterCost);
+ Filters[MmapFormat::SF548][ClientState::NORMAL_ALLIANCE]->setAreaCost(static_cast(NavArea548::MAGMA), badLiquidCost);
+ Filters[MmapFormat::SF548][ClientState::NORMAL_ALLIANCE]->setAreaCost(static_cast(NavArea548::SLIME), badLiquidCost);
+
+ Filters[MmapFormat::SF548][ClientState::NORMAL_HORDE]->setIncludeFlags(includeFlags);
+ Filters[MmapFormat::SF548][ClientState::NORMAL_HORDE]->setExcludeFlags(excludeFlags);
+ Filters[MmapFormat::SF548][ClientState::NORMAL_HORDE]->setAreaCost(static_cast(NavArea548::WATER), waterCost);
+ Filters[MmapFormat::SF548][ClientState::NORMAL_HORDE]->setAreaCost(static_cast(NavArea548::MAGMA), badLiquidCost);
+ Filters[MmapFormat::SF548][ClientState::NORMAL_HORDE]->setAreaCost(static_cast(NavArea548::SLIME), badLiquidCost);
+
+ Filters[MmapFormat::SF548][ClientState::DEAD]->setIncludeFlags(includeFlags);
+ Filters[MmapFormat::SF548][ClientState::DEAD]->setExcludeFlags(excludeFlags);
+ }
+
+ ~QueryFilterProvider() noexcept
+ {
+ for (auto& [format, filter] : Filters)
+ {
+ for (auto& [state, f] : filter)
+ {
+ delete f;
+ }
+ }
+ }
+
+ inline dtQueryFilter* Get(MmapFormat format, ClientState state) noexcept { return Filters[format][state]; }
+};
diff --git a/AmeisenNavigation/src/Utils/Path.hpp b/AmeisenNavigation/src/Utils/Path.hpp
index fd73d9c..b953a53 100644
--- a/AmeisenNavigation/src/Utils/Path.hpp
+++ b/AmeisenNavigation/src/Utils/Path.hpp
@@ -1,6 +1,7 @@
#pragma once
#include "Vector3.hpp"
+#include "VectorUtils.hpp"
struct Path
{
@@ -26,4 +27,51 @@ struct Path
constexpr inline Vector3* end() const noexcept { return points + pointCount; }
constexpr inline int GetSpace() const noexcept { return maxSize - pointCount; }
+ constexpr inline bool IsFull() const noexcept { return pointCount > maxSize - 1; }
+
+ ///
+ /// Appends a Vector3 to the path.
+ ///
+ inline void Append(Vector3* v3) noexcept
+ {
+ InsertVector3(points, pointCount, v3);
+ }
+
+ ///
+ /// Adds Vector3 to the path if there is space for it.
+ ///
+ ///
+ ///
+ inline bool TryAppend(Vector3* v3) noexcept
+ {
+ if (!IsFull())
+ {
+ InsertVector3(points, pointCount, v3);
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Convert the whole path's points to wow coordinates;
+ ///
+ constexpr inline void ToWowCoords() noexcept
+ {
+ for (auto& point : *this)
+ {
+ point.ToWowCoords();
+ }
+ }
+
+ ///
+ /// Convert the whole path's points to rd coordinates;
+ ///
+ constexpr inline void ToRdCoords() noexcept
+ {
+ for (auto& point : *this)
+ {
+ point.ToRDCoords();
+ }
+ }
};
diff --git a/AmeisenNavigation/src/Utils/VectorUtils.hpp b/AmeisenNavigation/src/Utils/VectorUtils.hpp
index a921b11..382326d 100644
--- a/AmeisenNavigation/src/Utils/VectorUtils.hpp
+++ b/AmeisenNavigation/src/Utils/VectorUtils.hpp
@@ -7,9 +7,17 @@
///
/// Helper function to insert a vector3 into a float buffer.
///
-inline void InsertVector3(Vector3* target, int& index, const Vector3* vec, int offset = 0) noexcept
+inline void InsertVector3At(Vector3* target, int index, const Vector3* vec, int offset = 0) noexcept
{
memcpy(target + index, vec + offset, sizeof(Vector3));
+}
+
+///
+/// Helper function to insert a vector3 into a float buffer.
+///
+inline void InsertVector3(Vector3* target, int& index, const Vector3* vec, int offset = 0) noexcept
+{
+ InsertVector3At(target, index, vec, offset);
index++;
}
diff --git a/dep/StormLib/lib/Win32/StormLibDUS.lib b/dep/StormLib/lib/Win32/StormLibDUS.lib
new file mode 100644
index 0000000..42d618a
Binary files /dev/null and b/dep/StormLib/lib/Win32/StormLibDUS.lib differ
diff --git a/dep/StormLib/lib/Win32/StormLibRUS.lib b/dep/StormLib/lib/Win32/StormLibRUS.lib
index ca694af..faab6e0 100644
Binary files a/dep/StormLib/lib/Win32/StormLibRUS.lib and b/dep/StormLib/lib/Win32/StormLibRUS.lib differ
diff --git a/dep/StormLib/lib/x64/StormLibRUS.lib b/dep/StormLib/lib/x64/StormLibRUS.lib
index 6bc050e..65fb199 100644
Binary files a/dep/StormLib/lib/x64/StormLibRUS.lib and b/dep/StormLib/lib/x64/StormLibRUS.lib differ