dxx-rebirth/similar/main/gameseq.cpp

2426 lines
75 KiB
C++
Raw Normal View History

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.
*
*/
#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>
#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"
#include "dxxerror.h"
2006-03-20 17:12:09 +00:00
#include "joy.h"
#include "timer.h"
#include "laser.h"
#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"
#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"
#if DXX_USE_EDITOR
2006-03-20 17:12:09 +00:00
#include "editor/editor.h"
#endif
#include "strutil.h"
#include "segment.h"
#include "gameseg.h"
#include "fmtcheck.h"
2006-03-20 17:12:09 +00:00
2014-10-12 23:05:46 +00:00
#include "compiler-range_for.h"
#include "d_enumerate.h"
#include "d_levelstate.h"
#include "partial_range.h"
2019-05-04 18:27:36 +00:00
#include "d_range.h"
#include "d_underlying_value.h"
#include "d_zip.h"
2014-10-12 23:05:46 +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
namespace d1x {
namespace {
static int8_t find_next_level(const next_level_request_secret_flag secret_flag, const int current_level_num, const Mission &mission)
{
if (secret_flag != next_level_request_secret_flag::only_normal_level)
{ //go to secret level instead
for (const auto &&[idx, table_entry] : enumerate(
unchecked_partial_range(
mission.secret_level_table.get(),
static_cast<unsigned>(-mission.last_secret_level)
)
))
if (table_entry == current_level_num)
return -(idx + 1);
//couldn't find which secret level
LevelError("secret exit from level %u failed to find secret level; continuing to next normal level", current_level_num);
}
else if (current_level_num < 0)
{ //on secret level, where to go?
//shouldn't be going to secret level
assert(current_level_num <= -1 && current_level_num >= mission.last_secret_level);
return mission.secret_level_table[(-current_level_num) - 1] + 1;
}
return current_level_num + 1; //assume go to next normal level
}
}
}
#elif defined(DXX_BUILD_DESCENT_II)
#include "movie.h"
#define GLITZ_BACKGROUND STARS_BACKGROUND
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);
static void InitPlayerPosition(fvmobjptridx &vmobjptridx, fvmsegptridx &vmsegptridx, int random_flag);
static void DoEndGame();
2020-12-26 21:17:29 +00:00
}
PHYSFSX_gets_line_t<FILENAME_LEN> Current_level_palette;
int First_secret_visit = 1;
}
#endif
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:
preserve_player_object_info(fvcobjptr &vcobjptr, const objnum_t &o) :
objnum(o)
{
auto &plr = *vcobjptr(objnum);
plr_shields = plr.shields;
plr_info = plr.ctype.player_info;
}
void restore(fvmobjptr &vmobjptr) const
{
auto &plr = *vmobjptr(objnum);
plr.shields = plr_shields;
plr.ctype.player_info = plr_info;
}
};
}
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
//0 used to mean not a real level loaded (i.e. editor generated level), but this hack has been removed
int Current_level_num=1;
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.
fix StartingShields=INITIAL_SHIELDS;
per_player_array<obj_position> Player_init;
2006-03-20 17:12:09 +00:00
// Global variables telling what sort of game we have
unsigned NumNetPlayerPositions;
2006-03-20 17:12:09 +00:00
int Do_appearance_effect=0;
2020-12-26 21:17:29 +00:00
namespace {
template <object_type_t type>
static bool is_object_of_type(const object_base &o)
{
return o.type == type;
}
static unsigned get_starting_concussion_missile_count()
{
return 2 + NDL - underlying_value(GameUniqueState.Difficulty_level);
}
}
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(
#if defined(DXX_BUILD_DESCENT_I)
#undef AdvanceLevel
next_level_request_secret_flag secret_flag
#elif defined(DXX_BUILD_DESCENT_II)
#define AdvanceLevel(secret_flag) ((void)secret_flag,AdvanceLevel())
#endif
);
2020-12-26 21:17:29 +00:00
static void StartLevel(int random_flag);
static void copy_defaults_to_robot_all(const d_robot_info_array &Robot_info);
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
{
auto &Objects = LevelUniqueObjectState.Objects;
auto &vmobjptr = Objects.vmptr;
Assert(Player_num < Players.size());
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
}
template <object_type_t type>
static unsigned count_number_of_objects_of_type(fvcobjptr &vcobjptr)
2006-03-20 17:12:09 +00:00
{
return std::count_if(vcobjptr.begin(), vcobjptr.end(), is_object_of_type<type>);
2006-03-20 17:12:09 +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
constexpr constant_xrange<sidenum_t, sidenum_t::WRIGHT, sidenum_t::WFRONT> displacement_sides{};
static_assert(static_cast<uint8_t>(sidenum_t::WBACK) + 1 == static_cast<uint8_t>(sidenum_t::WFRONT), "side ordering error");
static unsigned generate_extra_starts_by_copying(object_array &Objects, valptridx<player>::array_managed_type &Players, segment_array &Segments, const xrange<unsigned, std::integral_constant<unsigned, 0>> preplaced_start_range, const per_player_array<sidemask_t> &player_init_segment_capacity_flag, const unsigned total_required_num_starts, unsigned synthetic_player_idx)
{
for (const auto side : displacement_sides)
{
for (const auto old_player_idx : preplaced_start_range)
{
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] & build_sidemask(side))
{
auto &&segp = Segments.vmptridx(old_player_obj.segnum);
/* Copy the start exactly. The next loop in the caller 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(synthetic_player_idx)->objnum = extra_player_ptridx;
auto &extra_player_obj = *extra_player_ptridx;
set_player_id(extra_player_obj, synthetic_player_idx);
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, synthetic_player_idx, extra_player_ptridx.get_unchecked_index());
if (++ synthetic_player_idx >= total_required_num_starts)
return synthetic_player_idx;
}
}
}
return synthetic_player_idx;
}
static unsigned generate_extra_starts_by_displacement_within_segment(const unsigned preplaced_starts, const unsigned total_required_num_starts)
{
const auto &&preplaced_start_range = xrange(preplaced_starts);
auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
auto &Objects = LevelUniqueObjectState.Objects;
auto &Vertices = LevelSharedVertexState.get_vertices();
auto &vcobjptr = Objects.vcptr;
auto &vmobjptr = Objects.vmptr;
per_player_array<sidemask_t> player_init_segment_capacity_flag{};
DXX_MAKE_VAR_UNDEFINED(player_init_segment_capacity_flag);
static_assert(static_cast<uint8_t>(sidenum_t::WRIGHT) + 1 == static_cast<uint8_t>(sidenum_t::WBOTTOM), "side ordering error");
static_assert(static_cast<uint8_t>(sidenum_t::WBOTTOM) + 1 == static_cast<uint8_t>(sidenum_t::WBACK), "side ordering error");
constexpr auto capacity_x = build_sidemask(sidenum_t::WRIGHT);
constexpr auto capacity_y = build_sidemask(sidenum_t::WBOTTOM);
constexpr auto capacity_z = build_sidemask(sidenum_t::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;
for (const auto i : preplaced_start_range)
{
/* 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;
const shared_segment &seg = *vcsegptr(segnum);
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[segment_relative_vertnum::_0]);
sidemask_t capacity_flag{};
if (vm_vec_dist2(v0, vcvertptr(seg.verts[segment_relative_vertnum::_1])) > size2)
capacity_flag |= capacity_x;
if (vm_vec_dist2(v0, vcvertptr(seg.verts[segment_relative_vertnum::_3])) > size2)
capacity_flag |= capacity_y;
if (vm_vec_dist2(v0, vcvertptr(seg.verts[segment_relative_vertnum::_4])) > size2)
capacity_flag |= capacity_z;
player_init_segment_capacity_flag[i] = capacity_flag;
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), underlying_value(capacity_flag));
if (capacity_flag != sidemask_t{})
++segments_with_spare_capacity;
}
if (!segments_with_spare_capacity)
/* Every segment checked was too small to add an extra start. Give up
* early.
*/
return preplaced_starts;
unsigned k = generate_extra_starts_by_copying(Objects, Players, Segments, preplaced_start_range, player_init_segment_capacity_flag, total_required_num_starts, preplaced_starts);
for (const auto old_player_idx : preplaced_start_range)
{
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{};
DXX_MAKE_VAR_UNDEFINED(vec_displacement);
const shared_segment &seg = *vcsegptr(old_player_init.segnum);
/* 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 (auto &&[side, displacement] : zip(displacement_sides, vec_displacement))
{
const auto &&center_on_side = compute_center_point_on_side(vcvertptr, seg, side);
displacement = vm_vec_sub(center_on_side, old_player_init.pos);
}
const auto displace_player = [&](const unsigned plridx, object_base &plrobj, const unsigned displacement_direction) {
vms_vector disp{};
unsigned dimensions = 0;
for (const auto &&[i, side] : enumerate(displacement_sides))
{
if (!(player_init_segment_capacity_flag[old_player_idx] & build_sidemask(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, underlying_value(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, underlying_value(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));
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; sidemask != sidemask_t{})
{
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, underlying_value(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;
plrobj.pos = Player_init[plridx].pos;
};
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
#if defined(DXX_BUILD_DESCENT_I)
#define gameseq_init_network_players(Robot_info,Objects) gameseq_init_network_players(Objects)
#elif defined(DXX_BUILD_DESCENT_II)
#undef gameseq_init_network_players
#endif
static void gameseq_init_network_players(const d_robot_info_array &Robot_info, object_array &Objects)
2006-03-20 17:12:09 +00:00
{
// Initialize network player start locations and object numbers
ConsoleObject = &Objects.front();
2017-08-13 20:38:31 +00:00
unsigned j = 0, k = 0;
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;
const auto retain_guidebot = Netgame.AllowGuidebot;
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
auto &vmobjptridx = Objects.vmptridx;
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
{
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;
auto &pi = Player_init[k];
pi.pos = o->pos;
pi.orient = o->orient;
pi.segnum = o->segnum;
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
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)];
if ((!retain_guidebot && robot_is_companion(ri)) ||
(remove_thief && robot_is_thief(ri)))
{
object_create_robot_egg(Robot_info, o);
obj_delete(LevelUniqueObjectState, Segments, o); //kill the buddy in netgames
}
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
}
if (multiplayer_coop)
{
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);
/*
* 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);
}
2006-03-20 17:12:09 +00:00
NumNetPlayerPositions = k;
}
2020-12-26 21:17:29 +00:00
}
void gameseq_remove_unused_players(const d_robot_info_array &Robot_info)
2006-03-20 17:12:09 +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
{
if (vcplayerptr(i)->connected == player_connection_status::disconnected || 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
{
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)
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))
{
object_create_robot_egg(Robot_info, o);
obj_delete(LevelUniqueObjectState, Segments, o);
}
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
{
auto &Objects = LevelUniqueObjectState.Objects;
auto &vmobjptr = Objects.vmptr;
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 = {};
player_info.net_killed_total = 0;
player_info.net_kills_total = 0;
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;
player_info.mission.hostages_rescued_total = 0;
init_player_stats_new_ship(pnum);
#if defined(DXX_BUILD_DESCENT_II)
if (pnum == Player_num)
First_secret_visit = 1;
#endif
2006-03-20 17:12:09 +00:00
}
2020-12-26 21:17:29 +00:00
namespace {
static void init_ammo_and_energy(object &plrobj)
2006-03-20 17:12:09 +00:00
{
auto &player_info = plrobj.ctype.player_info;
2016-07-03 00:54:16 +00:00
{
auto &energy = player_info.energy;
#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;
}
{
auto &shields = plrobj.shields;
if (shields < StartingShields)
shields = StartingShields;
}
const unsigned minimum_missiles = get_starting_concussion_missile_count();
auto &concussion = player_info.secondary_ammo[CONCUSSION_INDEX];
if (concussion < minimum_missiles)
concussion = minimum_missiles;
2006-03-20 17:12:09 +00:00
}
2020-12-26 21:17:29 +00:00
}
#if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
extern ubyte Last_afterburner_state;
#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
{
#if defined(DXX_BUILD_DESCENT_II)
auto &Objects = LevelUniqueObjectState.Objects;
auto &vcobjptridx = Objects.vcptridx;
#endif
2006-03-20 17:12:09 +00:00
plr.level = Current_level_num;
2006-03-20 17:12:09 +00:00
if (!Network_rejoined) {
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
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) {
init_ammo_and_energy(plrobj);
2006-03-20 17:12:09 +00:00
auto &powerup_flags = player_info.powerup_flags;
powerup_flags &= ~(PLAYER_FLAGS_INVULNERABLE | PLAYER_FLAGS_CLOAKED);
#if defined(DXX_BUILD_DESCENT_II)
powerup_flags &= ~(PLAYER_FLAGS_MAP_ALL);
#endif
2006-03-20 17:12:09 +00:00
DXX_MAKE_VAR_UNDEFINED(player_info.cloak_time);
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))
powerup_flags |= all_keys;
2015-10-30 02:52:55 +00:00
else
powerup_flags &= ~all_keys;
2006-03-20 17:12:09 +00:00
}
Player_dead_state = player_dead_state::no; // Added by RH
Dead_player_camera = NULL;
2006-03-20 17:12:09 +00:00
#if defined(DXX_BUILD_DESCENT_II)
Controls.state.afterburner = 0;
2006-03-20 17:12:09 +00:00
Last_afterburner_state = 0;
digi_kill_sound_linked_to_object(vcobjptridx(plr.objnum));
#endif
2006-03-20 17:12:09 +00:00
init_gauges();
#if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
Missile_viewer = NULL;
#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
void init_player_stats_new_ship(const playernum_t pnum)
2006-03-20 17:12:09 +00:00
{
auto &Objects = LevelUniqueObjectState.Objects;
auto &vmobjptridx = Objects.vmptridx;
auto &plr = *vcplayerptr(pnum);
const auto &&plrobj = vmobjptridx(plr.objnum);
2015-11-07 21:55:58 +00:00
plrobj->shields = StartingShields;
auto &player_info = plrobj->ctype.player_info;
player_info.energy = INITIAL_ENERGY;
player_info.secondary_ammo = {{
static_cast<uint8_t>(get_starting_concussion_missile_count())
}};
2015-03-28 17:18:02 +00:00
const auto GrantedItems = (Game_mode & GM_MULTI) ? Netgame.SpawnGrantedItems : 0;
player_info.vulcan_ammo = map_granted_flags_to_vulcan_ammo(GrantedItems);
const auto granted_laser_level = map_granted_flags_to_laser_level(GrantedItems);
player_info.laser_level = granted_laser_level;
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;
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);
player_info.Omega_charge = (granted_primary_weapon_flags & HAS_OMEGA_FLAG)
? MAX_OMEGA_CHARGE
: 0;
player_info.Omega_recharge_delay = 0;
if (game_mode_hoard())
player_info.hoard.orbs = 0;
#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);
if (pnum == Player_num)
{
if (Game_mode & GM_MULTI && Netgame.InvulAppear)
{
player_info.powerup_flags |= PLAYER_FLAGS_INVULNERABLE;
player_info.invulnerable_time = GameTime64 - (i2f(58 - Netgame.InvulAppear) >> 1);
player_info.FakingInvul = 1;
}
set_primary_weapon(player_info, [=]{
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)
{
if (granted_laser_level <= laser_level::_4)
/* 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;
}());
#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)
{
/* 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;
/* 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;
/* 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;
#endif
if (Newdemo_state == ND_STATE_RECORDING)
{
newdemo_record_laser_level(player_info.laser_level, laser_level::_1);
}
set_secondary_weapon_to_concussion(player_info);
dead_player_end(); //player no longer dead
Player_dead_state = player_dead_state::no;
player_info.Player_eggs_dropped = false;
Dead_player_camera = 0;
#if defined(DXX_BUILD_DESCENT_II)
auto &Secondary_last_was_super = player_info.Secondary_last_was_super;
Secondary_last_was_super = {};
2015-04-19 04:18:53 +00:00
Afterburner_charge = GrantedItems.has_afterburner() ? F1_0 : 0;
Controls.state.afterburner = 0;
Last_afterburner_state = 0;
2015-03-22 18:49:21 +00:00
Missile_viewer = nullptr; //reset missile camera if out there
#endif
init_ai_for_ship();
2006-03-20 17:12:09 +00:00
}
digi_kill_sound_linked_to_object(plrobj);
2019-07-07 22:00:02 +00:00
init_player_stats_ship(plrobj, GameTime64);
}
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)
{
auto &player_info = plrobj.ctype.player_info;
player_info.lavafall_hiss_playing = false;
player_info.missile_gun = 0;
player_info.Spreadfire_toggle = 0;
player_info.killer_objnum = object_none;
#if defined(DXX_BUILD_DESCENT_II)
player_info.Omega_recharge_delay = 0;
player_info.Helix_orientation = 0;
#endif
player_info.mission.hostages_on_board = 0;
player_info.homing_object_dist = -F1_0; // Added by RH
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
}
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))
{
2017-08-13 20:38:31 +00:00
plr.time_level -= i2f(3600);
++ plr.hours_level;
}
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))
{
2017-08-13 20:38:31 +00:00
plr.time_total -= i2f(3600);
++ plr.hours_total;
}
2006-03-20 17:12:09 +00:00
}
//go through this level and start any eclip sounds
namespace dsx {
2020-12-26 21:17:29 +00:00
namespace {
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
{
auto &Effects = LevelUniqueEffectsClipState.Effects;
auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
2006-03-20 17:12:09 +00:00
digi_init_sounds(); //clear old sounds
#if defined(DXX_BUILD_DESCENT_II)
auto &Walls = LevelUniqueWallSubsystemState.Walls;
auto &vcwallptr = Walls.vcptr;
#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
const auto get_eclip_for_tmap = [](const d_level_unique_tmap_info_state::TmapInfo_array &TmapInfo, const unique_side &side) {
if (const auto tm2 = side.tmap_num2; tm2 != texture2_value::None)
{
const auto ec = TmapInfo[get_texture_index(tm2)].eclip_num;
#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;
#endif
};
2016-02-12 04:02:28 +00:00
range_for (const auto &&seg, vcsegptridx)
2014-10-12 23:10:05 +00:00
{
for (const auto sidenum : MAX_SIDES_PER_SEGMENT)
2019-05-04 18:27:36 +00:00
{
int sn;
2006-03-20 17:12:09 +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);
if (!(wid & WALL_IS_DOORWAY_FLAG::render))
continue;
#endif
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) {
#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.
if (IS_CHILD(csegnum) && csegnum < seg) {
if (wid & (WALL_IS_DOORWAY_FLAG::fly | WALL_IS_DOORWAY_FLAG::rendpast)) {
const auto &&csegp = vcsegptr(seg->children[sidenum]);
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
}
}
#endif
2006-03-20 17:12:09 +00:00
const auto &&pnt = compute_center_point_on_side(vcvertptr, seg, sidenum);
digi_link_sound_to_pos(sn, seg, sidenum, pnt, 1, F1_0/2);
2006-03-20 17:12:09 +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;
}
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
{
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
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) {
effect_obj->orient = player_obj.orient;
2006-03-20 17:12:09 +00:00
const auto sound_num = Vclip[VCLIP_PLAYER_APPEARANCE].sound_num;
if (sound_num > -1)
digi_link_sound_to_pos(sound_num, seg, sidenum_t::WLEFT, effect_obj->pos, 0, F0_5);
2006-03-20 17:12:09 +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
}
// 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)
{
while(len--) {
*s1 += *b++;
if (*s1 >= 255) *s1 -= 255;
*s2 += *s1;
}
}
2020-12-26 21:17:29 +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()
{
unsigned int sum1,sum2;
short s;
int t;
sum1 = sum2 = 0;
range_for (auto &&segp, vcsegptr)
{
const cscusegment i = *segp;
for (auto &&[sside, uside] : zip(i.s.sides, i.u.sides))
2015-02-14 22:48:27 +00:00
{
do_checksum_calc(reinterpret_cast<const uint8_t *>(&(sside.get_type())), 1, &sum1, &sum2);
s = INTEL_SHORT(underlying_value(sside.wall_num));
do_checksum_calc(reinterpret_cast<uint8_t *>(&s), 2, &sum1, &sum2);
s = underlying_value(uside.tmap_num);
2020-09-11 03:08:02 +00:00
s = INTEL_SHORT(s);
do_checksum_calc(reinterpret_cast<uint8_t *>(&s), 2, &sum1, &sum2);
s = underlying_value(uside.tmap_num2);
s = INTEL_SHORT(s);
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);
do_checksum_calc(reinterpret_cast<uint8_t *>(&t), 4, &sum1, &sum2);
2016-07-15 03:43:03 +00:00
t = INTEL_INT(k.v);
do_checksum_calc(reinterpret_cast<uint8_t *>(&t), 4, &sum1, &sum2);
2016-07-15 03:43:03 +00:00
t = INTEL_INT(k.l);
do_checksum_calc(reinterpret_cast<uint8_t *>(&t), 4, &sum1, &sum2);
}
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);
do_checksum_calc(reinterpret_cast<uint8_t *>(&t), 4, &sum1, &sum2);
2016-07-15 03:43:03 +00:00
t = INTEL_INT(k.y);
do_checksum_calc(reinterpret_cast<uint8_t *>(&t), 4, &sum1, &sum2);
2016-07-15 03:43:03 +00:00
t = INTEL_INT(k.z);
do_checksum_calc(reinterpret_cast<uint8_t *>(&t), 4, &sum1, &sum2);
}
}
for (const typename std::underlying_type<segnum_t>::type j : i.s.children)
2015-02-14 22:48:27 +00:00
{
const auto s = INTEL_SHORT(j);
do_checksum_calc(reinterpret_cast<const uint8_t *>(&s), 2, &sum1, &sum2);
}
2020-12-26 21:17:29 +00:00
range_for (const auto vn, i.s.verts)
2015-02-14 22:48:27 +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));
do_checksum_calc(reinterpret_cast<uint8_t *>(&s), 2, &sum1, &sum2);
}
s = INTEL_SHORT(i.u.objects);
do_checksum_calc(reinterpret_cast<uint8_t *>(&s), 2, &sum1, &sum2);
#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);
do_checksum_calc(reinterpret_cast<const uint8_t *>(&i.s.matcen_num), 1, &sum1, &sum2);
t = INTEL_INT(i.u.static_light);
do_checksum_calc(reinterpret_cast<uint8_t *>(&t), 4, &sum1, &sum2);
#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);
}
}
sum2 %= 255;
return ((sum1<<8)+ sum2);
}
}
2020-12-26 21:17:29 +00:00
}
#if defined(DXX_BUILD_DESCENT_II)
2015-12-13 18:00:49 +00:00
namespace dsx {
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);
load_level_robots(level_name);
}
// load just the hxm file
void load_level_robots(const d_fname &level_name)
{
if (Robot_replacements_loaded) {
free_polygon_models(LevelSharedPolygonModelState);
load_mission_ham();
Robot_replacements_loaded = 0;
}
load_robot_replacements(level_name);
}
}
#endif
2006-03-20 17:12:09 +00:00
//load a level off disk. level numbers start at 1. Secret levels are -1,-2,-3
namespace dsx {
2006-03-20 17:12:09 +00:00
void LoadLevel(int level_num,int page_in_textures)
{
auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
auto &Objects = LevelUniqueObjectState.Objects;
auto &Vertices = LevelSharedVertexState.get_vertices();
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);
#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)
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
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
auto &LevelSharedDestructibleLightState = LevelSharedSegmentState.DestructibleLights;
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)
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;
2022-09-24 17:47:52 +00:00
load_palette(Current_level_palette.line(), 1, 1); //don't change screen
#endif
2006-03-20 17:12:09 +00:00
#if DXX_USE_EDITOR
if (!EditorWindow)
#endif
{
2021-09-12 16:20:52 +00:00
gr_set_default_canvas();
show_boxed_message(*grd_curcanv, TXT_LOADING);
gr_flip();
}
#ifdef RELEASE
timer_delay(F1_0);
#endif
2006-03-20 17:12:09 +00:00
load_endlevel_data(level_num);
#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();
#endif
2006-03-20 17:12:09 +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
#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
#if defined(DXX_BUILD_DESCENT_I)
if ( page_in_textures )
piggy_load_level_data();
#endif
gameseq_init_network_players(LevelSharedRobotInfoState.Robot_info, Objects);
p.restore(vmobjptr);
2006-03-20 17:12:09 +00:00
}
}
2006-03-20 17:12:09 +00:00
//sets up Player_num & ConsoleObject
void InitPlayerObject()
{
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);
console->control_source = object::control_type::flying;
console->movement_source = object::movement_type::physics;
2006-03-20 17:12:09 +00:00
}
//starts a new game on the given level
namespace dsx {
void StartNewGame(const int start_level)
2006-03-20 17:12:09 +00:00
{
GameUniqueState.quicksave_selection = d_game_unique_state::save_slot::None; // for first blind save, pick slot to save in
reset_globals_for_new_game();
2006-03-20 17:12:09 +00:00
Game_mode = GM_NORMAL;
2006-03-20 17:12:09 +00:00
InitPlayerObject(); //make sure player's object set up
LevelUniqueObjectState.accumulated_robots = 0;
LevelUniqueObjectState.total_hostages = 0;
GameUniqueState.accumulated_robots = 0;
GameUniqueState.total_hostages = 0;
init_player_stats_game(Player_num); //clear all stats
2006-03-20 17:12:09 +00:00
N_players = 1;
#if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
if (start_level < 0)
{
/* 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);
}
2006-03-20 17:12:09 +00:00
else
#endif
{
StartNewLevel(start_level);
/* 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
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();
#if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
init_seismic_disturbances();
#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.
static void DoEndLevelScoreGlitz()
2006-03-20 17:12:09 +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
char m_str[N_GLITZITEMS][32];
2013-11-10 03:31:22 +00:00
newmenu_item m[N_GLITZITEMS];
int i;
#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)
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);
#endif
2006-03-20 17:12:09 +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
const auto Difficulty_level = GameUniqueState.Difficulty_level;
if (!cheats.enabled) {
const auto d = underlying_value(Difficulty_level);
switch (Difficulty_level)
{
default:
case Difficulty_level_type::_0:
case Difficulty_level_type::_1:
skill_points = 0;
break;
case Difficulty_level_type::_2:
case Difficulty_level_type::_3:
case Difficulty_level_type::_4:
#if defined(DXX_BUILD_DESCENT_I)
skill_points = level_points * (d - 1) / 2;
#elif defined(DXX_BUILD_DESCENT_II)
skill_points = level_points * d / 4;
#endif
skill_points -= skill_points % 100;
break;
}
2006-03-20 17:12:09 +00:00
hostage_points = player_info.mission.hostages_on_board * 500 * (d + 1);
#if defined(DXX_BUILD_DESCENT_I)
shield_points = f2i(plrobj.shields) * 10 * (d + 1);
energy_points = f2i(player_info.energy) * 5 * (d + 1);
#elif defined(DXX_BUILD_DESCENT_II)
shield_points = f2i(plrobj.shields) * 5 * mine_level;
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;
#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();
unsigned c = 0;
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);
else if (LevelUniqueObjectState.total_hostages > hostages_on_board)
{
const auto hostages_lost = LevelUniqueObjectState.total_hostages - hostages_on_board;
snprintf(hostage_text, sizeof(hostage_text), "Hostages lost: \t%u", hostages_lost);
}
else
2017-08-13 20:38:31 +00:00
{
all_hostage_points = hostages_on_board * 1000 * (underlying_value(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
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;
}
2006-03-20 17:12:09 +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;
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);
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)
{
}
};
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
}
}
2006-03-20 17:12:09 +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
{
auto &Objects = LevelUniqueObjectState.Objects;
auto &vmobjptridx = Objects.vmptridx;
Assert(Player_dead_state == player_dead_state::no);
2006-03-20 17:12:09 +00:00
InitPlayerPosition(vmobjptridx, vmsegptridx, 0);
2006-03-20 17:12:09 +00:00
verify_console_object();
ConsoleObject->control_source = object::control_type::flying;
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();
auto &player_info = ConsoleObject->ctype.player_info;
player_info.Auto_fire_fusion_cannon_time = 0;
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 {
if (PHYSFSX_exists(SECRETC_FILENAME,0))
2006-03-20 17:12:09 +00:00
{
return 0;
} else {
return 1;
}
}
}
// -----------------------------------------------------------------------------------------------------
#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
#endif
2020-12-26 21:17:29 +00:00
}
namespace {
static void do_screen_message(const char *msg) __attribute_nonnull();
static void do_screen_message(const char *msg)
{
if (Game_mode & GM_MULTI)
return;
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) :
items_type{{
2021-06-28 03:37:49 +00:00
newmenu_item::nm_item_menu{TXT_OK},
}},
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)
{
}
};
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 {
#if defined(DXX_BUILD_DESCENT_II)
2020-12-26 21:17:29 +00:00
namespace {
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
{
auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
auto &Objects = LevelUniqueObjectState.Objects;
auto &vmobjptr = Objects.vmptr;
2006-03-20 17:12:09 +00:00
2022-02-12 18:57:12 +00:00
last_drawn_cockpit = cockpit_mode_t{UINT8_MAX};
2006-03-20 17:12:09 +00:00
if (Newdemo_state == ND_STATE_PAUSED)
Newdemo_state = ND_STATE_RECORDING;
2006-03-20 17:12:09 +00:00
if (Newdemo_state == ND_STATE_RECORDING) {
newdemo_set_new_level(level_num);
Giving credits function ability to use custom creditfile (again); Made laser-offset for laser exclusive so Prox mines won't go tru doors; Preventing cycling tru cockpit modes while dead, but allowing to load a state; Implemented D2X' lighting code to D1X (faster, better, sexier - weeee); Try to hop over some errors regarding walls/doors in levels instead of using -1 indexes for arrays; Made the briefing text ptr a bit more failsafe in case the file is corrupt/non-standard; Made scores use the menu screen even in GAME OVER; Fixed bug in neighbour fields of Weapon Keys table; Added the Weapon Keys stuff to TABLE_CREATION; Fixed bug where D2X did not recall applied resolution in the resolutions menu; Simpler check to create DEMO_DIR; Seperated X/Y sensitivity for mouse and joystick; Flush controls when Automap toggles so keypress won't deactivate it again; Made FrameCount in Demos aligned to the Dropframe condition; Added KEy to ttoggle playback text off; Gracefully exit demo code if demo is corrupt; Removed that new percent counter because many old demos seem to have corrupted last frames; Closing endlevel data file if IFF error so the mission still can be freed; Fixed Cruising for keyboard which was not aligned to FPS correctly; Used mouse delta scaling in kconfig.c instead of mouse.c to not screw up when delta is requested in non-ingame situations - it actually belongs to the controls IMHO; Now support up to 8 joysticks; Changed some leftover malloc's to d_malloc and free to d_free
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);
2006-03-20 17:12:09 +00:00
if (First_secret_visit) {
do_screen_message(TXT_SECRET_EXIT);
2006-03-20 17:12:09 +00:00
} else {
if (PHYSFSX_exists(SECRETC_FILENAME,0))
2006-03-20 17:12:09 +00:00
{
do_screen_message(TXT_SECRET_EXIT);
2006-03-20 17:12:09 +00:00
} else {
do_screen_message("Secret level already destroyed.\nAdvancing 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();
automap_clear_visited(LevelUniqueAutomapState);
2006-03-20 17:12:09 +00:00
Viewer = &get_local_plrobj();
2006-03-20 17:12:09 +00:00
gameseq_remove_unused_players(LevelSharedRobotInfoState.Robot_info);
2006-03-20 17:12:09 +00:00
Game_suspended = 0;
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(LevelSharedRobotInfoState.Robot_info);
2006-03-20 17:12:09 +00:00
init_smega_detonates();
init_morphs();
init_all_matcens();
reset_special_effects();
StartSecretLevel();
} else {
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 {
do_screen_message("Secret level already destroyed.\nAdvancing 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(LevelSharedRobotInfoState.Robot_info);
2006-03-20 17:12:09 +00:00
}
init_controlcen_for_level(LevelSharedRobotInfoState.Robot_info);
2006-03-20 17:12:09 +00:00
2011-09-26 23:31:19 +00:00
// Say player can use FLASH cheat to mark path to exit.
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
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.
window_event_result ExitSecretLevel()
2006-03-20 17:12:09 +00:00
{
auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
auto &Objects = LevelUniqueObjectState.Objects;
auto &vmobjptr = Objects.vmptr;
auto result = window_event_result::handled;
2006-03-20 17:12:09 +00:00
if (Newdemo_state == ND_STATE_PLAYBACK)
return window_event_result::ignored;
2006-03-20 17:12:09 +00:00
const auto g = Game_wind;
if (g)
g->set_visible(0);
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
}
if (PHYSFSX_exists(SECRETB_FILENAME,0))
2006-03-20 17:12:09 +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)
{
2006-03-20 17:12:09 +00:00
DoEndGame();
result = window_event_result::close;
}
2006-03-20 17:12:09 +00:00
else {
do_screen_message(TXT_SECRET_ADVANCE);
StartNewLevel(Entered_from_level+1);
2006-03-20 17:12:09 +00:00
}
}
if (g)
g->set_visible(1);
reset_time();
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.
void do_cloak_invul_secret_stuff(fix64 old_gametime, player_info &player_info)
2006-03-20 17:12:09 +00:00
{
auto &pl_flags = player_info.powerup_flags;
2016-07-03 00:54:15 +00:00
if (pl_flags & PLAYER_FLAGS_INVULNERABLE)
{
fix64 time_used;
2006-03-20 17:12:09 +00:00
auto &t = player_info.invulnerable_time;
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;
auto &t = player_info.cloak_time;
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)
{
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) );
const auto g = Game_wind;
if (g)
g->set_visible(0);
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;
if (LevelUniqueControlCenterState.Control_center_destroyed)
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.
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
// 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);
do_screen_message("Alternate Exit Found!\n\nProceeding to Secret Level!");
StartNewLevel(Next_level_num);
} else {
StartNewLevelSecret(Next_level_num, 1);
}
// END NMN
2006-03-20 17:12:09 +00:00
// do_cloak_invul_stuff();
if (g)
g->set_visible(1);
reset_time();
2006-03-20 17:12:09 +00:00
}
#endif
2006-03-20 17:12:09 +00:00
//called when the player has finished a level
window_event_result (PlayerFinishedLevel)(
#if defined(DXX_BUILD_DESCENT_I)
const next_level_request_secret_flag secret_flag
#endif
)
2006-03-20 17:12:09 +00:00
{
auto &Objects = LevelUniqueObjectState.Objects;
auto &vmobjptr = Objects.vmptr;
const auto g = Game_wind;
if (g)
g->set_visible(0);
2006-03-20 17:12:09 +00:00
//credit the player for hostages
auto &player_info = get_local_plrobj().ctype.player_info;
player_info.mission.hostages_rescued_total += player_info.mission.hostages_on_board;
#if defined(DXX_BUILD_DESCENT_I)
if (!(Game_mode & GM_MULTI) && secret_flag != next_level_request_secret_flag::only_normal_level) {
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) :
items_type{{
2021-06-28 03:37:49 +00:00
newmenu_item::nm_item_text{" "}, //TXT_SECRET_EXIT;
}},
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)
{
}
};
2021-09-12 16:20:52 +00:00
run_blocking_newmenu<message_menu>(*grd_curcanv);
}
#elif defined(DXX_BUILD_DESCENT_II)
constexpr auto secret_flag = next_level_request_secret_flag::only_normal_level;
#endif
2006-03-20 17:12:09 +00:00
if (Game_mode & GM_NETWORK)
get_local_player().connected = player_connection_status::waiting; // Finished but did not die
2006-03-20 17:12:09 +00:00
2022-02-12 18:57:12 +00:00
last_drawn_cockpit = cockpit_mode_t{UINT8_MAX};
2006-03-20 17:12:09 +00:00
auto result = AdvanceLevel(secret_flag); //now go on to the next one (if one)
if (g)
g->set_visible(1);
reset_time();
return result;
}
2006-03-20 17:12:09 +00:00
#if defined(DXX_BUILD_DESCENT_II)
#define MOVIE_REQUIRED 1
2006-03-20 17:12:09 +00:00
#define ENDMOVIE "end"
#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
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 );
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
#if defined(DXX_BUILD_DESCENT_II)
auto played = PlayMovie(ENDMOVIE ".tex", ENDMOVIE ".mve", play_movie_warn_missing::urgent);
2021-01-17 22:23:23 +00:00
if (played == movie_play_status::skipped)
#endif
{
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
}
}
else if (!(Game_mode & GM_MULTI)) //not multi
{
2006-03-20 17:12:09 +00:00
char tname[FILENAME_LEN];
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
DoEndLevelScoreGlitz();
2006-03-20 17:12:09 +00:00
if (PLAYING_BUILTIN_MISSION && !((Game_mode & GM_MULTI) && !(Game_mode & GM_MULTI_COOP))) {
gr_set_default_canvas();
2017-01-01 00:45:45 +00:00
gr_clear_canvas(*grd_curcanv, BM_XRGB(0,0,0));
#if defined(DXX_BUILD_DESCENT_II)
2007-06-11 15:54:09 +00:00
load_palette(D2_DEFAULT_PALETTE,0,1);
#endif
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.
static window_event_result (AdvanceLevel)(
#if defined(DXX_BUILD_DESCENT_I)
next_level_request_secret_flag secret_flag
#endif
)
2006-03-20 17:12:09 +00:00
{
auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
auto rval = window_event_result::handled;
#if defined(DXX_BUILD_DESCENT_II)
// 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();
}
#endif
2021-11-01 03:37:19 +00:00
if (Current_level_num != Current_mission->last_level)
{
2006-03-20 17:12:09 +00:00
if (Game_mode & GM_MULTI)
{
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!!!
DoEndLevelScoreGlitz(); //give bonuses
2006-03-20 17:12:09 +00:00
}
LevelUniqueControlCenterState.Control_center_destroyed = 0;
2006-03-20 17:12:09 +00:00
if (Game_mode & GM_MULTI)
{
2013-11-10 03:31:22 +00:00
int result;
result = multi::dispatch->end_current_level(
#if defined(DXX_BUILD_DESCENT_I)
&secret_flag
#endif
); // Wait for other players to reach this point
2006-03-20 17:12:09 +00:00
if (result) // failed to sync
{
// 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!
2006-03-20 17:12:09 +00:00
DoEndGame();
rval = window_event_result::close;
2006-03-20 17:12:09 +00:00
} else {
#if defined(DXX_BUILD_DESCENT_I)
const auto Next_level_num = find_next_level(secret_flag, Current_level_num, *Current_mission.get());
#elif defined(DXX_BUILD_DESCENT_II)
int8_t Next_level_num;
//NMN 04/08/07 If we are in a secret level and playing a D1
// level, then use Entered_from_level # instead
if (Current_level_num < 0 && EMULATING_D1)
{
Next_level_num = Entered_from_level+1; //assume go to next normal level
2013-11-10 03:31:22 +00:00
} else {
Next_level_num = Current_level_num+1; //assume go to next normal level
}
// END NMN
#endif
rval = std::max(StartNewLevel(Next_level_num), rval);
2006-03-20 17:12:09 +00:00
}
return rval;
2006-03-20 17:12:09 +00:00
}
2020-12-26 21:17:29 +00:00
}
window_event_result DoPlayerDead()
2006-03-20 17:12:09 +00:00
{
auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
auto &Objects = LevelUniqueObjectState.Objects;
auto &vmobjptr = Objects.vmptr;
const bool pause = !(((Game_mode & GM_MULTI) && (Newdemo_state != ND_STATE_PLAYBACK)) && (!Endlevel_sequence));
auto result = window_event_result::handled;
if (pause)
stop_time();
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)
{
if (PLAYING_BUILTIN_MISSION)
scores_maybe_add_player();
if (pause)
start_time();
return window_event_result::close;
2006-03-20 17:12:09 +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;
player_info.mission.hostages_on_board = 0;
player_info.energy = 0;
2017-08-13 20:38:31 +00:00
plrobj.shields = 0;
plr.connected = player_connection_status::died_in_mine;
2006-03-20 17:12:09 +00:00
do_screen_message(TXT_DIED_IN_MINE); // Give them some indication of what happened
#if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
if (Current_level_num < 0) {
if (PHYSFSX_exists(SECRETB_FILENAME,0))
2006-03-20 17:12:09 +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)
{
DoEndGame();
result = window_event_result::close;
}
else {
do_screen_message(TXT_SECRET_ADVANCE);
StartNewLevel(Entered_from_level+1);
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!
}
2006-03-20 17:12:09 +00:00
}
} else
#endif
{
2006-03-20 17:12:09 +00:00
const auto g = Game_wind;
if (g)
g->set_visible(0);
result = AdvanceLevel(next_level_request_secret_flag::only_normal_level); //if finished, go on to next level
2006-03-20 17:12:09 +00:00
init_player_stats_new_ship(Player_num);
2022-02-12 18:57:12 +00:00
last_drawn_cockpit = cockpit_mode_t{UINT8_MAX};
if (g)
g->set_visible(1);
2006-03-20 17:12:09 +00:00
}
#if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
} else if (Current_level_num < 0) {
if (PHYSFSX_exists(SECRETB_FILENAME,0))
2006-03-20 17:12:09 +00:00
{
do_screen_message(TXT_SECRET_RETURN);
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 {
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)
{
DoEndGame();
result = window_event_result::close;
}
else {
do_screen_message(TXT_SECRET_ADVANCE);
StartNewLevel(Entered_from_level+1);
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!
}
2006-03-20 17:12:09 +00:00
}
#endif
2006-03-20 17:12:09 +00:00
} else {
init_player_stats_new_ship(Player_num);
2006-03-20 17:12:09 +00:00
StartLevel(1);
}
digi_sync_sounds();
if (pause)
start_time();
reset_time();
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)
window_event_result StartNewLevelSub(const d_robot_info_array &Robot_info, const int level_num, const int page_in_textures)
2015-04-02 02:36:53 +00:00
#elif defined(DXX_BUILD_DESCENT_II)
window_event_result StartNewLevelSub(const d_robot_info_array &Robot_info, 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
{
auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
auto &Objects = LevelUniqueObjectState.Objects;
auto &vmobjptr = Objects.vmptr;
2006-03-20 17:12:09 +00:00
if (!(Game_mode & GM_MULTI)) {
2022-02-12 18:57:12 +00:00
last_drawn_cockpit = cockpit_mode_t{UINT8_MAX};
2006-03-20 17:12:09 +00:00
}
#if defined(DXX_BUILD_DESCENT_I)
static constexpr std::integral_constant<secret_restore, secret_restore::none> secret_flag{};
#elif defined(DXX_BUILD_DESCENT_II)
BigWindowSwitch=0;
#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);
Giving credits function ability to use custom creditfile (again); Made laser-offset for laser exclusive so Prox mines won't go tru doors; Preventing cycling tru cockpit modes while dead, but allowing to load a state; Implemented D2X' lighting code to D1X (faster, better, sexier - weeee); Try to hop over some errors regarding walls/doors in levels instead of using -1 indexes for arrays; Made the briefing text ptr a bit more failsafe in case the file is corrupt/non-standard; Made scores use the menu screen even in GAME OVER; Fixed bug in neighbour fields of Weapon Keys table; Added the Weapon Keys stuff to TABLE_CREATION; Fixed bug where D2X did not recall applied resolution in the resolutions menu; Simpler check to create DEMO_DIR; Seperated X/Y sensitivity for mouse and joystick; Flush controls when Automap toggles so keypress won't deactivate it again; Made FrameCount in Demos aligned to the Dropframe condition; Added KEy to ttoggle playback text off; Gracefully exit demo code if demo is corrupt; Removed that new percent counter because many old demos seem to have corrupted last frames; Closing endlevel data file if IFF error so the mission still can be freed; Fixed Cruising for keyboard which was not aligned to FPS correctly; Used mouse delta scaling in kconfig.c instead of mouse.c to not screw up when delta is requested in non-ingame situations - it actually belongs to the controls IMHO; Now support up to 8 joysticks; Changed some leftover malloc's to d_malloc and free to d_free
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
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)
{
multi_prep_level_objects(Powerup_info, Vclip);
if (multi::dispatch->level_sync() == window_event_result::close) // After calling this, Player_num is set
{
songs_play_song( SONG_TITLE, 1 ); // level song already plays but we fail to start level...
return window_event_result::close;
}
2006-03-20 17:12:09 +00:00
}
HUD_clear_messages();
automap_clear_visited(LevelUniqueAutomapState);
2006-03-20 17:12:09 +00:00
LevelUniqueObjectState.accumulated_robots = count_number_of_robots(Objects.vcptr);
GameUniqueState.accumulated_robots += LevelUniqueObjectState.accumulated_robots;
LevelUniqueObjectState.total_hostages = count_number_of_hostages(Objects.vcptr);
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
#if defined(DXX_BUILD_DESCENT_I)
gr_use_palette_table( "palette.256" );
#elif defined(DXX_BUILD_DESCENT_II)
2022-09-24 17:47:52 +00:00
load_palette(Current_level_palette.line(), 0, 1);
#endif
gr_palette_load(gr_palette);
2006-03-20 17:12:09 +00:00
if ((Game_mode & GM_MULTI_COOP) && Network_rejoined)
{
for (playernum_t i = 0; i < N_players; ++i)
{
const auto &&plobj = vmobjptr(vcplayerptr(i)->objnum);
plobj->ctype.player_info.powerup_flags |= Netgame.net_player_flags[i];
}
2006-03-20 17:12:09 +00:00
}
if (Game_mode & GM_MULTI)
{
multi_prep_level_player();
2006-03-20 17:12:09 +00:00
}
gameseq_remove_unused_players(Robot_info);
2006-03-20 17:12:09 +00:00
Game_suspended = 0;
LevelUniqueControlCenterState.Control_center_destroyed = 0;
2006-03-20 17:12:09 +00:00
#if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
set_screen_mode(SCREEN_GAME);
#endif
2006-03-20 17:12:09 +00:00
init_cockpit();
init_robots_for_level();
init_ai_objects(Robot_info);
2006-03-20 17:12:09 +00:00
init_morphs();
init_all_matcens();
reset_palette_add();
LevelUniqueStuckObjectState.init_stuck_objects();
#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))
filter_objects_from_level(Powerup_info, Vclip, vmobjptr);
#endif
2006-03-20 17:12:09 +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();
#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(Robot_info);
init_controlcen_for_level(Robot_info);
2006-03-20 17:12:09 +00:00
// Say player can use FLASH cheat to mark path to exit.
LevelUniqueObjectState.Level_path_created = 0;
// 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();
if (!Game_wind)
game();
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
}
#if defined(DXX_BUILD_DESCENT_II)
2020-12-26 21:17:29 +00:00
namespace {
struct intro_movie_t {
2006-03-20 17:12:09 +00:00
int level_num;
char movie_name[8];
};
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)) {
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)
{
if (level_num != 1)
return;
2006-03-20 17:12:09 +00:00
}
else if (is_D2_OEM)
{
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;
PlayMovie({}, i.movie_name, play_movie_warn_missing::urgent);
2006-03-20 17:12:09 +00:00
break;
}
}
}
}
//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
}
#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
window_event_result StartNewLevel(int level_num)
2006-03-20 17:12:09 +00:00
{
hide_menus();
GameTime64 = 0;
/* Autosave is permitted immediately on entering a new level */
state_set_immediate_autosave(GameUniqueState);
ThisLevelTime = {};
2006-03-20 17:12:09 +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);
}
#elif defined(DXX_BUILD_DESCENT_II)
if (level_num > 0) {
2006-03-20 17:12:09 +00:00
maybe_set_first_secret_visit(level_num);
}
ShowLevelIntro(level_num);
#endif
2006-03-20 17:12:09 +00:00
return StartNewLevelSub(LevelSharedRobotInfoState.Robot_info, 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;
per_player_array<site> sites;
2015-07-04 21:01:18 +00:00
public:
respawn_locations(fvmobjptr &vmobjptr, fvcsegptridx &vcsegptridx)
2015-07-04 21:01:18 +00:00
{
const auto player_num = Player_num;
const auto find_closest_player = [player_num, &vmobjptr, &vcsegptridx](const obj_position &candidate) {
fix closest_dist = INT32_MAX;
const auto &&candidate_segp = vcsegptridx(candidate.segnum);
for (playernum_t i = N_players; i--;)
2015-07-04 21:01:18 +00:00
{
if (i == player_num)
continue;
const auto &&objp = vmobjptr(vcplayerptr(i)->objnum);
2015-07-04 21:01:18 +00:00
if (objp->type != OBJ_PLAYER)
continue;
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;
};
const auto max_spawn_sites = std::min<unsigned>(NumNetPlayerPositions, sites.size());
for (playernum_t i = max_spawn_sites; i--;)
2015-07-04 21:01:18 +00:00
{
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[](const playernum_t i) const
2015-07-04 21:01:18 +00:00
{
return sites[i];
}
};
2006-03-20 17:12:09 +00:00
//initialize the player object position & orientation (at start of game, or new ship)
static void InitPlayerPosition(fvmobjptridx &vmobjptridx, fvmsegptridx &vmsegptridx, int random_flag)
2006-03-20 17:12:09 +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)
{
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;
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 {
// If deathmatch and not random, positions were already determined by sync packet
reset_player_object(*ConsoleObject);
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));
reset_player_object(*ConsoleObject);
2006-03-20 17:12:09 +00:00
}
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?
void copy_defaults_to_robot(const d_robot_info_array &Robot_info, object_base &objp)
2006-03-20 17:12:09 +00:00
{
assert(objp.type == OBJ_ROBOT);
const unsigned objid = get_robot_id(objp);
2006-03-20 17:12:09 +00:00
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.
fix shields = robptr.strength;
2006-03-20 17:12:09 +00:00
#if defined(DXX_BUILD_DESCENT_II)
const auto &Difficulty_level = GameUniqueState.Difficulty_level;
2013-11-03 22:27:28 +00:00
if ((robot_is_thief(robptr)) || (robot_is_companion(robptr))) {
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) {
case Difficulty_level_type::_0:
shields = i2f(20000);
break; // Trainee, basically unkillable
case Difficulty_level_type::_1:
shields *= 3;
break; // Rookie, pretty dang hard
case Difficulty_level_type::_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.
{
2006-03-20 17:12:09 +00:00
// Additional wimpification of bosses at Trainee
shields = shields / (NDL + 3) * (Difficulty_level != Difficulty_level_type::_0 ? underlying_value(Difficulty_level) + 4 : 2);
}
#endif
objp.shields = shields;
}
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.
static void copy_defaults_to_robot_all(const d_robot_info_array &Robot_info)
2006-03-20 17:12:09 +00:00
{
auto &Objects = LevelUniqueObjectState.Objects;
auto &vmobjptr = Objects.vmptr;
range_for (object_base &objp, vmobjptr)
2015-06-13 22:42:17 +00:00
{
if (objp.type == OBJ_ROBOT)
copy_defaults_to_robot(Robot_info, objp);
2015-06-13 22:42:17 +00:00
}
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
{
auto &Objects = LevelUniqueObjectState.Objects;
auto &vmobjptridx = Objects.vmptridx;
assert(Player_dead_state == player_dead_state::no);
2006-03-20 17:12:09 +00:00
InitPlayerPosition(vmobjptridx, vmsegptridx, random_flag);
2006-03-20 17:12:09 +00:00
verify_console_object();
ConsoleObject->control_source = object::control_type::flying;
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();
multi::dispatch->do_protocol_frame(1, 1);
}
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();
auto &player_info = ConsoleObject->ctype.player_info;
player_info.Auto_fire_fusion_cannon_time = 0;
player_info.Fusion_charge = 0;
2006-03-20 17:12:09 +00:00
}
2020-12-26 21:17:29 +00:00
}
}