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

Return-To-The-Roots / s25client / 28242163216

26 Jun 2026 01:46PM UTC coverage: 50.449% (+0.07%) from 50.377%
28242163216

Pull #1949

github

web-flow
Merge 02cc02719 into d90bf9ac6
Pull Request #1949: Fix and extend AI-Battle

1 of 3 new or added lines in 2 files covered. (33.33%)

534 existing lines in 14 files now uncovered.

23270 of 46126 relevant lines covered (50.45%)

47704.69 hits per line

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

76.31
/libs/s25main/world/MapLoader.cpp
1
// Copyright (C) 2005 - 2026 Settlers Freaks (sf-team at siedler25.org)
2
//
3
// SPDX-License-Identifier: GPL-2.0-or-later
4

5
#include "world/MapLoader.h"
6
#include "Game.h"
7
#include "GamePlayer.h"
8
#include "GameWorldBase.h"
9
#include "GlobalGameSettings.h"
10
#include "PointOutput.h"
11
#include "RttrForeachPt.h"
12
#include "addons/const_addons.h"
13
#include "buildings/nobHQ.h"
14
#include "factories/BuildingFactory.h"
15
#include "helpers/IdRange.h"
16
#include "helpers/Range.h"
17
#include "helpers/containerUtils.h"
18
#include "helpers/mathFuncs.h"
19
#include "lua/GameDataLoader.h"
20
#include "pathfinding/PathConditionShip.h"
21
#include "random/Random.h"
22
#include "world/World.h"
23
#include "nodeObjs/noAnimal.h"
24
#include "nodeObjs/noEnvObject.h"
25
#include "nodeObjs/noGranite.h"
26
#include "nodeObjs/noStaticObject.h"
27
#include "nodeObjs/noTree.h"
28
#include "gameTypes/ShipDirection.h"
29
#include "gameData/MaxPlayers.h"
30
#include "gameData/TerrainDesc.h"
31
#include "libsiedler2/Archiv.h"
32
#include "libsiedler2/ArchivItem_Map.h"
33
#include "libsiedler2/ArchivItem_Map_Header.h"
34
#include "libsiedler2/prototypen.h"
35
#include "s25util/Log.h"
36
#include <boost/filesystem/operations.hpp>
37
#include <algorithm>
38
#include <map>
39
#include <queue>
40

41
class noBase;
42

43
MapLoader::MapLoader(GameWorldBase& world) : world_(world) {}
7✔
44

45
bool MapLoader::Load(const libsiedler2::ArchivItem_Map& map, Exploration exploration)
5✔
46
{
47
    GameDataLoader gdLoader(world_.GetDescriptionWriteable());
10✔
48
    if(!gdLoader.Load())
5✔
49
        return false;
×
50

51
    uint8_t gfxSet = map.getHeader().getGfxSet();
5✔
52
    DescIdx<LandscapeDesc> lt =
53
      world_.GetDescription().landscapes.find([gfxSet](const LandscapeDesc& l) { return l.s2Id == gfxSet; });
10✔
54
    world_.Init(MapExtent(map.getHeader().getWidth(), map.getHeader().getHeight()), lt); //-V807
5✔
55

56
    if(!InitNodes(map, exploration))
5✔
57
        return false;
×
58
    PlaceObjects(map);
5✔
59
    PlaceAnimals(map);
5✔
60
    if(!InitSeasAndHarbors(world_))
5✔
61
        return false;
×
62

63
    /// Schatten
64
    InitShadows(world_);
5✔
65

66
    // If we have explored FoW, create the FoW objects
67
    if(exploration == Exploration::FogOfWarExplored)
5✔
68
        SetMapExplored(world_);
×
69

70
    return true;
5✔
71
}
72

73
bool MapLoader::Load(const boost::filesystem::path& mapFilePath)
3✔
74
{
75
    // Map laden
76
    libsiedler2::Archiv mapArchiv;
6✔
77

78
    // Karteninformationen laden
79
    if(libsiedler2::loader::LoadMAP(mapFilePath, mapArchiv) != 0)
3✔
80
        return false;
×
81

82
    const libsiedler2::ArchivItem_Map& map = *static_cast<libsiedler2::ArchivItem_Map*>(mapArchiv[0]);
3✔
83

84
    if(!Load(map, world_.GetGGS().exploration))
3✔
85
        return false;
×
86
    if(!PlaceHQs())
3✔
87
        return false;
×
88

89
    return true;
3✔
90
}
91

92
bool MapLoader::LoadLuaScript(Game& game, ILocalGameState& localgameState, const boost::filesystem::path& luaFilePath)
3✔
93
{
94
    if(!bfs::exists(luaFilePath))
3✔
UNCOV
95
        return false;
×
96
    auto lua = std::make_unique<LuaInterfaceGame>(game, localgameState);
6✔
97
    if(!lua->loadScript(luaFilePath) || !lua->CheckScriptVersion())
3✔
98
        return false;
1✔
99
    game.SetLua(std::move(lua));
2✔
100
    return true;
2✔
101
}
102

103
bool MapLoader::PlaceHQs(bool addStartWares)
3✔
104
{
105
    std::vector<MapPoint> hqPositions = hqPositions_;
6✔
106
    if(world_.GetGGS().randomStartPosition)
3✔
107
        RANDOM_SHUFFLE2(hqPositions, 0);
1✔
108
    return PlaceHQs(world_, hqPositions, addStartWares);
6✔
109
}
110

111
void MapLoader::SetupResources(GameWorldBase& world, const bool fixFish)
3✔
112
{
113
    ResourceType target;
114
    switch(world.GetGGS().getSelection(AddonId::CHANGE_GOLD_DEPOSITS))
3✔
115
    {
116
        case 0:
3✔
117
        default: target = ResourceType::Gold; break;
3✔
UNCOV
118
        case 1: target = ResourceType::Nothing; break;
×
UNCOV
119
        case 2: target = ResourceType::Iron; break;
×
120
        case 3: target = ResourceType::Coal; break;
×
121
        case 4: target = ResourceType::Granite; break;
×
122
    }
123
    ConvertMineResourceTypes(world, ResourceType::Gold, target);
3✔
124
    PlaceAndFixWater(world);
3✔
125
    if(fixFish)
3✔
126
        RemoveUnusableFishResources(world);
3✔
127
}
3✔
128

129
void MapLoader::ConvertMineResourceTypes(GameWorldBase& world, ResourceType from, ResourceType to)
3✔
130
{
131
    // LOG.write(("Convert map resources from %i to %i\n", from, to);
132
    if(from == to)
3✔
133
        return;
3✔
134

UNCOV
135
    RTTR_FOREACH_PT(MapPoint, world.GetSize())
×
136
    {
137
        Resource resources = world.GetNode(pt).resources;
×
138
        // Gibt es Ressourcen dieses Typs?
139
        // Wenn ja, dann umwandeln bzw löschen
UNCOV
140
        if(resources.getType() == from)
×
141
        {
142
            resources.setType(to);
×
UNCOV
143
            world.SetResource(pt, resources);
×
144
        }
145
    }
146
}
147

148
void MapLoader::PlaceAndFixWater(GameWorldBase& world)
3✔
149
{
150
    const bool waterEverywhere = world.GetGGS().getSelection(AddonId::EXHAUSTIBLE_WATER) == 1;
3✔
151

152
    RTTR_FOREACH_PT(MapPoint, world.GetSize())
1,383✔
153
    {
154
        Resource curNodeResource = world.GetNode(pt).resources;
1,320✔
155

156
        if(curNodeResource.getType() == ResourceType::Nothing)
1,320✔
157
        {
158
            if(!waterEverywhere)
1,313✔
159
                continue;
1,320✔
160
        } else if(curNodeResource.getType() != ResourceType::Water)
7✔
161
            continue; // do not override maps resource.
7✔
162

UNCOV
163
        uint8_t minHumidity = 100;
×
UNCOV
164
        for(const DescIdx<TerrainDesc> tIdx : world.GetTerrainsAround(pt))
×
165
        {
166
            const uint8_t curHumidity = world.GetDescription().get(tIdx).humidity;
×
UNCOV
167
            if(curHumidity < minHumidity)
×
168
            {
169
                minHumidity = curHumidity;
×
UNCOV
170
                if(minHumidity == 0)
×
171
                    break;
×
172
            }
173
        }
UNCOV
174
        if(minHumidity)
×
175
        {
176
            curNodeResource =
×
UNCOV
177
              Resource(ResourceType::Water, waterEverywhere ? 7 : helpers::iround<uint8_t>(minHumidity * 7. / 100.));
×
178
        } else
179
            curNodeResource = Resource(ResourceType::Nothing, 0);
×
180

181
        world.SetResource(pt, curNodeResource);
×
182
    }
183
}
3✔
184

185
void MapLoader::RemoveUnusableFishResources(GameWorldBase& world)
3✔
186
{
187
    const auto isWaterPoint = [&world](const MapPoint nb) { return world.IsWaterPoint(nb); };
28✔
188
    for(const MapCoord y : helpers::range(world.GetHeight()))
132✔
189
    {
190
        // Optimization: When there was fish on the previous node (in the same row)
191
        // we do not need to check for isolated water points, as there is at least that water point
192
        bool previousHasFish = false;
60✔
193
        for(const MapCoord x : helpers::range(world.GetWidth()))
2,880✔
194
        {
195
            const MapPoint pt(x, y);
1,320✔
196
            bool hasFish = false;
1,320✔
197

198
            if(world.GetNode(pt).resources.has(ResourceType::Fish))
1,320✔
199
            {
200
                if(isWaterPoint(pt) && (previousHasFish || helpers::contains_if(world.GetNeighbours(pt), isWaterPoint)))
7✔
201
                    hasFish = true;
5✔
202
                else
203
                    world.SetResource(pt, Resource(ResourceType::Nothing, 0));
2✔
204
            }
205
            previousHasFish = hasFish;
1,320✔
206
        }
207
    }
208
}
3✔
209

210
void MapLoader::InitShadows(World& world)
5✔
211
{
212
    RTTR_FOREACH_PT(MapPoint, world.GetSize())
70,805✔
213
        world.RecalcShadow(pt);
70,400✔
214
}
5✔
215

UNCOV
216
void MapLoader::SetMapExplored(World& world)
×
217
{
218
    RTTR_FOREACH_PT(MapPoint, world.GetSize())
×
219
    {
220
        // For every player
UNCOV
221
        for(const auto i : helpers::range(MAX_PLAYERS))
×
222
        {
223
            // If we have FoW here, save it
UNCOV
224
            if(world.GetNode(pt).fow[i].visibility == Visibility::FogOfWar)
×
UNCOV
225
                world.SaveFOWNode(pt, i, 0);
×
226
        }
227
    }
UNCOV
228
}
×
229

230
DescIdx<TerrainDesc> MapLoader::getTerrainFromS2(uint8_t s2Id) const
140,800✔
231
{
232
    return world_.GetDescription().terrain.find([s2Id, landscape = world_.GetLandscapeType()](const TerrainDesc& t) {
140,800✔
233
        return t.s2Id == s2Id && t.landscape == landscape;
1,442,160✔
234
    });
140,800✔
235
}
236

237
bool MapLoader::InitNodes(const libsiedler2::ArchivItem_Map& map, Exploration exploration)
5✔
238
{
239
    using libsiedler2::MapLayer;
240
    // Init node data (everything except the objects, figures and BQ)
241
    RTTR_FOREACH_PT(MapPoint, world_.GetSize())
70,805✔
242
    {
243
        MapNode& node = world_.GetNodeInt(pt);
70,400✔
244

245
        std::fill(node.roads.begin(), node.roads.end(), PointRoad::None);
70,400✔
246
        node.altitude = map.getMapDataAt(MapLayer::Altitude, pt.x, pt.y);
70,400✔
247
        unsigned char t1 = map.getMapDataAt(MapLayer::Terrain1, pt.x, pt.y),
70,400✔
248
                      t2 = map.getMapDataAt(MapLayer::Terrain2, pt.x, pt.y);
70,400✔
249

250
        // Hafenplatz?
251
        if((t1 & libsiedler2::HARBOR_MASK) != 0)
70,400✔
UNCOV
252
            world_.harborData.push_back(HarborPos(pt));
×
253

254
        // Will be set later
255
        node.harborId.reset();
70,400✔
256

257
        node.t1 = getTerrainFromS2(t1 & 0x3F); // Only lower 6 bits
70,400✔
258
        node.t2 = getTerrainFromS2(t2 & 0x3F); // Only lower 6 bits
70,400✔
259
        if(!node.t1 || !node.t2)
70,400✔
UNCOV
260
            return false;
×
261

262
        unsigned char mapResource = map.getMapDataAt(MapLayer::Resources, pt.x, pt.y);
70,400✔
263
        Resource resource;
70,400✔
264
        // Wasser?
265
        if(mapResource == 0x20 || mapResource == 0x21)
70,400✔
266
            resource = Resource(ResourceType::Water, 7);
36,625✔
267
        else if(mapResource > 0x40 && mapResource < 0x48)
33,775✔
268
            resource = Resource(ResourceType::Coal, mapResource - 0x40);
9,815✔
269
        else if(mapResource > 0x48 && mapResource < 0x50)
23,960✔
270
            resource = Resource(ResourceType::Iron, mapResource - 0x48);
2,525✔
271
        else if(mapResource > 0x50 && mapResource < 0x58)
21,435✔
272
            resource = Resource(ResourceType::Gold, mapResource - 0x50);
1,660✔
273
        else if(mapResource > 0x58 && mapResource < 0x60)
19,775✔
274
            resource = Resource(ResourceType::Granite, mapResource - 0x58);
550✔
275
        else if(mapResource > 0x80 && mapResource < 0x90) // fish
19,225✔
276
            resource = Resource(ResourceType::Fish, 4);   // Use 4 fish
2,420✔
277
        node.resources = resource;
70,400✔
278

279
        node.reserved = false;
70,400✔
280
        node.owner = 0;
70,400✔
281
        std::fill(node.boundary_stones.begin(), node.boundary_stones.end(), 0);
70,400✔
282
        node.seaId.reset();
70,400✔
283

284
        Visibility fowVisibility;
285
        switch(exploration)
70,400✔
286
        {
UNCOV
287
            case Exploration::Disabled: fowVisibility = Visibility::Visible; break;
×
288
            case Exploration::Classic:
70,400✔
289
            case Exploration::FogOfWar: fowVisibility = Visibility::Invisible; break;
70,400✔
UNCOV
290
            case Exploration::FogOfWarExplored: fowVisibility = Visibility::FogOfWar; break;
×
UNCOV
291
            default: throw std::invalid_argument("Visibility for FoW");
×
292
        }
293

294
        // FOW-Zeug initialisieren
295
        for(auto& fow : node.fow)
633,600✔
296
        {
297
            fow = FoWNode();
563,200✔
298
            fow.visibility = fowVisibility;
563,200✔
299
        }
300

301
        RTTR_Assert(node.figures.empty());
70,400✔
302
    }
303
    return true;
5✔
304
}
305

306
void MapLoader::PlaceObjects(const libsiedler2::ArchivItem_Map& map)
5✔
307
{
308
    hqPositions_.clear();
5✔
309

310
    RTTR_FOREACH_PT(MapPoint, world_.GetSize())
70,805✔
311
    {
312
        using libsiedler2::MapLayer;
313
        unsigned char lc = map.getMapDataAt(MapLayer::ObjectIndex, pt.x, pt.y);
70,400✔
314
        noBase* obj = nullptr;
70,400✔
315

316
        switch(map.getMapDataAt(MapLayer::ObjectType, pt.x, pt.y))
70,400✔
317
        {
318
            // Player Startpos (provisorisch)
319
            case 0x80:
20✔
320
            {
321
                if(lc < MAX_PLAYERS)
20✔
322
                {
323
                    while(hqPositions_.size() <= lc)
40✔
324
                        hqPositions_.push_back(MapPoint::Invalid());
20✔
325
                    hqPositions_[lc] = pt;
20✔
326
                }
327
            }
328
            break;
20✔
329

330
            // Baum 1-4
331
            case 0xC4:
17,640✔
332
            {
333
                if(lc >= 0x30 && lc <= 0x3D)
17,640✔
334
                    obj = new noTree(pt, 0, 3);
5,610✔
335
                else if(lc >= 0x70 && lc <= 0x7D)
12,030✔
336
                    obj = new noTree(pt, 1, 3);
5,860✔
337
                else if(lc >= 0xB0 && lc <= 0xBD)
6,170✔
338
                    obj = new noTree(pt, 2, 3);
5,555✔
339
                else if(lc >= 0xF0 && lc <= 0xFD)
615✔
340
                    obj = new noTree(pt, 3, 3);
615✔
341
                else
UNCOV
342
                    LOG.write(_("Unknown tree1-4 at %1%: (0x%2$x)\n")) % pt % unsigned(lc);
×
343
            }
344
            break;
17,640✔
345

346
            // Baum 5-8
UNCOV
347
            case 0xC5:
×
348
            {
349
                if(lc >= 0x30 && lc <= 0x3D)
×
UNCOV
350
                    obj = new noTree(pt, 4, 3);
×
351
                else if(lc >= 0x70 && lc <= 0x7D)
×
352
                    obj = new noTree(pt, 5, 3);
×
353
                else if(lc >= 0xB0 && lc <= 0xBD)
×
354
                    obj = new noTree(pt, 6, 3);
×
355
                else if(lc >= 0xF0 && lc <= 0xFD)
×
356
                    obj = new noTree(pt, 7, 3);
×
357
                else
358
                    LOG.write(_("Unknown tree5-8 at %1%: (0x%2$x)\n")) % pt % unsigned(lc);
×
359
            }
360
            break;
×
361

362
            // Baum 9
UNCOV
363
            case 0xC6:
×
364
            {
365
                if(lc >= 0x30 && lc <= 0x3D)
×
UNCOV
366
                    obj = new noTree(pt, 8, 3);
×
367
                else
368
                    LOG.write(_("Unknown tree9 at %1%: (0x%2$x)\n")) % pt % unsigned(lc);
×
369
            }
370
            break;
×
371

372
            // Sonstiges Naturzeug ohne Funktion, nur zur Dekoration
373
            case 0xC8:
3,740✔
374
            case 0xC9: // Note: 0xC9 is actually a bug and should be 0xC8. But the random map generator produced that...
375
            {
376
                // "wasserstein" aus der map_?_z.lst
377
                if(lc == 0x0B)
3,740✔
UNCOV
378
                    obj = new noStaticObject(pt, 500 + lc);
×
379
                // Objekte aus der map_?_z.lst
380
                else if(lc <= 0x0F)
3,740✔
381
                    obj = new noEnvObject(pt, 500 + lc);
1,615✔
382
                // Objekte aus der map.lst
383
                else if(lc <= 0x14)
2,125✔
384
                    obj = new noEnvObject(pt, 542 + lc - 0x10);
1,580✔
385
                // exists in mis0bobs-mis5bobs -> take stranded ship
386
                else if(lc == 0x15)
545✔
UNCOV
387
                    obj = new noStaticObject(pt, 0, 0);
×
388
                // gate
389
                else if(lc == 0x16)
545✔
UNCOV
390
                    obj = new noStaticObject(pt, 560);
×
391
                // open gate
392
                else if(lc == 0x17)
545✔
UNCOV
393
                    obj = new noStaticObject(pt, 561);
×
394
                // Stalagmiten (mis1bobs)
395
                else if(lc <= 0x1E)
545✔
UNCOV
396
                    obj = new noStaticObject(pt, (lc - 0x18) * 2, 1);
×
397
                // toter Baum (mis1bobs)
398
                else if(lc <= 0x20)
545✔
UNCOV
399
                    obj = new noStaticObject(pt, 20 + (lc - 0x1F) * 2, 1);
×
400
                // Gerippe (mis1bobs)
401
                else if(lc == 0x21)
545✔
UNCOV
402
                    obj = new noStaticObject(pt, 30, 1);
×
403
                // Objekte aus der map.lst
404
                else if(lc <= 0x2B)
545✔
405
                    obj = new noEnvObject(pt, 550 + lc - 0x22);
545✔
406
                // tent, ruin of guardhouse, tower ruin, cross
UNCOV
407
                else if(lc <= 0x2E || lc == 0x30)
×
UNCOV
408
                    obj = new noStaticObject(pt, (lc - 0x2C) * 2, 2);
×
409
                // castle ruin
410
                else if(lc == 0x2F)
×
UNCOV
411
                    obj = new noStaticObject(pt, (lc - 0x2C) * 2, 2, 2);
×
412
                // small wiking with boat
413
                else if(lc == 0x31)
×
UNCOV
414
                    obj = new noStaticObject(pt, 0, 3);
×
415
                // Pile of wood
416
                else if(lc == 0x32)
×
UNCOV
417
                    obj = new noStaticObject(pt, 0, 4);
×
418
                // whale skeleton (head right)
419
                else if(lc == 0x33)
×
UNCOV
420
                    obj = new noStaticObject(pt, 0, 5);
×
421
                // The next 2 are non standard and only for access in RTTR (replaced in original by
422
                // whale skeleton (head left)
UNCOV
423
                else if(lc == 0x34)
×
UNCOV
424
                    obj = new noStaticObject(pt, 2, 5);
×
425
                // Cave
426
                else if(lc == 0x35)
×
UNCOV
427
                    obj = new noStaticObject(pt, 4, 5);
×
428
                else
429
                    LOG.write(_("Unknown nature object at %1%: (0x%2$x)\n")) % pt % unsigned(lc);
×
430
            }
431
            break;
3,740✔
432

433
            // Granit Typ 1
434
            case 0xCC:
1,370✔
435
            {
436
                if(lc >= 0x01 && lc <= 0x06)
1,370✔
437
                    obj = new noGranite(GraniteType::One, lc - 1);
1,370✔
438
                else
UNCOV
439
                    LOG.write(_("Unknown granite type2 at %1%: (0x%2$x)\n")) % pt % unsigned(lc);
×
440
            }
441
            break;
1,370✔
442

443
            // Granit Typ 2
444
            case 0xCD:
1,395✔
445
            {
446
                if(lc >= 0x01 && lc <= 0x06)
1,395✔
447
                    obj = new noGranite(GraniteType::Two, lc - 1);
1,395✔
448
                else
UNCOV
449
                    LOG.write(_("Unknown granite type2 at %1%: (0x%2$x)\n")) % pt % unsigned(lc);
×
450
            }
451
            break;
1,395✔
452

453
            // Nichts
454
            case 0: break;
46,235✔
455

UNCOV
456
            default:
×
457
#ifndef NDEBUG
458
                unsigned unknownObj = map.getMapDataAt(MapLayer::ObjectType, pt.x, pt.y);
×
UNCOV
459
                LOG.write(_("Unknown object at %1%: (0x%2$x: 0x%3$x)\n")) % pt % unknownObj % unsigned(lc);
×
460
#endif // !NDEBUG
461
                break;
×
462
        }
463

464
        world_.GetNodeInt(pt).obj = obj;
70,400✔
465
    }
466
}
5✔
467

468
void MapLoader::PlaceAnimals(const libsiedler2::ArchivItem_Map& map)
5✔
469
{
470
    // Tiere auslesen
471
    RTTR_FOREACH_PT(MapPoint, world_.GetSize())
70,805✔
472
    {
473
        Species species;
474
        using libsiedler2::MapLayer;
475
        switch(map.getMapDataAt(MapLayer::Animals, pt.x, pt.y))
70,400✔
476
        {
477
            // TODO: Which id is the polar bear?
478
            case 1:
175✔
479
                species = (RANDOM.Rand(RANDOM_CONTEXT2(0), 2) == 0) ? Species::RabbitWhite : Species::RabbitGrey;
175✔
480
                break; // Random rabbit
175✔
481
            case 2: species = Species::Fox; break;
140✔
UNCOV
482
            case 3: species = Species::Stag; break;
×
483
            case 4: species = Species::Deer; break;
140✔
484
            case 5: species = Species::Duck; break;
×
485
            case 6: species = Species::Sheep; break;
70✔
486
            case 0:
69,875✔
487
            case 0xFF: // 0xFF is for (really) old S2 maps
488
                continue;
69,875✔
UNCOV
489
            default:
×
490
#ifndef NDEBUG
491
                unsigned unknownAnimal = map.getMapDataAt(MapLayer::Animals, pt.x, pt.y);
×
UNCOV
492
                LOG.write(_("Unknown animal species at %1%: (0x%2$x)\n")) % pt % unknownAnimal;
×
493
#endif // !NDEBUG
494
                continue;
69,875✔
495
        }
496

497
        world_.AddFigure(pt, std::make_unique<noAnimal>(species, pt)).StartLiving();
525✔
498
    }
499
}
5✔
500

501
bool MapLoader::PlaceHQs(GameWorldBase& world, const std::vector<MapPoint>& hqPositions, const bool addStartWares)
181✔
502
{
503
    for(unsigned i = 0; i < world.GetNumPlayers(); ++i)
563✔
504
    {
505
        // Skip unused slots
506
        if(!world.GetPlayer(i).isUsed())
382✔
507
            continue;
13✔
508

509
        // Does the HQ have a position?
510
        if(i >= hqPositions.size() || !hqPositions[i].isValid())
369✔
511
        {
UNCOV
512
            LOG.write(_("Player %u does not have a valid start position!")) % i;
×
UNCOV
513
            return false;
×
514
        }
515

516
        auto* hq = checkedCast<nobHQ*>(BuildingFactory::CreateBuilding(world, BuildingType::Headquarters,
369✔
517
                                                                       hqPositions[i], i, world.GetPlayer(i).nation));
369✔
518
        if(addStartWares)
369✔
519
            hq->addStartWares();
8✔
520
    }
521
    return true;
181✔
522
}
523

524
bool MapLoader::InitSeasAndHarbors(World& world, const std::vector<MapPoint>& additionalHarbors)
30✔
525
{
526
    for(MapPoint pt : additionalHarbors)
204✔
527
        world.harborData.push_back(HarborPos(pt));
174✔
528
    // Clear current harbors and seas
529
    RTTR_FOREACH_PT(MapPoint, world.GetSize()) //-V807
241,742✔
530
    {
531
        MapNode& node = world.GetNodeInt(pt);
238,764✔
532
        node.seaId.reset();
238,764✔
533
        node.harborId.reset();
238,764✔
534
    }
535

536
    /// Determine all seas
537
    world.seas.clear();
30✔
538
    SeaId curSeaId(1);
30✔
539
    RTTR_FOREACH_PT(MapPoint, world.GetSize())
241,742✔
540
    {
541
        // Point is not yet assigned a sea but should be
542
        if(!world.GetNode(pt).seaId && world.IsSeaPoint(pt))
238,764✔
543
        {
544
            const auto seaSize = MeasureSea(world, pt, curSeaId);
52✔
545
            world.seas.push_back(World::Sea(seaSize));
52✔
546
            curSeaId = curSeaId.next();
52✔
547
        }
548
    }
549

550
    /// Determine seas adjacent to the harbor places
551
    HarborId curHarborId(1);
30✔
552
    for(auto it = world.harborData.begin(); it != world.harborData.end();)
228✔
553
    {
554
        helpers::StrongIdVector<bool, SeaId> hasCoastAtSea(world.seas.size(), false);
396✔
555
        bool foundCoast = false;
198✔
556
        for(const auto dir : helpers::EnumRange<Direction>{})
3,168✔
557
        {
558
            SeaId seaId;
1,188✔
559
            // Skip point at NW as often there is no path from it if the harbor is north of an island
560
            if(dir != Direction::NorthWest)
1,188✔
561
            {
562
                seaId = world.GetSeaFromCoastalPoint(world.GetNeighbour(it->pos, dir));
990✔
563
                if(seaId)
990✔
564
                {
565
                    foundCoast = true;
256✔
566
                    // Only 1 coastal point per sea
567
                    if(hasCoastAtSea[seaId])
256✔
568
                        seaId.reset();
58✔
569
                    else
570
                        hasCoastAtSea[seaId] = true;
198✔
571
                }
572
            }
573
            it->seaIds[dir] = seaId;
1,188✔
574
        }
575
        if(!foundCoast)
198✔
576
        {
UNCOV
577
            LOG.write("Map Bug: Found harbor without coast at %1%. Removing!\n") % it->pos;
×
UNCOV
578
            it = world.harborData.erase(it);
×
579
        } else
580
        {
581
            world.GetNodeInt(it->pos).harborId = curHarborId;
198✔
582
            curHarborId = curHarborId.next();
198✔
583
            ++it;
198✔
584
        }
585
    }
586

587
    // Calculate the neighbors and distances
588
    CalcHarborPosNeighbors(world);
30✔
589
    return true;
30✔
590
}
591

592
// class for finding harbor neighbors
593
struct CalcHarborPosNeighborsNode
594
{
595
    CalcHarborPosNeighborsNode() = default; //-V730
596
    CalcHarborPosNeighborsNode(const MapPoint pt, unsigned distance) : pos(pt), distance(distance) {}
343,624✔
597

598
    MapPoint pos;
599
    unsigned distance;
600
};
601

602
/// Calculate the distance from each harbor to the others
603
void MapLoader::CalcHarborPosNeighbors(World& world)
30✔
604
{
605
    for(HarborPos& harbor : world.harborData)
228✔
606
    {
607
        for(const auto dir : helpers::EnumRange<ShipDirection>{})
3,168✔
608
            harbor.neighbors[dir].clear();
1,188✔
609
    }
610
    PathConditionShip shipPathChecker(world);
30✔
611

612
    // pre-calculate sea-points, as IsSeaPoint is rather expensive
613
    std::vector<int8_t> ptIsSeaPt(world.nodes.size()); //-V656
60✔
614

615
    RTTR_FOREACH_PT(MapPoint, world.GetSize())
241,742✔
616
    {
617
        if(shipPathChecker.IsNodeOk(pt))
238,764✔
618
            ptIsSeaPt[world.GetIdx(pt)] = -1;
91,980✔
619
    }
620

621
    // FIFO queue used for a BFS
622
    std::queue<CalcHarborPosNeighborsNode> todo_list;
60✔
623

624
    for(const auto startHbId : helpers::idRange<HarborId>(world.harborData.size()))
228✔
625
    {
626
        RTTR_Assert(todo_list.empty());
198✔
627

628
        // Copy sea points to working flags. Possible values are
629
        // -1 - sea point, not already visited
630
        // 0 - visited or no sea point
631
        // 1 - Coast to a harbor
632
        std::vector<int8_t> ptToVisitOrHb(ptIsSeaPt);
396✔
633

634
        helpers::StrongIdVector<bool, HarborId> hbFound(world.harborData.size(), false);
396✔
635
        // For each sea, store the coastal point indices and their harbor
636
        helpers::StrongIdVector<std::multimap<unsigned, HarborId>, SeaId> coastToHarborPerSea(world.seas.size());
396✔
637
        std::vector<MapPoint> ownCoastalPoints;
396✔
638

639
        // mark coastal points around harbors
640
        for(const auto otherHbId : helpers::idRange<HarborId>(world.harborData.size()))
1,816✔
641
        {
642
            for(const auto dir : helpers::EnumRange<Direction>{})
25,888✔
643
            {
644
                SeaId seaId = world.GetSeaId(otherHbId, dir);
9,708✔
645
                // No sea? -> Next
646
                if(!seaId)
9,708✔
647
                    continue;
8,090✔
648
                const MapPoint coastPt = world.GetNeighbour(world.GetHarborPoint(otherHbId), dir);
1,618✔
649
                // This should not be marked for visit
650
                unsigned idx = world.GetIdx(coastPt);
1,618✔
651
                RTTR_Assert(ptToVisitOrHb[idx] != -1);
1,618✔
652
                if(otherHbId == startHbId)
1,618✔
653
                {
654
                    // This is our start harbor. Add the coast points around it to our todo list.
655
                    ownCoastalPoints.push_back(coastPt);
198✔
656
                } else
657
                {
658
                    ptToVisitOrHb[idx] = 1;
1,420✔
659
                    coastToHarborPerSea[seaId].insert(std::make_pair(idx, otherHbId));
1,420✔
660
                }
661
            }
662
        }
663

664
        for(const MapPoint& ownCoastPt : ownCoastalPoints)
396✔
665
        {
666
            // Special case: Get all harbors that share the coast point with us
667
            const SeaId seaId = world.GetSeaFromCoastalPoint(ownCoastPt);
198✔
668
            auto const coastToHbs = coastToHarborPerSea[seaId].equal_range(world.GetIdx(ownCoastPt));
198✔
669
            for(auto it = coastToHbs.first; it != coastToHbs.second; ++it)
204✔
670
            {
671
                ShipDirection shipDir = world.GetShipDir(ownCoastPt, ownCoastPt);
6✔
672
                world.harborData[startHbId].neighbors[shipDir].push_back(HarborPos::Neighbor(it->second, seaId, 0));
6✔
673
                hbFound[it->second] = true;
6✔
674
            }
675
            todo_list.push(CalcHarborPosNeighborsNode(ownCoastPt, 0));
198✔
676
        }
677

678
        while(!todo_list.empty()) // as long as there are sea points on our todo list...
343,822✔
679
        {
680
            CalcHarborPosNeighborsNode curNode = todo_list.front();
343,624✔
681
            todo_list.pop();
343,624✔
682

683
            for(const auto dir : helpers::EnumRange<Direction>{})
5,497,984✔
684
            {
685
                MapPoint curPt = world.GetNeighbour(curNode.pos, dir);
2,061,744✔
686
                unsigned idx = world.GetIdx(curPt);
2,061,744✔
687

688
                const int8_t ptValue = ptToVisitOrHb[idx];
2,061,744✔
689
                // Already visited
690
                if(ptValue == 0)
2,061,744✔
691
                    continue;
1,718,318✔
692
                // Not reachable
693
                if(!shipPathChecker.IsEdgeOk(curNode.pos, dir))
343,442✔
694
                    continue;
16✔
695

696
                if(ptValue > 0) // found harbor(s)
343,426✔
697
                {
698
                    const ShipDirection shipDir = world.GetShipDir(world.harborData[startHbId].pos, curPt);
669✔
699
                    const SeaId seaId = world.GetSeaFromCoastalPoint(curPt);
669✔
700
                    RTTR_Assert(seaId);
669✔
701
                    auto const coastToHbs = coastToHarborPerSea[seaId].equal_range(idx);
669✔
702
                    for(auto it = coastToHbs.first; it != coastToHbs.second; ++it)
1,361✔
703
                    {
704
                        const HarborId otherHbId = it->second;
692✔
705
                        if(hbFound[otherHbId])
692✔
706
                            continue;
6✔
707

708
                        hbFound[otherHbId] = true;
686✔
709
                        world.harborData[startHbId].neighbors[shipDir].push_back(
1,372✔
710
                          HarborPos::Neighbor(otherHbId, seaId, curNode.distance + 1));
686✔
711

712
                        // Make this the only coastal point of this harbor for this sea
713
                        HarborPos& otherHb = world.harborData[otherHbId];
686✔
714
                        for(const auto hbDir : helpers::EnumRange<Direction>{})
10,976✔
715
                        {
716
                            if(otherHb.seaIds[hbDir] == seaId && world.GetNeighbour(otherHb.pos, hbDir) != curPt)
4,116✔
UNCOV
717
                                otherHb.seaIds[hbDir].reset();
×
718
                        }
719
                    }
720
                }
721
                todo_list.push(CalcHarborPosNeighborsNode(curPt, curNode.distance + 1));
343,426✔
722
                ptToVisitOrHb[idx] = 0; // mark as visited, so we do not go here again
343,426✔
723
            }
724
        }
725
    }
726
}
30✔
727

728
/// Vermisst ein neues Weltmeer von einem Punkt aus, indem es alle mit diesem Punkt verbundenen
729
/// Wasserpunkte mit der gleichen ID belegt und die Anzahl zur�ckgibt
730
unsigned MapLoader::MeasureSea(World& world, const MapPoint start, SeaId seaId)
52✔
731
{
732
    // Breitensuche von diesem Punkt aus durchf�hren
733
    std::vector<bool> visited(world.GetWidth() * world.GetHeight(), false);
104✔
734
    std::queue<MapPoint> todo;
52✔
735

736
    todo.push(start);
52✔
737
    visited[world.GetIdx(start)] = true;
52✔
738

739
    // Count of nodes (including start node)
740
    unsigned count = 0;
52✔
741

742
    while(!todo.empty())
92,032✔
743
    {
744
        MapPoint p = todo.front();
91,980✔
745
        todo.pop();
91,980✔
746

747
        RTTR_Assert(visited[world.GetIdx(p)]);
91,980✔
748
        world.GetNodeInt(p).seaId = seaId;
91,980✔
749

750
        for(const MapPoint neighbourPt : world.GetNeighbours(p))
643,860✔
751
        {
752
            if(visited[world.GetIdx(neighbourPt)])
551,880✔
753
                continue;
446,635✔
754
            visited[world.GetIdx(neighbourPt)] = true;
105,245✔
755

756
            // Ist das dort auch ein Meerespunkt?
757
            if(world.IsSeaPoint(neighbourPt))
105,245✔
758
                todo.push(neighbourPt);
91,928✔
759
        }
760

761
        ++count;
91,980✔
762
    }
763

764
    return count;
104✔
765
}
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