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