mirror of
https://github.com/ZDoom/Raze.git
synced 2024-11-25 21:41:55 +00:00
2022de845a
To allow adding a "User map" item and prevent accidental reset of ongoing games, both the items for starting the campaign and the training map are now in a submenu. This allows the game to go through the regular startup procedure common to all games, which in turn allows adding the "User map" item to the episode selection.
783 lines
21 KiB
C++
783 lines
21 KiB
C++
/*
|
|
** menu.cpp
|
|
** Menu base class and global interface
|
|
**
|
|
**---------------------------------------------------------------------------
|
|
** Copyright 2010 Christoph Oelckers
|
|
** All rights reserved.
|
|
**
|
|
** Redistribution and use in source and binary forms, with or without
|
|
** modification, are permitted provided that the following conditions
|
|
** are met:
|
|
**
|
|
** 1. Redistributions of source code must retain the above copyright
|
|
** notice, this list of conditions and the following disclaimer.
|
|
** 2. Redistributions in binary form must reproduce the above copyright
|
|
** notice, this list of conditions and the following disclaimer in the
|
|
** documentation and/or other materials provided with the distribution.
|
|
** 3. The name of the author may not be used to endorse or promote products
|
|
** derived from this software without specific prior written permission.
|
|
**
|
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
**---------------------------------------------------------------------------
|
|
**
|
|
*/
|
|
|
|
#include "c_dispatch.h"
|
|
#include "d_gui.h"
|
|
#include "c_buttons.h"
|
|
#include "c_console.h"
|
|
#include "c_bind.h"
|
|
#include "d_eventbase.h"
|
|
#include "g_input.h"
|
|
#include "configfile.h"
|
|
#include "gstrings.h"
|
|
#include "menu.h"
|
|
#include "vm.h"
|
|
#include "v_video.h"
|
|
#include "i_system.h"
|
|
#include "types.h"
|
|
#include "texturemanager.h"
|
|
#include "v_draw.h"
|
|
#include "vm.h"
|
|
#include "gamestate.h"
|
|
#include "i_interface.h"
|
|
#include "d_event.h"
|
|
#include "st_start.h"
|
|
#include "i_system.h"
|
|
#include "gameconfigfile.h"
|
|
#include "gamecontrol.h"
|
|
#include "raze_sound.h"
|
|
#include "gamestruct.h"
|
|
#include "razemenu.h"
|
|
#include "mapinfo.h"
|
|
#include "statistics.h"
|
|
#include "i_net.h"
|
|
#include "savegamehelp.h"
|
|
#include "gi.h"
|
|
#include "raze_music.h"
|
|
#include "razefont.h"
|
|
|
|
EXTERN_CVAR(Int, cl_gfxlocalization)
|
|
EXTERN_CVAR(Bool, m_quickexit)
|
|
EXTERN_CVAR(Bool, saveloadconfirmation) // [mxd]
|
|
EXTERN_CVAR(Bool, quicksaverotation)
|
|
EXTERN_CVAR(Bool, show_messages)
|
|
CVAR(Bool, menu_sounds, true, CVAR_ARCHIVE) // added mainly because RR's sounds are so supremely annoying.
|
|
|
|
typedef void(*hfunc)();
|
|
DMenu* CreateMessageBoxMenu(DMenu* parent, const char* message, int messagemode, bool playsound, FName action = NAME_None, hfunc handler = nullptr);
|
|
bool OkForLocalization(FTextureID texnum, const char* substitute);
|
|
void D_ToggleHud();
|
|
void I_WaitVBL(int count);
|
|
|
|
extern bool hud_toggled;
|
|
bool help_disabled;
|
|
FNewGameStartup NewGameStartupInfo;
|
|
|
|
|
|
|
|
//FNewGameStartup NewGameStartupInfo;
|
|
|
|
static bool DoStartGame(FNewGameStartup& gs)
|
|
{
|
|
MapRecord* map;
|
|
if (gs.Map == nullptr)
|
|
{
|
|
auto vol = FindVolume(gs.Episode);
|
|
if (!vol) return false;
|
|
|
|
if (isShareware() && (vol->flags & VF_SHAREWARELOCK))
|
|
{
|
|
M_StartMessage(GStrings("SHAREWARELOCK"), 1, NAME_None);
|
|
return false;
|
|
}
|
|
|
|
map = FindMapByName(vol->startmap);
|
|
if (!map) return false;
|
|
}
|
|
else
|
|
map = gs.Map;
|
|
|
|
soundEngine->StopAllChannels();
|
|
|
|
gi->StartGame(gs); // play game specific effects (like Duke/RR/SW's voice lines when starting a game.)
|
|
|
|
DeferredStartGame(map, gs.Skill);
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
bool M_SetSpecialMenu(FName& menu, int param)
|
|
{
|
|
// Engine credits need a different approach to work with the option search
|
|
#if 0
|
|
// Transitions between the engine credits pages need to pop off the last slide
|
|
if (!strnicmp(menu.GetChars(), "EngineCredits", 13) && CurrentMenu && !strnicmp(CurrentMenu->GetClass()->TypeName.GetChars(), "EngineCredits", 13))
|
|
{
|
|
auto m = CurrentMenu;
|
|
CurrentMenu = m->mParentMenu;
|
|
m->mParentMenu = nullptr;
|
|
m->Destroy();
|
|
}
|
|
#endif
|
|
|
|
switch (menu.GetIndex())
|
|
{
|
|
case NAME_Mainmenu:
|
|
if (gi->CanSave()) menu = NAME_IngameMenu;
|
|
break;
|
|
|
|
case NAME_Skillmenu:
|
|
// sent from the episode or user map menu
|
|
if (param != INT_MAX)
|
|
{
|
|
NewGameStartupInfo.Map = nullptr;
|
|
NewGameStartupInfo.Episode = param;
|
|
NewGameStartupInfo.Level = 0;
|
|
}
|
|
NewGameStartupInfo.Skill = gDefaultSkill;
|
|
return true;
|
|
|
|
case NAME_Startgame:
|
|
NewGameStartupInfo.Skill = param;
|
|
if (DoStartGame(NewGameStartupInfo))
|
|
{
|
|
M_ClearMenus();
|
|
int ep = NewGameStartupInfo.Episode;
|
|
auto vol = FindVolume(ep);
|
|
if (vol) STAT_StartNewGame(vol->name, NewGameStartupInfo.Skill);
|
|
inputState.ClearAllInput();
|
|
}
|
|
return false;
|
|
|
|
case NAME_Savegamemenu:
|
|
if (!gi->CanSave())
|
|
{
|
|
// cannot save outside the game.
|
|
M_StartMessage(GStrings("SAVEDEAD"), 1, NAME_None);
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case NAME_Quitmenu:
|
|
// This is no separate class
|
|
C_DoCommand("menu_quit");
|
|
return false;
|
|
|
|
case NAME_EndGameMenu:
|
|
// This is no separate class
|
|
C_DoCommand("menu_endgame");
|
|
return false;
|
|
}
|
|
|
|
// End of special checks
|
|
return true;
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
void M_StartControlPanel(bool makeSound, bool)
|
|
{
|
|
// intro might call this repeatedly
|
|
if (CurrentMenu != NULL)
|
|
return;
|
|
|
|
GSnd->SetSfxPaused(true, PAUSESFX_MENU);
|
|
gi->MenuOpened();
|
|
if (makeSound && menu_sounds) gi->MenuSound(ActivateSound);
|
|
M_DoStartControlPanel(false);
|
|
}
|
|
|
|
|
|
void System_MenuClosed()
|
|
{
|
|
GSnd->SetSfxPaused(false, PAUSESFX_MENU);
|
|
inputState.ClearAllInput();
|
|
gi->MenuClosed();
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
//
|
|
//
|
|
//==========================================================================
|
|
|
|
void System_MenuDim()
|
|
{
|
|
if (gamestate != GS_MENUSCREEN) // With GS_MENUSCREEN we can assume that the background has been tuned for proper menu display already.
|
|
{
|
|
Dim(twod, 0, 0.5f, 0, 0, screen->GetWidth(), screen->GetHeight());
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
CCMD(menu_quit)
|
|
{ // F10
|
|
|
|
M_StartControlPanel(true);
|
|
|
|
FString EndString;
|
|
EndString << GStrings("CONFIRM_QUITMSG") << "\n\n" << GStrings("PRESSYN");
|
|
|
|
DMenu* newmenu = CreateMessageBoxMenu(CurrentMenu, EndString, 0, false, NAME_None, []()
|
|
{
|
|
M_ClearMenus();
|
|
gi->ExitFromMenu();
|
|
});
|
|
|
|
M_ActivateMenu(newmenu);
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
CCMD(menu_endgame)
|
|
{ // F7
|
|
if (!gi->CanSave())
|
|
{
|
|
return;
|
|
}
|
|
|
|
M_StartControlPanel(true);
|
|
FString tempstring;
|
|
tempstring << GStrings("ENDGAME") << "\n\n" << GStrings("PRESSYN");
|
|
DMenu* newmenu = CreateMessageBoxMenu(CurrentMenu, tempstring, 0, false, NAME_None, []()
|
|
{
|
|
STAT_Cancel();
|
|
M_ClearMenus();
|
|
Mus_Stop();
|
|
gameaction = ga_mainmenu;
|
|
});
|
|
|
|
M_ActivateMenu(newmenu);
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
CCMD(quicksave)
|
|
{ // F6
|
|
if (!gi->CanSave()) return;
|
|
|
|
if (savegameManager.quickSaveSlot == NULL || savegameManager.quickSaveSlot == (FSaveGameNode*)1)
|
|
{
|
|
M_StartControlPanel(true);
|
|
M_SetMenu(NAME_Savegamemenu);
|
|
return;
|
|
}
|
|
|
|
auto slot = savegameManager.quickSaveSlot;
|
|
|
|
// [mxd]. Just save the game, no questions asked.
|
|
if (!saveloadconfirmation)
|
|
{
|
|
G_SaveGame(savegameManager.quickSaveSlot->Filename, savegameManager.quickSaveSlot->SaveTitle);
|
|
return;
|
|
}
|
|
|
|
FString tempstring = GStrings("QSPROMPT");
|
|
tempstring.Substitute("%s", slot->SaveTitle.GetChars());
|
|
M_StartControlPanel(true);
|
|
|
|
DMenu* newmenu = CreateMessageBoxMenu(CurrentMenu, tempstring, 0, false, NAME_None, []()
|
|
{
|
|
M_ClearMenus();
|
|
G_SaveGame(savegameManager.quickSaveSlot->Filename, savegameManager.quickSaveSlot->SaveTitle);
|
|
});
|
|
|
|
M_ActivateMenu(newmenu);
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
CCMD(quickload)
|
|
{ // F9
|
|
if (netgame)
|
|
{
|
|
M_StartControlPanel(true);
|
|
M_StartMessage(GStrings("QLOADNET"), 1);
|
|
return;
|
|
}
|
|
|
|
if (savegameManager.quickSaveSlot == nullptr || savegameManager.quickSaveSlot == (FSaveGameNode*)1)
|
|
{
|
|
M_StartControlPanel(true);
|
|
// signal that whatever gets loaded should be the new quicksave
|
|
savegameManager.quickSaveSlot = (FSaveGameNode*)1;
|
|
M_SetMenu(NAME_Loadgamemenu);
|
|
return;
|
|
}
|
|
|
|
// [mxd]. Just load the game, no questions asked.
|
|
if (!saveloadconfirmation)
|
|
{
|
|
G_LoadGame(savegameManager.quickSaveSlot->Filename);
|
|
return;
|
|
}
|
|
FString tempstring = GStrings("QLPROMPT");
|
|
tempstring.Substitute("%s", savegameManager.quickSaveSlot->SaveTitle.GetChars());
|
|
|
|
M_StartControlPanel(true);
|
|
|
|
DMenu* newmenu = CreateMessageBoxMenu(CurrentMenu, tempstring, 0, false, NAME_None, []()
|
|
{
|
|
M_ClearMenus();
|
|
G_LoadGame(savegameManager.quickSaveSlot->Filename);
|
|
});
|
|
M_ActivateMenu(newmenu);
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// Creation wrapper
|
|
//
|
|
//=============================================================================
|
|
|
|
static DMenuItemBase* CreateCustomListMenuItemText(double x, double y, int height, int hotkey, const char* text, FFont* font, PalEntry color1, PalEntry color2, FName command, int param)
|
|
{
|
|
const char* classname =
|
|
isBlood() ? "ListMenuItemBloodTextItem" :
|
|
isSWALL() ? "ListMenuItemSWTextItem" :
|
|
(g_gameType & GAMEFLAG_PSEXHUMED) ? "ListMenuItemExhumedTextItem" : "ListMenuItemDukeTextItem";
|
|
auto c = PClass::FindClass(classname);
|
|
auto p = c->CreateNew();
|
|
FString keystr = FString(char(hotkey));
|
|
FString textstr = text;
|
|
VMValue params[] = { p, x, y, height, &keystr, &textstr, font, int(color1.d), int(color2.d), command.GetIndex(), param };
|
|
auto f = dyn_cast<PFunction>(c->FindSymbol("InitDirect", false));
|
|
VMCall(f->Variants[0].Implementation, params, countof(params), nullptr, 0);
|
|
return (DMenuItemBase*)p;
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// Creates the episode menu
|
|
//
|
|
//=============================================================================
|
|
extern TArray<VolumeRecord> volumes;
|
|
|
|
static void BuildEpisodeMenu()
|
|
{
|
|
// Build episode menu
|
|
int addedVolumes = 0;
|
|
bool textadded = false;
|
|
DMenuDescriptor** desc = MenuDescriptors.CheckKey(NAME_Episodemenu);
|
|
if (desc != nullptr && (*desc)->IsKindOf(RUNTIME_CLASS(DListMenuDescriptor)))
|
|
{
|
|
DListMenuDescriptor* ld = static_cast<DListMenuDescriptor*>(*desc);
|
|
|
|
DMenuItemBase* popped = nullptr;
|
|
if (ld->mItems.Size() && ld->mItems.Last()->IsKindOf(NAME_ListMenuItemBloodDripDrawer))
|
|
{
|
|
ld->mItems.Pop(popped);
|
|
}
|
|
|
|
ld->mSelectedItem = gDefaultVolume + ld->mItems.Size(); // account for pre-added items
|
|
double y = ld->mYpos;
|
|
|
|
// Volume definitions should be sorted by intended menu order.
|
|
for (auto &vol : volumes)
|
|
{
|
|
if (vol.name.IsNotEmpty() && !(vol.flags & VF_HIDEFROMSP))
|
|
{
|
|
bool isShareware = ((g_gameType & GAMEFLAG_DUKE) && (g_gameType & GAMEFLAG_SHAREWARE) && (vol.flags & VF_SHAREWARELOCK));
|
|
auto it = CreateCustomListMenuItemText(ld->mXpos, y, ld->mLinespacing, vol.name[0],
|
|
vol.name, ld->mFont, CR_UNTRANSLATED, int(isShareware), NAME_Skillmenu, vol.index); // font colors are not used, so hijack one for the shareware flag.
|
|
|
|
y += ld->mLinespacing;
|
|
ld->mItems.Push(it);
|
|
addedVolumes++;
|
|
if (vol.subtitle.IsNotEmpty())
|
|
{
|
|
auto item = CreateCustomListMenuItemText(ld->mXpos, y, ld->mLinespacing * 6 / 10, 1,
|
|
vol.subtitle, SmallFont, CR_GRAY, false, NAME_None, vol.index);
|
|
y += ld->mLinespacing * 6 / 10;
|
|
ld->mItems.Push(item);
|
|
textadded = true;
|
|
}
|
|
}
|
|
}
|
|
if (!(g_gameType & GAMEFLAG_SHAREWARE))
|
|
{
|
|
y += ld->mLinespacing / 3;
|
|
auto it = CreateCustomListMenuItemText(ld->mXpos, y, ld->mLinespacing, 'U', "$MNU_USERMAP", ld->mFont, CR_UNTRANSLATED, 0, NAME_UsermapMenu, 0);
|
|
ld->mItems.Push(it);
|
|
addedVolumes++;
|
|
}
|
|
|
|
if (addedVolumes == 1)
|
|
{
|
|
ld->mAutoselect = ld->mItems.Size() - (textadded ? 2 : 1);
|
|
}
|
|
if (popped) ld->mItems.Push(popped);
|
|
}
|
|
|
|
// Build skill menu
|
|
int addedSkills = 0;
|
|
desc = MenuDescriptors.CheckKey(NAME_Skillmenu);
|
|
// If the skill names list ios empty, a predefined menu is assumed
|
|
if (desc != nullptr && gSkillNames[0].IsNotEmpty() && (*desc)->IsKindOf(RUNTIME_CLASS(DListMenuDescriptor)))
|
|
{
|
|
DListMenuDescriptor* ld = static_cast<DListMenuDescriptor*>(*desc);
|
|
DMenuItemBase* popped = nullptr;
|
|
if (ld->mItems.Size() && ld->mItems.Last()->IsKindOf(NAME_ListMenuItemBloodDripDrawer))
|
|
{
|
|
ld->mItems.Pop(popped);
|
|
}
|
|
if (isBlood() || isSWALL()) gDefaultSkill = 2;
|
|
ld->mSelectedItem = gDefaultSkill + ld->mItems.Size(); // account for pre-added items
|
|
double y = ld->mYpos;
|
|
|
|
for (int i = 0; i < MAXSKILLS; i++)
|
|
{
|
|
if (gSkillNames[i].IsNotEmpty())
|
|
{
|
|
auto it = CreateCustomListMenuItemText(ld->mXpos, y, ld->mLinespacing, gSkillNames[i][0], gSkillNames[i], ld->mFont, CR_UNTRANSLATED, 0, NAME_Startgame, i);
|
|
y += ld->mLinespacing;
|
|
ld->mItems.Push(it);
|
|
addedSkills++;
|
|
}
|
|
}
|
|
if (addedSkills == 0)
|
|
{
|
|
// Need to add one item with the default skill so that the menu does not break.
|
|
auto it = CreateCustomListMenuItemText(ld->mXpos, y, ld->mLinespacing, 0, "", ld->mFont, 0, 0, NAME_Startgame, gDefaultSkill);
|
|
ld->mItems.Push(it);
|
|
}
|
|
if (addedSkills == 1)
|
|
{
|
|
ld->mAutoselect = ld->mItems.Size() - 1;
|
|
}
|
|
if (popped) ld->mItems.Push(popped);
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// Reads any XHAIRS lumps for the names of crosshairs and
|
|
// adds them to the display options menu.
|
|
//
|
|
//=============================================================================
|
|
|
|
static void InitCrosshairsList()
|
|
{
|
|
int lastlump, lump;
|
|
|
|
lastlump = 0;
|
|
|
|
FOptionValues **opt = OptionValues.CheckKey(NAME_Crosshairs);
|
|
if (opt == nullptr)
|
|
{
|
|
return; // no crosshair value list present. No need to go on.
|
|
}
|
|
|
|
FOptionValues::Pair *pair = &(*opt)->mValues[(*opt)->mValues.Reserve(1)];
|
|
pair->Value = 0;
|
|
pair->Text = "None";
|
|
|
|
while ((lump = fileSystem.FindLump("XHAIRS", &lastlump)) != -1)
|
|
{
|
|
FScanner sc(lump);
|
|
while (sc.GetNumber())
|
|
{
|
|
FOptionValues::Pair value;
|
|
value.Value = sc.Number;
|
|
sc.MustGetString();
|
|
value.Text = sc.String;
|
|
if (value.Value != 0)
|
|
{ // Check if it already exists. If not, add it.
|
|
unsigned int i;
|
|
|
|
for (i = 1; i < (*opt)->mValues.Size(); ++i)
|
|
{
|
|
if ((*opt)->mValues[i].Value == value.Value)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (i < (*opt)->mValues.Size())
|
|
{
|
|
(*opt)->mValues[i].Text = value.Text;
|
|
}
|
|
else
|
|
{
|
|
(*opt)->mValues.Push(value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// Defines how graphics substitution is handled.
|
|
// 0: Never replace a text-containing graphic with a font-based text.
|
|
// 1: Always replace, regardless of any missing information. Useful for testing the substitution without providing full data.
|
|
// 2: Only replace for non-default texts, i.e. if some language redefines the string's content, use it instead of the graphic. Never replace a localized graphic.
|
|
// 3: Only replace if the string is not the default and the graphic comes from the IWAD. Never replace a localized graphic.
|
|
// 4: Like 1, but lets localized graphics pass.
|
|
//
|
|
// The default is 3, which only replaces known content with non-default texts.
|
|
//
|
|
//==========================================================================
|
|
|
|
bool CheckSkipGameOptionBlock(FScanner& sc) { return false; } // not applicable
|
|
|
|
#if 0
|
|
CUSTOM_CVAR(Int, cl_gfxlocalization, 3, CVAR_ARCHIVE)
|
|
{
|
|
if (self < 0 || self > 4) self = 0;
|
|
}
|
|
|
|
bool OkForLocalization(FTextureID texnum, const char* substitute)
|
|
{
|
|
if (!texnum.isValid()) return false;
|
|
|
|
// First the unconditional settings, 0='never' and 1='always'.
|
|
if (cl_gfxlocalization == 1 || gameinfo.forcetextinmenus) return false;
|
|
if (cl_gfxlocalization == 0 || gameinfo.forcenogfxsubstitution) return true;
|
|
return TexMan.OkForLocalization(texnum, substitute, cl_gfxlocalization);
|
|
}
|
|
|
|
#endif
|
|
void SetDefaultMenuColors()
|
|
{
|
|
PClass* cls = nullptr;
|
|
//OptionSettings.mTitleColor = CR_RED;// V_FindFontColor(gameinfo.mTitleColor);
|
|
OptionSettings.mFontColor = CR_RED;
|
|
OptionSettings.mFontColorValue = CR_GRAY;
|
|
OptionSettings.mFontColorMore = CR_GRAY;
|
|
OptionSettings.mFontColorHeader = CR_GOLD;
|
|
OptionSettings.mFontColorHighlight = CR_YELLOW;
|
|
OptionSettings.mFontColorSelection = CR_BRICK;
|
|
gameinfo.mSliderColor = "Orange";
|
|
gameinfo.mSliderBackColor = "White";
|
|
|
|
if (isBlood())
|
|
{
|
|
OptionSettings.mFontColorHeader = CR_DARKGRAY;
|
|
OptionSettings.mFontColorHighlight = CR_WHITE;
|
|
OptionSettings.mFontColorSelection = CR_DARKRED;
|
|
gameinfo.mSliderColor = "Red";
|
|
cls = PClass::FindClass("BloodMenuDelegate");
|
|
}
|
|
else if (isSWALL())
|
|
{
|
|
OptionSettings.mFontColorHeader = CR_DARKRED;
|
|
OptionSettings.mFontColorHighlight = CR_WHITE;
|
|
gameinfo.mSliderColor = "Red";
|
|
cls = PClass::FindClass("SWMenuDelegate");
|
|
}
|
|
else if (g_gameType & GAMEFLAG_PSEXHUMED)
|
|
{
|
|
OptionSettings.mFontColorHeader = CR_LIGHTBLUE;
|
|
OptionSettings.mFontColorHighlight = CR_SAPPHIRE;
|
|
OptionSettings.mFontColorSelection = CR_ORANGE;
|
|
OptionSettings.mFontColor = CR_FIRE;
|
|
gameinfo.mSliderColor = "Yellow";
|
|
cls = PClass::FindClass("ExhumedMenuDelegate");
|
|
}
|
|
else
|
|
{
|
|
if (isNamWW2GI())
|
|
{
|
|
OptionSettings.mFontColor = CR_DARKGREEN;
|
|
OptionSettings.mFontColorHeader = CR_DARKGRAY;
|
|
OptionSettings.mFontColorHighlight = CR_WHITE;
|
|
OptionSettings.mFontColorSelection = CR_DARKGREEN;
|
|
gameinfo.mSliderColor = "Green";
|
|
}
|
|
else if (isRR())
|
|
{
|
|
OptionSettings.mFontColor = CR_BROWN;
|
|
OptionSettings.mFontColorHeader = CR_DARKBROWN;
|
|
OptionSettings.mFontColorHighlight = CR_ORANGE;
|
|
OptionSettings.mFontColorSelection = CR_TAN;
|
|
gameinfo.mSliderColor = "Tan";
|
|
}
|
|
cls = PClass::FindClass("DukeMenuDelegate");
|
|
}
|
|
if (!cls) cls = PClass::FindClass("RazeMenuDelegate");
|
|
if (cls) menuDelegate = cls->CreateNew();
|
|
}
|
|
|
|
void BuildGameMenus()
|
|
{
|
|
BuildEpisodeMenu();
|
|
InitCrosshairsList();
|
|
UpdateJoystickMenu(nullptr);
|
|
}
|
|
|
|
|
|
//=============================================================================
|
|
//
|
|
// [RH] Most menus can now be accessed directly
|
|
// through console commands.
|
|
//
|
|
//=============================================================================
|
|
|
|
EXTERN_CVAR(Int, screenblocks)
|
|
|
|
CCMD(reset2defaults)
|
|
{
|
|
C_SetDefaultBindings();
|
|
C_SetCVarsToDefaults();
|
|
}
|
|
|
|
CCMD(reset2saved)
|
|
{
|
|
GameConfig->DoGlobalSetup();
|
|
GameConfig->DoGameSetup(currentGame);
|
|
}
|
|
|
|
CCMD(menu_main)
|
|
{
|
|
if (gamestate == GS_FULLCONSOLE) gamestate = GS_MENUSCREEN;
|
|
M_StartControlPanel(true);
|
|
M_SetMenu(NAME_Mainmenu, -1);
|
|
}
|
|
|
|
CCMD(openhelpmenu)
|
|
{
|
|
if (!help_disabled)
|
|
{
|
|
M_StartControlPanel(true);
|
|
M_SetMenu(NAME_HelpMenu);
|
|
}
|
|
}
|
|
|
|
CCMD(opensavemenu)
|
|
{
|
|
if (gi->CanSave())
|
|
{
|
|
M_StartControlPanel(true);
|
|
M_SetMenu(NAME_Savegamemenu);
|
|
}
|
|
}
|
|
|
|
CCMD(openloadmenu)
|
|
{
|
|
M_StartControlPanel(true);
|
|
M_SetMenu(NAME_Loadgamemenu);
|
|
}
|
|
|
|
|
|
// The sound system is not yet capable of resolving this properly.
|
|
DEFINE_ACTION_FUNCTION(_RazeMenuDelegate, PlaySound)
|
|
{
|
|
PARAM_SELF_STRUCT_PROLOGUE(void);
|
|
PARAM_NAME(name);
|
|
EMenuSounds soundindex;
|
|
|
|
switch (name.GetIndex())
|
|
{
|
|
case NAME_menu_cursor:
|
|
soundindex = CursorSound;
|
|
break;
|
|
|
|
case NAME_menu_choose:
|
|
soundindex = ChooseSound;
|
|
break;
|
|
|
|
case NAME_menu_backup:
|
|
soundindex = BackSound;
|
|
break;
|
|
|
|
case NAME_menu_clear:
|
|
case NAME_menu_dismiss:
|
|
soundindex = CloseSound;
|
|
break;
|
|
|
|
case NAME_menu_change:
|
|
soundindex = ChangeSound;
|
|
break;
|
|
|
|
case NAME_menu_advance:
|
|
soundindex = AdvanceSound;
|
|
break;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
gi->MenuSound(soundindex);
|
|
return 0;
|
|
}
|
|
|
|
// C_ToggleConsole cannot be exported for security reasons as it can be used to make the engine unresponsive.
|
|
DEFINE_ACTION_FUNCTION(_RazeMenuDelegate, MenuDismissed)
|
|
{
|
|
if (CurrentMenu == nullptr && gamestate == GS_MENUSCREEN) C_ToggleConsole();
|
|
return 0;
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION(_PlayerMenu, DrawPlayerSprite)
|
|
{
|
|
PARAM_PROLOGUE;
|
|
PARAM_INT(selected);
|
|
gi->DrawPlayerSprite(DVector2(0.,0.), selected);
|
|
return 0;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
EXTERN_CVAR(Bool, vr_enable_quadbuffered)
|
|
#endif
|
|
|
|
void UpdateVRModes(bool considerQuadBuffered)
|
|
{
|
|
FOptionValues** pVRModes = OptionValues.CheckKey("VRMode");
|
|
if (pVRModes == nullptr) return;
|
|
|
|
TArray<FOptionValues::Pair>& vals = (*pVRModes)->mValues;
|
|
TArray<FOptionValues::Pair> filteredValues;
|
|
int cnt = vals.Size();
|
|
for (int i = 0; i < cnt; ++i) {
|
|
auto const& mode = vals[i];
|
|
if (mode.Value == 7) { // Quad-buffered stereo
|
|
#ifdef _WIN32
|
|
if (!vr_enable_quadbuffered) continue;
|
|
#else
|
|
continue; // Remove quad-buffered option on Mac and Linux
|
|
#endif
|
|
if (!considerQuadBuffered) continue; // Probably no compatible screen mode was found
|
|
}
|
|
filteredValues.Push(mode);
|
|
}
|
|
vals = filteredValues;
|
|
}
|