This commit was manufactured by cvs2svn to create branch
[btb/d2x.git] / main / ai.c
1 /* $Id: ai.c,v 1.5 2003-03-14 21:24:03 btb Exp $ */
2 /*
3 THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
4 SOFTWARE CORPORATION ("PARALLAX").  PARALLAX, IN DISTRIBUTING THE CODE TO
5 END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
6 ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
7 IN USING, DISPLAYING,  AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
8 SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
9 FREE PURPOSES.  IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
10 CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES.  THE END-USER UNDERSTANDS
11 AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.
12 COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION.  ALL RIGHTS RESERVED.
13 */
14
15 /*
16  *
17  * Autonomous Individual movement.
18  *
19  * Old Log:
20  * Revision 1.1  1995/12/05  14:15:37  allender
21  * Initial revision
22  *
23  * Revision 1.10  1995/11/09  09:36:12  allender
24  * cheats not active during demo playback
25  *
26  * Revision 1.9  1995/11/03  12:51:55  allender
27  * shareware changes
28  *
29  * Revision 1.8  1995/10/31  10:25:07  allender
30  * shareware stuff
31  *
32  * Revision 1.7  1995/10/26  14:01:38  allender
33  * optimization for doing robot stuff only if anim angles done last frame
34  *
35  * Revision 1.6  1995/10/25  09:35:43  allender
36  * prototype some functions causing mcc problems
37  *
38  * Revision 1.5  1995/10/17  13:11:40  allender
39  * fix in ai code that makes bots only look for you every so often
40  *
41  * Revision 1.4  1995/10/10  11:48:10  allender
42  * PC ai code
43  *
44  * Revision 2.11  1995/07/09  11:15:48  john
45  * Put in Mike's code to fix bug where bosses don't gate in bots after
46  * 32767 seconds of playing.
47  *
48  * Revision 2.10  1995/06/15  12:31:08  john
49  * Fixed bug with cheats getting enabled when you type
50  * the whole alphabet.
51  *
52  * Revision 2.9  1995/05/26  16:16:18  john
53  * Split SATURN into define's for requiring cd, using cd, etc.
54  * Also started adding all the Rockwell stuff.
55  *
56  * Revision 2.8  1995/04/06  15:12:27  john
57  * Fixed bug with insane not working.
58  *
59  * Revision 2.7  1995/03/30  16:36:44  mike
60  * text localization.
61  *
62  * Revision 2.6  1995/03/28  11:22:24  john
63  * Added cheats to save file. Changed lunacy text.
64  *
65  * Revision 2.5  1995/03/27  16:45:07  john
66  * Fixed some cheat bugs.  Added astral cheat.
67  *
68  * Revision 2.4  1995/03/24  15:29:17  mike
69  * add new cheats.
70  *
71  * Revision 2.3  1995/03/21  14:39:45  john
72  * Ifdef'd out the NETWORK code.
73  *
74  * Revision 2.2  1995/03/14  18:24:39  john
75  * Force Destination Saturn to use CD-ROM drive.
76  *
77  * Revision 2.1  1995/03/06  16:47:14  mike
78  * destination saturn
79  *
80  * Revision 2.0  1995/02/27  11:30:01  john
81  * New version 2.0, which has no anonymous unions, builds with
82  * Watcom 10.0, and doesn't require parsing BITMAPS.TBL.
83  *
84  * Revision 1.295  1995/02/22  13:23:04  allender
85  * remove anonymous unions from object structure
86  *
87  * Revision 1.294  1995/02/13  11:00:43  rob
88  * Make brain guys high enough to get an open slot.
89  *
90  * Revision 1.293  1995/02/13  10:31:55  mike
91  * Make brains understand they can't open locked doors.
92  *
93  * Revision 1.292  1995/02/13  10:18:01  rob
94  * Reduced brain guy's level of awareness to keep him from hogging slots.
95  *
96  * Revision 1.291  1995/02/11  12:27:12  mike
97  * fix path-to-exit cheat.
98  *
99  * Revision 1.290  1995/02/11  01:56:30  mike
100  * robots don't fire cheat.
101  *
102  * Revision 1.289  1995/02/10  17:15:09  rob
103  * Fixed some stuff with 64 awareness stuff.
104  *
105  * Revision 1.288  1995/02/10  16:31:32  mike
106  * oops.
107  *
108  * Revision 1.287  1995/02/10  16:24:45  mike
109  * fix the network follow path fix.
110  *
111  * Revision 1.286  1995/02/10  16:11:40  mike
112  * in serial or modem games, follow path guys don't move if far away and
113  * can't see player.
114  *
115  * Revision 1.285  1995/02/09  13:11:35  mike
116  * comment out a bunch of mprintfs.
117  * add toaster (drops prox bombs, runs away) to boss gate list.
118  *
119  * Revision 1.284  1995/02/08  22:44:53  rob
120  * Lowerd anger level for follow path of any sort.
121  *
122  * Revision 1.283  1995/02/08  22:30:43  mike
123  * lower awareness on station guys if they are returning home (multiplayer).
124  *
125  * Revision 1.282  1995/02/08  17:01:06  rob
126  * Fixed problem with toasters dropping of proximity bombs.
127  *
128  * Revision 1.281  1995/02/08  11:49:35  rob
129  * Reduce Green-guy attack awareness level so we don't let him attack us too.
130  *
131  * Revision 1.280  1995/02/08  11:37:52  mike
132  * Check for failures in call to obj_create.
133  *
134  * Revision 1.279  1995/02/07  20:38:46  mike
135  * fix toasters in multiplayer
136  *
137  *
138  * Revision 1.278  1995/02/07  16:51:07  mike
139  * fix sound time play bug.
140  *
141  * Revision 1.277  1995/02/06  22:33:04  mike
142  * make robots follow path better in cooperative/roboarchy.
143  *
144  * Revision 1.276  1995/02/06  18:15:42  rob
145  * Added forced sends for evasion movemnet.
146  *
147  * Revision 1.275  1995/02/06  16:41:22  rob
148  * Change some positioning calls.
149  *
150  * Revision 1.274  1995/02/06  11:40:33  mike
151  * replace some lint-related hacks with clean, proper code.
152  *
153  * Revision 1.273  1995/02/04  17:28:19  mike
154  * make station guys return better.
155  *
156  * Revision 1.272  1995/02/03  17:40:55  mike
157  * fix problem with robots falling asleep if you sit in game overnight, not in pause...bah.
158  *
159  * Revision 1.271  1995/02/02  21:11:25  rob
160  * Tweaking stuff for multiplayer ai.
161  *
162  * Revision 1.270  1995/02/02  17:32:06  john
163  * Added Hack for Assert that Mike put in after using Lint to find
164  * uninitialized variables.
165  *
166  * Revision 1.269  1995/02/02  16:46:31  mike
167  * fix boss gating.
168  *
169  * Revision 1.268  1995/02/02  16:27:29  mike
170  * make boss not put out infinite robots.
171  *
172  * Revision 1.267  1995/02/01  21:10:02  mike
173  * lint found bug! player_visibility not initialized!
174  *
175  * Revision 1.266  1995/02/01  20:51:27  john
176  * Lintized
177  *
178  * Revision 1.265  1995/02/01  17:14:05  mike
179  * fix robot sounds.
180  *
181  * Revision 1.264  1995/01/31  16:16:40  mike
182  * Comment out "Darn you, John" Int3().
183  *
184  * Revision 1.263  1995/01/30  20:55:04  mike
185  * fix nonsense in robot firing when a player is cloaked.
186  *
187  * Revision 1.262  1995/01/30  17:15:10  rob
188  * Fixed problems with bigboss eclip messages.
189  * Tweaked robot position sending for modem purposes.
190  *
191  * Revision 1.261  1995/01/30  15:30:31  rob
192  * Prevent non-master players from gating in robots.
193  *
194  * Revision 1.260  1995/01/30  13:30:55  mike
195  * new cases for firing at other players were bogus, could send position
196  * without permission.
197  *
198  * Revision 1.259  1995/01/30  13:01:17  mike
199  * Make robots fire at player other than one they are controlled by sometimes.
200  *
201  * Revision 1.258  1995/01/29  16:09:17  rob
202  * Trying to get robots to shoot at non-controlling players.
203  *
204  * Revision 1.257  1995/01/29  13:47:05  mike
205  * Make boss have more fireballs on death, have until end (though silent at end).
206  * Fix bug which was preventing him from teleporting until hit, so he'd always
207  * be in the same place when the player enters the room.
208  *
209  * Revision 1.256  1995/01/28  17:40:18  mike
210  * make boss teleport & gate before you see him.
211  *
212  * Revision 1.255  1995/01/27  17:02:08  mike
213  * move code around, was sending one frame (or worse!) old robot information.
214  *
215  * Revision 1.254  1995/01/26  17:02:43  mike
216  * make fusion cannon have more chrome, make fusion, mega rock you!
217  *
218  * Revision 1.253  1995/01/26  15:11:17  rob
219  * Shutup!  I fixed it!
220  *
221  * Revision 1.252  1995/01/26  15:08:55  rob
222  * Changed robot gating to accomodate multiplayer.
223  *
224  * Revision 1.251  1995/01/26  14:49:04  rob
225  * Increase awareness level for firing to 94.
226  *
227  * Revision 1.250  1995/01/26  12:41:20  mike
228  * fix bogus multiplayer code, would send permission without getting permission.
229  *
230  * Revision 1.249  1995/01/26  12:23:23  rob
231  * Removed defines that were moved to ai.h
232  *
233  * Revision 1.248  1995/01/25  23:38:48  mike
234  * modify list of robots gated in by super boss.
235  *
236  * Revision 1.247  1995/01/25  21:21:13  rob
237  * Trying to let robots fire at a player even if they're not in control.
238  *
239  * Revision 1.246  1995/01/25  13:50:37  mike
240  * Robots make angry sounds.
241  *
242  * Revision 1.245  1995/01/25  10:53:47  mike
243  * better handling of robots which poke out of mine and try to recover.
244  *
245  * Revision 1.244  1995/01/24  22:03:02  mike
246  * Tricky code to move a robot to a legal position if he is poking out of
247  * the mine, even if it means moving him to another segment.
248  *
249  * Revision 1.243  1995/01/24  20:12:06  rob
250  * Changed robot fire awareness level from 74 to 94.
251  *
252  * Revision 1.242  1995/01/24  13:22:32  mike
253  * make robots accelerate faster, and Difficulty_level dependent.
254  *
255  * Revision 1.241  1995/01/24  12:09:39  mike
256  * make robots animate in multiplayer.
257  *
258  * Revision 1.240  1995/01/21  21:21:10  mike
259  * Make boss only gate robots into specified segments.
260  *
261  * Revision 1.239  1995/01/20  20:21:26  mike
262  * prevent unnecessary boss cloaking.
263  *
264  */
265
266
267 #ifdef HAVE_CONFIG_H
268 #include <conf.h>
269 #endif
270
271 char ai_rcsid[] = "$Id: ai.c,v 1.5 2003-03-14 21:24:03 btb Exp $";
272
273 #include <stdio.h>
274 #include <stdlib.h>
275 #include <time.h>
276
277 #include "inferno.h"
278 #include "game.h"
279 #include "mono.h"
280 #include "3d.h"
281
282 #include "object.h"
283 #include "render.h"
284 #include "error.h"
285 #include "ai.h"
286 #include "laser.h"
287 #include "fvi.h"
288 #include "polyobj.h"
289 #include "bm.h"
290 #include "weapon.h"
291 #include "physics.h"
292 #include "collide.h"
293 #include "player.h"
294 #include "wall.h"
295 #include "vclip.h"
296 #include "fireball.h"
297 #include "morph.h"
298 #include "effects.h"
299 #include "timer.h"
300 #include "sounds.h"
301 #include "cntrlcen.h"
302 #include "multibot.h"
303 #ifdef NETWORK
304 #include "multi.h"
305 #include "network.h"
306 #endif
307 #include "gameseq.h"
308 #include "key.h"
309 #include "powerup.h"
310 #include "gauges.h"
311 #include "text.h"
312 #include "fuelcen.h"
313 #include "controls.h"
314 #include "kconfig.h"
315
316 #ifdef EDITOR
317 #include "editor/editor.h"
318 #endif
319
320 #include "string.h"
321
322 #ifndef NDEBUG
323 #include <time.h>
324 #endif
325
326 // ---------- John: These variables must be saved as part of gamesave. --------
327 int             Ai_initialized = 0;
328 int             Overall_agitation;
329 ai_local        Ai_local_info[MAX_OBJECTS];
330 point_seg       Point_segs[MAX_POINT_SEGS];
331 point_seg       *Point_segs_free_ptr = Point_segs;
332 ai_cloak_info   Ai_cloak_info[MAX_AI_CLOAK_INFO];
333 fix             Boss_cloak_start_time = 0;
334 fix             Boss_cloak_end_time = 0;
335 fix             Last_teleport_time = 0;
336 fix             Boss_teleport_interval = F1_0*8;
337 fix             Boss_cloak_interval = F1_0*10;                    //    Time between cloaks
338 fix             Boss_cloak_duration = BOSS_CLOAK_DURATION;
339 fix             Last_gate_time = 0;
340 fix             Gate_interval = F1_0*6;
341 fix             Boss_dying_start_time;
342 fix             Boss_hit_time;
343 byte            Boss_dying, Boss_dying_sound_playing, unused123, unused234;
344
345 // -- MK, 10/21/95, unused! -- int             Boss_been_hit=0;
346
347
348 // ------ John: End of variables which must be saved as part of gamesave. -----
349
350
351 // -- ubyte Boss_cloaks[NUM_D2_BOSSES]              = {1,1,1,1,1,1};      // Set byte if this boss can cloak
352
353 ubyte Boss_teleports[NUM_D2_BOSSES]           = {1,1,1,1,1,1, 1,1}; // Set byte if this boss can teleport
354 ubyte Boss_spew_more[NUM_D2_BOSSES]           = {0,1,0,0,0,0, 0,0}; // If set, 50% of time, spew two bots.
355 ubyte Boss_spews_bots_energy[NUM_D2_BOSSES]   = {1,1,0,1,0,1, 1,1}; // Set byte if boss spews bots when hit by energy weapon.
356 ubyte Boss_spews_bots_matter[NUM_D2_BOSSES]   = {0,0,1,1,1,1, 0,1}; // Set byte if boss spews bots when hit by matter weapon.
357 ubyte Boss_invulnerable_energy[NUM_D2_BOSSES] = {0,0,1,1,0,0, 0,0}; // Set byte if boss is invulnerable to energy weapons.
358 ubyte Boss_invulnerable_matter[NUM_D2_BOSSES] = {0,0,0,0,1,1, 1,0}; // Set byte if boss is invulnerable to matter weapons.
359 ubyte Boss_invulnerable_spot[NUM_D2_BOSSES]   = {0,0,0,0,0,1, 0,1}; // Set byte if boss is invulnerable in all but a certain spot.  (Dot product fvec|vec_to_collision < BOSS_INVULNERABLE_DOT)
360
361 int ai_evaded=0;
362
363 // -- byte Super_boss_gate_list[MAX_GATE_INDEX] = {0, 1, 8, 9, 10, 11, 12, 15, 16, 18, 19, 20, 22, 0, 8, 11, 19, 20, 8, 20, 8};
364
365 int Robot_firing_enabled = 1;
366 int Animation_enabled = 1;
367
368 #ifndef NDEBUG
369 int Ai_info_enabled=0;
370 #endif
371
372
373 // These globals are set by a call to find_vector_intersection, which is a slow routine,
374 // so we don't want to call it again (for this object) unless we have to.
375 vms_vector  Hit_pos;
376 int         Hit_type, Hit_seg;
377 fvi_info    Hit_data;
378
379 int             Num_awareness_events = 0;
380 awareness_event Awareness_events[MAX_AWARENESS_EVENTS];
381
382 vms_vector      Believed_player_pos;
383 int             Believed_player_seg;
384
385 #ifndef NDEBUG
386 // Index into this array with ailp->mode
387 char *mode_text[18] = {
388         "STILL",
389         "WANDER",
390         "FOL_PATH",
391         "CHASE_OBJ",
392         "RUN_FROM",
393         "BEHIND",
394         "FOL_PATH2",
395         "OPEN_DOOR",
396         "GOTO_PLR",
397         "GOTO_OBJ",
398         "SN_ATT",
399         "SN_FIRE",
400         "SN_RETR",
401         "SN_RTBK",
402         "SN_WAIT",
403         "TH_ATTACK",
404         "TH_RETREAT",
405         "TH_WAIT",
406 };
407
408 //      Index into this array with aip->behavior
409 char behavior_text[6][9] = {
410         "STILL   ",
411         "NORMAL  ",
412         "HIDE    ",
413         "RUN_FROM",
414         "FOLPATH ",
415         "STATION "
416 };
417
418 // Index into this array with aip->GOAL_STATE or aip->CURRENT_STATE
419 char state_text[8][5] = {
420         "NONE",
421         "REST",
422         "SRCH",
423         "LOCK",
424         "FLIN",
425         "FIRE",
426         "RECO",
427         "ERR_",
428 };
429
430
431 #endif
432
433 // Current state indicates where the robot current is, or has just done.
434 // Transition table between states for an AI object.
435 // First dimension is trigger event.
436 // Second dimension is current state.
437 // Third dimension is goal state.
438 // Result is new goal state.
439 // ERR_ means something impossible has happened.
440 byte Ai_transition_table[AI_MAX_EVENT][AI_MAX_STATE][AI_MAX_STATE] = {
441         {
442                 // Event = AIE_FIRE, a nearby object fired
443                 // none     rest      srch      lock      flin      fire      reco        // CURRENT is rows, GOAL is columns
444                 { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO }, // none
445                 { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO }, // rest
446                 { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO }, // search
447                 { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO }, // lock
448                 { AIS_ERR_, AIS_REST, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FIRE, AIS_RECO }, // flinch
449                 { AIS_ERR_, AIS_FIRE, AIS_FIRE, AIS_FIRE, AIS_FLIN, AIS_FIRE, AIS_RECO }, // fire
450                 { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_FIRE }  // recoil
451         },
452
453         // Event = AIE_HITT, a nearby object was hit (or a wall was hit)
454         {
455                 { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO},
456                 { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO},
457                 { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO},
458                 { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO},
459                 { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FLIN},
460                 { AIS_ERR_, AIS_REST, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FIRE, AIS_RECO},
461                 { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_FIRE}
462         },
463
464         // Event = AIE_COLL, player collided with robot
465         {
466                 { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO},
467                 { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO},
468                 { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO},
469                 { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO},
470                 { AIS_ERR_, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_LOCK, AIS_FLIN, AIS_FLIN},
471                 { AIS_ERR_, AIS_REST, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FIRE, AIS_RECO},
472                 { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_FIRE}
473         },
474
475         // Event = AIE_HURT, player hurt robot (by firing at and hitting it)
476         // Note, this doesn't necessarily mean the robot JUST got hit, only that that is the most recent thing that happened.
477         {
478                 { AIS_ERR_, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN},
479                 { AIS_ERR_, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN},
480                 { AIS_ERR_, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN},
481                 { AIS_ERR_, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN},
482                 { AIS_ERR_, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN},
483                 { AIS_ERR_, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN},
484                 { AIS_ERR_, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN}
485         }
486 };
487
488
489
490 fix Dist_to_last_fired_upon_player_pos = 0;
491
492 // ----------------------------------------------------------------------------
493 void init_ai_frame(void)
494 {
495         int ab_state;
496
497         Dist_to_last_fired_upon_player_pos = vm_vec_dist_quick(&Last_fired_upon_player_pos, &Believed_player_pos);
498
499         ab_state = Afterburner_charge && Controls.afterburner_state && (Players[Player_num].flags & PLAYER_FLAGS_AFTERBURNER);
500
501         if (!(Players[Player_num].flags & PLAYER_FLAGS_CLOAKED) || (Players[Player_num].flags & PLAYER_FLAGS_HEADLIGHT_ON) || ab_state) {
502                 ai_do_cloak_stuff();
503         }
504 }
505
506 // ----------------------------------------------------------------------------
507 // Return firing status.
508 // If ready to fire a weapon, return true, else return false.
509 // Ready to fire a weapon if next_fire <= 0 or next_fire2 <= 0.
510 int ready_to_fire(robot_info *robptr, ai_local *ailp)
511 {
512         if (robptr->weapon_type2 != -1)
513                 return (ailp->next_fire <= 0) || (ailp->next_fire2 <= 0);
514         else
515                 return (ailp->next_fire <= 0);
516 }
517
518 // ----------------------------------------------------------------------------
519 // Make a robot near the player snipe.
520 #define MNRS_SEG_MAX    70
521 void make_nearby_robot_snipe(void)
522 {
523         int bfs_length, i;
524         short bfs_list[MNRS_SEG_MAX];
525
526         create_bfs_list(ConsoleObject->segnum, bfs_list, &bfs_length, MNRS_SEG_MAX);
527
528         for (i=0; i<bfs_length; i++) {
529                 int objnum = Segments[bfs_list[i]].objects;
530
531                 while (objnum != -1) {
532                         object *objp = &Objects[objnum];
533                         robot_info *robptr = &Robot_info[objp->id];
534
535                         if ((objp->type == OBJ_ROBOT) && (objp->id != ROBOT_BRAIN)) {
536                                 if ((objp->ctype.ai_info.behavior != AIB_SNIPE) && (objp->ctype.ai_info.behavior != AIB_RUN_FROM) && !Robot_info[objp->id].boss_flag && !robptr->companion) {
537                                         objp->ctype.ai_info.behavior = AIB_SNIPE;
538                                         Ai_local_info[objnum].mode = AIM_SNIPE_ATTACK;
539                                         mprintf((0, "Making robot #%i go into snipe mode!\n", objnum));
540                                         return;
541                                 }
542                         }
543                         objnum = objp->next;
544                 }
545         }
546
547         mprintf((0, "Couldn't find a robot to make snipe!\n"));
548
549 }
550
551 int Ai_last_missile_camera;
552
553 int Robots_kill_robots_cheat = 0;
554
555 // --------------------------------------------------------------------------------------------------------------------
556 void do_ai_frame(object *obj)
557 {
558         int         objnum = obj-Objects;
559         ai_static   *aip = &obj->ctype.ai_info;
560         ai_local    *ailp = &Ai_local_info[objnum];
561         fix         dist_to_player;
562         vms_vector  vec_to_player;
563         fix         dot;
564         robot_info  *robptr;
565         int         player_visibility=-1;
566         int         obj_ref;
567         int         object_animates;
568         int         new_goal_state;
569         int         visibility_and_vec_computed = 0;
570         int         previous_visibility;
571         vms_vector  gun_point;
572         vms_vector  vis_vec_pos;
573
574         ailp->next_action_time -= FrameTime;
575
576         if (aip->SKIP_AI_COUNT) {
577                 aip->SKIP_AI_COUNT--;
578                 if (obj->mtype.phys_info.flags & PF_USES_THRUST) {
579                         obj->mtype.phys_info.rotthrust.x = (obj->mtype.phys_info.rotthrust.x * 15)/16;
580                         obj->mtype.phys_info.rotthrust.y = (obj->mtype.phys_info.rotthrust.y * 15)/16;
581                         obj->mtype.phys_info.rotthrust.z = (obj->mtype.phys_info.rotthrust.z * 15)/16;
582                         if (!aip->SKIP_AI_COUNT)
583                                 obj->mtype.phys_info.flags &= ~PF_USES_THRUST;
584                 }
585                 return;
586         }
587
588         robptr = &Robot_info[obj->id];
589         Assert(robptr->always_0xabcd == 0xabcd);
590
591         if (do_any_robot_dying_frame(obj))
592                 return;
593
594         // Kind of a hack.  If a robot is flinching, but it is time for it to fire, unflinch it.
595         // Else, you can turn a big nasty robot into a wimp by firing flares at it.
596         // This also allows the player to see the cool flinch effect for mechs without unbalancing the game.
597         if ((aip->GOAL_STATE == AIS_FLIN) && ready_to_fire(robptr, ailp)) {
598                 aip->GOAL_STATE = AIS_FIRE;
599         }
600
601 #ifndef NDEBUG
602         if ((aip->behavior == AIB_RUN_FROM) && (ailp->mode != AIM_RUN_FROM_OBJECT))
603                 Int3(); // This is peculiar.  Behavior is run from, but mode is not.  Contact Mike.
604
605         mprintf_animation_info((obj));
606
607         if (!Do_ai_flag)
608                 return;
609
610         if (Break_on_object != -1)
611                 if ((obj-Objects) == Break_on_object)
612                         Int3(); // Contact Mike: This is a debug break
613 #endif
614
615         //mprintf((0, "Object %i: behavior = %02x, mode = %i, awareness = %i, time = %7.3f\n", obj-Objects, aip->behavior, ailp->mode, ailp->player_awareness_type, f2fl(ailp->player_awareness_time)));
616         //mprintf((0, "Object %i: behavior = %02x, mode = %i, awareness = %i, cur=%i, goal=%i\n", obj-Objects, aip->behavior, ailp->mode, ailp->player_awareness_type, aip->CURRENT_STATE, aip->GOAL_STATE));
617
618         //Assert((aip->behavior >= MIN_BEHAVIOR) && (aip->behavior <= MAX_BEHAVIOR));
619         if (!((aip->behavior >= MIN_BEHAVIOR) && (aip->behavior <= MAX_BEHAVIOR))) {
620                 //mprintf((0, "Object %i behavior is %i, setting to AIB_NORMAL, fix in editor!\n", objnum, aip->behavior));
621                 aip->behavior = AIB_NORMAL;
622         }
623
624         Assert(obj->segnum != -1);
625         Assert(obj->id < N_robot_types);
626
627         obj_ref = objnum ^ FrameCount;
628
629         if (ailp->next_fire > -F1_0*8)
630                 ailp->next_fire -= FrameTime;
631
632         if (robptr->weapon_type2 != -1) {
633                 if (ailp->next_fire2 > -F1_0*8)
634                         ailp->next_fire2 -= FrameTime;
635         } else
636                 ailp->next_fire2 = F1_0*8;
637
638         if (ailp->time_since_processed < F1_0*256)
639                 ailp->time_since_processed += FrameTime;
640
641         previous_visibility = ailp->previous_visibility;    //  Must get this before we toast the master copy!
642
643         // -- (No robots have this behavior...)
644         // -- // Deal with cloaking for robots which are cloaked except just before firing.
645         // -- if (robptr->cloak_type == RI_CLOAKED_EXCEPT_FIRING)
646         // --   if (ailp->next_fire < F1_0/2)
647         // --           aip->CLOAKED = 1;
648         // --   else
649         // --           aip->CLOAKED = 0;
650
651         // If only awake because of a camera, make that the believed player position.
652         if ((aip->SUB_FLAGS & SUB_FLAGS_CAMERA_AWAKE) && (Ai_last_missile_camera != -1))
653                 Believed_player_pos = Objects[Ai_last_missile_camera].pos;
654         else {
655                 if (Robots_kill_robots_cheat) {
656                         vis_vec_pos = obj->pos;
657                         compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
658                         if (player_visibility) {
659                                 int ii, min_obj = -1;
660                                 fix min_dist = F1_0*200, cur_dist;
661
662                                 for (ii=0; ii<=Highest_object_index; ii++)
663                                         if ((Objects[ii].type == OBJ_ROBOT) && (ii != objnum)) {
664                                                 cur_dist = vm_vec_dist_quick(&obj->pos, &Objects[ii].pos);
665
666                                                 if (cur_dist < F1_0*100)
667                                                         if (object_to_object_visibility(obj, &Objects[ii], FQ_TRANSWALL))
668                                                                 if (cur_dist < min_dist) {
669                                                                         min_obj = ii;
670                                                                         min_dist = cur_dist;
671                                                                 }
672                                         }
673                                 if (min_obj != -1) {
674                                         Believed_player_pos = Objects[min_obj].pos;
675                                         Believed_player_seg = Objects[min_obj].segnum;
676                                         vm_vec_normalized_dir_quick(&vec_to_player, &Believed_player_pos, &obj->pos);
677                                 } else
678                                         goto _exit_cheat;
679                         } else
680                                 goto _exit_cheat;
681                 } else {
682 _exit_cheat:
683                         visibility_and_vec_computed = 0;
684                         if (!(Players[Player_num].flags & PLAYER_FLAGS_CLOAKED))
685                                 Believed_player_pos = ConsoleObject->pos;
686                         else
687                                 Believed_player_pos = Ai_cloak_info[objnum & (MAX_AI_CLOAK_INFO-1)].last_position;
688                 }
689         }
690         dist_to_player = vm_vec_dist_quick(&Believed_player_pos, &obj->pos);
691         //if (robptr->companion)
692         //      mprintf((0, "%3i: %3i %8.3f %8s %8s [%3i %4i]\n", objnum, obj->segnum, f2fl(dist_to_player), mode_text[ailp->mode], behavior_text[aip->behavior-0x80], aip->hide_index, aip->path_length));
693
694         // If this robot can fire, compute visibility from gun position.
695         // Don't want to compute visibility twice, as it is expensive.  (So is call to calc_gun_point).
696         if ((previous_visibility || !(obj_ref & 3)) && ready_to_fire(robptr, ailp) && (dist_to_player < F1_0*200) && (robptr->n_guns) && !(robptr->attack_type)) {
697                 // Since we passed ready_to_fire(), either next_fire or next_fire2 <= 0.  calc_gun_point from relevant one.
698                 // If both are <= 0, we will deal with the mess in ai_do_actual_firing_stuff
699                 if (ailp->next_fire <= 0)
700                         calc_gun_point(&gun_point, obj, aip->CURRENT_GUN);
701                 else
702                         calc_gun_point(&gun_point, obj, 0);
703                 vis_vec_pos = gun_point;
704         } else {
705                 vis_vec_pos = obj->pos;
706                 vm_vec_zero(&gun_point);
707                 //mprintf((0, "Visibility = %i, computed from center.\n", player_visibility));
708         }
709
710 // MK: Debugging, July 26, 1995!
711 // if (objnum == 1)
712 // {
713 //      compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
714 //      mprintf((0, "Frame %i: dist=%7.3f, vecdot = %7.3f, mode=%i\n", FrameCount, f2fl(dist_to_player), f2fl(vm_vec_dot(&vec_to_player, &obj->orient.fvec)), ailp->mode));
715 // }
716         // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - 
717         // Occasionally make non-still robots make a path to the player.  Based on agitation and distance from player.
718         if ((aip->behavior != AIB_SNIPE) && (aip->behavior != AIB_RUN_FROM) && (aip->behavior != AIB_STILL) && !(Game_mode & GM_MULTI) && (robptr->companion != 1) && (robptr->thief != 1))
719                 if (Overall_agitation > 70) {
720                         if ((dist_to_player < F1_0*200) && (d_rand() < FrameTime/4)) {
721                                 if (d_rand() * (Overall_agitation - 40) > F1_0*5) {
722                                         // -- mprintf((0, "(1) Object #%i going from still to path in frame %i.\n", objnum, FrameCount));
723                                         create_path_to_player(obj, 4 + Overall_agitation/8 + Difficulty_level, 1);
724                                         return;
725                                 }
726                         }
727                 }
728
729         // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
730         // If retry count not 0, then add it into consecutive_retries.
731         // If it is 0, cut down consecutive_retries.
732         // This is largely a hack to speed up physics and deal with stupid
733         // AI.  This is low level communication between systems of a sort
734         // that should not be done.
735         if ((ailp->retry_count) && !(Game_mode & GM_MULTI)) {
736                 ailp->consecutive_retries += ailp->retry_count;
737                 ailp->retry_count = 0;
738                 if (ailp->consecutive_retries > 3) {
739                         switch (ailp->mode) {
740                                 case AIM_GOTO_PLAYER:
741                                         // -- mprintf((0, "Buddy stuck going to player...\n"));
742                                         // -- Buddy_got_stuck = 1;
743                                         move_towards_segment_center(obj);
744                                         create_path_to_player(obj, 100, 1);
745                                         // -- Buddy_got_stuck = 0;
746                                         break;
747                                 case AIM_GOTO_OBJECT:
748                                         // -- mprintf((0, "Buddy stuck going to object...\n"));
749                                         Escort_goal_object = ESCORT_GOAL_UNSPECIFIED;
750                                         //if (obj->segnum == ConsoleObject->segnum) {
751                                         //      if (Point_segs[aip->hide_index + aip->cur_path_index].segnum == obj->segnum)
752                                         //              if ((aip->cur_path_index + aip->PATH_DIR >= 0) && (aip->cur_path_index + aip->PATH_DIR < aip->path_length-1))
753                                         //                      aip->cur_path_index += aip->PATH_DIR;
754                                         //}
755                                         break;
756                                 case AIM_CHASE_OBJECT:
757                                         // -- mprintf((0, "(2) Object #%i, retries while chasing, creating path to player in frame %i\n", objnum, FrameCount));
758                                         create_path_to_player(obj, 4 + Overall_agitation/8 + Difficulty_level, 1);
759                                         break;
760                                 case AIM_STILL:
761                                         if (robptr->attack_type)
762                                                 move_towards_segment_center(obj);
763                                         else if (!((aip->behavior == AIB_STILL) || (aip->behavior == AIB_STATION) || (aip->behavior == AIB_FOLLOW)))    // Behavior is still, so don't follow path.
764                                                 attempt_to_resume_path(obj);
765                                         break;
766                                 case AIM_FOLLOW_PATH:
767                                         // mprintf((0, "Object %i following path got %i retries in frame %i\n", obj-Objects, ailp->consecutive_retries, FrameCount));
768                                         if (Game_mode & GM_MULTI) {
769                                                 ailp->mode = AIM_STILL;
770                                         } else
771                                                 attempt_to_resume_path(obj);
772                                         break;
773                                 case AIM_RUN_FROM_OBJECT:
774                                         move_towards_segment_center(obj);
775                                         obj->mtype.phys_info.velocity.x = 0;
776                                         obj->mtype.phys_info.velocity.y = 0;
777                                         obj->mtype.phys_info.velocity.z = 0;
778                                         create_n_segment_path(obj, 5, -1);
779                                         ailp->mode = AIM_RUN_FROM_OBJECT;
780                                         break;
781                                 case AIM_BEHIND:
782                                         mprintf((0, "Hiding robot (%i) collided much.\n", obj-Objects));
783                                         move_towards_segment_center(obj);
784                                         obj->mtype.phys_info.velocity.x = 0;
785                                         obj->mtype.phys_info.velocity.y = 0;
786                                         obj->mtype.phys_info.velocity.z = 0;
787                                         break;
788                                 case AIM_OPEN_DOOR:
789                                         create_n_segment_path_to_door(obj, 5, -1);
790                                         break;
791                                 #ifndef NDEBUG
792                                 case AIM_FOLLOW_PATH_2:
793                                         Int3(); // Should never happen!
794                                         break;
795                                 #endif
796                         }
797                         ailp->consecutive_retries = 0;
798                 }
799         } else
800                 ailp->consecutive_retries /= 2;
801
802         // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
803         // If in materialization center, exit
804         if (!(Game_mode & GM_MULTI) && (Segment2s[obj->segnum].special == SEGMENT_IS_ROBOTMAKER)) {
805                 if (Station[Segment2s[obj->segnum].value].Enabled) {
806                         ai_follow_path(obj, 1, 1, NULL);    // 1 = player is visible, which might be a lie, but it works.
807                         return;
808                 }
809         }
810
811         // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
812         // Decrease player awareness due to the passage of time.
813         if (ailp->player_awareness_type) {
814                 if (ailp->player_awareness_time > 0) {
815                         ailp->player_awareness_time -= FrameTime;
816                         if (ailp->player_awareness_time <= 0) {
817                                 ailp->player_awareness_time = F1_0*2;   //new: 11/05/94
818                                 ailp->player_awareness_type--;          //new: 11/05/94
819                         }
820                 } else {
821                         ailp->player_awareness_type--;
822                         ailp->player_awareness_time = F1_0*2;
823                         //aip->GOAL_STATE = AIS_REST;
824                 }
825         } else
826                 aip->GOAL_STATE = AIS_REST;                     //new: 12/13/94
827
828
829         if (Player_is_dead && (ailp->player_awareness_type == 0))
830                 if ((dist_to_player < F1_0*200) && (d_rand() < FrameTime/8)) {
831                         if ((aip->behavior != AIB_STILL) && (aip->behavior != AIB_RUN_FROM)) {
832                                 if (!ai_multiplayer_awareness(obj, 30))
833                                         return;
834                                 #ifndef SHAREWARE
835                                 ai_multi_send_robot_position(objnum, -1);
836                                 #endif
837
838                                 if (!((ailp->mode == AIM_FOLLOW_PATH) && (aip->cur_path_index < aip->path_length-1)))
839                                         if ((aip->behavior != AIB_SNIPE) && (aip->behavior != AIB_RUN_FROM)) {
840                                                 if (dist_to_player < F1_0*30)
841                                                         create_n_segment_path(obj, 5, 1);
842                                                 else
843                                                         create_path_to_player(obj, 20, 1);
844                                         }
845                         }
846                 }
847
848         // -- // Make sure that if this guy got hit or bumped, then he's chasing player.
849         // -- if ((ailp->player_awareness_type == PA_WEAPON_ROBOT_COLLISION) || (ailp->player_awareness_type >= PA_PLAYER_COLLISION)) {
850         // --   if ((ailp->mode != AIM_BEHIND) && (aip->behavior != AIB_STILL) && (aip->behavior != AIB_SNIPE) && (aip->behavior != AIB_RUN_FROM) && (!robptr->companion) && (!robptr->thief) && (obj->id != ROBOT_BRAIN)) {
851         // --           ailp->mode = AIM_CHASE_OBJECT;
852         // --           ailp->player_awareness_type = 0;
853         // --           ailp->player_awareness_time = 0;
854         // --   }
855         // -- }
856
857         // Make sure that if this guy got hit or bumped, then he's chasing player.
858         if ((ailp->player_awareness_type == PA_WEAPON_ROBOT_COLLISION) || (ailp->player_awareness_type >= PA_PLAYER_COLLISION)) {
859                 compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
860                 if (player_visibility == 1) // Only increase visibility if unobstructed, else claw guys attack through doors.
861                         player_visibility = 2;
862         } else if (((obj_ref&3) == 0) && !previous_visibility && (dist_to_player < F1_0*100)) {
863                 fix sval, rval;
864
865                 rval = d_rand();
866                 sval = (dist_to_player * (Difficulty_level+1))/64;
867
868                 // -- mprintf((0, "Object #%3i: dist = %7.3f, rval = %8x, sval = %8x", obj-Objects, f2fl(dist_to_player), rval, sval));
869                 if ((fixmul(rval, sval) < FrameTime) || (Players[Player_num].flags & PLAYER_FLAGS_HEADLIGHT_ON)) {
870                         ailp->player_awareness_type = PA_PLAYER_COLLISION;
871                         ailp->player_awareness_time = F1_0*3;
872                         compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
873                         if (player_visibility == 1) {
874                                 player_visibility = 2;
875                                 // -- mprintf((0, "...SWITCH!"));
876                         }
877                 }
878
879                 // -- mprintf((0, "\n"));
880         }
881
882
883         // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
884         if ((aip->GOAL_STATE == AIS_FLIN) && (aip->CURRENT_STATE == AIS_FLIN))
885                 aip->GOAL_STATE = AIS_LOCK;
886
887         // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
888         // Note: Should only do these two function calls for objects which animate
889         if (Animation_enabled && (dist_to_player < F1_0*100)) { // && !(Game_mode & GM_MULTI)) {
890                 object_animates = do_silly_animation(obj);
891                 if (object_animates)
892                         ai_frame_animation(obj);
893                 //mprintf((0, "Object %i: goal=%i, current=%i\n", obj-Objects, obj->ctype.ai_info.GOAL_STATE, obj->ctype.ai_info.CURRENT_STATE));
894         } else {
895                 // If Object is supposed to animate, but we don't let it animate due to distance, then
896                 // we must change its state, else it will never update.
897                 aip->CURRENT_STATE = aip->GOAL_STATE;
898                 object_animates = 0;        // If we're not doing the animation, then should pretend it doesn't animate.
899         }
900
901         switch (Robot_info[obj->id].boss_flag) {
902         case 0:
903                 break;
904
905         case 1:
906         case 2:
907                 mprintf((1, "Warning: D1 boss detected.  Not supported!\n"));
908                 break;
909
910         default:
911                 {
912                         int     pv;
913                         fix     dtp = dist_to_player/4;
914
915                         if (aip->GOAL_STATE == AIS_FLIN)
916                                 aip->GOAL_STATE = AIS_FIRE;
917                         if (aip->CURRENT_STATE == AIS_FLIN)
918                                 aip->CURRENT_STATE = AIS_FIRE;
919
920                         compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
921
922                         pv = player_visibility;
923
924                         // If player cloaked, visibility is screwed up and superboss will gate in robots when not supposed to.
925                         if (Players[Player_num].flags & PLAYER_FLAGS_CLOAKED) {
926                                 pv = 0;
927                                 dtp = vm_vec_dist_quick(&ConsoleObject->pos, &obj->pos)/4;
928                         }
929
930                         do_boss_stuff(obj, pv);
931                 }
932                 break;
933         }
934
935         // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
936         // Time-slice, don't process all the time, purely an efficiency hack.
937         // Guys whose behavior is station and are not at their hide segment get processed anyway.
938         if (!((aip->behavior == AIB_SNIPE) && (ailp->mode != AIM_SNIPE_WAIT)) && !robptr->companion && !robptr->thief && (ailp->player_awareness_type < PA_WEAPON_ROBOT_COLLISION-1)) { // If robot got hit, he gets to attack player always!
939 #ifndef NDEBUG
940                 if (Break_on_object != objnum) {    // don't time slice if we're interested in this object.
941 #endif
942                         if ((aip->behavior == AIB_STATION) && (ailp->mode == AIM_FOLLOW_PATH) && (aip->hide_segment != obj->segnum)) {
943                                 if (dist_to_player > F1_0*250)  // station guys not at home always processed until 250 units away.
944                                         return;
945                         } else if ((!ailp->previous_visibility) && ((dist_to_player >> 7) > ailp->time_since_processed)) {  // 128 units away (6.4 segments) processed after 1 second.
946                                 if (robptr->thief)
947                                         mprintf((0, "T"));
948                                 return;
949                         }
950 #ifndef NDEBUG
951                 }
952 #endif
953         }
954
955         // Reset time since processed, but skew objects so not everything
956         // processed synchronously, else we get fast frames with the
957         // occasional very slow frame.
958         // AI_proc_time = ailp->time_since_processed;
959         ailp->time_since_processed = - ((objnum & 0x03) * FrameTime ) / 2;
960
961         // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
962         //      Perform special ability
963         switch (obj->id) {
964                 case ROBOT_BRAIN:
965                         // Robots function nicely if behavior is Station.  This
966                         // means they won't move until they can see the player, at
967                         // which time they will start wandering about opening doors.
968                         if (ConsoleObject->segnum == obj->segnum) {
969                                 if (!ai_multiplayer_awareness(obj, 97))
970                                         return;
971                                 compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
972                                 move_away_from_player(obj, &vec_to_player, 0);
973                                 ai_multi_send_robot_position(objnum, -1);
974                         } else if (ailp->mode != AIM_STILL) {
975                                 int r;
976
977                                 r = openable_doors_in_segment(obj->segnum);
978                                 if (r != -1) {
979                                         ailp->mode = AIM_OPEN_DOOR;
980                                         aip->GOALSIDE = r;
981                                 } else if (ailp->mode != AIM_FOLLOW_PATH) {
982                                         if (!ai_multiplayer_awareness(obj, 50))
983                                                 return;
984                                         create_n_segment_path_to_door(obj, 8+Difficulty_level, -1);     // third parameter is avoid_seg, -1 means avoid nothing.
985                                         ai_multi_send_robot_position(objnum, -1);
986                                 }
987
988                                 if (ailp->next_action_time < 0) {
989                                         compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
990                                         if (player_visibility) {
991                                                 make_nearby_robot_snipe();
992                                                 ailp->next_action_time = (NDL - Difficulty_level) * 2*F1_0;
993                                         }
994                                 }
995                         } else {
996                                 compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
997                                 if (player_visibility) {
998                                         if (!ai_multiplayer_awareness(obj, 50))
999                                                 return;
1000                                         create_n_segment_path_to_door(obj, 8+Difficulty_level, -1);     // third parameter is avoid_seg, -1 means avoid nothing.
1001                                         ai_multi_send_robot_position(objnum, -1);
1002                                 }
1003                         }
1004                         break;
1005                 default:
1006                         break;
1007         }
1008
1009         if (aip->behavior == AIB_SNIPE) {
1010                 if ((Game_mode & GM_MULTI) && !robptr->thief) {
1011                         aip->behavior = AIB_NORMAL;
1012                         ailp->mode = AIM_CHASE_OBJECT;
1013                         return;
1014                 }
1015
1016                 if (!(obj_ref & 3) || previous_visibility) {
1017                         compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
1018
1019                         // If this sniper is in still mode, if he was hit or can see player, switch to snipe mode.
1020                         if (ailp->mode == AIM_STILL)
1021                                 if (player_visibility || (ailp->player_awareness_type == PA_WEAPON_ROBOT_COLLISION))
1022                                         ailp->mode = AIM_SNIPE_ATTACK;
1023
1024                         if (!robptr->thief && (ailp->mode != AIM_STILL))
1025                                 do_snipe_frame(obj, dist_to_player, player_visibility, &vec_to_player);
1026                 } else if (!robptr->thief && !robptr->companion)
1027                         return;
1028         }
1029
1030         // More special ability stuff, but based on a property of a robot, not its ID.
1031         if (robptr->companion) {
1032
1033                 compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
1034                 do_escort_frame(obj, dist_to_player, player_visibility);
1035
1036                 if (obj->ctype.ai_info.danger_laser_num != -1) {
1037                         object *dobjp = &Objects[obj->ctype.ai_info.danger_laser_num];
1038
1039                         if ((dobjp->type == OBJ_WEAPON) && (dobjp->signature == obj->ctype.ai_info.danger_laser_signature)) {
1040                                 fix circle_distance;
1041                                 // -- mprintf((0, "Evading!  "));
1042                                 circle_distance = robptr->circle_distance[Difficulty_level] + ConsoleObject->size;
1043                                 ai_move_relative_to_player(obj, ailp, dist_to_player, &vec_to_player, circle_distance, 1, player_visibility);
1044                         }
1045                 }
1046
1047                 if (ready_to_fire(robptr, ailp)) {
1048                         int do_stuff = 0;
1049                         if (openable_doors_in_segment(obj->segnum) != -1)
1050                                 do_stuff = 1;
1051                         else if (openable_doors_in_segment(Point_segs[aip->hide_index + aip->cur_path_index + aip->PATH_DIR].segnum) != -1)
1052                                 do_stuff = 1;
1053                         else if (openable_doors_in_segment(Point_segs[aip->hide_index + aip->cur_path_index + 2*aip->PATH_DIR].segnum) != -1)
1054                                 do_stuff = 1;
1055                         else if ((ailp->mode == AIM_GOTO_PLAYER) && (dist_to_player < 3*MIN_ESCORT_DISTANCE/2) && (vm_vec_dot(&ConsoleObject->orient.fvec, &vec_to_player) > -F1_0/4)) {
1056                                 // mprintf((0, "Firing at player because dot = %7.3f\n", f2fl(vm_vec_dot(&ConsoleObject->orient.fvec, &vec_to_player))));
1057                                 do_stuff = 1;
1058                         } else
1059                                 ; // mprintf((0, "Not Firing at player because dot = %7.3f, dist = %7.3f\n", f2fl(vm_vec_dot(&ConsoleObject->orient.fvec, &vec_to_player)), f2fl(dist_to_player)));
1060
1061                         if (do_stuff) {
1062                                 Laser_create_new_easy( &obj->orient.fvec, &obj->pos, obj-Objects, FLARE_ID, 1);
1063                                 ailp->next_fire = F1_0/2;
1064                                 if (!Buddy_allowed_to_talk) // If buddy not talking, make him fire flares less often.
1065                                         ailp->next_fire += d_rand()*4;
1066                         }
1067
1068                 }
1069         }
1070
1071         if (robptr->thief) {
1072
1073                 compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
1074                 do_thief_frame(obj, dist_to_player, player_visibility, &vec_to_player);
1075
1076                 if (ready_to_fire(robptr, ailp)) {
1077                         int do_stuff = 0;
1078                         if (openable_doors_in_segment(obj->segnum) != -1)
1079                                 do_stuff = 1;
1080                         else if (openable_doors_in_segment(Point_segs[aip->hide_index + aip->cur_path_index + aip->PATH_DIR].segnum) != -1)
1081                                 do_stuff = 1;
1082                         else if (openable_doors_in_segment(Point_segs[aip->hide_index + aip->cur_path_index + 2*aip->PATH_DIR].segnum) != -1)
1083                                 do_stuff = 1;
1084
1085                         if (do_stuff) {
1086                                 // @mk, 05/08/95: Firing flare from center of object, this is dumb...
1087                                 Laser_create_new_easy( &obj->orient.fvec, &obj->pos, obj-Objects, FLARE_ID, 1);
1088                                 ailp->next_fire = F1_0/2;
1089                                 if (Stolen_item_index == 0)     // If never stolen an item, fire flares less often (bad: Stolen_item_index wraps, but big deal)
1090                                         ailp->next_fire += d_rand()*4;
1091                         }
1092                 }
1093         }
1094
1095         // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
1096         switch (ailp->mode) {
1097                 case AIM_CHASE_OBJECT: {        // chasing player, sort of, chase if far, back off if close, circle in between
1098                         fix circle_distance;
1099
1100                         circle_distance = robptr->circle_distance[Difficulty_level] + ConsoleObject->size;
1101                         // Green guy doesn't get his circle distance boosted, else he might never attack.
1102                         if (robptr->attack_type != 1)
1103                                 circle_distance += (objnum&0xf) * F1_0/2;
1104
1105                         compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
1106
1107                         // @mk, 12/27/94, structure here was strange.  Would do both clauses of what are now this if/then/else.  Used to be if/then, if/then.
1108                         if ((player_visibility < 2) && (previous_visibility == 2)) { // this is redundant: mk, 01/15/95: && (ailp->mode == AIM_CHASE_OBJECT)) {
1109                                 // -- mprintf((0, "I used to be able to see the player!\n"));
1110                                 if (!ai_multiplayer_awareness(obj, 53)) {
1111                                         if (maybe_ai_do_actual_firing_stuff(obj, aip))
1112                                                 ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates, aip->CURRENT_GUN);
1113                                         return;
1114                                 }
1115                                 // -- mprintf((0, "(3) Object #%i going from chase to player path in frame %i.\n", objnum, FrameCount));
1116                                 create_path_to_player(obj, 8, 1);
1117                                 ai_multi_send_robot_position(objnum, -1);
1118                         } else if ((player_visibility == 0) && (dist_to_player > F1_0*80) && (!(Game_mode & GM_MULTI))) {
1119                                 // If pretty far from the player, player cannot be seen
1120                                 // (obstructed) and in chase mode, switch to follow path mode.
1121                                 // This has one desirable benefit of avoiding physics retries.
1122                                 if (aip->behavior == AIB_STATION) {
1123                                         ailp->goal_segment = aip->hide_segment;
1124                                         // -- mprintf((0, "(1) Object #%i going from chase to STATION in frame %i.\n", objnum, FrameCount));
1125                                         create_path_to_station(obj, 15);
1126                                 } // -- this looks like a dumb thing to do...robots following paths far away from you! else create_n_segment_path(obj, 5, -1);
1127                                 break;
1128                         }
1129
1130                         if ((aip->CURRENT_STATE == AIS_REST) && (aip->GOAL_STATE == AIS_REST)) {
1131                                 if (player_visibility) {
1132                                         if (d_rand() < FrameTime*player_visibility) {
1133                                                 if (dist_to_player/256 < d_rand()*player_visibility) {
1134                                                         // mprintf((0, "Object %i searching for player.\n", obj-Objects));
1135                                                         aip->GOAL_STATE = AIS_SRCH;
1136                                                         aip->CURRENT_STATE = AIS_SRCH;
1137                                                 }
1138                                         }
1139                                 }
1140                         }
1141
1142                         if (GameTime - ailp->time_player_seen > CHASE_TIME_LENGTH) {
1143
1144                                 if (Game_mode & GM_MULTI)
1145                                         if (!player_visibility && (dist_to_player > F1_0*70)) {
1146                                                 ailp->mode = AIM_STILL;
1147                                                 return;
1148                                         }
1149
1150                                 if (!ai_multiplayer_awareness(obj, 64)) {
1151                                         if (maybe_ai_do_actual_firing_stuff(obj, aip))
1152                                                 ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates, aip->CURRENT_GUN);
1153                                         return;
1154                                 }
1155                                 // -- bad idea, robots charge player they've never seen! -- mprintf((0, "(4) Object #%i going from chase to player path in frame %i.\n", objnum, FrameCount));
1156                                 // -- bad idea, robots charge player they've never seen! -- create_path_to_player(obj, 10, 1);
1157                                 // -- bad idea, robots charge player they've never seen! -- ai_multi_send_robot_position(objnum, -1);
1158                         } else if ((aip->CURRENT_STATE != AIS_REST) && (aip->GOAL_STATE != AIS_REST)) {
1159                                 if (!ai_multiplayer_awareness(obj, 70)) {
1160                                         if (maybe_ai_do_actual_firing_stuff(obj, aip))
1161                                                 ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates, aip->CURRENT_GUN);
1162                                         return;
1163                                 }
1164                                 ai_move_relative_to_player(obj, ailp, dist_to_player, &vec_to_player, circle_distance, 0, player_visibility);
1165
1166                                 if ((obj_ref & 1) && ((aip->GOAL_STATE == AIS_SRCH) || (aip->GOAL_STATE == AIS_LOCK))) {
1167                                         if (player_visibility) // == 2)
1168                                                 ai_turn_towards_vector(&vec_to_player, obj, robptr->turn_time[Difficulty_level]);
1169                                 }
1170
1171                                 if (ai_evaded) {
1172                                         ai_multi_send_robot_position(objnum, 1);
1173                                         ai_evaded = 0;
1174                                 } else
1175                                         ai_multi_send_robot_position(objnum, -1);
1176
1177                                 do_firing_stuff(obj, player_visibility, &vec_to_player);
1178                         }
1179                         break;
1180                 }
1181
1182                 case AIM_RUN_FROM_OBJECT:
1183                         compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
1184
1185                         if (player_visibility) {
1186                                 if (ailp->player_awareness_type == 0)
1187                                         ailp->player_awareness_type = PA_WEAPON_ROBOT_COLLISION;
1188
1189                         }
1190
1191                         // If in multiplayer, only do if player visible.  If not multiplayer, do always.
1192                         if (!(Game_mode & GM_MULTI) || player_visibility)
1193                                 if (ai_multiplayer_awareness(obj, 75)) {
1194                                         ai_follow_path(obj, player_visibility, previous_visibility, &vec_to_player);
1195                                         ai_multi_send_robot_position(objnum, -1);
1196                                 }
1197
1198                         if (aip->GOAL_STATE != AIS_FLIN)
1199                                 aip->GOAL_STATE = AIS_LOCK;
1200                         else if (aip->CURRENT_STATE == AIS_FLIN)
1201                                 aip->GOAL_STATE = AIS_LOCK;
1202
1203                         // Bad to let run_from robot fire at player because it
1204                         // will cause a war in which it turns towards the player
1205                         // to fire and then towards its goal to move.
1206                         // do_firing_stuff(obj, player_visibility, &vec_to_player);
1207                         // Instead, do this:
1208                         // (Note, only drop if player is visible.  This prevents
1209                         // the bombs from being a giveaway, and also ensures that
1210                         // the robot is moving while it is dropping.  Also means
1211                         // fewer will be dropped.)
1212                         if ((ailp->next_fire <= 0) && (player_visibility)) {
1213                                 vms_vector fire_vec, fire_pos;
1214
1215                                 if (!ai_multiplayer_awareness(obj, 75))
1216                                         return;
1217
1218                                 fire_vec = obj->orient.fvec;
1219                                 vm_vec_negate(&fire_vec);
1220                                 vm_vec_add(&fire_pos, &obj->pos, &fire_vec);
1221
1222                                 if (aip->SUB_FLAGS & SUB_FLAGS_SPROX)
1223                                         Laser_create_new_easy( &fire_vec, &fire_pos, obj-Objects, ROBOT_SUPERPROX_ID, 1);
1224                                 else
1225                                         Laser_create_new_easy( &fire_vec, &fire_pos, obj-Objects, PROXIMITY_ID, 1);
1226
1227                                 ailp->next_fire = (F1_0/2)*(NDL+5 - Difficulty_level);      // Drop a proximity bomb every 5 seconds.
1228
1229 #ifdef NETWORK
1230 #ifndef SHAREWARE
1231                                 if (Game_mode & GM_MULTI)
1232                                 {
1233                                         ai_multi_send_robot_position(obj-Objects, -1);
1234                                         if (aip->SUB_FLAGS & SUB_FLAGS_SPROX)
1235                                                 multi_send_robot_fire(obj-Objects, -2, &fire_vec);
1236                                         else
1237                                                 multi_send_robot_fire(obj-Objects, -1, &fire_vec);
1238                                 }
1239 #endif
1240 #endif
1241                         }
1242                         break;
1243
1244                 case AIM_GOTO_PLAYER:
1245                 case AIM_GOTO_OBJECT:
1246                         ai_follow_path(obj, 2, previous_visibility, &vec_to_player);    // Follows path as if player can see robot.
1247                         ai_multi_send_robot_position(objnum, -1);
1248                         break;
1249
1250                 case AIM_FOLLOW_PATH: {
1251                         int anger_level = 65;
1252
1253                         if (aip->behavior == AIB_STATION)
1254                                 if (Point_segs[aip->hide_index + aip->path_length - 1].segnum == aip->hide_segment) {
1255                                         anger_level = 64;
1256                                         // mprintf((0, "Object %i, station, lowering anger to 64.\n"));
1257                                 }
1258
1259                         compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
1260
1261                         if (Game_mode & (GM_MODEM | GM_SERIAL))
1262                                 if (!player_visibility && (dist_to_player > F1_0*70)) {
1263                                         ailp->mode = AIM_STILL;
1264                                         return;
1265                                 }
1266
1267                         if (!ai_multiplayer_awareness(obj, anger_level)) {
1268                                 if (maybe_ai_do_actual_firing_stuff(obj, aip)) {
1269                                         compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
1270                                         ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates, aip->CURRENT_GUN);
1271                                 }
1272                                 return;
1273                         }
1274
1275                         ai_follow_path(obj, player_visibility, previous_visibility, &vec_to_player);
1276
1277                         if (aip->GOAL_STATE != AIS_FLIN)
1278                                 aip->GOAL_STATE = AIS_LOCK;
1279                         else if (aip->CURRENT_STATE == AIS_FLIN)
1280                                 aip->GOAL_STATE = AIS_LOCK;
1281
1282                         if (aip->behavior != AIB_RUN_FROM)
1283                                 do_firing_stuff(obj, player_visibility, &vec_to_player);
1284
1285                         if ((player_visibility == 2) && (aip->behavior != AIB_SNIPE) && (aip->behavior != AIB_FOLLOW) && (aip->behavior != AIB_RUN_FROM) && (obj->id != ROBOT_BRAIN) && (robptr->companion != 1) && (robptr->thief != 1)) {
1286                                 if (robptr->attack_type == 0)
1287                                         ailp->mode = AIM_CHASE_OBJECT;
1288                                 // This should not just be distance based, but also time-since-player-seen based.
1289                         } else if ((dist_to_player > F1_0*(20*(2*Difficulty_level + robptr->pursuit)))
1290                                                 && (GameTime - ailp->time_player_seen > (F1_0/2*(Difficulty_level+robptr->pursuit)))
1291                                                 && (player_visibility == 0)
1292                                                 && (aip->behavior == AIB_NORMAL)
1293                                                 && (ailp->mode == AIM_FOLLOW_PATH)) {
1294                                 ailp->mode = AIM_STILL;
1295                                 aip->hide_index = -1;
1296                                 aip->path_length = 0;
1297                         }
1298
1299                         ai_multi_send_robot_position(objnum, -1);
1300
1301                         break;
1302                 }
1303
1304                 case AIM_BEHIND:
1305                         if (!ai_multiplayer_awareness(obj, 71)) {
1306                                 if (maybe_ai_do_actual_firing_stuff(obj, aip)) {
1307                                         compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
1308                                         ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates, aip->CURRENT_GUN);
1309                                 }
1310                                 return;
1311                         }
1312
1313                         compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
1314
1315                         if (player_visibility == 2) {
1316                                 // Get behind the player.
1317                                 // Method:
1318                                 // If vec_to_player dot player_rear_vector > 0, behind is goal.
1319                                 // Else choose goal with larger dot from left, right.
1320                                 vms_vector  goal_point, goal_vector, vec_to_goal, rand_vec;
1321                                 fix         dot;
1322
1323                                 dot = vm_vec_dot(&ConsoleObject->orient.fvec, &vec_to_player);
1324                                 if (dot > 0) {          // Remember, we're interested in the rear vector dot being < 0.
1325                                         goal_vector = ConsoleObject->orient.fvec;
1326                                         vm_vec_negate(&goal_vector);
1327                                         // -- mprintf((0, "Goal is BEHIND\n"));
1328                                 } else {
1329                                         fix dot;
1330                                         dot = vm_vec_dot(&ConsoleObject->orient.rvec, &vec_to_player);
1331                                         goal_vector = ConsoleObject->orient.rvec;
1332                                         if (dot > 0) {
1333                                                 vm_vec_negate(&goal_vector);
1334                                                 // -- mprintf((0, "Goal is LEFT\n"));
1335                                         } else
1336                                                 ; // -- mprintf((0, "Goal is RIGHT\n"));
1337                                 }
1338
1339                                 vm_vec_scale(&goal_vector, 2*(ConsoleObject->size + obj->size + (((objnum*4 + FrameCount) & 63) << 12)));
1340                                 vm_vec_add(&goal_point, &ConsoleObject->pos, &goal_vector);
1341                                 make_random_vector(&rand_vec);
1342                                 vm_vec_scale_add2(&goal_point, &rand_vec, F1_0*8);
1343                                 vm_vec_sub(&vec_to_goal, &goal_point, &obj->pos);
1344                                 vm_vec_normalize_quick(&vec_to_goal);
1345                                 move_towards_vector(obj, &vec_to_goal, 0);
1346                                 ai_turn_towards_vector(&vec_to_player, obj, robptr->turn_time[Difficulty_level]);
1347                                 ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates, aip->CURRENT_GUN);
1348                         }
1349
1350                         if (aip->GOAL_STATE != AIS_FLIN)
1351                                 aip->GOAL_STATE = AIS_LOCK;
1352                         else if (aip->CURRENT_STATE == AIS_FLIN)
1353                                 aip->GOAL_STATE = AIS_LOCK;
1354
1355                         ai_multi_send_robot_position(objnum, -1);
1356                         break;
1357
1358                 case AIM_STILL:
1359                         if ((dist_to_player < F1_0*120+Difficulty_level*F1_0*20) || (ailp->player_awareness_type >= PA_WEAPON_ROBOT_COLLISION-1)) {
1360                                 compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
1361
1362                                 // turn towards vector if visible this time or last time, or rand
1363                                 // new!
1364                                 if ((player_visibility == 2) || (previous_visibility == 2)) { // -- MK, 06/09/95:  || ((d_rand() > 0x4000) && !(Game_mode & GM_MULTI))) {
1365                                         if (!ai_multiplayer_awareness(obj, 71)) {
1366                                                 if (maybe_ai_do_actual_firing_stuff(obj, aip))
1367                                                         ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates, aip->CURRENT_GUN);
1368                                                 return;
1369                                         }
1370                                         ai_turn_towards_vector(&vec_to_player, obj, robptr->turn_time[Difficulty_level]);
1371                                         ai_multi_send_robot_position(objnum, -1);
1372                                 }
1373
1374                                 do_firing_stuff(obj, player_visibility, &vec_to_player);
1375                                 if (player_visibility == 2) {  // Changed @mk, 09/21/95: Require that they be looking to evade.  Change, MK, 01/03/95 for Multiplayer reasons.  If robots can't see you (even with eyes on back of head), then don't do evasion.
1376                                         if (robptr->attack_type == 1) {
1377                                                 aip->behavior = AIB_NORMAL;
1378                                                 if (!ai_multiplayer_awareness(obj, 80)) {
1379                                                         if (maybe_ai_do_actual_firing_stuff(obj, aip))
1380                                                                 ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates, aip->CURRENT_GUN);
1381                                                         return;
1382                                                 }
1383                                                 ai_move_relative_to_player(obj, ailp, dist_to_player, &vec_to_player, 0, 0, player_visibility);
1384                                                 if (ai_evaded) {
1385                                                         ai_multi_send_robot_position(objnum, 1);
1386                                                         ai_evaded = 0;
1387                                                 }
1388                                                 else
1389                                                         ai_multi_send_robot_position(objnum, -1);
1390                                         } else {
1391                                                 // Robots in hover mode are allowed to evade at half normal speed.
1392                                                 if (!ai_multiplayer_awareness(obj, 81)) {
1393                                                         if (maybe_ai_do_actual_firing_stuff(obj, aip))
1394                                                                 ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates, aip->CURRENT_GUN);
1395                                                         return;
1396                                                 }
1397                                                 ai_move_relative_to_player(obj, ailp, dist_to_player, &vec_to_player, 0, 1, player_visibility);
1398                                                 if (ai_evaded) {
1399                                                         ai_multi_send_robot_position(objnum, -1);
1400                                                         ai_evaded = 0;
1401                                                 }
1402                                                 else
1403                                                         ai_multi_send_robot_position(objnum, -1);
1404                                         }
1405                                 } else if ((obj->segnum != aip->hide_segment) && (dist_to_player > F1_0*80) && (!(Game_mode & GM_MULTI))) {
1406                                         // If pretty far from the player, player cannot be
1407                                         // seen (obstructed) and in chase mode, switch to
1408                                         // follow path mode.
1409                                         // This has one desirable benefit of avoiding physics retries.
1410                                         if (aip->behavior == AIB_STATION) {
1411                                                 ailp->goal_segment = aip->hide_segment;
1412                                                 // -- mprintf((0, "(2) Object #%i going from STILL to STATION in frame %i.\n", objnum, FrameCount));
1413                                                 create_path_to_station(obj, 15);
1414                                         }
1415                                         break;
1416                                 }
1417                         }
1418
1419                         break;
1420                 case AIM_OPEN_DOOR: {       // trying to open a door.
1421                         vms_vector center_point, goal_vector;
1422                         Assert(obj->id == ROBOT_BRAIN);     // Make sure this guy is allowed to be in this mode.
1423
1424                         if (!ai_multiplayer_awareness(obj, 62))
1425                                 return;
1426                         compute_center_point_on_side(&center_point, &Segments[obj->segnum], aip->GOALSIDE);
1427                         vm_vec_sub(&goal_vector, &center_point, &obj->pos);
1428                         vm_vec_normalize_quick(&goal_vector);
1429                         ai_turn_towards_vector(&goal_vector, obj, robptr->turn_time[Difficulty_level]);
1430                         move_towards_vector(obj, &goal_vector, 0);
1431                         ai_multi_send_robot_position(objnum, -1);
1432
1433                         break;
1434                 }
1435
1436                 case AIM_SNIPE_WAIT:
1437                         break;
1438                 case AIM_SNIPE_RETREAT:
1439                         // -- if (ai_multiplayer_awareness(obj, 53))
1440                         // --   if (ailp->next_fire < -F1_0)
1441                         // --           ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates, aip->CURRENT_GUN);
1442                         break;
1443                 case AIM_SNIPE_RETREAT_BACKWARDS:
1444                 case AIM_SNIPE_ATTACK:
1445                 case AIM_SNIPE_FIRE:
1446                         if (ai_multiplayer_awareness(obj, 53)) {
1447                                 ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates, aip->CURRENT_GUN);
1448                                 if (robptr->thief)
1449                                         ai_move_relative_to_player(obj, ailp, dist_to_player, &vec_to_player, 0, 0, player_visibility);
1450                                 break;
1451                         }
1452                         break;
1453
1454                 case AIM_THIEF_WAIT:
1455                 case AIM_THIEF_ATTACK:
1456                 case AIM_THIEF_RETREAT:
1457                 case AIM_WANDER:    // Used for Buddy Bot
1458                         break;
1459
1460                 default:
1461                         mprintf((0, "Unknown mode = %i in robot %i, behavior = %i\n", ailp->mode, obj-Objects, aip->behavior));
1462                         ailp->mode = AIM_CHASE_OBJECT;
1463                         break;
1464         }       // end: switch (ailp->mode) {
1465
1466         // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
1467         // If the robot can see you, increase his awareness of you.
1468         // This prevents the problem of a robot looking right at you but doing nothing.
1469         // Assert(player_visibility != -1); // Means it didn't get initialized!
1470         compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
1471         if ((player_visibility == 2) && (aip->behavior != AIB_FOLLOW) && (!robptr->thief)) {
1472                 if ((ailp->player_awareness_type == 0) && (aip->SUB_FLAGS & SUB_FLAGS_CAMERA_AWAKE))
1473                         aip->SUB_FLAGS &= ~SUB_FLAGS_CAMERA_AWAKE;
1474                 else if (ailp->player_awareness_type == 0)
1475                         ailp->player_awareness_type = PA_PLAYER_COLLISION;
1476         }
1477
1478         // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
1479         if (!object_animates) {
1480                 aip->CURRENT_STATE = aip->GOAL_STATE;
1481                 // mprintf((0, "Setting current to goal (%i) because object doesn't animate.\n", aip->GOAL_STATE));
1482         }
1483
1484         Assert(ailp->player_awareness_type <= AIE_MAX);
1485         Assert(aip->CURRENT_STATE < AIS_MAX);
1486         Assert(aip->GOAL_STATE < AIS_MAX);
1487
1488         // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
1489         if (ailp->player_awareness_type) {
1490                 new_goal_state = Ai_transition_table[ailp->player_awareness_type-1][aip->CURRENT_STATE][aip->GOAL_STATE];
1491                 if (ailp->player_awareness_type == PA_WEAPON_ROBOT_COLLISION) {
1492                         // Decrease awareness, else this robot will flinch every frame.
1493                         ailp->player_awareness_type--;
1494                         ailp->player_awareness_time = F1_0*3;
1495                 }
1496
1497                 if (new_goal_state == AIS_ERR_)
1498                         new_goal_state = AIS_REST;
1499
1500                 if (aip->CURRENT_STATE == AIS_NONE)
1501                         aip->CURRENT_STATE = AIS_REST;
1502
1503                 aip->GOAL_STATE = new_goal_state;
1504
1505         }
1506
1507         // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
1508         // If new state = fire, then set all gun states to fire.
1509         if ((aip->GOAL_STATE == AIS_FIRE) ) {
1510                 int i,num_guns;
1511                 num_guns = Robot_info[obj->id].n_guns;
1512                 for (i=0; i<num_guns; i++)
1513                         ailp->goal_state[i] = AIS_FIRE;
1514         }
1515
1516         // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
1517         // Hack by mk on 01/04/94, if a guy hasn't animated to the firing state, but his next_fire says ok to fire, bash him there
1518         if (ready_to_fire(robptr, ailp) && (aip->GOAL_STATE == AIS_FIRE))
1519                 aip->CURRENT_STATE = AIS_FIRE;
1520
1521         if ((aip->GOAL_STATE != AIS_FLIN)  && (obj->id != ROBOT_BRAIN)) {
1522                 switch (aip->CURRENT_STATE) {
1523                 case AIS_NONE:
1524                         compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
1525
1526                         dot = vm_vec_dot(&obj->orient.fvec, &vec_to_player);
1527                         if (dot >= F1_0/2)
1528                                 if (aip->GOAL_STATE == AIS_REST)
1529                                         aip->GOAL_STATE = AIS_SRCH;
1530                         break;
1531                 case AIS_REST:
1532                         if (aip->GOAL_STATE == AIS_REST) {
1533                                 compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
1534                                 if (ready_to_fire(robptr, ailp) && (player_visibility)) {
1535                                         // mprintf((0, "Setting goal state to fire from rest.\n"));
1536                                         aip->GOAL_STATE = AIS_FIRE;
1537                                 }
1538                         }
1539                         break;
1540                 case AIS_SRCH:
1541                         if (!ai_multiplayer_awareness(obj, 60))
1542                                 return;
1543
1544                         compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
1545
1546                         if (player_visibility == 2) {
1547                                 ai_turn_towards_vector(&vec_to_player, obj, robptr->turn_time[Difficulty_level]);
1548                                 ai_multi_send_robot_position(objnum, -1);
1549                         }
1550                         break;
1551                 case AIS_LOCK:
1552                         compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
1553
1554                         if (!(Game_mode & GM_MULTI) || (player_visibility)) {
1555                                 if (!ai_multiplayer_awareness(obj, 68))
1556                                         return;
1557
1558                                 if (player_visibility == 2) {   // @mk, 09/21/95, require that they be looking towards you to turn towards you.
1559                                         ai_turn_towards_vector(&vec_to_player, obj, robptr->turn_time[Difficulty_level]);
1560                                         ai_multi_send_robot_position(objnum, -1);
1561                                 }
1562                         }
1563                         break;
1564                 case AIS_FIRE:
1565                         compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
1566
1567                         if (player_visibility == 2) {
1568                                 if (!ai_multiplayer_awareness(obj, (ROBOT_FIRE_AGITATION-1))) {
1569                                         if (Game_mode & GM_MULTI) {
1570                                                 ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates, aip->CURRENT_GUN);
1571                                                 return;
1572                                         }
1573                                 }
1574                                 ai_turn_towards_vector(&vec_to_player, obj, robptr->turn_time[Difficulty_level]);
1575                                 ai_multi_send_robot_position(objnum, -1);
1576                         }
1577
1578                         // Fire at player, if appropriate.
1579                         ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates, aip->CURRENT_GUN);
1580
1581                         break;
1582                 case AIS_RECO:
1583                         if (!(obj_ref & 3)) {
1584                                 compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
1585                                 if (player_visibility == 2) {
1586                                         if (!ai_multiplayer_awareness(obj, 69))
1587                                                 return;
1588                                         ai_turn_towards_vector(&vec_to_player, obj, robptr->turn_time[Difficulty_level]);
1589                                         ai_multi_send_robot_position(objnum, -1);
1590                                 } // -- MK, 06/09/95: else if (!(Game_mode & GM_MULTI)) {
1591                         }
1592                         break;
1593                 case AIS_FLIN:
1594                         // mprintf((0, "State = flinch, goal = %i.\n", aip->GOAL_STATE));
1595                         break;
1596                 default:
1597                         mprintf((1, "Unknown mode for AI object #%i\n", objnum));
1598                         aip->GOAL_STATE = AIS_REST;
1599                         aip->CURRENT_STATE = AIS_REST;
1600                         break;
1601                 }
1602         } // end of: if (aip->GOAL_STATE != AIS_FLIN) {
1603
1604         // Switch to next gun for next fire.
1605         if (player_visibility == 0) {
1606                 aip->CURRENT_GUN++;
1607                 if (aip->CURRENT_GUN >= Robot_info[obj->id].n_guns)
1608                 {
1609                         if ((robptr->n_guns == 1) || (robptr->weapon_type2 == -1))  // Two weapon types hack.
1610                                 aip->CURRENT_GUN = 0;
1611                         else
1612                                 aip->CURRENT_GUN = 1;
1613                 }
1614         }
1615
1616 }
1617
1618 // ----------------------------------------------------------------------------
1619 void ai_do_cloak_stuff(void)
1620 {
1621         int i;
1622
1623         for (i=0; i<MAX_AI_CLOAK_INFO; i++) {
1624                 Ai_cloak_info[i].last_position = ConsoleObject->pos;
1625                 Ai_cloak_info[i].last_segment = ConsoleObject->segnum;
1626                 Ai_cloak_info[i].last_time = GameTime;
1627         }
1628
1629         // Make work for control centers.
1630         Believed_player_pos = Ai_cloak_info[0].last_position;
1631         Believed_player_seg = Ai_cloak_info[0].last_segment;
1632
1633 }
1634
1635 // ----------------------------------------------------------------------------
1636 // Returns false if awareness is considered too puny to add, else returns true.
1637 int add_awareness_event(object *objp, int type)
1638 {
1639         // If player cloaked and hit a robot, then increase awareness
1640         if ((type == PA_WEAPON_ROBOT_COLLISION) || (type == PA_WEAPON_WALL_COLLISION) || (type == PA_PLAYER_COLLISION))
1641                 ai_do_cloak_stuff();
1642
1643         if (Num_awareness_events < MAX_AWARENESS_EVENTS) {
1644                 if ((type == PA_WEAPON_WALL_COLLISION) || (type == PA_WEAPON_ROBOT_COLLISION))
1645                         if (objp->id == VULCAN_ID)
1646                                 if (d_rand() > 3276)
1647                                         return 0;       // For vulcan cannon, only about 1/10 actually cause awareness
1648
1649                 Awareness_events[Num_awareness_events].segnum = objp->segnum;
1650                 Awareness_events[Num_awareness_events].pos = objp->pos;
1651                 Awareness_events[Num_awareness_events].type = type;
1652                 Num_awareness_events++;
1653         } else {
1654                 //Int3();   // Hey -- Overflowed Awareness_events, make more or something
1655                 // This just gets ignored, so you can just
1656                 // continue.
1657         }
1658         return 1;
1659
1660 }
1661
1662 // ----------------------------------------------------------------------------------
1663 // Robots will become aware of the player based on something that occurred.
1664 // The object (probably player or weapon) which created the awareness is objp.
1665 void create_awareness_event(object *objp, int type)
1666 {
1667         // If not in multiplayer, or in multiplayer with robots, do this, else unnecessary!
1668         if (!(Game_mode & GM_MULTI) || (Game_mode & GM_MULTI_ROBOTS)) {
1669                 if (add_awareness_event(objp, type)) {
1670                         if (((d_rand() * (type+4)) >> 15) > 4)
1671                                 Overall_agitation++;
1672                         if (Overall_agitation > OVERALL_AGITATION_MAX)
1673                                 Overall_agitation = OVERALL_AGITATION_MAX;
1674                 }
1675         }
1676 }
1677
1678 byte New_awareness[MAX_SEGMENTS];
1679
1680 // ----------------------------------------------------------------------------------
1681 void pae_aux(int segnum, int type, int level)
1682 {
1683         int j;
1684
1685         if (New_awareness[segnum] < type)
1686                 New_awareness[segnum] = type;
1687
1688         // Process children.
1689         for (j=0; j<MAX_SIDES_PER_SEGMENT; j++)
1690                 if (IS_CHILD(Segments[segnum].children[j]))
1691                 {
1692                         if (level <= 3)
1693                         {
1694                                 if (type == 4)
1695                                         pae_aux(Segments[segnum].children[j], type-1, level+1);
1696                                 else
1697                                         pae_aux(Segments[segnum].children[j], type, level+1);
1698                         }
1699                 }
1700 }
1701
1702
1703 // ----------------------------------------------------------------------------------
1704 void process_awareness_events(void)
1705 {
1706         int i;
1707
1708         if (!(Game_mode & GM_MULTI) || (Game_mode & GM_MULTI_ROBOTS)) {
1709                 memset(New_awareness, 0, sizeof(New_awareness[0]) * (Highest_segment_index+1));
1710
1711                 for (i=0; i<Num_awareness_events; i++)
1712                         pae_aux(Awareness_events[i].segnum, Awareness_events[i].type, 1);
1713
1714         }
1715
1716         Num_awareness_events = 0;
1717 }
1718
1719 // ----------------------------------------------------------------------------------
1720 void set_player_awareness_all(void)
1721 {
1722         int i;
1723
1724         process_awareness_events();
1725
1726         for (i=0; i<=Highest_object_index; i++)
1727                 if (Objects[i].control_type == CT_AI) {
1728                         if (New_awareness[Objects[i].segnum] > Ai_local_info[i].player_awareness_type) {
1729                                 Ai_local_info[i].player_awareness_type = New_awareness[Objects[i].segnum];
1730                                 Ai_local_info[i].player_awareness_time = PLAYER_AWARENESS_INITIAL_TIME;
1731                         }
1732
1733                         // Clear the bit that says this robot is only awake because a camera woke it up.
1734                         if (New_awareness[Objects[i].segnum] > Ai_local_info[i].player_awareness_type)
1735                                 Objects[i].ctype.ai_info.SUB_FLAGS &= ~SUB_FLAGS_CAMERA_AWAKE;
1736                 }
1737 }
1738
1739 #ifndef NDEBUG
1740 int Ai_dump_enable = 0;
1741
1742 FILE *Ai_dump_file = NULL;
1743
1744 char Ai_error_message[128] = "";
1745
1746 // ----------------------------------------------------------------------------------
1747 void force_dump_ai_objects_all(char *msg)
1748 {
1749         int tsave;
1750
1751         tsave = Ai_dump_enable;
1752
1753         Ai_dump_enable = 1;
1754
1755         sprintf(Ai_error_message, "%s\n", msg);
1756         //dump_ai_objects_all();
1757         Ai_error_message[0] = 0;
1758
1759         Ai_dump_enable = tsave;
1760 }
1761
1762 // ----------------------------------------------------------------------------------
1763 void turn_off_ai_dump(void)
1764 {
1765         if (Ai_dump_file != NULL)
1766                 fclose(Ai_dump_file);
1767
1768         Ai_dump_file = NULL;
1769 }
1770
1771 #endif
1772
1773 extern void do_boss_dying_frame(object *objp);
1774
1775 // ----------------------------------------------------------------------------------
1776 // Do things which need to get done for all AI objects each frame.
1777 // This includes:
1778 //  Setting player_awareness (a fix, time in seconds which object is aware of player)
1779 void do_ai_frame_all(void)
1780 {
1781 #ifndef NDEBUG
1782         //dump_ai_objects_all();
1783 #endif
1784
1785         set_player_awareness_all();
1786
1787         if (Ai_last_missile_camera != -1) {
1788                 // Clear if supposed misisle camera is not a weapon, or just every so often, just in case.
1789                 if (((FrameCount & 0x0f) == 0) || (Objects[Ai_last_missile_camera].type != OBJ_WEAPON)) {
1790                         int i;
1791
1792                         Ai_last_missile_camera = -1;
1793                         for (i=0; i<=Highest_object_index; i++)
1794                                 if (Objects[i].type == OBJ_ROBOT)
1795                                         Objects[i].ctype.ai_info.SUB_FLAGS &= ~SUB_FLAGS_CAMERA_AWAKE;
1796                 }
1797         }
1798
1799         // (Moved here from do_boss_stuff() because that only gets called if robot aware of player.)
1800         if (Boss_dying) {
1801                 int i;
1802
1803                 for (i=0; i<=Highest_object_index; i++)
1804                         if (Objects[i].type == OBJ_ROBOT)
1805                                 if (Robot_info[Objects[i].id].boss_flag)
1806                                         do_boss_dying_frame(&Objects[i]);
1807         }
1808 }
1809
1810
1811 extern int Final_boss_is_dead;
1812 extern fix Boss_invulnerable_dot;
1813
1814 // Initializations to be performed for all robots for a new level.
1815 void init_robots_for_level(void)
1816 {
1817         Overall_agitation = 0;
1818         Final_boss_is_dead=0;
1819
1820         Buddy_objnum = 0;
1821         Buddy_allowed_to_talk = 0;
1822
1823         Boss_invulnerable_dot = F1_0/4 - i2f(Difficulty_level)/8;
1824         Boss_dying_start_time = 0;
1825 }
1826
1827 int ai_save_state( FILE * fp )
1828 {
1829         fwrite( &Ai_initialized, sizeof(int), 1, fp );
1830         fwrite( &Overall_agitation, sizeof(int), 1, fp );
1831         fwrite( Ai_local_info, sizeof(ai_local)*MAX_OBJECTS, 1, fp );
1832         fwrite( Point_segs, sizeof(point_seg)*MAX_POINT_SEGS, 1, fp );
1833         fwrite( Ai_cloak_info, sizeof(ai_cloak_info)*MAX_AI_CLOAK_INFO, 1, fp );
1834         fwrite( &Boss_cloak_start_time, sizeof(fix), 1, fp );
1835         fwrite( &Boss_cloak_end_time , sizeof(fix), 1, fp );
1836         fwrite( &Last_teleport_time , sizeof(fix), 1, fp );
1837         fwrite( &Boss_teleport_interval, sizeof(fix), 1, fp );
1838         fwrite( &Boss_cloak_interval, sizeof(fix), 1, fp );
1839         fwrite( &Boss_cloak_duration, sizeof(fix), 1, fp );
1840         fwrite( &Last_gate_time, sizeof(fix), 1, fp );
1841         fwrite( &Gate_interval, sizeof(fix), 1, fp );
1842         fwrite( &Boss_dying_start_time, sizeof(fix), 1, fp );
1843         fwrite( &Boss_dying, sizeof(int), 1, fp );
1844         fwrite( &Boss_dying_sound_playing, sizeof(int), 1, fp );
1845         fwrite( &Boss_hit_time, sizeof(fix), 1, fp );
1846         // -- MK, 10/21/95, unused! -- fwrite( &Boss_been_hit, sizeof(int), 1, fp );
1847
1848         fwrite( &Escort_kill_object, sizeof(Escort_kill_object), 1, fp);
1849         fwrite( &Escort_last_path_created, sizeof(Escort_last_path_created), 1, fp);
1850         fwrite( &Escort_goal_object, sizeof(Escort_goal_object), 1, fp);
1851         fwrite( &Escort_special_goal, sizeof(Escort_special_goal), 1, fp);
1852         fwrite( &Escort_goal_index, sizeof(Escort_goal_index), 1, fp);
1853         fwrite( &Stolen_items, sizeof(Stolen_items[0])*MAX_STOLEN_ITEMS, 1, fp);
1854
1855         { int temp;
1856         temp = Point_segs_free_ptr - Point_segs;
1857         fwrite( &temp, sizeof(int), 1, fp );
1858         }
1859
1860         fwrite(&Num_boss_teleport_segs, sizeof(Num_boss_teleport_segs), 1, fp);
1861         fwrite(&Num_boss_gate_segs, sizeof(Num_boss_gate_segs), 1, fp);
1862
1863         if (Num_boss_gate_segs)
1864                 fwrite(Boss_gate_segs, sizeof(Boss_gate_segs[0]), Num_boss_gate_segs, fp);
1865
1866         if (Num_boss_teleport_segs)
1867                 fwrite(Boss_teleport_segs, sizeof(Boss_teleport_segs[0]), Num_boss_teleport_segs, fp);
1868
1869         return 1;
1870 }
1871
1872 int ai_restore_state( FILE * fp, int version )
1873 {
1874         fread( &Ai_initialized, sizeof(int), 1, fp );
1875         fread( &Overall_agitation, sizeof(int), 1, fp );
1876         fread( Ai_local_info, sizeof(ai_local)*MAX_OBJECTS, 1, fp );
1877         fread( Point_segs, sizeof(point_seg)*MAX_POINT_SEGS, 1, fp );
1878         fread( Ai_cloak_info, sizeof(ai_cloak_info)*MAX_AI_CLOAK_INFO, 1, fp );
1879         fread( &Boss_cloak_start_time, sizeof(fix), 1, fp );
1880         fread( &Boss_cloak_end_time , sizeof(fix), 1, fp );
1881         fread( &Last_teleport_time , sizeof(fix), 1, fp );
1882         fread( &Boss_teleport_interval, sizeof(fix), 1, fp );
1883         fread( &Boss_cloak_interval, sizeof(fix), 1, fp );
1884         fread( &Boss_cloak_duration, sizeof(fix), 1, fp );
1885         fread( &Last_gate_time, sizeof(fix), 1, fp );
1886         fread( &Gate_interval, sizeof(fix), 1, fp );
1887         fread( &Boss_dying_start_time, sizeof(fix), 1, fp );
1888         fread( &Boss_dying, sizeof(int), 1, fp );
1889         fread( &Boss_dying_sound_playing, sizeof(int), 1, fp );
1890         fread( &Boss_hit_time, sizeof(fix), 1, fp );
1891         // -- MK, 10/21/95, unused! -- fread( &Boss_been_hit, sizeof(int), 1, fp );
1892
1893         if (version >= 8) {
1894                 fread( &Escort_kill_object, sizeof(Escort_kill_object), 1, fp);
1895                 fread( &Escort_last_path_created, sizeof(Escort_last_path_created), 1, fp);
1896                 fread( &Escort_goal_object, sizeof(Escort_goal_object), 1, fp);
1897                 fread( &Escort_special_goal, sizeof(Escort_special_goal), 1, fp);
1898                 fread( &Escort_goal_index, sizeof(Escort_goal_index), 1, fp);
1899                 fread( &Stolen_items, sizeof(Stolen_items[0])*MAX_STOLEN_ITEMS, 1, fp);
1900         } else {
1901                 int i;
1902
1903                 Escort_kill_object = -1;
1904                 Escort_last_path_created = 0;
1905                 Escort_goal_object = ESCORT_GOAL_UNSPECIFIED;
1906                 Escort_special_goal = -1;
1907                 Escort_goal_index = -1;
1908
1909                 for (i=0; i<MAX_STOLEN_ITEMS; i++) {
1910                         Stolen_items[i] = 255;
1911                 }
1912
1913         }
1914
1915         if (version >= 15) {
1916                 int temp;
1917                 fread( &temp, sizeof(int), 1, fp );
1918                 Point_segs_free_ptr = &Point_segs[temp];
1919         } else
1920                 ai_reset_all_paths();
1921
1922         if (version >= 21) {
1923                 fread(&Num_boss_teleport_segs, sizeof(Num_boss_teleport_segs), 1, fp);
1924                 fread(&Num_boss_gate_segs, sizeof(Num_boss_gate_segs), 1, fp);
1925
1926                 if (Num_boss_gate_segs)
1927                         fread(Boss_gate_segs, sizeof(Boss_gate_segs[0]), Num_boss_gate_segs, fp);
1928
1929                 if (Num_boss_teleport_segs)
1930                         fread(Boss_teleport_segs, sizeof(Boss_teleport_segs[0]), Num_boss_teleport_segs, fp);
1931         } else {
1932                 // -- Num_boss_teleport_segs = 1;
1933                 // -- Num_boss_gate_segs = 1;
1934                 // -- Boss_teleport_segs[0] = 0;
1935                 // -- Boss_gate_segs[0] = 0;
1936                 // Note: Maybe better to leave alone...will probably be ok.
1937                 mprintf((1, "Warning: If you fight the boss, he might teleport to segment #0!\n"));
1938         }
1939
1940         return 1;
1941 }