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: Scenario selection dialog
19
20#pragma once
21
22#include "C4Gui.h"
23#include "C4Startup.h"
24#include "C4Scenario.h"
25#include "C4Folder.h"
26
27#include <memory>
28
29class C4StartupScenSelDlg;
30
31const int32_t C4StartupScenSel_DefaultIcon_Scenario = 14,
32 C4StartupScenSel_DefaultIcon_Folder = 0,
33 C4StartupScenSel_DefaultIcon_WinFolder = 44,
34 C4StartupScenSel_DefaultIcon_OldIconBG = 18,
35 C4StartupScenSel_IconCount = 52,
36 C4StartupScenSel_TitlePictureWdt = 200,
37 C4StartupScenSel_TitlePictureHgt = 150,
38 C4StartupScenSel_TitlePicturePadding = 10,
39 C4StartupScenSel_TitleOverlayMargin = 10; // number of pixels to each side of title overlay picture
40
41// a list of loaded scenarios
42class C4ScenarioListLoader
43{
44public:
45 class Folder;
46 // either a scenario or scenario folder; manages singly linked tree
47 class Entry
48 {
49 protected:
50 Entry *pNext;
51 class Folder *pParent;
52
53 friend class Folder;
54
55 protected:
56 StdStrBuf sName, sFilename, sDesc, sVersion, sAuthor, sMaker;
57 C4FacetExSurface fctIcon, fctTitle;
58 bool fBaseLoaded, fExLoaded;
59 int iIconIndex;
60 int iDifficulty;
61 int iFolderIndex;
62
63 public:
64 Entry(class Folder *pParent);
65 virtual ~Entry(); // dtor: unlink from tree
66
67 bool Load(C4Group *pFromGrp, const StdStrBuf *psFilename, bool fLoadEx); // load as child if pFromGrp, else directly from filename
68 virtual bool LoadCustom(C4Group &rGrp, bool fNameLoaded, bool fIconLoaded) { return true; } // load custom data for entry type (e.g. scenario title fallback in Scenario.txt)
69 virtual bool LoadCustomPre(C4Group &rGrp) { return true; } // preload stuff that's early in the group (Scenario.txt)
70 virtual bool Start() = 0; // start/open entry
71 virtual Folder *GetIsFolder() { return nullptr; } // return this if this is a folder
72
73 const StdStrBuf &GetName() const { return sName; }
74 const StdStrBuf &GetEntryFilename() const { return sFilename; }
75 const StdStrBuf &GetVersion() const { return sVersion; }
76 const StdStrBuf &GetAuthor() const { return sAuthor; }
77 const C4Facet &GetIconFacet() const { return fctIcon; }
78 const C4Facet &GetTitlePicture() const { return fctTitle; }
79 const StdStrBuf &GetDesc() const { return sDesc; }
80 const int GetIconIndex() { return iIconIndex; }
81 const int GetDifficulty() { return iDifficulty; }
82 const int GetFolderIndex() { return iFolderIndex; }
83 Entry *GetNext() const { return pNext; }
84 class Folder *GetParent() const { return pParent; }
85 virtual StdStrBuf GetTypeName() = 0;
86
87 static Entry *CreateEntryForFile(const StdStrBuf &sFilename, Folder *pParent); // create correct entry type based on file extension
88
89 virtual bool CanOpen(StdStrBuf &sError) { return true; } // whether item can be started/opened (e.g. mission access)
90 virtual bool HasMissionAccess() const { return true; }
91 virtual StdStrBuf GetOpenText() = 0; // get open button text
92 virtual StdStrBuf GetOpenTooltip() = 0;
93
94 virtual C4SForceFairCrew GetFairCrewAllowed() const { return C4SFairCrew_Free; }
95
96 virtual const char *GetDefaultExtension() { return nullptr; } // extension to be added when item is renamed
97 virtual bool SetTitleInGroup(C4Group &rGrp, const char *szNewTitle);
98 bool RenameTo(const char *szNewName); // change name+filename
99 virtual bool IsScenario() { return false; }
100 };
101
102 // a loaded scenario to be started
103 class Scenario : public Entry
104 {
105 private:
106 C4Scenario C4S;
107 bool fNoMissionAccess;
108 int32_t iMinPlrCount;
109
110 public:
111 Scenario(class Folder *pParent) : Entry(pParent), fNoMissionAccess(false), iMinPlrCount(0) {}
112 virtual ~Scenario() {}
113
114 virtual bool LoadCustom(C4Group &rGrp, bool fNameLoaded, bool fIconLoaded) override; // do fallbacks for title and icon; check whether scenario is valid
115 virtual bool LoadCustomPre(C4Group &rGrp) override; // load scenario core
116 virtual bool Start() override; // launch scenario!
117
118 virtual bool CanOpen(StdStrBuf &sError) override; // check mission access, player count, etc.
119 virtual bool HasMissionAccess() const override { return !fNoMissionAccess; } // check mission access only
120 virtual StdStrBuf GetOpenText() override; // get open button text
121 virtual StdStrBuf GetOpenTooltip() override;
122 const C4Scenario &GetC4S() const { return C4S; } // get scenario core
123
124 virtual C4SForceFairCrew GetFairCrewAllowed() const override { return static_cast<C4SForceFairCrew>(C4S.Head.ForcedFairCrew); }
125
126 virtual StdStrBuf GetTypeName() override { return StdStrBuf(LoadResStr(id: C4ResStrTableKey::IDS_TYPE_SCENARIO)); }
127
128 virtual const char *GetDefaultExtension() override { return "c4s"; }
129
130 virtual bool IsScenario() override { return true; }
131 };
132
133 // scenario folder
134 class Folder : public Entry
135 {
136 protected:
137 C4Folder C4F;
138 bool fContentsLoaded; // if set, directory contents are already loaded
139 Entry *pFirst; // tree structure
140 class C4MapFolderData *pMapData; // if set, contains gfx and data for special map-style folders
141
142 friend class Entry;
143
144 public:
145 Folder(Folder *pParent) : Entry(pParent), fContentsLoaded(false), pFirst(nullptr), pMapData(nullptr) {}
146 virtual ~Folder();
147
148 virtual bool LoadCustomPre(C4Group &rGrp) override; // load folder core
149
150 bool LoadContents(C4ScenarioListLoader *pLoader, C4Group *pFromGrp, const StdStrBuf *psFilename, bool fLoadEx, bool fReload); // load folder contents as child if pFromGrp, else directly from filename
151 uint32_t GetEntryCount() const;
152
153 protected:
154 void ClearChildren();
155 void Sort();
156 virtual bool DoLoadContents(C4ScenarioListLoader *pLoader, C4Group *pFromGrp, const StdStrBuf &sFilename, bool fLoadEx) = 0; // load folder contents as child if pFromGrp, else directly from filename
157
158 public:
159 virtual bool Start() override; // open as subfolder
160 virtual Folder *GetIsFolder() override { return this; } // this is a folder
161 Entry *GetFirstEntry() const { return pFirst; }
162 void Resort() { Sort(); }
163 Entry *FindEntryByName(const char *szFilename) const; // find entry by filename comparison
164
165 virtual bool CanOpen(StdStrBuf &sError) override { return true; } // can always open folders
166 virtual StdStrBuf GetOpenText() override; // get open button text
167 virtual StdStrBuf GetOpenTooltip() override;
168 C4MapFolderData *GetMapData() const { return pMapData; }
169 };
170
171 // .c4f subfolder: Read through by group
172 class SubFolder : public Folder
173 {
174 public:
175 SubFolder(Folder *pParent) : Folder(pParent) {}
176 virtual ~SubFolder() {}
177
178 virtual const char *GetDefaultExtension() override { return "c4f"; }
179
180 virtual StdStrBuf GetTypeName() override { return StdStrBuf(LoadResStr(id: C4ResStrTableKey::IDS_TYPE_FOLDER)); }
181
182 protected:
183 virtual bool LoadCustom(C4Group &rGrp, bool fNameLoaded, bool fIconLoaded) override; // load custom data for entry type - icon fallback to folder icon
184 virtual bool DoLoadContents(C4ScenarioListLoader *pLoader, C4Group *pFromGrp, const StdStrBuf &sFilename, bool fLoadEx) override; // load folder contents as child if pFromGrp, else directly from filename
185 };
186
187 // regular, open folder: Read through by directory iterator
188 class RegularFolder : public Folder
189 {
190 public:
191 RegularFolder(Folder *pParent) : Folder(pParent) {}
192 virtual ~RegularFolder() {}
193
194 virtual StdStrBuf GetTypeName() override { return StdStrBuf(LoadResStr(id: C4ResStrTableKey::IDS_TYPE_DIRECTORY)); }
195
196 protected:
197 virtual bool LoadCustom(C4Group &rGrp, bool fNameLoaded, bool fIconLoaded) override; // load custom data for entry type - icon fallback to folder icon
198 virtual bool DoLoadContents(C4ScenarioListLoader *pLoader, C4Group *pFromGrp, const StdStrBuf &sFilename, bool fLoadEx) override; // load folder contents as child if pFromGrp, else directly from filename
199 };
200
201private:
202 Folder *pRootFolder, *pCurrFolder; // scenario list in working directory
203 int32_t iLoading, iProgress, iMaxProgress;
204 bool fAbortThis, fAbortPrevious; // activity state
205 unsigned long lastCheckTimer{0};
206
207public:
208 C4ScenarioListLoader();
209 ~C4ScenarioListLoader();
210
211private:
212 // activity control (to be replaced by true multithreading)
213 bool BeginActivity(bool fAbortPrevious);
214 void EndActivity();
215
216public:
217 bool DoProcessCallback(int32_t iProgress, int32_t iMaxProgress); // returns false if the activity was aborted
218
219public:
220 bool Load(const StdStrBuf &sRootFolder); // (unthreaded) loading of all entries in root folder
221 bool Load(Folder *pSpecifiedFolder, bool fReload); // (unthreaded) loading of all entries in subfolder
222 bool LoadExtended(Entry *pEntry); // (unthreaded) loading of desc and title image of specified entry
223 bool FolderBack(); // go upwards by one folder
224 bool ReloadCurrent(); // reload file list
225 bool IsLoading() const { return !!iLoading; }
226 Entry *GetFirstEntry() const { return pCurrFolder ? pCurrFolder->GetFirstEntry() : nullptr; }
227
228 Folder *GetCurrFolder() const { return pCurrFolder; }
229 Folder *GetRootFolder() const { return pRootFolder; }
230
231 int32_t GetProgressPercent() const { return iProgress * 100 / std::max<int32_t>(a: iMaxProgress, b: 1); }
232};
233
234// for map-style folders: Data for map display
235class C4MapFolderData
236{
237private:
238 struct Scenario
239 {
240 // compiled data
241 StdStrBuf sFilename;
242 StdStrBuf sBaseImage, sOverlayImage;
243 bool singleClick{false}; // selected by single click instead of double click
244
245 // parameters for title as drawn on the map (if desired; otherwise sTitle empty)
246 StdStrBuf sTitle;
247 int32_t iTitleFontSize;
248 uint32_t dwTitleInactClr, dwTitleActClr;
249 int32_t iTitleOffX, iTitleOffY;
250 uint8_t byTitleAlign;
251 bool fTitleBookFont;
252 bool fImgDump; // developer help: Dump background image part
253
254 C4Rect rcOverlayPos;
255 C4GUI::FLOAT_RECT rcfOverlayPos;
256
257 // set during initialization
258 C4FacetExSurface fctBase, fctOverlay;
259 C4ScenarioListLoader::Entry *pScenEntry;
260 C4GUI::Button *pBtn; // used to resolve button events to scenario
261
262 void CompileFunc(StdCompiler *pComp);
263 };
264
265 struct AccessGfx
266 {
267 // compiled data
268 StdStrBuf sPassword;
269 StdStrBuf sOverlayImage;
270 C4Rect rcOverlayPos;
271 C4GUI::FLOAT_RECT rcfOverlayPos;
272
273 // set during initialization
274 C4FacetExSurface fctOverlay;
275
276 void CompileFunc(StdCompiler *pComp);
277 };
278
279 // non-interactive map item
280 class MapPic : public C4GUI::Picture
281 {
282 private:
283 C4GUI::FLOAT_RECT rcfBounds; // drawing bounds
284
285 public:
286 MapPic(const C4GUI::FLOAT_RECT &rcfBounds, const C4Facet &rfct);
287
288 protected:
289 virtual void MouseInput(C4GUI::CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, uint32_t dwKeyParam) override; // input: mouse movement or buttons - deselect everything if clicked
290 virtual void DrawElement(C4FacetEx &cgo) override; // draw the image
291 };
292
293private:
294 C4FacetExSurface fctBackgroundPicture; C4GUI::FLOAT_RECT rcfBG;
295 bool fCoordinatesAdjusted;
296 C4Rect rcScenInfoArea; // area in which scenario info is displayed
297 class C4ScenarioListLoader::Folder *pScenarioFolder;
298 class C4ScenarioListLoader::Entry *pSelectedEntry;
299 C4GUI::TextWindow *pSelectionInfoBox;
300 int32_t MinResX, MinResY; // minimum resolution for display of the map
301 bool fUseFullscreenMap;
302 bool hideTitle;
303 Scenario **ppScenList; int32_t iScenCount;
304 AccessGfx **ppAccessGfxList; int32_t iAccessGfxCount;
305 class C4StartupScenSelDlg *pMainDlg;
306
307public:
308 C4MapFolderData() : fCoordinatesAdjusted(false), iScenCount(0), ppScenList(nullptr), iAccessGfxCount(0), ppAccessGfxList(nullptr), pMainDlg(nullptr) {}
309 ~C4MapFolderData() { Clear(); }
310
311private:
312 void ConvertFacet2ScreenCoord(const C4Rect &rc, C4GUI::FLOAT_RECT *pfrc, float fBGZoomX, float fBGZoomY, int iOffX, int iOffY);
313 void ConvertFacet2ScreenCoord(int32_t *piValue, float fBGZoom, int iOff);
314 void ConvertFacet2ScreenCoord(C4Rect &rcMapArea, bool fAspect); // adjust coordinates of loaded facets so they match given area
315
316protected:
317 bool OnButtonScenario(C4GUI::Control *pEl);
318
319 friend class C4StartupScenSelDlg;
320
321public:
322 void Clear();
323 bool Load(C4Group &hGroup, C4ScenarioListLoader::Folder *pScenLoaderFolder);
324 void CompileFunc(StdCompiler *pComp);
325 void CreateGUIElements(C4StartupScenSelDlg *pMainDlg, C4GUI::Window &rContainer);
326 void ResetSelection();
327
328 C4GUI::TextWindow *GetSelectionInfoBox() const { return pSelectionInfoBox; }
329 C4ScenarioListLoader::Entry *GetSelectedEntry() const { return pSelectedEntry; }
330};
331
332// startup dialog: Scenario selection
333class C4StartupScenSelDlg : public C4StartupDlg
334{
335public:
336 // one item in the scenario list
337 class ScenListItem : public C4GUI::Window
338 {
339 private:
340 typedef C4GUI::Window BaseClass;
341 // subcomponents
342 C4GUI::Picture *pIcon; // item icon
343 C4GUI::Label *pNameLabel; // item caption
344 C4ScenarioListLoader::Entry *pScenListEntry; // associated, loaded item info
345
346 public:
347 ScenListItem(C4GUI::ListBox *pForListBox, C4ScenarioListLoader::Entry *pForEntry, C4GUI::Element *pInsertBeforeElement = nullptr);
348
349 protected:
350 struct RenameParams {};
351 void AbortRenaming(RenameParams par);
352 C4GUI::RenameResult DoRenaming(RenameParams par, const char *szNewName);
353
354 public:
355 bool KeyRename();
356
357 protected:
358 virtual void UpdateOwnPos() override; // recalculate item positioning
359 virtual void MouseInput(C4GUI::CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, uint32_t dwKeyParam) override;
360
361 void Update() {}
362
363 public:
364 C4ScenarioListLoader::Entry *GetEntry() const { return pScenListEntry; }
365 ScenListItem *GetNext() { return static_cast<ScenListItem *>(BaseClass::GetNext()); }
366
367 virtual bool CheckNameHotkey(const char *c) override; // return whether this item can be selected by entering given char
368 };
369
370public:
371 C4StartupScenSelDlg(bool fNetwork);
372 ~C4StartupScenSelDlg();
373
374private:
375 enum { ShowStyle_Book = 0, ShowStyle_Map = 1, };
376 enum { IconLabelSpacing = 2 }; // space between an icon and its text
377
378 // book style scenario selection
379 C4GUI::Label *pScenSelCaption; // caption label atop scenario list; indicating current folder
380 C4GUI::ListBox *pScenSelList; // left page of book: Scenario selection
381 C4GUI::Label *pScenSelProgressLabel; // progress label shown while scenario list is being generated
382 C4GUI::TextWindow *pSelectionInfo; // used to display the description of the current selection
383
384 std::unique_ptr<C4KeyBinding> keyRefresh, keyBack, keyForward, keyRename, keyDelete, keyCheat, keySearch, keyEscape;
385 class C4GameOptionButtons *pGameOptionButtons;
386 C4GUI::Button *pOpenBtn;
387 C4GUI::Tabular *pScenSelStyleTabular;
388 C4GUI::Edit *searchBar;
389
390 C4ScenarioListLoader *pScenLoader;
391
392 // map style scenario selection
393 C4MapFolderData *pMapData;
394 C4FacetEx *pfctBackground;
395
396 bool fIsInitialLoading;
397 bool fStartNetworkGame;
398
399 C4GUI::RenameEdit *pRenameEdit;
400 C4GUI::CheckBox *btnAllowUserChange;
401
402public:
403 static C4StartupScenSelDlg *pInstance; // singleton
404
405protected:
406 virtual int32_t GetMarginTop() override { return (rcBounds.Hgt / 7); }
407 virtual bool HasBackground() override { return true; }
408 virtual void DrawElement(C4FacetEx &cgo) override;
409 void HideTitle(bool hide = false);
410
411 virtual bool OnEnter() override { DoOK(); return true; }
412 virtual bool OnEscape() override { DoBack(fAllowClose: true); return true; }
413 bool KeyBack() { return DoBack(fAllowClose: true); }
414 bool KeyRefresh() { DoRefresh(); return true; }
415 bool KeyForward() { DoOK(); return true; }
416 bool KeyRename();
417 bool KeyDelete();
418 bool KeyCheat();
419 bool KeySearch();
420 bool KeyEscape();
421 void KeyCheat2(const StdStrBuf &rsCheatCode);
422
423 void DeleteConfirm(ScenListItem *pSel);
424
425 virtual void OnShown() override; // callback when shown: Init file list
426 virtual void OnClosed(bool fOK) override; // callback when dlg got closed: Return to main screen
427 void OnBackBtn(C4GUI::Control *btn) { DoBack(fAllowClose: true); }
428 void OnNextBtn(C4GUI::Control *btn) { DoOK(); }
429 void OnSelChange(class C4GUI::Element *pEl) { UpdateSelection(); }
430 void OnSelDblClick(class C4GUI::Element *pEl) { DoOK(); }
431 void UpdateUseCrewBtn();
432 void OnButtonScenario(C4GUI::Control *pEl);
433
434 C4GUI::InputResult OnSearchBarEnter(C4GUI::Edit *edt, bool fPasting, bool fPastingMore)
435 {
436 UpdateList(); return C4GUI::IR_Abort;
437 }
438
439 friend class C4MapFolderData;
440
441private:
442 void UpdateList();
443 void UpdateSelection();
444 void ResortFolder();
445 ScenListItem *GetSelectedItem();
446 C4ScenarioListLoader::Entry *GetSelectedEntry();
447 void SetOpenButtonDefaultText();
448 void FocusScenList();
449
450public:
451 bool StartScenario(C4ScenarioListLoader::Scenario *pStartScen);
452 bool OpenFolder(C4ScenarioListLoader::Folder *pNewFolder);
453 void ProcessCallback() { UpdateList(); } // process callback by loader
454 bool DoOK(); // open/start currently selected item
455 bool DoBack(bool fAllowClose); // back folder, or abort dialog
456 void DoRefresh(); // refresh file list
457 void DeselectAll(); // reset focus and update selection info
458
459 void StartRenaming(C4GUI::RenameEdit *pNewRenameEdit);
460 void AbortRenaming();
461 bool IsRenaming() const { return !!pRenameEdit; }
462 void SetRenamingDone() { pRenameEdit = nullptr; }
463
464 void SetBackground(C4FacetEx *pNewBG) { pfctBackground = pNewBG; }
465
466 bool IsNetworkStart() const { return fStartNetworkGame; }
467
468 friend class ScenListItem;
469};
470