Skip to content

Commit

Permalink
Add campaign menu and mission selection screen
Browse files Browse the repository at this point in the history
  • Loading branch information
ottml authored and Flow86 committed Nov 6, 2023
1 parent 4ce19f4 commit 2dc5e3b
Show file tree
Hide file tree
Showing 4 changed files with 496 additions and 0 deletions.
185 changes: 185 additions & 0 deletions libs/s25main/desktops/dskCampaignMissionSelection.cpp
@@ -0,0 +1,185 @@
// Copyright (C) 2005 - 2023 Settlers Freaks (sf-team at siedler25.org)
//
// SPDX-License-Identifier: GPL-2.0-or-later

#include "dskCampaignMissionSelection.h"
#include "Loader.h"
#include "WindowManager.h"
#include "commonDefines.h"
#include "controls/ctrlGroup.h"
#include "controls/ctrlImageButton.h"
#include "controls/ctrlText.h"
#include "dskCampaignSelection.h"
#include "ingameWindows/iwConnecting.h"
#include "ingameWindows/iwMsgbox.h"
#include "lua/CampaignDataLoader.h"
#include "network/GameClient.h"
#include "ogl/glFont.h"
#include "gameData/CampaignDescription.h"
#include "libsiedler2/ArchivItem_Map.h"
#include "libsiedler2/ArchivItem_Map_Header.h"
#include "libsiedler2/ErrorCodes.h"
#include "libsiedler2/prototypen.h"
#include "s25util/Log.h"
#include "s25util/utf8.h"

namespace bfs = boost::filesystem;
constexpr unsigned ID_msgBoxError = 0;

namespace {
enum
{
ID_Back,
ID_FirstPage,
ID_NextPage,
ID_PageLabel,
ID_PreviousPage,
ID_LastPage,
ID_ChooseMissionLabel,
ID_GroupStart
};

constexpr int startOffsetY = 70;
constexpr int distanceBetweenElementsY = 10;
constexpr int distanceBetweenMissionButtonsY = 10;
constexpr Extent buttonSize(22, 20);
constexpr int spacingBetweenButtons = 2;

int getStartOffsetMissionButtonsY()
{
return startOffsetY + LargeFont->getHeight() + distanceBetweenElementsY + 8;
}
} // namespace

dskCampaignMissionSelection::dskCampaignMissionSelection(CreateServerInfo csi, boost::filesystem::path campaignFolder)
: Desktop(LOADER.GetImageN("setup015", 0)), campaignFolder_(std::move(campaignFolder)), csi_(std::move(csi)),
currentPage_(0), lastPage_(0), missionsPerPage_(10)
{
const unsigned int btOffset = getStartOffsetMissionButtonsY()
+ missionsPerPage_ * (buttonSize.y + distanceBetweenMissionButtonsY)
+ distanceBetweenElementsY;
AddTextButton(ID_Back, DrawPoint(300, 560), Extent(200, 22), TextureColor::Red1, _("Back"), NormalFont);

AddImageButton(ID_FirstPage, DrawPoint(400 - buttonSize.x * 3 - 2 * spacingBetweenButtons, btOffset), buttonSize,
TextureColor::Green2, LOADER.GetImageN("io", 102));

AddImageButton(ID_PreviousPage, DrawPoint(400 - buttonSize.x * 2, btOffset), buttonSize, TextureColor::Green2,
LOADER.GetImageN("io", 103));

AddImageButton(ID_NextPage, DrawPoint(400 + buttonSize.x, btOffset), buttonSize, TextureColor::Green2,
LOADER.GetImageN("io", 104));

AddImageButton(ID_LastPage, DrawPoint(400 + buttonSize.x * 2 + 2 * spacingBetweenButtons, btOffset), buttonSize,
TextureColor::Green2, LOADER.GetImageN("io", 105));

settings_ = std::make_unique<CampaignDescription>();
CampaignDataLoader loader(*settings_, campaignFolder_);
if(!loader.Load() || settings_->getNumMaps() == 0)
LOG.write(_("Failed to load campaign %1%.\n")) % campaignFolder_;
else
lastPage_ = (settings_->getNumMaps() - 1) / missionsPerPage_;

UpdateEnabledStateOfNextPreviousButton();

AddText(ID_PageLabel, DrawPoint(400, btOffset + buttonSize.y / 2),
std::to_string(currentPage_ + 1) + "/" + std::to_string(lastPage_ + 1), COLOR_YELLOW,
FontStyle::CENTER | FontStyle::VCENTER, LargeFont);

AddText(ID_ChooseMissionLabel, DrawPoint(400, startOffsetY), _("Choose mission"), COLOR_YELLOW, FontStyle::CENTER,
LargeFont);

UpdateMissionPage();
}

void dskCampaignMissionSelection::UpdateMissionPage()
{
ctrlGroup* group = AddGroup(ID_GroupStart + currentPage_);
Extent catBtSize = Extent(300, buttonSize.y);
DrawPoint curBtPos(250, getStartOffsetMissionButtonsY());
for(unsigned int i = 0; i < missionsPerPage_; i++)
{
unsigned int missionIndex = currentPage_ * missionsPerPage_ + i;
if(missionIndex >= settings_->getNumMaps())
break;

const bfs::path& mapFilePath = settings_->getMapFilePath(missionIndex);

libsiedler2::Archiv map;
if(int ec = libsiedler2::loader::LoadMAP(mapFilePath, map, true))
{
LOG.write(_("Failed to load map %1%: %2%\n")) % mapFilePath % libsiedler2::getErrorString(ec);
continue;
}

const libsiedler2::ArchivItem_Map_Header& header =
checkedCast<const libsiedler2::ArchivItem_Map*>(map[0])->getHeader();

group->AddTextButton(i, curBtPos, catBtSize, TextureColor::Grey, s25util::ansiToUTF8(header.getName()),
NormalFont);

curBtPos.y += catBtSize.y + distanceBetweenMissionButtonsY;
}
}

void dskCampaignMissionSelection::StartServer(const boost::filesystem::path& mapPath)
{
// Start server
if(!GAMECLIENT.HostGame(csi_, mapPath, MapType::OldMap))
{
WINDOWMANAGER.Show(std::make_unique<iwMsgbox>(_("Error"), _("Hosting of game not possible"), this,
MsgboxButton::Ok, MsgboxIcon::ExclamationRed, ID_msgBoxError));
} else
{
iwConnecting& wnd =
WINDOWMANAGER.Show(std::make_unique<iwConnecting>(csi_.type, std::unique_ptr<ILobbyClient>()));
onErrorConnection_ = wnd.onError.connect([this](ClientError error) {
WINDOWMANAGER.Show(std::make_unique<iwMsgbox>(_("Error"), ClientErrorToStr(error), this, MsgboxButton::Ok,
MsgboxIcon::ExclamationRed, ID_msgBoxError));
});
}
}

void dskCampaignMissionSelection::UpdateEnabledStateOfNextPreviousButton()
{
GetCtrl<ctrlImageButton>(ID_PreviousPage)->SetEnabled(currentPage_ > 0);
GetCtrl<ctrlImageButton>(ID_NextPage)->SetEnabled(currentPage_ < lastPage_);
GetCtrl<ctrlImageButton>(ID_FirstPage)->SetEnabled(currentPage_ > 0);
GetCtrl<ctrlImageButton>(ID_LastPage)->SetEnabled(currentPage_ < lastPage_);
}

void dskCampaignMissionSelection::Msg_Group_ButtonClick(unsigned group_id, unsigned ctrl_id)
{
unsigned int missionIndex = (group_id - ID_GroupStart) * missionsPerPage_ + ctrl_id;
const bfs::path mapPath = settings_->getMapFilePath(missionIndex);
const bfs::path luaPath = settings_->getLuaFilePath(missionIndex);
StartServer(mapPath, luaPath);
}

void dskCampaignMissionSelection::Msg_ButtonClick(unsigned ctrl_id)
{
if(ctrl_id == ID_Back)
WINDOWMANAGER.Switch(std::make_unique<dskCampaignSelection>(csi_));
else if(ctrl_id >= ID_FirstPage && ctrl_id <= ID_LastPage)
{
// Destroy all controls first (the whole group)
DeleteCtrl(ID_GroupStart + currentPage_);

switch(ctrl_id)
{
case ID_FirstPage: currentPage_ = 0; break;
case ID_NextPage:
if(currentPage_ < lastPage_)
currentPage_++;
break;
case ID_PreviousPage:
if(currentPage_ > 0)
currentPage_--;
break;
case ID_LastPage: currentPage_ = lastPage_; break;
}
UpdateMissionPage();
GetCtrl<ctrlText>(ID_PageLabel)
->SetText(std::to_string(currentPage_ + 1) + "/" + std::to_string(lastPage_ + 1));
UpdateEnabledStateOfNextPreviousButton();
}
}
32 changes: 32 additions & 0 deletions libs/s25main/desktops/dskCampaignMissionSelection.h
@@ -0,0 +1,32 @@
// Copyright (C) 2005 - 2023 Settlers Freaks (sf-team at siedler25.org)
//
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "desktops/Desktop.h"
#include "network/CreateServerInfo.h"
#include <boost/filesystem.hpp>
#include <boost/signals2/connection.hpp>

struct CampaignDescription;

class dskCampaignMissionSelection : public Desktop
{
public:
dskCampaignMissionSelection(CreateServerInfo csi, boost::filesystem::path campaignFolder);

private:
void UpdateMissionPage();
void Msg_ButtonClick(unsigned ctrl_id) override;
void Msg_Group_ButtonClick(unsigned group_id, unsigned ctrl_id) override;
void StartServer(const boost::filesystem::path& mapPath);
void UpdateEnabledStateOfNextPreviousButton();
boost::filesystem::path campaignFolder_;
CreateServerInfo csi_;
unsigned currentPage_;
unsigned lastPage_;
std::unique_ptr<CampaignDescription> settings_;
unsigned missionsPerPage_;
boost::signals2::scoped_connection onErrorConnection_;
};

0 comments on commit 2dc5e3b

Please sign in to comment.