ref: 0f28c0feb37aae1fc19e112bea7a342b3e9564b3
dir: /crbot/cr_main.c/
#include <u.h>
#include <libc.h>
#include <stdio.h>
#include "../dat.h"
#include "../fns.h"
enum{
_DEBUG = 0
};
#define sqr(x) ((x)*(x))
qboolean bVectorZero( vec3_t a )
{
return fabs(a[0])<0.05f && fabs(a[1])<0.05f && fabs(a[2])<0.05f;
}
void YawVector( float yaw, vec3_t dir )
{
float angle;
angle = yaw*( M_PI*2 / 360 );
dir[0] = cos(angle);
dir[1] = sin(angle);
dir[2] = 0;
}
#define dist(x,y) (sqrt(sqr(x[0]-y[0])+sqr(x[1]-y[1])+sqr(x[2]-y[2])))
#define dist2(x,y) (sqr(x[0]-y[0])+sqr(x[1]-y[1])+sqr(x[2]-y[2]))
#define dist2d(x,y) (sqrt(sqr(x[0]-y[0])+sqr(x[1]-y[1])))
#define dist2d2(x,y) (sqr(x[0]-y[0])+sqr(x[1]-y[1]))
//== from p_client.c
void SelectSpawnPoint (edict_t *ent, vec3_t origin, vec3_t angles);
void InitClientPersistant (gclient_t *client);
void TossClientWeapon (edict_t *self);
void ClientObituary (edict_t *self, edict_t *inflictor, edict_t *attacker);
void CopyToBodyQue (edict_t *ent);
//== from g_target.c
void target_laser_start (edict_t *self);
//== from g_items.c
#define HEALTH_IGNORE_MAX 1
qboolean Pickup_Health (edict_t *ent, edict_t *other);
qboolean Pickup_Ammo (edict_t *ent, edict_t *other);
qboolean Pickup_Armor (edict_t *ent, edict_t *other);
qboolean Pickup_Weapon (edict_t *ent, edict_t *other);
qboolean Pickup_Adrenaline (edict_t *ent, edict_t *other);
qboolean Pickup_Powerup(edict_t *ent, edict_t *other);
qboolean Pickup_PowerArmor(edict_t *ent, edict_t *other);
qboolean Pickup_Bandolier(edict_t *ent, edict_t *other);
qboolean Pickup_Pack(edict_t *ent, edict_t *other);
void Use_Quad( edict_t *ent, gitem_t *item );
void Use_Invulnerability( edict_t *ent, gitem_t *item );
void Use_Breather( edict_t *ent, gitem_t *item );
void Weapon_Blaster (edict_t *ent);
//== from g_monster.c
void M_WorldEffects (edict_t *self);
//== touch funcs
void Touch_Plat_Center (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf);
void teleporter_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf);
void Use_Plat (edict_t *ent, edict_t *other, edict_t *activator);
//void trigger_elevator_use (edict_t *self, edict_t *other, edict_t *activator);
void door_use (edict_t *self, edict_t *other, edict_t *activator);
void rocket_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf);
void blaster_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf);
void Grenade_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf);
//== local functions
void cr_pain ( edict_t *self, edict_t *other, float kick, int damage );
void cr_die ( edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point);
void cr_respawn( edict_t *self );
void cr_sight(edict_t *self, edict_t *other);
void cr_think (edict_t *self);
void cr_think_pickup ( edict_t *self );
void cr_think_temp_target ( edict_t *self );
void cr_think_attack (edict_t *self);
void cr_think_chase (edict_t *self);
void cr_think_chase_route (edict_t *self);
void cr_think_taunt (edict_t *self);
void cr_think_flip (edict_t *self);
void cr_think_salute (edict_t *self);
void cr_think_run_for_life (edict_t *self);
void cr_think_follow_route( edict_t *self );
void cr_think_team_help( edict_t *self );
void cr_think_team_group( edict_t *self );
void cr_think_team_guard( edict_t *self );
void cr_think_team_patrol( edict_t *self );
void cr_think_wait( edict_t *self );
void cr_run_frames( edict_t *self, int start, int end );
void cr_animate_frames( edict_t *self, int start, int end );
qboolean cr_fire_weapon( edict_t *self, edict_t *target );
qboolean cr_moveto( edict_t *self );
qboolean cr_find_route( edict_t *self, vec3_t target, qboolean b_important );
void cr_init_node_net(void);
void cr_init_node_keeper(void);
//== sounds
static int sound_footstep[4];
//== constants
const float BOT_MIN_SPEED = 120.f;
const float BOT_MAX_SPEED = 300.f;
const float BOT_MIN_ROT_SPEED = 10.f;
const float BOT_MAX_ROT_SPEED = 90.f;
const int STEPSIZE = 22;
const int TIME_WEAPON_CHANGE = 1.f;
const int CROUCH_DELTA = 32;
const float WATER_SPEED_COEF = 0.8f;
const float CROUCH_SPEED_COEF = 0.6f;
const float FIGHT_MSG_DELAY = 10;
const float JUMP_DELAY = 0.2f;
const float MESSAGE_DELAY = 10.f;
const float ROCKET_MIN_AVOID_DIST = 30.f;
const float ROCKET_AVOID_DIST = 180.f;
const float ROCKET_MAX_AVOID_DIST = 250.f;
//=== weapons
#define SPEED_MODIFIER 1.2f
const int BLASTER_SPEED = 1000;
const int BLASTER_DAMAGE = 15;
const float TIME_BLASTER_SHOT = 0.5f*SPEED_MODIFIER;
const float TIME_BLASTER_AFTERSHOT = 0.2f;
const int SHOTGUN_SPEED = -1;
const int SHOTGUN_DAMAGE = 4;
const float TIME_SHOTGUN_SHOT = 1.2f*SPEED_MODIFIER;
const float TIME_SHOTGUN_AFTERSHOT = 0.2f;
const int SUPERSHOTGUN_SPEED = -1;
const int SUPERSHOTGUN_DAMAGE = 6;
const float TIME_SUPERSHOTGUN_SHOT = 1.2f*SPEED_MODIFIER;
const float TIME_SUPERSHOTGUN_AFTERSHOT = 0.2f;
const int MACHINEGUN_SPEED = -1;
const int MACHINEGUN_DAMAGE = 8;
const float TIME_MACHINEGUN_SHOT = 0.1f;//*SPEED_MODIFIER;
const float TIME_MACHINEGUN_AFTERSHOT = 0.2f;
const int CHAINGUN_SPEED = -1;
const int CHAINGUN_DAMAGE = 6;
const float TIME_CHAINGUN_SHOT = 0.04f;//*SPEED_MODIFIER;
const float TIME_CHAINGUN_AFTERSHOT = 1.f;
const int ROCKET_SPEED = 650;
const int ROCKET_DAMAGE = 100;
const float TIME_ROCKET_SHOT = 0.85f*SPEED_MODIFIER;
const float TIME_ROCKET_AFTERSHOT = 0.f;
const int GRENADE_SPEED = 600;
const int GRENADE_DAMAGE = 120;
const float TIME_GRENADE_SHOT = 1.2f*SPEED_MODIFIER;
const float TIME_GRENADE_AFTERSHOT = 0.f;
const int HYPERBLASTER_SPEED = 1000;
const int HYPERBLASTER_DAMAGE = 15;
const float TIME_HYPERBLASTER_SHOT = 0.1f;//*SPEED_MODIFIER;
const float TIME_HYPERBLASTER_AFTERSHOT = 0.5f;
const int BFG_SPEED = 400;
const int BFG_DAMAGE = 200;
const float TIME_BFG_SHOT = 1.2f;//*SPEED_MODIFIER;
const float TIME_BFG_AFTERSHOT = 1.1f;
const float TIME_BFG_SPINUP = 1.f;
const int RAILGUN_SPEED = -1;
const int RAILGUN_DAMAGE = 100;
const float TIME_RAILGUN_SHOT = 1.5f*SPEED_MODIFIER;
const float TIME_RAILGUN_AFTERSHOT = 0.2f;
//== team constants
const float TEAM_HELP_TIME = 20;
//== AI constants
const float CHASE_TIME = 20; // 20 secs.
const float ATTACK_RANGE_MIN = 200;
const float ATTACK_RANGE_MAX = 350;
const float ENGAGE_RANGE_MIN = 300;
const float ENGAGE_RANGE_MAX = 500;
const float PICKUP_RANGE_MIN = 120;
const float PICKUP_RANGE_MAX = 300;
const float MELEE_COMBAT_DIST = 40;
const float TOUCH_DIST = 10;
const float UNREACHABLE_TIMEOUT = 30;
const float UNREACHABLE_ENEMY_TIMEOUT = 10;
const float NODE_MAX_DIST = 280;
const float NODE_MIN_DIST = 90;
const float MOVE_JUMP_DIST = 30;
const float JUMP_DIST = 150;
const float JUMP_HEIGHT = 50;
const float BOT_JUMP_SPEED = 300.f;
const float ROCKETJUMP_MAXDIST = 320;
const float ROCKETJUMP_DIST = 200;
const float ROCKETJUMP_MINDIST = 100;
const float ROCKETJUMP_HEIGHT = 220;
const float TIME_STUCK = 0.6f;
const float TIME_NEXT_ENEMY_MIN = 0.3f;
const float TIME_NEXT_ENEMY_MAX = 1.5f;
const float TIME_NEXT_PICKUP = 0.8f;
const float TIME_LONE_NODE = 30.f;
const float TIME_ROAM_DIR_CHANGE = 3.f;
const float BOT_ACCURACY_VERT = 0.3f;
const float BOT_ACCURACY_HORZ = 0.6f;
const float TIME_CROUCH = 0.5f;
const float TIME_CHECK_STUCK = 0.6f;
//== global net of nodes
static path_node_t* cr_node_head = NULL;
static edict_t* cr_node_keeper = NULL;
static int node_count = 0;
const int MAX_NODE_COUNT = 2000;
//== number of bots respawned
static int global_bot_number=1;
static bot_info_pers_t* global_bots;
static int global_bots_count;
//== map cycle related
#define MAX_MAP 256
typedef char pathname[32];
static int map_cycle_count=0, next_map=0;
static pathname map_list[MAX_MAP];
void cr_load_maplist(void);
//== debug vars
static edict_t* global_path_route=NULL;
#define is_closer(pos1,pos2,xx) ((sqr((pos1)[0]-(pos2)[0])+sqr((pos1)[1]-(pos2)[1])+sqr((pos1)[2]-(pos2)[2]))<((xx)*(xx)))
#define is_closer_b(pos1,pos2,xx) ( fabs((pos1)[0]-(pos2)[0])<(xx) && fabs((pos1)[1]-(pos2)[1])<(xx) && fabs((pos1)[2]-(pos2)[2])<(xx) )
#define MAX_STACK_NODE 1024
#define MAX_MSG_LEN 256
#define MAX_MSG 4096
static char* msg_pain[MAX_MSG];
static int msg_pain_count=0, msg_pain_last=0;
static char* msg_kill[MAX_MSG];
static int msg_kill_count=0, msg_kill_last=0;
static char* msg_death[MAX_MSG];
static int msg_death_count=0, msg_death_last=0;
static char* msg_fight[MAX_MSG];
static int msg_fight_count=0, msg_fight_last=0;
static int INDEX_SLUGS;
static int INDEX_RAILGUN;
static int INDEX_CELLS;
static int INDEX_HYPERBLASTER;
static int INDEX_ROCKETS;
static int INDEX_ROCKET_LAUNCHER;
static int INDEX_GRENADES;
static int INDEX_GRENADE_LAUNCHER;
static int INDEX_BULLETS;
static int INDEX_CHAINGUN;
static int INDEX_MACHINEGUN;
static int INDEX_SHELLS;
static int INDEX_SUPER_SHOTGUN;
static int INDEX_SHOTGUN;
static int INDEX_BLASTER;
static int INDEX_POWER_SCREEN;
static int INDEX_POWER_SHIELD;
static int INDEX_QUAD;
static int INDEX_INVULN;
static int INDEX_BREATHER;
static int INDEX_BFG;
static int INDEX_TECH1, INDEX_TECH2, INDEX_TECH3, INDEX_TECH4;
static int INDEX_FLAG1, INDEX_FLAG2;
void cr_init_variables(void)
{
INDEX_SLUGS = ITEM_INDEX(FindItem("slugs"));
INDEX_RAILGUN = ITEM_INDEX(FindItem("railgun"));
INDEX_CELLS = ITEM_INDEX(FindItem("cells"));
INDEX_BFG = ITEM_INDEX(FindItem("BFG10K"));
INDEX_HYPERBLASTER = ITEM_INDEX(FindItem("hyperblaster"));
INDEX_ROCKETS = ITEM_INDEX(FindItem("rockets"));
INDEX_ROCKET_LAUNCHER = ITEM_INDEX(FindItem("rocket launcher"));
INDEX_GRENADES = ITEM_INDEX(FindItem("grenades"));
INDEX_GRENADE_LAUNCHER = ITEM_INDEX(FindItem("grenade launcher"));
INDEX_BULLETS = ITEM_INDEX(FindItem("bullets"));
INDEX_CHAINGUN = ITEM_INDEX(FindItem("chaingun"));
INDEX_MACHINEGUN = ITEM_INDEX(FindItem("machinegun"));
INDEX_SHELLS = ITEM_INDEX(FindItem("shells"));
INDEX_SUPER_SHOTGUN = ITEM_INDEX(FindItem("super shotgun"));
INDEX_SHOTGUN = ITEM_INDEX(FindItem("shotgun"));
INDEX_BLASTER = ITEM_INDEX(FindItem("blaster"));
INDEX_POWER_SCREEN = ITEM_INDEX(FindItem("Power Screen"));
INDEX_POWER_SHIELD = ITEM_INDEX(FindItem("Power Shield"));
INDEX_INVULN = ITEM_INDEX(FindItem("Invulnerability"));
INDEX_QUAD = ITEM_INDEX(FindItem("Quad Damage"));
INDEX_BREATHER = ITEM_INDEX(FindItem("Rebreather"));
INDEX_TECH1 = ITEM_INDEX(FindItemByClassname("item_tech1"));
INDEX_TECH2 = ITEM_INDEX(FindItemByClassname("item_tech2"));
INDEX_TECH3 = ITEM_INDEX(FindItemByClassname("item_tech3"));
INDEX_TECH4 = ITEM_INDEX(FindItemByClassname("item_tech4"));
INDEX_FLAG1 = ITEM_INDEX(FindItemByClassname("item_flag_team1"));
INDEX_FLAG2 = ITEM_INDEX(FindItemByClassname("item_flag_team2"));
}
qboolean pos_visible ( vec3_t spot1, vec3_t spot2 )
{
trace_t trace;
trace = gi.trace ( spot1, vec3_origin, vec3_origin, spot2, NULL, MASK_OPAQUE );
return (trace.fraction == 1.f);
}
qboolean pos_reachable ( vec3_t spot1, vec3_t spot2 )
{
trace_t trace;
trace = gi.trace ( spot1, vec3_origin, vec3_origin, spot2, NULL,
(CONTENTS_SOLID|CONTENTS_WINDOW|CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_PLAYERCLIP) );
return (trace.fraction==1.f);
}
qboolean can_reach ( edict_t *self, edict_t *other )
{
vec3_t spot1;
vec3_t spot2;
VectorCopy (self->s.origin, spot1);
spot1[2] += self->viewheight;
VectorCopy (other->s.origin, spot2);
spot2[2] += other->viewheight;
return pos_reachable (spot1,spot2);
}
qboolean cr_infront ( edict_t *self, edict_t *other )
{
vec3_t vec;
float dot;
vec3_t forward;
AngleVectors (self->s.angles, forward, NULL, NULL);
VectorSubtract (other->s.origin, self->s.origin, vec);
VectorNormalize (vec);
dot = DotProduct (vec, forward);
return dot > (13-self->bot_pers->skill)*0.06f;
}
qboolean is_touched( edict_t* self, vec3_t pos )
{
if ( pos[0] < (self->s.origin[0] + self->mins[0]) ) return false;
if ( pos[0] > (self->s.origin[0] + self->maxs[0]) ) return false;
if ( pos[1] < (self->s.origin[1] + self->mins[1]) ) return false;
if ( pos[1] > (self->s.origin[1] + self->maxs[1]) ) return false;
if ( pos[2] < (self->s.origin[2] + self->mins[2]) ) return false;
if ( pos[2] > (self->s.origin[2] + self->maxs[2]) ) return false;
return true;
}
qboolean is_touched2d( edict_t* self, vec3_t pos )
{
if ( pos[0] < (self->s.origin[0] + self->mins[0]) ) return false;
if ( pos[0] > (self->s.origin[0] + self->maxs[0]) ) return false;
if ( pos[1] < (self->s.origin[1] + self->mins[1]) ) return false;
if ( pos[1] > (self->s.origin[1] + self->maxs[1]) ) return false;
return true;
}
void line_think ( edict_t* self )
{
VectorCopy ( self->pos2, self->s.old_origin);
self->nextthink = level.time + FRAMETIME;
self->think = G_FreeEdict;
}
#define COLOR_BEAM_GREEN 4
#define COLOR_BEAM_BLUE 8
void cr_draw_line ( vec3_t pos1, vec3_t pos2, int col, edict_t** chain )
{
edict_t *beam;
beam = G_Spawn();
beam->spawnflags = 1 | col;
beam->dmg = 0.001;
beam->classname = "laser line";
target_laser_start (beam);
VectorCopy( pos1, beam->s.origin );
VectorCopy( pos2, beam->pos2 );
beam->think = line_think;
beam->nextthink = level.time + FRAMETIME;
gi.linkentity (beam);
if (chain) {
beam->flags |= FL_TEAMSLAVE;
beam->teamchain = *chain;
*chain = beam;
}
}
void cr_marker( vec3_t pos )
{
edict_t* t;
t = G_Spawn();
VectorSet( t->mins, -10, -10, -10);
VectorSet( t->maxs, 10, 10, 10);
VectorClear( t->velocity );
VectorCopy( pos, t->s.origin );
t->classname = "debug marker";
t->model = "models/items/adrenal/tris.md2";
t->s.modelindex = gi.modelindex(t->model);
t->nextthink = level.time + 30;
t->think = G_FreeEdict;
gi.linkentity(t);
// gi.bprintf ( PRINT_MEDIUM, "pos %.1f %.1f %.1f\n", pos[0], pos[1], pos[2] );
}
void cr_set_move_target( edict_t* self, vec3_t target )
{
VectorCopy( target, self->bot_info->move_target );
self->bot_info->move_target[2] += STEPSIZE;
}
void cr_add_direct_route( path_node_t* node1, path_node_t* node2, qboolean bComputeDistance )
{
qboolean bAdd;
int i, j;
float d;
// establish links
if (!node1 || !node2 || node1==node2) return;
//if(_DEBUG)
// cr_draw_line( node1->position, node2->position, COLOR_BEAM_BLUE, NULL );
bAdd=false;
for ( i=0; i<MAX_NODE_LINKS; i++ ) {
if ( node1->link_to[i]==node2 ) break;
if (!node1->link_to[i]) {
bAdd=true;
break;
}
}
if (!bAdd) return;
bAdd=false;
for ( j=0; j<MAX_NODE_LINKS; j++ ) {
if ( node2->link_from[j]==node1 ||
!node2->link_from[j] ) {
bAdd=true;
break;
}
}
if (!bAdd) return;
node1->link_to[i] = node2;
node2->link_from[j] = node1;
if (bComputeDistance) d=dist( node1->position, node2->position );
else d=1.f;
node1->link_dist[i] = d;
}
void cr_add_direct_route_uni( path_node_t* node1, path_node_t* node2 )
{
if (!node1 || !node2 || node1==node2) return;
cr_add_direct_route( node1, node2, true );
cr_add_direct_route( node2, node1, true );
}
void cr_remove_direct_route( path_node_t* node1, path_node_t* node2 )
{
int i, r;
if (!node1 || !node2 || node1==node2) return;
r=-1;
for ( i=0; i<MAX_NODE_LINKS; i++ ) {
if (!node1->link_to[i]) break;
if ( node1->link_to[i]==node2 ) r=i;
}
i--;
if (i>=0 && r>=0) {
node1->link_to[r] = node1->link_to[i];
node1->link_to[i] = NULL;
}
r=-1;
for ( i=0; i<MAX_NODE_LINKS; i++ ) {
if (!node2->link_from[i]) break;
if ( node2->link_from[i]==node1 ) r=i;
}
i--;
if (i>=0 && r>=0) {
node2->link_from[r] = node2->link_from[i];
node2->link_from[i] = NULL;
}
}
void cr_message_pain ( edict_t* self, edict_t* other )
{
int n;
if (!bot_chat->value) return;
if (!other || !other->inuse || !other->client || other->bot_info) return;
if (msg_pain_count<2) return;
if ( (level.time-self->bot_info->time_last_message)<MESSAGE_DELAY ) return;
self->bot_info->time_last_message = level.time - qrandom()*MESSAGE_DELAY/4;
while (1) {
n = rand() % msg_pain_count;
if (n==msg_pain_last) continue;
msg_pain_last=n;
gi.cprintf ( other, PRINT_CHAT, "%s: %s\n", self->client->pers.netname, msg_pain[n] );
break;
}
}
void cr_message_kill ( edict_t* self, edict_t* other )
{
int n;
if (!bot_chat->value) return;
if (!other || !other->inuse || !other->client || other->bot_info) return;
if (msg_kill_count<2) return;
if ( (level.time-self->bot_info->time_last_message)<MESSAGE_DELAY ) return;
self->bot_info->time_last_message = level.time - qrandom()*MESSAGE_DELAY/4;
while (1) {
n = rand() % msg_kill_count;
if (n==msg_kill_last) continue;
msg_kill_last=n;
gi.cprintf ( other, PRINT_CHAT, "%s: %s\n", self->client->pers.netname, msg_kill[n] );
break;
}
}
void cr_message_death ( edict_t* self, edict_t* other )
{
int n;
if (!bot_chat->value) return;
if (!other || !other->inuse || !other->client || other->bot_info) return;
if (msg_death_count<2) return;
if ( (level.time-self->bot_info->time_last_message)<MESSAGE_DELAY ) return;
self->bot_info->time_last_message = level.time - qrandom()*MESSAGE_DELAY/4;
while (1) {
n = rand() % msg_death_count;
if (n==msg_death_last) continue;
msg_death_last=n;
gi.cprintf ( other, PRINT_CHAT, "%s: %s\n", self->client->pers.netname, msg_death[n] );
break;
}
}
void cr_message_fight ( edict_t* self, edict_t* other )
{
int n;
if (!bot_chat->value) return;
if (!other || !other->inuse || !other->client || other->bot_info) return;
if (msg_fight_count<2) return;
if ( (level.time-self->bot_info->time_last_message)<MESSAGE_DELAY ) return;
self->bot_info->time_last_message = level.time - qrandom()*MESSAGE_DELAY/4;
while (1) {
n = rand() % msg_fight_count;
if (n==msg_fight_last) continue;
msg_fight_last=n;
gi.cprintf ( other, PRINT_CHAT, "%s: %s\n", self->client->pers.netname, msg_fight[n] );
break;
}
}
qboolean cr_get_avoid_vector( edict_t* self, vec3_t av )
{
edict_t *hit;
float d, old_d;
vec3_t pos, rpos, avoid_vect, dir_v;
int i;
qboolean bRes;
VectorCopy( self->s.origin, pos );
VectorClear( av );
bRes = false;
for ( hit=g_edicts+1; hit<&g_edicts[globals.num_edicts]; hit++) {
if (!hit->inuse) continue;
if ( hit->touch != rocket_touch &&
hit->touch != Grenade_Touch &&
(self->bot_pers->skill<6 || hit->touch!=blaster_touch || ((rand()&3)==3)) ) continue;
if ((hit->svflags & SVF_NOCLIENT) || (hit->solid==SOLID_NOT)) continue;
VectorCopy( hit->s.origin, rpos );
old_d = sqr(pos[0]-rpos[0])+sqr(pos[1]-rpos[1])+sqr(pos[2]-rpos[2]);
if (old_d>(ROCKET_MAX_AVOID_DIST*ROCKET_MAX_AVOID_DIST)) continue;
VectorMA( rpos, FRAMETIME/2, hit->velocity, rpos );
for (i=0; i<7; i++) {
avoid_vect[0] = pos[0]-rpos[0];
avoid_vect[1] = pos[1]-rpos[1];
avoid_vect[2] = pos[2]-rpos[2];
d = sqr(avoid_vect[0])+sqr(avoid_vect[1])+sqr(avoid_vect[2]);
if (d>(ROCKET_MAX_AVOID_DIST*ROCKET_MAX_AVOID_DIST)) break;
if (d>old_d) break;
// old_d = d;
d = sqrt(d);
if (d<ROCKET_MIN_AVOID_DIST) {
// move bot perpendicular to rocket speed
CrossProduct( avoid_vect, hit->velocity, dir_v );
if (self->groundentity) {
dir_v[0]=0;
dir_v[1]=0;
dir_v[2] = dir_v[2]>0 ? 1.f : -1.f;
}
CrossProduct( hit->velocity, dir_v, avoid_vect );
VectorNormalize( avoid_vect );
VectorMA( av, 2*ROCKET_AVOID_DIST/ROCKET_MIN_AVOID_DIST, avoid_vect, av );
}
else {
VectorMA( av, ROCKET_AVOID_DIST/(d*d), avoid_vect, av );
}
bRes = true;
VectorMA( rpos, FRAMETIME, hit->velocity, rpos );
}
}
return bRes;
}
void cr_avoid_rocket( edict_t* self )
{
vec3_t av;
float yaw;
if ( self->bot_info->time_next_rocket_avoid>level.time ) return;
self->bot_info->time_next_rocket_avoid = level.time + FRAMETIME*(10-self->bot_pers->skill);
if (!cr_get_avoid_vector(self,av)) return;
// gi.dprintf ( "avoid: %.1f %.1f %.1f\n", av[0], av[1], av[2] );
yaw = self->ideal_yaw*M_PI*2/360;
av[0] += cos(yaw);
av[1] += sin(yaw);
self->ideal_yaw = vectoyaw(av);
}
void cr_add_links_radius( path_node_t *new_node )
{
path_node_t *node;
float max_dist;
max_dist = NODE_MIN_DIST*1.4f;
for ( node=cr_node_head; node!=NULL; node=node->next ) {
if ( is_closer_b( new_node->position, node->position, max_dist) &&
pos_reachable( new_node->position, node->position ) )
cr_add_direct_route_uni( node, new_node );
if (new_node->link_to[MAX_NODE_LINKS-1]) return;
}
max_dist = NODE_MAX_DIST;
for ( node=cr_node_head; node!=NULL; node=node->next ) {
if ( is_closer_b( new_node->position, node->position, max_dist) &&
pos_reachable( new_node->position, node->position ) )
cr_add_direct_route_uni( node, new_node );
if (new_node->link_to[MAX_NODE_LINKS-1]) return;
}
}
path_node_t* cr_find_touched_node ( edict_t *self, vec3_t pos )
{
path_node_t *best, *node;
VectorCopy ( self->s.origin, self->s.old_origin);
VectorCopy ( pos, self->s.origin );
best = NULL;
for ( node=cr_node_head; node!=NULL; node=node->next ) {
if ( is_touched(self,node->position) ) {
best = node;
break;
}
}
VectorCopy ( self->s.old_origin, self->s.origin );
return best;
}
path_node_t *
cr_insert_node(vec3_t pos, path_node_t* d, int flags)
{
path_node_t *p;
if(_DEBUG)
cr_marker(pos);
p = Z_TagMalloc(sizeof *p, TAG_GAME);
memset(p, 0, sizeof *p);
p->time = level.time;
VectorCopy(pos, p->position);
if(d != nil)
cr_add_direct_route_uni(p, d);
// try to establish links to all visible nodes around
cr_add_links_radius(p);
// link new node in
p->next = cr_node_head;
cr_node_head = p;
p->flags = flags;
return p;
}
float distance( edict_t *e1, edict_t *e2 )
{
vec3_t vec;
if (!e1 || !e2) return 1e32;
VectorSubtract ( e1->s.origin, e2->s.origin, vec);
return VectorLength (vec);
}
void cr_add_unreachable( edict_t *self, edict_t *other, float timeout )
{
int i, best_i;
float best_time;
bot_info_t* bi = self->bot_info;
if (!other) return;
best_time = 1e32;
best_i=0;
for ( i=0; i<MAX_UNREACHABLES; i++ ) {
if ( bi->unreachable[i]==other || !bi->unreachable[i] ) {
best_i = i;
break;
}
if (bi->time_unreachable[i]<best_time) {
best_i = i;
best_time = bi->time_unreachable[i];
}
}
bi->unreachable[best_i] = other;
bi->time_unreachable[best_i] = level.time + timeout*(0.9f+qrandom()*0.2f);
// gi.bprintf ( PRINT_MEDIUM, "marking %s as unreachable for %.1f sec. (%d)\n", other->classname, bi->time_unreachable[best_i]-level.time, best_i );
}
qboolean cr_check_unreachable( edict_t *self, edict_t *other )
{
int i;
bot_info_t* bi = self->bot_info;
if (!other) return true;
for ( i=0; i<MAX_UNREACHABLES; i++ ) {
if (!bi->unreachable[i]) continue;
if ( bi->time_unreachable[i]<level.time ) {
// remove it
// gi.bprintf ( PRINT_MEDIUM, "(1) removing %s from unreachables\n", bi->unreachable[i]->classname );
bi->unreachable[i] = NULL;
bi->time_unreachable[i] = 0;
}
else
if (bi->unreachable[i]==other) return true;
}
return false;
}
void cr_remove_unreachable( edict_t *self, edict_t *other )
{
int i;
bot_info_t* bi = self->bot_info;
if (!other) return;
for ( i=0; i<MAX_UNREACHABLES; i++ ) {
if (!bi->unreachable[i]) continue;
if ( bi->time_unreachable[i]<level.time ||
bi->unreachable[i]==other ) {
// remove it
bi->unreachable[i] = NULL;
bi->time_unreachable[i] = 0;
// gi.bprintf ( PRINT_MEDIUM, "(2) removing %s from unreachables\n", other->classname );
}
}
}
void cr_forget_pickup_target( edict_t *self )
{
self->bot_info->pickup_target = NULL;
self->bot_info->pickup_target_score = 1e32;
}
void cr_forget_enemy( edict_t *self )
{
self->oldenemy = self->enemy;
self->enemy = NULL;
}
qboolean cr_low_health( edict_t *self )
{
return self->health<(self->max_health/8);
}
qboolean cr_avoid_fight( edict_t *self )
{
return (self->client->pers.inventory[INDEX_FLAG1]>0) ||
(self->client->pers.inventory[INDEX_FLAG2]>0);
}
qboolean cr_find_enemy( edict_t *self )
{
edict_t *client, *best_client;
float d, best_dist;
int min_light_level;
if (cr_avoid_fight(self)) return false;
if (cr_low_health(self)) return false;
min_light_level = (14 - self->bot_pers->skill)*.4f;
if (self->enemy && self->bot_info->time_next_enemy>level.time) {
self->oldenemy = self->enemy;
VectorCopy( self->enemy->s.origin, self->monsterinfo.last_sighting );
self->monsterinfo.trail_time = level.time;
return true;
}
self->bot_info->time_next_enemy = level.time +
TIME_NEXT_ENEMY_MIN + (TIME_NEXT_ENEMY_MAX-TIME_NEXT_ENEMY_MIN)*0.1f*self->bot_pers->skill;
if (self->client->pers.weapon == FindItem ("blaster")) {
if (qrandom()<0.8f) return false;
}
best_client=NULL;
best_dist=1e32;
for ( client=g_edicts+1; client<=(g_edicts+game.maxclients); client++) {
if (!client->inuse || client==self || (client->flags & FL_NOTARGET)) continue;
// attack only bots from other teams, unless we are in team #0
if ( self->client->pers.team_no>0 &&
client->client->pers.team_no==self->client->pers.team_no ) continue;
// is it dead already?
if ( client->deadflag>DEAD_DYING || (client->svflags & SVF_NOCLIENT) ) continue;
if (cr_check_unreachable(self,client)) continue;
// can we see him?
if (client!=self->oldenemy && !cr_infront (self,client)) continue;
if (!can_reach(self,client)) continue;
d = distance(self,client);
// is client in an spot too dark to be seen?
if (!client->bot_info && client->light_level<=min_light_level) {
if ( client->light_level < (d-100)*0.002f*min_light_level ) continue;
}
if ( !best_client || d<best_dist ) {
best_client = client;
best_dist = d;
if (best_dist<MELEE_COMBAT_DIST) break;
}
}
if (!best_client) return false;
if ( !self->enemy &&
self->bot_info->time_next_shot < level.time ) {
self->bot_info->time_next_shot = level.time + 0.05f*(11-self->bot_pers->skill)*(1.f+0.2f*qrandom());
}
cr_forget_enemy(self);
self->enemy = best_client;
VectorCopy( self->enemy->s.origin, self->monsterinfo.last_sighting );
self->monsterinfo.trail_time = level.time;
cr_forget_pickup_target(self);
return true;
}
void
cr_create_bot_structure(edict_t *p)
{
p->bot_pers = Z_TagMalloc(sizeof *p->bot_pers, TAG_GAME);
memset(p->bot_pers, 0, sizeof *p->bot_pers);
p->bot_info = Z_TagMalloc(sizeof *p->bot_info, TAG_GAME);
memset(p->bot_info, 0, sizeof *p->bot_info);
}
void cr_compute_skills( edict_t *self, int skill_level )
{
self->bot_pers->speed = BOT_MIN_SPEED + (BOT_MAX_SPEED-BOT_MIN_SPEED)*0.1f*skill_level;
self->bot_pers->rot_speed = BOT_MIN_ROT_SPEED + (BOT_MAX_ROT_SPEED-BOT_MIN_ROT_SPEED)*0.1f*skill_level;
self->bot_pers->attack_range = ATTACK_RANGE_MIN + (ATTACK_RANGE_MAX-ATTACK_RANGE_MIN)*0.1f*skill_level;
self->bot_pers->engage_range = ENGAGE_RANGE_MIN + (ENGAGE_RANGE_MAX-ENGAGE_RANGE_MIN)*0.1f*skill_level;
self->bot_pers->adapt_count = 0;
}
void cr_register_new_bot( bot_info_pers_t* new_bot )
{
int i;
for ( i=0; i<global_bots_count; i++ ) {
if (global_bots[i].b_inuse) continue;
memcpy( global_bots+i, new_bot, sizeof(bot_info_pers_t) );
global_bots[i].b_inuse = true;
break;
}
}
void cr_unregister_bot( bot_info_pers_t* bot )
{
int i;
for ( i=0; i<global_bots_count; i++ ) {
if (!global_bots[i].b_inuse ||
bot->playernum!=global_bots[i].playernum) continue;
memset(global_bots+i,0,sizeof(bot_info_pers_t));
game.clients[bot->playernum].inuse = false;
global_bots[i].b_inuse = false;
}
}
int cr_find_unused_client_slot (void)
{
edict_t *cl_ent;
int i;
for ( i=0; i<game.maxclients; i++ ) {
cl_ent = g_edicts + 1 + i;
if ( !cl_ent->inuse && !cl_ent->client->inuse ) return i;
}
return -1;
}
void cr_init_per_info( edict_t* self )
{
strcpy ( self->client->pers.netname, self->bot_pers->name );
self->client->pers.team_no = self->bot_pers->team_no;
sprintf ( self->client->pers.userinfo, "\\msg\\1\\rate\\25000\\name\\%s\\skin\\%s\\fov\\90\\hand\\0\\ip\\loopback", self->bot_pers->name, self->bot_pers->skin );
}
void cr_update_userinfo( bot_info_pers_t* bot_pers )
{
gi.configstring( CS_PLAYERSKINS+bot_pers->playernum,
va( "%s\\%s", bot_pers->name, bot_pers->skin ) );
}
void SP_crbot( char* name, int skill_level, char* skin, int team, char* model )
{
edict_t *new_crbot;
int playernum;
if (!deathmatch->value) {
gi.dprintf ( "Start a deathmatch game first or add '+set deathmatch 1' to the command line\n" );
return;
}
playernum = cr_find_unused_client_slot();
if (playernum<0) {
gi.dprintf ( "*** Not enough client slots to create a new bot.\n*** Increase 'maxclients' var and restart the map.\n*** You can do it by typing 'maxclients #' in the console window or\n*** by adding '+set maxclients #' to your command line.\n*** To restart the map type 'map map_name_here'\n" );
return;
}
// spawn the bot on a spawn spot
new_crbot = g_edicts + playernum + 1; //G_Spawn();
G_InitEdict(new_crbot);
cr_create_bot_structure(new_crbot);
//=== init bot_pers
new_crbot->bot_pers->b_adapting = (skill_level==0);
new_crbot->bot_pers->playernum = playernum;
if (skill_level<1) skill_level=5;
if (skill_level>10) skill_level=10;
new_crbot->bot_pers->skill = skill_level;
cr_compute_skills( new_crbot, skill_level );
new_crbot->bot_pers->team_no = team;
if (!skin || !*skin) skin = bot_skin->string;
strncpy( new_crbot->bot_pers->skin, skin, 64 );
if (!model || !*model) model = bot_model->string;
sprintf( new_crbot->bot_pers->model, "players/%s/tris.md2", model );
if (!strchr(new_crbot->bot_pers->skin,'/')) {
char nm[128];
sprintf( nm, "%s/%s", bot_model->string, new_crbot->bot_pers->skin );
strncpy( new_crbot->bot_pers->skin, nm, 64 );
}
if (!name || !*name) sprintf( new_crbot->bot_pers->name, "crbot%d", global_bot_number++ );
else strcpy ( new_crbot->bot_pers->name, name );
//===
cr_update_userinfo(new_crbot->bot_pers);
cr_register_new_bot(new_crbot->bot_pers);
// new_crbot->client = &game.clients[new_crbot->bot_pers->playernum]; // not required any more
new_crbot->client->inuse = true;
InitClientResp(new_crbot->client);
InitClientPersistant(new_crbot->client);
cr_init_per_info(new_crbot);
new_crbot->classname = "new_bot";
new_crbot->think = cr_respawn;
new_crbot->nextthink = level.time + qrandom();
}
qboolean cr_force_attack_enemy( edict_t*self, edict_t *enemy )
{
if (cr_find_route( self, enemy->s.origin, true )) {
VectorCopy( enemy->s.origin, self->monsterinfo.last_sighting );
self->monsterinfo.trail_time = level.time;
self->enemy = enemy;
self->think = cr_think_chase_route;
return true;
}
return false;
}
void cr_call_teamers( edict_t*self, edict_t *enemy )
{
edict_t* bot;
if (self->client->pers.team_no<=0) return;
for ( bot=g_edicts+1; bot<=(g_edicts+game.maxclients); bot++) {
if ( !bot->inuse || bot==self || bot->deadflag==DEAD_DEAD ||
cistrcmp( bot->classname, "bot" )!=0 ) continue;
if ( bot->client->pers.team_no==self->client->pers.team_no &&
!cr_low_health(bot) &&
!bot->enemy && (bot->think==cr_think || bot->think==cr_think_pickup) ) {
cr_force_attack_enemy(bot,enemy);
}
}
}
void cr_team_help( edict_t* self )
{
edict_t* bot;
if (self->client->pers.team_no<=0) {
gi.cprintf( self, PRINT_HIGH, "You are not in any team!\n");
return;
}
for ( bot=g_edicts+1; bot<=(g_edicts+game.maxclients); bot++) {
if ( !bot->inuse || bot==self || bot->deadflag==DEAD_DEAD ||
cistrcmp( bot->classname, "bot" )!=0 ) continue;
if ( bot->client->pers.team_no!=self->client->pers.team_no ||
cr_low_health(self) ||
bot->enemy || (bot->think!=cr_think && bot->think!=cr_think_pickup) ) continue;
if (!cr_find_route( bot, self->s.origin, true )) {
gi.cprintf ( self, PRINT_CHAT, "%s: Don't know how to get to your location, Sir!\n", bot->client->pers.netname );
}
else {
gi.cprintf ( self, PRINT_CHAT,
qrandom()<0.5f ? "%s: Yes, Sir!\n" : "%s: On my way, Sir!\n", bot->client->pers.netname );
bot->bot_info->team_leader = self;
bot->bot_info->bot_assignment = ASSN_HELP;
bot->think = cr_think_team_help;
}
}
}
void cr_team_group( edict_t* self )
{
edict_t* bot;
if (self->client->pers.team_no<=0) {
gi.cprintf( self, PRINT_HIGH, "You are not in any team!\n");
return;
}
for ( bot=g_edicts+1; bot<=(g_edicts+game.maxclients); bot++) {
if ( !bot->inuse || bot==self || bot->deadflag==DEAD_DEAD ||
cistrcmp( bot->classname, "bot" )!=0 ) continue;
if ( bot->client->pers.team_no!=self->client->pers.team_no ||
cr_low_health(self) ||
bot->enemy || (bot->think!=cr_think && bot->think!=cr_think_pickup) ) continue;
if (!cr_find_route( bot, self->s.origin, true )) {
gi.cprintf ( self, PRINT_CHAT, "%s: Don't know how to get to your location, Sir!\n", bot->client->pers.netname );
}
else {
gi.cprintf ( self, PRINT_CHAT,
qrandom()<0.6f ? "%s: Yes, Sir!\n" : "%s: On my way, Sir!\n", bot->client->pers.netname );
bot->bot_info->team_leader = self;
bot->bot_info->bot_assignment = ASSN_GROUP;
bot->think = cr_think_team_group;
}
}
}
void cr_team_patrol( edict_t* self, char* name )
{
edict_t* bot;
char* bot_name = NULL;
if (name && *name) bot_name=name;
if (self->client->pers.team_no<=0) {
gi.cprintf( self, PRINT_HIGH, "You are not in any team!\n");
return;
}
for ( bot=g_edicts+1; bot<=(g_edicts+game.maxclients); bot++) {
if ( !bot->inuse || bot==self || bot->deadflag==DEAD_DEAD ||
cistrcmp( bot->classname, "bot" )!=0 ) continue;
if (bot->client->pers.team_no!=self->client->pers.team_no) continue;
if (cr_low_health(self) || bot->enemy) continue;
if ( bot->bot_info->bot_assignment==ASSN_NONE &&
bot->think!=cr_think && bot->think!=cr_think_pickup ) continue;
if (!bot_name) {
if (!infront(self,bot)) continue;
if (!pos_visible(self->s.origin,bot->s.origin)) continue;
}
else
if (cistrcmp(bot->client->pers.netname,bot_name)!=0 ) continue;
if (!cr_find_route( bot, self->s.origin, true )) {
gi.cprintf ( self, PRINT_CHAT, "%s: Don't know how to get to your location, Sir!\n", bot->client->pers.netname );
}
else {
gi.cprintf ( self, PRINT_CHAT,
qrandom()<0.6f ? "%s: Yes, Sir! Patroling the area.\n" : "%s: On my way, Sir!\n", bot->client->pers.netname );
bot->bot_info->team_leader = self;
VectorCopy ( self->s.origin, bot->bot_info->bot_anchor );
bot->bot_info->bot_assignment = ASSN_PATROL;
bot->think = cr_think_team_patrol;
}
}
}
void cr_team_guard( edict_t* self, char* name )
{
edict_t* bot;
char* bot_name = NULL;
if (name && *name) bot_name=name;
if (self->client->pers.team_no<=0) {
gi.cprintf( self, PRINT_HIGH, "You are not in any team!\n");
return;
}
for ( bot=g_edicts+1; bot<=(g_edicts+game.maxclients); bot++) {
if ( !bot->inuse || bot==self || bot->deadflag==DEAD_DEAD ||
cistrcmp( bot->classname, "bot" )!=0 ) continue;
if (bot->client->pers.team_no!=self->client->pers.team_no) continue;
if (cr_low_health(self) || bot->enemy) continue;
if ( bot->bot_info->bot_assignment==ASSN_NONE &&
bot->think!=cr_think && bot->think!=cr_think_pickup ) continue;
if (!bot_name) {
if (!infront(self,bot)) continue;
if (!pos_visible(self->s.origin,bot->s.origin)) continue;
}
else
if (cistrcmp(bot->client->pers.netname,bot_name)!=0 ) continue;
if (!cr_find_route( bot, self->s.origin, true )) {
gi.cprintf ( self, PRINT_CHAT, "%s: Don't know how to get to your location, Sir!\n", bot->client->pers.netname );
}
else {
gi.cprintf ( self, PRINT_CHAT,
qrandom()<0.6f ? "%s: Yes, Sir! Guarding the area.\n" : "%s: On my way, Sir!\n", bot->client->pers.netname );
bot->bot_info->team_leader = self;
VectorCopy ( self->s.origin, bot->bot_info->bot_anchor );
bot->bot_info->bot_assignment = ASSN_GUARD;
bot->think = cr_think_team_guard;
}
}
}
void cr_dismiss( edict_t* self )
{
self->bot_info->team_leader = NULL;
self->bot_info->bot_assignment = ASSN_NONE;
VectorClear(self->bot_info->bot_anchor);
}
void cr_team_free( edict_t* self, char* name )
{
char* bot_name = NULL;
edict_t* bot;
if (name && *name) bot_name=name;
if (self->client->pers.team_no<=0) {
gi.cprintf( self, PRINT_HIGH, "You are not in any team!\n");
return;
}
for ( bot=g_edicts+1; bot<=(g_edicts+game.maxclients); bot++) {
if ( !bot->inuse || bot==self || bot->deadflag==DEAD_DEAD ||
cistrcmp( bot->classname, "bot" )!=0 ) continue;
if ( bot->client->pers.team_no!=self->client->pers.team_no ) continue;
if ( bot_name && cistrcmp(bot->client->pers.netname,bot_name)!=0 ) continue;
if (bot->bot_info->bot_assignment!=ASSN_NONE) {
gi.cprintf( self, PRINT_CHAT, "%s: Yes, Sir!\n", bot->client->pers.netname );
cr_dismiss(bot);
}
}
}
void cr_pain ( edict_t *self, edict_t *other, float /*kick*/, int /*damage*/ )
{
if (self->deadflag==DEAD_DEAD) return;
if (level.time>self->pain_debounce_time) {
int r, l;
if (qrandom()<0.4f) cr_message_pain ( self, other );
self->pain_debounce_time = level.time + 0.8f;
r = 1 + (rand()&1);
if (self->health < 25) l = 25;
else if (self->health < 50) l = 50;
else if (self->health < 75) l = 75;
else l = 100;
gi.sound ( self, CHAN_VOICE, gi.soundindex(va("*pain%i_%i.wav", l, r)), 0.6f, ATTN_NORM, 0);
}
if (other && other->client) {
if (other!=self && self->client->pers.team_no>0) {
// call other teamers!
if ( self->client->pers.team_no != other->client->pers.team_no &&
level.time > self->bot_info->time_next_callforhelp ) {
cr_call_teamers( self, other );
self->bot_info->time_next_callforhelp = level.time + 5.f;
}
}
if ( other!=self && other!=self->enemy &&
(!self->enemy || qrandom()<0.3f) && other->client &&
( self->client->pers.team_no<=0 ||
self->client->pers.team_no!=other->client->pers.team_no || qrandom()<0.2f ) ) {
cr_forget_pickup_target(self);
cr_forget_enemy(self);
self->enemy = other;
cr_remove_unreachable( self, self->enemy );
if ( level.time > self->bot_info->time_next_shot )
self->bot_info->time_next_shot = level.time + TIME_WEAPON_CHANGE*(11-self->bot_pers->skill)/5;
self->think = cr_think_chase;
}
}
}
edict_t* cr_respawn_bot( bot_info_pers_t* bot_pers, client_respawn_t* resp )
{
edict_t *new_crbot;
new_crbot = g_edicts + bot_pers->playernum + 1; //G_Spawn();
G_InitEdict(new_crbot);
cr_create_bot_structure(new_crbot);
*new_crbot->bot_pers = *bot_pers;
// new_crbot->client = &game.clients[new_crbot->bot_pers->playernum]; // not requaired any more
new_crbot->client->inuse = true;
InitClientResp(new_crbot->client);
InitClientPersistant(new_crbot->client);
if (resp) new_crbot->client->resp = *resp;
cr_init_per_info(new_crbot);
new_crbot->classname = "bot";
new_crbot->think = cr_respawn;
new_crbot->nextthink = level.time + 2.f+qrandom(); // 2-3 secs
return new_crbot;
}
void cr_free_bot( edict_t *self )
{
gi.TagFree(self->bot_pers);
self->bot_pers=NULL;
gi.TagFree(self->bot_info);
self->bot_info=NULL;
// G_FreeEdict (self);
self->s.modelindex = 0;
self->s.modelindex2 = 0;
self->solid = SOLID_NOT;
self->inuse = false;
self->classname = "disconnected";
}
void cr_death( edict_t *self )
{
if ( self->s.frame < (self->client->anim_end-10) ) {
self->s.frame = self->client->anim_end-6;
}
self->s.frame++;
if (self->s.frame<self->client->anim_end) {
self->nextthink = level.time + FRAMETIME;
}
else {
bot_info_pers_t save_bot_pers;
client_respawn_t resp;
self->s.frame = self->client->anim_end;
save_bot_pers = *self->bot_pers;
resp = self->client->resp;
self->client->anim_priority = ANIM_BASIC;
if ( self->takedamage!=DAMAGE_NO &&
self->s.frame >= FRAME_crdeath1 ) {
self->s.effects = 0;
self->s.renderfx = 0;
CopyToBodyQue(self);
}
cr_free_bot( self );
cr_respawn_bot( &save_bot_pers, &resp );
}
}
void cr_die ( edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t)
{
self->think = cr_death;
self->nextthink = level.time + FRAMETIME;
VectorClear (self->avelocity);
self->takedamage = DAMAGE_YES;
self->movetype = MOVETYPE_TOSS;
self->s.modelindex2 = 0; // remove linked weapon model
self->s.modelindex3 = 0; // remove linked ctf flag
self->s.angles[0] = 0;
self->s.angles[2] = 0;
self->s.sound = 0;
self->client->weapon_sound = 0;
self->maxs[2] = -8;
self->svflags |= SVF_DEADMONSTER;
if (!self->deadflag) {
if (attacker && attacker->client && !attacker->bot_info) self->bot_pers->adapt_count++;
ClientObituary( self, inflictor, attacker );
CTFFragBonuses(self, inflictor, attacker);
TossClientWeapon(self);
CTFPlayerResetGrapple(self);
CTFDeadDropFlag(self);
CTFDeadDropTech(self);
self->client->pers.weapon = NULL;
memset ( self->client->pers.inventory, 0, sizeof(self->client->pers.inventory) );
if (qrandom()<0.4f) cr_message_death( self, attacker );
}
// remove powerups
self->client->quad_framenum = 0;
self->client->invincible_framenum = 0;
self->client->breather_framenum = 0;
self->client->enviro_framenum = 0;
if (self->health < -40) {
int n;
gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
for ( n=0; n<3; n++)
ThrowGib ( self, rand()&1 ? "models/objects/gibs/bone/tris.md2" :
"models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC );
self->takedamage = DAMAGE_NO;
self->client->anim_priority = ANIM_DEATH;
self->client->anim_end = 0;
}
else {
if (!self->deadflag) {
static int i;
i = (i+1)%3;
// start a death animation
self->client->anim_priority = ANIM_DEATH;
if (self->bot_info->b_crouch) {
self->s.frame = FRAME_crdeath1-1;
self->client->anim_end = FRAME_crdeath5;
}
else switch (i) {
case 0:
self->s.frame = FRAME_death101-1;
self->client->anim_end = FRAME_death106;
break;
case 1:
self->s.frame = FRAME_death201-1;
self->client->anim_end = FRAME_death206;
break;
case 2:
self->s.frame = FRAME_death301-1;
self->client->anim_end = FRAME_death308;
break;
}
gi.sound (self, CHAN_VOICE, gi.soundindex(va("*death%i.wav", (rand()%4)+1)), 1, ATTN_NORM, 0);
}
}
self->deadflag = DEAD_DEAD;
gi.linkentity(self);
}
path_node_t* cr_get_next_path_node( edict_t *self )
{
self->bot_info->path_nodes--;
if (self->bot_info->path_nodes<0) {
self->bot_info->path_nodes=-1;
return NULL;
}
return self->bot_info->path[self->bot_info->path_nodes];
}
path_node_t* cr_get_current_path_node( edict_t *self )
{
if (self->bot_info->path_nodes<0) return NULL;
return self->bot_info->path[self->bot_info->path_nodes];
}
path_node_t* cr_find_closest_node ( edict_t *self )
{
float d, min_dist, min_idist;
path_node_t *best, *node, *ibest;
vec3_t pos;
VectorCopy ( self->s.origin, pos );
best = NULL;
ibest = NULL;
min_dist = 1e32;
min_idist = 1e32;
for ( node=cr_node_head; node!=NULL; node=node->next ) {
d = dist2( pos, node->position );
if (d<min_idist) {
min_idist=d;
ibest=node;
}
if (d>min_dist) continue;
if (is_touched(self,node->position)) {
best = node;
break;
}
if (!pos_reachable(pos,node->position)) continue;
//if (!can_reach( self, node->position )) continue;
best = node;
min_dist = d;
}
// can't find any visible nodes :(
if (!best) best=ibest;
return best;
}
path_node_t* cr_find_closest_enemy_node( edict_t *self )
{
path_node_t* node;
if (!self->enemy) return NULL;
VectorCopy ( self->s.origin, self->s.old_origin);
VectorCopy ( self->enemy->s.origin, self->s.origin );
node = cr_find_closest_node(self);
VectorCopy ( self->s.old_origin, self->s.origin );
return node;
}
path_node_t* cr_find_closest_node_pos ( edict_t* self, vec3_t pos )
{
path_node_t* node;
VectorCopy ( self->s.origin, self->s.old_origin);
VectorCopy ( pos, self->s.origin );
node = cr_find_closest_node(self);
VectorCopy ( self->s.old_origin, self->s.origin );
return node;
}
void cr_register_position( edict_t* self, int flags )
{
vec3_t pos;
path_node_t *node;
int n;
float d;
VectorCopy ( self->s.origin, pos );
if (self->prev_node) {
if ( is_closer_b( pos, self->prev_node->position, NODE_MIN_DIST ) &&
pos_reachable( pos, self->prev_node->position ) ) return;
}
// must be far enough from all other nodes
n=0;
for ( node=cr_node_head; node!=NULL; node=node->next ) {
d = dist2( pos, node->position );
if (d<50*50) break;
if (d>(NODE_MAX_DIST*NODE_MAX_DIST*0.5f)) continue;
if (!pos_reachable( pos, node->position )) continue;
n++;
if (d>NODE_MIN_DIST*NODE_MIN_DIST) continue;
if (n>=2) break;
}
if (!node)
self->prev_node = cr_insert_node( pos, self->prev_node, flags );
else
self->prev_node = node;
}
void cr_update_routes ( edict_t *self )
{
vec3_t pos;
if (self->watertype & (CONTENTS_LAVA|CONTENTS_SLIME)) return;
if (!self->groundentity && self->waterlevel==0) return;
if (node_count>MAX_NODE_COUNT) return;
VectorCopy ( self->s.origin, pos );
if (self->bot_info->last_node) {
if ( is_closer_b( pos, self->bot_info->last_node->position, NODE_MIN_DIST ) &&
pos_reachable( pos, self->bot_info->last_node->position ) ) return;
}
if (self->bot_info->next_node) {
if ( is_closer_b( pos, self->bot_info->next_node->position, NODE_MIN_DIST ) &&
pos_reachable( pos, self->bot_info->next_node->position ) ) return;
}
cr_register_position( self, self->bot_info->b_on_ladder ? NF_LADDER : 0 );
}
void cr_pick_random_destination( edict_t * self )
{
float d, an;
d = NODE_MIN_DIST + (NODE_MAX_DIST-NODE_MIN_DIST)*qrandom();
an = qrandom()*M_PI*2;
self->bot_info->move_target[0] = self->s.origin[0] + d*cos(an);
self->bot_info->move_target[1] = self->s.origin[1] + d*sin(an);
self->bot_info->move_target[2] = self->s.origin[2] + STEPSIZE;
// gi.bprintf ( PRINT_MEDIUM, "roaming to %.1f %.1f %.1f\n", self->bot_info->move_target[0], self->bot_info->move_target[1], self->bot_info->move_target[2] );
}
float cr_pickup_importance ( edict_t *self, edict_t *item_ent )
{
float res = -1;
gitem_t* item = item_ent->item;
if (!item) return -1;
if (item->pickup==CTFPickup_Flag) {
if (item_ent->spawnflags & DROPPED_ITEM) {
res = 5000;
}
else {
if (strcmp(item_ent->classname, "item_flag_team1") == 0) {
if ( self->client->resp.ctf_team != CTF_TEAM1 ||
self->client->pers.inventory[INDEX_FLAG2] ) res = 5000;
}
else
if (strcmp(item_ent->classname, "item_flag_team2") == 0) {
if ( self->client->resp.ctf_team != CTF_TEAM2 ||
self->client->pers.inventory[INDEX_FLAG1] ) res = 1100;
}
}
}
else
if (item->pickup==CTFPickup_Tech) {
//FIXME: teach bot to drop old powerup!
if ( !self->client->pers.inventory[INDEX_TECH1] &&
!self->client->pers.inventory[INDEX_TECH2] &&
!self->client->pers.inventory[INDEX_TECH3] &&
!self->client->pers.inventory[INDEX_TECH4] ) {
switch (item_ent->classname[9]) {
case '1':
res = 1000;
break; // no breaks in original code because ????
case '2':
res = 1500;
break;
case '3':
res = 1500;
break;
case '4':
res = 2000;
}
}
}
else
if (item->pickup==Pickup_Adrenaline) {
res = (95-self->health)*4;
if (res<0) res=-1;
}
else
if (item->pickup==Pickup_Powerup) {
if (item->use==Use_Quad ||
item->use==Use_Invulnerability) res=1000;
else if (item->use==Use_Breather) res=100;
}
else
if (item->pickup==Pickup_PowerArmor) {
res = (self->flags & FL_POWER_ARMOR) ? 1 : 1000;
}
else
if (item->pickup==Pickup_Health) {
//== health
if (item_ent->style & HEALTH_IGNORE_MAX) res=item_ent->count*10;
else {
if (self->health >= self->max_health)
/* code folded from here */
/* code folded from here */
res=-1;
/* unfolding */
/* unfolding */
else
/* code folded from here */
/* code folded from here */
res = cr_low_health(self) ? 20*item_ent->count : 0.2f*item_ent->count*(self->max_health - self->health);
/* unfolding */
/* unfolding */
}
}
else
//== armor
if (item->pickup==Pickup_Armor) {
int old_armor_index;
gitem_armor_t *newinfo;
// get info on new armor
newinfo = (gitem_armor_t *)item->info;
old_armor_index = ArmorIndex(self);
// handle armor shards specially
if (item->tag == ARMOR_SHARD) {
/* code folded from here */
/* code folded from here */
res = 5.f;
/* unfolding */
/* unfolding */
}
else
/* code folded from here */
/* code folded from here */
if (!old_armor_index) {
res = 2.f*newinfo->base_count;
}
else
res = 2.f*(newinfo->base_count - self->client->pers.inventory[old_armor_index]);
/* unfolding */
/* unfolding */
if (cr_low_health(self)) res*=4;
}
else
//== weapon
if (item->pickup==Pickup_Weapon) {
/* code folded from here */
/* code folded from here */
int index, ammo_idx;
qboolean bWeaponsStay = ((int)(dmflags->value) & DF_WEAPONS_STAY);
index = ITEM_INDEX(item);
if ( self->client->pers.inventory[index]>0 ) res = bWeaponsStay ? -1 : 0.5f;
else
if ( index>ITEM_INDEX(self->client->pers.weapon) ) {
ammo_idx = ITEM_INDEX(FindItem(item->ammo));
if ( self->client->pers.inventory[ammo_idx]>0 ||
!(item_ent->spawnflags & DROPPED_ITEM) )
res = self->client->pers.weapon == FindItem ("blaster") ? 400 : 100;
else
res = bWeaponsStay ? 2.f : 5.f; // this weapon is better than we have,
// but we don't have ammo for it yet
}
else
res = 0.5f; // this weapon is worse than we have
/* unfolding */
/* unfolding */
}
else
/* code folded from here */
/* code folded from here */
//== ammo
if (item->pickup==Pickup_Ammo) {
int count, cur_count, max=0, mod=1, index;
index = ITEM_INDEX(item);
if (item_ent->count>0) count = item_ent->count;
else count = item->quantity;
if (item->tag == AMMO_BULLETS) {
max = self->client->pers.max_bullets;
mod=1;
if (self->client->pers.inventory[INDEX_MACHINEGUN] || self->client->pers.inventory[INDEX_CHAINGUN]) mod*=2;
}
else if (item->tag == AMMO_SHELLS) {
max = self->client->pers.max_shells;
mod=1;
if (self->client->pers.inventory[INDEX_SUPER_SHOTGUN] || self->client->pers.inventory[INDEX_SHOTGUN]) mod*=2;
}
else if (item->tag == AMMO_ROCKETS) {
max = self->client->pers.max_rockets;
mod=3;
if (self->client->pers.inventory[INDEX_ROCKET_LAUNCHER]) mod*=2;
}
else if (item->tag == AMMO_GRENADES) {
max = self->client->pers.max_grenades;
mod=2;
if (self->client->pers.inventory[INDEX_GRENADE_LAUNCHER]) mod*=2;
}
else if (item->tag == AMMO_CELLS) {
max = self->client->pers.max_cells;
mod=3;
if (self->client->pers.inventory[INDEX_HYPERBLASTER] || self->client->pers.inventory[INDEX_BFG]) mod*=3;
}
else if (item->tag == AMMO_SLUGS) {
max = self->client->pers.max_slugs;
mod=2;
if (self->client->pers.inventory[INDEX_RAILGUN]) mod*=2;
}
cur_count = self->client->pers.inventory[index];
if (cur_count>=max) res = -1;
else res = mod*10.f*count*(max-cur_count)/sqr(max);
}
else
if (item->pickup==Pickup_Bandolier) {
if (self->client->pers.max_bullets < 250) res = 50;
else res = 5;
}
else
if (item->pickup==Pickup_Pack) {
if (self->client->pers.max_bullets < 300) res = 100;
else res = 10;
}
/* unfolding */
/* unfolding */
//FIXME: support the rest of pickups (items, etc.)
if (res<=0) res=-1;
return res;
}
float last_map_search=0;
void cr_find_pickup_on_map( edict_t *self )
{
path_node_t *node_stack1[MAX_STACK_NODE], *node_stack2[MAX_STACK_NODE];
path_node_t *node, *link_node, *next_link;
path_node_t **nodes, **new_nodes;
int cur_stack, stack_nodes, new_stack_nodes, i, j, max_nodes, n_count;
float cur_dist, best_dist, d, imp, best_score;
path_node_t *best_node;
edict_t* hit;
// qboolean b_show=false;
if (last_map_search==level.time) return;
last_map_search=level.time;
self->bot_info->last_node = cr_find_closest_node(self);
if (self->bot_info->bot_assignment==ASSN_GROUP) return;
// initialize node network
for ( node=cr_node_head; node!=NULL; node=node->next ) node->route_dist=-1;
self->bot_info->last_node->route_dist = 0.001;
node_stack1[0] = self->bot_info->last_node;
new_stack_nodes = 1;
cur_stack = 0;
max_nodes = 20 + 2*self->bot_pers->skill;
if (max_nodes>(MAX_PATH_NODES-2)) max_nodes = MAX_PATH_NODES-2;
best_score = self->bot_info->pickup_target_score;
best_node = NULL;
n_count=0;
while ( (best_score>TOUCH_DIST || n_count<(max_nodes/2)) &&
n_count<max_nodes) {
stack_nodes = new_stack_nodes;
new_stack_nodes = 0;
if (cur_stack==0) {
nodes = node_stack1;
new_nodes = node_stack2;
cur_stack=1;
}
else {
nodes = node_stack2;
new_nodes = node_stack1;
cur_stack=0;
}
best_dist = 1e32;
for ( i=0; i<stack_nodes; i++ ) {
node = *nodes++;
cur_dist = node->route_dist;
if (best_dist>cur_dist) best_dist=cur_dist;
// go through all links
for ( j=0; j<MAX_NODE_LINKS; j++ ) {
link_node = node->link_to[j];
if ( !link_node ) break;
d = cur_dist + node->link_dist[j];
if (link_node->route_dist<0 && new_stack_nodes<MAX_STACK_NODE) {
// add new downlink node
*new_nodes = link_node;
new_nodes++;
new_stack_nodes++;
}
if ( link_node->route_dist<0 || link_node->route_dist>d ) {
// update route_dist if this route is shorter
link_node->route_dist = d;
hit = link_node->item;
if (!hit) continue;
if (!hit->inuse) continue;
if ( (hit->svflags & SVF_NOCLIENT) || hit->solid==SOLID_NOT ) continue;
if (cr_check_unreachable(self,hit)) continue;
imp = cr_pickup_importance(self,hit);
if (imp>0) {
d /= imp;
if (!best_node || d<best_score) {
best_score = d;
best_node = link_node;
}
}
/*
if (imp>1001) {
b_show = true;
gi.dprintf( "%s scored %.1f\n", hit->classname, d );
} */
}
}
}
if (!new_stack_nodes) break;
n_count++;
}
/*
// remove path indication
{ edict_t* line, *next_line;
for ( line = global_path_route; line!=NULL; line=next_line ) {
next_line = line->teamchain;
G_FreeEdict(line);
}
} */
if (!best_node) return;
self->bot_info->path_nodes=-1;
n_count=0;
node = best_node;
while (n_count<=max_nodes) {
// search for shortest possible distance from "node"
best_dist=0;
link_node=NULL;
for ( i=0; i<MAX_NODE_LINKS; i++ ) {
if ( !(next_link = node->link_from[i]) ) break;
d = next_link->route_dist;
if (d>=0 && (!link_node || best_dist>d) ) {
link_node = next_link;
best_dist = d;
}
}
self->bot_info->path[++self->bot_info->path_nodes]=node;
if (!link_node || link_node==self->bot_info->last_node) break;
node->route_dist = -2;
if(_DEBUG)
cr_draw_line( node->position, link_node->position, COLOR_BEAM_BLUE, &global_path_route );
node = link_node;
n_count++;
}
self->think = cr_think_pickup;
self->bot_info->pickup_target = best_node->item;
self->bot_info->pickup_target_score = best_score;
self->bot_info->next_node = node;
cr_set_move_target( self, node->position );
// if (b_show) {
// gi.bprintf ( PRINT_MEDIUM, "gonna get %s with score %.1f from map\n", self->bot_info->pickup_target->classname, best_score );
// }
}
qboolean cr_force_pickup_target( edict_t *self, edict_t *target )
{
if (cr_find_route( self, target->s.origin, true )) {
self->think = cr_think_pickup;
self->bot_info->pickup_target = target;
self->bot_info->pickup_target_score = 1e-6f;
return true;
}
return false;
}
void cr_find_pickup_target( edict_t *self )
{
edict_t *hit, *best=NULL;
vec3_t orig;
float min_dist, dd, imp;
if (self->bot_info->time_next_pickup>level.time) return;
self->bot_info->time_next_pickup = level.time + TIME_NEXT_PICKUP;
VectorCopy ( self->s.origin, orig );
min_dist = self->bot_info->pickup_target_score;
for ( hit=g_edicts+1; hit<&g_edicts[globals.num_edicts]; hit++) {
if (!hit->inuse) continue;
if (!hit->item || !hit->item->pickup) continue;
if ((hit->svflags & SVF_NOCLIENT) || (hit->solid==SOLID_NOT)) continue;
if (cr_check_unreachable(self,hit)) continue;
imp = cr_pickup_importance(self,hit);
if (imp<=0) continue;
dd = sqrt(sqr(orig[0]-hit->s.origin[0]) + sqr(orig[1]-hit->s.origin[1]) + sqr(orig[2]-hit->s.origin[2]));
dd /= imp;
if (dd>min_dist) continue;
if (!can_reach(self,hit)) continue;
// gi.bprintf ( PRINT_MEDIUM, "rated %s at %.2f\n", hit->classname, imp );
best = hit;
min_dist = dd;
if (min_dist<TOUCH_DIST) break;
}
if (best) {
self->bot_info->pickup_target = best;
self->bot_info->pickup_target_score = min_dist;
cr_set_move_target(self,self->bot_info->pickup_target->s.origin);
}
}
qboolean cr_find_pickup_urgent( edict_t *self )
{
edict_t *hit, *best=NULL;
vec3_t orig;
float min_dist, dd, imp, pickup_range;
float bottom_limit, top_limit;
if (self->bot_info->time_next_pickup>level.time) return false;
self->bot_info->time_next_pickup = level.time + TIME_NEXT_PICKUP;
VectorCopy ( self->s.origin, orig );
top_limit = orig[2] + self->maxs[2]*0.9f;
bottom_limit = orig[2] + self->mins[2]*0.9f;
pickup_range = PICKUP_RANGE_MIN + (PICKUP_RANGE_MAX - PICKUP_RANGE_MIN)*0.1f*self->bot_pers->skill;
if (self->client->pers.weapon->weaponthink == Weapon_Blaster) pickup_range *= 3;
min_dist = 1e32;//self->bot_info->pickup_target_score;
for ( hit=g_edicts+1; hit<&g_edicts[globals.num_edicts]; hit++) {
if (!hit->inuse) continue;
if (!hit->item || !hit->item->pickup) continue;
if ((hit->svflags & SVF_NOCLIENT) || (hit->solid==SOLID_NOT)) continue;
if (cr_check_unreachable(self,hit)) continue;
if ( hit->s.origin[2]<bottom_limit ||
hit->s.origin[2]>top_limit ) continue;
imp = cr_pickup_importance(self,hit);
if (imp<=0) continue;
dd = sqrt(sqr(orig[0]-hit->s.origin[0]) + sqr(orig[1]-hit->s.origin[1]) + sqr(orig[2]-hit->s.origin[2]));
if (dd>pickup_range) continue;
dd /= imp;
// gi.bprintf ( PRINT_MEDIUM, "%s rated with %.2f score\n", hit->classname, dd );
if (dd>min_dist) continue;
if (!can_reach(self,hit)) continue;
// if (!can_reach(self,hit->s.origin)) continue;
best = hit;
min_dist = dd;
if (min_dist<TOUCH_DIST) break;
}
if (min_dist>pickup_range) return false;
if (best) {
// gi.bprintf ( PRINT_MEDIUM, "going for %s with %.2f score\n", best->classname, min_dist );
self->bot_info->pickup_target = best;
self->bot_info->pickup_target_score = min_dist;
cr_set_move_target( self, self->bot_info->pickup_target->s.origin );
return true;
}
return false;
}
qboolean cr_vertical_ok ( edict_t* self )
{
return self->bot_info->next_node &&
( (self->bot_info->next_node->flags & NF_ELEVATOR) ||
(self->bot_info->next_node->flags & NF_LADDER) );
}
qboolean cr_wait_ok ( edict_t* self )
{
return self->think==cr_think_wait || self->think==cr_think_salute ||
self->think==cr_think_taunt || self->think==cr_think_flip ||
self->think==cr_think_attack ||
( self->bot_info->next_node &&
( (self->bot_info->next_node->flags & NF_DOOR) ||
(self->bot_info->next_node->flags & NF_ELEVATOR) ) );
}
qboolean cr_no_way( edict_t *self, vec3_t spot )
{
float dd;
if (cr_vertical_ok(self)) return false;
if (!self->groundentity && spot[2]<self->s.origin[2]) return false;
dd = dist( self->s.origin, spot );
if (dd<TOUCH_DIST) return false;
return is_touched2d( self, spot );
}
void cr_crouch( edict_t* self, qboolean bCrouch )
{
if (bCrouch) {
if (self->bot_info->b_crouch) return;
self->maxs[2] -= CROUCH_DELTA;
self->bot_info->b_crouch = true;
}
else {
if (!self->bot_info->b_crouch) return;
self->maxs[2] += CROUCH_DELTA;
self->bot_info->b_crouch = false;
}
// gi.linkentity(self);
}
qboolean cr_check_bottom( edict_t *self )
{
vec3_t mins, maxs, start, stop;
trace_t trace;
int x, y, fail_count;
float mid;
VectorAdd (self->s.origin, self->mins, mins);
VectorAdd (self->s.origin, self->maxs, maxs);
// if all of the points under the corners are solid world, don't bother
// with the tougher checks
// the corners must be within 16 of the midpoint
start[2] = mins[2] - 1;
for (x=0 ; x<=1 ; x++)
for (y=0 ; y<=1 ; y++) {
start[0] = x ? maxs[0] : mins[0];
start[1] = y ? maxs[1] : mins[1];
if (gi.pointcontents (start) != CONTENTS_SOLID)
goto realcheck;
}
return true; // we got out easy
realcheck:
//
// check it for real...
//
start[2] = mins[2];
start[0] = stop[0] = (mins[0] + maxs[0])*0.5;
start[1] = stop[1] = (mins[1] + maxs[1])*0.5;
stop[2] = start[2] - (STEPSIZE + JUMP_HEIGHT);
trace = gi.trace( start, vec3_origin, vec3_origin, stop, self, MASK_PLAYERSOLID );
if (trace.fraction == 1.f) return false;
mid = trace.endpos[2];
// the corners must be within 16 of the midpoint
fail_count=0;
for (x=0 ; x<=1 ; x++)
for (y=0 ; y<=1 ; y++) {
start[0] = stop[0] = x ? maxs[0] : mins[0];
start[1] = stop[1] = y ? maxs[1] : mins[1];
trace = gi.trace( start, vec3_origin, vec3_origin, stop, self, MASK_PLAYERSOLID );
if ( trace.fraction==1.f || (mid - trace.endpos[2]) > JUMP_HEIGHT ) {
fail_count++;
if (fail_count>2) return false;
}
}
return true;
}
qboolean cr_try_move ( edict_t *self, float d_yaw, qboolean bCheckFall )
{
vec3_t move, oldorg, start, end;
trace_t trace, trace_lava;
float d, yaw;
if (!self->groundentity) return true;
d = FRAMETIME*self->bot_pers->speed;
if (self->bot_info->b_crouch) d *= CROUCH_SPEED_COEF;
else if (self->waterlevel>1) d *= WATER_SPEED_COEF;
yaw = (self->ideal_yaw + d_yaw)*M_PI*2/360;
move[0] = d*cos(yaw);
move[1] = d*sin(yaw);
move[2] = 0;
VectorScale ( move, FRAMETIME, self->velocity );
VectorCopy ( self->velocity, self->movedir );
// try the move
VectorCopy ( self->s.origin, oldorg );
VectorAdd ( self->s.origin, move, start );
VectorCopy ( start, end );
start[2] += STEPSIZE;
end[2] -= /*STEPSIZE +*/ JUMP_HEIGHT;
trace = gi.trace ( start, self->mins, self->maxs, end, self, MASK_PLAYERSOLID );
if (trace.allsolid) return false;
if (trace.startsolid) {
start[2] -= STEPSIZE;
trace = gi.trace ( start, self->mins, self->maxs, end, self, MASK_PLAYERSOLID );
if (trace.allsolid) return false;
if (trace.startsolid) {
if (self->bot_info->b_crouch) return false;
// can we crouch?!
trace = gi.trace ( start, self->mins,
tv( self->maxs[0], self->maxs[1], self->maxs[2]-CROUCH_DELTA),
end, self, MASK_PLAYERSOLID );
if (trace.allsolid || trace.startsolid) return false;
cr_crouch(self,true);
}
}
if (bCheckFall && !(trace.contents & CONTENTS_WATER)) {
if (trace.fraction == 1.f) return false; // walked off an edge
// check point traces down for dangling corners
VectorCopy ( trace.endpos, self->s.origin );
if (!cr_check_bottom(self)) {
VectorCopy ( oldorg, self->s.origin );
return false;
}
}
VectorScale ( move, 1.1f, move );
VectorAdd ( oldorg, move, start );
VectorCopy( start, end );
end[2] -= 300.f;
trace_lava = gi.trace ( start, vec3_origin, vec3_origin, end, self, MASK_PLAYERSOLID | CONTENTS_LAVA | CONTENTS_SLIME );
if (trace_lava.fraction>0.99f || trace_lava.contents & (CONTENTS_LAVA|CONTENTS_SLIME)) {
VectorCopy ( oldorg, self->s.origin );
return false;
}
VectorCopy ( trace.endpos, self->s.origin );
self->groundentity = trace.ent;
self->groundentity_linkcount = trace.ent->linkcount;
/*!!!
// double-check if inside something
VectorCopy ( self->s.origin, start );
VectorCopy ( start, end );
end[2] += 0.25f;
trace = gi.trace ( start, self->mins, self->maxs, end, self, MASK_PLAYERSOLID );
if (trace.allsolid || trace.startsolid) {
VectorCopy ( oldorg, self->s.origin );
return false;
}
*/
self->ideal_yaw = anglemod( self->ideal_yaw + d_yaw );
// the move is ok
gi.linkentity (self);
return true;
}
qboolean cr_can_stand( edict_t* self )
{
vec3_t start, end;
trace_t trace;
if (!self->bot_info->b_crouch) return false;
if ( self->bot_info->time_next_crouch > level.time ) return false;
self->bot_info->time_next_crouch = level.time + TIME_CROUCH;
VectorCopy ( self->s.origin, start );
VectorCopy ( start, end );
end[2] += 0.25f;
// end[2] += CROUCH_DELTA;
self->maxs[2] += CROUCH_DELTA;
trace = gi.trace ( start, self->mins, self->maxs, end, self, MASK_PLAYERSOLID );
self->maxs[2] -= CROUCH_DELTA;
if (trace.startsolid || trace.allsolid || trace.fraction<0.99) return false;
return true;
}
void cr_jump( edict_t *self )
{
if (!self->groundentity) return;
if (self->bot_info->b_crouch || self->bot_info->b_on_ladder) return;
YawVector( self->ideal_yaw, self->velocity );
self->velocity[0] *= BOT_MAX_SPEED;
self->velocity[1] *= BOT_MAX_SPEED;
self->velocity[2] = BOT_JUMP_SPEED;
VectorCopy( self->velocity, self->movedir );
self->groundentity = NULL;
gi.sound( self, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0);
self->s.frame = FRAME_jump1;
}
qboolean cr_can_jump ( edict_t *self )
{
vec3_t move, neworg, start, end, oldorg;
trace_t trace, trace_lava;
float cs, sn, yaw;
if (self->bot_info->b_crouch || self->bot_info->b_on_ladder) return false;
if (self->bot_info->time_next_jump>level.time) return false;
yaw = self->ideal_yaw*M_PI*2/360;
cs = cos(yaw);
sn = sin(yaw);
move[0] = MOVE_JUMP_DIST*cs;
move[1] = MOVE_JUMP_DIST*sn;
move[2] = 0;
// try the move
VectorAdd ( self->s.origin, move, neworg );
VectorCopy ( neworg, end );
neworg[2] += JUMP_HEIGHT;
end[2] -= STEPSIZE;
trace = gi.trace ( neworg, self->mins, self->maxs, end, self, MASK_PLAYERSOLID );
if (trace.allsolid || trace.startsolid) return false;
neworg[0] -= move[0];
neworg[1] -= move[1];
end[0] = neworg[0] + JUMP_DIST*cs;
end[1] = neworg[1] + JUMP_DIST*sn;
end[2] = neworg[2];
trace = gi.trace ( neworg, self->mins, self->maxs, end, self, MASK_PLAYERSOLID | CONTENTS_LADDER );
if (trace.allsolid || trace.startsolid) return false;
if (trace.contents & CONTENTS_LADDER) return true;
VectorCopy ( trace.endpos, neworg );
VectorCopy ( neworg, end );
end[2] -= 300.f;
trace = gi.trace ( neworg, self->mins, self->maxs, end, self, MASK_PLAYERSOLID );
if (trace.allsolid || trace.startsolid || trace.fraction>0.99f) return false;
if ( trace.endpos[2]<(self->bot_info->move_target[2]-STEPSIZE) ) return false;
// check for solid ground
VectorCopy ( self->s.origin, oldorg );
VectorCopy ( trace.endpos, self->s.origin );
if (!cr_check_bottom(self)) {
VectorCopy ( oldorg, self->s.origin );
return false;
}
VectorCopy ( oldorg, self->s.origin );
VectorCopy( trace.endpos, start );
VectorCopy( start, end );
end[2] -= 300.f;
trace_lava = gi.trace ( start, vec3_origin, vec3_origin, end, self, MASK_PLAYERSOLID | CONTENTS_LAVA | CONTENTS_SLIME );
if (trace_lava.fraction>0.99f || trace_lava.contents & (CONTENTS_LAVA|CONTENTS_SLIME)) return false;
return true;
}
void cr_effect( edict_t *self, int eff )
{
gi.WriteByte ( svc_muzzleflash );
gi.WriteShort ( self - g_edicts );
gi.WriteByte ( eff );
gi.multicast ( self->s.origin, MULTICAST_PVS );
}
qboolean cr_try_rocketjump( edict_t *self )
{
edict_t* pt;
float d, max_importance;
vec3_t move, start, end, oldorg, aim;
trace_t trace;
int armor_index;
if ( !self->groundentity || self->bot_info->b_crouch ||
self->bot_info->time_next_jump>level.time) return false;
if ( self->client->invincible_framenum < level.framenum &&
(self->health<80.f || self->client->breather_framenum > level.framenum) ) return false;
if ( self->bot_info->time_next_shot > (level.time-TIME_WEAPON_CHANGE) ||
self->client->pers.inventory[INDEX_ROCKETS]<1 ||
self->client->pers.inventory[INDEX_ROCKET_LAUNCHER]<1 ) return false;
pt = self->bot_info->pickup_target;
if ( !pt || pt->s.origin[2]<(self->s.origin[2]+JUMP_HEIGHT) ||
pt->s.origin[2]>(self->s.origin[2]+ROCKETJUMP_HEIGHT) ) return false;
d = dist2d2(pt->s.origin,self->s.origin);
if ( d>ROCKETJUMP_MAXDIST*ROCKETJUMP_MAXDIST ||
d<ROCKETJUMP_MINDIST*ROCKETJUMP_MINDIST) return false;
armor_index = ArmorIndex(self);
if (!armor_index) max_importance = 600;
else max_importance = 600 - 4*self->client->pers.inventory[armor_index];
if (cr_pickup_importance(self,pt)<max_importance) return false;
VectorSubtract( pt->s.origin, self->s.origin, move );
move[2]=0;
VectorNormalize( move );
VectorScale( move, ROCKETJUMP_DIST, move );
VectorCopy( self->s.origin, start );
VectorAdd( start, move, end );
end[2] += ROCKETJUMP_HEIGHT;
// cr_draw_line( start, end, COLOR_BEAM_GREEN, &global_path_route );
trace = gi.trace ( start, self->mins, self->maxs, end, self, MASK_PLAYERSOLID );
if (trace.allsolid || trace.startsolid) return false;
if (trace.fraction==1.f) {
// continue the jump
VectorCopy( end, start );
VectorAdd( start, move, end );
end[2] -= ROCKETJUMP_HEIGHT-JUMP_HEIGHT;
// cr_draw_line( start, end, COLOR_BEAM_GREEN, &global_path_route );
trace = gi.trace ( start, self->mins, self->maxs, end, self, MASK_PLAYERSOLID );
if ( trace.allsolid || trace.startsolid || trace.fraction>0.9f ) return false;
}
if (trace.endpos[2]<(pt->s.origin[2]-STEPSIZE)) return false;
// cast a ray down
VectorCopy( trace.endpos, start );
VectorCopy( start, end );
end[2] = pt->s.origin[2]-JUMP_HEIGHT;
// cr_draw_line( start, end, COLOR_BEAM_BLUE, &global_path_route );
trace = gi.trace ( start, self->mins, self->maxs, end, self, MASK_PLAYERSOLID | CONTENTS_LAVA | CONTENTS_SLIME );
if ( trace.allsolid || trace.startsolid || trace.fraction==1.f ||
trace.contents & (CONTENTS_LAVA|CONTENTS_SLIME) ) return false;
VectorCopy ( self->s.origin, oldorg );
VectorCopy ( trace.endpos, self->s.origin );
if (!cr_check_bottom(self)) {
VectorCopy ( oldorg, self->s.origin );
return false;
}
VectorCopy ( oldorg, self->s.origin );
// victory!
VectorCopy ( pt->s.origin, self->bot_info->move_target );
self->ideal_yaw = vectoyaw(move);
cr_jump(self);
// fire a rocket down below!
VectorSet( aim, 0,0,-1);
fire_rocket ( self, self->s.origin, aim,
ROCKET_DAMAGE, ROCKET_SPEED, 120/*damage_rad*/, ROCKET_DAMAGE/*radius_damage*/ );
cr_effect( self, MZ_ROCKET );
self->bot_info->time_next_shot = level.time + TIME_WEAPON_CHANGE;
self->client->pers.inventory[INDEX_ROCKETS]--;
return true;
}
qboolean cr_try_jump ( edict_t *self, float d_yaw )
{
float oldyaw;
oldyaw = self->ideal_yaw;
self->ideal_yaw = anglemod( self->ideal_yaw + d_yaw );
if (cr_can_jump(self)) {
cr_jump(self);
return true;
}
self->ideal_yaw = oldyaw;
return false;
}
qboolean cr_move_avoid ( edict_t *self, qboolean bSafe )
{
qboolean bRes = true;
if (!self->groundentity) return false;
if (!bSafe && self->bot_info->b_on_platform) bSafe = true;
if (self->bot_pers->skill>3) cr_avoid_rocket(self);
if (self->bot_pers->skill>5 && cr_try_rocketjump(self)) return false;
// jump on ladder
if ( self->bot_info->next_node && (self->bot_info->next_node->flags & NF_LADDER) ) {
if ( dist2d2( self->s.origin, self->bot_info->next_node->position ) < sqr(JUMP_DIST*0.6f) ) {
cr_jump(self);
return false;
}
}
if (!cr_try_move( self, 0, bSafe )) {
float dy;
qboolean bReverse;
if (cr_try_jump( self, 0 )) return false;
bRes = false;
bReverse = qrandom()>0.5f;
for ( dy=30; ; dy+=30 ) {
if (bReverse) dy = -dy;
if (cr_try_move( self, dy, bSafe )) break;
if (cr_try_jump( self, dy )) return false;
if (cr_try_move( self, -dy, bSafe )) break;
if (cr_try_jump( self, -dy )) return false;
if (bReverse) dy = -dy;
if (dy>90) break;
}
}
if (self->bot_info->b_crouch && cr_can_stand( self )) {
cr_crouch(self,false);
}
return bRes;
}
qboolean cr_swim( edict_t *self )
{
vec3_t start, end, normal, velocity, end_pos;
trace_t trace;
int x, y;
float d;
VectorSubtract ( self->bot_info->move_target, self->s.origin, self->velocity );
VectorNormalize ( self->velocity );
VectorScale ( self->velocity, self->bot_pers->speed*WATER_SPEED_COEF, self->velocity );
VectorCopy ( self->velocity, velocity );
VectorMA( self->s.origin, FRAMETIME, velocity, end_pos );
// check if we can go where we want
VectorCopy( self->s.origin, start );
VectorCopy( end_pos, end );
trace = gi.trace( start, self->mins, self->maxs, end, self, MASK_PLAYERSOLID );
if (!trace.allsolid && !trace.startsolid && trace.fraction==1.f) {
// check that we are not leaving water by this move
end[2] += self->mins[2];
if ( !(gi.pointcontents(end) & (CONTENTS_WATER|CONTENTS_SOLID)) ) {
self->velocity[2] = 0;
VectorNormalize ( self->velocity );
VectorScale ( self->velocity, self->bot_pers->speed*WATER_SPEED_COEF, self->velocity );
return false;
}
return true;
}
VectorCopy ( trace.plane.normal, normal );
// check if we can go out of water
VectorCopy( end_pos, end );
VectorCopy( end, start );
start[2] += 2*STEPSIZE;
trace = gi.trace( start, self->mins, self->maxs, end, self, MASK_PLAYERSOLID );
if (trace.startsolid) {
start[2] -= STEPSIZE;
trace = gi.trace( start, self->mins, self->maxs, end, self, MASK_PLAYERSOLID );
}
if ( !trace.startsolid && !trace.allsolid && trace.fraction<0.99f &&
!(gi.pointcontents(trace.endpos) & CONTENTS_WATER) ) {
// yes, we can get out of the water!
VectorCopy ( trace.endpos, self->s.origin );
gi.linkentity(self);
return true;
}
d = DotProduct( normal, velocity );
if ( fabs(d)>0.9f*self->bot_pers->speed*WATER_SPEED_COEF ) {
vec3_t right, up, angles;
VectorCopy( end_pos, end );
vectoangles ( velocity, angles );
AngleVectors ( angles, NULL, right, up );
VectorScale( right, (fabs(self->maxs[0])+fabs(self->maxs[0]))/2, right );
VectorScale( up, (fabs(self->maxs[2])+fabs(self->maxs[2]))/2, up );
for ( x=-1; x<=1; x++ )
for ( y=-1; y<=1; y++ ) {
if ( x==0 && y==0 ) continue;
VectorCopy( end, start );
if (x) VectorMA( start, x, right, start );
if (y) VectorMA( start, y, up, start );
if ( !(gi.pointcontents(start) & CONTENTS_SOLID) ) {
VectorSubtract ( end, self->s.origin, self->velocity );
VectorMA ( self->velocity, y, up, self->velocity );
VectorNormalize ( self->velocity );
VectorScale ( self->velocity, self->bot_pers->speed*WATER_SPEED_COEF, self->velocity );
return true;
}
}
}
VectorMA( velocity, -0.6f*d, normal, self->velocity );
VectorNormalize( self->velocity );
VectorScale( self->velocity, self->bot_pers->speed*WATER_SPEED_COEF, self->velocity );
return false;
}
qboolean cr_move( edict_t *self, qboolean bFacing, qboolean bSafe )
{
qboolean bStuck;
if (bFacing) M_ChangeYaw (self);
if (self->bot_info->b_on_ladder) {
vec3_t start, end;
trace_t trace;
VectorSubtract ( self->bot_info->move_target, self->s.origin, self->velocity );
VectorNormalize( self->velocity );
VectorScale( self->velocity, 200, self->velocity );
VectorCopy ( self->velocity, self->movedir );
VectorCopy ( self->s.origin, start );
VectorCopy ( start, end );
end[2] += STEPSIZE;
trace = gi.trace ( start, self->mins, self->maxs, end, self, MASK_PLAYERSOLID );
if (!trace.allsolid && !trace.startsolid && trace.fraction==1.f) {
VectorCopy ( end, self->s.origin );
gi.linkentity( self );
self->bot_info->time_last_stuck=0;
return true;
}
// we're stuck!
if (!self->bot_info->time_last_stuck) {
self->bot_info->time_last_stuck=level.time;
return true;
}
return (level.time - self->bot_info->time_last_stuck)<3*TIME_STUCK;
}
if ( self->waterlevel>2 ||
(self->waterlevel && !self->groundentity)) {
if (self->bot_info->b_crouch) cr_crouch(self,false);
bStuck = !cr_swim(self);
if (bStuck) {
if (!self->bot_info->time_last_stuck) {
self->bot_info->time_last_stuck=level.time;
return true;
}
return (level.time - self->bot_info->time_last_stuck)<3*TIME_STUCK;
}
self->bot_info->time_last_stuck=0;
return true;
}
if (!self->groundentity) {
if ( is_touched2d( self, self->bot_info->move_target ) ) {
VectorClear(self->movedir);
}
self->velocity[0] = self->movedir[0];
self->velocity[1] = self->movedir[1];
return true;
}
// we hit the slope!
if (self->bot_info->b_on_slope) {
self->bot_info->b_on_slope = false;
self->bot_info->time_last_stuck = 0;
return false;
}
bStuck = !cr_move_avoid( self, bSafe );
if (bStuck) {
self->bot_info->move_block_count=0;
// can't move
if (!self->bot_info->time_last_stuck) {
self->bot_info->time_last_stuck=level.time;
return true;
}
else
if ( (level.time - self->bot_info->time_last_stuck) >
(cr_vertical_ok(self) ? 10*TIME_STUCK : (cr_wait_ok(self) ? 5*TIME_STUCK : TIME_STUCK) ) ) {
// gi.bprintf ( PRINT_MEDIUM, "%s reporting stuck!\n", self->client->pers.netname );
self->bot_info->time_last_stuck=0;
return false;
}
else
return true;
}
if (self->bot_info->move_block_count>1)
self->bot_info->time_last_stuck=0;
else
self->bot_info->move_block_count++;
return true;
}
qboolean cr_moveto( edict_t *self )
{
vec3_t move;
float dt;
VectorSubtract ( self->bot_info->move_target, self->s.origin, move );
if (!is_closer_b( self->bot_info->move_target, self->bot_info->last_move_target, 1 )) {
VectorCopy ( self->bot_info->move_target, self->bot_info->last_move_target );
dt = 0.5f + 1.2f*VectorLength(move)/self->bot_pers->speed;
if (self->waterlevel>1) dt /= WATER_SPEED_COEF;
else if (self->bot_info->b_crouch) dt /= CROUCH_SPEED_COEF;
self->bot_info->time_last_move_target = level.time + dt;
// gi.dprintf( "%s has %.1f sec to cover %.1f distance!\n", self->client->pers.netname, dt, VectorLength(move) );
}
else {
if (level.time > self->bot_info->time_last_move_target) {
// gi.dprintf( "time is up for %s!\n", self->client->pers.netname );
return false;
}
}
self->ideal_yaw = vectoyaw(move);
return cr_move( self, true, (self->bot_info->move_target[2]-self->s.origin[2])>-STEPSIZE );
}
qboolean cr_move_yaw( edict_t *self, qboolean bFacing, qboolean bSafe )
{
vec3_t move;
YawVector( self->ideal_yaw, move );
VectorMA( self->s.origin, 80, move, self->bot_info->move_target );
self->bot_info->move_target[2] += STEPSIZE;
if (self->waterlevel==1) self->bot_info->move_target[2] -= 2*STEPSIZE;
return cr_move( self, bFacing, bSafe );
}
void cr_update_enemy( edict_t *self )
{
if (!self->enemy) return;
if ( !self->enemy->inuse ||
self->enemy->deadflag>=DEAD_DYING || !visible(self,self->enemy)) {
// we lost him!
cr_forget_enemy(self);
return;
}
VectorCopy( self->enemy->s.origin, self->monsterinfo.last_sighting );
self->monsterinfo.trail_time = level.time;
}
void cr_try_attack( edict_t *self )
{
if (!self->enemy) return;
if ( distance(self, self->enemy)<self->bot_pers->attack_range &&
fabs(self->s.origin[2]-self->enemy->s.origin[2])<(self->bot_pers->attack_range*0.5f) ) {
self->think = cr_think_attack;
}
}
void cr_check_ground( edict_t *self )
{
vec3_t point;
trace_t trace;
qboolean was_on_ladder;
self->bot_info->b_on_platform = false;
was_on_ladder = self->bot_info->b_on_ladder;
self->bot_info->b_on_ladder = false;
if (self->velocity[2] > 200) {
self->groundentity = NULL;
return;
}
// check if we are on ladder
if ( self->deadflag!=DEAD_DEAD &&
self->bot_info->move_target[2]>self->s.origin[2] ) {
vec3_t start, end;
if (!was_on_ladder) YawVector( self->ideal_yaw, self->bot_info->ladder_dir );
VectorCopy( self->s.origin, start );
VectorMA ( start, was_on_ladder ? 60 : 50, self->bot_info->ladder_dir, end );
trace = gi.trace ( start, self->mins, self->maxs, end, self, CONTENTS_LADDER | CONTENTS_SOLID );
if ( trace.contents & CONTENTS_LADDER ) {
self->bot_info->b_on_ladder = true;
if (!was_on_ladder) {
// face the ladder!
VectorScale ( trace.plane.normal, -1, self->bot_info->ladder_dir );
}
// gi.dprintf("%.1f %.1f %.1f \n", trace.plane.normal[0], trace.plane.normal[1], trace.plane.normal[2] );
}
else {
if (was_on_ladder) {
VectorScale( self->bot_info->ladder_dir, 150, self->velocity );
self->velocity[2] = 250;
VectorCopy ( self->velocity, self->movedir );
}
}
}
// if the hull point one-quarter unit down is solid the entity is on ground
point[0] = self->s.origin[0];
point[1] = self->s.origin[1];
point[2] = self->s.origin[2] - 0.25;
trace = gi.trace ( self->s.origin, self->mins, self->maxs, point, self, MASK_PLAYERSOLID );
// check steepness
if ( trace.plane.normal[2] < 0.7 && !trace.startsolid) {
self->groundentity = NULL;
if (trace.plane.normal[0]!=0 || trace.plane.normal[1]!=0) {
VectorScale( trace.plane.normal, 100, self->velocity );
VectorCopy ( self->velocity, self->movedir );
self->bot_info->b_on_slope = true;
}
return;
}
if (!trace.startsolid && !trace.allsolid) {
VectorCopy (trace.endpos, self->s.origin);
self->groundentity = trace.ent;
self->groundentity_linkcount = trace.ent->linkcount;
self->velocity[2] = 0;
}
if (self->groundentity && self->groundentity->blocked)
self->bot_info->b_on_platform = true;
}
void cr_skip_enemy( edict_t *self )
{
if (!self->enemy) return;
cr_add_unreachable( self, self->enemy, UNREACHABLE_ENEMY_TIMEOUT );
cr_forget_enemy(self);
}
void cr_skip_pickup_target( edict_t *self )
{
if (self->bot_info->pickup_target) {
cr_add_unreachable( self, self->bot_info->pickup_target,
strcmp( self->bot_info->pickup_target->classname, "item_flag_team1" )==0 ||
strcmp( self->bot_info->pickup_target->classname, "item_flag_team2" )==0 ?
UNREACHABLE_TIMEOUT/3 : UNREACHABLE_TIMEOUT );
}
cr_forget_pickup_target( self );
}
void cr_do_physics( edict_t* self )
{
cr_check_ground (self);
if (self->bot_info->b_airborn && self->groundentity) {
self->bot_info->time_next_jump = level.time + JUMP_DELAY;
}
self->bot_info->b_airborn = !self->groundentity;
}
void cr_catagorize_position (edict_t *self)
{
vec3_t point;
int cont;
// get waterlevel
point[0] = self->s.origin[0];
point[1] = self->s.origin[1];
point[2] = self->s.origin[2] + self->mins[2] + 1;
cont = gi.pointcontents (point);
if ( !(cont & CONTENTS_SOLID) )
self->bot_info->time_next_solid = level.time+1.f;
else {
if ( self->bot_info->time_next_solid < level.time ) {
self->bot_info->time_next_solid = level.time+1.f;
T_Damage ( self, self, self, vec3_origin, self->s.origin, vec3_origin, 20, 0, 0, MOD_SUICIDE );
// gi.dprintf( "%s is in solid!\n", self->client->pers.netname );
// FIXME: push bot along normal!
if (cont & MASK_WATER) {
self->s.origin[2] -= 0.2f;
gi.linkentity(self);
}
}
}
if (!(cont & MASK_WATER)) {
self->waterlevel = 0;
self->watertype = 0;
return;
}
self->watertype = cont;
self->waterlevel = 1;
point[2] += 24;
cont = gi.pointcontents (point);
if (!(cont & MASK_WATER)) return;
self->waterlevel = 2;
point[2] += 22;
cont = gi.pointcontents (point);
if (cont & MASK_WATER) self->waterlevel = 3;
}
void cr_update_environment( edict_t *self )
{
cr_do_physics(self);
cr_catagorize_position (self);
}
void cr_check_stuck( edict_t *self )
{
if ( level.time < self->bot_info->time_stuck_check ) return;
if ( is_closer_b( self->s.origin, self->bot_info->old_origin, 5 ) &&
!cr_vertical_ok(self) && !cr_wait_ok(self)) {
self->ideal_yaw = 360*qrandom();
YawVector( self->ideal_yaw, self->velocity );
self->bot_info->stuck_count++;
if (self->bot_info->stuck_count>4) cr_jump(self);
}
else
self->bot_info->stuck_count=0;
if (self->bot_info->stuck_count>8) {
T_Damage ( self, self, self, vec3_origin, self->s.origin, vec3_origin, 20, 0, 0, MOD_SUICIDE );
self->bot_info->stuck_count=0;
// gi.bprintf( PRINT_MEDIUM, "%s punished at %.2f %.2f %.2f\n", self->client->pers.netname, self->s.origin[0], self->s.origin[1], self->s.origin[2] );
}
VectorCopy ( self->s.origin, self->bot_info->old_origin );
self->bot_info->time_stuck_check = level.time + TIME_CHECK_STUCK;
}
qboolean cr_update( edict_t *self, qboolean bTryEnemy )
{
if (!self->inuse) return false;
self->nextthink = level.time + FRAMETIME;
if (self->bot_pers->b_adapting) {
if (self->bot_pers->adapt_count<-1 && self->bot_pers->skill>1) {
self->bot_pers->skill--;
cr_compute_skills(self,self->bot_pers->skill);
}
if (self->bot_pers->adapt_count>1 && self->bot_pers->skill<10) {
self->bot_pers->skill++;
cr_compute_skills(self,self->bot_pers->skill);
}
}
cr_update_environment(self);
if (bTryEnemy) {
cr_update_enemy(self);
if ( cr_low_health(self) ) {
if ( self->think==cr_think_attack ||
self->think==cr_think_chase ||
self->think==cr_think_chase_route ) {
self->think = cr_think_run_for_life;
}
}
else {
cr_try_attack(self);
}
}
cr_update_routes(self);
cr_check_stuck(self);
return true;
}
void cr_fire_and_run( edict_t *self )
{
qboolean bFire;
bFire = false;
//FIXME: face the enemy!
if (self->enemy) {
if (cr_infront(self,self->enemy))
bFire = cr_fire_weapon( self, self->enemy );
}
else {
edict_t *enemy, *best_enemy=NULL;
float d, best_dist=1e10;
// do a breaf search for enemy
for ( enemy=g_edicts+1; enemy<=(g_edicts+game.maxclients); enemy++) {
if (!enemy->inuse || enemy==self || (enemy->flags & FL_NOTARGET)) continue;
// attack only bots from other teams, unless we are in team #0
if ( self->client->pers.team_no>0 &&
enemy->client->pers.team_no==self->client->pers.team_no ) continue;
// is it dead already?
if ( enemy->deadflag>DEAD_DYING || (enemy->svflags & SVF_NOCLIENT) ) continue;
// can we see him?
if (!cr_infront (self,enemy)) continue;
if (!can_reach(self,enemy)) continue;
d = dist2(self->s.origin,enemy->s.origin);
if (d>ATTACK_RANGE_MAX*ATTACK_RANGE_MAX) continue;
if (!best_enemy || d<best_dist ) {
best_enemy = enemy;
best_dist = d;
if (best_dist<MELEE_COMBAT_DIST*MELEE_COMBAT_DIST) break;
}
}
if (best_enemy) {
bFire = cr_fire_weapon( self, best_enemy );
}
}
if (bFire) {
self->client->anim_end = self->s.frame;
if (!self->bot_info->b_crouch) self->s.frame = FRAME_attack1;
else self->s.frame = FRAME_crattak1;
}
else {
if (!self->bot_info->b_crouch) cr_animate_frames( self, FRAME_run1, FRAME_run6 );
else cr_animate_frames( self, FRAME_crwalk1, FRAME_crwalk6 );
}
}
void cr_post_think( edict_t *self )
{
int i;
edict_t* other;
if ( !self->bot_info->b_shot_this_frame &&
self->bot_info->time_stop_shoting > level.time ) {
cr_fire_weapon( self, NULL );
}
self->bot_info->b_shot_this_frame = false;
VectorCopy ( self->s.angles, self->client->ps.viewangles);
VectorCopy ( self->s.angles, self->client->v_angle);
// turn on powerups
if (!(self->flags & FL_POWER_ARMOR) &&
(self->client->pers.inventory[INDEX_POWER_SCREEN]>0 || self->client->pers.inventory[INDEX_POWER_SHIELD]>0) &&
self->client->pers.inventory[INDEX_CELLS]>0 ) {
self->flags |= FL_POWER_ARMOR;
gi.sound( self, CHAN_AUTO, gi.soundindex("misc/power1.wav"), 1, ATTN_NORM, 0 );
}
if ( self->client->quad_framenum<level.framenum &&
self->client->pers.inventory[INDEX_QUAD]>0 ) {
gitem_t *item;
item = FindItem("Quad Damage");
if (item && item->use) item->use( self, item );
}
if ( self->client->invincible_framenum<level.framenum &&
self->client->pers.inventory[INDEX_INVULN]>0 ) {
gitem_t *item;
item = FindItem("Invulnerability");
if (item && item->use) item->use( self, item );
}
if ( self->waterlevel>2 &&
self->client->breather_framenum<level.framenum &&
self->client->pers.inventory[INDEX_BREATHER]>0 ) {
gitem_t *item;
item = FindItem("Rebreather");
if (item && item->use) item->use( self, item );
}
// CTF stuff
if (self->client->ctf_grapple) {
CTFGrapplePull(self->client->ctf_grapple);
gi.linkentity(self);
}
CTFApplyRegeneration(self);
for (i = 1; i <= maxclients->value; i++) {
other = g_edicts + i;
if (other->inuse && other->client->chase_target == self)
UpdateChaseCam(other);
}
}
qboolean cr_try_pickup_urgent ( edict_t *self )
{
if ( self->enemy &&
self->enemy->health>0 && self->enemy->health<50 ) return false;
if (!cr_find_pickup_urgent(self)) return false;
self->bot_info->old_think = self->think;
self->think = cr_think_temp_target;
self->bot_info->last_node=NULL;
self->bot_info->next_node=NULL;
// gi.bprintf ( PRINT_MEDIUM, "urgently going for %s\n", self->bot_info->pickup_target->classname );
return true;
}
void cr_think_team_help( edict_t *self )
{
qboolean bStuck;
if (!cr_update(self,true)) return;
if (self->bot_info->bot_assignment!=ASSN_HELP) {
self->think = cr_think;
return;
}
// if (cr_try_pickup_urgent(self)) return;
cr_find_enemy(self);
if ( level.time>self->bot_info->time_next_chase_update ) {
self->bot_info->time_next_chase_update = level.time + 0.5f;
if ( cr_find_closest_node( self->bot_info->team_leader ) !=
self->bot_info->target_node ) {
if (!cr_find_route( self, self->bot_info->team_leader->s.origin, false )) {
cr_dismiss(self);
self->think = cr_think;
}
}
}
//FIXME: dodge while following the path
bStuck = !cr_moveto(self);
// move towards enemy by following the route
if (is_touched( self, self->bot_info->move_target )) {
// got there!
self->bot_info->last_node = self->bot_info->next_node;
self->bot_info->next_node = cr_get_next_path_node(self);
if (self->bot_info->next_node)
cr_set_move_target( self, self->bot_info->next_node->position );
else {
cr_dismiss(self);
self->think = cr_think;
}
}
else
if ( bStuck || cr_no_way( self, self->bot_info->move_target ) ) {
// can't get there
cr_remove_direct_route( self->bot_info->last_node, self->bot_info->next_node );
// let's find detour!
if (!cr_find_route( self, self->bot_info->team_leader->s.origin, false )) {
cr_dismiss(self);
self->think = cr_think;
}
}
// check if we are near our destination
if (self->bot_info->team_leader) {
if ( (int)dist2(self->s.origin,self->bot_info->team_leader->s.origin) < (rand()&0x2FFF) ) {
cr_dismiss(self);
if (self->enemy) self->think = cr_think_attack;
else self->think = cr_think_salute;
}
}
cr_fire_and_run(self);
}
void cr_think_team_group( edict_t *self )
{
qboolean bStuck;
if (!cr_update(self,true)) return;
if (self->bot_info->bot_assignment!=ASSN_GROUP) {
self->think = cr_think;
return;
}
// if (cr_try_pickup_urgent(self)) return;
cr_find_enemy(self);
if ( level.time>self->bot_info->time_next_chase_update ) {
self->bot_info->time_next_chase_update = level.time + 0.5f;
if ( cr_find_closest_node( self->bot_info->team_leader ) !=
self->bot_info->target_node ) {
if (!cr_find_route( self, self->bot_info->team_leader->s.origin, false )) {
self->bot_info->target_node = NULL;
self->think = cr_think;
}
}
}
//FIXME: dodge while following the path
bStuck = !cr_moveto(self);
// move towards enemy by following the route
if (is_touched( self, self->bot_info->move_target )) {
// got there!
self->bot_info->last_node = self->bot_info->next_node;
self->bot_info->next_node = cr_get_next_path_node(self);
if (self->bot_info->next_node)
cr_set_move_target( self, self->bot_info->next_node->position );
else {
self->bot_info->target_node = NULL;
self->think = cr_think;
}
}
else
if ( bStuck || cr_no_way( self, self->bot_info->move_target ) ) {
// can't get there
cr_remove_direct_route( self->bot_info->last_node, self->bot_info->next_node );
// let's find detour!
if (!cr_find_route( self, self->bot_info->team_leader->s.origin, false )) {
self->bot_info->target_node = NULL;
self->think = cr_think;
}
}
// check if we are near our destination
if (self->bot_info->team_leader) {
if ( (int)dist2(self->s.origin,self->bot_info->team_leader->s.origin) < (rand()&0x2FFF) ) {
if (self->enemy) self->think = cr_think_attack;
else self->think = cr_think_salute;
}
}
cr_fire_and_run(self);
}
void cr_think_wait( edict_t *self )
{
if (!cr_update(self,false)) return;
self->bot_info->time_last_stuck=0;
self->bot_info->stuck_count=0;
// scan around
self->ideal_yaw = anglemod( self->ideal_yaw + self->bot_info->strafe_dir*100*FRAMETIME );
M_ChangeYaw(self);
if (level.time > self->bot_info->time_last_strafe_switch) {
self->bot_info->time_last_strafe_switch = level.time + 2.f+qrandom();
self->bot_info->strafe_dir = -self->bot_info->strafe_dir;
}
if (cr_find_enemy(self))
cr_sight(self,self->enemy);
else
if (self->bot_info->bot_assignment==ASSN_GROUP) {
if ( (int)dist2(self->s.origin,self->bot_info->team_leader->s.origin) > 0x5000 ) {
self->think = cr_think_team_group;
}
}
else
if (self->bot_info->bot_assignment==ASSN_NONE) {
self->think = cr_think;
}
if (!self->bot_info->b_crouch) cr_animate_frames( self, FRAME_stand01, FRAME_stand40 );
else cr_animate_frames( self, FRAME_crstnd01, FRAME_crstnd19 );
}
void cr_think_team_patrol( edict_t *self )
{
qboolean bStuck;
if (!cr_update(self,true)) return;
if (self->bot_info->bot_assignment!=ASSN_PATROL) {
self->think = cr_think;
return;
}
if (cr_try_pickup_urgent(self)) return;
cr_find_enemy(self);
if ( level.time>self->bot_info->time_next_chase_update ) {
self->bot_info->time_next_chase_update = level.time + 0.5f;
if ( cr_find_closest_node_pos( self, self->bot_info->bot_anchor ) !=
self->bot_info->target_node ) {
if (!cr_find_route( self, self->bot_info->bot_anchor, false )) {
cr_dismiss(self);
self->think = cr_think;
}
}
}
//FIXME: dodge while following the path
bStuck = !cr_moveto(self);
// move towards enemy by following the route
if (is_touched( self, self->bot_info->move_target )) {
// got there!
self->bot_info->last_node = self->bot_info->next_node;
self->bot_info->next_node = cr_get_next_path_node(self);
if (self->bot_info->next_node)
cr_set_move_target( self, self->bot_info->next_node->position );
else {
self->bot_info->target_node = NULL;
self->think = cr_think;
}
}
else
if ( bStuck || cr_no_way( self, self->bot_info->move_target ) ) {
// can't get there
cr_remove_direct_route( self->bot_info->last_node, self->bot_info->next_node );
// let's find detour!
if (!cr_find_route( self, self->bot_info->bot_anchor, false )) {
cr_dismiss(self);
self->think = cr_think;
}
}
// check if we are near our destination
if (self->bot_info->team_leader) {
if ( (int)dist2(self->s.origin,self->bot_info->team_leader->s.origin) < (rand()&0x2FFF) ) {
if (self->enemy) self->think = cr_think_attack;
else self->think = cr_think_salute;
}
}
cr_fire_and_run(self);
}
void cr_think_team_guard( edict_t *self )
{
qboolean bStuck;
if (!cr_update(self,true)) return;
if (self->bot_info->bot_assignment!=ASSN_GUARD) {
self->think = cr_think;
return;
}
// if (cr_try_pickup_urgent(self)) return;
cr_find_enemy(self);
if ( level.time>self->bot_info->time_next_chase_update ) {
self->bot_info->time_next_chase_update = level.time + 0.5f;
if ( cr_find_closest_node_pos( self, self->bot_info->bot_anchor ) !=
self->bot_info->target_node ) {
if (!cr_find_route( self, self->bot_info->bot_anchor, false )) {
// got to the point!
self->think = cr_think_salute;
self->bot_info->target_node = NULL;
}
}
}
//FIXME: dodge while following the path
bStuck = !cr_moveto(self);
// move towards enemy by following the route
if (is_touched( self, self->bot_info->move_target )) {
// got there!
self->bot_info->last_node = self->bot_info->next_node;
self->bot_info->next_node = cr_get_next_path_node(self);
if (self->bot_info->next_node)
cr_set_move_target( self, self->bot_info->next_node->position );
else {
// got to the point!
self->think = cr_think_salute;
self->bot_info->target_node = NULL;
}
}
else
if ( bStuck || cr_no_way( self, self->bot_info->move_target ) ) {
// can't get there
cr_remove_direct_route( self->bot_info->last_node, self->bot_info->next_node );
// let's find detour!
if (!cr_find_route( self, self->bot_info->bot_anchor, false )) {
// got to the point!
self->think = cr_think_salute;
self->bot_info->target_node = NULL;
}
}
// check if we are near our destination
if (self->bot_info->team_leader) {
if ( (int)dist2(self->s.origin,self->bot_info->team_leader->s.origin) < (rand()&0x2FFF) ) {
if (self->enemy) self->think = cr_think_attack;
else self->think = cr_think_salute;
}
}
cr_fire_and_run(self);
}
void cr_think_temp_target( edict_t *self )
{
if (!cr_update(self,false)) return;
if ( self->bot_info->pickup_target==NULL ||
(self->bot_info->pickup_target->svflags & SVF_NOCLIENT) ||
self->bot_info->pickup_target->solid==SOLID_NOT ||
!cr_moveto( self ) || is_touched(self, self->bot_info->move_target) ||
cr_no_way( self, self->bot_info->move_target ) ) {
// can't get there, let's return to our old target
cr_skip_pickup_target(self);
if (self->bot_info->old_think) self->think = self->bot_info->old_think;
else self->think = cr_think;
self->bot_info->old_think = NULL;
}
if (!self->bot_info->b_crouch) cr_animate_frames( self, FRAME_run1, FRAME_run6 );
else cr_animate_frames( self, FRAME_crwalk1, FRAME_crwalk6 );
}
void cr_change_weapon( edict_t *self )
{
int i;
self->client->pers.weapon = self->client->newweapon;
self->client->newweapon = NULL;
self->client->machinegun_shots = 0;
// set visible model
if (self->s.modelindex == 255) {
if (self->client->pers.weapon)
i = ((self->client->pers.weapon->weapmodel & 0xff) << 8);
else
i = 0;
self->s.skinnum = (self - g_edicts - 1) | i;
}
// gi.bprintf ( PRINT_MEDIUM, "new weapon selected %s\n", self->client->pers.weapon->classname );
self->bot_info->time_next_shot = level.time + TIME_WEAPON_CHANGE;
if (self->client->pers.weapon && self->client->pers.weapon->ammo)
self->client->ammo_index = ITEM_INDEX(FindItem(self->client->pers.weapon->ammo));
else
self->client->ammo_index = 0;
}
void cr_choose_best_weapon( edict_t *self )
{
float enemy_dist = -1;
if (self->enemy) enemy_dist = dist( self->s.origin, self->enemy->s.origin );
else if (self->oldenemy) enemy_dist = dist( self->s.origin, self->oldenemy->s.origin );
if ( self->client->pers.inventory[INDEX_CELLS]>=50 &&
self->client->pers.inventory[INDEX_BFG] ) {
self->client->newweapon = FindItem ("BFG10K");
return;
}
if ( self->client->pers.inventory[INDEX_SLUGS] &&
self->client->pers.inventory[INDEX_RAILGUN] ) {
self->client->newweapon = FindItem ("railgun");
return;
}
if ( self->client->pers.inventory[INDEX_CELLS] &&
self->client->pers.inventory[INDEX_HYPERBLASTER] ) {
self->client->newweapon = FindItem ("hyperblaster");
return;
}
if ( (enemy_dist<0 || enemy_dist>(120-self->health)) &&
self->client->pers.inventory[INDEX_ROCKETS] &&
self->client->pers.inventory[INDEX_ROCKET_LAUNCHER] ) {
self->client->newweapon = FindItem ("rocket launcher");
return;
}
if ( (enemy_dist<0 || (enemy_dist>(120-self->health) && enemy_dist<500)) &&
self->client->pers.inventory[INDEX_GRENADES] &&
self->client->pers.inventory[INDEX_GRENADE_LAUNCHER] ) {
self->client->newweapon = FindItem ("grenade launcher");
return;
}
if ( (enemy_dist<0 || enemy_dist<1000) &&
self->client->pers.inventory[INDEX_BULLETS] &&
self->client->pers.inventory[INDEX_CHAINGUN] ) {
self->client->newweapon = FindItem ("chaingun");
return;
}
if ( self->client->pers.inventory[INDEX_BULLETS] &&
self->client->pers.inventory[INDEX_MACHINEGUN] ) {
self->client->newweapon = FindItem ("machinegun");
return;
}
if ( (enemy_dist<0 || enemy_dist<600) &&
self->client->pers.inventory[INDEX_SHELLS] &&
self->client->pers.inventory[INDEX_SUPER_SHOTGUN] ) {
self->client->newweapon = FindItem ("super shotgun");
return;
}
if ( (enemy_dist<0 || enemy_dist<800) &&
self->client->pers.inventory[INDEX_SHELLS] &&
self->client->pers.inventory[INDEX_SHOTGUN] ) {
self->client->newweapon = FindItem ("shotgun");
return;
}
self->client->newweapon = FindItem ("blaster");
}
static vec3_t flash_offset = {
1.48, 7.4, 9.6 };
qboolean cr_aim_ahead ( edict_t *self, edict_t *target, float aim_coeff, float speed,
vec3_t start, vec3_t aim, qboolean aim_at_feet )
{
vec3_t end, forward, right, up, to;
float tt, d;
int tries;
trace_t trace;
float skill_mod;
vec3_t dir;
float r, u, dd;
AngleVectors ( self->s.angles, forward, right, NULL );
G_ProjectSource ( self->s.origin, flash_offset, forward, right, start );
if (!target) {
VectorSubtract( self->bot_info->shoot_last_target, start, aim );
VectorNormalize( aim );
return true;
}
skill_mod = (11-self->bot_pers->skill)*0.1f;
VectorCopy( target->s.origin, to );
if (aim_at_feet) to[2] += target->mins[2]*(0.5f+0.4f*qrandom())*(1.f-skill_mod);
VectorSubtract( to, start, aim );
d = VectorLength(aim);
VectorCopy( to, self->bot_info->shoot_last_target );
for ( tries=0; ; tries++) {
tt = 0.8f*aim_coeff*crandom()*skill_mod;
if (speed>0) tt += d/speed;
VectorMA ( to, tt, target->velocity, end );
VectorSubtract ( end, start, aim );
vectoangles ( aim, dir );
AngleVectors ( dir, forward, right, up );
dd = d*skill_mod*(0.05f+qrandom());
r = dd*crandom()*BOT_ACCURACY_HORZ*aim_coeff;
u = dd*crandom()*BOT_ACCURACY_VERT*aim_coeff;
VectorMA ( aim, r, right, aim );
VectorMA ( aim, u, up, aim );
VectorNormalize(aim);
// test if we can get to the target shooting like this
VectorMA( start, d, aim, end );
trace = gi.trace ( start, vec3_origin, vec3_origin, end, self, MASK_SHOT );
if (trace.startsolid || trace.allsolid) return false;
if (trace.fraction>0.9f || trace.ent==target) break;
if (tries>2 && self->client->pers.team_no>0 && trace.ent->client &&
self->client->pers.team_no!=trace.ent->client->pers.team_no) break;
if (tries>4) return false;
}
VectorMA( start, d, aim, self->bot_info->shoot_last_target );
return true;
}
qboolean cr_fire_weapon( edict_t *self, edict_t *target )
{
vec3_t start, aim;
int damage, damage_amp, cur_weapon;
qboolean b_quad;
self->bot_info->b_shot_this_frame = true;
if (self->bot_info->time_next_weapon_change>self->bot_info->time_stop_shoting)
self->bot_info->time_next_weapon_change = self->bot_info->time_stop_shoting;
if (self->bot_info->time_next_weapon_change < level.time) {
cr_choose_best_weapon(self);
if ( self->client->pers.weapon != self->client->newweapon ) {
self->bot_info->time_next_weapon_change = level.time + 1.5f*TIME_WEAPON_CHANGE;
cr_change_weapon(self);
return false;
}
}
cur_weapon = ITEM_INDEX(self->client->pers.weapon);
if (target) {
if (cur_weapon == INDEX_RAILGUN) {
self->bot_info->time_stop_shoting = level.time + TIME_RAILGUN_AFTERSHOT;
}
else if ( cur_weapon==INDEX_HYPERBLASTER ) {
self->bot_info->time_stop_shoting = level.time + TIME_HYPERBLASTER_AFTERSHOT;
}
else if ( cur_weapon==INDEX_ROCKET_LAUNCHER ) {
self->bot_info->time_stop_shoting = level.time + TIME_ROCKET_AFTERSHOT;
}
else if ( cur_weapon==INDEX_GRENADE_LAUNCHER ) {
self->bot_info->time_stop_shoting = level.time + TIME_GRENADE_AFTERSHOT;
}
else if ( cur_weapon==INDEX_CHAINGUN ) {
self->bot_info->time_stop_shoting = level.time + TIME_CHAINGUN_AFTERSHOT;
}
else if ( cur_weapon==INDEX_MACHINEGUN ) {
self->bot_info->time_stop_shoting = level.time + TIME_MACHINEGUN_AFTERSHOT;
}
else if ( cur_weapon==INDEX_SUPER_SHOTGUN ) {
self->bot_info->time_stop_shoting = level.time + TIME_SUPERSHOTGUN_AFTERSHOT;
}
else if ( cur_weapon==INDEX_SHOTGUN ) {
self->bot_info->time_stop_shoting = level.time + TIME_SHOTGUN_AFTERSHOT;
}
else if ( cur_weapon==INDEX_BLASTER ) {
self->bot_info->time_stop_shoting = level.time + TIME_BLASTER_AFTERSHOT;
}
else if ( cur_weapon==INDEX_BFG ) {
if ( self->bot_info->time_next_shot <= level.time &&
self->bot_info->time_weapon_spin_down <= level.time &&
self->bot_info->time_weapon_spin_up < level.time ) {
cr_effect(self,MZ_BFG);
self->bot_info->time_stop_shoting = level.time + TIME_BFG_AFTERSHOT;
self->bot_info->time_weapon_spin_up = level.time + TIME_BFG_SPINUP;
return true;
}
}
else {
self->bot_info->time_stop_shoting = level.time + FRAMETIME/2;
}
}
else {
if ( cur_weapon==INDEX_HYPERBLASTER ) {
self->bot_info->time_weapon_spin_down = level.time + TIME_WEAPON_CHANGE;
}
else if ( cur_weapon==INDEX_CHAINGUN ) {
self->bot_info->time_weapon_spin_down = level.time + TIME_WEAPON_CHANGE;
}
else {
self->bot_info->time_weapon_spin_down = level.time - FRAMETIME;
}
}
if ( self->bot_info->time_next_shot > level.time ) return false;
if ( self->bot_info->time_weapon_spin_down > level.time ) return false;
if ( self->bot_info->time_weapon_spin_up > level.time ) return false;
if ( (int)dmflags->value & DF_INFINITE_AMMO ) {
if (self->client->ammo_index) self->client->pers.inventory[self->client->ammo_index] = 999;
}
if (self->bot_info->time_next_fight_message<level.time) {
cr_message_fight ( self, target );
self->bot_info->time_next_fight_message = level.time + FIGHT_MSG_DELAY*(0.7f+0.6f*qrandom());
}
b_quad = self->client->quad_framenum > level.framenum;
damage_amp = 1;
if ( b_quad ) damage_amp = 4;
if ( self->client->pers.inventory[INDEX_TECH2] ) damage_amp *= 2;
#define hasted(x) (CTFApplyHaste(self) ? (x)/2 : (x))
if ( cur_weapon==INDEX_RAILGUN ) {
// fire "railgun"
if (!cr_aim_ahead( self, target, 2.f, RAILGUN_SPEED, start, aim, false )) return false;
damage = RAILGUN_DAMAGE*damage_amp;
fire_rail ( self, start, aim, damage, b_quad ? 800 : 200 );
cr_effect( self, MZ_RAILGUN );
self->bot_info->time_next_shot = level.time + hasted(TIME_RAILGUN_SHOT);
self->client->pers.inventory[self->client->ammo_index] -= self->client->pers.weapon->quantity;
return true;
}
if ( cur_weapon==INDEX_HYPERBLASTER ) {
// fire "hyperblaster"
if (!cr_aim_ahead( self, target, 1.6f, HYPERBLASTER_SPEED, start, aim, false )) return false;
damage = HYPERBLASTER_DAMAGE*damage_amp;
fire_blaster ( self, start, aim, damage, BLASTER_SPEED, EF_HYPERBLASTER, true );
cr_effect( self, MZ_HYPERBLASTER );
self->bot_info->time_next_shot = level.time + hasted(TIME_HYPERBLASTER_SHOT);
self->client->pers.inventory[self->client->ammo_index] -= self->client->pers.weapon->quantity;
return true;
}
if ( cur_weapon==INDEX_BFG ) {
// fire "BFG"
if (!cr_aim_ahead( self, target, 1.f, BFG_SPEED, start, aim, false )) return false;
damage = BFG_DAMAGE*damage_amp;
fire_bfg ( self, start, aim, damage, BFG_SPEED, 1000 );
self->bot_info->time_next_shot = level.time + hasted(TIME_BFG_SHOT);
self->bot_info->time_weapon_spin_down = level.time + TIME_BFG_SPINUP;
self->client->pers.inventory[self->client->ammo_index] -= 50;
return true;
}
if ( cur_weapon==INDEX_ROCKET_LAUNCHER ) {
// fire "hyperblaster"
if (!cr_aim_ahead( self, target, 0.6f, ROCKET_SPEED, start, aim, true )) return false;
VectorMA( start, 5.f, aim, start );
damage = (1.f+0.2f*qrandom())*ROCKET_DAMAGE*damage_amp;
fire_rocket ( self, start, aim, damage, ROCKET_SPEED, 120/*damage_rad*/, damage/*radius_damage*/ );
cr_effect( self, MZ_ROCKET );
self->bot_info->time_next_shot = level.time + hasted(TIME_ROCKET_SHOT);
self->client->pers.inventory[self->client->ammo_index] -= self->client->pers.weapon->quantity;
return true;
}
if ( cur_weapon==INDEX_GRENADE_LAUNCHER ) {
// fire "grenade"
if (!cr_aim_ahead( self, target, 0.4f, GRENADE_SPEED, start, aim, rand()%1 )) return false;
damage = GRENADE_DAMAGE*damage_amp;
fire_grenade ( self, start, aim, damage, GRENADE_SPEED, 2.5, damage+40 );
cr_effect( self, MZ_GRENADE );
self->bot_info->time_next_shot = level.time + hasted(TIME_GRENADE_SHOT);
self->client->pers.inventory[self->client->ammo_index] -= self->client->pers.weapon->quantity;
return true;
}
if ( cur_weapon==INDEX_CHAINGUN ) {
// fire "chaingun"
if (!cr_aim_ahead( self, target, 1.8f, CHAINGUN_SPEED, start, aim, false )) return false;
damage = CHAINGUN_DAMAGE*damage_amp;
fire_bullet ( self, start, aim, damage, b_quad ? 8 : 2, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_CHAINGUN );
cr_effect( self, MZ_CHAINGUN2 );
self->bot_info->time_next_shot = level.time + hasted(TIME_CHAINGUN_SHOT);
self->client->pers.inventory[self->client->ammo_index] -= self->client->pers.weapon->quantity;
return true;
}
if ( cur_weapon==INDEX_MACHINEGUN ) {
// fire "machinegun"
if (!cr_aim_ahead( self, target, 1.1f, MACHINEGUN_SPEED, start, aim, false )) return false;
damage = MACHINEGUN_DAMAGE*damage_amp;
fire_bullet ( self, start, aim, damage, b_quad ? 8 : 2, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_MACHINEGUN );
cr_effect( self, MZ_MACHINEGUN );
self->bot_info->time_next_shot = level.time + hasted(TIME_MACHINEGUN_SHOT);
self->client->pers.inventory[self->client->ammo_index] -= self->client->pers.weapon->quantity;
return true;
}
if ( cur_weapon==INDEX_SUPER_SHOTGUN ) {
// fire "super shotgun"
if (!cr_aim_ahead( self, target, 1.4f, SUPERSHOTGUN_SPEED, start, aim, false )) return false;
damage = SUPERSHOTGUN_DAMAGE*damage_amp;
fire_shotgun ( self, start, aim, damage, b_quad ? 4*12 : 12,
DEFAULT_SHOTGUN_HSPREAD, 1.6f*DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SSHOTGUN_COUNT, MOD_SSHOTGUN );
cr_effect( self, MZ_SSHOTGUN );
self->bot_info->time_next_shot = level.time + hasted(TIME_SUPERSHOTGUN_SHOT);
self->client->pers.inventory[self->client->ammo_index] -= self->client->pers.weapon->quantity;
return true;
}
if ( cur_weapon==INDEX_SHOTGUN ) {
// fire "shotgun"
if (!cr_aim_ahead( self, target, 1.f, SHOTGUN_SPEED, start, aim, false )) return false;
damage = SHOTGUN_DAMAGE*damage_amp;
fire_shotgun ( self, start, aim, damage, b_quad ? 4*8 : 8,
500, 500, DEFAULT_DEATHMATCH_SHOTGUN_COUNT, MOD_SHOTGUN );
cr_effect( self, MZ_SHOTGUN );
self->bot_info->time_next_shot = level.time + hasted(TIME_SHOTGUN_SHOT);
self->client->pers.inventory[self->client->ammo_index] -= self->client->pers.weapon->quantity;
return true;
}
if ( cur_weapon==INDEX_BLASTER ) {
// fire "blaster"
if (!cr_aim_ahead( self, target, 0.8f, BLASTER_SPEED, start, aim, false )) return false;
damage = BLASTER_DAMAGE*damage_amp;
fire_blaster ( self, start, aim, damage, BLASTER_SPEED, EF_BLASTER, false );
cr_effect( self, MZ_BLASTER );
self->bot_info->time_next_shot = level.time + hasted(TIME_BLASTER_SHOT);
return true;
}
#undef hasted
return false;
}
qboolean cr_find_route( edict_t *self, vec3_t target, qboolean b_important )
{
path_node_t *node_stack1[MAX_STACK_NODE], *node_stack2[MAX_STACK_NODE];
path_node_t *node, *link_node, *target_node, *next_link;
path_node_t **nodes, **new_nodes;
int cur_stack, stack_nodes, new_stack_nodes, i, j, max_nodes, n_count;
float cur_dist, best_dist, d;
self->bot_info->next_node=NULL;
self->bot_info->last_node = cr_find_closest_node(self);
VectorCopy ( self->s.origin, self->s.old_origin);
VectorCopy ( target, self->s.origin );
target_node = cr_find_closest_node(self);
VectorCopy ( self->s.old_origin, self->s.origin );
self->bot_info->target_node = target_node;
if (target_node == self->bot_info->last_node) {
// gi.bprintf ( PRINT_MEDIUM, "Can't find route: starting node == target node\n" );
return false;
}
// initialize node network
for ( node=cr_node_head; node!=NULL; node=node->next ) node->route_dist=-1;
max_nodes = 20 + 3*self->bot_pers->skill;
if (b_important) max_nodes *= 2;
if (max_nodes>(MAX_PATH_NODES-2)) max_nodes = MAX_PATH_NODES-2;
// find actual route
self->bot_info->last_node->route_dist = 0.001;
node_stack1[0] = self->bot_info->last_node;
new_stack_nodes = 1;
cur_stack = 0;
n_count=0;
while (n_count<max_nodes) {
stack_nodes = new_stack_nodes;
new_stack_nodes = 0;
if (cur_stack==0) {
nodes = node_stack1;
new_nodes = node_stack2;
cur_stack=1;
}
else {
nodes = node_stack2;
new_nodes = node_stack1;
cur_stack=0;
}
best_dist = 1e32;
for ( i=0; i<stack_nodes; i++ ) {
node = *nodes++;
cur_dist = node->route_dist;
if (best_dist>cur_dist) best_dist=cur_dist;
// go through all links
for ( j=0; j<MAX_NODE_LINKS; j++ ) {
if ( !(link_node = node->link_to[j]) ) break;
d = cur_dist + node->link_dist[j];
if (link_node!=target_node && link_node->route_dist<0 && new_stack_nodes<MAX_STACK_NODE) {
// add new downlink node
*new_nodes = link_node;
new_nodes++;
new_stack_nodes++;
}
if ( link_node->route_dist<0 || link_node->route_dist>d ) {
// update route_dist if this route is shorter
link_node->route_dist = d;
}
}
}
if (!new_stack_nodes) {
// gi.bprintf ( PRINT_MEDIUM, "no new nodes! \n" );
break;
}
if ( target_node->route_dist>=0 && target_node->route_dist<best_dist ) {
// gi.bprintf ( PRINT_MEDIUM, "Route found! %.2f\n", target_node->route_dist );
break;
}
n_count++;
}
if ( target_node->route_dist<0 ) {
//gi.bprintf ( PRINT_MEDIUM, "Can't find route: no route to target\n" );
return false;
}
/*
// remove path indication
{ edict_t* line, *next_line;
for ( line = global_path_route; line!=NULL; line=next_line ) {
next_line = line->teamchain;
G_FreeEdict(line);
}
} */
self->bot_info->path_nodes=-1;
n_count=0;
node = target_node;
while (n_count<=max_nodes) {
// search for shortest possible distance from "node"
best_dist=0;
link_node=NULL;
for ( i=0; i<MAX_NODE_LINKS; i++ ) {
if ( !(next_link = node->link_from[i]) ) break;
d = next_link->route_dist;
if (d>=0 && (!link_node || best_dist>d) ) {
link_node = next_link;
best_dist = d;
}
}
self->bot_info->path[++self->bot_info->path_nodes]=node;
if (!link_node || link_node==self->bot_info->last_node) break;
node->route_dist = -2;
if(_DEBUG)
cr_draw_line( node->position, link_node->position, COLOR_BEAM_GREEN, &global_path_route );
node = link_node;
n_count++;
}
self->bot_info->next_node = node;
cr_set_move_target( self, node->position );
return true;
}
void cr_try_to_find_route( edict_t *self, vec3_t target )
{
if (!cr_find_route( self, target, false )) {
cr_skip_enemy(self);
self->think = cr_think;
}
else {
self->think = cr_think_chase_route;
}
}
/*
qboolean cr_try_to_hide( edict_t *self )
{
if ( (level.time - self->bot_info->time_indarkness) > FRAMETIME*5 ) {
gi.dprintf( "%s is hiding at %.1f %.1f %.1f!\n", self->client->pers.netname, self->s.origin[0], self->s.origin[1], self->s.origin[2] );
cr_crouch(self,true);
self->think = cr_think_hide;
return true;
}
return false;
}
void cr_think_hide( edict_t *self )
{
if (!cr_update(self,true)) return;
cr_animate_frames( self, FRAME_crstnd01, FRAME_crstnd19 );
}
*/
void cr_think_run_for_life ( edict_t *self )
{
qboolean bStuck;
if (!cr_update(self,true)) return;
//FIXME: implement searching for health only!
if (!self->bot_info->pickup_target) {
self->think = cr_think;
return;
}
bStuck = !cr_moveto(self);
if ( is_touched( self, self->bot_info->move_target ) ) {
if ( self->bot_info->pickup_target &&
is_touched( self, self->bot_info->pickup_target->s.origin ) ) {
self->bot_info->pickup_target=NULL;
}
else {
self->bot_info->last_node = self->bot_info->next_node;
self->bot_info->next_node = cr_get_next_path_node(self);
if (self->bot_info->next_node)
cr_set_move_target( self, self->bot_info->next_node->position );
else {
self->bot_info->pickup_target=NULL;
cr_find_pickup_on_map(self);
}
}
}
else
if ( bStuck || cr_no_way( self, self->bot_info->move_target ) ) {
cr_remove_direct_route( self->bot_info->last_node, self->bot_info->next_node );
cr_skip_pickup_target(self);
}
if (self->bot_info->pickup_target) {
if ( !self->bot_info->pickup_target->inuse ||
(self->bot_info->pickup_target->svflags & SVF_NOCLIENT) ||
self->bot_info->pickup_target->solid==SOLID_NOT ) {
cr_forget_pickup_target(self);
}
}
cr_fire_and_run(self);
}
void cr_think_chase_route( edict_t *self )
{
qboolean bStuck;
if (!cr_update(self,true)) return;
if (cr_try_pickup_urgent(self)) return;
cr_find_enemy(self);
if (!self->enemy) {
if ( level.time > self->bot_info->time_chase ) {
self->bot_info->next_node=NULL;
self->think = cr_think;
return;
}
if ( self->oldenemy &&
(level.time-self->monsterinfo.trail_time)>(5.f*qrandom()*(11-self->bot_pers->skill)) ) {
VectorCopy( self->oldenemy->s.origin, self->monsterinfo.last_sighting );
self->monsterinfo.trail_time = level.time;
cr_try_to_find_route( self, self->monsterinfo.last_sighting );
}
}
else {
self->bot_info->time_chase = level.time + CHASE_TIME*(0.9f+qrandom()*0.2f);
}
//FIXME: check for new position not so often
if ( self->enemy &&
cr_find_closest_enemy_node(self)!=self->bot_info->target_node ) {
cr_try_to_find_route( self, self->enemy->s.origin );
}
//FIXME: dodge while following the path
bStuck = !cr_moveto(self);
// move towards enemy by following the route
if (is_touched( self, self->bot_info->move_target )) {
// got there!
self->bot_info->last_node = self->bot_info->next_node;
self->bot_info->next_node = cr_get_next_path_node(self);
if (self->bot_info->next_node)
cr_set_move_target( self, self->bot_info->next_node->position );
else {
self->think = cr_think;
}
}
else
if ( bStuck || cr_no_way( self, self->bot_info->move_target ) ) {
// can't get there
cr_remove_direct_route( self->bot_info->last_node, self->bot_info->next_node );
// let's find detour!
if (self->enemy) cr_try_to_find_route( self, self->enemy->s.origin );
else cr_try_to_find_route( self, self->monsterinfo.last_sighting );
}
cr_fire_and_run(self);
}
void cr_think_follow_route( edict_t *self )
{
qboolean bStuck;
if (!cr_update(self,true)) return;
bStuck = !cr_moveto(self);
// move towards enemy by following the route
if (is_touched( self, self->bot_info->move_target )) {
// got there!
self->bot_info->last_node = self->bot_info->next_node;
self->bot_info->next_node = cr_get_next_path_node(self);
if (self->bot_info->next_node)
cr_set_move_target( self, self->bot_info->next_node->position );
else {
self->think = cr_think;
}
}
else
if ( bStuck || cr_no_way( self, self->bot_info->move_target ) ) {
// can't get there
cr_remove_direct_route( self->bot_info->last_node, self->bot_info->next_node );
if (!cr_find_route( self, self->monsterinfo.last_sighting, false )) {
self->think = cr_think;
}
}
if ( (level.time-self->monsterinfo.trail_time)>TEAM_HELP_TIME ) {
self->think = cr_think;
}
cr_fire_and_run(self);
}
void cr_think_chase (edict_t *self)
{
qboolean bStuck;
if (!cr_update(self,true)) return;
if (cr_try_pickup_urgent(self)) return;
cr_find_enemy(self);
// move towards enemy
if (!self->enemy) {
if ( level.time > self->bot_info->time_chase ) {
self->think = cr_think;
return;
}
cr_set_move_target( self, self->monsterinfo.last_sighting );
}
else {
self->bot_info->time_chase = level.time + CHASE_TIME*(0.9f+qrandom()*0.2f);
cr_set_move_target( self, self->enemy->s.origin );
}
//FIXME: dodge while going towards the enemy
bStuck = !cr_moveto(self);
if (is_touched( self, self->bot_info->move_target )) {
if ( self->oldenemy && (10*qrandom())<self->bot_pers->skill &&
ITEM_INDEX(self->client->pers.weapon)>INDEX_SUPER_SHOTGUN ) {
VectorCopy( self->oldenemy->s.origin, self->monsterinfo.last_sighting );
self->monsterinfo.trail_time = level.time;
cr_try_to_find_route( self, self->monsterinfo.last_sighting );
}
else
self->think = cr_think;
}
else
if ( bStuck || cr_no_way( self, self->bot_info->move_target ) ) {
// can't get there, let's find detour!
cr_try_to_find_route( self, self->bot_info->move_target );
}
cr_fire_and_run(self);
}
void cr_switch_strafe( edict_t *self )
{
self->bot_info->time_last_strafe_switch = level.time + 0.1f;
self->bot_info->strafe_dir = -self->bot_info->strafe_dir;
}
void cr_think_attack( edict_t *self )
{
float d, strafe_a;
vec3_t move;
qboolean bSwitchStrafeDir;
if (!cr_update(self,true)) return;
if (!self->enemy && self->oldenemy) {
if (self->oldenemy->deadflag==DEAD_DEAD) {
if (self->oldenemy->client && !self->oldenemy->bot_info) self->bot_pers->adapt_count--;
if (qrandom()<0.3f) cr_message_kill( self, self->oldenemy );
if ( (int)bot_taunt->value && !cr_find_enemy(self) &&
!self->bot_info->b_crouch && !self->waterlevel )
self->think = qrandom()<0.3f ? cr_think_taunt : cr_think_flip;
self->oldenemy=NULL;
return;
}
}
if (cr_try_pickup_urgent(self)) return;
d = distance( self, self->enemy );
if (!self->enemy || d>self->bot_pers->attack_range*1.1f) {
cr_switch_strafe(self);
self->think = cr_think_chase;
return;
}
if (self->bot_info->strafe_dir>0) self->bot_info->strafe_dir=1;
else self->bot_info->strafe_dir=-1;
if ( 50*qrandom()<(self->bot_pers->attack_range - d) ) strafe_a = 98 + 8*qrandom();
else strafe_a = 82 - 8*qrandom();
// face the enemy
VectorSubtract ( self->enemy->s.origin, self->s.origin, move );
self->ideal_yaw = vectoyaw(move);
M_ChangeYaw(self);
self->ideal_yaw = anglemod( self->ideal_yaw + self->bot_info->strafe_dir*strafe_a );
bSwitchStrafeDir = (3+1.6f*(10-self->bot_pers->skill))*qrandom()<(level.time-self->bot_info->time_last_strafe_switch);
if (!cr_move_yaw( self, false, true )) bSwitchStrafeDir=true;
if (bSwitchStrafeDir) cr_switch_strafe(self);
cr_fire_and_run(self);
}
void cr_sight(edict_t *self, edict_t *)
{
self->think = cr_think_chase;
}
void cr_think_taunt ( edict_t *self )
{
if (!cr_update(self,false)) return;
self->bot_info->time_last_stuck=0;
self->bot_info->stuck_count=0;
cr_run_frames( self, FRAME_taunt01, FRAME_taunt17 );
if (self->s.frame==FRAME_taunt17) {
self->think = cr_think;
}
}
void cr_think_flip ( edict_t *self )
{
if (!cr_update(self,false)) return;
self->bot_info->time_last_stuck=0;
self->bot_info->stuck_count=0;
cr_run_frames( self, FRAME_flip01, FRAME_flip12 );
if (self->s.frame==FRAME_flip12) {
self->think = cr_think;
}
}
void cr_think_salute ( edict_t *self )
{
if (!cr_update(self,false)) return;
self->bot_info->time_last_stuck=0;
self->bot_info->stuck_count=0;
cr_run_frames( self, FRAME_salute01, FRAME_salute11 );
if ( level.time < self->bot_info->time_next_salute ||
self->s.frame==FRAME_salute11 ) {
self->bot_info->time_next_salute = level.time + 10.f+3*qrandom();
if (self->bot_info->bot_assignment==ASSN_GROUP)
self->think = cr_think_wait;
else
if (self->bot_info->bot_assignment==ASSN_GUARD)
self->think = cr_think_wait;
else
if (self->bot_info->bot_assignment==ASSN_PATROL)
self->think = cr_think_team_patrol;
else
self->think = cr_think;
}
}
void cr_think_pickup ( edict_t *self )
{
qboolean bStuck;
if (!cr_update(self,true)) return;
if (!self->bot_info->pickup_target) {
self->bot_info->last_node=NULL;
self->bot_info->next_node=NULL;
self->think = cr_think;
return;
}
if (cr_find_enemy(self)) {
cr_sight(self,self->enemy);
return;
}
bStuck = !cr_moveto(self);
if (is_touched( self, self->bot_info->move_target )) {
// gi.bprintf ( PRINT_MEDIUM, "got to waypoint!\n" );
if ( self->bot_info->pickup_target &&
is_touched( self, self->bot_info->pickup_target->s.origin ) ) {
self->bot_info->pickup_target=NULL;
}
else {
self->bot_info->last_node = self->bot_info->next_node;
self->bot_info->next_node = cr_get_next_path_node(self);
if (self->bot_info->next_node)
cr_set_move_target( self, self->bot_info->next_node->position );
else {
self->bot_info->pickup_target=NULL;
// gi.bprintf ( PRINT_MEDIUM, "path ended!\n" );
}
//cr_find_pickup_on_map(self);
}
}
else
if ( bStuck || cr_no_way( self, self->bot_info->move_target ) ) {
cr_remove_direct_route( self->bot_info->last_node, self->bot_info->next_node );
cr_skip_pickup_target(self);
// gi.bprintf ( PRINT_MEDIUM, "can't get there!\n" );
}
if ( self->bot_info->pickup_target &&
// special case: missing flag on CTF base
!( (self->client->pers.inventory[INDEX_FLAG2]>0 &&
strcmp( self->bot_info->pickup_target->classname, "item_flag_team1")==0) ||
(self->client->pers.inventory[INDEX_FLAG1]>0 &&
strcmp( self->bot_info->pickup_target->classname, "item_flag_team2")==0) ) ) {
if (!self->bot_info->pickup_target->inuse ||
(self->bot_info->pickup_target->svflags & SVF_NOCLIENT) ||
self->bot_info->pickup_target->solid==SOLID_NOT ) {
// gi.bprintf ( PRINT_MEDIUM, "somebody picked it up before me!\n" );
cr_forget_pickup_target(self);
}
}
cr_fire_and_run(self);
}
qboolean cr_return_to_base( edict_t *self )
{
edict_t *hit;
for ( hit=g_edicts+1; hit<&g_edicts[globals.num_edicts]; hit++) {
if (!hit->inuse || !hit->item) continue;
// if ((hit->svflags & SVF_NOCLIENT) || (hit->solid==SOLID_NOT)) continue;
if (hit->spawnflags & DROPPED_ITEM) continue;
if (hit->item->pickup==CTFPickup_Flag) {
if ( self->client->resp.ctf_team==CTF_TEAM1 &&
strcmp(hit->classname, "item_flag_team1")==0 ) break;
if ( self->client->resp.ctf_team==CTF_TEAM2 &&
strcmp(hit->classname, "item_flag_team2")==0 ) break;
}
}
if (!hit) {
// gi.dprintf("NO BASE FLAG FOUND!\n"); //!!!
return false;
}
return cr_force_pickup_target( self, hit );
}
qboolean cr_try_special_assignment( edict_t *self )
{
edict_t *hit, *enemy_flag, *team_flag;
int flag;
if (!ctf->value) return false;
if ( self->bot_info->time_next_special_assignment > level.time ) return false;
self->bot_info->time_next_special_assignment = level.time + 8.f + 2*qrandom();
// special case: we have enemy flag, let's go return it
if ((self->client->pers.inventory[INDEX_FLAG1]>0) ||
(self->client->pers.inventory[INDEX_FLAG2]>0)) {
if (cr_return_to_base(self)) return true;
}
enemy_flag=NULL;
team_flag=NULL;
for ( hit=g_edicts+1; hit<&g_edicts[globals.num_edicts]; hit++) {
if (!hit->inuse) continue;
if ((hit->svflags & SVF_NOCLIENT) || (hit->solid==SOLID_NOT)) continue;
if (hit->item && hit->item->pickup==CTFPickup_Flag) {
if (strcmp(hit->classname, "item_flag_team1")==0) flag=CTF_TEAM1;
else if (strcmp(hit->classname, "item_flag_team2")==0) flag=CTF_TEAM2;
else continue;
if ( flag==self->client->resp.ctf_team ) team_flag=hit;
else enemy_flag=hit;
}
else
if ( hit->client &&
(hit->client->pers.inventory[INDEX_FLAG1] || hit->client->pers.inventory[INDEX_FLAG2]) ) {
if ( hit->client->resp.ctf_team!=self->client->resp.ctf_team ) team_flag=hit;
else enemy_flag=hit;
}
if (enemy_flag && team_flag) break;
}
if (team_flag) {
// check if team_flag at base and thus alright
if (!team_flag->client && !(team_flag->spawnflags & DROPPED_ITEM)) {
// if we have enemy flag, let's go score a capture!
if (self->client->pers.inventory[INDEX_FLAG1] || self->client->pers.inventory[INDEX_FLAG2]) {
if (team_flag && cr_force_pickup_target( self, team_flag )) {
// gi.dprintf ( "%s going to finish the capture!\n", self->client->pers.netname );
return true;
}
// else {
// gi.dprintf ( "%s doesn't know going to get enemy flag to the base!\n", self->client->pers.netname );
// }
}
}
else {
// rescue our team_flag!
if (team_flag->client) {
if ( (rand()%1) && cr_force_attack_enemy( self, team_flag )) {
// gi.dprintf ( "%s going to kill flag carrier!\n", self->client->pers.netname );
return true;
}
}
else {
if (cr_force_pickup_target( self, team_flag )) {
// gi.dprintf ( "%s going to return the flag!\n", self->client->pers.netname );
return true;
}
}
}
}
if (enemy_flag && !enemy_flag->client) {
if (cr_force_pickup_target( self, enemy_flag )) {
// gi.dprintf ( "%s going to capture the flag!\n", self->client->pers.netname );
return true;
}
}
return false;
}
void cr_think ( edict_t *self )
{
if (!cr_update(self,true)) return;
if (level.time > self->bot_info->time_next_assignment_check) {
self->bot_info->time_next_assignment_check = level.time+3.f;
if (self->bot_info->bot_assignment==ASSN_GROUP) {
self->think = cr_think_team_group;
return;
}
else
if (self->bot_info->bot_assignment==ASSN_GUARD) {
self->think = cr_think_team_guard;
return;
}
else
if (self->bot_info->bot_assignment!=ASSN_NONE) {
cr_dismiss(self);
}
}
if (cr_find_enemy(self)) {
cr_sight(self,self->enemy);
return;
}
if (cr_try_special_assignment(self)) return;
cr_find_pickup_target(self);
if (!self->bot_info->pickup_target &&
self->bot_info->bot_assignment==ASSN_NONE) {
// let's go for pickups we saw once
self->bot_info->pickup_target_score = 1e32;
cr_find_pickup_on_map(self);
}
if (!self->bot_info->pickup_target) {
if ( level.time > self->bot_info->time_next_roam_dir_change ||
!cr_move_yaw( self, true, qrandom()>0.02f ) ) {
float dy;
qboolean bReverse;
self->ideal_yaw = anglemod( self->ideal_yaw + 10*(0.5f-qrandom()) );
bReverse = qrandom()>0.5f;
for ( dy=60; ; dy+=30*(0.8f+0.2f*qrandom()) ) {
if (bReverse) dy = -dy;
if (cr_try_move(self, dy, true )) break;
if (cr_try_move(self, -dy, true )) break;
if (bReverse) dy = -dy;
if (dy>120) {
self->ideal_yaw = 360*qrandom();
break;
}
}
self->bot_info->time_next_roam_dir_change = level.time + TIME_ROAM_DIR_CHANGE;
}
}
else
if ( !cr_moveto(self) ||
is_touched2d( self, self->bot_info->move_target ) ) {
cr_skip_pickup_target(self);
cr_pick_random_destination(self);
}
if (self->bot_info->pickup_target) {
if ( !self->bot_info->pickup_target->inuse ||
(self->bot_info->pickup_target->svflags & SVF_NOCLIENT) ||
self->bot_info->pickup_target->solid==SOLID_NOT )
cr_forget_pickup_target(self);
}
if (!self->bot_info->b_crouch) cr_animate_frames( self, FRAME_run1, FRAME_run6 );
else cr_animate_frames( self, FRAME_crwalk1, FRAME_crwalk6 );
}
void cr_run_frames( edict_t *self, int start, int end )
{
if ( self->s.frame<start ||
self->s.frame>=end )
self->s.frame = start;
else
self->s.frame++;
}
void cr_animate_frames( edict_t *self, int start, int end )
{
if (self->client->anim_priority==ANIM_DEATH) {
self->s.frame = self->client->anim_end;
self->think = cr_death;
return;
}
if (!self->groundentity && !self->waterlevel) {
if (self->s.frame<FRAME_jump1 || self->s.frame>FRAME_jump6) self->s.frame = FRAME_jump1;
if (self->s.frame!=FRAME_jump6) self->s.frame++;
return;
}
cr_run_frames(self,start,end);
if (!self->waterlevel) {
switch (self->s.frame) {
case FRAME_crwalk3:
gi.sound ( self, CHAN_BODY, sound_footstep[(int)(qrandom()*4)], 0.4f, ATTN_NORM, 0 );
break;
case FRAME_run2:
case FRAME_run5:
gi.sound ( self, CHAN_BODY, sound_footstep[(int)(qrandom()*4)], 0.6f, ATTN_NORM, 0 );
break;
}
}
}
void cr_routes_load(void);
void cr_init_node_net(void)
{
int count=0;
edict_t *hit, *other;
path_node_t *node, *node2;
vec3_t pos;
for ( hit=g_edicts+1; hit<&g_edicts[globals.num_edicts]; hit++) {
if (!hit->inuse) continue;
if (hit->touch==teleporter_touch) {
other = G_Find( NULL, FOFS(targetname), hit->target );
if (!other) continue;
node = cr_insert_node( hit->s.origin, NULL, 0 );
node2 = cr_insert_node( other->s.origin, NULL, NF_TELEPORT );
cr_add_direct_route( node, node2, false );
continue;
}
if (hit->touch == Touch_Plat_Center) {
pos[0] = (hit->absmin[0]+hit->absmax[0])/2;
pos[1] = (hit->absmin[1]+hit->absmax[1])/2;
pos[2] = hit->absmin[2] + STEPSIZE;
node = cr_insert_node( pos, NULL, NF_ELEVATOR );
pos[2] = hit->absmax[2] + STEPSIZE;
node2 = cr_insert_node( pos, NULL, NF_ELEVATOR );
cr_add_direct_route_uni( node, node2 );
continue;
}
if (hit->use == door_use) {
pos[0] = (hit->absmax[0]+hit->absmin[0])/2;
pos[1] = (hit->absmax[1]+hit->absmin[1])/2;
pos[2] = (hit->absmax[2]+hit->absmin[2]*3)/4;
continue;
}
if (!hit->item || !hit->item->pickup) continue;
// insert new node
node = cr_insert_node( hit->s.origin, NULL, 0 );
node->item = hit;
count++;
}
cr_routes_load();
}
void cr_check_pending_bots(void)
{
int i, pn;
edict_t* new_bot;
for ( i=0; i<global_bots_count; i++ ) {
if (!global_bots[i].b_inuse || global_bots[i].playernum>=0) continue;
pn = cr_find_unused_client_slot();
if (pn<0) break;
global_bots[i].playernum = pn;
new_bot = cr_respawn_bot( global_bots+i, NULL );
if (new_bot) {
new_bot->classname = "new_bot";
cr_update_userinfo(new_bot->bot_pers);
new_bot->client->resp.ctf_team = CTF_NOTEAM;
}
}
}
void
cr_debug_draw(edict_t* p)
{
edict_t *hit;
edict_t *line, *d;
//!!! remove path indication
for(line = global_path_route; line != nil; line = d){
d = line->teamchain;
G_FreeEdict(line);
}
for(hit=g_edicts+1; hit<&g_edicts[globals.num_edicts]; hit++){
if(!hit->inuse)
continue;
if(hit->svflags & SVF_NOCLIENT || hit->solid == SOLID_NOT)
continue;
if(hit->item && hit->item->pickup == CTFPickup_Flag){
p->bot_info = Z_TagMalloc(sizeof *p->bot_info, TAG_GAME);
memset(p->bot_info, 0, sizeof *p->bot_info);
p->bot_pers = Z_TagMalloc(sizeof *p->bot_pers, TAG_GAME);
memset(p->bot_pers, 0, sizeof *p->bot_pers);
cr_find_route(p, hit->s.origin, true);
Z_Free(p->bot_info);
p->bot_info = nil;
Z_Free(p->bot_pers);
p->bot_pers = nil;
}
}
}
void cr_node_keeper_think ( edict_t* self )
{
int count;
edict_t *client;
path_node_t *node;
path_node_t *next_node;
self->nextthink = level.time + 0.3f;
if (!cr_node_head) cr_init_node_net();
cr_check_pending_bots();
// rcount=0;
node_count=0;
count = qrandom()*11;
for ( node=cr_node_head; node!=NULL; node=next_node ) {
node_count++;
next_node = node->next;
if (!next_node) break;
if (count++<11) continue;
if ( !next_node->link_from[2/*MAX_NODE_LINKS/2*/] &&
(level.time - next_node->time)>10) {
cr_add_links_radius(next_node);
next_node->time = level.time;
count=0;
}
}
// if ((node_count%10)==0) gi.bprintf ( PRINT_MEDIUM, "nodes: %d removed: %d\n", node_count, rcount );
if ( node_count<MAX_NODE_COUNT*(0.7f+0.5f*qrandom()) ) {
trace_t trace;
vec3_t dir, start, end;
int flags;
for ( client=g_edicts+1; client<=(g_edicts+game.maxclients); client++) {
if (!client->inuse || !client->client || cistrcmp(client->classname, "player")!=0 ) continue;
flags = 0;
if (client->watertype & (CONTENTS_LAVA|CONTENTS_SLIME)) continue;
if (!client->groundentity && client->waterlevel==0) {
AngleVectors( client->client->v_angle, dir, NULL, NULL );
VectorCopy( client->s.origin, start );
VectorMA ( start, 60, dir, end );
trace = gi.trace ( start, client->mins, client->maxs, end, client, CONTENTS_LADDER | CONTENTS_SOLID );
if ( !(trace.contents & CONTENTS_LADDER) ) continue;
flags |= NF_LADDER;
}
cr_register_position( client, flags );
// cr_debug_draw(client);
}
}
}
void cr_init_node_keeper(void)
{
if (cr_node_keeper) return;
cr_node_keeper = G_Spawn();
cr_node_keeper->classname = "node keeper";
cr_node_keeper->svflags |= SVF_NOCLIENT;
cr_node_keeper->solid = SOLID_NOT;
cr_node_keeper->think = cr_node_keeper_think;
cr_node_keeper->nextthink = level.time + 1.5f;
gi.linkentity(cr_node_keeper);
}
void cr_join_ctf( edict_t *self )
{
int team;
if (self->bot_pers->team_no>0) {
if (self->bot_pers->team_no&1) team = CTF_TEAM1;
else team = CTF_TEAM2;
if (self->client->resp.ctf_team==team) return;
}
else
if ( self->client->resp.ctf_team==CTF_TEAM1 ||
self->client->resp.ctf_team==CTF_TEAM2 ) {
team = self->client->resp.ctf_team;
}
else {
edict_t *player;
int i, team1count = 0, team2count = 0;
for (i = 1; i <= maxclients->value; i++) {
player = &g_edicts[i];
if (!player->inuse || player==self)
continue;
switch (player->client->resp.ctf_team) {
case CTF_TEAM1:
team1count++;
break;
case CTF_TEAM2:
team2count++;
break;
}
}
team = CTF_TEAM1;
if (team2count<team1count ||
(team2count==team1count && (rand()&1))) team = CTF_TEAM2;
}
CTFJoinTeam(self,team);
}
float last_bot_respawn_time=0;
void cr_respawn( edict_t *self )
{
vec3_t spawn_origin, spawn_angles;
int i;
if (level.time<last_bot_respawn_time) last_bot_respawn_time=level.time;
if (!cr_node_head || level.time<(last_bot_respawn_time+0.6f)) {
self->nextthink = level.time + FRAMETIME;
return;
}
last_bot_respawn_time=level.time;
if ( strcmp(self->classname,"new_bot")==0 ) {
if (self->bot_pers->team_no>0)
gi.bprintf ( PRINT_MEDIUM, "%s has entered the game, team: %d\n",
self->client->pers.netname, self->bot_pers->team_no );
else
gi.bprintf ( PRINT_MEDIUM, "%s has entered the game\n", self->client->pers.netname );
}
if (ctf->value) cr_join_ctf(self);
// spawn the bot on a spawn spot
SelectSpawnPoint( self, spawn_origin, spawn_angles );
spawn_origin[2] += 3.f; // make sure off ground
self->ideal_yaw = spawn_angles[YAW];
VectorCopy ( spawn_origin, self->s.origin );
VectorCopy ( self->s.origin, self->s.old_origin );
// clear playerstate values
memset (&self->client->ps, 0, sizeof(self->client->ps));
self->client->ps.pmove.origin[0] = spawn_origin[0]*8;
self->client->ps.pmove.origin[1] = spawn_origin[1]*8;
self->client->ps.pmove.origin[2] = spawn_origin[2]*8;
// set the delta angle
for (i=0; i<3; i++)
self->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(spawn_angles[i] - self->client->resp.cmd_angles[i]);
self->s.angles[PITCH] = 0;
self->s.angles[YAW] = spawn_angles[YAW];
self->s.angles[ROLL] = 0;
VectorCopy ( self->s.angles, self->client->ps.viewangles);
VectorCopy ( self->s.angles, self->client->v_angle);
// clear entity values
self->classname = "bot";
self->groundentity = NULL;
self->takedamage = DAMAGE_AIM;
self->deadflag = DEAD_NO;
self->movetype = MOVETYPE_STEP; //MOVETYPE_NONE;
self->mass = 200;
self->solid = SOLID_BBOX;
self->clipmask = MASK_PLAYERSOLID;
self->max_health = 100;
self->gib_health = -40;
self->waterlevel = 0;
self->watertype = 0;
self->health = 100;
self->max_health = 100;
self->viewheight = 22;
self->inuse = true;
self->yaw_speed = self->bot_pers->rot_speed;
self->air_finished = level.time + 12;
self->flags &= ~FL_NO_KNOCKBACK;
self->svflags &= ~SVF_DEADMONSTER;
self->bot_info->strafe_dir=1;
self->bot_info->pickup_target=NULL;
self->bot_info->pickup_target_score = 1e32;
self->bot_info->last_node=NULL;
self->bot_info->next_node=NULL;
self->bot_info->target_node=NULL;
self->bot_info->time_last_stuck=0;
self->bot_info->time_next_enemy=0;
self->bot_info->time_next_pickup=0;
self->bot_info->time_next_shot=0;
self->bot_info->time_last_strafe_switch=0;
self->bot_info->old_think = NULL;
self->bot_info->stuck_count = 0;
self->bot_info->move_block_count = 0;
self->bot_info->path_nodes = 0;
self->bot_info->time_next_crouch = level.time + TIME_CROUCH;
self->bot_info->time_stuck_check = level.time + TIME_CHECK_STUCK;
VectorCopy ( self->s.origin, self->bot_info->old_origin );
self->bot_info->time_next_fight_message = level.time + FIGHT_MSG_DELAY;
self->bot_info->time_last_message=0;
self->model = self->bot_pers->model;
self->s.modelindex = 255; //gi.modelindex(self->model);//255;
self->s.skinnum = self->bot_pers->playernum; //self->bot_pers->skin_no; //game.maxclients-1;
self->s.frame = 0;
self->s.modelindex2 = 255;
self->s.modelindex3 = 0; // remove linked ctf flag
self->client->newweapon = self->client->pers.weapon;
cr_change_weapon (self);
self->client->anim_priority = ANIM_BASIC;
self->client->anim_end = 0;
self->pain = cr_pain;
self->die = cr_die;
self->think = cr_think;
self->nextthink = level.time + FRAMETIME;
self->bot_info->postthink = cr_post_think;
self->touch = NULL; //cr_touch;
self->monsterinfo.stand = NULL;
self->monsterinfo.walk = NULL;
self->monsterinfo.run = NULL;
self->monsterinfo.attack = NULL;
self->monsterinfo.melee = NULL;
self->monsterinfo.sight = cr_sight;
self->monsterinfo.search = NULL;
self->monsterinfo.checkattack = NULL;
self->bot_info->b_crouch = false;
VectorSet( self->mins, -16, -16, -24);
VectorSet( self->maxs, 16, 16, 32);
VectorClear( self->velocity );
self->s.event = EV_PLAYER_TELEPORT;
KillBox (self);
gi.linkentity(self);
self->client->pers.weapon = FindItem ("blaster");
self->client->newweapon = self->client->pers.weapon;
cr_pick_random_destination(self);
}
void cr_show_stats( edict_t *self )
{
edict_t *bot;
gi.cprintf( self, PRINT_MEDIUM, "-------------------------------------------------------\n");
for ( bot=g_edicts+1; bot<=(g_edicts+game.maxclients); bot++) {
if (!bot->inuse || !bot->client || !bot->bot_info) continue;
gi.cprintf( self, PRINT_MEDIUM, "%s -- skill:%d adp:%s team:%d frags:%d\n",
bot->client->pers.netname,
bot->bot_pers->skill,
bot->bot_pers->b_adapting ? "yes" : "no ",
bot->bot_pers->team_no,
// bot->bot_pers->skin,
// bot->bot_pers->model,
bot->client->resp.score );
}
gi.cprintf( self, PRINT_MEDIUM, "-------------------------------------------------------\n" );
// gi.cprintf( self, PRINT_MEDIUM, "total nodes in waypoints network = %d\n\n", node_count );
}
void cr_show_team_stats( edict_t *self )
{
edict_t *bot;
int n, i, j;
qboolean b_show;
char total[128];
gi.cprintf( self, PRINT_MEDIUM, "--------- team scores ----------\n");
for ( i=1; i<20; i++ ) {
n=0;
b_show = false;
for ( bot=g_edicts+1; bot<=(g_edicts+game.maxclients); bot++) {
if ( !bot->inuse || !bot->client ||
bot->client->pers.team_no!=i ) continue;
gi.cprintf( self, PRINT_MEDIUM, "%12s = %d\n",
bot->client->pers.netname,
bot->client->resp.score );
n += bot->client->resp.score;
b_show = true;
}
if (b_show) {
sprintf ( total, " === %d", n );
for ( j=0; j<strlen(total); j++ ) total[j] |= 0x80;
gi.cprintf( self, PRINT_MEDIUM, "%s\n\n", total );
}
}
}
/*
void cr_scoreboard_message( char* string, edict_t *self )
{
#define MAX_SB_BOT 64
#define MAX_LEN (1400-1)
gclient_t* bot[MAX_SB_BOT], *cl;
int i, j, sl, el, total;
edict_t *client;
char entry[1024];
total=0;
for ( client=g_edicts; client<&g_edicts[globals.num_edicts]; client++ ) {
if (!client->client) continue;
if (!client->classname) continue;
if ( cistrcmp (client->classname, "bot") &&
cistrcmp (client->classname, "player") ) continue;
bot[total] = client->client;
total++;
if (total>=MAX_SB_BOT) break;
}
for ( i=0; i<total; i++ ) {
sl = i;
el = bot[i]->resp.score;
for ( j=i+1; j<total; j++ ) {
if ( el<bot[j]->resp.score ) {
sl = j;
el = bot[j]->resp.score;
}
}
if (sl!=i) {
cl = bot[i];
bot[i] = bot[sl];
bot[sl] = cl;
}
}
if (total>16) total=16;
sl = strlen(string);
for ( i=0; i<total; i++ ) {
if (self->client==bot[i]) {
char str1[80], str2[8], *pch;
strncpy(str1,bot[i]->pers.netname,sizeof(str1));
for( pch=str1; *pch; pch++) *pch|=0x80;
sprintf(str2, "%d", bot[i]->resp.score);
for( pch=str2; *pch; pch++) *pch|=0x80;
Com_sprintf ( entry, sizeof(entry),
"xv 170 yv %i string2 \"%s\" "
"xv 290 yv %i string2 \"%s\" ",
5+i*9, str1,
5+i*9, str2 );
}
else {
Com_sprintf ( entry, sizeof(entry),
"xv 170 yv %i string2 \"%s\" "
"xv 290 yv %i string2 \"%i\" ",
5+i*9, bot[i]->pers.netname,
5+i*9, bot[i]->resp.score );
}
el = strlen(entry);
if ( (sl+el)>=MAX_LEN ) break;
strcpy( string+sl, entry );
sl += el;
}
}
*/
void cr_get_full_pathname( char* fullname, char* name )
{
char* pc;
strcpy( fullname, base_path->string );
pc = fullname + strlen(fullname)-1;
while (pc>=fullname && *pc==' ') *pc--='\0';
if (*pc) {
if (*pc!='/') *++pc='/';
*++pc='\0';
}
strcpy( pc, game_path->string );
pc+=strlen(game_path->string);
strcpy( pc, "/" );
pc++;
strcpy( pc, name );
}
qboolean
cr_load_message_file(char** msg_array, int* msg_count, char* filename)
{
FILE *f;
char msg[MAX_MSG_LEN];
int n;
/* already freed ffs */
//for(i=0; i<*msg_count; i++)
// Z_Free(msg_array[i]);
*msg_count=0;
cr_get_full_pathname( msg, filename );
f = fopen( msg, "rt" );
if (!f) {
gi.dprintf( "ERROR loading %s\n", msg );
return false;
}
while (1) {
if (!fgets( msg, sizeof(msg), f )) break;
n = strlen(msg);
while(n>0) {
if (msg[n-1]!='\r' && msg[n-1]!='\n') break;
n--;
msg[n]='\0';
}
if (n<=0) continue;
msg_array[*msg_count] = gi.TagMalloc( n+2, TAG_GAME );
strcpy( msg_array[*msg_count], msg );
(*msg_count)++;
if (*msg_count>=MAX_MSG) break;
}
fclose (f);
return true;
}
void cr_init(void)
{
//path_node_t *node, *next_node;
gi.dprintf ( "-_-_-_-_-_- CRBot init -_-_-_-_-_-\n" );
cr_init_variables();
cr_load_message_file( msg_pain, &msg_pain_count, "pain.txt" );
cr_load_message_file( msg_kill, &msg_kill_count, "kill.txt" );
cr_load_message_file( msg_death, &msg_death_count, "death.txt" );
cr_load_message_file( msg_fight, &msg_fight_count, "fight.txt" );
cr_load_maplist();
sound_footstep[0] = gi.soundindex ("player/step1.wav");
sound_footstep[1] = gi.soundindex ("player/step2.wav");
sound_footstep[2] = gi.soundindex ("player/step3.wav");
sound_footstep[3] = gi.soundindex ("player/step4.wav");
// free old node-net
/* already freed in GameShutdown, christ
for ( node=cr_node_head; node!=NULL; node=next_node ) {
next_node=node->next;
gi.TagFree(node);
}
*/
cr_node_head=NULL;
if (cr_node_keeper) {
G_FreeEdict(cr_node_keeper);
cr_node_keeper=NULL;
}
cr_init_node_keeper();
global_bot_number = 1;
}
void cr_remove_bot( edict_t *bot )
{
// send effect
gi.WriteByte( svc_muzzleflash );
gi.WriteShort( bot-g_edicts );
gi.WriteByte( MZ_LOGOUT );
gi.multicast( bot->s.origin, MULTICAST_PVS );
gi.bprintf ( PRINT_HIGH, "%s disconnected\n", bot->client->pers.netname );
cr_free_bot( bot );
}
void cr_kill_bot( char* bot_name )
{
edict_t *hit, *bot;
bot = NULL;
for ( hit=g_edicts+1; hit<&g_edicts[globals.num_edicts]; hit++) {
if (!hit->inuse || !hit->client) continue;
if (cistrcmp (hit->classname, "bot")) continue;
if (cistrcmp ( hit->client->pers.netname, bot_name )) continue;
bot = hit;
break;
}
if (bot) {
for ( hit=g_edicts+1; hit<&g_edicts[globals.num_edicts]; hit++) {
if (!hit->inuse || !hit->client) continue;
if (cistrcmp (hit->classname, "bot")) continue;
if (hit->enemy==bot) {
hit->enemy=NULL;
hit->oldenemy=NULL;
}
}
cr_unregister_bot(bot->bot_pers);
cr_remove_bot( bot );
}
}
void cr_client_connect(edict_t *ent)
{
int i, playernum, pn;
edict_t* bot;
qboolean old_inuse;
playernum = ent-g_edicts-1;
old_inuse = ent->inuse;
ent->inuse = true;
for ( bot=g_edicts+1; bot<&g_edicts[globals.num_edicts]; bot++) {
if (!bot->inuse || !bot->bot_pers) continue;
if (bot->bot_pers->playernum==playernum) {
// find new playernum
/*
pn = cr_find_unused_client_slot();
if (pn<0) {
bot->client->inuse = false;
cr_remove_bot( bot );
}
else {
gclient_t* old_client = bot->client;
bot->client = &game.clients[pn];
*bot->client = *old_client;
old_client->inuse = false;
bot->client->inuse = true;
bot->bot_pers->playernum = pn;
cr_update_userinfo(bot->bot_pers);
}
*/
pn = -1;
bot->client->inuse = false;
cr_remove_bot( bot );
// correct playernum in bot's list
for ( i=0; i<global_bots_count; i++ ) {
if (!global_bots[i].b_inuse) continue;
if (global_bots[i].playernum==playernum) {
global_bots[i].playernum=pn;
}
}
break;
}
}
ent->inuse = old_inuse;
}
void
cr_init_globals(void)
{
int size;
global_bots_count = game.maxclients;
/* already freed by ShutownGame */
//if(global_bots != nil)
// Z_Free(global_bots);
size = global_bots_count * sizeof *global_bots;
global_bots = Z_TagMalloc(size, TAG_GAME);
memset(global_bots, 0, size);
}
void cr_exit_level(void)
{
int i;
edict_t* bot;
// mark all bots to respawn on new level
for ( i=0; i<global_bots_count; i++ ) {
if (!global_bots[i].b_inuse) continue;
game.clients[global_bots[i].playernum].inuse = false;
global_bots[i].playernum = -1;
}
for ( bot=g_edicts+1; bot<=(g_edicts+game.maxclients); bot++) {
if (!bot->inuse || !bot->client || !bot->bot_info) continue;
cr_free_bot( bot );
}
}
void cr_load_maplist(void)
{
FILE *f;
char line[256];
int n;
map_cycle_count=0;
cr_get_full_pathname( line, "maplist.txt" );
f = fopen( line, "rt" );
if (!f) {
gi.dprintf( "ERROR loading maplist.txt\n" );
return;
}
while (map_cycle_count<(MAX_MAP-1)) {
if (!fgets( line, sizeof(line), f )) break;
if (!*line || *line=='#' || *line==';' || *line=='/' ||
*line=='\\' || *line==' ' || *line=='\r' || *line=='\n' ) continue;
n = strlen(line);
while(n>0) {
if (line[n-1]!='\r' && line[n-1]!='\n') break;
n--;
line[n]='\0';
}
if (n<=0) continue;
strncpy( (char*)(map_list + map_cycle_count++), line, 32 );
}
fclose (f);
next_map %= map_cycle_count;
}
char next_map_name[64]="\0";
qboolean cr_map_cycle(void)
{
int new_map;
if (!(int)map_cycle->value || map_cycle_count<2) return false;
switch ((int)map_cycle->value) {
case 2:
do {
new_map = rand() % map_cycle_count;
} while( new_map==next_map );
next_map = new_map;
break;
default:
next_map = (next_map+1) % map_cycle_count;
}
next_map %= map_cycle_count;
strcpy( next_map_name, (char*)(map_list + next_map) );
return true;
}
void cr_check_ctf_routes( edict_t *self )
{
edict_t *hit;
edict_t *flag1=NULL, *flag2=NULL;
vec3_t old_origin;
for ( hit=g_edicts+1; hit<&g_edicts[globals.num_edicts]; hit++) {
if (!hit->inuse) continue;
if (hit->spawnflags & DROPPED_ITEM) continue;
// if ((hit->svflags & SVF_NOCLIENT) || (hit->solid==SOLID_NOT)) continue;
if (hit->item && hit->item->pickup==CTFPickup_Flag) {
if (!flag1) flag1=hit;
else {
flag2=hit;
break;
}
}
}
if (!flag1) {
gi.dprintf("Warning: No flags found on this map!\n");
return;
}
if (!flag2) {
gi.dprintf("Warning: Only one flag found on this map!\n");
return;
}
VectorCopy( self->s.origin, old_origin );
VectorCopy( flag1->s.origin, self->s.origin );
self->bot_info = gi.TagMalloc ( sizeof(bot_info_t), TAG_GAME );
memset( self->bot_info, 0, sizeof(bot_info_t) );
self->bot_pers = gi.TagMalloc ( sizeof(bot_info_pers_t), TAG_GAME );
memset(self->bot_pers,0,sizeof(bot_info_pers_t));
self->bot_pers->skill = 100;
if (!cr_find_route( self, flag2->s.origin, true )) {
gi.dprintf("Warning: No route from one flag to another!\n");
}
gi.TagFree(self->bot_info);
self->bot_info=NULL;
gi.TagFree(self->bot_pers);
self->bot_pers=NULL;
VectorCopy( old_origin, self->s.origin );
}
char file_signature[] = "CRBOT.ROUTE.MAP.01.";
void cr_routes_save( edict_t *self )
{
path_node_t *node;
FILE *f;
char filename[256], rt_name[80];
int reserved[20], total, saved;
if (ctf->value) {
cr_check_ctf_routes(self);
}
cr_get_full_pathname( filename, "nodemaps" );
mkdir(filename);
sprintf( rt_name, "nodemaps%s%s.crn", "/", level.mapname );
cr_get_full_pathname( filename, rt_name );
f = fopen( filename, "wb" );
if (!f) {
gi.dprintf( "ERROR creating file: %s\n", rt_name );
return;
}
if (!fwrite(file_signature,sizeof(file_signature),1,f)) {
gi.dprintf( "ERROR writing file: %s\n", rt_name );
fclose(f);
return;
}
memset(filename,0,256);
if (!fwrite(filename,256,1,f)) {
gi.dprintf( "ERROR writing file: %s\n", rt_name );
fclose(f);
return;
}
// save nodes
memset(reserved,0,sizeof(reserved));
total=saved=0;
for ( node=cr_node_head; node!=NULL; node=node->next ) {
total++;
if (node->item) continue;
saved++;
if (!fwrite(reserved,sizeof(reserved),1,f)) {
gi.dprintf( "ERROR writing file: %s\n", rt_name );
fclose(f);
return;
}
if (!fwrite(node->position,sizeof(node->position),1,f)) {
gi.dprintf( "ERROR writing file: %s\n", rt_name );
fclose(f);
return;
}
if (!fwrite(&node->flags,sizeof(node->flags),1,f)) {
gi.dprintf( "ERROR friting file: %s\n", rt_name );
fclose(f);
return;
}
}
reserved[0]=1;
if (!fwrite(reserved,sizeof(reserved),1,f)) {
gi.dprintf( "ERROR writing file: %s\n", rt_name );
fclose(f);
return;
}
fclose(f);
gi.dprintf( "total nodes: %d, saved: %d\n", total, saved );
}
void cr_routes_load(void)
{
vec3_t pos;
int flags;
FILE *f;
char filename[256], rt_name[80];
int reserved[20], total;
sprintf( rt_name, "nodemaps%s%s.crn", "/", level.mapname );
cr_get_full_pathname( filename, rt_name );
f = fopen( filename, "rb" );
if (!f) return;
memset(filename,0,256);
if (!fread(filename,sizeof(file_signature),1,f)) {
gi.dprintf( "ERROR reading file: %s\n", rt_name );
fclose(f);
return;
}
if (strcmp(filename,file_signature)) return;
if (!fread(filename,256,1,f)) {
gi.dprintf( "ERROR reading file: %s\n", rt_name );
fclose(f);
return;
}
//read nodes
total=0;
while(true) {
total++;
if (!fread(reserved,sizeof(reserved),1,f)) {
gi.dprintf( "ERROR reading file: %s\n", rt_name );
fclose(f);
return;
}
if (reserved[0]) break;
if (!fread(pos,sizeof(pos),1,f)) {
gi.dprintf( "ERROR reading file: %s\n", rt_name );
fclose(f);
return;
}
if (!fread(&flags,sizeof(flags),1,f)) {
gi.dprintf( "ERROR reading file: %s\n", rt_name );
fclose(f);
return;
}
cr_insert_node( pos, NULL, flags );
}
fclose(f);
gi.dprintf( "total nodes read from nodemap file: %d\n", total );
}