• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Return-To-The-Roots / s25client / 14551527845

19 Apr 2025 05:54PM UTC coverage: 50.309% (+0.02%) from 50.288%
14551527845

Pull #1750

github

web-flow
Merge 21c2aa548 into c8cf430f2
Pull Request #1750: Fix size of Settings window

11 of 28 new or added lines in 15 files covered. (39.29%)

2 existing lines in 1 file now uncovered.

22365 of 44455 relevant lines covered (50.31%)

34666.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/libs/s25main/desktops/dskSelectMap.cpp
1
// Copyright (C) 2005 - 2021 Settlers Freaks (sf-team at siedler25.org)
2
//
3
// SPDX-License-Identifier: GPL-2.0-or-later
4

5
#include "dskSelectMap.h"
6
#include "ListDir.h"
7
#include "Loader.h"
8
#include "RttrConfig.h"
9
#include "RttrLobbyClient.hpp"
10
#include "WindowManager.h"
11
#include "commonDefines.h"
12
#include "controls/ctrlButton.h"
13
#include "controls/ctrlOptionGroup.h"
14
#include "controls/ctrlPreviewMinimap.h"
15
#include "controls/ctrlTable.h"
16
#include "controls/ctrlText.h"
17
#include "desktops/dskDirectIP.h"
18
#include "desktops/dskLAN.h"
19
#include "desktops/dskLobby.h"
20
#include "desktops/dskSinglePlayer.h"
21
#include "files.h"
22
#include "helpers/containerUtils.h"
23
#include "helpers/format.hpp"
24
#include "helpers/toString.h"
25
#include "ingameWindows/iwConnecting.h"
26
#include "ingameWindows/iwMapGenerator.h"
27
#include "ingameWindows/iwMsgbox.h"
28
#include "ingameWindows/iwPleaseWait.h"
29
#include "ingameWindows/iwSave.h"
30
#include "lua/GameDataLoader.h"
31
#include "mapGenerator/RandomMap.h"
32
#include "network/GameClient.h"
33
#include "ogl/FontStyle.h"
34
#include "gameData/MapConsts.h"
35
#include "gameData/WorldDescription.h"
36
#include "liblobby/LobbyClient.h"
37
#include "libsiedler2/ArchivItem_Map.h"
38
#include "libsiedler2/ArchivItem_Map_Header.h"
39
#include "libsiedler2/ErrorCodes.h"
40
#include "libsiedler2/prototypen.h"
41
#include "s25util/Log.h"
42
#include "s25util/utf8.h"
43
#include <boost/filesystem/operations.hpp>
44
#include <boost/pointer_cast.hpp>
45
#include <stdexcept>
46
#include <utility>
47

48
namespace bfs = boost::filesystem;
49
constexpr unsigned ID_msgBoxError = 0;
50

51
/**
52
 *  Konstruktor von @p dskSelectMap.
53
 *
54
 *  @param[in] type Typ des Servers
55
 *  @param[in] name Server-Name
56
 *  @param[in] pass Server-Passwort
57
 */
58
dskSelectMap::dskSelectMap(CreateServerInfo csi)
×
59
    : Desktop(LOADER.GetImageN("setup015", 0)), csi(std::move(csi)), mapGenThread(nullptr), waitWnd(nullptr)
×
60
{
61
    WorldDescription desc;
×
62
    GameDataLoader gdLoader(desc);
×
63
    if(!gdLoader.Load())
×
64
    {
65
        LC_Status_Error(_("Failed to load game data!"));
×
66
        return;
×
67
    }
68

69
    for(const auto& l : desc.landscapes)
×
70
        landscapeNames[l.s2Id] = _(l.name);
×
71

72
    // Die Tabelle für die Maps
73
    using SRT = ctrlTable::SortType;
74
    AddTable(1, DrawPoint(110, 35), Extent(680, 400), TextureColor::Grey, NormalFont,
×
75
             ctrlTable::Columns{{_("Name"), 250, SRT::String},
×
76
                                {_("Author"), 216, SRT::String},
77
                                {_("Player"), 170, SRT::Number},
78
                                {_("Type"), 180, SRT::String},
79
                                {_("Size"), 134, SRT::MapSize},
80
                                {"", 0, SRT::Default}});
×
81

82
    // "Karten Auswahl"
83
    AddText(2, DrawPoint(400, 5), _("Selection of maps"), COLOR_YELLOW, FontStyle::CENTER, LargeFont);
×
84

85
    // "Zurück"
86
    AddTextButton(3, DrawPoint(380, 560), Extent(200, 22), TextureColor::Red1, _("Back"), NormalFont);
×
87
    // "Spiel laden..."
88
    AddTextButton(4, DrawPoint(590, 530), Extent(200, 22), TextureColor::Green2, _("Load game..."), NormalFont);
×
89
    // "Weiter"
90
    AddTextButton(5, DrawPoint(590, 560), Extent(200, 22), TextureColor::Green2, _("Continue"), NormalFont);
×
91
    // random map generation
92
    AddTextButton(6, DrawPoint(380, 530), Extent(150, 22), TextureColor::Green2, _("Random Map"), NormalFont);
×
93
    // random map settings
94
    AddTextButton(7, DrawPoint(540, 530), Extent(40, 22), TextureColor::Green2, _("..."), NormalFont);
×
95

96
    ctrlOptionGroup* optiongroup = AddOptionGroup(10, GroupSelectType::Check);
×
97
    Extent catBtSize = Extent(90, 22);
×
98
    // "Alte"
99
    DrawPoint curBtPos = DrawPoint(10, 35);
×
100
    optiongroup->AddTextButton(0, curBtPos, catBtSize, TextureColor::Grey, _("Old maps"), NormalFont);
×
101
    curBtPos.y += catBtSize.y + 3;
×
102
    // "Neue"
103
    optiongroup->AddTextButton(1, curBtPos, catBtSize, TextureColor::Grey, _("New maps"), NormalFont);
×
104
    curBtPos.y += catBtSize.y + 3;
×
105
    // "Eigene"
106
    optiongroup->AddTextButton(2, curBtPos, catBtSize, TextureColor::Grey, _("Own maps"), NormalFont);
×
107
    curBtPos.y += catBtSize.y + 3;
×
108
    // "Kontinente"
109
    optiongroup->AddTextButton(3, curBtPos, catBtSize, TextureColor::Grey, _("Continents"), NormalFont);
×
110
    curBtPos.y += catBtSize.y + 3;
×
111
    // "Kampagne"
112
    optiongroup->AddTextButton(4, curBtPos, catBtSize, TextureColor::Grey, _("Campaign"), NormalFont);
×
113
    curBtPos.y += catBtSize.y + 3;
×
114
    // "RTTR"
115
    optiongroup->AddTextButton(5, curBtPos, catBtSize, TextureColor::Grey, _("RTTR"), NormalFont);
×
116
    curBtPos.y += catBtSize.y + 3;
×
117
    // "Andere"
118
    optiongroup->AddTextButton(6, curBtPos, catBtSize, TextureColor::Grey, _("Other"), NormalFont);
×
119
    curBtPos.y += catBtSize.y + 3;
×
120
    // "Andere"
121
    optiongroup->AddTextButton(7, curBtPos, catBtSize, TextureColor::Grey, _("Sea"), NormalFont);
×
122
    curBtPos.y += catBtSize.y + 3;
×
123
    // "Heruntergeladene"
124
    optiongroup->AddTextButton(8, curBtPos, catBtSize, TextureColor::Grey, _("Played"), NormalFont);
×
125

126
    AddPreviewMinimap(11, DrawPoint(110, 445), Extent(140, 140), nullptr);
×
127
    AddText(12, DrawPoint(260, 470), _("Map: "), COLOR_YELLOW, FontStyle::LEFT, NormalFont);
×
128
    AddText(13, DrawPoint(260, 490), _("Mapfile: "), COLOR_YELLOW, FontStyle::LEFT, NormalFont);
×
129

130
    // "Eigene" auswählen
131
    optiongroup->SetSelection(5, true);
×
132

133
    LOBBYCLIENT.AddListener(this);
×
134
}
135

136
dskSelectMap::~dskSelectMap()
×
137
{
138
    // if(mapGenThread)
139
    //    mapGenThread->join();
140
    LOBBYCLIENT.RemoveListener(this);
×
141
}
×
142

143
void dskSelectMap::Msg_OptionGroupChange(const unsigned /*ctrl_id*/, unsigned selection)
×
144
{
145
    auto* table = GetCtrl<ctrlTable>(1);
×
146

147
    // Tabelle leeren
148
    table->DeleteAllItems();
×
149

150
    // Old, New, Own, Continents, Campaign, RTTR, Other, Sea, Played
151
    static const std::array<std::string, 9> ids = {{s25::folders::mapsOld, s25::folders::mapsNew, s25::folders::mapsOwn,
152
                                                    s25::folders::mapsContinents, s25::folders::mapsCampaign,
153
                                                    s25::folders::mapsRttr, s25::folders::mapsOther,
154
                                                    s25::folders::mapsSea, s25::folders::mapsPlayed}};
×
155

156
    const size_t numFaultyMapsPrior = brokenMapPaths.size();
×
157
    const bfs::path mapPath = RTTRCONFIG.ExpandPath(ids[selection]);
×
158
    FillTable(ListDir(mapPath, "swd"));
×
159
    FillTable(ListDir(mapPath, "wld"));
×
160
    // For own maps (WORLDS folder) also use the one in the installation folder as S2 does
161
    if(mapPath.filename() == "WORLDS")
×
162
    {
163
        const bfs::path worldsPath = RTTRCONFIG.ExpandPath("WORLDS");
×
164
        FillTable(ListDir(worldsPath, "swd"));
×
165
        FillTable(ListDir(worldsPath, "wld"));
×
166
    }
167

168
    if(brokenMapPaths.size() > numFaultyMapsPrior)
×
169
    {
170
        std::string errorTxt = helpers::format(_("%1% map(s) could not be loaded. Check the log for details"),
171
                                               brokenMapPaths.size() - numFaultyMapsPrior);
×
172
        WINDOWMANAGER.Show(
×
173
          std::make_unique<iwMsgbox>(_("Error"), errorTxt, this, MsgboxButton::Ok, MsgboxIcon::ExclamationRed, 1));
×
174
    }
175

176
    // Dann noch sortieren
177
    table->SortRows(0, TableSortDir::Ascending);
×
178

179
    // und Auswahl zurücksetzen
180
    table->SetSelection(boost::none);
×
181
}
×
182

183
/// Load a map, throw on error
184
static std::unique_ptr<libsiedler2::ArchivItem_Map> loadAndVerifyMap(const std::string& path)
×
185
{
186
    libsiedler2::Archiv archive;
×
187
    if(int ec = libsiedler2::loader::LoadMAP(path, archive))
×
188
        throw std::runtime_error(libsiedler2::getErrorString(ec));
×
189
    auto map = boost::dynamic_pointer_cast<libsiedler2::ArchivItem_Map>(archive.release(0));
×
190
    if(!map)
×
191
        throw std::runtime_error(_("Unexpected dynamic type of map"));
×
192
    if(map->getHeader().getWidth() > MAX_MAP_SIZE || map->getHeader().getHeight() > MAX_MAP_SIZE)
×
193
        throw std::runtime_error(helpers::format(_("Map is bigger than allowed size of %1% nodes"), MAX_MAP_SIZE));
×
194
    if(map->getHeader().getNumPlayers() > MAX_PLAYERS)
×
195
        throw std::runtime_error(helpers::format(_("Map has more than %1% players"), MAX_PLAYERS));
×
196
    return map;
×
197
}
198

199
/**
200
 *  Occurs when user changes the selection in the table of maps.
201
 */
202
void dskSelectMap::Msg_TableSelectItem(const unsigned ctrl_id, const boost::optional<unsigned>& selection)
×
203
{
204
    if(ctrl_id != 1)
×
205
        return;
×
206

207
    ctrlPreviewMinimap& preview = *GetCtrl<ctrlPreviewMinimap>(11);
×
208
    ctrlText& txtMapName = *GetCtrl<ctrlText>(12);
×
209
    ctrlText& txtMapPath = *GetCtrl<ctrlText>(13);
×
210
    ctrlButton& btContinue = *GetCtrl<ctrlButton>(5);
×
211
    preview.SetMap(nullptr);
×
212
    txtMapName.SetText("");
×
213
    txtMapPath.SetText("");
×
214
    btContinue.SetEnabled(false);
×
215

216
    // is the selection valid?
217
    if(selection)
×
218
    {
219
        ctrlTable& table = *GetCtrl<ctrlTable>(1);
×
220
        const std::string& path = table.GetItemText(*selection, 5);
×
221
        if(!path.empty())
×
222
        {
223
            try
224
            {
225
                std::unique_ptr<libsiedler2::ArchivItem_Map> map = loadAndVerifyMap(path);
×
226
                RTTR_Assert(map);
×
227
                preview.SetMap(map.get());
×
228
                txtMapName.SetText(s25util::ansiToUTF8(map->getHeader().getName()));
×
229
                txtMapPath.SetText(path);
×
230
                btContinue.SetEnabled(true);
×
231
            } catch(const std::runtime_error& e)
×
232
            {
233
                const std::string errorTxt = helpers::format(_("Could not load map:\n%1%\n%2%"), path, e.what());
×
234
                LOG.write("%1%\n") % errorTxt;
×
235
                WINDOWMANAGER.Show(std::make_unique<iwMsgbox>(_("Error"), errorTxt, this, MsgboxButton::Ok,
×
236
                                                              MsgboxIcon::ExclamationRed, 1));
×
237
                brokenMapPaths.insert(path);
×
238
                table.RemoveRow(*selection);
×
239
            }
240
        }
241
    }
242
    const unsigned txtXPos = preview.GetPos().x + preview.GetSize().x + 10;
×
243
    txtMapName.SetPos(DrawPoint(txtXPos, txtMapName.GetPos().y));
×
244
    txtMapPath.SetPos(DrawPoint(txtXPos, txtMapPath.GetPos().y));
×
245
}
246

247
void dskSelectMap::GoBack() const
×
248
{
249
    if(csi.type == ServerType::Local)
×
250
        WINDOWMANAGER.Switch(std::make_unique<dskSinglePlayer>());
×
251
    else if(csi.type == ServerType::LAN)
×
252
        WINDOWMANAGER.Switch(std::make_unique<dskLAN>());
×
253
    else if(csi.type == ServerType::Lobby && LOBBYCLIENT.IsLoggedIn())
×
254
        WINDOWMANAGER.Switch(std::make_unique<dskLobby>());
×
255
    else
256
        WINDOWMANAGER.Switch(std::make_unique<dskDirectIP>());
×
257
}
×
258

259
void dskSelectMap::Msg_ButtonClick(const unsigned ctrl_id)
×
260
{
261
    switch(ctrl_id)
×
262
    {
263
        case 3: // "Zurück"
×
264
        {
265
            GoBack();
×
266
        }
267
        break;
×
268
        case 4: // "Spiel laden..."
×
269
        {
270
            // Ladefenster aufrufen
271
            WINDOWMANAGER.ToggleWindow(std::make_unique<iwLoad>(csi));
×
272
        }
273
        break;
×
274
        case 5: // "Weiter"
×
275
        {
276
            StartServer();
×
277
        }
278
        break;
×
279
        case 6: // random map
×
280
        {
281
            if(!mapGenThread)
×
282
            {
283
                newRandMapPath.clear();
×
284
                randMapGenError.clear();
×
285
                waitWnd = &WINDOWMANAGER.Show(std::make_unique<iwPleaseWait>());
×
286
                // mapGenThread = new boost::thread(boost::bind(&dskSelectMap::CreateRandomMap, this));
287
                CreateRandomMap();
×
288
            }
289
        }
290
        break;
×
291
        case 7: // random map generator settings
×
292
        {
293
            WINDOWMANAGER.ToggleWindow(std::make_unique<iwMapGenerator>(rndMapSettings));
×
294
        }
295
        break;
×
296
    }
297
}
×
298

299
void dskSelectMap::Msg_TableChooseItem(const unsigned /*ctrl_id*/, const unsigned /*selection*/)
×
300
{
301
    // Doppelklick auf bestimmte Map -> weiter
302
    StartServer();
×
303
}
×
304

305
void dskSelectMap::CreateRandomMap()
×
306
{
307
    // setup filepath for the random map
308
    const auto mapPath = RTTRCONFIG.ExpandPath(s25::folders::mapsPlayed) / "Random.swd";
×
309

310
    try
311
    {
312
        // create a random map and save filepath
313
        rttr::mapGenerator::CreateRandomMap(mapPath, rndMapSettings);
×
314
        newRandMapPath = mapPath;
×
NEW
315
    } catch(const std::runtime_error& e)
×
316
    {
317
        randMapGenError = e.what();
×
318
    }
319
}
×
320

321
void dskSelectMap::OnMapCreated(const boost::filesystem::path& mapPath)
×
322
{
323
    // select the "played maps" entry
324
    auto* optionGroup = GetCtrl<ctrlOptionGroup>(10);
×
325
    optionGroup->SetSelection(8, true);
×
326

327
    // search for the random map entry and select it in the table
328
    auto* table = GetCtrl<ctrlTable>(1);
×
329
    const auto& mapPathString = mapPath.string();
×
330
    for(int i = 0; i < table->GetNumRows(); i++)
×
331
    {
332
        if(table->GetItemText(i, 5) == mapPathString)
×
333
        {
334
            table->SetSelection(i);
×
335
            break;
×
336
        }
337
    }
338
}
×
339

340
/// Startet das Spiel mit einer bestimmten Auswahl in der Tabelle
341
void dskSelectMap::StartServer()
×
342
{
343
    auto* table = GetCtrl<ctrlTable>(1);
×
344
    const auto& selection = table->GetSelection();
×
345

346
    // Ist die Auswahl gültig?
347
    if(selection)
×
348
    {
349
        // Kartenpfad aus Tabelle holen
350
        const std::string& mapPath = table->GetItemText(*selection, 5);
×
351

352
        // Server starten
353
        if(!GAMECLIENT.HostGame(csi, {mapPath, MapType::OldMap}))
×
354
            GoBack();
×
355
        else
356
        {
357
            std::unique_ptr<ILobbyClient> lobbyClient;
×
358
            if(csi.type == ServerType::Lobby)
×
359
                lobbyClient = std::make_unique<RttrLobbyClient>(LOBBYCLIENT);
×
360
            iwConnecting& wnd = WINDOWMANAGER.Show(std::make_unique<iwConnecting>(csi.type, std::move(lobbyClient)));
×
361
            onErrorConnection_ = wnd.onError.connect([this](ClientError error) {
×
362
                WINDOWMANAGER.Show(std::make_unique<iwMsgbox>(_("Error"), ClientErrorToStr(error), this,
×
363
                                                              MsgboxButton::Ok, MsgboxIcon::ExclamationRed,
×
364
                                                              ID_msgBoxError));
×
365
            });
×
366
        }
367
    }
368
}
×
369

370
void dskSelectMap::Msg_MsgBoxResult(const unsigned msgbox_id, const MsgboxResult /*mbr*/)
×
371
{
372
    if(msgbox_id == ID_msgBoxError)
×
373
    {
374
        GAMECLIENT.Stop();
×
375

376
        if(csi.type == ServerType::Lobby && LOBBYCLIENT.IsLoggedIn()) // steht die Lobbyverbindung noch?
×
377
            WINDOWMANAGER.Switch(std::make_unique<dskLobby>());
×
378
        else if(csi.type == ServerType::Lobby)
×
379
            WINDOWMANAGER.Switch(std::make_unique<dskDirectIP>());
×
380
        else if(csi.type == ServerType::LAN)
×
381
            WINDOWMANAGER.Switch(std::make_unique<dskLAN>());
×
382
        else
383
            WINDOWMANAGER.Switch(std::make_unique<dskSinglePlayer>());
×
384
    }
385
}
×
386

387
/**
388
 *  (Lobby-)Status: Benutzerdefinierter Fehler (kann auch Conn-Loss o.ä sein)
389
 */
390
void dskSelectMap::LC_Status_Error(const std::string& error)
×
391
{
392
    WINDOWMANAGER.Show(std::make_unique<iwMsgbox>(_("Error"), error, this, MsgboxButton::Ok, MsgboxIcon::ExclamationRed,
×
393
                                                  ID_msgBoxError));
×
394
}
×
395

396
void dskSelectMap::Draw_()
×
397
{
398
    if(!newRandMapPath.empty() || !randMapGenError.empty())
×
399
    {
400
        if(waitWnd)
×
401
        {
402
            waitWnd->Close();
×
403
            waitWnd = nullptr;
×
404
        }
405
        if(!randMapGenError.empty())
×
406
        {
407
            const std::string errorTxt = _("Failed to generate random map.\nReason: ") + randMapGenError;
×
408
            WINDOWMANAGER.Show(
×
409
              std::make_unique<iwMsgbox>(_("Error"), errorTxt, nullptr, MsgboxButton::Ok, MsgboxIcon::ExclamationRed));
×
410
        } else
411
            OnMapCreated(newRandMapPath);
×
412
        newRandMapPath.clear();
×
413
        randMapGenError.clear();
×
414
    }
415
    Desktop::Draw_();
×
416
}
×
417

418
void dskSelectMap::FillTable(const std::vector<bfs::path>& files)
×
419
{
420
    auto* table = GetCtrl<ctrlTable>(1);
×
421

422
    for(const bfs::path& filePath : files)
×
423
    {
424
        if(helpers::contains(brokenMapPaths, filePath))
×
425
            continue;
×
426
        // Karteninformationen laden
427
        libsiedler2::Archiv map;
×
428
        if(int ec = libsiedler2::loader::LoadMAP(filePath, map, true))
×
429
        {
430
            LOG.write(_("Failed to load map %1%: %2%\n")) % filePath % libsiedler2::getErrorString(ec);
×
431
            brokenMapPaths.insert(filePath);
×
432
            continue;
×
433
        }
434

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

438
        const bfs::path luaFilepath = bfs::path(filePath).replace_extension("lua");
×
439
        const bool hasLua = bfs::is_regular_file(luaFilepath);
×
440

441
        // Und Zeilen vorbereiten
442
        std::string players = (boost::format(_("%d Player")) % static_cast<unsigned>(header.getNumPlayers())).str();
×
443
        std::string size = helpers::toString(header.getWidth()) + "x" + helpers::toString(header.getHeight());
×
444

445
        std::string name = s25util::ansiToUTF8(header.getName());
×
446
        if(hasLua)
×
447
            name += " (*)";
×
448
        std::string author = s25util::ansiToUTF8(header.getAuthor());
×
449

450
        table->AddRow({name, author, players, landscapeNames[header.getGfxSet()], size, filePath.string()});
×
451
    }
452
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc