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