/* Copyright (C) 1997-2001 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -------------------------------------------------------------- The ACE Bot is a product of Steve Yeager, and is available from the ACE Bot homepage, at http://www.axionfx.com/ace. This program is a modification of the ACE Bot, and is therefore in NO WAY supported by Steve Yeager. */ #include "../g_local.h" #include "ai_local.h" edict_t *LINKS_PASSENT = NULL; //========================================== // AI_LinkString //========================================== char *AI_LinkString( int linktype ) { char *s; if( linktype == LINK_MOVE ) s = "LINK_MOVE"; else if( linktype == LINK_STAIRS ) s = "LINK_STAIRS"; else if( linktype == LINK_FALL ) s = "LINK_FALL"; else if( linktype == LINK_CLIMB ) s = "LINK_CLIMB"; else if( linktype == LINK_TELEPORT ) s = "LINK_TELEPORT"; else if( linktype == LINK_PLATFORM ) s = "LINK_PLATFORM"; else if( linktype == LINK_JUMPPAD ) s = "LINK_JUMPAD"; else if( linktype == LINK_WATER ) s = "LINK_WATER"; else if( linktype == LINK_WATERJUMP ) s = "LINK_WATERJUMP"; else if( linktype == LINK_LADDER ) s = "LINK_LADDER"; else if( linktype == LINK_INVALID ) s = "LINK_INVALID"; else if( linktype == LINK_JUMP ) s = "LINK_JUMP"; else if( linktype ) s = "UNKNOWN"; else s = "ZERO"; return s; } //========================================== // AI_VisibleOrigins // same as is visible, but doesn't need ents //========================================== qboolean AI_VisibleOrigins (vec3_t spot1, vec3_t spot2) { trace_t trace; // AILink_Trace( &trace, spot1, vec3_origin, vec3_origin, spot2, NULL, MASK_NODESOLID ); trap_Trace( &trace, spot1, vec3_origin, vec3_origin, spot2, LINKS_PASSENT, MASK_NODESOLID ); if (trace.fraction == 1.0 && !trace.startsolid) return qtrue; //Com_Printf("Bloqued"); return qfalse; } //================= //AI_findNodeInRadius // // Copy of findradius to act with nodes instead of entities // Setting up ignoreHeight uses a cilinder instead of a sphere (used to catch fall links) //================= int AI_findNodeInRadius (int from, vec3_t org, float rad, qboolean ignoreHeight) { vec3_t eorg; int j; if( from < 0 ) return -1; else if( from > nav.num_nodes ) return -1; else if( !nav.num_nodes ) return -1; else from++; for( ; from < nav.num_nodes; from++) { for( j=0 ; j<3 ; j++ ) eorg[j] = org[j] - nodes[from].origin[j]; if( ignoreHeight ) eorg[2] = 0; if( VectorLengthFast(eorg) > rad ) continue; return from; } return -1; } //========================================== // AI_FindLinkDistance // returns world distance between both nodes //========================================== float AI_FindLinkDistance(int n1, int n2) { // Teleporters exception: JALFIXME: add check for Destiny being teleporter's target if (nodes[n1].flags & NODEFLAGS_TELEPORTER_IN && nodes[n2].flags & NODEFLAGS_TELEPORTER_OUT ) return NODE_DENSITY; //not 0, just because teleporting has a strategical cost return DistanceFast( nodes[n1].origin, nodes[2].origin ); } //================================================================== // // PLINKS (nodes linking. 1 hop only. Used later for pathfinding) // //================================================================== //========================================== // AI_AddLink // force-add of a link //========================================== qboolean AI_AddLink( int n1, int n2, int linkType ) { //never store self-link if( n1 == n2 ) return qfalse; //already referenced if( AI_PlinkExists(n1, n2) ) return qfalse; if (linkType == LINK_INVALID) return qfalse; //add the link if (pLinks[n1].numLinks > NODES_MAX_PLINKS) { // G_Printf("MaxPlinks Reached! node:%i numPlinks:%i\n", n1, pLinks[n1].numLinks); return qfalse; } pLinks[n1].nodes[pLinks[n1].numLinks] = n2; pLinks[n1].dist[pLinks[n1].numLinks] = (int)AI_FindLinkDistance(n1, n2); pLinks[n1].moveType[pLinks[n1].numLinks] = linkType; pLinks[n1].numLinks++; return qtrue; } //========================================== // AI_PlinkExists // Just check if the plink is already stored //========================================== qboolean AI_PlinkExists(int n1, int n2) { int i; if( n1 == n2 ) return qfalse; for (i=0; i= waterorigin[2]) heightdiff = solidorigin[2] - waterorigin[2]; else heightdiff = waterorigin[2] - solidorigin[2]; if( heightdiff > AI_WATERJUMP_HEIGHT ) //jalfixme: find qtrue waterjump size return LINK_INVALID; //now find if bloqued waterorigin[2] = nodes[n2].origin[2]; trap_Trace( &trace, nodes[n1].origin, tv(-15,-15,0), tv(15,15,0), waterorigin, NULL, MASK_NODESOLID ); if( trace.fraction < 1.0 ) return LINK_INVALID; trap_Trace( &trace, waterorigin, tv(-15,-15,0), tv(15,15,0), nodes[n2].origin, NULL, MASK_NODESOLID ); if( trace.fraction < 1.0 ) return LINK_INVALID; return LINK_WATERJUMP; } //========================================== // AI_GravityBoxStep // move the box one step for walk movetype //========================================== int AI_GravityBoxStep( vec3_t origin, float scale, vec3_t destvec, vec3_t neworigin, vec3_t mins, vec3_t maxs ) { trace_t trace; vec3_t v1, v2, forward, up, angles, movedir; int movemask = 0; int eternalfall = 0; float xzdist, xzscale; float ydist, yscale; float dist; trap_Trace( &trace, origin, mins, maxs, origin, LINKS_PASSENT, MASK_NODESOLID ); if( trace.startsolid ) return LINK_INVALID; VectorSubtract( destvec, origin, movedir); VectorNormalize( movedir ); VecToAngles( movedir, angles ); //remaining distance in planes if( scale < 1 ) scale = 1; dist = DistanceFast( origin, destvec ); if( scale > dist ) scale = dist; xzscale = scale; xzdist = DistanceFast( tv(origin[0], origin[1], destvec[2]), destvec ); if( xzscale > xzdist ) xzscale = xzdist; yscale = scale; ydist = DistanceFast( tv(0,0,origin[2]), tv(0,0,destvec[2]) ); if( yscale > ydist ) yscale = ydist; //float move step if( trap_PointContents( origin ) & MASK_WATER ) { angles[ROLL] = 0; AngleVectors( angles, forward, NULL, up ); VectorMA( origin, scale, movedir, neworigin ); trap_Trace( &trace, origin, mins, maxs, neworigin, LINKS_PASSENT, MASK_NODESOLID); if( trace.startsolid || trace.fraction < 1.0 ) VectorCopy( origin, neworigin ); //update if valid if( VectorCompare(origin, neworigin) ) return LINK_INVALID; if( trap_PointContents( neworigin ) & MASK_WATER ) return LINK_WATER; //jal: Actually GravityBox can't leave water. //return INVALID and WATERJUMP, so it can validate the rest outside return (LINK_INVALID|LINK_WATERJUMP); } //gravity step angles[PITCH] = 0; angles[ROLL] = 0; AngleVectors( angles, forward, NULL, NULL ); VectorNormalize( forward ); // try moving forward VectorMA( origin, xzscale, forward, neworigin ); trap_Trace( &trace, origin, mins, maxs, neworigin, LINKS_PASSENT, MASK_NODESOLID ); if( trace.fraction == 1.0 ) //moved { movemask |= LINK_MOVE; goto droptofloor; } else { //bloqued, try stepping up VectorCopy( origin, v1 ); VectorMA( v1, xzscale, forward, v2 ); for( ; v1[2] < origin[2] + AI_JUMPABLE_HEIGHT; v1[2] += scale, v2[2] += scale ) { trap_Trace( &trace, v1, mins, maxs, v2, LINKS_PASSENT, MASK_NODESOLID ); if( !trace.startsolid && trace.fraction == 1.0 ) { VectorCopy( v2, neworigin ); if( origin[2] + AI_STEPSIZE > v2[2] ) movemask |= LINK_STAIRS; else movemask |= LINK_JUMP; goto droptofloor; } } //still failed, try slide move VectorMA( origin, xzscale, forward, neworigin ); trap_Trace( &trace, origin, mins, maxs, neworigin, LINKS_PASSENT, MASK_NODESOLID ); if( trace.plane.normal[2] < 0.5 && trace.plane.normal[2] >= -0.4 ) { VectorCopy( trace.endpos, neworigin ); VectorCopy( trace.plane.normal, v1 ); v1[2] = 0; VectorNormalize( v1 ); VectorMA( neworigin, xzscale, v1, neworigin ); //if new position is closer to destiny, might be valid if( DistanceFast( origin, destvec ) > DistanceFast( neworigin, destvec ) ) { trap_Trace( &trace, trace.endpos, mins, maxs, neworigin, LINKS_PASSENT, MASK_NODESOLID ); if( !trace.startsolid && trace.fraction == 1.0 ) goto droptofloor; } } VectorCopy( origin, neworigin ); return LINK_INVALID; } droptofloor: while(eternalfall < 20000) { if( trap_PointContents(neworigin) & MASK_WATER ) { if( origin[2] > neworigin[2] + AI_JUMPABLE_HEIGHT ) movemask |= LINK_FALL; else if ( origin[2] > neworigin[2] + AI_STEPSIZE ) movemask |= LINK_STAIRS; return movemask; } trap_Trace( &trace, neworigin, mins, maxs, tv(neworigin[0], neworigin[1], (neworigin[2] - AI_STEPSIZE)), LINKS_PASSENT, MASK_NODESOLID ); if( trace.startsolid ) { return LINK_INVALID; //G_Error("AI_GravityBoxStep: Trace startsolid in droptofloor\n"); } VectorCopy( trace.endpos, neworigin ); if( trace.fraction < 1.0 ) { if( origin[2] > neworigin[2] + AI_JUMPABLE_HEIGHT ) movemask |= LINK_FALL; else if ( origin[2] > neworigin[2] + AI_STEPSIZE ) movemask |= LINK_STAIRS; if( VectorCompare( origin, neworigin ) ) return LINK_INVALID; return movemask; } eternalfall++; } //G_Error ("ETERNAL FALL\n"); return LINK_INVALID; //jabot092 } //========================================== // AI_RunGravityBox // move a box along the link //========================================== int AI_RunGravityBox( int n1, int n2 ) { int move; int movemask = 0; float movescale = 8; trace_t trace; vec3_t boxmins, boxmaxs; vec3_t o1; vec3_t v1; //qboolean crouched = qfalse; int eternalcount = 0; if( n1 == n2 ) return LINK_INVALID; //set up box VectorCopy( playerbox_stand_mins, boxmins ); VectorCopy( playerbox_stand_maxs, boxmaxs ); //try some shortcuts before //water link shortcut if( trap_PointContents(nodes[n1].origin) & MASK_WATER && trap_PointContents(nodes[n2].origin) & MASK_WATER && AI_VisibleOrigins(nodes[n1].origin, nodes[n2].origin) ) return LINK_WATER; //waterjump link if( trap_PointContents(nodes[n1].origin) & MASK_WATER && !(trap_PointContents(nodes[n2].origin) & MASK_WATER) && DistanceFast(nodes[n1].origin, nodes[n2].origin) < NODE_DENSITY ) { if( AI_IsWaterJumpLink( n1, n2 ) == LINK_WATERJUMP ) return LINK_WATERJUMP; } //proceed //put box at first node VectorCopy( nodes[n1].origin, o1 ); trap_Trace( &trace, o1, boxmins, boxmaxs, o1, LINKS_PASSENT, MASK_NODESOLID ); if( trace.startsolid ) { //try crouched boxmaxs[2] = playerbox_crouch_maxs[2]; trap_Trace( &trace, o1, boxmins, boxmaxs, o1, LINKS_PASSENT, MASK_NODESOLID ); if( trace.startsolid ) return LINK_INVALID; //crouched = qtrue; movemask |= LINK_CROUCH; } //start moving the box to o2 while(eternalcount < 20000000) { move = AI_GravityBoxStep( o1, movescale, nodes[n2].origin, v1, boxmins, boxmaxs); if( move & LINK_INVALID && !(movemask & LINK_CROUCH)/*!crouched*/ ) { //retry crouched boxmaxs[2] = 14; //crouched = qtrue; movemask |= LINK_CROUCH; move = AI_GravityBoxStep( o1, movescale, nodes[n2].origin, v1, boxmins, boxmaxs); } if( move & LINK_INVALID ) { //gravitybox can't reach waterjump links. So, check them here if( move & LINK_WATERJUMP ) { if( AI_IsWaterJumpLink( n1, n2 ) == LINK_WATERJUMP ) return LINK_WATERJUMP; } return ( movemask|move ); } //next movemask |= move; VectorCopy( v1, o1 ); //check for reached if( DistanceFast( o1, nodes[n2].origin ) < 24 && AI_VisibleOrigins( o1, nodes[n2].origin ) ) { movemask |= move; return movemask; } eternalcount++; } G_Error ("ETERNAL COUNT\n"); //should never get here return 0; } //========================================== // AI_GravityBoxToLink // move a box along the link //========================================== int AI_GravityBoxToLink(int n1, int n2) { int link; //find out the link move type against the world link = AI_RunGravityBox( n1, n2 ); //don't fall to JUMPAD nodes, or will be sent up again if( nodes[n2].flags & NODEFLAGS_JUMPPAD && (link & LINK_FALL) ) return LINK_INVALID; //simplify if( link & LINK_INVALID ) return LINK_INVALID; if( link & LINK_CLIMB ) return LINK_INVALID; // No actual bot is able to climb if( link & LINK_WATERJUMP ) return LINK_WATERJUMP; if( link == LINK_WATER || link == (LINK_WATER|LINK_CROUCH) ) //only pure flags return LINK_WATER; if( link & LINK_CROUCH ) return LINK_CROUCH; if( link & LINK_JUMP ) return LINK_JUMP; //there are simple ledge jumps only if( link & LINK_FALL ) return LINK_FALL; if( link & LINK_STAIRS ) return LINK_STAIRS; return LINK_MOVE; } //========================================== // AI_FindFallOrigin // helper for AI_IsJumpLink //========================================== int AI_FindFallOrigin( int n1, int n2, vec3_t fallorigin ) { int move; int movemask = 0; float movescale = 8; trace_t trace; vec3_t boxmins, boxmaxs; vec3_t o1; vec3_t v1; int eternalcount = 0; if( n1 == n2 ) return LINK_INVALID; //set up box VectorCopy( playerbox_stand_mins, boxmins ); VectorCopy( playerbox_stand_maxs, boxmaxs ); //put box at first node VectorCopy( nodes[n1].origin, o1 ); trap_Trace( &trace, o1, boxmins, boxmaxs, o1, LINKS_PASSENT, MASK_NODESOLID ); if( trace.startsolid ) return LINK_INVALID; //moving the box to o2 until falls. Keep last origin before falling while( 1 ) { move = AI_GravityBoxStep( o1, movescale, nodes[n2].origin, v1, boxmins, boxmaxs); if( move & LINK_INVALID ) return LINK_INVALID; movemask |= move; if( move & LINK_FALL ) { VectorCopy( o1, fallorigin ); return LINK_FALL; } VectorCopy( v1, o1 ); //next step //check for reached ( which is invalid in this case ) if( DistanceFast( o1, nodes[n2].origin ) < 24 && AI_VisibleOrigins( o1, nodes[n2].origin ) ) { return LINK_INVALID; } eternalcount++; if( eternalcount > 200000 ) return LINK_INVALID; } return LINK_INVALID; } //========================================== // AI_LadderLink_FindUpperNode // finds upper node in same ladder, if any //========================================== int AI_LadderLink_FindUpperNode( int node ) { int i = INVALID; int j; vec3_t eorg; float xzdist; int candidate = INVALID; for( i=0; i 8) //not in our ladder continue; if( nodes[node].origin[2] > nodes[i].origin[2] ) //below continue; if( candidate == INVALID ) { //first found candidate = i; continue; } //shorter is better if( nodes[i].origin[2] - nodes[node].origin[2] < nodes[candidate].origin[2] - nodes[node].origin[2] ) candidate = i; } // if( candidate != -1 ) // Com_Printf( "LADDER: FOUND upper node in ladder\n"); return candidate; } //========================================== // AI_LadderLink_FindLowerNode // finds lower node in same ladder, if any //========================================== int AI_LadderLink_FindLowerNode( int node ) { int i = INVALID; int j; vec3_t eorg; float xzdist; int candidate = INVALID; for( i=0; i 8) //not in our ladder continue; if( nodes[i].origin[2] > nodes[node].origin[2] ) //above continue; if( candidate == INVALID ) { //first found candidate = i; continue; } //shorter is better if( nodes[node].origin[2] - nodes[i].origin[2] < nodes[node].origin[2] - nodes[candidate].origin[2] ) candidate = i; } // if( candidate != -1 ) // Com_Printf( "LADDER: FOUND lower node in ladder\n"); return candidate; } //========================================== // AI_IsLadderLink // interpretation of this link //========================================== int AI_IsLadderLink( int n1, int n2 ) { int j; vec3_t eorg; float xzdist; for (j=0 ; j<2 ; j++) eorg[j] = nodes[n2].origin[j] - nodes[n1].origin[j]; eorg[2] = 0; //ignore height xzdist = VectorLengthFast(eorg); if(xzdist < 0) xzdist = -xzdist; //if both are ladder nodes if( nodes[n1].flags & NODEFLAGS_LADDER && nodes[n2].flags & NODEFLAGS_LADDER ) { int candidate = AI_LadderLink_FindUpperNode( n1 ); if( candidate != n2 ) return LINK_INVALID; return LINK_LADDER; } //if only second is ladder node if( !(nodes[n1].flags & NODEFLAGS_LADDER) && nodes[n2].flags & NODEFLAGS_LADDER ) { int candidate; if( nodes[n1].flags & NODEFLAGS_WATER ) { if( AI_VisibleOrigins(nodes[n1].origin, nodes[n2].origin) ) { if( nodes[n2].flags & NODEFLAGS_WATER ) return LINK_WATER; else return LINK_LADDER; } return LINK_INVALID; } //only allow linking to the bottom ladder node from outside the ladder candidate = AI_LadderLink_FindLowerNode( n2 ); if( candidate == -1 ) { int link; if( nodes[n2].flags & NODEFLAGS_WATER ) { link = AI_RunGravityBox( n1, n2 ); if( link & LINK_INVALID ) return LINK_INVALID; return LINK_WATER; } else { return AI_GravityBoxToLink( n1, n2 ); } } return LINK_INVALID; } //if only first is ladder node if( nodes[n1].flags & NODEFLAGS_LADDER && !(nodes[n2].flags & NODEFLAGS_LADDER) ) { int candidate; int link; //if it has a upper ladder node, it can only link to it candidate = AI_LadderLink_FindUpperNode( n1 ); if( candidate != INVALID ) return LINK_INVALID; if( DistanceFast(nodes[n1].origin, nodes[n2].origin) > (NODE_DENSITY*0.8) ) return LINK_INVALID; link = AI_RunGravityBox( n2, n1 ); //try to reach backwards if( link & LINK_INVALID || link & LINK_FALL ) return LINK_INVALID; return LINK_LADDER; } return LINK_INVALID; } //========================================== // AI_FindLinkType // interpretation of this link //========================================== int AI_FindLinkType(int n1, int n2) { if( n1 == n2 || n1 == INVALID || n2 == INVALID ) return LINK_INVALID; if( AI_PlinkExists( n1, n2 )) return LINK_INVALID; //already saved //ignore server links if( nodes[n1].flags & NODEFLAGS_SERVERLINK || nodes[n2].flags & NODEFLAGS_SERVERLINK ) return LINK_INVALID; // they are added only by the server at dropping entity nodes //LINK_LADDER if( nodes[n1].flags & NODEFLAGS_LADDER || nodes[n2].flags & NODEFLAGS_LADDER ) return AI_IsLadderLink( n1, n2 ); //find out the link move type against the world return AI_GravityBoxToLink( n1, n2 ); } //========================================== // AI_IsJumpLink // interpretation of this link //========================================== int AI_IsJumpLink(int n1, int n2) { int link; int climblink = 0; if( n1 == n2 || n1 == INVALID || n2 == INVALID ) return LINK_INVALID; //ignore server links if( nodes[n1].flags & NODEFLAGS_SERVERLINK || nodes[n2].flags & NODEFLAGS_SERVERLINK ) return LINK_INVALID; // they are added only by the server at dropping entity nodes //LINK_LADDER if( nodes[n1].flags & NODEFLAGS_LADDER || nodes[n2].flags & NODEFLAGS_LADDER ) return LINK_INVALID; //can't jump if begins inside water if( nodes[n1].flags & NODEFLAGS_WATER ) return LINK_INVALID; //find out the link move type against the world link = AI_RunGravityBox( n1, n2 ); if( !(link & LINK_INVALID) ) // recheck backwards for climb links return LINK_INVALID; if( AI_PlinkExists( n2, n1 )) climblink = AI_PlinkMoveType( n2, n1 ); else climblink = AI_RunGravityBox( n2, n1 ); if( climblink & LINK_FALL ) { climblink &= ~LINK_FALL; link &= ~LINK_INVALID; link = (link|climblink|LINK_CLIMB); } //see if we can jump it if( link & LINK_CLIMB && link & LINK_FALL ) { vec3_t fo1, fo2; int link; float dist; float heightdiff; VectorSet( fo1, 0,0,0 ); VectorSet( fo2, 0,0,0 ); link = AI_FindFallOrigin( n1, n2, fo1 ); if( !(link & LINK_FALL) ) return LINK_INVALID; link = AI_FindFallOrigin( n2, n1, fo2 ); if( !(link & LINK_FALL) ) return LINK_INVALID; //reachable? (faster) if( !AI_VisibleOrigins( fo1, fo2 ) ) return LINK_INVALID; if( fo2[2] > fo1[2] + AI_JUMPABLE_HEIGHT ) //n1 is just too low return LINK_INVALID; heightdiff = fo2[2] - fo1[2]; //if n2 is higher, shorten xzplane dist if( heightdiff < 0 ) heightdiff = 0; //xzplane dist is valid? fo2[2] = fo1[2]; dist = DistanceFast( fo1, fo2 ); if( (dist + heightdiff) < AI_JUMPABLE_DISTANCE && dist > 24 ) return LINK_JUMP; } return LINK_INVALID; } //========================================== // AI_LinkCloseNodes_JumpPass // extended radius for jump links. // Standard movetypes nodes must be stored before calling this one //========================================== int AI_LinkCloseNodes_JumpPass( int start ) { int n1, n2; int count = 0; //float pLinkRadius = NODE_DENSITY*2; float pLinkRadius = AI_JUMPABLE_DISTANCE; qboolean ignoreHeight = qtrue; int linkType; if( nav.num_nodes < 1 ) return 0; //do it for everynode in the list for( n1 = start; n1 4 ) { if( AI_AddLink(n1, n2, LINK_JUMP) ) count++; } } } //next n2 = AI_findNodeInRadius ( n2, nodes[n1].origin, pLinkRadius, ignoreHeight); } } return count; } //========================================== // AI_LinkCloseNodes // track the nodes list and find close nodes around. Link them if possible //========================================== int AI_LinkCloseNodes( void ) { int n1, n2; int count = 0; float pLinkRadius = NODE_DENSITY*1.5; qboolean ignoreHeight = qtrue; //do it for everynode in the list for( n1=0; n1