1038 lines
29 KiB
C
1038 lines
29 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_movement.c 27 22/10/99 7:13 Riever $
|
|
*
|
|
* $Log: /LicenseToKill/src/acesrc/acebot_movement.c $
|
|
*
|
|
* 27 22/10/99 7:13 Riever
|
|
* New ladder detection function enables creation of ladder nodes.
|
|
* Distance checks added for ladder climbing.
|
|
* Specfial handling to reduce velocity at ladder mount point.
|
|
* Ledge handling code allows jumping up small obstacles.
|
|
* Air Walking codce found and removed.
|
|
* Ladder climbing code refined and working.
|
|
*
|
|
* 26 20/10/99 20:33 Riever
|
|
* Removed a velocity hack
|
|
*
|
|
* 25 16/10/99 18:10 Riever
|
|
* Messing with wander code - marked up.
|
|
*
|
|
* 24 16/10/99 17:03 Riever
|
|
* Added jump to shoot when line of sight is blocked.
|
|
*
|
|
* 23 16/10/99 13:11 Riever
|
|
* Changed special move code to get jumps working better.
|
|
*
|
|
* 22 10/10/99 9:34 Riever
|
|
* Anglechange before random move added
|
|
* Changed special move code.
|
|
*
|
|
* 21 6/10/99 20:46 Riever
|
|
* Taught bots to aim round teammates and not fire at walls.
|
|
*
|
|
* 20 6/10/99 17:40 Riever
|
|
* Added TeamPlay state STATE_POSITION to enable bots to seperate and
|
|
* avoid centipede formations.
|
|
*
|
|
* 19 1/10/99 18:19 Riever
|
|
* Removed a debug print
|
|
*
|
|
* 18 30/09/99 8:03 Riever
|
|
* Put a distance limit on where crouching starts in the random combat
|
|
* code.
|
|
*
|
|
* 17 27/09/99 14:28 Riever
|
|
* Added (UNUSED) code for AntPathMove as a reference for anyone who feels
|
|
* like experimenting with it in another bot.
|
|
*
|
|
* 16 21/09/99 22:35 Riever
|
|
* Comments...
|
|
*
|
|
* 15 21/09/99 21:29 Riever
|
|
* Changed checkeyes focalpoint to 64
|
|
*
|
|
* 14 21/09/99 21:25 Riever
|
|
* Steve was messing with the drown time so I took that bit out.
|
|
*
|
|
* 13 21/09/99 20:19 Riever
|
|
* Set teamPauseTime in attack routine.
|
|
*
|
|
* 12 21/09/99 20:14 Riever
|
|
* Reduced backwards movement on ledge avoidance.
|
|
*
|
|
* 11 21/09/99 12:21 Riever
|
|
* Ledge checking code inserted in movement functions to try to prevent
|
|
* sky diving...
|
|
*
|
|
* 10 21/09/99 11:18 Riever
|
|
* Changed logic checking in ledge code to avoid jumping off roofs.
|
|
*
|
|
* 9 18/09/99 19:20 Riever
|
|
* NODE_DOOR special handling added. I sweated blood over this so there's
|
|
* better not be any complaints!
|
|
*
|
|
* 8 18/09/99 9:51 Riever
|
|
* Reduced ledge check to 100 (was 400)
|
|
* Added some DroneBot code
|
|
*
|
|
* 7 14/09/99 14:37 Riever
|
|
* ForwardMove is now forced if only weapon available is the knife
|
|
*
|
|
* 6 14/09/99 14:15 Riever
|
|
* Made check for WEAPON_READY before firing
|
|
*
|
|
* 5 14/09/99 14:09 Riever
|
|
* Modified bot stupidity to always be off target a little if not
|
|
* ltk_skill == 10
|
|
*
|
|
* 4 14/09/99 8:35 Riever
|
|
* Updated comments - tested - works
|
|
*
|
|
* 3 14/09/99 8:13 Riever
|
|
* Now using ltk_skill cvar to control bot accuracy
|
|
*
|
|
* 2 13/09/99 19:52 Riever
|
|
* Added headers
|
|
*
|
|
*/
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// acebot_movement.c - This file contains all of the
|
|
// movement routines for the ACE bot
|
|
//
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
#include "../g_local.h"
|
|
#include "acebot.h"
|
|
|
|
qboolean ACEAI_CheckShot(edict_t *self);
|
|
void ACEMV_ChangeBotAngle (edict_t *ent);
|
|
qboolean ACEND_LadderForward( edict_t *self );
|
|
|
|
//=============================================================
|
|
// CanMoveSafely (dronebot)
|
|
//=============================================================
|
|
// Checks for lava and slime
|
|
#define MASK_DEADLY (CONTENTS_LAVA|CONTENTS_SLIME)
|
|
#define TRACE_DIST_SHORT 48
|
|
#define TRACE_DOWN 96
|
|
#define VEC_ORIGIN tv(0,0,0)
|
|
//
|
|
|
|
qboolean CanMoveSafely(edict_t *self, vec3_t angles)
|
|
{
|
|
vec3_t dir, angle, dest1, dest2;
|
|
trace_t trace;
|
|
float this_dist;
|
|
|
|
// self->bot_ai.next_safety_time = level.time + EYES_FREQ;
|
|
|
|
VectorClear(angle);
|
|
angle[1] = angles[1];
|
|
|
|
AngleVectors(angle, dir, NULL, NULL);
|
|
|
|
// create a position in front of the bot
|
|
VectorMA(self->s.origin, TRACE_DIST_SHORT, dir, dest1);
|
|
|
|
// Modified to check for crawl direction
|
|
//trace = gi.trace(self->s.origin, mins, self->maxs, dest, self, MASK_SOLID);
|
|
//TempLaser (self->s.origin, dest1);
|
|
trace = gi.trace(self->s.origin, tv(-16,-16,0), tv(16,16,0), dest1, self, MASK_PLAYERSOLID);
|
|
|
|
// Check if we are looking inside a wall!
|
|
if (trace.startsolid)
|
|
return (true);
|
|
|
|
if (trace.fraction > 0)
|
|
{ // check that destination is onground, or not above lava/slime
|
|
dest1[0] = trace.endpos[0];
|
|
dest1[1] = trace.endpos[1];
|
|
dest1[2] = trace.endpos[2] - 28;
|
|
this_dist = trace.fraction * TRACE_DIST_SHORT;
|
|
|
|
if (gi.pointcontents(dest1) & MASK_PLAYERSOLID)
|
|
return (true);
|
|
|
|
|
|
// create a position a distance below it
|
|
VectorCopy( trace.endpos, dest2);
|
|
dest2[2] -= TRACE_DOWN;
|
|
//TempLaser (trace.endpos, dest2);
|
|
trace = gi.trace(trace.endpos, VEC_ORIGIN, VEC_ORIGIN, dest2, self, MASK_PLAYERSOLID | MASK_DEADLY);
|
|
|
|
if( (trace.fraction == 1.0) // long drop!
|
|
|| (trace.contents & MASK_DEADLY) ) // avoid SLIME or LAVA
|
|
{
|
|
return (false);
|
|
}
|
|
else
|
|
{
|
|
return (true);
|
|
}
|
|
}
|
|
//gi.bprintf(PRINT_HIGH,"Default failure from LAVACHECK\n");
|
|
return (true);
|
|
}
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Checks if bot can move (really just checking the ground)
|
|
// Also, this is not a real accurate check, but does a
|
|
// pretty good job and looks for lava/slime.
|
|
///////////////////////////////////////////////////////////////////////
|
|
qboolean ACEMV_CanMove(edict_t *self, int direction)
|
|
{
|
|
vec3_t forward, right;
|
|
vec3_t offset,start,end;
|
|
vec3_t angles;
|
|
trace_t tr;
|
|
|
|
// Now check to see if move will move us off an edge
|
|
VectorCopy(self->s.angles,angles);
|
|
|
|
if(direction == MOVE_LEFT)
|
|
angles[1] += 90;
|
|
else if(direction == MOVE_RIGHT)
|
|
angles[1] -= 90;
|
|
else if(direction == MOVE_BACK)
|
|
angles[1] -=180;
|
|
|
|
// Set up the vectors
|
|
AngleVectors (angles, forward, right, NULL);
|
|
|
|
VectorSet(offset, 36, 0, 24);
|
|
G_ProjectSource (self->s.origin, offset, forward, right, start);
|
|
|
|
VectorSet(offset, 36, 0, -100); // RiEvEr reduced drop distance
|
|
G_ProjectSource (self->s.origin, offset, forward, right, end);
|
|
|
|
//AQ2 ADDED MASK_SOLID
|
|
tr = gi.trace(start, NULL, NULL, end, self, MASK_SOLID|MASK_OPAQUE);
|
|
|
|
if(tr.fraction == 1.0 || tr.contents & (CONTENTS_LAVA|CONTENTS_SLIME))
|
|
{
|
|
if(debug_mode)
|
|
debug_printf("%s: move blocked\n",self->client->pers.netname);
|
|
return false;
|
|
}
|
|
|
|
return true; // yup, can move
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Handle special cases of crouch/jump
|
|
//
|
|
// If the move is resolved here, this function returns
|
|
// true.
|
|
///////////////////////////////////////////////////////////////////////
|
|
qboolean ACEMV_SpecialMove(edict_t *self, usercmd_t *ucmd)
|
|
{
|
|
vec3_t dir,forward,right,start,end,offset;
|
|
vec3_t top;
|
|
trace_t tr;
|
|
|
|
// Get current direction
|
|
VectorCopy(self->client->ps.viewangles,dir);
|
|
dir[YAW] = self->s.angles[YAW];
|
|
AngleVectors (dir, forward, right, NULL);
|
|
|
|
VectorSet(offset, 0, 0, 0); // changed from 18,0,0
|
|
G_ProjectSource (self->s.origin, offset, forward, right, start);
|
|
offset[0] += 18;
|
|
G_ProjectSource (self->s.origin, offset, forward, right, end);
|
|
|
|
// trace it
|
|
start[2] += 18; // so they are not jumping all the time
|
|
end[2] += 18;
|
|
tr = gi.trace (start, self->mins, self->maxs, end, self, MASK_MONSTERSOLID);
|
|
|
|
//RiEvEr if(tr.allsolid)
|
|
if( tr.fraction < 1.0)
|
|
{
|
|
// Check for crouching
|
|
start[2] -= 14;
|
|
end[2] -= 14;
|
|
|
|
// Set up for crouching check
|
|
VectorCopy(self->maxs,top);
|
|
top[2] = 0.0; // crouching height
|
|
tr = gi.trace (start, self->mins, top, end, self, MASK_PLAYERSOLID);
|
|
|
|
// Crouch
|
|
//RiEvEr if(!tr.allsolid)
|
|
if( tr.fraction == 1.0 )
|
|
{
|
|
ucmd->forwardmove = 400;
|
|
ucmd->upmove = -400;
|
|
return true;
|
|
}
|
|
|
|
// Check for jump
|
|
start[2] += 32;
|
|
end[2] += 32;
|
|
tr = gi.trace (start, self->mins, self->maxs, end, self, MASK_MONSTERSOLID);
|
|
|
|
//RiEvEr if(!tr.allsolid)
|
|
if( tr.fraction == 1.0)
|
|
{
|
|
ucmd->forwardmove = 400;
|
|
ucmd->upmove = 400;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false; // We did not resolve a move here
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Checks for obstructions in front of bot
|
|
//
|
|
// This is a function I created origianlly for ACE that
|
|
// tries to help steer the bot around obstructions.
|
|
//
|
|
// If the move is resolved here, this function returns true.
|
|
///////////////////////////////////////////////////////////////////////
|
|
qboolean ACEMV_CheckEyes(edict_t *self, usercmd_t *ucmd)
|
|
{
|
|
vec3_t forward, right;
|
|
vec3_t leftstart, rightstart,focalpoint;
|
|
vec3_t /* upstart,*/upend;
|
|
vec3_t dir,offset;
|
|
|
|
trace_t traceRight,traceLeft,/*traceUp,*/ traceFront; // for eyesight
|
|
|
|
// Get current angle and set up "eyes"
|
|
VectorCopy(self->s.angles,dir);
|
|
AngleVectors (dir, forward, right, NULL);
|
|
|
|
// Let them move to targets by walls
|
|
if(!self->movetarget)
|
|
// VectorSet(offset,200,0,4); // focalpoint
|
|
VectorSet(offset,64,0,4); // focalpoint
|
|
else
|
|
VectorSet(offset,36,0,4); // focalpoint
|
|
|
|
G_ProjectSource (self->s.origin, offset, forward, right, focalpoint);
|
|
|
|
// Check from self to focalpoint
|
|
// Ladder code
|
|
VectorSet(offset,36,0,0); // set as high as possible
|
|
G_ProjectSource (self->s.origin, offset, forward, right, upend);
|
|
//AQ2 ADDED MASK_SOLID
|
|
traceFront = gi.trace(self->s.origin, self->mins, self->maxs, upend, self, MASK_SOLID|MASK_OPAQUE);
|
|
|
|
if(traceFront.contents & 0x8000000) // using detail brush here cuz sometimes it does not pick up ladders...??
|
|
{
|
|
ucmd->upmove = 400;
|
|
ucmd->forwardmove = 400;
|
|
return true;
|
|
}
|
|
|
|
// If this check fails we need to continue on with more detailed checks
|
|
if(traceFront.fraction == 1)
|
|
{
|
|
ucmd->forwardmove = 400;
|
|
return true;
|
|
}
|
|
|
|
VectorSet(offset, 0, 18, 4);
|
|
G_ProjectSource (self->s.origin, offset, forward, right, leftstart);
|
|
|
|
offset[1] -= 36; // want to make sure this is correct
|
|
//VectorSet(offset, 0, -18, 4);
|
|
G_ProjectSource (self->s.origin, offset, forward, right, rightstart);
|
|
|
|
traceRight = gi.trace(rightstart, NULL, NULL, focalpoint, self, MASK_OPAQUE);
|
|
traceLeft = gi.trace(leftstart, NULL, NULL, focalpoint, self, MASK_OPAQUE);
|
|
|
|
// Wall checking code, this will degenerate progressivly so the least cost
|
|
// check will be done first.
|
|
|
|
// If open space move ok
|
|
if(traceRight.fraction != 1 || traceLeft.fraction != 1 || strcmp(traceLeft.ent->classname,"func_door")!=0)
|
|
{
|
|
//@@ weird code...
|
|
/* // Special uppoint logic to check for slopes/stairs/jumping etc.
|
|
VectorSet(offset, 0, 18, 24);
|
|
G_ProjectSource (self->s.origin, offset, forward, right, upstart);
|
|
|
|
VectorSet(offset,0,0,200); // scan for height above head
|
|
G_ProjectSource (self->s.origin, offset, forward, right, upend);
|
|
traceUp = gi.trace(upstart, NULL, NULL, upend, self, MASK_OPAQUE);
|
|
|
|
VectorSet(offset,200,0,200*traceUp.fraction-5); // set as high as possible
|
|
G_ProjectSource (self->s.origin, offset, forward, right, upend);
|
|
traceUp = gi.trace(upstart, NULL, NULL, upend, self, MASK_OPAQUE);
|
|
|
|
// If the upper trace is not open, we need to turn.
|
|
if(traceUp.fraction != 1)*/
|
|
{
|
|
if(traceRight.fraction > traceLeft.fraction)
|
|
self->s.angles[YAW] += (1.0 - traceLeft.fraction) * 45.0;
|
|
else
|
|
self->s.angles[YAW] += -(1.0 - traceRight.fraction) * 45.0;
|
|
|
|
ucmd->forwardmove = 400;
|
|
ACEMV_ChangeBotAngle(self);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Make the change in angles a little more gradual, not so snappy
|
|
// Subtle, but noticeable.
|
|
//
|
|
// Modified from the original id ChangeYaw code...
|
|
///////////////////////////////////////////////////////////////////////
|
|
void ACEMV_ChangeBotAngle (edict_t *ent)
|
|
{
|
|
float ideal_yaw;
|
|
float ideal_pitch;
|
|
float current_yaw;
|
|
float current_pitch;
|
|
float move;
|
|
float speed;
|
|
vec3_t ideal_angle;
|
|
|
|
// Normalize the move angle first
|
|
VectorNormalize(ent->move_vector);
|
|
|
|
current_yaw = anglemod(ent->s.angles[YAW]);
|
|
current_pitch = anglemod(ent->s.angles[PITCH]);
|
|
|
|
vectoangles (ent->move_vector, ideal_angle);
|
|
|
|
ideal_yaw = anglemod(ideal_angle[YAW]);
|
|
ideal_pitch = anglemod(ideal_angle[PITCH]);
|
|
|
|
// Yaw
|
|
if (current_yaw != ideal_yaw)
|
|
{
|
|
move = ideal_yaw - current_yaw;
|
|
speed = ent->yaw_speed;
|
|
if (ideal_yaw > current_yaw)
|
|
{
|
|
if (move >= 180)
|
|
move = move - 360;
|
|
}
|
|
else
|
|
{
|
|
if (move <= -180)
|
|
move = move + 360;
|
|
}
|
|
if (move > 0)
|
|
{
|
|
if (move > speed)
|
|
move = speed;
|
|
}
|
|
else
|
|
{
|
|
if (move < -speed)
|
|
move = -speed;
|
|
}
|
|
ent->s.angles[YAW] = anglemod (current_yaw + move);
|
|
}
|
|
|
|
// Pitch
|
|
if (current_pitch != ideal_pitch)
|
|
{
|
|
move = ideal_pitch - current_pitch;
|
|
speed = ent->yaw_speed;
|
|
if (ideal_pitch > current_pitch)
|
|
{
|
|
if (move >= 180)
|
|
move = move - 360;
|
|
}
|
|
else
|
|
{
|
|
if (move <= -180)
|
|
move = move + 360;
|
|
}
|
|
if (move > 0)
|
|
{
|
|
if (move > speed)
|
|
move = speed;
|
|
}
|
|
else
|
|
{
|
|
if (move < -speed)
|
|
move = -speed;
|
|
}
|
|
ent->s.angles[PITCH] = anglemod (current_pitch + move);
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Set bot to move to it's movetarget. (following node path)
|
|
///////////////////////////////////////////////////////////////////////
|
|
void ACEMV_MoveToGoal(edict_t *self, usercmd_t *ucmd)
|
|
{
|
|
// If a rocket or grenade is around deal with it
|
|
// Simple, but effective (could be rewritten to be more accurate)
|
|
if(strcmp(self->movetarget->classname,"rocket")==0 ||
|
|
strcmp(self->movetarget->classname,"grenade")==0)
|
|
{
|
|
VectorSubtract (self->movetarget->s.origin, self->s.origin, self->move_vector);
|
|
ACEMV_ChangeBotAngle(self);
|
|
if(debug_mode)
|
|
debug_printf("%s: Oh crap a rocket!\n",self->client->pers.netname);
|
|
|
|
// strafe left/right
|
|
if(rand()%1 && ACEMV_CanMove(self, MOVE_LEFT))
|
|
ucmd->sidemove = -400;
|
|
else if(ACEMV_CanMove(self, MOVE_RIGHT))
|
|
ucmd->sidemove = 400;
|
|
return;
|
|
|
|
}
|
|
else
|
|
{
|
|
// Set bot's movement direction
|
|
VectorSubtract (self->movetarget->s.origin, self->s.origin, self->move_vector);
|
|
ACEMV_ChangeBotAngle(self);
|
|
ucmd->forwardmove = 400;
|
|
return;
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Main movement code. (following node path)
|
|
///////////////////////////////////////////////////////////////////////
|
|
void ACEMV_Move(edict_t *self, usercmd_t *ucmd)
|
|
{
|
|
vec3_t dist;
|
|
int current_node_type=-1;
|
|
int next_node_type=-1;
|
|
int i;
|
|
float distance;
|
|
|
|
// Get current and next node back from nav code.
|
|
if(!ACEND_FollowPath(self))
|
|
{
|
|
if(
|
|
( !teamplay->value ) ||
|
|
(teamplay->value && level.time >= (self->teamPauseTime))
|
|
)
|
|
{
|
|
self->state = STATE_WANDER;
|
|
self->wander_timeout = level.time + 1.0;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// Teamplay mode - just fan out and chill
|
|
self->state = STATE_POSITION;
|
|
self->goal_node = INVALID;
|
|
self->wander_timeout = level.time + 1.0;
|
|
return;
|
|
}
|
|
}
|
|
|
|
current_node_type = nodes[self->current_node].type;
|
|
next_node_type = nodes[self->next_node].type;
|
|
|
|
///////////////////////////
|
|
// Move To Goal
|
|
///////////////////////////
|
|
if (self->movetarget)
|
|
ACEMV_MoveToGoal(self,ucmd);
|
|
|
|
/* ////////////////////////////////////////////////////////
|
|
// Grapple
|
|
///////////////////////////////////////////////////////
|
|
if(next_node_type == NODE_GRAPPLE)
|
|
{
|
|
ACEMV_ChangeBotAngle(self);
|
|
ACEIT_ChangeWeapon(self,FindItem("grapple"));
|
|
ucmd->buttons = BUTTON_ATTACK;
|
|
return;
|
|
}
|
|
// Reset the grapple if hangin on a graple node
|
|
if(current_node_type == NODE_GRAPPLE)
|
|
{
|
|
CTFPlayerResetGrapple(self);
|
|
return;
|
|
}*/
|
|
////////////////////////////////////////////////////////
|
|
// Doors - RiEvEr
|
|
///////////////////////////////////////////////////////
|
|
if(current_node_type == NODE_DOOR)
|
|
{
|
|
// check to see if door is open?
|
|
for(i=0;i<num_items;i++)
|
|
{
|
|
if(item_table[i].node == self->current_node)
|
|
{
|
|
if( (item_table[i].ent->moveinfo.state != STATE_TOP) &&
|
|
(item_table[i].ent->moveinfo.state != STATE_UP) )
|
|
{
|
|
vec3_t difference;
|
|
float distance;
|
|
|
|
// Find distance to the door
|
|
VectorSubtract (self->s.origin, nodes[self->current_node].origin, difference);
|
|
distance = VectorLength(difference);
|
|
|
|
// If not close enough carry on moving
|
|
if( (distance > 64.0)
|
|
&& ( VectorLength(item_table[i].ent->avelocity) == 0.0) )
|
|
{
|
|
ucmd->forwardmove = 200;
|
|
return;
|
|
}
|
|
|
|
// Within range
|
|
if( self->last_door_time < (level.time - 2.0) )
|
|
{
|
|
Cmd_OpenDoor_f ( self ); // Open the door
|
|
self->last_door_time = level.time; // wait!
|
|
}
|
|
ucmd->forwardmove = -200; //walk backwards a little
|
|
return; // Wait for door to open
|
|
}
|
|
else if( VectorLength(item_table[i].ent->avelocity) > 0.0)
|
|
{
|
|
ucmd->forwardmove = -200; //walk backwards a little
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////
|
|
// Platforms
|
|
///////////////////////////////////////////////////////
|
|
if(current_node_type != NODE_PLATFORM && next_node_type == NODE_PLATFORM)
|
|
{
|
|
// check to see if lift is down?
|
|
for(i=0;i<num_items;i++)
|
|
if(item_table[i].node == self->next_node)
|
|
if(item_table[i].ent->moveinfo.state != STATE_BOTTOM)
|
|
return; // Wait for elevator
|
|
}
|
|
if(current_node_type == NODE_PLATFORM && next_node_type == NODE_PLATFORM)
|
|
{
|
|
// Move to the center
|
|
self->move_vector[2] = 0; // kill z movement
|
|
if(VectorLength(self->move_vector) > 10)
|
|
ucmd->forwardmove = 200; // walk to center
|
|
|
|
ACEMV_ChangeBotAngle(self);
|
|
|
|
return; // No move, riding elevator
|
|
}
|
|
|
|
////////////////////////////////////////////////////////
|
|
// Jumpto Nodes
|
|
///////////////////////////////////////////////////////
|
|
if(next_node_type == NODE_JUMP ||
|
|
(current_node_type == NODE_JUMP && next_node_type != NODE_ITEM && nodes[self->next_node].origin[2] > self->s.origin[2]))
|
|
{
|
|
// Set up a jump move
|
|
ucmd->forwardmove = 400;
|
|
ucmd->upmove = 400;
|
|
|
|
ACEMV_ChangeBotAngle(self);
|
|
|
|
// RiEvEr - kill the velocity hack
|
|
/* VectorCopy(self->move_vector,dist);
|
|
VectorScale(dist,440,self->velocity);*/
|
|
|
|
return;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////
|
|
// Ladder Nodes
|
|
///////////////////////////////////////////////////////
|
|
|
|
// Find the distance vector to the next node
|
|
VectorSubtract( nodes[self->next_node].origin, self->s.origin, dist);
|
|
// Lose the vertical component
|
|
dist[2] = 0;
|
|
// Get the absolute length
|
|
distance = VectorLength(dist);
|
|
|
|
if(next_node_type == NODE_LADDER && //(gi.pointcontents(self->s.origin) & CONTENTS_LADDER) &&
|
|
nodes[self->next_node].origin[2] > self->s.origin[2] &&
|
|
distance < NODE_DENSITY)
|
|
{
|
|
// Otherwise move as fast as we can
|
|
ucmd->forwardmove = 100; // Reduced from 400
|
|
self->velocity[2] = 320; // Reduced from 320
|
|
ucmd->sidemove = 0;
|
|
|
|
ACEMV_ChangeBotAngle(self);
|
|
|
|
return;
|
|
|
|
}
|
|
// If getting off the ladder
|
|
if(
|
|
(current_node_type == NODE_LADDER && next_node_type != NODE_LADDER &&
|
|
nodes[self->next_node].origin[2] > self->s.origin[2])
|
|
)
|
|
{
|
|
ucmd->forwardmove = 200; // Reduced from 400
|
|
// ucmd->upmove = 200;
|
|
self->velocity[2] = 200;
|
|
ACEMV_ChangeBotAngle(self);
|
|
return;
|
|
}
|
|
// If getting onto the ladder
|
|
if(
|
|
(current_node_type != NODE_LADDER && next_node_type == NODE_LADDER )//&&
|
|
// (nodes[self->next_node].origin[2] > self->s.origin[2])
|
|
)
|
|
{
|
|
ucmd->forwardmove = 100; // Reduced from 400
|
|
// ucmd->upmove = 200;
|
|
// self->velocity[2] = 200;
|
|
ACEMV_ChangeBotAngle(self);
|
|
return;
|
|
}
|
|
|
|
//==============================
|
|
// LEDGES etc..
|
|
// If trying to jump up a ledge
|
|
if(
|
|
(current_node_type == NODE_MOVE &&
|
|
nodes[self->next_node].origin[2] > self->s.origin[2]+16 && distance < NODE_DENSITY)
|
|
)
|
|
{
|
|
ucmd->forwardmove = 400;
|
|
ucmd->upmove = 400;
|
|
ACEMV_ChangeBotAngle(self);
|
|
return;
|
|
}
|
|
////////////////////////////////////////////////////////
|
|
// Water Nodes
|
|
///////////////////////////////////////////////////////
|
|
if(current_node_type == NODE_WATER)
|
|
{
|
|
// We need to be pointed up/down
|
|
ACEMV_ChangeBotAngle(self);
|
|
|
|
// If the next node is not in the water, then move up to get out.
|
|
if(next_node_type != NODE_WATER && !(gi.pointcontents(nodes[self->next_node].origin) & MASK_WATER)) // Exit water
|
|
ucmd->upmove = 400;
|
|
|
|
ucmd->forwardmove = 300;
|
|
return;
|
|
|
|
}
|
|
|
|
// Falling off ledge?
|
|
/* if(!self->groundentity)
|
|
{
|
|
ACEMV_ChangeBotAngle(self);
|
|
|
|
self->velocity[0] = self->move_vector[0] * 360;
|
|
self->velocity[1] = self->move_vector[1] * 360;
|
|
|
|
return;
|
|
}*/
|
|
|
|
// Check to see if stuck, and if so try to free us
|
|
// Also handles crouching
|
|
if(VectorLength(self->velocity) < 37)
|
|
{
|
|
// Keep a random factor just in case....
|
|
if(random() > 0.1 && ACEMV_SpecialMove(self, ucmd))
|
|
return;
|
|
|
|
self->s.angles[YAW] += random() * 180 - 90;
|
|
|
|
ACEMV_ChangeBotAngle(self);
|
|
|
|
ucmd->forwardmove = 400;
|
|
|
|
return;
|
|
}
|
|
|
|
ACEMV_ChangeBotAngle(self);
|
|
|
|
// Otherwise move as fast as we can
|
|
// If it's safe to move forward (I can't believe ACE didn't check this!
|
|
if( ACEMV_CanMove( self, MOVE_FORWARD) || (current_node_type == NODE_LADDER) )
|
|
{
|
|
ucmd->forwardmove = 400;
|
|
}
|
|
else
|
|
{
|
|
// Forget about this route!
|
|
// ucmd->forwardmove = -100;
|
|
self->movetarget = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Wandering code (based on old ACE movement code)
|
|
///////////////////////////////////////////////////////////////////////
|
|
//
|
|
// RiEvEr - this routine is of a very poor standard and has a LOT of problems
|
|
// Maybe replace this with the DroneBot or ReDeMpTiOn wander code?
|
|
//
|
|
void ACEMV_Wander(edict_t *self, usercmd_t *ucmd)
|
|
{
|
|
vec3_t temp;
|
|
|
|
// Do not move
|
|
if(self->next_move_time > level.time)
|
|
return;
|
|
|
|
// Special check for elevators, stand still until the ride comes to a complete stop.
|
|
if(self->groundentity != NULL && self->groundentity->use == Use_Plat)
|
|
if(self->groundentity->moveinfo.state == STATE_UP ||
|
|
self->groundentity->moveinfo.state == STATE_DOWN) // only move when platform not
|
|
{
|
|
self->velocity[0] = 0;
|
|
self->velocity[1] = 0;
|
|
self->velocity[2] = 0;
|
|
self->next_move_time = level.time + 0.5;
|
|
return;
|
|
}
|
|
|
|
|
|
// Is there a target to move to
|
|
if (self->movetarget)
|
|
ACEMV_MoveToGoal(self,ucmd);
|
|
|
|
////////////////////////////////
|
|
// Swimming?
|
|
////////////////////////////////
|
|
VectorCopy(self->s.origin,temp);
|
|
temp[2]+=24;
|
|
|
|
if(gi.pointcontents (temp) & MASK_WATER)
|
|
{
|
|
// If drowning and no node, move up
|
|
if(self->client->next_drown_time > 0)
|
|
{
|
|
ucmd->upmove = 1;
|
|
self->s.angles[PITCH] = -45;
|
|
}
|
|
else
|
|
ucmd->upmove = 15;
|
|
|
|
ucmd->forwardmove = 300;
|
|
}
|
|
// else
|
|
// self->client->next_drown_time = 0; // probably shound not be messing with this, but
|
|
|
|
////////////////////////////////
|
|
// Lava?
|
|
////////////////////////////////
|
|
temp[2]-=48;
|
|
if(gi.pointcontents(temp) & (CONTENTS_LAVA|CONTENTS_SLIME))
|
|
{
|
|
// safe_bprintf(PRINT_MEDIUM,"lava jump\n");
|
|
self->s.angles[YAW] += random() * 360 - 180;
|
|
ucmd->forwardmove = 400;
|
|
ucmd->upmove = 400;
|
|
return;
|
|
}
|
|
|
|
//@@ if(ACEMV_CheckEyes(self,ucmd))
|
|
// return;
|
|
|
|
// Check for special movement if we have a normal move (have to test)
|
|
if(VectorLength(self->velocity) < 37)
|
|
{
|
|
if(random() > 0.1 && ACEMV_SpecialMove(self,ucmd))
|
|
return;
|
|
|
|
self->s.angles[YAW] += random() * 180 - 90;
|
|
|
|
if(!M_CheckBottom && !self->groundentity) // if there is ground continue otherwise wait for next move
|
|
ucmd->forwardmove = 0;
|
|
else if( ACEMV_CanMove( self, MOVE_FORWARD))
|
|
ucmd->forwardmove = 200;
|
|
|
|
return;
|
|
}
|
|
|
|
// Otherwise move as fast as we can
|
|
// If it's safe to move forward (I can't believe ACE didn't check this!
|
|
if( ACEMV_CanMove( self, MOVE_FORWARD))
|
|
{
|
|
ucmd->forwardmove = 400;
|
|
}
|
|
else
|
|
{
|
|
// Need a "findbestdirection" routine in here
|
|
// Forget about this route!
|
|
ucmd->forwardmove = -100;
|
|
self->movetarget = NULL;
|
|
}
|
|
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// Attack movement routine
|
|
//
|
|
// NOTE: Very simple for now, just a basic move about avoidance.
|
|
// Change this routine for more advanced attack movement.
|
|
///////////////////////////////////////////////////////////////////////
|
|
void ACEMV_Attack (edict_t *self, usercmd_t *ucmd)
|
|
{
|
|
float c;
|
|
vec3_t target;
|
|
vec3_t angles;
|
|
vec3_t attackvector;
|
|
float dist;
|
|
|
|
// Randomly choose a movement direction
|
|
c = random();
|
|
|
|
if(c < 0.2 && ACEMV_CanMove(self,MOVE_LEFT))
|
|
ucmd->sidemove -= 400;
|
|
else if(c < 0.4 && ACEMV_CanMove(self,MOVE_RIGHT))
|
|
ucmd->sidemove += 400;
|
|
|
|
//AQ2 CHANGE
|
|
// Don't stand around if all you have is a knife!
|
|
if( self->client->pers.weapon == FindItem(KNIFE_NAME) )
|
|
{
|
|
ucmd->forwardmove += 400;
|
|
}
|
|
else
|
|
{
|
|
if(c < 0.6 && ACEMV_CanMove(self,MOVE_FORWARD))
|
|
ucmd->forwardmove += 400;
|
|
else if(c < 0.8 && ACEMV_CanMove(self,MOVE_FORWARD))
|
|
ucmd->forwardmove -= 400;
|
|
}
|
|
//AQ2 END
|
|
// Check distance to enemy
|
|
VectorSubtract( self->s.origin, self->enemy->s.origin, attackvector);
|
|
dist = VectorLength( attackvector);
|
|
|
|
if( (dist < 600) && !(self->client->pers.weapon == FindItem(KNIFE_NAME)) )
|
|
{
|
|
// Randomly choose a vertical movement direction
|
|
c = random();
|
|
|
|
if(c < 0.15)
|
|
ucmd->upmove += 200;
|
|
else
|
|
ucmd->upmove -= 200;
|
|
}
|
|
|
|
// Set the attack
|
|
//@@ Check this doesn't break grenades!
|
|
if((self->client->weaponstate == WEAPON_READY)||(self->client->weaponstate == WEAPON_FIRING))
|
|
{
|
|
// Only shoot if the weapon is ready and we can hit the target!
|
|
//@@ Removed to try to help RobbieBoy!
|
|
// if( ACEAI_CheckShot( self ))
|
|
ucmd->buttons = BUTTON_ATTACK;
|
|
// else
|
|
// ucmd->upmove = 200;
|
|
}
|
|
|
|
// Aim
|
|
VectorCopy(self->enemy->s.origin,target);
|
|
|
|
// modify attack angles based on accuracy (mess this up to make the bot's aim not so deadly)
|
|
// target[0] += (random()-0.5) * 20;
|
|
// target[1] += (random()-0.5) * 20;
|
|
|
|
//AQ2 ADD - RiEvEr
|
|
// Alter aiming based on skill level
|
|
if( ltk_skill->value < 10 )
|
|
{
|
|
short int up, right;
|
|
up = (random() < 0.5)? -1 :1;
|
|
right = (random() < 0.5)? -1 : 1;
|
|
|
|
// Not that complex. We miss by 0 to 80 units based on skill value and random factor
|
|
target[0] += ( right * (10 - ltk_skill->value +((8*(10 - ltk_skill->value)) *random())) );
|
|
target[2] += ( up * (10 - ltk_skill->value +((8*(10 - ltk_skill->value)) *random())) );
|
|
}
|
|
//AQ2 END
|
|
|
|
// Set direction
|
|
VectorSubtract (target, self->s.origin, self->move_vector);
|
|
vectoangles (self->move_vector, angles);
|
|
VectorCopy(angles,self->s.angles);
|
|
|
|
// Store time we last saw an enemy
|
|
// This value is used to decide if we initiate a long range search or not.
|
|
self->teamPauseTime = level.time;
|
|
|
|
// if(debug_mode)
|
|
// debug_printf("%s attacking %s\n",self->client->pers.netname,self->enemy->client->pers.netname);
|
|
}
|
|
|
|
//==========================
|
|
// AntPathMove
|
|
//==========================
|
|
//
|
|
qboolean AntPathMove( edict_t *self )
|
|
{
|
|
node_t *temp = &nodes[self->current_node]; // For checking our position
|
|
|
|
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 ai.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
|
|
// 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;
|
|
}
|
|
|
|
// And change our pathfinding to use it
|
|
if ( !SLLempty(&self->pathList) ) // We have a path
|
|
{
|
|
// if( AtNextNode( self->s.origin, SLLfront(&self->pathList)) // we're at the nextnode
|
|
if( ACEND_FindClosestReachableNode(self,NODE_DENSITY*0.66,NODE_ALL) == SLLfront(&self->pathList) // we're at the nextnode
|
|
|| self->current_node == SLLfront(&self->pathList)
|
|
)
|
|
{
|
|
self->current_node = SLLfront(&self->pathList); // Show we're there
|
|
SLLpop_front(&self->pathList); // Remove the top node
|
|
}
|
|
if( !SLLempty(&self->pathList) )
|
|
self->next_node = SLLfront(&self->pathList); // Set our next destination
|
|
else
|
|
self->next_node = INVALID; // We're at the target location
|
|
return true;
|
|
}
|
|
return true; // Pathlist is emptyand we are at our destination
|
|
}
|
|
|