2928 lines
96 KiB
C++
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];
|
|
}
|