/src/C4Application.cpp
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