]> icculus.org git repositories - btb/d2x.git/blob - main/escort.c
cvars for weapon ordering
[btb/d2x.git] / main / escort.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  * Escort robot behavior.
17  *
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #include <conf.h>
22 #endif
23
24 #include <stdio.h>              // for printf()
25 #include <stdlib.h>             // for rand() and qsort()
26 #include <string.h>             // for memset()
27
28 #include "inferno.h"
29 #include "mono.h"
30 #include "fix.h"
31 #include "vecmat.h"
32 #include "gr.h"
33 #include "3d.h"
34 #include "palette.h"
35
36 #include "object.h"
37 #include "error.h"
38 #include "ai.h"
39 #include "robot.h"
40 #include "fvi.h"
41 #include "physics.h"
42 #include "wall.h"
43 #include "player.h"
44 #include "fireball.h"
45 #include "game.h"
46 #include "powerup.h"
47 #include "cntrlcen.h"
48 #include "gauges.h"
49 #include "key.h"
50 #include "fuelcen.h"
51 #include "sounds.h"
52 #include "screens.h"
53 #include "text.h"
54 #include "gamefont.h"
55 #include "newmenu.h"
56 #include "playsave.h"
57 #include "gameseq.h"
58 #include "automap.h"
59 #include "laser.h"
60 #include "escort.h"
61
62 #ifdef EDITOR
63 #include "editor/editor.h"
64 #endif
65
66 extern void multi_send_stolen_items();
67 void say_escort_goal(int goal_num);
68 void show_escort_menu(char *msg);
69
70
71 char *Escort_goal_text[MAX_ESCORT_GOALS] = {
72         "BLUE KEY",
73         "YELLOW KEY",
74         "RED KEY",
75         "REACTOR",
76         "EXIT",
77         "ENERGY",
78         "ENERGYCEN",
79         "SHIELD",
80         "POWERUP",
81         "ROBOT",
82         "HOSTAGES",
83         "SPEW",
84         "SCRAM",
85         "EXIT",
86         "BOSS",
87         "MARKER 1",
88         "MARKER 2",
89         "MARKER 3",
90         "MARKER 4",
91         "MARKER 5",
92         "MARKER 6",
93         "MARKER 7",
94         "MARKER 8",
95         "MARKER 9",
96 // -- too much work --  "KAMIKAZE  "
97 };
98
99 int     Max_escort_length = 200;
100 int     Escort_kill_object = -1;
101 ubyte Stolen_items[MAX_STOLEN_ITEMS];
102 int     Stolen_item_index;
103 fix     Escort_last_path_created = 0;
104 int     Escort_goal_object = ESCORT_GOAL_UNSPECIFIED, Escort_special_goal = -1, Escort_goal_index = -1, Buddy_messages_suppressed = 0;
105 fix     Buddy_sorry_time;
106 int     Buddy_objnum, Buddy_allowed_to_talk;
107 int     Looking_for_marker;
108 int     Last_buddy_key;
109
110 fix     Last_buddy_message_time;
111
112 char guidebot_name[GUIDEBOT_NAME_LEN+1] = "GUIDE-BOT";
113 cvar_t real_guidebot_name = { "guidebot_name", "GUIDE-BOT", 1 };
114
115 void init_buddy_for_level(void)
116 {
117         int     i;
118
119         Buddy_allowed_to_talk = 0;
120         Buddy_objnum = -1;
121         Escort_goal_object = ESCORT_GOAL_UNSPECIFIED;
122         Escort_special_goal = -1;
123         Escort_goal_index = -1;
124         Buddy_messages_suppressed = 0;
125
126         for (i=0; i<=Highest_object_index; i++)
127                 if (Robot_info[Objects[i].id].companion)
128                         break;
129         if (i <= Highest_object_index)
130                 Buddy_objnum = i;
131
132         Buddy_sorry_time = -F1_0;
133
134         Looking_for_marker = -1;
135         Last_buddy_key = -1;
136 }
137
138 //      -----------------------------------------------------------------------------
139 //      See if segment from curseg through sidenum is reachable.
140 //      Return true if it is reachable, else return false.
141 int segment_is_reachable(int curseg, int sidenum)
142 {
143         int             wall_num, rval;
144         segment *segp = &Segments[curseg];
145
146         if (!IS_CHILD(segp->children[sidenum]))
147                 return 0;
148
149         wall_num = segp->sides[sidenum].wall_num;
150
151         //      If no wall, then it is reachable
152         if (wall_num == -1)
153                 return 1;
154
155         rval = ai_door_is_openable(NULL, segp, sidenum);
156
157         return rval;
158
159 // -- MK, 10/17/95 -- 
160 // -- MK, 10/17/95 --   //      Hmm, a closed wall.  I think this mean not reachable.
161 // -- MK, 10/17/95 --   if (Walls[wall_num].type == WALL_CLOSED)
162 // -- MK, 10/17/95 --           return 0;
163 // -- MK, 10/17/95 -- 
164 // -- MK, 10/17/95 --   if (Walls[wall_num].type == WALL_DOOR) {
165 // -- MK, 10/17/95 --           if (Walls[wall_num].keys == KEY_NONE) {
166 // -- MK, 10/17/95 --                   return 1;               //      @MK, 10/17/95: Be consistent with ai_door_is_openable
167 // -- MK, 10/17/95 -- // --                     if (Walls[wall_num].flags & WALL_DOOR_LOCKED)
168 // -- MK, 10/17/95 -- // --                             return 0;
169 // -- MK, 10/17/95 -- // --                     else
170 // -- MK, 10/17/95 -- // --                             return 1;
171 // -- MK, 10/17/95 --           } else if (Walls[wall_num].keys == KEY_BLUE)
172 // -- MK, 10/17/95 --                   return (Players[Player_num].flags & PLAYER_FLAGS_BLUE_KEY);
173 // -- MK, 10/17/95 --           else if (Walls[wall_num].keys == KEY_GOLD)
174 // -- MK, 10/17/95 --                   return (Players[Player_num].flags & PLAYER_FLAGS_GOLD_KEY);
175 // -- MK, 10/17/95 --           else if (Walls[wall_num].keys == KEY_RED)
176 // -- MK, 10/17/95 --                   return (Players[Player_num].flags & PLAYER_FLAGS_RED_KEY);
177 // -- MK, 10/17/95 --           else
178 // -- MK, 10/17/95 --                   Int3(); //      Impossible!  Doesn't have no key, but doesn't have any key!
179 // -- MK, 10/17/95 --   } else
180 // -- MK, 10/17/95 --           return 1;
181 // -- MK, 10/17/95 -- 
182 // -- MK, 10/17/95 --   Int3(); //      Hmm, thought 'if' above had to return!
183 // -- MK, 10/17/95 --   return 0;
184
185 }
186
187
188 //      -----------------------------------------------------------------------------
189 //      Create a breadth-first list of segments reachable from current segment.
190 //      max_segs is maximum number of segments to search.  Use MAX_SEGMENTS to search all.
191 //      On exit, *length <= max_segs.
192 //      Input:
193 //              start_seg
194 //      Output:
195 //              bfs_list:       array of shorts, each reachable segment.  Includes start segment.
196 //              length:         number of elements in bfs_list
197 void create_bfs_list(int start_seg, short bfs_list[], int *length, int max_segs)
198 {
199         int     i, head, tail;
200         sbyte   visited[MAX_SEGMENTS];
201
202         for (i=0; i<MAX_SEGMENTS; i++)
203                 visited[i] = 0;
204
205         head = 0;
206         tail = 0;
207
208         bfs_list[head++] = start_seg;
209         visited[start_seg] = 1;
210
211         while ((head != tail) && (head < max_segs)) {
212                 int             i;
213                 int             curseg;
214                 segment *cursegp;
215
216                 curseg = bfs_list[tail++];
217                 cursegp = &Segments[curseg];
218
219                 for (i=0; i<MAX_SIDES_PER_SEGMENT; i++) {
220                         int     connected_seg;
221
222                         connected_seg = cursegp->children[i];
223
224                         if (IS_CHILD(connected_seg) && (visited[connected_seg] == 0)) {
225                                 if (segment_is_reachable(curseg, i)) {
226                                         bfs_list[head++] = connected_seg;
227                                         if (head >= max_segs)
228                                                 break;
229                                         visited[connected_seg] = 1;
230                                         Assert(head < MAX_SEGMENTS);
231                                 }
232                         }
233                 }
234         }
235
236         *length = head;
237         
238 }
239
240 //      -----------------------------------------------------------------------------
241 //      Return true if ok for buddy to talk, else return false.
242 //      Buddy is allowed to talk if the segment he is in does not contain a blastable wall that has not been blasted
243 //      AND he has never yet, since being initialized for level, been allowed to talk.
244 int ok_for_buddy_to_talk(void)
245 {
246         int             i;
247         segment *segp;
248
249         if (Objects[Buddy_objnum].type != OBJ_ROBOT) {
250                 Buddy_allowed_to_talk = 0;
251                 return 0;
252         }
253
254         if (Buddy_allowed_to_talk)
255                 return 1;
256
257         if ((Objects[Buddy_objnum].type == OBJ_ROBOT) && (Buddy_objnum <= Highest_object_index) && !Robot_info[Objects[Buddy_objnum].id].companion) {
258                 for (i=0; i<=Highest_object_index; i++)
259                         if (Robot_info[Objects[i].id].companion)
260                                 break;
261                 if (i > Highest_object_index)
262                         return 0;
263                 else
264                         Buddy_objnum = i;
265         }
266
267         segp = &Segments[Objects[Buddy_objnum].segnum];
268
269         for (i=0; i<MAX_SIDES_PER_SEGMENT; i++) {
270                 int     wall_num = segp->sides[i].wall_num;
271
272                 if (wall_num != -1) {
273                         if ((Walls[wall_num].type == WALL_BLASTABLE) && !(Walls[wall_num].flags & WALL_BLASTED))
274                                 return 0;
275                 }
276
277                 //      Check one level deeper.
278                 if (IS_CHILD(segp->children[i])) {
279                         int             j;
280                         segment *csegp = &Segments[segp->children[i]];
281
282                         for (j=0; j<MAX_SIDES_PER_SEGMENT; j++) {
283                                 int     wall2 = csegp->sides[j].wall_num;
284
285                                 if (wall2 != -1) {
286                                         if ((Walls[wall2].type == WALL_BLASTABLE) && !(Walls[wall2].flags & WALL_BLASTED))
287                                                 return 0;
288                                 }
289                         }
290                 }
291         }
292
293         Buddy_allowed_to_talk = 1;
294         return 1;
295 }
296
297 //      --------------------------------------------------------------------------------------------
298 void detect_escort_goal_accomplished(int index)
299 {
300         int     i,j;
301         int     detected = 0;
302
303         if (!Buddy_allowed_to_talk)
304                 return;
305
306         //      If goal is to go away, how can it be achieved?
307         if (Escort_special_goal == ESCORT_GOAL_SCRAM)
308                 return;
309
310 //      See if goal found was a key.  Need to handle default goals differently.
311 //      Note, no buddy_met_goal sound when blow up reactor or exit.  Not great, but ok
312 //      since for reactor, noisy, for exit, buddy is disappearing.
313 if ((Escort_special_goal == -1) && (Escort_goal_index == index)) {
314         detected = 1;
315         goto dega_ok;
316 }
317
318 if ((Escort_goal_index <= ESCORT_GOAL_RED_KEY) && (index >= 0)) {
319         if (Objects[index].type == OBJ_POWERUP)  {
320                 if (Objects[index].id == POW_KEY_BLUE) {
321                         if (Escort_goal_index == ESCORT_GOAL_BLUE_KEY) {
322                                 detected = 1;
323                                 goto dega_ok;
324                         }
325                 } else if (Objects[index].id == POW_KEY_GOLD) {
326                         if (Escort_goal_index == ESCORT_GOAL_GOLD_KEY) {
327                                 detected = 1;
328                                 goto dega_ok;
329                         }
330                 } else if (Objects[index].id == POW_KEY_RED) {
331                         if (Escort_goal_index == ESCORT_GOAL_RED_KEY) {
332                                 detected = 1;
333                                 goto dega_ok;
334                         }
335                 }
336         }
337 }
338         if (Escort_special_goal != -1)
339         {
340                 if (Escort_special_goal == ESCORT_GOAL_ENERGYCEN) {
341                         if (index == -4)
342                                 detected = 1;
343                         else {
344                                 for (i=0; i<MAX_SIDES_PER_SEGMENT; i++)
345                                         if (Segments[index].children[i] == Escort_goal_index) {
346                                                 detected = 1;
347                                                 goto dega_ok;
348                                         } else {
349                                                 for (j=0; j<MAX_SIDES_PER_SEGMENT; j++)
350                                                         if (Segments[i].children[j] == Escort_goal_index) {
351                                                                 detected = 1;
352                                                                 goto dega_ok;
353                                                         }
354                                         }
355                         }
356                 } else if ((Objects[index].type == OBJ_POWERUP) && (Escort_special_goal == ESCORT_GOAL_POWERUP))
357                         detected = 1;   //      Any type of powerup picked up will do.
358                 else if ((Objects[index].type == Objects[Escort_goal_index].type) && (Objects[index].id == Objects[Escort_goal_index].id)) {
359                         //      Note: This will help a little bit in making the buddy believe a goal is satisfied.  Won't work for a general goal like "find any powerup"
360                         // because of the insistence of both type and id matching.
361                         detected = 1;
362                 }
363         }
364
365 dega_ok: ;
366         if (detected && ok_for_buddy_to_talk()) {
367                 digi_play_sample_once(SOUND_BUDDY_MET_GOAL, F1_0);
368                 Escort_goal_index = -1;
369                 Escort_goal_object = ESCORT_GOAL_UNSPECIFIED;
370                 Escort_special_goal = -1;
371                 Looking_for_marker = -1;
372         }
373
374 }
375
376 void change_guidebot_name()
377 {
378         newmenu_item m;
379         char text[GUIDEBOT_NAME_LEN+1]="";
380         int item;
381
382         strcpy(text,guidebot_name);
383
384         m.type=NM_TYPE_INPUT; m.text_len = GUIDEBOT_NAME_LEN; m.text = text;
385         item = newmenu_do( NULL, "Enter Guide-bot name:", 1, &m, NULL );
386
387         if (item != -1) {
388                 strcpy(guidebot_name,text);
389                 cvar_set_cvar(&real_guidebot_name, text);
390         }
391 }
392
393 //      -----------------------------------------------------------------------------
394 void buddy_message(char * format, ... )
395 {
396         if (Buddy_messages_suppressed)
397                 return;
398
399         if (Game_mode & GM_MULTI)
400                 return;
401
402         if ((Last_buddy_message_time + F1_0 < GameTime) || (Last_buddy_message_time > GameTime)) {
403                 if (ok_for_buddy_to_talk()) {
404                         char    gb_str[16], new_format[128];
405                         va_list args;
406                         int t;
407
408                         va_start(args, format );
409                         vsprintf(new_format, format, args);
410                         va_end(args);
411
412                         gb_str[0] = 1;
413                         gb_str[1] = BM_XRGB(28, 0, 0);
414                         strcpy(&gb_str[2], guidebot_name);
415                         t = strlen(gb_str);
416                         gb_str[t] = ':';
417                         gb_str[t+1] = 1;
418                         gb_str[t+2] = BM_XRGB(0, 31, 0);
419                         gb_str[t+3] = 0;
420
421                         HUD_init_message("%s %s", gb_str, new_format);
422
423                         Last_buddy_message_time = GameTime;
424                 }
425         }
426
427 }
428
429 //      -----------------------------------------------------------------------------
430 void thief_message(char * format, ... )
431 {
432
433         char    gb_str[16], new_format[128];
434         va_list args;
435
436         va_start(args, format );
437         vsprintf(new_format, format, args);
438         va_end(args);
439
440         gb_str[0] = 1;
441         gb_str[1] = BM_XRGB(28, 0, 0);
442         strcpy(&gb_str[2], "THIEF:");
443         gb_str[8] = 1;
444         gb_str[9] = BM_XRGB(0, 31, 0);
445         gb_str[10] = 0;
446
447         HUD_init_message("%s %s", gb_str, new_format);
448
449 }
450
451 //      -----------------------------------------------------------------------------
452 //      Return true if marker #id has been placed.
453 int marker_exists_in_mine(int id)
454 {
455         int     i;
456
457         for (i=0; i<=Highest_object_index; i++)
458                 if (Objects[i].type == OBJ_MARKER)
459                         if (Objects[i].id == id)
460                                 return 1;
461
462         return 0;
463 }
464
465 //      -----------------------------------------------------------------------------
466 void set_escort_special_goal(int special_key)
467 {
468         int marker_key;
469
470         Buddy_messages_suppressed = 0;
471
472         if (!Buddy_allowed_to_talk) {
473                 ok_for_buddy_to_talk();
474                 if (!Buddy_allowed_to_talk) {
475                         int     i;
476
477                         for (i=0; i<=Highest_object_index; i++)
478                                 if ((Objects[i].type == OBJ_ROBOT) && Robot_info[Objects[i].id].companion) {
479                                         HUD_init_message("%s has not been released.",guidebot_name);
480                                         break;
481                                 }
482                         if (i == Highest_object_index+1)
483                                 HUD_init_message("No Guide-Bot in mine.");
484
485                         return;
486                 }
487         }
488
489         special_key = special_key & (~KEY_SHIFTED);
490
491         marker_key = special_key;
492         
493         #ifdef MACINTOSH
494         switch(special_key) {
495                 case KEY_5:
496                         marker_key = KEY_1+4;
497                         break;
498                 case KEY_6:
499                         marker_key = KEY_1+5;
500                         break;
501                 case KEY_7:
502                         marker_key = KEY_1+6;
503                         break;
504                 case KEY_8:
505                         marker_key = KEY_1+7;
506                         break;
507                 case KEY_9:
508                         marker_key = KEY_1+8;
509                         break;
510                 case KEY_0:
511                         marker_key = KEY_1+9;
512                         break;
513         }
514         #endif
515
516         if (Last_buddy_key == special_key)
517         {
518                 if ((Looking_for_marker == -1) && (special_key != KEY_0)) {
519                         if (marker_exists_in_mine(marker_key - KEY_1))
520                                 Looking_for_marker = marker_key - KEY_1;
521                         else {
522                                 Last_buddy_message_time = 0;    //      Force this message to get through.
523                                 buddy_message("Marker %i not placed.", marker_key - KEY_1 + 1);
524                                 Looking_for_marker = -1;
525                         }
526                 } else {
527                         Looking_for_marker = -1;
528                 }
529         }
530
531         Last_buddy_key = special_key;
532
533         if (special_key == KEY_0)
534                 Looking_for_marker = -1;
535                 
536         if ( Looking_for_marker != -1 ) {
537                 Escort_special_goal = ESCORT_GOAL_MARKER1 + marker_key - KEY_1;
538         } else {
539                 switch (special_key) {
540                         case KEY_1:     Escort_special_goal = ESCORT_GOAL_ENERGY;                       break;
541                         case KEY_2:     Escort_special_goal = ESCORT_GOAL_ENERGYCEN;            break;
542                         case KEY_3:     Escort_special_goal = ESCORT_GOAL_SHIELD;                       break;
543                         case KEY_4:     Escort_special_goal = ESCORT_GOAL_POWERUP;              break;
544                         case KEY_5:     Escort_special_goal = ESCORT_GOAL_ROBOT;                        break;
545                         case KEY_6:     Escort_special_goal = ESCORT_GOAL_HOSTAGE;              break;
546                         case KEY_7:     Escort_special_goal = ESCORT_GOAL_SCRAM;                        break;
547                         case KEY_8:     Escort_special_goal = ESCORT_GOAL_PLAYER_SPEW;  break;
548                         case KEY_9:     Escort_special_goal = ESCORT_GOAL_EXIT;                 break;
549                         case KEY_0:     Escort_special_goal = -1;                                                               break;
550                         default:
551                                 Int3();         //      Oops, called with illegal key value.
552                 }
553         }
554
555         Last_buddy_message_time = GameTime - 2*F1_0;    //      Allow next message to come through.
556
557         say_escort_goal(Escort_special_goal);
558         // -- Escort_goal_object = escort_set_goal_object();
559
560         Escort_goal_object = ESCORT_GOAL_UNSPECIFIED;
561 }
562
563 // -- old, pre-bfs, way -- //   -----------------------------------------------------------------------------
564 // -- old, pre-bfs, way -- //   Return object of interest.
565 // -- old, pre-bfs, way -- int exists_in_mine(int objtype, int objid)
566 // -- old, pre-bfs, way -- {
567 // -- old, pre-bfs, way --      int     i;
568 // -- old, pre-bfs, way -- 
569 // -- old, pre-bfs, way --      mprintf((0, "exists_in_mine, type == %i, id == %i\n", objtype, objid));
570 // -- old, pre-bfs, way -- 
571 // -- old, pre-bfs, way --      if (objtype == FUELCEN_CHECK) {
572 // -- old, pre-bfs, way --              for (i=0; i<=Highest_segment_index; i++)
573 // -- old, pre-bfs, way --                      if (Segments[i].special == SEGMENT_IS_FUELCEN)
574 // -- old, pre-bfs, way --                              return i;
575 // -- old, pre-bfs, way --      } else {
576 // -- old, pre-bfs, way --              for (i=0; i<=Highest_object_index; i++) {
577 // -- old, pre-bfs, way --                      if (Objects[i].type == objtype) {
578 // -- old, pre-bfs, way --                              //      Don't find escort robots if looking for robot!
579 // -- old, pre-bfs, way --                              if ((Objects[i].type == OBJ_ROBOT) && (Robot_info[Objects[i].id].companion))
580 // -- old, pre-bfs, way --                                      continue;
581 // -- old, pre-bfs, way -- 
582 // -- old, pre-bfs, way --                              if (objid == -1) {
583 // -- old, pre-bfs, way --                                      if ((objtype == OBJ_POWERUP) && (Objects[i].id != POW_KEY_BLUE) && (Objects[i].id != POW_KEY_GOLD) && (Objects[i].id != POW_KEY_RED))
584 // -- old, pre-bfs, way --                                              return i;
585 // -- old, pre-bfs, way --                                      else
586 // -- old, pre-bfs, way --                                              return i;
587 // -- old, pre-bfs, way --                              } else if (Objects[i].id == objid)
588 // -- old, pre-bfs, way --                                      return i;
589 // -- old, pre-bfs, way --                      }
590 // -- old, pre-bfs, way --              }
591 // -- old, pre-bfs, way --      }
592 // -- old, pre-bfs, way -- 
593 // -- old, pre-bfs, way --      return -1;
594 // -- old, pre-bfs, way -- 
595 // -- old, pre-bfs, way -- }
596
597 //      -----------------------------------------------------------------------------
598 //      Return id of boss.
599 int get_boss_id(void)
600 {
601         int     i;
602
603         for (i=0; i<=Highest_object_index; i++)
604                 if (Objects[i].type == OBJ_ROBOT)
605                         if (Robot_info[Objects[i].id].boss_flag)
606                                 return Objects[i].id;
607
608         return -1;
609 }
610
611 //      -----------------------------------------------------------------------------
612 //      Return object index if object of objtype, objid exists in mine, else return -1
613 //      "special" is used to find objects spewed by player which is hacked into flags field of powerup.
614 int exists_in_mine_2(int segnum, int objtype, int objid, int special)
615 {
616         if (Segments[segnum].objects != -1) {
617                 int             objnum = Segments[segnum].objects;
618
619                 while (objnum != -1) {
620                         object  *curobjp = &Objects[objnum];
621
622                         if (special == ESCORT_GOAL_PLAYER_SPEW) {
623                                 if (curobjp->flags & OF_PLAYER_DROPPED)
624                                         return objnum;
625                         }
626
627                         if (curobjp->type == objtype) {
628                                 //      Don't find escort robots if looking for robot!
629                                 if ((curobjp->type == OBJ_ROBOT) && (Robot_info[curobjp->id].companion))
630                                         ;
631                                 else if (objid == -1) {
632                                         if ((objtype == OBJ_POWERUP) && (curobjp->id != POW_KEY_BLUE) && (curobjp->id != POW_KEY_GOLD) && (curobjp->id != POW_KEY_RED))
633                                                 return objnum;
634                                         else
635                                                 return objnum;
636                                 } else if (curobjp->id == objid)
637                                         return objnum;
638                         }
639
640                         if (objtype == OBJ_POWERUP)
641                                 if (curobjp->contains_count)
642                                         if (curobjp->contains_type == OBJ_POWERUP)
643                                                 if (curobjp->contains_id == objid)
644                                                         return objnum;
645
646                         objnum = curobjp->next;
647                 }
648         }
649
650         return -1;
651 }
652
653 //      -----------------------------------------------------------------------------
654 //      Return nearest object of interest.
655 //      If special == ESCORT_GOAL_PLAYER_SPEW, then looking for any object spewed by player.
656 //      -1 means object does not exist in mine.
657 //      -2 means object does exist in mine, but buddy-bot can't reach it (eg, behind triggered wall)
658 int exists_in_mine(int start_seg, int objtype, int objid, int special)
659 {
660         int     segindex, segnum;
661         short   bfs_list[MAX_SEGMENTS];
662         int     length;
663
664 //      mprintf((0, "exists_in_mine, type == %i, id == %i\n", objtype, objid));
665
666         create_bfs_list(start_seg, bfs_list, &length, MAX_SEGMENTS);
667
668         if (objtype == FUELCEN_CHECK) {
669                 for (segindex=0; segindex<length; segindex++) {
670                         segnum = bfs_list[segindex];
671                         if (Segment2s[segnum].special == SEGMENT_IS_FUELCEN)
672                                 return segnum;
673                 }
674         } else {
675                 for (segindex=0; segindex<length; segindex++) {
676                         int     objnum;
677
678                         segnum = bfs_list[segindex];
679
680                         objnum = exists_in_mine_2(segnum, objtype, objid, special);
681                         if (objnum != -1)
682                                 return objnum;
683
684                 }
685         }
686
687         //      Couldn't find what we're looking for by looking at connectivity.
688         //      See if it's in the mine.  It could be hidden behind a trigger or switch
689         //      which the buddybot doesn't understand.
690         if (objtype == FUELCEN_CHECK) {
691                 for (segnum=0; segnum<=Highest_segment_index; segnum++)
692                         if (Segment2s[segnum].special == SEGMENT_IS_FUELCEN)
693                                 return -2;
694         } else {
695                 for (segnum=0; segnum<=Highest_segment_index; segnum++) {
696                         int     objnum;
697
698                         objnum = exists_in_mine_2(segnum, objtype, objid, special);
699                         if (objnum != -1)
700                                 return -2;
701                 }
702         }
703
704         return -1;
705 }
706
707 //      -----------------------------------------------------------------------------
708 //      Return true if it happened, else return false.
709 int find_exit_segment(void)
710 {
711         int     i,j;
712
713         //      ---------- Find exit doors ----------
714         for (i=0; i<=Highest_segment_index; i++)
715                 for (j=0; j<MAX_SIDES_PER_SEGMENT; j++)
716                         if (Segments[i].children[j] == -2) {
717                                 return i;
718                         }
719
720         return -1;
721 }
722
723 #define BUDDY_MARKER_TEXT_LEN   25
724
725 //      -----------------------------------------------------------------------------
726 void say_escort_goal(int goal_num)
727 {
728         if (Player_is_dead)
729                 return;
730
731         switch (goal_num) {
732                 case ESCORT_GOAL_BLUE_KEY:              buddy_message("Finding BLUE KEY");                      break;
733                 case ESCORT_GOAL_GOLD_KEY:              buddy_message("Finding YELLOW KEY");            break;
734                 case ESCORT_GOAL_RED_KEY:               buddy_message("Finding RED KEY");                       break;
735                 case ESCORT_GOAL_CONTROLCEN:    buddy_message("Finding REACTOR");                       break;
736                 case ESCORT_GOAL_EXIT:                  buddy_message("Finding EXIT");                          break;
737                 case ESCORT_GOAL_ENERGY:                buddy_message("Finding ENERGY");                                break;
738                 case ESCORT_GOAL_ENERGYCEN:     buddy_message("Finding ENERGY CENTER"); break;
739                 case ESCORT_GOAL_SHIELD:                buddy_message("Finding a SHIELD");                      break;
740                 case ESCORT_GOAL_POWERUP:               buddy_message("Finding a POWERUP");                     break;
741                 case ESCORT_GOAL_ROBOT:                 buddy_message("Finding a ROBOT");                       break;
742                 case ESCORT_GOAL_HOSTAGE:               buddy_message("Finding a HOSTAGE");                     break;
743                 case ESCORT_GOAL_SCRAM:                 buddy_message("Staying away...");                       break;
744                 case ESCORT_GOAL_BOSS:                  buddy_message("Finding BOSS robot");            break;
745                 case ESCORT_GOAL_PLAYER_SPEW:   buddy_message("Finding your powerups"); break;
746                 case ESCORT_GOAL_MARKER1:
747                 case ESCORT_GOAL_MARKER2:
748                 case ESCORT_GOAL_MARKER3:
749                 case ESCORT_GOAL_MARKER4:
750                 case ESCORT_GOAL_MARKER5:
751                 case ESCORT_GOAL_MARKER6:
752                 case ESCORT_GOAL_MARKER7:
753                 case ESCORT_GOAL_MARKER8:
754                 case ESCORT_GOAL_MARKER9:
755                         { char marker_text[BUDDY_MARKER_TEXT_LEN];
756                         strncpy(marker_text, MarkerMessage[goal_num-ESCORT_GOAL_MARKER1], BUDDY_MARKER_TEXT_LEN-1);
757                         marker_text[BUDDY_MARKER_TEXT_LEN-1] = 0;
758                         buddy_message("Finding marker %i: '%s'", goal_num-ESCORT_GOAL_MARKER1+1, marker_text);
759                         break;
760                         }
761         }
762 }
763
764 //      -----------------------------------------------------------------------------
765 void escort_create_path_to_goal(object *objp)
766 {
767         int     goal_seg = -1;
768         int         objnum = OBJECT_NUMBER(objp);
769         ai_static       *aip = &objp->ctype.ai_info;
770         ai_local                *ailp = &Ai_local_info[objnum];
771
772         if (Escort_special_goal != -1)
773                 Escort_goal_object = Escort_special_goal;
774
775         Escort_kill_object = -1;
776
777         if (Looking_for_marker != -1) {
778
779                 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_MARKER, Escort_goal_object-ESCORT_GOAL_MARKER1, -1);
780                 if (Escort_goal_index > -1)
781                         goal_seg = Objects[Escort_goal_index].segnum;
782         } else {
783                 switch (Escort_goal_object) {
784                         case ESCORT_GOAL_BLUE_KEY:
785                                 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_POWERUP, POW_KEY_BLUE, -1);
786                                 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
787                                 break;
788                         case ESCORT_GOAL_GOLD_KEY:
789                                 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_POWERUP, POW_KEY_GOLD, -1);
790                                 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
791                                 break;
792                         case ESCORT_GOAL_RED_KEY:
793                                 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_POWERUP, POW_KEY_RED, -1);
794                                 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
795                                 break;
796                         case ESCORT_GOAL_CONTROLCEN:
797                                 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_CNTRLCEN, -1, -1);
798                                 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
799                                 break;
800                         case ESCORT_GOAL_EXIT:
801                         case ESCORT_GOAL_EXIT2:
802                                 goal_seg = find_exit_segment();
803                                 Escort_goal_index = goal_seg;
804                                 break;
805                         case ESCORT_GOAL_ENERGY:
806                                 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_POWERUP, POW_ENERGY, -1);
807                                 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
808                                 break;
809                         case ESCORT_GOAL_ENERGYCEN:
810                                 goal_seg = exists_in_mine(objp->segnum, FUELCEN_CHECK, -1, -1);
811                                 Escort_goal_index = goal_seg;
812                                 break;
813                         case ESCORT_GOAL_SHIELD:
814                                 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_POWERUP, POW_SHIELD_BOOST, -1);
815                                 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
816                                 break;
817                         case ESCORT_GOAL_POWERUP:
818                                 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_POWERUP, -1, -1);
819                                 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
820                                 break;
821                         case ESCORT_GOAL_ROBOT:
822                                 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_ROBOT, -1, -1);
823                                 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
824                                 break;
825                         case ESCORT_GOAL_HOSTAGE:
826                                 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_HOSTAGE, -1, -1);
827                                 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
828                                 break;
829                         case ESCORT_GOAL_PLAYER_SPEW:
830                                 Escort_goal_index = exists_in_mine(objp->segnum, -1, -1, ESCORT_GOAL_PLAYER_SPEW);
831                                 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
832                                 break;
833                         case ESCORT_GOAL_SCRAM:
834                                 goal_seg = -3;          //      Kinda a hack.
835                                 Escort_goal_index = goal_seg;
836                                 break;
837                         case ESCORT_GOAL_BOSS: {
838                                 int     boss_id;
839         
840                                 boss_id = get_boss_id();
841                                 Assert(boss_id != -1);
842                                 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_ROBOT, boss_id, -1);
843                                 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
844                                 break;
845                         }
846                         default:
847                                 Int3(); //      Oops, Illegal value in Escort_goal_object.
848                                 goal_seg = 0;
849                                 break;
850                 }
851         }
852
853         // -- mprintf((0, "Creating path from escort to goal #%i in segment #%i.\n", Escort_goal_object, goal_seg));
854         if ((Escort_goal_index < 0) && (Escort_goal_index != -3)) {     //      I apologize for this statement -- MK, 09/22/95
855                 if (Escort_goal_index == -1) {
856                         Last_buddy_message_time = 0;    //      Force this message to get through.
857                         buddy_message("No %s in mine.", Escort_goal_text[Escort_goal_object-1]);
858                         Looking_for_marker = -1;
859                 } else if (Escort_goal_index == -2) {
860                         Last_buddy_message_time = 0;    //      Force this message to get through.
861                         buddy_message("Can't reach %s.", Escort_goal_text[Escort_goal_object-1]);
862                         Looking_for_marker = -1;
863                 } else
864                         Int3();
865
866                 Escort_goal_object = ESCORT_GOAL_UNSPECIFIED;
867                 Escort_special_goal = -1;
868         } else {
869                 if (goal_seg == -3) {
870                         create_n_segment_path(objp, 16 + d_rand() * 16, -1);
871                         aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length);
872                 } else {
873                         create_path_to_segment(objp, goal_seg, Max_escort_length, 1);   //      MK!: Last parm (safety_flag) used to be 1!!
874                         if (aip->path_length > 3)
875                                 aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length);
876                         if ((aip->path_length > 0) && (Point_segs[aip->hide_index + aip->path_length - 1].segnum != goal_seg)) {
877                                 fix     dist_to_player;
878                                 Last_buddy_message_time = 0;    //      Force this message to get through.
879                                 buddy_message("Can't reach %s.", Escort_goal_text[Escort_goal_object-1]);
880                                 Looking_for_marker = -1;
881                                 Escort_goal_object = ESCORT_GOAL_SCRAM;
882                                 dist_to_player = find_connected_distance(&objp->pos, objp->segnum, &Believed_player_pos, Believed_player_seg, 100, WID_FLY_FLAG);
883                                 if (dist_to_player > MIN_ESCORT_DISTANCE)
884                                         create_path_to_player(objp, Max_escort_length, 1);      //      MK!: Last parm used to be 1!
885                                 else {
886                                         create_n_segment_path(objp, 8 + d_rand() * 8, -1);
887                                         aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length);
888                                 }
889                         }
890                 }
891
892                 ailp->mode = AIM_GOTO_OBJECT;
893
894                 say_escort_goal(Escort_goal_object);
895         }
896
897 }
898
899 //      -----------------------------------------------------------------------------
900 //      Escort robot chooses goal object based on player's keys, location.
901 //      Returns goal object.
902 int escort_set_goal_object(void)
903 {
904         if (Escort_special_goal != -1)
905                 return ESCORT_GOAL_UNSPECIFIED;
906         else if (!(ConsoleObject->flags & PLAYER_FLAGS_BLUE_KEY) && (exists_in_mine(ConsoleObject->segnum, OBJ_POWERUP, POW_KEY_BLUE, -1) != -1))
907                 return ESCORT_GOAL_BLUE_KEY;
908         else if (!(ConsoleObject->flags & PLAYER_FLAGS_GOLD_KEY) && (exists_in_mine(ConsoleObject->segnum, OBJ_POWERUP, POW_KEY_GOLD, -1) != -1))
909                 return ESCORT_GOAL_GOLD_KEY;
910         else if (!(ConsoleObject->flags & PLAYER_FLAGS_RED_KEY) && (exists_in_mine(ConsoleObject->segnum, OBJ_POWERUP, POW_KEY_RED, -1) != -1))
911                 return ESCORT_GOAL_RED_KEY;
912         else if (Control_center_destroyed == 0) {
913                 if (Num_boss_teleport_segs)
914                         return ESCORT_GOAL_BOSS;
915                 else
916                         return ESCORT_GOAL_CONTROLCEN;
917         } else
918                 return ESCORT_GOAL_EXIT;
919         
920 }
921
922 #define MAX_ESCORT_TIME_AWAY            (F1_0*4)
923
924 fix     Buddy_last_seen_player = 0, Buddy_last_player_path_created;
925
926 //      -----------------------------------------------------------------------------
927 int time_to_visit_player(object *objp, ai_local *ailp, ai_static *aip)
928 {
929         //      Note: This one has highest priority because, even if already going towards player,
930         //      might be necessary to create a new path, as player can move.
931         if (GameTime - Buddy_last_seen_player > MAX_ESCORT_TIME_AWAY)
932                 if (GameTime - Buddy_last_player_path_created > F1_0)
933                         return 1;
934
935         if (ailp->mode == AIM_GOTO_PLAYER)
936                 return 0;
937
938         if (objp->segnum == ConsoleObject->segnum)
939                 return 0;
940
941         if (aip->cur_path_index < aip->path_length/2)
942                 return 0;
943         
944         return 1;
945 }
946
947 int     Buddy_objnum;
948 int Buddy_dude_cheat;
949 fix     Last_come_back_message_time = 0;
950
951 fix     Buddy_last_missile_time;
952
953 //      -----------------------------------------------------------------------------
954 void bash_buddy_weapon_info(int weapon_objnum)
955 {
956         object  *objp = &Objects[weapon_objnum];
957
958         objp->ctype.laser_info.parent_num = OBJECT_NUMBER(ConsoleObject);
959         objp->ctype.laser_info.parent_type = OBJ_PLAYER;
960         objp->ctype.laser_info.parent_signature = ConsoleObject->signature;
961 }
962
963 //      -----------------------------------------------------------------------------
964 int maybe_buddy_fire_mega(int objnum)
965 {
966         object  *objp = &Objects[objnum];
967         object  *buddy_objp = &Objects[Buddy_objnum];
968         fix             dist, dot;
969         vms_vector      vec_to_robot;
970         int             weapon_objnum;
971
972         vm_vec_sub(&vec_to_robot, &buddy_objp->pos, &objp->pos);
973         dist = vm_vec_normalize_quick(&vec_to_robot);
974
975         if (dist > F1_0*100)
976                 return 0;
977
978         dot = vm_vec_dot(&vec_to_robot, &buddy_objp->orient.fvec);
979
980         if (dot < F1_0/2)
981                 return 0;
982
983         if (!object_to_object_visibility(buddy_objp, objp, FQ_TRANSWALL))
984                 return 0;
985
986         if (Weapon_info[MEGA_ID].render_type == 0) {
987                 con_printf(CON_VERBOSE, "Buddy can't fire mega (shareware)\n");
988                 buddy_message("CLICK!");
989                 return 0;
990         }
991
992         mprintf((0, "Buddy firing mega in frame %i\n", FrameCount));
993
994         buddy_message("GAHOOGA!");
995
996         weapon_objnum = Laser_create_new_easy( &buddy_objp->orient.fvec, &buddy_objp->pos, objnum, MEGA_ID, 1);
997
998         if (weapon_objnum != -1)
999                 bash_buddy_weapon_info(weapon_objnum);
1000
1001         return 1;
1002 }
1003
1004 //-----------------------------------------------------------------------------
1005 int maybe_buddy_fire_smart(int objnum)
1006 {
1007         object  *objp = &Objects[objnum];
1008         object  *buddy_objp = &Objects[Buddy_objnum];
1009         fix             dist;
1010         int             weapon_objnum;
1011
1012         dist = vm_vec_dist_quick(&buddy_objp->pos, &objp->pos);
1013
1014         if (dist > F1_0*80)
1015                 return 0;
1016
1017         if (!object_to_object_visibility(buddy_objp, objp, FQ_TRANSWALL))
1018                 return 0;
1019
1020         mprintf((0, "Buddy firing smart missile in frame %i\n", FrameCount));
1021
1022         buddy_message("WHAMMO!");
1023
1024         weapon_objnum = Laser_create_new_easy( &buddy_objp->orient.fvec, &buddy_objp->pos, objnum, SMART_ID, 1);
1025
1026         if (weapon_objnum != -1)
1027                 bash_buddy_weapon_info(weapon_objnum);
1028
1029         return 1;
1030 }
1031
1032 //      -----------------------------------------------------------------------------
1033 void do_buddy_dude_stuff(void)
1034 {
1035         int     i;
1036
1037         if (!ok_for_buddy_to_talk())
1038                 return;
1039
1040         if (Buddy_last_missile_time > GameTime)
1041                 Buddy_last_missile_time = 0;
1042
1043         if (Buddy_last_missile_time + F1_0*2 < GameTime) {
1044                 //      See if a robot potentially in view cone
1045                 for (i=0; i<=Highest_object_index; i++)
1046                         if ((Objects[i].type == OBJ_ROBOT) && !Robot_info[Objects[i].id].companion)
1047                                 if (maybe_buddy_fire_mega(i)) {
1048                                         Buddy_last_missile_time = GameTime;
1049                                         return;
1050                                 }
1051
1052                 //      See if a robot near enough that buddy should fire smart missile
1053                 for (i=0; i<=Highest_object_index; i++)
1054                         if ((Objects[i].type == OBJ_ROBOT) && !Robot_info[Objects[i].id].companion)
1055                                 if (maybe_buddy_fire_smart(i)) {
1056                                         Buddy_last_missile_time = GameTime;
1057                                         return;
1058                                 }
1059
1060         }
1061 }
1062
1063 //      -----------------------------------------------------------------------------
1064 //      Called every frame (or something).
1065 void do_escort_frame(object *objp, fix dist_to_player, int player_visibility)
1066 {
1067         int         objnum = OBJECT_NUMBER(objp);
1068         ai_static       *aip = &objp->ctype.ai_info;
1069         ai_local                *ailp = &Ai_local_info[objnum];
1070
1071         Buddy_objnum = OBJECT_NUMBER(objp);
1072
1073         if (player_visibility) {
1074                 Buddy_last_seen_player = GameTime;
1075                 if (Players[Player_num].flags & PLAYER_FLAGS_HEADLIGHT_ON)      //      DAMN! MK, stupid bug, fixed 12/08/95, changed PLAYER_FLAGS_HEADLIGHT to PLAYER_FLAGS_HEADLIGHT_ON
1076                         if (f2i(Players[Player_num].energy) < 40)
1077                                 if ((f2i(Players[Player_num].energy)/2) & 2)
1078                                         if (!Player_is_dead)
1079                                                 buddy_message("Hey, your headlight's on!");
1080
1081         }
1082
1083         if (Buddy_dude_cheat)
1084                 do_buddy_dude_stuff();
1085
1086         if (Buddy_sorry_time + F1_0 > GameTime) {
1087                 Last_buddy_message_time = 0;    //      Force this message to get through.
1088                 if (Buddy_sorry_time < GameTime + F1_0*2)
1089                         buddy_message("Oops, sorry 'bout that...");
1090                 Buddy_sorry_time = -F1_0*2;
1091         }
1092
1093         //      If buddy not allowed to talk, then he is locked in his room.  Make him mostly do nothing unless you're nearby.
1094         if (!Buddy_allowed_to_talk)
1095                 if (dist_to_player > F1_0*100)
1096                         aip->SKIP_AI_COUNT = (F1_0/4)/FrameTime;
1097
1098         // -- mprintf((0, "%10s: Dist to player = %7.3f, segnum = %4i\n", mode_text[ailp->mode], f2fl(dist_to_player), objp->segnum));
1099
1100         //      AIM_WANDER has been co-opted for buddy behavior (didn't want to modify aistruct.h)
1101         //      It means the object has been told to get lost and has come to the end of its path.
1102         //      If the player is now visible, then create a path.
1103         if (ailp->mode == AIM_WANDER)
1104                 if (player_visibility) {
1105                         // -- mprintf((0, "Buddy: Going from wander to path following!\n"));
1106                         create_n_segment_path(objp, 16 + d_rand() * 16, -1);
1107                         aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length);
1108                 }
1109
1110         if (Escort_special_goal == ESCORT_GOAL_SCRAM) {
1111                 if (player_visibility)
1112                         if (Escort_last_path_created + F1_0*3 < GameTime) {
1113                                 mprintf((0, "Frame %i: Buddy creating new scram path.\n", FrameCount));
1114                                 create_n_segment_path(objp, 10 + d_rand() * 16, ConsoleObject->segnum);
1115                                 Escort_last_path_created = GameTime;
1116                         }
1117
1118                 // -- Int3();
1119                 // -- mprintf((0, "Buddy: Seg = %3i, dist = %7.3f\n", objp->segnum, f2fl(dist_to_player)));
1120                 return;
1121         }
1122
1123         //      Force checking for new goal every 5 seconds, and create new path, if necessary.
1124         if (((Escort_special_goal != ESCORT_GOAL_SCRAM) && ((Escort_last_path_created + F1_0*5) < GameTime)) ||
1125                 ((Escort_special_goal == ESCORT_GOAL_SCRAM) && ((Escort_last_path_created + F1_0*15) < GameTime))) {
1126                 Escort_goal_object = ESCORT_GOAL_UNSPECIFIED;
1127                 Escort_last_path_created = GameTime;
1128         }
1129
1130         if ((Escort_special_goal != ESCORT_GOAL_SCRAM) && time_to_visit_player(objp, ailp, aip)) {
1131                 int     max_len;
1132
1133                 Buddy_last_player_path_created = GameTime;
1134                 ailp->mode = AIM_GOTO_PLAYER;
1135                 if (!player_visibility) {
1136                         if ((Last_come_back_message_time + F1_0 < GameTime) || (Last_come_back_message_time > GameTime)) {
1137                                 buddy_message("Coming back to get you.");
1138                                 Last_come_back_message_time = GameTime;
1139                         }
1140                 }
1141                 //      No point in Buddy creating very long path if he's not allowed to talk.  Really kills framerate.
1142                 max_len = Max_escort_length;
1143                 if (!Buddy_allowed_to_talk)
1144                         max_len = 3;
1145                 create_path_to_player(objp, max_len, 1);        //      MK!: Last parm used to be 1!
1146                 aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length);
1147                 // -- mprintf((0, "Creating path to player, length = %i\n", aip->path_length));
1148                 ailp->mode = AIM_GOTO_PLAYER;
1149         }       else if (GameTime - Buddy_last_seen_player > MAX_ESCORT_TIME_AWAY) {
1150                 //      This is to prevent buddy from looking for a goal, which he will do because we only allow path creation once/second.
1151                 return;
1152         } else if ((ailp->mode == AIM_GOTO_PLAYER) && (dist_to_player < MIN_ESCORT_DISTANCE)) {
1153                 Escort_goal_object = escort_set_goal_object();
1154                 ailp->mode = AIM_GOTO_OBJECT;           //      May look stupid to be before path creation, but ai_door_is_openable uses mode to determine what doors can be got through
1155                 escort_create_path_to_goal(objp);
1156                 aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length);
1157                 // mprintf((0, "Creating path to goal, length = %i\n", aip->path_length));
1158                 if (aip->path_length < 3) {
1159                         create_n_segment_path(objp, 5, Believed_player_seg);
1160                         // mprintf((0, "Path to goal has length %i, just wandering...\n", aip->path_length));
1161                 }
1162                 ailp->mode = AIM_GOTO_OBJECT;
1163         } else if (Escort_goal_object == ESCORT_GOAL_UNSPECIFIED) {
1164                 if ((ailp->mode != AIM_GOTO_PLAYER) || (dist_to_player < MIN_ESCORT_DISTANCE)) {
1165                         Escort_goal_object = escort_set_goal_object();
1166                         ailp->mode = AIM_GOTO_OBJECT;           //      May look stupid to be before path creation, but ai_door_is_openable uses mode to determine what doors can be got through
1167                         escort_create_path_to_goal(objp);
1168                         aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length);
1169                         // mprintf((0, "Creating path to goal, length = %i\n", aip->path_length));
1170                         if (aip->path_length < 3) {
1171                                 create_n_segment_path(objp, 5, Believed_player_seg);
1172                                 // mprintf((0, "Path to goal has length %i, just wandering...\n", aip->path_length));
1173                         }
1174                         ailp->mode = AIM_GOTO_OBJECT;
1175                 }
1176         } else
1177                 ; // mprintf((0, "!"));
1178
1179 }
1180
1181 void invalidate_escort_goal(void)
1182 {
1183         Escort_goal_object = -1;
1184 }
1185
1186 //      -------------------------------------------------------------------------------------------------
1187 void do_snipe_frame(object *objp, fix dist_to_player, int player_visibility, vms_vector *vec_to_player)
1188 {
1189         int         objnum = OBJECT_NUMBER(objp);
1190         ai_local                *ailp = &Ai_local_info[objnum];
1191         fix                     connected_distance;
1192
1193         if (dist_to_player > F1_0*500)
1194                 return;
1195
1196         // -- mprintf((0, "Mode: %10s, Dist: %7.3f\n", mode_text[ailp->mode], f2fl(dist_to_player)));
1197
1198         switch (ailp->mode) {
1199                 case AIM_SNIPE_WAIT:
1200                         if ((dist_to_player > F1_0*50) && (ailp->next_action_time > 0))
1201                                 return;
1202
1203                         ailp->next_action_time = SNIPE_WAIT_TIME;
1204
1205                         connected_distance = find_connected_distance(&objp->pos, objp->segnum, &Believed_player_pos, Believed_player_seg, 30, WID_FLY_FLAG);
1206                         if (connected_distance < F1_0*500) {
1207                                 // -- mprintf((0, "Object #%i entering attack mode.\n", objnum));
1208                                 create_path_to_player(objp, 30, 1);
1209                                 ailp->mode = AIM_SNIPE_ATTACK;
1210                                 ailp->next_action_time = SNIPE_ATTACK_TIME;     //      have up to 10 seconds to find player.
1211                         }
1212                         break;
1213
1214                 case AIM_SNIPE_RETREAT:
1215                 case AIM_SNIPE_RETREAT_BACKWARDS:
1216                         if (ailp->next_action_time < 0) {
1217                                 ailp->mode = AIM_SNIPE_WAIT;
1218                                 ailp->next_action_time = SNIPE_WAIT_TIME;
1219                                 // -- mprintf((0, "Object #%i going from retreat to wait.\n", objnum));
1220                         } else if ((player_visibility == 0) || (ailp->next_action_time > SNIPE_ABORT_RETREAT_TIME)) {
1221                                 ai_follow_path(objp, player_visibility, player_visibility, vec_to_player);
1222                                 ailp->mode = AIM_SNIPE_RETREAT_BACKWARDS;
1223                         } else {
1224                                 // -- mprintf((0, "Object #%i going from retreat to fire.\n", objnum));
1225                                 ailp->mode = AIM_SNIPE_FIRE;
1226                                 ailp->next_action_time = SNIPE_FIRE_TIME/2;
1227                         }
1228                         break;
1229
1230                 case AIM_SNIPE_ATTACK:
1231                         if (ailp->next_action_time < 0) {
1232                                 // -- mprintf((0, "Object #%i timed out from attack to retreat mode.\n", objnum));
1233                                 ailp->mode = AIM_SNIPE_RETREAT;
1234                                 ailp->next_action_time = SNIPE_WAIT_TIME;
1235                         } else {
1236                                 // -- mprintf((0, "Object #%i attacking: visibility = %i\n", player_visibility));
1237                                 ai_follow_path(objp, player_visibility, player_visibility, vec_to_player);
1238                                 if (player_visibility) {
1239                                         ailp->mode = AIM_SNIPE_FIRE;
1240                                         ailp->next_action_time = SNIPE_FIRE_TIME;
1241                                 } else
1242                                         ailp->mode = AIM_SNIPE_ATTACK;
1243                         }
1244                         break;
1245
1246                 case AIM_SNIPE_FIRE:
1247                         if (ailp->next_action_time < 0) {
1248                                 ai_static       *aip = &objp->ctype.ai_info;
1249                                 // -- mprintf((0, "Object #%i going from fire to retreat.\n", objnum));
1250                                 create_n_segment_path(objp, 10 + d_rand()/2048, ConsoleObject->segnum);
1251                                 aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length);
1252                                 if (d_rand() < 8192)
1253                                         ailp->mode = AIM_SNIPE_RETREAT_BACKWARDS;
1254                                 else
1255                                         ailp->mode = AIM_SNIPE_RETREAT;
1256                                 ailp->next_action_time = SNIPE_RETREAT_TIME;
1257                         } else {
1258                         }
1259                         break;
1260
1261                 default:
1262                         Int3(); //      Oops, illegal mode for snipe behavior.
1263                         ailp->mode = AIM_SNIPE_ATTACK;
1264                         ailp->next_action_time = F1_0;
1265                         break;
1266         }
1267
1268 }
1269
1270 #define THIEF_DEPTH     20
1271
1272 extern int pick_connected_segment(object *objp, int max_depth);
1273
1274 //      ------------------------------------------------------------------------------------------------------
1275 //      Choose segment to recreate thief in.
1276 int choose_thief_recreation_segment(void)
1277 {
1278         int     segnum = -1;
1279         int     cur_drop_depth;
1280
1281         cur_drop_depth = THIEF_DEPTH;
1282
1283         while ((segnum == -1) && (cur_drop_depth > THIEF_DEPTH/2)) {
1284                 segnum = pick_connected_segment(&Objects[Players[Player_num].objnum], cur_drop_depth);
1285                 if (Segment2s[segnum].special == SEGMENT_IS_CONTROLCEN)
1286                         segnum = -1;
1287                 cur_drop_depth--;
1288         }
1289
1290         if (segnum == -1) {
1291                 mprintf((1, "Warning: Unable to find a connected segment for thief recreation.\n"));
1292                 return (d_rand() * Highest_segment_index) >> 15;
1293         } else
1294                 return segnum;
1295
1296 }
1297
1298 extern object * create_morph_robot( segment *segp, vms_vector *object_pos, int object_id);
1299
1300 fix     Re_init_thief_time = 0x3f000000;
1301
1302 //      ----------------------------------------------------------------------
1303 void recreate_thief(object *objp)
1304 {
1305         int                     segnum;
1306         vms_vector      center_point;
1307         object          *new_obj;
1308
1309         segnum = choose_thief_recreation_segment();
1310         compute_segment_center(&center_point, &Segments[segnum]);
1311
1312         new_obj = create_morph_robot( &Segments[segnum], &center_point, objp->id);
1313         init_ai_object(OBJECT_NUMBER(new_obj), AIB_SNIPE, -1);
1314         Re_init_thief_time = GameTime + F1_0*10;                //      In 10 seconds, re-initialize thief.
1315 }
1316
1317 //      ----------------------------------------------------------------------------
1318 #define THIEF_ATTACK_TIME               (F1_0*10)
1319
1320 fix     Thief_wait_times[NDL] = {F1_0*30, F1_0*25, F1_0*20, F1_0*15, F1_0*10};
1321
1322 //      -------------------------------------------------------------------------------------------------
1323 void do_thief_frame(object *objp, fix dist_to_player, int player_visibility, vms_vector *vec_to_player)
1324 {
1325         int         objnum = OBJECT_NUMBER(objp);
1326         ai_local                *ailp = &Ai_local_info[objnum];
1327         fix                     connected_distance;
1328
1329         // -- mprintf((0, "%10s: Action Time: %7.3f\n", mode_text[ailp->mode], f2fl(ailp->next_action_time)));
1330
1331         if ((Current_level_num < 0) && (Re_init_thief_time < GameTime)) {
1332                 if (Re_init_thief_time > GameTime - F1_0*2)
1333                         init_thief_for_level();
1334                 Re_init_thief_time = 0x3f000000;
1335         }
1336
1337         if ((dist_to_player > F1_0*500) && (ailp->next_action_time > 0))
1338                 return;
1339
1340         if (Player_is_dead)
1341                 ailp->mode = AIM_THIEF_RETREAT;
1342
1343         switch (ailp->mode) {
1344                 case AIM_THIEF_WAIT:
1345                         // -- mprintf((0, "WAIT\n"));
1346
1347                         if (ailp->player_awareness_type >= PA_PLAYER_COLLISION) {
1348                                 ailp->player_awareness_type = 0;
1349                                 // -- mprintf((0, "Thief: Awareness = %i ", ailp->player_awareness_type));
1350
1351                                 // -- mprintf((0, "ATTACK\n"));
1352                                 create_path_to_player(objp, 30, 1);
1353                                 ailp->mode = AIM_THIEF_ATTACK;
1354                                 ailp->next_action_time = THIEF_ATTACK_TIME/2;
1355                                 return;
1356                         } else if (player_visibility) {
1357                                 // -- mprintf((0, "RETREAT\n"));
1358                                 create_n_segment_path(objp, 15, ConsoleObject->segnum);
1359                                 ailp->mode = AIM_THIEF_RETREAT;
1360                                 return;
1361                         }
1362
1363                         if ((dist_to_player > F1_0*50) && (ailp->next_action_time > 0))
1364                                 return;
1365
1366                         ailp->next_action_time = Thief_wait_times[Difficulty_level]/2;
1367
1368                         connected_distance = find_connected_distance(&objp->pos, objp->segnum, &Believed_player_pos, Believed_player_seg, 30, WID_FLY_FLAG);
1369                         if (connected_distance < F1_0*500) {
1370                                 // -- mprintf((0, "Thief creating path to player.\n", objnum));
1371                                 create_path_to_player(objp, 30, 1);
1372                                 ailp->mode = AIM_THIEF_ATTACK;
1373                                 ailp->next_action_time = THIEF_ATTACK_TIME;     //      have up to 10 seconds to find player.
1374                         }
1375
1376                         break;
1377
1378                 case AIM_THIEF_RETREAT:
1379                         // -- mprintf((0, "RETREAT\n"));
1380
1381                         if (ailp->next_action_time < 0) {
1382                                 ailp->mode = AIM_THIEF_WAIT;
1383                                 ailp->next_action_time = Thief_wait_times[Difficulty_level];
1384                         } else if ((dist_to_player < F1_0*100) || player_visibility || (ailp->player_awareness_type >= PA_PLAYER_COLLISION)) {
1385                                 ai_follow_path(objp, player_visibility, player_visibility, vec_to_player);
1386                                 if ((dist_to_player < F1_0*100) || (ailp->player_awareness_type >= PA_PLAYER_COLLISION)) {
1387                                         ai_static       *aip = &objp->ctype.ai_info;
1388                                         if (((aip->cur_path_index <=1) && (aip->PATH_DIR == -1)) || ((aip->cur_path_index >= aip->path_length-1) && (aip->PATH_DIR == 1))) {
1389                                                 ailp->player_awareness_type = 0;
1390                                                 create_n_segment_path(objp, 10, ConsoleObject->segnum);
1391
1392                                                 //      If path is real short, try again, allowing to go through player's segment
1393                                                 if (aip->path_length < 4) {
1394                                                         // -- mprintf((0, "Thief is cornered.  Willing to fly through player.\n"));
1395                                                         create_n_segment_path(objp, 10, -1);
1396                                                 } else if (objp->shields* 4 < Robot_info[objp->id].strength) {
1397                                                         //      If robot really low on hits, will run through player with even longer path
1398                                                         if (aip->path_length < 8) {
1399                                                                 create_n_segment_path(objp, 10, -1);
1400                                                         }
1401                                                 }
1402
1403                                                 ailp->mode = AIM_THIEF_RETREAT;
1404                                                 // -- mprintf((0, "Thief creating new RETREAT path.\n"));
1405                                         }
1406                                 } else
1407                                         ailp->mode = AIM_THIEF_RETREAT;
1408
1409                         }
1410
1411                         break;
1412
1413                 //      This means the thief goes from wherever he is to the player.
1414                 //      Note: When thief successfully steals something, his action time is forced negative and his mode is changed
1415                 //                      to retreat to get him out of attack mode.
1416                 case AIM_THIEF_ATTACK:
1417                         // -- mprintf((0, "ATTACK\n"));
1418
1419                         if (ailp->player_awareness_type >= PA_PLAYER_COLLISION) {
1420                                 ailp->player_awareness_type = 0;
1421                                 if (d_rand() > 8192) {
1422                                         // --- mprintf((0, "RETREAT!!\n"));
1423                                         create_n_segment_path(objp, 10, ConsoleObject->segnum);
1424                                         Ai_local_info[OBJECT_NUMBER(objp)].next_action_time = Thief_wait_times[Difficulty_level]/2;
1425                                         Ai_local_info[OBJECT_NUMBER(objp)].mode = AIM_THIEF_RETREAT;
1426                                 }
1427                         } else if (ailp->next_action_time < 0) {
1428                                 //      This forces him to create a new path every second.
1429                                 ailp->next_action_time = F1_0;
1430                                 create_path_to_player(objp, 100, 0);
1431                                 ailp->mode = AIM_THIEF_ATTACK;
1432                                 // -- mprintf((0, "Creating path to player.\n"));
1433                         } else {
1434                                 if (player_visibility && (dist_to_player < F1_0*100)) {
1435                                         //      If the player is close to looking at the thief, thief shall run away.
1436                                         //      No more stupid thief trying to sneak up on you when you're looking right at him!
1437                                         if (dist_to_player > F1_0*60) {
1438                                                 fix     dot = vm_vec_dot(vec_to_player, &ConsoleObject->orient.fvec);
1439                                                 if (dot < -F1_0/2) {    //      Looking at least towards thief, so thief will run!
1440                                                         create_n_segment_path(objp, 10, ConsoleObject->segnum);
1441                                                         Ai_local_info[OBJECT_NUMBER(objp)].next_action_time = Thief_wait_times[Difficulty_level]/2;
1442                                                         Ai_local_info[OBJECT_NUMBER(objp)].mode = AIM_THIEF_RETREAT;
1443                                                 }
1444                                         } 
1445                                         ai_turn_towards_vector(vec_to_player, objp, F1_0/4);
1446                                         move_towards_player(objp, vec_to_player);
1447                                 } else {
1448                                         ai_static       *aip = &objp->ctype.ai_info;
1449                                         //      If path length == 0, then he will keep trying to create path, but he is probably stuck in his closet.
1450                                         if ((aip->path_length > 1) || ((FrameCount & 0x0f) == 0)) {
1451                                                 ai_follow_path(objp, player_visibility, player_visibility, vec_to_player);
1452                                                 ailp->mode = AIM_THIEF_ATTACK;
1453                                         }
1454                                 }
1455                         }
1456                         break;
1457
1458                 default:
1459                         mprintf ((0,"Thief mode (broken) = %d\n",ailp->mode));
1460                         // -- Int3();   //      Oops, illegal mode for thief behavior.
1461                         ailp->mode = AIM_THIEF_ATTACK;
1462                         ailp->next_action_time = F1_0;
1463                         break;
1464         }
1465
1466 }
1467
1468 //      ----------------------------------------------------------------------------
1469 //      Return true if this item (whose presence is indicated by Players[player_num].flags) gets stolen.
1470 int maybe_steal_flag_item(int player_num, int flagval)
1471 {
1472         if (Players[player_num].flags & flagval) {
1473                 if (d_rand() < THIEF_PROBABILITY) {
1474                         int     powerup_index=-1;
1475                         Players[player_num].flags &= (~flagval);
1476                         // -- mprintf((0, "You lost your %4x capability!\n", flagval));
1477                         switch (flagval) {
1478                                 case PLAYER_FLAGS_INVULNERABLE:
1479                                         powerup_index = POW_INVULNERABILITY;
1480                                         thief_message("Invulnerability stolen!");
1481                                         break;
1482                                 case PLAYER_FLAGS_CLOAKED:
1483                                         powerup_index = POW_CLOAK;
1484                                         thief_message("Cloak stolen!");
1485                                         break;
1486                                 case PLAYER_FLAGS_MAP_ALL:
1487                                         powerup_index = POW_FULL_MAP;
1488                                         thief_message("Full map stolen!");
1489                                         break;
1490                                 case PLAYER_FLAGS_QUAD_LASERS:
1491                                         powerup_index = POW_QUAD_FIRE;
1492                                         thief_message("Quad lasers stolen!");
1493                                         break;
1494                                 case PLAYER_FLAGS_AFTERBURNER:
1495                                         powerup_index = POW_AFTERBURNER;
1496                                         thief_message("Afterburner stolen!");
1497                                         break;
1498 // --                           case PLAYER_FLAGS_AMMO_RACK:
1499 // --                                   powerup_index = POW_AMMO_RACK;
1500 // --                                   thief_message("Ammo Rack stolen!");
1501 // --                                   break;
1502                                 case PLAYER_FLAGS_CONVERTER:
1503                                         powerup_index = POW_CONVERTER;
1504                                         thief_message("Converter stolen!");
1505                                         break;
1506                                 case PLAYER_FLAGS_HEADLIGHT:
1507                                         powerup_index = POW_HEADLIGHT;
1508                                         thief_message("Headlight stolen!");
1509                                    Players[Player_num].flags &= ~PLAYER_FLAGS_HEADLIGHT_ON;
1510                                         break;
1511                         }
1512                         Assert(powerup_index != -1);
1513                         Stolen_items[Stolen_item_index] = powerup_index;
1514
1515                         digi_play_sample_once(SOUND_WEAPON_STOLEN, F1_0);
1516                         return 1;
1517                 }
1518         }
1519
1520         return 0;
1521 }
1522
1523 //      ----------------------------------------------------------------------------
1524 int maybe_steal_secondary_weapon(int player_num, int weapon_num)
1525 {
1526         if ((Players[player_num].secondary_weapon_flags & HAS_FLAG(weapon_num)) && Players[player_num].secondary_ammo[weapon_num])
1527                 if (d_rand() < THIEF_PROBABILITY) {
1528                         if (weapon_num == PROXIMITY_INDEX)
1529                                 if (d_rand() > 8192)            //      Come in groups of 4, only add 1/4 of time.
1530                                         return 0;
1531                         Players[player_num].secondary_ammo[weapon_num]--;
1532
1533                         //      Smart mines and proxbombs don't get dropped because they only come in 4 packs.
1534                         if ((weapon_num != PROXIMITY_INDEX) && (weapon_num != SMART_MINE_INDEX)) {
1535                                 Stolen_items[Stolen_item_index] = Secondary_weapon_to_powerup[weapon_num];
1536                         }
1537
1538                         thief_message("%s stolen!", Text_string[114+weapon_num]);               //      Danger! Danger! Use of literal!  Danger!
1539                         if (Players[Player_num].secondary_ammo[weapon_num] == 0)
1540                                 auto_select_weapon(1);
1541
1542                         // -- compress_stolen_items();
1543                         digi_play_sample_once(SOUND_WEAPON_STOLEN, F1_0);
1544                         return 1;
1545                 }
1546
1547         return 0;
1548 }
1549
1550 //      ----------------------------------------------------------------------------
1551 int maybe_steal_primary_weapon(int player_num, int weapon_num)
1552 {
1553         if ((Players[player_num].primary_weapon_flags & HAS_FLAG(weapon_num)) && Players[player_num].primary_ammo[weapon_num]) {
1554                 if (d_rand() < THIEF_PROBABILITY) {
1555                         if (weapon_num == 0) {
1556                                 if (Players[player_num].laser_level > 0) {
1557                                         if (Players[player_num].laser_level > 3) {
1558                                                 Stolen_items[Stolen_item_index] = POW_SUPER_LASER;
1559                                         } else {
1560                                                 Stolen_items[Stolen_item_index] = Primary_weapon_to_powerup[weapon_num];
1561                                         }
1562                                         thief_message("%s level decreased!", Text_string[104+weapon_num]);              //      Danger! Danger! Use of literal!  Danger!
1563                                         Players[player_num].laser_level--;
1564                                         digi_play_sample_once(SOUND_WEAPON_STOLEN, F1_0);
1565                                         return 1;
1566                                 }
1567                         } else if (Players[player_num].primary_weapon_flags & (1 << weapon_num)) {
1568                                 Players[player_num].primary_weapon_flags &= ~(1 << weapon_num);
1569                                 Stolen_items[Stolen_item_index] = Primary_weapon_to_powerup[weapon_num];
1570
1571                                 thief_message("%s stolen!", Text_string[104+weapon_num]);               //      Danger! Danger! Use of literal!  Danger!
1572                                 auto_select_weapon(0);
1573                                 digi_play_sample_once(SOUND_WEAPON_STOLEN, F1_0);
1574                                 return 1;
1575                         }
1576                 }
1577         }
1578
1579         return 0;
1580 }
1581
1582
1583
1584 //      ----------------------------------------------------------------------------
1585 //      Called for a thief-type robot.
1586 //      If a item successfully stolen, returns true, else returns false.
1587 //      If a wapon successfully stolen, do everything, removing it from player,
1588 //      updating Stolen_items information, deselecting, etc.
1589 int attempt_to_steal_item_3(object *objp, int player_num)
1590 {
1591         int     i;
1592
1593         if (Ai_local_info[OBJECT_NUMBER(objp)].mode != AIM_THIEF_ATTACK)
1594                 return 0;
1595
1596         //      First, try to steal equipped items.
1597
1598         if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_INVULNERABLE))
1599                 return 1;
1600
1601         //      If primary weapon = laser, first try to rip away those nasty quad lasers!
1602         if (Primary_weapon == 0)
1603                 if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_QUAD_LASERS))
1604                         return 1;
1605
1606         //      Makes it more likely to steal primary than secondary.
1607         for (i=0; i<2; i++)
1608                 if (maybe_steal_primary_weapon(player_num, Primary_weapon))
1609                         return 1;
1610
1611         if (maybe_steal_secondary_weapon(player_num, Secondary_weapon))
1612                 return 1;
1613
1614         //      See what the player has and try to snag something.
1615         //      Try best things first.
1616         if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_INVULNERABLE))
1617                 return 1;
1618         if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_CLOAKED))
1619                 return 1;
1620         if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_QUAD_LASERS))
1621                 return 1;
1622         if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_AFTERBURNER))
1623                 return 1;
1624         if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_CONVERTER))
1625                 return 1;
1626 // --   if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_AMMO_RACK))  //      Can't steal because what if have too many items, say 15 homing missiles?
1627 // --           return 1;
1628         if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_HEADLIGHT))
1629                 return 1;
1630         if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_MAP_ALL))
1631                 return 1;
1632
1633         for (i=MAX_SECONDARY_WEAPONS-1; i>=0; i--) {
1634                 if (maybe_steal_primary_weapon(player_num, i))
1635                         return 1;
1636                 if (maybe_steal_secondary_weapon(player_num, i))
1637                         return 1;
1638         }
1639
1640         return 0;
1641 }
1642
1643 //      ----------------------------------------------------------------------------
1644 int attempt_to_steal_item_2(object *objp, int player_num)
1645 {
1646         int     rval;
1647
1648         rval = attempt_to_steal_item_3(objp, player_num);
1649
1650         if (rval) {
1651                 Stolen_item_index = (Stolen_item_index+1) % MAX_STOLEN_ITEMS;
1652                 if (d_rand() > 20000)   //      Occasionally, boost the value again
1653                         Stolen_item_index = (Stolen_item_index+1) % MAX_STOLEN_ITEMS;
1654         }
1655
1656         return rval;
1657 }
1658
1659 //      ----------------------------------------------------------------------------
1660 //      Called for a thief-type robot.
1661 //      If a item successfully stolen, returns true, else returns false.
1662 //      If a wapon successfully stolen, do everything, removing it from player,
1663 //      updating Stolen_items information, deselecting, etc.
1664 int attempt_to_steal_item(object *objp, int player_num)
1665 {
1666         int     i;
1667         int     rval = 0;
1668
1669         if (objp->ctype.ai_info.dying_start_time)
1670                 return 0;
1671
1672         rval += attempt_to_steal_item_2(objp, player_num);
1673
1674         for (i=0; i<3; i++) {
1675                 if (!rval || (d_rand() < 11000)) {      //      about 1/3 of time, steal another item
1676                         rval += attempt_to_steal_item_2(objp, player_num);
1677                 } else
1678                         break;
1679         }
1680         // -- mprintf((0, "%i items were stolen!\n", rval));
1681
1682         create_n_segment_path(objp, 10, ConsoleObject->segnum);
1683         Ai_local_info[OBJECT_NUMBER(objp)].next_action_time = Thief_wait_times[Difficulty_level]/2;
1684         Ai_local_info[OBJECT_NUMBER(objp)].mode = AIM_THIEF_RETREAT;
1685         if (rval) {
1686                 PALETTE_FLASH_ADD(30, 15, -20);
1687                 update_laser_weapon_info();
1688 //              digi_link_sound_to_pos( SOUND_NASTY_ROBOT_HIT_1, objp->segnum, 0, &objp->pos, 0 , DEFAULT_ROBOT_SOUND_VOLUME);
1689 //      I removed this to make the "steal sound" more obvious -AP
1690 #ifdef NETWORK
1691                 if (Game_mode & GM_NETWORK)
1692                  multi_send_stolen_items();
1693 #endif
1694         }
1695         return rval;
1696 }
1697
1698 // --------------------------------------------------------------------------------------------------------------
1699 //      Indicate no items have been stolen.
1700 void init_thief_for_level(void)
1701 {
1702         int     i;
1703
1704         for (i=0; i<MAX_STOLEN_ITEMS; i++)
1705                 Stolen_items[i] = 255;
1706
1707         Assert (MAX_STOLEN_ITEMS >= 3*2);       //      Oops!  Loop below will overwrite memory!
1708   
1709    if (!(Game_mode & GM_MULTI))    
1710                 for (i=0; i<3; i++) {
1711                         Stolen_items[2*i] = POW_SHIELD_BOOST;
1712                         Stolen_items[2*i+1] = POW_ENERGY;
1713                 }
1714
1715         Stolen_item_index = 0;
1716 }
1717
1718 // --------------------------------------------------------------------------------------------------------------
1719 void drop_stolen_items(object *objp)
1720 {
1721         int     i;
1722
1723         mprintf ((0,"Dropping thief items!\n"));
1724
1725         // -- compress_stolen_items();
1726
1727         for (i=0; i<MAX_STOLEN_ITEMS; i++) {
1728                 if (Stolen_items[i] != 255)
1729                         drop_powerup(OBJ_POWERUP, Stolen_items[i], 1, &objp->mtype.phys_info.velocity, &objp->pos, objp->segnum);
1730                 Stolen_items[i] = 255;
1731         }
1732
1733 }
1734
1735 // --------------------------------------------------------------------------------------------------------------
1736 void do_escort_menu(void)
1737 {
1738         int     i;
1739         char    msg[300];
1740         int     paused;
1741         int     key;
1742         int     next_goal;
1743         char    goal_str[32], tstr[32];
1744
1745         if (Game_mode & GM_MULTI) {
1746                 HUD_init_message("No Guide-Bot in Multiplayer!");
1747                 return;
1748         }
1749
1750         for (i=0; i<=Highest_object_index; i++) {
1751                 if (Objects[i].type == OBJ_ROBOT)
1752                         if (Robot_info[Objects[i].id].companion)
1753                                 break;
1754         }
1755
1756         if (i > Highest_object_index) {
1757
1758                 HUD_init_message("No Guide-Bot present in mine!");
1759
1760                 #ifndef NDEBUG
1761                 //      If no buddy bot, create one!
1762                 HUD_init_message("Debug Version: Creating Guide-Bot!");
1763                 create_buddy_bot();
1764                 #else
1765                 return;
1766                 #endif
1767         }
1768
1769         ok_for_buddy_to_talk(); //      Needed here or we might not know buddy can talk when he can.
1770
1771         if (!Buddy_allowed_to_talk) {
1772                 HUD_init_message("%s has not been released",guidebot_name);
1773                 return;
1774         }
1775
1776         digi_pause_digi_sounds();
1777         stop_time();
1778
1779         palette_save();
1780         apply_modified_palette();
1781         reset_palette_add();
1782
1783         game_flush_inputs();
1784
1785         paused = 1;
1786
1787 //      set_screen_mode( SCREEN_MENU );
1788         set_popup_screen();
1789         gr_palette_load( gr_palette );
1790
1791         //      This prevents the buddy from coming back if you've told him to scram.
1792         //      If we don't set next_goal, we get garbage there.
1793         if (Escort_special_goal == ESCORT_GOAL_SCRAM) {
1794                 Escort_special_goal = -1;       //      Else setting next goal might fail.
1795                 next_goal = escort_set_goal_object();
1796                 Escort_special_goal = ESCORT_GOAL_SCRAM;
1797         } else {
1798                 Escort_special_goal = -1;       //      Else setting next goal might fail.
1799                 next_goal = escort_set_goal_object();
1800         }
1801
1802         switch (next_goal) {
1803         #ifndef NDEBUG
1804                 case ESCORT_GOAL_UNSPECIFIED:
1805                         Int3();
1806                         sprintf(goal_str, "ERROR");
1807                         break;
1808         #endif
1809                         
1810                 case ESCORT_GOAL_BLUE_KEY:
1811                         sprintf(goal_str, "blue key");
1812                         break;
1813                 case ESCORT_GOAL_GOLD_KEY:
1814                         sprintf(goal_str, "yellow key");
1815                         break;
1816                 case ESCORT_GOAL_RED_KEY:
1817                         sprintf(goal_str, "red key");
1818                         break;
1819                 case ESCORT_GOAL_CONTROLCEN:
1820                         sprintf(goal_str, "reactor");
1821                         break;
1822                 case ESCORT_GOAL_BOSS:
1823                         sprintf(goal_str, "boss");
1824                         break;
1825                 case ESCORT_GOAL_EXIT:
1826                         sprintf(goal_str, "exit");
1827                         break;
1828                 case ESCORT_GOAL_MARKER1:
1829                 case ESCORT_GOAL_MARKER2:
1830                 case ESCORT_GOAL_MARKER3:
1831                 case ESCORT_GOAL_MARKER4:
1832                 case ESCORT_GOAL_MARKER5:
1833                 case ESCORT_GOAL_MARKER6:
1834                 case ESCORT_GOAL_MARKER7:
1835                 case ESCORT_GOAL_MARKER8:
1836                 case ESCORT_GOAL_MARKER9:
1837                         sprintf(goal_str, "marker %i", next_goal-ESCORT_GOAL_MARKER1+1);
1838                         break;
1839
1840         }
1841                         
1842         if (!Buddy_messages_suppressed)
1843                 sprintf(tstr, "Suppress");
1844         else
1845                 sprintf(tstr, "Enable");
1846
1847         sprintf(msg,    "Select Guide-Bot Command:\n\n"
1848                                                 "0.  Next Goal: %s" CC_LSPACING_S "3\n"
1849                                                 "\x84.  Find Energy Powerup" CC_LSPACING_S "3\n"
1850                                                 "2.  Find Energy Center" CC_LSPACING_S "3\n"
1851                                                 "3.  Find Shield Powerup" CC_LSPACING_S "3\n"
1852                                                 "4.  Find Any Powerup" CC_LSPACING_S "3\n"
1853                                                 "5.  Find a Robot" CC_LSPACING_S "3\n"
1854                                                 "6.  Find a Hostage" CC_LSPACING_S "3\n"
1855                                                 "7.  Stay Away From Me" CC_LSPACING_S "3\n"
1856                                                 "8.  Find My Powerups" CC_LSPACING_S "3\n"
1857                                                 "9.  Find the exit\n\n"
1858                                                 "T.  %s Messages\n"
1859                                                 // -- "9.       Find the exit" CC_LSPACING_S "3\n"
1860                                 , goal_str, tstr);
1861
1862         show_escort_menu(msg);          //TXT_PAUSE);
1863
1864         while (paused) {
1865                 key = key_getch();
1866
1867                 switch (key) {
1868                         case KEY_0:
1869                         case KEY_1:
1870                         case KEY_2:
1871                         case KEY_3:
1872                         case KEY_4:
1873                         case KEY_5:
1874                         case KEY_6:
1875                         case KEY_7:
1876                         case KEY_8:
1877                         case KEY_9:
1878                                 Looking_for_marker = -1;
1879                                 Last_buddy_key = -1;
1880                                 set_escort_special_goal(key);
1881                                 Last_buddy_key = -1;
1882                                 paused = 0;
1883                                 break;
1884
1885                         case KEY_ESC:
1886                         case KEY_ENTER:
1887                                 clear_boxed_message();
1888                                 paused=0;
1889                                 break;
1890
1891 //--10/08/95-- Screwed up font, background.  Why needed, anyway?
1892 //--10/08/95--                  case KEY_F1:
1893 //--10/08/95--                          clear_boxed_message();
1894 //--10/08/95--                          do_show_help();
1895 //--10/08/95--                          show_boxed_message(msg);
1896 //--10/08/95--                          break;
1897
1898                         case KEY_PRINT_SCREEN:
1899                                 save_screen_shot(0);
1900                                 break;
1901
1902                         #ifndef RELEASE
1903                         case KEY_BACKSP: Int3(); break;
1904                         #endif
1905
1906                         case KEY_T: {
1907                                 char    msg[32];
1908                                 int     temp;
1909
1910                                 temp = !Buddy_messages_suppressed;
1911
1912                                 if (temp)
1913                                         strcpy(msg, "suppressed");
1914                                 else
1915                                         strcpy(msg, "enabled");
1916
1917                                 Buddy_messages_suppressed = 1;
1918                                 buddy_message("Messages %s.", msg);
1919
1920                                 Buddy_messages_suppressed = temp;
1921
1922                                 paused = 0;
1923                                 break;
1924                         }
1925
1926                         default:
1927                                 break;
1928
1929                 }
1930
1931         }
1932
1933         game_flush_inputs();
1934
1935         palette_restore();
1936
1937         start_time();
1938         digi_resume_digi_sounds();
1939
1940 }
1941
1942 //      -------------------------------------------------------------------------------
1943 //      Show the Buddy menu!
1944 void show_escort_menu(char *msg)
1945 {       
1946         int     w,h,aw;
1947         int     x,y;
1948
1949
1950         gr_set_current_canvas(&VR_screen_pages[0]);
1951
1952         gr_set_curfont( GAME_FONT );
1953
1954         gr_get_string_size(msg,&w,&h,&aw);
1955
1956         x = (grd_curscreen->sc_w-w)/2;
1957         y = (grd_curscreen->sc_h-h)/4;
1958
1959         gr_set_fontcolor( gr_getcolor(0, 28, 0), -1 );
1960    
1961    nm_draw_background(x-15,y-15,x+w+15-1,y+h+15-1);
1962
1963         gr_ustring( x, y, msg );
1964         gr_update();
1965
1966         reset_cockpit();
1967 }