quake2-action/acesrc/acebot_movement.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
}