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