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