/* Emacs style mode select -*- C++ -*- *----------------------------------------------------------------------------- * * * PrBoom a Doom port merged with LxDoom and LSDLDoom * based on BOOM, a modified and improved DOOM engine * Copyright (C) 1999 by * id Software, Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman * Copyright (C) 1999-2000 by * Jess Haas, Nicolas Kalkhof, Colin Phipps, Florian Schulze * * 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 "rockmacros.h" #include "doomdef.h" #include "doomtype.h" #include "doomstat.h" #include "dstrings.h" #include "sounds.h" #include "z_zone.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_misc.h" #include "m_menu.h" #include "i_system.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_draw.h" #include "r_main.h" #include "d_main.h" #include "d_deh.h" // Ty 04/08/98 - Externalizations #include "am_map.h" #include "m_swap.h" // DEHacked support - Ty 03/09/97 // CPhipps - const char*'s void ProcessDehFile(const char *filename, const char *outfilename, int lumpnum); // CPhipps - removed wadfiles[] stuff boolean devparm; // started game with -devparm // jff 1/24/98 add new versions of these variables to remember command line boolean clnomonsters; // checkparm of -nomonsters boolean clrespawnparm; // checkparm of -respawn boolean clfastparm; // checkparm of -fast // jff 1/24/98 end definition of command line version of play mode switches boolean nomonsters; // working -nomonsters boolean respawnparm; // working -respawn boolean fastparm; // working -fast boolean dehout=true; boolean singletics = false; // debug flag to cancel adaptiveness bool doomexit; //jff 1/22/98 parms for disabling music and sound boolean nomusicparm=0; //jff 4/18/98 extern boolean inhelpscreens; skill_t startskill; int startepisode; int startmap; boolean autostart; int debugfile; int ffmap; boolean advancedemo; extern boolean timingdemo, singledemo, demoplayback, fastdemo; // killough int basetic; void D_DoAdvanceDemo (void); /* * D_PostEvent - Event handling * * Called by I/O functions when an event is received. * Try event handlers for each code area in turn. */ void D_PostEvent(event_t *ev) { /* cph - suppress all input events at game start * FIXME: This is a lousy kludge */ if (gametic < 3) return; if(!M_Responder(ev)) { if(gamestate == GS_LEVEL && ( HU_Responder(ev) || ST_Responder(ev) || AM_Responder(ev) )) return; else G_Responder(ev); } } // // D_Wipe // // CPhipps - moved the screen wipe code from D_Display to here // The screens to wipe between are already stored, this just does the timing // and screen updating static void D_Wipe(void) { boolean done; int wipestart = I_GetTime () - 1; do { int nowtime, tics; do { //I_uSleep(5000); // CPhipps - don't thrash cpu in this loop nowtime = I_GetTime(); tics = nowtime - wipestart; } while (!tics); wipestart = nowtime; done = wipe_ScreenWipe(0,0,SCREENWIDTH,SCREENHEIGHT,tics); M_Drawer(); // menu is drawn even on top of wipes I_FinishUpdate(); // page flip or blit buffer } while (!done); } // // 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 gamestate_t wipegamestate = GS_DEMOSCREEN; extern boolean setsizeneeded; extern int showMessages; void D_Display (void) { static boolean isborderstate IDATA_ATTR= false; static boolean borderwillneedredraw IDATA_ATTR= false; static boolean inhelpscreensstate IDATA_ATTR= false; static gamestate_t oldgamestate IDATA_ATTR= -1; boolean wipe; boolean viewactive = false, isborder = false; if (nodrawers) // for comparative timing / profiling return; // save the current screen if about to wipe if ((wipe = gamestate != wipegamestate)) wipe_StartScreen(0, 0, SCREENWIDTH, SCREENHEIGHT); if (gamestate != GS_LEVEL) { // Not a level switch (oldgamestate) { case -1: case GS_LEVEL: V_SetPalette(0); // cph - use default (basic) palette default: break; } switch (gamestate) { case GS_INTERMISSION: WI_Drawer(); break; case GS_FINALE: F_Drawer(); break; case GS_DEMOSCREEN: D_PageDrawer(); break; default: break; } } else if (gametic != basetic) { // In a level boolean redrawborderstuff; HU_Erase(); if (setsizeneeded) { // change the view size if needed R_ExecuteSetViewSize(); oldgamestate = -1; // force background redraw } // Work out if the player view is visible, and if there is a border viewactive = (!(automapmode & am_active) || (automapmode & am_overlay)) && !inhelpscreens; isborder = viewactive ? (viewheight != SCREENHEIGHT) : (!inhelpscreens && (automapmode & am_active)); if (oldgamestate != GS_LEVEL) { R_FillBackScreen (); // draw the pattern into the back screen redrawborderstuff = isborder; } else { // CPhipps - // If there is a border, and either there was no border last time, // or the border might need refreshing, then redraw it. redrawborderstuff = isborder && (!isborderstate || borderwillneedredraw); // The border may need redrawing next time if the border surrounds the screen, // and there is a menu being displayed borderwillneedredraw = menuactive && isborder && viewactive && (viewwidth != SCREENWIDTH); } if (redrawborderstuff) R_DrawViewBorder(); // Now do the drawing if (viewactive) R_RenderPlayerView (&players[displayplayer]); if (automapmode & am_active) AM_Drawer(); ST_Drawer((viewheight != SCREENHEIGHT) || ((automapmode & am_active) && !(automapmode & am_overlay)), redrawborderstuff); R_DrawViewBorder(); HU_Drawer(); } inhelpscreensstate = inhelpscreens; isborderstate = isborder; oldgamestate = wipegamestate = gamestate; // draw pause pic if (paused) { static int x; if (!x) { // Cache results of x pos calc int lump = W_GetNumForName("M_PAUSE"); const patch_t* p = W_CacheLumpNum(lump); x = (320 - SHORT(p->width))/2; W_UnlockLumpNum(lump); } // CPhipps - updated for new patch drawing V_DrawNamePatch(x, (!(automapmode & am_active) || (automapmode & am_overlay)) ? 4+(viewwindowy*200/SCREENHEIGHT) : 4, // cph - Must un-stretch viewwindowy 0, "M_PAUSE", CR_DEFAULT, VPT_STRETCH); } // menus go directly to the screen M_Drawer(); // menu is drawn even on top of everything D_BuildNewTiccmds(); // normal update if (!wipe) I_FinishUpdate (); // page flip or blit buffer else { // wipe update wipe_EndScreen(0, 0, SCREENWIDTH, SCREENHEIGHT); D_Wipe(); } } // // 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 // extern boolean demorecording; static void D_DoomLoop (void) { basetic = gametic; I_SubmitSound(); while (!doomexit) { // process one or more tics if (singletics) { I_StartTic (); G_BuildTiccmd (&netcmds[consoleplayer][maketic%BACKUPTICS]); if (advancedemo) D_DoAdvanceDemo (); M_Ticker (); G_Ticker (); gametic++; maketic++; } else TryRunTics (); // will run at least one tic // killough 3/16/98: change consoleplayer to displayplayer if (players[displayplayer].mo) // cph 2002/08/10 S_UpdateSounds(players[displayplayer].mo);// move positional sounds // Update display, next frame, with current state. D_Display(); // Give the system some time rb->yield(); } } // // DEMO LOOP // static int demosequence; // killough 5/2/98: made static static int pagetic; static const char *pagename; // CPhipps - const // // D_PageTicker // Handles timing for warped projection // void D_PageTicker(void) { if (--pagetic < 0) D_AdvanceDemo(); } // // D_PageDrawer // void D_PageDrawer(void) { // CPhipps - updated for new patch drawing V_DrawNamePatch(0, 0, 0, pagename, CR_DEFAULT, VPT_STRETCH); } // // D_AdvanceDemo // Called after each demo or intro demosequence finishes // void D_AdvanceDemo (void) { advancedemo = true; } /* killough 11/98: functions to perform demo sequences * cphipps 10/99: constness fixes */ static void D_SetPageName(const char *name) { pagename = name; } static void D_DrawTitle1(const char *name) { S_StartMusic(mus_intro); pagetic = (TICRATE*170)/35; D_SetPageName(name); } static void D_DrawTitle2(const char *name) { S_StartMusic(mus_dm2ttl); D_SetPageName(name); } /* killough 11/98: tabulate demo sequences */ static struct { void (*func)(const char *); const char *name; } const demostates[][4] = { { {D_DrawTitle1, "TITLEPIC"}, {D_DrawTitle1, "TITLEPIC"}, {D_DrawTitle2, "TITLEPIC"}, {D_DrawTitle1, "TITLEPIC"}, }, { {G_DeferedPlayDemo, "demo1"}, {G_DeferedPlayDemo, "demo1"}, {G_DeferedPlayDemo, "demo1"}, {G_DeferedPlayDemo, "demo1"}, }, { {D_SetPageName, "CREDIT"}, {D_SetPageName, "CREDIT"}, {D_SetPageName, "CREDIT"}, {D_SetPageName, "CREDIT"}, }, { {G_DeferedPlayDemo, "demo2"}, {G_DeferedPlayDemo, "demo2"}, {G_DeferedPlayDemo, "demo2"}, {G_DeferedPlayDemo, "demo2"}, }, { {D_SetPageName, "HELP2"}, {D_SetPageName, "HELP2"}, {D_SetPageName, "CREDIT"}, {D_DrawTitle1, "TITLEPIC"}, }, { {G_DeferedPlayDemo, "demo3"}, {G_DeferedPlayDemo, "demo3"}, {G_DeferedPlayDemo, "demo3"}, {G_DeferedPlayDemo, "demo3"}, }, { {NULL,0}, {NULL,0}, {NULL,0}, {D_SetPageName, "CREDIT"}, }, { {NULL,0}, {NULL,0}, {NULL,0}, {G_DeferedPlayDemo, "demo4"}, }, { {NULL,0}, {NULL,0}, {NULL,0}, {NULL,0}, } }; /* * This cycles through the demo sequences. * killough 11/98: made table-driven */ void D_DoAdvanceDemo(void) { players[consoleplayer].playerstate = PST_LIVE; /* not reborn */ advancedemo = usergame = paused = false; gameaction = ga_nothing; pagetic = TICRATE * 11; /* killough 11/98: default behavior */ gamestate = GS_DEMOSCREEN; if (netgame && !demoplayback) { demosequence = 0; } else if (!demostates[++demosequence][gamemode].func) demosequence = 0; demostates[demosequence][gamemode].func(demostates[demosequence][gamemode].name); } // // D_StartTitle // void D_StartTitle (void) { gameaction = ga_nothing; demosequence = -1; D_AdvanceDemo(); } // // D_AddFile // // Rewritten by Lee Killough // // Ty 08/29/98 - add source parm to indicate where this came from // CPhipps - static, const char* parameter // - source is an enum // - modified to allocate & use new wadfiles array void D_AddFile (const char *file, wad_source_t source) { wadfiles = realloc(wadfiles, sizeof(*wadfiles)*(numwadfiles+1)); wadfiles[numwadfiles].name = AddDefaultExtension(strcpy(malloc(strlen(file)+5), file), ".wad"); wadfiles[numwadfiles].src = source; // Ty 08/29/98 numwadfiles++; } // // CheckIWAD // // Verify a file is indeed tagged as an IWAD // Scan its lumps for levelnames and return gamemode as indicated // Detect missing wolf levels in DOOM II // // The filename to check is passed in iwadname, the gamemode detected is // returned in gmode, hassec returns the presence of secret levels // // jff 4/19/98 Add routine to test IWAD for validity and determine // the gamemode from it. Also note if DOOM II, whether secret levels exist // CPhipps - const char* for iwadname, made static #if 0 static void CheckIWAD(const char *iwadname,GameMode_t *gmode,boolean *hassec) { if ( !fileexists (iwadname) ) { int ud=0,rg=0,sw=0,cm=0,sc=0; int handle; // Identify IWAD correctly if ( (handle = open (iwadname,O_RDONLY)) != -1) { wadinfo_t header; // read IWAD header read (handle, &header, sizeof(header)); if (!strncmp(header.identification,"IWAD",4)) { size_t length; filelump_t *fileinfo; // read IWAD directory header.numlumps = LONG(header.numlumps); header.infotableofs = LONG(header.infotableofs); length = header.numlumps; fileinfo = malloc(length*sizeof(filelump_t)); lseek (handle, header.infotableofs, SEEK_SET); read (handle, fileinfo, length*sizeof(filelump_t)); close(handle); // scan directory for levelname lumps while (length--) if (fileinfo[length].name[0] == 'E' && fileinfo[length].name[2] == 'M' && fileinfo[length].name[4] == 0) { if (fileinfo[length].name[1] == '4') ++ud; else if (fileinfo[length].name[1] == '3') ++rg; else if (fileinfo[length].name[1] == '2') ++rg; else if (fileinfo[length].name[1] == '1') ++sw; } else if (fileinfo[length].name[0] == 'M' && fileinfo[length].name[1] == 'A' && fileinfo[length].name[2] == 'P' && fileinfo[length].name[5] == 0) { ++cm; if (fileinfo[length].name[3] == '3') if (fileinfo[length].name[4] == '1' || fileinfo[length].name[4] == '2') ++sc; } free(fileinfo); } else // missing IWAD tag in header I_Error("CheckIWAD: IWAD tag %s not present", iwadname); } else // error from open call I_Error("CheckIWAD: Can't open IWAD %s", iwadname); // Determine game mode from levels present // Must be a full set for whichever mode is present // Lack of wolf-3d levels also detected here *gmode = indetermined; *hassec = false; if (cm>=30) { *gmode = commercial; *hassec = sc>=2; } else if (ud>=9) *gmode = retail; else if (rg>=18) *gmode = registered; else if (sw>=9) *gmode = shareware; } else // error from access call I_Error("CheckIWAD: IWAD %s not readable", iwadname); } #endif void D_DoomMainSetup(void) { int p; nomonsters = M_CheckParm ("-nomonsters"); respawnparm = M_CheckParm ("-respawn"); fastparm = M_CheckParm ("-fast"); devparm = M_CheckParm ("-devparm"); if (M_CheckParm ("-altdeath")) deathmatch = 2; else if (M_CheckParm ("-deathmatch")) deathmatch = 1; printf("Welcome to Rockdoom\n"); switch ( gamemode ) { case retail: printf ("The Ultimate DOOM Startup v%d.%d\n",DVERSION/100,DVERSION%100); break; case shareware: printf ("DOOM Shareware Startup v%d.%d\n",DVERSION/100,DVERSION%100); break; case registered: printf ("DOOM Registered Startup v%d.%d\n",DVERSION/100,DVERSION%100); break; case commercial: switch (gamemission) { case pack_plut: printf ("DOOM 2: Plutonia Experiment v%d.%d\n",DVERSION/100,DVERSION%100); break; case pack_tnt: printf ("DOOM 2: TNT - Evilution v%d.%d\n",DVERSION/100,DVERSION%100); break; default: printf ("DOOM 2: Hell on Earth v%d.%d\n",DVERSION/100,DVERSION%100); break; } break; default: printf ("Public DOOM v%d.%d\n",DVERSION/100,DVERSION%100); break; } if (devparm) printf(D_DEVSTR); // turbo option if ((p=M_CheckParm ("-turbo"))) { int scale = 200; extern int forwardmove[2]; extern int sidemove[2]; if (p 400) scale = 400; printf ("turbo scale: %d%%\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; } // get skill / episode / map from parms startskill = sk_medium; startepisode = 1; startmap = 1; autostart = false; p = M_CheckParm ("-skill"); if (p && p < myargc-1) { startskill = myargv[p+1][0]-'1'; autostart = true; } p = M_CheckParm ("-episode"); if (p && p < myargc-1) { startepisode = myargv[p+1][0]-'0'; startmap = 1; autostart = true; } p = M_CheckParm ("-warp"); if (p && p < myargc-1) { if (gamemode == commercial) startmap = atoi (myargv[p+1]); else { startepisode = myargv[p+1][0]-'0'; startmap = myargv[p+2][0]-'0'; } autostart = true; } // CPhipps - move up netgame init printf("D_InitNetGame: Checking for network game.\n"); D_InitNetGame(); // init subsystems printf ("V_Init: allocate screens.\n"); V_Init (); printf ("W_Init: Init WADfiles.\n"); W_Init(); if ((p = W_CheckNumForName("DEHACKED")) != -1) // cph - add dehacked-in-a-wad support ProcessDehFile(NULL, dehout ? "/dehlog.txt" : NULL, p); V_InitColorTranslation(); //jff 4/24/98 load color translation lumps // 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. const char name[23][8]= { "e2m1","e2m2","e2m3","e2m4","e2m5","e2m6","e2m7","e2m8","e2m9", "e3m1","e3m3","e3m3","e3m4","e3m5","e3m6","e3m7","e3m8","e3m9", "dphoof","bfgga0","heada1","cybra1","spida1d1" }; int i; if ( gamemode == shareware) I_Error("\nYou cannot -file with the shareware version. Register!\n"); // Check for fake IWAD with right name, // but w/o all the lumps of the registered version. if (gamemode == registered) for (i = 0;i < 23; i++) if (W_CheckNumForName(name[i])<0) I_Error("This is not the registered version.\n"); } // Iff additonal PWAD files are used, print modified banner if (modifiedgame) printf ("ATTENTION: This version of DOOM has been modified.\n"); // Check and print which version is executed. switch ( gamemode ) { case shareware: case indetermined: printf ("Shareware!\n"); break; case registered: case retail: case commercial: printf ("Commercial product - do not distribute!\n"); break; default: // Ouch. break; } printf ("M_Init: Init miscellaneous info.\n"); M_Init (); printf ("R_Init: Init DOOM refresh daemon - "); R_Init (); printf ("P_Init: Init Playloop state.\n"); P_Init (); printf ("I_Init: Setting up machine state.\n"); I_Init (); printf ("S_Init: Setting up sound.\n"); S_Init (snd_SfxVolume /* *8 */, snd_MusicVolume /* *8*/ ); printf ("HU_Init: Setting up heads up display.\n"); HU_Init (); I_InitGraphics (); printf ("ST_Init: Init status bar.\n"); ST_Init (); // check for a driver that wants intermission stats p = M_CheckParm ("-statcopy"); if (p && p