1/*
2 * LegacyClonk
3 *
4 * Copyright (c) RedWolf Design
5 * Copyright (c) 2005, Sven2
6 * Copyright (c) 2017-2020, The LegacyClonk Team and contributors
7 *
8 * Distributed under the terms of the ISC license; see accompanying file
9 * "COPYING" for details.
10 *
11 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12 * See accompanying file "TRADEMARK" for details.
13 *
14 * To redistribute this file separately, substitute the full license texts
15 * for the above references.
16 */
17
18// Startup screen for non-parameterized engine start
19
20#include <C4Include.h>
21#include <C4Startup.h>
22
23#include <C4StartupMainDlg.h>
24#include <C4StartupScenSelDlg.h>
25#include <C4StartupNetDlg.h>
26#include <C4StartupOptionsDlg.h>
27#include <C4StartupAboutDlg.h>
28#include <C4StartupPlrSelDlg.h>
29#include <C4Game.h>
30#include <C4Application.h>
31#include <C4Log.h>
32
33bool C4StartupGraphics::LoadFile(C4FacetExID &rToFct, const char *szFilename)
34{
35 return Game.GraphicsResource.LoadFile(fct&: rToFct, szName: szFilename, rGfxSet&: Game.GraphicsResource.Files);
36}
37
38bool C4StartupGraphics::Init()
39{
40 // load startup specific graphics from gfxsys groupset
41 fctScenSelBG.GetFace().SetBackground();
42 if (!LoadFile(rToFct&: fctScenSelBG, szFilename: "StartupScenSelBG")) return false;
43 Game.SetInitProgress(45);
44 if (!LoadFile(rToFct&: fctPlrSelBG, szFilename: "StartupPlrSelBG")) return false;
45 Game.SetInitProgress(50);
46 if (!LoadFile(rToFct&: fctPlrPropBG, szFilename: "StartupPlrPropBG")) return false;
47 Game.SetInitProgress(55);
48 if (!LoadFile(rToFct&: fctNetBG, szFilename: "StartupNetworkBG")) return false;
49 Game.SetInitProgress(57);
50 if (!LoadFile(rToFct&: fctAboutBG, szFilename: "LoaderWatercave1")) return false;
51 Game.SetInitProgress(60);
52 if (!LoadFile(rToFct&: fctMainButtons, szFilename: "StartupBigButton")) return false;
53 Game.SetInitProgress(62);
54 barMainButtons.SetHorizontal(rByFct&: fctMainButtons);
55 if (!LoadFile(rToFct&: fctMainButtonsDown, szFilename: "StartupBigButtonDown")) return false;
56 Game.SetInitProgress(64);
57 barMainButtonsDown.SetHorizontal(rByFct&: fctMainButtonsDown);
58 if (!LoadFile(rToFct&: fctBookScroll, szFilename: "StartupBookScroll")) return false;
59 sfctBookScroll.Set(rByFct: fctBookScroll);
60 sfctBookScrollR.Set(rByFct: fctBookScroll, iPinIndex: 1);
61 sfctBookScrollG.Set(rByFct: fctBookScroll, iPinIndex: 2);
62 sfctBookScrollB.Set(rByFct: fctBookScroll, iPinIndex: 3);
63 Game.SetInitProgress(66);
64 if (!LoadFile(rToFct&: fctContext, szFilename: "StartupContext")) return false;
65 fctContext.Set(nsfc: fctContext.Surface, nx: 0, ny: 0, nwdt: fctContext.Hgt, nhgt: fctContext.Hgt);
66 Game.SetInitProgress(67);
67 if (!LoadFile(rToFct&: fctScenSelIcons, szFilename: "StartupScenSelIcons")) return false;
68 Game.SetInitProgress(68);
69 fctScenSelIcons.Wdt = fctScenSelIcons.Hgt; // icon width is determined by icon height
70 if (!LoadFile(rToFct&: fctScenSelTitleOverlay, szFilename: "StartupScenSelTitleOv")) return false;
71 Game.SetInitProgress(70);
72 if (!LoadFile(rToFct&: fctPlrCtrlType, szFilename: "StartupPlrCtrlType")) return false;
73 fctPlrCtrlType.Set(nsfc: fctPlrCtrlType.Surface, nx: 0, ny: 0, nwdt: 128, nhgt: 52);
74 Game.SetInitProgress(72);
75 if (!LoadFile(rToFct&: fctOptionsDlgPaper, szFilename: "StartupDlgPaper")) return false;
76 Game.SetInitProgress(74);
77 if (!LoadFile(rToFct&: fctOptionsIcons, szFilename: "StartupOptionIcons")) return false;
78 fctOptionsIcons.Set(nsfc: fctOptionsIcons.Surface, nx: 0, ny: 0, nwdt: fctOptionsIcons.Hgt, nhgt: fctOptionsIcons.Hgt);
79 Game.SetInitProgress(76);
80 if (!LoadFile(rToFct&: fctOptionsTabClip, szFilename: "StartupTabClip")) return false;
81 Game.SetInitProgress(80);
82 if (!LoadFile(rToFct&: fctNetGetRef, szFilename: "StartupNetGetRef")) return false;
83 fctNetGetRef.Wdt = 40;
84#ifndef USE_CONSOLE
85 Game.SetInitProgress(82);
86 if (!InitFonts()) return false;
87#endif
88 Game.SetInitProgress(100);
89 return true;
90}
91
92#ifndef USE_CONSOLE
93bool C4StartupGraphics::InitFonts()
94{
95 const char *szFont = Config.General.RXFontName;
96 if (!Game.FontLoader.InitFont(rFont&: BookFontCapt, szFontName: szFont, eType: C4FontLoader::C4FT_Caption, iSize: Config.General.RXFontSize, pGfxGroups: &Game.GraphicsResource.Files, fDoShadow: false))
97 {
98 LogFatalNTr(message: "Font Error (1)"); return false;
99 }
100 Game.SetInitProgress(85);
101 if (!Game.FontLoader.InitFont(rFont&: BookFont, szFontName: szFont, eType: C4FontLoader::C4FT_Main, iSize: Config.General.RXFontSize, pGfxGroups: &Game.GraphicsResource.Files, fDoShadow: false))
102 {
103 LogFatalNTr(message: "Font Error (2)"); return false;
104 }
105 Game.SetInitProgress(90);
106 if (!Game.FontLoader.InitFont(rFont&: BookFontTitle, szFontName: szFont, eType: C4FontLoader::C4FT_Title, iSize: Config.General.RXFontSize, pGfxGroups: &Game.GraphicsResource.Files, fDoShadow: false))
107 {
108 LogFatalNTr(message: "Font Error (3)"); return false;
109 }
110 Game.SetInitProgress(95);
111 if (!Game.FontLoader.InitFont(rFont&: BookSmallFont, szFontName: szFont, eType: C4FontLoader::C4FT_MainSmall, iSize: Config.General.RXFontSize, pGfxGroups: &Game.GraphicsResource.Files, fDoShadow: false))
112 {
113 LogFatalNTr(message: "Font Error (4)"); return false;
114 }
115 return true;
116}
117#endif
118
119CStdFont &C4StartupGraphics::GetBlackFontByHeight(int32_t iHgt, float *pfZoom)
120{
121 // get optimal font for given control size
122 CStdFont *pUseFont;
123 if (iHgt <= BookSmallFont.GetLineHeight()) pUseFont = &BookSmallFont;
124 else if (iHgt <= BookFont.GetLineHeight()) pUseFont = &BookFont;
125 else if (iHgt <= BookFontCapt.GetLineHeight()) pUseFont = &BookFontCapt;
126 else pUseFont = &BookFontTitle;
127 // determine zoom
128 if (pfZoom)
129 {
130 int32_t iLineHgt = pUseFont->GetLineHeight();
131 if (iLineHgt)
132 *pfZoom = static_cast<float>(iHgt) / static_cast<float>(iLineHgt);
133 else
134 *pfZoom = 1.0f; // error
135 }
136 return *pUseFont;
137}
138
139// statics
140C4Startup::DialogID C4Startup::eLastDlgID = C4Startup::SDID_Main;
141
142// startup singleton instance
143C4Startup *C4Startup::pInstance = nullptr;
144
145C4Startup::C4Startup() : fInStartup(false), fAborted(false), pLastDlg(nullptr), pCurrDlg(nullptr)
146{
147 // must be single!
148 assert(!pInstance);
149 pInstance = this;
150}
151
152C4Startup::~C4Startup()
153{
154 pInstance = nullptr;
155 if (Game.pGUI)
156 {
157 delete pLastDlg;
158 delete pCurrDlg;
159 }
160}
161
162void C4Startup::Start()
163{
164 assert(fInStartup);
165 // flag game start
166 fAborted = false;
167 fInStartup = false;
168 fLastDlgWasBack = false;
169}
170
171void C4Startup::Exit()
172{
173 assert(fInStartup);
174 // flag game start
175 fAborted = true;
176 fInStartup = false;
177}
178
179C4StartupDlg *C4Startup::SwitchDialog(DialogID eToDlg, bool fFade)
180{
181 // can't go back twice, because dialog is not remembered: Always go back to main in this case
182 if (eToDlg == SDID_Back && (fLastDlgWasBack || !pLastDlg)) eToDlg = SDID_Main;
183 fLastDlgWasBack = false;
184 // create new dialog
185 C4StartupDlg *pToDlg = nullptr;
186 switch (eToDlg)
187 {
188 case SDID_Main:
189 pToDlg = new C4StartupMainDlg();
190 break;
191 case SDID_ScenSel:
192 pToDlg = new C4StartupScenSelDlg(false);
193 break;
194 case SDID_ScenSelNetwork:
195 pToDlg = new C4StartupScenSelDlg(true);
196 break;
197 case SDID_NetJoin:
198 pToDlg = new C4StartupNetDlg();
199 break;
200 case SDID_Options:
201 pToDlg = new C4StartupOptionsDlg();
202 break;
203 case SDID_About:
204 pToDlg = new C4StartupAboutDlg();
205 break;
206 case SDID_PlrSel:
207 pToDlg = new C4StartupPlrSelDlg();
208 break;
209 case SDID_Back:
210 pToDlg = pLastDlg;
211 fLastDlgWasBack = true;
212 break;
213 };
214 assert(pToDlg);
215 if (!pToDlg) return nullptr;
216 if (pToDlg != pLastDlg)
217 {
218 // remember current position
219 eLastDlgID = eToDlg;
220 // kill any old dialog
221 delete pLastDlg;
222 }
223 // retain current dialog as last, so it can fade out and may be used later
224 if (pLastDlg = pCurrDlg)
225 if (fFade)
226 {
227 if (!pLastDlg->IsShown()) pLastDlg->Show(pOnScreen: Game.pGUI, fCB: false);
228 pLastDlg->FadeOut(fCloseWithOK: true);
229 }
230 else
231 {
232 delete pLastDlg;
233 pLastDlg = nullptr;
234 }
235 // Okay; now using this dialog
236 pCurrDlg = pToDlg;
237 // fade in new dlg
238 if (fFade)
239 {
240 if (!pToDlg->FadeIn(pOnScreen: Game.pGUI))
241 {
242 delete pToDlg; pCurrDlg = nullptr;
243 return nullptr;
244 }
245 }
246 else
247 {
248 if (!pToDlg->Show(pOnScreen: Game.pGUI, fCB: true))
249 {
250 delete pToDlg; pCurrDlg = nullptr;
251 return nullptr;
252 }
253 }
254 return pToDlg;
255}
256
257bool C4Startup::DoStartup()
258{
259 assert(!fInStartup);
260 assert(Game.pGUI);
261 // now in startup!
262 fInStartup = true;
263 fLastDlgWasBack = false;
264
265 // Play some music!
266 Application.MusicSystem->PlayFrontendMusic();
267
268 // clear any previous
269 delete pLastDlg; pLastDlg = nullptr;
270 delete pCurrDlg; pCurrDlg = nullptr;
271
272 // start with the last dlg that was shown - at first startup main dialog
273 if (!SwitchDialog(eToDlg: eLastDlgID)) return false;
274
275 // show error dlg if restart
276 if (const std::string fatalError{Application.LogSystem.GetFatalErrorString()}; Game.fQuitWithError || !fatalError.empty())
277 {
278 Game.fQuitWithError = false;
279 // preferred: Show fatal error
280 if (!fatalError.empty())
281 {
282 Application.LogSystem.ClearRingbuffer();
283 Game.pGUI->ShowMessage(szMessage: fatalError.c_str(), szCaption: LoadResStr(id: C4ResStrTableKey::IDS_DLG_LOG), icoIcon: C4GUI::Ico_Error);
284 }
285 else
286 {
287 if (const auto logEntries = Application.LogSystem.GetRingbufferLogEntries(); !logEntries.empty())
288 {
289 std::string logEntriesString;
290 for (const auto &error : logEntries)
291 {
292 if (!logEntriesString.empty())
293 {
294 logEntriesString += '|';
295 }
296
297 logEntriesString += error;
298 }
299
300 Game.pGUI->ShowRemoveDlg(pDlg: new C4GUI::InfoDialog(LoadResStr(id: C4ResStrTableKey::IDS_DLG_LOG), 10, StdStrBuf{logEntriesString.c_str(), logEntriesString.size(), false}));
301 }
302 else
303 {
304 Game.pGUI->ShowMessage(szMessage: "(no error)", szCaption: LoadResStr(id: C4ResStrTableKey::IDS_DLG_LOG), icoIcon: C4GUI::Ico_Error);
305 }
306 }
307 Application.LogSystem.ResetFatalErrors();
308 }
309
310 // while state startup: keep looping
311 while (fInStartup && Game.pGUI && !pCurrDlg->IsAborted())
312 if (Application.HandleMessage() == HR_Failure) return false;
313
314 // check whether startup was aborted; first checking Game.pGUI
315 // (because an external call to Game.Clear() would invalidate dialogs)
316 if (!Game.pGUI) return false;
317 delete pLastDlg; pLastDlg = nullptr;
318 if (pCurrDlg)
319 {
320 // deinit last shown dlg
321 if (pCurrDlg->IsAborted())
322 {
323 // force abort flag if dlg abort done by user
324 fAborted = true;
325 }
326 else if (pCurrDlg->IsShown())
327 {
328 pCurrDlg->Close(fOK: true);
329 }
330 delete pCurrDlg;
331 pCurrDlg = nullptr;
332 }
333
334 // now no more in startup!
335 fInStartup = false;
336
337 // after startup: cleanup
338 if (Game.pGUI) Game.pGUI->CloseAllDialogs(fWithOK: true);
339
340 // reinit keyboard to reflect any config changes that might have been done
341 // this is a good time to do it, because no GUI dialogs are opened
342 if (Game.pGUI) if (!Game.InitKeyboard()) LogFatal(id: C4ResStrTableKey::IDS_ERR_NOKEYBOARD);
343
344 // all okay; return whether startup finished with a game start selection
345 return !fAborted;
346}
347
348C4Startup *C4Startup::EnsureLoaded()
349{
350 // create and load startup data if not done yet
351 assert(Game.pGUI);
352 if (!pInstance)
353 {
354 Game.SetInitProgress(40);
355 C4Startup *pStartup = new C4Startup();
356 // load startup specific gfx
357 if (!pStartup->Graphics.Init())
358 {
359 LogFatal(id: C4ResStrTableKey::IDS_ERR_NOGFXSYS); delete pStartup; return nullptr;
360 }
361 }
362 return pInstance;
363}
364
365void C4Startup::Unload()
366{
367 // make sure startup data is destroyed
368 delete pInstance; pInstance = nullptr;
369}
370
371bool C4Startup::Execute()
372{
373 // ensure gfx are loaded
374 C4Startup *pStartup = EnsureLoaded();
375 if (!pStartup) return false;
376 // exec it
377 bool fResult = pStartup->DoStartup();
378 return fResult;
379}
380
381bool C4Startup::SetStartScreen(const char *szScreen)
382{
383 // set dialog ID to be shown to specified value
384 if (SEqualNoCase(szStr1: szScreen, szStr2: "main"))
385 eLastDlgID = SDID_Main;
386 if (SEqualNoCase(szStr1: szScreen, szStr2: "scen"))
387 eLastDlgID = SDID_ScenSel;
388 if (SEqualNoCase(szStr1: szScreen, szStr2: "netscen"))
389 eLastDlgID = SDID_ScenSelNetwork;
390 else if (SEqualNoCase(szStr1: szScreen, szStr2: "net"))
391 eLastDlgID = SDID_NetJoin;
392 else if (SEqualNoCase(szStr1: szScreen, szStr2: "options"))
393 eLastDlgID = SDID_Options;
394 else if (SEqualNoCase(szStr1: szScreen, szStr2: "plrsel"))
395 eLastDlgID = SDID_PlrSel;
396 else if (SEqualNoCase(szStr1: szScreen, szStr2: "about"))
397 eLastDlgID = SDID_About;
398 else return false;
399 return true;
400}
401