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