mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-11 21:01:29 +00:00
1925 lines
44 KiB
C++
1925 lines
44 KiB
C++
#include "../common/global_define.h"
|
|
|
|
#include "client.h"
|
|
#include "doors.h"
|
|
#include "pathing.h"
|
|
#include "water_map.h"
|
|
#include "zone.h"
|
|
|
|
#include <fstream>
|
|
#include <list>
|
|
#include <math.h>
|
|
#include <sstream>
|
|
#include <string.h>
|
|
|
|
#ifdef _WINDOWS
|
|
#define snprintf _snprintf
|
|
#endif
|
|
|
|
//#define PATHDEBUG
|
|
|
|
extern Zone *zone;
|
|
|
|
float VectorDistance(glm::vec3 a, glm::vec3 b)
|
|
{
|
|
float xdist = a.x - b.x;
|
|
float ydist = a.y - b.y;
|
|
float zdist = a.z - b.z;
|
|
return sqrtf(xdist * xdist + ydist * ydist + zdist * zdist);
|
|
}
|
|
|
|
float VectorDistanceNoRoot(glm::vec3 a, glm::vec3 b)
|
|
{
|
|
float xdist = a.x - b.x;
|
|
float ydist = a.y - b.y;
|
|
float zdist = a.z - b.z;
|
|
return xdist * xdist + ydist * ydist + zdist * zdist;
|
|
|
|
}
|
|
|
|
PathManager* PathManager::LoadPathFile(const char* ZoneName)
|
|
{
|
|
|
|
FILE *PathFile = nullptr;
|
|
|
|
char LowerCaseZoneName[64];
|
|
|
|
char ZonePathFileName[256];
|
|
|
|
PathManager* Ret = nullptr;
|
|
|
|
strn0cpy(LowerCaseZoneName, ZoneName, 64);
|
|
|
|
strlwr(LowerCaseZoneName);
|
|
|
|
snprintf(ZonePathFileName, 250, "%s%s.path", Config->MapDir.c_str(), LowerCaseZoneName);
|
|
|
|
if((PathFile = fopen(ZonePathFileName, "rb")))
|
|
{
|
|
Ret = new PathManager();
|
|
|
|
if(Ret->loadPaths(PathFile))
|
|
{
|
|
Log(Logs::General, Logs::Status, "Path File %s loaded.", ZonePathFileName);
|
|
|
|
}
|
|
else
|
|
{
|
|
Log(Logs::General, Logs::Error, "Path File %s failed to load.", ZonePathFileName);
|
|
safe_delete(Ret);
|
|
}
|
|
fclose(PathFile);
|
|
}
|
|
else
|
|
{
|
|
Log(Logs::General, Logs::Error, "Path File %s not found.", ZonePathFileName);
|
|
}
|
|
|
|
return Ret;
|
|
}
|
|
|
|
PathManager::PathManager()
|
|
{
|
|
PathNodes = nullptr;
|
|
ClosedListFlag = nullptr;
|
|
Head.PathNodeCount = 0;
|
|
Head.version = 2;
|
|
QuickConnectTarget = -1;
|
|
}
|
|
|
|
PathManager::~PathManager()
|
|
{
|
|
safe_delete_array(PathNodes);
|
|
safe_delete_array(ClosedListFlag);
|
|
}
|
|
|
|
bool PathManager::loadPaths(FILE *PathFile)
|
|
{
|
|
|
|
char Magic[10];
|
|
|
|
fread(&Magic, 9, 1, PathFile);
|
|
|
|
if(strncmp(Magic, "EQEMUPATH", 9))
|
|
{
|
|
Log(Logs::General, Logs::Error, "Bad Magic String in .path file.");
|
|
return false;
|
|
}
|
|
|
|
fread(&Head, sizeof(Head), 1, PathFile);
|
|
|
|
Log(Logs::General, Logs::Status, "Path File Header: Version %ld, PathNodes %ld",
|
|
(long)Head.version, (long)Head.PathNodeCount);
|
|
|
|
if(Head.version != 2)
|
|
{
|
|
Log(Logs::General, Logs::Error, "Unsupported path file version.");
|
|
return false;
|
|
}
|
|
|
|
PathNodes = new PathNode[Head.PathNodeCount];
|
|
|
|
fread(PathNodes, sizeof(PathNode), Head.PathNodeCount, PathFile);
|
|
|
|
ClosedListFlag = new int[Head.PathNodeCount];
|
|
|
|
#ifdef PATHDEBUG
|
|
PrintPathing();
|
|
#endif
|
|
|
|
int MaxNodeID = Head.PathNodeCount - 1;
|
|
|
|
bool PathFileValid = true;
|
|
|
|
for(uint32 i = 0; i < Head.PathNodeCount; ++i)
|
|
{
|
|
for(uint32 j = 0; j < PATHNODENEIGHBOURS; ++j)
|
|
{
|
|
if(PathNodes[i].Neighbours[j].id > MaxNodeID)
|
|
{
|
|
Log(Logs::General, Logs::Error, "Path Node %i, Neighbour %i (%i) out of range.", i, j, PathNodes[i].Neighbours[j].id);
|
|
|
|
PathFileValid = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!PathFileValid)
|
|
{
|
|
safe_delete_array(PathNodes);
|
|
}
|
|
|
|
return PathFileValid;
|
|
}
|
|
|
|
void PathManager::PrintPathing()
|
|
{
|
|
|
|
for(uint32 i = 0; i < Head.PathNodeCount; ++i)
|
|
{
|
|
printf("PathNode: %2d id %2d. (%8.3f, %8.3f, %8.3f), BestZ: %8.3f\n",
|
|
i, PathNodes[i].id, PathNodes[i].v.x, PathNodes[i].v.y, PathNodes[i].v.z, PathNodes[i].bestz);
|
|
|
|
|
|
if(PathNodes[i].Neighbours[0].id == -1)
|
|
{
|
|
printf(" NO NEIGHBOURS.\n");
|
|
continue;
|
|
}
|
|
|
|
for(int j=0; j<PATHNODENEIGHBOURS; j++)
|
|
{
|
|
if(PathNodes[i].Neighbours[j].id == -1)
|
|
break;
|
|
|
|
printf(" Neighbour: %2d, Distance %8.3f", PathNodes[i].Neighbours[j].id,
|
|
PathNodes[i].Neighbours[j].distance);
|
|
|
|
if(PathNodes[i].Neighbours[j].Teleport)
|
|
printf(" ***** TELEPORT *****");
|
|
|
|
if(PathNodes[i].Neighbours[j].DoorID >= 0)
|
|
printf(" ***** via door %i *****", PathNodes[i].Neighbours[j].DoorID);
|
|
|
|
printf("\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
glm::vec3 PathManager::GetPathNodeCoordinates(int NodeNumber, bool BestZ)
|
|
{
|
|
glm::vec3 Result;
|
|
|
|
if(NodeNumber < Head.PathNodeCount)
|
|
{
|
|
Result = PathNodes[NodeNumber].v;
|
|
|
|
if(!BestZ)
|
|
return Result;
|
|
|
|
Result.z = PathNodes[NodeNumber].bestz;
|
|
}
|
|
|
|
return Result;
|
|
|
|
}
|
|
|
|
std::deque<int> PathManager::FindRoute(int startID, int endID)
|
|
{
|
|
Log(Logs::Detail, Logs::None, "FindRoute from node %i to %i", startID, endID);
|
|
|
|
memset(ClosedListFlag, 0, sizeof(int) * Head.PathNodeCount);
|
|
|
|
std::deque<AStarNode> OpenList, ClosedList;
|
|
|
|
std::deque<int>Route;
|
|
|
|
AStarNode AStarEntry, CurrentNode;
|
|
|
|
AStarEntry.PathNodeID = startID;
|
|
AStarEntry.Parent = -1;
|
|
AStarEntry.HCost = 0;
|
|
AStarEntry.GCost = 0;
|
|
AStarEntry.Teleport = false;
|
|
|
|
OpenList.push_back(AStarEntry);
|
|
|
|
while(!OpenList.empty())
|
|
{
|
|
// The OpenList is maintained in sorted order, lowest to highest cost.
|
|
|
|
CurrentNode = (*OpenList.begin());
|
|
|
|
ClosedList.push_back(CurrentNode);
|
|
|
|
ClosedListFlag[CurrentNode.PathNodeID] = true;
|
|
|
|
OpenList.pop_front();
|
|
|
|
for(int i = 0; i < PATHNODENEIGHBOURS; ++i)
|
|
{
|
|
if(PathNodes[CurrentNode.PathNodeID].Neighbours[i].id == -1)
|
|
break;
|
|
|
|
if(PathNodes[CurrentNode.PathNodeID].Neighbours[i].id == CurrentNode.Parent)
|
|
continue;
|
|
|
|
if(PathNodes[CurrentNode.PathNodeID].Neighbours[i].id == endID)
|
|
{
|
|
Route.push_back(CurrentNode.PathNodeID);
|
|
|
|
Route.push_back(endID);
|
|
|
|
std::deque<AStarNode>::iterator RouteIterator;
|
|
|
|
while(CurrentNode.PathNodeID != startID)
|
|
{
|
|
for(RouteIterator = ClosedList.begin(); RouteIterator != ClosedList.end(); ++RouteIterator)
|
|
{
|
|
if((*RouteIterator).PathNodeID == CurrentNode.Parent)
|
|
{
|
|
if(CurrentNode.Teleport)
|
|
Route.insert(Route.begin(), -1);
|
|
|
|
CurrentNode = (*RouteIterator);
|
|
|
|
Route.insert(Route.begin(), CurrentNode.PathNodeID);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return Route;
|
|
}
|
|
if(ClosedListFlag[PathNodes[CurrentNode.PathNodeID].Neighbours[i].id])
|
|
continue;
|
|
|
|
AStarEntry.PathNodeID = PathNodes[CurrentNode.PathNodeID].Neighbours[i].id;
|
|
|
|
AStarEntry.Parent = CurrentNode.PathNodeID;
|
|
|
|
AStarEntry.Teleport = PathNodes[CurrentNode.PathNodeID].Neighbours[i].Teleport;
|
|
|
|
// HCost is the estimated cost to get from this node to the end.
|
|
AStarEntry.HCost = VectorDistance(PathNodes[PathNodes[CurrentNode.PathNodeID].Neighbours[i].id].v,
|
|
PathNodes[endID].v);
|
|
|
|
AStarEntry.GCost = CurrentNode.GCost + PathNodes[CurrentNode.PathNodeID].Neighbours[i].distance;
|
|
|
|
float FCost = AStarEntry.HCost + AStarEntry.GCost;
|
|
#ifdef PATHDEBUG
|
|
printf("Node: %i, Open Neighbour %i has HCost %8.3f, GCost %8.3f (Total Cost: %8.3f)\n",
|
|
CurrentNode.PathNodeID,
|
|
PathNodes[CurrentNode.PathNodeID].Neighbours[i].id,
|
|
AStarEntry.HCost,
|
|
AStarEntry.GCost,
|
|
AStarEntry.HCost + AStarEntry.GCost);
|
|
#endif
|
|
|
|
bool AlreadyInOpenList = false;
|
|
|
|
std::deque<AStarNode>::iterator OpenListIterator, InsertionPoint = OpenList.end();
|
|
|
|
for(OpenListIterator = OpenList.begin(); OpenListIterator != OpenList.end(); ++OpenListIterator)
|
|
{
|
|
if((*OpenListIterator).PathNodeID == PathNodes[CurrentNode.PathNodeID].Neighbours[i].id)
|
|
{
|
|
AlreadyInOpenList = true;
|
|
|
|
float GCostToNode = CurrentNode.GCost + PathNodes[CurrentNode.PathNodeID].Neighbours[i].distance;
|
|
|
|
if(GCostToNode < (*OpenListIterator).GCost)
|
|
{
|
|
(*OpenListIterator).Parent = CurrentNode.PathNodeID;
|
|
|
|
(*OpenListIterator).GCost = GCostToNode;
|
|
|
|
(*OpenListIterator).Teleport = PathNodes[CurrentNode.PathNodeID].Neighbours[i].Teleport;
|
|
}
|
|
break;
|
|
}
|
|
else if((InsertionPoint == OpenList.end()) && (((*OpenListIterator).HCost + (*OpenListIterator).GCost) > FCost))
|
|
{
|
|
InsertionPoint = OpenListIterator;
|
|
}
|
|
}
|
|
if(!AlreadyInOpenList)
|
|
OpenList.insert(InsertionPoint, AStarEntry);
|
|
}
|
|
|
|
}
|
|
Log(Logs::Detail, Logs::None, "Unable to find a route.");
|
|
return Route;
|
|
|
|
}
|
|
|
|
bool CheckLOSBetweenPoints(glm::vec3 start, glm::vec3 end) {
|
|
|
|
glm::vec3 hit;
|
|
|
|
if((zone->zonemap) && (zone->zonemap->LineIntersectsZone(start, end, 1, &hit)))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
auto path_compare = [](const PathNodeSortStruct& a, const PathNodeSortStruct& b)
|
|
{
|
|
return a.Distance < b.Distance;
|
|
};
|
|
|
|
std::deque<int> PathManager::FindRoute(glm::vec3 Start, glm::vec3 End)
|
|
{
|
|
Log(Logs::Detail, Logs::None, "FindRoute(%8.3f, %8.3f, %8.3f, %8.3f, %8.3f, %8.3f)", Start.x, Start.y, Start.z, End.x, End.y, End.z);
|
|
|
|
std::deque<int> noderoute;
|
|
|
|
float CandidateNodeRangeXY = RuleR(Pathing, CandidateNodeRangeXY);
|
|
|
|
float CandidateNodeRangeZ = RuleR(Pathing, CandidateNodeRangeZ);
|
|
|
|
// Find the nearest PathNode the Start has LOS to.
|
|
//
|
|
//
|
|
int ClosestPathNodeToStart = -1;
|
|
|
|
std::deque<PathNodeSortStruct> SortedByDistance;
|
|
|
|
PathNodeSortStruct TempNode;
|
|
|
|
for(uint32 i = 0 ; i < Head.PathNodeCount; ++i)
|
|
{
|
|
if ((std::abs(Start.x - PathNodes[i].v.x) <= CandidateNodeRangeXY) &&
|
|
(std::abs(Start.y - PathNodes[i].v.y) <= CandidateNodeRangeXY) &&
|
|
(std::abs(Start.z - PathNodes[i].v.z) <= CandidateNodeRangeZ)) {
|
|
TempNode.id = i;
|
|
TempNode.Distance = VectorDistanceNoRoot(Start, PathNodes[i].v);
|
|
SortedByDistance.push_back(TempNode);
|
|
|
|
}
|
|
}
|
|
|
|
std::sort(SortedByDistance.begin(), SortedByDistance.end(), path_compare);
|
|
|
|
for(auto Iterator = SortedByDistance.begin(); Iterator != SortedByDistance.end(); ++Iterator)
|
|
{
|
|
Log(Logs::Detail, Logs::None, "Checking Reachability of Node %i from Start Position.", PathNodes[(*Iterator).id].id);
|
|
|
|
if(!zone->zonemap->LineIntersectsZone(Start, PathNodes[(*Iterator).id].v, 1.0f, nullptr))
|
|
{
|
|
ClosestPathNodeToStart = (*Iterator).id;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(ClosestPathNodeToStart <0 ) {
|
|
Log(Logs::Detail, Logs::None, "No LOS to any starting Path Node within range.");
|
|
return noderoute;
|
|
}
|
|
|
|
Log(Logs::Detail, Logs::None, "Closest Path Node To Start: %2d", ClosestPathNodeToStart);
|
|
|
|
// Find the nearest PathNode the end point has LOS to
|
|
|
|
int ClosestPathNodeToEnd = -1;
|
|
|
|
SortedByDistance.clear();
|
|
|
|
for(uint32 i = 0 ; i < Head.PathNodeCount; ++i)
|
|
{
|
|
if ((std::abs(End.x - PathNodes[i].v.x) <= CandidateNodeRangeXY) &&
|
|
(std::abs(End.y - PathNodes[i].v.y) <= CandidateNodeRangeXY) &&
|
|
(std::abs(End.z - PathNodes[i].v.z) <= CandidateNodeRangeZ)) {
|
|
TempNode.id = i;
|
|
TempNode.Distance = VectorDistanceNoRoot(End, PathNodes[i].v);
|
|
SortedByDistance.push_back(TempNode);
|
|
}
|
|
}
|
|
|
|
std::sort(SortedByDistance.begin(), SortedByDistance.end(), path_compare);
|
|
|
|
for(auto Iterator = SortedByDistance.begin(); Iterator != SortedByDistance.end(); ++Iterator)
|
|
{
|
|
Log(Logs::Detail, Logs::None, "Checking Reachability of Node %i from End Position.", PathNodes[(*Iterator).id].id);
|
|
Log(Logs::Detail, Logs::None, " (%8.3f, %8.3f, %8.3f) to (%8.3f, %8.3f, %8.3f)",
|
|
End.x, End.y, End.z,
|
|
PathNodes[(*Iterator).id].v.x, PathNodes[(*Iterator).id].v.y, PathNodes[(*Iterator).id].v.z);
|
|
|
|
if(!zone->zonemap->LineIntersectsZone(End, PathNodes[(*Iterator).id].v, 1.0f, nullptr))
|
|
{
|
|
ClosestPathNodeToEnd = (*Iterator).id;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(ClosestPathNodeToEnd < 0) {
|
|
Log(Logs::Detail, Logs::None, "No LOS to any end Path Node within range.");
|
|
return noderoute;
|
|
}
|
|
|
|
Log(Logs::Detail, Logs::None, "Closest Path Node To End: %2d", ClosestPathNodeToEnd);
|
|
|
|
if(ClosestPathNodeToStart == ClosestPathNodeToEnd)
|
|
{
|
|
noderoute.push_back(ClosestPathNodeToStart);
|
|
return noderoute;
|
|
}
|
|
noderoute = FindRoute(ClosestPathNodeToStart, ClosestPathNodeToEnd);
|
|
|
|
int NodesToAttemptToCull = RuleI(Pathing, CullNodesFromStart);
|
|
|
|
if(NodesToAttemptToCull > 0)
|
|
{
|
|
int CulledNodes = 0;
|
|
|
|
std::deque<int>::iterator First, Second;
|
|
|
|
while((noderoute.size() >= 2) && (CulledNodes < NodesToAttemptToCull))
|
|
{
|
|
First = noderoute.begin();
|
|
|
|
Second = First;
|
|
|
|
++Second;
|
|
|
|
if((*Second) < 0)
|
|
break;
|
|
|
|
if(!zone->zonemap->LineIntersectsZone(Start, PathNodes[(*Second)].v, 1.0f, nullptr)
|
|
&& NoHazards(Start, PathNodes[(*Second)].v))
|
|
{
|
|
noderoute.erase(First);
|
|
|
|
++CulledNodes;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
|
|
NodesToAttemptToCull = RuleI(Pathing, CullNodesFromEnd);
|
|
|
|
if(NodesToAttemptToCull > 0)
|
|
{
|
|
int CulledNodes = 0;
|
|
|
|
std::deque<int>::iterator First, Second;
|
|
|
|
while((noderoute.size() >= 2) && (CulledNodes < NodesToAttemptToCull))
|
|
{
|
|
First = noderoute.end();
|
|
|
|
--First;
|
|
|
|
Second = First;
|
|
|
|
--Second;
|
|
|
|
if((*Second) < 0)
|
|
break;
|
|
|
|
if(!zone->zonemap->LineIntersectsZone(End, PathNodes[(*Second)].v, 1.0f, nullptr)
|
|
&& NoHazards(End, PathNodes[(*Second)].v))
|
|
{
|
|
noderoute.erase(First);
|
|
|
|
++CulledNodes;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
|
|
return noderoute;
|
|
}
|
|
|
|
const char* DigitToWord(int i)
|
|
{
|
|
switch(i) {
|
|
case 0:
|
|
return "zero";
|
|
case 1:
|
|
return "one";
|
|
case 2:
|
|
return "two";
|
|
case 3:
|
|
return "three";
|
|
case 4:
|
|
return "four";
|
|
case 5:
|
|
return "five";
|
|
case 6:
|
|
return "six";
|
|
case 7:
|
|
return "seven";
|
|
case 8:
|
|
return "eight";
|
|
case 9:
|
|
return "nine";
|
|
}
|
|
return "";
|
|
}
|
|
|
|
void PathManager::SpawnPathNodes()
|
|
{
|
|
|
|
for(uint32 i = 0; i < Head.PathNodeCount; ++i)
|
|
{
|
|
auto npc_type = new NPCType;
|
|
memset(npc_type, 0, sizeof(NPCType));
|
|
|
|
auto c = PathNodes[i].id / 1000u;
|
|
sprintf(npc_type->name, "Node%u", c);
|
|
npc_type->cur_hp = 4000000;
|
|
npc_type->max_hp = 4000000;
|
|
npc_type->race = 2254;
|
|
npc_type->gender = 2;
|
|
npc_type->class_ = 9;
|
|
npc_type->deity= 1;
|
|
npc_type->level = 75;
|
|
npc_type->npc_id = 0;
|
|
npc_type->loottable_id = 0;
|
|
npc_type->texture = 1;
|
|
npc_type->light = 0;
|
|
npc_type->runspeed = 0;
|
|
npc_type->d_melee_texture1 = 1;
|
|
npc_type->d_melee_texture2 = 1;
|
|
npc_type->merchanttype = 1;
|
|
npc_type->bodytype = 1;
|
|
|
|
npc_type->STR = 150;
|
|
npc_type->STA = 150;
|
|
npc_type->DEX = 150;
|
|
npc_type->AGI = 150;
|
|
npc_type->INT = 150;
|
|
npc_type->WIS = 150;
|
|
npc_type->CHA = 150;
|
|
|
|
npc_type->findable = 1;
|
|
auto position = glm::vec4(PathNodes[i].v.x, PathNodes[i].v.y, PathNodes[i].v.z, 0.0f);
|
|
auto npc = new NPC(npc_type, nullptr, position, FlyMode1);
|
|
npc->GiveNPCTypeData(npc_type);
|
|
|
|
entity_list.AddNPC(npc, true, true);
|
|
}
|
|
}
|
|
|
|
void PathManager::MeshTest()
|
|
{
|
|
// This will test connectivity between all path nodes
|
|
|
|
int TotalTests = 0;
|
|
int NoConnections = 0;
|
|
|
|
printf("Beginning Pathmanager connectivity tests.\n"); fflush(stdout);
|
|
|
|
for(uint32 i = 0; i < Head.PathNodeCount; ++i)
|
|
{
|
|
for(uint32 j = 0; j < Head.PathNodeCount; ++j)
|
|
{
|
|
if(j == i)
|
|
continue;
|
|
|
|
std::deque<int> Route = FindRoute(PathNodes[i].id, PathNodes[j].id);
|
|
|
|
if(Route.empty())
|
|
{
|
|
++NoConnections;
|
|
printf("FindRoute(%i, %i) **** NO ROUTE FOUND ****\n", PathNodes[i].id, PathNodes[j].id);
|
|
}
|
|
++TotalTests;
|
|
}
|
|
}
|
|
printf("Executed %i route searches.\n", TotalTests);
|
|
printf("Failed to find %i routes.\n", NoConnections);
|
|
fflush(stdout);
|
|
}
|
|
|
|
void PathManager::SimpleMeshTest()
|
|
{
|
|
// This will test connectivity between the first path node and all other nodes
|
|
|
|
int TotalTests = 0;
|
|
int NoConnections = 0;
|
|
|
|
printf("Beginning Pathmanager connectivity tests.\n");
|
|
fflush(stdout);
|
|
|
|
for(uint32 j = 1; j < Head.PathNodeCount; ++j)
|
|
{
|
|
std::deque<int> Route = FindRoute(PathNodes[0].id, PathNodes[j].id);
|
|
|
|
if(Route.empty())
|
|
{
|
|
++NoConnections;
|
|
printf("FindRoute(%i, %i) **** NO ROUTE FOUND ****\n", PathNodes[0].id, PathNodes[j].id);
|
|
}
|
|
++TotalTests;
|
|
}
|
|
printf("Executed %i route searches.\n", TotalTests);
|
|
printf("Failed to find %i routes.\n", NoConnections);
|
|
fflush(stdout);
|
|
}
|
|
|
|
glm::vec3 Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &WaypointChanged, bool &NodeReached)
|
|
{
|
|
glm::vec3 To(ToX, ToY, ToZ);
|
|
if (Speed <= 0) {
|
|
return To;
|
|
}
|
|
|
|
glm::vec3 From(GetX(), GetY(), GetZ());
|
|
|
|
if (DistanceSquared(To, From) < 1.0f) {
|
|
WaypointChanged = false;
|
|
NodeReached = true;
|
|
Route.clear();
|
|
return To;
|
|
}
|
|
|
|
if (Route.empty()) {
|
|
Route = zone->pathing->FindRoute(From, To);
|
|
PathingDestination = To;
|
|
WaypointChanged = true;
|
|
NodeReached = false;
|
|
return *Route.begin();
|
|
}
|
|
else {
|
|
bool SameDestination = DistanceSquared(To, PathingDestination) < 4.0f;
|
|
if (!SameDestination) {
|
|
//We had a route but our target position moved too much
|
|
Route = zone->pathing->FindRoute(From, To);
|
|
PathingDestination = To;
|
|
WaypointChanged = true;
|
|
NodeReached = false;
|
|
return *Route.begin();
|
|
}
|
|
else {
|
|
bool AtNextNode = DistanceSquared(From, *Route.begin()) < 4.0f;
|
|
if (AtNextNode) {
|
|
WaypointChanged = false;
|
|
NodeReached = true;
|
|
|
|
Route.pop_front();
|
|
|
|
if (Route.empty()) {
|
|
Route = zone->pathing->FindRoute(From, To);
|
|
PathingDestination = To;
|
|
WaypointChanged = true;
|
|
return *Route.begin();
|
|
}
|
|
else {
|
|
auto node = *Route.begin();
|
|
if (node.x == 1000000.0f && node.y == 1000000.0f && node.z == 1000000.0f) {
|
|
//If is identity node then is teleport node.
|
|
Route.pop_front();
|
|
|
|
if (Route.empty()) {
|
|
return To;
|
|
}
|
|
|
|
auto nextNode = *Route.begin();
|
|
|
|
Teleport(nextNode);
|
|
|
|
Route.pop_front();
|
|
|
|
if (Route.empty()) {
|
|
return To;
|
|
}
|
|
|
|
return *Route.begin();
|
|
}
|
|
|
|
return node;
|
|
}
|
|
}
|
|
else {
|
|
WaypointChanged = false;
|
|
NodeReached = false;
|
|
return *Route.begin();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int PathManager::FindNearestPathNode(glm::vec3 Position)
|
|
{
|
|
|
|
// Find the nearest PathNode we have LOS to.
|
|
//
|
|
//
|
|
|
|
float CandidateNodeRangeXY = RuleR(Pathing, CandidateNodeRangeXY);
|
|
|
|
float CandidateNodeRangeZ = RuleR(Pathing, CandidateNodeRangeZ);
|
|
|
|
int ClosestPathNodeToStart = -1;
|
|
|
|
std::deque<PathNodeSortStruct> SortedByDistance;
|
|
|
|
PathNodeSortStruct TempNode;
|
|
|
|
for(uint32 i = 0 ; i < Head.PathNodeCount; ++i)
|
|
{
|
|
if ((std::abs(Position.x - PathNodes[i].v.x) <= CandidateNodeRangeXY) &&
|
|
(std::abs(Position.y - PathNodes[i].v.y) <= CandidateNodeRangeXY) &&
|
|
(std::abs(Position.z - PathNodes[i].v.z) <= CandidateNodeRangeZ)) {
|
|
TempNode.id = i;
|
|
TempNode.Distance = VectorDistanceNoRoot(Position, PathNodes[i].v);
|
|
SortedByDistance.push_back(TempNode);
|
|
|
|
}
|
|
}
|
|
|
|
std::sort(SortedByDistance.begin(), SortedByDistance.end(), path_compare);
|
|
|
|
for(auto Iterator = SortedByDistance.begin(); Iterator != SortedByDistance.end(); ++Iterator)
|
|
{
|
|
Log(Logs::Detail, Logs::None, "Checking Reachability of Node %i from Start Position.", PathNodes[(*Iterator).id].id);
|
|
|
|
if(!zone->zonemap->LineIntersectsZone(Position, PathNodes[(*Iterator).id].v, 1.0f, nullptr))
|
|
{
|
|
ClosestPathNodeToStart = (*Iterator).id;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(ClosestPathNodeToStart <0 ) {
|
|
Log(Logs::Detail, Logs::None, "No LOS to any starting Path Node within range.");
|
|
return -1;
|
|
}
|
|
return ClosestPathNodeToStart;
|
|
}
|
|
|
|
bool PathManager::NoHazards(glm::vec3 From, glm::vec3 To)
|
|
{
|
|
// Test the Z coordinate at the mid point.
|
|
//
|
|
glm::vec3 MidPoint((From.x + To.x) / 2, (From.y + To.y) / 2, From.z);
|
|
|
|
float NewZ = zone->zonemap->FindBestZ(MidPoint, nullptr);
|
|
|
|
if (std::abs(NewZ - From.z) > RuleR(Pathing, ZDiffThresholdNew)) {
|
|
Log(Logs::Detail, Logs::None, " HAZARD DETECTED moving from %8.3f, %8.3f, %8.3f to %8.3f, %8.3f, %8.3f. Z Change is %8.3f",
|
|
From.x, From.y, From.z, MidPoint.x, MidPoint.y, MidPoint.z, NewZ - From.z);
|
|
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
Log(Logs::Detail, Logs::None, "No HAZARD DETECTED moving from %8.3f, %8.3f, %8.3f to %8.3f, %8.3f, %8.3f. Z Change is %8.3f",
|
|
From.x, From.y, From.z, MidPoint.x, MidPoint.y, MidPoint.z, NewZ - From.z);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool PathManager::NoHazardsAccurate(glm::vec3 From, glm::vec3 To)
|
|
{
|
|
float stepx, stepy, stepz, curx, cury, curz;
|
|
glm::vec3 cur = From;
|
|
float last_z = From.z;
|
|
float step_size = 1.0;
|
|
|
|
curx = From.x;
|
|
cury = From.y;
|
|
curz = From.z;
|
|
|
|
do
|
|
{
|
|
stepx = (float)To.x - curx;
|
|
stepy = (float)To.y - cury;
|
|
stepz = (float)To.z - curz;
|
|
float factor = sqrt(stepx*stepx + stepy*stepy + stepz*stepz);
|
|
stepx = (stepx / factor)*step_size;
|
|
stepy = (stepy / factor)*step_size;
|
|
stepz = (stepz / factor)*step_size;
|
|
|
|
glm::vec3 TestPoint(curx, cury, curz);
|
|
float NewZ = zone->zonemap->FindBestZ(TestPoint, nullptr);
|
|
if (std::abs(NewZ - last_z) > 5.0f) {
|
|
Log(Logs::Detail, Logs::None, " HAZARD DETECTED moving from %8.3f, %8.3f, %8.3f to %8.3f, %8.3f, %8.3f. Best Z %8.3f, Z Change is %8.3f",
|
|
From.x, From.y, From.z, TestPoint.x, TestPoint.y, TestPoint.z, NewZ, NewZ - From.z);
|
|
return false;
|
|
}
|
|
last_z = NewZ;
|
|
|
|
if (zone->watermap)
|
|
{
|
|
auto from = glm::vec3(From.x, From.y, From.z);
|
|
auto to = glm::vec3(To.x, To.y, To.z);
|
|
if (zone->watermap->InLiquid(from) || zone->watermap->InLiquid(to))
|
|
{
|
|
break;
|
|
}
|
|
auto testPointNewZ = glm::vec3(TestPoint.x, TestPoint.y, NewZ);
|
|
if (zone->watermap->InLiquid(testPointNewZ))
|
|
{
|
|
glm::vec3 TestPointWater(TestPoint.x, TestPoint.y, NewZ - 0.5f);
|
|
glm::vec3 TestPointWaterDest = TestPointWater;
|
|
glm::vec3 hit;
|
|
TestPointWaterDest.z -= 500;
|
|
float best_z2 = -999990;
|
|
if (zone->zonemap->LineIntersectsZone(TestPointWater, TestPointWaterDest, 1.0f, &hit))
|
|
{
|
|
best_z2 = hit.z;
|
|
}
|
|
if (best_z2 == -999990)
|
|
{
|
|
Log(Logs::Detail, Logs::None, " HAZARD DETECTED, really deep water/lava!");
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if (std::abs(NewZ - best_z2) > RuleR(Pathing, ZDiffThresholdNew)) {
|
|
Log(Logs::Detail, Logs::None,
|
|
" HAZARD DETECTED, water is fairly deep at %8.3f units deep",
|
|
std::abs(NewZ - best_z2));
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
Log(Logs::Detail, Logs::None,
|
|
" HAZARD NOT DETECTED, water is shallow at %8.3f units deep",
|
|
std::abs(NewZ - best_z2));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Log(Logs::Detail, Logs::None, "Hazard point not in water or lava!");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Log(Logs::Detail, Logs::None, "No water map loaded for hazards!");
|
|
}
|
|
|
|
curx += stepx;
|
|
cury += stepy;
|
|
curz += stepz;
|
|
|
|
cur.x = curx;
|
|
cur.y = cury;
|
|
cur.z = curz;
|
|
|
|
if (std::abs(curx - To.x) < step_size)
|
|
cur.x = To.x;
|
|
if (std::abs(cury - To.y) < step_size)
|
|
cur.y = To.y;
|
|
if (std::abs(curz - To.z) < step_size)
|
|
cur.z = To.z;
|
|
|
|
} while (cur.x != To.x || cur.y != To.y || cur.z != To.z);
|
|
return true;
|
|
}
|
|
|
|
void Mob::PrintRoute()
|
|
{
|
|
|
|
//printf("Route is : ");
|
|
//
|
|
//for(auto Iterator = Route.begin(); Iterator !=Route.end(); ++Iterator)
|
|
//{
|
|
// printf("%i, ", (*Iterator));
|
|
//}
|
|
//
|
|
//printf("\n");
|
|
|
|
}
|
|
|
|
void PathManager::OpenDoors(int Node1, int Node2, Mob *ForWho)
|
|
{
|
|
if(!ForWho || (Node1 >= Head.PathNodeCount) || (Node2 >= Head.PathNodeCount) || (Node1 < 0) || (Node2 < 0))
|
|
return;
|
|
|
|
for(int i = 0; i < PATHNODENEIGHBOURS; ++i)
|
|
{
|
|
if(PathNodes[Node1].Neighbours[i].id == -1)
|
|
return;
|
|
|
|
if(PathNodes[Node1].Neighbours[i].id != Node2)
|
|
continue;
|
|
|
|
if(PathNodes[Node1].Neighbours[i].DoorID >= 0)
|
|
{
|
|
Doors *d = entity_list.FindDoor(PathNodes[Node1].Neighbours[i].DoorID);
|
|
|
|
if(d && !d->IsDoorOpen() )
|
|
{
|
|
Log(Logs::Detail, Logs::None, "Opening door %i for %s", PathNodes[Node1].Neighbours[i].DoorID, ForWho->GetName());
|
|
|
|
d->ForceOpen(ForWho);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
//this assumes that the first point in the list is the player's
|
|
//current position, I dont know how well it works if its not.
|
|
void Client::SendPathPacket(std::vector<FindPerson_Point> &points) {
|
|
if(points.size() < 2) {
|
|
//empty length packet == not found.
|
|
EQApplicationPacket outapp(OP_FindPersonReply, 0);
|
|
QueuePacket(&outapp);
|
|
return;
|
|
}
|
|
|
|
int len = sizeof(FindPersonResult_Struct) + (points.size()+1) * sizeof(FindPerson_Point);
|
|
auto outapp = new EQApplicationPacket(OP_FindPersonReply, len);
|
|
FindPersonResult_Struct* fpr=(FindPersonResult_Struct*)outapp->pBuffer;
|
|
|
|
std::vector<FindPerson_Point>::iterator cur, end;
|
|
cur = points.begin();
|
|
end = points.end();
|
|
unsigned int r;
|
|
for(r = 0; cur != end; ++cur, r++) {
|
|
fpr->path[r] = *cur;
|
|
|
|
}
|
|
//put the last element into the destination field
|
|
--cur;
|
|
fpr->path[r] = *cur;
|
|
fpr->dest = *cur;
|
|
|
|
FastQueuePacket(&outapp);
|
|
|
|
|
|
}
|
|
|
|
PathNode* PathManager::FindPathNodeByCoordinates(float x, float y, float z)
|
|
{
|
|
for(uint32 i = 0; i < Head.PathNodeCount; ++i)
|
|
if((PathNodes[i].v.x == x) && (PathNodes[i].v.y == y) && (PathNodes[i].v.z == z))
|
|
return &PathNodes[i];
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
int PathManager::GetRandomPathNode()
|
|
{
|
|
return zone->random.Int(0, Head.PathNodeCount - 1);
|
|
|
|
}
|
|
|
|
void PathManager::ShowPathNodeNeighbours(Client *c)
|
|
{
|
|
if(!c || !c->GetTarget())
|
|
return;
|
|
|
|
|
|
PathNode *Node = FindPathNodeByCoordinates(c->GetTarget()->GetX(), c->GetTarget()->GetY(), c->GetTarget()->GetZ());
|
|
|
|
if(!Node)
|
|
{
|
|
c->Message(0, "Unable to find path node.");
|
|
return;
|
|
}
|
|
c->Message(0, "Path node %4i", Node->id);
|
|
|
|
for(uint32 i = 0; i < Head.PathNodeCount; ++i)
|
|
{
|
|
char Name[64];
|
|
|
|
if(PathNodes[i].id < 10)
|
|
sprintf(Name, "%s000", DigitToWord(PathNodes[i].id));
|
|
else if(PathNodes[i].id < 100)
|
|
sprintf(Name, "%s_%s000", DigitToWord(PathNodes[i].id / 10), DigitToWord(PathNodes[i].id % 10));
|
|
else
|
|
sprintf(Name, "%s_%s_%s000", DigitToWord(PathNodes[i].id/100), DigitToWord((PathNodes[i].id % 100)/10),
|
|
DigitToWord(((PathNodes[i].id % 100) %10)));
|
|
|
|
Mob *m = entity_list.GetMob(Name);
|
|
|
|
if(m)
|
|
m->SendIllusionPacket(151);
|
|
}
|
|
|
|
std::stringstream Neighbours;
|
|
|
|
for(int i = 0; i < PATHNODENEIGHBOURS; ++i)
|
|
{
|
|
if(Node->Neighbours[i].id == -1)
|
|
break;
|
|
Neighbours << Node->Neighbours[i].id << ", ";
|
|
|
|
char Name[64];
|
|
|
|
if(Node->Neighbours[i].id < 10)
|
|
sprintf(Name, "%s000", DigitToWord(Node->Neighbours[i].id));
|
|
else if(Node->Neighbours[i].id < 100)
|
|
sprintf(Name, "%s_%s000", DigitToWord(Node->Neighbours[i].id / 10), DigitToWord(Node->Neighbours[i].id % 10));
|
|
else
|
|
sprintf(Name, "%s_%s_%s000", DigitToWord(Node->Neighbours[i].id/100), DigitToWord((Node->Neighbours[i].id % 100)/10),
|
|
DigitToWord(((Node->Neighbours[i].id % 100) %10)));
|
|
|
|
Mob *m = entity_list.GetMob(Name);
|
|
|
|
if(m)
|
|
m->SendIllusionPacket(46);
|
|
}
|
|
c->Message(0, "Neighbours: %s", Neighbours.str().c_str());
|
|
}
|
|
|
|
void PathManager::NodeInfo(Client *c)
|
|
{
|
|
if(!c)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(!c->GetTarget())
|
|
{
|
|
c->Message(0, "You must target a node.");
|
|
return;
|
|
}
|
|
|
|
PathNode *Node = FindPathNodeByCoordinates(c->GetTarget()->GetX(), c->GetTarget()->GetY(), c->GetTarget()->GetZ());
|
|
if(!Node)
|
|
{
|
|
return;
|
|
}
|
|
|
|
c->Message(0, "Pathing node: %i at (%.2f, %.2f, %.2f) with bestz %.2f",
|
|
Node->id, Node->v.x, Node->v.y, Node->v.z, Node->bestz);
|
|
|
|
bool neighbour = false;
|
|
for(int x = 0; x < 50; ++x)
|
|
{
|
|
if(Node->Neighbours[x].id != -1)
|
|
{
|
|
if(!neighbour)
|
|
{
|
|
c->Message(0, "Neighbours found:");
|
|
neighbour = true;
|
|
}
|
|
c->Message(0, "id: %i, distance: %.2f, door id: %i, is teleport: %i",
|
|
Node->Neighbours[x].id, Node->Neighbours[x].distance,
|
|
Node->Neighbours[x].DoorID, Node->Neighbours[x].Teleport);
|
|
}
|
|
}
|
|
|
|
if(!neighbour)
|
|
{
|
|
c->Message(0, "No neighbours found!");
|
|
}
|
|
return;
|
|
}
|
|
|
|
void PathManager::DumpPath(std::string filename)
|
|
{
|
|
std::ofstream o_file;
|
|
|
|
std::string file_to_write = StringFormat("%s%s", Config->MapDir.c_str(), filename.c_str());
|
|
|
|
o_file.open(file_to_write.c_str(), std::ios_base::binary | std::ios_base::trunc | std::ios_base::out);
|
|
o_file.write("EQEMUPATH", 9);
|
|
o_file.write((const char*)&Head, sizeof(Head));
|
|
o_file.write((const char*)PathNodes, (sizeof(PathNode)*Head.PathNodeCount));
|
|
o_file.close();
|
|
}
|
|
|
|
int32 PathManager::AddNode(float x, float y, float z, float best_z, int32 requested_id)
|
|
{
|
|
int32 new_id = -1;
|
|
if(requested_id != 0)
|
|
{
|
|
new_id = requested_id;
|
|
for(uint32 i = 0; i < Head.PathNodeCount; ++i)
|
|
{
|
|
if(PathNodes[i].id == requested_id)
|
|
{
|
|
new_id = -1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(new_id == -1)
|
|
{
|
|
for(uint32 i = 0; i < Head.PathNodeCount; ++i)
|
|
{
|
|
if(PathNodes[i].id - new_id > 1) {
|
|
new_id = PathNodes[i].id - 1;
|
|
break;
|
|
}
|
|
|
|
if(PathNodes[i].id > new_id)
|
|
new_id = PathNodes[i].id;
|
|
}
|
|
new_id++;
|
|
}
|
|
|
|
PathNode new_node;
|
|
new_node.v.x = x;
|
|
new_node.v.y = y;
|
|
new_node.v.z = z;
|
|
new_node.bestz = best_z;
|
|
new_node.id = (uint16)new_id;
|
|
for(int x = 0; x < PATHNODENEIGHBOURS; ++x)
|
|
{
|
|
new_node.Neighbours[x].id = -1;
|
|
new_node.Neighbours[x].distance = 0.0;
|
|
new_node.Neighbours[x].DoorID = -1;
|
|
new_node.Neighbours[x].Teleport = 0;
|
|
}
|
|
|
|
Head.PathNodeCount++;
|
|
if(Head.PathNodeCount > 1)
|
|
{
|
|
auto t_PathNodes = new PathNode[Head.PathNodeCount];
|
|
for(uint32 x = 0; x < (Head.PathNodeCount - 1); ++x)
|
|
{
|
|
t_PathNodes[x].v.x = PathNodes[x].v.x;
|
|
t_PathNodes[x].v.y = PathNodes[x].v.y;
|
|
t_PathNodes[x].v.z = PathNodes[x].v.z;
|
|
t_PathNodes[x].bestz = PathNodes[x].bestz;
|
|
t_PathNodes[x].id = PathNodes[x].id;
|
|
for(int n = 0; n < PATHNODENEIGHBOURS; ++n)
|
|
{
|
|
t_PathNodes[x].Neighbours[n].distance = PathNodes[x].Neighbours[n].distance;
|
|
t_PathNodes[x].Neighbours[n].DoorID = PathNodes[x].Neighbours[n].DoorID;
|
|
t_PathNodes[x].Neighbours[n].id = PathNodes[x].Neighbours[n].id;
|
|
t_PathNodes[x].Neighbours[n].Teleport = PathNodes[x].Neighbours[n].Teleport;
|
|
}
|
|
|
|
}
|
|
|
|
int32 index = (Head.PathNodeCount - 1);
|
|
t_PathNodes[index].v.x = new_node.v.x;
|
|
t_PathNodes[index].v.y = new_node.v.y;
|
|
t_PathNodes[index].v.z = new_node.v.z;
|
|
t_PathNodes[index].bestz = new_node.bestz;
|
|
t_PathNodes[index].id = new_node.id;
|
|
for(int n = 0; n < PATHNODENEIGHBOURS; ++n)
|
|
{
|
|
t_PathNodes[index].Neighbours[n].distance = new_node.Neighbours[n].distance;
|
|
t_PathNodes[index].Neighbours[n].DoorID = new_node.Neighbours[n].DoorID;
|
|
t_PathNodes[index].Neighbours[n].id = new_node.Neighbours[n].id;
|
|
t_PathNodes[index].Neighbours[n].Teleport = new_node.Neighbours[n].Teleport;
|
|
}
|
|
|
|
delete[] PathNodes;
|
|
PathNodes = t_PathNodes;
|
|
|
|
auto npc_type = new NPCType;
|
|
memset(npc_type, 0, sizeof(NPCType));
|
|
if(new_id < 10)
|
|
sprintf(npc_type->name, "%s", DigitToWord(new_id));
|
|
else if(new_id < 100)
|
|
sprintf(npc_type->name, "%s_%s", DigitToWord(new_id/10), DigitToWord(new_id % 10));
|
|
else
|
|
sprintf(npc_type->name, "%s_%s_%s", DigitToWord(new_id/100), DigitToWord((new_id % 100)/10),
|
|
DigitToWord(((new_id % 100) %10)));
|
|
|
|
sprintf(npc_type->lastname, "%i", new_id);
|
|
npc_type->cur_hp = 4000000;
|
|
npc_type->max_hp = 4000000;
|
|
npc_type->race = 151;
|
|
npc_type->gender = 2;
|
|
npc_type->class_ = 9;
|
|
npc_type->deity= 1;
|
|
npc_type->level = 75;
|
|
npc_type->npc_id = 0;
|
|
npc_type->loottable_id = 0;
|
|
npc_type->texture = 1;
|
|
npc_type->light = 0;
|
|
npc_type->runspeed = 0;
|
|
npc_type->d_melee_texture1 = 1;
|
|
npc_type->d_melee_texture2 = 1;
|
|
npc_type->merchanttype = 1;
|
|
npc_type->bodytype = 1;
|
|
npc_type->STR = 150;
|
|
npc_type->STA = 150;
|
|
npc_type->DEX = 150;
|
|
npc_type->AGI = 150;
|
|
npc_type->INT = 150;
|
|
npc_type->WIS = 150;
|
|
npc_type->CHA = 150;
|
|
npc_type->findable = 1;
|
|
|
|
auto position = glm::vec4(new_node.v.x, new_node.v.y, new_node.v.z, 0.0f);
|
|
auto npc = new NPC(npc_type, nullptr, position, FlyMode1);
|
|
npc->GiveNPCTypeData(npc_type);
|
|
entity_list.AddNPC(npc, true, true);
|
|
|
|
safe_delete_array(ClosedListFlag);
|
|
ClosedListFlag = new int[Head.PathNodeCount];
|
|
return new_id;
|
|
}
|
|
else
|
|
{
|
|
PathNodes = new PathNode[Head.PathNodeCount];
|
|
PathNodes[0].v.x = new_node.v.x;
|
|
PathNodes[0].v.y = new_node.v.y;
|
|
PathNodes[0].v.z = new_node.v.z;
|
|
PathNodes[0].bestz = new_node.bestz;
|
|
PathNodes[0].id = new_node.id;
|
|
for(int n = 0; n < PATHNODENEIGHBOURS; ++n)
|
|
{
|
|
PathNodes[0].Neighbours[n].distance = new_node.Neighbours[n].distance;
|
|
PathNodes[0].Neighbours[n].DoorID = new_node.Neighbours[n].DoorID;
|
|
PathNodes[0].Neighbours[n].id = new_node.Neighbours[n].id;
|
|
PathNodes[0].Neighbours[n].Teleport = new_node.Neighbours[n].Teleport;
|
|
}
|
|
|
|
auto npc_type = new NPCType;
|
|
memset(npc_type, 0, sizeof(NPCType));
|
|
if(new_id < 10)
|
|
sprintf(npc_type->name, "%s", DigitToWord(new_id));
|
|
else if(new_id < 100)
|
|
sprintf(npc_type->name, "%s_%s", DigitToWord(new_id/10), DigitToWord(new_id % 10));
|
|
else
|
|
sprintf(npc_type->name, "%s_%s_%s", DigitToWord(new_id/100), DigitToWord((new_id % 100)/10),
|
|
DigitToWord(((new_id % 100) %10)));
|
|
|
|
sprintf(npc_type->lastname, "%i", new_id);
|
|
npc_type->cur_hp = 4000000;
|
|
npc_type->max_hp = 4000000;
|
|
npc_type->race = 151;
|
|
npc_type->gender = 2;
|
|
npc_type->class_ = 9;
|
|
npc_type->deity= 1;
|
|
npc_type->level = 75;
|
|
npc_type->npc_id = 0;
|
|
npc_type->loottable_id = 0;
|
|
npc_type->texture = 1;
|
|
npc_type->light = 0;
|
|
npc_type->runspeed = 0;
|
|
npc_type->d_melee_texture1 = 1;
|
|
npc_type->d_melee_texture2 = 1;
|
|
npc_type->merchanttype = 1;
|
|
npc_type->bodytype = 1;
|
|
npc_type->STR = 150;
|
|
npc_type->STA = 150;
|
|
npc_type->DEX = 150;
|
|
npc_type->AGI = 150;
|
|
npc_type->INT = 150;
|
|
npc_type->WIS = 150;
|
|
npc_type->CHA = 150;
|
|
npc_type->findable = 1;
|
|
|
|
auto position = glm::vec4(new_node.v.x, new_node.v.y, new_node.v.z, 0.0f);
|
|
auto npc = new NPC(npc_type, nullptr, position, FlyMode1);
|
|
npc->GiveNPCTypeData(npc_type);
|
|
entity_list.AddNPC(npc, true, true);
|
|
|
|
ClosedListFlag = new int[Head.PathNodeCount];
|
|
|
|
return new_id;
|
|
}
|
|
}
|
|
|
|
bool PathManager::DeleteNode(Client *c)
|
|
{
|
|
if(!c)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if(!c->GetTarget())
|
|
{
|
|
c->Message(0, "You must target a node.");
|
|
return false;
|
|
}
|
|
|
|
PathNode *Node = FindPathNodeByCoordinates(c->GetTarget()->GetX(), c->GetTarget()->GetY(), c->GetTarget()->GetZ());
|
|
if(!Node)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return DeleteNode(Node->id);
|
|
}
|
|
|
|
bool PathManager::DeleteNode(int32 id)
|
|
{
|
|
//if the current list is > 1 in size create a new list of size current size - 1
|
|
//transfer all but the current node to this new list and delete our current list
|
|
//set this new list to be our current list
|
|
//else if the size is 1 just delete our current list and set it to zero.
|
|
//go through and delete all ref in neighbors...
|
|
|
|
if(Head.PathNodeCount > 1)
|
|
{
|
|
auto t_PathNodes = new PathNode[Head.PathNodeCount - 1];
|
|
uint32 index = 0;
|
|
for(uint32 x = 0; x < Head.PathNodeCount; x++)
|
|
{
|
|
if(PathNodes[x].id != id)
|
|
{
|
|
t_PathNodes[index].id = PathNodes[x].id;
|
|
t_PathNodes[index].v.x = PathNodes[x].v.x;
|
|
t_PathNodes[index].v.y = PathNodes[x].v.y;
|
|
t_PathNodes[index].v.z = PathNodes[x].v.z;
|
|
t_PathNodes[index].bestz = PathNodes[x].bestz;
|
|
for(int n = 0; n < PATHNODENEIGHBOURS; ++n)
|
|
{
|
|
t_PathNodes[index].Neighbours[n].distance = PathNodes[x].Neighbours[n].distance;
|
|
t_PathNodes[index].Neighbours[n].DoorID = PathNodes[x].Neighbours[n].DoorID;
|
|
t_PathNodes[index].Neighbours[n].id = PathNodes[x].Neighbours[n].id;
|
|
t_PathNodes[index].Neighbours[n].Teleport = PathNodes[x].Neighbours[n].Teleport;
|
|
}
|
|
index++;
|
|
}
|
|
}
|
|
Head.PathNodeCount--;
|
|
delete[] PathNodes;
|
|
PathNodes = t_PathNodes;
|
|
|
|
for(uint32 y = 0; y < Head.PathNodeCount; ++y)
|
|
{
|
|
for(int n = 0; n < PATHNODENEIGHBOURS; ++n)
|
|
{
|
|
if(PathNodes[y].Neighbours[n].id == id)
|
|
{
|
|
PathNodes[y].Neighbours[n].Teleport = 0;
|
|
PathNodes[y].Neighbours[n].DoorID = -1;
|
|
PathNodes[y].Neighbours[n].distance = 0.0;
|
|
PathNodes[y].Neighbours[n].id = -1;
|
|
}
|
|
}
|
|
}
|
|
safe_delete_array(ClosedListFlag);
|
|
ClosedListFlag = new int[Head.PathNodeCount];
|
|
}
|
|
else
|
|
{
|
|
delete[] PathNodes;
|
|
PathNodes = nullptr;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void PathManager::ConnectNodeToNode(Client *c, int32 Node2, int32 teleport, int32 doorid)
|
|
{
|
|
if(!c)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(!c->GetTarget())
|
|
{
|
|
c->Message(0, "You must target a node.");
|
|
return;
|
|
}
|
|
|
|
PathNode *Node = FindPathNodeByCoordinates(c->GetTarget()->GetX(), c->GetTarget()->GetY(), c->GetTarget()->GetZ());
|
|
if(!Node)
|
|
{
|
|
return;
|
|
}
|
|
|
|
c->Message(0, "Connecting %i to %i", Node->id, Node2);
|
|
|
|
if(doorid == 0)
|
|
ConnectNodeToNode(Node->id, Node2, teleport);
|
|
else
|
|
ConnectNodeToNode(Node->id, Node2, teleport, doorid);
|
|
}
|
|
|
|
void PathManager::ConnectNodeToNode(int32 Node1, int32 Node2, int32 teleport, int32 doorid)
|
|
{
|
|
PathNode *a = nullptr;
|
|
PathNode *b = nullptr;
|
|
for(uint32 x = 0; x < Head.PathNodeCount; ++x)
|
|
{
|
|
if(PathNodes[x].id == Node1)
|
|
{
|
|
a = &PathNodes[x];
|
|
if(b)
|
|
break;
|
|
}
|
|
else if(PathNodes[x].id == Node2)
|
|
{
|
|
b = &PathNodes[x];
|
|
if(a)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(a == nullptr || b == nullptr)
|
|
return;
|
|
|
|
bool connect_a_to_b = true;
|
|
if(NodesConnected(a, b))
|
|
connect_a_to_b = false;
|
|
|
|
bool connect_b_to_a = true;
|
|
if(NodesConnected(b, a))
|
|
connect_b_to_a = false;
|
|
|
|
|
|
if(connect_a_to_b)
|
|
{
|
|
for(int a_i = 0; a_i < PATHNODENEIGHBOURS; ++a_i)
|
|
{
|
|
if(a->Neighbours[a_i].id == -1)
|
|
{
|
|
a->Neighbours[a_i].id = b->id;
|
|
a->Neighbours[a_i].DoorID = doorid;
|
|
a->Neighbours[a_i].Teleport = teleport;
|
|
a->Neighbours[a_i].distance = VectorDistance(a->v, b->v);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(connect_b_to_a)
|
|
{
|
|
for(int b_i = 0; b_i < PATHNODENEIGHBOURS; ++b_i)
|
|
{
|
|
if(b->Neighbours[b_i].id == -1)
|
|
{
|
|
b->Neighbours[b_i].id = a->id;
|
|
b->Neighbours[b_i].DoorID = doorid;
|
|
b->Neighbours[b_i].Teleport = teleport;
|
|
b->Neighbours[b_i].distance = VectorDistance(a->v, b->v);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void PathManager::ConnectNode(Client *c, int32 Node2, int32 teleport, int32 doorid)
|
|
{
|
|
if(!c)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(!c->GetTarget())
|
|
{
|
|
c->Message(0, "You must target a node.");
|
|
return;
|
|
}
|
|
|
|
PathNode *Node = FindPathNodeByCoordinates(c->GetTarget()->GetX(), c->GetTarget()->GetY(), c->GetTarget()->GetZ());
|
|
if(!Node)
|
|
{
|
|
return;
|
|
}
|
|
|
|
c->Message(0, "Connecting %i to %i", Node->id, Node2);
|
|
|
|
if(doorid == 0)
|
|
ConnectNode(Node->id, Node2, teleport);
|
|
else
|
|
ConnectNode(Node->id, Node2, teleport, doorid);
|
|
}
|
|
|
|
void PathManager::ConnectNode(int32 Node1, int32 Node2, int32 teleport, int32 doorid)
|
|
{
|
|
PathNode *a = nullptr;
|
|
PathNode *b = nullptr;
|
|
for(uint32 x = 0; x < Head.PathNodeCount; ++x)
|
|
{
|
|
if(PathNodes[x].id == Node1)
|
|
{
|
|
a = &PathNodes[x];
|
|
if(b)
|
|
break;
|
|
}
|
|
else if(PathNodes[x].id == Node2)
|
|
{
|
|
b = &PathNodes[x];
|
|
if(a)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(a == nullptr || b == nullptr)
|
|
return;
|
|
|
|
bool connect_a_to_b = true;
|
|
if(NodesConnected(a, b))
|
|
connect_a_to_b = false;
|
|
|
|
if(connect_a_to_b)
|
|
{
|
|
for(int a_i = 0; a_i < PATHNODENEIGHBOURS; ++a_i)
|
|
{
|
|
if(a->Neighbours[a_i].id == -1)
|
|
{
|
|
a->Neighbours[a_i].id = b->id;
|
|
a->Neighbours[a_i].DoorID = doorid;
|
|
a->Neighbours[a_i].Teleport = teleport;
|
|
a->Neighbours[a_i].distance = VectorDistance(a->v, b->v);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void PathManager::DisconnectNodeToNode(Client *c, int32 Node2)
|
|
{
|
|
if(!c)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(!c->GetTarget())
|
|
{
|
|
c->Message(0, "You must target a node.");
|
|
return;
|
|
}
|
|
|
|
PathNode *Node = FindPathNodeByCoordinates(c->GetTarget()->GetX(), c->GetTarget()->GetY(), c->GetTarget()->GetZ());
|
|
if(!Node)
|
|
{
|
|
return;
|
|
}
|
|
|
|
DisconnectNodeToNode(Node->id, Node2);
|
|
}
|
|
|
|
void PathManager::DisconnectNodeToNode(int32 Node1, int32 Node2)
|
|
{
|
|
PathNode *a = nullptr;
|
|
PathNode *b = nullptr;
|
|
for(uint32 x = 0; x < Head.PathNodeCount; ++x)
|
|
{
|
|
if(PathNodes[x].id == Node1)
|
|
{
|
|
a = &PathNodes[x];
|
|
if(b)
|
|
break;
|
|
}
|
|
else if(PathNodes[x].id == Node2)
|
|
{
|
|
b = &PathNodes[x];
|
|
if(a)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(a == nullptr || b == nullptr)
|
|
return;
|
|
|
|
bool disconnect_a_from_b = false;
|
|
if(NodesConnected(a, b))
|
|
disconnect_a_from_b = true;
|
|
|
|
bool disconnect_b_from_a = false;
|
|
if(NodesConnected(b, a))
|
|
disconnect_b_from_a = true;
|
|
|
|
if(disconnect_a_from_b)
|
|
{
|
|
for(int a_i = 0; a_i < PATHNODENEIGHBOURS; ++a_i)
|
|
{
|
|
if(a->Neighbours[a_i].id == b->id)
|
|
{
|
|
a->Neighbours[a_i].distance = 0.0;
|
|
a->Neighbours[a_i].DoorID = -1;
|
|
a->Neighbours[a_i].id = -1;
|
|
a->Neighbours[a_i].Teleport = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(disconnect_b_from_a)
|
|
{
|
|
for(int b_i = 0; b_i < PATHNODENEIGHBOURS; ++b_i)
|
|
{
|
|
if(b->Neighbours[b_i].id == a->id)
|
|
{
|
|
b->Neighbours[b_i].distance = 0.0;
|
|
b->Neighbours[b_i].DoorID = -1;
|
|
b->Neighbours[b_i].id = -1;
|
|
b->Neighbours[b_i].Teleport = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void PathManager::MoveNode(Client *c)
|
|
{
|
|
if(!c)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(!c->GetTarget())
|
|
{
|
|
c->Message(0, "You must target a node.");
|
|
return;
|
|
}
|
|
|
|
PathNode *Node = FindPathNodeByCoordinates(c->GetTarget()->GetX(), c->GetTarget()->GetY(), c->GetTarget()->GetZ());
|
|
if(!Node)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Node->v.x = c->GetX();
|
|
Node->v.y = c->GetY();
|
|
Node->v.z = c->GetZ();
|
|
|
|
if(zone->zonemap)
|
|
{
|
|
glm::vec3 loc(c->GetX(), c->GetY(), c->GetZ());
|
|
Node->bestz = zone->zonemap->FindBestZ(loc, nullptr);
|
|
}
|
|
else
|
|
{
|
|
Node->bestz = Node->v.z;
|
|
}
|
|
}
|
|
|
|
void PathManager::DisconnectAll(Client *c)
|
|
{
|
|
if(!c)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(!c->GetTarget())
|
|
{
|
|
c->Message(0, "You must target a node.");
|
|
return;
|
|
}
|
|
|
|
PathNode *Node = FindPathNodeByCoordinates(c->GetTarget()->GetX(), c->GetTarget()->GetY(), c->GetTarget()->GetZ());
|
|
if(!Node)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for(int x = 0; x < PATHNODENEIGHBOURS; ++x)
|
|
{
|
|
Node->Neighbours[x].distance = 0;
|
|
Node->Neighbours[x].Teleport = 0;
|
|
Node->Neighbours[x].DoorID = -1;
|
|
Node->Neighbours[x].id = -1;
|
|
}
|
|
|
|
for(uint32 i = 0; i < Head.PathNodeCount; ++i)
|
|
{
|
|
if(PathNodes[i].id == Node->id)
|
|
continue;
|
|
|
|
for(int ix = 0; ix < PATHNODENEIGHBOURS; ++ix)
|
|
{
|
|
if(PathNodes[i].Neighbours[ix].id == Node->id)
|
|
{
|
|
PathNodes[i].Neighbours[ix].distance = 0;
|
|
PathNodes[i].Neighbours[ix].Teleport = 0;
|
|
PathNodes[i].Neighbours[ix].id = -1;
|
|
PathNodes[i].Neighbours[ix].DoorID = -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//checks if anything in a points to b
|
|
bool PathManager::NodesConnected(PathNode *a, PathNode *b)
|
|
{
|
|
if(!a)
|
|
return false;
|
|
|
|
if(!b)
|
|
return false;
|
|
|
|
for(int x = 0; x < PATHNODENEIGHBOURS; ++x)
|
|
{
|
|
if(a->Neighbours[x].id == b->id)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool PathManager::CheckLosFN(glm::vec3 a, glm::vec3 b)
|
|
{
|
|
if(zone->zonemap)
|
|
{
|
|
glm::vec3 hit;
|
|
|
|
glm::vec3 myloc;
|
|
glm::vec3 oloc;
|
|
|
|
myloc.x = a.x;
|
|
myloc.y = a.y;
|
|
myloc.z = a.z;
|
|
|
|
oloc.x = b.x;
|
|
oloc.y = b.y;
|
|
oloc.z = b.z;
|
|
|
|
|
|
if(zone->zonemap->LineIntersectsZone(myloc, oloc, 1.0f, nullptr))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void PathManager::ProcessNodesAndSave(std::string filename)
|
|
{
|
|
if(zone->zonemap)
|
|
{
|
|
for(uint32 i = 0; i < Head.PathNodeCount; ++i)
|
|
{
|
|
for(int in = 0; in < PATHNODENEIGHBOURS; ++in)
|
|
{
|
|
PathNodes[i].Neighbours[in].distance = 0.0;
|
|
PathNodes[i].Neighbours[in].DoorID = -1;
|
|
PathNodes[i].Neighbours[in].id = -1;
|
|
PathNodes[i].Neighbours[in].Teleport = 0;
|
|
}
|
|
}
|
|
|
|
for(uint32 x = 0; x < Head.PathNodeCount; ++x)
|
|
{
|
|
for(uint32 y = 0; y < Head.PathNodeCount; ++y)
|
|
{
|
|
if(y == x) //can't connect to ourselves.
|
|
continue;
|
|
|
|
if(!NodesConnected(&PathNodes[x], &PathNodes[y]))
|
|
{
|
|
if(VectorDistance(PathNodes[x].v, PathNodes[y].v) <= 200)
|
|
{
|
|
if(CheckLosFN(PathNodes[x].v, PathNodes[y].v))
|
|
{
|
|
if(NoHazardsAccurate(PathNodes[x].v, PathNodes[y].v))
|
|
{
|
|
ConnectNodeToNode(PathNodes[x].id, PathNodes[y].id, 0, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
DumpPath(filename);
|
|
}
|
|
|
|
void PathManager::ResortConnections()
|
|
{
|
|
NeighbourNode Neigh[PATHNODENEIGHBOURS];
|
|
for(uint32 x = 0; x < Head.PathNodeCount; ++x)
|
|
{
|
|
int index = 0;
|
|
for(int y = 0; y < PATHNODENEIGHBOURS; ++y)
|
|
{
|
|
Neigh[y].distance = 0;
|
|
Neigh[y].DoorID = -1;
|
|
Neigh[y].id = -1;
|
|
Neigh[y].Teleport = 0;
|
|
}
|
|
|
|
for(int z = 0; z < PATHNODENEIGHBOURS; ++z)
|
|
{
|
|
if(PathNodes[x].Neighbours[z].id != -1)
|
|
{
|
|
Neigh[index].id = PathNodes[x].Neighbours[z].id;
|
|
Neigh[index].distance = PathNodes[x].Neighbours[z].distance;
|
|
Neigh[index].DoorID = PathNodes[x].Neighbours[z].DoorID;
|
|
Neigh[index].Teleport = PathNodes[x].Neighbours[z].Teleport;
|
|
index++;
|
|
}
|
|
}
|
|
|
|
for(int i = 0; i < PATHNODENEIGHBOURS; ++i)
|
|
{
|
|
PathNodes[x].Neighbours[i].distance = 0;
|
|
PathNodes[x].Neighbours[i].DoorID = -1;
|
|
PathNodes[x].Neighbours[i].id = -1;
|
|
PathNodes[x].Neighbours[i].Teleport = 0;
|
|
}
|
|
|
|
for(int z = 0; z < PATHNODENEIGHBOURS; ++z)
|
|
{
|
|
PathNodes[x].Neighbours[z].distance = Neigh[z].distance;
|
|
PathNodes[x].Neighbours[z].DoorID = Neigh[z].DoorID;
|
|
PathNodes[x].Neighbours[z].id = Neigh[z].id;
|
|
PathNodes[x].Neighbours[z].Teleport = Neigh[z].Teleport;
|
|
}
|
|
}
|
|
}
|
|
|
|
void PathManager::QuickConnect(Client *c, bool set)
|
|
{
|
|
if(!c)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(!c->GetTarget())
|
|
{
|
|
c->Message(0, "You must target a node.");
|
|
return;
|
|
}
|
|
|
|
PathNode *Node = FindPathNodeByCoordinates(c->GetTarget()->GetX(), c->GetTarget()->GetY(), c->GetTarget()->GetZ());
|
|
if(!Node)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(set)
|
|
{
|
|
c->Message(0, "Setting %i to the quick connect target", Node->id);
|
|
QuickConnectTarget = Node->id;
|
|
}
|
|
else
|
|
{
|
|
if(QuickConnectTarget >= 0)
|
|
{
|
|
ConnectNodeToNode(QuickConnectTarget, Node->id);
|
|
}
|
|
}
|
|
}
|
|
|
|
struct InternalPathSort
|
|
{
|
|
int16 old_id;
|
|
int16 new_id;
|
|
};
|
|
|
|
void PathManager::SortNodes()
|
|
{
|
|
std::vector<InternalPathSort> sorted_vals;
|
|
for(uint32 x = 0; x < Head.PathNodeCount; ++x)
|
|
{
|
|
InternalPathSort tmp;
|
|
tmp.old_id = PathNodes[x].id;
|
|
sorted_vals.push_back(tmp);
|
|
}
|
|
|
|
auto t_PathNodes = new PathNode[Head.PathNodeCount];
|
|
memcpy(t_PathNodes, PathNodes, sizeof(PathNode)*Head.PathNodeCount);
|
|
for(uint32 i = 0; i < Head.PathNodeCount; ++i)
|
|
{
|
|
for(size_t j = 0; j < sorted_vals.size(); ++j)
|
|
{
|
|
if(sorted_vals[j].old_id == PathNodes[i].id)
|
|
{
|
|
if(i != PathNodes[i].id)
|
|
{
|
|
printf("Assigning new id of index %i differs from old id %i\n", i, PathNodes[i].id);
|
|
}
|
|
sorted_vals[j].new_id = i;
|
|
}
|
|
}
|
|
t_PathNodes[i].id = i;
|
|
}
|
|
|
|
for(uint32 y = 0; y < Head.PathNodeCount; ++y)
|
|
{
|
|
for(int z = 0; z < PATHNODENEIGHBOURS; ++z)
|
|
{
|
|
if(PathNodes[y].Neighbours[z].id != -1)
|
|
{
|
|
int new_val = -1;
|
|
for(size_t c = 0; c < sorted_vals.size(); ++c)
|
|
{
|
|
if(PathNodes[y].Neighbours[z].id == sorted_vals[c].old_id)
|
|
{
|
|
new_val = sorted_vals[c].new_id;
|
|
break;
|
|
}
|
|
}
|
|
if(new_val != -1)
|
|
{
|
|
if(t_PathNodes[y].Neighbours[z].id != new_val)
|
|
{
|
|
printf("changing neighbor value to %i from %i\n", new_val, t_PathNodes[y].Neighbours[z].id);
|
|
}
|
|
t_PathNodes[y].Neighbours[z].id = new_val;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
safe_delete_array(PathNodes);
|
|
PathNodes = t_PathNodes;
|
|
}
|
|
|