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 .
*/
/*
*
* Code for the control center
*
*/
# include <stdlib.h>
# include <stdio.h>
# if !defined(_WIN32) && !defined(macintosh)
# include <unistd.h>
# endif
# include "pstypes.h"
2012-07-07 18:35:06 +00:00
# include "dxxerror.h"
2006-03-20 17:12:09 +00:00
# include "inferno.h"
# include "cntrlcen.h"
# include "game.h"
# include "laser.h"
# include "gameseq.h"
# include "ai.h"
2015-07-25 23:10:45 +00:00
# include "player.h"
2006-03-20 17:12:09 +00:00
# include "multi.h"
2015-10-09 02:46:10 +00:00
# include "fwd-wall.h"
2014-12-22 04:35:49 +00:00
# include "segment.h"
2006-03-20 17:12:09 +00:00
# include "object.h"
# include "robot.h"
# include "vclip.h"
2014-01-18 18:02:02 +00:00
# include "physfs-serial.h"
2006-03-20 17:12:09 +00:00
# include "fireball.h"
# include "endlevel.h"
2007-05-21 11:24:27 +00:00
# include "state.h"
2013-03-03 19:41:09 +00:00
# include "args.h"
2018-12-13 02:31:38 +00:00
# include "wall.h"
2006-03-20 17:12:09 +00:00
2014-10-12 23:05:46 +00:00
# include "compiler-range_for.h"
2015-02-28 19:36:01 +00:00
# include "partial_range.h"
2014-10-12 23:05:46 +00:00
2015-12-22 04:18:50 +00:00
namespace dsx {
2015-02-28 19:36:01 +00:00
array < reactor , MAX_REACTORS > Reactors ;
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2015-02-28 19:36:01 +00:00
unsigned Num_reactors ;
2013-03-03 01:03:33 +00:00
# endif
2015-12-22 04:18:50 +00:00
}
2006-03-20 17:12:09 +00:00
control_center_triggers ControlCenterTriggers ;
2015-12-22 04:18:50 +00:00
namespace dsx {
2016-12-24 08:50:29 +00:00
static window_event_result do_countdown_frame ( ) ;
2006-03-20 17:12:09 +00:00
// -----------------------------------------------------------------------------
//return the position & orientation of a gun on the control center object
2016-04-23 17:59:47 +00:00
static void calc_controlcen_gun_point ( reactor & r , object & obj , const uint_fast32_t gun_num )
2006-03-20 17:12:09 +00:00
{
//instance gun position & orientation
2016-04-23 17:59:47 +00:00
auto & gun_point = obj . ctype . reactor_info . gun_pos [ gun_num ] ;
auto & gun_dir = obj . ctype . reactor_info . gun_dir [ gun_num ] ;
const auto & & m = vm_transposed_matrix ( obj . orient ) ;
2015-08-26 03:15:10 +00:00
vm_vec_rotate ( gun_point , r . gun_points [ gun_num ] , m ) ;
2016-04-23 17:59:47 +00:00
vm_vec_add2 ( gun_point , obj . pos ) ;
2015-08-26 03:15:10 +00:00
vm_vec_rotate ( gun_dir , r . gun_dirs [ gun_num ] , m ) ;
}
2016-04-23 17:59:47 +00:00
void calc_controlcen_gun_point ( object & obj )
2015-08-26 03:15:10 +00:00
{
2016-04-23 17:59:47 +00:00
assert ( obj . type = = OBJ_CNTRLCEN ) ;
assert ( obj . render_type = = RT_POLYOBJ ) ;
2015-08-26 03:15:10 +00:00
auto & reactor = get_reactor_definition ( get_reactor_id ( obj ) ) ;
for ( uint_fast32_t i = reactor . n_guns ; i - - ; )
calc_controlcen_gun_point ( reactor , obj , i ) ;
2006-03-20 17:12:09 +00:00
}
// -----------------------------------------------------------------------------
// Look at control center guns, find best one to fire at *objp.
// Return best gun number (one whose direction dotted with vector to player is largest).
// If best gun has negative dot, return -1, meaning no gun is good.
2018-06-24 05:06:15 +00:00
static int calc_best_gun ( const unsigned num_guns , const object & objreactor , const vms_vector & objpos )
2006-03-20 17:12:09 +00:00
{
int i ;
fix best_dot ;
int best_gun ;
2018-06-24 05:06:15 +00:00
auto & gun_pos = objreactor . ctype . reactor_info . gun_pos ;
auto & gun_dir = objreactor . ctype . reactor_info . gun_dir ;
2006-03-20 17:12:09 +00:00
best_dot = - F1_0 * 2 ;
best_gun = - 1 ;
for ( i = 0 ; i < num_guns ; i + + ) {
fix dot ;
2014-11-02 03:41:45 +00:00
const auto gun_vec = vm_vec_normalized_quick ( vm_vec_sub ( objpos , gun_pos [ i ] ) ) ;
dot = vm_vec_dot ( gun_dir [ i ] , gun_vec ) ;
2006-03-20 17:12:09 +00:00
if ( dot > best_dot ) {
best_dot = dot ;
best_gun = i ;
}
}
Assert ( best_gun ! = - 1 ) ; // Contact Mike. This is impossible. Or maybe you're getting an unnormalized vector somewhere.
if ( best_dot < 0 )
return - 1 ;
else
return best_gun ;
2015-12-22 04:18:50 +00:00
}
2006-03-20 17:12:09 +00:00
}
2015-12-22 04:18:50 +00:00
namespace dcx {
2016-07-16 16:52:04 +00:00
constexpr int D1_Alan_pavlish_reactor_times [ NDL ] = { 50 , 45 , 40 , 35 , 30 } ;
2015-12-22 04:18:50 +00:00
}
namespace dsx {
2013-12-17 05:07:35 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2016-07-16 16:52:04 +00:00
constexpr int D2_Alan_pavlish_reactor_times [ NDL ] = { 90 , 60 , 45 , 35 , 30 } ;
2013-03-03 01:03:33 +00:00
# endif
2006-03-20 17:12:09 +00:00
// -----------------------------------------------------------------------------
// Called every frame. If control center been destroyed, then actually do something.
2016-12-24 08:50:29 +00:00
window_event_result do_controlcen_dead_frame ( )
2006-03-20 17:12:09 +00:00
{
2019-08-15 01:34:22 +00:00
auto & LevelUniqueControlCenterState = LevelUniqueObjectState . ControlCenterState ;
2019-03-03 00:31:08 +00:00
auto & Objects = LevelUniqueObjectState . Objects ;
auto & vmobjptridx = Objects . vmptridx ;
2015-07-25 23:10:46 +00:00
if ( ( Game_mode & GM_MULTI ) & & ( get_local_player ( ) . connected ! = CONNECT_PLAYING ) ) // if out of level already there's no need for this
2016-12-24 08:50:29 +00:00
return window_event_result : : ignored ;
2012-05-25 10:14:28 +00:00
2019-08-15 01:34:22 +00:00
const auto Dead_controlcen_object_num = LevelUniqueControlCenterState . Dead_controlcen_object_num ;
2019-08-15 01:34:22 +00:00
if ( Dead_controlcen_object_num ! = object_none & & LevelUniqueControlCenterState . Countdown_seconds_left > 0 )
2006-03-20 17:12:09 +00:00
if ( d_rand ( ) < FrameTime * 4 )
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_I)
# define CC_FIREBALL_SCALE F1_0*3
# elif defined(DXX_BUILD_DESCENT_II)
# define CC_FIREBALL_SCALE F1_0
# endif
2017-06-10 03:31:02 +00:00
create_small_fireball_on_object ( vmobjptridx ( Dead_controlcen_object_num ) , CC_FIREBALL_SCALE , 1 ) ;
2006-03-20 17:12:09 +00:00
2019-08-15 01:34:22 +00:00
if ( LevelUniqueControlCenterState . Control_center_destroyed & & ! Endlevel_sequence )
2016-12-24 08:50:29 +00:00
return do_countdown_frame ( ) ;
return window_event_result : : ignored ;
2006-03-20 17:12:09 +00:00
}
# define COUNTDOWN_VOICE_TIME fl2f(12.75)
2016-12-24 08:50:29 +00:00
window_event_result do_countdown_frame ( )
2006-03-20 17:12:09 +00:00
{
2019-08-15 01:34:22 +00:00
auto & LevelUniqueControlCenterState = LevelUniqueObjectState . ControlCenterState ;
2006-03-20 17:12:09 +00:00
fix old_time ;
2019-08-15 01:34:22 +00:00
if ( ! LevelUniqueControlCenterState . Control_center_destroyed )
return window_event_result : : ignored ;
2006-03-20 17:12:09 +00:00
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
if ( ! is_D2_OEM & & ! is_MAC_SHARE & & ! is_SHAREWARE ) // get countdown in OEM and SHAREWARE only
{
// On last level, we don't want a countdown.
if ( PLAYING_BUILTIN_MISSION & & Current_level_num = = Last_level )
{
if ( ! ( Game_mode & GM_MULTI ) )
2016-12-24 08:50:29 +00:00
return window_event_result : : ignored ;
2006-03-20 17:12:09 +00:00
if ( Game_mode & GM_MULTI_ROBOTS )
2016-12-24 08:50:29 +00:00
return window_event_result : : ignored ;
2006-03-20 17:12:09 +00:00
}
}
2013-03-03 01:03:33 +00:00
# endif
2006-03-20 17:12:09 +00:00
// Control center destroyed, rock the player's ship.
2012-05-14 17:06:28 +00:00
if ( d_tick_step )
2008-09-30 00:07:07 +00:00
{
2019-06-27 03:26:20 +00:00
auto & rotvel = ConsoleObject - > mtype . phys_info . rotvel ;
2019-08-15 01:34:22 +00:00
const auto get_base_disturbance = [ fc = std : : min ( LevelUniqueControlCenterState . Countdown_seconds_left , 16 ) ] ( ) {
2019-06-27 03:26:20 +00:00
return fixmul ( d_rand ( ) - 16384 , 3 * F1_0 / 16 + ( F1_0 * ( 16 - fc ) ) / 32 ) ;
} ;
fix disturb_x = get_base_disturbance ( ) , disturb_z = get_base_disturbance ( ) ;
// At Trainee, decrease rocking of ship by 4x.
2019-06-27 03:26:20 +00:00
if ( GameUniqueState . Difficulty_level = = Difficulty_0 )
2019-06-27 03:26:20 +00:00
{
disturb_x / = 4 ;
disturb_z / = 4 ;
}
rotvel . x + = disturb_x ;
rotvel . z + = disturb_z ;
2008-09-30 00:07:07 +00:00
}
2006-03-20 17:12:09 +00:00
// Hook in the rumble sound effect here.
2019-08-15 01:34:22 +00:00
old_time = LevelUniqueControlCenterState . Countdown_timer ;
LevelUniqueControlCenterState . Countdown_timer - = FrameTime ;
const auto Countdown_timer = LevelUniqueControlCenterState . Countdown_timer ;
2019-08-15 01:34:22 +00:00
const auto Countdown_seconds_left = LevelUniqueControlCenterState . Countdown_seconds_left = f2i ( Countdown_timer + F1_0 * 7 / 8 ) ;
2006-03-20 17:12:09 +00:00
2019-08-15 01:34:22 +00:00
if ( old_time > COUNTDOWN_VOICE_TIME & & Countdown_timer < = COUNTDOWN_VOICE_TIME )
{
2006-03-20 17:12:09 +00:00
digi_play_sample ( SOUND_COUNTDOWN_13_SECS , F3_0 ) ;
}
2019-08-15 01:34:22 +00:00
if ( f2i ( old_time + F1_0 * 7 / 8 ) ! = Countdown_seconds_left )
{
if ( Countdown_seconds_left > = 0 & & Countdown_seconds_left < 10 )
digi_play_sample ( SOUND_COUNTDOWN_0_SECS + Countdown_seconds_left , F3_0 ) ;
2019-08-15 01:34:22 +00:00
if ( Countdown_seconds_left = = LevelUniqueControlCenterState . Total_countdown_time - 1 )
2006-03-20 17:12:09 +00:00
digi_play_sample ( SOUND_COUNTDOWN_29_SECS , F3_0 ) ;
}
if ( Countdown_timer > 0 ) {
fix size , old_size ;
2019-08-15 01:34:22 +00:00
const auto Total_countdown_time = LevelUniqueControlCenterState . Total_countdown_time ;
size = ( i2f ( Total_countdown_time ) - Countdown_timer ) / fl2f ( 0.65 ) ;
old_size = ( i2f ( Total_countdown_time ) - old_time ) / fl2f ( 0.65 ) ;
2019-08-15 01:34:22 +00:00
if ( size ! = old_size & & Countdown_seconds_left < Total_countdown_time - 5 )
{ // Every 2 seconds!
2006-03-20 17:12:09 +00:00
//@@if (Dead_controlcen_object_num != -1) {
//@@ vms_vector vp; //,v,c;
//@@ compute_segment_center(&vp, &Segments[Objects[Dead_controlcen_object_num].segnum]);
//@@ object_create_explosion( Objects[Dead_controlcen_object_num].segnum, &vp, size*10, VCLIP_SMALL_EXPLOSION);
//@@}
digi_play_sample ( SOUND_CONTROL_CENTER_WARNING_SIREN , F3_0 ) ;
}
} else {
int flash_value ;
if ( old_time > 0 )
digi_play_sample ( SOUND_MINE_BLEW_UP , F1_0 ) ;
flash_value = f2i ( - Countdown_timer * ( 64 / 4 ) ) ; // 4 seconds to total whiteness
PALETTE_FLASH_SET ( flash_value , flash_value , flash_value ) ;
if ( PaletteBlueAdd > 64 ) {
2017-11-05 20:49:08 +00:00
gr_set_default_canvas ( ) ;
2017-01-01 00:45:45 +00:00
gr_clear_canvas ( * grd_curcanv , BM_XRGB ( 31 , 31 , 31 ) ) ; //make screen all white to match palette effect
2006-03-20 17:12:09 +00:00
reset_palette_add ( ) ; //restore palette for death message
//controlcen->MaxCapacity = Fuelcen_max_amount;
//gauge_message( "Control Center Reset" );
2016-12-24 08:50:29 +00:00
return DoPlayerDead ( ) ; //kill_player();
2006-03-20 17:12:09 +00:00
}
}
2016-12-24 08:50:29 +00:00
return window_event_result : : handled ;
2006-03-20 17:12:09 +00:00
}
// -----------------------------------------------------------------------------
// Called when control center gets destroyed.
// This code is common to whether control center is implicitly imbedded in a boss,
// or is an object of its own.
// if objp == NULL that means the boss was the control center and don't set Dead_controlcen_object_num
2019-12-22 05:34:08 +00:00
void do_controlcen_destroyed_stuff ( const imobjidx_t objp )
2006-03-20 17:12:09 +00:00
{
2019-08-15 01:34:22 +00:00
auto & LevelUniqueControlCenterState = LevelUniqueObjectState . ControlCenterState ;
2011-09-26 23:31:19 +00:00
int i ;
2006-03-20 17:12:09 +00:00
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2019-08-15 01:34:22 +00:00
if ( ( Game_mode & GM_MULTI_ROBOTS ) & & LevelUniqueControlCenterState . Control_center_destroyed )
2011-09-26 23:31:19 +00:00
return ; // Don't allow resetting if control center and boss on same level
2013-03-03 01:03:33 +00:00
# endif
2006-03-20 17:12:09 +00:00
2018-12-30 00:43:58 +00:00
auto & Walls = LevelUniqueWallSubsystemState . Walls ;
auto & vmwallptr = Walls . vmptr ;
2006-03-20 17:12:09 +00:00
// Must toggle walls whether it is a boss or control center.
for ( i = 0 ; i < ControlCenterTriggers . num_links ; i + + )
2018-12-13 02:31:38 +00:00
wall_toggle ( vmwallptr , vmsegptridx ( ControlCenterTriggers . seg [ i ] ) , ControlCenterTriggers . side [ i ] ) ;
2006-03-20 17:12:09 +00:00
// And start the countdown stuff.
2019-08-15 01:34:22 +00:00
LevelUniqueControlCenterState . Control_center_destroyed = 1 ;
2006-03-20 17:12:09 +00:00
2019-06-27 03:26:20 +00:00
const auto Difficulty_level = GameUniqueState . Difficulty_level ;
2019-08-15 01:34:22 +00:00
int Total_countdown_time ;
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2011-09-26 23:31:19 +00:00
// If a secret level, delete secret.sgc to indicate that we can't return to our secret level.
if ( Current_level_num < 0 )
PHYSFS_delete ( SECRETC_FILENAME ) ;
2006-03-20 17:12:09 +00:00
2019-08-15 01:34:22 +00:00
const auto Base_control_center_explosion_time = LevelSharedControlCenterState . Base_control_center_explosion_time ;
2006-03-20 17:12:09 +00:00
if ( Base_control_center_explosion_time ! = DEFAULT_CONTROL_CENTER_EXPLOSION_TIME )
Total_countdown_time = Base_control_center_explosion_time + Base_control_center_explosion_time * ( NDL - Difficulty_level - 1 ) / 2 ;
2013-12-17 05:07:35 +00:00
else if ( ! EMULATING_D1 )
Total_countdown_time = D2_Alan_pavlish_reactor_times [ Difficulty_level ] ;
2006-03-20 17:12:09 +00:00
else
2013-03-03 01:03:33 +00:00
# endif
2013-12-17 05:07:35 +00:00
Total_countdown_time = D1_Alan_pavlish_reactor_times [ Difficulty_level ] ;
2006-03-20 17:12:09 +00:00
2019-08-15 01:34:22 +00:00
LevelUniqueControlCenterState . Total_countdown_time = Total_countdown_time ;
2019-08-15 01:34:22 +00:00
LevelUniqueControlCenterState . Countdown_timer = i2f ( Total_countdown_time ) ;
2006-03-20 17:12:09 +00:00
2019-08-15 01:34:22 +00:00
if ( ! LevelUniqueControlCenterState . Control_center_present | | objp = = object_none )
2006-03-20 17:12:09 +00:00
return ;
2019-08-15 01:34:22 +00:00
LevelUniqueControlCenterState . Dead_controlcen_object_num = objp ;
2006-03-20 17:12:09 +00:00
}
// -----------------------------------------------------------------------------
//do whatever this thing does in a frame
2017-06-10 03:31:02 +00:00
void do_controlcen_frame ( const vmobjptridx_t obj )
2006-03-20 17:12:09 +00:00
{
2019-08-15 01:34:22 +00:00
auto & LevelUniqueControlCenterState = LevelUniqueObjectState . ControlCenterState ;
2006-03-20 17:12:09 +00:00
int best_gun_num ;
2019-03-03 00:31:08 +00:00
auto & Objects = LevelUniqueObjectState . Objects ;
auto & vmobjptr = Objects . vmptr ;
2006-03-20 17:12:09 +00:00
// If a boss level, then Control_center_present will be 0.
2019-08-15 01:34:22 +00:00
if ( ! LevelUniqueControlCenterState . Control_center_present )
2006-03-20 17:12:09 +00:00
return ;
# ifndef NDEBUG
2011-02-14 21:27:07 +00:00
if ( cheats . robotfiringsuspended | | ( Game_suspended & SUSP_ROBOTS ) )
2006-03-20 17:12:09 +00:00
return ;
# else
2011-02-14 21:27:07 +00:00
if ( cheats . robotfiringsuspended )
2006-03-20 17:12:09 +00:00
return ;
# endif
2016-10-02 00:34:48 +00:00
auto & plrobj = get_local_plrobj ( ) ;
2019-08-15 01:34:22 +00:00
if ( ! ( LevelUniqueControlCenterState . Control_center_been_hit | | player_is_visible ( LevelUniqueControlCenterState . Control_center_player_been_seen ) ) )
2019-06-20 04:02:27 +00:00
{
2012-05-14 17:06:28 +00:00
if ( ! ( d_tick_count % 8 ) ) { // Do every so often...
2006-03-20 17:12:09 +00:00
// This is a hack. Since the control center is not processed by
// ai_do_frame, it doesn't know to deal with cloaked dudes. It
// seems to work in single-player mode because it is actually using
// the value of Believed_player_position that was set by the last
// person to go through ai_do_frame. But since a no-robots game
// never goes through ai_do_frame, I'm making it so the control
// center can spot cloaked dudes.
if ( Game_mode & GM_MULTI )
2016-10-02 00:34:48 +00:00
Believed_player_pos = plrobj . pos ;
2006-03-20 17:12:09 +00:00
// Hack for special control centers which are isolated and not reachable because the
// real control center is inside the boss.
2016-09-04 00:02:50 +00:00
auto & children = vcsegptr ( obj - > segnum ) - > children ;
if ( std : : none_of ( children . begin ( ) , children . end ( ) , IS_CHILD ) )
2006-03-20 17:12:09 +00:00
return ;
2014-10-29 03:24:31 +00:00
auto vec_to_player = vm_vec_sub ( ConsoleObject - > pos , obj - > pos ) ;
2015-03-12 02:21:19 +00:00
auto dist_to_player = vm_vec_normalize_quick ( vec_to_player ) ;
2006-03-20 17:12:09 +00:00
if ( dist_to_player < F1_0 * 200 ) {
2019-08-15 01:34:22 +00:00
LevelUniqueControlCenterState . Control_center_player_been_seen = player_is_visible_from_object ( obj , obj - > pos , 0 , vec_to_player ) ;
2019-08-15 01:34:22 +00:00
LevelUniqueControlCenterState . Frametime_until_next_fire = 0 ;
2006-03-20 17:12:09 +00:00
}
}
return ;
}
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
// Periodically, make the reactor fall asleep if player not visible.
2019-08-15 01:34:22 +00:00
if ( ! EMULATING_D1 & & ( LevelUniqueControlCenterState . Control_center_been_hit | | player_is_visible ( LevelUniqueControlCenterState . Control_center_player_been_seen ) ) )
2019-06-20 04:02:27 +00:00
{
2019-08-15 01:34:22 +00:00
if ( LevelUniqueControlCenterState . Last_time_cc_vis_check + F1_0 * 5 < GameTime64 | | LevelUniqueControlCenterState . Last_time_cc_vis_check > GameTime64 )
{
LevelUniqueControlCenterState . Last_time_cc_vis_check = GameTime64 ;
2006-03-20 17:12:09 +00:00
fix dist_to_player ;
2014-10-29 03:24:31 +00:00
auto vec_to_player = vm_vec_sub ( ConsoleObject - > pos , obj - > pos ) ;
2014-09-28 21:11:04 +00:00
dist_to_player = vm_vec_normalize_quick ( vec_to_player ) ;
2006-03-20 17:12:09 +00:00
if ( dist_to_player < F1_0 * 120 ) {
2019-08-15 01:34:22 +00:00
LevelUniqueControlCenterState . Control_center_player_been_seen = player_is_visible_from_object ( obj , obj - > pos , 0 , vec_to_player ) ;
if ( ! player_is_visible ( LevelUniqueControlCenterState . Control_center_player_been_seen ) )
2019-08-15 01:34:22 +00:00
LevelUniqueControlCenterState . Control_center_been_hit = 0 ;
2006-03-20 17:12:09 +00:00
}
}
}
2013-03-03 01:03:33 +00:00
# endif
2006-03-20 17:12:09 +00:00
2019-08-15 01:34:22 +00:00
constexpr fix Relative_frametime_cease_fire = F1_0 * 2 ;
auto & Frametime_since_player_died = LevelUniqueControlCenterState . Frametime_since_player_died ;
2015-12-15 04:09:35 +00:00
if ( Player_dead_state ! = player_dead_state : : no )
2019-08-15 01:34:22 +00:00
{
if ( Frametime_since_player_died < = Relative_frametime_cease_fire )
Frametime_since_player_died + = FrameTime ;
}
2008-04-13 13:52:58 +00:00
else
2019-08-15 01:34:22 +00:00
Frametime_since_player_died = 0 ;
2008-04-13 13:52:58 +00:00
2019-08-15 01:34:22 +00:00
if ( LevelUniqueControlCenterState . Frametime_until_next_fire < 0 & & ! ( Frametime_since_player_died > Relative_frametime_cease_fire ) )
2019-08-15 01:34:22 +00:00
{
2016-10-02 00:34:48 +00:00
auto & player_info = plrobj . ctype . player_info ;
2016-10-02 00:34:40 +00:00
const auto & player_pos = ( player_info . powerup_flags & PLAYER_FLAGS_CLOAKED ) ? Believed_player_pos : ConsoleObject - > pos ;
2015-08-26 03:15:10 +00:00
best_gun_num = calc_best_gun (
get_reactor_definition ( get_reactor_id ( obj ) ) . n_guns ,
obj ,
player_pos
) ;
2006-03-20 17:12:09 +00:00
if ( best_gun_num ! = - 1 ) {
fix delta_fire_time ;
2015-08-26 03:15:10 +00:00
auto vec_to_goal = vm_vec_sub ( player_pos , obj - > ctype . reactor_info . gun_pos [ best_gun_num ] ) ;
2015-03-12 02:21:19 +00:00
auto dist_to_player = vm_vec_normalize_quick ( vec_to_goal ) ;
2006-03-20 17:12:09 +00:00
if ( dist_to_player > F1_0 * 300 )
{
2019-08-15 01:34:22 +00:00
LevelUniqueControlCenterState . Control_center_been_hit = 0 ;
2019-08-15 01:34:22 +00:00
LevelUniqueControlCenterState . Control_center_player_been_seen = player_visibility_state : : no_line_of_sight ;
2006-03-20 17:12:09 +00:00
return ;
}
if ( Game_mode & GM_MULTI )
2014-10-30 03:37:13 +00:00
multi_send_controlcen_fire ( vec_to_goal , best_gun_num , obj ) ;
2015-12-03 03:26:49 +00:00
Laser_create_new_easy ( vec_to_goal , obj - > ctype . reactor_info . gun_pos [ best_gun_num ] , obj , weapon_id_type : : CONTROLCEN_WEAPON_NUM , 1 ) ;
2006-03-20 17:12:09 +00:00
2013-03-03 01:03:33 +00:00
int count = 0 ;
# if defined(DXX_BUILD_DESCENT_I)
const unsigned scale_divisor = 4 ;
if ( d_rand ( ) < 32767 / 4 )
# elif defined(DXX_BUILD_DESCENT_II)
const unsigned scale_divisor = 6 ;
int rand_prob ;
2006-03-20 17:12:09 +00:00
// some of time, based on level, fire another thing, not directly at player, so it might hit him if he's constantly moving.
rand_prob = F1_0 / ( abs ( Current_level_num ) / 4 + 2 ) ;
2013-03-03 01:03:33 +00:00
while ( ( d_rand ( ) > rand_prob ) & & ( count < 4 ) )
# endif
{
2016-07-24 04:04:25 +00:00
vm_vec_scale_add2 ( vec_to_goal , make_random_vector ( ) , F1_0 / scale_divisor ) ;
2014-09-28 21:11:04 +00:00
vm_vec_normalize_quick ( vec_to_goal ) ;
2006-03-20 17:12:09 +00:00
if ( Game_mode & GM_MULTI )
2014-10-30 03:37:13 +00:00
multi_send_controlcen_fire ( vec_to_goal , best_gun_num , obj ) ;
2015-12-03 03:26:49 +00:00
Laser_create_new_easy ( vec_to_goal , obj - > ctype . reactor_info . gun_pos [ best_gun_num ] , obj , weapon_id_type : : CONTROLCEN_WEAPON_NUM , count = = 0 ) ;
2006-03-20 17:12:09 +00:00
count + + ;
}
2019-06-27 03:26:20 +00:00
const auto Difficulty_level = GameUniqueState . Difficulty_level ;
2006-03-20 17:12:09 +00:00
delta_fire_time = ( NDL - Difficulty_level ) * F1_0 / 4 ;
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
if ( Difficulty_level = = 0 )
delta_fire_time + = F1_0 / 2 ;
2013-03-03 01:03:33 +00:00
# endif
2006-03-20 17:12:09 +00:00
if ( Game_mode & GM_MULTI ) // slow down rate of fire in multi player
delta_fire_time * = 2 ;
2019-08-15 01:34:22 +00:00
LevelUniqueControlCenterState . Frametime_until_next_fire = delta_fire_time ;
2006-03-20 17:12:09 +00:00
}
} else
2019-08-15 01:34:22 +00:00
LevelUniqueControlCenterState . Frametime_until_next_fire - = FrameTime ;
2006-03-20 17:12:09 +00:00
}
// -----------------------------------------------------------------------------
// This must be called at the start of each level.
// If this level contains a boss and mode != multiplayer, don't do control center stuff. (Ghost out control center object.)
// If this level contains a boss and mode == multiplayer, do control center stuff.
void init_controlcen_for_level ( void )
{
2019-08-15 01:34:22 +00:00
auto & LevelUniqueControlCenterState = LevelUniqueObjectState . ControlCenterState ;
2017-06-10 03:31:02 +00:00
imobjptr_t cntrlcen_objnum = nullptr , boss_objnum = nullptr ;
2006-03-20 17:12:09 +00:00
2019-03-03 00:31:08 +00:00
auto & Objects = LevelUniqueObjectState . Objects ;
auto & vmobjptridx = Objects . vmptridx ;
2018-12-30 00:43:59 +00:00
auto & Robot_info = LevelSharedRobotInfoState . Robot_info ;
2017-06-10 03:31:02 +00:00
range_for ( const auto & & objp , vmobjptridx )
2014-10-12 23:05:46 +00:00
{
2006-03-20 17:12:09 +00:00
if ( objp - > type = = OBJ_CNTRLCEN )
{
2015-12-03 03:26:49 +00:00
if ( cntrlcen_objnum = = nullptr )
2015-12-22 04:18:51 +00:00
cntrlcen_objnum = objp ;
2006-03-20 17:12:09 +00:00
}
2015-12-22 04:18:51 +00:00
else if ( objp - > type = = OBJ_ROBOT & & ( Robot_info [ get_robot_id ( objp ) ] . boss_flag ) )
{
2015-12-03 03:26:49 +00:00
if ( boss_objnum = = nullptr )
2015-12-22 04:18:51 +00:00
boss_objnum = objp ;
2006-03-20 17:12:09 +00:00
}
}
2015-12-03 03:26:49 +00:00
if ( boss_objnum ! = nullptr & & ! ( ( Game_mode & GM_MULTI ) & & ! ( Game_mode & GM_MULTI_ROBOTS ) ) )
{
if ( cntrlcen_objnum ! = nullptr )
{
2018-06-24 05:06:15 +00:00
auto & objp = * cntrlcen_objnum ;
objp . type = OBJ_GHOST ;
objp . control_type = CT_NONE ;
objp . render_type = RT_NONE ;
2019-08-15 01:34:22 +00:00
LevelUniqueControlCenterState . Control_center_present = 0 ;
2006-03-20 17:12:09 +00:00
}
2015-12-03 03:26:49 +00:00
}
else if ( cntrlcen_objnum ! = nullptr )
{
2006-03-20 17:12:09 +00:00
// Compute all gun positions.
2019-12-22 05:34:08 +00:00
object & objp = cntrlcen_objnum ;
2015-08-26 03:15:10 +00:00
calc_controlcen_gun_point ( objp ) ;
2019-08-15 01:34:22 +00:00
LevelUniqueControlCenterState . Control_center_present = 1 ;
2006-03-20 17:12:09 +00:00
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_I)
const unsigned secret_level_shield_multiplier = 100 ;
# elif defined(DXX_BUILD_DESCENT_II)
const unsigned secret_level_shield_multiplier = 150 ;
2019-08-15 01:34:22 +00:00
const auto Reactor_strength = LevelSharedControlCenterState . Reactor_strength ;
2013-03-03 01:03:33 +00:00
if ( Reactor_strength ! = - 1 )
2019-12-22 05:34:08 +00:00
objp . shields = i2f ( Reactor_strength ) ;
2013-03-03 01:03:33 +00:00
else
# endif
{ //use old defaults
2006-03-20 17:12:09 +00:00
// Boost control center strength at higher levels.
if ( Current_level_num > = 0 )
2019-12-22 05:34:08 +00:00
objp . shields = F1_0 * 200 + ( F1_0 * 200 / 4 ) * Current_level_num ;
2006-03-20 17:12:09 +00:00
else
2019-12-22 05:34:08 +00:00
objp . shields = F1_0 * 200 - Current_level_num * F1_0 * secret_level_shield_multiplier ;
2006-03-20 17:12:09 +00:00
}
}
// Say the control center has not yet been hit.
2019-08-15 01:34:22 +00:00
LevelUniqueControlCenterState . Control_center_been_hit = 0 ;
2019-08-15 01:34:22 +00:00
LevelUniqueControlCenterState . Control_center_player_been_seen = player_visibility_state : : no_line_of_sight ;
2019-08-15 01:34:22 +00:00
LevelUniqueControlCenterState . Frametime_until_next_fire = 0 ;
2019-08-15 01:34:22 +00:00
LevelUniqueControlCenterState . Dead_controlcen_object_num = object_none ;
2006-03-20 17:12:09 +00:00
}
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2015-12-22 04:18:50 +00:00
void special_reactor_stuff ( )
2006-03-20 17:12:09 +00:00
{
2019-08-15 01:34:22 +00:00
auto & LevelUniqueControlCenterState = LevelUniqueObjectState . ControlCenterState ;
if ( LevelUniqueControlCenterState . Control_center_destroyed ) {
2019-08-15 01:34:22 +00:00
const auto Base_control_center_explosion_time = LevelSharedControlCenterState . Base_control_center_explosion_time ;
2019-08-15 01:34:22 +00:00
LevelUniqueControlCenterState . Countdown_timer + = i2f ( Base_control_center_explosion_time + ( NDL - 1 - GameUniqueState . Difficulty_level ) * Base_control_center_explosion_time / ( NDL - 1 ) ) ;
LevelUniqueControlCenterState . Total_countdown_time = f2i ( LevelUniqueControlCenterState . Countdown_timer ) + 2 ; // Will prevent "Self destruct sequence activated" message from replaying.
2006-03-20 17:12:09 +00:00
}
}
/*
2016-01-09 16:38:14 +00:00
* reads n reactor structs from a PHYSFS_File
2006-03-20 17:12:09 +00:00
*/
2016-01-09 16:38:14 +00:00
void reactor_read_n ( PHYSFS_File * fp , partial_range_t < reactor * > r )
2006-03-20 17:12:09 +00:00
{
2015-02-28 19:36:01 +00:00
range_for ( auto & i , r )
{
i . model_num = PHYSFSX_readInt ( fp ) ;
i . n_guns = PHYSFSX_readInt ( fp ) ;
range_for ( auto & j , i . gun_points )
PHYSFSX_readVector ( fp , j ) ;
range_for ( auto & j , i . gun_dirs )
PHYSFSX_readVector ( fp , j ) ;
2006-03-20 17:12:09 +00:00
}
}
2013-03-03 01:03:33 +00:00
# endif
2015-12-22 04:18:50 +00:00
}
2006-03-20 17:12:09 +00:00
2014-01-18 18:02:02 +00:00
DEFINE_SERIAL_UDT_TO_MESSAGE ( control_center_triggers , cct , ( cct . num_links , cct . seg , cct . side ) ) ;
ASSERT_SERIAL_UDT_MESSAGE_SIZE ( control_center_triggers , 42 ) ;
2009-10-05 02:51:37 +00:00
/*
2016-01-09 16:38:14 +00:00
* reads n control_center_triggers structs from a PHYSFS_File and swaps if specified
2009-10-05 02:51:37 +00:00
*/
2016-01-09 16:38:14 +00:00
void control_center_triggers_read ( control_center_triggers * cct , PHYSFS_File * fp )
2009-10-05 02:51:37 +00:00
{
2014-01-18 18:02:02 +00:00
PHYSFSX_serialize_read ( fp , * cct ) ;
2009-10-05 02:51:37 +00:00
}
2016-01-09 16:38:14 +00:00
void control_center_triggers_write ( const control_center_triggers * cct , PHYSFS_File * fp )
2006-03-20 17:12:09 +00:00
{
2014-01-18 18:02:02 +00:00
PHYSFSX_serialize_write ( fp , * cct ) ;
2006-03-20 17:12:09 +00:00
}