/* * Portions of this file are copyright Rebirth contributors and licensed as * described in COPYING.txt. * Portions of this file are copyright Parallax Software and licensed * according to the Parallax license below. * See COPYING.txt for license details. THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX SOFTWARE CORPORATION ("PARALLAX"). PARALLAX, IN DISTRIBUTING THE CODE TO END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS IN USING, DISPLAYING, AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE FREE PURPOSES. IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES. THE END-USER UNDERSTANDS AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE. COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED. */ /* * * Escort robot behavior. * */ #include // for printf() #include // for rand() and qsort() #include // for memset() #include "window.h" #include "console.h" #include "vecmat.h" #include "gr.h" #include "gameseg.h" #include "3d.h" #include "palette.h" #include "timer.h" #include "u_mem.h" #include "object.h" #include "dxxerror.h" #include "ai.h" #include "robot.h" #include "fvi.h" #include "physics.h" #include "wall.h" #include "player.h" #include "fireball.h" #include "game.h" #include "powerup.h" #include "cntrlcen.h" #include "gauges.h" #include "key.h" #include "fuelcen.h" #include "sounds.h" #include "screens.h" #include "text.h" #include "gamefont.h" #include "newmenu.h" #include "playsave.h" #include "gameseq.h" #include "automap.h" #include "laser.h" #include "escort.h" #include "segiter.h" #include "compiler-range_for.h" #include "highest_valid.h" #ifdef EDITOR #include "editor/editor.h" #endif static void say_escort_goal(int goal_num); static void show_escort_menu(char *msg); static const char Escort_goal_text[MAX_ESCORT_GOALS][12] = { "BLUE KEY", "YELLOW KEY", "RED KEY", "REACTOR", "EXIT", "ENERGY", "ENERGYCEN", "SHIELD", "POWERUP", "ROBOT", "HOSTAGES", "SPEW", "SCRAM", "EXIT", "BOSS", "MARKER 1", "MARKER 2", "MARKER 3", "MARKER 4", "MARKER 5", "MARKER 6", "MARKER 7", "MARKER 8", "MARKER 9", // -- too much work -- "KAMIKAZE " }; int Max_escort_length = 200; int Escort_kill_object = -1; stolen_items_t Stolen_items; int Stolen_item_index; fix64 Escort_last_path_created = 0; int Escort_goal_object = ESCORT_GOAL_UNSPECIFIED, Escort_special_goal = -1, Buddy_messages_suppressed = 0; fix64 Buddy_sorry_time; objnum_t Escort_goal_index,Buddy_objnum; int Buddy_allowed_to_talk; int Looking_for_marker; int Last_buddy_key; fix64 Last_buddy_message_time; void init_buddy_for_level(void) { Buddy_allowed_to_talk = 0; Buddy_objnum = object_none; Escort_goal_object = ESCORT_GOAL_UNSPECIFIED; Escort_special_goal = -1; Escort_goal_index = object_none; Buddy_messages_suppressed = 0; range_for (auto i, highest_valid(Objects)) if (Robot_info[get_robot_id(&Objects[i])].companion) { Buddy_objnum = i; break; } Buddy_sorry_time = -F1_0; Looking_for_marker = -1; Last_buddy_key = -1; } // ----------------------------------------------------------------------------- // See if segment from curseg through sidenum is reachable. // Return true if it is reachable, else return false. static int segment_is_reachable(segnum_t curseg, int sidenum) { int rval; segment *segp = &Segments[curseg]; if (!IS_CHILD(segp->children[sidenum])) return 0; auto wall_num = segp->sides[sidenum].wall_num; // If no wall, then it is reachable if (wall_num == wall_none) return 1; rval = ai_door_is_openable(NULL, segp, sidenum); return rval; // -- MK, 10/17/95 -- // -- MK, 10/17/95 -- // Hmm, a closed wall. I think this mean not reachable. // -- MK, 10/17/95 -- if (Walls[wall_num].type == WALL_CLOSED) // -- MK, 10/17/95 -- return 0; // -- MK, 10/17/95 -- // -- MK, 10/17/95 -- if (Walls[wall_num].type == WALL_DOOR) { // -- MK, 10/17/95 -- if (Walls[wall_num].keys == KEY_NONE) { // -- MK, 10/17/95 -- return 1; // @MK, 10/17/95: Be consistent with ai_door_is_openable // -- MK, 10/17/95 -- // -- if (Walls[wall_num].flags & WALL_DOOR_LOCKED) // -- MK, 10/17/95 -- // -- return 0; // -- MK, 10/17/95 -- // -- else // -- MK, 10/17/95 -- // -- return 1; // -- MK, 10/17/95 -- } else if (Walls[wall_num].keys == KEY_BLUE) // -- MK, 10/17/95 -- return (Players[Player_num].flags & PLAYER_FLAGS_BLUE_KEY); // -- MK, 10/17/95 -- else if (Walls[wall_num].keys == KEY_GOLD) // -- MK, 10/17/95 -- return (Players[Player_num].flags & PLAYER_FLAGS_GOLD_KEY); // -- MK, 10/17/95 -- else if (Walls[wall_num].keys == KEY_RED) // -- MK, 10/17/95 -- return (Players[Player_num].flags & PLAYER_FLAGS_RED_KEY); // -- MK, 10/17/95 -- else // -- MK, 10/17/95 -- Int3(); // Impossible! Doesn't have no key, but doesn't have any key! // -- MK, 10/17/95 -- } else // -- MK, 10/17/95 -- return 1; // -- MK, 10/17/95 -- // -- MK, 10/17/95 -- Int3(); // Hmm, thought 'if' above had to return! // -- MK, 10/17/95 -- return 0; } // ----------------------------------------------------------------------------- // Create a breadth-first list of segments reachable from current segment. // max_segs is maximum number of segments to search. Use MAX_SEGMENTS to search all. // On exit, *length <= max_segs. // Input: // start_seg // Output: // bfs_list: array of shorts, each reachable segment. Includes start segment. // length: number of elements in bfs_list void create_bfs_list(segnum_t start_seg, segnum_t bfs_list[], unsigned &length, unsigned max_segs) { int head, tail; visited_segment_bitarray_t visited; head = 0; tail = 0; bfs_list[head++] = start_seg; visited[start_seg] = true; while ((head != tail) && (head < max_segs)) { segment *cursegp; segnum_t curseg = bfs_list[tail++]; cursegp = &Segments[curseg]; for (int i=0; ichildren[i]; if (IS_CHILD(connected_seg) && (!visited[connected_seg])) { if (segment_is_reachable(curseg, i)) { bfs_list[head++] = connected_seg; if (head >= max_segs) break; visited[connected_seg] = true; Assert(head < MAX_SEGMENTS); } } } } length = head; } // ----------------------------------------------------------------------------- // Return true if ok for buddy to talk, else return false. // Buddy is allowed to talk if the segment he is in does not contain a blastable wall that has not been blasted // AND he has never yet, since being initialized for level, been allowed to talk. static int ok_for_buddy_to_talk(void) { segment *segp; if (Buddy_objnum == object_none) return 0; vobjptridx_t buddy = vobjptridx(Buddy_objnum); if (buddy->type != OBJ_ROBOT) { Buddy_allowed_to_talk = 0; return 0; } if (Buddy_allowed_to_talk) return 1; segp = &Segments[buddy->segnum]; for (int i=0; isides[i].wall_num; if (wall_num != wall_none) { if ((Walls[wall_num].type == WALL_BLASTABLE) && !(Walls[wall_num].flags & WALL_BLASTED)) return 0; } // Check one level deeper. if (IS_CHILD(segp->children[i])) { int j; segment *csegp = &Segments[segp->children[i]]; for (j=0; jsides[j].wall_num; if (wall2 != wall_none) { if ((Walls[wall2].type == WALL_BLASTABLE) && !(Walls[wall2].flags & WALL_BLASTED)) return 0; } } } } Buddy_allowed_to_talk = 1; return 1; } static void record_escort_goal_accomplished() { if (ok_for_buddy_to_talk()) { digi_play_sample_once(SOUND_BUDDY_MET_GOAL, F1_0); Escort_goal_index = object_none; Escort_goal_object = ESCORT_GOAL_UNSPECIFIED; Escort_special_goal = -1; Looking_for_marker = -1; } } // -------------------------------------------------------------------------------------------- void detect_escort_goal_fuelcen_accomplished() { if (!Buddy_allowed_to_talk) return; if (Escort_special_goal == ESCORT_GOAL_ENERGYCEN) record_escort_goal_accomplished(); } void detect_escort_goal_accomplished(objptridx_t index) { if (!Buddy_allowed_to_talk) return; // If goal is to go away, how can it be achieved? if (Escort_special_goal == ESCORT_GOAL_SCRAM) return; // See if goal found was a key. Need to handle default goals differently. // Note, no buddy_met_goal sound when blow up reactor or exit. Not great, but ok // since for reactor, noisy, for exit, buddy is disappearing. if ((Escort_special_goal == -1) && (Escort_goal_index == index)) { record_escort_goal_accomplished(); return; } if ((Escort_goal_index <= ESCORT_GOAL_RED_KEY) && (index >= 0)) { if (index->type == OBJ_POWERUP) { if (index->id == POW_KEY_BLUE) { if (Escort_goal_index == ESCORT_GOAL_BLUE_KEY) { record_escort_goal_accomplished(); return; } } else if (index->id == POW_KEY_GOLD) { if (Escort_goal_index == ESCORT_GOAL_GOLD_KEY) { record_escort_goal_accomplished(); return; } } else if (index->id == POW_KEY_RED) { if (Escort_goal_index == ESCORT_GOAL_RED_KEY) { record_escort_goal_accomplished(); return; } } } } if (Escort_special_goal != -1) { if (Escort_special_goal == ESCORT_GOAL_ENERGYCEN) { } else if ((index->type == OBJ_POWERUP) && (Escort_special_goal == ESCORT_GOAL_POWERUP)) record_escort_goal_accomplished(); // Any type of powerup picked up will do. else if ((index->type == Objects[Escort_goal_index].type) && (index->id == Objects[Escort_goal_index].id)) { // 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" // because of the insistence of both type and id matching. record_escort_goal_accomplished(); } } } void change_guidebot_name() { newmenu_item m; char text[GUIDEBOT_NAME_LEN+1]=""; int item; strcpy(text,PlayerCfg.GuidebotName); nm_set_item_input(&m, GUIDEBOT_NAME_LEN, text); item = newmenu_do( NULL, "Enter Guide-bot name:", 1, &m, unused_newmenu_subfunction, unused_newmenu_userdata ); if (item != -1) { strcpy(PlayerCfg.GuidebotName,text); strcpy(PlayerCfg.GuidebotNameReal,text); write_player_file(); } } // ----------------------------------------------------------------------------- static int show_buddy_message() { if (Buddy_messages_suppressed) return 0; if (Game_mode & GM_MULTI) return 0; if (Last_buddy_message_time + F1_0 < GameTime64) { if (ok_for_buddy_to_talk()) return 1; } return 0; } static void _buddy_message(const char *str) { HUD_init_message(HM_DEFAULT, "%c%c%s:%c%c %s", CC_COLOR, BM_XRGB(28, 0, 0), PlayerCfg.GuidebotName, CC_COLOR, BM_XRGB(0, 31, 0), str); Last_buddy_message_time = GameTime64; } void (buddy_message)(const char * format, ... ) { if (!show_buddy_message()) return; char new_format[128]; va_list args; va_start(args, format ); vsnprintf(new_format, sizeof(new_format), format, args); va_end(args); _buddy_message(new_format); } void buddy_message_str(const char *str) { if (!show_buddy_message()) return; _buddy_message(str); } // ----------------------------------------------------------------------------- static void thief_message_str(const char * str) __attribute_nonnull(); static void thief_message_str(const char * str) { HUD_init_message(HM_DEFAULT, "%c%cTHIEF:%c%c %s", 1, BM_XRGB(28, 0, 0), 1, BM_XRGB(0, 31, 0), str); } static void thief_message(const char * format, ... ) __attribute_format_printf(1, 2); static void thief_message(const char * format, ... ) #define thief_message(F,...) dxx_call_printf_checked(thief_message,thief_message_str,(),(F),##__VA_ARGS__) { char new_format[128]; va_list args; va_start(args, format ); vsnprintf(new_format, sizeof(new_format), format, args); va_end(args); thief_message_str(new_format); } // ----------------------------------------------------------------------------- // Return true if marker #id has been placed. static int marker_exists_in_mine(int id) { range_for (auto i, highest_valid(Objects)) if (Objects[i].type == OBJ_MARKER) if (Objects[i].id == id) return 1; return 0; } // ----------------------------------------------------------------------------- void set_escort_special_goal(int special_key) { int marker_key; Buddy_messages_suppressed = 0; if (!Buddy_allowed_to_talk) { ok_for_buddy_to_talk(); if (!Buddy_allowed_to_talk) { for (objnum_t i=object_first;; i++) { if (!(i <= Highest_object_index)) { HUD_init_message_literal(HM_DEFAULT, "No Guide-Bot in mine."); break; } if ((Objects[i].type == OBJ_ROBOT) && Robot_info[get_robot_id(&Objects[i])].companion) { HUD_init_message(HM_DEFAULT, "%s has not been released.",PlayerCfg.GuidebotName); break; } } return; } } special_key = special_key & (~KEY_SHIFTED); marker_key = special_key; if (Last_buddy_key == special_key) { if ((Looking_for_marker == -1) && (special_key != KEY_0)) { if (marker_exists_in_mine(marker_key - KEY_1)) Looking_for_marker = marker_key - KEY_1; else { Last_buddy_message_time = 0; // Force this message to get through. buddy_message("Marker %i not placed.", marker_key - KEY_1 + 1); Looking_for_marker = -1; } } else { Looking_for_marker = -1; } } Last_buddy_key = special_key; if (special_key == KEY_0) Looking_for_marker = -1; if ( Looking_for_marker != -1 ) { Escort_special_goal = ESCORT_GOAL_MARKER1 + marker_key - KEY_1; } else { switch (special_key) { case KEY_1: Escort_special_goal = ESCORT_GOAL_ENERGY; break; case KEY_2: Escort_special_goal = ESCORT_GOAL_ENERGYCEN; break; case KEY_3: Escort_special_goal = ESCORT_GOAL_SHIELD; break; case KEY_4: Escort_special_goal = ESCORT_GOAL_POWERUP; break; case KEY_5: Escort_special_goal = ESCORT_GOAL_ROBOT; break; case KEY_6: Escort_special_goal = ESCORT_GOAL_HOSTAGE; break; case KEY_7: Escort_special_goal = ESCORT_GOAL_SCRAM; break; case KEY_8: Escort_special_goal = ESCORT_GOAL_PLAYER_SPEW; break; case KEY_9: Escort_special_goal = ESCORT_GOAL_EXIT; break; case KEY_0: Escort_special_goal = -1; break; default: Int3(); // Oops, called with illegal key value. } } Last_buddy_message_time = GameTime64 - 2*F1_0; // Allow next message to come through. say_escort_goal(Escort_special_goal); // -- Escort_goal_object = escort_set_goal_object(); Escort_goal_object = ESCORT_GOAL_UNSPECIFIED; } // ----------------------------------------------------------------------------- // Return id of boss. static int get_boss_id(void) { range_for (auto i, highest_valid(Objects)) if (Objects[i].type == OBJ_ROBOT) if (Robot_info[get_robot_id(&Objects[i])].boss_flag) return Objects[i].id; return -1; } // ----------------------------------------------------------------------------- // Return object index if object of objtype, objid exists in mine, else return -1 // "special" is used to find objects spewed by player which is hacked into flags field of powerup. static objnum_t exists_in_mine_2(segnum_t segnum, int objtype, int objid, int special) { range_for (auto curobjp, objects_in(Segments[segnum])) { const auto &objnum = curobjp; if (special == ESCORT_GOAL_PLAYER_SPEW) { if (curobjp->flags & OF_PLAYER_DROPPED) return objnum; } if (curobjp->type == objtype) { // Don't find escort robots if looking for robot! if ((curobjp->type == OBJ_ROBOT) && (Robot_info[curobjp->id].companion)) ; else if (objid == -1) { if ((objtype == OBJ_POWERUP) && (curobjp->id != POW_KEY_BLUE) && (curobjp->id != POW_KEY_GOLD) && (curobjp->id != POW_KEY_RED)) return objnum; else return objnum; } else if (curobjp->id == objid) return objnum; } if (objtype == OBJ_POWERUP) if (curobjp->contains_count) if (curobjp->contains_type == OBJ_POWERUP) if (curobjp->contains_id == objid) return objnum; } return object_none; } // ----------------------------------------------------------------------------- static segnum_t exists_fuelcen_in_mine(segnum_t start_seg) { segnum_t bfs_list[MAX_SEGMENTS]; unsigned length; create_bfs_list(start_seg, bfs_list, length); segnum_t segnum; for (int segindex=0; segindex