834aeea23f
Commit8c8b7419b6
defined Shift-F5 and Shift-F6 to adjust the VR handling. However, Shift-F5 and Shift-F6 already had a meaning: drop primary weapon / drop secondary weapon. The ordering of the logic causes the new VR handling to suppress the old drop handling, thus preventing players from dropping weapons. Disable the VR meaning for these keys so that drop-weapon works again, since F1 documents those keys as drop-weapon. Fixes:8c8b7419b6
("Improved conditionals for stereo vs non-stereo modes.")
2247 lines
61 KiB
C++
2247 lines
61 KiB
C++
/*
|
|
* 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.
|
|
|
|
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 Controls Stuff
|
|
*
|
|
*/
|
|
|
|
#include <algorithm>
|
|
#include <stdio.h>
|
|
#include <cstdlib>
|
|
#include <string.h>
|
|
#include <stdarg.h>
|
|
|
|
#include "window.h"
|
|
#include "console.h"
|
|
#include "collide.h"
|
|
#include "strutil.h"
|
|
#include "game.h"
|
|
#include "player.h"
|
|
#include "key.h"
|
|
#include "object.h"
|
|
#include "menu.h"
|
|
#include "dxxerror.h"
|
|
#include "joy.h"
|
|
#include "timer.h"
|
|
#include "render.h"
|
|
#include "laser.h"
|
|
#include "screens.h"
|
|
#include "textures.h"
|
|
#include "slew.h"
|
|
#include "gauges.h"
|
|
#include "texmap.h"
|
|
#include "3d.h"
|
|
#include "gameseg.h"
|
|
#include "wall.h"
|
|
#include "ai.h"
|
|
#include "rbaudio.h"
|
|
#include "digi.h"
|
|
#include "u_mem.h"
|
|
#include "palette.h"
|
|
#include "morph.h"
|
|
#include "robot.h"
|
|
#include "lighting.h"
|
|
#include "newdemo.h"
|
|
#include "weapon.h"
|
|
#include "sounds.h"
|
|
#include "args.h"
|
|
#include "gameseq.h"
|
|
#include "automap.h"
|
|
#include "text.h"
|
|
#include "powerup.h"
|
|
#include "songs.h"
|
|
#include "newmenu.h"
|
|
#include "gamefont.h"
|
|
#include "endlevel.h"
|
|
#include "config.h"
|
|
#include "hudmsg.h"
|
|
#include "kconfig.h"
|
|
#include "titles.h"
|
|
#include "gr.h"
|
|
#include "playsave.h"
|
|
|
|
#include "multi.h"
|
|
#include "cntrlcen.h"
|
|
#include "fuelcen.h"
|
|
#include "state.h"
|
|
#include "piggy.h"
|
|
#include "ai.h"
|
|
#include "rbaudio.h"
|
|
#include "switch.h"
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
#include "escort.h"
|
|
#include "movie.h"
|
|
#endif
|
|
#include "window.h"
|
|
|
|
#if DXX_USE_EDITOR
|
|
#include "editor/editor.h"
|
|
#include "editor/esegment.h"
|
|
#endif
|
|
|
|
#include "compiler-range_for.h"
|
|
#include "d_levelstate.h"
|
|
#include "d_range.h"
|
|
#include "partial_range.h"
|
|
#include <array>
|
|
#include <utility>
|
|
|
|
#include <SDL.h>
|
|
|
|
#if defined(__GNUC__) && defined(WIN32)
|
|
/* Mingw64 _mingw_print_pop.h changes PRIi64 to POSIX-style. Change it
|
|
* back here.
|
|
*
|
|
* Some outdated mingw32 users are also affected.
|
|
*/
|
|
#undef PRIi64
|
|
#define PRIi64 "I64i"
|
|
#endif
|
|
|
|
using std::min;
|
|
|
|
namespace dcx {
|
|
|
|
namespace {
|
|
|
|
struct pause_window : window
|
|
{
|
|
using window::window;
|
|
std::array<char, 128> msg;
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
namespace dsx {
|
|
|
|
namespace {
|
|
|
|
struct pause_window : ::dcx::pause_window
|
|
{
|
|
using ::dcx::pause_window::pause_window;
|
|
virtual window_event_result event_handler(const d_event &) override;
|
|
};
|
|
|
|
#ifndef RELEASE
|
|
static void do_cheat_menu(object &plrobj, grs_canvas &cv_canvas);
|
|
static void play_test_sound();
|
|
#endif
|
|
|
|
int allowed_to_fire_flare(player_info &player_info)
|
|
{
|
|
auto &Next_flare_fire_time = player_info.Next_flare_fire_time;
|
|
if (Next_flare_fire_time > GameTime64)
|
|
return 0;
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
if (player_info.energy < Weapon_info[weapon_id_type::FLARE_ID].energy_usage)
|
|
#define FLARE_BIG_DELAY (F1_0*2)
|
|
Next_flare_fire_time = GameTime64 + FLARE_BIG_DELAY;
|
|
else
|
|
#endif
|
|
Next_flare_fire_time = GameTime64 + F1_0/4;
|
|
return 1;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#define key_isfunc(k) (((k&0xff)>=KEY_F1 && (k&0xff)<=KEY_F10) || (k&0xff)==KEY_F11 || (k&0xff)==KEY_F12)
|
|
|
|
// Functions ------------------------------------------------------------------
|
|
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
#define CONVERTER_RATE 20 //10 units per second xfer rate
|
|
#define CONVERTER_SCALE 2 //2 units energy -> 1 unit shields
|
|
|
|
#define CONVERTER_SOUND_DELAY (f1_0/2) //play every half second
|
|
|
|
namespace dsx {
|
|
namespace {
|
|
|
|
static void transfer_energy_to_shield(object &plrobj)
|
|
{
|
|
static fix64 last_play_time=0;
|
|
|
|
auto &player_info = plrobj.ctype.player_info;
|
|
auto &shields = plrobj.shields;
|
|
auto &energy = player_info.energy;
|
|
//how much energy gets transfered
|
|
const fix e = min(min(FrameTime*CONVERTER_RATE, energy - INITIAL_ENERGY), (MAX_SHIELDS - shields) * CONVERTER_SCALE);
|
|
|
|
if (e <= 0) {
|
|
|
|
if (energy <= INITIAL_ENERGY) {
|
|
HUD_init_message(HM_DEFAULT, "Need more than %i energy to enable transfer", f2i(INITIAL_ENERGY));
|
|
}
|
|
else if (shields >= MAX_SHIELDS)
|
|
{
|
|
HUD_init_message_literal(HM_DEFAULT, "No transfer: Shields already at max");
|
|
}
|
|
return;
|
|
}
|
|
|
|
energy -= e;
|
|
shields += e / CONVERTER_SCALE;
|
|
|
|
if (last_play_time > GameTime64)
|
|
last_play_time = 0;
|
|
|
|
if (GameTime64 > last_play_time+CONVERTER_SOUND_DELAY) {
|
|
digi_play_sample_once(SOUND_CONVERT_ENERGY, F1_0);
|
|
last_play_time = GameTime64;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
#endif
|
|
|
|
namespace {
|
|
|
|
// Control Functions
|
|
|
|
static fix64 newdemo_single_frame_time;
|
|
|
|
static void update_vcr_state(void)
|
|
{
|
|
if ((keyd_pressed[KEY_LSHIFT] || keyd_pressed[KEY_RSHIFT]) && keyd_pressed[KEY_RIGHT] && d_tick_step)
|
|
Newdemo_vcr_state = ND_STATE_FASTFORWARD;
|
|
else if ((keyd_pressed[KEY_LSHIFT] || keyd_pressed[KEY_RSHIFT]) && keyd_pressed[KEY_LEFT] && d_tick_step)
|
|
Newdemo_vcr_state = ND_STATE_REWINDING;
|
|
else if (!(keyd_pressed[KEY_LCTRL] || keyd_pressed[KEY_RCTRL]) && keyd_pressed[KEY_RIGHT] && ((GameTime64 - newdemo_single_frame_time) >= F1_0) && d_tick_step)
|
|
Newdemo_vcr_state = ND_STATE_ONEFRAMEFORWARD;
|
|
else if (!(keyd_pressed[KEY_LCTRL] || keyd_pressed[KEY_RCTRL]) && keyd_pressed[KEY_LEFT] && ((GameTime64 - newdemo_single_frame_time) >= F1_0) && d_tick_step)
|
|
Newdemo_vcr_state = ND_STATE_ONEFRAMEBACKWARD;
|
|
else if ((Newdemo_vcr_state == ND_STATE_FASTFORWARD) || (Newdemo_vcr_state == ND_STATE_REWINDING))
|
|
Newdemo_vcr_state = ND_STATE_PLAYBACK;
|
|
}
|
|
|
|
}
|
|
|
|
namespace dsx {
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
|
|
namespace {
|
|
|
|
//returns which bomb will be dropped next time the bomb key is pressed
|
|
template <typename T>
|
|
secondary_weapon_index_t read_update_which_proximity_mine_to_use(T &player_info)
|
|
{
|
|
//use the last one selected, unless there aren't any, in which case use
|
|
//the other if there are any
|
|
auto &Secondary_last_was_super = player_info.Secondary_last_was_super;
|
|
const auto mask = 1 << PROXIMITY_INDEX;
|
|
const auto bomb = (Secondary_last_was_super & mask) ? SMART_MINE_INDEX : PROXIMITY_INDEX;
|
|
auto &secondary_ammo = player_info.secondary_ammo;
|
|
if (secondary_ammo[bomb])
|
|
/* Player has the requested bomb type available. Use it. */
|
|
return bomb;
|
|
const auto alt_bomb = static_cast<secondary_weapon_index_t>(SMART_MINE_INDEX + PROXIMITY_INDEX - bomb);
|
|
if (secondary_ammo[alt_bomb])
|
|
{
|
|
/* Player has the alternate bomb type, but not the requested
|
|
* bomb type. Switch.
|
|
*/
|
|
if constexpr (!std::is_const<T>::value)
|
|
Secondary_last_was_super ^= mask;
|
|
return alt_bomb;
|
|
}
|
|
/* Player has no bombs of either type. */
|
|
return bomb;
|
|
}
|
|
|
|
}
|
|
|
|
secondary_weapon_index_t which_bomb(const player_info &player_info)
|
|
{
|
|
return read_update_which_proximity_mine_to_use(player_info);
|
|
}
|
|
|
|
secondary_weapon_index_t which_bomb(player_info &player_info)
|
|
{
|
|
return read_update_which_proximity_mine_to_use(player_info);
|
|
}
|
|
#endif
|
|
|
|
namespace {
|
|
|
|
static void do_weapon_n_item_stuff(object_array &Objects, control_info &Controls)
|
|
{
|
|
auto &vmobjptridx = Objects.vmptridx;
|
|
auto &vmobjptr = Objects.vmptr;
|
|
auto &plrobj = get_local_plrobj();
|
|
auto &player_info = plrobj.ctype.player_info;
|
|
if (Controls.state.fire_flare > 0)
|
|
{
|
|
Controls.state.fire_flare = 0;
|
|
if (allowed_to_fire_flare(player_info))
|
|
Flare_create(vmobjptridx(ConsoleObject));
|
|
}
|
|
|
|
if (Controls.state.fire_secondary && allowed_to_fire_missile(player_info))
|
|
{
|
|
Global_missile_firing_count += Weapon_info[Secondary_weapon_to_weapon_info[player_info.Secondary_weapon]].fire_count;
|
|
}
|
|
|
|
if (Global_missile_firing_count) {
|
|
do_missile_firing(0);
|
|
Global_missile_firing_count--;
|
|
}
|
|
|
|
if (Controls.state.cycle_primary > 0)
|
|
{
|
|
for (uint_fast32_t i = std::exchange(Controls.state.cycle_primary, 0); i--;)
|
|
CyclePrimary(player_info);
|
|
}
|
|
if (Controls.state.cycle_secondary > 0)
|
|
{
|
|
for (uint_fast32_t i = std::exchange(Controls.state.cycle_secondary, 0); i--;)
|
|
CycleSecondary(player_info);
|
|
}
|
|
if (Controls.state.select_weapon > 0)
|
|
{
|
|
const auto select_weapon = std::exchange(Controls.state.select_weapon, 0) - 1;
|
|
const auto weapon_num = select_weapon > 4 ? select_weapon - 5 : select_weapon;
|
|
if (select_weapon > 4)
|
|
do_secondary_weapon_select(player_info, static_cast<secondary_weapon_index_t>(select_weapon - 5));
|
|
else
|
|
do_primary_weapon_select(player_info, static_cast<primary_weapon_index_t>(weapon_num));
|
|
}
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
if (auto &headlight = Controls.state.headlight)
|
|
{
|
|
if (std::exchange(headlight, 0) & 1)
|
|
toggle_headlight_active(plrobj);
|
|
}
|
|
#endif
|
|
|
|
if (Global_missile_firing_count < 0)
|
|
Global_missile_firing_count = 0;
|
|
|
|
// Drop proximity bombs.
|
|
if (Controls.state.drop_bomb > 0)
|
|
{
|
|
for (uint_fast32_t i = std::exchange(Controls.state.drop_bomb, 0); i--;)
|
|
do_missile_firing(1);
|
|
}
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
if (Controls.state.toggle_bomb > 0)
|
|
{
|
|
auto &Secondary_last_was_super = player_info.Secondary_last_was_super;
|
|
auto &secondary_ammo = player_info.secondary_ammo;
|
|
int sound;
|
|
if (!secondary_ammo[PROXIMITY_INDEX] && !secondary_ammo[SMART_MINE_INDEX])
|
|
{
|
|
HUD_init_message_literal(HM_DEFAULT, "No bombs available!");
|
|
sound = SOUND_BAD_SELECTION;
|
|
}
|
|
else
|
|
{
|
|
const auto mask = (1 << PROXIMITY_INDEX);
|
|
const char *desc;
|
|
const auto bomb = (Secondary_last_was_super & mask) ? (desc = "Proximity bombs", PROXIMITY_INDEX) : (desc = "Smart mines", SMART_MINE_INDEX);
|
|
if (secondary_ammo[bomb] == 0)
|
|
{
|
|
HUD_init_message(HM_DEFAULT, "No %s available!", desc);
|
|
sound = SOUND_BAD_SELECTION;
|
|
}
|
|
else
|
|
{
|
|
Secondary_last_was_super ^= mask;
|
|
sound = SOUND_GOOD_SELECTION_SECONDARY;
|
|
}
|
|
}
|
|
digi_play_sample_once(sound, F1_0);
|
|
Controls.state.toggle_bomb = 0;
|
|
}
|
|
|
|
if (Controls.state.energy_to_shield && (player_info.powerup_flags & PLAYER_FLAGS_CONVERTER))
|
|
transfer_energy_to_shield(plrobj);
|
|
#endif
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
namespace dcx {
|
|
|
|
namespace {
|
|
|
|
#ifndef RELEASE
|
|
#if DXX_USE_EDITOR
|
|
struct choose_curseg_menu_items
|
|
{
|
|
std::array<char, 40> caption;
|
|
std::array<char, sizeof("65535")> text;
|
|
std::array<newmenu_item, 2> m;
|
|
choose_curseg_menu_items(const d_level_shared_segment_state &LevelSharedSegmentState) :
|
|
m{{
|
|
newmenu_item::nm_item_text{(snprintf(caption.data(), caption.size(), "Enter target segment number (max=%u)", LevelSharedSegmentState.get_segments().get_count()), caption.data())},
|
|
newmenu_item::nm_item_input{(text.front() = 0, text)},
|
|
}}
|
|
{
|
|
}
|
|
};
|
|
|
|
struct choose_curseg_menu : choose_curseg_menu_items, passive_newmenu
|
|
{
|
|
choose_curseg_menu(const d_level_shared_segment_state &LevelSharedSegmentState, grs_canvas &src) :
|
|
choose_curseg_menu_items(LevelSharedSegmentState),
|
|
passive_newmenu(menu_title{nullptr}, menu_subtitle{nullptr}, menu_filename{nullptr}, tiny_mode_flag::normal, tab_processing_flag::ignore, adjusted_citem::create(m, 0), src)
|
|
{
|
|
}
|
|
virtual window_event_result event_handler(const d_event &) override;
|
|
};
|
|
|
|
window_event_result choose_curseg_menu::event_handler(const d_event &event)
|
|
{
|
|
switch (event.type)
|
|
{
|
|
case EVENT_WINDOW_CLOSE:
|
|
{
|
|
char *p;
|
|
const auto s = strtoul(text.data(), &p, 0);
|
|
if (*p)
|
|
break;
|
|
if (s >= Segments.get_count())
|
|
break;
|
|
Cursegp = Segments.vmptridx(static_cast<segnum_t>(s));
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
return newmenu::event_handler(event);
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
|
|
namespace dsx {
|
|
|
|
namespace {
|
|
|
|
//Process selected keys until game unpaused
|
|
window_event_result pause_window::event_handler(const d_event &event)
|
|
{
|
|
switch (event.type)
|
|
{
|
|
case EVENT_WINDOW_ACTIVATED:
|
|
game_flush_inputs(Controls);
|
|
break;
|
|
|
|
case EVENT_KEY_COMMAND:
|
|
switch (event_key_get(event))
|
|
{
|
|
case 0:
|
|
break;
|
|
case KEY_ESC:
|
|
return window_event_result::close;
|
|
case KEY_F1:
|
|
show_help();
|
|
return window_event_result::handled;
|
|
case KEY_PAUSE:
|
|
return window_event_result::close;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case EVENT_IDLE:
|
|
timer_delay2(50);
|
|
break;
|
|
|
|
case EVENT_WINDOW_DRAW:
|
|
gr_set_default_canvas();
|
|
show_boxed_message(*grd_curcanv, msg.data());
|
|
break;
|
|
|
|
case EVENT_WINDOW_CLOSE:
|
|
songs_resume();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return window_event_result::ignored;
|
|
}
|
|
|
|
static void do_game_pause()
|
|
{
|
|
auto &Objects = LevelUniqueObjectState.Objects;
|
|
auto &vcobjptr = Objects.vcptr;
|
|
|
|
if (Game_mode & GM_MULTI)
|
|
{
|
|
netplayerinfo_on= !netplayerinfo_on;
|
|
return;
|
|
}
|
|
|
|
auto p = window_create<pause_window>(grd_curscreen->sc_canvas, 0, 0, SWIDTH, SHEIGHT);
|
|
songs_pause();
|
|
|
|
auto &plr = get_local_player();
|
|
struct hms_time
|
|
{
|
|
unsigned h, m, s;
|
|
hms_time(const unsigned hours_extra, const int d1_rem, const std::div_t d2) :
|
|
h(d2.quot + hours_extra), m(d2.rem), s(d1_rem)
|
|
{
|
|
}
|
|
hms_time(const unsigned hours_extra, const std::div_t d1) :
|
|
hms_time(hours_extra, d1.rem, std::div(d1.quot, 60))
|
|
{
|
|
}
|
|
hms_time(const unsigned hours_extra, const fix seconds) :
|
|
hms_time(hours_extra, std::div(f2i(seconds), 60))
|
|
{
|
|
}
|
|
};
|
|
auto &player_info = vcobjptr(plr.objnum)->ctype.player_info;
|
|
if (Newdemo_state!=ND_STATE_PLAYBACK)
|
|
{
|
|
const hms_time human_time_total{plr.hours_total, plr.time_total}, human_time_level{plr.hours_level, plr.time_level};
|
|
snprintf(&p->msg[0], p->msg.size(), "PAUSE\n\n"
|
|
"Skill level: %s\n"
|
|
"Hostages on board: %d\n"
|
|
"Time on level: %1u:%02u:%02u\n"
|
|
"Total time in game: %1u:%02u:%02u",
|
|
MENU_DIFFICULTY_TEXT(GameUniqueState.Difficulty_level), player_info.mission.hostages_on_board, human_time_level.h, human_time_level.m, human_time_level.s, human_time_total.h, human_time_total.m, human_time_total.s);
|
|
}
|
|
else
|
|
snprintf(&p->msg[0], p->msg.size(), "PAUSE\n\n\n\n");
|
|
set_screen_mode(SCREEN_MENU);
|
|
|
|
// Keycode returning ripped out (kreatordxx)
|
|
}
|
|
|
|
static window_event_result HandleEndlevelKey(int key)
|
|
{
|
|
switch (key)
|
|
{
|
|
case KEY_COMMAND+KEY_P:
|
|
case KEY_PAUSE:
|
|
do_game_pause();
|
|
return window_event_result::handled;
|
|
|
|
case KEY_ESC:
|
|
last_drawn_cockpit = cockpit_mode_t{UINT8_MAX};
|
|
return stop_endlevel_sequence();
|
|
}
|
|
|
|
return window_event_result::ignored;
|
|
}
|
|
|
|
static int HandleDeathInput(const d_event &event, control_info &Controls)
|
|
{
|
|
const auto input_aborts_death_sequence = [&]() {
|
|
const auto RespawnMode = PlayerCfg.RespawnMode;
|
|
if (event.type == EVENT_KEY_COMMAND)
|
|
{
|
|
const auto key = event_key_get(event);
|
|
if ((RespawnMode == RespawnPress::Any && !key_isfunc(key) && key != KEY_PAUSE && key) ||
|
|
(key == KEY_ESC && ConsoleObject->flags & OF_EXPLODING))
|
|
return 1;
|
|
}
|
|
|
|
if (RespawnMode == RespawnPress::Any
|
|
? (
|
|
#if DXX_MAX_BUTTONS_PER_JOYSTICK
|
|
event.type == EVENT_JOYSTICK_BUTTON_UP ||
|
|
#endif
|
|
event.type == EVENT_MOUSE_BUTTON_UP)
|
|
: (Controls.state.fire_primary || Controls.state.fire_secondary || Controls.state.fire_flare))
|
|
return 1;
|
|
return 0;
|
|
};
|
|
if (Player_dead_state == player_dead_state::exploded && input_aborts_death_sequence())
|
|
{
|
|
GameViewUniqueState.Death_sequence_aborted = 1;
|
|
}
|
|
|
|
if (GameViewUniqueState.Death_sequence_aborted)
|
|
{
|
|
game_flush_respawn_inputs(Controls);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if DXX_USE_SCREENSHOT
|
|
static void save_pr_screenshot()
|
|
{
|
|
gr_set_default_canvas();
|
|
auto &canvas = *grd_curcanv;
|
|
{
|
|
window_rendered_data window;
|
|
render_frame(canvas, 0, window);
|
|
}
|
|
auto &medium2_font = *MEDIUM2_FONT;
|
|
gr_string(canvas, medium2_font, SWIDTH - FSPACX(92), SHEIGHT - LINE_SPACING(medium2_font, *GAME_FONT), "DXX-Rebirth\n");
|
|
gr_flip();
|
|
save_screen_shot(0);
|
|
}
|
|
|
|
static void save_clean_screenshot()
|
|
{
|
|
game_render_frame_mono(CGameArg.DbgNoDoubleBuffer, Controls);
|
|
save_screen_shot(0);
|
|
}
|
|
#endif
|
|
|
|
static window_event_result HandleDemoKey(int key)
|
|
{
|
|
switch (key) {
|
|
KEY_MAC(case KEY_COMMAND+KEY_1:)
|
|
case KEY_F1: show_newdemo_help(); break;
|
|
KEY_MAC(case KEY_COMMAND+KEY_2:)
|
|
case KEY_F2: do_options_menu(); break;
|
|
KEY_MAC(case KEY_COMMAND+KEY_3:)
|
|
case KEY_F3:
|
|
if (Viewer->type == OBJ_PLAYER)
|
|
toggle_cockpit();
|
|
break;
|
|
KEY_MAC(case KEY_COMMAND+KEY_4:)
|
|
case KEY_F4: Newdemo_show_percentage = !Newdemo_show_percentage; break;
|
|
KEY_MAC(case KEY_COMMAND+KEY_7:)
|
|
case KEY_F7:
|
|
Show_kill_list = static_cast<show_kill_list_mode>((underlying_value(Show_kill_list) + 1) % ((Newdemo_game_mode & GM_TEAM) ? 4 : 3));
|
|
break;
|
|
case KEY_ESC:
|
|
if (CGameArg.SysAutoDemo)
|
|
{
|
|
int choice;
|
|
choice = nm_messagebox_str(menu_title{nullptr}, nm_messagebox_tie(TXT_YES, TXT_NO), menu_subtitle{TXT_ABORT_AUTODEMO});
|
|
if (choice == 0)
|
|
CGameArg.SysAutoDemo = false;
|
|
else
|
|
break;
|
|
}
|
|
newdemo_stop_playback();
|
|
return window_event_result::close;
|
|
break;
|
|
case KEY_UP:
|
|
Newdemo_vcr_state = ND_STATE_PLAYBACK;
|
|
break;
|
|
case KEY_DOWN:
|
|
Newdemo_vcr_state = ND_STATE_PAUSED;
|
|
break;
|
|
case KEY_LEFT:
|
|
newdemo_single_frame_time = GameTime64;
|
|
Newdemo_vcr_state = ND_STATE_ONEFRAMEBACKWARD;
|
|
break;
|
|
case KEY_RIGHT:
|
|
newdemo_single_frame_time = GameTime64;
|
|
Newdemo_vcr_state = ND_STATE_ONEFRAMEFORWARD;
|
|
break;
|
|
case KEY_CTRLED + KEY_RIGHT:
|
|
return newdemo_goto_end(0);
|
|
break;
|
|
case KEY_CTRLED + KEY_LEFT:
|
|
return newdemo_goto_beginning();
|
|
break;
|
|
|
|
KEY_MAC(case KEY_COMMAND+KEY_P:)
|
|
case KEY_PAUSE:
|
|
do_game_pause();
|
|
break;
|
|
|
|
#if DXX_USE_SCREENSHOT
|
|
#ifdef macintosh
|
|
case KEY_COMMAND + KEY_SHIFTED + KEY_3:
|
|
#endif
|
|
case KEY_PRINT_SCREEN:
|
|
{
|
|
if (PlayerCfg.PRShot)
|
|
{
|
|
save_pr_screenshot();
|
|
}
|
|
else
|
|
{
|
|
int old_state;
|
|
old_state = Newdemo_show_percentage;
|
|
Newdemo_show_percentage = 0;
|
|
save_clean_screenshot();
|
|
Newdemo_show_percentage = old_state;
|
|
}
|
|
break;
|
|
}
|
|
#endif
|
|
#ifndef NDEBUG
|
|
case KEY_DEBUGGED + KEY_I:
|
|
Newdemo_do_interpolate = !Newdemo_do_interpolate;
|
|
HUD_init_message(HM_DEFAULT, "Demo playback interpolation %s", Newdemo_do_interpolate?"ON":"OFF");
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
return window_event_result::ignored;
|
|
}
|
|
|
|
return window_event_result::handled;
|
|
}
|
|
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
//switch a cockpit window to the next function
|
|
static int select_next_window_function(const gauge_inset_window_view w)
|
|
{
|
|
auto &Objects = LevelUniqueObjectState.Objects;
|
|
auto &vmobjptridx = Objects.vmptridx;
|
|
|
|
auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
|
|
switch (PlayerCfg.Cockpit3DView[w]) {
|
|
case cockpit_3d_view::None:
|
|
PlayerCfg.Cockpit3DView[w] = cockpit_3d_view::Rear;
|
|
break;
|
|
case cockpit_3d_view::Rear:
|
|
if (find_escort(vmobjptridx, Robot_info) != object_none)
|
|
{
|
|
PlayerCfg.Cockpit3DView[w] = cockpit_3d_view::Escort;
|
|
break;
|
|
}
|
|
//if no ecort, fall through
|
|
[[fallthrough]];
|
|
case cockpit_3d_view::Escort:
|
|
Coop_view_player[w] = UINT_MAX; //force first player
|
|
[[fallthrough]];
|
|
case cockpit_3d_view::Coop:
|
|
Marker_viewer_num[w] = game_marker_index::None;
|
|
if ((Game_mode & GM_MULTI_COOP) || (Game_mode & GM_TEAM)) {
|
|
PlayerCfg.Cockpit3DView[w] = cockpit_3d_view::Coop;
|
|
for (;;)
|
|
{
|
|
const auto cvp = ++ Coop_view_player[w];
|
|
if (cvp == MAX_PLAYERS - 1)
|
|
{
|
|
PlayerCfg.Cockpit3DView[w] = cockpit_3d_view::Marker;
|
|
goto case_marker;
|
|
}
|
|
if (cvp == Player_num)
|
|
continue;
|
|
if (vcplayerptr(cvp)->connected != CONNECT_PLAYING)
|
|
continue;
|
|
|
|
if (Game_mode & GM_MULTI_COOP)
|
|
break;
|
|
else if (get_team(cvp) == get_team(Player_num))
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
//if not multi,
|
|
[[fallthrough]];
|
|
case cockpit_3d_view::Marker:
|
|
case_marker:;
|
|
if ((Game_mode & GM_MULTI) && !(Game_mode & GM_MULTI_COOP) && Netgame.Allow_marker_view) { //anarchy only
|
|
PlayerCfg.Cockpit3DView[w] = cockpit_3d_view::Marker;
|
|
auto &mvn = Marker_viewer_num[w];
|
|
const auto gmi0 = convert_player_marker_index_to_game_marker_index(Game_mode, Netgame.max_numplayers, Player_num, player_marker_index::_0);
|
|
if (!MarkerState.imobjidx.valid_index(mvn))
|
|
mvn = gmi0;
|
|
else
|
|
{
|
|
++ mvn;
|
|
if (!MarkerState.imobjidx.valid_index(mvn))
|
|
PlayerCfg.Cockpit3DView[w] = cockpit_3d_view::None;
|
|
}
|
|
}
|
|
else
|
|
PlayerCfg.Cockpit3DView[w] = cockpit_3d_view::None;
|
|
break;
|
|
}
|
|
write_player_file();
|
|
|
|
return 1; //screen_changed
|
|
}
|
|
#endif
|
|
|
|
//this is for system-level keys, such as help, etc.
|
|
//returns 1 if screen changed
|
|
static window_event_result HandleSystemKey(int key)
|
|
{
|
|
if (Player_dead_state == player_dead_state::no)
|
|
switch (key)
|
|
{
|
|
case KEY_ESC:
|
|
{
|
|
const bool allow_saveload = !(Game_mode & GM_MULTI) || ((Game_mode & GM_MULTI_COOP) && Player_num == 0);
|
|
const auto choice = nm_messagebox_str(menu_title{nullptr}, allow_saveload ? nm_messagebox_tie("Abort Game", TXT_OPTIONS_, "Save Game...", TXT_LOAD_GAME) : nm_messagebox_tie("Abort Game", TXT_OPTIONS_), menu_subtitle{"Game Menu"});
|
|
switch(choice)
|
|
{
|
|
case 0:
|
|
return window_event_result::close;
|
|
case 1:
|
|
return HandleSystemKey(KEY_F2);
|
|
case 2:
|
|
return HandleSystemKey(KEY_ALTED | KEY_F2);
|
|
case 3:
|
|
return HandleSystemKey(KEY_ALTED | KEY_F3);
|
|
}
|
|
return window_event_result::handled;
|
|
}
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
// fleshed these out because F1 and F2 aren't sequenctial keycodes on mac -- MWA
|
|
|
|
KEY_MAC(case KEY_COMMAND+KEY_SHIFTED+KEY_1:)
|
|
case KEY_SHIFTED+KEY_F1:
|
|
select_next_window_function(gauge_inset_window_view::primary);
|
|
return window_event_result::handled;
|
|
KEY_MAC(case KEY_COMMAND+KEY_SHIFTED+KEY_2:)
|
|
case KEY_SHIFTED+KEY_F2:
|
|
select_next_window_function(gauge_inset_window_view::secondary);
|
|
return window_event_result::handled;
|
|
#endif
|
|
}
|
|
|
|
switch (key)
|
|
{
|
|
KEY_MAC( case KEY_COMMAND+KEY_PAUSE+KEY_SHIFTED: )
|
|
case KEY_PAUSE+KEY_SHIFTED:
|
|
if (Game_mode & GM_MULTI)
|
|
show_netgame_info(Netgame);
|
|
break;
|
|
KEY_MAC( case KEY_COMMAND+KEY_P: )
|
|
case KEY_PAUSE:
|
|
do_game_pause();
|
|
break;
|
|
|
|
|
|
#if DXX_USE_SCREENSHOT
|
|
#ifdef macintosh
|
|
case KEY_COMMAND + KEY_SHIFTED + KEY_3:
|
|
#endif
|
|
case KEY_PRINT_SCREEN:
|
|
{
|
|
if (PlayerCfg.PRShot)
|
|
{
|
|
save_pr_screenshot();
|
|
}
|
|
else
|
|
{
|
|
save_clean_screenshot();
|
|
}
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
KEY_MAC(case KEY_COMMAND+KEY_1:)
|
|
case KEY_F1: if (Game_mode & GM_MULTI) show_netgame_help(); else show_help(); break;
|
|
|
|
KEY_MAC(case KEY_COMMAND+KEY_2:)
|
|
case KEY_F2:
|
|
{
|
|
do_options_menu();
|
|
break;
|
|
}
|
|
|
|
|
|
KEY_MAC(case KEY_COMMAND+KEY_3:)
|
|
|
|
case KEY_F3:
|
|
if (Player_dead_state == player_dead_state::no && Viewer->type == OBJ_PLAYER)
|
|
{
|
|
toggle_cockpit();
|
|
}
|
|
break;
|
|
|
|
KEY_MAC(case KEY_COMMAND+KEY_5:)
|
|
case KEY_F5:
|
|
if ( Newdemo_state == ND_STATE_RECORDING )
|
|
newdemo_stop_recording();
|
|
else if ( Newdemo_state == ND_STATE_NORMAL )
|
|
newdemo_start_recording();
|
|
break;
|
|
KEY_MAC(case KEY_COMMAND+KEY_ALTED+KEY_4:)
|
|
case KEY_ALTED + KEY_F4:
|
|
Show_reticle_name = (Show_reticle_name+1)%2;
|
|
break;
|
|
|
|
KEY_MAC(case KEY_COMMAND+KEY_7:)
|
|
case KEY_F7:
|
|
Show_kill_list = static_cast<show_kill_list_mode>((underlying_value(Show_kill_list) + 1) % ((Game_mode & GM_TEAM) ? 4 : 3));
|
|
if (Game_mode & GM_MULTI)
|
|
multi_sort_kill_list();
|
|
break;
|
|
|
|
KEY_MAC(case KEY_COMMAND+KEY_8:)
|
|
case KEY_F8:
|
|
multi_send_message_start();
|
|
break;
|
|
|
|
case KEY_F9:
|
|
case KEY_F10:
|
|
case KEY_F11:
|
|
case KEY_F12:
|
|
multi_send_macro(key);
|
|
break; // send taunt macros
|
|
|
|
#if defined(__APPLE__) || defined(macintosh)
|
|
case KEY_9 + KEY_COMMAND:
|
|
multi_send_macro(KEY_F9);
|
|
break;
|
|
case KEY_0 + KEY_COMMAND:
|
|
multi_send_macro(KEY_F10);
|
|
break;
|
|
case KEY_1 + KEY_COMMAND + KEY_CTRLED:
|
|
multi_send_macro(KEY_F11);
|
|
break;
|
|
case KEY_2 + KEY_COMMAND + KEY_CTRLED:
|
|
multi_send_macro(KEY_F12);
|
|
break;
|
|
#endif
|
|
|
|
case KEY_SHIFTED + KEY_F9:
|
|
case KEY_SHIFTED + KEY_F10:
|
|
case KEY_SHIFTED + KEY_F11:
|
|
case KEY_SHIFTED + KEY_F12:
|
|
multi_define_macro(key);
|
|
break; // redefine taunt macros
|
|
|
|
#if defined(__APPLE__) || defined(macintosh)
|
|
case KEY_9 + KEY_SHIFTED + KEY_COMMAND:
|
|
multi_define_macro(KEY_F9);
|
|
break;
|
|
case KEY_0 + KEY_SHIFTED + KEY_COMMAND:
|
|
multi_define_macro(KEY_F10);
|
|
break;
|
|
case KEY_1 + KEY_SHIFTED + KEY_COMMAND + KEY_CTRLED:
|
|
multi_define_macro(KEY_F11);
|
|
break;
|
|
case KEY_2 + KEY_SHIFTED + KEY_COMMAND + KEY_CTRLED:
|
|
multi_define_macro(KEY_F12);
|
|
break;
|
|
#endif
|
|
|
|
|
|
KEY_MAC(case KEY_COMMAND+KEY_SHIFTED+KEY_S:)
|
|
KEY_MAC(case KEY_COMMAND+KEY_ALTED+KEY_2:)
|
|
case KEY_ALTED+KEY_F2:
|
|
if (Player_dead_state == player_dead_state::no)
|
|
state_save_all(secret_save::none, blind_save::no); // 0 means not between levels.
|
|
break;
|
|
|
|
KEY_MAC(case KEY_COMMAND+KEY_S:)
|
|
case KEY_ALTED+KEY_SHIFTED+KEY_F2:
|
|
if (Player_dead_state == player_dead_state::no)
|
|
state_save_all(secret_save::none, blind_save::yes);
|
|
break;
|
|
KEY_MAC(case KEY_COMMAND+KEY_SHIFTED+KEY_O:)
|
|
KEY_MAC(case KEY_COMMAND+KEY_ALTED+KEY_3:)
|
|
case KEY_ALTED+KEY_F3:
|
|
if (!((Game_mode & GM_MULTI) && !(Game_mode & GM_MULTI_COOP)))
|
|
state_restore_all(1, secret_restore::none, nullptr, blind_save::no);
|
|
break;
|
|
KEY_MAC(case KEY_COMMAND+KEY_O:)
|
|
case KEY_ALTED+KEY_SHIFTED+KEY_F3:
|
|
if (!((Game_mode & GM_MULTI) && !(Game_mode & GM_MULTI_COOP)))
|
|
state_restore_all(1, secret_restore::none, nullptr, blind_save::yes);
|
|
break;
|
|
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
KEY_MAC(case KEY_COMMAND+KEY_SHIFTED+KEY_4:)
|
|
case KEY_F4 + KEY_SHIFTED:
|
|
do_escort_menu();
|
|
break;
|
|
|
|
|
|
KEY_MAC(case KEY_COMMAND+KEY_SHIFTED+KEY_ALTED+KEY_4:)
|
|
case KEY_F4 + KEY_SHIFTED + KEY_ALTED:
|
|
change_guidebot_name();
|
|
break;
|
|
#endif
|
|
|
|
#if DXX_USE_OGL
|
|
#if 0
|
|
/* These conflict with the drop-primary and drop-secondary
|
|
* keybindings. Dropping items is more common than using VR, so
|
|
* disable these.
|
|
*/
|
|
case KEY_SHIFTED + KEY_F5:
|
|
VR_eye_offset -= 1;
|
|
reset_cockpit();
|
|
break;
|
|
case KEY_SHIFTED + KEY_F6:
|
|
VR_eye_offset += 1;
|
|
reset_cockpit();
|
|
break;
|
|
#endif
|
|
case KEY_SHIFTED + KEY_ALTED + KEY_F5:
|
|
VR_eye_width -= (F1_0/10); //*= 10/11;
|
|
reset_cockpit();
|
|
break;
|
|
case KEY_SHIFTED + KEY_ALTED + KEY_F6:
|
|
VR_eye_width += (F1_0/10); //*= 11/10;
|
|
reset_cockpit();
|
|
break;
|
|
case KEY_SHIFTED + KEY_F7:
|
|
case KEY_SHIFTED + KEY_ALTED + KEY_F7:
|
|
VR_eye_width = F1_0;
|
|
VR_eye_offset = 0;
|
|
reset_cockpit();
|
|
break;
|
|
case KEY_SHIFTED + KEY_F8:
|
|
case KEY_SHIFTED + KEY_ALTED + KEY_F8:
|
|
VR_stereo = static_cast<StereoFormat>((static_cast<unsigned>(VR_stereo) + 1) % (static_cast<unsigned>(StereoFormat::HighestFormat) + 1));
|
|
init_stereo();
|
|
reset_cockpit();
|
|
break;
|
|
#endif
|
|
|
|
/*
|
|
* Jukebox hotkeys -- MD2211, 2007
|
|
* Now for all music
|
|
* ==============================================
|
|
*/
|
|
case KEY_ALTED + KEY_SHIFTED + KEY_F9:
|
|
KEY_MAC(case KEY_COMMAND+KEY_E:)
|
|
#if DXX_USE_SDL_REDBOOK_AUDIO
|
|
if (GameCfg.MusicType == MUSIC_TYPE_REDBOOK)
|
|
{
|
|
songs_stop_all();
|
|
RBAEjectDisk();
|
|
}
|
|
#endif
|
|
break;
|
|
|
|
case KEY_ALTED + KEY_SHIFTED + KEY_F10:
|
|
KEY_MAC(case KEY_COMMAND+KEY_UP:)
|
|
KEY_MAC(case KEY_COMMAND+KEY_DOWN:)
|
|
songs_pause_resume();
|
|
break;
|
|
|
|
case KEY_MINUS + KEY_ALTED:
|
|
case KEY_ALTED + KEY_SHIFTED + KEY_F11:
|
|
KEY_MAC(case KEY_COMMAND+KEY_LEFT:)
|
|
songs_play_level_song( Current_level_num, -1 );
|
|
break;
|
|
case KEY_EQUAL + KEY_ALTED:
|
|
case KEY_ALTED + KEY_SHIFTED + KEY_F12:
|
|
KEY_MAC(case KEY_COMMAND+KEY_RIGHT:)
|
|
songs_play_level_song( Current_level_num, 1 );
|
|
break;
|
|
|
|
default:
|
|
return window_event_result::ignored;
|
|
}
|
|
return window_event_result::handled;
|
|
}
|
|
|
|
static window_event_result HandleGameKey(int key, control_info &Controls)
|
|
{
|
|
auto &Objects = LevelUniqueObjectState.Objects;
|
|
auto &vmobjptr = Objects.vmptr;
|
|
switch (key) {
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
case KEY_1 + KEY_SHIFTED:
|
|
case KEY_2 + KEY_SHIFTED:
|
|
case KEY_3 + KEY_SHIFTED:
|
|
case KEY_4 + KEY_SHIFTED:
|
|
case KEY_5 + KEY_SHIFTED:
|
|
case KEY_6 + KEY_SHIFTED:
|
|
case KEY_7 + KEY_SHIFTED:
|
|
case KEY_8 + KEY_SHIFTED:
|
|
case KEY_9 + KEY_SHIFTED:
|
|
case KEY_0 + KEY_SHIFTED:
|
|
if (PlayerCfg.EscortHotKeys)
|
|
{
|
|
auto &BuddyState = LevelUniqueObjectState.BuddyState;
|
|
if (Game_mode & GM_MULTI)
|
|
{
|
|
if (!check_warn_local_player_can_control_guidebot(Objects.vcptr, BuddyState, Netgame))
|
|
return window_event_result::handled;
|
|
}
|
|
set_escort_special_goal(BuddyState, key);
|
|
game_flush_inputs(Controls);
|
|
return window_event_result::handled;
|
|
}
|
|
else
|
|
return window_event_result::ignored;
|
|
#endif
|
|
|
|
case KEY_ALTED+KEY_F7:
|
|
KEY_MAC(case KEY_COMMAND+KEY_ALTED+KEY_7:)
|
|
PlayerCfg.HudMode = static_cast<HudType>((static_cast<unsigned>(PlayerCfg.HudMode) + 1) % GAUGE_HUD_NUMMODES);
|
|
write_player_file();
|
|
switch (PlayerCfg.HudMode)
|
|
{
|
|
case HudType::Standard:
|
|
HUD_init_message_literal(HM_DEFAULT, "Standard HUD");
|
|
break;
|
|
case HudType::Alternate1:
|
|
HUD_init_message_literal(HM_DEFAULT, "Alternative HUD #1");
|
|
break;
|
|
case HudType::Alternate2:
|
|
HUD_init_message_literal(HM_DEFAULT, "Alternative HUD #2");
|
|
break;
|
|
case HudType::Hidden:
|
|
HUD_init_message_literal(HM_DEFAULT, "No HUD");
|
|
break;
|
|
}
|
|
return window_event_result::handled;
|
|
|
|
KEY_MAC(case KEY_COMMAND+KEY_6:)
|
|
case KEY_F6:
|
|
if (Netgame.RefusePlayers && WaitForRefuseAnswer && !(Game_mode & GM_TEAM))
|
|
{
|
|
RefuseThisPlayer=1;
|
|
HUD_init_message_literal(HM_MULTI, "Player accepted!");
|
|
}
|
|
return window_event_result::handled;
|
|
case KEY_ALTED + KEY_1:
|
|
if (Netgame.RefusePlayers && WaitForRefuseAnswer && (Game_mode & GM_TEAM))
|
|
{
|
|
RefuseThisPlayer=1;
|
|
HUD_init_message_literal(HM_MULTI, "Player accepted!");
|
|
RefuseTeam=1;
|
|
game_flush_inputs(Controls);
|
|
}
|
|
return window_event_result::handled;
|
|
case KEY_ALTED + KEY_2:
|
|
if (Netgame.RefusePlayers && WaitForRefuseAnswer && (Game_mode & GM_TEAM))
|
|
{
|
|
RefuseThisPlayer=1;
|
|
HUD_init_message_literal(HM_MULTI, "Player accepted!");
|
|
RefuseTeam=2;
|
|
game_flush_inputs(Controls);
|
|
}
|
|
return window_event_result::handled;
|
|
|
|
default:
|
|
break;
|
|
|
|
} //switch (key)
|
|
|
|
if (Player_dead_state == player_dead_state::no)
|
|
switch (key)
|
|
{
|
|
KEY_MAC(case KEY_COMMAND+KEY_SHIFTED+KEY_5:)
|
|
case KEY_F5 + KEY_SHIFTED:
|
|
DropCurrentWeapon(get_local_plrobj().ctype.player_info);
|
|
break;
|
|
|
|
KEY_MAC(case KEY_COMMAND+KEY_SHIFTED+KEY_6:)
|
|
case KEY_F6 + KEY_SHIFTED:
|
|
DropSecondaryWeapon(get_local_plrobj().ctype.player_info);
|
|
break;
|
|
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
case KEY_0 + KEY_ALTED:
|
|
DropFlag ();
|
|
game_flush_inputs(Controls);
|
|
break;
|
|
|
|
KEY_MAC(case KEY_COMMAND+KEY_4:)
|
|
case KEY_F4:
|
|
if (!MarkerState.DefiningMarkerMessage())
|
|
InitMarkerInput();
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
return window_event_result::ignored;
|
|
}
|
|
else
|
|
return window_event_result::ignored;
|
|
|
|
return window_event_result::handled;
|
|
}
|
|
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
static void kill_all_robots(void)
|
|
{
|
|
auto &Objects = LevelUniqueObjectState.Objects;
|
|
auto &vmobjptr = Objects.vmptr;
|
|
int dead_count=0;
|
|
//int boss_index = -1;
|
|
auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
|
|
|
|
// Kill all bots except for Buddy bot and boss. However, if only boss and buddy left, kill boss.
|
|
range_for (const auto &&objp, vmobjptr)
|
|
{
|
|
if (objp->type == OBJ_ROBOT)
|
|
{
|
|
if (!Robot_info[get_robot_id(objp)].companion && !Robot_info[get_robot_id(objp)].boss_flag) {
|
|
dead_count++;
|
|
objp->flags |= OF_EXPLODING|OF_SHOULD_BE_DEAD;
|
|
}
|
|
}
|
|
}
|
|
|
|
// -- // Now, if more than boss and buddy left, un-kill boss.
|
|
// -- if ((dead_count > 2) && (boss_index != -1)) {
|
|
// -- Objects[boss_index].flags &= ~(OF_EXPLODING|OF_SHOULD_BE_DEAD);
|
|
// -- dead_count--;
|
|
// -- } else if (boss_index != -1)
|
|
// -- HUD_init_message(HM_DEFAULT, "Toasted the BOSS!");
|
|
|
|
// Toast the buddy if nothing else toasted!
|
|
if (dead_count == 0)
|
|
range_for (const auto &&objp, vmobjptr)
|
|
{
|
|
if (objp->type == OBJ_ROBOT)
|
|
if (Robot_info[get_robot_id(objp)].companion) {
|
|
objp->flags |= OF_EXPLODING|OF_SHOULD_BE_DEAD;
|
|
HUD_init_message_literal(HM_DEFAULT, "Toasted the Buddy! *sniff*");
|
|
dead_count++;
|
|
}
|
|
}
|
|
|
|
HUD_init_message(HM_DEFAULT, "%i robots toasted!", dead_count);
|
|
}
|
|
#endif
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Detonate reactor.
|
|
// Award player all powerups in mine.
|
|
// Place player just outside exit.
|
|
// Kill all bots in mine.
|
|
// Yippee!!
|
|
static void kill_and_so_forth(fvmobjptridx &vmobjptridx, fvmsegptridx &vmsegptridx)
|
|
{
|
|
auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
|
|
auto &Objects = LevelUniqueObjectState.Objects;
|
|
auto &Vertices = LevelSharedVertexState.get_vertices();
|
|
auto &vmobjptr = Objects.vmptr;
|
|
HUD_init_message_literal(HM_DEFAULT, "Killing, awarding, etc.!");
|
|
|
|
range_for (const auto &&o, vmobjptridx)
|
|
{
|
|
switch (o->type) {
|
|
case OBJ_ROBOT:
|
|
apply_damage_to_robot(o, o->shields + 1, get_local_player().objnum);
|
|
break;
|
|
case OBJ_POWERUP:
|
|
do_powerup(o);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
do_controlcen_destroyed_stuff(object_none);
|
|
|
|
auto &Triggers = LevelUniqueWallSubsystemState.Triggers;
|
|
auto &Walls = LevelUniqueWallSubsystemState.Walls;
|
|
auto &vcwallptr = Walls.vcptr;
|
|
for (const auto &&t : Triggers.vcptridx)
|
|
{
|
|
if (trigger_is_exit(t))
|
|
{
|
|
range_for (const auto &&wp, vcwallptr)
|
|
{
|
|
auto &w = *wp;
|
|
if (w.trigger == t)
|
|
{
|
|
const auto &&segp = vmsegptridx(w.segnum);
|
|
auto &vcvertptr = Vertices.vcptr;
|
|
compute_segment_center(vcvertptr, ConsoleObject->pos, segp);
|
|
obj_relink(vmobjptr, vmsegptr, vmobjptridx(ConsoleObject), segp);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifndef RELEASE
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
static void kill_all_snipers(void) __attribute_used;
|
|
static void kill_all_snipers(void)
|
|
{
|
|
auto &Objects = LevelUniqueObjectState.Objects;
|
|
auto &vmobjptr = Objects.vmptr;
|
|
int dead_count=0;
|
|
|
|
// Kill all snipers.
|
|
range_for (const auto &&objp, vmobjptr)
|
|
{
|
|
if (objp->type == OBJ_ROBOT)
|
|
if (objp->ctype.ai_info.behavior == ai_behavior::AIB_SNIPE)
|
|
{
|
|
dead_count++;
|
|
objp->flags |= OF_EXPLODING|OF_SHOULD_BE_DEAD;
|
|
}
|
|
}
|
|
|
|
HUD_init_message(HM_DEFAULT, "%i robots toasted!", dead_count);
|
|
}
|
|
|
|
static void kill_thief(void) __attribute_used;
|
|
static void kill_thief(void)
|
|
{
|
|
auto &Objects = LevelUniqueObjectState.Objects;
|
|
auto &vmobjptr = Objects.vmptr;
|
|
auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
|
|
// Kill thief.
|
|
range_for (const auto &&objp, vmobjptr)
|
|
{
|
|
if (objp->type == OBJ_ROBOT)
|
|
if (Robot_info[get_robot_id(objp)].thief)
|
|
{
|
|
objp->flags |= OF_EXPLODING|OF_SHOULD_BE_DEAD;
|
|
HUD_init_message_literal(HM_DEFAULT, "Thief toasted!");
|
|
}
|
|
}
|
|
}
|
|
|
|
static void kill_buddy(void) __attribute_used;
|
|
static void kill_buddy(void)
|
|
{
|
|
auto &Objects = LevelUniqueObjectState.Objects;
|
|
auto &vmobjptr = Objects.vmptr;
|
|
auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
|
|
// Kill buddy.
|
|
range_for (const auto &&objp, vmobjptr)
|
|
{
|
|
if (objp->type == OBJ_ROBOT)
|
|
if (Robot_info[get_robot_id(objp)].companion)
|
|
{
|
|
objp->flags |= OF_EXPLODING|OF_SHOULD_BE_DEAD;
|
|
HUD_init_message_literal(HM_DEFAULT, "Buddy toasted!");
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static window_event_result HandleTestKey(fvmsegptridx &vmsegptridx, int key, control_info &Controls)
|
|
{
|
|
auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
|
|
auto &LevelUniqueMorphObjectState = LevelUniqueObjectState.MorphObjectState;
|
|
auto &Objects = LevelUniqueObjectState.Objects;
|
|
auto &Vertices = LevelSharedVertexState.get_vertices();
|
|
auto &vmobjptr = Objects.vmptr;
|
|
auto &vmobjptridx = Objects.vmptridx;
|
|
switch (key)
|
|
{
|
|
|
|
#ifdef SHOW_EXIT_PATH
|
|
case KEY_DEBUGGED+KEY_1: create_special_path(); break;
|
|
#endif
|
|
|
|
case KEY_DEBUGGED+KEY_Y:
|
|
do_controlcen_destroyed_stuff(object_none);
|
|
break;
|
|
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
case KEY_DEBUGGED+KEY_ALTED+KEY_D:
|
|
PlayerCfg.NetlifeKills=4000; PlayerCfg.NetlifeKilled=5;
|
|
multi_add_lifetime_kills(1);
|
|
break;
|
|
|
|
case KEY_DEBUGGED+KEY_R+KEY_SHIFTED:
|
|
kill_all_robots();
|
|
break;
|
|
#endif
|
|
|
|
case KEY_BACKSP:
|
|
case KEY_CTRLED+KEY_BACKSP:
|
|
case KEY_ALTED+KEY_BACKSP:
|
|
case KEY_SHIFTED+KEY_BACKSP:
|
|
case KEY_SHIFTED+KEY_ALTED+KEY_BACKSP:
|
|
case KEY_CTRLED+KEY_ALTED+KEY_BACKSP:
|
|
case KEY_SHIFTED+KEY_CTRLED+KEY_BACKSP:
|
|
case KEY_SHIFTED+KEY_CTRLED+KEY_ALTED+KEY_BACKSP:
|
|
|
|
Int3(); break;
|
|
|
|
case KEY_DEBUGGED+KEY_P:
|
|
if (Game_suspended & SUSP_ROBOTS)
|
|
Game_suspended &= ~SUSP_ROBOTS; //robots move
|
|
else
|
|
Game_suspended |= SUSP_ROBOTS; //robots don't move
|
|
break;
|
|
case KEY_DEBUGGED+KEY_M:
|
|
{
|
|
static int i = 0;
|
|
const auto &&segp = vmsegptridx(ConsoleObject->segnum);
|
|
auto &vcvertptr = Vertices.vcptr;
|
|
const auto &&new_obj = create_morph_robot(segp, compute_segment_center(vcvertptr, segp), i);
|
|
if (new_obj != object_none)
|
|
morph_start(LevelUniqueMorphObjectState, LevelSharedPolygonModelState, new_obj);
|
|
i++;
|
|
if (i >= LevelSharedRobotInfoState.N_robot_types)
|
|
i = 0;
|
|
break;
|
|
}
|
|
case KEY_DEBUGGED+KEY_K:
|
|
get_local_plrobj().shields = 1;
|
|
break; // a virtual kill
|
|
case KEY_DEBUGGED+KEY_SHIFTED + KEY_K:
|
|
get_local_plrobj().shields = -1;
|
|
break; // an actual kill
|
|
case KEY_DEBUGGED+KEY_X: get_local_player().lives++; break; // Extra life cheat key.
|
|
case KEY_DEBUGGED+KEY_H:
|
|
{
|
|
if (Player_dead_state != player_dead_state::no)
|
|
return window_event_result::ignored;
|
|
|
|
auto &player_info = get_local_plrobj().ctype.player_info;
|
|
auto &pl_flags = player_info.powerup_flags;
|
|
pl_flags ^= PLAYER_FLAGS_CLOAKED;
|
|
if (pl_flags & PLAYER_FLAGS_CLOAKED) {
|
|
if (Game_mode & GM_MULTI)
|
|
multi_send_cloak();
|
|
ai_do_cloak_stuff();
|
|
player_info.cloak_time = GameTime64;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case KEY_DEBUGGED+KEY_R:
|
|
cheats.robotfiringsuspended = !cheats.robotfiringsuspended;
|
|
break;
|
|
|
|
#if DXX_USE_EDITOR //editor-specific functions
|
|
|
|
case KEY_E + KEY_DEBUGGED:
|
|
{
|
|
const auto g = Game_wind;
|
|
g->set_visible(0); // don't let the game do anything while we set the editor up
|
|
auto old_gamestate = gamestate;
|
|
gamestate = editor_gamestate::unsaved; // saved game editing mode
|
|
|
|
init_editor();
|
|
// If editor failed to load, carry on playing
|
|
if (!EditorWindow)
|
|
{
|
|
g->set_visible(1);
|
|
gamestate = old_gamestate;
|
|
return window_event_result::handled;
|
|
}
|
|
return window_event_result::close;
|
|
}
|
|
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
case KEY_Q + KEY_SHIFTED + KEY_DEBUGGED:
|
|
{
|
|
palette_array_t save_pal;
|
|
save_pal = gr_palette;
|
|
PlayMovie ("end.tex", "end.mve",MOVIE_ABORT_ON);
|
|
Screen_mode = -1;
|
|
set_screen_mode(SCREEN_GAME);
|
|
reset_cockpit();
|
|
gr_palette = save_pal;
|
|
gr_palette_load(gr_palette);
|
|
break;
|
|
}
|
|
#endif
|
|
case KEY_C + KEY_CTRLED + KEY_DEBUGGED:
|
|
window_create<choose_curseg_menu>(LevelSharedSegmentState, *grd_curcanv);
|
|
break;
|
|
case KEY_C + KEY_SHIFTED + KEY_DEBUGGED:
|
|
if (Player_dead_state == player_dead_state::no &&
|
|
!(Game_mode & GM_MULTI))
|
|
move_player_2_segment(Cursegp,Curside);
|
|
break; //move eye to curseg
|
|
|
|
|
|
case KEY_DEBUGGED+KEY_W: draw_world_from_game(); break;
|
|
|
|
#endif //#ifdef EDITOR
|
|
|
|
case KEY_DEBUGGED+KEY_LAPOSTRO: Show_view_text_timer = 0x30000; object_goto_next_viewer(); break;
|
|
case KEY_DEBUGGED+KEY_SHIFTED+KEY_LAPOSTRO: Viewer=ConsoleObject; break;
|
|
case KEY_DEBUGGED+KEY_O: toggle_outline_mode(); break;
|
|
case KEY_DEBUGGED+KEY_T:
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
{
|
|
static int Toggle_var;
|
|
Toggle_var = !Toggle_var;
|
|
if (Toggle_var)
|
|
CGameArg.SysMaxFPS = 300;
|
|
else
|
|
CGameArg.SysMaxFPS = 30;
|
|
}
|
|
#endif
|
|
break;
|
|
case KEY_DEBUGGED + KEY_L:
|
|
#if !DXX_USE_OGL
|
|
if (++Lighting_on >= 2)
|
|
Lighting_on = 0;
|
|
#endif
|
|
break;
|
|
case KEY_PAD5: slew_stop(); break;
|
|
|
|
#ifndef NDEBUG
|
|
case KEY_DEBUGGED + KEY_F11: play_test_sound(); break;
|
|
#endif
|
|
|
|
case KEY_DEBUGGED + KEY_C:
|
|
{
|
|
auto &plrobj = get_local_plrobj();
|
|
do_cheat_menu(plrobj, grd_curscreen->sc_canvas);
|
|
break;
|
|
}
|
|
case KEY_DEBUGGED + KEY_SHIFTED + KEY_A:
|
|
{
|
|
auto &plrobj = get_local_plrobj();
|
|
do_megawow_powerup(plrobj, 10);
|
|
break;
|
|
}
|
|
case KEY_DEBUGGED + KEY_A: {
|
|
auto &plrobj = get_local_plrobj();
|
|
do_megawow_powerup(plrobj, 200);
|
|
break;
|
|
}
|
|
|
|
case KEY_DEBUGGED+KEY_SPACEBAR: //KEY_F7: // Toggle physics flying
|
|
slew_stop();
|
|
game_flush_inputs(Controls);
|
|
if (ConsoleObject->control_source != object::control_type::flying)
|
|
{
|
|
fly_init(*ConsoleObject);
|
|
Game_suspended &= ~SUSP_ROBOTS; //robots move
|
|
} else {
|
|
slew_init(vmobjptr(ConsoleObject)); //start player slewing
|
|
Game_suspended |= SUSP_ROBOTS; //robots don't move
|
|
}
|
|
break;
|
|
|
|
case KEY_DEBUGGED+KEY_COMMA: Render_zoom = fixmul(Render_zoom,62259); break;
|
|
case KEY_DEBUGGED+KEY_PERIOD: Render_zoom = fixmul(Render_zoom,68985); break;
|
|
|
|
#ifndef NDEBUG
|
|
case KEY_DEBUGGED+KEY_D:
|
|
if ((CGameArg.DbgNoDoubleBuffer = !CGameArg.DbgNoDoubleBuffer)!=0)
|
|
init_cockpit();
|
|
break;
|
|
#endif
|
|
|
|
#if DXX_USE_EDITOR
|
|
case KEY_DEBUGGED+KEY_Q:
|
|
{
|
|
pause_game_world_time p;
|
|
dump_used_textures_all();
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
case KEY_DEBUGGED+KEY_B: {
|
|
d_fname text{};
|
|
int item;
|
|
std::array<newmenu_item, 1> m{{
|
|
newmenu_item::nm_item_input(text),
|
|
}};
|
|
struct briefing_menu : passive_newmenu
|
|
{
|
|
briefing_menu(grs_canvas &canvas, partial_range_t<newmenu_item *> items) :
|
|
passive_newmenu(menu_title{nullptr}, menu_subtitle{"Briefing to play?"}, menu_filename{nullptr}, tiny_mode_flag::normal, tab_processing_flag::ignore, adjusted_citem::create(items, 0), canvas)
|
|
{
|
|
}
|
|
};
|
|
item = run_blocking_newmenu<briefing_menu>(*grd_curcanv, m);
|
|
if (item != -1) {
|
|
do_briefing_screens(text,1);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case KEY_DEBUGGED+KEY_SHIFTED+KEY_B:
|
|
if (Player_dead_state != player_dead_state::no)
|
|
return window_event_result::ignored;
|
|
|
|
kill_and_so_forth(vmobjptridx, vmsegptridx);
|
|
break;
|
|
case KEY_DEBUGGED+KEY_G:
|
|
GameTime64 = (INT64_MAX) - (F1_0*10);
|
|
HUD_init_message(HM_DEFAULT, "GameTime %" PRIi64 " - Reset in 10 seconds!", GameTime64);
|
|
break;
|
|
default:
|
|
return window_event_result::ignored;
|
|
}
|
|
return window_event_result::handled;
|
|
}
|
|
#endif //#ifndef RELEASE
|
|
|
|
#define CHEAT_MAX_LEN 15
|
|
struct cheat_code
|
|
{
|
|
const char string[CHEAT_MAX_LEN];
|
|
int game_cheats::*stateptr;
|
|
};
|
|
|
|
constexpr cheat_code cheat_codes[] = {
|
|
#if defined(DXX_BUILD_DESCENT_I)
|
|
{ "gabbagabbahey", &game_cheats::enabled },
|
|
{ "scourge", &game_cheats::wowie },
|
|
{ "bigred", &game_cheats::wowie2 },
|
|
{ "mitzi", &game_cheats::allkeys },
|
|
{ "racerx", &game_cheats::invul },
|
|
{ "guile", &game_cheats::cloak },
|
|
{ "twilight", &game_cheats::shields },
|
|
{ "poboys", &game_cheats::killreactor },
|
|
{ "farmerjoe", &game_cheats::levelwarp },
|
|
{ "bruin", &game_cheats::extralife },
|
|
{ "porgys", &game_cheats::rapidfire },
|
|
{ "ahimsa", &game_cheats::robotfiringsuspended },
|
|
{ "baldguy", &game_cheats::baldguy },
|
|
#elif defined(DXX_BUILD_DESCENT_II)
|
|
{ "gabbagabbahey", &game_cheats::lamer },
|
|
{ "motherlode", &game_cheats::lamer },
|
|
{ "currygoat", &game_cheats::lamer },
|
|
{ "zingermans", &game_cheats::lamer },
|
|
{ "eatangelos", &game_cheats::lamer },
|
|
{ "ericaanne", &game_cheats::lamer },
|
|
{ "joshuaakira", &game_cheats::lamer },
|
|
{ "whammazoom", &game_cheats::lamer },
|
|
{ "honestbob", &game_cheats::wowie },
|
|
{ "oralgroove", &game_cheats::allkeys },
|
|
{ "alifalafel", &game_cheats::accessory },
|
|
{ "almighty", &game_cheats::invul },
|
|
{ "blueorb", &game_cheats::shields },
|
|
{ "delshiftb", &game_cheats::killreactor },
|
|
{ "freespace", &game_cheats::levelwarp },
|
|
{ "rockrgrl", &game_cheats::fullautomap },
|
|
{ "wildfire", &game_cheats::rapidfire },
|
|
{ "duddaboo", &game_cheats::bouncyfire },
|
|
{ "lpnlizard", &game_cheats::homingfire },
|
|
{ "imagespace", &game_cheats::robotfiringsuspended },
|
|
{ "spaniard", &game_cheats::killallrobots },
|
|
{ "silkwing", &game_cheats::robotskillrobots },
|
|
{ "godzilla", &game_cheats::monsterdamage },
|
|
{ "helpvishnu", &game_cheats::buddyclone },
|
|
{ "gowingnut", &game_cheats::buddyangry },
|
|
#endif
|
|
{ "flash", &game_cheats::exitpath },
|
|
{ "astral", &game_cheats::ghostphysics },
|
|
{ "buggin", &game_cheats::turbo },
|
|
{ "bittersweet", &game_cheats::acid },
|
|
};
|
|
|
|
struct levelwarp_menu_items
|
|
{
|
|
std::array<char, 8> text{};
|
|
std::array<newmenu_item, 1> menu_items{{
|
|
newmenu_item::nm_item_input(text),
|
|
}};
|
|
};
|
|
|
|
struct levelwarp_menu : levelwarp_menu_items, passive_newmenu
|
|
{
|
|
levelwarp_menu(grs_canvas &src) :
|
|
passive_newmenu(menu_title{nullptr}, menu_subtitle{TXT_WARP_TO_LEVEL}, menu_filename{nullptr}, tiny_mode_flag::normal, tab_processing_flag::ignore, adjusted_citem::create(menu_items, 0), src)
|
|
{
|
|
}
|
|
virtual window_event_result event_handler(const d_event &event) override;
|
|
void handle_close_event();
|
|
};
|
|
|
|
void levelwarp_menu::handle_close_event()
|
|
{
|
|
if (!text[0])
|
|
return;
|
|
char *p;
|
|
const auto l = strtoul(text.data(), &p, 0);
|
|
if (*p)
|
|
return;
|
|
/* No handling for secret levels. Warping to secret
|
|
* levels is not supported.
|
|
*/
|
|
if (l > Current_mission->last_level)
|
|
return;
|
|
const auto g = Game_wind;
|
|
g->set_visible(0);
|
|
StartNewLevel(l);
|
|
g->set_visible(1);
|
|
}
|
|
|
|
window_event_result levelwarp_menu::event_handler(const d_event &event)
|
|
{
|
|
switch (event.type)
|
|
{
|
|
case EVENT_WINDOW_CLOSE:
|
|
handle_close_event();
|
|
return window_event_result::ignored;
|
|
default:
|
|
return newmenu::event_handler(event);
|
|
}
|
|
}
|
|
|
|
static window_event_result FinalCheats()
|
|
{
|
|
auto &Objects = LevelUniqueObjectState.Objects;
|
|
auto &vmobjptr = Objects.vmptr;
|
|
auto &vmobjptridx = Objects.vmptridx;
|
|
int game_cheats::*gotcha;
|
|
|
|
if (Game_mode & GM_MULTI)
|
|
return window_event_result::ignored;
|
|
|
|
static std::array<char, CHEAT_MAX_LEN> cheat_buffer;
|
|
std::move(std::next(cheat_buffer.begin()), cheat_buffer.end(), cheat_buffer.begin());
|
|
cheat_buffer.back() = key_ascii();
|
|
for (unsigned i = 0;; i++)
|
|
{
|
|
if (i >= std::size(cheat_codes))
|
|
return window_event_result::ignored;
|
|
int cheatlen = strlen(cheat_codes[i].string);
|
|
Assert(cheatlen <= CHEAT_MAX_LEN);
|
|
if (d_strnicmp(cheat_codes[i].string, &cheat_buffer[CHEAT_MAX_LEN-cheatlen], cheatlen)==0)
|
|
{
|
|
gotcha = cheat_codes[i].stateptr;
|
|
#if defined(DXX_BUILD_DESCENT_I)
|
|
if (!cheats.enabled && gotcha != &game_cheats::enabled)
|
|
return window_event_result::ignored;
|
|
if (!cheats.enabled)
|
|
HUD_init_message_literal(HM_DEFAULT, TXT_CHEATS_ENABLED);
|
|
#endif
|
|
cheats.*gotcha = !(cheats.*gotcha);
|
|
cheats.enabled = 1;
|
|
digi_play_sample( SOUND_CHEATER, F1_0);
|
|
break;
|
|
}
|
|
}
|
|
auto &plrobj = get_local_plrobj();
|
|
auto &player_info = plrobj.ctype.player_info;
|
|
player_info.mission.score = 0;
|
|
|
|
#if defined(DXX_BUILD_DESCENT_I)
|
|
if (gotcha == &game_cheats::wowie)
|
|
{
|
|
HUD_init_message_literal(HM_DEFAULT, TXT_WOWIE_ZOWIE);
|
|
|
|
player_info.primary_weapon_flags |= (HAS_LASER_FLAG | HAS_VULCAN_FLAG | HAS_SPREADFIRE_FLAG);
|
|
|
|
player_info.vulcan_ammo = VULCAN_AMMO_MAX;
|
|
auto &secondary_ammo = player_info.secondary_ammo;
|
|
for (const auto i : {secondary_weapon_index_t::CONCUSSION_INDEX, secondary_weapon_index_t::HOMING_INDEX, secondary_weapon_index_t::PROXIMITY_INDEX})
|
|
secondary_ammo[i] = Secondary_ammo_max[i];
|
|
|
|
if (Newdemo_state == ND_STATE_RECORDING)
|
|
newdemo_record_laser_level(player_info.laser_level, MAX_LASER_LEVEL);
|
|
|
|
player_info.energy = MAX_ENERGY;
|
|
player_info.laser_level = MAX_LASER_LEVEL;
|
|
player_info.powerup_flags |= PLAYER_FLAGS_QUAD_LASERS;
|
|
update_laser_weapon_info();
|
|
}
|
|
|
|
if (gotcha == &game_cheats::wowie2)
|
|
{
|
|
HUD_init_message(HM_DEFAULT, "SUPER %s",TXT_WOWIE_ZOWIE);
|
|
|
|
player_info.primary_weapon_flags = (HAS_LASER_FLAG | HAS_VULCAN_FLAG | HAS_SPREADFIRE_FLAG | HAS_PLASMA_FLAG | HAS_FUSION_FLAG);
|
|
|
|
player_info.vulcan_ammo = VULCAN_AMMO_MAX;
|
|
auto &secondary_ammo = player_info.secondary_ammo;
|
|
secondary_ammo = Secondary_ammo_max;
|
|
|
|
if (Newdemo_state == ND_STATE_RECORDING)
|
|
newdemo_record_laser_level(player_info.laser_level, MAX_LASER_LEVEL);
|
|
|
|
player_info.energy = MAX_ENERGY;
|
|
player_info.laser_level = MAX_LASER_LEVEL;
|
|
player_info.powerup_flags |= PLAYER_FLAGS_QUAD_LASERS;
|
|
update_laser_weapon_info();
|
|
}
|
|
#elif defined(DXX_BUILD_DESCENT_II)
|
|
if (gotcha == &game_cheats::lamer)
|
|
{
|
|
plrobj.shields = player_info.energy = i2f(1);
|
|
HUD_init_message_literal(HM_DEFAULT, "Take that...cheater!");
|
|
}
|
|
|
|
if (gotcha == &game_cheats::wowie)
|
|
{
|
|
HUD_init_message_literal(HM_DEFAULT, TXT_WOWIE_ZOWIE);
|
|
|
|
if (Piggy_hamfile_version < 3) // SHAREWARE
|
|
{
|
|
player_info.primary_weapon_flags |= (HAS_LASER_FLAG | HAS_VULCAN_FLAG | HAS_SPREADFIRE_FLAG | HAS_PLASMA_FLAG) | (HAS_GAUSS_FLAG | HAS_HELIX_FLAG);
|
|
}
|
|
else
|
|
{
|
|
player_info.primary_weapon_flags = (HAS_LASER_FLAG | HAS_VULCAN_FLAG | HAS_SPREADFIRE_FLAG | HAS_PLASMA_FLAG | HAS_FUSION_FLAG) | (HAS_GAUSS_FLAG | HAS_HELIX_FLAG | HAS_PHOENIX_FLAG | HAS_OMEGA_FLAG);
|
|
}
|
|
|
|
player_info.vulcan_ammo = VULCAN_AMMO_MAX;
|
|
auto &secondary_ammo = player_info.secondary_ammo;
|
|
secondary_ammo = Secondary_ammo_max;
|
|
|
|
if (Piggy_hamfile_version < 3) // SHAREWARE
|
|
{
|
|
secondary_ammo[SMISSILE4_INDEX] = 0;
|
|
secondary_ammo[SMISSILE5_INDEX] = 0;
|
|
secondary_ammo[MEGA_INDEX] = 0;
|
|
}
|
|
|
|
if (Newdemo_state == ND_STATE_RECORDING)
|
|
newdemo_record_laser_level(player_info.laser_level, MAX_SUPER_LASER_LEVEL);
|
|
|
|
player_info.energy = MAX_ENERGY;
|
|
player_info.laser_level = MAX_SUPER_LASER_LEVEL;
|
|
player_info.powerup_flags |= PLAYER_FLAGS_QUAD_LASERS;
|
|
update_laser_weapon_info();
|
|
}
|
|
|
|
if (gotcha == &game_cheats::accessory)
|
|
{
|
|
player_info.powerup_flags |= PLAYER_FLAGS_HEADLIGHT | PLAYER_FLAGS_AFTERBURNER | PLAYER_FLAGS_AMMO_RACK | PLAYER_FLAGS_CONVERTER;
|
|
HUD_init_message_literal(HM_DEFAULT, "Accessories!!");
|
|
}
|
|
#endif
|
|
|
|
if (gotcha == &game_cheats::allkeys)
|
|
{
|
|
HUD_init_message_literal(HM_DEFAULT, TXT_ALL_KEYS);
|
|
player_info.powerup_flags |= PLAYER_FLAGS_BLUE_KEY | PLAYER_FLAGS_RED_KEY | PLAYER_FLAGS_GOLD_KEY;
|
|
}
|
|
|
|
if (gotcha == &game_cheats::invul)
|
|
{
|
|
player_info.invulnerable_time = GameTime64+i2f(1000);
|
|
auto &pl_flags = player_info.powerup_flags;
|
|
pl_flags ^= PLAYER_FLAGS_INVULNERABLE;
|
|
HUD_init_message(HM_DEFAULT, "%s %s!", TXT_INVULNERABILITY, (pl_flags & PLAYER_FLAGS_INVULNERABLE) ? (player_info.FakingInvul = 0, TXT_ON) : TXT_OFF);
|
|
}
|
|
|
|
if (gotcha == &game_cheats::shields)
|
|
{
|
|
HUD_init_message_literal(HM_DEFAULT, TXT_FULL_SHIELDS);
|
|
plrobj.shields = MAX_SHIELDS;
|
|
}
|
|
|
|
#if defined(DXX_BUILD_DESCENT_I)
|
|
if (gotcha == &game_cheats::cloak)
|
|
{
|
|
auto &pl_flags = player_info.powerup_flags;
|
|
pl_flags ^= PLAYER_FLAGS_CLOAKED;
|
|
const auto have_cloaked = pl_flags & PLAYER_FLAGS_CLOAKED;
|
|
HUD_init_message(HM_DEFAULT, "%s %s!", TXT_CLOAK, have_cloaked ? TXT_ON : TXT_OFF);
|
|
if (have_cloaked)
|
|
{
|
|
ai_do_cloak_stuff();
|
|
player_info.cloak_time = GameTime64;
|
|
}
|
|
}
|
|
|
|
if (gotcha == &game_cheats::extralife)
|
|
{
|
|
auto &plr = get_local_player();
|
|
if (plr.lives < 50)
|
|
{
|
|
plr.lives++;
|
|
HUD_init_message_literal(HM_DEFAULT, "Extra life!");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (gotcha == &game_cheats::killreactor)
|
|
{
|
|
kill_and_so_forth(vmobjptridx, vmsegptridx);
|
|
}
|
|
|
|
if (gotcha == &game_cheats::exitpath)
|
|
{
|
|
if (create_special_path())
|
|
HUD_init_message_literal(HM_DEFAULT, "Exit path illuminated!");
|
|
}
|
|
|
|
if (gotcha == &game_cheats::levelwarp)
|
|
{
|
|
auto menu = window_create<levelwarp_menu>(grd_curscreen->sc_canvas);
|
|
(void)menu;
|
|
}
|
|
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
if (gotcha == &game_cheats::fullautomap)
|
|
HUD_init_message_literal(HM_DEFAULT, cheats.fullautomap ? "FULL MAP!" : "REGULAR MAP");
|
|
#endif
|
|
|
|
if (gotcha == &game_cheats::ghostphysics)
|
|
{
|
|
HUD_init_message(HM_DEFAULT, "%s %s!", "Ghosty mode", cheats.ghostphysics?TXT_ON:TXT_OFF);
|
|
}
|
|
|
|
if (gotcha == &game_cheats::rapidfire)
|
|
{
|
|
HUD_init_message(HM_DEFAULT, "Rapid fire %s!", cheats.rapidfire?TXT_ON:TXT_OFF);
|
|
#if defined(DXX_BUILD_DESCENT_I)
|
|
if (cheats.rapidfire)
|
|
do_megawow_powerup(plrobj, 200);
|
|
#endif
|
|
}
|
|
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
if (gotcha == &game_cheats::bouncyfire)
|
|
{
|
|
|
|
HUD_init_message(HM_DEFAULT, "Bouncing weapons %s!", cheats.bouncyfire?TXT_ON:TXT_OFF);
|
|
}
|
|
|
|
if (gotcha == &game_cheats::homingfire)
|
|
{
|
|
HUD_init_message(HM_DEFAULT, "Homing weapons %s!", cheats.homingfire ? (weapons_homing_all(), TXT_ON) : (weapons_homing_all_reset(), TXT_OFF));
|
|
}
|
|
#endif
|
|
|
|
if (gotcha == &game_cheats::turbo)
|
|
{
|
|
HUD_init_message(HM_DEFAULT, "%s %s!", "Turbo mode", cheats.turbo?TXT_ON:TXT_OFF);
|
|
}
|
|
|
|
if (gotcha == &game_cheats::robotfiringsuspended)
|
|
{
|
|
HUD_init_message(HM_DEFAULT, "Robot firing %s!", cheats.robotfiringsuspended?TXT_OFF:TXT_ON);
|
|
}
|
|
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
if (gotcha == &game_cheats::killallrobots)
|
|
{
|
|
kill_all_robots();
|
|
}
|
|
|
|
if (gotcha == &game_cheats::robotskillrobots)
|
|
{
|
|
HUD_init_message_literal(HM_DEFAULT, cheats.robotskillrobots?"Rabid robots!":"Kill the player!");
|
|
}
|
|
|
|
if (gotcha == &game_cheats::monsterdamage)
|
|
{
|
|
HUD_init_message_literal(HM_DEFAULT, cheats.monsterdamage?"Oh no, there goes Tokyo!":"What have you done, I'm shrinking!!");
|
|
}
|
|
|
|
if (gotcha == &game_cheats::buddyclone)
|
|
{
|
|
HUD_init_message_literal(HM_DEFAULT, "What's this? Another buddy bot!");
|
|
create_buddy_bot();
|
|
}
|
|
|
|
if (gotcha == &game_cheats::buddyangry)
|
|
{
|
|
|
|
if (cheats.buddyangry)
|
|
{
|
|
HUD_init_message(HM_DEFAULT, "%s gets angry!", static_cast<const char *>(PlayerCfg.GuidebotName));
|
|
PlayerCfg.GuidebotName = "Wingnut";
|
|
}
|
|
else
|
|
{
|
|
PlayerCfg.GuidebotName = PlayerCfg.GuidebotNameReal;
|
|
HUD_init_message(HM_DEFAULT, "%s calms down", static_cast<const char *>(PlayerCfg.GuidebotName));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (gotcha == &game_cheats::acid)
|
|
{
|
|
HUD_init_message_literal(HM_DEFAULT, cheats.acid?"Going up!":"Coming down!");
|
|
}
|
|
|
|
return window_event_result::handled;
|
|
}
|
|
|
|
// Internal Cheat Menu
|
|
#ifndef RELEASE
|
|
|
|
class menu_fix_wrapper
|
|
{
|
|
fix &m_value;
|
|
public:
|
|
constexpr menu_fix_wrapper(fix &t) :
|
|
m_value(t)
|
|
{
|
|
}
|
|
operator int() const
|
|
{
|
|
return f2i(m_value);
|
|
}
|
|
menu_fix_wrapper &operator=(int n)
|
|
{
|
|
m_value = i2f(n);
|
|
return *this;
|
|
}
|
|
};
|
|
|
|
class cheat_menu_bit_invulnerability :
|
|
std::reference_wrapper<player_info>,
|
|
public menu_bit_wrapper_t<player_flags, std::integral_constant<PLAYER_FLAG, PLAYER_FLAGS_INVULNERABLE>>
|
|
{
|
|
public:
|
|
cheat_menu_bit_invulnerability(player_info &pl_info) :
|
|
reference_wrapper(pl_info),
|
|
menu_bit_wrapper_t(pl_info.powerup_flags, {})
|
|
{
|
|
}
|
|
cheat_menu_bit_invulnerability &operator=(const uint32_t n)
|
|
{
|
|
this->menu_bit_wrapper_t::operator=(n);
|
|
if (n)
|
|
{
|
|
auto &player_info = get();
|
|
player_info.FakingInvul = 0;
|
|
player_info.invulnerable_time = GameTime64+i2f(1000);
|
|
}
|
|
return *this;
|
|
}
|
|
};
|
|
|
|
class cheat_menu_bit_cloak :
|
|
std::reference_wrapper<player_info>,
|
|
public menu_bit_wrapper_t<player_flags, std::integral_constant<PLAYER_FLAG, PLAYER_FLAGS_CLOAKED>>
|
|
{
|
|
public:
|
|
cheat_menu_bit_cloak(player_info &pl_info) :
|
|
reference_wrapper(pl_info),
|
|
menu_bit_wrapper_t(pl_info.powerup_flags, {})
|
|
{
|
|
}
|
|
cheat_menu_bit_cloak &operator=(const uint32_t n)
|
|
{
|
|
this->menu_bit_wrapper_t::operator=(n);
|
|
if (n)
|
|
{
|
|
if (Game_mode & GM_MULTI)
|
|
multi_send_cloak();
|
|
ai_do_cloak_stuff();
|
|
get().cloak_time = GameTime64;
|
|
}
|
|
return *this;
|
|
}
|
|
};
|
|
|
|
#if defined(DXX_BUILD_DESCENT_I)
|
|
#define WIMP_MENU_DXX(VERB)
|
|
#elif defined(DXX_BUILD_DESCENT_II)
|
|
/* Adding an afterburner like this adds it at 0% charge. This is OK for
|
|
* a cheat. The player can change his energy up if he needs more.
|
|
*/
|
|
#define WIMP_MENU_DXX(VERB) \
|
|
DXX_MENUITEM(VERB, CHECK, TXT_AFTERBURNER, opt_afterburner, menu_bit_wrapper(pl_info.powerup_flags, PLAYER_FLAGS_AFTERBURNER)) \
|
|
|
|
#endif
|
|
|
|
#define DXX_WIMP_MENU(VERB) \
|
|
DXX_MENUITEM(VERB, CHECK, TXT_INVULNERABILITY, opt_invul, cheat_menu_bit_invulnerability(pl_info)) \
|
|
DXX_MENUITEM(VERB, CHECK, TXT_CLOAKED, opt_cloak, cheat_menu_bit_cloak(pl_info)) \
|
|
DXX_MENUITEM(VERB, CHECK, "BLUE KEY", opt_key_blue, menu_bit_wrapper(pl_info.powerup_flags, PLAYER_FLAGS_BLUE_KEY)) \
|
|
DXX_MENUITEM(VERB, CHECK, "GOLD KEY", opt_key_gold, menu_bit_wrapper(pl_info.powerup_flags, PLAYER_FLAGS_GOLD_KEY)) \
|
|
DXX_MENUITEM(VERB, CHECK, "RED KEY", opt_key_red, menu_bit_wrapper(pl_info.powerup_flags, PLAYER_FLAGS_RED_KEY)) \
|
|
WIMP_MENU_DXX(VERB) \
|
|
DXX_MENUITEM(VERB, NUMBER, TXT_ENERGY, opt_energy, menu_fix_wrapper(pl_info.energy), 0, 200) \
|
|
DXX_MENUITEM(VERB, NUMBER, "Shields", opt_shields, menu_fix_wrapper(plrobj.shields), 0, 200) \
|
|
DXX_MENUITEM(VERB, TEXT, TXT_SCORE, opt_txt_score) \
|
|
DXX_MENUITEM(VERB, INPUT, score_text, opt_score) \
|
|
DXX_MENUITEM(VERB, NUMBER, "Laser Level", opt_laser_level, menu_number_bias_wrapper<1>(plr_laser_level), static_cast<uint8_t>(laser_level::_1) + 1, static_cast<uint8_t>(DXX_MAXIMUM_LASER_LEVEL) + 1) \
|
|
DXX_MENUITEM(VERB, NUMBER, "Concussion", opt_concussion, pl_info.secondary_ammo[CONCUSSION_INDEX], 0, 200) \
|
|
|
|
struct wimp_menu_items
|
|
{
|
|
object &plrobj;
|
|
std::array<char, sizeof("2147483647")> score_text;
|
|
enum {
|
|
DXX_WIMP_MENU(ENUM)
|
|
};
|
|
std::array<newmenu_item, DXX_WIMP_MENU(COUNT)> m;
|
|
wimp_menu_items(object &plr) :
|
|
plrobj(plr)
|
|
{
|
|
auto &pl_info = plrobj.ctype.player_info;
|
|
snprintf(score_text.data(), score_text.size(), "%d", pl_info.mission.score);
|
|
const uint8_t plr_laser_level = static_cast<uint8_t>(pl_info.laser_level);
|
|
DXX_WIMP_MENU(ADD);
|
|
}
|
|
};
|
|
|
|
struct wimp_menu : wimp_menu_items, newmenu
|
|
{
|
|
wimp_menu(object &plr, grs_canvas &src) :
|
|
wimp_menu_items(plr),
|
|
newmenu(menu_title{"Wimp Menu"}, menu_subtitle{nullptr}, menu_filename{nullptr}, tiny_mode_flag::normal, tab_processing_flag::ignore, adjusted_citem::create(m, 0), src)
|
|
{
|
|
}
|
|
virtual window_event_result event_handler(const d_event &event) override;
|
|
};
|
|
|
|
window_event_result wimp_menu::event_handler(const d_event &event)
|
|
{
|
|
switch (event.type)
|
|
{
|
|
case EVENT_WINDOW_CLOSE:
|
|
{
|
|
auto &pl_info = plrobj.ctype.player_info;
|
|
uint8_t plr_laser_level;
|
|
DXX_WIMP_MENU(READ);
|
|
pl_info.laser_level = laser_level{plr_laser_level};
|
|
char *p;
|
|
auto ul = strtoul(score_text.data(), &p, 10);
|
|
if (!*p)
|
|
pl_info.mission.score = static_cast<int>(ul);
|
|
init_gauges();
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
return newmenu::event_handler(event);
|
|
}
|
|
|
|
static void do_cheat_menu(object &plrobj, grs_canvas &cv_canvas)
|
|
{
|
|
auto menu = window_create<wimp_menu>(plrobj, cv_canvas);
|
|
(void)menu;
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
// Testing functions ----------------------------------------------------------
|
|
|
|
#ifndef NDEBUG
|
|
// Sounds for testing
|
|
__attribute_used
|
|
static int Test_sound;
|
|
|
|
static void play_test_sound()
|
|
{
|
|
digi_play_sample(Test_sound, F1_0);
|
|
}
|
|
#endif //ifndef NDEBUG
|
|
|
|
}
|
|
|
|
window_event_result ReadControls(const d_event &event, control_info &Controls)
|
|
{
|
|
auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
|
|
auto &Objects = LevelUniqueObjectState.Objects;
|
|
int key;
|
|
static ubyte exploding_flag=0;
|
|
|
|
Player_fired_laser_this_frame=object_none;
|
|
|
|
if (Player_dead_state == player_dead_state::exploded)
|
|
{
|
|
if (exploding_flag==0) {
|
|
exploding_flag = 1; // When player starts exploding, clear all input devices...
|
|
game_flush_inputs(Controls);
|
|
}
|
|
} else {
|
|
exploding_flag=0;
|
|
}
|
|
if (Player_dead_state != player_dead_state::no &&
|
|
!((Game_mode & GM_MULTI) &&
|
|
(multi_sending_message[Player_num] != msgsend_state::none || multi_defining_message)
|
|
)
|
|
)
|
|
HandleDeathInput(event, Controls);
|
|
|
|
if (Newdemo_state == ND_STATE_PLAYBACK)
|
|
update_vcr_state();
|
|
|
|
if (event.type == EVENT_KEY_COMMAND)
|
|
{
|
|
key = event_key_get(event);
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
if (MarkerState.DefiningMarkerMessage())
|
|
{
|
|
return MarkerInputMessage(key, Controls);
|
|
}
|
|
#endif
|
|
if ( (Game_mode & GM_MULTI) && (multi_sending_message[Player_num] != msgsend_state::none || multi_defining_message) )
|
|
{
|
|
return multi_message_input_sub(key, Controls);
|
|
}
|
|
|
|
#ifndef RELEASE
|
|
if ((key&KEY_DEBUGGED)&&(Game_mode&GM_MULTI)) {
|
|
Network_message_reciever = 100; // Send to everyone...
|
|
snprintf(Network_message.data(), Network_message.size(), "%s %s", TXT_I_AM_A, TXT_CHEATER);
|
|
}
|
|
#endif
|
|
|
|
if (Endlevel_sequence)
|
|
{
|
|
auto result = HandleEndlevelKey(key);
|
|
if (result != window_event_result::ignored)
|
|
return result;
|
|
}
|
|
else if (Newdemo_state == ND_STATE_PLAYBACK )
|
|
{
|
|
auto r = HandleDemoKey(key);
|
|
if (r != window_event_result::ignored)
|
|
return r;
|
|
}
|
|
else
|
|
{
|
|
window_event_result r = FinalCheats();
|
|
if (r == window_event_result::ignored)
|
|
r = HandleSystemKey(key);
|
|
if (r == window_event_result::ignored)
|
|
r = HandleGameKey(key, Controls);
|
|
if (r != window_event_result::ignored)
|
|
return r;
|
|
}
|
|
|
|
#ifndef RELEASE
|
|
{
|
|
window_event_result r = HandleTestKey(vmsegptridx, key, Controls);
|
|
if (r != window_event_result::ignored)
|
|
return r;
|
|
}
|
|
#endif
|
|
|
|
auto result = call_default_handler(event);
|
|
if (result != window_event_result::ignored)
|
|
return result;
|
|
}
|
|
|
|
if (!Endlevel_sequence && Newdemo_state != ND_STATE_PLAYBACK)
|
|
{
|
|
kconfig_read_controls(Controls, event, 0);
|
|
const auto Player_is_dead = Player_dead_state;
|
|
if (Player_is_dead != player_dead_state::no && HandleDeathInput(event, Controls))
|
|
return window_event_result::handled;
|
|
|
|
check_rear_view(Controls);
|
|
|
|
// If automap key pressed, enable automap unless you are in network mode, control center destroyed and < 10 seconds left
|
|
if ( Controls.state.automap )
|
|
{
|
|
Controls.state.automap = 0;
|
|
if (Player_is_dead != player_dead_state::no || !((Game_mode & GM_MULTI) && LevelUniqueControlCenterState.Control_center_destroyed && LevelUniqueControlCenterState.Countdown_seconds_left < 10))
|
|
{
|
|
do_automap();
|
|
return window_event_result::handled;
|
|
}
|
|
}
|
|
if (Player_is_dead != player_dead_state::no)
|
|
return window_event_result::ignored;
|
|
do_weapon_n_item_stuff(Objects, Controls);
|
|
}
|
|
|
|
if (Controls.state.show_menu)
|
|
{
|
|
Controls.state.show_menu = 0;
|
|
return HandleSystemKey(KEY_ESC);
|
|
}
|
|
|
|
return window_event_result::ignored;
|
|
}
|
|
|
|
}
|