2006-03-20 17:12:09 +00:00
/*
2014-06-01 17:55:23 +00:00
* 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 .
2006-03-20 17:12:09 +00:00
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()
2010-01-09 01:56:38 +00:00
# include "window.h"
2008-04-06 20:23:28 +00:00
# include "console.h"
2006-03-20 17:12:09 +00:00
# include "vecmat.h"
# include "gr.h"
2013-12-26 04:18:28 +00:00
# include "gameseg.h"
2006-03-20 17:12:09 +00:00
# include "3d.h"
# include "palette.h"
2010-03-26 14:05:40 +00:00
# include "timer.h"
2013-12-26 04:18:28 +00:00
# include "u_mem.h"
2006-03-20 17:12:09 +00:00
# include "object.h"
2012-07-07 18:35:06 +00:00
# include "dxxerror.h"
2006-03-20 17:12:09 +00:00
# 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"
2015-04-19 04:18:51 +00:00
# include "hudmsg.h"
2006-03-20 17:12:09 +00:00
# include "cntrlcen.h"
# include "gauges.h"
2015-10-09 02:46:10 +00:00
# include "event.h"
2006-03-20 17:12:09 +00:00
# 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"
2014-07-29 03:21:53 +00:00
# include "segiter.h"
2015-01-12 00:26:03 +00:00
# include "compiler-exchange.h"
2014-07-29 03:21:53 +00:00
# include "compiler-range_for.h"
2014-10-21 03:15:12 +00:00
# include "partial_range.h"
2014-07-29 03:21:53 +00:00
2016-09-11 18:49:16 +00:00
# if DXX_USE_EDITOR
2006-03-20 17:12:09 +00:00
# include "editor/editor.h"
# endif
2015-12-22 04:18:50 +00:00
namespace dsx {
2015-04-19 04:18:49 +00:00
static void show_escort_menu ( const array < char , 300 > & ) ;
2015-06-13 22:42:15 +00:00
static void say_escort_goal ( escort_goal_t goal_num ) ;
2016-07-14 01:59:02 +00:00
static fix64 Last_come_back_message_time ;
static fix64 Buddy_last_missile_time ;
2006-03-20 17:12:09 +00:00
2016-07-16 16:52:04 +00:00
constexpr array < char [ 12 ] , ESCORT_GOAL_MARKER9 > Escort_goal_text = { {
2006-03-20 17:12:09 +00:00
" 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 "
2015-06-13 22:42:15 +00:00
} } ;
2006-03-20 17:12:09 +00:00
2018-06-20 04:16:06 +00:00
constexpr std : : integral_constant < unsigned , 200 > Max_escort_length { } ;
2013-12-12 23:48:34 +00:00
stolen_items_t Stolen_items ;
2006-03-20 17:12:09 +00:00
int Stolen_item_index ;
2010-12-22 00:17:59 +00:00
fix64 Escort_last_path_created = 0 ;
2015-01-12 00:26:03 +00:00
static int Buddy_messages_suppressed ;
2015-06-13 22:42:15 +00:00
escort_goal_t Escort_goal_object = ESCORT_GOAL_UNSPECIFIED ;
escort_goal_t Escort_special_goal = ESCORT_GOAL_UNSPECIFIED ;
2010-12-22 00:17:59 +00:00
fix64 Buddy_sorry_time ;
2013-12-29 04:28:07 +00:00
objnum_t Escort_goal_index , Buddy_objnum ;
int Buddy_allowed_to_talk ;
2015-01-12 00:26:04 +00:00
static int Looking_for_marker ;
static int Last_buddy_key ;
2006-03-20 17:12:09 +00:00
2015-01-12 00:26:04 +00:00
static fix64 Last_buddy_message_time ;
2006-03-20 17:12:09 +00:00
void init_buddy_for_level ( void )
{
Buddy_allowed_to_talk = 0 ;
Escort_goal_object = ESCORT_GOAL_UNSPECIFIED ;
2015-06-13 22:42:15 +00:00
Escort_special_goal = ESCORT_GOAL_UNSPECIFIED ;
2013-12-29 04:28:07 +00:00
Escort_goal_index = object_none ;
2006-03-20 17:12:09 +00:00
Buddy_messages_suppressed = 0 ;
2018-09-19 02:13:29 +00:00
Buddy_objnum = find_escort ( vmobjptridx , Robot_info ) ;
2006-03-20 17:12:09 +00:00
Buddy_sorry_time = - F1_0 ;
Looking_for_marker = - 1 ;
Last_buddy_key = - 1 ;
}
// -----------------------------------------------------------------------------
// See if segment from curseg through sidenum is reachable.
// Return true if it is reachable, else return false.
2018-12-13 02:31:38 +00:00
static int segment_is_reachable ( const vmobjptr_t robot , const shared_segment & segp , int sidenum , const player_flags powerup_flags )
2006-03-20 17:12:09 +00:00
{
2018-12-13 02:31:38 +00:00
const auto wall_num = segp . sides [ sidenum ] . wall_num ;
2006-03-20 17:12:09 +00:00
// If no wall, then it is reachable
2014-09-21 22:11:51 +00:00
if ( wall_num = = wall_none )
2006-03-20 17:12:09 +00:00
return 1 ;
2018-03-27 03:49:34 +00:00
return ai_door_is_openable ( robot , powerup_flags , segp , sidenum ) ;
2006-03-20 17:12:09 +00:00
// -- 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
2017-06-10 03:31:02 +00:00
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 )
2006-03-20 17:12:09 +00:00
{
2015-01-28 03:42:52 +00:00
std : : size_t head = 0 , tail = 0 ;
2013-12-18 23:40:02 +00:00
visited_segment_bitarray_t visited ;
2006-03-20 17:12:09 +00:00
bfs_list [ head + + ] = start_seg ;
2013-12-18 23:40:02 +00:00
visited [ start_seg ] = true ;
2006-03-20 17:12:09 +00:00
while ( ( head ! = tail ) & & ( head < max_segs ) ) {
2014-11-20 03:00:36 +00:00
auto curseg = bfs_list [ tail + + ] ;
2016-01-09 16:38:13 +00:00
const auto & & cursegp = vcsegptr ( curseg ) ;
2014-09-26 02:42:16 +00:00
for ( int i = 0 ; i < MAX_SIDES_PER_SEGMENT ; i + + ) {
2014-11-20 03:00:36 +00:00
auto connected_seg = cursegp - > children [ i ] ;
2006-03-20 17:12:09 +00:00
2013-12-18 23:40:02 +00:00
if ( IS_CHILD ( connected_seg ) & & ( ! visited [ connected_seg ] ) ) {
2017-02-19 19:33:45 +00:00
if ( segment_is_reachable ( robot , cursegp , i , powerup_flags ) ) {
2006-03-20 17:12:09 +00:00
bfs_list [ head + + ] = connected_seg ;
if ( head > = max_segs )
break ;
2013-12-18 23:40:02 +00:00
visited [ connected_seg ] = true ;
2006-03-20 17:12:09 +00:00
Assert ( head < MAX_SEGMENTS ) ;
}
}
}
}
2015-01-28 03:42:52 +00:00
return head ;
2006-03-20 17:12:09 +00:00
}
// -----------------------------------------------------------------------------
// 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.
2013-10-27 22:00:14 +00:00
static int ok_for_buddy_to_talk ( void )
2006-03-20 17:12:09 +00:00
{
2013-12-26 22:21:16 +00:00
if ( Buddy_objnum = = object_none )
2011-09-26 16:58:12 +00:00
return 0 ;
2017-06-10 03:31:02 +00:00
const vmobjptridx_t buddy = vmobjptridx ( Buddy_objnum ) ;
2014-08-16 23:18:17 +00:00
if ( buddy - > type ! = OBJ_ROBOT ) {
2006-03-20 17:12:09 +00:00
Buddy_allowed_to_talk = 0 ;
return 0 ;
}
if ( Buddy_allowed_to_talk )
2017-02-26 00:00:02 +00:00
return Buddy_allowed_to_talk ;
2006-03-20 17:12:09 +00:00
2018-06-24 05:06:15 +00:00
const shared_segment & segp = vcsegptr ( buddy - > segnum ) ;
2006-03-20 17:12:09 +00:00
2014-09-26 02:42:16 +00:00
for ( int i = 0 ; i < MAX_SIDES_PER_SEGMENT ; i + + ) {
2018-06-24 05:06:15 +00:00
const auto wall_num = segp . sides [ i ] . wall_num ;
2006-03-20 17:12:09 +00:00
2014-09-21 22:11:51 +00:00
if ( wall_num ! = wall_none ) {
2016-02-12 04:02:28 +00:00
auto & w = * vcwallptr ( wall_num ) ;
if ( w . type = = WALL_BLASTABLE & & ! ( w . flags & WALL_BLASTED ) )
2006-03-20 17:12:09 +00:00
return 0 ;
}
// Check one level deeper.
2018-06-24 05:06:15 +00:00
const auto child = segp . children [ i ] ;
if ( IS_CHILD ( child ) )
{
2018-12-13 02:31:38 +00:00
const shared_segment & cseg = * vcsegptr ( child ) ;
2018-06-24 05:06:15 +00:00
range_for ( const auto & j , cseg . sides )
2015-01-12 00:26:04 +00:00
{
auto wall2 = j . wall_num ;
2014-10-04 15:04:44 +00:00
if ( wall2 ! = wall_none ) {
2016-02-12 04:02:28 +00:00
auto & w = * vcwallptr ( wall2 ) ;
if ( w . type = = WALL_BLASTABLE & & ! ( w . flags & WALL_BLASTED ) )
2006-03-20 17:12:09 +00:00
return 0 ;
}
}
}
}
Buddy_allowed_to_talk = 1 ;
return 1 ;
}
2012-12-09 05:07:45 +00:00
static void record_escort_goal_accomplished ( )
{
if ( ok_for_buddy_to_talk ( ) ) {
digi_play_sample_once ( SOUND_BUDDY_MET_GOAL , F1_0 ) ;
2013-12-29 04:28:07 +00:00
Escort_goal_index = object_none ;
2012-12-09 05:07:45 +00:00
Escort_goal_object = ESCORT_GOAL_UNSPECIFIED ;
2015-06-13 22:42:15 +00:00
Escort_special_goal = ESCORT_GOAL_UNSPECIFIED ;
2012-12-09 05:07:45 +00:00
Looking_for_marker = - 1 ;
}
}
2006-03-20 17:12:09 +00:00
// --------------------------------------------------------------------------------------------
2012-12-08 21:28:59 +00:00
void detect_escort_goal_fuelcen_accomplished ( )
{
if ( ! Buddy_allowed_to_talk )
return ;
if ( Escort_special_goal = = ESCORT_GOAL_ENERGYCEN )
record_escort_goal_accomplished ( ) ;
}
2017-06-10 03:31:02 +00:00
void detect_escort_goal_accomplished ( const vmobjptridx_t index )
2006-03-20 17:12:09 +00:00
{
if ( ! Buddy_allowed_to_talk )
return ;
2016-11-29 18:30:16 +00:00
// 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 ( Escort_goal_index = = object_none )
2006-03-20 17:12:09 +00:00
return ;
2016-11-29 18:30:16 +00:00
// 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.
2015-06-13 22:42:15 +00:00
if ( Escort_special_goal = = ESCORT_GOAL_UNSPECIFIED & & Escort_goal_index = = index )
{
2016-11-29 18:30:16 +00:00
record_escort_goal_accomplished ( ) ;
return ;
}
2006-03-20 17:12:09 +00:00
2013-12-26 23:12:54 +00:00
if ( index - > type = = OBJ_POWERUP ) {
2015-11-27 03:56:13 +00:00
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 ( Escort_goal_object = = goal_key )
{
2012-12-09 05:07:45 +00:00
record_escort_goal_accomplished ( ) ;
return ;
2006-03-20 17:12:09 +00:00
}
}
}
2015-06-13 22:42:15 +00:00
if ( Escort_special_goal ! = ESCORT_GOAL_UNSPECIFIED )
2006-03-20 17:12:09 +00:00
{
2016-11-29 18:30:16 +00:00
if ( ( index - > type = = OBJ_POWERUP ) & & ( Escort_special_goal = = ESCORT_GOAL_POWERUP ) )
2012-12-09 05:07:45 +00:00
record_escort_goal_accomplished ( ) ; // Any type of powerup picked up will do.
2017-01-29 18:54:52 +00:00
else if ( Escort_goal_index ! = object_guidebot_cannot_reach )
2015-12-03 03:26:49 +00:00
{
const auto & & egi_objp = vcobjptr ( Escort_goal_index ) ;
if ( index - > type = = egi_objp - > type & & index - > id = = egi_objp - > id )
2016-11-29 18:30:16 +00:00
// 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 ( ) ;
2006-03-20 17:12:09 +00:00
}
}
}
2018-05-12 18:24:19 +00:00
# define DXX_GUIDEBOT_RENAME_MENU(VERB) \
DXX_MENUITEM ( VERB , INPUT , text , opt_name ) \
2006-03-20 17:12:09 +00:00
void change_guidebot_name ( )
{
2015-01-12 00:26:02 +00:00
auto text = PlayerCfg . GuidebotName ;
2018-05-12 18:24:19 +00:00
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 ) ;
2006-03-20 17:12:09 +00:00
if ( item ! = - 1 ) {
2015-01-12 00:26:02 +00:00
PlayerCfg . GuidebotName = PlayerCfg . GuidebotNameReal = text ;
2006-03-20 17:12:09 +00:00
write_player_file ( ) ;
}
}
2018-05-12 18:24:19 +00:00
# undef DXX_GUIDEBOT_RENAME_MENU
2006-03-20 17:12:09 +00:00
// -----------------------------------------------------------------------------
2013-12-08 23:37:40 +00:00
static int show_buddy_message ( )
2006-03-20 17:12:09 +00:00
{
if ( Buddy_messages_suppressed )
2013-12-08 23:37:40 +00:00
return 0 ;
2006-03-20 17:12:09 +00:00
if ( Game_mode & GM_MULTI )
2013-12-08 23:37:40 +00:00
return 0 ;
2006-03-20 17:12:09 +00:00
2010-12-22 00:17:59 +00:00
if ( Last_buddy_message_time + F1_0 < GameTime64 ) {
2017-02-26 00:00:02 +00:00
if ( auto r = ok_for_buddy_to_talk ( ) )
return r ;
2013-12-08 23:37:40 +00:00
}
return 0 ;
}
2018-04-03 03:20:08 +00:00
__attribute_nonnull ( )
2018-03-27 03:49:34 +00:00
static void buddy_message_force_str ( const char * str )
2013-12-08 23:37:40 +00:00
{
Last_buddy_message_time = GameTime64 ;
2016-07-14 01:59:02 +00:00
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 ) ;
2013-12-08 23:37:40 +00:00
}
2018-04-03 03:20:08 +00:00
__attribute_format_printf ( 1 , 0 )
2018-03-27 03:49:34 +00:00
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 ) ;
}
2018-04-03 03:20:08 +00:00
__attribute_format_printf ( 1 , 2 )
2018-03-27 03:49:34 +00:00
static void buddy_message_ignore_time ( const char * const fmt , . . . )
{
if ( Buddy_messages_suppressed )
return ;
va_list args ;
va_start ( args , fmt ) ;
buddy_message_force_va ( fmt , args ) ;
va_end ( args ) ;
}
2013-12-08 23:37:40 +00:00
void ( buddy_message ) ( const char * format , . . . )
{
if ( ! show_buddy_message ( ) )
return ;
2006-03-20 17:12:09 +00:00
va_list args ;
va_start ( args , format ) ;
2018-03-27 03:49:34 +00:00
buddy_message_force_va ( format , args ) ;
2006-03-20 17:12:09 +00:00
va_end ( args ) ;
2013-12-08 23:37:40 +00:00
}
2006-03-20 17:12:09 +00:00
2013-12-08 23:37:40 +00:00
void buddy_message_str ( const char * str )
{
if ( ! show_buddy_message ( ) )
return ;
2018-03-27 03:49:34 +00:00
buddy_message_force_str ( str ) ;
2006-03-20 17:12:09 +00:00
}
// -----------------------------------------------------------------------------
2013-12-08 23:37:40 +00:00
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 ) ;
}
2013-06-27 02:35:22 +00:00
static void thief_message ( const char * format , . . . ) __attribute_format_printf ( 1 , 2 ) ;
static void thief_message ( const char * format , . . . )
2013-12-08 23:37:40 +00:00
# define thief_message(F,...) dxx_call_printf_checked(thief_message,thief_message_str,(),(F),##__VA_ARGS__)
2006-03-20 17:12:09 +00:00
{
2012-06-24 21:05:03 +00:00
char new_format [ 128 ] ;
2006-03-20 17:12:09 +00:00
va_list args ;
va_start ( args , format ) ;
2012-06-24 21:05:03 +00:00
vsnprintf ( new_format , sizeof ( new_format ) , format , args ) ;
2006-03-20 17:12:09 +00:00
va_end ( args ) ;
2013-12-08 23:37:40 +00:00
thief_message_str ( new_format ) ;
2006-03-20 17:12:09 +00:00
}
// -----------------------------------------------------------------------------
// Return true if marker #id has been placed.
2013-10-27 22:00:14 +00:00
static int marker_exists_in_mine ( int id )
2006-03-20 17:12:09 +00:00
{
2016-02-12 04:02:28 +00:00
range_for ( const auto & & objp , vcobjptr )
2015-06-13 22:42:16 +00:00
{
if ( objp - > type = = OBJ_MARKER )
2015-11-27 03:56:13 +00:00
if ( get_marker_id ( objp ) = = id )
2006-03-20 17:12:09 +00:00
return 1 ;
2015-06-13 22:42:16 +00:00
}
2006-03-20 17:12:09 +00:00
return 0 ;
}
// -----------------------------------------------------------------------------
void set_escort_special_goal ( int special_key )
{
int marker_key ;
Buddy_messages_suppressed = 0 ;
if ( ! Buddy_allowed_to_talk ) {
ok_for_buddy_to_talk ( ) ;
if ( ! Buddy_allowed_to_talk ) {
2018-09-19 02:13:29 +00:00
const auto & & o = find_escort ( vmobjptridx , Robot_info ) ;
2015-01-12 00:26:04 +00:00
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 ) ) ;
2006-03-20 17:12:09 +00:00
return ;
}
}
special_key = special_key & ( ~ KEY_SHIFTED ) ;
marker_key = special_key ;
if ( Last_buddy_key = = special_key )
{
if ( ( Looking_for_marker = = - 1 ) & & ( special_key ! = KEY_0 ) ) {
if ( marker_exists_in_mine ( marker_key - KEY_1 ) )
Looking_for_marker = marker_key - KEY_1 ;
else {
2018-03-27 03:49:34 +00:00
buddy_message_ignore_time ( " Marker %i not placed. " , marker_key - KEY_1 + 1 ) ;
2006-03-20 17:12:09 +00:00
Looking_for_marker = - 1 ;
}
} else {
Looking_for_marker = - 1 ;
}
}
Last_buddy_key = special_key ;
if ( special_key = = KEY_0 )
Looking_for_marker = - 1 ;
if ( Looking_for_marker ! = - 1 ) {
2015-06-13 22:42:15 +00:00
Escort_special_goal = static_cast < escort_goal_t > ( ESCORT_GOAL_MARKER1 + marker_key - KEY_1 ) ;
2006-03-20 17:12:09 +00:00
} else {
switch ( special_key ) {
case KEY_1 : Escort_special_goal = ESCORT_GOAL_ENERGY ; break ;
case KEY_2 : Escort_special_goal = ESCORT_GOAL_ENERGYCEN ; break ;
case KEY_3 : Escort_special_goal = ESCORT_GOAL_SHIELD ; break ;
case KEY_4 : Escort_special_goal = ESCORT_GOAL_POWERUP ; break ;
case KEY_5 : Escort_special_goal = ESCORT_GOAL_ROBOT ; break ;
case KEY_6 : Escort_special_goal = ESCORT_GOAL_HOSTAGE ; break ;
case KEY_7 : Escort_special_goal = ESCORT_GOAL_SCRAM ; break ;
case KEY_8 : Escort_special_goal = ESCORT_GOAL_PLAYER_SPEW ; break ;
case KEY_9 : Escort_special_goal = ESCORT_GOAL_EXIT ; break ;
2015-06-13 22:42:15 +00:00
case KEY_0 : Escort_special_goal = ESCORT_GOAL_UNSPECIFIED ; break ;
2006-03-20 17:12:09 +00:00
default :
Int3 ( ) ; // Oops, called with illegal key value.
}
}
2010-12-22 00:17:59 +00:00
Last_buddy_message_time = GameTime64 - 2 * F1_0 ; // Allow next message to come through.
2006-03-20 17:12:09 +00:00
say_escort_goal ( Escort_special_goal ) ;
// -- Escort_goal_object = escort_set_goal_object();
Escort_goal_object = ESCORT_GOAL_UNSPECIFIED ;
}
// -----------------------------------------------------------------------------
// Return id of boss.
2013-10-27 22:00:14 +00:00
static int get_boss_id ( void )
2006-03-20 17:12:09 +00:00
{
2016-02-12 04:02:28 +00:00
range_for ( const auto & & objp , vcobjptr )
2015-06-13 22:42:16 +00:00
{
if ( objp - > type = = OBJ_ROBOT )
2015-11-27 03:56:13 +00:00
{
const auto objp_id = get_robot_id ( objp ) ;
if ( Robot_info [ objp_id ] . boss_flag )
return objp_id ;
}
2015-06-13 22:42:16 +00:00
}
2006-03-20 17:12:09 +00:00
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.
2018-06-24 05:06:14 +00:00
static objnum_t exists_in_mine_2 ( const unique_segment & segp , const int objtype , const int objid , const int special )
2006-03-20 17:12:09 +00:00
{
2017-08-13 20:38:31 +00:00
range_for ( const auto curobjp , objects_in ( segp , vcobjptridx , vcsegptr ) )
2014-07-29 03:21:53 +00:00
{
const auto & objnum = curobjp ;
2015-01-28 03:42:52 +00:00
if ( special = = ESCORT_GOAL_PLAYER_SPEW & & curobjp - > type = = OBJ_POWERUP )
{
2006-03-20 17:12:09 +00:00
if ( curobjp - > flags & OF_PLAYER_DROPPED )
return objnum ;
}
if ( curobjp - > type = = objtype ) {
// Don't find escort robots if looking for robot!
2015-11-27 03:56:13 +00:00
if ( ( curobjp - > type = = OBJ_ROBOT ) & & ( Robot_info [ get_robot_id ( curobjp ) ] . companion ) )
2006-03-20 17:12:09 +00:00
;
else if ( objid = = - 1 ) {
return objnum ;
} else if ( curobjp - > id = = objid )
return objnum ;
}
2015-01-28 03:42:52 +00:00
if ( objtype = = OBJ_POWERUP & & curobjp - > type = = OBJ_ROBOT )
2006-03-20 17:12:09 +00:00
if ( curobjp - > contains_count )
if ( curobjp - > contains_type = = OBJ_POWERUP )
if ( curobjp - > contains_id = = objid )
return objnum ;
}
2013-12-26 22:21:16 +00:00
return object_none ;
2006-03-20 17:12:09 +00:00
}
// -----------------------------------------------------------------------------
2016-10-15 00:53:20 +00:00
static segnum_t exists_fuelcen_in_mine ( const vcsegidx_t start_seg , const player_flags powerup_flags )
2006-03-20 17:12:09 +00:00
{
2015-01-28 03:42:52 +00:00
array < segnum_t , MAX_SEGMENTS > bfs_list ;
2017-06-10 03:31:02 +00:00
const auto length = create_bfs_list ( vmobjptr ( Buddy_objnum ) , start_seg , powerup_flags , bfs_list ) ;
2015-12-03 03:26:49 +00:00
auto predicate = [ ] ( const segnum_t & s ) { return vcsegptr ( s ) - > special = = SEGMENT_IS_FUELCEN ; } ;
2014-10-21 03:15:12 +00:00
{
2016-02-12 04:02:28 +00:00
const auto & & rb = partial_const_range ( bfs_list , length ) ;
2014-10-21 03:15:12 +00:00
auto i = std : : find_if ( rb . begin ( ) , rb . end ( ) , predicate ) ;
if ( i ! = rb . end ( ) )
return * i ;
}
{
2016-02-06 22:12:54 +00:00
const auto & rh = vcsegptr ;
2018-12-30 00:43:57 +00:00
const auto a = [ ] ( const shared_segment & s ) {
return s . special = = SEGMENT_IS_FUELCEN ;
2015-12-22 04:18:51 +00:00
} ;
if ( std : : find_if ( rh . begin ( ) , rh . end ( ) , a ) ! = rh . end ( ) )
2014-10-21 03:15:12 +00:00
return segment_exit ;
}
2013-12-29 04:28:07 +00:00
return segment_none ;
2012-12-08 23:36:56 +00:00
}
// 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)
2016-10-15 00:53:20 +00:00
static objnum_t exists_in_mine ( const vcsegidx_t start_seg , const int objtype , const int objid , const int special , const player_flags powerup_flags )
2012-12-08 23:36:56 +00:00
{
2015-01-28 03:42:52 +00:00
array < segnum_t , MAX_SEGMENTS > bfs_list ;
2017-06-10 03:31:02 +00:00
const auto length = create_bfs_list ( vmobjptr ( Buddy_objnum ) , start_seg , powerup_flags , bfs_list ) ;
2012-12-08 23:36:56 +00:00
2016-02-12 04:02:28 +00:00
range_for ( const auto segnum , partial_const_range ( bfs_list , length ) )
2015-01-12 00:26:04 +00:00
{
2016-10-08 03:34:17 +00:00
const auto & & objnum = exists_in_mine_2 ( vcsegptr ( segnum ) , objtype , objid , special ) ;
2013-12-26 22:21:16 +00:00
if ( objnum ! = object_none )
2006-03-20 17:12:09 +00:00
return objnum ;
}
// 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.
2016-10-08 03:34:17 +00:00
range_for ( const auto & & segnum , vcsegptr )
2014-10-12 23:10:05 +00:00
{
2016-10-08 03:34:17 +00:00
const auto & & objnum = exists_in_mine_2 ( segnum , objtype , objid , special ) ;
2013-12-26 22:21:16 +00:00
if ( objnum ! = object_none )
return object_guidebot_cannot_reach ;
2006-03-20 17:12:09 +00:00
}
2013-12-26 22:21:16 +00:00
return object_none ;
2006-03-20 17:12:09 +00:00
}
// -----------------------------------------------------------------------------
// Return true if it happened, else return false.
2017-06-10 03:31:02 +00:00
static imsegidx_t find_exit_segment ( )
2006-03-20 17:12:09 +00:00
{
// ---------- Find exit doors ----------
2016-02-12 04:02:28 +00:00
range_for ( const auto & & segp , vcsegptridx )
2015-06-13 22:42:16 +00:00
{
range_for ( const auto j , segp - > children )
2015-01-12 00:26:04 +00:00
if ( j = = segment_exit )
2015-12-22 04:18:51 +00:00
return segp ;
2015-06-13 22:42:16 +00:00
}
2013-12-26 22:21:16 +00:00
return segment_none ;
2006-03-20 17:12:09 +00:00
}
// -----------------------------------------------------------------------------
2018-03-10 18:32:02 +00:00
static void say_escort_goal ( const escort_goal_t goal_num )
2006-03-20 17:12:09 +00:00
{
2015-12-15 04:09:35 +00:00
if ( Player_dead_state ! = player_dead_state : : no )
2006-03-20 17:12:09 +00:00
return ;
2016-07-14 01:59:02 +00:00
const char * str ;
2006-03-20 17:12:09 +00:00
switch ( goal_num ) {
2012-12-08 21:35:30 +00:00
default :
case ESCORT_GOAL_UNSPECIFIED :
2016-07-14 01:59:02 +00:00
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 " ;
2012-12-08 21:35:30 +00:00
break ;
2006-03-20 17:12:09 +00:00
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 :
2018-03-10 18:32:02 +00:00
{
const unsigned zero_based_goal_num = goal_num - ESCORT_GOAL_MARKER1 ;
buddy_message ( " Finding marker %i: '%.24s' " , zero_based_goal_num + 1 , & MarkerState . message [ zero_based_goal_num ] [ 0 ] ) ;
}
2016-07-14 01:59:02 +00:00
return ;
2006-03-20 17:12:09 +00:00
}
2016-07-14 01:59:02 +00:00
buddy_message_str ( str ) ;
2006-03-20 17:12:09 +00:00
}
2015-06-13 22:42:15 +00:00
static void clear_escort_goals ( )
{
2016-07-14 01:59:02 +00:00
Looking_for_marker = - 1 ;
2015-06-13 22:42:15 +00:00
Escort_goal_object = ESCORT_GOAL_UNSPECIFIED ;
Escort_special_goal = ESCORT_GOAL_UNSPECIFIED ;
}
static void escort_goal_does_not_exist ( escort_goal_t goal )
{
2018-03-27 03:49:34 +00:00
buddy_message_ignore_time ( " No %s in mine. " , Escort_goal_text [ goal - 1 ] ) ;
2015-06-13 22:42:15 +00:00
clear_escort_goals ( ) ;
}
static void escort_goal_unreachable ( escort_goal_t goal )
{
2018-03-27 03:49:34 +00:00
buddy_message_ignore_time ( " Can't reach %s. " , Escort_goal_text [ goal - 1 ] ) ;
2015-06-13 22:42:15 +00:00
clear_escort_goals ( ) ;
}
2017-06-10 03:31:02 +00:00
static void escort_go_to_goal ( const vmobjptridx_t objp , ai_static * aip , segnum_t goal_seg )
2015-06-13 22:42:15 +00:00
{
2018-06-20 04:16:06 +00:00
create_path_to_segment ( objp , goal_seg , Max_escort_length , create_path_safety_flag : : safe ) ; // MK!: Last parm (safety_flag) used to be 1!!
2015-06-13 22:42:15 +00:00
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 ) ) {
fix dist_to_player ;
2018-03-27 03:49:34 +00:00
buddy_message_ignore_time ( " Can't reach %s. " , Escort_goal_text [ Escort_goal_object - 1 ] ) ;
2015-06-13 22:42:15 +00:00
Looking_for_marker = - 1 ;
Escort_goal_object = ESCORT_GOAL_SCRAM ;
2017-06-10 03:31:02 +00:00
dist_to_player = find_connected_distance ( objp - > pos , vmsegptridx ( objp - > segnum ) , Believed_player_pos , vmsegptridx ( Believed_player_seg ) , 100 , WID_FLY_FLAG ) ;
2015-06-13 22:42:15 +00:00
if ( dist_to_player > MIN_ESCORT_DISTANCE )
2018-06-20 04:16:06 +00:00
create_path_to_player ( objp , Max_escort_length , create_path_safety_flag : : safe ) ; // MK!: Last parm used to be 1!
2015-06-13 22:42:15 +00:00
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 ) ;
}
}
}
2006-03-20 17:12:09 +00:00
// -----------------------------------------------------------------------------
2017-06-10 03:31:02 +00:00
static imsegidx_t escort_get_goal_segment ( const vcobjptr_t objp , int objtype , int objid , const player_flags powerup_flags )
2015-12-03 03:26:49 +00:00
{
2016-10-15 00:53:20 +00:00
const auto egi = exists_in_mine ( objp - > segnum , objtype , objid , - 1 , powerup_flags ) ;
2015-12-03 03:26:49 +00:00
Escort_goal_index = egi ;
2016-10-05 03:55:19 +00:00
if ( egi ! = object_none & & egi ! = object_guidebot_cannot_reach )
2015-12-03 03:26:49 +00:00
return vcobjptr ( egi ) - > segnum ;
return segment_none ;
}
2017-06-10 03:31:02 +00:00
static void escort_create_path_to_goal ( const vmobjptridx_t objp , const player_info & player_info )
2006-03-20 17:12:09 +00:00
{
2013-12-29 04:28:07 +00:00
segnum_t goal_seg = segment_none ;
2006-03-20 17:12:09 +00:00
ai_static * aip = & objp - > ctype . ai_info ;
2013-12-25 03:16:41 +00:00
ai_local * ailp = & objp - > ctype . ai_info . ail ;
2006-03-20 17:12:09 +00:00
2015-06-13 22:42:15 +00:00
if ( Escort_special_goal ! = ESCORT_GOAL_UNSPECIFIED )
2006-03-20 17:12:09 +00:00
Escort_goal_object = Escort_special_goal ;
2016-10-15 00:53:20 +00:00
const auto powerup_flags = player_info . powerup_flags ;
2006-03-20 17:12:09 +00:00
if ( Looking_for_marker ! = - 1 ) {
2016-10-15 00:53:20 +00:00
goal_seg = escort_get_goal_segment ( objp , OBJ_MARKER , Escort_goal_object - ESCORT_GOAL_MARKER1 , powerup_flags ) ;
2006-03-20 17:12:09 +00:00
} else {
switch ( Escort_goal_object ) {
case ESCORT_GOAL_BLUE_KEY :
2016-10-15 00:53:20 +00:00
goal_seg = escort_get_goal_segment ( objp , OBJ_POWERUP , POW_KEY_BLUE , powerup_flags ) ;
2006-03-20 17:12:09 +00:00
break ;
case ESCORT_GOAL_GOLD_KEY :
2016-10-15 00:53:20 +00:00
goal_seg = escort_get_goal_segment ( objp , OBJ_POWERUP , POW_KEY_GOLD , powerup_flags ) ;
2006-03-20 17:12:09 +00:00
break ;
case ESCORT_GOAL_RED_KEY :
2016-10-15 00:53:20 +00:00
goal_seg = escort_get_goal_segment ( objp , OBJ_POWERUP , POW_KEY_RED , powerup_flags ) ;
2006-03-20 17:12:09 +00:00
break ;
case ESCORT_GOAL_CONTROLCEN :
2016-10-15 00:53:20 +00:00
goal_seg = escort_get_goal_segment ( objp , OBJ_CNTRLCEN , - 1 , powerup_flags ) ;
2006-03-20 17:12:09 +00:00
break ;
case ESCORT_GOAL_EXIT :
goal_seg = find_exit_segment ( ) ;
2016-11-29 18:30:16 +00:00
Escort_goal_index = object_none ;
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 ;
2006-03-20 17:12:09 +00:00
case ESCORT_GOAL_ENERGY :
2016-10-15 00:53:20 +00:00
goal_seg = escort_get_goal_segment ( objp , OBJ_POWERUP , POW_ENERGY , powerup_flags ) ;
2006-03-20 17:12:09 +00:00
break ;
case ESCORT_GOAL_ENERGYCEN :
2016-10-15 00:53:20 +00:00
goal_seg = exists_fuelcen_in_mine ( objp - > segnum , powerup_flags ) ;
2016-11-29 18:30:16 +00:00
Escort_goal_index = object_none ;
2015-06-13 22:42:15 +00:00
if ( goal_seg = = segment_none )
escort_goal_does_not_exist ( ESCORT_GOAL_ENERGYCEN ) ;
else if ( goal_seg = = segment_exit )
escort_goal_unreachable ( ESCORT_GOAL_ENERGYCEN ) ;
else
escort_go_to_goal ( objp , aip , goal_seg ) ;
return ;
2006-03-20 17:12:09 +00:00
case ESCORT_GOAL_SHIELD :
2016-10-15 00:53:20 +00:00
goal_seg = escort_get_goal_segment ( objp , OBJ_POWERUP , POW_SHIELD_BOOST , powerup_flags ) ;
2006-03-20 17:12:09 +00:00
break ;
case ESCORT_GOAL_POWERUP :
2016-10-15 00:53:20 +00:00
goal_seg = escort_get_goal_segment ( objp , OBJ_POWERUP , - 1 , powerup_flags ) ;
2006-03-20 17:12:09 +00:00
break ;
case ESCORT_GOAL_ROBOT :
2016-10-15 00:53:20 +00:00
goal_seg = escort_get_goal_segment ( objp , OBJ_ROBOT , - 1 , powerup_flags ) ;
2006-03-20 17:12:09 +00:00
break ;
case ESCORT_GOAL_HOSTAGE :
2016-10-15 00:53:20 +00:00
goal_seg = escort_get_goal_segment ( objp , OBJ_HOSTAGE , - 1 , powerup_flags ) ;
2006-03-20 17:12:09 +00:00
break ;
case ESCORT_GOAL_PLAYER_SPEW :
2016-10-15 00:53:20 +00:00
Escort_goal_index = exists_in_mine ( objp - > segnum , - 1 , - 1 , ESCORT_GOAL_PLAYER_SPEW , powerup_flags ) ;
2013-12-29 04:28:07 +00:00
if ( Escort_goal_index ! = object_none ) goal_seg = Objects [ Escort_goal_index ] . segnum ;
2006-03-20 17:12:09 +00:00
break ;
case ESCORT_GOAL_SCRAM :
2016-11-29 18:30:16 +00:00
Escort_goal_index = object_none ;
2006-03-20 17:12:09 +00:00
break ;
case ESCORT_GOAL_BOSS : {
int boss_id ;
boss_id = get_boss_id ( ) ;
Assert ( boss_id ! = - 1 ) ;
2016-10-15 00:53:20 +00:00
goal_seg = escort_get_goal_segment ( objp , OBJ_ROBOT , boss_id , powerup_flags ) ;
2006-03-20 17:12:09 +00:00
break ;
}
default :
Int3 ( ) ; // Oops, Illegal value in Escort_goal_object.
goal_seg = 0 ;
2016-11-29 18:30:16 +00:00
Escort_goal_index = object_none ;
2006-03-20 17:12:09 +00:00
break ;
}
}
2013-12-29 04:28:07 +00:00
if ( Escort_goal_index = = object_none ) {
2015-06-13 22:42:15 +00:00
escort_goal_does_not_exist ( Escort_goal_object ) ;
2013-12-29 04:28:07 +00:00
} else if ( Escort_goal_index = = object_guidebot_cannot_reach ) {
2015-06-13 22:42:15 +00:00
escort_goal_unreachable ( Escort_goal_object ) ;
2006-03-20 17:12:09 +00:00
} else {
2012-11-24 17:59:53 +00:00
if ( Escort_goal_object = = ESCORT_GOAL_SCRAM ) {
2013-12-29 04:28:07 +00:00
create_n_segment_path ( objp , 16 + d_rand ( ) * 16 , segment_none ) ;
2006-03-20 17:12:09 +00:00
aip - > path_length = polish_path ( objp , & Point_segs [ aip - > hide_index ] , aip - > path_length ) ;
} else {
2015-06-13 22:42:15 +00:00
escort_go_to_goal ( objp , aip , goal_seg ) ;
2006-03-20 17:12:09 +00:00
}
2015-04-26 20:15:51 +00:00
ailp - > mode = ai_mode : : AIM_GOTO_OBJECT ;
2006-03-20 17:12:09 +00:00
say_escort_goal ( Escort_goal_object ) ;
}
}
// -----------------------------------------------------------------------------
// Escort robot chooses goal object based on player's keys, location.
// Returns goal object.
2016-10-02 00:34:41 +00:00
static escort_goal_t escort_set_goal_object ( const player_flags pl_flags )
2006-03-20 17:12:09 +00:00
{
2015-06-13 22:42:15 +00:00
if ( Escort_special_goal ! = ESCORT_GOAL_UNSPECIFIED )
2006-03-20 17:12:09 +00:00
return ESCORT_GOAL_UNSPECIFIED ;
2016-11-21 01:57:08 +00:00
if ( ! ( pl_flags & PLAYER_FLAGS_BLUE_KEY ) & & exists_in_mine ( ConsoleObject - > segnum , OBJ_POWERUP , POW_KEY_BLUE , - 1 , pl_flags ) ! = object_none )
return ESCORT_GOAL_BLUE_KEY ;
else if ( ! ( pl_flags & PLAYER_FLAGS_GOLD_KEY ) & & exists_in_mine ( ConsoleObject - > segnum , OBJ_POWERUP , POW_KEY_GOLD , - 1 , pl_flags ) ! = object_none )
return ESCORT_GOAL_GOLD_KEY ;
else if ( ! ( pl_flags & PLAYER_FLAGS_RED_KEY ) & & exists_in_mine ( ConsoleObject - > segnum , OBJ_POWERUP , POW_KEY_RED , - 1 , pl_flags ) ! = object_none )
return ESCORT_GOAL_RED_KEY ;
else if ( Control_center_destroyed = = 0 )
2016-10-15 00:53:20 +00:00
{
2017-06-10 03:31:03 +00:00
if ( ! Boss_teleport_segs . empty ( ) )
2006-03-20 17:12:09 +00:00
return ESCORT_GOAL_BOSS ;
else
return ESCORT_GOAL_CONTROLCEN ;
} else
return ESCORT_GOAL_EXIT ;
}
# define MAX_ESCORT_TIME_AWAY (F1_0*4)
2010-12-22 00:17:59 +00:00
fix64 Buddy_last_seen_player = 0 , Buddy_last_player_path_created ;
2006-03-20 17:12:09 +00:00
// -----------------------------------------------------------------------------
2017-06-10 03:31:02 +00:00
static int time_to_visit_player ( const vmobjptr_t objp , ai_local * ailp , ai_static * aip )
2006-03-20 17:12:09 +00:00
{
// 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.
2010-12-22 00:17:59 +00:00
if ( GameTime64 - Buddy_last_seen_player > MAX_ESCORT_TIME_AWAY )
if ( GameTime64 - Buddy_last_player_path_created > F1_0 )
2006-03-20 17:12:09 +00:00
return 1 ;
2015-04-26 20:15:51 +00:00
if ( ailp - > mode = = ai_mode : : AIM_GOTO_PLAYER )
2006-03-20 17:12:09 +00:00
return 0 ;
if ( objp - > segnum = = ConsoleObject - > segnum )
return 0 ;
if ( aip - > cur_path_index < aip - > path_length / 2 )
return 0 ;
return 1 ;
}
// -----------------------------------------------------------------------------
2017-06-10 03:31:02 +00:00
static void bash_buddy_weapon_info ( const vmobjptridx_t objp )
2006-03-20 17:12:09 +00:00
{
2016-07-14 01:59:02 +00:00
auto & laser_info = objp - > ctype . laser_info ;
const auto console = ConsoleObject ;
2017-06-10 03:31:02 +00:00
laser_info . parent_num = vmobjptridx ( console ) ;
2016-07-14 01:59:02 +00:00
laser_info . parent_type = OBJ_PLAYER ;
laser_info . parent_signature = console - > signature ;
2006-03-20 17:12:09 +00:00
}
// -----------------------------------------------------------------------------
2017-06-10 03:31:02 +00:00
static int maybe_buddy_fire_mega ( const vmobjptridx_t objp )
2006-03-20 17:12:09 +00:00
{
2015-12-22 04:18:51 +00:00
const auto & & buddy_objp = objp . absolute_sibling ( Buddy_objnum ) ;
2006-03-20 17:12:09 +00:00
fix dist , dot ;
2014-10-29 03:24:31 +00:00
auto vec_to_robot = vm_vec_sub ( buddy_objp - > pos , objp - > pos ) ;
2014-09-28 21:11:04 +00:00
dist = vm_vec_normalize_quick ( vec_to_robot ) ;
2006-03-20 17:12:09 +00:00
if ( dist > F1_0 * 100 )
return 0 ;
2014-09-28 21:11:48 +00:00
dot = vm_vec_dot ( vec_to_robot , buddy_objp - > orient . fvec ) ;
2006-03-20 17:12:09 +00:00
if ( dot < F1_0 / 2 )
return 0 ;
if ( ! object_to_object_visibility ( buddy_objp , objp , FQ_TRANSWALL ) )
return 0 ;
2015-12-03 03:26:49 +00:00
if ( Weapon_info [ weapon_id_type : : MEGA_ID ] . render_type = = 0 ) {
2017-12-05 05:29:55 +00:00
con_puts ( CON_VERBOSE , " Buddy can't fire mega (shareware) " ) ;
2006-03-20 17:12:09 +00:00
buddy_message ( " CLICK! " ) ;
return 0 ;
}
buddy_message ( " GAHOOGA! " ) ;
2017-06-10 03:31:02 +00:00
const imobjptridx_t weapon_objnum = Laser_create_new_easy ( buddy_objp - > orient . fvec , buddy_objp - > pos , objp , weapon_id_type : : MEGA_ID , 1 ) ;
2006-03-20 17:12:09 +00:00
2013-12-26 22:21:16 +00:00
if ( weapon_objnum ! = object_none )
2006-03-20 17:12:09 +00:00
bash_buddy_weapon_info ( weapon_objnum ) ;
return 1 ;
}
//-----------------------------------------------------------------------------
2017-06-10 03:31:02 +00:00
static int maybe_buddy_fire_smart ( const vmobjptridx_t objp )
2006-03-20 17:12:09 +00:00
{
2015-12-22 04:18:51 +00:00
const auto & & buddy_objp = objp . absolute_sibling ( Buddy_objnum ) ;
2006-03-20 17:12:09 +00:00
fix dist ;
2014-10-01 02:28:41 +00:00
dist = vm_vec_dist_quick ( buddy_objp - > pos , objp - > pos ) ;
2006-03-20 17:12:09 +00:00
if ( dist > F1_0 * 80 )
return 0 ;
if ( ! object_to_object_visibility ( buddy_objp , objp , FQ_TRANSWALL ) )
return 0 ;
buddy_message ( " WHAMMO! " ) ;
2017-06-10 03:31:02 +00:00
const imobjptridx_t weapon_objnum = Laser_create_new_easy ( buddy_objp - > orient . fvec , buddy_objp - > pos , objp , weapon_id_type : : SMART_ID , 1 ) ;
2006-03-20 17:12:09 +00:00
2013-12-26 22:21:16 +00:00
if ( weapon_objnum ! = object_none )
2006-03-20 17:12:09 +00:00
bash_buddy_weapon_info ( weapon_objnum ) ;
return 1 ;
}
// -----------------------------------------------------------------------------
2013-10-27 22:00:14 +00:00
static void do_buddy_dude_stuff ( void )
2006-03-20 17:12:09 +00:00
{
if ( ! ok_for_buddy_to_talk ( ) )
return ;
2010-12-22 00:17:59 +00:00
if ( Buddy_last_missile_time > GameTime64 )
2006-03-20 17:12:09 +00:00
Buddy_last_missile_time = 0 ;
2010-12-22 00:17:59 +00:00
if ( Buddy_last_missile_time + F1_0 * 2 < GameTime64 ) {
2006-03-20 17:12:09 +00:00
// See if a robot potentially in view cone
2017-06-10 03:31:03 +00:00
auto & & rh = make_range ( vmobjptridx ) ;
2015-12-22 04:18:51 +00:00
range_for ( const auto & & objp , rh )
2014-11-26 03:39:21 +00:00
{
if ( ( objp - > type = = OBJ_ROBOT ) & & ! Robot_info [ get_robot_id ( objp ) ] . companion )
if ( maybe_buddy_fire_mega ( objp ) ) {
2010-12-22 00:17:59 +00:00
Buddy_last_missile_time = GameTime64 ;
2006-03-20 17:12:09 +00:00
return ;
}
2014-11-26 03:39:21 +00:00
}
2006-03-20 17:12:09 +00:00
// See if a robot near enough that buddy should fire smart missile
2015-12-22 04:18:51 +00:00
range_for ( const auto & & objp , rh )
2014-11-26 03:39:21 +00:00
{
if ( ( objp - > type = = OBJ_ROBOT ) & & ! Robot_info [ get_robot_id ( objp ) ] . companion )
if ( maybe_buddy_fire_smart ( objp ) ) {
2010-12-22 00:17:59 +00:00
Buddy_last_missile_time = GameTime64 ;
2006-03-20 17:12:09 +00:00
return ;
}
2014-11-26 03:39:21 +00:00
}
2006-03-20 17:12:09 +00:00
}
}
// -----------------------------------------------------------------------------
// Called every frame (or something).
2017-06-10 03:31:02 +00:00
void do_escort_frame ( const vmobjptridx_t objp , const object & plrobj , fix dist_to_player , int player_visibility )
2006-03-20 17:12:09 +00:00
{
ai_static * aip = & objp - > ctype . ai_info ;
2013-12-25 03:16:41 +00:00
ai_local * ailp = & objp - > ctype . ai_info . ail ;
2006-03-20 17:12:09 +00:00
2013-12-24 04:53:59 +00:00
Buddy_objnum = objp ;
2006-03-20 17:12:09 +00:00
2016-10-28 03:39:41 +00:00
auto & player_info = plrobj . ctype . player_info ;
2006-03-20 17:12:09 +00:00
if ( player_visibility ) {
2010-12-22 00:17:59 +00:00
Buddy_last_seen_player = GameTime64 ;
2016-10-28 03:39:41 +00:00
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
2015-11-07 21:55:58 +00:00
{
2016-10-28 03:39:41 +00:00
const auto energy = player_info . energy ;
2015-11-07 21:55:58 +00:00
const auto ienergy = f2i ( energy ) ;
if ( ienergy < 40 )
if ( ienergy & 4 )
2006-03-20 17:12:09 +00:00
buddy_message ( " Hey, your headlight's on! " ) ;
2015-11-07 21:55:58 +00:00
}
2006-03-20 17:12:09 +00:00
}
2011-02-14 21:27:07 +00:00
if ( cheats . buddyangry )
2006-03-20 17:12:09 +00:00
do_buddy_dude_stuff ( ) ;
2016-07-14 01:59:02 +00:00
{
const auto buddy_sorry_time = Buddy_sorry_time ;
if ( buddy_sorry_time + F1_0 > GameTime64 )
{
Buddy_sorry_time = - F1_0 * 2 ;
if ( buddy_sorry_time < GameTime64 + F1_0 * 2 )
{
2018-03-27 03:49:34 +00:00
buddy_message_ignore_time ( " Oops, sorry 'bout that... " ) ;
2016-07-14 01:59:02 +00:00
}
}
2006-03-20 17:12:09 +00:00
}
// If buddy not allowed to talk, then he is locked in his room. Make him mostly do nothing unless you're nearby.
if ( ! Buddy_allowed_to_talk )
if ( dist_to_player > F1_0 * 100 )
aip - > SKIP_AI_COUNT = ( F1_0 / 4 ) / FrameTime ;
2015-04-26 20:15:51 +00:00
// ai_mode::AIM_WANDER has been co-opted for buddy behavior (didn't want to modify aistruct.h)
2006-03-20 17:12:09 +00:00
// 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.
2015-04-26 20:15:51 +00:00
if ( ailp - > mode = = ai_mode : : AIM_WANDER )
2006-03-20 17:12:09 +00:00
if ( player_visibility ) {
2013-12-26 22:21:16 +00:00
create_n_segment_path ( objp , 16 + d_rand ( ) * 16 , segment_none ) ;
2006-03-20 17:12:09 +00:00
aip - > path_length = polish_path ( objp , & Point_segs [ aip - > hide_index ] , aip - > path_length ) ;
}
if ( Escort_special_goal = = ESCORT_GOAL_SCRAM ) {
if ( player_visibility )
2010-12-22 00:17:59 +00:00
if ( Escort_last_path_created + F1_0 * 3 < GameTime64 ) {
2006-03-20 17:12:09 +00:00
create_n_segment_path ( objp , 10 + d_rand ( ) * 16 , ConsoleObject - > segnum ) ;
2010-12-22 00:17:59 +00:00
Escort_last_path_created = GameTime64 ;
2006-03-20 17:12:09 +00:00
}
return ;
}
// Force checking for new goal every 5 seconds, and create new path, if necessary.
2010-12-22 00:17:59 +00:00
if ( ( ( Escort_special_goal ! = ESCORT_GOAL_SCRAM ) & & ( ( Escort_last_path_created + F1_0 * 5 ) < GameTime64 ) ) | |
( ( Escort_special_goal = = ESCORT_GOAL_SCRAM ) & & ( ( Escort_last_path_created + F1_0 * 15 ) < GameTime64 ) ) ) {
2006-03-20 17:12:09 +00:00
Escort_goal_object = ESCORT_GOAL_UNSPECIFIED ;
2010-12-22 00:17:59 +00:00
Escort_last_path_created = GameTime64 ;
2006-03-20 17:12:09 +00:00
}
if ( ( Escort_special_goal ! = ESCORT_GOAL_SCRAM ) & & time_to_visit_player ( objp , ailp , aip ) ) {
2018-06-20 04:16:06 +00:00
unsigned max_len ;
2006-03-20 17:12:09 +00:00
2010-12-22 00:17:59 +00:00
Buddy_last_player_path_created = GameTime64 ;
2015-04-26 20:15:51 +00:00
ailp - > mode = ai_mode : : AIM_GOTO_PLAYER ;
2006-03-20 17:12:09 +00:00
if ( ! player_visibility ) {
2010-12-22 00:17:59 +00:00
if ( ( Last_come_back_message_time + F1_0 < GameTime64 ) | | ( Last_come_back_message_time > GameTime64 ) ) {
2006-03-20 17:12:09 +00:00
buddy_message ( " Coming back to get you. " ) ;
2010-12-22 00:17:59 +00:00
Last_come_back_message_time = GameTime64 ;
2006-03-20 17:12:09 +00:00
}
}
// No point in Buddy creating very long path if he's not allowed to talk. Really kills framerate.
max_len = Max_escort_length ;
if ( ! Buddy_allowed_to_talk )
max_len = 3 ;
2018-06-20 04:16:06 +00:00
create_path_to_player ( objp , max_len , create_path_safety_flag : : safe ) ; // MK!: Last parm used to be 1!
2006-03-20 17:12:09 +00:00
aip - > path_length = polish_path ( objp , & Point_segs [ aip - > hide_index ] , aip - > path_length ) ;
2015-04-26 20:15:51 +00:00
ailp - > mode = ai_mode : : AIM_GOTO_PLAYER ;
2010-12-22 00:17:59 +00:00
} else if ( GameTime64 - Buddy_last_seen_player > MAX_ESCORT_TIME_AWAY ) {
2006-03-20 17:12:09 +00:00
// This is to prevent buddy from looking for a goal, which he will do because we only allow path creation once/second.
return ;
2015-04-26 20:15:51 +00:00
} else if ( ( ailp - > mode = = ai_mode : : AIM_GOTO_PLAYER ) & & ( dist_to_player < MIN_ESCORT_DISTANCE ) ) {
2016-10-28 03:39:41 +00:00
Escort_goal_object = escort_set_goal_object ( player_info . powerup_flags ) ;
2015-04-26 20:15:51 +00:00
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
2016-10-28 03:39:41 +00:00
escort_create_path_to_goal ( objp , player_info ) ;
2006-03-20 17:12:09 +00:00
aip - > path_length = polish_path ( objp , & Point_segs [ aip - > hide_index ] , aip - > path_length ) ;
if ( aip - > path_length < 3 ) {
create_n_segment_path ( objp , 5 , Believed_player_seg ) ;
}
2015-04-26 20:15:51 +00:00
ailp - > mode = ai_mode : : AIM_GOTO_OBJECT ;
2006-03-20 17:12:09 +00:00
} else if ( Escort_goal_object = = ESCORT_GOAL_UNSPECIFIED ) {
2015-04-26 20:15:51 +00:00
if ( ( ailp - > mode ! = ai_mode : : AIM_GOTO_PLAYER ) | | ( dist_to_player < MIN_ESCORT_DISTANCE ) ) {
2016-10-28 03:39:41 +00:00
Escort_goal_object = escort_set_goal_object ( player_info . powerup_flags ) ;
2015-04-26 20:15:51 +00:00
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
2016-10-28 03:39:41 +00:00
escort_create_path_to_goal ( objp , player_info ) ;
2006-03-20 17:12:09 +00:00
aip - > path_length = polish_path ( objp , & Point_segs [ aip - > hide_index ] , aip - > path_length ) ;
if ( aip - > path_length < 3 ) {
create_n_segment_path ( objp , 5 , Believed_player_seg ) ;
}
2015-04-26 20:15:51 +00:00
ailp - > mode = ai_mode : : AIM_GOTO_OBJECT ;
2006-03-20 17:12:09 +00:00
}
2008-04-06 20:23:28 +00:00
}
2006-03-20 17:12:09 +00:00
}
void invalidate_escort_goal ( void )
{
2015-06-13 22:42:15 +00:00
Escort_goal_object = ESCORT_GOAL_UNSPECIFIED ;
2006-03-20 17:12:09 +00:00
}
// -------------------------------------------------------------------------------------------------
2017-06-10 03:31:02 +00:00
void do_snipe_frame ( const vmobjptridx_t objp , fix dist_to_player , int player_visibility , const vms_vector & vec_to_player )
2006-03-20 17:12:09 +00:00
{
2013-12-25 03:16:41 +00:00
ai_local * ailp = & objp - > ctype . ai_info . ail ;
2006-03-20 17:12:09 +00:00
fix connected_distance ;
if ( dist_to_player > F1_0 * 500 )
return ;
switch ( ailp - > mode ) {
2015-04-26 20:15:51 +00:00
case ai_mode : : AIM_SNIPE_WAIT :
2006-03-20 17:12:09 +00:00
if ( ( dist_to_player > F1_0 * 50 ) & & ( ailp - > next_action_time > 0 ) )
return ;
ailp - > next_action_time = SNIPE_WAIT_TIME ;
2017-06-10 03:31:02 +00:00
connected_distance = find_connected_distance ( objp - > pos , vmsegptridx ( objp - > segnum ) , Believed_player_pos , vmsegptridx ( Believed_player_seg ) , 30 , WID_FLY_FLAG ) ;
2006-03-20 17:12:09 +00:00
if ( connected_distance < F1_0 * 500 ) {
2018-06-20 04:16:06 +00:00
create_path_to_player ( objp , 30 , create_path_safety_flag : : safe ) ;
2015-04-26 20:15:51 +00:00
ailp - > mode = ai_mode : : AIM_SNIPE_ATTACK ;
2006-03-20 17:12:09 +00:00
ailp - > next_action_time = SNIPE_ATTACK_TIME ; // have up to 10 seconds to find player.
}
break ;
2015-04-26 20:15:51 +00:00
case ai_mode : : AIM_SNIPE_RETREAT :
case ai_mode : : AIM_SNIPE_RETREAT_BACKWARDS :
2006-03-20 17:12:09 +00:00
if ( ailp - > next_action_time < 0 ) {
2015-04-26 20:15:51 +00:00
ailp - > mode = ai_mode : : AIM_SNIPE_WAIT ;
2006-03-20 17:12:09 +00:00
ailp - > next_action_time = SNIPE_WAIT_TIME ;
} else if ( ( player_visibility = = 0 ) | | ( ailp - > next_action_time > SNIPE_ABORT_RETREAT_TIME ) ) {
2014-10-28 01:45:53 +00:00
ai_follow_path ( objp , player_visibility , & vec_to_player ) ;
2015-04-26 20:15:51 +00:00
ailp - > mode = ai_mode : : AIM_SNIPE_RETREAT_BACKWARDS ;
2006-03-20 17:12:09 +00:00
} else {
2015-04-26 20:15:51 +00:00
ailp - > mode = ai_mode : : AIM_SNIPE_FIRE ;
2006-03-20 17:12:09 +00:00
ailp - > next_action_time = SNIPE_FIRE_TIME / 2 ;
}
break ;
2015-04-26 20:15:51 +00:00
case ai_mode : : AIM_SNIPE_ATTACK :
2006-03-20 17:12:09 +00:00
if ( ailp - > next_action_time < 0 ) {
2015-04-26 20:15:51 +00:00
ailp - > mode = ai_mode : : AIM_SNIPE_RETREAT ;
2006-03-20 17:12:09 +00:00
ailp - > next_action_time = SNIPE_WAIT_TIME ;
} else {
2014-10-28 01:45:53 +00:00
ai_follow_path ( objp , player_visibility , & vec_to_player ) ;
2006-03-20 17:12:09 +00:00
if ( player_visibility ) {
2015-04-26 20:15:51 +00:00
ailp - > mode = ai_mode : : AIM_SNIPE_FIRE ;
2006-03-20 17:12:09 +00:00
ailp - > next_action_time = SNIPE_FIRE_TIME ;
} else
2015-04-26 20:15:51 +00:00
ailp - > mode = ai_mode : : AIM_SNIPE_ATTACK ;
2006-03-20 17:12:09 +00:00
}
break ;
2015-04-26 20:15:51 +00:00
case ai_mode : : AIM_SNIPE_FIRE :
2006-03-20 17:12:09 +00:00
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 )
2015-04-26 20:15:51 +00:00
ailp - > mode = ai_mode : : AIM_SNIPE_RETREAT_BACKWARDS ;
2006-03-20 17:12:09 +00:00
else
2015-04-26 20:15:51 +00:00
ailp - > mode = ai_mode : : AIM_SNIPE_RETREAT ;
2006-03-20 17:12:09 +00:00
ailp - > next_action_time = SNIPE_RETREAT_TIME ;
} else {
}
break ;
default :
Int3 ( ) ; // Oops, illegal mode for snipe behavior.
2015-04-26 20:15:51 +00:00
ailp - > mode = ai_mode : : AIM_SNIPE_ATTACK ;
2006-03-20 17:12:09 +00:00
ailp - > next_action_time = F1_0 ;
break ;
}
}
# define THIEF_DEPTH 20
// ------------------------------------------------------------------------------------------------------
// Choose segment to recreate thief in.
2017-06-10 03:31:02 +00:00
static vmsegidx_t choose_thief_recreation_segment ( const vcsegidx_t plrseg )
2006-03-20 17:12:09 +00:00
{
2013-12-29 04:28:07 +00:00
segnum_t segnum = segment_none ;
2006-03-20 17:12:09 +00:00
int cur_drop_depth ;
cur_drop_depth = THIEF_DEPTH ;
2013-12-26 22:21:16 +00:00
while ( ( segnum = = segment_none ) & & ( cur_drop_depth > THIEF_DEPTH / 2 ) ) {
2016-10-02 00:34:41 +00:00
segnum = pick_connected_segment ( plrseg , cur_drop_depth ) ;
2015-09-29 02:41:22 +00:00
if ( segnum ! = segment_none & & vcsegptr ( segnum ) - > special = = SEGMENT_IS_CONTROLCEN )
2013-12-26 22:21:16 +00:00
segnum = segment_none ;
2006-03-20 17:12:09 +00:00
cur_drop_depth - - ;
}
2013-12-26 22:21:16 +00:00
if ( segnum = = segment_none ) {
2006-03-20 17:12:09 +00:00
return ( d_rand ( ) * Highest_segment_index ) > > 15 ;
} else
return segnum ;
}
2013-11-28 00:23:40 +00:00
static fix64 Re_init_thief_time = 0x3f000000 ;
2006-03-20 17:12:09 +00:00
// ----------------------------------------------------------------------
2016-04-06 03:34:15 +00:00
void recreate_thief ( const uint8_t thief_id )
2006-03-20 17:12:09 +00:00
{
2016-10-02 00:34:41 +00:00
const auto segnum = choose_thief_recreation_segment ( ConsoleObject - > segnum ) ;
2017-06-10 03:31:02 +00:00
const auto & & segp = vmsegptridx ( segnum ) ;
2018-03-12 03:43:47 +00:00
const auto & & center_point = compute_segment_center ( vcvertptr , segp ) ;
2006-03-20 17:12:09 +00:00
2016-04-06 03:34:15 +00:00
const auto & & new_obj = create_morph_robot ( segp , center_point , thief_id ) ;
2014-09-13 22:01:17 +00:00
if ( new_obj = = object_none )
return ;
2010-12-22 00:17:59 +00:00
Re_init_thief_time = GameTime64 + F1_0 * 10 ; // In 10 seconds, re-initialize thief.
2006-03-20 17:12:09 +00:00
}
// ----------------------------------------------------------------------------
# define THIEF_ATTACK_TIME (F1_0*10)
2016-07-16 16:52:04 +00:00
constexpr array < fix , NDL > Thief_wait_times = { {
F1_0 * 30 , F1_0 * 25 , F1_0 * 20 , F1_0 * 15 , F1_0 * 10
} } ;
2006-03-20 17:12:09 +00:00
// -------------------------------------------------------------------------------------------------
2017-06-10 03:31:02 +00:00
void do_thief_frame ( const vmobjptridx_t objp , fix dist_to_player , int player_visibility , const vms_vector & vec_to_player )
2006-03-20 17:12:09 +00:00
{
2013-12-25 03:16:41 +00:00
ai_local * ailp = & objp - > ctype . ai_info . ail ;
2006-03-20 17:12:09 +00:00
fix connected_distance ;
2010-12-22 00:17:59 +00:00
if ( ( Current_level_num < 0 ) & & ( Re_init_thief_time < GameTime64 ) ) {
if ( Re_init_thief_time > GameTime64 - F1_0 * 2 )
2006-03-20 17:12:09 +00:00
init_thief_for_level ( ) ;
Re_init_thief_time = 0x3f000000 ;
}
if ( ( dist_to_player > F1_0 * 500 ) & & ( ailp - > next_action_time > 0 ) )
return ;
2015-12-15 04:09:35 +00:00
if ( Player_dead_state ! = player_dead_state : : no )
2015-04-26 20:15:51 +00:00
ailp - > mode = ai_mode : : AIM_THIEF_RETREAT ;
2006-03-20 17:12:09 +00:00
switch ( ailp - > mode ) {
2015-04-26 20:15:51 +00:00
case ai_mode : : AIM_THIEF_WAIT :
2015-04-02 02:36:53 +00:00
if ( ailp - > player_awareness_type > = player_awareness_type_t : : PA_PLAYER_COLLISION ) {
ailp - > player_awareness_type = player_awareness_type_t : : PA_NONE ;
2018-06-20 04:16:06 +00:00
create_path_to_player ( objp , 30 , create_path_safety_flag : : safe ) ;
2015-04-26 20:15:51 +00:00
ailp - > mode = ai_mode : : AIM_THIEF_ATTACK ;
2006-03-20 17:12:09 +00:00
ailp - > next_action_time = THIEF_ATTACK_TIME / 2 ;
return ;
} else if ( player_visibility ) {
create_n_segment_path ( objp , 15 , ConsoleObject - > segnum ) ;
2015-04-26 20:15:51 +00:00
ailp - > mode = ai_mode : : AIM_THIEF_RETREAT ;
2006-03-20 17:12:09 +00:00
return ;
}
if ( ( dist_to_player > F1_0 * 50 ) & & ( ailp - > next_action_time > 0 ) )
return ;
ailp - > next_action_time = Thief_wait_times [ Difficulty_level ] / 2 ;
2017-06-10 03:31:02 +00:00
connected_distance = find_connected_distance ( objp - > pos , vmsegptridx ( objp - > segnum ) , Believed_player_pos , vmsegptridx ( Believed_player_seg ) , 30 , WID_FLY_FLAG ) ;
2006-03-20 17:12:09 +00:00
if ( connected_distance < F1_0 * 500 ) {
2018-06-20 04:16:06 +00:00
create_path_to_player ( objp , 30 , create_path_safety_flag : : safe ) ;
2015-04-26 20:15:51 +00:00
ailp - > mode = ai_mode : : AIM_THIEF_ATTACK ;
2006-03-20 17:12:09 +00:00
ailp - > next_action_time = THIEF_ATTACK_TIME ; // have up to 10 seconds to find player.
}
break ;
2015-04-26 20:15:51 +00:00
case ai_mode : : AIM_THIEF_RETREAT :
2006-03-20 17:12:09 +00:00
if ( ailp - > next_action_time < 0 ) {
2015-04-26 20:15:51 +00:00
ailp - > mode = ai_mode : : AIM_THIEF_WAIT ;
2006-03-20 17:12:09 +00:00
ailp - > next_action_time = Thief_wait_times [ Difficulty_level ] ;
2015-04-02 02:36:53 +00:00
} else if ( ( dist_to_player < F1_0 * 100 ) | | player_visibility | | ( ailp - > player_awareness_type > = player_awareness_type_t : : PA_PLAYER_COLLISION ) ) {
2014-10-02 03:02:36 +00:00
ai_follow_path ( objp , player_visibility , & vec_to_player ) ;
2015-04-02 02:36:53 +00:00
if ( ( dist_to_player < F1_0 * 100 ) | | ( ailp - > player_awareness_type > = player_awareness_type_t : : PA_PLAYER_COLLISION ) ) {
2006-03-20 17:12:09 +00:00
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 ) ) ) {
2015-04-02 02:36:53 +00:00
ailp - > player_awareness_type = player_awareness_type_t : : PA_NONE ;
2006-03-20 17:12:09 +00:00
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 ) {
2013-12-26 22:21:16 +00:00
create_n_segment_path ( objp , 10 , segment_none ) ;
2013-10-07 23:52:33 +00:00
} else if ( objp - > shields * 4 < Robot_info [ get_robot_id ( objp ) ] . strength ) {
2006-03-20 17:12:09 +00:00
// If robot really low on hits, will run through player with even longer path
if ( aip - > path_length < 8 ) {
2013-12-26 22:21:16 +00:00
create_n_segment_path ( objp , 10 , segment_none ) ;
2006-03-20 17:12:09 +00:00
}
}
2015-04-26 20:15:51 +00:00
ailp - > mode = ai_mode : : AIM_THIEF_RETREAT ;
2006-03-20 17:12:09 +00:00
}
} else
2015-04-26 20:15:51 +00:00
ailp - > mode = ai_mode : : AIM_THIEF_RETREAT ;
2006-03-20 17:12:09 +00:00
}
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.
2015-04-26 20:15:51 +00:00
case ai_mode : : AIM_THIEF_ATTACK :
2015-04-02 02:36:53 +00:00
if ( ailp - > player_awareness_type > = player_awareness_type_t : : PA_PLAYER_COLLISION ) {
ailp - > player_awareness_type = player_awareness_type_t : : PA_NONE ;
2006-03-20 17:12:09 +00:00
if ( d_rand ( ) > 8192 ) {
create_n_segment_path ( objp , 10 , ConsoleObject - > segnum ) ;
2013-12-25 03:16:41 +00:00
ailp - > next_action_time = Thief_wait_times [ Difficulty_level ] / 2 ;
2015-04-26 20:15:51 +00:00
ailp - > mode = ai_mode : : AIM_THIEF_RETREAT ;
2006-03-20 17:12:09 +00:00
}
} else if ( ailp - > next_action_time < 0 ) {
// This forces him to create a new path every second.
ailp - > next_action_time = F1_0 ;
2018-06-20 04:16:06 +00:00
create_path_to_player ( objp , 100 , create_path_safety_flag : : unsafe ) ;
2015-04-26 20:15:51 +00:00
ailp - > mode = ai_mode : : AIM_THIEF_ATTACK ;
2006-03-20 17:12:09 +00:00
} else {
if ( 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 ) {
2014-10-02 03:02:36 +00:00
fix dot = vm_vec_dot ( vec_to_player , ConsoleObject - > orient . fvec ) ;
2006-03-20 17:12:09 +00:00
if ( dot < - F1_0 / 2 ) { // Looking at least towards thief, so thief will run!
create_n_segment_path ( objp , 10 , ConsoleObject - > segnum ) ;
2013-12-25 03:16:41 +00:00
ailp - > next_action_time = Thief_wait_times [ Difficulty_level ] / 2 ;
2015-04-26 20:15:51 +00:00
ailp - > mode = ai_mode : : AIM_THIEF_RETREAT ;
2006-03-20 17:12:09 +00:00
}
}
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.
2012-05-14 17:06:28 +00:00
if ( ( aip - > path_length > 1 ) | | ( ( d_tick_count & 0x0f ) = = 0 ) ) {
2014-10-02 03:02:36 +00:00
ai_follow_path ( objp , player_visibility , & vec_to_player ) ;
2015-04-26 20:15:51 +00:00
ailp - > mode = ai_mode : : AIM_THIEF_ATTACK ;
2006-03-20 17:12:09 +00:00
}
}
}
break ;
default :
2015-04-26 20:15:51 +00:00
ailp - > mode = ai_mode : : AIM_THIEF_ATTACK ;
2006-03-20 17:12:09 +00:00
ailp - > next_action_time = F1_0 ;
break ;
}
}
// ----------------------------------------------------------------------------
// Return true if this item (whose presence is indicated by Players[player_num].flags) gets stolen.
2017-06-10 03:31:02 +00:00
static int maybe_steal_flag_item ( const vmobjptr_t playerobjp , const PLAYER_FLAG flagval )
2006-03-20 17:12:09 +00:00
{
2015-11-07 21:55:59 +00:00
auto & plr_flags = playerobjp - > ctype . player_info . powerup_flags ;
2015-10-10 03:44:14 +00:00
if ( plr_flags & flagval )
{
2006-03-20 17:12:09 +00:00
if ( d_rand ( ) < THIEF_PROBABILITY ) {
2015-09-26 21:17:13 +00:00
int powerup_index ;
const char * msg ;
2015-10-10 03:44:14 +00:00
plr_flags & = ( ~ flagval ) ;
2006-03-20 17:12:09 +00:00
switch ( flagval ) {
case PLAYER_FLAGS_INVULNERABLE :
powerup_index = POW_INVULNERABILITY ;
2015-09-26 21:17:13 +00:00
msg = " Invulnerability stolen! " ;
2006-03-20 17:12:09 +00:00
break ;
case PLAYER_FLAGS_CLOAKED :
powerup_index = POW_CLOAK ;
2015-09-26 21:17:13 +00:00
msg = " Cloak stolen! " ;
2006-03-20 17:12:09 +00:00
break ;
case PLAYER_FLAGS_MAP_ALL :
powerup_index = POW_FULL_MAP ;
2015-09-26 21:17:13 +00:00
msg = " Full map stolen! " ;
2006-03-20 17:12:09 +00:00
break ;
case PLAYER_FLAGS_QUAD_LASERS :
powerup_index = POW_QUAD_FIRE ;
2015-09-26 21:17:13 +00:00
msg = " Quad lasers stolen! " ;
2006-03-20 17:12:09 +00:00
break ;
case PLAYER_FLAGS_AFTERBURNER :
powerup_index = POW_AFTERBURNER ;
2015-09-26 21:17:13 +00:00
msg = " Afterburner stolen! " ;
2006-03-20 17:12:09 +00:00
break ;
case PLAYER_FLAGS_CONVERTER :
powerup_index = POW_CONVERTER ;
2015-09-26 21:17:13 +00:00
msg = " Converter stolen! " ;
2006-03-20 17:12:09 +00:00
break ;
2015-10-30 02:52:55 +00:00
case PLAYER_FLAG : : HEADLIGHT_PRESENT_AND_ON :
2006-03-20 17:12:09 +00:00
powerup_index = POW_HEADLIGHT ;
2015-09-26 21:17:13 +00:00
msg = " Headlight stolen! " ;
2006-03-20 17:12:09 +00:00
break ;
2015-09-26 21:17:13 +00:00
default :
assert ( false ) ;
return 0 ;
2006-03-20 17:12:09 +00:00
}
Stolen_items [ Stolen_item_index ] = powerup_index ;
2015-09-26 21:17:13 +00:00
thief_message_str ( msg ) ;
2006-03-20 17:12:09 +00:00
return 1 ;
}
}
return 0 ;
}
// ----------------------------------------------------------------------------
2017-06-10 03:31:02 +00:00
static int maybe_steal_secondary_weapon ( const vmobjptr_t playerobjp , int weapon_num )
2006-03-20 17:12:09 +00:00
{
2016-10-02 00:34:43 +00:00
auto & player_info = playerobjp - > ctype . player_info ;
if ( auto & secondary_ammo = player_info . secondary_ammo [ weapon_num ] )
2006-03-20 17:12:09 +00:00
if ( d_rand ( ) < THIEF_PROBABILITY ) {
2013-09-21 22:01:38 +00:00
if ( weapon_index_is_player_bomb ( weapon_num ) )
{
2006-03-20 17:12:09 +00:00
if ( d_rand ( ) > 8192 ) // Come in groups of 4, only add 1/4 of time.
return 0 ;
2013-09-21 22:01:38 +00:00
}
2006-03-20 17:12:09 +00:00
// Smart mines and proxbombs don't get dropped because they only come in 4 packs.
2013-09-21 22:01:38 +00:00
else
2006-03-20 17:12:09 +00:00
Stolen_items [ Stolen_item_index ] = Secondary_weapon_to_powerup [ weapon_num ] ;
2012-07-01 01:14:25 +00:00
thief_message ( " %s stolen! " , SECONDARY_WEAPON_NAMES ( weapon_num ) ) ; // Danger! Danger! Use of literal! Danger!
2015-10-10 03:44:14 +00:00
if ( - - secondary_ammo = = 0 )
2016-10-02 00:34:43 +00:00
auto_select_secondary_weapon ( player_info ) ;
2006-03-20 17:12:09 +00:00
// -- compress_stolen_items();
return 1 ;
}
return 0 ;
}
// ----------------------------------------------------------------------------
2017-06-10 03:31:02 +00:00
static int maybe_steal_primary_weapon ( const vmobjptr_t playerobjp , int weapon_num )
2006-03-20 17:12:09 +00:00
{
2015-11-07 21:55:59 +00:00
auto & player_info = playerobjp - > ctype . player_info ;
Allow players to remove thief at level start
Commit f4b21088a039 ("Track vulcan ammo explicitly") fixed an original
retail bug that prevented the thief from stealing energy weapons,
because the thief could only steal weapons for which the player had ammo
and energy weapons never have ammo. This went unremarked for several
years, until a recent report of the new semantics as a game-breaking
regression because the thief is now "ridiculously potent".
Address this report, as well as an intermittently raised issue from
various users over time, by adding two new knobs to both the single
player "Gameplay" menu and the multiplayer setup screen: "Remove Thief
at level start" and "Prevent Thief Stealing Energy Weapons".
"Remove Thief" deletes the thief object during level load. It has no
impact on save games, and changing it after entering a level has no
effect on any thief already in the level.
"Prevent Thief Stealing" is checked at the moment of theft and, when
enabled, prevents stealing primary weapons other than Vulcan/Gauss.
This can be changed at will in single player and is immediately
effective. In multiplayer, this option can only be changed by the game
host in the pre-game setup.
For both knobs, there is one pair of checkboxes to control this as a
player preference, which applies in single player games. There is a
second pair of checkboxes in the multiplayer setup, which applies only
to multiplayer games. Therefore, in multiplayer, the host chooses thief
settings and all clients use the host's choice. The host may configure
the thief differently in multiplayer from how the host plays in single
player.
For users who wanted to remove the thief, no specific tally has been
kept for who requested it or when. Now that the code is being updated,
this is thrown in as an easy addition.
Reported-by: MegaDescent <http://forum.dxx-rebirth.com/showthread.php?tid=980> (for the thief stealing energy weapons as a game-breaking regression)
2017-08-26 19:47:52 +00:00
bool is_energy_weapon = true ;
2017-08-06 17:25:46 +00:00
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 ;
Allow players to remove thief at level start
Commit f4b21088a039 ("Track vulcan ammo explicitly") fixed an original
retail bug that prevented the thief from stealing energy weapons,
because the thief could only steal weapons for which the player had ammo
and energy weapons never have ammo. This went unremarked for several
years, until a recent report of the new semantics as a game-breaking
regression because the thief is now "ridiculously potent".
Address this report, as well as an intermittently raised issue from
various users over time, by adding two new knobs to both the single
player "Gameplay" menu and the multiplayer setup screen: "Remove Thief
at level start" and "Prevent Thief Stealing Energy Weapons".
"Remove Thief" deletes the thief object during level load. It has no
impact on save games, and changing it after entering a level has no
effect on any thief already in the level.
"Prevent Thief Stealing" is checked at the moment of theft and, when
enabled, prevents stealing primary weapons other than Vulcan/Gauss.
This can be changed at will in single player and is immediately
effective. In multiplayer, this option can only be changed by the game
host in the pre-game setup.
For both knobs, there is one pair of checkboxes to control this as a
player preference, which applies in single player games. There is a
second pair of checkboxes in the multiplayer setup, which applies only
to multiplayer games. Therefore, in multiplayer, the host chooses thief
settings and all clients use the host's choice. The host may configure
the thief differently in multiplayer from how the host plays in single
player.
For users who wanted to remove the thief, no specific tally has been
kept for who requested it or when. Now that the code is being updated,
this is thrown in as an easy addition.
Reported-by: MegaDescent <http://forum.dxx-rebirth.com/showthread.php?tid=980> (for the thief stealing energy weapons as a game-breaking regression)
2017-08-26 19:47:52 +00:00
is_energy_weapon = false ;
2017-08-06 17:25:46 +00:00
//-fallthrough
default :
if ( ! ( player_info . primary_weapon_flags & HAS_PRIMARY_FLAG ( weapon_num ) ) )
return 0 ;
break ;
}
Allow players to remove thief at level start
Commit f4b21088a039 ("Track vulcan ammo explicitly") fixed an original
retail bug that prevented the thief from stealing energy weapons,
because the thief could only steal weapons for which the player had ammo
and energy weapons never have ammo. This went unremarked for several
years, until a recent report of the new semantics as a game-breaking
regression because the thief is now "ridiculously potent".
Address this report, as well as an intermittently raised issue from
various users over time, by adding two new knobs to both the single
player "Gameplay" menu and the multiplayer setup screen: "Remove Thief
at level start" and "Prevent Thief Stealing Energy Weapons".
"Remove Thief" deletes the thief object during level load. It has no
impact on save games, and changing it after entering a level has no
effect on any thief already in the level.
"Prevent Thief Stealing" is checked at the moment of theft and, when
enabled, prevents stealing primary weapons other than Vulcan/Gauss.
This can be changed at will in single player and is immediately
effective. In multiplayer, this option can only be changed by the game
host in the pre-game setup.
For both knobs, there is one pair of checkboxes to control this as a
player preference, which applies in single player games. There is a
second pair of checkboxes in the multiplayer setup, which applies only
to multiplayer games. Therefore, in multiplayer, the host chooses thief
settings and all clients use the host's choice. The host may configure
the thief differently in multiplayer from how the host plays in single
player.
For users who wanted to remove the thief, no specific tally has been
kept for who requested it or when. Now that the code is being updated,
this is thrown in as an easy addition.
Reported-by: MegaDescent <http://forum.dxx-rebirth.com/showthread.php?tid=980> (for the thief stealing energy weapons as a game-breaking regression)
2017-08-26 19:47:52 +00:00
if ( is_energy_weapon )
{
if ( ( ( Game_mode & GM_MULTI )
? Netgame . ThiefModifierFlags
: PlayerCfg . ThiefModifierFlags ) & ThiefModifier : : NoEnergyWeapons )
return 0 ;
}
2015-10-10 03:44:14 +00:00
{
2006-03-20 17:12:09 +00:00
if ( d_rand ( ) < THIEF_PROBABILITY ) {
2015-10-18 21:01:18 +00:00
if ( weapon_num = = primary_weapon_index_t : : LASER_INDEX )
2015-10-10 03:44:14 +00:00
{
2017-08-06 17:25:46 +00:00
auto & laser_level = player_info . laser_level ;
2016-07-14 01:59:02 +00:00
if ( laser_level > MAX_LASER_LEVEL )
2015-10-10 03:44:14 +00:00
{
2006-03-20 17:12:09 +00:00
Stolen_items [ Stolen_item_index ] = POW_SUPER_LASER ;
} else {
Stolen_items [ Stolen_item_index ] = Primary_weapon_to_powerup [ weapon_num ] ;
}
2017-08-06 17:25:46 +00:00
/* 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 ;
2015-10-10 03:44:14 +00:00
- - laser_level ;
2017-08-06 17:25:46 +00:00
thief_message ( " %s level decreased to %u! " , PRIMARY_WEAPON_NAMES ( weapon_num ) , l ) ;
2015-10-10 03:44:14 +00:00
}
else
{
2015-11-07 21:55:59 +00:00
player_info . primary_weapon_flags & = ~ HAS_PRIMARY_FLAG ( weapon_num ) ;
2006-03-20 17:12:09 +00:00
Stolen_items [ Stolen_item_index ] = Primary_weapon_to_powerup [ weapon_num ] ;
2012-07-01 01:14:25 +00:00
thief_message ( " %s stolen! " , PRIMARY_WEAPON_NAMES ( weapon_num ) ) ; // Danger! Danger! Use of literal! Danger!
2016-10-02 00:34:43 +00:00
auto_select_primary_weapon ( player_info ) ;
2006-03-20 17:12:09 +00:00
}
2017-08-06 17:25:46 +00:00
return 1 ;
2006-03-20 17:12:09 +00:00
}
}
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.
2017-06-10 03:31:02 +00:00
static int attempt_to_steal_item_3 ( const vmobjptr_t objp , const vmobjptr_t player_num )
2006-03-20 17:12:09 +00:00
{
2013-12-25 03:16:41 +00:00
ai_local * ailp = & objp - > ctype . ai_info . ail ;
2015-04-26 20:15:51 +00:00
if ( ailp - > mode ! = ai_mode : : AIM_THIEF_ATTACK )
2006-03-20 17:12:09 +00:00
return 0 ;
// First, try to steal equipped items.
2017-02-26 00:00:01 +00:00
if ( auto r = maybe_steal_flag_item ( player_num , PLAYER_FLAGS_INVULNERABLE ) )
return r ;
2006-03-20 17:12:09 +00:00
// If primary weapon = laser, first try to rip away those nasty quad lasers!
2016-08-28 22:41:49 +00:00
const auto Primary_weapon = player_num - > ctype . player_info . Primary_weapon ;
2015-04-26 20:15:57 +00:00
if ( Primary_weapon = = primary_weapon_index_t : : LASER_INDEX )
2017-02-26 00:00:01 +00:00
if ( auto r = maybe_steal_flag_item ( player_num , PLAYER_FLAGS_QUAD_LASERS ) )
return r ;
2006-03-20 17:12:09 +00:00
// Makes it more likely to steal primary than secondary.
2014-09-26 02:42:16 +00:00
for ( int i = 0 ; i < 2 ; i + + )
2017-02-26 00:00:02 +00:00
if ( auto r = maybe_steal_primary_weapon ( player_num , Primary_weapon ) )
return r ;
2006-03-20 17:12:09 +00:00
2017-02-26 00:00:01 +00:00
if ( auto r = maybe_steal_secondary_weapon ( player_num , player_num - > ctype . player_info . Secondary_weapon ) )
return r ;
2006-03-20 17:12:09 +00:00
// See what the player has and try to snag something.
// Try best things first.
2017-02-26 00:00:01 +00:00
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 ;
2006-03-20 17:12:09 +00:00
// -- 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;
2017-02-26 00:00:01 +00:00
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 ;
2006-03-20 17:12:09 +00:00
2014-09-26 02:42:16 +00:00
for ( int i = MAX_SECONDARY_WEAPONS - 1 ; i > = 0 ; i - - ) {
2017-02-26 00:00:02 +00:00
if ( auto r = maybe_steal_primary_weapon ( player_num , i ) )
return r ;
2017-02-26 00:00:01 +00:00
if ( auto r = maybe_steal_secondary_weapon ( player_num , i ) )
return r ;
2006-03-20 17:12:09 +00:00
}
return 0 ;
}
// ----------------------------------------------------------------------------
2017-06-10 03:31:02 +00:00
static int attempt_to_steal_item_2 ( const vmobjptr_t objp , const vmobjptr_t player_num )
2006-03-20 17:12:09 +00:00
{
2017-08-06 17:25:46 +00:00
const auto rval = attempt_to_steal_item_3 ( objp , player_num ) ;
2006-03-20 17:12:09 +00:00
if ( rval ) {
2017-08-06 17:25:46 +00:00
digi_play_sample_once ( SOUND_WEAPON_STOLEN , F1_0 ) ;
2016-07-14 01:59:02 +00:00
auto i = Stolen_item_index ;
2006-03-20 17:12:09 +00:00
if ( d_rand ( ) > 20000 ) // Occasionally, boost the value again
2016-07-14 01:59:02 +00:00
+ + i ;
constexpr auto size = Stolen_items . size ( ) ;
if ( + + i > = size )
i - = size ;
Stolen_item_index = i ;
2006-03-20 17:12:09 +00:00
}
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.
2017-06-10 03:31:02 +00:00
int attempt_to_steal_item ( const vmobjptridx_t objp , const vmobjptr_t player_num )
2006-03-20 17:12:09 +00:00
{
int rval = 0 ;
if ( objp - > ctype . ai_info . dying_start_time )
return 0 ;
rval + = attempt_to_steal_item_2 ( objp , player_num ) ;
2014-09-26 02:42:16 +00:00
for ( int i = 0 ; i < 3 ; i + + ) {
2006-03-20 17:12:09 +00:00
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 ) ;
2013-12-25 03:16:41 +00:00
ai_local * ailp = & objp - > ctype . ai_info . ail ;
ailp - > next_action_time = Thief_wait_times [ Difficulty_level ] / 2 ;
2015-04-26 20:15:51 +00:00
ailp - > mode = ai_mode : : AIM_THIEF_RETREAT ;
2006-03-20 17:12:09 +00:00
if ( rval ) {
PALETTE_FLASH_ADD ( 30 , 15 , - 20 ) ;
update_laser_weapon_info ( ) ;
// digi_link_sound_to_pos( SOUND_NASTY_ROBOT_HIT_1, objp->segnum, 0, &objp->pos, 0 , DEFAULT_ROBOT_SOUND_VOLUME);
// I removed this to make the "steal sound" more obvious -AP
if ( Game_mode & GM_NETWORK )
multi_send_stolen_items ( ) ;
}
return rval ;
}
// --------------------------------------------------------------------------------------------------------------
// Indicate no items have been stolen.
void init_thief_for_level ( void )
{
2013-12-12 23:48:34 +00:00
Stolen_items . fill ( 255 ) ;
2006-03-20 17:12:09 +00:00
2016-07-10 04:11:34 +00:00
constexpr unsigned iterations = 3 ;
static_assert ( Stolen_items . size ( ) > = iterations * 2 , " Stolen_items too small " ) ; // Oops! Loop below will overwrite memory!
2006-03-20 17:12:09 +00:00
if ( ! ( Game_mode & GM_MULTI ) )
2016-07-10 04:11:34 +00:00
for ( unsigned i = 0 ; i < iterations ; i + + )
{
2006-03-20 17:12:09 +00:00
Stolen_items [ 2 * i ] = POW_SHIELD_BOOST ;
Stolen_items [ 2 * i + 1 ] = POW_ENERGY ;
}
Stolen_item_index = 0 ;
}
// --------------------------------------------------------------------------------------------------------------
2014-10-02 03:02:34 +00:00
void drop_stolen_items ( const vcobjptr_t objp )
2006-03-20 17:12:09 +00:00
{
2017-06-10 03:31:02 +00:00
const auto & & segp = vmsegptridx ( objp - > segnum ) ;
2015-01-12 00:26:03 +00:00
range_for ( auto & i , Stolen_items )
{
if ( i ! = 255 )
2013-12-12 23:48:34 +00:00
{
2018-10-21 00:24:07 +00:00
drop_powerup ( Vclip , exchange ( i , 255 ) , 1 , objp - > mtype . phys_info . velocity , objp - > pos , segp , true ) ;
2013-12-12 23:48:34 +00:00
}
2006-03-20 17:12:09 +00:00
}
}
// --------------------------------------------------------------------------------------------------------------
2015-08-12 03:11:46 +00:00
namespace {
2014-09-07 18:06:59 +00:00
struct escort_menu : ignore_window_pointer_t
2006-03-20 17:12:09 +00:00
{
2015-04-19 04:18:49 +00:00
array < char , 300 > msg ;
2016-10-29 23:16:15 +00:00
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 ) ;
2013-12-22 22:03:07 +00:00
} ;
2010-01-09 01:56:38 +00:00
2015-08-12 03:11:46 +00:00
}
2016-10-29 23:16:15 +00:00
window_event_result escort_menu : : event_key_command ( const d_event & event )
2010-01-09 01:56:38 +00:00
{
2016-10-29 23:16:15 +00:00
switch ( const auto key = event_key_get ( event ) )
{
2010-01-09 01:56:38 +00:00
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 :
Looking_for_marker = - 1 ;
Last_buddy_key = - 1 ;
set_escort_special_goal ( key ) ;
Last_buddy_key = - 1 ;
2014-08-06 02:10:49 +00:00
return window_event_result : : close ;
2010-01-09 01:56:38 +00:00
case KEY_ESC :
case KEY_ENTER :
2014-08-06 02:10:49 +00:00
return window_event_result : : close ;
2010-01-09 01:56:38 +00:00
case KEY_T : {
2015-01-12 00:26:03 +00:00
auto temp = exchange ( Buddy_messages_suppressed , 0 ) ;
buddy_message ( " Messages %s. " , temp ? " enabled " : " suppressed " ) ;
Buddy_messages_suppressed = ~ temp ;
2014-08-06 02:10:49 +00:00
return window_event_result : : close ;
2010-01-09 01:56:38 +00:00
}
default :
break ;
}
2014-08-06 02:10:49 +00:00
return window_event_result : : ignored ;
2010-01-20 11:04:09 +00:00
}
2016-10-29 23:16:15 +00:00
window_event_result escort_menu : : event_handler ( window * , const d_event & event , escort_menu * menu )
2010-01-20 11:04:09 +00:00
{
2014-10-04 21:47:13 +00:00
switch ( event . type )
2010-01-20 11:04:09 +00:00
{
2010-02-25 08:00:15 +00:00
case EVENT_WINDOW_ACTIVATED :
game_flush_inputs ( ) ;
break ;
2010-03-26 14:05:40 +00:00
case EVENT_KEY_COMMAND :
2016-10-29 23:16:15 +00:00
return event_key_command ( event ) ;
2010-01-20 11:04:09 +00:00
case EVENT_IDLE :
2010-03-26 14:05:40 +00:00
timer_delay2 ( 50 ) ;
2010-01-20 11:04:09 +00:00
break ;
case EVENT_WINDOW_DRAW :
show_escort_menu ( menu - > msg ) ; //TXT_PAUSE);
break ;
case EVENT_WINDOW_CLOSE :
2015-02-28 22:34:07 +00:00
d_free ( menu ) ;
2014-08-06 02:10:49 +00:00
return window_event_result : : ignored ; // continue closing
2010-01-20 11:04:09 +00:00
default :
2014-08-06 02:10:49 +00:00
return window_event_result : : ignored ;
2010-01-20 11:04:09 +00:00
}
2014-08-06 02:10:49 +00:00
return window_event_result : : handled ;
2010-01-09 01:56:38 +00:00
}
void do_escort_menu ( void )
{
2006-03-20 17:12:09 +00:00
int next_goal ;
2018-08-26 18:10:36 +00:00
char goal_str [ 12 ] ;
2012-12-08 21:21:09 +00:00
const char * goal_txt ;
2012-12-08 21:18:08 +00:00
const char * tstr ;
2010-01-09 01:56:38 +00:00
escort_menu * menu ;
2006-03-20 17:12:09 +00:00
if ( Game_mode & GM_MULTI ) {
2013-06-23 16:27:34 +00:00
HUD_init_message_literal ( HM_DEFAULT , " No Guide-Bot in Multiplayer! " ) ;
2006-03-20 17:12:09 +00:00
return ;
}
2018-09-19 02:13:29 +00:00
const auto & & buddy = find_escort ( vmobjptridx , Robot_info ) ;
2015-01-12 00:26:04 +00:00
if ( buddy = = object_none )
{
HUD_init_message_literal ( HM_DEFAULT , " No Guide-Bot present in mine! " ) ;
return ;
2006-03-20 17:12:09 +00:00
}
ok_for_buddy_to_talk ( ) ; // Needed here or we might not know buddy can talk when he can.
if ( ! Buddy_allowed_to_talk ) {
2015-01-12 00:26:02 +00:00
HUD_init_message ( HM_DEFAULT , " %s has not been released. " , static_cast < const char * > ( PlayerCfg . GuidebotName ) ) ;
2006-03-20 17:12:09 +00:00
return ;
}
2010-01-09 01:56:38 +00:00
MALLOC ( menu , escort_menu , 1 ) ;
if ( ! menu )
return ;
// Just make it the full screen size and let show_escort_menu figure it out
2016-10-29 23:16:15 +00:00
const auto wind = window_create ( grd_curscreen - > sc_canvas , 0 , 0 , SWIDTH , SHEIGHT , & escort_menu : : event_handler , menu ) ;
2010-01-09 01:56:38 +00:00
if ( ! wind )
{
d_free ( menu ) ;
return ;
}
2016-10-02 00:34:41 +00:00
auto & plrobj = get_local_plrobj ( ) ;
2006-03-20 17:12:09 +00:00
// 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 ( Escort_special_goal = = ESCORT_GOAL_SCRAM ) {
2015-06-13 22:42:15 +00:00
Escort_special_goal = ESCORT_GOAL_UNSPECIFIED ; // Else setting next goal might fail.
2016-10-02 00:34:41 +00:00
next_goal = escort_set_goal_object ( plrobj . ctype . player_info . powerup_flags ) ;
2006-03-20 17:12:09 +00:00
Escort_special_goal = ESCORT_GOAL_SCRAM ;
} else {
2015-06-13 22:42:15 +00:00
Escort_special_goal = ESCORT_GOAL_UNSPECIFIED ; // Else setting next goal might fail.
2016-10-02 00:34:41 +00:00
next_goal = escort_set_goal_object ( plrobj . ctype . player_info . powerup_flags ) ;
2006-03-20 17:12:09 +00:00
}
switch ( next_goal ) {
2012-12-08 21:21:09 +00:00
default :
2006-03-20 17:12:09 +00:00
case ESCORT_GOAL_UNSPECIFIED :
Int3 ( ) ;
2012-12-08 21:21:09 +00:00
goal_txt = " ERROR " ;
2006-03-20 17:12:09 +00:00
break ;
case ESCORT_GOAL_BLUE_KEY :
2012-12-08 21:21:09 +00:00
goal_txt = " blue key " ;
2006-03-20 17:12:09 +00:00
break ;
case ESCORT_GOAL_GOLD_KEY :
2012-12-08 21:21:09 +00:00
goal_txt = " yellow key " ;
2006-03-20 17:12:09 +00:00
break ;
case ESCORT_GOAL_RED_KEY :
2012-12-08 21:21:09 +00:00
goal_txt = " red key " ;
2006-03-20 17:12:09 +00:00
break ;
case ESCORT_GOAL_CONTROLCEN :
2012-12-08 21:21:09 +00:00
goal_txt = " reactor " ;
2006-03-20 17:12:09 +00:00
break ;
case ESCORT_GOAL_BOSS :
2012-12-08 21:21:09 +00:00
goal_txt = " boss " ;
2006-03-20 17:12:09 +00:00
break ;
case ESCORT_GOAL_EXIT :
2012-12-08 21:21:09 +00:00
goal_txt = " exit " ;
2006-03-20 17:12:09 +00:00
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 :
2012-12-08 21:21:09 +00:00
goal_txt = goal_str ;
snprintf ( goal_str , sizeof ( goal_str ) , " marker %i " , next_goal - ESCORT_GOAL_MARKER1 + 1 ) ;
2006-03-20 17:12:09 +00:00
break ;
}
if ( ! Buddy_messages_suppressed )
2012-12-08 21:18:08 +00:00
tstr = " Suppress " ;
2006-03-20 17:12:09 +00:00
else
2012-12-08 21:18:08 +00:00
tstr = " Enable " ;
2006-03-20 17:12:09 +00:00
2015-04-19 04:18:49 +00:00
snprintf ( menu - > msg . data ( ) , menu - > msg . size ( ) , " Select Guide-Bot Command: \n \n \n "
2006-03-20 17:12:09 +00:00
" 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 "
2008-02-24 14:41:27 +00:00
" T. %s Messages "
2006-03-20 17:12:09 +00:00
// -- "9. Find the exit" CC_LSPACING_S "3\n"
2012-12-08 21:21:09 +00:00
, goal_txt , tstr ) ;
2006-03-20 17:12:09 +00:00
}
// -------------------------------------------------------------------------------
// Show the Buddy menu!
2015-04-19 04:18:49 +00:00
void show_escort_menu ( const array < char , 300 > & amsg )
2006-03-20 17:12:09 +00:00
{
2015-04-19 04:18:49 +00:00
const auto msg = amsg . data ( ) ;
2015-09-29 02:41:22 +00:00
int w , h ;
2006-03-20 17:12:09 +00:00
int x , y ;
2017-11-05 20:49:08 +00:00
gr_set_default_canvas ( ) ;
2006-03-20 17:12:09 +00:00
2017-02-11 21:42:37 +00:00
auto & canvas = * grd_curcanv ;
2018-05-19 23:21:42 +00:00
const auto & game_font = * GAME_FONT ;
gr_get_string_size ( game_font , msg , & w , & h , nullptr ) ;
2006-03-20 17:12:09 +00:00
2008-02-24 14:41:27 +00:00
x = ( SWIDTH - w ) / 2 ;
y = ( SHEIGHT - h ) / 2 ;
2006-03-20 17:12:09 +00:00
2017-02-11 21:42:37 +00:00
gr_set_fontcolor ( canvas , BM_XRGB ( 0 , 28 , 0 ) , - 1 ) ;
2006-03-20 17:12:09 +00:00
2017-04-08 16:48:20 +00:00
nm_draw_background ( canvas , x - BORDERX , y - BORDERY , x + w + BORDERX , y + h + BORDERY ) ;
2007-03-22 11:32:22 +00:00
2018-05-19 23:21:42 +00:00
gr_ustring ( canvas , game_font , x , y , msg ) ;
2006-03-20 17:12:09 +00:00
reset_cockpit ( ) ;
}
2015-12-22 04:18:50 +00:00
}