2 THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
3 SOFTWARE CORPORATION ("PARALLAX"). PARALLAX, IN DISTRIBUTING THE CODE TO
4 END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
5 ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
6 IN USING, DISPLAYING, AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
7 SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
8 FREE PURPOSES. IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
9 CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES. THE END-USER UNDERSTANDS
10 AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.
11 COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED.
16 * Escort robot behavior.
24 #include <stdio.h> // for printf()
25 #include <stdlib.h> // for rand() and qsort()
26 #include <string.h> // for memset()
37 #include "editor/editor.h"
41 extern void multi_send_stolen_items(void);
42 void say_escort_goal(int goal_num);
43 void show_escort_menu(char *msg);
46 static const char *const Escort_goal_text[MAX_ESCORT_GOALS] = {
71 // -- too much work -- "KAMIKAZE "
74 int Max_escort_length = 200;
75 int Escort_kill_object = -1;
76 ubyte Stolen_items[MAX_STOLEN_ITEMS];
77 int Stolen_item_index;
78 fix Escort_last_path_created = 0;
79 int Escort_goal_object = ESCORT_GOAL_UNSPECIFIED, Escort_special_goal = -1, Escort_goal_index = -1, Buddy_messages_suppressed = 0;
81 int Buddy_objnum, Buddy_allowed_to_talk;
82 int Looking_for_marker;
85 fix Last_buddy_message_time;
87 char guidebot_name[GUIDEBOT_NAME_LEN+1] = "GUIDE-BOT";
88 cvar_t real_guidebot_name = { "GuideBotName", "GUIDE-BOT", CVAR_ARCHIVE };
90 void init_buddy_for_level(void)
94 Buddy_allowed_to_talk = 0;
96 Escort_goal_object = ESCORT_GOAL_UNSPECIFIED;
97 Escort_special_goal = -1;
98 Escort_goal_index = -1;
99 Buddy_messages_suppressed = 0;
101 for (i=0; i<=Highest_object_index; i++)
102 if (Robot_info[Objects[i].id].companion)
104 if (i <= Highest_object_index)
107 Buddy_sorry_time = -F1_0;
109 Looking_for_marker = -1;
113 // -----------------------------------------------------------------------------
114 // See if segment from curseg through sidenum is reachable.
115 // Return true if it is reachable, else return false.
116 int segment_is_reachable(int curseg, int sidenum)
119 segment *segp = &Segments[curseg];
121 if (!IS_CHILD(segp->children[sidenum]))
124 wall_num = segp->sides[sidenum].wall_num;
126 // If no wall, then it is reachable
130 rval = ai_door_is_openable(NULL, segp, sidenum);
134 // -- MK, 10/17/95 --
135 // -- MK, 10/17/95 -- // Hmm, a closed wall. I think this mean not reachable.
136 // -- MK, 10/17/95 -- if (Walls[wall_num].type == WALL_CLOSED)
137 // -- MK, 10/17/95 -- return 0;
138 // -- MK, 10/17/95 --
139 // -- MK, 10/17/95 -- if (Walls[wall_num].type == WALL_DOOR) {
140 // -- MK, 10/17/95 -- if (Walls[wall_num].keys == KEY_NONE) {
141 // -- MK, 10/17/95 -- return 1; // @MK, 10/17/95: Be consistent with ai_door_is_openable
142 // -- MK, 10/17/95 -- // -- if (Walls[wall_num].flags & WALL_DOOR_LOCKED)
143 // -- MK, 10/17/95 -- // -- return 0;
144 // -- MK, 10/17/95 -- // -- else
145 // -- MK, 10/17/95 -- // -- return 1;
146 // -- MK, 10/17/95 -- } else if (Walls[wall_num].keys == KEY_BLUE)
147 // -- MK, 10/17/95 -- return (Players[Player_num].flags & PLAYER_FLAGS_BLUE_KEY);
148 // -- MK, 10/17/95 -- else if (Walls[wall_num].keys == KEY_GOLD)
149 // -- MK, 10/17/95 -- return (Players[Player_num].flags & PLAYER_FLAGS_GOLD_KEY);
150 // -- MK, 10/17/95 -- else if (Walls[wall_num].keys == KEY_RED)
151 // -- MK, 10/17/95 -- return (Players[Player_num].flags & PLAYER_FLAGS_RED_KEY);
152 // -- MK, 10/17/95 -- else
153 // -- MK, 10/17/95 -- Int3(); // Impossible! Doesn't have no key, but doesn't have any key!
154 // -- MK, 10/17/95 -- } else
155 // -- MK, 10/17/95 -- return 1;
156 // -- MK, 10/17/95 --
157 // -- MK, 10/17/95 -- Int3(); // Hmm, thought 'if' above had to return!
158 // -- MK, 10/17/95 -- return 0;
163 // -----------------------------------------------------------------------------
164 // Create a breadth-first list of segments reachable from current segment.
165 // max_segs is maximum number of segments to search. Use MAX_SEGMENTS to search all.
166 // On exit, *length <= max_segs.
170 // bfs_list: array of shorts, each reachable segment. Includes start segment.
171 // length: number of elements in bfs_list
172 void create_bfs_list(int start_seg, short bfs_list[], int *length, int max_segs)
175 sbyte visited[MAX_SEGMENTS];
177 for (i=0; i<MAX_SEGMENTS; i++)
183 bfs_list[head++] = start_seg;
184 visited[start_seg] = 1;
186 while ((head != tail) && (head < max_segs)) {
191 curseg = bfs_list[tail++];
192 cursegp = &Segments[curseg];
194 for (i=0; i<MAX_SIDES_PER_SEGMENT; i++) {
197 connected_seg = cursegp->children[i];
199 if (IS_CHILD(connected_seg) && (visited[connected_seg] == 0)) {
200 if (segment_is_reachable(curseg, i)) {
201 bfs_list[head++] = connected_seg;
202 if (head >= max_segs)
204 visited[connected_seg] = 1;
205 Assert(head < MAX_SEGMENTS);
215 // -----------------------------------------------------------------------------
216 // Return true if ok for buddy to talk, else return false.
217 // Buddy is allowed to talk if the segment he is in does not contain a blastable wall that has not been blasted
218 // AND he has never yet, since being initialized for level, been allowed to talk.
219 int ok_for_buddy_to_talk(void)
224 if (Objects[Buddy_objnum].type != OBJ_ROBOT) {
225 Buddy_allowed_to_talk = 0;
229 if (Buddy_allowed_to_talk)
232 if ((Objects[Buddy_objnum].type == OBJ_ROBOT) && (Buddy_objnum <= Highest_object_index) && !Robot_info[Objects[Buddy_objnum].id].companion) {
233 for (i=0; i<=Highest_object_index; i++)
234 if (Robot_info[Objects[i].id].companion)
236 if (i > Highest_object_index)
242 segp = &Segments[Objects[Buddy_objnum].segnum];
244 for (i=0; i<MAX_SIDES_PER_SEGMENT; i++) {
245 int wall_num = segp->sides[i].wall_num;
247 if (wall_num != -1) {
248 if ((Walls[wall_num].type == WALL_BLASTABLE) && !(Walls[wall_num].flags & WALL_BLASTED))
252 // Check one level deeper.
253 if (IS_CHILD(segp->children[i])) {
255 segment *csegp = &Segments[segp->children[i]];
257 for (j=0; j<MAX_SIDES_PER_SEGMENT; j++) {
258 int wall2 = csegp->sides[j].wall_num;
261 if ((Walls[wall2].type == WALL_BLASTABLE) && !(Walls[wall2].flags & WALL_BLASTED))
268 Buddy_allowed_to_talk = 1;
272 // --------------------------------------------------------------------------------------------
273 void detect_escort_goal_accomplished(int index)
278 if (!Buddy_allowed_to_talk)
281 // If goal is to go away, how can it be achieved?
282 if (Escort_special_goal == ESCORT_GOAL_SCRAM)
285 // See if goal found was a key. Need to handle default goals differently.
286 // Note, no buddy_met_goal sound when blow up reactor or exit. Not great, but ok
287 // since for reactor, noisy, for exit, buddy is disappearing.
288 if ((Escort_special_goal == -1) && (Escort_goal_index == index)) {
293 if ((Escort_goal_index <= ESCORT_GOAL_RED_KEY) && (index >= 0)) {
294 if (Objects[index].type == OBJ_POWERUP) {
295 if (Objects[index].id == POW_KEY_BLUE) {
296 if (Escort_goal_index == ESCORT_GOAL_BLUE_KEY) {
300 } else if (Objects[index].id == POW_KEY_GOLD) {
301 if (Escort_goal_index == ESCORT_GOAL_GOLD_KEY) {
305 } else if (Objects[index].id == POW_KEY_RED) {
306 if (Escort_goal_index == ESCORT_GOAL_RED_KEY) {
313 if (Escort_special_goal != -1)
315 if (Escort_special_goal == ESCORT_GOAL_ENERGYCEN) {
319 for (i=0; i<MAX_SIDES_PER_SEGMENT; i++)
320 if (Segments[index].children[i] == Escort_goal_index) {
324 for (j=0; j<MAX_SIDES_PER_SEGMENT; j++)
325 if (Segments[i].children[j] == Escort_goal_index) {
331 } else if ((Objects[index].type == OBJ_POWERUP) && (Escort_special_goal == ESCORT_GOAL_POWERUP))
332 detected = 1; // Any type of powerup picked up will do.
333 else if ((Objects[index].type == Objects[Escort_goal_index].type) && (Objects[index].id == Objects[Escort_goal_index].id)) {
334 // Note: This will help a little bit in making the buddy believe a goal is satisfied. Won't work for a general goal like "find any powerup"
335 // because of the insistence of both type and id matching.
341 if (detected && ok_for_buddy_to_talk()) {
342 digi_play_sample_once(SOUND_BUDDY_MET_GOAL, F1_0);
343 Escort_goal_index = -1;
344 Escort_goal_object = ESCORT_GOAL_UNSPECIFIED;
345 Escort_special_goal = -1;
346 Looking_for_marker = -1;
351 void change_guidebot_name()
354 char text[GUIDEBOT_NAME_LEN+1]="";
357 strcpy(text,guidebot_name);
359 m.type=NM_TYPE_INPUT; m.text_len = GUIDEBOT_NAME_LEN; m.text = text;
360 item = newmenu_do( NULL, "Enter Guide-bot name:", 1, &m, NULL );
363 strcpy(guidebot_name,text);
364 cvar_set_cvar(&real_guidebot_name, text);
368 // -----------------------------------------------------------------------------
369 void buddy_message(char * format, ... )
371 if (Buddy_messages_suppressed)
374 if (Game_mode & GM_MULTI)
377 if ((Last_buddy_message_time + F1_0 < GameTime) || (Last_buddy_message_time > GameTime)) {
378 if (ok_for_buddy_to_talk()) {
379 char gb_str[16], new_format[128];
383 va_start(args, format );
384 vsprintf(new_format, format, args);
388 gb_str[1] = BM_XRGB(28, 0, 0);
389 strcpy(&gb_str[2], guidebot_name);
390 t = (int)strlen(gb_str);
393 gb_str[t+2] = BM_XRGB(0, 31, 0);
396 HUD_init_message("%s %s", gb_str, new_format);
398 Last_buddy_message_time = GameTime;
404 // -----------------------------------------------------------------------------
405 void thief_message(char * format, ... )
408 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], "THIEF:");
419 gb_str[9] = BM_XRGB(0, 31, 0);
422 HUD_init_message("%s %s", gb_str, new_format);
426 // -----------------------------------------------------------------------------
427 // Return true if marker #id has been placed.
428 int marker_exists_in_mine(int id)
432 for (i=0; i<=Highest_object_index; i++)
433 if (Objects[i].type == OBJ_MARKER)
434 if (Objects[i].id == id)
440 // -----------------------------------------------------------------------------
441 void set_escort_special_goal(int special_key)
445 Buddy_messages_suppressed = 0;
447 if (!Buddy_allowed_to_talk) {
448 ok_for_buddy_to_talk();
449 if (!Buddy_allowed_to_talk) {
452 for (i=0; i<=Highest_object_index; i++)
453 if ((Objects[i].type == OBJ_ROBOT) && Robot_info[Objects[i].id].companion) {
454 HUD_init_message("%s has not been released.",guidebot_name);
457 if (i == Highest_object_index+1)
458 HUD_init_message("No Guide-Bot in mine.");
464 special_key = special_key & (~KEY_SHIFTED);
466 marker_key = special_key;
469 switch(special_key) {
471 marker_key = KEY_1+4;
474 marker_key = KEY_1+5;
477 marker_key = KEY_1+6;
480 marker_key = KEY_1+7;
483 marker_key = KEY_1+8;
486 marker_key = KEY_1+9;
491 if (Last_buddy_key == special_key)
493 if ((Looking_for_marker == -1) && (special_key != KEY_0)) {
494 if (marker_exists_in_mine(marker_key - KEY_1))
495 Looking_for_marker = marker_key - KEY_1;
497 Last_buddy_message_time = 0; // Force this message to get through.
498 buddy_message("Marker %i not placed.", marker_key - KEY_1 + 1);
499 Looking_for_marker = -1;
502 Looking_for_marker = -1;
506 Last_buddy_key = special_key;
508 if (special_key == KEY_0)
509 Looking_for_marker = -1;
511 if ( Looking_for_marker != -1 ) {
512 Escort_special_goal = ESCORT_GOAL_MARKER1 + marker_key - KEY_1;
514 switch (special_key) {
515 case KEY_1: Escort_special_goal = ESCORT_GOAL_ENERGY; break;
516 case KEY_2: Escort_special_goal = ESCORT_GOAL_ENERGYCEN; break;
517 case KEY_3: Escort_special_goal = ESCORT_GOAL_SHIELD; break;
518 case KEY_4: Escort_special_goal = ESCORT_GOAL_POWERUP; break;
519 case KEY_5: Escort_special_goal = ESCORT_GOAL_ROBOT; break;
520 case KEY_6: Escort_special_goal = ESCORT_GOAL_HOSTAGE; break;
521 case KEY_7: Escort_special_goal = ESCORT_GOAL_SCRAM; break;
522 case KEY_8: Escort_special_goal = ESCORT_GOAL_PLAYER_SPEW; break;
523 case KEY_9: Escort_special_goal = ESCORT_GOAL_EXIT; break;
524 case KEY_0: Escort_special_goal = -1; break;
526 Int3(); // Oops, called with illegal key value.
530 Last_buddy_message_time = GameTime - 2*F1_0; // Allow next message to come through.
532 say_escort_goal(Escort_special_goal);
533 // -- Escort_goal_object = escort_set_goal_object();
535 Escort_goal_object = ESCORT_GOAL_UNSPECIFIED;
538 // -- old, pre-bfs, way -- // -----------------------------------------------------------------------------
539 // -- old, pre-bfs, way -- // Return object of interest.
540 // -- old, pre-bfs, way -- int exists_in_mine(int objtype, int objid)
541 // -- old, pre-bfs, way -- {
542 // -- old, pre-bfs, way -- int i;
543 // -- old, pre-bfs, way --
544 // -- old, pre-bfs, way -- mprintf((0, "exists_in_mine, type == %i, id == %i\n", objtype, objid));
545 // -- old, pre-bfs, way --
546 // -- old, pre-bfs, way -- if (objtype == FUELCEN_CHECK) {
547 // -- old, pre-bfs, way -- for (i=0; i<=Highest_segment_index; i++)
548 // -- old, pre-bfs, way -- if (Segments[i].special == SEGMENT_IS_FUELCEN)
549 // -- old, pre-bfs, way -- return i;
550 // -- old, pre-bfs, way -- } else {
551 // -- old, pre-bfs, way -- for (i=0; i<=Highest_object_index; i++) {
552 // -- old, pre-bfs, way -- if (Objects[i].type == objtype) {
553 // -- old, pre-bfs, way -- // Don't find escort robots if looking for robot!
554 // -- old, pre-bfs, way -- if ((Objects[i].type == OBJ_ROBOT) && (Robot_info[Objects[i].id].companion))
555 // -- old, pre-bfs, way -- continue;
556 // -- old, pre-bfs, way --
557 // -- old, pre-bfs, way -- if (objid == -1) {
558 // -- old, pre-bfs, way -- if ((objtype == OBJ_POWERUP) && (Objects[i].id != POW_KEY_BLUE) && (Objects[i].id != POW_KEY_GOLD) && (Objects[i].id != POW_KEY_RED))
559 // -- old, pre-bfs, way -- return i;
560 // -- old, pre-bfs, way -- else
561 // -- old, pre-bfs, way -- return i;
562 // -- old, pre-bfs, way -- } else if (Objects[i].id == objid)
563 // -- old, pre-bfs, way -- return i;
564 // -- old, pre-bfs, way -- }
565 // -- old, pre-bfs, way -- }
566 // -- old, pre-bfs, way -- }
567 // -- old, pre-bfs, way --
568 // -- old, pre-bfs, way -- return -1;
569 // -- old, pre-bfs, way --
570 // -- old, pre-bfs, way -- }
572 // -----------------------------------------------------------------------------
573 // Return id of boss.
574 int get_boss_id(void)
578 for (i=0; i<=Highest_object_index; i++)
579 if (Objects[i].type == OBJ_ROBOT)
580 if (Robot_info[Objects[i].id].boss_flag)
581 return Objects[i].id;
586 // -----------------------------------------------------------------------------
587 // Return object index if object of objtype, objid exists in mine, else return -1
588 // "special" is used to find objects spewed by player which is hacked into flags field of powerup.
589 int exists_in_mine_2(int segnum, int objtype, int objid, int special)
591 if (Segments[segnum].objects != -1) {
592 int objnum = Segments[segnum].objects;
594 while (objnum != -1) {
595 object *curobjp = &Objects[objnum];
597 if (special == ESCORT_GOAL_PLAYER_SPEW) {
598 if (curobjp->flags & OF_PLAYER_DROPPED)
602 if (curobjp->type == objtype) {
603 // Don't find escort robots if looking for robot!
604 if ((curobjp->type == OBJ_ROBOT) && (Robot_info[curobjp->id].companion))
606 else if (objid == -1) {
607 if ((objtype == OBJ_POWERUP) && (curobjp->id != POW_KEY_BLUE) && (curobjp->id != POW_KEY_GOLD) && (curobjp->id != POW_KEY_RED))
611 } else if (curobjp->id == objid)
615 if (objtype == OBJ_POWERUP)
616 if (curobjp->contains_count)
617 if (curobjp->contains_type == OBJ_POWERUP)
618 if (curobjp->contains_id == objid)
621 objnum = curobjp->next;
628 // -----------------------------------------------------------------------------
629 // Return nearest object of interest.
630 // If special == ESCORT_GOAL_PLAYER_SPEW, then looking for any object spewed by player.
631 // -1 means object does not exist in mine.
632 // -2 means object does exist in mine, but buddy-bot can't reach it (eg, behind triggered wall)
633 int exists_in_mine(int start_seg, int objtype, int objid, int special)
635 int segindex, segnum;
636 short bfs_list[MAX_SEGMENTS];
639 // mprintf((0, "exists_in_mine, type == %i, id == %i\n", objtype, objid));
641 create_bfs_list(start_seg, bfs_list, &length, MAX_SEGMENTS);
643 if (objtype == FUELCEN_CHECK) {
644 for (segindex=0; segindex<length; segindex++) {
645 segnum = bfs_list[segindex];
646 if (Segment2s[segnum].special == SEGMENT_IS_FUELCEN)
650 for (segindex=0; segindex<length; segindex++) {
653 segnum = bfs_list[segindex];
655 objnum = exists_in_mine_2(segnum, objtype, objid, special);
662 // Couldn't find what we're looking for by looking at connectivity.
663 // See if it's in the mine. It could be hidden behind a trigger or switch
664 // which the buddybot doesn't understand.
665 if (objtype == FUELCEN_CHECK) {
666 for (segnum=0; segnum<=Highest_segment_index; segnum++)
667 if (Segment2s[segnum].special == SEGMENT_IS_FUELCEN)
670 for (segnum=0; segnum<=Highest_segment_index; segnum++) {
673 objnum = exists_in_mine_2(segnum, objtype, objid, special);
682 // -----------------------------------------------------------------------------
683 // Return true if it happened, else return false.
684 int find_exit_segment(void)
688 // ---------- Find exit doors ----------
689 for (i=0; i<=Highest_segment_index; i++)
690 for (j=0; j<MAX_SIDES_PER_SEGMENT; j++)
691 if (Segments[i].children[j] == -2) {
698 #define BUDDY_MARKER_TEXT_LEN 25
700 // -----------------------------------------------------------------------------
701 void say_escort_goal(int goal_num)
707 case ESCORT_GOAL_BLUE_KEY: buddy_message("Finding BLUE KEY"); break;
708 case ESCORT_GOAL_GOLD_KEY: buddy_message("Finding YELLOW KEY"); break;
709 case ESCORT_GOAL_RED_KEY: buddy_message("Finding RED KEY"); break;
710 case ESCORT_GOAL_CONTROLCEN: buddy_message("Finding REACTOR"); break;
711 case ESCORT_GOAL_EXIT: buddy_message("Finding EXIT"); break;
712 case ESCORT_GOAL_ENERGY: buddy_message("Finding ENERGY"); break;
713 case ESCORT_GOAL_ENERGYCEN: buddy_message("Finding ENERGY CENTER"); break;
714 case ESCORT_GOAL_SHIELD: buddy_message("Finding a SHIELD"); break;
715 case ESCORT_GOAL_POWERUP: buddy_message("Finding a POWERUP"); break;
716 case ESCORT_GOAL_ROBOT: buddy_message("Finding a ROBOT"); break;
717 case ESCORT_GOAL_HOSTAGE: buddy_message("Finding a HOSTAGE"); break;
718 case ESCORT_GOAL_SCRAM: buddy_message("Staying away..."); break;
719 case ESCORT_GOAL_BOSS: buddy_message("Finding BOSS robot"); break;
720 case ESCORT_GOAL_PLAYER_SPEW: buddy_message("Finding your powerups"); break;
721 case ESCORT_GOAL_MARKER1:
722 case ESCORT_GOAL_MARKER2:
723 case ESCORT_GOAL_MARKER3:
724 case ESCORT_GOAL_MARKER4:
725 case ESCORT_GOAL_MARKER5:
726 case ESCORT_GOAL_MARKER6:
727 case ESCORT_GOAL_MARKER7:
728 case ESCORT_GOAL_MARKER8:
729 case ESCORT_GOAL_MARKER9:
730 { char marker_text[BUDDY_MARKER_TEXT_LEN];
731 strncpy(marker_text, MarkerMessage[goal_num-ESCORT_GOAL_MARKER1], BUDDY_MARKER_TEXT_LEN-1);
732 marker_text[BUDDY_MARKER_TEXT_LEN-1] = 0;
733 buddy_message("Finding marker %i: '%s'", goal_num-ESCORT_GOAL_MARKER1+1, marker_text);
739 // -----------------------------------------------------------------------------
740 void escort_create_path_to_goal(object *objp)
743 int objnum = OBJECT_NUMBER(objp);
744 ai_static *aip = &objp->ctype.ai_info;
745 ai_local *ailp = &Ai_local_info[objnum];
747 if (Escort_special_goal != -1)
748 Escort_goal_object = Escort_special_goal;
750 Escort_kill_object = -1;
752 if (Looking_for_marker != -1) {
754 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_MARKER, Escort_goal_object-ESCORT_GOAL_MARKER1, -1);
755 if (Escort_goal_index > -1)
756 goal_seg = Objects[Escort_goal_index].segnum;
758 switch (Escort_goal_object) {
759 case ESCORT_GOAL_BLUE_KEY:
760 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_POWERUP, POW_KEY_BLUE, -1);
761 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
763 case ESCORT_GOAL_GOLD_KEY:
764 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_POWERUP, POW_KEY_GOLD, -1);
765 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
767 case ESCORT_GOAL_RED_KEY:
768 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_POWERUP, POW_KEY_RED, -1);
769 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
771 case ESCORT_GOAL_CONTROLCEN:
772 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_CNTRLCEN, -1, -1);
773 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
775 case ESCORT_GOAL_EXIT:
776 case ESCORT_GOAL_EXIT2:
777 goal_seg = find_exit_segment();
778 Escort_goal_index = goal_seg;
780 case ESCORT_GOAL_ENERGY:
781 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_POWERUP, POW_ENERGY, -1);
782 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
784 case ESCORT_GOAL_ENERGYCEN:
785 goal_seg = exists_in_mine(objp->segnum, FUELCEN_CHECK, -1, -1);
786 Escort_goal_index = goal_seg;
788 case ESCORT_GOAL_SHIELD:
789 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_POWERUP, POW_SHIELD_BOOST, -1);
790 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
792 case ESCORT_GOAL_POWERUP:
793 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_POWERUP, -1, -1);
794 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
796 case ESCORT_GOAL_ROBOT:
797 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_ROBOT, -1, -1);
798 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
800 case ESCORT_GOAL_HOSTAGE:
801 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_HOSTAGE, -1, -1);
802 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
804 case ESCORT_GOAL_PLAYER_SPEW:
805 Escort_goal_index = exists_in_mine(objp->segnum, -1, -1, ESCORT_GOAL_PLAYER_SPEW);
806 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
808 case ESCORT_GOAL_SCRAM:
809 goal_seg = -3; // Kinda a hack.
810 Escort_goal_index = goal_seg;
812 case ESCORT_GOAL_BOSS: {
815 boss_id = get_boss_id();
816 Assert(boss_id != -1);
817 Escort_goal_index = exists_in_mine(objp->segnum, OBJ_ROBOT, boss_id, -1);
818 if (Escort_goal_index > -1) goal_seg = Objects[Escort_goal_index].segnum;
822 Int3(); // Oops, Illegal value in Escort_goal_object.
828 // -- mprintf((0, "Creating path from escort to goal #%i in segment #%i.\n", Escort_goal_object, goal_seg));
829 if ((Escort_goal_index < 0) && (Escort_goal_index != -3)) { // I apologize for this statement -- MK, 09/22/95
830 if (Escort_goal_index == -1) {
831 Last_buddy_message_time = 0; // Force this message to get through.
832 buddy_message("No %s in mine.", Escort_goal_text[Escort_goal_object-1]);
833 Looking_for_marker = -1;
834 } else if (Escort_goal_index == -2) {
835 Last_buddy_message_time = 0; // Force this message to get through.
836 buddy_message("Can't reach %s.", Escort_goal_text[Escort_goal_object-1]);
837 Looking_for_marker = -1;
841 Escort_goal_object = ESCORT_GOAL_UNSPECIFIED;
842 Escort_special_goal = -1;
844 if (goal_seg == -3) {
845 create_n_segment_path(objp, 16 + d_rand() * 16, -1);
846 aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length);
848 create_path_to_segment(objp, goal_seg, Max_escort_length, 1); // MK!: Last parm (safety_flag) used to be 1!!
849 if (aip->path_length > 3)
850 aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length);
851 if ((aip->path_length > 0) && (Point_segs[aip->hide_index + aip->path_length - 1].segnum != goal_seg)) {
853 Last_buddy_message_time = 0; // Force this message to get through.
854 buddy_message("Can't reach %s.", Escort_goal_text[Escort_goal_object-1]);
855 Looking_for_marker = -1;
856 Escort_goal_object = ESCORT_GOAL_SCRAM;
857 dist_to_player = find_connected_distance(&objp->pos, objp->segnum, &Believed_player_pos, Believed_player_seg, 100, WID_FLY_FLAG);
858 if (dist_to_player > MIN_ESCORT_DISTANCE)
859 create_path_to_player(objp, Max_escort_length, 1); // MK!: Last parm used to be 1!
861 create_n_segment_path(objp, 8 + d_rand() * 8, -1);
862 aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length);
867 ailp->mode = AIM_GOTO_OBJECT;
869 say_escort_goal(Escort_goal_object);
874 // -----------------------------------------------------------------------------
875 // Escort robot chooses goal object based on player's keys, location.
876 // Returns goal object.
877 int escort_set_goal_object(void)
879 if (Escort_special_goal != -1)
880 return ESCORT_GOAL_UNSPECIFIED;
881 else if (!(ConsoleObject->flags & PLAYER_FLAGS_BLUE_KEY) && (exists_in_mine(ConsoleObject->segnum, OBJ_POWERUP, POW_KEY_BLUE, -1) != -1))
882 return ESCORT_GOAL_BLUE_KEY;
883 else if (!(ConsoleObject->flags & PLAYER_FLAGS_GOLD_KEY) && (exists_in_mine(ConsoleObject->segnum, OBJ_POWERUP, POW_KEY_GOLD, -1) != -1))
884 return ESCORT_GOAL_GOLD_KEY;
885 else if (!(ConsoleObject->flags & PLAYER_FLAGS_RED_KEY) && (exists_in_mine(ConsoleObject->segnum, OBJ_POWERUP, POW_KEY_RED, -1) != -1))
886 return ESCORT_GOAL_RED_KEY;
887 else if (Control_center_destroyed == 0) {
888 if (Num_boss_teleport_segs)
889 return ESCORT_GOAL_BOSS;
891 return ESCORT_GOAL_CONTROLCEN;
893 return ESCORT_GOAL_EXIT;
897 #define MAX_ESCORT_TIME_AWAY (F1_0*4)
899 fix Buddy_last_seen_player = 0, Buddy_last_player_path_created;
901 // -----------------------------------------------------------------------------
902 int time_to_visit_player(object *objp, ai_local *ailp, ai_static *aip)
904 // Note: This one has highest priority because, even if already going towards player,
905 // might be necessary to create a new path, as player can move.
906 if (GameTime - Buddy_last_seen_player > MAX_ESCORT_TIME_AWAY)
907 if (GameTime - Buddy_last_player_path_created > F1_0)
910 if (ailp->mode == AIM_GOTO_PLAYER)
913 if (objp->segnum == ConsoleObject->segnum)
916 if (aip->cur_path_index < aip->path_length/2)
923 cvar_t Buddy_dude_cheat = { "BuddyDude", "0", CVAR_CHEAT };
924 fix Last_come_back_message_time = 0;
926 fix Buddy_last_missile_time;
928 // -----------------------------------------------------------------------------
929 void bash_buddy_weapon_info(int weapon_objnum)
931 object *objp = &Objects[weapon_objnum];
933 objp->ctype.laser_info.parent_num = OBJECT_NUMBER(ConsoleObject);
934 objp->ctype.laser_info.parent_type = OBJ_PLAYER;
935 objp->ctype.laser_info.parent_signature = ConsoleObject->signature;
938 // -----------------------------------------------------------------------------
939 int maybe_buddy_fire_mega(int objnum)
941 object *objp = &Objects[objnum];
942 object *buddy_objp = &Objects[Buddy_objnum];
944 vms_vector vec_to_robot;
947 vm_vec_sub(&vec_to_robot, &buddy_objp->pos, &objp->pos);
948 dist = vm_vec_normalize_quick(&vec_to_robot);
953 dot = vm_vec_dot(&vec_to_robot, &buddy_objp->orient.fvec);
958 if (!object_to_object_visibility(buddy_objp, objp, FQ_TRANSWALL))
961 if (Weapon_info[MEGA_ID].render_type == 0) {
962 con_printf(CON_VERBOSE, "Buddy can't fire mega (shareware)\n");
963 buddy_message("CLICK!");
967 mprintf((0, "Buddy firing mega in frame %i\n", FrameCount));
969 buddy_message("GAHOOGA!");
971 weapon_objnum = Laser_create_new_easy( &buddy_objp->orient.fvec, &buddy_objp->pos, objnum, MEGA_ID, 1);
973 if (weapon_objnum != -1)
974 bash_buddy_weapon_info(weapon_objnum);
979 //-----------------------------------------------------------------------------
980 int maybe_buddy_fire_smart(int objnum)
982 object *objp = &Objects[objnum];
983 object *buddy_objp = &Objects[Buddy_objnum];
987 dist = vm_vec_dist_quick(&buddy_objp->pos, &objp->pos);
992 if (!object_to_object_visibility(buddy_objp, objp, FQ_TRANSWALL))
995 mprintf((0, "Buddy firing smart missile in frame %i\n", FrameCount));
997 buddy_message("WHAMMO!");
999 weapon_objnum = Laser_create_new_easy( &buddy_objp->orient.fvec, &buddy_objp->pos, objnum, SMART_ID, 1);
1001 if (weapon_objnum != -1)
1002 bash_buddy_weapon_info(weapon_objnum);
1007 // -----------------------------------------------------------------------------
1008 void do_buddy_dude_stuff(void)
1012 if (!ok_for_buddy_to_talk())
1015 if (Buddy_last_missile_time > GameTime)
1016 Buddy_last_missile_time = 0;
1018 if (Buddy_last_missile_time + F1_0*2 < GameTime) {
1019 // See if a robot potentially in view cone
1020 for (i=0; i<=Highest_object_index; i++)
1021 if ((Objects[i].type == OBJ_ROBOT) && !Robot_info[Objects[i].id].companion)
1022 if (maybe_buddy_fire_mega(i)) {
1023 Buddy_last_missile_time = GameTime;
1027 // See if a robot near enough that buddy should fire smart missile
1028 for (i=0; i<=Highest_object_index; i++)
1029 if ((Objects[i].type == OBJ_ROBOT) && !Robot_info[Objects[i].id].companion)
1030 if (maybe_buddy_fire_smart(i)) {
1031 Buddy_last_missile_time = GameTime;
1038 // -----------------------------------------------------------------------------
1039 // Called every frame (or something).
1040 void do_escort_frame(object *objp, fix dist_to_player, int player_visibility)
1042 int objnum = OBJECT_NUMBER(objp);
1043 ai_static *aip = &objp->ctype.ai_info;
1044 ai_local *ailp = &Ai_local_info[objnum];
1046 Buddy_objnum = OBJECT_NUMBER(objp);
1048 if (player_visibility) {
1049 Buddy_last_seen_player = GameTime;
1050 if (Players[Player_num].flags & PLAYER_FLAGS_HEADLIGHT_ON) // DAMN! MK, stupid bug, fixed 12/08/95, changed PLAYER_FLAGS_HEADLIGHT to PLAYER_FLAGS_HEADLIGHT_ON
1051 if (f2i(Players[Player_num].energy) < 40)
1052 if ((f2i(Players[Player_num].energy)/2) & 2)
1053 if (!Player_is_dead)
1054 buddy_message("Hey, your headlight's on!");
1058 if (Buddy_dude_cheat.intval)
1059 do_buddy_dude_stuff();
1061 if (Buddy_sorry_time + F1_0 > GameTime) {
1062 Last_buddy_message_time = 0; // Force this message to get through.
1063 if (Buddy_sorry_time < GameTime + F1_0*2)
1064 buddy_message("Oops, sorry 'bout that...");
1065 Buddy_sorry_time = -F1_0*2;
1068 // If buddy not allowed to talk, then he is locked in his room. Make him mostly do nothing unless you're nearby.
1069 if (!Buddy_allowed_to_talk)
1070 if (dist_to_player > F1_0*100)
1071 aip->SKIP_AI_COUNT = (F1_0/4)/FrameTime;
1073 // -- mprintf((0, "%10s: Dist to player = %7.3f, segnum = %4i\n", mode_text[ailp->mode], f2fl(dist_to_player), objp->segnum));
1075 // AIM_WANDER has been co-opted for buddy behavior (didn't want to modify aistruct.h)
1076 // It means the object has been told to get lost and has come to the end of its path.
1077 // If the player is now visible, then create a path.
1078 if (ailp->mode == AIM_WANDER)
1079 if (player_visibility) {
1080 // -- mprintf((0, "Buddy: Going from wander to path following!\n"));
1081 create_n_segment_path(objp, 16 + d_rand() * 16, -1);
1082 aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length);
1085 if (Escort_special_goal == ESCORT_GOAL_SCRAM) {
1086 if (player_visibility)
1087 if (Escort_last_path_created + F1_0*3 < GameTime) {
1088 mprintf((0, "Frame %i: Buddy creating new scram path.\n", FrameCount));
1089 create_n_segment_path(objp, 10 + d_rand() * 16, ConsoleObject->segnum);
1090 Escort_last_path_created = GameTime;
1094 // -- mprintf((0, "Buddy: Seg = %3i, dist = %7.3f\n", objp->segnum, f2fl(dist_to_player)));
1098 // Force checking for new goal every 5 seconds, and create new path, if necessary.
1099 if (((Escort_special_goal != ESCORT_GOAL_SCRAM) && ((Escort_last_path_created + F1_0*5) < GameTime)) ||
1100 ((Escort_special_goal == ESCORT_GOAL_SCRAM) && ((Escort_last_path_created + F1_0*15) < GameTime))) {
1101 Escort_goal_object = ESCORT_GOAL_UNSPECIFIED;
1102 Escort_last_path_created = GameTime;
1105 if ((Escort_special_goal != ESCORT_GOAL_SCRAM) && time_to_visit_player(objp, ailp, aip)) {
1108 Buddy_last_player_path_created = GameTime;
1109 ailp->mode = AIM_GOTO_PLAYER;
1110 if (!player_visibility) {
1111 if ((Last_come_back_message_time + F1_0 < GameTime) || (Last_come_back_message_time > GameTime)) {
1112 buddy_message("Coming back to get you.");
1113 Last_come_back_message_time = GameTime;
1116 // No point in Buddy creating very long path if he's not allowed to talk. Really kills framerate.
1117 max_len = Max_escort_length;
1118 if (!Buddy_allowed_to_talk)
1120 create_path_to_player(objp, max_len, 1); // MK!: Last parm used to be 1!
1121 aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length);
1122 // -- mprintf((0, "Creating path to player, length = %i\n", aip->path_length));
1123 ailp->mode = AIM_GOTO_PLAYER;
1124 } else if (GameTime - Buddy_last_seen_player > MAX_ESCORT_TIME_AWAY) {
1125 // This is to prevent buddy from looking for a goal, which he will do because we only allow path creation once/second.
1127 } else if ((ailp->mode == AIM_GOTO_PLAYER) && (dist_to_player < MIN_ESCORT_DISTANCE)) {
1128 Escort_goal_object = escort_set_goal_object();
1129 ailp->mode = AIM_GOTO_OBJECT; // May look stupid to be before path creation, but ai_door_is_openable uses mode to determine what doors can be got through
1130 escort_create_path_to_goal(objp);
1131 aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length);
1132 // mprintf((0, "Creating path to goal, length = %i\n", aip->path_length));
1133 if (aip->path_length < 3) {
1134 create_n_segment_path(objp, 5, Believed_player_seg);
1135 // mprintf((0, "Path to goal has length %i, just wandering...\n", aip->path_length));
1137 ailp->mode = AIM_GOTO_OBJECT;
1138 } else if (Escort_goal_object == ESCORT_GOAL_UNSPECIFIED) {
1139 if ((ailp->mode != AIM_GOTO_PLAYER) || (dist_to_player < MIN_ESCORT_DISTANCE)) {
1140 Escort_goal_object = escort_set_goal_object();
1141 ailp->mode = AIM_GOTO_OBJECT; // May look stupid to be before path creation, but ai_door_is_openable uses mode to determine what doors can be got through
1142 escort_create_path_to_goal(objp);
1143 aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length);
1144 // mprintf((0, "Creating path to goal, length = %i\n", aip->path_length));
1145 if (aip->path_length < 3) {
1146 create_n_segment_path(objp, 5, Believed_player_seg);
1147 // mprintf((0, "Path to goal has length %i, just wandering...\n", aip->path_length));
1149 ailp->mode = AIM_GOTO_OBJECT;
1152 ; // mprintf((0, "!"));
1156 void invalidate_escort_goal(void)
1158 Escort_goal_object = -1;
1161 // -------------------------------------------------------------------------------------------------
1162 void do_snipe_frame(object *objp, fix dist_to_player, int player_visibility, vms_vector *vec_to_player)
1164 int objnum = OBJECT_NUMBER(objp);
1165 ai_local *ailp = &Ai_local_info[objnum];
1166 fix connected_distance;
1168 if (dist_to_player > F1_0*500)
1171 // -- mprintf((0, "Mode: %10s, Dist: %7.3f\n", mode_text[ailp->mode], f2fl(dist_to_player)));
1173 switch (ailp->mode) {
1174 case AIM_SNIPE_WAIT:
1175 if ((dist_to_player > F1_0*50) && (ailp->next_action_time > 0))
1178 ailp->next_action_time = SNIPE_WAIT_TIME;
1180 connected_distance = find_connected_distance(&objp->pos, objp->segnum, &Believed_player_pos, Believed_player_seg, 30, WID_FLY_FLAG);
1181 if (connected_distance < F1_0*500) {
1182 // -- mprintf((0, "Object #%i entering attack mode.\n", objnum));
1183 create_path_to_player(objp, 30, 1);
1184 ailp->mode = AIM_SNIPE_ATTACK;
1185 ailp->next_action_time = SNIPE_ATTACK_TIME; // have up to 10 seconds to find player.
1189 case AIM_SNIPE_RETREAT:
1190 case AIM_SNIPE_RETREAT_BACKWARDS:
1191 if (ailp->next_action_time < 0) {
1192 ailp->mode = AIM_SNIPE_WAIT;
1193 ailp->next_action_time = SNIPE_WAIT_TIME;
1194 // -- mprintf((0, "Object #%i going from retreat to wait.\n", objnum));
1195 } else if ((player_visibility == 0) || (ailp->next_action_time > SNIPE_ABORT_RETREAT_TIME)) {
1196 ai_follow_path(objp, player_visibility, player_visibility, vec_to_player);
1197 ailp->mode = AIM_SNIPE_RETREAT_BACKWARDS;
1199 // -- mprintf((0, "Object #%i going from retreat to fire.\n", objnum));
1200 ailp->mode = AIM_SNIPE_FIRE;
1201 ailp->next_action_time = SNIPE_FIRE_TIME/2;
1205 case AIM_SNIPE_ATTACK:
1206 if (ailp->next_action_time < 0) {
1207 // -- mprintf((0, "Object #%i timed out from attack to retreat mode.\n", objnum));
1208 ailp->mode = AIM_SNIPE_RETREAT;
1209 ailp->next_action_time = SNIPE_WAIT_TIME;
1211 // -- mprintf((0, "Object #%i attacking: visibility = %i\n", player_visibility));
1212 ai_follow_path(objp, player_visibility, player_visibility, vec_to_player);
1213 if (player_visibility) {
1214 ailp->mode = AIM_SNIPE_FIRE;
1215 ailp->next_action_time = SNIPE_FIRE_TIME;
1217 ailp->mode = AIM_SNIPE_ATTACK;
1221 case AIM_SNIPE_FIRE:
1222 if (ailp->next_action_time < 0) {
1223 ai_static *aip = &objp->ctype.ai_info;
1224 // -- mprintf((0, "Object #%i going from fire to retreat.\n", objnum));
1225 create_n_segment_path(objp, 10 + d_rand()/2048, ConsoleObject->segnum);
1226 aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length);
1227 if (d_rand() < 8192)
1228 ailp->mode = AIM_SNIPE_RETREAT_BACKWARDS;
1230 ailp->mode = AIM_SNIPE_RETREAT;
1231 ailp->next_action_time = SNIPE_RETREAT_TIME;
1237 Int3(); // Oops, illegal mode for snipe behavior.
1238 ailp->mode = AIM_SNIPE_ATTACK;
1239 ailp->next_action_time = F1_0;
1245 #define THIEF_DEPTH 20
1247 extern int pick_connected_segment(object *objp, int max_depth);
1249 // ------------------------------------------------------------------------------------------------------
1250 // Choose segment to recreate thief in.
1251 int choose_thief_recreation_segment(void)
1256 cur_drop_depth = THIEF_DEPTH;
1258 while ((segnum == -1) && (cur_drop_depth > THIEF_DEPTH/2)) {
1259 segnum = pick_connected_segment(&Objects[Players[Player_num].objnum], cur_drop_depth);
1260 if (Segment2s[segnum].special == SEGMENT_IS_CONTROLCEN)
1266 mprintf((1, "Warning: Unable to find a connected segment for thief recreation.\n"));
1267 return (d_rand() * Highest_segment_index) >> 15;
1273 extern object * create_morph_robot( segment *segp, vms_vector *object_pos, int object_id);
1275 fix Re_init_thief_time = 0x3f000000;
1277 // ----------------------------------------------------------------------
1278 void recreate_thief(object *objp)
1281 vms_vector center_point;
1284 segnum = choose_thief_recreation_segment();
1285 compute_segment_center(¢er_point, &Segments[segnum]);
1287 new_obj = create_morph_robot( &Segments[segnum], ¢er_point, objp->id);
1288 init_ai_object(OBJECT_NUMBER(new_obj), AIB_SNIPE, -1);
1289 Re_init_thief_time = GameTime + F1_0*10; // In 10 seconds, re-initialize thief.
1292 // ----------------------------------------------------------------------------
1293 #define THIEF_ATTACK_TIME (F1_0*10)
1295 fix Thief_wait_times[NDL] = {F1_0*30, F1_0*25, F1_0*20, F1_0*15, F1_0*10};
1297 // -------------------------------------------------------------------------------------------------
1298 void do_thief_frame(object *objp, fix dist_to_player, int player_visibility, vms_vector *vec_to_player)
1300 int objnum = OBJECT_NUMBER(objp);
1301 ai_local *ailp = &Ai_local_info[objnum];
1302 fix connected_distance;
1304 // -- mprintf((0, "%10s: Action Time: %7.3f\n", mode_text[ailp->mode], f2fl(ailp->next_action_time)));
1306 if ((Current_level_num < 0) && (Re_init_thief_time < GameTime)) {
1307 if (Re_init_thief_time > GameTime - F1_0*2)
1308 init_thief_for_level();
1309 Re_init_thief_time = 0x3f000000;
1312 if ((dist_to_player > F1_0*500) && (ailp->next_action_time > 0))
1316 ailp->mode = AIM_THIEF_RETREAT;
1318 switch (ailp->mode) {
1319 case AIM_THIEF_WAIT:
1320 // -- mprintf((0, "WAIT\n"));
1322 if (ailp->player_awareness_type >= PA_PLAYER_COLLISION) {
1323 ailp->player_awareness_type = 0;
1324 // -- mprintf((0, "Thief: Awareness = %i ", ailp->player_awareness_type));
1326 // -- mprintf((0, "ATTACK\n"));
1327 create_path_to_player(objp, 30, 1);
1328 ailp->mode = AIM_THIEF_ATTACK;
1329 ailp->next_action_time = THIEF_ATTACK_TIME/2;
1331 } else if (player_visibility) {
1332 // -- mprintf((0, "RETREAT\n"));
1333 create_n_segment_path(objp, 15, ConsoleObject->segnum);
1334 ailp->mode = AIM_THIEF_RETREAT;
1338 if ((dist_to_player > F1_0*50) && (ailp->next_action_time > 0))
1341 ailp->next_action_time = Thief_wait_times[Difficulty_level]/2;
1343 connected_distance = find_connected_distance(&objp->pos, objp->segnum, &Believed_player_pos, Believed_player_seg, 30, WID_FLY_FLAG);
1344 if (connected_distance < F1_0*500) {
1345 // -- mprintf((0, "Thief creating path to player.\n", objnum));
1346 create_path_to_player(objp, 30, 1);
1347 ailp->mode = AIM_THIEF_ATTACK;
1348 ailp->next_action_time = THIEF_ATTACK_TIME; // have up to 10 seconds to find player.
1353 case AIM_THIEF_RETREAT:
1354 // -- mprintf((0, "RETREAT\n"));
1356 if (ailp->next_action_time < 0) {
1357 ailp->mode = AIM_THIEF_WAIT;
1358 ailp->next_action_time = Thief_wait_times[Difficulty_level];
1359 } else if ((dist_to_player < F1_0*100) || player_visibility || (ailp->player_awareness_type >= PA_PLAYER_COLLISION)) {
1360 ai_follow_path(objp, player_visibility, player_visibility, vec_to_player);
1361 if ((dist_to_player < F1_0*100) || (ailp->player_awareness_type >= PA_PLAYER_COLLISION)) {
1362 ai_static *aip = &objp->ctype.ai_info;
1363 if (((aip->cur_path_index <=1) && (aip->PATH_DIR == -1)) || ((aip->cur_path_index >= aip->path_length-1) && (aip->PATH_DIR == 1))) {
1364 ailp->player_awareness_type = 0;
1365 create_n_segment_path(objp, 10, ConsoleObject->segnum);
1367 // If path is real short, try again, allowing to go through player's segment
1368 if (aip->path_length < 4) {
1369 // -- mprintf((0, "Thief is cornered. Willing to fly through player.\n"));
1370 create_n_segment_path(objp, 10, -1);
1371 } else if (objp->shields* 4 < Robot_info[objp->id].strength) {
1372 // If robot really low on hits, will run through player with even longer path
1373 if (aip->path_length < 8) {
1374 create_n_segment_path(objp, 10, -1);
1378 ailp->mode = AIM_THIEF_RETREAT;
1379 // -- mprintf((0, "Thief creating new RETREAT path.\n"));
1382 ailp->mode = AIM_THIEF_RETREAT;
1388 // This means the thief goes from wherever he is to the player.
1389 // Note: When thief successfully steals something, his action time is forced negative and his mode is changed
1390 // to retreat to get him out of attack mode.
1391 case AIM_THIEF_ATTACK:
1392 // -- mprintf((0, "ATTACK\n"));
1394 if (ailp->player_awareness_type >= PA_PLAYER_COLLISION) {
1395 ailp->player_awareness_type = 0;
1396 if (d_rand() > 8192) {
1397 // --- mprintf((0, "RETREAT!!\n"));
1398 create_n_segment_path(objp, 10, ConsoleObject->segnum);
1399 Ai_local_info[OBJECT_NUMBER(objp)].next_action_time = Thief_wait_times[Difficulty_level]/2;
1400 Ai_local_info[OBJECT_NUMBER(objp)].mode = AIM_THIEF_RETREAT;
1402 } else if (ailp->next_action_time < 0) {
1403 // This forces him to create a new path every second.
1404 ailp->next_action_time = F1_0;
1405 create_path_to_player(objp, 100, 0);
1406 ailp->mode = AIM_THIEF_ATTACK;
1407 // -- mprintf((0, "Creating path to player.\n"));
1409 if (player_visibility && (dist_to_player < F1_0*100)) {
1410 // If the player is close to looking at the thief, thief shall run away.
1411 // No more stupid thief trying to sneak up on you when you're looking right at him!
1412 if (dist_to_player > F1_0*60) {
1413 fix dot = vm_vec_dot(vec_to_player, &ConsoleObject->orient.fvec);
1414 if (dot < -F1_0/2) { // Looking at least towards thief, so thief will run!
1415 create_n_segment_path(objp, 10, ConsoleObject->segnum);
1416 Ai_local_info[OBJECT_NUMBER(objp)].next_action_time = Thief_wait_times[Difficulty_level]/2;
1417 Ai_local_info[OBJECT_NUMBER(objp)].mode = AIM_THIEF_RETREAT;
1420 ai_turn_towards_vector(vec_to_player, objp, F1_0/4);
1421 move_towards_player(objp, vec_to_player);
1423 ai_static *aip = &objp->ctype.ai_info;
1424 // If path length == 0, then he will keep trying to create path, but he is probably stuck in his closet.
1425 if ((aip->path_length > 1) || ((FrameCount & 0x0f) == 0)) {
1426 ai_follow_path(objp, player_visibility, player_visibility, vec_to_player);
1427 ailp->mode = AIM_THIEF_ATTACK;
1434 mprintf ((0,"Thief mode (broken) = %d\n",ailp->mode));
1435 // -- Int3(); // Oops, illegal mode for thief behavior.
1436 ailp->mode = AIM_THIEF_ATTACK;
1437 ailp->next_action_time = F1_0;
1443 // ----------------------------------------------------------------------------
1444 // Return true if this item (whose presence is indicated by Players[player_num].flags) gets stolen.
1445 int maybe_steal_flag_item(int player_num, int flagval)
1447 if (Players[player_num].flags & flagval) {
1448 if (d_rand() < THIEF_PROBABILITY) {
1449 int powerup_index=-1;
1450 Players[player_num].flags &= (~flagval);
1451 // -- mprintf((0, "You lost your %4x capability!\n", flagval));
1453 case PLAYER_FLAGS_INVULNERABLE:
1454 powerup_index = POW_INVULNERABILITY;
1455 thief_message("Invulnerability stolen!");
1457 case PLAYER_FLAGS_CLOAKED:
1458 powerup_index = POW_CLOAK;
1459 thief_message("Cloak stolen!");
1461 case PLAYER_FLAGS_MAP_ALL:
1462 powerup_index = POW_FULL_MAP;
1463 thief_message("Full map stolen!");
1465 case PLAYER_FLAGS_QUAD_LASERS:
1466 powerup_index = POW_QUAD_FIRE;
1467 thief_message("Quad lasers stolen!");
1469 case PLAYER_FLAGS_AFTERBURNER:
1470 powerup_index = POW_AFTERBURNER;
1471 thief_message("Afterburner stolen!");
1473 // -- case PLAYER_FLAGS_AMMO_RACK:
1474 // -- powerup_index = POW_AMMO_RACK;
1475 // -- thief_message("Ammo Rack stolen!");
1477 case PLAYER_FLAGS_CONVERTER:
1478 powerup_index = POW_CONVERTER;
1479 thief_message("Converter stolen!");
1481 case PLAYER_FLAGS_HEADLIGHT:
1482 powerup_index = POW_HEADLIGHT;
1483 thief_message("Headlight stolen!");
1484 Players[Player_num].flags &= ~PLAYER_FLAGS_HEADLIGHT_ON;
1487 Assert(powerup_index != -1);
1488 Stolen_items[Stolen_item_index] = powerup_index;
1490 digi_play_sample_once(SOUND_WEAPON_STOLEN, F1_0);
1498 // ----------------------------------------------------------------------------
1499 int maybe_steal_secondary_weapon(int player_num, int weapon_num)
1501 if ((Players[player_num].secondary_weapon_flags & HAS_FLAG(weapon_num)) && Players[player_num].secondary_ammo[weapon_num])
1502 if (d_rand() < THIEF_PROBABILITY) {
1503 if (weapon_num == PROXIMITY_INDEX)
1504 if (d_rand() > 8192) // Come in groups of 4, only add 1/4 of time.
1506 Players[player_num].secondary_ammo[weapon_num]--;
1508 // Smart mines and proxbombs don't get dropped because they only come in 4 packs.
1509 if ((weapon_num != PROXIMITY_INDEX) && (weapon_num != SMART_MINE_INDEX)) {
1510 Stolen_items[Stolen_item_index] = Secondary_weapon_to_powerup[weapon_num];
1513 thief_message("%s stolen!", Text_string[114+weapon_num]); // Danger! Danger! Use of literal! Danger!
1514 if (Players[Player_num].secondary_ammo[weapon_num] == 0)
1515 auto_select_weapon(1);
1517 // -- compress_stolen_items();
1518 digi_play_sample_once(SOUND_WEAPON_STOLEN, F1_0);
1525 // ----------------------------------------------------------------------------
1526 int maybe_steal_primary_weapon(int player_num, int weapon_num)
1528 if ((Players[player_num].primary_weapon_flags & HAS_FLAG(weapon_num)) && Players[player_num].primary_ammo[weapon_num]) {
1529 if (d_rand() < THIEF_PROBABILITY) {
1530 if (weapon_num == 0) {
1531 if (Players[player_num].laser_level > 0) {
1532 if (Players[player_num].laser_level > 3) {
1533 Stolen_items[Stolen_item_index] = POW_SUPER_LASER;
1535 Stolen_items[Stolen_item_index] = Primary_weapon_to_powerup[weapon_num];
1537 thief_message("%s level decreased!", Text_string[104+weapon_num]); // Danger! Danger! Use of literal! Danger!
1538 Players[player_num].laser_level--;
1539 digi_play_sample_once(SOUND_WEAPON_STOLEN, F1_0);
1542 } else if (Players[player_num].primary_weapon_flags & (1 << weapon_num)) {
1543 Players[player_num].primary_weapon_flags &= ~(1 << weapon_num);
1544 Stolen_items[Stolen_item_index] = Primary_weapon_to_powerup[weapon_num];
1546 thief_message("%s stolen!", Text_string[104+weapon_num]); // Danger! Danger! Use of literal! Danger!
1547 auto_select_weapon(0);
1548 digi_play_sample_once(SOUND_WEAPON_STOLEN, F1_0);
1559 // ----------------------------------------------------------------------------
1560 // Called for a thief-type robot.
1561 // If a item successfully stolen, returns true, else returns false.
1562 // If a wapon successfully stolen, do everything, removing it from player,
1563 // updating Stolen_items information, deselecting, etc.
1564 int attempt_to_steal_item_3(object *objp, int player_num)
1568 if (Ai_local_info[OBJECT_NUMBER(objp)].mode != AIM_THIEF_ATTACK)
1571 // First, try to steal equipped items.
1573 if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_INVULNERABLE))
1576 // If primary weapon = laser, first try to rip away those nasty quad lasers!
1577 if (Primary_weapon == 0)
1578 if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_QUAD_LASERS))
1581 // Makes it more likely to steal primary than secondary.
1583 if (maybe_steal_primary_weapon(player_num, Primary_weapon))
1586 if (maybe_steal_secondary_weapon(player_num, Secondary_weapon))
1589 // See what the player has and try to snag something.
1590 // Try best things first.
1591 if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_INVULNERABLE))
1593 if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_CLOAKED))
1595 if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_QUAD_LASERS))
1597 if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_AFTERBURNER))
1599 if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_CONVERTER))
1601 // -- if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_AMMO_RACK)) // Can't steal because what if have too many items, say 15 homing missiles?
1603 if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_HEADLIGHT))
1605 if (maybe_steal_flag_item(player_num, PLAYER_FLAGS_MAP_ALL))
1608 for (i=MAX_SECONDARY_WEAPONS-1; i>=0; i--) {
1609 if (maybe_steal_primary_weapon(player_num, i))
1611 if (maybe_steal_secondary_weapon(player_num, i))
1618 // ----------------------------------------------------------------------------
1619 int attempt_to_steal_item_2(object *objp, int player_num)
1623 rval = attempt_to_steal_item_3(objp, player_num);
1626 Stolen_item_index = (Stolen_item_index+1) % MAX_STOLEN_ITEMS;
1627 if (d_rand() > 20000) // Occasionally, boost the value again
1628 Stolen_item_index = (Stolen_item_index+1) % MAX_STOLEN_ITEMS;
1634 // ----------------------------------------------------------------------------
1635 // Called for a thief-type robot.
1636 // If a item successfully stolen, returns true, else returns false.
1637 // If a wapon successfully stolen, do everything, removing it from player,
1638 // updating Stolen_items information, deselecting, etc.
1639 int attempt_to_steal_item(object *objp, int player_num)
1644 if (objp->ctype.ai_info.dying_start_time)
1647 rval += attempt_to_steal_item_2(objp, player_num);
1649 for (i=0; i<3; i++) {
1650 if (!rval || (d_rand() < 11000)) { // about 1/3 of time, steal another item
1651 rval += attempt_to_steal_item_2(objp, player_num);
1655 // -- mprintf((0, "%i items were stolen!\n", rval));
1657 create_n_segment_path(objp, 10, ConsoleObject->segnum);
1658 Ai_local_info[OBJECT_NUMBER(objp)].next_action_time = Thief_wait_times[Difficulty_level]/2;
1659 Ai_local_info[OBJECT_NUMBER(objp)].mode = AIM_THIEF_RETREAT;
1661 PALETTE_FLASH_ADD(30, 15, -20);
1662 update_laser_weapon_info();
1663 // digi_link_sound_to_pos( SOUND_NASTY_ROBOT_HIT_1, objp->segnum, 0, &objp->pos, 0 , DEFAULT_ROBOT_SOUND_VOLUME);
1664 // I removed this to make the "steal sound" more obvious -AP
1666 if (Game_mode & GM_NETWORK)
1667 multi_send_stolen_items();
1673 // --------------------------------------------------------------------------------------------------------------
1674 // Indicate no items have been stolen.
1675 void init_thief_for_level(void)
1679 for (i=0; i<MAX_STOLEN_ITEMS; i++)
1680 Stolen_items[i] = 255;
1682 Assert (MAX_STOLEN_ITEMS >= 3*2); // Oops! Loop below will overwrite memory!
1684 if (!(Game_mode & GM_MULTI))
1685 for (i=0; i<3; i++) {
1686 Stolen_items[2*i] = POW_SHIELD_BOOST;
1687 Stolen_items[2*i+1] = POW_ENERGY;
1690 Stolen_item_index = 0;
1693 // --------------------------------------------------------------------------------------------------------------
1694 void drop_stolen_items(object *objp)
1698 mprintf ((0,"Dropping thief items!\n"));
1700 // -- compress_stolen_items();
1702 for (i=0; i<MAX_STOLEN_ITEMS; i++) {
1703 if (Stolen_items[i] != 255)
1704 drop_powerup(OBJ_POWERUP, Stolen_items[i], 1, &objp->mtype.phys_info.velocity, &objp->pos, objp->segnum);
1705 Stolen_items[i] = 255;
1710 // --------------------------------------------------------------------------------------------------------------
1711 void do_escort_menu(void)
1718 char goal_str[32], tstr[32];
1720 if (Game_mode & GM_MULTI) {
1721 HUD_init_message("No Guide-Bot in Multiplayer!");
1725 for (i=0; i<=Highest_object_index; i++) {
1726 if (Objects[i].type == OBJ_ROBOT)
1727 if (Robot_info[Objects[i].id].companion)
1731 if (i > Highest_object_index) {
1733 HUD_init_message("No Guide-Bot present in mine!");
1736 // If no buddy bot, create one!
1737 HUD_init_message("Debug Version: Creating Guide-Bot!");
1744 ok_for_buddy_to_talk(); // Needed here or we might not know buddy can talk when he can.
1746 if (!Buddy_allowed_to_talk) {
1747 HUD_init_message("%s has not been released",guidebot_name);
1751 digi_pause_digi_sounds();
1755 apply_modified_palette();
1756 reset_palette_add();
1758 game_flush_inputs();
1762 // set_screen_mode( SCREEN_MENU );
1764 gr_palette_load( gr_palette );
1766 // This prevents the buddy from coming back if you've told him to scram.
1767 // If we don't set next_goal, we get garbage there.
1768 if (Escort_special_goal == ESCORT_GOAL_SCRAM) {
1769 Escort_special_goal = -1; // Else setting next goal might fail.
1770 next_goal = escort_set_goal_object();
1771 Escort_special_goal = ESCORT_GOAL_SCRAM;
1773 Escort_special_goal = -1; // Else setting next goal might fail.
1774 next_goal = escort_set_goal_object();
1777 switch (next_goal) {
1779 case ESCORT_GOAL_UNSPECIFIED:
1781 sprintf(goal_str, "ERROR");
1785 case ESCORT_GOAL_BLUE_KEY:
1786 sprintf(goal_str, "blue key");
1788 case ESCORT_GOAL_GOLD_KEY:
1789 sprintf(goal_str, "yellow key");
1791 case ESCORT_GOAL_RED_KEY:
1792 sprintf(goal_str, "red key");
1794 case ESCORT_GOAL_CONTROLCEN:
1795 sprintf(goal_str, "reactor");
1797 case ESCORT_GOAL_BOSS:
1798 sprintf(goal_str, "boss");
1800 case ESCORT_GOAL_EXIT:
1801 sprintf(goal_str, "exit");
1803 case ESCORT_GOAL_MARKER1:
1804 case ESCORT_GOAL_MARKER2:
1805 case ESCORT_GOAL_MARKER3:
1806 case ESCORT_GOAL_MARKER4:
1807 case ESCORT_GOAL_MARKER5:
1808 case ESCORT_GOAL_MARKER6:
1809 case ESCORT_GOAL_MARKER7:
1810 case ESCORT_GOAL_MARKER8:
1811 case ESCORT_GOAL_MARKER9:
1812 sprintf(goal_str, "marker %i", next_goal-ESCORT_GOAL_MARKER1+1);
1817 if (!Buddy_messages_suppressed)
1818 sprintf(tstr, "Suppress");
1820 sprintf(tstr, "Enable");
1822 sprintf(msg, "Select Guide-Bot Command:\n\n"
1823 "0. Next Goal: %.26s" CC_LSPACING_S "3\n"
1824 "\x84. Find Energy Powerup" CC_LSPACING_S "3\n"
1825 "2. Find Energy Center" CC_LSPACING_S "3\n"
1826 "3. Find Shield Powerup" CC_LSPACING_S "3\n"
1827 "4. Find Any Powerup" CC_LSPACING_S "3\n"
1828 "5. Find a Robot" CC_LSPACING_S "3\n"
1829 "6. Find a Hostage" CC_LSPACING_S "3\n"
1830 "7. Stay Away From Me" CC_LSPACING_S "3\n"
1831 "8. Find My Powerups" CC_LSPACING_S "3\n"
1832 "9. Find the exit\n\n"
1833 "T. %.8s Messages\n"
1834 // -- "9. Find the exit" CC_LSPACING_S "3\n"
1837 show_escort_menu(msg); //TXT_PAUSE);
1840 key = newmenu_getch();
1853 Looking_for_marker = -1;
1854 Last_buddy_key = -1;
1855 set_escort_special_goal(key);
1856 Last_buddy_key = -1;
1862 clear_boxed_message();
1866 //--10/08/95-- Screwed up font, background. Why needed, anyway?
1867 //--10/08/95-- case KEY_F1:
1868 //--10/08/95-- clear_boxed_message();
1869 //--10/08/95-- do_show_help();
1870 //--10/08/95-- show_boxed_message(msg);
1871 //--10/08/95-- break;
1873 case KEY_PRINT_SCREEN:
1874 save_screen_shot(0);
1878 case KEY_BACKSP: Int3(); break;
1885 temp = !Buddy_messages_suppressed;
1888 strcpy(msg, "suppressed");
1890 strcpy(msg, "enabled");
1892 Buddy_messages_suppressed = 1;
1893 buddy_message("Messages %s.", msg);
1895 Buddy_messages_suppressed = temp;
1908 game_flush_inputs();
1913 digi_resume_digi_sounds();
1917 // -------------------------------------------------------------------------------
1918 // Show the Buddy menu!
1919 void show_escort_menu(char *msg)
1925 gr_set_current_canvas(&VR_screen_pages[0]);
1927 gr_set_curfont( GAME_FONT );
1929 gr_get_string_size(msg,&w,&h,&aw);
1931 x = (grd_curscreen->sc_w-w)/2;
1932 y = (grd_curscreen->sc_h-h)/4;
1934 gr_set_fontcolor( gr_getcolor(0, 28, 0), -1 );
1936 nm_draw_background(x-15,y-15,x+w+15-1,y+h+15-1);
1938 gr_ustring( x, y, msg );