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 .
*/
/*
*
* Routines for EndGame , EndLevel , etc .
*
*/
2016-09-24 18:06:10 +00:00
# include "dxxsconf.h"
2015-09-09 03:27:52 +00:00
# include <cctype>
2015-07-04 21:01:18 +00:00
# include <utility>
2006-03-20 17:12:09 +00:00
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# if !defined(_MSC_VER) && !defined(macintosh)
# include <unistd.h>
# endif
# include <time.h>
2016-09-24 18:06:11 +00:00
# if DXX_USE_OGL
2006-03-20 17:12:09 +00:00
# include "ogl_init.h"
# endif
# include "inferno.h"
# include "game.h"
# include "player.h"
# include "key.h"
# include "object.h"
2012-07-07 18:35:06 +00:00
# include "dxxerror.h"
2006-03-20 17:12:09 +00:00
# include "joy.h"
# include "timer.h"
# include "laser.h"
2015-10-09 02:46:10 +00:00
# include "event.h"
2006-03-20 17:12:09 +00:00
# include "screens.h"
# include "textures.h"
# include "gauges.h"
# include "3d.h"
# include "effects.h"
# include "menu.h"
# include "gameseg.h"
# include "wall.h"
# include "ai.h"
# include "fuelcen.h"
# include "switch.h"
# include "digi.h"
# include "gamesave.h"
# include "scores.h"
# include "u_mem.h"
# include "palette.h"
# include "morph.h"
# include "newdemo.h"
# include "titles.h"
# include "weapon.h"
# include "sounds.h"
# include "args.h"
# include "gameseq.h"
# include "gamefont.h"
# include "newmenu.h"
2015-04-19 04:18:51 +00:00
# include "hudmsg.h"
2006-03-20 17:12:09 +00:00
# include "endlevel.h"
2015-03-24 02:07:42 +00:00
# include "kmatrix.h"
2020-12-20 20:39:07 +00:00
# include "net_udp.h"
2006-03-20 17:12:09 +00:00
# include "playsave.h"
# include "fireball.h"
# include "kconfig.h"
# include "robot.h"
# include "automap.h"
# include "cntrlcen.h"
# include "powerup.h"
# include "text.h"
# include "piggy.h"
# include "mission.h"
# include "state.h"
# include "songs.h"
# include "gamepal.h"
# include "controls.h"
# include "credits.h"
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
# include "strutil.h"
2011-05-22 17:54:47 +00:00
# include "segment.h"
# include "gameseg.h"
2013-12-08 23:37:40 +00:00
# include "fmtcheck.h"
2006-03-20 17:12:09 +00:00
2014-10-12 23:05:46 +00:00
# include "compiler-range_for.h"
2020-08-10 03:45:14 +00:00
# include "d_levelstate.h"
2014-12-23 04:20:27 +00:00
# include "partial_range.h"
2019-05-04 18:27:36 +00:00
# include "d_range.h"
2021-09-04 12:17:14 +00:00
# include "d_underlying_value.h"
2020-07-05 23:34:33 +00:00
# include "d_zip.h"
2014-10-12 23:05:46 +00:00
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_I)
# include "custom.h"
# define GLITZ_BACKGROUND Menu_pcx_name
2013-11-10 03:31:22 +00:00
2013-03-03 01:03:33 +00:00
# elif defined(DXX_BUILD_DESCENT_II)
# include "movie.h"
# define GLITZ_BACKGROUND STARS_BACKGROUND
2006-08-30 18:12:04 +00:00
2015-12-13 18:00:49 +00:00
namespace dsx {
2020-12-26 21:17:29 +00:00
namespace {
2013-09-22 22:26:27 +00:00
static void StartNewLevelSecret ( int level_num , int page_in_textures ) ;
2017-07-26 03:15:59 +00:00
static void InitPlayerPosition ( fvmobjptridx & vmobjptridx , fvmsegptridx & vmsegptridx , int random_flag ) ;
2016-12-24 08:50:29 +00:00
static void DoEndGame ( ) ;
2020-12-26 21:17:29 +00:00
}
2015-12-13 18:00:48 +00:00
PHYSFSX_gets_line_t < FILENAME_LEN > Current_level_palette ;
int First_secret_visit = 1 ;
}
2013-03-03 01:03:33 +00:00
# endif
2016-03-19 19:08:11 +00:00
namespace {
class preserve_player_object_info
{
player_info plr_info ;
fix plr_shields ;
/* Cache the reference, not the value. This class is designed
* to be alive across a call to LoadLevel , which may change the
* value of the object number .
*/
const objnum_t & objnum ;
public :
2019-03-03 00:31:08 +00:00
preserve_player_object_info ( fvcobjptr & vcobjptr , const objnum_t & o ) :
2016-03-19 19:08:11 +00:00
objnum ( o )
{
2018-06-24 05:06:15 +00:00
auto & plr = * vcobjptr ( objnum ) ;
plr_shields = plr . shields ;
plr_info = plr . ctype . player_info ;
2016-03-19 19:08:11 +00:00
}
2019-03-03 00:31:08 +00:00
void restore ( fvmobjptr & vmobjptr ) const
2016-03-19 19:08:11 +00:00
{
2018-06-24 05:06:15 +00:00
auto & plr = * vmobjptr ( objnum ) ;
plr . shields = plr_shields ;
plr . ctype . player_info = plr_info ;
2016-03-19 19:08:11 +00:00
}
} ;
}
2015-12-13 18:00:49 +00:00
namespace dcx {
2006-03-20 17:12:09 +00:00
//Current_level_num starts at 1 for the first level
//-1,-2,-3 are secret levels
2016-11-15 03:13:23 +00:00
//0 used to mean not a real level loaded (i.e. editor generated level), but this hack has been removed
2021-11-01 03:37:19 +00:00
int Current_level_num = 1 ;
2014-12-20 04:36:07 +00:00
PHYSFSX_gets_line_t < LEVEL_NAME_LEN > Current_level_name ;
2006-03-20 17:12:09 +00:00
// Global variables describing the player
2014-09-20 23:47:27 +00:00
unsigned N_players = 1 ; // Number of players ( >1 means a net game, eh?)
2014-09-21 22:10:12 +00:00
playernum_t Player_num ; // The player number who is on the console.
2015-12-13 18:00:48 +00:00
fix StartingShields = INITIAL_SHIELDS ;
2020-05-02 21:18:42 +00:00
std : : array < obj_position , MAX_PLAYERS > Player_init ;
2006-03-20 17:12:09 +00:00
// Global variables telling what sort of game we have
2014-12-23 04:20:27 +00:00
unsigned NumNetPlayerPositions ;
2006-03-20 17:12:09 +00:00
int Do_appearance_effect = 0 ;
2018-04-30 05:31:16 +00:00
2020-12-26 21:17:29 +00:00
namespace {
2018-04-30 05:31:16 +00:00
template < object_type_t type >
static bool is_object_of_type ( const object_base & o )
{
return o . type = = type ;
}
2019-06-27 03:26:20 +00:00
static unsigned get_starting_concussion_missile_count ( )
{
return 2 + NDL - GameUniqueState . Difficulty_level ;
}
2015-12-13 18:00:48 +00:00
}
2020-12-26 21:17:29 +00:00
}
2015-12-13 18:00:49 +00:00
namespace dsx {
2020-12-26 21:17:29 +00:00
namespace {
static void init_player_stats_ship ( object & , fix GameTime64 ) ;
static window_event_result AdvanceLevel ( int secret_flag ) ;
static void StartLevel ( int random_flag ) ;
static void copy_defaults_to_robot_all ( void ) ;
2006-03-20 17:12:09 +00:00
//--------------------------------------------------------------------
2013-10-27 22:00:14 +00:00
static void verify_console_object ( )
2006-03-20 17:12:09 +00:00
{
2019-03-03 00:31:08 +00:00
auto & Objects = LevelUniqueObjectState . Objects ;
auto & vmobjptr = Objects . vmptr ;
2014-09-27 22:57:15 +00:00
Assert ( Player_num < Players . size ( ) ) ;
2017-06-10 03:31:02 +00:00
const auto & & console = vmobjptr ( get_local_player ( ) . objnum ) ;
2015-07-12 01:04:20 +00:00
ConsoleObject = console ;
Assert ( console - > type = = OBJ_PLAYER ) ;
Assert ( get_player_id ( console ) = = Player_num ) ;
2006-03-20 17:12:09 +00:00
}
2016-03-17 03:34:24 +00:00
template < object_type_t type >
2018-04-30 05:31:16 +00:00
static unsigned count_number_of_objects_of_type ( fvcobjptr & vcobjptr )
2006-03-20 17:12:09 +00:00
{
2018-04-30 05:31:16 +00:00
return std : : count_if ( vcobjptr . begin ( ) , vcobjptr . end ( ) , is_object_of_type < type > ) ;
2006-03-20 17:12:09 +00:00
}
2016-03-17 03:34:24 +00:00
# define count_number_of_robots count_number_of_objects_of_type<OBJ_ROBOT>
# define count_number_of_hostages count_number_of_objects_of_type<OBJ_HOSTAGE>
2006-03-20 17:12:09 +00:00
2018-12-03 04:25:11 +00:00
static bool operator ! = ( const vms_vector & a , const vms_vector & b )
{
return a . x ! = b . x | | a . y ! = b . y | | a . z ! = b . z ;
}
static unsigned generate_extra_starts_by_displacement_within_segment ( const unsigned preplaced_starts , const unsigned total_required_num_starts )
{
2020-05-17 23:35:25 +00:00
auto & LevelSharedVertexState = LevelSharedSegmentState . get_vertex_state ( ) ;
2019-03-03 00:31:08 +00:00
auto & Objects = LevelUniqueObjectState . Objects ;
2020-05-17 23:35:25 +00:00
auto & Vertices = LevelSharedVertexState . get_vertices ( ) ;
2019-03-03 00:31:08 +00:00
auto & vcobjptr = Objects . vcptr ;
auto & vmobjptr = Objects . vmptr ;
2020-05-02 21:18:42 +00:00
std : : array < uint8_t , MAX_PLAYERS > player_init_segment_capacity_flag { } ;
2018-12-03 04:25:11 +00:00
DXX_MAKE_VAR_UNDEFINED ( player_init_segment_capacity_flag ) ;
static_assert ( WRIGHT + 1 = = WBOTTOM , " side ordering error " ) ;
static_assert ( WBOTTOM + 1 = = WBACK , " side ordering error " ) ;
constexpr uint8_t capacity_x = 1 < < WRIGHT ;
constexpr uint8_t capacity_y = 1 < < WBOTTOM ;
constexpr uint8_t capacity_z = 1 < < WBACK ;
/* When players are displaced, they are moved by their size
* multiplied by this constant . Larger values provide more
* separation between the player starts , but increase the chance
* that the player will be too close to a wall or that the segment
* will be deemed too small to support displacement .
*/
constexpr fix size_scalar = 0x18000 ; // 1.5 in fixed point
unsigned segments_with_spare_capacity = 0 ;
2018-12-30 00:43:57 +00:00
auto & vcvertptr = Vertices . vcptr ;
2018-12-03 04:25:11 +00:00
for ( unsigned i = 0 ; i < preplaced_starts ; + + i )
{
/* For each existing Player_init, compute whether the segment is
* large enough in each dimension to support adding more ships .
*/
const auto & pi = Player_init [ i ] ;
const auto segnum = pi . segnum ;
2019-12-22 05:34:08 +00:00
const shared_segment & seg = * vcsegptr ( segnum ) ;
2018-12-03 04:25:11 +00:00
auto & plr = * Players . vcptr ( i ) ;
auto & old_player_obj = * vcobjptr ( plr . objnum ) ;
const vm_distance_squared size2 ( fixmul64 ( old_player_obj . size * old_player_obj . size , size_scalar ) ) ;
auto & v0 = * vcvertptr ( seg . verts [ 0 ] ) ;
uint8_t capacity_flag = 0 ;
if ( vm_vec_dist2 ( v0 , vcvertptr ( seg . verts [ 1 ] ) ) > size2 )
capacity_flag | = capacity_x ;
if ( vm_vec_dist2 ( v0 , vcvertptr ( seg . verts [ 3 ] ) ) > size2 )
capacity_flag | = capacity_y ;
if ( vm_vec_dist2 ( v0 , vcvertptr ( seg . verts [ 4 ] ) ) > size2 )
capacity_flag | = capacity_z ;
player_init_segment_capacity_flag [ i ] = capacity_flag ;
2020-01-06 01:25:35 +00:00
con_printf ( CON_NORMAL , " Original player %u has size %u, starts in segment #%hu, and has segment capacity flags %x. " , i , old_player_obj . size , static_cast < segnum_t > ( segnum ) , capacity_flag ) ;
2018-12-03 04:25:11 +00:00
if ( capacity_flag )
+ + segments_with_spare_capacity ;
}
if ( ! segments_with_spare_capacity )
return preplaced_starts ;
unsigned k = preplaced_starts ;
for ( unsigned old_player_idx = - 1 , side = WRIGHT ; + + old_player_idx ! = preplaced_starts | | ( old_player_idx = 0 , side + + ! = WBACK ) ; )
{
auto & old_player_ref = * Players . vcptr ( old_player_idx ) ;
const auto & & old_player_ptridx = Objects . vcptridx ( old_player_ref . objnum ) ;
auto & old_player_obj = * old_player_ptridx ;
if ( player_init_segment_capacity_flag [ old_player_idx ] & ( 1 < < side ) )
{
auto & & segp = vmsegptridx ( old_player_obj . segnum ) ;
/* Copy the start exactly. The next loop will fix the
* collisions caused by placing the clone on top of the
* original .
*
* Currently , there is no handling for the case that the
* level author already put two players too close together .
* If this is a problem , more logic can be added to suppress
* cloning in that case .
*/
const auto & & extra_player_ptridx = obj_create_copy ( old_player_obj , segp ) ;
if ( extra_player_ptridx = = object_none )
{
con_printf ( CON_URGENT , " %s:%u: warning: failed to copy start object %hu " , __FILE__ , __LINE__ , old_player_ptridx . get_unchecked_index ( ) ) ;
continue ;
}
Players . vmptr ( k ) - > objnum = extra_player_ptridx ;
auto & extra_player_obj = * extra_player_ptridx ;
set_player_id ( extra_player_obj , k ) ;
con_printf ( CON_NORMAL , " Copied player %u (object %hu at {%i, %i, %i}) to create player %u (object %hu). " , old_player_idx , old_player_ptridx . get_unchecked_index ( ) , old_player_obj . pos . x , old_player_obj . pos . y , old_player_obj . pos . z , k , extra_player_ptridx . get_unchecked_index ( ) ) ;
if ( + + k > = total_required_num_starts )
break ;
}
}
for ( unsigned old_player_idx = 0 ; old_player_idx < preplaced_starts ; + + old_player_idx )
{
auto & old_player_init = Player_init [ old_player_idx ] ;
const auto old_player_pos = old_player_init . pos ;
auto & old_player_obj = * vmobjptr ( Players . vcptr ( old_player_idx ) - > objnum ) ;
2020-05-02 21:18:42 +00:00
std : : array < vms_vector , 3 > vec_displacement { } ;
2018-12-03 04:25:11 +00:00
DXX_MAKE_VAR_UNDEFINED ( vec_displacement ) ;
2019-12-22 05:34:08 +00:00
const shared_segment & seg = * vcsegptr ( old_player_init . segnum ) ;
2018-12-03 04:25:11 +00:00
/* For each of [right, bottom, back], compute the vector between
* the center of that side and the reference player ' s start
* point . This will be used in the next loop .
*/
for ( unsigned side = WRIGHT ; side ! = WBACK + 1 ; + + side )
{
const auto & & center_on_side = compute_center_point_on_side ( vcvertptr , seg , side ) ;
const auto & & vec_pos_to_center_on_side = vm_vec_sub ( center_on_side , old_player_init . pos ) ;
const unsigned idxside = side - WRIGHT ;
assert ( idxside < vec_displacement . size ( ) ) ;
vec_displacement [ idxside ] = vec_pos_to_center_on_side ;
}
const auto displace_player = [ & ] ( const unsigned plridx , object_base & plrobj , const unsigned displacement_direction ) {
vms_vector disp { } ;
unsigned dimensions = 0 ;
for ( unsigned i = 0 , side = WRIGHT ; side ! = WBACK + 1 ; + + side , + + i )
{
if ( ! ( player_init_segment_capacity_flag [ old_player_idx ] & ( 1 < < side ) ) )
{
con_printf ( CON_NORMAL , " Cannot displace player %u at {%i, %i, %i}: not enough room in dimension %u. " , plridx , plrobj . pos . x , plrobj . pos . y , plrobj . pos . z , side ) ;
continue ;
}
const auto & v = vec_displacement [ i ] ;
const auto & va = ( displacement_direction & ( 1 < < i ) ) ? v : vm_vec_negated ( v ) ;
con_printf ( CON_NORMAL , " Add displacement of {%i, %i, %i} for dimension %u for player %u. " , va . x , va . y , va . z , side , plridx ) ;
+ + dimensions ;
vm_vec_add2 ( disp , va ) ;
}
if ( ! dimensions )
return ;
vm_vec_normalize ( disp ) ;
vm_vec_scale ( disp , fixmul ( old_player_obj . size , size_scalar > > 1 ) ) ;
2020-01-06 01:25:35 +00:00
const auto target_position = vm_vec_add ( Player_init [ plridx ] . pos , disp ) ;
if ( const auto sidemask = get_seg_masks ( vcvertptr , target_position , vcsegptr ( plrobj . segnum ) , 1 ) . sidemask )
{
con_printf ( CON_NORMAL , " Cannot displace player %u at {%i, %i, %i} to {%i, %i, %i}: would be outside segment for sides %x. " , plridx , plrobj . pos . x , plrobj . pos . y , plrobj . pos . z , target_position . x , target_position . y , target_position . z , sidemask ) ;
return ;
}
con_printf ( CON_NORMAL , " Displace player %u at {%i, %i, %i} by {%i, %i, %i} to {%i, %i, %i}. " , plridx , plrobj . pos . x , plrobj . pos . y , plrobj . pos . z , disp . x , disp . y , disp . z , target_position . x , target_position . y , target_position . z ) ;
Player_init [ plridx ] . pos = target_position ;
2019-12-22 05:34:08 +00:00
plrobj . pos = Player_init [ plridx ] . pos ;
2018-12-03 04:25:11 +00:00
} ;
for ( unsigned extra_player_idx = preplaced_starts , displacements = 0 ; extra_player_idx < k ; + + extra_player_idx )
{
auto & extra_player_obj = * vmobjptr ( Players . vcptr ( extra_player_idx ) - > objnum ) ;
if ( old_player_pos ! = extra_player_obj . pos )
/* This clone is associated with some other player.
* Skip it here . It will be handled in a different pass
* of the loop .
*/
continue ;
auto & extra_player_init = Player_init [ extra_player_idx ] ;
extra_player_init = old_player_init ;
if ( ! displacements + + )
displace_player ( old_player_idx , old_player_obj , 0 ) ;
displace_player ( extra_player_idx , extra_player_obj , displacements ) ;
}
}
return k ;
}
2006-03-20 17:12:09 +00:00
//added 10/12/95: delete buddy bot if coop game. Probably doesn't really belong here. -MT
2019-03-03 00:31:08 +00:00
static void gameseq_init_network_players ( object_array & Objects )
2006-03-20 17:12:09 +00:00
{
// Initialize network player start locations and object numbers
2019-03-03 00:31:08 +00:00
ConsoleObject = & Objects . front ( ) ;
2017-08-13 20:38:31 +00:00
unsigned j = 0 , k = 0 ;
2019-06-27 03:26:20 +00:00
const auto multiplayer_coop = Game_mode & GM_MULTI_COOP ;
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 defined(DXX_BUILD_DESCENT_II)
const auto remove_thief = Netgame . ThiefModifierFlags & ThiefModifier : : Absent ;
2016-03-17 03:34:23 +00:00
const auto multiplayer = Game_mode & GM_MULTI ;
2019-06-27 03:26:20 +00:00
const auto retain_guidebot = Netgame . AllowGuidebot ;
2018-12-30 00:43:59 +00:00
auto & Robot_info = LevelSharedRobotInfoState . Robot_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
# endif
2019-03-03 00:31:08 +00:00
auto & vmobjptridx = Objects . vmptridx ;
2017-06-10 03:31:02 +00:00
range_for ( const auto & & o , vmobjptridx )
2014-10-12 23:05:46 +00:00
{
2016-03-17 03:34:23 +00:00
const auto type = o - > type ;
if ( type = = OBJ_PLAYER | | type = = OBJ_GHOST | | type = = OBJ_COOP )
2006-03-20 17:12:09 +00:00
{
2018-12-03 04:25:11 +00:00
if ( likely ( k < Player_init . size ( ) ) & &
multiplayer_coop
2016-03-17 03:34:23 +00:00
? ( j = = 0 | | type = = OBJ_COOP )
: ( type = = OBJ_PLAYER | | type = = OBJ_GHOST )
)
2006-03-20 17:12:09 +00:00
{
2014-11-26 03:39:21 +00:00
o - > type = OBJ_PLAYER ;
2018-12-03 04:25:11 +00:00
auto & pi = Player_init [ k ] ;
pi . pos = o - > pos ;
pi . orient = o - > orient ;
pi . segnum = o - > segnum ;
2017-08-13 20:38:32 +00:00
vmplayerptr ( k ) - > objnum = o ;
2014-11-26 03:39:21 +00:00
set_player_id ( o , k ) ;
2006-03-20 17:12:09 +00:00
k + + ;
}
else
2018-12-30 00:43:57 +00:00
obj_delete ( LevelUniqueObjectState , Segments , o ) ;
2006-03-20 17:12:09 +00:00
j + + ;
}
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 defined(DXX_BUILD_DESCENT_II)
else if ( type = = OBJ_ROBOT & & multiplayer )
{
auto & ri = Robot_info [ get_robot_id ( o ) ] ;
2019-06-27 03:26:20 +00:00
if ( ( ! retain_guidebot & & robot_is_companion ( ri ) ) | |
( remove_thief & & robot_is_thief ( ri ) ) )
2020-01-09 03:19:58 +00:00
{
object_create_robot_egg ( o ) ;
2018-12-30 00:43:57 +00:00
obj_delete ( LevelUniqueObjectState , Segments , o ) ; //kill the buddy in netgames
2020-01-09 03:19:58 +00:00
}
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
}
# endif
2006-03-20 17:12:09 +00:00
}
2019-02-02 18:36:39 +00:00
if ( multiplayer_coop )
{
2018-12-03 04:25:11 +00:00
const unsigned total_required_num_starts = Netgame . max_numplayers ;
if ( k < total_required_num_starts )
{
2020-01-18 21:57:39 +00:00
con_printf ( CON_NORMAL , " Insufficient cooperative starts found in mission \" %s \" level %u (need %u, found %u). Generating extra starts... " , Current_mission - > path . c_str ( ) , Current_level_num , total_required_num_starts , k ) ;
2018-12-03 04:25:11 +00:00
/*
* First , try displacing the starts within the existing segment .
*/
const unsigned preplaced_starts = k ;
k = generate_extra_starts_by_displacement_within_segment ( preplaced_starts , total_required_num_starts ) ;
con_printf ( CON_NORMAL , " Generated %u starts by displacement within the original segment. " , k - preplaced_starts ) ;
}
else
2020-01-18 21:57:39 +00:00
con_printf ( CON_NORMAL , " Found %u cooperative starts in mission \" %s \" level %u. " , k , Current_mission - > path . c_str ( ) , Current_level_num ) ;
2019-02-02 18:36:39 +00:00
}
2006-03-20 17:12:09 +00:00
NumNetPlayerPositions = k ;
}
2020-12-26 21:17:29 +00:00
}
2006-03-20 17:12:09 +00:00
void gameseq_remove_unused_players ( )
{
2019-03-03 00:31:08 +00:00
auto & Objects = LevelUniqueObjectState . Objects ;
auto & vmobjptridx = Objects . vmptridx ;
2006-03-20 17:12:09 +00:00
// 'Remove' the unused players
if ( Game_mode & GM_MULTI )
{
2016-02-12 04:02:28 +00:00
for ( unsigned i = 0 ; i < NumNetPlayerPositions ; + + i )
2006-03-20 17:12:09 +00:00
{
2017-08-13 20:38:32 +00:00
if ( ( ! vcplayerptr ( i ) - > connected ) | | ( i > = N_players ) )
2006-03-20 17:12:09 +00:00
{
multi_make_player_ghost ( i ) ;
}
}
}
else
{ // Note link to above if!!!
2016-02-12 04:02:28 +00:00
range_for ( auto & i , partial_const_range ( Players , 1u , NumNetPlayerPositions ) )
2006-03-20 17:12:09 +00:00
{
2018-12-30 00:43:57 +00:00
obj_delete ( LevelUniqueObjectState , Segments , vmobjptridx ( i . objnum ) ) ;
2006-03-20 17:12:09 +00:00
}
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 defined(DXX_BUILD_DESCENT_II)
2018-12-30 00:43:59 +00:00
auto & Robot_info = LevelSharedRobotInfoState . Robot_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
if ( PlayerCfg . ThiefModifierFlags & ThiefModifier : : Absent )
{
range_for ( const auto & & o , vmobjptridx )
{
const auto type = o - > type ;
if ( type = = OBJ_ROBOT )
{
auto & ri = Robot_info [ get_robot_id ( o ) ] ;
if ( robot_is_thief ( ri ) )
2020-01-09 03:19:58 +00:00
{
object_create_robot_egg ( o ) ;
2018-12-30 00:43:57 +00:00
obj_delete ( LevelUniqueObjectState , Segments , o ) ;
2020-01-09 03:19:58 +00:00
}
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
}
}
}
# endif
2006-03-20 17:12:09 +00:00
}
}
// Setup player for new game
2017-08-13 20:38:31 +00:00
void init_player_stats_game ( const playernum_t pnum )
2006-03-20 17:12:09 +00:00
{
2019-03-03 00:31:08 +00:00
auto & Objects = LevelUniqueObjectState . Objects ;
auto & vmobjptr = Objects . vmptr ;
2017-08-13 20:38:32 +00:00
auto & plr = * vmplayerptr ( pnum ) ;
2017-08-13 20:38:31 +00:00
plr . lives = INITIAL_LIVES ;
plr . level = 1 ;
plr . time_level = 0 ;
plr . time_total = 0 ;
plr . hours_level = 0 ;
plr . hours_total = 0 ;
plr . num_kills_level = 0 ;
plr . num_kills_total = 0 ;
const auto & & plobj = vmobjptr ( plr . objnum ) ;
2016-10-15 00:53:19 +00:00
auto & player_info = plobj - > ctype . player_info ;
player_info . powerup_flags = { } ;
2016-11-12 18:10:07 +00:00
player_info . net_killed_total = 0 ;
2016-11-12 18:10:07 +00:00
player_info . net_kills_total = 0 ;
2016-11-12 18:10:07 +00:00
player_info . KillGoalCount = 0 ;
2016-10-15 00:53:19 +00:00
player_info . mission . score = 0 ;
2016-10-15 00:53:19 +00:00
player_info . mission . last_score = 0 ;
2016-10-15 00:53:19 +00:00
player_info . mission . hostages_rescued_total = 0 ;
2012-04-15 13:32:48 +00:00
init_player_stats_new_ship ( pnum ) ;
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2012-04-15 13:32:48 +00:00
if ( pnum = = Player_num )
First_secret_visit = 1 ;
2013-03-03 01:03:33 +00:00
# endif
2006-03-20 17:12:09 +00:00
}
2020-12-26 21:17:29 +00:00
namespace {
2016-10-28 03:39:42 +00:00
static void init_ammo_and_energy ( object & plrobj )
2006-03-20 17:12:09 +00:00
{
2016-10-28 03:39:42 +00:00
auto & player_info = plrobj . ctype . player_info ;
2016-07-03 00:54:16 +00:00
{
2016-09-11 18:49:13 +00:00
auto & energy = player_info . energy ;
2016-11-26 22:51:49 +00:00
# if defined(DXX_BUILD_DESCENT_II)
if ( player_info . primary_weapon_flags & HAS_PRIMARY_FLAG ( OMEGA_INDEX ) )
{
const auto old_omega_charge = player_info . Omega_charge ;
if ( old_omega_charge < MAX_OMEGA_CHARGE )
{
const auto energy_used = get_omega_energy_consumption ( ( player_info . Omega_charge = MAX_OMEGA_CHARGE ) - old_omega_charge ) ;
energy - = energy_used ;
}
}
# endif
2016-07-03 00:54:16 +00:00
if ( energy < INITIAL_ENERGY )
energy = INITIAL_ENERGY ;
}
2016-07-03 00:54:16 +00:00
{
2016-10-28 03:39:42 +00:00
auto & shields = plrobj . shields ;
2016-07-03 00:54:16 +00:00
if ( shields < StartingShields )
shields = StartingShields ;
}
2019-06-27 03:26:20 +00:00
const unsigned minimum_missiles = get_starting_concussion_missile_count ( ) ;
2016-09-11 18:49:13 +00:00
auto & concussion = player_info . secondary_ammo [ CONCUSSION_INDEX ] ;
2019-06-27 03:26:20 +00:00
if ( concussion < minimum_missiles )
concussion = minimum_missiles ;
2006-03-20 17:12:09 +00:00
}
2020-12-26 21:17:29 +00:00
}
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
extern ubyte Last_afterburner_state ;
2013-03-03 01:03:33 +00:00
# endif
2006-03-20 17:12:09 +00:00
2020-12-26 21:17:29 +00:00
namespace {
2006-03-20 17:12:09 +00:00
// Setup player for new level (After completion of previous level)
2019-07-16 04:00:50 +00:00
static void init_player_stats_level ( player & plr , object & plrobj , const secret_restore secret_flag )
2006-03-20 17:12:09 +00:00
{
2019-07-16 04:00:50 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2019-03-03 00:31:08 +00:00
auto & Objects = LevelUniqueObjectState . Objects ;
auto & vcobjptridx = Objects . vcptridx ;
2013-03-03 01:03:33 +00:00
# endif
2006-03-20 17:12:09 +00:00
2016-03-18 02:05:16 +00:00
plr . level = Current_level_num ;
2006-03-20 17:12:09 +00:00
if ( ! Network_rejoined ) {
2016-03-18 02:05:16 +00:00
plr . time_level = 0 ;
plr . hours_level = 0 ;
2006-03-20 17:12:09 +00:00
}
2016-10-15 00:53:19 +00:00
auto & player_info = plrobj . ctype . player_info ;
2016-10-15 00:53:19 +00:00
player_info . mission . last_score = player_info . mission . score ;
2006-03-20 17:12:09 +00:00
2016-03-18 02:05:16 +00:00
plr . num_kills_level = 0 ;
2006-03-20 17:12:09 +00:00
2015-04-19 04:18:49 +00:00
if ( secret_flag = = secret_restore : : none ) {
2016-10-28 03:39:42 +00:00
init_ammo_and_energy ( plrobj ) ;
2006-03-20 17:12:09 +00:00
2016-08-28 22:41:49 +00:00
auto & powerup_flags = player_info . powerup_flags ;
2016-03-17 03:34:24 +00:00
powerup_flags & = ~ ( PLAYER_FLAGS_INVULNERABLE | PLAYER_FLAGS_CLOAKED ) ;
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2016-03-17 03:34:24 +00:00
powerup_flags & = ~ ( PLAYER_FLAGS_MAP_ALL ) ;
2013-03-03 01:03:33 +00:00
# endif
2006-03-20 17:12:09 +00:00
2016-10-02 00:34:39 +00:00
DXX_MAKE_VAR_UNDEFINED ( player_info . cloak_time ) ;
2016-08-28 22:41:49 +00:00
DXX_MAKE_VAR_UNDEFINED ( player_info . invulnerable_time ) ;
2006-03-20 17:12:09 +00:00
2015-10-30 02:52:55 +00:00
const auto all_keys = PLAYER_FLAGS_BLUE_KEY | PLAYER_FLAGS_GOLD_KEY | PLAYER_FLAGS_RED_KEY ;
2006-03-20 17:12:09 +00:00
if ( ( Game_mode & GM_MULTI ) & & ! ( Game_mode & GM_MULTI_COOP ) )
2016-03-17 03:34:24 +00:00
powerup_flags | = all_keys ;
2015-10-30 02:52:55 +00:00
else
2016-03-17 03:34:24 +00:00
powerup_flags & = ~ all_keys ;
2006-03-20 17:12:09 +00:00
}
2015-12-15 04:09:35 +00:00
Player_dead_state = player_dead_state : : no ; // Added by RH
2011-01-18 19:02:04 +00:00
Dead_player_camera = NULL ;
2006-03-20 17:12:09 +00:00
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2013-11-26 22:46:48 +00:00
Controls . state . afterburner = 0 ;
2006-03-20 17:12:09 +00:00
Last_afterburner_state = 0 ;
2016-03-18 02:05:16 +00:00
digi_kill_sound_linked_to_object ( vcobjptridx ( plr . objnum ) ) ;
2013-03-03 01:03:33 +00:00
# endif
2006-03-20 17:12:09 +00:00
init_gauges ( ) ;
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
Missile_viewer = NULL ;
2013-03-03 01:03:33 +00:00
# endif
2019-07-07 22:00:02 +00:00
init_player_stats_ship ( plrobj , GameTime64 ) ;
2006-03-20 17:12:09 +00:00
}
2020-12-26 21:17:29 +00:00
}
2006-03-20 17:12:09 +00:00
// Setup player for a brand-new ship
2017-08-13 20:38:32 +00:00
void init_player_stats_new_ship ( const playernum_t pnum )
2006-03-20 17:12:09 +00:00
{
2019-03-03 00:31:08 +00:00
auto & Objects = LevelUniqueObjectState . Objects ;
auto & vmobjptridx = Objects . vmptridx ;
2017-08-13 20:38:32 +00:00
auto & plr = * vcplayerptr ( pnum ) ;
2017-06-10 03:31:02 +00:00
const auto & & plrobj = vmobjptridx ( plr . objnum ) ;
2015-11-07 21:55:58 +00:00
plrobj - > shields = StartingShields ;
2015-11-07 21:55:58 +00:00
auto & player_info = plrobj - > ctype . player_info ;
player_info . energy = INITIAL_ENERGY ;
2015-11-07 21:55:58 +00:00
player_info . secondary_ammo = { {
2019-06-27 03:26:20 +00:00
static_cast < uint8_t > ( get_starting_concussion_missile_count ( ) )
2015-11-07 21:55:58 +00:00
} } ;
2015-03-28 17:18:02 +00:00
const auto GrantedItems = ( Game_mode & GM_MULTI ) ? Netgame . SpawnGrantedItems : 0 ;
2015-11-07 21:55:59 +00:00
player_info . vulcan_ammo = map_granted_flags_to_vulcan_ammo ( GrantedItems ) ;
2015-09-26 21:17:14 +00:00
const auto granted_laser_level = map_granted_flags_to_laser_level ( GrantedItems ) ;
2015-11-07 21:55:59 +00:00
player_info . laser_level = granted_laser_level ;
2015-11-07 21:55:59 +00:00
const auto granted_primary_weapon_flags = HAS_LASER_FLAG | map_granted_flags_to_primary_weapon_flags ( GrantedItems ) ;
player_info . primary_weapon_flags = granted_primary_weapon_flags ;
2016-02-27 19:02:21 +00:00
player_info . powerup_flags & = ~ ( PLAYER_FLAGS_QUAD_LASERS | PLAYER_FLAGS_CLOAKED | PLAYER_FLAGS_INVULNERABLE ) ;
# if defined(DXX_BUILD_DESCENT_II)
player_info . powerup_flags & = ~ ( PLAYER_FLAGS_AFTERBURNER | PLAYER_FLAGS_MAP_ALL | PLAYER_FLAGS_CONVERTER | PLAYER_FLAGS_AMMO_RACK | PLAYER_FLAGS_HEADLIGHT | PLAYER_FLAGS_HEADLIGHT_ON | PLAYER_FLAGS_FLAG ) ;
2016-11-19 18:09:26 +00:00
player_info . Omega_charge = ( granted_primary_weapon_flags & HAS_OMEGA_FLAG )
? MAX_OMEGA_CHARGE
: 0 ;
2016-11-12 18:10:08 +00:00
player_info . Omega_recharge_delay = 0 ;
2020-08-01 18:29:01 +00:00
player_info . hoard . orbs = 0 ;
2016-02-27 19:02:21 +00:00
# endif
player_info . powerup_flags | = map_granted_flags_to_player_flags ( GrantedItems ) ;
DXX_MAKE_VAR_UNDEFINED ( player_info . cloak_time ) ;
DXX_MAKE_VAR_UNDEFINED ( player_info . invulnerable_time ) ;
2012-04-15 13:32:48 +00:00
if ( pnum = = Player_num )
{
2016-05-30 13:00:07 +00:00
if ( Game_mode & GM_MULTI & & Netgame . InvulAppear )
2016-02-27 19:02:21 +00:00
{
player_info . powerup_flags | = PLAYER_FLAGS_INVULNERABLE ;
player_info . invulnerable_time = GameTime64 - ( i2f ( 58 - Netgame . InvulAppear ) > > 1 ) ;
player_info . FakingInvul = 1 ;
}
2016-12-25 00:33:25 +00:00
set_primary_weapon ( player_info , [ = ] {
2015-09-26 21:17:14 +00:00
range_for ( auto i , PlayerCfg . PrimaryOrder )
{
if ( i > = MAX_PRIMARY_WEAPONS )
break ;
if ( i = = primary_weapon_index_t : : LASER_INDEX )
break ;
# if defined(DXX_BUILD_DESCENT_II)
if ( i = = primary_weapon_index_t : : SUPER_LASER_INDEX )
{
2020-08-24 01:31:28 +00:00
if ( granted_laser_level < = laser_level : : _4 )
2015-09-26 21:17:14 +00:00
/* Granted lasers are not super lasers */
continue ;
/* Super lasers still set LASER_INDEX, not
* SUPER_LASER_INDEX
*/
break ;
}
# endif
if ( HAS_PRIMARY_FLAG ( i ) & static_cast < unsigned > ( granted_primary_weapon_flags ) )
return static_cast < primary_weapon_index_t > ( i ) ;
}
return primary_weapon_index_t : : LASER_INDEX ;
2016-12-25 00:33:25 +00:00
} ( ) ) ;
2015-09-26 21:17:14 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2017-02-19 19:33:44 +00:00
auto primary_last_was_super = player_info . Primary_last_was_super ;
for ( uint_fast32_t i = primary_weapon_index_t : : VULCAN_INDEX , mask = 1 < < i ; i ! = primary_weapon_index_t : : SUPER_LASER_INDEX ; + + i , mask < < = 1 )
2015-09-26 21:17:14 +00:00
{
/* If no super granted, force to non-super. */
if ( ! ( HAS_PRIMARY_FLAG ( i + 5 ) & granted_primary_weapon_flags ) )
2017-02-19 19:33:44 +00:00
primary_last_was_super & = ~ mask ;
2015-09-26 21:17:14 +00:00
/* If only super granted, force to super. */
else if ( ! ( HAS_PRIMARY_FLAG ( i ) & granted_primary_weapon_flags ) )
2017-02-19 19:33:44 +00:00
primary_last_was_super | = mask ;
2015-09-26 21:17:14 +00:00
/* else both granted, so leave as-is. */
else
continue ;
}
2017-02-19 19:33:44 +00:00
player_info . Primary_last_was_super = primary_last_was_super ;
2015-09-26 21:17:14 +00:00
# endif
2012-04-15 13:32:48 +00:00
if ( Newdemo_state = = ND_STATE_RECORDING )
{
2020-08-24 01:31:28 +00:00
newdemo_record_laser_level ( player_info . laser_level , laser_level : : _1 ) ;
2012-04-15 13:32:48 +00:00
}
2016-12-25 00:33:25 +00:00
set_secondary_weapon_to_concussion ( player_info ) ;
2012-04-15 13:32:48 +00:00
dead_player_end ( ) ; //player no longer dead
2015-12-15 04:09:35 +00:00
Player_dead_state = player_dead_state : : no ;
2016-12-10 17:51:09 +00:00
player_info . Player_eggs_dropped = false ;
2012-04-15 23:13:29 +00:00
Dead_player_camera = 0 ;
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2016-08-28 22:41:48 +00:00
auto & Secondary_last_was_super = player_info . Secondary_last_was_super ;
2015-09-26 21:17:14 +00:00
Secondary_last_was_super = { } ;
2015-04-19 04:18:53 +00:00
Afterburner_charge = GrantedItems . has_afterburner ( ) ? F1_0 : 0 ;
2013-11-26 22:46:48 +00:00
Controls . state . afterburner = 0 ;
2012-04-15 13:32:48 +00:00
Last_afterburner_state = 0 ;
2015-03-22 18:49:21 +00:00
Missile_viewer = nullptr ; //reset missile camera if out there
2013-03-03 01:03:33 +00:00
# endif
2017-07-08 18:17:49 +00:00
init_ai_for_ship ( ) ;
2006-03-20 17:12:09 +00:00
}
2016-11-26 22:51:49 +00:00
digi_kill_sound_linked_to_object ( plrobj ) ;
2019-07-07 22:00:02 +00:00
init_player_stats_ship ( plrobj , GameTime64 ) ;
2016-11-26 22:51:49 +00:00
}
2020-12-26 21:17:29 +00:00
namespace {
2019-07-07 22:00:02 +00:00
void init_player_stats_ship ( object & plrobj , const fix GameTime64 )
2016-11-26 22:51:49 +00:00
{
auto & player_info = plrobj . ctype . player_info ;
2016-12-25 00:33:24 +00:00
player_info . lavafall_hiss_playing = false ;
player_info . missile_gun = 0 ;
2019-05-04 18:27:36 +00:00
player_info . Spreadfire_toggle = 0 ;
2016-11-26 22:51:49 +00:00
player_info . killer_objnum = object_none ;
# if defined(DXX_BUILD_DESCENT_II)
player_info . Omega_recharge_delay = 0 ;
2019-05-04 18:27:36 +00:00
player_info . Helix_orientation = 0 ;
2016-11-26 22:51:49 +00:00
# endif
2016-10-15 00:53:19 +00:00
player_info . mission . hostages_on_board = 0 ;
2015-11-14 18:17:22 +00:00
player_info . homing_object_dist = - F1_0 ; // Added by RH
2016-11-26 22:51:49 +00:00
player_info . Next_flare_fire_time = player_info . Next_laser_fire_time = player_info . Next_missile_fire_time = GameTime64 ;
2006-03-20 17:12:09 +00:00
}
2020-12-26 21:17:29 +00:00
}
2006-03-20 17:12:09 +00:00
2015-12-13 18:00:48 +00:00
}
2006-03-20 17:12:09 +00:00
//update various information about the player
void update_player_stats ( )
{
2017-08-13 20:38:31 +00:00
auto & plr = get_local_player ( ) ;
plr . time_level + = FrameTime ; //the never-ending march of time...
if ( plr . time_level > i2f ( 3600 ) )
2015-07-25 23:10:46 +00:00
{
2017-08-13 20:38:31 +00:00
plr . time_level - = i2f ( 3600 ) ;
+ + plr . hours_level ;
2013-02-21 00:20:26 +00:00
}
2006-03-20 17:12:09 +00:00
2017-08-13 20:38:31 +00:00
plr . time_total + = FrameTime ; //the never-ending march of time...
if ( plr . time_total > i2f ( 3600 ) )
2015-07-25 23:10:46 +00:00
{
2017-08-13 20:38:31 +00:00
plr . time_total - = i2f ( 3600 ) ;
+ + plr . hours_total ;
2013-02-21 00:20:26 +00:00
}
2006-03-20 17:12:09 +00:00
}
//go through this level and start any eclip sounds
2016-08-25 04:05:32 +00:00
namespace dsx {
2020-12-26 21:17:29 +00:00
namespace {
2017-07-26 03:15:59 +00:00
2018-12-30 00:43:57 +00:00
static void set_sound_sources ( fvcsegptridx & vcsegptridx , fvcvertptr & vcvertptr )
2006-03-20 17:12:09 +00:00
{
2019-03-03 00:31:09 +00:00
auto & Effects = LevelUniqueEffectsClipState . Effects ;
2018-12-30 00:43:59 +00:00
auto & TmapInfo = LevelUniqueTmapInfoState . TmapInfo ;
2006-03-20 17:12:09 +00:00
digi_init_sounds ( ) ; //clear old sounds
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2018-12-30 00:43:58 +00:00
auto & Walls = LevelUniqueWallSubsystemState . Walls ;
auto & vcwallptr = Walls . vcptr ;
2013-03-03 01:03:33 +00:00
# endif
Backport D2's Dont_start_sound_objects to D1
Descent 2 has a hack, present as far back as I can trace, that
suppresses starting sounds during level load. The original reason was
not recorded, but this hack has the useful side effect that it avoids
using uninitialized data when set_sound_sources tries to use a Viewer
that has not been reset for the objects of the new level.
Descent 1 lacks this hack, so an invalid Viewer is used, which may
trigger a valptridx trap if the undefined data has an invalid segment
number, and could cause memory corruption in builds which do not
validate the segment index. The valptridx trap:
```
terminate called after throwing an instance of 'valptridx<dcx::segment>::index_range_exception'
what(): similar/main/digiobj.cpp:389: invalid index used in array subscript: base=(nil) size=9000 index=65021
```
The backtrace leading to the trap:
```
d1x::digi_link_sound_common (viewer=..., so=..., pos=..., forever=<optimized out>, max_volume=<optimized out>, max_distance=..., soundnum=42, segnum=...) at similar/main/digiobj.cpp:389
0x00005555555a4e2d in d1x::digi_link_sound_to_pos2 (vcobjptr=..., max_distance=..., max_volume=32768, forever=1, pos=..., sidenum=4, segnum=..., org_soundnum=121) at similar/main/digiobj.cpp:483
d1x::digi_link_sound_to_pos (soundnum=soundnum@entry=121, segnum=..., sidenum=sidenum@entry=4, pos=..., forever=forever@entry=1, max_volume=32768) at similar/main/digiobj.cpp:490
0x00005555555c140d in d1x::set_sound_sources (vcsegptridx=..., vcvertptr=...) at similar/main/gameseq.cpp:817
d1x::LoadLevel (level_num=<optimized out>, page_in_textures=1) at similar/main/gameseq.cpp:1022
0x00005555555c2654 in d1x::StartNewLevelSub (level_num=-1, page_in_textures=<optimized out>) at similar/main/gameseq.cpp:1865
```
Backport this hack into Descent 1. Ultimately, the hack should go away
and data should be loaded in an order that does not access undefined
memory.
Reported-by: Spacecpp <https://github.com/dxx-rebirth/dxx-rebirth/issues/463>
2019-10-26 23:13:14 +00:00
Dont_start_sound_objects = 1 ;
2006-03-20 17:12:09 +00:00
2018-12-30 00:43:59 +00:00
const auto get_eclip_for_tmap = [ ] ( const d_level_unique_tmap_info_state : : TmapInfo_array & TmapInfo , const unique_side & side ) {
2020-08-24 01:31:28 +00:00
if ( const auto tm2 = side . tmap_num2 ; tm2 ! = texture2_value : : None )
2018-12-30 00:43:59 +00:00
{
2020-08-24 01:31:28 +00:00
const auto ec = TmapInfo [ get_texture_index ( tm2 ) ] . eclip_num ;
2018-12-30 00:43:59 +00:00
# if defined(DXX_BUILD_DESCENT_II)
if ( ec ! = eclip_none )
# endif
return ec ;
}
# if defined(DXX_BUILD_DESCENT_I)
return eclip_none . value ;
# elif defined(DXX_BUILD_DESCENT_II)
2020-09-11 03:08:02 +00:00
return TmapInfo [ get_texture_index ( side . tmap_num ) ] . eclip_num ;
2018-12-30 00:43:59 +00:00
# endif
} ;
2016-02-12 04:02:28 +00:00
range_for ( const auto & & seg , vcsegptridx )
2014-10-12 23:10:05 +00:00
{
2019-05-04 18:27:36 +00:00
range_for ( const uint_fast32_t sidenum , xrange ( MAX_SIDES_PER_SEGMENT ) )
{
2018-12-30 00:43:59 +00:00
int sn ;
2006-03-20 17:12:09 +00:00
2018-12-30 00:43:59 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2019-12-27 02:02:23 +00:00
const auto wid = WALL_IS_DOORWAY ( GameBitmaps , Textures , vcwallptr , seg , sidenum ) ;
2020-12-19 16:13:26 +00:00
if ( ! ( wid & WALL_IS_DOORWAY_FLAG : : render ) )
2018-12-30 00:43:59 +00:00
continue ;
2013-03-03 01:03:33 +00:00
# endif
2018-12-30 00:43:59 +00:00
const auto ec = get_eclip_for_tmap ( TmapInfo , seg - > unique_segment : : sides [ sidenum ] ) ;
if ( ec ! = eclip_none )
{
2006-03-20 17:12:09 +00:00
if ( ( sn = Effects [ ec ] . sound_num ) ! = - 1 ) {
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2014-11-20 03:00:36 +00:00
auto csegnum = seg - > children [ sidenum ] ;
2006-03-20 17:12:09 +00:00
//check for sound on other side of wall. Don't add on
//both walls if sound travels through wall. If sound
//does travel through wall, add sound for lower-numbered
//segment.
2015-12-22 04:18:51 +00:00
if ( IS_CHILD ( csegnum ) & & csegnum < seg ) {
2020-12-19 16:13:26 +00:00
if ( wid & ( WALL_IS_DOORWAY_FLAG : : fly | WALL_IS_DOORWAY_FLAG : : rendpast ) ) {
2015-07-18 21:01:55 +00:00
const auto & & csegp = vcsegptr ( seg - > children [ sidenum ] ) ;
2014-09-06 04:06:18 +00:00
auto csidenum = find_connect_side ( seg , csegp ) ;
2006-03-20 17:12:09 +00:00
2018-12-13 02:31:38 +00:00
if ( csegp - > unique_segment : : sides [ csidenum ] . tmap_num2 = = seg - > unique_segment : : sides [ sidenum ] . tmap_num2 )
2006-03-20 17:12:09 +00:00
continue ; //skip this one
}
}
2013-03-03 01:03:33 +00:00
# endif
2006-03-20 17:12:09 +00:00
2018-03-12 03:43:47 +00:00
const auto & & pnt = compute_center_point_on_side ( vcvertptr , seg , sidenum ) ;
2015-12-22 04:18:51 +00:00
digi_link_sound_to_pos ( sn , seg , sidenum , pnt , 1 , F1_0 / 2 ) ;
2006-03-20 17:12:09 +00:00
}
2018-12-30 00:43:59 +00:00
}
2006-03-20 17:12:09 +00:00
}
2014-10-12 23:10:05 +00:00
}
2006-03-20 17:12:09 +00:00
Dont_start_sound_objects = 0 ;
}
2016-10-02 19:35:34 +00:00
constexpr fix flash_dist = fl2f ( .9 ) ;
2020-12-26 21:17:29 +00:00
}
2006-03-20 17:12:09 +00:00
//create flash for player appearance
2018-10-21 00:24:07 +00:00
void create_player_appearance_effect ( const d_vclip_array & Vclip , const object_base & player_obj )
2006-03-20 17:12:09 +00:00
{
2016-10-02 19:35:34 +00:00
const auto pos = ( & player_obj = = Viewer )
? vm_vec_scale_add ( player_obj . pos , player_obj . orient . fvec , fixmul ( player_obj . size , flash_dist ) )
: player_obj . pos ;
2006-03-20 17:12:09 +00:00
2018-07-14 17:23:15 +00:00
const auto & & seg = vmsegptridx ( player_obj . segnum ) ;
const auto & & effect_obj = object_create_explosion ( seg , pos , player_obj . size , VCLIP_PLAYER_APPEARANCE ) ;
2006-03-20 17:12:09 +00:00
if ( effect_obj ) {
2016-10-02 19:35:34 +00:00
effect_obj - > orient = player_obj . orient ;
2006-03-20 17:12:09 +00:00
2018-07-14 17:23:15 +00:00
const auto sound_num = Vclip [ VCLIP_PLAYER_APPEARANCE ] . sound_num ;
if ( sound_num > - 1 )
2021-06-28 03:37:51 +00:00
digi_link_sound_to_pos ( sound_num , seg , 0 , effect_obj - > pos , 0 , F0_5 ) ;
2006-03-20 17:12:09 +00:00
}
}
2016-10-02 19:35:34 +00:00
}
2006-03-20 17:12:09 +00:00
//
// New Game sequencing functions
//
2020-12-26 21:17:29 +00:00
namespace {
2013-11-10 03:31:22 +00:00
//get level filename. level numbers start at 1. Secret levels are -1,-2,-3
2014-07-23 02:27:22 +00:00
static const d_fname & get_level_file ( int level_num )
2013-11-10 03:31:22 +00:00
{
if ( level_num < 0 ) //secret level
2021-11-01 03:37:19 +00:00
return Current_mission - > secret_level_names [ - level_num - 1 ] ;
2013-11-10 03:31:22 +00:00
else //normal level
2021-11-01 03:37:19 +00:00
return Current_mission - > level_names [ level_num - 1 ] ;
2013-11-10 03:31:22 +00:00
}
2011-05-22 17:54:47 +00:00
// routine to calculate the checksum of the segments.
2015-06-13 22:42:21 +00:00
static void do_checksum_calc ( const uint8_t * b , int len , unsigned int * s1 , unsigned int * s2 )
2009-03-20 12:10:38 +00:00
{
while ( len - - ) {
* s1 + = * b + + ;
if ( * s1 > = 255 ) * s1 - = 255 ;
* s2 + = * s1 ;
}
}
2020-12-26 21:17:29 +00:00
}
2016-08-25 04:05:32 +00:00
namespace dsx {
2020-12-26 21:17:29 +00:00
namespace {
2013-10-27 22:00:14 +00:00
static ushort netmisc_calc_checksum ( )
2009-03-20 12:10:38 +00:00
{
unsigned int sum1 , sum2 ;
short s ;
int t ;
sum1 = sum2 = 0 ;
2016-10-15 00:53:16 +00:00
range_for ( auto & & segp , vcsegptr )
{
2019-12-22 05:34:08 +00:00
const cscusegment i = * segp ;
2020-07-05 23:34:33 +00:00
for ( auto & & [ sside , uside ] : zip ( i . s . sides , i . u . sides ) )
2015-02-14 22:48:27 +00:00
{
2020-07-05 23:34:33 +00:00
do_checksum_calc ( reinterpret_cast < const uint8_t * > ( & ( sside . get_type ( ) ) ) , 1 , & sum1 , & sum2 ) ;
2021-09-04 12:17:14 +00:00
s = INTEL_SHORT ( underlying_value ( sside . wall_num ) ) ;
2016-06-05 01:04:26 +00:00
do_checksum_calc ( reinterpret_cast < uint8_t * > ( & s ) , 2 , & sum1 , & sum2 ) ;
2021-09-04 12:17:14 +00:00
s = underlying_value ( uside . tmap_num ) ;
2020-09-11 03:08:02 +00:00
s = INTEL_SHORT ( s ) ;
2016-06-05 01:04:26 +00:00
do_checksum_calc ( reinterpret_cast < uint8_t * > ( & s ) , 2 , & sum1 , & sum2 ) ;
2021-09-04 12:17:14 +00:00
s = underlying_value ( uside . tmap_num2 ) ;
2020-08-24 01:31:28 +00:00
s = INTEL_SHORT ( s ) ;
2016-06-05 01:04:26 +00:00
do_checksum_calc ( reinterpret_cast < uint8_t * > ( & s ) , 2 , & sum1 , & sum2 ) ;
2018-12-13 02:31:38 +00:00
range_for ( auto & k , uside . uvls )
2015-02-14 22:48:27 +00:00
{
2016-07-15 03:43:03 +00:00
t = INTEL_INT ( k . u ) ;
2016-06-05 01:04:26 +00:00
do_checksum_calc ( reinterpret_cast < uint8_t * > ( & t ) , 4 , & sum1 , & sum2 ) ;
2016-07-15 03:43:03 +00:00
t = INTEL_INT ( k . v ) ;
2016-06-05 01:04:26 +00:00
do_checksum_calc ( reinterpret_cast < uint8_t * > ( & t ) , 4 , & sum1 , & sum2 ) ;
2016-07-15 03:43:03 +00:00
t = INTEL_INT ( k . l ) ;
2016-06-05 01:04:26 +00:00
do_checksum_calc ( reinterpret_cast < uint8_t * > ( & t ) , 4 , & sum1 , & sum2 ) ;
2009-03-20 12:10:38 +00:00
}
2020-07-05 23:34:33 +00:00
range_for ( auto & k , sside . normals )
2015-02-14 22:48:27 +00:00
{
2016-07-15 03:43:03 +00:00
t = INTEL_INT ( k . x ) ;
2016-06-05 01:04:26 +00:00
do_checksum_calc ( reinterpret_cast < uint8_t * > ( & t ) , 4 , & sum1 , & sum2 ) ;
2016-07-15 03:43:03 +00:00
t = INTEL_INT ( k . y ) ;
2016-06-05 01:04:26 +00:00
do_checksum_calc ( reinterpret_cast < uint8_t * > ( & t ) , 4 , & sum1 , & sum2 ) ;
2016-07-15 03:43:03 +00:00
t = INTEL_INT ( k . z ) ;
2016-06-05 01:04:26 +00:00
do_checksum_calc ( reinterpret_cast < uint8_t * > ( & t ) , 4 , & sum1 , & sum2 ) ;
2009-03-20 12:10:38 +00:00
}
}
2019-12-22 05:34:08 +00:00
range_for ( auto & j , i . s . children )
2015-02-14 22:48:27 +00:00
{
s = INTEL_SHORT ( j ) ;
2016-06-05 01:04:26 +00:00
do_checksum_calc ( reinterpret_cast < uint8_t * > ( & s ) , 2 , & sum1 , & sum2 ) ;
2009-03-20 12:10:38 +00:00
}
2020-12-26 21:17:29 +00:00
range_for ( const auto vn , i . s . verts )
2015-02-14 22:48:27 +00:00
{
2021-09-04 12:17:14 +00:00
const auto j { underlying_value ( vn ) } ;
2020-12-26 21:17:29 +00:00
static_assert ( MAX_VERTICES < = UINT16_MAX ) ;
s = INTEL_SHORT ( static_cast < uint16_t > ( j ) ) ;
2016-06-05 01:04:26 +00:00
do_checksum_calc ( reinterpret_cast < uint8_t * > ( & s ) , 2 , & sum1 , & sum2 ) ;
2009-03-20 12:10:38 +00:00
}
2019-12-22 05:34:08 +00:00
s = INTEL_SHORT ( i . u . objects ) ;
2016-06-05 01:04:26 +00:00
do_checksum_calc ( reinterpret_cast < uint8_t * > ( & s ) , 2 , & sum1 , & sum2 ) ;
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_I)
2021-11-01 03:37:19 +00:00
const auto special = underlying_value ( i . s . special ) ;
do_checksum_calc ( & special , 1 , & sum1 , & sum2 ) ;
2019-12-22 05:34:08 +00:00
do_checksum_calc ( reinterpret_cast < const uint8_t * > ( & i . s . matcen_num ) , 1 , & sum1 , & sum2 ) ;
t = INTEL_INT ( i . u . static_light ) ;
2016-06-05 01:04:26 +00:00
do_checksum_calc ( reinterpret_cast < uint8_t * > ( & t ) , 4 , & sum1 , & sum2 ) ;
2013-03-03 01:03:33 +00:00
# endif
2021-11-01 03:37:20 +00:00
{
const auto station_idx = underlying_value ( i . s . station_idx ) ;
do_checksum_calc ( & station_idx , 1 , & sum1 , & sum2 ) ;
}
2009-03-20 12:10:38 +00:00
}
sum2 % = 255 ;
return ( ( sum1 < < 8 ) + sum2 ) ;
}
2016-08-25 04:05:32 +00:00
}
2020-12-26 21:17:29 +00:00
}
2009-03-20 12:10:38 +00:00
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2015-12-13 18:00:49 +00:00
namespace dsx {
2020-08-24 01:31:28 +00:00
2009-11-15 08:22:08 +00:00
void load_level_robots ( int level_num )
{
2014-07-23 02:27:22 +00:00
const d_fname & level_name = get_level_file ( level_num ) ;
2020-08-24 01:31:28 +00:00
load_level_robots ( level_name ) ;
}
// load just the hxm file
void load_level_robots ( const d_fname & level_name )
{
2009-11-15 08:22:08 +00:00
if ( Robot_replacements_loaded ) {
2020-08-24 01:31:28 +00:00
free_polygon_models ( LevelSharedPolygonModelState ) ;
2013-04-06 21:24:40 +00:00
load_mission_ham ( ) ;
2009-11-15 08:22:08 +00:00
Robot_replacements_loaded = 0 ;
}
load_robot_replacements ( level_name ) ;
}
2020-08-24 01:31:28 +00:00
2015-12-13 18:00:48 +00:00
}
2013-03-03 01:03:33 +00:00
# endif
2009-11-15 08:22:08 +00:00
2006-03-20 17:12:09 +00:00
//load a level off disk. level numbers start at 1. Secret levels are -1,-2,-3
2016-08-25 04:05:32 +00:00
namespace dsx {
2006-03-20 17:12:09 +00:00
void LoadLevel ( int level_num , int page_in_textures )
{
2020-05-17 23:35:25 +00:00
auto & LevelSharedVertexState = LevelSharedSegmentState . get_vertex_state ( ) ;
2019-03-03 00:31:08 +00:00
auto & Objects = LevelUniqueObjectState . Objects ;
2020-05-17 23:35:25 +00:00
auto & Vertices = LevelSharedVertexState . get_vertices ( ) ;
2019-03-03 00:31:08 +00:00
auto & vcobjptr = Objects . vcptr ;
auto & vmobjptr = Objects . vmptr ;
preserve_player_object_info p ( vcobjptr , vcplayerptr ( Player_num ) - > objnum ) ;
2006-03-20 17:12:09 +00:00
2017-08-13 20:38:31 +00:00
auto & plr = get_local_player ( ) ;
auto save_player = plr ;
2006-03-20 17:12:09 +00:00
2021-11-01 03:37:19 +00:00
assert ( level_num < = Current_mission - > last_level & & level_num > = Current_mission - > last_secret_level & & level_num ! = 0 ) ;
2014-07-23 02:27:22 +00:00
const d_fname & level_name = get_level_file ( level_num ) ;
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_I)
if ( ! load_level ( level_name ) )
Current_level_num = level_num ;
gr_use_palette_table ( " palette.256 " ) ;
# elif defined(DXX_BUILD_DESCENT_II)
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 ( 0 , 0 , 0 ) ) ; //so palette switching is less obvious
2006-03-20 17:12:09 +00:00
2020-08-24 01:31:28 +00:00
load_level_robots ( level_name ) ;
Load robot customizations before use
AlexanderBorisov reported[1] a broken demo that ultimately traced to
incorrect reuse of stale data. The campaign[2] includes per-level robot
definitions for level 3. Descent incorrectly accessed Robot_info before
reloading it for the new level, so it used the level 3 data on level 4
robots during a warm start of level 4 for regular play, but used only
the level 4 data for a cold start of level 4 for regular play and for
playback of a demo recorded solely on level 4. The particular
customizations applied on level 3 caused a level 4 demo recorded with
level 3 robot customizations to use different record lengths than a
level 4 demo recorded with level 4 robot customizations. Since demos do
not record their record lengths, mismatched record lengths lead to
effectively corrupt demos. In this case, it manifested as writing 8
anim_angles (per model_num 108, the model incorrectly inherited from
level 3) instead of 1 anim_angles (per model_num 37, used only on cold
starts). The extra anim_angles were then misinterpreted as a run of 42
ND_EVENT_EOF, because the extra angles were unused and the newdemo code
defines the null byte as EOF (separate from receiving an actual EOF
indicator from the file I/O library). That run of spurious EOF caused
the demo playback code to refuse to play past the spurious EOF,
resulting in a seemingly broken demo.
As an unfortunate, but unavoidable, consequence, this change modifies
the common path (warm playthrough of every level in the campaign) to
work like the uncommon path (cold start of each level). Further, the
affected robot triggers a trap in d2x::do_silly_animation[3] when using
the correct model_num, because that polygon model has too few models for
the joints used by the robot. When using the incorrect level 3 data,
the robot animates without trapping.
[1] https://forum.dxx-rebirth.com/showthread.php?tid=1023
[2] http://www.enspiar.com/dmdb/viewMission.php?id=212
```
sha1sum ENTROPY.HOG ENTROPY.MN2
7bc7a12d00a1ddd3ae92ce90eb67d581ecab004a ENTROPY.HOG
f2688a634f22b30a02c43d8fa1a049deb5a03f70 ENTROPY.MN2
stat -c '%s %Y %n' ENTROPY.HOG ENTROPY.MN2
2402638 875757712 ENTROPY.HOG
511 875757164 ENTROPY.MN2
```
[3]
```
799 if (jointnum >= Polygon_models[objp.rtype.pobj_info.model_num].n_models) {
800 Int3(); // Contact Mike: incompatible data, illegal jointnum, problem in pof file?
801 continue;
802 }
```
2017-12-31 21:11:25 +00:00
2018-12-30 00:43:58 +00:00
auto & LevelSharedDestructibleLightState = LevelSharedSegmentState . DestructibleLights ;
2018-12-30 00:43:58 +00:00
int load_ret = load_level ( LevelSharedDestructibleLightState , level_name ) ; //actually load the data from disk!
2006-03-20 17:12:09 +00:00
if ( load_ret )
2016-11-13 05:27:18 +00:00
Error ( " Could not load level file <%s>, error = %d " , static_cast < const char * > ( level_name ) , load_ret ) ;
2006-03-20 17:12:09 +00:00
Current_level_num = level_num ;
load_palette ( Current_level_palette , 1 , 1 ) ; //don't change screen
2013-03-03 01:03:33 +00:00
# endif
2006-03-20 17:12:09 +00:00
2016-11-15 08:18:56 +00:00
# if DXX_USE_EDITOR
if ( ! EditorWindow )
# endif
2021-09-12 16:20:52 +00:00
{
2021-09-12 16:20:52 +00:00
gr_set_default_canvas ( ) ;
show_boxed_message ( * grd_curcanv , TXT_LOADING ) ;
2021-09-12 16:20:52 +00:00
gr_flip ( ) ;
}
2010-06-27 11:28:26 +00:00
# ifdef RELEASE
timer_delay ( F1_0 ) ;
# endif
2008-03-20 23:23:46 +00:00
2006-03-20 17:12:09 +00:00
load_endlevel_data ( level_num ) ;
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_I)
load_custom_data ( level_name ) ;
# elif defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
if ( EMULATING_D1 )
load_d1_bitmap_replacements ( ) ;
else
load_bitmap_replacements ( level_name ) ;
if ( page_in_textures )
piggy_load_level_data ( ) ;
2013-03-03 01:03:33 +00:00
# endif
2006-03-20 17:12:09 +00:00
2011-05-22 17:54:47 +00:00
my_segments_checksum = netmisc_calc_checksum ( ) ;
2006-03-20 17:12:09 +00:00
reset_network_objects ( ) ;
2017-08-13 20:38:31 +00:00
plr = save_player ;
2006-03-20 17:12:09 +00:00
2018-12-30 00:43:57 +00:00
auto & vcvertptr = Vertices . vcptr ;
2018-12-30 00:43:57 +00:00
set_sound_sources ( vcsegptridx , vcvertptr ) ;
2006-03-20 17:12:09 +00:00
2016-11-15 08:18:56 +00:00
# if DXX_USE_EDITOR
if ( ! EditorWindow )
# endif
songs_play_level_song ( Current_level_num , 0 ) ;
2006-03-20 17:12:09 +00:00
gr_palette_load ( gr_palette ) ; //actually load the palette
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_I)
if ( page_in_textures )
piggy_load_level_data ( ) ;
# endif
2016-03-19 19:08:10 +00:00
2017-07-26 03:15:59 +00:00
gameseq_init_network_players ( Objects ) ;
2019-03-03 00:31:08 +00:00
p . restore ( vmobjptr ) ;
2006-03-20 17:12:09 +00:00
}
2016-08-25 04:05:32 +00:00
}
2006-03-20 17:12:09 +00:00
//sets up Player_num & ConsoleObject
void InitPlayerObject ( )
{
2019-03-03 00:31:08 +00:00
auto & Objects = LevelUniqueObjectState . Objects ;
auto & vmobjptr = Objects . vmptr ;
2014-09-21 22:10:12 +00:00
Assert ( Player_num < MAX_PLAYERS ) ;
2006-03-20 17:12:09 +00:00
if ( Player_num ! = 0 ) {
2017-08-13 20:38:31 +00:00
Players [ 0u ] = get_local_player ( ) ;
2006-03-20 17:12:09 +00:00
Player_num = 0 ;
}
2017-08-13 20:38:31 +00:00
auto & plr = get_local_player ( ) ;
plr . objnum = object_first ;
const auto & & console = vmobjptr ( plr . objnum ) ;
2015-07-12 01:04:20 +00:00
ConsoleObject = console ;
console - > type = OBJ_PLAYER ;
set_player_id ( console , Player_num ) ;
2020-08-10 03:45:13 +00:00
console - > control_source = object : : control_type : : flying ;
2020-08-10 03:45:13 +00:00
console - > movement_source = object : : movement_type : : physics ;
2006-03-20 17:12:09 +00:00
}
//starts a new game on the given level
2016-08-25 04:05:32 +00:00
namespace dsx {
2019-06-27 03:26:20 +00:00
void StartNewGame ( const int start_level )
2006-03-20 17:12:09 +00:00
{
2020-04-26 17:26:23 +00:00
GameUniqueState . quicksave_selection = d_game_unique_state : : save_slot : : None ; // for first blind save, pick slot to save in
2019-06-27 03:26:20 +00:00
reset_globals_for_new_game ( ) ;
2010-02-25 03:08:10 +00:00
2006-03-20 17:12:09 +00:00
Game_mode = GM_NORMAL ;
2010-06-14 08:13:16 +00:00
2006-03-20 17:12:09 +00:00
InitPlayerObject ( ) ; //make sure player's object set up
2019-07-16 04:00:50 +00:00
LevelUniqueObjectState . accumulated_robots = 0 ;
2019-07-16 04:00:50 +00:00
LevelUniqueObjectState . total_hostages = 0 ;
2019-07-16 04:00:50 +00:00
GameUniqueState . accumulated_robots = 0 ;
2019-07-16 04:00:50 +00:00
GameUniqueState . total_hostages = 0 ;
2012-04-15 13:32:48 +00:00
init_player_stats_game ( Player_num ) ; //clear all stats
2006-03-20 17:12:09 +00:00
N_players = 1 ;
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
if ( start_level < 0 )
2020-04-19 20:47:07 +00:00
{
/* Allow an autosave as soon as the user exits the secret level.
*/
state_set_immediate_autosave ( GameUniqueState ) ;
2006-03-20 17:12:09 +00:00
StartNewLevelSecret ( start_level , 0 ) ;
2020-04-19 20:47:07 +00:00
}
2006-03-20 17:12:09 +00:00
else
2013-03-03 01:03:33 +00:00
# endif
2020-04-19 20:47:07 +00:00
{
2013-03-23 20:50:35 +00:00
StartNewLevel ( start_level ) ;
2020-04-19 20:47:07 +00:00
/* Override Next_autosave to avoid creating an autosave
* immediately after starting a new game . No state can be lost
* at that point , so there is no reason to save .
*/
state_set_next_autosave ( GameUniqueState , PlayerCfg . SPGameplayOptions . AutosaveInterval ) ;
}
2006-03-20 17:12:09 +00:00
2019-08-25 17:53:57 +00:00
auto & plr = get_local_player ( ) ;
plr . starting_level = start_level ; // Mark where they started
plr . callsign = InterfaceUniqueState . PilotName ;
2006-03-20 17:12:09 +00:00
game_disable_cheats ( ) ;
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
init_seismic_disturbances ( ) ;
2013-03-03 01:03:33 +00:00
# endif
2006-03-20 17:12:09 +00:00
}
2020-12-26 21:17:29 +00:00
namespace {
2006-03-20 17:12:09 +00:00
// -----------------------------------------------------------------------------
// Does the bonus scoring.
// Call with dead_flag = 1 if player died, but deserves some portion of bonus (only skill points), anyway.
2016-10-15 00:53:14 +00:00
static void DoEndLevelScoreGlitz ( )
2006-03-20 17:12:09 +00:00
{
2019-03-03 00:31:08 +00:00
auto & Objects = LevelUniqueObjectState . Objects ;
auto & vmobjptr = Objects . vmptr ;
2006-03-20 17:12:09 +00:00
int level_points , skill_points , energy_points , shield_points , hostage_points ;
2013-11-10 03:31:22 +00:00
# define N_GLITZITEMS 9
2018-08-26 18:10:36 +00:00
char m_str [ N_GLITZITEMS ] [ 32 ] ;
2013-11-10 03:31:22 +00:00
newmenu_item m [ N_GLITZITEMS ] ;
2020-09-11 03:08:02 +00:00
int i ;
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_I)
gr_palette_load ( gr_palette ) ;
# elif defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
int mine_level ;
// Compute level player is on, deal with secret levels (negative numbers)
2015-07-25 23:10:46 +00:00
mine_level = get_local_player ( ) . level ;
2006-03-20 17:12:09 +00:00
if ( mine_level < 0 )
2021-11-01 03:37:19 +00:00
mine_level * = - ( Current_mission - > last_level / Current_mission - > n_secret_levels ) ;
2013-03-03 01:03:33 +00:00
# endif
2006-03-20 17:12:09 +00:00
2016-10-28 03:39:42 +00:00
auto & plrobj = get_local_plrobj ( ) ;
auto & player_info = plrobj . ctype . player_info ;
2016-10-15 00:53:19 +00:00
level_points = player_info . mission . score - player_info . mission . last_score ;
2006-03-20 17:12:09 +00:00
2019-06-27 03:26:20 +00:00
const auto Difficulty_level = GameUniqueState . Difficulty_level ;
2011-02-14 21:27:07 +00:00
if ( ! cheats . enabled ) {
2006-03-20 17:12:09 +00:00
if ( Difficulty_level > 1 ) {
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_I)
skill_points = level_points * ( Difficulty_level - 1 ) / 2 ;
# elif defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
skill_points = level_points * ( Difficulty_level ) / 4 ;
2013-03-03 01:03:33 +00:00
# endif
2006-03-20 17:12:09 +00:00
skill_points - = skill_points % 100 ;
} else
skill_points = 0 ;
2016-10-15 00:53:19 +00:00
hostage_points = player_info . mission . hostages_on_board * 500 * ( Difficulty_level + 1 ) ;
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_I)
2016-10-28 03:39:42 +00:00
shield_points = f2i ( plrobj . shields ) * 10 * ( Difficulty_level + 1 ) ;
2016-09-11 18:49:13 +00:00
energy_points = f2i ( player_info . energy ) * 5 * ( Difficulty_level + 1 ) ;
2013-03-03 01:03:33 +00:00
# elif defined(DXX_BUILD_DESCENT_II)
2016-10-28 03:39:42 +00:00
shield_points = f2i ( plrobj . shields ) * 5 * mine_level ;
2016-09-11 18:49:13 +00:00
energy_points = f2i ( player_info . energy ) * 2 * mine_level ;
2006-03-20 17:12:09 +00:00
shield_points - = shield_points % 50 ;
energy_points - = energy_points % 50 ;
2013-03-03 01:03:33 +00:00
# endif
2006-03-20 17:12:09 +00:00
} else {
skill_points = 0 ;
shield_points = 0 ;
energy_points = 0 ;
hostage_points = 0 ;
}
2017-08-13 20:38:31 +00:00
auto & plr = get_local_player ( ) ;
2018-08-26 18:10:36 +00:00
2020-09-11 03:08:02 +00:00
unsigned c = 0 ;
2018-08-26 18:10:36 +00:00
snprintf ( m_str [ c + + ] , sizeof ( m_str [ 0 ] ) , " %s%i " , TXT_SHIELD_BONUS , shield_points ) ; // Return at start to lower menu...
snprintf ( m_str [ c + + ] , sizeof ( m_str [ 0 ] ) , " %s%i " , TXT_ENERGY_BONUS , energy_points ) ;
snprintf ( m_str [ c + + ] , sizeof ( m_str [ 0 ] ) , " %s%i " , TXT_HOSTAGE_BONUS , hostage_points ) ;
snprintf ( m_str [ c + + ] , sizeof ( m_str [ 0 ] ) , " %s%i " , TXT_SKILL_BONUS , skill_points ) ;
const unsigned hostages_on_board = player_info . mission . hostages_on_board ;
unsigned all_hostage_points = 0 ;
unsigned endgame_points = 0 ;
uint8_t is_last_level = 0 ;
auto & hostage_text = m_str [ c + + ] ;
if ( cheats . enabled )
snprintf ( hostage_text , sizeof ( hostage_text ) , " Hostages saved: \t %u " , hostages_on_board ) ;
2019-07-16 04:00:50 +00:00
else if ( const auto hostages_lost = LevelUniqueObjectState . total_hostages - hostages_on_board )
2018-08-26 18:10:36 +00:00
snprintf ( hostage_text , sizeof ( hostage_text ) , " Hostages lost: \t %u " , hostages_lost ) ;
else
2017-08-13 20:38:31 +00:00
{
2018-08-26 18:10:36 +00:00
all_hostage_points = hostages_on_board * 1000 * ( Difficulty_level + 1 ) ;
snprintf ( hostage_text , sizeof ( hostage_text ) , " %s%i \n " , TXT_FULL_RESCUE_BONUS , all_hostage_points ) ;
}
2006-03-20 17:12:09 +00:00
2018-08-26 18:10:36 +00:00
auto & endgame_text = m_str [ c + + ] ;
endgame_text [ 0 ] = 0 ;
if ( cheats . enabled )
{
/* Nothing */
}
2021-11-01 03:37:19 +00:00
else if ( ! ( Game_mode & GM_MULTI ) & & plr . lives & & Current_level_num = = Current_mission - > last_level )
2017-08-13 20:38:31 +00:00
{ //player has finished the game!
endgame_points = plr . lives * 10000 ;
2016-01-03 20:21:36 +00:00
snprintf ( endgame_text , sizeof ( endgame_text ) , " %s%i \n " , TXT_SHIP_BONUS , endgame_points ) ;
2006-03-20 17:12:09 +00:00
is_last_level = 1 ;
2018-08-26 18:10:36 +00:00
}
2006-03-20 17:12:09 +00:00
2021-09-12 16:20:52 +00:00
add_bonus_points_to_score ( player_info , skill_points + energy_points + shield_points + hostage_points + all_hostage_points + endgame_points , Game_mode ) ;
2006-03-20 17:12:09 +00:00
2016-01-03 20:21:36 +00:00
snprintf ( m_str [ c + + ] , sizeof ( m_str [ 0 ] ) , " %s%i \n " , TXT_TOTAL_BONUS , shield_points + energy_points + hostage_points + skill_points + all_hostage_points + endgame_points ) ;
2016-10-15 00:53:19 +00:00
snprintf ( m_str [ c + + ] , sizeof ( m_str [ 0 ] ) , " %s%i " , TXT_TOTAL_SCORE , player_info . mission . score ) ;
2006-03-20 17:12:09 +00:00
for ( i = 0 ; i < c ; i + + ) {
2015-01-18 01:58:31 +00:00
nm_set_item_text ( m [ i ] , m_str [ i ] ) ;
2006-03-20 17:12:09 +00:00
}
2014-12-18 04:12:39 +00:00
auto current_level_num = Current_level_num ;
const auto txt_level = ( current_level_num < 0 ) ? ( current_level_num = - current_level_num , TXT_SECRET_LEVEL ) : TXT_LEVEL ;
2020-12-19 16:13:26 +00:00
char subtitle [ 128 ] ;
snprintf ( subtitle , sizeof ( subtitle ) , " %s%s %d %s \n %s %s " , is_last_level ? " \n \n \n " : " \n " , txt_level , current_level_num , TXT_COMPLETE , static_cast < const char * > ( Current_level_name ) , TXT_DESTROYED ) ;
2006-03-20 17:12:09 +00:00
Assert ( c < = N_GLITZITEMS ) ;
2020-12-27 22:03:09 +00:00
struct glitz_menu : passive_newmenu
{
2021-09-12 16:20:52 +00:00
glitz_menu ( grs_canvas & canvas , menu_subtitle subtitle , partial_range_t < newmenu_item * > items ) :
passive_newmenu ( menu_title { nullptr } , subtitle , menu_filename { GLITZ_BACKGROUND } , tiny_mode_flag : : normal , tab_processing_flag : : ignore , adjusted_citem : : create ( items , 0 ) , canvas , draw_box_flag : : none )
2020-12-27 22:03:09 +00:00
{
}
} ;
2021-09-12 16:20:52 +00:00
run_blocking_newmenu < glitz_menu > ( * grd_curcanv , menu_subtitle { subtitle } , partial_range ( m , c ) ) ;
2006-03-20 17:12:09 +00:00
}
2020-09-11 03:08:02 +00:00
2016-08-25 04:05:32 +00:00
}
2006-03-20 17:12:09 +00:00
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2020-12-26 21:17:29 +00:00
namespace {
2006-03-20 17:12:09 +00:00
// -----------------------------------------------------------------------------------------------------
//called when the player is starting a level (new game or new ship)
2013-10-27 22:00:14 +00:00
static void StartSecretLevel ( )
2006-03-20 17:12:09 +00:00
{
2019-03-03 00:31:08 +00:00
auto & Objects = LevelUniqueObjectState . Objects ;
auto & vmobjptridx = Objects . vmptridx ;
2015-12-15 04:09:35 +00:00
Assert ( Player_dead_state = = player_dead_state : : no ) ;
2006-03-20 17:12:09 +00:00
2017-07-26 03:15:59 +00:00
InitPlayerPosition ( vmobjptridx , vmsegptridx , 0 ) ;
2006-03-20 17:12:09 +00:00
verify_console_object ( ) ;
2020-08-10 03:45:13 +00:00
ConsoleObject - > control_source = object : : control_type : : flying ;
2020-08-10 03:45:13 +00:00
ConsoleObject - > movement_source = object : : movement_type : : physics ;
2006-03-20 17:12:09 +00:00
// -- WHY? -- disable_matcens();
clear_transient_objects ( 0 ) ; //0 means leave proximity bombs
// create_player_appearance_effect(ConsoleObject);
Do_appearance_effect = 1 ;
ai_reset_all_paths ( ) ;
// -- NO? -- reset_time();
reset_rear_view ( ) ;
2016-12-10 17:51:09 +00:00
auto & player_info = ConsoleObject - > ctype . player_info ;
2016-12-10 17:51:09 +00:00
player_info . Auto_fire_fusion_cannon_time = 0 ;
2016-12-10 17:51:09 +00:00
player_info . Fusion_charge = 0 ;
2006-03-20 17:12:09 +00:00
}
2020-12-26 21:17:29 +00:00
}
2006-03-20 17:12:09 +00:00
// Returns true if secret level has been destroyed.
int p_secret_level_destroyed ( void )
{
if ( First_secret_visit ) {
return 0 ; // Never been there, can't have been destroyed.
} else {
2011-06-01 07:59:55 +00:00
if ( PHYSFSX_exists ( SECRETC_FILENAME , 0 ) )
2006-03-20 17:12:09 +00:00
{
return 0 ;
} else {
return 1 ;
}
}
}
// -----------------------------------------------------------------------------------------------------
2010-07-27 11:41:43 +00:00
# define TXT_SECRET_RETURN "Returning to level %i", Entered_from_level
# define TXT_SECRET_ADVANCE "Base level destroyed.\nAdvancing to level %i", Entered_from_level+1
2013-03-03 01:03:33 +00:00
# endif
2010-07-27 11:41:43 +00:00
2020-12-26 21:17:29 +00:00
}
namespace {
2013-12-08 23:37:40 +00:00
static void do_screen_message ( const char * msg ) __attribute_nonnull ( ) ;
static void do_screen_message ( const char * msg )
2010-07-27 11:41:43 +00:00
{
if ( Game_mode & GM_MULTI )
return ;
2020-12-27 22:03:09 +00:00
using items_type = std : : array < newmenu_item , 1 > ;
struct glitz_menu : items_type , passive_newmenu
{
2021-09-12 16:20:52 +00:00
glitz_menu ( grs_canvas & canvas , menu_subtitle subtitle ) :
2020-12-27 22:03:09 +00:00
items_type { {
2021-06-28 03:37:49 +00:00
newmenu_item : : nm_item_menu { TXT_OK } ,
2020-12-27 22:03:09 +00:00
} } ,
2021-09-12 16:20:52 +00:00
passive_newmenu ( menu_title { nullptr } , subtitle , menu_filename { GLITZ_BACKGROUND } , tiny_mode_flag : : normal , tab_processing_flag : : ignore , adjusted_citem : : create ( * static_cast < items_type * > ( this ) , 0 ) , canvas , draw_box_flag : : menu_background )
2020-12-27 22:03:09 +00:00
{
}
} ;
2021-09-12 16:20:52 +00:00
run_blocking_newmenu < glitz_menu > ( * grd_curcanv , menu_subtitle { msg } ) ;
2006-03-20 17:12:09 +00:00
}
2020-12-26 21:17:29 +00:00
}
2015-12-13 18:00:49 +00:00
namespace dsx {
2019-03-03 00:31:07 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2020-12-26 21:17:29 +00:00
namespace {
2013-12-08 23:37:40 +00:00
static void do_screen_message_fmt ( const char * fmt , . . . ) __attribute_format_printf ( 1 , 2 ) ;
static void do_screen_message_fmt ( const char * fmt , . . . )
{
va_list arglist ;
char msg [ 1024 ] ;
va_start ( arglist , fmt ) ;
vsnprintf ( msg , sizeof ( msg ) , fmt , arglist ) ;
va_end ( arglist ) ;
do_screen_message ( msg ) ;
}
# define do_screen_message(F,...) dxx_call_printf_checked(do_screen_message_fmt,do_screen_message,(),F,##__VA_ARGS__)
2006-03-20 17:12:09 +00:00
// -----------------------------------------------------------------------------------------------------
// called when the player is starting a new level for normal game mode and restore state
// Need to deal with whether this is the first time coming to this level or not. If not the
// first time, instead of initializing various things, need to do a game restore for all the
// robots, powerups, walls, doors, etc.
2013-09-22 22:26:27 +00:00
static void StartNewLevelSecret ( int level_num , int page_in_textures )
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 & vmobjptr = Objects . vmptr ;
2006-03-20 17:12:09 +00:00
2007-04-01 02:14:38 +00:00
last_drawn_cockpit = - 1 ;
2006-03-20 17:12:09 +00:00
if ( Newdemo_state = = ND_STATE_PAUSED )
Newdemo_state = ND_STATE_RECORDING ;
2010-06-14 08:13:16 +00:00
2006-03-20 17:12:09 +00:00
if ( Newdemo_state = = ND_STATE_RECORDING ) {
newdemo_set_new_level ( level_num ) ;
2008-10-16 17:27:02 +00:00
newdemo_record_start_frame ( FrameTime ) ;
2006-03-20 17:12:09 +00:00
} else if ( Newdemo_state ! = ND_STATE_PLAYBACK ) {
set_screen_mode ( SCREEN_MENU ) ;
2010-06-14 08:13:16 +00:00
2006-03-20 17:12:09 +00:00
if ( First_secret_visit ) {
2010-07-27 11:41:43 +00:00
do_screen_message ( TXT_SECRET_EXIT ) ;
2006-03-20 17:12:09 +00:00
} else {
2011-06-01 07:59:55 +00:00
if ( PHYSFSX_exists ( SECRETC_FILENAME , 0 ) )
2006-03-20 17:12:09 +00:00
{
2010-07-27 11:41:43 +00:00
do_screen_message ( TXT_SECRET_EXIT ) ;
2006-03-20 17:12:09 +00:00
} else {
2010-07-27 11:41:43 +00:00
do_screen_message ( " Secret level already destroyed. \n Advancing to level %i. " , Current_level_num + 1 ) ;
2006-03-20 17:12:09 +00:00
}
}
}
LoadLevel ( level_num , page_in_textures ) ;
2011-09-26 23:31:19 +00:00
Assert ( Current_level_num = = level_num ) ; // make sure level set right
2006-03-20 17:12:09 +00:00
HUD_clear_messages ( ) ;
2020-08-28 00:18:45 +00:00
automap_clear_visited ( LevelUniqueAutomapState ) ;
2006-03-20 17:12:09 +00:00
2015-07-25 23:10:45 +00:00
Viewer = & get_local_plrobj ( ) ;
2006-03-20 17:12:09 +00:00
gameseq_remove_unused_players ( ) ;
Game_suspended = 0 ;
2019-08-15 01:34:22 +00:00
LevelUniqueControlCenterState . Control_center_destroyed = 0 ;
2006-03-20 17:12:09 +00:00
init_cockpit ( ) ;
reset_palette_add ( ) ;
if ( First_secret_visit | | ( Newdemo_state = = ND_STATE_PLAYBACK ) ) {
init_robots_for_level ( ) ;
init_ai_objects ( ) ;
init_smega_detonates ( ) ;
init_morphs ( ) ;
init_all_matcens ( ) ;
reset_special_effects ( ) ;
StartSecretLevel ( ) ;
} else {
2011-06-01 07:59:55 +00:00
if ( PHYSFSX_exists ( SECRETC_FILENAME , 0 ) )
2006-03-20 17:12:09 +00:00
{
2016-08-28 22:41:48 +00:00
auto & player_info = get_local_plrobj ( ) . ctype . player_info ;
2016-08-28 22:41:49 +00:00
const auto pw_save = player_info . Primary_weapon ;
2016-08-28 22:41:48 +00:00
const auto sw_save = player_info . Secondary_weapon ;
2015-04-19 04:18:49 +00:00
state_restore_all ( 1 , secret_restore : : survived , SECRETC_FILENAME , blind_save : : no ) ;
2016-08-28 22:41:49 +00:00
player_info . Primary_weapon = pw_save ;
2016-08-28 22:41:48 +00:00
player_info . Secondary_weapon = sw_save ;
2006-03-20 17:12:09 +00:00
reset_special_effects ( ) ;
StartSecretLevel ( ) ;
// -- No: This is only for returning to base level: set_pos_from_return_segment();
} else {
2010-07-27 11:41:43 +00:00
do_screen_message ( " Secret level already destroyed. \n Advancing to level %i. " , Current_level_num + 1 ) ;
2006-03-20 17:12:09 +00:00
return ;
}
}
if ( First_secret_visit ) {
copy_defaults_to_robot_all ( ) ;
}
init_controlcen_for_level ( ) ;
2011-09-26 23:31:19 +00:00
// Say player can use FLASH cheat to mark path to exit.
2021-11-01 03:37:19 +00:00
LevelUniqueObjectState . Level_path_created = 0 ;
2006-03-20 17:12:09 +00:00
First_secret_visit = 0 ;
}
2015-04-19 04:18:49 +00:00
static int Entered_from_level ;
2006-03-20 17:12:09 +00:00
2021-09-19 10:53:48 +00:00
static void filter_objects_from_level ( const d_powerup_info_array & Powerup_info , const d_vclip_array & Vclip , fvmobjptr & vmobjptr )
{
for ( const auto & & objp : vmobjptr )
{
object_base & obj = * objp ;
if ( obj . type = = OBJ_POWERUP )
{
const auto powerup_id = get_powerup_id ( obj ) ;
if ( powerup_id = = POW_FLAG_RED | | powerup_id = = POW_FLAG_BLUE )
bash_to_shield ( Powerup_info , Vclip , obj ) ;
}
}
}
2020-12-26 21:17:29 +00:00
}
2006-03-20 17:12:09 +00:00
// ---------------------------------------------------------------------------------------------------------------
// Called from switch.c when player is on a secret level and hits exit to return to base level.
2016-12-24 08:50:29 +00:00
window_event_result ExitSecretLevel ( )
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 & vmobjptr = Objects . vmptr ;
2016-12-24 08:50:29 +00:00
auto result = window_event_result : : handled ;
2006-03-20 17:12:09 +00:00
if ( Newdemo_state = = ND_STATE_PLAYBACK )
2016-12-24 08:50:29 +00:00
return window_event_result : : ignored ;
2006-03-20 17:12:09 +00:00
2021-11-01 03:37:18 +00:00
const auto g = Game_wind ;
if ( g )
g - > set_visible ( 0 ) ;
2010-06-27 11:28:26 +00:00
2019-08-15 01:34:22 +00:00
if ( ! LevelUniqueControlCenterState . Control_center_destroyed )
{
2015-04-19 04:18:49 +00:00
state_save_all ( secret_save : : c , blind_save : : no ) ;
2006-03-20 17:12:09 +00:00
}
2011-06-01 07:59:55 +00:00
if ( PHYSFSX_exists ( SECRETB_FILENAME , 0 ) )
2006-03-20 17:12:09 +00:00
{
2010-07-27 11:41:43 +00:00
do_screen_message ( TXT_SECRET_RETURN ) ;
2016-08-28 22:41:48 +00:00
auto & player_info = get_local_plrobj ( ) . ctype . player_info ;
2016-08-28 22:41:49 +00:00
const auto pw_save = player_info . Primary_weapon ;
2016-08-28 22:41:48 +00:00
const auto sw_save = player_info . Secondary_weapon ;
2015-04-19 04:18:49 +00:00
state_restore_all ( 1 , secret_restore : : survived , SECRETB_FILENAME , blind_save : : no ) ;
2016-08-28 22:41:49 +00:00
player_info . Primary_weapon = pw_save ;
2016-08-28 22:41:48 +00:00
player_info . Secondary_weapon = sw_save ;
2006-03-20 17:12:09 +00:00
} else {
// File doesn't exist, so can't return to base level. Advance to next one.
2021-11-01 03:37:19 +00:00
if ( Entered_from_level = = Current_mission - > last_level )
2016-12-24 08:50:29 +00:00
{
2006-03-20 17:12:09 +00:00
DoEndGame ( ) ;
2016-12-24 08:50:29 +00:00
result = window_event_result : : close ;
}
2006-03-20 17:12:09 +00:00
else {
2010-07-27 11:41:43 +00:00
do_screen_message ( TXT_SECRET_ADVANCE ) ;
2013-03-23 20:50:35 +00:00
StartNewLevel ( Entered_from_level + 1 ) ;
2006-03-20 17:12:09 +00:00
}
}
2010-06-27 11:28:26 +00:00
2021-11-01 03:37:18 +00:00
if ( g )
g - > set_visible ( 1 ) ;
2010-07-04 13:12:08 +00:00
reset_time ( ) ;
2016-12-24 08:50:29 +00:00
return result ;
2006-03-20 17:12:09 +00:00
}
// ---------------------------------------------------------------------------------------------------------------
// Set invulnerable_time and cloak_time in player struct to preserve amount of time left to
// be invulnerable or cloaked.
2016-10-28 03:39:42 +00:00
void do_cloak_invul_secret_stuff ( fix64 old_gametime , player_info & player_info )
2006-03-20 17:12:09 +00:00
{
2016-10-02 00:34:40 +00:00
auto & pl_flags = player_info . powerup_flags ;
2016-07-03 00:54:15 +00:00
if ( pl_flags & PLAYER_FLAGS_INVULNERABLE )
{
2010-12-22 00:17:59 +00:00
fix64 time_used ;
2006-03-20 17:12:09 +00:00
2016-08-28 22:41:49 +00:00
auto & t = player_info . invulnerable_time ;
2016-07-03 00:54:15 +00:00
time_used = old_gametime - t ;
t = GameTime64 - time_used ;
2006-03-20 17:12:09 +00:00
}
2016-07-03 00:54:15 +00:00
if ( pl_flags & PLAYER_FLAGS_CLOAKED )
{
2006-03-20 17:12:09 +00:00
fix time_used ;
2016-10-02 00:34:39 +00:00
auto & t = player_info . cloak_time ;
2016-07-03 00:54:15 +00:00
time_used = old_gametime - t ;
t = GameTime64 - time_used ;
2006-03-20 17:12:09 +00:00
}
}
// ---------------------------------------------------------------------------------------------------------------
// Called from switch.c when player passes through secret exit. That means he was on a non-secret level and he
// is passing to the secret level.
// Do a savegame.
void EnterSecretLevel ( void )
{
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
Assert ( ! ( Game_mode & GM_MULTI ) ) ;
2021-11-01 03:37:18 +00:00
const auto g = Game_wind ;
if ( g )
g - > set_visible ( 0 ) ;
2010-06-27 11:28:26 +00:00
2010-09-17 11:09:57 +00:00
digi_play_sample ( SOUND_SECRET_EXIT , F1_0 ) ; // after above call which stops all sounds
2006-03-20 17:12:09 +00:00
Entered_from_level = Current_level_num ;
2019-08-15 01:34:22 +00:00
if ( LevelUniqueControlCenterState . Control_center_destroyed )
2016-10-15 00:53:14 +00:00
DoEndLevelScoreGlitz ( ) ;
2006-03-20 17:12:09 +00:00
if ( Newdemo_state ! = ND_STATE_PLAYBACK )
2015-04-19 04:18:49 +00:00
state_save_all ( secret_save : : b , blind_save : : no ) ; // Not between levels (ie, save all), IS a secret level, NO filename override
2006-03-20 17:12:09 +00:00
// Find secret level number to go to, stuff in Next_level_num.
2021-11-01 03:37:19 +00:00
int8_t Next_level_num { } ;
2021-11-01 03:37:19 +00:00
for ( i = 0 ; i < - Current_mission - > last_secret_level ; + + i )
2021-11-01 03:37:19 +00:00
if ( Current_mission - > secret_level_table [ i ] = = Current_level_num )
{
2006-03-20 17:12:09 +00:00
Next_level_num = - ( i + 1 ) ;
break ;
2021-11-01 03:37:19 +00:00
}
else if ( Current_mission - > secret_level_table [ i ] > Current_level_num )
{ // Allows multiple exits in same group.
2006-03-20 17:12:09 +00:00
Next_level_num = - i ;
break ;
}
2021-11-01 03:37:19 +00:00
if ( ! ( i < - Current_mission - > last_secret_level ) ) //didn't find level, so must be last
Next_level_num = Current_mission - > last_secret_level ;
2006-03-20 17:12:09 +00:00
2007-04-14 20:44:09 +00:00
// NMN 04/09/07 Do a REAL start level routine if we are playing a D1 level so we have
// briefings
if ( EMULATING_D1 )
{
set_screen_mode ( SCREEN_MENU ) ;
2010-07-27 11:41:43 +00:00
do_screen_message ( " Alternate Exit Found! \n \n Proceeding to Secret Level! " ) ;
2013-03-23 20:50:35 +00:00
StartNewLevel ( Next_level_num ) ;
2007-04-14 20:44:09 +00:00
} else {
StartNewLevelSecret ( Next_level_num , 1 ) ;
}
// END NMN
2010-06-14 08:13:16 +00:00
2006-03-20 17:12:09 +00:00
// do_cloak_invul_stuff();
2021-11-01 03:37:18 +00:00
if ( g )
g - > set_visible ( 1 ) ;
2010-07-04 13:12:08 +00:00
reset_time ( ) ;
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
//called when the player has finished a level
2016-12-24 08:50:29 +00:00
window_event_result PlayerFinishedLevel ( int secret_flag )
2006-03-20 17:12:09 +00:00
{
2019-03-03 00:31:08 +00:00
auto & Objects = LevelUniqueObjectState . Objects ;
auto & vmobjptr = Objects . vmptr ;
2021-11-01 03:37:18 +00:00
const auto g = Game_wind ;
if ( g )
g - > set_visible ( 0 ) ;
2010-06-27 11:28:26 +00:00
2006-03-20 17:12:09 +00:00
//credit the player for hostages
2016-10-15 00:53:19 +00:00
auto & player_info = get_local_plrobj ( ) . ctype . player_info ;
2016-10-15 00:53:19 +00:00
player_info . mission . hostages_rescued_total + = player_info . mission . hostages_on_board ;
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_I)
if ( ! ( Game_mode & GM_MULTI ) & & ( secret_flag ) ) {
2020-12-27 22:03:09 +00:00
using items_type = std : : array < newmenu_item , 1 > ;
struct message_menu : items_type , passive_newmenu
{
2021-09-12 16:20:52 +00:00
message_menu ( grs_canvas & canvas ) :
2020-12-27 22:03:09 +00:00
items_type { {
2021-06-28 03:37:49 +00:00
newmenu_item : : nm_item_text { " " } , //TXT_SECRET_EXIT;
2020-12-27 22:03:09 +00:00
} } ,
2021-09-12 16:20:52 +00:00
passive_newmenu ( menu_title { nullptr } , menu_subtitle { TXT_SECRET_EXIT } , menu_filename { Menu_pcx_name } , tiny_mode_flag : : normal , tab_processing_flag : : ignore , adjusted_citem : : create ( * static_cast < items_type * > ( this ) , 0 ) , canvas , draw_box_flag : : none )
2020-12-27 22:03:09 +00:00
{
}
} ;
2021-09-12 16:20:52 +00:00
run_blocking_newmenu < message_menu > ( * grd_curcanv ) ;
2013-03-03 01:03:33 +00:00
}
# elif defined(DXX_BUILD_DESCENT_II)
2013-11-10 03:31:22 +00:00
Assert ( ! secret_flag ) ;
2015-03-23 01:43:31 +00:00
# endif
2006-03-20 17:12:09 +00:00
if ( Game_mode & GM_NETWORK )
2015-07-25 23:10:46 +00:00
get_local_player ( ) . connected = CONNECT_WAITING ; // Finished but did not die
2006-03-20 17:12:09 +00:00
2007-04-01 02:14:38 +00:00
last_drawn_cockpit = - 1 ;
2006-03-20 17:12:09 +00:00
2016-12-24 08:50:29 +00:00
auto result = AdvanceLevel ( secret_flag ) ; //now go on to the next one (if one)
2013-03-03 01:03:33 +00:00
2021-11-01 03:37:18 +00:00
if ( g )
g - > set_visible ( 1 ) ;
2013-03-03 01:03:33 +00:00
reset_time ( ) ;
2016-12-24 08:50:29 +00:00
return result ;
2013-03-03 01:03:33 +00:00
}
2006-03-20 17:12:09 +00:00
2015-03-23 01:43:31 +00:00
# if defined(DXX_BUILD_DESCENT_II)
# define MOVIE_REQUIRED 1
2006-03-20 17:12:09 +00:00
# define ENDMOVIE "end"
2015-03-23 01:43:31 +00:00
# endif
2006-03-20 17:12:09 +00:00
2020-12-26 21:17:29 +00:00
namespace {
2006-03-20 17:12:09 +00:00
//called when the player has finished the last level
2016-12-24 08:50:29 +00:00
static void DoEndGame ( )
2006-03-20 17:12:09 +00:00
{
if ( ( Newdemo_state = = ND_STATE_RECORDING ) | | ( Newdemo_state = = ND_STATE_PAUSED ) )
newdemo_stop_recording ( ) ;
set_screen_mode ( SCREEN_MENU ) ;
2017-11-05 20:49:08 +00:00
gr_set_default_canvas ( ) ;
2006-03-20 17:12:09 +00:00
key_flush ( ) ;
if ( PLAYING_BUILTIN_MISSION & & ! ( Game_mode & GM_MULTI ) )
{ //only built-in mission, & not multi
2015-03-23 01:43:31 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2021-01-17 22:23:23 +00:00
auto played = PlayMovie ( ENDMOVIE " .tex " , ENDMOVIE " .mve " , MOVIE_REQUIRED ) ;
if ( played = = movie_play_status : : skipped )
2015-03-23 01:43:31 +00:00
# endif
2010-03-25 23:37:19 +00:00
{
2021-11-01 03:37:19 +00:00
do_end_briefing_screens ( Current_mission - > ending_text_filename ) ;
2006-03-20 17:12:09 +00:00
}
2015-03-23 01:43:31 +00:00
}
else if ( ! ( Game_mode & GM_MULTI ) ) //not multi
{
2006-03-20 17:12:09 +00:00
char tname [ FILENAME_LEN ] ;
2010-03-25 23:37:19 +00:00
2021-11-01 03:37:19 +00:00
do_end_briefing_screens ( Current_mission - > ending_text_filename ) ;
2006-03-20 17:12:09 +00:00
//try doing special credits
2020-01-18 21:57:39 +00:00
snprintf ( tname , sizeof ( tname ) , " %s.ctb " , & * Current_mission - > filename ) ;
2006-03-20 17:12:09 +00:00
credits_show ( tname ) ;
}
key_flush ( ) ;
if ( Game_mode & GM_MULTI )
multi_endlevel_score ( ) ;
else
// NOTE LINK TO ABOVE
2016-10-15 00:53:14 +00:00
DoEndLevelScoreGlitz ( ) ;
2006-03-20 17:12:09 +00:00
if ( PLAYING_BUILTIN_MISSION & & ! ( ( Game_mode & GM_MULTI ) & & ! ( Game_mode & GM_MULTI_COOP ) ) ) {
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 ( 0 , 0 , 0 ) ) ;
2015-03-23 01:43:31 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2007-06-11 15:54:09 +00:00
load_palette ( D2_DEFAULT_PALETTE , 0 , 1 ) ;
2015-03-23 01:43:31 +00:00
# endif
2016-12-10 17:51:08 +00:00
scores_maybe_add_player ( ) ;
2006-03-20 17:12:09 +00:00
}
}
//called to go to the next level (if there is one)
//if secret_flag is true, advance to secret level, else next normal one
// Return true if game over.
2016-12-24 08:50:29 +00:00
static window_event_result AdvanceLevel ( int secret_flag )
2006-03-20 17:12:09 +00:00
{
2019-08-15 01:34:22 +00:00
auto & LevelUniqueControlCenterState = LevelUniqueObjectState . ControlCenterState ;
2016-12-24 08:50:29 +00:00
auto rval = window_event_result : : handled ;
2015-03-23 01:43:31 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
Assert ( ! secret_flag ) ;
2017-01-29 21:02:48 +00:00
// Loading a level can write over homing_flag.
// So for simplicity, reset the homing weapon cheat here.
if ( cheats . homingfire )
{
cheats . homingfire = 0 ;
weapons_homing_all_reset ( ) ;
}
2015-03-23 01:43:31 +00:00
# endif
2021-11-01 03:37:19 +00:00
if ( Current_level_num ! = Current_mission - > last_level )
2016-12-24 08:50:29 +00:00
{
2006-03-20 17:12:09 +00:00
if ( Game_mode & GM_MULTI )
2016-12-24 08:50:29 +00:00
{
const auto result = multi_endlevel_score ( ) ;
if ( result = = kmatrix_result : : abort )
return window_event_result : : close ; // Exit out of game loop
}
2006-03-20 17:12:09 +00:00
else
// NOTE LINK TO ABOVE!!!
2016-10-15 00:53:14 +00:00
DoEndLevelScoreGlitz ( ) ; //give bonuses
2006-03-20 17:12:09 +00:00
}
2019-08-15 01:34:22 +00:00
LevelUniqueControlCenterState . Control_center_destroyed = 0 ;
2006-03-20 17:12:09 +00:00
2016-12-24 08:50:29 +00:00
if ( Game_mode & GM_MULTI )
{
2013-11-10 03:31:22 +00:00
int result ;
2020-12-20 20:39:07 +00:00
result = multi : : dispatch - > end_current_level ( & secret_flag ) ; // Wait for other players to reach this point
2006-03-20 17:12:09 +00:00
if ( result ) // failed to sync
{
2016-12-24 08:50:29 +00:00
// check if player has finished the game
2021-11-01 03:37:19 +00:00
return Current_level_num = = Current_mission - > last_level ? window_event_result : : close : rval ;
2006-03-20 17:12:09 +00:00
}
}
2021-11-01 03:37:19 +00:00
if ( Current_level_num = = Current_mission - > last_level )
{ //player has finished the game!
2010-06-14 08:13:16 +00:00
2006-03-20 17:12:09 +00:00
DoEndGame ( ) ;
2016-12-24 08:50:29 +00:00
rval = window_event_result : : close ;
2006-03-20 17:12:09 +00:00
} else {
2015-03-23 01:43:31 +00:00
# if defined(DXX_BUILD_DESCENT_I)
2021-11-01 03:37:19 +00:00
int8_t Next_level_num = Current_level_num + 1 ; //assume go to next normal level
2015-03-23 01:43:31 +00:00
if ( secret_flag ) { //go to secret level instead
int i ;
2021-11-01 03:37:19 +00:00
for ( i = 0 ; i < - Current_mission - > last_secret_level ; + + i )
2021-11-01 03:37:19 +00:00
if ( Current_mission - > secret_level_table [ i ] = = Current_level_num )
{
2015-03-23 01:43:31 +00:00
Next_level_num = - ( i + 1 ) ;
break ;
}
2021-11-01 03:37:19 +00:00
assert ( i < - Current_mission - > last_secret_level ) ; //couldn't find which secret level
2015-03-23 01:43:31 +00:00
}
if ( Current_level_num < 0 ) { //on secret level, where to go?
Assert ( ! secret_flag ) ; //shouldn't be going to secret level
2021-11-01 03:37:19 +00:00
assert ( Current_level_num < = - 1 & & Current_level_num > = Current_mission - > last_secret_level ) ;
2015-03-23 01:43:31 +00:00
2021-11-01 03:37:19 +00:00
Next_level_num = Current_mission - > secret_level_table [ ( - Current_level_num ) - 1 ] + 1 ;
2015-03-23 01:43:31 +00:00
}
# elif defined(DXX_BUILD_DESCENT_II)
2021-11-01 03:37:19 +00:00
int8_t Next_level_num ;
2015-03-23 01:43:31 +00:00
2007-04-14 20:44:09 +00:00
//NMN 04/08/07 If we are in a secret level and playing a D1
// level, then use Entered_from_level # instead
2010-06-14 08:13:16 +00:00
if ( Current_level_num < 0 & & EMULATING_D1 )
2007-04-14 20:44:09 +00:00
{
Next_level_num = Entered_from_level + 1 ; //assume go to next normal level
2013-11-10 03:31:22 +00:00
} else {
2007-04-14 20:44:09 +00:00
Next_level_num = Current_level_num + 1 ; //assume go to next normal level
}
// END NMN
2015-03-23 01:43:31 +00:00
# endif
2017-01-10 05:02:59 +00:00
rval = std : : max ( StartNewLevel ( Next_level_num ) , rval ) ;
2006-03-20 17:12:09 +00:00
}
2016-12-24 08:50:29 +00:00
return rval ;
2006-03-20 17:12:09 +00:00
}
2020-12-26 21:17:29 +00:00
}
2016-12-24 08:50:29 +00:00
window_event_result DoPlayerDead ( )
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 & vmobjptr = Objects . vmptr ;
2017-02-09 08:31:49 +00:00
const bool pause = ! ( ( ( Game_mode & GM_MULTI ) & & ( Newdemo_state ! = ND_STATE_PLAYBACK ) ) & & ( ! Endlevel_sequence ) ) ;
2016-12-24 08:50:29 +00:00
auto result = window_event_result : : handled ;
2017-02-09 08:31:49 +00:00
if ( pause )
2017-02-07 05:22:30 +00:00
stop_time ( ) ;
2010-06-27 11:28:26 +00:00
2006-03-20 17:12:09 +00:00
reset_palette_add ( ) ;
gr_palette_load ( gr_palette ) ;
dead_player_end ( ) ; //terminate death sequence (if playing)
2017-08-13 20:38:31 +00:00
auto & plr = get_local_player ( ) ;
2006-03-20 17:12:09 +00:00
if ( Game_mode & GM_MULTI )
{
}
else
{ //Note link to above else!
2017-08-13 20:38:31 +00:00
- - plr . lives ;
if ( plr . lives = = 0 )
2010-06-14 08:13:16 +00:00
{
2020-12-20 20:39:07 +00:00
if ( PLAYING_BUILTIN_MISSION )
scores_maybe_add_player ( ) ;
2017-02-09 08:31:49 +00:00
if ( pause )
start_time ( ) ;
2016-12-24 08:50:29 +00:00
return window_event_result : : close ;
2006-03-20 17:12:09 +00:00
}
}
2010-06-14 08:13:16 +00:00
2019-08-15 01:34:22 +00:00
if ( LevelUniqueControlCenterState . Control_center_destroyed )
{
2006-03-20 17:12:09 +00:00
//clear out stuff so no bonus
2017-08-13 20:38:31 +00:00
auto & plrobj = get_local_plrobj ( ) ;
auto & player_info = plrobj . ctype . player_info ;
2016-10-15 00:53:19 +00:00
player_info . mission . hostages_on_board = 0 ;
2016-09-11 18:49:13 +00:00
player_info . energy = 0 ;
2017-08-13 20:38:31 +00:00
plrobj . shields = 0 ;
plr . connected = CONNECT_DIED_IN_MINE ;
2006-03-20 17:12:09 +00:00
2010-07-27 11:41:43 +00:00
do_screen_message ( TXT_DIED_IN_MINE ) ; // Give them some indication of what happened
2015-03-23 01:43:31 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
if ( Current_level_num < 0 ) {
2011-06-01 07:59:55 +00:00
if ( PHYSFSX_exists ( SECRETB_FILENAME , 0 ) )
2006-03-20 17:12:09 +00:00
{
2010-07-27 11:41:43 +00:00
do_screen_message ( TXT_SECRET_RETURN ) ;
2015-04-19 04:18:49 +00:00
state_restore_all ( 1 , secret_restore : : died , SECRETB_FILENAME , blind_save : : no ) ; // 2 means you died
2006-03-20 17:12:09 +00:00
set_pos_from_return_segment ( ) ;
2017-08-13 20:38:31 +00:00
plr . lives - - ; // re-lose the life, get_local_player().lives got written over in restore.
2006-03-20 17:12:09 +00:00
} else {
2021-11-01 03:37:19 +00:00
if ( Entered_from_level = = Current_mission - > last_level )
2016-12-24 08:50:29 +00:00
{
2008-11-26 01:19:26 +00:00
DoEndGame ( ) ;
2016-12-24 08:50:29 +00:00
result = window_event_result : : close ;
}
2008-11-26 01:19:26 +00:00
else {
2010-07-27 11:41:43 +00:00
do_screen_message ( TXT_SECRET_ADVANCE ) ;
2013-03-23 20:50:35 +00:00
StartNewLevel ( Entered_from_level + 1 ) ;
2012-04-15 13:32:48 +00:00
init_player_stats_new_ship ( Player_num ) ; // New, MK, 05/29/96!, fix bug with dying in secret level, advance to next level, keep powerups!
2008-11-26 01:19:26 +00:00
}
2006-03-20 17:12:09 +00:00
}
2015-03-23 01:43:31 +00:00
} else
# endif
{
2006-03-20 17:12:09 +00:00
2021-11-01 03:37:18 +00:00
const auto g = Game_wind ;
if ( g )
g - > set_visible ( 0 ) ;
2016-12-24 08:50:29 +00:00
result = AdvanceLevel ( 0 ) ; //if finished, go on to next level
2006-03-20 17:12:09 +00:00
2012-04-15 13:32:48 +00:00
init_player_stats_new_ship ( Player_num ) ;
2007-04-01 02:14:38 +00:00
last_drawn_cockpit = - 1 ;
2021-11-01 03:37:18 +00:00
if ( g )
g - > set_visible ( 1 ) ;
2006-03-20 17:12:09 +00:00
}
2015-03-23 01:43:31 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
} else if ( Current_level_num < 0 ) {
2011-06-01 07:59:55 +00:00
if ( PHYSFSX_exists ( SECRETB_FILENAME , 0 ) )
2006-03-20 17:12:09 +00:00
{
2010-07-27 11:41:43 +00:00
do_screen_message ( TXT_SECRET_RETURN ) ;
2019-08-15 01:34:22 +00:00
if ( ! LevelUniqueControlCenterState . Control_center_destroyed )
2015-04-19 04:18:49 +00:00
state_save_all ( secret_save : : c , blind_save : : no ) ;
state_restore_all ( 1 , secret_restore : : died , SECRETB_FILENAME , blind_save : : no ) ;
2006-03-20 17:12:09 +00:00
set_pos_from_return_segment ( ) ;
2017-08-13 20:38:31 +00:00
plr . lives - - ; // re-lose the life, get_local_player().lives got written over in restore.
2006-03-20 17:12:09 +00:00
} else {
2010-07-27 11:41:43 +00:00
do_screen_message ( TXT_DIED_IN_MINE ) ; // Give them some indication of what happened
2021-11-01 03:37:19 +00:00
if ( Entered_from_level = = Current_mission - > last_level )
2016-12-24 08:50:29 +00:00
{
2008-11-26 01:19:26 +00:00
DoEndGame ( ) ;
2016-12-24 08:50:29 +00:00
result = window_event_result : : close ;
}
2008-11-26 01:19:26 +00:00
else {
2010-07-27 11:41:43 +00:00
do_screen_message ( TXT_SECRET_ADVANCE ) ;
2013-03-23 20:50:35 +00:00
StartNewLevel ( Entered_from_level + 1 ) ;
2012-04-15 13:32:48 +00:00
init_player_stats_new_ship ( Player_num ) ; // New, MK, 05/29/96!, fix bug with dying in secret level, advance to next level, keep powerups!
2008-11-26 01:19:26 +00:00
}
2006-03-20 17:12:09 +00:00
}
2015-03-23 01:43:31 +00:00
# endif
2006-03-20 17:12:09 +00:00
} else {
2012-04-15 13:32:48 +00:00
init_player_stats_new_ship ( Player_num ) ;
2006-03-20 17:12:09 +00:00
StartLevel ( 1 ) ;
}
digi_sync_sounds ( ) ;
2010-06-27 11:28:26 +00:00
2017-02-09 08:31:49 +00:00
if ( pause )
2017-02-07 05:22:30 +00:00
start_time ( ) ;
2010-07-04 13:12:08 +00:00
reset_time ( ) ;
2016-12-24 08:50:29 +00:00
return result ;
2006-03-20 17:12:09 +00:00
}
//called when the player is starting a new level for normal game mode and restore state
// secret_flag set if came from a secret level
2015-04-02 02:36:53 +00:00
# if defined(DXX_BUILD_DESCENT_I)
2017-01-10 05:02:59 +00:00
window_event_result StartNewLevelSub ( const int level_num , const int page_in_textures )
2015-04-02 02:36:53 +00:00
# elif defined(DXX_BUILD_DESCENT_II)
2017-01-10 05:02:59 +00:00
window_event_result StartNewLevelSub ( const int level_num , const int page_in_textures , const secret_restore secret_flag )
2015-04-02 02:36:53 +00:00
# endif
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 & vmobjptr = Objects . vmptr ;
2006-03-20 17:12:09 +00:00
if ( ! ( Game_mode & GM_MULTI ) ) {
2007-04-01 02:14:38 +00:00
last_drawn_cockpit = - 1 ;
2006-03-20 17:12:09 +00:00
}
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_I)
2017-06-25 20:46:03 +00:00
static constexpr std : : integral_constant < secret_restore , secret_restore : : none > secret_flag { } ;
2013-03-03 01:03:33 +00:00
# elif defined(DXX_BUILD_DESCENT_II)
2015-03-23 01:43:31 +00:00
BigWindowSwitch = 0 ;
2013-03-03 01:03:33 +00:00
# endif
2006-03-20 17:12:09 +00:00
if ( Newdemo_state = = ND_STATE_PAUSED )
Newdemo_state = ND_STATE_RECORDING ;
if ( Newdemo_state = = ND_STATE_RECORDING ) {
newdemo_set_new_level ( level_num ) ;
2008-10-16 17:27:02 +00:00
newdemo_record_start_frame ( FrameTime ) ;
2006-03-20 17:12:09 +00:00
}
LoadLevel ( level_num , page_in_textures ) ;
Assert ( Current_level_num = = level_num ) ; //make sure level set right
2015-07-25 23:10:45 +00:00
Viewer = & get_local_plrobj ( ) ;
2006-03-20 17:12:09 +00:00
Assert ( N_players < = NumNetPlayerPositions ) ;
//If this assert fails, there's not enough start positions
if ( Game_mode & GM_NETWORK )
{
2021-09-19 10:53:48 +00:00
multi_prep_level_objects ( Powerup_info , Vclip ) ;
2020-12-20 20:39:07 +00:00
if ( multi : : dispatch - > level_sync ( ) = = window_event_result : : close ) // After calling this, Player_num is set
2010-06-29 21:14:30 +00:00
{
songs_play_song ( SONG_TITLE , 1 ) ; // level song already plays but we fail to start level...
2017-01-10 05:02:59 +00:00
return window_event_result : : close ;
2010-06-29 21:14:30 +00:00
}
2006-03-20 17:12:09 +00:00
}
HUD_clear_messages ( ) ;
2020-08-28 00:18:45 +00:00
automap_clear_visited ( LevelUniqueAutomapState ) ;
2006-03-20 17:12:09 +00:00
2019-07-16 04:00:50 +00:00
LevelUniqueObjectState . accumulated_robots = count_number_of_robots ( Objects . vcptr ) ;
2019-07-16 04:00:50 +00:00
GameUniqueState . accumulated_robots + = LevelUniqueObjectState . accumulated_robots ;
2019-07-16 04:00:50 +00:00
LevelUniqueObjectState . total_hostages = count_number_of_hostages ( Objects . vcptr ) ;
2019-07-16 04:00:50 +00:00
GameUniqueState . total_hostages + = LevelUniqueObjectState . total_hostages ;
2017-04-30 16:25:16 +00:00
init_player_stats_level ( get_local_player ( ) , get_local_plrobj ( ) , secret_flag ) ;
2006-03-20 17:12:09 +00:00
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_I)
gr_use_palette_table ( " palette.256 " ) ;
# elif defined(DXX_BUILD_DESCENT_II)
2007-05-10 08:53:35 +00:00
load_palette ( Current_level_palette , 0 , 1 ) ;
2013-03-03 01:03:33 +00:00
# endif
2007-05-10 08:53:35 +00:00
gr_palette_load ( gr_palette ) ;
2006-03-20 17:12:09 +00:00
if ( ( Game_mode & GM_MULTI_COOP ) & & Network_rejoined )
{
2017-08-13 20:38:32 +00:00
for ( playernum_t i = 0 ; i < N_players ; + + i )
2015-11-07 21:55:59 +00:00
{
2017-08-13 20:38:32 +00:00
const auto & & plobj = vmobjptr ( vcplayerptr ( i ) - > objnum ) ;
2015-11-07 21:55:59 +00:00
plobj - > ctype . player_info . powerup_flags | = Netgame . net_player_flags [ i ] ;
}
2006-03-20 17:12:09 +00:00
}
if ( Game_mode & GM_MULTI )
{
2016-02-25 13:11:08 +00:00
multi_prep_level_player ( ) ;
2006-03-20 17:12:09 +00:00
}
gameseq_remove_unused_players ( ) ;
Game_suspended = 0 ;
2019-08-15 01:34:22 +00:00
LevelUniqueControlCenterState . Control_center_destroyed = 0 ;
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
set_screen_mode ( SCREEN_GAME ) ;
2013-03-03 01:03:33 +00:00
# endif
2007-04-04 17:18:17 +00:00
2006-03-20 17:12:09 +00:00
init_cockpit ( ) ;
init_robots_for_level ( ) ;
init_ai_objects ( ) ;
init_morphs ( ) ;
init_all_matcens ( ) ;
reset_palette_add ( ) ;
2018-08-04 17:52:57 +00:00
LevelUniqueStuckObjectState . init_stuck_objects ( ) ;
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2013-11-10 03:31:22 +00:00
init_smega_detonates ( ) ;
2006-03-20 17:12:09 +00:00
init_thief_for_level ( ) ;
if ( ! ( Game_mode & GM_MULTI ) )
2021-09-19 10:53:48 +00:00
filter_objects_from_level ( Powerup_info , Vclip , vmobjptr ) ;
2013-03-03 01:03:33 +00:00
# endif
2006-03-20 17:12:09 +00:00
2011-02-14 21:27:07 +00:00
if ( ! ( Game_mode & GM_MULTI ) & & ! cheats . enabled )
2006-03-20 17:12:09 +00:00
set_highest_level ( Current_level_num ) ;
else
read_player_file ( ) ; //get window sizes
reset_special_effects ( ) ;
2016-09-24 18:06:11 +00:00
# if DXX_USE_OGL
2006-03-20 17:12:09 +00:00
ogl_cache_level_textures ( ) ;
# endif
if ( Network_rejoined = = 1 )
{
Network_rejoined = 0 ;
StartLevel ( 1 ) ;
}
else
StartLevel ( 0 ) ; // Note link to above if!
copy_defaults_to_robot_all ( ) ;
init_controlcen_for_level ( ) ;
// Say player can use FLASH cheat to mark path to exit.
2021-11-01 03:37:19 +00:00
LevelUniqueObjectState . Level_path_created = 0 ;
2010-06-14 08:13:16 +00:00
2010-02-05 02:31:36 +00:00
// Initialise for palette_restore()
// Also takes care of nm_draw_background() possibly being called
if ( ! ( ( Game_mode & GM_MULTI ) & & ( Newdemo_state ! = ND_STATE_PLAYBACK ) ) )
full_palette_save ( ) ;
2010-06-14 08:13:16 +00:00
2010-02-08 05:34:43 +00:00
if ( ! Game_wind )
game ( ) ;
2017-01-10 05:02:59 +00:00
return window_event_result : : handled ;
2006-03-20 17:12:09 +00:00
}
2018-10-21 00:24:07 +00:00
void bash_to_shield ( const d_powerup_info_array & Powerup_info , const d_vclip_array & Vclip , object_base & i )
2006-03-20 17:12:09 +00:00
{
2018-10-08 03:58:48 +00:00
set_powerup_id ( Powerup_info , Vclip , i , POW_SHIELD_BOOST ) ;
2006-03-20 17:12:09 +00:00
}
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2020-12-26 21:17:29 +00:00
namespace {
2015-12-13 18:00:48 +00:00
2012-11-11 22:12:51 +00:00
struct intro_movie_t {
2006-03-20 17:12:09 +00:00
int level_num ;
2021-09-19 10:53:48 +00:00
char movie_name [ 8 ] ;
2012-11-11 22:12:51 +00:00
} ;
2021-09-19 10:53:48 +00:00
constexpr std : : array < intro_movie_t , 7 > intro_movie { {
{ 1 , " PLA.MVE " } ,
{ 5 , " PLB.MVE " } ,
{ 9 , " PLC.MVE " } ,
{ 13 , " PLD.MVE " } ,
{ 17 , " PLE.MVE " } ,
{ 21 , " PLF.MVE " } ,
{ 24 , " PLG.MVE " }
2015-01-29 04:27:36 +00:00
} } ;
2006-03-20 17:12:09 +00:00
2013-10-27 22:00:14 +00:00
static void ShowLevelIntro ( int level_num )
2006-03-20 17:12:09 +00:00
{
//if shareware, show a briefing?
if ( ! ( Game_mode & GM_MULTI ) ) {
2021-09-19 10:53:48 +00:00
palette_array_t save_pal = gr_palette ;
2006-03-20 17:12:09 +00:00
if ( PLAYING_BUILTIN_MISSION ) {
if ( is_SHAREWARE | | is_MAC_SHARE )
{
2021-09-19 10:53:48 +00:00
if ( level_num ! = 1 )
return ;
2006-03-20 17:12:09 +00:00
}
else if ( is_D2_OEM )
{
2021-09-19 10:53:48 +00:00
if ( level_num ! = 1 )
return ;
if ( intro_played )
return ;
2006-03-20 17:12:09 +00:00
}
else // full version
{
2015-01-29 04:27:36 +00:00
range_for ( auto & i , intro_movie )
2006-03-20 17:12:09 +00:00
{
2015-01-29 04:27:36 +00:00
if ( i . level_num = = level_num )
2006-03-20 17:12:09 +00:00
{
Screen_mode = - 1 ;
2015-01-29 04:27:36 +00:00
PlayMovie ( NULL , i . movie_name , MOVIE_REQUIRED ) ;
2006-03-20 17:12:09 +00:00
break ;
}
}
}
}
2021-09-19 10:53:48 +00:00
//else not the built-in mission (maybe d1, too).
/* Play a level-appropriate briefing, whether built-in, add-on,
* or Descent 1.
*/
2021-11-01 03:37:19 +00:00
do_briefing_screens ( Current_mission - > briefing_text_filename , level_num ) ;
2006-03-20 17:12:09 +00:00
2013-01-06 21:11:53 +00:00
gr_palette = save_pal ;
2006-03-20 17:12:09 +00:00
}
}
// ---------------------------------------------------------------------------
2021-11-01 03:37:19 +00:00
// If starting a level which appears in the Current_mission->secret_level_table, then set First_secret_visit.
2006-03-20 17:12:09 +00:00
// Reason: On this level, if player goes to a secret level, he will be going to a different
// secret level than he's ever been to before.
// Sets the global First_secret_visit if necessary. Otherwise leaves it unchanged.
2013-10-27 22:00:14 +00:00
static void maybe_set_first_secret_visit ( int level_num )
2006-03-20 17:12:09 +00:00
{
2021-11-01 03:37:19 +00:00
range_for ( auto & i , unchecked_partial_range ( Current_mission - > secret_level_table . get ( ) , Current_mission - > n_secret_levels ) )
2015-01-29 04:27:36 +00:00
{
if ( i = = level_num )
{
2006-03-20 17:12:09 +00:00
First_secret_visit = 1 ;
2015-01-29 04:27:36 +00:00
break ;
2006-03-20 17:12:09 +00:00
}
}
}
2020-12-26 21:17:29 +00:00
}
2013-03-03 01:03:33 +00:00
# endif
2006-03-20 17:12:09 +00:00
//called when the player is starting a new level for normal game model
// secret_flag if came from a secret level
2017-01-10 05:02:59 +00:00
window_event_result StartNewLevel ( int level_num )
2006-03-20 17:12:09 +00:00
{
2010-03-27 03:24:14 +00:00
hide_menus ( ) ;
2010-06-14 08:13:16 +00:00
2010-12-22 00:17:59 +00:00
GameTime64 = 0 ;
2020-04-19 20:47:07 +00:00
/* Autosave is permitted immediately on entering a new level */
state_set_immediate_autosave ( GameUniqueState ) ;
2020-04-19 20:47:07 +00:00
ThisLevelTime = { } ;
2006-03-20 17:12:09 +00:00
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_I)
if ( ! ( Game_mode & GM_MULTI ) ) {
2021-11-01 03:37:19 +00:00
do_briefing_screens ( Current_mission - > briefing_text_filename , level_num ) ;
2013-03-03 01:03:33 +00:00
}
# elif defined(DXX_BUILD_DESCENT_II)
2013-03-23 20:50:35 +00:00
if ( level_num > 0 ) {
2006-03-20 17:12:09 +00:00
maybe_set_first_secret_visit ( level_num ) ;
}
ShowLevelIntro ( level_num ) ;
2013-03-03 01:03:33 +00:00
# endif
2006-03-20 17:12:09 +00:00
2017-01-10 05:02:59 +00:00
return StartNewLevelSub ( level_num , 1 , secret_restore : : none ) ;
2006-03-20 17:12:09 +00:00
}
2020-12-26 21:17:29 +00:00
namespace {
2015-07-04 21:01:18 +00:00
class respawn_locations
{
typedef std : : pair < int , fix > site ;
unsigned max_usable_spawn_sites ;
2020-05-02 21:18:42 +00:00
std : : array < site , MAX_PLAYERS > sites ;
2015-07-04 21:01:18 +00:00
public :
2019-03-03 00:31:08 +00:00
respawn_locations ( fvmobjptr & vmobjptr , fvcsegptridx & vcsegptridx )
2015-07-04 21:01:18 +00:00
{
const auto player_num = Player_num ;
2019-03-03 00:31:08 +00:00
const auto find_closest_player = [ player_num , & vmobjptr , & vcsegptridx ] ( const obj_position & candidate ) {
2017-02-22 03:05:43 +00:00
fix closest_dist = INT32_MAX ;
2016-01-09 16:38:14 +00:00
const auto & & candidate_segp = vcsegptridx ( candidate . segnum ) ;
2017-08-13 20:38:32 +00:00
for ( playernum_t i = N_players ; i - - ; )
2015-07-04 21:01:18 +00:00
{
if ( i = = player_num )
continue ;
2017-08-13 20:38:32 +00:00
const auto & & objp = vmobjptr ( vcplayerptr ( i ) - > objnum ) ;
2015-07-04 21:01:18 +00:00
if ( objp - > type ! = OBJ_PLAYER )
continue ;
2020-12-19 16:13:26 +00:00
const auto dist = find_connected_distance ( objp - > pos , candidate_segp . absolute_sibling ( objp - > segnum ) , candidate . pos , candidate_segp , - 1 , WALL_IS_DOORWAY_FLAG : : None ) ;
2015-07-04 21:01:18 +00:00
if ( dist > = 0 & & closest_dist > dist )
closest_dist = dist ;
}
return closest_dist ;
} ;
2016-08-19 03:41:42 +00:00
const auto max_spawn_sites = std : : min < unsigned > ( NumNetPlayerPositions , sites . size ( ) ) ;
2015-07-04 21:01:18 +00:00
for ( uint_fast32_t i = max_spawn_sites ; i - - ; )
{
auto & s = sites [ i ] ;
s . first = i ;
s . second = find_closest_player ( Player_init [ i ] ) ;
}
const unsigned SecludedSpawns = Netgame . SecludedSpawns + 1 ;
if ( max_spawn_sites > SecludedSpawns )
{
max_usable_spawn_sites = SecludedSpawns ;
2016-02-12 04:02:28 +00:00
const auto & & predicate = [ ] ( const site & a , const site & b ) {
2015-07-04 21:01:18 +00:00
return a . second > b . second ;
} ;
const auto b = sites . begin ( ) ;
const auto m = std : : next ( b , SecludedSpawns ) ;
const auto e = std : : next ( b , max_spawn_sites ) ;
2016-02-12 04:02:28 +00:00
std : : partial_sort ( b , m , e , predicate ) ;
2015-07-04 21:01:18 +00:00
}
else
max_usable_spawn_sites = max_spawn_sites ;
}
unsigned get_usable_sites ( ) const
{
return max_usable_spawn_sites ;
}
const site & operator [ ] ( std : : size_t i ) const
{
return sites [ i ] ;
}
} ;
2006-03-20 17:12:09 +00:00
//initialize the player object position & orientation (at start of game, or new ship)
2017-07-26 03:15:59 +00:00
static void InitPlayerPosition ( fvmobjptridx & vmobjptridx , fvmsegptridx & vmsegptridx , int random_flag )
2006-03-20 17:12:09 +00:00
{
2019-03-03 00:31:08 +00:00
auto & Objects = LevelUniqueObjectState . Objects ;
auto & vmobjptr = Objects . vmptr ;
2015-07-04 21:01:18 +00:00
reset_cruise ( ) ;
2006-03-20 17:12:09 +00:00
int NewPlayer = 0 ;
if ( ! ( ( Game_mode & GM_MULTI ) & & ! ( Game_mode & GM_MULTI_COOP ) ) ) // If not deathmatch
NewPlayer = Player_num ;
else if ( random_flag = = 1 )
{
2019-03-03 00:31:08 +00:00
const respawn_locations locations ( vmobjptr , vcsegptridx ) ;
2015-07-04 21:01:18 +00:00
if ( ! locations . get_usable_sites ( ) )
return ;
2014-09-20 23:47:27 +00:00
uint_fast32_t trys = 0 ;
2015-02-05 03:03:48 +00:00
d_srand ( static_cast < fix > ( timer_update ( ) ) ) ;
2006-03-20 17:12:09 +00:00
do {
trys + + ;
2015-07-04 21:01:18 +00:00
NewPlayer = d_rand ( ) % locations . get_usable_sites ( ) ;
const auto closest_dist = locations [ NewPlayer ] . second ;
if ( closest_dist > = i2f ( 15 * 20 ) )
break ;
} while ( trys < MAX_PLAYERS * 2 ) ;
NewPlayer = locations [ NewPlayer ] . first ;
2006-03-20 17:12:09 +00:00
}
else {
2011-02-02 22:51:34 +00:00
// If deathmatch and not random, positions were already determined by sync packet
reset_player_object ( ) ;
return ;
2006-03-20 17:12:09 +00:00
}
Assert ( NewPlayer > = 0 ) ;
Assert ( NewPlayer < NumNetPlayerPositions ) ;
ConsoleObject - > pos = Player_init [ NewPlayer ] . pos ;
ConsoleObject - > orient = Player_init [ NewPlayer ] . orient ;
2018-03-12 03:43:46 +00:00
obj_relink ( vmobjptr , vmsegptr , vmobjptridx ( ConsoleObject ) , vmsegptridx ( Player_init [ NewPlayer ] . segnum ) ) ;
2006-03-20 17:12:09 +00:00
reset_player_object ( ) ;
}
2020-12-26 21:17:29 +00:00
}
2006-03-20 17:12:09 +00:00
// -----------------------------------------------------------------------------------------------------
// Initialize default parameters for one robot, copying from Robot_info to *objp.
// What about setting size!? Where does that come from?
2019-03-03 00:31:07 +00:00
void copy_defaults_to_robot ( object_base & objp )
2006-03-20 17:12:09 +00:00
{
2019-03-03 00:31:07 +00:00
assert ( objp . type = = OBJ_ROBOT ) ;
const unsigned objid = get_robot_id ( objp ) ;
2018-12-30 00:43:59 +00:00
assert ( objid < LevelSharedRobotInfoState . N_robot_types ) ;
2006-03-20 17:12:09 +00:00
2018-12-30 00:43:59 +00:00
auto & Robot_info = LevelSharedRobotInfoState . Robot_info ;
2017-08-26 19:47:51 +00:00
auto & robptr = Robot_info [ objid ] ;
2006-03-20 17:12:09 +00:00
// Boost shield for Thief and Buddy based on level.
2019-03-03 00:31:07 +00:00
fix shields = robptr . strength ;
2006-03-20 17:12:09 +00:00
2013-03-03 01:03:33 +00:00
# if defined(DXX_BUILD_DESCENT_II)
2019-06-27 03:26:20 +00:00
const auto & Difficulty_level = GameUniqueState . Difficulty_level ;
2013-11-03 22:27:28 +00:00
if ( ( robot_is_thief ( robptr ) ) | | ( robot_is_companion ( robptr ) ) ) {
2019-03-03 00:31:07 +00:00
shields = ( shields * ( abs ( Current_level_num ) + 7 ) ) / 8 ;
2006-03-20 17:12:09 +00:00
2013-11-03 22:27:28 +00:00
if ( robot_is_companion ( robptr ) ) {
2006-03-20 17:12:09 +00:00
// Now, scale guide-bot hits by skill level
switch ( Difficulty_level ) {
2019-03-03 00:31:07 +00:00
case 0 : shields = i2f ( 20000 ) ; break ; // Trainee, basically unkillable
case 1 : shields * = 3 ; break ; // Rookie, pretty dang hard
case 2 : shields * = 2 ; break ; // Hotshot, a bit tough
2006-03-20 17:12:09 +00:00
default : break ;
}
}
2017-08-26 19:47:51 +00:00
} else if ( robptr . boss_flag ) // MK, 01/16/95, make boss shields lower on lower diff levels.
2019-03-03 00:31:07 +00:00
{
2006-03-20 17:12:09 +00:00
// Additional wimpification of bosses at Trainee
2019-06-27 03:26:20 +00:00
shields = shields / ( NDL + 3 ) * ( Difficulty_level ? Difficulty_level + 4 : 2 ) ;
2019-03-03 00:31:07 +00:00
}
2013-03-03 01:03:33 +00:00
# endif
2019-03-03 00:31:07 +00:00
objp . shields = shields ;
2016-08-25 04:05:32 +00:00
}
2006-03-20 17:12:09 +00:00
2020-12-26 21:17:29 +00:00
namespace {
2006-03-20 17:12:09 +00:00
// -----------------------------------------------------------------------------------------------------
// Copy all values from the robot info structure to all instances of robots.
// This allows us to change bitmaps.tbl and have these changes manifested in existing robots.
// This function should be called at level load time.
2013-11-10 03:31:22 +00:00
static void copy_defaults_to_robot_all ( void )
2006-03-20 17:12:09 +00:00
{
2019-03-03 00:31:08 +00:00
auto & Objects = LevelUniqueObjectState . Objects ;
auto & vmobjptr = Objects . vmptr ;
2019-03-03 00:31:07 +00:00
range_for ( object_base & objp , vmobjptr )
2015-06-13 22:42:17 +00:00
{
2019-03-03 00:31:07 +00:00
if ( objp . type = = OBJ_ROBOT )
2015-06-13 22:42:17 +00:00
copy_defaults_to_robot ( objp ) ;
}
2006-03-20 17:12:09 +00:00
}
// -----------------------------------------------------------------------------------------------------
//called when the player is starting a level (new game or new ship)
2013-09-22 22:26:27 +00:00
static void StartLevel ( int random_flag )
2006-03-20 17:12:09 +00:00
{
2019-03-03 00:31:08 +00:00
auto & Objects = LevelUniqueObjectState . Objects ;
auto & vmobjptridx = Objects . vmptridx ;
2015-12-15 04:09:35 +00:00
assert ( Player_dead_state = = player_dead_state : : no ) ;
2006-03-20 17:12:09 +00:00
2017-07-26 03:15:59 +00:00
InitPlayerPosition ( vmobjptridx , vmsegptridx , random_flag ) ;
2006-03-20 17:12:09 +00:00
verify_console_object ( ) ;
2020-08-10 03:45:13 +00:00
ConsoleObject - > control_source = object : : control_type : : flying ;
2020-08-10 03:45:13 +00:00
ConsoleObject - > movement_source = object : : movement_type : : physics ;
2006-03-20 17:12:09 +00:00
// create_player_appearance_effect(ConsoleObject);
Do_appearance_effect = 1 ;
if ( Game_mode & GM_MULTI )
{
if ( Game_mode & GM_MULTI_COOP )
multi_send_score ( ) ;
multi_send_reappear ( ) ;
2020-12-20 20:39:07 +00:00
multi : : dispatch - > do_protocol_frame ( 1 , 1 ) ;
2012-05-24 10:46:35 +00:00
}
else // in Singleplayer, after we died ...
{
disable_matcens ( ) ; // ... disable matcens and ...
clear_transient_objects ( 0 ) ; // ... clear all transient objects.
}
2006-03-20 17:12:09 +00:00
ai_reset_all_paths ( ) ;
ai_init_boss_for_ship ( ) ;
reset_rear_view ( ) ;
2016-12-10 17:51:09 +00:00
auto & player_info = ConsoleObject - > ctype . player_info ;
2016-12-10 17:51:09 +00:00
player_info . Auto_fire_fusion_cannon_time = 0 ;
2016-12-10 17:51:09 +00:00
player_info . Fusion_charge = 0 ;
2006-03-20 17:12:09 +00:00
}
2020-12-26 21:17:29 +00:00
}
2016-08-25 04:05:32 +00:00
}