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