diff options
Diffstat (limited to 'src/strife/d_main.c')
-rw-r--r-- | src/strife/d_main.c | 1966 |
1 files changed, 1966 insertions, 0 deletions
diff --git a/src/strife/d_main.c b/src/strife/d_main.c new file mode 100644 index 00000000..855dae7d --- /dev/null +++ b/src/strife/d_main.c @@ -0,0 +1,1966 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005 Simon Howard +// +// 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 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +// 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. +// +//----------------------------------------------------------------------------- + + +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "config.h" +#include "deh_main.h" +#include "doomdef.h" +#include "doomstat.h" + +#include "dstrings.h" +#include "doomfeatures.h" +#include "sounds.h" + +#include "d_iwad.h" + +#include "z_zone.h" +#include "w_main.h" +#include "w_wad.h" +#include "s_sound.h" +#include "v_video.h" + +#include "f_finale.h" +#include "f_wipe.h" + +#include "m_argv.h" +#include "m_config.h" +#include "m_controls.h" +#include "m_misc.h" +#include "m_menu.h" +#include "m_saves.h" // haleyjd [STRIFE] +#include "p_saveg.h" +#include "p_dialog.h" // haleyjd [STRIFE] + +#include "i_endoom.h" +#include "i_joystick.h" +#include "i_system.h" +#include "i_timer.h" +#include "i_video.h" +#include "i_swap.h" + +#include "g_game.h" + +#include "hu_stuff.h" +#include "wi_stuff.h" +#include "st_stuff.h" +#include "am_map.h" +#include "net_client.h" +#include "net_dedicated.h" +#include "net_query.h" + +#include "p_setup.h" +#include "r_local.h" + +#include "d_main.h" + +// +// D-DoomLoop() +// Not a globally visible function, +// just included for source reference, +// called by D_DoomMain, never exits. +// Manages timing and IO, +// calls all ?_Responder, ?_Ticker, and ?_Drawer, +// calls I_GetTime, I_StartFrame, and I_StartTic +// +void D_DoomLoop (void); + +static boolean D_AddFile(char *filename); + +// Location where savegames are stored + +char * savegamedir; + +// location of IWAD and WAD files + +char * iwadfile; + + +boolean devparm; // started game with -devparm +boolean nomonsters; // checkparm of -nomonsters +boolean respawnparm; // checkparm of -respawn +boolean fastparm; // checkparm of -fast +boolean flipparm; // [STRIFE] haleyjd 20110629: checkparm of -flip +boolean randomparm; // [STRIFE] haleyjd 20130915: checkparm of -random + +boolean showintro = true; // [STRIFE] checkparm of -nograph, disables intro + + +//extern int soundVolume; +//extern int sfxVolume; +//extern int musicVolume; + +extern boolean inhelpscreens; + +skill_t startskill; +int startepisode; +int startmap; +boolean autostart; +int startloadgame; + +boolean advancedemo; + +// villsa [STRIFE] workparm variable (similar to devparm?) +boolean workparm = false; + +// villsa [STRIFE] stonecold cheat variable +boolean stonecold = false; + +// haleyjd 09/11/10: [STRIFE] Game type variables +boolean isregistered; +boolean isdemoversion; + +// Store demo, do not accept any inputs +// haleyjd [STRIFE] Unused. +//boolean storedemo; + + +char wadfile[1024]; // primary wad file +char mapdir[1024]; // directory of development maps + +int show_endoom = 1; +int graphical_startup = 1; + +// If true, startup has completed and the main game loop has started. + +static boolean main_loop_started = false; + +// fraggle 06/03/11 [STRIFE]: Unused config variable, preserved +// for compatibility: + +static int comport = 0; + +// fraggle 06/03/11 [STRIFE]: Multiplayer nickname? +char *nickname = NULL; + +void D_ConnectNetGame(void); +void D_CheckNetGame(void); + + +// +// D_ProcessEvents +// Send all the events of the given timestamp down the responder chain +// +void D_ProcessEvents (void) +{ + event_t* ev; + + // haleyjd 08/22/2010: [STRIFE] there is no such thing as a "store demo" + // version of Strife + + // IF STORE DEMO, DO NOT ACCEPT INPUT + //if (storedemo) + // return; + + while ((ev = D_PopEvent()) != NULL) + { + if (M_Responder (ev)) + continue; // menu ate the event + G_Responder (ev); + } +} + + + + +// +// D_Display +// draw current display, possibly wiping it from the previous +// +// wipegamestate can be set to -1 to force a wipe on the next draw +// +// haleyjd 08/23/10: [STRIFE]: +// * Changes to eliminate intermission and change timing of screenwipe +// * 20100901: Added ST_DrawExternal and popupactivestate static variable +// * 20110206: Start wipegamestate at GS_UNKNOWN (STRIFE-TODO: rename?) +// +gamestate_t wipegamestate = GS_UNKNOWN; +extern boolean setsizeneeded; +//extern int showMessages; [STRIFE] no such variable +void R_ExecuteSetViewSize (void); + +void D_Display (void) +{ + static boolean viewactivestate = false; + static boolean menuactivestate = false; + static boolean inhelpscreensstate = false; + static boolean popupactivestate = false; // [STRIFE] + static boolean fullscreen = false; + static gamestate_t oldgamestate = -1; + static int borderdrawcount; + int nowtime; + int tics; + int wipestart; + int y; + boolean done; + boolean wipe; + boolean redrawsbar; + + if (nodrawers) + return; // for comparative timing / profiling + + redrawsbar = false; + + // change the view size if needed + if (setsizeneeded) + { + R_ExecuteSetViewSize (); + oldgamestate = -1; // force background redraw + borderdrawcount = 3; + } + + // save the current screen if about to wipe + if (gamestate != wipegamestate) + { + wipe = true; + wipe_StartScreen(0, 0, SCREENWIDTH, SCREENHEIGHT); + } + else + wipe = false; + + if (gamestate == GS_LEVEL && gametic) + HU_Erase(); + + // do buffered drawing + switch (gamestate) + { + case GS_LEVEL: + if (!gametic) + break; + if (automapactive) + AM_Drawer (); + if (wipe || (viewheight != 200 && fullscreen) ) + redrawsbar = true; + // haleyjd 08/29/10: [STRIFE] Always redraw sbar if menu is/was active + if (menuactivestate || (inhelpscreensstate && !inhelpscreens)) + redrawsbar = true; // just put away the help screen + ST_Drawer (viewheight == 200, redrawsbar ); + fullscreen = viewheight == 200; + break; + + // haleyjd 08/23/2010: [STRIFE] No intermission + /* + case GS_INTERMISSION: + WI_Drawer (); + break; + */ + + case GS_FINALE: + F_Drawer (); + break; + + case GS_DEMOSCREEN: + D_PageDrawer (); + break; + + default: + break; + } + + // draw buffered stuff to screen + I_UpdateNoBlit (); + + // draw the view directly + if (gamestate == GS_LEVEL && !automapactive && gametic) + R_RenderPlayerView (&players[displayplayer]); + + // clean up border stuff + if (gamestate != oldgamestate && gamestate != GS_LEVEL) + I_SetPalette (W_CacheLumpName (DEH_String("PLAYPAL"),PU_CACHE)); + + // see if the border needs to be initially drawn + if (gamestate == GS_LEVEL && oldgamestate != GS_LEVEL) + { + viewactivestate = false; // view was not active + R_FillBackScreen (); // draw the pattern into the back screen + } + + // see if the border needs to be updated to the screen + if (gamestate == GS_LEVEL && !automapactive && scaledviewwidth != 320) + { + if (menuactive || menuactivestate || !viewactivestate) + { + borderdrawcount = 3; + popupactivestate = false; + } + if (borderdrawcount) + { + R_DrawViewBorder (); // erase old menu stuff + borderdrawcount--; + } + + } + + if (testcontrols) + { + // Box showing current mouse speed + + V_DrawMouseSpeedBox(testcontrols_mousespeed); + } + + menuactivestate = menuactive; + viewactivestate = viewactive; + inhelpscreensstate = inhelpscreens; + oldgamestate = wipegamestate = gamestate; + + // haleyjd 20120208: [STRIFE] Rogue moved this down to below border drawing + if (gamestate == GS_LEVEL && gametic) + { + HU_Drawer (); + if(ST_DrawExternal()) + popupactivestate = true; + else if(popupactivestate) + { + popupactivestate = false; + menuactivestate = 1; + } + } + + // draw pause pic + if (paused) + { + if (automapactive) + y = 4; + else + y = viewwindowy+4; + V_DrawPatchDirect(viewwindowx + (scaledviewwidth - 68) / 2, y, + W_CacheLumpName (DEH_String("M_PAUSE"), PU_CACHE)); + } + + + // menus go directly to the screen + M_Drawer (); // menu is drawn even on top of everything + NetUpdate (); // send out any new accumulation + + + // normal update + if (!wipe) + { + I_FinishUpdate (); // page flip or blit buffer + return; + } + + // wipe update + wipe_EndScreen(0, 0, SCREENWIDTH, SCREENHEIGHT); + + wipestart = I_GetTime () - 1; + + do + { + do + { + nowtime = I_GetTime (); + tics = nowtime - wipestart; + I_Sleep(1); + } while (tics < 3); // haleyjd 08/23/2010: [STRIFE] Changed from == 0 to < 3 + + // haleyjd 08/26/10: [STRIFE] Changed to use ColorXForm wipe. + wipestart = nowtime; + done = wipe_ScreenWipe(wipe_ColorXForm + , 0, 0, SCREENWIDTH, SCREENHEIGHT, tics); + I_UpdateNoBlit (); + M_Drawer (); // menu is drawn even on top of wipes + I_FinishUpdate (); // page flip or blit buffer + } while (!done); +} + +// +// Add configuration file variable bindings. +// + +void D_BindVariables(void) +{ + int i; + + M_ApplyPlatformDefaults(); + + I_BindVideoVariables(); + I_BindJoystickVariables(); + I_BindSoundVariables(); + + M_BindBaseControls(); + M_BindWeaponControls(); + M_BindMapControls(); + M_BindMenuControls(); + M_BindStrifeControls(); // haleyjd 09/01/10: [STRIFE] + M_BindChatControls(MAXPLAYERS); + + // haleyjd 20130915: Strife chat keys + key_multi_msgplayer[0] = '1'; + key_multi_msgplayer[1] = '2'; + key_multi_msgplayer[2] = '3'; + key_multi_msgplayer[3] = '4'; + key_multi_msgplayer[4] = '5'; + key_multi_msgplayer[5] = '6'; + key_multi_msgplayer[6] = '7'; + key_multi_msgplayer[7] = '8'; + +#ifdef FEATURE_MULTIPLAYER + NET_BindVariables(); +#endif + + // haleyjd 08/29/10: [STRIFE] + // * Added voice volume + // * Added back flat + // * Removed show_messages + // * Added show_talk + // fraggle 03/06/10: [STRIFE] + // * Removed detailLevel + // * screenblocks -> screensize + // * Added nickname, comport + + M_BindVariable("mouse_sensitivity", &mouseSensitivity); + M_BindVariable("sfx_volume", &sfxVolume); + M_BindVariable("music_volume", &musicVolume); + M_BindVariable("voice_volume", &voiceVolume); + M_BindVariable("show_talk", &dialogshowtext); + M_BindVariable("screensize", &screenblocks); + M_BindVariable("snd_channels", &snd_channels); + M_BindVariable("vanilla_savegame_limit", &vanilla_savegame_limit); + M_BindVariable("vanilla_demo_limit", &vanilla_demo_limit); + M_BindVariable("show_endoom", &show_endoom); + M_BindVariable("back_flat", &back_flat); + M_BindVariable("graphical_startup", &graphical_startup); + + M_BindVariable("nickname", &nickname); + M_BindVariable("comport", &comport); + + // Multiplayer chat macros + + for (i=0; i<10; ++i) + { + char buf[12]; + + sprintf(buf, "chatmacro%i", i); + M_BindVariable(buf, &chat_macros[i]); + } +} + +// +// D_GrabMouseCallback +// +// Called to determine whether to grab the mouse pointer +// + +boolean D_GrabMouseCallback(void) +{ + // Drone players don't need mouse focus + + if (drone) + return false; + + // when menu is active or game is paused, release the mouse. + + if (menuactive || paused) + return false; + + // only grab mouse when playing levels (but not demos) + + return (gamestate == GS_LEVEL) && !demoplayback; +} + +// During startup, never grab the mouse. + +static boolean D_StartupGrabCallback(void) +{ + return false; +} + +// +// D_DoomLoop +// +// haleyjd 08/23/10: [STRIFE] Verified unmodified. +// +void D_DoomLoop (void) +{ + if (demorecording) + G_BeginRecording (); + + main_loop_started = true; + + TryRunTics(); + + if (!showintro) + { + I_SetWindowTitle(gamedescription); + I_InitGraphics(); + } + + I_EnableLoadingDisk(); + I_SetGrabMouseCallback(D_GrabMouseCallback); + + V_RestoreBuffer(); + R_ExecuteSetViewSize(); + + D_StartGameLoop(); + + if (testcontrols) + { + wipegamestate = gamestate; + } + + while (1) + { + // frame syncronous IO operations + I_StartFrame (); + + // process one or more tics + TryRunTics (); // will run at least one tic + + S_UpdateSounds (players[consoleplayer].mo);// move positional sounds + + // Update display, next frame, with current state. + if (screenvisible) + D_Display (); + } +} + + + +// +// DEMO LOOP +// +int demosequence; +int pagetic; +char *pagename; + + +// +// D_PageTicker +// Handles timing for warped projection +// +// haleyjd 08/22/2010: [STRIFE] verified unmodified +// +void D_PageTicker (void) +{ + if (--pagetic < 0) + D_AdvanceDemo (); +} + + + +// +// D_PageDrawer +// +// haleyjd 08/22/2010: [STRIFE] verified unmodified +// +void D_PageDrawer (void) +{ + V_DrawPatch (0, 0, W_CacheLumpName(pagename, PU_CACHE)); +} + + +// +// D_AdvanceDemo +// Called after each demo or intro demosequence finishes +// +// haleyjd 08/22/2010: [STRIFE] verified unmodified +// +void D_AdvanceDemo (void) +{ + advancedemo = true; +} + + +// +// This cycles through the demo sequences. +// FIXME - version dependend demo numbers? +// +// [STRIFE] Modified for the opening slideshow and the exit screen +// +void D_DoAdvanceDemo (void) +{ + players[consoleplayer].playerstate = PST_LIVE; // not reborn + advancedemo = false; + usergame = false; // no save / end game here + paused = false; + gameaction = ga_nothing; + + // villsa 09/12/10: [STRIFE] converted pagetics to ticrate + switch (demosequence) + { + case -5: // exit the game + I_Quit(); + return; + case -4: // show exit screen + menuactive = false; + pagetic = 3*TICRATE; + gamestate = GS_DEMOSCREEN; + pagename = DEH_String("PANEL7"); + S_StartMusic(mus_fast); + if(isdemoversion) + demosequence = -3; // show Velocity logo + else + demosequence = -5; // exit + return; + case -3: // show Velocity logo for demo version + pagetic = 6*TICRATE; + gamestate = GS_DEMOSCREEN; + pagename = DEH_String("vellogo"); + demosequence = -5; // exit + return; + case -2: // title screen + pagetic = 6*TICRATE; + gamestate = GS_DEMOSCREEN; + pagename = DEH_String("TITLEPIC"); + S_StartMusic(mus_logo); + demosequence = -1; // start intro cinematic + return; + case -1: // start of intro cinematic + pagetic = 10; + gamestate = GS_DEMOSCREEN; + pagename = DEH_String("PANEL0"); + S_StartSound(NULL, sfx_rb2act); + wipegamestate = -1; + break; + case 0: // Rogue logo + pagetic = 4*TICRATE; + gamestate = GS_DEMOSCREEN; + pagename = DEH_String("RGELOGO"); + wipegamestate = -1; + break; + case 1: + pagetic = 7*TICRATE; // The comet struck our planet without + gamestate = GS_DEMOSCREEN; // warning.We lost our paradise in a + pagename = DEH_String("PANEL1"); // single, violent stroke. + I_StartVoice(DEH_String("pro1")); + S_StartMusic(mus_intro); + break; + case 2: + pagetic = 9*TICRATE; // The impact released a virus which + gamestate = GS_DEMOSCREEN; // swept through the land and killed + pagename = DEH_String("PANEL2"); // millions. They turned out to be + I_StartVoice(DEH_String("pro2")); // the lucky ones... + break; + case 3: + pagetic = 12*TICRATE; // For those that did not die became + gamestate = GS_DEMOSCREEN; // mutations of humanity. Some became + pagename = DEH_String("PANEL3"); // fanatics who heard the voice of a + I_StartVoice(DEH_String("pro3")); // malignant God in their heads, and + break; // called themselves the Order. + case 4: + pagetic = 11*TICRATE; // Those of us who were deaf to this + pagename = DEH_String("PANEL4"); // voice suffer horribly and are + gamestate = GS_DEMOSCREEN; // forced to serve these ruthless + I_StartVoice(DEH_String("pro4")); // psychotics, who wield weapons more + break; // powerful than anything we can muster. + case 5: + pagetic = 10*TICRATE; // They destroy our women and children, + gamestate = GS_DEMOSCREEN; // so that we must hide them underground, + pagename = DEH_String("PANEL5"); // and live like animals in constant + I_StartVoice(DEH_String("pro5")); // fear for our lives. + break; + case 6: // But there are whispers of discontent. + pagetic = 16*TICRATE; // If we organize, can we defeat our + gamestate = GS_DEMOSCREEN; // masters? Weapons are being stolen, + pagename = DEH_String("PANEL6"); // soldiers are being trained. A + I_StartVoice(DEH_String("pro6")); // Movement is born! Born of lifelong + break; // STRIFE! + case 7: // titlepic again - unused... + pagetic = 9*TICRATE; + gamestate = GS_DEMOSCREEN; + pagename = DEH_String("TITLEPIC"); + wipegamestate = -1; + break; + case 8: // demo + ClearTmp(); + pagetic = 9*TICRATE; + G_DeferedPlayDemo(DEH_String("demo1")); + break; + case 9: // velocity logo? - unused... + pagetic = 6*TICRATE; + gamestate = GS_DEMOSCREEN; + pagename = DEH_String("vellogo"); + wipegamestate = -1; + break; + case 10: // credits + gamestate = GS_DEMOSCREEN; + pagetic = 12*TICRATE; + pagename = DEH_String("CREDIT"); + wipegamestate = -1; + break; + default: + break; + } + + ++demosequence; + + if(demosequence > 11) + demosequence = -2; + if(demosequence == 7 || demosequence == 9) + ++demosequence; +} + + + +// +// D_StartTitle +// +// [STRIFE] +// haleyjd 09/11/10: Small modifications for new demo sequence. +// +void D_StartTitle (void) +{ + gamestate = GS_DEMOSCREEN; + gameaction = ga_nothing; + demosequence = -2; + D_AdvanceDemo (); +} + +// +// D_QuitGame +// +// [STRIFE] New function +// haleyjd 09/11/10: Sets up the quit game snippet powered by the +// demo sequence. +// +void D_QuitGame(void) +{ + gameaction = ga_nothing; + demosequence = -4; + D_AdvanceDemo(); +} + +// Strings for dehacked replacements of the startup banner +// +// These are from the original source: some of them are perhaps +// not used in any dehacked patches + +static char *banners[] = +{ + // strife1.wad: + + " " + "STRIFE: Quest for the Sigil v1.2" + " " +}; + +// +// Get game name: if the startup banner has been replaced, use that. +// Otherwise, use the name given +// + +static char *GetGameName(char *gamename) +{ + size_t i; + char *deh_sub; + + for (i=0; i<arrlen(banners); ++i) + { + // Has the banner been replaced? + + deh_sub = DEH_String(banners[i]); + + if (deh_sub != banners[i]) + { + // Has been replaced + // We need to expand via printf to include the Doom version + // number + // We also need to cut off spaces to get the basic name + + gamename = Z_Malloc(strlen(deh_sub) + 10, PU_STATIC, 0); + sprintf(gamename, deh_sub, STRIFE_VERSION / 100, STRIFE_VERSION % 100); + + while (gamename[0] != '\0' && isspace(gamename[0])) + strcpy(gamename, gamename+1); + + while (gamename[0] != '\0' && isspace(gamename[strlen(gamename)-1])) + gamename[strlen(gamename) - 1] = '\0'; + + return gamename; + } + } + + return gamename; +} + +// +// Find out what version of Doom is playing. +// + +void D_IdentifyVersion(void) +{ + // gamemission is set up by the D_FindIWAD function. But if + // we specify '-iwad', we have to identify using + // IdentifyIWADByName. However, if the iwad does not match + // any known IWAD name, we may have a dilemma. Try to + // identify by its contents. + + // STRIFE-TODO: some elaborate checks? for now we assume... + // The logic in strife1.exe is simple: + // * if strife1.wad is found, set isregistered = true + // * if strife0.wad is found, set isdemoversion = true + + // Make sure gamemode is set up correctly + gamemode = commercial; + gamemission = strife; + isregistered = true; + + // Load voices.wad + if(isregistered) + { + char *name = D_FindWADByName("voices.wad"); + + if(!name) // not found? + { + int p; + + // haleyjd STRIFE-FIXME: Temporary? + // If -iwad was used, check and see if voices.wad exists on the + // same filepath. + if((p = M_CheckParm("-iwad")) && p < myargc - 1) + { + char *iwad = myargv[p + 1]; + size_t len = strlen(iwad) + 24; + char *filename = malloc(len); + char sepchar; + + // how the heck is Choco surviving without this routine? + sepchar = M_GetFilePath(iwad, filename, len); + filename[strlen(filename)] = sepchar; + strcat(filename, "voices.wad"); + + if(!M_FileExists(filename)) + disable_voices = 1; + else + name = filename; // STRIFE-FIXME: memory leak!! + } + else + disable_voices = 1; + } + + if(disable_voices) // voices disabled? + { + if(devparm) + printf("Voices disabled\n"); + return; + } + + D_AddFile(name); + } +} + +#if 0 +// +// DoTimeBomb +// +// haleyjd 08/23/2010: [STRIFE] New function +// Code with no xrefs; probably left over from a private alpha or beta. +// Translated here because it explains what the SERIAL lump was meant to do. +// +void DoTimeBomb(void) +{ + dosdate_t date; + char *serial; + int serialnum; + int serial_year; + int serial_month; + + serial = W_CacheLumpName("serial", PU_CACHE); + serialnum = atoi(serial); + + // Rogue, much like Governor Mourel, were lousy liars. These deceptive + // error messages are pretty low :P + dos_getdate(&date); + if(date.year > 1996 || date.day > 15 && date.month > 4) + I_Error("Data error! Corrupted WAD File!"); + serial_year = serialnum / 10000; + serial_month = serialnum / 100 - 100 * serial_year; + if(date.year < serial_year || + date.day < serialnum - 100 * serial_month - 10000 * serial_year && + date.month < serial_month) + I_Error("Bad wadfile"); +} +#endif + +// Set the gamedescription string + +void D_SetGameDescription(void) +{ + gamedescription = GetGameName("Strife: Quest for the Sigil"); +} + +// print title for every printed line +char title[128]; + +static boolean D_AddFile(char *filename) +{ + wad_file_t *handle; + + printf(" adding %s\n", filename); + handle = W_AddFile(filename); + + return handle != NULL; +} + +// Copyright message banners +// Some dehacked mods replace these. These are only displayed if they are +// replaced by dehacked. +// haleyjd 08/22/2010: [STRIFE] altered to match strings from binary +static char *copyright_banners[] = +{ + "===========================================================================\n" + "ATTENTION: This version of STRIFE has extra files added to it.\n" + " You will not receive technical support for modified games.\n" + "===========================================================================\n", + + "===========================================================================\n" + " This version is NOT SHAREWARE, do not distribute!\n" + " Please report software piracy to the SPA: 1-800-388-PIR8\n" + "===========================================================================\n", + + "===========================================================================\n" + " Shareware!\n" + "===========================================================================\n" +}; + +// Prints a message only if it has been modified by dehacked. + +void PrintDehackedBanners(void) +{ + size_t i; + + for (i=0; i<arrlen(copyright_banners); ++i) + { + char *deh_s; + + deh_s = DEH_String(copyright_banners[i]); + + if (deh_s != copyright_banners[i]) + { + printf("%s", deh_s); + + // Make sure the modified banner always ends in a newline character. + // If it doesn't, add a newline. This fixes av.wad. + + if (deh_s[strlen(deh_s) - 1] != '\n') + { + printf("\n"); + } + } + } +} + +static struct +{ + char *description; + char *cmdline; + GameVersion_t version; +} gameversions[] = { + { "Strife 1.2", "1.2", exe_strife_1_2 }, + { "Strife 1.31", "1.31", exe_strife_1_31 }, + { NULL, NULL, 0 } +}; + +// Initialize the game version + +static void InitGameVersion(void) +{ + int p; + int i; + + // haleyjd: we support emulating either the 1.2 or the 1.31 versions of + // Strife, which are the most significant. 1.2 is the most mature version + // that still has the single saveslot restriction, whereas 1.31 is the + // final revision. The differences between the two are barely worth + // mentioning aside from that main one. + + //! + // @arg <version> + // @category compat + // + // Emulate a specific version of Strife. Valid values are "1.2" and "1.31". + // + + p = M_CheckParmWithArgs("-gameversion", 1); + + if (p) + { + for (i=0; gameversions[i].description != NULL; ++i) + { + if (!strcmp(myargv[p+1], gameversions[i].cmdline)) + { + gameversion = gameversions[i].version; + break; + } + } + + if (gameversions[i].description == NULL) + { + printf("Supported game versions:\n"); + + for (i=0; gameversions[i].description != NULL; ++i) + { + printf("\t%s (%s)\n", gameversions[i].cmdline, + gameversions[i].description); + } + + I_Error("Unknown game version '%s'", myargv[p+1]); + } + } + else + { + gameversion = exe_strife_1_31; + } +} + +void PrintGameVersion(void) +{ + int i; + + for (i=0; gameversions[i].description != NULL; ++i) + { + if (gameversions[i].version == gameversion) + { + printf("Emulating the behavior of the " + "'%s' executable.\n", gameversions[i].description); + break; + } + } +} + +// Function called at exit to display the ENDOOM screen + +static void D_Endoom(void) +{ + byte *endoom; + + // Don't show ENDOOM if we have it disabled, or we're running + // in screensaver or control test mode. Only show it once the + // game has actually started. + + + if (!show_endoom || !main_loop_started || screensaver_mode || testcontrols) + { + return; + } + + // haleyjd 08/27/10: [STRIFE] ENDOOM -> ENDSTRF + endoom = W_CacheLumpName(DEH_String("ENDSTRF"), PU_STATIC); + + I_Endoom(endoom); +} + +//============================================================================= +// +// haleyjd: Chocolate Strife Specifics +// +// None of the code in here is from the original executable, but is needed for +// other reasons. + +// +// D_PatchClipCallback +// +// haleyjd 08/28/10: Clip patches to the framebuffer without errors. +// Returns false if V_DrawPatch should return without drawing. +// +boolean D_PatchClipCallback(patch_t *patch, int x, int y) +{ + // note that offsets were already accounted for in V_DrawPatch + return (x >= 0 && y >= 0 + && x + SHORT(patch->width) <= SCREENWIDTH + && y + SHORT(patch->height) <= SCREENHEIGHT); +} + +// +// D_InitChocoStrife +// +// haleyjd 08/28/10: Take care of some Strife-specific initialization +// that is necessitated by Chocolate Doom issues, such as setting global +// callbacks. +// +static void D_InitChocoStrife(void) +{ + // set the V_DrawPatch clipping callback + V_SetPatchClipCallback(D_PatchClipCallback); +} + + +// +// STRIFE Graphical Intro Sequence +// + +#define MAXINTROPROGRESS 69 + +static int introprogress; // track the progress of the intro + +static byte *rawgfx_startup0; // raw linear gfx for intro +static byte *rawgfx_startp[4]; +static byte *rawgfx_startlz[2]; +static byte *rawgfx_startbot; + +// +// D_IntroBackground +// +// [STRIFE] New function +// haleyjd 20110206: Strife only drew this once, but for supporting double- +// buffered or page-flipped surfaces it is best to redraw the entire screen +// every frame. +// +static void D_IntroBackground(void) +{ + if(!showintro) + return; + + // Fill the background entirely (wasn't needed in vanilla) + V_DrawFilledBox(0, 0, SCREENWIDTH, SCREENHEIGHT, 0); + + // Strife cleared the screen somewhere in the low-level code between the + // intro and the titlescreen, so this is to take care of that and get + // proper fade-in behavior on the titlescreen + if(introprogress >= MAXINTROPROGRESS) + { + I_FinishUpdate(); + return; + } + + // Draw a 95-pixel rect from STARTUP0 starting at y=57 to (0,41) on the + // screen (this was a memcpy directly to 0xA3340 in low DOS memory) + V_DrawBlock(0, 41, 320, 95, rawgfx_startup0 + (320*57)); +} + +// +// D_InitIntroSequence +// +// [STRIFE] New function +// haleyjd 20110206: Initialize the graphical introduction sequence +// + +static void D_InitIntroSequence(void) +{ + if(showintro) + { + // In vanilla Strife, Mode 13h was initialized directly in D_DoomMain. + // We have to be a little more courteous of the low-level code here. + I_SetWindowTitle(gamedescription); + I_SetGrabMouseCallback(D_StartupGrabCallback); + I_InitGraphics(); + V_RestoreBuffer(); // make the V_ routines work + + // Load all graphics + rawgfx_startup0 = W_CacheLumpName("STARTUP0", PU_STATIC); + rawgfx_startp[0] = W_CacheLumpName("STRTPA1", PU_STATIC); + rawgfx_startp[1] = W_CacheLumpName("STRTPB1", PU_STATIC); + rawgfx_startp[2] = W_CacheLumpName("STRTPC1", PU_STATIC); + rawgfx_startp[3] = W_CacheLumpName("STRTPD1", PU_STATIC); + rawgfx_startlz[0] = W_CacheLumpName("STRTLZ1", PU_STATIC); + rawgfx_startlz[1] = W_CacheLumpName("STRTLZ2", PU_STATIC); + rawgfx_startbot = W_CacheLumpName("STRTBOT", PU_STATIC); + + // Draw the background + D_IntroBackground(); + } + /* + // STRIFE-FIXME: This was actually displayed on a special textmode + // screen with ANSI color codes... would require use of textlib to + // emulate properly... + else + { + puts(DEH_String("Conversation ON")); + puts(DEH_String("Rogue Entertainment")); + puts(DEH_String("and")); + puts(DEH_String("Velocity Games")); + puts(DEH_String("present")); + puts(DEH_String("S T R I F E")); + puts(DEH_String("Loading...")); + } + */ +} + +// +// D_DrawIntroSequence +// +// [STRIFE] New function +// haleyjd 20110206: Refresh the intro sequence +// +static void D_DrawIntroSequence(void) +{ + int laserpos; + int robotpos; + + if(!showintro) + return; + + D_IntroBackground(); // haleyjd: refresh the background + + // Laser position + laserpos = (200 * introprogress / MAXINTROPROGRESS) + 60; + + // BUG: (?) Due to this clip, the laser never even comes close to + // touching the peasant; confirmed with vanilla. This MAY have been + // intentional, for effect, however, since no death frames are shown + // either... kind of a black-out death. + if(laserpos > 200) + laserpos = 200; + + // Draw the laser + // Blitted 16 bytes for 16 rows starting at 705280 + laserpos + // (705280 - 0xA0000) / 320 == 156 + V_DrawBlock(laserpos, 156, 16, 16, rawgfx_startlz[laserpos % 2]); + + // Robot position + robotpos = laserpos % 5 - 2; + + // Draw the robot + // Blitted 48 bytes for 48 rows starting at 699534 + (320*robotpos) + // 699534 - 0xA0000 == 44174, which % 320 == 14, / 320 == 138 + V_DrawBlock(14, 138 + robotpos, 48, 48, rawgfx_startbot); + + // Draw the peasant + // Blitted 32 bytes for 64 rows starting at 699142 + // 699142 - 0xA0000 == 43782, which % 320 == 262, / 320 == 136 + V_DrawBlock(262, 136, 32, 64, rawgfx_startp[laserpos % 4]); + + I_FinishUpdate(); +} + +// +// D_IntroTick +// +// Advance the intro sequence +// +void D_IntroTick(void) +{ + static boolean didsound = false; // haleyjd 20120209 + + if(devparm) + return; + + ++introprogress; + if(introprogress >= MAXINTROPROGRESS) + { + D_IntroBackground(); // haleyjd: clear the bg anyway + + // haleyjd 20120209: This isn't 100% true to vanilla because vanilla + // would play this sound a half-dozen times. BUT, in vanilla, for + // whatever reason, under DMX, playing the same sound multiple times + // doesn't add up violently like it does under SDL_mixer. This means + // that without this one-time limitation, the sound is far too loud. + if(!didsound) + { + S_StartSound(NULL, sfx_psdtha); + didsound = true; + } + } + else + D_DrawIntroSequence(); +} + +// +// End Chocolate Strife Specifics +// +//============================================================================= + +// +// D_DoomMain +// +void D_DoomMain (void) +{ + int p; + char file[256]; + char demolumpname[9]; + + I_AtExit(D_Endoom, false); + + // haleyjd 20110206 [STRIFE]: -nograph parameter + + //! + // @vanilla + // + // Disable graphical introduction sequence + // + + if (M_ParmExists("-nograph")) + showintro = false; + + // Undocumented: + // Invoked by setup to test the controls. + + if (M_ParmExists("-testcontrols")) + { + testcontrols = true; + showintro = false; + } + + // haleyjd 20110206: Moved up -devparm for max visibility + + //! + // @vanilla + // + // Developer mode. Implies -nograph. + // + + devparm = M_CheckParm ("-devparm"); + + // print banner + + I_PrintBanner(PACKAGE_STRING); + + //DEH_printf("Z_Init: Init zone memory allocation daemon. \n"); [STRIFE] removed + Z_Init (); + +#ifdef FEATURE_MULTIPLAYER + //! + // @category net + // + // Start a dedicated server, routing packets but not participating + // in the game itself. + // + + if (M_CheckParm("-dedicated") > 0) + { + printf("Dedicated server mode.\n"); + NET_DedicatedServer(); + + // Never returns + } + + //! + // @category net + // + // Query the Internet master server for a global list of active + // servers. + // + + if (M_CheckParm("-search")) + { + NET_MasterQuery(); + exit(0); + } + + //! + // @arg <address> + // @category net + // + // Query the status of the server running on the given IP + // address. + // + + p = M_CheckParmWithArgs("-query", 1); + + if (p) + { + NET_QueryAddress(myargv[p+1]); + exit(0); + } + + //! + // @category net + // + // Search the local LAN for running servers. + // + + if (M_CheckParm("-localsearch")) + { + NET_LANQuery(); + exit(0); + } + +#endif + +#ifdef FEATURE_DEHACKED + if(devparm) + printf("DEH_Init: Init Dehacked support.\n"); + DEH_Init(); +#endif + + iwadfile = D_FindIWAD(IWAD_MASK_STRIFE, &gamemission); + + // None found? + + if (iwadfile == NULL) + { + I_Error("Game mode indeterminate. No IWAD file was found. Try\n" + "specifying one with the '-iwad' command line parameter.\n"); + } + + modifiedgame = false; + + //! + // @vanilla + // + // Disable monsters. + // + + nomonsters = M_CheckParm ("-nomonsters"); + + //! + // @vanilla + // + // Set Rogue playtesting mode (godmode, noclip toggled by backspace) + // + + workparm = M_CheckParm ("-work"); + + //! + // @vanilla + // + // Flip player gun sprites (broken). + // + + flipparm = M_CheckParm ("-flip"); + + //! + // @vanilla + // + // Respawn monsters after they are killed. + // + + respawnparm = M_CheckParm ("-respawn"); + + //! + // @vanilla + // + // Items respawn at random locations + // + + randomparm = M_CheckParm ("-random"); + + //! + // @vanilla + // + // Monsters move faster. + // + + fastparm = M_CheckParm ("-fast"); + + I_DisplayFPSDots(devparm); + + // haleyjd 20110206 [STRIFE]: -devparm implies -nograph + if(devparm) + showintro = false; + + // Note: Vanilla Strife does not understand the -deathmatch command + // line parameter. deathmatch=1 is the default behavior when + // playing a netgame. + + //! + // @category net + // @vanilla + // + // Start a deathmatch game. Weapons do not stay in place and + // all items respawn after 30 seconds. + // + + if (M_CheckParm ("-altdeath")) + deathmatch = 2; + + if (devparm) + DEH_printf(D_DEVSTR); + + // find which dir to use for config files + +#ifdef _WIN32 + + //! + // @platform windows + // @vanilla + // + // Save configuration data and savegames in c:\strife.cd, + // allowing play from CD. + // + + if (M_CheckParm("-cdrom") > 0) + { + printf(D_CDROM); + + // haleyjd 08/22/2010: [STRIFE] Use strife.cd folder for -cdrom + M_SetConfigDir("c:\\strife.cd\\"); + } + else +#endif + { + // Auto-detect the configuration dir. + + M_SetConfigDir(NULL); + } + + //! + // @arg <x> + // @vanilla + // + // Turbo mode. The player's speed is multiplied by x%. If unspecified, + // x defaults to 200. Values are rounded up to 10 and down to 400. + // + + if ( (p=M_CheckParm ("-turbo")) ) + { + int scale = 200; + extern int forwardmove[2]; + extern int sidemove[2]; + + if (p<myargc-1) + scale = atoi (myargv[p+1]); + if (scale < 10) + scale = 10; + if (scale > 400) + scale = 400; + DEH_printf("turbo scale: %i%%\n", scale); + forwardmove[0] = forwardmove[0]*scale/100; + forwardmove[1] = forwardmove[1]*scale/100; + sidemove[0] = sidemove[0]*scale/100; + sidemove[1] = sidemove[1]*scale/100; + } + + // init subsystems + // DEH_printf("V_Init: allocate screens.\n"); [STRIFE] removed + V_Init (); + + // Load configuration files before initialising other subsystems. + // haleyjd 08/22/2010: [STRIFE] - use strife.cfg + // DEH_printf("M_LoadDefaults: Load system defaults.\n"); [STRIFE] removed + M_SetConfigFilenames("strife.cfg", PROGRAM_PREFIX "strife.cfg"); + D_BindVariables(); + M_LoadDefaults(); + + if (!graphical_startup) + { + showintro = false; + } + + // Save configuration at exit. + I_AtExit(M_SaveDefaults, false); + + if(devparm) // [STRIFE] Devparm only + DEH_printf("W_Init: Init WADfiles.\n"); + D_AddFile(iwadfile); + modifiedgame = W_ParseCommandLine(); + + // [STRIFE] serial number output + if(devparm) + { + char msgbuf[80]; + char *serial = W_CacheLumpName("SERIAL", PU_CACHE); + int serialnum = atoi(serial); + + DEH_snprintf(msgbuf, sizeof(msgbuf), "Wad Serial Number: %d:", serialnum); + printf("%s\n", msgbuf); + } + + // add any files specified on the command line with -file wadfile + // to the wad list + // + + // Debug: +// W_PrintDirectory(); + + //! + // @arg <demo> + // @category demo + // @vanilla + // + // Play back the demo named demo.lmp. + // + + p = M_CheckParmWithArgs ("-playdemo", 1); + + if (!p) + { + //! + // @arg <demo> + // @category demo + // @vanilla + // + // Play back the demo named demo.lmp, determining the framerate + // of the screen. + // + p = M_CheckParmWithArgs("-timedemo", 1); + + } + + if (p) + { + if (!strcasecmp(myargv[p+1] + strlen(myargv[p+1]) - 4, ".lmp")) + { + strcpy(file, myargv[p + 1]); + } + else + { + sprintf (file,"%s.lmp", myargv[p+1]); + } + + if (D_AddFile (file)) + { + strncpy(demolumpname, lumpinfo[numlumps - 1].name, 8); + demolumpname[8] = '\0'; + + printf("Playing demo %s.\n", file); + } + else + { + // If file failed to load, still continue trying to play + // the demo in the same way as Vanilla Doom. This makes + // tricks like "-playdemo demo1" possible. + + strncpy(demolumpname, myargv[p + 1], 8); + demolumpname[8] = '\0'; + } + + } + + I_AtExit((atexit_func_t) G_CheckDemoStatus, true); + + // Generate the WAD hash table. Speed things up a bit. + + W_GenerateHashTable(); + + D_IdentifyVersion(); + InitGameVersion(); + D_SetGameDescription(); + savegamedir = M_GetSaveGameDir("strife1.wad"); + + // fraggle 20130405: I_InitTimer is needed here for the netgame + // startup. + I_InitTimer(); + +#ifdef FEATURE_MULTIPLAYER + if(devparm) // [STRIFE] + printf ("NET_Init: Init network subsystem.\n"); + NET_Init(); +#endif + D_ConnectNetGame(); + + // haleyjd 20110210: Create Strife hub save folders + M_CreateSaveDirs(savegamedir); + + I_GraphicsCheckCommandLine(); + + // haleyjd 20110206 [STRIFE] Startup the introduction sequence + D_InitIntroSequence(); + + // haleyjd 20110924: moved S_Init up to here + if(devparm) // [STRIFE] + DEH_printf("S_Init: Setting up sound.\n"); + S_Init (sfxVolume * 8, musicVolume * 8, voiceVolume * 8); // [STRIFE]: voice + D_IntroTick(); // [STRIFE] + + // Check for -file in shareware + if (modifiedgame) + { + // These are the lumps that will be checked in IWAD, + // if any one is not present, execution will be aborted. + // haleyjd 08/22/2010: [STRIFE] Check for Strife lumps. + char name[3][8]= + { + "map23", "map30", "ROB3E1" + }; + int i; + + // haleyjd 08/22/2010: [STRIFE] Changed string to match binary + // STRIFE-FIXME: Needs to test isdemoversion variable + if ( gamemode == shareware) + I_Error(DEH_String("\nYou cannot -file with the demo " + "version. You must buy the real game!")); + + // Check for fake IWAD with right name, + // but w/o all the lumps of the registered version. + // STRIFE-FIXME: Needs to test isregistered variable + if (gamemode == registered) + for (i = 0; i < 3; i++) + if (W_CheckNumForName(name[i])<0) + I_Error(DEH_String("\nThis is not the registered version.")); + } + + D_IntroTick(); // [STRIFE] + + // get skill / episode / map from parms + startskill = sk_medium; + startepisode = 1; + startmap = 1; + autostart = false; + + //! + // @arg <skill> + // @vanilla + // + // Set the game skill, 1-5 (1: easiest, 5: hardest). A skill of + // 0 disables all monsters. + // + + p = M_CheckParmWithArgs("-skill", 1); + + if (p) + { + startskill = myargv[p+1][0]-'1'; + autostart = true; + } + + // [STRIFE] no such thing in Strife + // + // // @arg <n> + // // @vanilla + // // + // // Start playing on episode n (1-4) + // // + + // p = M_CheckParmWithArgs("-episode", 1); + + // if (p) + // { + // startepisode = myargv[p+1][0]-'0'; + // startmap = 1; + // autostart = true; + // } + + timelimit = 0; + + //! + // @arg <n> + // @category net + // @vanilla + // + // For multiplayer games: exit each level after n minutes. + // + + p = M_CheckParmWithArgs("-timer", 1); + + if (p) + { + timelimit = atoi(myargv[p+1]); + printf("timer: %i\n", timelimit); + } + + //! + // @category net + // @vanilla + // + // Austin Virtual Gaming: end levels after 20 minutes. + // + + p = M_CheckParm ("-avg"); + + if (p) + { + timelimit = 20; + } + + //! + // @arg x + // @vanilla + // + // Start a game immediately, warping to level x. + // + + p = M_CheckParmWithArgs("-warp", 1); + + if (p) + { + if (gamemode == commercial) + startmap = atoi (myargv[p+1]); + else + { + startepisode = myargv[p+1][0]-'0'; + + if (p + 2 < myargc) + { + startmap = myargv[p+2][0]-'0'; + } + else + { + startmap = 1; + } + } + autostart = true; + } + + if (testcontrols) + { + startepisode = 1; + startmap = 3; + autostart = true; + } + + // Check for load game parameter + // We do this here and save the slot number, so that the network code + // can override it or send the load slot to other players. + + //! + // @arg <s> + // @vanilla + // + // Load the game in slot s. + // + + p = M_CheckParmWithArgs("-loadgame", 1); + + if (p) + { + startloadgame = atoi(myargv[p+1]); + } + else + { + // Not loading a game + startloadgame = -1; + } + + if (W_CheckNumForName("SS_START") >= 0 + || W_CheckNumForName("FF_END") >= 0) + { + I_PrintDivider(); + printf(" WARNING: The loaded WAD file contains modified sprites or\n" + " floor textures. You may want to use the '-merge' command\n" + " line option instead of '-file'.\n"); + } + + I_PrintStartupBanner(gamedescription); + PrintDehackedBanners(); + + // haleyjd 08/28/10: Init Choco Strife stuff. + D_InitChocoStrife(); + + // haleyjd 08/22/2010: [STRIFE] Modified string to match binary + if(devparm) // [STRIFE] + DEH_printf("R_Init: Loading Graphics - "); + R_Init (); + D_IntroTick(); // [STRIFE] + + if(devparm) // [STRIFE] + DEH_printf("\nP_Init: Init Playloop state.\n"); + P_Init (); + D_IntroTick(); // [STRIFE] + + if(devparm) // [STRIFE] + DEH_printf("I_Init: Setting up machine state.\n"); + I_CheckIsScreensaver(); + I_InitJoystick(); + D_IntroTick(); // [STRIFE] + + D_IntroTick(); // [STRIFE] + + if(devparm) // [STRIFE] + DEH_printf("M_Init: Init Menu.\n"); + M_Init (); + D_IntroTick(); // [STRIFE] + + // haleyjd 20110924: Moved S_Init up. + D_IntroTick(); + + // haleyjd 20110220: This stuff was done in I_StartupSound in vanilla, but + // we'll do it here instead so we don't have to modify the low-level shared + // code with Strife-specific stuff. + + //! + // @vanilla + // + // Disable voice dialog and show dialog as text instead, + // even if voices.wad can be found. + // + + if(disable_voices || M_CheckParm("-novoice")) + { + dialogshowtext = disable_voices = 1; + } + + if(devparm) + DEH_printf(" Play voices = %d\n", disable_voices == 0); + + if(devparm) // [STRIFE] + DEH_printf("D_CheckNetGame: Checking network game status.\n"); + D_CheckNetGame (); + + PrintGameVersion(); + + if(devparm) + DEH_printf("HU_Init: Setting up heads up display.\n"); + HU_Init (); + D_IntroTick(); // [STRIFE] + + if(devparm) + DEH_printf("ST_Init: Init status bar.\n"); + ST_Init (); + D_IntroTick(); // [STRIFE] + + // haleyjd [STRIFE] -statcopy used to be here... + D_IntroTick(); // [STRIFE] + + // If Doom II without a MAP01 lump, this is a store demo. + // Moved this here so that MAP01 isn't constantly looked up + // in the main loop. + // haleyjd 08/23/2010: [STRIFE] There is no storedemo version of Strife + /* + if (gamemode == commercial && W_CheckNumForName("map01") < 0) + storedemo = true; + */ + + //! + // @arg <x> + // @category demo + // @vanilla + // + // Record a demo named x.lmp. + // + + p = M_CheckParmWithArgs("-record", 1); + + if (p) + { + G_RecordDemo (myargv[p+1]); + autostart = true; + } + D_IntroTick(); // [STRIFE] + + p = M_CheckParmWithArgs("-playdemo", 1); + if (p) + { + singledemo = true; // quit after one demo + G_DeferedPlayDemo (demolumpname); + D_DoomLoop (); // never returns + } + D_IntroTick(); // [STRIFE] + + p = M_CheckParmWithArgs("-timedemo", 1); + if (p) + { + G_TimeDemo (demolumpname); + D_DoomLoop (); // never returns + } + D_IntroTick(); // [STRIFE] + + if (startloadgame >= 0) + { + // [STRIFE]: different, for hubs + M_LoadSelect(startloadgame); + } + D_IntroTick(); // [STRIFE] + + + if (gameaction != ga_loadgame ) + { + if (autostart || netgame) + G_InitNew (startskill, startmap); + else + D_StartTitle (); // start up intro loop + } + + D_DoomLoop (); // never returns +} |