/src/C4Application.cpp

https://bitbucket.org/randrian/openclonk2 · C++ · 601 lines · 416 code · 58 blank · 127 comment · 88 complexity · e09b4277eb2f73508a97c5bc25b4d049 MD5 · raw file

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