openclonk/src/gui/C4StartupPlrSelDlg.cpp

1717 lines
63 KiB
C++

/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 2005-2009, RedWolf Design GmbH, http://www.clonk.de/
* Copyright (c) 2009-2016, The OpenClonk Team and contributors
*
* Distributed under the terms of the ISC license; see accompanying file
* "COPYING" for details.
*
* "Clonk" is a registered trademark of Matthes Bender, used with permission.
* See accompanying file "TRADEMARK" for details.
*
* To redistribute this file separately, substitute the full license texts
* for the above references.
*/
// Startup screen for non-parameterized engine start: Player selection dialog
// Also contains player creation, editing and crew management
#include "C4Include.h"
#include "gui/C4StartupPlrSelDlg.h"
#include "graphics/C4Draw.h"
#include "graphics/C4GraphicsResource.h"
#include "gui/C4FileSelDlg.h"
#include "gui/C4MouseControl.h"
#include "gui/C4StartupMainDlg.h"
#include "lib/C4Random.h"
#include "lib/StdColors.h"
#include "player/C4RankSystem.h"
// font clrs
const uint32_t ClrPlayerItem = 0xffffffff;
// Arbitrary cut-off value for player color value. This avoids pitch black
// colors which look ugly. Note that this limit is only applied in the UI,
// it's still possible to edit the Player.txt by hand.
const uint32_t PlayerColorValueLowBound = 64;
// ----- C4Utilities
StdStrBuf TimeString(int iSeconds)
{
int iHours = iSeconds / 3600; iSeconds -= 3600*iHours;
int iMinutes = iSeconds / 60; iSeconds -= 60*iMinutes;
return FormatString("%02d:%02d:%02d",iHours,iMinutes,iSeconds);
}
StdStrBuf DateString(int iTime)
{
if (!iTime) return StdStrBuf("", true);
time_t tTime = iTime;
struct tm *pLocalTime;
pLocalTime=localtime(&tTime);
return FormatString( "%02d.%02d.%d %02d:%02d",
pLocalTime->tm_mday,
pLocalTime->tm_mon+1,
pLocalTime->tm_year+1900,
pLocalTime->tm_hour,
pLocalTime->tm_min);
}
// ------------------------------------------------
// --- C4StartupPlrSelDlg::ListItem
C4StartupPlrSelDlg::ListItem::ListItem(C4StartupPlrSelDlg *pForDlg, C4GUI::ListBox *pForListBox, C4GUI::Element *pInsertBeforeElement, bool fActivated)
: Control(C4Rect(0,0,0,0)), pCheck(nullptr), pNameLabel(nullptr), pPlrSelDlg(pForDlg), pIcon(nullptr)
{
CStdFont &rUseFont = GraphicsResource.FontRegular;
// calc height
int32_t iHeight = rUseFont.GetLineHeight() + 2 * IconLabelSpacing;
// create subcomponents
pCheck = new C4GUI::CheckBox(C4Rect(0, 0, iHeight, iHeight), nullptr, fActivated);
pCheck->SetOnChecked(new C4GUI::CallbackHandler<C4StartupPlrSelDlg>(pForDlg, &C4StartupPlrSelDlg::OnItemCheckChange));
pKeyCheck = new C4KeyBinding(C4KeyCodeEx(K_SPACE), "StartupPlrSelTogglePlayerActive", KEYSCOPE_Gui,
new C4GUI::ControlKeyCB<ListItem>(*this, &ListItem::KeyCheck), C4CustomKey::PRIO_Ctrl);
pIcon = new C4GUI::Icon(C4Rect(iHeight + IconLabelSpacing, 0, iHeight, iHeight), C4GUI::Ico_Player);
pNameLabel = new C4GUI::Label("Q", (iHeight + IconLabelSpacing)*2, IconLabelSpacing, ALeft, ClrPlayerItem, &rUseFont, false, false);
pNameLabel->SetAutosize(false);
// calc own bounds - use icon bounds only, because only the height is used when the item is added
SetBounds(pIcon->GetBounds());
// add components
AddElement(pCheck);
AddElement(pIcon); AddElement(pNameLabel);
// add to listbox (will get resized horizontally and moved) - zero indent; no tree structure in this dialog
pForListBox->InsertElement(this, pInsertBeforeElement, 0);
// update name label width to stretch max listbox width
C4Rect rcNameLabelBounds = pNameLabel->GetBounds();
rcNameLabelBounds.Wdt = GetClientRect().Wdt - rcNameLabelBounds.x - IconLabelSpacing;
pNameLabel->SetBounds(rcNameLabelBounds);
// context menu
SetContextHandler(new C4GUI::CBContextHandler<C4StartupPlrSelDlg::ListItem>(this, &C4StartupPlrSelDlg::ListItem::ContextMenu));
}
C4StartupPlrSelDlg::ListItem::~ListItem()
{
delete pKeyCheck;
}
const char *C4StartupPlrSelDlg::ListItem::GetName() const
{
// name is stored in label only
return pNameLabel->GetText();
}
void C4StartupPlrSelDlg::ListItem::SetName(const char *szNewName)
{
// update name in label
pNameLabel->SetText(szNewName);
// tooltip by name, so long names can be read via tooltip
SetToolTip(szNewName);
}
void C4StartupPlrSelDlg::ListItem::GrabIcon(C4FacetSurface &rFromFacet)
{
// take over icon gfx from facet - deletes them from source facet!
if (rFromFacet.Surface)
{
pIcon->GetMFacet().GrabFrom(rFromFacet);
}
else
{
// reset custom icon
// following update-call will reset to default icon
pIcon->GetMFacet().Clear();
}
}
void C4StartupPlrSelDlg::ListItem::SetIcon(C4GUI::Icons icoNew)
{
pIcon->SetIcon(icoNew);
}
void C4StartupPlrSelDlg::ListItem::UpdateOwnPos()
{
// parent for client rect
typedef C4GUI::Window ParentClass;
ParentClass::UpdateOwnPos();
// reposition items
C4GUI::ComponentAligner caBounds(GetContainedClientRect(), IconLabelSpacing, IconLabelSpacing);
// nothing to reposition for now...
}
void C4StartupPlrSelDlg::ListItem::SetFilename(const StdStrBuf &sNewFN)
{
// just set fn - UpdateCore-call will follow later
Filename.Copy(sNewFN);
}
bool C4StartupPlrSelDlg::ListItem::CheckNameHotkey(const char * c)
{
// return whether this item can be selected by entering given char:
// first char of name must match
// FIXME: Unicode
if (!pNameLabel) return false;
const char *szName = pNameLabel->GetText();
return szName && (toupper(*szName) == toupper(c[0]));
}
// ------------------------------------------------
// --- C4StartupPlrSelDlg::PlayerListItem
C4StartupPlrSelDlg::PlayerListItem::PlayerListItem(C4StartupPlrSelDlg *pForDlg, C4GUI::ListBox *pForListBox, C4GUI::Element *pInsertBeforeElement, bool fActivated)
: ListItem(pForDlg, pForListBox, pInsertBeforeElement, fActivated), fHasCustomIcon(false)
{
}
void C4StartupPlrSelDlg::PlayerListItem::Load(const StdStrBuf &rsFilename)
{
int32_t iHeight = GetBounds().Hgt;
// backup filename
SetFilename(rsFilename);
// load player info
C4Group PlrGroup;
if (!PlrGroup.Open(Config.AtUserDataPath(rsFilename.getData())))
throw LoadError(FormatString("Error loading player file from %s: Error opening group: %s", rsFilename.getData(), PlrGroup.GetError()));
if (!Core.Load(PlrGroup))
throw LoadError(FormatString("Error loading player file from %s: Core data invalid or missing (Group: %s)!", rsFilename.getData(), PlrGroup.GetError()));
// load icon
C4FacetSurface fctIcon;
if (PlrGroup.FindEntry(C4CFN_BigIcon) && fctIcon.Load(PlrGroup, C4CFN_BigIcon, C4FCT_Full, C4FCT_Full, false, 0))
fHasCustomIcon = true;
else
{
// no custom icon: create default by player color
fctIcon.Create(iHeight,iHeight);
::GraphicsResource.fctPlayerClr.DrawClr(fctIcon, true, Core.PrefColorDw);
}
GrabIcon(fctIcon);
// done loading
if (!PlrGroup.Close())
throw LoadError(FormatString("Error loading player file from %s: Error closing group: %s", rsFilename.getData(), PlrGroup.GetError()));
// default name
if (!*Core.PrefName) SCopy(GetFilenameOnly(rsFilename.getData()), Core.PrefName, sizeof(Core.PrefName)-1);
SetName(Core.PrefName);
}
C4GUI::ContextMenu *C4StartupPlrSelDlg::PlayerListItem::ContextMenu()
{
// menu operations work on selected item only
pPlrSelDlg->SetSelection(this);
// context menu operations
C4GUI::ContextMenu *pCtx = new C4GUI::ContextMenu();
pCtx->AddItem(LoadResStr("IDS_BTN_PROPERTIES"), LoadResStr("IDS_DLGTIP_PLAYERPROPERTIES"), C4GUI::Ico_None, new C4GUI::CBMenuHandler<C4StartupPlrSelDlg>(pPlrSelDlg, &C4StartupPlrSelDlg::OnPropertyCtx));
pCtx->AddItem(LoadResStr("IDS_BTN_DELETE"), LoadResStr("IDS_DLGTIP_PLAYERDELETE"), C4GUI::Ico_None, new C4GUI::CBMenuHandler<C4StartupPlrSelDlg>(pPlrSelDlg, &C4StartupPlrSelDlg::OnDelCtx));
return pCtx;
}
void C4StartupPlrSelDlg::PlayerListItem::GrabCustomIcon(C4FacetSurface &fctGrabFrom)
{
// set flag
fHasCustomIcon = !!fctGrabFrom.Surface;
// base class grab
GrabIcon(fctGrabFrom);
}
void C4StartupPlrSelDlg::PlayerListItem::UpdateCore(C4PlayerInfoCore & NewCore)
{
C4Group PlrGroup;
if (!PlrGroup.Open(Config.AtUserDataPath(GetFilename().getData()))
|| !NewCore.Save(PlrGroup)
|| !PlrGroup.Close())
{
GetScreen()->ShowMessage(LoadResStr("IDS_FAIL_MODIFY"), "", C4GUI::Ico_Error);
return;
}
Core = NewCore;
SetName(Core.PrefName);
// re-set non-custom icons
if (!fHasCustomIcon)
{
fHasCustomIcon = false;
int32_t iHeight = GetBounds().Hgt;
C4FacetSurface fctIcon; fctIcon.Create(iHeight,iHeight);
::GraphicsResource.fctPlayerClr.DrawClr(fctIcon, true, Core.PrefColorDw);
GrabIcon(fctIcon);
}
// update in selection
C4StartupPlrSelDlg *pDlg = static_cast<C4StartupPlrSelDlg *>(GetDlg());
if (pDlg && pDlg->GetSelection() == this) pDlg->UpdateSelection();
}
void C4StartupPlrSelDlg::PlayerListItem::SetSelectionInfo(C4GUI::TextWindow *pSelectionInfo)
{
// write info text for player
pSelectionInfo->ClearText(false);
pSelectionInfo->AddTextLine(FormatString("%s", Core.PrefName).getData(), &C4Startup::Get()->Graphics.BookFontCapt, ClrPlayerItem, false, false);
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);
if (Core.LastRound.Title[0])
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);
pSelectionInfo->UpdateHeight();
}
StdStrBuf C4StartupPlrSelDlg::PlayerListItem::GetDelWarning()
{
StdStrBuf sWarning;
sWarning.Format(LoadResStr("IDS_MSG_DELETEPLR"), Core.PrefName);
int32_t iPlrTime = Core.TotalPlayingTime;
if (iPlrTime > 60*60*10)
sWarning.Append(FormatString(LoadResStr("IDS_MSG_DELETEPLR_PLAYTIME"),
TimeString(iPlrTime).getData()).getData());
return sWarning;
}
bool C4StartupPlrSelDlg::PlayerListItem::MoveFilename(const char *szToFilename)
{
// anything to do?
if (ItemIdentical(GetFilename().getData(), szToFilename)) return true;
// do it
StdStrBuf PathFrom(Config.General.UserDataPath);
PathFrom.Append(GetFilename());
StdStrBuf PathTo(Config.General.UserDataPath);
PathTo.Append(szToFilename);
if (!MoveItem(PathFrom.getData(), PathTo.getData())) return false;
// reflect change in class
SetFilename(StdStrBuf(szToFilename));
return true;
}
// ------------------------------------------------
// --- C4StartupPlrSelDlg::CrewListItem
C4StartupPlrSelDlg::CrewListItem::CrewListItem(C4StartupPlrSelDlg *pForDlg, C4GUI::ListBox *pForListBox, uint32_t dwPlrClr)
: ListItem(pForDlg, pForListBox, nullptr, false), fLoaded(false), dwPlrClr(dwPlrClr), pParentGrp(nullptr)
{
SetIcon(C4GUI::Ico_Wait);
}
void C4StartupPlrSelDlg::CrewListItem::UpdateClonkEnabled()
{
if (!fLoaded) return;
Core.Participation = pCheck->GetChecked();
// immediate save of changes
RewriteCore();
}
void C4StartupPlrSelDlg::CrewListItem::Load(C4Group &rGrp, const StdStrBuf &rsFilename)
{
// backup filename (doesn't include path)
SetFilename(rsFilename);
// load core
C4Group CrewGroup;
if (!CrewGroup.OpenAsChild(&rGrp, rsFilename.getData()))
throw LoadError(FormatString("Error loading crew file from %s in %s: Error opening group: %s",
rsFilename.getData(), rGrp.GetFullName().getData(), CrewGroup.GetError()));
if (!Core.Load(CrewGroup))
throw LoadError(FormatString("Error loading crew file from %s: Core data invalid or missing (Group: %s)!",
CrewGroup.GetFullName().getData(), CrewGroup.GetError()));
ListItem::SetName(Core.Name);
pCheck->SetChecked(!!Core.Participation);
// load rank as icon
C4FacetSurface fctIcon;
if (fctIcon.Load(CrewGroup, C4CFN_ClonkRank, C4FCT_Full, C4FCT_Full, false, true))
{
GrabIcon(fctIcon);
}
else
{
// no custom icon: create default by rank system
if (C4RankSystem::DrawRankSymbol(&fctIcon, Core.Rank, &::GraphicsResource.fctRank, ::GraphicsResource.iNumRanks, true))
GrabIcon(fctIcon);
}
// backup group loaded from - assumes it stays valid!
pParentGrp = &rGrp;
// load success!
fLoaded=true;
}
C4GUI::ContextMenu *C4StartupPlrSelDlg::CrewListItem::ContextMenu()
{
// menu operations work on selected item only
pPlrSelDlg->SetSelection(this);
// context menu operations
C4GUI::ContextMenu *pCtx = new C4GUI::ContextMenu();
pCtx->AddItem(LoadResStr("IDS_BTN_RENAME"), LoadResStr("IDS_DESC_CREWRENAME"), C4GUI::Ico_None, new C4GUI::CBMenuHandler<C4StartupPlrSelDlg>(pPlrSelDlg, &C4StartupPlrSelDlg::OnPropertyCtx));
pCtx->AddItem(LoadResStr("IDS_BTN_DELETE"), LoadResStr("IDS_MSG_DELETECLONK_DESC"), C4GUI::Ico_None, new C4GUI::CBMenuHandler<C4StartupPlrSelDlg>(pPlrSelDlg, &C4StartupPlrSelDlg::OnDelCtx));
pCtx->AddItem(LoadResStr("IDS_MSG_SETDEATHMESSAGE"), LoadResStr("IDS_MSG_SETTHEMESSAGETHATAPPEARWH"), C4GUI::Ico_None, new C4GUI::CBMenuHandler<C4StartupPlrSelDlg::CrewListItem>(this, &C4StartupPlrSelDlg::CrewListItem::OnDeathMessageCtx));
return pCtx;
}
void C4StartupPlrSelDlg::CrewListItem::OnDeathMessageCtx(C4GUI::Element *el)
{
// Death message dialog
C4GUI::InputDialog *pDlg;
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));
pDlg->SetMaxText(C4MaxDeathMsg);
pDlg->SetInputText(Core.DeathMessage);
}
void C4StartupPlrSelDlg::CrewListItem::OnDeathMessageSet(const StdStrBuf &rsNewMessage)
{
// copy msg
if (!rsNewMessage) *Core.DeathMessage='\0'; else SCopy(rsNewMessage.getData(), Core.DeathMessage, C4MaxDeathMsg);
// save
RewriteCore();
// acoustic feedback
C4GUI::GUISound("UI::Confirmed");
}
void C4StartupPlrSelDlg::CrewListItem::RewriteCore()
{
if (!fLoaded) return;
C4Group CrewGroup;
if (!CrewGroup.OpenAsChild(pParentGrp, GetFilename().getData())
|| !Core.Save(CrewGroup, nullptr)
|| !CrewGroup.Close() || !pParentGrp->Save(true))
{
GetScreen()->ShowMessage(LoadResStr("IDS_FAIL_MODIFY"), "", C4GUI::Ico_Error);
return;
}
}
bool C4StartupPlrSelDlg::CrewListItem::SetName(const char *szNewName)
{
if (!fLoaded) return false;
// validate name
if (!szNewName || !*szNewName) return false;
if (SEqual(szNewName, Core.Name)) return true;
// generate filename from new name
char fn[_MAX_PATH+1];
SCopy(szNewName, fn, _MAX_PATH);
MakeFilenameFromTitle(fn);
if (!*fn) return false;
SAppend(".oci", fn, _MAX_PATH);
// check if a rename is due
if (!ItemIdentical(fn, GetFilename().getData()))
{
// check for duplicate filename
if (pParentGrp->FindEntry(fn))
{
StdStrBuf sMsg; sMsg.Format(LoadResStr("IDS_ERR_CLONKCOLLISION"), fn);
::pGUI->ShowMessageModal(sMsg.getData(), LoadResStr("IDS_FAIL_RENAME"), C4GUI::MessageDialog::btnOK, C4GUI::Ico_Error);
return false;
}
// OK; then rename
if (!pParentGrp->Rename(GetFilename().getData(), fn) || !pParentGrp->Save(true))
{
StdStrBuf sMsg; sMsg.Format(LoadResStr("IDS_ERR_RENAMEFILE"), GetFilename().getData(), fn);
::pGUI->ShowMessageModal(sMsg.getData(), LoadResStr("IDS_FAIL_RENAME"), C4GUI::MessageDialog::btnOK, C4GUI::Ico_Error);
return false;
}
const char *szConstFn = fn;
SetFilename(StdStrBuf(szConstFn));
}
// update clonk name and core
ListItem::SetName(szNewName);
SCopy(szNewName, Core.Name, C4MaxName);
RewriteCore();
return true;
}
void C4StartupPlrSelDlg::CrewListItem::SetSelectionInfo(C4GUI::TextWindow *pSelectionInfo)
{
// write info text for player
pSelectionInfo->ClearText(false);
pSelectionInfo->AddTextLine(FormatString("%s %s", Core.sRankName.getData(), Core.Name).getData(), &C4Startup::Get()->Graphics.BookFontCapt, ClrPlayerItem, false, false);
StdStrBuf sPromo;
int32_t iNextRankExp; StdStrBuf sNextRankName;
if (Core.GetNextRankInfo(::DefaultRanks, &iNextRankExp, &sNextRankName))
sPromo.Format(LoadResStr("IDS_DESC_PROMO"),sNextRankName.getData(),(int) iNextRankExp);
else
sPromo.Copy(LoadResStr("IDS_DESC_NOPROMO"));
pSelectionInfo->AddTextLine(FormatString(LoadResStr("IDS_DESC_OBJECT"),
Core.TypeName, Core.Experience, Core.Rounds, Core.DeathCount,
sPromo.getData(), TimeString(Core.TotalPlayingTime).getData(), DateString(Core.Birthday).getData()).getData(),
&C4Startup::Get()->Graphics.BookFont, ClrPlayerItem, false, false);
pSelectionInfo->UpdateHeight();
}
StdStrBuf C4StartupPlrSelDlg::CrewListItem::GetDelWarning()
{
StdStrBuf sWarning;
sWarning.Format(LoadResStr("IDS_MSG_DELETECLONK"),
Core.sRankName.getData(), Core.Name, GetFilename().getData());
int32_t iPlrTime = Core.TotalPlayingTime;
if (iPlrTime > 60*60*10)
sWarning.Append(static_cast<const StdStrBuf &>(FormatString(LoadResStr("IDS_MSG_DELETECLONK_PLAYTIME"), TimeString(iPlrTime).getData())));
return sWarning;
}
void C4StartupPlrSelDlg::CrewListItem::CrewRename()
{
if (pPlrSelDlg->pRenameEdit) return;
// rename this entry
pPlrSelDlg->pRenameEdit = new C4GUI::CallbackRenameEdit<C4StartupPlrSelDlg::CrewListItem, RenameParams>(pNameLabel, this, RenameParams(), &C4StartupPlrSelDlg::CrewListItem::DoRenaming, &C4StartupPlrSelDlg::CrewListItem::AbortRenaming);
}
void C4StartupPlrSelDlg::CrewListItem::AbortRenaming(RenameParams par)
{
// no renaming
pPlrSelDlg->pRenameEdit = nullptr;
}
C4GUI::RenameEdit::RenameResult C4StartupPlrSelDlg::CrewListItem::DoRenaming(RenameParams par, const char *szNewName)
{
// accept if name can be set; will fail if name is invalid or already given to another Crew member
if (!SetName(szNewName)) return C4GUI::RenameEdit::RR_Invalid;
pPlrSelDlg->pRenameEdit = nullptr;
// update in selection
C4StartupPlrSelDlg *pDlg = static_cast<C4StartupPlrSelDlg *>(GetDlg());
if (pDlg && pDlg->GetSelection() == this) pDlg->UpdateSelection();
return C4GUI::RenameEdit::RR_Accepted;
}
// ------------------------------------------------
// --- C4StartupPlrSelDlg
C4StartupPlrSelDlg::C4StartupPlrSelDlg() : C4StartupDlg("W")
{
// ctor
UpdateSize(); // for clientrect
// screen calculations
int iButtonHeight = C4GUI_ButtonHgt;
int iButtonXSpacing = (GetClientRect().Wdt > 700) ? GetClientRect().Wdt/58 : 2;
int iButtonCount = 6;
C4GUI::ComponentAligner caMain(GetClientRect(), 0,0, true);
C4GUI::ComponentAligner caButtonArea(caMain.GetFromBottom(std::max(caMain.GetHeight()/15, iButtonHeight)),0,0);
rcBottomButtons = caButtonArea.GetCentered(caMain.GetWidth(), iButtonHeight);
iBottomButtonWidth = (caButtonArea.GetWidth() - iButtonXSpacing * (iButtonCount-1)) / iButtonCount;
C4Rect rcMain = caMain.GetAll();
C4Rect rcPlrList = C4Rect(rcMain.Wdt/8, rcMain.Hgt/8, rcMain.Wdt*5/16, rcMain.Hgt*6/8);
C4Rect rcInfoWindow = C4Rect(rcMain.Wdt*9/16, rcMain.Hgt/8, rcMain.Wdt*5/16, rcMain.Hgt*6/8);
AddElement(pPlrListBox = new C4GUI::ListBox(rcPlrList));
pPlrListBox->SetToolTip(LoadResStr("IDS_DLGTIP_PLAYERFILES"));
pPlrListBox->SetDecoration(true, &C4Startup::Get()->Graphics.sfctBookScroll, true);
pPlrListBox->UpdateElementPositions();
pPlrListBox->SetSelectionChangeCallbackFn(new C4GUI::CallbackHandler<C4StartupPlrSelDlg>(this, &C4StartupPlrSelDlg::OnSelChange));
pPlrListBox->SetSelectionDblClickFn(new C4GUI::CallbackHandler<C4StartupPlrSelDlg>(this, &C4StartupPlrSelDlg::OnSelDblClick));
AddElement(pSelectionInfo = new C4GUI::TextWindow(rcInfoWindow,0,0,0,100,4096," ",false,nullptr,0,true));
pSelectionInfo->SetDecoration(true, true, &C4Startup::Get()->Graphics.sfctBookScroll, true);
pSelectionInfo->UpdateHeight();
// bottom line buttons - positioning done in UpdateBottomButtons by UpdatePlayerList
C4Rect rcDefault(0,0,10,10);
AddElement(btnBack = new C4GUI::CallbackButton<C4StartupPlrSelDlg>(LoadResStr("IDS_BTN_BACK"), rcDefault, &C4StartupPlrSelDlg::OnBackBtn));
AddElement(btnNew = new C4GUI::CallbackButton<C4StartupPlrSelDlg>(LoadResStr("IDS_BTN_NEW"), rcDefault, &C4StartupPlrSelDlg::OnNewBtn));
btnNew->SetToolTip(LoadResStr("IDS_DLGTIP_NEWPLAYER"));
AddElement(btnActivatePlr = new C4GUI::CallbackButton<C4StartupPlrSelDlg>(nullptr, rcDefault, &C4StartupPlrSelDlg::OnActivateBtn));
AddElement(btnDelete = new C4GUI::CallbackButton<C4StartupPlrSelDlg>(LoadResStr("IDS_BTN_DELETE"), rcDefault, &C4StartupPlrSelDlg::OnDelBtn));
AddElement(btnProperties = new C4GUI::CallbackButton<C4StartupPlrSelDlg>(nullptr, rcDefault, &C4StartupPlrSelDlg::OnPropertyBtn));
AddElement(btnCrew = new C4GUI::CallbackButton<C4StartupPlrSelDlg>(LoadResStr("IDS_SELECT_CREW"), rcDefault, &C4StartupPlrSelDlg::OnCrewBtn));
btnCrew->SetToolTip(LoadResStr("IDS_DLGTIP_PLAYERCREW"));
// refill listboxes
UpdatePlayerList();
// Just safety incase listbox was empty, in which case no selection change callback will have been done:
// Update current listbox selection to that of no selected item
if (!pPlrListBox->GetFirst()) UpdateSelection();
// initial focus on player list
SetFocus(pPlrListBox, false);
// key bindings
C4CustomKey::CodeList keys;
keys.emplace_back(K_BACK);
keys.emplace_back(K_LEFT);
keys.emplace_back(K_ESCAPE);
if (Config.Controls.GamepadGuiControl)
{
ControllerKeys::Cancel(keys);
}
pKeyBack = new C4KeyBinding(keys, "StartupPlrSelBack", KEYSCOPE_Gui,
new C4GUI::DlgKeyCB<C4StartupPlrSelDlg>(*this, &C4StartupPlrSelDlg::KeyBack), C4CustomKey::PRIO_CtrlOverride);
pKeyProperties = new C4KeyBinding(C4KeyCodeEx(K_F2), "StartupPlrSelProp", KEYSCOPE_Gui,
new C4GUI::DlgKeyCB<C4StartupPlrSelDlg>(*this, &C4StartupPlrSelDlg::KeyProperties), C4CustomKey::PRIO_CtrlOverride);
pKeyCrew = new C4KeyBinding(C4KeyCodeEx(K_RIGHT), "StartupPlrSelCrew", KEYSCOPE_Gui,
new C4GUI::ControlKeyDlgCB<C4StartupPlrSelDlg>(pPlrListBox, *this, &C4StartupPlrSelDlg::KeyCrew), C4CustomKey::PRIO_CtrlOverride);
pKeyDelete = new C4KeyBinding(C4KeyCodeEx(K_DELETE), "StartupPlrSelDelete", KEYSCOPE_Gui,
new C4GUI::DlgKeyCB<C4StartupPlrSelDlg>(*this, &C4StartupPlrSelDlg::KeyDelete), C4CustomKey::PRIO_CtrlOverride);
pKeyNew = new C4KeyBinding(C4KeyCodeEx(K_INSERT), "StartupPlrSelNew", KEYSCOPE_Gui,
new C4GUI::DlgKeyCB<C4StartupPlrSelDlg>(*this, &C4StartupPlrSelDlg::KeyNew), C4CustomKey::PRIO_CtrlOverride);
}
C4StartupPlrSelDlg::~C4StartupPlrSelDlg()
{
delete pKeyDelete;
delete pKeyCrew;
delete pKeyProperties;
delete pKeyBack;
delete pKeyNew;
}
void C4StartupPlrSelDlg::AbortRenaming()
{
if (pRenameEdit) pRenameEdit->Abort();
}
void C4StartupPlrSelDlg::DrawElement(C4TargetFacet &cgo)
{
// draw background
typedef C4GUI::FullscreenDialog Base;
Base::DrawElement(cgo);
}
void C4StartupPlrSelDlg::UpdateBottomButtons()
{
// bottom line buttons depend on list mode
C4GUI::ComponentAligner caBottomButtons(rcBottomButtons,0,0);
switch (eMode)
{
case PSDM_Player:
{
// update some buttons for player mode
btnProperties->SetText(LoadResStr("IDS_BTN_PROPERTIES"));
btnProperties->SetToolTip(LoadResStr("IDS_DLGTIP_PLAYERPROPERTIES"));
btnNew->SetVisibility(true);
btnCrew->SetVisibility(true);
btnDelete->SetToolTip(LoadResStr("IDS_DLGTIP_PLAYERDELETE"));
btnBack->SetToolTip(LoadResStr("IDS_DLGTIP_BACKMAIN"));
btnBack ->SetBounds(caBottomButtons.GetGridCell(0,6,0,1,iBottomButtonWidth,C4GUI_ButtonHgt,true));
btnNew ->SetBounds(caBottomButtons.GetGridCell(1,6,0,1,iBottomButtonWidth,C4GUI_ButtonHgt,true));
btnActivatePlr->SetBounds(caBottomButtons.GetGridCell(2,6,0,1,iBottomButtonWidth,C4GUI_ButtonHgt,true));
btnDelete ->SetBounds(caBottomButtons.GetGridCell(3,6,0,1,iBottomButtonWidth,C4GUI_ButtonHgt,true));
btnProperties ->SetBounds(caBottomButtons.GetGridCell(4,6,0,1,iBottomButtonWidth,C4GUI_ButtonHgt,true));
btnCrew ->SetBounds(caBottomButtons.GetGridCell(5,6,0,1,iBottomButtonWidth,C4GUI_ButtonHgt,true));
}
break;
case PSDM_Crew:
{
// update some buttons for player mode
btnProperties->SetText(LoadResStr("IDS_BTN_RENAME"));
btnProperties->SetToolTip(LoadResStr("IDS_DESC_CREWRENAME"));
btnNew->SetVisibility(false);
btnCrew->SetVisibility(false);
btnDelete->SetToolTip(LoadResStr("IDS_MSG_DELETECLONK_DESC"));
btnBack->SetToolTip(LoadResStr("IDS_MSG_BACKTOPLAYERDLG"));
btnBack ->SetBounds(caBottomButtons.GetGridCell(0,4,0,1,iBottomButtonWidth,C4GUI_ButtonHgt,true));
btnActivatePlr->SetBounds(caBottomButtons.GetGridCell(1,4,0,1,iBottomButtonWidth,C4GUI_ButtonHgt,true));
btnDelete ->SetBounds(caBottomButtons.GetGridCell(2,4,0,1,iBottomButtonWidth,C4GUI_ButtonHgt,true));
btnProperties ->SetBounds(caBottomButtons.GetGridCell(3,4,0,1,iBottomButtonWidth,C4GUI_ButtonHgt,true));
}
break;
};
}
void C4StartupPlrSelDlg::UpdatePlayerList()
{
// something has changed!
AbortRenaming();
// refill pPlrListBox with players in player folder or crew
// clear old items
C4GUI::Element *pEl;
while ((pEl = pPlrListBox->GetFirst())) delete pEl;
// update command buttons
UpdateBottomButtons();
// create new
switch (eMode)
{
case PSDM_Player:
{
SetTitle(LoadResStrNoAmp("IDS_DLG_PLAYERSELECTION"));
// player mode: insert all players
const char *szFn;
StdStrBuf sSearchPath(Config.General.UserDataPath);
PlayerListItem *pFirstActivatedPlrItem=nullptr, *pFirstDeactivatedPlrItem=nullptr, *pPlrItem=nullptr;
for (DirectoryIterator i(sSearchPath.getData()); (szFn=*i); i++)
{
szFn = Config.AtRelativePath(szFn);
if (*GetFilename(szFn) == '.') continue; // ignore ".", ".." and private files (".*")
if (!WildcardMatch(C4CFN_PlayerFiles, GetFilename(szFn))) continue;
bool fIsParticipating = !!SIsModule(Config.General.Participants, szFn, nullptr, false);
pPlrItem = new PlayerListItem(this, pPlrListBox, nullptr, fIsParticipating);
try
{
pPlrItem->Load(StdStrBuf(szFn));
}
catch (ListItem::LoadError & e)
{
// invalid player: ignore but log error message
DebugLog(e.getData());
delete pPlrItem;
continue;
}
if (fIsParticipating)
{
if (!pFirstActivatedPlrItem) pFirstActivatedPlrItem = pPlrItem;
}
else if (!pFirstDeactivatedPlrItem) pFirstDeactivatedPlrItem = pPlrItem;
}
// select first element; prefer activated player
if (!(pPlrItem = pFirstActivatedPlrItem))
pPlrItem = pFirstDeactivatedPlrItem;
if (pPlrItem)
pPlrListBox->SelectEntry(pPlrItem, false);
// re-validate Game.PlayerFilename
UpdateActivatedPlayers();
break;
}
case PSDM_Crew:
{
SetTitle(FormatString("%s %s", LoadResStrNoAmp("IDS_CTL_CREW"), CurrPlayer.Core.PrefName).getData());
// crew mode: Insert complete crew of player (2do: sort)
bool fSucc; char szFn[_MAX_PATH+1];
for (fSucc=CurrPlayer.Grp.FindEntry(C4CFN_ObjectInfoFiles, szFn); fSucc; fSucc=CurrPlayer.Grp.FindNextEntry(C4CFN_ObjectInfoFiles, szFn, nullptr, true))
{
CrewListItem *pCrewItem = new CrewListItem(this, pPlrListBox, CurrPlayer.Core.PrefColorDw);
try
{
pCrewItem->Load(CurrPlayer.Grp, StdStrBuf(szFn));
}
catch (ListItem::LoadError & e)
{
// invalid player: ignore but log error message
DebugLog(e.getData());
delete pCrewItem;
continue;
}
}
// resort crew by type and experience
ResortCrew();
pPlrListBox->SelectFirstEntry(false);
break;
}
}
}
C4StartupPlrSelDlg::ListItem *C4StartupPlrSelDlg::GetSelection()
{
// get selected item: There's only instances of PlrListItem in this list
return static_cast<ListItem *>(pPlrListBox->GetSelectedItem());
}
void C4StartupPlrSelDlg::SetSelection(ListItem *pNewItem)
{
// update selection in listbox
pPlrListBox->SelectEntry(pNewItem, false);
}
void C4StartupPlrSelDlg::UpdateSelection()
{
// something has changed!
AbortRenaming();
// get currently selected player
ListItem *pSel = GetSelection();
// button text 'activate' if current player is deactivated; 'deactivate' otherwise
if (pSel && pSel->IsActivated())
{
btnActivatePlr->SetText(LoadResStr("IDS_BTN_DEACTIVATE"));
btnActivatePlr->SetToolTip(FormatString(LoadResStr("IDS_MSG_NOPARTICIPATE_DESC"), pSel->GetName()).getData());
}
else
{
btnActivatePlr->SetText(LoadResStr("IDS_BTN_ACTIVATE"));
btnActivatePlr->SetToolTip(FormatString(LoadResStr("IDS_MSG_PARTICIPATE_DESC"), pSel ? pSel->GetName() : "").getData());
}
// no item selected?
if (!pSel)
{
pSelectionInfo->ClearText(true);
// 2do: disable buttons
return;
}
// info text for selection
pSel->SetSelectionInfo(pSelectionInfo);
}
void C4StartupPlrSelDlg::OnItemCheckChange(C4GUI::Element *pCheckBox)
{
switch (eMode)
{
case PSDM_Player:
// Deselect all other players
for (ListItem* pEl = static_cast<ListItem*>(pPlrListBox->GetFirst()); pEl != nullptr; pEl = pEl->GetNext())
if (pCheckBox && pEl != pCheckBox->GetParent())
pEl->SetActivated(false);
// update Config.General.Participants
UpdateActivatedPlayers();
break;
case PSDM_Crew:
// update affected crew item
if (pCheckBox) static_cast<CrewListItem *>(pCheckBox->GetParent())->UpdateClonkEnabled();
break;
}
// update player selection text
UpdateSelection();
}
void C4StartupPlrSelDlg::UpdateActivatedPlayers()
{
assert(eMode == PSDM_Player);
// refill Config.General.Participants-list
*Config.General.Participants = '\0';
for (ListItem *pPlrItem = static_cast<ListItem *>(pPlrListBox->GetFirst()); pPlrItem; pPlrItem = pPlrItem->GetNext())
if (pPlrItem->IsActivated())
{
const char *szAddFilename = pPlrItem->GetFilename().getData();
if (std::strlen(Config.General.Participants) + 1 + std::strlen(szAddFilename) < sizeof(Config.General.Participants))
SAddModule(Config.General.Participants, szAddFilename);
else
{
pPlrItem->SetActivated(false);
GetScreen()->ShowMessage(FormatString(LoadResStr("IDS_ERR_PLAYERSTOOLONG"), pPlrItem->GetName()).getData(), LoadResStr("IDS_ERR_TITLE"), C4GUI::Ico_Error);
}
}
}
void C4StartupPlrSelDlg::OnActivateBtn(C4GUI::Control *btn)
{
// toggle activation state of current item
// get currently selected player
ListItem *pSel = GetSelection();
if (!pSel) return;
pSel->SetActivated(!pSel->IsActivated());
// update stuff
OnItemCheckChange(pSel->GetCheckBox());
}
void C4StartupPlrSelDlg::DoBack()
{
switch (eMode)
{
case PSDM_Player:
{
// back 2 main
C4Startup::Get()->SwitchDialog(C4Startup::SDID_Back);
break;
}
case PSDM_Crew:
// back 2 player list
SetPlayerMode();
break;
}
}
void C4StartupPlrSelDlg::OnNewBtn(C4GUI::Control *btn)
{
if (eMode != PSDM_Player) return;
C4GUI::Dialog *pDlg;
GetScreen()->ShowRemoveDlg(pDlg=new C4StartupPlrPropertiesDlg(nullptr, this));
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));
}
bool C4StartupPlrSelDlg::CheckPlayerName(const StdStrBuf &Playername, StdStrBuf &Filename, const StdStrBuf *pPrevFilename, bool fWarnEmpty)
{
// must not be empty
if (!Playername.getLength())
{
if (fWarnEmpty) C4GUI::Screen::GetScreenS()->ShowMessage(LoadResStr("IDS_ERR_PLRNAME_EMPTY"), "", C4GUI::Ico_Error);
return false;
}
// generate valid filename
Filename.Copy(Playername);
// Slashes in Filenames are no good
SReplaceChar(Filename.getMData(), '\\', '_');
SReplaceChar(Filename.getMData(), '/', '_');
SReplaceChar(Filename.getMData(), ':', '_');
SReplaceChar(Filename.getMData(), '*', '_');
SReplaceChar(Filename.getMData(), '?', '_');
SReplaceChar(Filename.getMData(), '"', '_');
SReplaceChar(Filename.getMData(), '<', '_');
SReplaceChar(Filename.getMData(), '>', '_');
SReplaceChar(Filename.getMData(), '|', '_');
if (*Filename.getData() == '.') *Filename.getMData() = '_';
Filename.Append(".ocp");
StdStrBuf Path(Config.General.UserDataPath); // start at local path
Path.Append(Filename);
// validity check: Must not exist yet if renamed
if (!pPrevFilename || !ItemIdentical(Path.getData(), Config.AtUserDataPath(pPrevFilename->getData()))) if (ItemExists(Path.getData()))
{
C4GUI::Screen::GetScreenS()->ShowMessage(FormatString(LoadResStr("IDS_ERR_PLRNAME_TAKEN"),
Playername.getData()).getData(), "", C4GUI::Ico_Error);
return false;
}
return true;
}
void C4StartupPlrSelDlg::OnCrewBtn(C4GUI::Control *btn)
{
// display crew for activated player
if (eMode != PSDM_Player) return;
PlayerListItem *pSel = static_cast<PlayerListItem *>(GetSelection());
if (!pSel) return;
SetCrewMode(pSel);
}
void C4StartupPlrSelDlg::SetPlayerMode()
{
// change view to listing players
C4GUI::GUISound("UI::Close");
StdStrBuf LastPlrFilename;
LastPlrFilename.Copy(static_cast<const StdStrBuf &>(CurrPlayer.Grp.GetFullName()));
CurrPlayer.Grp.Close();
eMode = PSDM_Player;
UpdatePlayerList();
SelectItem(LastPlrFilename, false);
UpdateSelection();
}
void C4StartupPlrSelDlg::SetCrewMode(PlayerListItem *pSel)
{
// change view to listing crew of a player
CurrPlayer.Core = pSel->GetCore();
StdStrBuf Path(Config.General.UserDataPath); // start at local path
// Path.Append(Config.General.PlayerPath);
Path.Append(pSel->GetFilename());
if (!CurrPlayer.Grp.Open(Path.getData())) return;
if (!CurrPlayer.Grp.FindEntry(C4CFN_ObjectInfoFiles))
{
StdCopyStrBuf strCrew(FormatString("%s %s", LoadResStrNoAmp("IDS_CTL_CREW"), CurrPlayer.Core.PrefName));
// player has no crew!
GetScreen()->ShowMessage(FormatString(LoadResStr("IDS_ERR_PLRNOCREW"),
CurrPlayer.Core.PrefName).getData(),
strCrew.getData(), C4GUI::Ico_Player);
return;
}
C4GUI::GUISound("UI::Open");
eMode = PSDM_Crew;
UpdatePlayerList();
UpdateSelection();
}
void C4StartupPlrSelDlg::OnDelBtn(C4GUI::Control *btn)
{
// something has changed!
AbortRenaming();
// delete selected player
ListItem *pSel = GetSelection();
if (!pSel) return;
StdStrBuf sWarning; sWarning.Take(pSel->GetDelWarning());
GetScreen()->ShowRemoveDlg(new C4GUI::ConfirmationDialog(sWarning.getData(), LoadResStr("IDS_BTN_DELETE"),
new C4GUI::CallbackHandlerExPar<C4StartupPlrSelDlg, ListItem *>(this, &C4StartupPlrSelDlg::OnDelBtnConfirm, pSel), C4GUI::MessageDialog::btnYesNo));
}
void C4StartupPlrSelDlg::OnDelBtnConfirm(ListItem *pSel)
{
StdStrBuf Path;
switch (eMode)
{
case PSDM_Player:
Path.Append(Config.General.UserDataPath); // start at local path
Path.Append(pSel->GetFilename());
if (!C4Group_DeleteItem(Path.getData()))
{
StdStrBuf sMsg; sMsg.Copy(LoadResStr("IDS_FAIL_DELETE"));
GetScreen()->ShowMessage(sMsg.getData(), LoadResStr("IDS_DLG_CLEAR"), C4GUI::Ico_Error);
}
break;
case PSDM_Crew:
if (!CurrPlayer.Grp.DeleteEntry(pSel->GetFilename().getData()))
{
StdStrBuf sMsg; sMsg.Copy(LoadResStr("IDS_FAIL_DELETE"));
GetScreen()->ShowMessage(sMsg.getData(), LoadResStr("IDS_DLG_CLEAR"), C4GUI::Ico_Error);
}
break;
}
// update buttons 'n stuff
UpdatePlayerList();
}
void C4StartupPlrSelDlg::SelectItem(const StdStrBuf &Filename, bool fActivate)
{
// find item
for (ListItem *pPlrItem = static_cast<ListItem *>(pPlrListBox->GetFirst()); pPlrItem; pPlrItem = pPlrItem->GetNext())
if (ItemIdentical(pPlrItem->GetFilename().getData(), Filename.getData()))
{
// select it
pPlrListBox->SelectEntry(pPlrItem, false);
// activate it
if (fActivate)
{
pPlrItem->SetActivated(true);
// player activation updates
OnItemCheckChange(pPlrItem->GetCheckBox());
}
// max one
return;
}
}
void C4StartupPlrSelDlg::OnPropertyBtn(C4GUI::Control *btn)
{
// something has changed!
AbortRenaming();
switch (eMode)
{
case PSDM_Player:
{
// show property dialog for selected player
PlayerListItem *pSel = static_cast<PlayerListItem *>(GetSelection());
if (!pSel) return;
C4GUI::Dialog *pDlg;
GetScreen()->ShowRemoveDlg(pDlg=new C4StartupPlrPropertiesDlg(pSel, this));
pDlg->SetPos(std::min<int32_t>(GetBounds().Wdt/10, GetBounds().Wdt - pDlg->GetBounds().Wdt),
(GetBounds().Hgt - pDlg->GetBounds().Hgt) / 2);
}
break;
case PSDM_Crew:
// rename crew
CrewListItem *pSel = static_cast<CrewListItem *>(GetSelection());
if (!pSel) return;
pSel->CrewRename();
break;
}
}
/* -- Crew sorting -- */
struct C4StartupPlrSelDlg_CrewSortDataEntry
{
int32_t iMaxExp;
C4ID idType;
C4StartupPlrSelDlg_CrewSortDataEntry(int32_t iMaxExp, C4ID idType) : iMaxExp(iMaxExp), idType(idType) {}
};
class C4StartupPlrSelDlg_CrewSortDataMatchType
{
C4ID idType;
public:
C4StartupPlrSelDlg_CrewSortDataMatchType(C4ID idType) : idType(idType) {}
bool operator()(C4StartupPlrSelDlg_CrewSortDataEntry Check) { return Check.idType == idType; }
};
typedef std::vector<C4StartupPlrSelDlg_CrewSortDataEntry> C4StartupPlrSelDlg_CrewSortData;
int32_t C4StartupPlrSelDlg::CrewSortFunc(const C4GUI::Element *pEl1, const C4GUI::Element *pEl2, void *par)
{
const auto *pItem1 = static_cast<const CrewListItem *>(pEl1);
const auto *pItem2 = static_cast<const CrewListItem *>(pEl2);
C4StartupPlrSelDlg_CrewSortData &rSortData = *static_cast<C4StartupPlrSelDlg_CrewSortData *>(par);
C4StartupPlrSelDlg_CrewSortData::iterator i = std::find_if(rSortData.begin(), rSortData.end(), C4StartupPlrSelDlg_CrewSortDataMatchType(pItem1->GetCore().id)),
j = std::find_if(rSortData.begin(), rSortData.end(), C4StartupPlrSelDlg_CrewSortDataMatchType(pItem2->GetCore().id));
// primary sort: By Clonk type, where high exp Clonk types are sorted atop low exp Clonk types
if (i != j)
{
if (i == rSortData.end()) return -1; else if (j == rSortData.end()) return +1; // can't really happen
return (*i).iMaxExp - (*j).iMaxExp;
}
// secondary: By experience
return pItem1->GetCore().Experience - pItem2->GetCore().Experience;
}
void C4StartupPlrSelDlg::ResortCrew()
{
assert(eMode == PSDM_Crew);
// create a list of Clonk types and their respective maximum experience
C4StartupPlrSelDlg_CrewSortData SortData;
for (CrewListItem *pCrewItem = static_cast<CrewListItem *>(pPlrListBox->GetFirst()); pCrewItem; pCrewItem = pCrewItem->GetNext())
{
C4StartupPlrSelDlg_CrewSortData::iterator i = std::find_if(SortData.begin(), SortData.end(), C4StartupPlrSelDlg_CrewSortDataMatchType(pCrewItem->GetCore().id));
if (i == SortData.end())
SortData.emplace_back(pCrewItem->GetCore().Experience, pCrewItem->GetCore().id);
else
(*i).iMaxExp = std::max<int32_t>((*i).iMaxExp, pCrewItem->GetCore().Experience);
}
pPlrListBox->SortElements(&CrewSortFunc, &SortData);
}
// ------------------------------------------------
// --- Player color HSV chooser
class C4StartupPlrColorPickerDlg : public C4GUI::Dialog
{
public:
C4StartupPlrColorPickerDlg(C4PlayerInfoCore *plrcore);
protected:
// Event handler
void OnClosed(bool commit) override;
private:
class Picker : public C4GUI::Control
{
public:
Picker(const C4Rect &bounds);
// Set/retrieve current color value
void SetColor(uint32_t rgb);
uint32_t GetColor() const;
protected:
// Event handlers, overridden from C4GUI::Control
void DrawElement(C4TargetFacet &cgo) override;
void MouseInput(C4GUI::CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam) override;
void DoDragging(C4GUI::CMouse &rMouse, int32_t iX, int32_t iY, DWORD dwKeyParam) override;
private:
static const unsigned int HSPickerCursorSize = 5;
static const unsigned int VPickerCursorSize = 7;
C4FacetSurface hsFacet, vFacet; // chooser backgrounds
C4Rect hsPickerRect, vPickerRect;
C4GUI::Picture *flagPreview, *crewPreview;
uint32_t hsv; // current color
enum {
PS_Idle, // user isn't dragging anything
PS_IdleDragging, // user started the drag on empty space
PS_DragHS, // user started the drag over the HS picker
PS_DragV // user started the drag over the V picker
} state;
bool HandleMouseDown(int32_t x, int32_t y);
void UpdateVFacet(uint32_t h, uint32_t s);
void UpdatePreview();
};
C4PlayerInfoCore *plrcore;
Picker *picker;
static uint32_t HSV2RGB(uint32_t hsv)
{
float h = GetRedValue(hsv) / 255.f * 6.f;
float s = GetGreenValue(hsv) / 255.f;
float v = GetBlueValue(hsv) / 255.f;
float chroma = s * v;
float x = chroma * (1.f - std::abs(std::fmod(h, 2.f) - 1.f));
float r = 0;
float g = 0;
float b = 0;
switch (static_cast<int>(h))
{
case 0: case 6:
r = chroma; g = x; break;
case 1:
r = x; g = chroma; break;
case 2:
g = chroma; b = x; break;
case 3:
g = x; b = chroma; break;
case 4:
b = chroma; r = x; break;
case 5:
b = x; r = chroma; break;
}
r += v-chroma;
g += v-chroma;
b += v-chroma;
return RGBA(r * 255.f, g * 255.f, b * 255.f, hsv >> 24);
}
static uint32_t RGB2HSV(uint32_t rgb)
{
float r = GetRedValue(rgb) / 255.f;
float g = GetGreenValue(rgb) / 255.f;
float b = GetBlueValue(rgb) / 255.f;
float min = std::min(r, std::min(g, b));
float max = std::max(r, std::max(g, b));
float chroma = max - min;
float hue = 0;
if (r == max)
hue = std::fmod((g-b) / chroma, 6.f);
else if (g == max)
hue = (b-r) / chroma + 2.f;
else
hue = (r-g) / chroma + 4.f;
float h = hue / 6.f;
float s = max == 0 ? 0.f : chroma / max;
float v = max;
return RGBA(h * 255.f, s * 255.f, v * 255.f, rgb >> 24);
}
};
C4StartupPlrColorPickerDlg::C4StartupPlrColorPickerDlg(C4PlayerInfoCore *plrcore)
: Dialog(400, 296 + C4GUI_ButtonAreaHgt, LoadResStr("IDS_DLG_PLAYERCOLORSELECTION"), false), plrcore(plrcore)
{
C4GUI::ComponentAligner caMain(GetClientRect(), 0, 1, true);
picker = new Picker(caMain.GetFromTop(280));
picker->SetColor(plrcore->PrefColorDw);
AddElement(picker);
// buttons
C4GUI::ComponentAligner caButtonArea(caMain.GetFromBottom(C4GUI_ButtonAreaHgt), 0, 0);
caButtonArea = C4GUI::ComponentAligner(caButtonArea.GetCentered(2*128 + 4*8, C4GUI_ButtonAreaHgt), 8, 8);
C4GUI::Button *cancelButton = new C4GUI::CancelButton(caButtonArea.GetFromRight(128));
C4GUI::Button *okButton = new C4GUI::OKButton(caButtonArea.GetFromRight(128));
AddElement(okButton);
AddElement(cancelButton);
}
void C4StartupPlrColorPickerDlg::OnClosed(bool commit)
{
// Write chosen color back to player core
if (commit)
plrcore->PrefColorDw = picker->GetColor();
}
C4StartupPlrColorPickerDlg::Picker::Picker(const C4Rect &bounds)
: Control(bounds), state(PS_Idle)
{
C4GUI::ComponentAligner caMain(bounds, 8, 8, true);
caMain.ExpandBottom(-(caMain.GetInnerHeight() - 256));
hsPickerRect = caMain.GetFromLeft(256);
vPickerRect = caMain.GetFromLeft(16 + VPickerCursorSize);
vPickerRect.Hgt = 256 - PlayerColorValueLowBound;
C4Facet &flagPreviewPic = ::GraphicsResource.fctFlagClr;
int preview_width = std::min<int>(flagPreviewPic.Wdt, caMain.GetInnerWidth());
flagPreview = new C4GUI::Picture(caMain.GetFromTop(flagPreviewPic.GetHeightByWidth(preview_width), preview_width), true);
flagPreview->SetFacet(flagPreviewPic);
AddElement(flagPreview);
C4Facet &crewPreviewPic = ::GraphicsResource.fctCrewClr;
preview_width = std::min<int>(crewPreviewPic.Wdt, caMain.GetInnerWidth());
crewPreview = new C4GUI::Picture(caMain.GetFromTop(crewPreviewPic.GetHeightByWidth(preview_width), preview_width), true);
crewPreview->SetFacet(crewPreviewPic);
AddElement(crewPreview);
// Pre-draw the H+S chooser background, it never changes anyway
hsFacet.Create(hsPickerRect.Wdt, hsPickerRect.Hgt);
hsFacet.Surface->Lock();
for (int y = 0; y < hsFacet.Hgt; ++y)
for (int x = 0; x < hsFacet.Wdt; ++x)
hsFacet.Surface->SetPixDw(x, y, HSV2RGB(C4RGB(x, 255-y, 255)));
hsFacet.Surface->Unlock();
vFacet.Create(vPickerRect.Wdt - VPickerCursorSize, vPickerRect.Hgt);
UpdateVFacet(255, 255);
}
void C4StartupPlrColorPickerDlg::Picker::UpdateVFacet(uint32_t h, uint32_t s)
{
// Draw the V chooser background according to current H+S values
vFacet.Surface->Lock();
for (int y = 0; y < vPickerRect.Hgt; ++y)
for (int x = 0; x < vFacet.Wdt; ++x)
vFacet.Surface->SetPixDw(x, y, HSV2RGB(C4RGB(h, s, 255-y)));
vFacet.Surface->Unlock();
}
void C4StartupPlrColorPickerDlg::Picker::UpdatePreview()
{
flagPreview->SetDrawColor(HSV2RGB(hsv));
crewPreview->SetDrawColor(HSV2RGB(hsv));
}
void C4StartupPlrColorPickerDlg::Picker::SetColor(uint32_t rgb)
{
hsv = RGB2HSV(rgb);
UpdateVFacet(GetRedValue(hsv), GetGreenValue(hsv));
UpdatePreview();
}
uint32_t C4StartupPlrColorPickerDlg::Picker::GetColor() const
{
return HSV2RGB(hsv);
}
void C4StartupPlrColorPickerDlg::Picker::DrawElement(C4TargetFacet &cgo)
{
// H+S chooser background
C4Facet cgoPicker(cgo.Surface, cgo.TargetX + hsPickerRect.x, cgo.TargetY + hsPickerRect.y, hsPickerRect.Wdt, hsPickerRect.Hgt);
hsFacet.Draw(cgoPicker.Surface, cgoPicker.X, cgoPicker.Y);
// H+S cursor
cgoPicker.Wdt = cgoPicker.Hgt = HSPickerCursorSize;
cgoPicker.X += GetRedValue(hsv) - cgoPicker.Wdt / 2;
cgoPicker.Y += 255 - GetGreenValue(hsv) - cgoPicker.Hgt / 2;
pDraw->DrawLineDw(cgoPicker.Surface, cgoPicker.X, cgoPicker.Y, cgoPicker.X + cgoPicker.Wdt, cgoPicker.Y + cgoPicker.Hgt, C4RGB(0, 0, 0));
pDraw->DrawLineDw(cgoPicker.Surface, cgoPicker.X + cgoPicker.Wdt, cgoPicker.Y, cgoPicker.X, cgoPicker.Y + cgoPicker.Hgt, C4RGB(0, 0, 0));
// V chooser background
cgoPicker.Set(cgo.Surface, cgo.TargetX + vPickerRect.x + VPickerCursorSize, cgo.TargetY + vPickerRect.y, vPickerRect.Wdt - VPickerCursorSize, vPickerRect.Hgt);
vFacet.Draw(cgoPicker.Surface, cgoPicker.X, cgoPicker.Y);
// V cursor
cgoPicker.Wdt = cgoPicker.Hgt = VPickerCursorSize;
cgoPicker.X -= cgoPicker.Wdt / 2 + 1;
cgoPicker.Y += 255 - GetBlueValue(hsv) - cgoPicker.Hgt / 2;
for (int i = 0; i < cgoPicker.Hgt / 2 + 1; ++i)
pDraw->DrawLineDw(cgoPicker.Surface, cgoPicker.X + i, cgoPicker.Y + i, cgoPicker.X + i, cgoPicker.Y + cgoPicker.Hgt - i, C4RGB(255, 255, 255));
}
bool C4StartupPlrColorPickerDlg::Picker::HandleMouseDown(int32_t x, int32_t y)
{
if (state == PS_IdleDragging)
{
// User is dragging something that is neither of the pickers. Ignore.
return false;
}
// Check if a drag starts or was originally started over a picker
else if (state == PS_DragHS || (state == PS_Idle && hsPickerRect.Contains(x, y)))
{
int h = Clamp(x - hsPickerRect.x, 0, hsPickerRect.Wdt - 1);
assert(Inside(h, 0, 255));
int s = 255 - Clamp(y - hsPickerRect.y, 0, hsPickerRect.Hgt - 1);
assert(Inside(s, 0, 255));
hsv = C4RGB(h, s, GetBlueValue(hsv));
UpdateVFacet(h, s);
UpdatePreview();
state = PS_DragHS;
return true;
}
else if (state == PS_DragV || (state == PS_Idle && vPickerRect.Contains(x, y)))
{
int v = 255 - Clamp(y - vPickerRect.y, 0, vPickerRect.Hgt - 1);
assert(Inside(v, 0, 255));
hsv = (hsv & 0xFFFFFF00) | v;
UpdatePreview();
state = PS_DragV;
return true;
}
else
{
// Drag started outside of all picker areas; ignore movement until user releases mouse button.
state = PS_IdleDragging;
return false;
}
}
void C4StartupPlrColorPickerDlg::Picker::MouseInput(C4GUI::CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam)
{
Control::MouseInput(rMouse, iButton, iX, iY, dwKeyParam);
if (!rMouse.IsLDown()) state = PS_Idle;
if (rMouse.pDragElement) return;
if (rMouse.IsLDown())
{
if (HandleMouseDown(iX, iY))
{
rMouse.pDragElement = this;
C4GUI::GUISound("UI::Select");
}
else
{
rMouse.pDragElement = nullptr;
}
}
}
void C4StartupPlrColorPickerDlg::Picker::DoDragging(C4GUI::CMouse &rMouse, int32_t iX, int32_t iY, DWORD dwKeyParam)
{
HandleMouseDown(iX, iY);
}
/* ---- Player property dlg ---- */
C4StartupPlrPropertiesDlg::C4StartupPlrPropertiesDlg(C4StartupPlrSelDlg::PlayerListItem * pForPlayer, C4StartupPlrSelDlg *pParentDlg)
: Dialog(C4Startup::Get()->Graphics.fctPlrPropBG.Wdt, C4Startup::Get()->Graphics.fctPlrPropBG.Hgt, "", false), pMainDlg(pParentDlg), pForPlayer(pForPlayer), fClearBigIcon(false)
{
if (pForPlayer)
{
// edit existing player
C4P = pForPlayer->GetCore();
}
else
{
// create new player: Use default C4P values, with a few exceptions
// FIXME: Use Player, not Clonkranks
C4P.Default(&::DefaultRanks);
// Set name, color, comment
SCopy(LoadResStr("IDS_PLR_NEWCOMMENT"), C4P.Comment, C4MaxComment);
C4P.PrefColor = UnsyncedRandom(8);
C4P.PrefColorDw = C4P.GetPrefColorValue(C4P.PrefColor);
C4P.OldPrefControlStyle = 1;
C4P.OldPrefAutoContextMenu = 1;
C4P.OldPrefControl = 0;
}
const int32_t BetweenElementDist = 2;
// use black fonts here
CStdFont *pUseFont = &C4Startup::Get()->Graphics.BookFont;
CStdFont *pSmallFont = &C4Startup::Get()->Graphics.BookSmallFont;
// get positions
UpdateSize();
C4GUI::ComponentAligner caMain(GetClientRect(), 0, 1, true);
C4GUI::ComponentAligner caButtonArea(caMain.GetFromBottom(C4GUI_ButtonAreaHgt), 0,0);
// dlg title
const char *szTitle;
if (pForPlayer)
{
szTitle = LoadResStr("IDS_DLG_PLAYER2");
}
else
{
szTitle = LoadResStr("IDS_PLR_NEWPLAYER");
}
C4GUI::Label *pLbl = new C4GUI::Label(szTitle, caMain.GetFromTop(pUseFont->GetLineHeight()), ALeft, C4StartupFontClr, pUseFont, false);
AddElement(pLbl);
caMain.ExpandTop(-BetweenElementDist);
// place name label
AddElement(new C4GUI::Label(LoadResStr("IDS_CTL_NAME2"), caMain.GetFromTop(pSmallFont->GetLineHeight()), ALeft, C4StartupFontClr, pSmallFont, false));
// place name edit
pNameEdit = new C4GUI::Edit(caMain.GetFromTop(C4GUI::Edit::GetCustomEditHeight(pUseFont)));
pNameEdit->SetFont(pUseFont);
pNameEdit->SetColors(C4StartupEditBGColor, C4StartupFontClr, C4StartupEditBorderColor);
pNameEdit->InsertText(C4P.PrefName, false);
pNameEdit->SetMaxText(C4MaxName);
AddElement(pNameEdit);
SetFocus(pNameEdit, false);
caMain.ExpandTop(-BetweenElementDist);
int32_t iControlPicSize = C4GUI::ArrowButton::GetDefaultHeight(); // GetGridCell(0,3,0,1,-1,-1,false,2)
int32_t label_hgt = pSmallFont->GetLineHeight();
// place color label
C4GUI::ComponentAligner caColorArea(caMain.GetFromTop(iControlPicSize + BetweenElementDist + label_hgt), 2, 0);
C4GUI::ComponentAligner caPictureArea(caColorArea.GetFromRight(iControlPicSize, iControlPicSize + BetweenElementDist + label_hgt), 2,0);
caColorArea.ExpandLeft(2);
AddElement(new C4GUI::Label(FormatString("%s:", LoadResStr("IDS_CTL_COLOR")).getData(), caColorArea.GetFromTop(label_hgt), ALeft, C4StartupFontClr, pSmallFont, false));
caColorArea.ExpandTop(-BetweenElementDist);
// place picture label
AddElement(new C4GUI::Label(LoadResStr("IDS_CTL_PICTURE"), caPictureArea.GetFromTop(label_hgt), ALeft, C4StartupFontClr, pSmallFont, false));
caPictureArea.ExpandTop(-BetweenElementDist);
// place color controls
C4GUI::Button *pBtn; const char *szTip;
szTip = LoadResStr("IDS_DLGTIP_PLAYERCOLORS");
AddElement(pBtn = new C4GUI::CallbackButton<C4StartupPlrPropertiesDlg, C4GUI::ArrowButton>(C4GUI::ArrowButton::Left, caColorArea.GetFromLeft(C4GUI::ArrowButton::GetDefaultWidth()), &C4StartupPlrPropertiesDlg::OnClrChangeLeft));
pBtn->SetToolTip(szTip);
C4Facet &rfctClrPreviewPic = ::GraphicsResource.fctFlagClr;
pClrPreview = new C4GUI::CallbackButton<C4StartupPlrPropertiesDlg, C4GUI::IconButton>(C4GUI::Ico_None, caColorArea.GetFromLeft(rfctClrPreviewPic.GetWidthByHeight(caColorArea.GetHeight())), 'C', &C4StartupPlrPropertiesDlg::OnClrChangeCustom);
pClrPreview->SetFacet(rfctClrPreviewPic);
AddElement(pClrPreview);
AddElement(pBtn = new C4GUI::CallbackButton<C4StartupPlrPropertiesDlg, C4GUI::ArrowButton>(C4GUI::ArrowButton::Right, caColorArea.GetFromLeft(C4GUI::ArrowButton::GetDefaultWidth()), &C4StartupPlrPropertiesDlg::OnClrChangeRight));
pBtn->SetToolTip(szTip);
if (!C4P.PrefColorDw) C4P.PrefColorDw=0xff;
// Place picture controls
AddElement(pPictureBtn = new C4GUI::CallbackButton<C4StartupPlrPropertiesDlg, C4GUI::IconButton>(C4GUI::Ico_Player, caPictureArea.GetAll(), 'P' /* 2do */, &C4StartupPlrPropertiesDlg::OnPictureBtn));
pPictureBtn->SetToolTip(LoadResStr("IDS_DESC_SELECTAPICTUREANDORLOBBYI"));
UpdateBigIcon();
UpdatePlayerColor(true);
caMain.ExpandTop(-BetweenElementDist);
// place control label
C4GUI::ComponentAligner caControlArea(caMain.GetFromTop(iControlPicSize + label_hgt + BetweenElementDist), 0,0, false);
C4GUI::ComponentAligner caSkinArea(caControlArea.GetFromRight(iControlPicSize + label_hgt + BetweenElementDist), 0,0, false);
AddElement(new C4GUI::Label(FormatString("%s:", LoadResStr("IDS_CTL_CONTROL")).getData(), caControlArea.GetFromTop(label_hgt), ALeft, C4StartupFontClr, pSmallFont, false));
caControlArea.ExpandTop(-BetweenElementDist);
// place clonk style label
AddElement(new C4GUI::Label(LoadResStr("IDS_CTL_CLONKSKIN"), caSkinArea.GetFromTop(label_hgt), ALeft, C4StartupFontClr, pSmallFont, false));
caSkinArea.ExpandTop(-BetweenElementDist);
// place control controls
C4GUI::ComponentAligner caControl(caControlArea.GetFromTop(iControlPicSize), 2,0);
szTip = LoadResStr("IDS_DLGTIP_PLAYERCONTROL");
AddElement(pBtn = new C4GUI::CallbackButton<C4StartupPlrPropertiesDlg, C4GUI::ArrowButton>(C4GUI::ArrowButton::Left, caControl.GetFromLeft(C4GUI::ArrowButton::GetDefaultWidth()), &C4StartupPlrPropertiesDlg::OnCtrlChangeLeft));
pBtn->SetToolTip(szTip);
caControl.ExpandBottom(label_hgt); C4Rect ctrl_name_rect = caControl.GetFromBottom(label_hgt);
C4Facet &rfctCtrlPic = ::GraphicsResource.fctKeyboard; // UpdatePlayerControl() will alternatively set fctGamepad
AddElement(pCtrlImg = new C4GUI::Picture(caControl.GetFromLeft(rfctCtrlPic.GetWidthByHeight(caControl.GetHeight())), true));
pCtrlImg->SetToolTip(szTip);
AddElement(pBtn = new C4GUI::CallbackButton<C4StartupPlrPropertiesDlg, C4GUI::ArrowButton>(C4GUI::ArrowButton::Right, caControl.GetFromLeft(C4GUI::ArrowButton::GetDefaultWidth()), &C4StartupPlrPropertiesDlg::OnCtrlChangeRight));
pBtn->SetToolTip(szTip);
caControl.ExpandLeft(-10);
C4P.OldPrefControl = Clamp<int32_t>(C4P.OldPrefControl, 0, C4MaxControlSet-1);
ctrl_name_lbl = new C4GUI::Label("CtrlName", ctrl_name_rect, ALeft, C4StartupFontClr, pSmallFont, false, false, true);
AddElement(ctrl_name_lbl);
UpdatePlayerControl();
C4GUI::ComponentAligner caSkin(caSkinArea.GetFromTop(iControlPicSize), 2,0);
szTip = LoadResStr("IDS_DLGTIP_PLAYERCREWSKIN");
AddElement(pBtn = new C4GUI::CallbackButton<C4StartupPlrPropertiesDlg, C4GUI::ArrowButton>(C4GUI::ArrowButton::Left, caSkin.GetFromLeft(C4GUI::ArrowButton::GetDefaultWidth()), &C4StartupPlrPropertiesDlg::OnSkinChangeLeft));
pBtn->SetToolTip(szTip);
C4Facet rfctSkinPic = ::GraphicsResource.fctClonkSkin.GetPhase(0);
AddElement(pSkinImg = new C4GUI::Picture(caSkin.GetFromLeft(rfctSkinPic.GetWidthByHeight(caSkin.GetHeight())), true));
pSkinImg->SetToolTip(szTip);
pSkinImg->SetFacet(::GraphicsResource.fctClonkSkin.GetPhase(0));
AddElement(pBtn = new C4GUI::CallbackButton<C4StartupPlrPropertiesDlg, C4GUI::ArrowButton>(C4GUI::ArrowButton::Right, caSkin.GetFromLeft(C4GUI::ArrowButton::GetDefaultWidth()), &C4StartupPlrPropertiesDlg::OnSkinChangeRight));
pBtn->SetToolTip(szTip);
caSkin.ExpandLeft(-10);
UpdatePlayerSkin();
caMain.ExpandTop(-BetweenElementDist);
// place buttons
// OK
C4GUI::Button *pBtnOK = new C4GUI::OKIconButton(C4Rect(147-GetMarginLeft(), 295+35-GetMarginTop(), 54, 33), C4GUI::Ico_None);
AddElement(pBtnOK);
// Cancel
C4GUI::Button *pBtnAbort = new C4GUI::CancelIconButton(C4Rect(317-GetMarginLeft(), 16-GetMarginTop(), 21, 21), C4GUI::Ico_None);
AddElement(pBtnAbort);
// when called from player selection screen: input dlg always closed in the end
// otherwise, modal proc will delete
if (pMainDlg) SetDelOnClose();
}
void C4StartupPlrPropertiesDlg::DrawElement(C4TargetFacet &cgo)
{
C4Startup::Get()->Graphics.fctPlrPropBG.Draw(cgo.Surface, rcBounds.x+cgo.TargetX, rcBounds.y+cgo.TargetY);
}
bool IsColorConflict(DWORD dwClr1, DWORD dwClr2);
void C4StartupPlrPropertiesDlg::UpdatePlayerColor(bool fUpdateSliders)
{
C4P.PrefColorDw = C4P.PrefColorDw | 0xFF000000; // Ensure full opacity
pClrPreview->SetColor(C4P.PrefColorDw);
pPictureBtn->SetColor(C4P.PrefColorDw);
}
void C4StartupPlrPropertiesDlg::OnClrChangeLeft(C4GUI::Control *pBtn)
{
// previous standard color in list
C4P.PrefColor = C4P.PrefColor ? C4P.PrefColor - 1 : 11;
C4P.PrefColorDw = C4PlayerInfoCore::GetPrefColorValue(C4P.PrefColor);
UpdatePlayerColor(true);
}
void C4StartupPlrPropertiesDlg::OnClrChangeCustom(C4GUI::Control *pBtn)
{
GetScreen()->ShowModalDlg(new C4StartupPlrColorPickerDlg(&C4P));
UpdatePlayerColor(true);
}
void C4StartupPlrPropertiesDlg::OnClrChangeRight(C4GUI::Control *pBtn)
{
// next standard color in list
C4P.PrefColor = (C4P.PrefColor + 1) % 12;
C4P.PrefColorDw = C4PlayerInfoCore::GetPrefColorValue(C4P.PrefColor);
UpdatePlayerColor(true);
}
void C4StartupPlrPropertiesDlg::UpdatePlayerControl()
{
C4PlayerControlAssignmentSet *control_set = Game.PlayerControlUserAssignmentSets.GetSetByName(C4P.PrefControl.getData());
if (!control_set) control_set = Game.PlayerControlUserAssignmentSets.GetDefaultSet();
// update keyboard image of selected control
C4Facet fctCtrlPic;
if (control_set) fctCtrlPic = control_set->GetPicture();
pCtrlImg->SetFacet(fctCtrlPic);
if (control_set)
ctrl_name_lbl->SetText(control_set->GetGUIName());
else
ctrl_name_lbl->SetText("???");
}
void C4StartupPlrPropertiesDlg::OnCtrlChangeLeft(C4GUI::Control *pBtn)
{
// previous control set in list
C4PlayerControlAssignmentSet *control_set = Game.PlayerControlUserAssignmentSets.GetSetByName(C4P.PrefControl.getData());
int32_t index = Game.PlayerControlUserAssignmentSets.GetSetIndex(control_set);
if (index < 0) index = 0; // defined control set not found - probably an old CR player file
if (!index--) index = Game.PlayerControlUserAssignmentSets.GetSetCount() - 1;
control_set = Game.PlayerControlUserAssignmentSets.GetSetByIndex(index);
if (control_set) C4P.PrefControl = control_set->GetName();
UpdatePlayerControl();
}
void C4StartupPlrPropertiesDlg::OnCtrlChangeRight(C4GUI::Control *pBtn)
{
// next control set in list
C4PlayerControlAssignmentSet *control_set = Game.PlayerControlUserAssignmentSets.GetSetByName(C4P.PrefControl.getData());
int32_t index = Game.PlayerControlUserAssignmentSets.GetSetIndex(control_set);
if (index < 0) index = 0; // defined control set not found - probably an old CR player file
if (++index >= int32_t(Game.PlayerControlUserAssignmentSets.GetSetCount())) index = 0;
control_set = Game.PlayerControlUserAssignmentSets.GetSetByIndex(index);
if (control_set) C4P.PrefControl = control_set->GetName();
UpdatePlayerControl();
}
void C4StartupPlrPropertiesDlg::UpdatePlayerSkin()
{
pSkinImg->SetFacet(::GraphicsResource.fctClonkSkin.GetPhase(C4P.PrefClonkSkin));
}
void C4StartupPlrPropertiesDlg::OnSkinChangeLeft(C4GUI::Control *pBtn)
{
// previous skin in list
C4P.PrefClonkSkin = C4P.PrefClonkSkin ? C4P.PrefClonkSkin - 1 : 3;
UpdatePlayerSkin();
}
void C4StartupPlrPropertiesDlg::OnSkinChangeRight(C4GUI::Control *pBtn)
{
// next skin in list
C4P.PrefClonkSkin = (C4P.PrefClonkSkin + 1) % 4;
UpdatePlayerSkin();
}
void C4StartupPlrPropertiesDlg::UserClose(bool fOK)
{
// check name validity
if (fOK)
{
StdStrBuf PlrName(pNameEdit->GetText()), Filename;
if (!C4StartupPlrSelDlg::CheckPlayerName(PlrName, Filename, pForPlayer ? &pForPlayer->GetFilename() : nullptr, true)) return;
// Warn that gamepad controls are still unfinished.
C4PlayerControlAssignmentSet *control_set = Game.PlayerControlUserAssignmentSets.GetSetByName(C4P.PrefControl.getData());
if (control_set && control_set->HasGamepad())
{
GetScreen()->ShowMessageModal(
LoadResStr("IDS_DLG_GAMEPADEXPERIMENTAL"),
LoadResStr("IDS_DLG_GAMEPADEXPTITLE"),
C4GUI::MessageDialog::btnOK,
C4GUI::Ico_Gamepad
);
}
}
Close(fOK);
}
void C4StartupPlrPropertiesDlg::OnClosed(bool fOK)
{
if (fOK)
{
// store selected data if desired
StdStrBuf PlrName(pNameEdit->GetText()), Filename;
if (C4StartupPlrSelDlg::CheckPlayerName(PlrName, Filename, pForPlayer ? &pForPlayer->GetFilename() : nullptr, true))
{
SCopy(PlrName.getData(), C4P.PrefName, C4MaxName);
C4Group PlrGroup;
bool fSucc=false;
// existant player: update file
if (pForPlayer)
{
if (!pForPlayer->MoveFilename(Filename.getData()))
GetScreen()->ShowMessage(LoadResStr("IDS_FAIL_RENAME"), "", C4GUI::Ico_Error);
// update bigicon
if (fClearBigIcon || fctNewBigIcon.Surface)
{
C4Group PlrGroup;
if (PlrGroup.Open(Config.AtUserDataPath(Filename.getData())))
{
if (fClearBigIcon || fctNewBigIcon.Surface) PlrGroup.Delete(C4CFN_BigIcon);
if (fctNewBigIcon.Surface) fctNewBigIcon.GetFace().SavePNG(PlrGroup, C4CFN_BigIcon);
if (PlrGroup.Close()) fSucc = true;
if (fClearBigIcon || fctNewBigIcon.Surface) pForPlayer->GrabCustomIcon(fctNewBigIcon);
}
}
else
{
fSucc = true;
}
pForPlayer->UpdateCore(C4P);
// player may have been activated: Make sure any new filename is reflected in participants list
if (pMainDlg) pMainDlg->UpdateActivatedPlayers();
}
else
{
// NewPlayer: Open new player group
if (PlrGroup.Open(Config.AtUserDataPath(Filename.getData()), true))
{
// Do not overwrite (should have been caught earlier anyway)
if (PlrGroup.FindEntry(C4CFN_PlayerInfoCore)) return;
// Save info core
C4P.Save(PlrGroup);
// Add BigIcon
if (fctNewBigIcon.Surface)
{
fctNewBigIcon.GetFace().SavePNG(PlrGroup, C4CFN_BigIcon);
}
// Close group
if (PlrGroup.Close()) fSucc=true;
// update activate button text
if (pMainDlg)
{
pMainDlg->UpdatePlayerList();
pMainDlg->SelectItem(Filename, true);
}
else
{
// no main player selection dialog: This means that this dlg was shown as a creation dialog from the main startup dlg
// Just set the newly created player as current selection
SCopy(Config.AtRelativePath(Filename.getData()), Config.General.Participants, sizeof Config.General.Participants);
}
}
}
if (!fSucc) GetScreen()->ShowErrorMessage(PlrGroup.GetError());
}
}
// Make the dialog go away
Dialog::OnClosed(fOK);
}
bool C4StartupPlrPropertiesDlg::SetNewPicture(C4Surface &srcSfc, C4FacetSurface *trgFct, int32_t iMaxSize, bool fColorize)
{
if (fColorize)
{
C4Surface srcSfcClr;
if (!srcSfcClr.CreateColorByOwner(&srcSfc)) return false;
return trgFct->CopyFromSfcMaxSize(srcSfcClr, iMaxSize, C4P.PrefColorDw);
}
else
{
return trgFct->CopyFromSfcMaxSize(srcSfc, iMaxSize);
}
}
void C4StartupPlrPropertiesDlg::SetNewPicture(const char *szFromFilename)
{
if (!szFromFilename)
{
// If szFromFilename==nullptr, clear bigicon
fClearBigIcon = true;
fctNewBigIcon.Clear();
}
else
{
// else set new bigicon by loading and scaling if necessary.
C4Surface sfcNewPic;
C4Group SrcGrp;
StdStrBuf sParentPath;
GetParentPath(szFromFilename, &sParentPath);
bool fSucc = false;
if (SrcGrp.Open(sParentPath.getData()))
{
if (sfcNewPic.Load(SrcGrp, GetFilename(szFromFilename), false, false, 0))
{
fSucc = true;
if (!SetNewPicture(sfcNewPic, &fctNewBigIcon, C4MaxBigIconSize, true)) fSucc = false;
}
}
if (!fSucc)
{
// error!
GetScreen()->ShowErrorMessage(FormatString(LoadResStr("IDS_PRC_NOGFXFILE"), szFromFilename, SrcGrp.GetError()).getData());
}
}
// update icon
UpdateBigIcon();
}
void C4StartupPlrPropertiesDlg::OnPictureBtn(C4GUI::Control *pBtn)
{
StdStrBuf sNewPic;
if (C4PortraitSelDlg::SelectPortrait(GetScreen(), &sNewPic))
{
SetNewPicture(sNewPic.getData());
}
}
void C4StartupPlrPropertiesDlg::UpdateBigIcon()
{
// new icon?
bool fHasIcon = false;
if (fctNewBigIcon.Surface)
{
pPictureBtn->SetFacet(fctNewBigIcon);
fHasIcon = true;
}
// old icon in existing player?
else if (!fClearBigIcon && pForPlayer)
{
C4Group PlrGroup;
if (PlrGroup.Open(Config.AtUserDataPath(pForPlayer->GetFilename().getData())))
{
if (PlrGroup.FindEntry(C4CFN_BigIcon))
{
if (fctOldBigIcon.Load(PlrGroup, C4CFN_BigIcon, C4FCT_Full, C4FCT_Full, false, 0))
{
pPictureBtn->SetFacet(fctOldBigIcon);
fHasIcon = true;
}
}
}
}
// no icon: Set default
if (!fHasIcon)
{
pPictureBtn->SetFacet(::GraphicsResource.fctPlayerClr);
}
}