2016-03-01 15:47:10 +00:00
/*
* * menudef . cpp
* * MENUDEF parser amd menu generation code
* *
* * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* * 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 <float.h>
# include "menu/menu.h"
# include "c_dispatch.h"
# include "w_wad.h"
# include "sc_man.h"
# include "v_font.h"
# include "g_level.h"
# include "d_player.h"
# include "v_video.h"
# include "i_system.h"
# include "c_bind.h"
# include "v_palette.h"
# include "d_event.h"
# include "d_gui.h"
# include "i_music.h"
# include "m_joy.h"
# include "gi.h"
# include "i_sound.h"
2016-10-12 17:22:33 +00:00
# include "cmdlib.h"
2017-04-12 23:12:04 +00:00
# include "vm.h"
# include "types.h"
2017-04-18 14:42:28 +00:00
# include "gameconfigfile.h"
2016-03-01 15:47:10 +00:00
2017-02-10 23:36:53 +00:00
2016-03-01 15:47:10 +00:00
void ClearSaveGames ( ) ;
MenuDescriptorList MenuDescriptors ;
2017-02-09 19:18:53 +00:00
static DListMenuDescriptor * DefaultListMenuSettings ; // contains common settings for all list menus
static DOptionMenuDescriptor * DefaultOptionMenuSettings ; // contains common settings for all Option menus
2016-03-01 15:47:10 +00:00
FOptionMenuSettings OptionSettings ;
FOptionMap OptionValues ;
bool mustPrintErrors ;
2017-02-16 16:45:04 +00:00
PClass * DefaultListMenuClass ;
PClass * DefaultOptionMenuClass ;
2016-03-01 15:47:10 +00:00
void I_BuildALDeviceList ( FOptionValues * opt ) ;
2017-05-06 12:41:48 +00:00
void I_BuildALResamplersList ( FOptionValues * opt ) ;
2016-03-01 15:47:10 +00:00
2017-03-13 13:42:14 +00:00
DEFINE_GLOBAL_NAMED ( OptionSettings , OptionMenuSettings )
2017-02-10 23:36:53 +00:00
DEFINE_ACTION_FUNCTION ( FOptionValues , GetCount )
{
PARAM_PROLOGUE ;
PARAM_NAME ( grp ) ;
int cnt = 0 ;
FOptionValues * * pGrp = OptionValues . CheckKey ( grp ) ;
if ( pGrp ! = nullptr )
{
cnt = ( * pGrp ) - > mValues . Size ( ) ;
}
ACTION_RETURN_INT ( cnt ) ;
}
DEFINE_ACTION_FUNCTION ( FOptionValues , GetValue )
{
PARAM_PROLOGUE ;
PARAM_NAME ( grp ) ;
PARAM_UINT ( index ) ;
double val = 0 ;
FOptionValues * * pGrp = OptionValues . CheckKey ( grp ) ;
if ( pGrp ! = nullptr )
{
if ( index < ( * pGrp ) - > mValues . Size ( ) )
{
val = ( * pGrp ) - > mValues [ index ] . Value ;
}
}
ACTION_RETURN_FLOAT ( val ) ;
}
DEFINE_ACTION_FUNCTION ( FOptionValues , GetTextValue )
{
PARAM_PROLOGUE ;
PARAM_NAME ( grp ) ;
PARAM_UINT ( index ) ;
FString val ;
FOptionValues * * pGrp = OptionValues . CheckKey ( grp ) ;
if ( pGrp ! = nullptr )
{
if ( index < ( * pGrp ) - > mValues . Size ( ) )
{
val = ( * pGrp ) - > mValues [ index ] . TextValue ;
}
}
ACTION_RETURN_STRING ( val ) ;
}
DEFINE_ACTION_FUNCTION ( FOptionValues , GetText )
{
PARAM_PROLOGUE ;
PARAM_NAME ( grp ) ;
PARAM_UINT ( index ) ;
FString val ;
FOptionValues * * pGrp = OptionValues . CheckKey ( grp ) ;
if ( pGrp ! = nullptr )
{
if ( index < ( * pGrp ) - > mValues . Size ( ) )
{
val = ( * pGrp ) - > mValues [ index ] . Text ;
}
}
ACTION_RETURN_STRING ( val ) ;
}
2017-04-18 14:54:06 +00:00
void DeinitMenus ( )
2016-03-01 15:47:10 +00:00
{
{
FOptionMap : : Iterator it ( OptionValues ) ;
FOptionMap : : Pair * pair ;
while ( it . NextPair ( pair ) )
{
delete pair - > Value ;
2017-02-09 19:18:53 +00:00
pair - > Value = nullptr ;
2016-03-01 15:47:10 +00:00
}
}
MenuDescriptors . Clear ( ) ;
OptionValues . Clear ( ) ;
2017-02-18 18:19:14 +00:00
CurrentMenu = nullptr ;
2017-02-11 16:20:12 +00:00
savegameManager . ClearSaveGames ( ) ;
2016-03-01 15:47:10 +00:00
}
static FTextureID GetMenuTexture ( const char * const name )
{
const FTextureID texture = TexMan . CheckForTexture ( name , FTexture : : TEX_MiscPatch ) ;
if ( ! texture . Exists ( ) & & mustPrintErrors )
{
Printf ( " Missing menu texture: \" %s \" \n " , name ) ;
}
return texture ;
}
//=============================================================================
//
//
//
//=============================================================================
static void SkipSubBlock ( FScanner & sc )
{
sc . MustGetStringName ( " { " ) ;
int depth = 1 ;
while ( depth > 0 )
{
sc . MustGetString ( ) ;
if ( sc . Compare ( " { " ) ) depth + + ;
if ( sc . Compare ( " } " ) ) depth - - ;
}
}
//=============================================================================
//
//
//
//=============================================================================
static bool CheckSkipGameBlock ( FScanner & sc )
{
bool filter = false ;
sc . MustGetStringName ( " ( " ) ;
do
{
sc . MustGetString ( ) ;
filter | = CheckGame ( sc . String , false ) ;
}
while ( sc . CheckString ( " , " ) ) ;
sc . MustGetStringName ( " ) " ) ;
if ( ! filter )
{
SkipSubBlock ( sc ) ;
return true ;
}
return false ;
}
//=============================================================================
//
//
//
//=============================================================================
static bool CheckSkipOptionBlock ( FScanner & sc )
{
bool filter = false ;
sc . MustGetStringName ( " ( " ) ;
do
{
sc . MustGetString ( ) ;
if ( sc . Compare ( " ReadThis " ) ) filter | = gameinfo . drawreadthis ;
else if ( sc . Compare ( " Swapmenu " ) ) filter | = gameinfo . swapmenu ;
else if ( sc . Compare ( " Windows " ) )
{
# ifdef _WIN32
filter = true ;
# endif
}
else if ( sc . Compare ( " unix " ) )
{
# ifdef __unix__
filter = true ;
# endif
}
else if ( sc . Compare ( " Mac " ) )
{
# ifdef __APPLE__
filter = true ;
# endif
}
else if ( sc . Compare ( " OpenAL " ) )
{
filter | = IsOpenALPresent ( ) ;
}
}
while ( sc . CheckString ( " , " ) ) ;
sc . MustGetStringName ( " ) " ) ;
if ( ! filter )
{
SkipSubBlock ( sc ) ;
return ! sc . CheckString ( " else " ) ;
}
return false ;
}
//=============================================================================
//
//
//
//=============================================================================
2017-02-09 19:18:53 +00:00
static void ParseListMenuBody ( FScanner & sc , DListMenuDescriptor * desc )
2016-03-01 15:47:10 +00:00
{
sc . MustGetStringName ( " { " ) ;
while ( ! sc . CheckString ( " } " ) )
{
sc . MustGetString ( ) ;
if ( sc . Compare ( " else " ) )
{
SkipSubBlock ( sc ) ;
}
else if ( sc . Compare ( " ifgame " ) )
{
if ( ! CheckSkipGameBlock ( sc ) )
{
// recursively parse sub-block
ParseListMenuBody ( sc , desc ) ;
}
}
else if ( sc . Compare ( " ifoption " ) )
{
if ( ! CheckSkipOptionBlock ( sc ) )
{
// recursively parse sub-block
ParseListMenuBody ( sc , desc ) ;
}
}
else if ( sc . Compare ( " Class " ) )
{
sc . MustGetString ( ) ;
2017-02-16 17:35:58 +00:00
PClass * cls = PClass : : FindClass ( sc . String ) ;
2017-02-18 17:35:44 +00:00
if ( cls = = nullptr | | ! cls - > IsDescendantOf ( " ListMenu " ) )
2016-03-01 15:47:10 +00:00
{
sc . ScriptError ( " Unknown menu class '%s' " , sc . String ) ;
}
desc - > mClass = cls ;
}
else if ( sc . Compare ( " Selector " ) )
{
sc . MustGetString ( ) ;
desc - > mSelector = GetMenuTexture ( sc . String ) ;
sc . MustGetStringName ( " , " ) ;
2017-02-19 16:24:30 +00:00
sc . MustGetFloat ( ) ;
desc - > mSelectOfsX = sc . Float ;
2016-03-01 15:47:10 +00:00
sc . MustGetStringName ( " , " ) ;
2017-02-19 16:24:30 +00:00
sc . MustGetFloat ( ) ;
desc - > mSelectOfsY = sc . Float ;
2016-03-01 15:47:10 +00:00
}
else if ( sc . Compare ( " Linespacing " ) )
{
sc . MustGetNumber ( ) ;
desc - > mLinespacing = sc . Number ;
}
else if ( sc . Compare ( " Position " ) )
{
2017-02-19 16:24:30 +00:00
sc . MustGetFloat ( ) ;
desc - > mXpos = sc . Float ;
2016-03-01 15:47:10 +00:00
sc . MustGetStringName ( " , " ) ;
2017-02-19 16:24:30 +00:00
sc . MustGetFloat ( ) ;
desc - > mYpos = sc . Float ;
2016-03-01 15:47:10 +00:00
}
else if ( sc . Compare ( " Centermenu " ) )
{
desc - > mCenter = true ;
}
else if ( sc . Compare ( " MouseWindow " ) )
{
sc . MustGetNumber ( ) ;
desc - > mWLeft = sc . Number ;
sc . MustGetStringName ( " , " ) ;
sc . MustGetNumber ( ) ;
desc - > mWRight = sc . Number ;
}
else if ( sc . Compare ( " Font " ) )
{
sc . MustGetString ( ) ;
FFont * newfont = V_GetFont ( sc . String ) ;
2017-02-09 19:18:53 +00:00
if ( newfont ! = nullptr ) desc - > mFont = newfont ;
2016-03-01 15:47:10 +00:00
if ( sc . CheckString ( " , " ) )
{
sc . MustGetString ( ) ;
desc - > mFontColor2 = desc - > mFontColor = V_FindFontColor ( ( FName ) sc . String ) ;
if ( sc . CheckString ( " , " ) )
{
sc . MustGetString ( ) ;
desc - > mFontColor2 = V_FindFontColor ( ( FName ) sc . String ) ;
}
}
else
{
desc - > mFontColor = OptionSettings . mFontColor ;
desc - > mFontColor2 = OptionSettings . mFontColorValue ;
}
}
else if ( sc . Compare ( " NetgameMessage " ) )
{
sc . MustGetString ( ) ;
desc - > mNetgameMessage = sc . String ;
}
2017-02-12 00:18:49 +00:00
else
2016-03-01 15:47:10 +00:00
{
2017-02-12 00:18:49 +00:00
bool success = false ;
FStringf buildname ( " ListMenuItem%s " , sc . String ) ;
PClass * cls = PClass : : FindClass ( buildname ) ;
if ( cls ! = nullptr & & cls - > IsDescendantOf ( " ListMenuItem " ) )
2016-03-01 15:47:10 +00:00
{
2017-04-12 11:08:41 +00:00
auto func = dyn_cast < PFunction > ( cls - > FindSymbol ( " Init " , true ) ) ;
2017-02-12 00:18:49 +00:00
if ( func ! = nullptr & & ! ( func - > Variants [ 0 ] . Flags & ( VARF_Protected | VARF_Private ) ) ) // skip internal classes which have a protexted init method.
2016-03-01 15:47:10 +00:00
{
2017-02-12 00:18:49 +00:00
auto & args = func - > Variants [ 0 ] . Proto - > ArgumentTypes ;
TArray < VMValue > params ;
int start = 1 ;
params . Push ( 0 ) ;
if ( args . Size ( ) > 1 & & args [ 1 ] = = NewPointer ( PClass : : FindClass ( " ListMenuDescriptor " ) ) )
{
params . Push ( desc ) ;
start = 2 ;
}
2017-04-12 13:12:41 +00:00
auto TypeCVar = NewPointer ( NewStruct ( " CVar " , nullptr , true ) ) ;
2017-02-12 00:18:49 +00:00
2017-03-22 00:44:56 +00:00
// Note that this array may not be reallocated so its initial size must be the maximum possible elements.
TArray < FString > strings ( args . Size ( ) ) ;
2017-02-12 13:04:48 +00:00
for ( unsigned i = start ; i < args . Size ( ) ; i + + )
2017-02-12 00:18:49 +00:00
{
sc . MustGetString ( ) ;
if ( args [ i ] = = TypeString )
{
2017-03-22 00:44:56 +00:00
strings . Push ( sc . String ) ;
params . Push ( & strings . Last ( ) ) ;
2017-02-12 00:18:49 +00:00
}
else if ( args [ i ] = = TypeName )
{
params . Push ( FName ( sc . String ) . GetIndex ( ) ) ;
}
else if ( args [ i ] = = TypeColor )
{
params . Push ( V_GetColor ( nullptr , sc ) ) ;
}
else if ( args [ i ] = = TypeFont )
{
2017-02-12 13:04:48 +00:00
auto f = FFont : : FindFont ( sc . String ) ;
if ( f = = nullptr )
{
sc . ScriptError ( " Unknown font %s " , sc . String ) ;
}
params . Push ( f ) ;
}
else if ( args [ i ] = = TypeTextureID )
{
auto f = TexMan . CheckForTexture ( sc . String , FTexture : : TEX_MiscPatch ) ;
2017-02-22 15:39:04 +00:00
if ( ! f . Exists ( ) )
2017-02-12 13:04:48 +00:00
{
2017-03-08 18:04:35 +00:00
sc . ScriptMessage ( " Unknown texture %s " , sc . String ) ;
2017-02-12 13:04:48 +00:00
}
params . Push ( f . GetIndex ( ) ) ;
2017-02-12 00:18:49 +00:00
}
2017-04-13 10:47:41 +00:00
else if ( args [ i ] - > isIntCompatible ( ) )
2017-02-12 00:18:49 +00:00
{
char * endp ;
int v = ( int ) strtoll ( sc . String , & endp , 0 ) ;
if ( * endp ! = 0 )
{
// special check for font color ranges.
v = V_FindFontColor ( sc . String ) ;
if ( v = = CR_UNTRANSLATED & & ! sc . Compare ( " untranslated " ) )
{
// todo: check other data types that may get used.
sc . ScriptError ( " Integer expected, got %s " , sc . String ) ;
}
}
if ( args [ i ] = = TypeBool ) v = ! ! v ;
params . Push ( v ) ;
}
2017-04-13 10:47:41 +00:00
else if ( args [ i ] - > isFloat ( ) )
2017-02-12 00:18:49 +00:00
{
char * endp ;
double v = strtod ( sc . String , & endp ) ;
if ( * endp ! = 0 )
{
sc . ScriptError ( " Float expected, got %s " , sc . String ) ;
}
params . Push ( v ) ;
}
else if ( args [ i ] = = TypeCVar )
{
auto cv = FindCVar ( sc . String , nullptr ) ;
if ( cv = = nullptr & & * sc . String )
{
sc . ScriptError ( " Unknown CVar %s " , sc . String ) ;
}
params . Push ( cv ) ;
}
else
{
sc . ScriptError ( " Invalid parameter type %s for menu item " , args [ i ] - > DescriptiveName ( ) ) ;
}
if ( sc . CheckString ( " , " ) )
{
if ( i = = args . Size ( ) - 1 )
{
sc . ScriptError ( " Too many parameters for %s " , cls - > TypeName . GetChars ( ) ) ;
}
}
else
{
if ( i < args . Size ( ) - 1 & & ! ( func - > Variants [ 0 ] . ArgFlags [ i + 1 ] & VARF_Optional ) )
{
sc . ScriptError ( " Insufficient parameters for %s " , cls - > TypeName . GetChars ( ) ) ;
}
break ;
}
}
DMenuItemBase * item = ( DMenuItemBase * ) cls - > CreateNew ( ) ;
params [ 0 ] = item ;
2017-04-12 23:12:04 +00:00
VMCall ( func - > Variants [ 0 ] . Implementation , & params [ 0 ] , params . Size ( ) , nullptr , 0 ) ;
2017-02-12 00:18:49 +00:00
desc - > mItems . Push ( ( DMenuItemBase * ) item ) ;
2017-02-12 13:04:48 +00:00
2017-02-12 00:18:49 +00:00
if ( cls - > IsDescendantOf ( " ListMenuItemSelectable " ) )
{
desc - > mYpos + = desc - > mLinespacing ;
if ( desc - > mSelectedItem = = - 1 ) desc - > mSelectedItem = desc - > mItems . Size ( ) - 1 ;
}
success = true ;
2016-03-01 15:47:10 +00:00
}
}
2017-02-12 00:18:49 +00:00
if ( ! success )
2016-03-01 15:47:10 +00:00
{
2017-02-12 00:18:49 +00:00
sc . ScriptError ( " Unknown keyword '%s' " , sc . String ) ;
2016-03-01 15:47:10 +00:00
}
}
}
2017-02-05 00:52:09 +00:00
for ( auto & p : desc - > mItems )
{
GC : : WriteBarrier ( p ) ;
}
2016-03-01 15:47:10 +00:00
}
//=============================================================================
//
//
//
//=============================================================================
2017-02-09 19:18:53 +00:00
static bool CheckCompatible ( DMenuDescriptor * newd , DMenuDescriptor * oldd )
2016-03-01 15:47:10 +00:00
{
2017-02-09 19:18:53 +00:00
if ( oldd - > mClass = = nullptr ) return true ;
2016-03-01 15:47:10 +00:00
return oldd - > mClass = = newd - > mClass ;
}
2017-02-09 19:18:53 +00:00
static bool ReplaceMenu ( FScanner & sc , DMenuDescriptor * desc )
2016-03-01 15:47:10 +00:00
{
2017-02-09 19:18:53 +00:00
DMenuDescriptor * * pOld = MenuDescriptors . CheckKey ( desc - > mMenuName ) ;
if ( pOld ! = nullptr & & * pOld ! = nullptr )
2016-03-01 15:47:10 +00:00
{
2017-02-09 19:18:53 +00:00
if ( ! CheckCompatible ( desc , * pOld ) )
2016-03-01 15:47:10 +00:00
{
sc . ScriptMessage ( " Tried to replace menu '%s' with a menu of different type " , desc - > mMenuName . GetChars ( ) ) ;
return true ;
}
}
MenuDescriptors [ desc - > mMenuName ] = desc ;
2017-02-09 19:18:53 +00:00
GC : : WriteBarrier ( desc ) ;
2016-03-01 15:47:10 +00:00
return false ;
}
//=============================================================================
//
//
//
//=============================================================================
static void ParseListMenu ( FScanner & sc )
{
sc . MustGetString ( ) ;
2017-04-14 11:31:58 +00:00
DListMenuDescriptor * desc = Create < DListMenuDescriptor > ( ) ;
2016-03-01 15:47:10 +00:00
desc - > mMenuName = sc . String ;
desc - > mSelectedItem = - 1 ;
desc - > mAutoselect = - 1 ;
2017-02-09 19:18:53 +00:00
desc - > mSelectOfsX = DefaultListMenuSettings - > mSelectOfsX ;
desc - > mSelectOfsY = DefaultListMenuSettings - > mSelectOfsY ;
desc - > mSelector = DefaultListMenuSettings - > mSelector ;
desc - > mDisplayTop = DefaultListMenuSettings - > mDisplayTop ;
desc - > mXpos = DefaultListMenuSettings - > mXpos ;
desc - > mYpos = DefaultListMenuSettings - > mYpos ;
desc - > mLinespacing = DefaultListMenuSettings - > mLinespacing ;
desc - > mNetgameMessage = DefaultListMenuSettings - > mNetgameMessage ;
desc - > mFont = DefaultListMenuSettings - > mFont ;
desc - > mFontColor = DefaultListMenuSettings - > mFontColor ;
desc - > mFontColor2 = DefaultListMenuSettings - > mFontColor2 ;
desc - > mClass = nullptr ;
2016-03-01 15:47:10 +00:00
desc - > mWLeft = 0 ;
desc - > mWRight = 0 ;
desc - > mCenter = false ;
ParseListMenuBody ( sc , desc ) ;
2017-02-09 19:18:53 +00:00
ReplaceMenu ( sc , desc ) ;
2016-03-01 15:47:10 +00:00
}
//=============================================================================
//
//
//
//=============================================================================
static void ParseOptionValue ( FScanner & sc )
{
FName optname ;
FOptionValues * val = new FOptionValues ;
sc . MustGetString ( ) ;
optname = sc . String ;
sc . MustGetStringName ( " { " ) ;
while ( ! sc . CheckString ( " } " ) )
{
FOptionValues : : Pair & pair = val - > mValues [ val - > mValues . Reserve ( 1 ) ] ;
sc . MustGetFloat ( ) ;
pair . Value = sc . Float ;
sc . MustGetStringName ( " , " ) ;
sc . MustGetString ( ) ;
pair . Text = strbin1 ( sc . String ) ;
}
FOptionValues * * pOld = OptionValues . CheckKey ( optname ) ;
2017-02-09 19:18:53 +00:00
if ( pOld ! = nullptr & & * pOld ! = nullptr )
2016-03-01 15:47:10 +00:00
{
delete * pOld ;
}
OptionValues [ optname ] = val ;
}
//=============================================================================
//
//
//
//=============================================================================
static void ParseOptionString ( FScanner & sc )
{
FName optname ;
FOptionValues * val = new FOptionValues ;
sc . MustGetString ( ) ;
optname = sc . String ;
sc . MustGetStringName ( " { " ) ;
while ( ! sc . CheckString ( " } " ) )
{
FOptionValues : : Pair & pair = val - > mValues [ val - > mValues . Reserve ( 1 ) ] ;
sc . MustGetString ( ) ;
pair . Value = DBL_MAX ;
pair . TextValue = sc . String ;
sc . MustGetStringName ( " , " ) ;
sc . MustGetString ( ) ;
pair . Text = strbin1 ( sc . String ) ;
}
FOptionValues * * pOld = OptionValues . CheckKey ( optname ) ;
2017-02-09 19:18:53 +00:00
if ( pOld ! = nullptr & & * pOld ! = nullptr )
2016-03-01 15:47:10 +00:00
{
delete * pOld ;
}
OptionValues [ optname ] = val ;
}
//=============================================================================
//
//
//
//=============================================================================
static void ParseOptionSettings ( FScanner & sc )
{
sc . MustGetStringName ( " { " ) ;
while ( ! sc . CheckString ( " } " ) )
{
sc . MustGetString ( ) ;
if ( sc . Compare ( " else " ) )
{
SkipSubBlock ( sc ) ;
}
else if ( sc . Compare ( " ifgame " ) )
{
if ( ! CheckSkipGameBlock ( sc ) )
{
// recursively parse sub-block
ParseOptionSettings ( sc ) ;
}
}
else if ( sc . Compare ( " Linespacing " ) )
{
sc . MustGetNumber ( ) ;
OptionSettings . mLinespacing = sc . Number ;
}
else if ( sc . Compare ( " LabelOffset " ) )
{
sc . MustGetNumber ( ) ;
// ignored
}
else
{
sc . ScriptError ( " Unknown keyword '%s' " , sc . String ) ;
}
}
}
//=============================================================================
//
//
//
//=============================================================================
2017-02-09 19:18:53 +00:00
static void ParseOptionMenuBody ( FScanner & sc , DOptionMenuDescriptor * desc )
2016-03-01 15:47:10 +00:00
{
sc . MustGetStringName ( " { " ) ;
while ( ! sc . CheckString ( " } " ) )
{
sc . MustGetString ( ) ;
if ( sc . Compare ( " else " ) )
{
SkipSubBlock ( sc ) ;
}
else if ( sc . Compare ( " ifgame " ) )
{
if ( ! CheckSkipGameBlock ( sc ) )
{
// recursively parse sub-block
ParseOptionMenuBody ( sc , desc ) ;
}
}
else if ( sc . Compare ( " ifoption " ) )
{
if ( ! CheckSkipOptionBlock ( sc ) )
{
// recursively parse sub-block
ParseOptionMenuBody ( sc , desc ) ;
}
}
else if ( sc . Compare ( " Class " ) )
{
sc . MustGetString ( ) ;
2017-02-16 17:35:58 +00:00
PClass * cls = PClass : : FindClass ( sc . String ) ;
2017-02-12 23:08:20 +00:00
if ( cls = = nullptr | | ! cls - > IsDescendantOf ( " OptionMenu " ) )
2016-03-01 15:47:10 +00:00
{
sc . ScriptError ( " Unknown menu class '%s' " , sc . String ) ;
}
desc - > mClass = cls ;
}
else if ( sc . Compare ( " Title " ) )
{
sc . MustGetString ( ) ;
desc - > mTitle = sc . String ;
}
else if ( sc . Compare ( " Position " ) )
{
sc . MustGetNumber ( ) ;
desc - > mPosition = sc . Number ;
}
else if ( sc . Compare ( " DefaultSelection " ) )
{
sc . MustGetNumber ( ) ;
desc - > mSelectedItem = sc . Number ;
}
else if ( sc . Compare ( " ScrollTop " ) )
{
sc . MustGetNumber ( ) ;
desc - > mScrollTop = sc . Number ;
}
else if ( sc . Compare ( " Indent " ) )
{
sc . MustGetNumber ( ) ;
desc - > mIndent = sc . Number ;
}
2017-02-11 20:28:48 +00:00
else
2016-03-01 15:47:10 +00:00
{
2017-02-11 20:28:48 +00:00
bool success = false ;
FStringf buildname ( " OptionMenuItem%s " , sc . String ) ;
// Handle one special case: MapControl maps to Control with one parameter different
PClass * cls = PClass : : FindClass ( buildname ) ;
if ( cls ! = nullptr & & cls - > IsDescendantOf ( " OptionMenuItem " ) )
2016-03-01 15:47:10 +00:00
{
2017-04-12 11:08:41 +00:00
auto func = dyn_cast < PFunction > ( cls - > FindSymbol ( " Init " , true ) ) ;
2017-02-11 20:28:48 +00:00
if ( func ! = nullptr & & ! ( func - > Variants [ 0 ] . Flags & ( VARF_Protected | VARF_Private ) ) ) // skip internal classes which have a protexted init method.
2016-03-01 15:47:10 +00:00
{
2017-02-11 20:28:48 +00:00
auto & args = func - > Variants [ 0 ] . Proto - > ArgumentTypes ;
TArray < VMValue > params ;
2016-03-01 15:47:10 +00:00
2017-02-11 20:28:48 +00:00
params . Push ( 0 ) ;
2017-04-12 13:12:41 +00:00
auto TypeCVar = NewPointer ( NewStruct ( " CVar " , nullptr , true ) ) ;
2017-03-22 00:44:56 +00:00
// Note that this array may not be reallocated so its initial size must be the maximum possible elements.
TArray < FString > strings ( args . Size ( ) ) ;
2017-02-11 20:28:48 +00:00
for ( unsigned i = 1 ; i < args . Size ( ) ; i + + )
2016-03-01 15:47:10 +00:00
{
sc . MustGetString ( ) ;
2017-02-11 20:28:48 +00:00
if ( args [ i ] = = TypeString )
{
2017-03-22 00:44:56 +00:00
strings . Push ( sc . String ) ;
params . Push ( & strings . Last ( ) ) ;
2017-02-11 20:28:48 +00:00
}
else if ( args [ i ] = = TypeName )
{
params . Push ( FName ( sc . String ) . GetIndex ( ) ) ;
}
else if ( args [ i ] = = TypeColor )
{
params . Push ( V_GetColor ( nullptr , sc ) ) ;
}
2017-04-13 10:47:41 +00:00
else if ( args [ i ] - > isIntCompatible ( ) )
2017-02-11 20:28:48 +00:00
{
char * endp ;
int v = ( int ) strtoll ( sc . String , & endp , 0 ) ;
if ( * endp ! = 0 )
{
// special check for font color ranges.
v = V_FindFontColor ( sc . String ) ;
if ( v = = CR_UNTRANSLATED & & ! sc . Compare ( " untranslated " ) )
{
// todo: check other data types that may get used.
sc . ScriptError ( " Integer expected, got %s " , sc . String ) ;
}
2017-02-12 13:04:48 +00:00
// Color ranges need to be marked for option menu items to support an older feature where a boolean number could be passed instead.
v | = 0x12340000 ;
2017-02-11 20:28:48 +00:00
}
if ( args [ i ] = = TypeBool ) v = ! ! v ;
params . Push ( v ) ;
}
2017-04-13 10:47:41 +00:00
else if ( args [ i ] - > isFloat ( ) )
2017-02-11 20:28:48 +00:00
{
char * endp ;
double v = strtod ( sc . String , & endp ) ;
if ( * endp ! = 0 )
{
sc . ScriptError ( " Float expected, got %s " , sc . String ) ;
}
params . Push ( v ) ;
}
else if ( args [ i ] = = TypeCVar )
{
auto cv = FindCVar ( sc . String , nullptr ) ;
if ( cv = = nullptr & & * sc . String )
{
2017-02-13 22:24:31 +00:00
if ( func - > Variants [ 0 ] . ArgFlags [ i ] & VARF_Optional )
sc . ScriptMessage ( " Unknown CVar %s " , sc . String ) ;
else
sc . ScriptError ( " Unknown CVar %s " , sc . String ) ;
2017-02-11 20:28:48 +00:00
}
params . Push ( cv ) ;
}
else
{
sc . ScriptError ( " Invalid parameter type %s for menu item " , args [ i ] - > DescriptiveName ( ) ) ;
}
if ( sc . CheckString ( " , " ) )
{
if ( i = = args . Size ( ) - 1 )
{
sc . ScriptError ( " Too many parameters for %s " , cls - > TypeName . GetChars ( ) ) ;
}
}
else
{
if ( i < args . Size ( ) - 1 & & ! ( func - > Variants [ 0 ] . ArgFlags [ i + 1 ] & VARF_Optional ) )
{
sc . ScriptError ( " Insufficient parameters for %s " , cls - > TypeName . GetChars ( ) ) ;
}
break ;
}
2016-03-01 15:47:10 +00:00
}
2017-02-12 13:04:48 +00:00
2017-02-11 20:28:48 +00:00
DMenuItemBase * item = ( DMenuItemBase * ) cls - > CreateNew ( ) ;
params [ 0 ] = item ;
2017-04-12 23:12:04 +00:00
VMCall ( func - > Variants [ 0 ] . Implementation , & params [ 0 ] , params . Size ( ) , nullptr , 0 ) ;
2017-02-11 20:28:48 +00:00
desc - > mItems . Push ( ( DMenuItemBase * ) item ) ;
2017-02-12 13:04:48 +00:00
2017-02-11 20:28:48 +00:00
success = true ;
2016-03-01 15:47:10 +00:00
}
}
2017-02-11 20:28:48 +00:00
if ( ! success )
{
sc . ScriptError ( " Unknown keyword '%s' " , sc . String ) ;
}
2016-03-01 15:47:10 +00:00
}
}
2017-02-05 00:52:09 +00:00
for ( auto & p : desc - > mItems )
{
GC : : WriteBarrier ( p ) ;
}
2016-03-01 15:47:10 +00:00
}
//=============================================================================
//
//
//
//=============================================================================
static void ParseOptionMenu ( FScanner & sc )
{
sc . MustGetString ( ) ;
2017-04-14 11:31:58 +00:00
DOptionMenuDescriptor * desc = Create < DOptionMenuDescriptor > ( ) ;
2016-03-01 15:47:10 +00:00
desc - > mMenuName = sc . String ;
desc - > mSelectedItem = - 1 ;
desc - > mScrollPos = 0 ;
2017-02-09 19:18:53 +00:00
desc - > mClass = nullptr ;
desc - > mPosition = DefaultOptionMenuSettings - > mPosition ;
desc - > mScrollTop = DefaultOptionMenuSettings - > mScrollTop ;
desc - > mIndent = DefaultOptionMenuSettings - > mIndent ;
desc - > mDontDim = DefaultOptionMenuSettings - > mDontDim ;
2016-03-01 15:47:10 +00:00
ParseOptionMenuBody ( sc , desc ) ;
2017-02-09 19:18:53 +00:00
ReplaceMenu ( sc , desc ) ;
2016-03-01 15:47:10 +00:00
}
2017-02-27 09:37:47 +00:00
//=============================================================================
//
//
//
//=============================================================================
static void ParseAddOptionMenu ( FScanner & sc )
{
sc . MustGetString ( ) ;
DMenuDescriptor * * pOld = MenuDescriptors . CheckKey ( sc . String ) ;
if ( pOld = = nullptr | | * pOld = = nullptr | | ! ( * pOld ) - > IsKindOf ( RUNTIME_CLASS ( DOptionMenuDescriptor ) ) )
{
sc . ScriptError ( " %s is not an option menu that can be extended " , sc . String ) ;
}
ParseOptionMenuBody ( sc , ( DOptionMenuDescriptor * ) ( * pOld ) ) ;
}
2016-03-01 15:47:10 +00:00
//=============================================================================
//
//
//
//=============================================================================
void M_ParseMenuDefs ( )
{
int lump , lastlump = 0 ;
OptionSettings . mTitleColor = V_FindFontColor ( gameinfo . mTitleColor ) ;
OptionSettings . mFontColor = V_FindFontColor ( gameinfo . mFontColor ) ;
OptionSettings . mFontColorValue = V_FindFontColor ( gameinfo . mFontColorValue ) ;
OptionSettings . mFontColorMore = V_FindFontColor ( gameinfo . mFontColorMore ) ;
OptionSettings . mFontColorHeader = V_FindFontColor ( gameinfo . mFontColorHeader ) ;
OptionSettings . mFontColorHighlight = V_FindFontColor ( gameinfo . mFontColorHighlight ) ;
OptionSettings . mFontColorSelection = V_FindFontColor ( gameinfo . mFontColorSelection ) ;
2017-02-09 19:18:53 +00:00
// these are supposed to get GC'd after parsing is complete.
2017-04-14 11:31:58 +00:00
DefaultListMenuSettings = Create < DListMenuDescriptor > ( ) ;
DefaultOptionMenuSettings = Create < DOptionMenuDescriptor > ( ) ;
2017-02-09 19:18:53 +00:00
DefaultListMenuSettings - > Reset ( ) ;
DefaultOptionMenuSettings - > Reset ( ) ;
2016-03-01 15:47:10 +00:00
atterm ( DeinitMenus ) ;
DeinitMenus ( ) ;
int IWADMenu = Wads . CheckNumForName ( " MENUDEF " , ns_global , FWadCollection : : IWAD_FILENUM ) ;
while ( ( lump = Wads . FindLump ( " MENUDEF " , & lastlump ) ) ! = - 1 )
{
FScanner sc ( lump ) ;
mustPrintErrors = lump > = IWADMenu ;
sc . SetCMode ( true ) ;
while ( sc . GetString ( ) )
{
if ( sc . Compare ( " LISTMENU " ) )
{
ParseListMenu ( sc ) ;
}
else if ( sc . Compare ( " DEFAULTLISTMENU " ) )
{
2017-02-09 19:18:53 +00:00
ParseListMenuBody ( sc , DefaultListMenuSettings ) ;
if ( DefaultListMenuSettings - > mItems . Size ( ) > 0 )
2016-03-01 15:47:10 +00:00
{
I_FatalError ( " You cannot add menu items to the menu default settings. " ) ;
}
}
else if ( sc . Compare ( " OPTIONVALUE " ) )
{
ParseOptionValue ( sc ) ;
}
else if ( sc . Compare ( " OPTIONSTRING " ) )
{
ParseOptionString ( sc ) ;
}
else if ( sc . Compare ( " OPTIONMENUSETTINGS " ) )
{
ParseOptionSettings ( sc ) ;
}
else if ( sc . Compare ( " OPTIONMENU " ) )
{
ParseOptionMenu ( sc ) ;
}
2017-02-27 09:37:47 +00:00
else if ( sc . Compare ( " ADDOPTIONMENU " ) )
{
ParseAddOptionMenu ( sc ) ;
}
2016-03-01 15:47:10 +00:00
else if ( sc . Compare ( " DEFAULTOPTIONMENU " ) )
{
2017-02-09 19:18:53 +00:00
ParseOptionMenuBody ( sc , DefaultOptionMenuSettings ) ;
if ( DefaultOptionMenuSettings - > mItems . Size ( ) > 0 )
2016-03-01 15:47:10 +00:00
{
I_FatalError ( " You cannot add menu items to the menu default settings. " ) ;
}
}
else
{
sc . ScriptError ( " Unknown keyword '%s' " , sc . String ) ;
}
}
}
2017-02-16 17:55:36 +00:00
DefaultListMenuClass = DefaultListMenuSettings - > mClass ;
2017-02-09 19:18:53 +00:00
DefaultListMenuSettings = nullptr ;
2017-02-16 17:55:36 +00:00
DefaultOptionMenuClass = DefaultOptionMenuSettings - > mClass ;
2017-02-09 19:18:53 +00:00
DefaultOptionMenuSettings = nullptr ;
2016-03-01 15:47:10 +00:00
}
//=============================================================================
//
// Creates the episode menu
// Falls back on an option menu if there's not enough screen space to show all episodes
//
//=============================================================================
static void BuildEpisodeMenu ( )
{
// Build episode menu
bool success = false ;
2017-02-09 19:18:53 +00:00
DMenuDescriptor * * desc = MenuDescriptors . CheckKey ( NAME_Episodemenu ) ;
if ( desc ! = nullptr )
2016-03-01 15:47:10 +00:00
{
2017-02-09 19:18:53 +00:00
if ( ( * desc ) - > IsKindOf ( RUNTIME_CLASS ( DListMenuDescriptor ) ) )
2016-03-01 15:47:10 +00:00
{
2017-02-09 19:18:53 +00:00
DListMenuDescriptor * ld = static_cast < DListMenuDescriptor * > ( * desc ) ;
2017-02-19 16:24:30 +00:00
int posy = ( int ) ld - > mYpos ;
2016-03-01 15:47:10 +00:00
int topy = posy ;
// Get lowest y coordinate of any static item in the menu
for ( unsigned i = 0 ; i < ld - > mItems . Size ( ) ; i + + )
{
2017-02-19 16:24:30 +00:00
int y = ( int ) ld - > mItems [ i ] - > GetY ( ) ;
2016-03-01 15:47:10 +00:00
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 + 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 ;
}
ld - > mSelectedItem = ld - > mItems . Size ( ) ;
for ( unsigned i = 0 ; i < AllEpisodes . Size ( ) ; i + + )
{
2017-02-03 23:19:25 +00:00
DMenuItemBase * it ;
2016-03-01 15:47:10 +00:00
if ( AllEpisodes [ i ] . mPicName . IsNotEmpty ( ) )
{
FTextureID tex = GetMenuTexture ( AllEpisodes [ i ] . mPicName ) ;
2017-02-12 00:18:49 +00:00
it = CreateListMenuItemPatch ( ld - > mXpos , posy , ld - > mLinespacing , AllEpisodes [ i ] . mShortcut , tex , NAME_Skillmenu , i ) ;
2016-03-01 15:47:10 +00:00
}
else
{
2017-02-12 00:18:49 +00:00
it = CreateListMenuItemText ( ld - > mXpos , posy , ld - > mLinespacing , AllEpisodes [ i ] . mShortcut ,
2016-03-01 15:47:10 +00:00
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 ;
2017-02-05 00:52:09 +00:00
for ( auto & p : ld - > mItems )
{
2017-02-09 19:18:53 +00:00
GC : : WriteBarrier ( * desc , p ) ;
2017-02-05 00:52:09 +00:00
}
2016-03-01 15:47:10 +00:00
}
}
}
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.
2017-04-14 11:31:58 +00:00
DOptionMenuDescriptor * od = Create < DOptionMenuDescriptor > ( ) ;
2016-03-01 15:47:10 +00:00
MenuDescriptors [ NAME_Episodemenu ] = od ;
od - > mMenuName = NAME_Episodemenu ;
od - > mTitle = " $MNU_EPISODE " ;
od - > mSelectedItem = 0 ;
od - > mScrollPos = 0 ;
2017-02-09 19:18:53 +00:00
od - > mClass = nullptr ;
2016-03-01 15:47:10 +00:00
od - > mPosition = - 15 ;
od - > mScrollTop = 0 ;
od - > mIndent = 160 ;
od - > mDontDim = false ;
2017-02-09 19:18:53 +00:00
GC : : WriteBarrier ( od ) ;
2016-03-01 15:47:10 +00:00
for ( unsigned i = 0 ; i < AllEpisodes . Size ( ) ; i + + )
{
2017-02-11 20:28:48 +00:00
auto it = CreateOptionMenuItemSubmenu ( AllEpisodes [ i ] . mEpisodeName , " Skillmenu " , i ) ;
2016-03-01 15:47:10 +00:00
od - > mItems . Push ( it ) ;
2017-02-09 19:18:53 +00:00
GC : : WriteBarrier ( od , it ) ;
2016-03-01 15:47:10 +00:00
}
}
}
//=============================================================================
//
//
//
//=============================================================================
static void BuildPlayerclassMenu ( )
{
bool success = false ;
// Build player class menu
2017-02-09 19:18:53 +00:00
DMenuDescriptor * * desc = MenuDescriptors . CheckKey ( NAME_Playerclassmenu ) ;
if ( desc ! = nullptr )
2016-03-01 15:47:10 +00:00
{
2017-02-09 19:18:53 +00:00
if ( ( * desc ) - > IsKindOf ( RUNTIME_CLASS ( DListMenuDescriptor ) ) )
2016-03-01 15:47:10 +00:00
{
2017-02-09 19:18:53 +00:00
DListMenuDescriptor * ld = static_cast < DListMenuDescriptor * > ( * desc ) ;
2016-03-01 15:47:10 +00:00
// add player display
ld - > mSelectedItem = ld - > mItems . Size ( ) ;
2017-02-19 16:24:30 +00:00
int posy = ( int ) ld - > mYpos ;
2016-03-01 15:47:10 +00:00
int topy = posy ;
// Get lowest y coordinate of any static item in the menu
for ( unsigned i = 0 ; i < ld - > mItems . Size ( ) ; i + + )
{
2017-02-19 16:24:30 +00:00
int y = ( int ) ld - > mItems [ i ] - > GetY ( ) ;
2016-03-01 15:47:10 +00:00
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 ) ;
2017-02-09 19:18:53 +00:00
if ( pname ! = nullptr )
2016-03-01 15:47:10 +00:00
{
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.
2017-02-12 00:18:49 +00:00
auto it = CreateListMenuItemText ( 0 , 0 , 0 , ' p ' , " player " ,
2016-03-01 15:47:10 +00:00
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 ) ;
2017-02-09 19:18:53 +00:00
if ( pname ! = nullptr )
2016-03-01 15:47:10 +00:00
{
2017-02-12 00:18:49 +00:00
auto it = CreateListMenuItemText ( ld - > mXpos , ld - > mYpos , ld - > mLinespacing , * pname ,
2016-03-01 15:47:10 +00:00
pname , ld - > mFont , ld - > mFontColor , ld - > mFontColor2 , NAME_Episodemenu , i ) ;
ld - > mItems . Push ( it ) ;
ld - > mYpos + = ld - > mLinespacing ;
n + + ;
}
}
}
if ( n > 1 & & ! gameinfo . norandomplayerclass )
{
2017-02-12 00:18:49 +00:00
auto it = CreateListMenuItemText ( ld - > mXpos , ld - > mYpos , ld - > mLinespacing , ' r ' ,
2016-03-01 15:47:10 +00:00
" $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 ) ;
2017-02-09 19:18:53 +00:00
if ( pname ! = nullptr )
2016-03-01 15:47:10 +00:00
{
2017-02-12 00:18:49 +00:00
auto it = CreateListMenuItemText ( ld - > mXpos , ld - > mYpos , ld - > mLinespacing , * pname ,
2016-03-01 15:47:10 +00:00
pname , ld - > mFont , ld - > mFontColor , ld - > mFontColor2 , NAME_Episodemenu , 0 ) ;
ld - > mItems . Push ( it ) ;
}
}
success = true ;
2017-02-05 00:52:09 +00:00
for ( auto & p : ld - > mItems )
{
2017-02-09 19:18:53 +00:00
GC : : WriteBarrier ( ld , p ) ;
2017-02-05 00:52:09 +00:00
}
2016-03-01 15:47:10 +00:00
}
}
}
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.
2017-04-14 11:31:58 +00:00
DOptionMenuDescriptor * od = Create < DOptionMenuDescriptor > ( ) ;
2016-03-01 15:47:10 +00:00
MenuDescriptors [ NAME_Playerclassmenu ] = od ;
od - > mMenuName = NAME_Playerclassmenu ;
od - > mTitle = " $MNU_CHOOSECLASS " ;
od - > mSelectedItem = 0 ;
od - > mScrollPos = 0 ;
2017-02-09 19:18:53 +00:00
od - > mClass = nullptr ;
2016-03-01 15:47:10 +00:00
od - > mPosition = - 15 ;
od - > mScrollTop = 0 ;
od - > mIndent = 160 ;
od - > mDontDim = false ;
od - > mNetgameMessage = " $NEWGAME " ;
2017-02-09 19:18:53 +00:00
GC : : WriteBarrier ( od ) ;
2016-03-01 15:47:10 +00:00
for ( unsigned i = 0 ; i < PlayerClasses . Size ( ) ; i + + )
{
if ( ! ( PlayerClasses [ i ] . Flags & PCF_NOMENU ) )
{
const char * pname = GetPrintableDisplayName ( PlayerClasses [ i ] . Type ) ;
2017-02-09 19:18:53 +00:00
if ( pname ! = nullptr )
2016-03-01 15:47:10 +00:00
{
2017-02-11 20:28:48 +00:00
auto it = CreateOptionMenuItemSubmenu ( pname , " Episodemenu " , i ) ;
2016-03-01 15:47:10 +00:00
od - > mItems . Push ( it ) ;
2017-02-09 19:18:53 +00:00
GC : : WriteBarrier ( od , it ) ;
2016-03-01 15:47:10 +00:00
}
}
}
2017-02-11 20:28:48 +00:00
auto it = CreateOptionMenuItemSubmenu ( " Random " , " Episodemenu " , - 1 ) ;
2016-03-01 15:47:10 +00:00
od - > mItems . Push ( it ) ;
2017-02-09 19:18:53 +00:00
GC : : WriteBarrier ( od , it ) ;
2016-03-01 15:47:10 +00:00
}
}
//=============================================================================
//
// 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 ) ;
2017-02-09 19:18:53 +00:00
if ( opt = = nullptr )
2016-03-01 15:47:10 +00:00
{
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 = Wads . 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 ) ;
}
}
}
}
}
2017-04-18 14:42:28 +00:00
//=============================================================================
//
// Initialize the music configuration submenus
//
//=============================================================================
static void InitMusicMenus ( )
{
DMenuDescriptor * * advmenu = MenuDescriptors . CheckKey ( " AdvSoundOptions " ) ;
DMenuDescriptor * * gusmenu = MenuDescriptors . CheckKey ( " GusConfigMenu " ) ;
DMenuDescriptor * * timiditymenu = MenuDescriptors . CheckKey ( " TimidityExeMenu " ) ;
DMenuDescriptor * * wildmidimenu = MenuDescriptors . CheckKey ( " WildMidiConfigMenu " ) ;
DMenuDescriptor * * fluidmenu = MenuDescriptors . CheckKey ( " FluidPatchsetMenu " ) ;
const char * key , * value ;
if ( GameConfig - > SetSection ( " SoundFonts " ) )
{
while ( GameConfig - > NextInSection ( key , value ) )
{
if ( FileExists ( value ) )
{
if ( fluidmenu ! = nullptr )
{
2017-04-18 15:46:53 +00:00
auto it = CreateOptionMenuItemCommand ( key , FStringf ( " fluid_patchset %s " , NicePath ( value ) . GetChars ( ) ) , true ) ;
2017-04-18 14:42:28 +00:00
static_cast < DOptionMenuDescriptor * > ( * fluidmenu ) - > mItems . Push ( it ) ;
}
}
}
}
else if ( advmenu ! = nullptr )
{
// Remove the item for this submenu
auto d = static_cast < DOptionMenuDescriptor * > ( * advmenu ) ;
auto it = d - > GetItem ( " FluidPatchsetMenu " ) ;
if ( it ! = nullptr ) d - > mItems . Delete ( d - > mItems . Find ( it ) ) ;
}
if ( GameConfig - > SetSection ( " PatchSets " ) )
{
while ( GameConfig - > NextInSection ( key , value ) )
{
if ( FileExists ( value ) )
{
if ( gusmenu ! = nullptr )
{
2017-04-18 15:46:53 +00:00
auto it = CreateOptionMenuItemCommand ( key , FStringf ( " midi_config %s " , NicePath ( value ) . GetChars ( ) ) , true ) ;
2017-04-18 14:42:28 +00:00
static_cast < DOptionMenuDescriptor * > ( * gusmenu ) - > mItems . Push ( it ) ;
}
if ( wildmidimenu ! = nullptr )
{
2017-04-18 15:46:53 +00:00
auto it = CreateOptionMenuItemCommand ( key , FStringf ( " wildmidi_config %s " , NicePath ( value ) . GetChars ( ) ) , true ) ;
2017-04-18 14:42:28 +00:00
static_cast < DOptionMenuDescriptor * > ( * wildmidimenu ) - > mItems . Push ( it ) ;
}
}
}
}
else if ( advmenu ! = nullptr )
{
// Remove the item for this submenu
auto d = static_cast < DOptionMenuDescriptor * > ( * advmenu ) ;
auto it = d - > GetItem ( " GusConfigMenu " ) ;
if ( it ! = nullptr ) d - > mItems . Delete ( d - > mItems . Find ( it ) ) ;
it = d - > GetItem ( " WildMidiConfigMenu " ) ;
if ( it ! = nullptr ) d - > mItems . Delete ( d - > mItems . Find ( it ) ) ;
}
# ifdef _WIN32 // Different Timidity paths only make sense if they can be stored in arbitrary paths with local configs (i.e. not if things are done the Linux way)
if ( GameConfig - > SetSection ( " TimidityExes " ) )
{
while ( GameConfig - > NextInSection ( key , value ) )
{
if ( FileExists ( value ) )
{
if ( timiditymenu ! = nullptr )
{
2017-04-18 15:46:53 +00:00
auto it = CreateOptionMenuItemCommand ( key , FStringf ( " timidity_exe %s " , NicePath ( value ) . GetChars ( ) ) , true ) ;
2017-04-18 14:42:28 +00:00
static_cast < DOptionMenuDescriptor * > ( * timiditymenu ) - > mItems . Push ( it ) ;
}
}
}
}
else
{
auto d = static_cast < DOptionMenuDescriptor * > ( * advmenu ) ;
auto it = d - > GetItem ( " TimidityExeMenu " ) ;
if ( it ! = nullptr ) d - > mItems . Delete ( d - > mItems . Find ( it ) ) ;
}
# endif
}
2016-03-01 15:47:10 +00:00
//=============================================================================
//
// With the current workings of the menu system this cannot be done any longer
// from within the respective CCMDs.
//
//=============================================================================
static void InitKeySections ( )
{
2017-02-09 19:18:53 +00:00
DMenuDescriptor * * desc = MenuDescriptors . CheckKey ( NAME_CustomizeControls ) ;
if ( desc ! = nullptr )
2016-03-01 15:47:10 +00:00
{
2017-02-09 19:18:53 +00:00
if ( ( * desc ) - > IsKindOf ( RUNTIME_CLASS ( DOptionMenuDescriptor ) ) )
2016-03-01 15:47:10 +00:00
{
2017-02-09 19:18:53 +00:00
DOptionMenuDescriptor * menu = static_cast < DOptionMenuDescriptor * > ( * desc ) ;
2016-03-01 15:47:10 +00:00
for ( unsigned i = 0 ; i < KeySections . Size ( ) ; i + + )
{
FKeySection * sect = & KeySections [ i ] ;
2017-02-11 20:28:48 +00:00
DMenuItemBase * item = CreateOptionMenuItemStaticText ( " " , false ) ;
2016-03-01 15:47:10 +00:00
menu - > mItems . Push ( item ) ;
2017-02-11 20:28:48 +00:00
item = CreateOptionMenuItemStaticText ( sect - > mTitle , true ) ;
2016-03-01 15:47:10 +00:00
menu - > mItems . Push ( item ) ;
for ( unsigned j = 0 ; j < sect - > mActions . Size ( ) ; j + + )
{
FKeyAction * act = & sect - > mActions [ j ] ;
2017-02-11 20:28:48 +00:00
item = CreateOptionMenuItemControl ( act - > mTitle , act - > mAction , & Bindings ) ;
2016-03-01 15:47:10 +00:00
menu - > mItems . Push ( item ) ;
}
}
2017-02-05 00:52:09 +00:00
for ( auto & p : menu - > mItems )
{
2017-02-09 19:18:53 +00:00
GC : : WriteBarrier ( * desc , p ) ;
2017-02-05 00:52:09 +00:00
}
2016-03-01 15:47:10 +00:00
}
}
}
//=============================================================================
//
// Special menus will be created once all engine data is loaded
//
//=============================================================================
void M_CreateMenus ( )
{
BuildEpisodeMenu ( ) ;
BuildPlayerclassMenu ( ) ;
InitCrosshairsList ( ) ;
2017-04-18 14:42:28 +00:00
InitMusicMenus ( ) ;
2016-03-01 15:47:10 +00:00
InitKeySections ( ) ;
FOptionValues * * opt = OptionValues . CheckKey ( NAME_Mididevices ) ;
2017-02-09 19:18:53 +00:00
if ( opt ! = nullptr )
2016-03-01 15:47:10 +00:00
{
I_BuildMIDIMenuList ( * opt ) ;
}
opt = OptionValues . CheckKey ( NAME_Aldevices ) ;
2017-02-09 19:18:53 +00:00
if ( opt ! = nullptr )
2016-03-01 15:47:10 +00:00
{
I_BuildALDeviceList ( * opt ) ;
}
2017-05-06 12:41:48 +00:00
opt = OptionValues . CheckKey ( NAME_Alresamplers ) ;
if ( opt ! = nullptr )
{
I_BuildALResamplersList ( * opt ) ;
}
2016-03-01 15:47:10 +00:00
}
//=============================================================================
//
// The skill menu must be refeshed each time it starts up
//
//=============================================================================
extern int restart ;
void M_StartupSkillMenu ( FGameStartup * gs )
{
static int done = - 1 ;
bool success = false ;
2017-02-26 21:10:35 +00:00
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 ;
}
}
2017-02-09 19:18:53 +00:00
DMenuDescriptor * * desc = MenuDescriptors . CheckKey ( NAME_Skillmenu ) ;
if ( desc ! = nullptr )
2016-03-01 15:47:10 +00:00
{
2017-02-09 19:18:53 +00:00
if ( ( * desc ) - > IsKindOf ( RUNTIME_CLASS ( DListMenuDescriptor ) ) )
2016-03-01 15:47:10 +00:00
{
2017-02-09 19:18:53 +00:00
DListMenuDescriptor * ld = static_cast < DListMenuDescriptor * > ( * desc ) ;
2017-02-19 16:24:30 +00:00
int x = ( int ) ld - > mXpos ;
int y = ( int ) ld - > mYpos ;
2016-03-01 15:47:10 +00:00
// Delete previous contents
for ( unsigned i = 0 ; i < ld - > mItems . Size ( ) ; i + + )
{
2017-02-18 18:11:53 +00:00
FName n = ld - > mItems [ i ] - > mAction ;
2016-03-01 15:47:10 +00:00
if ( n = = NAME_Startgame | | n = = NAME_StartgameConfirm )
{
ld - > mItems . Resize ( i ) ;
break ;
}
}
if ( done ! = restart )
{
done = restart ;
2017-02-26 21:10:35 +00:00
ld - > mSelectedItem = ld - > mItems . Size ( ) + defindex ;
2016-03-01 15:47:10 +00:00
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 + + )
{
2017-02-19 16:24:30 +00:00
int y = ( int ) ld - > mItems [ i ] - > GetY ( ) ;
2016-03-01 15:47:10 +00:00
if ( y < topy ) topy = y ;
}
// center the menu on the screen if the top space is larger than the bottom space
2017-02-26 21:10:35 +00:00
int totalheight = posy + MenuSkills . Size ( ) * ld - > mLinespacing - topy ;
2016-03-01 15:47:10 +00:00
2017-02-26 21:10:35 +00:00
if ( totalheight < 190 | | MenuSkills . Size ( ) = = 1 )
2016-03-01 15:47:10 +00:00
{
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 ) ;
}
2017-02-19 16:24:30 +00:00
ld - > mYpos = y = posy - topdelta ;
2016-03-01 15:47:10 +00:00
}
}
else
{
// too large
2017-02-09 19:18:53 +00:00
desc = nullptr ;
2016-03-01 15:47:10 +00:00
done = false ;
goto fail ;
}
}
unsigned firstitem = ld - > mItems . Size ( ) ;
2017-02-26 21:10:35 +00:00
for ( unsigned int i = 0 ; i < MenuSkills . Size ( ) ; i + + )
2016-03-01 15:47:10 +00:00
{
2017-02-26 21:10:35 +00:00
FSkillInfo & skill = * MenuSkills [ i ] ;
2017-02-03 23:19:25 +00:00
DMenuItemBase * li ;
2016-03-01 15:47:10 +00:00
// 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 ;
2017-02-09 19:18:53 +00:00
FString * pItemText = nullptr ;
if ( gs - > PlayerClass ! = nullptr )
2016-03-01 15:47:10 +00:00
{
pItemText = skill . MenuNamesForPlayerClass . CheckKey ( gs - > PlayerClass ) ;
}
2017-02-09 19:18:53 +00:00
if ( skill . PicName . Len ( ) ! = 0 & & pItemText = = nullptr )
2016-03-01 15:47:10 +00:00
{
FTextureID tex = GetMenuTexture ( skill . PicName ) ;
2017-02-26 21:10:35 +00:00
li = CreateListMenuItemPatch ( ld - > mXpos , y , ld - > mLinespacing , skill . Shortcut , tex , action , SkillIndices [ i ] ) ;
2016-03-01 15:47:10 +00:00
}
else
{
EColorRange color = ( EColorRange ) skill . GetTextColor ( ) ;
if ( color = = CR_UNTRANSLATED ) color = ld - > mFontColor ;
2017-02-12 00:18:49 +00:00
li = CreateListMenuItemText ( x , y , ld - > mLinespacing , skill . Shortcut ,
2017-02-26 21:10:35 +00:00
pItemText ? * pItemText : skill . MenuName , ld - > mFont , color , ld - > mFontColor2 , action , SkillIndices [ i ] ) ;
2016-03-01 15:47:10 +00:00
}
ld - > mItems . Push ( li ) ;
2017-02-09 19:18:53 +00:00
GC : : WriteBarrier ( * desc , li ) ;
2016-03-01 15:47:10 +00:00
y + = ld - > mLinespacing ;
}
2017-02-26 21:10:35 +00:00
if ( AllEpisodes [ gs - > Episode ] . mNoSkill | | MenuSkills . Size ( ) = = 1 )
2016-03-01 15:47:10 +00:00
{
2017-02-26 21:10:35 +00:00
ld - > mAutoselect = firstitem + defindex ;
2016-03-01 15:47:10 +00:00
}
else
{
ld - > mAutoselect = - 1 ;
}
success = true ;
}
}
if ( success ) return ;
fail :
// Option menu fallback for overlong skill lists
2017-02-09 19:18:53 +00:00
DOptionMenuDescriptor * od ;
if ( desc = = nullptr )
2016-03-01 15:47:10 +00:00
{
2017-04-14 11:31:58 +00:00
od = Create < DOptionMenuDescriptor > ( ) ;
2016-03-01 15:47:10 +00:00
MenuDescriptors [ NAME_Skillmenu ] = od ;
od - > mMenuName = NAME_Skillmenu ;
od - > mTitle = " $MNU_CHOOSESKILL " ;
2017-02-26 21:10:35 +00:00
od - > mSelectedItem = defindex ;
2016-03-01 15:47:10 +00:00
od - > mScrollPos = 0 ;
2017-02-09 19:18:53 +00:00
od - > mClass = nullptr ;
2016-03-01 15:47:10 +00:00
od - > mPosition = - 15 ;
od - > mScrollTop = 0 ;
od - > mIndent = 160 ;
od - > mDontDim = false ;
2017-02-09 19:18:53 +00:00
GC : : WriteBarrier ( od ) ;
2016-03-01 15:47:10 +00:00
}
else
{
2017-02-09 19:18:53 +00:00
od = static_cast < DOptionMenuDescriptor * > ( * desc ) ;
2016-03-01 15:47:10 +00:00
od - > mItems . Clear ( ) ;
}
2017-02-26 21:10:35 +00:00
for ( unsigned int i = 0 ; i < MenuSkills . Size ( ) ; i + + )
2016-03-01 15:47:10 +00:00
{
2017-02-26 21:10:35 +00:00
FSkillInfo & skill = * MenuSkills [ i ] ;
2017-02-11 20:28:48 +00:00
DMenuItemBase * li ;
2016-03-01 15:47:10 +00:00
// 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 " ;
2017-02-09 19:18:53 +00:00
FString * pItemText = nullptr ;
if ( gs - > PlayerClass ! = nullptr )
2016-03-01 15:47:10 +00:00
{
pItemText = skill . MenuNamesForPlayerClass . CheckKey ( gs - > PlayerClass ) ;
}
2017-02-26 21:10:35 +00:00
li = CreateOptionMenuItemSubmenu ( pItemText ? * pItemText : skill . MenuName , action , SkillIndices [ i ] ) ;
2016-03-01 15:47:10 +00:00
od - > mItems . Push ( li ) ;
2017-02-09 19:18:53 +00:00
GC : : WriteBarrier ( od , li ) ;
2016-03-01 15:47:10 +00:00
if ( ! done )
{
done = true ;
2017-02-26 21:10:35 +00:00
od - > mSelectedItem = defindex ;
2016-03-01 15:47:10 +00:00
}
}
}