318 lines
8.3 KiB
C
318 lines
8.3 KiB
C
/*
|
|
Copyright 2016-2018 Marco "eukara" Hladik
|
|
|
|
MIT LICENSE
|
|
|
|
Permission is hereby granted, free of charge, to any person
|
|
obtaining a copy of this software and associated documentation
|
|
files (the "Software"), to deal in the Software without
|
|
restriction, including without limitation the rights to use,
|
|
copy, modify, merge, publish, distribute, sublicense, and/or
|
|
sell copies of the Software, and to permit persons to whom the
|
|
Software is furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be
|
|
included in all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
#define COST_INFINITE 999999999999
|
|
|
|
enumflags {
|
|
WP_JUMP, //also implies that the bot must first go behind the wp...
|
|
WP_CLIMB,
|
|
WP_CROUCH,
|
|
WP_USE
|
|
};
|
|
|
|
typedef struct waypoint_s {
|
|
vector org;
|
|
float flRadius; //used for picking the closest waypoint. aka proximity weight. also relaxes routes inside the area.
|
|
struct wpneighbour_s
|
|
{
|
|
int node;
|
|
float linkcost;
|
|
int iFlags;
|
|
} *neighbour;
|
|
int iNeighbours;
|
|
} waypoint_t;
|
|
|
|
static waypoint_t *waypoints;
|
|
static int iWaypoints;
|
|
static int bWaypointsLoaded;
|
|
|
|
static void() Way_WipeWaypoints=
|
|
{
|
|
for (int i = 0; i < iWaypoints; i++)
|
|
memfree(waypoints[i].neighbour);
|
|
memfree(waypoints);
|
|
iWaypoints = 0;
|
|
}
|
|
|
|
void Way_DumpWaypoints( string filename )
|
|
{
|
|
float file = fopen(filename, FILE_WRITE);
|
|
if (file < 0)
|
|
{
|
|
print("RT_DumpWaypoints: unable to open ", filename, "\n");
|
|
return;
|
|
}
|
|
fputs(file, sprintf("%i\n", iWaypoints));
|
|
for(int i = 0i; i < iWaypoints; i++)
|
|
{
|
|
fputs(file, sprintf("%v %f %i\n", waypoints[i].org, waypoints[i].flRadius, waypoints[i].iNeighbours));
|
|
for(int j = 0i; j < waypoints[i].iNeighbours; j++)
|
|
fputs(file, sprintf(" %i %f %#x\n", waypoints[i].neighbour[j].node, waypoints[i].neighbour[j].linkcost, waypoints[i].neighbour[j].iFlags));
|
|
}
|
|
fclose(file);
|
|
}
|
|
|
|
void Way_ReadWaypoints( string filename )
|
|
{
|
|
float file = fopen(filename, FILE_READ);
|
|
bWaypointsLoaded = TRUE;
|
|
if (file < 0)
|
|
{
|
|
print("Way_DumpWaypoints: unable to open ", filename, "\n");
|
|
return;
|
|
}
|
|
Way_WipeWaypoints();
|
|
|
|
tokenize(fgets(file));
|
|
iWaypoints = stoi(argv(0));
|
|
|
|
waypoints = memalloc(sizeof(*waypoints)*iWaypoints);
|
|
for(int i = 0i; i < iWaypoints; i++)
|
|
{
|
|
tokenize(fgets(file));
|
|
waypoints[i].org[0] = stof(argv(0));
|
|
waypoints[i].org[1] = stof(argv(1));
|
|
waypoints[i].org[2] = stof(argv(2));
|
|
waypoints[i].flRadius = stof(argv(3));
|
|
waypoints[i].iNeighbours = stoi(argv(4));
|
|
waypoints[i].neighbour = memalloc(sizeof(*waypoints[i].neighbour)*waypoints[i].iNeighbours);
|
|
for(int j = 0i; j < waypoints[i].iNeighbours; j++)
|
|
{
|
|
tokenize(fgets(file));
|
|
waypoints[i].neighbour[j].node = stoi(argv(0));
|
|
waypoints[i].neighbour[j].linkcost = stof(argv(1));
|
|
waypoints[i].neighbour[j].iFlags = stoh(argv(2));
|
|
}
|
|
}
|
|
fclose(file);
|
|
}
|
|
|
|
static void* memrealloc( __variant *oldptr, int elementsize, int oldelements, int newelements )
|
|
{
|
|
void *n = memalloc(elementsize*newelements);
|
|
memcpy(n, oldptr, elementsize*min(oldelements,newelements));
|
|
memfree(oldptr);
|
|
|
|
return n;
|
|
}
|
|
|
|
static void Way_LinkWaypoints( waypoint_t *wp, waypoint_t *w2 )
|
|
{
|
|
int w2n = w2-waypoints;
|
|
for (int i = 0i; i < wp->iNeighbours; i++)
|
|
{
|
|
if (wp->neighbour[i].node == w2n)
|
|
return;
|
|
}
|
|
|
|
int idx = wp->iNeighbours++;
|
|
wp->neighbour = memrealloc(wp->neighbour, sizeof(*wp->neighbour), idx, wp->iNeighbours);
|
|
local struct wpneighbour_s *n = wp->neighbour+idx;
|
|
n->node = w2n;
|
|
n->linkcost = vlen(w2->org - wp->org);
|
|
n->iFlags = 0;
|
|
}
|
|
|
|
static void Way_AutoLink( waypoint_t *wp )
|
|
{
|
|
int wpidx = wp-waypoints;
|
|
for (int i = 0i; i < iWaypoints; i++)
|
|
{
|
|
if (i == wpidx)
|
|
continue; //don't link to ourself...
|
|
|
|
if (vlen(wp->org - waypoints[i].org) > autocvar( nav_linksize, 256, "Cuttoff distance between links" ) )
|
|
continue; //autolink distance cutoff.
|
|
|
|
//not going to use the full player size because that makes steps really messy.
|
|
//however, we do need a little size, for sanity's sake
|
|
tracebox(wp->org, '-16 -16 0', '16 16 32', waypoints[i].org, TRUE, world);
|
|
if (trace_fraction < 1)
|
|
continue; //light of sight blocked, don't try autolinking.
|
|
|
|
Way_LinkWaypoints(wp, &waypoints[i]);
|
|
Way_LinkWaypoints(&waypoints[i], wp);
|
|
}
|
|
}
|
|
|
|
void Way_Waypoint_Create( entity pl, float autolink )
|
|
{
|
|
vector pos = pl.origin;
|
|
int idx = iWaypoints++;
|
|
waypoints = memrealloc( waypoints, sizeof(waypoint_t), idx, iWaypoints );
|
|
waypoint_t *n = waypoints + idx;
|
|
n->org = pos;
|
|
n->neighbour = __NULL__;
|
|
n->iNeighbours = 0;
|
|
|
|
if (autolink)
|
|
Way_AutoLink(n);
|
|
}
|
|
|
|
void Way_Waypoint_Delete( int idx )
|
|
{
|
|
if (idx < 0i || idx >= iWaypoints)
|
|
{
|
|
print("RT_DeleteWaypoint: invalid waypoint\n");
|
|
return;
|
|
}
|
|
|
|
//wipe the waypoint
|
|
memfree(waypoints[idx].neighbour);
|
|
memcpy(waypoints+idx, waypoints+idx+1, (iWaypoints-(idx+1))*sizeof(*waypoints));
|
|
iWaypoints--;
|
|
|
|
//clean up any links to it.
|
|
for (int i = 0; i < iWaypoints; i++)
|
|
{
|
|
for (int j = 0; j < waypoints[i].iNeighbours; )
|
|
{
|
|
int l = waypoints[i].neighbour[j].node;
|
|
if (l == idx)
|
|
{
|
|
memcpy(waypoints[i].neighbour+j, waypoints[i].neighbour+j+1, (waypoints[i].iNeighbours-(j+1))*sizeof(*waypoints[i].neighbour));
|
|
waypoints[i].iNeighbours--;
|
|
continue;
|
|
}
|
|
else if (l > idx)
|
|
waypoints[i].neighbour[j].node = l-1;
|
|
j++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Way_Waypoint_SetRadius( int idx, float radius )
|
|
{
|
|
if (idx < 0i || idx >= iWaypoints)
|
|
{
|
|
print("RT_Waypoint_SetRadius: invalid waypoint\n");
|
|
return;
|
|
}
|
|
waypoints[idx].flRadius = radius;
|
|
}
|
|
|
|
void Way_Waypoint_MakeJump(int idx)
|
|
{
|
|
if (idx < 0i || idx >= iWaypoints)
|
|
{
|
|
print("RT_Waypoint_SetRadius: invalid waypoint\n");
|
|
return;
|
|
}
|
|
|
|
for(int j = 0i; j < waypoints[idx].iNeighbours; j++) {
|
|
int target = waypoints[idx].neighbour[j].node;
|
|
|
|
for(int b = 0i; b < waypoints[target].iNeighbours; b++) {
|
|
if ( waypoints[target].neighbour[b].node == idx ) {
|
|
waypoints[target].neighbour[b].iFlags = WP_JUMP;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-1 for no nodes anywhere...
|
|
int Way_FindClosestWaypoint( vector org )
|
|
{
|
|
int r = -1i;
|
|
float bestdist = COST_INFINITE;
|
|
for (int i = 0i; i < iWaypoints; i++)
|
|
{
|
|
float dist = vlen(waypoints[i].org - org) - waypoints[i].flRadius;
|
|
if (dist < bestdist)
|
|
{
|
|
if (dist < 0)
|
|
{ //within the waypoint's radius
|
|
bestdist = dist;
|
|
r = i;
|
|
}
|
|
else
|
|
{ //outside the waypoint, make sure its valid.
|
|
traceline(org, waypoints[i].org, TRUE, world);
|
|
if (trace_fraction == 1)
|
|
{ //FIXME: sort them frst, to avoid traces?
|
|
bestdist = dist;
|
|
r = i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return r;
|
|
}
|
|
|
|
//Lame visualisation stuff - this is only visible on listen servers.
|
|
void SV_AddDebugPolygons( void ) {
|
|
if ( !autocvar( way_display, 0, "Display current waypoints" ) ) {
|
|
return;
|
|
}
|
|
|
|
if ( !iWaypoints ) {
|
|
return;
|
|
}
|
|
|
|
int nearest = Way_FindClosestWaypoint( self.origin );
|
|
makevectors(self.v_angle);
|
|
R_BeginPolygon( "waypoint", 0, 0 );
|
|
|
|
for ( int i = 0i; i < iWaypoints; i++ ) {
|
|
waypoint_t *w = waypoints+i;
|
|
vector org = w->org;
|
|
vector rgb = '1 1 1';
|
|
if (nearest == i)
|
|
rgb = '0 1 0';
|
|
R_PolygonVertex(org + v_right*16 - v_up*16, '1 1', rgb, 1);
|
|
R_PolygonVertex(org - v_right*16 - v_up*16, '0 1', rgb, 1);
|
|
R_PolygonVertex(org - v_right*16 + v_up*16, '0 0', rgb, 1);
|
|
R_PolygonVertex(org + v_right*16 + v_up*16, '1 0', rgb, 1);
|
|
R_EndPolygon();
|
|
}
|
|
|
|
R_BeginPolygon("", 0, 0);
|
|
for ( int i = 0i; i < iWaypoints; i++ ) {
|
|
waypoint_t *w = waypoints+i;
|
|
vector org = w->org;
|
|
|
|
for (float j = 0; j < 2*M_PI; j += 2*M_PI/4)
|
|
R_PolygonVertex(org + [sin(j), cos(j)]*w->flRadius, '1 1', '0 0.25 0', 1);
|
|
R_EndPolygon();
|
|
}
|
|
|
|
R_BeginPolygon("", 1, 0);
|
|
for ( int i = 0i; i < iWaypoints; i++ ) {
|
|
waypoint_t *w = waypoints+i;
|
|
vector org = w->org;
|
|
vector rgb = '1 1 1';
|
|
for ( int j = 0i; j < w->iNeighbours; j++ ) {
|
|
int k = w->neighbour[j].node;
|
|
if ( k < 0i || k >= iWaypoints ) {
|
|
break;
|
|
}
|
|
waypoint_t *w2 = &waypoints[k];
|
|
R_PolygonVertex(org, '0 1', '1 0 1', 1);
|
|
R_PolygonVertex(w2->org, '1 1', '0 1 0', 1);
|
|
R_EndPolygon();
|
|
}
|
|
}
|
|
}
|