yapb-noob-edition/source/waypoint.cpp

2928 lines
96 KiB
C++

//
// Yet Another POD-Bot, based on PODBot by Markus Klinge ("CountFloyd").
// Copyright (c) YaPB Development Team.
//
// This software is licensed under the BSD-style license.
// Additional exceptions apply. For full license details, see LICENSE.txt or visit:
// https://yapb.ru/license
//
#include <yapb.h>
ConVar yb_wptsubfolder ("yb_wptsubfolder", "");
ConVar yb_waypoint_autodl_host ("yb_waypoint_autodl_host", "yapb.ru");
ConVar yb_waypoint_autodl_enable ("yb_waypoint_autodl_enable", "1");
void Waypoint::init (void) {
// this function initialize the waypoint structures..
m_loadTries = 0;
m_learnVelocity.nullify ();
m_learnPosition.nullify ();
m_lastWaypoint.nullify ();
m_pathDisplayTime = 0.0f;
m_arrowDisplayTime = 0.0f;
// have any waypoint path nodes been allocated yet?
if (m_waypointPaths) {
cleanupPathMemory ();
}
m_numWaypoints = 0;
}
void Waypoint::cleanupPathMemory (void) {
for (int i = 0; i < m_numWaypoints && m_paths[i] != nullptr; i++) {
delete m_paths[i];
m_paths[i] = nullptr;
}
}
int Waypoint::removeUselessConnections (int index, bool outputToConsole) {
// this function removes the useless paths connections from and to waypoint pointed by index. This is based on code from POD-bot MM from KWo
if (!exists (index)) {
return 0;
}
int numConnectionsFixed = 0;
if (bots.getBotCount () > 0) {
bots.kickEveryone (true);
}
const int INFINITE_DISTANCE = 99999;
auto printInfo = [&outputToConsole](const char *fmt, ...) {
if (!outputToConsole) {
return;
}
char buffer[MAX_PRINT_BUFFER];
va_list ap;
va_start (ap, fmt);
vsnprintf (buffer, MAX_PRINT_BUFFER - 1, fmt, ap);
va_end (ap);
engine.print (buffer);
};
struct Connection {
int index;
int number;
int distance;
float angles;
public:
Connection (void) {
reset ();
}
public:
void reset (void) {
index = INVALID_WAYPOINT_INDEX;
number = INVALID_WAYPOINT_INDEX;
distance = INFINITE_DISTANCE;
angles = 0.0f;
}
};
Connection sorted[MAX_PATH_INDEX];
Connection top;
for (int i = 0; i < MAX_PATH_INDEX; i++) {
auto &cur = sorted[i];
cur.number = i;
cur.index = m_paths[index]->index[i];
cur.distance = m_paths[index]->distances[i];
if (cur.index == INVALID_WAYPOINT_INDEX) {
cur.distance = INFINITE_DISTANCE;
}
if (cur.distance < top.distance) {
top.distance = m_paths[index]->distances[i];
top.number = i;
top.index = cur.index;
}
}
if (top.number == INVALID_WAYPOINT_INDEX) {
printInfo ("Cannot find path to the closest connected waypoint to waypoint number %d!\n", index);
return numConnectionsFixed;
}
bool sorting = false;
// sort paths from the closest waypoint to the farest away one...
do {
sorting = false;
for (int i = 0; i < MAX_PATH_INDEX - 1; i++) {
if (sorted[i].distance > sorted[i + 1].distance) {
cr::swap (sorted[i], sorted[i + 1]);
sorting = true;
}
}
} while (sorting);
// calculate angles related to the angle of the closeset connected waypoint
for (int i = 0; i < MAX_PATH_INDEX; i++) {
auto &cur = sorted[i];
if (cur.index == INVALID_WAYPOINT_INDEX) {
cur.distance = INFINITE_DISTANCE;
cur.angles = 360.0f;
}
else if (exists (cur.index)) {
cur.angles = ((m_paths[cur.index]->origin - m_paths[index]->origin).toAngles () - (m_paths[sorted[0].index]->origin - m_paths[index]->origin).toAngles ()).y;
if (cur.angles < 0.0f) {
cur.angles += 360.0f;
}
}
}
// sort the paths from the lowest to the highest angle (related to the vector closest waypoint - checked index)...
do {
sorting = false;
for (int i = 0; i < MAX_PATH_INDEX - 1; i++) {
if (sorted[i].index != INVALID_WAYPOINT_INDEX && sorted[i].angles > sorted[i + 1].angles) {
cr::swap (sorted[i], sorted[i + 1]);
sorting = true;
}
}
} while (sorting);
// reset top state
top.reset ();
auto unassignPath = [&](const int id1, const int id2) {
m_waypointsChanged = true;
m_paths[id1]->index[id2] = INVALID_WAYPOINT_INDEX;
m_paths[id1]->distances[id2] = 0;
m_paths[id1]->connectionFlags[id2] = 0;
m_paths[id1]->connectionVelocity[id2].nullify ();
m_waypointsChanged = true;
g_waypointOn = true;
numConnectionsFixed++;
};
// check pass 0
auto inspect_p0 = [&](const int id) -> bool {
if (id < 2) {
return false;
}
auto &cur = sorted[id], &prev = sorted[id - 1], &prev2 = sorted[id - 2];
if (cur.index == INVALID_WAYPOINT_INDEX || prev.index == INVALID_WAYPOINT_INDEX || prev2.index == INVALID_WAYPOINT_INDEX) {
return false;
}
// store the highest index which should be tested later...
top.index = cur.index;
top.distance = cur.distance;
top.angles = cur.angles;
if (cur.angles - prev2.angles < 80.0f) {
// leave alone ladder connections and don't remove jump connections..
if (((m_paths[index]->flags & FLAG_LADDER) && (m_paths[prev.index]->flags & FLAG_LADDER)) || (m_paths[index]->connectionFlags[prev.number] & PATHFLAG_JUMP)) {
return false;
}
if ((cur.distance + prev2.distance) * 1.1f / 2.0f < static_cast <float> (prev.distance)) {
if (m_paths[index]->index[prev.number] == prev.index) {
printInfo ("Removing a useless (P.0.1) connection from index = %d to %d.", index, prev.index);
// unassign this path
unassignPath (index, prev.number);
for (int j = 0; j < MAX_PATH_INDEX; j++) {
if (m_paths[prev.index]->index[j] == index && !(m_paths[prev.index]->connectionFlags[j] & PATHFLAG_JUMP)) {
printInfo ("Removing a useless (P.0.2) connection from index = %d to %d.", prev.index, index);
// unassign this path
unassignPath (prev.index, j);
}
}
prev.index = INVALID_WAYPOINT_INDEX;
for (int j = id - 1; j < MAX_PATH_INDEX - 1; j++) {
sorted[j] = cr::move (sorted[j + 1]);
}
sorted[MAX_PATH_INDEX - 1].index = INVALID_WAYPOINT_INDEX;
// do a second check
return true;
}
else {
printInfo ("Failed to remove a useless (P.0) connection from index = %d to %d.", index, prev.index);
return false;
}
}
}
return false;
};
for (int i = 2; i < MAX_PATH_INDEX; i++) {
while (inspect_p0 (i));
}
// check pass 1
if (exists (top.index) && exists (sorted[0].index) && exists (sorted[1].index)) {
if ((sorted[1].angles - top.angles < 80.0f || 360.0f - (sorted[1].angles - top.angles) < 80.0f) && (!(m_paths[sorted[0].index]->flags & FLAG_LADDER) || !(m_paths[index]->flags & FLAG_LADDER)) && !(m_paths[index]->connectionFlags[sorted[0].number] & PATHFLAG_JUMP)) {
if ((sorted[1].distance + top.distance) * 1.1f / 2.0f < static_cast <float> (sorted[0].distance)) {
if (m_paths[index]->index[sorted[0].number] == sorted[0].index) {
printInfo ("Removing a useless (P.1.1) connection from index = %d to %d.", index, sorted[0].index);
// unassign this path
unassignPath (index, sorted[0].number);
for (int j = 0; j < MAX_PATH_INDEX; j++) {
if (m_paths[sorted[0].index]->index[j] == index && !(m_paths[sorted[0].index]->connectionFlags[j] & PATHFLAG_JUMP)) {
printInfo ("Removing a useless (P.1.2) connection from index = %d to %d.", sorted[0].index, index);
// unassign this path
unassignPath (sorted[0].index, j);
}
}
sorted[0].index = INVALID_WAYPOINT_INDEX;
for (int j = 0; j < MAX_PATH_INDEX - 1; j++) {
sorted[j] = cr::move (sorted[j + 1]);
}
sorted[MAX_PATH_INDEX - 1].index = INVALID_WAYPOINT_INDEX;
}
else {
printInfo ("Failed to remove a useless (P.1) connection from index = %d to %d.", sorted[0].index, index);
}
}
}
}
top.reset ();
// check pass 2
auto inspect_p2 = [&](const int id) -> bool {
if (id < 1) {
return false;
}
auto &cur = sorted[id], &prev = sorted[id - 1];
if (cur.index == INVALID_WAYPOINT_INDEX || prev.index == INVALID_WAYPOINT_INDEX) {
return false;
}
if (cur.angles - prev.angles < 40.0f) {
if (prev.distance < static_cast <float> (cur.distance * 1.1f)) {
// leave alone ladder connections and don't remove jump connections..
if (((m_paths[index]->flags & FLAG_LADDER) && (m_paths[cur.index]->flags & FLAG_LADDER)) || (m_paths[index]->connectionFlags[cur.number] & PATHFLAG_JUMP)) {
return false;
}
if (m_paths[index]->index[cur.number] == cur.index) {
printInfo ("Removing a useless (P.2.1) connection from index = %d to %d.", index, cur.index);
// unassign this path
unassignPath (index, cur.number);
for (int j = 0; j < MAX_PATH_INDEX; j++) {
if (m_paths[cur.index]->index[j] == index && !(m_paths[cur.index]->connectionFlags[j] & PATHFLAG_JUMP)) {
printInfo ("Removing a useless (P.2.2) connection from index = %d to %d.", cur.index, index);
// unassign this path
unassignPath (cur.index, j);
}
}
cur.index = INVALID_WAYPOINT_INDEX;
for (int j = id - 1; j < MAX_PATH_INDEX - 1; j++) {
sorted[j] = cr::move (sorted[j + 1]);
}
sorted[MAX_PATH_INDEX - 1].index = INVALID_WAYPOINT_INDEX;
return true;
}
else {
printInfo ("Failed to remove a useless (P.2) connection from index = %d to %d.", index, cur.index);
}
}
else if (cur.distance < static_cast <float> (prev.distance * 1.1f)) {
// leave alone ladder connections and don't remove jump connections..
if (((m_paths[index]->flags & FLAG_LADDER) && (m_paths[prev.index]->flags & FLAG_LADDER)) || (m_paths[index]->connectionFlags[prev.number] & PATHFLAG_JUMP)) {
return false;
}
if (m_paths[index]->index[prev.number] == prev.index) {
printInfo ("Removing a useless (P.2.3) connection from index = %d to %d.", index, prev.index);
// unassign this path
unassignPath (index, prev.number);
for (int j = 0; j < MAX_PATH_INDEX; j++) {
if (m_paths[prev.index]->index[j] == index && !(m_paths[prev.index]->connectionFlags[j] & PATHFLAG_JUMP)) {
printInfo ("Removing a useless (P.2.4) connection from index = %d to %d.", prev.index, index);
// unassign this path
unassignPath (prev.index, j);
}
}
prev.index = INVALID_WAYPOINT_INDEX;
for (int j = id - 1; j < MAX_PATH_INDEX - 1; j++) {
sorted[j] = cr::move (sorted[j + 1]);
}
sorted[MAX_PATH_INDEX - 1].index = INVALID_WAYPOINT_INDEX;
// do a second check
return true;
}
else {
printInfo ("Failed to remove a useless (P.2) connection from index = %d to %d.", index, prev.index);
}
}
}
else {
top = cur;
}
return false;
};
for (int i = 1; i < MAX_PATH_INDEX; i++) {
while (inspect_p2 (i));
}
// check pass 3
if (exists (top.index) && exists (sorted[0].index)) {
if ((top.angles - sorted[0].angles < 40.0f || (360.0f - top.angles - sorted[0].angles) < 40.0f) && (!(m_paths[sorted[0].index]->flags & FLAG_LADDER) || !(m_paths[index]->flags & FLAG_LADDER)) && !(m_paths[index]->connectionFlags[sorted[0].number] & PATHFLAG_JUMP)) {
if (top.distance * 1.1f < static_cast <float> (sorted[0].distance)) {
if (m_paths[index]->index[sorted[0].number] == sorted[0].index) {
printInfo ("Removing a useless (P.3.1) connection from index = %d to %d.", index, sorted[0].index);
// unassign this path
unassignPath (index, sorted[0].number);
for (int j = 0; j < MAX_PATH_INDEX; j++) {
if (m_paths[sorted[0].index]->index[j] == index && !(m_paths[sorted[0].index]->connectionFlags[j] & PATHFLAG_JUMP)) {
printInfo ("Removing a useless (P.3.2) connection from index = %d to %d.", sorted[0].index, index);
// unassign this path
unassignPath (sorted[0].index, j);
}
}
sorted[0].index = INVALID_WAYPOINT_INDEX;
for (int j = 0; j < MAX_PATH_INDEX - 1; j++) {
sorted[j] = cr::move (sorted[j + 1]);
}
sorted[MAX_PATH_INDEX - 1].index = INVALID_WAYPOINT_INDEX;
}
else {
printInfo ("Failed to remove a useless (P.3) connection from index = %d to %d.", sorted[0].index, index);
}
}
else if (sorted[0].distance * 1.1f < static_cast <float> (top.distance) && !(m_paths[index]->connectionFlags[top.number] & PATHFLAG_JUMP)) {
if (m_paths[index]->index[top.number] == top.index) {
printInfo ("Removing a useless (P.3.3) connection from index = %d to %d.", index, sorted[0].index);
// unassign this path
unassignPath (index, top.number);
for (int j = 0; j < MAX_PATH_INDEX; j++) {
if (m_paths[top.index]->index[j] == index && !(m_paths[top.index]->connectionFlags[j] & PATHFLAG_JUMP)) {
printInfo ("Removing a useless (P.3.4) connection from index = %d to %d.", sorted[0].index, index);
// unassign this path
unassignPath (top.index, j);
}
}
sorted[0].index = INVALID_WAYPOINT_INDEX;
}
else {
printInfo ("Failed to remove a useless (P.3) connection from index = %d to %d.", sorted[0].index, index);
}
}
}
}
return numConnectionsFixed;
}
void Waypoint::addPath (int addIndex, int pathIndex, float distance) {
if (!exists (addIndex) || !exists (pathIndex)) {
return;
}
Path *path = m_paths[addIndex];
// don't allow paths get connected twice
for (int i = 0; i < MAX_PATH_INDEX; i++) {
if (path->index[i] == pathIndex) {
logEntry (true, LL_WARNING, "Denied path creation from %d to %d (path already exists)", addIndex, pathIndex);
return;
}
}
// check for free space in the connection indices
for (int16 i = 0; i < MAX_PATH_INDEX; i++) {
if (path->index[i] == INVALID_WAYPOINT_INDEX) {
path->index[i] = static_cast <int16> (pathIndex);
path->distances[i] = cr::abs (static_cast <int> (distance));
logEntry (true, LL_DEFAULT, "Path added from %d to %d", addIndex, pathIndex);
return;
}
}
// there wasn't any free space. try exchanging it with a long-distance path
int maxDistance = -9999;
int slotID = INVALID_WAYPOINT_INDEX;
for (int i = 0; i < MAX_PATH_INDEX; i++) {
if (path->distances[i] > maxDistance) {
maxDistance = path->distances[i];
slotID = i;
}
}
if (slotID != INVALID_WAYPOINT_INDEX) {
logEntry (true, LL_DEFAULT, "Path added from %d to %d", addIndex, pathIndex);
path->index[slotID] = static_cast <int16> (pathIndex);
path->distances[slotID] = cr::abs (static_cast <int> (distance));
}
}
int Waypoint::getFarest (const Vector &origin, float maxDistance) {
// find the farest waypoint to that Origin, and return the index to this waypoint
int index = INVALID_WAYPOINT_INDEX;
maxDistance = cr::square (maxDistance);
for (int i = 0; i < m_numWaypoints; i++) {
float distance = (m_paths[i]->origin - origin).lengthSq ();
if (distance > maxDistance) {
index = i;
maxDistance = distance;
}
}
return index;
}
int Waypoint::getNearestNoBuckets (const Vector &origin, float minDistance, int flags) {
// find the nearest waypoint to that origin and return the index
// fallback and go thru wall the waypoints...
int index = INVALID_WAYPOINT_INDEX;
minDistance = cr::square (minDistance);
for (int i = 0; i < m_numWaypoints; i++) {
if (flags != -1 && !(m_paths[i]->flags & flags)) {
continue; // if flag not -1 and waypoint has no this flag, skip waypoint
}
float distance = (m_paths[i]->origin - origin).lengthSq ();
if (distance < minDistance) {
index = i;
minDistance = distance;
}
}
return index;
}
int Waypoint::getEditorNeareset (void) {
if (!g_waypointOn) {
return INVALID_WAYPOINT_INDEX;
}
return getNearestNoBuckets (g_hostEntity->v.origin, 50.0f);
}
int Waypoint::getNearest (const Vector &origin, float minDistance, int flags) {
// find the nearest waypoint to that origin and return the index
auto &bucket = getWaypointsInBucket (origin);
if (bucket.empty ()) {
return getNearestNoBuckets (origin, minDistance, flags);
}
int index = INVALID_WAYPOINT_INDEX;
minDistance = cr::square (minDistance);
for (const auto at : bucket) {
if (flags != -1 && !(m_paths[at]->flags & flags)) {
continue; // if flag not -1 and waypoint has no this flag, skip waypoint
}
float distance = (m_paths[at]->origin - origin).lengthSq ();
if (distance < minDistance) {
index = at;
minDistance = distance;
}
}
return index;
}
IntArray Waypoint::searchRadius (float radius, const Vector &origin, int maxCount) {
// returns all waypoints within radius from position
IntArray result;
auto &bucket = getWaypointsInBucket (origin);
if (bucket.empty ()) {
result.push (getNearestNoBuckets (origin, radius));
return cr::move (result);
}
radius = cr::square (radius);
if (maxCount != -1) {
result.reserve (maxCount);
}
for (const auto at : bucket) {
if (maxCount != -1 && static_cast <int> (result.length ()) > maxCount) {
break;
}
if ((m_paths[at]->origin - origin).lengthSq () < radius) {
result.push (at);
}
}
return cr::move (result);
}
void Waypoint::push (int flags, const Vector &waypointOrigin) {
if (engine.isNullEntity (g_hostEntity)) {
return;
}
int index = INVALID_WAYPOINT_INDEX, i;
float distance;
Vector forward;
Path *path = nullptr;
bool placeNew = true;
Vector newOrigin = waypointOrigin;
if (waypointOrigin.empty ()) {
newOrigin = g_hostEntity->v.origin;
}
if (bots.getBotCount () > 0) {
bots.kickEveryone (true);
}
m_waypointsChanged = true;
switch (flags) {
case 6:
index = getEditorNeareset ();
if (index != INVALID_WAYPOINT_INDEX) {
path = m_paths[index];
if (!(path->flags & FLAG_CAMP)) {
engine.centerPrint ("This is not Camping Waypoint");
return;
}
makeVectors (g_hostEntity->v.v_angle);
forward = g_hostEntity->v.origin + g_hostEntity->v.view_ofs + g_pGlobals->v_forward * 640.0f;
path->campEndX = forward.x;
path->campEndY = forward.y;
// play "done" sound...
engine.playSound (g_hostEntity, "common/wpn_hudon.wav");
}
return;
case 9:
index = getEditorNeareset ();
if (index != INVALID_WAYPOINT_INDEX && m_paths[index] != nullptr) {
distance = (m_paths[index]->origin - g_hostEntity->v.origin).length ();
if (distance < 50.0f) {
placeNew = false;
path = m_paths[index];
path->origin = (path->origin + m_learnPosition) * 0.5f;
}
}
else
newOrigin = m_learnPosition;
break;
case 10:
index = getEditorNeareset ();
if (index != INVALID_WAYPOINT_INDEX && m_paths[index] != nullptr) {
distance = (m_paths[index]->origin - g_hostEntity->v.origin).length ();
if (distance < 50.0f) {
placeNew = false;
path = m_paths[index];
int connectionFlags = 0;
for (i = 0; i < MAX_PATH_INDEX; i++) {
connectionFlags += path->connectionFlags[i];
}
if (connectionFlags == 0) {
path->origin = (path->origin + g_hostEntity->v.origin) * 0.5f;
}
}
}
break;
}
if (placeNew) {
if (m_numWaypoints >= MAX_WAYPOINTS) {
return;
}
index = m_numWaypoints;
m_paths[index] = new Path;
path = m_paths[index];
// increment total number of waypoints
m_numWaypoints++;
path->pathNumber = index;
path->flags = 0;
// store the origin (location) of this waypoint
path->origin = newOrigin;
addToBucket (newOrigin, index);
path->campEndX = 0.0f;
path->campEndY = 0.0f;
path->campStartX = 0.0f;
path->campStartY = 0.0f;
for (i = 0; i < MAX_PATH_INDEX; i++) {
path->index[i] = INVALID_WAYPOINT_INDEX;
path->distances[i] = 0;
path->connectionFlags[i] = 0;
path->connectionVelocity[i].nullify ();
}
// store the last used waypoint for the auto waypoint code...
m_lastWaypoint = g_hostEntity->v.origin;
}
// set the time that this waypoint was originally displayed...
m_waypointDisplayTime[index] = 0;
if (flags == 9) {
m_lastJumpWaypoint = index;
}
else if (flags == 10) {
distance = (m_paths[m_lastJumpWaypoint]->origin - g_hostEntity->v.origin).length ();
addPath (m_lastJumpWaypoint, index, distance);
for (i = 0; i < MAX_PATH_INDEX; i++) {
if (m_paths[m_lastJumpWaypoint]->index[i] == index) {
m_paths[m_lastJumpWaypoint]->connectionFlags[i] |= PATHFLAG_JUMP;
m_paths[m_lastJumpWaypoint]->connectionVelocity[i] = m_learnVelocity;
break;
}
}
calculatePathRadius (index);
return;
}
if (path == nullptr) {
return;
}
if (g_hostEntity->v.flags & FL_DUCKING) {
path->flags |= FLAG_CROUCH; // set a crouch waypoint
}
if (g_hostEntity->v.movetype == MOVETYPE_FLY) {
path->flags |= FLAG_LADDER;
makeVectors (g_hostEntity->v.v_angle);
forward = g_hostEntity->v.origin + g_hostEntity->v.view_ofs + g_pGlobals->v_forward * 640.0f;
path->campStartY = forward.y;
}
else if (m_isOnLadder) {
path->flags |= FLAG_LADDER;
}
switch (flags) {
case 1:
path->flags |= FLAG_CROSSING;
path->flags |= FLAG_TF_ONLY;
break;
case 2:
path->flags |= FLAG_CROSSING;
path->flags |= FLAG_CF_ONLY;
break;
case 3:
path->flags |= FLAG_NOHOSTAGE;
break;
case 4:
path->flags |= FLAG_RESCUE;
break;
case 5:
path->flags |= FLAG_CROSSING;
path->flags |= FLAG_CAMP;
makeVectors (g_hostEntity->v.v_angle);
forward = g_hostEntity->v.origin + g_hostEntity->v.view_ofs + g_pGlobals->v_forward * 640.0f;
path->campStartX = forward.x;
path->campStartY = forward.y;
break;
case 100:
path->flags |= FLAG_GOAL;
break;
}
// Ladder waypoints need careful connections
if (path->flags & FLAG_LADDER) {
float minDistance = 9999.0f;
int destIndex = INVALID_WAYPOINT_INDEX;
TraceResult tr;
// calculate all the paths to this new waypoint
for (i = 0; i < m_numWaypoints; i++) {
if (i == index) {
continue; // skip the waypoint that was just added
}
// other ladder waypoints should connect to this
if (m_paths[i]->flags & FLAG_LADDER) {
// check if the waypoint is reachable from the new one
engine.testLine (newOrigin, m_paths[i]->origin, TRACE_IGNORE_MONSTERS, g_hostEntity, &tr);
if (tr.flFraction == 1.0f && cr::abs (newOrigin.x - m_paths[i]->origin.x) < 64.0f && cr::abs (newOrigin.y - m_paths[i]->origin.y) < 64.0f && cr::abs (newOrigin.z - m_paths[i]->origin.z) < g_autoPathDistance) {
distance = (m_paths[i]->origin - newOrigin).length ();
addPath (index, i, distance);
addPath (i, index, distance);
}
}
else {
// check if the waypoint is reachable from the new one
if (isNodeReacheable (newOrigin, m_paths[i]->origin) || isNodeReacheable (m_paths[i]->origin, newOrigin)) {
distance = (m_paths[i]->origin - newOrigin).length ();
if (distance < minDistance) {
destIndex = i;
minDistance = distance;
}
}
}
}
if (exists (destIndex)) {
// check if the waypoint is reachable from the new one (one-way)
if (isNodeReacheable (newOrigin, m_paths[destIndex]->origin)) {
distance = (m_paths[destIndex]->origin - newOrigin).length ();
addPath (index, destIndex, distance);
}
// check if the new one is reachable from the waypoint (other way)
if (isNodeReacheable (m_paths[destIndex]->origin, newOrigin)) {
distance = (m_paths[destIndex]->origin - newOrigin).length ();
addPath (destIndex, index, distance);
}
}
}
else {
// calculate all the paths to this new waypoint
for (i = 0; i < m_numWaypoints; i++) {
if (i == index) {
continue; // skip the waypoint that was just added
}
// check if the waypoint is reachable from the new one (one-way)
if (isNodeReacheable (newOrigin, m_paths[i]->origin)) {
distance = (m_paths[i]->origin - newOrigin).length ();
addPath (index, i, distance);
}
// check if the new one is reachable from the waypoint (other way)
if (isNodeReacheable (m_paths[i]->origin, newOrigin)) {
distance = (m_paths[i]->origin - newOrigin).length ();
addPath (i, index, distance);
}
}
removeUselessConnections (index, false);
}
engine.playSound (g_hostEntity, "weapons/xbow_hit1.wav");
calculatePathRadius (index); // calculate the wayzone of this waypoint
}
void Waypoint::erase (int target) {
m_waypointsChanged = true;
if (m_numWaypoints < 1) {
return;
}
if (bots.getBotCount () > 0) {
bots.kickEveryone (true);
}
int index = (target == INVALID_WAYPOINT_INDEX) ? getEditorNeareset () : target;
if (index == INVALID_WAYPOINT_INDEX) {
return;
}
Path *path = nullptr;
assert (m_paths[index] != nullptr);
int i, j;
for (i = 0; i < m_numWaypoints; i++) // delete all references to Node
{
path = m_paths[i];
for (j = 0; j < MAX_PATH_INDEX; j++) {
if (path->index[j] == index) {
path->index[j] = INVALID_WAYPOINT_INDEX; // unassign this path
path->connectionFlags[j] = 0;
path->distances[j] = 0;
path->connectionVelocity[j].nullify ();
}
}
}
for (i = 0; i < m_numWaypoints; i++) {
path = m_paths[i];
if (path->pathNumber > index) { // if pathnumber bigger than deleted node...
path->pathNumber--;
}
for (j = 0; j < MAX_PATH_INDEX; j++) {
if (path->index[j] > index) {
path->index[j]--;
}
}
}
eraseFromBucket (m_paths[index]->origin, index);
// free deleted node
delete m_paths[index];
m_paths[index] = nullptr;
// rotate path array down
for (i = index; i < m_numWaypoints - 1; i++) {
m_paths[i] = m_paths[i + 1];
}
m_numWaypoints--;
m_waypointDisplayTime[index] = 0;
engine.playSound (g_hostEntity, "weapons/mine_activate.wav");
}
void Waypoint::toggleFlags (int toggleFlag) {
// this function allow manually changing flags
int index = getEditorNeareset ();
if (index != INVALID_WAYPOINT_INDEX) {
if (m_paths[index]->flags & toggleFlag) {
m_paths[index]->flags &= ~toggleFlag;
}
else if (!(m_paths[index]->flags & toggleFlag)) {
if (toggleFlag == FLAG_SNIPER && !(m_paths[index]->flags & FLAG_CAMP)) {
logEntry (true, LL_ERROR, "Cannot assign sniper flag to waypoint #%d. This is not camp waypoint", index);
return;
}
m_paths[index]->flags |= toggleFlag;
}
// play "done" sound...
engine.playSound (g_hostEntity, "common/wpn_hudon.wav");
}
}
void Waypoint::setRadius (int radius) {
// this function allow manually setting the zone radius
int index = getEditorNeareset ();
if (index != INVALID_WAYPOINT_INDEX) {
m_paths[index]->radius = static_cast <float> (radius);
// play "done" sound...
engine.playSound (g_hostEntity, "common/wpn_hudon.wav");
}
}
bool Waypoint::isConnected (int pointA, int pointB) {
// this function checks if waypoint A has a connection to waypoint B
for (int i = 0; i < MAX_PATH_INDEX; i++) {
if (m_paths[pointA]->index[i] == pointB) {
return true;
}
}
return false;
}
int Waypoint::getFacingIndex (void) {
// this function finds waypoint the user is pointing at.
int indexToPoint = INVALID_WAYPOINT_INDEX;
Array <float> cones;
float maxCone = 0.0f;
// find the waypoint the user is pointing at
for (int i = 0; i < m_numWaypoints; i++) {
auto path = m_paths[i];
if ((path->origin - g_hostEntity->v.origin).lengthSq () > cr::square (500.0f)) {
continue;
}
cones.clear ();
// get the current view cones
cones.push (getShootingConeDeviation (g_hostEntity, path->origin));
cones.push (getShootingConeDeviation (g_hostEntity, path->origin - Vector (0.0f, 0.0f, (path->flags & FLAG_CROUCH) ? 6.0f : 12.0f)));
cones.push (getShootingConeDeviation (g_hostEntity, path->origin - Vector (0.0f, 0.0f, (path->flags & FLAG_CROUCH) ? 12.0f : 24.0f)));
cones.push (getShootingConeDeviation (g_hostEntity, path->origin + Vector (0.0f, 0.0f, (path->flags & FLAG_CROUCH) ? 6.0f : 12.0f)));
cones.push (getShootingConeDeviation (g_hostEntity, path->origin + Vector (0.0f, 0.0f, (path->flags & FLAG_CROUCH) ? 12.0f : 24.0f)));
// check if we can see it
for (auto &cone : cones) {
if (cone > 1.000f && cone > maxCone) {
maxCone = cone;
indexToPoint = i;
}
}
}
return indexToPoint;
}
void Waypoint::pathCreate (char dir) {
// this function allow player to manually create a path from one waypoint to another
int nodeFrom = getEditorNeareset ();
if (nodeFrom == INVALID_WAYPOINT_INDEX) {
engine.centerPrint ("Unable to find nearest waypoint in 50 units");
return;
}
int nodeTo = m_facingAtIndex;
if (!exists (nodeTo)) {
if (exists (m_cacheWaypointIndex)) {
nodeTo = m_cacheWaypointIndex;
}
else {
engine.centerPrint ("Unable to find destination waypoint");
return;
}
}
if (nodeTo == nodeFrom) {
engine.centerPrint ("Unable to connect waypoint with itself");
return;
}
float distance = (m_paths[nodeTo]->origin - m_paths[nodeFrom]->origin).length ();
if (dir == CONNECTION_OUTGOING) {
addPath (nodeFrom, nodeTo, distance);
}
else if (dir == CONNECTION_INCOMING) {
addPath (nodeTo, nodeFrom, distance);
}
else {
addPath (nodeFrom, nodeTo, distance);
addPath (nodeTo, nodeFrom, distance);
}
engine.playSound (g_hostEntity, "common/wpn_hudon.wav");
m_waypointsChanged = true;
}
void Waypoint::erasePath (void) {
// this function allow player to manually remove a path from one waypoint to another
int nodeFrom = getEditorNeareset ();
int index = 0;
if (nodeFrom == INVALID_WAYPOINT_INDEX) {
engine.centerPrint ("Unable to find nearest waypoint in 50 units");
return;
}
int nodeTo = m_facingAtIndex;
if (!exists (nodeTo)) {
if (exists (m_cacheWaypointIndex)) {
nodeTo = m_cacheWaypointIndex;
}
else {
engine.centerPrint ("Unable to find destination waypoint");
return;
}
}
for (index = 0; index < MAX_PATH_INDEX; index++) {
if (m_paths[nodeFrom]->index[index] == nodeTo) {
m_waypointsChanged = true;
m_paths[nodeFrom]->index[index] = INVALID_WAYPOINT_INDEX; // unassigns this path
m_paths[nodeFrom]->distances[index] = 0;
m_paths[nodeFrom]->connectionFlags[index] = 0;
m_paths[nodeFrom]->connectionVelocity[index].nullify ();
engine.playSound (g_hostEntity, "weapons/mine_activate.wav");
return;
}
}
// not found this way ? check for incoming connections then
index = nodeFrom;
nodeFrom = nodeTo;
nodeTo = index;
for (index = 0; index < MAX_PATH_INDEX; index++) {
if (m_paths[nodeFrom]->index[index] == nodeTo) {
m_waypointsChanged = true;
m_paths[nodeFrom]->index[index] = INVALID_WAYPOINT_INDEX; // unassign this path
m_paths[nodeFrom]->distances[index] = 0;
m_paths[nodeFrom]->connectionFlags[index] = 0;
m_paths[nodeFrom]->connectionVelocity[index].nullify ();
engine.playSound (g_hostEntity, "weapons/mine_activate.wav");
return;
}
}
engine.centerPrint ("There is already no path on this waypoint");
}
void Waypoint::cachePoint (void) {
int node = getEditorNeareset ();
if (node == INVALID_WAYPOINT_INDEX) {
m_cacheWaypointIndex = INVALID_WAYPOINT_INDEX;
engine.centerPrint ("Cached waypoint cleared (nearby point not found in 50 units range)");
return;
}
m_cacheWaypointIndex = node;
engine.centerPrint ("Waypoint #%d has been put into memory", m_cacheWaypointIndex);
}
void Waypoint::calculatePathRadius (int index) {
// calculate "wayzones" for the nearest waypoint to pentedict (meaning a dynamic distance area to vary waypoint origin)
Path *path = m_paths[index];
Vector start, direction;
if ((path->flags & (FLAG_LADDER | FLAG_GOAL | FLAG_CAMP | FLAG_RESCUE | FLAG_CROUCH)) || m_learnJumpWaypoint) {
path->radius = 0.0f;
return;
}
for (int i = 0; i < MAX_PATH_INDEX; i++) {
if (path->index[i] != INVALID_WAYPOINT_INDEX && (m_paths[path->index[i]]->flags & FLAG_LADDER)) {
path->radius = 0.0f;
return;
}
}
TraceResult tr;
bool wayBlocked = false;
for (float scanDistance = 32.0f; scanDistance < 128.0f; scanDistance += 16.0f) {
start = path->origin;
makeVectors (Vector::null ());
direction = g_pGlobals->v_forward * scanDistance;
direction = direction.toAngles ();
path->radius = scanDistance;
for (float circleRadius = 0.0f; circleRadius < 360.0f; circleRadius += 20.0f) {
makeVectors (direction);
Vector radiusStart = start + g_pGlobals->v_forward * scanDistance;
Vector radiusEnd = start + g_pGlobals->v_forward * scanDistance;
engine.testHull (radiusStart, radiusEnd, TRACE_IGNORE_MONSTERS, head_hull, nullptr, &tr);
if (tr.flFraction < 1.0f) {
engine.testLine (radiusStart, radiusEnd, TRACE_IGNORE_MONSTERS, nullptr, &tr);
if (strncmp ("func_door", STRING (tr.pHit->v.classname), 9) == 0) {
path->radius = 0.0f;
wayBlocked = true;
break;
}
wayBlocked = true;
path->radius -= 16.0f;
break;
}
Vector dropStart = start + g_pGlobals->v_forward * scanDistance;
Vector dropEnd = dropStart - Vector (0.0f, 0.0f, scanDistance + 60.0f);
engine.testHull (dropStart, dropEnd, TRACE_IGNORE_MONSTERS, head_hull, nullptr, &tr);
if (tr.flFraction >= 1.0f) {
wayBlocked = true;
path->radius -= 16.0f;
break;
}
dropStart = start - g_pGlobals->v_forward * scanDistance;
dropEnd = dropStart - Vector (0.0f, 0.0f, scanDistance + 60.0f);
engine.testHull (dropStart, dropEnd, TRACE_IGNORE_MONSTERS, head_hull, nullptr, &tr);
if (tr.flFraction >= 1.0f) {
wayBlocked = true;
path->radius -= 16.0f;
break;
}
radiusEnd.z += 34.0f;
engine.testHull (radiusStart, radiusEnd, TRACE_IGNORE_MONSTERS, head_hull, nullptr, &tr);
if (tr.flFraction < 1.0f) {
wayBlocked = true;
path->radius -= 16.0f;
break;
}
direction.y = cr::angleNorm (direction.y + circleRadius);
}
if (wayBlocked) {
break;
}
}
path->radius -= 16.0f;
if (path->radius < 0.0f)
path->radius = 0.0f;
}
void Waypoint::saveExperience (void) {
ExtensionHeader header;
if (m_numWaypoints < 1 || m_waypointsChanged) {
return;
}
memset (header.header, 0, sizeof (header.header));
strcpy (header.header, FH_EXPERIENCE);
header.fileVersion = FV_EXPERIENCE;
header.pointNumber = m_numWaypoints;
ExperienceSave *experienceSave = new ExperienceSave[m_numWaypoints * m_numWaypoints];
for (int i = 0; i < m_numWaypoints; i++) {
for (int j = 0; j < m_numWaypoints; j++) {
(experienceSave + (i * m_numWaypoints) + j)->team0Damage = static_cast <uint8> ((g_experienceData + (i * m_numWaypoints) + j)->team0Damage >> 3);
(experienceSave + (i * m_numWaypoints) + j)->team1Damage = static_cast <uint8> ((g_experienceData + (i * m_numWaypoints) + j)->team1Damage >> 3);
(experienceSave + (i * m_numWaypoints) + j)->team0Value = static_cast <int8> ((g_experienceData + (i * m_numWaypoints) + j)->team0Value / 8);
(experienceSave + (i * m_numWaypoints) + j)->team1Value = static_cast <int8> ((g_experienceData + (i * m_numWaypoints) + j)->team1Value / 8);
}
}
int result = Compress::encode (format ("%slearned/%s.exp", getDataDirectory (), engine.getMapName ()), (uint8 *)&header, sizeof (ExtensionHeader), (uint8 *) experienceSave, m_numWaypoints * m_numWaypoints * sizeof (ExperienceSave));
delete[] experienceSave;
if (result == -1) {
logEntry (true, LL_ERROR, "Couldn't save experience data");
return;
}
}
void Waypoint::initExperience (void) {
int i, j;
delete[] g_experienceData;
g_experienceData = nullptr;
if (m_numWaypoints < 1) {
return;
}
g_experienceData = new Experience[m_numWaypoints * m_numWaypoints];
g_highestDamageCT = 1;
g_highestDamageT = 1;
// initialize table by hand to correct values, and NOT zero it out
for (i = 0; i < m_numWaypoints; i++) {
for (j = 0; j < m_numWaypoints; j++) {
(g_experienceData + (i * m_numWaypoints) + j)->team0DangerIndex = INVALID_WAYPOINT_INDEX;
(g_experienceData + (i * m_numWaypoints) + j)->team1DangerIndex = INVALID_WAYPOINT_INDEX;
(g_experienceData + (i * m_numWaypoints) + j)->team0Damage = 0;
(g_experienceData + (i * m_numWaypoints) + j)->team1Damage = 0;
(g_experienceData + (i * m_numWaypoints) + j)->team0Value = 0;
(g_experienceData + (i * m_numWaypoints) + j)->team1Value = 0;
}
}
File fp (format ("%slearned/%s.exp", getDataDirectory (), engine.getMapName ()), "rb");
// if file exists, read the experience data from it
if (fp.isValid ()) {
ExtensionHeader header;
memset (&header, 0, sizeof (header));
if (fp.read (&header, sizeof (header)) == 0) {
logEntry (true, LL_ERROR, "Experience data damaged (unable to read header)");
fp.close ();
return;
}
fp.close ();
if (strncmp (header.header, FH_EXPERIENCE, strlen (FH_EXPERIENCE)) == 0) {
if (header.fileVersion == FV_EXPERIENCE && header.pointNumber == m_numWaypoints) {
ExperienceSave *experienceLoad = new ExperienceSave[m_numWaypoints * m_numWaypoints * sizeof (ExperienceSave)];
Compress::decode (format ("%slearned/%s.exp", getDataDirectory (), engine.getMapName ()), sizeof (ExtensionHeader), (uint8 *)experienceLoad, m_numWaypoints * m_numWaypoints * sizeof (ExperienceSave));
for (i = 0; i < m_numWaypoints; i++) {
for (j = 0; j < m_numWaypoints; j++) {
if (i == j) {
(g_experienceData + (i * m_numWaypoints) + j)->team0Damage = (uint16) ((experienceLoad + (i * m_numWaypoints) + j)->team0Damage);
(g_experienceData + (i * m_numWaypoints) + j)->team1Damage = (uint16) ((experienceLoad + (i * m_numWaypoints) + j)->team1Damage);
if ((g_experienceData + (i * m_numWaypoints) + j)->team0Damage > g_highestDamageT)
g_highestDamageT = (g_experienceData + (i * m_numWaypoints) + j)->team0Damage;
if ((g_experienceData + (i * m_numWaypoints) + j)->team1Damage > g_highestDamageCT)
g_highestDamageCT = (g_experienceData + (i * m_numWaypoints) + j)->team1Damage;
}
else {
(g_experienceData + (i * m_numWaypoints) + j)->team0Damage = (uint16) ((experienceLoad + (i * m_numWaypoints) + j)->team0Damage) << 3;
(g_experienceData + (i * m_numWaypoints) + j)->team1Damage = (uint16) ((experienceLoad + (i * m_numWaypoints) + j)->team1Damage) << 3;
}
(g_experienceData + (i * m_numWaypoints) + j)->team0Value = (int16) ((experienceLoad + i * (m_numWaypoints) + j)->team0Value) * 8;
(g_experienceData + (i * m_numWaypoints) + j)->team1Value = (int16) ((experienceLoad + i * (m_numWaypoints) + j)->team1Value) * 8;
}
}
delete[] experienceLoad;
}
else
logEntry (true, LL_WARNING, "Experience data damaged (wrong version, or not for this map)");
}
}
}
void Waypoint::saveVisibility (void) {
if (m_numWaypoints == 0) {
return;
}
ExtensionHeader header;
memset (&header, 0, sizeof (ExtensionHeader));
// parse header
memset (header.header, 0, sizeof (header.header));
strcpy (header.header, FH_VISTABLE);
header.fileVersion = FV_VISTABLE;
header.pointNumber = m_numWaypoints;
File fp (format ("%slearned/%s.vis", getDataDirectory (), engine.getMapName ()), "wb");
if (!fp.isValid ()) {
logEntry (true, LL_ERROR, "Failed to open visibility table for writing");
return;
}
fp.close ();
Compress::encode (format ("%slearned/%s.vis", getDataDirectory (), engine.getMapName ()), (uint8 *)&header, sizeof (ExtensionHeader), (uint8 *)m_visLUT, MAX_WAYPOINTS * (MAX_WAYPOINTS / 4) * sizeof (uint8));
}
void Waypoint::initVisibility (void) {
if (m_numWaypoints == 0)
return;
ExtensionHeader header;
File fp (format ("%slearned/%s.vis", getDataDirectory (), engine.getMapName ()), "rb");
m_redoneVisibility = false;
if (!fp.isValid ()) {
m_visibilityIndex = 0;
m_redoneVisibility = true;
logEntry (true, LL_DEFAULT, "Vistable doesn't exists, vistable will be rebuilded");
return;
}
// read the header of the file
if (fp.read (&header, sizeof (header)) == 0) {
logEntry (true, LL_ERROR, "Vistable damaged (unable to read header)");
fp.close ();
return;
}
if (strncmp (header.header, FH_VISTABLE, strlen (FH_VISTABLE)) != 0 || header.fileVersion != FV_VISTABLE || header.pointNumber != m_numWaypoints) {
m_visibilityIndex = 0;
m_redoneVisibility = true;
logEntry (true, LL_WARNING, "Visibility table damaged (wrong version, or not for this map), vistable will be rebuilded.");
fp.close ();
return;
}
int result = Compress::decode (format ("%slearned/%s.vis", getDataDirectory (), engine.getMapName ()), sizeof (ExtensionHeader), (uint8 *)m_visLUT, MAX_WAYPOINTS * (MAX_WAYPOINTS / 4) * sizeof (uint8));
if (result == -1) {
m_visibilityIndex = 0;
m_redoneVisibility = true;
logEntry (true, LL_ERROR, "Failed to decode vistable, vistable will be rebuilded.");
fp.close ();
return;
}
fp.close ();
}
void Waypoint::initLightLevels (void) {
// this function get's the light level for each waypoin on the map
// @todo: re-enable when working on flashlights
illum.enableAnimation (false);
return;
#if 0
// no waypoints ? no light levels, and only one-time init
if (!m_numWaypoints || !cr::fzero (m_waypointLightLevel[0])) {
return;
}
// update light levels for all waypoints
for (int i = 0; i < m_numWaypoints; i++) {
m_waypointLightLevel[i] = illum.getLightLevel (m_paths[i]->origin);
}
// disable lightstyle animations on finish (will be auto-enabled on mapchange)
illum.enableAnimation (false);
#endif
}
void Waypoint::initTypes (void) {
m_terrorPoints.clear ();
m_ctPoints.clear ();
m_goalPoints.clear ();
m_campPoints.clear ();
m_rescuePoints.clear ();
m_sniperPoints.clear ();
m_visitedGoals.clear ();
for (int i = 0; i < m_numWaypoints; i++) {
if (m_paths[i]->flags & FLAG_TF_ONLY) {
m_terrorPoints.push (i);
}
else if (m_paths[i]->flags & FLAG_CF_ONLY) {
m_ctPoints.push (i);
}
else if (m_paths[i]->flags & FLAG_GOAL) {
m_goalPoints.push (i);
}
else if (m_paths[i]->flags & FLAG_CAMP) {
m_campPoints.push (i);
}
else if (m_paths[i]->flags & FLAG_SNIPER) {
m_sniperPoints.push (i);
}
else if (m_paths[i]->flags & FLAG_RESCUE) {
m_rescuePoints.push (i);
}
}
}
bool Waypoint::load (void) {
initBuckets ();
if (m_loadTries++ > 3) {
m_loadTries = 0;
sprintf (m_infoBuffer, "Giving up loading waypoint file (%s). Something went wrong.", engine.getMapName ());
logEntry (true, LL_ERROR, m_infoBuffer);
return false;
}
MemFile fp (getWaypointFilename (true));
WaypointHeader header;
memset (&header, 0, sizeof (header));
// save for faster access
const char *map = engine.getMapName ();
// helper function
auto throwError = [&] (const char *fmt, ...) -> bool {
va_list ap;
va_start (ap, fmt);
vsnprintf (m_infoBuffer, MAX_PRINT_BUFFER - 1, fmt, ap);
va_end (ap);
logEntry (true, LL_ERROR, m_infoBuffer);
if (fp.isValid ()) {
fp.close ();
}
m_numWaypoints = 0;
m_waypointPaths = false;
return false;
};
if (fp.isValid ()) {
if (fp.read (&header, sizeof (header)) == 0) {
return throwError ("%s.pwf - damaged waypoint file (unable to read header)", map);
}
if (strncmp (header.header, FH_WAYPOINT, strlen (FH_WAYPOINT)) == 0) {
if (header.fileVersion != FV_WAYPOINT) {
return throwError ("%s.pwf - incorrect waypoint file version (expected '%d' found '%ld')", map, FV_WAYPOINT, header.fileVersion);
}
else if (!!stricmp (header.mapName, map)) {
return throwError ("%s.pwf - hacked waypoint file, file name doesn't match waypoint header information (mapname: '%s', header: '%s')", map, map, header.mapName);
}
else {
if (header.pointNumber == 0 || header.pointNumber > MAX_WAYPOINTS) {
return throwError ("%s.pwf - waypoint file contains illegal number of waypoints (mapname: '%s', header: '%s')", map, map, header.mapName);
}
init ();
m_numWaypoints = header.pointNumber;
for (int i = 0; i < m_numWaypoints; i++) {
m_paths[i] = new Path;
if (fp.read (m_paths[i], sizeof (Path)) == 0) {
return throwError ("%s.pwf - truncated waypoint file (count: %d, need: %d)", map, i, m_numWaypoints);
}
// more checks of waypoint quality
if (m_paths[i]->pathNumber < 0 || m_paths[i]->pathNumber > m_numWaypoints) {
return throwError ("%s.pwf - bad waypoint file (path #%d index is out of bounds)", map, i);
}
addToBucket (m_paths[i]->origin, i);
}
m_waypointPaths = true;
}
}
else {
return throwError ("%s.pwf is not a yapb waypoint file (header found '%s' needed '%s'", map, header.header, FH_WAYPOINT);
}
fp.close ();
}
else {
if (yb_waypoint_autodl_enable.boolean ()) {
logEntry (true, LL_DEFAULT, "%s.pwf does not exist, trying to download from waypoint database", map);
switch (downloadWaypoint ()) {
case WDE_SOCKET_ERROR:
return throwError ("%s.pwf does not exist. Can't autodownload. Socket error.", map);
case WDE_CONNECT_ERROR:
return throwError ("%s.pwf does not exist. Can't autodownload. Connection problems.", map);
case WDE_NOTFOUND_ERROR:
return throwError ("%s.pwf does not exist. Can't autodownload. Waypoint not available.", map);
case WDE_NOERROR:
logEntry (true, LL_DEFAULT, "%s.pwf was downloaded from waypoint database. Trying to load...", map);
return load ();
}
}
return throwError ("%s.pwf does not exist", map);
}
if (strncmp (header.author, "official", 7) == 0) {
sprintf (m_infoBuffer, "Using Official Waypoint File");
}
else {
sprintf (m_infoBuffer, "Using waypoint file by: %s", header.author);
}
for (int i = 0; i < m_numWaypoints; i++) {
m_waypointDisplayTime[i] = 0.0f;
m_waypointLightLevel[i] = 0.0f;
}
initPathMatrix ();
initTypes ();
m_waypointsChanged = false;
g_highestKills = 1;
m_pathDisplayTime = 0.0f;
m_arrowDisplayTime = 0.0f;
initVisibility ();
initExperience ();
extern ConVar yb_debug_goal;
yb_debug_goal.set (INVALID_WAYPOINT_INDEX);
return true;
}
void Waypoint::save (void) {
WaypointHeader header;
memset (header.mapName, 0, sizeof (header.mapName));
memset (header.author, 0, sizeof (header.author));
memset (header.header, 0, sizeof (header.header));
strcpy (header.header, FH_WAYPOINT);
strncpy (header.author, STRING (g_hostEntity->v.netname), cr::bufsize (header.author));
strncpy (header.mapName, engine.getMapName (), cr::bufsize (header.mapName));
header.mapName[31] = 0;
header.fileVersion = FV_WAYPOINT;
header.pointNumber = m_numWaypoints;
File fp (getWaypointFilename (), "wb");
// file was opened
if (fp.isValid ()) {
// write the waypoint header to the file...
fp.write (&header, sizeof (header), 1);
// save the waypoint paths...
for (int i = 0; i < m_numWaypoints; i++) {
fp.write (m_paths[i], sizeof (Path));
}
fp.close ();
}
else
logEntry (true, LL_ERROR, "Error writing '%s.pwf' waypoint file", engine.getMapName ());
}
const char *Waypoint::getWaypointFilename (bool isMemoryFile) {
static String buffer;
buffer.format ("%s%s%s.pwf", getDataDirectory (isMemoryFile), isEmptyStr (yb_wptsubfolder.str ()) ? "" : yb_wptsubfolder.str (), engine.getMapName ());
if (File::exists (buffer)) {
return buffer.chars ();
}
return format ("%s%s.pwf", getDataDirectory (isMemoryFile), engine.getMapName ());
}
float Waypoint::calculateTravelTime (float maxSpeed, const Vector &src, const Vector &origin) {
// this function returns 2D traveltime to a position
return (origin - src).length2D () / maxSpeed;
}
bool Waypoint::isReachable (Bot *bot, int index) {
// this function return whether bot able to reach index waypoint or not, depending on several factors.
if (!bot || !exists (index)) {
return false;
}
const Vector &src = bot->pev->origin;
const Vector &dst = m_paths[index]->origin;
// is the destination close enough?
if ((dst - src).lengthSq () >= cr::square (320.0f)) {
return false;
}
float ladderDist = (dst - src).length2D ();
TraceResult tr;
engine.testLine (src, dst, TRACE_IGNORE_MONSTERS, bot->ent (), &tr);
// if waypoint is visible from current position (even behind head)...
if (tr.flFraction >= 1.0f) {
// it's should be not a problem to reach waypoint inside water...
if (bot->pev->waterlevel == 2 || bot->pev->waterlevel == 3) {
return true;
}
// check for ladder
bool nonLadder = !(m_paths[index]->flags & FLAG_LADDER) || ladderDist > 16.0f;
// is dest waypoint higher than src? (62 is max jump height)
if (nonLadder && dst.z > src.z + 62.0f) {
return false; // can't reach this one
}
// is dest waypoint lower than src?
if (nonLadder && dst.z < src.z - 100.0f) {
return false; // can't reach this one
}
return true;
}
return false;
}
bool Waypoint::isNodeReacheable (const Vector &src, const Vector &destination) {
TraceResult tr;
float distance = (destination - src).length ();
// is the destination not close enough?
if (distance > g_autoPathDistance) {
return false;
}
// check if we go through a func_illusionary, in which case return false
engine.testHull (src, destination, TRACE_IGNORE_MONSTERS, head_hull, g_hostEntity, &tr);
if (!engine.isNullEntity (tr.pHit) && strcmp ("func_illusionary", STRING (tr.pHit->v.classname)) == 0) {
return false; // don't add pathwaypoints through func_illusionaries
}
// check if this waypoint is "visible"...
engine.testLine (src, destination, TRACE_IGNORE_MONSTERS, g_hostEntity, &tr);
// if waypoint is visible from current position (even behind head)...
if (tr.flFraction >= 1.0f || strncmp ("func_door", STRING (tr.pHit->v.classname), 9) == 0) {
// if it's a door check if nothing blocks behind
if (strncmp ("func_door", STRING (tr.pHit->v.classname), 9) == 0) {
engine.testLine (tr.vecEndPos, destination, TRACE_IGNORE_MONSTERS, tr.pHit, &tr);
if (tr.flFraction < 1.0f)
return false;
}
// check for special case of both waypoints being in water...
if (g_engfuncs.pfnPointContents (src) == CONTENTS_WATER && g_engfuncs.pfnPointContents (destination) == CONTENTS_WATER) {
return true; // then they're reachable each other
}
// is dest waypoint higher than src? (45 is max jump height)
if (destination.z > src.z + 45.0f) {
Vector sourceNew = destination;
Vector destinationNew = destination;
destinationNew.z = destinationNew.z - 50.0f; // straight down 50 units
engine.testLine (sourceNew, destinationNew, TRACE_IGNORE_MONSTERS, g_hostEntity, &tr);
// check if we didn't hit anything, if not then it's in mid-air
if (tr.flFraction >= 1.0) {
return false; // can't reach this one
}
}
// check if distance to ground drops more than step height at points between source and destination...
Vector direction = (destination - src).normalize (); // 1 unit long
Vector check = src, down = src;
down.z = down.z - 1000.0f; // straight down 1000 units
engine.testLine (check, down, TRACE_IGNORE_MONSTERS, g_hostEntity, &tr);
float lastHeight = tr.flFraction * 1000.0f; // height from ground
distance = (destination - check).length (); // distance from goal
while (distance > 10.0f) {
// move 10 units closer to the goal...
check = check + (direction * 10.0f);
down = check;
down.z = down.z - 1000.0f; // straight down 1000 units
engine.testLine (check, down, TRACE_IGNORE_MONSTERS, g_hostEntity, &tr);
float height = tr.flFraction * 1000.0f; // height from ground
// is the current height greater than the step height?
if (height < lastHeight - 18.0f) {
return false; // can't get there without jumping...
}
lastHeight = height;
distance = (destination - check).length (); // distance from goal
}
return true;
}
return false;
}
void Waypoint::rebuildVisibility (void) {
if (!m_redoneVisibility) {
return;
}
TraceResult tr;
uint8 res, shift;
for (m_visibilityIndex = 0; m_visibilityIndex < m_numWaypoints; m_visibilityIndex++) {
Vector sourceDuck = m_paths[m_visibilityIndex]->origin;
Vector sourceStand = m_paths[m_visibilityIndex]->origin;
if (m_paths[m_visibilityIndex]->flags & FLAG_CROUCH) {
sourceDuck.z += 12.0f;
sourceStand.z += 18.0f + 28.0f;
}
else {
sourceDuck.z += -18.0f + 12.0f;
sourceStand.z += 28.0f;
}
uint16 standCount = 0, crouchCount = 0;
for (int i = 0; i < m_numWaypoints; i++) {
// first check ducked visibility
Vector dest = m_paths[i]->origin;
engine.testLine (sourceDuck, dest, TRACE_IGNORE_MONSTERS, nullptr, &tr);
// check if line of sight to object is not blocked (i.e. visible)
if (tr.flFraction != 1.0f || tr.fStartSolid) {
res = 1;
}
else {
res = 0;
}
res <<= 1;
engine.testLine (sourceStand, dest, TRACE_IGNORE_MONSTERS, nullptr, &tr);
// check if line of sight to object is not blocked (i.e. visible)
if (tr.flFraction != 1.0f || tr.fStartSolid) {
res |= 1;
}
if (res != 0) {
dest = m_paths[i]->origin;
// first check ducked visibility
if (m_paths[i]->flags & FLAG_CROUCH) {
dest.z += 18.0f + 28.0f;
}
else {
dest.z += 28.0f;
}
engine.testLine (sourceDuck, dest, TRACE_IGNORE_MONSTERS, nullptr, &tr);
// check if line of sight to object is not blocked (i.e. visible)
if (tr.flFraction != 1.0f || tr.fStartSolid) {
res |= 2;
}
else {
res &= 1;
}
engine.testLine (sourceStand, dest, TRACE_IGNORE_MONSTERS, nullptr, &tr);
// check if line of sight to object is not blocked (i.e. visible)
if (tr.flFraction != 1.0f || tr.fStartSolid) {
res |= 1;
}
else {
res &= 2;
}
}
shift = (i % 4) << 1;
m_visLUT[m_visibilityIndex][i >> 2] &= ~(3 << shift);
m_visLUT[m_visibilityIndex][i >> 2] |= res << shift;
if (!(res & 2)) {
crouchCount++;
}
if (!(res & 1)) {
standCount++;
}
}
m_paths[m_visibilityIndex]->vis.crouch = crouchCount;
m_paths[m_visibilityIndex]->vis.stand = standCount;
}
m_redoneVisibility = false;
}
bool Waypoint::isVisible (int srcIndex, int destIndex) {
if (!exists (srcIndex) || !exists (destIndex)) {
return false;
}
uint8 res = m_visLUT[srcIndex][destIndex >> 2];
res >>= (destIndex % 4) << 1;
return !((res & 3) == 3);
}
bool Waypoint::isDuckVisible (int srcIndex, int destIndex) {
if (!exists (srcIndex) || !exists (destIndex)) {
return false;
}
uint8 res = m_visLUT[srcIndex][destIndex >> 2];
res >>= (destIndex % 4) << 1;
return !((res & 2) == 2);
}
bool Waypoint::isStandVisible (int srcIndex, int destIndex) {
if (!exists (srcIndex) || !exists (destIndex)) {
return false;
}
uint8 res = m_visLUT[srcIndex][destIndex >> 2];
res >>= (destIndex % 4) << 1;
return !((res & 1) == 1);
}
const char *Waypoint::getInformation (int id) {
// this function returns path information for waypoint pointed by id.
Path *path = m_paths[id];
// if this path is null, return
if (path == nullptr) {
return "\0";
}
bool jumpPoint = false;
// iterate through connections and find, if it's a jump path
for (int i = 0; i < MAX_PATH_INDEX; i++) {
// check if we got a valid connection
if (path->index[i] != INVALID_WAYPOINT_INDEX && (path->connectionFlags[i] & PATHFLAG_JUMP)) {
jumpPoint = true;
}
}
static char messageBuffer[MAX_PRINT_BUFFER];
sprintf (messageBuffer, "%s%s%s%s%s%s%s%s%s%s%s%s%s%s", (path->flags == 0 && !jumpPoint) ? " (none)" : "", (path->flags & FLAG_LIFT) ? " LIFT" : "", (path->flags & FLAG_CROUCH) ? " CROUCH" : "", (path->flags & FLAG_CROSSING) ? " CROSSING" : "", (path->flags & FLAG_CAMP) ? " CAMP" : "", (path->flags & FLAG_TF_ONLY) ? " TERRORIST" : "", (path->flags & FLAG_CF_ONLY) ? " CT" : "", (path->flags & FLAG_SNIPER) ? " SNIPER" : "", (path->flags & FLAG_GOAL) ? " GOAL" : "", (path->flags & FLAG_LADDER) ? " LADDER" : "", (path->flags & FLAG_RESCUE) ? " RESCUE" : "", (path->flags & FLAG_DOUBLEJUMP) ? " JUMPHELP" : "", (path->flags & FLAG_NOHOSTAGE) ? " NOHOSTAGE" : "", jumpPoint ? " JUMP" : "");
// return the message buffer
return messageBuffer;
}
void Waypoint::frame (void) {
// this function executes frame of waypoint operation code.
if (engine.isNullEntity (g_hostEntity)) {
return; // this function is only valid on listenserver, and in waypoint enabled mode.
}
float nearestDistance = 99999.0f;
int nearestIndex = INVALID_WAYPOINT_INDEX;
// check if it's time to add jump waypoint
if (m_learnJumpWaypoint) {
if (!m_endJumpPoint) {
if (g_hostEntity->v.button & IN_JUMP) {
push (9);
m_timeJumpStarted = engine.timebase ();
m_endJumpPoint = true;
}
else {
m_learnVelocity = g_hostEntity->v.velocity;
m_learnPosition = g_hostEntity->v.origin;
}
}
else if (((g_hostEntity->v.flags & FL_ONGROUND) || g_hostEntity->v.movetype == MOVETYPE_FLY) && m_timeJumpStarted + 0.1f < engine.timebase () && m_endJumpPoint) {
push (10);
m_learnJumpWaypoint = false;
m_endJumpPoint = false;
}
}
// check if it's a autowaypoint mode enabled
if (g_autoWaypoint && (g_hostEntity->v.flags & (FL_ONGROUND | FL_PARTIALGROUND))) {
// find the distance from the last used waypoint
float distance = (m_lastWaypoint - g_hostEntity->v.origin).lengthSq ();
if (distance > 16384.0f) {
// check that no other reachable waypoints are nearby...
for (int i = 0; i < m_numWaypoints; i++) {
if (isNodeReacheable (g_hostEntity->v.origin, m_paths[i]->origin)) {
distance = (m_paths[i]->origin - g_hostEntity->v.origin).lengthSq ();
if (distance < nearestDistance) {
nearestDistance = distance;
}
}
}
// make sure nearest waypoint is far enough away...
if (nearestDistance >= 16384.0f) {
push (0); // place a waypoint here
}
}
}
m_facingAtIndex = getFacingIndex ();
// reset the minimal distance changed before
nearestDistance = 999999.0f;
// now iterate through all waypoints in a map, and draw required ones
for (int i = 0; i < m_numWaypoints; i++) {
float distance = (m_paths[i]->origin - g_hostEntity->v.origin).length ();
// check if waypoint is whitin a distance, and is visible
if (distance < 512.0f && ((::isVisible (m_paths[i]->origin, g_hostEntity) && isInViewCone (m_paths[i]->origin, g_hostEntity)) || !isAlive (g_hostEntity) || distance < 128.0f)) {
// check the distance
if (distance < nearestDistance) {
nearestIndex = i;
nearestDistance = distance;
}
if (m_waypointDisplayTime[i] + 0.8f < engine.timebase ()) {
float nodeHeight = 0.0f;
// check the node height
if (m_paths[i]->flags & FLAG_CROUCH) {
nodeHeight = 36.0f;
}
else {
nodeHeight = 72.0f;
}
float nodeHalfHeight = nodeHeight * 0.5f;
// all waypoints are by default are green
Vector nodeColor;
// colorize all other waypoints
if (m_paths[i]->flags & FLAG_CAMP) {
nodeColor = Vector (0, 255, 255);
}
else if (m_paths[i]->flags & FLAG_GOAL) {
nodeColor = Vector (128, 0, 255);
}
else if (m_paths[i]->flags & FLAG_LADDER) {
nodeColor = Vector (128, 64, 0);
}
else if (m_paths[i]->flags & FLAG_RESCUE) {
nodeColor = Vector (255, 255, 255);
}
else {
nodeColor = Vector (0, 255, 0);
}
// colorize additional flags
Vector nodeFlagColor = Vector (-1, -1, -1);
// check the colors
if (m_paths[i]->flags & FLAG_SNIPER) {
nodeFlagColor = Vector (130, 87, 0);
}
else if (m_paths[i]->flags & FLAG_NOHOSTAGE) {
nodeFlagColor = Vector (255, 255, 255);
}
else if (m_paths[i]->flags & FLAG_TF_ONLY) {
nodeFlagColor = Vector (255, 0, 0);
}
else if (m_paths[i]->flags & FLAG_CF_ONLY) {
nodeFlagColor = Vector (0, 0, 255);
}
int nodeWidth = 14;
if (exists (m_facingAtIndex) && i == m_facingAtIndex) {
nodeWidth *= 2;
}
// draw node without additional flags
if (nodeFlagColor.x == -1) {
engine.drawLine (g_hostEntity, m_paths[i]->origin - Vector (0, 0, nodeHalfHeight), m_paths[i]->origin + Vector (0, 0, nodeHalfHeight), nodeWidth + 1, 0, static_cast <int> (nodeColor.x), static_cast <int> (nodeColor.y), static_cast <int> (nodeColor.z), 250, 0, 10);
}
// draw node with flags
else {
engine.drawLine (g_hostEntity, m_paths[i]->origin - Vector (0, 0, nodeHalfHeight), m_paths[i]->origin - Vector (0, 0, nodeHalfHeight - nodeHeight * 0.75f), nodeWidth, 0, static_cast <int> (nodeColor.x), static_cast <int> (nodeColor.y), static_cast <int> (nodeColor.z), 250, 0, 10); // draw basic path
engine.drawLine (g_hostEntity, m_paths[i]->origin - Vector (0, 0, nodeHalfHeight - nodeHeight * 0.75f), m_paths[i]->origin + Vector (0, 0, nodeHalfHeight), nodeWidth, 0, static_cast <int> (nodeFlagColor.x), static_cast <int> (nodeFlagColor.y), static_cast <int> (nodeFlagColor.z), 250, 0, 10); // draw additional path
}
m_waypointDisplayTime[i] = engine.timebase ();
}
}
}
if (nearestIndex == INVALID_WAYPOINT_INDEX) {
return;
}
// draw arrow to a some importaint waypoints
if (exists (m_findWPIndex) || exists (m_cacheWaypointIndex) || exists (m_facingAtIndex)) {
// check for drawing code
if (m_arrowDisplayTime + 0.5f < engine.timebase ()) {
// finding waypoint - pink arrow
if (m_findWPIndex != INVALID_WAYPOINT_INDEX) {
engine.drawLine (g_hostEntity, g_hostEntity->v.origin, m_paths[m_findWPIndex]->origin, 10, 0, 128, 0, 128, 200, 0, 5, DRAW_ARROW);
}
// cached waypoint - yellow arrow
if (m_cacheWaypointIndex != INVALID_WAYPOINT_INDEX) {
engine.drawLine (g_hostEntity, g_hostEntity->v.origin, m_paths[m_cacheWaypointIndex]->origin, 10, 0, 255, 255, 0, 200, 0, 5, DRAW_ARROW);
}
// waypoint user facing at - white arrow
if (m_facingAtIndex != INVALID_WAYPOINT_INDEX) {
engine.drawLine (g_hostEntity, g_hostEntity->v.origin, m_paths[m_facingAtIndex]->origin, 10, 0, 255, 255, 255, 200, 0, 5, DRAW_ARROW);
}
m_arrowDisplayTime = engine.timebase ();
}
}
// create path pointer for faster access
Path *path = m_paths[nearestIndex];
// draw a paths, camplines and danger directions for nearest waypoint
if (nearestDistance <= 56.0f && m_pathDisplayTime <= engine.timebase ()) {
m_pathDisplayTime = engine.timebase () + 1.0f;
// draw the camplines
if (path->flags & FLAG_CAMP) {
Vector campSourceOrigin = path->origin + Vector (0.0f, 0.0f, 36.0f);
// check if it's a source
if (path->flags & FLAG_CROUCH) {
campSourceOrigin = path->origin + Vector (0.0f, 0.0f, 18.0f);
}
Vector campStartOrigin = Vector (path->campStartX, path->campStartY, campSourceOrigin.z); // camp start
Vector campEndOrigin = Vector (path->campEndX, path->campEndY, campSourceOrigin.z); // camp end
// draw it now
engine.drawLine (g_hostEntity, campSourceOrigin, campStartOrigin, 10, 0, 255, 0, 0, 200, 0, 10);
engine.drawLine (g_hostEntity, campSourceOrigin, campEndOrigin, 10, 0, 255, 0, 0, 200, 0, 10);
}
// draw the connections
for (int i = 0; i < MAX_PATH_INDEX; i++) {
if (path->index[i] == INVALID_WAYPOINT_INDEX) {
continue;
}
// jump connection
if (path->connectionFlags[i] & PATHFLAG_JUMP) {
engine.drawLine (g_hostEntity, path->origin, m_paths[path->index[i]]->origin, 5, 0, 255, 0, 128, 200, 0, 10);
}
else if (isConnected (path->index[i], nearestIndex)) { // twoway connection
engine.drawLine (g_hostEntity, path->origin, m_paths[path->index[i]]->origin, 5, 0, 255, 255, 0, 200, 0, 10);
}
else { // oneway connection
engine.drawLine (g_hostEntity, path->origin, m_paths[path->index[i]]->origin, 5, 0, 250, 250, 250, 200, 0, 10);
}
}
// now look for oneway incoming connections
for (int i = 0; i < m_numWaypoints; i++) {
if (isConnected (m_paths[i]->pathNumber, path->pathNumber) && !isConnected (path->pathNumber, m_paths[i]->pathNumber)) {
engine.drawLine (g_hostEntity, path->origin, m_paths[i]->origin, 5, 0, 0, 192, 96, 200, 0, 10);
}
}
// draw the radius circle
Vector origin = (path->flags & FLAG_CROUCH) ? path->origin : path->origin - Vector (0.0f, 0.0f, 18.0f);
// if radius is nonzero, draw a full circle
if (path->radius > 0.0f) {
float sqr = cr::sqrtf (path->radius * path->radius * 0.5f);
engine.drawLine (g_hostEntity, origin + Vector (path->radius, 0.0f, 0.0f), origin + Vector (sqr, -sqr, 0.0f), 5, 0, 0, 0, 255, 200, 0, 10);
engine.drawLine (g_hostEntity, origin + Vector (sqr, -sqr, 0.0f), origin + Vector (0.0f, -path->radius, 0.0f), 5, 0, 0, 0, 255, 200, 0, 10);
engine.drawLine (g_hostEntity, origin + Vector (0.0f, -path->radius, 0.0f), origin + Vector (-sqr, -sqr, 0.0f), 5, 0, 0, 0, 255, 200, 0, 10);
engine.drawLine (g_hostEntity, origin + Vector (-sqr, -sqr, 0.0f), origin + Vector (-path->radius, 0.0f, 0.0f), 5, 0, 0, 0, 255, 200, 0, 10);
engine.drawLine (g_hostEntity, origin + Vector (-path->radius, 0.0f, 0.0f), origin + Vector (-sqr, sqr, 0.0f), 5, 0, 0, 0, 255, 200, 0, 10);
engine.drawLine (g_hostEntity, origin + Vector (-sqr, sqr, 0.0f), origin + Vector (0.0f, path->radius, 0.0f), 5, 0, 0, 0, 255, 200, 0, 10);
engine.drawLine (g_hostEntity, origin + Vector (0.0f, path->radius, 0.0f), origin + Vector (sqr, sqr, 0.0f), 5, 0, 0, 0, 255, 200, 0, 10);
engine.drawLine (g_hostEntity, origin + Vector (sqr, sqr, 0.0f), origin + Vector (path->radius, 0.0f, 0.0f), 5, 0, 0, 0, 255, 200, 0, 10);
}
else {
float sqr = cr::sqrtf (32.0f);
engine.drawLine (g_hostEntity, origin + Vector (sqr, -sqr, 0.0f), origin + Vector (-sqr, sqr, 0.0f), 5, 0, 255, 0, 0, 200, 0, 10);
engine.drawLine (g_hostEntity, origin + Vector (-sqr, -sqr, 0.0f), origin + Vector (sqr, sqr, 0.0f), 5, 0, 255, 0, 0, 200, 0, 10);
}
// draw the danger directions
if (!m_waypointsChanged) {
if ((g_experienceData + (nearestIndex * m_numWaypoints) + nearestIndex)->team0DangerIndex != INVALID_WAYPOINT_INDEX && engine.getTeam (g_hostEntity) == TEAM_TERRORIST) {
engine.drawLine (g_hostEntity, path->origin, m_paths[(g_experienceData + (nearestIndex * m_numWaypoints) + nearestIndex)->team0DangerIndex]->origin, 15, 0, 255, 0, 0, 200, 0, 10, DRAW_ARROW); // draw a red arrow to this index's danger point
}
if ((g_experienceData + (nearestIndex * m_numWaypoints) + nearestIndex)->team1DangerIndex != INVALID_WAYPOINT_INDEX && engine.getTeam (g_hostEntity) == TEAM_COUNTER) {
engine.drawLine (g_hostEntity, path->origin, m_paths[(g_experienceData + (nearestIndex * m_numWaypoints) + nearestIndex)->team1DangerIndex]->origin, 15, 0, 0, 0, 255, 200, 0, 10, DRAW_ARROW); // draw a blue arrow to this index's danger point
}
}
// display some information
char tempMessage[4096];
// show the information about that point
int length = sprintf (tempMessage,
"\n\n\n\n Waypoint Information:\n\n"
" Waypoint %d of %d, Radius: %.1f\n"
" Flags: %s\n\n",
nearestIndex, m_numWaypoints, path->radius, getInformation (nearestIndex));
// if waypoint is not changed display experience also
if (!m_waypointsChanged) {
int dangerIndexCT = (g_experienceData + nearestIndex * m_numWaypoints + nearestIndex)->team1DangerIndex;
int dangerIndexT = (g_experienceData + nearestIndex * m_numWaypoints + nearestIndex)->team0DangerIndex;
length += sprintf (&tempMessage[length],
" Experience Info:\n"
" CT: %d / %d\n"
" T: %d / %d\n",
dangerIndexCT, dangerIndexCT != INVALID_WAYPOINT_INDEX ? (g_experienceData + nearestIndex * m_numWaypoints + dangerIndexCT)->team1Damage : 0, dangerIndexT, dangerIndexT != INVALID_WAYPOINT_INDEX ? (g_experienceData + nearestIndex * m_numWaypoints + dangerIndexT)->team0Damage : 0);
}
// check if we need to show the cached point index
if (m_cacheWaypointIndex != INVALID_WAYPOINT_INDEX) {
length += sprintf (&tempMessage[length],
"\n Cached Waypoint Information:\n\n"
" Waypoint %d of %d, Radius: %.1f\n"
" Flags: %s\n",
m_cacheWaypointIndex, m_numWaypoints, m_paths[m_cacheWaypointIndex]->radius, getInformation (m_cacheWaypointIndex));
}
// check if we need to show the facing point index
if (m_facingAtIndex != INVALID_WAYPOINT_INDEX) {
length += sprintf (&tempMessage[length],
"\n Facing Waypoint Information:\n\n"
" Waypoint %d of %d, Radius: %.1f\n"
" Flags: %s\n",
m_facingAtIndex, m_numWaypoints, m_paths[m_facingAtIndex]->radius, getInformation (m_facingAtIndex));
}
// draw entire message
MessageWriter (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, Vector::null (), g_hostEntity)
.writeByte (TE_TEXTMESSAGE)
.writeByte (4) // channel
.writeShort (MessageWriter::fs16 (0, 1 << 13)) // x
.writeShort (MessageWriter::fs16 (0, 1 << 13)) // y
.writeByte (0) // effect
.writeByte (255) // r1
.writeByte (255) // g1
.writeByte (255) // b1
.writeByte (1) // a1
.writeByte (255) // r2
.writeByte (255) // g2
.writeByte (255) // b2
.writeByte (255) // a2
.writeShort (0) // fadeintime
.writeShort (0) // fadeouttime
.writeShort (MessageWriter::fu16 (1.1f, 1 << 8)) // holdtime
.writeString (tempMessage);
}
}
bool Waypoint::isConnected (int index) {
for (int i = 0; i < m_numWaypoints; i++) {
if (i == index) {
continue;
}
for (int j = 0; j < MAX_PATH_INDEX; j++) {
if (m_paths[i]->index[j] == index) {
return true;
}
}
}
return false;
}
bool Waypoint::checkNodes (void) {
int terrPoints = 0;
int ctPoints = 0;
int goalPoints = 0;
int rescuePoints = 0;
int i, j;
for (i = 0; i < m_numWaypoints; i++) {
int connections = 0;
for (j = 0; j < MAX_PATH_INDEX; j++) {
if (m_paths[i]->index[j] != INVALID_WAYPOINT_INDEX) {
if (m_paths[i]->index[j] > m_numWaypoints) {
logEntry (true, LL_WARNING, "Waypoint %d connected with invalid Waypoint #%d!", i, m_paths[i]->index[j]);
return false;
}
connections++;
break;
}
}
if (connections == 0) {
if (!isConnected (i)) {
logEntry (true, LL_WARNING, "Waypoint %d isn't connected with any other Waypoint!", i);
return false;
}
}
if (m_paths[i]->pathNumber != i) {
logEntry (true, LL_WARNING, "Waypoint %d pathnumber differs from index!", i);
return false;
}
if (m_paths[i]->flags & FLAG_CAMP) {
if (m_paths[i]->campEndX == 0.0f && m_paths[i]->campEndY == 0.0f) {
logEntry (true, LL_WARNING, "Waypoint %d Camp-Endposition not set!", i);
return false;
}
}
else if (m_paths[i]->flags & FLAG_TF_ONLY) {
terrPoints++;
}
else if (m_paths[i]->flags & FLAG_CF_ONLY) {
ctPoints++;
}
else if (m_paths[i]->flags & FLAG_GOAL) {
goalPoints++;
}
else if (m_paths[i]->flags & FLAG_RESCUE) {
rescuePoints++;
}
for (int k = 0; k < MAX_PATH_INDEX; k++) {
if (m_paths[i]->index[k] != INVALID_WAYPOINT_INDEX) {
if (!exists (m_paths[i]->index[k])) {
logEntry (true, LL_WARNING, "Waypoint %d - Pathindex %d out of Range!", i, k);
g_engfuncs.pfnSetOrigin (g_hostEntity, m_paths[i]->origin);
g_waypointOn = true;
g_editNoclip = true;
return false;
}
else if (m_paths[i]->index[k] == i) {
logEntry (true, LL_WARNING, "Waypoint %d - Pathindex %d points to itself!", i, k);
if (g_waypointOn && !engine.isDedicated ()) {
g_engfuncs.pfnSetOrigin (g_hostEntity, m_paths[i]->origin);
g_waypointOn = true;
g_editNoclip = true;
}
return false;
}
}
}
}
if (g_mapFlags & MAP_CS) {
if (rescuePoints == 0) {
logEntry (true, LL_WARNING, "You didn't set a Rescue Point!");
return false;
}
}
if (terrPoints == 0) {
logEntry (true, LL_WARNING, "You didn't set any Terrorist Important Point!");
return false;
}
else if (ctPoints == 0) {
logEntry (true, LL_WARNING, "You didn't set any CT Important Point!");
return false;
}
else if (goalPoints == 0) {
logEntry (true, LL_WARNING, "You didn't set any Goal Point!");
return false;
}
// perform DFS instead of floyd-warshall, this shit speedup this process in a bit
PathWalk walk;
Array <bool> visited;
visited.reserve (m_numWaypoints);
// first check incoming connectivity, initialize the "visited" table
for (i = 0; i < m_numWaypoints; i++) {
visited[i] = false;
}
walk.push (0); // always check from waypoint number 0
while (!walk.empty ()) {
// pop a node from the stack
const int current = walk.first ();
walk.shift ();
visited[current] = true;
for (j = 0; j < MAX_PATH_INDEX; j++) {
int index = m_paths[current]->index[j];
// skip this waypoint as it's already visited
if (exists (index) && !visited[index]) {
visited[index] = true;
walk.push (index);
}
}
}
for (i = 0; i < m_numWaypoints; i++) {
if (!visited[i]) {
logEntry (true, LL_WARNING, "Path broken from Waypoint #0 to Waypoint #%d!", i);
if (g_waypointOn && !engine.isDedicated ()) {
g_engfuncs.pfnSetOrigin (g_hostEntity, m_paths[i]->origin);
g_waypointOn = true;
g_editNoclip = true;
}
return false;
}
}
// then check outgoing connectivity
Array <IntArray> outgoingPaths; // store incoming paths for speedup
outgoingPaths.reserve (m_numWaypoints);
for (i = 0; i < m_numWaypoints; i++) {
outgoingPaths[i].reserve (m_numWaypoints + 1);
for (j = 0; j < MAX_PATH_INDEX; j++) {
if (exists (m_paths[i]->index[j])) {
outgoingPaths[m_paths[i]->index[j]].push (i);
}
}
}
// initialize the "visited" table
for (i = 0; i < m_numWaypoints; i++) {
visited[i] = false;
}
walk.clear ();
walk.push (0); // always check from waypoint number 0
while (!walk.empty ()) {
const int current = walk.first (); // pop a node from the stack
walk.shift ();
for (auto &outgoing : outgoingPaths[current]) {
if (visited[outgoing]) {
continue; // skip this waypoint as it's already visited
}
visited[outgoing] = true;
walk.push (outgoing);
}
}
for (i = 0; i < m_numWaypoints; i++) {
if (!visited[i]) {
logEntry (true, LL_WARNING, "Path broken from Waypoint #%d to Waypoint #0!", i);
if (g_waypointOn && !engine.isDedicated ()) {
g_engfuncs.pfnSetOrigin (g_hostEntity, m_paths[i]->origin);
g_waypointOn = true;
g_editNoclip = true;
}
return false;
}
}
return true;
}
void Waypoint::initPathMatrix (void) {
int i, j, k;
delete[] m_distMatrix;
delete[] m_pathMatrix;
m_distMatrix = nullptr;
m_pathMatrix = nullptr;
m_distMatrix = new int[m_numWaypoints * m_numWaypoints];
m_pathMatrix = new int[m_numWaypoints * m_numWaypoints];
if (loadPathMatrix ()) {
return; // matrix loaded from file
}
for (i = 0; i < m_numWaypoints; i++) {
for (j = 0; j < m_numWaypoints; j++) {
*(m_distMatrix + i * m_numWaypoints + j) = 999999;
*(m_pathMatrix + i * m_numWaypoints + j) = INVALID_WAYPOINT_INDEX;
}
}
for (i = 0; i < m_numWaypoints; i++) {
for (j = 0; j < MAX_PATH_INDEX; j++) {
if (m_paths[i]->index[j] >= 0 && m_paths[i]->index[j] < m_numWaypoints) {
*(m_distMatrix + (i * m_numWaypoints) + m_paths[i]->index[j]) = m_paths[i]->distances[j];
*(m_pathMatrix + (i * m_numWaypoints) + m_paths[i]->index[j]) = m_paths[i]->index[j];
}
}
}
for (i = 0; i < m_numWaypoints; i++) {
*(m_distMatrix + (i * m_numWaypoints) + i) = 0;
}
for (k = 0; k < m_numWaypoints; k++) {
for (i = 0; i < m_numWaypoints; i++) {
for (j = 0; j < m_numWaypoints; j++) {
if (*(m_distMatrix + (i * m_numWaypoints) + k) + *(m_distMatrix + (k * m_numWaypoints) + j) < (*(m_distMatrix + (i * m_numWaypoints) + j))) {
*(m_distMatrix + (i * m_numWaypoints) + j) = *(m_distMatrix + (i * m_numWaypoints) + k) + *(m_distMatrix + (k * m_numWaypoints) + j);
*(m_pathMatrix + (i * m_numWaypoints) + j) = *(m_pathMatrix + (i * m_numWaypoints) + k);
}
}
}
}
// save path matrix to file for faster access
savePathMatrix ();
}
void Waypoint::savePathMatrix (void) {
if (m_numWaypoints < 1 || m_waypointsChanged) {
return;
}
File fp (format ("%slearned/%s.pmt", getDataDirectory (), engine.getMapName ()), "wb");
// unable to open file
if (!fp.isValid ()) {
logEntry (false, LL_FATAL, "Failed to open file for writing");
return;
}
ExtensionHeader header;
memset (header.header, 0, sizeof (header.header));
strcpy (header.header, FH_MATRIX);
header.fileVersion = FV_MATRIX;
header.pointNumber = m_numWaypoints;
// write header info
fp.write (&header, sizeof (ExtensionHeader));
// write path & distance matrix
fp.write (m_pathMatrix, sizeof (int), m_numWaypoints * m_numWaypoints);
fp.write (m_distMatrix, sizeof (int), m_numWaypoints * m_numWaypoints);
// and close the file
fp.close ();
}
bool Waypoint::loadPathMatrix (void) {
File fp (format ("%slearned/%s.pmt", getDataDirectory (), engine.getMapName ()), "rb");
// file doesn't exists return false
if (!fp.isValid ()) {
return false;
}
ExtensionHeader header;
memset (&header, 0, sizeof (header));
// read number of waypoints
if (fp.read (&header, sizeof (ExtensionHeader)) == 0) {
fp.close ();
return false;
}
if (header.pointNumber != m_numWaypoints || header.fileVersion != FV_MATRIX) {
logEntry (true, LL_WARNING, "Pathmatrix damaged (wrong version, or not for this map). Pathmatrix will be rebuilt.");
fp.close ();
return false;
}
// read path & distance matrixes
if (fp.read (m_pathMatrix, sizeof (int), m_numWaypoints * m_numWaypoints) == 0) {
fp.close ();
return false;
}
if (fp.read (m_distMatrix, sizeof (int), m_numWaypoints * m_numWaypoints) == 0) {
fp.close ();
return false;
}
fp.close (); // and close the file
return true;
}
int Waypoint::getPathDist (int srcIndex, int destIndex) {
if (!exists (srcIndex) || !exists (destIndex)) {
return 1;
}
return *(m_distMatrix + (srcIndex * m_numWaypoints) + destIndex);
}
void Waypoint::setVisited (int index) {
if (!exists (index)) {
return;
}
if (!isVisited (index) && (m_paths[index]->flags & FLAG_GOAL))
m_visitedGoals.push (index);
}
void Waypoint::clearVisited (void) {
m_visitedGoals.clear ();
}
bool Waypoint::isVisited (int index) {
for (auto &visited : m_visitedGoals) {
if (visited == index) {
return true;
}
}
return false;
}
void Waypoint::addBasic (void) {
// this function creates basic waypoint types on map
edict_t *ent = nullptr;
// first of all, if map contains ladder points, create it
while (!engine.isNullEntity (ent = g_engfuncs.pfnFindEntityByString (ent, "classname", "func_ladder"))) {
Vector ladderLeft = ent->v.absmin;
Vector ladderRight = ent->v.absmax;
ladderLeft.z = ladderRight.z;
TraceResult tr;
Vector up, down, front, back;
Vector diff = ((ladderLeft - ladderRight) ^ Vector (0.0f, 0.0f, 0.0f)).normalize () * 15.0f;
front = back = engine.getAbsPos (ent);
front = front + diff; // front
back = back - diff; // back
up = down = front;
down.z = ent->v.absmax.z;
engine.testHull (down, up, TRACE_IGNORE_MONSTERS, point_hull, nullptr, &tr);
if (g_engfuncs.pfnPointContents (up) == CONTENTS_SOLID || tr.flFraction != 1.0f) {
up = down = back;
down.z = ent->v.absmax.z;
}
engine.testHull (down, up - Vector (0.0f, 0.0f, 1000.0f), TRACE_IGNORE_MONSTERS, point_hull, nullptr, &tr);
up = tr.vecEndPos;
Vector point = up + Vector (0.0f, 0.0f, 39.0f);
m_isOnLadder = true;
do {
if (getNearestNoBuckets (point, 50.0f) == INVALID_WAYPOINT_INDEX) {
push (3, point);
}
point.z += 160;
} while (point.z < down.z - 40.0f);
point = down + Vector (0.0f, 0.0f, 38.0f);
if (getNearestNoBuckets (point, 50.0f) == INVALID_WAYPOINT_INDEX) {
push (3, point);
}
m_isOnLadder = false;
}
auto autoCreateForEntity = [](int type, const char *entity) {
edict_t *ent = nullptr;
while (!engine.isNullEntity (ent = g_engfuncs.pfnFindEntityByString (ent, "classname", entity))) {
const Vector &pos = engine.getAbsPos (ent);
if (waypoints.getNearestNoBuckets (pos, 50.0f) == INVALID_WAYPOINT_INDEX) {
waypoints.push (type, pos);
}
}
};
autoCreateForEntity (0, "info_player_deathmatch"); // then terrortist spawnpoints
autoCreateForEntity (0, "info_player_start"); // then add ct spawnpoints
autoCreateForEntity (0, "info_vip_start"); // then vip spawnpoint
autoCreateForEntity (0, "armoury_entity"); // weapons on the map ?
autoCreateForEntity (4, "func_hostage_rescue"); // hostage rescue zone
autoCreateForEntity (4, "info_hostage_rescue"); // hostage rescue zone (same as above)
autoCreateForEntity (100, "func_bomb_target"); // bombspot zone
autoCreateForEntity (100, "info_bomb_target"); // bombspot zone (same as above)
autoCreateForEntity (100, "hostage_entity"); // hostage entities
autoCreateForEntity (100, "func_vip_safetyzone"); // vip rescue (safety) zone
autoCreateForEntity (100, "func_escapezone"); // terrorist escape zone
}
void Waypoint::eraseFromDisk (void) {
// this function removes waypoint file from the hard disk
StringArray forErase;
const char *map = engine.getMapName ();
bots.kickEveryone (true);
// if we're delete waypoint, delete all corresponding to it files
forErase.push (format ("%s%s.pwf", getDataDirectory (), map)); // waypoint itself
forErase.push (format ("%slearned/%s.exp", getDataDirectory (), map)); // corresponding to waypoint experience
forErase.push (format ("%slearned/%s.vis", getDataDirectory (), map)); // corresponding to waypoint vistable
forErase.push (format ("%slearned/%s.pmt", getDataDirectory (), map)); // corresponding to waypoint path matrix
for (auto &item : forErase) {
if (File::exists (const_cast <char *> (item.chars ()))) {
_unlink (item.chars ());
logEntry (true, LL_DEFAULT, "File %s, has been deleted from the hard disk", item.chars ());
}
else {
logEntry (true, LL_ERROR, "Unable to open %s", item.chars ());
}
}
init (); // reintialize points
}
const char *Waypoint::getDataDirectory (bool isMemoryFile) {
static String buffer;
if (isMemoryFile) {
buffer.assign ("addons/yapb/data/");
}
else {
buffer.format ("%s/addons/yapb/data/", engine.getModName ());
}
return buffer.chars ();
}
void Waypoint::setBombPos (bool reset, const Vector &pos) {
// this function stores the bomb position as a vector
if (reset) {
m_bombPos.nullify ();
g_bombPlanted = false;
return;
}
if (!pos.empty ()) {
m_bombPos = pos;
return;
}
edict_t *ent = nullptr;
while (!engine.isNullEntity (ent = g_engfuncs.pfnFindEntityByString (ent, "classname", "grenade"))) {
if (strcmp (STRING (ent->v.model) + 9, "c4.mdl") == 0) {
m_bombPos = engine.getAbsPos (ent);
break;
}
}
}
void Waypoint::startLearnJump (void) {
m_learnJumpWaypoint = true;
}
void Waypoint::setSearchIndex (int index) {
m_findWPIndex = index;
if (exists (m_findWPIndex)) {
engine.print ("Showing Direction to Waypoint #%d", m_findWPIndex);
}
else {
m_findWPIndex = INVALID_WAYPOINT_INDEX;
}
}
Waypoint::Waypoint (void) {
cleanupPathMemory ();
memset (m_visLUT, 0, sizeof (m_visLUT));
memset (m_waypointDisplayTime, 0, sizeof (m_waypointDisplayTime));
memset (m_waypointLightLevel, 0, sizeof (m_waypointLightLevel));
memset (m_infoBuffer, 0, sizeof (m_infoBuffer));
m_waypointPaths = false;
m_endJumpPoint = false;
m_redoneVisibility = false;
m_learnJumpWaypoint = false;
m_waypointsChanged = false;
m_timeJumpStarted = 0.0f;
m_lastJumpWaypoint = INVALID_WAYPOINT_INDEX;
m_cacheWaypointIndex = INVALID_WAYPOINT_INDEX;
m_findWPIndex = INVALID_WAYPOINT_INDEX;
m_facingAtIndex = INVALID_WAYPOINT_INDEX;
m_visibilityIndex = 0;
m_loadTries = 0;
m_numWaypoints = 0;
m_isOnLadder = false;
m_terrorPoints.clear ();
m_ctPoints.clear ();
m_goalPoints.clear ();
m_campPoints.clear ();
m_rescuePoints.clear ();
m_sniperPoints.clear ();
m_distMatrix = nullptr;
m_pathMatrix = nullptr;
for (int i = 0; i < MAX_WAYPOINTS; i++) {
m_paths[i] = nullptr;
}
}
Waypoint::~Waypoint (void) {
cleanupPathMemory ();
delete[] m_distMatrix;
delete[] m_pathMatrix;
m_distMatrix = nullptr;
m_pathMatrix = nullptr;
for (int i = 0; i < MAX_WAYPOINTS; i++) {
m_paths[i] = nullptr;
}
}
void Waypoint::closeSocket (int sock) {
#if defined(PLATFORM_WIN32)
if (sock != -1) {
closesocket (sock);
}
WSACleanup ();
#else
if (sock != -1)
close (sock);
#endif
}
WaypointDownloadError Waypoint::downloadWaypoint (void) {
#if defined(PLATFORM_WIN32)
WORD requestedVersion = MAKEWORD (1, 1);
WSADATA wsaData;
int wsa = WSAStartup (requestedVersion, &wsaData);
if (wsa != 0) {
return WDE_SOCKET_ERROR;
}
#endif
hostent *host = gethostbyname (yb_waypoint_autodl_host.str ());
if (host == nullptr) {
return WDE_SOCKET_ERROR;
}
auto socketHandle = static_cast <int> (socket (AF_INET, SOCK_STREAM, 0));
if (socketHandle < 0) {
closeSocket (socketHandle);
return WDE_SOCKET_ERROR;
}
sockaddr_in dest;
timeval timeout;
timeout.tv_sec = 5;
timeout.tv_usec = 0;
int result = setsockopt (socketHandle, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof (timeout));
if (result < 0) {
closeSocket (socketHandle);
return WDE_SOCKET_ERROR;
}
result = setsockopt (socketHandle, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof (timeout));
if (result < 0) {
closeSocket (socketHandle);
return WDE_SOCKET_ERROR;
}
memset (&dest, 0, sizeof (dest));
dest.sin_family = AF_INET;
dest.sin_port = htons (80);
dest.sin_addr.s_addr = inet_addr (inet_ntoa (*((struct in_addr *)host->h_addr)));
if (connect (socketHandle, (struct sockaddr *)&dest, (int)sizeof (dest)) == -1) {
closeSocket (socketHandle);
return WDE_CONNECT_ERROR;
}
String request;
request.format ("GET /wpdb/%s.pwf HTTP/1.0\r\nAccept: */*\r\nUser-Agent: YaPB/%s\r\nHost: %s\r\n\r\n", engine.getMapName (), PRODUCT_VERSION, yb_waypoint_autodl_host.str ());
if (send (socketHandle, request.chars (), static_cast <int> (request.length () + 1), 0) < 1) {
closeSocket (socketHandle);
return WDE_SOCKET_ERROR;
}
const int ChunkSize = MAX_PRINT_BUFFER;
char buffer[ChunkSize] = { 0, };
bool finished = false;
int recvPosition = 0;
int symbolsInLine = 0;
// scan for the end of the header
while (!finished && recvPosition < ChunkSize) {
if (recv (socketHandle, &buffer[recvPosition], 1, 0) == 0) {
finished = true;
}
// ugly, but whatever
if (recvPosition > 2 && buffer[recvPosition - 2] == '4' && buffer[recvPosition - 1] == '0' && buffer[recvPosition] == '4') {
closeSocket (socketHandle);
return WDE_NOTFOUND_ERROR;
}
switch (buffer[recvPosition]) {
case '\r':
break;
case '\n':
if (symbolsInLine == 0)
finished = true;
symbolsInLine = 0;
break;
default:
symbolsInLine++;
break;
}
recvPosition++;
}
File fp (waypoints.getWaypointFilename (), "wb");
if (!fp.isValid ()) {
closeSocket (socketHandle);
return WDE_SOCKET_ERROR;
}
int recvSize = 0;
do {
recvSize = recv (socketHandle, buffer, ChunkSize, 0);
if (recvSize > 0) {
fp.write (buffer, recvSize);
fp.flush ();
}
} while (recvSize != 0);
fp.close ();
closeSocket (socketHandle);
return WDE_NOERROR;
}
void Waypoint::initBuckets (void) {
m_numWaypoints = 0;
for (int x = 0; x < MAX_WAYPOINT_BUCKET_MAX; x++) {
for (int y = 0; y < MAX_WAYPOINT_BUCKET_MAX; y++) {
for (int z = 0; z < MAX_WAYPOINT_BUCKET_MAX; z++) {
m_buckets[x][y][z].reserve (MAX_WAYPOINT_BUCKET_WPTS);
m_buckets[x][y][z].clear ();
}
}
}
}
void Waypoint::addToBucket (const Vector &pos, int index) {
const Bucket &bucket = locateBucket (pos);
m_buckets[bucket.x][bucket.y][bucket.z].push (index);
}
void Waypoint::eraseFromBucket (const Vector &pos, int index) {
const Bucket &bucket = locateBucket (pos);
IntArray &data = m_buckets[bucket.x][bucket.y][bucket.z];
for (size_t i = 0; i < data.length (); i++) {
if (data[i] == index) {
data.erase (i, 1);
break;
}
}
}
Waypoint::Bucket Waypoint::locateBucket (const Vector &pos) {
constexpr float size = 4096.0f;
return {
cr::abs (static_cast <int> ((pos.x + size) / MAX_WAYPOINT_BUCKET_SIZE)),
cr::abs (static_cast <int> ((pos.y + size) / MAX_WAYPOINT_BUCKET_SIZE)),
cr::abs (static_cast <int> ((pos.z + size) / MAX_WAYPOINT_BUCKET_SIZE))
};
}
IntArray &Waypoint::getWaypointsInBucket (const Vector &pos) {
const Bucket &bucket = locateBucket (pos);
return m_buckets[bucket.x][bucket.y][bucket.z];
}