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