// 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 <assert.h>

#include "doomerrors.h"

#include "d_gui.h"
#include "m_alloc.h"
#include "m_random.h"
#include "doomdef.h"
#include "doomstat.h"
#include "gstrings.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 "a_doomglobal.h"
#include "gameconfigfile.h"
#include "sbar.h"
#include "decallib.h"
#include "r_polymost.h"

#include "v_text.h"

#include "gl/gl_functions.h"

// comment this out if you only want ZDoom's original.
#define ALTERNATIVE_HUD
EXTERN_CVAR(Bool, hud_althud)
void DrawHUD();

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

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

struct GameAtExit
{
	GameAtExit *Next;
	char Command[1];
};

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

extern void M_RestoreMode ();
extern void M_SetDefaultMode ();
extern void R_ExecuteSetViewSize ();
extern void G_NewInit ();

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

void D_CheckNetGame ();
void D_ProcessEvents ();
void G_BuildTiccmd (ticcmd_t* cmd);
void D_DoAdvanceDemo ();
void D_AddFile (const char *file);
void D_AddWildFile (const char *pattern);

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

void D_DoomLoop ();
static const char *BaseFileSearch (const char *file, const char *ext);
static void STACK_ARGS DoConsoleAtExit ();

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

EXTERN_CVAR (Float, turbo)
EXTERN_CVAR (Int, crosshair)
EXTERN_CVAR (Bool, freelook)
EXTERN_CVAR (Float, m_pitch)
EXTERN_CVAR (Float, m_yaw)
EXTERN_CVAR (Bool, invertmouse)
EXTERN_CVAR (Bool, lookstrafe)

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 gameinfo_t HexenDKGameInfo;
extern gameinfo_t StrifeGameInfo;
extern gameinfo_t StrifeTeaserGameInfo;
extern gameinfo_t StrifeTeaser2GameInfo;

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

extern cycle_t WallCycles, PlaneCycles, MaskedCycles, WallScanCycles;

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

CVAR (Int, fraglimit, 0, CVAR_SERVERINFO);
CVAR (Float, timelimit, 0.f, CVAR_SERVERINFO);
CVAR (Bool, queryiwad, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG);
CVAR (Int, wipetype, 1, CVAR_ARCHIVE);

bool DrawFSHUD;				// [RH] Draw fullscreen HUD?
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
bool PageBlank;
FTexture *Page;
FTexture *Advisory;

cycle_t FrameCycles;

const char *IWADTypeNames[NUM_IWAD_TYPES] =
{
	"DOOM 2: TNT - Evilution",
	"DOOM 2: Plutonia Experiment",
	"Hexen: Beyond Heretic",
	"Hexen: Deathkings of the Dark Citadel",
	"DOOM 2: Hell on Earth",
	"Heretic Shareware",
	"Heretic: Shadow of the Serpent Riders",
	"Heretic",
	"DOOM Shareware",
	"The Ultimate DOOM",
	"DOOM Registered",
	"Strife: Quest for the Sigil",
	"Strife: Teaser (Old Version)",
	"Strife: Teaser (New Version)"
};

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

static wadlist_t **wadtail = &wadfiles;
static int demosequence;
static int pagetic;
static const char *IWADNames[] =
{
	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",
	"heretic.wad",
	"heretic1.wad",
	"hexen.wad",
	"hexdd.wad",
	"strife1.wad",
	"strife0.wad",
	NULL
};
static GameAtExit *ExitCmdList;

// 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 == 1)
		{
			M_SetDefaultMode ();
		}
		else if (testingmode <= I_GetTime(false))
		{
			M_RestoreMode ();
		}
		return;
	}

	for (; eventtail != eventhead ; eventtail = (eventtail+1)&(MAXEVENTS-1))
	{
		ev = &events[eventtail];
		if (C_Responder (ev))
			continue;				// console ate the event
		if (M_Responder (ev))
			continue;				// menu ate the event
		if (testpolymost)
			Polymost_Responder (ev);
		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;
	if (ev->type == EV_Mouse && !testpolymost && !paused && menuactive == MENU_Off &&
		ConsoleState != c_down && ConsoleState != c_falling)
	{
		if (Button_Mlook.bDown || freelook)
		{
			int look = int(ev->y * m_pitch * mouse_sensitivity * 16.0);
			if (invertmouse)
				look = -look;
			G_AddViewPitch (look);
			events[eventhead].y = 0;
		}
		if (!Button_Strafe.bDown && !lookstrafe)
		{
			G_AddViewAngle (int(ev->x * m_yaw * mouse_sensitivity * 8.0));
			events[eventhead].x = 0;
		}
		if ((events[eventhead].x | events[eventhead].y) == 0)
		{
			return;
		}
	}
	eventhead = (eventhead+1)&(MAXEVENTS-1);
}

//==========================================================================
//
// CVAR dmflags
//
//==========================================================================

CUSTOM_CVAR (Int, dmflags, 0, CVAR_SERVERINFO)
{
	// In case DF_NO_FREELOOK was changed, reinitialize the sky
	// map. (If no freelook, then no need to stretch the sky.)
	if (sky1texture != 0)
		R_InitSkyMap ();

	if (self & DF_NO_FREELOOK)
	{
		Net_WriteByte (DEM_CENTERVIEW);
	}
	// If nofov is set, force everybody to the arbitrator's FOV.
	if ((self & DF_NO_FOV) && consoleplayer == Net_Arbitrator)
	{
		BYTE fov;

		Net_WriteByte (DEM_FOV);

		// If the game is started with DF_NO_FOV set, the arbitrator's
		// DesiredFOV will not be set when this callback is run, so
		// be sure not to transmit a 0 FOV.
		fov = (BYTE)players[consoleplayer].DesiredFOV;
		if (fov == 0)
		{
			fov = 90;
		}
		Net_WriteByte (fov);
	}
}

CVAR (Flag, sv_nohealth,		dmflags, DF_NO_HEALTH);
CVAR (Flag, sv_noitems,			dmflags, DF_NO_ITEMS);
CVAR (Flag, sv_weaponstay,		dmflags, DF_WEAPONS_STAY);
CVAR (Flag, sv_falldamage,		dmflags, DF_FORCE_FALLINGHX);
CVAR (Flag, sv_oldfalldamage,	dmflags, DF_FORCE_FALLINGZD);
CVAR (Flag, sv_samelevel,		dmflags, DF_SAME_LEVEL);
CVAR (Flag, sv_spawnfarthest,	dmflags, DF_SPAWN_FARTHEST);
CVAR (Flag, sv_forcerespawn,	dmflags, DF_FORCE_RESPAWN);
CVAR (Flag, sv_noarmor,			dmflags, DF_NO_ARMOR);
CVAR (Flag, sv_noexit,			dmflags, DF_NO_EXIT);
CVAR (Flag, sv_infiniteammo,	dmflags, DF_INFINITE_AMMO);
CVAR (Flag, sv_nomonsters,		dmflags, DF_NO_MONSTERS);
CVAR (Flag, sv_monsterrespawn,	dmflags, DF_MONSTERS_RESPAWN);
CVAR (Flag, sv_itemrespawn,		dmflags, DF_ITEMS_RESPAWN);
CVAR (Flag, sv_fastmonsters,	dmflags, DF_FAST_MONSTERS);
CVAR (Flag, sv_nojump,			dmflags, DF_NO_JUMP);
CVAR (Flag, sv_nofreelook,		dmflags, DF_NO_FREELOOK);
CVAR (Flag, sv_respawnsuper,	dmflags, DF_RESPAWN_SUPER);
CVAR (Flag, sv_nofov,			dmflags, DF_NO_FOV);

//==========================================================================
//
// CVAR dmflags2
//
// [RH] From Skull Tag. Some of these were already done as separate cvars
// (such as bfgaiming), but I collected them here like Skull Tag does.
//
//==========================================================================

CVAR (Int, dmflags2, 0, CVAR_SERVERINFO);
CVAR (Flag, sv_weapondrop,		dmflags2, DF2_YES_WEAPONDROP);
CVAR (Flag, sv_nobfgaim,		dmflags2, DF2_NO_FREEAIMBFG);
CVAR (Flag, sv_respawnprotect,	dmflags2, DF2_YES_INVUL);
CVAR (Flag, sv_barrelrespawn,	dmflags2, DF2_BARRELS_RESPAWN);

//==========================================================================
//
// CVAR compatflags
//
//==========================================================================

CVAR (Int, compatflags, 0, CVAR_ARCHIVE|CVAR_SERVERINFO);
CVAR (Flag, compat_shortTex,	compatflags, COMPATF_SHORTTEX);
CVAR (Flag, compat_stairs,		compatflags, COMPATF_STAIRINDEX);
CVAR (Flag, compat_limitpain,	compatflags, COMPATF_LIMITPAIN);
CVAR (Flag, compat_silentpickup,compatflags, COMPATF_SILENTPICKUP);
CVAR (Flag, compat_nopassover,	compatflags, COMPATF_NO_PASSMOBJ);
CVAR (Flag, compat_soundslots,	compatflags, COMPATF_MAGICSILENCE);
CVAR (Flag, compat_wallrun,		compatflags, COMPATF_WALLRUN);
CVAR (Flag, compat_notossdrops,	compatflags, COMPATF_NOTOSSDROPS);
CVAR (Flag, compat_useblocking, compatflags, COMPATF_USEBLOCKING);
CVAR (Flag, compat_nodoorlight,	compatflags, COMPATF_NODOORLIGHT);
CVAR (Flag, compat_ravenscroll,	compatflags, COMPATF_RAVENSCROLL);

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

void D_Display (bool screenshot)
{
	bool wipe;

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

	if (players[consoleplayer].camera == NULL)
	{
		players[consoleplayer].camera = players[consoleplayer].mo;
	}

	if (viewactive)
	{
		R_SetFOV (players[consoleplayer].camera && players[consoleplayer].camera->player ?
			players[consoleplayer].camera->player->FOV : 90.f);
	}

	// [RH] change the screen mode if needed
	I_CheckRestartRenderer();
	if (setmodeneeded)
	{
		switch(currentrenderer)
		{
		case 0:
			// Change screen mode.
			if (V_SetResolution (NewWidth, NewHeight, NewBits))
			{
				// Recalculate various view parameters.
				setsizeneeded = true;
				// Let the status bar know the screen size changed
				if (StatusBar != NULL)
				{
					StatusBar->ScreenSizeChanged ();
				}
				// Refresh the console.
				C_NewModeAdjust ();
				// Reload crosshair if transitioned to a different size
				crosshair.Callback ();
				setmodeneeded = false;
			}
			break;

		case 1:
			I_RestartRenderer();
			// Let the status bar know the screen size changed
			if (StatusBar != NULL)
			{
				StatusBar->ScreenSizeChanged ();
			}
			// Refresh the console.
			C_NewModeAdjust ();
			// Reload crosshair if transitioned to a different size
			crosshair.Callback ();
			break;
		}
	}

	RenderTarget = screen;

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

	if (screen->Lock (screenshot))
	{
		SB_state = screen->GetPageCount ();
		BorderNeedRefresh = screen->GetPageCount ();
	}

	// [RH] Allow temporarily disabling wipes
	if (NoWipe || currentrenderer == 1)
	{
		BorderNeedRefresh = screen->GetPageCount ();
		if (currentrenderer==0) NoWipe--;
		wipe = false;
		wipegamestate = gamestate;
	}
	else if (gamestate != wipegamestate && gamestate != GS_FULLCONSOLE && gamestate != GS_TITLELEVEL)
	{ // save the current screen if about to wipe
		BorderNeedRefresh = screen->GetPageCount ();
		wipe = true;
		if (wipegamestate != GS_FORCEWIPEFADE)
		{
			wipe_StartScreen (wipetype);
		}
		else
		{
			wipe_StartScreen (wipe_Fade);
		}
		wipegamestate = gamestate;
	}
	else
	{
		wipe = false;
	}

	if (testpolymost)
	{
		drawpolymosttest();
		C_DrawConsole();
		M_Drawer();
	}
	else
	{
		switch (gamestate)
		{
		case GS_FULLCONSOLE:
			C_DrawConsole ();
			M_Drawer ();
			if (!screenshot)
				screen->Update ();
			return;

		case GS_LEVEL:
		case GS_TITLELEVEL:
			if (!gametic)
				break;

			R_RefreshViewBorder ();
			if (currentrenderer==0)
			{
				R_RenderActorView (players[consoleplayer].mo);
				R_DetailDouble ();		// [RH] Apply detail mode expansion
				// [RH] Let cameras draw onto textures that were visible this frame.
				FCanvasTextureInfo::UpdateAll ();
			}
			else
			{
				gl_RenderPlayerView (&players[consoleplayer]);
			}

			if (automapactive)
			{
				int saved_ST_Y=ST_Y;
				if (hud_althud && realviewheight == SCREENHEIGHT) ST_Y=realviewheight;
				AM_Drawer ();
				ST_Y = saved_ST_Y;
			}

	#ifdef ALTERNATIVE_HUD

			if (hud_althud && realviewheight == SCREENHEIGHT)
			{
				if (DrawFSHUD || automapactive) DrawHUD();
				StatusBar->DrawTopStuff (DrawFSHUD ? HUD_Fullscreen : HUD_None);
			}
			else 
	#endif
			if (realviewheight == SCREENHEIGHT && viewactive)
			{
				StatusBar->Draw (DrawFSHUD ? HUD_Fullscreen : HUD_None);
				StatusBar->DrawTopStuff (DrawFSHUD ? HUD_Fullscreen : HUD_None);
			}
			else
			{
				StatusBar->Draw (HUD_StatusBar);
				StatusBar->DrawTopStuff (HUD_StatusBar);
			}
			CT_Drawer ();
			break;

		case GS_INTERMISSION:
			WI_Drawer ();
			CT_Drawer ();
			break;

		case GS_FINALE:
			F_Drawer ();
			CT_Drawer ();
			break;

		case GS_DEMOSCREEN:
			D_PageDrawer ();
			CT_Drawer ();
			break;

		default:
		    break;
		}
	}

	// draw pause pic
	if (paused && menuactive == MENU_Off)
	{
		FTexture *tex;
		int x;

		tex = TexMan[gameinfo.gametype & (GAME_Doom|GAME_Strife) ? "M_PAUSE" : "PAUSED"];
		x = (SCREENWIDTH - tex->GetWidth()*CleanXfac)/2 +
			tex->LeftOffset*CleanXfac;
		screen->DrawTexture (tex, x, 4, DTA_CleanNoMove, true, TAG_DONE);
	}

	// [RH] Draw icon, if any
	if (D_DrawIcon)
	{
		int picnum = TexMan.CheckForTexture (D_DrawIcon, FTexture::TEX_MiscPatch);

		D_DrawIcon = NULL;
		if (picnum >= 0)
		{
			FTexture *tex = TexMan[picnum];
			screen->DrawTexture (tex, 160-tex->GetWidth()/2, 100-tex->GetHeight()/2,
				DTA_320x200, true, TAG_DONE);
		}
		NoWipe = 10;
	}

	NetUpdate ();			// send out any new accumulation

	if (!wipe || screenshot || currentrenderer==1)
	{
		// normal update
		C_DrawConsole ();	// draw console
		M_Drawer ();		// menu is drawn even on top of everything
		FStat::PrintStat ();
		if (!screenshot)
			screen->Update ();	// page flip or blit buffer
	}
	else
	{
		// wipe update
		int wipestart, nowtime, tics;
		BOOL done;

		wipe_EndScreen ();
		screen->Unlock ();

		wipestart = I_GetTime (false) - 1;

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

	unclock (cycles);
	FrameCycles = cycles;
}

//==========================================================================
//
// DoConsoleAtExit
//
// Executes the contents of the atexit cvar, if any, at quit time.
//
//==========================================================================

static void STACK_ARGS DoConsoleAtExit ()
{
	GameAtExit *cmd = ExitCmdList;

	while (cmd != NULL)
	{
		GameAtExit *next = cmd->Next;
		AddCommandString (cmd->Command);
		free (cmd);
		cmd = next;
	}
}

//==========================================================================
//
// CCMD atexit
//
//==========================================================================

CCMD (atexit)
{
	if (argv.argc() == 1)
	{
		Printf ("Registered atexit commands:\n");
		GameAtExit *record = ExitCmdList;
		while (record != NULL)
		{
			Printf ("%s\n", record->Command);
			record = record->Next;
		}
		return;
	}
	for (int i = 1; i < argv.argc(); ++i)
	{
		GameAtExit *record = (GameAtExit *)Malloc (
			sizeof(GameAtExit)+strlen(argv[i]));
		strcpy (record->Command, argv[i]);
		record->Next = ExitCmdList;
		ExitCmdList = record;
	}
}

//==========================================================================
//
// D_ErrorCleanup ()
//
// Cleanup after a recoverable error.
//==========================================================================

void D_ErrorCleanup ()
{
	screen->Unlock ();
	bglobal.RemoveAllBots (true);
	D_QuitNetGame ();
	Net_ClearBuffers ();
	G_NewInit ();
	singletics = false;
	if (demorecording || demoplayback)
		G_CheckDemoStatus ();
	playeringame[0] = 1;
	players[0].playerstate = PST_LIVE;
	gameaction = ga_fullconsole;
	menuactive = MENU_Off;
}

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

void D_DoomLoop ()
{
	int lasttic = 0;

	for (;;)
	{
		try
		{
			// frame syncronous IO operations
			if (gametic > lasttic)
			{
				lasttic = gametic;
				I_StartFrame ();
			}
			
			// process one or more tics
			if (singletics)
			{
				I_StartTic ();
				DObject::BeginFrame ();
				D_ProcessEvents ();
				G_BuildTiccmd (&netcmds[consoleplayer][maketic%BACKUPTICS]);
				//Added by MC: For some of that bot stuff. The main bot function.
				int i;
				for (i = 0; i < MAXPLAYERS; i++)
				{
					if (playeringame[i] && players[i].isbot && players[i].mo)
					{
						players[i].savedyaw = players[i].mo->angle;
						players[i].savedpitch = players[i].mo->pitch;
					}
				}
				bglobal.Main (maketic%BACKUPTICS);
				for (i = 0; i < MAXPLAYERS; i++)
				{
					if (playeringame[i] && players[i].isbot && players[i].mo)
					{
						players[i].mo->angle = players[i].savedyaw;
						players[i].mo->pitch = players[i].savedpitch;
					}
				}
				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.
			I_StartTic ();
			D_Display (false);
		}
		catch (CRecoverableError &error)
		{
			if (error.GetMessage ())
			{
				Printf (PRINT_BOLD, "\n%s\n", error.GetMessage());
			}
			D_ErrorCleanup ();
		}
	}
}

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

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

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

void D_PageDrawer (void)
{
	if (Page != NULL)
	{
		screen->DrawTexture (Page, 0, 0,
			DTA_VirtualWidth, Page->GetWidth(),
			DTA_VirtualHeight, Page->GetHeight(),
			DTA_Masked, false,
			TAG_DONE);
		screen->FillBorder (NULL);
	}
	else
	{
		screen->Clear (0, 0, SCREENWIDTH, SCREENHEIGHT, 0);
		if (!PageBlank)
		{
			screen->DrawText (CR_WHITE, 0, 0, "Page graphic goes here", TAG_DONE);
		}
	}
	if (Advisory != NULL)
	{
		screen->DrawTexture (Advisory, 4, 160, DTA_320x200, true, TAG_DONE);
	}
}

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

void D_AdvanceDemo (void)
{
	advancedemo = true;
}

//==========================================================================
//
// D_DoStrifeAdvanceDemo
//
//==========================================================================

void D_DoStrifeAdvanceDemo ()
{
	static const char *const fullVoices[6] =
	{
		"svox/pro1", "svox/pro2", "svox/pro3", "svox/pro4", "svox/pro5", "svox/pro6"
	};
	static const char *const teaserVoices[6] =
	{
		"svox/voc91", "svox/voc92", "svox/voc93", "svox/voc94", "svox/voc95", "svox/voc96"
	};
	const char *const *voices = gameinfo.flags & GI_SHAREWARE ? teaserVoices : fullVoices;
	const char *pagename = NULL;

	gamestate = GS_DEMOSCREEN;
	PageBlank = false;

	switch (demosequence)
	{
	default:
	case 0:
		pagetic = 6 * TICRATE;
		pagename = "TITLEPIC";
		if (Wads.CheckNumForName ("d_logo") < 0)
		{ // strife0.wad does not have d_logo
			S_StartMusic ("");
		}
		else
		{
			S_StartMusic ("d_logo");
		}
		C_HideConsole ();
		break;

	case 1:
		// [RH] Strife fades to black and then to the Rogue logo, but
		// I think it looks better if it doesn't fade.
		pagetic = 10 * TICRATE/35;
		pagename = "";	// PANEL0, but strife0.wad doesn't have it, so don't use it.
		PageBlank = true;
		S_Sound (CHAN_VOICE, "bishop/active", 1, ATTN_NORM);
		break;

	case 2:
		pagetic = 4 * TICRATE;
		pagename = "RGELOGO";
		break;

	case 3:
		pagetic = 7 * TICRATE;
		pagename = "PANEL1";
		S_Sound (CHAN_VOICE, voices[0], 1, ATTN_NORM);
		S_StartMusic ("d_intro");
		break;

	case 4:
		pagetic = 9 * TICRATE;
		pagename = "PANEL2";
		S_Sound (CHAN_VOICE, voices[1], 1, ATTN_NORM);
		break;

	case 5:
		pagetic = 12 * TICRATE;
		pagename = "PANEL3";
		S_Sound (CHAN_VOICE, voices[2], 1, ATTN_NORM);
		break;

	case 6:
		pagetic = 11 * TICRATE;
		pagename = "PANEL4";
		S_Sound (CHAN_VOICE, voices[3], 1, ATTN_NORM);
		break;

	case 7:
		pagetic = 10 * TICRATE;
		pagename = "PANEL5";
		S_Sound (CHAN_VOICE, voices[4], 1, ATTN_NORM);
		break;

	case 8:
		pagetic = 16 * TICRATE;
		pagename = "PANEL6";
		S_Sound (CHAN_VOICE, voices[5], 1, ATTN_NORM);
		break;

	case 9:
		pagetic = 6 * TICRATE;
		pagename = "vellogo";
		wipegamestate = GS_FORCEWIPEFADE;
		break;

	case 10:
		pagetic = 12 * TICRATE;
		pagename = "CREDIT";
		wipegamestate = GS_FORCEWIPEFADE;
		break;
	}
	if (demosequence++ > 10)
		demosequence = 0;
	if (demosequence == 9 && !(gameinfo.flags & GI_SHAREWARE))
		demosequence = 10;

	if (pagename)
	{
		if (Page != NULL)
		{
			Page->Unload ();
			Page = NULL;
		}
		if (pagename[0])
		{
			Page = TexMan[pagename];
		}
	}
}

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

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

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

	// [RH] If you want something more dynamic for your title, create a map
	// and name it TITLEMAP. That map will be loaded and used as the title.
	if (Wads.CheckNumForName ("TITLEMAP") >= 0)
	{
		G_InitNew ("TITLEMAP", true);
		return;
	}

	if (gameinfo.gametype == GAME_Strife)
	{
		D_DoStrifeAdvanceDemo ();
		return;
	}

	switch (demosequence)
	{
	case 3:
		if (gameinfo.advisoryTime)
		{
			Advisory = TexMan["ADVISOR"];
			demosequence = 1;
			pagetic = (int)(gameinfo.advisoryTime * TICRATE);
			break;
		}
		// fall through to case 1 if no advisory notice

	case 1:
		Advisory = NULL;
		if (!M_DemoNoPlay)
		{
			BorderNeedRefresh = screen->GetPageCount ();
			democount++;
			sprintf (demoname + 4, "%d", democount);
			if (Wads.CheckNumForName (demoname) < 0)
			{
				demosequence = 0;
				democount = 0;
				// 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)
	{
		if (Page != NULL)
		{
			Page->Unload ();
		}
		Page = TexMan[pagename];
	}
}

//==========================================================================
//
// 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
//
//==========================================================================

CCMD (endgame)
{
	if (!netgame)
	{
		gameaction = ga_fullconsole;
		demosequence = -1;
	}
}

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

void D_AddFile (const char *file)
{
	if (file == NULL)
	{
		return;
	}

	if (!FileExists (file))
	{
		const char *f = BaseFileSearch (file, ".wad");
		if (f == NULL)
		{
			Printf ("Can't find '%s'\n", file);
			return;
		}
		file = f;
	}
	wadlist_t *wad = (wadlist_t *)Malloc (sizeof(*wad) + strlen(file));

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

//==========================================================================
//
// D_AddWildFile
//
//==========================================================================

void D_AddWildFile (const char *value)
{
	const char *wadfile = BaseFileSearch (value, ".wad");

	if (wadfile != NULL)
	{
		D_AddFile (wadfile);
	}
	else
	{ // Try pattern matching
		findstate_t findstate;
		char path[PATH_MAX];
		char *sep;
		void *handle = I_FindFirst (value, &findstate);

		strcpy (path, value);
		sep = strrchr (path, '/');
		if (sep == NULL)
		{
			sep = strrchr (path, '\\');
#ifdef _WIN32
			if (sep == NULL && path[1] == ':')
			{
				sep = path + 1;
			}
#endif
		}

		if (handle != ((void *)-1))
		{
			do
			{
				if (sep == NULL)
				{
					D_AddFile (I_FindName (&findstate));
				}
				else
				{
					strcpy (sep+1, I_FindName (&findstate));
					D_AddFile (path);
				}
			} while (I_FindNext (handle, &findstate) == 0);
		}
		I_FindClose (handle);
	}
}

//==========================================================================
//
// D_AddConfigWads
//
// Adds all files in the specified config file section.
//
//==========================================================================

void D_AddConfigWads (const char *section)
{
	if (GameConfig->SetSection (section))
	{
		const char *key;
		const char *value;
		FConfigFile::Position pos;

		while (GameConfig->NextInSection (key, value))
		{
			if (stricmp (key, "Path") == 0)
			{
				// D_AddWildFile resets GameConfig's position, so remember it
				GameConfig->GetPosition (pos);
				D_AddWildFile (value);
				// Reset GameConfig's position to get next wad
				GameConfig->SetPosition (pos);
			}
		}
	}
}

//==========================================================================
//
// D_AddDirectory
//
// Add all .wad files in a directory. Does not descend into subdirectories.
//
//==========================================================================

static void D_AddDirectory (const char *dir)
{
	char curdir[PATH_MAX];

	if (getcwd (curdir, PATH_MAX))
	{
		char skindir[PATH_MAX];
		findstate_t findstate;
		void *handle;
		size_t stuffstart;

		stuffstart = strlen (dir);
		memcpy (skindir, dir, stuffstart*sizeof(*dir));
		skindir[stuffstart] = 0;

		if (skindir[stuffstart-1] == '/')
		{
			skindir[--stuffstart] = 0;
		}

		if (!chdir (skindir))
		{
			skindir[stuffstart++] = '/';
			if ((handle = I_FindFirst ("*.wad", &findstate)) != (void *)-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);
	}
}

//==========================================================================
//
// SetIWAD
//
// Sets parameters for the game using the specified IWAD.
//==========================================================================

static void SetIWAD (const char *iwadpath, EIWADType type)
{
	static const struct
	{
		GameMode_t Mode;
		const gameinfo_t *Info;
		GameMission_t Mission;
	} Datas[NUM_IWAD_TYPES] = {
		{ commercial,	&CommercialGameInfo,	pack_tnt },		// Doom2TNT
		{ commercial,	&CommercialGameInfo,	pack_plut },	// Doom2Plutonia
		{ commercial,	&HexenGameInfo,			doom2 },		// Hexen
		{ commercial,	&HexenDKGameInfo,		doom2 },		// HexenDK
		{ commercial,	&CommercialGameInfo,	doom2 },		// Doom2
		{ shareware,	&HereticSWGameInfo,		doom },			// HereticShareware
		{ retail,		&HereticGameInfo,		doom },			// HereticExtended
		{ retail,		&HereticGameInfo,		doom },			// Heretic
		{ shareware,	&SharewareGameInfo,		doom },			// DoomShareware
		{ retail,		&RetailGameInfo,		doom },			// UltimateDoom
		{ registered,	&RegisteredGameInfo,	doom },			// DoomRegistered
		{ commercial,	&StrifeGameInfo,		doom2 },		// Strife
		{ commercial,	&StrifeTeaserGameInfo,	doom2 },		// StrifeTeaser
		{ commercial,	&StrifeTeaser2GameInfo,	doom2 },		// StrifeTeaser2
	};

	D_AddFile (iwadpath);

	if ((unsigned)type < NUM_IWAD_TYPES)
	{
		gamemode = Datas[type].Mode;
		gameinfo = *Datas[type].Info;
		gamemission = Datas[type].Mission;
		if (type == IWAD_HereticExtended)
		{
			gameinfo.flags |= GI_MENUHACK_EXTENDED;
		}
	}
	else
	{
		gamemode = undetermined;
	}
}

//==========================================================================
//
// ScanIWAD
//
// Scan the contents of an IWAD to determine which one it is
//==========================================================================

static EIWADType ScanIWAD (const char *iwad)
{
	static const char checklumps[][8] =
	{
		"E1M1",
		"E4M1",
		"MAP01",
		"MAP60",
		"TITLE",
		"REDTNT2",
		"CAMO1",
		{ 'E','X','T','E','N','D','E','D'},
		"ENDSTRF",
		"MAP33",
		"INVCURS",
		"E2M1","E2M2","E2M3","E2M4","E2M5","E2M6","E2M7","E2M8","E2M9",
		"E3M1","E3M2","E3M3","E3M4","E3M5","E3M6","E3M7","E3M8","E3M9",
		"DPHOOF","BFGGA0","HEADA1","CYBRA1",
		{ 'S','P','I','D','A','1','D','1' },
	};
#define NUM_CHECKLUMPS (sizeof(checklumps)/8)
	enum
	{
		Check_e1m1,
		Check_e4m1,
		Check_map01,
		Check_map60,
		Check_title,
		Check_redtnt2,
		Check_cam01,
		Check_Extended,
		Check_endstrf,
		Check_map33,
		Check_invcurs,
		Check_e2m1
	};
	int lumpsfound[NUM_CHECKLUMPS];
	size_t i;
	wadinfo_t header;
	FILE *f;

	memset (lumpsfound, 0, sizeof(lumpsfound));
	if ( (f = fopen (iwad, "rb")) )
	{
		fread (&header, sizeof(header), 1, f);
		if (header.Magic == IWAD_ID || header.Magic == PWAD_ID)
		{
			header.NumLumps = LittleLong(header.NumLumps);
			if (0 == fseek (f, LittleLong(header.InfoTableOfs), SEEK_SET))
			{
				for (i = 0; i < (size_t)header.NumLumps; i++)
				{
					wadlump_t lump;
					size_t j;

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

	if (lumpsfound[Check_title] && lumpsfound[Check_map60])
	{
		return IWAD_HexenDK;
	}
	else if (lumpsfound[Check_map33] && lumpsfound[Check_endstrf])
	{
		if (lumpsfound[Check_map01])
		{
			return IWAD_Strife;
		}
		else if (lumpsfound[Check_invcurs])
		{
			return IWAD_StrifeTeaser2;
		}
		else
		{
			return IWAD_StrifeTeaser;
		}
	}
	else if (lumpsfound[Check_map01])
	{
		if (lumpsfound[Check_redtnt2])
		{
			return IWAD_Doom2TNT;
		}
		else if (lumpsfound[Check_cam01])
		{
			return IWAD_Doom2Plutonia;
		}
		else
		{
			if (lumpsfound[Check_title])
			{
				return IWAD_Hexen;
			}
			else
			{
				return IWAD_Doom2;
			}
		}
	}
	else if (lumpsfound[Check_e1m1])
	{
		if (lumpsfound[Check_title])
		{
			if (!lumpsfound[Check_e2m1])
			{
				return IWAD_HereticShareware;
			}
			else
			{
				if (lumpsfound[Check_Extended])
				{
					return IWAD_HereticExtended;
				}
				else
				{
					return IWAD_Heretic;
				}
			}
		}
		else
		{
			for (i = Check_e2m1; i < NUM_CHECKLUMPS; i++)
			{
				if (!lumpsfound[i])
				{
					return IWAD_DoomShareware;
				}
			}
			if (i == NUM_CHECKLUMPS)
			{
				if (lumpsfound[Check_e4m1])
				{
					return IWAD_UltimateDoom;
				}
				else
				{
					return IWAD_DoomRegistered;
				}
			}
		}
	}
	return NUM_IWAD_TYPES;	// Don't know
}

//==========================================================================
//
// CheckIWAD
//
// Tries to find an IWAD from a set of known IWAD names, and checks the
// contents of each one found to determine which game it belongs to.
// Returns the number of new wads found in this pass (does not count wads
// found from a previous call).
// 
//==========================================================================

static int CheckIWAD (const char *doomwaddir, WadStuff *wads)
{
	const char *slash;
	char iwad[512];
	int i;
	int numfound;

	numfound = 0;

	slash = (doomwaddir[0] && doomwaddir[strlen (doomwaddir)-1] != '/') ? "/" : "";

	// Search for a pre-defined IWAD
	for (i = IWADNames[0] ? 0 : 1; IWADNames[i]; i++)
	{
		if (wads[i].Path == NULL)
		{
			sprintf (iwad, "%s%s%s", doomwaddir, slash, IWADNames[i]);
			FixPathSeperator (iwad);
			if (FileExists (iwad))
			{
				wads[i].Type = ScanIWAD (iwad);
				if (wads[i].Type != NUM_IWAD_TYPES)
				{
					wads[i].Path = copystring (iwad);
					numfound++;
				}
			}
		}
	}

	return numfound;
}

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

static int CheckIWADinEnvDir (const char *envname, WadStuff *wads)
{
	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, wads);
	}
	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)
//
// The search path can be altered by editing the IWADSearch.Directories
// section of the config file.
//
//==========================================================================

static EIWADType IdentifyVersion (void)
{
	WadStuff wads[sizeof(IWADNames)/sizeof(char *)];
	size_t foundwads[NUM_IWAD_TYPES] = { 0 };
	const char *iwadparm = Args.CheckValue ("-iwad");
	size_t numwads;
	int pickwad;
	size_t i;
	bool iwadparmfound = false;

	memset (wads, 0, sizeof(wads));

	if (iwadparm)
	{
		char *custwad = new char[strlen (iwadparm) + 5];
		strcpy (custwad, iwadparm);
		FixPathSeperator (custwad);
		if (CheckIWAD (custwad, wads))
		{ // -iwad parameter was a directory
			delete[] custwad;
			iwadparm = NULL;
		}
		else
		{
			DefaultExtension (custwad, ".wad");
			iwadparm = custwad;
			IWADNames[0] = iwadparm;
			CheckIWAD ("", wads);
		}
	}

	if (iwadparm == NULL || wads[0].Path == NULL)
	{
		if (GameConfig->SetSection ("IWADSearch.Directories"))
		{
			const char *key;
			const char *value;

			while (GameConfig->NextInSection (key, value))
			{
				if (stricmp (key, "Path") == 0)
				{
					if (*value == '$')
					{
						if (stricmp (value + 1, "progdir") == 0)
						{
							CheckIWAD (progdir, wads);
						}
						else
						{
							CheckIWADinEnvDir (value + 1, wads);
						}
					}
#ifdef unix
					else if (*value == '~' && (*(value + 1) == 0 || *(value + 1) == '/'))
					{
						string homepath = GetUserFile (*(value + 1) ? value + 2 : value + 1);
						CheckIWAD (homepath.GetChars(), wads);
					}
#endif
					else
					{
						CheckIWAD (value, wads);
					}
				}
			}
		}
	}

	if (iwadparm != NULL && wads[0].Path != NULL)
	{
		iwadparmfound = true;
	}

	for (i = numwads = 0; i < sizeof(IWADNames)/sizeof(char *); i++)
	{
		if (wads[i].Path != NULL)
		{
			if (i != numwads)
			{
				wads[numwads] = wads[i];
			}
			foundwads[wads[numwads].Type] = numwads+1;
			numwads++;
		}
	}

	if (foundwads[IWAD_HexenDK] && !foundwads[IWAD_Hexen])
	{ // Cannot play Hexen DK without Hexen
		size_t kill = foundwads[IWAD_HexenDK];
		if (kill != numwads)
		{
			memmove (&wads[kill-1], &wads[kill], numwads - kill);
		}
		numwads--;
	}

	if (numwads == 0)
	{
		I_FatalError ("Cannot find a game IWAD (doom.wad, doom2.wad, heretic.wad, etc.).\n"
					  "Did you install ZDoom properly? You can do either of the following:\n"
					  "\n"
					  "1. Place one or more of these wads in the same directory as ZDoom.\n"
					  "2. Edit your zdoom.ini and add the directories of your iwads\n"
					  "to the list beneath [IWADSearch.Directories]");
	}

	pickwad = 0;

	if (!iwadparmfound && numwads > 1 && queryiwad)
	{
		pickwad = I_PickIWad (wads, (int)numwads);
	}

	if (pickwad < 0)
		exit (0);

	if (wads[pickwad].Type == IWAD_HexenDK)
	{ // load hexen.wad before loading hexdd.wad
		D_AddFile (wads[foundwads[IWAD_Hexen]-1].Path);
	}

	SetIWAD (wads[pickwad].Path, wads[pickwad].Type);

	if (wads[pickwad].Type == IWAD_Strife)
	{ // Try to load voices.wad along with strife1.wad
		char *filepart = strrchr (wads[pickwad].Path, '/');
		if (filepart == NULL)
		{
			filepart = wads[pickwad].Path;
		}
		else
		{
			++filepart;
		}
		strcpy (filepart, "voices.wad");
		D_AddFile (wads[pickwad].Path);
	}

	for (i = 0; i < numwads; i++)
	{
		delete[] wads[i].Path;
	}

	return wads[pickwad].Type;
}

//==========================================================================
//
// BaseFileSearch
//
// If a file does not exist at <file>, looks for it in the directories
// specified in the config file. Returns the path to the file, if found,
// or NULL if it could not be found.
//
//==========================================================================

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

	if (FileExists (file))
	{
		return file;
	}

	if (GameConfig->SetSection ("FileSearch.Directories"))
	{
		const char *key;
		const char *value;

		while (GameConfig->NextInSection (key, value))
		{
			if (stricmp (key, "Path") == 0)
			{
				const char *dir;
				string homepath;

				if (*value == '$')
				{
					if (stricmp (value + 1, "progdir") == 0)
					{
						dir = progdir;
					}
					else
					{
						dir = getenv (value + 1);
					}
				}
#ifdef unix
				else if (*value == '~' && (*(value + 1) == 0 || *(value + 1) == '/'))
				{
					homepath = GetUserFile (*(value + 1) ? value + 2 : value + 1);
					dir = homepath.GetChars();
				}
#endif
				else
				{
					dir = value;
				}
				if (dir != NULL)
				{
					sprintf (wad, "%s%s%s", dir, dir[strlen (dir) - 1] != '/' ? "/" : "", file);
					if (FileExists (wad))
					{
						return wad;
					}
				}
			}
		}
	}

	// Retry, this time with a default extension
	if (ext != NULL)
	{
		string tmp = file;
		DefaultExtension (tmp, ext);
		return BaseFileSearch (tmp.GetChars(), NULL);
	}
	return NULL;
}

//==========================================================================
//
// ConsiderPatches
//
// Tries to add any deh/bex patches from the command line.
//
//==========================================================================

bool ConsiderPatches (const char *arg, const char *ext)
{
	bool noDef = false;
	DArgs *files = Args.GatherFiles (arg, ext, false);

	if (files->NumArgs() > 0)
	{
		int i;
		const char *f;

		for (i = 0; i < files->NumArgs(); ++i)
		{
			if ( (f = BaseFileSearch (files->GetArg (i), ".deh")) )
				DoDehPatch (f, false);
			else if ( (f = BaseFileSearch (files->GetArg (i), ".bex")) )
				DoDehPatch (f, false);
		}
		noDef = true;
	}
	delete files;
	return noDef;
}

//==========================================================================
//
// D_LoadWadSettings
//
// Parses any loaded KEYCONF lumps. These are restricted console scripts
// that can only execute the alias, defaultbind, addkeysection,
// addmenukey, weaponsection, and addslotdefault commands.
//
//==========================================================================

void D_LoadWadSettings ()
{
	char cmd[4096];
	int lump, lastlump = 0;

	ParsingKeyConf = true;

	while ((lump = Wads.FindLump ("KEYCONF", &lastlump)) != -1)
	{
		FMemLump data = Wads.ReadLump (lump);
		const char *eof = (char *)data.GetMem() + Wads.LumpLength (lump);
		const char *conf = (char *)data.GetMem();

		while (conf < eof)
		{
			size_t i;

			// Fetch a line to execute
			for (i = 0; conf + i < eof && conf[i] != '\n'; ++i)
			{
				cmd[i] = conf[i];
			}
			cmd[i] = 0;
			conf += i;
			if (*conf == '\n')
			{
				conf++;
			}

			// Comments begin with //
			char *stop = cmd + i - 1;
			char *comment = cmd;
			int inQuote = 0;

			if (*stop == '\r')
				*stop-- = 0;

			while (comment < stop)
			{
				if (*comment == '\"')
				{
					inQuote ^= 1;
				}
				else if (!inQuote && *comment == '/' && *(comment + 1) == '/')
				{
					break;
				}
				comment++;
			}
			if (comment == cmd)
			{ // Comment at line beginning
				continue;
			}
			else if (comment < stop)
			{ // Comment in middle of line
				*comment = 0;
			}

			AddCommandString (cmd);
		}
	}
	ParsingKeyConf = false;
}

//==========================================================================
//
// D_MultiExec
//
//==========================================================================

void D_MultiExec (DArgs *list, bool usePullin)
{
	for (int i = 0; i < list->NumArgs(); ++i)
	{
		C_ExecFile (list->GetArg (i), usePullin);
	}
}

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

void D_DoomMain (void)
{
	int p, flags;
	char file[PATH_MAX];
	char *v;
	const char *wad;
	DArgs *execFiles;

	file[PATH_MAX-1] = 0;

#if defined(_MSC_VER) || defined(__GNUC__)
	TypeInfo::StaticInit ();
#endif

	atterm (DObject::StaticShutdown);
	atterm (DoConsoleAtExit);

	gamestate = GS_STARTUP;

	SetLanguageIDs ();

	rngseed = (DWORD)time (NULL);
	FRandom::StaticClearRandom ();
	M_FindResponseFile ();
	M_LoadDefaults ();			// load before initing other systems

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

	I_SetTitleString (IWADTypeNames[IdentifyVersion ()]);
	GameConfig->DoGameSetup (GameNames[gameinfo.gametype]);

	// Run automatically executed files
	execFiles = new DArgs;
	GameConfig->AddAutoexec (execFiles, GameNames[gameinfo.gametype]);
	D_MultiExec (execFiles, true);
	delete execFiles;

	// Run .cfg files at the start of the command line.
	execFiles = Args.GatherFiles (NULL, ".cfg", false);
	D_MultiExec (execFiles, true);
	delete execFiles;

	C_ExecCmdLineParams ();		// [RH] do all +set commands on the command line

	// [RH] zvox.wad - A wad I had intended to be automatically generated
	// from Q2's pak0.pak so the female and cyborg player could have
	// voices. I never got around to writing the utility to do it, though.
	wad = BaseFileSearch ("zvox.wad", NULL);
	if (wad)
		D_AddFile (wad);

	// [RH] Add any .wad files in the skins directory
#ifdef unix
	sprintf (file, "%sskins", SHARE_DIR);
#else
	sprintf (file, "%sskins", progdir);
#endif
	D_AddDirectory (file);

	const char *home = getenv ("HOME");
	if (home)
	{
		sprintf (file, "%s%s.zdoom/skins", home,
			home[strlen(home)-1] == '/' ? "" : "/");
		D_AddDirectory (file);
	}

	if (!(gameinfo.flags & GI_SHAREWARE))
	{
		// Add common (global) wads
		D_AddConfigWads ("Global.Autoload");

		// Add game-specific wads
		sprintf (file, "%s.Autoload", GameNames[gameinfo.gametype]);
		D_AddConfigWads (file);
	}

	DArgs *files = Args.GatherFiles ("-file", ".wad", true);
	DArgs *files1 = Args.GatherFiles (NULL, ".zip", false);
	DArgs *files2 = Args.GatherFiles (NULL, ".pk3", false);
	if (files->NumArgs() > 0 || files1->NumArgs() > 0 || files2->NumArgs() > 0)
	{
		// Check for -file in shareware
		if (gameinfo.flags & GI_SHAREWARE)
		{
			I_FatalError ("You cannot -file with the shareware version. Register!");
		}

		// the files gathered are wadfile/lump names
		for (int i = 0; i < files->NumArgs(); i++)
		{
			D_AddWildFile (files->GetArg (i));
		}
		for (int i = 0; i < files1->NumArgs(); i++)
		{
			D_AddWildFile (files1->GetArg (i));
		}
		for (int i = 0; i < files2->NumArgs(); i++)
		{
			D_AddWildFile (files2->GetArg (i));
		}
	}
	delete files;
	delete files1;
	delete files2;

	Wads.InitMultipleFiles (&wadfiles);

	// [RH] Initialize localizable strings.
	GStrings.LoadStrings (false);

	//P_InitXlat ();

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

	CT_Init ();
	I_Init ();
	V_Init ();

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


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

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

	FActorInfo::StaticInit ();

	// [RH] Load custom key and weapon settings from WADs
	D_LoadWadSettings ();

	FActorInfo::StaticGameSet ();

	Printf ("Init DOOM refresh subsystem.\n");
	R_Init ();

	DecalLibrary.Clear ();
	DecalLibrary.ReadAllDecals ();

	// [RH] Try adding .deh and .bex files on the command line.
	// If there are none, try adding any in the config file.

	if (gameinfo.gametype == GAME_Doom)
	{
		if (!ConsiderPatches ("-deh", ".deh") &&
			!ConsiderPatches ("-bex", ".bex") &&
			GameConfig->SetSection ("Doom.DefaultDehacked"))
		{
			const char *key;
			const char *value;

			while (GameConfig->NextInSection (key, value))
			{
				if (stricmp (key, "Path") == 0 && FileExists (value))
				{
					Printf ("Applying patch %s\n", value);
					DoDehPatch (value, true);
				}
			}
		}

		DoDehPatch (NULL, true);	// See if there's a patch in a PWAD
		FinishDehPatch ();			// Create replacements for dehacked pickups
	}

	FActorInfo::StaticSetActorNums ();

	// [RH] User-configurable startup strings. Because BOOM does.
	static const char *startupString[5] = {
		"STARTUP1", "STARTUP2", "STARTUP3", "STARTUP4", "STARTUP5"
	};
	for (p = 0; p < 5; ++p)
	{
		const char *str = GStrings[startupString[p]];
		if (str != NULL && str[0] != '\0')
		{
			Printf ("%s\n", str);
		}
	}

	//Added by MC:
	bglobal.getspawned = Args.GatherFiles ("-bots", "", false);
	if (bglobal.getspawned->NumArgs() == 0)
	{
		delete bglobal.getspawned;
		bglobal.getspawned = NULL;
	}
	else
	{
		bglobal.spawn_tries = 0;
		bglobal.wanted_botnum = bglobal.getspawned->NumArgs();
	}

	flags = dmflags;
		
	if (Args.CheckParm ("-nomonsters"))		flags |= DF_NO_MONSTERS;
	if (Args.CheckParm ("-respawn"))		flags |= DF_MONSTERS_RESPAWN;
	if (Args.CheckParm ("-fast"))			flags |= DF_FAST_MONSTERS;

	devparm = Args.CheckParm ("-devparm");

	if (Args.CheckParm ("-altdeath"))
	{
		deathmatch = 1;
		flags |= DF_ITEMS_RESPAWN;
	}
	else if (Args.CheckParm ("-deathmatch"))
	{
		deathmatch = 1;
		flags |= DF_WEAPONS_STAY | DF_ITEMS_RESPAWN;
	}

	dmflags = flags;

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

	p = Args.CheckParm ("-warp");
	if (p && p < Args.NumArgs() - 1)
	{
		int ep, map;

		if (gameinfo.flags & GI_MAPxx)
		{
			ep = 1;
			map = atoi (Args.GetArg(p+1));
		}
		else 
		{
			ep = Args.GetArg(p+1)[0]-'0';
			map = p < Args.NumArgs() - 2 ? Args.GetArg(p+2)[0]-'0' : 10;
			if ((unsigned)map > 9)
			{
				map = ep;
				ep = 1;
			}
		}

		strncpy (startmap, CalcMapName (ep, map), 8);
		autostart = true;
	}

	//I_Error ("Oh gnos!");
	// [RH] Hack to handle +map
	p = Args.CheckParm ("+map");
	if (p && p < Args.NumArgs()-1)
	{
		if (Wads.CheckNumForName (Args.GetArg (p+1)) == -1)
		{
			Printf ("Can't find map %s\n", Args.GetArg (p+1));
		}
		else
		{
			strncpy (startmap, Args.GetArg (p+1), 8);
			Args.GetArg (p)[0] = '-';
			autostart = true;
		}
	}
	if (devparm)
	{
		Printf (GStrings("D_DEVSTR"));
	}

#ifndef unix
	// We do not need to support -cdrom under Unix, because all the files
	// that would go to c:\\zdoomdat are already stored in .zdoom inside
	// the user's home directory.
	if (Args.CheckParm("-cdrom"))
	{
		Printf (GStrings("D_CDROM"));
		mkdir ("c:\\zdoomdat", 0);
	}
#endif

	// turbo option  // [RH] (now a cvar)
	{
		UCVarValue value;

		value.String = Args.CheckValue ("-turbo");
		if (value.String == NULL)
			value.String = "100";
		else
			Printf ("turbo scale: %s%%\n", value.String);

		turbo.SetGenericRepDefault (value, CVAR_String);
	}

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

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

	Printf ("Init miscellaneous info.\n");
	M_Init ();

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

	Printf ("Setting up sound.\n");
	S_Init ();

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

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

	// [RH] Print an informative message if -heapsize is used
	if (Args.CheckParm ("-heapsize"))
	{
		Printf (TEXTCOLOR_ORANGE "Starting with -heapsize is unnecessary.\n"
				TEXTCOLOR_ORANGE "The zone heap is not used anymore.\n");
	}

	// [RH] Run any saved commands from the command line or autoexec.cfg now.
	gamestate = GS_FULLCONSOLE;
	Net_NewMakeTic ();
	DObject::BeginFrame ();
	DThinker::RunThinkers ();
	DObject::EndFrame ();
	gamestate = GS_STARTUP;

	// 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)
	{
		singledemo = true;				// quit after one demo
		G_DeferedPlayDemo (files->GetArg (0));
		delete files;
		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)
	{
		strncpy (file, v, sizeof(file)-1);
		FixPathSeperator (file);
		DefaultExtension (file, ".zds");
		G_LoadGame (file);
	}

	if (gameaction != ga_loadgame)
	{
		BorderNeedRefresh = screen->GetPageCount ();
		if (autostart || netgame)
		{
			CheckWarpTransMap (startmap, true);
			G_InitNew (startmap, false);
		}
		else
		{
			D_StartTitle ();				// start up intro loop
		}
	}

	if (demorecording)
		G_BeginRecording ();
				
	atterm (D_QuitNetGame);		// killough

	D_DoomLoop ();		// never returns
}

//==========================================================================
//
// STAT fps
//
// Displays statistics about rendering times
//
//==========================================================================

ADD_STAT (fps, out)
{
	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
		);
}

//==========================================================================
//
// STAT wallcycles
//
// Displays the minimum number of cycles spent drawing walls
//
//==========================================================================

static cycle_t bestwallcycles = INT_MAX;

ADD_STAT (wallcycles, out)
{
	if (WallCycles && WallCycles < bestwallcycles)
		bestwallcycles = WallCycles;
	sprintf (out, "%lu", bestwallcycles);
}

//==========================================================================
//
// CCMD clearwallcycles
//
// Resets the count of minimum wall drawing cycles
//
//==========================================================================

CCMD (clearwallcycles)
{
	bestwallcycles = INT_MAX;
}

#if 1
// To use these, also uncomment the clock/unclock in wallscan
static cycle_t bestscancycles = INT_MAX;

ADD_STAT (scancycles, out)
{
	if (WallScanCycles && WallScanCycles < bestscancycles)
		bestscancycles = WallScanCycles;
	sprintf (out, "%lu", bestscancycles);
}

CCMD (clearscancycles)
{
	bestscancycles = INT_MAX;
}
#endif
