ref: 1e4ae34f57695d9966ca8243c09f81dbb2e35462
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 ); }