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.
36 #include "editor/editor.h"
40 void teleport_boss(object *objp);
41 int boss_fits_in_seg(object *boss_objp, int segnum);
48 static const sbyte Mike_to_matt_xlate[] = {AS_REST, AS_REST, AS_ALERT, AS_ALERT, AS_FLINCH, AS_FIRE, AS_RECOIL, AS_REST};
50 // Amount of time since the current robot was last processed for things such as movement.
51 // It is not valid to use FrameTime because robots do not get moved every frame.
53 int Num_boss_teleport_segs;
54 short Boss_teleport_segs[MAX_BOSS_TELEPORT_SEGS];
55 int Num_boss_gate_segs;
56 short Boss_gate_segs[MAX_BOSS_TELEPORT_SEGS];
58 // ---------------------------------------------------------
59 // On entry, N_robot_types had darn sure better be set.
60 // Mallocs N_robot_types robot_info structs into global Robot_info.
61 void init_ai_system(void)
66 mprintf((0, "Trying to malloc %i bytes for Robot_info.\n", N_robot_types * sizeof(*Robot_info)));
67 Robot_info = (robot_info *) d_malloc( N_robot_types * sizeof(*Robot_info) );
68 mprintf((0, "Robot_info = %i\n", Robot_info));
70 for (i=0; i<N_robot_types; i++) {
71 Robot_info[i].field_of_view = F1_0/2;
72 Robot_info[i].firing_wait = F1_0;
73 Robot_info[i].turn_time = F1_0*2;
74 // -- Robot_info[i].fire_power = F1_0;
75 // -- Robot_info[i].shield = F1_0/2;
76 Robot_info[i].max_speed = F1_0*10;
77 Robot_info[i].always_0xabcd = 0xabcd;
83 // ---------------------------------------------------------------------------------------------------------------------
84 // Given a behavior, set initial mode.
85 int ai_behavior_to_mode(int behavior)
88 case AIB_STILL: return AIM_STILL;
89 case AIB_NORMAL: return AIM_CHASE_OBJECT;
90 case AIB_BEHIND: return AIM_BEHIND;
91 case AIB_RUN_FROM: return AIM_RUN_FROM_OBJECT;
92 case AIB_SNIPE: return AIM_STILL; // Changed, 09/13/95, MK, snipers are still until they see you or are hit.
93 case AIB_STATION: return AIM_STILL;
94 case AIB_FOLLOW: return AIM_FOLLOW_PATH;
95 default: Int3(); // Contact Mike: Error, illegal behavior type
101 // ---------------------------------------------------------------------------------------------------------------------
102 // Call every time the player starts a new ship.
103 void ai_init_boss_for_ship(void)
105 Boss_hit_time = -F1_0*10;
109 // ---------------------------------------------------------------------------------------------------------------------
110 // initial_mode == -1 means leave mode unchanged.
111 void init_ai_object(int objnum, int behavior, int hide_segment)
113 object *objp = &Objects[objnum];
114 ai_static *aip = &objp->ctype.ai_info;
115 ai_local *ailp = &Ai_local_info[objnum];
116 robot_info *robptr = &Robot_info[objp->id];
119 // mprintf((0, "Behavior of 0 for object #%i, bashing to AIB_NORMAL.\n", objnum));
120 behavior = AIB_NORMAL;
121 aip->behavior = behavior;
123 // mprintf((0, "Initializing object #%i\n", objnum));
125 // mode is now set from the Robot dialog, so this should get overwritten.
126 ailp->mode = AIM_STILL;
128 ailp->previous_visibility = 0;
130 if (behavior != -1) {
131 aip->behavior = behavior;
132 ailp->mode = ai_behavior_to_mode(aip->behavior);
133 } else if (!((aip->behavior >= MIN_BEHAVIOR) && (aip->behavior <= MAX_BEHAVIOR))) {
134 mprintf((0, "[obj %i -> normal] ", objnum));
135 aip->behavior = AIB_NORMAL;
138 if (robptr->companion) {
139 ailp->mode = AIM_GOTO_PLAYER;
140 Escort_kill_object = -1;
144 aip->behavior = AIB_SNIPE;
145 ailp->mode = AIM_THIEF_WAIT;
148 if (robptr->attack_type) {
149 aip->behavior = AIB_NORMAL;
150 ailp->mode = ai_behavior_to_mode(aip->behavior);
153 // This is astonishingly stupid! This routine gets called by matcens! KILL KILL KILL!!! Point_segs_free_ptr = Point_segs;
155 vm_vec_zero(&objp->mtype.phys_info.velocity);
156 // -- ailp->wait_time = F1_0*5;
157 ailp->player_awareness_time = 0;
158 ailp->player_awareness_type = 0;
159 aip->GOAL_STATE = AIS_SRCH;
160 aip->CURRENT_STATE = AIS_REST;
161 ailp->time_player_seen = GameTime;
162 ailp->next_misc_sound_time = GameTime;
163 ailp->time_player_sound_attacked = GameTime;
165 if ((behavior == AIB_SNIPE) || (behavior == AIB_STATION) || (behavior == AIB_RUN_FROM) || (behavior == AIB_FOLLOW)) {
166 aip->hide_segment = hide_segment;
167 ailp->goal_segment = hide_segment;
168 aip->hide_index = -1; // This means the path has not yet been created.
169 aip->cur_path_index = 0;
172 aip->SKIP_AI_COUNT = 0;
174 if (robptr->cloak_type == RI_CLOAKED_ALWAYS)
179 objp->mtype.phys_info.flags |= (PF_BOUNCE | PF_TURNROLL);
181 aip->REMOTE_OWNER = -1;
183 aip->dying_sound_playing = 0;
184 aip->dying_start_time = 0;
189 extern object * create_morph_robot( segment *segp, vms_vector *object_pos, int object_id);
191 // --------------------------------------------------------------------------------------------------------------------
192 // Create a Buddy bot.
193 // This automatically happens when you bring up the Buddy menu in a debug version.
194 // It is available as a cheat in a non-debug (release) version.
195 void create_buddy_bot(void)
198 vms_vector object_pos;
200 for (buddy_id=0; buddy_id<N_robot_types; buddy_id++)
201 if (Robot_info[buddy_id].companion)
204 if (buddy_id == N_robot_types) {
205 mprintf((0, "Can't create Buddy. No 'companion' bot found in Robot_info!\n"));
209 compute_segment_center(&object_pos, &Segments[ConsoleObject->segnum]);
211 create_morph_robot( &Segments[ConsoleObject->segnum], &object_pos, buddy_id);
214 #define QUEUE_SIZE 256
216 // --------------------------------------------------------------------------------------------------------------------
217 // Create list of segments boss is allowed to teleport to at segptr.
219 // Boss is allowed to teleport to segments he fits in (calls object_intersects_wall) and
220 // he can reach from his initial position (calls find_connected_distance).
221 // If size_check is set, then only add segment if boss can fit in it, else any segment is legal.
222 // one_wall_hack added by MK, 10/13/95: A mega-hack! Set to !0 to ignore the
223 void init_boss_segments(short segptr[], int *num_segs, int size_check, int one_wall_hack)
235 mprintf((0, "Boss fits in segments:\n"));
236 // See if there is a boss. If not, quick out.
237 for (i=0; i<=Highest_object_index; i++)
238 if ((Objects[i].type == OBJ_ROBOT) && (Robot_info[Objects[i].id].boss_flag)) {
239 if (boss_objnum != -1) // There are two bosses in this mine! i and boss_objnum!
240 Int3(); //do int3 here instead of assert so museum will work
244 if (boss_objnum != -1) {
245 int original_boss_seg;
246 vms_vector original_boss_pos;
247 object *boss_objp = &Objects[boss_objnum];
249 int seg_queue[QUEUE_SIZE];
250 //ALREADY IN RENDER.H sbyte visited[MAX_SEGMENTS];
253 boss_size_save = boss_objp->size;
254 // -- Causes problems!! -- boss_objp->size = fixmul((F1_0/4)*3, boss_objp->size);
255 original_boss_seg = boss_objp->segnum;
256 original_boss_pos = boss_objp->pos;
259 seg_queue[head++] = original_boss_seg;
261 segptr[(*num_segs)++] = original_boss_seg;
262 mprintf((0, "%4i ", original_boss_seg));
264 Selected_segs[N_selected_segs++] = original_boss_seg;
267 for (i=0; i<=Highest_segment_index; i++)
270 while (tail != head) {
272 segment *segp = &Segments[seg_queue[tail++]];
274 tail &= QUEUE_SIZE-1;
276 for (sidenum=0; sidenum<MAX_SIDES_PER_SEGMENT; sidenum++) {
279 if (((w = WALL_IS_DOORWAY(segp, sidenum)) & WID_FLY_FLAG) || one_wall_hack) {
280 // If we get here and w == WID_WALL, then we want to process through this wall, else not.
281 if (IS_CHILD(segp->children[sidenum])) {
287 if (visited[segp->children[sidenum]] == 0) {
288 seg_queue[head++] = segp->children[sidenum];
289 visited[segp->children[sidenum]] = 1;
290 head &= QUEUE_SIZE-1;
292 if (head == tail + QUEUE_SIZE-1)
293 Int3(); // queue overflow. Make it bigger!
295 if (head+QUEUE_SIZE == tail + QUEUE_SIZE-1)
296 Int3(); // queue overflow. Make it bigger!
298 if ((!size_check) || boss_fits_in_seg(boss_objp, segp->children[sidenum])) {
299 segptr[(*num_segs)++] = segp->children[sidenum];
300 if (size_check) mprintf((0, "%4i ", segp->children[sidenum]));
302 Selected_segs[N_selected_segs++] = segp->children[sidenum];
304 if (*num_segs >= MAX_BOSS_TELEPORT_SEGS) {
305 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));
315 boss_objp->size = boss_size_save;
316 boss_objp->pos = original_boss_pos;
317 obj_relink(boss_objnum, original_boss_seg);
323 extern void init_buddy_for_level(void);
325 // ---------------------------------------------------------------------------------------------------------------------
326 void init_ai_objects(void)
330 Point_segs_free_ptr = Point_segs;
332 for (i=0; i<MAX_OBJECTS; i++) {
333 object *objp = &Objects[i];
335 if (objp->control_type == CT_AI)
336 init_ai_object(i, objp->ctype.ai_info.behavior, objp->ctype.ai_info.hide_segment);
339 init_boss_segments(Boss_gate_segs, &Num_boss_gate_segs, 0, 0);
341 init_boss_segments(Boss_teleport_segs, &Num_boss_teleport_segs, 1, 0);
342 if (Num_boss_teleport_segs == 1)
343 init_boss_segments(Boss_teleport_segs, &Num_boss_teleport_segs, 1, 1);
345 Boss_dying_sound_playing = 0;
347 // -- unused! MK, 10/21/95 -- Boss_been_hit = 0;
348 Gate_interval = F1_0*4 - Difficulty_level*i2f(2)/3;
354 init_buddy_for_level();
356 if (Current_level_num == Last_level) {
357 Boss_teleport_interval = F1_0*10;
358 Boss_cloak_interval = F1_0*15; // Time between cloaks
360 Boss_teleport_interval = F1_0*7;
361 Boss_cloak_interval = F1_0*10; // Time between cloaks
368 fix Firing_wait_copy[MAX_ROBOT_TYPES];
369 fix Firing_wait2_copy[MAX_ROBOT_TYPES];
370 sbyte Rapidfire_count_copy[MAX_ROBOT_TYPES];
372 void do_lunacy_on(void)
376 if (Lunacy) //already on
381 Diff_save = Difficulty_level;
382 Difficulty_level = NDL-1;
384 for (i=0; i<MAX_ROBOT_TYPES; i++) {
385 Firing_wait_copy[i] = Robot_info[i].firing_wait[NDL-1];
386 Firing_wait2_copy[i] = Robot_info[i].firing_wait2[NDL-1];
387 Rapidfire_count_copy[i] = Robot_info[i].rapidfire_count[NDL-1];
389 Robot_info[i].firing_wait[NDL-1] = Robot_info[i].firing_wait[1];
390 Robot_info[i].firing_wait2[NDL-1] = Robot_info[i].firing_wait2[1];
391 Robot_info[i].rapidfire_count[NDL-1] = Robot_info[i].rapidfire_count[1];
396 void do_lunacy_off(void)
400 if (!Lunacy) //already off
405 for (i=0; i<MAX_ROBOT_TYPES; i++) {
406 Robot_info[i].firing_wait[NDL-1] = Firing_wait_copy[i];
407 Robot_info[i].firing_wait2[NDL-1] = Firing_wait2_copy[i];
408 Robot_info[i].rapidfire_count[NDL-1] = Rapidfire_count_copy[i];
411 Difficulty_level = Diff_save;
414 // ----------------------------------------------------------------
415 // Do *dest = *delta unless:
416 // *delta is pretty small
417 // and they are of different signs.
418 void set_rotvel_and_saturate(fix *dest, fix delta)
420 if ((delta ^ *dest) < 0) {
421 if (abs(delta) < F1_0/8) {
422 // mprintf((0, "D"));
425 // mprintf((0, "d"));
428 // mprintf((0, "!"));
433 //--debug-- #ifndef NDEBUG
434 //--debug-- int Total_turns=0;
435 //--debug-- int Prevented_turns=0;
438 #define AI_TURN_SCALE 1
439 #define BABY_SPIDER_ID 14
440 #define FIRE_AT_NEARBY_PLAYER_THRESHOLD (F1_0*40)
442 extern void physics_turn_towards_vector(vms_vector *goal_vector, object *obj, fix rate);
443 extern fix Seismic_tremor_magnitude;
445 //-------------------------------------------------------------------------------------------
446 void ai_turn_towards_vector(vms_vector *goal_vector, object *objp, fix rate)
451 // Not all robots can turn, eg, SPECIAL_REACTOR_ROBOT
455 if ((objp->id == BABY_SPIDER_ID) && (objp->type == OBJ_ROBOT)) {
456 physics_turn_towards_vector(goal_vector, objp, rate);
460 new_fvec = *goal_vector;
462 dot = vm_vec_dot(goal_vector, &objp->orient.fvec);
464 if (dot < (F1_0 - FrameTime/2)) {
466 fix new_scale = fixdiv(FrameTime * AI_TURN_SCALE, rate);
467 vm_vec_scale(&new_fvec, new_scale);
468 vm_vec_add2(&new_fvec, &objp->orient.fvec);
469 mag = vm_vec_normalize_quick(&new_fvec);
470 if (mag < F1_0/256) {
471 mprintf((1, "Degenerate vector in ai_turn_towards_vector (mag = %7.3f)\n", f2fl(mag)));
472 new_fvec = *goal_vector; // if degenerate vector, go right to goal
476 if (Seismic_tremor_magnitude) {
479 make_random_vector(&rand_vec);
480 scale = fixdiv(2*Seismic_tremor_magnitude, Robot_info[objp->id].mass);
481 vm_vec_scale_add2(&new_fvec, &rand_vec, scale);
484 vm_vector_2_matrix(&objp->orient, &new_fvec, NULL, &objp->orient.rvec);
487 // -- unused, 08/07/95 -- // --------------------------------------------------------------------------------------------------------------------
488 // -- unused, 08/07/95 -- void ai_turn_randomly(vms_vector *vec_to_player, object *obj, fix rate, int previous_visibility)
489 // -- unused, 08/07/95 -- {
490 // -- unused, 08/07/95 -- vms_vector curvec;
491 // -- unused, 08/07/95 --
492 // -- unused, 08/07/95 -- // -- MK, 06/09/95 // Random turning looks too stupid, so 1/4 of time, cheat.
493 // -- unused, 08/07/95 -- // -- MK, 06/09/95 if (previous_visibility)
494 // -- unused, 08/07/95 -- // -- MK, 06/09/95 if (d_rand() > 0x7400) {
495 // -- unused, 08/07/95 -- // -- MK, 06/09/95 ai_turn_towards_vector(vec_to_player, obj, rate);
496 // -- unused, 08/07/95 -- // -- MK, 06/09/95 return;
497 // -- unused, 08/07/95 -- // -- MK, 06/09/95 }
498 // -- unused, 08/07/95 --
499 // -- unused, 08/07/95 -- curvec = obj->mtype.phys_info.rotvel;
500 // -- unused, 08/07/95 --
501 // -- unused, 08/07/95 -- curvec.y += F1_0/64;
502 // -- unused, 08/07/95 --
503 // -- unused, 08/07/95 -- curvec.x += curvec.y/6;
504 // -- unused, 08/07/95 -- curvec.y += curvec.z/4;
505 // -- unused, 08/07/95 -- curvec.z += curvec.x/10;
506 // -- unused, 08/07/95 --
507 // -- unused, 08/07/95 -- if (abs(curvec.x) > F1_0/8) curvec.x /= 4;
508 // -- unused, 08/07/95 -- if (abs(curvec.y) > F1_0/8) curvec.y /= 4;
509 // -- unused, 08/07/95 -- if (abs(curvec.z) > F1_0/8) curvec.z /= 4;
510 // -- unused, 08/07/95 --
511 // -- unused, 08/07/95 -- obj->mtype.phys_info.rotvel = curvec;
512 // -- unused, 08/07/95 --
513 // -- unused, 08/07/95 -- }
515 // Overall_agitation affects:
516 // Widens field of view. Field of view is in range 0..1 (specified in bitmaps.tbl as N/360 degrees).
517 // Overall_agitation/128 subtracted from field of view, making robots see wider.
518 // Increases distance to which robot will search to create path to player by Overall_agitation/8 segments.
519 // Decreases wait between fire times by Overall_agitation/64 seconds.
522 // --------------------------------------------------------------------------------------------------------------------
524 // 0 Player is not visible from object, obstruction or something.
525 // 1 Player is visible, but not in field of view.
526 // 2 Player is visible and in field of view.
527 // Note: Uses Believed_player_pos as player's position for cloak effect.
528 // NOTE: Will destructively modify *pos if *pos is outside the mine.
529 int player_is_visible_from_object(object *objp, vms_vector *pos, fix field_of_view, vms_vector *vec_to_player)
534 // Assume that robot's gun tip is in same segment as robot's center.
535 objp->ctype.ai_info.SUB_FLAGS &= ~SUB_FLAGS_GUNSEG;
538 if ((pos->x != objp->pos.x) || (pos->y != objp->pos.y) || (pos->z != objp->pos.z)) {
539 int segnum = find_point_seg(pos, objp->segnum);
541 fq.startseg = objp->segnum;
543 mprintf((1, "Object %i, gun is outside mine, moving towards center.\n", OBJECT_NUMBER(objp)));
544 move_towards_segment_center(objp);
546 if (segnum != objp->segnum) {
547 // -- mprintf((0, "Warning: Robot's gun tip not in same segment as robot center, frame %i.\n", FrameCount));
548 objp->ctype.ai_info.SUB_FLAGS |= SUB_FLAGS_GUNSEG;
550 fq.startseg = segnum;
553 fq.startseg = objp->segnum;
554 fq.p1 = &Believed_player_pos;
556 fq.thisobjnum = OBJECT_NUMBER(objp);
557 fq.ignore_obj_list = NULL;
558 fq.flags = FQ_TRANSWALL; // -- Why were we checking objects? | FQ_CHECK_OBJS; //what about trans walls???
560 Hit_type = find_vector_intersection(&fq,&Hit_data);
562 Hit_pos = Hit_data.hit_pnt;
563 Hit_seg = Hit_data.hit_seg;
565 // -- when we stupidly checked objects -- if ((Hit_type == HIT_NONE) || ((Hit_type == HIT_OBJECT) && (Hit_data.hit_object == Players[Player_num].objnum))) {
566 if (Hit_type == HIT_NONE) {
567 dot = vm_vec_dot(vec_to_player, &objp->orient.fvec);
568 // 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)));
569 if (dot > field_of_view - (Overall_agitation << 9)) {
579 // ------------------------------------------------------------------------------------------------------------------
580 // Return 1 if animates, else return 0
581 int do_silly_animation(object *objp)
583 int objnum = OBJECT_NUMBER(objp);
585 int robot_type, gun_num, robot_state, num_joint_positions;
586 polyobj_info *pobj_info = &objp->rtype.pobj_info;
587 ai_static *aip = &objp->ctype.ai_info;
588 // ai_local *ailp = &Ai_local_info[objnum];
589 int num_guns, at_goal;
591 int flinch_attack_scale = 1;
593 robot_type = objp->id;
594 num_guns = Robot_info[robot_type].n_guns;
595 attack_type = Robot_info[robot_type].attack_type;
598 // mprintf((0, "Object #%i of type #%i has 0 guns.\n", OBJECT_NUMBER(objp), robot_type));
602 // This is a hack. All positions should be based on goal_state, not GOAL_STATE.
603 robot_state = Mike_to_matt_xlate[aip->GOAL_STATE];
604 // previous_robot_state = Mike_to_matt_xlate[aip->CURRENT_STATE];
606 if (attack_type) // && ((robot_state == AS_FIRE) || (robot_state == AS_RECOIL)))
607 flinch_attack_scale = Attack_scale;
608 else if ((robot_state == AS_FLINCH) || (robot_state == AS_RECOIL))
609 flinch_attack_scale = Flinch_scale;
612 for (gun_num=0; gun_num <= num_guns; gun_num++) {
615 num_joint_positions = robot_get_anim_state(&jp_list, robot_type, gun_num, robot_state);
617 for (joint=0; joint<num_joint_positions; joint++) {
618 fix delta_angle, delta_2;
619 int jointnum = jp_list[joint].jointnum;
620 vms_angvec *jp = &jp_list[joint].angles;
621 vms_angvec *pobjp = &pobj_info->anim_angles[jointnum];
623 if (jointnum >= Polygon_models[objp->rtype.pobj_info.model_num].n_models) {
624 Int3(); // Contact Mike: incompatible data, illegal jointnum, problem in pof file?
627 if (jp->p != pobjp->p) {
630 Ai_local_info[objnum].goal_angles[jointnum].p = jp->p;
632 delta_angle = jp->p - pobjp->p;
633 if (delta_angle >= F1_0/2)
634 delta_2 = -ANIM_RATE;
635 else if (delta_angle >= 0)
637 else if (delta_angle >= -F1_0/2)
638 delta_2 = -ANIM_RATE;
642 if (flinch_attack_scale != 1)
643 delta_2 *= flinch_attack_scale;
645 Ai_local_info[objnum].delta_angles[jointnum].p = delta_2/DELTA_ANG_SCALE; // complete revolutions per second
648 if (jp->b != pobjp->b) {
651 Ai_local_info[objnum].goal_angles[jointnum].b = jp->b;
653 delta_angle = jp->b - pobjp->b;
654 if (delta_angle >= F1_0/2)
655 delta_2 = -ANIM_RATE;
656 else if (delta_angle >= 0)
658 else if (delta_angle >= -F1_0/2)
659 delta_2 = -ANIM_RATE;
663 if (flinch_attack_scale != 1)
664 delta_2 *= flinch_attack_scale;
666 Ai_local_info[objnum].delta_angles[jointnum].b = delta_2/DELTA_ANG_SCALE; // complete revolutions per second
669 if (jp->h != pobjp->h) {
672 Ai_local_info[objnum].goal_angles[jointnum].h = jp->h;
674 delta_angle = jp->h - pobjp->h;
675 if (delta_angle >= F1_0/2)
676 delta_2 = -ANIM_RATE;
677 else if (delta_angle >= 0)
679 else if (delta_angle >= -F1_0/2)
680 delta_2 = -ANIM_RATE;
684 if (flinch_attack_scale != 1)
685 delta_2 *= flinch_attack_scale;
687 Ai_local_info[objnum].delta_angles[jointnum].h = delta_2/DELTA_ANG_SCALE; // complete revolutions per second
692 //ai_static *aip = &objp->ctype.ai_info;
693 ai_local *ailp = &Ai_local_info[OBJECT_NUMBER(objp)];
694 ailp->achieved_state[gun_num] = ailp->goal_state[gun_num];
695 if (ailp->achieved_state[gun_num] == AIS_RECO)
696 ailp->goal_state[gun_num] = AIS_FIRE;
698 if (ailp->achieved_state[gun_num] == AIS_FLIN)
699 ailp->goal_state[gun_num] = AIS_LOCK;
704 if (at_goal == 1) //num_guns)
705 aip->CURRENT_STATE = aip->GOAL_STATE;
710 // ------------------------------------------------------------------------------------------
711 // Move all sub-objects in an object towards their goals.
712 // Current orientation of object is at: pobj_info.anim_angles
713 // Goal orientation of object is at: ai_info.goal_angles
714 // Delta orientation of object is at: ai_info.delta_angles
715 void ai_frame_animation(object *objp)
717 int objnum = OBJECT_NUMBER(objp);
721 num_joints = Polygon_models[objp->rtype.pobj_info.model_num].n_models;
723 for (joint=1; joint<num_joints; joint++) {
725 fix scaled_delta_angle;
726 vms_angvec *curangp = &objp->rtype.pobj_info.anim_angles[joint];
727 vms_angvec *goalangp = &Ai_local_info[objnum].goal_angles[joint];
728 vms_angvec *deltaangp = &Ai_local_info[objnum].delta_angles[joint];
730 delta_to_goal = goalangp->p - curangp->p;
731 if (delta_to_goal > 32767)
732 delta_to_goal = delta_to_goal - 65536;
733 else if (delta_to_goal < -32767)
734 delta_to_goal = 65536 + delta_to_goal;
737 scaled_delta_angle = fixmul(deltaangp->p, FrameTime) * DELTA_ANG_SCALE;
738 curangp->p += scaled_delta_angle;
739 if (abs(delta_to_goal) < abs(scaled_delta_angle))
740 curangp->p = goalangp->p;
743 delta_to_goal = goalangp->b - curangp->b;
744 if (delta_to_goal > 32767)
745 delta_to_goal = delta_to_goal - 65536;
746 else if (delta_to_goal < -32767)
747 delta_to_goal = 65536 + delta_to_goal;
750 scaled_delta_angle = fixmul(deltaangp->b, FrameTime) * DELTA_ANG_SCALE;
751 curangp->b += scaled_delta_angle;
752 if (abs(delta_to_goal) < abs(scaled_delta_angle))
753 curangp->b = goalangp->b;
756 delta_to_goal = goalangp->h - curangp->h;
757 if (delta_to_goal > 32767)
758 delta_to_goal = delta_to_goal - 65536;
759 else if (delta_to_goal < -32767)
760 delta_to_goal = 65536 + delta_to_goal;
763 scaled_delta_angle = fixmul(deltaangp->h, FrameTime) * DELTA_ANG_SCALE;
764 curangp->h += scaled_delta_angle;
765 if (abs(delta_to_goal) < abs(scaled_delta_angle))
766 curangp->h = goalangp->h;
773 // ----------------------------------------------------------------------------------
774 void set_next_fire_time(object *objp, ai_local *ailp, robot_info *robptr, int gun_num)
776 // For guys in snipe mode, they have a 50% shot of getting this shot in free.
777 if ((gun_num != 0) || (robptr->weapon_type2 == -1))
778 if ((objp->ctype.ai_info.behavior != AIB_SNIPE) || (d_rand() > 16384))
779 ailp->rapidfire_count++;
781 // Old way, 10/15/95: Continuous rapidfire if rapidfire_count set.
782 // -- if (((robptr->weapon_type2 == -1) || (gun_num != 0)) && (ailp->rapidfire_count < robptr->rapidfire_count[Difficulty_level])) {
783 // -- ailp->next_fire = min(F1_0/8, robptr->firing_wait[Difficulty_level]/2);
785 // -- if ((robptr->weapon_type2 == -1) || (gun_num != 0)) {
786 // -- ailp->rapidfire_count = 0;
787 // -- ailp->next_fire = robptr->firing_wait[Difficulty_level];
789 // -- ailp->next_fire2 = robptr->firing_wait2[Difficulty_level];
792 if (((gun_num != 0) || (robptr->weapon_type2 == -1)) && (ailp->rapidfire_count < robptr->rapidfire_count[Difficulty_level])) {
793 ailp->next_fire = min(F1_0/8, robptr->firing_wait[Difficulty_level]/2);
795 if ((robptr->weapon_type2 == -1) || (gun_num != 0)) {
796 ailp->next_fire = robptr->firing_wait[Difficulty_level];
797 if (ailp->rapidfire_count >= robptr->rapidfire_count[Difficulty_level])
798 ailp->rapidfire_count = 0;
800 ailp->next_fire2 = robptr->firing_wait2[Difficulty_level];
804 // ----------------------------------------------------------------------------------
805 // When some robots collide with the player, they attack.
806 // If player is cloaked, then robot probably didn't actually collide, deal with that here.
807 void do_ai_robot_hit_attack(object *robot, object *playerobj, vms_vector *collision_point)
809 ai_local *ailp = &Ai_local_info[OBJECT_NUMBER(robot)];
810 robot_info *robptr = &Robot_info[robot->id];
813 if (!Robot_firing_enabled)
817 // If player is dead, stop firing.
818 if (Objects[Players[Player_num].objnum].type == OBJ_GHOST)
821 if (robptr->attack_type == 1) {
822 if (ailp->next_fire <= 0) {
823 if (!(Players[Player_num].flags & PLAYER_FLAGS_CLOAKED))
824 if (vm_vec_dist_quick(&ConsoleObject->pos, &robot->pos) < robot->size + ConsoleObject->size + F1_0*2) {
825 collide_player_and_nasty_robot( playerobj, robot, collision_point );
826 if (robptr->energy_drain && Players[Player_num].energy) {
827 Players[Player_num].energy -= robptr->energy_drain * F1_0;
828 if (Players[Player_num].energy < 0)
829 Players[Player_num].energy = 0;
830 // -- unused, use claw_sound in bitmaps.tbl -- digi_link_sound_to_pos( SOUND_ROBOT_SUCKED_PLAYER, playerobj->segnum, 0, collision_point, 0, F1_0 );
834 robot->ctype.ai_info.GOAL_STATE = AIS_RECO;
835 set_next_fire_time(robot, ailp, robptr, 1); // 1 = gun_num: 0 is special (uses next_fire2)
842 extern int Player_exploded;
845 #define FIRE_K 8 // Controls average accuracy of robot firing. Smaller numbers make firing worse. Being power of 2 doesn't matter.
847 // ====================================================================================================================
849 #define MIN_LEAD_SPEED (F1_0*4)
850 #define MAX_LEAD_DISTANCE (F1_0*200)
851 #define LEAD_RANGE (F1_0/2)
853 // --------------------------------------------------------------------------------------------------------------------
854 // Computes point at which projectile fired by robot can hit player given positions, player vel, elapsed time
855 fix compute_lead_component(fix player_pos, fix robot_pos, fix player_vel, fix elapsed_time)
857 return fixdiv(player_pos - robot_pos, elapsed_time) + player_vel;
860 // --------------------------------------------------------------------------------------------------------------------
861 // Lead the player, returning point to fire at in fire_point.
863 // Player not cloaked
864 // Player must be moving at a speed >= MIN_LEAD_SPEED
865 // Player not farther away than MAX_LEAD_DISTANCE
866 // dot(vector_to_player, player_direction) must be in -LEAD_RANGE..LEAD_RANGE
867 // if firing a matter weapon, less leading, based on skill level.
868 int lead_player(object *objp, vms_vector *fire_point, vms_vector *believed_player_pos, int gun_num, vms_vector *fire_vec)
870 fix dot, player_speed, dist_to_player, max_weapon_speed, projected_time;
871 vms_vector player_movement_dir, vec_to_player;
876 if (Players[Player_num].flags & PLAYER_FLAGS_CLOAKED)
879 player_movement_dir = ConsoleObject->mtype.phys_info.velocity;
880 player_speed = vm_vec_normalize_quick(&player_movement_dir);
882 if (player_speed < MIN_LEAD_SPEED)
885 vm_vec_sub(&vec_to_player, believed_player_pos, fire_point);
886 dist_to_player = vm_vec_normalize_quick(&vec_to_player);
887 if (dist_to_player > MAX_LEAD_DISTANCE)
890 dot = vm_vec_dot(&vec_to_player, &player_movement_dir);
892 if ((dot < -LEAD_RANGE) || (dot > LEAD_RANGE))
895 // Looks like it might be worth trying to lead the player.
896 robptr = &Robot_info[objp->id];
897 weapon_type = robptr->weapon_type;
898 if (robptr->weapon_type2 != -1)
900 weapon_type = robptr->weapon_type2;
902 wptr = &Weapon_info[weapon_type];
903 max_weapon_speed = wptr->speed[Difficulty_level];
904 if (max_weapon_speed < F1_0)
908 // At Rookie or Trainee, don't lead at all.
909 // At higher skill levels, don't lead as well. Accomplish this by screwing up max_weapon_speed.
912 if (Difficulty_level <= 1)
915 max_weapon_speed *= (NDL-Difficulty_level);
918 projected_time = fixdiv(dist_to_player, max_weapon_speed);
920 fire_vec->x = compute_lead_component(believed_player_pos->x, fire_point->x, ConsoleObject->mtype.phys_info.velocity.x, projected_time);
921 fire_vec->y = compute_lead_component(believed_player_pos->y, fire_point->y, ConsoleObject->mtype.phys_info.velocity.y, projected_time);
922 fire_vec->z = compute_lead_component(believed_player_pos->z, fire_point->z, ConsoleObject->mtype.phys_info.velocity.z, projected_time);
924 vm_vec_normalize_quick(fire_vec);
926 Assert(vm_vec_dot(fire_vec, &objp->orient.fvec) < 3*F1_0/2);
928 // Make sure not firing at especially strange angle. If so, try to correct. If still bad, give up after one try.
929 if (vm_vec_dot(fire_vec, &objp->orient.fvec) < F1_0/2) {
930 vm_vec_add2(fire_vec, &vec_to_player);
931 vm_vec_scale(fire_vec, F1_0/2);
932 if (vm_vec_dot(fire_vec, &objp->orient.fvec) < F1_0/2) {
940 // --------------------------------------------------------------------------------------------------------------------
941 // Note: Parameter vec_to_player is only passed now because guns which aren't on the forward vector from the
942 // center of the robot will not fire right at the player. We need to aim the guns at the player. Barring that, we cheat.
943 // When this routine is complete, the parameter vec_to_player should not be necessary.
944 void ai_fire_laser_at_player(object *obj, vms_vector *fire_point, int gun_num, vms_vector *believed_player_pos)
946 int objnum = OBJECT_NUMBER(obj);
947 ai_local *ailp = &Ai_local_info[objnum];
948 robot_info *robptr = &Robot_info[obj->id];
955 Assert(robptr->attack_type == 0); // We should never be coming here for the green guy, as he has no laser!
957 // If this robot is only awake because a camera woke it up, don't fire.
958 if (obj->ctype.ai_info.SUB_FLAGS & SUB_FLAGS_CAMERA_AWAKE)
961 if (!Robot_firing_enabled)
964 if (obj->control_type == CT_MORPH)
967 // If player is exploded, stop firing.
971 if (obj->ctype.ai_info.dying_start_time)
972 return; // No firing while in death roll.
974 // Don't let the boss fire while in death roll. Sorry, this is the easiest way to do this.
975 // If you try to key the boss off obj->ctype.ai_info.dying_start_time, it will hose the endlevel stuff.
976 if (Boss_dying_start_time & Robot_info[obj->id].boss_flag)
979 // If player is cloaked, maybe don't fire based on how long cloaked and randomness.
980 if (Players[Player_num].flags & PLAYER_FLAGS_CLOAKED) {
981 fix cloak_time = Ai_cloak_info[objnum % MAX_AI_CLOAK_INFO].last_time;
983 if (GameTime - cloak_time > CLOAK_TIME_MAX/4)
984 if (d_rand() > fixdiv(GameTime - cloak_time, CLOAK_TIME_MAX)/2) {
985 set_next_fire_time(obj, ailp, robptr, gun_num);
990 // Handle problem of a robot firing through a wall because its gun tip is on the other
991 // side of the wall than the robot's center. For speed reasons, we normally only compute
992 // the vector from the gun point to the player. But we need to know whether the gun point
993 // is separated from the robot's center by a wall. If so, don't fire!
994 if (obj->ctype.ai_info.SUB_FLAGS & SUB_FLAGS_GUNSEG) {
995 // Well, the gun point is in a different segment than the robot's center.
996 // This is almost always ok, but it is not ok if something solid is in between.
998 int gun_segnum = find_point_seg(fire_point, obj->segnum);
1000 // See if these segments are connected, which should almost always be the case.
1001 conn_side = find_connect_side(&Segments[gun_segnum], &Segments[obj->segnum]);
1002 if (conn_side != -1) {
1003 // They are connected via conn_side in segment obj->segnum.
1004 // See if they are unobstructed.
1005 if (!(WALL_IS_DOORWAY(&Segments[obj->segnum], conn_side) & WID_FLY_FLAG)) {
1006 // Can't fly through, so don't let this bot fire through!
1010 // Well, they are not directly connected, so use find_vector_intersection to see if they are unobstructed.
1015 fq.startseg = obj->segnum;
1019 fq.thisobjnum = OBJECT_NUMBER(obj);
1020 fq.ignore_obj_list = NULL;
1021 fq.flags = FQ_TRANSWALL;
1023 fate = find_vector_intersection(&fq, &hit_data);
1024 if (fate != HIT_NONE) {
1025 Int3(); // This bot's gun is poking through a wall, so don't fire.
1026 move_towards_segment_center(obj); // And decrease chances it will happen again.
1032 // -- mprintf((0, "Firing from gun #%i at time = %7.3f\n", gun_num, f2fl(GameTime)));
1034 // Set position to fire at based on difficulty level and robot's aiming ability
1035 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.
1037 // Robots aim more poorly during seismic disturbance.
1038 if (Seismic_tremor_magnitude) {
1041 temp = F1_0 - abs(Seismic_tremor_magnitude);
1045 aim = fixmul(aim, temp);
1048 // Lead the player half the time.
1049 // Note that when leading the player, aim is perfect. This is probably acceptable since leading is so hacked in.
1050 // Problem is all robots will lead equally badly.
1051 if (d_rand() < 16384) {
1052 if (lead_player(obj, fire_point, believed_player_pos, gun_num, &fire_vec)) // Stuff direction to fire at in fire_point.
1057 count = 0; // Don't want to sit in this loop forever...
1058 while ((count < 4) && (dot < F1_0/4)) {
1059 bpp_diff.x = believed_player_pos->x + fixmul((d_rand()-16384) * (NDL-Difficulty_level-1) * 4, aim);
1060 bpp_diff.y = believed_player_pos->y + fixmul((d_rand()-16384) * (NDL-Difficulty_level-1) * 4, aim);
1061 bpp_diff.z = believed_player_pos->z + fixmul((d_rand()-16384) * (NDL-Difficulty_level-1) * 4, aim);
1063 vm_vec_normalized_dir_quick(&fire_vec, &bpp_diff, fire_point);
1064 dot = vm_vec_dot(&obj->orient.fvec, &fire_vec);
1069 weapon_type = robptr->weapon_type;
1070 if (robptr->weapon_type2 != -1)
1072 weapon_type = robptr->weapon_type2;
1074 Laser_create_new_easy( &fire_vec, fire_point, OBJECT_NUMBER(obj), weapon_type, 1 );
1077 if (Game_mode & GM_MULTI) {
1078 ai_multi_send_robot_position(objnum, -1);
1079 multi_send_robot_fire(objnum, obj->ctype.ai_info.CURRENT_GUN, &fire_vec);
1083 create_awareness_event(obj, PA_NEARBY_ROBOT_FIRED);
1085 set_next_fire_time(obj, ailp, robptr, gun_num);
1089 // --------------------------------------------------------------------------------------------------------------------
1090 // vec_goal must be normalized, or close to it.
1091 // if dot_based set, then speed is based on direction of movement relative to heading
1092 void move_towards_vector(object *objp, vms_vector *vec_goal, int dot_based)
1094 physics_info *pptr = &objp->mtype.phys_info;
1095 fix speed, dot, max_speed;
1096 robot_info *robptr = &Robot_info[objp->id];
1099 // Trying to move towards player. If forward vector much different than velocity vector,
1100 // bash velocity vector twice as much towards player as usual.
1102 vel = pptr->velocity;
1103 vm_vec_normalize_quick(&vel);
1104 dot = vm_vec_dot(&vel, &objp->orient.fvec);
1109 if (dot_based && (dot < 3*F1_0/4)) {
1110 // This funny code is supposed to slow down the robot and move his velocity towards his direction
1111 // more quickly than the general code
1112 pptr->velocity.x = pptr->velocity.x/2 + fixmul(vec_goal->x, FrameTime*32);
1113 pptr->velocity.y = pptr->velocity.y/2 + fixmul(vec_goal->y, FrameTime*32);
1114 pptr->velocity.z = pptr->velocity.z/2 + fixmul(vec_goal->z, FrameTime*32);
1116 pptr->velocity.x += fixmul(vec_goal->x, FrameTime*64) * (Difficulty_level+5)/4;
1117 pptr->velocity.y += fixmul(vec_goal->y, FrameTime*64) * (Difficulty_level+5)/4;
1118 pptr->velocity.z += fixmul(vec_goal->z, FrameTime*64) * (Difficulty_level+5)/4;
1121 speed = vm_vec_mag_quick(&pptr->velocity);
1122 max_speed = robptr->max_speed[Difficulty_level];
1124 // Green guy attacks twice as fast as he moves away.
1125 if ((robptr->attack_type == 1) || robptr->thief || robptr->kamikaze)
1128 if (speed > max_speed) {
1129 pptr->velocity.x = (pptr->velocity.x*3)/4;
1130 pptr->velocity.y = (pptr->velocity.y*3)/4;
1131 pptr->velocity.z = (pptr->velocity.z*3)/4;
1135 // --------------------------------------------------------------------------------------------------------------------
1136 void move_towards_player(object *objp, vms_vector *vec_to_player)
1137 // vec_to_player must be normalized, or close to it.
1139 move_towards_vector(objp, vec_to_player, 1);
1142 // --------------------------------------------------------------------------------------------------------------------
1143 // I am ashamed of this: fast_flag == -1 means normal slide about. fast_flag = 0 means no evasion.
1144 void move_around_player(object *objp, vms_vector *vec_to_player, int fast_flag)
1146 physics_info *pptr = &objp->mtype.phys_info;
1148 robot_info *robptr = &Robot_info[objp->id];
1149 int objnum = OBJECT_NUMBER(objp);
1153 vms_vector evade_vector;
1165 while (ft < F1_0/4) {
1171 dir = (FrameCount + (count+1) * (objnum*8 + objnum*4 + objnum)) & dir_change;
1174 Assert((dir >= 0) && (dir <= 3));
1178 evade_vector.x = fixmul(vec_to_player->z, FrameTime*32);
1179 evade_vector.y = fixmul(vec_to_player->y, FrameTime*32);
1180 evade_vector.z = fixmul(-vec_to_player->x, FrameTime*32);
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->y, FrameTime*32);
1189 evade_vector.y = fixmul(vec_to_player->x, FrameTime*32);
1190 evade_vector.z = fixmul(vec_to_player->z, 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 Error("Function move_around_player: Bad case.");
1201 // Note: -1 means normal circling about the player. > 0 means fast evasion.
1202 if (fast_flag > 0) {
1205 // Only take evasive action if looking at player.
1206 // Evasion speed is scaled by percentage of shields left so wounded robots evade less effectively.
1208 dot = vm_vec_dot(vec_to_player, &objp->orient.fvec);
1209 if ((dot > robptr->field_of_view[Difficulty_level]) && !(ConsoleObject->flags & PLAYER_FLAGS_CLOAKED)) {
1212 if (robptr->strength)
1213 damage_scale = fixdiv(objp->shields, robptr->strength);
1215 damage_scale = F1_0;
1216 if (damage_scale > F1_0)
1217 damage_scale = F1_0; // Just in case...
1218 else if (damage_scale < 0)
1219 damage_scale = 0; // Just in case...
1221 vm_vec_scale(&evade_vector, i2f(fast_flag) + damage_scale);
1225 pptr->velocity.x += evade_vector.x;
1226 pptr->velocity.y += evade_vector.y;
1227 pptr->velocity.z += evade_vector.z;
1229 speed = vm_vec_mag_quick(&pptr->velocity);
1230 if ((OBJECT_NUMBER(objp) != 1) && (speed > robptr->max_speed[Difficulty_level])) {
1231 pptr->velocity.x = (pptr->velocity.x*3)/4;
1232 pptr->velocity.y = (pptr->velocity.y*3)/4;
1233 pptr->velocity.z = (pptr->velocity.z*3)/4;
1237 // --------------------------------------------------------------------------------------------------------------------
1238 void move_away_from_player(object *objp, vms_vector *vec_to_player, int attack_type)
1241 physics_info *pptr = &objp->mtype.phys_info;
1242 robot_info *robptr = &Robot_info[objp->id];
1245 pptr->velocity.x -= fixmul(vec_to_player->x, FrameTime*16);
1246 pptr->velocity.y -= fixmul(vec_to_player->y, FrameTime*16);
1247 pptr->velocity.z -= fixmul(vec_to_player->z, FrameTime*16);
1250 // Get value in 0..3 to choose evasion direction.
1251 objref = ((OBJECT_NUMBER(objp)) ^ ((FrameCount + 3*(OBJECT_NUMBER(objp))) >> 5)) & 3;
1254 case 0: vm_vec_scale_add2(&pptr->velocity, &objp->orient.uvec, FrameTime << 5); break;
1255 case 1: vm_vec_scale_add2(&pptr->velocity, &objp->orient.uvec, -FrameTime << 5); break;
1256 case 2: vm_vec_scale_add2(&pptr->velocity, &objp->orient.rvec, FrameTime << 5); break;
1257 case 3: vm_vec_scale_add2(&pptr->velocity, &objp->orient.rvec, -FrameTime << 5); break;
1258 default: Int3(); // Impossible, bogus value on objref, must be in 0..3
1263 speed = vm_vec_mag_quick(&pptr->velocity);
1265 if (speed > robptr->max_speed[Difficulty_level]) {
1266 pptr->velocity.x = (pptr->velocity.x*3)/4;
1267 pptr->velocity.y = (pptr->velocity.y*3)/4;
1268 pptr->velocity.z = (pptr->velocity.z*3)/4;
1273 // --------------------------------------------------------------------------------------------------------------------
1274 // Move towards, away_from or around player.
1275 // Also deals with evasion.
1276 // If the flag evade_only is set, then only allowed to evade, not allowed to move otherwise (must have mode == AIM_STILL).
1277 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)
1280 robot_info *robptr = &Robot_info[objp->id];
1282 Assert(player_visibility != -1);
1284 // See if should take avoidance.
1286 // New way, green guys don't evade: if ((robptr->attack_type == 0) && (objp->ctype.ai_info.danger_laser_num != -1)) {
1287 if (objp->ctype.ai_info.danger_laser_num != -1) {
1288 dobjp = &Objects[objp->ctype.ai_info.danger_laser_num];
1290 if ((dobjp->type == OBJ_WEAPON) && (dobjp->signature == objp->ctype.ai_info.danger_laser_signature)) {
1291 fix dot, dist_to_laser, field_of_view;
1292 vms_vector vec_to_laser, laser_fvec;
1294 field_of_view = Robot_info[objp->id].field_of_view[Difficulty_level];
1296 vm_vec_sub(&vec_to_laser, &dobjp->pos, &objp->pos);
1297 dist_to_laser = vm_vec_normalize_quick(&vec_to_laser);
1298 dot = vm_vec_dot(&vec_to_laser, &objp->orient.fvec);
1300 if ((dot > field_of_view) || (robptr->companion)) {
1301 fix laser_robot_dot;
1302 vms_vector laser_vec_to_robot;
1304 // The laser is seen by the robot, see if it might hit the robot.
1305 // Get the laser's direction. If it's a polyobj, it can be gotten cheaply from the orientation matrix.
1306 if (dobjp->render_type == RT_POLYOBJ)
1307 laser_fvec = dobjp->orient.fvec;
1308 else { // Not a polyobj, get velocity and normalize.
1309 laser_fvec = dobjp->mtype.phys_info.velocity; //dobjp->orient.fvec;
1310 vm_vec_normalize_quick(&laser_fvec);
1312 vm_vec_sub(&laser_vec_to_robot, &objp->pos, &dobjp->pos);
1313 vm_vec_normalize_quick(&laser_vec_to_robot);
1314 laser_robot_dot = vm_vec_dot(&laser_fvec, &laser_vec_to_robot);
1316 if ((laser_robot_dot > F1_0*7/8) && (dist_to_laser < F1_0*80)) {
1320 evade_speed = Robot_info[objp->id].evade_speed[Difficulty_level];
1322 move_around_player(objp, vec_to_player, evade_speed);
1329 // If only allowed to do evade code, then done.
1330 // Hmm, perhaps brilliant insight. If want claw-type guys to keep coming, don't return here after evasion.
1331 if ((!robptr->attack_type) && (!robptr->thief) && evade_only)
1334 // If we fall out of above, then no object to be avoided.
1335 objp->ctype.ai_info.danger_laser_num = -1;
1337 // Green guy selects move around/towards/away based on firing time, not distance.
1338 if (robptr->attack_type == 1) {
1339 if (((ailp->next_fire > robptr->firing_wait[Difficulty_level]/4) && (dist_to_player < F1_0*30)) || Player_is_dead) {
1340 // 1/4 of time, move around player, 3/4 of time, move away from player
1341 if (d_rand() < 8192) {
1342 move_around_player(objp, vec_to_player, -1);
1344 move_away_from_player(objp, vec_to_player, 1);
1347 move_towards_player(objp, vec_to_player);
1349 } else if (robptr->thief) {
1350 move_towards_player(objp, vec_to_player);
1352 int objval = ((OBJECT_NUMBER(objp)) & 0x0f) ^ 0x0a;
1354 // Changes here by MK, 12/29/95. Trying to get rid of endless circling around bots in a large room.
1355 if (robptr->kamikaze) {
1356 move_towards_player(objp, vec_to_player);
1357 } else if (dist_to_player < circle_distance)
1358 move_away_from_player(objp, vec_to_player, 0);
1359 else if ((dist_to_player < (3+objval)*circle_distance/2) && (ailp->next_fire > -F1_0)) {
1360 move_around_player(objp, vec_to_player, -1);
1362 if ((-ailp->next_fire > F1_0 + (objval << 12)) && player_visibility) {
1363 // Usually move away, but sometimes move around player.
1364 if ((((GameTime >> 18) & 0x0f) ^ objval) > 4) {
1365 move_away_from_player(objp, vec_to_player, 0);
1367 move_around_player(objp, vec_to_player, -1);
1370 move_towards_player(objp, vec_to_player);
1376 // --------------------------------------------------------------------------------------------------------------------
1377 // Compute a somewhat random, normalized vector.
1378 void make_random_vector(vms_vector *vec)
1380 vec->x = (d_rand() - 16384) | 1; // make sure we don't create null vector
1381 vec->y = d_rand() - 16384;
1382 vec->z = d_rand() - 16384;
1384 vm_vec_normalize_quick(vec);
1388 void mprintf_animation_info(object *objp)
1390 ai_static *aip = &objp->ctype.ai_info;
1391 ai_local *ailp = &Ai_local_info[OBJECT_NUMBER(objp)];
1393 if (!Ai_info_enabled)
1396 mprintf((0, "Goal = "));
1398 switch (aip->GOAL_STATE) {
1399 case AIS_NONE: mprintf((0, "NONE ")); break;
1400 case AIS_REST: mprintf((0, "REST ")); break;
1401 case AIS_SRCH: mprintf((0, "SRCH ")); break;
1402 case AIS_LOCK: mprintf((0, "LOCK ")); break;
1403 case AIS_FLIN: mprintf((0, "FLIN ")); break;
1404 case AIS_FIRE: mprintf((0, "FIRE ")); break;
1405 case AIS_RECO: mprintf((0, "RECO ")); break;
1406 case AIS_ERR_: mprintf((0, "ERR_ ")); break;
1410 mprintf((0, " Cur = "));
1412 switch (aip->CURRENT_STATE) {
1413 case AIS_NONE: mprintf((0, "NONE ")); break;
1414 case AIS_REST: mprintf((0, "REST ")); break;
1415 case AIS_SRCH: mprintf((0, "SRCH ")); break;
1416 case AIS_LOCK: mprintf((0, "LOCK ")); break;
1417 case AIS_FLIN: mprintf((0, "FLIN ")); break;
1418 case AIS_FIRE: mprintf((0, "FIRE ")); break;
1419 case AIS_RECO: mprintf((0, "RECO ")); break;
1420 case AIS_ERR_: mprintf((0, "ERR_ ")); break;
1423 mprintf((0, " Aware = "));
1425 switch (ailp->player_awareness_type) {
1426 case AIE_FIRE: mprintf((0, "FIRE ")); break;
1427 case AIE_HITT: mprintf((0, "HITT ")); break;
1428 case AIE_COLL: mprintf((0, "COLL ")); break;
1429 case AIE_HURT: mprintf((0, "HURT ")); break;
1432 mprintf((0, "Next fire = %6.3f, Time = %6.3f\n", f2fl(ailp->next_fire), f2fl(ailp->player_awareness_time)));
1437 // -------------------------------------------------------------------------------------------------------------------
1438 int Break_on_object = -1;
1440 void do_firing_stuff(object *obj, int player_visibility, vms_vector *vec_to_player)
1442 if ((Dist_to_last_fired_upon_player_pos < FIRE_AT_NEARBY_PLAYER_THRESHOLD ) || (player_visibility >= 1)) {
1443 // Now, if in robot's field of view, lock onto player
1444 fix dot = vm_vec_dot(&obj->orient.fvec, vec_to_player);
1445 if ((dot >= 7*F1_0/8) || (Players[Player_num].flags & PLAYER_FLAGS_CLOAKED)) {
1446 ai_static *aip = &obj->ctype.ai_info;
1447 ai_local *ailp = &Ai_local_info[OBJECT_NUMBER(obj)];
1449 switch (aip->GOAL_STATE) {
1454 aip->GOAL_STATE = AIS_FIRE;
1455 if (ailp->player_awareness_type <= PA_NEARBY_ROBOT_FIRED) {
1456 ailp->player_awareness_type = PA_NEARBY_ROBOT_FIRED;
1457 ailp->player_awareness_time = PLAYER_AWARENESS_INITIAL_TIME;
1461 } else if (dot >= F1_0/2) {
1462 ai_static *aip = &obj->ctype.ai_info;
1463 switch (aip->GOAL_STATE) {
1467 aip->GOAL_STATE = AIS_LOCK;
1474 // --------------------------------------------------------------------------------------------------------------------
1475 // If a hiding robot gets bumped or hit, he decides to find another hiding place.
1476 void do_ai_robot_hit(object *objp, int type)
1478 if (objp->control_type == CT_AI) {
1479 if ((type == PA_WEAPON_ROBOT_COLLISION) || (type == PA_PLAYER_COLLISION))
1480 switch (objp->ctype.ai_info.behavior) {
1485 // Attack robots (eg, green guy) shouldn't have behavior = still.
1486 Assert(Robot_info[objp->id].attack_type == 0);
1489 // 1/8 time, charge player, 1/4 time create path, rest of time, do nothing
1491 // -- mprintf((0, "Still guy switching to Station, creating path to player."));
1492 create_path_to_player(objp, 10, 1);
1493 objp->ctype.ai_info.behavior = AIB_STATION;
1494 objp->ctype.ai_info.hide_segment = objp->segnum;
1495 Ai_local_info[OBJECT_NUMBER(objp)].mode = AIM_CHASE_OBJECT;
1496 } else if (r < 4096+8192) {
1497 // -- mprintf((0, "Still guy creating n segment path."));
1498 create_n_segment_path(objp, d_rand()/8192 + 2, -1);
1499 Ai_local_info[OBJECT_NUMBER(objp)].mode = AIM_FOLLOW_PATH;
1510 int Cvv_last_time[MAX_OBJECTS];
1511 int Gun_point_hack=0;
1514 int Robot_sound_volume=DEFAULT_ROBOT_SOUND_VOLUME;
1516 // --------------------------------------------------------------------------------------------------------------------
1517 // Note: This function could be optimized. Surely player_is_visible_from_object would benefit from the
1518 // information of a normalized vec_to_player.
1519 // Return player visibility:
1521 // 1 visible, but robot not looking at player (ie, on an unobstructed vector)
1522 // 2 visible and in robot's field of view
1523 // -1 player is cloaked
1524 // If the player is cloaked, set vec_to_player based on time player cloaked and last uncloaked position.
1525 // Updates ailp->previous_visibility if player is not cloaked, in which case the previous visibility is left unchanged
1526 // and is copied to player_visibility
1527 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)
1530 if (Players[Player_num].flags & PLAYER_FLAGS_CLOAKED) {
1531 fix delta_time, dist;
1532 int cloak_index = (OBJECT_NUMBER(objp)) % MAX_AI_CLOAK_INFO;
1534 delta_time = GameTime - Ai_cloak_info[cloak_index].last_time;
1535 if (delta_time > F1_0*2) {
1538 Ai_cloak_info[cloak_index].last_time = GameTime;
1539 make_random_vector(&randvec);
1540 vm_vec_scale_add2(&Ai_cloak_info[cloak_index].last_position, &randvec, 8*delta_time );
1543 dist = vm_vec_normalized_dir_quick(vec_to_player, &Ai_cloak_info[cloak_index].last_position, pos);
1544 *player_visibility = player_is_visible_from_object(objp, pos, robptr->field_of_view[Difficulty_level], vec_to_player);
1545 // *player_visibility = 2;
1547 if ((ailp->next_misc_sound_time < GameTime) && ((ailp->next_fire < F1_0) || (ailp->next_fire2 < F1_0)) && (dist < F1_0*20)) {
1548 // mprintf((0, "ANGRY! "));
1549 ailp->next_misc_sound_time = GameTime + (d_rand() + F1_0) * (7 - Difficulty_level) / 1;
1550 digi_link_sound_to_pos( robptr->see_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume);
1553 // Compute expensive stuff -- vec_to_player and player_visibility
1554 vm_vec_normalized_dir_quick(vec_to_player, &Believed_player_pos, pos);
1555 if ((vec_to_player->x == 0) && (vec_to_player->y == 0) && (vec_to_player->z == 0)) {
1556 // -- mprintf((0, "Warning: Player and robot at exactly the same location.\n"));
1557 vec_to_player->x = F1_0;
1559 *player_visibility = player_is_visible_from_object(objp, pos, robptr->field_of_view[Difficulty_level], vec_to_player);
1561 // This horrible code added by MK in desperation on 12/13/94 to make robots wake up as soon as they
1562 // see you without killing frame rate.
1564 ai_static *aip = &objp->ctype.ai_info;
1565 if ((*player_visibility == 2) && (ailp->previous_visibility != 2))
1566 if ((aip->GOAL_STATE == AIS_REST) || (aip->CURRENT_STATE == AIS_REST)) {
1567 aip->GOAL_STATE = AIS_FIRE;
1568 aip->CURRENT_STATE = AIS_FIRE;
1572 if ((ailp->previous_visibility != *player_visibility) && (*player_visibility == 2)) {
1573 if (ailp->previous_visibility == 0) {
1574 if (ailp->time_player_seen + F1_0/2 < GameTime) {
1575 // -- mprintf((0, "SEE! "));
1576 // -- if (Player_exploded)
1577 // -- digi_link_sound_to_pos( robptr->taunt_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume);
1579 digi_link_sound_to_pos( robptr->see_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume);
1580 ailp->time_player_sound_attacked = GameTime;
1581 ailp->next_misc_sound_time = GameTime + F1_0 + d_rand()*4;
1583 } else if (ailp->time_player_sound_attacked + F1_0/4 < GameTime) {
1584 // -- mprintf((0, "ANGRY! "));
1585 // -- if (Player_exploded)
1586 // -- digi_link_sound_to_pos( robptr->taunt_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume);
1588 digi_link_sound_to_pos( robptr->attack_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume);
1589 ailp->time_player_sound_attacked = GameTime;
1593 if ((*player_visibility == 2) && (ailp->next_misc_sound_time < GameTime)) {
1594 // -- mprintf((0, "ATTACK! "));
1595 ailp->next_misc_sound_time = GameTime + (d_rand() + F1_0) * (7 - Difficulty_level) / 2;
1596 // -- if (Player_exploded)
1597 // -- digi_link_sound_to_pos( robptr->taunt_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume);
1599 digi_link_sound_to_pos( robptr->attack_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume);
1601 ailp->previous_visibility = *player_visibility;
1606 // @mk, 09/21/95: If player view is not obstructed and awareness is at least as high as a nearby collision,
1607 // act is if robot is looking at player.
1608 if (ailp->player_awareness_type >= PA_NEARBY_ROBOT_FIRED)
1609 if (*player_visibility == 1)
1610 *player_visibility = 2;
1612 if (*player_visibility) {
1613 ailp->time_player_seen = GameTime;
1619 // --------------------------------------------------------------------------------------------------------------------
1620 // Move the object objp to a spot in which it doesn't intersect a wall.
1621 // It might mean moving it outside its current segment.
1622 void move_object_to_legal_spot(object *objp)
1624 vms_vector original_pos = objp->pos;
1626 segment *segp = &Segments[objp->segnum];
1628 for (i=0; i<MAX_SIDES_PER_SEGMENT; i++) {
1629 if (WALL_IS_DOORWAY(segp, i) & WID_FLY_FLAG) {
1630 vms_vector segment_center, goal_dir;
1631 fix dist_to_center; // Value not used so far.
1633 compute_segment_center(&segment_center, &Segments[segp->children[i]]);
1634 vm_vec_sub(&goal_dir, &segment_center, &objp->pos);
1635 dist_to_center = vm_vec_normalize_quick(&goal_dir);
1636 vm_vec_scale(&goal_dir, objp->size);
1637 vm_vec_add2(&objp->pos, &goal_dir);
1638 if (!object_intersects_wall(objp)) {
1639 int new_segnum = find_point_seg(&objp->pos, objp->segnum);
1641 if (new_segnum != -1) {
1642 obj_relink(OBJECT_NUMBER(objp), new_segnum);
1646 objp->pos = original_pos;
1650 if (Robot_info[objp->id].boss_flag) {
1651 Int3(); // Note: Boss is poking outside mine. Will try to resolve.
1652 teleport_boss(objp);
1654 mprintf((0, "Note: Killing robot #%i because he's badly stuck outside the mine.\n", OBJECT_NUMBER(objp)));
1655 apply_damage_to_robot(objp, objp->shields*2, OBJECT_NUMBER(objp));
1659 // --------------------------------------------------------------------------------------------------------------------
1660 // Move object one object radii from current position towards segment center.
1661 // If segment center is nearer than 2 radii, move it to center.
1662 void move_towards_segment_center(object *objp)
1664 int segnum = objp->segnum;
1666 vms_vector segment_center, goal_dir;
1668 compute_segment_center(&segment_center, &Segments[segnum]);
1670 vm_vec_sub(&goal_dir, &segment_center, &objp->pos);
1671 dist_to_center = vm_vec_normalize_quick(&goal_dir);
1673 if (dist_to_center < objp->size) {
1674 // Center is nearer than the distance we want to move, so move to center.
1675 objp->pos = segment_center;
1676 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)));
1677 if (object_intersects_wall(objp)) {
1678 mprintf((0, "Object #%i still illegal, trying trickier move.\n"));
1679 move_object_to_legal_spot(objp);
1683 // Move one radii towards center.
1684 vm_vec_scale(&goal_dir, objp->size);
1685 vm_vec_add2(&objp->pos, &goal_dir);
1686 new_segnum = find_point_seg(&objp->pos, objp->segnum);
1687 if (new_segnum == -1) {
1688 objp->pos = segment_center;
1689 move_object_to_legal_spot(objp);
1691 // -- 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))));
1696 extern int Buddy_objnum;
1698 //int Buddy_got_stuck = 0;
1700 // -----------------------------------------------------------------------------------------------------------
1701 // Return true if door can be flown through by a suitable type robot.
1702 // Brains, avoid robots, companions can open doors.
1703 // objp == NULL means treat as buddy.
1704 int ai_door_is_openable(object *objp, segment *segp, int sidenum)
1709 if (!IS_CHILD(segp->children[sidenum]))
1710 return 0; //trap -2 (exit side)
1712 wall_num = segp->sides[sidenum].wall_num;
1714 if (wall_num == -1) //if there's no door at all...
1715 return 0; //..then say it can't be opened
1717 // The mighty console object can open all doors (for purposes of determining paths).
1718 if (objp == ConsoleObject) {
1720 if (Walls[wall_num].type == WALL_DOOR)
1724 wallp = &Walls[wall_num];
1726 if ((objp == NULL) || (Robot_info[objp->id].companion == 1)) {
1729 if (wallp->flags & WALL_BUDDY_PROOF) {
1730 if ((wallp->type == WALL_DOOR) && (wallp->state == WALL_DOOR_CLOSED))
1732 else if (wallp->type == WALL_CLOSED)
1734 else if ((wallp->type == WALL_ILLUSION) && !(wallp->flags & WALL_ILLUSION_OFF))
1738 if (wallp->keys != KEY_NONE) {
1739 if (wallp->keys == KEY_BLUE)
1740 return (Players[Player_num].flags & PLAYER_FLAGS_BLUE_KEY);
1741 else if (wallp->keys == KEY_GOLD)
1742 return (Players[Player_num].flags & PLAYER_FLAGS_GOLD_KEY);
1743 else if (wallp->keys == KEY_RED)
1744 return (Players[Player_num].flags & PLAYER_FLAGS_RED_KEY);
1747 if ((wallp->type != WALL_DOOR) && (wallp->type != WALL_CLOSED))
1750 // If Buddy is returning to player, don't let him think he can get through triggered doors.
1751 // It's only valid to think that if the player is going to get him through. But if he's
1752 // going to the player, the player is probably on the opposite side.
1754 ailp_mode = Ai_local_info[Buddy_objnum].mode;
1756 ailp_mode = Ai_local_info[OBJECT_NUMBER(objp)].mode;
1758 // -- if (Buddy_got_stuck) {
1759 if (ailp_mode == AIM_GOTO_PLAYER) {
1760 if ((wallp->type == WALL_BLASTABLE) && (wallp->state != WALL_BLASTED))
1762 if (wallp->type == WALL_CLOSED)
1764 if (wallp->type == WALL_DOOR) {
1765 if ((wallp->flags & WALL_DOOR_LOCKED) && (wallp->state == WALL_DOOR_CLOSED))
1771 if ((ailp_mode != AIM_GOTO_PLAYER) && (wallp->controlling_trigger != -1)) {
1772 int clip_num = wallp->clip_num;
1776 else if (WallAnims[clip_num].flags & WCF_HIDDEN) {
1777 if (wallp->state == WALL_DOOR_CLOSED)
1785 if (wallp->type == WALL_DOOR) {
1786 if (wallp->type == WALL_BLASTABLE)
1789 int clip_num = wallp->clip_num;
1793 // Buddy allowed to go through secret doors to get to player.
1794 else if ((ailp_mode != AIM_GOTO_PLAYER) && (WallAnims[clip_num].flags & WCF_HIDDEN)) {
1795 if (wallp->state == WALL_DOOR_CLOSED)
1803 } else if ((objp->id == ROBOT_BRAIN) || (objp->ctype.ai_info.behavior == AIB_RUN_FROM) || (objp->ctype.ai_info.behavior == AIB_SNIPE)) {
1806 if ((wallp->type == WALL_DOOR) && (wallp->keys == KEY_NONE) && !(wallp->flags & WALL_DOOR_LOCKED))
1808 else if (wallp->keys != KEY_NONE) { // Allow bots to open doors to which player has keys.
1809 if (wallp->keys & Players[Player_num].flags)
1817 // -----------------------------------------------------------------------------------------------------------
1818 // Return side of openable door in segment, if any. If none, return -1.
1819 int openable_doors_in_segment(int segnum)
1823 if ((segnum < 0) || (segnum > Highest_segment_index))
1826 for (i=0; i<MAX_SIDES_PER_SEGMENT; i++) {
1827 if (Segments[segnum].sides[i].wall_num != -1) {
1828 int wall_num = Segments[segnum].sides[i].wall_num;
1829 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))
1838 // -- // --------------------------------------------------------------------------------------------------------------------
1839 // -- // Return true if a special object (player or control center) is in this segment.
1840 // -- int special_object_in_seg(int segnum)
1844 // -- objnum = Segments[segnum].objects;
1846 // -- while (objnum != -1) {
1847 // -- if ((Objects[objnum].type == OBJ_PLAYER) || (Objects[objnum].type == OBJ_CNTRLCEN)) {
1848 // -- mprintf((0, "Special object of type %i in segment %i\n", Objects[objnum].type, segnum));
1851 // -- objnum = Objects[objnum].next;
1857 // -- // --------------------------------------------------------------------------------------------------------------------
1858 // -- // Randomly select a segment attached to *segp, reachable by flying.
1859 // -- int get_random_child(int segnum)
1862 // -- segment *segp = &Segments[segnum];
1864 // -- sidenum = (rand() * 6) >> 15;
1866 // -- while (!(WALL_IS_DOORWAY(segp, sidenum) & WID_FLY_FLAG))
1867 // -- sidenum = (rand() * 6) >> 15;
1869 // -- segnum = segp->children[sidenum];
1871 // -- return segnum;
1874 // --------------------------------------------------------------------------------------------------------------------
1875 // Return true if placing an object of size size at pos *pos intersects a (player or robot or control center) in segment *segp.
1876 int check_object_object_intersection(vms_vector *pos, fix size, segment *segp)
1880 // If this would intersect with another object (only check those in this segment), then try to move.
1881 curobjnum = segp->objects;
1882 while (curobjnum != -1) {
1883 object *curobjp = &Objects[curobjnum];
1884 if ((curobjp->type == OBJ_PLAYER) || (curobjp->type == OBJ_ROBOT) || (curobjp->type == OBJ_CNTRLCEN)) {
1885 if (vm_vec_dist_quick(pos, &curobjp->pos) < size + curobjp->size)
1888 curobjnum = curobjp->next;
1895 // --------------------------------------------------------------------------------------------------------------------
1896 // Return objnum if object created, else return -1.
1897 // If pos == NULL, pick random spot in segment.
1898 int create_gated_robot( int segnum, int object_id, vms_vector *pos)
1902 segment *segp = &Segments[segnum];
1903 vms_vector object_pos;
1904 robot_info *robptr = &Robot_info[object_id];
1906 fix objsize = Polygon_models[robptr->model_num].rad;
1907 int default_behavior;
1909 if (GameTime - Last_gate_time < Gate_interval)
1912 for (i=0; i<=Highest_object_index; i++)
1913 if (Objects[i].type == OBJ_ROBOT)
1914 if (Objects[i].matcen_creator == BOSS_GATE_MATCEN_NUM)
1917 if (count > 2*Difficulty_level + 6) {
1918 //mprintf((0, "Cannot gate in a robot until you kill one.\n"));
1919 Last_gate_time = GameTime - 3*Gate_interval/4;
1923 compute_segment_center(&object_pos, segp);
1925 pick_random_point_in_seg(&object_pos, SEGMENT_NUMBER(segp));
1929 // See if legal to place object here. If not, move about in segment and try again.
1930 if (check_object_object_intersection(&object_pos, objsize, segp)) {
1931 //mprintf((0, "Can't get in because object collides with something.\n"));
1932 Last_gate_time = GameTime - 3*Gate_interval/4;
1936 objnum = obj_create(OBJ_ROBOT, object_id, segnum, &object_pos, &vmd_identity_matrix, objsize, CT_AI, MT_PHYSICS, RT_POLYOBJ);
1939 // mprintf((1, "Can't get object to gate in robot. Not gating in.\n"));
1940 Last_gate_time = GameTime - 3*Gate_interval/4;
1944 //mprintf((0, "Gating in object %i in segment %i\n", objnum, SEGMENT_NUMBER(segp)));
1946 Objects[objnum].lifeleft = F1_0*30; // Gated in robots only live 30 seconds.
1949 Net_create_objnums[0] = objnum; // A convenient global to get objnum back to caller for multiplayer
1952 objp = &Objects[objnum];
1954 //Set polygon-object-specific data
1956 objp->rtype.pobj_info.model_num = robptr->model_num;
1957 objp->rtype.pobj_info.subobj_flags = 0;
1961 objp->mtype.phys_info.mass = robptr->mass;
1962 objp->mtype.phys_info.drag = robptr->drag;
1964 objp->mtype.phys_info.flags |= (PF_LEVELLING);
1966 objp->shields = robptr->strength;
1967 objp->matcen_creator = BOSS_GATE_MATCEN_NUM; // flag this robot as having been created by the boss.
1969 default_behavior = Robot_info[objp->id].behavior;
1970 init_ai_object( OBJECT_NUMBER(objp), default_behavior, -1 ); // Note, -1 = segment this robot goes to to hide, should probably be something useful
1972 object_create_explosion(segnum, &object_pos, i2f(10), VCLIP_MORPHING_ROBOT );
1973 digi_link_sound_to_pos( Vclip[VCLIP_MORPHING_ROBOT].sound_num, segnum, 0, &object_pos, 0 , F1_0);
1976 Last_gate_time = GameTime;
1978 Players[Player_num].num_robots_level++;
1979 Players[Player_num].num_robots_total++;
1981 return OBJECT_NUMBER(objp);
1984 #define MAX_SPEW_BOT 3
1986 int Spew_bots[NUM_D2_BOSSES][MAX_SPEW_BOT] = {
1998 int Max_spew_bots[NUM_D2_BOSSES] = {2, 1, 2, 3, 3, 3, 3, 3};
2000 // ----------------------------------------------------------------------------------------------------------
2001 // objp points at a boss. He was presumably just hit and he's supposed to create a bot at the hit location *pos.
2002 int boss_spew_robot(object *objp, vms_vector *pos)
2007 boss_index = Robot_info[objp->id].boss_flag - BOSS_D2;
2009 Assert((boss_index >= 0) && (boss_index < NUM_D2_BOSSES));
2011 segnum = find_point_seg(pos, objp->segnum);
2013 mprintf((0, "Tried to spew a bot outside the mine! Aborting!\n"));
2017 objnum = create_gated_robot( segnum, Spew_bots[boss_index][(Max_spew_bots[boss_index] * d_rand()) >> 15], pos);
2019 // Make spewed robot come tumbling out as if blasted by a flash missile.
2021 object *newobjp = &Objects[objnum];
2024 force_val = F1_0/FrameTime;
2027 newobjp->ctype.ai_info.SKIP_AI_COUNT += force_val;
2028 newobjp->mtype.phys_info.rotthrust.x = ((d_rand() - 16384) * force_val)/16;
2029 newobjp->mtype.phys_info.rotthrust.y = ((d_rand() - 16384) * force_val)/16;
2030 newobjp->mtype.phys_info.rotthrust.z = ((d_rand() - 16384) * force_val)/16;
2031 newobjp->mtype.phys_info.flags |= PF_USES_THRUST;
2033 // Now, give a big initial velocity to get moving away from boss.
2034 vm_vec_sub(&newobjp->mtype.phys_info.velocity, pos, &objp->pos);
2035 vm_vec_normalize_quick(&newobjp->mtype.phys_info.velocity);
2036 vm_vec_scale(&newobjp->mtype.phys_info.velocity, F1_0*128);
2043 // --------------------------------------------------------------------------------------------------------------------
2044 // Call this each time the player starts a new ship.
2045 void init_ai_for_ship(void)
2049 for (i=0; i<MAX_AI_CLOAK_INFO; i++) {
2050 Ai_cloak_info[i].last_time = GameTime;
2051 Ai_cloak_info[i].last_segment = ConsoleObject->segnum;
2052 Ai_cloak_info[i].last_position = ConsoleObject->pos;
2056 // --------------------------------------------------------------------------------------------------------------------
2057 // Make object objp gate in a robot.
2058 // The process of him bringing in a robot takes one second.
2059 // Then a robot appears somewhere near the player.
2060 // Return objnum if robot successfully created, else return -1
2061 int gate_in_robot(int type, int segnum)
2064 segnum = Boss_gate_segs[(d_rand() * Num_boss_gate_segs) >> 15];
2066 Assert((segnum >= 0) && (segnum <= Highest_segment_index));
2068 return create_gated_robot(segnum, type, NULL);
2071 // --------------------------------------------------------------------------------------------------------------------
2072 int boss_fits_in_seg(object *boss_objp, int segnum)
2074 vms_vector segcenter;
2075 int boss_objnum = OBJECT_NUMBER(boss_objp);
2078 compute_segment_center(&segcenter, &Segments[segnum]);
2080 for (posnum=0; posnum<9; posnum++) {
2082 vms_vector vertex_pos;
2084 Assert((posnum-1 >= 0) && (posnum-1 < 8));
2085 vertex_pos = Vertices[Segments[segnum].verts[posnum-1]];
2086 vm_vec_avg(&boss_objp->pos, &vertex_pos, &segcenter);
2088 boss_objp->pos = segcenter;
2090 obj_relink(boss_objnum, segnum);
2091 if (!object_intersects_wall(boss_objp))
2098 // --------------------------------------------------------------------------------------------------------------------
2099 void teleport_boss(object *objp)
2101 int rand_segnum, rand_index;
2102 vms_vector boss_dir;
2104 if (Num_boss_teleport_segs <= 0)
2107 con_printf(CON_URGENT, "No boss-teleportable segments!\n");
2108 Last_teleport_time = GameTime;
2112 // Pick a random segment from the list of boss-teleportable-to segments.
2113 rand_index = (d_rand() * Num_boss_teleport_segs) >> 15;
2114 rand_segnum = Boss_teleport_segs[rand_index];
2115 Assert((rand_segnum >= 0) && (rand_segnum <= Highest_segment_index));
2117 //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));
2120 if (Game_mode & GM_MULTI)
2121 multi_send_boss_actions(OBJECT_NUMBER(objp), 1, rand_segnum, 0);
2124 compute_segment_center(&objp->pos, &Segments[rand_segnum]);
2125 obj_relink(OBJECT_NUMBER(objp), rand_segnum);
2127 Last_teleport_time = GameTime;
2129 // make boss point right at player
2130 vm_vec_sub(&boss_dir, &Objects[Players[Player_num].objnum].pos, &objp->pos);
2131 vm_vector_2_matrix(&objp->orient, &boss_dir, NULL, NULL);
2133 digi_link_sound_to_pos( Vclip[VCLIP_MORPHING_ROBOT].sound_num, rand_segnum, 0, &objp->pos, 0 , F1_0);
2134 digi_kill_sound_linked_to_object( OBJECT_NUMBER(objp) );
2135 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
2137 // After a teleport, boss can fire right away.
2138 Ai_local_info[OBJECT_NUMBER(objp)].next_fire = 0;
2139 Ai_local_info[OBJECT_NUMBER(objp)].next_fire2 = 0;
2143 // ----------------------------------------------------------------------
2144 void start_boss_death_sequence(object *objp)
2146 if (Robot_info[objp->id].boss_flag) {
2148 Boss_dying_start_time = GameTime;
2153 // ----------------------------------------------------------------------
2154 // General purpose robot-dies-with-death-roll-and-groan code.
2155 // Return true if object just died.
2156 // scale: F1_0*4 for boss, much smaller for much smaller guys
2157 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)
2163 roll_duration = F1_0/4;
2165 roll_val = fixdiv(GameTime - start_time, roll_duration);
2167 fix_sincos(fixmul(roll_val, roll_val), &temp, &objp->mtype.phys_info.rotvel.x);
2168 fix_sincos(roll_val, &temp, &objp->mtype.phys_info.rotvel.y);
2169 fix_sincos(roll_val-F1_0/8, &temp, &objp->mtype.phys_info.rotvel.z);
2171 objp->mtype.phys_info.rotvel.x = (GameTime - start_time)/9;
2172 objp->mtype.phys_info.rotvel.y = (GameTime - start_time)/5;
2173 objp->mtype.phys_info.rotvel.z = (GameTime - start_time)/7;
2175 if (digi_sample_rate)
2176 sound_duration = fixdiv(GameSounds[digi_xlat_sound(death_sound)].length,digi_sample_rate);
2178 sound_duration = F1_0;
2180 if (start_time + roll_duration - sound_duration < GameTime) {
2181 if (!*dying_sound_playing) {
2182 mprintf((0, "Starting death sound!\n"));
2183 *dying_sound_playing = 1;
2184 digi_link_sound_to_object2( death_sound, OBJECT_NUMBER(objp), 0, sound_scale, sound_scale*256 ); // F1_0*512 means play twice as loud
2185 } else if (d_rand() < FrameTime*16)
2186 create_small_fireball_on_object(objp, (F1_0 + d_rand()) * (16 * expl_scale/F1_0)/8, 0);
2187 } else if (d_rand() < FrameTime*8)
2188 create_small_fireball_on_object(objp, (F1_0/2 + d_rand()) * (16 * expl_scale/F1_0)/8, 1);
2190 if (start_time + roll_duration < GameTime)
2196 // ----------------------------------------------------------------------
2197 void start_robot_death_sequence(object *objp)
2199 objp->ctype.ai_info.dying_start_time = GameTime;
2200 objp->ctype.ai_info.dying_sound_playing = 0;
2201 objp->ctype.ai_info.SKIP_AI_COUNT = 0;
2205 // ----------------------------------------------------------------------
2206 void do_boss_dying_frame(object *objp)
2210 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);
2213 do_controlcen_destroyed_stuff(NULL);
2214 explode_object(objp, F1_0/4);
2215 digi_link_sound_to_object2(SOUND_BADASS_EXPLOSION, OBJECT_NUMBER(objp), 0, F2_0, F1_0*512);
2219 extern void recreate_thief(object *objp);
2221 // ----------------------------------------------------------------------
2222 int do_any_robot_dying_frame(object *objp)
2224 if (objp->ctype.ai_info.dying_start_time) {
2225 int rval, death_roll;
2227 death_roll = Robot_info[objp->id].death_roll;
2228 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);
2231 explode_object(objp, F1_0/4);
2232 digi_link_sound_to_object2(SOUND_BADASS_EXPLOSION, OBJECT_NUMBER(objp), 0, F2_0, F1_0*512);
2233 if ((Current_level_num < 0) && (Robot_info[objp->id].thief))
2234 recreate_thief(objp);
2243 // --------------------------------------------------------------------------------------------------------------------
2244 // Called for an AI object if it is fairly aware of the player.
2245 // awareness_level is in 0..100. Larger numbers indicate greater awareness (eg, 99 if firing at player).
2246 // In a given frame, might not get called for an object, or might be called more than once.
2247 // The fact that this routine is not called for a given object does not mean that object is not interested in the player.
2248 // Objects are moved by physics, so they can move even if not interested in a player. However, if their velocity or
2249 // orientation is changing, this routine will be called.
2251 // 0 this player IS NOT allowed to move this robot.
2252 // 1 this player IS allowed to move this robot.
2253 int ai_multiplayer_awareness(object *objp, int awareness_level)
2258 if (Game_mode & GM_MULTI) {
2259 if (awareness_level == 0)
2261 rval = multi_can_move_robot(OBJECT_NUMBER(objp), awareness_level);
2270 fix Prev_boss_shields = -1;
2273 // --------------------------------------------------------------------------------------------------------------------
2274 // Do special stuff for a boss.
2275 void do_boss_stuff(object *objp, int player_visibility)
2277 int boss_id, boss_index;
2279 boss_id = Robot_info[objp->id].boss_flag;
2281 Assert(boss_id < BOSS_D2 + NUM_D2_BOSSES);
2283 boss_index = boss_id - BOSS_D2; // Negative means this is a D1 boss
2286 if (objp->shields != Prev_boss_shields) {
2287 mprintf((0, "Boss shields = %7.3f, object %i\n", f2fl(objp->shields), OBJECT_NUMBER(objp)));
2288 Prev_boss_shields = objp->shields;
2292 // New code, fixes stupid bug which meant boss never gated in robots if > 32767 seconds played.
2293 if (Last_teleport_time > GameTime)
2294 Last_teleport_time = GameTime;
2296 if (Last_gate_time > GameTime)
2297 Last_gate_time = GameTime;
2299 // @mk, 10/13/95: Reason:
2300 // Level 4 boss behind locked door. But he's allowed to teleport out of there. So he
2301 // teleports out of there right away, and blasts player right after first door.
2302 if (!player_visibility && (GameTime - Boss_hit_time > F1_0*2))
2305 if (!Boss_dying && (boss_index < 0 || Boss_teleports[boss_index])) {
2306 if (objp->ctype.ai_info.CLOAKED == 1) {
2307 Boss_hit_time = GameTime; // Keep the cloak:teleport process going.
2308 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)) {
2309 if (ai_multiplayer_awareness(objp, 98))
2310 teleport_boss(objp);
2311 } else if (GameTime - Boss_hit_time > F1_0*2) {
2312 Last_teleport_time -= Boss_teleport_interval/4;
2315 if (GameTime > Boss_cloak_end_time || GameTime < Boss_cloak_start_time)
2316 objp->ctype.ai_info.CLOAKED = 0;
2317 } else if ((GameTime - Boss_cloak_end_time > Boss_cloak_interval) || (GameTime - Boss_cloak_end_time < -Boss_cloak_duration)) {
2318 if (ai_multiplayer_awareness(objp, 95)) {
2319 Boss_cloak_start_time = GameTime;
2320 Boss_cloak_end_time = GameTime+Boss_cloak_duration;
2321 objp->ctype.ai_info.CLOAKED = 1;
2323 if (Game_mode & GM_MULTI)
2324 multi_send_boss_actions(OBJECT_NUMBER(objp), 2, 0, 0);
2332 #define BOSS_TO_PLAYER_GATE_DISTANCE (F1_0*200)
2335 // --------------------------------------------------------------------------------------------------------------------
2336 // Do special stuff for a boss.
2337 void do_super_boss_stuff(object *objp, fix dist_to_player, int player_visibility)
2339 static int eclip_state = 0;
2341 do_boss_stuff(objp, player_visibility);
2344 // Only master player can cause gating to occur.
2345 if ((Game_mode & GM_MULTI) && !network_i_am_master())
2349 if ((dist_to_player < BOSS_TO_PLAYER_GATE_DISTANCE) || player_visibility || (Game_mode & GM_MULTI)) {
2350 if (GameTime - Last_gate_time > Gate_interval/2) {
2351 restart_effect(BOSS_ECLIP_NUM);
2352 if (eclip_state == 0) {
2354 multi_send_boss_actions(OBJECT_NUMBER(objp), 4, 0, 0);
2359 stop_effect(BOSS_ECLIP_NUM);
2360 if (eclip_state == 1) {
2362 multi_send_boss_actions(OBJECT_NUMBER(objp), 5, 0, 0);
2368 if (GameTime - Last_gate_time > Gate_interval)
2369 if (ai_multiplayer_awareness(objp, 99)) {
2371 int randtype = (d_rand() * MAX_GATE_INDEX) >> 15;
2373 Assert(randtype < MAX_GATE_INDEX);
2374 randtype = Super_boss_gate_list[randtype];
2375 Assert(randtype < N_robot_types);
2377 rtval = gate_in_robot(randtype, -1);
2379 if ((rtval != -1) && (Game_mode & GM_MULTI))
2381 multi_send_boss_actions(OBJECT_NUMBER(objp), 3, randtype, Net_create_objnums[0]);
2382 map_objnum_local_to_local(Net_create_objnums[0]);
2390 //int multi_can_move_robot(object *objp, int awareness_level)
2395 void ai_multi_send_robot_position(int objnum, int force)
2398 if (Game_mode & GM_MULTI)
2401 multi_send_robot_position(objnum, 1);
2403 multi_send_robot_position(objnum, 0);
2409 // --------------------------------------------------------------------------------------------------------------------
2410 // Returns true if this object should be allowed to fire at the player.
2411 int maybe_ai_do_actual_firing_stuff(object *obj, ai_static *aip)
2414 if (Game_mode & GM_MULTI)
2415 if ((aip->GOAL_STATE != AIS_FLIN) && (obj->id != ROBOT_BRAIN))
2416 if (aip->CURRENT_STATE == AIS_FIRE)
2423 vms_vector Last_fired_upon_player_pos;
2425 // --------------------------------------------------------------------------------------------------------------------
2426 // If fire_anyway, fire even if player is not visible. We're firing near where we believe him to be. Perhaps he's
2427 // lurking behind a corner.
2428 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)
2432 if ((player_visibility == 2) || (Dist_to_last_fired_upon_player_pos < FIRE_AT_NEARBY_PLAYER_THRESHOLD )) {
2433 vms_vector fire_pos;
2435 fire_pos = Believed_player_pos;
2437 // Hack: If visibility not == 2, we're here because we're firing at a nearby player.
2438 // So, fire at Last_fired_upon_player_pos instead of the player position.
2439 if (!robptr->attack_type && (player_visibility != 2))
2440 fire_pos = Last_fired_upon_player_pos;
2442 // Changed by mk, 01/04/95, onearm would take about 9 seconds until he can fire at you.
2443 // 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.
2444 if (!object_animates || ready_to_fire(robptr, ailp)) {
2445 dot = vm_vec_dot(&obj->orient.fvec, vec_to_player);
2446 if ((dot >= 7*F1_0/8) || ((dot > F1_0/4) && robptr->boss_flag)) {
2448 if (gun_num < Robot_info[obj->id].n_guns) {
2449 if (robptr->attack_type == 1) {
2450 if (!Player_exploded && (dist_to_player < obj->size + ConsoleObject->size + F1_0*2)) { // robptr->circle_distance[Difficulty_level] + ConsoleObject->size) {
2451 if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION-2))
2453 do_ai_robot_hit_attack(obj, ConsoleObject, &obj->pos);
2455 // 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)));
2459 if ((gun_point->x == 0) && (gun_point->y == 0) && (gun_point->z == 0)) {
2460 ; //mprintf((0, "Would like to fire gun, but gun not selected.\n"));
2462 if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION))
2464 // New, multi-weapon-type system, 06/05/95 (life is slipping away...)
2466 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;
2471 if ((ailp->next_fire2 <= 0) && (robptr->weapon_type2 != -1)) {
2472 calc_gun_point(gun_point, obj, 0);
2473 ai_fire_laser_at_player(obj, gun_point, 0, &fire_pos);
2474 Last_fired_upon_player_pos = fire_pos;
2477 } else if (ailp->next_fire <= 0) {
2478 ai_fire_laser_at_player(obj, gun_point, gun_num, &fire_pos);
2479 Last_fired_upon_player_pos = fire_pos;
2484 // Wants to fire, so should go into chase mode, probably.
2485 if ( (aip->behavior != AIB_RUN_FROM)
2486 && (aip->behavior != AIB_STILL)
2487 && (aip->behavior != AIB_SNIPE)
2488 && (aip->behavior != AIB_FOLLOW)
2489 && (!robptr->attack_type)
2490 && ((ailp->mode == AIM_FOLLOW_PATH) || (ailp->mode == AIM_STILL)))
2491 ailp->mode = AIM_CHASE_OBJECT;
2494 aip->GOAL_STATE = AIS_RECO;
2495 ailp->goal_state[aip->CURRENT_GUN] = AIS_RECO;
2497 // Switch to next gun for next fire. If has 2 gun types, select gun #1, if exists.
2499 if (aip->CURRENT_GUN >= Robot_info[obj->id].n_guns)
2501 if ((Robot_info[obj->id].n_guns == 1) || (Robot_info[obj->id].weapon_type2 == -1))
2502 aip->CURRENT_GUN = 0;
2504 aip->CURRENT_GUN = 1;
2508 } 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))) ) {
2509 // Robots which fire homing weapons might fire even if they don't have a bead on the player.
2510 if (((!object_animates) || (ailp->achieved_state[aip->CURRENT_GUN] == AIS_FIRE))
2511 && (((ailp->next_fire <= 0) && (aip->CURRENT_GUN != 0)) || ((ailp->next_fire2 <= 0) && (aip->CURRENT_GUN == 0)))
2512 && (vm_vec_dist_quick(&Hit_pos, &obj->pos) > F1_0*40)) {
2513 if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION))
2515 ai_fire_laser_at_player(obj, gun_point, gun_num, &Believed_player_pos);
2517 aip->GOAL_STATE = AIS_RECO;
2518 ailp->goal_state[aip->CURRENT_GUN] = AIS_RECO;
2520 // Switch to next gun for next fire.
2522 if (aip->CURRENT_GUN >= Robot_info[obj->id].n_guns)
2523 aip->CURRENT_GUN = 0;
2525 // Switch to next gun for next fire.
2527 if (aip->CURRENT_GUN >= Robot_info[obj->id].n_guns)
2528 aip->CURRENT_GUN = 0;
2533 // ---------------------------------------------------------------
2535 vms_vector vec_to_last_pos;
2537 if (d_rand()/2 < fixmul(FrameTime, (Difficulty_level << 12) + 0x4000)) {
2538 if ((!object_animates || ready_to_fire(robptr, ailp)) && (Dist_to_last_fired_upon_player_pos < FIRE_AT_NEARBY_PLAYER_THRESHOLD)) {
2539 vm_vec_normalized_dir_quick(&vec_to_last_pos, &Believed_player_pos, &obj->pos);
2540 dot = vm_vec_dot(&obj->orient.fvec, &vec_to_last_pos);
2541 if (dot >= 7*F1_0/8) {
2543 if (aip->CURRENT_GUN < Robot_info[obj->id].n_guns) {
2544 if (robptr->attack_type == 1) {
2545 if (!Player_exploded && (dist_to_player < obj->size + ConsoleObject->size + F1_0*2)) { // robptr->circle_distance[Difficulty_level] + ConsoleObject->size) {
2546 if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION-2))
2548 do_ai_robot_hit_attack(obj, ConsoleObject, &obj->pos);
2550 // 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)));
2554 if ((gun_point->x == 0) && (gun_point->y == 0) && (gun_point->z == 0)) {
2555 ; //mprintf((0, "Would like to fire gun, but gun not selected.\n"));
2557 if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION))
2559 // New, multi-weapon-type system, 06/05/95 (life is slipping away...)
2561 if (ailp->next_fire <= 0)
2562 ai_fire_laser_at_player(obj, gun_point, gun_num, &Last_fired_upon_player_pos);
2564 if ((ailp->next_fire2 <= 0) && (robptr->weapon_type2 != -1)) {
2565 calc_gun_point(gun_point, obj, 0);
2566 ai_fire_laser_at_player(obj, gun_point, 0, &Last_fired_upon_player_pos);
2569 } else if (ailp->next_fire <= 0)
2570 ai_fire_laser_at_player(obj, gun_point, gun_num, &Last_fired_upon_player_pos);
2574 // Wants to fire, so should go into chase mode, probably.
2575 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)))
2576 ailp->mode = AIM_CHASE_OBJECT;
2578 aip->GOAL_STATE = AIS_RECO;
2579 ailp->goal_state[aip->CURRENT_GUN] = AIS_RECO;
2581 // Switch to next gun for next fire.
2583 if (aip->CURRENT_GUN >= Robot_info[obj->id].n_guns)
2585 if (Robot_info[obj->id].n_guns == 1)
2586 aip->CURRENT_GUN = 0;
2588 aip->CURRENT_GUN = 1;
2595 // ---------------------------------------------------------------