mirror of
https://github.com/ZDoom/Raze.git
synced 2024-11-15 17:01:28 +00:00
1ea6084478
Also using proper assets now.
1165 lines
31 KiB
C++
1165 lines
31 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"
|
|
|
|
EXTERN_CVAR(Int, cl_gfxlocalization)
|
|
EXTERN_CVAR(Bool, m_quickexit)
|
|
EXTERN_CVAR(Bool, saveloadconfirmation) // [mxd]
|
|
EXTERN_CVAR(Bool, quicksaverotation)
|
|
EXTERN_CVAR(Bool, show_messages)
|
|
|
|
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;
|
|
|
|
|
|
//FNewGameStartup NewGameStartupInfo;
|
|
|
|
|
|
bool M_SetSpecialMenu(FName& menu, int param)
|
|
{
|
|
#if 0
|
|
// some menus need some special treatment
|
|
switch (menu.GetIndex())
|
|
{
|
|
case NAME_Mainmenu:
|
|
break;
|
|
case NAME_Episodemenu:
|
|
// sent from the player class menu
|
|
break;
|
|
|
|
case NAME_Skillmenu:
|
|
// sent from the episode menu
|
|
break;
|
|
|
|
case NAME_StartgameConfirm:
|
|
{
|
|
// sent from the skill menu for a skill that needs to be confirmed
|
|
return false;
|
|
}
|
|
|
|
case NAME_Startgame:
|
|
// sent either from skill menu or confirmation screen. Skill gets only set if sent from skill menu
|
|
// Now we can finally start the game. Ugh...
|
|
//NewGameStartupInfo.Skill = param;
|
|
case NAME_StartgameConfirmed:
|
|
|
|
//G_DeferedInitNew (&NewGameStartupInfo);
|
|
if (gamestate == GS_FULLCONSOLE)
|
|
{
|
|
gamestate = GS_HIDECONSOLE;
|
|
gameaction = ga_newgame;
|
|
}
|
|
M_ClearMenus ();
|
|
return false;
|
|
|
|
case NAME_Savegamemenu:
|
|
if (!usergame || (players[consoleplayer].health <= 0 && !multiplayer) || gamestate != GS_LEVEL)
|
|
{
|
|
// cannot save outside the game.
|
|
M_StartMessage (GStrings("SAVEDEAD"), 1);
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case NAME_Quitmenu:
|
|
// The separate menu class no longer exists but the name still needs support for existing mods.
|
|
C_DoCommand("menu_quit");
|
|
return false;
|
|
|
|
case NAME_EndGameMenu:
|
|
// The separate menu class no longer exists but the name still needs support for existing mods.
|
|
void ActivateEndGameMenu();
|
|
ActivateEndGameMenu();
|
|
return false;
|
|
|
|
case NAME_Playermenu:
|
|
menu = NAME_NewPlayerMenu; // redirect the old player menu to the new one.
|
|
break;
|
|
}
|
|
|
|
DMenuDescriptor** desc = MenuDescriptors.CheckKey(menu);
|
|
if (desc != nullptr)
|
|
{
|
|
if ((*desc)->mNetgameMessage.IsNotEmpty() && netgame && !demoplayback)
|
|
{
|
|
M_StartMessage((*desc)->mNetgameMessage, 1);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
#endif
|
|
// End of special checks
|
|
return true;
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
void M_StartControlPanel(bool makeSound, bool scaleoverride)
|
|
{
|
|
#if 0
|
|
if (hud_toggled)
|
|
D_ToggleHud();
|
|
|
|
// intro might call this repeatedly
|
|
if (CurrentMenu != nullptr)
|
|
return;
|
|
|
|
P_CheckTickerPaused();
|
|
|
|
if (makeSound)
|
|
{
|
|
S_Sound(CHAN_VOICE, CHANF_UI, "menu/activate", snd_menuvolume, ATTN_NONE);
|
|
}
|
|
M_DoStartControlPanel(scaleoverride);
|
|
#endif
|
|
}
|
|
|
|
|
|
//==========================================================================
|
|
//
|
|
// M_Dim
|
|
//
|
|
// Applies a colored overlay to the entire screen, with the opacity
|
|
// determined by the dimamount cvar.
|
|
//
|
|
//==========================================================================
|
|
|
|
CUSTOM_CVAR(Float, dimamount, -1.f, CVAR_ARCHIVE)
|
|
{
|
|
if (self < 0.f && self != -1.f)
|
|
{
|
|
self = -1.f;
|
|
}
|
|
else if (self > 1.f)
|
|
{
|
|
self = 1.f;
|
|
}
|
|
}
|
|
CVAR(Color, dimcolor, 0xffd700, CVAR_ARCHIVE)
|
|
|
|
void System_M_Dim()
|
|
{
|
|
#if 0
|
|
PalEntry dimmer;
|
|
float amount;
|
|
|
|
if (dimamount >= 0)
|
|
{
|
|
dimmer = PalEntry(dimcolor);
|
|
amount = dimamount;
|
|
}
|
|
else
|
|
{
|
|
dimmer = gameinfo.dimcolor;
|
|
amount = gameinfo.dimamount;
|
|
}
|
|
|
|
Dim(twod, dimmer, amount, 0, 0, twod->GetWidth(), twod->GetHeight());
|
|
#endif
|
|
}
|
|
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
CCMD (menu_quit)
|
|
{ // F10
|
|
#if 0
|
|
if (m_quickexit)
|
|
{
|
|
ST_Endoom();
|
|
}
|
|
|
|
M_StartControlPanel (true);
|
|
|
|
const size_t messageindex = static_cast<size_t>(gametic) % gameinfo.quitmessages.Size();
|
|
FString EndString;
|
|
const char *msg = gameinfo.quitmessages[messageindex];
|
|
if (msg[0] == '$')
|
|
{
|
|
if (msg[1] == '*')
|
|
{
|
|
EndString = GStrings(msg + 2);
|
|
}
|
|
else
|
|
{
|
|
EndString.Format("%s\n\n%s", GStrings(msg + 1), GStrings("DOSY"));
|
|
}
|
|
}
|
|
else EndString = gameinfo.quitmessages[messageindex];
|
|
|
|
DMenu *newmenu = CreateMessageBoxMenu(CurrentMenu, EndString, 0, false, NAME_None, []()
|
|
{
|
|
if (!netgame)
|
|
{
|
|
if (gameinfo.quitSound.IsNotEmpty())
|
|
{
|
|
S_Sound(CHAN_VOICE, CHANF_UI, gameinfo.quitSound, snd_menuvolume, ATTN_NONE);
|
|
I_WaitVBL(105);
|
|
}
|
|
}
|
|
ST_Endoom();
|
|
});
|
|
|
|
|
|
M_ActivateMenu(newmenu);
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
void ActivateEndGameMenu()
|
|
{
|
|
#if 0
|
|
FString tempstring = GStrings(netgame ? "NETEND" : "ENDGAME");
|
|
DMenu *newmenu = CreateMessageBoxMenu(CurrentMenu, tempstring, 0, false, NAME_None, []()
|
|
{
|
|
M_ClearMenus();
|
|
if (!netgame)
|
|
{
|
|
if (demorecording)
|
|
G_CheckDemoStatus();
|
|
D_StartTitle();
|
|
}
|
|
});
|
|
|
|
M_ActivateMenu(newmenu);
|
|
#endif
|
|
}
|
|
|
|
CCMD (menu_endgame)
|
|
{ // F7
|
|
#if 0
|
|
if (!usergame)
|
|
{
|
|
S_Sound (CHAN_VOICE, CHANF_UI, "menu/invalid", snd_menuvolume, ATTN_NONE);
|
|
return;
|
|
}
|
|
|
|
//M_StartControlPanel (true);
|
|
S_Sound (CHAN_VOICE, CHANF_UI, "menu/activate", snd_menuvolume, ATTN_NONE);
|
|
|
|
ActivateEndGameMenu();
|
|
#endif
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
CCMD (quicksave)
|
|
{ // F6
|
|
#if 0
|
|
if (!usergame || (players[consoleplayer].health <= 0 && !multiplayer))
|
|
{
|
|
S_Sound (CHAN_VOICE, CHANF_UI, "menu/invalid", snd_menuvolume, ATTN_NONE);
|
|
return;
|
|
}
|
|
|
|
if (gamestate != GS_LEVEL)
|
|
return;
|
|
|
|
// If the quick save rotation is enabled, it handles the save slot.
|
|
if (quicksaverotation)
|
|
{
|
|
G_DoQuickSave();
|
|
return;
|
|
}
|
|
|
|
if (savegameManager.quickSaveSlot == NULL || savegameManager.quickSaveSlot == (FSaveGameNode*)1)
|
|
{
|
|
S_Sound(CHAN_VOICE, CHANF_UI, "menu/activate", snd_menuvolume, ATTN_NONE);
|
|
M_StartControlPanel(false);
|
|
M_SetMenu(NAME_Savegamemenu);
|
|
return;
|
|
}
|
|
|
|
// [mxd]. Just save the game, no questions asked.
|
|
if (!saveloadconfirmation)
|
|
{
|
|
G_SaveGame(savegameManager.quickSaveSlot->Filename.GetChars(), savegameManager.quickSaveSlot->SaveTitle.GetChars());
|
|
return;
|
|
}
|
|
|
|
S_Sound(CHAN_VOICE, CHANF_UI, "menu/activate", snd_menuvolume, ATTN_NONE);
|
|
|
|
FString tempstring = GStrings("QSPROMPT");
|
|
tempstring.Substitute("%s", savegameManager.quickSaveSlot->SaveTitle.GetChars());
|
|
|
|
DMenu *newmenu = CreateMessageBoxMenu(CurrentMenu, tempstring, 0, false, NAME_None, []()
|
|
{
|
|
G_SaveGame(savegameManager.quickSaveSlot->Filename.GetChars(), savegameManager.quickSaveSlot->SaveTitle.GetChars());
|
|
S_Sound(CHAN_VOICE, CHANF_UI, "menu/dismiss", snd_menuvolume, ATTN_NONE);
|
|
M_ClearMenus();
|
|
});
|
|
|
|
M_ActivateMenu(newmenu);
|
|
#endif
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
CCMD (quickload)
|
|
{ // F9
|
|
#if 0
|
|
if (netgame)
|
|
{
|
|
M_StartControlPanel(true);
|
|
M_StartMessage (GStrings("QLOADNET"), 1);
|
|
return;
|
|
}
|
|
|
|
if (savegameManager.quickSaveSlot == NULL || 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.GetChars());
|
|
return;
|
|
}
|
|
FString tempstring = GStrings("QLPROMPT");
|
|
tempstring.Substitute("%s", savegameManager.quickSaveSlot->SaveTitle.GetChars());
|
|
|
|
M_StartControlPanel(true);
|
|
|
|
DMenu *newmenu = CreateMessageBoxMenu(CurrentMenu, tempstring, 0, false, NAME_None, []()
|
|
{
|
|
G_LoadGame(savegameManager.quickSaveSlot->Filename.GetChars());
|
|
S_Sound(CHAN_VOICE, CHANF_UI, "menu/dismiss", snd_menuvolume, ATTN_NONE);
|
|
M_ClearMenus();
|
|
});
|
|
M_ActivateMenu(newmenu);
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
|
|
EXTERN_CVAR (Int, screenblocks)
|
|
|
|
#if 0
|
|
CCMD (sizedown)
|
|
{
|
|
screenblocks = screenblocks - 1;
|
|
S_Sound (CHAN_VOICE, CHANF_UI, "menu/change", snd_menuvolume, ATTN_NONE);
|
|
}
|
|
|
|
CCMD (sizeup)
|
|
{
|
|
screenblocks = screenblocks + 1;
|
|
S_Sound (CHAN_VOICE, CHANF_UI, "menu/change", snd_menuvolume, ATTN_NONE);
|
|
}
|
|
|
|
CCMD(reset2defaults)
|
|
{
|
|
C_SetDefaultBindings ();
|
|
C_SetCVarsToDefaults ();
|
|
R_SetViewSize (screenblocks);
|
|
}
|
|
|
|
CCMD(reset2saved)
|
|
{
|
|
GameConfig->DoGlobalSetup ();
|
|
GameConfig->DoGameSetup (gameinfo.ConfigName);
|
|
GameConfig->DoModSetup (gameinfo.ConfigName);
|
|
R_SetViewSize (screenblocks);
|
|
}
|
|
|
|
CCMD(resetb2defaults)
|
|
{
|
|
C_SetDefaultBindings ();
|
|
}
|
|
#endif
|
|
|
|
//=============================================================================
|
|
//
|
|
// Creates the episode menu
|
|
// Falls back on an option menu if there's not enough screen space to show all episodes
|
|
//
|
|
//=============================================================================
|
|
|
|
#if 0
|
|
void M_StartupEpisodeMenu(FNewGameStartup *gs)
|
|
{
|
|
// Build episode menu
|
|
bool success = false;
|
|
bool isOld = false;
|
|
DMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_Episodemenu);
|
|
if (desc != nullptr)
|
|
{
|
|
if ((*desc)->IsKindOf(RUNTIME_CLASS(DListMenuDescriptor)))
|
|
{
|
|
DListMenuDescriptor *ld = static_cast<DListMenuDescriptor*>(*desc);
|
|
|
|
// Delete previous contents
|
|
for(unsigned i=0; i<ld->mItems.Size(); i++)
|
|
{
|
|
FName n = ld->mItems[i]->mAction;
|
|
if (n == NAME_Skillmenu)
|
|
{
|
|
isOld = true;
|
|
ld->mItems.Resize(i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
int posx = (int)ld->mXpos;
|
|
int posy = (int)ld->mYpos;
|
|
int topy = posy;
|
|
|
|
// Get lowest y coordinate of any static item in the menu
|
|
for(unsigned i = 0; i < ld->mItems.Size(); i++)
|
|
{
|
|
int y = (int)ld->mItems[i]->GetY();
|
|
if (y < topy) topy = y;
|
|
}
|
|
|
|
// center the menu on the screen if the top space is larger than the bottom space
|
|
int totalheight = posy + AllEpisodes.Size() * ld->mLinespacing - topy;
|
|
|
|
if (totalheight < 190 || AllEpisodes.Size() == 1)
|
|
{
|
|
int newtop = (200 - totalheight) / 2;
|
|
int topdelta = newtop - topy;
|
|
if (topdelta < 0)
|
|
{
|
|
for(unsigned i = 0; i < ld->mItems.Size(); i++)
|
|
{
|
|
ld->mItems[i]->OffsetPositionY(topdelta);
|
|
}
|
|
posy += topdelta;
|
|
ld->mYpos += topdelta;
|
|
}
|
|
|
|
if (!isOld) ld->mSelectedItem = ld->mItems.Size();
|
|
|
|
for (unsigned i = 0; i < AllEpisodes.Size(); i++)
|
|
{
|
|
DMenuItemBase *it = nullptr;
|
|
if (AllEpisodes[i].mPicName.IsNotEmpty())
|
|
{
|
|
FTextureID tex = GetMenuTexture(AllEpisodes[i].mPicName);
|
|
if (AllEpisodes[i].mEpisodeName.IsEmpty() || OkForLocalization(tex, AllEpisodes[i].mEpisodeName))
|
|
continue; // We do not measure patch based entries. They are assumed to fit
|
|
}
|
|
const char *c = AllEpisodes[i].mEpisodeName;
|
|
if (*c == '$') c = GStrings(c + 1);
|
|
int textwidth = ld->mFont->StringWidth(c);
|
|
int textright = posx + textwidth;
|
|
if (posx + textright > 320) posx = std::max(0, 320 - textright);
|
|
}
|
|
|
|
for(unsigned i = 0; i < AllEpisodes.Size(); i++)
|
|
{
|
|
DMenuItemBase *it = nullptr;
|
|
if (AllEpisodes[i].mPicName.IsNotEmpty())
|
|
{
|
|
FTextureID tex = GetMenuTexture(AllEpisodes[i].mPicName);
|
|
if (AllEpisodes[i].mEpisodeName.IsEmpty() || OkForLocalization(tex, AllEpisodes[i].mEpisodeName))
|
|
it = CreateListMenuItemPatch(posx, posy, ld->mLinespacing, AllEpisodes[i].mShortcut, tex, NAME_Skillmenu, i);
|
|
}
|
|
if (it == nullptr)
|
|
{
|
|
it = CreateListMenuItemText(posx, posy, ld->mLinespacing, AllEpisodes[i].mShortcut,
|
|
AllEpisodes[i].mEpisodeName, ld->mFont, ld->mFontColor, ld->mFontColor2, NAME_Skillmenu, i);
|
|
}
|
|
ld->mItems.Push(it);
|
|
posy += ld->mLinespacing;
|
|
}
|
|
if (AllEpisodes.Size() == 1)
|
|
{
|
|
ld->mAutoselect = ld->mSelectedItem;
|
|
}
|
|
success = true;
|
|
for (auto &p : ld->mItems)
|
|
{
|
|
GC::WriteBarrier(*desc, p);
|
|
}
|
|
}
|
|
}
|
|
else return; // do not recreate the option menu variant, because it is always text based.
|
|
}
|
|
if (!success)
|
|
{
|
|
// Couldn't create the episode menu, either because there's too many episodes or some error occured
|
|
// Create an option menu for episode selection instead.
|
|
DOptionMenuDescriptor *od = Create<DOptionMenuDescriptor>();
|
|
MenuDescriptors[NAME_Episodemenu] = od;
|
|
od->mMenuName = NAME_Episodemenu;
|
|
od->mFont = gameinfo.gametype == GAME_Doom ? BigUpper : BigFont;
|
|
od->mTitle = "$MNU_EPISODE";
|
|
od->mSelectedItem = 0;
|
|
od->mScrollPos = 0;
|
|
od->mClass = nullptr;
|
|
od->mPosition = -15;
|
|
od->mScrollTop = 0;
|
|
od->mIndent = 160;
|
|
od->mDontDim = false;
|
|
GC::WriteBarrier(od);
|
|
for(unsigned i = 0; i < AllEpisodes.Size(); i++)
|
|
{
|
|
auto it = CreateOptionMenuItemSubmenu(AllEpisodes[i].mEpisodeName, "Skillmenu", i);
|
|
od->mItems.Push(it);
|
|
GC::WriteBarrier(od, it);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
static void BuildPlayerclassMenu()
|
|
{
|
|
#if 0
|
|
bool success = false;
|
|
|
|
// Build player class menu
|
|
DMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_Playerclassmenu);
|
|
if (desc != nullptr)
|
|
{
|
|
if ((*desc)->IsKindOf(RUNTIME_CLASS(DListMenuDescriptor)))
|
|
{
|
|
DListMenuDescriptor *ld = static_cast<DListMenuDescriptor*>(*desc);
|
|
// add player display
|
|
|
|
ld->mSelectedItem = ld->mItems.Size();
|
|
|
|
int posy = (int)ld->mYpos;
|
|
int topy = posy;
|
|
|
|
// Get lowest y coordinate of any static item in the menu
|
|
for(unsigned i = 0; i < ld->mItems.Size(); i++)
|
|
{
|
|
int y = (int)ld->mItems[i]->GetY();
|
|
if (y < topy) topy = y;
|
|
}
|
|
|
|
// Count the number of items this menu will show
|
|
int numclassitems = 0;
|
|
for (unsigned i = 0; i < PlayerClasses.Size (); i++)
|
|
{
|
|
if (!(PlayerClasses[i].Flags & PCF_NOMENU))
|
|
{
|
|
const char *pname = GetPrintableDisplayName(PlayerClasses[i].Type);
|
|
if (pname != nullptr)
|
|
{
|
|
numclassitems++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// center the menu on the screen if the top space is larger than the bottom space
|
|
int totalheight = posy + (numclassitems+1) * ld->mLinespacing - topy;
|
|
|
|
if (numclassitems <= 1)
|
|
{
|
|
// create a dummy item that auto-chooses the default class.
|
|
auto it = CreateListMenuItemText(0, 0, 0, 'p', "player",
|
|
ld->mFont,ld->mFontColor, ld->mFontColor2, NAME_Episodemenu, -1000);
|
|
ld->mAutoselect = ld->mItems.Push(it);
|
|
success = true;
|
|
}
|
|
else if (totalheight <= 190)
|
|
{
|
|
int newtop = (200 - totalheight + topy) / 2;
|
|
int topdelta = newtop - topy;
|
|
if (topdelta < 0)
|
|
{
|
|
for(unsigned i = 0; i < ld->mItems.Size(); i++)
|
|
{
|
|
ld->mItems[i]->OffsetPositionY(topdelta);
|
|
}
|
|
posy -= topdelta;
|
|
}
|
|
|
|
int n = 0;
|
|
for (unsigned i = 0; i < PlayerClasses.Size (); i++)
|
|
{
|
|
if (!(PlayerClasses[i].Flags & PCF_NOMENU))
|
|
{
|
|
const char *pname = GetPrintableDisplayName(PlayerClasses[i].Type);
|
|
if (pname != nullptr)
|
|
{
|
|
auto it = CreateListMenuItemText(ld->mXpos, ld->mYpos, ld->mLinespacing, *pname,
|
|
pname, ld->mFont,ld->mFontColor,ld->mFontColor2, NAME_Episodemenu, i);
|
|
ld->mItems.Push(it);
|
|
ld->mYpos += ld->mLinespacing;
|
|
n++;
|
|
}
|
|
}
|
|
}
|
|
if (n > 1 && !gameinfo.norandomplayerclass)
|
|
{
|
|
auto it = CreateListMenuItemText(ld->mXpos, ld->mYpos, ld->mLinespacing, 'r',
|
|
"$MNU_RANDOM", ld->mFont,ld->mFontColor,ld->mFontColor2, NAME_Episodemenu, -1);
|
|
ld->mItems.Push(it);
|
|
}
|
|
if (n == 0)
|
|
{
|
|
const char *pname = GetPrintableDisplayName(PlayerClasses[0].Type);
|
|
if (pname != nullptr)
|
|
{
|
|
auto it = CreateListMenuItemText(ld->mXpos, ld->mYpos, ld->mLinespacing, *pname,
|
|
pname, ld->mFont,ld->mFontColor,ld->mFontColor2, NAME_Episodemenu, 0);
|
|
ld->mItems.Push(it);
|
|
}
|
|
}
|
|
success = true;
|
|
for (auto &p : ld->mItems)
|
|
{
|
|
GC::WriteBarrier(ld, p);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!success)
|
|
{
|
|
// Couldn't create the playerclass menu, either because there's too many episodes or some error occured
|
|
// Create an option menu for class selection instead.
|
|
DOptionMenuDescriptor *od = Create<DOptionMenuDescriptor>();
|
|
MenuDescriptors[NAME_Playerclassmenu] = od;
|
|
od->mMenuName = NAME_Playerclassmenu;
|
|
od->mFont = gameinfo.gametype == GAME_Doom ? BigUpper : BigFont;
|
|
od->mTitle = "$MNU_CHOOSECLASS";
|
|
od->mSelectedItem = 0;
|
|
od->mScrollPos = 0;
|
|
od->mClass = nullptr;
|
|
od->mPosition = -15;
|
|
od->mScrollTop = 0;
|
|
od->mIndent = 160;
|
|
od->mDontDim = false;
|
|
od->mNetgameMessage = "$NEWGAME";
|
|
GC::WriteBarrier(od);
|
|
for (unsigned i = 0; i < PlayerClasses.Size (); i++)
|
|
{
|
|
if (!(PlayerClasses[i].Flags & PCF_NOMENU))
|
|
{
|
|
const char *pname = GetPrintableDisplayName(PlayerClasses[i].Type);
|
|
if (pname != nullptr)
|
|
{
|
|
auto it = CreateOptionMenuItemSubmenu(pname, "Episodemenu", i);
|
|
od->mItems.Push(it);
|
|
GC::WriteBarrier(od, it);
|
|
}
|
|
}
|
|
}
|
|
auto it = CreateOptionMenuItemSubmenu("Random", "Episodemenu", -1);
|
|
od->mItems.Push(it);
|
|
GC::WriteBarrier(od, it);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// Reads any XHAIRS lumps for the names of crosshairs and
|
|
// adds them to the display options menu.
|
|
//
|
|
//=============================================================================
|
|
|
|
static void InitCrosshairsList()
|
|
{
|
|
#if 0
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// Special menus will be created once all engine data is loaded
|
|
//
|
|
//=============================================================================
|
|
|
|
void M_CreateGameMenus()
|
|
{
|
|
#if 0
|
|
BuildPlayerclassMenu();
|
|
InitCrosshairsList();
|
|
|
|
auto opt = OptionValues.CheckKey(NAME_PlayerTeam);
|
|
if (opt != nullptr)
|
|
{
|
|
auto op = *opt;
|
|
op->mValues.Resize(Teams.Size() + 1);
|
|
op->mValues[0].Value = 0;
|
|
op->mValues[0].Text = "$OPTVAL_NONE";
|
|
for (unsigned i = 0; i < Teams.Size(); i++)
|
|
{
|
|
op->mValues[i+1].Value = i+1;
|
|
op->mValues[i+1].Text = Teams[i].GetName();
|
|
}
|
|
}
|
|
opt = OptionValues.CheckKey(NAME_PlayerClass);
|
|
if (opt != nullptr)
|
|
{
|
|
auto op = *opt;
|
|
int o = 0;
|
|
if (!gameinfo.norandomplayerclass && PlayerClasses.Size() > 1)
|
|
{
|
|
op->mValues.Resize(PlayerClasses.Size()+1);
|
|
op->mValues[0].Value = -1;
|
|
op->mValues[0].Text = "$MNU_RANDOM";
|
|
o = 1;
|
|
}
|
|
else op->mValues.Resize(PlayerClasses.Size());
|
|
for (unsigned i = 0; i < PlayerClasses.Size(); i++)
|
|
{
|
|
op->mValues[i+o].Value = i;
|
|
op->mValues[i+o].Text = GetPrintableDisplayName(PlayerClasses[i].Type);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
//=============================================================================
|
|
//
|
|
// The skill menu must be refeshed each time it starts up
|
|
//
|
|
//=============================================================================
|
|
extern int restart;
|
|
|
|
#if 0
|
|
void M_StartupSkillMenu(FNewGameStartup *gs)
|
|
{
|
|
static int done = -1;
|
|
bool success = false;
|
|
TArray<FSkillInfo*> MenuSkills;
|
|
TArray<int> SkillIndices;
|
|
if (MenuSkills.Size() == 0)
|
|
{
|
|
for (unsigned ind = 0; ind < AllSkills.Size(); ind++)
|
|
{
|
|
if (!AllSkills[ind].NoMenu)
|
|
{
|
|
MenuSkills.Push(&AllSkills[ind]);
|
|
SkillIndices.Push(ind);
|
|
}
|
|
}
|
|
}
|
|
if (MenuSkills.Size() == 0) I_Error("No valid skills for menu found. At least one must be defined.");
|
|
|
|
int defskill = DefaultSkill;
|
|
if ((unsigned int)defskill >= MenuSkills.Size())
|
|
{
|
|
defskill = SkillIndices[(MenuSkills.Size() - 1) / 2];
|
|
}
|
|
if (AllSkills[defskill].NoMenu)
|
|
{
|
|
for (defskill = 0; defskill < (int)AllSkills.Size(); defskill++)
|
|
{
|
|
if (!AllSkills[defskill].NoMenu) break;
|
|
}
|
|
}
|
|
int defindex = 0;
|
|
for (unsigned i = 0; i < MenuSkills.Size(); i++)
|
|
{
|
|
if (MenuSkills[i] == &AllSkills[defskill])
|
|
{
|
|
defindex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
DMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_Skillmenu);
|
|
if (desc != nullptr)
|
|
{
|
|
if ((*desc)->IsKindOf(RUNTIME_CLASS(DListMenuDescriptor)))
|
|
{
|
|
DListMenuDescriptor *ld = static_cast<DListMenuDescriptor*>(*desc);
|
|
int posx = (int)ld->mXpos;
|
|
int y = (int)ld->mYpos;
|
|
|
|
// Delete previous contents
|
|
for(unsigned i=0; i<ld->mItems.Size(); i++)
|
|
{
|
|
FName n = ld->mItems[i]->mAction;
|
|
if (n == NAME_Startgame || n == NAME_StartgameConfirm)
|
|
{
|
|
ld->mItems.Resize(i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (done != restart)
|
|
{
|
|
done = restart;
|
|
ld->mSelectedItem = ld->mItems.Size() + defindex;
|
|
|
|
int posy = y;
|
|
int topy = posy;
|
|
|
|
// Get lowest y coordinate of any static item in the menu
|
|
for(unsigned i = 0; i < ld->mItems.Size(); i++)
|
|
{
|
|
int y = (int)ld->mItems[i]->GetY();
|
|
if (y < topy) topy = y;
|
|
}
|
|
|
|
// center the menu on the screen if the top space is larger than the bottom space
|
|
int totalheight = posy + MenuSkills.Size() * ld->mLinespacing - topy;
|
|
|
|
if (totalheight < 190 || MenuSkills.Size() == 1)
|
|
{
|
|
int newtop = (200 - totalheight) / 2;
|
|
int topdelta = newtop - topy;
|
|
if (topdelta < 0)
|
|
{
|
|
for(unsigned i = 0; i < ld->mItems.Size(); i++)
|
|
{
|
|
ld->mItems[i]->OffsetPositionY(topdelta);
|
|
}
|
|
ld->mYpos = y = posy + topdelta;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// too large
|
|
desc = nullptr;
|
|
done = false;
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
for (unsigned int i = 0; i < MenuSkills.Size(); i++)
|
|
{
|
|
FSkillInfo &skill = *MenuSkills[i];
|
|
DMenuItemBase *li = nullptr;
|
|
|
|
FString *pItemText = nullptr;
|
|
if (gs->PlayerClass != nullptr)
|
|
{
|
|
pItemText = skill.MenuNamesForPlayerClass.CheckKey(gs->PlayerClass);
|
|
}
|
|
|
|
if (skill.PicName.Len() != 0 && pItemText == nullptr)
|
|
{
|
|
FTextureID tex = GetMenuTexture(skill.PicName);
|
|
if (skill.MenuName.IsEmpty() || OkForLocalization(tex, skill.MenuName))
|
|
continue;
|
|
}
|
|
const char *c = pItemText ? pItemText->GetChars() : skill.MenuName.GetChars();
|
|
if (*c == '$') c = GStrings(c + 1);
|
|
int textwidth = ld->mFont->StringWidth(c);
|
|
int textright = posx + textwidth;
|
|
if (posx + textright > 320) posx = std::max(0, 320 - textright);
|
|
}
|
|
|
|
unsigned firstitem = ld->mItems.Size();
|
|
for(unsigned int i = 0; i < MenuSkills.Size(); i++)
|
|
{
|
|
FSkillInfo &skill = *MenuSkills[i];
|
|
DMenuItemBase *li = nullptr;
|
|
// Using a different name for skills that must be confirmed makes handling this easier.
|
|
FName action = (skill.MustConfirm && !AllEpisodes[gs->Episode].mNoSkill) ?
|
|
NAME_StartgameConfirm : NAME_Startgame;
|
|
FString *pItemText = nullptr;
|
|
if (gs->PlayerClass != nullptr)
|
|
{
|
|
pItemText = skill.MenuNamesForPlayerClass.CheckKey(gs->PlayerClass);
|
|
}
|
|
|
|
EColorRange color = (EColorRange)skill.GetTextColor();
|
|
if (color == CR_UNTRANSLATED) color = ld->mFontColor;
|
|
if (skill.PicName.Len() != 0 && pItemText == nullptr)
|
|
{
|
|
FTextureID tex = GetMenuTexture(skill.PicName);
|
|
if (skill.MenuName.IsEmpty() || OkForLocalization(tex, skill.MenuName))
|
|
li = CreateListMenuItemPatch(posx, y, ld->mLinespacing, skill.Shortcut, tex, action, SkillIndices[i]);
|
|
}
|
|
if (li == nullptr)
|
|
{
|
|
li = CreateListMenuItemText(posx, y, ld->mLinespacing, skill.Shortcut,
|
|
pItemText? *pItemText : skill.MenuName, ld->mFont, color,ld->mFontColor2, action, SkillIndices[i]);
|
|
}
|
|
ld->mItems.Push(li);
|
|
GC::WriteBarrier(*desc, li);
|
|
y += ld->mLinespacing;
|
|
}
|
|
if (AllEpisodes[gs->Episode].mNoSkill || MenuSkills.Size() == 1)
|
|
{
|
|
ld->mAutoselect = firstitem + defindex;
|
|
}
|
|
else
|
|
{
|
|
ld->mAutoselect = -1;
|
|
}
|
|
success = true;
|
|
}
|
|
}
|
|
if (success) return;
|
|
fail:
|
|
// Option menu fallback for overlong skill lists
|
|
DOptionMenuDescriptor *od;
|
|
if (desc == nullptr)
|
|
{
|
|
od = Create<DOptionMenuDescriptor>();
|
|
MenuDescriptors[NAME_Skillmenu] = od;
|
|
od->mMenuName = NAME_Skillmenu;
|
|
od->mFont = gameinfo.gametype == GAME_Doom ? BigUpper : BigFont;
|
|
od->mTitle = "$MNU_CHOOSESKILL";
|
|
od->mSelectedItem = defindex;
|
|
od->mScrollPos = 0;
|
|
od->mClass = nullptr;
|
|
od->mPosition = -15;
|
|
od->mScrollTop = 0;
|
|
od->mIndent = 160;
|
|
od->mDontDim = false;
|
|
GC::WriteBarrier(od);
|
|
}
|
|
else
|
|
{
|
|
od = static_cast<DOptionMenuDescriptor*>(*desc);
|
|
od->mItems.Clear();
|
|
}
|
|
for(unsigned int i = 0; i < MenuSkills.Size(); i++)
|
|
{
|
|
FSkillInfo &skill = *MenuSkills[i];
|
|
DMenuItemBase *li;
|
|
// Using a different name for skills that must be confirmed makes handling this easier.
|
|
const char *action = (skill.MustConfirm && !AllEpisodes[gs->Episode].mNoSkill) ?
|
|
"StartgameConfirm" : "Startgame";
|
|
|
|
FString *pItemText = nullptr;
|
|
if (gs->PlayerClass != nullptr)
|
|
{
|
|
pItemText = skill.MenuNamesForPlayerClass.CheckKey(gs->PlayerClass);
|
|
}
|
|
li = CreateOptionMenuItemSubmenu(pItemText? *pItemText : skill.MenuName, action, SkillIndices[i]);
|
|
od->mItems.Push(li);
|
|
GC::WriteBarrier(od, li);
|
|
if (!done)
|
|
{
|
|
done = true;
|
|
od->mSelectedItem = defindex;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
//==========================================================================
|
|
//
|
|
// 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; }
|
|
|
|
#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);
|
|
}
|
|
|
|
bool CheckSkipGameOptionBlock(FScanner &sc)
|
|
{
|
|
bool filter = false;
|
|
if (sc.Compare("ReadThis")) filter |= gameinfo.drawreadthis;
|
|
else if (sc.Compare("Swapmenu")) filter |= gameinfo.swapmenu;
|
|
return filter;
|
|
}
|
|
#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;
|
|
|
|
if (g_gameType & GAMEFLAG_BLOOD)
|
|
{
|
|
OptionSettings.mFontColorHeader = CR_DARKGRAY;
|
|
OptionSettings.mFontColorHighlight = CR_WHITE;
|
|
OptionSettings.mFontColorSelection = CR_DARKRED;
|
|
cls = PClass::FindClass("MenuCustomizerBlood");
|
|
}
|
|
else if (g_gameType & GAMEFLAG_SW)
|
|
{
|
|
OptionSettings.mFontColorHeader = CR_DARKRED;
|
|
OptionSettings.mFontColorHighlight = CR_WHITE;
|
|
cls = PClass::FindClass("MenuCustomizerSW");
|
|
}
|
|
else if (g_gameType & GAMEFLAG_PSEXHUMED)
|
|
{
|
|
OptionSettings.mFontColorHeader = CR_LIGHTBLUE;
|
|
OptionSettings.mFontColorHighlight = CR_SAPPHIRE;
|
|
OptionSettings.mFontColorSelection = CR_ORANGE;
|
|
OptionSettings.mFontColor = CR_FIRE;
|
|
cls = PClass::FindClass("MenuCustomizerExhumed");
|
|
}
|
|
else
|
|
{
|
|
if (g_gameType & (GAMEFLAG_NAM | GAMEFLAG_NAPALM | GAMEFLAG_WW2GI))
|
|
{
|
|
OptionSettings.mFontColor = CR_DARKGREEN;
|
|
OptionSettings.mFontColorHeader = CR_DARKGRAY;
|
|
OptionSettings.mFontColorHighlight = CR_WHITE;
|
|
OptionSettings.mFontColorSelection = CR_DARKGREEN;
|
|
}
|
|
else if (g_gameType & GAMEFLAG_RRALL)
|
|
{
|
|
OptionSettings.mFontColor = CR_BROWN;
|
|
OptionSettings.mFontColorHeader = CR_DARKBROWN;
|
|
OptionSettings.mFontColorHighlight = CR_ORANGE;
|
|
OptionSettings.mFontColorSelection = CR_TAN;
|
|
}
|
|
cls = PClass::FindClass("MenuCustomizerDuke");
|
|
}
|
|
if (!cls) cls = PClass::FindClass("MenuCustomize");
|
|
if (cls) menuCustomizer = cls->CreateNew();
|
|
|
|
}
|