- /* 
-  * 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 <stdio.h>              // for printf() 
- #include <stdlib.h>             // for rand() and qsort() 
- #include <string.h>             // 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 "hudmsg.h" 
- #include "cntrlcen.h" 
- #include "gauges.h" 
- #include "event.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 "d_range.h" 
- #include "partial_range.h" 
- #include <utility> 
-   
- #if DXX_USE_EDITOR 
- #include "editor/editor.h" 
- #endif 
-   
- namespace dsx { 
-   
- static void show_escort_menu(const std::array<char, 300> &); 
- static void say_escort_goal(escort_goal_t goal_num); 
-   
- constexpr std::array<char[12], ESCORT_GOAL_MARKER9> Escort_goal_text = {{ 
-         "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  " 
- }}; 
-   
- constexpr std::integral_constant<unsigned, 200> Max_escort_length{}; 
-   
- void init_buddy_for_level(void) 
- { 
-         auto &Objects = LevelUniqueObjectState.Objects; 
-         auto &vmobjptridx = Objects.vmptridx; 
-         auto &Robot_info = LevelSharedRobotInfoState.Robot_info; 
-         auto &BuddyState = LevelUniqueObjectState.BuddyState; 
-         BuddyState = {}; 
-         BuddyState.Buddy_gave_hint_count = 5; 
-         BuddyState.Looking_for_marker = game_marker_index::None; 
-         BuddyState.Escort_goal_object = ESCORT_GOAL_UNSPECIFIED; 
-         BuddyState.Escort_special_goal = ESCORT_GOAL_UNSPECIFIED; 
-         BuddyState.Last_buddy_key = -1; 
-         BuddyState.Buddy_sorry_time = -F1_0; 
-         BuddyState.Buddy_last_seen_player = 0; 
-         BuddyState.Buddy_last_missile_time = 0; 
-         BuddyState.Last_time_buddy_gave_hint = 0; 
-         BuddyState.Last_come_back_message_time = 0; 
-         BuddyState.Escort_last_path_created = 0; 
-         BuddyState.Buddy_last_player_path_created = 0; 
-         BuddyState.Last_buddy_message_time = 0; 
-         BuddyState.Buddy_objnum = find_escort(vmobjptridx, Robot_info); 
- } 
-   
- //      ----------------------------------------------------------------------------- 
- //      See if segment from curseg through sidenum is reachable. 
- //      Return true if it is reachable, else return false. 
- static int segment_is_reachable(const vmobjptr_t robot, const shared_segment &segp, int sidenum, const player_flags powerup_flags) 
- { 
-         const auto wall_num = segp.sides[sidenum].wall_num; 
-   
-         //      If no wall, then it is reachable 
-         if (wall_num == wall_none) 
-                 return 1; 
-   
-         return ai_door_is_openable(robot, powerup_flags, segp, sidenum); 
-   
- // -- 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 
- std::size_t create_bfs_list(const vmobjptr_t robot, const vcsegidx_t start_seg, const player_flags powerup_flags, segnum_t *const bfs_list, std::size_t max_segs) 
- { 
-         std::size_t head = 0, tail = 0; 
-         visited_segment_bitarray_t visited; 
-         bfs_list[head++] = start_seg; 
-         visited[start_seg] = true; 
-   
-         while ((head != tail) && (head < max_segs)) { 
-                 auto curseg = bfs_list[tail++]; 
-                 const auto &&cursegp = vcsegptr(curseg); 
-                 for (int i=0; i<MAX_SIDES_PER_SEGMENT; i++) { 
-                         auto connected_seg = cursegp->children[i]; 
-   
-                         if (IS_CHILD(connected_seg) && (!visited[connected_seg])) { 
-                                 if (segment_is_reachable(robot, cursegp, i, powerup_flags)) { 
-                                         bfs_list[head++] = connected_seg; 
-                                         if (head >= max_segs) 
-                                                 break; 
-                                         visited[connected_seg] = true; 
-                                         Assert(head < MAX_SEGMENTS); 
-                                 } 
-                         } 
-                 } 
-         } 
-         return 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 uint8_t ok_for_buddy_to_talk(void) 
- { 
-         auto &Objects = LevelUniqueObjectState.Objects; 
-         auto &vmobjptridx = Objects.vmptridx; 
-         auto &BuddyState = LevelUniqueObjectState.BuddyState; 
-         const auto Buddy_objnum = BuddyState.Buddy_objnum; 
-         if (Buddy_objnum == object_none) 
-                 return 0; 
-   
-         const vmobjptridx_t buddy = vmobjptridx(Buddy_objnum); 
-         const auto buddy_type = buddy->type; 
-         if (buddy_type != OBJ_ROBOT) { 
-                 BuddyState.Buddy_allowed_to_talk = 0; 
-                 BuddyState.Buddy_objnum = object_none; 
-                 con_printf(CON_URGENT, "BUG: buddy is object %u, but that object is type %u.", Buddy_objnum.get_unchecked_index(), buddy_type); 
-                 return 0; 
-         } 
-   
-         if (const auto Buddy_allowed_to_talk = BuddyState.Buddy_allowed_to_talk) 
-                 return Buddy_allowed_to_talk; 
-   
-         const shared_segment &segp = vcsegptr(buddy->segnum); 
-   
-         auto &Walls = LevelUniqueWallSubsystemState.Walls; 
-         auto &vcwallptr = Walls.vcptr; 
-         for (int i=0; i<MAX_SIDES_PER_SEGMENT; i++) { 
-                 const auto wall_num = segp.sides[i].wall_num; 
-   
-                 if (wall_num != wall_none) { 
-                         auto &w = *vcwallptr(wall_num); 
-                         if (w.type == WALL_BLASTABLE && !(w.flags & WALL_BLASTED)) 
-                                 return 0; 
-                 } 
-   
-                 //      Check one level deeper. 
-                 const auto child = segp.children[i]; 
-                 if (IS_CHILD(child)) 
-                 { 
-                         const shared_segment &cseg = *vcsegptr(child); 
-                         range_for (const auto &j, cseg.sides) 
-                         { 
-                                 auto wall2 = j.wall_num; 
-                                 if (wall2 != wall_none) { 
-                                         auto &w = *vcwallptr(wall2); 
-                                         if (w.type == WALL_BLASTABLE && !(w.flags & WALL_BLASTED)) 
-                                                 return 0; 
-                                 } 
-                         } 
-                 } 
-         } 
-   
-         BuddyState.Buddy_allowed_to_talk = 1; 
-         return 1; 
- } 
-   
- static void record_escort_goal_accomplished() 
- { 
-         auto &BuddyState = LevelUniqueObjectState.BuddyState; 
-         if (ok_for_buddy_to_talk()) { 
-                 digi_play_sample_once(SOUND_BUDDY_MET_GOAL, F1_0); 
-                 BuddyState.Escort_goal_objidx = object_none; 
-                 BuddyState.Escort_goal_reachable = d_unique_buddy_state::Escort_goal_reachability::unreachable; 
-                 BuddyState.Looking_for_marker = game_marker_index::None; 
-                 BuddyState.Escort_goal_object = ESCORT_GOAL_UNSPECIFIED; 
-                 BuddyState.Escort_special_goal = ESCORT_GOAL_UNSPECIFIED; 
-         } 
- } 
-   
- //      -------------------------------------------------------------------------------------------- 
- void detect_escort_goal_fuelcen_accomplished() 
- { 
-         auto &BuddyState = LevelUniqueObjectState.BuddyState; 
-         if (!BuddyState.Buddy_allowed_to_talk) 
-                 return; 
-         if (BuddyState.Escort_special_goal == ESCORT_GOAL_ENERGYCEN) 
-                 record_escort_goal_accomplished(); 
- } 
-   
- void detect_escort_goal_accomplished(const vmobjptridx_t index) 
- { 
-         auto &Objects = LevelUniqueObjectState.Objects; 
-         auto &vcobjptr = Objects.vcptr; 
-         auto &BuddyState = LevelUniqueObjectState.BuddyState; 
-         if (!BuddyState.Buddy_allowed_to_talk) 
-                 return; 
-   
-         // If goal is not an object (FUELCEN, EXIT, SCRAM), bail. FUELCEN is handled in detect_escort_goal_fuelcen_accomplished(), EXIT and SCRAM are never accomplished. 
-         if (BuddyState.Escort_goal_reachable == d_unique_buddy_state::Escort_goal_reachability::unreachable) 
-                 return; 
-         const auto Escort_goal_objidx = BuddyState.Escort_goal_objidx; 
-         if (Escort_goal_objidx == object_none) 
-         { 
-                 con_printf(CON_URGENT, "BUG: buddy goal is reachable, but goal object is object_none"); 
-                 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 (BuddyState.Escort_special_goal == ESCORT_GOAL_UNSPECIFIED && Escort_goal_objidx == index) 
-         { 
-                 record_escort_goal_accomplished(); 
-                 return; 
-         } 
-   
-         if (index->type == OBJ_POWERUP)  { 
-                 const auto index_id = get_powerup_id(index); 
-                 escort_goal_t goal_key; 
-                 if ((index_id == POW_KEY_BLUE && (goal_key = ESCORT_GOAL_BLUE_KEY, true)) || 
-                         (index_id == POW_KEY_GOLD && (goal_key = ESCORT_GOAL_GOLD_KEY, true)) || 
-                         (index_id == POW_KEY_RED && (goal_key = ESCORT_GOAL_RED_KEY, true)) 
-                 ) 
-                 { 
-                         if (BuddyState.Escort_goal_object == goal_key) 
-                         { 
-                                 record_escort_goal_accomplished(); 
-                                 return; 
-                         } 
-                 } 
-         } 
-         if (BuddyState.Escort_special_goal != ESCORT_GOAL_UNSPECIFIED) 
-         { 
-                 if (index->type == OBJ_POWERUP && BuddyState.Escort_special_goal == ESCORT_GOAL_POWERUP) 
-                         record_escort_goal_accomplished();      //      Any type of powerup picked up will do. 
-                 else 
-                 { 
-                         auto &egi_obj = *vcobjptr(Escort_goal_objidx); 
-                         if (index->type == egi_obj.type && index->id == egi_obj.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(); 
-                 } 
-         } 
- } 
-   
- #define DXX_GUIDEBOT_RENAME_MENU(VERB)  \ 
-         DXX_MENUITEM(VERB, INPUT, text, opt_name)       \ 
-   
- void change_guidebot_name() 
- { 
-         auto text = PlayerCfg.GuidebotName; 
-         std::array<newmenu_item, DXX_GUIDEBOT_RENAME_MENU(COUNT)> m; 
-         enum 
-         { 
-                 DXX_GUIDEBOT_RENAME_MENU(ENUM) 
-         }; 
-         DXX_GUIDEBOT_RENAME_MENU(ADD); 
-         const auto item = newmenu_do(nullptr, "Enter Guide-bot name:", m, unused_newmenu_subfunction, unused_newmenu_userdata); 
-   
-         if (item != -1) { 
-                 PlayerCfg.GuidebotName = PlayerCfg.GuidebotNameReal = text; 
-                 write_player_file(); 
-         } 
- } 
-   
- #undef DXX_GUIDEBOT_RENAME_MENU 
-   
- //      ----------------------------------------------------------------------------- 
- static uint8_t show_buddy_message() 
- { 
-         auto &BuddyState = LevelUniqueObjectState.BuddyState; 
-         if (BuddyState.Buddy_messages_suppressed) 
-                 return 0; 
-   
-         if (Game_mode & GM_MULTI) 
-         { 
-                 if (!Netgame.AllowGuidebot) 
-                         return 0; 
-         } 
-   
-         if (BuddyState.Last_buddy_message_time + F1_0 < GameTime64) { 
-                 if (const auto r = ok_for_buddy_to_talk()) 
-                         return r; 
-         } 
-         return 0; 
- } 
-   
- __attribute_nonnull() 
- static void buddy_message_force_str(const char *str) 
- { 
-         auto &BuddyState = LevelUniqueObjectState.BuddyState; 
-         BuddyState.Last_buddy_message_time = GameTime64; 
-         HUD_init_message(HM_DEFAULT, "%c%c%s:%c%c %s", CC_COLOR, BM_XRGB(28, 0, 0), static_cast<const char *>(PlayerCfg.GuidebotName), CC_COLOR, BM_XRGB(0, 31, 0), str); 
- } 
-   
- __attribute_format_printf(1, 0) 
- static void buddy_message_force_va(const char *const fmt, va_list vl) 
- { 
-         char buf[128]; 
-         vsnprintf(buf, sizeof(buf), fmt, vl); 
-         buddy_message_force_str(buf); 
- } 
-   
- __attribute_format_printf(1, 2) 
- static void buddy_message_ignore_time(const char *const fmt, ...) 
- { 
-         auto &BuddyState = LevelUniqueObjectState.BuddyState; 
-         if (BuddyState.Buddy_messages_suppressed) 
-                 return; 
-         if (!ok_for_buddy_to_talk()) 
-                 return; 
-         va_list args; 
-         va_start(args, fmt); 
-         buddy_message_force_va(fmt, args); 
-         va_end(args); 
- } 
-   
- void (buddy_message)(const char * format, ... ) 
- { 
-         if (!show_buddy_message()) 
-                 return; 
-   
-                         va_list args; 
-   
-                         va_start(args, format ); 
-         buddy_message_force_va(format, args); 
-                         va_end(args); 
- } 
-   
- void buddy_message_str(const char *str) 
- { 
-         if (!show_buddy_message()) 
-                 return; 
-         buddy_message_force_str(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(const game_marker_index id) 
- { 
-         auto &Objects = LevelUniqueObjectState.Objects; 
-         auto &vcobjptr = Objects.vcptr; 
-         range_for (const auto &&objp, vcobjptr) 
-         { 
-                 if (objp->type == OBJ_MARKER) 
-                         if (get_marker_id(objp) == id) 
-                                 return 1; 
-         } 
-         return 0; 
- } 
-   
- //      ----------------------------------------------------------------------------- 
- void set_escort_special_goal(d_unique_buddy_state &BuddyState, const int raw_special_key) 
- { 
-         auto &Objects = LevelUniqueObjectState.Objects; 
-         auto &vmobjptridx = Objects.vmptridx; 
-         int marker_key; 
-   
-         BuddyState.Buddy_messages_suppressed = 0; 
-   
-         if (!BuddyState.Buddy_allowed_to_talk) { 
-                 if (!ok_for_buddy_to_talk()) { 
-                         auto &Robot_info = LevelSharedRobotInfoState.Robot_info; 
-                         const auto &&o = find_escort(vmobjptridx, Robot_info); 
-                         if (o == object_none) 
-                                 HUD_init_message_literal(HM_DEFAULT, "No Guide-Bot in mine."); 
-                         else 
-                                 HUD_init_message(HM_DEFAULT, "%s has not been released.", static_cast<const char *>(PlayerCfg.GuidebotName)); 
-                         return; 
-                 } 
-         } 
-   
-         const int special_key = raw_special_key & (~KEY_SHIFTED); 
-   
-         marker_key = special_key; 
-          
-         if (BuddyState.Last_buddy_key == special_key) 
-         { 
-                 auto &Looking_for_marker = BuddyState.Looking_for_marker; 
-                 if (Looking_for_marker == game_marker_index::None && special_key != KEY_0) 
-                 { 
-                         const unsigned zero_based_marker_id = marker_key - KEY_1; 
-                         const auto gmi = static_cast<game_marker_index>(zero_based_marker_id); 
-                         if (marker_exists_in_mine(gmi)) 
-                                 Looking_for_marker = gmi; 
-                         else { 
-                                 buddy_message_ignore_time("Marker %i not placed.", zero_based_marker_id + 1); 
-                                 Looking_for_marker = game_marker_index::None; 
-                         } 
-                 } else { 
-                         Looking_for_marker = game_marker_index::None; 
-                 } 
-         } 
-   
-         BuddyState.Last_buddy_key = special_key; 
-   
-         if (special_key == KEY_0) 
-                 BuddyState.Looking_for_marker = game_marker_index::None; 
-         else if (BuddyState.Looking_for_marker != game_marker_index::None) 
-         { 
-                 BuddyState.Escort_special_goal = static_cast<escort_goal_t>(ESCORT_GOAL_MARKER1 + marker_key - KEY_1); 
-         } else { 
-                 auto &Escort_special_goal = BuddyState.Escort_special_goal; 
-                 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 = ESCORT_GOAL_UNSPECIFIED;                                                          break; 
-                         default: 
-                                 Int3();         //      Oops, called with illegal key value. 
-                 } 
-         } 
-   
-         BuddyState.Last_buddy_message_time = GameTime64 - 2*F1_0;       //      Allow next message to come through. 
-   
-         say_escort_goal(BuddyState.Escort_special_goal); 
-         BuddyState.Escort_goal_object = ESCORT_GOAL_UNSPECIFIED; 
-         multi_send_escort_goal(BuddyState); 
- } 
-   
- //      ----------------------------------------------------------------------------- 
- //      Return id of boss. 
- static int get_boss_id(void) 
- { 
-         auto &Objects = LevelUniqueObjectState.Objects; 
-         auto &vcobjptr = Objects.vcptr; 
-         auto &Robot_info = LevelSharedRobotInfoState.Robot_info; 
-         range_for (const auto &&objp, vcobjptr) 
-         { 
-                 if (objp->type == OBJ_ROBOT) 
-                 { 
-                         const auto objp_id = get_robot_id(objp); 
-                         if (Robot_info[objp_id].boss_flag) 
-                                 return objp_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 icobjidx_t exists_in_mine_2(const unique_segment &segp, const int objtype, const int objid, const int special) 
- { 
-         auto &Objects = LevelUniqueObjectState.Objects; 
-         auto &vcobjptridx = Objects.vcptridx; 
-         auto &Robot_info = LevelSharedRobotInfoState.Robot_info; 
-         range_for (const auto curobjp, objects_in(segp, vcobjptridx, vcsegptr)) 
-         { 
-                         if (special == ESCORT_GOAL_PLAYER_SPEW && curobjp->type == OBJ_POWERUP) 
-                         { 
-                                 if (curobjp->flags & OF_PLAYER_DROPPED) 
-                                         return curobjp; 
-                         } 
-   
-                         if (curobjp->type == objtype) { 
-                                 //      Don't find escort robots if looking for robot! 
-                                 if ((curobjp->type == OBJ_ROBOT) && (Robot_info[get_robot_id(curobjp)].companion)) 
-                                         ; 
-                                 else if (objid == -1) { 
-                                                 return curobjp; 
-                                 } else if (curobjp->id == objid) 
-                                         return curobjp; 
-                         } 
-   
-                         if (objtype == OBJ_POWERUP && curobjp->type == OBJ_ROBOT) 
-                                 if (curobjp->contains_count) 
-                                         if (curobjp->contains_type == OBJ_POWERUP) 
-                                                 if (curobjp->contains_id == objid) 
-                                                         return curobjp; 
-         } 
-         return object_none; 
- } 
-   
- //      ----------------------------------------------------------------------------- 
- static std::pair<icsegidx_t, d_unique_buddy_state::Escort_goal_reachability> exists_fuelcen_in_mine(const vcsegidx_t start_seg, const player_flags powerup_flags) 
- { 
-         auto &Objects = LevelUniqueObjectState.Objects; 
-         auto &vmobjptr = Objects.vmptr; 
-         std::array<segnum_t, MAX_SEGMENTS> bfs_list; 
-         auto &BuddyState = LevelUniqueObjectState.BuddyState; 
-         const auto Buddy_objnum = BuddyState.Buddy_objnum; 
-         const auto length = create_bfs_list(vmobjptr(Buddy_objnum), start_seg, powerup_flags, bfs_list); 
-         { 
-                 const auto &&predicate = [](const segnum_t &s) { 
-                         return vcsegptr(s)->special == SEGMENT_IS_FUELCEN; 
-                 }; 
-                 const auto &&rb = partial_const_range(bfs_list, length); 
-                 const auto i = std::find_if(rb.begin(), rb.end(), predicate); 
-                 if (i != rb.end()) 
-                         return {*i, d_unique_buddy_state::Escort_goal_reachability::reachable}; 
-         } 
-         { 
-                 const auto &rh = vcsegptridx; 
-                 const auto &&predicate = [](const shared_segment &s) { 
-                         return s.special == SEGMENT_IS_FUELCEN; 
-                 }; 
-                 const auto i = std::find_if(rh.begin(), rh.end(), predicate); 
-                 if (i != rh.end()) 
-                         return {*i, d_unique_buddy_state::Escort_goal_reachability::unreachable}; 
-         } 
-         return {segment_none, d_unique_buddy_state::Escort_goal_reachability::unreachable}; 
- } 
-   
- //      Return nearest object of interest. 
- //      If special == ESCORT_GOAL_PLAYER_SPEW, then looking for any object spewed by player. 
- //      -1 means object does not exist in mine. 
- //      -2 means object does exist in mine, but buddy-bot can't reach it (eg, behind triggered wall) 
- static std::pair<icobjidx_t, d_unique_buddy_state::Escort_goal_reachability> exists_in_mine(const vcsegidx_t start_seg, const int objtype, const int objid, const int special, const player_flags powerup_flags) 
- { 
-         auto &Objects = LevelUniqueObjectState.Objects; 
-         auto &vmobjptr = Objects.vmptr; 
-         std::array<segnum_t, MAX_SEGMENTS> bfs_list; 
-         auto &BuddyState = LevelUniqueObjectState.BuddyState; 
-         const auto Buddy_objnum = BuddyState.Buddy_objnum; 
-         const auto length = create_bfs_list(vmobjptr(Buddy_objnum), start_seg, powerup_flags, bfs_list); 
-   
-         range_for (const auto segnum, partial_const_range(bfs_list, length)) 
-         { 
-                 const auto &&objnum = exists_in_mine_2(vcsegptr(segnum), objtype, objid, special); 
-                         if (objnum != object_none) 
-                                 return {objnum, d_unique_buddy_state::Escort_goal_reachability::reachable}; 
-         } 
-   
-         //      Couldn't find what we're looking for by looking at connectivity. 
-         //      See if it's in the mine.  It could be hidden behind a trigger or switch 
-         //      which the buddybot doesn't understand. 
-         range_for (const auto &&segnum, vcsegptr) 
-                 { 
-                 const auto &&objnum = exists_in_mine_2(segnum, objtype, objid, special); 
-                         if (objnum != object_none) 
-                                 return {objnum, d_unique_buddy_state::Escort_goal_reachability::unreachable}; 
-                 } 
-         return {object_none, d_unique_buddy_state::Escort_goal_reachability::unreachable}; 
- } 
-   
- //      ----------------------------------------------------------------------------- 
- //      Return true if it happened, else return false. 
- static imsegidx_t find_exit_segment() 
- { 
-         //      ---------- Find exit doors ---------- 
-         range_for (const auto &&segp, vcsegptridx) 
-         { 
-                 range_for (const auto j, segp->children) 
-                         if (j == segment_exit) 
-                                 return segp; 
-         } 
-         return segment_none; 
- } 
-   
- //      ----------------------------------------------------------------------------- 
- static void say_escort_goal(const escort_goal_t goal_num) 
- { 
-         if (Player_dead_state != player_dead_state::no) 
-                 return; 
-   
-         const char *str; 
-         switch (goal_num) { 
-                 default: 
-                 case ESCORT_GOAL_UNSPECIFIED: 
-                         return; 
-                 case ESCORT_GOAL_BLUE_KEY: 
-                         str = "Finding BLUE KEY"; 
-                         break; 
-                 case ESCORT_GOAL_GOLD_KEY: 
-                         str = "Finding YELLOW KEY"; 
-                         break; 
-                 case ESCORT_GOAL_RED_KEY: 
-                         str = "Finding RED KEY"; 
-                         break; 
-                 case ESCORT_GOAL_CONTROLCEN: 
-                         str = "Finding REACTOR"; 
-                         break; 
-                 case ESCORT_GOAL_EXIT: 
-                         str = "Finding EXIT"; 
-                         break; 
-                 case ESCORT_GOAL_ENERGY: 
-                         str = "Finding ENERGY"; 
-                         break; 
-                 case ESCORT_GOAL_ENERGYCEN: 
-                         str = "Finding ENERGY CENTER"; 
-                         break; 
-                 case ESCORT_GOAL_SHIELD: 
-                         str = "Finding a SHIELD"; 
-                         break; 
-                 case ESCORT_GOAL_POWERUP: 
-                         str = "Finding a POWERUP"; 
-                         break; 
-                 case ESCORT_GOAL_ROBOT: 
-                         str = "Finding a ROBOT"; 
-                         break; 
-                 case ESCORT_GOAL_HOSTAGE: 
-                         str = "Finding a HOSTAGE"; 
-                         break; 
-                 case ESCORT_GOAL_SCRAM: 
-                         str = "Staying away..."; 
-                         break; 
-                 case ESCORT_GOAL_BOSS: 
-                         str = "Finding BOSS robot"; 
-                         break; 
-                 case ESCORT_GOAL_PLAYER_SPEW: 
-                         str = "Finding your powerups"; 
-                         break; 
-                 case ESCORT_GOAL_MARKER1: 
-                 case ESCORT_GOAL_MARKER2: 
-                 case ESCORT_GOAL_MARKER3: 
-                 case ESCORT_GOAL_MARKER4: 
-                 case ESCORT_GOAL_MARKER5: 
-                 case ESCORT_GOAL_MARKER6: 
-                 case ESCORT_GOAL_MARKER7: 
-                 case ESCORT_GOAL_MARKER8: 
-                 case ESCORT_GOAL_MARKER9: 
-                         { 
-                                 const uint8_t zero_based_goal_num = goal_num - ESCORT_GOAL_MARKER1; 
-                                 buddy_message("Finding marker %i: '%.24s'", zero_based_goal_num + 1, &MarkerState.message[game_marker_index{zero_based_goal_num}][0]); 
-                         } 
-                         return; 
-         } 
-         buddy_message_str(str); 
- } 
-   
- static void clear_escort_goals() 
- { 
-         auto &BuddyState = LevelUniqueObjectState.BuddyState; 
-         BuddyState.Looking_for_marker = game_marker_index::None; 
-         BuddyState.Escort_goal_object = ESCORT_GOAL_UNSPECIFIED; 
-         BuddyState.Escort_special_goal = ESCORT_GOAL_UNSPECIFIED; 
- } 
-   
- static void escort_goal_does_not_exist(const escort_goal_t goal) 
- { 
-         buddy_message_ignore_time("No %s in mine.", Escort_goal_text[goal - 1]); 
-         clear_escort_goals(); 
- } 
-   
- static void escort_goal_unreachable(const escort_goal_t goal) 
- { 
-         buddy_message_ignore_time("Can't reach %s.", Escort_goal_text[goal - 1]); 
-         clear_escort_goals(); 
- } 
-   
- static void escort_go_to_goal(const vmobjptridx_t objp, ai_static *const aip, const segnum_t goal_seg) 
- { 
-         auto &BuddyState = LevelUniqueObjectState.BuddyState; 
-         auto &Objects = LevelUniqueObjectState.Objects; 
-         create_path_to_segment(objp, goal_seg, Max_escort_length, create_path_safety_flag::safe);       //      MK!: Last parm (safety_flag) used to be 1!! 
-         if (aip->path_length > 3) 
-                 aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length); 
-         if ((aip->path_length > 0) && (Point_segs[aip->hide_index + aip->path_length - 1].segnum != goal_seg)) { 
-                 const unsigned goal_text_index = std::exchange(BuddyState.Escort_goal_object, ESCORT_GOAL_SCRAM) - 1; 
-                 BuddyState.Looking_for_marker = game_marker_index::None; 
-                 auto &plr = get_player_controlling_guidebot(BuddyState, Players); 
-                 if (plr.objnum == object_none) 
-                         return; 
-                 auto &plrobj = *Objects.vcptr(plr.objnum); 
-                 if (plrobj.type != OBJ_PLAYER) 
-                         return; 
-                 buddy_message_ignore_time("Cannot reach %s.", goal_text_index < Escort_goal_text.size() ? Escort_goal_text[goal_text_index] : "<unknown>"); 
-                 const auto goal_segment = plrobj.segnum; 
-                 const fix dist_to_player = find_connected_distance(objp->pos, vmsegptridx(objp->segnum), plrobj.pos, vmsegptridx(goal_segment), 100, WID_FLY_FLAG); 
-                 if (dist_to_player > MIN_ESCORT_DISTANCE) 
-                         create_path_to_segment(objp, Max_escort_length, create_path_safety_flag::safe, goal_segment); 
-                 else { 
-                         create_n_segment_path(objp, 8 + d_rand() * 8, segment_none); 
-                         aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length); 
-                 } 
-         } 
- } 
-   
- //      ----------------------------------------------------------------------------- 
- static imsegidx_t escort_get_goal_segment(const object_base &buddy_obj, const int objtype, const int objid, const player_flags powerup_flags) 
- { 
-         auto &BuddyState = LevelUniqueObjectState.BuddyState; 
-         auto &Objects = LevelUniqueObjectState.Objects; 
-         auto &vcobjptr = Objects.vcptr; 
-         const auto &&eim = exists_in_mine(buddy_obj.segnum, objtype, objid, -1, powerup_flags); 
-         BuddyState.Escort_goal_objidx = eim.first; 
-         BuddyState.Escort_goal_reachable = eim.second; 
-         if (eim.second != d_unique_buddy_state::Escort_goal_reachability::unreachable) 
-                 return vcobjptr(eim.first)->segnum; 
-         return segment_none; 
- } 
-   
- static void set_escort_goal_non_object(d_unique_buddy_state &BuddyState) 
- { 
-         BuddyState.Escort_goal_objidx = object_none; 
-         BuddyState.Escort_goal_reachable = d_unique_buddy_state::Escort_goal_reachability::unreachable; 
- } 
-   
- static void escort_create_path_to_goal(const vmobjptridx_t objp, const player_info &player_info) 
- { 
-         auto &BuddyState = LevelUniqueObjectState.BuddyState; 
-         auto &Objects = LevelUniqueObjectState.Objects; 
-         auto &vcobjptr = Objects.vcptr; 
-         segnum_t        goal_seg = segment_none; 
-         ai_static       *aip = &objp->ctype.ai_info; 
-         ai_local                *ailp = &objp->ctype.ai_info.ail; 
-   
-         if (BuddyState.Escort_special_goal != ESCORT_GOAL_UNSPECIFIED) 
-                 BuddyState.Escort_goal_object = BuddyState.Escort_special_goal; 
-   
-         const auto powerup_flags = player_info.powerup_flags; 
-         const auto Escort_goal_object = BuddyState.Escort_goal_object; 
-         if (BuddyState.Looking_for_marker != game_marker_index::None) 
-         { 
-                 goal_seg = escort_get_goal_segment(objp, OBJ_MARKER, Escort_goal_object - ESCORT_GOAL_MARKER1, powerup_flags); 
-         } else { 
-                 switch (Escort_goal_object) { 
-                         case ESCORT_GOAL_BLUE_KEY: 
-                                 goal_seg = escort_get_goal_segment(objp, OBJ_POWERUP, POW_KEY_BLUE, powerup_flags); 
-                                 break; 
-                         case ESCORT_GOAL_GOLD_KEY: 
-                                 goal_seg = escort_get_goal_segment(objp, OBJ_POWERUP, POW_KEY_GOLD, powerup_flags); 
-                                 break; 
-                         case ESCORT_GOAL_RED_KEY: 
-                                 goal_seg = escort_get_goal_segment(objp, OBJ_POWERUP, POW_KEY_RED, powerup_flags); 
-                                 break; 
-                         case ESCORT_GOAL_CONTROLCEN: 
-                                 goal_seg = escort_get_goal_segment(objp, OBJ_CNTRLCEN, -1, powerup_flags); 
-                                 break; 
-                         case ESCORT_GOAL_EXIT: 
-                                 goal_seg = find_exit_segment(); 
-                                 set_escort_goal_non_object(BuddyState); 
-                                 if (goal_seg == segment_none) 
-                                         escort_goal_does_not_exist(ESCORT_GOAL_EXIT); 
-                                 else if (goal_seg == segment_exit) 
-                                         escort_goal_unreachable(ESCORT_GOAL_EXIT); 
-                                 else 
-                                         escort_go_to_goal(objp, aip, goal_seg); 
-                                 return; 
-                         case ESCORT_GOAL_ENERGY: 
-                                 goal_seg = escort_get_goal_segment(objp, OBJ_POWERUP, POW_ENERGY, powerup_flags); 
-                                 break; 
-                         case ESCORT_GOAL_ENERGYCEN: 
-                                 { 
-                                         const auto &&ef = exists_fuelcen_in_mine(objp->segnum, powerup_flags); 
-                                         set_escort_goal_non_object(BuddyState); 
-                                 if (ef.second != d_unique_buddy_state::Escort_goal_reachability::unreachable) 
-                                         escort_go_to_goal(objp, aip, ef.first); 
-                                 else if (ef.first == segment_none) 
-                                         escort_goal_does_not_exist(ESCORT_GOAL_ENERGYCEN); 
-                                 else 
-                                         escort_goal_unreachable(ESCORT_GOAL_ENERGYCEN); 
-                                 return; 
-                                 } 
-                         case ESCORT_GOAL_SHIELD: 
-                                 goal_seg = escort_get_goal_segment(objp, OBJ_POWERUP, POW_SHIELD_BOOST, powerup_flags); 
-                                 break; 
-                         case ESCORT_GOAL_POWERUP: 
-                                 goal_seg = escort_get_goal_segment(objp, OBJ_POWERUP, -1, powerup_flags); 
-                                 break; 
-                         case ESCORT_GOAL_ROBOT: 
-                                 goal_seg = escort_get_goal_segment(objp, OBJ_ROBOT, -1, powerup_flags); 
-                                 break; 
-                         case ESCORT_GOAL_HOSTAGE: 
-                                 goal_seg = escort_get_goal_segment(objp, OBJ_HOSTAGE, -1, powerup_flags); 
-                                 break; 
-                         case ESCORT_GOAL_PLAYER_SPEW: 
-                                 { 
-                                         const auto &&egi = exists_in_mine(objp->segnum, -1, -1, ESCORT_GOAL_PLAYER_SPEW, powerup_flags); 
-                                         BuddyState.Escort_goal_objidx = egi.first; 
-                                         BuddyState.Escort_goal_reachable = egi.second; 
-                                         if (egi.second != d_unique_buddy_state::Escort_goal_reachability::unreachable) 
-                                         { 
-                                                 auto &o = *vcobjptr(egi.first); 
-                                                 goal_seg = o.segnum; 
-                                         } 
-                                 } 
-                                 break; 
-                         case ESCORT_GOAL_BOSS: { 
-                                 int     boss_id; 
-          
-                                 boss_id = get_boss_id(); 
-                                 Assert(boss_id != -1); 
-                                 goal_seg = escort_get_goal_segment(objp, OBJ_ROBOT, boss_id, powerup_flags); 
-                                 break; 
-                         } 
-                         default: 
-                                 Int3(); //      Oops, Illegal value in Escort_goal_object. 
-                                 con_printf(CON_URGENT, "BUG: buddy goal is %.8x, resetting to SCRAM", Escort_goal_object); 
-                                 BuddyState.Escort_goal_object = ESCORT_GOAL_SCRAM; 
-                                 DXX_BOOST_FALLTHROUGH; 
-                         case ESCORT_GOAL_SCRAM: 
-                                 set_escort_goal_non_object(BuddyState); 
-                                 create_n_segment_path(objp, 16 + d_rand() * 16, segment_none); 
-                                 aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length); 
-                                 ailp->mode = ai_mode::AIM_GOTO_OBJECT; 
-                                 say_escort_goal(Escort_goal_object); 
-                                 return; 
-                 } 
-         } 
-         const auto Escort_goal_objidx = BuddyState.Escort_goal_objidx; 
-         if (BuddyState.Escort_goal_reachable == d_unique_buddy_state::Escort_goal_reachability::unreachable) 
-         { 
-                 if (Escort_goal_objidx == object_none) { 
-                         escort_goal_does_not_exist(Escort_goal_object); 
-                 } else { 
-                         escort_goal_unreachable(Escort_goal_object); 
-                 } 
-         } else { 
-                 escort_go_to_goal(objp, aip, goal_seg); 
-                 ailp->mode = ai_mode::AIM_GOTO_OBJECT; 
-                 say_escort_goal(Escort_goal_object); 
-         } 
- } 
-   
- //      ----------------------------------------------------------------------------- 
- //      Escort robot chooses goal object based on player's keys, location. 
- //      Returns goal object. 
- static escort_goal_t escort_set_goal_object(const player_flags pl_flags) 
- { 
-         auto &Boss_teleport_segs = LevelSharedBossState.Teleport_segs; 
-         auto &BuddyState = LevelUniqueObjectState.BuddyState; 
-         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState; 
-         auto &Objects = LevelUniqueObjectState.Objects; 
-         if (BuddyState.Escort_special_goal != ESCORT_GOAL_UNSPECIFIED) 
-                 return ESCORT_GOAL_UNSPECIFIED; 
-   
-         auto &plr = get_player_controlling_guidebot(BuddyState, Players); 
-         if (plr.objnum == object_none) 
-                 /* should never happen */ 
-                 return ESCORT_GOAL_UNSPECIFIED; 
-         auto &plrobj = *Objects.vcptr(plr.objnum); 
-         if (plrobj.type != OBJ_PLAYER) 
-                 return ESCORT_GOAL_UNSPECIFIED; 
-   
-         const auto need_key_and_key_exists = [pl_flags, start_search_seg = plrobj.segnum](const PLAYER_FLAG flag_key, const powerup_type_t powerup_key) { 
-                 if (pl_flags & flag_key) 
-                         /* Player already has this key, so no need to get it again. 
-                          */ 
-                         return false; 
-                 const auto &&e = exists_in_mine(start_search_seg, OBJ_POWERUP, powerup_key, -1, pl_flags); 
-                 /* For compatibility with classic Descent 2, test only whether 
-                  * the key exists, but ignore whether it can be reached by the 
-                  * guide bot. 
-                  */ 
-                 return e.first != object_none; 
-         }; 
-         if (need_key_and_key_exists(PLAYER_FLAGS_BLUE_KEY, POW_KEY_BLUE)) 
-                 return ESCORT_GOAL_BLUE_KEY; 
-         else if (need_key_and_key_exists(PLAYER_FLAGS_GOLD_KEY, POW_KEY_GOLD)) 
-                 return ESCORT_GOAL_GOLD_KEY; 
-         else if (need_key_and_key_exists(PLAYER_FLAGS_RED_KEY, POW_KEY_RED)) 
-                 return ESCORT_GOAL_RED_KEY; 
-         else if (LevelUniqueControlCenterState.Control_center_destroyed == 0) 
-         { 
-                 if (!Boss_teleport_segs.empty()) 
-                         return ESCORT_GOAL_BOSS; 
-                 else 
-                         return ESCORT_GOAL_CONTROLCEN; 
-         } else 
-                 return ESCORT_GOAL_EXIT; 
- } 
-   
- #define MAX_ESCORT_TIME_AWAY            (F1_0*4) 
-   
- //      ----------------------------------------------------------------------------- 
- static const player *time_to_visit_player(const d_level_unique_object_state &LevelUniqueObjectState, const object &buddy_object) 
- { 
-         auto &BuddyState = LevelUniqueObjectState.BuddyState; 
-         auto &plr = get_player_controlling_guidebot(BuddyState, Players); 
-         if (plr.objnum == object_none) 
-                 /* should never happen */ 
-                 return nullptr; 
-         auto &Objects = LevelUniqueObjectState.Objects; 
-         auto &plrobj = *Objects.vcptr(plr.objnum); 
-         if (plrobj.type != OBJ_PLAYER) 
-                 return nullptr; 
-         //      Note: This one has highest priority because, even if already going towards player, 
-         //      might be necessary to create a new path, as player can move. 
-         if (GameTime64 - BuddyState.Buddy_last_seen_player > MAX_ESCORT_TIME_AWAY) 
-                 if (GameTime64 - BuddyState.Buddy_last_player_path_created > F1_0) 
-                         return &plr; 
-   
-         auto &ais = buddy_object.ctype.ai_info; 
-         if (ais.ail.mode == ai_mode::AIM_GOTO_PLAYER) 
-                 return nullptr; 
-   
-         if (ais.cur_path_index < ais.path_length / 2) 
-                 return nullptr; 
-   
-         if (buddy_object.segnum == plrobj.segnum) 
-                 return nullptr; 
-         return &plr; 
- } 
-   
- //      ----------------------------------------------------------------------------- 
- static void bash_buddy_weapon_info(d_unique_buddy_state &BuddyState, fvmobjptridx &vmobjptridx, object &weapon_obj) 
- { 
-         auto &plr = get_player_controlling_guidebot(BuddyState, Players); 
-         if (plr.objnum == object_none) 
-                 /* should never happen */ 
-                 return; 
-         /* Buddy can still fire while player is dead, so skip check for 
-          * plrobj.type */ 
-         auto &&plrobj = vmobjptridx(plr.objnum); 
-         auto &laser_info = weapon_obj.ctype.laser_info; 
-         laser_info.parent_num = plrobj; 
-         laser_info.parent_type = OBJ_PLAYER; 
-         laser_info.parent_signature = plrobj->signature; 
- } 
-   
- //      ----------------------------------------------------------------------------- 
- static int maybe_buddy_fire_mega(const vmobjptridx_t objp) 
- { 
-         auto &BuddyState = LevelUniqueObjectState.BuddyState; 
-         auto &Objects = LevelUniqueObjectState.Objects; 
-         const auto Buddy_objnum = BuddyState.Buddy_objnum; 
-         const auto &&buddy_objp = objp.absolute_sibling(Buddy_objnum); 
-         fix             dist, dot; 
-         auto vec_to_robot = vm_vec_sub(buddy_objp->pos, objp->pos); 
-         dist = vm_vec_normalize_quick(vec_to_robot); 
-   
-         if (dist > F1_0*100) 
-                 return 0; 
-   
-         dot = vm_vec_dot(vec_to_robot, buddy_objp->orient.fvec); 
-   
-         if (dot < F1_0/2) 
-                 return 0; 
-   
-         if (!object_to_object_visibility(buddy_objp, objp, FQ_TRANSWALL)) 
-                 return 0; 
-   
-         if (Weapon_info[weapon_id_type::MEGA_ID].render_type == 0) { 
-                 con_puts(CON_VERBOSE, "Buddy can't fire mega (shareware)"); 
-                 buddy_message("CLICK!"); 
-                 return 0; 
-         } 
-   
-         buddy_message("GAHOOGA!"); 
-   
-         const imobjptridx_t weapon_objnum = Laser_create_new_easy( buddy_objp->orient.fvec, buddy_objp->pos, objp, weapon_id_type::MEGA_ID, 1); 
-   
-         if (weapon_objnum != object_none) 
-                 bash_buddy_weapon_info(BuddyState, Objects.vmptridx, weapon_objnum); 
-   
-         return 1; 
- } 
-   
- //----------------------------------------------------------------------------- 
- static int maybe_buddy_fire_smart(const vmobjptridx_t objp) 
- { 
-         auto &BuddyState = LevelUniqueObjectState.BuddyState; 
-         auto &Objects = LevelUniqueObjectState.Objects; 
-         const auto Buddy_objnum = BuddyState.Buddy_objnum; 
-         const auto &&buddy_objp = objp.absolute_sibling(Buddy_objnum); 
-         fix             dist; 
-   
-         dist = vm_vec_dist_quick(buddy_objp->pos, objp->pos); 
-   
-         if (dist > F1_0*80) 
-                 return 0; 
-   
-         if (!object_to_object_visibility(buddy_objp, objp, FQ_TRANSWALL)) 
-                 return 0; 
-   
-         buddy_message("WHAMMO!"); 
-   
-         const imobjptridx_t weapon_objnum = Laser_create_new_easy( buddy_objp->orient.fvec, buddy_objp->pos, objp, weapon_id_type::SMART_ID, 1); 
-   
-         if (weapon_objnum != object_none) 
-                 bash_buddy_weapon_info(BuddyState, Objects.vmptridx, weapon_objnum); 
-   
-         return 1; 
- } 
-   
- //      ----------------------------------------------------------------------------- 
- static void do_buddy_dude_stuff(void) 
- { 
-         auto &Objects = LevelUniqueObjectState.Objects; 
-         auto &BuddyState = LevelUniqueObjectState.BuddyState; 
-         auto &vmobjptridx = Objects.vmptridx; 
-         if (!ok_for_buddy_to_talk()) 
-                 return; 
-   
-         if (BuddyState.Buddy_last_missile_time + F1_0*2 < GameTime64) { 
-                 //      See if a robot potentially in view cone 
-                 auto &Robot_info = LevelSharedRobotInfoState.Robot_info; 
-                 auto &&rh = make_range(vmobjptridx); 
-                 range_for (const auto &&objp, rh) 
-                 { 
-                         if ((objp->type == OBJ_ROBOT) && !Robot_info[get_robot_id(objp)].companion) 
-                                 if (maybe_buddy_fire_mega(objp)) { 
-                                         BuddyState.Buddy_last_missile_time = GameTime64; 
-                                         return; 
-                                 } 
-                 } 
-   
-                 //      See if a robot near enough that buddy should fire smart missile 
-                 range_for (const auto &&objp, rh) 
-                 { 
-                         if ((objp->type == OBJ_ROBOT) && !Robot_info[get_robot_id(objp)].companion) 
-                                 if (maybe_buddy_fire_smart(objp)) { 
-                                         BuddyState.Buddy_last_missile_time = GameTime64; 
-                                         return; 
-                                 } 
-                 } 
-         } 
- } 
-   
- static void escort_set_goal_toward_controlling_player(d_unique_buddy_state &BuddyState, fvcobjptr &vcobjptr, const vmobjptridx_t buddy_obj) 
- { 
-         auto &aip = buddy_obj->ctype.ai_info; 
-         aip.path_length = polish_path(buddy_obj, &Point_segs[aip.hide_index], aip.path_length); 
-         if (aip.path_length < 3) { 
-                 auto &plr = get_player_controlling_guidebot(BuddyState, Players); 
-                 if (plr.objnum != object_none) 
-                 { 
-                         auto &plrobj = *vcobjptr(plr.objnum); 
-                         if (plrobj.type == OBJ_PLAYER) 
-                                 create_n_segment_path(buddy_obj, 5, plrobj.segnum); 
-                 } 
-         } 
-         aip.ail.mode = ai_mode::AIM_GOTO_OBJECT; 
- } 
-   
- //      ----------------------------------------------------------------------------- 
- //      Called every frame (or something). 
- void do_escort_frame(const vmobjptridx_t objp, const object &plrobj, const player_visibility_state player_visibility) 
- { 
-         auto &BuddyState = LevelUniqueObjectState.BuddyState; 
-         auto &Objects = LevelUniqueObjectState.Objects; 
-         auto &vcobjptr = Objects.vcptr; 
-         const auto dist_to_player = vm_vec_dist_quick(plrobj.pos, objp->pos); 
-         ai_static       *aip = &objp->ctype.ai_info; 
-         ai_local                *ailp = &objp->ctype.ai_info.ail; 
-   
-         auto &player_info = plrobj.ctype.player_info; 
-         if (player_is_visible(player_visibility)) 
-         { 
-                 BuddyState.Buddy_last_seen_player = GameTime64; 
-                 if (player_info.powerup_flags & PLAYER_FLAGS_HEADLIGHT_ON)      //      DAMN! MK, stupid bug, fixed 12/08/95, changed PLAYER_FLAGS_HEADLIGHT to PLAYER_FLAGS_HEADLIGHT_ON 
-                 { 
-                         const auto energy = player_info.energy; 
-                         const auto ienergy = f2i(energy); 
-                         if (ienergy < 40) 
-                                 if (ienergy & 4) 
-                                                 buddy_message("Hey, your headlight's on!"); 
-                 } 
-         } 
-   
-         if (cheats.buddyangry) 
-                 do_buddy_dude_stuff(); 
-   
-         { 
-                 const auto buddy_sorry_time = BuddyState.Buddy_sorry_time; 
-                 if (buddy_sorry_time + F1_0 > GameTime64) 
-                 { 
-                         BuddyState.Buddy_sorry_time = -F1_0*2; 
-                         if (buddy_sorry_time < GameTime64 + F1_0*2) 
-                         { 
-                                 buddy_message_ignore_time("Oops, sorry 'bout that..."); 
-                         } 
-                 } 
-         } 
-   
-         //      If buddy not allowed to talk, then he is locked in his room.  Make him mostly do nothing unless you're nearby. 
-         if (!BuddyState.Buddy_allowed_to_talk) 
-                 if (dist_to_player > F1_0*100) 
-                         aip->SKIP_AI_COUNT = (F1_0/4)/FrameTime; 
-   
-         //      ai_mode::AIM_WANDER has been co-opted for buddy behavior (didn't want to modify aistruct.h) 
-         //      It means the object has been told to get lost and has come to the end of its path. 
-         //      If the player is now visible, then create a path. 
-         if (ailp->mode == ai_mode::AIM_WANDER) 
-                 if (player_is_visible(player_visibility)) 
-                 { 
-                         create_n_segment_path(objp, 16 + d_rand() * 16, segment_none); 
-                         aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length); 
-                 } 
-   
-         if (BuddyState.Escort_special_goal == ESCORT_GOAL_SCRAM) { 
-                 auto &plr = get_player_controlling_guidebot(BuddyState, Players); 
-                 if (plr.objnum == object_none) 
-                         return; 
-                 auto &plrobj = *Objects.vcptr(plr.objnum); 
-                 if (plrobj.type != OBJ_PLAYER) 
-                         return; 
-                 if (player_is_visible(player_visibility)) 
-                         if (BuddyState.Escort_last_path_created + F1_0*3 < GameTime64) { 
-                                 BuddyState.Escort_last_path_created = GameTime64; 
-                                 create_n_segment_path(objp, 10 + d_rand() * 16, plrobj.segnum); 
-                         } 
-   
-                 return; 
-         } 
-   
-         //      Force checking for new goal every 5 seconds, and create new path, if necessary. 
-         if (BuddyState.Escort_last_path_created + (BuddyState.Escort_special_goal != ESCORT_GOAL_SCRAM ? (F1_0 * 5) : (F1_0 * 15)) < GameTime64) 
-         { 
-                 BuddyState.Escort_goal_object = ESCORT_GOAL_UNSPECIFIED; 
-                 BuddyState.Escort_last_path_created = GameTime64; 
-         } 
-   
-         const player *guidebot_controller_player; 
-         if (BuddyState.Escort_special_goal != ESCORT_GOAL_SCRAM && (guidebot_controller_player = time_to_visit_player(LevelUniqueObjectState, objp))) 
-         { 
-                 unsigned max_len; 
-                 BuddyState.Buddy_last_player_path_created = GameTime64; 
-                 ailp->mode = ai_mode::AIM_GOTO_PLAYER; 
-                 if (!player_is_visible(player_visibility)) 
-                 { 
-                         if (BuddyState.Last_come_back_message_time + F1_0 < GameTime64) 
-                         { 
-                                 BuddyState.Last_come_back_message_time = GameTime64; 
-                                 auto &local_player = *Players.vcptr(Player_num); 
-                                 if (guidebot_controller_player == &local_player) 
-                                         buddy_message_str("Coming back to get you."); 
-                                 else 
-                                         buddy_message("Going back to get %s.", guidebot_controller_player->callsign.operator const char *()); 
-                         } 
-                 } 
-                 //      No point in Buddy creating very long path if he's not allowed to talk.  Really kills framerate. 
-                 max_len = Max_escort_length; 
-                 if (!BuddyState.Buddy_allowed_to_talk) 
-                         max_len = 3; 
-                 create_path_to_segment(objp, max_len, create_path_safety_flag::safe, Believed_player_seg);      //      MK!: Last parm used to be 1! 
-                 aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length); 
-                 ailp->mode = ai_mode::AIM_GOTO_PLAYER; 
-         } 
-         else if (GameTime64 - BuddyState.Buddy_last_seen_player > MAX_ESCORT_TIME_AWAY) 
-         { 
-                 //      This is to prevent buddy from looking for a goal, which he will do because we only allow path creation once/second. 
-                 return; 
-         } else if ((ailp->mode == ai_mode::AIM_GOTO_PLAYER) && (dist_to_player < MIN_ESCORT_DISTANCE)) { 
-                 BuddyState.Escort_goal_object = escort_set_goal_object(player_info.powerup_flags); 
-                 ailp->mode = ai_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 
-                 escort_create_path_to_goal(objp, player_info); 
-                 escort_set_goal_toward_controlling_player(BuddyState, vcobjptr, objp); 
-         } 
-         else if (BuddyState.Escort_goal_object == ESCORT_GOAL_UNSPECIFIED) 
-         { 
-                 if ((ailp->mode != ai_mode::AIM_GOTO_PLAYER) || (dist_to_player < MIN_ESCORT_DISTANCE)) { 
-                         BuddyState.Escort_goal_object = escort_set_goal_object(player_info.powerup_flags); 
-                         ailp->mode = ai_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 
-                         escort_create_path_to_goal(objp, player_info); 
-                         escort_set_goal_toward_controlling_player(BuddyState, vcobjptr, objp); 
-                 } 
-         } 
- } 
-   
- void invalidate_escort_goal(d_unique_buddy_state &BuddyState) 
- { 
-         BuddyState.Escort_goal_object = ESCORT_GOAL_UNSPECIFIED; 
- } 
-   
- //      ------------------------------------------------------------------------------------------------- 
- void do_snipe_frame(const vmobjptridx_t objp, const fix dist_to_player, const player_visibility_state player_visibility, const vms_vector &vec_to_player) 
- { 
-         ai_local                *ailp = &objp->ctype.ai_info.ail; 
-         fix                     connected_distance; 
-   
-         if (dist_to_player > F1_0*500) 
-                 return; 
-   
-         switch (ailp->mode) { 
-                 case ai_mode::AIM_SNIPE_WAIT: 
-                         if ((dist_to_player > F1_0*50) && (ailp->next_action_time > 0)) 
-                                 return; 
-   
-                         ailp->next_action_time = SNIPE_WAIT_TIME; 
-   
-                         connected_distance = find_connected_distance(objp->pos, vmsegptridx(objp->segnum), Believed_player_pos, vmsegptridx(Believed_player_seg), 30, WID_FLY_FLAG); 
-                         if (connected_distance < F1_0*500) { 
-                                 create_path_to_believed_player_segment(objp, 30, create_path_safety_flag::safe); 
-                                 ailp->mode = ai_mode::AIM_SNIPE_ATTACK; 
-                                 ailp->next_action_time = SNIPE_ATTACK_TIME;     //      have up to 10 seconds to find player. 
-                         } 
-                         break; 
-   
-                 case ai_mode::AIM_SNIPE_RETREAT: 
-                 case ai_mode::AIM_SNIPE_RETREAT_BACKWARDS: 
-                         if (ailp->next_action_time < 0) { 
-                                 ailp->mode = ai_mode::AIM_SNIPE_WAIT; 
-                                 ailp->next_action_time = SNIPE_WAIT_TIME; 
-                         } 
-                         else if (player_visibility == player_visibility_state::no_line_of_sight || ailp->next_action_time > SNIPE_ABORT_RETREAT_TIME) 
-                         { 
-                                 ai_follow_path(objp, player_visibility, &vec_to_player); 
-                                 ailp->mode = ai_mode::AIM_SNIPE_RETREAT_BACKWARDS; 
-                         } else { 
-                                 ailp->mode = ai_mode::AIM_SNIPE_FIRE; 
-                                 ailp->next_action_time = SNIPE_FIRE_TIME/2; 
-                         } 
-                         break; 
-   
-                 case ai_mode::AIM_SNIPE_ATTACK: 
-                         if (ailp->next_action_time < 0) { 
-                                 ailp->mode = ai_mode::AIM_SNIPE_RETREAT; 
-                                 ailp->next_action_time = SNIPE_WAIT_TIME; 
-                         } else { 
-                                 ai_follow_path(objp, player_visibility, &vec_to_player); 
-                                 if (player_is_visible(player_visibility)) 
-                                 { 
-                                         ailp->mode = ai_mode::AIM_SNIPE_FIRE; 
-                                         ailp->next_action_time = SNIPE_FIRE_TIME; 
-                                 } else 
-                                         ailp->mode = ai_mode::AIM_SNIPE_ATTACK; 
-                         } 
-                         break; 
-   
-                 case ai_mode::AIM_SNIPE_FIRE: 
-                         if (ailp->next_action_time < 0) { 
-                                 ai_static       *aip = &objp->ctype.ai_info; 
-                                 create_n_segment_path(objp, 10 + d_rand()/2048, ConsoleObject->segnum); 
-                                 aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length); 
-                                 if (d_rand() < 8192) 
-                                         ailp->mode = ai_mode::AIM_SNIPE_RETREAT_BACKWARDS; 
-                                 else 
-                                         ailp->mode = ai_mode::AIM_SNIPE_RETREAT; 
-                                 ailp->next_action_time = SNIPE_RETREAT_TIME; 
-                         } else { 
-                         } 
-                         break; 
-   
-                 default: 
-                         Int3(); //      Oops, illegal mode for snipe behavior. 
-                         ailp->mode = ai_mode::AIM_SNIPE_ATTACK; 
-                         ailp->next_action_time = F1_0; 
-                         break; 
-         } 
-   
- } 
-   
- #define THIEF_DEPTH     20 
-   
- //      ------------------------------------------------------------------------------------------------------ 
- //      Choose segment to recreate thief in. 
- static vmsegidx_t choose_thief_recreation_segment(const vcsegidx_t plrseg) 
- { 
-         segnum_t        segnum = segment_none; 
-         int     cur_drop_depth; 
-   
-         cur_drop_depth = THIEF_DEPTH; 
-   
-         while ((segnum == segment_none) && (cur_drop_depth > THIEF_DEPTH/2)) { 
-                 segnum = pick_connected_segment(plrseg, cur_drop_depth); 
-                 if (segnum != segment_none && vcsegptr(segnum)->special == SEGMENT_IS_CONTROLCEN) 
-                         segnum = segment_none; 
-                 cur_drop_depth--; 
-         } 
-   
-         if (segnum == segment_none) { 
-                 return (d_rand() * Highest_segment_index) >> 15; 
-         } else 
-                 return segnum; 
-   
- } 
-   
- static fix64    Re_init_thief_time = 0x3f000000; 
-   
- //      ---------------------------------------------------------------------- 
- void recreate_thief(const uint8_t thief_id) 
- { 
-         auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); 
-         auto &Vertices = LevelSharedVertexState.get_vertices(); 
-         const auto segnum = choose_thief_recreation_segment(ConsoleObject->segnum); 
-         const auto &&segp = vmsegptridx(segnum); 
-         auto &vcvertptr = Vertices.vcptr; 
-         const auto &¢er_point = compute_segment_center(vcvertptr, segp); 
-   
-         const auto &&new_obj = create_morph_robot(segp, center_point, thief_id); 
-         if (new_obj == object_none) 
-                 return; 
-         Re_init_thief_time = GameTime64 + F1_0*10;              //      In 10 seconds, re-initialize thief. 
- } 
-   
- //      ---------------------------------------------------------------------------- 
- #define THIEF_ATTACK_TIME               (F1_0*10) 
-   
- constexpr std::array<fix, NDL> Thief_wait_times = {{ 
-         F1_0*30, F1_0*25, F1_0*20, F1_0*15, F1_0*10 
- }}; 
-   
- //      ------------------------------------------------------------------------------------------------- 
- void do_thief_frame(const vmobjptridx_t objp, const fix dist_to_player, const player_visibility_state player_visibility, const vms_vector &vec_to_player) 
- { 
-         const auto Difficulty_level = GameUniqueState.Difficulty_level; 
-         auto &Robot_info = LevelSharedRobotInfoState.Robot_info; 
-         ai_local                *ailp = &objp->ctype.ai_info.ail; 
-         fix                     connected_distance; 
-   
-         if ((Current_level_num < 0) && (Re_init_thief_time < GameTime64)) { 
-                 if (Re_init_thief_time > GameTime64 - F1_0*2) 
-                         init_thief_for_level(); 
-                 Re_init_thief_time = 0x3f000000; 
-         } 
-   
-         if ((dist_to_player > F1_0*500) && (ailp->next_action_time > 0)) 
-                 return; 
-   
-         if (Player_dead_state != player_dead_state::no) 
-                 ailp->mode = ai_mode::AIM_THIEF_RETREAT; 
-   
-         switch (ailp->mode) { 
-                 case ai_mode::AIM_THIEF_WAIT: 
-                         if (ailp->player_awareness_type >= player_awareness_type_t::PA_PLAYER_COLLISION) { 
-                                 ailp->player_awareness_type = player_awareness_type_t::PA_NONE; 
-                                 create_path_to_believed_player_segment(objp, 30, create_path_safety_flag::safe); 
-                                 ailp->mode = ai_mode::AIM_THIEF_ATTACK; 
-                                 ailp->next_action_time = THIEF_ATTACK_TIME/2; 
-                                 return; 
-                         } 
-                         else if (player_is_visible(player_visibility)) 
-                         { 
-                                 create_n_segment_path(objp, 15, ConsoleObject->segnum); 
-                                 ailp->mode = ai_mode::AIM_THIEF_RETREAT; 
-                                 return; 
-                         } 
-   
-                         if ((dist_to_player > F1_0*50) && (ailp->next_action_time > 0)) 
-                                 return; 
-   
-                         ailp->next_action_time = Thief_wait_times[Difficulty_level]/2; 
-   
-                         connected_distance = find_connected_distance(objp->pos, vmsegptridx(objp->segnum), Believed_player_pos, vmsegptridx(Believed_player_seg), 30, WID_FLY_FLAG); 
-                         if (connected_distance < F1_0*500) { 
-                                 create_path_to_believed_player_segment(objp, 30, create_path_safety_flag::safe); 
-                                 ailp->mode = ai_mode::AIM_THIEF_ATTACK; 
-                                 ailp->next_action_time = THIEF_ATTACK_TIME;     //      have up to 10 seconds to find player. 
-                         } 
-   
-                         break; 
-   
-                 case ai_mode::AIM_THIEF_RETREAT: 
-                         if (ailp->next_action_time < 0) { 
-                                 ailp->mode = ai_mode::AIM_THIEF_WAIT; 
-                                 ailp->next_action_time = Thief_wait_times[Difficulty_level]; 
-                         } 
-                         else if (dist_to_player < F1_0 * 100 || player_is_visible(player_visibility) || ailp->player_awareness_type >= player_awareness_type_t::PA_PLAYER_COLLISION) 
-                         { 
-                                 ai_follow_path(objp, player_visibility, &vec_to_player); 
-                                 if ((dist_to_player < F1_0*100) || (ailp->player_awareness_type >= player_awareness_type_t::PA_PLAYER_COLLISION)) { 
-                                         ai_static       *aip = &objp->ctype.ai_info; 
-                                         if (((aip->cur_path_index <=1) && (aip->PATH_DIR == -1)) || ((aip->cur_path_index >= aip->path_length-1) && (aip->PATH_DIR == 1))) { 
-                                                 ailp->player_awareness_type = player_awareness_type_t::PA_NONE; 
-                                                 create_n_segment_path(objp, 10, ConsoleObject->segnum); 
-   
-                                                 //      If path is real short, try again, allowing to go through player's segment 
-                                                 if (aip->path_length < 4) { 
-                                                         create_n_segment_path(objp, 10, segment_none); 
-                                                 } else if (objp->shields* 4 < Robot_info[get_robot_id(objp)].strength) { 
-                                                         //      If robot really low on hits, will run through player with even longer path 
-                                                         if (aip->path_length < 8) { 
-                                                                 create_n_segment_path(objp, 10, segment_none); 
-                                                         } 
-                                                 } 
-   
-                                                 ailp->mode = ai_mode::AIM_THIEF_RETREAT; 
-                                         } 
-                                 } else 
-                                         ailp->mode = ai_mode::AIM_THIEF_RETREAT; 
-   
-                         } 
-   
-                         break; 
-   
-                 //      This means the thief goes from wherever he is to the player. 
-                 //      Note: When thief successfully steals something, his action time is forced negative and his mode is changed 
-                 //                      to retreat to get him out of attack mode. 
-                 case ai_mode::AIM_THIEF_ATTACK: 
-                         if (ailp->player_awareness_type >= player_awareness_type_t::PA_PLAYER_COLLISION) { 
-                                 ailp->player_awareness_type = player_awareness_type_t::PA_NONE; 
-                                 if (d_rand() > 8192) { 
-                                         create_n_segment_path(objp, 10, ConsoleObject->segnum); 
-                                         ailp->next_action_time = Thief_wait_times[Difficulty_level]/2; 
-                                         ailp->mode = ai_mode::AIM_THIEF_RETREAT; 
-                                 } 
-                         } else if (ailp->next_action_time < 0) { 
-                                 //      This forces him to create a new path every second. 
-                                 ailp->next_action_time = F1_0; 
-                                 create_path_to_believed_player_segment(objp, 100, create_path_safety_flag::unsafe); 
-                                 ailp->mode = ai_mode::AIM_THIEF_ATTACK; 
-                         } else { 
-                                 if (player_is_visible(player_visibility) && dist_to_player < F1_0*100) 
-                                 { 
-                                         //      If the player is close to looking at the thief, thief shall run away. 
-                                         //      No more stupid thief trying to sneak up on you when you're looking right at him! 
-                                         if (dist_to_player > F1_0*60) { 
-                                                 fix     dot = vm_vec_dot(vec_to_player, ConsoleObject->orient.fvec); 
-                                                 if (dot < -F1_0/2) {    //      Looking at least towards thief, so thief will run! 
-                                                         create_n_segment_path(objp, 10, ConsoleObject->segnum); 
-                                                         ailp->next_action_time = Thief_wait_times[Difficulty_level]/2; 
-                                                         ailp->mode = ai_mode::AIM_THIEF_RETREAT; 
-                                                 } 
-                                         }  
-                                         ai_turn_towards_vector(vec_to_player, objp, F1_0/4); 
-                                         move_towards_player(objp, vec_to_player); 
-                                 } else { 
-                                         ai_static       *aip = &objp->ctype.ai_info; 
-                                         //      If path length == 0, then he will keep trying to create path, but he is probably stuck in his closet. 
-                                         if ((aip->path_length > 1) || ((d_tick_count & 0x0f) == 0)) { 
-                                                 ai_follow_path(objp, player_visibility, &vec_to_player); 
-                                                 ailp->mode = ai_mode::AIM_THIEF_ATTACK; 
-                                         } 
-                                 } 
-                         } 
-                         break; 
-   
-                 default: 
-                         ailp->mode = ai_mode::AIM_THIEF_ATTACK; 
-                         ailp->next_action_time = F1_0; 
-                         break; 
-         } 
-   
- } 
-   
- //      ---------------------------------------------------------------------------- 
- //      Return true if this item (whose presence is indicated by Players[player_num].flags) gets stolen. 
- static int maybe_steal_flag_item(const vmobjptr_t playerobjp, const PLAYER_FLAG flagval) 
- { 
-         auto &ThiefUniqueState = LevelUniqueObjectState.ThiefState; 
-         auto &plr_flags = playerobjp->ctype.player_info.powerup_flags; 
-         if (plr_flags & flagval) 
-         { 
-                 if (d_rand() < THIEF_PROBABILITY) { 
-                         int     powerup_index; 
-                         const char *msg; 
-                         plr_flags &= (~flagval); 
-                         switch (flagval) { 
-                                 case PLAYER_FLAGS_INVULNERABLE: 
-                                         powerup_index = POW_INVULNERABILITY; 
-                                         msg = "Invulnerability stolen!"; 
-                                         break; 
-                                 case PLAYER_FLAGS_CLOAKED: 
-                                         powerup_index = POW_CLOAK; 
-                                         msg = "Cloak stolen!"; 
-                                         break; 
-                                 case PLAYER_FLAGS_MAP_ALL: 
-                                         powerup_index = POW_FULL_MAP; 
-                                         msg = "Full map stolen!"; 
-                                         break; 
-                                 case PLAYER_FLAGS_QUAD_LASERS: 
-                                         update_laser_weapon_info(); 
-                                         powerup_index = POW_QUAD_FIRE; 
-                                         msg = "Quad lasers stolen!"; 
-                                         break; 
-                                 case PLAYER_FLAGS_AFTERBURNER: 
-                                         powerup_index = POW_AFTERBURNER; 
-                                         msg = "Afterburner stolen!"; 
-                                         break; 
-                                 case PLAYER_FLAGS_CONVERTER: 
-                                         powerup_index = POW_CONVERTER; 
-                                         msg = "Converter stolen!"; 
-                                         break; 
-                                 case PLAYER_FLAG::HEADLIGHT_PRESENT_AND_ON: 
-                                         powerup_index = POW_HEADLIGHT; 
-                                         msg = "Headlight stolen!"; 
-                                         break; 
-                                 default: 
-                                         assert(false); 
-                                         return 0; 
-                         } 
-                         ThiefUniqueState.Stolen_items[ThiefUniqueState.Stolen_item_index] = powerup_index; 
-                         thief_message_str(msg); 
-                         return 1; 
-                 } 
-         } 
-   
-         return 0; 
- } 
-   
- //      ---------------------------------------------------------------------------- 
- static int maybe_steal_secondary_weapon(const vmobjptr_t playerobjp, int weapon_num) 
- { 
-         auto &ThiefUniqueState = LevelUniqueObjectState.ThiefState; 
-         auto &player_info = playerobjp->ctype.player_info; 
-         if (auto &secondary_ammo = player_info.secondary_ammo[weapon_num]) 
-                 if (d_rand() < THIEF_PROBABILITY) { 
-                         if (weapon_index_is_player_bomb(weapon_num)) 
-                         { 
-                                 if (d_rand() > 8192)            //      Come in groups of 4, only add 1/4 of time. 
-                                         return 0; 
-                         } 
-                         //      Smart mines and proxbombs don't get dropped because they only come in 4 packs. 
-                         else 
-                                 ThiefUniqueState.Stolen_items[ThiefUniqueState.Stolen_item_index] = Secondary_weapon_to_powerup[weapon_num]; 
-                         thief_message("%s stolen!", SECONDARY_WEAPON_NAMES(weapon_num));                //      Danger! Danger! Use of literal!  Danger! 
-                         if (-- secondary_ammo == 0) 
-                                 auto_select_secondary_weapon(player_info); 
-   
-                         // -- compress_stolen_items(); 
-                         return 1; 
-                 } 
-   
-         return 0; 
- } 
-   
- //      ---------------------------------------------------------------------------- 
- static int maybe_steal_primary_weapon(const vmobjptr_t playerobjp, int weapon_num) 
- { 
-         auto &ThiefUniqueState = LevelUniqueObjectState.ThiefState; 
-         auto &player_info = playerobjp->ctype.player_info; 
-         bool is_energy_weapon = true; 
-         switch (static_cast<primary_weapon_index_t>(weapon_num)) 
-         { 
-                 case primary_weapon_index_t::LASER_INDEX: 
-                         if (!player_info.laser_level) 
-                                 return 0; 
-                         break; 
-                 case primary_weapon_index_t::VULCAN_INDEX: 
-                 case primary_weapon_index_t::GAUSS_INDEX: 
-                         if (!player_info.vulcan_ammo) 
-                                 return 0; 
-                         is_energy_weapon = false; 
-                         DXX_BOOST_FALLTHROUGH; 
-                 default: 
-                         if (!(player_info.primary_weapon_flags & HAS_PRIMARY_FLAG(weapon_num))) 
-                                 return 0; 
-                         break; 
-         } 
-         if (is_energy_weapon) 
-         { 
-                 if (((Game_mode & GM_MULTI) 
-                         ? Netgame.ThiefModifierFlags 
-                         : PlayerCfg.ThiefModifierFlags) & ThiefModifier::NoEnergyWeapons) 
-                         return 0; 
-         } 
-         { 
-                 if (d_rand() < THIEF_PROBABILITY) { 
-                         powerup_type_t primary_weapon_powerup; 
-                         if (weapon_num == primary_weapon_index_t::LASER_INDEX) 
-                         { 
-                                 auto &laser_level = player_info.laser_level; 
-                                 primary_weapon_powerup = (laser_level > MAX_LASER_LEVEL) 
-                                         ? POW_SUPER_LASER 
-                                         : Primary_weapon_to_powerup[weapon_num]; 
-                                         /* Laser levels are zero-based, so print the old 
-                                          * level, then decrement it.  Decrementing first 
-                                          * would produce confusing output, particularly when 
-                                          * the user loses the final laser powerup and drops 
-                                          * to level 0 lasers. 
-                                          */ 
-                                         const laser_level_t l = laser_level; 
-                                         -- laser_level; 
-                                         thief_message("%s level decreased to %u!", PRIMARY_WEAPON_NAMES(weapon_num), l); 
-                         } 
-                         else 
-                         { 
-                                 player_info.primary_weapon_flags &= ~HAS_PRIMARY_FLAG(weapon_num); 
-                                 primary_weapon_powerup = Primary_weapon_to_powerup[weapon_num]; 
-   
-                                 thief_message("%s stolen!", PRIMARY_WEAPON_NAMES(weapon_num));          //      Danger! Danger! Use of literal!  Danger! 
-                                 auto_select_primary_weapon(player_info); 
-                         } 
-                         ThiefUniqueState.Stolen_items[ThiefUniqueState.Stolen_item_index] = primary_weapon_powerup; 
-                         return 1; 
-                 } 
-         } 
-   
-         return 0; 
- } 
-   
-   
-   
- //      ---------------------------------------------------------------------------- 
- //      Called for a thief-type robot. 
- //      If a item successfully stolen, returns true, else returns false. 
- //      If a wapon successfully stolen, do everything, removing it from player, 
- //      updating Stolen_items information, deselecting, etc. 
- static int attempt_to_steal_item_3(const vmobjptr_t objp, const vmobjptr_t player_num) 
- { 
-         ai_local                *ailp = &objp->ctype.ai_info.ail; 
-         if (ailp->mode != ai_mode::AIM_THIEF_ATTACK) 
-                 return 0; 
-   
-         //      First, try to steal equipped items. 
-   
-         if (auto r = maybe_steal_flag_item(player_num, PLAYER_FLAGS_INVULNERABLE)) 
-                 return r; 
-   
-         //      If primary weapon = laser, first try to rip away those nasty quad lasers! 
-         const auto Primary_weapon = player_num->ctype.player_info.Primary_weapon; 
-         if (Primary_weapon == primary_weapon_index_t::LASER_INDEX) 
-                 if (auto r = maybe_steal_flag_item(player_num, PLAYER_FLAGS_QUAD_LASERS)) 
-                         return r; 
-   
-         //      Makes it more likely to steal primary than secondary. 
-         range_for (const int i, xrange(2u)) 
-         { 
-                 (void)i; 
-                 if (auto r = maybe_steal_primary_weapon(player_num, Primary_weapon)) 
-                         return r; 
-         } 
-   
-         if (auto r = maybe_steal_secondary_weapon(player_num, player_num->ctype.player_info.Secondary_weapon)) 
-                 return r; 
-   
-         //      See what the player has and try to snag something. 
-         //      Try best things first. 
-         if (auto r = maybe_steal_flag_item(player_num, PLAYER_FLAGS_INVULNERABLE)) 
-                 return r; 
-         if (auto r = maybe_steal_flag_item(player_num, PLAYER_FLAGS_CLOAKED)) 
-                 return r; 
-         if (auto r = maybe_steal_flag_item(player_num, PLAYER_FLAGS_QUAD_LASERS)) 
-                 return r; 
-         if (auto r = maybe_steal_flag_item(player_num, PLAYER_FLAGS_AFTERBURNER)) 
-                 return r; 
-         if (auto r = maybe_steal_flag_item(player_num, PLAYER_FLAGS_CONVERTER)) 
-                 return r; 
- // --   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? 
- // --           return 1; 
-         if (auto r = maybe_steal_flag_item(player_num, PLAYER_FLAG::HEADLIGHT_PRESENT_AND_ON)) 
-                 return r; 
-         if (auto r = maybe_steal_flag_item(player_num, PLAYER_FLAGS_MAP_ALL)) 
-                 return r; 
-   
-         for (int i=MAX_SECONDARY_WEAPONS-1; i>=0; i--) { 
-                 if (auto r = maybe_steal_primary_weapon(player_num, i)) 
-                         return r; 
-                 if (auto r = maybe_steal_secondary_weapon(player_num, i)) 
-                         return r; 
-         } 
-   
-         return 0; 
- } 
-   
- //      ---------------------------------------------------------------------------- 
- static int attempt_to_steal_item_2(const vmobjptr_t objp, const vmobjptr_t player_num) 
- { 
-         auto &ThiefUniqueState = LevelUniqueObjectState.ThiefState; 
-         const auto rval = attempt_to_steal_item_3(objp, player_num); 
-         if (rval) { 
-                 digi_play_sample_once(SOUND_WEAPON_STOLEN, F1_0); 
-                 auto i = ThiefUniqueState.Stolen_item_index; 
-                 if (d_rand() > 20000)   //      Occasionally, boost the value again 
-                         ++i; 
-                 constexpr auto size = std::tuple_size<decltype(ThiefUniqueState.Stolen_items)>::value; 
-                 if (++ i >= size) 
-                         i -= size; 
-                 ThiefUniqueState.Stolen_item_index = i; 
-         } 
-         return rval; 
- } 
-   
- //      ---------------------------------------------------------------------------- 
- //      Called for a thief-type robot. 
- //      If a item successfully stolen, returns true, else returns false. 
- //      If a wapon successfully stolen, do everything, removing it from player, 
- //      updating Stolen_items information, deselecting, etc. 
- int attempt_to_steal_item(const vmobjptridx_t objp, const vmobjptr_t player_num) 
- { 
-         int     rval = 0; 
-   
-         if (objp->ctype.ai_info.dying_start_time) 
-                 return 0; 
-   
-         rval += attempt_to_steal_item_2(objp, player_num); 
-   
-         range_for (const int i, xrange(3u)) { 
-                 (void)i; 
-                 if (!rval || (d_rand() < 11000)) {      //      about 1/3 of time, steal another item 
-                         rval += attempt_to_steal_item_2(objp, player_num); 
-                 } else 
-                         break; 
-         } 
-         create_n_segment_path(objp, 10, ConsoleObject->segnum); 
-         ai_local                *ailp = &objp->ctype.ai_info.ail; 
-         ailp->next_action_time = Thief_wait_times[GameUniqueState.Difficulty_level] / 2; 
-         ailp->mode = ai_mode::AIM_THIEF_RETREAT; 
-         if (rval) { 
-                 PALETTE_FLASH_ADD(30, 15, -20); 
-                 if (Game_mode & GM_NETWORK) 
-                  multi_send_stolen_items(); 
-         } 
-         return rval; 
- } 
-   
- // -------------------------------------------------------------------------------------------------------------- 
- //      Indicate no items have been stolen. 
- void init_thief_for_level(void) 
- { 
-         auto &ThiefUniqueState = LevelUniqueObjectState.ThiefState; 
-         ThiefUniqueState.Stolen_item_index = 0; 
-         auto &Stolen_items = ThiefUniqueState.Stolen_items; 
-         Stolen_items.fill(255); 
-   
-         constexpr unsigned iterations = 3; 
-         static_assert (std::tuple_size<decltype(ThiefUniqueState.Stolen_items)>::value >= iterations * 2, "Stolen_items too small");    //      Oops!  Loop below will overwrite memory! 
-    if (!(Game_mode & GM_MULTI))     
-                 for (unsigned i = 0; i < iterations; i++) 
-                 { 
-                         Stolen_items[2*i] = POW_SHIELD_BOOST; 
-                         Stolen_items[2*i+1] = POW_ENERGY; 
-                 } 
- } 
-   
- // -------------------------------------------------------------------------------------------------------------- 
- void drop_stolen_items(const vcobjptr_t objp) 
- { 
-         auto &ThiefUniqueState = LevelUniqueObjectState.ThiefState; 
-         const auto &&segp = vmsegptridx(objp->segnum); 
-         range_for (auto &i, ThiefUniqueState.Stolen_items) 
-         { 
-                 if (i != 255) 
-                 { 
-                         drop_powerup(Vclip, std::exchange(i, 255), 1, objp->mtype.phys_info.velocity, objp->pos, segp, true); 
-                 } 
-         } 
-   
- } 
-   
- // -------------------------------------------------------------------------------------------------------------- 
- namespace { 
-   
- struct escort_menu : ignore_window_pointer_t 
- { 
-         std::array<char, 300> msg; 
-         static window_event_result event_handler(window *wind, const d_event &event, escort_menu *menu); 
-         static window_event_result event_key_command(const d_event &event); 
- }; 
-   
- } 
-   
- window_event_result escort_menu::event_key_command(const d_event &event) 
- { 
-         auto &BuddyState = LevelUniqueObjectState.BuddyState; 
-         switch (const auto key = event_key_get(event)) 
-         { 
-                 case KEY_0: 
-                 case KEY_1: 
-                 case KEY_2: 
-                 case KEY_3: 
-                 case KEY_4: 
-                 case KEY_5: 
-                 case KEY_6: 
-                 case KEY_7: 
-                 case KEY_8: 
-                 case KEY_9: 
-                         BuddyState.Looking_for_marker = game_marker_index::None; 
-                         BuddyState.Last_buddy_key = -1; 
-                         set_escort_special_goal(BuddyState, key); 
-                         BuddyState.Last_buddy_key = -1; 
-                         return window_event_result::close; 
-                 case KEY_ESC: 
-                 case KEY_ENTER: 
-                         return window_event_result::close; 
-                 case KEY_T: { 
-                         const auto temp = std::exchange(BuddyState.Buddy_messages_suppressed, 0); 
-                         buddy_message("Messages %s.", temp ? "enabled" : "suppressed"); 
-                         BuddyState.Buddy_messages_suppressed = ~temp; 
-                         return window_event_result::close; 
-                 } 
-                          
-                 default: 
-                         break; 
-         } 
-         return window_event_result::ignored; 
- } 
-   
- window_event_result escort_menu::event_handler(window *, const d_event &event, escort_menu *menu) 
- { 
-         switch (event.type) 
-         { 
-                 case EVENT_WINDOW_ACTIVATED: 
-                         game_flush_inputs(); 
-                         break; 
-                          
-                 case EVENT_KEY_COMMAND: 
-                         return event_key_command(event); 
-                 case EVENT_IDLE: 
-                         timer_delay2(50); 
-                         break; 
-                          
-                 case EVENT_WINDOW_DRAW: 
-                         show_escort_menu(menu->msg);            //TXT_PAUSE); 
-                         break; 
-                          
-                 case EVENT_WINDOW_CLOSE: 
-                         d_free(menu); 
-                         return window_event_result::ignored;    // continue closing 
-                 default: 
-                         return window_event_result::ignored; 
-         } 
-         return window_event_result::handled; 
- } 
-   
- unsigned check_warn_local_player_can_control_guidebot(fvcobjptr &vcobjptr, const d_unique_buddy_state &BuddyState, const netgame_info &Netgame) 
- { 
-         if (!Netgame.AllowGuidebot || !(Game_mode & GM_MULTI_COOP)) 
-         { 
-                 HUD_init_message_literal(HM_DEFAULT, "Guide-Bot is not enabled!"); 
-                 return 0; 
-         } 
-         auto &plr = get_player_controlling_guidebot(BuddyState, Players); 
-         if (plr.objnum == object_none) 
-                 return 0; 
-         auto &plrobj = *vcobjptr(plr.objnum); 
-         if (ConsoleObject != &plrobj) 
-         { 
-                 HUD_init_message(HM_DEFAULT, "Guide-Bot is controlled by %s!", plr.callsign.operator const char *()); 
-                 return 0; 
-         } 
-         return 1; 
- } 
-   
- void do_escort_menu(void) 
- { 
-         auto &BuddyState = LevelUniqueObjectState.BuddyState; 
-         auto &Objects = LevelUniqueObjectState.Objects; 
-         auto &vcobjptr = Objects.vcptr; 
-         auto &vmobjptr = Objects.vmptr; 
-         auto &vmobjptridx = Objects.vmptridx; 
-         int     next_goal; 
-         char    goal_str[12]; 
-         const char *goal_txt; 
-         const char *tstr; 
-         escort_menu *menu; 
-   
-         if (Game_mode & GM_MULTI) { 
-                 if (!check_warn_local_player_can_control_guidebot(vcobjptr, BuddyState, Netgame)) 
-                         return; 
-         } 
-   
-         auto &Robot_info = LevelSharedRobotInfoState.Robot_info; 
-         const auto &&buddy = find_escort(vmobjptridx, Robot_info); 
-         if (buddy == object_none) 
-         { 
-                 HUD_init_message_literal(HM_DEFAULT, "No Guide-Bot present in mine!"); 
-                 return; 
-         } 
-         //      Needed here or we might not know buddy can talk when he can. 
-         if (!ok_for_buddy_to_talk()) { 
-                 HUD_init_message(HM_DEFAULT, "%s has not been released.", static_cast<const char *>(PlayerCfg.GuidebotName)); 
-                 return; 
-         } 
-   
-         MALLOC(menu, escort_menu, 1); 
-         if (!menu) 
-                 return; 
-          
-         // Just make it the full screen size and let show_escort_menu figure it out 
-         const auto wind = window_create(grd_curscreen->sc_canvas, 0, 0, SWIDTH, SHEIGHT, &escort_menu::event_handler, menu); 
-         if (!wind) 
-         { 
-                 d_free(menu); 
-                 return; 
-         } 
-          
-         auto &plrobj = get_local_plrobj(); 
-         //      This prevents the buddy from coming back if you've told him to scram. 
-         //      If we don't set next_goal, we get garbage there. 
-         if (BuddyState.Escort_special_goal == ESCORT_GOAL_SCRAM) { 
-                 BuddyState.Escort_special_goal = ESCORT_GOAL_UNSPECIFIED;       //      Else setting next goal might fail. 
-                 next_goal = escort_set_goal_object(plrobj.ctype.player_info.powerup_flags); 
-                 BuddyState.Escort_special_goal = ESCORT_GOAL_SCRAM; 
-         } else { 
-                 BuddyState.Escort_special_goal = ESCORT_GOAL_UNSPECIFIED;       //      Else setting next goal might fail. 
-                 next_goal = escort_set_goal_object(plrobj.ctype.player_info.powerup_flags); 
-         } 
-   
-         switch (next_goal) { 
-                 default: 
-                 case ESCORT_GOAL_UNSPECIFIED: 
-                         Int3(); 
-                         goal_txt = "ERROR"; 
-                         break; 
-                 case ESCORT_GOAL_BLUE_KEY: 
-                         goal_txt = "blue key"; 
-                         break; 
-                 case ESCORT_GOAL_GOLD_KEY: 
-                         goal_txt = "yellow key"; 
-                         break; 
-                 case ESCORT_GOAL_RED_KEY: 
-                         goal_txt = "red key"; 
-                         break; 
-                 case ESCORT_GOAL_CONTROLCEN: 
-                         goal_txt = "reactor"; 
-                         break; 
-                 case ESCORT_GOAL_BOSS: 
-                         goal_txt = "boss"; 
-                         break; 
-                 case ESCORT_GOAL_EXIT: 
-                         goal_txt = "exit"; 
-                         break; 
-                 case ESCORT_GOAL_MARKER1: 
-                 case ESCORT_GOAL_MARKER2: 
-                 case ESCORT_GOAL_MARKER3: 
-                 case ESCORT_GOAL_MARKER4: 
-                 case ESCORT_GOAL_MARKER5: 
-                 case ESCORT_GOAL_MARKER6: 
-                 case ESCORT_GOAL_MARKER7: 
-                 case ESCORT_GOAL_MARKER8: 
-                 case ESCORT_GOAL_MARKER9: 
-                         goal_txt = goal_str; 
-                         snprintf(goal_str, sizeof(goal_str), "marker %i", next_goal-ESCORT_GOAL_MARKER1+1); 
-                         break; 
-   
-         } 
-                          
-         if (!BuddyState.Buddy_messages_suppressed) 
-                 tstr =  "Suppress"; 
-         else 
-                 tstr =  "Enable"; 
-   
-         snprintf(menu->msg.data(), menu->msg.size(), "Select Guide-Bot Command:\n\n\n" 
-                                                 "0.  Next Goal: %s" CC_LSPACING_S "3\n\n" 
-                                                 "\x84.  Find Energy Powerup" CC_LSPACING_S "3\n\n" 
-                                                 "2.  Find Energy Center" CC_LSPACING_S "3\n\n" 
-                                                 "3.  Find Shield Powerup" CC_LSPACING_S "3\n\n" 
-                                                 "4.  Find Any Powerup" CC_LSPACING_S "3\n\n" 
-                                                 "5.  Find a Robot" CC_LSPACING_S "3\n\n" 
-                                                 "6.  Find a Hostage" CC_LSPACING_S "3\n\n" 
-                                                 "7.  Stay Away From Me" CC_LSPACING_S "3\n\n" 
-                                                 "8.  Find My Powerups" CC_LSPACING_S "3\n\n" 
-                                                 "9.  Find the exit\n\n" 
-                                                 "T.  %s Messages" 
-                                                 // -- "9.       Find the exit" CC_LSPACING_S "3\n" 
-                                 , goal_txt, tstr); 
- } 
-   
- //      ------------------------------------------------------------------------------- 
- //      Show the Buddy menu! 
- void show_escort_menu(const std::array<char, 300> &amsg) 
- {        
-         const auto msg = amsg.data(); 
-         int     w,h; 
-         int     x,y; 
-   
-   
-         gr_set_default_canvas(); 
-   
-         auto &canvas = *grd_curcanv; 
-         const auto &game_font = *GAME_FONT; 
-         gr_get_string_size(game_font, msg, &w, &h, nullptr); 
-   
-         x = (SWIDTH-w)/2; 
-         y = (SHEIGHT-h)/2; 
-   
-         gr_set_fontcolor(canvas, BM_XRGB(0, 28, 0), -1); 
-   
-         nm_draw_background(canvas, x - BORDERX, y - BORDERY, x + w + BORDERX, y + h + BORDERY); 
-   
-         gr_ustring(canvas, game_font, x, y, msg); 
-   
-         reset_cockpit(); 
- } 
-   
- } 
-