PageRenderTime 47ms CodeModel.GetById 14ms app.highlight 27ms RepoModel.GetById 0ms app.codeStats 0ms

/src/C4Application.cpp

https://bitbucket.org/randrian/openclonk2
C++ | 601 lines | 416 code | 58 blank | 127 comment | 88 complexity | e09b4277eb2f73508a97c5bc25b4d049 MD5 | raw file
Possible License(s): WTFPL, 0BSD, LGPL-2.1, CC-BY-3.0
  1/*
  2 * OpenClonk, http://www.openclonk.org
  3 *
  4 * Copyright (c) 1998-2000, 2004-2005, 2007-2008  Matthes Bender
  5 * Copyright (c) 2004-2008  Sven Eberhardt
  6 * Copyright (c) 2005-2006, 2009  Peter Wortmann
  7 * Copyright (c) 2005-2006, 2008-2009  G?nther Brammer
  8 * Copyright (c) 2009  Nicolas Hake
  9 * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de
 10 *
 11 * Portions might be copyrighted by other authors who have contributed
 12 * to OpenClonk.
 13 *
 14 * Permission to use, copy, modify, and/or distribute this software for any
 15 * purpose with or without fee is hereby granted, provided that the above
 16 * copyright notice and this permission notice appear in all copies.
 17 * See isc_license.txt for full license and disclaimer.
 18 *
 19 * "Clonk" is a registered trademark of Matthes Bender.
 20 * See clonk_trademark_license.txt for full license.
 21 */
 22
 23/* Main class to initialize configuration and execute the game */
 24
 25#include <C4Include.h>
 26#include <C4Application.h>
 27#include <C4Version.h>
 28#ifdef _WIN32
 29#include <StdRegistry.h>
 30#include <C4UpdateDlg.h>
 31#endif
 32
 33#ifndef BIG_C4INCLUDE
 34#include "C4Game.h"
 35#include "C4GraphicsSystem.h"
 36#include "C4GraphicsResource.h"
 37#include "C4MessageInput.h"
 38#include <C4FileClasses.h>
 39#include <C4FullScreen.h>
 40#include <C4Language.h>
 41#include <C4Console.h>
 42#include <C4Startup.h>
 43#include <C4Log.h>
 44#include <C4GamePadCon.h>
 45#include <C4GameLobby.h>
 46#endif
 47
 48#include <StdRegistry.h> // For DDraw emulation warning
 49
 50
 51C4Application::C4Application():
 52	isFullScreen(true), UseStartupDialog(true), launchEditor(false), restartAtEnd(false),
 53	DDraw(NULL), AppState(C4AS_None),
 54	pGamePadControl(NULL),
 55	CheckForUpdates(false), NoSplash(false)
 56	{
 57	}
 58
 59C4Application::~C4Application()
 60	{
 61	// clear gamepad
 62	if (pGamePadControl) delete pGamePadControl;
 63	// Close log
 64	CloseLog();
 65	// Launch editor
 66	if (launchEditor)
 67		{
 68#ifdef _WIN32
 69		char strCommandLine[_MAX_PATH + 1]; SCopy(Config.AtExePath(C4CFN_Editor), strCommandLine);
 70		STARTUPINFO StartupInfo; ZeroMemory(&StartupInfo, sizeof StartupInfo);
 71		StartupInfo.cb = sizeof StartupInfo;
 72		PROCESS_INFORMATION ProcessInfo; ZeroMemory(&ProcessInfo, sizeof ProcessInfo);
 73		CreateProcess(NULL, strCommandLine, NULL, NULL, TRUE, 0, NULL, NULL, &StartupInfo, &ProcessInfo);
 74#endif
 75		}
 76	}
 77
 78bool C4Application::DoInit()
 79	{
 80	assert(AppState == C4AS_None);
 81	// Config overwrite by parameter
 82	StdStrBuf sConfigFilename;
 83  char szParameter[_MAX_PATH+1];
 84	for (int32_t iPar=0; SGetParameter(GetCommandLine(), iPar, szParameter, _MAX_PATH); iPar++)
 85		if (SEqual2NoCase(szParameter, "/config:"))
 86			sConfigFilename.Copy(szParameter + 8);
 87	// Config check
 88	Config.Init();
 89	Config.Load(true, sConfigFilename.getData());
 90	Config.Save();
 91	// sometimes, the configuration can become corrupted due to loading errors or w/e
 92	// check this and reset defaults if necessary
 93	if (Config.IsCorrupted())
 94		{
 95		if (sConfigFilename)
 96			{
 97			// custom config corrupted: Fail
 98			Log("Warning: Custom configuration corrupted - program abort!\n");
 99			return false;
100			}
101		else
102			{
103			// default config corrupted: Restore default
104			Log("Warning: Configuration corrupted - restoring default!\n");
105			Config.Default();
106			Config.Save();
107			Config.Load();
108			}
109		}
110	// Init C4Group
111	C4Group_SetMaker(Config.General.Name);
112	C4Group_SetProcessCallback(&ProcessCallback);
113	C4Group_SetTempPath(Config.General.TempPath);
114	C4Group_SetSortList(C4CFN_FLS);
115
116	// Open log
117	OpenLog();
118
119	// init system group
120	if (!SystemGroup.Open(C4CFN_System))
121		{
122		// Error opening system group - no LogFatal, because it needs language table.
123		// This will *not* use the FatalErrors stack, but this will cause the game
124		// to instantly halt, anyway.
125		const char *szMessage = "Error opening system group file (System.c4g)!";
126		Log(szMessage);
127		// Fatal error, game cannot start - have player notice
128		MessageDialog(szMessage);
129		return false;
130		}
131
132	// Language override by parameter
133	const char *pLanguage;
134	if (pLanguage = SSearchNoCase(GetCommandLine(), "/Language:"))
135		SCopyUntil(pLanguage, Config.General.LanguageEx, ' ', CFG_MaxString);
136
137	// Init external language packs
138	Languages.Init();
139	// Load language string table
140	if (!Languages.LoadLanguage(Config.General.LanguageEx))
141		// No language table was loaded - bad luck...
142		if (!IsResStrTableLoaded())
143			Log("WARNING: No language string table loaded!");
144
145	// Set unregistered user name
146	if (!Config.Registered())
147		C4Group_SetMaker(LoadResStr("IDS_PRC_UNREGUSER"));
148
149	// Parse command line
150	Game.ParseCommandLine(GetCommandLine());
151
152#ifdef WIN32
153	// Windows: handle incoming updates directly, even before starting up the gui
154	//          because updates will be applied in the console anyway.
155	if (Application.IncomingUpdate)
156		if (C4UpdateDlg::ApplyUpdate(Application.IncomingUpdate.getData(), false, NULL))
157			return true;
158#endif
159
160	DDrawCfg.Shader = Config.Graphics.EnableShaders;
161	switch (Config.Graphics.Engine) {
162#ifdef USE_DIRECTX
163		case GFXENGN_DIRECTX:
164		case GFXENGN_DIRECTXS:
165			// Direct3D
166			DDrawCfg.Set(Config.Graphics.NewGfxCfg, (float) Config.Graphics.BlitOff/100.0f);
167			break;
168#endif
169#ifdef USE_GL
170		case GFXENGN_OPENGL:
171		// OpenGL
172		DDrawCfg.Set(Config.Graphics.NewGfxCfgGL, (float) Config.Graphics.BlitOffGL/100.0f);
173		break;
174#endif
175		default: ;	// Always have at least one statement
176	}
177
178	// Fixup resolution
179	ApplyResolutionConstraints();	
180
181	// activate
182	Active=true;
183
184	// Init carrier window
185	if (isFullScreen)
186		{
187		if (!(pWindow = FullScreen.Init(this)))
188			{ Clear(); return false; }
189		}
190	else
191		{
192		if (!(pWindow = Console.Init(this)))
193			{ Clear(); return false; }
194		}
195
196	// init timers (needs window)
197	Add(pGameTimer = new C4ApplicationGameTimer());
198
199	// Engine header message
200	Log(C4ENGINEINFOLONG);
201	LogF("Version: %s %s", C4VERSION, C4_OS);
202
203	// Log registration info
204	if (Config.Registered())
205		{
206		char buf[4096 + 1] = "";
207		SAppend("Registered to: ", buf, 4096);
208		SAppend(Config.General.Name, buf, 4096); SAppend(" ", buf, 4096);
209		if (Config.GetRegistrationData("Nick")[0])
210			{ SAppend("(", buf, 4096); SAppend(Config.GetRegistrationData("Nick"), buf, 4096); SAppend(") ", buf, 4096); }
211		SAppend("[", buf, 4096); SAppend(Config.GetRegistrationData("Cuid"), buf, 4096); SAppend("]", buf, 4096);
212		Log(buf);
213		}
214	else
215		Log(Config.GetRegistrationError());
216
217#if defined(USE_DIRECTX) && defined(_WIN32)
218	// DDraw emulation warning
219	DWORD DDrawEmulationState;
220	if (GetRegistryDWord(HKEY_LOCAL_MACHINE,"Software\\Microsoft\\DirectDraw","EmulationOnly",&DDrawEmulationState))
221		if (DDrawEmulationState)
222			Log("WARNING: DDraw Software emulation is activated!");
223#endif
224	// Initialize D3D/OpenGL
225	DDraw = DDrawInit(this, isFullScreen, false, Config.Graphics.ResX, Config.Graphics.ResY, Config.Graphics.BitDepth, Config.Graphics.Engine, Config.Graphics.Monitor);
226	if (!DDraw) { LogFatal(LoadResStr("IDS_ERR_DDRAW")); Clear(); return false; }
227
228#if defined(_WIN32) && !defined(USE_CONSOLE)
229	// Register clonk file classes - notice: under Vista this will only work if we have administrator rights
230	char szModule[_MAX_PATH+1]; GetModuleFileName(NULL, szModule, _MAX_PATH);
231	SetC4FileClasses(szModule);
232#endif
233
234	// Initialize gamepad
235	if (!pGamePadControl && Config.General.GamepadEnabled)
236		pGamePadControl = new C4GamePadControl();
237
238	AppState = C4AS_PreInit;
239
240	return true;
241	}
242
243	
244void C4Application::ApplyResolutionConstraints()
245{
246	// Enumerate display modes
247	int32_t idx = 0, iXRes, iYRes, iBitDepth;
248	int32_t best_match = -1;
249	uint32_t best_delta = ~0;
250	int32_t ResX = Config.Graphics.ResX, ResY = Config.Graphics.ResY, BitDepth = Config.Graphics.BitDepth;
251	while (GetIndexedDisplayMode(idx++, &iXRes, &iYRes, &iBitDepth, Config.Graphics.Monitor))
252	{
253		uint32_t delta = std::abs(ResX*ResY - iXRes*iYRes);
254		if (!delta && iBitDepth == BitDepth)
255			return; // Exactly the expected mode
256		if (delta < best_delta)
257		{
258			// Better match than before
259			best_match = idx;
260			best_delta = delta;
261		}
262	}
263	if (best_match != -1)
264	{
265		// Apply next-best mode
266		GetIndexedDisplayMode(best_match, &iXRes, &iYRes, &iBitDepth, Config.Graphics.Monitor);
267		if (iXRes != ResX || iYRes != ResY)
268			// Don't warn if only bit depth changes
269			// Also, lang table not loaded yet
270			LogF("Warning: The selected resolution %dx%d is not available and has been changed to %dx%d.", ResX, ResY, iXRes, iYRes);
271		ResX = iXRes; ResY = iYRes;
272	}
273}
274
275bool C4Application::PreInit()
276	{
277	if (!Game.PreInit()) return false;
278
279	// startup dialog: Only use if no next mission has been provided
280	bool fDoUseStartupDialog = UseStartupDialog && !*Game.ScenarioFilename;
281
282	// init loader: Black screen for first start if a video is to be shown; otherwise default spec
283	if (fDoUseStartupDialog)
284		{
285		//Log(LoadResStr("IDS_PRC_INITLOADER"));
286		bool fUseBlackScreenLoader = UseStartupDialog && !C4Startup::WasFirstRun() && !Config.Startup.NoSplash && !NoSplash && FileExists(C4CFN_Splash);
287		if (!::GraphicsSystem.InitLoaderScreen(C4CFN_StartupBackgroundMain, fUseBlackScreenLoader))
288			{ LogFatal(LoadResStr("IDS_PRC_ERRLOADER")); return false; }
289		}
290
291	Game.SetInitProgress(fDoUseStartupDialog ? 10.0f : 1.0f);
292
293	// Music
294	if (!MusicSystem.Init("Frontend.*"))
295		Log(LoadResStr("IDS_PRC_NOMUSIC"));
296
297	Game.SetInitProgress(fDoUseStartupDialog ? 20.0f : 2.0f);
298
299	// Sound
300	if (!SoundSystem.Init())
301		Log(LoadResStr("IDS_PRC_NOSND"));
302
303	Game.SetInitProgress(fDoUseStartupDialog ? 30.0f : 3.0f);
304
305	AppState = fDoUseStartupDialog ? C4AS_Startup : C4AS_StartGame;
306
307	return true;
308	}
309
310bool C4Application::ProcessCallback(const char *szMessage, int iProcess)
311	{
312	Console.Out(szMessage);
313	return true;
314	}
315
316void C4Application::Clear()
317	{
318	Game.Clear();
319	NextMission.Clear();
320	// stop timer
321	Remove(pGameTimer);
322	delete pGameTimer; pGameTimer = NULL;
323	// close system group (System.c4g)
324	SystemGroup.Close();
325	// Log
326	if (IsResStrTableLoaded()) // Avoid (double and undefined) message on (second?) shutdown...
327		Log(LoadResStr("IDS_PRC_DEINIT"));
328	// Clear external language packs and string table
329	Languages.Clear();
330	Languages.ClearLanguage();
331	// gamepad clear
332	if (pGamePadControl) { delete pGamePadControl; pGamePadControl=NULL; }
333	// music system clear
334	MusicSystem.Clear();
335	SoundSystem.Clear();
336	RestoreVideoMode();
337	// Clear direct draw (late, because it's needed for e.g. Log)
338	if (DDraw) { delete DDraw; DDraw=NULL; }
339	// Close window
340	FullScreen.Clear();
341	Console.Clear();
342	// The very final stuff
343	CStdApp::Clear();
344	}
345
346bool C4Application::OpenGame()
347	{
348	if (isFullScreen)
349		{
350		// Open game
351		return Game.Init();
352		}
353	else
354		{
355		// Execute command line
356		if (Game.ScenarioFilename[0] || Game.DirectJoinAddress[0])
357			return Console.OpenGame(szCmdLine);
358		}
359	// done; success
360	return true;
361	}
362
363void C4Application::Quit()
364	{
365	// Clear definitions passed by frontend for this round
366	Config.General.Definitions[0] = 0;
367	// Participants should not be cleared for usual startup dialog
368	//Config.General.Participants[0] = 0;
369	// Save config if there was no loading error
370	if (Config.fConfigLoaded) Config.Save();
371	// quit app
372	CStdApp::Quit();
373	AppState = C4AS_Quit;
374	}
375
376void C4Application::QuitGame()
377	{
378	// reinit desired? Do restart
379	if (UseStartupDialog || NextMission)
380		{
381		// backup last start params
382		bool fWasNetworkActive = Game.NetworkActive;
383		// stop game
384		Game.Clear();
385		Game.Default();
386		AppState = C4AS_PreInit;
387		// if a next mission is desired, set to start it
388		if (NextMission)
389			{
390			SCopy(NextMission.getData(), Game.ScenarioFilename, _MAX_PATH);
391			SReplaceChar(Game.ScenarioFilename, '\\', DirSep[0]); // linux/mac: make sure we are using forward slashes
392			Game.fLobby = Game.NetworkActive = fWasNetworkActive;
393			Game.fObserve = false;
394			Game.Record = !!Config.General.Record;
395			NextMission.Clear();
396			}
397		}
398	else
399		{
400		Quit();
401		}
402	}
403
404void C4Application::GameTick()
405	{
406	// Exec depending on game state
407	assert(AppState != C4AS_None);
408	switch (AppState)
409		{
410		case C4AS_Quit:
411			// Do nothing, HandleMessage will return HR_Failure soon
412			break;
413		case C4AS_PreInit:
414			if (!PreInit()) Quit();
415			break;
416		case C4AS_Startup:
417			AppState = C4AS_Game;
418			// if no scenario or direct join has been specified, get game startup parameters by startup dialog
419			Game.ScenarioTitle.Copy(LoadResStr("IDS_PRC_INITIALIZE"));
420			if (!C4Startup::Execute()) { Quit(); return; }
421			AppState = C4AS_StartGame;
422			break;
423		case C4AS_StartGame:
424			// immediate progress to next state; OpenGame will enter HandleMessage-loops in startup and lobby!
425			AppState = C4AS_Game;
426			// first-time game initialization
427			if (!OpenGame())
428				{
429				// set error flag (unless this was a lobby user abort)
430				if (!C4GameLobby::UserAbort)
431					Game.fQuitWithError = true;
432				// no start: Regular QuitGame; this may reset the engine to startup mode if desired
433				QuitGame();
434				}
435			break;
436		case C4AS_Game:
437			// Game
438			if (Game.IsRunning)
439				Game.Execute();
440			Game.DoSkipFrame = false;
441			// Sound
442			SoundSystem.Execute();
443			// Gamepad
444			if (pGamePadControl) pGamePadControl->Execute();
445			break;
446		}
447	}
448
449void C4Application::Draw()
450	{
451	// Graphics
452	if(!Game.DoSkipFrame)
453		{
454		// Fullscreen mode
455		if (isFullScreen)
456			FullScreen.Execute();
457		// Console mode
458		else
459			Console.Execute();
460		}
461	}
462
463void C4Application::SetGameTickDelay(int iDelay)
464	{
465	if(!pGameTimer) return;
466	pGameTimer->SetGameTickDelay(iDelay);
467	}
468
469void C4Application::OnResolutionChanged(unsigned int iXRes, unsigned int iYRes)
470	{
471	// notify game
472	if (DDraw)
473		{
474		Game.OnResolutionChanged(iXRes, iYRes);
475		DDraw->OnResolutionChanged(iXRes, iYRes);
476		}
477	}
478
479bool C4Application::SetGameFont(const char *szFontFace, int32_t iFontSize)
480	{
481#ifndef USE_CONSOLE
482	// safety
483	if (!szFontFace || !*szFontFace || iFontSize<1 || SLen(szFontFace)>=static_cast<int>(sizeof Config.General.RXFontName)) return false;
484	// first, check if the selected font can be created at all
485	// check regular font only - there's no reason why the other fonts couldn't be created
486	CStdFont TestFont;
487	if (!Game.FontLoader.InitFont(TestFont, szFontFace, C4FontLoader::C4FT_Main, iFontSize, &::GraphicsResource.Files))
488		return false;
489	// OK; reinit all fonts
490	StdStrBuf sOldFont; sOldFont.Copy(Config.General.RXFontName);
491	int32_t iOldFontSize = Config.General.RXFontSize;
492	SCopy(szFontFace, Config.General.RXFontName);
493	Config.General.RXFontSize = iFontSize;
494	if (!::GraphicsResource.InitFonts() || !C4Startup::Get()->Graphics.InitFonts())
495		{
496		// failed :o
497		// shouldn't happen. Better restore config.
498		SCopy(sOldFont.getData(), Config.General.RXFontName);
499		Config.General.RXFontSize = iOldFontSize;
500		return false;
501		}
502#endif
503	// save changes
504	return true;
505	}
506
507void C4Application::OnCommand(const char *szCmd)
508	{
509	// reroute to whatever seems to take commands at the moment
510	if(AppState == C4AS_Game)
511		::MessageInput.ProcessInput(szCmd);
512	}
513
514void C4Application::Activate()
515	{
516#ifdef WIN32
517	// Activate the application to regain focus if it has been lost during loading.
518	// As this is officially not possible any more in new versions of Windows
519	// (BringWindowTopTop alone won't have any effect if the calling process is
520	// not in the foreground itself), we are using an ugly OS hack.
521  DWORD nForeThread = GetWindowThreadProcessId(GetForegroundWindow(), 0);
522  DWORD nAppThread = GetCurrentThreadId();
523	if (nForeThread != nAppThread)
524		{
525    AttachThreadInput(nForeThread, nAppThread, TRUE);
526    BringWindowToTop(FullScreen.hWindow);
527    ShowWindow(FullScreen.hWindow, SW_SHOW);
528    AttachThreadInput(nForeThread, nAppThread, FALSE);
529		}
530  else
531		{
532    BringWindowToTop(FullScreen.hWindow);
533    ShowWindow(FullScreen.hWindow, SW_SHOW);
534		}
535#endif
536	}
537
538void C4Application::SetNextMission(const char *szMissionFilename)
539	{
540	// set next mission if any is desired
541	if (szMissionFilename)
542		NextMission.Copy(szMissionFilename);
543	else
544		NextMission.Clear();
545	}
546
547void C4Application::NextTick()
548	{
549	if(!pGameTimer) return;
550	pGameTimer->Set();
551	}
552
553// *** C4ApplicationGameTimer
554
555C4ApplicationGameTimer::C4ApplicationGameTimer()
556	: iLastGameTick(0), iGameTickDelay(0),
557		CStdMultimediaTimerProc(26)
558	{
559	}
560
561void C4ApplicationGameTimer::SetGameTickDelay(uint32_t iDelay)
562	{
563	// Smaller than minimum refresh delay?
564	if (iDelay < Config.Graphics.MaxRefreshDelay)
565		{
566		// Set critical timer
567		SetDelay(iDelay);
568		// No additional breaking needed
569		iGameTickDelay = 0;
570		}
571	else
572		{
573		// Do some magic to get as near as possible to the requested delay
574		int iGraphDelay = Max<uint32_t>(1, iDelay);
575		iGraphDelay /= (iGraphDelay + Config.Graphics.MaxRefreshDelay - 1) / Config.Graphics.MaxRefreshDelay;
576		// Set critical timer
577		SetDelay(iGraphDelay);
578		// Slow down game tick
579		iGameTickDelay = iDelay - iGraphDelay / 2;
580		}
581	}
582
583bool C4ApplicationGameTimer::Execute(int iTimeout, pollfd *)
584	{
585	// Check timer and reset
586	if (!CheckAndReset()) return true;
587	int Now = timeGetTime();
588	// Execute
589	if(Now >= iLastGameTick + iGameTickDelay || Game.GameGo)
590		{
591		iLastGameTick += iGameTickDelay;
592		// Compensate if things get too slow
593		if (Now >= iLastGameTick + iGameTickDelay)
594			iLastGameTick += (Now - iLastGameTick) / 2;
595		Application.GameTick();
596		}
597	// Draw always
598	Application.Draw();
599	return true;
600	}
601