raze/source/core/gamecontrol.cpp

1660 lines
42 KiB
C++
Raw Normal View History

//-------------------------------------------------------------------------
/*
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.
*/
//-------------------------------------------------------------------------
#include <stdexcept>
#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"
#include "gamecvars.h"
2019-10-26 11:16:32 +00:00
#include "build.h"
#include "inputstate.h"
#include "m_argv.h"
#include "rts.h"
#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"
#include "raze_music.h"
#include "statistics.h"
#include "razemenu.h"
2019-11-23 11:38:38 +00:00
#include "gstrings.h"
#include "quotemgr.h"
#include "mapinfo.h"
#include "raze_sound.h"
#include "i_system.h"
#include "inputstate.h"
#include "v_video.h"
#include "st_start.h"
#include "s_music.h"
#include "i_video.h"
#include "v_text.h"
#include "resourcefile.h"
#include "c_dispatch.h"
2020-04-11 21:50:43 +00:00
#include "engineerrors.h"
2020-04-11 22:04:02 +00:00
#include "gamestate.h"
#include "gstrings.h"
2020-05-24 21:13:08 +00:00
#include "texturemanager.h"
#include "i_interface.h"
#include "x86.h"
#include "startupinfo.h"
#include "mapinfo.h"
#include "menustate.h"
#include "screenjob_.h"
#include "statusbar.h"
#include "uiinput.h"
#include "d_net.h"
#include "automap.h"
#include "v_draw.h"
#include "gi.h"
2021-04-22 17:59:45 +00:00
#include "vm.h"
#include "g_mapinfo.h"
2021-03-25 08:13:16 +00:00
#include "gamefuncs.h"
#include "hw_voxels.h"
#include "hw_palmanager.h"
#include "razefont.h"
#include "coreactor.h"
#include "wipe.h"
#include "findfile.h"
#include "version.h"
2022-08-03 13:50:27 +00:00
#include "hw_material.h"
void LoadHexFont(const char* filename);
CVAR(Bool, autoloadlights, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
CVAR(Bool, autoloadbrightmaps, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
2020-10-31 11:46:53 +00:00
CVAR(Bool, autoloadwidescreen, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
// Note: For the automap label there is a separate option "am_textfont".
CVARD(Bool, hud_textfont, false, CVAR_ARCHIVE, "Use the regular text font as replacement for the tiny 3x5 font for HUD messages whenever possible")
2020-10-04 22:46:24 +00:00
EXTERN_CVAR(Bool, ui_generic)
2022-10-02 18:33:18 +00:00
EXTERN_CVAR(String, language)
CUSTOM_CVAR(Int, mouse_capturemode, 1, CVAR_GLOBALCONFIG | CVAR_ARCHIVE)
{
if (self < 0)
{
self = 0;
}
else if (self > 2)
{
self = 2;
}
}
void I_UpdateWindowTitle();
CUSTOM_CVAR (Bool, i_discordrpc, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
{
I_UpdateWindowTitle();
}
CUSTOM_CVAR(Int, I_FriendlyWindowTitle, 1, CVAR_GLOBALCONFIG|CVAR_ARCHIVE|CVAR_NOINITCALL)
{
I_UpdateWindowTitle();
}
// The last remains of sdlayer.cpp
GameInterface* gi;
int myconnectindex, numplayers;
int connecthead, connectpoint2[MAXPLAYERS];
auto vsnprintfptr = vsnprintf; // This is an inline in Visual Studio but we need an address for it to satisfy the MinGW compiled libraries.
int lastTic;
extern bool pauseext;
cycle_t thinktime, actortime, gameupdatetime, drawtime;
2020-06-23 19:12:15 +00:00
gameaction_t gameaction = ga_nothing;
// gameaction state
MapRecord* g_nextmap;
int g_nextskill;
int g_bossexit;
2020-04-11 22:04:02 +00:00
2020-04-11 21:54:33 +00:00
FILE* hashfile;
InputState inputState;
int ShowStartupWindow(TArray<GrpEntry> &);
TArray<FString> GetGameFronUserFiles();
void InitFileSystem(TArray<GrpEntry>&);
void I_SetWindowTitle(const char* caption);
void S_ParseSndInfo();
void I_DetectOS(void);
void LoadScripts();
void MainLoop();
void SetConsoleNotifyBuffer();
bool PreBindTexture(FRenderState* state, FGameTexture*& tex, EUpscaleFlags& flags, int& scaleflags, int& clampmode, int& translation, int& overrideshader);
void highTileSetup();
void FontCharCreated(FGameTexture* base, FGameTexture* untranslated);
void LoadVoxelModels();
void MarkMap();
void BuildFogTable();
void ParseGLDefs();
void I_UpdateDiscordPresence(bool SendPresence, const char* curstatus, const char* appid, const char* steamappid);
2022-10-02 18:33:18 +00:00
bool G_Responder(event_t* ev);
void HudScaleChanged();
bool M_SetSpecialMenu(FName& menu, int param);
void OnMenuOpen(bool makeSound);
DStatusBarCore* StatusBar;
2019-10-31 23:32:56 +00:00
FString currentGame;
FString LumpFilter;
2019-10-26 11:16:32 +00:00
2022-10-02 18:33:18 +00:00
EXTERN_CVAR(Bool, queryiwad);
EXTERN_CVAR(String, defaultiwad);
CVAR(Bool, disableautoload, 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;
bool pausedWithKey;
bool gamesetinput = false;
int PlayClock;
extern int nextwipe;
CUSTOM_CVAR(Int, cl_gender, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
{
if (self < 0 || self > 3) self = 0;
}
int StrTable_GetGender()
{
return cl_gender;
}
bool validFilter(const char* str);
extern int chatmodeon;
bool System_WantGuiCapture()
{
bool wantCapt;
if (menuactive == MENU_Off)
{
wantCapt = ConsoleState == c_down || ConsoleState == c_falling || chatmodeon;
}
else
{
wantCapt = (menuactive == MENU_On || menuactive == MENU_OnNoPause);
}
return wantCapt;
}
bool System_DispatchEvent(event_t* ev)
{
if (ev->type == EV_Mouse && !System_WantGuiCapture())
{
inputState.MouseAddToPos(ev->x, -ev->y);
return true;
}
inputState.AddEvent(ev);
return false;
}
bool System_WantLeftButton()
{
return false;// (gamestate == GS_MENUSCREEN || gamestate == GS_TITLELEVEL);
}
bool System_NetGame()
{
return false; // fixme later. For now there is no netgame support.
}
bool System_WantNativeMouse()
{
return false;
}
static bool System_CaptureModeInGame()
{
return true;
}
static bool System_DisableTextureFilter()
{
return hw_useindexedcolortextures;
}
static IntRect System_GetSceneRect()
{
int viewbottom = viewport3d.Bottom();
int viewheight = viewport3d.Height();
int viewright = viewport3d.Right();
int viewwidth = viewport3d.Width();
int renderheight;
if (viewheight == screen->GetHeight()) renderheight = viewheight;
else renderheight = (viewwidth * screen->GetHeight() / screen->GetWidth()) & ~7;
IntRect mSceneViewport;
mSceneViewport.left = viewport3d.Left();
mSceneViewport.top = screen->GetHeight() - (renderheight + viewport3d.Top() - ((renderheight - viewheight) / 2));
mSceneViewport.width = viewwidth;
mSceneViewport.height = renderheight;
return mSceneViewport;
}
2020-01-08 00:00:57 +00:00
//==========================================================================
//
// DoomSpecificInfo
2020-01-08 00:00:57 +00:00
//
// Called by the crash logger to get application-specific information.
2020-01-08 00:00:57 +00:00
//
//==========================================================================
void System_CrashInfo(char* buffer, size_t bufflen, const char *lfstr)
2020-01-08 00:00:57 +00:00
{
const char* arg;
char* const buffend = buffer + bufflen - 2; // -2 for CRLF at end
int i;
buffer += mysnprintf(buffer, buffend - buffer, GAMENAME " version %s (%s)", GetVersionString(), GetGitHash());
buffer += snprintf(buffer, buffend - buffer, "%sCommand line:", lfstr);
for (i = 0; i < Args->NumArgs(); ++i)
{
buffer += snprintf(buffer, buffend - buffer, " %s", Args->GetArg(i));
}
for (i = 0; (arg = fileSystem.GetResourceFileName(i)) != NULL; ++i)
{
buffer += mysnprintf(buffer, buffend - buffer, "%sFile %d: %s", lfstr, i, arg);
}
buffer += mysnprintf(buffer, buffend - buffer, "%s", lfstr);
*buffer = 0;
2020-01-08 00:00:57 +00:00
}
2020-01-08 00:00:57 +00:00
//==========================================================================
//
//
//
//==========================================================================
UserConfig userConfig;
2021-04-22 17:59:45 +00:00
DEFINE_GLOBAL(userConfig)
DEFINE_FIELD_X(UserConfigStruct, UserConfig, nomonsters)
DEFINE_FIELD_X(UserConfigStruct, UserConfig, nosound)
DEFINE_FIELD_X(UserConfigStruct, UserConfig, nologo)
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"))
{
Printf("Build-format config files not supported and will be ignored\n");
}
auto v = Args->CheckValue("-addon");
if (v)
{
auto val = strtol(v, nullptr, 0);
static const char* const addons[] = { "DUKE3D.GRP", "DUKEDC.GRP", "NWINTER.GRP", "VACATION.GRP" };
if (val >= 0 && val < 4) gamegrp = addons[val];
else Printf("%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";
}
// 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));
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");
}
else if (Args->CheckParm("-cryptic"))
{
gamegrp = "BLOOD.RFF";
DefaultCon = "CRYPTIC.INI";
const char* argv[] = { "CPART07.AR_", "CPART15.AR_" };
AddArt.reset(new FArgs(2, argv));
}
v = Args->CheckValue("-gamegrp");
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");
UserDef = Args->CheckValue("-def");
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");
}
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", "-nocreatures", 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");
nosound = Args->CheckParm("-nosfx") || Args->CheckParm("-nosound");
if (Args->CheckParm("-setup")) queryiwad = 1;
else if (Args->CheckParm("-nosetup")) queryiwad = 0;
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
{
// Trying to emulate Build. This means to treat RFF files as lowest priority, then all GRPs and then all directories.
// This is only for people depending on lauchers. Since the semantics are so crappy it is strongly recommended to
// use -file instead which gives the user full control over the order in which things are added.
// 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"));
}
if (Args->CheckParm("-showcoords") || Args->CheckParm("-w"))
{
C_DoCommand("stat coord");
}
}
//==========================================================================
//
//
//
//==========================================================================
void CheckUserMap()
{
if (userConfig.CommandMap.IsEmpty()) return;
if (FindMapByName(userConfig.CommandMap))
{
return; // we already got a record for this map so no need for further checks.
}
FString startupMap = userConfig.CommandMap;
DefaultExtension(startupMap, ".map");
startupMap.Substitute("\\", "/");
NormalizeFileName(startupMap);
if (!fileSystem.FileExists(startupMap))
{
Printf(PRINT_HIGH, "Level \"%s\" not found.\n", startupMap.GetChars());
startupMap = "";
}
userConfig.CommandMap = startupMap;
}
//==========================================================================
//
//
//
//==========================================================================
namespace Duke3d
{
::GameInterface* CreateInterface();
}
namespace Blood
{
::GameInterface* CreateInterface();
}
namespace ShadowWarrior
{
::GameInterface* CreateInterface();
}
namespace Exhumed
{
::GameInterface* CreateInterface();
}
void CheckFrontend(int flags)
{
if (flags & GAMEFLAG_BLOOD)
{
gi = Blood::CreateInterface();
}
else if (flags & GAMEFLAG_SW)
{
gi = ShadowWarrior::CreateInterface();
}
else if (flags & GAMEFLAG_PSEXHUMED)
{
gi = Exhumed::CreateInterface();
}
else
{
gi = Duke3d::CreateInterface();
}
}
static void System_ToggleFullConsole()
{
gameaction = ga_fullconsole;
}
static void System_StartCutscene(bool blockui)
{
gameaction = blockui ? ga_intro : ga_intermission;
}
static void System_SetTransition(int type)
{
nextwipe = type;
}
2022-10-02 18:33:18 +00:00
void I_StartupJoysticks();
void I_ShutdownInput();
int RunGame();
void System_MenuClosed();
void System_MenuDim();
2019-11-05 19:07:16 +00:00
int GameMain()
{
int r;
I_InitTime();
2022-10-21 22:12:17 +00:00
C_InitCVars(0);
SetConsoleNotifyBuffer();
2020-10-03 15:04:45 +00:00
sysCallbacks =
{
2022-10-02 18:33:18 +00:00
G_Responder,
System_WantGuiCapture,
System_WantLeftButton,
System_NetGame,
System_WantNativeMouse,
System_CaptureModeInGame,
nullptr,
nullptr,
nullptr,
System_DisableTextureFilter,
nullptr,
System_GetSceneRect,
nullptr,
System_MenuDim,
nullptr,
System_DispatchEvent,
validFilter,
StrTable_GetGender,
System_MenuClosed,
nullptr,
nullptr,
PreBindTexture,
FontCharCreated,
System_ToggleFullConsole,
System_StartCutscene,
System_SetTransition,
2022-10-02 18:33:18 +00:00
CheckCheatmode,
HudScaleChanged,
M_SetSpecialMenu,
OnMenuOpen,
nullptr,
nullptr,
[]() ->FConfigFile* { return GameConfig; }
};
2019-11-05 19:07:16 +00:00
try
{
r = RunGame();
2019-11-05 19:07:16 +00:00
}
catch (const CExitEvent& exit)
2019-11-05 19:07:16 +00:00
{
// Just let the rest of the function execute.
r = exit.Reason();
}
catch (const std::exception& err)
{
// shut down critical systems before showing a message box.
I_ShowFatalError(err.what());
r = -1;
}
//DeleteScreenJob();
if (gi) gi->FreeLevelData();
2020-10-10 13:16:28 +00:00
DeinitMenus();
if (StatusBar) StatusBar->Destroy();
StatusBar = nullptr;
if (gi)
{
gi->FreeGameData(); // Must be done before taking down any subsystems.
}
S_StopMusic(true);
if (soundEngine) delete soundEngine;
soundEngine = nullptr;
I_CloseSound();
I_ShutdownInput();
G_SaveConfig();
C_DeinitConsole();
V_ClearFonts();
voxClear();
ClearPalManager();
TexMan.DeleteAll();
TileFiles.CloseAll(); // delete the texture data before shutting down graphics.
I_ShutdownGraphics();
if (gi)
{
delete gi;
gi = nullptr;
}
DeleteStartupScreen();
PClass::StaticShutdown();
2022-10-21 22:12:17 +00:00
C_UninitCVars();
if (Args) delete Args;
2019-11-05 19:07:16 +00:00
return r;
}
2019-10-26 17:24:46 +00:00
//==========================================================================
//
//
//
//==========================================================================
void SetDefaultStrings()
{
// Blood hard codes its skill names, so we have to define them manually.
if (isBlood())
{
gSkillNames[0] = "$STILL KICKING";
gSkillNames[1] = "$PINK ON THE INSIDE";
gSkillNames[2] = "$LIGHTLY BROILED";
gSkillNames[3] = "$WELL DONE";
gSkillNames[4] = "$EXTRA CRISPY";
}
// Exhumed has no skills, but we still need a menu with one entry.
else if (isExhumed())
{
gSkillNames[0] = "Default";
}
//Set a few quotes which are used for common handling of a few status messages
quoteMgr.InitializeQuote(23, "$MESSAGES: ON");
quoteMgr.InitializeQuote(24, "$MESSAGES: OFF");
quoteMgr.InitializeQuote(83, "$FOLLOW MODE OFF");
quoteMgr.InitializeQuote(84, "$FOLLOW MODE ON");
quoteMgr.InitializeQuote(85, "$AUTORUNOFF");
quoteMgr.InitializeQuote(86, "$AUTORUNON");
}
2019-10-26 17:24:46 +00:00
//==========================================================================
//
//
//
//==========================================================================
static TArray<GrpEntry> SetupGame()
2019-10-26 11:16:32 +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();
if (groups.Size() == 0)
{
// Abort if no game data found.
G_SaveConfig();
I_Error("Unable to find any game data. Please verify your settings."
#ifdef WIN32
);
#else
"\nInstall game data files in subfolders of '%s'\n\n", M_GetAppDataPath(false).GetChars());
#endif
}
decltype(groups) usedgroups;
int groupno = -1;
auto game = GetGameFronUserFiles();
if (userConfig.gamegrp.IsEmpty())
{
for (auto& str : game)
{
int g = 0;
for (auto& grp : groups)
{
if (grp.FileInfo.gameid.CompareNoCase(str) == 0)
{
userConfig.gamegrp = grp.FileName;
groupno = g;
goto foundit;
}
g++;
}
}
}
foundit:
// If the user has specified a file name, let's see if we know it.
//
if (groupno == -1 && userConfig.gamegrp.Len())
2019-10-31 23:32:56 +00:00
{
FString gamegrplower = userConfig.gamegrp.MakeLower();
if (gamegrplower[1] != ':' || gamegrplower[2] != '/') gamegrplower.Insert(0, "/");
int g = 0;
for (auto& grp : groups)
2019-10-31 23:32:56 +00:00
{
auto grplower = grp.FileName.MakeLower();
FixPathSeperator(grplower);
auto pos = grplower.LastIndexOf(gamegrplower);
if (pos >= 0 && pos == ptrdiff_t(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;
}
g++;
}
}
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);
}
2022-10-02 18:33:18 +00:00
int flags = 0;
if (disableautoload) flags |= 1;
if (autoloadlights) flags |= 2;
if (autoloadbrightmaps) flags |= 4;
if (autoloadwidescreen) flags |= 8;
pick = I_PickIWad(&wads[0], (int)wads.Size(), queryiwad, pick, flags);
if (pick >= 0)
{
2022-10-02 18:33:18 +00:00
disableautoload = !!(flags & 1);
autoloadlights = !!(flags & 2);
autoloadbrightmaps = !!(flags & 4);
autoloadwidescreen = !!(flags & 8);
// The newly selected IWAD becomes the new default
defaultiwad = groups[pick].FileName;
}
groupno = pick;
}
}
else if (groups.Size() == 1)
{
groupno = 0;
}
}
if (groupno == -1) return TArray<GrpEntry>();
auto& group = groups[groupno];
// 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)
{
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
}
}
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.
if (ugroup.FileInfo.rtsname.IsNotEmpty() && **rtsname == 0) RTS_Init(ugroup.FileInfo.rtsname);
// 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 = GameStartupInfo.con.IsNotEmpty()? GameStartupInfo.con : 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-31 23:32:56 +00:00
currentGame = LumpFilter;
currentGame.Truncate(currentGame.IndexOf("."));
PClass::StaticInit();
CheckFrontend(g_gameType);
gameinfo.gametype = g_gameType;
return usedgroups;
}
//==========================================================================
//
//
//
//==========================================================================
void InitLanguages()
{
GStrings.LoadStrings(language);
}
void CreateStatusBar()
{
auto stbarclass = PClass::FindClass(globalCutscenes.StatusBarClass);
if (!stbarclass)
{
I_FatalError("No status bar defined");
}
StatusBar = static_cast<DStatusBarCore*>(stbarclass->CreateNew());
StatusBar->SetSize(0, 320, 200);
2021-05-13 17:48:27 +00:00
InitStatusBar();
GC::AddMarkerFunc([]() { GC::Mark(StatusBar); });
}
void GetGames()
{
auto getgames = Args->CheckValue("-getgames");
if (getgames)
{
try
{
auto groups = GrpScan();
FSerializer arc;
if (arc.OpenWriter())
{
if (arc.BeginArray("games"))
{
for (auto& entry : groups)
{
if (arc.BeginObject(nullptr))
{
arc("filename", entry.FileName)
("description", entry.FileInfo.name)
("defname", entry.FileInfo.defname)
("scriptname", entry.FileInfo.scriptname)
("gamefilter", entry.FileInfo.gamefilter)
("gameid", entry.FileInfo.gameid)
("fgcolor", entry.FileInfo.FgColor)
("bkcolor", entry.FileInfo.BgColor)
("addon", entry.FileInfo.isAddon)
.EndObject();
}
}
arc.EndArray();
}
unsigned int len;
auto p = arc.GetOutput(&len);
FILE* f = fopen(getgames, "wb");
if (f)
{
fwrite(p, 1, len, f);
fclose(f);
}
}
}
catch (...)
{
// Ignore all errors
}
throw CExitEvent(0);
}
}
//==========================================================================
//
//
//
//==========================================================================
static void InitTextures()
{
TexMan.usefullnames = true;
TexMan.Init();
TexMan.AddTextures([]() {}, [](BuildInfo&) {});
StartWindow->Progress();
TileFiles.Init();
TileFiles.LoadArtSet("tiles%03d.art"); // it's the same for all games.
voxInit();
gi->LoadGameTextures(); // loads game-side data that must be present before processing the .def files.
LoadDefinitions();
InitFont(); // InitFonts may only be called once all texture data has been initialized.
lookups.postLoadTables();
highTileSetup();
lookups.postLoadLookups();
SetupFontSubstitution();
V_LoadTranslations(); // loading the translations must be delayed until the palettes have been fully set up.
UpdateUpscaleMask();
TileFiles.SetBackup();
}
//==========================================================================
//
//
//
//==========================================================================
static uint8_t palindexmap[256];
int RunGame()
{
GameStartupInfo.FgColor = 0xffffff;
G_LoadConfig();
auto wad = BaseFileSearch(ENGINERES_FILE, NULL, true, GameConfig);
if (wad == NULL)
{
I_FatalError("Cannot find " ENGINERES_FILE);
}
LoadHexFont(wad); // load hex font early so we have it during startup.
// 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.
if (logfile.IsEmpty()) logfile.Format("%s" GAMENAMELOWERCASE ".log", M_GetDocumentsPath().GetChars());
if (logfile.IsNotEmpty())
{
execLogfile(logfile);
}
I_DetectOS();
userConfig.ProcessOptions();
GetGames();
auto usedgroups = SetupGame();
2021-04-12 17:39:42 +00:00
bool colorset = false;
for (int i = usedgroups.Size()-1; i >= 0; i--)
{
2021-04-12 17:39:42 +00:00
auto& grp = usedgroups[i];
if (grp.FileInfo.name.IsNotEmpty())
{
if (GameStartupInfo.Name.IsEmpty()) GameStartupInfo.Name = grp.FileInfo.name;
2021-04-12 17:39:42 +00:00
if (!colorset && grp.FileInfo.FgColor != grp.FileInfo.BgColor && (GameStartupInfo.FgColor != 0 || GameStartupInfo.BkColor != 0))
{
GameStartupInfo.FgColor = grp.FileInfo.FgColor;
GameStartupInfo.BkColor = grp.FileInfo.BgColor;
2021-04-12 17:39:42 +00:00
colorset = true;
}
}
if (grp.FileInfo.exclepisodes.Size())
{
for (auto& episode : grp.FileInfo.exclepisodes)
{
gi->AddExcludedEpisode(episode);
}
}
}
I_SetIWADInfo();
2019-11-02 19:13:00 +00:00
InitFileSystem(usedgroups);
if (usedgroups.Size() == 0) return 0;
// Handle CVARs with game specific defaults here.
if (isBlood())
{
2022-10-21 22:12:17 +00:00
mus_redbook->SetGenericRepDefault(false, CVAR_Bool); // Blood should default to CD Audio off - all other games must default to on.
am_showlabel->SetGenericRepDefault(true, CVAR_Bool);
}
if (isSWALL())
2020-01-22 14:21:07 +00:00
{
2022-10-21 22:12:17 +00:00
cl_weaponswitch->SetGenericRepDefault(1, CVAR_Int);
if (cl_weaponswitch > 1) cl_weaponswitch = 1;
2020-01-22 14:21:07 +00:00
}
if (g_gameType & (GAMEFLAG_BLOOD|GAMEFLAG_RR))
{
2022-10-21 22:12:17 +00:00
am_nameontop->SetGenericRepDefault(true, CVAR_Bool); // Blood and RR show the map name on the top of the screen by default.
}
G_ReadConfig(currentGame);
V_InitFontColors();
InitLanguages();
CheckCPUID(&CPU);
CalculateCPUSpeed();
auto ci = DumpCPUInfo(&CPU);
Printf("%s", ci.GetChars());
V_InitScreenSize();
V_InitScreen();
StartWindow = FStartupScreen::CreateInstance(8, true);
StartWindow->Progress();
TArray<FString> addArt;
for (auto& grp : usedgroups)
{
for (auto& art : grp.FileInfo.loadart)
{
addArt.Push(art);
}
}
if (userConfig.AddArt) for (auto& art : *userConfig.AddArt)
{
addArt.Push(art);
}
TileFiles.AddArt(addArt);
2019-11-02 19:13:00 +00:00
inputState.ClearAllInput();
2019-11-02 19:13:00 +00:00
if (!GameConfig->IsInitialized())
{
CONFIG_ReadCombatMacros();
}
if (userConfig.CommandName.IsNotEmpty())
{
playername = userConfig.CommandName;
}
GameTicRate = 30;
CheckUserMap();
palindexmap[0] = 255;
for (int i = 1; i <= 255; i++) palindexmap[i] = i;
GPalette.Init(MAXPALOOKUPS + 2, palindexmap); // one slot for each translation, plus a separate one for the base palettes and the internal one
gi->loadPalette();
BuildFogTable();
StartWindow->Progress();
InitTextures();
StartWindow->Progress();
I_InitSound();
StartWindow->Progress();
Mus_InitMusic();
S_ParseSndInfo();
S_ParseReverbDef();
InitStatistics();
LoadScripts();
StartWindow->Progress();
SetDefaultStrings();
2021-04-26 21:10:38 +00:00
Job_Init();
Local_Job_Init();
if (Args->CheckParm("-sounddebug"))
C_DoCommand("stat sounddebug");
SetupGameButtons();
gameinfo.mBackButton = "engine/graphics/m_back.png";
StartWindow->Progress();
GC::AddMarkerFunc(MarkMap);
gi->app_init();
StartWindow->Progress();
G_ParseMapInfo();
ParseGLDefs();
ReplaceMusics(true);
CreateStatusBar();
2020-10-04 20:21:11 +00:00
SetDefaultMenuColors();
M_Init();
BuildGameMenus();
StartWindow->Progress();
if (!(paletteloaded & PALETTE_MAIN))
I_FatalError("No palette found.");
FMaterial::SetLayerCallback(setpalettelayer);
I_UpdateWindowTitle();
DeleteStartupScreen();
V_Init2();
while (!screen->CompileNextShader())
{
// here we can do some visual updates later
}
2021-04-05 13:41:57 +00:00
twod->Begin(screen->GetWidth(), screen->GetHeight());
twod->End();
UpdateJoystickMenu(NULL);
UpdateVRModes();
setVideoMode();
LoadVoxelModels();
screen->BeginFrame();
screen->SetTextureFilterMode();
setViewport(hud_size);
D_CheckNetGame();
UpdateGenericUI(ui_generic);
MainLoop();
2020-09-02 21:03:48 +00:00
return 0; // this is never reached. MainLoop only exits via exception.
}
//---------------------------------------------------------------------------
//
// The one and only main loop in the entire engine. Yay!
//
//---------------------------------------------------------------------------
void updatePauseStatus()
{
// This must go through the network in multiplayer games.
if (M_Active() || System_WantGuiCapture() || !AppActive)
{
paused = 1;
}
else if (!M_Active() || !System_WantGuiCapture())
{
if (!pausedWithKey)
{
paused = 0;
}
if (sendPause)
{
sendPause = false;
paused = pausedWithKey ? 0 : 2;
pausedWithKey = !!paused;
}
}
if (paused)
S_PauseSound(!pausedWithKey, !paused);
else
S_ResumeSound(paused);
}
//==========================================================================
//
//
//
//==========================================================================
void LoadVoxelModels(void);
2021-02-25 11:16:21 +00:00
void setVideoMode()
{
int xdim = screen->GetWidth();
int ydim = screen->GetHeight();
2021-02-25 11:16:21 +00:00
V_UpdateModeSize(xdim, ydim);
viewport3d = { 0, 0, xdim, ydim };
}
//==========================================================================
//
//
//
//==========================================================================
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)
2022-10-21 22:12:17 +00:00
FStringCVarRef* 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
{
sc.Open("engine/combatmacros.txt");
2019-11-24 09:03:19 +00:00
for (auto s : CombatMacros)
{
sc.MustGetToken(TK_StringConst);
UCVarValue val;
val.String = sc.String;
2022-10-21 22:12:17 +00:00
s->get()->SetGenericRepDefault(val, CVAR_String);
2019-11-24 09:03:19 +00:00
}
}
2020-04-11 21:50:43 +00:00
catch (const CRecoverableError &)
{
2019-11-24 09:03:19 +00:00
// We do not want this to error out. Just ignore if it fails.
}
}
//==========================================================================
//
//
//
//==========================================================================
CCMD(snd_reset)
{
Mus_Stop();
if (soundEngine) soundEngine->Reset();
Mus_ResumeSaved();
}
2022-01-16 01:20:46 +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());
}
2020-04-11 22:04:02 +00:00
CCMD (togglemsg)
{
FBaseCVar *var, *prev;
UCVarValue val;
if (argv.argc() > 1)
{
if ( (var = FindCVar (argv[1], &prev)) )
{
var->MarkUnsafe();
val = var->GetGenericRep (CVAR_Bool);
val.Bool = !val.Bool;
var->SetGenericRep (val, CVAR_Bool);
const char *statestr = argv.argc() <= 2? "*" : argv[2];
if (*statestr == '*')
{
2020-06-30 20:53:15 +00:00
Printf(PRINT_MEDIUM|PRINT_NOTIFY, "\"%s\" = \"%s\"\n", var->GetName(), val.Bool ? "true" : "false");
2020-04-11 22:04:02 +00:00
}
else
{
int state = (int)strtoll(argv[2], nullptr, 0);
if (state != 0)
{
// Order of Duke's quote string varies, some have on first, some off, so use the sign of the parameter to decide.
// Positive means Off/On, negative means On/Off
int quote = state > 0? state + val.Bool : -(state + val.Bool);
auto text = quoteMgr.GetQuote(quote);
2020-06-30 20:53:15 +00:00
if (text) Printf(PRINT_MEDIUM|PRINT_NOTIFY, "%s\n", text);
2020-04-11 22:04:02 +00:00
}
}
}
}
}
bool OkForLocalization(FTextureID texnum, const char* substitute)
{
return false;
}
// Mainly a dummy.
CCMD(taunt)
{
if (argv.argc() > 2)
{
int taunt = atoi(argv[1]);
int mode = atoi(argv[2]);
// In a ZDoom-style protocol this should be sent:
// Net_WriteByte(DEM_TAUNT);
// Net_WriteByte(taunt);
// Net_WriteByte(mode);
if (mode == 1)
{
// todo:
//gi->PlayTaunt(taunt);
// Duke:
// startrts(taunt, 1)
// Blood:
// sndStartSample(4400 + taunt, 128, 1, 0);
// SW:
// PlaySoundRTS(taunt);
// Exhumed does not implement RTS, should be like Duke
//
}
Printf(PRINT_NOTIFY, "%s", **CombatMacros[taunt - 1]);
}
}
void GameInterface::loadPalette()
{
paletteLoadFromDisk();
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void GameInterface::FreeLevelData()
{
// Make sure that there is no more level to toy around with.
InitSpriteLists();
sector.Reset();
wall.Reset();
currentLevel = nullptr;
GC::FullGC();
}
//---------------------------------------------------------------------------
//
// DrawCrosshair
//
//---------------------------------------------------------------------------
void ST_DrawCrosshair(int phealth, double xpos, double ypos, double scale);
//void DrawGenericCrosshair(int num, int phealth, double xdelta);
void ST_LoadCrosshair(int num, bool alwaysload);
CVAR(Int, crosshair, 0, CVAR_ARCHIVE)
void DrawCrosshair(int deftile, int health, double xdelta, double ydelta, double scale, PalEntry color)
{
if (automapMode == am_off && cl_crosshair)
{
if (deftile < MAXTILES && crosshair == 0)
{
auto tile = tileGetTexture(deftile);
if (tile)
{
double crosshair_scale = crosshairscale * scale;
DrawTexture(twod, tile, 160 + xdelta, 100 + ydelta, DTA_Color, color,
DTA_FullscreenScale, FSMode_Fit320x200, DTA_ScaleX, crosshair_scale, DTA_ScaleY, crosshair_scale, DTA_CenterOffsetRel, true,
DTA_ViewportX, viewport3d.Left(), DTA_ViewportY, viewport3d.Top(), DTA_ViewportWidth, viewport3d.Width(), DTA_ViewportHeight, viewport3d.Height(), TAG_DONE);
return;
}
}
// 0 means 'game provided crosshair' - use type 2 as fallback.
ST_LoadCrosshair(crosshair == 0 ? 2 : *crosshair, false);
double xpos = viewport3d.Width() * 0.5 + xdelta * viewport3d.Height() / 240.;
double ypos = viewport3d.Height() * 0.5;
ST_DrawCrosshair(health, xpos, ypos, 1);
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void LoadDefinitions()
{
const char* defsfile = G_DefFile();
FString razedefsfile = defsfile;
razedefsfile.Substitute(".def", "-raze.def");
loaddefinitionsfile("engine/engine.def", true, true); // Internal stuff that is required.
// check what we have.
// user .defs override the default ones and are not cumulative.
// if we fine even one Raze-specific file, all of those will be loaded cumulatively.
// otherwise the default rules inherited from older ports apply.
if (userConfig.UserDef.IsNotEmpty())
{
loaddefinitionsfile(userConfig.UserDef, false);
}
else
{
if (fileSystem.FileExists(razedefsfile))
{
loaddefinitionsfile(razedefsfile, true);
}
else if (fileSystem.FileExists(defsfile))
{
loaddefinitionsfile(defsfile, false);
}
}
if (userConfig.AddDefs)
{
for (auto& m : *userConfig.AddDefs)
{
loaddefinitionsfile(m, false);
}
userConfig.AddDefs.reset();
}
if (GameStartupInfo.def.IsNotEmpty())
{
loaddefinitionsfile(GameStartupInfo.def); // Stuff from gameinfo.
}
// load the widescreen replacements last. This ensures that mods still get the correct CRCs for their own tile replacements.
if (fileSystem.FindFile("engine/widescreen.def") >= 0 && !Args->CheckParm("-nowidescreen"))
{
loaddefinitionsfile("engine/widescreen.def");
}
fileSystem.InitHashChains(); // make sure that any resources that got added can be found again.
}
bool M_Active()
{
return CurrentMenu != nullptr || ConsoleState == c_down || ConsoleState == c_falling;
}
struct gamefilter
{
const char* gamename;
int gameflag;
};
static const gamefilter games[] = {
{ "Duke", GAMEFLAG_DUKE},
{ "Nam", GAMEFLAG_NAM | GAMEFLAG_NAPALM},
{ "NamOnly", GAMEFLAG_NAM}, // for cases where the difference matters.
{ "Napalm", GAMEFLAG_NAPALM},
{ "WW2GI", GAMEFLAG_WW2GI},
{ "Redneck", GAMEFLAG_RR},
{ "RedneckRides", GAMEFLAG_RRRA},
{ "Blood", GAMEFLAG_BLOOD},
{ "ShadowWarrior", GAMEFLAG_SW},
{ "Exhumed", GAMEFLAG_POWERSLAVE | GAMEFLAG_EXHUMED},
2020-10-08 20:20:41 +00:00
{ "Plutopak", GAMEFLAG_PLUTOPAK},
{ "Worldtour", GAMEFLAG_WORLDTOUR},
{ "Shareware", GAMEFLAG_SHAREWARE},
};
bool validFilter(const char* str)
{
for (auto& gf : games)
{
if (g_gameType & gf.gameflag)
{
if (!stricmp(str, gf.gamename)) return true;
}
}
return false;
}
2020-10-05 18:44:30 +00:00
#include "vm.h"
DEFINE_ACTION_FUNCTION(_Screen, GetViewWindow)
{
PARAM_PROLOGUE;
if (numret > 0) ret[0].SetInt(viewport3d.Left());
if (numret > 1) ret[1].SetInt(viewport3d.Top());
if (numret > 2) ret[2].SetInt(viewport3d.Width());
if (numret > 3) ret[3].SetInt(viewport3d.Height());
return min(numret, 4);
2020-10-05 18:44:30 +00:00
}
2021-04-23 08:07:02 +00:00
DEFINE_ACTION_FUNCTION_NATIVE(_Raze, ShadeToLight, shadeToLight)
2020-10-06 22:50:26 +00:00
{
PARAM_PROLOGUE;
PARAM_INT(shade);
ACTION_RETURN_INT(shadeToLight(shade));
}
2021-04-23 10:24:42 +00:00
DEFINE_ACTION_FUNCTION(_Raze, PlayerName)
{
PARAM_PROLOGUE;
PARAM_INT(index);
ACTION_RETURN_STRING(unsigned(index) >= MAXPLAYERS ? "" : PlayerName(index));
}
DEFINE_ACTION_FUNCTION_NATIVE(_Raze, GetBuildTime, I_GetBuildTime)
{
ACTION_RETURN_INT(I_GetBuildTime());
}
DEFINE_ACTION_FUNCTION(_Raze, PickTexture)
{
PARAM_PROLOGUE;
PARAM_INT(texid);
TexturePick pick;
if (PickTexture(TexMan.GetGameTexture(FSetTextureID(texid)), TRANSLATION(Translation_Remap, 0), pick))
{
ACTION_RETURN_INT(pick.texture->GetID().GetIndex());
}
ACTION_RETURN_INT(texid);
}
2021-05-01 22:35:56 +00:00
DEFINE_ACTION_FUNCTION(_MapRecord, GetCluster)
{
PARAM_SELF_STRUCT_PROLOGUE(MapRecord);
ACTION_RETURN_POINTER(FindCluster(self->cluster));
}
DEFINE_ACTION_FUNCTION(_Screen, GetTextScreenSize)
{
ACTION_RETURN_VEC2(DVector2(640, 480));
}
2020-10-05 18:44:30 +00:00
extern bool demoplayback;
DEFINE_GLOBAL(multiplayer)
DEFINE_GLOBAL(netgame)
DEFINE_GLOBAL(gameaction)
DEFINE_GLOBAL(gamestate)
DEFINE_GLOBAL(demoplayback)
DEFINE_GLOBAL(consoleplayer)
2021-04-23 10:24:42 +00:00
DEFINE_GLOBAL(currentLevel)
DEFINE_GLOBAL(paused)
DEFINE_GLOBAL(automapMode)
DEFINE_GLOBAL(PlayClock)
2021-04-23 10:24:42 +00:00
2021-05-01 22:35:56 +00:00
DEFINE_FIELD_X(ClusterDef, ClusterDef, name)
DEFINE_FIELD_X(ClusterDef, ClusterDef, InterBackground)
2021-04-23 10:24:42 +00:00
DEFINE_FIELD_X(MapRecord, MapRecord, parTime)
DEFINE_FIELD_X(MapRecord, MapRecord, designerTime)
DEFINE_FIELD_X(MapRecord, MapRecord, fileName)
DEFINE_FIELD_X(MapRecord, MapRecord, labelName)
DEFINE_FIELD_X(MapRecord, MapRecord, name)
DEFINE_FIELD_X(MapRecord, MapRecord, music)
DEFINE_FIELD_X(MapRecord, MapRecord, cdSongId)
DEFINE_FIELD_X(MapRecord, MapRecord, flags)
DEFINE_FIELD_X(MapRecord, MapRecord, levelNumber)
DEFINE_FIELD_X(MapRecord, MapRecord, cluster)
DEFINE_FIELD_X(MapRecord, MapRecord, NextMap)
DEFINE_FIELD_X(MapRecord, MapRecord, NextSecret)
2021-04-23 10:24:42 +00:00
//native readonly String messages[MAX_MESSAGES];
DEFINE_FIELD_X(MapRecord, MapRecord, Author)
2021-05-01 22:35:56 +00:00
DEFINE_FIELD_X(MapRecord, MapRecord, InterBackground)
DEFINE_FIELD_X(SummaryInfo, SummaryInfo, kills)
DEFINE_FIELD_X(SummaryInfo, SummaryInfo, maxkills)
DEFINE_FIELD_X(SummaryInfo, SummaryInfo, secrets)
DEFINE_FIELD_X(SummaryInfo, SummaryInfo, maxsecrets)
DEFINE_FIELD_X(SummaryInfo, SummaryInfo, supersecrets)
DEFINE_FIELD_X(SummaryInfo, SummaryInfo, playercount)
DEFINE_FIELD_X(SummaryInfo, SummaryInfo, time)
DEFINE_FIELD_X(SummaryInfo, SummaryInfo, cheated)
DEFINE_FIELD_X(SummaryInfo, SummaryInfo, endofgame)
void InitBuildTiles()
{
// need to find a better way to handle this thing.
}
static FString LevelName;
void TITLE_InformName(const char* newname)
{
LevelName = newname;
if (newname[0] == '$')
LevelName = GStrings(newname + 1);
I_UpdateWindowTitle();
}
void I_UpdateWindowTitle()
{
FString titlestr;
if (!(GameStartupInfo.Name.IsNotEmpty()))
return;
switch (I_FriendlyWindowTitle)
{
case 1:
if (LevelName.IsNotEmpty())
{
titlestr.Format("%s - %s", LevelName.GetChars(), GameStartupInfo.Name.GetChars());
break;
}
[[fallthrough]];
case 2:
titlestr = GameStartupInfo.Name;
break;
default:
I_UpdateDiscordPresence(false, NULL, GameStartupInfo.DiscordAppId.GetChars(), GameStartupInfo.SteamAppId.GetChars());
I_SetWindowTitle(NULL);
return;
}
// Strip out any color escape sequences before setting a window title
TArray<char> copy(titlestr.Len() + 1);
const char* srcp = titlestr;
char* dstp = copy.Data();
while (*srcp != 0)
{
if (*srcp != TEXTCOLOR_ESCAPE)
{
*dstp++ = *srcp++;
}
else if (srcp[1] == '[')
{
srcp += 2;
while (*srcp != ']' && *srcp != 0) srcp++;
if (*srcp == ']') srcp++;
}
else
{
if (srcp[1] != 0) srcp += 2;
else break;
}
}
*dstp = 0;
if (i_discordrpc)
I_UpdateDiscordPresence(true, copy.Data(), GameStartupInfo.DiscordAppId.GetChars(), GameStartupInfo.SteamAppId.GetChars());
else
I_UpdateDiscordPresence(false, nullptr, nullptr, nullptr);
I_SetWindowTitle(copy.Data());
}