2020-10-04 16:31:48 +00:00
/*
* * 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"
2020-10-04 20:21:11 +00:00
# include "gamecontrol.h"
2020-10-06 21:49:34 +00:00
# include "raze_sound.h"
# include "gamestruct.h"
2020-10-06 23:00:43 +00:00
# include "razemenu.h"
2020-10-04 16:31:48 +00:00
EXTERN_CVAR ( Int , cl_gfxlocalization )
EXTERN_CVAR ( Bool , m_quickexit )
EXTERN_CVAR ( Bool , saveloadconfirmation ) // [mxd]
EXTERN_CVAR ( Bool , quicksaverotation )
EXTERN_CVAR ( Bool , show_messages )
2020-10-06 21:49:34 +00:00
CVAR ( Bool , menu_sounds , true , CVAR_ARCHIVE ) // added mainly because RR's sounds are so supremely annoying.
2020-10-04 16:31:48 +00:00
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 ;
}
//=============================================================================
//
//
//
//=============================================================================
2020-10-06 21:49:34 +00:00
void M_StartControlPanel ( bool makeSound , bool )
{
static bool created = false ;
2020-10-04 16:31:48 +00:00
// intro might call this repeatedly
2020-10-06 21:49:34 +00:00
if ( CurrentMenu ! = NULL )
2020-10-04 16:31:48 +00:00
return ;
2020-10-06 21:49:34 +00:00
if ( ! created ) // Cannot do this earlier.
2020-10-04 16:31:48 +00:00
{
2020-10-06 21:49:34 +00:00
created = true ;
M_CreateMenus ( ) ;
2020-10-04 16:31:48 +00:00
}
2020-10-06 21:49:34 +00:00
GSnd - > SetSfxPaused ( true , PAUSESFX_MENU ) ;
gi - > MenuOpened ( ) ;
2020-10-06 23:00:43 +00:00
if ( makeSound & & menu_sounds ) gi - > MenuSound ( ActivateSound ) ;
2020-10-06 21:49:34 +00:00
M_DoStartControlPanel ( false ) ;
2020-10-04 16:31:48 +00:00
}
//==========================================================================
//
// 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.
//
//==========================================================================
2020-10-04 20:14:20 +00:00
bool CheckSkipGameOptionBlock ( FScanner & sc ) { return false ; }
2020-10-04 16:31:48 +00:00
#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 ;
}
2020-10-04 20:21:11 +00:00
# endif
2020-10-04 16:31:48 +00:00
void SetDefaultMenuColors ( )
{
2020-10-06 18:49:55 +00:00
PClass * cls = nullptr ;
2020-10-04 20:21:11 +00:00
//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 ;
2020-10-06 18:49:55 +00:00
if ( g_gameType & GAMEFLAG_BLOOD )
2020-10-04 20:21:11 +00:00
{
OptionSettings . mFontColorHeader = CR_DARKGRAY ;
OptionSettings . mFontColorHighlight = CR_WHITE ;
2020-10-06 18:49:55 +00:00
OptionSettings . mFontColorSelection = CR_DARKRED ;
2020-10-06 23:00:43 +00:00
cls = PClass : : FindClass ( " BloodMenuDelegate " ) ;
2020-10-04 20:21:11 +00:00
}
2020-10-06 18:49:55 +00:00
else if ( g_gameType & GAMEFLAG_SW )
2020-10-04 20:21:11 +00:00
{
2020-10-06 18:49:55 +00:00
OptionSettings . mFontColorHeader = CR_DARKRED ;
2020-10-04 20:21:11 +00:00
OptionSettings . mFontColorHighlight = CR_WHITE ;
2020-10-06 23:00:43 +00:00
cls = PClass : : FindClass ( " SWMenuDelegate " ) ;
2020-10-04 20:21:11 +00:00
}
2020-10-06 18:49:55 +00:00
else if ( g_gameType & GAMEFLAG_PSEXHUMED )
2020-10-06 20:01:20 +00:00
{
2020-10-06 18:49:55 +00:00
OptionSettings . mFontColorHeader = CR_LIGHTBLUE ;
OptionSettings . mFontColorHighlight = CR_SAPPHIRE ;
2020-10-06 20:35:25 +00:00
OptionSettings . mFontColorSelection = CR_ORANGE ;
OptionSettings . mFontColor = CR_FIRE ;
2020-10-06 23:00:43 +00:00
cls = PClass : : FindClass ( " ExhumedMenuDelegate " ) ;
2020-10-04 20:21:11 +00:00
}
2020-10-06 18:49:55 +00:00
else
2020-10-04 20:21:11 +00:00
{
2020-10-06 18:49:55 +00:00
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 ;
}
2020-10-06 23:00:43 +00:00
cls = PClass : : FindClass ( " DukeMenuDelegate " ) ;
2020-10-04 20:21:11 +00:00
}
2020-10-06 23:00:43 +00:00
if ( ! cls ) cls = PClass : : FindClass ( " RazeMenuDelegate " ) ;
if ( cls ) menuDelegate = cls - > CreateNew ( ) ;
2020-10-06 18:49:55 +00:00
2020-10-04 16:31:48 +00:00
}
2020-10-06 23:00:43 +00:00
// 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 ;
}