forked from Mirrors/openclonk
1991 lines
72 KiB
C++
1991 lines
72 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: Scenario selection dialog
|
|
|
|
#include "C4Include.h"
|
|
#include "gui/C4StartupScenSelDlg.h"
|
|
|
|
#include "c4group/C4ComponentHost.h"
|
|
#include "c4group/C4Components.h"
|
|
#include "game/C4Application.h"
|
|
#include "graphics/C4Draw.h"
|
|
#include "graphics/C4GraphicsResource.h"
|
|
#include "gui/C4FileSelDlg.h"
|
|
#include "gui/C4GameDialogs.h"
|
|
#include "gui/C4GameOptions.h"
|
|
#include "gui/C4MouseControl.h"
|
|
#include "gui/C4StartupMainDlg.h"
|
|
#include "gui/C4StartupNetDlg.h"
|
|
#include "network/C4Network2Dialogs.h"
|
|
|
|
// singleton
|
|
C4StartupScenSelDlg *C4StartupScenSelDlg::pInstance=nullptr;
|
|
|
|
|
|
// ----------------------------------------------------------------
|
|
// Map folder data
|
|
|
|
void C4MapFolderData::Scenario::CompileFunc(StdCompiler *pComp)
|
|
{
|
|
pComp->Value(mkNamingAdapt( sFilename, "File" , StdStrBuf()));
|
|
pComp->Value(mkNamingAdapt( sBaseImage, "BaseImage" , StdStrBuf()));
|
|
pComp->Value(mkNamingAdapt( sOverlayImage, "OverlayImage" , StdStrBuf()));
|
|
pComp->Value(mkNamingAdapt( rcOverlayPos, "Area", C4Rect()));
|
|
pComp->Value(mkNamingAdapt( sTitle, "Title" , StdStrBuf()));
|
|
pComp->Value(mkNamingAdapt( iTitleFontSize, "TitleFontSize", 20));
|
|
pComp->Value(mkNamingAdapt( dwTitleInactClr, "TitleColorInactive", 0x7fffffffu));
|
|
pComp->Value(mkNamingAdapt( dwTitleActClr, "TitleColorActive", 0x0fffffffu));
|
|
pComp->Value(mkNamingAdapt( iTitleOffX, "TitleOffX", 0));
|
|
pComp->Value(mkNamingAdapt( iTitleOffY, "TitleOffY", 0));
|
|
pComp->Value(mkNamingAdapt( byTitleAlign, "TitleAlign", ACenter));
|
|
pComp->Value(mkNamingAdapt( fTitleBookFont, "TitleUseBookFont", true));
|
|
pComp->Value(mkNamingAdapt( fImgDump, "ImageDump", false)); // developer help
|
|
}
|
|
|
|
void C4MapFolderData::AccessGfx::CompileFunc(StdCompiler *pComp)
|
|
{
|
|
pComp->Value(mkNamingAdapt( sPassword, "Access", StdStrBuf()));
|
|
pComp->Value(mkNamingAdapt( sOverlayImage, "OverlayImage" , StdStrBuf()));
|
|
pComp->Value(mkNamingAdapt( rcOverlayPos, "Area", C4Rect()));
|
|
}
|
|
|
|
C4MapFolderData::MapPic::MapPic(const FLOAT_RECT &rcfBounds, const C4Facet &rfct) : C4GUI::Picture(C4Rect(rcfBounds), false), rcfBounds(rcfBounds)
|
|
{
|
|
// ctor
|
|
SetFacet(rfct);
|
|
SetToolTip(LoadResStr("IDS_MSG_MAP_DESC"));
|
|
}
|
|
|
|
void C4MapFolderData::MapPic::MouseInput(C4GUI::CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam)
|
|
{
|
|
typedef C4GUI::Picture Parent;
|
|
Parent::MouseInput(rMouse, iButton, iX, iY, dwKeyParam);
|
|
// input: mouse movement or buttons - deselect everything if clicked
|
|
if (iButton == C4MC_Button_LeftDown && C4StartupScenSelDlg::pInstance)
|
|
{
|
|
C4StartupScenSelDlg::pInstance->DeselectAll();
|
|
}
|
|
}
|
|
|
|
void C4MapFolderData::MapPic::DrawElement(C4TargetFacet &cgo)
|
|
{
|
|
// get drawing bounds
|
|
float x0 = rcfBounds.left + cgo.TargetX, y0 = rcfBounds.top + cgo.TargetY;
|
|
// draw the image
|
|
GetFacet().DrawXFloat(cgo.Surface, x0, y0, rcfBounds.right-rcfBounds.left, rcfBounds.bottom-rcfBounds.top);
|
|
}
|
|
|
|
void C4MapFolderData::Clear()
|
|
{
|
|
fCoordinatesAdjusted = false;
|
|
fctBackgroundPicture.Clear();
|
|
pScenarioFolder = nullptr;
|
|
pSelectedEntry = nullptr;
|
|
pSelectionInfoBox = nullptr;
|
|
rcScenInfoArea.Set(0,0,0,0);
|
|
MinResX=MinResY=0;
|
|
fUseFullscreenMap=false;
|
|
int i;
|
|
for (i=0; i<iScenCount; ++i) delete ppScenList[i];
|
|
iScenCount=0;
|
|
delete [] ppScenList; ppScenList=nullptr;
|
|
for (i=0; i<iAccessGfxCount; ++i) delete ppAccessGfxList[i];
|
|
iAccessGfxCount=0;
|
|
delete [] ppAccessGfxList; ppAccessGfxList=nullptr;
|
|
pMainDlg = nullptr;
|
|
}
|
|
|
|
bool C4MapFolderData::Load(C4Group &hGroup, C4ScenarioListLoader::Folder *pScenLoaderFolder)
|
|
{
|
|
// clear previous
|
|
Clear();
|
|
// load localization info
|
|
C4LangStringTable LangTable;
|
|
bool fHasLangTable = C4Language::LoadComponentHost(&LangTable, hGroup, C4CFN_ScriptStringTbl, Config.General.LanguageEx);
|
|
// load core data
|
|
StdStrBuf Buf;
|
|
if (!hGroup.LoadEntryString(C4CFN_MapFolderData, &Buf)) return false;
|
|
if (fHasLangTable) LangTable.ReplaceStrings(Buf);
|
|
if (!CompileFromBuf_LogWarn<StdCompilerINIRead>(mkNamingAdapt(*this, "FolderMap"), Buf, C4CFN_MapFolderData)) return false;
|
|
// check resolution requirement
|
|
if (MinResX && MinResX>C4GUI::GetScreenWdt()) return false;
|
|
if (MinResY && MinResY>C4GUI::GetScreenHgt()) return false;
|
|
// load images
|
|
if (!fctBackgroundPicture.Load(hGroup, C4CFN_MapFolderBG, C4FCT_Full, C4FCT_Full, false, 0))
|
|
{
|
|
DebugLogF(R"(C4MapFolderData::Load(%s): Could not load background graphic "%s")", hGroup.GetName(), C4CFN_MapFolderBG);
|
|
return false;
|
|
}
|
|
int i;
|
|
for (i=0; i<iScenCount; ++i)
|
|
{
|
|
// init scenario entry stuff
|
|
Scenario *pScen = ppScenList[i];
|
|
pScen->pScenEntry = pScenLoaderFolder->FindEntryByName(pScen->sFilename.getData());
|
|
pScen->pBtn = nullptr;
|
|
pScen->sTitle.Replace("TITLE", pScen->pScenEntry ? pScen->pScenEntry->GetName().getData() : "<c ff0000>ERROR</c>" /* scenario not loaded; title cannot be referenced */);
|
|
// developer image dump
|
|
if (pScen->fImgDump)
|
|
{
|
|
C4FacetSurface fctDump; bool fSuccess=false;
|
|
if (fctDump.Create(pScen->rcOverlayPos.Wdt, pScen->rcOverlayPos.Hgt, C4FCT_Full, C4FCT_Full))
|
|
{
|
|
pDraw->Blit(fctBackgroundPicture.Surface,
|
|
(float) pScen->rcOverlayPos.x, (float) pScen->rcOverlayPos.y,
|
|
(float) pScen->rcOverlayPos.Wdt, (float) pScen->rcOverlayPos.Hgt,
|
|
fctDump.Surface,
|
|
0, 0,
|
|
fctDump.Wdt, fctDump.Hgt);
|
|
fSuccess = fctDump.Surface->SavePNG(pScen->sBaseImage.getData(), true, false, false);
|
|
}
|
|
if (!fSuccess)
|
|
DebugLogF(R"(C4MapFolderData::Load(%s): Could not dump graphic "%s")", hGroup.GetName(), pScen->sBaseImage.getData());
|
|
continue;
|
|
}
|
|
// load images
|
|
if (pScen->sBaseImage.getLength()>0) if (!pScen->fctBase.Load(hGroup, pScen->sBaseImage.getData(), C4FCT_Full, C4FCT_Full, false, 0))
|
|
{
|
|
DebugLogF(R"(C4MapFolderData::Load(%s): Could not load base graphic "%s")", hGroup.GetName(), pScen->sBaseImage.getData());
|
|
return false;
|
|
}
|
|
if (pScen->sOverlayImage.getLength()>0) if (!pScen->fctOverlay.Load(hGroup, pScen->sOverlayImage.getData(), C4FCT_Full, C4FCT_Full, false, 0))
|
|
{
|
|
DebugLogF(R"(C4MapFolderData::Load(%s): Could not load graphic "%s")", hGroup.GetName(), pScen->sOverlayImage.getData());
|
|
return false;
|
|
}
|
|
}
|
|
for (i=0; i<iAccessGfxCount; ++i)
|
|
{
|
|
AccessGfx *pGfx= ppAccessGfxList[i];
|
|
if (pGfx->sOverlayImage.getLength()>0) if (!pGfx->fctOverlay.Load(hGroup, pGfx->sOverlayImage.getData(), C4FCT_Full, C4FCT_Full, false, 0))
|
|
{
|
|
DebugLogF(R"(C4MapFolderData::Load(%s): Could not load graphic "%s")", hGroup.GetName(), pGfx->sOverlayImage.getData());
|
|
return false;
|
|
}
|
|
}
|
|
// all loaded
|
|
pScenarioFolder = pScenLoaderFolder;
|
|
return true;
|
|
}
|
|
|
|
void C4MapFolderData::CompileFunc(StdCompiler *pComp)
|
|
{
|
|
// core values
|
|
pComp->Value(mkNamingAdapt( rcScenInfoArea, "ScenInfoArea", C4Rect(0,0,0,0)));
|
|
pComp->Value(mkNamingAdapt( MinResX, "MinResX", 0));
|
|
pComp->Value(mkNamingAdapt( MinResY, "MinResY", 0));
|
|
pComp->Value(mkNamingAdapt( fUseFullscreenMap,"FullscreenBG", false));
|
|
// compile scenario list
|
|
int32_t iOldScenCount = iScenCount;
|
|
pComp->Value(mkNamingCountAdapt(iScenCount, "Scenario"));
|
|
if (pComp->isDeserializer())
|
|
{
|
|
while (iOldScenCount--) delete ppScenList[iOldScenCount];
|
|
delete [] ppScenList;
|
|
if (iScenCount)
|
|
{
|
|
ppScenList = new Scenario *[iScenCount];
|
|
memset(ppScenList, 0, sizeof(Scenario *)*iScenCount);
|
|
}
|
|
else
|
|
ppScenList = nullptr;
|
|
}
|
|
if (iScenCount)
|
|
{
|
|
mkPtrAdaptNoNull(*ppScenList);
|
|
pComp->Value(mkNamingAdapt(mkArrayAdaptMap(ppScenList, iScenCount, mkPtrAdaptNoNull<Scenario>), "Scenario"));
|
|
}
|
|
// compile access gfx list
|
|
int32_t iOldAccesGfxCount = iAccessGfxCount;
|
|
pComp->Value(mkNamingCountAdapt(iAccessGfxCount, "AccessGfx"));
|
|
if (pComp->isDeserializer())
|
|
{
|
|
while (iOldAccesGfxCount--) delete ppAccessGfxList[iOldAccesGfxCount];
|
|
delete [] ppAccessGfxList;
|
|
if (iAccessGfxCount)
|
|
{
|
|
ppAccessGfxList = new AccessGfx *[iAccessGfxCount];
|
|
memset(ppAccessGfxList, 0, sizeof(AccessGfx *)*iAccessGfxCount);
|
|
}
|
|
else
|
|
ppAccessGfxList = nullptr;
|
|
}
|
|
if (iAccessGfxCount)
|
|
{
|
|
mkPtrAdaptNoNull(*ppAccessGfxList);
|
|
pComp->Value(mkNamingAdapt(mkArrayAdaptMap(ppAccessGfxList, iAccessGfxCount, mkPtrAdaptNoNull<AccessGfx>), "AccessGfx"));
|
|
}
|
|
}
|
|
|
|
void C4MapFolderData::ConvertFacet2ScreenCoord(const C4Rect &rc, FLOAT_RECT *pfrc, float fBGZoomX, float fBGZoomY, int iOffX, int iOffY)
|
|
{
|
|
pfrc->left = (fBGZoomX * rc.x) + iOffX;
|
|
pfrc->top = (fBGZoomY * rc.y) + iOffY;
|
|
pfrc->right = pfrc->left + (fBGZoomX * rc.Wdt);
|
|
pfrc->bottom = pfrc->top + (fBGZoomY * rc.Hgt);
|
|
}
|
|
|
|
void C4MapFolderData::ConvertFacet2ScreenCoord(int32_t *piValue, float fBGZoom, int iOff)
|
|
{
|
|
*piValue = int32_t(floorf(fBGZoom * *piValue + 0.5f)) + iOff;
|
|
}
|
|
|
|
void C4MapFolderData::ConvertFacet2ScreenCoord(C4Rect &rcMapArea, bool fAspect)
|
|
{
|
|
if (!fctBackgroundPicture.Wdt || !fctBackgroundPicture.Hgt) return; // invalid BG - should not happen
|
|
// get zoom of background image
|
|
float fBGZoomX = 1.0f, fBGZoomY = 1.0f; int iOffX=0, iOffY=0;
|
|
if (fAspect)
|
|
{
|
|
if (fctBackgroundPicture.Wdt * rcMapArea.Hgt > rcMapArea.Wdt * fctBackgroundPicture.Hgt)
|
|
{
|
|
// background image is limited by width
|
|
fBGZoomX = fBGZoomY = (float) rcMapArea.Wdt / fctBackgroundPicture.Wdt;
|
|
iOffY = std::max<int>(0, (int)(rcMapArea.Hgt - (fBGZoomX * fctBackgroundPicture.Hgt)))/2;
|
|
}
|
|
else
|
|
{
|
|
// background image is limited by height
|
|
fBGZoomX = fBGZoomY = (float) rcMapArea.Hgt / fctBackgroundPicture.Hgt;
|
|
iOffX = std::max<int>(0, (int)(rcMapArea.Wdt - (fBGZoomY * fctBackgroundPicture.Wdt)))/2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// do not keep aspect: Independant X and Y zoom
|
|
fBGZoomX = (float) rcMapArea.Wdt / fctBackgroundPicture.Wdt;;
|
|
fBGZoomY = (float) rcMapArea.Hgt / fctBackgroundPicture.Hgt;;
|
|
}
|
|
iOffX -= rcMapArea.x; iOffY -= rcMapArea.y;
|
|
C4Rect rcBG; rcBG.Set(0,0,fctBackgroundPicture.Wdt, fctBackgroundPicture.Hgt);
|
|
ConvertFacet2ScreenCoord(rcBG, &rcfBG, fBGZoomX, fBGZoomY, iOffX, iOffY);
|
|
// default for scenario info area: 1/3rd of right area
|
|
if (!rcScenInfoArea.Wdt)
|
|
rcScenInfoArea.Set((int32_t)(fctBackgroundPicture.Wdt*2/3), (int32_t)(fctBackgroundPicture.Hgt/16), (int32_t)(fctBackgroundPicture.Wdt/3), (int32_t)(fctBackgroundPicture.Hgt*7/8));
|
|
// assume all facet coordinates are referring to background image zoom; convert them to screen coordinates by applying zoom and offset
|
|
FLOAT_RECT rcfScenInfoArea;
|
|
ConvertFacet2ScreenCoord(rcScenInfoArea, &rcfScenInfoArea, fBGZoomX, fBGZoomY, iOffX, iOffY);
|
|
rcScenInfoArea.x = (int32_t) rcfScenInfoArea.left; rcScenInfoArea.y = (int32_t) rcfScenInfoArea.top;
|
|
rcScenInfoArea.Wdt = (int32_t) (rcfScenInfoArea.right - rcfScenInfoArea.left);
|
|
rcScenInfoArea.Hgt = (int32_t) (rcfScenInfoArea.bottom - rcfScenInfoArea.top);
|
|
int i;
|
|
for (i=0; i<iScenCount; ++i)
|
|
{
|
|
Scenario *pScen = ppScenList[i];
|
|
ConvertFacet2ScreenCoord(pScen->rcOverlayPos, &(pScen->rcfOverlayPos), fBGZoomX, fBGZoomY, iOffX, iOffY);
|
|
// title sizes
|
|
ConvertFacet2ScreenCoord(&(pScen->iTitleFontSize), fBGZoomY, 0);
|
|
// title position: Relative to title rect; so do not add offset here
|
|
ConvertFacet2ScreenCoord(&(pScen->iTitleOffX), fBGZoomX, 0);
|
|
ConvertFacet2ScreenCoord(&(pScen->iTitleOffY), fBGZoomY, 0);
|
|
}
|
|
for (i=0; i<iAccessGfxCount; ++i) ConvertFacet2ScreenCoord(ppAccessGfxList[i]->rcOverlayPos, &(ppAccessGfxList[i]->rcfOverlayPos), fBGZoomX, fBGZoomY, iOffX, iOffY);
|
|
// done
|
|
fCoordinatesAdjusted = true;
|
|
}
|
|
|
|
void C4MapFolderData::CreateGUIElements(C4StartupScenSelDlg *pMainDlg, C4GUI::Window &rContainer)
|
|
{
|
|
this->pMainDlg = pMainDlg;
|
|
// convert all coordinates to match the container sizes
|
|
// do this only once; assume container won't change between loads
|
|
if (!fCoordinatesAdjusted)
|
|
{
|
|
if (!fUseFullscreenMap)
|
|
ConvertFacet2ScreenCoord(rContainer.GetClientRect(), true);
|
|
else
|
|
{
|
|
C4Rect rcMapRect = pMainDlg->GetBounds();
|
|
rContainer.ClientPos2ScreenPos(rcMapRect.x, rcMapRect.y);
|
|
ConvertFacet2ScreenCoord(rcMapRect, false);
|
|
}
|
|
}
|
|
// empty any previous stuff in container
|
|
while (rContainer.GetFirst()) delete rContainer.GetFirst();
|
|
// create background image
|
|
if (!fUseFullscreenMap)
|
|
rContainer.AddElement(new MapPic(rcfBG, fctBackgroundPicture));
|
|
else
|
|
{
|
|
pMainDlg->SetBackground(&fctBackgroundPicture);
|
|
}
|
|
// create mission access overlays
|
|
int i;
|
|
for (i=0; i<iAccessGfxCount; ++i)
|
|
{
|
|
AccessGfx *pGfx = ppAccessGfxList[i];
|
|
const char *szPassword = pGfx->sPassword.getData();
|
|
if (!szPassword || !*szPassword || SIsModule(Config.General.MissionAccess, szPassword))
|
|
{
|
|
// ACCESS TO GFX GRANTED: draw it
|
|
rContainer.AddElement(new MapPic(pGfx->rcfOverlayPos, pGfx->fctOverlay));
|
|
}
|
|
}
|
|
// create buttons for scenarios
|
|
C4GUI::Button *pBtnFirst = nullptr;
|
|
for (i=0; i<iScenCount; ++i)
|
|
{
|
|
Scenario *pScen = ppScenList[i];
|
|
if (pScen->pScenEntry && !pScen->pScenEntry->HasMissionAccess())
|
|
{
|
|
// no access to this scenario: Do not create a button at all; not even base image. The scenario is "invisible".
|
|
}
|
|
else
|
|
{
|
|
C4GUI::CallbackButtonEx<C4StartupScenSelDlg, C4GUI::FacetButton> *pBtn = new C4GUI::CallbackButtonEx<C4StartupScenSelDlg, C4GUI::FacetButton>
|
|
(pScen->fctBase, pScen->fctOverlay, pScen->rcfOverlayPos, 0, pMainDlg, &C4StartupScenSelDlg::OnButtonScenario);
|
|
ppScenList[i]->pBtn = pBtn;
|
|
if (pScen->pScenEntry)
|
|
pBtn->SetToolTip(FormatString(LoadResStr("IDS_MSG_MAP_STARTSCEN"), pScen->pScenEntry->GetName().getData()).getData());
|
|
if (pScen->sTitle.getLength()>0)
|
|
{
|
|
pBtn->SetText(pScen->sTitle.getData());
|
|
pBtn->SetTextColors(pScen->dwTitleInactClr, pScen->dwTitleActClr);
|
|
pBtn->SetTextPos(pScen->iTitleOffX, pScen->iTitleOffY, pScen->byTitleAlign);
|
|
CStdFont *pUseFont; float fFontZoom=1.0f;
|
|
if (pScen->fTitleBookFont)
|
|
pUseFont = &(C4Startup::Get()->Graphics.GetBlackFontByHeight(pScen->iTitleFontSize, &fFontZoom));
|
|
else
|
|
pUseFont = &(::GraphicsResource.GetFontByHeight(pScen->iTitleFontSize, &fFontZoom));
|
|
if (Inside<float>(fFontZoom, 0.8f, 1.25f)) fFontZoom = 1.0f; // some tolerance for font zoom
|
|
pBtn->SetTextFont(pUseFont, fFontZoom);
|
|
}
|
|
rContainer.AddElement(pBtn);
|
|
if (!pBtnFirst) pBtnFirst = pBtn;
|
|
}
|
|
}
|
|
// create scenario info listbox
|
|
pSelectionInfoBox = new C4GUI::TextWindow(rcScenInfoArea,
|
|
C4StartupScenSel_TitlePictureWdt+2*C4StartupScenSel_TitleOverlayMargin, C4StartupScenSel_TitlePictureHgt+2*C4StartupScenSel_TitleOverlayMargin,
|
|
C4StartupScenSel_TitlePicturePadding, 100, 4096, nullptr, true, &C4Startup::Get()->Graphics.fctScenSelTitleOverlay, C4StartupScenSel_TitleOverlayMargin);
|
|
pSelectionInfoBox->SetDecoration(false, false, &C4Startup::Get()->Graphics.sfctBookScroll, true);
|
|
rContainer.AddElement(pSelectionInfoBox);
|
|
}
|
|
|
|
void C4MapFolderData::OnButtonScenario(C4GUI::Control *pEl)
|
|
{
|
|
// get associated scenario entry
|
|
int i;
|
|
for (i=0; i<iScenCount; ++i)
|
|
if (pEl == ppScenList[i]->pBtn)
|
|
break;
|
|
if (i == iScenCount) return;
|
|
// select the associated entry
|
|
pSelectedEntry = ppScenList[i]->pScenEntry;
|
|
}
|
|
|
|
void C4MapFolderData::ResetSelection()
|
|
{
|
|
pSelectedEntry = nullptr;
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------
|
|
// Scenario list loader
|
|
|
|
// ------------------------------------
|
|
// Entry
|
|
|
|
C4ScenarioListLoader::Entry::Entry(class C4ScenarioListLoader *pLoader, Folder *pParent) : pLoader(pLoader), pNext(nullptr), pParent(pParent), fBaseLoaded(false), fExLoaded(false)
|
|
{
|
|
// ctor: Put into parent tree node
|
|
if (pParent)
|
|
{
|
|
pNext = pParent->pFirst;
|
|
pParent->pFirst = this;
|
|
}
|
|
iIconIndex = -1;
|
|
iDifficulty = 0;
|
|
iFolderIndex = 0;
|
|
}
|
|
|
|
C4ScenarioListLoader::Entry::~Entry()
|
|
{
|
|
// dtor: unlink from parent list (MUST be in there)
|
|
if (pParent)
|
|
{
|
|
Entry **ppCheck = &(pParent->pFirst);
|
|
while (*ppCheck != this)
|
|
{
|
|
ppCheck = &(*ppCheck)->pNext;
|
|
}
|
|
*ppCheck = pNext;
|
|
}
|
|
}
|
|
|
|
|
|
bool C4ScenarioListLoader::Entry::Load(C4Group *pFromGrp, const StdStrBuf *psFilename, bool fLoadEx)
|
|
{
|
|
// nothing to do if already loaded
|
|
if (fBaseLoaded && (fExLoaded || !fLoadEx)) return true;
|
|
C4Group Group;
|
|
// group specified: Load as child
|
|
if (pFromGrp)
|
|
{
|
|
assert(psFilename);
|
|
if (!Group.OpenAsChild(pFromGrp, psFilename->getData())) return false;
|
|
// set FN by complete entry name
|
|
this->sFilename.Take(Group.GetFullName());
|
|
}
|
|
else
|
|
{
|
|
// set FN by complete entry name
|
|
if (psFilename) this->sFilename.Copy(*psFilename);
|
|
// no parent group: Direct load from filename
|
|
if (!Group.Open(sFilename.getData())) return false;
|
|
}
|
|
// okay; load standard stuff from group
|
|
bool fNameLoaded=false, fIconLoaded=false;
|
|
if (fBaseLoaded)
|
|
{
|
|
fNameLoaded = fIconLoaded = true;
|
|
}
|
|
else
|
|
{
|
|
// Set default name as filename without extension
|
|
sName.Copy(GetFilename(sFilename.getData()));
|
|
char *szBuf = sName.GrabPointer();
|
|
RemoveExtension(szBuf);
|
|
sName.Take(szBuf);
|
|
// load entry specific stuff that's in the front of the group
|
|
if (!LoadCustomPre(Group))
|
|
return false;
|
|
// Load entry name
|
|
C4ComponentHost DefNames;
|
|
if (C4Language::LoadComponentHost(&DefNames, Group, C4CFN_Title, Config.General.LanguageEx))
|
|
if (DefNames.GetLanguageString(Config.General.LanguageEx, sName))
|
|
fNameLoaded = true;
|
|
// load entry icon
|
|
if (Group.FindEntry(C4CFN_IconPNG) && fctIcon.Load(Group, C4CFN_IconPNG, C4FCT_Full, C4FCT_Full, false, 0))
|
|
fIconLoaded = true;
|
|
else
|
|
{
|
|
C4FacetSurface fctTemp;
|
|
if (Group.FindEntry(C4CFN_ScenarioIcon) && fctTemp.Load(Group, C4CFN_ScenarioIcon, C4FCT_Full, C4FCT_Full, true, 0))
|
|
{
|
|
// old style icon: Blit it on a pieace of paper
|
|
fctTemp.Surface->Lock();
|
|
for (int y=0; y<fctTemp.Hgt; ++y)
|
|
for (int x=0; x<fctTemp.Wdt; ++x)
|
|
{
|
|
uint32_t dwPix = fctTemp.Surface->GetPixDw(x,y, false);
|
|
// transparency has some tolerance...
|
|
if (Inside<uint8_t>(dwPix & 0xff, 0xb8, 0xff))
|
|
if (Inside<uint8_t>((dwPix>>0x08) & 0xff, 0x00, 0x0f))
|
|
if (Inside<uint8_t>((dwPix>>0x10) & 0xff, 0xb8, 0xff))
|
|
fctTemp.Surface->SetPixDw(x,y,0x00ffffff);
|
|
}
|
|
fctTemp.Surface->Unlock();
|
|
int iIconSize = C4Startup::Get()->Graphics.fctScenSelIcons.Hgt;
|
|
fctIcon.Create(iIconSize, iIconSize, C4FCT_Full, C4FCT_Full);
|
|
C4Startup::Get()->Graphics.fctScenSelIcons.GetPhase(C4StartupScenSel_DefaultIcon_OldIconBG).Draw(fctIcon);
|
|
fctTemp.Draw(fctIcon.Surface, (fctIcon.Wdt-fctTemp.Wdt)/2, (fctIcon.Hgt-fctTemp.Hgt)/2);
|
|
fctTemp.Clear();
|
|
fIconLoaded = true;
|
|
}
|
|
}
|
|
// load any entryx-type-specific custom data (e.g. fallbacks for scenario title, and icon)
|
|
if (!LoadCustom(Group, fNameLoaded, fIconLoaded)) return false;
|
|
fBaseLoaded = true;
|
|
}
|
|
// load extended stuff: title picture
|
|
if (fLoadEx && !fExLoaded)
|
|
{
|
|
// load desc
|
|
C4ComponentHost DefDesc;
|
|
if (C4Language::LoadComponentHost(&DefDesc, Group, C4CFN_ScenarioDesc, Config.General.LanguageEx))
|
|
{
|
|
sDesc.Copy(DefDesc.GetData());
|
|
}
|
|
// load title
|
|
fctTitle.Load(Group, C4CFN_ScenarioTitle,C4FCT_Full,C4FCT_Full,false,true);
|
|
fExLoaded = true;
|
|
// load version
|
|
Group.LoadEntryString(C4CFN_Version, &sVersion);
|
|
}
|
|
// done, success
|
|
return true;
|
|
}
|
|
|
|
// helper func: Recursive check whether a directory contains a .ocs or .ocf file
|
|
bool DirContainsScenarios(const char *szDir)
|
|
{
|
|
// Ignore object and group folders to avoid descending e.g. deep into unpacked Objects.ocd
|
|
if (WildcardMatch(C4CFN_DefFiles, szDir) || WildcardMatch(C4CFN_GenericGroupFiles, szDir))
|
|
{
|
|
return false;
|
|
}
|
|
// create iterator on free store to avoid stack overflow with deeply recursed folders
|
|
DirectoryIterator *pIter = new DirectoryIterator(szDir);
|
|
const char *szChildFilename;
|
|
for (; (szChildFilename = **pIter); ++*pIter)
|
|
{
|
|
// Ignore directory navigation entries and CVS folders
|
|
if (C4Group_TestIgnore(szChildFilename)) continue;
|
|
if (WildcardMatch(C4CFN_ScenarioFiles, szChildFilename) || WildcardMatch(C4CFN_FolderFiles, szChildFilename)) break;
|
|
if (DirectoryExists(szChildFilename))
|
|
if (DirContainsScenarios(szChildFilename))
|
|
break;
|
|
}
|
|
delete pIter;
|
|
// return true if loop was broken, in which case a matching entry was found
|
|
return !!szChildFilename;
|
|
}
|
|
|
|
C4ScenarioListLoader::Entry *C4ScenarioListLoader::Entry::CreateEntryForFile(const StdStrBuf &sFilename, C4ScenarioListLoader *pLoader, Folder *pParent)
|
|
{
|
|
// determine entry type by file type
|
|
const char *szFilename = sFilename.getData();
|
|
if (!szFilename || !*szFilename) return nullptr;
|
|
if (WildcardMatch(C4CFN_ScenarioFiles, sFilename.getData())) return new Scenario(pLoader, pParent);
|
|
if (WildcardMatch(C4CFN_FolderFiles, sFilename.getData())) return new SubFolder(pLoader, pParent);
|
|
// regular, open folder (C4Group-packed folders without extensions are not regarded, because they could contain anything!)
|
|
const char *szExt = GetExtension(szFilename);
|
|
if ((!szExt || !*szExt) && DirectoryExists(sFilename.getData()))
|
|
{
|
|
// open folders only if they contain a scenario or folder
|
|
if (DirContainsScenarios(szFilename))
|
|
return new RegularFolder(pLoader, pParent);
|
|
}
|
|
// type not recognized
|
|
return nullptr;
|
|
}
|
|
|
|
bool C4ScenarioListLoader::Entry::RenameTo(const char *szNewName)
|
|
{
|
|
// change name+filename
|
|
// some name sanity validation
|
|
if (!szNewName || !*szNewName) return false;
|
|
if (SEqual(szNewName, sName.getData())) return true;
|
|
char fn[_MAX_PATH+1];
|
|
SCopy(szNewName, fn, _MAX_PATH);
|
|
// generate new file name
|
|
MakeFilenameFromTitle(fn);
|
|
if (!*fn) return false;
|
|
const char *szExt = GetDefaultExtension();
|
|
if (szExt) { SAppend(".", fn, _MAX_PATH); SAppend(szExt, fn, _MAX_PATH); }
|
|
char fullfn[_MAX_PATH+1];
|
|
SCopy(sFilename.getData(), fullfn, _MAX_PATH);
|
|
char *fullfn_fn = GetFilename(fullfn);
|
|
SCopy(fn, fullfn_fn, _MAX_PATH - (fullfn_fn - fullfn));
|
|
StdCopyStrBuf strErr(LoadResStr("IDS_FAIL_RENAME"));
|
|
// check if a rename is due
|
|
if (!ItemIdentical(sFilename.getData(), fullfn))
|
|
{
|
|
// check for duplicate filename
|
|
if (ItemExists(fullfn))
|
|
{
|
|
StdStrBuf sMsg; sMsg.Format(LoadResStr("IDS_ERR_FILEEXISTS"), fullfn);
|
|
::pGUI->ShowMessageModal(sMsg.getData(), strErr.getData(), C4GUI::MessageDialog::btnOK, C4GUI::Ico_Error);
|
|
return false;
|
|
}
|
|
// OK; then rename
|
|
if (!C4Group_MoveItem(sFilename.getData(), fullfn, true))
|
|
{
|
|
StdStrBuf sMsg; sMsg.Format(LoadResStr("IDS_ERR_RENAMEFILE"), sFilename.getData(), fullfn);
|
|
::pGUI->ShowMessageModal(sMsg.getData(), strErr.getData(), C4GUI::MessageDialog::btnOK, C4GUI::Ico_Error);
|
|
return false;
|
|
}
|
|
sFilename.Copy(fullfn);
|
|
}
|
|
// update real name in group, if this is a group
|
|
if (C4Group_IsGroup(fullfn))
|
|
{
|
|
C4Group Grp;
|
|
if (!Grp.Open(fullfn))
|
|
{
|
|
StdStrBuf sMsg; sMsg.Format(LoadResStr("IDS_ERR_OPENFILE"), sFilename.getData(), Grp.GetError());
|
|
::pGUI->ShowMessageModal(sMsg.getData(), strErr.getData(), C4GUI::MessageDialog::btnOK, C4GUI::Ico_Error);
|
|
return false;
|
|
}
|
|
if (!Grp.Delete(C4CFN_Title))
|
|
{
|
|
StdStrBuf sMsg; sMsg.Format(LoadResStr("IDS_ERR_DELOLDTITLE"), sFilename.getData(), Grp.GetError());
|
|
::pGUI->ShowMessageModal(sMsg.getData(), strErr.getData(), C4GUI::MessageDialog::btnOK, C4GUI::Ico_Error);
|
|
return false;
|
|
}
|
|
if (!SetTitleInGroup(Grp, szNewName)) return false;
|
|
if (!Grp.Close())
|
|
{
|
|
StdStrBuf sMsg; sMsg.Format(LoadResStr("IDS_ERR_WRITENEWTITLE"), sFilename.getData(), Grp.GetError());
|
|
::pGUI->ShowMessageModal(sMsg.getData(), strErr.getData(), C4GUI::MessageDialog::btnOK, C4GUI::Ico_Error);
|
|
return false;
|
|
}
|
|
}
|
|
// update title
|
|
sName.Copy(szNewName);
|
|
// done
|
|
return true;
|
|
}
|
|
|
|
bool C4ScenarioListLoader::Entry::SetTitleInGroup(C4Group &rGrp, const char *szNewTitle)
|
|
{
|
|
// default for group files: Create a title text file and set the title in there
|
|
// no title needed if filename is sufficient - except for scenarios, where a Scenario.txt could overwrite the title
|
|
if (!IsScenario())
|
|
{
|
|
StdStrBuf sNameByFile; sNameByFile.Copy(GetFilename(sFilename.getData()));
|
|
char *szBuf = sNameByFile.GrabPointer();
|
|
RemoveExtension(szBuf);
|
|
sNameByFile.Take(szBuf);
|
|
if (SEqual(szNewTitle, sNameByFile.getData())) return true;
|
|
}
|
|
// okay, make a title
|
|
StdStrBuf sTitle; sTitle.Format("%s:%s", Config.General.Language, szNewTitle);
|
|
if (!rGrp.Add(C4CFN_WriteTitle, sTitle, false, true))
|
|
{
|
|
StdStrBuf sMsg; sMsg.Format(LoadResStr("IDS_ERR_ERRORADDINGNEWTITLEFORFIL"), sFilename.getData(), rGrp.GetError());
|
|
::pGUI->ShowMessageModal(sMsg.getData(), LoadResStr("IDS_FAIL_RENAME"), C4GUI::MessageDialog::btnOK, C4GUI::Ico_Error);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
// ------------------------------------
|
|
// Scenario
|
|
|
|
bool C4ScenarioListLoader::Scenario::LoadCustomPre(C4Group &rGrp)
|
|
{
|
|
// load scenario core first
|
|
StdStrBuf sFileContents;
|
|
if (!rGrp.LoadEntryString(C4CFN_ScenarioCore, &sFileContents)) return false;
|
|
if (!CompileFromBuf_LogWarn<StdCompilerINIRead>(mkParAdapt(C4S, false), sFileContents, (rGrp.GetFullName() + DirSep C4CFN_ScenarioCore).getData()))
|
|
return false;
|
|
// Mission access
|
|
fNoMissionAccess = (!C4S.Head.MissionAccess.empty() && !SIsModule(Config.General.MissionAccess, C4S.Head.MissionAccess.c_str()));
|
|
// Localized parameter definitions. needed for achievements and parameter input boxes.
|
|
// Only show them for "real" scenarios
|
|
if (!C4S.Head.SaveGame && !C4S.Head.Replay)
|
|
{
|
|
// Skipping ahead in regular reading list, so keep other entries in memory
|
|
rGrp.PreCacheEntries(C4CFN_AnyScriptStringTbl, true);
|
|
rGrp.PreCacheEntries(C4CFN_ScenarioParameterDefs, true);
|
|
C4LangStringTable ScenarioLangStringTable;
|
|
C4Language::LoadComponentHost(&ScenarioLangStringTable, rGrp, C4CFN_ScriptStringTbl, Config.General.LanguageEx);
|
|
ParameterDefs.Load(rGrp, &ScenarioLangStringTable);
|
|
// achievement images: Loaded from this entry and parent folder
|
|
nAchievements = 0;
|
|
const C4ScenarioParameterDefs *deflists[] = { pParent ? pParent->GetAchievementDefs() : nullptr, &ParameterDefs };
|
|
for (auto deflist : deflists)
|
|
{
|
|
if (!deflist) continue;
|
|
const C4ScenarioParameterDef *def; size_t idx=0;
|
|
while ((def = deflist->GetParameterDefByIndex(idx++)))
|
|
{
|
|
if (def->IsAchievement())
|
|
{
|
|
int32_t val = pLoader->GetAchievements().GetValueByID(C4ScenarioParameters::AddFilename2ID(rGrp.GetFullName().getData(), def->GetID()).getData(), def->GetDefault());
|
|
if (val)
|
|
{
|
|
// player has this achievement - find graphics for it
|
|
const char *achievement_gfx = def->GetAchievement();
|
|
StdStrBuf sAchievementFilename(C4CFN_Achievements);
|
|
sAchievementFilename.Replace("*", achievement_gfx);
|
|
// look in scenario
|
|
if (!fctAchievements[nAchievements].Load(rGrp, sAchievementFilename.getData(), C4FCT_Height, C4FCT_Full, false, true))
|
|
{
|
|
// look in parent folder
|
|
const C4FacetSurface *fct = nullptr;
|
|
const C4AchievementGraphics *parent_achv_gfx;
|
|
if (pParent && (parent_achv_gfx = pParent->GetAchievementGfx())) fct = parent_achv_gfx->FindByName(achievement_gfx);
|
|
// look in main gfx group file
|
|
if (!fct) fct = ::GraphicsResource.Achievements.FindByName(achievement_gfx);
|
|
if (!fct) continue; // achievement graphics not found :(
|
|
fctAchievements[nAchievements].Set((const C4Facet &)*fct);
|
|
}
|
|
// section by achievement index (1-based, since zero means no achievement)
|
|
if (val>1) fctAchievements[nAchievements].X += fctAchievements[nAchievements].Wdt * (val-1);
|
|
// description for this achievement is taken from option
|
|
const C4ScenarioParameterDef::Option *opt = def->GetOptionByValue(val);
|
|
if (opt) sAchievementDescriptions[nAchievements] = opt->Description;
|
|
// keep track of achievement count
|
|
++nAchievements;
|
|
if (nAchievements == C4StartupScenSel_MaxAchievements) break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool C4ScenarioListLoader::Scenario::LoadCustom(C4Group &rGrp, bool fNameLoaded, bool fIconLoaded)
|
|
{
|
|
// icon fallback: Standard scenario icon
|
|
if (!fIconLoaded)
|
|
{
|
|
iIconIndex = C4S.Head.Icon;
|
|
fctIcon.Set(C4Startup::Get()->Graphics.fctScenSelIcons.GetSection(C4S.Head.Icon));
|
|
}
|
|
// scenario name fallback to core
|
|
if (!fNameLoaded)
|
|
sName = C4S.Head.Title;
|
|
// difficulty: Set only for regular rounds (not savegame or record) to avoid bogus sorting
|
|
if (!C4S.Head.SaveGame && !C4S.Head.Replay)
|
|
iDifficulty = C4S.Head.Difficulty;
|
|
else
|
|
iDifficulty = 0;
|
|
// minimum required player count
|
|
iMinPlrCount = C4S.GetMinPlayer();
|
|
return true;
|
|
}
|
|
|
|
bool C4ScenarioListLoader::Scenario::GetAchievement(int32_t idx, C4Facet *out_facet, const char **out_description)
|
|
{
|
|
// return true and fill output parameters if player got the indexed achievement
|
|
if (idx < 0 || idx >= nAchievements) return false;
|
|
*out_facet = fctAchievements[idx];
|
|
*out_description = sAchievementDescriptions[idx].getData();
|
|
return true;
|
|
}
|
|
|
|
bool C4ScenarioListLoader::Scenario::Start()
|
|
{
|
|
// gogo!
|
|
if (!(C4StartupScenSelDlg::pInstance)) return false;
|
|
return (C4StartupScenSelDlg::pInstance)->StartScenario(this);
|
|
}
|
|
|
|
bool C4ScenarioListLoader::Scenario::CanOpen(StdStrBuf &sErrOut, bool &CanHide)
|
|
{
|
|
// safety
|
|
C4StartupScenSelDlg *pDlg = C4StartupScenSelDlg::pInstance;
|
|
if (!pDlg) return false;
|
|
// check mission access
|
|
if (!HasMissionAccess())
|
|
{
|
|
sErrOut.Copy(LoadResStr("IDS_PRC_NOMISSIONACCESS"));
|
|
return false;
|
|
}
|
|
// replay
|
|
if (C4S.Head.Replay)
|
|
{
|
|
// replays can currently not be launched in network mode
|
|
if (pDlg->IsNetworkStart())
|
|
{
|
|
sErrOut.Copy(LoadResStr("IDS_PRC_NONETREPLAY"));
|
|
return false;
|
|
}
|
|
}
|
|
// regular game
|
|
else
|
|
{
|
|
// check player count
|
|
int32_t iPlrCount = SModuleCount(Config.General.Participants);
|
|
int32_t iMaxPlrCount = C4S.Head.MaxPlayer;
|
|
if (C4S.Head.SaveGame)
|
|
{
|
|
// Some scenarios have adjusted MaxPlayerCount to 0 after starting to prevent future joins
|
|
// make sure it's possible to start the savegame anyway
|
|
iMaxPlrCount = std::max<int32_t>(iMinPlrCount, iMaxPlrCount);
|
|
|
|
// <Sven2> Savegames store a lot of internal stuff. If you updated clonk in the meantime, many things tend to break
|
|
if (C4S.Head.C4XVer[0] != C4XVER1 || C4S.Head.C4XVer[1] != C4XVER2)
|
|
{
|
|
// Only show a warning to let players try it anyways.
|
|
sErrOut.Format(LoadResStr("IDS_MSG_SAVEGAMEVERSIONMISMATCH"), C4S.Head.C4XVer[0], C4S.Head.C4XVer[1]);
|
|
CanHide = false;
|
|
}
|
|
}
|
|
// normal scenarios: At least one player except in network mode, where it is possible to wait for the additional players
|
|
// Melees need at least two
|
|
if ((iPlrCount < iMinPlrCount))
|
|
{
|
|
if (pDlg->IsNetworkStart())
|
|
{
|
|
// network game: Players may yet join in lobby
|
|
// only issue a warning for too few players (by setting the error but not returning false here)
|
|
sErrOut.Format(LoadResStr("IDS_MSG_TOOFEWPLAYERSNET"), (int) iMinPlrCount);
|
|
CanHide = true;
|
|
}
|
|
else
|
|
{
|
|
// for regular games, this is a fatal no-start-cause
|
|
sErrOut.Format(LoadResStr("IDS_MSG_TOOFEWPLAYERS"), (int) iMinPlrCount);
|
|
return false;
|
|
}
|
|
}
|
|
// scenarios (both normal and savegame) may also impose a maximum player restriction
|
|
if (iPlrCount > iMaxPlrCount)
|
|
{
|
|
sErrOut.Format(LoadResStr("IDS_MSG_TOOMANYPLAYERS"), (int) C4S.Head.MaxPlayer);
|
|
return false;
|
|
}
|
|
}
|
|
// Okay, start!
|
|
return true;
|
|
}
|
|
|
|
StdStrBuf C4ScenarioListLoader::Scenario::GetOpenText()
|
|
{
|
|
return StdCopyStrBuf(LoadResStr("IDS_BTN_STARTGAME"));
|
|
}
|
|
|
|
StdStrBuf C4ScenarioListLoader::Scenario::GetOpenTooltip()
|
|
{
|
|
return StdCopyStrBuf(LoadResStr("IDS_DLGTIP_SCENSELNEXT"));
|
|
}
|
|
|
|
|
|
// ------------------------------------
|
|
// Folder
|
|
|
|
C4ScenarioListLoader::Folder::~Folder()
|
|
{
|
|
if (pMapData) delete pMapData;
|
|
ClearChildren();
|
|
}
|
|
|
|
bool C4ScenarioListLoader::Folder::Start()
|
|
{
|
|
// open as subfolder
|
|
if (!C4StartupScenSelDlg::pInstance) return false;
|
|
return C4StartupScenSelDlg::pInstance->OpenFolder(this);
|
|
}
|
|
int
|
|
#ifdef _MSC_VER
|
|
__cdecl
|
|
#endif
|
|
EntrySortFunc(const void *pEl1, const void *pEl2)
|
|
{
|
|
C4ScenarioListLoader::Entry *pEntry1 = *(C4ScenarioListLoader::Entry * const *) pEl1, *pEntry2 = *(C4ScenarioListLoader::Entry * const *) pEl2;
|
|
// sort folders before scenarios
|
|
bool fS1 = !pEntry1->GetIsFolder(), fS2 = !pEntry2->GetIsFolder();
|
|
if (fS1 != fS2) return fS1-fS2;
|
|
// sort by folder index (undefined index 0 goes to the end)
|
|
if (!Config.Startup.AlphabeticalSorting) if (pEntry1->GetFolderIndex() || pEntry2->GetFolderIndex())
|
|
{
|
|
if (!pEntry1->GetFolderIndex()) return +1;
|
|
if (!pEntry2->GetFolderIndex()) return -1;
|
|
int32_t iDiff = pEntry1->GetFolderIndex() - pEntry2->GetFolderIndex();
|
|
if (iDiff) return iDiff;
|
|
}
|
|
// sort by numbered standard scenario icons
|
|
if (Inside(pEntry1->GetIconIndex(), 2, 11))
|
|
{
|
|
int32_t iDiff = pEntry1->GetIconIndex() - pEntry2->GetIconIndex();
|
|
if (iDiff) return iDiff;
|
|
}
|
|
// sort by difficulty (undefined difficulty goes to the end)
|
|
if (!Config.Startup.AlphabeticalSorting) if (pEntry1->GetDifficulty() || pEntry2->GetDifficulty())
|
|
{
|
|
if (!pEntry1->GetDifficulty()) return +1;
|
|
if (!pEntry2->GetDifficulty()) return -1;
|
|
int32_t iDiff = pEntry1->GetDifficulty() - pEntry2->GetDifficulty();
|
|
if (iDiff) return iDiff;
|
|
}
|
|
// otherwise, sort by name
|
|
return stricmp(pEntry1->GetName().getData(), pEntry2->GetName().getData());
|
|
}
|
|
|
|
uint32_t C4ScenarioListLoader::Folder::GetEntryCount() const
|
|
{
|
|
uint32_t iCount = 0;
|
|
for (Entry *i = pFirst; i; i = i->pNext) ++iCount;
|
|
return iCount;
|
|
}
|
|
|
|
void C4ScenarioListLoader::Folder::Sort()
|
|
{
|
|
// use C-Library-QSort on a buffer of entry pointers; then re-link list
|
|
if (!pFirst) return;
|
|
uint32_t iCount,i;
|
|
Entry **ppEntries = new Entry *[i = iCount = GetEntryCount()], **ppI, *pI=pFirst, **ppIThis;
|
|
for (ppI = ppEntries; i--; pI = pI->pNext) *ppI++ = pI;
|
|
qsort(ppEntries, iCount, sizeof(Entry *), &EntrySortFunc);
|
|
ppIThis = &pFirst;
|
|
for (ppI = ppEntries; iCount--; ppIThis = &((*ppIThis)->pNext)) *ppIThis = *ppI++;
|
|
*ppIThis = nullptr;
|
|
delete [] ppEntries;
|
|
}
|
|
|
|
void C4ScenarioListLoader::Folder::ClearChildren()
|
|
{
|
|
// folder deletion: del all the tree non-recursively
|
|
Folder *pDelFolder = this, *pCheckFolder;
|
|
for (;;)
|
|
{
|
|
// delete all children as long as they are not folders
|
|
Entry *pChild;
|
|
while ((pChild = pDelFolder->pFirst))
|
|
if ((pCheckFolder = pChild->GetIsFolder()))
|
|
// child entry if folder: Continue delete in there
|
|
pDelFolder = pCheckFolder;
|
|
else
|
|
// regular child entry: del it
|
|
// destructor of child will remove it from list
|
|
delete pChild;
|
|
// this emptied: Done!
|
|
if (pDelFolder == this) break;
|
|
// deepest child recursion reached: Travel up folders
|
|
pDelFolder = (pCheckFolder = pDelFolder)->pParent;
|
|
assert(pDelFolder);
|
|
delete pCheckFolder;
|
|
}
|
|
}
|
|
|
|
bool C4ScenarioListLoader::Folder::LoadContents(C4ScenarioListLoader *pLoader, C4Group *pFromGrp, const StdStrBuf *psFilename, bool fLoadEx, bool fReload)
|
|
{
|
|
// contents already loaded?
|
|
if (fContentsLoaded && !fReload) return true;
|
|
// clear previous
|
|
if (pMapData) { delete pMapData; pMapData = nullptr; }
|
|
// if filename is not given, assume it's been loaded in this entry
|
|
if (!psFilename) psFilename = &this->sFilename; else this->sFilename = *psFilename;
|
|
// nothing loaded: Load now
|
|
if (!DoLoadContents(pLoader, pFromGrp, *psFilename, fLoadEx)) return false;
|
|
// sort loaded stuff by name
|
|
Sort();
|
|
return true;
|
|
}
|
|
|
|
C4ScenarioListLoader::Entry *C4ScenarioListLoader::Folder::FindEntryByName(const char *szFilename) const
|
|
{
|
|
// do a case-insensitive filename comparison
|
|
for (Entry *pEntry = pFirst; pEntry; pEntry = pEntry->GetNext())
|
|
if (SEqualNoCase(szFilename, GetFilename(pEntry->GetEntryFilename().getData())))
|
|
return pEntry;
|
|
// nothing found
|
|
return nullptr;
|
|
}
|
|
|
|
StdStrBuf C4ScenarioListLoader::Folder::GetOpenText()
|
|
{
|
|
return StdCopyStrBuf(LoadResStr("IDS_BTN_OPEN"));
|
|
}
|
|
|
|
StdStrBuf C4ScenarioListLoader::Folder::GetOpenTooltip()
|
|
{
|
|
return StdCopyStrBuf(LoadResStr("IDS_DLGTIP_SCENSELNEXT"));
|
|
}
|
|
|
|
bool C4ScenarioListLoader::Folder::IsGrayed()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool C4ScenarioListLoader::Folder::LoadCustomPre(C4Group &rGrp)
|
|
{
|
|
// load folder core if available
|
|
StdStrBuf sFileContents;
|
|
if (rGrp.LoadEntryString(C4CFN_FolderCore, &sFileContents))
|
|
if (!CompileFromBuf_LogWarn<StdCompilerINIRead>(C4F, sFileContents, (rGrp.GetFullName() + DirSep C4CFN_FolderCore).getData()))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
// ------------------------------------
|
|
// SubFolder
|
|
|
|
bool C4ScenarioListLoader::SubFolder::LoadCustom(C4Group &rGrp, bool fNameLoaded, bool fIconLoaded)
|
|
{
|
|
// default icon fallback
|
|
if (!fIconLoaded)
|
|
{
|
|
if(WildcardMatch(C4CFN_Savegames, GetFilename(sFilename.getData()))) iIconIndex = C4StartupScenSel_DefaultIcon_SavegamesFolder;
|
|
else iIconIndex = C4StartupScenSel_DefaultIcon_Folder;
|
|
fctIcon.Set(C4Startup::Get()->Graphics.fctScenSelIcons.GetSection(iIconIndex));
|
|
}
|
|
// folder index
|
|
iFolderIndex = C4F.Head.Index;
|
|
return true;
|
|
}
|
|
|
|
bool C4ScenarioListLoader::SubFolder::DoLoadContents(C4ScenarioListLoader *pLoader, C4Group *pFromGrp, const StdStrBuf &sFilename, bool fLoadEx)
|
|
{
|
|
assert(pLoader);
|
|
// clear any previous
|
|
ClearChildren();
|
|
// group specified: Load as child
|
|
C4Group Group;
|
|
if (pFromGrp)
|
|
{
|
|
if (!Group.OpenAsChild(pFromGrp, sFilename.getData())) return false;
|
|
}
|
|
else
|
|
// no parent group: Direct load from filename
|
|
if (!Group.Open(sFilename.getData())) return false;
|
|
// Load achievement data contained scenarios can fall back to
|
|
C4LangStringTable FolderLangStringTable;
|
|
C4Language::LoadComponentHost(&FolderLangStringTable, Group, C4CFN_ScriptStringTbl, Config.General.LanguageEx);
|
|
AchievementDefs.Load(Group, &FolderLangStringTable);
|
|
AchievementGfx.Init(Group);
|
|
// get number of entries, to estimate progress
|
|
const char *szC4CFN_ScenarioFiles = C4CFN_ScenarioFiles; // assign values for constant comparison
|
|
const char *szSearchMask; int32_t iEntryCount=0;
|
|
for (szSearchMask = szC4CFN_ScenarioFiles; szSearchMask;)
|
|
{
|
|
Group.ResetSearch();
|
|
while (Group.FindNextEntry(szSearchMask)) ++iEntryCount;
|
|
// next search mask
|
|
if (szSearchMask == szC4CFN_ScenarioFiles)
|
|
szSearchMask = C4CFN_FolderFiles;
|
|
else
|
|
szSearchMask = nullptr;
|
|
}
|
|
// initial progress estimate
|
|
if (!pLoader->DoProcessCallback(0, iEntryCount, nullptr)) return false;
|
|
// iterate through group contents
|
|
char ChildFilename[_MAX_FNAME+1]; StdStrBuf sChildFilename; int32_t iLoadCount=0;
|
|
for (szSearchMask = szC4CFN_ScenarioFiles; szSearchMask;)
|
|
{
|
|
Group.ResetSearch();
|
|
while (Group.FindNextEntry(szSearchMask, ChildFilename))
|
|
{
|
|
// mark progress
|
|
if (!pLoader->DoProcessCallback(iLoadCount, iEntryCount, ChildFilename)) return false;
|
|
sChildFilename.Ref(ChildFilename);
|
|
// okay; create this item
|
|
Entry *pNewEntry = Entry::CreateEntryForFile(sChildFilename, pLoader, this);
|
|
if (pNewEntry)
|
|
{
|
|
// ...and load it
|
|
if (!pNewEntry->Load(&Group, &sChildFilename, fLoadEx))
|
|
{
|
|
DebugLogF(R"(Error loading entry "%s" in SubFolder "%s"!)", sChildFilename.getData(), Group.GetFullName().getData());
|
|
delete pNewEntry;
|
|
}
|
|
}
|
|
++iLoadCount;
|
|
}
|
|
// next search mask
|
|
if (szSearchMask == szC4CFN_ScenarioFiles)
|
|
szSearchMask = C4CFN_FolderFiles;
|
|
else
|
|
szSearchMask = nullptr;
|
|
}
|
|
// load map folder data
|
|
if (Group.FindEntry(C4CFN_MapFolderData))
|
|
{
|
|
pMapData = new C4MapFolderData();
|
|
if (!pMapData->Load(Group, this))
|
|
{
|
|
// load error :(
|
|
delete pMapData;
|
|
pMapData = nullptr;
|
|
}
|
|
}
|
|
// done, success
|
|
fContentsLoaded = true;
|
|
return true;
|
|
}
|
|
|
|
|
|
// ------------------------------------
|
|
// RegularFolder
|
|
|
|
C4ScenarioListLoader::RegularFolder::~RegularFolder() = default;
|
|
|
|
bool C4ScenarioListLoader::RegularFolder::LoadCustom(C4Group &rGrp, bool fNameLoaded, bool fIconLoaded)
|
|
{
|
|
// default icon fallback
|
|
if (!fIconLoaded)
|
|
fctIcon.Set(C4Startup::Get()->Graphics.fctScenSelIcons.GetSection(C4StartupScenSel_DefaultIcon_WinFolder));
|
|
// folder index
|
|
iFolderIndex = C4F.Head.Index;
|
|
return true;
|
|
}
|
|
|
|
bool C4ScenarioListLoader::RegularFolder::DoLoadContents(C4ScenarioListLoader *pLoader, C4Group *pFromGrp, const StdStrBuf &sFilename, bool fLoadEx)
|
|
{
|
|
// clear any previous
|
|
ClearChildren();
|
|
// regular folders must exist and not be within group!
|
|
assert(!pFromGrp);
|
|
if (sFilename.getData() && sFilename[0])
|
|
Merge(sFilename.getData());
|
|
|
|
// get number of entries, to estimate progress
|
|
int32_t iCountLoaded=0, iCountTotal=0;
|
|
NameList::iterator it;
|
|
for (it = contents.begin(); it != contents.end(); ++it)
|
|
{
|
|
if (!DirectoryExists(it->c_str())) continue;
|
|
DirectoryIterator DirIter(it->c_str());
|
|
const char *szChildFilename;
|
|
for (; (szChildFilename = *DirIter); ++DirIter)
|
|
{
|
|
if (!*szChildFilename || *GetFilename(szChildFilename)=='.') continue;
|
|
++iCountTotal;
|
|
}
|
|
}
|
|
// initial progress estimate
|
|
if (!pLoader->DoProcessCallback(iCountLoaded, iCountTotal, nullptr)) return false;
|
|
|
|
// do actual loading of files
|
|
std::set<std::string> names;
|
|
const char *szChildFilename;
|
|
for (it = contents.begin(); it != contents.end(); ++it)
|
|
{
|
|
if (!pLoader->DoProcessCallback(iCountLoaded, iCountTotal, GetFilename(it->c_str()))) return false;
|
|
for (DirectoryIterator DirIter(it->c_str()); (szChildFilename = *DirIter); ++DirIter)
|
|
{
|
|
StdStrBuf sChildFilename(szChildFilename);
|
|
szChildFilename = GetFilename(szChildFilename);
|
|
// progress callback
|
|
if (!pLoader->DoProcessCallback(iCountLoaded, iCountTotal, szChildFilename)) return false;
|
|
// Ignore directory navigation entries and CVS folders
|
|
if (C4Group_TestIgnore(szChildFilename)) continue;
|
|
if (names.find(szChildFilename) != names.end()) continue;
|
|
names.insert(szChildFilename);
|
|
// filename okay; create this item
|
|
Entry *pNewEntry = Entry::CreateEntryForFile(sChildFilename, pLoader, this);
|
|
if (pNewEntry)
|
|
{
|
|
// ...and load it
|
|
if (!pNewEntry->Load(nullptr, &sChildFilename, fLoadEx))
|
|
{
|
|
DebugLogF(R"(Error loading entry "%s" in Folder "%s"!)", szChildFilename, it->c_str());
|
|
delete pNewEntry;
|
|
}
|
|
}
|
|
++iCountLoaded;
|
|
}
|
|
}
|
|
// done, success
|
|
fContentsLoaded = true;
|
|
return true;
|
|
}
|
|
|
|
void C4ScenarioListLoader::RegularFolder::Merge(const char *szPath)
|
|
{
|
|
contents.emplace_back(szPath);
|
|
}
|
|
|
|
// ------------------------------------
|
|
// C4ScenarioListLoader
|
|
|
|
C4ScenarioListLoader::C4ScenarioListLoader(const C4ScenarioParameters &Achievements) : Achievements(Achievements), pRootFolder(nullptr), pCurrFolder(nullptr),
|
|
iLoading(0), iProgress(0), iMaxProgress(0), fAbortThis(false), fAbortPrevious(false)
|
|
{
|
|
}
|
|
|
|
C4ScenarioListLoader::~C4ScenarioListLoader()
|
|
{
|
|
if (pRootFolder) delete pRootFolder;
|
|
}
|
|
|
|
bool C4ScenarioListLoader::BeginActivity(bool fAbortPrevious)
|
|
{
|
|
// if previous activities were running, stop them first if desired
|
|
if (iLoading && fAbortPrevious)
|
|
this->fAbortPrevious = true;
|
|
// mark this activity
|
|
++iLoading;
|
|
// progress of activity not yet decided
|
|
iProgress = iMaxProgress = 0;
|
|
current_load_info.Clear();
|
|
// okay; start activity
|
|
return true;
|
|
}
|
|
|
|
void C4ScenarioListLoader::EndActivity()
|
|
{
|
|
assert(iLoading);
|
|
if (!--iLoading)
|
|
{
|
|
// last activity done: Reset any flags
|
|
fAbortThis = false;
|
|
fAbortPrevious = false;
|
|
iProgress = iMaxProgress = 0;
|
|
current_load_info.Clear();
|
|
}
|
|
else
|
|
{
|
|
// child activity done: Transfer abort flag for next activity
|
|
fAbortThis = fAbortPrevious;
|
|
}
|
|
}
|
|
|
|
bool C4ScenarioListLoader::DoProcessCallback(int32_t iProgress, int32_t iMaxProgress, const char *current_load_info)
|
|
{
|
|
this->iProgress = iProgress;
|
|
this->iMaxProgress = iMaxProgress;
|
|
this->current_load_info.Copy(current_load_info);
|
|
// callback to dialog
|
|
if (C4StartupScenSelDlg::pInstance) C4StartupScenSelDlg::pInstance->ProcessCallback();
|
|
// process callback - abort at a few ugly circumstances...
|
|
// schedule with 1ms delay to force event processing
|
|
// (delay 0 would be nice, but isn't supported properly by our Windows implementation of ScheduleProcs)
|
|
if (!Application.ScheduleProcs(1) // WM_QUIT message?
|
|
|| !C4StartupScenSelDlg::pInstance // host dialog removed?
|
|
|| !C4StartupScenSelDlg::pInstance->IsShown() // host dialog closed?
|
|
) return false;
|
|
// and also abort if flagged
|
|
return !fAbortThis;
|
|
}
|
|
|
|
bool C4ScenarioListLoader::Load(const StdStrBuf &sRootFolder)
|
|
{
|
|
// (unthreaded) loading of all entries in root folder
|
|
if (!BeginActivity(true)) return false;
|
|
if (pRootFolder) { delete pRootFolder; pRootFolder = nullptr; }
|
|
pCurrFolder = pRootFolder = new RegularFolder(this, nullptr);
|
|
// Load regular game data if no explicit path specified
|
|
if(!sRootFolder.getData())
|
|
for(const auto & iter : Reloc)
|
|
pRootFolder->Merge(iter.strBuf.getData());
|
|
bool fSuccess = pRootFolder->LoadContents(this, nullptr, &sRootFolder, false, false);
|
|
EndActivity();
|
|
return fSuccess;
|
|
}
|
|
|
|
bool C4ScenarioListLoader::Load(Folder *pSpecifiedFolder, bool fReload)
|
|
{
|
|
// call safety
|
|
if (!pRootFolder || !pSpecifiedFolder) return false;
|
|
// set new current and load it
|
|
if (!BeginActivity(true)) return false;
|
|
pCurrFolder = pSpecifiedFolder;
|
|
bool fSuccess = pCurrFolder->LoadContents(this, nullptr, nullptr, false, fReload);
|
|
EndActivity();
|
|
return fSuccess;
|
|
}
|
|
|
|
bool C4ScenarioListLoader::LoadExtended(Entry *pEntry)
|
|
{
|
|
// call safety
|
|
if (!pRootFolder || !pEntry) return false;
|
|
// load info of selection
|
|
if (!BeginActivity(false)) return false;
|
|
bool fSuccess = pEntry->Load(nullptr, nullptr, true);
|
|
EndActivity();
|
|
return fSuccess;
|
|
}
|
|
|
|
bool C4ScenarioListLoader::FolderBack()
|
|
{
|
|
// call safety
|
|
if (!pRootFolder || !pCurrFolder) return false;
|
|
// already in root: Can't go up
|
|
if (pCurrFolder == pRootFolder) return false;
|
|
// otherwise, up one level
|
|
return Load(pCurrFolder->GetParent(), false);
|
|
}
|
|
|
|
bool C4ScenarioListLoader::ReloadCurrent()
|
|
{
|
|
// call safety
|
|
if (!pRootFolder || !pCurrFolder) return false;
|
|
// reload current
|
|
return Load(pCurrFolder, true);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------
|
|
// Scenario selection GUI
|
|
|
|
|
|
// font clrs
|
|
const uint32_t ClrScenarioItem = 0xff000000,
|
|
ClrScenarioItemXtra = 0x7f000000,
|
|
ClrScenarioItemDisabled = 0x7f000000;
|
|
|
|
// ------------------------------------------------
|
|
// --- C4StartupScenSelDlg::ScenListItem
|
|
C4StartupScenSelDlg::ScenListItem::ScenListItem(C4GUI::ListBox *pForListBox, C4ScenarioListLoader::Entry *pForEntry, C4GUI::Element *pInsertBeforeElement)
|
|
: pIcon(nullptr), pNameLabel(nullptr), pScenListEntry(pForEntry)
|
|
{
|
|
assert(pScenListEntry);
|
|
CStdFont &rUseFont = C4Startup::Get()->Graphics.BookFont;
|
|
StdStrBuf sIgnore; bool bIgnore;
|
|
bool fEnabled = pScenListEntry->CanOpen(sIgnore, bIgnore) && !pScenListEntry->IsGrayed();
|
|
// calc height
|
|
int32_t iHeight = rUseFont.GetLineHeight() + 2 * IconLabelSpacing;
|
|
// create subcomponents
|
|
pIcon = new C4GUI::Picture(C4Rect(0, 0, iHeight, iHeight), true);
|
|
pIcon->SetFacet(pScenListEntry->GetIconFacet());
|
|
pNameLabel = new C4GUI::Label(pScenListEntry->GetName().getData(), iHeight + IconLabelSpacing, IconLabelSpacing, ALeft, fEnabled ? ClrScenarioItem : ClrScenarioItemDisabled, &rUseFont, false, false);
|
|
// achievement components
|
|
for (int32_t i=0; i<C4StartupScenSel_MaxAchievements; ++i)
|
|
{
|
|
C4Facet fct; const char *desc;
|
|
if (pForEntry->GetAchievement(i, &fct, &desc))
|
|
{
|
|
ppAchievements[i] = new C4GUI::Picture(C4Rect(iHeight * (i+2), 0, iHeight, iHeight), true); // position will be adjusted later
|
|
ppAchievements[i]->SetFacet(fct);
|
|
ppAchievements[i]->SetToolTip(desc);
|
|
}
|
|
else
|
|
{
|
|
ppAchievements[i] = nullptr;
|
|
}
|
|
}
|
|
// calc own bounds - use icon bounds only, because only the height is used when the item is added
|
|
SetBounds(pIcon->GetBounds());
|
|
// add components
|
|
AddElement(pIcon); AddElement(pNameLabel);
|
|
for (auto & ppAchievement : ppAchievements) if (ppAchievement) AddElement(ppAchievement);
|
|
// tooltip by name, so long names can be read via tooltip
|
|
SetToolTip(pScenListEntry->GetName().getData());
|
|
// 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 reflect new horizontal size
|
|
// name label width must be set so rename edit will take its size
|
|
pNameLabel->SetAutosize(false);
|
|
C4Rect rcNLB = pNameLabel->GetBounds(); rcNLB.Wdt = GetClientRect().Wdt - rcNLB.x - IconLabelSpacing;
|
|
pNameLabel->SetBounds(rcNLB);
|
|
}
|
|
|
|
void C4StartupScenSelDlg::ScenListItem::UpdateOwnPos()
|
|
{
|
|
// parent for client rect
|
|
typedef C4GUI::Window ParentClass;
|
|
ParentClass::UpdateOwnPos();
|
|
// reposition achievement items
|
|
C4GUI::ComponentAligner caBounds(GetContainedClientRect(), IconLabelSpacing, IconLabelSpacing);
|
|
for (auto & ppAchievement : ppAchievements) if (ppAchievement)
|
|
{
|
|
ppAchievement->SetBounds(caBounds.GetFromRight(caBounds.GetHeight()));
|
|
}
|
|
}
|
|
|
|
void C4StartupScenSelDlg::ScenListItem::MouseInput(C4GUI::CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam)
|
|
{
|
|
// double-click opens/starts item - currently processed by ListBox already!
|
|
// inherited processing
|
|
typedef C4GUI::Window BaseClass;
|
|
BaseClass::MouseInput(rMouse, iButton, iX, iY, dwKeyParam);
|
|
}
|
|
|
|
bool C4StartupScenSelDlg::ScenListItem::CheckNameHotkey(const char * c)
|
|
{
|
|
// return whether this item can be selected by entering given char:
|
|
// first char of name must match
|
|
// FIXME: make unicode-ready
|
|
if (!pScenListEntry) return false;
|
|
const char *szName = pScenListEntry->GetName().getData();
|
|
return szName && (toupper(*szName) == toupper(c[0]));
|
|
}
|
|
|
|
bool C4StartupScenSelDlg::ScenListItem::KeyRename()
|
|
{
|
|
// rename this entry
|
|
C4StartupScenSelDlg::pInstance->StartRenaming(new C4GUI::CallbackRenameEdit<C4StartupScenSelDlg::ScenListItem, RenameParams>(pNameLabel, this, RenameParams(), &C4StartupScenSelDlg::ScenListItem::DoRenaming, &C4StartupScenSelDlg::ScenListItem::AbortRenaming));
|
|
return true;
|
|
}
|
|
|
|
void C4StartupScenSelDlg::ScenListItem::AbortRenaming(RenameParams par)
|
|
{
|
|
// no renaming
|
|
C4StartupScenSelDlg::pInstance->SetRenamingDone();
|
|
}
|
|
|
|
C4GUI::RenameEdit::RenameResult C4StartupScenSelDlg::ScenListItem::DoRenaming(RenameParams par, const char *szNewName)
|
|
{
|
|
// check validity for new name
|
|
if (!GetEntry()->RenameTo(szNewName)) return C4GUI::RenameEdit::RR_Invalid;
|
|
// rename label
|
|
pNameLabel->SetText(GetEntry()->GetName().getData());
|
|
// main dlg update
|
|
C4StartupScenSelDlg::pInstance->SetRenamingDone();
|
|
C4StartupScenSelDlg::pInstance->ResortFolder();
|
|
C4StartupScenSelDlg::pInstance->UpdateSelection();
|
|
C4StartupScenSelDlg::pInstance->FocusScenList();
|
|
// done; rename accepted and control deleted by ResortFolder
|
|
return C4GUI::RenameEdit::RR_Deleted;
|
|
}
|
|
|
|
// ------------------------------------------------
|
|
// --- C4StartupScenSelDlg
|
|
|
|
C4StartupScenSelDlg::C4StartupScenSelDlg(bool fNetwork) : C4StartupDlg(LoadResStrNoAmp(fNetwork ? "IDS_DLG_NETSTART" : "IDS_DLG_STARTGAME")), pScenLoader(nullptr), pMapData(nullptr), pfctBackground(nullptr), fIsInitialLoading(false), fStartNetworkGame(fNetwork), pRenameEdit(nullptr)
|
|
{
|
|
// ctor
|
|
// assign singleton
|
|
pInstance = this;
|
|
|
|
// screen calculations
|
|
UpdateSize();
|
|
int32_t iButtonWidth,iCaptionFontHgt;
|
|
int iButtonHeight = C4GUI_ButtonHgt;
|
|
int iBookPageWidth;
|
|
int iExtraHPadding = rcBounds.Wdt >= 700 ? rcBounds.Wdt/50 : 0;
|
|
::GraphicsResource.CaptionFont.GetTextExtent("<< BACK", iButtonWidth, iCaptionFontHgt, true);
|
|
iButtonWidth *= 3;
|
|
C4GUI::ComponentAligner caMain(GetClientRect(), 0,0, true);
|
|
C4GUI::ComponentAligner caButtonArea(caMain.GetFromBottom(caMain.GetHeight()/8),rcBounds.Wdt/(rcBounds.Wdt >= 700 ? 128 : 256),0);
|
|
C4Rect rcMap = caMain.GetCentered(caMain.GetWidth(), caMain.GetHeight());
|
|
|
|
// tabular for different scenario selection designs
|
|
pScenSelStyleTabular = new C4GUI::Tabular(rcMap, C4GUI::Tabular::tbNone);
|
|
pScenSelStyleTabular->SetSheetMargin(0);
|
|
pScenSelStyleTabular->SetGfx(&C4Startup::Get()->Graphics.fctDlgPaper, &C4Startup::Get()->Graphics.fctOptionsTabClip, &C4Startup::Get()->Graphics.fctOptionsIcons, &C4Startup::Get()->Graphics.BookSmallFont, false);
|
|
AddElement(pScenSelStyleTabular);
|
|
C4GUI::Tabular::Sheet *pSheetBook = pScenSelStyleTabular->AddSheet(nullptr);
|
|
/* C4GUI::Tabular::Sheet *pSheetMap = */ pScenSelStyleTabular->AddSheet(nullptr);
|
|
|
|
// scenario selection list
|
|
C4GUI::ComponentAligner caBook(pSheetBook->GetClientRect(), caMain.GetWidth()/20, caMain.GetHeight()/20, true);
|
|
C4GUI::ComponentAligner caBookLeft(caBook.GetFromLeft(iBookPageWidth=caBook.GetWidth()*4/9+4-iExtraHPadding*2), 0,5);
|
|
|
|
CStdFont &rScenSelCaptionFont = C4Startup::Get()->Graphics.BookFontTitle;
|
|
pScenSelCaption = new C4GUI::Label("", caBookLeft.GetFromTop(rScenSelCaptionFont.GetLineHeight()), ACenter, ClrScenarioItem, &rScenSelCaptionFont, false);
|
|
pSheetBook->AddElement(pScenSelCaption);
|
|
pScenSelCaption->SetToolTip(LoadResStr("IDS_DLGTIP_SELECTSCENARIO"));
|
|
pScenSelList = new C4GUI::ListBox(caBookLeft.GetAll());
|
|
pScenSelList->SetToolTip(LoadResStr("IDS_DLGTIP_SELECTSCENARIO"));
|
|
pScenSelList->SetDecoration(false, &C4Startup::Get()->Graphics.sfctBookScroll, true);
|
|
pSheetBook->AddElement(pScenSelList);
|
|
pScenSelList->SetSelectionChangeCallbackFn(new C4GUI::CallbackHandler<C4StartupScenSelDlg>(this, &C4StartupScenSelDlg::OnSelChange));
|
|
pScenSelList->SetSelectionDblClickFn(new C4GUI::CallbackHandler<C4StartupScenSelDlg>(this, &C4StartupScenSelDlg::OnSelDblClick));
|
|
// scenario selection list progress labels
|
|
pScenSelProgressLabel = new C4GUI::Label("", pScenSelList->GetBounds().GetMiddleX(), pScenSelList->GetBounds().GetMiddleY()-iCaptionFontHgt, ACenter, ClrScenarioItem, &(C4Startup::Get()->Graphics.BookFontCapt), false);
|
|
pSheetBook->AddElement(pScenSelProgressLabel);
|
|
pScenSelProgressInfoLabel = new C4GUI::Label("", pScenSelList->GetBounds().GetMiddleX(), pScenSelList->GetBounds().GetMiddleY(), ACenter, ClrScenarioItemXtra, &(C4Startup::Get()->Graphics.BookFontCapt), false);
|
|
pSheetBook->AddElement(pScenSelProgressInfoLabel);
|
|
|
|
// right side of book: Displaying current selection
|
|
C4Rect bounds = caBook.GetFromRight(iBookPageWidth);
|
|
const int32_t AvailWidth = bounds.Wdt;
|
|
const int32_t AvailHeight = 2 * bounds.Hgt / 5;
|
|
int32_t PictureWidth, PictureHeight;
|
|
if(AvailWidth * C4StartupScenSel_TitlePictureHgt < AvailHeight * C4StartupScenSel_TitlePictureWdt)
|
|
{
|
|
PictureWidth = C4StartupScenSel_TitlePictureWdt * AvailWidth / C4StartupScenSel_TitlePictureWdt;
|
|
PictureHeight = C4StartupScenSel_TitlePictureHgt * AvailWidth / C4StartupScenSel_TitlePictureWdt;
|
|
}
|
|
else
|
|
{
|
|
PictureWidth = C4StartupScenSel_TitlePictureWdt * AvailHeight / C4StartupScenSel_TitlePictureHgt;
|
|
PictureHeight = C4StartupScenSel_TitlePictureHgt * AvailHeight / C4StartupScenSel_TitlePictureHgt;
|
|
}
|
|
pSelectionInfo = new C4GUI::TextWindow(bounds, PictureWidth+2*C4StartupScenSel_TitleOverlayMargin, PictureHeight+2*C4StartupScenSel_TitleOverlayMargin,
|
|
C4StartupScenSel_TitlePicturePadding, 100, 4096, nullptr, true, &C4Startup::Get()->Graphics.fctScenSelTitleOverlay, C4StartupScenSel_TitleOverlayMargin);
|
|
pSelectionInfo->SetDecoration(false, false, &C4Startup::Get()->Graphics.sfctBookScroll, true);
|
|
pSheetBook->AddElement(pSelectionInfo);
|
|
|
|
// bottom of right side of book: Custom options on selection
|
|
// Arbitrary height and invisible by default. Height will be adjusted when options are created.
|
|
pSelectionOptions = new C4GameOptionsList(C4Rect(bounds.x, bounds.y+bounds.Hgt-10, bounds.Wdt, 10), false, fNetwork ? C4GameOptionsList::GOLS_PreGameNetwork : C4GameOptionsList::GOLS_PreGameSingle);
|
|
pSelectionOptions->SetDecoration(false, &C4Startup::Get()->Graphics.sfctBookScroll, true);
|
|
pSelectionOptions->SetVisibility(false);
|
|
pSheetBook->AddElement(pSelectionOptions);
|
|
|
|
// back button
|
|
C4GUI::CallbackButton<C4StartupScenSelDlg> *btn;
|
|
AddElement(btn = new C4GUI::CallbackButton<C4StartupScenSelDlg>(LoadResStr("IDS_BTN_BACK"), caButtonArea.GetFromLeft(iButtonWidth, iButtonHeight), &C4StartupScenSelDlg::OnBackBtn));
|
|
btn->SetToolTip(LoadResStr("IDS_DLGTIP_BACKMAIN"));
|
|
AddElement(btn);
|
|
// next button
|
|
pOpenBtn = new C4GUI::CallbackButton<C4StartupScenSelDlg>(LoadResStr("IDS_BTN_OPEN"), caButtonArea.GetFromRight(iButtonWidth, iButtonHeight), &C4StartupScenSelDlg::OnNextBtn);
|
|
pOpenBtn->SetToolTip(LoadResStr("IDS_DLGTIP_SCENSELNEXT"));
|
|
// game options boxes
|
|
pGameOptionButtons = new C4GameOptionButtons(caButtonArea.GetAll(), fNetwork, true, false);
|
|
AddElement(pGameOptionButtons);
|
|
// next button adding
|
|
AddElement(pOpenBtn);
|
|
|
|
// dlg starts with focus on listbox
|
|
SetFocus(pScenSelList, false);
|
|
|
|
// key bindings
|
|
C4CustomKey::CodeList keys;
|
|
keys.emplace_back(K_BACK); keys.emplace_back(K_LEFT);
|
|
pKeyBack = new C4KeyBinding(keys, "StartupScenSelFolderUp", KEYSCOPE_Gui,
|
|
new C4GUI::DlgKeyCB<C4StartupScenSelDlg>(*this, &C4StartupScenSelDlg::KeyBack), C4CustomKey::PRIO_CtrlOverride);
|
|
pKeyRefresh = new C4KeyBinding(C4KeyCodeEx(K_F5), "StartupScenSelReload", KEYSCOPE_Gui,
|
|
new C4GUI::DlgKeyCB<C4StartupScenSelDlg>(*this, &C4StartupScenSelDlg::KeyRefresh), C4CustomKey::PRIO_CtrlOverride);
|
|
pKeyForward = new C4KeyBinding(C4KeyCodeEx(K_RIGHT), "StartupScenSelNext", KEYSCOPE_Gui,
|
|
new C4GUI::DlgKeyCB<C4StartupScenSelDlg>(*this, &C4StartupScenSelDlg::KeyForward), C4CustomKey::PRIO_CtrlOverride);
|
|
pKeyRename = new C4KeyBinding(C4KeyCodeEx(K_F2), "StartupScenSelRename", KEYSCOPE_Gui,
|
|
new C4GUI::ControlKeyDlgCB<C4StartupScenSelDlg>(pScenSelList, *this, &C4StartupScenSelDlg::KeyRename), C4CustomKey::PRIO_CtrlOverride);
|
|
pKeyDelete = new C4KeyBinding(C4KeyCodeEx(K_DELETE), "StartupScenSelDelete", KEYSCOPE_Gui,
|
|
new C4GUI::ControlKeyDlgCB<C4StartupScenSelDlg>(pScenSelList, *this, &C4StartupScenSelDlg::KeyDelete), C4CustomKey::PRIO_CtrlOverride);
|
|
pKeyCheat = new C4KeyBinding(C4KeyCodeEx(K_M, KEYS_Control), "StartupScenSelCheat", KEYSCOPE_Gui,
|
|
new C4GUI::ControlKeyDlgCB<C4StartupScenSelDlg>(pScenSelList, *this, &C4StartupScenSelDlg::KeyCheat), C4CustomKey::PRIO_CtrlOverride);
|
|
}
|
|
|
|
C4StartupScenSelDlg::~C4StartupScenSelDlg()
|
|
{
|
|
if (pScenLoader) delete pScenLoader;
|
|
if (this == pInstance) pInstance = nullptr;
|
|
delete pKeyCheat;
|
|
delete pKeyDelete;
|
|
delete pKeyRename;
|
|
delete pKeyForward;
|
|
delete pKeyRefresh;
|
|
delete pKeyBack;
|
|
}
|
|
|
|
void C4StartupScenSelDlg::DrawElement(C4TargetFacet &cgo)
|
|
{
|
|
// draw background
|
|
if (pfctBackground)
|
|
DrawBackground(cgo, *pfctBackground);
|
|
}
|
|
|
|
void C4StartupScenSelDlg::OnShown()
|
|
{
|
|
C4StartupDlg::OnShown();
|
|
// Collect achievements of all activated players
|
|
UpdateAchievements();
|
|
// init file list
|
|
fIsInitialLoading = true;
|
|
if (!pScenLoader) pScenLoader = new C4ScenarioListLoader(Achievements);
|
|
pScenLoader->Load(StdStrBuf());
|
|
UpdateList();
|
|
UpdateSelection();
|
|
fIsInitialLoading = false;
|
|
// network activation by dialog type
|
|
Game.NetworkActive = fStartNetworkGame;
|
|
}
|
|
|
|
void C4StartupScenSelDlg::OnClosed(bool fOK)
|
|
{
|
|
AbortRenaming();
|
|
// clear laoded scenarios
|
|
if (pScenLoader)
|
|
{
|
|
delete pScenLoader;
|
|
pScenLoader = nullptr;
|
|
UpdateList(); // must clear scenario list, because it points to deleted stuff
|
|
UpdateSelection(); // must clear picture facet of selection!
|
|
}
|
|
// dlg abort: return to main screen
|
|
if (!fOK)
|
|
{
|
|
// clear settings: Password
|
|
::Network.SetPassword(nullptr);
|
|
C4Startup::Get()->SwitchDialog(C4Startup::SDID_Back);
|
|
}
|
|
}
|
|
|
|
void C4StartupScenSelDlg::UpdateList()
|
|
{
|
|
AbortRenaming();
|
|
// default: Show book (also for loading screen)
|
|
pMapData = nullptr;
|
|
pScenSelStyleTabular->SelectSheet(ShowStyle_Book, false);
|
|
// and delete any stuff from map selection
|
|
C4GUI::Tabular::Sheet *pMapSheet = pScenSelStyleTabular->GetSheet(ShowStyle_Map);
|
|
while (pMapSheet->GetFirst()) delete pMapSheet->GetFirst();
|
|
pfctBackground = nullptr;
|
|
// for now, all the list is loaded at once anyway
|
|
// so just clear and add all loaded items
|
|
// remember old selection
|
|
C4ScenarioListLoader::Entry *pOldSelection = GetSelectedEntry();
|
|
C4GUI::Element *pEl;
|
|
while ((pEl = pScenSelList->GetFirst())) delete pEl;
|
|
pScenSelCaption->SetText("");
|
|
// scen loader still busy: Nothing to add
|
|
if (!pScenLoader) return;
|
|
if (pScenLoader->IsLoading())
|
|
{
|
|
StdStrBuf sProgressText;
|
|
sProgressText.Format(LoadResStr("IDS_MSG_SCENARIODESC_LOADING"), (int32_t) pScenLoader->GetProgressPercent());
|
|
pScenSelProgressLabel->SetText(sProgressText.getData());
|
|
pScenSelProgressLabel->SetVisibility(true);
|
|
pScenSelProgressInfoLabel->SetText(pScenLoader->GetProgressInfo());
|
|
pScenSelProgressInfoLabel->SetVisibility(true);
|
|
return;
|
|
}
|
|
pScenSelProgressLabel->SetVisibility(false);
|
|
pScenSelProgressInfoLabel->SetVisibility(false);
|
|
// is this a map folder? Then show the map instead
|
|
C4ScenarioListLoader::Folder *pFolder = static_cast<C4ScenarioListLoader::Folder *>(pScenLoader->GetCurrFolder());
|
|
if ((pMapData = pFolder->GetMapData()))
|
|
{
|
|
pMapData->ResetSelection();
|
|
pMapData->CreateGUIElements(this, *pScenSelStyleTabular->GetSheet(ShowStyle_Map));
|
|
pScenSelStyleTabular->SelectSheet(ShowStyle_Map, false);
|
|
}
|
|
else
|
|
{
|
|
// book style selection
|
|
// add what has been loaded
|
|
for (C4ScenarioListLoader::Entry *pEnt = pScenLoader->GetFirstEntry(); pEnt; pEnt = pEnt->GetNext())
|
|
{
|
|
if (pEnt->IsHidden()) continue; // no UI entry at all for hidden items
|
|
ScenListItem *pEntItem = new ScenListItem(pScenSelList, pEnt);
|
|
if (pEnt == pOldSelection) pScenSelList->SelectEntry(pEntItem, false);
|
|
}
|
|
// set title of current folder
|
|
// but not root
|
|
if (pFolder && pFolder != pScenLoader->GetRootFolder())
|
|
pScenSelCaption->SetText(pFolder->GetName().getData());
|
|
else
|
|
{
|
|
// special root title
|
|
pScenSelCaption->SetText(LoadResStr("IDS_DLG_SCENARIOS"));
|
|
}
|
|
// new list has been loaded: Select first entry if nothing else had been selected
|
|
if (!pOldSelection) pScenSelList->SelectFirstEntry(false);
|
|
}
|
|
}
|
|
|
|
void C4StartupScenSelDlg::ResortFolder()
|
|
{
|
|
// if it's still loading, sorting will be done in the end anyway
|
|
if (!pScenLoader || pScenLoader->IsLoading()) return;
|
|
C4ScenarioListLoader::Folder *pFolder = pScenLoader->GetCurrFolder();
|
|
if (!pFolder) return;
|
|
pFolder->Resort();
|
|
UpdateList();
|
|
}
|
|
|
|
void C4StartupScenSelDlg::UpdateSelection()
|
|
{
|
|
AbortRenaming();
|
|
if (!pScenLoader)
|
|
{
|
|
C4Facet fctNoPic;
|
|
pSelectionInfo->SetPicture(fctNoPic);
|
|
pSelectionInfo->ClearText(false);
|
|
SetOpenButtonDefaultText();
|
|
return;
|
|
}
|
|
// determine target text box
|
|
C4GUI::TextWindow *pSelectionInfo = pMapData ? pMapData->GetSelectionInfoBox() : this->pSelectionInfo;
|
|
// get selected entry
|
|
C4ScenarioListLoader::Entry *pSel = GetSelectedEntry();
|
|
if (!pSel)
|
|
{
|
|
// no selection: Display data of current parent folder
|
|
pSel = pScenLoader->GetCurrFolder();
|
|
// but not root
|
|
if (pSel == pScenLoader->GetRootFolder()) pSel = nullptr;
|
|
}
|
|
// get title image and desc of selected entry
|
|
C4Facet fctTitle; StdStrBuf sTitle, sDesc, sVersion, sAuthor;
|
|
if (pSel)
|
|
{
|
|
pScenLoader->LoadExtended(pSel); // 2do: Multithreaded
|
|
fctTitle = pSel->GetTitlePicture();
|
|
sTitle.Ref(pSel->GetName());
|
|
sDesc.Ref(pSel->GetDesc());
|
|
sVersion.Ref(pSel->GetVersion());
|
|
sAuthor.Ref(pSel->GetAuthor());
|
|
// never show a pure title string: There must always be some text or an image
|
|
if (!fctTitle.Surface && (!sDesc || !*sDesc.getData()))
|
|
sTitle.Clear();
|
|
// selection specific open/start button
|
|
pOpenBtn->SetText(pSel->GetOpenText().getData());
|
|
pOpenBtn->SetToolTip(pSel->GetOpenTooltip().getData());
|
|
}
|
|
else
|
|
SetOpenButtonDefaultText();
|
|
// set data in selection component
|
|
pSelectionInfo->ClearText(false);
|
|
pSelectionInfo->SetPicture(fctTitle);
|
|
if (sTitle && (!sDesc || !*sDesc.getData())) pSelectionInfo->AddTextLine(sTitle.getData(), &C4Startup::Get()->Graphics.BookFontCapt, ClrScenarioItem, false, false);
|
|
if (sDesc) pSelectionInfo->AddTextLine(sDesc.getData(), &C4Startup::Get()->Graphics.BookFont, ClrScenarioItem, false, false, &C4Startup::Get()->Graphics.BookFontCapt);
|
|
if (sAuthor) pSelectionInfo->AddTextLine(FormatString(LoadResStr("IDS_CTL_AUTHOR"), sAuthor.getData()).getData(),
|
|
&C4Startup::Get()->Graphics.BookFont, ClrScenarioItemXtra, false, false);
|
|
if (sVersion) pSelectionInfo->AddTextLine(FormatString(LoadResStr("IDS_DLG_VERSION"), sVersion.getData()).getData(),
|
|
&C4Startup::Get()->Graphics.BookFont, ClrScenarioItemXtra, false, false);
|
|
// update custom scenario options panel
|
|
if (pSel)
|
|
{
|
|
pSelectionOptions->SetParameters(pSel->GetParameterDefs(), pSel->GetParameters());
|
|
pSelectionOptions->Update();
|
|
}
|
|
else
|
|
pSelectionOptions->SetParameters(nullptr, nullptr);
|
|
// update component heights
|
|
C4Rect rcSelBounds = pSelectionInfo->GetBounds();
|
|
int32_t ymax = pSelectionOptions->GetBounds().GetBottom();
|
|
C4GUI::Element *pLastOption = pSelectionOptions->GetLast();
|
|
if (pLastOption)
|
|
{
|
|
// custom options present: Info box reduced; options box at bottom
|
|
// set options box max size to 1/3rd of selection info area
|
|
int32_t options_hgt = std::min<int32_t>(pLastOption->GetBounds().GetBottom() + pSelectionOptions->GetMarginTop() + pSelectionOptions->GetMarginTop(), rcSelBounds.Hgt/3);
|
|
rcSelBounds.Hgt = ymax - options_hgt - rcSelBounds.y;
|
|
pSelectionInfo->SetBounds(rcSelBounds);
|
|
rcSelBounds.y = ymax - options_hgt;
|
|
rcSelBounds.Hgt = options_hgt;
|
|
pSelectionOptions->SetBounds(rcSelBounds);
|
|
pSelectionOptions->SetVisibility(true);
|
|
}
|
|
else
|
|
{
|
|
// custom options absent: Info takes full area
|
|
pSelectionOptions->SetVisibility(false);
|
|
rcSelBounds.Hgt = ymax - rcSelBounds.y;
|
|
pSelectionInfo->SetBounds(rcSelBounds);
|
|
}
|
|
pSelectionInfo->UpdateHeight();
|
|
}
|
|
|
|
C4StartupScenSelDlg::ScenListItem *C4StartupScenSelDlg::GetSelectedItem()
|
|
{
|
|
return static_cast<ScenListItem *>(pScenSelList->GetSelectedItem());
|
|
}
|
|
|
|
C4ScenarioListLoader::Entry *C4StartupScenSelDlg::GetSelectedEntry()
|
|
{
|
|
// map layout: Get selection from map
|
|
if (pMapData) return pMapData->GetSelectedEntry();
|
|
// get selection in listbox
|
|
ScenListItem *pSel = static_cast<ScenListItem *>(pScenSelList->GetSelectedItem());
|
|
return pSel ? pSel->GetEntry() : nullptr;
|
|
}
|
|
|
|
bool C4StartupScenSelDlg::StartScenario(C4ScenarioListLoader::Scenario *pStartScen)
|
|
{
|
|
assert(pStartScen);
|
|
if (!pStartScen) return false;
|
|
// get required object definitions
|
|
if (pStartScen->GetC4S().Definitions.AllowUserChange)
|
|
{
|
|
// get definitions as user selects them
|
|
StdStrBuf sDefinitions;
|
|
if (!pStartScen->GetC4S().Definitions.GetModules(&sDefinitions)) sDefinitions.Copy("Objects.ocd");
|
|
if (!C4DefinitionSelDlg::SelectDefinitions(GetScreen(), &sDefinitions))
|
|
// user aborted during definition selection
|
|
return false;
|
|
SCopy(sDefinitions.getData(), ::Game.DefinitionFilenames, (sizeof Game.DefinitionFilenames)-1);
|
|
}
|
|
else
|
|
// for no user change, just set default objects. Custom settings will override later anyway
|
|
SCopy("Objects.ocd", ::Game.DefinitionFilenames);
|
|
// set other default startup parameters
|
|
::Game.fLobby = !!::Game.NetworkActive; // always lobby in network
|
|
::Game.fObserve = false;
|
|
C4ScenarioParameters *custom_params = pStartScen->GetParameters();
|
|
if (custom_params) ::Game.StartupScenarioParameters = *custom_params; else ::Game.StartupScenarioParameters.Clear();
|
|
// start with this set!
|
|
::Application.OpenGame(pStartScen->GetEntryFilename().getData());
|
|
return true;
|
|
}
|
|
|
|
bool C4StartupScenSelDlg::OpenFolder(C4ScenarioListLoader::Folder *pNewFolder)
|
|
{
|
|
// open it through loader
|
|
if (!pScenLoader) return false;
|
|
bool fSuccess = pScenLoader->Load(pNewFolder, false);
|
|
UpdateList();
|
|
UpdateSelection();
|
|
if (!pMapData) SetFocus(pScenSelList, false);
|
|
return fSuccess;
|
|
}
|
|
|
|
bool C4StartupScenSelDlg::DoOK()
|
|
{
|
|
AbortRenaming();
|
|
// get selected entry
|
|
C4ScenarioListLoader::Entry *pSel = GetSelectedEntry();
|
|
if (!pSel) return false;
|
|
// check if open is possible
|
|
StdStrBuf sError;
|
|
bool CanHide = false;
|
|
if (!pSel->CanOpen(sError, CanHide))
|
|
{
|
|
GetScreen()->ShowMessage(sError.getData(), LoadResStr("IDS_MSG_CANNOTSTARTSCENARIO"), C4GUI::Ico_Error);
|
|
return false;
|
|
}
|
|
// if CanOpen returned true but set an error message, that means it's a warning. Display it!
|
|
if (sError.getLength())
|
|
{
|
|
if (!GetScreen()->ShowMessageModal(sError.getData(), LoadResStrNoAmp("IDS_DLG_STARTGAME"), C4GUI::MessageDialog::btnOKAbort, C4GUI::Ico_Notify, CanHide ? &Config.Startup.HideMsgStartDedicated : nullptr))
|
|
// user chose to not start it
|
|
return false;
|
|
}
|
|
// start it!
|
|
return pSel->Start();
|
|
}
|
|
|
|
bool C4StartupScenSelDlg::DoBack(bool fAllowClose)
|
|
{
|
|
AbortRenaming();
|
|
// if in a subfolder, try backtrace folders first
|
|
if (pScenLoader && pScenLoader->FolderBack())
|
|
{
|
|
UpdateList();
|
|
UpdateSelection();
|
|
return true;
|
|
}
|
|
// while this isn't multithreaded, the dialog must not be aborted while initial load...
|
|
if (pScenLoader && pScenLoader->IsLoading()) return false;
|
|
// return to main screen
|
|
if (fAllowClose)
|
|
{
|
|
Close(false);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void C4StartupScenSelDlg::DoRefresh()
|
|
{
|
|
if (pScenLoader && !pScenLoader->IsLoading())
|
|
{
|
|
pScenSelList->SelectNone(false);
|
|
pScenLoader->ReloadCurrent();
|
|
UpdateList();
|
|
UpdateSelection();
|
|
}
|
|
}
|
|
|
|
void C4StartupScenSelDlg::SetOpenButtonDefaultText()
|
|
{
|
|
pOpenBtn->SetText(LoadResStr("IDS_BTN_OPEN"));
|
|
pOpenBtn->SetToolTip(LoadResStr("IDS_DLGTIP_SCENSELNEXT"));
|
|
}
|
|
|
|
bool C4StartupScenSelDlg::KeyRename()
|
|
{
|
|
// no rename in map mode
|
|
if (pMapData) return false;
|
|
// not if renaming already
|
|
if (IsRenaming()) return false;
|
|
// forward to selected scenario list item
|
|
ScenListItem *pSel = GetSelectedItem();
|
|
if (!pSel) return false;
|
|
return pSel->KeyRename();
|
|
}
|
|
|
|
bool C4StartupScenSelDlg::KeyDelete()
|
|
{
|
|
// do not delete from map folder
|
|
if (pMapData) return false;
|
|
// cancel renaming
|
|
AbortRenaming();
|
|
// delete selected item: Confirmation first
|
|
ScenListItem *pSel = GetSelectedItem();
|
|
if (!pSel) return false;
|
|
C4ScenarioListLoader::Entry *pEnt = pSel->GetEntry();
|
|
StdStrBuf sWarning;
|
|
sWarning.Format(LoadResStr("IDS_MSG_PROMPTDELETE"), FormatString("%s %s", pEnt->GetTypeName().getData(), pEnt->GetName().getData()).getData(), pEnt->GetEntryFilename().getData());
|
|
GetScreen()->ShowRemoveDlg(new C4GUI::ConfirmationDialog(sWarning.getData(), LoadResStr("IDS_MNU_DELETE"),
|
|
new C4GUI::CallbackHandlerExPar<C4StartupScenSelDlg, ScenListItem *>(this, &C4StartupScenSelDlg::DeleteConfirm, pSel), C4GUI::MessageDialog::btnYesNo));
|
|
return true;
|
|
}
|
|
|
|
void C4StartupScenSelDlg::DeleteConfirm(ScenListItem *pSel)
|
|
{
|
|
// deletion confirmed. Do it.
|
|
C4ScenarioListLoader::Entry *pEnt = pSel->GetEntry();
|
|
if (!C4Group_DeleteItem(pEnt->GetEntryFilename().getData(), true))
|
|
{
|
|
StdStrBuf sMsg; sMsg.Format("%s", LoadResStr("IDS_FAIL_DELETE"));
|
|
::pGUI->ShowMessageModal(sMsg.getData(), LoadResStr("IDS_MNU_DELETE"), C4GUI::MessageDialog::btnOK, C4GUI::Ico_Error);
|
|
return;
|
|
}
|
|
// remove from scenario list
|
|
pScenSelList->SelectEntry(pSel->GetNext(), false);
|
|
delete pEnt;
|
|
delete pSel;
|
|
}
|
|
|
|
bool C4StartupScenSelDlg::KeyCheat()
|
|
{
|
|
return ::pGUI->ShowRemoveDlg(new C4GUI::InputDialog(LoadResStr("IDS_TEXT_ENTERMISSIONPASSWORD"), LoadResStr("IDS_DLG_MISSIONACCESS"), C4GUI::Ico_Options,
|
|
new C4GUI::InputCallback<C4StartupScenSelDlg>(this, &C4StartupScenSelDlg::KeyCheat2),
|
|
false));
|
|
}
|
|
|
|
void C4StartupScenSelDlg::KeyCheat2(const StdStrBuf &rsCheatCode)
|
|
{
|
|
// Special character "-": remove mission password(s)
|
|
if (SEqual2(rsCheatCode.getData(), "-"))
|
|
{
|
|
const char *szPass = rsCheatCode.getPtr(1);
|
|
if (szPass && *szPass)
|
|
{
|
|
SRemoveModules(Config.General.MissionAccess, szPass, false);
|
|
UpdateList();
|
|
UpdateSelection();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// No special character: add mission password(s)
|
|
const char *szPass = rsCheatCode.getPtr(0);
|
|
if (szPass && *szPass)
|
|
{
|
|
SAddModules(Config.General.MissionAccess, szPass, false);
|
|
UpdateList();
|
|
UpdateSelection();
|
|
return;
|
|
}
|
|
|
|
}
|
|
|
|
void C4StartupScenSelDlg::FocusScenList()
|
|
{
|
|
SetFocus(pScenSelList, false);
|
|
}
|
|
|
|
void C4StartupScenSelDlg::OnButtonScenario(C4GUI::Control *pEl)
|
|
{
|
|
// map button was clicked: Update selected scenario
|
|
if (!pMapData || !pEl) return;
|
|
C4ScenarioListLoader::Entry *pSel = GetSelectedEntry(), *pSel2;
|
|
pMapData->OnButtonScenario(pEl);
|
|
pSel2 = GetSelectedEntry();
|
|
if (pSel && pSel==pSel2)
|
|
{
|
|
// clicking on the selected scenario again starts it
|
|
DoOK();
|
|
return;
|
|
}
|
|
// the first click selects it
|
|
SetFocus(pEl, false);
|
|
UpdateSelection();
|
|
}
|
|
|
|
void C4StartupScenSelDlg::DeselectAll()
|
|
{
|
|
// Deselect all so current folder info is displayed
|
|
if (GetFocus()) C4GUI::GUISound("UI::Tick");
|
|
SetFocus(nullptr, true);
|
|
if (pMapData) pMapData->ResetSelection();
|
|
UpdateSelection();
|
|
}
|
|
|
|
void C4StartupScenSelDlg::StartRenaming(C4GUI::RenameEdit *pNewRenameEdit)
|
|
{
|
|
pRenameEdit = pNewRenameEdit;
|
|
}
|
|
|
|
void C4StartupScenSelDlg::AbortRenaming()
|
|
{
|
|
if (pRenameEdit) pRenameEdit->Abort();
|
|
}
|
|
|
|
void C4StartupScenSelDlg::UpdateAchievements()
|
|
{
|
|
// Extract all achievements from activated player files and merge them
|
|
Achievements.Clear();
|
|
char PlayerFilename[_MAX_FNAME+1];
|
|
C4Group PlayerGrp;
|
|
for (int i = 0; SCopySegment(Config.General.Participants, i, PlayerFilename, ';', _MAX_FNAME, true); i++)
|
|
{
|
|
const char *szPlayerFilename = Config.AtUserDataPath(PlayerFilename);
|
|
if (!FileExists(szPlayerFilename)) continue;
|
|
if (!PlayerGrp.Open(szPlayerFilename)) continue;
|
|
C4PlayerInfoCore nfo;
|
|
if (!nfo.Load(PlayerGrp)) continue;
|
|
Achievements.Merge(nfo.Achievements);
|
|
}
|
|
}
|
|
|
|
void C4StartupScenSelDlg::OnLeagueOptionChanged()
|
|
{
|
|
if (pSelectionOptions) pSelectionOptions->Update();
|
|
}
|
|
|
|
// NICHT: 9, 7.2.2, 113-114, 8a
|
|
|