2001-01-19 03:30:16 +00:00
/*
THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
SOFTWARE CORPORATION ( " PARALLAX " ) . PARALLAX , IN DISTRIBUTING THE CODE TO
END - USERS , AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN , GRANTS A
ROYALTY - FREE , PERPETUAL LICENSE TO SUCH END - USERS FOR USE BY SUCH END - USERS
IN USING , DISPLAYING , AND CREATING DERIVATIVE WORKS THEREOF , SO LONG AS
SUCH USE , DISPLAY OR CREATION IS FOR NON - COMMERCIAL , ROYALTY OR REVENUE
FREE PURPOSES . IN NO EVENT SHALL THE END - USER USE THE COMPUTER CODE
CONTAINED HEREIN FOR REVENUE - BEARING PURPOSES . THE END - USER UNDERSTANDS
AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE .
COPYRIGHT 1993 - 1999 PARALLAX SOFTWARE CORPORATION . ALL RIGHTS RESERVED .
*/
2001-01-31 15:18:05 +00:00
# ifdef HAVE_CONFIG_H
# include <conf.h>
# endif
2001-01-19 03:30:16 +00:00
# ifdef RCS
2001-10-25 02:15:57 +00:00
static char rcsid [ ] = " $Id: ai2.c,v 1.3 2001-10-25 02:15:55 bradleyb Exp $ " ;
2001-01-19 03:30:16 +00:00
# endif
# include <stdio.h>
# include <stdlib.h>
# include <time.h>
# include "inferno.h"
# include "game.h"
# include "mono.h"
# include "3d.h"
# include "u_mem.h"
# include "object.h"
# include "render.h"
# include "error.h"
# include "ai.h"
# include "laser.h"
# include "fvi.h"
# include "polyobj.h"
# include "bm.h"
# include "weapon.h"
# include "physics.h"
# include "collide.h"
# include "player.h"
# include "wall.h"
# include "vclip.h"
# include "digi.h"
# include "fireball.h"
# include "morph.h"
# include "effects.h"
# include "timer.h"
# include "sounds.h"
# include "cntrlcen.h"
# include "multibot.h"
2001-10-25 02:15:57 +00:00
# ifdef NETWORK
2001-01-19 03:30:16 +00:00
# include "multi.h"
# include "network.h"
2001-10-25 02:15:57 +00:00
# endif
2001-01-19 03:30:16 +00:00
# include "gameseq.h"
# include "key.h"
# include "powerup.h"
# include "gauges.h"
# include "text.h"
# ifdef EDITOR
2001-10-25 02:15:57 +00:00
# include "editor/editor.h"
# include "editor/kdefs.h"
2001-01-19 03:30:16 +00:00
# endif
# ifndef NDEBUG
# include "string.h"
# include <time.h>
# endif
void teleport_boss ( object * objp ) ;
int boss_fits_in_seg ( object * boss_objp , int segnum ) ;
int Flinch_scale = 4 ;
int Attack_scale = 24 ;
byte Mike_to_matt_xlate [ ] = { AS_REST , AS_REST , AS_ALERT , AS_ALERT , AS_FLINCH , AS_FIRE , AS_RECOIL , AS_REST } ;
// Amount of time since the current robot was last processed for things such as movement.
// It is not valid to use FrameTime because robots do not get moved every frame.
int Num_boss_teleport_segs ;
short Boss_teleport_segs [ MAX_BOSS_TELEPORT_SEGS ] ;
int Num_boss_gate_segs ;
short Boss_gate_segs [ MAX_BOSS_TELEPORT_SEGS ] ;
// ---------------------------------------------------------
// On entry, N_robot_types had darn sure better be set.
// Mallocs N_robot_types robot_info structs into global Robot_info.
void init_ai_system ( void )
{
#if 0
int i ;
mprintf ( ( 0 , " Trying to malloc %i bytes for Robot_info. \n " , N_robot_types * sizeof ( * Robot_info ) ) ) ;
Robot_info = ( robot_info * ) d_malloc ( N_robot_types * sizeof ( * Robot_info ) ) ;
mprintf ( ( 0 , " Robot_info = %i \n " , Robot_info ) ) ;
for ( i = 0 ; i < N_robot_types ; i + + ) {
Robot_info [ i ] . field_of_view = F1_0 / 2 ;
Robot_info [ i ] . firing_wait = F1_0 ;
Robot_info [ i ] . turn_time = F1_0 * 2 ;
// -- Robot_info[i].fire_power = F1_0;
// -- Robot_info[i].shield = F1_0/2;
Robot_info [ i ] . max_speed = F1_0 * 10 ;
Robot_info [ i ] . always_0xabcd = 0xabcd ;
}
# endif
}
// ---------------------------------------------------------------------------------------------------------------------
// Given a behavior, set initial mode.
int ai_behavior_to_mode ( int behavior )
{
switch ( behavior ) {
case AIB_STILL : return AIM_STILL ;
case AIB_NORMAL : return AIM_CHASE_OBJECT ;
case AIB_BEHIND : return AIM_BEHIND ;
case AIB_RUN_FROM : return AIM_RUN_FROM_OBJECT ;
case AIB_SNIPE : return AIM_STILL ; // Changed, 09/13/95, MK, snipers are still until they see you or are hit.
case AIB_STATION : return AIM_STILL ;
case AIB_FOLLOW : return AIM_FOLLOW_PATH ;
default : Int3 ( ) ; // Contact Mike: Error, illegal behavior type
}
return AIM_STILL ;
}
// ---------------------------------------------------------------------------------------------------------------------
// Call every time the player starts a new ship.
void ai_init_boss_for_ship ( void )
{
Boss_hit_time = - F1_0 * 10 ;
}
// ---------------------------------------------------------------------------------------------------------------------
// initial_mode == -1 means leave mode unchanged.
void init_ai_object ( int objnum , int behavior , int hide_segment )
{
object * objp = & Objects [ objnum ] ;
ai_static * aip = & objp - > ctype . ai_info ;
ai_local * ailp = & Ai_local_info [ objnum ] ;
robot_info * robptr = & Robot_info [ objp - > id ] ;
if ( behavior = = 0 ) {
// mprintf((0, "Behavior of 0 for object #%i, bashing to AIB_NORMAL.\n", objnum));
behavior = AIB_NORMAL ;
aip - > behavior = behavior ;
}
// mprintf((0, "Initializing object #%i\n", objnum));
// mode is now set from the Robot dialog, so this should get overwritten.
ailp - > mode = AIM_STILL ;
ailp - > previous_visibility = 0 ;
if ( behavior ! = - 1 ) {
aip - > behavior = behavior ;
ailp - > mode = ai_behavior_to_mode ( aip - > behavior ) ;
} else if ( ! ( ( aip - > behavior > = MIN_BEHAVIOR ) & & ( aip - > behavior < = MAX_BEHAVIOR ) ) ) {
mprintf ( ( 0 , " [obj %i -> normal] " , objnum ) ) ;
aip - > behavior = AIB_NORMAL ;
}
if ( robptr - > companion ) {
ailp - > mode = AIM_GOTO_PLAYER ;
Escort_kill_object = - 1 ;
}
if ( robptr - > thief ) {
aip - > behavior = AIB_SNIPE ;
ailp - > mode = AIM_THIEF_WAIT ;
}
if ( robptr - > attack_type ) {
aip - > behavior = AIB_NORMAL ;
ailp - > mode = ai_behavior_to_mode ( aip - > behavior ) ;
}
// This is astonishingly stupid! This routine gets called by matcens! KILL KILL KILL!!! Point_segs_free_ptr = Point_segs;
vm_vec_zero ( & objp - > mtype . phys_info . velocity ) ;
// -- ailp->wait_time = F1_0*5;
ailp - > player_awareness_time = 0 ;
ailp - > player_awareness_type = 0 ;
aip - > GOAL_STATE = AIS_SRCH ;
aip - > CURRENT_STATE = AIS_REST ;
ailp - > time_player_seen = GameTime ;
ailp - > next_misc_sound_time = GameTime ;
ailp - > time_player_sound_attacked = GameTime ;
if ( ( behavior = = AIB_SNIPE ) | | ( behavior = = AIB_STATION ) | | ( behavior = = AIB_RUN_FROM ) | | ( behavior = = AIB_FOLLOW ) ) {
aip - > hide_segment = hide_segment ;
ailp - > goal_segment = hide_segment ;
aip - > hide_index = - 1 ; // This means the path has not yet been created.
aip - > cur_path_index = 0 ;
}
aip - > SKIP_AI_COUNT = 0 ;
if ( robptr - > cloak_type = = RI_CLOAKED_ALWAYS )
aip - > CLOAKED = 1 ;
else
aip - > CLOAKED = 0 ;
objp - > mtype . phys_info . flags | = ( PF_BOUNCE | PF_TURNROLL ) ;
aip - > REMOTE_OWNER = - 1 ;
aip - > dying_sound_playing = 0 ;
aip - > dying_start_time = 0 ;
}
extern object * create_morph_robot ( segment * segp , vms_vector * object_pos , int object_id ) ;
// --------------------------------------------------------------------------------------------------------------------
// Create a Buddy bot.
// This automatically happens when you bring up the Buddy menu in a debug version.
// It is available as a cheat in a non-debug (release) version.
void create_buddy_bot ( void )
{
int buddy_id ;
vms_vector object_pos ;
for ( buddy_id = 0 ; buddy_id < N_robot_types ; buddy_id + + )
if ( Robot_info [ buddy_id ] . companion )
break ;
if ( buddy_id = = N_robot_types ) {
mprintf ( ( 0 , " Can't create Buddy. No 'companion' bot found in Robot_info! \n " ) ) ;
return ;
}
compute_segment_center ( & object_pos , & Segments [ ConsoleObject - > segnum ] ) ;
create_morph_robot ( & Segments [ ConsoleObject - > segnum ] , & object_pos , buddy_id ) ;
}
# define QUEUE_SIZE 256
// --------------------------------------------------------------------------------------------------------------------
// Create list of segments boss is allowed to teleport to at segptr.
// Set *num_segs.
// Boss is allowed to teleport to segments he fits in (calls object_intersects_wall) and
// he can reach from his initial position (calls find_connected_distance).
// If size_check is set, then only add segment if boss can fit in it, else any segment is legal.
// one_wall_hack added by MK, 10/13/95: A mega-hack! Set to !0 to ignore the
void init_boss_segments ( short segptr [ ] , int * num_segs , int size_check , int one_wall_hack )
{
int boss_objnum = - 1 ;
int i ;
* num_segs = 0 ;
# ifdef EDITOR
N_selected_segs = 0 ;
# endif
if ( size_check )
mprintf ( ( 0 , " Boss fits in segments: \n " ) ) ;
// See if there is a boss. If not, quick out.
for ( i = 0 ; i < = Highest_object_index ; i + + )
if ( ( Objects [ i ] . type = = OBJ_ROBOT ) & & ( Robot_info [ Objects [ i ] . id ] . boss_flag ) ) {
if ( boss_objnum ! = - 1 ) // There are two bosses in this mine! i and boss_objnum!
Int3 ( ) ; //do int3 here instead of assert so museum will work
boss_objnum = i ;
}
if ( boss_objnum ! = - 1 ) {
int original_boss_seg ;
vms_vector original_boss_pos ;
object * boss_objp = & Objects [ boss_objnum ] ;
int head , tail ;
int seg_queue [ QUEUE_SIZE ] ;
//ALREADY IN RENDER.H byte visited[MAX_SEGMENTS];
fix boss_size_save ;
boss_size_save = boss_objp - > size ;
// -- Causes problems!! -- boss_objp->size = fixmul((F1_0/4)*3, boss_objp->size);
original_boss_seg = boss_objp - > segnum ;
original_boss_pos = boss_objp - > pos ;
head = 0 ;
tail = 0 ;
seg_queue [ head + + ] = original_boss_seg ;
segptr [ ( * num_segs ) + + ] = original_boss_seg ;
mprintf ( ( 0 , " %4i " , original_boss_seg ) ) ;
# ifdef EDITOR
Selected_segs [ N_selected_segs + + ] = original_boss_seg ;
# endif
for ( i = 0 ; i < = Highest_segment_index ; i + + )
visited [ i ] = 0 ;
while ( tail ! = head ) {
int sidenum ;
segment * segp = & Segments [ seg_queue [ tail + + ] ] ;
tail & = QUEUE_SIZE - 1 ;
for ( sidenum = 0 ; sidenum < MAX_SIDES_PER_SEGMENT ; sidenum + + ) {
int w ;
if ( ( ( w = WALL_IS_DOORWAY ( segp , sidenum ) ) & WID_FLY_FLAG ) | | one_wall_hack ) {
// If we get here and w == WID_WALL, then we want to process through this wall, else not.
if ( IS_CHILD ( segp - > children [ sidenum ] ) ) {
if ( one_wall_hack )
one_wall_hack - - ;
} else
continue ;
if ( visited [ segp - > children [ sidenum ] ] = = 0 ) {
seg_queue [ head + + ] = segp - > children [ sidenum ] ;
visited [ segp - > children [ sidenum ] ] = 1 ;
head & = QUEUE_SIZE - 1 ;
if ( head > tail ) {
if ( head = = tail + QUEUE_SIZE - 1 )
Int3 ( ) ; // queue overflow. Make it bigger!
} else
if ( head + QUEUE_SIZE = = tail + QUEUE_SIZE - 1 )
Int3 ( ) ; // queue overflow. Make it bigger!
if ( ( ! size_check ) | | boss_fits_in_seg ( boss_objp , segp - > children [ sidenum ] ) ) {
segptr [ ( * num_segs ) + + ] = segp - > children [ sidenum ] ;
if ( size_check ) mprintf ( ( 0 , " %4i " , segp - > children [ sidenum ] ) ) ;
# ifdef EDITOR
Selected_segs [ N_selected_segs + + ] = segp - > children [ sidenum ] ;
# endif
if ( * num_segs > = MAX_BOSS_TELEPORT_SEGS ) {
mprintf ( ( 1 , " Warning: Too many boss teleport segments. Found %i after searching %i/%i segments. \n " , MAX_BOSS_TELEPORT_SEGS , segp - > children [ sidenum ] , Highest_segment_index + 1 ) ) ;
tail = head ;
}
}
}
}
}
}
boss_objp - > size = boss_size_save ;
boss_objp - > pos = original_boss_pos ;
obj_relink ( boss_objnum , original_boss_seg ) ;
}
}
extern void init_buddy_for_level ( void ) ;
// ---------------------------------------------------------------------------------------------------------------------
void init_ai_objects ( void )
{
int i ;
Point_segs_free_ptr = Point_segs ;
for ( i = 0 ; i < MAX_OBJECTS ; i + + ) {
object * objp = & Objects [ i ] ;
if ( objp - > control_type = = CT_AI )
init_ai_object ( i , objp - > ctype . ai_info . behavior , objp - > ctype . ai_info . hide_segment ) ;
}
init_boss_segments ( Boss_gate_segs , & Num_boss_gate_segs , 0 , 0 ) ;
init_boss_segments ( Boss_teleport_segs , & Num_boss_teleport_segs , 1 , 0 ) ;
if ( Num_boss_teleport_segs = = 1 )
init_boss_segments ( Boss_teleport_segs , & Num_boss_teleport_segs , 1 , 1 ) ;
Boss_dying_sound_playing = 0 ;
Boss_dying = 0 ;
// -- unused! MK, 10/21/95 -- Boss_been_hit = 0;
Gate_interval = F1_0 * 4 - Difficulty_level * i2f ( 2 ) / 3 ;
Ai_initialized = 1 ;
ai_do_cloak_stuff ( ) ;
init_buddy_for_level ( ) ;
if ( Current_level_num = = Last_level ) {
Boss_teleport_interval = F1_0 * 10 ;
Boss_cloak_interval = F1_0 * 15 ; // Time between cloaks
} else {
Boss_teleport_interval = F1_0 * 7 ;
Boss_cloak_interval = F1_0 * 10 ; // Time between cloaks
}
}
int Lunacy = 0 ;
int Diff_save = 1 ;
fix Firing_wait_copy [ MAX_ROBOT_TYPES ] ;
fix Firing_wait2_copy [ MAX_ROBOT_TYPES ] ;
byte Rapidfire_count_copy [ MAX_ROBOT_TYPES ] ;
void do_lunacy_on ( void )
{
int i ;
if ( Lunacy ) //already on
return ;
Lunacy = 1 ;
Diff_save = Difficulty_level ;
Difficulty_level = NDL - 1 ;
for ( i = 0 ; i < MAX_ROBOT_TYPES ; i + + ) {
Firing_wait_copy [ i ] = Robot_info [ i ] . firing_wait [ NDL - 1 ] ;
Firing_wait2_copy [ i ] = Robot_info [ i ] . firing_wait2 [ NDL - 1 ] ;
Rapidfire_count_copy [ i ] = Robot_info [ i ] . rapidfire_count [ NDL - 1 ] ;
Robot_info [ i ] . firing_wait [ NDL - 1 ] = Robot_info [ i ] . firing_wait [ 1 ] ;
Robot_info [ i ] . firing_wait2 [ NDL - 1 ] = Robot_info [ i ] . firing_wait2 [ 1 ] ;
Robot_info [ i ] . rapidfire_count [ NDL - 1 ] = Robot_info [ i ] . rapidfire_count [ 1 ] ;
}
}
void do_lunacy_off ( void )
{
int i ;
if ( ! Lunacy ) //already off
return ;
Lunacy = 0 ;
for ( i = 0 ; i < MAX_ROBOT_TYPES ; i + + ) {
Robot_info [ i ] . firing_wait [ NDL - 1 ] = Firing_wait_copy [ i ] ;
Robot_info [ i ] . firing_wait2 [ NDL - 1 ] = Firing_wait2_copy [ i ] ;
Robot_info [ i ] . rapidfire_count [ NDL - 1 ] = Rapidfire_count_copy [ i ] ;
}
Difficulty_level = Diff_save ;
}
// ----------------------------------------------------------------
// Do *dest = *delta unless:
// *delta is pretty small
// and they are of different signs.
void set_rotvel_and_saturate ( fix * dest , fix delta )
{
if ( ( delta ^ * dest ) < 0 ) {
if ( abs ( delta ) < F1_0 / 8 ) {
// mprintf((0, "D"));
* dest = delta / 4 ;
} else
// mprintf((0, "d"));
* dest = delta ;
} else {
// mprintf((0, "!"));
* dest = delta ;
}
}
//--debug-- #ifndef NDEBUG
//--debug-- int Total_turns=0;
//--debug-- int Prevented_turns=0;
//--debug-- #endif
# define AI_TURN_SCALE 1
# define BABY_SPIDER_ID 14
# define FIRE_AT_NEARBY_PLAYER_THRESHOLD (F1_0*40)
extern void physics_turn_towards_vector ( vms_vector * goal_vector , object * obj , fix rate ) ;
extern fix Seismic_tremor_magnitude ;
//-------------------------------------------------------------------------------------------
void ai_turn_towards_vector ( vms_vector * goal_vector , object * objp , fix rate )
{
vms_vector new_fvec ;
fix dot ;
// Not all robots can turn, eg, SPECIAL_REACTOR_ROBOT
if ( rate = = 0 )
return ;
if ( ( objp - > id = = BABY_SPIDER_ID ) & & ( objp - > type = = OBJ_ROBOT ) ) {
physics_turn_towards_vector ( goal_vector , objp , rate ) ;
return ;
}
new_fvec = * goal_vector ;
dot = vm_vec_dot ( goal_vector , & objp - > orient . fvec ) ;
if ( dot < ( F1_0 - FrameTime / 2 ) ) {
fix mag ;
fix new_scale = fixdiv ( FrameTime * AI_TURN_SCALE , rate ) ;
vm_vec_scale ( & new_fvec , new_scale ) ;
vm_vec_add2 ( & new_fvec , & objp - > orient . fvec ) ;
mag = vm_vec_normalize_quick ( & new_fvec ) ;
if ( mag < F1_0 / 256 ) {
mprintf ( ( 1 , " Degenerate vector in ai_turn_towards_vector (mag = %7.3f) \n " , f2fl ( mag ) ) ) ;
new_fvec = * goal_vector ; // if degenerate vector, go right to goal
}
}
if ( Seismic_tremor_magnitude ) {
vms_vector rand_vec ;
fix scale ;
make_random_vector ( & rand_vec ) ;
scale = fixdiv ( 2 * Seismic_tremor_magnitude , Robot_info [ objp - > id ] . mass ) ;
vm_vec_scale_add2 ( & new_fvec , & rand_vec , scale ) ;
}
vm_vector_2_matrix ( & objp - > orient , & new_fvec , NULL , & objp - > orient . rvec ) ;
}
// -- unused, 08/07/95 -- // --------------------------------------------------------------------------------------------------------------------
// -- unused, 08/07/95 -- void ai_turn_randomly(vms_vector *vec_to_player, object *obj, fix rate, int previous_visibility)
// -- unused, 08/07/95 -- {
// -- unused, 08/07/95 -- vms_vector curvec;
// -- unused, 08/07/95 --
// -- unused, 08/07/95 -- // -- MK, 06/09/95 // Random turning looks too stupid, so 1/4 of time, cheat.
// -- unused, 08/07/95 -- // -- MK, 06/09/95 if (previous_visibility)
// -- unused, 08/07/95 -- // -- MK, 06/09/95 if (d_rand() > 0x7400) {
// -- unused, 08/07/95 -- // -- MK, 06/09/95 ai_turn_towards_vector(vec_to_player, obj, rate);
// -- unused, 08/07/95 -- // -- MK, 06/09/95 return;
// -- unused, 08/07/95 -- // -- MK, 06/09/95 }
// -- unused, 08/07/95 --
// -- unused, 08/07/95 -- curvec = obj->mtype.phys_info.rotvel;
// -- unused, 08/07/95 --
// -- unused, 08/07/95 -- curvec.y += F1_0/64;
// -- unused, 08/07/95 --
// -- unused, 08/07/95 -- curvec.x += curvec.y/6;
// -- unused, 08/07/95 -- curvec.y += curvec.z/4;
// -- unused, 08/07/95 -- curvec.z += curvec.x/10;
// -- unused, 08/07/95 --
// -- unused, 08/07/95 -- if (abs(curvec.x) > F1_0/8) curvec.x /= 4;
// -- unused, 08/07/95 -- if (abs(curvec.y) > F1_0/8) curvec.y /= 4;
// -- unused, 08/07/95 -- if (abs(curvec.z) > F1_0/8) curvec.z /= 4;
// -- unused, 08/07/95 --
// -- unused, 08/07/95 -- obj->mtype.phys_info.rotvel = curvec;
// -- unused, 08/07/95 --
// -- unused, 08/07/95 -- }
// Overall_agitation affects:
// Widens field of view. Field of view is in range 0..1 (specified in bitmaps.tbl as N/360 degrees).
// Overall_agitation/128 subtracted from field of view, making robots see wider.
// Increases distance to which robot will search to create path to player by Overall_agitation/8 segments.
// Decreases wait between fire times by Overall_agitation/64 seconds.
// --------------------------------------------------------------------------------------------------------------------
// Returns:
// 0 Player is not visible from object, obstruction or something.
// 1 Player is visible, but not in field of view.
// 2 Player is visible and in field of view.
// Note: Uses Believed_player_pos as player's position for cloak effect.
// NOTE: Will destructively modify *pos if *pos is outside the mine.
int player_is_visible_from_object ( object * objp , vms_vector * pos , fix field_of_view , vms_vector * vec_to_player )
{
fix dot ;
fvi_query fq ;
// Assume that robot's gun tip is in same segment as robot's center.
objp - > ctype . ai_info . SUB_FLAGS & = ~ SUB_FLAGS_GUNSEG ;
fq . p0 = pos ;
if ( ( pos - > x ! = objp - > pos . x ) | | ( pos - > y ! = objp - > pos . y ) | | ( pos - > z ! = objp - > pos . z ) ) {
int segnum = find_point_seg ( pos , objp - > segnum ) ;
if ( segnum = = - 1 ) {
fq . startseg = objp - > segnum ;
* pos = objp - > pos ;
mprintf ( ( 1 , " Object %i, gun is outside mine, moving towards center. \n " , objp - Objects ) ) ;
move_towards_segment_center ( objp ) ;
} else {
if ( segnum ! = objp - > segnum ) {
// -- mprintf((0, "Warning: Robot's gun tip not in same segment as robot center, frame %i.\n", FrameCount));
objp - > ctype . ai_info . SUB_FLAGS | = SUB_FLAGS_GUNSEG ;
}
fq . startseg = segnum ;
}
} else
fq . startseg = objp - > segnum ;
fq . p1 = & Believed_player_pos ;
fq . rad = F1_0 / 4 ;
fq . thisobjnum = objp - Objects ;
fq . ignore_obj_list = NULL ;
fq . flags = FQ_TRANSWALL ; // -- Why were we checking objects? | FQ_CHECK_OBJS; //what about trans walls???
Hit_type = find_vector_intersection ( & fq , & Hit_data ) ;
Hit_pos = Hit_data . hit_pnt ;
Hit_seg = Hit_data . hit_seg ;
// -- when we stupidly checked objects -- if ((Hit_type == HIT_NONE) || ((Hit_type == HIT_OBJECT) && (Hit_data.hit_object == Players[Player_num].objnum))) {
if ( Hit_type = = HIT_NONE ) {
dot = vm_vec_dot ( vec_to_player , & objp - > orient . fvec ) ;
// mprintf((0, "Fvec = [%5.2f %5.2f %5.2f], vec_to_player = [%5.2f %5.2f %5.2f], dot = %7.3f\n", f2fl(objp->orient.fvec.x), f2fl(objp->orient.fvec.y), f2fl(objp->orient.fvec.z), f2fl(vec_to_player->x), f2fl(vec_to_player->y), f2fl(vec_to_player->z), f2fl(dot)));
if ( dot > field_of_view - ( Overall_agitation < < 9 ) ) {
return 2 ;
} else {
return 1 ;
}
} else {
return 0 ;
}
}
// ------------------------------------------------------------------------------------------------------------------
// Return 1 if animates, else return 0
int do_silly_animation ( object * objp )
{
int objnum = objp - Objects ;
jointpos * jp_list ;
int robot_type , gun_num , robot_state , num_joint_positions ;
polyobj_info * pobj_info = & objp - > rtype . pobj_info ;
ai_static * aip = & objp - > ctype . ai_info ;
// ai_local *ailp = &Ai_local_info[objnum];
int num_guns , at_goal ;
int attack_type ;
int flinch_attack_scale = 1 ;
robot_type = objp - > id ;
num_guns = Robot_info [ robot_type ] . n_guns ;
attack_type = Robot_info [ robot_type ] . attack_type ;
if ( num_guns = = 0 ) {
// mprintf((0, "Object #%i of type #%i has 0 guns.\n", objp-Objects, robot_type));
return 0 ;
}
// This is a hack. All positions should be based on goal_state, not GOAL_STATE.
robot_state = Mike_to_matt_xlate [ aip - > GOAL_STATE ] ;
// previous_robot_state = Mike_to_matt_xlate[aip->CURRENT_STATE];
if ( attack_type ) // && ((robot_state == AS_FIRE) || (robot_state == AS_RECOIL)))
flinch_attack_scale = Attack_scale ;
else if ( ( robot_state = = AS_FLINCH ) | | ( robot_state = = AS_RECOIL ) )
flinch_attack_scale = Flinch_scale ;
at_goal = 1 ;
for ( gun_num = 0 ; gun_num < = num_guns ; gun_num + + ) {
int joint ;
num_joint_positions = robot_get_anim_state ( & jp_list , robot_type , gun_num , robot_state ) ;
for ( joint = 0 ; joint < num_joint_positions ; joint + + ) {
fix delta_angle , delta_2 ;
int jointnum = jp_list [ joint ] . jointnum ;
vms_angvec * jp = & jp_list [ joint ] . angles ;
vms_angvec * pobjp = & pobj_info - > anim_angles [ jointnum ] ;
if ( jointnum > = Polygon_models [ objp - > rtype . pobj_info . model_num ] . n_models ) {
Int3 ( ) ; // Contact Mike: incompatible data, illegal jointnum, problem in pof file?
continue ;
}
if ( jp - > p ! = pobjp - > p ) {
if ( gun_num = = 0 )
at_goal = 0 ;
Ai_local_info [ objnum ] . goal_angles [ jointnum ] . p = jp - > p ;
delta_angle = jp - > p - pobjp - > p ;
if ( delta_angle > = F1_0 / 2 )
delta_2 = - ANIM_RATE ;
else if ( delta_angle > = 0 )
delta_2 = ANIM_RATE ;
else if ( delta_angle > = - F1_0 / 2 )
delta_2 = - ANIM_RATE ;
else
delta_2 = ANIM_RATE ;
if ( flinch_attack_scale ! = 1 )
delta_2 * = flinch_attack_scale ;
Ai_local_info [ objnum ] . delta_angles [ jointnum ] . p = delta_2 / DELTA_ANG_SCALE ; // complete revolutions per second
}
if ( jp - > b ! = pobjp - > b ) {
if ( gun_num = = 0 )
at_goal = 0 ;
Ai_local_info [ objnum ] . goal_angles [ jointnum ] . b = jp - > b ;
delta_angle = jp - > b - pobjp - > b ;
if ( delta_angle > = F1_0 / 2 )
delta_2 = - ANIM_RATE ;
else if ( delta_angle > = 0 )
delta_2 = ANIM_RATE ;
else if ( delta_angle > = - F1_0 / 2 )
delta_2 = - ANIM_RATE ;
else
delta_2 = ANIM_RATE ;
if ( flinch_attack_scale ! = 1 )
delta_2 * = flinch_attack_scale ;
Ai_local_info [ objnum ] . delta_angles [ jointnum ] . b = delta_2 / DELTA_ANG_SCALE ; // complete revolutions per second
}
if ( jp - > h ! = pobjp - > h ) {
if ( gun_num = = 0 )
at_goal = 0 ;
Ai_local_info [ objnum ] . goal_angles [ jointnum ] . h = jp - > h ;
delta_angle = jp - > h - pobjp - > h ;
if ( delta_angle > = F1_0 / 2 )
delta_2 = - ANIM_RATE ;
else if ( delta_angle > = 0 )
delta_2 = ANIM_RATE ;
else if ( delta_angle > = - F1_0 / 2 )
delta_2 = - ANIM_RATE ;
else
delta_2 = ANIM_RATE ;
if ( flinch_attack_scale ! = 1 )
delta_2 * = flinch_attack_scale ;
Ai_local_info [ objnum ] . delta_angles [ jointnum ] . h = delta_2 / DELTA_ANG_SCALE ; // complete revolutions per second
}
}
if ( at_goal ) {
//ai_static *aip = &objp->ctype.ai_info;
ai_local * ailp = & Ai_local_info [ objp - Objects ] ;
ailp - > achieved_state [ gun_num ] = ailp - > goal_state [ gun_num ] ;
if ( ailp - > achieved_state [ gun_num ] = = AIS_RECO )
ailp - > goal_state [ gun_num ] = AIS_FIRE ;
if ( ailp - > achieved_state [ gun_num ] = = AIS_FLIN )
ailp - > goal_state [ gun_num ] = AIS_LOCK ;
}
}
if ( at_goal = = 1 ) //num_guns)
aip - > CURRENT_STATE = aip - > GOAL_STATE ;
return 1 ;
}
// ------------------------------------------------------------------------------------------
// Move all sub-objects in an object towards their goals.
// Current orientation of object is at: pobj_info.anim_angles
// Goal orientation of object is at: ai_info.goal_angles
// Delta orientation of object is at: ai_info.delta_angles
void ai_frame_animation ( object * objp )
{
int objnum = objp - Objects ;
int joint ;
int num_joints ;
num_joints = Polygon_models [ objp - > rtype . pobj_info . model_num ] . n_models ;
for ( joint = 1 ; joint < num_joints ; joint + + ) {
fix delta_to_goal ;
fix scaled_delta_angle ;
vms_angvec * curangp = & objp - > rtype . pobj_info . anim_angles [ joint ] ;
vms_angvec * goalangp = & Ai_local_info [ objnum ] . goal_angles [ joint ] ;
vms_angvec * deltaangp = & Ai_local_info [ objnum ] . delta_angles [ joint ] ;
delta_to_goal = goalangp - > p - curangp - > p ;
if ( delta_to_goal > 32767 )
delta_to_goal = delta_to_goal - 65536 ;
else if ( delta_to_goal < - 32767 )
delta_to_goal = 65536 + delta_to_goal ;
if ( delta_to_goal ) {
scaled_delta_angle = fixmul ( deltaangp - > p , FrameTime ) * DELTA_ANG_SCALE ;
curangp - > p + = scaled_delta_angle ;
if ( abs ( delta_to_goal ) < abs ( scaled_delta_angle ) )
curangp - > p = goalangp - > p ;
}
delta_to_goal = goalangp - > b - curangp - > b ;
if ( delta_to_goal > 32767 )
delta_to_goal = delta_to_goal - 65536 ;
else if ( delta_to_goal < - 32767 )
delta_to_goal = 65536 + delta_to_goal ;
if ( delta_to_goal ) {
scaled_delta_angle = fixmul ( deltaangp - > b , FrameTime ) * DELTA_ANG_SCALE ;
curangp - > b + = scaled_delta_angle ;
if ( abs ( delta_to_goal ) < abs ( scaled_delta_angle ) )
curangp - > b = goalangp - > b ;
}
delta_to_goal = goalangp - > h - curangp - > h ;
if ( delta_to_goal > 32767 )
delta_to_goal = delta_to_goal - 65536 ;
else if ( delta_to_goal < - 32767 )
delta_to_goal = 65536 + delta_to_goal ;
if ( delta_to_goal ) {
scaled_delta_angle = fixmul ( deltaangp - > h , FrameTime ) * DELTA_ANG_SCALE ;
curangp - > h + = scaled_delta_angle ;
if ( abs ( delta_to_goal ) < abs ( scaled_delta_angle ) )
curangp - > h = goalangp - > h ;
}
}
}
// ----------------------------------------------------------------------------------
void set_next_fire_time ( object * objp , ai_local * ailp , robot_info * robptr , int gun_num )
{
// For guys in snipe mode, they have a 50% shot of getting this shot in free.
if ( ( gun_num ! = 0 ) | | ( robptr - > weapon_type2 = = - 1 ) )
if ( ( objp - > ctype . ai_info . behavior ! = AIB_SNIPE ) | | ( d_rand ( ) > 16384 ) )
ailp - > rapidfire_count + + ;
// Old way, 10/15/95: Continuous rapidfire if rapidfire_count set.
// -- if (((robptr->weapon_type2 == -1) || (gun_num != 0)) && (ailp->rapidfire_count < robptr->rapidfire_count[Difficulty_level])) {
// -- ailp->next_fire = min(F1_0/8, robptr->firing_wait[Difficulty_level]/2);
// -- } else {
// -- if ((robptr->weapon_type2 == -1) || (gun_num != 0)) {
// -- ailp->rapidfire_count = 0;
// -- ailp->next_fire = robptr->firing_wait[Difficulty_level];
// -- } else
// -- ailp->next_fire2 = robptr->firing_wait2[Difficulty_level];
// -- }
if ( ( ( gun_num ! = 0 ) | | ( robptr - > weapon_type2 = = - 1 ) ) & & ( ailp - > rapidfire_count < robptr - > rapidfire_count [ Difficulty_level ] ) ) {
ailp - > next_fire = min ( F1_0 / 8 , robptr - > firing_wait [ Difficulty_level ] / 2 ) ;
} else {
if ( ( robptr - > weapon_type2 = = - 1 ) | | ( gun_num ! = 0 ) ) {
ailp - > next_fire = robptr - > firing_wait [ Difficulty_level ] ;
if ( ailp - > rapidfire_count > = robptr - > rapidfire_count [ Difficulty_level ] )
ailp - > rapidfire_count = 0 ;
} else
ailp - > next_fire2 = robptr - > firing_wait2 [ Difficulty_level ] ;
}
}
// ----------------------------------------------------------------------------------
// When some robots collide with the player, they attack.
// If player is cloaked, then robot probably didn't actually collide, deal with that here.
void do_ai_robot_hit_attack ( object * robot , object * playerobj , vms_vector * collision_point )
{
ai_local * ailp = & Ai_local_info [ robot - Objects ] ;
robot_info * robptr = & Robot_info [ robot - > id ] ;
//#ifndef NDEBUG
if ( ! Robot_firing_enabled )
return ;
//#endif
// If player is dead, stop firing.
if ( Objects [ Players [ Player_num ] . objnum ] . type = = OBJ_GHOST )
return ;
if ( robptr - > attack_type = = 1 ) {
if ( ailp - > next_fire < = 0 ) {
if ( ! ( Players [ Player_num ] . flags & PLAYER_FLAGS_CLOAKED ) )
if ( vm_vec_dist_quick ( & ConsoleObject - > pos , & robot - > pos ) < robot - > size + ConsoleObject - > size + F1_0 * 2 ) {
collide_player_and_nasty_robot ( playerobj , robot , collision_point ) ;
if ( robptr - > energy_drain & & Players [ Player_num ] . energy ) {
Players [ Player_num ] . energy - = robptr - > energy_drain * F1_0 ;
if ( Players [ Player_num ] . energy < 0 )
Players [ Player_num ] . energy = 0 ;
// -- unused, use claw_sound in bitmaps.tbl -- digi_link_sound_to_pos( SOUND_ROBOT_SUCKED_PLAYER, playerobj->segnum, 0, collision_point, 0, F1_0 );
}
}
robot - > ctype . ai_info . GOAL_STATE = AIS_RECO ;
set_next_fire_time ( robot , ailp , robptr , 1 ) ; // 1 = gun_num: 0 is special (uses next_fire2)
}
}
}
# ifndef _OBJECT_H
extern int Player_exploded ;
# endif
# define FIRE_K 8 // Controls average accuracy of robot firing. Smaller numbers make firing worse. Being power of 2 doesn't matter.
// ====================================================================================================================
# define MIN_LEAD_SPEED (F1_0*4)
# define MAX_LEAD_DISTANCE (F1_0*200)
# define LEAD_RANGE (F1_0 / 2)
// --------------------------------------------------------------------------------------------------------------------
// Computes point at which projectile fired by robot can hit player given positions, player vel, elapsed time
fix compute_lead_component ( fix player_pos , fix robot_pos , fix player_vel , fix elapsed_time )
{
return fixdiv ( player_pos - robot_pos , elapsed_time ) + player_vel ;
}
// --------------------------------------------------------------------------------------------------------------------
// Lead the player, returning point to fire at in fire_point.
// Rules:
// Player not cloaked
// Player must be moving at a speed >= MIN_LEAD_SPEED
// Player not farther away than MAX_LEAD_DISTANCE
// dot(vector_to_player, player_direction) must be in -LEAD_RANGE..LEAD_RANGE
// if firing a matter weapon, less leading, based on skill level.
int lead_player ( object * objp , vms_vector * fire_point , vms_vector * believed_player_pos , int gun_num , vms_vector * fire_vec )
{
fix dot , player_speed , dist_to_player , max_weapon_speed , projected_time ;
vms_vector player_movement_dir , vec_to_player ;
int weapon_type ;
weapon_info * wptr ;
robot_info * robptr ;
if ( Players [ Player_num ] . flags & PLAYER_FLAGS_CLOAKED )
return 0 ;
player_movement_dir = ConsoleObject - > mtype . phys_info . velocity ;
player_speed = vm_vec_normalize_quick ( & player_movement_dir ) ;
if ( player_speed < MIN_LEAD_SPEED )
return 0 ;
vm_vec_sub ( & vec_to_player , believed_player_pos , fire_point ) ;
dist_to_player = vm_vec_normalize_quick ( & vec_to_player ) ;
if ( dist_to_player > MAX_LEAD_DISTANCE )
return 0 ;
dot = vm_vec_dot ( & vec_to_player , & player_movement_dir ) ;
if ( ( dot < - LEAD_RANGE ) | | ( dot > LEAD_RANGE ) )
return 0 ;
// Looks like it might be worth trying to lead the player.
robptr = & Robot_info [ objp - > id ] ;
weapon_type = robptr - > weapon_type ;
if ( robptr - > weapon_type2 ! = - 1 )
if ( gun_num = = 0 )
weapon_type = robptr - > weapon_type2 ;
wptr = & Weapon_info [ weapon_type ] ;
max_weapon_speed = wptr - > speed [ Difficulty_level ] ;
if ( max_weapon_speed < F1_0 )
return 0 ;
// Matter weapons:
// At Rookie or Trainee, don't lead at all.
// At higher skill levels, don't lead as well. Accomplish this by screwing up max_weapon_speed.
if ( wptr - > matter )
{
if ( Difficulty_level < = 1 )
return 0 ;
else
max_weapon_speed * = ( NDL - Difficulty_level ) ;
}
projected_time = fixdiv ( dist_to_player , max_weapon_speed ) ;
fire_vec - > x = compute_lead_component ( believed_player_pos - > x , fire_point - > x , ConsoleObject - > mtype . phys_info . velocity . x , projected_time ) ;
fire_vec - > y = compute_lead_component ( believed_player_pos - > y , fire_point - > y , ConsoleObject - > mtype . phys_info . velocity . y , projected_time ) ;
fire_vec - > z = compute_lead_component ( believed_player_pos - > z , fire_point - > z , ConsoleObject - > mtype . phys_info . velocity . z , projected_time ) ;
vm_vec_normalize_quick ( fire_vec ) ;
Assert ( vm_vec_dot ( fire_vec , & objp - > orient . fvec ) < 3 * F1_0 / 2 ) ;
// Make sure not firing at especially strange angle. If so, try to correct. If still bad, give up after one try.
if ( vm_vec_dot ( fire_vec , & objp - > orient . fvec ) < F1_0 / 2 ) {
vm_vec_add2 ( fire_vec , & vec_to_player ) ;
vm_vec_scale ( fire_vec , F1_0 / 2 ) ;
if ( vm_vec_dot ( fire_vec , & objp - > orient . fvec ) < F1_0 / 2 ) {
return 0 ;
}
}
return 1 ;
}
// --------------------------------------------------------------------------------------------------------------------
// Note: Parameter vec_to_player is only passed now because guns which aren't on the forward vector from the
// center of the robot will not fire right at the player. We need to aim the guns at the player. Barring that, we cheat.
// When this routine is complete, the parameter vec_to_player should not be necessary.
void ai_fire_laser_at_player ( object * obj , vms_vector * fire_point , int gun_num , vms_vector * believed_player_pos )
{
int objnum = obj - Objects ;
ai_local * ailp = & Ai_local_info [ objnum ] ;
robot_info * robptr = & Robot_info [ obj - > id ] ;
vms_vector fire_vec ;
vms_vector bpp_diff ;
int weapon_type ;
fix aim , dot ;
int count ;
Assert ( robptr - > attack_type = = 0 ) ; // We should never be coming here for the green guy, as he has no laser!
// If this robot is only awake because a camera woke it up, don't fire.
if ( obj - > ctype . ai_info . SUB_FLAGS & SUB_FLAGS_CAMERA_AWAKE )
return ;
if ( ! Robot_firing_enabled )
return ;
if ( obj - > control_type = = CT_MORPH )
return ;
// If player is exploded, stop firing.
if ( Player_exploded )
return ;
if ( obj - > ctype . ai_info . dying_start_time )
return ; // No firing while in death roll.
// Don't let the boss fire while in death roll. Sorry, this is the easiest way to do this.
// If you try to key the boss off obj->ctype.ai_info.dying_start_time, it will hose the endlevel stuff.
if ( Boss_dying_start_time & Robot_info [ obj - > id ] . boss_flag )
return ;
// If player is cloaked, maybe don't fire based on how long cloaked and randomness.
if ( Players [ Player_num ] . flags & PLAYER_FLAGS_CLOAKED ) {
fix cloak_time = Ai_cloak_info [ objnum % MAX_AI_CLOAK_INFO ] . last_time ;
if ( GameTime - cloak_time > CLOAK_TIME_MAX / 4 )
if ( d_rand ( ) > fixdiv ( GameTime - cloak_time , CLOAK_TIME_MAX ) / 2 ) {
set_next_fire_time ( obj , ailp , robptr , gun_num ) ;
return ;
}
}
// Handle problem of a robot firing through a wall because its gun tip is on the other
// side of the wall than the robot's center. For speed reasons, we normally only compute
// the vector from the gun point to the player. But we need to know whether the gun point
// is separated from the robot's center by a wall. If so, don't fire!
if ( obj - > ctype . ai_info . SUB_FLAGS & SUB_FLAGS_GUNSEG ) {
// Well, the gun point is in a different segment than the robot's center.
// This is almost always ok, but it is not ok if something solid is in between.
int conn_side ;
int gun_segnum = find_point_seg ( fire_point , obj - > segnum ) ;
// See if these segments are connected, which should almost always be the case.
conn_side = find_connect_side ( & Segments [ gun_segnum ] , & Segments [ obj - > segnum ] ) ;
if ( conn_side ! = - 1 ) {
// They are connected via conn_side in segment obj->segnum.
// See if they are unobstructed.
if ( ! ( WALL_IS_DOORWAY ( & Segments [ obj - > segnum ] , conn_side ) & WID_FLY_FLAG ) ) {
// Can't fly through, so don't let this bot fire through!
return ;
}
} else {
// Well, they are not directly connected, so use find_vector_intersection to see if they are unobstructed.
fvi_query fq ;
fvi_info hit_data ;
int fate ;
fq . startseg = obj - > segnum ;
fq . p0 = & obj - > pos ;
fq . p1 = fire_point ;
fq . rad = 0 ;
fq . thisobjnum = obj - Objects ;
fq . ignore_obj_list = NULL ;
fq . flags = FQ_TRANSWALL ;
fate = find_vector_intersection ( & fq , & hit_data ) ;
if ( fate ! = HIT_NONE ) {
Int3 ( ) ; // This bot's gun is poking through a wall, so don't fire.
move_towards_segment_center ( obj ) ; // And decrease chances it will happen again.
return ;
}
}
}
// -- mprintf((0, "Firing from gun #%i at time = %7.3f\n", gun_num, f2fl(GameTime)));
// Set position to fire at based on difficulty level and robot's aiming ability
aim = FIRE_K * F1_0 - ( FIRE_K - 1 ) * ( robptr - > aim < < 8 ) ; // F1_0 in bitmaps.tbl = same as used to be. Worst is 50% more error.
// Robots aim more poorly during seismic disturbance.
if ( Seismic_tremor_magnitude ) {
fix temp ;
temp = F1_0 - abs ( Seismic_tremor_magnitude ) ;
if ( temp < F1_0 / 2 )
temp = F1_0 / 2 ;
aim = fixmul ( aim , temp ) ;
}
// Lead the player half the time.
// Note that when leading the player, aim is perfect. This is probably acceptable since leading is so hacked in.
// Problem is all robots will lead equally badly.
if ( d_rand ( ) < 16384 ) {
if ( lead_player ( obj , fire_point , believed_player_pos , gun_num , & fire_vec ) ) // Stuff direction to fire at in fire_point.
goto player_led ;
}
dot = 0 ;
count = 0 ; // Don't want to sit in this loop forever...
while ( ( count < 4 ) & & ( dot < F1_0 / 4 ) ) {
bpp_diff . x = believed_player_pos - > x + fixmul ( ( d_rand ( ) - 16384 ) * ( NDL - Difficulty_level - 1 ) * 4 , aim ) ;
bpp_diff . y = believed_player_pos - > y + fixmul ( ( d_rand ( ) - 16384 ) * ( NDL - Difficulty_level - 1 ) * 4 , aim ) ;
bpp_diff . z = believed_player_pos - > z + fixmul ( ( d_rand ( ) - 16384 ) * ( NDL - Difficulty_level - 1 ) * 4 , aim ) ;
vm_vec_normalized_dir_quick ( & fire_vec , & bpp_diff , fire_point ) ;
dot = vm_vec_dot ( & obj - > orient . fvec , & fire_vec ) ;
count + + ;
}
player_led : ;
weapon_type = robptr - > weapon_type ;
if ( robptr - > weapon_type2 ! = - 1 )
if ( gun_num = = 0 )
weapon_type = robptr - > weapon_type2 ;
Laser_create_new_easy ( & fire_vec , fire_point , obj - Objects , weapon_type , 1 ) ;
# ifdef NETWORK
if ( Game_mode & GM_MULTI ) {
ai_multi_send_robot_position ( objnum , - 1 ) ;
multi_send_robot_fire ( objnum , obj - > ctype . ai_info . CURRENT_GUN , & fire_vec ) ;
}
# endif
create_awareness_event ( obj , PA_NEARBY_ROBOT_FIRED ) ;
set_next_fire_time ( obj , ailp , robptr , gun_num ) ;
}
// --------------------------------------------------------------------------------------------------------------------
// vec_goal must be normalized, or close to it.
// if dot_based set, then speed is based on direction of movement relative to heading
void move_towards_vector ( object * objp , vms_vector * vec_goal , int dot_based )
{
physics_info * pptr = & objp - > mtype . phys_info ;
fix speed , dot , max_speed ;
robot_info * robptr = & Robot_info [ objp - > id ] ;
vms_vector vel ;
// Trying to move towards player. If forward vector much different than velocity vector,
// bash velocity vector twice as much towards player as usual.
vel = pptr - > velocity ;
vm_vec_normalize_quick ( & vel ) ;
dot = vm_vec_dot ( & vel , & objp - > orient . fvec ) ;
if ( robptr - > thief )
dot = ( F1_0 + dot ) / 2 ;
if ( dot_based & & ( dot < 3 * F1_0 / 4 ) ) {
// This funny code is supposed to slow down the robot and move his velocity towards his direction
// more quickly than the general code
pptr - > velocity . x = pptr - > velocity . x / 2 + fixmul ( vec_goal - > x , FrameTime * 32 ) ;
pptr - > velocity . y = pptr - > velocity . y / 2 + fixmul ( vec_goal - > y , FrameTime * 32 ) ;
pptr - > velocity . z = pptr - > velocity . z / 2 + fixmul ( vec_goal - > z , FrameTime * 32 ) ;
} else {
pptr - > velocity . x + = fixmul ( vec_goal - > x , FrameTime * 64 ) * ( Difficulty_level + 5 ) / 4 ;
pptr - > velocity . y + = fixmul ( vec_goal - > y , FrameTime * 64 ) * ( Difficulty_level + 5 ) / 4 ;
pptr - > velocity . z + = fixmul ( vec_goal - > z , FrameTime * 64 ) * ( Difficulty_level + 5 ) / 4 ;
}
speed = vm_vec_mag_quick ( & pptr - > velocity ) ;
max_speed = robptr - > max_speed [ Difficulty_level ] ;
// Green guy attacks twice as fast as he moves away.
if ( ( robptr - > attack_type = = 1 ) | | robptr - > thief | | robptr - > kamikaze )
max_speed * = 2 ;
if ( speed > max_speed ) {
pptr - > velocity . x = ( pptr - > velocity . x * 3 ) / 4 ;
pptr - > velocity . y = ( pptr - > velocity . y * 3 ) / 4 ;
pptr - > velocity . z = ( pptr - > velocity . z * 3 ) / 4 ;
}
}
// --------------------------------------------------------------------------------------------------------------------
void move_towards_player ( object * objp , vms_vector * vec_to_player )
// vec_to_player must be normalized, or close to it.
{
move_towards_vector ( objp , vec_to_player , 1 ) ;
}
// --------------------------------------------------------------------------------------------------------------------
// I am ashamed of this: fast_flag == -1 means normal slide about. fast_flag = 0 means no evasion.
void move_around_player ( object * objp , vms_vector * vec_to_player , int fast_flag )
{
physics_info * pptr = & objp - > mtype . phys_info ;
fix speed ;
robot_info * robptr = & Robot_info [ objp - > id ] ;
int objnum = objp - Objects ;
int dir ;
int dir_change ;
fix ft ;
vms_vector evade_vector ;
int count = 0 ;
if ( fast_flag = = 0 )
return ;
dir_change = 48 ;
ft = FrameTime ;
if ( ft < F1_0 / 32 ) {
dir_change * = 8 ;
count + = 3 ;
} else
while ( ft < F1_0 / 4 ) {
dir_change * = 2 ;
ft * = 2 ;
count + + ;
}
dir = ( FrameCount + ( count + 1 ) * ( objnum * 8 + objnum * 4 + objnum ) ) & dir_change ;
dir > > = ( 4 + count ) ;
Assert ( ( dir > = 0 ) & & ( dir < = 3 ) ) ;
switch ( dir ) {
case 0 :
evade_vector . x = fixmul ( vec_to_player - > z , FrameTime * 32 ) ;
evade_vector . y = fixmul ( vec_to_player - > y , FrameTime * 32 ) ;
evade_vector . z = fixmul ( - vec_to_player - > x , FrameTime * 32 ) ;
break ;
case 1 :
evade_vector . x = fixmul ( - vec_to_player - > z , FrameTime * 32 ) ;
evade_vector . y = fixmul ( vec_to_player - > y , FrameTime * 32 ) ;
evade_vector . z = fixmul ( vec_to_player - > x , FrameTime * 32 ) ;
break ;
case 2 :
evade_vector . x = fixmul ( - vec_to_player - > y , FrameTime * 32 ) ;
evade_vector . y = fixmul ( vec_to_player - > x , FrameTime * 32 ) ;
evade_vector . z = fixmul ( vec_to_player - > z , FrameTime * 32 ) ;
break ;
case 3 :
evade_vector . x = fixmul ( vec_to_player - > y , FrameTime * 32 ) ;
evade_vector . y = fixmul ( - vec_to_player - > x , FrameTime * 32 ) ;
evade_vector . z = fixmul ( vec_to_player - > z , FrameTime * 32 ) ;
break ;
default :
Error ( " Function move_around_player: Bad case. " ) ;
}
// Note: -1 means normal circling about the player. > 0 means fast evasion.
if ( fast_flag > 0 ) {
fix dot ;
// Only take evasive action if looking at player.
// Evasion speed is scaled by percentage of shields left so wounded robots evade less effectively.
dot = vm_vec_dot ( vec_to_player , & objp - > orient . fvec ) ;
if ( ( dot > robptr - > field_of_view [ Difficulty_level ] ) & & ! ( ConsoleObject - > flags & PLAYER_FLAGS_CLOAKED ) ) {
fix damage_scale ;
if ( robptr - > strength )
damage_scale = fixdiv ( objp - > shields , robptr - > strength ) ;
else
damage_scale = F1_0 ;
if ( damage_scale > F1_0 )
damage_scale = F1_0 ; // Just in case...
else if ( damage_scale < 0 )
damage_scale = 0 ; // Just in case...
vm_vec_scale ( & evade_vector , i2f ( fast_flag ) + damage_scale ) ;
}
}
pptr - > velocity . x + = evade_vector . x ;
pptr - > velocity . y + = evade_vector . y ;
pptr - > velocity . z + = evade_vector . z ;
speed = vm_vec_mag_quick ( & pptr - > velocity ) ;
if ( ( objp - Objects ! = 1 ) & & ( speed > robptr - > max_speed [ Difficulty_level ] ) ) {
pptr - > velocity . x = ( pptr - > velocity . x * 3 ) / 4 ;
pptr - > velocity . y = ( pptr - > velocity . y * 3 ) / 4 ;
pptr - > velocity . z = ( pptr - > velocity . z * 3 ) / 4 ;
}
}
// --------------------------------------------------------------------------------------------------------------------
void move_away_from_player ( object * objp , vms_vector * vec_to_player , int attack_type )
{
fix speed ;
physics_info * pptr = & objp - > mtype . phys_info ;
robot_info * robptr = & Robot_info [ objp - > id ] ;
int objref ;
pptr - > velocity . x - = fixmul ( vec_to_player - > x , FrameTime * 16 ) ;
pptr - > velocity . y - = fixmul ( vec_to_player - > y , FrameTime * 16 ) ;
pptr - > velocity . z - = fixmul ( vec_to_player - > z , FrameTime * 16 ) ;
if ( attack_type ) {
// Get value in 0..3 to choose evasion direction.
objref = ( ( objp - Objects ) ^ ( ( FrameCount + 3 * ( objp - Objects ) ) > > 5 ) ) & 3 ;
switch ( objref ) {
case 0 : vm_vec_scale_add2 ( & pptr - > velocity , & objp - > orient . uvec , FrameTime < < 5 ) ; break ;
case 1 : vm_vec_scale_add2 ( & pptr - > velocity , & objp - > orient . uvec , - FrameTime < < 5 ) ; break ;
case 2 : vm_vec_scale_add2 ( & pptr - > velocity , & objp - > orient . rvec , FrameTime < < 5 ) ; break ;
case 3 : vm_vec_scale_add2 ( & pptr - > velocity , & objp - > orient . rvec , - FrameTime < < 5 ) ; break ;
default : Int3 ( ) ; // Impossible, bogus value on objref, must be in 0..3
}
}
speed = vm_vec_mag_quick ( & pptr - > velocity ) ;
if ( speed > robptr - > max_speed [ Difficulty_level ] ) {
pptr - > velocity . x = ( pptr - > velocity . x * 3 ) / 4 ;
pptr - > velocity . y = ( pptr - > velocity . y * 3 ) / 4 ;
pptr - > velocity . z = ( pptr - > velocity . z * 3 ) / 4 ;
}
}
// --------------------------------------------------------------------------------------------------------------------
// Move towards, away_from or around player.
// Also deals with evasion.
// If the flag evade_only is set, then only allowed to evade, not allowed to move otherwise (must have mode == AIM_STILL).
void ai_move_relative_to_player ( object * objp , ai_local * ailp , fix dist_to_player , vms_vector * vec_to_player , fix circle_distance , int evade_only , int player_visibility )
{
object * dobjp ;
robot_info * robptr = & Robot_info [ objp - > id ] ;
Assert ( player_visibility ! = - 1 ) ;
// See if should take avoidance.
// New way, green guys don't evade: if ((robptr->attack_type == 0) && (objp->ctype.ai_info.danger_laser_num != -1)) {
if ( objp - > ctype . ai_info . danger_laser_num ! = - 1 ) {
dobjp = & Objects [ objp - > ctype . ai_info . danger_laser_num ] ;
if ( ( dobjp - > type = = OBJ_WEAPON ) & & ( dobjp - > signature = = objp - > ctype . ai_info . danger_laser_signature ) ) {
fix dot , dist_to_laser , field_of_view ;
vms_vector vec_to_laser , laser_fvec ;
field_of_view = Robot_info [ objp - > id ] . field_of_view [ Difficulty_level ] ;
vm_vec_sub ( & vec_to_laser , & dobjp - > pos , & objp - > pos ) ;
dist_to_laser = vm_vec_normalize_quick ( & vec_to_laser ) ;
dot = vm_vec_dot ( & vec_to_laser , & objp - > orient . fvec ) ;
if ( ( dot > field_of_view ) | | ( robptr - > companion ) ) {
fix laser_robot_dot ;
vms_vector laser_vec_to_robot ;
// The laser is seen by the robot, see if it might hit the robot.
// Get the laser's direction. If it's a polyobj, it can be gotten cheaply from the orientation matrix.
if ( dobjp - > render_type = = RT_POLYOBJ )
laser_fvec = dobjp - > orient . fvec ;
else { // Not a polyobj, get velocity and normalize.
laser_fvec = dobjp - > mtype . phys_info . velocity ; //dobjp->orient.fvec;
vm_vec_normalize_quick ( & laser_fvec ) ;
}
vm_vec_sub ( & laser_vec_to_robot , & objp - > pos , & dobjp - > pos ) ;
vm_vec_normalize_quick ( & laser_vec_to_robot ) ;
laser_robot_dot = vm_vec_dot ( & laser_fvec , & laser_vec_to_robot ) ;
if ( ( laser_robot_dot > F1_0 * 7 / 8 ) & & ( dist_to_laser < F1_0 * 80 ) ) {
int evade_speed ;
ai_evaded = 1 ;
evade_speed = Robot_info [ objp - > id ] . evade_speed [ Difficulty_level ] ;
move_around_player ( objp , vec_to_player , evade_speed ) ;
}
}
return ;
}
}
// If only allowed to do evade code, then done.
// Hmm, perhaps brilliant insight. If want claw-type guys to keep coming, don't return here after evasion.
if ( ( ! robptr - > attack_type ) & & ( ! robptr - > thief ) & & evade_only )
return ;
// If we fall out of above, then no object to be avoided.
objp - > ctype . ai_info . danger_laser_num = - 1 ;
// Green guy selects move around/towards/away based on firing time, not distance.
if ( robptr - > attack_type = = 1 ) {
if ( ( ( ailp - > next_fire > robptr - > firing_wait [ Difficulty_level ] / 4 ) & & ( dist_to_player < F1_0 * 30 ) ) | | Player_is_dead ) {
// 1/4 of time, move around player, 3/4 of time, move away from player
if ( d_rand ( ) < 8192 ) {
move_around_player ( objp , vec_to_player , - 1 ) ;
} else {
move_away_from_player ( objp , vec_to_player , 1 ) ;
}
} else {
move_towards_player ( objp , vec_to_player ) ;
}
} else if ( robptr - > thief ) {
move_towards_player ( objp , vec_to_player ) ;
} else {
int objval = ( ( objp - Objects ) & 0x0f ) ^ 0x0a ;
// Changes here by MK, 12/29/95. Trying to get rid of endless circling around bots in a large room.
if ( robptr - > kamikaze ) {
move_towards_player ( objp , vec_to_player ) ;
} else if ( dist_to_player < circle_distance )
move_away_from_player ( objp , vec_to_player , 0 ) ;
else if ( ( dist_to_player < ( 3 + objval ) * circle_distance / 2 ) & & ( ailp - > next_fire > - F1_0 ) ) {
move_around_player ( objp , vec_to_player , - 1 ) ;
} else {
if ( ( - ailp - > next_fire > F1_0 + ( objval < < 12 ) ) & & player_visibility ) {
// Usually move away, but sometimes move around player.
if ( ( ( ( GameTime > > 18 ) & 0x0f ) ^ objval ) > 4 ) {
move_away_from_player ( objp , vec_to_player , 0 ) ;
} else {
move_around_player ( objp , vec_to_player , - 1 ) ;
}
} else
move_towards_player ( objp , vec_to_player ) ;
}
}
}
// --------------------------------------------------------------------------------------------------------------------
// Compute a somewhat random, normalized vector.
void make_random_vector ( vms_vector * vec )
{
vec - > x = ( d_rand ( ) - 16384 ) | 1 ; // make sure we don't create null vector
vec - > y = d_rand ( ) - 16384 ;
vec - > z = d_rand ( ) - 16384 ;
vm_vec_normalize_quick ( vec ) ;
}
# ifndef NDEBUG
void mprintf_animation_info ( object * objp )
{
ai_static * aip = & objp - > ctype . ai_info ;
ai_local * ailp = & Ai_local_info [ objp - Objects ] ;
if ( ! Ai_info_enabled )
return ;
mprintf ( ( 0 , " Goal = " ) ) ;
switch ( aip - > GOAL_STATE ) {
case AIS_NONE : mprintf ( ( 0 , " NONE " ) ) ; break ;
case AIS_REST : mprintf ( ( 0 , " REST " ) ) ; break ;
case AIS_SRCH : mprintf ( ( 0 , " SRCH " ) ) ; break ;
case AIS_LOCK : mprintf ( ( 0 , " LOCK " ) ) ; break ;
case AIS_FLIN : mprintf ( ( 0 , " FLIN " ) ) ; break ;
case AIS_FIRE : mprintf ( ( 0 , " FIRE " ) ) ; break ;
case AIS_RECO : mprintf ( ( 0 , " RECO " ) ) ; break ;
case AIS_ERR_ : mprintf ( ( 0 , " ERR_ " ) ) ; break ;
}
mprintf ( ( 0 , " Cur = " ) ) ;
switch ( aip - > CURRENT_STATE ) {
case AIS_NONE : mprintf ( ( 0 , " NONE " ) ) ; break ;
case AIS_REST : mprintf ( ( 0 , " REST " ) ) ; break ;
case AIS_SRCH : mprintf ( ( 0 , " SRCH " ) ) ; break ;
case AIS_LOCK : mprintf ( ( 0 , " LOCK " ) ) ; break ;
case AIS_FLIN : mprintf ( ( 0 , " FLIN " ) ) ; break ;
case AIS_FIRE : mprintf ( ( 0 , " FIRE " ) ) ; break ;
case AIS_RECO : mprintf ( ( 0 , " RECO " ) ) ; break ;
case AIS_ERR_ : mprintf ( ( 0 , " ERR_ " ) ) ; break ;
}
mprintf ( ( 0 , " Aware = " ) ) ;
switch ( ailp - > player_awareness_type ) {
case AIE_FIRE : mprintf ( ( 0 , " FIRE " ) ) ; break ;
case AIE_HITT : mprintf ( ( 0 , " HITT " ) ) ; break ;
case AIE_COLL : mprintf ( ( 0 , " COLL " ) ) ; break ;
case AIE_HURT : mprintf ( ( 0 , " HURT " ) ) ; break ;
}
mprintf ( ( 0 , " Next fire = %6.3f, Time = %6.3f \n " , f2fl ( ailp - > next_fire ) , f2fl ( ailp - > player_awareness_time ) ) ) ;
}
# endif
// -------------------------------------------------------------------------------------------------------------------
int Break_on_object = - 1 ;
void do_firing_stuff ( object * obj , int player_visibility , vms_vector * vec_to_player )
{
if ( ( Dist_to_last_fired_upon_player_pos < FIRE_AT_NEARBY_PLAYER_THRESHOLD ) | | ( player_visibility > = 1 ) ) {
// Now, if in robot's field of view, lock onto player
fix dot = vm_vec_dot ( & obj - > orient . fvec , vec_to_player ) ;
if ( ( dot > = 7 * F1_0 / 8 ) | | ( Players [ Player_num ] . flags & PLAYER_FLAGS_CLOAKED ) ) {
ai_static * aip = & obj - > ctype . ai_info ;
ai_local * ailp = & Ai_local_info [ obj - Objects ] ;
switch ( aip - > GOAL_STATE ) {
case AIS_NONE :
case AIS_REST :
case AIS_SRCH :
case AIS_LOCK :
aip - > GOAL_STATE = AIS_FIRE ;
if ( ailp - > player_awareness_type < = PA_NEARBY_ROBOT_FIRED ) {
ailp - > player_awareness_type = PA_NEARBY_ROBOT_FIRED ;
ailp - > player_awareness_time = PLAYER_AWARENESS_INITIAL_TIME ;
}
break ;
}
} else if ( dot > = F1_0 / 2 ) {
ai_static * aip = & obj - > ctype . ai_info ;
switch ( aip - > GOAL_STATE ) {
case AIS_NONE :
case AIS_REST :
case AIS_SRCH :
aip - > GOAL_STATE = AIS_LOCK ;
break ;
}
}
}
}
// --------------------------------------------------------------------------------------------------------------------
// If a hiding robot gets bumped or hit, he decides to find another hiding place.
void do_ai_robot_hit ( object * objp , int type )
{
if ( objp - > control_type = = CT_AI ) {
if ( ( type = = PA_WEAPON_ROBOT_COLLISION ) | | ( type = = PA_PLAYER_COLLISION ) )
switch ( objp - > ctype . ai_info . behavior ) {
case AIB_STILL :
{
int r ;
// Attack robots (eg, green guy) shouldn't have behavior = still.
Assert ( Robot_info [ objp - > id ] . attack_type = = 0 ) ;
r = d_rand ( ) ;
// 1/8 time, charge player, 1/4 time create path, rest of time, do nothing
if ( r < 4096 ) {
// -- mprintf((0, "Still guy switching to Station, creating path to player."));
create_path_to_player ( objp , 10 , 1 ) ;
objp - > ctype . ai_info . behavior = AIB_STATION ;
objp - > ctype . ai_info . hide_segment = objp - > segnum ;
Ai_local_info [ objp - Objects ] . mode = AIM_CHASE_OBJECT ;
} else if ( r < 4096 + 8192 ) {
// -- mprintf((0, "Still guy creating n segment path."));
create_n_segment_path ( objp , d_rand ( ) / 8192 + 2 , - 1 ) ;
Ai_local_info [ objp - Objects ] . mode = AIM_FOLLOW_PATH ;
}
break ;
}
}
}
}
# ifndef NDEBUG
int Do_ai_flag = 1 ;
int Cvv_test = 0 ;
int Cvv_last_time [ MAX_OBJECTS ] ;
int Gun_point_hack = 0 ;
# endif
int Robot_sound_volume = DEFAULT_ROBOT_SOUND_VOLUME ;
// --------------------------------------------------------------------------------------------------------------------
// Note: This function could be optimized. Surely player_is_visible_from_object would benefit from the
// information of a normalized vec_to_player.
// Return player visibility:
// 0 not visible
// 1 visible, but robot not looking at player (ie, on an unobstructed vector)
// 2 visible and in robot's field of view
// -1 player is cloaked
// If the player is cloaked, set vec_to_player based on time player cloaked and last uncloaked position.
// Updates ailp->previous_visibility if player is not cloaked, in which case the previous visibility is left unchanged
// and is copied to player_visibility
void compute_vis_and_vec ( object * objp , vms_vector * pos , ai_local * ailp , vms_vector * vec_to_player , int * player_visibility , robot_info * robptr , int * flag )
{
if ( ! * flag ) {
if ( Players [ Player_num ] . flags & PLAYER_FLAGS_CLOAKED ) {
fix delta_time , dist ;
int cloak_index = ( objp - Objects ) % MAX_AI_CLOAK_INFO ;
delta_time = GameTime - Ai_cloak_info [ cloak_index ] . last_time ;
if ( delta_time > F1_0 * 2 ) {
vms_vector randvec ;
Ai_cloak_info [ cloak_index ] . last_time = GameTime ;
make_random_vector ( & randvec ) ;
vm_vec_scale_add2 ( & Ai_cloak_info [ cloak_index ] . last_position , & randvec , 8 * delta_time ) ;
}
dist = vm_vec_normalized_dir_quick ( vec_to_player , & Ai_cloak_info [ cloak_index ] . last_position , pos ) ;
* player_visibility = player_is_visible_from_object ( objp , pos , robptr - > field_of_view [ Difficulty_level ] , vec_to_player ) ;
// *player_visibility = 2;
if ( ( ailp - > next_misc_sound_time < GameTime ) & & ( ( ailp - > next_fire < F1_0 ) | | ( ailp - > next_fire2 < F1_0 ) ) & & ( dist < F1_0 * 20 ) ) {
// mprintf((0, "ANGRY! "));
ailp - > next_misc_sound_time = GameTime + ( d_rand ( ) + F1_0 ) * ( 7 - Difficulty_level ) / 1 ;
digi_link_sound_to_pos ( robptr - > see_sound , objp - > segnum , 0 , pos , 0 , Robot_sound_volume ) ;
}
} else {
// Compute expensive stuff -- vec_to_player and player_visibility
vm_vec_normalized_dir_quick ( vec_to_player , & Believed_player_pos , pos ) ;
if ( ( vec_to_player - > x = = 0 ) & & ( vec_to_player - > y = = 0 ) & & ( vec_to_player - > z = = 0 ) ) {
// -- mprintf((0, "Warning: Player and robot at exactly the same location.\n"));
vec_to_player - > x = F1_0 ;
}
* player_visibility = player_is_visible_from_object ( objp , pos , robptr - > field_of_view [ Difficulty_level ] , vec_to_player ) ;
// This horrible code added by MK in desperation on 12/13/94 to make robots wake up as soon as they
// see you without killing frame rate.
{
ai_static * aip = & objp - > ctype . ai_info ;
if ( ( * player_visibility = = 2 ) & & ( ailp - > previous_visibility ! = 2 ) )
if ( ( aip - > GOAL_STATE = = AIS_REST ) | | ( aip - > CURRENT_STATE = = AIS_REST ) ) {
aip - > GOAL_STATE = AIS_FIRE ;
aip - > CURRENT_STATE = AIS_FIRE ;
}
}
if ( ( ailp - > previous_visibility ! = * player_visibility ) & & ( * player_visibility = = 2 ) ) {
if ( ailp - > previous_visibility = = 0 ) {
if ( ailp - > time_player_seen + F1_0 / 2 < GameTime ) {
// -- mprintf((0, "SEE! "));
// -- if (Player_exploded)
// -- digi_link_sound_to_pos( robptr->taunt_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume);
// -- else
digi_link_sound_to_pos ( robptr - > see_sound , objp - > segnum , 0 , pos , 0 , Robot_sound_volume ) ;
ailp - > time_player_sound_attacked = GameTime ;
ailp - > next_misc_sound_time = GameTime + F1_0 + d_rand ( ) * 4 ;
}
} else if ( ailp - > time_player_sound_attacked + F1_0 / 4 < GameTime ) {
// -- mprintf((0, "ANGRY! "));
// -- if (Player_exploded)
// -- digi_link_sound_to_pos( robptr->taunt_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume);
// -- else
digi_link_sound_to_pos ( robptr - > attack_sound , objp - > segnum , 0 , pos , 0 , Robot_sound_volume ) ;
ailp - > time_player_sound_attacked = GameTime ;
}
}
if ( ( * player_visibility = = 2 ) & & ( ailp - > next_misc_sound_time < GameTime ) ) {
// -- mprintf((0, "ATTACK! "));
ailp - > next_misc_sound_time = GameTime + ( d_rand ( ) + F1_0 ) * ( 7 - Difficulty_level ) / 2 ;
// -- if (Player_exploded)
// -- digi_link_sound_to_pos( robptr->taunt_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume);
// -- else
digi_link_sound_to_pos ( robptr - > attack_sound , objp - > segnum , 0 , pos , 0 , Robot_sound_volume ) ;
}
ailp - > previous_visibility = * player_visibility ;
}
* flag = 1 ;
// @mk, 09/21/95: If player view is not obstructed and awareness is at least as high as a nearby collision,
// act is if robot is looking at player.
if ( ailp - > player_awareness_type > = PA_NEARBY_ROBOT_FIRED )
if ( * player_visibility = = 1 )
* player_visibility = 2 ;
if ( * player_visibility ) {
ailp - > time_player_seen = GameTime ;
}
}
}
// --------------------------------------------------------------------------------------------------------------------
// Move the object objp to a spot in which it doesn't intersect a wall.
// It might mean moving it outside its current segment.
void move_object_to_legal_spot ( object * objp )
{
vms_vector original_pos = objp - > pos ;
int i ;
segment * segp = & Segments [ objp - > segnum ] ;
for ( i = 0 ; i < MAX_SIDES_PER_SEGMENT ; i + + ) {
if ( WALL_IS_DOORWAY ( segp , i ) & WID_FLY_FLAG ) {
vms_vector segment_center , goal_dir ;
fix dist_to_center ; // Value not used so far.
compute_segment_center ( & segment_center , & Segments [ segp - > children [ i ] ] ) ;
vm_vec_sub ( & goal_dir , & segment_center , & objp - > pos ) ;
dist_to_center = vm_vec_normalize_quick ( & goal_dir ) ;
vm_vec_scale ( & goal_dir , objp - > size ) ;
vm_vec_add2 ( & objp - > pos , & goal_dir ) ;
if ( ! object_intersects_wall ( objp ) ) {
int new_segnum = find_point_seg ( & objp - > pos , objp - > segnum ) ;
if ( new_segnum ! = - 1 ) {
obj_relink ( objp - Objects , new_segnum ) ;
return ;
}
} else
objp - > pos = original_pos ;
}
}
if ( Robot_info [ objp - > id ] . boss_flag ) {
Int3 ( ) ; // Note: Boss is poking outside mine. Will try to resolve.
teleport_boss ( objp ) ;
} else {
mprintf ( ( 0 , " Note: Killing robot #%i because he's badly stuck outside the mine. \n " , objp - Objects ) ) ;
apply_damage_to_robot ( objp , objp - > shields * 2 , objp - Objects ) ;
}
}
// --------------------------------------------------------------------------------------------------------------------
// Move object one object radii from current position towards segment center.
// If segment center is nearer than 2 radii, move it to center.
void move_towards_segment_center ( object * objp )
{
int segnum = objp - > segnum ;
fix dist_to_center ;
vms_vector segment_center , goal_dir ;
compute_segment_center ( & segment_center , & Segments [ segnum ] ) ;
vm_vec_sub ( & goal_dir , & segment_center , & objp - > pos ) ;
dist_to_center = vm_vec_normalize_quick ( & goal_dir ) ;
if ( dist_to_center < objp - > size ) {
// Center is nearer than the distance we want to move, so move to center.
objp - > pos = segment_center ;
mprintf ( ( 0 , " Object #%i moved to center of segment #%i (%7.3f %7.3f %7.3f) \n " , objp - Objects , objp - > segnum , f2fl ( objp - > pos . x ) , f2fl ( objp - > pos . y ) , f2fl ( objp - > pos . z ) ) ) ;
if ( object_intersects_wall ( objp ) ) {
mprintf ( ( 0 , " Object #%i still illegal, trying trickier move. \n " ) ) ;
move_object_to_legal_spot ( objp ) ;
}
} else {
int new_segnum ;
// Move one radii towards center.
vm_vec_scale ( & goal_dir , objp - > size ) ;
vm_vec_add2 ( & objp - > pos , & goal_dir ) ;
new_segnum = find_point_seg ( & objp - > pos , objp - > segnum ) ;
if ( new_segnum = = - 1 ) {
objp - > pos = segment_center ;
move_object_to_legal_spot ( objp ) ;
}
// -- mprintf((0, "Obj %i moved twrds seg %i (%6.2f %6.2f %6.2f), dists: [%6.2f %6.2f]\n", objp-Objects, objp->segnum, f2fl(objp->pos.x), f2fl(objp->pos.y), f2fl(objp->pos.z), f2fl(vm_vec_dist_quick(&objp->pos, &segment_center)), f2fl(vm_vec_dist_quick(&objp->pos, &segment_center))));
}
}
extern int Buddy_objnum ;
//int Buddy_got_stuck = 0;
// -----------------------------------------------------------------------------------------------------------
// Return true if door can be flown through by a suitable type robot.
// Brains, avoid robots, companions can open doors.
// objp == NULL means treat as buddy.
int ai_door_is_openable ( object * objp , segment * segp , int sidenum )
{
int wall_num ;
wall * wallp ;
if ( ! IS_CHILD ( segp - > children [ sidenum ] ) )
return 0 ; //trap -2 (exit side)
wall_num = segp - > sides [ sidenum ] . wall_num ;
if ( wall_num = = - 1 ) //if there's no door at all...
return 0 ; //..then say it can't be opened
// The mighty console object can open all doors (for purposes of determining paths).
if ( objp = = ConsoleObject ) {
if ( Walls [ wall_num ] . type = = WALL_DOOR )
return 1 ;
}
wallp = & Walls [ wall_num ] ;
if ( ( objp = = NULL ) | | ( Robot_info [ objp - > id ] . companion = = 1 ) ) {
int ailp_mode ;
if ( wallp - > flags & WALL_BUDDY_PROOF ) {
if ( ( wallp - > type = = WALL_DOOR ) & & ( wallp - > state = = WALL_DOOR_CLOSED ) )
return 0 ;
else if ( wallp - > type = = WALL_CLOSED )
return 0 ;
else if ( ( wallp - > type = = WALL_ILLUSION ) & & ! ( wallp - > flags & WALL_ILLUSION_OFF ) )
return 0 ;
}
if ( wallp - > keys ! = KEY_NONE ) {
if ( wallp - > keys = = KEY_BLUE )
return ( Players [ Player_num ] . flags & PLAYER_FLAGS_BLUE_KEY ) ;
else if ( wallp - > keys = = KEY_GOLD )
return ( Players [ Player_num ] . flags & PLAYER_FLAGS_GOLD_KEY ) ;
else if ( wallp - > keys = = KEY_RED )
return ( Players [ Player_num ] . flags & PLAYER_FLAGS_RED_KEY ) ;
}
if ( ( wallp - > type ! = WALL_DOOR ) & & ( wallp - > type ! = WALL_CLOSED ) )
return 1 ;
// If Buddy is returning to player, don't let him think he can get through triggered doors.
// It's only valid to think that if the player is going to get him through. But if he's
// going to the player, the player is probably on the opposite side.
if ( objp = = NULL )
ailp_mode = Ai_local_info [ Buddy_objnum ] . mode ;
else
ailp_mode = Ai_local_info [ objp - Objects ] . mode ;
// -- if (Buddy_got_stuck) {
if ( ailp_mode = = AIM_GOTO_PLAYER ) {
if ( ( wallp - > type = = WALL_BLASTABLE ) & & ( wallp - > state ! = WALL_BLASTED ) )
return 0 ;
if ( wallp - > type = = WALL_CLOSED )
return 0 ;
if ( wallp - > type = = WALL_DOOR ) {
if ( ( wallp - > flags & WALL_DOOR_LOCKED ) & & ( wallp - > state = = WALL_DOOR_CLOSED ) )
return 0 ;
}
}
// -- }
if ( ( ailp_mode ! = AIM_GOTO_PLAYER ) & & ( wallp - > controlling_trigger ! = - 1 ) ) {
int clip_num = wallp - > clip_num ;
if ( clip_num = = - 1 )
return 1 ;
else if ( WallAnims [ clip_num ] . flags & WCF_HIDDEN ) {
if ( wallp - > state = = WALL_DOOR_CLOSED )
return 0 ;
else
return 1 ;
} else
return 1 ;
}
if ( wallp - > type = = WALL_DOOR ) {
if ( wallp - > type = = WALL_BLASTABLE )
return 1 ;
else {
int clip_num = wallp - > clip_num ;
if ( clip_num = = - 1 )
return 1 ;
// Buddy allowed to go through secret doors to get to player.
else if ( ( ailp_mode ! = AIM_GOTO_PLAYER ) & & ( WallAnims [ clip_num ] . flags & WCF_HIDDEN ) ) {
if ( wallp - > state = = WALL_DOOR_CLOSED )
return 0 ;
else
return 1 ;
} else
return 1 ;
}
}
} else if ( ( objp - > id = = ROBOT_BRAIN ) | | ( objp - > ctype . ai_info . behavior = = AIB_RUN_FROM ) | | ( objp - > ctype . ai_info . behavior = = AIB_SNIPE ) ) {
if ( wall_num ! = - 1 )
{
if ( ( wallp - > type = = WALL_DOOR ) & & ( wallp - > keys = = KEY_NONE ) & & ! ( wallp - > flags & WALL_DOOR_LOCKED ) )
return 1 ;
else if ( wallp - > keys ! = KEY_NONE ) { // Allow bots to open doors to which player has keys.
if ( wallp - > keys & Players [ Player_num ] . flags )
return 1 ;
}
}
}
return 0 ;
}
// -----------------------------------------------------------------------------------------------------------
// Return side of openable door in segment, if any. If none, return -1.
int openable_doors_in_segment ( int segnum )
{
int i ;
if ( ( segnum < 0 ) | | ( segnum > Highest_segment_index ) )
return - 1 ;
for ( i = 0 ; i < MAX_SIDES_PER_SEGMENT ; i + + ) {
if ( Segments [ segnum ] . sides [ i ] . wall_num ! = - 1 ) {
int wall_num = Segments [ segnum ] . sides [ i ] . wall_num ;
if ( ( Walls [ wall_num ] . type = = WALL_DOOR ) & & ( Walls [ wall_num ] . keys = = KEY_NONE ) & & ( Walls [ wall_num ] . state = = WALL_DOOR_CLOSED ) & & ! ( Walls [ wall_num ] . flags & WALL_DOOR_LOCKED ) & & ! ( WallAnims [ Walls [ wall_num ] . clip_num ] . flags & WCF_HIDDEN ) )
return i ;
}
}
return - 1 ;
}
// -- // --------------------------------------------------------------------------------------------------------------------
// -- // Return true if a special object (player or control center) is in this segment.
// -- int special_object_in_seg(int segnum)
// -- {
// -- int objnum;
// --
// -- objnum = Segments[segnum].objects;
// --
// -- while (objnum != -1) {
// -- if ((Objects[objnum].type == OBJ_PLAYER) || (Objects[objnum].type == OBJ_CNTRLCEN)) {
// -- mprintf((0, "Special object of type %i in segment %i\n", Objects[objnum].type, segnum));
// -- return 1;
// -- } else
// -- objnum = Objects[objnum].next;
// -- }
// --
// -- return 0;
// -- }
// -- // --------------------------------------------------------------------------------------------------------------------
// -- // Randomly select a segment attached to *segp, reachable by flying.
// -- int get_random_child(int segnum)
// -- {
// -- int sidenum;
// -- segment *segp = &Segments[segnum];
// --
// -- sidenum = (rand() * 6) >> 15;
// --
// -- while (!(WALL_IS_DOORWAY(segp, sidenum) & WID_FLY_FLAG))
// -- sidenum = (rand() * 6) >> 15;
// --
// -- segnum = segp->children[sidenum];
// --
// -- return segnum;
// -- }
// --------------------------------------------------------------------------------------------------------------------
// Return true if placing an object of size size at pos *pos intersects a (player or robot or control center) in segment *segp.
int check_object_object_intersection ( vms_vector * pos , fix size , segment * segp )
{
int curobjnum ;
// If this would intersect with another object (only check those in this segment), then try to move.
curobjnum = segp - > objects ;
while ( curobjnum ! = - 1 ) {
object * curobjp = & Objects [ curobjnum ] ;
if ( ( curobjp - > type = = OBJ_PLAYER ) | | ( curobjp - > type = = OBJ_ROBOT ) | | ( curobjp - > type = = OBJ_CNTRLCEN ) ) {
if ( vm_vec_dist_quick ( pos , & curobjp - > pos ) < size + curobjp - > size )
return 1 ;
}
curobjnum = curobjp - > next ;
}
return 0 ;
}
// --------------------------------------------------------------------------------------------------------------------
// Return objnum if object created, else return -1.
// If pos == NULL, pick random spot in segment.
int create_gated_robot ( int segnum , int object_id , vms_vector * pos )
{
int objnum ;
object * objp ;
segment * segp = & Segments [ segnum ] ;
vms_vector object_pos ;
robot_info * robptr = & Robot_info [ object_id ] ;
int i , count = 0 ;
fix objsize = Polygon_models [ robptr - > model_num ] . rad ;
int default_behavior ;
if ( GameTime - Last_gate_time < Gate_interval )
return - 1 ;
for ( i = 0 ; i < = Highest_object_index ; i + + )
if ( Objects [ i ] . type = = OBJ_ROBOT )
if ( Objects [ i ] . matcen_creator = = BOSS_GATE_MATCEN_NUM )
count + + ;
if ( count > 2 * Difficulty_level + 6 ) {
//mprintf((0, "Cannot gate in a robot until you kill one.\n"));
Last_gate_time = GameTime - 3 * Gate_interval / 4 ;
return - 1 ;
}
compute_segment_center ( & object_pos , segp ) ;
if ( pos = = NULL )
pick_random_point_in_seg ( & object_pos , segp - Segments ) ;
else
object_pos = * pos ;
// See if legal to place object here. If not, move about in segment and try again.
if ( check_object_object_intersection ( & object_pos , objsize , segp ) ) {
//mprintf((0, "Can't get in because object collides with something.\n"));
Last_gate_time = GameTime - 3 * Gate_interval / 4 ;
return - 1 ;
}
objnum = obj_create ( OBJ_ROBOT , object_id , segnum , & object_pos , & vmd_identity_matrix , objsize , CT_AI , MT_PHYSICS , RT_POLYOBJ ) ;
if ( objnum < 0 ) {
// mprintf((1, "Can't get object to gate in robot. Not gating in.\n"));
Last_gate_time = GameTime - 3 * Gate_interval / 4 ;
return - 1 ;
}
//mprintf((0, "Gating in object %i in segment %i\n", objnum, segp-Segments));
Objects [ objnum ] . lifeleft = F1_0 * 30 ; // Gated in robots only live 30 seconds.
# ifdef NETWORK
Net_create_objnums [ 0 ] = objnum ; // A convenient global to get objnum back to caller for multiplayer
# endif
objp = & Objects [ objnum ] ;
//Set polygon-object-specific data
objp - > rtype . pobj_info . model_num = robptr - > model_num ;
objp - > rtype . pobj_info . subobj_flags = 0 ;
//set Physics info
objp - > mtype . phys_info . mass = robptr - > mass ;
objp - > mtype . phys_info . drag = robptr - > drag ;
objp - > mtype . phys_info . flags | = ( PF_LEVELLING ) ;
objp - > shields = robptr - > strength ;
objp - > matcen_creator = BOSS_GATE_MATCEN_NUM ; // flag this robot as having been created by the boss.
default_behavior = Robot_info [ objp - > id ] . behavior ;
init_ai_object ( objp - Objects , default_behavior , - 1 ) ; // Note, -1 = segment this robot goes to to hide, should probably be something useful
object_create_explosion ( segnum , & object_pos , i2f ( 10 ) , VCLIP_MORPHING_ROBOT ) ;
digi_link_sound_to_pos ( Vclip [ VCLIP_MORPHING_ROBOT ] . sound_num , segnum , 0 , & object_pos , 0 , F1_0 ) ;
morph_start ( objp ) ;
Last_gate_time = GameTime ;
Players [ Player_num ] . num_robots_level + + ;
Players [ Player_num ] . num_robots_total + + ;
return objp - Objects ;
}
# define MAX_SPEW_BOT 3
int Spew_bots [ NUM_D2_BOSSES ] [ MAX_SPEW_BOT ] = {
{ 38 , 40 , - 1 } ,
{ 37 , - 1 , - 1 } ,
{ 43 , 57 , - 1 } ,
{ 26 , 27 , 58 } ,
{ 59 , 58 , 54 } ,
{ 60 , 61 , 54 } ,
{ 69 , 29 , 24 } ,
{ 72 , 60 , 73 }
} ;
int Max_spew_bots [ NUM_D2_BOSSES ] = { 2 , 1 , 2 , 3 , 3 , 3 , 3 , 3 } ;
// ----------------------------------------------------------------------------------------------------------
// objp points at a boss. He was presumably just hit and he's supposed to create a bot at the hit location *pos.
int boss_spew_robot ( object * objp , vms_vector * pos )
{
int objnum , segnum ;
int boss_index ;
boss_index = Robot_info [ objp - > id ] . boss_flag - BOSS_D2 ;
Assert ( ( boss_index > = 0 ) & & ( boss_index < NUM_D2_BOSSES ) ) ;
segnum = find_point_seg ( pos , objp - > segnum ) ;
if ( segnum = = - 1 ) {
mprintf ( ( 0 , " Tried to spew a bot outside the mine! Aborting! \n " ) ) ;
return - 1 ;
}
objnum = create_gated_robot ( segnum , Spew_bots [ boss_index ] [ ( Max_spew_bots [ boss_index ] * d_rand ( ) ) > > 15 ] , pos ) ;
// Make spewed robot come tumbling out as if blasted by a flash missile.
if ( objnum ! = - 1 ) {
object * newobjp = & Objects [ objnum ] ;
int force_val ;
force_val = F1_0 / FrameTime ;
if ( force_val ) {
newobjp - > ctype . ai_info . SKIP_AI_COUNT + = force_val ;
newobjp - > mtype . phys_info . rotthrust . x = ( ( d_rand ( ) - 16384 ) * force_val ) / 16 ;
newobjp - > mtype . phys_info . rotthrust . y = ( ( d_rand ( ) - 16384 ) * force_val ) / 16 ;
newobjp - > mtype . phys_info . rotthrust . z = ( ( d_rand ( ) - 16384 ) * force_val ) / 16 ;
newobjp - > mtype . phys_info . flags | = PF_USES_THRUST ;
// Now, give a big initial velocity to get moving away from boss.
vm_vec_sub ( & newobjp - > mtype . phys_info . velocity , pos , & objp - > pos ) ;
vm_vec_normalize_quick ( & newobjp - > mtype . phys_info . velocity ) ;
vm_vec_scale ( & newobjp - > mtype . phys_info . velocity , F1_0 * 128 ) ;
}
}
return objnum ;
}
// --------------------------------------------------------------------------------------------------------------------
// Call this each time the player starts a new ship.
void init_ai_for_ship ( void )
{
int i ;
for ( i = 0 ; i < MAX_AI_CLOAK_INFO ; i + + ) {
Ai_cloak_info [ i ] . last_time = GameTime ;
Ai_cloak_info [ i ] . last_segment = ConsoleObject - > segnum ;
Ai_cloak_info [ i ] . last_position = ConsoleObject - > pos ;
}
}
// --------------------------------------------------------------------------------------------------------------------
// Make object objp gate in a robot.
// The process of him bringing in a robot takes one second.
// Then a robot appears somewhere near the player.
// Return objnum if robot successfully created, else return -1
int gate_in_robot ( int type , int segnum )
{
if ( segnum < 0 )
segnum = Boss_gate_segs [ ( d_rand ( ) * Num_boss_gate_segs ) > > 15 ] ;
Assert ( ( segnum > = 0 ) & & ( segnum < = Highest_segment_index ) ) ;
return create_gated_robot ( segnum , type , NULL ) ;
}
// --------------------------------------------------------------------------------------------------------------------
int boss_fits_in_seg ( object * boss_objp , int segnum )
{
vms_vector segcenter ;
int boss_objnum = boss_objp - Objects ;
int posnum ;
compute_segment_center ( & segcenter , & Segments [ segnum ] ) ;
for ( posnum = 0 ; posnum < 9 ; posnum + + ) {
if ( posnum > 0 ) {
vms_vector vertex_pos ;
Assert ( ( posnum - 1 > = 0 ) & & ( posnum - 1 < 8 ) ) ;
vertex_pos = Vertices [ Segments [ segnum ] . verts [ posnum - 1 ] ] ;
vm_vec_avg ( & boss_objp - > pos , & vertex_pos , & segcenter ) ;
} else
boss_objp - > pos = segcenter ;
obj_relink ( boss_objnum , segnum ) ;
if ( ! object_intersects_wall ( boss_objp ) )
return 1 ;
}
return 0 ;
}
// --------------------------------------------------------------------------------------------------------------------
void teleport_boss ( object * objp )
{
int rand_segnum , rand_index ;
vms_vector boss_dir ;
Assert ( Num_boss_teleport_segs > 0 ) ;
// Pick a random segment from the list of boss-teleportable-to segments.
rand_index = ( d_rand ( ) * Num_boss_teleport_segs ) > > 15 ;
rand_segnum = Boss_teleport_segs [ rand_index ] ;
Assert ( ( rand_segnum > = 0 ) & & ( rand_segnum < = Highest_segment_index ) ) ;
//mprintf((0, "Frame %i: Boss teleporting to segment #%i, pos = {%8x %8x %8x}.\n", FrameCount, rand_segnum, objp->pos.x, objp->pos.y, objp->pos.z));
# ifdef NETWORK
if ( Game_mode & GM_MULTI )
multi_send_boss_actions ( objp - Objects , 1 , rand_segnum , 0 ) ;
# endif
compute_segment_center ( & objp - > pos , & Segments [ rand_segnum ] ) ;
obj_relink ( objp - Objects , rand_segnum ) ;
Last_teleport_time = GameTime ;
// make boss point right at player
vm_vec_sub ( & boss_dir , & Objects [ Players [ Player_num ] . objnum ] . pos , & objp - > pos ) ;
vm_vector_2_matrix ( & objp - > orient , & boss_dir , NULL , NULL ) ;
digi_link_sound_to_pos ( Vclip [ VCLIP_MORPHING_ROBOT ] . sound_num , rand_segnum , 0 , & objp - > pos , 0 , F1_0 ) ;
digi_kill_sound_linked_to_object ( objp - Objects ) ;
digi_link_sound_to_object2 ( Robot_info [ objp - > id ] . see_sound , objp - Objects , 1 , F1_0 , F1_0 * 512 ) ; // F1_0*512 means play twice as loud
// After a teleport, boss can fire right away.
Ai_local_info [ objp - Objects ] . next_fire = 0 ;
Ai_local_info [ objp - Objects ] . next_fire2 = 0 ;
}
// ----------------------------------------------------------------------
void start_boss_death_sequence ( object * objp )
{
if ( Robot_info [ objp - > id ] . boss_flag ) {
Boss_dying = 1 ;
Boss_dying_start_time = GameTime ;
}
}
// ----------------------------------------------------------------------
// General purpose robot-dies-with-death-roll-and-groan code.
// Return true if object just died.
// scale: F1_0*4 for boss, much smaller for much smaller guys
int do_robot_dying_frame ( object * objp , fix start_time , fix roll_duration , byte * dying_sound_playing , int death_sound , fix expl_scale , fix sound_scale )
{
fix roll_val , temp ;
fix sound_duration ;
if ( ! roll_duration )
roll_duration = F1_0 / 4 ;
roll_val = fixdiv ( GameTime - start_time , roll_duration ) ;
fix_sincos ( fixmul ( roll_val , roll_val ) , & temp , & objp - > mtype . phys_info . rotvel . x ) ;
fix_sincos ( roll_val , & temp , & objp - > mtype . phys_info . rotvel . y ) ;
fix_sincos ( roll_val - F1_0 / 8 , & temp , & objp - > mtype . phys_info . rotvel . z ) ;
objp - > mtype . phys_info . rotvel . x = ( GameTime - start_time ) / 9 ;
objp - > mtype . phys_info . rotvel . y = ( GameTime - start_time ) / 5 ;
objp - > mtype . phys_info . rotvel . z = ( GameTime - start_time ) / 7 ;
if ( digi_sample_rate )
sound_duration = fixdiv ( GameSounds [ digi_xlat_sound ( death_sound ) ] . length , digi_sample_rate ) ;
else
sound_duration = F1_0 ;
if ( start_time + roll_duration - sound_duration < GameTime ) {
if ( ! * dying_sound_playing ) {
mprintf ( ( 0 , " Starting death sound! \n " ) ) ;
* dying_sound_playing = 1 ;
digi_link_sound_to_object2 ( death_sound , objp - Objects , 0 , sound_scale , sound_scale * 256 ) ; // F1_0*512 means play twice as loud
} else if ( d_rand ( ) < FrameTime * 16 )
create_small_fireball_on_object ( objp , ( F1_0 + d_rand ( ) ) * ( 16 * expl_scale / F1_0 ) / 8 , 0 ) ;
} else if ( d_rand ( ) < FrameTime * 8 )
create_small_fireball_on_object ( objp , ( F1_0 / 2 + d_rand ( ) ) * ( 16 * expl_scale / F1_0 ) / 8 , 1 ) ;
if ( start_time + roll_duration < GameTime )
return 1 ;
else
return 0 ;
}
// ----------------------------------------------------------------------
void start_robot_death_sequence ( object * objp )
{
objp - > ctype . ai_info . dying_start_time = GameTime ;
objp - > ctype . ai_info . dying_sound_playing = 0 ;
objp - > ctype . ai_info . SKIP_AI_COUNT = 0 ;
}
// ----------------------------------------------------------------------
void do_boss_dying_frame ( object * objp )
{
int rval ;
rval = do_robot_dying_frame ( objp , Boss_dying_start_time , BOSS_DEATH_DURATION , & Boss_dying_sound_playing , Robot_info [ objp - > id ] . deathroll_sound , F1_0 * 4 , F1_0 * 4 ) ;
if ( rval ) {
do_controlcen_destroyed_stuff ( NULL ) ;
explode_object ( objp , F1_0 / 4 ) ;
digi_link_sound_to_object2 ( SOUND_BADASS_EXPLOSION , objp - Objects , 0 , F2_0 , F1_0 * 512 ) ;
}
}
extern void recreate_thief ( object * objp ) ;
// ----------------------------------------------------------------------
int do_any_robot_dying_frame ( object * objp )
{
if ( objp - > ctype . ai_info . dying_start_time ) {
int rval , death_roll ;
death_roll = Robot_info [ objp - > id ] . death_roll ;
rval = do_robot_dying_frame ( objp , objp - > ctype . ai_info . dying_start_time , min ( death_roll / 2 + 1 , 6 ) * F1_0 , & objp - > ctype . ai_info . dying_sound_playing , Robot_info [ objp - > id ] . deathroll_sound , death_roll * F1_0 / 8 , death_roll * F1_0 / 2 ) ;
if ( rval ) {
explode_object ( objp , F1_0 / 4 ) ;
digi_link_sound_to_object2 ( SOUND_BADASS_EXPLOSION , objp - Objects , 0 , F2_0 , F1_0 * 512 ) ;
if ( ( Current_level_num < 0 ) & & ( Robot_info [ objp - > id ] . thief ) )
recreate_thief ( objp ) ;
}
return 1 ;
}
return 0 ;
}
// --------------------------------------------------------------------------------------------------------------------
// Called for an AI object if it is fairly aware of the player.
// awareness_level is in 0..100. Larger numbers indicate greater awareness (eg, 99 if firing at player).
// In a given frame, might not get called for an object, or might be called more than once.
// The fact that this routine is not called for a given object does not mean that object is not interested in the player.
// Objects are moved by physics, so they can move even if not interested in a player. However, if their velocity or
// orientation is changing, this routine will be called.
// Return value:
// 0 this player IS NOT allowed to move this robot.
// 1 this player IS allowed to move this robot.
int ai_multiplayer_awareness ( object * objp , int awareness_level )
{
int rval = 1 ;
# ifdef NETWORK
if ( Game_mode & GM_MULTI ) {
if ( awareness_level = = 0 )
return 0 ;
rval = multi_can_move_robot ( objp - Objects , awareness_level ) ;
}
# endif
return rval ;
}
# ifndef NDEBUG
fix Prev_boss_shields = - 1 ;
# endif
// --------------------------------------------------------------------------------------------------------------------
// Do special stuff for a boss.
void do_boss_stuff ( object * objp , int player_visibility )
{
int boss_id , boss_index ;
boss_id = Robot_info [ objp - > id ] . boss_flag ;
Assert ( ( boss_id > = BOSS_D2 ) & & ( boss_id < BOSS_D2 + NUM_D2_BOSSES ) ) ;
boss_index = boss_id - BOSS_D2 ;
# ifndef NDEBUG
if ( objp - > shields ! = Prev_boss_shields ) {
mprintf ( ( 0 , " Boss shields = %7.3f, object %i \n " , f2fl ( objp - > shields ) , objp - Objects ) ) ;
Prev_boss_shields = objp - > shields ;
}
# endif
// New code, fixes stupid bug which meant boss never gated in robots if > 32767 seconds played.
if ( Last_teleport_time > GameTime )
Last_teleport_time = GameTime ;
if ( Last_gate_time > GameTime )
Last_gate_time = GameTime ;
// @mk, 10/13/95: Reason:
// Level 4 boss behind locked door. But he's allowed to teleport out of there. So he
// teleports out of there right away, and blasts player right after first door.
if ( ! player_visibility & & ( GameTime - Boss_hit_time > F1_0 * 2 ) )
return ;
if ( ! Boss_dying & & Boss_teleports [ boss_index ] ) {
if ( objp - > ctype . ai_info . CLOAKED = = 1 ) {
Boss_hit_time = GameTime ; // Keep the cloak:teleport process going.
if ( ( GameTime - Boss_cloak_start_time > BOSS_CLOAK_DURATION / 3 ) & & ( Boss_cloak_end_time - GameTime > BOSS_CLOAK_DURATION / 3 ) & & ( GameTime - Last_teleport_time > Boss_teleport_interval ) ) {
if ( ai_multiplayer_awareness ( objp , 98 ) )
teleport_boss ( objp ) ;
} else if ( GameTime - Boss_hit_time > F1_0 * 2 ) {
Last_teleport_time - = Boss_teleport_interval / 4 ;
}
if ( GameTime > Boss_cloak_end_time | | GameTime < Boss_cloak_start_time )
objp - > ctype . ai_info . CLOAKED = 0 ;
} else if ( ( GameTime - Boss_cloak_end_time > Boss_cloak_interval ) | | ( GameTime - Boss_cloak_end_time < - Boss_cloak_duration ) ) {
if ( ai_multiplayer_awareness ( objp , 95 ) ) {
Boss_cloak_start_time = GameTime ;
Boss_cloak_end_time = GameTime + Boss_cloak_duration ;
objp - > ctype . ai_info . CLOAKED = 1 ;
# ifdef NETWORK
if ( Game_mode & GM_MULTI )
multi_send_boss_actions ( objp - Objects , 2 , 0 , 0 ) ;
# endif
}
}
}
}
# define BOSS_TO_PLAYER_GATE_DISTANCE (F1_0*200)
// -- Obsolete D1 code -- // --------------------------------------------------------------------------------------------------------------------
// -- Obsolete D1 code -- // Do special stuff for a boss.
// -- Obsolete D1 code -- void do_super_boss_stuff(object *objp, fix dist_to_player, int player_visibility)
// -- Obsolete D1 code -- {
// -- Obsolete D1 code -- static int eclip_state = 0;
// -- Obsolete D1 code --
// -- Obsolete D1 code -- do_boss_stuff(objp, player_visibility);
// -- Obsolete D1 code --
// -- Obsolete D1 code -- // Only master player can cause gating to occur.
// -- Obsolete D1 code -- if ((Game_mode & GM_MULTI) && !network_i_am_master())
// -- Obsolete D1 code -- return;
// -- Obsolete D1 code --
// -- Obsolete D1 code -- if ((dist_to_player < BOSS_TO_PLAYER_GATE_DISTANCE) || player_visibility || (Game_mode & GM_MULTI)) {
// -- Obsolete D1 code -- if (GameTime - Last_gate_time > Gate_interval/2) {
// -- Obsolete D1 code -- restart_effect(BOSS_ECLIP_NUM);
// -- Obsolete D1 code -- if (eclip_state == 0) {
// -- Obsolete D1 code -- multi_send_boss_actions(objp-Objects, 4, 0, 0);
// -- Obsolete D1 code -- eclip_state = 1;
// -- Obsolete D1 code -- }
// -- Obsolete D1 code -- }
// -- Obsolete D1 code -- else {
// -- Obsolete D1 code -- stop_effect(BOSS_ECLIP_NUM);
// -- Obsolete D1 code -- if (eclip_state == 1) {
// -- Obsolete D1 code -- multi_send_boss_actions(objp-Objects, 5, 0, 0);
// -- Obsolete D1 code -- eclip_state = 0;
// -- Obsolete D1 code -- }
// -- Obsolete D1 code -- }
// -- Obsolete D1 code --
// -- Obsolete D1 code -- if (GameTime - Last_gate_time > Gate_interval)
// -- Obsolete D1 code -- if (ai_multiplayer_awareness(objp, 99)) {
// -- Obsolete D1 code -- int rtval;
// -- Obsolete D1 code -- int randtype = (d_rand() * MAX_GATE_INDEX) >> 15;
// -- Obsolete D1 code --
// -- Obsolete D1 code -- Assert(randtype < MAX_GATE_INDEX);
// -- Obsolete D1 code -- randtype = Super_boss_gate_list[randtype];
// -- Obsolete D1 code -- Assert(randtype < N_robot_types);
// -- Obsolete D1 code --
// -- Obsolete D1 code -- rtval = gate_in_robot(randtype, -1);
// -- Obsolete D1 code -- if ((rtval != -1) && (Game_mode & GM_MULTI))
// -- Obsolete D1 code -- {
// -- Obsolete D1 code -- multi_send_boss_actions(objp-Objects, 3, randtype, Net_create_objnums[0]);
// -- Obsolete D1 code -- map_objnum_local_to_local(Net_create_objnums[0]);
// -- Obsolete D1 code -- }
// -- Obsolete D1 code -- }
// -- Obsolete D1 code -- }
// -- Obsolete D1 code -- }
//int multi_can_move_robot(object *objp, int awareness_level)
//{
// return 0;
//}
void ai_multi_send_robot_position ( int objnum , int force )
{
# ifdef NETWORK
if ( Game_mode & GM_MULTI )
{
if ( force ! = - 1 )
multi_send_robot_position ( objnum , 1 ) ;
else
multi_send_robot_position ( objnum , 0 ) ;
}
# endif
return ;
}
// --------------------------------------------------------------------------------------------------------------------
// Returns true if this object should be allowed to fire at the player.
int maybe_ai_do_actual_firing_stuff ( object * obj , ai_static * aip )
{
# ifdef NETWORK
if ( Game_mode & GM_MULTI )
if ( ( aip - > GOAL_STATE ! = AIS_FLIN ) & & ( obj - > id ! = ROBOT_BRAIN ) )
if ( aip - > CURRENT_STATE = = AIS_FIRE )
return 1 ;
# endif
return 0 ;
}
vms_vector Last_fired_upon_player_pos ;
// --------------------------------------------------------------------------------------------------------------------
// If fire_anyway, fire even if player is not visible. We're firing near where we believe him to be. Perhaps he's
// lurking behind a corner.
void ai_do_actual_firing_stuff ( object * obj , ai_static * aip , ai_local * ailp , robot_info * robptr , vms_vector * vec_to_player , fix dist_to_player , vms_vector * gun_point , int player_visibility , int object_animates , int gun_num )
{
fix dot ;
if ( ( player_visibility = = 2 ) | | ( Dist_to_last_fired_upon_player_pos < FIRE_AT_NEARBY_PLAYER_THRESHOLD ) ) {
vms_vector fire_pos ;
fire_pos = Believed_player_pos ;
// Hack: If visibility not == 2, we're here because we're firing at a nearby player.
// So, fire at Last_fired_upon_player_pos instead of the player position.
if ( ! robptr - > attack_type & & ( player_visibility ! = 2 ) )
fire_pos = Last_fired_upon_player_pos ;
// Changed by mk, 01/04/95, onearm would take about 9 seconds until he can fire at you.
// Above comment corrected. Date changed from 1994, to 1995. Should fix some very subtle bugs, as well as not cause me to wonder, in the future, why I was writing AI code for onearm ten months before he existed.
if ( ! object_animates | | ready_to_fire ( robptr , ailp ) ) {
dot = vm_vec_dot ( & obj - > orient . fvec , vec_to_player ) ;
if ( ( dot > = 7 * F1_0 / 8 ) | | ( ( dot > F1_0 / 4 ) & & robptr - > boss_flag ) ) {
if ( gun_num < Robot_info [ obj - > id ] . n_guns ) {
if ( robptr - > attack_type = = 1 ) {
if ( ! Player_exploded & & ( dist_to_player < obj - > size + ConsoleObject - > size + F1_0 * 2 ) ) { // robptr->circle_distance[Difficulty_level] + ConsoleObject->size) {
if ( ! ai_multiplayer_awareness ( obj , ROBOT_FIRE_AGITATION - 2 ) )
return ;
do_ai_robot_hit_attack ( obj , ConsoleObject , & obj - > pos ) ;
} else {
// mprintf((0, "Green won't fire: Too far: dist = %7.3f, threshold = %7.3f\n", f2fl(dist_to_player), f2fl(obj->size + ConsoleObject->size + F1_0*2)));
return ;
}
} else {
if ( ( gun_point - > x = = 0 ) & & ( gun_point - > y = = 0 ) & & ( gun_point - > z = = 0 ) ) {
; //mprintf((0, "Would like to fire gun, but gun not selected.\n"));
} else {
if ( ! ai_multiplayer_awareness ( obj , ROBOT_FIRE_AGITATION ) )
return ;
// New, multi-weapon-type system, 06/05/95 (life is slipping away...)
if ( gun_num ! = 0 ) {
if ( ailp - > next_fire < = 0 ) {
ai_fire_laser_at_player ( obj , gun_point , gun_num , & fire_pos ) ;
Last_fired_upon_player_pos = fire_pos ;
}
if ( ( ailp - > next_fire2 < = 0 ) & & ( robptr - > weapon_type2 ! = - 1 ) ) {
calc_gun_point ( gun_point , obj , 0 ) ;
ai_fire_laser_at_player ( obj , gun_point , 0 , & fire_pos ) ;
Last_fired_upon_player_pos = fire_pos ;
}
} else if ( ailp - > next_fire < = 0 ) {
ai_fire_laser_at_player ( obj , gun_point , gun_num , & fire_pos ) ;
Last_fired_upon_player_pos = fire_pos ;
}
}
}
// Wants to fire, so should go into chase mode, probably.
if ( ( aip - > behavior ! = AIB_RUN_FROM )
& & ( aip - > behavior ! = AIB_STILL )
& & ( aip - > behavior ! = AIB_SNIPE )
& & ( aip - > behavior ! = AIB_FOLLOW )
& & ( ! robptr - > attack_type )
& & ( ( ailp - > mode = = AIM_FOLLOW_PATH ) | | ( ailp - > mode = = AIM_STILL ) ) )
ailp - > mode = AIM_CHASE_OBJECT ;
}
aip - > GOAL_STATE = AIS_RECO ;
ailp - > goal_state [ aip - > CURRENT_GUN ] = AIS_RECO ;
// Switch to next gun for next fire. If has 2 gun types, select gun #1, if exists.
aip - > CURRENT_GUN + + ;
if ( aip - > CURRENT_GUN > = Robot_info [ obj - > id ] . n_guns )
{
if ( ( Robot_info [ obj - > id ] . n_guns = = 1 ) | | ( Robot_info [ obj - > id ] . weapon_type2 = = - 1 ) )
aip - > CURRENT_GUN = 0 ;
else
aip - > CURRENT_GUN = 1 ;
}
}
}
} else if ( ( ( ! robptr - > attack_type ) & & ( Weapon_info [ Robot_info [ obj - > id ] . weapon_type ] . homing_flag = = 1 ) ) | | ( ( ( Robot_info [ obj - > id ] . weapon_type2 ! = - 1 ) & & ( Weapon_info [ Robot_info [ obj - > id ] . weapon_type2 ] . homing_flag = = 1 ) ) ) ) {
// Robots which fire homing weapons might fire even if they don't have a bead on the player.
if ( ( ( ! object_animates ) | | ( ailp - > achieved_state [ aip - > CURRENT_GUN ] = = AIS_FIRE ) )
& & ( ( ( ailp - > next_fire < = 0 ) & & ( aip - > CURRENT_GUN ! = 0 ) ) | | ( ( ailp - > next_fire2 < = 0 ) & & ( aip - > CURRENT_GUN = = 0 ) ) )
& & ( vm_vec_dist_quick ( & Hit_pos , & obj - > pos ) > F1_0 * 40 ) ) {
if ( ! ai_multiplayer_awareness ( obj , ROBOT_FIRE_AGITATION ) )
return ;
ai_fire_laser_at_player ( obj , gun_point , gun_num , & Believed_player_pos ) ;
aip - > GOAL_STATE = AIS_RECO ;
ailp - > goal_state [ aip - > CURRENT_GUN ] = AIS_RECO ;
// Switch to next gun for next fire.
aip - > CURRENT_GUN + + ;
if ( aip - > CURRENT_GUN > = Robot_info [ obj - > id ] . n_guns )
aip - > CURRENT_GUN = 0 ;
} else {
// Switch to next gun for next fire.
aip - > CURRENT_GUN + + ;
if ( aip - > CURRENT_GUN > = Robot_info [ obj - > id ] . n_guns )
aip - > CURRENT_GUN = 0 ;
}
} else {
// ---------------------------------------------------------------
vms_vector vec_to_last_pos ;
if ( d_rand ( ) / 2 < fixmul ( FrameTime , ( Difficulty_level < < 12 ) + 0x4000 ) ) {
if ( ( ! object_animates | | ready_to_fire ( robptr , ailp ) ) & & ( Dist_to_last_fired_upon_player_pos < FIRE_AT_NEARBY_PLAYER_THRESHOLD ) ) {
vm_vec_normalized_dir_quick ( & vec_to_last_pos , & Believed_player_pos , & obj - > pos ) ;
dot = vm_vec_dot ( & obj - > orient . fvec , & vec_to_last_pos ) ;
if ( dot > = 7 * F1_0 / 8 ) {
if ( aip - > CURRENT_GUN < Robot_info [ obj - > id ] . n_guns ) {
if ( robptr - > attack_type = = 1 ) {
if ( ! Player_exploded & & ( dist_to_player < obj - > size + ConsoleObject - > size + F1_0 * 2 ) ) { // robptr->circle_distance[Difficulty_level] + ConsoleObject->size) {
if ( ! ai_multiplayer_awareness ( obj , ROBOT_FIRE_AGITATION - 2 ) )
return ;
do_ai_robot_hit_attack ( obj , ConsoleObject , & obj - > pos ) ;
} else {
// mprintf((0, "Green won't fire: Too far: dist = %7.3f, threshold = %7.3f\n", f2fl(dist_to_player), f2fl(obj->size + ConsoleObject->size + F1_0*2)));
return ;
}
} else {
if ( ( gun_point - > x = = 0 ) & & ( gun_point - > y = = 0 ) & & ( gun_point - > z = = 0 ) ) {
; //mprintf((0, "Would like to fire gun, but gun not selected.\n"));
} else {
if ( ! ai_multiplayer_awareness ( obj , ROBOT_FIRE_AGITATION ) )
return ;
// New, multi-weapon-type system, 06/05/95 (life is slipping away...)
if ( gun_num ! = 0 ) {
if ( ailp - > next_fire < = 0 )
ai_fire_laser_at_player ( obj , gun_point , gun_num , & Last_fired_upon_player_pos ) ;
if ( ( ailp - > next_fire2 < = 0 ) & & ( robptr - > weapon_type2 ! = - 1 ) ) {
calc_gun_point ( gun_point , obj , 0 ) ;
ai_fire_laser_at_player ( obj , gun_point , 0 , & Last_fired_upon_player_pos ) ;
}
} else if ( ailp - > next_fire < = 0 )
ai_fire_laser_at_player ( obj , gun_point , gun_num , & Last_fired_upon_player_pos ) ;
}
}
// Wants to fire, so should go into chase mode, probably.
if ( ( aip - > behavior ! = AIB_RUN_FROM ) & & ( aip - > behavior ! = AIB_STILL ) & & ( aip - > behavior ! = AIB_SNIPE ) & & ( aip - > behavior ! = AIB_FOLLOW ) & & ( ( ailp - > mode = = AIM_FOLLOW_PATH ) | | ( ailp - > mode = = AIM_STILL ) ) )
ailp - > mode = AIM_CHASE_OBJECT ;
}
aip - > GOAL_STATE = AIS_RECO ;
ailp - > goal_state [ aip - > CURRENT_GUN ] = AIS_RECO ;
// Switch to next gun for next fire.
aip - > CURRENT_GUN + + ;
if ( aip - > CURRENT_GUN > = Robot_info [ obj - > id ] . n_guns )
{
if ( Robot_info [ obj - > id ] . n_guns = = 1 )
aip - > CURRENT_GUN = 0 ;
else
aip - > CURRENT_GUN = 1 ;
}
}
}
}
// ---------------------------------------------------------------
}
}