// Emacs style mode select	 -*- C++ -*- 
//-----------------------------------------------------------------------------
//
// $Id:$
//
// Copyright (C) 1993-1996 by id Software, Inc.
//
// This source is available for distribution and/or modification
// only under the terms of the DOOM Source Code License as
// published by id Software. All rights reserved.
//
// The source is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License
// for more details.
//
// $Log:$
//
// 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 <direct.h>
#define mkdir(a,b) _mkdir (a)
#else
#include <sys/stat.h>
#endif

#ifdef UNIX
#include <unistd.h>
#endif

#include <time.h>
#include <math.h>

#include "errors.h"

#include "m_alloc.h"
#include "m_random.h"
#include "minilzo.h"
#include "doomdef.h"
#include "doomstat.h"
#include "dstrings.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 "c_console.h"
#include "c_dispatch.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_local.h"
#include "r_sky.h"
#include "d_main.h"
#include "d_dehacked.h"
#include "cmdlib.h"
#include "s_sound.h"
#include "m_swap.h"
#include "v_text.h"
#include "gi.h"
#include "b_bot.h"		//Added by MC:
#include "stats.h"
#include "w_wad.h"	// [BC]
#include "d_netinf.h"
#include "sv_main.h"

// MACROS ------------------------------------------------------------------

// TYPES -------------------------------------------------------------------

// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------

extern void M_RestoreMode (void);
extern void R_ExecuteSetViewSize (void);

// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------

void D_CheckNetGame (void);
void D_ProcessEvents (void);
void G_BuildTiccmd (ticcmd_t* cmd);
void D_DoAdvanceDemo (void);

// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------

void D_DoomLoop (void);
static void D_AddFile (const char *file);
//static void DoLooseFiles (void);

// EXTERNAL DATA DECLARATIONS ----------------------------------------------

extern gameinfo_t SharewareGameInfo;
extern gameinfo_t RegisteredGameInfo;
extern gameinfo_t RetailGameInfo;
extern gameinfo_t CommercialGameInfo;
extern gameinfo_t HereticGameInfo;
extern gameinfo_t HereticSWGameInfo;
extern gameinfo_t HexenGameInfo;

extern int testingmode;
extern BOOL setsizeneeded;
extern BOOL setmodeneeded;
extern BOOL netdemo;
extern int NewWidth, NewHeight, NewBits, DisplayBits;
EXTERN_CVAR (st_scale)
extern BOOL gameisdead;
extern BOOL demorecording;
extern BOOL M_DemoNoPlay;	// [RH] if true, then skip any demos in the loop
extern DThinker ThinkerCap;

extern cycle_t WallCycles, PlaneCycles, MaskedCycles;

// PUBLIC DATA DEFINITIONS -------------------------------------------------

CVAR (fraglimit, "0", CVAR_SERVERINFO | CVAR_SPLOCK);	// [BC] Limits
CVAR (timelimit, "0", CVAR_SERVERINFO | CVAR_SPLOCK);
CVAR (scorelimit, "0", CVAR_SERVERINFO | CVAR_SPLOCK);
CVAR (pointlimit, "0", CVAR_SERVERINFO | CVAR_SPLOCK);
CVAR (def_patch, "", CVAR_ARCHIVE);


wadlist_t *wadfiles;		// [RH] remove limit on # of loaded wads
BOOL devparm;				// started game with -devparm
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
char startmap[8];
BOOL autostart;
BOOL advancedemo;
FILE *debugfile;
event_t events[MAXEVENTS];
int eventhead;
int eventtail;
gamestate_t wipegamestate = GS_DEMOSCREEN;	// can be -1 to force a wipe
netstate_t netstate = NS_SINGLEPLAYER;	// [BC]
DCanvas *page;

cycle_t FrameCycles;

// PRIVATE DATA DEFINITIONS ------------------------------------------------

static wadlist_t **wadtail = &wadfiles;
static int demosequence;
static int pagetic;

// CODE --------------------------------------------------------------------

//==========================================================================
//
// D_ProcessEvents
//
// Send all the events of the given timestamp down the responder chain.
// Events are asynchronous inputs generally generated by the game user.
// Events can be discarded if no responder claims them
//
//==========================================================================

void D_ProcessEvents (void)
{
	event_t *ev;
		
	// [RH] If testing mode, do not accept input until test is over
	if (testingmode)
	{
		if (testingmode <= I_GetTime())
		{
			M_RestoreMode ();
		}
		return;
	}
		
	for (; eventtail != eventhead ; eventtail = (++eventtail)&(MAXEVENTS-1))
	{
		ev = &events[eventtail];
		if (C_Responder (ev))
			continue;				// console ate the event
		if (M_Responder (ev))
			continue;				// menu ate the event
		G_Responder (ev);
	}
}

//==========================================================================
//
// D_PostEvent
//
// Called by the I/O functions when input is detected.
//
//==========================================================================

void D_PostEvent (const event_t* ev)
{
	events[eventhead] = *ev;
	eventhead = (++eventhead)&(MAXEVENTS-1);
}

//==========================================================================
//
// DMFlagsCallback
//
// [RH] Each time dmflags is changed, this function is called and
//		transforms it into an integer so that we don't need to make
//		the conversion each time we check its value.
//
//==========================================================================

static void DMFlagsCallback (cvar_t &var)
{
	dmflags = (int)var.value;

	// In case DF_NO_FREELOOK was changed, reinitialize the sky
	// map. (If no freelook, then no need to stretch the sky.)
	if (textureheight)
		R_InitSkyMap ();

	if (dmflags & DF_NO_FREELOOK)
		AddCommandString ("centerview");
}
cvar_t dmflagsvar ("dmflags", "0", CVAR_SERVERINFO, DMFlagsCallback);
// [BC] Sounds good. Let's do that for dmflags2
void DMFlags2Callback (cvar_t &var)
{
	dmflags2 = (int)var.value;
}
cvar_t dmflags2var ("dmflags2", "0", CVAR_SERVERINFO, DMFlags2Callback);

void CLDMFlagsCallback (cvar_t &var)
{
	cl_dmflags = (int)var.value;
}
cvar_t cl_dmflagsvar ("cl_dmflags", "0", CVAR_SERVERINFO, CLDMFlagsCallback);

void CLDMFlags2Callback (cvar_t &var)
{
	cl_dmflags2 = (int)var.value;
}
cvar_t cl_dmflags2var ("cl_dmflags2", "0", CVAR_SERVERINFO, CLDMFlags2Callback);

//==========================================================================
//
//
// D_Display
//
// Draw current display, possibly wiping it from the previous
//
//==========================================================================

void D_Display (void)
{
	if (netstate == NS_SERVER)
		return;

	BOOL wipe;

	if (nodrawers)
		return; 				// for comparative timing / profiling
	
	cycle_t cycles = 0;
	clock (cycles);

	if (gamestate == GS_LEVEL && viewactive && netstate != NS_SERVER && netstate != NS_CLIENT)
		R_SetFOV (players[consoleplayer].camera->player ?
			players[consoleplayer].camera->player->fov : 90.0f);

	// [RH] change the screen mode if needed
	if (setmodeneeded)
	{
		int oldwidth = screen->width;
		int oldheight = screen->height;
		int oldbits = DisplayBits;

		// Change screen mode.
		if (!V_SetResolution (NewWidth, NewHeight, NewBits))
			if (!V_SetResolution (oldwidth, oldheight, oldbits))
				I_FatalError ("Could not change screen mode");

		// Recalculate various view parameters.
		setsizeneeded = true;
		// Trick status bar into rethinking its position
		st_scale.Callback ();
		SB_state = -1;
		// Refresh the console.
		C_NewModeAdjust ();
	}

	// change the view size if needed
	if (setsizeneeded)
	{
		R_ExecuteSetViewSize ();
		setmodeneeded = false;
	}

	I_BeginUpdate ();

	// [RH] Allow temporarily disabling wipes
	if (NoWipe)
	{
		BorderNeedRefresh = true;
		NoWipe--;
		wipe = false;
		wipegamestate = gamestate;
	}
	else if (gamestate != wipegamestate && gamestate != GS_FULLCONSOLE)
	{ // save the current screen if about to wipe
		BorderNeedRefresh = true;
		wipe = true;
		wipe_StartScreen ();
		wipegamestate = gamestate;
	}
	else
	{
		wipe = false;
	}

	// [BC] Hack hack
	if (netstate == NS_SERVER)
	{
		C_DrawConsole ();
		M_Drawer ();
		I_FinishUpdate ();
		return;
	}

	switch (gamestate)
	{
		case GS_FULLCONSOLE:
			C_DrawConsole ();
			M_Drawer ();
			I_FinishUpdate ();
			return;

		case GS_LEVEL:
			if (!gametic)
				break;

			if (viewactive)
				R_RenderPlayerView (&players[consoleplayer]);
//			if (automapactive)
			AM_Drawer ();
			C_DrawMid ();
			ST_Drawer ();
			CT_Drawer ();
			break;

		case GS_INTERMISSION:
			WI_Drawer ();
//			if (ConsoleState == c_up)
			C_DrawNotifyText ();

			CT_Drawer ();

			break;

		case GS_FINALE:
			F_Drawer ();
			break;

		case GS_DEMOSCREEN:
			D_PageDrawer ();
			break;

	default:
	    break;
	}

	// draw pause pic
	if (paused && !menuactive)
	{
		patch_t *pause = (patch_t *)W_CacheLumpName ("M_PAUSE", PU_CACHE);
		int y;

		y = (automapactive && !viewactive) ? 4 : viewwindowy + 4;
		screen->DrawPatchCleanNoMove (pause, (screen->width-(pause->width)*CleanXfac)/2, y);
	}

	// [RH] Draw icon, if any
	if (D_DrawIcon)
	{
		int lump = W_CheckNumForName (D_DrawIcon);

		D_DrawIcon = NULL;
		if (lump >= 0)
		{
			patch_t *p = (patch_t *)W_CacheLumpNum (lump, PU_CACHE);

			screen->DrawPatchIndirect (p, 160-SHORT(p->width)/2, 100-SHORT(p->height)/2);
		}
		NoWipe = 10;
	}

	NetUpdate ();			// send out any new accumulation

	if (!wipe)
	{
		// normal update
		C_DrawConsole ();	// draw console
		M_Drawer ();		// menu is drawn even on top of everything
		FStat::PrintStat ();
		I_FinishUpdate ();	// page flip or blit buffer
	}
	else
	{
		// wipe update
		int wipestart, nowtime, tics;
		BOOL done;

		wipe_EndScreen ();
		I_FinishUpdateNoBlit ();

		wipestart = I_GetTime () - 1;

		do
		{
			do
			{
				nowtime = I_GetTime ();
				tics = nowtime - wipestart;
			} while (!tics);
			wipestart = nowtime;
			I_BeginUpdate ();
			done = wipe_ScreenWipe (tics);
			C_DrawConsole ();
			M_Drawer ();			// menu is drawn even on top of wipes
			I_FinishUpdate ();		// page flip or blit buffer
		} while (!done);
	}

	unclock (cycles);
	FrameCycles = cycles;
}

//==========================================================================
//
// D_DoomLoop
//
// Manages timing and IO, calls all ?_Responder, ?_Ticker, and ?_Drawer,
// calls I_GetTime, I_StartFrame, and I_StartTic
//
//==========================================================================

void D_DoomLoop (void)
{
	while (1)
	{
		try
		{
			if (netstate == NS_SERVER)
				SV_RunTics (); // will run at least one tic
			else if (netstate == NS_CLIENT)
			{
				CL_TryRunTics (); // will run at least one tic            

				if (clact == ca_disconnected)
					CL_TryToConnect();
			}
			else
			{

				// frame syncronous IO operations
				I_StartFrame ();
			
				// process one or more tics
				if (singletics)
				{
					I_StartTic ();
					DObject::BeginFrame ();
					D_ProcessEvents ();
					G_BuildTiccmd (&netcmds[consoleplayer][maketic%BACKUPTICS]);
					if (advancedemo)
						D_DoAdvanceDemo ();
					C_Ticker ();
					M_Ticker ();
					G_Ticker ();
					gametic++;
					maketic++;
					DObject::EndFrame ();
					Net_NewMakeTic ();
				}
				else
				{
					TryRunTics (); // will run at least one tic
				}
			}

			// [RH] Use the consoleplayer's camera to update sounds
			S_UpdateSounds (players[consoleplayer].camera);	// move positional sounds

			// Update display, next frame, with current state.
			D_Display ();
		}
		catch (CRecoverableError &error)
		{
			int i;

			B_RemoveBots ();

			if (error.GetMessage ())
				Printf_Bold ("\n%s\n", error.GetMessage());

			D_QuitNetGame ();
			G_ClearSnapshots ();
			netdemo = false;
			singletics = false;
			if (netstate != NS_SERVER)
				netstate = NS_SINGLEPLAYER;
			if (demorecording || demoplayback)
				G_CheckDemoStatus ();

			for (i = 0; i < MAXPLAYERS; i++)
				playeringame[i] = 0;
			consoleplayer = 0;
			if (netstate != NS_SERVER)
			{
				playeringame[consoleplayer] = true;
				players[0].playerstate = PST_LIVE;
			}
			gameaction = ga_fullconsole;
		}
	}
}

//==========================================================================
//
// D_PageTicker
//
//==========================================================================

void D_PageTicker (void)
{
	if (--pagetic < 0)
		D_AdvanceDemo ();
}

//==========================================================================
//
// D_PageDrawer
//
//==========================================================================

void D_PageDrawer (void)
{
	if (page)
	{
		page->Blit (0, 0, page->width, page->height,
			screen, 0, 0, screen->width, screen->height);
	}
	else
	{
		screen->Clear (0, 0, screen->width, screen->height, 0);
		screen->PrintStr (0, 0, "Page graphic goes here", 22);
	}
}

//==========================================================================
//
// D_AdvanceDemo
//
// Called after each demo or intro demosequence finishes
//
//==========================================================================

void D_AdvanceDemo (void)
{
	advancedemo = true;
}

//==========================================================================
//
// D_DoAdvanceDemo
//
//==========================================================================

void D_DoAdvanceDemo (void)
{
	static char demoname[8] = "DEMO1";
	static int democount = 0;
	static int pagecount;
	char *pagename = NULL;

	players[consoleplayer].playerstate = PST_LIVE;	// not reborn
	advancedemo = false;
	usergame = false;				// no save / end game here
	paused = false;
	gameaction = ga_nothing;

	switch (demosequence)
	{
		case 3:
			if (gameinfo.advisoryTime)
			{
				if (page)
				{
					page->Lock ();
					page->DrawPatch ((patch_t *)W_CacheLumpName ("ADVISOR", PU_CACHE), 4, 160);
				}
				page->Unlock ();
				demosequence = 1;
				pagetic = (int)(gameinfo.advisoryTime * TICRATE);
				break;
			}
			// fall through to case 1 if no advisory notice

		case 1:
			if (!M_DemoNoPlay)
			{
				BorderNeedRefresh = true;
				democount++;
				sprintf (demoname + 4, "%d", democount);
				if (W_CheckNumForName (demoname) < 0)
				{
					demosequence = 0;
					democount = 1;
					// falls through to case 0 below
				}
				else
				{
					G_DeferedPlayDemo (demoname);
					demosequence = 2;
					break;
				}
			}

		default:
		case 0:
			gamestate = GS_DEMOSCREEN;
			pagename = gameinfo.titlePage;
			pagetic = (int)(gameinfo.titleTime * TICRATE);
			S_StartMusic (gameinfo.titleMusic);
			demosequence = 3;
			pagecount = 0;
			C_HideConsole ();
			break;

		case 2:
			pagetic = (int)(gameinfo.pageTime * TICRATE);
			gamestate = GS_DEMOSCREEN;
			if (pagecount == 0)
				pagename = gameinfo.creditPage1;
			else
				pagename = gameinfo.creditPage2;
			pagecount ^= 1;
			demosequence = 1;
			break;
	}

	if (pagename)
	{
		int width, height;
		patch_t *data;

		if (gameinfo.flags & GI_PAGESARERAW)
		{
			data = (patch_t *)W_CacheLumpName (pagename, PU_CACHE);
			width = 320;
			height = 200;
		}
		else
		{
			data = (patch_t *)W_CacheLumpName (pagename, PU_CACHE);
			width = SHORT(data->width);
			height = SHORT(data->height);
		}

		if (page && (page->width != width || page->height != height))
		{
			delete page;
			page = NULL;
		}

		if (page == NULL)
			page = new DCanvas (width, height, 8);

		page->Lock ();
		if (gameinfo.flags & GI_PAGESARERAW)
			page->DrawBlock (0, 0, 320, 200, (byte *)data);
		else
			page->DrawPatch (data, 0, 0);
		page->Unlock ();
	}
}

//==========================================================================
//
// D_StartTitle
//
//==========================================================================

void D_StartTitle (void)
{
	gameaction = ga_nothing;
	demosequence = -1;
	D_AdvanceDemo ();
}

//==========================================================================
//
// Cmd_Endgame
//
// [RH] Quit the current game and go to fullscreen console
//
//==========================================================================

BEGIN_COMMAND (endgame)
{
	if (netstate != NS_SERVER)
	{
		gameaction = ga_fullconsole;
		demosequence = -1;
	}
}
END_COMMAND (endgame)

//==========================================================================
//
// D_AddFile
//
//==========================================================================

void D_AddFile (const char *file)
{
	wadlist_t *wad = (wadlist_t *)Z_Malloc (sizeof(*wad) + strlen(file), PU_STATIC, 0);

	*wadtail = wad;
	wad->next = NULL;
	strcpy (wad->name, file);
	wadtail = &wad->next;
}

//==========================================================================
//
// CheckIWAD
//
// Tries to find an IWAD from a set of know IWAD names, and checks the first
// one found's contents to determine whether registered/commercial features
// should be executed (notably loading PWAD's).
// 
//==========================================================================

static bool CheckIWAD (const char *doomwaddir, const char *parm, const char* &titlestring)
{
	static const char *doomwadnames[] =
	{
		NULL,
		"doom2f.wad",
		"doom2.wad",
		"plutonia.wad",
		"tnt.wad",
		"doomu.wad", // Hack from original Linux version. Not necessary, but I threw it in anyway.
		"doom.wad",
		"doom1.wad",
		NULL
	};
	char iwad[256];
	int i;

	doomwadnames[0] = parm;

	// Search for a pre-defined IWAD from the list above
	for (i = parm ? 0 : 1; doomwadnames[i]; i++)
	{
		sprintf (iwad, "%s%s", doomwaddir, doomwadnames[i]);
		if (FileExists (iwad))
			break;
	}
	if (!doomwadnames[i])
		iwad[0] = 0;

	// Now scan the contents of the IWAD to determine which one it is
	if (iwad[0])
	{
#define NUM_CHECKLUMPS 9
		static const char checklumps[NUM_CHECKLUMPS][8] = {
			"E1M1", "E2M1", "E4M1", "MAP01",
			{ 'A','N','I','M','D','E','F','S'},
			"FINAL2", "REDTNT2", "CAMO1",
			{ 'E','X','T','E','N','D','E','D'}
		};
		int lumpsfound[NUM_CHECKLUMPS];
		wadinfo_t header;
		FILE *f;

		memset (lumpsfound, 0, sizeof(lumpsfound));
		if ( (f = fopen (iwad, "rb")) )
		{
			fread (&header, sizeof(header), 1, f);
			if (header.identification == IWAD_ID ||
				header.identification == PWAD_ID)
			{
				header.numlumps = LONG(header.numlumps);
				if (0 == fseek (f, LONG(header.infotableofs), SEEK_SET))
				{
					for (i = 0; i < header.numlumps; i++)
					{
						filelump_t lump;
						int j;

						if (0 == fread (&lump, sizeof(lump), 1, f))
							break;
						for (j = 0; j < NUM_CHECKLUMPS; j++)
							if (!strnicmp (lump.name, checklumps[j], 8))
								lumpsfound[j]++;
					}
				}
			}
			fclose (f);
		}

		gamemode = undetermined;

		if (lumpsfound[3])
		{
			gamemode = commercial;
			gameinfo = CommercialGameInfo;
			if (lumpsfound[6])
			{
				gamemission = pack_tnt;
				titlestring = "DOOM 2: TNT - Evilution";
			}
			else if (lumpsfound[7])
			{
				gamemission = pack_plut;
				titlestring = "DOOM 2: Plutonia Experiment";
			}
			else
			{
				gamemission = doom2;
				if (lumpsfound[4])
				{
					gameinfo = HexenGameInfo;
					titlestring = "Hexen: Beyond Heretic";
				}
				else
					titlestring = "DOOM 2: Hell on Earth";
			}
		}
		else if (lumpsfound[0])
		{
			gamemission = doom;
			if (lumpsfound[1])
			{
				if (lumpsfound[2])
				{
					gamemode = retail;
					gameinfo = RetailGameInfo;
					titlestring = "The Ultimate DOOM";
				}
				else
				{
					gamemode = registered;
					gameinfo = RegisteredGameInfo;
					titlestring = "DOOM Registered";
				}
			}
			else
			{
				gamemode = shareware;
				gameinfo = SharewareGameInfo;
				titlestring = "DOOM Shareware";
			}
			if (lumpsfound[5])
			{
				if (lumpsfound[1])
				{
					gameinfo = HereticSWGameInfo;
					titlestring = "Heretic Shareware";
				}
				else
				{
					gameinfo = HereticGameInfo;
					if (lumpsfound[8])
					{
						gameinfo.flags |= GI_MENUHACK_EXTENDED;
						titlestring = "Heretic: Shadow of the Serpent Riders";
					}
					else
						titlestring = "Heretic";
				}
			}
		}
	}

	if (gamemode == undetermined)
	{
		gameinfo = SharewareGameInfo;
	}
	if (iwad[0])
		D_AddFile (iwad);

	return iwad[0] != '\0';
}

//==========================================================================
//
// CheckIWADinEnvDir
//
// Checks for an IWAD in a directory specified in an environment variable.
//
//==========================================================================

static bool CheckIWADinEnvDir (const char *envname, const char *parm, const char* &titlestring)
{
	char dir[512];
	const char *envdir = getenv (envname);

	if (envdir)
	{
		strcpy (dir, envdir);
		FixPathSeperator (dir);
		if (dir[strlen(dir) - 1] != '/')
			strcat (dir, "/");
		return CheckIWAD (dir, parm, titlestring);
	}
	return false;
}

//==========================================================================
//
// IdentifyVersion
//
// Tries to find an IWAD in one of four directories under DOS or Win32:
//	  1. Current directory
//	  2. Executable directory
//	  3. $DOOMWADDIR
//	  4. $HOME
//
// Under UNIX OSes, the search path is:
//	  1. Current directory
//	  2. $DOOMWADDIR
//	  3. $HOME/.zdoom
//	  4. The share directory defined at compile time (/usr/local/share/zdoom)
//
//==========================================================================

static const char *IdentifyVersion (void)
{
	const char *titlestring = "Public DOOM - ";
	const char *iwadparm = Args.CheckValue ("-iwad");
	char *homepath = NULL;

	if (iwadparm)
	{
		char *custwad = new char[strlen (iwadparm) + 5];
		strcpy (custwad, iwadparm);
		FixPathSeperator (custwad);
		DefaultExtension (custwad, ".wad");
		iwadparm = custwad;
	}

#ifndef UNIX
	if (!CheckIWAD ("", iwadparm, titlestring) &&
		!CheckIWAD (progdir, iwadparm, titlestring) &&
		!CheckIWADinEnvDir ("DOOMWADDIR", iwadparm, titlestring) &&
		!CheckIWADinEnvDir ("HOME", iwadparm, titlestring))
#else
	if (!CheckIWAD ("", iwadparm, titlestring) &&
		!CheckIWADinEnvDir ("DOOMWADDIR", iwadparm, titlestring) &&
		(homepath = GetUserFile (""),
		 !CheckIWAD (homepath, iwadparm, titlestring)) &&
		!CheckIWAD (SHARE_DIR, iwadparm, titlestring))
#endif
	{
		Printf (PRINT_HIGH, "Game mode indeterminate.\n");
	}

	if (homepath)
		delete[] homepath;

	if (iwadparm)
		delete[] const_cast<char *>(iwadparm);

	return titlestring;
}

//==========================================================================
//
// TransSoulsCallabck
//
// [RH] This function is called whenever the lost soul translucency level
// changes. It searches through the entire world for any lost souls and sets
// their translucency levels as appropriate. New skulls are also set to
// spawn with the desired translucency.
//
//==========================================================================

BEGIN_CUSTOM_CVAR (transsouls, "0.75", CVAR_ARCHIVE)
{
	if (var.value < 0.25)
	{
		var.Set (0.25);
	}
	else if (var.value > 1)
	{
		var.Set (1);
	}
	else
	{
		fixed_t newlucent = (fixed_t)(FRACUNIT * var.value);

		mobjinfo[MT_SKULL].translucency = newlucent;

		// Find all the lost souls in the world and change them, too.
		AActor *actor;
		TThinkerIterator<AActor> iterator;

		while ( (actor = iterator.Next ()) )
		{
			if (actor->type == MT_SKULL)
				actor->translucency = newlucent;
		}
	}
}
END_CUSTOM_CVAR (transsouls)

//==========================================================================
//
// BaseFileSearch
//
//==========================================================================

static const char *BaseFileSearch (const char *file, const char *ext)
{
	static char wad[256];

	if (!FileExists (file))
	{
#ifndef UNIX
		sprintf (wad, "%s%s", progdir, file);
		if (!FileExists (wad))
		{
			char *doomwaddir = getenv ("DOOMWADDIR");
			if (doomwaddir)
			{
				char dir[256];
				strcpy (dir, doomwaddir);
				FixPathSeperator (dir);
				sprintf (wad, "%s%s%s",
						 dir,
						 dir[strlen (dir) - 1] != '/' ? "/" : "",
						 file);
				if (!FileExists (wad))
					goto retry;
			}
			else
				goto retry;
		}
#else
		sprintf (wad, "%s%s", SHARE_DIR, file);
		if (!FileExists (wad))
		{
			char *uwad = GetUserFile (file);
			if (!FileExists (uwad))
			{
				char *doomwaddir = getenv ("DOOMWADDIR");
				delete[] uwad;
				if (doomwaddir)
				{
					char dir[256];
					strcpy (dir, doomwaddir);
					FixPathSeperator (dir);
					sprintf (wad, "%s%s%s",
							 dir,
							 dir[strlen (dir) - 1] != '/' ? "/" : "",
							 file);
					if (!FileExists (wad))
						goto retry;
				}
				else
					goto retry;
			}
			else
			{
				strcpy (wad, uwad);
				delete[] uwad;
			}
		}
#endif
	}
	else
		return file;
	return wad;

 retry:
	if (ext)
	{
		char tmp[256];
		strcpy (tmp, file);
		DefaultExtension (tmp, ext);
		return BaseFileSearch (tmp, NULL);
	}
	return NULL;
}

// [BC] Load wads dynamically
BEGIN_COMMAND (load)
{
	bool success = false;

	if (argc < 2)
	{
		Printf (PRINT_HIGH, "load [pwad] <pwad> ...: loads a pwad\n");
		return;
	}

	numskins = 0;
	numinfoskins = 0;
	gamestate = GS_STARTUP;
	gameaction = ga_fullconsole;
	demosequence = -1;

	wadfiles = new wadlist_t;
	wadtail = &wadfiles;

	Printf (PRINT_HIGH, "\nLoading wads...\n");
	I_SetTitleString (IdentifyVersion ());	// [BC] Load up IWAD first

	const char *wad;

	wad = BaseFileSearch ("skulltag.wad", NULL);
	if (wad)
		D_AddFile (wad);
	else
		I_FatalError ("Cannot find skulltag.wad!");

	for (int i=1; i<argc; i++)
	{
		wad = BaseFileSearch (argv[i], NULL);
		if (wad)
		{
			D_AddFile (wad);
			success = true;
		}
		else
			Printf (PRINT_HIGH, "Cannot find %s!\n", argv[i]);
	}

	// [BC] Don't bother reloading everything
	if (!success)
		return;

	W_InitMultipleFiles (&wadfiles);

	// [BC] Now we need to reinitialize everything. Fun, huh?
	if (netstate != NS_SERVER)
	{
		Printf (PRINT_HIGH, "M_Init: Init miscellaneous info.\n");
		M_Init ();
	}

	Printf (PRINT_HIGH, "R_Init: Init DOOM refresh daemon\n");
	R_Init ();

	Printf (PRINT_HIGH, "P_Init: Init Playloop state.\n");
	P_Init ();

	Printf (PRINT_HIGH, "S_Init: Setting up sound.\n");
	S_Init ((int)snd_sfxvolume.value /* *8 */, (int)snd_musicvolume.value /* *8*/ );

	if (netstate != NS_SERVER)
	{
		Printf (PRINT_HIGH, "ST_Init: Init status bar.\n");
		ST_Init ();
	}

	D_StartTitle ();				// start up intro loop
	wipegamestate = GS_FORCEWIPE;
}
END_COMMAND (load)

//==========================================================================
//
// D_DoomMain
//
//==========================================================================

void D_DoomMain (void)
{
	int p, flags, flags2;	// [BC]
	char file[256];
	char *v;

	atterm (DObject::StaticShutdown);

	rngseed = (DWORD)time (NULL);

	gamestate = GS_STARTUP;
	M_FindResponseFile ();
	if (lzo_init () != LZO_E_OK)	// [RH] Initialize the minilzo package.
		I_FatalError ("Could not initialize LZO routines");

	M_LoadDefaults ();			// load before initing other systems
	C_ExecCmdLineParams (true);	// [RH] do all +set commands on the command line

	I_SetTitleString (IdentifyVersion ());	// [BC] Load up IWAD first

	{
		// [RH] Make sure zdoom.wad is always loaded,
		// as it contains magic stuff we need.
		const char *wad;
		
		wad = BaseFileSearch ("skulltag.wad", NULL);
		if (wad)
			D_AddFile (wad);
		else
			I_FatalError ("Cannot find skulltag.wad!");

		wad = BaseFileSearch ("zvox.wad", NULL);
		if (wad)
			D_AddFile (wad);
	}

	// [RH] Add any .wad files in the skins directory
	{
		char curdir[256];

		if (getcwd (curdir, 256))
		{
			char skindir[256];
			findstate_t findstate;
			long handle;
			int stuffstart;

#ifdef UNIX
			stuffstart = sprintf (skindir, "%sskins", SHARE_DIR);
#else
			stuffstart = sprintf (skindir, "%sskins", progdir);
#endif
			if (!chdir (skindir))
			{
				skindir[stuffstart++] = '/';
				if ((handle = I_FindFirst ("*.wad", &findstate)) != -1)
				{
					do
					{
						if (!(I_FindAttr (&findstate) & FA_DIREC))
						{
							strcpy (skindir + stuffstart,
									I_FindName (&findstate));
							D_AddFile (skindir);
						}
					} while (I_FindNext (handle, &findstate) == 0);
					I_FindClose (handle);
				}
			}

			const char *home = getenv ("HOME");
			if (home)
			{
				stuffstart = sprintf (skindir, "%s%s.zdoom/skins", home,
									  home[strlen(home)-1] == '/' ? "" : "/");
				if (!chdir (skindir))
				{
					skindir[stuffstart++] = '/';
					if ((handle = I_FindFirst ("*.wad", &findstate)) != -1)
					{
						do
						{
							if (!(I_FindAttr (&findstate) & FA_DIREC))
							{
								strcpy (skindir + stuffstart,
										I_FindName (&findstate));
								D_AddFile (skindir);
							}
						} while (I_FindNext (handle, &findstate) == 0);
						I_FindClose (handle);
					}
				}
			}
			chdir (curdir);
		}
	}

	modifiedgame = false;
	pwad = false;

	DArgs *files = Args.GatherFiles ("-file", ".wad", true);
	if (files->NumArgs() > 0)
	{
		// the files gathered are wadfile/lump names
		modifiedgame = true;			// homebrew levels
		int i;
		for (i = 0; i < files->NumArgs(); i++)
		{
			const char *f = BaseFileSearch (files->GetArg (i), ".wad");
			if (f)
				D_AddFile (files->GetArg (i));
		}

		// [csDoom]
		char fullwadname[512];
		int  len, slash=0;

		strcpy (fullwadname, files->GetArg (0));
		len = strlen(fullwadname);

		for (i=0; i<len; i++)
			if (fullwadname[i] == '\\' || fullwadname[i] == '/')
				slash=i+1;
		
		for (i=slash; i<=len; i++)
			wadname[i-slash] = fullwadname[i];

		pwad = true;
	}
	delete files;
	
	W_InitMultipleFiles (&wadfiles);

	// [RH] Moved these up here so that we can do most of our
	//		startup output in a fullscreen console.

	CT_Init ();

	if (!Args.CheckParm("-host"))
	{
		I_Init (false);
		V_Init ();
	}
	else
		I_Init (true);

	Printf (PRINT_HIGH, "D_CheckNetGame: Checking network game status.\n");
	D_CheckNetGame ();

	// Base systems have been inited; enable cvar callbacks
	cvar_t::EnableCallbacks ();

	// [RH] Initialize configurable strings.
	D_InitStrings ();

	// [RH] Apply any DeHackEd patch
	{
		const char *f;
		bool noDef = false;
		int i;

		// try .deh files on command line
		files = Args.GatherFiles ("-deh", ".deh", false);
		if (files->NumArgs() > 0)
		{
			for (i = 0; i < files->NumArgs(); i++)
				if ( (f = BaseFileSearch (files->GetArg (i), ".deh")) )
					DoDehPatch (f, false);
			noDef = true;
		}
		delete files;

		// try .bex files on command line
		files = Args.GatherFiles ("-bex", ".bex", false);
		if (files->NumArgs() > 0)
		{
			for (i = 0; i < files->NumArgs(); i++)
			{
				printf (":%s\n", files->GetArg (i));
				if ( (f = BaseFileSearch (files->GetArg (i), ".bex")) )
					printf ("%s\n", f), DoDehPatch (f, false);
			}
			noDef = true;
		}
		delete files;

		// try default patches
		if (!noDef)
		{
			if (FileExists (def_patch.string))
				// Use patch specified by def_patch.
				DoDehPatch (def_patch.string, true);
			else
				DoDehPatch (NULL, true);	// See if there's a patch in a PWAD
		}
	}

	// [RH] User-configurable startup strings. Because BOOM does.
	if (STARTUP1[0])	Printf (PRINT_HIGH, "%s\n", STARTUP1);
	if (STARTUP2[0])	Printf (PRINT_HIGH, "%s\n", STARTUP2);
	if (STARTUP3[0])	Printf (PRINT_HIGH, "%s\n", STARTUP3);
	if (STARTUP4[0])	Printf (PRINT_HIGH, "%s\n", STARTUP4);
	if (STARTUP5[0])	Printf (PRINT_HIGH, "%s\n", STARTUP5);

	flags = dmflags;
	flags2 = dmflags2;	// [BC]
		
	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");

	// [BC] Mucho new modes
	if (Args.CheckParm ("-altdeath"))
	{
		deathmatch.Set (1.0f);
		flags |= DF_ITEMS_RESPAWN | DF_NO_MONSTERS;	// [BC] Added DF_NO_MONSTERS to all DM settings;
	}
	else if (Args.CheckParm ("-deathmatch"))
	{
		deathmatch.Set (1.0f);
		flags |= DF_WEAPONS_STAY | DF_ITEMS_RESPAWN | DF_NO_MONSTERS;
		flags2 |= DF2_YES_DOUBLEAMMO | DF2_YES_INVUL;
	}
	else if (Args.CheckParm ("-teamgame"))
	{
		teamgame.Set (1.0f);
		deathmatch.Set (0.0f);
		flags |= DF_WEAPONS_STAY | DF_ITEMS_RESPAWN | DF_NO_MONSTERS;
	}
	
	if (Args.CheckParm ("-olddeathmatch"))
	{
		deathmatch.Set (1.0f);
		flags |= DF_WEAPONS_STAY | DF_ITEMS_RESPAWN | DF_NO_MONSTERS;
	}
	else if (Args.CheckParm ("-survival"))
	{
		deathmatch.Set (1.0f);
		flags |= DF_WEAPONS_STAY | DF_ITEMS_RESPAWN | DF_FORCE_RESPAWN | DF_NO_MONSTERS;
		flags2 |= DF2_YES_DOUBLEAMMO | DF2_YES_LOSEFRAG | DF2_YES_INVUL;
	}
	else if (Args.CheckParm ("-terminator"))
	{
		terminator.Set (1.0f);
		deathmatch.Set (1.0f);
		flags |= DF_WEAPONS_STAY | DF_ITEMS_RESPAWN | DF_NO_MONSTERS;
		flags2 |= DF2_YES_DOUBLEAMMO | DF2_YES_INVUL;
	}
	else if (Args.CheckParm ("-instagib"))
	{
		instagib.Set (1.0f);
		deathmatch.Set (1.0f);
		flags |= DF_WEAPONS_STAY | DF_ITEMS_RESPAWN | DF_NO_MONSTERS;
		flags2 |= DF2_YES_DOUBLEAMMO | DF2_YES_INVUL;
	}
	else if (Args.CheckParm ("-fragpoints"))
	{
		fragpoints.Set (1.0f);
	}

	if (Args.CheckParm ("-oldschool"))
	{
		oldschool.Set (1.0f);
		deathmatch.Set (1.0f);
		flags = DF_WEAPONS_STAY | DF_ITEMS_RESPAWN | DF_NO_MONSTERS | DF_NO_FREELOOK | DF_NO_JUMP;
		flags2 = 0;
		//flags = 200708;
	}

	dmflagsvar.Set ((float)flags);
	dmflags2var.Set ((float)flags2);	// [BC]

	// get skill / episode / map from parms
	strcpy (startmap, (gameinfo.flags & GI_MAPxx) ? "MAP01" : "E1M1");
	autostart = false;
				
	const char *val = Args.CheckValue ("-skill");
	if (val)
	{
		gameskill.Set ((float)(val[0]-'1'));
		autostart = true;
	}

	p = Args.CheckParm ("-warp");
	if (p && p < Args.NumArgs() - (1+(gameinfo.flags & GI_MAPxx ? 0 : 1)))
	{
		int ep, map;

		if (gameinfo.flags & GI_MAPxx)
		{
			ep = 1;
			map = atoi (Args.GetArg(p+1));
		}
		else 
		{
			ep = Args.GetArg(p+1)[0]-'0';
			map = Args.GetArg(p+2)[0]-'0';
		}

		// [BC] Indicate 1st scenario
		strncpy (startmap, CalcMapName (ep, map, 1), 8);
		autostart = true;
	}

	// [RH] Hack to handle +map
	p = Args.CheckParm ("+map");
	if (p && p < Args.NumArgs()-1)
	{
		strncpy (startmap, Args.GetArg (p+1), 8);
		Args.GetArg (p)[0] = '-';
		autostart = true;
	}
	if (devparm)
		Printf (PRINT_HIGH, Strings[0].builtin);	// D_DEVSTR

#ifndef UNIX
	if (Args.CheckParm("-cdrom"))
	{
		Printf (PRINT_HIGH, Strings[1].builtin);	// D_CDROM
		mkdir ("c:\\zdoomdat", 0);
	}
#endif

	// turbo option  // [RH] (now a cvar)
	{
		char *value = Args.CheckValue ("-turbo");
		if (value == NULL)
			value = "100";
		else
			Printf (PRINT_HIGH, "turbo scale: %s%\n", value);

		turbo.SetDefault (value);
	}

	v = Args.CheckValue ("-timer");
	if (v)
	{
		double time = atof (v);
		Printf (PRINT_HIGH, "Levels will end after %g minute%s.\n", time, time > 1 ? "s" : "");
		timelimit.Set ((float)time);
	}

	v = Args.CheckValue ("-avg");
	if (v)
	{
		Printf (PRINT_HIGH, "Austin Virtual Gaming: Levels will end after 20 minutes\n");
		timelimit.Set (20);
	}

	// [RH] Now that all text strings are set up,
	// insert them into the level and cluster data.
	G_SetLevelStrings ();
	
	// [RH] Parse through all loaded mapinfo lumps
	G_ParseMapInfo ();

	// [RH] Parse any SNDINFO lumps
	S_ParseSndInfo();

	// Check for -file in shareware
	if (modifiedgame && (gameinfo.flags & GI_SHAREWARE))
		I_FatalError ("\nYou cannot -file with the shareware version. Register!");
	
	if (!Args.CheckParm("-host"))
	{
		Printf (PRINT_HIGH, "M_Init: Init miscellaneous info.\n");
		M_Init ();
	}

	Printf (PRINT_HIGH, "R_Init: Init DOOM refresh daemon\n");
	R_Init ();

	Printf (PRINT_HIGH, "P_Init: Init Playloop state.\n");
	P_Init ();

	Printf (PRINT_HIGH, "S_Init: Setting up sound.\n");
	S_Init ((int)snd_sfxvolume.value /* *8 */, (int)snd_musicvolume.value /* *8*/ );

	I_FinishClockCalibration ();

	if (netstate != NS_SERVER)
	{
		Printf (PRINT_HIGH, "ST_Init: Init status bar.\n");
		ST_Init ();
	}

	// [RH] Setup user info
	D_SetupUserInfo (&players[consoleplayer]);	// [BC] Moved out here

	// start the apropriate game based on parms
	v = Args.CheckValue ("-record");

	if (v)
	{
		G_RecordDemo (v);
		autostart = true;
	}
		
	files = Args.GatherFiles ("-playdemo", ".lmp", false);
	if (files->NumArgs() > 0)
	{
		char blah[256];

		singledemo = true;				// quit after one demo

		strcpy (blah, files->GetArg (0));
		FixPathSeperator (blah);
		ExtractFileBase (blah, file);
		G_DeferedPlayDemo (file);
		D_DoomLoop ();	// never returns
	}
	delete files;

	v = Args.CheckValue ("-timedemo");
	if (v)
	{
		G_TimeDemo (v);
		D_DoomLoop ();	// never returns
	}
		
	v = Args.CheckValue ("-loadgame");
	if (v)
	{
		G_BuildSaveName (file, v[0] - '0');
		G_LoadGame (file);
	}

	// [RH] Initialize items. Still only used for the give command. :-(
	InitItems ();

	// [RH] Lock any cvars that should be locked now that we're
	// about to begin the game.
	cvar_t::EnableNoSet ();

	// [RH] Now that all game subsystems have been initialized,
	// do all commands on the command line other than +set
	if (netstate != NS_SERVER)
		Net_NewMakeTic ();
	C_ExecCmdLineParams (false);

	if (gameaction != ga_loadgame)
	{
		BorderNeedRefresh = true;
		if (autostart)// || netstate != NS_SINGLEPLAYER)
		{
			G_InitNew (startmap);
		}
		else if (netstate != NS_CLIENT)
		{
			D_StartTitle ();				// start up intro loop
		}
	}

	if (demorecording)
		G_BeginRecording ();
				
	if (Args.CheckParm ("-debugfile"))
	{
		char	filename[20];
		sprintf (filename,"debug%i.txt",consoleplayer);
		Printf (PRINT_HIGH, "debug output to: %s\n",filename);
		debugfile = fopen (filename,"w");
	}

	if (netstate != NS_SERVER)
		atterm (D_QuitNetGame);		// killough

	setmodeneeded = false;	// [Fly] we don't need to set a video mode here!
							// [BC] Damn right we don't!

	if (netstate == NS_SERVER)
	{
		if (!autostart)
		{
			if (deathmatch.value && !oldschool.value)
				G_InitNew ("D2DM1");
			else if (teamgame.value && !oldschool.value)
				G_InitNew ("D2CT1");
			else
				G_InitNew (startmap);
		}

		masterupdate = true; // udate the master server (don't wait 25 seconds)
	}

	D_DoomLoop ();		// never returns
}

BEGIN_STAT(fps)
{
	sprintf (out,
		"frame=%04.1f ms  walls=%04.1f ms  planes=%04.1f ms  masked=%04.1f ms",
		(double)FrameCycles * SecondsPerCycle * 1000,
		(double)WallCycles * SecondsPerCycle * 1000,
		(double)PlaneCycles * SecondsPerCycle * 1000,
		(double)MaskedCycles * SecondsPerCycle * 1000
		);
}
END_STAT(fps)
