mirror of
https://git.code.sf.net/p/quake/game-source
synced 2024-11-10 14:41:57 +00:00
8935fc4c84
test classname `properly' (ie, with -classname) bot_move.qc: re-implement the lost obstruction detection code in a way suitable for the new physics handling. Probably needs tuning. bot_qw.qc libfrikbot.h: Break and itos for debugging waypoint.r: implement -realorigin. oops. that caused how long a wild goose chase?
534 lines
13 KiB
C++
534 lines
13 KiB
C++
/***********************************************
|
|
* *
|
|
* FrikBot Movement AI *
|
|
* "The slightly better movement AI" *
|
|
* *
|
|
***********************************************/
|
|
|
|
/*
|
|
This program is in the Public Domain. My crack legal
|
|
team would like to add:
|
|
|
|
RYAN "FRIKAC" SMITH IS PROVIDING THIS SOFTWARE "AS IS"
|
|
AND MAKES NO WARRANTY, EXPRESS OR IMPLIED, AS TO THE
|
|
ACCURACY, CAPABILITY, EFFICIENCY, MERCHANTABILITY, OR
|
|
FUNCTIONING OF THIS SOFTWARE AND/OR DOCUMENTATION. IN
|
|
NO EVENT WILL RYAN "FRIKAC" SMITH BE LIABLE FOR ANY
|
|
GENERAL, CONSEQUENTIAL, INDIRECT, INCIDENTAL,
|
|
EXEMPLARY, OR SPECIAL DAMAGES, EVEN IF RYAN "FRIKAC"
|
|
SMITH HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
|
DAMAGES, IRRESPECTIVE OF THE CAUSE OF SUCH DAMAGES.
|
|
|
|
You accept this software on the condition that you
|
|
indemnify and hold harmless Ryan "FrikaC" Smith from
|
|
any and all liability or damages to third parties,
|
|
including attorney fees, court costs, and other
|
|
related costs and expenses, arising out of your use
|
|
of this software irrespective of the cause of said
|
|
liability.
|
|
|
|
The export from the United States or the subsequent
|
|
reexport of this software is subject to compliance
|
|
with United States export control and munitions
|
|
control restrictions. You agree that in the event you
|
|
seek to export this software, you assume full
|
|
responsibility for obtaining all necessary export
|
|
licenses and approvals and for assuring compliance
|
|
with applicable reexport restrictions.
|
|
|
|
Any reproduction of this software must contain
|
|
this notice in its entirety.
|
|
*/
|
|
|
|
#include "libfrikbot.h"
|
|
|
|
integer bot_move_linker;
|
|
|
|
float (integer keys, integer key) key_state =
|
|
{
|
|
return ((keys & key) != 0) ? 1.0 : 0.0;
|
|
};
|
|
|
|
void (vector start, vector mins, vector maxs, vector end, float type,
|
|
entity passent) checkmove = #98; // Wrapper around SV_Move.
|
|
|
|
|
|
@implementation Bot (Move)
|
|
|
|
-(void)sendMove
|
|
{
|
|
local vector movevect = '0 0 0';
|
|
local float anglespeed;
|
|
local vector view;
|
|
local vector probe, start;
|
|
local integer obstructed = 0;
|
|
|
|
movevect.y += (350 * key_state (keys, KEY_MOVERIGHT));
|
|
movevect.y -= (350 * key_state (keys, KEY_MOVELEFT));
|
|
|
|
movevect.x += (200 * key_state (keys, KEY_MOVEFORWARD));
|
|
movevect.x -= (200 * key_state (keys, KEY_MOVEBACK));
|
|
|
|
movevect.z += (200 * key_state (keys, KEY_MOVEUP));
|
|
movevect.z -= (200 * key_state (keys, KEY_MOVEDOWN));
|
|
|
|
if (!(b_aiflags & AI_PRECISION))
|
|
movevect *= 2;
|
|
|
|
if (b_skill != 2) {
|
|
// use mouse emulation
|
|
anglespeed = 1.5 * real_frametime;
|
|
// 1.5 is the default cl_anglespeedkey & bot always has +speed
|
|
ent.v_angle_y += anglespeed * key_state (keys, KEY_LOOKLEFT) * 140;
|
|
// 140 is default cl_yawspeed
|
|
ent.v_angle_y -= anglespeed * key_state (keys, KEY_LOOKRIGHT) * 140;
|
|
// 140 is default cl_yawspeed
|
|
ent.v_angle_x -= anglespeed * key_state (keys, KEY_LOOKUP) * 150;
|
|
// 150 is default cl_pitchspeed
|
|
ent.v_angle_x += anglespeed * key_state (keys, KEY_LOOKDOWN) * 150;
|
|
// 150 is default cl_pitchspeed
|
|
} else {
|
|
view.x = angcomp (b_angle.x, ent.v_angle_x);
|
|
view.y = angcomp (b_angle.y, ent.v_angle_y);
|
|
view.z = 0;
|
|
if (vlen (view) > 30) {
|
|
mouse_emu += (view * 30);
|
|
if (vlen(mouse_emu) > 180)
|
|
mouse_emu = normalize (mouse_emu) * 180;
|
|
} else
|
|
mouse_emu = view * (1 / real_frametime);
|
|
ent.v_angle += mouse_emu * real_frametime;
|
|
|
|
|
|
}
|
|
start = ent.origin;
|
|
//start.z += 16; // step offset
|
|
makevectors (ent.v_angle);
|
|
probe = v_forward * movevect.x + v_right * movevect.y + ent.velocity;
|
|
probe.z = 0;
|
|
if (probe) {
|
|
probe = normalize (probe);
|
|
probe = probe * 32 + start;
|
|
checkmove (start, ent.mins, ent.maxs, probe, 1, ent);
|
|
if (trace_fraction != 1) {
|
|
obstructed = 1;
|
|
probe = (probe - start);
|
|
if (trace_fraction)
|
|
probe *= trace_fraction;
|
|
else
|
|
probe = normalize (probe);
|
|
}
|
|
}
|
|
//dprint (itos (buttons) + " " + itos (impulse) + "\n");
|
|
//dprint (ftos (real_frametime) + "\n");
|
|
SV_UserCmd (ent, real_frametime, ent.v_angle, movevect, buttons, impulse);
|
|
impulse = 0;
|
|
if (obstructed)
|
|
[self obstructed:probe:FALSE];
|
|
}
|
|
|
|
- (void) jump
|
|
{
|
|
// TODO check for precision, etc.
|
|
buttons |= 4;
|
|
}
|
|
|
|
- (integer) canRJ
|
|
{
|
|
// this returns true of the bot can rocket/superjump/hook
|
|
// if your mod doesn't have an RL you can just return FALSE all the time
|
|
// if it has a hook or some other means for the bot to get to high places
|
|
// you can check here for that capability
|
|
|
|
// am I dumb?
|
|
if (b_skill == 0)
|
|
return FALSE;
|
|
|
|
// quad = bad
|
|
if (ent.items & IT_QUAD)
|
|
return FALSE;
|
|
|
|
// do I have rockets & RL?
|
|
if (!((ent.items & IT_ROCKET_LAUNCHER) && (ent.ammo_rockets > 0)))
|
|
return FALSE;
|
|
|
|
// do I have pent?
|
|
if (ent.items & IT_INVULNERABILITY)
|
|
return TRUE;
|
|
|
|
if (ent.health > 50)
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
- (integer) recognizePlat: (integer) flag
|
|
{
|
|
local integer ret;
|
|
|
|
ret = [super recognizePlat:flag];
|
|
if (ret) {
|
|
if (flag) {
|
|
// afect bot movement too
|
|
if (keys & KEY_MOVEUP) {
|
|
if (trace_ent.velocity_z > 0)
|
|
keys &= ~KEY_MOVE;
|
|
} else if (keys & KEY_MOVEDOWN) {
|
|
if (trace_ent.velocity_z < 0)
|
|
keys &= ~KEY_MOVE;
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
-(integer)keysForDir: (vector) sdir
|
|
{
|
|
local integer outkeys;
|
|
local float tang;
|
|
local vector keydir;
|
|
|
|
outkeys = 0;
|
|
if (sdir_x || sdir_y) {
|
|
// Everything is tested against 60 degrees, this allows the bot to
|
|
// overlap the keys 30 degrees on each diagonal 45 degrees might look
|
|
// more realistic
|
|
|
|
keydir = vectoangles (sdir);
|
|
tang = angcomp (keydir_y, ent.v_angle_y);
|
|
if ((tang <= 150) && (tang >= 30))
|
|
outkeys |= KEY_MOVELEFT;
|
|
else if ((tang >= -150) && (tang <= -30))
|
|
outkeys |= KEY_MOVERIGHT;
|
|
if (fabs(tang) <= 60)
|
|
outkeys |= KEY_MOVEFORWARD;
|
|
else if (fabs(tang) >= 120)
|
|
outkeys |= KEY_MOVEBACK;
|
|
}
|
|
if (sdir_z > 0.7)
|
|
outkeys |= KEY_MOVEUP;
|
|
else if (sdir_z < 0.7)
|
|
outkeys |= KEY_MOVEDOWN;
|
|
return outkeys;
|
|
}
|
|
|
|
/*
|
|
frik_obstructed
|
|
|
|
Bot has hit a ledge or wall that he should
|
|
manuever around.
|
|
*/
|
|
-(void)obstructed: (vector) whichway : (integer) danger
|
|
{
|
|
local float dist;
|
|
local vector disway, org;
|
|
|
|
// TODO: something
|
|
if (b_aiflags & AI_BLIND)
|
|
return;
|
|
|
|
if (danger) {
|
|
b_aiflags |= AI_DANGER;
|
|
keys = [self keysForDir:'0 0 0' - whichway];
|
|
}
|
|
if (b_aiflags & AI_PRECISION)
|
|
return;
|
|
|
|
disway_z = 0;
|
|
|
|
if (targets[0]) {
|
|
if (b_aiflags & AI_OBSTRUCTED) {
|
|
if (!(b_aiflags & AI_DANGER)) {
|
|
b_aiflags &= ~AI_OBSTRUCTED;
|
|
return;
|
|
} else if (!danger)
|
|
return;
|
|
}
|
|
org = [targets[0] realorigin];
|
|
obs_dir = whichway;
|
|
disway_x = whichway_y * -1;
|
|
disway_y = whichway_x;
|
|
dist = vlen (org - (ent.origin + disway));
|
|
disway_x = whichway_y;
|
|
disway_y = whichway_x * -1;
|
|
wallhug = (vlen (org - (ent.origin + disway)) > dist);
|
|
b_aiflags |= AI_OBSTRUCTED;
|
|
|
|
} else {
|
|
disway_x = whichway_y * -1;
|
|
disway_y = whichway_x;
|
|
dist = vlen (disway - obs_dir);
|
|
disway_x = whichway_y;
|
|
disway_y = whichway_x * -1;
|
|
wallhug = (vlen (disway - obs_dir) < dist);
|
|
obs_dir = whichway;
|
|
|
|
b_aiflags |= AI_OBSTRUCTED;
|
|
}
|
|
}
|
|
|
|
/*
|
|
frik_obstacles
|
|
|
|
Detects small bumps the bot needs to jump over
|
|
or ledges the bot should avoid falling in.
|
|
Also responsible for jumping gaps.
|
|
*/
|
|
-(void)obstacles
|
|
{
|
|
local vector start, stop, ang;
|
|
local float test, conts, dist, hgt;
|
|
|
|
if (!(ent.flags & FL_ONGROUND))
|
|
return;
|
|
if (b_aiflags & AI_BLIND)
|
|
return;
|
|
|
|
ang = normalize(ent.velocity);
|
|
ang_z = 0;
|
|
start = ent.origin + ang * 32; // ahem
|
|
start_z = ent.origin_z + ent.maxs_z;
|
|
stop = start;
|
|
stop_z = ent.origin_z + ent.mins_z;
|
|
traceline (start, stop - '0 0 256', TRUE, ent);
|
|
if (trace_allsolid || trace_startsolid)
|
|
return;
|
|
hgt = trace_endpos_z - stop_z;
|
|
|
|
if (hgt > 18) {
|
|
[self jump];
|
|
return;
|
|
}
|
|
if (hgt >= 0)
|
|
return;
|
|
|
|
conts = pointcontents (trace_endpos + '0 0 4');
|
|
start = stop - '0 0 8';
|
|
stop = start + ang * 256;
|
|
traceline (start, stop, TRUE, ent);
|
|
test = vlen(trace_endpos - start);
|
|
if (test <= 20)
|
|
return; // it's a walkable gap, do nothing
|
|
ang_x = ent.velocity_y * -1;
|
|
ang_y = ent.velocity_x;
|
|
ang = normalize (ang);
|
|
traceline (start - (ang * 10), start + (ang * 10), TRUE, ent);
|
|
if ((trace_fraction != 1) || trace_startsolid)
|
|
return; // gap is only 20 wide, walkable
|
|
ang = ent.velocity;
|
|
ang_z = 0;
|
|
dist = ((540 / sv_gravity) * vlen (ang))/* + 32*/;
|
|
if (test > dist) {
|
|
// I can't make it
|
|
if (conts < -3) {
|
|
// bad stuff down dare
|
|
[self obstructed: ang :TRUE];
|
|
return;
|
|
} else {
|
|
if (targets[0]) {
|
|
stop = [targets[0] realorigin];
|
|
if ((stop_z - ent.origin_z) < -32)
|
|
return; // safe to fall
|
|
}
|
|
[self obstructed: ang :FALSE];
|
|
return;
|
|
}
|
|
} else {
|
|
ang = normalize(ang);
|
|
//look for a ledge
|
|
traceline (ent.origin, ent.origin + (ang * (test + 20)), TRUE, ent);
|
|
if (trace_fraction != 1) {
|
|
if (conts < -3) {
|
|
// bad stuff down dare
|
|
[self obstructed: ang :TRUE];
|
|
return;
|
|
} else {
|
|
if (targets[0]) {
|
|
stop = [targets[0] realorigin];
|
|
if ((stop_z - ent.origin_z) < -32)
|
|
return; // safe to fall
|
|
}
|
|
[self obstructed: ang :FALSE];
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (targets[0]) {
|
|
// getting furter away from my target?
|
|
test = vlen ([targets[0] origin] - (ang + ent.origin));
|
|
if (test > vlen ([targets[0] origin] - ent.origin)) {
|
|
if (conts < -3) {
|
|
// bad stuff down dare
|
|
[self obstructed: ang :TRUE];
|
|
return;
|
|
} else {
|
|
[self obstructed: ang :FALSE];
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (hgt < -18) {
|
|
if (targets[0]) {
|
|
stop = [targets[0] realorigin];
|
|
if ((stop_z - ent.origin_z) < -32)
|
|
return; // safe to fall
|
|
}
|
|
[self jump];
|
|
}
|
|
// go for it
|
|
|
|
}
|
|
|
|
/*
|
|
After frik_obstructed, the bot uses the
|
|
following funtion to move "around" the obstacle
|
|
|
|
I have no idea how well it will work
|
|
*/
|
|
-(void)dodgeObstruction
|
|
{
|
|
local vector way, org;
|
|
local float oflags, yaw;
|
|
|
|
if (!(b_aiflags & AI_OBSTRUCTED))
|
|
return;
|
|
if ((b_aiflags & (AI_BLIND | AI_PRECISION))
|
|
|| !(ent.flags & FL_ONGROUND)) {
|
|
b_aiflags &= ~AI_OBSTRUCTED;
|
|
return;
|
|
}
|
|
|
|
// perform a walkmove check to see if the obs_dir is still obstructed
|
|
// walkmove is less forgiving than frik_obstacles, so I dunno
|
|
// how well this will work
|
|
|
|
oflags = ent.flags;
|
|
org = ent.origin;
|
|
|
|
yaw = vectoyaw (obs_dir);
|
|
if (walkmove (yaw, 32))
|
|
b_aiflags &= ~AI_OBSTRUCTED;
|
|
else {
|
|
if (b_aiflags & AI_DANGER) {
|
|
way = '0 0 0' - obs_dir;
|
|
} else if (wallhug) {
|
|
way.x = obs_dir.y * -1;
|
|
way.y = obs_dir.x;
|
|
} else {
|
|
way.x = obs_dir.y;
|
|
way.y = obs_dir.x * -1;
|
|
}
|
|
way_z = 0;
|
|
keys &= ~KEY_MOVE;
|
|
keys |= [self keysForDir: way];
|
|
}
|
|
|
|
// fix the bot
|
|
ent.origin = org;
|
|
ent.flags = oflags;
|
|
}
|
|
|
|
/*
|
|
movetogoal and walkmove replacements
|
|
|
|
blah
|
|
*/
|
|
-(void) movetogoal
|
|
{
|
|
local vector way;
|
|
local float g;
|
|
|
|
if (targets[0] == NIL) {
|
|
makevectors(ent.v_angle);
|
|
[self walkmove: v_forward];
|
|
return;
|
|
}
|
|
way = [targets[0] realorigin] - ent.origin;
|
|
if (vlen(way) < 25) {
|
|
keys &= ~KEY_MOVE;
|
|
return;
|
|
}
|
|
|
|
way = normalize (way);
|
|
keys &= ~KEY_MOVE;
|
|
keys |= [self keysForDir: way];
|
|
|
|
[self dodgeObstruction];
|
|
[self recognizePlat: TRUE];
|
|
|
|
if (b_aiflags & AI_PRECISION) {
|
|
g = angcomp (ent.v_angle_x, b_angle.x);
|
|
if (fabs (g) > 10)
|
|
keys &= ~KEY_MOVE;
|
|
g = angcomp (ent.v_angle_y, b_angle.y);
|
|
if (fabs(g) > 10)
|
|
keys &= ~KEY_MOVE;
|
|
}
|
|
}
|
|
|
|
-(integer)walkmove: (vector) weird
|
|
{
|
|
// okay so it's not walkmove
|
|
// sue me
|
|
keys &= ~KEY_MOVE;
|
|
keys |= [self keysForDir: weird];
|
|
|
|
[self dodgeObstruction];
|
|
[self recognizePlat: TRUE];
|
|
if (b_aiflags & AI_OBSTRUCTED)
|
|
return FALSE;
|
|
else
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
|
|
The "hook" method of navigation. This nav
|
|
system is copyrighted 1999 by Ryan "Frika C"
|
|
Smith, keep that in mind when you steal it.
|
|
|
|
I brought this back because normal roaming
|
|
won't work - the bot gets distracted by it's
|
|
own waypoints.
|
|
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
|
|
*/
|
|
|
|
-(void)roam
|
|
{
|
|
local float loopcount, flag, dist;
|
|
local vector org, ang, org1;
|
|
|
|
loopcount = 26;
|
|
flag = FALSE;
|
|
while ((loopcount > 0) && !flag) {
|
|
loopcount--;
|
|
org = ent.origin + ent.view_ofs;
|
|
ang = ent.angles;
|
|
ang_y = frik_anglemod (ang_y - 90 + (180 * random ()));
|
|
ang_x = 0; // avoid upward sloping
|
|
makevectors (ang);
|
|
traceline (org, org + v_forward * 2300, TRUE, ent);
|
|
if (trace_fraction != 1) {
|
|
org1 = trace_endpos;
|
|
ang = normalize (trace_plane_normal);
|
|
ang_z = 0; // avoid upward sloping
|
|
traceline (org1, org1 + (ang * 2300), TRUE, ent);
|
|
if ((trace_fraction != 1) && (vlen(trace_endpos - org1) >= 64)) {
|
|
org = trace_endpos;
|
|
traceline (org, ent.origin + ent.view_ofs, TRUE, ent);
|
|
if (trace_fraction != 1) {
|
|
dist = vlen (org1 - org) /2;
|
|
org = org1 + (ang * dist);
|
|
traceline(org, org - '0 0 48', TRUE, ent);
|
|
if (trace_fraction != 1) {
|
|
[self spawnTempWaypoint:org];
|
|
flag = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
b_angle.y = ent.v_angle_y + 10;
|
|
}
|
|
@end
|