forked from Mirrors/openclonk
602 lines
17 KiB
C++
602 lines
17 KiB
C++
/*
|
|
* OpenClonk, http://www.openclonk.org
|
|
*
|
|
* Copyright (c) 1998-2000, 2004-2005, 2007-2008 Matthes Bender
|
|
* Copyright (c) 2004-2008 Sven Eberhardt
|
|
* Copyright (c) 2005-2006, 2009 Peter Wortmann
|
|
* Copyright (c) 2005-2006, 2008-2009 Günther Brammer
|
|
* Copyright (c) 2009 Nicolas Hake
|
|
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de
|
|
*
|
|
* Portions might be copyrighted by other authors who have contributed
|
|
* to OpenClonk.
|
|
*
|
|
* Permission to use, copy, modify, and/or distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
* See isc_license.txt for full license and disclaimer.
|
|
*
|
|
* "Clonk" is a registered trademark of Matthes Bender.
|
|
* See clonk_trademark_license.txt for full license.
|
|
*/
|
|
|
|
/* Main class to initialize configuration and execute the game */
|
|
|
|
#include <C4Include.h>
|
|
#include <C4Application.h>
|
|
#include <C4Version.h>
|
|
#ifdef _WIN32
|
|
#include <StdRegistry.h>
|
|
#include <C4UpdateDlg.h>
|
|
#endif
|
|
|
|
#ifndef BIG_C4INCLUDE
|
|
#include "C4Game.h"
|
|
#include "C4GraphicsSystem.h"
|
|
#include "C4GraphicsResource.h"
|
|
#include "C4MessageInput.h"
|
|
#include <C4FileClasses.h>
|
|
#include <C4FullScreen.h>
|
|
#include <C4Language.h>
|
|
#include <C4Console.h>
|
|
#include <C4Startup.h>
|
|
#include <C4Log.h>
|
|
#include <C4GamePadCon.h>
|
|
#include <C4GameLobby.h>
|
|
#endif
|
|
|
|
#include <StdRegistry.h> // For DDraw emulation warning
|
|
|
|
|
|
C4Application::C4Application():
|
|
isFullScreen(true), UseStartupDialog(true), launchEditor(false), restartAtEnd(false),
|
|
DDraw(NULL), AppState(C4AS_None),
|
|
pGamePadControl(NULL),
|
|
CheckForUpdates(false), NoSplash(false)
|
|
{
|
|
}
|
|
|
|
C4Application::~C4Application()
|
|
{
|
|
// clear gamepad
|
|
if (pGamePadControl) delete pGamePadControl;
|
|
// Close log
|
|
CloseLog();
|
|
// Launch editor
|
|
if (launchEditor)
|
|
{
|
|
#ifdef _WIN32
|
|
char strCommandLine[_MAX_PATH + 1]; SCopy(Config.AtExePath(C4CFN_Editor), strCommandLine);
|
|
STARTUPINFO StartupInfo; ZeroMemory(&StartupInfo, sizeof StartupInfo);
|
|
StartupInfo.cb = sizeof StartupInfo;
|
|
PROCESS_INFORMATION ProcessInfo; ZeroMemory(&ProcessInfo, sizeof ProcessInfo);
|
|
CreateProcess(NULL, strCommandLine, NULL, NULL, TRUE, 0, NULL, NULL, &StartupInfo, &ProcessInfo);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
bool C4Application::DoInit()
|
|
{
|
|
assert(AppState == C4AS_None);
|
|
// Config overwrite by parameter
|
|
StdStrBuf sConfigFilename;
|
|
char szParameter[_MAX_PATH+1];
|
|
for (int32_t iPar=0; SGetParameter(GetCommandLine(), iPar, szParameter, _MAX_PATH); iPar++)
|
|
if (SEqual2NoCase(szParameter, "/config:"))
|
|
sConfigFilename.Copy(szParameter + 8);
|
|
// Config check
|
|
Config.Init();
|
|
Config.Load(true, sConfigFilename.getData());
|
|
Config.Save();
|
|
// sometimes, the configuration can become corrupted due to loading errors or w/e
|
|
// check this and reset defaults if necessary
|
|
if (Config.IsCorrupted())
|
|
{
|
|
if (sConfigFilename)
|
|
{
|
|
// custom config corrupted: Fail
|
|
Log("Warning: Custom configuration corrupted - program abort!\n");
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// default config corrupted: Restore default
|
|
Log("Warning: Configuration corrupted - restoring default!\n");
|
|
Config.Default();
|
|
Config.Save();
|
|
Config.Load();
|
|
}
|
|
}
|
|
// Init C4Group
|
|
C4Group_SetMaker(Config.General.Name);
|
|
C4Group_SetProcessCallback(&ProcessCallback);
|
|
C4Group_SetTempPath(Config.General.TempPath);
|
|
C4Group_SetSortList(C4CFN_FLS);
|
|
|
|
// Open log
|
|
OpenLog();
|
|
|
|
// init system group
|
|
if (!SystemGroup.Open(C4CFN_System))
|
|
{
|
|
// Error opening system group - no LogFatal, because it needs language table.
|
|
// This will *not* use the FatalErrors stack, but this will cause the game
|
|
// to instantly halt, anyway.
|
|
const char *szMessage = "Error opening system group file (System.c4g)!";
|
|
Log(szMessage);
|
|
// Fatal error, game cannot start - have player notice
|
|
MessageDialog(szMessage);
|
|
return false;
|
|
}
|
|
|
|
// Language override by parameter
|
|
const char *pLanguage;
|
|
if (pLanguage = SSearchNoCase(GetCommandLine(), "/Language:"))
|
|
SCopyUntil(pLanguage, Config.General.LanguageEx, ' ', CFG_MaxString);
|
|
|
|
// Init external language packs
|
|
Languages.Init();
|
|
// Load language string table
|
|
if (!Languages.LoadLanguage(Config.General.LanguageEx))
|
|
// No language table was loaded - bad luck...
|
|
if (!IsResStrTableLoaded())
|
|
Log("WARNING: No language string table loaded!");
|
|
|
|
// Set unregistered user name
|
|
if (!Config.Registered())
|
|
C4Group_SetMaker(LoadResStr("IDS_PRC_UNREGUSER"));
|
|
|
|
// Parse command line
|
|
Game.ParseCommandLine(GetCommandLine());
|
|
|
|
#ifdef WIN32
|
|
// Windows: handle incoming updates directly, even before starting up the gui
|
|
// because updates will be applied in the console anyway.
|
|
if (Application.IncomingUpdate)
|
|
if (C4UpdateDlg::ApplyUpdate(Application.IncomingUpdate.getData(), false, NULL))
|
|
return true;
|
|
#endif
|
|
|
|
DDrawCfg.Shader = Config.Graphics.EnableShaders;
|
|
switch (Config.Graphics.Engine) {
|
|
#ifdef USE_DIRECTX
|
|
case GFXENGN_DIRECTX:
|
|
case GFXENGN_DIRECTXS:
|
|
// Direct3D
|
|
DDrawCfg.Set(Config.Graphics.NewGfxCfg, (float) Config.Graphics.BlitOff/100.0f);
|
|
break;
|
|
#endif
|
|
#ifdef USE_GL
|
|
case GFXENGN_OPENGL:
|
|
// OpenGL
|
|
DDrawCfg.Set(Config.Graphics.NewGfxCfgGL, (float) Config.Graphics.BlitOffGL/100.0f);
|
|
break;
|
|
#endif
|
|
default: ; // Always have at least one statement
|
|
}
|
|
|
|
// Fixup resolution
|
|
ApplyResolutionConstraints();
|
|
|
|
// activate
|
|
Active=TRUE;
|
|
|
|
// Init carrier window
|
|
if (isFullScreen)
|
|
{
|
|
if (!(pWindow = FullScreen.Init(this)))
|
|
{ Clear(); return false; }
|
|
}
|
|
else
|
|
{
|
|
if (!(pWindow = Console.Init(this)))
|
|
{ Clear(); return false; }
|
|
}
|
|
|
|
// init timers (needs window)
|
|
Add(pGameTimer = new C4ApplicationGameTimer());
|
|
|
|
// Engine header message
|
|
Log(C4ENGINEINFOLONG);
|
|
LogF("Version: %s %s", C4VERSION, C4_OS);
|
|
|
|
// Log registration info
|
|
if (Config.Registered())
|
|
{
|
|
char buf[4096 + 1] = "";
|
|
SAppend("Registered to: ", buf, 4096);
|
|
SAppend(Config.General.Name, buf, 4096); SAppend(" ", buf, 4096);
|
|
if (Config.GetRegistrationData("Nick")[0])
|
|
{ SAppend("(", buf, 4096); SAppend(Config.GetRegistrationData("Nick"), buf, 4096); SAppend(") ", buf, 4096); }
|
|
SAppend("[", buf, 4096); SAppend(Config.GetRegistrationData("Cuid"), buf, 4096); SAppend("]", buf, 4096);
|
|
Log(buf);
|
|
}
|
|
else
|
|
Log(Config.GetRegistrationError());
|
|
|
|
#if defined(USE_DIRECTX) && defined(_WIN32)
|
|
// DDraw emulation warning
|
|
DWORD DDrawEmulationState;
|
|
if (GetRegistryDWord(HKEY_LOCAL_MACHINE,"Software\\Microsoft\\DirectDraw","EmulationOnly",&DDrawEmulationState))
|
|
if (DDrawEmulationState)
|
|
Log("WARNING: DDraw Software emulation is activated!");
|
|
#endif
|
|
// Initialize D3D/OpenGL
|
|
DDraw = DDrawInit(this, isFullScreen, FALSE, Config.Graphics.ResX, Config.Graphics.ResY, Config.Graphics.BitDepth, Config.Graphics.Engine, Config.Graphics.Monitor);
|
|
if (!DDraw) { LogFatal(LoadResStr("IDS_ERR_DDRAW")); Clear(); return false; }
|
|
|
|
#if defined(_WIN32) && !defined(USE_CONSOLE)
|
|
// Register clonk file classes - notice: under Vista this will only work if we have administrator rights
|
|
char szModule[_MAX_PATH+1]; GetModuleFileName(NULL, szModule, _MAX_PATH);
|
|
SetC4FileClasses(szModule);
|
|
#endif
|
|
|
|
// Initialize gamepad
|
|
if (!pGamePadControl && Config.General.GamepadEnabled)
|
|
pGamePadControl = new C4GamePadControl();
|
|
|
|
AppState = C4AS_PreInit;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void C4Application::ApplyResolutionConstraints()
|
|
{
|
|
// Enumerate display modes
|
|
int32_t idx = 0, iXRes, iYRes, iBitDepth;
|
|
int32_t best_match = -1;
|
|
uint32_t best_delta = ~0;
|
|
int32_t ResX = Config.Graphics.ResX, ResY = Config.Graphics.ResY, BitDepth = Config.Graphics.BitDepth;
|
|
while (GetIndexedDisplayMode(idx++, &iXRes, &iYRes, &iBitDepth, Config.Graphics.Monitor))
|
|
{
|
|
uint32_t delta = std::abs(ResX*ResY - iXRes*iYRes);
|
|
if (!delta && iBitDepth == BitDepth)
|
|
return; // Exactly the expected mode
|
|
if (delta < best_delta)
|
|
{
|
|
// Better match than before
|
|
best_match = idx;
|
|
best_delta = delta;
|
|
}
|
|
}
|
|
if (best_match != -1)
|
|
{
|
|
// Apply next-best mode
|
|
GetIndexedDisplayMode(best_match, &iXRes, &iYRes, &iBitDepth, Config.Graphics.Monitor);
|
|
if (iXRes != ResX || iYRes != ResY)
|
|
// Don't warn if only bit depth changes
|
|
// Also, lang table not loaded yet
|
|
LogF("Warning: The selected resolution %dx%d is not available and has been changed to %dx%d.", ResX, ResY, iXRes, iYRes);
|
|
ResX = iXRes; ResY = iYRes;
|
|
}
|
|
}
|
|
|
|
bool C4Application::PreInit()
|
|
{
|
|
if (!Game.PreInit()) return false;
|
|
|
|
// startup dialog: Only use if no next mission has been provided
|
|
bool fDoUseStartupDialog = UseStartupDialog && !*Game.ScenarioFilename;
|
|
|
|
// init loader: Black screen for first start if a video is to be shown; otherwise default spec
|
|
if (fDoUseStartupDialog)
|
|
{
|
|
//Log(LoadResStr("IDS_PRC_INITLOADER"));
|
|
bool fUseBlackScreenLoader = UseStartupDialog && !C4Startup::WasFirstRun() && !Config.Startup.NoSplash && !NoSplash && FileExists(C4CFN_Splash);
|
|
if (!::GraphicsSystem.InitLoaderScreen(C4CFN_StartupBackgroundMain, fUseBlackScreenLoader))
|
|
{ LogFatal(LoadResStr("IDS_PRC_ERRLOADER")); return false; }
|
|
}
|
|
|
|
Game.SetInitProgress(fDoUseStartupDialog ? 10.0f : 1.0f);
|
|
|
|
// Music
|
|
if (!MusicSystem.Init("Frontend.*"))
|
|
Log(LoadResStr("IDS_PRC_NOMUSIC"));
|
|
|
|
Game.SetInitProgress(fDoUseStartupDialog ? 20.0f : 2.0f);
|
|
|
|
// Sound
|
|
if (!SoundSystem.Init())
|
|
Log(LoadResStr("IDS_PRC_NOSND"));
|
|
|
|
Game.SetInitProgress(fDoUseStartupDialog ? 30.0f : 3.0f);
|
|
|
|
AppState = fDoUseStartupDialog ? C4AS_Startup : C4AS_StartGame;
|
|
|
|
return true;
|
|
}
|
|
|
|
BOOL C4Application::ProcessCallback(const char *szMessage, int iProcess)
|
|
{
|
|
Console.Out(szMessage);
|
|
return TRUE;
|
|
}
|
|
|
|
void C4Application::Clear()
|
|
{
|
|
Game.Clear();
|
|
NextMission.Clear();
|
|
// stop timer
|
|
Remove(pGameTimer);
|
|
delete pGameTimer; pGameTimer = NULL;
|
|
// close system group (System.c4g)
|
|
SystemGroup.Close();
|
|
// Log
|
|
if (IsResStrTableLoaded()) // Avoid (double and undefined) message on (second?) shutdown...
|
|
Log(LoadResStr("IDS_PRC_DEINIT"));
|
|
// Clear external language packs and string table
|
|
Languages.Clear();
|
|
Languages.ClearLanguage();
|
|
// gamepad clear
|
|
if (pGamePadControl) { delete pGamePadControl; pGamePadControl=NULL; }
|
|
// music system clear
|
|
MusicSystem.Clear();
|
|
SoundSystem.Clear();
|
|
RestoreVideoMode();
|
|
// Clear direct draw (late, because it's needed for e.g. Log)
|
|
if (DDraw) { delete DDraw; DDraw=NULL; }
|
|
// Close window
|
|
FullScreen.Clear();
|
|
Console.Clear();
|
|
// The very final stuff
|
|
CStdApp::Clear();
|
|
}
|
|
|
|
bool C4Application::OpenGame()
|
|
{
|
|
if (isFullScreen)
|
|
{
|
|
// Open game
|
|
return Game.Init();
|
|
}
|
|
else
|
|
{
|
|
// Execute command line
|
|
if (Game.ScenarioFilename[0] || Game.DirectJoinAddress[0])
|
|
return Console.OpenGame(szCmdLine);
|
|
}
|
|
// done; success
|
|
return true;
|
|
}
|
|
|
|
void C4Application::Quit()
|
|
{
|
|
// Clear definitions passed by frontend for this round
|
|
Config.General.Definitions[0] = 0;
|
|
// Participants should not be cleared for usual startup dialog
|
|
//Config.General.Participants[0] = 0;
|
|
// Save config if there was no loading error
|
|
if (Config.fConfigLoaded) Config.Save();
|
|
// quit app
|
|
CStdApp::Quit();
|
|
AppState = C4AS_Quit;
|
|
}
|
|
|
|
void C4Application::QuitGame()
|
|
{
|
|
// reinit desired? Do restart
|
|
if (UseStartupDialog || NextMission)
|
|
{
|
|
// backup last start params
|
|
bool fWasNetworkActive = Game.NetworkActive;
|
|
// stop game
|
|
Game.Clear();
|
|
Game.Default();
|
|
AppState = C4AS_PreInit;
|
|
// if a next mission is desired, set to start it
|
|
if (NextMission)
|
|
{
|
|
SCopy(NextMission.getData(), Game.ScenarioFilename, _MAX_PATH);
|
|
SReplaceChar(Game.ScenarioFilename, '\\', DirSep[0]); // linux/mac: make sure we are using forward slashes
|
|
Game.fLobby = Game.NetworkActive = fWasNetworkActive;
|
|
Game.fObserve = false;
|
|
Game.Record = !!Config.General.Record;
|
|
NextMission.Clear();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Quit();
|
|
}
|
|
}
|
|
|
|
void C4Application::GameTick()
|
|
{
|
|
// Exec depending on game state
|
|
assert(AppState != C4AS_None);
|
|
switch (AppState)
|
|
{
|
|
case C4AS_Quit:
|
|
// Do nothing, HandleMessage will return HR_Failure soon
|
|
break;
|
|
case C4AS_PreInit:
|
|
if (!PreInit()) Quit();
|
|
break;
|
|
case C4AS_Startup:
|
|
AppState = C4AS_Game;
|
|
// if no scenario or direct join has been specified, get game startup parameters by startup dialog
|
|
Game.ScenarioTitle.Copy(LoadResStr("IDS_PRC_INITIALIZE"));
|
|
if (!C4Startup::Execute()) { Quit(); return; }
|
|
AppState = C4AS_StartGame;
|
|
break;
|
|
case C4AS_StartGame:
|
|
// immediate progress to next state; OpenGame will enter HandleMessage-loops in startup and lobby!
|
|
AppState = C4AS_Game;
|
|
// first-time game initialization
|
|
if (!OpenGame())
|
|
{
|
|
// set error flag (unless this was a lobby user abort)
|
|
if (!C4GameLobby::UserAbort)
|
|
Game.fQuitWithError = true;
|
|
// no start: Regular QuitGame; this may reset the engine to startup mode if desired
|
|
QuitGame();
|
|
}
|
|
break;
|
|
case C4AS_Game:
|
|
// Game
|
|
if (Game.IsRunning)
|
|
Game.Execute();
|
|
Game.DoSkipFrame = false;
|
|
// Sound
|
|
SoundSystem.Execute();
|
|
// Gamepad
|
|
if (pGamePadControl) pGamePadControl->Execute();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void C4Application::Draw()
|
|
{
|
|
// Graphics
|
|
if(!Game.DoSkipFrame)
|
|
{
|
|
// Fullscreen mode
|
|
if (isFullScreen)
|
|
FullScreen.Execute();
|
|
// Console mode
|
|
else
|
|
Console.Execute();
|
|
}
|
|
}
|
|
|
|
void C4Application::SetGameTickDelay(int iDelay)
|
|
{
|
|
if(!pGameTimer) return;
|
|
pGameTimer->SetGameTickDelay(iDelay);
|
|
}
|
|
|
|
void C4Application::OnResolutionChanged(unsigned int iXRes, unsigned int iYRes)
|
|
{
|
|
// notify game
|
|
if (DDraw)
|
|
{
|
|
Game.OnResolutionChanged(iXRes, iYRes);
|
|
DDraw->OnResolutionChanged(iXRes, iYRes);
|
|
}
|
|
}
|
|
|
|
bool C4Application::SetGameFont(const char *szFontFace, int32_t iFontSize)
|
|
{
|
|
#ifndef USE_CONSOLE
|
|
// safety
|
|
if (!szFontFace || !*szFontFace || iFontSize<1 || SLen(szFontFace)>=static_cast<int>(sizeof Config.General.RXFontName)) return false;
|
|
// first, check if the selected font can be created at all
|
|
// check regular font only - there's no reason why the other fonts couldn't be created
|
|
CStdFont TestFont;
|
|
if (!Game.FontLoader.InitFont(TestFont, szFontFace, C4FontLoader::C4FT_Main, iFontSize, &::GraphicsResource.Files))
|
|
return false;
|
|
// OK; reinit all fonts
|
|
StdStrBuf sOldFont; sOldFont.Copy(Config.General.RXFontName);
|
|
int32_t iOldFontSize = Config.General.RXFontSize;
|
|
SCopy(szFontFace, Config.General.RXFontName);
|
|
Config.General.RXFontSize = iFontSize;
|
|
if (!::GraphicsResource.InitFonts() || !C4Startup::Get()->Graphics.InitFonts())
|
|
{
|
|
// failed :o
|
|
// shouldn't happen. Better restore config.
|
|
SCopy(sOldFont.getData(), Config.General.RXFontName);
|
|
Config.General.RXFontSize = iOldFontSize;
|
|
return false;
|
|
}
|
|
#endif
|
|
// save changes
|
|
return true;
|
|
}
|
|
|
|
void C4Application::OnCommand(const char *szCmd)
|
|
{
|
|
// reroute to whatever seems to take commands at the moment
|
|
if(AppState == C4AS_Game)
|
|
::MessageInput.ProcessInput(szCmd);
|
|
}
|
|
|
|
void C4Application::Activate()
|
|
{
|
|
#ifdef WIN32
|
|
// Activate the application to regain focus if it has been lost during loading.
|
|
// As this is officially not possible any more in new versions of Windows
|
|
// (BringWindowTopTop alone won't have any effect if the calling process is
|
|
// not in the foreground itself), we are using an ugly OS hack.
|
|
DWORD nForeThread = GetWindowThreadProcessId(GetForegroundWindow(), 0);
|
|
DWORD nAppThread = GetCurrentThreadId();
|
|
if (nForeThread != nAppThread)
|
|
{
|
|
AttachThreadInput(nForeThread, nAppThread, TRUE);
|
|
BringWindowToTop(FullScreen.hWindow);
|
|
ShowWindow(FullScreen.hWindow, SW_SHOW);
|
|
AttachThreadInput(nForeThread, nAppThread, FALSE);
|
|
}
|
|
else
|
|
{
|
|
BringWindowToTop(FullScreen.hWindow);
|
|
ShowWindow(FullScreen.hWindow, SW_SHOW);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void C4Application::SetNextMission(const char *szMissionFilename)
|
|
{
|
|
// set next mission if any is desired
|
|
if (szMissionFilename)
|
|
NextMission.Copy(szMissionFilename);
|
|
else
|
|
NextMission.Clear();
|
|
}
|
|
|
|
void C4Application::NextTick()
|
|
{
|
|
if(!pGameTimer) return;
|
|
pGameTimer->Set();
|
|
}
|
|
|
|
// *** C4ApplicationGameTimer
|
|
|
|
C4ApplicationGameTimer::C4ApplicationGameTimer()
|
|
: iLastGameTick(0), iGameTickDelay(0),
|
|
CStdMultimediaTimerProc(26)
|
|
{
|
|
}
|
|
|
|
void C4ApplicationGameTimer::SetGameTickDelay(uint32_t iDelay)
|
|
{
|
|
// Smaller than minimum refresh delay?
|
|
if (iDelay < Config.Graphics.MaxRefreshDelay)
|
|
{
|
|
// Set critical timer
|
|
SetDelay(iDelay);
|
|
// No additional breaking needed
|
|
iGameTickDelay = 0;
|
|
}
|
|
else
|
|
{
|
|
// Do some magic to get as near as possible to the requested delay
|
|
int iGraphDelay = Max<uint32_t>(1, iDelay);
|
|
iGraphDelay /= (iGraphDelay + Config.Graphics.MaxRefreshDelay - 1) / Config.Graphics.MaxRefreshDelay;
|
|
// Set critical timer
|
|
SetDelay(iGraphDelay);
|
|
// Slow down game tick
|
|
iGameTickDelay = iDelay - iGraphDelay / 2;
|
|
}
|
|
}
|
|
|
|
bool C4ApplicationGameTimer::Execute(int iTimeout, pollfd *)
|
|
{
|
|
// Check timer and reset
|
|
if (!CheckAndReset()) return true;
|
|
int Now = timeGetTime();
|
|
// Execute
|
|
if(Now >= iLastGameTick + iGameTickDelay || Game.GameGo)
|
|
{
|
|
iLastGameTick += iGameTickDelay;
|
|
// Compensate if things get too slow
|
|
if (Now >= iLastGameTick + iGameTickDelay)
|
|
iLastGameTick += (Now - iLastGameTick) / 2;
|
|
Application.GameTick();
|
|
}
|
|
// Draw always
|
|
Application.Draw();
|
|
return true;
|
|
}
|
|
|