2 THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
3 SOFTWARE CORPORATION ("PARALLAX"). PARALLAX, IN DISTRIBUTING THE CODE TO
4 END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
5 ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
6 IN USING, DISPLAYING, AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
7 SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
8 FREE PURPOSES. IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
9 CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES. THE END-USER UNDERSTANDS
10 AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.
11 COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED.
16 * Split ai.c into two files: ai.c, ai2.c.
37 #include "editor/editor.h"
38 #include "editor/kdefs.h"
47 void teleport_boss(object *objp);
48 int boss_fits_in_seg(object *boss_objp, int segnum);
52 int Attack_scale = 24;
53 sbyte Mike_to_matt_xlate[] = {AS_REST, AS_REST, AS_ALERT, AS_ALERT, AS_FLINCH, AS_FIRE, AS_RECOIL, AS_REST};
55 // Amount of time since the current robot was last processed for things such as movement.
56 // It is not valid to use FrameTime because robots do not get moved every frame.
58 int Num_boss_teleport_segs;
59 short Boss_teleport_segs[MAX_BOSS_TELEPORT_SEGS];
60 int Num_boss_gate_segs;
61 short Boss_gate_segs[MAX_BOSS_TELEPORT_SEGS];
63 // ---------------------------------------------------------
64 // On entry, N_robot_types had darn sure better be set.
65 // Mallocs N_robot_types robot_info structs into global Robot_info.
66 void init_ai_system(void)
71 mprintf((0, "Trying to malloc %i bytes for Robot_info.\n", N_robot_types * sizeof(*Robot_info)));
72 Robot_info = (robot_info *) d_malloc( N_robot_types * sizeof(*Robot_info) );
73 mprintf((0, "Robot_info = %i\n", Robot_info));
75 for (i=0; i<N_robot_types; i++) {
76 Robot_info[i].field_of_view = F1_0/2;
77 Robot_info[i].firing_wait = F1_0;
78 Robot_info[i].turn_time = F1_0*2;
79 // -- Robot_info[i].fire_power = F1_0;
80 // -- Robot_info[i].shield = F1_0/2;
81 Robot_info[i].max_speed = F1_0*10;
82 Robot_info[i].always_0xabcd = 0xabcd;
88 // ---------------------------------------------------------------------------------------------------------------------
89 // Given a behavior, set initial mode.
90 int ai_behavior_to_mode(int behavior)
93 case AIB_STILL: return AIM_STILL;
94 case AIB_NORMAL: return AIM_CHASE_OBJECT;
95 case AIB_BEHIND: return AIM_BEHIND;
96 case AIB_RUN_FROM: return AIM_RUN_FROM_OBJECT;
97 case AIB_SNIPE: return AIM_STILL; // Changed, 09/13/95, MK, snipers are still until they see you or are hit.
98 case AIB_STATION: return AIM_STILL;
99 case AIB_FOLLOW: return AIM_FOLLOW_PATH;
100 default: Int3(); // Contact Mike: Error, illegal behavior type
106 // ---------------------------------------------------------------------------------------------------------------------
107 // Call every time the player starts a new ship.
108 void ai_init_boss_for_ship(void)
110 Boss_hit_time = -F1_0*10;
114 // ---------------------------------------------------------------------------------------------------------------------
115 // initial_mode == -1 means leave mode unchanged.
116 void init_ai_object(int objnum, int behavior, int hide_segment)
118 object *objp = &Objects[objnum];
119 ai_static *aip = &objp->ctype.ai_info;
120 ai_local *ailp = &Ai_local_info[objnum];
121 robot_info *robptr = &Robot_info[objp->id];
124 // mprintf((0, "Behavior of 0 for object #%i, bashing to AIB_NORMAL.\n", objnum));
125 behavior = AIB_NORMAL;
126 aip->behavior = behavior;
128 // mprintf((0, "Initializing object #%i\n", objnum));
130 // mode is now set from the Robot dialog, so this should get overwritten.
131 ailp->mode = AIM_STILL;
133 ailp->previous_visibility = 0;
135 if (behavior != -1) {
136 aip->behavior = behavior;
137 ailp->mode = ai_behavior_to_mode(aip->behavior);
138 } else if (!((aip->behavior >= MIN_BEHAVIOR) && (aip->behavior <= MAX_BEHAVIOR))) {
139 mprintf((0, "[obj %i -> normal] ", objnum));
140 aip->behavior = AIB_NORMAL;
143 if (robptr->companion) {
144 ailp->mode = AIM_GOTO_PLAYER;
145 Escort_kill_object = -1;
149 aip->behavior = AIB_SNIPE;
150 ailp->mode = AIM_THIEF_WAIT;
153 if (robptr->attack_type) {
154 aip->behavior = AIB_NORMAL;
155 ailp->mode = ai_behavior_to_mode(aip->behavior);
158 // This is astonishingly stupid! This routine gets called by matcens! KILL KILL KILL!!! Point_segs_free_ptr = Point_segs;
160 vm_vec_zero(&objp->mtype.phys_info.velocity);
161 // -- ailp->wait_time = F1_0*5;
162 ailp->player_awareness_time = 0;
163 ailp->player_awareness_type = 0;
164 aip->GOAL_STATE = AIS_SRCH;
165 aip->CURRENT_STATE = AIS_REST;
166 ailp->time_player_seen = GameTime;
167 ailp->next_misc_sound_time = GameTime;
168 ailp->time_player_sound_attacked = GameTime;
170 if ((behavior == AIB_SNIPE) || (behavior == AIB_STATION) || (behavior == AIB_RUN_FROM) || (behavior == AIB_FOLLOW)) {
171 aip->hide_segment = hide_segment;
172 ailp->goal_segment = hide_segment;
173 aip->hide_index = -1; // This means the path has not yet been created.
174 aip->cur_path_index = 0;
177 aip->SKIP_AI_COUNT = 0;
179 if (robptr->cloak_type == RI_CLOAKED_ALWAYS)
184 objp->mtype.phys_info.flags |= (PF_BOUNCE | PF_TURNROLL);
186 aip->REMOTE_OWNER = -1;
188 aip->dying_sound_playing = 0;
189 aip->dying_start_time = 0;
194 extern object * create_morph_robot( segment *segp, vms_vector *object_pos, int object_id);
196 // --------------------------------------------------------------------------------------------------------------------
197 // Create a Buddy bot.
198 // This automatically happens when you bring up the Buddy menu in a debug version.
199 // It is available as a cheat in a non-debug (release) version.
200 void create_buddy_bot(void)
203 vms_vector object_pos;
205 for (buddy_id=0; buddy_id<N_robot_types; buddy_id++)
206 if (Robot_info[buddy_id].companion)
209 if (buddy_id == N_robot_types) {
210 mprintf((0, "Can't create Buddy. No 'companion' bot found in Robot_info!\n"));
214 compute_segment_center(&object_pos, &Segments[ConsoleObject->segnum]);
216 create_morph_robot( &Segments[ConsoleObject->segnum], &object_pos, buddy_id);
219 #define QUEUE_SIZE 256
221 // --------------------------------------------------------------------------------------------------------------------
222 // Create list of segments boss is allowed to teleport to at segptr.
224 // Boss is allowed to teleport to segments he fits in (calls object_intersects_wall) and
225 // he can reach from his initial position (calls find_connected_distance).
226 // If size_check is set, then only add segment if boss can fit in it, else any segment is legal.
227 // one_wall_hack added by MK, 10/13/95: A mega-hack! Set to !0 to ignore the
228 void init_boss_segments(short segptr[], int *num_segs, int size_check, int one_wall_hack)
240 mprintf((0, "Boss fits in segments:\n"));
241 // See if there is a boss. If not, quick out.
242 for (i=0; i<=Highest_object_index; i++)
243 if ((Objects[i].type == OBJ_ROBOT) && (Robot_info[Objects[i].id].boss_flag)) {
244 if (boss_objnum != -1) // There are two bosses in this mine! i and boss_objnum!
245 Int3(); //do int3 here instead of assert so museum will work
249 if (boss_objnum != -1) {
250 int original_boss_seg;
251 vms_vector original_boss_pos;
252 object *boss_objp = &Objects[boss_objnum];
254 int seg_queue[QUEUE_SIZE];
255 //ALREADY IN RENDER.H sbyte visited[MAX_SEGMENTS];
258 boss_size_save = boss_objp->size;
259 // -- Causes problems!! -- boss_objp->size = fixmul((F1_0/4)*3, boss_objp->size);
260 original_boss_seg = boss_objp->segnum;
261 original_boss_pos = boss_objp->pos;
264 seg_queue[head++] = original_boss_seg;
266 segptr[(*num_segs)++] = original_boss_seg;
267 mprintf((0, "%4i ", original_boss_seg));
269 Selected_segs[N_selected_segs++] = original_boss_seg;
272 for (i=0; i<=Highest_segment_index; i++)
275 while (tail != head) {
277 segment *segp = &Segments[seg_queue[tail++]];
279 tail &= QUEUE_SIZE-1;
281 for (sidenum=0; sidenum<MAX_SIDES_PER_SEGMENT; sidenum++) {
284 if (((w = WALL_IS_DOORWAY(segp, sidenum)) & WID_FLY_FLAG) || one_wall_hack) {
285 // If we get here and w == WID_WALL, then we want to process through this wall, else not.
286 if (IS_CHILD(segp->children[sidenum])) {
292 if (visited[segp->children[sidenum]] == 0) {
293 seg_queue[head++] = segp->children[sidenum];
294 visited[segp->children[sidenum]] = 1;
295 head &= QUEUE_SIZE-1;
297 if (head == tail + QUEUE_SIZE-1)
298 Int3(); // queue overflow. Make it bigger!
300 if (head+QUEUE_SIZE == tail + QUEUE_SIZE-1)
301 Int3(); // queue overflow. Make it bigger!
303 if ((!size_check) || boss_fits_in_seg(boss_objp, segp->children[sidenum])) {
304 segptr[(*num_segs)++] = segp->children[sidenum];
305 if (size_check) mprintf((0, "%4i ", segp->children[sidenum]));
307 Selected_segs[N_selected_segs++] = segp->children[sidenum];
309 if (*num_segs >= MAX_BOSS_TELEPORT_SEGS) {
310 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));
320 boss_objp->size = boss_size_save;
321 boss_objp->pos = original_boss_pos;
322 obj_relink(boss_objnum, original_boss_seg);
328 extern void init_buddy_for_level(void);
330 // ---------------------------------------------------------------------------------------------------------------------
331 void init_ai_objects(void)
335 Point_segs_free_ptr = Point_segs;
337 for (i=0; i<MAX_OBJECTS; i++) {
338 object *objp = &Objects[i];
340 if (objp->control_type == CT_AI)
341 init_ai_object(i, objp->ctype.ai_info.behavior, objp->ctype.ai_info.hide_segment);
344 init_boss_segments(Boss_gate_segs, &Num_boss_gate_segs, 0, 0);
346 init_boss_segments(Boss_teleport_segs, &Num_boss_teleport_segs, 1, 0);
347 if (Num_boss_teleport_segs == 1)
348 init_boss_segments(Boss_teleport_segs, &Num_boss_teleport_segs, 1, 1);
350 Boss_dying_sound_playing = 0;
352 // -- unused! MK, 10/21/95 -- Boss_been_hit = 0;
353 Gate_interval = F1_0*4 - Difficulty_level*i2f(2)/3;
359 init_buddy_for_level();
361 if (Current_level_num == Last_level) {
362 Boss_teleport_interval = F1_0*10;
363 Boss_cloak_interval = F1_0*15; // Time between cloaks
365 Boss_teleport_interval = F1_0*7;
366 Boss_cloak_interval = F1_0*10; // Time between cloaks
373 fix Firing_wait_copy[MAX_ROBOT_TYPES];
374 fix Firing_wait2_copy[MAX_ROBOT_TYPES];
375 sbyte Rapidfire_count_copy[MAX_ROBOT_TYPES];
377 void do_lunacy_on(void)
381 if (Lunacy) //already on
386 Diff_save = Difficulty_level;
387 Difficulty_level = NDL-1;
389 for (i=0; i<MAX_ROBOT_TYPES; i++) {
390 Firing_wait_copy[i] = Robot_info[i].firing_wait[NDL-1];
391 Firing_wait2_copy[i] = Robot_info[i].firing_wait2[NDL-1];
392 Rapidfire_count_copy[i] = Robot_info[i].rapidfire_count[NDL-1];
394 Robot_info[i].firing_wait[NDL-1] = Robot_info[i].firing_wait[1];
395 Robot_info[i].firing_wait2[NDL-1] = Robot_info[i].firing_wait2[1];
396 Robot_info[i].rapidfire_count[NDL-1] = Robot_info[i].rapidfire_count[1];
401 void do_lunacy_off(void)
405 if (!Lunacy) //already off
410 for (i=0; i<MAX_ROBOT_TYPES; i++) {
411 Robot_info[i].firing_wait[NDL-1] = Firing_wait_copy[i];
412 Robot_info[i].firing_wait2[NDL-1] = Firing_wait2_copy[i];
413 Robot_info[i].rapidfire_count[NDL-1] = Rapidfire_count_copy[i];
416 Difficulty_level = Diff_save;
419 // ----------------------------------------------------------------
420 // Do *dest = *delta unless:
421 // *delta is pretty small
422 // and they are of different signs.
423 void set_rotvel_and_saturate(fix *dest, fix delta)
425 if ((delta ^ *dest) < 0) {
426 if (abs(delta) < F1_0/8) {
427 // mprintf((0, "D"));
430 // mprintf((0, "d"));
433 // mprintf((0, "!"));
438 //--debug-- #ifndef NDEBUG
439 //--debug-- int Total_turns=0;
440 //--debug-- int Prevented_turns=0;
443 #define AI_TURN_SCALE 1
444 #define BABY_SPIDER_ID 14
445 #define FIRE_AT_NEARBY_PLAYER_THRESHOLD (F1_0*40)
447 extern void physics_turn_towards_vector(vms_vector *goal_vector, object *obj, fix rate);
448 extern fix Seismic_tremor_magnitude;
450 //-------------------------------------------------------------------------------------------
451 void ai_turn_towards_vector(vms_vector *goal_vector, object *objp, fix rate)
456 // Not all robots can turn, eg, SPECIAL_REACTOR_ROBOT
460 if ((objp->id == BABY_SPIDER_ID) && (objp->type == OBJ_ROBOT)) {
461 physics_turn_towards_vector(goal_vector, objp, rate);
465 new_fvec = *goal_vector;
467 dot = vm_vec_dot(goal_vector, &objp->orient.fvec);
469 if (dot < (F1_0 - FrameTime/2)) {
471 fix new_scale = fixdiv(FrameTime * AI_TURN_SCALE, rate);
472 vm_vec_scale(&new_fvec, new_scale);
473 vm_vec_add2(&new_fvec, &objp->orient.fvec);
474 mag = vm_vec_normalize_quick(&new_fvec);
475 if (mag < F1_0/256) {
476 mprintf((1, "Degenerate vector in ai_turn_towards_vector (mag = %7.3f)\n", f2fl(mag)));
477 new_fvec = *goal_vector; // if degenerate vector, go right to goal
481 if (Seismic_tremor_magnitude) {
484 make_random_vector(&rand_vec);
485 scale = fixdiv(2*Seismic_tremor_magnitude, Robot_info[objp->id].mass);
486 vm_vec_scale_add2(&new_fvec, &rand_vec, scale);
489 vm_vector_2_matrix(&objp->orient, &new_fvec, NULL, &objp->orient.rvec);
492 // -- unused, 08/07/95 -- // --------------------------------------------------------------------------------------------------------------------
493 // -- unused, 08/07/95 -- void ai_turn_randomly(vms_vector *vec_to_player, object *obj, fix rate, int previous_visibility)
494 // -- unused, 08/07/95 -- {
495 // -- unused, 08/07/95 -- vms_vector curvec;
496 // -- unused, 08/07/95 --
497 // -- unused, 08/07/95 -- // -- MK, 06/09/95 // Random turning looks too stupid, so 1/4 of time, cheat.
498 // -- unused, 08/07/95 -- // -- MK, 06/09/95 if (previous_visibility)
499 // -- unused, 08/07/95 -- // -- MK, 06/09/95 if (d_rand() > 0x7400) {
500 // -- unused, 08/07/95 -- // -- MK, 06/09/95 ai_turn_towards_vector(vec_to_player, obj, rate);
501 // -- unused, 08/07/95 -- // -- MK, 06/09/95 return;
502 // -- unused, 08/07/95 -- // -- MK, 06/09/95 }
503 // -- unused, 08/07/95 --
504 // -- unused, 08/07/95 -- curvec = obj->mtype.phys_info.rotvel;
505 // -- unused, 08/07/95 --
506 // -- unused, 08/07/95 -- curvec.y += F1_0/64;
507 // -- unused, 08/07/95 --
508 // -- unused, 08/07/95 -- curvec.x += curvec.y/6;
509 // -- unused, 08/07/95 -- curvec.y += curvec.z/4;
510 // -- unused, 08/07/95 -- curvec.z += curvec.x/10;
511 // -- unused, 08/07/95 --
512 // -- unused, 08/07/95 -- if (abs(curvec.x) > F1_0/8) curvec.x /= 4;
513 // -- unused, 08/07/95 -- if (abs(curvec.y) > F1_0/8) curvec.y /= 4;
514 // -- unused, 08/07/95 -- if (abs(curvec.z) > F1_0/8) curvec.z /= 4;
515 // -- unused, 08/07/95 --
516 // -- unused, 08/07/95 -- obj->mtype.phys_info.rotvel = curvec;
517 // -- unused, 08/07/95 --
518 // -- unused, 08/07/95 -- }
520 // Overall_agitation affects:
521 // Widens field of view. Field of view is in range 0..1 (specified in bitmaps.tbl as N/360 degrees).
522 // Overall_agitation/128 subtracted from field of view, making robots see wider.
523 // Increases distance to which robot will search to create path to player by Overall_agitation/8 segments.
524 // Decreases wait between fire times by Overall_agitation/64 seconds.
527 // --------------------------------------------------------------------------------------------------------------------
529 // 0 Player is not visible from object, obstruction or something.
530 // 1 Player is visible, but not in field of view.
531 // 2 Player is visible and in field of view.
532 // Note: Uses Believed_player_pos as player's position for cloak effect.
533 // NOTE: Will destructively modify *pos if *pos is outside the mine.
534 int player_is_visible_from_object(object *objp, vms_vector *pos, fix field_of_view, vms_vector *vec_to_player)
539 // Assume that robot's gun tip is in same segment as robot's center.
540 objp->ctype.ai_info.SUB_FLAGS &= ~SUB_FLAGS_GUNSEG;
543 if ((pos->x != objp->pos.x) || (pos->y != objp->pos.y) || (pos->z != objp->pos.z)) {
544 int segnum = find_point_seg(pos, objp->segnum);
546 fq.startseg = objp->segnum;
548 mprintf((1, "Object %i, gun is outside mine, moving towards center.\n", OBJECT_NUMBER(objp)));
549 move_towards_segment_center(objp);
551 if (segnum != objp->segnum) {
552 // -- mprintf((0, "Warning: Robot's gun tip not in same segment as robot center, frame %i.\n", FrameCount));
553 objp->ctype.ai_info.SUB_FLAGS |= SUB_FLAGS_GUNSEG;
555 fq.startseg = segnum;
558 fq.startseg = objp->segnum;
559 fq.p1 = &Believed_player_pos;
561 fq.thisobjnum = OBJECT_NUMBER(objp);
562 fq.ignore_obj_list = NULL;
563 fq.flags = FQ_TRANSWALL; // -- Why were we checking objects? | FQ_CHECK_OBJS; //what about trans walls???
565 Hit_type = find_vector_intersection(&fq,&Hit_data);
567 Hit_pos = Hit_data.hit_pnt;
568 Hit_seg = Hit_data.hit_seg;
570 // -- when we stupidly checked objects -- if ((Hit_type == HIT_NONE) || ((Hit_type == HIT_OBJECT) && (Hit_data.hit_object == Players[Player_num].objnum))) {
571 if (Hit_type == HIT_NONE) {
572 dot = vm_vec_dot(vec_to_player, &objp->orient.fvec);
573 // 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)));
574 if (dot > field_of_view - (Overall_agitation << 9)) {
584 // ------------------------------------------------------------------------------------------------------------------
585 // Return 1 if animates, else return 0
586 int do_silly_animation(object *objp)
588 int objnum = OBJECT_NUMBER(objp);
590 int robot_type, gun_num, robot_state, num_joint_positions;
591 polyobj_info *pobj_info = &objp->rtype.pobj_info;
592 ai_static *aip = &objp->ctype.ai_info;
593 // ai_local *ailp = &Ai_local_info[objnum];
594 int num_guns, at_goal;
596 int flinch_attack_scale = 1;
598 robot_type = objp->id;
599 num_guns = Robot_info[robot_type].n_guns;
600 attack_type = Robot_info[robot_type].attack_type;
603 // mprintf((0, "Object #%i of type #%i has 0 guns.\n", OBJECT_NUMBER(objp), robot_type));
607 // This is a hack. All positions should be based on goal_state, not GOAL_STATE.
608 robot_state = Mike_to_matt_xlate[aip->GOAL_STATE];
609 // previous_robot_state = Mike_to_matt_xlate[aip->CURRENT_STATE];
611 if (attack_type) // && ((robot_state == AS_FIRE) || (robot_state == AS_RECOIL)))
612 flinch_attack_scale = Attack_scale;
613 else if ((robot_state == AS_FLINCH) || (robot_state == AS_RECOIL))
614 flinch_attack_scale = Flinch_scale;
617 for (gun_num=0; gun_num <= num_guns; gun_num++) {
620 num_joint_positions = robot_get_anim_state(&jp_list, robot_type, gun_num, robot_state);
622 for (joint=0; joint<num_joint_positions; joint++) {
623 fix delta_angle, delta_2;
624 int jointnum = jp_list[joint].jointnum;
625 vms_angvec *jp = &jp_list[joint].angles;
626 vms_angvec *pobjp = &pobj_info->anim_angles[jointnum];
628 if (jointnum >= Polygon_models[objp->rtype.pobj_info.model_num].n_models) {
629 Int3(); // Contact Mike: incompatible data, illegal jointnum, problem in pof file?
632 if (jp->p != pobjp->p) {
635 Ai_local_info[objnum].goal_angles[jointnum].p = jp->p;
637 delta_angle = jp->p - pobjp->p;
638 if (delta_angle >= F1_0/2)
639 delta_2 = -ANIM_RATE;
640 else if (delta_angle >= 0)
642 else if (delta_angle >= -F1_0/2)
643 delta_2 = -ANIM_RATE;
647 if (flinch_attack_scale != 1)
648 delta_2 *= flinch_attack_scale;
650 Ai_local_info[objnum].delta_angles[jointnum].p = delta_2/DELTA_ANG_SCALE; // complete revolutions per second
653 if (jp->b != pobjp->b) {
656 Ai_local_info[objnum].goal_angles[jointnum].b = jp->b;
658 delta_angle = jp->b - pobjp->b;
659 if (delta_angle >= F1_0/2)
660 delta_2 = -ANIM_RATE;
661 else if (delta_angle >= 0)
663 else if (delta_angle >= -F1_0/2)
664 delta_2 = -ANIM_RATE;
668 if (flinch_attack_scale != 1)
669 delta_2 *= flinch_attack_scale;
671 Ai_local_info[objnum].delta_angles[jointnum].b = delta_2/DELTA_ANG_SCALE; // complete revolutions per second
674 if (jp->h != pobjp->h) {
677 Ai_local_info[objnum].goal_angles[jointnum].h = jp->h;
679 delta_angle = jp->h - pobjp->h;
680 if (delta_angle >= F1_0/2)
681 delta_2 = -ANIM_RATE;
682 else if (delta_angle >= 0)
684 else if (delta_angle >= -F1_0/2)
685 delta_2 = -ANIM_RATE;
689 if (flinch_attack_scale != 1)
690 delta_2 *= flinch_attack_scale;
692 Ai_local_info[objnum].delta_angles[jointnum].h = delta_2/DELTA_ANG_SCALE; // complete revolutions per second
697 //ai_static *aip = &objp->ctype.ai_info;
698 ai_local *ailp = &Ai_local_info[OBJECT_NUMBER(objp)];
699 ailp->achieved_state[gun_num] = ailp->goal_state[gun_num];
700 if (ailp->achieved_state[gun_num] == AIS_RECO)
701 ailp->goal_state[gun_num] = AIS_FIRE;
703 if (ailp->achieved_state[gun_num] == AIS_FLIN)
704 ailp->goal_state[gun_num] = AIS_LOCK;
709 if (at_goal == 1) //num_guns)
710 aip->CURRENT_STATE = aip->GOAL_STATE;
715 // ------------------------------------------------------------------------------------------
716 // Move all sub-objects in an object towards their goals.
717 // Current orientation of object is at: pobj_info.anim_angles
718 // Goal orientation of object is at: ai_info.goal_angles
719 // Delta orientation of object is at: ai_info.delta_angles
720 void ai_frame_animation(object *objp)
722 int objnum = OBJECT_NUMBER(objp);
726 num_joints = Polygon_models[objp->rtype.pobj_info.model_num].n_models;
728 for (joint=1; joint<num_joints; joint++) {
730 fix scaled_delta_angle;
731 vms_angvec *curangp = &objp->rtype.pobj_info.anim_angles[joint];
732 vms_angvec *goalangp = &Ai_local_info[objnum].goal_angles[joint];
733 vms_angvec *deltaangp = &Ai_local_info[objnum].delta_angles[joint];
735 delta_to_goal = goalangp->p - curangp->p;
736 if (delta_to_goal > 32767)
737 delta_to_goal = delta_to_goal - 65536;
738 else if (delta_to_goal < -32767)
739 delta_to_goal = 65536 + delta_to_goal;
742 scaled_delta_angle = fixmul(deltaangp->p, FrameTime) * DELTA_ANG_SCALE;
743 curangp->p += scaled_delta_angle;
744 if (abs(delta_to_goal) < abs(scaled_delta_angle))
745 curangp->p = goalangp->p;
748 delta_to_goal = goalangp->b - curangp->b;
749 if (delta_to_goal > 32767)
750 delta_to_goal = delta_to_goal - 65536;
751 else if (delta_to_goal < -32767)
752 delta_to_goal = 65536 + delta_to_goal;
755 scaled_delta_angle = fixmul(deltaangp->b, FrameTime) * DELTA_ANG_SCALE;
756 curangp->b += scaled_delta_angle;
757 if (abs(delta_to_goal) < abs(scaled_delta_angle))
758 curangp->b = goalangp->b;
761 delta_to_goal = goalangp->h - curangp->h;
762 if (delta_to_goal > 32767)
763 delta_to_goal = delta_to_goal - 65536;
764 else if (delta_to_goal < -32767)
765 delta_to_goal = 65536 + delta_to_goal;
768 scaled_delta_angle = fixmul(deltaangp->h, FrameTime) * DELTA_ANG_SCALE;
769 curangp->h += scaled_delta_angle;
770 if (abs(delta_to_goal) < abs(scaled_delta_angle))
771 curangp->h = goalangp->h;
778 // ----------------------------------------------------------------------------------
779 void set_next_fire_time(object *objp, ai_local *ailp, robot_info *robptr, int gun_num)
781 // For guys in snipe mode, they have a 50% shot of getting this shot in free.
782 if ((gun_num != 0) || (robptr->weapon_type2 == -1))
783 if ((objp->ctype.ai_info.behavior != AIB_SNIPE) || (d_rand() > 16384))
784 ailp->rapidfire_count++;
786 // Old way, 10/15/95: Continuous rapidfire if rapidfire_count set.
787 // -- if (((robptr->weapon_type2 == -1) || (gun_num != 0)) && (ailp->rapidfire_count < robptr->rapidfire_count[Difficulty_level])) {
788 // -- ailp->next_fire = min(F1_0/8, robptr->firing_wait[Difficulty_level]/2);
790 // -- if ((robptr->weapon_type2 == -1) || (gun_num != 0)) {
791 // -- ailp->rapidfire_count = 0;
792 // -- ailp->next_fire = robptr->firing_wait[Difficulty_level];
794 // -- ailp->next_fire2 = robptr->firing_wait2[Difficulty_level];
797 if (((gun_num != 0) || (robptr->weapon_type2 == -1)) && (ailp->rapidfire_count < robptr->rapidfire_count[Difficulty_level])) {
798 ailp->next_fire = min(F1_0/8, robptr->firing_wait[Difficulty_level]/2);
800 if ((robptr->weapon_type2 == -1) || (gun_num != 0)) {
801 ailp->next_fire = robptr->firing_wait[Difficulty_level];
802 if (ailp->rapidfire_count >= robptr->rapidfire_count[Difficulty_level])
803 ailp->rapidfire_count = 0;
805 ailp->next_fire2 = robptr->firing_wait2[Difficulty_level];
809 // ----------------------------------------------------------------------------------
810 // When some robots collide with the player, they attack.
811 // If player is cloaked, then robot probably didn't actually collide, deal with that here.
812 void do_ai_robot_hit_attack(object *robot, object *playerobj, vms_vector *collision_point)
814 ai_local *ailp = &Ai_local_info[OBJECT_NUMBER(robot)];
815 robot_info *robptr = &Robot_info[robot->id];
818 if (!Robot_firing_enabled)
822 // If player is dead, stop firing.
823 if (Objects[Players[Player_num].objnum].type == OBJ_GHOST)
826 if (robptr->attack_type == 1) {
827 if (ailp->next_fire <= 0) {
828 if (!(Players[Player_num].flags & PLAYER_FLAGS_CLOAKED))
829 if (vm_vec_dist_quick(&ConsoleObject->pos, &robot->pos) < robot->size + ConsoleObject->size + F1_0*2) {
830 collide_player_and_nasty_robot( playerobj, robot, collision_point );
831 if (robptr->energy_drain && Players[Player_num].energy) {
832 Players[Player_num].energy -= robptr->energy_drain * F1_0;
833 if (Players[Player_num].energy < 0)
834 Players[Player_num].energy = 0;
835 // -- unused, use claw_sound in bitmaps.tbl -- digi_link_sound_to_pos( SOUND_ROBOT_SUCKED_PLAYER, playerobj->segnum, 0, collision_point, 0, F1_0 );
839 robot->ctype.ai_info.GOAL_STATE = AIS_RECO;
840 set_next_fire_time(robot, ailp, robptr, 1); // 1 = gun_num: 0 is special (uses next_fire2)
847 extern int Player_exploded;
850 #define FIRE_K 8 // Controls average accuracy of robot firing. Smaller numbers make firing worse. Being power of 2 doesn't matter.
852 // ====================================================================================================================
854 #define MIN_LEAD_SPEED (F1_0*4)
855 #define MAX_LEAD_DISTANCE (F1_0*200)
856 #define LEAD_RANGE (F1_0/2)
858 // --------------------------------------------------------------------------------------------------------------------
859 // Computes point at which projectile fired by robot can hit player given positions, player vel, elapsed time
860 fix compute_lead_component(fix player_pos, fix robot_pos, fix player_vel, fix elapsed_time)
862 return fixdiv(player_pos - robot_pos, elapsed_time) + player_vel;
865 // --------------------------------------------------------------------------------------------------------------------
866 // Lead the player, returning point to fire at in fire_point.
868 // Player not cloaked
869 // Player must be moving at a speed >= MIN_LEAD_SPEED
870 // Player not farther away than MAX_LEAD_DISTANCE
871 // dot(vector_to_player, player_direction) must be in -LEAD_RANGE..LEAD_RANGE
872 // if firing a matter weapon, less leading, based on skill level.
873 int lead_player(object *objp, vms_vector *fire_point, vms_vector *believed_player_pos, int gun_num, vms_vector *fire_vec)
875 fix dot, player_speed, dist_to_player, max_weapon_speed, projected_time;
876 vms_vector player_movement_dir, vec_to_player;
881 if (Players[Player_num].flags & PLAYER_FLAGS_CLOAKED)
884 player_movement_dir = ConsoleObject->mtype.phys_info.velocity;
885 player_speed = vm_vec_normalize_quick(&player_movement_dir);
887 if (player_speed < MIN_LEAD_SPEED)
890 vm_vec_sub(&vec_to_player, believed_player_pos, fire_point);
891 dist_to_player = vm_vec_normalize_quick(&vec_to_player);
892 if (dist_to_player > MAX_LEAD_DISTANCE)
895 dot = vm_vec_dot(&vec_to_player, &player_movement_dir);
897 if ((dot < -LEAD_RANGE) || (dot > LEAD_RANGE))
900 // Looks like it might be worth trying to lead the player.
901 robptr = &Robot_info[objp->id];
902 weapon_type = robptr->weapon_type;
903 if (robptr->weapon_type2 != -1)
905 weapon_type = robptr->weapon_type2;
907 wptr = &Weapon_info[weapon_type];
908 max_weapon_speed = wptr->speed[Difficulty_level];
909 if (max_weapon_speed < F1_0)
913 // At Rookie or Trainee, don't lead at all.
914 // At higher skill levels, don't lead as well. Accomplish this by screwing up max_weapon_speed.
917 if (Difficulty_level <= 1)
920 max_weapon_speed *= (NDL-Difficulty_level);
923 projected_time = fixdiv(dist_to_player, max_weapon_speed);
925 fire_vec->x = compute_lead_component(believed_player_pos->x, fire_point->x, ConsoleObject->mtype.phys_info.velocity.x, projected_time);
926 fire_vec->y = compute_lead_component(believed_player_pos->y, fire_point->y, ConsoleObject->mtype.phys_info.velocity.y, projected_time);
927 fire_vec->z = compute_lead_component(believed_player_pos->z, fire_point->z, ConsoleObject->mtype.phys_info.velocity.z, projected_time);
929 vm_vec_normalize_quick(fire_vec);
931 Assert(vm_vec_dot(fire_vec, &objp->orient.fvec) < 3*F1_0/2);
933 // Make sure not firing at especially strange angle. If so, try to correct. If still bad, give up after one try.
934 if (vm_vec_dot(fire_vec, &objp->orient.fvec) < F1_0/2) {
935 vm_vec_add2(fire_vec, &vec_to_player);
936 vm_vec_scale(fire_vec, F1_0/2);
937 if (vm_vec_dot(fire_vec, &objp->orient.fvec) < F1_0/2) {
945 // --------------------------------------------------------------------------------------------------------------------
946 // Note: Parameter vec_to_player is only passed now because guns which aren't on the forward vector from the
947 // center of the robot will not fire right at the player. We need to aim the guns at the player. Barring that, we cheat.
948 // When this routine is complete, the parameter vec_to_player should not be necessary.
949 void ai_fire_laser_at_player(object *obj, vms_vector *fire_point, int gun_num, vms_vector *believed_player_pos)
951 int objnum = OBJECT_NUMBER(obj);
952 ai_local *ailp = &Ai_local_info[objnum];
953 robot_info *robptr = &Robot_info[obj->id];
960 Assert(robptr->attack_type == 0); // We should never be coming here for the green guy, as he has no laser!
962 // If this robot is only awake because a camera woke it up, don't fire.
963 if (obj->ctype.ai_info.SUB_FLAGS & SUB_FLAGS_CAMERA_AWAKE)
966 if (!Robot_firing_enabled)
969 if (obj->control_type == CT_MORPH)
972 // If player is exploded, stop firing.
976 if (obj->ctype.ai_info.dying_start_time)
977 return; // No firing while in death roll.
979 // Don't let the boss fire while in death roll. Sorry, this is the easiest way to do this.
980 // If you try to key the boss off obj->ctype.ai_info.dying_start_time, it will hose the endlevel stuff.
981 if (Boss_dying_start_time & Robot_info[obj->id].boss_flag)
984 // If player is cloaked, maybe don't fire based on how long cloaked and randomness.
985 if (Players[Player_num].flags & PLAYER_FLAGS_CLOAKED) {
986 fix cloak_time = Ai_cloak_info[objnum % MAX_AI_CLOAK_INFO].last_time;
988 if (GameTime - cloak_time > CLOAK_TIME_MAX/4)
989 if (d_rand() > fixdiv(GameTime - cloak_time, CLOAK_TIME_MAX)/2) {
990 set_next_fire_time(obj, ailp, robptr, gun_num);
995 // Handle problem of a robot firing through a wall because its gun tip is on the other
996 // side of the wall than the robot's center. For speed reasons, we normally only compute
997 // the vector from the gun point to the player. But we need to know whether the gun point
998 // is separated from the robot's center by a wall. If so, don't fire!
999 if (obj->ctype.ai_info.SUB_FLAGS & SUB_FLAGS_GUNSEG) {
1000 // Well, the gun point is in a different segment than the robot's center.
1001 // This is almost always ok, but it is not ok if something solid is in between.
1003 int gun_segnum = find_point_seg(fire_point, obj->segnum);
1005 // See if these segments are connected, which should almost always be the case.
1006 conn_side = find_connect_side(&Segments[gun_segnum], &Segments[obj->segnum]);
1007 if (conn_side != -1) {
1008 // They are connected via conn_side in segment obj->segnum.
1009 // See if they are unobstructed.
1010 if (!(WALL_IS_DOORWAY(&Segments[obj->segnum], conn_side) & WID_FLY_FLAG)) {
1011 // Can't fly through, so don't let this bot fire through!
1015 // Well, they are not directly connected, so use find_vector_intersection to see if they are unobstructed.
1020 fq.startseg = obj->segnum;
1024 fq.thisobjnum = OBJECT_NUMBER(obj);
1025 fq.ignore_obj_list = NULL;
1026 fq.flags = FQ_TRANSWALL;
1028 fate = find_vector_intersection(&fq, &hit_data);
1029 if (fate != HIT_NONE) {
1030 Int3(); // This bot's gun is poking through a wall, so don't fire.
1031 move_towards_segment_center(obj); // And decrease chances it will happen again.
1037 // -- mprintf((0, "Firing from gun #%i at time = %7.3f\n", gun_num, f2fl(GameTime)));
1039 // Set position to fire at based on difficulty level and robot's aiming ability
1040 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.
1042 // Robots aim more poorly during seismic disturbance.
1043 if (Seismic_tremor_magnitude) {
1046 temp = F1_0 - abs(Seismic_tremor_magnitude);
1050 aim = fixmul(aim, temp);
1053 // Lead the player half the time.
1054 // Note that when leading the player, aim is perfect. This is probably acceptable since leading is so hacked in.
1055 // Problem is all robots will lead equally badly.
1056 if (d_rand() < 16384) {
1057 if (lead_player(obj, fire_point, believed_player_pos, gun_num, &fire_vec)) // Stuff direction to fire at in fire_point.
1062 count = 0; // Don't want to sit in this loop forever...
1063 while ((count < 4) && (dot < F1_0/4)) {
1064 bpp_diff.x = believed_player_pos->x + fixmul((d_rand()-16384) * (NDL-Difficulty_level-1) * 4, aim);
1065 bpp_diff.y = believed_player_pos->y + fixmul((d_rand()-16384) * (NDL-Difficulty_level-1) * 4, aim);
1066 bpp_diff.z = believed_player_pos->z + fixmul((d_rand()-16384) * (NDL-Difficulty_level-1) * 4, aim);
1068 vm_vec_normalized_dir_quick(&fire_vec, &bpp_diff, fire_point);
1069 dot = vm_vec_dot(&obj->orient.fvec, &fire_vec);
1074 weapon_type = robptr->weapon_type;
1075 if (robptr->weapon_type2 != -1)
1077 weapon_type = robptr->weapon_type2;
1079 Laser_create_new_easy( &fire_vec, fire_point, OBJECT_NUMBER(obj), weapon_type, 1 );
1082 if (Game_mode & GM_MULTI) {
1083 ai_multi_send_robot_position(objnum, -1);
1084 multi_send_robot_fire(objnum, obj->ctype.ai_info.CURRENT_GUN, &fire_vec);
1088 create_awareness_event(obj, PA_NEARBY_ROBOT_FIRED);
1090 set_next_fire_time(obj, ailp, robptr, gun_num);
1094 // --------------------------------------------------------------------------------------------------------------------
1095 // vec_goal must be normalized, or close to it.
1096 // if dot_based set, then speed is based on direction of movement relative to heading
1097 void move_towards_vector(object *objp, vms_vector *vec_goal, int dot_based)
1099 physics_info *pptr = &objp->mtype.phys_info;
1100 fix speed, dot, max_speed;
1101 robot_info *robptr = &Robot_info[objp->id];
1104 // Trying to move towards player. If forward vector much different than velocity vector,
1105 // bash velocity vector twice as much towards player as usual.
1107 vel = pptr->velocity;
1108 vm_vec_normalize_quick(&vel);
1109 dot = vm_vec_dot(&vel, &objp->orient.fvec);
1114 if (dot_based && (dot < 3*F1_0/4)) {
1115 // This funny code is supposed to slow down the robot and move his velocity towards his direction
1116 // more quickly than the general code
1117 pptr->velocity.x = pptr->velocity.x/2 + fixmul(vec_goal->x, FrameTime*32);
1118 pptr->velocity.y = pptr->velocity.y/2 + fixmul(vec_goal->y, FrameTime*32);
1119 pptr->velocity.z = pptr->velocity.z/2 + fixmul(vec_goal->z, FrameTime*32);
1121 pptr->velocity.x += fixmul(vec_goal->x, FrameTime*64) * (Difficulty_level+5)/4;
1122 pptr->velocity.y += fixmul(vec_goal->y, FrameTime*64) * (Difficulty_level+5)/4;
1123 pptr->velocity.z += fixmul(vec_goal->z, FrameTime*64) * (Difficulty_level+5)/4;
1126 speed = vm_vec_mag_quick(&pptr->velocity);
1127 max_speed = robptr->max_speed[Difficulty_level];
1129 // Green guy attacks twice as fast as he moves away.
1130 if ((robptr->attack_type == 1) || robptr->thief || robptr->kamikaze)
1133 if (speed > max_speed) {
1134 pptr->velocity.x = (pptr->velocity.x*3)/4;
1135 pptr->velocity.y = (pptr->velocity.y*3)/4;
1136 pptr->velocity.z = (pptr->velocity.z*3)/4;
1140 // --------------------------------------------------------------------------------------------------------------------
1141 void move_towards_player(object *objp, vms_vector *vec_to_player)
1142 // vec_to_player must be normalized, or close to it.
1144 move_towards_vector(objp, vec_to_player, 1);
1147 // --------------------------------------------------------------------------------------------------------------------
1148 // I am ashamed of this: fast_flag == -1 means normal slide about. fast_flag = 0 means no evasion.
1149 void move_around_player(object *objp, vms_vector *vec_to_player, int fast_flag)
1151 physics_info *pptr = &objp->mtype.phys_info;
1153 robot_info *robptr = &Robot_info[objp->id];
1154 int objnum = OBJECT_NUMBER(objp);
1158 vms_vector evade_vector;
1170 while (ft < F1_0/4) {
1176 dir = (FrameCount + (count+1) * (objnum*8 + objnum*4 + objnum)) & dir_change;
1179 Assert((dir >= 0) && (dir <= 3));
1183 evade_vector.x = fixmul(vec_to_player->z, FrameTime*32);
1184 evade_vector.y = fixmul(vec_to_player->y, FrameTime*32);
1185 evade_vector.z = fixmul(-vec_to_player->x, FrameTime*32);
1188 evade_vector.x = fixmul(-vec_to_player->z, FrameTime*32);
1189 evade_vector.y = fixmul(vec_to_player->y, FrameTime*32);
1190 evade_vector.z = fixmul(vec_to_player->x, FrameTime*32);
1193 evade_vector.x = fixmul(-vec_to_player->y, FrameTime*32);
1194 evade_vector.y = fixmul(vec_to_player->x, FrameTime*32);
1195 evade_vector.z = fixmul(vec_to_player->z, FrameTime*32);
1198 evade_vector.x = fixmul(vec_to_player->y, FrameTime*32);
1199 evade_vector.y = fixmul(-vec_to_player->x, FrameTime*32);
1200 evade_vector.z = fixmul(vec_to_player->z, FrameTime*32);
1203 Error("Function move_around_player: Bad case.");
1206 // Note: -1 means normal circling about the player. > 0 means fast evasion.
1207 if (fast_flag > 0) {
1210 // Only take evasive action if looking at player.
1211 // Evasion speed is scaled by percentage of shields left so wounded robots evade less effectively.
1213 dot = vm_vec_dot(vec_to_player, &objp->orient.fvec);
1214 if ((dot > robptr->field_of_view[Difficulty_level]) && !(ConsoleObject->flags & PLAYER_FLAGS_CLOAKED)) {
1217 if (robptr->strength)
1218 damage_scale = fixdiv(objp->shields, robptr->strength);
1220 damage_scale = F1_0;
1221 if (damage_scale > F1_0)
1222 damage_scale = F1_0; // Just in case...
1223 else if (damage_scale < 0)
1224 damage_scale = 0; // Just in case...
1226 vm_vec_scale(&evade_vector, i2f(fast_flag) + damage_scale);
1230 pptr->velocity.x += evade_vector.x;
1231 pptr->velocity.y += evade_vector.y;
1232 pptr->velocity.z += evade_vector.z;
1234 speed = vm_vec_mag_quick(&pptr->velocity);
1235 if ((OBJECT_NUMBER(objp) != 1) && (speed > robptr->max_speed[Difficulty_level])) {
1236 pptr->velocity.x = (pptr->velocity.x*3)/4;
1237 pptr->velocity.y = (pptr->velocity.y*3)/4;
1238 pptr->velocity.z = (pptr->velocity.z*3)/4;
1242 // --------------------------------------------------------------------------------------------------------------------
1243 void move_away_from_player(object *objp, vms_vector *vec_to_player, int attack_type)
1246 physics_info *pptr = &objp->mtype.phys_info;
1247 robot_info *robptr = &Robot_info[objp->id];
1250 pptr->velocity.x -= fixmul(vec_to_player->x, FrameTime*16);
1251 pptr->velocity.y -= fixmul(vec_to_player->y, FrameTime*16);
1252 pptr->velocity.z -= fixmul(vec_to_player->z, FrameTime*16);
1255 // Get value in 0..3 to choose evasion direction.
1256 objref = ((OBJECT_NUMBER(objp)) ^ ((FrameCount + 3*(OBJECT_NUMBER(objp))) >> 5)) & 3;
1259 case 0: vm_vec_scale_add2(&pptr->velocity, &objp->orient.uvec, FrameTime << 5); break;
1260 case 1: vm_vec_scale_add2(&pptr->velocity, &objp->orient.uvec, -FrameTime << 5); break;
1261 case 2: vm_vec_scale_add2(&pptr->velocity, &objp->orient.rvec, FrameTime << 5); break;
1262 case 3: vm_vec_scale_add2(&pptr->velocity, &objp->orient.rvec, -FrameTime << 5); break;
1263 default: Int3(); // Impossible, bogus value on objref, must be in 0..3
1268 speed = vm_vec_mag_quick(&pptr->velocity);
1270 if (speed > robptr->max_speed[Difficulty_level]) {
1271 pptr->velocity.x = (pptr->velocity.x*3)/4;
1272 pptr->velocity.y = (pptr->velocity.y*3)/4;
1273 pptr->velocity.z = (pptr->velocity.z*3)/4;
1278 // --------------------------------------------------------------------------------------------------------------------
1279 // Move towards, away_from or around player.
1280 // Also deals with evasion.
1281 // If the flag evade_only is set, then only allowed to evade, not allowed to move otherwise (must have mode == AIM_STILL).
1282 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)
1285 robot_info *robptr = &Robot_info[objp->id];
1287 Assert(player_visibility != -1);
1289 // See if should take avoidance.
1291 // New way, green guys don't evade: if ((robptr->attack_type == 0) && (objp->ctype.ai_info.danger_laser_num != -1)) {
1292 if (objp->ctype.ai_info.danger_laser_num != -1) {
1293 dobjp = &Objects[objp->ctype.ai_info.danger_laser_num];
1295 if ((dobjp->type == OBJ_WEAPON) && (dobjp->signature == objp->ctype.ai_info.danger_laser_signature)) {
1296 fix dot, dist_to_laser, field_of_view;
1297 vms_vector vec_to_laser, laser_fvec;
1299 field_of_view = Robot_info[objp->id].field_of_view[Difficulty_level];
1301 vm_vec_sub(&vec_to_laser, &dobjp->pos, &objp->pos);
1302 dist_to_laser = vm_vec_normalize_quick(&vec_to_laser);
1303 dot = vm_vec_dot(&vec_to_laser, &objp->orient.fvec);
1305 if ((dot > field_of_view) || (robptr->companion)) {
1306 fix laser_robot_dot;
1307 vms_vector laser_vec_to_robot;
1309 // The laser is seen by the robot, see if it might hit the robot.
1310 // Get the laser's direction. If it's a polyobj, it can be gotten cheaply from the orientation matrix.
1311 if (dobjp->render_type == RT_POLYOBJ)
1312 laser_fvec = dobjp->orient.fvec;
1313 else { // Not a polyobj, get velocity and normalize.
1314 laser_fvec = dobjp->mtype.phys_info.velocity; //dobjp->orient.fvec;
1315 vm_vec_normalize_quick(&laser_fvec);
1317 vm_vec_sub(&laser_vec_to_robot, &objp->pos, &dobjp->pos);
1318 vm_vec_normalize_quick(&laser_vec_to_robot);
1319 laser_robot_dot = vm_vec_dot(&laser_fvec, &laser_vec_to_robot);
1321 if ((laser_robot_dot > F1_0*7/8) && (dist_to_laser < F1_0*80)) {
1325 evade_speed = Robot_info[objp->id].evade_speed[Difficulty_level];
1327 move_around_player(objp, vec_to_player, evade_speed);
1334 // If only allowed to do evade code, then done.
1335 // Hmm, perhaps brilliant insight. If want claw-type guys to keep coming, don't return here after evasion.
1336 if ((!robptr->attack_type) && (!robptr->thief) && evade_only)
1339 // If we fall out of above, then no object to be avoided.
1340 objp->ctype.ai_info.danger_laser_num = -1;
1342 // Green guy selects move around/towards/away based on firing time, not distance.
1343 if (robptr->attack_type == 1) {
1344 if (((ailp->next_fire > robptr->firing_wait[Difficulty_level]/4) && (dist_to_player < F1_0*30)) || Player_is_dead) {
1345 // 1/4 of time, move around player, 3/4 of time, move away from player
1346 if (d_rand() < 8192) {
1347 move_around_player(objp, vec_to_player, -1);
1349 move_away_from_player(objp, vec_to_player, 1);
1352 move_towards_player(objp, vec_to_player);
1354 } else if (robptr->thief) {
1355 move_towards_player(objp, vec_to_player);
1357 int objval = ((OBJECT_NUMBER(objp)) & 0x0f) ^ 0x0a;
1359 // Changes here by MK, 12/29/95. Trying to get rid of endless circling around bots in a large room.
1360 if (robptr->kamikaze) {
1361 move_towards_player(objp, vec_to_player);
1362 } else if (dist_to_player < circle_distance)
1363 move_away_from_player(objp, vec_to_player, 0);
1364 else if ((dist_to_player < (3+objval)*circle_distance/2) && (ailp->next_fire > -F1_0)) {
1365 move_around_player(objp, vec_to_player, -1);
1367 if ((-ailp->next_fire > F1_0 + (objval << 12)) && player_visibility) {
1368 // Usually move away, but sometimes move around player.
1369 if ((((GameTime >> 18) & 0x0f) ^ objval) > 4) {
1370 move_away_from_player(objp, vec_to_player, 0);
1372 move_around_player(objp, vec_to_player, -1);
1375 move_towards_player(objp, vec_to_player);
1381 // --------------------------------------------------------------------------------------------------------------------
1382 // Compute a somewhat random, normalized vector.
1383 void make_random_vector(vms_vector *vec)
1385 vec->x = (d_rand() - 16384) | 1; // make sure we don't create null vector
1386 vec->y = d_rand() - 16384;
1387 vec->z = d_rand() - 16384;
1389 vm_vec_normalize_quick(vec);
1393 void mprintf_animation_info(object *objp)
1395 ai_static *aip = &objp->ctype.ai_info;
1396 ai_local *ailp = &Ai_local_info[OBJECT_NUMBER(objp)];
1398 if (!Ai_info_enabled)
1401 mprintf((0, "Goal = "));
1403 switch (aip->GOAL_STATE) {
1404 case AIS_NONE: mprintf((0, "NONE ")); break;
1405 case AIS_REST: mprintf((0, "REST ")); break;
1406 case AIS_SRCH: mprintf((0, "SRCH ")); break;
1407 case AIS_LOCK: mprintf((0, "LOCK ")); break;
1408 case AIS_FLIN: mprintf((0, "FLIN ")); break;
1409 case AIS_FIRE: mprintf((0, "FIRE ")); break;
1410 case AIS_RECO: mprintf((0, "RECO ")); break;
1411 case AIS_ERR_: mprintf((0, "ERR_ ")); break;
1415 mprintf((0, " Cur = "));
1417 switch (aip->CURRENT_STATE) {
1418 case AIS_NONE: mprintf((0, "NONE ")); break;
1419 case AIS_REST: mprintf((0, "REST ")); break;
1420 case AIS_SRCH: mprintf((0, "SRCH ")); break;
1421 case AIS_LOCK: mprintf((0, "LOCK ")); break;
1422 case AIS_FLIN: mprintf((0, "FLIN ")); break;
1423 case AIS_FIRE: mprintf((0, "FIRE ")); break;
1424 case AIS_RECO: mprintf((0, "RECO ")); break;
1425 case AIS_ERR_: mprintf((0, "ERR_ ")); break;
1428 mprintf((0, " Aware = "));
1430 switch (ailp->player_awareness_type) {
1431 case AIE_FIRE: mprintf((0, "FIRE ")); break;
1432 case AIE_HITT: mprintf((0, "HITT ")); break;
1433 case AIE_COLL: mprintf((0, "COLL ")); break;
1434 case AIE_HURT: mprintf((0, "HURT ")); break;
1437 mprintf((0, "Next fire = %6.3f, Time = %6.3f\n", f2fl(ailp->next_fire), f2fl(ailp->player_awareness_time)));
1442 // -------------------------------------------------------------------------------------------------------------------
1443 int Break_on_object = -1;
1445 void do_firing_stuff(object *obj, int player_visibility, vms_vector *vec_to_player)
1447 if ((Dist_to_last_fired_upon_player_pos < FIRE_AT_NEARBY_PLAYER_THRESHOLD ) || (player_visibility >= 1)) {
1448 // Now, if in robot's field of view, lock onto player
1449 fix dot = vm_vec_dot(&obj->orient.fvec, vec_to_player);
1450 if ((dot >= 7*F1_0/8) || (Players[Player_num].flags & PLAYER_FLAGS_CLOAKED)) {
1451 ai_static *aip = &obj->ctype.ai_info;
1452 ai_local *ailp = &Ai_local_info[OBJECT_NUMBER(obj)];
1454 switch (aip->GOAL_STATE) {
1459 aip->GOAL_STATE = AIS_FIRE;
1460 if (ailp->player_awareness_type <= PA_NEARBY_ROBOT_FIRED) {
1461 ailp->player_awareness_type = PA_NEARBY_ROBOT_FIRED;
1462 ailp->player_awareness_time = PLAYER_AWARENESS_INITIAL_TIME;
1466 } else if (dot >= F1_0/2) {
1467 ai_static *aip = &obj->ctype.ai_info;
1468 switch (aip->GOAL_STATE) {
1472 aip->GOAL_STATE = AIS_LOCK;
1479 // --------------------------------------------------------------------------------------------------------------------
1480 // If a hiding robot gets bumped or hit, he decides to find another hiding place.
1481 void do_ai_robot_hit(object *objp, int type)
1483 if (objp->control_type == CT_AI) {
1484 if ((type == PA_WEAPON_ROBOT_COLLISION) || (type == PA_PLAYER_COLLISION))
1485 switch (objp->ctype.ai_info.behavior) {
1490 // Attack robots (eg, green guy) shouldn't have behavior = still.
1491 Assert(Robot_info[objp->id].attack_type == 0);
1494 // 1/8 time, charge player, 1/4 time create path, rest of time, do nothing
1496 // -- mprintf((0, "Still guy switching to Station, creating path to player."));
1497 create_path_to_player(objp, 10, 1);
1498 objp->ctype.ai_info.behavior = AIB_STATION;
1499 objp->ctype.ai_info.hide_segment = objp->segnum;
1500 Ai_local_info[OBJECT_NUMBER(objp)].mode = AIM_CHASE_OBJECT;
1501 } else if (r < 4096+8192) {
1502 // -- mprintf((0, "Still guy creating n segment path."));
1503 create_n_segment_path(objp, d_rand()/8192 + 2, -1);
1504 Ai_local_info[OBJECT_NUMBER(objp)].mode = AIM_FOLLOW_PATH;
1515 int Cvv_last_time[MAX_OBJECTS];
1516 int Gun_point_hack=0;
1519 int Robot_sound_volume=DEFAULT_ROBOT_SOUND_VOLUME;
1521 // --------------------------------------------------------------------------------------------------------------------
1522 // Note: This function could be optimized. Surely player_is_visible_from_object would benefit from the
1523 // information of a normalized vec_to_player.
1524 // Return player visibility:
1526 // 1 visible, but robot not looking at player (ie, on an unobstructed vector)
1527 // 2 visible and in robot's field of view
1528 // -1 player is cloaked
1529 // If the player is cloaked, set vec_to_player based on time player cloaked and last uncloaked position.
1530 // Updates ailp->previous_visibility if player is not cloaked, in which case the previous visibility is left unchanged
1531 // and is copied to player_visibility
1532 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)
1535 if (Players[Player_num].flags & PLAYER_FLAGS_CLOAKED) {
1536 fix delta_time, dist;
1537 int cloak_index = (OBJECT_NUMBER(objp)) % MAX_AI_CLOAK_INFO;
1539 delta_time = GameTime - Ai_cloak_info[cloak_index].last_time;
1540 if (delta_time > F1_0*2) {
1543 Ai_cloak_info[cloak_index].last_time = GameTime;
1544 make_random_vector(&randvec);
1545 vm_vec_scale_add2(&Ai_cloak_info[cloak_index].last_position, &randvec, 8*delta_time );
1548 dist = vm_vec_normalized_dir_quick(vec_to_player, &Ai_cloak_info[cloak_index].last_position, pos);
1549 *player_visibility = player_is_visible_from_object(objp, pos, robptr->field_of_view[Difficulty_level], vec_to_player);
1550 // *player_visibility = 2;
1552 if ((ailp->next_misc_sound_time < GameTime) && ((ailp->next_fire < F1_0) || (ailp->next_fire2 < F1_0)) && (dist < F1_0*20)) {
1553 // mprintf((0, "ANGRY! "));
1554 ailp->next_misc_sound_time = GameTime + (d_rand() + F1_0) * (7 - Difficulty_level) / 1;
1555 digi_link_sound_to_pos( robptr->see_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume);
1558 // Compute expensive stuff -- vec_to_player and player_visibility
1559 vm_vec_normalized_dir_quick(vec_to_player, &Believed_player_pos, pos);
1560 if ((vec_to_player->x == 0) && (vec_to_player->y == 0) && (vec_to_player->z == 0)) {
1561 // -- mprintf((0, "Warning: Player and robot at exactly the same location.\n"));
1562 vec_to_player->x = F1_0;
1564 *player_visibility = player_is_visible_from_object(objp, pos, robptr->field_of_view[Difficulty_level], vec_to_player);
1566 // This horrible code added by MK in desperation on 12/13/94 to make robots wake up as soon as they
1567 // see you without killing frame rate.
1569 ai_static *aip = &objp->ctype.ai_info;
1570 if ((*player_visibility == 2) && (ailp->previous_visibility != 2))
1571 if ((aip->GOAL_STATE == AIS_REST) || (aip->CURRENT_STATE == AIS_REST)) {
1572 aip->GOAL_STATE = AIS_FIRE;
1573 aip->CURRENT_STATE = AIS_FIRE;
1577 if ((ailp->previous_visibility != *player_visibility) && (*player_visibility == 2)) {
1578 if (ailp->previous_visibility == 0) {
1579 if (ailp->time_player_seen + F1_0/2 < GameTime) {
1580 // -- mprintf((0, "SEE! "));
1581 // -- if (Player_exploded)
1582 // -- digi_link_sound_to_pos( robptr->taunt_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume);
1584 digi_link_sound_to_pos( robptr->see_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume);
1585 ailp->time_player_sound_attacked = GameTime;
1586 ailp->next_misc_sound_time = GameTime + F1_0 + d_rand()*4;
1588 } else if (ailp->time_player_sound_attacked + F1_0/4 < GameTime) {
1589 // -- mprintf((0, "ANGRY! "));
1590 // -- if (Player_exploded)
1591 // -- digi_link_sound_to_pos( robptr->taunt_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume);
1593 digi_link_sound_to_pos( robptr->attack_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume);
1594 ailp->time_player_sound_attacked = GameTime;
1598 if ((*player_visibility == 2) && (ailp->next_misc_sound_time < GameTime)) {
1599 // -- mprintf((0, "ATTACK! "));
1600 ailp->next_misc_sound_time = GameTime + (d_rand() + F1_0) * (7 - Difficulty_level) / 2;
1601 // -- if (Player_exploded)
1602 // -- digi_link_sound_to_pos( robptr->taunt_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume);
1604 digi_link_sound_to_pos( robptr->attack_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume);
1606 ailp->previous_visibility = *player_visibility;
1611 // @mk, 09/21/95: If player view is not obstructed and awareness is at least as high as a nearby collision,
1612 // act is if robot is looking at player.
1613 if (ailp->player_awareness_type >= PA_NEARBY_ROBOT_FIRED)
1614 if (*player_visibility == 1)
1615 *player_visibility = 2;
1617 if (*player_visibility) {
1618 ailp->time_player_seen = GameTime;
1624 // --------------------------------------------------------------------------------------------------------------------
1625 // Move the object objp to a spot in which it doesn't intersect a wall.
1626 // It might mean moving it outside its current segment.
1627 void move_object_to_legal_spot(object *objp)
1629 vms_vector original_pos = objp->pos;
1631 segment *segp = &Segments[objp->segnum];
1633 for (i=0; i<MAX_SIDES_PER_SEGMENT; i++) {
1634 if (WALL_IS_DOORWAY(segp, i) & WID_FLY_FLAG) {
1635 vms_vector segment_center, goal_dir;
1636 fix dist_to_center; // Value not used so far.
1638 compute_segment_center(&segment_center, &Segments[segp->children[i]]);
1639 vm_vec_sub(&goal_dir, &segment_center, &objp->pos);
1640 dist_to_center = vm_vec_normalize_quick(&goal_dir);
1641 vm_vec_scale(&goal_dir, objp->size);
1642 vm_vec_add2(&objp->pos, &goal_dir);
1643 if (!object_intersects_wall(objp)) {
1644 int new_segnum = find_point_seg(&objp->pos, objp->segnum);
1646 if (new_segnum != -1) {
1647 obj_relink(OBJECT_NUMBER(objp), new_segnum);
1651 objp->pos = original_pos;
1655 if (Robot_info[objp->id].boss_flag) {
1656 Int3(); // Note: Boss is poking outside mine. Will try to resolve.
1657 teleport_boss(objp);
1659 mprintf((0, "Note: Killing robot #%i because he's badly stuck outside the mine.\n", OBJECT_NUMBER(objp)));
1660 apply_damage_to_robot(objp, objp->shields*2, OBJECT_NUMBER(objp));
1664 // --------------------------------------------------------------------------------------------------------------------
1665 // Move object one object radii from current position towards segment center.
1666 // If segment center is nearer than 2 radii, move it to center.
1667 void move_towards_segment_center(object *objp)
1669 int segnum = objp->segnum;
1671 vms_vector segment_center, goal_dir;
1673 compute_segment_center(&segment_center, &Segments[segnum]);
1675 vm_vec_sub(&goal_dir, &segment_center, &objp->pos);
1676 dist_to_center = vm_vec_normalize_quick(&goal_dir);
1678 if (dist_to_center < objp->size) {
1679 // Center is nearer than the distance we want to move, so move to center.
1680 objp->pos = segment_center;
1681 mprintf((0, "Object #%i moved to center of segment #%i (%7.3f %7.3f %7.3f)\n", OBJECT_NUMBER(objp), objp->segnum, f2fl(objp->pos.x), f2fl(objp->pos.y), f2fl(objp->pos.z)));
1682 if (object_intersects_wall(objp)) {
1683 mprintf((0, "Object #%i still illegal, trying trickier move.\n"));
1684 move_object_to_legal_spot(objp);
1688 // Move one radii towards center.
1689 vm_vec_scale(&goal_dir, objp->size);
1690 vm_vec_add2(&objp->pos, &goal_dir);
1691 new_segnum = find_point_seg(&objp->pos, objp->segnum);
1692 if (new_segnum == -1) {
1693 objp->pos = segment_center;
1694 move_object_to_legal_spot(objp);
1696 // -- mprintf((0, "Obj %i moved twrds seg %i (%6.2f %6.2f %6.2f), dists: [%6.2f %6.2f]\n", OBJECT_NUMBER(objp), 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))));
1701 extern int Buddy_objnum;
1703 //int Buddy_got_stuck = 0;
1705 // -----------------------------------------------------------------------------------------------------------
1706 // Return true if door can be flown through by a suitable type robot.
1707 // Brains, avoid robots, companions can open doors.
1708 // objp == NULL means treat as buddy.
1709 int ai_door_is_openable(object *objp, segment *segp, int sidenum)
1714 if (!IS_CHILD(segp->children[sidenum]))
1715 return 0; //trap -2 (exit side)
1717 wall_num = segp->sides[sidenum].wall_num;
1719 if (wall_num == -1) //if there's no door at all...
1720 return 0; //..then say it can't be opened
1722 // The mighty console object can open all doors (for purposes of determining paths).
1723 if (objp == ConsoleObject) {
1725 if (Walls[wall_num].type == WALL_DOOR)
1729 wallp = &Walls[wall_num];
1731 if ((objp == NULL) || (Robot_info[objp->id].companion == 1)) {
1734 if (wallp->flags & WALL_BUDDY_PROOF) {
1735 if ((wallp->type == WALL_DOOR) && (wallp->state == WALL_DOOR_CLOSED))
1737 else if (wallp->type == WALL_CLOSED)
1739 else if ((wallp->type == WALL_ILLUSION) && !(wallp->flags & WALL_ILLUSION_OFF))
1743 if (wallp->keys != KEY_NONE) {
1744 if (wallp->keys == KEY_BLUE)
1745 return (Players[Player_num].flags & PLAYER_FLAGS_BLUE_KEY);
1746 else if (wallp->keys == KEY_GOLD)
1747 return (Players[Player_num].flags & PLAYER_FLAGS_GOLD_KEY);
1748 else if (wallp->keys == KEY_RED)
1749 return (Players[Player_num].flags & PLAYER_FLAGS_RED_KEY);
1752 if ((wallp->type != WALL_DOOR) && (wallp->type != WALL_CLOSED))
1755 // If Buddy is returning to player, don't let him think he can get through triggered doors.
1756 // It's only valid to think that if the player is going to get him through. But if he's
1757 // going to the player, the player is probably on the opposite side.
1759 ailp_mode = Ai_local_info[Buddy_objnum].mode;
1761 ailp_mode = Ai_local_info[OBJECT_NUMBER(objp)].mode;
1763 // -- if (Buddy_got_stuck) {
1764 if (ailp_mode == AIM_GOTO_PLAYER) {
1765 if ((wallp->type == WALL_BLASTABLE) && (wallp->state != WALL_BLASTED))
1767 if (wallp->type == WALL_CLOSED)
1769 if (wallp->type == WALL_DOOR) {
1770 if ((wallp->flags & WALL_DOOR_LOCKED) && (wallp->state == WALL_DOOR_CLOSED))
1776 if ((ailp_mode != AIM_GOTO_PLAYER) && (wallp->controlling_trigger != -1)) {
1777 int clip_num = wallp->clip_num;
1781 else if (WallAnims[clip_num].flags & WCF_HIDDEN) {
1782 if (wallp->state == WALL_DOOR_CLOSED)
1790 if (wallp->type == WALL_DOOR) {
1791 if (wallp->type == WALL_BLASTABLE)
1794 int clip_num = wallp->clip_num;
1798 // Buddy allowed to go through secret doors to get to player.
1799 else if ((ailp_mode != AIM_GOTO_PLAYER) && (WallAnims[clip_num].flags & WCF_HIDDEN)) {
1800 if (wallp->state == WALL_DOOR_CLOSED)
1808 } else if ((objp->id == ROBOT_BRAIN) || (objp->ctype.ai_info.behavior == AIB_RUN_FROM) || (objp->ctype.ai_info.behavior == AIB_SNIPE)) {
1811 if ((wallp->type == WALL_DOOR) && (wallp->keys == KEY_NONE) && !(wallp->flags & WALL_DOOR_LOCKED))
1813 else if (wallp->keys != KEY_NONE) { // Allow bots to open doors to which player has keys.
1814 if (wallp->keys & Players[Player_num].flags)
1822 // -----------------------------------------------------------------------------------------------------------
1823 // Return side of openable door in segment, if any. If none, return -1.
1824 int openable_doors_in_segment(int segnum)
1828 if ((segnum < 0) || (segnum > Highest_segment_index))
1831 for (i=0; i<MAX_SIDES_PER_SEGMENT; i++) {
1832 if (Segments[segnum].sides[i].wall_num != -1) {
1833 int wall_num = Segments[segnum].sides[i].wall_num;
1834 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))
1843 // -- // --------------------------------------------------------------------------------------------------------------------
1844 // -- // Return true if a special object (player or control center) is in this segment.
1845 // -- int special_object_in_seg(int segnum)
1849 // -- objnum = Segments[segnum].objects;
1851 // -- while (objnum != -1) {
1852 // -- if ((Objects[objnum].type == OBJ_PLAYER) || (Objects[objnum].type == OBJ_CNTRLCEN)) {
1853 // -- mprintf((0, "Special object of type %i in segment %i\n", Objects[objnum].type, segnum));
1856 // -- objnum = Objects[objnum].next;
1862 // -- // --------------------------------------------------------------------------------------------------------------------
1863 // -- // Randomly select a segment attached to *segp, reachable by flying.
1864 // -- int get_random_child(int segnum)
1867 // -- segment *segp = &Segments[segnum];
1869 // -- sidenum = (rand() * 6) >> 15;
1871 // -- while (!(WALL_IS_DOORWAY(segp, sidenum) & WID_FLY_FLAG))
1872 // -- sidenum = (rand() * 6) >> 15;
1874 // -- segnum = segp->children[sidenum];
1876 // -- return segnum;
1879 // --------------------------------------------------------------------------------------------------------------------
1880 // Return true if placing an object of size size at pos *pos intersects a (player or robot or control center) in segment *segp.
1881 int check_object_object_intersection(vms_vector *pos, fix size, segment *segp)
1885 // If this would intersect with another object (only check those in this segment), then try to move.
1886 curobjnum = segp->objects;
1887 while (curobjnum != -1) {
1888 object *curobjp = &Objects[curobjnum];
1889 if ((curobjp->type == OBJ_PLAYER) || (curobjp->type == OBJ_ROBOT) || (curobjp->type == OBJ_CNTRLCEN)) {
1890 if (vm_vec_dist_quick(pos, &curobjp->pos) < size + curobjp->size)
1893 curobjnum = curobjp->next;
1900 // --------------------------------------------------------------------------------------------------------------------
1901 // Return objnum if object created, else return -1.
1902 // If pos == NULL, pick random spot in segment.
1903 int create_gated_robot( int segnum, int object_id, vms_vector *pos)
1907 segment *segp = &Segments[segnum];
1908 vms_vector object_pos;
1909 robot_info *robptr = &Robot_info[object_id];
1911 fix objsize = Polygon_models[robptr->model_num].rad;
1912 int default_behavior;
1914 if (GameTime - Last_gate_time < Gate_interval)
1917 for (i=0; i<=Highest_object_index; i++)
1918 if (Objects[i].type == OBJ_ROBOT)
1919 if (Objects[i].matcen_creator == BOSS_GATE_MATCEN_NUM)
1922 if (count > 2*Difficulty_level + 6) {
1923 //mprintf((0, "Cannot gate in a robot until you kill one.\n"));
1924 Last_gate_time = GameTime - 3*Gate_interval/4;
1928 compute_segment_center(&object_pos, segp);
1930 pick_random_point_in_seg(&object_pos, SEGMENT_NUMBER(segp));
1934 // See if legal to place object here. If not, move about in segment and try again.
1935 if (check_object_object_intersection(&object_pos, objsize, segp)) {
1936 //mprintf((0, "Can't get in because object collides with something.\n"));
1937 Last_gate_time = GameTime - 3*Gate_interval/4;
1941 objnum = obj_create(OBJ_ROBOT, object_id, segnum, &object_pos, &vmd_identity_matrix, objsize, CT_AI, MT_PHYSICS, RT_POLYOBJ);
1944 // mprintf((1, "Can't get object to gate in robot. Not gating in.\n"));
1945 Last_gate_time = GameTime - 3*Gate_interval/4;
1949 //mprintf((0, "Gating in object %i in segment %i\n", objnum, SEGMENT_NUMBER(segp)));
1951 Objects[objnum].lifeleft = F1_0*30; // Gated in robots only live 30 seconds.
1954 Net_create_objnums[0] = objnum; // A convenient global to get objnum back to caller for multiplayer
1957 objp = &Objects[objnum];
1959 //Set polygon-object-specific data
1961 objp->rtype.pobj_info.model_num = robptr->model_num;
1962 objp->rtype.pobj_info.subobj_flags = 0;
1966 objp->mtype.phys_info.mass = robptr->mass;
1967 objp->mtype.phys_info.drag = robptr->drag;
1969 objp->mtype.phys_info.flags |= (PF_LEVELLING);
1971 objp->shields = robptr->strength;
1972 objp->matcen_creator = BOSS_GATE_MATCEN_NUM; // flag this robot as having been created by the boss.
1974 default_behavior = Robot_info[objp->id].behavior;
1975 init_ai_object( OBJECT_NUMBER(objp), default_behavior, -1 ); // Note, -1 = segment this robot goes to to hide, should probably be something useful
1977 object_create_explosion(segnum, &object_pos, i2f(10), VCLIP_MORPHING_ROBOT );
1978 digi_link_sound_to_pos( Vclip[VCLIP_MORPHING_ROBOT].sound_num, segnum, 0, &object_pos, 0 , F1_0);
1981 Last_gate_time = GameTime;
1983 Players[Player_num].num_robots_level++;
1984 Players[Player_num].num_robots_total++;
1986 return OBJECT_NUMBER(objp);
1989 #define MAX_SPEW_BOT 3
1991 int Spew_bots[NUM_D2_BOSSES][MAX_SPEW_BOT] = {
2003 int Max_spew_bots[NUM_D2_BOSSES] = {2, 1, 2, 3, 3, 3, 3, 3};
2005 // ----------------------------------------------------------------------------------------------------------
2006 // objp points at a boss. He was presumably just hit and he's supposed to create a bot at the hit location *pos.
2007 int boss_spew_robot(object *objp, vms_vector *pos)
2012 boss_index = Robot_info[objp->id].boss_flag - BOSS_D2;
2014 Assert((boss_index >= 0) && (boss_index < NUM_D2_BOSSES));
2016 segnum = find_point_seg(pos, objp->segnum);
2018 mprintf((0, "Tried to spew a bot outside the mine! Aborting!\n"));
2022 objnum = create_gated_robot( segnum, Spew_bots[boss_index][(Max_spew_bots[boss_index] * d_rand()) >> 15], pos);
2024 // Make spewed robot come tumbling out as if blasted by a flash missile.
2026 object *newobjp = &Objects[objnum];
2029 force_val = F1_0/FrameTime;
2032 newobjp->ctype.ai_info.SKIP_AI_COUNT += force_val;
2033 newobjp->mtype.phys_info.rotthrust.x = ((d_rand() - 16384) * force_val)/16;
2034 newobjp->mtype.phys_info.rotthrust.y = ((d_rand() - 16384) * force_val)/16;
2035 newobjp->mtype.phys_info.rotthrust.z = ((d_rand() - 16384) * force_val)/16;
2036 newobjp->mtype.phys_info.flags |= PF_USES_THRUST;
2038 // Now, give a big initial velocity to get moving away from boss.
2039 vm_vec_sub(&newobjp->mtype.phys_info.velocity, pos, &objp->pos);
2040 vm_vec_normalize_quick(&newobjp->mtype.phys_info.velocity);
2041 vm_vec_scale(&newobjp->mtype.phys_info.velocity, F1_0*128);
2048 // --------------------------------------------------------------------------------------------------------------------
2049 // Call this each time the player starts a new ship.
2050 void init_ai_for_ship(void)
2054 for (i=0; i<MAX_AI_CLOAK_INFO; i++) {
2055 Ai_cloak_info[i].last_time = GameTime;
2056 Ai_cloak_info[i].last_segment = ConsoleObject->segnum;
2057 Ai_cloak_info[i].last_position = ConsoleObject->pos;
2061 // --------------------------------------------------------------------------------------------------------------------
2062 // Make object objp gate in a robot.
2063 // The process of him bringing in a robot takes one second.
2064 // Then a robot appears somewhere near the player.
2065 // Return objnum if robot successfully created, else return -1
2066 int gate_in_robot(int type, int segnum)
2069 segnum = Boss_gate_segs[(d_rand() * Num_boss_gate_segs) >> 15];
2071 Assert((segnum >= 0) && (segnum <= Highest_segment_index));
2073 return create_gated_robot(segnum, type, NULL);
2076 // --------------------------------------------------------------------------------------------------------------------
2077 int boss_fits_in_seg(object *boss_objp, int segnum)
2079 vms_vector segcenter;
2080 int boss_objnum = OBJECT_NUMBER(boss_objp);
2083 compute_segment_center(&segcenter, &Segments[segnum]);
2085 for (posnum=0; posnum<9; posnum++) {
2087 vms_vector vertex_pos;
2089 Assert((posnum-1 >= 0) && (posnum-1 < 8));
2090 vertex_pos = Vertices[Segments[segnum].verts[posnum-1]];
2091 vm_vec_avg(&boss_objp->pos, &vertex_pos, &segcenter);
2093 boss_objp->pos = segcenter;
2095 obj_relink(boss_objnum, segnum);
2096 if (!object_intersects_wall(boss_objp))
2103 // --------------------------------------------------------------------------------------------------------------------
2104 void teleport_boss(object *objp)
2106 int rand_segnum, rand_index;
2107 vms_vector boss_dir;
2108 Assert(Num_boss_teleport_segs > 0);
2110 // Pick a random segment from the list of boss-teleportable-to segments.
2111 rand_index = (d_rand() * Num_boss_teleport_segs) >> 15;
2112 rand_segnum = Boss_teleport_segs[rand_index];
2113 Assert((rand_segnum >= 0) && (rand_segnum <= Highest_segment_index));
2115 //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));
2118 if (Game_mode & GM_MULTI)
2119 multi_send_boss_actions(OBJECT_NUMBER(objp), 1, rand_segnum, 0);
2122 compute_segment_center(&objp->pos, &Segments[rand_segnum]);
2123 obj_relink(OBJECT_NUMBER(objp), rand_segnum);
2125 Last_teleport_time = GameTime;
2127 // make boss point right at player
2128 vm_vec_sub(&boss_dir, &Objects[Players[Player_num].objnum].pos, &objp->pos);
2129 vm_vector_2_matrix(&objp->orient, &boss_dir, NULL, NULL);
2131 digi_link_sound_to_pos( Vclip[VCLIP_MORPHING_ROBOT].sound_num, rand_segnum, 0, &objp->pos, 0 , F1_0);
2132 digi_kill_sound_linked_to_object( OBJECT_NUMBER(objp) );
2133 digi_link_sound_to_object2( Robot_info[objp->id].see_sound, OBJECT_NUMBER(objp), 1, F1_0, F1_0*512 ); // F1_0*512 means play twice as loud
2135 // After a teleport, boss can fire right away.
2136 Ai_local_info[OBJECT_NUMBER(objp)].next_fire = 0;
2137 Ai_local_info[OBJECT_NUMBER(objp)].next_fire2 = 0;
2141 // ----------------------------------------------------------------------
2142 void start_boss_death_sequence(object *objp)
2144 if (Robot_info[objp->id].boss_flag) {
2146 Boss_dying_start_time = GameTime;
2151 // ----------------------------------------------------------------------
2152 // General purpose robot-dies-with-death-roll-and-groan code.
2153 // Return true if object just died.
2154 // scale: F1_0*4 for boss, much smaller for much smaller guys
2155 int do_robot_dying_frame(object *objp, fix start_time, fix roll_duration, sbyte *dying_sound_playing, int death_sound, fix expl_scale, fix sound_scale)
2161 roll_duration = F1_0/4;
2163 roll_val = fixdiv(GameTime - start_time, roll_duration);
2165 fix_sincos(fixmul(roll_val, roll_val), &temp, &objp->mtype.phys_info.rotvel.x);
2166 fix_sincos(roll_val, &temp, &objp->mtype.phys_info.rotvel.y);
2167 fix_sincos(roll_val-F1_0/8, &temp, &objp->mtype.phys_info.rotvel.z);
2169 objp->mtype.phys_info.rotvel.x = (GameTime - start_time)/9;
2170 objp->mtype.phys_info.rotvel.y = (GameTime - start_time)/5;
2171 objp->mtype.phys_info.rotvel.z = (GameTime - start_time)/7;
2173 if (digi_sample_rate)
2174 sound_duration = fixdiv(GameSounds[digi_xlat_sound(death_sound)].length,digi_sample_rate);
2176 sound_duration = F1_0;
2178 if (start_time + roll_duration - sound_duration < GameTime) {
2179 if (!*dying_sound_playing) {
2180 mprintf((0, "Starting death sound!\n"));
2181 *dying_sound_playing = 1;
2182 digi_link_sound_to_object2( death_sound, OBJECT_NUMBER(objp), 0, sound_scale, sound_scale*256 ); // F1_0*512 means play twice as loud
2183 } else if (d_rand() < FrameTime*16)
2184 create_small_fireball_on_object(objp, (F1_0 + d_rand()) * (16 * expl_scale/F1_0)/8, 0);
2185 } else if (d_rand() < FrameTime*8)
2186 create_small_fireball_on_object(objp, (F1_0/2 + d_rand()) * (16 * expl_scale/F1_0)/8, 1);
2188 if (start_time + roll_duration < GameTime)
2194 // ----------------------------------------------------------------------
2195 void start_robot_death_sequence(object *objp)
2197 objp->ctype.ai_info.dying_start_time = GameTime;
2198 objp->ctype.ai_info.dying_sound_playing = 0;
2199 objp->ctype.ai_info.SKIP_AI_COUNT = 0;
2203 // ----------------------------------------------------------------------
2204 void do_boss_dying_frame(object *objp)
2208 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);
2211 do_controlcen_destroyed_stuff(NULL);
2212 explode_object(objp, F1_0/4);
2213 digi_link_sound_to_object2(SOUND_BADASS_EXPLOSION, OBJECT_NUMBER(objp), 0, F2_0, F1_0*512);
2217 extern void recreate_thief(object *objp);
2219 // ----------------------------------------------------------------------
2220 int do_any_robot_dying_frame(object *objp)
2222 if (objp->ctype.ai_info.dying_start_time) {
2223 int rval, death_roll;
2225 death_roll = Robot_info[objp->id].death_roll;
2226 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);
2229 explode_object(objp, F1_0/4);
2230 digi_link_sound_to_object2(SOUND_BADASS_EXPLOSION, OBJECT_NUMBER(objp), 0, F2_0, F1_0*512);
2231 if ((Current_level_num < 0) && (Robot_info[objp->id].thief))
2232 recreate_thief(objp);
2241 // --------------------------------------------------------------------------------------------------------------------
2242 // Called for an AI object if it is fairly aware of the player.
2243 // awareness_level is in 0..100. Larger numbers indicate greater awareness (eg, 99 if firing at player).
2244 // In a given frame, might not get called for an object, or might be called more than once.
2245 // The fact that this routine is not called for a given object does not mean that object is not interested in the player.
2246 // Objects are moved by physics, so they can move even if not interested in a player. However, if their velocity or
2247 // orientation is changing, this routine will be called.
2249 // 0 this player IS NOT allowed to move this robot.
2250 // 1 this player IS allowed to move this robot.
2251 int ai_multiplayer_awareness(object *objp, int awareness_level)
2256 if (Game_mode & GM_MULTI) {
2257 if (awareness_level == 0)
2259 rval = multi_can_move_robot(OBJECT_NUMBER(objp), awareness_level);
2268 fix Prev_boss_shields = -1;
2271 // --------------------------------------------------------------------------------------------------------------------
2272 // Do special stuff for a boss.
2273 void do_boss_stuff(object *objp, int player_visibility)
2275 int boss_id, boss_index;
2277 boss_id = Robot_info[objp->id].boss_flag;
2279 Assert((boss_id >= BOSS_D2) && (boss_id < BOSS_D2 + NUM_D2_BOSSES));
2281 boss_index = boss_id - BOSS_D2;
2284 if (objp->shields != Prev_boss_shields) {
2285 mprintf((0, "Boss shields = %7.3f, object %i\n", f2fl(objp->shields), OBJECT_NUMBER(objp)));
2286 Prev_boss_shields = objp->shields;
2290 // New code, fixes stupid bug which meant boss never gated in robots if > 32767 seconds played.
2291 if (Last_teleport_time > GameTime)
2292 Last_teleport_time = GameTime;
2294 if (Last_gate_time > GameTime)
2295 Last_gate_time = GameTime;
2297 // @mk, 10/13/95: Reason:
2298 // Level 4 boss behind locked door. But he's allowed to teleport out of there. So he
2299 // teleports out of there right away, and blasts player right after first door.
2300 if (!player_visibility && (GameTime - Boss_hit_time > F1_0*2))
2303 if (!Boss_dying && Boss_teleports[boss_index]) {
2304 if (objp->ctype.ai_info.CLOAKED == 1) {
2305 Boss_hit_time = GameTime; // Keep the cloak:teleport process going.
2306 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)) {
2307 if (ai_multiplayer_awareness(objp, 98))
2308 teleport_boss(objp);
2309 } else if (GameTime - Boss_hit_time > F1_0*2) {
2310 Last_teleport_time -= Boss_teleport_interval/4;
2313 if (GameTime > Boss_cloak_end_time || GameTime < Boss_cloak_start_time)
2314 objp->ctype.ai_info.CLOAKED = 0;
2315 } else if ((GameTime - Boss_cloak_end_time > Boss_cloak_interval) || (GameTime - Boss_cloak_end_time < -Boss_cloak_duration)) {
2316 if (ai_multiplayer_awareness(objp, 95)) {
2317 Boss_cloak_start_time = GameTime;
2318 Boss_cloak_end_time = GameTime+Boss_cloak_duration;
2319 objp->ctype.ai_info.CLOAKED = 1;
2321 if (Game_mode & GM_MULTI)
2322 multi_send_boss_actions(OBJECT_NUMBER(objp), 2, 0, 0);
2330 #define BOSS_TO_PLAYER_GATE_DISTANCE (F1_0*200)
2332 // -- Obsolete D1 code -- // --------------------------------------------------------------------------------------------------------------------
2333 // -- Obsolete D1 code -- // Do special stuff for a boss.
2334 // -- Obsolete D1 code -- void do_super_boss_stuff(object *objp, fix dist_to_player, int player_visibility)
2335 // -- Obsolete D1 code -- {
2336 // -- Obsolete D1 code -- static int eclip_state = 0;
2337 // -- Obsolete D1 code --
2338 // -- Obsolete D1 code -- do_boss_stuff(objp, player_visibility);
2339 // -- Obsolete D1 code --
2340 // -- Obsolete D1 code -- // Only master player can cause gating to occur.
2341 // -- Obsolete D1 code -- if ((Game_mode & GM_MULTI) && !network_i_am_master())
2342 // -- Obsolete D1 code -- return;
2343 // -- Obsolete D1 code --
2344 // -- Obsolete D1 code -- if ((dist_to_player < BOSS_TO_PLAYER_GATE_DISTANCE) || player_visibility || (Game_mode & GM_MULTI)) {
2345 // -- Obsolete D1 code -- if (GameTime - Last_gate_time > Gate_interval/2) {
2346 // -- Obsolete D1 code -- restart_effect(BOSS_ECLIP_NUM);
2347 // -- Obsolete D1 code -- if (eclip_state == 0) {
2348 // -- Obsolete D1 code -- multi_send_boss_actions(OBJECT_NUMBER(objp), 4, 0, 0);
2349 // -- Obsolete D1 code -- eclip_state = 1;
2350 // -- Obsolete D1 code -- }
2351 // -- Obsolete D1 code -- }
2352 // -- Obsolete D1 code -- else {
2353 // -- Obsolete D1 code -- stop_effect(BOSS_ECLIP_NUM);
2354 // -- Obsolete D1 code -- if (eclip_state == 1) {
2355 // -- Obsolete D1 code -- multi_send_boss_actions(OBJECT_NUMBER(objp), 5, 0, 0);
2356 // -- Obsolete D1 code -- eclip_state = 0;
2357 // -- Obsolete D1 code -- }
2358 // -- Obsolete D1 code -- }
2359 // -- Obsolete D1 code --
2360 // -- Obsolete D1 code -- if (GameTime - Last_gate_time > Gate_interval)
2361 // -- Obsolete D1 code -- if (ai_multiplayer_awareness(objp, 99)) {
2362 // -- Obsolete D1 code -- int rtval;
2363 // -- Obsolete D1 code -- int randtype = (d_rand() * MAX_GATE_INDEX) >> 15;
2364 // -- Obsolete D1 code --
2365 // -- Obsolete D1 code -- Assert(randtype < MAX_GATE_INDEX);
2366 // -- Obsolete D1 code -- randtype = Super_boss_gate_list[randtype];
2367 // -- Obsolete D1 code -- Assert(randtype < N_robot_types);
2368 // -- Obsolete D1 code --
2369 // -- Obsolete D1 code -- rtval = gate_in_robot(randtype, -1);
2370 // -- Obsolete D1 code -- if ((rtval != -1) && (Game_mode & GM_MULTI))
2371 // -- Obsolete D1 code -- {
2372 // -- Obsolete D1 code -- multi_send_boss_actions(OBJECT_NUMBER(objp), 3, randtype, Net_create_objnums[0]);
2373 // -- Obsolete D1 code -- map_objnum_local_to_local(Net_create_objnums[0]);
2374 // -- Obsolete D1 code -- }
2375 // -- Obsolete D1 code -- }
2376 // -- Obsolete D1 code -- }
2377 // -- Obsolete D1 code -- }
2379 //int multi_can_move_robot(object *objp, int awareness_level)
2384 void ai_multi_send_robot_position(int objnum, int force)
2387 if (Game_mode & GM_MULTI)
2390 multi_send_robot_position(objnum, 1);
2392 multi_send_robot_position(objnum, 0);
2398 // --------------------------------------------------------------------------------------------------------------------
2399 // Returns true if this object should be allowed to fire at the player.
2400 int maybe_ai_do_actual_firing_stuff(object *obj, ai_static *aip)
2403 if (Game_mode & GM_MULTI)
2404 if ((aip->GOAL_STATE != AIS_FLIN) && (obj->id != ROBOT_BRAIN))
2405 if (aip->CURRENT_STATE == AIS_FIRE)
2412 vms_vector Last_fired_upon_player_pos;
2414 // --------------------------------------------------------------------------------------------------------------------
2415 // If fire_anyway, fire even if player is not visible. We're firing near where we believe him to be. Perhaps he's
2416 // lurking behind a corner.
2417 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)
2421 if ((player_visibility == 2) || (Dist_to_last_fired_upon_player_pos < FIRE_AT_NEARBY_PLAYER_THRESHOLD )) {
2422 vms_vector fire_pos;
2424 fire_pos = Believed_player_pos;
2426 // Hack: If visibility not == 2, we're here because we're firing at a nearby player.
2427 // So, fire at Last_fired_upon_player_pos instead of the player position.
2428 if (!robptr->attack_type && (player_visibility != 2))
2429 fire_pos = Last_fired_upon_player_pos;
2431 // Changed by mk, 01/04/95, onearm would take about 9 seconds until he can fire at you.
2432 // 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.
2433 if (!object_animates || ready_to_fire(robptr, ailp)) {
2434 dot = vm_vec_dot(&obj->orient.fvec, vec_to_player);
2435 if ((dot >= 7*F1_0/8) || ((dot > F1_0/4) && robptr->boss_flag)) {
2437 if (gun_num < Robot_info[obj->id].n_guns) {
2438 if (robptr->attack_type == 1) {
2439 if (!Player_exploded && (dist_to_player < obj->size + ConsoleObject->size + F1_0*2)) { // robptr->circle_distance[Difficulty_level] + ConsoleObject->size) {
2440 if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION-2))
2442 do_ai_robot_hit_attack(obj, ConsoleObject, &obj->pos);
2444 // 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)));
2448 if ((gun_point->x == 0) && (gun_point->y == 0) && (gun_point->z == 0)) {
2449 ; //mprintf((0, "Would like to fire gun, but gun not selected.\n"));
2451 if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION))
2453 // New, multi-weapon-type system, 06/05/95 (life is slipping away...)
2455 if (ailp->next_fire <= 0) {
2456 ai_fire_laser_at_player(obj, gun_point, gun_num, &fire_pos);
2457 Last_fired_upon_player_pos = fire_pos;
2460 if ((ailp->next_fire2 <= 0) && (robptr->weapon_type2 != -1)) {
2461 calc_gun_point(gun_point, obj, 0);
2462 ai_fire_laser_at_player(obj, gun_point, 0, &fire_pos);
2463 Last_fired_upon_player_pos = fire_pos;
2466 } else if (ailp->next_fire <= 0) {
2467 ai_fire_laser_at_player(obj, gun_point, gun_num, &fire_pos);
2468 Last_fired_upon_player_pos = fire_pos;
2473 // Wants to fire, so should go into chase mode, probably.
2474 if ( (aip->behavior != AIB_RUN_FROM)
2475 && (aip->behavior != AIB_STILL)
2476 && (aip->behavior != AIB_SNIPE)
2477 && (aip->behavior != AIB_FOLLOW)
2478 && (!robptr->attack_type)
2479 && ((ailp->mode == AIM_FOLLOW_PATH) || (ailp->mode == AIM_STILL)))
2480 ailp->mode = AIM_CHASE_OBJECT;
2483 aip->GOAL_STATE = AIS_RECO;
2484 ailp->goal_state[aip->CURRENT_GUN] = AIS_RECO;
2486 // Switch to next gun for next fire. If has 2 gun types, select gun #1, if exists.
2488 if (aip->CURRENT_GUN >= Robot_info[obj->id].n_guns)
2490 if ((Robot_info[obj->id].n_guns == 1) || (Robot_info[obj->id].weapon_type2 == -1))
2491 aip->CURRENT_GUN = 0;
2493 aip->CURRENT_GUN = 1;
2497 } 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))) ) {
2498 // Robots which fire homing weapons might fire even if they don't have a bead on the player.
2499 if (((!object_animates) || (ailp->achieved_state[aip->CURRENT_GUN] == AIS_FIRE))
2500 && (((ailp->next_fire <= 0) && (aip->CURRENT_GUN != 0)) || ((ailp->next_fire2 <= 0) && (aip->CURRENT_GUN == 0)))
2501 && (vm_vec_dist_quick(&Hit_pos, &obj->pos) > F1_0*40)) {
2502 if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION))
2504 ai_fire_laser_at_player(obj, gun_point, gun_num, &Believed_player_pos);
2506 aip->GOAL_STATE = AIS_RECO;
2507 ailp->goal_state[aip->CURRENT_GUN] = AIS_RECO;
2509 // Switch to next gun for next fire.
2511 if (aip->CURRENT_GUN >= Robot_info[obj->id].n_guns)
2512 aip->CURRENT_GUN = 0;
2514 // Switch to next gun for next fire.
2516 if (aip->CURRENT_GUN >= Robot_info[obj->id].n_guns)
2517 aip->CURRENT_GUN = 0;
2522 // ---------------------------------------------------------------
2524 vms_vector vec_to_last_pos;
2526 if (d_rand()/2 < fixmul(FrameTime, (Difficulty_level << 12) + 0x4000)) {
2527 if ((!object_animates || ready_to_fire(robptr, ailp)) && (Dist_to_last_fired_upon_player_pos < FIRE_AT_NEARBY_PLAYER_THRESHOLD)) {
2528 vm_vec_normalized_dir_quick(&vec_to_last_pos, &Believed_player_pos, &obj->pos);
2529 dot = vm_vec_dot(&obj->orient.fvec, &vec_to_last_pos);
2530 if (dot >= 7*F1_0/8) {
2532 if (aip->CURRENT_GUN < Robot_info[obj->id].n_guns) {
2533 if (robptr->attack_type == 1) {
2534 if (!Player_exploded && (dist_to_player < obj->size + ConsoleObject->size + F1_0*2)) { // robptr->circle_distance[Difficulty_level] + ConsoleObject->size) {
2535 if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION-2))
2537 do_ai_robot_hit_attack(obj, ConsoleObject, &obj->pos);
2539 // 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)));
2543 if ((gun_point->x == 0) && (gun_point->y == 0) && (gun_point->z == 0)) {
2544 ; //mprintf((0, "Would like to fire gun, but gun not selected.\n"));
2546 if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION))
2548 // New, multi-weapon-type system, 06/05/95 (life is slipping away...)
2550 if (ailp->next_fire <= 0)
2551 ai_fire_laser_at_player(obj, gun_point, gun_num, &Last_fired_upon_player_pos);
2553 if ((ailp->next_fire2 <= 0) && (robptr->weapon_type2 != -1)) {
2554 calc_gun_point(gun_point, obj, 0);
2555 ai_fire_laser_at_player(obj, gun_point, 0, &Last_fired_upon_player_pos);
2558 } else if (ailp->next_fire <= 0)
2559 ai_fire_laser_at_player(obj, gun_point, gun_num, &Last_fired_upon_player_pos);
2563 // Wants to fire, so should go into chase mode, probably.
2564 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)))
2565 ailp->mode = AIM_CHASE_OBJECT;
2567 aip->GOAL_STATE = AIS_RECO;
2568 ailp->goal_state[aip->CURRENT_GUN] = AIS_RECO;
2570 // Switch to next gun for next fire.
2572 if (aip->CURRENT_GUN >= Robot_info[obj->id].n_guns)
2574 if (Robot_info[obj->id].n_guns == 1)
2575 aip->CURRENT_GUN = 0;
2577 aip->CURRENT_GUN = 1;
2584 // ---------------------------------------------------------------