1 /*
2 * OpenClonk, http://www.openclonk.org
3 *
4 * Copyright (c) 2005-2009, RedWolf Design GmbH, http://www.clonk.de/
5 * Copyright (c) 2009-2016, The OpenClonk Team and contributors
6 *
7 * Distributed under the terms of the ISC license; see accompanying file
8 * "COPYING" for details.
9 *
10 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11 * See accompanying file "TRADEMARK" for details.
12 *
13 * To redistribute this file separately, substitute the full license texts
14 * for the above references.
15 */
16 // Startup screen for non-parameterized engine start: Player selection dialog
17 // Also contains player creation, editing and crew management
18
19 #include "C4Include.h"
20 #include "gui/C4StartupPlrSelDlg.h"
21
22 #include "graphics/C4Draw.h"
23 #include "graphics/C4GraphicsResource.h"
24 #include "gui/C4FileSelDlg.h"
25 #include "gui/C4MouseControl.h"
26 #include "gui/C4StartupMainDlg.h"
27 #include "lib/C4Random.h"
28 #include "lib/StdColors.h"
29 #include "player/C4RankSystem.h"
30
31 // font clrs
32 const uint32_t ClrPlayerItem = 0xffffffff;
33
34 // Arbitrary cut-off value for player color value. This avoids pitch black
35 // colors which look ugly. Note that this limit is only applied in the UI,
36 // it's still possible to edit the Player.txt by hand.
37 const uint32_t PlayerColorValueLowBound = 64;
38
39 // ----- C4Utilities
40
TimeString(int iSeconds)41 StdStrBuf TimeString(int iSeconds)
42 {
43 int iHours = iSeconds / 3600; iSeconds -= 3600*iHours;
44 int iMinutes = iSeconds / 60; iSeconds -= 60*iMinutes;
45 return FormatString("%02d:%02d:%02d",iHours,iMinutes,iSeconds);
46 }
47
DateString(int iTime)48 StdStrBuf DateString(int iTime)
49 {
50 if (!iTime) return StdStrBuf("", true);
51 time_t tTime = iTime;
52 struct tm *pLocalTime;
53 pLocalTime=localtime(&tTime);
54 return FormatString( "%02d.%02d.%d %02d:%02d",
55 pLocalTime->tm_mday,
56 pLocalTime->tm_mon+1,
57 pLocalTime->tm_year+1900,
58 pLocalTime->tm_hour,
59 pLocalTime->tm_min);
60 }
61
62 // ------------------------------------------------
63 // --- C4StartupPlrSelDlg::ListItem
ListItem(C4StartupPlrSelDlg * pForDlg,C4GUI::ListBox * pForListBox,C4GUI::Element * pInsertBeforeElement,bool fActivated)64 C4StartupPlrSelDlg::ListItem::ListItem(C4StartupPlrSelDlg *pForDlg, C4GUI::ListBox *pForListBox, C4GUI::Element *pInsertBeforeElement, bool fActivated)
65 : Control(C4Rect(0,0,0,0)), pCheck(nullptr), pNameLabel(nullptr), pPlrSelDlg(pForDlg), pIcon(nullptr)
66 {
67 CStdFont &rUseFont = GraphicsResource.FontRegular;
68 // calc height
69 int32_t iHeight = rUseFont.GetLineHeight() + 2 * IconLabelSpacing;
70 // create subcomponents
71 pCheck = new C4GUI::CheckBox(C4Rect(0, 0, iHeight, iHeight), nullptr, fActivated);
72 pCheck->SetOnChecked(new C4GUI::CallbackHandler<C4StartupPlrSelDlg>(pForDlg, &C4StartupPlrSelDlg::OnItemCheckChange));
73 pKeyCheck = new C4KeyBinding(C4KeyCodeEx(K_SPACE), "StartupPlrSelTogglePlayerActive", KEYSCOPE_Gui,
74 new C4GUI::ControlKeyCB<ListItem>(*this, &ListItem::KeyCheck), C4CustomKey::PRIO_Ctrl);
75 pIcon = new C4GUI::Icon(C4Rect(iHeight + IconLabelSpacing, 0, iHeight, iHeight), C4GUI::Ico_Player);
76 pNameLabel = new C4GUI::Label("Q", (iHeight + IconLabelSpacing)*2, IconLabelSpacing, ALeft, ClrPlayerItem, &rUseFont, false, false);
77 pNameLabel->SetAutosize(false);
78 // calc own bounds - use icon bounds only, because only the height is used when the item is added
79 SetBounds(pIcon->GetBounds());
80 // add components
81 AddElement(pCheck);
82 AddElement(pIcon); AddElement(pNameLabel);
83 // add to listbox (will get resized horizontally and moved) - zero indent; no tree structure in this dialog
84 pForListBox->InsertElement(this, pInsertBeforeElement, 0);
85 // update name label width to stretch max listbox width
86 C4Rect rcNameLabelBounds = pNameLabel->GetBounds();
87 rcNameLabelBounds.Wdt = GetClientRect().Wdt - rcNameLabelBounds.x - IconLabelSpacing;
88 pNameLabel->SetBounds(rcNameLabelBounds);
89 // context menu
90 SetContextHandler(new C4GUI::CBContextHandler<C4StartupPlrSelDlg::ListItem>(this, &C4StartupPlrSelDlg::ListItem::ContextMenu));
91 }
92
~ListItem()93 C4StartupPlrSelDlg::ListItem::~ListItem()
94 {
95 delete pKeyCheck;
96 }
97
GetName() const98 const char *C4StartupPlrSelDlg::ListItem::GetName() const
99 {
100 // name is stored in label only
101 return pNameLabel->GetText();
102 }
103
SetName(const char * szNewName)104 void C4StartupPlrSelDlg::ListItem::SetName(const char *szNewName)
105 {
106 // update name in label
107 pNameLabel->SetText(szNewName);
108 // tooltip by name, so long names can be read via tooltip
109 SetToolTip(szNewName);
110 }
111
GrabIcon(C4FacetSurface & rFromFacet)112 void C4StartupPlrSelDlg::ListItem::GrabIcon(C4FacetSurface &rFromFacet)
113 {
114 // take over icon gfx from facet - deletes them from source facet!
115 if (rFromFacet.Surface)
116 {
117 pIcon->GetMFacet().GrabFrom(rFromFacet);
118 }
119 else
120 {
121 // reset custom icon
122 // following update-call will reset to default icon
123 pIcon->GetMFacet().Clear();
124 }
125 }
126
SetIcon(C4GUI::Icons icoNew)127 void C4StartupPlrSelDlg::ListItem::SetIcon(C4GUI::Icons icoNew)
128 {
129 pIcon->SetIcon(icoNew);
130 }
131
UpdateOwnPos()132 void C4StartupPlrSelDlg::ListItem::UpdateOwnPos()
133 {
134 // parent for client rect
135 typedef C4GUI::Window ParentClass;
136 ParentClass::UpdateOwnPos();
137 // reposition items
138 C4GUI::ComponentAligner caBounds(GetContainedClientRect(), IconLabelSpacing, IconLabelSpacing);
139 // nothing to reposition for now...
140 }
141
SetFilename(const StdStrBuf & sNewFN)142 void C4StartupPlrSelDlg::ListItem::SetFilename(const StdStrBuf &sNewFN)
143 {
144 // just set fn - UpdateCore-call will follow later
145 Filename.Copy(sNewFN);
146 }
147
CheckNameHotkey(const char * c)148 bool C4StartupPlrSelDlg::ListItem::CheckNameHotkey(const char * c)
149 {
150 // return whether this item can be selected by entering given char:
151 // first char of name must match
152 // FIXME: Unicode
153 if (!pNameLabel) return false;
154 const char *szName = pNameLabel->GetText();
155 return szName && (toupper(*szName) == toupper(c[0]));
156 }
157
158
159 // ------------------------------------------------
160 // --- C4StartupPlrSelDlg::PlayerListItem
PlayerListItem(C4StartupPlrSelDlg * pForDlg,C4GUI::ListBox * pForListBox,C4GUI::Element * pInsertBeforeElement,bool fActivated)161 C4StartupPlrSelDlg::PlayerListItem::PlayerListItem(C4StartupPlrSelDlg *pForDlg, C4GUI::ListBox *pForListBox, C4GUI::Element *pInsertBeforeElement, bool fActivated)
162 : ListItem(pForDlg, pForListBox, pInsertBeforeElement, fActivated), fHasCustomIcon(false)
163 {
164 }
165
Load(const StdStrBuf & rsFilename)166 void C4StartupPlrSelDlg::PlayerListItem::Load(const StdStrBuf &rsFilename)
167 {
168 int32_t iHeight = GetBounds().Hgt;
169 // backup filename
170 SetFilename(rsFilename);
171 // load player info
172 C4Group PlrGroup;
173 if (!PlrGroup.Open(Config.AtUserDataPath(rsFilename.getData())))
174 throw LoadError(FormatString("Error loading player file from %s: Error opening group: %s", rsFilename.getData(), PlrGroup.GetError()));
175 if (!Core.Load(PlrGroup))
176 throw LoadError(FormatString("Error loading player file from %s: Core data invalid or missing (Group: %s)!", rsFilename.getData(), PlrGroup.GetError()));
177 // load icon
178 C4FacetSurface fctIcon;
179 if (PlrGroup.FindEntry(C4CFN_BigIcon) && fctIcon.Load(PlrGroup, C4CFN_BigIcon, C4FCT_Full, C4FCT_Full, false, 0))
180 fHasCustomIcon = true;
181 else
182 {
183 // no custom icon: create default by player color
184 fctIcon.Create(iHeight,iHeight);
185 ::GraphicsResource.fctPlayerClr.DrawClr(fctIcon, true, Core.PrefColorDw);
186 }
187 GrabIcon(fctIcon);
188 // done loading
189 if (!PlrGroup.Close())
190 throw LoadError(FormatString("Error loading player file from %s: Error closing group: %s", rsFilename.getData(), PlrGroup.GetError()));
191 // default name
192 if (!*Core.PrefName) SCopy(GetFilenameOnly(rsFilename.getData()), Core.PrefName, sizeof(Core.PrefName)-1);
193 SetName(Core.PrefName);
194 }
195
ContextMenu()196 C4GUI::ContextMenu *C4StartupPlrSelDlg::PlayerListItem::ContextMenu()
197 {
198 // menu operations work on selected item only
199 pPlrSelDlg->SetSelection(this);
200 // context menu operations
201 C4GUI::ContextMenu *pCtx = new C4GUI::ContextMenu();
202 pCtx->AddItem(LoadResStr("IDS_BTN_PROPERTIES"), LoadResStr("IDS_DLGTIP_PLAYERPROPERTIES"), C4GUI::Ico_None, new C4GUI::CBMenuHandler<C4StartupPlrSelDlg>(pPlrSelDlg, &C4StartupPlrSelDlg::OnPropertyCtx));
203 pCtx->AddItem(LoadResStr("IDS_BTN_DELETE"), LoadResStr("IDS_DLGTIP_PLAYERDELETE"), C4GUI::Ico_None, new C4GUI::CBMenuHandler<C4StartupPlrSelDlg>(pPlrSelDlg, &C4StartupPlrSelDlg::OnDelCtx));
204 return pCtx;
205 }
206
GrabCustomIcon(C4FacetSurface & fctGrabFrom)207 void C4StartupPlrSelDlg::PlayerListItem::GrabCustomIcon(C4FacetSurface &fctGrabFrom)
208 {
209 // set flag
210 fHasCustomIcon = !!fctGrabFrom.Surface;
211 // base class grab
212 GrabIcon(fctGrabFrom);
213 }
214
UpdateCore(C4PlayerInfoCore & NewCore)215 void C4StartupPlrSelDlg::PlayerListItem::UpdateCore(C4PlayerInfoCore & NewCore)
216 {
217 C4Group PlrGroup;
218 if (!PlrGroup.Open(Config.AtUserDataPath(GetFilename().getData()))
219 || !NewCore.Save(PlrGroup)
220 || !PlrGroup.Close())
221 {
222 GetScreen()->ShowMessage(LoadResStr("IDS_FAIL_MODIFY"), "", C4GUI::Ico_Error);
223 return;
224 }
225 Core = NewCore;
226 SetName(Core.PrefName);
227 // re-set non-custom icons
228 if (!fHasCustomIcon)
229 {
230 fHasCustomIcon = false;
231 int32_t iHeight = GetBounds().Hgt;
232 C4FacetSurface fctIcon; fctIcon.Create(iHeight,iHeight);
233 ::GraphicsResource.fctPlayerClr.DrawClr(fctIcon, true, Core.PrefColorDw);
234 GrabIcon(fctIcon);
235 }
236 // update in selection
237 C4StartupPlrSelDlg *pDlg = static_cast<C4StartupPlrSelDlg *>(GetDlg());
238 if (pDlg && pDlg->GetSelection() == this) pDlg->UpdateSelection();
239 }
240
SetSelectionInfo(C4GUI::TextWindow * pSelectionInfo)241 void C4StartupPlrSelDlg::PlayerListItem::SetSelectionInfo(C4GUI::TextWindow *pSelectionInfo)
242 {
243 // write info text for player
244 pSelectionInfo->ClearText(false);
245 pSelectionInfo->AddTextLine(FormatString("%s", Core.PrefName).getData(), &C4Startup::Get()->Graphics.BookFontCapt, ClrPlayerItem, false, false);
246 pSelectionInfo->AddTextLine(FormatString(LoadResStr("IDS_DESC_PLAYER"), (int)Core.TotalScore, (int)Core.Rounds, (int)Core.RoundsWon, (int)Core.RoundsLost, TimeString(Core.TotalPlayingTime).getData(), Core.Comment).getData(), &C4Startup::Get()->Graphics.BookFont, ClrPlayerItem, false, false);
247 if (Core.LastRound.Title[0])
248 pSelectionInfo->AddTextLine(FormatString(LoadResStr("IDS_DESC_LASTGAME"),Core.LastRound.Title.getData(),DateString(Core.LastRound.Date).getData(),TimeString(Core.LastRound.Duration).getData(),(int)Core.LastRound.FinalScore).getData(), &C4Startup::Get()->Graphics.BookFont, ClrPlayerItem, false, false);
249 pSelectionInfo->UpdateHeight();
250 }
251
GetDelWarning()252 StdStrBuf C4StartupPlrSelDlg::PlayerListItem::GetDelWarning()
253 {
254 StdStrBuf sWarning;
255 sWarning.Format(LoadResStr("IDS_MSG_DELETEPLR"), Core.PrefName);
256 int32_t iPlrTime = Core.TotalPlayingTime;
257 if (iPlrTime > 60*60*10)
258 sWarning.Append(FormatString(LoadResStr("IDS_MSG_DELETEPLR_PLAYTIME"),
259 TimeString(iPlrTime).getData()).getData());
260 return sWarning;
261 }
262
MoveFilename(const char * szToFilename)263 bool C4StartupPlrSelDlg::PlayerListItem::MoveFilename(const char *szToFilename)
264 {
265 // anything to do?
266 if (ItemIdentical(GetFilename().getData(), szToFilename)) return true;
267 // do it
268 StdStrBuf PathFrom(Config.General.UserDataPath);
269 PathFrom.Append(GetFilename());
270 StdStrBuf PathTo(Config.General.UserDataPath);
271 PathTo.Append(szToFilename);
272 if (!MoveItem(PathFrom.getData(), PathTo.getData())) return false;
273 // reflect change in class
274 SetFilename(StdStrBuf(szToFilename));
275 return true;
276 }
277
278
279 // ------------------------------------------------
280 // --- C4StartupPlrSelDlg::CrewListItem
281
CrewListItem(C4StartupPlrSelDlg * pForDlg,C4GUI::ListBox * pForListBox,uint32_t dwPlrClr)282 C4StartupPlrSelDlg::CrewListItem::CrewListItem(C4StartupPlrSelDlg *pForDlg, C4GUI::ListBox *pForListBox, uint32_t dwPlrClr)
283 : ListItem(pForDlg, pForListBox, nullptr, false), fLoaded(false), dwPlrClr(dwPlrClr), pParentGrp(nullptr)
284 {
285 SetIcon(C4GUI::Ico_Wait);
286 }
287
UpdateClonkEnabled()288 void C4StartupPlrSelDlg::CrewListItem::UpdateClonkEnabled()
289 {
290 if (!fLoaded) return;
291 Core.Participation = pCheck->GetChecked();
292 // immediate save of changes
293 RewriteCore();
294 }
295
Load(C4Group & rGrp,const StdStrBuf & rsFilename)296 void C4StartupPlrSelDlg::CrewListItem::Load(C4Group &rGrp, const StdStrBuf &rsFilename)
297 {
298 // backup filename (doesn't include path)
299 SetFilename(rsFilename);
300 // load core
301 C4Group CrewGroup;
302 if (!CrewGroup.OpenAsChild(&rGrp, rsFilename.getData()))
303 throw LoadError(FormatString("Error loading crew file from %s in %s: Error opening group: %s",
304 rsFilename.getData(), rGrp.GetFullName().getData(), CrewGroup.GetError()));
305 if (!Core.Load(CrewGroup))
306 throw LoadError(FormatString("Error loading crew file from %s: Core data invalid or missing (Group: %s)!",
307 CrewGroup.GetFullName().getData(), CrewGroup.GetError()));
308 ListItem::SetName(Core.Name);
309 pCheck->SetChecked(!!Core.Participation);
310 // load rank as icon
311 C4FacetSurface fctIcon;
312 if (fctIcon.Load(CrewGroup, C4CFN_ClonkRank, C4FCT_Full, C4FCT_Full, false, true))
313 {
314 GrabIcon(fctIcon);
315 }
316 else
317 {
318 // no custom icon: create default by rank system
319 if (C4RankSystem::DrawRankSymbol(&fctIcon, Core.Rank, &::GraphicsResource.fctRank, ::GraphicsResource.iNumRanks, true))
320 GrabIcon(fctIcon);
321 }
322 // backup group loaded from - assumes it stays valid!
323 pParentGrp = &rGrp;
324 // load success!
325 fLoaded=true;
326 }
327
ContextMenu()328 C4GUI::ContextMenu *C4StartupPlrSelDlg::CrewListItem::ContextMenu()
329 {
330 // menu operations work on selected item only
331 pPlrSelDlg->SetSelection(this);
332 // context menu operations
333 C4GUI::ContextMenu *pCtx = new C4GUI::ContextMenu();
334 pCtx->AddItem(LoadResStr("IDS_BTN_RENAME"), LoadResStr("IDS_DESC_CREWRENAME"), C4GUI::Ico_None, new C4GUI::CBMenuHandler<C4StartupPlrSelDlg>(pPlrSelDlg, &C4StartupPlrSelDlg::OnPropertyCtx));
335 pCtx->AddItem(LoadResStr("IDS_BTN_DELETE"), LoadResStr("IDS_MSG_DELETECLONK_DESC"), C4GUI::Ico_None, new C4GUI::CBMenuHandler<C4StartupPlrSelDlg>(pPlrSelDlg, &C4StartupPlrSelDlg::OnDelCtx));
336 pCtx->AddItem(LoadResStr("IDS_MSG_SETDEATHMESSAGE"), LoadResStr("IDS_MSG_SETTHEMESSAGETHATAPPEARWH"), C4GUI::Ico_None, new C4GUI::CBMenuHandler<C4StartupPlrSelDlg::CrewListItem>(this, &C4StartupPlrSelDlg::CrewListItem::OnDeathMessageCtx));
337 return pCtx;
338 }
339
OnDeathMessageCtx(C4GUI::Element * el)340 void C4StartupPlrSelDlg::CrewListItem::OnDeathMessageCtx(C4GUI::Element *el)
341 {
342 // Death message dialog
343 C4GUI::InputDialog *pDlg;
344 GetScreen()->ShowRemoveDlg(pDlg=new C4GUI::InputDialog(LoadResStr("IDS_MSG_ENTERNEWDEATHMESSAGE"), LoadResStr("IDS_MSG_SETDEATHMESSAGE"), C4GUI::Ico_Ex_Comment, new C4GUI::InputCallback<C4StartupPlrSelDlg::CrewListItem>(this, &C4StartupPlrSelDlg::CrewListItem::OnDeathMessageSet), false));
345 pDlg->SetMaxText(C4MaxDeathMsg);
346 pDlg->SetInputText(Core.DeathMessage);
347 }
348
OnDeathMessageSet(const StdStrBuf & rsNewMessage)349 void C4StartupPlrSelDlg::CrewListItem::OnDeathMessageSet(const StdStrBuf &rsNewMessage)
350 {
351 // copy msg
352 if (!rsNewMessage) *Core.DeathMessage='\0'; else SCopy(rsNewMessage.getData(), Core.DeathMessage, C4MaxDeathMsg);
353 // save
354 RewriteCore();
355 // acoustic feedback
356 C4GUI::GUISound("UI::Confirmed");
357 }
358
RewriteCore()359 void C4StartupPlrSelDlg::CrewListItem::RewriteCore()
360 {
361 if (!fLoaded) return;
362 C4Group CrewGroup;
363 if (!CrewGroup.OpenAsChild(pParentGrp, GetFilename().getData())
364 || !Core.Save(CrewGroup, nullptr)
365 || !CrewGroup.Close() || !pParentGrp->Save(true))
366 {
367 GetScreen()->ShowMessage(LoadResStr("IDS_FAIL_MODIFY"), "", C4GUI::Ico_Error);
368 return;
369 }
370 }
371
SetName(const char * szNewName)372 bool C4StartupPlrSelDlg::CrewListItem::SetName(const char *szNewName)
373 {
374 if (!fLoaded) return false;
375 // validate name
376 if (!szNewName || !*szNewName) return false;
377 if (SEqual(szNewName, Core.Name)) return true;
378 // generate filename from new name
379 char fn[_MAX_PATH+1];
380 SCopy(szNewName, fn, _MAX_PATH);
381 MakeFilenameFromTitle(fn);
382 if (!*fn) return false;
383 SAppend(".oci", fn, _MAX_PATH);
384 // check if a rename is due
385 if (!ItemIdentical(fn, GetFilename().getData()))
386 {
387 // check for duplicate filename
388 if (pParentGrp->FindEntry(fn))
389 {
390 StdStrBuf sMsg; sMsg.Format(LoadResStr("IDS_ERR_CLONKCOLLISION"), fn);
391 ::pGUI->ShowMessageModal(sMsg.getData(), LoadResStr("IDS_FAIL_RENAME"), C4GUI::MessageDialog::btnOK, C4GUI::Ico_Error);
392 return false;
393 }
394 // OK; then rename
395 if (!pParentGrp->Rename(GetFilename().getData(), fn) || !pParentGrp->Save(true))
396 {
397 StdStrBuf sMsg; sMsg.Format(LoadResStr("IDS_ERR_RENAMEFILE"), GetFilename().getData(), fn);
398 ::pGUI->ShowMessageModal(sMsg.getData(), LoadResStr("IDS_FAIL_RENAME"), C4GUI::MessageDialog::btnOK, C4GUI::Ico_Error);
399 return false;
400 }
401 const char *szConstFn = fn;
402 SetFilename(StdStrBuf(szConstFn));
403 }
404 // update clonk name and core
405 ListItem::SetName(szNewName);
406 SCopy(szNewName, Core.Name, C4MaxName);
407 RewriteCore();
408 return true;
409 }
410
SetSelectionInfo(C4GUI::TextWindow * pSelectionInfo)411 void C4StartupPlrSelDlg::CrewListItem::SetSelectionInfo(C4GUI::TextWindow *pSelectionInfo)
412 {
413 // write info text for player
414 pSelectionInfo->ClearText(false);
415 pSelectionInfo->AddTextLine(FormatString("%s %s", Core.sRankName.getData(), Core.Name).getData(), &C4Startup::Get()->Graphics.BookFontCapt, ClrPlayerItem, false, false);
416 StdStrBuf sPromo;
417 int32_t iNextRankExp; StdStrBuf sNextRankName;
418 if (Core.GetNextRankInfo(::DefaultRanks, &iNextRankExp, &sNextRankName))
419 sPromo.Format(LoadResStr("IDS_DESC_PROMO"),sNextRankName.getData(),(int) iNextRankExp);
420 else
421 sPromo.Copy(LoadResStr("IDS_DESC_NOPROMO"));
422 pSelectionInfo->AddTextLine(FormatString(LoadResStr("IDS_DESC_OBJECT"),
423 Core.TypeName, Core.Experience, Core.Rounds, Core.DeathCount,
424 sPromo.getData(), TimeString(Core.TotalPlayingTime).getData(), DateString(Core.Birthday).getData()).getData(),
425 &C4Startup::Get()->Graphics.BookFont, ClrPlayerItem, false, false);
426 pSelectionInfo->UpdateHeight();
427 }
428
GetDelWarning()429 StdStrBuf C4StartupPlrSelDlg::CrewListItem::GetDelWarning()
430 {
431 StdStrBuf sWarning;
432 sWarning.Format(LoadResStr("IDS_MSG_DELETECLONK"),
433 Core.sRankName.getData(), Core.Name, GetFilename().getData());
434 int32_t iPlrTime = Core.TotalPlayingTime;
435 if (iPlrTime > 60*60*10)
436 sWarning.Append(static_cast<const StdStrBuf &>(FormatString(LoadResStr("IDS_MSG_DELETECLONK_PLAYTIME"), TimeString(iPlrTime).getData())));
437 return sWarning;
438 }
439
CrewRename()440 void C4StartupPlrSelDlg::CrewListItem::CrewRename()
441 {
442 if (pPlrSelDlg->pRenameEdit) return;
443 // rename this entry
444 pPlrSelDlg->pRenameEdit = new C4GUI::CallbackRenameEdit<C4StartupPlrSelDlg::CrewListItem, RenameParams>(pNameLabel, this, RenameParams(), &C4StartupPlrSelDlg::CrewListItem::DoRenaming, &C4StartupPlrSelDlg::CrewListItem::AbortRenaming);
445 }
446
AbortRenaming(RenameParams par)447 void C4StartupPlrSelDlg::CrewListItem::AbortRenaming(RenameParams par)
448 {
449 // no renaming
450 pPlrSelDlg->pRenameEdit = nullptr;
451 }
452
DoRenaming(RenameParams par,const char * szNewName)453 C4GUI::RenameEdit::RenameResult C4StartupPlrSelDlg::CrewListItem::DoRenaming(RenameParams par, const char *szNewName)
454 {
455 // accept if name can be set; will fail if name is invalid or already given to another Crew member
456 if (!SetName(szNewName)) return C4GUI::RenameEdit::RR_Invalid;
457 pPlrSelDlg->pRenameEdit = nullptr;
458 // update in selection
459 C4StartupPlrSelDlg *pDlg = static_cast<C4StartupPlrSelDlg *>(GetDlg());
460 if (pDlg && pDlg->GetSelection() == this) pDlg->UpdateSelection();
461 return C4GUI::RenameEdit::RR_Accepted;
462 }
463
464
465
466 // ------------------------------------------------
467 // --- C4StartupPlrSelDlg
468
C4StartupPlrSelDlg()469 C4StartupPlrSelDlg::C4StartupPlrSelDlg() : C4StartupDlg("W")
470 {
471 // ctor
472 UpdateSize(); // for clientrect
473
474 // screen calculations
475 int iButtonHeight = C4GUI_ButtonHgt;
476 int iButtonXSpacing = (GetClientRect().Wdt > 700) ? GetClientRect().Wdt/58 : 2;
477 int iButtonCount = 6;
478 C4GUI::ComponentAligner caMain(GetClientRect(), 0,0, true);
479 C4GUI::ComponentAligner caButtonArea(caMain.GetFromBottom(std::max(caMain.GetHeight()/15, iButtonHeight)),0,0);
480 rcBottomButtons = caButtonArea.GetCentered(caMain.GetWidth(), iButtonHeight);
481 iBottomButtonWidth = (caButtonArea.GetWidth() - iButtonXSpacing * (iButtonCount-1)) / iButtonCount;
482 C4Rect rcMain = caMain.GetAll();
483 C4Rect rcPlrList = C4Rect(rcMain.Wdt/8, rcMain.Hgt/8, rcMain.Wdt*5/16, rcMain.Hgt*6/8);
484 C4Rect rcInfoWindow = C4Rect(rcMain.Wdt*9/16, rcMain.Hgt/8, rcMain.Wdt*5/16, rcMain.Hgt*6/8);
485
486 AddElement(pPlrListBox = new C4GUI::ListBox(rcPlrList));
487 pPlrListBox->SetToolTip(LoadResStr("IDS_DLGTIP_PLAYERFILES"));
488 pPlrListBox->SetDecoration(true, &C4Startup::Get()->Graphics.sfctBookScroll, true);
489 pPlrListBox->UpdateElementPositions();
490 pPlrListBox->SetSelectionChangeCallbackFn(new C4GUI::CallbackHandler<C4StartupPlrSelDlg>(this, &C4StartupPlrSelDlg::OnSelChange));
491 pPlrListBox->SetSelectionDblClickFn(new C4GUI::CallbackHandler<C4StartupPlrSelDlg>(this, &C4StartupPlrSelDlg::OnSelDblClick));
492 AddElement(pSelectionInfo = new C4GUI::TextWindow(rcInfoWindow,0,0,0,100,4096," ",false,nullptr,0,true));
493 pSelectionInfo->SetDecoration(true, true, &C4Startup::Get()->Graphics.sfctBookScroll, true);
494 pSelectionInfo->UpdateHeight();
495
496 // bottom line buttons - positioning done in UpdateBottomButtons by UpdatePlayerList
497 C4Rect rcDefault(0,0,10,10);
498 AddElement(btnBack = new C4GUI::CallbackButton<C4StartupPlrSelDlg>(LoadResStr("IDS_BTN_BACK"), rcDefault, &C4StartupPlrSelDlg::OnBackBtn));
499 AddElement(btnNew = new C4GUI::CallbackButton<C4StartupPlrSelDlg>(LoadResStr("IDS_BTN_NEW"), rcDefault, &C4StartupPlrSelDlg::OnNewBtn));
500 btnNew->SetToolTip(LoadResStr("IDS_DLGTIP_NEWPLAYER"));
501 AddElement(btnActivatePlr = new C4GUI::CallbackButton<C4StartupPlrSelDlg>(nullptr, rcDefault, &C4StartupPlrSelDlg::OnActivateBtn));
502 AddElement(btnDelete = new C4GUI::CallbackButton<C4StartupPlrSelDlg>(LoadResStr("IDS_BTN_DELETE"), rcDefault, &C4StartupPlrSelDlg::OnDelBtn));
503 AddElement(btnProperties = new C4GUI::CallbackButton<C4StartupPlrSelDlg>(nullptr, rcDefault, &C4StartupPlrSelDlg::OnPropertyBtn));
504 AddElement(btnCrew = new C4GUI::CallbackButton<C4StartupPlrSelDlg>(LoadResStr("IDS_SELECT_CREW"), rcDefault, &C4StartupPlrSelDlg::OnCrewBtn));
505 btnCrew->SetToolTip(LoadResStr("IDS_DLGTIP_PLAYERCREW"));
506
507 // refill listboxes
508 UpdatePlayerList();
509 // Just safety incase listbox was empty, in which case no selection change callback will have been done:
510 // Update current listbox selection to that of no selected item
511 if (!pPlrListBox->GetFirst()) UpdateSelection();
512
513 // initial focus on player list
514 SetFocus(pPlrListBox, false);
515
516 // key bindings
517 C4CustomKey::CodeList keys;
518 keys.emplace_back(K_BACK);
519 keys.emplace_back(K_LEFT);
520 keys.emplace_back(K_ESCAPE);
521 if (Config.Controls.GamepadGuiControl)
522 {
523 ControllerKeys::Cancel(keys);
524 }
525 pKeyBack = new C4KeyBinding(keys, "StartupPlrSelBack", KEYSCOPE_Gui,
526 new C4GUI::DlgKeyCB<C4StartupPlrSelDlg>(*this, &C4StartupPlrSelDlg::KeyBack), C4CustomKey::PRIO_CtrlOverride);
527 pKeyProperties = new C4KeyBinding(C4KeyCodeEx(K_F2), "StartupPlrSelProp", KEYSCOPE_Gui,
528 new C4GUI::DlgKeyCB<C4StartupPlrSelDlg>(*this, &C4StartupPlrSelDlg::KeyProperties), C4CustomKey::PRIO_CtrlOverride);
529 pKeyCrew = new C4KeyBinding(C4KeyCodeEx(K_RIGHT), "StartupPlrSelCrew", KEYSCOPE_Gui,
530 new C4GUI::ControlKeyDlgCB<C4StartupPlrSelDlg>(pPlrListBox, *this, &C4StartupPlrSelDlg::KeyCrew), C4CustomKey::PRIO_CtrlOverride);
531 pKeyDelete = new C4KeyBinding(C4KeyCodeEx(K_DELETE), "StartupPlrSelDelete", KEYSCOPE_Gui,
532 new C4GUI::DlgKeyCB<C4StartupPlrSelDlg>(*this, &C4StartupPlrSelDlg::KeyDelete), C4CustomKey::PRIO_CtrlOverride);
533 pKeyNew = new C4KeyBinding(C4KeyCodeEx(K_INSERT), "StartupPlrSelNew", KEYSCOPE_Gui,
534 new C4GUI::DlgKeyCB<C4StartupPlrSelDlg>(*this, &C4StartupPlrSelDlg::KeyNew), C4CustomKey::PRIO_CtrlOverride);
535 }
536
~C4StartupPlrSelDlg()537 C4StartupPlrSelDlg::~C4StartupPlrSelDlg()
538 {
539 delete pKeyDelete;
540 delete pKeyCrew;
541 delete pKeyProperties;
542 delete pKeyBack;
543 delete pKeyNew;
544 }
545
AbortRenaming()546 void C4StartupPlrSelDlg::AbortRenaming()
547 {
548 if (pRenameEdit) pRenameEdit->Abort();
549 }
550
DrawElement(C4TargetFacet & cgo)551 void C4StartupPlrSelDlg::DrawElement(C4TargetFacet &cgo)
552 {
553 // draw background
554 typedef C4GUI::FullscreenDialog Base;
555 Base::DrawElement(cgo);
556 }
557
UpdateBottomButtons()558 void C4StartupPlrSelDlg::UpdateBottomButtons()
559 {
560 // bottom line buttons depend on list mode
561 C4GUI::ComponentAligner caBottomButtons(rcBottomButtons,0,0);
562 switch (eMode)
563 {
564 case PSDM_Player:
565 {
566 // update some buttons for player mode
567 btnProperties->SetText(LoadResStr("IDS_BTN_PROPERTIES"));
568 btnProperties->SetToolTip(LoadResStr("IDS_DLGTIP_PLAYERPROPERTIES"));
569 btnNew->SetVisibility(true);
570 btnCrew->SetVisibility(true);
571 btnDelete->SetToolTip(LoadResStr("IDS_DLGTIP_PLAYERDELETE"));
572 btnBack->SetToolTip(LoadResStr("IDS_DLGTIP_BACKMAIN"));
573 btnBack ->SetBounds(caBottomButtons.GetGridCell(0,6,0,1,iBottomButtonWidth,C4GUI_ButtonHgt,true));
574 btnNew ->SetBounds(caBottomButtons.GetGridCell(1,6,0,1,iBottomButtonWidth,C4GUI_ButtonHgt,true));
575 btnActivatePlr->SetBounds(caBottomButtons.GetGridCell(2,6,0,1,iBottomButtonWidth,C4GUI_ButtonHgt,true));
576 btnDelete ->SetBounds(caBottomButtons.GetGridCell(3,6,0,1,iBottomButtonWidth,C4GUI_ButtonHgt,true));
577 btnProperties ->SetBounds(caBottomButtons.GetGridCell(4,6,0,1,iBottomButtonWidth,C4GUI_ButtonHgt,true));
578 btnCrew ->SetBounds(caBottomButtons.GetGridCell(5,6,0,1,iBottomButtonWidth,C4GUI_ButtonHgt,true));
579 }
580 break;
581
582 case PSDM_Crew:
583 {
584 // update some buttons for player mode
585 btnProperties->SetText(LoadResStr("IDS_BTN_RENAME"));
586 btnProperties->SetToolTip(LoadResStr("IDS_DESC_CREWRENAME"));
587 btnNew->SetVisibility(false);
588 btnCrew->SetVisibility(false);
589 btnDelete->SetToolTip(LoadResStr("IDS_MSG_DELETECLONK_DESC"));
590 btnBack->SetToolTip(LoadResStr("IDS_MSG_BACKTOPLAYERDLG"));
591 btnBack ->SetBounds(caBottomButtons.GetGridCell(0,4,0,1,iBottomButtonWidth,C4GUI_ButtonHgt,true));
592 btnActivatePlr->SetBounds(caBottomButtons.GetGridCell(1,4,0,1,iBottomButtonWidth,C4GUI_ButtonHgt,true));
593 btnDelete ->SetBounds(caBottomButtons.GetGridCell(2,4,0,1,iBottomButtonWidth,C4GUI_ButtonHgt,true));
594 btnProperties ->SetBounds(caBottomButtons.GetGridCell(3,4,0,1,iBottomButtonWidth,C4GUI_ButtonHgt,true));
595 }
596 break;
597 };
598 }
599
UpdatePlayerList()600 void C4StartupPlrSelDlg::UpdatePlayerList()
601 {
602 // something has changed!
603 AbortRenaming();
604 // refill pPlrListBox with players in player folder or crew
605 // clear old items
606 C4GUI::Element *pEl;
607 while ((pEl = pPlrListBox->GetFirst())) delete pEl;
608 // update command buttons
609 UpdateBottomButtons();
610 // create new
611 switch (eMode)
612 {
613 case PSDM_Player:
614 {
615 SetTitle(LoadResStrNoAmp("IDS_DLG_PLAYERSELECTION"));
616 // player mode: insert all players
617 const char *szFn;
618 StdStrBuf sSearchPath(Config.General.UserDataPath);
619 PlayerListItem *pFirstActivatedPlrItem=nullptr, *pFirstDeactivatedPlrItem=nullptr, *pPlrItem=nullptr;
620 for (DirectoryIterator i(sSearchPath.getData()); (szFn=*i); i++)
621 {
622 szFn = Config.AtRelativePath(szFn);
623 if (*GetFilename(szFn) == '.') continue; // ignore ".", ".." and private files (".*")
624 if (!WildcardMatch(C4CFN_PlayerFiles, GetFilename(szFn))) continue;
625 bool fIsParticipating = !!SIsModule(Config.General.Participants, szFn, nullptr, false);
626 pPlrItem = new PlayerListItem(this, pPlrListBox, nullptr, fIsParticipating);
627 try
628 {
629 pPlrItem->Load(StdStrBuf(szFn));
630 }
631 catch (ListItem::LoadError & e)
632 {
633 // invalid player: ignore but log error message
634 DebugLog(e.getData());
635 delete pPlrItem;
636 continue;
637 }
638 if (fIsParticipating)
639 {
640 if (!pFirstActivatedPlrItem) pFirstActivatedPlrItem = pPlrItem;
641 }
642 else if (!pFirstDeactivatedPlrItem) pFirstDeactivatedPlrItem = pPlrItem;
643 }
644 // select first element; prefer activated player
645 if (!(pPlrItem = pFirstActivatedPlrItem))
646 pPlrItem = pFirstDeactivatedPlrItem;
647 if (pPlrItem)
648 pPlrListBox->SelectEntry(pPlrItem, false);
649 // re-validate Game.PlayerFilename
650 UpdateActivatedPlayers();
651 break;
652 }
653
654 case PSDM_Crew:
655 {
656 SetTitle(FormatString("%s %s", LoadResStrNoAmp("IDS_CTL_CREW"), CurrPlayer.Core.PrefName).getData());
657 // crew mode: Insert complete crew of player (2do: sort)
658 bool fSucc; char szFn[_MAX_PATH+1];
659 for (fSucc=CurrPlayer.Grp.FindEntry(C4CFN_ObjectInfoFiles, szFn); fSucc; fSucc=CurrPlayer.Grp.FindNextEntry(C4CFN_ObjectInfoFiles, szFn, nullptr, true))
660 {
661 CrewListItem *pCrewItem = new CrewListItem(this, pPlrListBox, CurrPlayer.Core.PrefColorDw);
662 try
663 {
664 pCrewItem->Load(CurrPlayer.Grp, StdStrBuf(szFn));
665 }
666 catch (ListItem::LoadError & e)
667 {
668 // invalid player: ignore but log error message
669 DebugLog(e.getData());
670 delete pCrewItem;
671 continue;
672 }
673 }
674 // resort crew by type and experience
675 ResortCrew();
676 pPlrListBox->SelectFirstEntry(false);
677 break;
678 }
679 }
680 }
681
GetSelection()682 C4StartupPlrSelDlg::ListItem *C4StartupPlrSelDlg::GetSelection()
683 {
684 // get selected item: There's only instances of PlrListItem in this list
685 return static_cast<ListItem *>(pPlrListBox->GetSelectedItem());
686 }
687
SetSelection(ListItem * pNewItem)688 void C4StartupPlrSelDlg::SetSelection(ListItem *pNewItem)
689 {
690 // update selection in listbox
691 pPlrListBox->SelectEntry(pNewItem, false);
692 }
693
UpdateSelection()694 void C4StartupPlrSelDlg::UpdateSelection()
695 {
696 // something has changed!
697 AbortRenaming();
698 // get currently selected player
699 ListItem *pSel = GetSelection();
700 // button text 'activate' if current player is deactivated; 'deactivate' otherwise
701 if (pSel && pSel->IsActivated())
702 {
703 btnActivatePlr->SetText(LoadResStr("IDS_BTN_DEACTIVATE"));
704 btnActivatePlr->SetToolTip(FormatString(LoadResStr("IDS_MSG_NOPARTICIPATE_DESC"), pSel->GetName()).getData());
705 }
706 else
707 {
708 btnActivatePlr->SetText(LoadResStr("IDS_BTN_ACTIVATE"));
709 btnActivatePlr->SetToolTip(FormatString(LoadResStr("IDS_MSG_PARTICIPATE_DESC"), pSel ? pSel->GetName() : "").getData());
710 }
711 // no item selected?
712 if (!pSel)
713 {
714 pSelectionInfo->ClearText(true);
715 // 2do: disable buttons
716 return;
717 }
718 // info text for selection
719 pSel->SetSelectionInfo(pSelectionInfo);
720 }
721
OnItemCheckChange(C4GUI::Element * pCheckBox)722 void C4StartupPlrSelDlg::OnItemCheckChange(C4GUI::Element *pCheckBox)
723 {
724 switch (eMode)
725 {
726 case PSDM_Player:
727 // Deselect all other players
728 for (ListItem* pEl = static_cast<ListItem*>(pPlrListBox->GetFirst()); pEl != nullptr; pEl = pEl->GetNext())
729 if (pCheckBox && pEl != pCheckBox->GetParent())
730 pEl->SetActivated(false);
731 // update Config.General.Participants
732 UpdateActivatedPlayers();
733 break;
734 case PSDM_Crew:
735 // update affected crew item
736 if (pCheckBox) static_cast<CrewListItem *>(pCheckBox->GetParent())->UpdateClonkEnabled();
737 break;
738 }
739 // update player selection text
740 UpdateSelection();
741 }
742
UpdateActivatedPlayers()743 void C4StartupPlrSelDlg::UpdateActivatedPlayers()
744 {
745 assert(eMode == PSDM_Player);
746 // refill Config.General.Participants-list
747 *Config.General.Participants = '\0';
748 for (ListItem *pPlrItem = static_cast<ListItem *>(pPlrListBox->GetFirst()); pPlrItem; pPlrItem = pPlrItem->GetNext())
749 if (pPlrItem->IsActivated())
750 {
751 const char *szAddFilename = pPlrItem->GetFilename().getData();
752 if (std::strlen(Config.General.Participants) + 1 + std::strlen(szAddFilename) < sizeof(Config.General.Participants))
753 SAddModule(Config.General.Participants, szAddFilename);
754 else
755 {
756 pPlrItem->SetActivated(false);
757 GetScreen()->ShowMessage(FormatString(LoadResStr("IDS_ERR_PLAYERSTOOLONG"), pPlrItem->GetName()).getData(), LoadResStr("IDS_ERR_TITLE"), C4GUI::Ico_Error);
758 }
759 }
760 }
761
OnActivateBtn(C4GUI::Control * btn)762 void C4StartupPlrSelDlg::OnActivateBtn(C4GUI::Control *btn)
763 {
764 // toggle activation state of current item
765 // get currently selected player
766 ListItem *pSel = GetSelection();
767 if (!pSel) return;
768 pSel->SetActivated(!pSel->IsActivated());
769 // update stuff
770 OnItemCheckChange(pSel->GetCheckBox());
771 }
772
DoBack()773 void C4StartupPlrSelDlg::DoBack()
774 {
775 switch (eMode)
776 {
777 case PSDM_Player:
778 {
779 // back 2 main
780 C4Startup::Get()->SwitchDialog(C4Startup::SDID_Back);
781 break;
782 }
783
784 case PSDM_Crew:
785 // back 2 player list
786 SetPlayerMode();
787 break;
788 }
789 }
790
OnNewBtn(C4GUI::Control * btn)791 void C4StartupPlrSelDlg::OnNewBtn(C4GUI::Control *btn)
792 {
793 if (eMode != PSDM_Player) return;
794 C4GUI::Dialog *pDlg;
795 GetScreen()->ShowRemoveDlg(pDlg=new C4StartupPlrPropertiesDlg(nullptr, this));
796 pDlg->SetPos(std::min<int32_t>(GetBounds().Wdt/10, GetBounds().Wdt - pDlg->GetBounds().Wdt), std::min<int32_t>(GetBounds().Hgt/4, GetBounds().Hgt - pDlg->GetBounds().Hgt));
797 }
798
CheckPlayerName(const StdStrBuf & Playername,StdStrBuf & Filename,const StdStrBuf * pPrevFilename,bool fWarnEmpty)799 bool C4StartupPlrSelDlg::CheckPlayerName(const StdStrBuf &Playername, StdStrBuf &Filename, const StdStrBuf *pPrevFilename, bool fWarnEmpty)
800 {
801 // must not be empty
802 if (!Playername.getLength())
803 {
804 if (fWarnEmpty) C4GUI::Screen::GetScreenS()->ShowMessage(LoadResStr("IDS_ERR_PLRNAME_EMPTY"), "", C4GUI::Ico_Error);
805 return false;
806 }
807 // generate valid filename
808 Filename.Copy(Playername);
809 // Slashes in Filenames are no good
810 SReplaceChar(Filename.getMData(), '\\', '_');
811 SReplaceChar(Filename.getMData(), '/', '_');
812 SReplaceChar(Filename.getMData(), ':', '_');
813 SReplaceChar(Filename.getMData(), '*', '_');
814 SReplaceChar(Filename.getMData(), '?', '_');
815 SReplaceChar(Filename.getMData(), '"', '_');
816 SReplaceChar(Filename.getMData(), '<', '_');
817 SReplaceChar(Filename.getMData(), '>', '_');
818 SReplaceChar(Filename.getMData(), '|', '_');
819 if (*Filename.getData() == '.') *Filename.getMData() = '_';
820 Filename.Append(".ocp");
821 StdStrBuf Path(Config.General.UserDataPath); // start at local path
822
823 Path.Append(Filename);
824 // validity check: Must not exist yet if renamed
825 if (!pPrevFilename || !ItemIdentical(Path.getData(), Config.AtUserDataPath(pPrevFilename->getData()))) if (ItemExists(Path.getData()))
826 {
827 C4GUI::Screen::GetScreenS()->ShowMessage(FormatString(LoadResStr("IDS_ERR_PLRNAME_TAKEN"),
828 Playername.getData()).getData(), "", C4GUI::Ico_Error);
829 return false;
830 }
831
832 return true;
833 }
834
OnCrewBtn(C4GUI::Control * btn)835 void C4StartupPlrSelDlg::OnCrewBtn(C4GUI::Control *btn)
836 {
837 // display crew for activated player
838 if (eMode != PSDM_Player) return;
839 PlayerListItem *pSel = static_cast<PlayerListItem *>(GetSelection());
840 if (!pSel) return;
841 SetCrewMode(pSel);
842 }
843
SetPlayerMode()844 void C4StartupPlrSelDlg::SetPlayerMode()
845 {
846 // change view to listing players
847 C4GUI::GUISound("UI::Close");
848 StdStrBuf LastPlrFilename;
849 LastPlrFilename.Copy(static_cast<const StdStrBuf &>(CurrPlayer.Grp.GetFullName()));
850 CurrPlayer.Grp.Close();
851 eMode = PSDM_Player;
852 UpdatePlayerList();
853 SelectItem(LastPlrFilename, false);
854 UpdateSelection();
855 }
856
SetCrewMode(PlayerListItem * pSel)857 void C4StartupPlrSelDlg::SetCrewMode(PlayerListItem *pSel)
858 {
859 // change view to listing crew of a player
860 CurrPlayer.Core = pSel->GetCore();
861
862 StdStrBuf Path(Config.General.UserDataPath); // start at local path
863 // Path.Append(Config.General.PlayerPath);
864 Path.Append(pSel->GetFilename());
865
866 if (!CurrPlayer.Grp.Open(Path.getData())) return;
867 if (!CurrPlayer.Grp.FindEntry(C4CFN_ObjectInfoFiles))
868 {
869 StdCopyStrBuf strCrew(FormatString("%s %s", LoadResStrNoAmp("IDS_CTL_CREW"), CurrPlayer.Core.PrefName));
870 // player has no crew!
871 GetScreen()->ShowMessage(FormatString(LoadResStr("IDS_ERR_PLRNOCREW"),
872 CurrPlayer.Core.PrefName).getData(),
873 strCrew.getData(), C4GUI::Ico_Player);
874 return;
875 }
876 C4GUI::GUISound("UI::Open");
877 eMode = PSDM_Crew;
878 UpdatePlayerList();
879 UpdateSelection();
880 }
881
OnDelBtn(C4GUI::Control * btn)882 void C4StartupPlrSelDlg::OnDelBtn(C4GUI::Control *btn)
883 {
884 // something has changed!
885 AbortRenaming();
886 // delete selected player
887 ListItem *pSel = GetSelection();
888 if (!pSel) return;
889 StdStrBuf sWarning; sWarning.Take(pSel->GetDelWarning());
890 GetScreen()->ShowRemoveDlg(new C4GUI::ConfirmationDialog(sWarning.getData(), LoadResStr("IDS_BTN_DELETE"),
891 new C4GUI::CallbackHandlerExPar<C4StartupPlrSelDlg, ListItem *>(this, &C4StartupPlrSelDlg::OnDelBtnConfirm, pSel), C4GUI::MessageDialog::btnYesNo));
892 }
893
OnDelBtnConfirm(ListItem * pSel)894 void C4StartupPlrSelDlg::OnDelBtnConfirm(ListItem *pSel)
895 {
896 StdStrBuf Path;
897
898 switch (eMode)
899 {
900 case PSDM_Player:
901 Path.Append(Config.General.UserDataPath); // start at local path
902 Path.Append(pSel->GetFilename());
903 if (!C4Group_DeleteItem(Path.getData()))
904 {
905 StdStrBuf sMsg; sMsg.Copy(LoadResStr("IDS_FAIL_DELETE"));
906 GetScreen()->ShowMessage(sMsg.getData(), LoadResStr("IDS_DLG_CLEAR"), C4GUI::Ico_Error);
907 }
908 break;
909
910 case PSDM_Crew:
911 if (!CurrPlayer.Grp.DeleteEntry(pSel->GetFilename().getData()))
912 {
913 StdStrBuf sMsg; sMsg.Copy(LoadResStr("IDS_FAIL_DELETE"));
914 GetScreen()->ShowMessage(sMsg.getData(), LoadResStr("IDS_DLG_CLEAR"), C4GUI::Ico_Error);
915 }
916 break;
917 }
918 // update buttons 'n stuff
919 UpdatePlayerList();
920 }
921
SelectItem(const StdStrBuf & Filename,bool fActivate)922 void C4StartupPlrSelDlg::SelectItem(const StdStrBuf &Filename, bool fActivate)
923 {
924 // find item
925 for (ListItem *pPlrItem = static_cast<ListItem *>(pPlrListBox->GetFirst()); pPlrItem; pPlrItem = pPlrItem->GetNext())
926 if (ItemIdentical(pPlrItem->GetFilename().getData(), Filename.getData()))
927 {
928 // select it
929 pPlrListBox->SelectEntry(pPlrItem, false);
930 // activate it
931 if (fActivate)
932 {
933 pPlrItem->SetActivated(true);
934 // player activation updates
935 OnItemCheckChange(pPlrItem->GetCheckBox());
936 }
937 // max one
938 return;
939 }
940 }
941
OnPropertyBtn(C4GUI::Control * btn)942 void C4StartupPlrSelDlg::OnPropertyBtn(C4GUI::Control *btn)
943 {
944 // something has changed!
945 AbortRenaming();
946 switch (eMode)
947 {
948 case PSDM_Player:
949 {
950 // show property dialog for selected player
951 PlayerListItem *pSel = static_cast<PlayerListItem *>(GetSelection());
952 if (!pSel) return;
953 C4GUI::Dialog *pDlg;
954 GetScreen()->ShowRemoveDlg(pDlg=new C4StartupPlrPropertiesDlg(pSel, this));
955 pDlg->SetPos(std::min<int32_t>(GetBounds().Wdt/10, GetBounds().Wdt - pDlg->GetBounds().Wdt),
956 (GetBounds().Hgt - pDlg->GetBounds().Hgt) / 2);
957 }
958 break;
959
960 case PSDM_Crew:
961 // rename crew
962 CrewListItem *pSel = static_cast<CrewListItem *>(GetSelection());
963 if (!pSel) return;
964 pSel->CrewRename();
965 break;
966 }
967 }
968
969
970 /* -- Crew sorting -- */
971
972 struct C4StartupPlrSelDlg_CrewSortDataEntry
973 {
974 int32_t iMaxExp;
975 C4ID idType;
976
C4StartupPlrSelDlg_CrewSortDataEntryC4StartupPlrSelDlg_CrewSortDataEntry977 C4StartupPlrSelDlg_CrewSortDataEntry(int32_t iMaxExp, C4ID idType) : iMaxExp(iMaxExp), idType(idType) {}
978 };
979
980 class C4StartupPlrSelDlg_CrewSortDataMatchType
981 {
982 C4ID idType;
983
984 public:
C4StartupPlrSelDlg_CrewSortDataMatchType(C4ID idType)985 C4StartupPlrSelDlg_CrewSortDataMatchType(C4ID idType) : idType(idType) {}
operator ()(C4StartupPlrSelDlg_CrewSortDataEntry Check)986 bool operator()(C4StartupPlrSelDlg_CrewSortDataEntry Check) { return Check.idType == idType; }
987 };
988
989 typedef std::vector<C4StartupPlrSelDlg_CrewSortDataEntry> C4StartupPlrSelDlg_CrewSortData;
990
CrewSortFunc(const C4GUI::Element * pEl1,const C4GUI::Element * pEl2,void * par)991 int32_t C4StartupPlrSelDlg::CrewSortFunc(const C4GUI::Element *pEl1, const C4GUI::Element *pEl2, void *par)
992 {
993 const auto *pItem1 = static_cast<const CrewListItem *>(pEl1);
994 const auto *pItem2 = static_cast<const CrewListItem *>(pEl2);
995 C4StartupPlrSelDlg_CrewSortData &rSortData = *static_cast<C4StartupPlrSelDlg_CrewSortData *>(par);
996 C4StartupPlrSelDlg_CrewSortData::iterator i = std::find_if(rSortData.begin(), rSortData.end(), C4StartupPlrSelDlg_CrewSortDataMatchType(pItem1->GetCore().id)),
997 j = std::find_if(rSortData.begin(), rSortData.end(), C4StartupPlrSelDlg_CrewSortDataMatchType(pItem2->GetCore().id));
998 // primary sort: By Clonk type, where high exp Clonk types are sorted atop low exp Clonk types
999 if (i != j)
1000 {
1001 if (i == rSortData.end()) return -1; else if (j == rSortData.end()) return +1; // can't really happen
1002 return (*i).iMaxExp - (*j).iMaxExp;
1003 }
1004 // secondary: By experience
1005 return pItem1->GetCore().Experience - pItem2->GetCore().Experience;
1006 }
1007
ResortCrew()1008 void C4StartupPlrSelDlg::ResortCrew()
1009 {
1010 assert(eMode == PSDM_Crew);
1011 // create a list of Clonk types and their respective maximum experience
1012 C4StartupPlrSelDlg_CrewSortData SortData;
1013 for (CrewListItem *pCrewItem = static_cast<CrewListItem *>(pPlrListBox->GetFirst()); pCrewItem; pCrewItem = pCrewItem->GetNext())
1014 {
1015 C4StartupPlrSelDlg_CrewSortData::iterator i = std::find_if(SortData.begin(), SortData.end(), C4StartupPlrSelDlg_CrewSortDataMatchType(pCrewItem->GetCore().id));
1016 if (i == SortData.end())
1017 SortData.emplace_back(pCrewItem->GetCore().Experience, pCrewItem->GetCore().id);
1018 else
1019 (*i).iMaxExp = std::max<int32_t>((*i).iMaxExp, pCrewItem->GetCore().Experience);
1020 }
1021 pPlrListBox->SortElements(&CrewSortFunc, &SortData);
1022 }
1023
1024 // ------------------------------------------------
1025 // --- Player color HSV chooser
1026 class C4StartupPlrColorPickerDlg : public C4GUI::Dialog
1027 {
1028 public:
1029 C4StartupPlrColorPickerDlg(C4PlayerInfoCore *plrcore);
1030
1031 protected:
1032 // Event handler
1033 void OnClosed(bool commit) override;
1034
1035 private:
1036 class Picker : public C4GUI::Control
1037 {
1038 public:
1039 Picker(const C4Rect &bounds);
1040
1041 // Set/retrieve current color value
1042 void SetColor(uint32_t rgb);
1043 uint32_t GetColor() const;
1044
1045 protected:
1046 // Event handlers, overridden from C4GUI::Control
1047 void DrawElement(C4TargetFacet &cgo) override;
1048 void MouseInput(C4GUI::CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam) override;
1049 void DoDragging(C4GUI::CMouse &rMouse, int32_t iX, int32_t iY, DWORD dwKeyParam) override;
1050
1051 private:
1052 static const unsigned int HSPickerCursorSize = 5;
1053 static const unsigned int VPickerCursorSize = 7;
1054 C4FacetSurface hsFacet, vFacet; // chooser backgrounds
1055 C4Rect hsPickerRect, vPickerRect;
1056 C4GUI::Picture *flagPreview, *crewPreview;
1057 uint32_t hsv; // current color
1058 enum {
1059 PS_Idle, // user isn't dragging anything
1060 PS_IdleDragging, // user started the drag on empty space
1061 PS_DragHS, // user started the drag over the HS picker
1062 PS_DragV // user started the drag over the V picker
1063 } state;
1064
1065 bool HandleMouseDown(int32_t x, int32_t y);
1066 void UpdateVFacet(uint32_t h, uint32_t s);
1067 void UpdatePreview();
1068 };
1069
1070 C4PlayerInfoCore *plrcore;
1071 Picker *picker;
1072
HSV2RGB(uint32_t hsv)1073 static uint32_t HSV2RGB(uint32_t hsv)
1074 {
1075 float h = GetRedValue(hsv) / 255.f * 6.f;
1076 float s = GetGreenValue(hsv) / 255.f;
1077 float v = GetBlueValue(hsv) / 255.f;
1078
1079 float chroma = s * v;
1080 float x = chroma * (1.f - std::abs(std::fmod(h, 2.f) - 1.f));
1081
1082 float r = 0;
1083 float g = 0;
1084 float b = 0;
1085
1086 switch (static_cast<int>(h))
1087 {
1088 case 0: case 6:
1089 r = chroma; g = x; break;
1090 case 1:
1091 r = x; g = chroma; break;
1092 case 2:
1093 g = chroma; b = x; break;
1094 case 3:
1095 g = x; b = chroma; break;
1096 case 4:
1097 b = chroma; r = x; break;
1098 case 5:
1099 b = x; r = chroma; break;
1100 }
1101 r += v-chroma;
1102 g += v-chroma;
1103 b += v-chroma;
1104
1105 return RGBA(r * 255.f, g * 255.f, b * 255.f, hsv >> 24);
1106 }
RGB2HSV(uint32_t rgb)1107 static uint32_t RGB2HSV(uint32_t rgb)
1108 {
1109 float r = GetRedValue(rgb) / 255.f;
1110 float g = GetGreenValue(rgb) / 255.f;
1111 float b = GetBlueValue(rgb) / 255.f;
1112
1113 float min = std::min(r, std::min(g, b));
1114 float max = std::max(r, std::max(g, b));
1115
1116 float chroma = max - min;
1117
1118 float hue = 0;
1119 if (r == max)
1120 hue = std::fmod((g-b) / chroma, 6.f);
1121 else if (g == max)
1122 hue = (b-r) / chroma + 2.f;
1123 else
1124 hue = (r-g) / chroma + 4.f;
1125
1126 float h = hue / 6.f;
1127 float s = max == 0 ? 0.f : chroma / max;
1128 float v = max;
1129
1130 return RGBA(h * 255.f, s * 255.f, v * 255.f, rgb >> 24);
1131 }
1132 };
1133
C4StartupPlrColorPickerDlg(C4PlayerInfoCore * plrcore)1134 C4StartupPlrColorPickerDlg::C4StartupPlrColorPickerDlg(C4PlayerInfoCore *plrcore)
1135 : Dialog(400, 296 + C4GUI_ButtonAreaHgt, LoadResStr("IDS_DLG_PLAYERCOLORSELECTION"), false), plrcore(plrcore)
1136 {
1137 C4GUI::ComponentAligner caMain(GetClientRect(), 0, 1, true);
1138
1139 picker = new Picker(caMain.GetFromTop(280));
1140 picker->SetColor(plrcore->PrefColorDw);
1141 AddElement(picker);
1142
1143 // buttons
1144 C4GUI::ComponentAligner caButtonArea(caMain.GetFromBottom(C4GUI_ButtonAreaHgt), 0, 0);
1145 caButtonArea = C4GUI::ComponentAligner(caButtonArea.GetCentered(2*128 + 4*8, C4GUI_ButtonAreaHgt), 8, 8);
1146 C4GUI::Button *cancelButton = new C4GUI::CancelButton(caButtonArea.GetFromRight(128));
1147 C4GUI::Button *okButton = new C4GUI::OKButton(caButtonArea.GetFromRight(128));
1148 AddElement(okButton);
1149 AddElement(cancelButton);
1150 }
1151
OnClosed(bool commit)1152 void C4StartupPlrColorPickerDlg::OnClosed(bool commit)
1153 {
1154 // Write chosen color back to player core
1155 if (commit)
1156 plrcore->PrefColorDw = picker->GetColor();
1157 }
1158
Picker(const C4Rect & bounds)1159 C4StartupPlrColorPickerDlg::Picker::Picker(const C4Rect &bounds)
1160 : Control(bounds), state(PS_Idle)
1161 {
1162 C4GUI::ComponentAligner caMain(bounds, 8, 8, true);
1163 caMain.ExpandBottom(-(caMain.GetInnerHeight() - 256));
1164 hsPickerRect = caMain.GetFromLeft(256);
1165 vPickerRect = caMain.GetFromLeft(16 + VPickerCursorSize);
1166 vPickerRect.Hgt = 256 - PlayerColorValueLowBound;
1167
1168 C4Facet &flagPreviewPic = ::GraphicsResource.fctFlagClr;
1169 int preview_width = std::min<int>(flagPreviewPic.Wdt, caMain.GetInnerWidth());
1170 flagPreview = new C4GUI::Picture(caMain.GetFromTop(flagPreviewPic.GetHeightByWidth(preview_width), preview_width), true);
1171 flagPreview->SetFacet(flagPreviewPic);
1172 AddElement(flagPreview);
1173
1174 C4Facet &crewPreviewPic = ::GraphicsResource.fctCrewClr;
1175 preview_width = std::min<int>(crewPreviewPic.Wdt, caMain.GetInnerWidth());
1176 crewPreview = new C4GUI::Picture(caMain.GetFromTop(crewPreviewPic.GetHeightByWidth(preview_width), preview_width), true);
1177 crewPreview->SetFacet(crewPreviewPic);
1178 AddElement(crewPreview);
1179
1180 // Pre-draw the H+S chooser background, it never changes anyway
1181 hsFacet.Create(hsPickerRect.Wdt, hsPickerRect.Hgt);
1182 hsFacet.Surface->Lock();
1183 for (int y = 0; y < hsFacet.Hgt; ++y)
1184 for (int x = 0; x < hsFacet.Wdt; ++x)
1185 hsFacet.Surface->SetPixDw(x, y, HSV2RGB(C4RGB(x, 255-y, 255)));
1186 hsFacet.Surface->Unlock();
1187
1188 vFacet.Create(vPickerRect.Wdt - VPickerCursorSize, vPickerRect.Hgt);
1189 UpdateVFacet(255, 255);
1190 }
1191
UpdateVFacet(uint32_t h,uint32_t s)1192 void C4StartupPlrColorPickerDlg::Picker::UpdateVFacet(uint32_t h, uint32_t s)
1193 {
1194 // Draw the V chooser background according to current H+S values
1195 vFacet.Surface->Lock();
1196 for (int y = 0; y < vPickerRect.Hgt; ++y)
1197 for (int x = 0; x < vFacet.Wdt; ++x)
1198 vFacet.Surface->SetPixDw(x, y, HSV2RGB(C4RGB(h, s, 255-y)));
1199 vFacet.Surface->Unlock();
1200 }
1201
UpdatePreview()1202 void C4StartupPlrColorPickerDlg::Picker::UpdatePreview()
1203 {
1204 flagPreview->SetDrawColor(HSV2RGB(hsv));
1205 crewPreview->SetDrawColor(HSV2RGB(hsv));
1206 }
1207
SetColor(uint32_t rgb)1208 void C4StartupPlrColorPickerDlg::Picker::SetColor(uint32_t rgb)
1209 {
1210 hsv = RGB2HSV(rgb);
1211 UpdateVFacet(GetRedValue(hsv), GetGreenValue(hsv));
1212 UpdatePreview();
1213 }
1214
GetColor() const1215 uint32_t C4StartupPlrColorPickerDlg::Picker::GetColor() const
1216 {
1217 return HSV2RGB(hsv);
1218 }
1219
DrawElement(C4TargetFacet & cgo)1220 void C4StartupPlrColorPickerDlg::Picker::DrawElement(C4TargetFacet &cgo)
1221 {
1222 // H+S chooser background
1223 C4Facet cgoPicker(cgo.Surface, cgo.TargetX + hsPickerRect.x, cgo.TargetY + hsPickerRect.y, hsPickerRect.Wdt, hsPickerRect.Hgt);
1224 hsFacet.Draw(cgoPicker.Surface, cgoPicker.X, cgoPicker.Y);
1225 // H+S cursor
1226 cgoPicker.Wdt = cgoPicker.Hgt = HSPickerCursorSize;
1227 cgoPicker.X += GetRedValue(hsv) - cgoPicker.Wdt / 2;
1228 cgoPicker.Y += 255 - GetGreenValue(hsv) - cgoPicker.Hgt / 2;
1229 pDraw->DrawLineDw(cgoPicker.Surface, cgoPicker.X, cgoPicker.Y, cgoPicker.X + cgoPicker.Wdt, cgoPicker.Y + cgoPicker.Hgt, C4RGB(0, 0, 0));
1230 pDraw->DrawLineDw(cgoPicker.Surface, cgoPicker.X + cgoPicker.Wdt, cgoPicker.Y, cgoPicker.X, cgoPicker.Y + cgoPicker.Hgt, C4RGB(0, 0, 0));
1231
1232 // V chooser background
1233 cgoPicker.Set(cgo.Surface, cgo.TargetX + vPickerRect.x + VPickerCursorSize, cgo.TargetY + vPickerRect.y, vPickerRect.Wdt - VPickerCursorSize, vPickerRect.Hgt);
1234 vFacet.Draw(cgoPicker.Surface, cgoPicker.X, cgoPicker.Y);
1235 // V cursor
1236 cgoPicker.Wdt = cgoPicker.Hgt = VPickerCursorSize;
1237 cgoPicker.X -= cgoPicker.Wdt / 2 + 1;
1238 cgoPicker.Y += 255 - GetBlueValue(hsv) - cgoPicker.Hgt / 2;
1239 for (int i = 0; i < cgoPicker.Hgt / 2 + 1; ++i)
1240 pDraw->DrawLineDw(cgoPicker.Surface, cgoPicker.X + i, cgoPicker.Y + i, cgoPicker.X + i, cgoPicker.Y + cgoPicker.Hgt - i, C4RGB(255, 255, 255));
1241 }
1242
HandleMouseDown(int32_t x,int32_t y)1243 bool C4StartupPlrColorPickerDlg::Picker::HandleMouseDown(int32_t x, int32_t y)
1244 {
1245 if (state == PS_IdleDragging)
1246 {
1247 // User is dragging something that is neither of the pickers. Ignore.
1248 return false;
1249 }
1250 // Check if a drag starts or was originally started over a picker
1251 else if (state == PS_DragHS || (state == PS_Idle && hsPickerRect.Contains(x, y)))
1252 {
1253 int h = Clamp(x - hsPickerRect.x, 0, hsPickerRect.Wdt - 1);
1254 assert(Inside(h, 0, 255));
1255 int s = 255 - Clamp(y - hsPickerRect.y, 0, hsPickerRect.Hgt - 1);
1256 assert(Inside(s, 0, 255));
1257 hsv = C4RGB(h, s, GetBlueValue(hsv));
1258 UpdateVFacet(h, s);
1259 UpdatePreview();
1260 state = PS_DragHS;
1261 return true;
1262 }
1263 else if (state == PS_DragV || (state == PS_Idle && vPickerRect.Contains(x, y)))
1264 {
1265 int v = 255 - Clamp(y - vPickerRect.y, 0, vPickerRect.Hgt - 1);
1266 assert(Inside(v, 0, 255));
1267 hsv = (hsv & 0xFFFFFF00) | v;
1268 UpdatePreview();
1269 state = PS_DragV;
1270 return true;
1271 }
1272 else
1273 {
1274 // Drag started outside of all picker areas; ignore movement until user releases mouse button.
1275 state = PS_IdleDragging;
1276 return false;
1277 }
1278 }
1279
MouseInput(C4GUI::CMouse & rMouse,int32_t iButton,int32_t iX,int32_t iY,DWORD dwKeyParam)1280 void C4StartupPlrColorPickerDlg::Picker::MouseInput(C4GUI::CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam)
1281 {
1282 Control::MouseInput(rMouse, iButton, iX, iY, dwKeyParam);
1283
1284 if (!rMouse.IsLDown()) state = PS_Idle;
1285 if (rMouse.pDragElement) return;
1286 if (rMouse.IsLDown())
1287 {
1288 if (HandleMouseDown(iX, iY))
1289 {
1290 rMouse.pDragElement = this;
1291 C4GUI::GUISound("UI::Select");
1292 }
1293 else
1294 {
1295 rMouse.pDragElement = nullptr;
1296 }
1297 }
1298 }
1299
DoDragging(C4GUI::CMouse & rMouse,int32_t iX,int32_t iY,DWORD dwKeyParam)1300 void C4StartupPlrColorPickerDlg::Picker::DoDragging(C4GUI::CMouse &rMouse, int32_t iX, int32_t iY, DWORD dwKeyParam)
1301 {
1302 HandleMouseDown(iX, iY);
1303 }
1304
1305 /* ---- Player property dlg ---- */
1306
C4StartupPlrPropertiesDlg(C4StartupPlrSelDlg::PlayerListItem * pForPlayer,C4StartupPlrSelDlg * pParentDlg)1307 C4StartupPlrPropertiesDlg::C4StartupPlrPropertiesDlg(C4StartupPlrSelDlg::PlayerListItem * pForPlayer, C4StartupPlrSelDlg *pParentDlg)
1308 : Dialog(C4Startup::Get()->Graphics.fctPlrPropBG.Wdt, C4Startup::Get()->Graphics.fctPlrPropBG.Hgt, "", false), pMainDlg(pParentDlg), pForPlayer(pForPlayer), fClearBigIcon(false)
1309 {
1310 if (pForPlayer)
1311 {
1312 // edit existing player
1313 C4P = pForPlayer->GetCore();
1314 }
1315 else
1316 {
1317 // create new player: Use default C4P values, with a few exceptions
1318 // FIXME: Use Player, not Clonkranks
1319 C4P.Default(&::DefaultRanks);
1320 // Set name, color, comment
1321 SCopy(LoadResStr("IDS_PLR_NEWCOMMENT"), C4P.Comment, C4MaxComment);
1322 C4P.PrefColor = UnsyncedRandom(8);
1323 C4P.PrefColorDw = C4P.GetPrefColorValue(C4P.PrefColor);
1324 C4P.OldPrefControlStyle = 1;
1325 C4P.OldPrefAutoContextMenu = 1;
1326 C4P.OldPrefControl = 0;
1327 }
1328 const int32_t BetweenElementDist = 2;
1329 // use black fonts here
1330 CStdFont *pUseFont = &C4Startup::Get()->Graphics.BookFont;
1331 CStdFont *pSmallFont = &C4Startup::Get()->Graphics.BookSmallFont;
1332 // get positions
1333 UpdateSize();
1334 C4GUI::ComponentAligner caMain(GetClientRect(), 0, 1, true);
1335 C4GUI::ComponentAligner caButtonArea(caMain.GetFromBottom(C4GUI_ButtonAreaHgt), 0,0);
1336 // dlg title
1337 const char *szTitle;
1338 if (pForPlayer)
1339 {
1340 szTitle = LoadResStr("IDS_DLG_PLAYER2");
1341 }
1342 else
1343 {
1344 szTitle = LoadResStr("IDS_PLR_NEWPLAYER");
1345 }
1346 C4GUI::Label *pLbl = new C4GUI::Label(szTitle, caMain.GetFromTop(pUseFont->GetLineHeight()), ALeft, C4StartupFontClr, pUseFont, false);
1347 AddElement(pLbl);
1348 caMain.ExpandTop(-BetweenElementDist);
1349 // place name label
1350 AddElement(new C4GUI::Label(LoadResStr("IDS_CTL_NAME2"), caMain.GetFromTop(pSmallFont->GetLineHeight()), ALeft, C4StartupFontClr, pSmallFont, false));
1351 // place name edit
1352 pNameEdit = new C4GUI::Edit(caMain.GetFromTop(C4GUI::Edit::GetCustomEditHeight(pUseFont)));
1353 pNameEdit->SetFont(pUseFont);
1354 pNameEdit->SetColors(C4StartupEditBGColor, C4StartupFontClr, C4StartupEditBorderColor);
1355 pNameEdit->InsertText(C4P.PrefName, false);
1356 pNameEdit->SetMaxText(C4MaxName);
1357 AddElement(pNameEdit);
1358 SetFocus(pNameEdit, false);
1359 caMain.ExpandTop(-BetweenElementDist);
1360
1361 int32_t iControlPicSize = C4GUI::ArrowButton::GetDefaultHeight(); // GetGridCell(0,3,0,1,-1,-1,false,2)
1362 int32_t label_hgt = pSmallFont->GetLineHeight();
1363
1364 // place color label
1365 C4GUI::ComponentAligner caColorArea(caMain.GetFromTop(iControlPicSize + BetweenElementDist + label_hgt), 2, 0);
1366 C4GUI::ComponentAligner caPictureArea(caColorArea.GetFromRight(iControlPicSize, iControlPicSize + BetweenElementDist + label_hgt), 2,0);
1367 caColorArea.ExpandLeft(2);
1368 AddElement(new C4GUI::Label(FormatString("%s:", LoadResStr("IDS_CTL_COLOR")).getData(), caColorArea.GetFromTop(label_hgt), ALeft, C4StartupFontClr, pSmallFont, false));
1369 caColorArea.ExpandTop(-BetweenElementDist);
1370 // place picture label
1371 AddElement(new C4GUI::Label(LoadResStr("IDS_CTL_PICTURE"), caPictureArea.GetFromTop(label_hgt), ALeft, C4StartupFontClr, pSmallFont, false));
1372 caPictureArea.ExpandTop(-BetweenElementDist);
1373 // place color controls
1374 C4GUI::Button *pBtn; const char *szTip;
1375 szTip = LoadResStr("IDS_DLGTIP_PLAYERCOLORS");
1376 AddElement(pBtn = new C4GUI::CallbackButton<C4StartupPlrPropertiesDlg, C4GUI::ArrowButton>(C4GUI::ArrowButton::Left, caColorArea.GetFromLeft(C4GUI::ArrowButton::GetDefaultWidth()), &C4StartupPlrPropertiesDlg::OnClrChangeLeft));
1377 pBtn->SetToolTip(szTip);
1378 C4Facet &rfctClrPreviewPic = ::GraphicsResource.fctFlagClr;
1379 pClrPreview = new C4GUI::CallbackButton<C4StartupPlrPropertiesDlg, C4GUI::IconButton>(C4GUI::Ico_None, caColorArea.GetFromLeft(rfctClrPreviewPic.GetWidthByHeight(caColorArea.GetHeight())), 'C', &C4StartupPlrPropertiesDlg::OnClrChangeCustom);
1380 pClrPreview->SetFacet(rfctClrPreviewPic);
1381 AddElement(pClrPreview);
1382 AddElement(pBtn = new C4GUI::CallbackButton<C4StartupPlrPropertiesDlg, C4GUI::ArrowButton>(C4GUI::ArrowButton::Right, caColorArea.GetFromLeft(C4GUI::ArrowButton::GetDefaultWidth()), &C4StartupPlrPropertiesDlg::OnClrChangeRight));
1383 pBtn->SetToolTip(szTip);
1384 if (!C4P.PrefColorDw) C4P.PrefColorDw=0xff;
1385 // Place picture controls
1386 AddElement(pPictureBtn = new C4GUI::CallbackButton<C4StartupPlrPropertiesDlg, C4GUI::IconButton>(C4GUI::Ico_Player, caPictureArea.GetAll(), 'P' /* 2do */, &C4StartupPlrPropertiesDlg::OnPictureBtn));
1387 pPictureBtn->SetToolTip(LoadResStr("IDS_DESC_SELECTAPICTUREANDORLOBBYI"));
1388 UpdateBigIcon();
1389 UpdatePlayerColor(true);
1390 caMain.ExpandTop(-BetweenElementDist);
1391 // place control label
1392 C4GUI::ComponentAligner caControlArea(caMain.GetFromTop(iControlPicSize + label_hgt + BetweenElementDist), 0,0, false);
1393 C4GUI::ComponentAligner caSkinArea(caControlArea.GetFromRight(iControlPicSize + label_hgt + BetweenElementDist), 0,0, false);
1394 AddElement(new C4GUI::Label(FormatString("%s:", LoadResStr("IDS_CTL_CONTROL")).getData(), caControlArea.GetFromTop(label_hgt), ALeft, C4StartupFontClr, pSmallFont, false));
1395 caControlArea.ExpandTop(-BetweenElementDist);
1396 // place clonk style label
1397 AddElement(new C4GUI::Label(LoadResStr("IDS_CTL_CLONKSKIN"), caSkinArea.GetFromTop(label_hgt), ALeft, C4StartupFontClr, pSmallFont, false));
1398 caSkinArea.ExpandTop(-BetweenElementDist);
1399 // place control controls
1400 C4GUI::ComponentAligner caControl(caControlArea.GetFromTop(iControlPicSize), 2,0);
1401 szTip = LoadResStr("IDS_DLGTIP_PLAYERCONTROL");
1402 AddElement(pBtn = new C4GUI::CallbackButton<C4StartupPlrPropertiesDlg, C4GUI::ArrowButton>(C4GUI::ArrowButton::Left, caControl.GetFromLeft(C4GUI::ArrowButton::GetDefaultWidth()), &C4StartupPlrPropertiesDlg::OnCtrlChangeLeft));
1403 pBtn->SetToolTip(szTip);
1404 caControl.ExpandBottom(label_hgt); C4Rect ctrl_name_rect = caControl.GetFromBottom(label_hgt);
1405 C4Facet &rfctCtrlPic = ::GraphicsResource.fctKeyboard; // UpdatePlayerControl() will alternatively set fctGamepad
1406 AddElement(pCtrlImg = new C4GUI::Picture(caControl.GetFromLeft(rfctCtrlPic.GetWidthByHeight(caControl.GetHeight())), true));
1407 pCtrlImg->SetToolTip(szTip);
1408 AddElement(pBtn = new C4GUI::CallbackButton<C4StartupPlrPropertiesDlg, C4GUI::ArrowButton>(C4GUI::ArrowButton::Right, caControl.GetFromLeft(C4GUI::ArrowButton::GetDefaultWidth()), &C4StartupPlrPropertiesDlg::OnCtrlChangeRight));
1409 pBtn->SetToolTip(szTip);
1410 caControl.ExpandLeft(-10);
1411 C4P.OldPrefControl = Clamp<int32_t>(C4P.OldPrefControl, 0, C4MaxControlSet-1);
1412 ctrl_name_lbl = new C4GUI::Label("CtrlName", ctrl_name_rect, ALeft, C4StartupFontClr, pSmallFont, false, false, true);
1413 AddElement(ctrl_name_lbl);
1414 UpdatePlayerControl();
1415
1416 C4GUI::ComponentAligner caSkin(caSkinArea.GetFromTop(iControlPicSize), 2,0);
1417 szTip = LoadResStr("IDS_DLGTIP_PLAYERCREWSKIN");
1418 AddElement(pBtn = new C4GUI::CallbackButton<C4StartupPlrPropertiesDlg, C4GUI::ArrowButton>(C4GUI::ArrowButton::Left, caSkin.GetFromLeft(C4GUI::ArrowButton::GetDefaultWidth()), &C4StartupPlrPropertiesDlg::OnSkinChangeLeft));
1419 pBtn->SetToolTip(szTip);
1420 C4Facet rfctSkinPic = ::GraphicsResource.fctClonkSkin.GetPhase(0);
1421 AddElement(pSkinImg = new C4GUI::Picture(caSkin.GetFromLeft(rfctSkinPic.GetWidthByHeight(caSkin.GetHeight())), true));
1422 pSkinImg->SetToolTip(szTip);
1423 pSkinImg->SetFacet(::GraphicsResource.fctClonkSkin.GetPhase(0));
1424 AddElement(pBtn = new C4GUI::CallbackButton<C4StartupPlrPropertiesDlg, C4GUI::ArrowButton>(C4GUI::ArrowButton::Right, caSkin.GetFromLeft(C4GUI::ArrowButton::GetDefaultWidth()), &C4StartupPlrPropertiesDlg::OnSkinChangeRight));
1425 pBtn->SetToolTip(szTip);
1426 caSkin.ExpandLeft(-10);
1427 UpdatePlayerSkin();
1428
1429 caMain.ExpandTop(-BetweenElementDist);
1430 // place buttons
1431 // OK
1432 C4GUI::Button *pBtnOK = new C4GUI::OKIconButton(C4Rect(147-GetMarginLeft(), 295+35-GetMarginTop(), 54, 33), C4GUI::Ico_None);
1433 AddElement(pBtnOK);
1434 // Cancel
1435 C4GUI::Button *pBtnAbort = new C4GUI::CancelIconButton(C4Rect(317-GetMarginLeft(), 16-GetMarginTop(), 21, 21), C4GUI::Ico_None);
1436 AddElement(pBtnAbort);
1437 // when called from player selection screen: input dlg always closed in the end
1438 // otherwise, modal proc will delete
1439 if (pMainDlg) SetDelOnClose();
1440 }
1441
DrawElement(C4TargetFacet & cgo)1442 void C4StartupPlrPropertiesDlg::DrawElement(C4TargetFacet &cgo)
1443 {
1444 C4Startup::Get()->Graphics.fctPlrPropBG.Draw(cgo.Surface, rcBounds.x+cgo.TargetX, rcBounds.y+cgo.TargetY);
1445 }
1446
1447 bool IsColorConflict(DWORD dwClr1, DWORD dwClr2);
1448
UpdatePlayerColor(bool fUpdateSliders)1449 void C4StartupPlrPropertiesDlg::UpdatePlayerColor(bool fUpdateSliders)
1450 {
1451 C4P.PrefColorDw = C4P.PrefColorDw | 0xFF000000; // Ensure full opacity
1452 pClrPreview->SetColor(C4P.PrefColorDw);
1453 pPictureBtn->SetColor(C4P.PrefColorDw);
1454 }
1455
OnClrChangeLeft(C4GUI::Control * pBtn)1456 void C4StartupPlrPropertiesDlg::OnClrChangeLeft(C4GUI::Control *pBtn)
1457 {
1458 // previous standard color in list
1459 C4P.PrefColor = C4P.PrefColor ? C4P.PrefColor - 1 : 11;
1460 C4P.PrefColorDw = C4PlayerInfoCore::GetPrefColorValue(C4P.PrefColor);
1461 UpdatePlayerColor(true);
1462 }
OnClrChangeCustom(C4GUI::Control * pBtn)1463 void C4StartupPlrPropertiesDlg::OnClrChangeCustom(C4GUI::Control *pBtn)
1464 {
1465 GetScreen()->ShowModalDlg(new C4StartupPlrColorPickerDlg(&C4P));
1466 UpdatePlayerColor(true);
1467 }
1468
OnClrChangeRight(C4GUI::Control * pBtn)1469 void C4StartupPlrPropertiesDlg::OnClrChangeRight(C4GUI::Control *pBtn)
1470 {
1471 // next standard color in list
1472 C4P.PrefColor = (C4P.PrefColor + 1) % 12;
1473 C4P.PrefColorDw = C4PlayerInfoCore::GetPrefColorValue(C4P.PrefColor);
1474 UpdatePlayerColor(true);
1475 }
1476
UpdatePlayerControl()1477 void C4StartupPlrPropertiesDlg::UpdatePlayerControl()
1478 {
1479 C4PlayerControlAssignmentSet *control_set = Game.PlayerControlUserAssignmentSets.GetSetByName(C4P.PrefControl.getData());
1480 if (!control_set) control_set = Game.PlayerControlUserAssignmentSets.GetDefaultSet();
1481 // update keyboard image of selected control
1482 C4Facet fctCtrlPic;
1483 if (control_set) fctCtrlPic = control_set->GetPicture();
1484 pCtrlImg->SetFacet(fctCtrlPic);
1485 if (control_set)
1486 ctrl_name_lbl->SetText(control_set->GetGUIName());
1487 else
1488 ctrl_name_lbl->SetText("???");
1489 }
1490
OnCtrlChangeLeft(C4GUI::Control * pBtn)1491 void C4StartupPlrPropertiesDlg::OnCtrlChangeLeft(C4GUI::Control *pBtn)
1492 {
1493 // previous control set in list
1494 C4PlayerControlAssignmentSet *control_set = Game.PlayerControlUserAssignmentSets.GetSetByName(C4P.PrefControl.getData());
1495 int32_t index = Game.PlayerControlUserAssignmentSets.GetSetIndex(control_set);
1496 if (index < 0) index = 0; // defined control set not found - probably an old CR player file
1497 if (!index--) index = Game.PlayerControlUserAssignmentSets.GetSetCount() - 1;
1498 control_set = Game.PlayerControlUserAssignmentSets.GetSetByIndex(index);
1499 if (control_set) C4P.PrefControl = control_set->GetName();
1500 UpdatePlayerControl();
1501 }
1502
OnCtrlChangeRight(C4GUI::Control * pBtn)1503 void C4StartupPlrPropertiesDlg::OnCtrlChangeRight(C4GUI::Control *pBtn)
1504 {
1505 // next control set in list
1506 C4PlayerControlAssignmentSet *control_set = Game.PlayerControlUserAssignmentSets.GetSetByName(C4P.PrefControl.getData());
1507 int32_t index = Game.PlayerControlUserAssignmentSets.GetSetIndex(control_set);
1508 if (index < 0) index = 0; // defined control set not found - probably an old CR player file
1509 if (++index >= int32_t(Game.PlayerControlUserAssignmentSets.GetSetCount())) index = 0;
1510 control_set = Game.PlayerControlUserAssignmentSets.GetSetByIndex(index);
1511 if (control_set) C4P.PrefControl = control_set->GetName();
1512 UpdatePlayerControl();
1513 }
1514
UpdatePlayerSkin()1515 void C4StartupPlrPropertiesDlg::UpdatePlayerSkin()
1516 {
1517 pSkinImg->SetFacet(::GraphicsResource.fctClonkSkin.GetPhase(C4P.PrefClonkSkin));
1518 }
1519
OnSkinChangeLeft(C4GUI::Control * pBtn)1520 void C4StartupPlrPropertiesDlg::OnSkinChangeLeft(C4GUI::Control *pBtn)
1521 {
1522 // previous skin in list
1523 C4P.PrefClonkSkin = C4P.PrefClonkSkin ? C4P.PrefClonkSkin - 1 : 3;
1524 UpdatePlayerSkin();
1525 }
OnSkinChangeRight(C4GUI::Control * pBtn)1526 void C4StartupPlrPropertiesDlg::OnSkinChangeRight(C4GUI::Control *pBtn)
1527 {
1528 // next skin in list
1529 C4P.PrefClonkSkin = (C4P.PrefClonkSkin + 1) % 4;
1530 UpdatePlayerSkin();
1531 }
1532
UserClose(bool fOK)1533 void C4StartupPlrPropertiesDlg::UserClose(bool fOK)
1534 {
1535 // check name validity
1536 if (fOK)
1537 {
1538 StdStrBuf PlrName(pNameEdit->GetText()), Filename;
1539 if (!C4StartupPlrSelDlg::CheckPlayerName(PlrName, Filename, pForPlayer ? &pForPlayer->GetFilename() : nullptr, true)) return;
1540
1541 // Warn that gamepad controls are still unfinished.
1542 C4PlayerControlAssignmentSet *control_set = Game.PlayerControlUserAssignmentSets.GetSetByName(C4P.PrefControl.getData());
1543 if (control_set && control_set->HasGamepad())
1544 {
1545 GetScreen()->ShowMessageModal(
1546 LoadResStr("IDS_DLG_GAMEPADEXPERIMENTAL"),
1547 LoadResStr("IDS_DLG_GAMEPADEXPTITLE"),
1548 C4GUI::MessageDialog::btnOK,
1549 C4GUI::Ico_Gamepad
1550 );
1551 }
1552 }
1553 Close(fOK);
1554 }
1555
OnClosed(bool fOK)1556 void C4StartupPlrPropertiesDlg::OnClosed(bool fOK)
1557 {
1558 if (fOK)
1559 {
1560 // store selected data if desired
1561 StdStrBuf PlrName(pNameEdit->GetText()), Filename;
1562 if (C4StartupPlrSelDlg::CheckPlayerName(PlrName, Filename, pForPlayer ? &pForPlayer->GetFilename() : nullptr, true))
1563 {
1564 SCopy(PlrName.getData(), C4P.PrefName, C4MaxName);
1565 C4Group PlrGroup;
1566 bool fSucc=false;
1567 // existant player: update file
1568 if (pForPlayer)
1569 {
1570 if (!pForPlayer->MoveFilename(Filename.getData()))
1571 GetScreen()->ShowMessage(LoadResStr("IDS_FAIL_RENAME"), "", C4GUI::Ico_Error);
1572 // update bigicon
1573 if (fClearBigIcon || fctNewBigIcon.Surface)
1574 {
1575 C4Group PlrGroup;
1576 if (PlrGroup.Open(Config.AtUserDataPath(Filename.getData())))
1577 {
1578 if (fClearBigIcon || fctNewBigIcon.Surface) PlrGroup.Delete(C4CFN_BigIcon);
1579 if (fctNewBigIcon.Surface) fctNewBigIcon.GetFace().SavePNG(PlrGroup, C4CFN_BigIcon);
1580 if (PlrGroup.Close()) fSucc = true;
1581 if (fClearBigIcon || fctNewBigIcon.Surface) pForPlayer->GrabCustomIcon(fctNewBigIcon);
1582 }
1583 }
1584 else
1585 {
1586 fSucc = true;
1587 }
1588 pForPlayer->UpdateCore(C4P);
1589 // player may have been activated: Make sure any new filename is reflected in participants list
1590 if (pMainDlg) pMainDlg->UpdateActivatedPlayers();
1591 }
1592 else
1593 {
1594 // NewPlayer: Open new player group
1595 if (PlrGroup.Open(Config.AtUserDataPath(Filename.getData()), true))
1596 {
1597 // Do not overwrite (should have been caught earlier anyway)
1598 if (PlrGroup.FindEntry(C4CFN_PlayerInfoCore)) return;
1599 // Save info core
1600 C4P.Save(PlrGroup);
1601 // Add BigIcon
1602 if (fctNewBigIcon.Surface)
1603 {
1604 fctNewBigIcon.GetFace().SavePNG(PlrGroup, C4CFN_BigIcon);
1605 }
1606 // Close group
1607 if (PlrGroup.Close()) fSucc=true;
1608 // update activate button text
1609 if (pMainDlg)
1610 {
1611 pMainDlg->UpdatePlayerList();
1612 pMainDlg->SelectItem(Filename, true);
1613 }
1614 else
1615 {
1616 // no main player selection dialog: This means that this dlg was shown as a creation dialog from the main startup dlg
1617 // Just set the newly created player as current selection
1618 SCopy(Config.AtRelativePath(Filename.getData()), Config.General.Participants, sizeof Config.General.Participants);
1619 }
1620 }
1621 }
1622 if (!fSucc) GetScreen()->ShowErrorMessage(PlrGroup.GetError());
1623 }
1624 }
1625 // Make the dialog go away
1626 Dialog::OnClosed(fOK);
1627 }
1628
SetNewPicture(C4Surface & srcSfc,C4FacetSurface * trgFct,int32_t iMaxSize,bool fColorize)1629 bool C4StartupPlrPropertiesDlg::SetNewPicture(C4Surface &srcSfc, C4FacetSurface *trgFct, int32_t iMaxSize, bool fColorize)
1630 {
1631 if (fColorize)
1632 {
1633 C4Surface srcSfcClr;
1634 if (!srcSfcClr.CreateColorByOwner(&srcSfc)) return false;
1635 return trgFct->CopyFromSfcMaxSize(srcSfcClr, iMaxSize, C4P.PrefColorDw);
1636 }
1637 else
1638 {
1639 return trgFct->CopyFromSfcMaxSize(srcSfc, iMaxSize);
1640 }
1641 }
1642
SetNewPicture(const char * szFromFilename)1643 void C4StartupPlrPropertiesDlg::SetNewPicture(const char *szFromFilename)
1644 {
1645 if (!szFromFilename)
1646 {
1647 // If szFromFilename==nullptr, clear bigicon
1648 fClearBigIcon = true;
1649 fctNewBigIcon.Clear();
1650 }
1651 else
1652 {
1653 // else set new bigicon by loading and scaling if necessary.
1654 C4Surface sfcNewPic;
1655 C4Group SrcGrp;
1656 StdStrBuf sParentPath;
1657 GetParentPath(szFromFilename, &sParentPath);
1658 bool fSucc = false;
1659 if (SrcGrp.Open(sParentPath.getData()))
1660 {
1661 if (sfcNewPic.Load(SrcGrp, GetFilename(szFromFilename), false, false, 0))
1662 {
1663 fSucc = true;
1664 if (!SetNewPicture(sfcNewPic, &fctNewBigIcon, C4MaxBigIconSize, true)) fSucc = false;
1665 }
1666 }
1667 if (!fSucc)
1668 {
1669 // error!
1670 GetScreen()->ShowErrorMessage(FormatString(LoadResStr("IDS_PRC_NOGFXFILE"), szFromFilename, SrcGrp.GetError()).getData());
1671 }
1672 }
1673 // update icon
1674 UpdateBigIcon();
1675 }
1676
OnPictureBtn(C4GUI::Control * pBtn)1677 void C4StartupPlrPropertiesDlg::OnPictureBtn(C4GUI::Control *pBtn)
1678 {
1679 StdStrBuf sNewPic;
1680 if (C4PortraitSelDlg::SelectPortrait(GetScreen(), &sNewPic))
1681 {
1682 SetNewPicture(sNewPic.getData());
1683 }
1684 }
1685
UpdateBigIcon()1686 void C4StartupPlrPropertiesDlg::UpdateBigIcon()
1687 {
1688 // new icon?
1689 bool fHasIcon = false;
1690 if (fctNewBigIcon.Surface)
1691 {
1692 pPictureBtn->SetFacet(fctNewBigIcon);
1693 fHasIcon = true;
1694 }
1695 // old icon in existing player?
1696 else if (!fClearBigIcon && pForPlayer)
1697 {
1698 C4Group PlrGroup;
1699 if (PlrGroup.Open(Config.AtUserDataPath(pForPlayer->GetFilename().getData())))
1700 {
1701 if (PlrGroup.FindEntry(C4CFN_BigIcon))
1702 {
1703 if (fctOldBigIcon.Load(PlrGroup, C4CFN_BigIcon, C4FCT_Full, C4FCT_Full, false, 0))
1704 {
1705 pPictureBtn->SetFacet(fctOldBigIcon);
1706 fHasIcon = true;
1707 }
1708 }
1709 }
1710 }
1711 // no icon: Set default
1712 if (!fHasIcon)
1713 {
1714 pPictureBtn->SetFacet(::GraphicsResource.fctPlayerClr);
1715 }
1716 }
1717