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