2019-12-14 19:15:15 +00:00
//-------------------------------------------------------------------------
/*
Copyright ( C ) 2019 Christoph Oelckers
This is free software ; you can redistribute it and / or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation ; either version 2
of the License , or ( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE .
See the GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program ; if not , write to the Free Software
Foundation , Inc . , 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 , USA .
*/
//-------------------------------------------------------------------------
2020-01-12 13:54:43 +00:00
# include <stdexcept>
2019-10-25 22:32:49 +00:00
# include "gamecontrol.h"
# include "tarray.h"
# include "zstring.h"
# include "name.h"
# include "sc_man.h"
2019-10-26 11:16:32 +00:00
# include "c_cvars.h"
2019-10-26 17:24:46 +00:00
# include "gameconfigfile.h"
2019-10-26 21:45:55 +00:00
# include "gamecvars.h"
2019-10-26 11:16:32 +00:00
# include "build.h"
2019-10-28 05:47:49 +00:00
# include "inputstate.h"
2019-10-28 21:19:50 +00:00
# include "m_argv.h"
# include "rts.h"
2019-11-01 18:25:42 +00:00
# include "printf.h"
2019-11-03 19:24:50 +00:00
# include "c_bind.h"
2019-11-05 19:07:16 +00:00
# include "v_font.h"
# include "c_console.h"
# include "c_dispatch.h"
# include "i_specialpaths.h"
2019-11-11 00:01:18 +00:00
# include "z_music.h"
2019-11-14 20:07:43 +00:00
# include "statistics.h"
2019-11-21 21:31:46 +00:00
# include "menu.h"
2019-11-23 11:38:38 +00:00
# include "gstrings.h"
2019-12-08 23:33:14 +00:00
# include "quotemgr.h"
2019-12-09 23:01:45 +00:00
# include "mapinfo.h"
2019-12-15 12:34:00 +00:00
# include "s_soundinternal.h"
2019-12-23 18:37:40 +00:00
# include "i_system.h"
# include "inputstate.h"
# include "v_video.h"
# include "st_start.h"
2019-12-24 11:59:26 +00:00
# include "s_music.h"
2019-12-24 17:53:29 +00:00
# include "i_video.h"
2020-01-11 16:05:25 +00:00
# include "v_text.h"
# include "resourcefile.h"
2019-12-28 11:59:19 +00:00
# include "c_dispatch.h"
2019-12-24 17:53:29 +00:00
# include "glbackend/glbackend.h"
2020-03-29 13:22:07 +00:00
# include "mmulti.h"
// The last remains of sdlayer.cpp
double g_beforeSwapTime ;
GameInterface * gi ;
int myconnectindex , numplayers ;
int connecthead , connectpoint2 [ MAXMULTIPLAYERS ] ;
int32_t xres = - 1 , yres = - 1 , bpp = 0 , refreshfreq = - 1 ;
auto vsnprintfptr = vsnprintf ; // This is an inline in Visual Studio but we need an address for it to satisfy the MinGW compiled libraries.
2019-10-25 22:32:49 +00:00
2019-12-10 21:22:59 +00:00
MapRecord mapList [ 512 ] ; // Due to how this gets used it needs to be static. EDuke defines 7 episode plus one spare episode with 64 potential levels each and relies on the static array which is freely accessible by scripts.
MapRecord * currentLevel ; // level that is currently played. (The real level, not what script hacks modfifying the current level index can pretend.)
MapRecord * lastLevel ; // Same here, for the last level.
MapRecord userMapRecord ; // stand-in for the user map.
2019-12-09 23:01:45 +00:00
2020-01-11 16:05:25 +00:00
FStartupInfo RazeStartupInfo ;
2019-12-24 17:53:29 +00:00
FMemArena dump ; // this is for memory blocks than cannot be deallocated without some huge effort. Put them in here so that they do not register on shutdown.
2020-01-08 00:00:57 +00:00
FString progdir ;
2019-12-24 17:53:29 +00:00
2019-12-02 01:07:32 +00:00
void C_CON_SetAliases ( ) ;
2019-10-27 23:24:09 +00:00
InputState inputState ;
2019-10-28 21:19:50 +00:00
void SetClipshapes ( ) ;
2019-10-31 22:25:21 +00:00
int ShowStartupWindow ( TArray < GrpEntry > & ) ;
2020-01-11 16:05:25 +00:00
FString GetGameFronUserFiles ( ) ;
2019-11-01 18:25:42 +00:00
void InitFileSystem ( TArray < GrpEntry > & ) ;
2020-01-11 16:05:25 +00:00
void I_SetWindowTitle ( const char * caption ) ;
2020-02-03 20:14:35 +00:00
void InitENet ( ) ;
void ShutdownENet ( ) ;
2020-01-08 00:00:57 +00:00
bool AppActive ;
2019-10-27 23:24:09 +00:00
2019-10-31 23:32:56 +00:00
FString currentGame ;
2019-10-28 22:46:15 +00:00
FString LumpFilter ;
2019-11-28 00:02:45 +00:00
TMap < FName , int32_t > NameToTileIndex ; // for assigning names to tiles. The menu accesses this list. By default it gets everything from the dynamic tile map in Duke Nukem and Redneck Rampage.
// Todo: Add additional definition file for the other games or textures not in that list so that the menu does not have to rely on indices.
2019-10-26 11:16:32 +00:00
CVAR ( Int , cl_defaultconfiguration , 2 , CVAR_ARCHIVE | CVAR_GLOBALCONFIG )
2019-12-24 14:28:00 +00:00
CVAR ( Bool , queryiwad , true , CVAR_ARCHIVE | CVAR_GLOBALCONFIG ) ;
CVAR ( String , defaultiwad , " " , CVAR_ARCHIVE | CVAR_GLOBALCONFIG ) ;
CVAR ( Bool , disableautoload , false , CVAR_ARCHIVE | CVAR_NOINITCALL | CVAR_GLOBALCONFIG )
//CVAR(Bool, autoloadbrightmaps, false, CVAR_ARCHIVE | CVAR_NOINITCALL | CVAR_GLOBALCONFIG) // hopefully this is an option for later
//CVAR(Bool, autoloadlights, false, CVAR_ARCHIVE | CVAR_NOINITCALL | CVAR_GLOBALCONFIG)
2019-10-26 11:16:32 +00:00
2020-01-22 12:53:26 +00:00
extern int hud_size_max ;
2020-01-11 16:05:25 +00:00
2020-01-08 00:00:57 +00:00
//==========================================================================
//
//
//
//==========================================================================
2020-01-08 16:36:00 +00:00
bool grab_mouse ;
2020-01-08 00:00:57 +00:00
void mouseGrabInput ( bool grab )
{
grab_mouse = grab ;
if ( grab ) GUICapture & = ~ 1 ;
else GUICapture | = 1 ;
}
//==========================================================================
//
//
//
//==========================================================================
2019-10-28 21:19:50 +00:00
UserConfig userConfig ;
void UserConfig : : ProcessOptions ( )
{
// -help etc are omitted
// -cfg / -setupfile refer to Build style config which are not supported.
if ( Args - > CheckParm ( " -cfg " ) | | Args - > CheckParm ( " -setupfile " ) )
{
initprintf ( " Build-format config files not supported and will be ignored \n " ) ;
}
2019-12-11 22:41:05 +00:00
#if 0 // MP disabled pending evaluation
2019-10-28 21:19:50 +00:00
auto v = Args - > CheckValue ( " -port " ) ;
if ( v ) netPort = strtol ( v , nullptr , 0 ) ;
netServerMode = Args - > CheckParm ( " -server " ) ;
netServerAddress = Args - > CheckValue ( " -connect " ) ;
netPassword = Args - > CheckValue ( " -password " ) ;
2019-12-11 22:41:05 +00:00
# endif
2019-10-28 21:19:50 +00:00
2019-12-11 22:41:05 +00:00
auto v = Args - > CheckValue ( " -addon " ) ;
2019-10-28 21:19:50 +00:00
if ( v )
{
auto val = strtol ( v , nullptr , 0 ) ;
static const char * const addons [ ] = { " DUKE3D.GRP " , " DUKEDC.GRP " , " NWINTER.GRP " , " VACATION.GRP " } ;
2020-02-09 08:48:55 +00:00
if ( val > = 0 & & val < 4 ) gamegrp = addons [ val ] ;
2019-10-28 21:19:50 +00:00
else initprintf ( " %s: Unknown Addon \n " , v ) ;
}
else if ( Args - > CheckParm ( " -nam " ) )
{
gamegrp = " NAM.GRP " ;
}
else if ( Args - > CheckParm ( " -napalm " ) )
{
gamegrp = " NAPALM.GRP " ;
}
else if ( Args - > CheckParm ( " -ww2gi " ) )
{
gamegrp = " WW2GI.GRP " ;
}
2019-12-11 22:41:05 +00:00
// Set up all needed content for these two mod which feature a very messy distribution.
// As an alternative they can be zipped up - the launcher will be able to detect and set up such versions automatically.
else if ( Args - > CheckParm ( " -route66 " ) )
{
gamegrp = " REDNECK.GRP " ;
DefaultCon = " GAME66.CON " ;
const char * argv [ ] = { " tilesa66.art " , " tilesb66.art " } ;
AddArt . reset ( new FArgs ( 2 , argv ) ) ;
2020-01-10 20:36:46 +00:00
toBeDeleted . Push ( " turd66.anm*turdmov.anm " ) ;
toBeDeleted . Push ( " turd66.voc*turdmov.voc " ) ;
toBeDeleted . Push ( " end66.anm*rr_outro.anm " ) ;
toBeDeleted . Push ( " end66.voc*rr_outro.voc " ) ;
2019-12-11 22:41:05 +00:00
}
else if ( Args - > CheckParm ( " -cryptic " ) )
{
gamegrp = " BLOOD.RFF " ;
DefaultCon = " CRYPTIC.INI " ;
const char * argv [ ] = { " cpart07.ar_ " , " cpart15.ar_ " } ;
AddArt . reset ( new FArgs ( 2 , argv ) ) ;
}
2019-10-28 21:19:50 +00:00
2019-11-01 18:25:42 +00:00
v = Args - > CheckValue ( " -gamegrp " ) ;
2019-10-28 21:19:50 +00:00
if ( v )
{
gamegrp = v ;
}
else
{
// This is to enable the use of Doom launchers. that are limited to -iwad for specifying the game's main resource.
v = Args - > CheckValue ( " -iwad " ) ;
if ( v )
{
gamegrp = v ;
}
}
Args - > CollectFiles ( " -rts " , " .rts " ) ;
auto rts = Args - > CheckValue ( " -rts " ) ;
if ( rts ) RTS_Init ( rts ) ;
Args - > CollectFiles ( " -map " , " .map " ) ;
CommandMap = Args - > CheckValue ( " -map " ) ;
static const char * defs [ ] = { " -def " , " -h " , nullptr } ;
Args - > CollectFiles ( " -def " , defs , " .def " ) ;
DefaultDef = Args - > CheckValue ( " -def " ) ;
2020-01-10 20:36:46 +00:00
if ( DefaultCon . IsEmpty ( ) )
{
static const char * cons [ ] = { " -con " , " -x " , nullptr } ;
Args - > CollectFiles ( " -con " , cons , " .con " ) ;
DefaultCon = Args - > CheckValue ( " -con " ) ;
if ( DefaultCon . IsEmpty ( ) ) DefaultCon = Args - > CheckValue ( " -ini " ) ;
}
2019-10-28 21:19:50 +00:00
static const char * demos [ ] = { " -playback " , " -d " , " -demo " , nullptr } ;
Args - > CollectFiles ( " -demo " , demos , " .dmo " ) ;
CommandDemo = Args - > CheckValue ( " -demo " ) ;
static const char * names [ ] = { " -pname " , " -name " , nullptr } ;
Args - > CollectFiles ( " -name " , names , " .--- " ) ; // this shouldn't collect any file names at all so use a nonsense extension
CommandName = Args - > CheckValue ( " -name " ) ;
static const char * nomos [ ] = { " -nomonsters " , " -nodudes " , nullptr } ;
Args - > CollectFiles ( " -nomonsters " , nomos , " .--- " ) ; // this shouldn't collect any file names at all so use a nonsense extension
nomonsters = Args - > CheckParm ( " -nomonsters " ) ;
static const char * acons [ ] = { " -addcon " , " -mx " , nullptr } ;
Args - > CollectFiles ( " -addcon " , acons , " .con " ) ;
AddCons . reset ( Args - > GatherFiles ( " -addcon " ) ) ;
static const char * adefs [ ] = { " -adddef " , " -mh " , nullptr } ;
Args - > CollectFiles ( " -adddef " , adefs , " .def " ) ;
AddDefs . reset ( Args - > GatherFiles ( " -adddef " ) ) ;
Args - > CollectFiles ( " -art " , " .art " ) ;
AddArt . reset ( Args - > GatherFiles ( " -art " ) ) ;
nologo = Args - > CheckParm ( " -nologo " ) | | Args - > CheckParm ( " -quick " ) ;
nomusic = Args - > CheckParm ( " -nomusic " ) ;
nosound = Args - > CheckParm ( " -nosfx " ) ;
if ( Args - > CheckParm ( " -nosound " ) ) nomusic = nosound = true ;
2019-12-24 14:28:00 +00:00
if ( Args - > CheckParm ( " -setup " ) ) queryiwad = 1 ;
else if ( Args - > CheckParm ( " -nosetup " ) ) queryiwad = 0 ;
2019-10-28 21:19:50 +00:00
if ( Args - > CheckParm ( " -file " ) )
{
// For file loading there's two modes:
// If -file is given, all content will be processed in order and the legacy options be ignored entirely.
//This allows mixing directories and GRP files in arbitrary order.
Args - > CollectFiles ( " -file " , NULL ) ;
AddFiles . reset ( Args - > GatherFiles ( " -file " ) ) ;
}
else
{
2019-11-01 18:25:42 +00:00
// Trying to emulate Build. This means to treat RFF files as lowest priority, then all GRPs and then all directories.
2019-10-28 21:19:50 +00:00
// This is only for people depending on lauchers. Since the semantics are so crappy it is strongly recommended to
2019-11-01 18:25:42 +00:00
// use -file instead which gives the user full control over the order in which things are added.
2019-10-28 21:19:50 +00:00
// For single mods this is no problem but don't even think about loading more stuff consistently...
static const char * grps [ ] = { " -g " , " -grp " , nullptr } ;
static const char * dirs [ ] = { " -game_dir " , " -j " , nullptr } ;
static const char * rffs [ ] = { " -rff " , " -snd " , nullptr } ;
static const char * twostep [ ] = { " -rff " , " -grp " , nullptr } ;
// Abuse the inner workings to get the files into proper order. This is not 100% accurate but should work fine for everything that doesn't intentionally fuck things up.
Args - > CollectFiles ( " -rff " , rffs , " .rff " ) ;
Args - > CollectFiles ( " -grp " , grps , nullptr ) ;
Args - > CollectFiles ( " -grp " , twostep , nullptr ) ; // The two previous calls have already brought the content in order so collecting it again gives us one list with everything.
AddFilesPre . reset ( Args - > GatherFiles ( " -grp " ) ) ;
Args - > CollectFiles ( " -game_dir " , dirs , nullptr ) ;
AddFiles . reset ( Args - > GatherFiles ( " -game_dir " ) ) ;
}
2019-12-23 19:55:12 +00:00
if ( Args - > CheckParm ( " -showcoords " ) | | Args - > CheckParm ( " -w " ) )
{
C_DoCommand ( " stat coord " ) ;
}
2019-10-28 21:19:50 +00:00
}
2019-10-31 22:25:21 +00:00
//==========================================================================
//
//
//
//==========================================================================
namespace Duke
{
2019-11-03 11:32:58 +00:00
: : GameInterface * CreateInterface ( ) ;
2019-10-31 22:25:21 +00:00
}
namespace Redneck
{
2019-11-03 11:32:58 +00:00
: : GameInterface * CreateInterface ( ) ;
2019-10-31 22:25:21 +00:00
}
namespace Blood
{
2019-11-03 11:32:58 +00:00
: : GameInterface * CreateInterface ( ) ;
2019-10-31 22:25:21 +00:00
}
namespace ShadowWarrior
{
2019-11-03 11:32:58 +00:00
: : GameInterface * CreateInterface ( ) ;
2019-10-31 22:25:21 +00:00
}
2019-12-12 23:19:34 +00:00
namespace Powerslave
{
: : GameInterface * CreateInterface ( ) ;
}
2019-10-31 22:25:21 +00:00
2020-02-12 19:25:59 +00:00
CVAR ( Bool , duke_compatibility_15 , false , CVAR_ARCHIVE | CVAR_GLOBALCONFIG )
2019-10-31 22:25:21 +00:00
void CheckFrontend ( int flags )
{
2020-02-13 15:09:27 +00:00
bool duke_compat = duke_compatibility_15 ;
// This point is too early to have cmdline CVAR checkers working so it must be with a switch.
auto c = Args - > CheckValue ( " -duke_compatibility_15 " ) ;
if ( c )
{
if ( strtol ( c , nullptr , 0 ) ) duke_compatibility_15 = true ;
else duke_compatibility_15 = false ;
}
2019-10-31 22:25:21 +00:00
if ( flags & GAMEFLAG_BLOOD )
{
2019-11-03 11:32:58 +00:00
gi = Blood : : CreateInterface ( ) ;
2019-10-31 22:25:21 +00:00
}
2020-02-26 19:16:27 +00:00
else if ( flags & GAMEFLAG_RRALL )
2019-10-31 22:25:21 +00:00
{
2019-11-03 11:32:58 +00:00
gi = Redneck : : CreateInterface ( ) ;
2019-10-31 22:25:21 +00:00
}
else if ( flags & GAMEFLAG_SW )
{
2019-11-03 11:32:58 +00:00
gi = ShadowWarrior : : CreateInterface ( ) ;
2019-10-31 22:25:21 +00:00
}
2019-12-12 23:19:34 +00:00
else if ( flags & GAMEFLAG_PSEXHUMED )
{
gi = Powerslave : : CreateInterface ( ) ;
}
2020-03-07 19:14:03 +00:00
else if ( ( flags & GAMEFLAG_FURY ) | | RazeStartupInfo . modern > 0 )
2019-10-31 22:25:21 +00:00
{
2019-11-03 11:32:58 +00:00
gi = Duke : : CreateInterface ( ) ;
2019-10-31 22:25:21 +00:00
}
2020-03-07 19:14:03 +00:00
else if ( RazeStartupInfo . modern < 0 )
{
gi = Redneck : : CreateInterface ( ) ;
}
2020-02-12 19:25:59 +00:00
else
{
gi = * duke_compatibility_15 ? Redneck : : CreateInterface ( ) : Duke : : CreateInterface ( ) ;
}
2019-10-31 22:25:21 +00:00
}
2019-12-14 16:15:17 +00:00
void I_StartupJoysticks ( ) ;
void I_ShutdownInput ( ) ;
int RunGame ( ) ;
2019-10-31 22:25:21 +00:00
2019-11-05 19:07:16 +00:00
int GameMain ( )
{
2019-12-24 18:59:14 +00:00
set_memerr_handler ( G_HandleMemErr ) ;
2019-11-05 19:07:16 +00:00
int r ;
try
{
2019-12-14 16:15:17 +00:00
r = RunGame ( ) ;
2019-11-05 19:07:16 +00:00
}
catch ( const ExitEvent & exit )
{
// Just let the rest of the function execute.
r = exit . Reason ( ) ;
}
2019-12-24 15:30:33 +00:00
catch ( const std : : exception & err )
{
// shut down critical systems before showing a message box.
I_ShowFatalError ( err . what ( ) ) ;
r = - 1 ;
}
2019-12-25 10:26:19 +00:00
M_ClearMenus ( true ) ;
if ( gi )
{
gi - > FreeGameData ( ) ; // Must be done before taking down any subsystems.
}
2019-12-24 15:30:33 +00:00
S_StopMusic ( true ) ;
if ( soundEngine ) delete soundEngine ;
2019-12-24 17:53:29 +00:00
soundEngine = nullptr ;
I_CloseSound ( ) ;
2019-12-24 15:30:33 +00:00
I_ShutdownInput ( ) ;
2019-11-10 18:42:26 +00:00
G_SaveConfig ( ) ;
2019-12-24 15:09:43 +00:00
C_DeinitConsole ( ) ;
2019-12-24 15:30:33 +00:00
V_ClearFonts ( ) ;
2019-12-25 07:57:58 +00:00
vox_deinit ( ) ;
2019-12-24 17:53:29 +00:00
TileFiles . ClearTextureCache ( ) ;
TileFiles . CloseAll ( ) ; // do this before shutting down graphics.
GLInterface . Deinit ( ) ;
I_ShutdownGraphics ( ) ;
2019-12-24 19:06:55 +00:00
M_DeinitMenus ( ) ;
2019-12-24 18:47:34 +00:00
paletteFreeColorTables ( ) ;
2019-12-25 10:26:19 +00:00
engineUnInit ( ) ;
2019-12-24 19:06:55 +00:00
if ( gi )
{
delete gi ;
gi = nullptr ;
}
2020-02-03 20:14:35 +00:00
InitENet ( ) ;
2019-12-24 15:30:33 +00:00
DeleteStartupScreen ( ) ;
2019-12-24 15:09:43 +00:00
if ( Args ) delete Args ;
2019-11-05 19:07:16 +00:00
return r ;
}
2019-10-26 17:24:46 +00:00
//==========================================================================
//
//
//
//==========================================================================
2019-11-24 23:02:00 +00:00
void SetDefaultStrings ( )
{
2020-01-09 16:35:38 +00:00
if ( ( g_gameType & GAMEFLAG_DUKE ) & & fileSystem . FindFile ( " E4L1.MAP " ) < 0 )
2020-01-02 23:44:39 +00:00
{
2020-01-09 16:35:38 +00:00
// Pre-Atomic releases do not define this.
2020-01-02 23:44:39 +00:00
gVolumeNames [ 0 ] = " $L.A. Meltdown " ;
gVolumeNames [ 1 ] = " $Lunar Apocalypse " ;
gVolumeNames [ 2 ] = " $Shrapnel City " ;
2020-01-09 16:35:38 +00:00
if ( g_gameType & GAMEFLAG_SHAREWARE ) gVolumeNames [ 3 ] = " $The Birth " ;
2020-01-02 23:44:39 +00:00
gSkillNames [ 0 ] = " $Piece of Cake " ;
gSkillNames [ 1 ] = " $Let's Rock " ;
gSkillNames [ 2 ] = " $Come get Some " ;
gSkillNames [ 3 ] = " $Damn I'm Good " ;
}
2019-12-08 23:33:14 +00:00
// Blood hard codes its skill names, so we have to define them manually.
if ( g_gameType & GAMEFLAG_BLOOD )
2019-11-24 23:02:00 +00:00
{
2019-12-08 23:33:14 +00:00
gSkillNames [ 0 ] = " $STILL KICKING " ;
gSkillNames [ 1 ] = " $PINK ON THE INSIDE " ;
gSkillNames [ 2 ] = " $LIGHTLY BROILED " ;
gSkillNames [ 3 ] = " $WELL DONE " ;
gSkillNames [ 4 ] = " $EXTRA CRISPY " ;
2019-11-24 23:02:00 +00:00
}
2019-12-08 23:33:14 +00:00
//Set a few quotes which are used for common handling of a few status messages
quoteMgr . InitializeQuote ( 23 , " $MESSAGES: ON " ) ;
2019-12-09 01:01:30 +00:00
quoteMgr . InitializeQuote ( 24 , " $MESSAGES: OFF " ) ;
quoteMgr . InitializeQuote ( 83 , " $FOLLOW MODE OFF " ) ;
quoteMgr . InitializeQuote ( 84 , " $FOLLOW MODE ON " ) ;
2019-12-08 23:33:14 +00:00
quoteMgr . InitializeQuote ( 85 , " $AUTORUNOFF " ) ;
quoteMgr . InitializeQuote ( 86 , " $AUTORUNON " ) ;
2019-11-24 23:02:00 +00:00
}
2019-12-08 23:33:14 +00:00
2019-10-26 17:24:46 +00:00
//==========================================================================
//
//
//
//==========================================================================
2019-12-23 18:37:40 +00:00
static TArray < GrpEntry > SetupGame ( )
2019-10-26 11:16:32 +00:00
{
2019-10-29 18:53:46 +00:00
// Startup dialog must be presented here so that everything can be set up before reading the keybinds.
2019-10-30 23:41:56 +00:00
auto groups = GrpScan ( ) ;
2019-11-01 18:25:42 +00:00
if ( groups . Size ( ) = = 0 )
{
// Abort if no game data found.
2019-11-03 09:51:47 +00:00
G_SaveConfig ( ) ;
2019-11-01 18:25:42 +00:00
I_Error ( " Unable to find any game data. Please verify your settings. " ) ;
}
decltype ( groups ) usedgroups ;
int groupno = - 1 ;
// If the user has specified a file name, let's see if we know it.
//
2020-01-11 16:05:25 +00:00
FString game = GetGameFronUserFiles ( ) ;
if ( userConfig . gamegrp . IsEmpty ( ) )
{
userConfig . gamegrp = game ;
}
2019-11-19 21:35:52 +00:00
if ( userConfig . gamegrp . Len ( ) )
2019-10-31 23:32:56 +00:00
{
2019-11-19 21:35:52 +00:00
FString gamegrplower = " / " + userConfig . gamegrp . MakeLower ( ) ;
2019-11-01 18:25:42 +00:00
int g = 0 ;
for ( auto & grp : groups )
2019-10-31 23:32:56 +00:00
{
2019-11-19 20:35:35 +00:00
auto grplower = grp . FileName . MakeLower ( ) ;
2019-11-19 21:35:52 +00:00
grplower . Substitute ( " \\ " , " / " ) ;
2019-11-19 20:35:35 +00:00
if ( grplower . LastIndexOf ( gamegrplower ) = = grplower . Len ( ) - gamegrplower . Len ( ) )
2019-10-31 23:32:56 +00:00
{
2019-11-02 12:27:40 +00:00
groupno = g ;
2019-10-31 23:32:56 +00:00
break ;
}
2019-11-01 18:25:42 +00:00
g + + ;
}
}
2019-12-24 14:28:00 +00:00
if ( groupno = = - 1 )
{
int pick = 0 ;
// We got more than one so present the IWAD selection box.
if ( groups . Size ( ) > 1 )
{
// Locate the user's prefered IWAD, if it was found.
if ( defaultiwad [ 0 ] ! = ' \0 ' )
{
for ( unsigned i = 0 ; i < groups . Size ( ) ; + + i )
{
FString & basename = groups [ i ] . FileName ;
if ( stricmp ( basename , defaultiwad ) = = 0 )
{
pick = i ;
break ;
}
}
}
if ( groups . Size ( ) > 1 )
{
TArray < WadStuff > wads ;
for ( auto & found : groups )
{
WadStuff stuff ;
stuff . Name = found . FileInfo . name ;
stuff . Path = ExtractFileBase ( found . FileName ) ;
wads . Push ( stuff ) ;
}
pick = I_PickIWad ( & wads [ 0 ] , ( int ) wads . Size ( ) , queryiwad , pick ) ;
if ( pick > = 0 )
{
// The newly selected IWAD becomes the new default
defaultiwad = groups [ pick ] . FileName ;
}
groupno = pick ;
}
}
2019-12-29 03:49:28 +00:00
else if ( groups . Size ( ) = = 1 )
{
groupno = 0 ;
}
2019-12-24 14:28:00 +00:00
}
2019-11-01 18:25:42 +00:00
2019-12-23 18:37:40 +00:00
if ( groupno = = - 1 ) return TArray < GrpEntry > ( ) ;
auto & group = groups [ groupno ] ;
2019-11-01 18:25:42 +00:00
2020-01-11 16:05:25 +00:00
if ( RazeStartupInfo . Name . IsNotEmpty ( ) ) I_SetWindowTitle ( RazeStartupInfo . Name ) ;
else I_SetWindowTitle ( group . FileInfo . name ) ;
2019-11-01 18:25:42 +00:00
// Now filter out the data we actually need and delete the rest.
usedgroups . Push ( group ) ;
auto crc = group . FileInfo . dependencyCRC ;
2019-11-02 12:27:40 +00:00
if ( crc ! = 0 ) for ( auto & dep : groups )
2019-11-01 18:25:42 +00:00
{
if ( dep . FileInfo . CRC = = crc )
{
usedgroups . Insert ( 0 , dep ) ; // Order from least dependent to most dependent, which is the loading order of data.
2019-10-31 23:32:56 +00:00
}
}
2019-11-01 18:25:42 +00:00
groups . Reset ( ) ;
FString selectedScript ;
FString selectedDef ;
for ( auto & ugroup : usedgroups )
{
// For CONs the command line has priority, aside from that, the last one wins. For Blood this handles INIs - the rules are the same.
if ( ugroup . FileInfo . scriptname . IsNotEmpty ( ) ) selectedScript = ugroup . FileInfo . scriptname ;
if ( ugroup . FileInfo . defname . IsNotEmpty ( ) ) selectedDef = ugroup . FileInfo . defname ;
// CVAR has priority. This also overwrites the global variable each time. Init here is lazy so this is ok.
2019-12-23 18:37:40 +00:00
if ( ugroup . FileInfo . rtsname . IsNotEmpty ( ) & & * * rtsname = = 0 ) RTS_Init ( ugroup . FileInfo . rtsname ) ;
2019-11-01 18:25:42 +00:00
// For the game filter the last non-empty one wins.
if ( ugroup . FileInfo . gamefilter . IsNotEmpty ( ) ) LumpFilter = ugroup . FileInfo . gamefilter ;
g_gameType | = ugroup . FileInfo . flags ;
}
if ( userConfig . DefaultCon . IsEmpty ( ) ) userConfig . DefaultCon = selectedScript ;
if ( userConfig . DefaultDef . IsEmpty ( ) ) userConfig . DefaultDef = selectedDef ;
// This can only happen with a custom game that does not define any filter.
// In this case take the display name and strip all whitespace and invaliid path characters from it.
if ( LumpFilter . IsEmpty ( ) )
{
LumpFilter = usedgroups . Last ( ) . FileInfo . name ;
LumpFilter . StripChars ( " .:/ \\ <>? \" *| \t \r \n " ) ;
}
2019-10-29 18:53:46 +00:00
2019-10-31 23:32:56 +00:00
currentGame = LumpFilter ;
currentGame . Truncate ( currentGame . IndexOf ( " . " ) ) ;
2019-11-01 18:25:42 +00:00
CheckFrontend ( g_gameType ) ;
2019-12-23 18:37:40 +00:00
return usedgroups ;
}
//==========================================================================
//
//
//
//==========================================================================
int RunGame ( )
{
// Set up the console before anything else so that it can receive text.
C_InitConsole ( 1024 , 768 , true ) ;
// +logfile gets checked too late to catch the full startup log in the logfile so do some extra check for it here.
FString logfile = Args - > TakeValue ( " +logfile " ) ;
// As long as this engine is still in prerelease mode let's always write a log file.
2019-12-26 13:04:53 +00:00
if ( logfile . IsEmpty ( ) ) logfile . Format ( " %s " GAMENAMELOWERCASE " .log " , M_GetDocumentsPath ( ) . GetChars ( ) ) ;
2019-12-23 18:37:40 +00:00
if ( logfile . IsNotEmpty ( ) )
{
execLogfile ( logfile ) ;
}
I_DetectOS ( ) ;
SetClipshapes ( ) ;
userConfig . ProcessOptions ( ) ;
G_LoadConfig ( ) ;
2020-02-03 20:14:35 +00:00
ShutdownENet ( ) ;
2019-12-23 18:37:40 +00:00
auto usedgroups = SetupGame ( ) ;
2019-10-28 21:19:50 +00:00
2019-11-02 19:13:00 +00:00
InitFileSystem ( usedgroups ) ;
2019-12-23 18:37:40 +00:00
if ( usedgroups . Size ( ) = = 0 ) return 0 ;
2020-01-22 15:14:01 +00:00
// Handle CVARs with game specific defaults here.
2019-12-26 12:04:29 +00:00
if ( g_gameType & GAMEFLAG_BLOOD )
{
2020-01-22 12:53:26 +00:00
mus_redbook . SetGenericRepDefault ( false , CVAR_Bool ) ; // Blood should default to CD Audio off - all other games must default to on.
2020-01-22 14:21:07 +00:00
hud_size . SetGenericRepDefault ( 6 , CVAR_Int ) ;
2020-01-22 12:53:26 +00:00
hud_size_max = 7 ;
2019-12-26 12:04:29 +00:00
}
2020-01-22 14:21:07 +00:00
if ( g_gameType & GAMEFLAG_SW )
{
hud_size . SetGenericRepDefault ( 8 , CVAR_Int ) ;
hud_size_max = 9 ;
2020-02-12 21:46:18 +00:00
cl_weaponswitch . SetGenericRepDefault ( 1 , CVAR_Int ) ;
if ( cl_weaponswitch > 1 ) cl_weaponswitch = 1 ;
2020-01-22 14:21:07 +00:00
}
2020-01-22 15:14:01 +00:00
if ( g_gameType & GAMEFLAG_PSEXHUMED )
{
hud_size . SetGenericRepDefault ( 7 , CVAR_Int ) ;
hud_size_max = 8 ;
}
2019-12-26 12:04:29 +00:00
2019-12-23 18:37:40 +00:00
G_ReadConfig ( currentGame ) ;
V_InitFontColors ( ) ;
GStrings . LoadStrings ( ) ;
I_Init ( ) ;
V_InitScreenSize ( ) ;
V_InitScreen ( ) ;
StartScreen = FStartupScreen : : CreateInstance ( 100 ) ;
2019-12-11 22:41:05 +00:00
TArray < FString > addArt ;
for ( auto & grp : usedgroups )
{
for ( auto & art : grp . FileInfo . loadart )
{
addArt . Push ( art ) ;
}
}
2019-12-17 19:08:59 +00:00
if ( userConfig . AddArt ) for ( auto & art : * userConfig . AddArt )
{
addArt . Push ( art ) ;
}
2019-12-11 22:41:05 +00:00
TileFiles . AddArt ( addArt ) ;
2019-11-02 19:13:00 +00:00
2019-12-23 18:37:40 +00:00
inputState . ClearAllInput ( ) ;
2019-11-04 22:01:50 +00:00
2019-11-02 19:13:00 +00:00
if ( ! GameConfig - > IsInitialized ( ) )
{
CONFIG_ReadCombatMacros ( ) ;
}
if ( userConfig . CommandName . IsNotEmpty ( ) )
{
playername = userConfig . CommandName ;
}
2019-11-05 19:07:16 +00:00
V_InitFonts ( ) ;
2019-12-02 01:07:32 +00:00
C_CON_SetAliases ( ) ;
2019-12-26 13:04:53 +00:00
sfx_empty = fileSystem . FindFile ( " engine/dsempty.lmp " ) ; // this must be done outside the sound code because it's initialized late.
2019-11-11 00:01:18 +00:00
Mus_Init ( ) ;
2019-11-14 20:07:43 +00:00
InitStatistics ( ) ;
2019-11-21 21:31:46 +00:00
M_Init ( ) ;
2019-11-24 23:02:00 +00:00
SetDefaultStrings ( ) ;
2020-02-26 19:16:27 +00:00
if ( g_gameType & ( GAMEFLAG_RR | GAMEFLAG_RRRA ) ) InitRREndMap ( ) ; // this needs to be done better later
2019-12-28 11:59:19 +00:00
if ( Args - > CheckParm ( " -sounddebug " ) )
C_DoCommand ( " stat sounddebug " ) ;
2019-12-23 18:37:40 +00:00
if ( enginePreInit ( ) )
{
2019-12-24 18:59:14 +00:00
I_FatalError ( " app_main: There was a problem initializing the Build engine: %s \n " , engineerrstr ) ;
2019-12-23 18:37:40 +00:00
}
2019-12-24 11:59:26 +00:00
mouseGrabInput ( true ) ; // the intros require the mouse to be grabbed.
2020-01-23 18:14:10 +00:00
auto exec = C_ParseCmdLineParams ( nullptr ) ;
if ( exec ) exec - > ExecCommands ( ) ;
2019-10-31 23:32:56 +00:00
return gi - > app_main ( ) ;
2019-10-25 22:32:49 +00:00
}
2019-12-24 18:59:14 +00:00
void G_HandleMemErr ( int32_t lineNum , const char * fileName , const char * funcName )
{
I_FatalError ( " Out of memory in %s:%d (%s) \n " , fileName , lineNum , funcName ) ;
}
void G_FatalEngineError ( void )
{
2020-01-07 00:11:19 +00:00
I_FatalError ( " There was a problem initializing the engine: %s \n \n The application will now close. " , engineerrstr ) ;
2019-12-24 18:59:14 +00:00
}
2019-10-27 07:14:58 +00:00
//==========================================================================
//
//
//
//==========================================================================
CVAR ( String , combatmacro0 , " " , CVAR_ARCHIVE | CVAR_USERINFO )
CVAR ( String , combatmacro1 , " " , CVAR_ARCHIVE | CVAR_USERINFO )
CVAR ( String , combatmacro2 , " " , CVAR_ARCHIVE | CVAR_USERINFO )
CVAR ( String , combatmacro3 , " " , CVAR_ARCHIVE | CVAR_USERINFO )
CVAR ( String , combatmacro4 , " " , CVAR_ARCHIVE | CVAR_USERINFO )
CVAR ( String , combatmacro5 , " " , CVAR_ARCHIVE | CVAR_USERINFO )
CVAR ( String , combatmacro6 , " " , CVAR_ARCHIVE | CVAR_USERINFO )
CVAR ( String , combatmacro7 , " " , CVAR_ARCHIVE | CVAR_USERINFO )
CVAR ( String , combatmacro8 , " " , CVAR_ARCHIVE | CVAR_USERINFO )
CVAR ( String , combatmacro9 , " " , CVAR_ARCHIVE | CVAR_USERINFO )
FStringCVar * const CombatMacros [ ] = { & combatmacro0 , & combatmacro1 , & combatmacro2 , & combatmacro3 , & combatmacro4 , & combatmacro5 , & combatmacro6 , & combatmacro7 , & combatmacro8 , & combatmacro9 } ;
void CONFIG_ReadCombatMacros ( )
{
FScanner sc ;
2019-11-24 09:03:19 +00:00
try
{
2019-12-26 13:46:14 +00:00
sc . Open ( " engine/combatmacros.txt " ) ;
2019-11-24 09:03:19 +00:00
for ( auto s : CombatMacros )
{
sc . MustGetToken ( TK_StringConst ) ;
if ( strlen ( * s ) = = 0 )
* s = sc . String ;
}
}
catch ( const std : : runtime_error & )
2019-10-27 07:14:58 +00:00
{
2019-11-24 09:03:19 +00:00
// We do not want this to error out. Just ignore if it fails.
2019-10-27 07:14:58 +00:00
}
}
2019-10-26 19:50:49 +00:00
//==========================================================================
//
//
//
//==========================================================================
2019-10-27 12:40:24 +00:00
static FString CONFIG_GetMD4EntryName ( uint8_t const * const md4 )
{
return FStringf ( " MD4_%08x%08x%08x%08x " ,
B_BIG32 ( B_UNBUF32 ( & md4 [ 0 ] ) ) , B_BIG32 ( B_UNBUF32 ( & md4 [ 4 ] ) ) ,
B_BIG32 ( B_UNBUF32 ( & md4 [ 8 ] ) ) , B_BIG32 ( B_UNBUF32 ( & md4 [ 12 ] ) ) ) ;
}
int32_t CONFIG_GetMapBestTime ( char const * const mapname , uint8_t const * const mapmd4 )
{
auto m = CONFIG_GetMD4EntryName ( mapmd4 ) ;
if ( GameConfig - > SetSection ( " MapTimes " ) )
{
auto s = GameConfig - > GetValueForKey ( m ) ;
2019-12-02 01:07:32 +00:00
if ( s ) return ( int ) strtoull ( s , nullptr , 0 ) ;
2019-10-27 12:40:24 +00:00
}
return - 1 ;
}
int CONFIG_SetMapBestTime ( uint8_t const * const mapmd4 , int32_t tm )
{
FStringf t ( " %d " , tm ) ;
auto m = CONFIG_GetMD4EntryName ( mapmd4 ) ;
if ( GameConfig - > SetSection ( " MapTimes " ) )
{
GameConfig - > SetValueForKey ( m , t ) ;
}
return 0 ;
}
2019-10-26 21:45:55 +00:00
2019-12-15 12:34:00 +00:00
CCMD ( snd_reset )
2019-10-26 21:45:55 +00:00
{
2019-12-15 12:34:00 +00:00
Mus_Stop ( ) ;
2019-12-15 16:16:11 +00:00
if ( soundEngine ) soundEngine - > Reset ( ) ;
2019-12-26 12:04:29 +00:00
Mus_ResumeSaved ( ) ;
2019-10-26 21:45:55 +00:00
}
2019-12-22 19:55:47 +00:00
//==========================================================================
//
// S_SetSoundPaused
//
// Called with state non-zero when the app is active, zero when it isn't.
//
//==========================================================================
void S_SetSoundPaused ( int state )
{
#if 0
if ( state )
{
if ( paused = = 0 )
{
S_ResumeSound ( true ) ;
if ( GSnd ! = nullptr )
{
GSnd - > SetInactive ( SoundRenderer : : INACTIVE_Active ) ;
}
}
}
else
{
if ( paused = = 0 )
{
S_PauseSound ( false , true ) ;
if ( GSnd ! = nullptr )
{
GSnd - > SetInactive ( gamestate = = GS_LEVEL | | gamestate = = GS_TITLELEVEL ?
SoundRenderer : : INACTIVE_Complete :
SoundRenderer : : INACTIVE_Mute ) ;
}
}
}
if ( ! netgame
# ifdef _DEBUG
& & ! demoplayback
# endif
)
{
pauseext = ! state ;
}
# endif
}
2020-01-12 13:54:43 +00:00
# define MAX_ERRORTEXT 4096
//==========================================================================
//
// I_Error
//
// Throw an error that will send us to the console if we are far enough
// along in the startup process.
//
//==========================================================================
void I_Error ( const char * error , . . . )
{
va_list argptr ;
char errortext [ MAX_ERRORTEXT ] ;
va_start ( argptr , error ) ;
vsnprintf ( errortext , MAX_ERRORTEXT , error , argptr ) ;
va_end ( argptr ) ;
2020-02-03 20:30:57 +00:00
I_DebugPrint ( errortext ) ;
2020-01-12 13:54:43 +00:00
throw std : : runtime_error ( errortext ) ;
}
void I_FatalError ( const char * error , . . . )
{
va_list argptr ;
char errortext [ MAX_ERRORTEXT ] ;
va_start ( argptr , error ) ;
vsnprintf ( errortext , MAX_ERRORTEXT , error , argptr ) ;
va_end ( argptr ) ;
2020-02-03 20:30:57 +00:00
I_DebugPrint ( errortext ) ;
2020-01-12 13:54:43 +00:00
throw std : : runtime_error ( errortext ) ;
}
//
// debugprintf() -- sends a debug string to the debugger
//
void debugprintf ( const char * f , . . . )
{
va_list va ;
va_start ( va , f ) ;
FString out ;
out . VFormat ( f , va ) ;
I_DebugPrint ( out ) ;
}
2020-02-02 09:27:47 +00:00
int CalcSmoothRatio ( const ClockTicks & totalclk , const ClockTicks & ototalclk , int realgameticspersec )
{
const double TICRATE = 120. ;
double rfreq = ( refreshfreq ! = - 1 ? refreshfreq : 60 ) ;
rfreq = rfreq * TICRATE / timerGetClockRate ( ) ;
double elapsedTime = ( totalclk - ototalclk ) . toScale16F ( ) ;
double elapsedFrames = elapsedTime * rfreq * ( 1. / TICRATE ) ;
double ratio = ( elapsedFrames * realgameticspersec ) / rfreq ;
return clamp ( xs_RoundToInt ( ratio * 65536 ) , 0 , 65536 ) ;
}
2020-02-05 11:57:17 +00:00
FString G_GetDemoPath ( )
{
FString path = M_GetDemoPath ( ) ;
path < < LumpFilter < < ' / ' ;
CreatePath ( path ) ;
return path ;
}
2020-03-08 12:54:00 +00:00
CCMD ( printinterface )
{
Printf ( " Current interface is %s \n " , gi - > Name ( ) ) ;
}