]> icculus.org git repositories - btb/d2x.git/blob - main/ai.c
use the orientation parameter of g3_draw_bitmap
[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 "dxxerror.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 const ubyte Boss_teleports[NUM_D2_BOSSES]           = {1,1,1,1,1,1, 1,1}; // Set byte if this boss can teleport
68 const ubyte Boss_spew_more[NUM_D2_BOSSES]           = {0,1,0,0,0,0, 0,0}; // If set, 50% of time, spew two bots.
69 const 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 const 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 const 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 const 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 const 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 const char mode_text[18][16] = {
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 const 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 const 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 static const 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         if (Robot_info[obj->id].boss_flag) {
616                         int     pv;
617                         fix     dtp = dist_to_player/4;
618
619                         if (aip->GOAL_STATE == AIS_FLIN)
620                                 aip->GOAL_STATE = AIS_FIRE;
621                         if (aip->CURRENT_STATE == AIS_FLIN)
622                                 aip->CURRENT_STATE = AIS_FIRE;
623
624                         compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
625
626                         pv = player_visibility;
627
628                         // If player cloaked, visibility is screwed up and superboss will gate in robots when not supposed to.
629                         if (Players[Player_num].flags & PLAYER_FLAGS_CLOAKED) {
630                                 pv = 0;
631                                 dtp = vm_vec_dist_quick(&ConsoleObject->pos, &obj->pos)/4;
632                         }
633
634                         if (Robot_info[obj->id].boss_flag == BOSS_SUPER)
635                                 do_super_boss_stuff(obj, dtp, pv);
636                         else
637                                 do_boss_stuff(obj, pv);
638         }
639
640         // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
641         // Time-slice, don't process all the time, purely an efficiency hack.
642         // Guys whose behavior is station and are not at their hide segment get processed anyway.
643         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!
644 #ifndef NDEBUG
645                 if (Break_on_object != objnum) {    // don't time slice if we're interested in this object.
646 #endif
647                         if ((aip->behavior == AIB_STATION) && (ailp->mode == AIM_FOLLOW_PATH) && (aip->hide_segment != obj->segnum)) {
648                                 if (dist_to_player > F1_0*250)  // station guys not at home always processed until 250 units away.
649                                         return;
650                         } else if ((!ailp->previous_visibility) && ((dist_to_player >> 7) > ailp->time_since_processed)) {  // 128 units away (6.4 segments) processed after 1 second.
651                                 if (robptr->thief)
652                                         mprintf((0, "T"));
653                                 return;
654                         }
655 #ifndef NDEBUG
656                 }
657 #endif
658         }
659
660         // Reset time since processed, but skew objects so not everything
661         // processed synchronously, else we get fast frames with the
662         // occasional very slow frame.
663         // AI_proc_time = ailp->time_since_processed;
664         ailp->time_since_processed = - ((objnum & 0x03) * FrameTime ) / 2;
665
666         // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
667         //      Perform special ability
668         switch (obj->id) {
669                 case ROBOT_BRAIN:
670                         // Robots function nicely if behavior is Station.  This
671                         // means they won't move until they can see the player, at
672                         // which time they will start wandering about opening doors.
673                         if (ConsoleObject->segnum == obj->segnum) {
674                                 if (!ai_multiplayer_awareness(obj, 97))
675                                         return;
676                                 compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
677                                 move_away_from_player(obj, &vec_to_player, 0);
678                                 ai_multi_send_robot_position(objnum, -1);
679                         } else if (ailp->mode != AIM_STILL) {
680                                 int r;
681
682                                 r = openable_doors_in_segment(obj->segnum);
683                                 if (r != -1) {
684                                         ailp->mode = AIM_OPEN_DOOR;
685                                         aip->GOALSIDE = r;
686                                 } else if (ailp->mode != AIM_FOLLOW_PATH) {
687                                         if (!ai_multiplayer_awareness(obj, 50))
688                                                 return;
689                                         create_n_segment_path_to_door(obj, 8+Difficulty_level, -1);     // third parameter is avoid_seg, -1 means avoid nothing.
690                                         ai_multi_send_robot_position(objnum, -1);
691                                 }
692
693                                 if (ailp->next_action_time < 0) {
694                                         compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
695                                         if (player_visibility) {
696                                                 make_nearby_robot_snipe();
697                                                 ailp->next_action_time = (NDL - Difficulty_level) * 2*F1_0;
698                                         }
699                                 }
700                         } else {
701                                 compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
702                                 if (player_visibility) {
703                                         if (!ai_multiplayer_awareness(obj, 50))
704                                                 return;
705                                         create_n_segment_path_to_door(obj, 8+Difficulty_level, -1);     // third parameter is avoid_seg, -1 means avoid nothing.
706                                         ai_multi_send_robot_position(objnum, -1);
707                                 }
708                         }
709                         break;
710                 default:
711                         break;
712         }
713
714         if (aip->behavior == AIB_SNIPE) {
715                 if ((Game_mode & GM_MULTI) && !robptr->thief) {
716                         aip->behavior = AIB_NORMAL;
717                         ailp->mode = AIM_CHASE_OBJECT;
718                         return;
719                 }
720
721                 if (!(obj_ref & 3) || previous_visibility) {
722                         compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
723
724                         // If this sniper is in still mode, if he was hit or can see player, switch to snipe mode.
725                         if (ailp->mode == AIM_STILL)
726                                 if (player_visibility || (ailp->player_awareness_type == PA_WEAPON_ROBOT_COLLISION))
727                                         ailp->mode = AIM_SNIPE_ATTACK;
728
729                         if (!robptr->thief && (ailp->mode != AIM_STILL))
730                                 do_snipe_frame(obj, dist_to_player, player_visibility, &vec_to_player);
731                 } else if (!robptr->thief && !robptr->companion)
732                         return;
733         }
734
735         // More special ability stuff, but based on a property of a robot, not its ID.
736         if (robptr->companion) {
737
738                 compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
739                 do_escort_frame(obj, dist_to_player, player_visibility);
740
741                 if (obj->ctype.ai_info.danger_laser_num != -1) {
742                         object *dobjp = &Objects[obj->ctype.ai_info.danger_laser_num];
743
744                         if ((dobjp->type == OBJ_WEAPON) && (dobjp->signature == obj->ctype.ai_info.danger_laser_signature)) {
745                                 fix circle_distance;
746                                 // -- mprintf((0, "Evading!  "));
747                                 circle_distance = robptr->circle_distance[Difficulty_level] + ConsoleObject->size;
748                                 ai_move_relative_to_player(obj, ailp, dist_to_player, &vec_to_player, circle_distance, 1, player_visibility);
749                         }
750                 }
751
752                 if (ready_to_fire(robptr, ailp)) {
753                         int do_stuff = 0;
754                         if (openable_doors_in_segment(obj->segnum) != -1)
755                                 do_stuff = 1;
756                         else if (openable_doors_in_segment(Point_segs[aip->hide_index + aip->cur_path_index + aip->PATH_DIR].segnum) != -1)
757                                 do_stuff = 1;
758                         else if (openable_doors_in_segment(Point_segs[aip->hide_index + aip->cur_path_index + 2*aip->PATH_DIR].segnum) != -1)
759                                 do_stuff = 1;
760                         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)) {
761                                 // mprintf((0, "Firing at player because dot = %7.3f\n", f2fl(vm_vec_dot(&ConsoleObject->orient.fvec, &vec_to_player))));
762                                 do_stuff = 1;
763                         } else
764                                 ; // 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)));
765
766                         if (do_stuff) {
767                                 Laser_create_new_easy( &obj->orient.fvec, &obj->pos, OBJECT_NUMBER(obj), FLARE_ID, 1 );
768                                 ailp->next_fire = F1_0/2;
769                                 if (!Buddy_allowed_to_talk) // If buddy not talking, make him fire flares less often.
770                                         ailp->next_fire += d_rand()*4;
771                         }
772
773                 }
774         }
775
776         if (robptr->thief) {
777
778                 compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
779                 do_thief_frame(obj, dist_to_player, player_visibility, &vec_to_player);
780
781                 if (ready_to_fire(robptr, ailp)) {
782                         int do_stuff = 0;
783                         if (openable_doors_in_segment(obj->segnum) != -1)
784                                 do_stuff = 1;
785                         else if (openable_doors_in_segment(Point_segs[aip->hide_index + aip->cur_path_index + aip->PATH_DIR].segnum) != -1)
786                                 do_stuff = 1;
787                         else if (openable_doors_in_segment(Point_segs[aip->hide_index + aip->cur_path_index + 2*aip->PATH_DIR].segnum) != -1)
788                                 do_stuff = 1;
789
790                         if (do_stuff) {
791                                 // @mk, 05/08/95: Firing flare from center of object, this is dumb...
792                                 Laser_create_new_easy( &obj->orient.fvec, &obj->pos, OBJECT_NUMBER(obj), FLARE_ID, 1 );
793                                 ailp->next_fire = F1_0/2;
794                                 if (Stolen_item_index == 0)     // If never stolen an item, fire flares less often (bad: Stolen_item_index wraps, but big deal)
795                                         ailp->next_fire += d_rand()*4;
796                         }
797                 }
798         }
799
800         // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
801         switch (ailp->mode) {
802                 case AIM_CHASE_OBJECT: {        // chasing player, sort of, chase if far, back off if close, circle in between
803                         fix circle_distance;
804
805                         circle_distance = robptr->circle_distance[Difficulty_level] + ConsoleObject->size;
806                         // Green guy doesn't get his circle distance boosted, else he might never attack.
807                         if (robptr->attack_type != 1)
808                                 circle_distance += (objnum&0xf) * F1_0/2;
809
810                         compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
811
812                         // @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.
813                         if ((player_visibility < 2) && (previous_visibility == 2)) { // this is redundant: mk, 01/15/95: && (ailp->mode == AIM_CHASE_OBJECT)) {
814                                 // -- mprintf((0, "I used to be able to see the player!\n"));
815                                 if (!ai_multiplayer_awareness(obj, 53)) {
816                                         if (maybe_ai_do_actual_firing_stuff(obj, aip))
817                                                 ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates, aip->CURRENT_GUN);
818                                         return;
819                                 }
820                                 // -- mprintf((0, "(3) Object #%i going from chase to player path in frame %i.\n", objnum, FrameCount));
821                                 create_path_to_player(obj, 8, 1);
822                                 ai_multi_send_robot_position(objnum, -1);
823                         } else if ((player_visibility == 0) && (dist_to_player > F1_0*80) && (!(Game_mode & GM_MULTI))) {
824                                 // If pretty far from the player, player cannot be seen
825                                 // (obstructed) and in chase mode, switch to follow path mode.
826                                 // This has one desirable benefit of avoiding physics retries.
827                                 if (aip->behavior == AIB_STATION) {
828                                         ailp->goal_segment = aip->hide_segment;
829                                         // -- mprintf((0, "(1) Object #%i going from chase to STATION in frame %i.\n", objnum, FrameCount));
830                                         create_path_to_station(obj, 15);
831                                 } // -- this looks like a dumb thing to do...robots following paths far away from you! else create_n_segment_path(obj, 5, -1);
832                                 break;
833                         }
834
835                         if ((aip->CURRENT_STATE == AIS_REST) && (aip->GOAL_STATE == AIS_REST)) {
836                                 if (player_visibility) {
837                                         if (d_rand() < FrameTime*player_visibility) {
838                                                 if (dist_to_player/256 < d_rand()*player_visibility) {
839                                                         // mprintf((0, "Object %i searching for player.\n", OBJECT_NUMBER(obj)));
840                                                         aip->GOAL_STATE = AIS_SRCH;
841                                                         aip->CURRENT_STATE = AIS_SRCH;
842                                                 }
843                                         }
844                                 }
845                         }
846
847                         if (GameTime - ailp->time_player_seen > CHASE_TIME_LENGTH) {
848
849                                 if (Game_mode & GM_MULTI)
850                                         if (!player_visibility && (dist_to_player > F1_0*70)) {
851                                                 ailp->mode = AIM_STILL;
852                                                 return;
853                                         }
854
855                                 if (!ai_multiplayer_awareness(obj, 64)) {
856                                         if (maybe_ai_do_actual_firing_stuff(obj, aip))
857                                                 ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates, aip->CURRENT_GUN);
858                                         return;
859                                 }
860                                 // -- 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));
861                                 // -- bad idea, robots charge player they've never seen! -- create_path_to_player(obj, 10, 1);
862                                 // -- bad idea, robots charge player they've never seen! -- ai_multi_send_robot_position(objnum, -1);
863                         } else if ((aip->CURRENT_STATE != AIS_REST) && (aip->GOAL_STATE != AIS_REST)) {
864                                 if (!ai_multiplayer_awareness(obj, 70)) {
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                                 ai_move_relative_to_player(obj, ailp, dist_to_player, &vec_to_player, circle_distance, 0, player_visibility);
870
871                                 if ((obj_ref & 1) && ((aip->GOAL_STATE == AIS_SRCH) || (aip->GOAL_STATE == AIS_LOCK))) {
872                                         if (player_visibility) // == 2)
873                                                 ai_turn_towards_vector(&vec_to_player, obj, robptr->turn_time[Difficulty_level]);
874                                 }
875
876                                 if (ai_evaded) {
877                                         ai_multi_send_robot_position(objnum, 1);
878                                         ai_evaded = 0;
879                                 } else
880                                         ai_multi_send_robot_position(objnum, -1);
881
882                                 do_firing_stuff(obj, player_visibility, &vec_to_player);
883                         }
884                         break;
885                 }
886
887                 case AIM_RUN_FROM_OBJECT:
888                         compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
889
890                         if (player_visibility) {
891                                 if (ailp->player_awareness_type == 0)
892                                         ailp->player_awareness_type = PA_WEAPON_ROBOT_COLLISION;
893
894                         }
895
896                         // If in multiplayer, only do if player visible.  If not multiplayer, do always.
897                         if (!(Game_mode & GM_MULTI) || player_visibility)
898                                 if (ai_multiplayer_awareness(obj, 75)) {
899                                         ai_follow_path(obj, player_visibility, previous_visibility, &vec_to_player);
900                                         ai_multi_send_robot_position(objnum, -1);
901                                 }
902
903                         if (aip->GOAL_STATE != AIS_FLIN)
904                                 aip->GOAL_STATE = AIS_LOCK;
905                         else if (aip->CURRENT_STATE == AIS_FLIN)
906                                 aip->GOAL_STATE = AIS_LOCK;
907
908                         // Bad to let run_from robot fire at player because it
909                         // will cause a war in which it turns towards the player
910                         // to fire and then towards its goal to move.
911                         // do_firing_stuff(obj, player_visibility, &vec_to_player);
912                         // Instead, do this:
913                         // (Note, only drop if player is visible.  This prevents
914                         // the bombs from being a giveaway, and also ensures that
915                         // the robot is moving while it is dropping.  Also means
916                         // fewer will be dropped.)
917                         if ((ailp->next_fire <= 0) && (player_visibility)) {
918                                 vms_vector fire_vec, fire_pos;
919
920                                 if (!ai_multiplayer_awareness(obj, 75))
921                                         return;
922
923                                 fire_vec = obj->orient.fvec;
924                                 vm_vec_negate(&fire_vec);
925                                 vm_vec_add(&fire_pos, &obj->pos, &fire_vec);
926
927                                 if (aip->SUB_FLAGS & SUB_FLAGS_SPROX)
928                                         Laser_create_new_easy( &fire_vec, &fire_pos, OBJECT_NUMBER(obj), ROBOT_SUPERPROX_ID, 1 );
929                                 else
930                                         Laser_create_new_easy( &fire_vec, &fire_pos, OBJECT_NUMBER(obj), PROXIMITY_ID, 1 );
931
932                                 ailp->next_fire = (F1_0/2)*(NDL+5 - Difficulty_level);      // Drop a proximity bomb every 5 seconds.
933
934 #ifdef NETWORK
935 #ifndef SHAREWARE
936                                 if (Game_mode & GM_MULTI)
937                                 {
938                                         ai_multi_send_robot_position(OBJECT_NUMBER(obj), -1);
939                                         if (aip->SUB_FLAGS & SUB_FLAGS_SPROX)
940                                                 multi_send_robot_fire(OBJECT_NUMBER(obj), -2, &fire_vec);
941                                         else
942                                                 multi_send_robot_fire(OBJECT_NUMBER(obj), -1, &fire_vec);
943                                 }
944 #endif
945 #endif
946                         }
947                         break;
948
949                 case AIM_GOTO_PLAYER:
950                 case AIM_GOTO_OBJECT:
951                         ai_follow_path(obj, 2, previous_visibility, &vec_to_player);    // Follows path as if player can see robot.
952                         ai_multi_send_robot_position(objnum, -1);
953                         break;
954
955                 case AIM_FOLLOW_PATH: {
956                         int anger_level = 65;
957
958                         if (aip->behavior == AIB_STATION)
959                                 if (Point_segs[aip->hide_index + aip->path_length - 1].segnum == aip->hide_segment) {
960                                         anger_level = 64;
961                                         // mprintf((0, "Object %i, station, lowering anger to 64.\n"));
962                                 }
963
964                         compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
965
966                         if (Game_mode & (GM_MODEM | GM_SERIAL))
967                                 if (!player_visibility && (dist_to_player > F1_0*70)) {
968                                         ailp->mode = AIM_STILL;
969                                         return;
970                                 }
971
972                         if (!ai_multiplayer_awareness(obj, anger_level)) {
973                                 if (maybe_ai_do_actual_firing_stuff(obj, aip)) {
974                                         compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
975                                         ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates, aip->CURRENT_GUN);
976                                 }
977                                 return;
978                         }
979
980                         ai_follow_path(obj, player_visibility, previous_visibility, &vec_to_player);
981
982                         if (aip->GOAL_STATE != AIS_FLIN)
983                                 aip->GOAL_STATE = AIS_LOCK;
984                         else if (aip->CURRENT_STATE == AIS_FLIN)
985                                 aip->GOAL_STATE = AIS_LOCK;
986
987                         if (aip->behavior != AIB_RUN_FROM)
988                                 do_firing_stuff(obj, player_visibility, &vec_to_player);
989
990                         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)) {
991                                 if (robptr->attack_type == 0)
992                                         ailp->mode = AIM_CHASE_OBJECT;
993                                 // This should not just be distance based, but also time-since-player-seen based.
994                         } else if ((dist_to_player > F1_0*(20*(2*Difficulty_level + robptr->pursuit)))
995                                                 && (GameTime - ailp->time_player_seen > (F1_0/2*(Difficulty_level+robptr->pursuit)))
996                                                 && (player_visibility == 0)
997                                                 && (aip->behavior == AIB_NORMAL)
998                                                 && (ailp->mode == AIM_FOLLOW_PATH)) {
999                                 ailp->mode = AIM_STILL;
1000                                 aip->hide_index = -1;
1001                                 aip->path_length = 0;
1002                         }
1003
1004                         ai_multi_send_robot_position(objnum, -1);
1005
1006                         break;
1007                 }
1008
1009                 case AIM_BEHIND:
1010                         if (!ai_multiplayer_awareness(obj, 71)) {
1011                                 if (maybe_ai_do_actual_firing_stuff(obj, aip)) {
1012                                         compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
1013                                         ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates, aip->CURRENT_GUN);
1014                                 }
1015                                 return;
1016                         }
1017
1018                         compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
1019
1020                         if (player_visibility == 2) {
1021                                 // Get behind the player.
1022                                 // Method:
1023                                 // If vec_to_player dot player_rear_vector > 0, behind is goal.
1024                                 // Else choose goal with larger dot from left, right.
1025                                 vms_vector  goal_point, goal_vector, vec_to_goal, rand_vec;
1026                                 fix         dot;
1027
1028                                 dot = vm_vec_dot(&ConsoleObject->orient.fvec, &vec_to_player);
1029                                 if (dot > 0) {          // Remember, we're interested in the rear vector dot being < 0.
1030                                         goal_vector = ConsoleObject->orient.fvec;
1031                                         vm_vec_negate(&goal_vector);
1032                                         // -- mprintf((0, "Goal is BEHIND\n"));
1033                                 } else {
1034                                         fix dot;
1035                                         dot = vm_vec_dot(&ConsoleObject->orient.rvec, &vec_to_player);
1036                                         goal_vector = ConsoleObject->orient.rvec;
1037                                         if (dot > 0) {
1038                                                 vm_vec_negate(&goal_vector);
1039                                                 // -- mprintf((0, "Goal is LEFT\n"));
1040                                         } else
1041                                                 ; // -- mprintf((0, "Goal is RIGHT\n"));
1042                                 }
1043
1044                                 vm_vec_scale(&goal_vector, 2*(ConsoleObject->size + obj->size + (((objnum*4 + FrameCount) & 63) << 12)));
1045                                 vm_vec_add(&goal_point, &ConsoleObject->pos, &goal_vector);
1046                                 make_random_vector(&rand_vec);
1047                                 vm_vec_scale_add2(&goal_point, &rand_vec, F1_0*8);
1048                                 vm_vec_sub(&vec_to_goal, &goal_point, &obj->pos);
1049                                 vm_vec_normalize_quick(&vec_to_goal);
1050                                 move_towards_vector(obj, &vec_to_goal, 0);
1051                                 ai_turn_towards_vector(&vec_to_player, obj, robptr->turn_time[Difficulty_level]);
1052                                 ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates, aip->CURRENT_GUN);
1053                         }
1054
1055                         if (aip->GOAL_STATE != AIS_FLIN)
1056                                 aip->GOAL_STATE = AIS_LOCK;
1057                         else if (aip->CURRENT_STATE == AIS_FLIN)
1058                                 aip->GOAL_STATE = AIS_LOCK;
1059
1060                         ai_multi_send_robot_position(objnum, -1);
1061                         break;
1062
1063                 case AIM_STILL:
1064                         if ((dist_to_player < F1_0*120+Difficulty_level*F1_0*20) || (ailp->player_awareness_type >= PA_WEAPON_ROBOT_COLLISION-1)) {
1065                                 compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
1066
1067                                 // turn towards vector if visible this time or last time, or rand
1068                                 // new!
1069                                 if ((player_visibility == 2) || (previous_visibility == 2)) { // -- MK, 06/09/95:  || ((d_rand() > 0x4000) && !(Game_mode & GM_MULTI))) {
1070                                         if (!ai_multiplayer_awareness(obj, 71)) {
1071                                                 if (maybe_ai_do_actual_firing_stuff(obj, aip))
1072                                                         ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates, aip->CURRENT_GUN);
1073                                                 return;
1074                                         }
1075                                         ai_turn_towards_vector(&vec_to_player, obj, robptr->turn_time[Difficulty_level]);
1076                                         ai_multi_send_robot_position(objnum, -1);
1077                                 }
1078
1079                                 do_firing_stuff(obj, player_visibility, &vec_to_player);
1080                                 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.
1081                                         if (robptr->attack_type == 1) {
1082                                                 aip->behavior = AIB_NORMAL;
1083                                                 if (!ai_multiplayer_awareness(obj, 80)) {
1084                                                         if (maybe_ai_do_actual_firing_stuff(obj, aip))
1085                                                                 ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates, aip->CURRENT_GUN);
1086                                                         return;
1087                                                 }
1088                                                 ai_move_relative_to_player(obj, ailp, dist_to_player, &vec_to_player, 0, 0, player_visibility);
1089                                                 if (ai_evaded) {
1090                                                         ai_multi_send_robot_position(objnum, 1);
1091                                                         ai_evaded = 0;
1092                                                 }
1093                                                 else
1094                                                         ai_multi_send_robot_position(objnum, -1);
1095                                         } else {
1096                                                 // Robots in hover mode are allowed to evade at half normal speed.
1097                                                 if (!ai_multiplayer_awareness(obj, 81)) {
1098                                                         if (maybe_ai_do_actual_firing_stuff(obj, aip))
1099                                                                 ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates, aip->CURRENT_GUN);
1100                                                         return;
1101                                                 }
1102                                                 ai_move_relative_to_player(obj, ailp, dist_to_player, &vec_to_player, 0, 1, player_visibility);
1103                                                 if (ai_evaded) {
1104                                                         ai_multi_send_robot_position(objnum, -1);
1105                                                         ai_evaded = 0;
1106                                                 }
1107                                                 else
1108                                                         ai_multi_send_robot_position(objnum, -1);
1109                                         }
1110                                 } else if ((obj->segnum != aip->hide_segment) && (dist_to_player > F1_0*80) && (!(Game_mode & GM_MULTI))) {
1111                                         // If pretty far from the player, player cannot be
1112                                         // seen (obstructed) and in chase mode, switch to
1113                                         // follow path mode.
1114                                         // This has one desirable benefit of avoiding physics retries.
1115                                         if (aip->behavior == AIB_STATION) {
1116                                                 ailp->goal_segment = aip->hide_segment;
1117                                                 // -- mprintf((0, "(2) Object #%i going from STILL to STATION in frame %i.\n", objnum, FrameCount));
1118                                                 create_path_to_station(obj, 15);
1119                                         }
1120                                         break;
1121                                 }
1122                         }
1123
1124                         break;
1125                 case AIM_OPEN_DOOR: {       // trying to open a door.
1126                         vms_vector center_point, goal_vector;
1127                         Assert(obj->id == ROBOT_BRAIN);     // Make sure this guy is allowed to be in this mode.
1128
1129                         if (!ai_multiplayer_awareness(obj, 62))
1130                                 return;
1131                         compute_center_point_on_side(&center_point, &Segments[obj->segnum], aip->GOALSIDE);
1132                         vm_vec_sub(&goal_vector, &center_point, &obj->pos);
1133                         vm_vec_normalize_quick(&goal_vector);
1134                         ai_turn_towards_vector(&goal_vector, obj, robptr->turn_time[Difficulty_level]);
1135                         move_towards_vector(obj, &goal_vector, 0);
1136                         ai_multi_send_robot_position(objnum, -1);
1137
1138                         break;
1139                 }
1140
1141                 case AIM_SNIPE_WAIT:
1142                         break;
1143                 case AIM_SNIPE_RETREAT:
1144                         // -- if (ai_multiplayer_awareness(obj, 53))
1145                         // --   if (ailp->next_fire < -F1_0)
1146                         // --           ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates, aip->CURRENT_GUN);
1147                         break;
1148                 case AIM_SNIPE_RETREAT_BACKWARDS:
1149                 case AIM_SNIPE_ATTACK:
1150                 case AIM_SNIPE_FIRE:
1151                         if (ai_multiplayer_awareness(obj, 53)) {
1152                                 ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates, aip->CURRENT_GUN);
1153                                 if (robptr->thief)
1154                                         ai_move_relative_to_player(obj, ailp, dist_to_player, &vec_to_player, 0, 0, player_visibility);
1155                                 break;
1156                         }
1157                         break;
1158
1159                 case AIM_THIEF_WAIT:
1160                 case AIM_THIEF_ATTACK:
1161                 case AIM_THIEF_RETREAT:
1162                 case AIM_WANDER:    // Used for Buddy Bot
1163                         break;
1164
1165                 default:
1166                         mprintf((0, "Unknown mode = %i in robot %i, behavior = %i\n", ailp->mode, OBJECT_NUMBER(obj), aip->behavior));
1167                         ailp->mode = AIM_CHASE_OBJECT;
1168                         break;
1169         }       // end: switch (ailp->mode) {
1170
1171         // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
1172         // If the robot can see you, increase his awareness of you.
1173         // This prevents the problem of a robot looking right at you but doing nothing.
1174         // Assert(player_visibility != -1); // Means it didn't get initialized!
1175         compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
1176         if ((player_visibility == 2) && (aip->behavior != AIB_FOLLOW) && (!robptr->thief)) {
1177                 if ((ailp->player_awareness_type == 0) && (aip->SUB_FLAGS & SUB_FLAGS_CAMERA_AWAKE))
1178                         aip->SUB_FLAGS &= ~SUB_FLAGS_CAMERA_AWAKE;
1179                 else if (ailp->player_awareness_type == 0)
1180                         ailp->player_awareness_type = PA_PLAYER_COLLISION;
1181         }
1182
1183         // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
1184         if (!object_animates) {
1185                 aip->CURRENT_STATE = aip->GOAL_STATE;
1186                 // mprintf((0, "Setting current to goal (%i) because object doesn't animate.\n", aip->GOAL_STATE));
1187         }
1188
1189         Assert(ailp->player_awareness_type <= AIE_MAX);
1190         Assert(aip->CURRENT_STATE < AIS_MAX);
1191         Assert(aip->GOAL_STATE < AIS_MAX);
1192
1193         // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
1194         if (ailp->player_awareness_type) {
1195                 new_goal_state = Ai_transition_table[ailp->player_awareness_type-1][aip->CURRENT_STATE][aip->GOAL_STATE];
1196                 if (ailp->player_awareness_type == PA_WEAPON_ROBOT_COLLISION) {
1197                         // Decrease awareness, else this robot will flinch every frame.
1198                         ailp->player_awareness_type--;
1199                         ailp->player_awareness_time = F1_0*3;
1200                 }
1201
1202                 if (new_goal_state == AIS_ERR_)
1203                         new_goal_state = AIS_REST;
1204
1205                 if (aip->CURRENT_STATE == AIS_NONE)
1206                         aip->CURRENT_STATE = AIS_REST;
1207
1208                 aip->GOAL_STATE = new_goal_state;
1209
1210         }
1211
1212         // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
1213         // If new state = fire, then set all gun states to fire.
1214         if ( aip->GOAL_STATE == AIS_FIRE ) {
1215                 int i,num_guns;
1216                 num_guns = Robot_info[obj->id].n_guns;
1217                 for (i=0; i<num_guns; i++)
1218                         ailp->goal_state[i] = AIS_FIRE;
1219         }
1220
1221         // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
1222         // 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
1223         if (ready_to_fire(robptr, ailp) && (aip->GOAL_STATE == AIS_FIRE))
1224                 aip->CURRENT_STATE = AIS_FIRE;
1225
1226         if ((aip->GOAL_STATE != AIS_FLIN)  && (obj->id != ROBOT_BRAIN)) {
1227                 switch (aip->CURRENT_STATE) {
1228                 case AIS_NONE:
1229                         compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
1230
1231                         dot = vm_vec_dot(&obj->orient.fvec, &vec_to_player);
1232                         if (dot >= F1_0/2)
1233                                 if (aip->GOAL_STATE == AIS_REST)
1234                                         aip->GOAL_STATE = AIS_SRCH;
1235                         break;
1236                 case AIS_REST:
1237                         if (aip->GOAL_STATE == AIS_REST) {
1238                                 compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
1239                                 if (ready_to_fire(robptr, ailp) && (player_visibility)) {
1240                                         // mprintf((0, "Setting goal state to fire from rest.\n"));
1241                                         aip->GOAL_STATE = AIS_FIRE;
1242                                 }
1243                         }
1244                         break;
1245                 case AIS_SRCH:
1246                         if (!ai_multiplayer_awareness(obj, 60))
1247                                 return;
1248
1249                         compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
1250
1251                         if (player_visibility == 2) {
1252                                 ai_turn_towards_vector(&vec_to_player, obj, robptr->turn_time[Difficulty_level]);
1253                                 ai_multi_send_robot_position(objnum, -1);
1254                         }
1255                         break;
1256                 case AIS_LOCK:
1257                         compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
1258
1259                         if (!(Game_mode & GM_MULTI) || (player_visibility)) {
1260                                 if (!ai_multiplayer_awareness(obj, 68))
1261                                         return;
1262
1263                                 if (player_visibility == 2) {   // @mk, 09/21/95, require that they be looking towards you to turn towards you.
1264                                         ai_turn_towards_vector(&vec_to_player, obj, robptr->turn_time[Difficulty_level]);
1265                                         ai_multi_send_robot_position(objnum, -1);
1266                                 }
1267                         }
1268                         break;
1269                 case AIS_FIRE:
1270                         compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
1271
1272                         if (player_visibility == 2) {
1273                                 if (!ai_multiplayer_awareness(obj, (ROBOT_FIRE_AGITATION-1))) {
1274                                         if (Game_mode & GM_MULTI) {
1275                                                 ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates, aip->CURRENT_GUN);
1276                                                 return;
1277                                         }
1278                                 }
1279                                 ai_turn_towards_vector(&vec_to_player, obj, robptr->turn_time[Difficulty_level]);
1280                                 ai_multi_send_robot_position(objnum, -1);
1281                         }
1282
1283                         // Fire at player, if appropriate.
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
1286                         break;
1287                 case AIS_RECO:
1288                         if (!(obj_ref & 3)) {
1289                                 compute_vis_and_vec(obj, &vis_vec_pos, ailp, &vec_to_player, &player_visibility, robptr, &visibility_and_vec_computed);
1290                                 if (player_visibility == 2) {
1291                                         if (!ai_multiplayer_awareness(obj, 69))
1292                                                 return;
1293                                         ai_turn_towards_vector(&vec_to_player, obj, robptr->turn_time[Difficulty_level]);
1294                                         ai_multi_send_robot_position(objnum, -1);
1295                                 } // -- MK, 06/09/95: else if (!(Game_mode & GM_MULTI)) {
1296                         }
1297                         break;
1298                 case AIS_FLIN:
1299                         // mprintf((0, "State = flinch, goal = %i.\n", aip->GOAL_STATE));
1300                         break;
1301                 default:
1302                         mprintf((1, "Unknown mode for AI object #%i\n", objnum));
1303                         aip->GOAL_STATE = AIS_REST;
1304                         aip->CURRENT_STATE = AIS_REST;
1305                         break;
1306                 }
1307         } // end of: if (aip->GOAL_STATE != AIS_FLIN) {
1308
1309         // Switch to next gun for next fire.
1310         if (player_visibility == 0) {
1311                 aip->CURRENT_GUN++;
1312                 if (aip->CURRENT_GUN >= Robot_info[obj->id].n_guns)
1313                 {
1314                         if ((robptr->n_guns == 1) || (robptr->weapon_type2 == -1))  // Two weapon types hack.
1315                                 aip->CURRENT_GUN = 0;
1316                         else
1317                                 aip->CURRENT_GUN = 1;
1318                 }
1319         }
1320
1321 }
1322
1323 // ----------------------------------------------------------------------------
1324 void ai_do_cloak_stuff(void)
1325 {
1326         int i;
1327
1328         for (i=0; i<MAX_AI_CLOAK_INFO; i++) {
1329                 Ai_cloak_info[i].last_position = ConsoleObject->pos;
1330                 Ai_cloak_info[i].last_segment = ConsoleObject->segnum;
1331                 Ai_cloak_info[i].last_time = GameTime;
1332         }
1333
1334         // Make work for control centers.
1335         Believed_player_pos = Ai_cloak_info[0].last_position;
1336         Believed_player_seg = Ai_cloak_info[0].last_segment;
1337
1338 }
1339
1340 // ----------------------------------------------------------------------------
1341 // Returns false if awareness is considered too puny to add, else returns true.
1342 int add_awareness_event(object *objp, int type)
1343 {
1344         // If player cloaked and hit a robot, then increase awareness
1345         if ((type == PA_WEAPON_ROBOT_COLLISION) || (type == PA_WEAPON_WALL_COLLISION) || (type == PA_PLAYER_COLLISION))
1346                 ai_do_cloak_stuff();
1347
1348         if (Num_awareness_events < MAX_AWARENESS_EVENTS) {
1349                 if ((type == PA_WEAPON_WALL_COLLISION) || (type == PA_WEAPON_ROBOT_COLLISION))
1350                         if (objp->id == VULCAN_ID)
1351                                 if (d_rand() > 3276)
1352                                         return 0;       // For vulcan cannon, only about 1/10 actually cause awareness
1353
1354                 Awareness_events[Num_awareness_events].segnum = objp->segnum;
1355                 Awareness_events[Num_awareness_events].pos = objp->pos;
1356                 Awareness_events[Num_awareness_events].type = type;
1357                 Num_awareness_events++;
1358         } else {
1359                 //Int3();   // Hey -- Overflowed Awareness_events, make more or something
1360                 // This just gets ignored, so you can just
1361                 // continue.
1362         }
1363         return 1;
1364
1365 }
1366
1367 // ----------------------------------------------------------------------------------
1368 // Robots will become aware of the player based on something that occurred.
1369 // The object (probably player or weapon) which created the awareness is objp.
1370 void create_awareness_event(object *objp, int type)
1371 {
1372         // If not in multiplayer, or in multiplayer with robots, do this, else unnecessary!
1373         if (!(Game_mode & GM_MULTI) || (Game_mode & GM_MULTI_ROBOTS)) {
1374                 if (add_awareness_event(objp, type)) {
1375                         if (((d_rand() * (type+4)) >> 15) > 4)
1376                                 Overall_agitation++;
1377                         if (Overall_agitation > OVERALL_AGITATION_MAX)
1378                                 Overall_agitation = OVERALL_AGITATION_MAX;
1379                 }
1380         }
1381 }
1382
1383 sbyte New_awareness[MAX_SEGMENTS];
1384
1385 // ----------------------------------------------------------------------------------
1386 void pae_aux(int segnum, int type, int level)
1387 {
1388         int j;
1389
1390         if (New_awareness[segnum] < type)
1391                 New_awareness[segnum] = type;
1392
1393         // Process children.
1394         for (j=0; j<MAX_SIDES_PER_SEGMENT; j++)
1395                 if (IS_CHILD(Segments[segnum].children[j]))
1396                 {
1397                         if (level <= 3)
1398                         {
1399                                 if (type == 4)
1400                                         pae_aux(Segments[segnum].children[j], type-1, level+1);
1401                                 else
1402                                         pae_aux(Segments[segnum].children[j], type, level+1);
1403                         }
1404                 }
1405 }
1406
1407
1408 // ----------------------------------------------------------------------------------
1409 void process_awareness_events(void)
1410 {
1411         int i;
1412
1413         if (!(Game_mode & GM_MULTI) || (Game_mode & GM_MULTI_ROBOTS)) {
1414                 memset(New_awareness, 0, sizeof(New_awareness[0]) * (Highest_segment_index+1));
1415
1416                 for (i=0; i<Num_awareness_events; i++)
1417                         pae_aux(Awareness_events[i].segnum, Awareness_events[i].type, 1);
1418
1419         }
1420
1421         Num_awareness_events = 0;
1422 }
1423
1424 // ----------------------------------------------------------------------------------
1425 void set_player_awareness_all(void)
1426 {
1427         int i;
1428
1429         process_awareness_events();
1430
1431         for (i=0; i<=Highest_object_index; i++)
1432                 if (Objects[i].control_type == CT_AI) {
1433                         if (New_awareness[Objects[i].segnum] > Ai_local_info[i].player_awareness_type) {
1434                                 Ai_local_info[i].player_awareness_type = New_awareness[Objects[i].segnum];
1435                                 Ai_local_info[i].player_awareness_time = PLAYER_AWARENESS_INITIAL_TIME;
1436                         }
1437
1438                         // Clear the bit that says this robot is only awake because a camera woke it up.
1439                         if (New_awareness[Objects[i].segnum] > Ai_local_info[i].player_awareness_type)
1440                                 Objects[i].ctype.ai_info.SUB_FLAGS &= ~SUB_FLAGS_CAMERA_AWAKE;
1441                 }
1442 }
1443
1444 #ifndef NDEBUG
1445 int Ai_dump_enable = 0;
1446
1447 FILE *Ai_dump_file = NULL;
1448
1449 char Ai_error_message[128] = "";
1450
1451 // ----------------------------------------------------------------------------------
1452 void force_dump_ai_objects_all(char *msg)
1453 {
1454         int tsave;
1455
1456         tsave = Ai_dump_enable;
1457
1458         Ai_dump_enable = 1;
1459
1460         sprintf(Ai_error_message, "%s\n", msg);
1461         //dump_ai_objects_all();
1462         Ai_error_message[0] = 0;
1463
1464         Ai_dump_enable = tsave;
1465 }
1466
1467 // ----------------------------------------------------------------------------------
1468 void turn_off_ai_dump(void)
1469 {
1470         if (Ai_dump_file != NULL)
1471                 fclose(Ai_dump_file);
1472
1473         Ai_dump_file = NULL;
1474 }
1475
1476 #endif
1477
1478 extern void do_boss_dying_frame(object *objp);
1479
1480 // ----------------------------------------------------------------------------------
1481 // Do things which need to get done for all AI objects each frame.
1482 // This includes:
1483 //  Setting player_awareness (a fix, time in seconds which object is aware of player)
1484 void do_ai_frame_all(void)
1485 {
1486 #ifndef NDEBUG
1487         //dump_ai_objects_all();
1488 #endif
1489
1490         set_player_awareness_all();
1491
1492         if (Ai_last_missile_camera != -1) {
1493                 // Clear if supposed misisle camera is not a weapon, or just every so often, just in case.
1494                 if (((FrameCount & 0x0f) == 0) || (Objects[Ai_last_missile_camera].type != OBJ_WEAPON)) {
1495                         int i;
1496
1497                         Ai_last_missile_camera = -1;
1498                         for (i=0; i<=Highest_object_index; i++)
1499                                 if (Objects[i].type == OBJ_ROBOT)
1500                                         Objects[i].ctype.ai_info.SUB_FLAGS &= ~SUB_FLAGS_CAMERA_AWAKE;
1501                 }
1502         }
1503
1504         // (Moved here from do_boss_stuff() because that only gets called if robot aware of player.)
1505         if (Boss_dying) {
1506                 int i;
1507
1508                 for (i=0; i<=Highest_object_index; i++)
1509                         if (Objects[i].type == OBJ_ROBOT)
1510                                 if (Robot_info[Objects[i].id].boss_flag)
1511                                         do_boss_dying_frame(&Objects[i]);
1512         }
1513 }
1514
1515
1516 extern int Final_boss_is_dead;
1517 extern fix Boss_invulnerable_dot;
1518
1519 // Initializations to be performed for all robots for a new level.
1520 void init_robots_for_level(void)
1521 {
1522         Overall_agitation = 0;
1523         Final_boss_is_dead=0;
1524
1525         Buddy_objnum = 0;
1526         Buddy_allowed_to_talk = 0;
1527
1528         Boss_invulnerable_dot = F1_0/4 - i2f(Difficulty_level)/8;
1529         Boss_dying_start_time = 0;
1530 }
1531
1532 int ai_save_state(PHYSFS_file *fp)
1533 {
1534         PHYSFS_write(fp, &Ai_initialized, sizeof(int), 1);
1535         PHYSFS_write(fp, &Overall_agitation, sizeof(int), 1);
1536         PHYSFS_write(fp, Ai_local_info, sizeof(ai_local) * MAX_OBJECTS, 1);
1537         PHYSFS_write(fp, Point_segs, sizeof(point_seg) * MAX_POINT_SEGS, 1);
1538         PHYSFS_write(fp, Ai_cloak_info, sizeof(ai_cloak_info) * MAX_AI_CLOAK_INFO, 1);
1539         PHYSFS_write(fp, &Boss_cloak_start_time, sizeof(fix), 1);
1540         PHYSFS_write(fp, &Boss_cloak_end_time, sizeof(fix), 1);
1541         PHYSFS_write(fp, &Last_teleport_time, sizeof(fix), 1);
1542         PHYSFS_write(fp, &Boss_teleport_interval, sizeof(fix), 1);
1543         PHYSFS_write(fp, &Boss_cloak_interval, sizeof(fix), 1);
1544         PHYSFS_write(fp, &Boss_cloak_duration, sizeof(fix), 1);
1545         PHYSFS_write(fp, &Last_gate_time, sizeof(fix), 1);
1546         PHYSFS_write(fp, &Gate_interval, sizeof(fix), 1);
1547         PHYSFS_write(fp, &Boss_dying_start_time, sizeof(fix), 1);
1548         PHYSFS_write(fp, &Boss_dying, sizeof(int), 1);
1549         PHYSFS_write(fp, &Boss_dying_sound_playing, sizeof(int), 1);
1550         PHYSFS_write(fp, &Boss_hit_time, sizeof(fix), 1);
1551         // -- MK, 10/21/95, unused! -- PHYSFS_write(fp, &Boss_been_hit, sizeof(int), 1);
1552
1553         PHYSFS_write(fp, &Escort_kill_object, sizeof(Escort_kill_object), 1);
1554         PHYSFS_write(fp, &Escort_last_path_created, sizeof(Escort_last_path_created), 1);
1555         PHYSFS_write(fp, &Escort_goal_object, sizeof(Escort_goal_object), 1);
1556         PHYSFS_write(fp, &Escort_special_goal, sizeof(Escort_special_goal), 1);
1557         PHYSFS_write(fp, &Escort_goal_index, sizeof(Escort_goal_index), 1);
1558         PHYSFS_write(fp, &Stolen_items, sizeof(Stolen_items[0])*MAX_STOLEN_ITEMS, 1);
1559
1560         {
1561                 int temp;
1562                 temp = POINT_SEG_NUMBER(Point_segs_free_ptr);
1563                 PHYSFS_write(fp, &temp, sizeof(int), 1);
1564         }
1565
1566         PHYSFS_write(fp, &Num_boss_teleport_segs, sizeof(Num_boss_teleport_segs), 1);
1567         PHYSFS_write(fp, &Num_boss_gate_segs, sizeof(Num_boss_gate_segs), 1);
1568
1569         if (Num_boss_gate_segs)
1570                 PHYSFS_write(fp, Boss_gate_segs, sizeof(Boss_gate_segs[0]), Num_boss_gate_segs);
1571
1572         if (Num_boss_teleport_segs)
1573                 PHYSFS_write(fp, Boss_teleport_segs, sizeof(Boss_teleport_segs[0]), Num_boss_teleport_segs);
1574
1575         return 1;
1576 }
1577
1578 int ai_restore_state(PHYSFS_file *fp, int version)
1579 {
1580         PHYSFS_read(fp, &Ai_initialized, sizeof(int), 1);
1581         PHYSFS_read(fp, &Overall_agitation, sizeof(int), 1);
1582         PHYSFS_read(fp, Ai_local_info, sizeof(ai_local) * MAX_OBJECTS, 1);
1583         PHYSFS_read(fp, Point_segs, sizeof(point_seg) * MAX_POINT_SEGS, 1);
1584         PHYSFS_read(fp, Ai_cloak_info, sizeof(ai_cloak_info) * MAX_AI_CLOAK_INFO, 1);
1585         PHYSFS_read(fp, &Boss_cloak_start_time, sizeof(fix), 1);
1586         PHYSFS_read(fp, &Boss_cloak_end_time, sizeof(fix), 1);
1587         PHYSFS_read(fp, &Last_teleport_time, sizeof(fix), 1);
1588         PHYSFS_read(fp, &Boss_teleport_interval, sizeof(fix), 1);
1589         PHYSFS_read(fp, &Boss_cloak_interval, sizeof(fix), 1);
1590         PHYSFS_read(fp, &Boss_cloak_duration, sizeof(fix), 1);
1591         PHYSFS_read(fp, &Last_gate_time, sizeof(fix), 1);
1592         PHYSFS_read(fp, &Gate_interval, sizeof(fix), 1);
1593         PHYSFS_read(fp, &Boss_dying_start_time, sizeof(fix), 1);
1594         PHYSFS_read(fp, &Boss_dying, sizeof(int), 1);
1595         PHYSFS_read(fp, &Boss_dying_sound_playing, sizeof(int), 1);
1596         PHYSFS_read(fp, &Boss_hit_time, sizeof(fix), 1);
1597         // -- MK, 10/21/95, unused! -- PHYSFS_read(fp, &Boss_been_hit, sizeof(int), 1);
1598
1599         if (version >= 8) {
1600                 PHYSFS_read(fp, &Escort_kill_object, sizeof(Escort_kill_object), 1);
1601                 PHYSFS_read(fp, &Escort_last_path_created, sizeof(Escort_last_path_created), 1);
1602                 PHYSFS_read(fp, &Escort_goal_object, sizeof(Escort_goal_object), 1);
1603                 PHYSFS_read(fp, &Escort_special_goal, sizeof(Escort_special_goal), 1);
1604                 PHYSFS_read(fp, &Escort_goal_index, sizeof(Escort_goal_index), 1);
1605                 PHYSFS_read(fp, &Stolen_items, sizeof(Stolen_items[0]) * MAX_STOLEN_ITEMS, 1);
1606         } else {
1607                 int i;
1608
1609                 Escort_kill_object = -1;
1610                 Escort_last_path_created = 0;
1611                 Escort_goal_object = ESCORT_GOAL_UNSPECIFIED;
1612                 Escort_special_goal = -1;
1613                 Escort_goal_index = -1;
1614
1615                 for (i=0; i<MAX_STOLEN_ITEMS; i++) {
1616                         Stolen_items[i] = 255;
1617                 }
1618
1619         }
1620
1621         if (version >= 15) {
1622                 int temp;
1623                 PHYSFS_read(fp, &temp, sizeof(int), 1);
1624                 Point_segs_free_ptr = &Point_segs[temp];
1625         } else
1626                 ai_reset_all_paths();
1627
1628         if (version >= 21) {
1629                 PHYSFS_read(fp, &Num_boss_teleport_segs, sizeof(Num_boss_teleport_segs), 1);
1630                 PHYSFS_read(fp, &Num_boss_gate_segs, sizeof(Num_boss_gate_segs), 1);
1631
1632                 if (Num_boss_gate_segs)
1633                         PHYSFS_read(fp, Boss_gate_segs, sizeof(Boss_gate_segs[0]), Num_boss_gate_segs);
1634
1635                 if (Num_boss_teleport_segs)
1636                         PHYSFS_read(fp, Boss_teleport_segs, sizeof(Boss_teleport_segs[0]), Num_boss_teleport_segs);
1637         } else {
1638                 // -- Num_boss_teleport_segs = 1;
1639                 // -- Num_boss_gate_segs = 1;
1640                 // -- Boss_teleport_segs[0] = 0;
1641                 // -- Boss_gate_segs[0] = 0;
1642                 // Note: Maybe better to leave alone...will probably be ok.
1643                 mprintf((1, "Warning: If you fight the boss, he might teleport to segment #0!\n"));
1644         }
1645
1646         return 1;
1647 }