Exemplo n.º 1
0
// moved into a helper function to ensure args is destroyed before
// exit(), which may result in a memory leak.
static void RunGameOrAtlas(int argc, const char* argv[])
{
	CmdLineArgs args(argc, argv);

	// We need to initialise libxml2 in the main thread before
	// any thread uses it. So initialise it here before we
	// might run Atlas.
	CXeromyces::Startup();

	// run Atlas (if requested via args)
	bool ran_atlas = ATLAS_RunIfOnCmdLine(args, false);
	// Atlas handles the whole init/shutdown/etc sequence by itself;
	// when we get here, it has exited and we're done.
	if(ran_atlas)
		return;

	// run non-visual simulation replay if requested
	if (args.Has("replay"))
	{
		// TODO: Support mods
		Paths paths(args);
		g_VFS = CreateVfs(20 * MiB);
		g_VFS->Mount(L"cache/", paths.Cache(), VFS_MOUNT_ARCHIVABLE);
		g_VFS->Mount(L"", paths.RData()/"mods"/"public", VFS_MOUNT_MUST_EXIST);

		{
			CReplayPlayer replay;
			replay.Load(args.Get("replay"));
			replay.Replay();
		}

		g_VFS.reset();

		CXeromyces::Terminate();
		return;
	}

	// run in archive-building mode if requested
	if (args.Has("archivebuild"))
	{
		Paths paths(args);

		OsPath mod(args.Get("archivebuild"));
		OsPath zip;
		if (args.Has("archivebuild-output"))
			zip = args.Get("archivebuild-output");
		else
			zip = mod.Filename().ChangeExtension(L".zip");

		CArchiveBuilder builder(mod, paths.Cache());
		builder.Build(zip, args.Has("archivebuild-compress"));

		CXeromyces::Terminate();
		return;
	}

	const double res = timer_Resolution();
	g_frequencyFilter = CreateFrequencyFilter(res, 30.0);

	// run the game
	Init(args, 0);
	InitGraphics(args, 0);
	MainControllerInit();
	while(!quit)
		Frame();
	Shutdown(0);
	MainControllerShutdown();

	if (restart_in_atlas)
	{
		ATLAS_RunIfOnCmdLine(args, true);
		return;
	}

	// Shut down libxml2 (done here to match the Startup call)
	CXeromyces::Terminate();
}
Exemplo n.º 2
0
Arquivo: main.cpp Projeto: 2asoft/0ad
// moved into a helper function to ensure args is destroyed before
// exit(), which may result in a memory leak.
static void RunGameOrAtlas(int argc, const char* argv[])
{
	CmdLineArgs args(argc, argv);

	g_args = args;

	if (args.Has("version") || args.Has("-version"))
	{
		debug_printf("Pyrogenesis %s\n", engine_version);
		return;
	}

	const bool isVisualReplay = args.Has("replay-visual");
	const bool isNonVisualReplay = args.Has("replay");

	const CStr replayFile =
		isVisualReplay ? args.Get("replay-visual") :
		isNonVisualReplay ? args.Get("replay") : "";

	if (isVisualReplay || isNonVisualReplay)
	{
		if (!FileExists(OsPath(replayFile)))
		{
			debug_printf("ERROR: The requested replay file '%s' does not exist!\n", replayFile.c_str());
			return;
		}
		if (DirectoryExists(OsPath(replayFile)))
		{
			debug_printf("ERROR: The requested replay file '%s' is a directory!\n", replayFile.c_str());
			return;
		}
	}

	// We need to initialize SpiderMonkey and libxml2 in the main thread before
	// any thread uses them. So initialize them here before we might run Atlas.
	ScriptEngine scriptEngine;
	CXeromyces::Startup();

	if (ATLAS_RunIfOnCmdLine(args, false))
	{
		CXeromyces::Terminate();
		return;
	}

	if (isNonVisualReplay)
	{
		if (!args.Has("mod"))
		{
			LOGERROR("At least one mod should be specified! Did you mean to add the argument '-mod=public'?");
			CXeromyces::Terminate();
			return;
		}

		Paths paths(args);
		g_VFS = CreateVfs(20 * MiB);
		g_VFS->Mount(L"cache/", paths.Cache(), VFS_MOUNT_ARCHIVABLE);
		MountMods(paths, GetMods(args, INIT_MODS));

		{
			CReplayPlayer replay;
			replay.Load(replayFile);
			replay.Replay(
				args.Has("serializationtest"),
				args.Has("rejointest") ? args.Get("rejointest").ToInt() : -1,
				args.Has("ooslog"));
		}

		g_VFS.reset();

		CXeromyces::Terminate();
		return;
	}

	// run in archive-building mode if requested
	if (args.Has("archivebuild"))
	{
		Paths paths(args);

		OsPath mod(args.Get("archivebuild"));
		OsPath zip;
		if (args.Has("archivebuild-output"))
			zip = args.Get("archivebuild-output");
		else
			zip = mod.Filename().ChangeExtension(L".zip");

		CArchiveBuilder builder(mod, paths.Cache());

		// Add mods provided on the command line
		// NOTE: We do not handle mods in the user mod path here
		std::vector<CStr> mods = args.GetMultiple("mod");
		for (size_t i = 0; i < mods.size(); ++i)
			builder.AddBaseMod(paths.RData()/"mods"/mods[i]);

		builder.Build(zip, args.Has("archivebuild-compress"));

		CXeromyces::Terminate();
		return;
	}

	const double res = timer_Resolution();
	g_frequencyFilter = CreateFrequencyFilter(res, 30.0);

	// run the game
	int flags = INIT_MODS;
	do
	{
		restart = false;
		quit = false;
		if (!Init(args, flags))
		{
			flags &= ~INIT_MODS;
			Shutdown(SHUTDOWN_FROM_CONFIG);
			continue;
		}
		InitGraphics(args, 0);
		MainControllerInit();
		while (!quit)
			Frame();
		Shutdown(0);
		MainControllerShutdown();
		flags &= ~INIT_MODS;
	} while (restart);

	if (restart_in_atlas)
		ATLAS_RunIfOnCmdLine(args, true);

	CXeromyces::Terminate();
}
Exemplo n.º 3
0
void CReplayManager::RestoreSnapshot()
{
	float watchDuration = gpGlobals->curtime - m_flReplayStartTime;

	if (watchDuration > m_flRunDuration)
	{
		if (m_nReplayRunIndex < m_nMaxReplayRuns - 1)
		{
			m_nReplayRunIndex += 1;
			m_bIsReplayStart = true;
		}
		else if (m_bIsHighlightReplay)
		{
			m_nReplayIndex = FindNextHighlightReplayIndex(m_nReplayIndex + 1, SDKGameRules()->State_Get());

			if (m_nReplayIndex == -1)
			{
				StopReplay();
				return;
			}

			m_nReplayRunIndex = 0;
			m_bIsReplayStart = true;
			m_bIsHighlightStart = true;
		}
		else
		{
			StopReplay();
			return;
		}
	}

	MatchEvent *pMatchEvent = m_MatchEvents[m_nReplayIndex];

	if (m_bIsReplayStart)
	{
		m_bIsReplayStart = false;
		m_bAtMinGoalPos = pMatchEvent->atMinGoalPos;
		m_bIsReplaying = true;
		m_bReplayIsPending = false;
		CalcMaxReplayRunsAndDuration(pMatchEvent, gpGlobals->curtime);
		watchDuration = 0;

		if (m_bIsHighlightReplay && m_bIsHighlightStart)
		{
			m_bIsHighlightStart = false;

			if (pMatchEvent->matchEventType == MATCH_EVENT_GOAL)
			{
				IGameEvent *pEvent = gameeventmanager->CreateEvent("highlight_goal");
				if (pEvent)
				{
					pEvent->SetInt("second", pMatchEvent->second);
					pEvent->SetInt("match_period", pMatchEvent->matchPeriod);
					pEvent->SetInt("scoring_team", pMatchEvent->team);
					pEvent->SetString("scorer", pMatchEvent->pPlayer1Data ? pMatchEvent->pPlayer1Data->m_szName : "");
					pEvent->SetString("first_assister", pMatchEvent->pPlayer2Data ? pMatchEvent->pPlayer2Data->m_szName : "");
					pEvent->SetString("second_assister", pMatchEvent->pPlayer3Data ? pMatchEvent->pPlayer3Data->m_szName : "");
					gameeventmanager->FireEvent(pEvent);
				}
			}
			else if (pMatchEvent->matchEventType == MATCH_EVENT_OWNGOAL)
			{
				IGameEvent *pEvent = gameeventmanager->CreateEvent("highlight_owngoal");
				if (pEvent)
				{
					pEvent->SetInt("second", pMatchEvent->second);
					pEvent->SetInt("match_period", pMatchEvent->matchPeriod);
					pEvent->SetInt("scoring_team", pMatchEvent->team);
					pEvent->SetString("scorer", pMatchEvent->pPlayer1Data ? pMatchEvent->pPlayer1Data->m_szName : "");
					gameeventmanager->FireEvent(pEvent);
				}
			}
			else if (pMatchEvent->matchEventType == MATCH_EVENT_MISS)
			{
				IGameEvent *pEvent = gameeventmanager->CreateEvent("highlight_miss");
				if (pEvent)
				{
					pEvent->SetInt("second", pMatchEvent->second);
					pEvent->SetInt("match_period", pMatchEvent->matchPeriod);
					pEvent->SetInt("finishing_team", pMatchEvent->team);
					pEvent->SetString("finisher", pMatchEvent->pPlayer1Data ? pMatchEvent->pPlayer1Data->m_szName : "");
					pEvent->SetString("first_assister", pMatchEvent->pPlayer2Data ? pMatchEvent->pPlayer2Data->m_szName : "");
					pEvent->SetString("second_assister", pMatchEvent->pPlayer3Data ? pMatchEvent->pPlayer3Data->m_szName : "");
					gameeventmanager->FireEvent(pEvent);
				}
			}
			else if (pMatchEvent->matchEventType == MATCH_EVENT_KEEPERSAVE)
			{
				IGameEvent *pEvent = gameeventmanager->CreateEvent("highlight_keepersave");
				if (pEvent)
				{
					pEvent->SetInt("second", pMatchEvent->second);
					pEvent->SetInt("match_period", pMatchEvent->matchPeriod);
					pEvent->SetInt("keeper_team", pMatchEvent->team);
					pEvent->SetString("keeper", pMatchEvent->pPlayer1Data ? pMatchEvent->pPlayer1Data->m_szName : "");
					pEvent->SetString("finisher", pMatchEvent->pPlayer2Data ? pMatchEvent->pPlayer2Data->m_szName : "");
					gameeventmanager->FireEvent(pEvent);
				}
			}
			else if (pMatchEvent->matchEventType == MATCH_EVENT_REDCARD)
			{
				IGameEvent *pEvent = gameeventmanager->CreateEvent("highlight_redcard");
				if (pEvent)
				{
					pEvent->SetInt("second", pMatchEvent->second);
					pEvent->SetInt("match_period", pMatchEvent->matchPeriod);
					pEvent->SetInt("fouling_team", pMatchEvent->team);
					pEvent->SetString("fouling_player", pMatchEvent->pPlayer1Data ? pMatchEvent->pPlayer1Data->m_szName : "");
					gameeventmanager->FireEvent(pEvent);
				}
			}
		}
	}

	CBall *pRealBall = GetMatchBall();
	if (pRealBall && !(pRealBall->GetEffects() & EF_NODRAW))
	{
		pRealBall->AddEffects(EF_NODRAW);
		pRealBall->AddSolidFlags(FSOLID_NOT_SOLID);
	}

	for (int i = 1; i <= gpGlobals->maxClients; i++)
	{
		CSDKPlayer *pRealPl = ToSDKPlayer(UTIL_PlayerByIndex(i));
		if (!CSDKPlayer::IsOnField(pRealPl))
			continue;

		if (!(pRealPl->GetEffects() & EF_NODRAW))
		{
			pRealPl->AddEffects(EF_NODRAW);
			pRealPl->DoServerAnimationEvent(PLAYERANIMEVENT_CANCEL);
			pRealPl->AddSolidFlags(FSOLID_NOT_SOLID);
			pRealPl->SetMoveType(MOVETYPE_NONE);
		}
		
		if (pRealPl->GetPlayerBall() && !(pRealPl->GetPlayerBall()->GetEffects() & EF_NODRAW))
		{
			pRealPl->GetPlayerBall()->AddEffects(EF_NODRAW);
			pRealPl->GetPlayerBall()->AddSolidFlags(FSOLID_NOT_SOLID);
		}
	}

	float normalRunDuration = m_flRunDuration - m_flSlowMoDuration;

	if (watchDuration > normalRunDuration)
		watchDuration = normalRunDuration + m_flSlowMoCoeff * (watchDuration - normalRunDuration);

	// To find the correct snapshot calculate the time passed since we started watching the replay and match it to the right snapshot in our recording list

	Snapshot *pNextSnap = NULL;
	Snapshot *pSnap = NULL;
	float snapDuration = 0;

	// Traverse backwards looking for a recorded snapshot matching the time since replay start
	for (int i = pMatchEvent->snapshots.Count() - 1; i >= 0; i--)
	{
		// Save the snapshot of the previous iteration, so we have a snapshot to interpolate to when we found our target snapshot
		pNextSnap = pSnap;

		// Save the current snapshot
		pSnap = pMatchEvent->snapshots[i];

		// Snapshots have absolute match times, so calculate the relative time span between the first recorded snapshot and the current snapshot
		snapDuration = pSnap->snaptime - pMatchEvent->snapshots[0]->snaptime - m_flReplayStartTimeOffset;

		// We usually only play the last x seconds of a replay instead of the whole thing, so subtract the start time offset from the time since the first snapshot.
		// The first snapshot which time span is equal or shorter than the duration since replay start is the one which should be shown next.
		if (snapDuration <= watchDuration)
			break;
	}

	// No snapshots in the list
	if (!pSnap)
		return;

	float nextSnapDuration;

	if (pNextSnap)
		nextSnapDuration = pNextSnap->snaptime - pMatchEvent->snapshots[0]->snaptime - m_flReplayStartTimeOffset;
	else
		nextSnapDuration = 0;

	BallSnapshot *pBallSnap = pSnap->pBallSnapshot;

	if (pBallSnap)
	{
		if (!m_pBall)
		{
			m_pBall = (CReplayBall *)CreateEntityByName("replayball");
			m_pBall->Spawn();
		}

		if (Q_strcmp(m_pBall->m_szSkinName, GetMatchBall()->GetSkinName()))
			Q_strncpy(m_pBall->m_szSkinName.GetForModify(), GetMatchBall()->GetSkinName(), MAX_KITNAME_LENGTH);

		m_pBall->VPhysicsGetObject()->SetPosition(pBallSnap->pos, pBallSnap->ang, false);
		m_pBall->VPhysicsGetObject()->SetVelocity(&pBallSnap->vel, &pBallSnap->rot);
	}
	else
	{
		UTIL_Remove(m_pBall);
		m_pBall = NULL;
	}

	float frac;

	if (pNextSnap)
	{
		// Calc fraction between both snapshots
		frac = clamp((watchDuration - snapDuration) / (nextSnapDuration - snapDuration), 0.0f, 1.0f);
	}
	else
	{
		// Exact snapshot time matched or no next snapshot to interpolate to
		frac = 0.0f;
	}

	for (int i = 0; i < 2; i++)
	{
		for (int j = 0; j < 11; j++)
		{
			PlayerSnapshot *pPlSnap = pSnap->pPlayerSnapshot[i][j];
			if (!pPlSnap)
			{
				UTIL_Remove(m_pPlayers[i][j]);
				m_pPlayers[i][j] = NULL;
				continue;
			}

			if (!m_pPlayers[i][j])
			{
				m_pPlayers[i][j] = (CReplayPlayer *)CreateEntityByName("replayplayer");
				m_pPlayers[i][j]->Spawn();
				m_pPlayers[i][j]->SetNumAnimOverlays(NUM_LAYERS_WANTED);
			}

			CReplayPlayer *pPl = m_pPlayers[i][j];

			pPl->m_nTeamNumber = pPlSnap->teamNumber;
			pPl->m_nTeamPosIndex = pPlSnap->teamPosIndex;
			pPl->m_bIsKeeper = pPlSnap->isKeeper;
			pPl->m_nShirtNumber = pPlSnap->shirtNumber;

			if (Q_strcmp(pPl->m_szPlayerName, pPlSnap->pPlayerData->m_szName))
				Q_strncpy(pPl->m_szPlayerName.GetForModify(), pPlSnap->pPlayerData->m_szName, MAX_PLAYER_NAME_LENGTH);

			if (Q_strcmp(pPl->m_szShirtName, pPlSnap->pPlayerData->m_szShirtName))
				Q_strncpy(pPl->m_szShirtName.GetForModify(), pPlSnap->pPlayerData->m_szShirtName, MAX_PLAYER_NAME_LENGTH);

			pPl->m_nSkinIndex = pPlSnap->skinIndex;
			pPl->m_nBody = pPlSnap->body;

			PlayerSnapshot *pNextPlSnap = NULL;

			if (pNextSnap)
				pNextPlSnap = pNextSnap->pPlayerSnapshot[i][j];

			if (frac > 0.0f && pNextPlSnap)
			{
				pPl->SetLocalOrigin(Lerp( frac, pPlSnap->pos, pNextPlSnap->pos  ));
				//pPl->SetLocalVelocity(Lerp( frac, pPlSnap->vel, pNextPlSnap->vel  ));
				pPl->SetLocalAngles(Lerp( frac, pPlSnap->ang, pNextPlSnap->ang ));
			}
			else
			{
				pPl->SetLocalOrigin(pPlSnap->pos);
				//pPl->SetLocalVelocity(pPlSnap->vel);
				pPl->SetLocalAngles(pPlSnap->ang);
			}

			bool interpolationAllowed;

			if (frac > 0.0f && pNextPlSnap && pPlSnap->masterSequence == pNextPlSnap->masterSequence)
			{
				// If the master state changes, all layers will be invalid too, so don't interp (ya know, interp barely ever happens anyway)
				interpolationAllowed = true;
			}
			else
				interpolationAllowed = false;

			// First do the master settings
			if (interpolationAllowed)
			{
				pPl->SetSequence( Lerp( frac, pPlSnap->masterSequence, pNextPlSnap->masterSequence ) );
				pPl->SetCycle( Lerp( frac, pPlSnap->masterCycle, pNextPlSnap->masterCycle ) );

				if( pPlSnap->masterCycle > pNextPlSnap->masterCycle )
				{
					// the older record is higher in frame than the newer, it must have wrapped around from 1 back to 0
					// add one to the newer so it is lerping from .9 to 1.1 instead of .9 to .1, for example.
					float newCycle = Lerp( frac, pPlSnap->masterCycle, pNextPlSnap->masterCycle + 1 );
					pPl->SetCycle(newCycle < 1 ? newCycle : newCycle - 1 );// and make sure .9 to 1.2 does not end up 1.05
				}
				else
				{
					pPl->SetCycle( Lerp( frac, pPlSnap->masterCycle, pNextPlSnap->masterCycle ) );
				}

				pPl->SetPoseParameter(pPl->GetModelPtr(), 4, Lerp(frac, pPlSnap->moveX, pNextPlSnap->moveX));
				pPl->SetPoseParameter(pPl->GetModelPtr(), 3, Lerp(frac, pPlSnap->moveY, pNextPlSnap->moveY));
			}
			else
			{
				pPl->SetSequence(pPlSnap->masterSequence);
				pPl->SetCycle(pPlSnap->masterCycle);
				pPl->SetPoseParameter(pPl->GetModelPtr(), 4, pPlSnap->moveX);
				pPl->SetPoseParameter(pPl->GetModelPtr(), 3, pPlSnap->moveY);
			}

			// Now do all the layers
			for (int layerIndex = 0; layerIndex < NUM_LAYERS_WANTED; layerIndex++)
			{
				CAnimationLayer *pTargetLayer = pPl->GetAnimOverlay(layerIndex);
				if(!pTargetLayer)
					continue;

				LayerRecord *pSourceLayer = &pPlSnap->layerRecords[layerIndex];

				pTargetLayer->m_nOrder = pSourceLayer->order;
				pTargetLayer->m_nSequence = pSourceLayer->sequence;
				pTargetLayer->m_flPlaybackRate = pSourceLayer->playbackRate;
				pTargetLayer->m_fFlags = pSourceLayer->flags;
				pTargetLayer->m_bLooping = pSourceLayer->looping;
				pTargetLayer->m_bSequenceFinished = pSourceLayer->sequenceFinished;
				pTargetLayer->m_nPriority = pSourceLayer->priority;
				pTargetLayer->m_nActivity = pSourceLayer->activity;
				pTargetLayer->m_flLastAccess = pSourceLayer->lastAccess;
				pTargetLayer->m_flLastEventCheck = pSourceLayer->lastEventCheck;

				if (interpolationAllowed)
				{
					LayerRecord *pNextSourceLayer = &pNextPlSnap->layerRecords[layerIndex];

					if(pSourceLayer->order == pNextSourceLayer->order && pSourceLayer->sequence == pNextSourceLayer->sequence)
					{
						// We can't interpolate across a sequence or order change
						if( pSourceLayer->cycle > pNextSourceLayer->cycle )
						{
							// the older record is higher in frame than the newer, it must have wrapped around from 1 back to 0
							// add one to the newer so it is lerping from .9 to 1.1 instead of .9 to .1, for example.
							float newCycle = Lerp( frac, pSourceLayer->cycle, pNextSourceLayer->cycle + 1 );
							pTargetLayer->m_flCycle = newCycle < 1 ? newCycle : newCycle - 1;// and make sure .9 to 1.2 does not end up 1.05
						}
						else
						{
							pTargetLayer->m_flCycle = Lerp(frac, pSourceLayer->cycle, pNextSourceLayer->cycle);
						}

						pTargetLayer->m_flWeight = Lerp(frac, pSourceLayer->weight, pNextSourceLayer->weight);
						pTargetLayer->m_flLayerAnimtime = Lerp(frac, pSourceLayer->layerAnimtime, pNextSourceLayer->layerAnimtime);
						pTargetLayer->m_flBlendIn = Lerp(frac, pSourceLayer->blendIn, pNextSourceLayer->blendIn);
						pTargetLayer->m_flBlendOut = Lerp(frac, pSourceLayer->blendOut, pNextSourceLayer->blendOut);
						pTargetLayer->m_flPrevCycle = Lerp(frac, pSourceLayer->prevCycle, pNextSourceLayer->prevCycle);
						pTargetLayer->m_flKillDelay = Lerp(frac, pSourceLayer->killDelay, pNextSourceLayer->killDelay);
						pTargetLayer->m_flKillRate = Lerp(frac, pSourceLayer->killRate, pNextSourceLayer->killRate);
						pTargetLayer->m_flLayerFadeOuttime = Lerp(frac, pSourceLayer->layerFadeOuttime, pNextSourceLayer->layerFadeOuttime);
					}
				}
				else
				{
					//Either no interp, or interp failed.  Just use record.
					pTargetLayer->m_flCycle = pSourceLayer->cycle;
					pTargetLayer->m_flWeight = pSourceLayer->weight;
					pTargetLayer->m_flLayerAnimtime = pSourceLayer->layerAnimtime;
					pTargetLayer->m_flBlendIn = pSourceLayer->blendIn;
					pTargetLayer->m_flBlendOut = pSourceLayer->blendOut;
					pTargetLayer->m_flPrevCycle = pSourceLayer->prevCycle;
					pTargetLayer->m_flKillDelay = pSourceLayer->killDelay;
					pTargetLayer->m_flKillRate = pSourceLayer->killRate;
					pTargetLayer->m_flLayerFadeOuttime = pSourceLayer->layerFadeOuttime;
				}
			}
		}
	}
}
Exemplo n.º 4
0
// moved into a helper function to ensure args is destroyed before
// exit(), which may result in a memory leak.
static void RunGameOrAtlas(int argc, const char* argv[])
{
	CmdLineArgs args(argc, argv);

	g_args = args;

	// We need to initialise libxml2 in the main thread before
	// any thread uses it. So initialise it here before we
	// might run Atlas.
	CXeromyces::Startup();

	// run Atlas (if requested via args)
	bool ran_atlas = ATLAS_RunIfOnCmdLine(args, false);
	// Atlas handles the whole init/shutdown/etc sequence by itself;
	// when we get here, it has exited and we're done.
	if(ran_atlas)
		return;

	// run non-visual simulation replay if requested
	if (args.Has("replay"))
	{
		std::string replayFile = args.Get("replay");
		if (!FileExists(OsPath(replayFile)))
		{
			debug_printf("ERROR: The requested replay file '%s' does not exist!\n", replayFile.c_str());
			return;
		}
		Paths paths(args);
		g_VFS = CreateVfs(20 * MiB);
		g_VFS->Mount(L"cache/", paths.Cache(), VFS_MOUNT_ARCHIVABLE);
		MountMods(paths, GetMods(args, INIT_MODS));

		{
			CReplayPlayer replay;
			replay.Load(replayFile);
			replay.Replay(args.Has("serializationtest"), args.Has("ooslog"));
		}

		g_VFS.reset();

		CXeromyces::Terminate();
		return;
	}

	// If visual replay file does not exist, quit before starting the renderer
	if (args.Has("replay-visual"))
	{
		std::string replayFile = args.Get("replay-visual");
		if (!FileExists(OsPath(replayFile)))
		{
			debug_printf("ERROR: The requested replay file '%s' does not exist!\n", replayFile.c_str());
			return;
		}
	}

	// run in archive-building mode if requested
	if (args.Has("archivebuild"))
	{
		Paths paths(args);

		OsPath mod(args.Get("archivebuild"));
		OsPath zip;
		if (args.Has("archivebuild-output"))
			zip = args.Get("archivebuild-output");
		else
			zip = mod.Filename().ChangeExtension(L".zip");

		CArchiveBuilder builder(mod, paths.Cache());

		// Add mods provided on the command line
		// NOTE: We do not handle mods in the user mod path here
		std::vector<CStr> mods = args.GetMultiple("mod");
		for (size_t i = 0; i < mods.size(); ++i)
			builder.AddBaseMod(paths.RData()/"mods"/mods[i]);

		builder.Build(zip, args.Has("archivebuild-compress"));

		CXeromyces::Terminate();
		return;
	}

	const double res = timer_Resolution();
	g_frequencyFilter = CreateFrequencyFilter(res, 30.0);

	// run the game
	int flags = INIT_MODS;
	do
	{
		restart = false;
		quit = false;
		if (!Init(args, flags))
		{
			flags &= ~INIT_MODS;
			Shutdown(SHUTDOWN_FROM_CONFIG);
			continue;
		}
		InitGraphics(args, 0);
		MainControllerInit();
		while (!quit)
			Frame();
		Shutdown(0);
		MainControllerShutdown();
		flags &= ~INIT_MODS;
	} while (restart);

	if (restart_in_atlas)
	{
		ATLAS_RunIfOnCmdLine(args, true);
		return;
	}

	// Shut down libxml2 (done here to match the Startup call)
	CXeromyces::Terminate();
}