Npc positioning Implemented slope check to avoid unwanted climbing for some kind of movements (backwards, repositioning etc.) Implemented backwards movement Re-implemented circle repositioning algorithm (smartest than retail, but with the same feeling) Fixed random position of summoned minions Improved pet following movement. Also, they attack NPC from behind now. Thanks to @Footman Swimming creatures Fixed max_z coordinate for swimming creatures. Now only part of their body is allowed to be out of the water level Fixed pathfinder for swimming creatures creating shortcuts for specific segments, now they swim underwater to reach the seashore instead of flying above the water level. Creatures with water InhabitType but no swimming flag now, when not in combat, will walk on water depth instead of swimming. Thanks @jackpoz for the original code UNIT_FLAG_SWIMMING in UpdateEnvironmentIfNeeded to show the swimming animation correctly when underwater Implemented HasEnoughWater check to avoid swimming creatures to go where the water level is too low but also to properly enable swimming animation only when a creature has enough water to swim. Walking creatures Extended the DetourNavMeshQuery adding area cost based on walkability (slope angle + source height) to find better paths at runtime instead of completely remove them from mmaps improve Z height in certain conditions (see #4205, #4203, #4247 ) Flying creatures Rewriting of the hover system Removed hacks and improved the UpdateEnvironmentIfNeeded. Now creatures can properly switch from flying to walk etc. Spells LOS on spell effect must be calculated on CollisionHeight and HitSpherePoint instead of position coords. Improved position for object/creature spawned via spells Improved checks for Fleeing movements (fear spells) Other improvements Implemented method to calculate the CollisionWidth from dbc (used by repositioning algorithm etc.) Improved raycast and collision checks Co-authored-by: Footman <p.alexej@freenet.de> Co-authored-by: Helias <stefanoborzi32@gmail.com> Co-authored-by: Francesco Borzì <borzifrancesco@gmail.com> Co-authored-by: Kitzunu <24550914+Kitzunu@users.noreply.github.com>
474 lines
17 KiB
C++
474 lines
17 KiB
C++
/*
|
|
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>
|
|
* Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/>
|
|
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
|
*/
|
|
|
|
#include "MPQManager.h"
|
|
#include "WDT.h"
|
|
#include "ContinentBuilder.h"
|
|
#include "Cache.h"
|
|
#include "DBC.h"
|
|
#include "Constants.h"
|
|
#include "Model.h"
|
|
|
|
#include "Recast.h"
|
|
#include "DetourNavMesh.h"
|
|
#include "DetourNavMeshQuery.h"
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <set>
|
|
|
|
MPQManager* MPQHandler;
|
|
CacheClass* Cache;
|
|
|
|
void ExtractMMaps(std::set<uint32>& mapIds, uint32 threads)
|
|
{
|
|
DBC* dbc = MPQHandler->GetDBC("Map");
|
|
printf("Map.dbc contains %u rows.\n", dbc->Records.size());
|
|
for (std::vector<Record*>::iterator itr = dbc->Records.begin(); itr != dbc->Records.end(); ++itr)
|
|
{
|
|
uint32 mapId = (*itr)->Values[0];
|
|
|
|
// Skip this map if a list of specific maps was provided and this one is not contained in it.
|
|
if (!mapIds.empty() && mapIds.find(mapId) == mapIds.end())
|
|
{
|
|
if (Constants::Debug)
|
|
printf("Map %u will not be built.\n", mapId);
|
|
continue;
|
|
}
|
|
|
|
std::string name = (*itr)->GetString(1);
|
|
WDT wdt("World\\maps\\" + name + "\\" + name + ".wdt");
|
|
if (!wdt.IsValid)
|
|
{
|
|
printf("Could not find WDT data for map %u (%s)\n", mapId, name.c_str());
|
|
continue;
|
|
}
|
|
printf("Building %s MapId %u\n", name.c_str(), mapId);
|
|
ContinentBuilder builder(name, mapId, &wdt, threads);
|
|
builder.Build();
|
|
}
|
|
}
|
|
|
|
void ExtractDBCs()
|
|
{
|
|
printf("Extracting DBCs\n");
|
|
// Create the file system structure
|
|
std::string baseDBCPath = "dbc/";
|
|
Utils::CreateDir(baseDBCPath);
|
|
|
|
std::set<std::string> DBCFiles;
|
|
const size_t extLen = strlen(".dbc");
|
|
// Populate list of DBC files
|
|
// We get the DBC names by going over the (guaranteed to exist) default locale files
|
|
// Then we look in other locale files in case that they are available.
|
|
for (std::vector<std::string>::iterator itr = MPQHandler->LocaleFiles[MPQHandler->BaseLocale]->Files.begin(); itr != MPQHandler->LocaleFiles[MPQHandler->BaseLocale]->Files.end(); ++itr)
|
|
if (itr->rfind(".dbc") == itr->length() - extLen) // Check if the extension is ".dbc"
|
|
DBCFiles.insert(*itr);
|
|
|
|
const size_t folderLen = strlen("DBFilesClient\\");
|
|
// Iterate over all available locales
|
|
for (std::set<uint32>::iterator itr = MPQHandler->AvailableLocales.begin(); itr != MPQHandler->AvailableLocales.end(); ++itr)
|
|
{
|
|
printf("Extracting DBCs for locale %s\n", MPQManager::Languages[*itr]);
|
|
std::string path = baseDBCPath;
|
|
if (*itr != uint32(MPQHandler->BaseLocale))
|
|
{
|
|
path += std::string(MPQManager::Languages[*itr]) + "/";
|
|
Utils::CreateDir(path);
|
|
}
|
|
|
|
std::string component = "component.wow-" + std::string(MPQManager::Languages[*itr]) + ".txt";
|
|
// Extract the component file
|
|
Utils::SaveToDisk(MPQHandler->GetFileFrom(component, MPQHandler->LocaleFiles[*itr]), path + component);
|
|
// Extract the DBC files for the given locale
|
|
for (std::set<std::string>::iterator itr2 = DBCFiles.begin(); itr2 != DBCFiles.end(); ++itr2)
|
|
Utils::SaveToDisk(MPQHandler->GetFileFrom(*itr2, MPQHandler->LocaleFiles[*itr]), path + (itr2->c_str() + folderLen));
|
|
}
|
|
printf("DBC extraction finished!\n");
|
|
}
|
|
|
|
void ExtractGameobjectModels()
|
|
{
|
|
Constants::ToWoWCoords = true;
|
|
printf("Extracting GameObject models\n");
|
|
|
|
std::string baseBuildingsPath = "Buildings/";
|
|
std::string baseVmapsPath = "vmaps/";
|
|
Utils::CreateDir(baseVmapsPath);
|
|
Utils::CreateDir(baseBuildingsPath);
|
|
|
|
FILE* modelList = fopen((baseVmapsPath + "GameObjectModels.list").c_str(), "wb");
|
|
if (!modelList)
|
|
{
|
|
printf("Could not create file vmaps/GameObjectModels.list, please make sure that you have the write permissions in the folder\n");
|
|
return;
|
|
}
|
|
|
|
DBC* dbc = MPQHandler->GetDBC("GameObjectDisplayInfo");
|
|
for (std::vector<Record*>::iterator itr = dbc->Records.begin(); itr != dbc->Records.end(); ++itr)
|
|
{
|
|
std::string path = (*itr)->GetString(1);
|
|
std::string fileName = Utils::GetPlainName(path.c_str());
|
|
std::string extension = Utils::GetExtension(fileName);
|
|
// Convert the extension to lowercase
|
|
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
|
|
if (extension == "mdx" || extension == "m2")
|
|
{
|
|
fileName = Utils::FixModelPath(fileName);
|
|
Model model(path);
|
|
|
|
if (model.IsBad)
|
|
continue;
|
|
|
|
FILE* output = fopen((baseBuildingsPath + fileName).c_str(), "wb");
|
|
if (!output)
|
|
{
|
|
printf("Could not create file %s, please check that you have write permissions\n", (baseBuildingsPath + fileName).c_str());
|
|
continue;
|
|
}
|
|
// Placeholder for 0 values
|
|
int Nop[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
|
|
|
fwrite(Constants::VMAPMagic, 8, 1, output);
|
|
uint32 numVerts = model.Header.CountBoundingVertices;
|
|
fwrite(&numVerts, sizeof(uint32), 1, output);
|
|
uint32 numGroups = 1;
|
|
fwrite(&numGroups, sizeof(uint32), 1, output);
|
|
fwrite(Nop, 4 * 3, 1, output); // rootwmoid, flags, groupid
|
|
fwrite(Nop, sizeof(float), 3 * 2, output);//bbox, only needed for WMO currently
|
|
fwrite(Nop, 4, 1, output);// liquidflags
|
|
fwrite("GRP ", 4, 1, output);
|
|
|
|
uint32 branches = 1;
|
|
uint32 wsize = sizeof(branches) + sizeof(uint32) * branches;
|
|
fwrite(&wsize, sizeof(uint32), 1, output);
|
|
fwrite(&branches, sizeof(branches), 1, output);
|
|
uint32 numTris = model.Header.CountBoundingTriangles;
|
|
fwrite(&numTris, sizeof(uint32), 1, output);
|
|
fwrite("INDX", 4, 1, output);
|
|
wsize = sizeof(uint32) + sizeof(unsigned short) * numTris;
|
|
fwrite(&wsize, sizeof(int), 1, output);
|
|
fwrite(&numTris, sizeof(uint32), 1, output);
|
|
uint16* indices = new uint16[numTris];
|
|
|
|
if (numTris > 0)
|
|
{
|
|
uint32 i = 0;
|
|
for (std::vector<Triangle<uint16>>::iterator itr2 = model.Triangles.begin(); itr2 != model.Triangles.end(); ++itr2, ++i)
|
|
{
|
|
indices[i * 3 + 0] = itr2->V0;
|
|
indices[i * 3 + 1] = itr2->V1;
|
|
indices[i * 3 + 2] = itr2->V2;
|
|
}
|
|
fwrite(indices, sizeof(uint16), numTris, output);
|
|
}
|
|
|
|
fwrite("VERT", 4, 1, output);
|
|
wsize = sizeof(int) + sizeof(float) * 3 * numVerts;
|
|
fwrite(&wsize, sizeof(int), 1, output);
|
|
fwrite(&numVerts, sizeof(int), 1, output);
|
|
float* vertices = new float[numVerts * 3];
|
|
|
|
if (numVerts > 0)
|
|
{
|
|
uint32 i = 0;
|
|
for (std::vector<Vector3>::iterator itr2 = model.Vertices.begin(); itr2 != model.Vertices.end(); ++itr2, ++i)
|
|
{
|
|
vertices[i * 3 + 0] = itr2->x;
|
|
vertices[i * 3 + 1] = itr2->y;
|
|
vertices[i * 3 + 2] = itr2->z;
|
|
}
|
|
|
|
fwrite(vertices, sizeof(float), numVerts * 3, output);
|
|
}
|
|
|
|
fclose(output);
|
|
delete[] indices;
|
|
delete[] vertices;
|
|
|
|
uint32 displayId = (*itr)->Values[0];
|
|
uint32 pathLength = fileName.size();
|
|
fwrite(&displayId, sizeof(uint32), 1, modelList);
|
|
fwrite(&pathLength, sizeof(uint32), 1, modelList);
|
|
fwrite(fileName.c_str(), sizeof(char), pathLength, modelList);
|
|
}
|
|
else if (extension == "wmo")
|
|
{
|
|
WorldModelRoot model(path);
|
|
|
|
FILE* output = fopen((baseBuildingsPath + fileName).c_str(), "wb");
|
|
if (!output)
|
|
{
|
|
printf("Could not create file %s, please check that you have write permissions\n", (baseBuildingsPath + fileName).c_str());
|
|
continue;
|
|
}
|
|
|
|
fwrite(Constants::VMAPMagic, 1, 8, output);
|
|
uint32 numVertices = 0;
|
|
fwrite(&numVertices, sizeof(uint32), 1, output); // will be filled later
|
|
fwrite(&model.Header.CountGroups, sizeof(uint32), 1, output);
|
|
fwrite(&model.Header.WmoId, sizeof(uint32), 1, output);
|
|
|
|
const char grp[] = { 'G', 'R', 'P', ' ' };
|
|
for (std::vector<WorldModelGroup>::iterator itr2 = model.Groups.begin(); itr2 != model.Groups.end(); ++itr2)
|
|
{
|
|
const WMOGroupHeader& header = itr2->Header;
|
|
fwrite(&header.Flags, sizeof(uint32), 1, output);
|
|
fwrite(&header.WmoId, sizeof(uint32), 1, output);
|
|
fwrite(&header.BoundingBox[0], sizeof(uint32), 1, output);
|
|
fwrite(&header.BoundingBox[1], sizeof(uint32), 1, output);
|
|
uint32 LiquidFlags = itr2->HasLiquidData ? 1 : 0;
|
|
fwrite(&LiquidFlags, sizeof(uint32), 1, output);
|
|
|
|
fwrite(grp, sizeof(char), sizeof(grp), output);
|
|
uint32 k = 0;
|
|
uint32 mobaBatch = itr2->MOBALength / 12;
|
|
uint32* MobaEx = new uint32[mobaBatch * 4];
|
|
|
|
for (uint32 i = 8; i < itr2->MOBALength; i += 12)
|
|
MobaEx[k++] = itr2->MOBA[i];
|
|
|
|
int mobaSizeGrp = mobaBatch * 4 + 4;
|
|
fwrite(&mobaSizeGrp, 4, 1, output);
|
|
fwrite(&mobaBatch, 4, 1, output);
|
|
fwrite(MobaEx, 4, k, output);
|
|
delete[] MobaEx;
|
|
|
|
//@TODO: Finish this.
|
|
}
|
|
|
|
fclose(output);
|
|
}
|
|
}
|
|
|
|
fclose(modelList);
|
|
printf("GameObject models extraction finished!");
|
|
Constants::ToWoWCoords = false;
|
|
}
|
|
|
|
bool HandleArgs(int argc, char** argv, uint32& threads, std::set<uint32>& mapList, bool& debugOutput, uint32& extractFlags)
|
|
{
|
|
char* param = nullptr;
|
|
extractFlags = 0;
|
|
|
|
for (int i = 1; i < argc; ++i)
|
|
{
|
|
if (strcmp(argv[i], "--threads") == 0)
|
|
{
|
|
param = argv[++i];
|
|
if (!param)
|
|
return false;
|
|
|
|
threads = atoi(param);
|
|
printf("Using %u threads\n", threads);
|
|
}
|
|
else if (strcmp(argv[i], "--maps") == 0)
|
|
{
|
|
param = argv[++i];
|
|
if (!param)
|
|
return false;
|
|
|
|
char* copy = strdup(param);
|
|
char* token = strtok(copy, ",");
|
|
while (token)
|
|
{
|
|
mapList.insert(atoi(token));
|
|
token = strtok(nullptr, ",");
|
|
}
|
|
|
|
free(copy);
|
|
|
|
printf("Extracting only provided list of maps (%u).\n", uint32(mapList.size()));
|
|
}
|
|
else if (strcmp(argv[i], "--debug") == 0)
|
|
{
|
|
param = argv[++i];
|
|
if (!param)
|
|
return false;
|
|
debugOutput = atoi(param);
|
|
if (debugOutput)
|
|
printf("Output will contain debug information (.obj files)\n");
|
|
}
|
|
else if (strcmp(argv[i], "--extract") == 0)
|
|
{
|
|
param = argv[++i];
|
|
if (!param)
|
|
return false;
|
|
|
|
extractFlags = atoi(param);
|
|
|
|
if (!(extractFlags & Constants::EXTRACT_FLAG_ALLOWED)) // Tried to use an invalid flag
|
|
return false;
|
|
|
|
printf("Detected flags: \n");
|
|
printf("* Extract DBCs: %s\n", (extractFlags & Constants::EXTRACT_FLAG_DBC) ? "Yes" : "No");
|
|
printf("* Extract Maps: %s\n", (extractFlags & Constants::EXTRACT_FLAG_MAPS) ? "Yes" : "No");
|
|
printf("* Extract VMaps: %s\n", (extractFlags & Constants::EXTRACT_FLAG_VMAPS) ? "Yes" : "No");
|
|
printf("* Extract GameObject Models: %s\n", (extractFlags & Constants::EXTRACT_FLAG_GOB_MODELS) ? "Yes" : "No");
|
|
printf("* Extract MMaps: %s\n", (extractFlags & Constants::EXTRACT_FLAG_MMAPS) ? "Yes" : "No");
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void PrintUsage()
|
|
{
|
|
printf("MeshExtractor help.\n");
|
|
printf("* Use \"--threads <number>\" to specify <number> threads, default to 4 (Only available when extracting MMaps)\n");
|
|
printf("* Use \"--maps a,b,c,d,e\" to extract only the maps specified (Do not use spaces)\n");
|
|
printf("* Use \"--debug 1\" to generate debug information of the tiles (Only available when extracting MMaps)\n");
|
|
printf("* Use \"--extract X\" to extract the data specified by the flag X (Note: You can combine the flags with the bitwise OR operator |). Available flags are: \n");
|
|
{
|
|
printf("- %u to extract DBCs\n", Constants::EXTRACT_FLAG_DBC);
|
|
printf("- %u to extract Maps (Not yet implemented)\n", Constants::EXTRACT_FLAG_MAPS);
|
|
printf("- %u to extract VMaps (Not yet implemented)\n", Constants::EXTRACT_FLAG_VMAPS);
|
|
printf("- %u to extract GameObject models (Not yet finished, you need to run VMapAssembler on the extracted files)\n", Constants::EXTRACT_FLAG_GOB_MODELS);
|
|
printf("- %u to extract MMaps (Not yet finished)\n", Constants::EXTRACT_FLAG_MMAPS);
|
|
}
|
|
}
|
|
|
|
void LoadTile(dtNavMesh*& navMesh, const char* tile)
|
|
{
|
|
FILE* f = fopen(tile, "rb");
|
|
if (!f)
|
|
return;
|
|
MmapTileHeader header;
|
|
|
|
if (fread(&header, sizeof(MmapTileHeader), 1, f) != 1)
|
|
return;
|
|
|
|
uint8* nav = new uint8[header.size];
|
|
if (fread(nav, header.size, 1, f) != 1)
|
|
return;
|
|
|
|
navMesh->addTile(nav, header.size, DT_TILE_FREE_DATA, 0, nullptr);
|
|
|
|
fclose(f);
|
|
}
|
|
|
|
int main(int argc, char* argv[])
|
|
{
|
|
_setmaxstdio(2048);
|
|
uint32 threads = 4, extractFlags = 0;
|
|
std::set<uint32> mapIds;
|
|
|
|
if (!HandleArgs(argc, argv, threads, mapIds, Constants::Debug, extractFlags))
|
|
{
|
|
PrintUsage();
|
|
return -1;
|
|
}
|
|
|
|
if (extractFlags == 0)
|
|
{
|
|
printf("You must provide valid extract flags.\n");
|
|
PrintUsage();
|
|
return -1;
|
|
}
|
|
|
|
Cache = new CacheClass();
|
|
MPQHandler = new MPQManager();
|
|
MPQHandler->Initialize();
|
|
|
|
if (extractFlags & Constants::EXTRACT_FLAG_DBC)
|
|
ExtractDBCs();
|
|
|
|
if (extractFlags & Constants::EXTRACT_FLAG_MMAPS)
|
|
ExtractMMaps(mapIds, threads);
|
|
|
|
if (extractFlags & Constants::EXTRACT_FLAG_GOB_MODELS)
|
|
ExtractGameobjectModels();
|
|
|
|
if (extractFlags & Constants::EXTRACT_FLAG_TEST)
|
|
{
|
|
float start[] = { 16226.200195f, 16257.000000f, 13.202200f };
|
|
float end[] = { 16245.725586f, 16382.465820f, 47.384956f };
|
|
|
|
//
|
|
float m_spos[3];
|
|
m_spos[0] = -start[1];
|
|
m_spos[1] = start[2];
|
|
m_spos[2] = -start[0];
|
|
|
|
//
|
|
float m_epos[3];
|
|
m_epos[0] = -end[1];
|
|
m_epos[1] = end[2];
|
|
m_epos[2] = -end[0];
|
|
|
|
//
|
|
dtQueryFilterExt m_filter;
|
|
m_filter.setIncludeFlags(Constants::POLY_AREA_ROAD | Constants::POLY_AREA_TERRAIN);
|
|
m_filter.setExcludeFlags(Constants::POLY_AREA_WATER);
|
|
|
|
//
|
|
float m_polyPickExt[3];
|
|
m_polyPickExt[0] = 2.5f;
|
|
m_polyPickExt[1] = 2.5f;
|
|
m_polyPickExt[2] = 2.5f;
|
|
|
|
//
|
|
dtPolyRef m_startRef;
|
|
dtPolyRef m_endRef;
|
|
|
|
FILE* mmap = fopen("mmaps/001.mmap", "rb");
|
|
dtNavMeshParams params;
|
|
int count = fread(¶ms, sizeof(dtNavMeshParams), 1, mmap);
|
|
fclose(mmap);
|
|
if (count != 1)
|
|
{
|
|
printf("main: Error reading from .mmap\n");
|
|
return 0;
|
|
}
|
|
|
|
dtNavMesh* navMesh = new dtNavMesh();
|
|
dtNavMeshQuery* navMeshQuery = new dtNavMeshQuery();
|
|
|
|
navMesh->init(¶ms);
|
|
for (int i = 0; i <= 32; ++i)
|
|
{
|
|
for (int j = 0; j <= 32; ++j)
|
|
{
|
|
char buff[100];
|
|
sprintf(buff, "mmaps/001%02i%02i.mmtile", i, j);
|
|
LoadTile(navMesh, buff);
|
|
}
|
|
}
|
|
|
|
navMeshQuery->init(navMesh, 2048);
|
|
|
|
float nearestPt[3];
|
|
|
|
navMeshQuery->findNearestPoly(m_spos, m_polyPickExt, &m_filter, &m_startRef, nearestPt);
|
|
navMeshQuery->findNearestPoly(m_epos, m_polyPickExt, &m_filter, &m_endRef, nearestPt);
|
|
|
|
if ( !m_startRef || !m_endRef )
|
|
{
|
|
std::cerr << "Could not find any nearby poly's (" << m_startRef << "," << m_endRef << ")" << std::endl;
|
|
return 0;
|
|
}
|
|
|
|
int hops;
|
|
dtPolyRef* hopBuffer = new dtPolyRef[8192];
|
|
dtStatus status = navMeshQuery->findPath(m_startRef, m_endRef, m_spos, m_epos, &m_filter, hopBuffer, &hops, 8192);
|
|
|
|
int resultHopCount;
|
|
float* straightPath = new float[2048 * 3];
|
|
unsigned char* pathFlags = new unsigned char[2048];
|
|
dtPolyRef* pathRefs = new dtPolyRef[2048];
|
|
|
|
status = navMeshQuery->findStraightPath(m_spos, m_epos, hopBuffer, hops, straightPath, pathFlags, pathRefs, &resultHopCount, 2048);
|
|
std::vector<Vector3> FinalPath;
|
|
FinalPath.reserve(resultHopCount);
|
|
for (int32 i = 0; i < resultHopCount; ++i)
|
|
{
|
|
Vector3 finalV = Utils::ToWoWCoords(Vector3(straightPath[i * 3 + 0], straightPath[i * 3 + 1], straightPath[i * 3 + 2]));
|
|
FinalPath.push_back(finalV);
|
|
printf("Point %f %f %f\n", finalV.x, finalV.y, finalV.z);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|