1 /* $Id: escort.c,v 1.8 2004-08-28 23:17:45 schaffner Exp $ */
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.
17 * Escort robot behavior.
25 #include <stdio.h> // for printf()
26 #include <stdlib.h> // for rand() and qsort()
27 #include <string.h> // for memset()
65 #include "editor/editor.h"
68 extern void multi_send_stolen_items();
69 void say_escort_goal(int goal_num);
70 void show_escort_menu(char *msg);
73 char *Escort_goal_text[MAX_ESCORT_GOALS] = {
98 // -- too much work -- "KAMIKAZE "
101 int Max_escort_length = 200;
102 int Escort_kill_object = -1;
103 ubyte Stolen_items[MAX_STOLEN_ITEMS];
104 int Stolen_item_index;
105 fix Escort_last_path_created = 0;
106 int Escort_goal_object = ESCORT_GOAL_UNSPECIFIED, Escort_special_goal = -1, Escort_goal_index = -1, Buddy_messages_suppressed = 0;
107 fix Buddy_sorry_time;
108 int Buddy_objnum, Buddy_allowed_to_talk;
109 int Looking_for_marker;
112 fix Last_buddy_message_time;
114 char guidebot_name[GUIDEBOT_NAME_LEN+1] = "GUIDE-BOT";
115 char real_guidebot_name[GUIDEBOT_NAME_LEN+1] = "GUIDE-BOT";
117 void init_buddy_for_level(void)
121 Buddy_allowed_to_talk = 0;
123 Escort_goal_object = ESCORT_GOAL_UNSPECIFIED;
124 Escort_special_goal = -1;
125 Escort_goal_index = -1;
126 Buddy_messages_suppressed = 0;
128 for (i=0; i<=Highest_object_index; i++)
129 if (Robot_info[Objects[i].id].companion)
131 if (i <= Highest_object_index)
134 Buddy_sorry_time = -F1_0;
136 Looking_for_marker = -1;
140 // -----------------------------------------------------------------------------
141 // See if segment from curseg through sidenum is reachable.
142 // Return true if it is reachable, else return false.
143 int segment_is_reachable(int curseg, int sidenum)
146 segment *segp = &Segments[curseg];
148 if (!IS_CHILD(segp->children[sidenum]))
151 wall_num = segp->sides[sidenum].wall_num;
153 // If no wall, then it is reachable
157 rval = ai_door_is_openable(NULL, segp, sidenum);
161 // -- MK, 10/17/95 --
162 // -- MK, 10/17/95 -- // Hmm, a closed wall. I think this mean not reachable.
163 // -- MK, 10/17/95 -- if (Walls[wall_num].type == WALL_CLOSED)
164 // -- MK, 10/17/95 -- return 0;
165 // -- MK, 10/17/95 --
166 // -- MK, 10/17/95 -- if (Walls[wall_num].type == WALL_DOOR) {
167 // -- MK, 10/17/95 -- if (Walls[wall_num].keys == KEY_NONE) {
168 // -- MK, 10/17/95 -- return 1; // @MK, 10/17/95: Be consistent with ai_door_is_openable
169 // -- MK, 10/17/95 -- // -- if (Walls[wall_num].flags & WALL_DOOR_LOCKED)
170 // -- MK, 10/17/95 -- // -- return 0;
171 // -- MK, 10/17/95 -- // -- else
172 // -- MK, 10/17/95 -- // -- return 1;
173 // -- MK, 10/17/95 -- } else if (Walls[wall_num].keys == KEY_BLUE)
174 // -- MK, 10/17/95 -- return (Players[Player_num].flags & PLAYER_FLAGS_BLUE_KEY);
175 // -- MK, 10/17/95 -- else if (Walls[wall_num].keys == KEY_GOLD)
176 // -- MK, 10/17/95 -- return (Players[Player_num].flags & PLAYER_FLAGS_GOLD_KEY);
177 // -- MK, 10/17/95 -- else if (Walls[wall_num].keys == KEY_RED)
178 // -- MK, 10/17/95 -- return (Players[Player_num].flags & PLAYER_FLAGS_RED_KEY);
179 // -- MK, 10/17/95 -- else
180 // -- MK, 10/17/95 -- Int3(); // Impossible! Doesn't have no key, but doesn't have any key!
181 // -- MK, 10/17/95 -- } else
182 // -- MK, 10/17/95 -- return 1;
183 // -- MK, 10/17/95 --
184 // -- MK, 10/17/95 -- Int3(); // Hmm, thought 'if' above had to return!
185 // -- MK, 10/17/95 -- return 0;
190 // -----------------------------------------------------------------------------
191 // Create a breadth-first list of segments reachable from current segment.
192 // max_segs is maximum number of segments to search. Use MAX_SEGMENTS to search all.
193 // On exit, *length <= max_segs.
197 // bfs_list: array of shorts, each reachable segment. Includes start segment.
198 // length: number of elements in bfs_list
199 void create_bfs_list(int start_seg, short bfs_list[], int *length, int max_segs)
202 sbyte visited[MAX_SEGMENTS];
204 for (i=0; i<MAX_SEGMENTS; i++)
210 bfs_list[head++] = start_seg;
211 visited[start_seg] = 1;
213 while ((head != tail) && (head < max_segs)) {
218 curseg = bfs_list[tail++];
219 cursegp = &Segments[curseg];
221 for (i=0; i<MAX_SIDES_PER_SEGMENT; i++) {
224 connected_seg = cursegp->children[i];
226 if (IS_CHILD(connected_seg) && (visited[connected_seg] == 0)) {
227 if (segment_is_reachable(curseg, i)) {
228 bfs_list[head++] = connected_seg;
229 if (head >= max_segs)
231 visited[connected_seg] = 1;
232 Assert(head < MAX_SEGMENTS);
242 // -----------------------------------------------------------------------------
243 // Return true if ok for buddy to talk, else return false.
244 // Buddy is allowed to talk if the segment he is in does not contain a blastable wall that has not been blasted
245 // AND he has never yet, since being initialized for level, been allowed to talk.
246 int ok_for_buddy_to_talk(void)
251 if (Objects[Buddy_objnum].type != OBJ_ROBOT) {
252 Buddy_allowed_to_talk = 0;
256 if (Buddy_allowed_to_talk)
259 if ((Objects[Buddy_objnum].type == OBJ_ROBOT) && (Buddy_objnum <= Highest_object_index) && !Robot_info[Objects[Buddy_objnum].id].companion) {
260 for (i=0; i<=Highest_object_index; i++)
261 if (Robot_info[Objects[i].id].companion)
263 if (i > Highest_object_index)
269 segp = &Segments[Objects[Buddy_objnum].segnum];
271 for (i=0; i<MAX_SIDES_PER_SEGMENT; i++) {
272 int wall_num = segp->sides[i].wall_num;
274 if (wall_num != -1) {
275 if ((Walls[wall_num].type == WALL_BLASTABLE) && !(Walls[wall_num].flags & WALL_BLASTED))
279 // Check one level deeper.
280 if (IS_CHILD(segp->children[i])) {
282 segment *csegp = &Segments[segp->children[i]];
284 for (j=0; j<MAX_SIDES_PER_SEGMENT; j++) {
285 int wall2 = csegp->sides[j].wall_num;
288 if ((Walls[wall2].type == WALL_BLASTABLE) && !(Walls[wall2].flags & WALL_BLASTED))
295 Buddy_allowed_to_talk = 1;
299 // --------------------------------------------------------------------------------------------
300 void detect_escort_goal_accomplished(int index)
305 if (!Buddy_allowed_to_talk)
308 // If goal is to go away, how can it be achieved?
309 if (Escort_special_goal == ESCORT_GOAL_SCRAM)
312 // See if goal found was a key. Need to handle default goals differently.
313 // Note, no buddy_met_goal sound when blow up reactor or exit. Not great, but ok
314 // since for reactor, noisy, for exit, buddy is disappearing.
315 if ((Escort_special_goal == -1) && (Escort_goal_index == index)) {
320 if ((Escort_goal_index <= ESCORT_GOAL_RED_KEY) && (index >= 0)) {
321 if (Objects[index].type == OBJ_POWERUP) {
322 if (Objects[index].id == POW_KEY_BLUE) {
323 if (Escort_goal_index == ESCORT_GOAL_BLUE_KEY) {
327 } else if (Objects[index].id == POW_KEY_GOLD) {
328 if (Escort_goal_index == ESCORT_GOAL_GOLD_KEY) {
332 } else if (Objects[index].id == POW_KEY_RED) {
333 if (Escort_goal_index == ESCORT_GOAL_RED_KEY) {
340 if (Escort_special_goal != -1)
342 if (Escort_special_goal == ESCORT_GOAL_ENERGYCEN) {
346 for (i=0; i<MAX_SIDES_PER_SEGMENT; i++)
347 if (Segments[index].children[i] == Escort_goal_index) {
351 for (j=0; j<MAX_SIDES_PER_SEGMENT; j++)
352 if (Segments[i].children[j] == Escort_goal_index) {
358 } else if ((Objects[index].type == OBJ_POWERUP) && (Escort_special_goal == ESCORT_GOAL_POWERUP))
359 detected = 1; // Any type of powerup picked up will do.
360 else if ((Objects[index].type == Objects[Escort_goal_index].type) && (Objects[index].id == Objects[Escort_goal_index].id)) {
361 // 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"
362 // because of the insistence of both type and id matching.
368 if (detected && ok_for_buddy_to_talk()) {
369 digi_play_sample_once(SOUND_BUDDY_MET_GOAL, F1_0);
370 Escort_goal_index = -1;
371 Escort_goal_object = ESCORT_GOAL_UNSPECIFIED;
372 Escort_special_goal = -1;
373 Looking_for_marker = -1;
378 void change_guidebot_name()
381 char text[GUIDEBOT_NAME_LEN+1]="";
384 strcpy(text,guidebot_name);
386 m.type=NM_TYPE_INPUT; m.text_len = GUIDEBOT_NAME_LEN; m.text = text;
387 item = newmenu_do( NULL, "Enter Guide-bot name:", 1, &m, NULL );
390 strcpy(guidebot_name,text);
391 strcpy(real_guidebot_name,text);
396 // -----------------------------------------------------------------------------
397 void buddy_message(char * format, ... )
399 if (Buddy_messages_suppressed)
402 if (Game_mode & GM_MULTI)
405 if ((Last_buddy_message_time + F1_0 < GameTime) || (Last_buddy_message_time > GameTime)) {
406 if (ok_for_buddy_to_talk()) {
407 char gb_str[16], new_format[128];
411 va_start(args, format );
412 vsprintf(new_format, format, args);
416 gb_str[1] = BM_XRGB(28, 0, 0);
417 strcpy(&gb_str[2], guidebot_name);
421 gb_str[t+2] = BM_XRGB(0, 31, 0);
424 HUD_init_message("%s %s", gb_str, new_format);
426 Last_buddy_message_time = GameTime;
432 // -----------------------------------------------------------------------------
433 void thief_message(char * format, ... )
436 char gb_str[16], new_format[128];
439 va_start(args, format );
440 vsprintf(new_format, format, args);
444 gb_str[1] = BM_XRGB(28, 0, 0);
445 strcpy(&gb_str[2], "THIEF:");
447 gb_str[9] = BM_XRGB(0, 31, 0);
450 HUD_init_message("%s %s", gb_str, new_format);
454 // -----------------------------------------------------------------------------
455 // Return true if marker #id has been placed.
456 int marker_exists_in_mine(int id)
460 for (i=0; i<=Highest_object_index; i++)
461 if (Objects[i].type == OBJ_MARKER)
462 if (Objects[i].id == id)
468 // -----------------------------------------------------------------------------
469 void set_escort_special_goal(int special_key)
473 Buddy_messages_suppressed = 0;
475 if (!Buddy_allowed_to_talk) {
476 ok_for_buddy_to_talk();
477 if (!Buddy_allowed_to_talk) {
480 for (i=0; i<=Highest_object_index; i++)
481 if ((Objects[i].type == OBJ_ROBOT) && Robot_info[Objects[i].id].companion) {
482 HUD_init_message("%s has not been released.",guidebot_name);
485 if (i == Highest_object_index+1)
486 HUD_init_message("No Guide-Bot in mine.");
492 special_key = special_key & (~KEY_SHIFTED);
494 marker_key = special_key;
497 switch(special_key) {
499 marker_key = KEY_1+4;
502 marker_key = KEY_1+5;
505 marker_key = KEY_1+6;
508 marker_key = KEY_1+7;
511 marker_key = KEY_1+8;
514 marker_key = KEY_1+9;
519 if (Last_buddy_key == special_key)
521 if ((Looking_for_marker == -1) && (special_key != KEY_0)) {
522 if (marker_exists_in_mine(marker_key - KEY_1))
523 Looking_for_marker = marker_key - KEY_1;
525 Last_buddy_message_time = 0; // Force this message to get through.
526 buddy_message("Marker %i not placed.", marker_key - KEY_1 + 1);
527 Looking_for_marker = -1;
530 Looking_for_marker = -1;
534 Last_buddy_key = special_key;
536 if (special_key == KEY_0)
537 Looking_for_marker = -1;
539 if ( Looking_for_marker != -1 ) {
540 Escort_special_goal = ESCORT_GOAL_MARKER1 + marker_key - KEY_1;
542 switch (special_key) {
543 case KEY_1: Escort_special_goal = ESCORT_GOAL_ENERGY; break;
544 case KEY_2: Escort_special_goal = ESCORT_GOAL_ENERGYCEN; break;
545 case KEY_3: Escort_special_goal = ESCORT_GOAL_SHIELD; break;
546 case KEY_4: Escort_special_goal = ESCORT_GOAL_POWERUP; break;
547 case KEY_5: Escort_special_goal = ESCORT_GOAL_ROBOT; break;
548 case KEY_6: Escort_special_goal = ESCORT_GOAL_HOSTAGE; break;
549 case KEY_7: Escort_special_goal = ESCORT_GOAL_SCRAM; break;
550 case KEY_8: Escort_special_goal = ESCORT_GOAL_PLAYER_SPEW; break;
551 case KEY_9: Escort_special_goal = ESCORT_GOAL_EXIT; break;
552 case KEY_0: Escort_special_goal = -1; break;
554 Int3(); // Oops, called with illegal key value.
558 Last_buddy_message_time = GameTime - 2*F1_0; // Allow next message to come through.
560 say_escort_goal(Escort_special_goal);
561 // -- Escort_goal_object = escort_set_goal_object();
563 Escort_goal_object = ESCORT_GOAL_UNSPECIFIED;
566 // -- old, pre-bfs, way -- // -----------------------------------------------------------------------------
567 // -- old, pre-bfs, way -- // Return object of interest.
568 // -- old, pre-bfs, way -- int exists_in_mine(int objtype, int objid)
569 // -- old, pre-bfs, way -- {
570 // -- old, pre-bfs, way -- int i;
571 // -- old, pre-bfs, way --
572 // -- old, pre-bfs, way -- mprintf((0, "exists_in_mine, type == %i, id == %i\n", objtype, objid));
573 // -- old, pre-bfs, way --
574 // -- old, pre-bfs, way -- if (objtype == FUELCEN_CHECK) {
575 // -- old, pre-bfs, way -- for (i=0; i<=Highest_segment_index; i++)
576 // -- old, pre-bfs, way -- if (Segments[i].special == SEGMENT_IS_FUELCEN)
577 // -- old, pre-bfs, way -- return i;
578 // -- old, pre-bfs, way -- } else {
579 // -- old, pre-bfs, way -- for (i=0; i<=Highest_object_index; i++) {
580 // -- old, pre-bfs, way -- if (Objects[i].type == objtype) {
581 // -- old, pre-bfs, way -- // Don't find escort robots if looking for robot!
582 // -- old, pre-bfs, way -- if ((Objects[i].type == OBJ_ROBOT) && (Robot_info[Objects[i].id].companion))
583 // -- old, pre-bfs, way -- continue;
584 // -- old, pre-bfs, way --
585 // -- old, pre-bfs, way -- if (objid == -1) {
586 // -- 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))
587 // -- old, pre-bfs, way -- return i;
588 // -- old, pre-bfs, way -- else
589 // -- old, pre-bfs, way -- return i;
590 // -- old, pre-bfs, way -- } else if (Objects[i].id == objid)
591 // -- old, pre-bfs, way -- return i;
592 // -- old, pre-bfs, way -- }
593 // -- old, pre-bfs, way -- }
594 // -- old, pre-bfs, way -- }
595 // -- old, pre-bfs, way --
596 // -- old, pre-bfs, way -- return -1;
597 // -- old, pre-bfs, way --
598 // -- old, pre-bfs, way -- }
600 // -----------------------------------------------------------------------------
601 // Return id of boss.
602 int get_boss_id(void)
606 for (i=0; i<=Highest_object_index; i++)
607 if (Objects[i].type == OBJ_ROBOT)
608 if (Robot_info[Objects[i].id].boss_flag)
609 return Objects[i].id;
614 // -----------------------------------------------------------------------------
615 // Return object index if object of objtype, objid exists in mine, else return -1
616 // "special" is used to find objects spewed by player which is hacked into flags field of powerup.
617 int exists_in_mine_2(int segnum, int objtype, int objid, int special)
619 if (Segments[segnum].objects != -1) {
620 int objnum = Segments[segnum].objects;
622 while (objnum != -1) {
623 object *curobjp = &Objects[objnum];
625 if (special == ESCORT_GOAL_PLAYER_SPEW) {
626 if (curobjp->flags & OF_PLAYER_DROPPED)
630 if (curobjp->type == objtype) {
631 // Don't find escort robots if looking for robot!
632 if ((curobjp->type == OBJ_ROBOT) && (Robot_info[curobjp->id].companion))
634 else if (objid == -1) {
635 if ((objtype == OBJ_POWERUP) && (curobjp->id != POW_KEY_BLUE) && (curobjp->id != POW_KEY_GOLD) && (curobjp->id != POW_KEY_RED))
639 } else if (curobjp->id == objid)
643 if (objtype == OBJ_POWERUP)
644 if (curobjp->contains_count)
645 if (curobjp->contains_type == OBJ_POWERUP)
646 if (curobjp->contains_id == objid)
649 objnum = curobjp->next;
656 // -----------------------------------------------------------------------------
657 // Return nearest object of interest.
658 // If special == ESCORT_GOAL_PLAYER_SPEW, then looking for any object spewed by player.
659 // -1 means object does not exist in mine.
660 // -2 means object does exist in mine, but buddy-bot can't reach it (eg, behind triggered wall)
661 int exists_in_mine(int start_seg, int objtype, int objid, int special)
663 int segindex, segnum;
664 short bfs_list[MAX_SEGMENTS];
667 // mprintf((0, "exists_in_mine, type == %i, id == %i\n", objtype, objid));
669 create_bfs_list(start_seg, bfs_list, &length, MAX_SEGMENTS);
671 if (objtype == FUELCEN_CHECK) {
672 for (segindex=0; segindex<length; segindex++) {
673 segnum = bfs_list[segindex];
674 if (Segment2s[segnum].special == SEGMENT_IS_FUELCEN)
678 for (segindex=0; segindex<length; segindex++) {
681 segnum = bfs_list[segindex];
683 objnum = exists_in_mine_2(segnum, objtype, objid, special);
690 // Couldn't find what we're looking for by looking at connectivity.
691 // See if it's in the mine. It could be hidden behind a trigger or switch
692 // which the buddybot doesn't understand.
693 if (objtype == FUELCEN_CHECK) {
694 for (segnum=0; segnum<=Highest_segment_index; segnum++)
695 if (Segment2s[segnum].special == SEGMENT_IS_FUELCEN)
698 for (segnum=0; segnum<=Highest_segment_index; segnum++) {
701 objnum = exists_in_mine_2(segnum, objtype, objid, special);
710 // -----------------------------------------------------------------------------
711 // Return true if it happened, else return false.
712 int find_exit_segment(void)
716 // ---------- Find exit doors ----------
717 for (i=0; i<=Highest_segment_index; i++)
718 for (j=0; j<MAX_SIDES_PER_SEGMENT; j++)
719 if (Segments[i].children[j] == -2) {
726 #define BUDDY_MARKER_TEXT_LEN 25
728 // -----------------------------------------------------------------------------
729 void say_escort_goal(int goal_num)
735 case ESCORT_GOAL_BLUE_KEY: buddy_message("Finding BLUE KEY"); break;
736 case ESCORT_GOAL_GOLD_KEY: buddy_message("Finding YELLOW KEY"); break;
737 case ESCORT_GOAL_RED_KEY: buddy_message("Finding RED KEY"); break;
738 case ESCORT_GOAL_CONTROLCEN: buddy_message("Finding REACTOR"); break;
739 case ESCORT_GOAL_EXIT: buddy_message("Finding EXIT"); break;
740 case ESCORT_GOAL_ENERGY: buddy_message("Finding ENERGY"); break;
741 case ESCORT_GOAL_ENERGYCEN: buddy_message("Finding ENERGY CENTER"); break;
742 case ESCORT_GOAL_SHIELD: buddy_message("Finding a SHIELD"); break;
743 case ESCORT_GOAL_POWERUP: buddy_message("Finding a POWERUP"); break;
744 case ESCORT_GOAL_ROBOT: buddy_message("Finding a ROBOT"); break;
745 case ESCORT_GOAL_HOSTAGE: buddy_message("Finding a HOSTAGE"); break;
746 case ESCORT_GOAL_SCRAM: buddy_message("Staying away..."); break;
747 case ESCORT_GOAL_BOSS: buddy_message("Finding BOSS robot"); break;
748 case ESCORT_GOAL_PLAYER_SPEW: buddy_message("Finding your powerups"); break;
749 case ESCORT_GOAL_MARKER1:
750 case ESCORT_GOAL_MARKER2:
751 case ESCORT_GOAL_MARKER3:
752 case ESCORT_GOAL_MARKER4:
753 case ESCORT_GOAL_MARKER5:
754 case ESCORT_GOAL_MARKER6:
755 case ESCORT_GOAL_MARKER7:
756 case ESCORT_GOAL_MARKER8:
757 case ESCORT_GOAL_MARKER9:
758 { char marker_text[BUDDY_MARKER_TEXT_LEN];
759 strncpy(marker_text, MarkerMessage[goal_num-ESCORT_GOAL_MARKER1], BUDDY_MARKER_TEXT_LEN-1);
760 marker_text[BUDDY_MARKER_TEXT_LEN-1] = 0;
761 buddy_message("Finding marker %i: '%s'", goal_num-ESCORT_GOAL_MARKER1+1, marker_text);
767 // -----------------------------------------------------------------------------
768 void escort_create_path_to_goal(object *objp)
771 int objnum = objp-Objects;
772 ai_static *aip = &objp->ctype.ai_info;
773 ai_local *ailp = &Ai_local_info[objnum];
775 if (Escort_special_goal != -1)
776 Escort_goal_object = Escort_special_goal;
778 Escort_kill_object = -1;
780 if (Looking_for_marker != -1) {
782 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_MARKER, Escort_goal_object-ESCORT_GOAL_MARKER1, -1);
783 if (Escort_goal_index > -1)
784 goal_seg = Objects[Escort_goal_index].segnum;
786 switch (Escort_goal_object) {
787 case ESCORT_GOAL_BLUE_KEY:
788 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_POWERUP, POW_KEY_BLUE, -1);
789 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
791 case ESCORT_GOAL_GOLD_KEY:
792 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_POWERUP, POW_KEY_GOLD, -1);
793 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
795 case ESCORT_GOAL_RED_KEY:
796 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_POWERUP, POW_KEY_RED, -1);
797 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
799 case ESCORT_GOAL_CONTROLCEN:
800 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_CNTRLCEN, -1, -1);
801 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
803 case ESCORT_GOAL_EXIT:
804 case ESCORT_GOAL_EXIT2:
805 goal_seg = find_exit_segment();
806 Escort_goal_index = goal_seg;
808 case ESCORT_GOAL_ENERGY:
809 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_POWERUP, POW_ENERGY, -1);
810 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
812 case ESCORT_GOAL_ENERGYCEN:
813 goal_seg = exists_in_mine(objp->segnum, FUELCEN_CHECK, -1, -1);
814 Escort_goal_index = goal_seg;
816 case ESCORT_GOAL_SHIELD:
817 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_POWERUP, POW_SHIELD_BOOST, -1);
818 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
820 case ESCORT_GOAL_POWERUP:
821 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_POWERUP, -1, -1);
822 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
824 case ESCORT_GOAL_ROBOT:
825 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_ROBOT, -1, -1);
826 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
828 case ESCORT_GOAL_HOSTAGE:
829 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_HOSTAGE, -1, -1);
830 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
832 case ESCORT_GOAL_PLAYER_SPEW:
833 Escort_goal_index = exists_in_mine(objp->segnum, -1, -1, ESCORT_GOAL_PLAYER_SPEW);
834 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
836 case ESCORT_GOAL_SCRAM:
837 goal_seg = -3; // Kinda a hack.
838 Escort_goal_index = goal_seg;
840 case ESCORT_GOAL_BOSS: {
843 boss_id = get_boss_id();
844 Assert(boss_id != -1);
845 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_ROBOT, boss_id, -1);
846 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
850 Int3(); // Oops, Illegal value in Escort_goal_object.
856 // -- mprintf((0, "Creating path from escort to goal #%i in segment #%i.\n", Escort_goal_object, goal_seg));
857 if ((Escort_goal_index < 0) && (Escort_goal_index != -3)) { // I apologize for this statement -- MK, 09/22/95
858 if (Escort_goal_index == -1) {
859 Last_buddy_message_time = 0; // Force this message to get through.
860 buddy_message("No %s in mine.", Escort_goal_text[Escort_goal_object-1]);
861 Looking_for_marker = -1;
862 } else if (Escort_goal_index == -2) {
863 Last_buddy_message_time = 0; // Force this message to get through.
864 buddy_message("Can't reach %s.", Escort_goal_text[Escort_goal_object-1]);
865 Looking_for_marker = -1;
869 Escort_goal_object = ESCORT_GOAL_UNSPECIFIED;
870 Escort_special_goal = -1;
872 if (goal_seg == -3) {
873 create_n_segment_path(objp, 16 + d_rand() * 16, -1);
874 aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length);
876 create_path_to_segment(objp, goal_seg, Max_escort_length, 1); // MK!: Last parm (safety_flag) used to be 1!!
877 if (aip->path_length > 3)
878 aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length);
879 if ((aip->path_length > 0) && (Point_segs[aip->hide_index + aip->path_length - 1].segnum != goal_seg)) {
881 Last_buddy_message_time = 0; // Force this message to get through.
882 buddy_message("Can't reach %s.", Escort_goal_text[Escort_goal_object-1]);
883 Looking_for_marker = -1;
884 Escort_goal_object = ESCORT_GOAL_SCRAM;
885 dist_to_player = find_connected_distance(&objp->pos, objp->segnum, &Believed_player_pos, Believed_player_seg, 100, WID_FLY_FLAG);
886 if (dist_to_player > MIN_ESCORT_DISTANCE)
887 create_path_to_player(objp, Max_escort_length, 1); // MK!: Last parm used to be 1!
889 create_n_segment_path(objp, 8 + d_rand() * 8, -1);
890 aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length);
895 ailp->mode = AIM_GOTO_OBJECT;
897 say_escort_goal(Escort_goal_object);
902 // -----------------------------------------------------------------------------
903 // Escort robot chooses goal object based on player's keys, location.
904 // Returns goal object.
905 int escort_set_goal_object(void)
907 if (Escort_special_goal != -1)
908 return ESCORT_GOAL_UNSPECIFIED;
909 else if (!(ConsoleObject->flags & PLAYER_FLAGS_BLUE_KEY) && (exists_in_mine(ConsoleObject->segnum, OBJ_POWERUP, POW_KEY_BLUE, -1) != -1))
910 return ESCORT_GOAL_BLUE_KEY;
911 else if (!(ConsoleObject->flags & PLAYER_FLAGS_GOLD_KEY) && (exists_in_mine(ConsoleObject->segnum, OBJ_POWERUP, POW_KEY_GOLD, -1) != -1))
912 return ESCORT_GOAL_GOLD_KEY;
913 else if (!(ConsoleObject->flags & PLAYER_FLAGS_RED_KEY) && (exists_in_mine(ConsoleObject->segnum, OBJ_POWERUP, POW_KEY_RED, -1) != -1))
914 return ESCORT_GOAL_RED_KEY;
915 else if (Control_center_destroyed == 0) {
916 if (Num_boss_teleport_segs)
917 return ESCORT_GOAL_BOSS;
919 return ESCORT_GOAL_CONTROLCEN;
921 return ESCORT_GOAL_EXIT;
925 #define MAX_ESCORT_TIME_AWAY (F1_0*4)
927 fix Buddy_last_seen_player = 0, Buddy_last_player_path_created;
929 // -----------------------------------------------------------------------------
930 int time_to_visit_player(object *objp, ai_local *ailp, ai_static *aip)
932 // Note: This one has highest priority because, even if already going towards player,
933 // might be necessary to create a new path, as player can move.
934 if (GameTime - Buddy_last_seen_player > MAX_ESCORT_TIME_AWAY)
935 if (GameTime - Buddy_last_player_path_created > F1_0)
938 if (ailp->mode == AIM_GOTO_PLAYER)
941 if (objp->segnum == ConsoleObject->segnum)
944 if (aip->cur_path_index < aip->path_length/2)
951 int Buddy_dude_cheat;
952 fix Last_come_back_message_time = 0;
954 fix Buddy_last_missile_time;
956 // -----------------------------------------------------------------------------
957 void bash_buddy_weapon_info(int weapon_objnum)
959 object *objp = &Objects[weapon_objnum];
961 objp->ctype.laser_info.parent_num = ConsoleObject-Objects;
962 objp->ctype.laser_info.parent_type = OBJ_PLAYER;
963 objp->ctype.laser_info.parent_signature = ConsoleObject->signature;
966 // -----------------------------------------------------------------------------
967 int maybe_buddy_fire_mega(int objnum)
969 object *objp = &Objects[objnum];
970 object *buddy_objp = &Objects[Buddy_objnum];
972 vms_vector vec_to_robot;
975 vm_vec_sub(&vec_to_robot, &buddy_objp->pos, &objp->pos);
976 dist = vm_vec_normalize_quick(&vec_to_robot);
981 dot = vm_vec_dot(&vec_to_robot, &buddy_objp->orient.fvec);
986 if (!object_to_object_visibility(buddy_objp, objp, FQ_TRANSWALL))
989 if (Weapon_info[MEGA_ID].render_type == 0) {
990 con_printf(CON_VERBOSE, "Buddy can't fire mega (shareware)\n");
991 buddy_message("CLICK!");
995 mprintf((0, "Buddy firing mega in frame %i\n", FrameCount));
997 buddy_message("GAHOOGA!");
999 weapon_objnum = Laser_create_new_easy( &buddy_objp->orient.fvec, &buddy_objp->pos, objnum, MEGA_ID, 1);
1001 if (weapon_objnum != -1)
1002 bash_buddy_weapon_info(weapon_objnum);
1007 //-----------------------------------------------------------------------------
1008 int maybe_buddy_fire_smart(int objnum)
1010 object *objp = &Objects[objnum];
1011 object *buddy_objp = &Objects[Buddy_objnum];
1015 dist = vm_vec_dist_quick(&buddy_objp->pos, &objp->pos);
1020 if (!object_to_object_visibility(buddy_objp, objp, FQ_TRANSWALL))
1023 mprintf((0, "Buddy firing smart missile in frame %i\n", FrameCount));
1025 buddy_message("WHAMMO!");
1027 weapon_objnum = Laser_create_new_easy( &buddy_objp->orient.fvec, &buddy_objp->pos, objnum, SMART_ID, 1);
1029 if (weapon_objnum != -1)
1030 bash_buddy_weapon_info(weapon_objnum);
1035 // -----------------------------------------------------------------------------
1036 void do_buddy_dude_stuff(void)
1040 if (!ok_for_buddy_to_talk())
1043 if (Buddy_last_missile_time > GameTime)
1044 Buddy_last_missile_time = 0;
1046 if (Buddy_last_missile_time + F1_0*2 < GameTime) {
1047 // See if a robot potentially in view cone
1048 for (i=0; i<=Highest_object_index; i++)
1049 if ((Objects[i].type == OBJ_ROBOT) && !Robot_info[Objects[i].id].companion)
1050 if (maybe_buddy_fire_mega(i)) {
1051 Buddy_last_missile_time = GameTime;
1055 // See if a robot near enough that buddy should fire smart missile
1056 for (i=0; i<=Highest_object_index; i++)
1057 if ((Objects[i].type == OBJ_ROBOT) && !Robot_info[Objects[i].id].companion)
1058 if (maybe_buddy_fire_smart(i)) {
1059 Buddy_last_missile_time = GameTime;
1066 // -----------------------------------------------------------------------------
1067 // Called every frame (or something).
1068 void do_escort_frame(object *objp, fix dist_to_player, int player_visibility)
1070 int objnum = objp-Objects;
1071 ai_static *aip = &objp->ctype.ai_info;
1072 ai_local *ailp = &Ai_local_info[objnum];
1074 Buddy_objnum = objp-Objects;
1076 if (player_visibility) {
1077 Buddy_last_seen_player = GameTime;
1078 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
1079 if (f2i(Players[Player_num].energy) < 40)
1080 if ((f2i(Players[Player_num].energy)/2) & 2)
1081 if (!Player_is_dead)
1082 buddy_message("Hey, your headlight's on!");
1086 if (Buddy_dude_cheat)
1087 do_buddy_dude_stuff();
1089 if (Buddy_sorry_time + F1_0 > GameTime) {
1090 Last_buddy_message_time = 0; // Force this message to get through.
1091 if (Buddy_sorry_time < GameTime + F1_0*2)
1092 buddy_message("Oops, sorry 'bout that...");
1093 Buddy_sorry_time = -F1_0*2;
1096 // If buddy not allowed to talk, then he is locked in his room. Make him mostly do nothing unless you're nearby.
1097 if (!Buddy_allowed_to_talk)
1098 if (dist_to_player > F1_0*100)
1099 aip->SKIP_AI_COUNT = (F1_0/4)/FrameTime;
1101 // -- mprintf((0, "%10s: Dist to player = %7.3f, segnum = %4i\n", mode_text[ailp->mode], f2fl(dist_to_player), objp->segnum));
1103 // AIM_WANDER has been co-opted for buddy behavior (didn't want to modify aistruct.h)
1104 // It means the object has been told to get lost and has come to the end of its path.
1105 // If the player is now visible, then create a path.
1106 if (ailp->mode == AIM_WANDER)
1107 if (player_visibility) {
1108 // -- mprintf((0, "Buddy: Going from wander to path following!\n"));
1109 create_n_segment_path(objp, 16 + d_rand() * 16, -1);
1110 aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length);
1113 if (Escort_special_goal == ESCORT_GOAL_SCRAM) {
1114 if (player_visibility)
1115 if (Escort_last_path_created + F1_0*3 < GameTime) {
1116 mprintf((0, "Frame %i: Buddy creating new scram path.\n", FrameCount));
1117 create_n_segment_path(objp, 10 + d_rand() * 16, ConsoleObject->segnum);
1118 Escort_last_path_created = GameTime;
1122 // -- mprintf((0, "Buddy: Seg = %3i, dist = %7.3f\n", objp->segnum, f2fl(dist_to_player)));
1126 // Force checking for new goal every 5 seconds, and create new path, if necessary.
1127 if (((Escort_special_goal != ESCORT_GOAL_SCRAM) && ((Escort_last_path_created + F1_0*5) < GameTime)) ||
1128 ((Escort_special_goal == ESCORT_GOAL_SCRAM) && ((Escort_last_path_created + F1_0*15) < GameTime))) {
1129 Escort_goal_object = ESCORT_GOAL_UNSPECIFIED;
1130 Escort_last_path_created = GameTime;
1133 if ((Escort_special_goal != ESCORT_GOAL_SCRAM) && time_to_visit_player(objp, ailp, aip)) {
1136 Buddy_last_player_path_created = GameTime;
1137 ailp->mode = AIM_GOTO_PLAYER;
1138 if (!player_visibility) {
1139 if ((Last_come_back_message_time + F1_0 < GameTime) || (Last_come_back_message_time > GameTime)) {
1140 buddy_message("Coming back to get you.");
1141 Last_come_back_message_time = GameTime;
1144 // No point in Buddy creating very long path if he's not allowed to talk. Really kills framerate.
1145 max_len = Max_escort_length;
1146 if (!Buddy_allowed_to_talk)
1148 create_path_to_player(objp, max_len, 1); // MK!: Last parm used to be 1!
1149 aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length);
1150 // -- mprintf((0, "Creating path to player, length = %i\n", aip->path_length));
1151 ailp->mode = AIM_GOTO_PLAYER;
1152 } else if (GameTime - Buddy_last_seen_player > MAX_ESCORT_TIME_AWAY) {
1153 // This is to prevent buddy from looking for a goal, which he will do because we only allow path creation once/second.
1155 } else if ((ailp->mode == AIM_GOTO_PLAYER) && (dist_to_player < MIN_ESCORT_DISTANCE)) {
1156 Escort_goal_object = escort_set_goal_object();
1157 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
1158 escort_create_path_to_goal(objp);
1159 aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length);
1160 // mprintf((0, "Creating path to goal, length = %i\n", aip->path_length));
1161 if (aip->path_length < 3) {
1162 create_n_segment_path(objp, 5, Believed_player_seg);
1163 // mprintf((0, "Path to goal has length %i, just wandering...\n", aip->path_length));
1165 ailp->mode = AIM_GOTO_OBJECT;
1166 } else if (Escort_goal_object == ESCORT_GOAL_UNSPECIFIED) {
1167 if ((ailp->mode != AIM_GOTO_PLAYER) || (dist_to_player < MIN_ESCORT_DISTANCE)) {
1168 Escort_goal_object = escort_set_goal_object();
1169 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
1170 escort_create_path_to_goal(objp);
1171 aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length);
1172 // mprintf((0, "Creating path to goal, length = %i\n", aip->path_length));
1173 if (aip->path_length < 3) {
1174 create_n_segment_path(objp, 5, Believed_player_seg);
1175 // mprintf((0, "Path to goal has length %i, just wandering...\n", aip->path_length));
1177 ailp->mode = AIM_GOTO_OBJECT;
1180 ; // mprintf((0, "!"));
1184 void invalidate_escort_goal(void)
1186 Escort_goal_object = -1;
1189 // -------------------------------------------------------------------------------------------------
1190 void do_snipe_frame(object *objp, fix dist_to_player, int player_visibility, vms_vector *vec_to_player)
1192 int objnum = objp-Objects;
1193 ai_local *ailp = &Ai_local_info[objnum];
1194 fix connected_distance;
1196 if (dist_to_player > F1_0*500)
1199 // -- mprintf((0, "Mode: %10s, Dist: %7.3f\n", mode_text[ailp->mode], f2fl(dist_to_player)));
1201 switch (ailp->mode) {
1202 case AIM_SNIPE_WAIT:
1203 if ((dist_to_player > F1_0*50) && (ailp->next_action_time > 0))
1206 ailp->next_action_time = SNIPE_WAIT_TIME;
1208 connected_distance = find_connected_distance(&objp->pos, objp->segnum, &Believed_player_pos, Believed_player_seg, 30, WID_FLY_FLAG);
1209 if (connected_distance < F1_0*500) {
1210 // -- mprintf((0, "Object #%i entering attack mode.\n", objnum));
1211 create_path_to_player(objp, 30, 1);
1212 ailp->mode = AIM_SNIPE_ATTACK;
1213 ailp->next_action_time = SNIPE_ATTACK_TIME; // have up to 10 seconds to find player.
1217 case AIM_SNIPE_RETREAT:
1218 case AIM_SNIPE_RETREAT_BACKWARDS:
1219 if (ailp->next_action_time < 0) {
1220 ailp->mode = AIM_SNIPE_WAIT;
1221 ailp->next_action_time = SNIPE_WAIT_TIME;
1222 // -- mprintf((0, "Object #%i going from retreat to wait.\n", objnum));
1223 } else if ((player_visibility == 0) || (ailp->next_action_time > SNIPE_ABORT_RETREAT_TIME)) {
1224 ai_follow_path(objp, player_visibility, player_visibility, vec_to_player);
1225 ailp->mode = AIM_SNIPE_RETREAT_BACKWARDS;
1227 // -- mprintf((0, "Object #%i going from retreat to fire.\n", objnum));
1228 ailp->mode = AIM_SNIPE_FIRE;
1229 ailp->next_action_time = SNIPE_FIRE_TIME/2;
1233 case AIM_SNIPE_ATTACK:
1234 if (ailp->next_action_time < 0) {
1235 // -- mprintf((0, "Object #%i timed out from attack to retreat mode.\n", objnum));
1236 ailp->mode = AIM_SNIPE_RETREAT;
1237 ailp->next_action_time = SNIPE_WAIT_TIME;
1239 // -- mprintf((0, "Object #%i attacking: visibility = %i\n", player_visibility));
1240 ai_follow_path(objp, player_visibility, player_visibility, vec_to_player);
1241 if (player_visibility) {
1242 ailp->mode = AIM_SNIPE_FIRE;
1243 ailp->next_action_time = SNIPE_FIRE_TIME;
1245 ailp->mode = AIM_SNIPE_ATTACK;
1249 case AIM_SNIPE_FIRE:
1250 if (ailp->next_action_time < 0) {
1251 ai_static *aip = &objp->ctype.ai_info;
1252 // -- mprintf((0, "Object #%i going from fire to retreat.\n", objnum));
1253 create_n_segment_path(objp, 10 + d_rand()/2048, ConsoleObject->segnum);
1254 aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length);
1255 if (d_rand() < 8192)
1256 ailp->mode = AIM_SNIPE_RETREAT_BACKWARDS;
1258 ailp->mode = AIM_SNIPE_RETREAT;
1259 ailp->next_action_time = SNIPE_RETREAT_TIME;
1265 Int3(); // Oops, illegal mode for snipe behavior.
1266 ailp->mode = AIM_SNIPE_ATTACK;
1267 ailp->next_action_time = F1_0;
1273 #define THIEF_DEPTH 20
1275 extern int pick_connected_segment(object *objp, int max_depth);
1277 // ------------------------------------------------------------------------------------------------------
1278 // Choose segment to recreate thief in.
1279 int choose_thief_recreation_segment(void)
1284 cur_drop_depth = THIEF_DEPTH;
1286 while ((segnum == -1) && (cur_drop_depth > THIEF_DEPTH/2)) {
1287 segnum = pick_connected_segment(&Objects[Players[Player_num].objnum], cur_drop_depth);
1288 if (Segment2s[segnum].special == SEGMENT_IS_CONTROLCEN)
1294 mprintf((1, "Warning: Unable to find a connected segment for thief recreation.\n"));
1295 return (d_rand() * Highest_segment_index) >> 15;
1301 extern object * create_morph_robot( segment *segp, vms_vector *object_pos, int object_id);
1303 fix Re_init_thief_time = 0x3f000000;
1305 // ----------------------------------------------------------------------
1306 void recreate_thief(object *objp)
1309 vms_vector center_point;
1312 segnum = choose_thief_recreation_segment();
1313 compute_segment_center(¢er_point, &Segments[segnum]);
1315 new_obj = create_morph_robot( &Segments[segnum], ¢er_point, objp->id);
1316 init_ai_object(new_obj-Objects, AIB_SNIPE, -1);
1317 Re_init_thief_time = GameTime + F1_0*10; // In 10 seconds, re-initialize thief.
1320 // ----------------------------------------------------------------------------
1321 #define THIEF_ATTACK_TIME (F1_0*10)
1323 fix Thief_wait_times[NDL] = {F1_0*30, F1_0*25, F1_0*20, F1_0*15, F1_0*10};
1325 // -------------------------------------------------------------------------------------------------
1326 void do_thief_frame(object *objp, fix dist_to_player, int player_visibility, vms_vector *vec_to_player)
1328 int objnum = objp-Objects;
1329 ai_local *ailp = &Ai_local_info[objnum];
1330 fix connected_distance;
1332 // -- mprintf((0, "%10s: Action Time: %7.3f\n", mode_text[ailp->mode], f2fl(ailp->next_action_time)));
1334 if ((Current_level_num < 0) && (Re_init_thief_time < GameTime)) {
1335 if (Re_init_thief_time > GameTime - F1_0*2)
1336 init_thief_for_level();
1337 Re_init_thief_time = 0x3f000000;
1340 if ((dist_to_player > F1_0*500) && (ailp->next_action_time > 0))
1344 ailp->mode = AIM_THIEF_RETREAT;
1346 switch (ailp->mode) {
1347 case AIM_THIEF_WAIT:
1348 // -- mprintf((0, "WAIT\n"));
1350 if (ailp->player_awareness_type >= PA_PLAYER_COLLISION) {
1351 ailp->player_awareness_type = 0;
1352 // -- mprintf((0, "Thief: Awareness = %i ", ailp->player_awareness_type));
1354 // -- mprintf((0, "ATTACK\n"));
1355 create_path_to_player(objp, 30, 1);
1356 ailp->mode = AIM_THIEF_ATTACK;
1357 ailp->next_action_time = THIEF_ATTACK_TIME/2;
1359 } else if (player_visibility) {
1360 // -- mprintf((0, "RETREAT\n"));
1361 create_n_segment_path(objp, 15, ConsoleObject->segnum);
1362 ailp->mode = AIM_THIEF_RETREAT;
1366 if ((dist_to_player > F1_0*50) && (ailp->next_action_time > 0))
1369 ailp->next_action_time = Thief_wait_times[Difficulty_level]/2;
1371 connected_distance = find_connected_distance(&objp->pos, objp->segnum, &Believed_player_pos, Believed_player_seg, 30, WID_FLY_FLAG);
1372 if (connected_distance < F1_0*500) {
1373 // -- mprintf((0, "Thief creating path to player.\n", objnum));
1374 create_path_to_player(objp, 30, 1);
1375 ailp->mode = AIM_THIEF_ATTACK;
1376 ailp->next_action_time = THIEF_ATTACK_TIME; // have up to 10 seconds to find player.
1381 case AIM_THIEF_RETREAT:
1382 // -- mprintf((0, "RETREAT\n"));
1384 if (ailp->next_action_time < 0) {
1385 ailp->mode = AIM_THIEF_WAIT;
1386 ailp->next_action_time = Thief_wait_times[Difficulty_level];
1387 } else if ((dist_to_player < F1_0*100) || player_visibility || (ailp->player_awareness_type >= PA_PLAYER_COLLISION)) {
1388 ai_follow_path(objp, player_visibility, player_visibility, vec_to_player);
1389 if ((dist_to_player < F1_0*100) || (ailp->player_awareness_type >= PA_PLAYER_COLLISION)) {
1390 ai_static *aip = &objp->ctype.ai_info;
1391 if (((aip->cur_path_index <=1) && (aip->PATH_DIR == -1)) || ((aip->cur_path_index >= aip->path_length-1) && (aip->PATH_DIR == 1))) {
1392 ailp->player_awareness_type = 0;
1393 create_n_segment_path(objp, 10, ConsoleObject->segnum);
1395 // If path is real short, try again, allowing to go through player's segment
1396 if (aip->path_length < 4) {
1397 // -- mprintf((0, "Thief is cornered. Willing to fly through player.\n"));
1398 create_n_segment_path(objp, 10, -1);
1399 } else if (objp->shields* 4 < Robot_info[objp->id].strength) {
1400 // If robot really low on hits, will run through player with even longer path
1401 if (aip->path_length < 8) {
1402 create_n_segment_path(objp, 10, -1);
1406 ailp->mode = AIM_THIEF_RETREAT;
1407 // -- mprintf((0, "Thief creating new RETREAT path.\n"));
1410 ailp->mode = AIM_THIEF_RETREAT;
1416 // This means the thief goes from wherever he is to the player.
1417 // Note: When thief successfully steals something, his action time is forced negative and his mode is changed
1418 // to retreat to get him out of attack mode.
1419 case AIM_THIEF_ATTACK:
1420 // -- mprintf((0, "ATTACK\n"));
1422 if (ailp->player_awareness_type >= PA_PLAYER_COLLISION) {
1423 ailp->player_awareness_type = 0;
1424 if (d_rand() > 8192) {
1425 // --- mprintf((0, "RETREAT!!\n"));
1426 create_n_segment_path(objp, 10, ConsoleObject->segnum);
1427 Ai_local_info[objp-Objects].next_action_time = Thief_wait_times[Difficulty_level]/2;
1428 Ai_local_info[objp-Objects].mode = AIM_THIEF_RETREAT;
1430 } else if (ailp->next_action_time < 0) {
1431 // This forces him to create a new path every second.
1432 ailp->next_action_time = F1_0;
1433 create_path_to_player(objp, 100, 0);
1434 ailp->mode = AIM_THIEF_ATTACK;
1435 // -- mprintf((0, "Creating path to player.\n"));
1437 if (player_visibility && (dist_to_player < F1_0*100)) {
1438 // If the player is close to looking at the thief, thief shall run away.
1439 // No more stupid thief trying to sneak up on you when you're looking right at him!
1440 if (dist_to_player > F1_0*60) {
1441 fix dot = vm_vec_dot(vec_to_player, &ConsoleObject->orient.fvec);
1442 if (dot < -F1_0/2) { // Looking at least towards thief, so thief will run!
1443 create_n_segment_path(objp, 10, ConsoleObject->segnum);
1444 Ai_local_info[objp-Objects].next_action_time = Thief_wait_times[Difficulty_level]/2;
1445 Ai_local_info[objp-Objects].mode = AIM_THIEF_RETREAT;
1448 ai_turn_towards_vector(vec_to_player, objp, F1_0/4);
1449 move_towards_player(objp, vec_to_player);
1451 ai_static *aip = &objp->ctype.ai_info;
1452 // If path length == 0, then he will keep trying to create path, but he is probably stuck in his closet.
1453 if ((aip->path_length > 1) || ((FrameCount & 0x0f) == 0)) {
1454 ai_follow_path(objp, player_visibility, player_visibility, vec_to_player);
1455 ailp->mode = AIM_THIEF_ATTACK;
1462 mprintf ((0,"Thief mode (broken) = %d\n",ailp->mode));
1463 // -- Int3(); // Oops, illegal mode for thief behavior.
1464 ailp->mode = AIM_THIEF_ATTACK;
1465 ailp->next_action_time = F1_0;
1471 // ----------------------------------------------------------------------------
1472 // Return true if this item (whose presence is indicated by Players[player_num].flags) gets stolen.
1473 int maybe_steal_flag_item(int player_num, int flagval)
1475 if (Players[player_num].flags & flagval) {
1476 if (d_rand() < THIEF_PROBABILITY) {
1477 int powerup_index=-1;
1478 Players[player_num].flags &= (~flagval);
1479 // -- mprintf((0, "You lost your %4x capability!\n", flagval));
1481 case PLAYER_FLAGS_INVULNERABLE:
1482 powerup_index = POW_INVULNERABILITY;
1483 thief_message("Invulnerability stolen!");
1485 case PLAYER_FLAGS_CLOAKED:
1486 powerup_index = POW_CLOAK;
1487 thief_message("Cloak stolen!");
1489 case PLAYER_FLAGS_MAP_ALL:
1490 powerup_index = POW_FULL_MAP;
1491 thief_message("Full map stolen!");
1493 case PLAYER_FLAGS_QUAD_LASERS:
1494 powerup_index = POW_QUAD_FIRE;
1495 thief_message("Quad lasers stolen!");
1497 case PLAYER_FLAGS_AFTERBURNER:
1498 powerup_index = POW_AFTERBURNER;
1499 thief_message("Afterburner stolen!");
1501 // -- case PLAYER_FLAGS_AMMO_RACK:
1502 // -- powerup_index = POW_AMMO_RACK;
1503 // -- thief_message("Ammo Rack stolen!");
1505 case PLAYER_FLAGS_CONVERTER:
1506 powerup_index = POW_CONVERTER;
1507 thief_message("Converter stolen!");
1509 case PLAYER_FLAGS_HEADLIGHT:
1510 powerup_index = POW_HEADLIGHT;
1511 thief_message("Headlight stolen!");
1512 Players[Player_num].flags &= ~PLAYER_FLAGS_HEADLIGHT_ON;
1515 Assert(powerup_index != -1);
1516 Stolen_items[Stolen_item_index] = powerup_index;
1518 digi_play_sample_once(SOUND_WEAPON_STOLEN, F1_0);
1526 // ----------------------------------------------------------------------------
1527 int maybe_steal_secondary_weapon(int player_num, int weapon_num)
1529 if ((Players[player_num].secondary_weapon_flags & HAS_FLAG(weapon_num)) && Players[player_num].secondary_ammo[weapon_num])
1530 if (d_rand() < THIEF_PROBABILITY) {
1531 if (weapon_num == PROXIMITY_INDEX)
1532 if (d_rand() > 8192) // Come in groups of 4, only add 1/4 of time.
1534 Players[player_num].secondary_ammo[weapon_num]--;
1536 // Smart mines and proxbombs don't get dropped because they only come in 4 packs.
1537 if ((weapon_num != PROXIMITY_INDEX) && (weapon_num != SMART_MINE_INDEX)) {
1538 Stolen_items[Stolen_item_index] = Secondary_weapon_to_powerup[weapon_num];
1541 thief_message("%s stolen!", Text_string[114+weapon_num]); // Danger! Danger! Use of literal! Danger!
1542 if (Players[Player_num].secondary_ammo[weapon_num] == 0)
1543 auto_select_weapon(1);
1545 // -- compress_stolen_items();
1546 digi_play_sample_once(SOUND_WEAPON_STOLEN, F1_0);
1553 // ----------------------------------------------------------------------------
1554 int maybe_steal_primary_weapon(int player_num, int weapon_num)
1556 if ((Players[player_num].primary_weapon_flags & HAS_FLAG(weapon_num)) && Players[player_num].primary_ammo[weapon_num]) {
1557 if (d_rand() < THIEF_PROBABILITY) {
1558 if (weapon_num == 0) {
1559 if (Players[player_num].laser_level > 0) {
1560 if (Players[player_num].laser_level > 3) {
1561 Stolen_items[Stolen_item_index] = POW_SUPER_LASER;
1563 Stolen_items[Stolen_item_index] = Primary_weapon_to_powerup[weapon_num];
1565 thief_message("%s level decreased!", Text_string[104+weapon_num]); // Danger! Danger! Use of literal! Danger!
1566 Players[player_num].laser_level--;
1567 digi_play_sample_once(SOUND_WEAPON_STOLEN, F1_0);
1570 } else if (Players[player_num].primary_weapon_flags & (1 << weapon_num)) {
1571 Players[player_num].primary_weapon_flags &= ~(1 << weapon_num);
1572 Stolen_items[Stolen_item_index] = Primary_weapon_to_powerup[weapon_num];
1574 thief_message("%s stolen!", Text_string[104+weapon_num]); // Danger! Danger! Use of literal! Danger!
1575 auto_select_weapon(0);
1576 digi_play_sample_once(SOUND_WEAPON_STOLEN, F1_0);
1587 // ----------------------------------------------------------------------------
1588 // Called for a thief-type robot.
1589 // If a item successfully stolen, returns true, else returns false.
1590 // If a wapon successfully stolen, do everything, removing it from player,
1591 // updating Stolen_items information, deselecting, etc.
1592 int attempt_to_steal_item_3(object *objp, int player_num)
1596 if (Ai_local_info[objp-Objects].mode != AIM_THIEF_ATTACK)
1599 // First, try to steal equipped items.
1601 if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_INVULNERABLE))
1604 // If primary weapon = laser, first try to rip away those nasty quad lasers!
1605 if (Primary_weapon == 0)
1606 if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_QUAD_LASERS))
1609 // Makes it more likely to steal primary than secondary.
1611 if (maybe_steal_primary_weapon(player_num, Primary_weapon))
1614 if (maybe_steal_secondary_weapon(player_num, Secondary_weapon))
1617 // See what the player has and try to snag something.
1618 // Try best things first.
1619 if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_INVULNERABLE))
1621 if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_CLOAKED))
1623 if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_QUAD_LASERS))
1625 if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_AFTERBURNER))
1627 if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_CONVERTER))
1629 // -- 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?
1631 if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_HEADLIGHT))
1633 if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_MAP_ALL))
1636 for (i=MAX_SECONDARY_WEAPONS-1; i>=0; i--) {
1637 if (maybe_steal_primary_weapon(player_num, i))
1639 if (maybe_steal_secondary_weapon(player_num, i))
1646 // ----------------------------------------------------------------------------
1647 int attempt_to_steal_item_2(object *objp, int player_num)
1651 rval = attempt_to_steal_item_3(objp, player_num);
1654 Stolen_item_index = (Stolen_item_index+1) % MAX_STOLEN_ITEMS;
1655 if (d_rand() > 20000) // Occasionally, boost the value again
1656 Stolen_item_index = (Stolen_item_index+1) % MAX_STOLEN_ITEMS;
1662 // ----------------------------------------------------------------------------
1663 // Called for a thief-type robot.
1664 // If a item successfully stolen, returns true, else returns false.
1665 // If a wapon successfully stolen, do everything, removing it from player,
1666 // updating Stolen_items information, deselecting, etc.
1667 int attempt_to_steal_item(object *objp, int player_num)
1672 if (objp->ctype.ai_info.dying_start_time)
1675 rval += attempt_to_steal_item_2(objp, player_num);
1677 for (i=0; i<3; i++) {
1678 if (!rval || (d_rand() < 11000)) { // about 1/3 of time, steal another item
1679 rval += attempt_to_steal_item_2(objp, player_num);
1683 // -- mprintf((0, "%i items were stolen!\n", rval));
1685 create_n_segment_path(objp, 10, ConsoleObject->segnum);
1686 Ai_local_info[objp-Objects].next_action_time = Thief_wait_times[Difficulty_level]/2;
1687 Ai_local_info[objp-Objects].mode = AIM_THIEF_RETREAT;
1689 PALETTE_FLASH_ADD(30, 15, -20);
1690 update_laser_weapon_info();
1691 // digi_link_sound_to_pos( SOUND_NASTY_ROBOT_HIT_1, objp->segnum, 0, &objp->pos, 0 , DEFAULT_ROBOT_SOUND_VOLUME);
1692 // I removed this to make the "steal sound" more obvious -AP
1694 if (Game_mode & GM_NETWORK)
1695 multi_send_stolen_items();
1701 // --------------------------------------------------------------------------------------------------------------
1702 // Indicate no items have been stolen.
1703 void init_thief_for_level(void)
1707 for (i=0; i<MAX_STOLEN_ITEMS; i++)
1708 Stolen_items[i] = 255;
1710 Assert (MAX_STOLEN_ITEMS >= 3*2); // Oops! Loop below will overwrite memory!
1712 if (!(Game_mode & GM_MULTI))
1713 for (i=0; i<3; i++) {
1714 Stolen_items[2*i] = POW_SHIELD_BOOST;
1715 Stolen_items[2*i+1] = POW_ENERGY;
1718 Stolen_item_index = 0;
1721 // --------------------------------------------------------------------------------------------------------------
1722 void drop_stolen_items(object *objp)
1726 mprintf ((0,"Dropping thief items!\n"));
1728 // -- compress_stolen_items();
1730 for (i=0; i<MAX_STOLEN_ITEMS; i++) {
1731 if (Stolen_items[i] != 255)
1732 drop_powerup(OBJ_POWERUP, Stolen_items[i], 1, &objp->mtype.phys_info.velocity, &objp->pos, objp->segnum);
1733 Stolen_items[i] = 255;
1738 // --------------------------------------------------------------------------------------------------------------
1739 void do_escort_menu(void)
1746 char goal_str[32], tstr[32];
1748 if (Game_mode & GM_MULTI) {
1749 HUD_init_message("No Guide-Bot in Multiplayer!");
1753 for (i=0; i<=Highest_object_index; i++) {
1754 if (Objects[i].type == OBJ_ROBOT)
1755 if (Robot_info[Objects[i].id].companion)
1759 if (i > Highest_object_index) {
1761 HUD_init_message("No Guide-Bot present in mine!");
1764 // If no buddy bot, create one!
1765 HUD_init_message("Debug Version: Creating Guide-Bot!");
1772 ok_for_buddy_to_talk(); // Needed here or we might not know buddy can talk when he can.
1774 if (!Buddy_allowed_to_talk) {
1775 HUD_init_message("%s has not been released",guidebot_name);
1779 digi_pause_digi_sounds();
1783 apply_modified_palette();
1784 reset_palette_add();
1786 game_flush_inputs();
1790 // set_screen_mode( SCREEN_MENU );
1792 gr_palette_load( gr_palette );
1794 // This prevents the buddy from coming back if you've told him to scram.
1795 // If we don't set next_goal, we get garbage there.
1796 if (Escort_special_goal == ESCORT_GOAL_SCRAM) {
1797 Escort_special_goal = -1; // Else setting next goal might fail.
1798 next_goal = escort_set_goal_object();
1799 Escort_special_goal = ESCORT_GOAL_SCRAM;
1801 Escort_special_goal = -1; // Else setting next goal might fail.
1802 next_goal = escort_set_goal_object();
1805 switch (next_goal) {
1807 case ESCORT_GOAL_UNSPECIFIED:
1809 sprintf(goal_str, "ERROR");
1813 case ESCORT_GOAL_BLUE_KEY:
1814 sprintf(goal_str, "blue key");
1816 case ESCORT_GOAL_GOLD_KEY:
1817 sprintf(goal_str, "yellow key");
1819 case ESCORT_GOAL_RED_KEY:
1820 sprintf(goal_str, "red key");
1822 case ESCORT_GOAL_CONTROLCEN:
1823 sprintf(goal_str, "reactor");
1825 case ESCORT_GOAL_BOSS:
1826 sprintf(goal_str, "boss");
1828 case ESCORT_GOAL_EXIT:
1829 sprintf(goal_str, "exit");
1831 case ESCORT_GOAL_MARKER1:
1832 case ESCORT_GOAL_MARKER2:
1833 case ESCORT_GOAL_MARKER3:
1834 case ESCORT_GOAL_MARKER4:
1835 case ESCORT_GOAL_MARKER5:
1836 case ESCORT_GOAL_MARKER6:
1837 case ESCORT_GOAL_MARKER7:
1838 case ESCORT_GOAL_MARKER8:
1839 case ESCORT_GOAL_MARKER9:
1840 sprintf(goal_str, "marker %i", next_goal-ESCORT_GOAL_MARKER1+1);
1845 if (!Buddy_messages_suppressed)
1846 sprintf(tstr, "Suppress");
1848 sprintf(tstr, "Enable");
1850 sprintf(msg, "Select Guide-Bot Command:\n\n"
1851 "0. Next Goal: %s" CC_LSPACING_S "3\n"
1852 "\x84. Find Energy Powerup" CC_LSPACING_S "3\n"
1853 "2. Find Energy Center" CC_LSPACING_S "3\n"
1854 "3. Find Shield Powerup" CC_LSPACING_S "3\n"
1855 "4. Find Any Powerup" CC_LSPACING_S "3\n"
1856 "5. Find a Robot" CC_LSPACING_S "3\n"
1857 "6. Find a Hostage" CC_LSPACING_S "3\n"
1858 "7. Stay Away From Me" CC_LSPACING_S "3\n"
1859 "8. Find My Powerups" CC_LSPACING_S "3\n"
1860 "9. Find the exit\n\n"
1862 // -- "9. Find the exit" CC_LSPACING_S "3\n"
1865 show_escort_menu(msg); //TXT_PAUSE);
1869 while (!(key = key_inkey()))
1872 DoMessageStuff(&wmsg);
1889 Looking_for_marker = -1;
1890 Last_buddy_key = -1;
1891 set_escort_special_goal(key);
1892 Last_buddy_key = -1;
1898 clear_boxed_message();
1902 //--10/08/95-- Screwed up font, background. Why needed, anyway?
1903 //--10/08/95-- case KEY_F1:
1904 //--10/08/95-- clear_boxed_message();
1905 //--10/08/95-- do_show_help();
1906 //--10/08/95-- show_boxed_message(msg);
1907 //--10/08/95-- break;
1909 case KEY_PRINT_SCREEN:
1910 save_screen_shot(0);
1914 case KEY_BACKSP: Int3(); break;
1921 temp = !Buddy_messages_suppressed;
1924 strcpy(msg, "suppressed");
1926 strcpy(msg, "enabled");
1928 Buddy_messages_suppressed = 1;
1929 buddy_message("Messages %s.", msg);
1931 Buddy_messages_suppressed = temp;
1944 game_flush_inputs();
1949 digi_resume_digi_sounds();
1953 // -------------------------------------------------------------------------------
1954 // Show the Buddy menu!
1955 void show_escort_menu(char *msg)
1962 dd_gr_set_current_canvas(&dd_VR_screen_pages[0]),
1963 gr_set_current_canvas(&VR_screen_pages[0])
1966 gr_set_curfont( GAME_FONT );
1968 gr_get_string_size(msg,&w,&h,&aw);
1970 x = (grd_curscreen->sc_w-w)/2;
1971 y = (grd_curscreen->sc_h-h)/4;
1973 gr_set_fontcolor( gr_getcolor(0, 28, 0), -1 );
1975 PA_DFX (pa_set_frontbuffer_current());
1976 PA_DFX (nm_draw_background(x-15,y-15,x+w+15-1,y+h+15-1));
1977 PA_DFX (pa_set_backbuffer_current());
1978 nm_draw_background(x-15,y-15,x+w+15-1,y+h+15-1);
1980 WIN(DDGRLOCK(dd_grd_curcanv));\
1981 PA_DFX (pa_set_frontbuffer_current());
1982 PA_DFX (gr_ustring( x, y, msg ));
1983 PA_DFX (pa_set_backbuffer_current());
1984 gr_ustring( x, y, msg );
1985 WIN(DDGRUNLOCK(dd_grd_curcanv));