Files
devilutionX/Source/gamemenu.cpp
T
Gleb Mazovetskiy 680ab5ec40 Overhaul backbuffer state handling
When rendering directly to the output buffer, we need to maintain the
state of what has been drawn and what needs redrawing per-buffer.

We previously tried to do it implicitly by checking `SDL_DOUBLEBUF` and
other flags. The previous implementation was broken in several
ways, resulting in rendering issues on devices that support 8-bit output
directly.

Changes this mechanism to explicitly maintain buffer state per output
buffer. The new mechanism doesn't require knowledge of the number of
buffers, and thus also works correctly with triple-buffering.

Fixes #5447
2022-11-01 02:43:24 +00:00

366 lines
8.9 KiB
C++

/**
* @file gamemenu.cpp
*
* Implementation of the in-game menu functions.
*/
#include "gamemenu.h"
#include "cursor.h"
#include "engine/backbuffer_state.hpp"
#include "engine/sound.h"
#include "engine/sound_defs.hpp"
#include "error.h"
#include "gmenu.h"
#include "init.h"
#include "loadsave.h"
#include "options.h"
#include "pfile.h"
#include "utils/language.h"
namespace devilution {
namespace {
// Forward-declare menu handlers, used by the global menu structs below.
void GamemenuPrevious(bool bActivate);
void GamemenuNewGame(bool bActivate);
void GamemenuRestartTown(bool bActivate);
void GamemenuOptions(bool bActivate);
void GamemenuMusicVolume(bool bActivate);
void GamemenuSoundVolume(bool bActivate);
void GamemenuGamma(bool bActivate);
void GamemenuSpeed(bool bActivate);
/** Contains the game menu items of the single player menu. */
TMenuItem sgSingleMenu[] = {
// clang-format off
// dwFlags, pszStr, fnMenu
{ GMENU_ENABLED, N_("Save Game"), &gamemenu_save_game },
{ GMENU_ENABLED, N_("Options"), &GamemenuOptions },
{ GMENU_ENABLED, N_("New Game"), &GamemenuNewGame },
{ GMENU_ENABLED, N_("Load Game"), &gamemenu_load_game },
{ GMENU_ENABLED, N_("Quit Game"), &gamemenu_quit_game },
{ GMENU_ENABLED, nullptr, nullptr }
// clang-format on
};
/** Contains the game menu items of the multi player menu. */
TMenuItem sgMultiMenu[] = {
// clang-format off
// dwFlags, pszStr, fnMenu
{ GMENU_ENABLED, N_("Options"), &GamemenuOptions },
{ GMENU_ENABLED, N_("New Game"), &GamemenuNewGame },
{ GMENU_ENABLED, N_("Restart In Town"), &GamemenuRestartTown },
{ GMENU_ENABLED, N_("Quit Game"), &gamemenu_quit_game },
{ GMENU_ENABLED, nullptr, nullptr },
// clang-format on
};
TMenuItem sgOptionsMenu[] = {
// clang-format off
// dwFlags, pszStr, fnMenu
{ GMENU_ENABLED | GMENU_SLIDER, nullptr, &GamemenuMusicVolume },
{ GMENU_ENABLED | GMENU_SLIDER, nullptr, &GamemenuSoundVolume },
{ GMENU_ENABLED | GMENU_SLIDER, N_("Gamma"), &GamemenuGamma },
{ GMENU_ENABLED | GMENU_SLIDER, N_("Speed"), &GamemenuSpeed },
{ GMENU_ENABLED , N_("Previous Menu"), &GamemenuPrevious },
{ GMENU_ENABLED , nullptr, nullptr },
// clang-format on
};
/** Specifies the menu names for music enabled and disabled. */
const char *const MusicToggleNames[] = {
N_("Music"),
N_("Music Disabled"),
};
/** Specifies the menu names for sound enabled and disabled. */
const char *const SoundToggleNames[] = {
N_("Sound"),
N_("Sound Disabled"),
};
void GamemenuUpdateSingle()
{
sgSingleMenu[3].setEnabled(gbValidSaveFile);
bool enable = MyPlayer->_pmode != PM_DEATH && !MyPlayerIsDead;
sgSingleMenu[0].setEnabled(enable);
}
void GamemenuUpdateMulti()
{
sgMultiMenu[2].setEnabled(MyPlayerIsDead);
}
void GamemenuPrevious(bool /*bActivate*/)
{
gamemenu_on();
}
void GamemenuNewGame(bool /*bActivate*/)
{
for (Player &player : Players) {
player._pmode = PM_QUIT;
player._pInvincible = true;
}
MyPlayerIsDead = false;
if (!HeadlessMode) {
RedrawEverything();
scrollrt_draw_game_screen();
}
CornerStone.activated = false;
gbRunGame = false;
gamemenu_off();
}
void GamemenuRestartTown(bool /*bActivate*/)
{
NetSendCmd(true, CMD_RETOWN);
}
void GamemenuSoundMusicToggle(const char *const *names, TMenuItem *menuItem, int volume)
{
if (gbSndInited) {
menuItem->addFlags(GMENU_ENABLED | GMENU_SLIDER);
menuItem->pszStr = names[0];
gmenu_slider_steps(menuItem, VOLUME_STEPS);
gmenu_slider_set(menuItem, VOLUME_MIN, VOLUME_MAX, volume);
return;
}
menuItem->removeFlags(GMENU_ENABLED | GMENU_SLIDER);
menuItem->pszStr = names[1];
}
int GamemenuSliderMusicSound(TMenuItem *menuItem)
{
return gmenu_slider_get(menuItem, VOLUME_MIN, VOLUME_MAX);
}
void GamemenuGetMusic()
{
GamemenuSoundMusicToggle(MusicToggleNames, sgOptionsMenu, sound_get_or_set_music_volume(1));
}
void GamemenuGetSound()
{
GamemenuSoundMusicToggle(SoundToggleNames, &sgOptionsMenu[1], sound_get_or_set_sound_volume(1));
}
void GamemenuGetGamma()
{
gmenu_slider_steps(&sgOptionsMenu[2], 15);
gmenu_slider_set(&sgOptionsMenu[2], 30, 100, UpdateGamma(0));
}
void GamemenuGetSpeed()
{
if (gbIsMultiplayer) {
sgOptionsMenu[3].removeFlags(GMENU_ENABLED | GMENU_SLIDER);
if (sgGameInitInfo.nTickRate >= 50)
sgOptionsMenu[3].pszStr = _("Speed: Fastest").data();
else if (sgGameInitInfo.nTickRate >= 40)
sgOptionsMenu[3].pszStr = _("Speed: Faster").data();
else if (sgGameInitInfo.nTickRate >= 30)
sgOptionsMenu[3].pszStr = _("Speed: Fast").data();
else if (sgGameInitInfo.nTickRate == 20)
sgOptionsMenu[3].pszStr = _("Speed: Normal").data();
return;
}
sgOptionsMenu[3].addFlags(GMENU_ENABLED | GMENU_SLIDER);
sgOptionsMenu[3].pszStr = _("Speed").data();
gmenu_slider_steps(&sgOptionsMenu[3], 46);
gmenu_slider_set(&sgOptionsMenu[3], 20, 50, sgGameInitInfo.nTickRate);
}
int GamemenuSliderGamma()
{
return gmenu_slider_get(&sgOptionsMenu[2], 30, 100);
}
void GamemenuOptions(bool /*bActivate*/)
{
GamemenuGetMusic();
GamemenuGetSound();
GamemenuGetGamma();
GamemenuGetSpeed();
gmenu_set_items(sgOptionsMenu, nullptr);
}
void GamemenuMusicVolume(bool bActivate)
{
if (bActivate) {
if (gbMusicOn) {
gbMusicOn = false;
music_stop();
sound_get_or_set_music_volume(VOLUME_MIN);
} else {
gbMusicOn = true;
sound_get_or_set_music_volume(VOLUME_MAX);
music_start(GetLevelMusic(leveltype));
}
} else {
int volume = GamemenuSliderMusicSound(&sgOptionsMenu[0]);
sound_get_or_set_music_volume(volume);
if (volume == VOLUME_MIN) {
if (gbMusicOn) {
gbMusicOn = false;
music_stop();
}
} else if (!gbMusicOn) {
gbMusicOn = true;
music_start(GetLevelMusic(leveltype));
}
}
GamemenuGetMusic();
}
void GamemenuSoundVolume(bool bActivate)
{
if (bActivate) {
if (gbSoundOn) {
gbSoundOn = false;
sound_stop();
sound_get_or_set_sound_volume(VOLUME_MIN);
} else {
gbSoundOn = true;
sound_get_or_set_sound_volume(VOLUME_MAX);
}
} else {
int volume = GamemenuSliderMusicSound(&sgOptionsMenu[1]);
sound_get_or_set_sound_volume(volume);
if (volume == VOLUME_MIN) {
if (gbSoundOn) {
gbSoundOn = false;
sound_stop();
}
} else if (!gbSoundOn) {
gbSoundOn = true;
}
}
PlaySFX(IS_TITLEMOV);
GamemenuGetSound();
}
void GamemenuGamma(bool bActivate)
{
int gamma;
if (bActivate) {
gamma = UpdateGamma(0);
if (gamma == 30)
gamma = 100;
else
gamma = 30;
} else {
gamma = GamemenuSliderGamma();
}
UpdateGamma(gamma);
GamemenuGetGamma();
}
void GamemenuSpeed(bool bActivate)
{
if (bActivate) {
if (sgGameInitInfo.nTickRate != 20)
sgGameInitInfo.nTickRate = 20;
else
sgGameInitInfo.nTickRate = 50;
gmenu_slider_set(&sgOptionsMenu[3], 20, 50, sgGameInitInfo.nTickRate);
} else {
sgGameInitInfo.nTickRate = gmenu_slider_get(&sgOptionsMenu[3], 20, 50);
}
sgOptions.Gameplay.tickRate.SetValue(sgGameInitInfo.nTickRate);
gnTickDelay = 1000 / sgGameInitInfo.nTickRate;
}
} // namespace
void gamemenu_quit_game(bool bActivate)
{
GamemenuNewGame(bActivate);
#ifndef NOEXIT
gbRunGameResult = false;
#else
ReturnToMainMenu = true;
#endif
}
void gamemenu_load_game(bool /*bActivate*/)
{
EventHandler saveProc = SetEventHandler(DisableInputEventHandler);
gamemenu_off();
NewCursor(CURSOR_NONE);
InitDiabloMsg(EMSG_LOADING);
RedrawEverything();
DrawAndBlit();
LoadGame(false);
ClrDiabloMsg();
CornerStone.activated = false;
PaletteFadeOut(8);
MyPlayerIsDead = false;
RedrawEverything();
DrawAndBlit();
LoadPWaterPalette();
PaletteFadeIn(8);
NewCursor(CURSOR_HAND);
interface_msg_pump();
SetEventHandler(saveProc);
}
void gamemenu_save_game(bool /*bActivate*/)
{
if (pcurs != CURSOR_HAND) {
return;
}
if (MyPlayer->_pmode == PM_DEATH || MyPlayerIsDead) {
gamemenu_off();
return;
}
EventHandler saveProc = SetEventHandler(DisableInputEventHandler);
NewCursor(CURSOR_NONE);
gamemenu_off();
InitDiabloMsg(EMSG_SAVING);
RedrawEverything();
DrawAndBlit();
SaveGame();
ClrDiabloMsg();
RedrawEverything();
NewCursor(CURSOR_HAND);
if (CornerStone.activated) {
CornerstoneSave();
SaveOptions();
}
interface_msg_pump();
SetEventHandler(saveProc);
}
void gamemenu_on()
{
if (!gbIsMultiplayer) {
gmenu_set_items(sgSingleMenu, GamemenuUpdateSingle);
} else {
gmenu_set_items(sgMultiMenu, GamemenuUpdateMulti);
}
PressEscKey();
}
void gamemenu_off()
{
gmenu_set_items(nullptr, nullptr);
}
void gamemenu_handle_previous()
{
if (gmenu_is_active())
gamemenu_off();
else
gamemenu_on();
}
} // namespace devilution