quake2-action/acesrc/acebot_nodes.c

1246 lines
35 KiB
C

///////////////////////////////////////////////////////////////////////
//
// ACE - Quake II Bot Base Code
//
// Version 1.0
//
// Original file is Copyright(c), Steve Yeager 1998, All Rights Reserved
//
//
// All other files are Copyright(c) Id Software, Inc.
////////////////////////////////////////////////////////////////////////
/*
* $Header: /LicenseToKill/src/acesrc/acebot_nodes.c 29 26/11/99 0:48 Riever $
*
* $Log: /LicenseToKill/src/acesrc/acebot_nodes.c $
*
* 29 26/11/99 0:48 Riever
* Link creation safety checks..
*
* 28 27/10/99 18:49 Riever
* Added some debug code to handle lightlevel checks.
*
* 27 22/10/99 20:53 Riever
* Placed own path showing code under ltk_showpath control.
*
* 26 22/10/99 20:30 Riever
* Checking ltk_showpath set up right.
*
* 25 22/10/99 7:36 Riever
* Safety check to prevent falling damage links being created.
*
* 24 22/10/99 7:27 Riever
* Updated node file version to 3
*
* 23 22/10/99 7:17 Riever
* Stopped node files of less than 100 nodes being saved - this will
* prevent good node files being over-written when the game glitches.
*
* 22 21/10/99 8:15 Riever
* Added ltk_showpath CVAR to toggle display of bot path selection
* information.
*
* 21 20/10/99 20:26 Riever
* Fixed Ladder node creation.
*
* 20 10/10/99 9:33 Riever
* Found a bug in reverse link - fixed!
*
* 19 6/10/99 17:54 Riever
* ... and another!
*
* 18 6/10/99 17:53 Riever
* Removed another debug print.
*
* 17 6/10/99 17:51 Riever
* Removed debug prints and disabled path visibility code.
*
* 16 6/10/99 17:40 Riever
* Added TeamPlay state STATE_POSITION to enable bots to seperate and
* avoid centipede formations.
*
* 15 29/09/99 13:32 Riever
* Changed all node linkage code to check a trace.
* Changed nearest node code to find a reliable reachable node.
* Changed ShowPath function to show the AntPath to the destination, not
* the array path.
*
* 14 28/09/99 7:47 Riever
* Switched out a couple of debug prints that were appearing in non debug
* mode
*
* 13 27/09/99 20:46 Riever
* Changed trace tests for node linkage and detection to use vec3_origins
*
* 12 27/09/99 16:01 Riever
* Added "self" to ACEND_UpdateNodeEdge calls
*
* 11 27/09/99 14:59 Riever
* Changed ACE link code to TRACE IT FIRST!!! I know, no-one else will
* believe that this wasn't in there either *grin*....
*
* 10 27/09/99 14:53 Riever
* Changed nearest node check to mask ALL (temporary until I find the
* problem with thin walls in this game!)
* Increased node table version number to 2 because node density increased
* in acebot.h to 96.
*
* 9 27/09/99 14:29 Riever
* Modified ACEND_FollowPath to utilise the AntPath code
*
* 8 26/09/99 8:01 Riever
* ACEND_ReverseLink function in place ready for new path system.
*
* 7 21/09/99 11:58 Riever
* fixed INVALID node searches
*
* 6 18/09/99 19:21 Riever
* NODE_DOOR creation fixed to place the node in the center of the closed
* door.
*
* 5 18/09/99 8:08 Riever
* NODE_DOOR creation now places node at center of the door to help bot
* navigation. Still need to sort out non rotating doors.
*
* 4 17/09/99 17:06 Riever
* Changed node table save directory to be "terrain" as advised by William
*
* 3 17/09/99 17:04 Riever
* New node structure implemented
* Link structure added
* Found nodetable creation bug at timelimit and fixed it.
* Changed load and save code to be "game directory" friendly.
* Made node table version number a CONSTANT
*
* 2 13/09/99 19:52 Riever
* Added headers
*
*/
///////////////////////////////////////////////////////////////////////
//
// acebot_nodes.c - This file contains all of the
// pathing routines for the ACE bot.
//
///////////////////////////////////////////////////////////////////////
#include "../g_local.h"
#include "acebot.h"
#define LTK_NODEVERSION 3
#define TRACE_DIST_LADDER 16
// flags
qboolean newmap=true;
// Total number of nodes that are items
int numitemnodes;
// Total number of nodes
int numnodes;
// For debugging paths
int show_path_from = -1;
int show_path_to = -1;
// array for node data
node_t nodes[MAX_NODES];
short int path_table[MAX_NODES][MAX_NODES];
///////////////////////////////////////////////////////////////////////
// NODE INFORMATION FUNCTIONS
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
// Determin cost of moving from one node to another
///////////////////////////////////////////////////////////////////////
int ACEND_FindCost(int from, int to)
{
// RiEvEr - Bug Hunting
int curnode = INVALID;
int cost=1; // Shortest possible is 1
// If we can not get there then return invalid
if( (from == INVALID) || (to == INVALID) ||
(path_table[from][to] == INVALID) )
return INVALID;
// Otherwise check the path and return the cost
curnode = path_table[from][to];
// Find a path (linear time, very fast)
while(curnode != to)
{
curnode = path_table[curnode][to];
if(curnode == INVALID) // something has corrupted the path abort
return INVALID;
cost++;
}
return cost;
}
///////////////////////////////////////////////////////////////////////
// Find a close node to the player within dist.
//
// Faster than looking for the closest node, but not very
// accurate.
///////////////////////////////////////////////////////////////////////
int ACEND_FindCloseReachableNode(edict_t *self, int range, int type)
{
vec3_t v;
int i;
trace_t tr;
float dist;
vec3_t maxs,mins;
VectorCopy(self->mins,mins);
mins[2] += 16;
VectorCopy(self->maxs,maxs);
maxs[2] -= 16;
range *= range;
for(i=0;i<numnodes;i++)
{
if(type == NODE_ALL || type == nodes[i].type) // check node type
{
VectorSubtract(nodes[i].origin,self->s.origin,v); // subtract first
dist = v[0]*v[0] + v[1]*v[1] + v[2]*v[2];
if(dist < range) // square range instead of sqrt
{
// make sure it is visible
//AQ2 ADDED MASK_SOLID
//trace = gi.trace (self->s.origin, vec3_origin, vec3_origin, nodes[i].origin, self, MASK_SOLID|MASK_OPAQUE);
tr = gi.trace (self->s.origin, mins, maxs, nodes[i].origin, self, MASK_ALL);
if(tr.fraction == 1.0)
return i;
}
}
}
return -1;
}
///////////////////////////////////////////////////////////////////////
// Find the closest node to the player within a certain range
///////////////////////////////////////////////////////////////////////
int ACEND_FindClosestReachableNode(edict_t *self, int range, int type)
{
int i;
float closest = 99999;
float dist;
int node=-1;
vec3_t v;
trace_t tr;
float rng;
vec3_t maxs,mins;
VectorCopy(self->mins,mins);
VectorCopy(self->maxs,maxs);
// For Ladders, do not worry so much about reachability
if(type == NODE_LADDER)
{
VectorCopy(vec3_origin,maxs);
VectorCopy(vec3_origin,mins);
}
else
{
mins[2] += 18; // Stepsize
maxs[2] -= 16; // Duck a little..
}
rng = (float)(range * range); // square range for distance comparison (eliminate sqrt)
for(i=0;i<numnodes;i++)
{
if(type == NODE_ALL || type == nodes[i].type) // check node type
{
VectorSubtract(nodes[i].origin, self->s.origin,v); // subtract first
dist = v[0]*v[0] + v[1]*v[1] + v[2]*v[2];
if(dist < closest && dist < rng)
{
// make sure it is visible
//AQ2 added MASK_SOLID
tr = gi.trace (self->s.origin, mins, maxs, nodes[i].origin, self, MASK_SOLID|MASK_OPAQUE);
// tr = gi.trace (self->s.origin, vec3_origin, vec3_origin, nodes[i].origin, self, MASK_ALL);
if( (tr.fraction == 1.0) ||
( (tr.fraction > 0.9) // may be blocked by the door itself!
&& (Q_stricmp(tr.ent->classname, "func_door_rotating") == 0) )
)
{
node = i;
closest = dist;
}
}
}
}
return node;
}
///////////////////////////////////////////////////////////////////////
// BOT NAVIGATION ROUTINES
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
// Set up the goal
///////////////////////////////////////////////////////////////////////
void ACEND_SetGoal(edict_t *self, int goal_node)
{
int node;
self->goal_node = goal_node;
node = ACEND_FindClosestReachableNode(self, NODE_DENSITY*3, NODE_ALL);
if(node == -1)
return;
if(debug_mode)
debug_printf("%s new start node selected %d\n",self->client->pers.netname,node);
self->current_node = node;
self->next_node = self->current_node; // make sure we get to the nearest node first
self->node_timeout = 0;
}
///////////////////////////////////////////////////////////////////////
// Move closer to goal by pointing the bot to the next node
// that is closer to the goal
///////////////////////////////////////////////////////////////////////
qboolean ACEND_FollowPath(edict_t *self)
{
vec3_t v;
//////////////////////////////////////////
// Show the path (uncomment for debugging)
show_path_from = self->current_node;
show_path_to = self->goal_node;
if( ltk_showpath->value )
{
ACEND_DrawPath(self);
}
//////////////////////////////////////////
// Try again?
if(self->node_timeout ++ > 30)
{
if(self->tries++ > 3)
return false;
else
ACEND_SetGoal(self,self->goal_node);
}
//RiEvEr - new path code & algorithm
// This part checks if we are off course
if( level.time == (float)((int)level.time) )
{
if(
!AntLinkExists( self->current_node, SLLfront(&self->pathList) )
&& ( self->current_node != SLLfront(&self->pathList) )
)
{
// We are off the path - clear out the lists
AntInitSearch( self );
}
}
// Boot in our new pathing algorithm
// This will fill self->pathList with the information we need
if(
SLLempty(&self->pathList) // We have no path and
&& (self->current_node != self->goal_node) // we're not at our destination
)
{
if( !AntStartSearch( self, self->current_node, self->goal_node)) // Set up our pathList
{
// Failed to find a path
if( debug_mode )
gi.bprintf(PRINT_HIGH,"%s: Target at(%i) - No Path \n",
self->client->pers.netname, self->goal_node, self->next_node);
return false;
}
// return true;
}
//R
// Are we there yet?
VectorSubtract(self->s.origin,nodes[self->next_node].origin,v);
if(VectorLength(v) < 32)
{
// reset timeout
self->node_timeout = 0;
if(self->next_node == self->goal_node)
{
if(debug_mode)
debug_printf("%s reached goal!\n",self->client->pers.netname);
ACEAI_PickLongRangeGoal(self); // Pick a new goal
}
else
{
self->current_node = self->next_node;
// self->next_node = path_table[self->current_node][self->goal_node];
// Removethe front entry from the list
SLLpop_front(&self->pathList);
// Get the next node - if there is one!
if( !SLLempty(&self->pathList))
self->next_node = SLLfront( &self->pathList);
else
{
// We messed up...
if( debug_mode)
gi.bprintf(PRINT_HIGH, "Trying to read an empty SLL nodelist!\n");
self->next_node = INVALID;
}
}
}
if(self->current_node == -1 || self->next_node ==-1)
return false;
// Set bot's movement vector
VectorSubtract (nodes[self->next_node].origin, self->s.origin , self->move_vector);
return true;
}
///////////////////////////////////////////////////////////////////////
// MAPPING CODE
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
// Capture when the grappling hook has been fired for mapping purposes.
///////////////////////////////////////////////////////////////////////
void ACEND_GrapFired(edict_t *self)
{
/* int closest_node;
if(!self->owner)
return; // should not be here
// Check to see if the grapple is in pull mode
if(self->owner->client->ctf_grapplestate == CTF_GRAPPLE_STATE_PULL)
{
// Look for the closest node of type grapple
closest_node = ACEND_FindClosestReachableNode(self,NODE_DENSITY,NODE_GRAPPLE);
if(closest_node == -1 ) // we need to drop a node
{
closest_node = ACEND_AddNode(self,NODE_GRAPPLE);
// Add an edge
ACEND_UpdateNodeEdge(self, self->owner->last_node,closest_node);
self->owner->last_node = closest_node;
}
else
self->owner->last_node = closest_node; // zero out so other nodes will not be linked
}*/
}
///////////////////////////////////////////////////////////////////////
// Check for adding ladder nodes
///////////////////////////////////////////////////////////////////////
qboolean ACEND_CheckForLadder(edict_t *self)
{
int closest_node;
// If there is a ladder and we are moving up, see if we should add a ladder node
if (gi.pointcontents(self->s.origin) & CONTENTS_LADDER && self->velocity[2] > 0)
{
//debug_printf("contents: %x\n",tr.contents);
closest_node = ACEND_FindClosestReachableNode(self,NODE_DENSITY,NODE_LADDER);
if(closest_node == -1)
{
closest_node = ACEND_AddNode(self,NODE_LADDER);
// Now add link
ACEND_UpdateNodeEdge(self, self->last_node,closest_node);
// Set current to last
self->last_node = closest_node;
}
else
{
ACEND_UpdateNodeEdge(self, self->last_node,closest_node);
self->last_node = closest_node; // set visited to last
}
return true;
}
return false;
}
//=======================================================
// LadderForward
//=======================================================
//
// The ACE code version of this doesn't work!
qboolean ACEND_LadderForward( edict_t *self )//, vec3_t angles )
{
vec3_t dir, angle, dest, min, max;
trace_t trace;
int closest_node;
VectorClear(angle);
angle[1] = self->s.angles[1];
AngleVectors(angle, dir, NULL, NULL);
VectorCopy(self->mins,min);
min[2] += 22;
VectorCopy(self->maxs,max);
VectorMA(self->s.origin, TRACE_DIST_LADDER, dir, dest);
trace = gi.trace(self->s.origin, min, max, dest, self, MASK_ALL);
//TempLaser(self->s.origin, dest);
if (trace.fraction == 1.0)
return (false);
// safe_bprintf(PRINT_HIGH,"Contents forward are %d\n", trace.contents);
if (trace.contents & CONTENTS_LADDER || trace.contents &CONTENTS_DETAIL)
{
// Debug print
// safe_bprintf(PRINT_HIGH,"contents: %x\n",trace.contents);
closest_node = ACEND_FindClosestReachableNode(self,NODE_DENSITY,NODE_LADDER);
if(closest_node == -1)
{
closest_node = ACEND_AddNode(self,NODE_LADDER);
// Now add link
ACEND_UpdateNodeEdge(self, self->last_node,closest_node);
// Set current to last
self->last_node = closest_node;
}
else
{
ACEND_UpdateNodeEdge(self, self->last_node,closest_node);
self->last_node = closest_node; // set visited to last
}
return (true);
}
return (false);
}
///////////////////////////////////////////////////////////////////////
// This routine is called to hook in the pathing code and sets
// the current node if valid.
///////////////////////////////////////////////////////////////////////
void ACEND_PathMap(edict_t *self)
{
int closest_node;
// Removed last_update checks since this stopped multiple node files being built
vec3_t v;
// Special node drawing code for debugging
if( ltk_showpath->value )
{
if(show_path_to != -1)
ACEND_DrawPath( self );
}
// Just checking lightlevels - uncomment to use
// if( debug_mode && !self->is_bot)
// safe_bprintf(PRINT_HIGH,"LightLevel = %d\n", self->light_level);
////////////////////////////////////////////////////////
// Special check for ladder nodes
///////////////////////////////////////////////////////
// Replace non-working ACE version with mine.
// if(ACEND_CheckForLadder(self)) // check for ladder nodes
if(ACEND_LadderForward(self)) // check for ladder nodes
return;
// Not on ground, and not in the water, so bail
if(!self->groundentity && !self->waterlevel)
return;
////////////////////////////////////////////////////////
// Lava/Slime
////////////////////////////////////////////////////////
VectorCopy(self->s.origin,v);
v[2] -= 18;
if(gi.pointcontents(v) & (CONTENTS_LAVA|CONTENTS_SLIME))
return; // no nodes in slime
////////////////////////////////////////////////////////
// Jumping
///////////////////////////////////////////////////////
if(self->is_jumping)
{
// See if there is a closeby jump landing node (prevent adding too many)
closest_node = ACEND_FindClosestReachableNode(self, 64, NODE_JUMP);
if(closest_node == INVALID)
closest_node = ACEND_AddNode(self,NODE_JUMP);
// Now add link
if(self->last_node != -1)
ACEND_UpdateNodeEdge(self, self->last_node, closest_node);
self->is_jumping = false;
return;
}
/* ////////////////////////////////////////////////////////////
// Grapple
// Do not add nodes during grapple, added elsewhere manually
////////////////////////////////////////////////////////////
if(ctf->value && self->client->ctf_grapplestate == CTF_GRAPPLE_STATE_PULL)
return;*/
// Iterate through all nodes to make sure far enough apart
closest_node = ACEND_FindClosestReachableNode(self, NODE_DENSITY, NODE_ALL);
////////////////////////////////////////////////////////
// Special Check for Platforms
////////////////////////////////////////////////////////
if(self->groundentity && self->groundentity->use == Use_Plat)
{
if(closest_node == INVALID)
return; // Do not want to do anything here.
// Here we want to add links
if(closest_node != self->last_node && self->last_node != INVALID)
ACEND_UpdateNodeEdge(self, self->last_node,closest_node);
self->last_node = closest_node; // set visited to last
return;
}
////////////////////////////////////////////////////////
// Add Nodes as needed
////////////////////////////////////////////////////////
if(closest_node == INVALID)
{
// Add nodes in the water as needed
if(self->waterlevel)
closest_node = ACEND_AddNode(self,NODE_WATER);
else
closest_node = ACEND_AddNode(self,NODE_MOVE);
// Now add link
if(self->last_node != -1)
ACEND_UpdateNodeEdge(self, self->last_node, closest_node);
}
else if(closest_node != self->last_node && self->last_node != INVALID)
ACEND_UpdateNodeEdge(self, self->last_node,closest_node);
self->last_node = closest_node; // set visited to last
}
///////////////////////////////////////////////////////////////////////
// Init node array (set all to INVALID)
///////////////////////////////////////////////////////////////////////
void ACEND_InitNodes(void)
{
numnodes = 1;
numitemnodes = 1;
memset(nodes,0,sizeof(node_t) * MAX_NODES);
memset(path_table,INVALID,sizeof(short int)*MAX_NODES*MAX_NODES);
}
///////////////////////////////////////////////////////////////////////
// Show the node for debugging (utility function)
///////////////////////////////////////////////////////////////////////
void ACEND_ShowNode(int node)
{
edict_t *ent;
// return; // commented out for now. uncommend to show nodes during debugging,
// but too many will cause overflows. You have been warned.
ent = G_Spawn();
ent->movetype = MOVETYPE_NONE;
ent->solid = SOLID_NOT;
if(nodes[node].type == NODE_MOVE)
ent->s.renderfx = RF_SHELL_BLUE;
else if (nodes[node].type == NODE_WATER)
ent->s.renderfx = RF_SHELL_RED;
else
ent->s.renderfx = RF_SHELL_GREEN; // action nodes
ent->s.modelindex = gi.modelindex ("models/items/ammo/grenades/medium/tris.md2");
ent->owner = ent;
ent->nextthink = level.time + 600.0; // 10 minutes is long enough!
ent->think = G_FreeEdict;
ent->dmg = 0;
VectorCopy(nodes[node].origin,ent->s.origin);
gi.linkentity (ent);
}
///////////////////////////////////////////////////////////////////////
// Draws the current path (utility function)
///////////////////////////////////////////////////////////////////////
void ACEND_DrawPath(edict_t *self)
{
int current_node, goal_node, next_node;
current_node = show_path_from;
goal_node = show_path_to;
// RiEvEr - rewritten to use Ant system
AntStartSearch( self, current_node, goal_node);
next_node = SLLfront(&self->pathList);
// Now set up and display the path
while( current_node != goal_node && current_node != INVALID)
{
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_BFG_LASER);
gi.WritePosition (nodes[current_node].origin);
gi.WritePosition (nodes[next_node].origin);
gi.multicast (nodes[current_node].origin, MULTICAST_PVS);
current_node = next_node;
SLLpop_front( &self->pathList);
next_node = SLLfront(&self->pathList);
}
/*
next_node = path_table[current_node][goal_node];
// Now set up and display the path
while(current_node != goal_node && current_node != -1)
{
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_BFG_LASER);
gi.WritePosition (nodes[current_node].origin);
gi.WritePosition (nodes[next_node].origin);
gi.multicast (nodes[current_node].origin, MULTICAST_PVS);
current_node = next_node;
next_node = path_table[current_node][goal_node];
}*/
}
///////////////////////////////////////////////////////////////////////
// Turns on showing of the path, set goal to -1 to
// shut off. (utility function)
///////////////////////////////////////////////////////////////////////
void ACEND_ShowPath(edict_t *self, int goal_node)
{
show_path_from = ACEND_FindClosestReachableNode(self, NODE_DENSITY, NODE_ALL);
show_path_to = goal_node;
}
///////////////////////////////////////////////////////////////////////
// Add a node of type ?
///////////////////////////////////////////////////////////////////////
int ACEND_AddNode(edict_t *self, int type)
{
vec3_t v1,v2;
int i;
// Block if we exceed maximum
if (numnodes + 1 > MAX_NODES)
return false;
// Set location
VectorCopy(self->s.origin, nodes[numnodes].origin);
// Set type
nodes[numnodes].type = type;
// Set number - RiEvEr
nodes[numnodes].nodenum = numnodes;
// Clear out the link information - RiEvEr
for( i = 0; i< MAXLINKS; i++)
{
nodes[numnodes].links[i].targetNode = INVALID;
}
/////////////////////////////////////////////////////
// ITEMS
// Move the z location up just a bit.
if(type == NODE_ITEM)
{
nodes[numnodes].origin[2] += 16;
numitemnodes++;
}
// Teleporters
if(type == NODE_TELEPORTER)
{
// Up 32
nodes[numnodes].origin[2] += 32;
}
// Doors
if(type == NODE_DOOR)
{
vec3_t position;
// Find mid point of door max and min and put the node there
VectorCopy(self->s.origin, position);
// find center of door
position[0] = position[0] + self->mins[0] + ((self->maxs[0] - self->mins[0]) /2);
position[1] = position[1] + self->mins[1] + ((self->maxs[1] - self->mins[1]) /2);
position[2] -= 16; // lower it a little
// Set location
VectorCopy(position, nodes[numnodes].origin);
}
if(type == NODE_LADDER)
{
nodes[numnodes].type = NODE_LADDER;
if(debug_mode)
{
debug_printf("Node added %d type: Ladder\n",numnodes);
ACEND_ShowNode(numnodes);
}
numnodes++;
return numnodes-1; // return the node added
}
// For platforms drop two nodes one at top, one at bottom
if(type == NODE_PLATFORM)
{
VectorCopy(self->maxs,v1);
VectorCopy(self->mins,v2);
// To get the center
nodes[numnodes].origin[0] = (v1[0] - v2[0]) / 2 + v2[0];
nodes[numnodes].origin[1] = (v1[1] - v2[1]) / 2 + v2[1];
nodes[numnodes].origin[2] = self->maxs[2];
if(debug_mode)
ACEND_ShowNode(numnodes);
numnodes++;
nodes[numnodes].origin[0] = nodes[numnodes-1].origin[0];
nodes[numnodes].origin[1] = nodes[numnodes-1].origin[1];
nodes[numnodes].origin[2] = self->mins[2]+64;
nodes[numnodes].type = NODE_PLATFORM;
// Add a link
//RiEvEr modified to pass in calling entity
ACEND_UpdateNodeEdge(self, numnodes, numnodes-1);
if(debug_mode)
{
debug_printf("Node added %d type: Platform\n",numnodes);
ACEND_ShowNode(numnodes);
}
numnodes++;
return numnodes -1;
}
if(debug_mode)
{
if(nodes[numnodes].type == NODE_MOVE)
debug_printf("Node added %d type: Move\n",numnodes);
else if(nodes[numnodes].type == NODE_TELEPORTER)
debug_printf("Node added %d type: Teleporter\n",numnodes);
else if(nodes[numnodes].type == NODE_ITEM)
debug_printf("Node added %d type: Item\n",numnodes);
else if(nodes[numnodes].type == NODE_WATER)
debug_printf("Node added %d type: Water\n",numnodes);
else if(nodes[numnodes].type == NODE_GRAPPLE)
debug_printf("Node added %d type: Grapple\n",numnodes);
ACEND_ShowNode(numnodes);
}
numnodes++;
return numnodes-1; // return the node added
}
// RiEvEr
//=======================================
// ReverseLink
//=======================================
// Takes the path BACK to where we came from
// and tries to link the two nodes
// This helps make good path files
//
void ACEND_ReverseLink( edict_t *self, int from, int to )
{
int i;
trace_t trace;
vec3_t min,max;
if(from == INVALID || to == INVALID || from == to)
return; // safety
// Need to trace from -> to and check heights
// if from is much lower than to, forget it
if( (nodes[from].origin[2]+32.0) < (nodes[to].origin[2]) )
{
// May not be able to jump that high so do not allow the return link
return;
}
VectorCopy(self->mins, min);
// if( (nodes[from].origin[2]) < (nodes[2].origin[2]) )
min[2] =0; // Allow for steps etc.
VectorCopy(self->maxs, max);
// if( (nodes[from].origin[2]) > (nodes[2].origin[2]) )
max[2] =0; // Could be a downward sloping feature above our head
// This should not be necessary, but I've heard that before!
// Now trace it again
trace = gi.trace( nodes[from].origin, min, max, nodes[to].origin, self, MASK_SOLID);
if( trace.fraction < 1.0)
{
// can't get there for some reason
return;
}
// Add the link
path_table[from][to] = to;
// Checks if the link exists and then may create a new one - RiEvEr
for( i=0; i<MAXLINKS; i++)
{
if ( nodes[from].links[i].targetNode == to)
break;
if ( nodes[from].links[i].targetNode == INVALID)
{
// RiEvEr
// William uses a time factor here, whereas I use distance
// His is possibly more efficient
vec3_t v;
float thisCost;
VectorSubtract(nodes[from].origin, nodes[to].origin, v); // subtract first
thisCost = VectorLength(v);
nodes[from].links[i].targetNode = to;
nodes[from].links[i].cost = thisCost;
break;
}
}
// Now for the self-referencing part, linear time for each link added
for(i=0;i<numnodes;i++)
if(path_table[i][from] != INVALID)
if(i == to)
path_table[i][to] = INVALID; // make sure we terminate
else
path_table[i][to] = path_table[i][from];
// if(debug_mode)
// debug_printf("ReverseLink %d -> %d\n", from, to);
}
//R
///////////////////////////////////////////////////////////////////////
// Add/Update node connections (paths)
///////////////////////////////////////////////////////////////////////
void ACEND_UpdateNodeEdge(edict_t *self, int from, int to)
{
int i;
trace_t trace;
vec3_t min,max;
if(from == INVALID || to == INVALID || from == to)
return; // safety
// Try to stop impossible links!
// If it looks higher than a jump...
if( nodes[to].origin[2] > nodes[from].origin[2]+36)
{
// If we are coming from a move or jump node
if( (nodes[from].type == NODE_MOVE) ||
(nodes[from].type == NODE_JUMP) )
{
// No if the to node is the same, it's illegal
if( (nodes[to].type == NODE_MOVE) ||
(nodes[to].type == NODE_JUMP) )
{
// Too high - not possible!!
return;
}
}
}
// Do not allow creation of nodes where the falling distance would kill you!
if( (nodes[from].origin[2]) > (nodes[to].origin[2] + 180) )
return;
/* VectorCopy(self->mins, min);
// If going up
// if( (nodes[from].origin[2]) < (nodes[to].origin[2]) )
min[2] = 0; // Allow for steps up etc.
VectorCopy(self->maxs, max);
// If going down
// if( (nodes[from].origin[2]) > (nodes[to].origin[2]) )
max[2] = 0; // door node linking*/
VectorCopy( vec3_origin, min);
VectorCopy( vec3_origin, max);
// Now trace it - more safety stuff!
trace = gi.trace( nodes[from].origin, min, max, nodes[to].origin, self, MASK_SOLID);
if( trace.fraction < 1.0)
{
// can't do it
return;
}
// Add the link
path_table[from][to] = to;
// Checks if the link exists and then may create a new one - RiEvEr
for( i=0; i<MAXLINKS; i++)
{
if ( nodes[from].links[i].targetNode == to)
break;
if ( nodes[from].links[i].targetNode == INVALID)
{
// RiEvEr
// William uses a time factor here, whereas I use distance
// His is possibly more efficient
vec3_t v;
float thisCost;
VectorSubtract(nodes[from].origin, nodes[to].origin, v); // subtract first
thisCost = VectorLength(v);
nodes[from].links[i].targetNode = to;
nodes[from].links[i].cost = thisCost;
break;
}
}
// Now for the self-referencing part, linear time for each link added
for(i=0;i<numnodes;i++)
if(path_table[i][from] != INVALID)
if(i == to)
path_table[i][to] = INVALID; // make sure we terminate
else
path_table[i][to] = path_table[i][from];
// if(debug_mode)
// debug_printf("Link %d -> %d\n", from, to);
// RiEvEr - check for the link going back the other way
// Reverse the input data so it works properly!
ACEND_ReverseLink( self, to, from );
// R
}
///////////////////////////////////////////////////////////////////////
// Remove a node edge
///////////////////////////////////////////////////////////////////////
void ACEND_RemoveNodeEdge(edict_t *self, int from, int to)
{
int i;
if(debug_mode)
debug_printf("%s: Removing Edge %d -> %d\n", self->client->pers.netname, from, to);
path_table[from][to] = INVALID; // set to invalid
// Make sure this gets updated in our path array
for(i=0;i<numnodes;i++)
if(path_table[from][i] == to)
path_table[from][i] = INVALID;
}
///////////////////////////////////////////////////////////////////////
// This function will resolve all paths that are incomplete
// usually called before saving to disk
///////////////////////////////////////////////////////////////////////
void ACEND_ResolveAllPaths()
{
int i, from, to;
int num=0;
// return; // RiEvEr - disabled since it will interfere with the optimiser
safe_bprintf(PRINT_HIGH,"Resolving all paths...");
for(from=0;from<numnodes;from++)
{
for(to=0;to<numnodes;to++)
{
// update unresolved paths
// Not equal to itself, not equal to -1 and equal to the last link
if(from != to && path_table[from][to] == to)
{
num++;
// Now for the self-referencing part linear time for each link added
for(i=0;i<numnodes;i++)
if(path_table[i][from] != -1)
if(i == to)
path_table[i][to] = -1; // make sure we terminate
else
path_table[i][to] = path_table[i][from];
}
}
}
safe_bprintf(PRINT_MEDIUM,"done (%d updated)\n",num);
}
///////////////////////////////////////////////////////////////////////
// Save to disk file
//
// Since my compression routines are one thing I did not want to
// release, I took out the compressed format option. Most levels will
// save out to a node file around 50-200k, so compression is not really
// a big deal.
///////////////////////////////////////////////////////////////////////
void ACEND_SaveNodes()
{
FILE *pOut;
char filename[60];
int i,j;
int version;
cvar_t *game_dir;
version = LTK_NODEVERSION;
// Stop overwriting good node tables with bad!
if( numnodes < 100)
return;
game_dir = gi.cvar ("game", "", 0);
//@@ change 'nav' to 'terrain' to line up with William
#ifdef _WIN32
i = sprintf(filename, ".\\");
i += sprintf(filename + i, game_dir->string);
i += sprintf(filename + i, "\\terrain\\");
i += sprintf(filename + i, level.mapname);
i += sprintf(filename + i, ".ltk");
#else
strcpy(filename, "./");
strcat(filename, game_dir->string);
strcat(filename, "/terrain/");
strcat(filename,level.mapname);
strcat(filename,".ltk");
#endif
// Resolve paths
ACEND_ResolveAllPaths();
safe_bprintf(PRINT_MEDIUM,"Saving node table...");
/* strcpy(filename,"action\\nav\\");
strcat(filename,level.mapname);
strcat(filename,".nod");*/
if((pOut = fopen(filename, "wb" )) == NULL)
return; // bail
fwrite(&version,sizeof(int),1,pOut); // write version
fwrite(&numnodes,sizeof(int),1,pOut); // write count
fwrite(&num_items,sizeof(int),1,pOut); // write facts count
fwrite(nodes,sizeof(node_t),numnodes,pOut); // write nodes
for(i=0;i<numnodes;i++)
for(j=0;j<numnodes;j++)
fwrite(&path_table[i][j],sizeof(short int),1,pOut); // write count
fwrite(item_table,sizeof(item_table_t),num_items,pOut); // write out the fact table
fclose(pOut);
safe_bprintf(PRINT_MEDIUM,"done.\n");
}
///////////////////////////////////////////////////////////////////////
// Read from disk file
///////////////////////////////////////////////////////////////////////
void ACEND_LoadNodes(void)
{
FILE *pIn;
int i,j;
char filename[60];
int version;
cvar_t *game_dir;
game_dir = gi.cvar ("game", "", 0);
#ifdef _WIN32
i = sprintf(filename, ".\\");
i += sprintf(filename + i, game_dir->string);
i += sprintf(filename + i, "\\terrain\\");
i += sprintf(filename + i, level.mapname);
i += sprintf(filename + i, ".ltk");
#else
strcpy(filename, "./");
strcat(filename, game_dir->string);
strcat(filename, "/terrain/");
strcat(filename,level.mapname);
strcat(filename,".ltk");
#endif
/*
strcpy(filename,"action\\nav\\");
strcat(filename,level.mapname);
strcat(filename,".nod");*/
if((pIn = fopen(filename, "rb" )) == NULL)
{
// Create item table
safe_bprintf(PRINT_MEDIUM, "ACE: No node file found, creating new one...");
ACEIT_BuildItemNodeTable(false);
safe_bprintf(PRINT_MEDIUM, "done.\n");
return;
}
// determin version
fread(&version,sizeof(int),1,pIn); // read version
if(version == LTK_NODEVERSION)
{
safe_bprintf(PRINT_MEDIUM,"ACE: Loading node table...");
fread(&numnodes,sizeof(int),1,pIn); // read count
fread(&num_items,sizeof(int),1,pIn); // read facts count
fread(nodes,sizeof(node_t),numnodes,pIn);
for(i=0;i<numnodes;i++)
for(j=0;j<numnodes;j++)
fread(&path_table[i][j],sizeof(short int),1,pIn); // write count
fread(item_table,sizeof(item_table_t),num_items,pIn);
fclose(pIn);
}
else
{
// Create item table
safe_bprintf(PRINT_MEDIUM, "ACE: No node file found, creating new one...");
ACEIT_BuildItemNodeTable(false);
safe_bprintf(PRINT_MEDIUM, "done.\n");
return; // bail
}
safe_bprintf(PRINT_MEDIUM, "done.\n");
ACEIT_BuildItemNodeTable(true);
}