//----------------------------------------------------------------------------- // Copyright 1993-1996 id Software // Copyright 1999-2016 Randy Heit // Copyright 2002-2016 Christoph Oelckers // // This program 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 3 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, see http://www.gnu.org/licenses/ // // // DESCRIPTION: // DOOM main program (D_DoomMain) and game loop (D_DoomLoop), // plus functions to determine game mode (shareware, registered), // parse command line parameters, configure game parameters (turbo), // and call the startup functions. // //----------------------------------------------------------------------------- // HEADER FILES ------------------------------------------------------------ #ifdef _WIN32 #include #endif #ifdef HAVE_FPU_CONTROL #include #endif #if defined(__unix__) || defined(__APPLE__) #include #endif #include #include #include "engineerrors.h" #include "i_time.h" #include "d_gui.h" #include "m_random.h" #include "doomdef.h" #include "doomstat.h" #include "gstrings.h" #include "filesystem.h" #include "s_sound.h" #include "v_video.h" #include "intermission/intermission.h" #include "wipe.h" #include "m_argv.h" #include "m_misc.h" #include "menu.h" #include "doommenu.h" #include "c_console.h" #include "c_dispatch.h" #include "i_sound.h" #include "i_video.h" #include "g_game.h" #include "hu_stuff.h" #include "wi_stuff.h" #include "st_stuff.h" #include "am_map.h" #include "p_setup.h" #include "r_utility.h" #include "r_sky.h" #include "d_main.h" #include "d_dehacked.h" #include "cmdlib.h" #include "v_text.h" #include "gi.h" #include "a_dynlight.h" #include "gameconfigfile.h" #include "sbar.h" #include "decallib.h" #include "version.h" #include "st_start.h" #include "teaminfo.h" #include "hardware.h" #include "sbarinfo.h" #include "d_net.h" #include "d_event.h" #include "d_netinf.h" #include "m_cheat.h" #include "m_joy.h" #include "v_draw.h" #include "po_man.h" #include "p_local.h" #include "autosegs.h" #include "fragglescript/t_fs.h" #include "g_levellocals.h" #include "events.h" #include "vm.h" #include "types.h" #include "i_system.h" #include "g_cvars.h" #include "r_data/r_vanillatrans.h" #include "s_music.h" #include "swrenderer/r_swcolormaps.h" #include "findfile.h" #include "md5.h" #include "c_buttons.h" #include "d_buttons.h" #include "i_interface.h" #include "animations.h" #include "texturemanager.h" #include "formats/multipatchtexture.h" #include "scriptutil.h" #include "v_palette.h" #include "texturemanager.h" #include "hw_clock.h" #include "hwrenderer/scene/hw_drawinfo.h" #include "doomfont.h" #include "screenjob.h" #ifdef __unix__ #include "i_system.h" // for SHARE_DIR #endif // __unix__ EXTERN_CVAR(Bool, hud_althud) EXTERN_CVAR(Int, vr_mode) EXTERN_CVAR(Bool, cl_customizeinvulmap) EXTERN_CVAR(Bool, log_vgafont) EXTERN_CVAR(Bool, dlg_vgafont) CVAR(Int, vid_renderer, 1, 0) // for some stupid mods which threw caution out of the window... void DrawHUD(); void D_DoAnonStats(); void I_DetectOS(); void UpdateGenericUI(bool cvar); // MACROS ------------------------------------------------------------------ // TYPES ------------------------------------------------------------------- // EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- extern void I_SetWindowTitle(const char* caption); extern void ReadStatistics(); extern void M_SetDefaultMode (); extern void G_NewInit (); extern void SetupPlayerClasses (); void DeinitMenus(); void CloseNetwork(); void P_Shutdown(); void M_SaveDefaultsFinal(); void R_Shutdown(); void I_ShutdownInput(); void SetConsoleNotifyBuffer(); void I_UpdateDiscordPresence(bool SendPresence, const char* curstatus, const char* appid, const char* steamappid); const FIWADInfo *D_FindIWAD(TArray &wadfiles, const char *iwad, const char *basewad); // PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- bool D_CheckNetGame (); void D_ProcessEvents (); void G_BuildTiccmd (ticcmd_t* cmd); void D_DoAdvanceDemo (); void D_LoadWadSettings (); void ParseGLDefs(); void DrawFullscreenSubtitle(FFont* font, const char *text); void D_Cleanup(); void FreeSBarInfoScript(); void I_UpdateWindowTitle(); void S_ParseMusInfo(); void D_GrabCVarDefaults(); // PRIVATE FUNCTION PROTOTYPES --------------------------------------------- void D_DoomLoop (); // EXTERNAL DATA DECLARATIONS ---------------------------------------------- EXTERN_CVAR (Float, turbo) EXTERN_CVAR (Bool, freelook) EXTERN_CVAR (Float, m_pitch) EXTERN_CVAR (Float, m_yaw) EXTERN_CVAR (Bool, invertmouse) EXTERN_CVAR (Bool, invertmousex) EXTERN_CVAR (Bool, lookstrafe) EXTERN_CVAR (Int, screenblocks) EXTERN_CVAR (Bool, sv_cheats) EXTERN_CVAR (Bool, sv_unlimited_pickup) EXTERN_CVAR (Bool, r_drawplayersprites) EXTERN_CVAR (Bool, show_messages) EXTERN_CVAR(Bool, ticker) extern bool setmodeneeded; extern bool demorecording; bool M_DemoNoPlay; // [RH] if true, then skip any demos in the loop extern bool insave; extern TDeletingArray LightDefaults; extern FName MessageBoxClass; const char* iwad_folders[14] = { "flats/", "textures/", "hires/", "sprites/", "voxels/", "colormaps/", "acs/", "maps/", "voices/", "patches/", "graphics/", "sounds/", "music/", "materials/"}; const char* iwad_reserved[12] = { "mapinfo", "zmapinfo", "gameinfo", "sndinfo", "sbarinfo", "menudef", "gldefs", "animdefs", "decorate", "zscript", "iwadinfo", "maps/" }; CUSTOM_CVAR(Float, i_timescale, 1.0f, CVAR_NOINITCALL | CVAR_VIRTUAL) { if (netgame) { Printf("Time scale cannot be changed in net games.\n"); self = 1.0f; } else if (self >= 0.05f) { I_FreezeTime(true); TimeScale = self; I_FreezeTime(false); } else { Printf("Time scale must be at least 0.05!\n"); } } // PUBLIC DATA DEFINITIONS ------------------------------------------------- CUSTOM_CVAR(Int, vid_rendermode, 4, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOINITCALL) { if (self < 0 || self > 4) { self = 4; } else if (self == 2 || self == 3) { self = self - 2; // softpoly to software } if (usergame) { // [SP] Update pitch limits to the netgame/gamesim. players[consoleplayer].SendPitchLimits(); } screen->SetTextureFilterMode(); // No further checks needed. All this changes now is which scene drawer the render backend calls. } CUSTOM_CVAR (Int, fraglimit, 0, CVAR_SERVERINFO) { // Check for the fraglimit being hit because the fraglimit is being // lowered below somebody's current frag count. if (deathmatch && self > 0) { for (int i = 0; i < MAXPLAYERS; ++i) { if (playeringame[i] && self <= D_GetFragCount(&players[i])) { Printf ("%s\n", GStrings("TXT_FRAGLIMIT")); primaryLevel->ExitLevel (0, false); break; } } } } CVAR (Float, timelimit, 0.f, CVAR_SERVERINFO); CVAR (Int, wipetype, 1, CVAR_ARCHIVE); CVAR (Int, snd_drawoutput, 0, 0); CUSTOM_CVAR (String, vid_cursor, "None", CVAR_ARCHIVE | CVAR_NOINITCALL) { bool res = false; if (!stricmp(self, "None" ) && gameinfo.CursorPic.IsNotEmpty()) { res = I_SetCursor(TexMan.GetGameTextureByName(gameinfo.CursorPic)); } else { res = I_SetCursor(TexMan.GetGameTextureByName(self)); } if (!res) { I_SetCursor(TexMan.GetGameTextureByName("cursor")); } } // Controlled by startup dialog CVAR (Bool, disableautoload, false, CVAR_ARCHIVE | CVAR_NOINITCALL | CVAR_GLOBALCONFIG) CVAR (Bool, autoloadbrightmaps, false, CVAR_ARCHIVE | CVAR_NOINITCALL | CVAR_GLOBALCONFIG) CVAR (Bool, autoloadlights, false, CVAR_ARCHIVE | CVAR_NOINITCALL | CVAR_GLOBALCONFIG) CVAR (Bool, autoloadwidescreen, true, CVAR_ARCHIVE | CVAR_NOINITCALL | CVAR_GLOBALCONFIG) CVAR (Bool, r_debug_disable_vis_filter, false, 0) CVAR(Bool, vid_fps, false, 0) CVAR(Int, vid_showpalette, 0, 0) 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(); } bool hud_toggled = false; bool wantToRestart; bool DrawFSHUD; // [RH] Draw fullscreen HUD? bool devparm; // started game with -devparm const char *D_DrawIcon; // [RH] Patch name of icon to draw on next refresh int NoWipe; // [RH] Allow wipe? (Needs to be set each time) bool singletics = false; // debug flag to cancel adaptiveness FString startmap; bool autostart; bool advancedemo; FILE *debugfile; gamestate_t wipegamestate = GS_DEMOSCREEN; // can be -1 to force a wipe bool PageBlank; FGameTexture *Advisory; FTextureID Page; const char *Subtitle; bool nospriterename; FStartupInfo GameStartupInfo; FString lastIWAD; int restart = 0; bool AppActive = true; bool playedtitlemusic; cycle_t FrameCycles; // [SP] Store the capabilities of the renderer in a global variable, to prevent excessive per-frame processing uint32_t r_renderercaps = 0; // PRIVATE DATA DEFINITIONS ------------------------------------------------ static int demosequence; static int pagetic; // CODE -------------------------------------------------------------------- //========================================================================== // // D_ToggleHud // // Turns off 2D drawing temporarily. // //========================================================================== void D_ToggleHud() { static int saved_screenblocks; static bool saved_drawplayersprite, saved_showmessages; if ((hud_toggled = !hud_toggled)) { saved_screenblocks = screenblocks; saved_drawplayersprite = r_drawplayersprites; saved_showmessages = show_messages; screenblocks = 12; r_drawplayersprites = false; show_messages = false; C_HideConsole(); M_ClearMenus(); } else { screenblocks = saved_screenblocks; r_drawplayersprites = saved_drawplayersprite; show_messages = saved_showmessages; } } CCMD(togglehud) { D_ToggleHud(); } CVAR(Float, m_pitch, 1.f, CVAR_GLOBALCONFIG | CVAR_ARCHIVE) // Mouse speeds CVAR(Float, m_yaw, 1.f, CVAR_GLOBALCONFIG | CVAR_ARCHIVE) //========================================================================== // // Render wrapper. // This function contains all the needed setup and cleanup for starting a render job. // //========================================================================== void D_Render(std::function action, bool interpolate) { for (auto Level : AllLevels()) { // Check for the presence of dynamic lights at the start of the frame once. if ((gl_lights && vid_rendermode == 4) || (r_dynlights && vid_rendermode != 4) || Level->LightProbes.Size() > 0) { Level->HasDynamicLights = Level->lights || Level->LightProbes.Size() > 0; } else Level->HasDynamicLights = false; // lights are off so effectively we have none. if (interpolate) Level->interpolator.DoInterpolations(I_GetTimeFrac()); P_FindParticleSubsectors(Level); PO_LinkToSubsectors(Level); } action(); if (interpolate) for (auto Level : AllLevels()) { Level->interpolator.RestoreInterpolations(); } } //========================================================================== // // CVAR dmflags // //========================================================================== CUSTOM_CVAR (Int, dmflags, 0, CVAR_SERVERINFO | CVAR_NOINITCALL) { // In case DF_NO_FREELOOK was changed, reinitialize the sky // map. (If no freelook, then no need to stretch the sky.) R_InitSkyMap (); if (self & DF_NO_FREELOOK) { Net_WriteByte (DEM_CENTERVIEW); } // If nofov is set, force everybody to the arbitrator's FOV. if ((self & DF_NO_FOV) && consoleplayer == Net_Arbitrator) { float fov; Net_WriteByte (DEM_FOV); // If the game is started with DF_NO_FOV set, the arbitrator's // DesiredFOV will not be set when this callback is run, so // be sure not to transmit a 0 FOV. fov = players[consoleplayer].DesiredFOV; if (fov == 0) { fov = 90; } Net_WriteFloat (fov); } } CVAR (Flag, sv_nohealth, dmflags, DF_NO_HEALTH); CVAR (Flag, sv_noitems, dmflags, DF_NO_ITEMS); CVAR (Flag, sv_weaponstay, dmflags, DF_WEAPONS_STAY); CVAR (Flag, sv_falldamage, dmflags, DF_FORCE_FALLINGHX); CVAR (Flag, sv_oldfalldamage, dmflags, DF_FORCE_FALLINGZD); CVAR (Flag, sv_samelevel, dmflags, DF_SAME_LEVEL); CVAR (Flag, sv_spawnfarthest, dmflags, DF_SPAWN_FARTHEST); CVAR (Flag, sv_forcerespawn, dmflags, DF_FORCE_RESPAWN); CVAR (Flag, sv_noarmor, dmflags, DF_NO_ARMOR); CVAR (Flag, sv_noexit, dmflags, DF_NO_EXIT); CVAR (Flag, sv_infiniteammo, dmflags, DF_INFINITE_AMMO); CVAR (Flag, sv_nomonsters, dmflags, DF_NO_MONSTERS); CVAR (Flag, sv_monsterrespawn, dmflags, DF_MONSTERS_RESPAWN); CVAR (Flag, sv_itemrespawn, dmflags, DF_ITEMS_RESPAWN); CVAR (Flag, sv_fastmonsters, dmflags, DF_FAST_MONSTERS); CVAR (Flag, sv_nojump, dmflags, DF_NO_JUMP); CVAR (Flag, sv_allowjump, dmflags, DF_YES_JUMP); CVAR (Flag, sv_nofreelook, dmflags, DF_NO_FREELOOK); CVAR (Flag, sv_allowfreelook, dmflags, DF_YES_FREELOOK); CVAR (Flag, sv_nofov, dmflags, DF_NO_FOV); CVAR (Flag, sv_noweaponspawn, dmflags, DF_NO_COOP_WEAPON_SPAWN); CVAR (Flag, sv_nocrouch, dmflags, DF_NO_CROUCH); CVAR (Flag, sv_allowcrouch, dmflags, DF_YES_CROUCH); CVAR (Flag, sv_cooploseinventory, dmflags, DF_COOP_LOSE_INVENTORY); CVAR (Flag, sv_cooplosekeys, dmflags, DF_COOP_LOSE_KEYS); CVAR (Flag, sv_cooploseweapons, dmflags, DF_COOP_LOSE_WEAPONS); CVAR (Flag, sv_cooplosearmor, dmflags, DF_COOP_LOSE_ARMOR); CVAR (Flag, sv_cooplosepowerups, dmflags, DF_COOP_LOSE_POWERUPS); CVAR (Flag, sv_cooploseammo, dmflags, DF_COOP_LOSE_AMMO); CVAR (Flag, sv_coophalveammo, dmflags, DF_COOP_HALVE_AMMO); CVAR (Flag, sv_instantreaction, dmflags, DF_INSTANT_REACTION); // Some (hopefully cleaner) interface to these settings. CVAR (Mask, sv_crouch, dmflags, DF_NO_CROUCH|DF_YES_CROUCH); CVAR (Mask, sv_jump, dmflags, DF_NO_JUMP|DF_YES_JUMP); CVAR (Mask, sv_fallingdamage, dmflags, DF_FORCE_FALLINGHX|DF_FORCE_FALLINGZD); CVAR (Mask, sv_freelook, dmflags, DF_NO_FREELOOK|DF_YES_FREELOOK); //========================================================================== // // CVAR dmflags2 // // [RH] From Skull Tag. Some of these were already done as separate cvars // (such as bfgaiming), but I collected them here like Skull Tag does. // //========================================================================== CUSTOM_CVAR (Int, dmflags2, 0, CVAR_SERVERINFO | CVAR_NOINITCALL) { // Stop the automap if we aren't allowed to use it. if ((self & DF2_NO_AUTOMAP) && automapactive) AM_Stop (); for (int i = 0; i < MAXPLAYERS; i++) { player_t *p = &players[i]; if (!playeringame[i]) continue; // Revert our view to our own eyes if spying someone else. if (self & DF2_DISALLOW_SPYING) { // The player isn't looking through its own eyes, so make it. if (p->camera != p->mo) { p->camera = p->mo; S_UpdateSounds (p->camera); StatusBar->AttachToPlayer (p); if (demoplayback || multiplayer) StatusBar->ShowPlayerName (); } } // Come out of chasecam mode if we're not allowed to use chasecam. if (!(dmflags2 & DF2_CHASECAM) && CheckCheatmode(false)) { // Take us out of chasecam mode only. if (p->cheats & CF_CHASECAM) cht_DoCheat (p, CHT_CHASECAM); } } } CVAR (Flag, sv_weapondrop, dmflags2, DF2_YES_WEAPONDROP); CVAR (Flag, sv_noteamswitch, dmflags2, DF2_NO_TEAM_SWITCH); CVAR (Flag, sv_doubleammo, dmflags2, DF2_YES_DOUBLEAMMO); CVAR (Flag, sv_degeneration, dmflags2, DF2_YES_DEGENERATION); CVAR (Flag, sv_nobfgaim, dmflags2, DF2_NO_FREEAIMBFG); CVAR (Flag, sv_barrelrespawn, dmflags2, DF2_BARRELS_RESPAWN); CVAR (Flag, sv_keepfrags, dmflags2, DF2_YES_KEEPFRAGS); CVAR (Flag, sv_norespawn, dmflags2, DF2_NO_RESPAWN); CVAR (Flag, sv_losefrag, dmflags2, DF2_YES_LOSEFRAG); CVAR (Flag, sv_respawnprotect, dmflags2, DF2_YES_RESPAWN_INVUL); CVAR (Flag, sv_samespawnspot, dmflags2, DF2_SAME_SPAWN_SPOT); CVAR (Flag, sv_infiniteinventory, dmflags2, DF2_INFINITE_INVENTORY); CVAR (Flag, sv_killallmonsters, dmflags2, DF2_KILL_MONSTERS); CVAR (Flag, sv_noautomap, dmflags2, DF2_NO_AUTOMAP); CVAR (Flag, sv_noautomapallies, dmflags2, DF2_NO_AUTOMAP_ALLIES); CVAR (Flag, sv_disallowspying, dmflags2, DF2_DISALLOW_SPYING); CVAR (Flag, sv_chasecam, dmflags2, DF2_CHASECAM); CVAR (Flag, sv_disallowsuicide, dmflags2, DF2_NOSUICIDE); CVAR (Flag, sv_noautoaim, dmflags2, DF2_NOAUTOAIM); CVAR (Flag, sv_dontcheckammo, dmflags2, DF2_DONTCHECKAMMO); CVAR (Flag, sv_killbossmonst, dmflags2, DF2_KILLBOSSMONST); CVAR (Flag, sv_nocountendmonst, dmflags2, DF2_NOCOUNTENDMONST); CVAR (Flag, sv_respawnsuper, dmflags2, DF2_RESPAWN_SUPER); CVAR (Flag, sv_nothingspawn, dmflags2, DF2_NO_COOP_THING_SPAWN); CVAR (Flag, sv_alwaysspawnmulti, dmflags2, DF2_ALWAYS_SPAWN_MULTI); CVAR (Flag, sv_novertspread, dmflags2, DF2_NOVERTSPREAD); //========================================================================== // // CVAR compatflags // //========================================================================== EXTERN_CVAR(Int, compatmode) CUSTOM_CVAR (Int, compatflags, 0, CVAR_ARCHIVE|CVAR_SERVERINFO | CVAR_NOINITCALL) { if (FBaseCVar::m_inEnable) return; for (auto Level : AllLevels()) { Level->ApplyCompatibility(); } } CUSTOM_CVAR (Int, compatflags2, 0, CVAR_ARCHIVE|CVAR_SERVERINFO | CVAR_NOINITCALL) { if (FBaseCVar::m_inEnable) return; for (auto Level : AllLevels()) { Level->ApplyCompatibility2(); Level->SetCompatLineOnSide(true); } } CUSTOM_CVAR(Int, compatmode, 0, CVAR_ARCHIVE) { int v, w = 0; switch (self) { default: case 0: v = 0; break; case 1: // Doom2.exe compatible with a few relaxed settings v = COMPATF_SHORTTEX | COMPATF_STAIRINDEX | COMPATF_USEBLOCKING | COMPATF_NODOORLIGHT | COMPATF_SPRITESORT | COMPATF_TRACE | COMPATF_MISSILECLIP | COMPATF_SOUNDTARGET | COMPATF_DEHHEALTH | COMPATF_CROSSDROPOFF | COMPATF_LIGHT | COMPATF_MASKEDMIDTEX; w = COMPATF2_FLOORMOVE | COMPATF2_EXPLODE1 | COMPATF2_NOMBF21; break; case 2: // same as 1 but stricter (NO_PASSMOBJ and INVISIBILITY are also set) v = COMPATF_SHORTTEX | COMPATF_STAIRINDEX | COMPATF_USEBLOCKING | COMPATF_NODOORLIGHT | COMPATF_SPRITESORT | COMPATF_TRACE | COMPATF_MISSILECLIP | COMPATF_SOUNDTARGET | COMPATF_NO_PASSMOBJ | COMPATF_LIMITPAIN | COMPATF_DEHHEALTH | COMPATF_INVISIBILITY | COMPATF_CROSSDROPOFF | COMPATF_VILEGHOSTS | COMPATF_HITSCAN | COMPATF_WALLRUN | COMPATF_NOTOSSDROPS | COMPATF_LIGHT | COMPATF_MASKEDMIDTEX; w = COMPATF2_BADANGLES | COMPATF2_FLOORMOVE | COMPATF2_POINTONLINE | COMPATF2_EXPLODE2 | COMPATF2_NOMBF21; break; case 3: // Boom compat mode v = COMPATF_TRACE|COMPATF_SOUNDTARGET|COMPATF_BOOMSCROLL|COMPATF_MISSILECLIP|COMPATF_MASKEDMIDTEX; w = COMPATF2_EXPLODE1 | COMPATF2_NOMBF21; break; case 4: // Old ZDoom compat mode v = COMPATF_SOUNDTARGET | COMPATF_LIGHT; w = COMPATF2_MULTIEXIT | COMPATF2_TELEPORT | COMPATF2_PUSHWINDOW | COMPATF2_CHECKSWITCHRANGE | COMPATF2_NOMBF21; break; case 5: // MBF compat mode v = COMPATF_TRACE | COMPATF_SOUNDTARGET | COMPATF_BOOMSCROLL | COMPATF_MISSILECLIP | COMPATF_MUSHROOM | COMPATF_MBFMONSTERMOVE | COMPATF_NOBLOCKFRIENDS | COMPATF_MASKEDMIDTEX; w = COMPATF2_EXPLODE1 | COMPATF2_AVOID_HAZARDS | COMPATF2_STAYONLIFT | COMPATF2_NOMBF21; break; case 6: // Boom with some added settings to reenable some 'broken' behavior v = COMPATF_TRACE | COMPATF_SOUNDTARGET | COMPATF_BOOMSCROLL | COMPATF_MISSILECLIP | COMPATF_NO_PASSMOBJ | COMPATF_INVISIBILITY | COMPATF_HITSCAN | COMPATF_WALLRUN | COMPATF_NOTOSSDROPS | COMPATF_MASKEDMIDTEX; w = COMPATF2_POINTONLINE | COMPATF2_EXPLODE2 | COMPATF2_NOMBF21; break; case 7: // Stricter MBF compatibility v = COMPATF_NOBLOCKFRIENDS | COMPATF_MBFMONSTERMOVE | COMPATF_INVISIBILITY | COMPATF_NOTOSSDROPS | COMPATF_MUSHROOM | COMPATF_NO_PASSMOBJ | COMPATF_BOOMSCROLL | COMPATF_WALLRUN | COMPATF_TRACE | COMPATF_HITSCAN | COMPATF_MISSILECLIP | COMPATF_MASKEDMIDTEX | COMPATF_SOUNDTARGET; w = COMPATF2_POINTONLINE | COMPATF2_EXPLODE1 | COMPATF2_EXPLODE2 | COMPATF2_AVOID_HAZARDS | COMPATF2_STAYONLIFT | COMPATF2_NOMBF21; break; } compatflags = v; compatflags2 = w; } CVAR (Flag, compat_shortTex, compatflags, COMPATF_SHORTTEX); CVAR (Flag, compat_stairs, compatflags, COMPATF_STAIRINDEX); CVAR (Flag, compat_limitpain, compatflags, COMPATF_LIMITPAIN); CVAR (Flag, compat_silentpickup, compatflags, COMPATF_SILENTPICKUP); CVAR (Flag, compat_nopassover, compatflags, COMPATF_NO_PASSMOBJ); CVAR (Flag, compat_soundslots, compatflags, COMPATF_MAGICSILENCE); CVAR (Flag, compat_wallrun, compatflags, COMPATF_WALLRUN); CVAR (Flag, compat_notossdrops, compatflags, COMPATF_NOTOSSDROPS); CVAR (Flag, compat_useblocking, compatflags, COMPATF_USEBLOCKING); CVAR (Flag, compat_nodoorlight, compatflags, COMPATF_NODOORLIGHT); CVAR (Flag, compat_ravenscroll, compatflags, COMPATF_RAVENSCROLL); CVAR (Flag, compat_soundtarget, compatflags, COMPATF_SOUNDTARGET); CVAR (Flag, compat_dehhealth, compatflags, COMPATF_DEHHEALTH); CVAR (Flag, compat_trace, compatflags, COMPATF_TRACE); CVAR (Flag, compat_dropoff, compatflags, COMPATF_DROPOFF); CVAR (Flag, compat_boomscroll, compatflags, COMPATF_BOOMSCROLL); CVAR (Flag, compat_invisibility, compatflags, COMPATF_INVISIBILITY); CVAR (Flag, compat_silentinstantfloors, compatflags, COMPATF_SILENT_INSTANT_FLOORS); CVAR (Flag, compat_sectorsounds, compatflags, COMPATF_SECTORSOUNDS); CVAR (Flag, compat_missileclip, compatflags, COMPATF_MISSILECLIP); CVAR (Flag, compat_crossdropoff, compatflags, COMPATF_CROSSDROPOFF); CVAR (Flag, compat_anybossdeath, compatflags, COMPATF_ANYBOSSDEATH); CVAR (Flag, compat_minotaur, compatflags, COMPATF_MINOTAUR); CVAR (Flag, compat_mushroom, compatflags, COMPATF_MUSHROOM); CVAR (Flag, compat_mbfmonstermove, compatflags, COMPATF_MBFMONSTERMOVE); CVAR (Flag, compat_vileghosts, compatflags, COMPATF_VILEGHOSTS); CVAR (Flag, compat_noblockfriends, compatflags, COMPATF_NOBLOCKFRIENDS); CVAR (Flag, compat_spritesort, compatflags, COMPATF_SPRITESORT); CVAR (Flag, compat_hitscan, compatflags, COMPATF_HITSCAN); CVAR (Flag, compat_light, compatflags, COMPATF_LIGHT); CVAR (Flag, compat_polyobj, compatflags, COMPATF_POLYOBJ); CVAR (Flag, compat_maskedmidtex, compatflags, COMPATF_MASKEDMIDTEX); CVAR (Flag, compat_badangles, compatflags2, COMPATF2_BADANGLES); CVAR (Flag, compat_floormove, compatflags2, COMPATF2_FLOORMOVE); CVAR (Flag, compat_soundcutoff, compatflags2, COMPATF2_SOUNDCUTOFF); CVAR (Flag, compat_pointonline, compatflags2, COMPATF2_POINTONLINE); CVAR (Flag, compat_multiexit, compatflags2, COMPATF2_MULTIEXIT); CVAR (Flag, compat_teleport, compatflags2, COMPATF2_TELEPORT); CVAR (Flag, compat_pushwindow, compatflags2, COMPATF2_PUSHWINDOW); CVAR (Flag, compat_checkswitchrange, compatflags2, COMPATF2_CHECKSWITCHRANGE); CVAR (Flag, compat_explode1, compatflags2, COMPATF2_EXPLODE1); CVAR (Flag, compat_explode2, compatflags2, COMPATF2_EXPLODE2); CVAR (Flag, compat_railing, compatflags2, COMPATF2_RAILING); CVAR (Flag, compat_avoidhazard, compatflags2, COMPATF2_AVOID_HAZARDS); CVAR (Flag, compat_stayonlift, compatflags2, COMPATF2_STAYONLIFT); CVAR (Flag, compat_nombf21, compatflags2, COMPATF2_NOMBF21); CVAR(Bool, vid_activeinbackground, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) EXTERN_CVAR(Bool, r_drawvoxels) EXTERN_CVAR(Int, gl_tonemap) static uint32_t GetCaps() { ActorRenderFeatureFlags FlagSet; if (!V_IsHardwareRenderer()) { FlagSet = RFF_UNCLIPPEDTEX; if (V_IsTrueColor()) FlagSet |= RFF_TRUECOLOR; else FlagSet |= RFF_COLORMAP; } else { // describe our basic feature set FlagSet = RFF_FLATSPRITES | RFF_MODELS | RFF_SLOPE3DFLOORS | RFF_TILTPITCH | RFF_ROLLSPRITES | RFF_POLYGONAL | RFF_MATSHADER | RFF_POSTSHADER | RFF_BRIGHTMAP; if (gl_tonemap != 5) // not running palette tonemap shader FlagSet |= RFF_TRUECOLOR; } if (r_drawvoxels) FlagSet |= RFF_VOXELS; return (uint32_t)FlagSet; } //========================================================================== // // // //========================================================================== static void DrawPaletteTester(int paletteno) { int blocksize = screen->GetHeight() / 50; int t = paletteno; int k = 0; for (int i = 0; i < 16; ++i) { for (int j = 0; j < 16; ++j) { PalEntry pe; if (t > 1) { auto palette = GPalette.GetTranslation(TRANSLATION_Standard, t - 2)->Palette; pe = palette[k]; } else GPalette.BaseColors[k]; k++; Dim(twod, pe, 1.f, j * blocksize, i * blocksize, blocksize, blocksize); } } } //========================================================================== // // DFrameBuffer :: DrawRateStuff // // Draws the fps counter, dot ticker, and palette debug. // //========================================================================== uint64_t LastCount; static void DrawRateStuff() { static uint64_t LastMS = 0, LastSec = 0, FrameCount = 0, LastTic = 0; // Draws frame time and cumulative fps if (vid_fps) { uint64_t ms = screen->FrameTime; uint64_t howlong = ms - LastMS; if ((signed)howlong >= 0) { char fpsbuff[40]; int chars; int rate_x; int textScale = active_con_scale(twod); chars = mysnprintf(fpsbuff, countof(fpsbuff), "%2llu ms (%3llu fps)", (unsigned long long)howlong, (unsigned long long)LastCount); rate_x = screen->GetWidth() / textScale - NewConsoleFont->StringWidth(&fpsbuff[0]); ClearRect(twod, rate_x * textScale, 0, screen->GetWidth(), NewConsoleFont->GetHeight() * textScale, GPalette.BlackIndex, 0); DrawText(twod, NewConsoleFont, CR_WHITE, rate_x, 0, (char*)&fpsbuff[0], DTA_VirtualWidth, screen->GetWidth() / textScale, DTA_VirtualHeight, screen->GetHeight() / textScale, DTA_KeepRatio, true, TAG_DONE); uint32_t thisSec = (uint32_t)(ms / 1000); if (LastSec < thisSec) { LastCount = FrameCount / (thisSec - LastSec); LastSec = thisSec; FrameCount = 0; } FrameCount++; } LastMS = ms; } int Height = screen->GetHeight(); // draws little dots on the bottom of the screen if (ticker) { int64_t t = I_GetTime(); int64_t tics = t - LastTic; LastTic = t; if (tics > 20) tics = 20; int i; for (i = 0; i < tics * 2; i += 2) ClearRect(twod, i, Height - 1, i + 1, Height, 255, 0); for (; i < 20 * 2; i += 2) ClearRect(twod, i, Height - 1, i + 1, Height, 0, 0); } // draws the palette for debugging if (vid_showpalette) { DrawPaletteTester(vid_showpalette); } } static void DrawOverlays() { NetUpdate (); C_DrawConsole (); M_Drawer (); DrawRateStuff(); if (!hud_toggled) FStat::PrintStat (twod); } static void End2DAndUpdate() { twod->End(); CheckBench(); screen->Update(); twod->OnFrameDone(); } //========================================================================== // // D_Display // // Draw current display, possibly wiping it from the previous // //========================================================================== void D_Display () { FTexture *wipestart = nullptr; int wipe_type; sector_t *viewsec; if (nodrawers || screen == NULL) return; // for comparative timing / profiling if (!AppActive && (screen->IsFullscreen() || !vid_activeinbackground)) { return; } cycle_t cycles; cycles.Reset(); cycles.Clock(); r_UseVanillaTransparency = UseVanillaTransparency(); // [SP] Cache UseVanillaTransparency() call r_renderercaps = GetCaps(); // [SP] Get the current capabilities of the renderer if (players[consoleplayer].camera == NULL) { players[consoleplayer].camera = players[consoleplayer].mo; } auto &vp = r_viewpoint; if (viewactive) { DAngle fov = 90.f; AActor *cam = players[consoleplayer].camera; if (cam) { if (cam->player) fov = cam->player->FOV; else fov = cam->CameraFOV; } R_SetFOV(vp, fov); } // fullscreen toggle has been requested if (setmodeneeded) { setmodeneeded = false; screen->ToggleFullscreen(vid_fullscreen); V_OutputResized(screen->GetWidth(), screen->GetHeight()); } // change the view size if needed if (setsizeneeded) { if (StatusBar == nullptr) { viewwidth = SCREENWIDTH; viewheight = SCREENHEIGHT; setsizeneeded = false; } else { R_ExecuteSetViewSize (vp, r_viewwindow); } } // [RH] Allow temporarily disabling wipes if (NoWipe) { NoWipe--; wipestart = nullptr; wipegamestate = gamestate; } // No wipes when in a stereo3D VR mode else if (gamestate != wipegamestate && gamestate != GS_FULLCONSOLE && gamestate != GS_TITLELEVEL) { if (vr_mode == 0 || vid_rendermode != 4) { // save the current screen if about to wipe wipestart = screen->WipeStartScreen(); switch (wipegamestate) { default: wipe_type = wipetype; break; case GS_FORCEWIPEFADE: wipe_type = wipe_Fade; break; case GS_FORCEWIPEBURN: wipe_type = wipe_Burn; break; case GS_FORCEWIPEMELT: wipe_type = wipe_Melt; break; } } wipegamestate = gamestate; } else { wipestart = nullptr; } screen->FrameTime = I_msTimeFS(); TexAnim.UpdateAnimations(screen->FrameTime); R_UpdateSky(screen->FrameTime); screen->BeginFrame(); twod->ClearClipRect(); if ((gamestate == GS_LEVEL || gamestate == GS_TITLELEVEL) && gametic != 0) { // [ZZ] execute event hook that we just started the frame //E_RenderFrame(); // D_Render([&]() { viewsec = RenderView(&players[consoleplayer]); }, true); twod->Begin(screen->GetWidth(), screen->GetHeight()); if (!hud_toggled) { V_DrawBlend(viewsec); if (automapactive) { primaryLevel->automap->Drawer ((hud_althud && viewheight == SCREENHEIGHT) ? viewheight : StatusBar->GetTopOfStatusbar()); } // for timing the statusbar code. //cycle_t stb; //stb.Reset(); //stb.Clock(); if (!automapactive || viewactive) { StatusBar->RefreshViewBorder (); } if (hud_althud && viewheight == SCREENHEIGHT && screenblocks > 10) { StatusBar->DrawBottomStuff (HUD_AltHud); if (DrawFSHUD || automapactive) StatusBar->DrawAltHUD(); if (players[consoleplayer].camera && players[consoleplayer].camera->player && !automapactive) { StatusBar->DrawCrosshair(); } StatusBar->CallDraw (HUD_AltHud, vp.TicFrac); StatusBar->DrawTopStuff (HUD_AltHud); } else if (viewheight == SCREENHEIGHT && viewactive && screenblocks > 10) { EHudState state = DrawFSHUD ? HUD_Fullscreen : HUD_None; StatusBar->DrawBottomStuff (state); StatusBar->CallDraw (state, vp.TicFrac); StatusBar->DrawTopStuff (state); } else { StatusBar->DrawBottomStuff (HUD_StatusBar); StatusBar->CallDraw (HUD_StatusBar, vp.TicFrac); StatusBar->DrawTopStuff (HUD_StatusBar); } //stb.Unclock(); //Printf("Stbar = %f\n", stb.TimeMS()); } } else { twod->Begin(screen->GetWidth(), screen->GetHeight()); switch (gamestate) { case GS_FULLCONSOLE: D_PageDrawer(); C_DrawConsole (); M_Drawer (); End2DAndUpdate (); return; case GS_DEMOSCREEN: D_PageDrawer (); break; case GS_CUTSCENE: case GS_INTRO: ScreenJobDraw(); break; default: break; } } if (!hud_toggled) { CT_Drawer (); // draw pause pic if ((paused || pauseext) && menuactive == MENU_Off && StatusBar != nullptr) { // [MK] optionally let the status bar handle this bool skip = false; IFVIRTUALPTR(StatusBar, DBaseStatusBar, DrawPaused) { VMValue params[] { (DObject*)StatusBar, paused-1 }; int rv; VMReturn ret(&rv); VMCall(func, params, countof(params), &ret, 1); skip = !!rv; } if ( !skip ) { auto tex = TexMan.GetGameTextureByName(gameinfo.PauseSign, true); double x = (SCREENWIDTH - tex->GetDisplayWidth() * CleanXfac)/2 + tex->GetDisplayLeftOffset() * CleanXfac; DrawTexture(twod, tex, x, 4, DTA_CleanNoMove, true, TAG_DONE); if (paused && multiplayer) { FFont *font = generic_ui? NewSmallFont : SmallFont; FString pstring = GStrings("TXT_BY"); pstring.Substitute("%s", players[paused - 1].userinfo.GetName()); DrawText(twod, font, CR_RED, (twod->GetWidth() - font->StringWidth(pstring)*CleanXfac) / 2, (tex->GetDisplayHeight() * CleanYfac) + 4, pstring, DTA_CleanNoMove, true, TAG_DONE); } } } // [RH] Draw icon, if any if (D_DrawIcon) { FTextureID picnum = TexMan.CheckForTexture (D_DrawIcon, ETextureType::MiscPatch); D_DrawIcon = NULL; if (picnum.isValid()) { auto tex = TexMan.GetGameTexture(picnum); DrawTexture(twod, tex, 160 - tex->GetDisplayWidth()/2, 100 - tex->GetDisplayHeight()/2, DTA_320x200, true, TAG_DONE); } NoWipe = 10; } if (snd_drawoutput) { GSnd->DrawWaveDebug(snd_drawoutput); } } if (!wipestart || NoWipe < 0 || wipe_type == wipe_None || hud_toggled) { if (wipestart != nullptr) wipestart->DecRef(); wipestart = nullptr; DrawOverlays(); End2DAndUpdate (); } else { NetUpdate(); // send out any new accumulation PerformWipe(wipestart, screen->WipeEndScreen(), wipe_type, false, DrawOverlays); } cycles.Unclock(); FrameCycles = cycles; } //========================================================================== // // D_ErrorCleanup () // // Cleanup after a recoverable error or a restart //========================================================================== void D_ErrorCleanup () { savegamerestore = false; primaryLevel->BotInfo.RemoveAllBots (primaryLevel, true); D_QuitNetGame (); if (demorecording || demoplayback) G_CheckDemoStatus (); Net_ClearBuffers (); G_NewInit (); M_ClearMenus (); singletics = false; playeringame[0] = 1; players[0].playerstate = PST_LIVE; gameaction = ga_fullconsole; if (gamestate == GS_DEMOSCREEN) { menuactive = MENU_Off; } insave = false; ClearGlobalVMStack(); } //========================================================================== // // D_DoomLoop // // Manages timing and IO, calls all ?_Responder, ?_Ticker, and ?_Drawer, // calls I_GetTime, I_StartFrame, and I_StartTic // //========================================================================== void D_DoomLoop () { int lasttic = 0; // Clamp the timer to TICRATE until the playloop has been entered. r_NoInterpolate = true; Page.SetInvalid(); Subtitle = nullptr; Advisory = nullptr; vid_cursor.Callback(); for (;;) { try { // frame syncronous IO operations if (gametic > lasttic) { lasttic = gametic; I_StartFrame (); } I_SetFrameTime(); // process one or more tics if (singletics) { I_StartTic (); D_ProcessEvents (); G_BuildTiccmd (&netcmds[consoleplayer][maketic%BACKUPTICS]); if (advancedemo) D_DoAdvanceDemo (); C_Ticker (); M_Ticker (); G_Ticker (); // [RH] Use the consoleplayer's camera to update sounds S_UpdateSounds (players[consoleplayer].camera); // move positional sounds gametic++; maketic++; GC::CheckGC (); Net_NewMakeTic (); } else { TryRunTics (); // will run at least one tic } // Update display, next frame, with current state. I_StartTic (); D_Display (); S_UpdateMusic(); if (wantToRestart) { wantToRestart = false; return; } } catch (CRecoverableError &error) { if (error.GetMessage ()) { Printf (PRINT_BOLD, "\n%s\n", error.GetMessage()); } D_ErrorCleanup (); } catch (CVMAbortException &error) { error.MaybePrintMessage(); Printf("%s", error.stacktrace.GetChars()); D_ErrorCleanup(); } } } //========================================================================== // // D_PageTicker // //========================================================================== void D_PageTicker (void) { if (--pagetic < 0) D_AdvanceDemo (); } //========================================================================== // // D_PageDrawer // //========================================================================== void D_PageDrawer (void) { ClearRect(twod, 0, 0, SCREENWIDTH, SCREENHEIGHT, 0, 0); if (Page.Exists()) { DrawTexture(twod, TexMan.GetGameTexture(Page, true), 0, 0, DTA_Fullscreen, true, DTA_Masked, false, DTA_BilinearFilter, true, TAG_DONE); } if (Subtitle != nullptr) { FFont* font = generic_ui ? NewSmallFont : SmallFont; DrawFullscreenSubtitle(font, GStrings[Subtitle]); } if (Advisory != nullptr) { DrawTexture(twod, Advisory, 4, 160, DTA_320x200, true, TAG_DONE); } } //========================================================================== // // D_AdvanceDemo // // Called after each demo or intro demosequence finishes // //========================================================================== void D_AdvanceDemo (void) { advancedemo = true; } //========================================================================== // // D_DoStrifeAdvanceDemo // //========================================================================== void D_DoStrifeAdvanceDemo () { static const char *const fullVoices[6] = { "svox/pro1", "svox/pro2", "svox/pro3", "svox/pro4", "svox/pro5", "svox/pro6" }; static const char *const teaserVoices[6] = { "svox/voc91", "svox/voc92", "svox/voc93", "svox/voc94", "svox/voc95", "svox/voc96" }; const char *const *voices = gameinfo.flags & GI_SHAREWARE ? teaserVoices : fullVoices; const char *pagename = nullptr; const char *subtitle = nullptr; gamestate = GS_DEMOSCREEN; PageBlank = false; switch (demosequence) { default: case 0: pagetic = 6 * TICRATE; pagename = "TITLEPIC"; if (fileSystem.CheckNumForName ("d_logo", ns_music) < 0) { // strife0.wad does not have d_logo S_StartMusic (""); } else { S_StartMusic ("d_logo"); } C_HideConsole (); break; case 1: // [RH] Strife fades to black and then to the Rogue logo, but // I think it looks better if it doesn't fade. pagetic = 10 * TICRATE/35; pagename = ""; // PANEL0, but strife0.wad doesn't have it, so don't use it. PageBlank = true; S_Sound (CHAN_VOICE, CHANF_UI, "bishop/active", 1, ATTN_NORM); break; case 2: pagetic = 4 * TICRATE; pagename = "RGELOGO"; break; case 3: pagetic = 7 * TICRATE; pagename = "PANEL1"; subtitle = "$TXT_SUB_INTRO1"; S_Sound (CHAN_VOICE, CHANF_UI, voices[0], 1, ATTN_NORM); // The new Strife teaser has D_FMINTR. // The full retail Strife has D_INTRO. // And the old Strife teaser has both. (I do not know which one it actually uses, nor do I care.) S_StartMusic (gameinfo.flags & GI_TEASER2 ? "d_fmintr" : "d_intro"); break; case 4: pagetic = 9 * TICRATE; pagename = "PANEL2"; subtitle = "$TXT_SUB_INTRO2"; S_Sound (CHAN_VOICE, CHANF_UI, voices[1], 1, ATTN_NORM); break; case 5: pagetic = 12 * TICRATE; pagename = "PANEL3"; subtitle = "$TXT_SUB_INTRO3"; S_Sound (CHAN_VOICE, CHANF_UI, voices[2], 1, ATTN_NORM); break; case 6: pagetic = 11 * TICRATE; pagename = "PANEL4"; subtitle = "$TXT_SUB_INTRO4"; S_Sound (CHAN_VOICE, CHANF_UI, voices[3], 1, ATTN_NORM); break; case 7: pagetic = 10 * TICRATE; pagename = "PANEL5"; subtitle = "$TXT_SUB_INTRO5"; S_Sound (CHAN_VOICE, CHANF_UI, voices[4], 1, ATTN_NORM); break; case 8: pagetic = 16 * TICRATE; pagename = "PANEL6"; subtitle = "$TXT_SUB_INTRO6"; S_Sound (CHAN_VOICE, CHANF_UI, voices[5], 1, ATTN_NORM); break; case 9: pagetic = 6 * TICRATE; pagename = "vellogo"; wipegamestate = GS_FORCEWIPEFADE; break; case 10: pagetic = 12 * TICRATE; pagename = "CREDIT"; wipegamestate = GS_FORCEWIPEFADE; break; } if (demosequence++ > 10) demosequence = 0; if (demosequence == 9 && !(gameinfo.flags & GI_SHAREWARE)) demosequence = 10; if (pagename != nullptr) { Page = TexMan.CheckForTexture(pagename, ETextureType::MiscPatch); Subtitle = subtitle; } else { Subtitle = nullptr; } } //========================================================================== // // D_DoAdvanceDemo // //========================================================================== void D_DoAdvanceDemo (void) { static char demoname[8] = "DEMO1"; static int democount = 0; static int pagecount; FString pagename; advancedemo = false; if (gameaction != ga_nothing) { return; } players[consoleplayer].playerstate = PST_LIVE; // not reborn usergame = false; // no save / end game here paused = 0; // [RH] If you want something more dynamic for your title, create a map // and name it TITLEMAP. That map will be loaded and used as the title. if (P_CheckMapData("TITLEMAP")) { G_InitNew ("TITLEMAP", true); return; } if (gameinfo.gametype == GAME_Strife) { D_DoStrifeAdvanceDemo (); return; } switch (demosequence) { case 3: if (gameinfo.advisoryTime) { Advisory = TexMan.GetGameTextureByName("ADVISOR"); demosequence = 1; pagetic = (int)(gameinfo.advisoryTime * TICRATE); break; } // fall through to case 1 if no advisory notice [[fallthrough]]; case 1: Advisory = NULL; if (!M_DemoNoPlay) { democount++; mysnprintf (demoname + 4, countof(demoname) - 4, "%d", democount); if (fileSystem.CheckNumForName (demoname) < 0) { demosequence = 0; democount = 0; // falls through to case 0 below } else { singledemo = false; G_DeferedPlayDemo (demoname); demosequence = 2; break; } } [[fallthrough]]; default: case 0: gamestate = GS_DEMOSCREEN; pagename = gameinfo.TitlePage; pagetic = (int)(gameinfo.titleTime * TICRATE); if (!playedtitlemusic) S_ChangeMusic (gameinfo.titleMusic, gameinfo.titleOrder, false); playedtitlemusic = true; demosequence = 3; pagecount = 0; C_HideConsole (); break; case 2: pagetic = (int)(gameinfo.pageTime * TICRATE); gamestate = GS_DEMOSCREEN; if (gameinfo.creditPages.Size() > 0) { pagename = gameinfo.creditPages[pagecount].GetChars(); pagecount = (pagecount+1) % gameinfo.creditPages.Size(); } demosequence = 1; break; } if (pagename.IsNotEmpty()) { Page = TexMan.CheckForTexture(pagename, ETextureType::MiscPatch); } } //========================================================================== // // D_StartTitle // //========================================================================== void D_StartTitle (void) { playedtitlemusic = false; gameaction = ga_nothing; demosequence = -1; D_AdvanceDemo (); } //========================================================================== // // Cmd_Endgame // // [RH] Quit the current game and go to fullscreen console // //========================================================================== CCMD (endgame) { if (!netgame) { gameaction = ga_fullconsole; demosequence = -1; G_CheckDemoStatus(); } } //========================================================================== // // ParseCVarInfo // //========================================================================== void ParseCVarInfo() { int lump, lastlump = 0; bool addedcvars = false; while ((lump = fileSystem.FindLump("CVARINFO", &lastlump)) != -1) { FScanner sc(lump); sc.SetCMode(true); while (sc.GetToken()) { FString cvarname; char *cvardefault = NULL; ECVarType cvartype = CVAR_Dummy; int cvarflags = CVAR_MOD|CVAR_ARCHIVE; FBaseCVar *cvar; // Check for flag tokens. while (sc.TokenType == TK_Identifier) { if (stricmp(sc.String, "server") == 0) { cvarflags |= CVAR_SERVERINFO; } else if (stricmp(sc.String, "user") == 0) { cvarflags |= CVAR_USERINFO; } else if (stricmp(sc.String, "noarchive") == 0) { cvarflags &= ~CVAR_ARCHIVE; } else if (stricmp(sc.String, "cheat") == 0) { cvarflags |= CVAR_CHEAT; } else if (stricmp(sc.String, "latch") == 0) { cvarflags |= CVAR_LATCH; } else if (stricmp(sc.String, "nosave") == 0) { cvarflags |= CVAR_CONFIG_ONLY; } else { sc.ScriptError("Unknown cvar attribute '%s'", sc.String); } sc.MustGetAnyToken(); } // Possibility of defining a cvar as 'server nosave' or 'user nosave' is kept for // compatibility reasons. if (cvarflags & CVAR_CONFIG_ONLY) { cvarflags &= ~CVAR_SERVERINFO; cvarflags &= ~CVAR_USERINFO; } // Do some sanity checks. // No need to check server-nosave and user-nosave combinations because they // are made impossible right above. if ((cvarflags & (CVAR_SERVERINFO|CVAR_USERINFO|CVAR_CONFIG_ONLY)) == 0 || (cvarflags & (CVAR_SERVERINFO|CVAR_USERINFO)) == (CVAR_SERVERINFO|CVAR_USERINFO)) { sc.ScriptError("One of 'server', 'user', or 'nosave' must be specified"); } // The next token must be the cvar type. if (sc.TokenType == TK_Bool) { cvartype = CVAR_Bool; } else if (sc.TokenType == TK_Int) { cvartype = CVAR_Int; } else if (sc.TokenType == TK_Float) { cvartype = CVAR_Float; } else if (sc.TokenType == TK_Color) { cvartype = CVAR_Color; } else if (sc.TokenType == TK_String) { cvartype = CVAR_String; } else { sc.ScriptError("Bad cvar type '%s'", sc.String); } // The next token must be the cvar name. sc.MustGetToken(TK_Identifier); if (FindCVar(sc.String, NULL) != NULL) { sc.ScriptError("cvar '%s' already exists", sc.String); } cvarname = sc.String; // A default value is optional and signalled by a '=' token. if (sc.CheckToken('=')) { switch (cvartype) { case CVAR_Bool: if (!sc.CheckToken(TK_True) && !sc.CheckToken(TK_False)) { sc.ScriptError("Expected true or false"); } cvardefault = sc.String; break; case CVAR_Int: sc.MustGetNumber(); cvardefault = sc.String; break; case CVAR_Float: sc.MustGetFloat(); cvardefault = sc.String; break; default: sc.MustGetString(); cvardefault = sc.String; break; } } // Now create the cvar. cvar = C_CreateCVar(cvarname, cvartype, cvarflags); if (cvardefault != NULL) { UCVarValue val; val.String = cvardefault; cvar->SetGenericRepDefault(val, CVAR_String); } // To be like C and ACS, require a semicolon after everything. sc.MustGetToken(';'); addedcvars = true; } } // Only load mod cvars from the config if we defined some, so we don't // clutter up the cvar space when not playing mods with custom cvars. if (addedcvars) { GameConfig->DoModSetup (gameinfo.ConfigName); } } //========================================================================== // // ConsiderPatches // // Tries to add any deh/bex patches from the command line. // //========================================================================== bool ConsiderPatches (const char *arg) { int i, argc; FString *args; const char *f; argc = Args->CheckParmList(arg, &args); for (i = 0; i < argc; ++i) { if ( (f = BaseFileSearch(args[i], ".deh", false, GameConfig)) || (f = BaseFileSearch(args[i], ".bex", false, GameConfig)) ) { D_LoadDehFile(f); } } return argc > 0; } //========================================================================== // // D_MultiExec // //========================================================================== FExecList *D_MultiExec (FArgs *list, FExecList *exec) { for (int i = 0; i < list->NumArgs(); ++i) { exec = C_ParseExecFile(list->GetArg(i), exec); } return exec; } static void GetCmdLineFiles(TArray &wadfiles) { FString *args; int i, argc; argc = Args->CheckParmList("-file", &args); for (i = 0; i < argc; ++i) { D_AddWildFile(wadfiles, args[i], ".wad", GameConfig); } } static FString ParseGameInfo(TArray &pwads, const char *fn, const char *data, int size) { FScanner sc; FString iwad; int pos = 0; const char *lastSlash = strrchr (fn, '/'); sc.OpenMem("GAMEINFO", data, size); while(sc.GetToken()) { sc.TokenMustBe(TK_Identifier); FString nextKey = sc.String; sc.MustGetToken('='); if (!nextKey.CompareNoCase("IWAD")) { sc.MustGetString(); iwad = sc.String; } else if (!nextKey.CompareNoCase("LOAD")) { do { sc.MustGetString(); // Try looking for the wad in the same directory as the .wad // before looking for it in the current directory. FString checkpath; if (lastSlash != NULL) { checkpath = FString(fn, (lastSlash - fn) + 1); checkpath += sc.String; } else { checkpath = sc.String; } if (!FileExists(checkpath)) { pos += D_AddFile(pwads, sc.String, true, pos, GameConfig); } else { pos += D_AddFile(pwads, checkpath, true, pos, GameConfig); } } while (sc.CheckToken(',')); } else if (!nextKey.CompareNoCase("NOSPRITERENAME")) { sc.MustGetString(); nospriterename = sc.Compare("true"); } else if (!nextKey.CompareNoCase("STARTUPTITLE")) { sc.MustGetString(); GameStartupInfo.Name = sc.String; } else if (!nextKey.CompareNoCase("STARTUPCOLORS")) { sc.MustGetString(); GameStartupInfo.FgColor = V_GetColor(sc); sc.MustGetStringName(","); sc.MustGetString(); GameStartupInfo.BkColor = V_GetColor(sc); } else if (!nextKey.CompareNoCase("STARTUPTYPE")) { sc.MustGetString(); FString sttype = sc.String; if (!sttype.CompareNoCase("DOOM")) GameStartupInfo.Type = FStartupInfo::DoomStartup; else if (!sttype.CompareNoCase("HERETIC")) GameStartupInfo.Type = FStartupInfo::HereticStartup; else if (!sttype.CompareNoCase("HEXEN")) GameStartupInfo.Type = FStartupInfo::HexenStartup; else if (!sttype.CompareNoCase("STRIFE")) GameStartupInfo.Type = FStartupInfo::StrifeStartup; else GameStartupInfo.Type = FStartupInfo::DefaultStartup; } else if (!nextKey.CompareNoCase("STARTUPSONG")) { sc.MustGetString(); GameStartupInfo.Song = sc.String; } else if (!nextKey.CompareNoCase("LOADLIGHTS")) { sc.MustGetNumber(); GameStartupInfo.LoadLights = !!sc.Number; } else if (!nextKey.CompareNoCase("LOADBRIGHTMAPS")) { sc.MustGetNumber(); GameStartupInfo.LoadBrightmaps = !!sc.Number; } else if (!nextKey.CompareNoCase("LOADWIDESCREEN")) { sc.MustGetNumber(); GameStartupInfo.LoadWidescreen = !!sc.Number; } else if (!nextKey.CompareNoCase("DISCORDAPPID")) { sc.MustGetString(); GameStartupInfo.DiscordAppId = sc.String; } else if (!nextKey.CompareNoCase("STEAMAPPID")) { sc.MustGetString(); GameStartupInfo.SteamAppId = sc.String; } else { // Silently ignore unknown properties do { sc.MustGetAnyToken(); } while(sc.CheckToken(',')); } } return iwad; } static FString CheckGameInfo(TArray & pwads) { FileSystem check; LumpFilterInfo lfi; for (auto p : iwad_folders) lfi.reservedFolders.Push(p); for (auto p : iwad_reserved) lfi.requiredPrefixes.Push(p); // Open the entire list as a temporary file system and look for a GAMEINFO lump. The last one will automatically win. check.InitMultipleFiles(pwads, true, &lfi); if (check.GetNumEntries() > 0) { int num = check.CheckNumForName("GAMEINFO"); if (num >= 0) { // Found one! auto data = check.GetFileData(num); auto wadname = check.GetResourceFileName(check.GetFileContainer(num)); return ParseGameInfo(pwads, wadname, (const char*)data.Data(), data.Size()); } } return ""; } //========================================================================== // // Checks the IWAD for MAP01 and if found sets GI_MAPxx // //========================================================================== static void SetMapxxFlag() { int lump_name = fileSystem.CheckNumForName("MAP01", ns_global, fileSystem.GetIwadNum()); int lump_wad = fileSystem.CheckNumForFullName("maps/map01.wad", fileSystem.GetIwadNum()); int lump_map = fileSystem.CheckNumForFullName("maps/map01.map", fileSystem.GetIwadNum()); if (lump_name >= 0 || lump_wad >= 0 || lump_map >= 0) gameinfo.flags |= GI_MAPxx; } //========================================================================== // // Initialize // //========================================================================== static void D_DoomInit() { // Check response files before coalescing file parameters. M_FindResponseFile (); // Combine different file parameters with their pre-switch bits. Args->CollectFiles("-deh", ".deh"); Args->CollectFiles("-bex", ".bex"); Args->CollectFiles("-exec", ".cfg"); Args->CollectFiles("-playdemo", ".lmp"); Args->CollectFiles("-file", NULL); // anything left goes after -file gamestate = GS_STARTUP; if (!batchrun) Printf ("M_LoadDefaults: Load system defaults.\n"); M_LoadDefaults (); // load before initing other systems } //========================================================================== // // AddAutoloadFiles // //========================================================================== static void AddAutoloadFiles(const char *autoname, TArray& allwads) { LumpFilterIWAD.Format("%s.", autoname); // The '.' is appened to simplify parsing the string // [SP] Dialog reaction - load lights.pk3 and brightmaps.pk3 based on user choices if (!(gameinfo.flags & GI_SHAREWARE)) { if (GameStartupInfo.LoadLights == 1 || (GameStartupInfo.LoadLights != 0 && autoloadlights)) { const char *lightswad = BaseFileSearch ("lights.pk3", NULL, true, GameConfig); if (lightswad) D_AddFile (allwads, lightswad, true, -1, GameConfig); } if (GameStartupInfo.LoadBrightmaps == 1 || (GameStartupInfo.LoadBrightmaps != 0 && autoloadbrightmaps)) { const char *bmwad = BaseFileSearch ("brightmaps.pk3", NULL, true, GameConfig); if (bmwad) D_AddFile (allwads, bmwad, true, -1, GameConfig); } if (GameStartupInfo.LoadWidescreen == 1 || (GameStartupInfo.LoadWidescreen != 0 && autoloadwidescreen)) { const char *wswad = BaseFileSearch ("game_widescreen_gfx.pk3", NULL, true, GameConfig); if (wswad) D_AddFile (allwads, wswad, true, -1, GameConfig); } } if (!(gameinfo.flags & GI_SHAREWARE) && !Args->CheckParm("-noautoload") && !disableautoload) { FString file; // [RH] zvox.wad - A wad I had intended to be automatically generated // from Q2's pak0.pak so the female and cyborg player could have // voices. I never got around to writing the utility to do it, though. // And I probably never will now. But I know at least one person uses // it for something else, so this gets to stay here. const char *wad = BaseFileSearch ("zvox.wad", NULL, false, GameConfig); if (wad) D_AddFile (allwads, wad, true, -1, GameConfig); // [RH] Add any .wad files in the skins directory #ifdef __unix__ file = SHARE_DIR; #else file = progdir; #endif file += "skins"; D_AddDirectory (allwads, file, "*.wad", GameConfig); #ifdef __unix__ file = NicePath("$HOME/" GAME_DIR "/skins"); D_AddDirectory (allwads, file, "*.wad", GameConfig); #endif // Add common (global) wads D_AddConfigFiles(allwads, "Global.Autoload", "*.wad", GameConfig); ptrdiff_t len; ptrdiff_t lastpos = -1; while ((len = LumpFilterIWAD.IndexOf('.', lastpos+1)) > 0) { file = LumpFilterIWAD.Left(len) + ".Autoload"; D_AddConfigFiles(allwads, file, "*.wad", GameConfig); lastpos = len; } } } //========================================================================== // // CheckCmdLine // //========================================================================== static void CheckCmdLine() { int flags = dmflags; int p; const char *v; if (!batchrun) Printf ("Checking cmd-line parameters...\n"); if (Args->CheckParm ("-nomonsters")) flags |= DF_NO_MONSTERS; if (Args->CheckParm ("-respawn")) flags |= DF_MONSTERS_RESPAWN; if (Args->CheckParm ("-fast")) flags |= DF_FAST_MONSTERS; devparm = !!Args->CheckParm ("-devparm"); if (Args->CheckParm ("-altdeath")) { deathmatch = 1; flags |= DF_ITEMS_RESPAWN; } else if (Args->CheckParm ("-deathmatch")) { deathmatch = 1; flags |= DF_WEAPONS_STAY | DF_ITEMS_RESPAWN; } dmflags = flags; // get skill / episode / map from parms if (gameinfo.gametype != GAME_Hexen) { startmap = (gameinfo.flags & GI_MAPxx) ? "MAP01" : "E1M1"; } else { startmap = "&wt@01"; } autostart = StoredWarp.IsNotEmpty(); const char *val = Args->CheckValue ("-skill"); if (val) { gameskill = val[0] - '1'; autostart = true; } p = Args->CheckParm ("-warp"); if (p && p < Args->NumArgs() - 1) { int ep, map; if (gameinfo.flags & GI_MAPxx) { ep = 1; map = atoi (Args->GetArg(p+1)); } else { ep = atoi (Args->GetArg(p+1)); map = p < Args->NumArgs() - 2 ? atoi (Args->GetArg(p+2)) : 10; if (map < 1 || map > 9) { map = ep; ep = 1; } } startmap = CalcMapName (ep, map); autostart = true; } // [RH] Hack to handle +map. The standard console command line handler // won't be able to handle it, so we take it out of the command line and set // it up like -warp. FString mapvalue = Args->TakeValue("+map"); if (mapvalue.IsNotEmpty()) { if (!P_CheckMapData(mapvalue)) { Printf ("Can't find map %s\n", mapvalue.GetChars()); } else { startmap = mapvalue; autostart = true; } } if (devparm) { Printf ("%s", GStrings("D_DEVSTR")); } // turbo option // [RH] (now a cvar) v = Args->CheckValue("-turbo"); if (v != NULL) { double amt = atof(v); Printf ("turbo scale: %.0f%%\n", amt); turbo = (float)amt; } v = Args->CheckValue ("-timer"); if (v) { double time = strtod (v, NULL); Printf ("Levels will end after %g minute%s.\n", time, time > 1 ? "s" : ""); timelimit = (float)time; } v = Args->CheckValue ("-avg"); if (v) { Printf ("Austin Virtual Gaming: Levels will end after 20 minutes\n"); timelimit = 20.f; } // // Build status bar line! // if (deathmatch) StartScreen->AppendStatusLine("DeathMatch..."); if (dmflags & DF_NO_MONSTERS) StartScreen->AppendStatusLine("No Monsters..."); if (dmflags & DF_MONSTERS_RESPAWN) StartScreen->AppendStatusLine("Respawning..."); if (autostart) { FString temp; temp.Format ("Warp to map %s, Skill %d ", startmap.GetChars(), gameskill + 1); StartScreen->AppendStatusLine(temp); } } static void NewFailure () { I_FatalError ("Failed to allocate memory from system heap"); } //========================================================================== // // RenameSprites // // Renames sprites in IWADs so that unique actors can have unique sprites, // making it possible to import any actor from any game into any other // game without jumping through hoops to resolve duplicate sprite names. // You just need to know what the sprite's new name is. // //========================================================================== static void RenameSprites(FileSystem &fileSystem, const TArray& deletelumps) { bool renameAll; bool MNTRZfound = false; const char* altbigfont = gameinfo.gametype == GAME_Strife ? "SBIGFONT" : (gameinfo.gametype & GAME_Raven) ? "HBIGFONT" : "DBIGFONT"; static const uint32_t HereticRenames[] = { MAKE_ID('H','E','A','D'), MAKE_ID('L','I','C','H'), // Ironlich }; static const uint32_t HexenRenames[] = { MAKE_ID('B','A','R','L'), MAKE_ID('Z','B','A','R'), // ZBarrel MAKE_ID('A','R','M','1'), MAKE_ID('A','R','_','1'), // MeshArmor MAKE_ID('A','R','M','2'), MAKE_ID('A','R','_','2'), // FalconShield MAKE_ID('A','R','M','3'), MAKE_ID('A','R','_','3'), // PlatinumHelm MAKE_ID('A','R','M','4'), MAKE_ID('A','R','_','4'), // AmuletOfWarding MAKE_ID('S','U','I','T'), MAKE_ID('Z','S','U','I'), // ZSuitOfArmor and ZArmorChunk MAKE_ID('T','R','E','1'), MAKE_ID('Z','T','R','E'), // ZTree and ZTreeDead MAKE_ID('T','R','E','2'), MAKE_ID('T','R','E','S'), // ZTreeSwamp150 MAKE_ID('C','A','N','D'), MAKE_ID('B','C','A','N'), // ZBlueCandle MAKE_ID('R','O','C','K'), MAKE_ID('R','O','K','K'), // rocks and dirt in a_debris.cpp MAKE_ID('W','A','T','R'), MAKE_ID('H','W','A','T'), // Strife also has WATR MAKE_ID('G','I','B','S'), MAKE_ID('P','O','L','5'), // RealGibs MAKE_ID('E','G','G','M'), MAKE_ID('P','R','K','M'), // PorkFX MAKE_ID('I','N','V','U'), MAKE_ID('D','E','F','N'), // Icon of the Defender }; static const uint32_t StrifeRenames[] = { MAKE_ID('M','I','S','L'), MAKE_ID('S','M','I','S'), // lots of places MAKE_ID('A','R','M','1'), MAKE_ID('A','R','M','3'), // MetalArmor MAKE_ID('A','R','M','2'), MAKE_ID('A','R','M','4'), // LeatherArmor MAKE_ID('P','M','A','P'), MAKE_ID('S','M','A','P'), // StrifeMap MAKE_ID('T','L','M','P'), MAKE_ID('T','E','C','H'), // TechLampSilver and TechLampBrass MAKE_ID('T','R','E','1'), MAKE_ID('T','R','E','T'), // TreeStub MAKE_ID('B','A','R','1'), MAKE_ID('B','A','R','C'), // BarricadeColumn MAKE_ID('S','H','T','2'), MAKE_ID('M','P','U','F'), // MaulerPuff MAKE_ID('B','A','R','L'), MAKE_ID('B','B','A','R'), // StrifeBurningBarrel MAKE_ID('T','R','C','H'), MAKE_ID('T','R','H','L'), // SmallTorchLit MAKE_ID('S','H','R','D'), MAKE_ID('S','H','A','R'), // glass shards MAKE_ID('B','L','S','T'), MAKE_ID('M','A','U','L'), // Mauler MAKE_ID('L','O','G','G'), MAKE_ID('L','O','G','W'), // StickInWater MAKE_ID('V','A','S','E'), MAKE_ID('V','A','Z','E'), // Pot and Pitcher MAKE_ID('C','N','D','L'), MAKE_ID('K','N','D','L'), // Candle MAKE_ID('P','O','T','1'), MAKE_ID('M','P','O','T'), // MetalPot MAKE_ID('S','P','I','D'), MAKE_ID('S','T','L','K'), // Stalker }; const uint32_t* renames; int numrenames; switch (gameinfo.gametype) { case GAME_Doom: default: // Doom's sprites don't get renamed. renames = nullptr; numrenames = 0; break; case GAME_Heretic: renames = HereticRenames; numrenames = sizeof(HereticRenames) / 8; break; case GAME_Hexen: renames = HexenRenames; numrenames = sizeof(HexenRenames) / 8; break; case GAME_Strife: renames = StrifeRenames; numrenames = sizeof(StrifeRenames) / 8; break; } unsigned NumFiles = fileSystem.GetNumEntries(); for (uint32_t i = 0; i < NumFiles; i++) { // check for full Minotaur animations. If this is not found // some frames need to be renamed. if (fileSystem.GetFileNamespace(i) == ns_sprites) { auto& shortName = fileSystem.GetShortName(i); if (shortName.dword == MAKE_ID('M', 'N', 'T', 'R') && shortName.String[4] == 'Z') { MNTRZfound = true; break; } } } renameAll = !!Args->CheckParm("-oldsprites") || nospriterename; for (uint32_t i = 0; i < NumFiles; i++) { auto& shortName = fileSystem.GetShortName(i); if (fileSystem.GetFileNamespace(i) == ns_sprites) { // Only sprites in the IWAD normally get renamed if (renameAll || fileSystem.GetFileContainer(i) == fileSystem.GetIwadNum()) { for (int j = 0; j < numrenames; ++j) { if (shortName.dword == renames[j * 2]) { shortName.dword = renames[j * 2 + 1]; } } if (gameinfo.gametype == GAME_Hexen) { if (fileSystem.CheckFileName(i, "ARTIINVU")) { shortName.String[4] = 'D'; shortName.String[5] = 'E'; shortName.String[6] = 'F'; shortName.String[7] = 'N'; } } } if (!MNTRZfound) { if (shortName.dword == MAKE_ID('M', 'N', 'T', 'R')) { for (size_t fi : {4, 6}) { if (shortName.String[fi] >= 'F' && shortName.String[fi] <= 'K') { shortName.String[fi] += 'U' - 'F'; } } } } // When not playing Doom rename all BLOD sprites to BLUD so that // the same blood states can be used everywhere if (!(gameinfo.gametype & GAME_DoomChex)) { if (shortName.dword == MAKE_ID('B', 'L', 'O', 'D')) { shortName.dword = MAKE_ID('B', 'L', 'U', 'D'); } } } else if (fileSystem.GetFileNamespace(i) == ns_global) { int fn = fileSystem.GetFileContainer(i); if (fn >= fileSystem.GetIwadNum() && fn <= fileSystem.GetMaxIwadNum() && deletelumps.Find(shortName.String) < deletelumps.Size()) { shortName.String[0] = 0; // Lump must be deleted from directory. } // Rename the game specific big font lumps so that the font manager does not have to do problematic special checks for them. else if (!strcmp(shortName.String, altbigfont)) { strcpy(shortName.String, "BIGFONT"); } } } } //========================================================================== // // RenameNerve // // Renames map headers and map name pictures in nerve.wad so as to load it // alongside Doom II and offer both episodes without causing conflicts. // MD5 checksum for NERVE.WAD: 967d5ae23daf45196212ae1b605da3b0 (3,819,855) // MD5 checksum for Unity version of NERVE.WAD: 4214c47651b63ee2257b1c2490a518c9 (3,821,966) // //========================================================================== void RenameNerve(FileSystem& fileSystem) { if (gameinfo.gametype != GAME_Doom) return; const int numnerveversions = 4; bool found = false; uint8_t cksum[16]; static const uint8_t nerve[numnerveversions][16] = { { 0x96, 0x7d, 0x5a, 0xe2, 0x3d, 0xaf, 0x45, 0x19, 0x62, 0x12, 0xae, 0x1b, 0x60, 0x5d, 0xa3, 0xb0 }, { 0x42, 0x14, 0xc4, 0x76, 0x51, 0xb6, 0x3e, 0xe2, 0x25, 0x7b, 0x1c, 0x24, 0x90, 0xa5, 0x18, 0xc9 }, { 0x35, 0x44, 0xe1, 0x90, 0x30, 0x91, 0xc5, 0x0b, 0xa5, 0x00, 0x49, 0xdb, 0x74, 0xcd, 0x7d, 0x25 }, { 0x91, 0x43, 0xb3, 0x92, 0x39, 0x2a, 0x7a, 0xc8, 0x70, 0xc2, 0xca, 0x36, 0xac, 0x65, 0xaf, 0x45 } }; size_t nervesize[numnerveversions] = { 3819855, 3821966, 3821885, 4003409 }; // NERVE.WAD's file size int w = fileSystem.GetIwadNum(); while (++w < fileSystem.GetNumWads()) { auto fr = fileSystem.GetFileReader(w); int isizecheck = -1; if (fr == NULL) { continue; } for (int icheck = 0; icheck < numnerveversions; icheck++) if (fr->GetLength() == (long)nervesize[icheck]) isizecheck = icheck; if (isizecheck == -1) { // Skip MD5 computation when there is a // cheaper way to know this is not the file continue; } fr->Seek(0, FileReader::SeekSet); MD5Context md5; md5Update(*fr, md5, (unsigned)fr->GetLength()); md5.Final(cksum); if (memcmp(nerve[isizecheck], cksum, 16) == 0) { found = true; break; } } if (!found) return; for (int i = fileSystem.GetFirstEntry(w); i <= fileSystem.GetLastEntry(w); i++) { auto& shortName = fileSystem.GetShortName(i); // Only rename the maps from NERVE.WAD if (shortName.dword == MAKE_ID('C', 'W', 'I', 'L')) { shortName.String[0] = 'N'; } else if (shortName.dword == MAKE_ID('M', 'A', 'P', '0')) { shortName.String[6] = shortName.String[4]; shortName.String[5] = '0'; shortName.String[4] = 'L'; shortName.dword = MAKE_ID('L', 'E', 'V', 'E'); } } } //========================================================================== // // FixMacHexen // // Discard all extra lumps in Mac version of Hexen IWAD (demo or full) // to avoid any issues caused by names of these lumps, including: // * Wrong height of small font // * Broken life bar of mage class // //========================================================================== void FixMacHexen(FileSystem& fileSystem) { if (GAME_Hexen != gameinfo.gametype) { return; } FileReader* reader = fileSystem.GetFileReader(fileSystem.GetIwadNum()); auto iwadSize = reader->GetLength(); static const long DEMO_SIZE = 13596228; static const long BETA_SIZE = 13749984; static const long FULL_SIZE = 21078584; if (DEMO_SIZE != iwadSize && BETA_SIZE != iwadSize && FULL_SIZE != iwadSize) { return; } reader->Seek(0, FileReader::SeekSet); uint8_t checksum[16]; MD5Context md5; md5Update(*reader, md5, (unsigned)iwadSize); md5.Final(checksum); static const uint8_t HEXEN_DEMO_MD5[16] = { 0x92, 0x5f, 0x9f, 0x50, 0x00, 0xe1, 0x7d, 0xc8, 0x4b, 0x0a, 0x6a, 0x3b, 0xed, 0x3a, 0x6f, 0x31 }; static const uint8_t HEXEN_BETA_MD5[16] = { 0x2a, 0xf1, 0xb2, 0x7c, 0xd1, 0x1f, 0xb1, 0x59, 0xe6, 0x08, 0x47, 0x2a, 0x1b, 0x53, 0xe4, 0x0e }; static const uint8_t HEXEN_FULL_MD5[16] = { 0xb6, 0x81, 0x40, 0xa7, 0x96, 0xf6, 0xfd, 0x7f, 0x3a, 0x5d, 0x32, 0x26, 0xa3, 0x2b, 0x93, 0xbe }; const bool isBeta = 0 == memcmp(HEXEN_BETA_MD5, checksum, sizeof checksum); if (!isBeta && 0 != memcmp(HEXEN_DEMO_MD5, checksum, sizeof checksum) && 0 != memcmp(HEXEN_FULL_MD5, checksum, sizeof checksum)) { return; } static const int EXTRA_LUMPS = 299; // Hexen Beta is very similar to Demo but it has MAP41: Maze at the end of the WAD // So keep this map if it's present but discard all extra lumps const int lastLump = fileSystem.GetLastEntry(fileSystem.GetIwadNum()) - (isBeta ? 12 : 0); assert(fileSystem.GetFirstEntry(fileSystem.GetIwadNum()) + 299 < lastLump); for (int i = lastLump - EXTRA_LUMPS + 1; i <= lastLump; ++i) { auto& shortName = fileSystem.GetShortName(i); shortName.String[0] = '\0'; } } static void FindStrifeTeaserVoices(FileSystem& fileSystem) { if (gameinfo.gametype == GAME_Strife && gameinfo.flags & GI_SHAREWARE) { unsigned NumFiles = fileSystem.GetNumEntries(); for (uint32_t i = 0; i < NumFiles; i++) { auto& shortName = fileSystem.GetShortName(i); if (fileSystem.GetFileNamespace(i) == ns_global) { // Only sprites in the IWAD normally get renamed if (fileSystem.GetFileContainer(i) == fileSystem.GetIwadNum()) { if (shortName.String[0] == 'V' && shortName.String[1] == 'O' && shortName.String[2] == 'C') { int j; for (j = 3; j < 8; ++j) { if (shortName.String[j] != 0 && !isdigit(shortName.String[j])) break; } if (j == 8) { fileSystem.SetFileNamespace(i, ns_strifevoices); } } } } } } } static const char *DoomButtons[] = { "am_panleft", "user2" , "jump" , "right" , "zoom" , "back" , "am_zoomin", "reload" , "lookdown" , "am_zoomout", "user4" , "attack" , "user1" , "klook" , "forward" , "movedown" , "altattack" , "moveleft" , "moveright" , "am_panright", "am_panup" , "mlook" , "crouch" , "left" , "lookup" , "user3" , "strafe" , "am_pandown", "showscores" , "speed" , "use" , "moveup" }; CVAR(Bool, lookspring, true, CVAR_ARCHIVE); // Generate centerview when -mlook encountered? 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 Mlook_ReleaseHandler() { if (lookspring) { Net_WriteByte(DEM_CENTERVIEW); } } int StrTable_GetGender() { return players[consoleplayer].userinfo.GetGender(); } bool StrTable_ValidFilter(const char* str) { if (gameinfo.gametype == GAME_Strife && (gameinfo.flags & GI_SHAREWARE) && !stricmp(str, "strifeteaser")) return true; return stricmp(str, GameNames[gameinfo.gametype]) == 0; } 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); } // [ZZ] check active event handlers that want the UI processing if (!wantCapt && primaryLevel->localEventManager->CheckUiProcessors()) wantCapt = true; return wantCapt; } bool System_WantLeftButton() { return (gamestate == GS_DEMOSCREEN || gamestate == GS_TITLELEVEL); } static bool System_DispatchEvent(event_t* ev) { if (ev->type == EV_Mouse && menuactive == MENU_Off && ConsoleState != c_down && ConsoleState != c_falling && !primaryLevel->localEventManager->Responder(ev) && !paused) { if (buttonMap.ButtonDown(Button_Mlook) || freelook) { int look = int(ev->y * m_pitch * 16.0); if (invertmouse) look = -look; G_AddViewPitch(look, true); ev->y = 0; } if (!buttonMap.ButtonDown(Button_Strafe) && !lookstrafe) { int turn = int(ev->x * m_yaw * 8.0); if (invertmousex) turn = -turn; G_AddViewAngle(turn, true); ev->x = 0; } if (ev->x == 0 && ev->y == 0) { return true; } } return false; } bool System_NetGame() { return netgame; } bool System_WantNativeMouse() { return primaryLevel->localEventManager->CheckRequireMouse(); } static bool System_CaptureModeInGame() { if (demoplayback || paused) return false; switch (mouse_capturemode) { default: case 0: return gamestate == GS_LEVEL; case 1: return gamestate == GS_LEVEL || gamestate == GS_CUTSCENE; case 2: return true; } } static void System_PlayStartupSound(const char* sndname) { S_Sound(CHAN_BODY, 0, sndname, 1, ATTN_NONE); } static bool System_IsSpecialUI() { return (generic_ui || !!log_vgafont || !!dlg_vgafont || ConsoleState != c_up || multiplayer || (menuactive == MENU_On && CurrentMenu && !CurrentMenu->IsKindOf("ConversationMenu"))); } static bool System_DisableTextureFilter() { return !V_IsHardwareRenderer(); } static void System_OnScreenSizeChanged() { if (StatusBar != NULL) { StatusBar->CallScreenSizeChanged(); } // Reload crosshair if transitioned to a different size ST_LoadCrosshair(true); if (primaryLevel && primaryLevel->automap) primaryLevel->automap->NewResolution(); } IntRect System_GetSceneRect() { IntRect mSceneViewport; // Special handling so the view with a visible status bar displays properly int height, width; if (screenblocks >= 10) { height = screen->GetHeight(); width = screen->GetWidth(); } else { height = (screenblocks * screen->GetHeight() / 10) & ~7; width = (screenblocks * screen->GetWidth() / 10); } mSceneViewport.left = viewwindowx; mSceneViewport.top = screen->GetHeight() - (height + viewwindowy - ((height - viewheight) / 2)); mSceneViewport.width = viewwidth; mSceneViewport.height = height; return mSceneViewport; } FString System_GetLocationDescription() { auto& vp = r_viewpoint; auto Level = vp.ViewLevel; return Level == nullptr ? FString() : FStringf("Map %s: \"%s\",\nx = %1.4f, y = %1.4f, z = %1.4f, angle = %1.4f, pitch = %1.4f\n%llu fps\n\n", Level->MapName.GetChars(), Level->LevelName.GetChars(), vp.Pos.X, vp.Pos.Y, vp.Pos.Z, vp.Angles.Yaw.Degrees, vp.Angles.Pitch.Degrees, (unsigned long long)LastCount); } FString System_GetPlayerName(int node) { return players[node].userinfo.GetName(); } void System_ConsoleToggled(int state) { if (state == c_falling && hud_toggled) D_ToggleHud(); } //========================================================================== // // DoomSpecificInfo // // Called by the crash logger to get application-specific information. // //========================================================================== void System_CrashInfo(char* buffer, size_t bufflen, const char *lfstr) { 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, "%sWad %d: %s", lfstr, i, arg); } if (gamestate != GS_LEVEL && gamestate != GS_TITLELEVEL) { buffer += mysnprintf(buffer, buffend - buffer, "%s%sNot in a level.", lfstr, lfstr); } else { buffer += mysnprintf(buffer, buffend - buffer, "%s%sCurrent map: %s", lfstr, lfstr, primaryLevel->MapName.GetChars()); if (!viewactive) { buffer += mysnprintf(buffer, buffend - buffer, "%s%sView not active.", lfstr, lfstr); } else { auto& vp = r_viewpoint; buffer += mysnprintf(buffer, buffend - buffer, "%s%sviewx = %f", lfstr, lfstr, vp.Pos.X); buffer += mysnprintf(buffer, buffend - buffer, "%sviewy = %f", lfstr, vp.Pos.Y); buffer += mysnprintf(buffer, buffend - buffer, "%sviewz = %f", lfstr, vp.Pos.Z); buffer += mysnprintf(buffer, buffend - buffer, "%sviewangle = %f", lfstr, vp.Angles.Yaw.Degrees); } } buffer += mysnprintf(buffer, buffend - buffer, "%s", lfstr); *buffer = 0; } void System_M_Dim(); static void PatchTextures() { // The Hexen scripts use BLANK as a blank texture, even though it's really not. // I guess the Doom renderer must have clipped away the line at the bottom of // the texture so it wasn't visible. Change its use type to a blank null texture to really make it blank. if (gameinfo.gametype == GAME_Hexen) { FTextureID tex = TexMan.CheckForTexture("BLANK", ETextureType::Wall, false); if (tex.Exists()) { auto texture = TexMan.GetGameTexture(tex, false); texture->SetUseType(ETextureType::Null); } } } //========================================================================== // // this gets called during texture creation to fix known problems // //========================================================================== static void CheckForHacks(BuildInfo& buildinfo) { if (buildinfo.Parts.Size() == 0) { return; } // Heretic sky textures are marked as only 128 pixels tall, // even though they are really 200 pixels tall. if (gameinfo.gametype == GAME_Heretic && buildinfo.Name.Len() == 4 && buildinfo.Name[0] == 'S' && buildinfo.Name[1] == 'K' && buildinfo.Name[2] == 'Y' && buildinfo.Name[3] >= '1' && buildinfo.Name[3] <= '3' && buildinfo.Height == 128 && buildinfo.Parts.Size() == 1) { // This must alter the size of both the texture image and the game texture. buildinfo.Height = buildinfo.Parts[0].TexImage->GetImage()->GetHeight(); buildinfo.texture->SetSize(buildinfo.Width, buildinfo.Height); return; } // The Doom E1 sky has its patch's y offset at -8 instead of 0. if (gameinfo.gametype == GAME_Doom && !(gameinfo.flags & GI_MAPxx) && buildinfo.Name.Len() == 4 && buildinfo.Parts.Size() == 1 && buildinfo.Height == 128 && buildinfo.Parts[0].OriginY == -8 && buildinfo.Name[0] == 'S' && buildinfo.Name[1] == 'K' && buildinfo.Name[2] == 'Y' && buildinfo.Name[3] == '1') { buildinfo.Parts[0].OriginY = 0; return; } // BIGDOOR7 in Doom also has patches at y offset -4 instead of 0. if (gameinfo.gametype == GAME_Doom && !(gameinfo.flags & GI_MAPxx) && buildinfo.Name.CompareNoCase("BIGDOOR7") == 0 && buildinfo.Parts.Size() == 2 && buildinfo.Height == 128 && buildinfo.Parts[0].OriginY == -4 && buildinfo.Parts[1].OriginY == -4) { buildinfo.Parts[0].OriginY = 0; buildinfo.Parts[1].OriginY = 0; return; } } static void FixWideStatusBar() { FGameTexture* sbartex = TexMan.FindGameTexture("stbar", ETextureType::MiscPatch); // only adjust offsets if none already exist and if the texture is not scaled. For scaled textures a proper offset is needed. if (sbartex && sbartex->GetTexelWidth() > 320 && sbartex->GetScaleX() == 1 && !sbartex->GetTexelLeftOffset(0) && !sbartex->GetTexelTopOffset(0)) { sbartex->SetOffsets(0, (sbartex->GetTexelWidth() - 320) / 2, 0); sbartex->SetOffsets(1, (sbartex->GetTexelWidth() - 320) / 2, 0); } } //========================================================================== // // // //========================================================================== static void Doom_CastSpriteIDToString(FString* a, unsigned int b) { *a = (b >= sprites.Size()) ? "TNT1" : sprites[b].name; } extern DThinker* NextToThink; static void GC_MarkGameRoots() { GC::Mark(staticEventManager.FirstEventHandler); GC::Mark(staticEventManager.LastEventHandler); for (auto Level : AllLevels()) Level->Mark(); // Mark players. for (int i = 0; i < MAXPLAYERS; i++) { if (playeringame[i]) players[i].PropagateMark(); } // NextToThink must not be freed while thinkers are ticking. GC::Mark(NextToThink); } 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) { if (type != wipe_None) wipegamestate = type == wipe_Burn? GS_FORCEWIPEBURN : type == wipe_Fade? GS_FORCEWIPEFADE : GS_FORCEWIPEMELT; } bool CheckSkipGameOptionBlock(const char* str); //========================================================================== // // // //========================================================================== static FILE* D_GetHashFile() { FILE *hashfile = nullptr; if (Args->CheckParm("-hashfiles")) { const char *filename = "fileinfo.txt"; Printf("Hashing loaded content to: %s\n", filename); hashfile = fopen(filename, "w"); if (hashfile) { Printf("Notice: File hashing is incredibly verbose. Expect loading files to take much longer than usual.\n"); fprintf(hashfile, "%s version %s (%s)\n", GAMENAME, GetVersionString(), GetGitHash()); #ifdef __VERSION__ fprintf(hashfile, "Compiler version: %s\n", __VERSION__); #endif fprintf(hashfile, "Command line:"); for (int i = 0; i < Args->NumArgs(); ++i) { fprintf(hashfile, " %s", Args->GetArg(i)); } fprintf(hashfile, "\n"); } } return hashfile; } //========================================================================== // // D_InitGame // //========================================================================== static int D_InitGame(const FIWADInfo* iwad_info, TArray& allwads, TArray& pwads) { gameinfo.gametype = iwad_info->gametype; gameinfo.flags = iwad_info->flags; gameinfo.nokeyboardcheats = iwad_info->nokeyboardcheats; gameinfo.ConfigName = iwad_info->Configname; const char *v = Args->CheckValue("-rngseed"); if (v) { rngseed = staticrngseed = atoi(v); use_staticrng = true; if (!batchrun) Printf("D_DoomInit: Static RNGseed %d set.\n", rngseed); } else { rngseed = I_MakeRNGSeed(); use_staticrng = false; } srand(rngseed); FRandom::StaticClearRandom (); FBaseCVar::DisableCallbacks(); GameConfig->DoGameSetup (gameinfo.ConfigName); AddAutoloadFiles(iwad_info->Autoname, allwads); // Process automatically executed files FExecList *exec; FArgs *execFiles = new FArgs; if (!(Args->CheckParm("-noautoexec"))) GameConfig->AddAutoexec(execFiles, gameinfo.ConfigName); exec = D_MultiExec(execFiles, NULL); delete execFiles; // Process .cfg files at the start of the command line. execFiles = Args->GatherFiles ("-exec"); exec = D_MultiExec(execFiles, exec); delete execFiles; // [RH] process all + commands on the command line exec = C_ParseCmdLineParams(exec); if (exec != NULL) { exec->AddPullins(allwads, GameConfig); } if (!batchrun) Printf ("W_Init: Init WADfiles.\n"); LumpFilterInfo lfi; lfi.dotFilter = LumpFilterIWAD; static const struct { int match; const char* name; } blanket[] = { { GAME_Raven, "game-Raven" }, { GAME_DoomStrifeChex, "game-DoomStrifeChex" }, { GAME_DoomChex, "game-DoomChex" }, { GAME_Any, NULL } }; for (auto& inf : blanket) { if (gameinfo.gametype & inf.match) lfi.gameTypeFilter.Push(inf.name); } lfi.gameTypeFilter.Push(FStringf("game-%s", GameTypeName())); for (auto p : iwad_folders) lfi.reservedFolders.Push(p); for (auto p : iwad_reserved) lfi.requiredPrefixes.Push(p); lfi.postprocessFunc = [&]() { RenameNerve(fileSystem); RenameSprites(fileSystem, iwad_info->DeleteLumps); FixMacHexen(fileSystem); FindStrifeTeaserVoices(fileSystem); }; allwads.Append(std::move(pwads)); bool allowduplicates = Args->CheckParm("-allowduplicates"); auto hashfile = D_GetHashFile(); fileSystem.InitMultipleFiles (allwads, false, &lfi, allowduplicates, hashfile); allwads.Clear(); allwads.ShrinkToFit(); SetMapxxFlag(); D_GrabCVarDefaults(); //parse DEFCVARS GameConfig->DoKeySetup(gameinfo.ConfigName); // Now that wads are loaded, define mod-specific cvars. ParseCVarInfo(); // Actually exec command line commands and exec files. if (exec != NULL) { exec->ExecCommands(); delete exec; exec = NULL; } // [RH] Initialize localizable strings. GStrings.LoadStrings (language); V_InitFontColors (); // [RH] Moved these up here so that we can do most of our // startup output in a fullscreen console. CT_Init (); if (!restart) { if (!batchrun) Printf ("I_Init: Setting up machine state.\n"); CheckCPUID(&CPU); CalculateCPUSpeed(); auto ci = DumpCPUInfo(&CPU); Printf("%s", ci.GetChars()); } // [RH] Initialize palette management InitPalette (); if (!batchrun) Printf ("V_Init: allocate screen.\n"); if (!restart) { V_InitScreenSize(); } if (!restart) { // This allocates a dummy framebuffer as a stand-in until V_Init2 is called. V_InitScreen (); } if (restart) { // Update screen palette when restarting screen->UpdatePalette(); } // Base systems have been inited; enable cvar callbacks FBaseCVar::EnableCallbacks (); if (!batchrun) Printf ("S_Init: Setting up sound.\n"); S_Init (); if (!batchrun) Printf ("ST_Init: Init startup screen.\n"); if (!restart) { if (GameStartupInfo.Type == FStartupInfo::DefaultStartup) { switch (gameinfo.gametype) { case GAME_Hexen: GameStartupInfo.Type = FStartupInfo::HexenStartup; break; case GAME_Heretic: GameStartupInfo.Type = FStartupInfo::HereticStartup; break; case GAME_Strife: GameStartupInfo.Type = FStartupInfo::StrifeStartup; break; default: break; } } StartScreen = FStartupScreen::CreateInstance (TexMan.GuesstimateNumTextures() + 5); } else { StartScreen = new FStartupScreen(0); } CheckCmdLine(); // [RH] Load sound environments S_ParseReverbDef (); // [RH] Parse any SNDINFO lumps if (!batchrun) Printf ("S_InitData: Load sound definitions.\n"); S_InitData (); // [RH] Parse through all loaded mapinfo lumps if (!batchrun) Printf ("G_ParseMapInfo: Load map definitions.\n"); G_ParseMapInfo (iwad_info->MapInfo); MessageBoxClass = gameinfo.MessageBoxClass; endoomName = gameinfo.Endoom; menuBlurAmount = gameinfo.bluramount; ReadStatistics(); // MUSINFO must be parsed after MAPINFO S_ParseMusInfo(); if (!batchrun) Printf ("Texman.Init: Init texture manager.\n"); UpdateUpscaleMask(); SpriteFrames.Clear(); TexMan.Init([]() { StartScreen->Progress(); }, CheckForHacks); PatchTextures(); TexAnim.Init(); C_InitConback(TexMan.CheckForTexture(gameinfo.BorderFlat, ETextureType::Flat), true, 0.25); FixWideStatusBar(); StartScreen->Progress(); V_InitFonts(); InitDoomFonts(); V_LoadTranslations(); UpdateGenericUI(false); // [CW] Parse any TEAMINFO lumps. if (!batchrun) Printf ("ParseTeamInfo: Load team definitions.\n"); TeamLibrary.ParseTeamInfo (); R_ParseTrnslate(); PClassActor::StaticInit (); Job_Init(); // [GRB] Initialize player class list SetupPlayerClasses (); // [RH] Load custom key and weapon settings from WADs D_LoadWadSettings (); // [GRB] Check if someone used clearplayerclasses but not addplayerclass if (PlayerClasses.Size () == 0) { I_FatalError ("No player classes defined"); } StartScreen->Progress (); ParseGLDefs(); if (!batchrun) Printf ("R_Init: Init %s refresh subsystem.\n", gameinfo.ConfigName.GetChars()); StartScreen->LoadingStatus ("Loading graphics", 0x3f); R_Init (); if (!batchrun) Printf ("DecalLibrary: Load decals.\n"); DecalLibrary.ReadAllDecals (); // Load embedded Dehacked patches D_LoadDehLumps(FromIWAD); // [RH] Add any .deh and .bex files on the command line. // If there are none, try adding any in the config file. // Note that the command line overrides defaults from the config. if ((ConsiderPatches("-deh") | ConsiderPatches("-bex")) == 0 && gameinfo.gametype == GAME_Doom && GameConfig->SetSection ("Doom.DefaultDehacked")) { const char *key; const char *value; while (GameConfig->NextInSection (key, value)) { if (stricmp (key, "Path") == 0 && FileExists (value)) { if (!batchrun) Printf ("Applying patch %s\n", value); D_LoadDehFile(value); } } } // Load embedded Dehacked patches D_LoadDehLumps(FromPWADs); // Create replacements for dehacked pickups FinishDehPatch(); if (!batchrun) Printf("M_Init: Init menus.\n"); SetDefaultMenuColors(); M_Init(); M_CreateGameMenus(); // clean up the compiler symbols which are not needed any longer. RemoveUnusedSymbols(); InitActorNumsFromMapinfo(); InitSpawnablesFromMapinfo(); PClassActor::StaticSetActorNums(); //Added by MC: primaryLevel->BotInfo.getspawned.Clear(); FString *args; int argcount = Args->CheckParmList("-bots", &args); for (int p = 0; p < argcount; ++p) { primaryLevel->BotInfo.getspawned.Push(args[p]); } primaryLevel->BotInfo.spawn_tries = 0; primaryLevel->BotInfo.wanted_botnum = primaryLevel->BotInfo.getspawned.Size(); if (!batchrun) Printf ("P_Init: Init Playloop state.\n"); StartScreen->LoadingStatus ("Init game engine", 0x3f); AM_StaticInit(); P_Init (); P_SetupWeapons_ntohton(); //SBarInfo support. Note that the first SBARINFO lump contains the mugshot definition so it even needs to be read when a regular status bar is being used. SBarInfo::Load(); if (!batchrun) { // [RH] User-configurable startup strings. Because BOOM does. static const char *startupString[5] = { "STARTUP1", "STARTUP2", "STARTUP3", "STARTUP4", "STARTUP5" }; for (int p = 0; p < 5; ++p) { // At this point we cannot use the player's gender info yet so force 'male' here. const char *str = GStrings.GetString(startupString[p], nullptr, 0); if (str != NULL && str[0] != '\0') { Printf("%s\n", str); } } } if (!restart) { if (!batchrun) Printf ("D_CheckNetGame: Checking network game status.\n"); StartScreen->LoadingStatus ("Checking network game status.", 0x3f); if (!D_CheckNetGame ()) { return 0; } } // [SP] Force vanilla transparency auto-detection to re-detect our game lumps now UpdateVanillaTransparency(); // [RH] Lock any cvars that should be locked now that we're // about to begin the game. FBaseCVar::EnableNoSet (); // [RH] Run any saved commands from the command line or autoexec.cfg now. gamestate = GS_FULLCONSOLE; Net_NewMakeTic (); C_RunDelayedCommands(); gamestate = GS_STARTUP; // enable custom invulnerability map here if (cl_customizeinvulmap) R_UpdateInvulnerabilityColormap(); if (!restart) { // start the apropriate game based on parms auto v = Args->CheckValue ("-record"); if (v) { G_RecordDemo (v); autostart = true; } delete StartScreen; StartScreen = NULL; S_Sound (CHAN_BODY, 0, "misc/startupdone", 1, ATTN_NONE); if (Args->CheckParm("-norun") || batchrun) { return 1337; // special exit } V_Init2(); twod->fullscreenautoaspect = gameinfo.fullscreenautoaspect; // Initialize the size of the 2D drawer so that an attempt to access it outside the draw code won't crash. twod->Begin(screen->GetWidth(), screen->GetHeight()); twod->End(); UpdateJoystickMenu(NULL); UpdateVRModes(); v = Args->CheckValue ("-loadgame"); if (v) { FString file(v); FixPathSeperator (file); DefaultExtension (file, "." SAVEGAME_EXT); G_LoadGame (file); } v = Args->CheckValue("-playdemo"); if (v != NULL) { singledemo = true; // quit after one demo G_DeferedPlayDemo (v); } else { v = Args->CheckValue("-timedemo"); if (v) { G_TimeDemo(v); } else { if (gameaction != ga_loadgame && gameaction != ga_loadgamehidecon) { if (autostart || netgame) { // Do not do any screenwipes when autostarting a game. if (!Args->CheckParm("-warpwipe")) { NoWipe = TICRATE; } CheckWarpTransMap(startmap, true); if (demorecording) G_BeginRecording(startmap); G_InitNew(startmap, false); if (StoredWarp.IsNotEmpty()) { AddCommandString(StoredWarp); StoredWarp = ""; } } else { D_StartTitle(); // start up intro loop } } else if (demorecording) { G_BeginRecording(NULL); } } } } else { // These calls from inside V_Init2 are still necessary C_NewModeAdjust(); D_StartTitle (); // start up intro loop setmodeneeded = false; // This may be set to true here, but isn't needed for a restart } return 0; } //========================================================================== // // D_DoomMain // //========================================================================== static int D_DoomMain_Internal (void) { const char *v; const char *wad; FIWadManager *iwad_man; GC::AddMarkerFunc(GC_MarkGameRoots); VM_CastSpriteIDToString = Doom_CastSpriteIDToString; // Set up the button list. Mlook and Klook need a bit of extra treatment. buttonMap.SetButtons(DoomButtons, countof(DoomButtons)); buttonMap.GetButton(Button_Mlook)->ReleaseHandler = Mlook_ReleaseHandler; buttonMap.GetButton(Button_Mlook)->bReleaseLock = true; buttonMap.GetButton(Button_Klook)->bReleaseLock = true; sysCallbacks = { System_WantGuiCapture, System_WantLeftButton, System_NetGame, System_WantNativeMouse, System_CaptureModeInGame, System_CrashInfo, System_PlayStartupSound, System_IsSpecialUI, System_DisableTextureFilter, System_OnScreenSizeChanged, System_GetSceneRect, System_GetLocationDescription, System_M_Dim, System_GetPlayerName, System_DispatchEvent, StrTable_ValidFilter, StrTable_GetGender, nullptr, CheckSkipGameOptionBlock, System_ConsoleToggled, nullptr, nullptr, System_ToggleFullConsole, System_StartCutscene, System_SetTransition, }; std::set_new_handler(NewFailure); const char *batchout = Args->CheckValue("-errorlog"); C_InitConsole(80*8, 25*8, false); I_DetectOS(); // +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"); if (logfile.IsNotEmpty()) { execLogfile(logfile); } else if (batchout != NULL && *batchout != 0) { batchrun = true; nosound = true; execLogfile(batchout, true); Printf("Command line: "); for (int i = 0; i < Args->NumArgs(); i++) { Printf("%s ", Args->GetArg(i)); } Printf("\n"); } if (!batchrun) Printf(PRINT_LOG, "%s version %s\n", GAMENAME, GetVersionString()); D_DoomInit(); extern void D_ConfirmSendStats(); D_ConfirmSendStats(); // [RH] Make sure zdoom.pk3 is always loaded, // as it contains magic stuff we need. wad = BaseFileSearch (BASEWAD, NULL, true, GameConfig); if (wad == NULL) { I_FatalError ("Cannot find " BASEWAD); } FString basewad = wad; FString optionalwad = BaseFileSearch(OPTIONALWAD, NULL, true, GameConfig); iwad_man = new FIWadManager(basewad, optionalwad); // Now that we have the IWADINFO, initialize the autoload ini sections. GameConfig->DoAutoloadSetup(iwad_man); // Prevent the game from starting if the savegame passed to -loadgame is invalid v = Args->CheckValue("-loadgame"); if (v) { FString file(v); FixPathSeperator(file); DefaultExtension(file, "." SAVEGAME_EXT); if (!FileExists(file)) { I_FatalError("Cannot find savegame %s", file.GetChars()); } } // reinit from here do { PClass::StaticInit(); PType::StaticInit(); if (restart) { C_InitConsole(SCREENWIDTH, SCREENHEIGHT, false); } nospriterename = false; if (iwad_man == NULL) { iwad_man = new FIWadManager(basewad, optionalwad); } // Load zdoom.pk3 alone so that we can get access to the internal gameinfos before // the IWAD is known. TArray pwads; GetCmdLineFiles(pwads); FString iwad = CheckGameInfo(pwads); // The IWAD selection dialogue does not show in fullscreen so if the // restart is initiated without a defined IWAD assume for now that it's not going to change. if (iwad.IsEmpty()) iwad = lastIWAD; TArray allwads; const FIWADInfo *iwad_info = iwad_man->FindIWAD(allwads, iwad, basewad, optionalwad); if (!iwad_info) return 0; // user exited the selection popup via cancel button. if ((iwad_info->flags & GI_SHAREWARE) && pwads.Size() > 0) { I_FatalError ("You cannot -file with the shareware version. Register!"); } lastIWAD = iwad; int ret = D_InitGame(iwad_info, allwads, pwads); allwads.Reset(); delete iwad_man; // now we won't need this anymore iwad_man = NULL; if (ret != 0) return ret; D_DoAnonStats(); I_UpdateWindowTitle(); D_DoomLoop (); // this only returns if a 'restart' CCMD is given. // // Clean up after a restart // D_Cleanup(); gamestate = GS_STARTUP; } while (1); } int GameMain() { int ret = 0; GameTicRate = TICRATE; ConsoleCallbacks cb = { D_UserInfoChanged, D_SendServerInfoChange, D_SendServerFlagChange, G_GetUserCVar, []() { return gamestate != GS_FULLCONSOLE && gamestate != GS_STARTUP; } }; C_InstallHandlers(&cb); SetConsoleNotifyBuffer(); try { ret = D_DoomMain_Internal(); } catch (const CExitEvent &exit) // This is a regular exit initiated from deeply nested code. { ret = exit.Reason(); } catch (const std::exception &error) { I_ShowFatalError(error.what()); ret = -1; } // Unless something really bad happened, the game should only exit through this single point in the code. // No more 'exit', please. D_Cleanup(); CloseNetwork(); GC::FinalGC = true; GC::FullGC(); GC::DelSoftRootHead(); // the soft root head will not be collected by a GC so we have to do it explicitly C_DeinitConsole(); R_DeinitColormaps(); R_Shutdown(); I_ShutdownGraphics(); I_ShutdownInput(); M_SaveDefaultsFinal(); DeleteStartupScreen(); delete Args; Args = nullptr; return ret; } //========================================================================== // // clean up the resources // //========================================================================== void D_Cleanup() { if (demorecording) { G_CheckDemoStatus(); } // Music and sound should be stopped first S_StopMusic(true); S_ClearSoundData(); S_UnloadReverbDef(); G_ClearMapinfo(); M_ClearMenus(); // close menu if open AM_ClearColorsets(); DeinitSWColorMaps(); FreeSBarInfoScript(); DeleteScreenJob(); // clean up game state D_ErrorCleanup (); P_Shutdown(); M_SaveDefaults(NULL); // save config before the restart // delete all data that cannot be left until reinitialization CleanSWDrawer(); V_ClearFonts(); // must clear global font pointers ColorSets.Clear(); PainFlashes.Clear(); gameinfo.~gameinfo_t(); new (&gameinfo) gameinfo_t; // Reset gameinfo S_Shutdown(); // free all channels and delete playlist C_ClearAliases(); // CCMDs won't be reinitialized so these need to be deleted here DestroyCVarsFlagged(CVAR_MOD); // Delete any cvar left by mods DeinitMenus(); savegameManager.ClearSaveGames(); LightDefaults.DeleteAndClear(); // this can leak heap memory if it isn't cleared. TexAnim.DeleteAll(); TexMan.DeleteAll(); // delete GameStartupInfo data GameStartupInfo.Name = ""; GameStartupInfo.BkColor = GameStartupInfo.FgColor = GameStartupInfo.Type = 0; GameStartupInfo.LoadWidescreen = GameStartupInfo.LoadLights = GameStartupInfo.LoadBrightmaps = -1; GameStartupInfo.DiscordAppId = ""; GameStartupInfo.SteamAppId = ""; GC::FullGC(); // clean up before taking down the object list. // Delete the reference to the VM functions here which were deleted and will be recreated after the restart. AutoSegs::ActionFunctons.ForEach([](AFuncDesc *afunc) { *(afunc->VMPointer) = NULL; }); GC::DelSoftRootHead(); for (auto& p : players) { p.PendingWeapon = nullptr; } PClassActor::AllActorClasses.Clear(); ScriptUtil::Clear(); PClass::StaticShutdown(); GC::FullGC(); // perform one final garbage collection after shutdown assert(GC::Root == nullptr); restart++; PClass::bShutdown = false; PClass::bVMOperational = false; } //========================================================================== // // restart the game // //========================================================================== UNSAFE_CCMD(restart) { // remove command line args that would get in the way during restart Args->RemoveArgs("-iwad"); Args->RemoveArgs("-deh"); Args->RemoveArgs("-bex"); Args->RemoveArgs("-playdemo"); Args->RemoveArgs("-file"); Args->RemoveArgs("-altdeath"); Args->RemoveArgs("-deathmatch"); Args->RemoveArgs("-skill"); Args->RemoveArgs("-savedir"); Args->RemoveArgs("-xlat"); Args->RemoveArgs("-oldsprites"); if (argv.argc() > 1) { for (int i = 1; iAppendArg(argv[i]); } } wantToRestart = true; } DEFINE_FIELD_X(InputEventData, event_t, type) DEFINE_FIELD_X(InputEventData, event_t, subtype) DEFINE_FIELD_X(InputEventData, event_t, data1) DEFINE_FIELD_X(InputEventData, event_t, data2) DEFINE_FIELD_X(InputEventData, event_t, data3) DEFINE_FIELD_X(InputEventData, event_t, x) DEFINE_FIELD_X(InputEventData, event_t, y) void I_UpdateWindowTitle() { FString titlestr; switch (I_FriendlyWindowTitle) { case 1: if (level.LevelName.IsNotEmpty()) { titlestr.Format("%s - %s", level.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 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()); }