1 /*
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.
12 */
13
14 #ifdef HAVE_CONFIG_H
15 #include <conf.h>
16 #endif
17
18 #ifdef RCS
19 static char rcsid[] = "$Id: ai2.c,v 1.3 2001/10/25 02:15:55 bradleyb Exp $";
20 #endif
21
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <time.h>
25
26 #include "inferno.h"
27 #include "game.h"
28 #include "mono.h"
29 #include "3d.h"
30
31 #include "u_mem.h"
32 #include "object.h"
33 #include "render.h"
34 #include "error.h"
35 #include "ai.h"
36 #include "laser.h"
37 #include "fvi.h"
38 #include "polyobj.h"
39 #include "bm.h"
40 #include "weapon.h"
41 #include "physics.h"
42 #include "collide.h"
43 #include "player.h"
44 #include "wall.h"
45 #include "vclip.h"
46 #include "digi.h"
47 #include "fireball.h"
48 #include "morph.h"
49 #include "effects.h"
50 #include "timer.h"
51 #include "sounds.h"
52 #include "cntrlcen.h"
53 #include "multibot.h"
54 #ifdef NETWORK
55 #include "multi.h"
56 #include "network.h"
57 #endif
58 #include "gameseq.h"
59 #include "key.h"
60 #include "powerup.h"
61 #include "gauges.h"
62 #include "text.h"
63
64 #ifdef EDITOR
65 #include "editor/editor.h"
66 #include "editor/kdefs.h"
67 #endif
68
69 #ifndef NDEBUG
70 #include "string.h"
71 #include <time.h>
72 #endif
73
74 void teleport_boss(object *objp);
75 int boss_fits_in_seg(object *boss_objp, int segnum);
76
77
78 int Flinch_scale = 4;
79 int Attack_scale = 24;
80 byte Mike_to_matt_xlate[] = {AS_REST, AS_REST, AS_ALERT, AS_ALERT, AS_FLINCH, AS_FIRE, AS_RECOIL, AS_REST};
81
82 // Amount of time since the current robot was last processed for things such as movement.
83 // It is not valid to use FrameTime because robots do not get moved every frame.
84
85 int Num_boss_teleport_segs;
86 short Boss_teleport_segs[MAX_BOSS_TELEPORT_SEGS];
87 int Num_boss_gate_segs;
88 short Boss_gate_segs[MAX_BOSS_TELEPORT_SEGS];
89
90 // ---------------------------------------------------------
91 // On entry, N_robot_types had darn sure better be set.
92 // Mallocs N_robot_types robot_info structs into global Robot_info.
init_ai_system(void)93 void init_ai_system(void)
94 {
95 #if 0
96 int i;
97
98 mprintf((0, "Trying to malloc %i bytes for Robot_info.\n", N_robot_types * sizeof(*Robot_info)));
99 Robot_info = (robot_info *) d_malloc( N_robot_types * sizeof(*Robot_info) );
100 mprintf((0, "Robot_info = %i\n", Robot_info));
101
102 for (i=0; i<N_robot_types; i++) {
103 Robot_info[i].field_of_view = F1_0/2;
104 Robot_info[i].firing_wait = F1_0;
105 Robot_info[i].turn_time = F1_0*2;
106 // -- Robot_info[i].fire_power = F1_0;
107 // -- Robot_info[i].shield = F1_0/2;
108 Robot_info[i].max_speed = F1_0*10;
109 Robot_info[i].always_0xabcd = 0xabcd;
110 }
111 #endif
112
113 }
114
115 // ---------------------------------------------------------------------------------------------------------------------
116 // Given a behavior, set initial mode.
ai_behavior_to_mode(int behavior)117 int ai_behavior_to_mode(int behavior)
118 {
119 switch (behavior) {
120 case AIB_STILL: return AIM_STILL;
121 case AIB_NORMAL: return AIM_CHASE_OBJECT;
122 case AIB_BEHIND: return AIM_BEHIND;
123 case AIB_RUN_FROM: return AIM_RUN_FROM_OBJECT;
124 case AIB_SNIPE: return AIM_STILL; // Changed, 09/13/95, MK, snipers are still until they see you or are hit.
125 case AIB_STATION: return AIM_STILL;
126 case AIB_FOLLOW: return AIM_FOLLOW_PATH;
127 default: Int3(); // Contact Mike: Error, illegal behavior type
128 }
129
130 return AIM_STILL;
131 }
132
133 // ---------------------------------------------------------------------------------------------------------------------
134 // Call every time the player starts a new ship.
ai_init_boss_for_ship(void)135 void ai_init_boss_for_ship(void)
136 {
137 Boss_hit_time = -F1_0*10;
138
139 }
140
141 // ---------------------------------------------------------------------------------------------------------------------
142 // initial_mode == -1 means leave mode unchanged.
init_ai_object(int objnum,int behavior,int hide_segment)143 void init_ai_object(int objnum, int behavior, int hide_segment)
144 {
145 object *objp = &Objects[objnum];
146 ai_static *aip = &objp->ctype.ai_info;
147 ai_local *ailp = &Ai_local_info[objnum];
148 robot_info *robptr = &Robot_info[objp->id];
149
150 if (behavior == 0) {
151 // mprintf((0, "Behavior of 0 for object #%i, bashing to AIB_NORMAL.\n", objnum));
152 behavior = AIB_NORMAL;
153 aip->behavior = behavior;
154 }
155 // mprintf((0, "Initializing object #%i\n", objnum));
156
157 // mode is now set from the Robot dialog, so this should get overwritten.
158 ailp->mode = AIM_STILL;
159
160 ailp->previous_visibility = 0;
161
162 if (behavior != -1) {
163 aip->behavior = behavior;
164 ailp->mode = ai_behavior_to_mode(aip->behavior);
165 } else if (!((aip->behavior >= MIN_BEHAVIOR) && (aip->behavior <= MAX_BEHAVIOR))) {
166 mprintf((0, "[obj %i -> normal] ", objnum));
167 aip->behavior = AIB_NORMAL;
168 }
169
170 if (robptr->companion) {
171 ailp->mode = AIM_GOTO_PLAYER;
172 Escort_kill_object = -1;
173 }
174
175 if (robptr->thief) {
176 aip->behavior = AIB_SNIPE;
177 ailp->mode = AIM_THIEF_WAIT;
178 }
179
180 if (robptr->attack_type) {
181 aip->behavior = AIB_NORMAL;
182 ailp->mode = ai_behavior_to_mode(aip->behavior);
183 }
184
185 // This is astonishingly stupid! This routine gets called by matcens! KILL KILL KILL!!! Point_segs_free_ptr = Point_segs;
186
187 vm_vec_zero(&objp->mtype.phys_info.velocity);
188 // -- ailp->wait_time = F1_0*5;
189 ailp->player_awareness_time = 0;
190 ailp->player_awareness_type = 0;
191 aip->GOAL_STATE = AIS_SRCH;
192 aip->CURRENT_STATE = AIS_REST;
193 ailp->time_player_seen = GameTime;
194 ailp->next_misc_sound_time = GameTime;
195 ailp->time_player_sound_attacked = GameTime;
196
197 if ((behavior == AIB_SNIPE) || (behavior == AIB_STATION) || (behavior == AIB_RUN_FROM) || (behavior == AIB_FOLLOW)) {
198 aip->hide_segment = hide_segment;
199 ailp->goal_segment = hide_segment;
200 aip->hide_index = -1; // This means the path has not yet been created.
201 aip->cur_path_index = 0;
202 }
203
204 aip->SKIP_AI_COUNT = 0;
205
206 if (robptr->cloak_type == RI_CLOAKED_ALWAYS)
207 aip->CLOAKED = 1;
208 else
209 aip->CLOAKED = 0;
210
211 objp->mtype.phys_info.flags |= (PF_BOUNCE | PF_TURNROLL);
212
213 aip->REMOTE_OWNER = -1;
214
215 aip->dying_sound_playing = 0;
216 aip->dying_start_time = 0;
217
218 }
219
220
221 extern object * create_morph_robot( segment *segp, vms_vector *object_pos, int object_id);
222
223 // --------------------------------------------------------------------------------------------------------------------
224 // Create a Buddy bot.
225 // This automatically happens when you bring up the Buddy menu in a debug version.
226 // It is available as a cheat in a non-debug (release) version.
create_buddy_bot(void)227 void create_buddy_bot(void)
228 {
229 int buddy_id;
230 vms_vector object_pos;
231
232 for (buddy_id=0; buddy_id<N_robot_types; buddy_id++)
233 if (Robot_info[buddy_id].companion)
234 break;
235
236 if (buddy_id == N_robot_types) {
237 mprintf((0, "Can't create Buddy. No 'companion' bot found in Robot_info!\n"));
238 return;
239 }
240
241 compute_segment_center(&object_pos, &Segments[ConsoleObject->segnum]);
242
243 create_morph_robot( &Segments[ConsoleObject->segnum], &object_pos, buddy_id);
244 }
245
246 #define QUEUE_SIZE 256
247
248 // --------------------------------------------------------------------------------------------------------------------
249 // Create list of segments boss is allowed to teleport to at segptr.
250 // Set *num_segs.
251 // Boss is allowed to teleport to segments he fits in (calls object_intersects_wall) and
252 // he can reach from his initial position (calls find_connected_distance).
253 // If size_check is set, then only add segment if boss can fit in it, else any segment is legal.
254 // one_wall_hack added by MK, 10/13/95: A mega-hack! Set to !0 to ignore the
init_boss_segments(short segptr[],int * num_segs,int size_check,int one_wall_hack)255 void init_boss_segments(short segptr[], int *num_segs, int size_check, int one_wall_hack)
256 {
257 int boss_objnum=-1;
258 int i;
259
260 *num_segs = 0;
261 #ifdef EDITOR
262 N_selected_segs = 0;
263 #endif
264
265
266 if (size_check)
267 mprintf((0, "Boss fits in segments:\n"));
268 // See if there is a boss. If not, quick out.
269 for (i=0; i<=Highest_object_index; i++)
270 if ((Objects[i].type == OBJ_ROBOT) && (Robot_info[Objects[i].id].boss_flag)) {
271 if (boss_objnum != -1) // There are two bosses in this mine! i and boss_objnum!
272 Int3(); //do int3 here instead of assert so museum will work
273 boss_objnum = i;
274 }
275
276 if (boss_objnum != -1) {
277 int original_boss_seg;
278 vms_vector original_boss_pos;
279 object *boss_objp = &Objects[boss_objnum];
280 int head, tail;
281 int seg_queue[QUEUE_SIZE];
282 //ALREADY IN RENDER.H byte visited[MAX_SEGMENTS];
283 fix boss_size_save;
284
285 boss_size_save = boss_objp->size;
286 // -- Causes problems!! -- boss_objp->size = fixmul((F1_0/4)*3, boss_objp->size);
287 original_boss_seg = boss_objp->segnum;
288 original_boss_pos = boss_objp->pos;
289 head = 0;
290 tail = 0;
291 seg_queue[head++] = original_boss_seg;
292
293 segptr[(*num_segs)++] = original_boss_seg;
294 mprintf((0, "%4i ", original_boss_seg));
295 #ifdef EDITOR
296 Selected_segs[N_selected_segs++] = original_boss_seg;
297 #endif
298
299 for (i=0; i<=Highest_segment_index; i++)
300 visited[i] = 0;
301
302 while (tail != head) {
303 int sidenum;
304 segment *segp = &Segments[seg_queue[tail++]];
305
306 tail &= QUEUE_SIZE-1;
307
308 for (sidenum=0; sidenum<MAX_SIDES_PER_SEGMENT; sidenum++) {
309 int w;
310
311 if (((w = WALL_IS_DOORWAY(segp, sidenum)) & WID_FLY_FLAG) || one_wall_hack) {
312 // If we get here and w == WID_WALL, then we want to process through this wall, else not.
313 if (IS_CHILD(segp->children[sidenum])) {
314 if (one_wall_hack)
315 one_wall_hack--;
316 } else
317 continue;
318
319 if (visited[segp->children[sidenum]] == 0) {
320 seg_queue[head++] = segp->children[sidenum];
321 visited[segp->children[sidenum]] = 1;
322 head &= QUEUE_SIZE-1;
323 if (head > tail) {
324 if (head == tail + QUEUE_SIZE-1)
325 Int3(); // queue overflow. Make it bigger!
326 } else
327 if (head+QUEUE_SIZE == tail + QUEUE_SIZE-1)
328 Int3(); // queue overflow. Make it bigger!
329
330 if ((!size_check) || boss_fits_in_seg(boss_objp, segp->children[sidenum])) {
331 segptr[(*num_segs)++] = segp->children[sidenum];
332 if (size_check) mprintf((0, "%4i ", segp->children[sidenum]));
333 #ifdef EDITOR
334 Selected_segs[N_selected_segs++] = segp->children[sidenum];
335 #endif
336 if (*num_segs >= MAX_BOSS_TELEPORT_SEGS) {
337 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));
338 tail = head;
339 }
340 }
341 }
342 }
343 }
344
345 }
346
347 boss_objp->size = boss_size_save;
348 boss_objp->pos = original_boss_pos;
349 obj_relink(boss_objnum, original_boss_seg);
350
351 }
352
353 }
354
355 extern void init_buddy_for_level(void);
356
357 // ---------------------------------------------------------------------------------------------------------------------
init_ai_objects(void)358 void init_ai_objects(void)
359 {
360 int i;
361
362 Point_segs_free_ptr = Point_segs;
363
364 for (i=0; i<MAX_OBJECTS; i++) {
365 object *objp = &Objects[i];
366
367 if (objp->control_type == CT_AI)
368 init_ai_object(i, objp->ctype.ai_info.behavior, objp->ctype.ai_info.hide_segment);
369 }
370
371 init_boss_segments(Boss_gate_segs, &Num_boss_gate_segs, 0, 0);
372
373 init_boss_segments(Boss_teleport_segs, &Num_boss_teleport_segs, 1, 0);
374 if (Num_boss_teleport_segs == 1)
375 init_boss_segments(Boss_teleport_segs, &Num_boss_teleport_segs, 1, 1);
376
377 Boss_dying_sound_playing = 0;
378 Boss_dying = 0;
379 // -- unused! MK, 10/21/95 -- Boss_been_hit = 0;
380 Gate_interval = F1_0*4 - Difficulty_level*i2f(2)/3;
381
382 Ai_initialized = 1;
383
384 ai_do_cloak_stuff();
385
386 init_buddy_for_level();
387
388 if (Current_level_num == Last_level) {
389 Boss_teleport_interval = F1_0*10;
390 Boss_cloak_interval = F1_0*15; // Time between cloaks
391 } else {
392 Boss_teleport_interval = F1_0*7;
393 Boss_cloak_interval = F1_0*10; // Time between cloaks
394 }
395 }
396
397 int Lunacy = 0;
398 int Diff_save = 1;
399
400 fix Firing_wait_copy[MAX_ROBOT_TYPES];
401 fix Firing_wait2_copy[MAX_ROBOT_TYPES];
402 byte Rapidfire_count_copy[MAX_ROBOT_TYPES];
403
do_lunacy_on(void)404 void do_lunacy_on(void)
405 {
406 int i;
407
408 if (Lunacy) //already on
409 return;
410
411 Lunacy = 1;
412
413 Diff_save = Difficulty_level;
414 Difficulty_level = NDL-1;
415
416 for (i=0; i<MAX_ROBOT_TYPES; i++) {
417 Firing_wait_copy[i] = Robot_info[i].firing_wait[NDL-1];
418 Firing_wait2_copy[i] = Robot_info[i].firing_wait2[NDL-1];
419 Rapidfire_count_copy[i] = Robot_info[i].rapidfire_count[NDL-1];
420
421 Robot_info[i].firing_wait[NDL-1] = Robot_info[i].firing_wait[1];
422 Robot_info[i].firing_wait2[NDL-1] = Robot_info[i].firing_wait2[1];
423 Robot_info[i].rapidfire_count[NDL-1] = Robot_info[i].rapidfire_count[1];
424 }
425
426 }
427
do_lunacy_off(void)428 void do_lunacy_off(void)
429 {
430 int i;
431
432 if (!Lunacy) //already off
433 return;
434
435 Lunacy = 0;
436
437 for (i=0; i<MAX_ROBOT_TYPES; i++) {
438 Robot_info[i].firing_wait[NDL-1] = Firing_wait_copy[i];
439 Robot_info[i].firing_wait2[NDL-1] = Firing_wait2_copy[i];
440 Robot_info[i].rapidfire_count[NDL-1] = Rapidfire_count_copy[i];
441 }
442
443 Difficulty_level = Diff_save;
444 }
445
446 // ----------------------------------------------------------------
447 // Do *dest = *delta unless:
448 // *delta is pretty small
449 // and they are of different signs.
set_rotvel_and_saturate(fix * dest,fix delta)450 void set_rotvel_and_saturate(fix *dest, fix delta)
451 {
452 if ((delta ^ *dest) < 0) {
453 if (abs(delta) < F1_0/8) {
454 // mprintf((0, "D"));
455 *dest = delta/4;
456 } else
457 // mprintf((0, "d"));
458 *dest = delta;
459 } else {
460 // mprintf((0, "!"));
461 *dest = delta;
462 }
463 }
464
465 //--debug-- #ifndef NDEBUG
466 //--debug-- int Total_turns=0;
467 //--debug-- int Prevented_turns=0;
468 //--debug-- #endif
469
470 #define AI_TURN_SCALE 1
471 #define BABY_SPIDER_ID 14
472 #define FIRE_AT_NEARBY_PLAYER_THRESHOLD (F1_0*40)
473
474 extern void physics_turn_towards_vector(vms_vector *goal_vector, object *obj, fix rate);
475 extern fix Seismic_tremor_magnitude;
476
477 //-------------------------------------------------------------------------------------------
ai_turn_towards_vector(vms_vector * goal_vector,object * objp,fix rate)478 void ai_turn_towards_vector(vms_vector *goal_vector, object *objp, fix rate)
479 {
480 vms_vector new_fvec;
481 fix dot;
482
483 // Not all robots can turn, eg, SPECIAL_REACTOR_ROBOT
484 if (rate == 0)
485 return;
486
487 if ((objp->id == BABY_SPIDER_ID) && (objp->type == OBJ_ROBOT)) {
488 physics_turn_towards_vector(goal_vector, objp, rate);
489 return;
490 }
491
492 new_fvec = *goal_vector;
493
494 dot = vm_vec_dot(goal_vector, &objp->orient.fvec);
495
496 if (dot < (F1_0 - FrameTime/2)) {
497 fix mag;
498 fix new_scale = fixdiv(FrameTime * AI_TURN_SCALE, rate);
499 vm_vec_scale(&new_fvec, new_scale);
500 vm_vec_add2(&new_fvec, &objp->orient.fvec);
501 mag = vm_vec_normalize_quick(&new_fvec);
502 if (mag < F1_0/256) {
503 mprintf((1, "Degenerate vector in ai_turn_towards_vector (mag = %7.3f)\n", f2fl(mag)));
504 new_fvec = *goal_vector; // if degenerate vector, go right to goal
505 }
506 }
507
508 if (Seismic_tremor_magnitude) {
509 vms_vector rand_vec;
510 fix scale;
511 make_random_vector(&rand_vec);
512 scale = fixdiv(2*Seismic_tremor_magnitude, Robot_info[objp->id].mass);
513 vm_vec_scale_add2(&new_fvec, &rand_vec, scale);
514 }
515
516 vm_vector_2_matrix(&objp->orient, &new_fvec, NULL, &objp->orient.rvec);
517 }
518
519 // -- unused, 08/07/95 -- // --------------------------------------------------------------------------------------------------------------------
520 // -- unused, 08/07/95 -- void ai_turn_randomly(vms_vector *vec_to_player, object *obj, fix rate, int previous_visibility)
521 // -- unused, 08/07/95 -- {
522 // -- unused, 08/07/95 -- vms_vector curvec;
523 // -- unused, 08/07/95 --
524 // -- unused, 08/07/95 -- // -- MK, 06/09/95 // Random turning looks too stupid, so 1/4 of time, cheat.
525 // -- unused, 08/07/95 -- // -- MK, 06/09/95 if (previous_visibility)
526 // -- unused, 08/07/95 -- // -- MK, 06/09/95 if (d_rand() > 0x7400) {
527 // -- unused, 08/07/95 -- // -- MK, 06/09/95 ai_turn_towards_vector(vec_to_player, obj, rate);
528 // -- unused, 08/07/95 -- // -- MK, 06/09/95 return;
529 // -- unused, 08/07/95 -- // -- MK, 06/09/95 }
530 // -- unused, 08/07/95 --
531 // -- unused, 08/07/95 -- curvec = obj->mtype.phys_info.rotvel;
532 // -- unused, 08/07/95 --
533 // -- unused, 08/07/95 -- curvec.y += F1_0/64;
534 // -- unused, 08/07/95 --
535 // -- unused, 08/07/95 -- curvec.x += curvec.y/6;
536 // -- unused, 08/07/95 -- curvec.y += curvec.z/4;
537 // -- unused, 08/07/95 -- curvec.z += curvec.x/10;
538 // -- unused, 08/07/95 --
539 // -- unused, 08/07/95 -- if (abs(curvec.x) > F1_0/8) curvec.x /= 4;
540 // -- unused, 08/07/95 -- if (abs(curvec.y) > F1_0/8) curvec.y /= 4;
541 // -- unused, 08/07/95 -- if (abs(curvec.z) > F1_0/8) curvec.z /= 4;
542 // -- unused, 08/07/95 --
543 // -- unused, 08/07/95 -- obj->mtype.phys_info.rotvel = curvec;
544 // -- unused, 08/07/95 --
545 // -- unused, 08/07/95 -- }
546
547 // Overall_agitation affects:
548 // Widens field of view. Field of view is in range 0..1 (specified in bitmaps.tbl as N/360 degrees).
549 // Overall_agitation/128 subtracted from field of view, making robots see wider.
550 // Increases distance to which robot will search to create path to player by Overall_agitation/8 segments.
551 // Decreases wait between fire times by Overall_agitation/64 seconds.
552
553
554 // --------------------------------------------------------------------------------------------------------------------
555 // Returns:
556 // 0 Player is not visible from object, obstruction or something.
557 // 1 Player is visible, but not in field of view.
558 // 2 Player is visible and in field of view.
559 // Note: Uses Believed_player_pos as player's position for cloak effect.
560 // NOTE: Will destructively modify *pos if *pos is outside the mine.
player_is_visible_from_object(object * objp,vms_vector * pos,fix field_of_view,vms_vector * vec_to_player)561 int player_is_visible_from_object(object *objp, vms_vector *pos, fix field_of_view, vms_vector *vec_to_player)
562 {
563 fix dot;
564 fvi_query fq;
565
566 // Assume that robot's gun tip is in same segment as robot's center.
567 objp->ctype.ai_info.SUB_FLAGS &= ~SUB_FLAGS_GUNSEG;
568
569 fq.p0 = pos;
570 if ((pos->x != objp->pos.x) || (pos->y != objp->pos.y) || (pos->z != objp->pos.z)) {
571 int segnum = find_point_seg(pos, objp->segnum);
572 if (segnum == -1) {
573 fq.startseg = objp->segnum;
574 *pos = objp->pos;
575 mprintf((1, "Object %i, gun is outside mine, moving towards center.\n", objp-Objects));
576 move_towards_segment_center(objp);
577 } else {
578 if (segnum != objp->segnum) {
579 // -- mprintf((0, "Warning: Robot's gun tip not in same segment as robot center, frame %i.\n", FrameCount));
580 objp->ctype.ai_info.SUB_FLAGS |= SUB_FLAGS_GUNSEG;
581 }
582 fq.startseg = segnum;
583 }
584 } else
585 fq.startseg = objp->segnum;
586 fq.p1 = &Believed_player_pos;
587 fq.rad = F1_0/4;
588 fq.thisobjnum = objp-Objects;
589 fq.ignore_obj_list = NULL;
590 fq.flags = FQ_TRANSWALL; // -- Why were we checking objects? | FQ_CHECK_OBJS; //what about trans walls???
591
592 Hit_type = find_vector_intersection(&fq,&Hit_data);
593
594 Hit_pos = Hit_data.hit_pnt;
595 Hit_seg = Hit_data.hit_seg;
596
597 // -- when we stupidly checked objects -- if ((Hit_type == HIT_NONE) || ((Hit_type == HIT_OBJECT) && (Hit_data.hit_object == Players[Player_num].objnum))) {
598 if (Hit_type == HIT_NONE) {
599 dot = vm_vec_dot(vec_to_player, &objp->orient.fvec);
600 // 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)));
601 if (dot > field_of_view - (Overall_agitation << 9)) {
602 return 2;
603 } else {
604 return 1;
605 }
606 } else {
607 return 0;
608 }
609 }
610
611 // ------------------------------------------------------------------------------------------------------------------
612 // Return 1 if animates, else return 0
do_silly_animation(object * objp)613 int do_silly_animation(object *objp)
614 {
615 int objnum = objp-Objects;
616 jointpos *jp_list;
617 int robot_type, gun_num, robot_state, num_joint_positions;
618 polyobj_info *pobj_info = &objp->rtype.pobj_info;
619 ai_static *aip = &objp->ctype.ai_info;
620 // ai_local *ailp = &Ai_local_info[objnum];
621 int num_guns, at_goal;
622 int attack_type;
623 int flinch_attack_scale = 1;
624
625 robot_type = objp->id;
626 num_guns = Robot_info[robot_type].n_guns;
627 attack_type = Robot_info[robot_type].attack_type;
628
629 if (num_guns == 0) {
630 // mprintf((0, "Object #%i of type #%i has 0 guns.\n", objp-Objects, robot_type));
631 return 0;
632 }
633
634 // This is a hack. All positions should be based on goal_state, not GOAL_STATE.
635 robot_state = Mike_to_matt_xlate[aip->GOAL_STATE];
636 // previous_robot_state = Mike_to_matt_xlate[aip->CURRENT_STATE];
637
638 if (attack_type) // && ((robot_state == AS_FIRE) || (robot_state == AS_RECOIL)))
639 flinch_attack_scale = Attack_scale;
640 else if ((robot_state == AS_FLINCH) || (robot_state == AS_RECOIL))
641 flinch_attack_scale = Flinch_scale;
642
643 at_goal = 1;
644 for (gun_num=0; gun_num <= num_guns; gun_num++) {
645 int joint;
646
647 num_joint_positions = robot_get_anim_state(&jp_list, robot_type, gun_num, robot_state);
648
649 for (joint=0; joint<num_joint_positions; joint++) {
650 fix delta_angle, delta_2;
651 int jointnum = jp_list[joint].jointnum;
652 vms_angvec *jp = &jp_list[joint].angles;
653 vms_angvec *pobjp = &pobj_info->anim_angles[jointnum];
654
655 if (jointnum >= Polygon_models[objp->rtype.pobj_info.model_num].n_models) {
656 Int3(); // Contact Mike: incompatible data, illegal jointnum, problem in pof file?
657 continue;
658 }
659 if (jp->p != pobjp->p) {
660 if (gun_num == 0)
661 at_goal = 0;
662 Ai_local_info[objnum].goal_angles[jointnum].p = jp->p;
663
664 delta_angle = jp->p - pobjp->p;
665 if (delta_angle >= F1_0/2)
666 delta_2 = -ANIM_RATE;
667 else if (delta_angle >= 0)
668 delta_2 = ANIM_RATE;
669 else if (delta_angle >= -F1_0/2)
670 delta_2 = -ANIM_RATE;
671 else
672 delta_2 = ANIM_RATE;
673
674 if (flinch_attack_scale != 1)
675 delta_2 *= flinch_attack_scale;
676
677 Ai_local_info[objnum].delta_angles[jointnum].p = delta_2/DELTA_ANG_SCALE; // complete revolutions per second
678 }
679
680 if (jp->b != pobjp->b) {
681 if (gun_num == 0)
682 at_goal = 0;
683 Ai_local_info[objnum].goal_angles[jointnum].b = jp->b;
684
685 delta_angle = jp->b - pobjp->b;
686 if (delta_angle >= F1_0/2)
687 delta_2 = -ANIM_RATE;
688 else if (delta_angle >= 0)
689 delta_2 = ANIM_RATE;
690 else if (delta_angle >= -F1_0/2)
691 delta_2 = -ANIM_RATE;
692 else
693 delta_2 = ANIM_RATE;
694
695 if (flinch_attack_scale != 1)
696 delta_2 *= flinch_attack_scale;
697
698 Ai_local_info[objnum].delta_angles[jointnum].b = delta_2/DELTA_ANG_SCALE; // complete revolutions per second
699 }
700
701 if (jp->h != pobjp->h) {
702 if (gun_num == 0)
703 at_goal = 0;
704 Ai_local_info[objnum].goal_angles[jointnum].h = jp->h;
705
706 delta_angle = jp->h - pobjp->h;
707 if (delta_angle >= F1_0/2)
708 delta_2 = -ANIM_RATE;
709 else if (delta_angle >= 0)
710 delta_2 = ANIM_RATE;
711 else if (delta_angle >= -F1_0/2)
712 delta_2 = -ANIM_RATE;
713 else
714 delta_2 = ANIM_RATE;
715
716 if (flinch_attack_scale != 1)
717 delta_2 *= flinch_attack_scale;
718
719 Ai_local_info[objnum].delta_angles[jointnum].h = delta_2/DELTA_ANG_SCALE; // complete revolutions per second
720 }
721 }
722
723 if (at_goal) {
724 //ai_static *aip = &objp->ctype.ai_info;
725 ai_local *ailp = &Ai_local_info[objp-Objects];
726 ailp->achieved_state[gun_num] = ailp->goal_state[gun_num];
727 if (ailp->achieved_state[gun_num] == AIS_RECO)
728 ailp->goal_state[gun_num] = AIS_FIRE;
729
730 if (ailp->achieved_state[gun_num] == AIS_FLIN)
731 ailp->goal_state[gun_num] = AIS_LOCK;
732
733 }
734 }
735
736 if (at_goal == 1) //num_guns)
737 aip->CURRENT_STATE = aip->GOAL_STATE;
738
739 return 1;
740 }
741
742 // ------------------------------------------------------------------------------------------
743 // Move all sub-objects in an object towards their goals.
744 // Current orientation of object is at: pobj_info.anim_angles
745 // Goal orientation of object is at: ai_info.goal_angles
746 // Delta orientation of object is at: ai_info.delta_angles
ai_frame_animation(object * objp)747 void ai_frame_animation(object *objp)
748 {
749 int objnum = objp-Objects;
750 int joint;
751 int num_joints;
752
753 num_joints = Polygon_models[objp->rtype.pobj_info.model_num].n_models;
754
755 for (joint=1; joint<num_joints; joint++) {
756 fix delta_to_goal;
757 fix scaled_delta_angle;
758 vms_angvec *curangp = &objp->rtype.pobj_info.anim_angles[joint];
759 vms_angvec *goalangp = &Ai_local_info[objnum].goal_angles[joint];
760 vms_angvec *deltaangp = &Ai_local_info[objnum].delta_angles[joint];
761
762 delta_to_goal = goalangp->p - curangp->p;
763 if (delta_to_goal > 32767)
764 delta_to_goal = delta_to_goal - 65536;
765 else if (delta_to_goal < -32767)
766 delta_to_goal = 65536 + delta_to_goal;
767
768 if (delta_to_goal) {
769 scaled_delta_angle = fixmul(deltaangp->p, FrameTime) * DELTA_ANG_SCALE;
770 curangp->p += scaled_delta_angle;
771 if (abs(delta_to_goal) < abs(scaled_delta_angle))
772 curangp->p = goalangp->p;
773 }
774
775 delta_to_goal = goalangp->b - curangp->b;
776 if (delta_to_goal > 32767)
777 delta_to_goal = delta_to_goal - 65536;
778 else if (delta_to_goal < -32767)
779 delta_to_goal = 65536 + delta_to_goal;
780
781 if (delta_to_goal) {
782 scaled_delta_angle = fixmul(deltaangp->b, FrameTime) * DELTA_ANG_SCALE;
783 curangp->b += scaled_delta_angle;
784 if (abs(delta_to_goal) < abs(scaled_delta_angle))
785 curangp->b = goalangp->b;
786 }
787
788 delta_to_goal = goalangp->h - curangp->h;
789 if (delta_to_goal > 32767)
790 delta_to_goal = delta_to_goal - 65536;
791 else if (delta_to_goal < -32767)
792 delta_to_goal = 65536 + delta_to_goal;
793
794 if (delta_to_goal) {
795 scaled_delta_angle = fixmul(deltaangp->h, FrameTime) * DELTA_ANG_SCALE;
796 curangp->h += scaled_delta_angle;
797 if (abs(delta_to_goal) < abs(scaled_delta_angle))
798 curangp->h = goalangp->h;
799 }
800
801 }
802
803 }
804
805 // ----------------------------------------------------------------------------------
set_next_fire_time(object * objp,ai_local * ailp,robot_info * robptr,int gun_num)806 void set_next_fire_time(object *objp, ai_local *ailp, robot_info *robptr, int gun_num)
807 {
808 // For guys in snipe mode, they have a 50% shot of getting this shot in free.
809 if ((gun_num != 0) || (robptr->weapon_type2 == -1))
810 if ((objp->ctype.ai_info.behavior != AIB_SNIPE) || (d_rand() > 16384))
811 ailp->rapidfire_count++;
812
813 // Old way, 10/15/95: Continuous rapidfire if rapidfire_count set.
814 // -- if (((robptr->weapon_type2 == -1) || (gun_num != 0)) && (ailp->rapidfire_count < robptr->rapidfire_count[Difficulty_level])) {
815 // -- ailp->next_fire = min(F1_0/8, robptr->firing_wait[Difficulty_level]/2);
816 // -- } else {
817 // -- if ((robptr->weapon_type2 == -1) || (gun_num != 0)) {
818 // -- ailp->rapidfire_count = 0;
819 // -- ailp->next_fire = robptr->firing_wait[Difficulty_level];
820 // -- } else
821 // -- ailp->next_fire2 = robptr->firing_wait2[Difficulty_level];
822 // -- }
823
824 if (((gun_num != 0) || (robptr->weapon_type2 == -1)) && (ailp->rapidfire_count < robptr->rapidfire_count[Difficulty_level])) {
825 ailp->next_fire = min(F1_0/8, robptr->firing_wait[Difficulty_level]/2);
826 } else {
827 if ((robptr->weapon_type2 == -1) || (gun_num != 0)) {
828 ailp->next_fire = robptr->firing_wait[Difficulty_level];
829 if (ailp->rapidfire_count >= robptr->rapidfire_count[Difficulty_level])
830 ailp->rapidfire_count = 0;
831 } else
832 ailp->next_fire2 = robptr->firing_wait2[Difficulty_level];
833 }
834 }
835
836 // ----------------------------------------------------------------------------------
837 // When some robots collide with the player, they attack.
838 // If player is cloaked, then robot probably didn't actually collide, deal with that here.
do_ai_robot_hit_attack(object * robot,object * playerobj,vms_vector * collision_point)839 void do_ai_robot_hit_attack(object *robot, object *playerobj, vms_vector *collision_point)
840 {
841 ai_local *ailp = &Ai_local_info[robot-Objects];
842 robot_info *robptr = &Robot_info[robot->id];
843
844 //#ifndef NDEBUG
845 if (!Robot_firing_enabled)
846 return;
847 //#endif
848
849 // If player is dead, stop firing.
850 if (Objects[Players[Player_num].objnum].type == OBJ_GHOST)
851 return;
852
853 if (robptr->attack_type == 1) {
854 if (ailp->next_fire <= 0) {
855 if (!(Players[Player_num].flags & PLAYER_FLAGS_CLOAKED))
856 if (vm_vec_dist_quick(&ConsoleObject->pos, &robot->pos) < robot->size + ConsoleObject->size + F1_0*2) {
857 collide_player_and_nasty_robot( playerobj, robot, collision_point );
858 if (robptr->energy_drain && Players[Player_num].energy) {
859 Players[Player_num].energy -= robptr->energy_drain * F1_0;
860 if (Players[Player_num].energy < 0)
861 Players[Player_num].energy = 0;
862 // -- unused, use claw_sound in bitmaps.tbl -- digi_link_sound_to_pos( SOUND_ROBOT_SUCKED_PLAYER, playerobj->segnum, 0, collision_point, 0, F1_0 );
863 }
864 }
865
866 robot->ctype.ai_info.GOAL_STATE = AIS_RECO;
867 set_next_fire_time(robot, ailp, robptr, 1); // 1 = gun_num: 0 is special (uses next_fire2)
868 }
869 }
870
871 }
872
873 #ifndef _OBJECT_H
874 extern int Player_exploded;
875 #endif
876
877 #define FIRE_K 8 // Controls average accuracy of robot firing. Smaller numbers make firing worse. Being power of 2 doesn't matter.
878
879 // ====================================================================================================================
880
881 #define MIN_LEAD_SPEED (F1_0*4)
882 #define MAX_LEAD_DISTANCE (F1_0*200)
883 #define LEAD_RANGE (F1_0/2)
884
885 // --------------------------------------------------------------------------------------------------------------------
886 // Computes point at which projectile fired by robot can hit player given positions, player vel, elapsed time
compute_lead_component(fix player_pos,fix robot_pos,fix player_vel,fix elapsed_time)887 fix compute_lead_component(fix player_pos, fix robot_pos, fix player_vel, fix elapsed_time)
888 {
889 return fixdiv(player_pos - robot_pos, elapsed_time) + player_vel;
890 }
891
892 // --------------------------------------------------------------------------------------------------------------------
893 // Lead the player, returning point to fire at in fire_point.
894 // Rules:
895 // Player not cloaked
896 // Player must be moving at a speed >= MIN_LEAD_SPEED
897 // Player not farther away than MAX_LEAD_DISTANCE
898 // dot(vector_to_player, player_direction) must be in -LEAD_RANGE..LEAD_RANGE
899 // if firing a matter weapon, less leading, based on skill level.
lead_player(object * objp,vms_vector * fire_point,vms_vector * believed_player_pos,int gun_num,vms_vector * fire_vec)900 int lead_player(object *objp, vms_vector *fire_point, vms_vector *believed_player_pos, int gun_num, vms_vector *fire_vec)
901 {
902 fix dot, player_speed, dist_to_player, max_weapon_speed, projected_time;
903 vms_vector player_movement_dir, vec_to_player;
904 int weapon_type;
905 weapon_info *wptr;
906 robot_info *robptr;
907
908 if (Players[Player_num].flags & PLAYER_FLAGS_CLOAKED)
909 return 0;
910
911 player_movement_dir = ConsoleObject->mtype.phys_info.velocity;
912 player_speed = vm_vec_normalize_quick(&player_movement_dir);
913
914 if (player_speed < MIN_LEAD_SPEED)
915 return 0;
916
917 vm_vec_sub(&vec_to_player, believed_player_pos, fire_point);
918 dist_to_player = vm_vec_normalize_quick(&vec_to_player);
919 if (dist_to_player > MAX_LEAD_DISTANCE)
920 return 0;
921
922 dot = vm_vec_dot(&vec_to_player, &player_movement_dir);
923
924 if ((dot < -LEAD_RANGE) || (dot > LEAD_RANGE))
925 return 0;
926
927 // Looks like it might be worth trying to lead the player.
928 robptr = &Robot_info[objp->id];
929 weapon_type = robptr->weapon_type;
930 if (robptr->weapon_type2 != -1)
931 if (gun_num == 0)
932 weapon_type = robptr->weapon_type2;
933
934 wptr = &Weapon_info[weapon_type];
935 max_weapon_speed = wptr->speed[Difficulty_level];
936 if (max_weapon_speed < F1_0)
937 return 0;
938
939 // Matter weapons:
940 // At Rookie or Trainee, don't lead at all.
941 // At higher skill levels, don't lead as well. Accomplish this by screwing up max_weapon_speed.
942 if (wptr->matter)
943 {
944 if (Difficulty_level <= 1)
945 return 0;
946 else
947 max_weapon_speed *= (NDL-Difficulty_level);
948 }
949
950 projected_time = fixdiv(dist_to_player, max_weapon_speed);
951
952 fire_vec->x = compute_lead_component(believed_player_pos->x, fire_point->x, ConsoleObject->mtype.phys_info.velocity.x, projected_time);
953 fire_vec->y = compute_lead_component(believed_player_pos->y, fire_point->y, ConsoleObject->mtype.phys_info.velocity.y, projected_time);
954 fire_vec->z = compute_lead_component(believed_player_pos->z, fire_point->z, ConsoleObject->mtype.phys_info.velocity.z, projected_time);
955
956 vm_vec_normalize_quick(fire_vec);
957
958 Assert(vm_vec_dot(fire_vec, &objp->orient.fvec) < 3*F1_0/2);
959
960 // Make sure not firing at especially strange angle. If so, try to correct. If still bad, give up after one try.
961 if (vm_vec_dot(fire_vec, &objp->orient.fvec) < F1_0/2) {
962 vm_vec_add2(fire_vec, &vec_to_player);
963 vm_vec_scale(fire_vec, F1_0/2);
964 if (vm_vec_dot(fire_vec, &objp->orient.fvec) < F1_0/2) {
965 return 0;
966 }
967 }
968
969 return 1;
970 }
971
972 // --------------------------------------------------------------------------------------------------------------------
973 // Note: Parameter vec_to_player is only passed now because guns which aren't on the forward vector from the
974 // center of the robot will not fire right at the player. We need to aim the guns at the player. Barring that, we cheat.
975 // When this routine is complete, the parameter vec_to_player should not be necessary.
ai_fire_laser_at_player(object * obj,vms_vector * fire_point,int gun_num,vms_vector * believed_player_pos)976 void ai_fire_laser_at_player(object *obj, vms_vector *fire_point, int gun_num, vms_vector *believed_player_pos)
977 {
978 int objnum = obj-Objects;
979 ai_local *ailp = &Ai_local_info[objnum];
980 robot_info *robptr = &Robot_info[obj->id];
981 vms_vector fire_vec;
982 vms_vector bpp_diff;
983 int weapon_type;
984 fix aim, dot;
985 int count;
986
987 Assert(robptr->attack_type == 0); // We should never be coming here for the green guy, as he has no laser!
988
989 // If this robot is only awake because a camera woke it up, don't fire.
990 if (obj->ctype.ai_info.SUB_FLAGS & SUB_FLAGS_CAMERA_AWAKE)
991 return;
992
993 if (!Robot_firing_enabled)
994 return;
995
996 if (obj->control_type == CT_MORPH)
997 return;
998
999 // If player is exploded, stop firing.
1000 if (Player_exploded)
1001 return;
1002
1003 if (obj->ctype.ai_info.dying_start_time)
1004 return; // No firing while in death roll.
1005
1006 // Don't let the boss fire while in death roll. Sorry, this is the easiest way to do this.
1007 // If you try to key the boss off obj->ctype.ai_info.dying_start_time, it will hose the endlevel stuff.
1008 if (Boss_dying_start_time & Robot_info[obj->id].boss_flag)
1009 return;
1010
1011 // If player is cloaked, maybe don't fire based on how long cloaked and randomness.
1012 if (Players[Player_num].flags & PLAYER_FLAGS_CLOAKED) {
1013 fix cloak_time = Ai_cloak_info[objnum % MAX_AI_CLOAK_INFO].last_time;
1014
1015 if (GameTime - cloak_time > CLOAK_TIME_MAX/4)
1016 if (d_rand() > fixdiv(GameTime - cloak_time, CLOAK_TIME_MAX)/2) {
1017 set_next_fire_time(obj, ailp, robptr, gun_num);
1018 return;
1019 }
1020 }
1021
1022 // Handle problem of a robot firing through a wall because its gun tip is on the other
1023 // side of the wall than the robot's center. For speed reasons, we normally only compute
1024 // the vector from the gun point to the player. But we need to know whether the gun point
1025 // is separated from the robot's center by a wall. If so, don't fire!
1026 if (obj->ctype.ai_info.SUB_FLAGS & SUB_FLAGS_GUNSEG) {
1027 // Well, the gun point is in a different segment than the robot's center.
1028 // This is almost always ok, but it is not ok if something solid is in between.
1029 int conn_side;
1030 int gun_segnum = find_point_seg(fire_point, obj->segnum);
1031
1032 // See if these segments are connected, which should almost always be the case.
1033 conn_side = find_connect_side(&Segments[gun_segnum], &Segments[obj->segnum]);
1034 if (conn_side != -1) {
1035 // They are connected via conn_side in segment obj->segnum.
1036 // See if they are unobstructed.
1037 if (!(WALL_IS_DOORWAY(&Segments[obj->segnum], conn_side) & WID_FLY_FLAG)) {
1038 // Can't fly through, so don't let this bot fire through!
1039 return;
1040 }
1041 } else {
1042 // Well, they are not directly connected, so use find_vector_intersection to see if they are unobstructed.
1043 fvi_query fq;
1044 fvi_info hit_data;
1045 int fate;
1046
1047 fq.startseg = obj->segnum;
1048 fq.p0 = &obj->pos;
1049 fq.p1 = fire_point;
1050 fq.rad = 0;
1051 fq.thisobjnum = obj-Objects;
1052 fq.ignore_obj_list = NULL;
1053 fq.flags = FQ_TRANSWALL;
1054
1055 fate = find_vector_intersection(&fq, &hit_data);
1056 if (fate != HIT_NONE) {
1057 Int3(); // This bot's gun is poking through a wall, so don't fire.
1058 move_towards_segment_center(obj); // And decrease chances it will happen again.
1059 return;
1060 }
1061 }
1062 }
1063
1064 // -- mprintf((0, "Firing from gun #%i at time = %7.3f\n", gun_num, f2fl(GameTime)));
1065
1066 // Set position to fire at based on difficulty level and robot's aiming ability
1067 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.
1068
1069 // Robots aim more poorly during seismic disturbance.
1070 if (Seismic_tremor_magnitude) {
1071 fix temp;
1072
1073 temp = F1_0 - abs(Seismic_tremor_magnitude);
1074 if (temp < F1_0/2)
1075 temp = F1_0/2;
1076
1077 aim = fixmul(aim, temp);
1078 }
1079
1080 // Lead the player half the time.
1081 // Note that when leading the player, aim is perfect. This is probably acceptable since leading is so hacked in.
1082 // Problem is all robots will lead equally badly.
1083 if (d_rand() < 16384) {
1084 if (lead_player(obj, fire_point, believed_player_pos, gun_num, &fire_vec)) // Stuff direction to fire at in fire_point.
1085 goto player_led;
1086 }
1087
1088 dot = 0;
1089 count = 0; // Don't want to sit in this loop forever...
1090 while ((count < 4) && (dot < F1_0/4)) {
1091 bpp_diff.x = believed_player_pos->x + fixmul((d_rand()-16384) * (NDL-Difficulty_level-1) * 4, aim);
1092 bpp_diff.y = believed_player_pos->y + fixmul((d_rand()-16384) * (NDL-Difficulty_level-1) * 4, aim);
1093 bpp_diff.z = believed_player_pos->z + fixmul((d_rand()-16384) * (NDL-Difficulty_level-1) * 4, aim);
1094
1095 vm_vec_normalized_dir_quick(&fire_vec, &bpp_diff, fire_point);
1096 dot = vm_vec_dot(&obj->orient.fvec, &fire_vec);
1097 count++;
1098 }
1099 player_led: ;
1100
1101 weapon_type = robptr->weapon_type;
1102 if (robptr->weapon_type2 != -1)
1103 if (gun_num == 0)
1104 weapon_type = robptr->weapon_type2;
1105
1106 Laser_create_new_easy( &fire_vec, fire_point, obj-Objects, weapon_type, 1);
1107
1108 #ifdef NETWORK
1109 if (Game_mode & GM_MULTI) {
1110 ai_multi_send_robot_position(objnum, -1);
1111 multi_send_robot_fire(objnum, obj->ctype.ai_info.CURRENT_GUN, &fire_vec);
1112 }
1113 #endif
1114
1115 create_awareness_event(obj, PA_NEARBY_ROBOT_FIRED);
1116
1117 set_next_fire_time(obj, ailp, robptr, gun_num);
1118
1119 }
1120
1121 // --------------------------------------------------------------------------------------------------------------------
1122 // vec_goal must be normalized, or close to it.
1123 // if dot_based set, then speed is based on direction of movement relative to heading
move_towards_vector(object * objp,vms_vector * vec_goal,int dot_based)1124 void move_towards_vector(object *objp, vms_vector *vec_goal, int dot_based)
1125 {
1126 physics_info *pptr = &objp->mtype.phys_info;
1127 fix speed, dot, max_speed;
1128 robot_info *robptr = &Robot_info[objp->id];
1129 vms_vector vel;
1130
1131 // Trying to move towards player. If forward vector much different than velocity vector,
1132 // bash velocity vector twice as much towards player as usual.
1133
1134 vel = pptr->velocity;
1135 vm_vec_normalize_quick(&vel);
1136 dot = vm_vec_dot(&vel, &objp->orient.fvec);
1137
1138 if (robptr->thief)
1139 dot = (F1_0+dot)/2;
1140
1141 if (dot_based && (dot < 3*F1_0/4)) {
1142 // This funny code is supposed to slow down the robot and move his velocity towards his direction
1143 // more quickly than the general code
1144 pptr->velocity.x = pptr->velocity.x/2 + fixmul(vec_goal->x, FrameTime*32);
1145 pptr->velocity.y = pptr->velocity.y/2 + fixmul(vec_goal->y, FrameTime*32);
1146 pptr->velocity.z = pptr->velocity.z/2 + fixmul(vec_goal->z, FrameTime*32);
1147 } else {
1148 pptr->velocity.x += fixmul(vec_goal->x, FrameTime*64) * (Difficulty_level+5)/4;
1149 pptr->velocity.y += fixmul(vec_goal->y, FrameTime*64) * (Difficulty_level+5)/4;
1150 pptr->velocity.z += fixmul(vec_goal->z, FrameTime*64) * (Difficulty_level+5)/4;
1151 }
1152
1153 speed = vm_vec_mag_quick(&pptr->velocity);
1154 max_speed = robptr->max_speed[Difficulty_level];
1155
1156 // Green guy attacks twice as fast as he moves away.
1157 if ((robptr->attack_type == 1) || robptr->thief || robptr->kamikaze)
1158 max_speed *= 2;
1159
1160 if (speed > max_speed) {
1161 pptr->velocity.x = (pptr->velocity.x*3)/4;
1162 pptr->velocity.y = (pptr->velocity.y*3)/4;
1163 pptr->velocity.z = (pptr->velocity.z*3)/4;
1164 }
1165 }
1166
1167 // --------------------------------------------------------------------------------------------------------------------
move_towards_player(object * objp,vms_vector * vec_to_player)1168 void move_towards_player(object *objp, vms_vector *vec_to_player)
1169 // vec_to_player must be normalized, or close to it.
1170 {
1171 move_towards_vector(objp, vec_to_player, 1);
1172 }
1173
1174 // --------------------------------------------------------------------------------------------------------------------
1175 // I am ashamed of this: fast_flag == -1 means normal slide about. fast_flag = 0 means no evasion.
move_around_player(object * objp,vms_vector * vec_to_player,int fast_flag)1176 void move_around_player(object *objp, vms_vector *vec_to_player, int fast_flag)
1177 {
1178 physics_info *pptr = &objp->mtype.phys_info;
1179 fix speed;
1180 robot_info *robptr = &Robot_info[objp->id];
1181 int objnum = objp-Objects;
1182 int dir;
1183 int dir_change;
1184 fix ft;
1185 vms_vector evade_vector;
1186 int count=0;
1187
1188 if (fast_flag == 0)
1189 return;
1190
1191 dir_change = 48;
1192 ft = FrameTime;
1193 if (ft < F1_0/32) {
1194 dir_change *= 8;
1195 count += 3;
1196 } else
1197 while (ft < F1_0/4) {
1198 dir_change *= 2;
1199 ft *= 2;
1200 count++;
1201 }
1202
1203 dir = (FrameCount + (count+1) * (objnum*8 + objnum*4 + objnum)) & dir_change;
1204 dir >>= (4+count);
1205
1206 Assert((dir >= 0) && (dir <= 3));
1207
1208 switch (dir) {
1209 case 0:
1210 evade_vector.x = fixmul(vec_to_player->z, FrameTime*32);
1211 evade_vector.y = fixmul(vec_to_player->y, FrameTime*32);
1212 evade_vector.z = fixmul(-vec_to_player->x, FrameTime*32);
1213 break;
1214 case 1:
1215 evade_vector.x = fixmul(-vec_to_player->z, FrameTime*32);
1216 evade_vector.y = fixmul(vec_to_player->y, FrameTime*32);
1217 evade_vector.z = fixmul(vec_to_player->x, FrameTime*32);
1218 break;
1219 case 2:
1220 evade_vector.x = fixmul(-vec_to_player->y, FrameTime*32);
1221 evade_vector.y = fixmul(vec_to_player->x, FrameTime*32);
1222 evade_vector.z = fixmul(vec_to_player->z, FrameTime*32);
1223 break;
1224 case 3:
1225 evade_vector.x = fixmul(vec_to_player->y, FrameTime*32);
1226 evade_vector.y = fixmul(-vec_to_player->x, FrameTime*32);
1227 evade_vector.z = fixmul(vec_to_player->z, FrameTime*32);
1228 break;
1229 default:
1230 Error("Function move_around_player: Bad case.");
1231 }
1232
1233 // Note: -1 means normal circling about the player. > 0 means fast evasion.
1234 if (fast_flag > 0) {
1235 fix dot;
1236
1237 // Only take evasive action if looking at player.
1238 // Evasion speed is scaled by percentage of shields left so wounded robots evade less effectively.
1239
1240 dot = vm_vec_dot(vec_to_player, &objp->orient.fvec);
1241 if ((dot > robptr->field_of_view[Difficulty_level]) && !(ConsoleObject->flags & PLAYER_FLAGS_CLOAKED)) {
1242 fix damage_scale;
1243
1244 if (robptr->strength)
1245 damage_scale = fixdiv(objp->shields, robptr->strength);
1246 else
1247 damage_scale = F1_0;
1248 if (damage_scale > F1_0)
1249 damage_scale = F1_0; // Just in case...
1250 else if (damage_scale < 0)
1251 damage_scale = 0; // Just in case...
1252
1253 vm_vec_scale(&evade_vector, i2f(fast_flag) + damage_scale);
1254 }
1255 }
1256
1257 pptr->velocity.x += evade_vector.x;
1258 pptr->velocity.y += evade_vector.y;
1259 pptr->velocity.z += evade_vector.z;
1260
1261 speed = vm_vec_mag_quick(&pptr->velocity);
1262 if ((objp-Objects != 1) && (speed > robptr->max_speed[Difficulty_level])) {
1263 pptr->velocity.x = (pptr->velocity.x*3)/4;
1264 pptr->velocity.y = (pptr->velocity.y*3)/4;
1265 pptr->velocity.z = (pptr->velocity.z*3)/4;
1266 }
1267 }
1268
1269 // --------------------------------------------------------------------------------------------------------------------
move_away_from_player(object * objp,vms_vector * vec_to_player,int attack_type)1270 void move_away_from_player(object *objp, vms_vector *vec_to_player, int attack_type)
1271 {
1272 fix speed;
1273 physics_info *pptr = &objp->mtype.phys_info;
1274 robot_info *robptr = &Robot_info[objp->id];
1275 int objref;
1276
1277 pptr->velocity.x -= fixmul(vec_to_player->x, FrameTime*16);
1278 pptr->velocity.y -= fixmul(vec_to_player->y, FrameTime*16);
1279 pptr->velocity.z -= fixmul(vec_to_player->z, FrameTime*16);
1280
1281 if (attack_type) {
1282 // Get value in 0..3 to choose evasion direction.
1283 objref = ((objp-Objects) ^ ((FrameCount + 3*(objp-Objects)) >> 5)) & 3;
1284
1285 switch (objref) {
1286 case 0: vm_vec_scale_add2(&pptr->velocity, &objp->orient.uvec, FrameTime << 5); break;
1287 case 1: vm_vec_scale_add2(&pptr->velocity, &objp->orient.uvec, -FrameTime << 5); break;
1288 case 2: vm_vec_scale_add2(&pptr->velocity, &objp->orient.rvec, FrameTime << 5); break;
1289 case 3: vm_vec_scale_add2(&pptr->velocity, &objp->orient.rvec, -FrameTime << 5); break;
1290 default: Int3(); // Impossible, bogus value on objref, must be in 0..3
1291 }
1292 }
1293
1294
1295 speed = vm_vec_mag_quick(&pptr->velocity);
1296
1297 if (speed > robptr->max_speed[Difficulty_level]) {
1298 pptr->velocity.x = (pptr->velocity.x*3)/4;
1299 pptr->velocity.y = (pptr->velocity.y*3)/4;
1300 pptr->velocity.z = (pptr->velocity.z*3)/4;
1301 }
1302
1303 }
1304
1305 // --------------------------------------------------------------------------------------------------------------------
1306 // Move towards, away_from or around player.
1307 // Also deals with evasion.
1308 // If the flag evade_only is set, then only allowed to evade, not allowed to move otherwise (must have mode == AIM_STILL).
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)1309 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)
1310 {
1311 object *dobjp;
1312 robot_info *robptr = &Robot_info[objp->id];
1313
1314 Assert(player_visibility != -1);
1315
1316 // See if should take avoidance.
1317
1318 // New way, green guys don't evade: if ((robptr->attack_type == 0) && (objp->ctype.ai_info.danger_laser_num != -1)) {
1319 if (objp->ctype.ai_info.danger_laser_num != -1) {
1320 dobjp = &Objects[objp->ctype.ai_info.danger_laser_num];
1321
1322 if ((dobjp->type == OBJ_WEAPON) && (dobjp->signature == objp->ctype.ai_info.danger_laser_signature)) {
1323 fix dot, dist_to_laser, field_of_view;
1324 vms_vector vec_to_laser, laser_fvec;
1325
1326 field_of_view = Robot_info[objp->id].field_of_view[Difficulty_level];
1327
1328 vm_vec_sub(&vec_to_laser, &dobjp->pos, &objp->pos);
1329 dist_to_laser = vm_vec_normalize_quick(&vec_to_laser);
1330 dot = vm_vec_dot(&vec_to_laser, &objp->orient.fvec);
1331
1332 if ((dot > field_of_view) || (robptr->companion)) {
1333 fix laser_robot_dot;
1334 vms_vector laser_vec_to_robot;
1335
1336 // The laser is seen by the robot, see if it might hit the robot.
1337 // Get the laser's direction. If it's a polyobj, it can be gotten cheaply from the orientation matrix.
1338 if (dobjp->render_type == RT_POLYOBJ)
1339 laser_fvec = dobjp->orient.fvec;
1340 else { // Not a polyobj, get velocity and normalize.
1341 laser_fvec = dobjp->mtype.phys_info.velocity; //dobjp->orient.fvec;
1342 vm_vec_normalize_quick(&laser_fvec);
1343 }
1344 vm_vec_sub(&laser_vec_to_robot, &objp->pos, &dobjp->pos);
1345 vm_vec_normalize_quick(&laser_vec_to_robot);
1346 laser_robot_dot = vm_vec_dot(&laser_fvec, &laser_vec_to_robot);
1347
1348 if ((laser_robot_dot > F1_0*7/8) && (dist_to_laser < F1_0*80)) {
1349 int evade_speed;
1350
1351 ai_evaded = 1;
1352 evade_speed = Robot_info[objp->id].evade_speed[Difficulty_level];
1353
1354 move_around_player(objp, vec_to_player, evade_speed);
1355 }
1356 }
1357 return;
1358 }
1359 }
1360
1361 // If only allowed to do evade code, then done.
1362 // Hmm, perhaps brilliant insight. If want claw-type guys to keep coming, don't return here after evasion.
1363 if ((!robptr->attack_type) && (!robptr->thief) && evade_only)
1364 return;
1365
1366 // If we fall out of above, then no object to be avoided.
1367 objp->ctype.ai_info.danger_laser_num = -1;
1368
1369 // Green guy selects move around/towards/away based on firing time, not distance.
1370 if (robptr->attack_type == 1) {
1371 if (((ailp->next_fire > robptr->firing_wait[Difficulty_level]/4) && (dist_to_player < F1_0*30)) || Player_is_dead) {
1372 // 1/4 of time, move around player, 3/4 of time, move away from player
1373 if (d_rand() < 8192) {
1374 move_around_player(objp, vec_to_player, -1);
1375 } else {
1376 move_away_from_player(objp, vec_to_player, 1);
1377 }
1378 } else {
1379 move_towards_player(objp, vec_to_player);
1380 }
1381 } else if (robptr->thief) {
1382 move_towards_player(objp, vec_to_player);
1383 } else {
1384 int objval = ((objp-Objects) & 0x0f) ^ 0x0a;
1385
1386 // Changes here by MK, 12/29/95. Trying to get rid of endless circling around bots in a large room.
1387 if (robptr->kamikaze) {
1388 move_towards_player(objp, vec_to_player);
1389 } else if (dist_to_player < circle_distance)
1390 move_away_from_player(objp, vec_to_player, 0);
1391 else if ((dist_to_player < (3+objval)*circle_distance/2) && (ailp->next_fire > -F1_0)) {
1392 move_around_player(objp, vec_to_player, -1);
1393 } else {
1394 if ((-ailp->next_fire > F1_0 + (objval << 12)) && player_visibility) {
1395 // Usually move away, but sometimes move around player.
1396 if ((((GameTime >> 18) & 0x0f) ^ objval) > 4) {
1397 move_away_from_player(objp, vec_to_player, 0);
1398 } else {
1399 move_around_player(objp, vec_to_player, -1);
1400 }
1401 } else
1402 move_towards_player(objp, vec_to_player);
1403 }
1404 }
1405
1406 }
1407
1408 // --------------------------------------------------------------------------------------------------------------------
1409 // Compute a somewhat random, normalized vector.
make_random_vector(vms_vector * vec)1410 void make_random_vector(vms_vector *vec)
1411 {
1412 vec->x = (d_rand() - 16384) | 1; // make sure we don't create null vector
1413 vec->y = d_rand() - 16384;
1414 vec->z = d_rand() - 16384;
1415
1416 vm_vec_normalize_quick(vec);
1417 }
1418
1419 #ifndef NDEBUG
mprintf_animation_info(object * objp)1420 void mprintf_animation_info(object *objp)
1421 {
1422 ai_static *aip = &objp->ctype.ai_info;
1423 ai_local *ailp = &Ai_local_info[objp-Objects];
1424
1425 if (!Ai_info_enabled)
1426 return;
1427
1428 mprintf((0, "Goal = "));
1429
1430 switch (aip->GOAL_STATE) {
1431 case AIS_NONE: mprintf((0, "NONE ")); break;
1432 case AIS_REST: mprintf((0, "REST ")); break;
1433 case AIS_SRCH: mprintf((0, "SRCH ")); break;
1434 case AIS_LOCK: mprintf((0, "LOCK ")); break;
1435 case AIS_FLIN: mprintf((0, "FLIN ")); break;
1436 case AIS_FIRE: mprintf((0, "FIRE ")); break;
1437 case AIS_RECO: mprintf((0, "RECO ")); break;
1438 case AIS_ERR_: mprintf((0, "ERR_ ")); break;
1439
1440 }
1441
1442 mprintf((0, " Cur = "));
1443
1444 switch (aip->CURRENT_STATE) {
1445 case AIS_NONE: mprintf((0, "NONE ")); break;
1446 case AIS_REST: mprintf((0, "REST ")); break;
1447 case AIS_SRCH: mprintf((0, "SRCH ")); break;
1448 case AIS_LOCK: mprintf((0, "LOCK ")); break;
1449 case AIS_FLIN: mprintf((0, "FLIN ")); break;
1450 case AIS_FIRE: mprintf((0, "FIRE ")); break;
1451 case AIS_RECO: mprintf((0, "RECO ")); break;
1452 case AIS_ERR_: mprintf((0, "ERR_ ")); break;
1453 }
1454
1455 mprintf((0, " Aware = "));
1456
1457 switch (ailp->player_awareness_type) {
1458 case AIE_FIRE: mprintf((0, "FIRE ")); break;
1459 case AIE_HITT: mprintf((0, "HITT ")); break;
1460 case AIE_COLL: mprintf((0, "COLL ")); break;
1461 case AIE_HURT: mprintf((0, "HURT ")); break;
1462 }
1463
1464 mprintf((0, "Next fire = %6.3f, Time = %6.3f\n", f2fl(ailp->next_fire), f2fl(ailp->player_awareness_time)));
1465
1466 }
1467 #endif
1468
1469 // -------------------------------------------------------------------------------------------------------------------
1470 int Break_on_object = -1;
1471
do_firing_stuff(object * obj,int player_visibility,vms_vector * vec_to_player)1472 void do_firing_stuff(object *obj, int player_visibility, vms_vector *vec_to_player)
1473 {
1474 if ((Dist_to_last_fired_upon_player_pos < FIRE_AT_NEARBY_PLAYER_THRESHOLD ) || (player_visibility >= 1)) {
1475 // Now, if in robot's field of view, lock onto player
1476 fix dot = vm_vec_dot(&obj->orient.fvec, vec_to_player);
1477 if ((dot >= 7*F1_0/8) || (Players[Player_num].flags & PLAYER_FLAGS_CLOAKED)) {
1478 ai_static *aip = &obj->ctype.ai_info;
1479 ai_local *ailp = &Ai_local_info[obj-Objects];
1480
1481 switch (aip->GOAL_STATE) {
1482 case AIS_NONE:
1483 case AIS_REST:
1484 case AIS_SRCH:
1485 case AIS_LOCK:
1486 aip->GOAL_STATE = AIS_FIRE;
1487 if (ailp->player_awareness_type <= PA_NEARBY_ROBOT_FIRED) {
1488 ailp->player_awareness_type = PA_NEARBY_ROBOT_FIRED;
1489 ailp->player_awareness_time = PLAYER_AWARENESS_INITIAL_TIME;
1490 }
1491 break;
1492 }
1493 } else if (dot >= F1_0/2) {
1494 ai_static *aip = &obj->ctype.ai_info;
1495 switch (aip->GOAL_STATE) {
1496 case AIS_NONE:
1497 case AIS_REST:
1498 case AIS_SRCH:
1499 aip->GOAL_STATE = AIS_LOCK;
1500 break;
1501 }
1502 }
1503 }
1504 }
1505
1506 // --------------------------------------------------------------------------------------------------------------------
1507 // If a hiding robot gets bumped or hit, he decides to find another hiding place.
do_ai_robot_hit(object * objp,int type)1508 void do_ai_robot_hit(object *objp, int type)
1509 {
1510 if (objp->control_type == CT_AI) {
1511 if ((type == PA_WEAPON_ROBOT_COLLISION) || (type == PA_PLAYER_COLLISION))
1512 switch (objp->ctype.ai_info.behavior) {
1513 case AIB_STILL:
1514 {
1515 int r;
1516
1517 // Attack robots (eg, green guy) shouldn't have behavior = still.
1518 Assert(Robot_info[objp->id].attack_type == 0);
1519
1520 r = d_rand();
1521 // 1/8 time, charge player, 1/4 time create path, rest of time, do nothing
1522 if (r < 4096) {
1523 // -- mprintf((0, "Still guy switching to Station, creating path to player."));
1524 create_path_to_player(objp, 10, 1);
1525 objp->ctype.ai_info.behavior = AIB_STATION;
1526 objp->ctype.ai_info.hide_segment = objp->segnum;
1527 Ai_local_info[objp-Objects].mode = AIM_CHASE_OBJECT;
1528 } else if (r < 4096+8192) {
1529 // -- mprintf((0, "Still guy creating n segment path."));
1530 create_n_segment_path(objp, d_rand()/8192 + 2, -1);
1531 Ai_local_info[objp-Objects].mode = AIM_FOLLOW_PATH;
1532 }
1533 break;
1534 }
1535 }
1536 }
1537
1538 }
1539 #ifndef NDEBUG
1540 int Do_ai_flag=1;
1541 int Cvv_test=0;
1542 int Cvv_last_time[MAX_OBJECTS];
1543 int Gun_point_hack=0;
1544 #endif
1545
1546 int Robot_sound_volume=DEFAULT_ROBOT_SOUND_VOLUME;
1547
1548 // --------------------------------------------------------------------------------------------------------------------
1549 // Note: This function could be optimized. Surely player_is_visible_from_object would benefit from the
1550 // information of a normalized vec_to_player.
1551 // Return player visibility:
1552 // 0 not visible
1553 // 1 visible, but robot not looking at player (ie, on an unobstructed vector)
1554 // 2 visible and in robot's field of view
1555 // -1 player is cloaked
1556 // If the player is cloaked, set vec_to_player based on time player cloaked and last uncloaked position.
1557 // Updates ailp->previous_visibility if player is not cloaked, in which case the previous visibility is left unchanged
1558 // and is copied to player_visibility
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)1559 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)
1560 {
1561 if (!*flag) {
1562 if (Players[Player_num].flags & PLAYER_FLAGS_CLOAKED) {
1563 fix delta_time, dist;
1564 int cloak_index = (objp-Objects) % MAX_AI_CLOAK_INFO;
1565
1566 delta_time = GameTime - Ai_cloak_info[cloak_index].last_time;
1567 if (delta_time > F1_0*2) {
1568 vms_vector randvec;
1569
1570 Ai_cloak_info[cloak_index].last_time = GameTime;
1571 make_random_vector(&randvec);
1572 vm_vec_scale_add2(&Ai_cloak_info[cloak_index].last_position, &randvec, 8*delta_time );
1573 }
1574
1575 dist = vm_vec_normalized_dir_quick(vec_to_player, &Ai_cloak_info[cloak_index].last_position, pos);
1576 *player_visibility = player_is_visible_from_object(objp, pos, robptr->field_of_view[Difficulty_level], vec_to_player);
1577 // *player_visibility = 2;
1578
1579 if ((ailp->next_misc_sound_time < GameTime) && ((ailp->next_fire < F1_0) || (ailp->next_fire2 < F1_0)) && (dist < F1_0*20)) {
1580 // mprintf((0, "ANGRY! "));
1581 ailp->next_misc_sound_time = GameTime + (d_rand() + F1_0) * (7 - Difficulty_level) / 1;
1582 digi_link_sound_to_pos( robptr->see_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume);
1583 }
1584 } else {
1585 // Compute expensive stuff -- vec_to_player and player_visibility
1586 vm_vec_normalized_dir_quick(vec_to_player, &Believed_player_pos, pos);
1587 if ((vec_to_player->x == 0) && (vec_to_player->y == 0) && (vec_to_player->z == 0)) {
1588 // -- mprintf((0, "Warning: Player and robot at exactly the same location.\n"));
1589 vec_to_player->x = F1_0;
1590 }
1591 *player_visibility = player_is_visible_from_object(objp, pos, robptr->field_of_view[Difficulty_level], vec_to_player);
1592
1593 // This horrible code added by MK in desperation on 12/13/94 to make robots wake up as soon as they
1594 // see you without killing frame rate.
1595 {
1596 ai_static *aip = &objp->ctype.ai_info;
1597 if ((*player_visibility == 2) && (ailp->previous_visibility != 2))
1598 if ((aip->GOAL_STATE == AIS_REST) || (aip->CURRENT_STATE == AIS_REST)) {
1599 aip->GOAL_STATE = AIS_FIRE;
1600 aip->CURRENT_STATE = AIS_FIRE;
1601 }
1602 }
1603
1604 if ((ailp->previous_visibility != *player_visibility) && (*player_visibility == 2)) {
1605 if (ailp->previous_visibility == 0) {
1606 if (ailp->time_player_seen + F1_0/2 < GameTime) {
1607 // -- mprintf((0, "SEE! "));
1608 // -- if (Player_exploded)
1609 // -- digi_link_sound_to_pos( robptr->taunt_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume);
1610 // -- else
1611 digi_link_sound_to_pos( robptr->see_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume);
1612 ailp->time_player_sound_attacked = GameTime;
1613 ailp->next_misc_sound_time = GameTime + F1_0 + d_rand()*4;
1614 }
1615 } else if (ailp->time_player_sound_attacked + F1_0/4 < GameTime) {
1616 // -- mprintf((0, "ANGRY! "));
1617 // -- if (Player_exploded)
1618 // -- digi_link_sound_to_pos( robptr->taunt_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume);
1619 // -- else
1620 digi_link_sound_to_pos( robptr->attack_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume);
1621 ailp->time_player_sound_attacked = GameTime;
1622 }
1623 }
1624
1625 if ((*player_visibility == 2) && (ailp->next_misc_sound_time < GameTime)) {
1626 // -- mprintf((0, "ATTACK! "));
1627 ailp->next_misc_sound_time = GameTime + (d_rand() + F1_0) * (7 - Difficulty_level) / 2;
1628 // -- if (Player_exploded)
1629 // -- digi_link_sound_to_pos( robptr->taunt_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume);
1630 // -- else
1631 digi_link_sound_to_pos( robptr->attack_sound, objp->segnum, 0, pos, 0 , Robot_sound_volume);
1632 }
1633 ailp->previous_visibility = *player_visibility;
1634 }
1635
1636 *flag = 1;
1637
1638 // @mk, 09/21/95: If player view is not obstructed and awareness is at least as high as a nearby collision,
1639 // act is if robot is looking at player.
1640 if (ailp->player_awareness_type >= PA_NEARBY_ROBOT_FIRED)
1641 if (*player_visibility == 1)
1642 *player_visibility = 2;
1643
1644 if (*player_visibility) {
1645 ailp->time_player_seen = GameTime;
1646 }
1647 }
1648
1649 }
1650
1651 // --------------------------------------------------------------------------------------------------------------------
1652 // Move the object objp to a spot in which it doesn't intersect a wall.
1653 // It might mean moving it outside its current segment.
move_object_to_legal_spot(object * objp)1654 void move_object_to_legal_spot(object *objp)
1655 {
1656 vms_vector original_pos = objp->pos;
1657 int i;
1658 segment *segp = &Segments[objp->segnum];
1659
1660 for (i=0; i<MAX_SIDES_PER_SEGMENT; i++) {
1661 if (WALL_IS_DOORWAY(segp, i) & WID_FLY_FLAG) {
1662 vms_vector segment_center, goal_dir;
1663 fix dist_to_center; // Value not used so far.
1664
1665 compute_segment_center(&segment_center, &Segments[segp->children[i]]);
1666 vm_vec_sub(&goal_dir, &segment_center, &objp->pos);
1667 dist_to_center = vm_vec_normalize_quick(&goal_dir);
1668 vm_vec_scale(&goal_dir, objp->size);
1669 vm_vec_add2(&objp->pos, &goal_dir);
1670 if (!object_intersects_wall(objp)) {
1671 int new_segnum = find_point_seg(&objp->pos, objp->segnum);
1672
1673 if (new_segnum != -1) {
1674 obj_relink(objp-Objects, new_segnum);
1675 return;
1676 }
1677 } else
1678 objp->pos = original_pos;
1679 }
1680 }
1681
1682 if (Robot_info[objp->id].boss_flag) {
1683 Int3(); // Note: Boss is poking outside mine. Will try to resolve.
1684 teleport_boss(objp);
1685 } else {
1686 mprintf((0, "Note: Killing robot #%i because he's badly stuck outside the mine.\n", objp-Objects));
1687 apply_damage_to_robot(objp, objp->shields*2, objp-Objects);
1688 }
1689 }
1690
1691 // --------------------------------------------------------------------------------------------------------------------
1692 // Move object one object radii from current position towards segment center.
1693 // If segment center is nearer than 2 radii, move it to center.
move_towards_segment_center(object * objp)1694 void move_towards_segment_center(object *objp)
1695 {
1696 int segnum = objp->segnum;
1697 fix dist_to_center;
1698 vms_vector segment_center, goal_dir;
1699
1700 compute_segment_center(&segment_center, &Segments[segnum]);
1701
1702 vm_vec_sub(&goal_dir, &segment_center, &objp->pos);
1703 dist_to_center = vm_vec_normalize_quick(&goal_dir);
1704
1705 if (dist_to_center < objp->size) {
1706 // Center is nearer than the distance we want to move, so move to center.
1707 objp->pos = segment_center;
1708 mprintf((0, "Object #%i moved to center of segment #%i (%7.3f %7.3f %7.3f)\n", objp-Objects, objp->segnum, f2fl(objp->pos.x), f2fl(objp->pos.y), f2fl(objp->pos.z)));
1709 if (object_intersects_wall(objp)) {
1710 mprintf((0, "Object #%i still illegal, trying trickier move.\n"));
1711 move_object_to_legal_spot(objp);
1712 }
1713 } else {
1714 int new_segnum;
1715 // Move one radii towards center.
1716 vm_vec_scale(&goal_dir, objp->size);
1717 vm_vec_add2(&objp->pos, &goal_dir);
1718 new_segnum = find_point_seg(&objp->pos, objp->segnum);
1719 if (new_segnum == -1) {
1720 objp->pos = segment_center;
1721 move_object_to_legal_spot(objp);
1722 }
1723 // -- mprintf((0, "Obj %i moved twrds seg %i (%6.2f %6.2f %6.2f), dists: [%6.2f %6.2f]\n", objp-Objects, objp->segnum, f2fl(objp->pos.x), f2fl(objp->pos.y), f2fl(objp->pos.z), f2fl(vm_vec_dist_quick(&objp->pos, &segment_center)), f2fl(vm_vec_dist_quick(&objp->pos, &segment_center))));
1724 }
1725
1726 }
1727
1728 extern int Buddy_objnum;
1729
1730 //int Buddy_got_stuck = 0;
1731
1732 // -----------------------------------------------------------------------------------------------------------
1733 // Return true if door can be flown through by a suitable type robot.
1734 // Brains, avoid robots, companions can open doors.
1735 // objp == NULL means treat as buddy.
ai_door_is_openable(object * objp,segment * segp,int sidenum)1736 int ai_door_is_openable(object *objp, segment *segp, int sidenum)
1737 {
1738 int wall_num;
1739 wall *wallp;
1740
1741 if (!IS_CHILD(segp->children[sidenum]))
1742 return 0; //trap -2 (exit side)
1743
1744 wall_num = segp->sides[sidenum].wall_num;
1745
1746 if (wall_num == -1) //if there's no door at all...
1747 return 0; //..then say it can't be opened
1748
1749 // The mighty console object can open all doors (for purposes of determining paths).
1750 if (objp == ConsoleObject) {
1751
1752 if (Walls[wall_num].type == WALL_DOOR)
1753 return 1;
1754 }
1755
1756 wallp = &Walls[wall_num];
1757
1758 if ((objp == NULL) || (Robot_info[objp->id].companion == 1)) {
1759 int ailp_mode;
1760
1761 if (wallp->flags & WALL_BUDDY_PROOF) {
1762 if ((wallp->type == WALL_DOOR) && (wallp->state == WALL_DOOR_CLOSED))
1763 return 0;
1764 else if (wallp->type == WALL_CLOSED)
1765 return 0;
1766 else if ((wallp->type == WALL_ILLUSION) && !(wallp->flags & WALL_ILLUSION_OFF))
1767 return 0;
1768 }
1769
1770 if (wallp->keys != KEY_NONE) {
1771 if (wallp->keys == KEY_BLUE)
1772 return (Players[Player_num].flags & PLAYER_FLAGS_BLUE_KEY);
1773 else if (wallp->keys == KEY_GOLD)
1774 return (Players[Player_num].flags & PLAYER_FLAGS_GOLD_KEY);
1775 else if (wallp->keys == KEY_RED)
1776 return (Players[Player_num].flags & PLAYER_FLAGS_RED_KEY);
1777 }
1778
1779 if ((wallp->type != WALL_DOOR) && (wallp->type != WALL_CLOSED))
1780 return 1;
1781
1782 // If Buddy is returning to player, don't let him think he can get through triggered doors.
1783 // It's only valid to think that if the player is going to get him through. But if he's
1784 // going to the player, the player is probably on the opposite side.
1785 if (objp == NULL)
1786 ailp_mode = Ai_local_info[Buddy_objnum].mode;
1787 else
1788 ailp_mode = Ai_local_info[objp-Objects].mode;
1789
1790 // -- if (Buddy_got_stuck) {
1791 if (ailp_mode == AIM_GOTO_PLAYER) {
1792 if ((wallp->type == WALL_BLASTABLE) && (wallp->state != WALL_BLASTED))
1793 return 0;
1794 if (wallp->type == WALL_CLOSED)
1795 return 0;
1796 if (wallp->type == WALL_DOOR) {
1797 if ((wallp->flags & WALL_DOOR_LOCKED) && (wallp->state == WALL_DOOR_CLOSED))
1798 return 0;
1799 }
1800 }
1801 // -- }
1802
1803 if ((ailp_mode != AIM_GOTO_PLAYER) && (wallp->controlling_trigger != -1)) {
1804 int clip_num = wallp->clip_num;
1805
1806 if (clip_num == -1)
1807 return 1;
1808 else if (WallAnims[clip_num].flags & WCF_HIDDEN) {
1809 if (wallp->state == WALL_DOOR_CLOSED)
1810 return 0;
1811 else
1812 return 1;
1813 } else
1814 return 1;
1815 }
1816
1817 if (wallp->type == WALL_DOOR) {
1818 if (wallp->type == WALL_BLASTABLE)
1819 return 1;
1820 else {
1821 int clip_num = wallp->clip_num;
1822
1823 if (clip_num == -1)
1824 return 1;
1825 // Buddy allowed to go through secret doors to get to player.
1826 else if ((ailp_mode != AIM_GOTO_PLAYER) && (WallAnims[clip_num].flags & WCF_HIDDEN)) {
1827 if (wallp->state == WALL_DOOR_CLOSED)
1828 return 0;
1829 else
1830 return 1;
1831 } else
1832 return 1;
1833 }
1834 }
1835 } else if ((objp->id == ROBOT_BRAIN) || (objp->ctype.ai_info.behavior == AIB_RUN_FROM) || (objp->ctype.ai_info.behavior == AIB_SNIPE)) {
1836 if (wall_num != -1)
1837 {
1838 if ((wallp->type == WALL_DOOR) && (wallp->keys == KEY_NONE) && !(wallp->flags & WALL_DOOR_LOCKED))
1839 return 1;
1840 else if (wallp->keys != KEY_NONE) { // Allow bots to open doors to which player has keys.
1841 if (wallp->keys & Players[Player_num].flags)
1842 return 1;
1843 }
1844 }
1845 }
1846 return 0;
1847 }
1848
1849 // -----------------------------------------------------------------------------------------------------------
1850 // Return side of openable door in segment, if any. If none, return -1.
openable_doors_in_segment(int segnum)1851 int openable_doors_in_segment(int segnum)
1852 {
1853 int i;
1854
1855 if ((segnum < 0) || (segnum > Highest_segment_index))
1856 return -1;
1857
1858 for (i=0; i<MAX_SIDES_PER_SEGMENT; i++) {
1859 if (Segments[segnum].sides[i].wall_num != -1) {
1860 int wall_num = Segments[segnum].sides[i].wall_num;
1861 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))
1862 return i;
1863 }
1864 }
1865
1866 return -1;
1867
1868 }
1869
1870 // -- // --------------------------------------------------------------------------------------------------------------------
1871 // -- // Return true if a special object (player or control center) is in this segment.
1872 // -- int special_object_in_seg(int segnum)
1873 // -- {
1874 // -- int objnum;
1875 // --
1876 // -- objnum = Segments[segnum].objects;
1877 // --
1878 // -- while (objnum != -1) {
1879 // -- if ((Objects[objnum].type == OBJ_PLAYER) || (Objects[objnum].type == OBJ_CNTRLCEN)) {
1880 // -- mprintf((0, "Special object of type %i in segment %i\n", Objects[objnum].type, segnum));
1881 // -- return 1;
1882 // -- } else
1883 // -- objnum = Objects[objnum].next;
1884 // -- }
1885 // --
1886 // -- return 0;
1887 // -- }
1888
1889 // -- // --------------------------------------------------------------------------------------------------------------------
1890 // -- // Randomly select a segment attached to *segp, reachable by flying.
1891 // -- int get_random_child(int segnum)
1892 // -- {
1893 // -- int sidenum;
1894 // -- segment *segp = &Segments[segnum];
1895 // --
1896 // -- sidenum = (rand() * 6) >> 15;
1897 // --
1898 // -- while (!(WALL_IS_DOORWAY(segp, sidenum) & WID_FLY_FLAG))
1899 // -- sidenum = (rand() * 6) >> 15;
1900 // --
1901 // -- segnum = segp->children[sidenum];
1902 // --
1903 // -- return segnum;
1904 // -- }
1905
1906 // --------------------------------------------------------------------------------------------------------------------
1907 // Return true if placing an object of size size at pos *pos intersects a (player or robot or control center) in segment *segp.
check_object_object_intersection(vms_vector * pos,fix size,segment * segp)1908 int check_object_object_intersection(vms_vector *pos, fix size, segment *segp)
1909 {
1910 int curobjnum;
1911
1912 // If this would intersect with another object (only check those in this segment), then try to move.
1913 curobjnum = segp->objects;
1914 while (curobjnum != -1) {
1915 object *curobjp = &Objects[curobjnum];
1916 if ((curobjp->type == OBJ_PLAYER) || (curobjp->type == OBJ_ROBOT) || (curobjp->type == OBJ_CNTRLCEN)) {
1917 if (vm_vec_dist_quick(pos, &curobjp->pos) < size + curobjp->size)
1918 return 1;
1919 }
1920 curobjnum = curobjp->next;
1921 }
1922
1923 return 0;
1924
1925 }
1926
1927 // --------------------------------------------------------------------------------------------------------------------
1928 // Return objnum if object created, else return -1.
1929 // If pos == NULL, pick random spot in segment.
create_gated_robot(int segnum,int object_id,vms_vector * pos)1930 int create_gated_robot( int segnum, int object_id, vms_vector *pos)
1931 {
1932 int objnum;
1933 object *objp;
1934 segment *segp = &Segments[segnum];
1935 vms_vector object_pos;
1936 robot_info *robptr = &Robot_info[object_id];
1937 int i, count=0;
1938 fix objsize = Polygon_models[robptr->model_num].rad;
1939 int default_behavior;
1940
1941 if (GameTime - Last_gate_time < Gate_interval)
1942 return -1;
1943
1944 for (i=0; i<=Highest_object_index; i++)
1945 if (Objects[i].type == OBJ_ROBOT)
1946 if (Objects[i].matcen_creator == BOSS_GATE_MATCEN_NUM)
1947 count++;
1948
1949 if (count > 2*Difficulty_level + 6) {
1950 //mprintf((0, "Cannot gate in a robot until you kill one.\n"));
1951 Last_gate_time = GameTime - 3*Gate_interval/4;
1952 return -1;
1953 }
1954
1955 compute_segment_center(&object_pos, segp);
1956 if (pos == NULL)
1957 pick_random_point_in_seg(&object_pos, segp-Segments);
1958 else
1959 object_pos = *pos;
1960
1961 // See if legal to place object here. If not, move about in segment and try again.
1962 if (check_object_object_intersection(&object_pos, objsize, segp)) {
1963 //mprintf((0, "Can't get in because object collides with something.\n"));
1964 Last_gate_time = GameTime - 3*Gate_interval/4;
1965 return -1;
1966 }
1967
1968 objnum = obj_create(OBJ_ROBOT, object_id, segnum, &object_pos, &vmd_identity_matrix, objsize, CT_AI, MT_PHYSICS, RT_POLYOBJ);
1969
1970 if ( objnum < 0 ) {
1971 // mprintf((1, "Can't get object to gate in robot. Not gating in.\n"));
1972 Last_gate_time = GameTime - 3*Gate_interval/4;
1973 return -1;
1974 }
1975
1976 //mprintf((0, "Gating in object %i in segment %i\n", objnum, segp-Segments));
1977
1978 Objects[objnum].lifeleft = F1_0*30; // Gated in robots only live 30 seconds.
1979
1980 #ifdef NETWORK
1981 Net_create_objnums[0] = objnum; // A convenient global to get objnum back to caller for multiplayer
1982 #endif
1983
1984 objp = &Objects[objnum];
1985
1986 //Set polygon-object-specific data
1987
1988 objp->rtype.pobj_info.model_num = robptr->model_num;
1989 objp->rtype.pobj_info.subobj_flags = 0;
1990
1991 //set Physics info
1992
1993 objp->mtype.phys_info.mass = robptr->mass;
1994 objp->mtype.phys_info.drag = robptr->drag;
1995
1996 objp->mtype.phys_info.flags |= (PF_LEVELLING);
1997
1998 objp->shields = robptr->strength;
1999 objp->matcen_creator = BOSS_GATE_MATCEN_NUM; // flag this robot as having been created by the boss.
2000
2001 default_behavior = Robot_info[objp->id].behavior;
2002 init_ai_object(objp-Objects, default_behavior, -1 ); // Note, -1 = segment this robot goes to to hide, should probably be something useful
2003
2004 object_create_explosion(segnum, &object_pos, i2f(10), VCLIP_MORPHING_ROBOT );
2005 digi_link_sound_to_pos( Vclip[VCLIP_MORPHING_ROBOT].sound_num, segnum, 0, &object_pos, 0 , F1_0);
2006 morph_start(objp);
2007
2008 Last_gate_time = GameTime;
2009
2010 Players[Player_num].num_robots_level++;
2011 Players[Player_num].num_robots_total++;
2012
2013 return objp-Objects;
2014 }
2015
2016 #define MAX_SPEW_BOT 3
2017
2018 int Spew_bots[NUM_D2_BOSSES][MAX_SPEW_BOT] = {
2019 {38, 40, -1},
2020 {37, -1, -1},
2021 {43, 57, -1},
2022 {26, 27, 58},
2023 {59, 58, 54},
2024 {60, 61, 54},
2025
2026 {69, 29, 24},
2027 {72, 60, 73}
2028 };
2029
2030 int Max_spew_bots[NUM_D2_BOSSES] = {2, 1, 2, 3, 3, 3, 3, 3};
2031
2032 // ----------------------------------------------------------------------------------------------------------
2033 // objp points at a boss. He was presumably just hit and he's supposed to create a bot at the hit location *pos.
boss_spew_robot(object * objp,vms_vector * pos)2034 int boss_spew_robot(object *objp, vms_vector *pos)
2035 {
2036 int objnum, segnum;
2037 int boss_index;
2038
2039 boss_index = Robot_info[objp->id].boss_flag - BOSS_D2;
2040
2041 Assert((boss_index >= 0) && (boss_index < NUM_D2_BOSSES));
2042
2043 segnum = find_point_seg(pos, objp->segnum);
2044 if (segnum == -1) {
2045 mprintf((0, "Tried to spew a bot outside the mine! Aborting!\n"));
2046 return -1;
2047 }
2048
2049 objnum = create_gated_robot( segnum, Spew_bots[boss_index][(Max_spew_bots[boss_index] * d_rand()) >> 15], pos);
2050
2051 // Make spewed robot come tumbling out as if blasted by a flash missile.
2052 if (objnum != -1) {
2053 object *newobjp = &Objects[objnum];
2054 int force_val;
2055
2056 force_val = F1_0/FrameTime;
2057
2058 if (force_val) {
2059 newobjp->ctype.ai_info.SKIP_AI_COUNT += force_val;
2060 newobjp->mtype.phys_info.rotthrust.x = ((d_rand() - 16384) * force_val)/16;
2061 newobjp->mtype.phys_info.rotthrust.y = ((d_rand() - 16384) * force_val)/16;
2062 newobjp->mtype.phys_info.rotthrust.z = ((d_rand() - 16384) * force_val)/16;
2063 newobjp->mtype.phys_info.flags |= PF_USES_THRUST;
2064
2065 // Now, give a big initial velocity to get moving away from boss.
2066 vm_vec_sub(&newobjp->mtype.phys_info.velocity, pos, &objp->pos);
2067 vm_vec_normalize_quick(&newobjp->mtype.phys_info.velocity);
2068 vm_vec_scale(&newobjp->mtype.phys_info.velocity, F1_0*128);
2069 }
2070 }
2071
2072 return objnum;
2073 }
2074
2075 // --------------------------------------------------------------------------------------------------------------------
2076 // Call this each time the player starts a new ship.
init_ai_for_ship(void)2077 void init_ai_for_ship(void)
2078 {
2079 int i;
2080
2081 for (i=0; i<MAX_AI_CLOAK_INFO; i++) {
2082 Ai_cloak_info[i].last_time = GameTime;
2083 Ai_cloak_info[i].last_segment = ConsoleObject->segnum;
2084 Ai_cloak_info[i].last_position = ConsoleObject->pos;
2085 }
2086 }
2087
2088 // --------------------------------------------------------------------------------------------------------------------
2089 // Make object objp gate in a robot.
2090 // The process of him bringing in a robot takes one second.
2091 // Then a robot appears somewhere near the player.
2092 // Return objnum if robot successfully created, else return -1
gate_in_robot(int type,int segnum)2093 int gate_in_robot(int type, int segnum)
2094 {
2095 if (segnum < 0)
2096 segnum = Boss_gate_segs[(d_rand() * Num_boss_gate_segs) >> 15];
2097
2098 Assert((segnum >= 0) && (segnum <= Highest_segment_index));
2099
2100 return create_gated_robot(segnum, type, NULL);
2101 }
2102
2103 // --------------------------------------------------------------------------------------------------------------------
boss_fits_in_seg(object * boss_objp,int segnum)2104 int boss_fits_in_seg(object *boss_objp, int segnum)
2105 {
2106 vms_vector segcenter;
2107 int boss_objnum = boss_objp-Objects;
2108 int posnum;
2109
2110 compute_segment_center(&segcenter, &Segments[segnum]);
2111
2112 for (posnum=0; posnum<9; posnum++) {
2113 if (posnum > 0) {
2114 vms_vector vertex_pos;
2115
2116 Assert((posnum-1 >= 0) && (posnum-1 < 8));
2117 vertex_pos = Vertices[Segments[segnum].verts[posnum-1]];
2118 vm_vec_avg(&boss_objp->pos, &vertex_pos, &segcenter);
2119 } else
2120 boss_objp->pos = segcenter;
2121
2122 obj_relink(boss_objnum, segnum);
2123 if (!object_intersects_wall(boss_objp))
2124 return 1;
2125 }
2126
2127 return 0;
2128 }
2129
2130 // --------------------------------------------------------------------------------------------------------------------
teleport_boss(object * objp)2131 void teleport_boss(object *objp)
2132 {
2133 int rand_segnum, rand_index;
2134 vms_vector boss_dir;
2135 Assert(Num_boss_teleport_segs > 0);
2136
2137 // Pick a random segment from the list of boss-teleportable-to segments.
2138 rand_index = (d_rand() * Num_boss_teleport_segs) >> 15;
2139 rand_segnum = Boss_teleport_segs[rand_index];
2140 Assert((rand_segnum >= 0) && (rand_segnum <= Highest_segment_index));
2141
2142 //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));
2143
2144 #ifdef NETWORK
2145 if (Game_mode & GM_MULTI)
2146 multi_send_boss_actions(objp-Objects, 1, rand_segnum, 0);
2147 #endif
2148
2149 compute_segment_center(&objp->pos, &Segments[rand_segnum]);
2150 obj_relink(objp-Objects, rand_segnum);
2151
2152 Last_teleport_time = GameTime;
2153
2154 // make boss point right at player
2155 vm_vec_sub(&boss_dir, &Objects[Players[Player_num].objnum].pos, &objp->pos);
2156 vm_vector_2_matrix(&objp->orient, &boss_dir, NULL, NULL);
2157
2158 digi_link_sound_to_pos( Vclip[VCLIP_MORPHING_ROBOT].sound_num, rand_segnum, 0, &objp->pos, 0 , F1_0);
2159 digi_kill_sound_linked_to_object( objp-Objects);
2160 digi_link_sound_to_object2( Robot_info[objp->id].see_sound, objp-Objects, 1, F1_0, F1_0*512 ); // F1_0*512 means play twice as loud
2161
2162 // After a teleport, boss can fire right away.
2163 Ai_local_info[objp-Objects].next_fire = 0;
2164 Ai_local_info[objp-Objects].next_fire2 = 0;
2165
2166 }
2167
2168 // ----------------------------------------------------------------------
start_boss_death_sequence(object * objp)2169 void start_boss_death_sequence(object *objp)
2170 {
2171 if (Robot_info[objp->id].boss_flag) {
2172 Boss_dying = 1;
2173 Boss_dying_start_time = GameTime;
2174 }
2175
2176 }
2177
2178 // ----------------------------------------------------------------------
2179 // General purpose robot-dies-with-death-roll-and-groan code.
2180 // Return true if object just died.
2181 // scale: F1_0*4 for boss, much smaller for much smaller guys
do_robot_dying_frame(object * objp,fix start_time,fix roll_duration,byte * dying_sound_playing,int death_sound,fix expl_scale,fix sound_scale)2182 int do_robot_dying_frame(object *objp, fix start_time, fix roll_duration, byte *dying_sound_playing, int death_sound, fix expl_scale, fix sound_scale)
2183 {
2184 fix roll_val, temp;
2185 fix sound_duration;
2186
2187 if (!roll_duration)
2188 roll_duration = F1_0/4;
2189
2190 roll_val = fixdiv(GameTime - start_time, roll_duration);
2191
2192 fix_sincos(fixmul(roll_val, roll_val), &temp, &objp->mtype.phys_info.rotvel.x);
2193 fix_sincos(roll_val, &temp, &objp->mtype.phys_info.rotvel.y);
2194 fix_sincos(roll_val-F1_0/8, &temp, &objp->mtype.phys_info.rotvel.z);
2195
2196 objp->mtype.phys_info.rotvel.x = (GameTime - start_time)/9;
2197 objp->mtype.phys_info.rotvel.y = (GameTime - start_time)/5;
2198 objp->mtype.phys_info.rotvel.z = (GameTime - start_time)/7;
2199
2200 if (digi_sample_rate)
2201 sound_duration = fixdiv(GameSounds[digi_xlat_sound(death_sound)].length,digi_sample_rate);
2202 else
2203 sound_duration = F1_0;
2204
2205 if (start_time + roll_duration - sound_duration < GameTime) {
2206 if (!*dying_sound_playing) {
2207 mprintf((0, "Starting death sound!\n"));
2208 *dying_sound_playing = 1;
2209 digi_link_sound_to_object2( death_sound, objp-Objects, 0, sound_scale, sound_scale*256 ); // F1_0*512 means play twice as loud
2210 } else if (d_rand() < FrameTime*16)
2211 create_small_fireball_on_object(objp, (F1_0 + d_rand()) * (16 * expl_scale/F1_0)/8, 0);
2212 } else if (d_rand() < FrameTime*8)
2213 create_small_fireball_on_object(objp, (F1_0/2 + d_rand()) * (16 * expl_scale/F1_0)/8, 1);
2214
2215 if (start_time + roll_duration < GameTime)
2216 return 1;
2217 else
2218 return 0;
2219 }
2220
2221 // ----------------------------------------------------------------------
start_robot_death_sequence(object * objp)2222 void start_robot_death_sequence(object *objp)
2223 {
2224 objp->ctype.ai_info.dying_start_time = GameTime;
2225 objp->ctype.ai_info.dying_sound_playing = 0;
2226 objp->ctype.ai_info.SKIP_AI_COUNT = 0;
2227
2228 }
2229
2230 // ----------------------------------------------------------------------
do_boss_dying_frame(object * objp)2231 void do_boss_dying_frame(object *objp)
2232 {
2233 int rval;
2234
2235 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);
2236
2237 if (rval) {
2238 do_controlcen_destroyed_stuff(NULL);
2239 explode_object(objp, F1_0/4);
2240 digi_link_sound_to_object2(SOUND_BADASS_EXPLOSION, objp-Objects, 0, F2_0, F1_0*512);
2241 }
2242 }
2243
2244 extern void recreate_thief(object *objp);
2245
2246 // ----------------------------------------------------------------------
do_any_robot_dying_frame(object * objp)2247 int do_any_robot_dying_frame(object *objp)
2248 {
2249 if (objp->ctype.ai_info.dying_start_time) {
2250 int rval, death_roll;
2251
2252 death_roll = Robot_info[objp->id].death_roll;
2253 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);
2254
2255 if (rval) {
2256 explode_object(objp, F1_0/4);
2257 digi_link_sound_to_object2(SOUND_BADASS_EXPLOSION, objp-Objects, 0, F2_0, F1_0*512);
2258 if ((Current_level_num < 0) && (Robot_info[objp->id].thief))
2259 recreate_thief(objp);
2260 }
2261
2262 return 1;
2263 }
2264
2265 return 0;
2266 }
2267
2268 // --------------------------------------------------------------------------------------------------------------------
2269 // Called for an AI object if it is fairly aware of the player.
2270 // awareness_level is in 0..100. Larger numbers indicate greater awareness (eg, 99 if firing at player).
2271 // In a given frame, might not get called for an object, or might be called more than once.
2272 // The fact that this routine is not called for a given object does not mean that object is not interested in the player.
2273 // Objects are moved by physics, so they can move even if not interested in a player. However, if their velocity or
2274 // orientation is changing, this routine will be called.
2275 // Return value:
2276 // 0 this player IS NOT allowed to move this robot.
2277 // 1 this player IS allowed to move this robot.
ai_multiplayer_awareness(object * objp,int awareness_level)2278 int ai_multiplayer_awareness(object *objp, int awareness_level)
2279 {
2280 int rval=1;
2281
2282 #ifdef NETWORK
2283 if (Game_mode & GM_MULTI) {
2284 if (awareness_level == 0)
2285 return 0;
2286 rval = multi_can_move_robot(objp-Objects, awareness_level);
2287 }
2288 #endif
2289
2290 return rval;
2291
2292 }
2293
2294 #ifndef NDEBUG
2295 fix Prev_boss_shields = -1;
2296 #endif
2297
2298 // --------------------------------------------------------------------------------------------------------------------
2299 // Do special stuff for a boss.
do_boss_stuff(object * objp,int player_visibility)2300 void do_boss_stuff(object *objp, int player_visibility)
2301 {
2302 int boss_id, boss_index;
2303
2304 boss_id = Robot_info[objp->id].boss_flag;
2305
2306 Assert((boss_id >= BOSS_D2) && (boss_id < BOSS_D2 + NUM_D2_BOSSES));
2307
2308 boss_index = boss_id - BOSS_D2;
2309
2310 #ifndef NDEBUG
2311 if (objp->shields != Prev_boss_shields) {
2312 mprintf((0, "Boss shields = %7.3f, object %i\n", f2fl(objp->shields), objp-Objects));
2313 Prev_boss_shields = objp->shields;
2314 }
2315 #endif
2316
2317 // New code, fixes stupid bug which meant boss never gated in robots if > 32767 seconds played.
2318 if (Last_teleport_time > GameTime)
2319 Last_teleport_time = GameTime;
2320
2321 if (Last_gate_time > GameTime)
2322 Last_gate_time = GameTime;
2323
2324 // @mk, 10/13/95: Reason:
2325 // Level 4 boss behind locked door. But he's allowed to teleport out of there. So he
2326 // teleports out of there right away, and blasts player right after first door.
2327 if (!player_visibility && (GameTime - Boss_hit_time > F1_0*2))
2328 return;
2329
2330 if (!Boss_dying && Boss_teleports[boss_index]) {
2331 if (objp->ctype.ai_info.CLOAKED == 1) {
2332 Boss_hit_time = GameTime; // Keep the cloak:teleport process going.
2333 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)) {
2334 if (ai_multiplayer_awareness(objp, 98))
2335 teleport_boss(objp);
2336 } else if (GameTime - Boss_hit_time > F1_0*2) {
2337 Last_teleport_time -= Boss_teleport_interval/4;
2338 }
2339
2340 if (GameTime > Boss_cloak_end_time || GameTime < Boss_cloak_start_time)
2341 objp->ctype.ai_info.CLOAKED = 0;
2342 } else if ((GameTime - Boss_cloak_end_time > Boss_cloak_interval) || (GameTime - Boss_cloak_end_time < -Boss_cloak_duration)) {
2343 if (ai_multiplayer_awareness(objp, 95)) {
2344 Boss_cloak_start_time = GameTime;
2345 Boss_cloak_end_time = GameTime+Boss_cloak_duration;
2346 objp->ctype.ai_info.CLOAKED = 1;
2347 #ifdef NETWORK
2348 if (Game_mode & GM_MULTI)
2349 multi_send_boss_actions(objp-Objects, 2, 0, 0);
2350 #endif
2351 }
2352 }
2353 }
2354
2355 }
2356
2357 #define BOSS_TO_PLAYER_GATE_DISTANCE (F1_0*200)
2358
2359 // -- Obsolete D1 code -- // --------------------------------------------------------------------------------------------------------------------
2360 // -- Obsolete D1 code -- // Do special stuff for a boss.
2361 // -- Obsolete D1 code -- void do_super_boss_stuff(object *objp, fix dist_to_player, int player_visibility)
2362 // -- Obsolete D1 code -- {
2363 // -- Obsolete D1 code -- static int eclip_state = 0;
2364 // -- Obsolete D1 code --
2365 // -- Obsolete D1 code -- do_boss_stuff(objp, player_visibility);
2366 // -- Obsolete D1 code --
2367 // -- Obsolete D1 code -- // Only master player can cause gating to occur.
2368 // -- Obsolete D1 code -- if ((Game_mode & GM_MULTI) && !network_i_am_master())
2369 // -- Obsolete D1 code -- return;
2370 // -- Obsolete D1 code --
2371 // -- Obsolete D1 code -- if ((dist_to_player < BOSS_TO_PLAYER_GATE_DISTANCE) || player_visibility || (Game_mode & GM_MULTI)) {
2372 // -- Obsolete D1 code -- if (GameTime - Last_gate_time > Gate_interval/2) {
2373 // -- Obsolete D1 code -- restart_effect(BOSS_ECLIP_NUM);
2374 // -- Obsolete D1 code -- if (eclip_state == 0) {
2375 // -- Obsolete D1 code -- multi_send_boss_actions(objp-Objects, 4, 0, 0);
2376 // -- Obsolete D1 code -- eclip_state = 1;
2377 // -- Obsolete D1 code -- }
2378 // -- Obsolete D1 code -- }
2379 // -- Obsolete D1 code -- else {
2380 // -- Obsolete D1 code -- stop_effect(BOSS_ECLIP_NUM);
2381 // -- Obsolete D1 code -- if (eclip_state == 1) {
2382 // -- Obsolete D1 code -- multi_send_boss_actions(objp-Objects, 5, 0, 0);
2383 // -- Obsolete D1 code -- eclip_state = 0;
2384 // -- Obsolete D1 code -- }
2385 // -- Obsolete D1 code -- }
2386 // -- Obsolete D1 code --
2387 // -- Obsolete D1 code -- if (GameTime - Last_gate_time > Gate_interval)
2388 // -- Obsolete D1 code -- if (ai_multiplayer_awareness(objp, 99)) {
2389 // -- Obsolete D1 code -- int rtval;
2390 // -- Obsolete D1 code -- int randtype = (d_rand() * MAX_GATE_INDEX) >> 15;
2391 // -- Obsolete D1 code --
2392 // -- Obsolete D1 code -- Assert(randtype < MAX_GATE_INDEX);
2393 // -- Obsolete D1 code -- randtype = Super_boss_gate_list[randtype];
2394 // -- Obsolete D1 code -- Assert(randtype < N_robot_types);
2395 // -- Obsolete D1 code --
2396 // -- Obsolete D1 code -- rtval = gate_in_robot(randtype, -1);
2397 // -- Obsolete D1 code -- if ((rtval != -1) && (Game_mode & GM_MULTI))
2398 // -- Obsolete D1 code -- {
2399 // -- Obsolete D1 code -- multi_send_boss_actions(objp-Objects, 3, randtype, Net_create_objnums[0]);
2400 // -- Obsolete D1 code -- map_objnum_local_to_local(Net_create_objnums[0]);
2401 // -- Obsolete D1 code -- }
2402 // -- Obsolete D1 code -- }
2403 // -- Obsolete D1 code -- }
2404 // -- Obsolete D1 code -- }
2405
2406 //int multi_can_move_robot(object *objp, int awareness_level)
2407 //{
2408 // return 0;
2409 //}
2410
ai_multi_send_robot_position(int objnum,int force)2411 void ai_multi_send_robot_position(int objnum, int force)
2412 {
2413 #ifdef NETWORK
2414 if (Game_mode & GM_MULTI)
2415 {
2416 if (force != -1)
2417 multi_send_robot_position(objnum, 1);
2418 else
2419 multi_send_robot_position(objnum, 0);
2420 }
2421 #endif
2422 return;
2423 }
2424
2425 // --------------------------------------------------------------------------------------------------------------------
2426 // Returns true if this object should be allowed to fire at the player.
maybe_ai_do_actual_firing_stuff(object * obj,ai_static * aip)2427 int maybe_ai_do_actual_firing_stuff(object *obj, ai_static *aip)
2428 {
2429 #ifdef NETWORK
2430 if (Game_mode & GM_MULTI)
2431 if ((aip->GOAL_STATE != AIS_FLIN) && (obj->id != ROBOT_BRAIN))
2432 if (aip->CURRENT_STATE == AIS_FIRE)
2433 return 1;
2434 #endif
2435
2436 return 0;
2437 }
2438
2439 vms_vector Last_fired_upon_player_pos;
2440
2441 // --------------------------------------------------------------------------------------------------------------------
2442 // If fire_anyway, fire even if player is not visible. We're firing near where we believe him to be. Perhaps he's
2443 // lurking behind a corner.
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)2444 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)
2445 {
2446 fix dot;
2447
2448 if ((player_visibility == 2) || (Dist_to_last_fired_upon_player_pos < FIRE_AT_NEARBY_PLAYER_THRESHOLD )) {
2449 vms_vector fire_pos;
2450
2451 fire_pos = Believed_player_pos;
2452
2453 // Hack: If visibility not == 2, we're here because we're firing at a nearby player.
2454 // So, fire at Last_fired_upon_player_pos instead of the player position.
2455 if (!robptr->attack_type && (player_visibility != 2))
2456 fire_pos = Last_fired_upon_player_pos;
2457
2458 // Changed by mk, 01/04/95, onearm would take about 9 seconds until he can fire at you.
2459 // 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.
2460 if (!object_animates || ready_to_fire(robptr, ailp)) {
2461 dot = vm_vec_dot(&obj->orient.fvec, vec_to_player);
2462 if ((dot >= 7*F1_0/8) || ((dot > F1_0/4) && robptr->boss_flag)) {
2463
2464 if (gun_num < Robot_info[obj->id].n_guns) {
2465 if (robptr->attack_type == 1) {
2466 if (!Player_exploded && (dist_to_player < obj->size + ConsoleObject->size + F1_0*2)) { // robptr->circle_distance[Difficulty_level] + ConsoleObject->size) {
2467 if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION-2))
2468 return;
2469 do_ai_robot_hit_attack(obj, ConsoleObject, &obj->pos);
2470 } else {
2471 // 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)));
2472 return;
2473 }
2474 } else {
2475 if ((gun_point->x == 0) && (gun_point->y == 0) && (gun_point->z == 0)) {
2476 ; //mprintf((0, "Would like to fire gun, but gun not selected.\n"));
2477 } else {
2478 if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION))
2479 return;
2480 // New, multi-weapon-type system, 06/05/95 (life is slipping away...)
2481 if (gun_num != 0) {
2482 if (ailp->next_fire <= 0) {
2483 ai_fire_laser_at_player(obj, gun_point, gun_num, &fire_pos);
2484 Last_fired_upon_player_pos = fire_pos;
2485 }
2486
2487 if ((ailp->next_fire2 <= 0) && (robptr->weapon_type2 != -1)) {
2488 calc_gun_point(gun_point, obj, 0);
2489 ai_fire_laser_at_player(obj, gun_point, 0, &fire_pos);
2490 Last_fired_upon_player_pos = fire_pos;
2491 }
2492
2493 } else if (ailp->next_fire <= 0) {
2494 ai_fire_laser_at_player(obj, gun_point, gun_num, &fire_pos);
2495 Last_fired_upon_player_pos = fire_pos;
2496 }
2497 }
2498 }
2499
2500 // Wants to fire, so should go into chase mode, probably.
2501 if ( (aip->behavior != AIB_RUN_FROM)
2502 && (aip->behavior != AIB_STILL)
2503 && (aip->behavior != AIB_SNIPE)
2504 && (aip->behavior != AIB_FOLLOW)
2505 && (!robptr->attack_type)
2506 && ((ailp->mode == AIM_FOLLOW_PATH) || (ailp->mode == AIM_STILL)))
2507 ailp->mode = AIM_CHASE_OBJECT;
2508 }
2509
2510 aip->GOAL_STATE = AIS_RECO;
2511 ailp->goal_state[aip->CURRENT_GUN] = AIS_RECO;
2512
2513 // Switch to next gun for next fire. If has 2 gun types, select gun #1, if exists.
2514 aip->CURRENT_GUN++;
2515 if (aip->CURRENT_GUN >= Robot_info[obj->id].n_guns)
2516 {
2517 if ((Robot_info[obj->id].n_guns == 1) || (Robot_info[obj->id].weapon_type2 == -1))
2518 aip->CURRENT_GUN = 0;
2519 else
2520 aip->CURRENT_GUN = 1;
2521 }
2522 }
2523 }
2524 } 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))) ) {
2525 // Robots which fire homing weapons might fire even if they don't have a bead on the player.
2526 if (((!object_animates) || (ailp->achieved_state[aip->CURRENT_GUN] == AIS_FIRE))
2527 && (((ailp->next_fire <= 0) && (aip->CURRENT_GUN != 0)) || ((ailp->next_fire2 <= 0) && (aip->CURRENT_GUN == 0)))
2528 && (vm_vec_dist_quick(&Hit_pos, &obj->pos) > F1_0*40)) {
2529 if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION))
2530 return;
2531 ai_fire_laser_at_player(obj, gun_point, gun_num, &Believed_player_pos);
2532
2533 aip->GOAL_STATE = AIS_RECO;
2534 ailp->goal_state[aip->CURRENT_GUN] = AIS_RECO;
2535
2536 // Switch to next gun for next fire.
2537 aip->CURRENT_GUN++;
2538 if (aip->CURRENT_GUN >= Robot_info[obj->id].n_guns)
2539 aip->CURRENT_GUN = 0;
2540 } else {
2541 // Switch to next gun for next fire.
2542 aip->CURRENT_GUN++;
2543 if (aip->CURRENT_GUN >= Robot_info[obj->id].n_guns)
2544 aip->CURRENT_GUN = 0;
2545 }
2546 } else {
2547
2548
2549 // ---------------------------------------------------------------
2550
2551 vms_vector vec_to_last_pos;
2552
2553 if (d_rand()/2 < fixmul(FrameTime, (Difficulty_level << 12) + 0x4000)) {
2554 if ((!object_animates || ready_to_fire(robptr, ailp)) && (Dist_to_last_fired_upon_player_pos < FIRE_AT_NEARBY_PLAYER_THRESHOLD)) {
2555 vm_vec_normalized_dir_quick(&vec_to_last_pos, &Believed_player_pos, &obj->pos);
2556 dot = vm_vec_dot(&obj->orient.fvec, &vec_to_last_pos);
2557 if (dot >= 7*F1_0/8) {
2558
2559 if (aip->CURRENT_GUN < Robot_info[obj->id].n_guns) {
2560 if (robptr->attack_type == 1) {
2561 if (!Player_exploded && (dist_to_player < obj->size + ConsoleObject->size + F1_0*2)) { // robptr->circle_distance[Difficulty_level] + ConsoleObject->size) {
2562 if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION-2))
2563 return;
2564 do_ai_robot_hit_attack(obj, ConsoleObject, &obj->pos);
2565 } else {
2566 // 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)));
2567 return;
2568 }
2569 } else {
2570 if ((gun_point->x == 0) && (gun_point->y == 0) && (gun_point->z == 0)) {
2571 ; //mprintf((0, "Would like to fire gun, but gun not selected.\n"));
2572 } else {
2573 if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION))
2574 return;
2575 // New, multi-weapon-type system, 06/05/95 (life is slipping away...)
2576 if (gun_num != 0) {
2577 if (ailp->next_fire <= 0)
2578 ai_fire_laser_at_player(obj, gun_point, gun_num, &Last_fired_upon_player_pos);
2579
2580 if ((ailp->next_fire2 <= 0) && (robptr->weapon_type2 != -1)) {
2581 calc_gun_point(gun_point, obj, 0);
2582 ai_fire_laser_at_player(obj, gun_point, 0, &Last_fired_upon_player_pos);
2583 }
2584
2585 } else if (ailp->next_fire <= 0)
2586 ai_fire_laser_at_player(obj, gun_point, gun_num, &Last_fired_upon_player_pos);
2587 }
2588 }
2589
2590 // Wants to fire, so should go into chase mode, probably.
2591 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)))
2592 ailp->mode = AIM_CHASE_OBJECT;
2593 }
2594 aip->GOAL_STATE = AIS_RECO;
2595 ailp->goal_state[aip->CURRENT_GUN] = AIS_RECO;
2596
2597 // Switch to next gun for next fire.
2598 aip->CURRENT_GUN++;
2599 if (aip->CURRENT_GUN >= Robot_info[obj->id].n_guns)
2600 {
2601 if (Robot_info[obj->id].n_guns == 1)
2602 aip->CURRENT_GUN = 0;
2603 else
2604 aip->CURRENT_GUN = 1;
2605 }
2606 }
2607 }
2608 }
2609
2610
2611 // ---------------------------------------------------------------
2612
2613
2614 }
2615
2616 }
2617
2618
2619