dxx-rebirth/similar/main/game.cpp

2523 lines
69 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.
*/
/*
*
* Game loop for Inferno
*
*/
#include "dxxsconf.h"
2006-03-20 17:12:09 +00:00
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
2013-06-30 02:22:56 +00:00
#include <SDL.h>
#include <ctime>
2018-02-18 00:42:42 +00:00
#if DXX_USE_SCREENSHOT_FORMAT_PNG
#include <png.h>
#include "vers_id.h"
#endif
2006-03-20 17:12:09 +00:00
#if DXX_USE_OGL
2006-03-20 17:12:09 +00:00
#include "ogl_init.h"
#endif
#include "pstypes.h"
#include "console.h"
#include "gr.h"
#include "inferno.h"
#include "game.h"
#include "key.h"
2013-12-26 04:18:28 +00:00
#include "config.h"
2006-03-20 17:12:09 +00:00
#include "object.h"
#include "dxxerror.h"
2006-03-20 17:12:09 +00:00
#include "joy.h"
#include "pcx.h"
#include "timer.h"
#include "render.h"
#include "laser.h"
#include "screens.h"
#include "textures.h"
#include "gauges.h"
#include "3d.h"
#include "effects.h"
#include "menu.h"
#include "player.h"
2006-03-20 17:12:09 +00:00
#include "gameseg.h"
#include "wall.h"
#include "ai.h"
#include "fuelcen.h"
#include "digi.h"
#include "u_mem.h"
#include "palette.h"
#include "morph.h"
#include "lighting.h"
#include "newdemo.h"
#include "collide.h"
#include "weapon.h"
#include "sounds.h"
#include "args.h"
#include "gameseq.h"
#include "automap.h"
#include "text.h"
#include "powerup.h"
#include "fireball.h"
#include "newmenu.h"
#include "gamefont.h"
#include "endlevel.h"
#include "kconfig.h"
#include "mouse.h"
#include "switch.h"
#include "controls.h"
#include "songs.h"
#include "multi.h"
#include "cntrlcen.h"
#include "pcx.h"
#include "state.h"
#include "piggy.h"
#include "ai.h"
#include "robot.h"
#include "playsave.h"
2012-07-01 02:54:33 +00:00
#include "maths.h"
2006-03-20 17:12:09 +00:00
#include "hudmsg.h"
#if defined(DXX_BUILD_DESCENT_II)
#include <climits>
#include "gamepal.h"
#include "movie.h"
#endif
#include "event.h"
#include "window.h"
2006-03-20 17:12:09 +00:00
#if DXX_USE_EDITOR
2006-03-20 17:12:09 +00:00
#include "editor/editor.h"
2013-03-16 03:10:55 +00:00
#include "editor/esegment.h"
2006-03-20 17:12:09 +00:00
#endif
#include "d_enumerate.h"
#include "d_levelstate.h"
#include "d_range.h"
#include "compiler-range_for.h"
2014-11-23 04:36:58 +00:00
#include "partial_range.h"
#include "segiter.h"
2006-03-20 17:12:09 +00:00
d_time_fix ThisLevelTime;
2006-03-20 17:12:09 +00:00
namespace dcx {
namespace {
static fix64 last_timer_value;
static fix64 sync_timer_value;
}
grs_subcanvas Screen_3d_window; // The rectangle for rendering the mine to
int force_cockpit_redraw=0;
int PaletteRedAdd, PaletteGreenAdd, PaletteBlueAdd;
2006-03-20 17:12:09 +00:00
int Game_suspended=0; //if non-zero, nothing moves but player
game_mode_flags Game_mode;
2006-03-20 17:12:09 +00:00
int Global_missile_firing_count = 0;
std::optional<Difficulty_level_type> build_difficulty_level_from_untrusted(const int8_t untrusted)
{
switch (untrusted)
{
case static_cast<int8_t>(Difficulty_level_type::_0):
case static_cast<int8_t>(Difficulty_level_type::_1):
case static_cast<int8_t>(Difficulty_level_type::_2):
case static_cast<int8_t>(Difficulty_level_type::_3):
case static_cast<int8_t>(Difficulty_level_type::_4):
return Difficulty_level_type{untrusted};
default:
return std::nullopt;
}
}
}
2006-03-20 17:12:09 +00:00
// Function prototypes for GAME.C exclusively.
namespace dsx {
game_window *Game_wind;
namespace {
static window_event_result GameProcessFrame(const d_level_shared_robot_info_state &);
static bool FireLaser(player_info &, const control_info &Controls);
static void powerup_grab_cheat_all();
2013-09-22 22:26:27 +00:00
}
2013-09-22 22:26:27 +00:00
#if defined(DXX_BUILD_DESCENT_II)
d_flickering_light_state Flickering_light_state;
namespace {
2013-09-22 22:26:27 +00:00
static void slide_textures(void);
static void flicker_lights(const d_level_shared_destructible_light_state &LevelSharedDestructibleLightState, d_flickering_light_state &fls, fvmsegptridx &vmsegptridx);
}
2013-09-22 22:26:27 +00:00
#endif
2006-03-20 17:12:09 +00:00
// Cheats
game_cheats cheats;
2006-03-20 17:12:09 +00:00
// ==============================================================================================
//this is called once per game
void init_game()
{
init_objects();
init_special_effects();
Clear_window = 2; // do portal only window clear.
}
}
namespace dcx {
2006-03-20 17:12:09 +00:00
void reset_palette_add()
{
PaletteRedAdd = 0;
PaletteGreenAdd = 0;
PaletteBlueAdd = 0;
}
constexpr screen_mode initial_large_game_screen_mode{1024, 768};
screen_mode Game_screen_mode = initial_large_game_screen_mode;
2006-03-20 17:12:09 +00:00
#if DXX_USE_STEREOSCOPIC_RENDER
2021-09-12 16:20:52 +00:00
StereoFormat VR_stereo;
fix VR_eye_width = F1_0;
int VR_eye_offset = 0;
int VR_sync_width = 20;
grs_subcanvas VR_hud_left;
grs_subcanvas VR_hud_right;
#endif
}
namespace dsx {
#if DXX_USE_STEREOSCOPIC_RENDER
void init_stereo()
{
#if DXX_USE_OGL
// init stereo options
if (CGameArg.OglStereo || CGameArg.OglStereoView) {
2021-09-12 16:20:52 +00:00
if (VR_stereo == StereoFormat::None && !VR_eye_offset)
VR_stereo = (CGameArg.OglStereoView) ? static_cast<StereoFormat>(CGameArg.OglStereoView % (static_cast<unsigned>(StereoFormat::HighestFormat) + 1)) : StereoFormat::AboveBelow;
constexpr int half_width_eye_offset = -6;
constexpr int full_width_eye_offset = -12;
switch (VR_stereo)
{
2021-09-12 16:20:52 +00:00
case StereoFormat::None:
case StereoFormat::AboveBelow:
case StereoFormat::AboveBelowSync:
VR_eye_offset = full_width_eye_offset;
break;
2021-09-12 16:20:52 +00:00
case StereoFormat::SideBySideFullHeight:
case StereoFormat::SideBySideHalfHeight:
VR_eye_offset = half_width_eye_offset;
break;
}
VR_eye_width = (F1_0 * 7) / 10; // Descent 1.5 defaults
VR_sync_width = (20 * SHEIGHT) / 480;
2022-02-12 18:57:12 +00:00
PlayerCfg.CockpitMode[1] = cockpit_mode_t::full_screen;
}
else {
2021-09-12 16:20:52 +00:00
VR_stereo = StereoFormat::None;
}
#endif
}
#endif
2006-03-20 17:12:09 +00:00
//initialize the various canvases on the game screen
//called every time the screen mode or cockpit changes
void init_cockpit()
{
//Initialize the on-screen canvases
2006-03-20 17:12:09 +00:00
if (Screen_mode != SCREEN_GAME)
return;
if ( Screen_mode == SCREEN_EDITOR )
2022-02-12 18:57:12 +00:00
PlayerCfg.CockpitMode[1] = cockpit_mode_t::full_screen;
2006-03-20 17:12:09 +00:00
#if !DXX_USE_OGL
if (PlayerCfg.CockpitMode[1] != cockpit_mode_t::letterbox)
2015-05-14 02:23:13 +00:00
{
#if defined(DXX_BUILD_DESCENT_II)
2015-05-14 02:23:13 +00:00
int HiresGFXAvailable = !GameArg.GfxSkipHiresGFX;
#endif
const auto full_screen_mode = HiresGFXAvailable
? screen_mode /* large_game_screen_mode */{640, 480}
: screen_mode /* small_game_screen_mode */{320, 200};
2015-05-14 02:23:13 +00:00
if (Game_screen_mode != full_screen_mode) {
PlayerCfg.CockpitMode[1] = cockpit_mode_t::full_screen;
2015-05-14 02:23:13 +00:00
}
}
#endif
2006-03-20 17:12:09 +00:00
gr_set_default_canvas();
2021-09-12 16:20:52 +00:00
auto &canvas = *grd_curcanv;
2006-03-20 17:12:09 +00:00
switch( PlayerCfg.CockpitMode[1] ) {
2022-02-12 18:57:12 +00:00
case cockpit_mode_t::full_cockpit:
2021-09-12 16:20:52 +00:00
game_init_render_sub_buffers(canvas, 0, 0, SWIDTH, (SHEIGHT*2)/3);
break;
2006-03-20 17:12:09 +00:00
2022-02-12 18:57:12 +00:00
case cockpit_mode_t::rear_view:
2014-07-01 00:23:30 +00:00
{
unsigned x1 = 0, y1 = 0, x2 = SWIDTH, y2 = (SHEIGHT*2)/3;
2022-02-12 18:57:12 +00:00
const auto mode =
#if defined(DXX_BUILD_DESCENT_II)
2022-02-12 18:57:12 +00:00
HIRESMODE
? static_cast<cockpit_mode_t>(static_cast<uint8_t>(cockpit_mode_t::rear_view) + (Num_cockpits / 2))
:
#endif
2022-02-12 18:57:12 +00:00
cockpit_mode_t::rear_view;
2022-12-31 16:21:47 +00:00
const auto cb = cockpit_bitmap[mode];
2022-02-12 18:57:12 +00:00
PIGGY_PAGE_IN(cb);
2022-12-31 16:21:47 +00:00
auto &bm = GameBitmaps[cb];
2014-07-01 00:23:30 +00:00
gr_bitblt_find_transparent_area(bm, x1, y1, x2, y2);
2021-09-12 16:20:52 +00:00
game_init_render_sub_buffers(canvas, x1 * (static_cast<float>(SWIDTH) / bm.bm_w), y1 * (static_cast<float>(SHEIGHT) / bm.bm_h), (x2 - x1 + 1) * (static_cast<float>(SWIDTH) / bm.bm_w), (y2 - y1 + 2) * (static_cast<float>(SHEIGHT) / bm.bm_h));
break;
}
2022-02-12 18:57:12 +00:00
case cockpit_mode_t::full_screen:
{
unsigned w = SWIDTH;
unsigned h = SHEIGHT;
#if DXX_USE_STEREOSCOPIC_RENDER
switch (VR_stereo)
{
2021-09-12 16:20:52 +00:00
case StereoFormat::None:
/* Preserve width */
/* Preserve height */
break;
2021-09-12 16:20:52 +00:00
case StereoFormat::AboveBelow:
case StereoFormat::AboveBelowSync:
/* Preserve width */
/* Change height */
h /= 2;
break;
2021-09-12 16:20:52 +00:00
case StereoFormat::SideBySideHalfHeight:
/* Change width */
/* Change height */
h /= 2;
[[fallthrough]];
2021-09-12 16:20:52 +00:00
case StereoFormat::SideBySideFullHeight:
/* Change width */
/* Preserve height */
w /= 2;
break;
}
#endif
2021-09-12 16:20:52 +00:00
game_init_render_sub_buffers(canvas, 0, 0, w, h);
}
break;
2022-02-12 18:57:12 +00:00
case cockpit_mode_t::status_bar:
2021-09-12 16:20:52 +00:00
game_init_render_sub_buffers(canvas, 0, 0, SWIDTH, (HIRESMODE?(SHEIGHT*2)/2.6:(SHEIGHT*2)/2.72));
break;
2006-03-20 17:12:09 +00:00
2022-02-12 18:57:12 +00:00
case cockpit_mode_t::letterbox: {
const unsigned gsm_height = grd_curscreen->get_screen_height();
const unsigned w = grd_curscreen->get_screen_width();
2015-05-14 02:23:13 +00:00
const unsigned h = (gsm_height * 3) / 4; // true letterbox size (16:9)
const unsigned x = 0;
const unsigned y = (gsm_height - h) / 2;
2006-03-20 17:12:09 +00:00
const uint8_t color = 0;
2017-03-10 01:22:27 +00:00
auto &canvas = *grd_curcanv;
gr_rect(canvas, x, 0, w, gsm_height - h, color);
gr_rect(canvas, x, gsm_height - h, w, gsm_height, color);
2021-09-12 16:20:52 +00:00
game_init_render_sub_buffers(canvas, x, y, w, h);
break;
}
2006-03-20 17:12:09 +00:00
}
gr_set_default_canvas();
2006-03-20 17:12:09 +00:00
}
}
2006-03-20 17:12:09 +00:00
//selects a given cockpit (or lack of one). See types in game.h
2022-02-12 18:57:12 +00:00
void select_cockpit(const cockpit_mode_t mode)
2006-03-20 17:12:09 +00:00
{
#if DXX_USE_STEREOSCOPIC_RENDER
// skip switching cockpit views while stereo viewport active
2022-02-12 18:57:12 +00:00
if (VR_stereo != StereoFormat::None && mode != cockpit_mode_t::full_screen)
return;
#endif
if (mode != PlayerCfg.CockpitMode[1]) { //new mode
PlayerCfg.CockpitMode[1]=mode;
2006-03-20 17:12:09 +00:00
init_cockpit();
}
}
namespace dcx {
2006-03-20 17:12:09 +00:00
//force cockpit redraw next time. call this if you've trashed the screen
void reset_cockpit()
{
force_cockpit_redraw=1;
2022-02-12 18:57:12 +00:00
last_drawn_cockpit = cockpit_mode_t{UINT8_MAX};
2006-03-20 17:12:09 +00:00
}
2021-09-12 16:20:52 +00:00
void game_init_render_sub_buffers(grs_canvas &canvas, const int x, const int y, const int w, const int h)
2006-03-20 17:12:09 +00:00
{
2021-09-12 16:20:52 +00:00
gr_clear_canvas(canvas, 0);
gr_init_sub_canvas(Screen_3d_window, canvas, x, y, w, h);
#if DXX_USE_STEREOSCOPIC_RENDER
2021-09-12 16:20:52 +00:00
if (VR_stereo != StereoFormat::None)
{
// offset HUD screen rects to force out-of-screen parallax on HUD overlays
2021-09-12 16:20:52 +00:00
const int dx = (VR_eye_offset < 0) ? -VR_eye_offset : 0;
const int dy = VR_sync_width / 2;
struct {
uint16_t x;
uint16_t y;
uint16_t w;
uint16_t h;
} l, r;
switch (VR_stereo) {
2021-09-12 16:20:52 +00:00
case StereoFormat::None:
default:
return;
2021-09-12 16:20:52 +00:00
case StereoFormat::AboveBelow:
l.x = x + dx;
l.y = y;
l.w = r.w = w - dx;
l.h = r.h = h;
r.x = x;
r.y = y + h;
break;
2021-09-12 16:20:52 +00:00
case StereoFormat::AboveBelowSync:
l.x = x + dx;
l.y = y;
l.w = r.w = w - dx;
l.h = r.h = h - dy;
r.x = x;
r.y = y + h + dy;
break;
2021-09-12 16:20:52 +00:00
case StereoFormat::SideBySideFullHeight:
l.x = x + dx;
l.y = r.y = y;
l.w = r.w = w - dx;
l.h = r.h = h;
r.x = x + w;
break;
2021-09-12 16:20:52 +00:00
case StereoFormat::SideBySideHalfHeight:
l.x = x + dx;
l.y = r.y = y + (h / 2);
l.w = r.w = w - dx;
l.h = r.h = h;
r.x = x + w;
break;
}
gr_init_sub_canvas(VR_hud_left, grd_curscreen->sc_canvas, l.x, l.y, l.w, l.h);
gr_init_sub_canvas(VR_hud_right, grd_curscreen->sc_canvas, r.x, r.y, r.w, r.h);
}
#endif
2006-03-20 17:12:09 +00:00
}
}
namespace dsx {
2006-03-20 17:12:09 +00:00
//called to change the screen mode. Parameter sm is the new mode, one of
//SMODE_GAME or SMODE_EDITOR. returns mode acutally set (could be other
//mode if cannot init requested mode)
int set_screen_mode(int sm)
{
2015-05-14 02:23:13 +00:00
if ( (Screen_mode == sm) && !((sm==SCREEN_GAME) && (grd_curscreen->get_screen_mode() != Game_screen_mode)) && !(sm==SCREEN_MENU) )
{
return 1;
}
#if DXX_USE_EDITOR
2006-03-20 17:12:09 +00:00
Canv_editor = NULL;
#endif
Screen_mode = sm;
Enable building with SDL2 This commit enables Rebirth to build with SDL2, but the result is not perfect. - SDL2 removed some sticky key support. Rebirth may behave differently now in this area. - SDL2 removed some key-repeat related support. Rebirth may behave differently now in this area. - SDL2 gained the ability to make a window fullscreen by sizing it to the desktop instead of by changing the desktop resolution. Rebirth uses this, and it mostly works. - Resizing while in the automap does not notify the automap code, so the view is wrong until the player switches out of automap mode and back in. - SDL2 changed how to enumerate available resolutions. Since fitting the window to the desktop is generally more useful than fitting the desktop to the window, I chose to drop support for enumerating resolutions instead of porting to the new API. Users can now enter an arbitrary window dimension and Rebirth will make an attempt to use it. - It might be useful to cap the window dimension at the desktop dimension, but that is not done yet. - Entering fullscreen mode through the Controls->Graphics submenu failed to notify the relevant subsystems, causing the rendered content not to rescale. For now, compile out the option to toggle full screen through that menu. Toggling through Alt+Enter works properly. Despite these quirks, this is a substantial improvement over the prior commit, where SDL2 cannot be used at all. The remaining issues can be resolved in future work. References: <https://github.com/dxx-rebirth/dxx-rebirth/issues/82>
2018-07-28 23:22:58 +00:00
#if SDL_MAJOR_VERSION == 1
2006-03-20 17:12:09 +00:00
switch( Screen_mode )
{
case SCREEN_MENU:
2015-05-14 02:23:13 +00:00
if (grd_curscreen->get_screen_mode() != Game_screen_mode)
if (gr_set_mode(Game_screen_mode))
Error("Cannot set screen mode.");
break;
case SCREEN_GAME:
2015-05-14 02:23:13 +00:00
if (grd_curscreen->get_screen_mode() != Game_screen_mode)
if (gr_set_mode(Game_screen_mode))
Error("Cannot set screen mode.");
break;
#if DXX_USE_EDITOR
case SCREEN_EDITOR:
2015-05-14 02:23:13 +00:00
{
const screen_mode editor_mode{800, 600};
if (grd_curscreen->get_screen_mode() != editor_mode)
{
int gr_error;
2015-05-14 02:23:13 +00:00
if ((gr_error = gr_set_mode(editor_mode)) != 0) { //force into game scrren
Warning("Cannot init editor screen (error=%d)",gr_error);
return 0;
}
2006-03-20 17:12:09 +00:00
}
2015-05-14 02:23:13 +00:00
}
break;
#endif
#if defined(DXX_BUILD_DESCENT_II)
case SCREEN_MOVIE:
2015-05-14 02:23:13 +00:00
{
const screen_mode movie_mode{MOVIE_WIDTH, MOVIE_HEIGHT};
if (grd_curscreen->get_screen_mode() != movie_mode)
{
if (gr_set_mode(movie_mode))
Error("Cannot set screen mode for game!");
gr_palette_load( gr_palette );
}
2015-05-14 02:23:13 +00:00
}
break;
#endif
default:
Error("Invalid screen mode %d",sm);
2006-03-20 17:12:09 +00:00
}
Enable building with SDL2 This commit enables Rebirth to build with SDL2, but the result is not perfect. - SDL2 removed some sticky key support. Rebirth may behave differently now in this area. - SDL2 removed some key-repeat related support. Rebirth may behave differently now in this area. - SDL2 gained the ability to make a window fullscreen by sizing it to the desktop instead of by changing the desktop resolution. Rebirth uses this, and it mostly works. - Resizing while in the automap does not notify the automap code, so the view is wrong until the player switches out of automap mode and back in. - SDL2 changed how to enumerate available resolutions. Since fitting the window to the desktop is generally more useful than fitting the desktop to the window, I chose to drop support for enumerating resolutions instead of porting to the new API. Users can now enter an arbitrary window dimension and Rebirth will make an attempt to use it. - It might be useful to cap the window dimension at the desktop dimension, but that is not done yet. - Entering fullscreen mode through the Controls->Graphics submenu failed to notify the relevant subsystems, causing the rendered content not to rescale. For now, compile out the option to toggle full screen through that menu. Toggling through Alt+Enter works properly. Despite these quirks, this is a substantial improvement over the prior commit, where SDL2 cannot be used at all. The remaining issues can be resolved in future work. References: <https://github.com/dxx-rebirth/dxx-rebirth/issues/82>
2018-07-28 23:22:58 +00:00
#endif
2006-03-20 17:12:09 +00:00
return 1;
}
}
namespace dcx {
namespace {
2006-03-20 17:12:09 +00:00
class game_world_time_paused
{
unsigned time_paused;
public:
explicit operator bool() const
{
return time_paused;
}
void increase_pause_count();
void decrease_pause_count();
};
static game_world_time_paused time_paused;
}
void game_world_time_paused::increase_pause_count()
2006-03-20 17:12:09 +00:00
{
if (time_paused==0) {
const fix64 time = timer_update();
2006-03-20 17:12:09 +00:00
last_timer_value = time - last_timer_value;
if (last_timer_value < 0) {
last_timer_value = 0;
}
}
time_paused++;
2006-03-20 17:12:09 +00:00
}
void game_world_time_paused::decrease_pause_count()
2006-03-20 17:12:09 +00:00
{
Assert(time_paused > 0);
--time_paused;
if (time_paused==0) {
const fix64 time = timer_update();
2006-03-20 17:12:09 +00:00
last_timer_value = time - last_timer_value;
}
}
void start_time()
{
time_paused.decrease_pause_count();
}
void stop_time()
{
time_paused.increase_pause_count();
}
pause_game_world_time::pause_game_world_time()
{
stop_time();
}
pause_game_world_time::~pause_game_world_time()
{
start_time();
}
namespace {
static void game_flush_common_inputs()
2006-03-20 17:12:09 +00:00
{
event_flush();
2006-03-20 17:12:09 +00:00
key_flush();
joy_flush();
mouse_flush();
}
}
}
namespace dsx {
void game_flush_inputs(control_info &Controls)
{
2014-07-04 03:50:50 +00:00
Controls = {};
game_flush_common_inputs();
}
void game_flush_respawn_inputs(control_info &Controls)
{
static_cast<control_info::fire_controls_t &>(Controls.state) = {};
2006-03-20 17:12:09 +00:00
}
}
namespace dcx {
/*
* timer that every sets d_tick_step true and increments d_tick_count every 1000/DESIGNATED_GAME_FPS ms.
*/
void calc_d_tick()
2006-03-20 17:12:09 +00:00
{
static fix timer = 0;
2015-07-18 21:01:57 +00:00
auto t = timer + FrameTime;
2015-07-18 21:01:57 +00:00
d_tick_step = t >= DESIGNATED_GAME_FRAMETIME;
if (d_tick_step)
{
d_tick_count++;
if (d_tick_count > 1000000)
d_tick_count = 0;
2015-07-18 21:01:57 +00:00
t -= DESIGNATED_GAME_FRAMETIME;
}
2015-07-18 21:01:57 +00:00
timer = t;
2006-03-20 17:12:09 +00:00
}
void reset_time()
{
last_timer_value = timer_update();
}
2006-03-20 17:12:09 +00:00
}
2006-03-20 17:12:09 +00:00
void calc_frame_time()
{
fix last_frametime = FrameTime;
2006-03-20 17:12:09 +00:00
2015-07-18 03:49:47 +00:00
const auto vsync = CGameCfg.VSync;
const auto bound = f1_0 / (likely(vsync) ? MAXIMUM_FPS : CGameArg.SysMaxFPS);
2015-12-24 04:01:26 +00:00
const auto may_sleep = !CGameArg.SysNoNiceFPS && !vsync;
2015-07-18 21:01:56 +00:00
for (;;)
2006-03-20 17:12:09 +00:00
{
2015-07-18 21:01:56 +00:00
const auto timer_value = timer_update();
FrameTime = timer_value - last_timer_value;
if (FrameTime > 0 && timer_value - sync_timer_value >= bound)
2015-07-18 21:01:56 +00:00
{
last_timer_value = timer_value;
2018-04-23 02:28:20 +00:00
sync_timer_value += bound;
if (sync_timer_value + bound < timer_value) {
sync_timer_value = timer_value;
2018-04-23 02:28:20 +00:00
}
2015-07-18 21:01:56 +00:00
break;
}
if (Game_mode & GM_MULTI)
multi_do_frame(); // during long wait, keep packets flowing
2015-07-18 03:49:47 +00:00
if (may_sleep)
timer_delay_ms(1);
2006-03-20 17:12:09 +00:00
}
if ( cheats.turbo )
2006-03-20 17:12:09 +00:00
FrameTime *= 2;
if (FrameTime < 0) //if bogus frametime...
FrameTime = (last_frametime==0?1:last_frametime); //...then use time from last frame
GameTime64 += FrameTime;
calc_d_tick();
#ifdef NEWHOMER
calc_d_homer_tick();
#endif
2006-03-20 17:12:09 +00:00
}
namespace dsx {
2018-12-30 00:43:57 +00:00
#if DXX_USE_EDITOR
2022-02-19 14:52:17 +00:00
void move_player_2_segment(const vmsegptridx_t seg, const sidenum_t side)
2006-03-20 17:12:09 +00:00
{
auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
auto &Objects = LevelUniqueObjectState.Objects;
auto &Vertices = LevelSharedVertexState.get_vertices();
auto &vmobjptr = Objects.vmptr;
auto &vmobjptridx = Objects.vmptridx;
const auto &&console = vmobjptridx(ConsoleObject);
2018-12-30 00:43:57 +00:00
auto &vcvertptr = Vertices.vcptr;
compute_segment_center(vcvertptr, console->pos, seg);
auto vp = compute_center_point_on_side(vcvertptr, seg, side);
vm_vec_sub2(vp, console->pos);
vm_vector_2_matrix(console->orient, vp, nullptr, nullptr);
2018-03-12 03:43:46 +00:00
obj_relink(vmobjptr, vmsegptr, console, seg);
2006-03-20 17:12:09 +00:00
}
2018-12-30 00:43:57 +00:00
#endif
2006-03-20 17:12:09 +00:00
}
namespace dcx {
2018-02-18 00:42:42 +00:00
namespace {
#if DXX_USE_SCREENSHOT_FORMAT_PNG
struct RAIIpng_struct
{
png_struct *png_ptr;
png_info *info_ptr = nullptr;
RAIIpng_struct(png_struct *const p) :
png_ptr(p)
{
}
~RAIIpng_struct()
{
png_destroy_write_struct(&png_ptr, &info_ptr);
}
};
struct d_screenshot : RAIIpng_struct
{
using RAIIpng_struct::RAIIpng_struct;
2018-02-18 00:42:42 +00:00
/* error handling callbacks */
[[noreturn]]
static void png_error_cb(png_struct *png, const char *str);
static void png_warn_cb(png_struct *png, const char *str);
/* output callbacks */
static void png_write_cb(png_struct *png, uint8_t *buf, png_size_t);
static void png_flush_cb(png_struct *png);
class png_exception : std::exception
{
};
};
void d_screenshot::png_error_cb(png_struct *const png, const char *const str)
{
/* libpng requires that this function not return to its caller, and
* will abort the program if this requirement is violated. However,
* throwing an exception that unwinds out past libpng is permitted.
*/
(void)png;
con_printf(CON_URGENT, "libpng error: %s", str);
throw png_exception();
}
void d_screenshot::png_warn_cb(png_struct *const png, const char *const str)
{
(void)png;
con_printf(CON_URGENT, "libpng warning: %s", str);
}
void d_screenshot::png_write_cb(png_struct *const png, uint8_t *const buf, const png_size_t size)
{
const auto file = reinterpret_cast<PHYSFS_File *>(png_get_io_ptr(png));
PHYSFS_write(file, buf, size, 1);
}
void d_screenshot::png_flush_cb(png_struct *const png)
{
(void)png;
}
void record_screenshot_time(const struct tm &tm, png_struct *const png_ptr, png_info *const info_ptr)
{
#ifdef PNG_tIME_SUPPORTED
png_time pt{};
pt.year = tm.tm_year + 1900;
pt.month = tm.tm_mon + 1;
pt.day = tm.tm_mday;
pt.hour = tm.tm_hour;
pt.minute = tm.tm_min;
pt.second = tm.tm_sec;
png_set_tIME(png_ptr, info_ptr, &pt);
#else
(void)png_ptr;
(void)info_ptr;
con_printf(CON_NORMAL, "libpng configured without support for time chunk: screenshot will lack time record.");
#endif
}
#ifdef PNG_TEXT_SUPPORTED
void record_screenshot_text_metadata(png_struct *const png_ptr, png_info *const info_ptr)
{
2020-05-02 21:18:42 +00:00
std::array<png_text, 6> text_fields{};
2018-02-18 00:42:42 +00:00
char descent_version[80];
char descent_build_datetime[21];
std::string current_mission_path;
ntstring<MISSION_NAME_LEN> current_mission_name;
char current_level_number[4];
char viewer_segment[sizeof("65536")];
unsigned idx = 0;
char key_descent_version[] = "Rebirth.version";
{
auto &t = text_fields[idx++];
auto &text = descent_version;
t.key = key_descent_version;
text[sizeof(text) - 1] = 0;
strncpy(text, g_descent_version, sizeof(text) - 1);
t.text = text;
t.compression = PNG_TEXT_COMPRESSION_NONE;
}
char key_descent_build_datetime[] = "Rebirth.build_datetime";
{
auto &t = text_fields[idx++];
auto &text = descent_build_datetime;
t.key = key_descent_build_datetime;
text[sizeof(text) - 1] = 0;
strncpy(text, g_descent_build_datetime, sizeof(text) - 1);
t.text = text;
t.compression = PNG_TEXT_COMPRESSION_NONE;
}
char key_current_mission_path[] = "Rebirth.mission.pathname";
char key_current_mission_name[] = "Rebirth.mission.textname";
char key_viewer_segment[] = "Rebirth.viewer_segment";
char key_current_level_number[] = "Rebirth.current_level_number";
if (const auto current_mission = Current_mission.get())
{
{
auto &t = text_fields[idx++];
t.key = key_current_mission_path;
current_mission_path = current_mission->path;
t.text = &current_mission_path[0];
t.compression = PNG_TEXT_COMPRESSION_NONE;
}
{
auto &t = text_fields[idx++];
t.key = key_current_mission_name;
current_mission_name = current_mission->mission_name;
t.text = current_mission_name.data();
t.compression = PNG_TEXT_COMPRESSION_NONE;
}
{
auto &t = text_fields[idx++];
t.key = key_current_level_number;
t.text = current_level_number;
t.compression = PNG_TEXT_COMPRESSION_NONE;
snprintf(current_level_number, sizeof(current_level_number), "%i", Current_level_num);
}
if (const auto viewer = Viewer)
{
auto &t = text_fields[idx++];
t.key = key_viewer_segment;
t.text = viewer_segment;
t.compression = PNG_TEXT_COMPRESSION_NONE;
snprintf(viewer_segment, sizeof(viewer_segment), "%hu", viewer->segnum);
2018-02-18 00:42:42 +00:00
}
}
png_set_text(png_ptr, info_ptr, text_fields.data(), idx);
}
#endif
#if DXX_USE_OGL
#define write_screenshot_png(F,T,B,P) write_screenshot_png(F,T,B)
#endif
unsigned write_screenshot_png(PHYSFS_File *const file, const struct tm *const tm, const grs_bitmap &bitmap, const palette_array_t &pal)
{
const unsigned bm_w = ((bitmap.bm_w + 3) & ~3);
const unsigned bm_h = ((bitmap.bm_h + 3) & ~3);
2018-02-18 00:42:42 +00:00
#if DXX_USE_OGL
const unsigned bufsize = bm_w * bm_h * 3;
const auto buf = std::make_unique<uint8_t[]>(bufsize);
const auto begin_byte_buffer = buf.get();
glReadPixels(0, 0, bm_w, bm_h, GL_RGB, GL_UNSIGNED_BYTE, begin_byte_buffer);
#else
const unsigned bufsize = bitmap.bm_rowsize * bm_h;
const auto begin_byte_buffer = bitmap.bm_mdata;
#endif
d_screenshot ss(png_create_write_struct(PNG_LIBPNG_VER_STRING, &ss, &d_screenshot::png_error_cb, &d_screenshot::png_warn_cb));
if (!ss.png_ptr)
{
con_puts(CON_URGENT, "Cannot save screenshot: libpng png_create_write_struct failed");
return 1;
}
/* Assert that Rebirth type rgb_t is layout compatible with
* libpng type png_color, so that the Rebirth palette_array_t
* can be safely reinterpret_cast to an array of png_color.
* Without this, it would be necessary to copy each rgb_t
* palette entry into a libpng png_color.
*/
static_assert(sizeof(png_color) == sizeof(rgb_t), "size mismatch");
static_assert(offsetof(png_color, red) == offsetof(rgb_t, r), "red offsetof mismatch");
static_assert(offsetof(png_color, green) == offsetof(rgb_t, g), "green offsetof mismatch");
static_assert(offsetof(png_color, blue) == offsetof(rgb_t, b), "blue offsetof mismatch");
try {
ss.info_ptr = png_create_info_struct(ss.png_ptr);
if (tm)
record_screenshot_time(*tm, ss.png_ptr, ss.info_ptr);
png_set_write_fn(ss.png_ptr, file, &d_screenshot::png_write_cb, &d_screenshot::png_flush_cb);
#if DXX_USE_OGL
const auto color_type = PNG_COLOR_TYPE_RGB;
#else
png_set_PLTE(ss.png_ptr, ss.info_ptr, reinterpret_cast<const png_color *>(pal.data()), pal.size());
const auto color_type = PNG_COLOR_TYPE_PALETTE;
#endif
png_set_IHDR(ss.png_ptr, ss.info_ptr, bm_w, bm_h, 8 /* always 256 colors */, color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
#ifdef PNG_TEXT_SUPPORTED
record_screenshot_text_metadata(ss.png_ptr, ss.info_ptr);
#endif
png_write_info(ss.png_ptr, ss.info_ptr);
2020-05-02 21:18:42 +00:00
std::array<png_byte *, 1024> row_pointers;
2018-02-18 00:42:42 +00:00
const auto rpb = row_pointers.begin();
auto o = rpb;
const auto end_byte_buffer = begin_byte_buffer + bufsize;
#if DXX_USE_OGL
/* OpenGL glReadPixels returns an image with origin in bottom
* left. Write rows from end back to beginning, since PNG
* expects origin in top left. If rows were written in memory
* order, the image would be vertically flipped.
*/
const uint_fast32_t stride = bm_w * 3; /* Without palette, written data is 3-byte-sized RGB tuples of color */
for (auto p = end_byte_buffer; p != begin_byte_buffer;)
#else
const uint_fast32_t stride = bm_w; /* With palette, written data is byte-sized indices into a color table */
/* SDL canvas uses an image with origin in top left. Write rows
* in memory order, since this matches the PNG layout.
*/
for (auto p = begin_byte_buffer; p != end_byte_buffer;)
#endif
{
#if DXX_USE_OGL
p -= stride;
#else
p += stride;
#endif
*o++ = p;
if (o == row_pointers.end())
{
/* Internal capacity exhausted. Flush rows and rewind
* to the beginning of the array.
*/
o = rpb;
png_write_rows(ss.png_ptr, o, row_pointers.size());
}
}
/* Flush any trailing rows */
if (const auto len = o - rpb)
png_write_rows(ss.png_ptr, rpb, len);
png_write_end(ss.png_ptr, ss.info_ptr);
return 0;
} catch (const d_screenshot::png_exception &) {
/* Destructor unwind will handle the exception. This catch is
* only required to prevent further propagation.
*
* Return nonzero to instruct the caller to delete the failed
* file.
*/
return 1;
}
}
#endif
}
#if DXX_USE_SCREENSHOT
2006-03-20 17:12:09 +00:00
void save_screen_shot(int automap_flag)
{
#if DXX_USE_OGL
if (!CGameArg.DbgGlReadPixelsOk)
{
if (!automap_flag)
HUD_init_message_literal(HM_DEFAULT, "glReadPixels not supported on your configuration");
return;
}
2018-02-18 00:42:42 +00:00
#endif
#if DXX_USE_SCREENSHOT_FORMAT_PNG
#define DXX_SCREENSHOT_FILE_EXTENSION "png"
#elif DXX_USE_SCREENSHOT_FORMAT_LEGACY
#if DXX_USE_OGL
#define DXX_SCREENSHOT_FILE_EXTENSION "tga"
#else
#define DXX_SCREENSHOT_FILE_EXTENSION "pcx"
2018-02-18 00:42:42 +00:00
#endif
#endif
2006-03-20 17:12:09 +00:00
if (!PHYSFSX_exists(SCRNS_DIR,0))
2007-01-25 10:30:33 +00:00
PHYSFS_mkdir(SCRNS_DIR); //try making directory
pause_game_world_time p;
2018-02-18 00:42:42 +00:00
unsigned tm_sec;
unsigned tm_min;
unsigned tm_hour;
unsigned tm_mday;
unsigned tm_mon;
unsigned tm_year;
const auto t = time(nullptr);
struct tm *tm = nullptr;
if (t == static_cast<time_t>(-1) || !(tm = gmtime(&t)))
tm_year = tm_mon = tm_mday = tm_hour = tm_min = tm_sec = 0;
else
{
2018-02-18 00:42:42 +00:00
tm_sec = tm->tm_sec;
tm_min = tm->tm_min;
tm_hour = tm->tm_hour;
tm_mday = tm->tm_mday;
tm_mon = tm->tm_mon + 1;
tm_year = tm->tm_year + 1900;
}
/* Colon is not legal in Windows filenames, so use - to separate
* hour:minute:second.
*/
#define DXX_SCREENSHOT_TIME_FORMAT_STRING SCRNS_DIR "%04u-%02u-%02u.%02u-%02u-%02u"
#define DXX_SCREENSHOT_TIME_FORMAT_VALUES tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec
/* Reserve extra space for the trailing -NN disambiguation. This
* is only used if multiple screenshots get the same time, so it is
* unlikely to be seen in practice and very unlikely to be exhausted
* (unless the clock is frozen or returns invalid times).
*/
char savename[sizeof(SCRNS_DIR) + sizeof("2000-01-01.00-00-00.NN.ext")];
snprintf(savename, sizeof(savename), DXX_SCREENSHOT_TIME_FORMAT_STRING "." DXX_SCREENSHOT_FILE_EXTENSION, DXX_SCREENSHOT_TIME_FORMAT_VALUES);
for (unsigned savenum = 0; PHYSFS_exists(savename) && savenum != 100; ++savenum)
{
snprintf(savename, sizeof(savename), DXX_SCREENSHOT_TIME_FORMAT_STRING ".%02u." DXX_SCREENSHOT_FILE_EXTENSION, DXX_SCREENSHOT_TIME_FORMAT_VALUES, savenum);
#undef DXX_SCREENSHOT_TIME_FORMAT_VALUES
#undef DXX_SCREENSHOT_TIME_FORMAT_STRING
#undef DXX_SCREENSHOT_FILE_EXTENSION
2018-02-18 00:42:42 +00:00
}
if (const auto &&[file, physfserr] = PHYSFSX_openWriteBuffered(savename); file)
2018-02-18 00:42:42 +00:00
{
if (!automap_flag)
HUD_init_message(HM_DEFAULT, "%s '%s'", TXT_DUMPING_SCREEN, &savename[sizeof(SCRNS_DIR) - 1]);
2018-02-18 00:42:42 +00:00
#if DXX_USE_OGL
#if !DXX_USE_OGLES
glReadBuffer(GL_FRONT);
#endif
2018-02-18 00:42:42 +00:00
#if DXX_USE_SCREENSHOT_FORMAT_PNG
auto write_error = write_screenshot_png(file, tm, grd_curscreen->sc_canvas.cv_bitmap, void /* unused */);
2018-02-18 00:42:42 +00:00
#elif DXX_USE_SCREENSHOT_FORMAT_LEGACY
write_bmp(file, grd_curscreen->get_screen_width(), grd_curscreen->get_screen_height());
/* write_bmp never fails */
std::false_type write_error;
2018-02-18 00:42:42 +00:00
#endif
#else
grs_canvas &screen_canv = grd_curscreen->sc_canvas;
palette_array_t pal;
const auto &&temp_canv = gr_create_canvas(screen_canv.cv_bitmap.bm_w, screen_canv.cv_bitmap.bm_h);
gr_ubitmap(*temp_canv, screen_canv.cv_bitmap);
2006-03-20 17:12:09 +00:00
gr_palette_read(pal); //get actual palette from the hardware
2018-02-18 00:42:42 +00:00
// Correct palette colors
range_for (auto &i, pal)
{
i.r <<= 2;
i.g <<= 2;
i.b <<= 2;
}
#if DXX_USE_SCREENSHOT_FORMAT_PNG
auto write_error = write_screenshot_png(file, tm, grd_curscreen->sc_canvas.cv_bitmap, pal);
2018-02-18 00:42:42 +00:00
#elif DXX_USE_SCREENSHOT_FORMAT_LEGACY
auto write_error = pcx_write_bitmap(file, &temp_canv->cv_bitmap, pal);
2006-03-20 17:12:09 +00:00
#endif
2018-02-18 00:42:42 +00:00
#endif
if (write_error)
PHYSFS_delete(savename);
2018-02-18 00:42:42 +00:00
}
else
{
const auto e = PHYSFS_getErrorByCode(physfserr);
2018-02-18 00:42:42 +00:00
if (!automap_flag)
HUD_init_message(HM_DEFAULT, "Failed to open screenshot file for writing: %s: %s", &savename[sizeof(SCRNS_DIR) - 1], e);
2018-02-18 00:42:42 +00:00
else
con_printf(CON_URGENT, "Failed to open screenshot file for writing: %s: %s", savename, e);
2018-02-18 00:42:42 +00:00
return;
}
}
#endif
2006-03-20 17:12:09 +00:00
//initialize flying
2016-09-11 18:49:13 +00:00
void fly_init(object_base &obj)
2006-03-20 17:12:09 +00:00
{
obj.control_source = object::control_type::flying;
obj.movement_source = object::movement_type::physics;
2016-09-11 18:49:13 +00:00
obj.mtype.phys_info.velocity = {};
obj.mtype.phys_info.thrust = {};
obj.mtype.phys_info.rotvel = {};
obj.mtype.phys_info.rotthrust = {};
}
2006-03-20 17:12:09 +00:00
}
2016-09-11 18:49:13 +00:00
namespace dsx {
namespace {
2006-03-20 17:12:09 +00:00
// ------------------------------------------------------------------------------------
static void do_cloak_stuff()
2006-03-20 17:12:09 +00:00
{
auto &Objects = LevelUniqueObjectState.Objects;
auto &vmobjptr = Objects.vmptr;
for (auto &&[i, value] : enumerate(partial_range(Players, N_players)))
{
auto &plobj = *vmobjptr(value.objnum);
auto &player_info = plobj.ctype.player_info;
auto &pl_flags = player_info.powerup_flags;
if (pl_flags & PLAYER_FLAGS_CLOAKED)
{
if (GameTime64 > player_info.cloak_time+CLOAK_TIME_MAX)
{
pl_flags &= ~PLAYER_FLAGS_CLOAKED;
2006-03-20 17:12:09 +00:00
if (i == Player_num) {
2015-08-05 02:59:02 +00:00
multi_digi_play_sample(SOUND_CLOAK_OFF, F1_0);
maybe_drop_net_powerup(POW_CLOAK, 1, 0);
if ( Newdemo_state != ND_STATE_PLAYBACK )
2007-02-28 20:43:10 +00:00
multi_send_decloak(); // For demo recording
2006-03-20 17:12:09 +00:00
}
}
}
}
2006-03-20 17:12:09 +00:00
}
// ------------------------------------------------------------------------------------
static void do_invulnerable_stuff(player_info &player_info)
2006-03-20 17:12:09 +00:00
{
auto &pl_flags = player_info.powerup_flags;
if (pl_flags & PLAYER_FLAGS_INVULNERABLE)
2015-09-26 21:17:13 +00:00
{
if (GameTime64 > player_info.invulnerable_time + INVULNERABLE_TIME_MAX)
{
pl_flags &= ~PLAYER_FLAGS_INVULNERABLE;
if (auto &FakingInvul = player_info.FakingInvul)
2006-03-20 17:12:09 +00:00
{
2015-09-26 21:17:13 +00:00
FakingInvul = 0;
return;
}
2015-08-05 02:59:02 +00:00
multi_digi_play_sample(SOUND_INVULNERABILITY_OFF, F1_0);
2006-03-20 17:12:09 +00:00
if (Game_mode & GM_MULTI)
{
maybe_drop_net_powerup(POW_INVULNERABILITY, 1, 0);
2006-03-20 17:12:09 +00:00
}
}
}
}
}
#if defined(DXX_BUILD_DESCENT_I)
static inline void do_afterburner_stuff(object_array &)
{
}
#elif defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
ubyte Last_afterburner_state = 0;
fix64 Time_flash_last_played;
2023-01-07 22:17:31 +00:00
#define AFTERBURNER_LOOP_START ((GameArg.SndDigiSampleRate == sound_sample_rate::_22k)?32027:(32027/2)) //20098
#define AFTERBURNER_LOOP_END ((GameArg.SndDigiSampleRate == sound_sample_rate::_22k)?48452:(48452/2)) //25776
2006-03-20 17:12:09 +00:00
namespace {
static fix Last_afterburner_charge;
static void do_afterburner_stuff(object_array &Objects)
2006-03-20 17:12:09 +00:00
{
auto &vmobjptr = Objects.vmptr;
auto &vcobjptridx = Objects.vcptridx;
static sbyte func_play = 0;
auto &player_info = get_local_plrobj().ctype.player_info;
const auto have_afterburner = player_info.powerup_flags & PLAYER_FLAGS_AFTERBURNER;
2016-07-03 00:54:15 +00:00
if (!have_afterburner)
Afterburner_charge = 0;
2006-03-20 17:12:09 +00:00
const auto plobj = vcobjptridx(get_local_player().objnum);
if (Endlevel_sequence || Player_dead_state != player_dead_state::no)
{
2014-12-13 17:47:06 +00:00
digi_kill_sound_linked_to_object(plobj);
if (Game_mode & GM_MULTI && func_play)
{
multi_send_sound_function (0,0);
func_play = 0;
2006-03-20 17:12:09 +00:00
}
}
2006-03-20 17:12:09 +00:00
if ((Controls.state.afterburner != Last_afterburner_state && Last_afterburner_charge) || (Last_afterburner_state && Last_afterburner_charge && !Afterburner_charge)) {
2016-07-03 00:54:15 +00:00
if (Afterburner_charge && Controls.state.afterburner && have_afterburner) {
digi_link_sound_to_object3(SOUND_AFTERBURNER_IGNITE, plobj, 1, F1_0, sound_stack::allow_stacking, vm_distance{i2f(256)}, AFTERBURNER_LOOP_START, AFTERBURNER_LOOP_END);
2006-03-20 17:12:09 +00:00
if (Game_mode & GM_MULTI)
{
2006-03-20 17:12:09 +00:00
multi_send_sound_function (3,SOUND_AFTERBURNER_IGNITE);
func_play = 1;
}
2006-03-20 17:12:09 +00:00
} else {
2014-12-13 17:47:06 +00:00
digi_kill_sound_linked_to_object(plobj);
digi_link_sound_to_object2(SOUND_AFTERBURNER_PLAY, plobj, 0, F1_0, sound_stack::allow_stacking, vm_distance{i2f(256)});
2006-03-20 17:12:09 +00:00
if (Game_mode & GM_MULTI)
{
2006-03-20 17:12:09 +00:00
multi_send_sound_function (0,0);
func_play = 0;
}
2006-03-20 17:12:09 +00:00
}
}
//@@if (Controls.state.afterburner && Afterburner_charge)
2006-03-20 17:12:09 +00:00
//@@ afterburner_shake();
Last_afterburner_state = Controls.state.afterburner;
2006-03-20 17:12:09 +00:00
Last_afterburner_charge = Afterburner_charge;
}
}
#endif
2006-03-20 17:12:09 +00:00
// Amount to diminish guns towards normal, per second.
#define DIMINISH_RATE 16 // gots to be a power of 2, else change the code in diminish_palette_towards_normal
2006-03-20 17:12:09 +00:00
//adds to rgb values for palette flash
void PALETTE_FLASH_ADD(int _dr, int _dg, int _db)
2006-03-20 17:12:09 +00:00
{
int maxval;
PaletteRedAdd += _dr;
PaletteGreenAdd += _dg;
PaletteBlueAdd += _db;
#if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
if (Flash_effect)
maxval = 60;
else
#endif
2006-03-20 17:12:09 +00:00
maxval = MAX_PALETTE_ADD;
if (PaletteRedAdd > maxval)
PaletteRedAdd = maxval;
if (PaletteGreenAdd > maxval)
PaletteGreenAdd = maxval;
if (PaletteBlueAdd > maxval)
PaletteBlueAdd = maxval;
if (PaletteRedAdd < -maxval)
PaletteRedAdd = -maxval;
if (PaletteGreenAdd < -maxval)
PaletteGreenAdd = -maxval;
if (PaletteBlueAdd < -maxval)
PaletteBlueAdd = -maxval;
}
}
namespace {
static void diminish_palette_color_toward_zero(int& palette_color_add, const int& dec_amount)
{
if (palette_color_add > 0 ) {
if (palette_color_add < dec_amount)
palette_color_add = 0;
else
palette_color_add -= dec_amount;
} else if (palette_color_add < 0 ) {
if (palette_color_add > -dec_amount )
palette_color_add = 0;
else
palette_color_add += dec_amount;
}
}
}
2015-12-13 18:00:49 +00:00
namespace dsx {
namespace {
2006-03-20 17:12:09 +00:00
// ------------------------------------------------------------------------------------
// Diminish palette effects towards normal.
2013-10-27 22:00:14 +00:00
static void diminish_palette_towards_normal(void)
2006-03-20 17:12:09 +00:00
{
int dec_amount = 0;
float brightness_correction = 1-(static_cast<float>(gr_palette_get_gamma())/64); // to compensate for brightness setting of the game
2006-03-20 17:12:09 +00:00
// Diminish at DIMINISH_RATE units/second.
if (FrameTime < (F1_0/DIMINISH_RATE))
{
static fix diminish_timer = 0;
diminish_timer += FrameTime;
if (diminish_timer >= (F1_0/DIMINISH_RATE))
{
diminish_timer -= (F1_0/DIMINISH_RATE);
2006-03-20 17:12:09 +00:00
dec_amount = 1;
}
}
else
{
dec_amount = f2i(FrameTime*DIMINISH_RATE); // one second = DIMINISH_RATE counts
2006-03-20 17:12:09 +00:00
if (dec_amount == 0)
dec_amount++; // make sure we decrement by something
2006-03-20 17:12:09 +00:00
}
#if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
if (Flash_effect) {
int force_do = 0;
static fix Flash_step_up_timer = 0;
2006-03-20 17:12:09 +00:00
// Part of hack system to force update of palette after exiting a menu.
2006-03-20 17:12:09 +00:00
if (Time_flash_last_played) {
force_do = 1;
PaletteRedAdd ^= 1; // Very Tricky! In gr_palette_step_up, if all stepups same as last time, won't do anything!
2006-03-20 17:12:09 +00:00
}
if (Time_flash_last_played + F1_0/8 < GameTime64) {
2006-03-20 17:12:09 +00:00
digi_play_sample( SOUND_CLOAK_OFF, Flash_effect/4);
Time_flash_last_played = GameTime64;
2006-03-20 17:12:09 +00:00
}
Flash_effect -= FrameTime;
Flash_step_up_timer += FrameTime;
2006-03-20 17:12:09 +00:00
if (Flash_effect < 0)
Flash_effect = 0;
if (force_do || (Flash_step_up_timer >= F1_0/26)) // originally time interval based on (d_rand() > 4096)
{
Flash_step_up_timer -= (F1_0/26);
if ( (Newdemo_state==ND_STATE_RECORDING) && (PaletteRedAdd || PaletteGreenAdd || PaletteBlueAdd) )
newdemo_record_palette_effect(PaletteRedAdd, PaletteGreenAdd, PaletteBlueAdd);
2006-03-20 17:12:09 +00:00
gr_palette_step_up( PaletteRedAdd*brightness_correction, PaletteGreenAdd*brightness_correction, PaletteBlueAdd*brightness_correction );
2006-03-20 17:12:09 +00:00
return;
}
}
#endif
2006-03-20 17:12:09 +00:00
diminish_palette_color_toward_zero(PaletteRedAdd, dec_amount);
diminish_palette_color_toward_zero(PaletteGreenAdd, dec_amount);
diminish_palette_color_toward_zero(PaletteBlueAdd, dec_amount);
2006-03-20 17:12:09 +00:00
if ( (Newdemo_state==ND_STATE_RECORDING) && (PaletteRedAdd || PaletteGreenAdd || PaletteBlueAdd) )
newdemo_record_palette_effect(PaletteRedAdd, PaletteGreenAdd, PaletteBlueAdd);
gr_palette_step_up( PaletteRedAdd*brightness_correction, PaletteGreenAdd*brightness_correction, PaletteBlueAdd*brightness_correction );
2006-03-20 17:12:09 +00:00
}
}
}
2015-12-04 03:36:31 +00:00
namespace {
2006-03-20 17:12:09 +00:00
int Redsave, Bluesave, Greensave;
2015-12-04 03:36:31 +00:00
}
#if defined(DXX_BUILD_DESCENT_II)
static
#endif
2006-03-20 17:12:09 +00:00
void palette_save(void)
{
Redsave = PaletteRedAdd; Bluesave = PaletteBlueAdd; Greensave = PaletteGreenAdd;
}
namespace dsx {
2006-03-20 17:12:09 +00:00
void palette_restore(void)
{
float brightness_correction = 1-(static_cast<float>(gr_palette_get_gamma())/64);
2006-03-20 17:12:09 +00:00
PaletteRedAdd = Redsave; PaletteBlueAdd = Bluesave; PaletteGreenAdd = Greensave;
gr_palette_step_up( PaletteRedAdd*brightness_correction, PaletteGreenAdd*brightness_correction, PaletteBlueAdd*brightness_correction );
2006-03-20 17:12:09 +00:00
#if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
// Forces flash effect to fixup palette next frame.
Time_flash_last_played = 0;
#endif
2006-03-20 17:12:09 +00:00
}
// --------------------------------------------------------------------------------------------------
bool allowed_to_fire_laser(const player_info &player_info)
2006-03-20 17:12:09 +00:00
{
if (Player_dead_state != player_dead_state::no)
{
2006-03-20 17:12:09 +00:00
Global_missile_firing_count = 0;
return 0;
}
auto &Next_laser_fire_time = player_info.Next_laser_fire_time;
// Make sure enough time has elapsed to fire laser
if (Next_laser_fire_time > GameTime64)
return 0;
2006-03-20 17:12:09 +00:00
return 1;
}
int allowed_to_fire_missile(const player_info &player_info)
2006-03-20 17:12:09 +00:00
{
auto &Next_missile_fire_time = player_info.Next_missile_fire_time;
// Make sure enough time has elapsed to fire missile
if (Next_missile_fire_time > GameTime64)
return 0;
2006-03-20 17:12:09 +00:00
return 1;
}
2013-10-28 03:41:44 +00:00
#if defined(DXX_BUILD_DESCENT_II)
2006-03-20 17:12:09 +00:00
void full_palette_save(void)
{
palette_save();
reset_palette_add();
gr_palette_load( gr_palette );
}
2013-10-28 03:41:44 +00:00
#endif
2006-03-20 17:12:09 +00:00
2020-09-11 03:08:02 +00:00
namespace {
#if DXX_USE_SDLMIXER
#define EXT_MUSIC_TEXT "Jukebox/Audio CD"
#else
#define EXT_MUSIC_TEXT "Audio CD"
#endif
2015-01-18 01:58:32 +00:00
#if (defined(__APPLE__) || defined(macintosh))
#define _DXX_HELP_MENU_SAVE_LOAD(VERB) \
DXX_MENUITEM(VERB, TEXT, "Alt-F2/F3 (\x85-SHIFT-s/o)\t SAVE/LOAD GAME", HELP_AF2_3) \
DXX_MENUITEM(VERB, TEXT, "Alt-Shift-F2/F3 (\x85-s/o)\t Quick Save/Load", HELP_ASF2_3)
#define _DXX_HELP_MENU_PAUSE(VERB) DXX_MENUITEM(VERB, TEXT, "Pause (\x85-P)\t Pause", HELP_PAUSE)
#if DXX_USE_SDL_REDBOOK_AUDIO
#define _DXX_HELP_MENU_AUDIO_REDBOOK(VERB) \
DXX_MENUITEM(VERB, TEXT, "\x85-E\t Eject Audio CD", HELP_ASF9) \
#else
#define _DXX_HELP_MENU_AUDIO_REDBOOK(VERB)
#endif
#define _DXX_HELP_MENU_AUDIO(VERB) \
_DXX_HELP_MENU_AUDIO_REDBOOK(VERB) \
DXX_MENUITEM(VERB, TEXT, "\x85-Up/Down\t Play/Pause " EXT_MUSIC_TEXT, HELP_ASF10) \
DXX_MENUITEM(VERB, TEXT, "\x85-Left/Right\t Previous/Next Song", HELP_ASF11_12)
2015-01-18 01:58:32 +00:00
#define _DXX_HELP_MENU_HINT_CMD_KEY(VERB, PREFIX) \
DXX_MENUITEM(VERB, TEXT, "", PREFIX##_SEP_HINT_CMD) \
DXX_MENUITEM(VERB, TEXT, "(Use \x85-# for F#. e.g. \x85-1 for F1)", PREFIX##_HINT_CMD)
2015-01-18 01:58:32 +00:00
#define _DXX_NETHELP_SAVELOAD_GAME(VERB) \
DXX_MENUITEM(VERB, TEXT, "Alt-F2/F3 (\x85-SHIFT-s/\x85-o)\t SAVE/LOAD COOP GAME", NETHELP_SAVELOAD)
2015-01-18 01:58:32 +00:00
#else
#define _DXX_HELP_MENU_SAVE_LOAD(VERB) \
DXX_MENUITEM(VERB, TEXT, "Alt-F2/F3\t SAVE/LOAD GAME", HELP_AF2_3) \
DXX_MENUITEM(VERB, TEXT, "Alt-Shift-F2/F3\t Fast Save", HELP_ASF2_3)
#define _DXX_HELP_MENU_PAUSE(VERB) DXX_MENUITEM(VERB, TEXT, TXT_HELP_PAUSE, HELP_PAUSE)
#if DXX_USE_SDL_REDBOOK_AUDIO
#define _DXX_HELP_MENU_AUDIO_REDBOOK(VERB) \
DXX_MENUITEM(VERB, TEXT, "Alt-Shift-F9\t Eject Audio CD", HELP_ASF9) \
#else
#define _DXX_HELP_MENU_AUDIO_REDBOOK(VERB)
#endif
#define _DXX_HELP_MENU_AUDIO(VERB) \
_DXX_HELP_MENU_AUDIO_REDBOOK(VERB) \
DXX_MENUITEM(VERB, TEXT, "Alt-Shift-F10\t Play/Pause " EXT_MUSIC_TEXT, HELP_ASF10) \
DXX_MENUITEM(VERB, TEXT, "Alt-Shift-F11/F12\t Previous/Next Song", HELP_ASF11_12)
2015-01-18 01:58:32 +00:00
#define _DXX_HELP_MENU_HINT_CMD_KEY(VERB, PREFIX)
2015-01-18 01:58:32 +00:00
#define _DXX_NETHELP_SAVELOAD_GAME(VERB) \
DXX_MENUITEM(VERB, TEXT, "Alt-F2/F3\t SAVE/LOAD COOP GAME", NETHELP_SAVELOAD)
2015-01-18 01:58:32 +00:00
#endif
#if defined(DXX_BUILD_DESCENT_II)
#define _DXX_HELP_MENU_D2_DXX_F4(VERB) DXX_MENUITEM(VERB, TEXT, TXT_HELP_F4, HELP_F4)
2015-01-18 01:58:32 +00:00
#define _DXX_HELP_MENU_D2_DXX_FEATURES(VERB) \
DXX_MENUITEM(VERB, TEXT, "Shift-F1/F2\t Cycle left/right window", HELP_SF1_2) \
DXX_MENUITEM(VERB, TEXT, "Shift-F4\t GuideBot menu", HELP_SF4) \
DXX_MENUITEM(VERB, TEXT, "Alt-Shift-F4\t Rename GuideBot", HELP_ASF4) \
DXX_MENUITEM(VERB, TEXT, "Shift-F5/F6\t Drop primary/secondary", HELP_SF5_6) \
DXX_MENUITEM(VERB, TEXT, "Shift-number\t GuideBot commands", HELP_GUIDEBOT_COMMANDS)
2020-09-11 03:08:02 +00:00
#define DSX_NETHELP_DROPFLAG(VERB) \
DXX_MENUITEM(VERB, TEXT, "ALT-0\t DROP FLAG", NETHELP_DROPFLAG)
2015-01-18 01:58:32 +00:00
#else
#define _DXX_HELP_MENU_D2_DXX_F4(VERB)
#define _DXX_HELP_MENU_D2_DXX_FEATURES(VERB)
2020-09-11 03:08:02 +00:00
#define DSX_NETHELP_DROPFLAG(VERB)
2015-01-18 01:58:32 +00:00
#endif
#define DXX_HELP_MENU(VERB) \
DXX_MENUITEM(VERB, TEXT, TXT_HELP_ESC, HELP_ESC) \
DXX_MENUITEM(VERB, TEXT, "SHIFT-ESC\t SHOW GAME LOG", HELP_LOG) \
DXX_MENUITEM(VERB, TEXT, "F1\t THIS SCREEN", HELP_HELP) \
DXX_MENUITEM(VERB, TEXT, TXT_HELP_F2, HELP_F2) \
2015-01-18 01:58:32 +00:00
_DXX_HELP_MENU_SAVE_LOAD(VERB) \
DXX_MENUITEM(VERB, TEXT, "F3\t SWITCH COCKPIT MODES", HELP_F3) \
2015-01-18 01:58:32 +00:00
_DXX_HELP_MENU_D2_DXX_F4(VERB) \
DXX_MENUITEM(VERB, TEXT, TXT_HELP_F5, HELP_F5) \
DXX_MENUITEM(VERB, TEXT, "ALT-F7\t SWITCH HUD MODES", HELP_AF7) \
2015-01-18 01:58:32 +00:00
_DXX_HELP_MENU_PAUSE(VERB) \
DXX_MENUITEM(VERB, TEXT, TXT_HELP_PRTSCN, HELP_PRTSCN) \
DXX_MENUITEM(VERB, TEXT, TXT_HELP_1TO5, HELP_1TO5) \
DXX_MENUITEM(VERB, TEXT, TXT_HELP_6TO10, HELP_6TO10) \
2015-01-18 01:58:32 +00:00
_DXX_HELP_MENU_D2_DXX_FEATURES(VERB) \
_DXX_HELP_MENU_AUDIO(VERB) \
_DXX_HELP_MENU_HINT_CMD_KEY(VERB, HELP) \
2020-09-11 03:08:02 +00:00
}
2006-03-20 17:12:09 +00:00
void show_help()
{
struct help_menu_items
{
enum {
DXX_HELP_MENU(ENUM)
};
std::array<newmenu_item, DXX_HELP_MENU(COUNT)> m;
help_menu_items()
{
DXX_HELP_MENU(ADD);
}
};
struct help_menu : help_menu_items, passive_newmenu
{
help_menu(grs_canvas &src) :
passive_newmenu(menu_title{nullptr}, menu_subtitle{TXT_KEYS}, menu_filename{nullptr}, tiny_mode_flag::tiny, tab_processing_flag::ignore, adjusted_citem::create(m, 0), src)
{
}
};
auto menu = window_create<help_menu>(grd_curscreen->sc_canvas);
(void)menu;
}
2015-01-18 01:58:32 +00:00
#undef DXX_HELP_MENU
2020-09-11 03:08:02 +00:00
#define DSX_NETHELP_MENU(VERB) \
DXX_MENUITEM(VERB, TEXT, "F1\t THIS SCREEN", NETHELP_HELP) \
2020-09-11 03:08:02 +00:00
DSX_NETHELP_DROPFLAG(VERB) \
2015-01-18 01:58:32 +00:00
_DXX_NETHELP_SAVELOAD_GAME(VERB) \
DXX_MENUITEM(VERB, TEXT, "ALT-F4\t SHOW PLAYER NAMES ON HUD", NETHELP_HUDNAMES) \
DXX_MENUITEM(VERB, TEXT, "F7\t TOGGLE KILL LIST", NETHELP_TOGGLE_KILL_LIST) \
DXX_MENUITEM(VERB, TEXT, "F8\t SEND MESSAGE", NETHELP_SENDMSG) \
DXX_MENUITEM(VERB, TEXT, "(SHIFT-)F9 to F12\t (DEFINE)SEND MACRO", NETHELP_MACRO) \
DXX_MENUITEM(VERB, TEXT, "PAUSE\t SHOW NETGAME INFORMATION", NETHELP_GAME_INFO) \
DXX_MENUITEM(VERB, TEXT, "SHIFT-PAUSE\t SHOW NETGAME INFO & RULES", NETHELP_GAME_INFORULES) \
2015-01-18 01:58:32 +00:00
_DXX_HELP_MENU_HINT_CMD_KEY(VERB, NETHELP) \
DXX_MENUITEM(VERB, TEXT, "", NETHELP_SEP1) \
DXX_MENUITEM(VERB, TEXT, "MULTIPLAYER MESSAGE COMMANDS:", NETHELP_COMMAND_HEADER) \
DXX_MENUITEM(VERB, TEXT, "(*): TEXT\t SEND TEXT TO PLAYER/TEAM (*)", NETHELP_DIRECT_MESSAGE) \
DXX_MENUITEM(VERB, TEXT, "/Handicap: (*)\t SET YOUR STARTING SHIELDS TO (*) [10-100]", NETHELP_COMMAND_HANDICAP) \
DXX_MENUITEM(VERB, TEXT, "/move: (*)\t MOVE PLAYER (*) TO OTHER TEAM (Host-only)", NETHELP_COMMAND_MOVE) \
DXX_MENUITEM(VERB, TEXT, "/kick: (*)\t KICK PLAYER (*) FROM GAME (Host-only)", NETHELP_COMMAND_KICK) \
DXX_MENUITEM(VERB, TEXT, "/KillReactor\t BLOW UP THE MINE (Host-only)", NETHELP_COMMAND_KILL_REACTOR) \
2015-01-18 01:58:32 +00:00
enum {
2020-09-11 03:08:02 +00:00
DSX_NETHELP_MENU(ENUM)
2015-01-18 01:58:32 +00:00
};
void show_netgame_help()
{
struct help_menu_items
{
enum {
DSX_NETHELP_MENU(ENUM)
};
std::array<newmenu_item, DSX_NETHELP_MENU(COUNT)> m;
help_menu_items()
{
DSX_NETHELP_MENU(ADD);
}
};
struct help_menu : help_menu_items, passive_newmenu
{
help_menu(grs_canvas &src) :
passive_newmenu(menu_title{nullptr}, menu_subtitle{TXT_KEYS}, menu_filename{nullptr}, tiny_mode_flag::tiny, tab_processing_flag::ignore, adjusted_citem::create(m, 0), src)
{
}
};
auto menu = window_create<help_menu>(grd_curscreen->sc_canvas);
(void)menu;
2006-03-20 17:12:09 +00:00
}
2020-09-11 03:08:02 +00:00
#undef DSX_NETHELP_MENU
2015-01-18 01:58:32 +00:00
2015-01-18 01:58:32 +00:00
#define DXX_NEWDEMO_HELP_MENU(VERB) \
DXX_MENUITEM(VERB, TEXT, "ESC\t QUIT DEMO PLAYBACK", DEMOHELP_QUIT) \
DXX_MENUITEM(VERB, TEXT, "F1\t THIS SCREEN", DEMOHELP_HELP) \
DXX_MENUITEM(VERB, TEXT, TXT_HELP_F2, DEMOHELP_F2) \
DXX_MENUITEM(VERB, TEXT, "F3\t SWITCH COCKPIT MODES", DEMOHELP_F3) \
DXX_MENUITEM(VERB, TEXT, "F4\t TOGGLE PERCENTAGE DISPLAY", DEMOHELP_F4) \
DXX_MENUITEM(VERB, TEXT, "UP\t PLAY", DEMOHELP_PLAY) \
DXX_MENUITEM(VERB, TEXT, "DOWN\t PAUSE", DEMOHELP_PAUSE) \
DXX_MENUITEM(VERB, TEXT, "RIGHT\t ONE FRAME FORWARD", DEMOHELP_FRAME_FORWARD) \
DXX_MENUITEM(VERB, TEXT, "LEFT\t ONE FRAME BACKWARD", DEMOHELP_FRAME_BACKWARD) \
DXX_MENUITEM(VERB, TEXT, "SHIFT-RIGHT\t FAST FORWARD", DEMOHELP_FAST_FORWARD) \
DXX_MENUITEM(VERB, TEXT, "SHIFT-LEFT\t FAST BACKWARD", DEMOHELP_FAST_BACKWARD) \
DXX_MENUITEM(VERB, TEXT, "CTRL-RIGHT\t JUMP TO END", DEMOHELP_JUMP_END) \
DXX_MENUITEM(VERB, TEXT, "CTRL-LEFT\t JUMP TO START", DEMOHELP_JUMP_START) \
2015-01-18 01:58:32 +00:00
_DXX_HELP_MENU_HINT_CMD_KEY(VERB, DEMOHELP) \
enum {
DXX_NEWDEMO_HELP_MENU(ENUM)
};
void show_newdemo_help()
{
struct help_menu_items
{
enum {
DXX_NEWDEMO_HELP_MENU(ENUM)
};
std::array<newmenu_item, DXX_NEWDEMO_HELP_MENU(COUNT)> m;
help_menu_items()
{
DXX_NEWDEMO_HELP_MENU(ADD);
}
};
struct help_menu : help_menu_items, passive_newmenu
{
help_menu(grs_canvas &src) :
passive_newmenu(menu_title{nullptr}, menu_subtitle{"DEMO PLAYBACK CONTROLS"}, menu_filename{nullptr}, tiny_mode_flag::tiny, tab_processing_flag::ignore, adjusted_citem::create(m, 0), src)
{
}
};
auto menu = window_create<help_menu>(grd_curscreen->sc_canvas);
(void)menu;
}
2020-09-11 03:08:02 +00:00
}
namespace {
2015-01-18 01:58:32 +00:00
#undef DXX_NEWDEMO_HELP_MENU
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
#define LEAVE_TIME 0x4000 //how long until we decide key is down (Used to be 0x4000)
2006-03-20 17:12:09 +00:00
2015-05-24 17:16:38 +00:00
enum class leave_type : uint_fast8_t
{
none,
maybe_on_release,
wait_for_release,
on_press,
};
static leave_type leave_mode;
static void end_rear_view()
{
Rear_view = 0;
2022-02-12 18:57:12 +00:00
if (PlayerCfg.CockpitMode[1] == cockpit_mode_t::rear_view)
2015-05-24 17:16:38 +00:00
select_cockpit(PlayerCfg.CockpitMode[0]);
if (Newdemo_state == ND_STATE_RECORDING)
newdemo_record_restore_rearview();
}
static void check_end_rear_view()
{
leave_mode = leave_type::none;
if (Rear_view)
end_rear_view();
}
2020-09-11 03:08:02 +00:00
}
namespace dsx {
2006-03-20 17:12:09 +00:00
//deal with rear view - switch it on, or off, or whatever
void check_rear_view(control_info &Controls)
2006-03-20 17:12:09 +00:00
{
static fix64 entry_time;
2006-03-20 17:12:09 +00:00
if (Newdemo_state == ND_STATE_PLAYBACK)
return;
2015-05-24 17:16:38 +00:00
const auto rear_view = Controls.state.rear_view;
switch (leave_mode)
{
case leave_type::none:
if (!rear_view)
return;
if (Rear_view)
end_rear_view();
else
{
Rear_view = 1;
leave_mode = leave_type::maybe_on_release; //means wait for another key
entry_time = timer_query();
2022-02-12 18:57:12 +00:00
if (PlayerCfg.CockpitMode[1] == cockpit_mode_t::full_cockpit)
select_cockpit(cockpit_mode_t::rear_view);
2015-05-24 17:16:38 +00:00
if (Newdemo_state == ND_STATE_RECORDING)
newdemo_record_rearview();
2006-03-20 17:12:09 +00:00
}
2015-05-24 17:16:38 +00:00
return;
case leave_type::maybe_on_release:
if (rear_view)
{
if (timer_query() - entry_time > LEAVE_TIME)
leave_mode = leave_type::wait_for_release;
2006-03-20 17:12:09 +00:00
}
2015-05-24 17:16:38 +00:00
else
leave_mode = leave_type::on_press;
return;
case leave_type::wait_for_release:
if (!rear_view)
check_end_rear_view();
return;
case leave_type::on_press:
if (rear_view)
{
Controls.state.rear_view = 0;
check_end_rear_view();
2006-03-20 17:12:09 +00:00
}
2015-05-24 17:16:38 +00:00
return;
default:
break;
}
2006-03-20 17:12:09 +00:00
}
}
2006-03-20 17:12:09 +00:00
void reset_rear_view(void)
{
if (Rear_view) {
if (Newdemo_state == ND_STATE_RECORDING)
newdemo_record_restore_rearview();
}
Rear_view = 0;
select_cockpit(PlayerCfg.CockpitMode[0]);
2006-03-20 17:12:09 +00:00
}
2015-07-18 03:49:47 +00:00
int cheats_enabled()
{
return cheats.enabled;
}
//turns off all cheats & resets cheater flag
2006-03-20 17:12:09 +00:00
void game_disable_cheats()
{
#if defined(DXX_BUILD_DESCENT_II)
if (cheats.homingfire)
weapons_homing_all_reset();
#endif
2014-06-26 02:24:32 +00:00
cheats = {};
2006-03-20 17:12:09 +00:00
}
// game_setup()
// ----------------------------------------------------------------------------
2016-01-09 16:38:12 +00:00
namespace dsx {
game_window *game_setup()
2006-03-20 17:12:09 +00:00
{
PlayerCfg.CockpitMode[1] = PlayerCfg.CockpitMode[0];
2022-02-12 18:57:12 +00:00
last_drawn_cockpit = cockpit_mode_t{UINT8_MAX}; // Force cockpit to redraw next time a frame renders.
2006-03-20 17:12:09 +00:00
Endlevel_sequence = 0;
auto game_wind = window_create<game_window>(grd_curscreen->sc_canvas, 0, 0, SWIDTH, SHEIGHT);
2006-03-20 17:12:09 +00:00
reset_palette_add();
#if DXX_USE_STEREOSCOPIC_RENDER
init_stereo();
#endif
2006-03-20 17:12:09 +00:00
init_cockpit();
init_gauges();
netplayerinfo_on = 0;
2006-03-20 17:12:09 +00:00
#if DXX_USE_EDITOR
if (!Cursegp)
{
Cursegp = imsegptridx(segment_first);
2022-01-09 15:25:42 +00:00
Curside = sidenum_t::WLEFT;
}
2006-03-20 17:12:09 +00:00
#endif
Viewer = ConsoleObject;
2016-09-11 18:49:13 +00:00
fly_init(*ConsoleObject);
2006-03-20 17:12:09 +00:00
Game_suspended = 0;
reset_time();
FrameTime = 0; //make first frame zero
fix_object_segs();
2015-12-24 04:01:27 +00:00
if (CGameArg.SysAutoRecordDemo && Newdemo_state == ND_STATE_NORMAL)
newdemo_start_recording();
return game_wind;
}
2006-03-20 17:12:09 +00:00
// Event handler for the game
window_event_result game_window::event_handler(const d_event &event)
2006-03-20 17:12:09 +00:00
{
auto result = window_event_result::ignored;
2014-10-04 21:47:13 +00:00
switch (event.type)
{
case EVENT_WINDOW_ACTIVATED:
2010-07-29 08:30:46 +00:00
set_screen_mode(SCREEN_GAME);
event_toggle_focus(1);
key_toggle_repeat(0);
game_flush_inputs(Controls);
if (time_paused)
start_time();
if (!((Game_mode & GM_MULTI) && (Newdemo_state != ND_STATE_PLAYBACK)))
digi_resume_digi_sounds();
if (!((Game_mode & GM_MULTI) && (Newdemo_state != ND_STATE_PLAYBACK)))
palette_restore();
2010-07-29 08:30:46 +00:00
reset_cockpit();
break;
case EVENT_WINDOW_DEACTIVATED:
if (!(((Game_mode & GM_MULTI) && (Newdemo_state != ND_STATE_PLAYBACK)) && (!Endlevel_sequence)) )
stop_time();
if (!((Game_mode & GM_MULTI) && (Newdemo_state != ND_STATE_PLAYBACK)))
digi_pause_digi_sounds();
if (!((Game_mode & GM_MULTI) && (Newdemo_state != ND_STATE_PLAYBACK)))
full_palette_save();
event_toggle_focus(0);
key_toggle_repeat(1);
break;
#if DXX_MAX_BUTTONS_PER_JOYSTICK
case EVENT_JOYSTICK_BUTTON_UP:
case EVENT_JOYSTICK_BUTTON_DOWN:
#endif
#if DXX_MAX_AXES_PER_JOYSTICK
case EVENT_JOYSTICK_MOVED:
#endif
case EVENT_MOUSE_BUTTON_UP:
case EVENT_MOUSE_BUTTON_DOWN:
case EVENT_MOUSE_MOVED:
case EVENT_KEY_COMMAND:
case EVENT_KEY_RELEASE:
case EVENT_IDLE:
return ReadControls(LevelSharedRobotInfoState, event, Controls);
case EVENT_WINDOW_DRAW:
if (!time_paused)
{
calc_frame_time();
result = GameProcessFrame(LevelSharedRobotInfoState);
}
if (!Automap_active) // efficiency hack
{
if (force_cockpit_redraw) { //screen need redrawing?
init_cockpit();
force_cockpit_redraw=0;
}
game_render_frame(LevelSharedRobotInfoState.Robot_info, Controls);
}
break;
case EVENT_WINDOW_CLOSE:
digi_stop_digi_sounds();
if ( (Newdemo_state == ND_STATE_RECORDING) || (Newdemo_state == ND_STATE_PAUSED) )
newdemo_stop_recording();
multi_leave_game();
if ( Newdemo_state == ND_STATE_PLAYBACK )
newdemo_stop_playback();
songs_play_song( SONG_TITLE, 1 );
game_disable_cheats();
Game_mode = {};
#if DXX_USE_EDITOR
if (!EditorWindow) // have to do it this way because of the necessary longjmp. Yuck.
#endif
show_menus();
event_toggle_focus(0);
key_toggle_repeat(1);
Game_wind = nullptr;
return window_event_result::ignored;
case EVENT_LOOP_BEGIN_LOOP:
kconfig_begin_loop(Controls);
break;
case EVENT_LOOP_END_LOOP:
kconfig_end_loop(Controls, FrameTime);
break;
default:
break;
}
return result;
}
// Initialise game, actually runs in main event loop
void game()
{
hide_menus();
Game_wind = game_setup();
2006-03-20 17:12:09 +00:00
}
2016-01-09 16:38:12 +00:00
}
2006-03-20 17:12:09 +00:00
//called at the end of the program
void close_game()
{
close_gauges();
2006-03-20 17:12:09 +00:00
restore_effect_bitmap_icons();
}
#if defined(DXX_BUILD_DESCENT_II)
namespace dsx {
2006-03-20 17:12:09 +00:00
object *Missile_viewer=NULL;
2015-03-22 18:49:21 +00:00
object_signature_t Missile_viewer_sig;
2006-03-20 17:12:09 +00:00
enumerated_array<game_marker_index, 2, gauge_inset_window_view> Marker_viewer_num{
{{
game_marker_index::None,
game_marker_index::None,
}}
};
enumerated_array<unsigned, 2, gauge_inset_window_view> Coop_view_player{
{{
UINT_MAX,
UINT_MAX
}}
};
2006-03-20 17:12:09 +00:00
//returns ptr to escort robot, or NULL
imobjptridx_t find_escort(fvmobjptridx &vmobjptridx, const d_robot_info_array &Robot_info)
2006-03-20 17:12:09 +00:00
{
range_for (const auto &&o, vmobjptridx)
2015-01-15 04:30:03 +00:00
{
if (o->type == OBJ_ROBOT && Robot_info[get_robot_id(o)].companion)
return imobjptridx_t(o);
2015-01-15 04:30:03 +00:00
}
2014-10-25 15:19:20 +00:00
return object_none;
2006-03-20 17:12:09 +00:00
}
namespace {
2006-03-20 17:12:09 +00:00
//if water or fire level, make occasional sound
static void do_ambient_sounds(const uint8_t s2_flags)
2006-03-20 17:12:09 +00:00
{
const auto has_water = (s2_flags & S2F_AMBIENT_WATER);
sound_effect sound;
if (s2_flags & S2F_AMBIENT_LAVA)
{ //has lava
2006-03-20 17:12:09 +00:00
sound = SOUND_AMBIENT_LAVA;
if (has_water && (d_rand() & 1)) //both, pick one
sound = SOUND_AMBIENT_WATER;
}
else if (has_water) //just water
sound = SOUND_AMBIENT_WATER;
else
return;
if (((d_rand() << 3) < FrameTime)) { //play the sound
fix volume = d_rand() + f1_0/2;
digi_play_sample(sound,volume);
}
}
}
}
#endif
2006-03-20 17:12:09 +00:00
void game_leave_menus(void)
{
if (!Game_wind)
return;
for (;;) // go through all windows and actually close them if they want to
{
const auto wind = window_get_front();
if (!wind)
break;
if (wind == Game_wind)
break;
if (!window_close(wind))
break;
}
}
namespace dsx {
namespace {
window_event_result GameProcessFrame(const d_level_shared_robot_info_state &LevelSharedRobotInfoState)
2006-03-20 17:12:09 +00:00
{
auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
auto &Objects = LevelUniqueObjectState.Objects;
auto &vmobjptr = Objects.vmptr;
auto &plrobj = get_local_plrobj();
auto &player_info = plrobj.ctype.player_info;
auto &local_player_shields_ref = plrobj.shields;
fix player_shields = local_player_shields_ref;
const auto player_was_dead = Player_dead_state;
auto result = window_event_result::ignored;
state_poll_autosave_game(GameUniqueState, LevelUniqueObjectState);
update_player_stats();
diminish_palette_towards_normal(); // Should leave palette effect up for as long as possible by putting right before render.
do_afterburner_stuff(Objects);
do_cloak_stuff();
do_invulnerable_stuff(player_info);
#if defined(DXX_BUILD_DESCENT_II)
init_ai_frame(player_info.powerup_flags, Controls);
result = do_final_boss_frame();
auto &pl_flags = player_info.powerup_flags;
2016-07-03 00:54:15 +00:00
if (pl_flags & PLAYER_FLAGS_HEADLIGHT_ON)
{
static int turned_off=0;
auto &energy = player_info.energy;
2016-07-03 00:54:16 +00:00
energy -= (FrameTime*3/8);
if (energy < i2f(10)) {
if (!turned_off) {
2016-07-03 00:54:15 +00:00
pl_flags &= ~PLAYER_FLAGS_HEADLIGHT_ON;
turned_off = 1;
2006-03-20 17:12:09 +00:00
if (Game_mode & GM_MULTI)
multi_send_flags(Player_num);
2006-03-20 17:12:09 +00:00
}
}
else
turned_off = 0;
2006-03-20 17:12:09 +00:00
2016-07-03 00:54:16 +00:00
if (energy <= 0)
{
2016-07-03 00:54:16 +00:00
energy = 0;
2016-07-03 00:54:15 +00:00
pl_flags &= ~PLAYER_FLAGS_HEADLIGHT_ON;
if (Game_mode & GM_MULTI)
multi_send_flags(Player_num);
2006-03-20 17:12:09 +00:00
}
}
#endif
2006-03-20 17:12:09 +00:00
#if DXX_USE_EDITOR
check_create_player_path();
player_follow_path(vmobjptr(ConsoleObject));
#endif
2006-03-20 17:12:09 +00:00
if (Game_mode & GM_MULTI)
{
result = std::max(multi_do_frame(), result);
if (Netgame.PlayTimeAllowed.count())
{
if (ThisLevelTime >= Netgame.PlayTimeAllowed)
multi_check_for_killgoal_winner(LevelSharedRobotInfoState.Robot_info);
ThisLevelTime += d_time_fix(FrameTime);
}
}
2006-03-20 17:12:09 +00:00
result = std::max(dead_player_frame(LevelSharedRobotInfoState.Robot_info), result);
if (Newdemo_state != ND_STATE_PLAYBACK)
result = std::max(do_controlcen_dead_frame(), result);
if (result == window_event_result::close)
return result; // skip everything else - don't set Player_dead_state again
2006-03-20 17:12:09 +00:00
#if defined(DXX_BUILD_DESCENT_II)
process_super_mines_frame();
do_seismic_stuff();
do_ambient_sounds(vcsegptr(ConsoleObject->segnum)->s2_flags);
#endif
2006-03-20 17:12:09 +00:00
digi_sync_sounds();
2006-03-20 17:12:09 +00:00
if (Endlevel_sequence) {
result = std::max(do_endlevel_frame(LevelSharedRobotInfoState), result);
powerup_grab_cheat_all();
do_special_effects();
return result; //skip everything else
}
2006-03-20 17:12:09 +00:00
if ((Newdemo_state != ND_STATE_PLAYBACK) || (Newdemo_vcr_state != ND_STATE_PAUSED)) {
do_special_effects();
wall_frame_process(LevelSharedRobotInfoState.Robot_info);
}
2006-03-20 17:12:09 +00:00
if (LevelUniqueControlCenterState.Control_center_destroyed)
{
if (Newdemo_state==ND_STATE_RECORDING )
newdemo_record_control_center_destroyed();
}
2006-03-20 17:12:09 +00:00
flash_frame();
2006-03-20 17:12:09 +00:00
if ( Newdemo_state == ND_STATE_PLAYBACK )
{
result = std::max(newdemo_playback_one_frame(), result);
if ( Newdemo_state != ND_STATE_PLAYBACK )
{
Assert(result == window_event_result::close);
return window_event_result::close; // Go back to menu
}
}
else
{ // Note the link to above!
#ifndef NEWHOMER
player_info.homing_object_dist = -1; // Assume not being tracked. Laser_do_weapon_sequence modifies this.
#endif
result = std::max(game_move_all_objects(LevelSharedRobotInfoState), result);
powerup_grab_cheat_all();
2006-03-20 17:12:09 +00:00
if (Endlevel_sequence) //might have been started during move
return result;
2006-03-20 17:12:09 +00:00
fuelcen_update_all(LevelSharedRobotInfoState.Robot_info);
do_ai_frame_all(LevelSharedRobotInfoState.Robot_info);
2006-03-20 17:12:09 +00:00
auto laser_firing_count = FireLaser(player_info, Controls);
if (auto &Auto_fire_fusion_cannon_time = player_info.Auto_fire_fusion_cannon_time)
{
2016-08-28 22:41:49 +00:00
if (player_info.Primary_weapon != primary_weapon_index_t::FUSION_INDEX)
Auto_fire_fusion_cannon_time = 0;
2016-02-27 19:02:21 +00:00
else if ((laser_firing_count = (GameTime64 + FrameTime/2 >= Auto_fire_fusion_cannon_time)))
{
Auto_fire_fusion_cannon_time = 0;
} else if (d_tick_step) {
2016-02-27 19:02:21 +00:00
const auto rx = (d_rand() - 16384) / 8;
const auto rz = (d_rand() - 16384) / 8;
const auto &&console = vmobjptr(ConsoleObject);
2016-02-27 19:02:21 +00:00
auto &rotvel = console->mtype.phys_info.rotvel;
rotvel.x += rx;
rotvel.z += rz;
2006-03-20 17:12:09 +00:00
const auto bump_amount = player_info.Fusion_charge > F1_0*2 ? player_info.Fusion_charge * 4 : F1_0 * 4;
2016-07-24 04:04:25 +00:00
bump_one_object(console, make_random_vector(), bump_amount);
}
2006-03-20 17:12:09 +00:00
}
2016-02-27 19:02:21 +00:00
if (laser_firing_count)
2017-02-08 23:34:41 +00:00
do_laser_firing_player(plrobj);
delayed_autoselect(player_info, Controls);
}
2006-03-20 17:12:09 +00:00
if (Do_appearance_effect) {
Do_appearance_effect = 0;
2018-10-21 00:24:07 +00:00
create_player_appearance_effect(Vclip, *ConsoleObject);
2006-03-20 17:12:09 +00:00
}
#if defined(DXX_BUILD_DESCENT_II)
omega_charge_frame(player_info);
slide_textures();
auto &LevelSharedDestructibleLightState = LevelSharedSegmentState.DestructibleLights;
flicker_lights(LevelSharedDestructibleLightState, Flickering_light_state, vmsegptridx);
//if the player is taking damage, give up guided missile control
if (local_player_shields_ref != player_shields)
{
const auto player_num = Player_num;
if (const auto &&gimobj = LevelUniqueObjectState.Guided_missile.get_player_active_guided_missile(vmobjptr, player_num))
release_local_guided_missile(LevelUniqueObjectState, player_num, *gimobj);
}
#endif
2006-03-20 17:12:09 +00:00
// Check if we have to close in-game menus for multiplayer
if ((Game_mode & GM_MULTI) && get_local_player().connected == player_connection_status::playing)
{
if (Endlevel_sequence || Player_dead_state != player_was_dead || local_player_shields_ref < player_shields || (LevelUniqueControlCenterState.Control_center_destroyed && LevelUniqueControlCenterState.Countdown_seconds_left < 10))
game_leave_menus();
}
return result;
2006-03-20 17:12:09 +00:00
}
}
#if defined(DXX_BUILD_DESCENT_II)
void compute_slide_segs()
2006-03-20 17:12:09 +00:00
{
auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
for (const csmusegment suseg : vmsegptr)
2014-10-12 23:10:05 +00:00
{
sidemask_t slide_textures{};
for (const auto sidenum : MAX_SIDES_PER_SEGMENT)
{
const auto &uside = suseg.u.sides[sidenum];
2020-09-11 03:08:02 +00:00
const auto &ti = TmapInfo[get_texture_index(uside.tmap_num)];
if (!(ti.slide_u || ti.slide_v))
continue;
const auto &sside = suseg.s.sides[sidenum];
if (IS_CHILD(suseg.s.children[sidenum]) && sside.wall_num == wall_none)
2015-05-17 20:37:59 +00:00
/* If a wall exists, it could be visible at start or
* become visible later, so always enable sliding for
* walls.
*/
continue;
slide_textures |= build_sidemask(sidenum);
2006-03-20 17:12:09 +00:00
}
suseg.u.slide_textures = slide_textures;
2006-03-20 17:12:09 +00:00
}
}
namespace {
2015-05-17 20:37:59 +00:00
template <fix uvl::*p>
2020-05-02 21:18:42 +00:00
static void update_uv(std::array<uvl, 4> &uvls, uvl &i, fix a)
2015-05-17 20:37:59 +00:00
{
if (!a)
return;
const auto ip = (i.*p += a);
if (ip > f2_0)
range_for (auto &j, uvls)
j.*p -= f1_0;
else if (ip < -f2_0)
range_for (auto &j, uvls)
j.*p += f1_0;
}
2006-03-20 17:12:09 +00:00
// -----------------------------------------------------------------------------
2013-09-22 22:26:27 +00:00
static void slide_textures(void)
2006-03-20 17:12:09 +00:00
{
auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
for (unique_segment &useg : vmsegptr)
2014-10-12 23:10:05 +00:00
{
if (const auto slide_seg = useg.slide_textures; slide_seg != sidemask_t{})
2015-05-17 20:37:59 +00:00
{
for (const auto sidenum : MAX_SIDES_PER_SEGMENT)
{
if (slide_seg & build_sidemask(sidenum))
2015-05-17 20:37:59 +00:00
{
auto &side = useg.sides[sidenum];
2020-09-11 03:08:02 +00:00
const auto &ti = TmapInfo[get_texture_index(side.tmap_num)];
2015-05-17 20:37:59 +00:00
const auto tiu = ti.slide_u;
const auto tiv = ti.slide_v;
if (tiu || tiv)
{
const auto frametime = FrameTime;
const auto ua = fixmul(frametime, tiu << 8);
const auto va = fixmul(frametime, tiv << 8);
auto &uvls = side.uvls;
range_for (auto &i, uvls)
{
update_uv<&uvl::u>(uvls, i, ua);
update_uv<&uvl::v>(uvls, i, va);
2006-03-20 17:12:09 +00:00
}
}
}
}
}
}
}
constexpr std::integral_constant<fix, INT32_MIN> flicker_timer_disabled{};
static void flicker_lights(const d_level_shared_destructible_light_state &LevelSharedDestructibleLightState, d_flickering_light_state &fls, fvmsegptridx &vmsegptridx)
2006-03-20 17:12:09 +00:00
{
auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
auto &Walls = LevelUniqueWallSubsystemState.Walls;
auto &vcwallptr = Walls.vcptr;
range_for (auto &f, partial_range(fls.Flickering_lights, fls.Num_flickering_lights))
2014-11-23 04:36:58 +00:00
{
if (f.timer == flicker_timer_disabled) //disabled
2006-03-20 17:12:09 +00:00
continue;
const auto &&segp = vmsegptridx(f.segnum);
2015-12-22 04:18:52 +00:00
const auto sidenum = f.sidenum;
{
2018-12-13 02:31:38 +00:00
auto &side = segp->unique_segment::sides[sidenum];
2020-09-11 03:08:02 +00:00
if (!(TmapInfo[get_texture_index(side.tmap_num)].lighting || TmapInfo[get_texture_index(side.tmap_num2)].lighting))
2015-12-22 04:18:52 +00:00
continue;
}
2006-03-20 17:12:09 +00:00
2015-12-22 04:18:52 +00:00
//make sure this is actually a light
if (! (WALL_IS_DOORWAY(GameBitmaps, Textures, vcwallptr, segp, sidenum) & WALL_IS_DOORWAY_FLAG::render))
2006-03-20 17:12:09 +00:00
continue;
2015-12-22 04:18:52 +00:00
if ((f.timer -= FrameTime) < 0)
{
while (f.timer < 0)
f.timer += f.delay;
f.mask = ((f.mask & 0x80000000) ? 1 : 0) + (f.mask << 1);
if (f.mask & 1)
add_light(LevelSharedDestructibleLightState, segp, sidenum);
2006-03-20 17:12:09 +00:00
else
subtract_light(LevelSharedDestructibleLightState, segp, sidenum);
2006-03-20 17:12:09 +00:00
}
}
}
//returns ptr to flickering light structure, or NULL if can't find
static std::pair<d_flickering_light_state::Flickering_light_array_t::iterator, d_flickering_light_state::Flickering_light_array_t::iterator> find_flicker(d_flickering_light_state &fls, const vmsegidx_t segnum, const sidenum_t sidenum)
2006-03-20 17:12:09 +00:00
{
//see if there's already an entry for this seg/side
const auto &&pr = partial_range(fls.Flickering_lights, fls.Num_flickering_lights);
const auto &&predicate = [segnum, sidenum](const flickering_light &f) {
2014-11-23 04:36:58 +00:00
return f.segnum == segnum && f.sidenum == sidenum; //found it!
};
const auto &&pe = pr.end();
return {ranges::find_if(pr.begin(), pe, predicate), pe};
2014-11-23 04:36:58 +00:00
}
2006-03-20 17:12:09 +00:00
static void update_flicker(d_flickering_light_state &fls, const vmsegidx_t segnum, const sidenum_t sidenum, const fix timer)
2014-11-23 04:36:58 +00:00
{
const auto &&i = find_flicker(fls, segnum, sidenum);
2014-11-23 04:36:58 +00:00
if (i.first != i.second)
2016-01-26 03:45:08 +00:00
i.first->timer = timer;
2006-03-20 17:12:09 +00:00
}
}
2006-03-20 17:12:09 +00:00
//turn flickering off (because light has been turned off)
void disable_flicker(d_flickering_light_state &fls, const vmsegidx_t segnum, const sidenum_t sidenum)
2006-03-20 17:12:09 +00:00
{
update_flicker(fls, segnum, sidenum, flicker_timer_disabled);
2006-03-20 17:12:09 +00:00
}
//turn flickering off (because light has been turned on)
void enable_flicker(d_flickering_light_state &fls, const vmsegidx_t segnum, const sidenum_t sidenum)
2006-03-20 17:12:09 +00:00
{
update_flicker(fls, segnum, sidenum, 0);
2006-03-20 17:12:09 +00:00
}
#endif
namespace {
2006-03-20 17:12:09 +00:00
// -----------------------------------------------------------------------------
// Fire Laser: Registers a laser fire, and performs special stuff for the fusion
// cannon.
bool FireLaser(player_info &player_info, const control_info &Controls)
2006-03-20 17:12:09 +00:00
{
auto &Objects = LevelUniqueObjectState.Objects;
auto &vmobjptr = Objects.vmptr;
auto &vmobjptridx = Objects.vmptridx;
2017-02-08 23:34:41 +00:00
if (!Controls.state.fire_primary)
return false;
if (!allowed_to_fire_laser(player_info))
return false;
2016-08-28 22:41:49 +00:00
auto &Primary_weapon = player_info.Primary_weapon;
2017-02-08 23:34:41 +00:00
if (!Weapon_info[Primary_weapon_to_weapon_info[Primary_weapon]].fire_count)
/* Retail data sets fire_count=1 for all primary weapons */
return false;
2006-03-20 17:12:09 +00:00
2017-02-08 23:34:41 +00:00
if (Primary_weapon == primary_weapon_index_t::FUSION_INDEX)
{
auto &energy = player_info.energy;
auto &Auto_fire_fusion_cannon_time = player_info.Auto_fire_fusion_cannon_time;
2016-07-03 00:54:16 +00:00
if (energy < F1_0 * 2 && Auto_fire_fusion_cannon_time == 0)
{
2017-02-08 23:34:41 +00:00
return false;
2006-03-20 17:12:09 +00:00
} else {
static fix64 Fusion_next_sound_time = 0;
if (player_info.Fusion_charge == 0)
2016-07-03 00:54:16 +00:00
energy -= F1_0*2;
2006-03-20 17:12:09 +00:00
const auto Fusion_charge = (player_info.Fusion_charge += FrameTime);
2016-07-03 00:54:16 +00:00
energy -= FrameTime;
2006-03-20 17:12:09 +00:00
2016-07-03 00:54:16 +00:00
if (energy <= 0)
{
2016-07-03 00:54:16 +00:00
energy = 0;
Auto_fire_fusion_cannon_time = GameTime64 -1; // Fire now!
2006-03-20 17:12:09 +00:00
} else
Auto_fire_fusion_cannon_time = GameTime64 + FrameTime/2 + 1; // Fire the fusion cannon at this time in the future.
2006-03-20 17:12:09 +00:00
{
int dg, db;
const int dr = Fusion_charge >> 11;
if (Fusion_charge < F1_0*2)
dg = 0, db = dr;
else
dg = dr, db = 0;
PALETTE_FLASH_ADD(dr, dg, db);
}
2006-03-20 17:12:09 +00:00
if (Fusion_next_sound_time > GameTime64 + F1_0/8 + D_RAND_MAX/4) // GameTime64 is smaller than max delay - player in new level?
Fusion_next_sound_time = GameTime64 - 1;
2006-03-20 17:12:09 +00:00
if (Fusion_next_sound_time < GameTime64) {
2006-03-20 17:12:09 +00:00
if (Fusion_charge > F1_0*2) {
digi_play_sample( 11, F1_0 );
#if defined(DXX_BUILD_DESCENT_I)
if(Game_mode & GM_MULTI)
multi_send_play_sound(11, F1_0, sound_stack::allow_stacking);
#endif
const auto cobjp = vmobjptridx(ConsoleObject);
apply_damage_to_player(cobjp, cobjp, d_rand() * 4, apply_damage_player::always);
2006-03-20 17:12:09 +00:00
} else {
create_awareness_event(vmobjptr(ConsoleObject), player_awareness_type_t::PA_WEAPON_ROBOT_COLLISION, LevelUniqueRobotAwarenessState);
2015-08-05 02:59:02 +00:00
multi_digi_play_sample(SOUND_FUSION_WARMUP, F1_0);
2006-03-20 17:12:09 +00:00
}
Fusion_next_sound_time = GameTime64 + F1_0/8 + d_rand()/4;
2006-03-20 17:12:09 +00:00
}
}
}
2017-02-08 23:34:41 +00:00
return true;
2006-03-20 17:12:09 +00:00
}
// -------------------------------------------------------------------------------------------------------
// If player is close enough to objnum, which ought to be a powerup, pick it up!
// This could easily be made difficulty level dependent.
static void powerup_grab_cheat(object &player, const vmobjptridx_t powerup)
2006-03-20 17:12:09 +00:00
{
fix powerup_size;
fix player_size;
2014-08-23 23:53:56 +00:00
Assert(powerup->type == OBJ_POWERUP);
2006-03-20 17:12:09 +00:00
2014-08-23 23:53:56 +00:00
powerup_size = powerup->size;
2016-04-23 17:59:47 +00:00
player_size = player.size;
2006-03-20 17:12:09 +00:00
2016-04-23 17:59:47 +00:00
const auto dist = vm_vec_dist_quick(powerup->pos, player.pos);
2006-03-20 17:12:09 +00:00
2014-08-23 23:53:56 +00:00
if ((dist < 2*(powerup_size + player_size)) && !(powerup->flags & OF_SHOULD_BE_DEAD)) {
collide_live_local_player_and_powerup(powerup);
2006-03-20 17:12:09 +00:00
}
}
// -------------------------------------------------------------------------------------------------------
// Make it easier to pick up powerups.
// For all powerups in this segment, pick them up at up to twice pickuppable distance based on dot product
// from player to powerup and player's forward vector.
// This has the effect of picking them up more easily left/right and up/down, but not making them disappear
// way before the player gets there.
void powerup_grab_cheat_all(void)
{
auto &Objects = LevelUniqueObjectState.Objects;
auto &vmobjptr = Objects.vmptr;
auto &vmobjptridx = Objects.vmptridx;
if (Endlevel_sequence)
return;
if (Player_dead_state != player_dead_state::no)
return;
const auto &&console = vmobjptr(ConsoleObject);
2017-08-13 20:38:31 +00:00
range_for (const auto objnum, objects_in(vmsegptr(console->segnum), vmobjptridx, vmsegptr))
if (objnum->type == OBJ_POWERUP)
2015-07-12 01:04:19 +00:00
powerup_grab_cheat(console, objnum);
2006-03-20 17:12:09 +00:00
}
}
}
2006-03-20 17:12:09 +00:00
#ifdef SHOW_EXIT_PATH
namespace dsx {
namespace {
2006-03-20 17:12:09 +00:00
// ------------------------------------------------------------------------------------------------------------------
// Create path for player from current segment to goal segment.
// Return true if path created, else return false.
2018-10-21 00:24:07 +00:00
static int mark_player_path_to_segment(const d_vclip_array &Vclip, fvmobjptridx &vmobjptridx, fvmsegptridx &vmsegptridx, segnum_t segnum)
2006-03-20 17:12:09 +00:00
{
int player_hide_index=-1;
if (LevelUniqueObjectState.Level_path_created)
2006-03-20 17:12:09 +00:00
return 0;
LevelUniqueObjectState.Level_path_created = 1;
2006-03-20 17:12:09 +00:00
auto objp = vmobjptridx(ConsoleObject);
const auto &&cr = create_path_points(objp, create_path_unused_robot_info, objp->segnum, segnum, Point_segs_free_ptr, 100, create_path_random_flag::nonrandom, create_path_safety_flag::unsafe, segment_none);
const unsigned player_path_length = cr.second;
if (cr.first == create_path_result::early)
2006-03-20 17:12:09 +00:00
return 0;
player_hide_index = Point_segs_free_ptr - Point_segs;
Point_segs_free_ptr += player_path_length;
if (Point_segs_free_ptr - Point_segs + MAX_PATH_LENGTH*2 > MAX_POINT_SEGS) {
ai_reset_all_paths();
return 0;
}
for (int i=1; i<player_path_length; i++) {
2006-03-20 17:12:09 +00:00
vms_vector seg_center;
seg_center = Point_segs[player_hide_index+i].point;
2022-07-02 18:10:45 +00:00
const auto &&obj = obj_create(LevelUniqueObjectState, LevelSharedSegmentState, LevelUniqueSegmentState, OBJ_POWERUP, POW_ENERGY, vmsegptridx(Point_segs[player_hide_index+i].segnum), seg_center, &vmd_identity_matrix, Powerup_info[POW_ENERGY].size, object::control_type::powerup, object::movement_type::None, RT_POWERUP);
if (obj == object_none) {
2006-03-20 17:12:09 +00:00
Int3(); // Unable to drop energy powerup for path
return 1;
}
obj->rtype.vclip_info.vclip_num = Powerup_info[get_powerup_id(obj)].vclip_num;
2006-03-20 17:12:09 +00:00
obj->rtype.vclip_info.frametime = Vclip[obj->rtype.vclip_info.vclip_num].frame_time;
obj->rtype.vclip_info.framenum = 0;
obj->lifeleft = F1_0*100 + d_rand() * 4;
}
return 1;
}
}
2006-03-20 17:12:09 +00:00
// Return true if it happened, else return false.
int create_special_path(void)
{
auto &Objects = LevelUniqueObjectState.Objects;
auto &vmobjptridx = Objects.vmptridx;
2006-03-20 17:12:09 +00:00
// ---------- Find exit doors ----------
2016-02-12 04:02:28 +00:00
range_for (const auto &&segp, vcsegptridx)
2015-06-13 22:42:17 +00:00
{
for (const auto child_segnum : segp->shared_segment::children)
if (child_segnum == segment_exit)
2015-06-13 22:42:17 +00:00
{
2018-10-21 00:24:07 +00:00
return mark_player_path_to_segment(Vclip, vmobjptridx, vmsegptridx, segp);
2006-03-20 17:12:09 +00:00
}
2015-06-13 22:42:17 +00:00
}
2006-03-20 17:12:09 +00:00
return 0;
}
}
2006-03-20 17:12:09 +00:00
#endif
#if defined(DXX_BUILD_DESCENT_II)
namespace dsx {
2006-03-20 17:12:09 +00:00
/*
* reads a flickering_light structure from a PHYSFS_File
2006-03-20 17:12:09 +00:00
*/
void flickering_light_read(flickering_light &fl, PHYSFS_File *fp)
2006-03-20 17:12:09 +00:00
{
{
const auto s = segnum_t{static_cast<uint16_t>(PHYSFSX_readShort(fp))};
fl.segnum = vmsegidx_t::check_nothrow_index(s) ? s : segment_none;
}
2022-01-09 15:25:42 +00:00
const auto sidenum = build_sidenum_from_untrusted(PHYSFSX_readShort(fp));
fl.mask = PHYSFSX_readInt(fp);
fl.timer = PHYSFSX_readFix(fp);
fl.delay = PHYSFSX_readFix(fp);
2022-01-09 15:25:42 +00:00
if (!sidenum)
{
fl = {};
return;
}
fl.sidenum = sidenum.value();
2006-03-20 17:12:09 +00:00
}
void flickering_light_write(const flickering_light &fl, PHYSFS_File *fp)
2006-03-20 17:12:09 +00:00
{
PHYSFS_writeSLE16(fp, fl.segnum);
PHYSFS_writeSLE16(fp, underlying_value(fl.sidenum));
PHYSFS_writeULE32(fp, fl.mask);
PHYSFSX_writeFix(fp, fl.timer);
PHYSFSX_writeFix(fp, fl.delay);
2006-03-20 17:12:09 +00:00
}
}
#endif