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