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

Return-To-The-Roots / s25client / 24151579386

08 Apr 2026 06:24PM UTC coverage: 50.371% (+0.03%) from 50.337%
24151579386

push

github

Flow86
Use `helpers::sort` consistently

14 of 18 new or added lines in 12 files covered. (77.78%)

840 existing lines in 36 files now uncovered.

23069 of 45798 relevant lines covered (50.37%)

45802.94 hits per line

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

78.25
/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 "buildings/nobHQ.h"
13
#include "factories/BuildingFactory.h"
14
#include "helpers/IdRange.h"
15
#include "lua/GameDataLoader.h"
16
#include "pathfinding/PathConditionShip.h"
17
#include "random/Random.h"
18
#include "world/World.h"
19
#include "nodeObjs/noAnimal.h"
20
#include "nodeObjs/noEnvObject.h"
21
#include "nodeObjs/noGranite.h"
22
#include "nodeObjs/noStaticObject.h"
23
#include "nodeObjs/noTree.h"
24
#include "gameTypes/ShipDirection.h"
25
#include "gameData/MaxPlayers.h"
26
#include "gameData/TerrainDesc.h"
27
#include "libsiedler2/Archiv.h"
28
#include "libsiedler2/ArchivItem_Map.h"
29
#include "libsiedler2/ArchivItem_Map_Header.h"
30
#include "libsiedler2/prototypen.h"
31
#include "s25util/Log.h"
32
#include <boost/filesystem/operations.hpp>
33
#include <algorithm>
34
#include <map>
35
#include <queue>
36

37
class noBase;
38

39
MapLoader::MapLoader(GameWorldBase& world) : world_(world) {}
7✔
40

41
bool MapLoader::Load(const libsiedler2::ArchivItem_Map& map, Exploration exploration)
5✔
42
{
43
    GameDataLoader gdLoader(world_.GetDescriptionWriteable());
10✔
44
    if(!gdLoader.Load())
5✔
UNCOV
45
        return false;
×
46

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

52
    if(!InitNodes(map, exploration))
5✔
UNCOV
53
        return false;
×
54
    PlaceObjects(map);
5✔
55
    PlaceAnimals(map);
5✔
56
    if(!InitSeasAndHarbors(world_))
5✔
UNCOV
57
        return false;
×
58

59
    /// Schatten
60
    InitShadows(world_);
5✔
61

62
    // If we have explored FoW, create the FoW objects
63
    if(exploration == Exploration::FogOfWarExplored)
5✔
UNCOV
64
        SetMapExplored(world_);
×
65

66
    return true;
5✔
67
}
68

69
bool MapLoader::Load(const boost::filesystem::path& mapFilePath)
3✔
70
{
71
    // Map laden
72
    libsiedler2::Archiv mapArchiv;
6✔
73

74
    // Karteninformationen laden
75
    if(libsiedler2::loader::LoadMAP(mapFilePath, mapArchiv) != 0)
3✔
UNCOV
76
        return false;
×
77

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

80
    if(!Load(map, world_.GetGGS().exploration))
3✔
UNCOV
81
        return false;
×
82
    if(!PlaceHQs())
3✔
UNCOV
83
        return false;
×
84

85
    world_.CreateTradeGraphs();
3✔
86

87
    return true;
3✔
88
}
89

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

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

109
void MapLoader::InitShadows(World& world)
5✔
110
{
111
    RTTR_FOREACH_PT(MapPoint, world.GetSize())
70,805✔
112
        world.RecalcShadow(pt);
70,400✔
113
}
5✔
114

UNCOV
115
void MapLoader::SetMapExplored(World& world)
×
116
{
UNCOV
117
    RTTR_FOREACH_PT(MapPoint, world.GetSize())
×
118
    {
119
        // For every player
120
        for(unsigned i = 0; i < MAX_PLAYERS; ++i)
×
121
        {
122
            // If we have FoW here, save it
123
            if(world.GetNode(pt).fow[i].visibility == Visibility::FogOfWar)
×
UNCOV
124
                world.SaveFOWNode(pt, i, 0);
×
125
        }
126
    }
UNCOV
127
}
×
128

129
DescIdx<TerrainDesc> MapLoader::getTerrainFromS2(uint8_t s2Id) const
140,800✔
130
{
131
    return world_.GetDescription().terrain.find([s2Id, landscape = world_.GetLandscapeType()](const TerrainDesc& t) {
140,800✔
132
        return t.s2Id == s2Id && t.landscape == landscape;
1,442,160✔
133
    });
140,800✔
134
}
135

136
bool MapLoader::InitNodes(const libsiedler2::ArchivItem_Map& map, Exploration exploration)
5✔
137
{
138
    using libsiedler2::MapLayer;
139
    // Init node data (everything except the objects, figures and BQ)
140
    RTTR_FOREACH_PT(MapPoint, world_.GetSize())
70,805✔
141
    {
142
        MapNode& node = world_.GetNodeInt(pt);
70,400✔
143

144
        std::fill(node.roads.begin(), node.roads.end(), PointRoad::None);
70,400✔
145
        node.altitude = map.getMapDataAt(MapLayer::Altitude, pt.x, pt.y);
70,400✔
146
        unsigned char t1 = map.getMapDataAt(MapLayer::Terrain1, pt.x, pt.y),
70,400✔
147
                      t2 = map.getMapDataAt(MapLayer::Terrain2, pt.x, pt.y);
70,400✔
148

149
        // Hafenplatz?
150
        if((t1 & libsiedler2::HARBOR_MASK) != 0)
70,400✔
UNCOV
151
            world_.harborData.push_back(HarborPos(pt));
×
152

153
        // Will be set later
154
        node.harborId.reset();
70,400✔
155

156
        node.t1 = getTerrainFromS2(t1 & 0x3F); // Only lower 6 bits
70,400✔
157
        node.t2 = getTerrainFromS2(t2 & 0x3F); // Only lower 6 bits
70,400✔
158
        if(!node.t1 || !node.t2)
70,400✔
UNCOV
159
            return false;
×
160

161
        unsigned char mapResource = map.getMapDataAt(MapLayer::Resources, pt.x, pt.y);
70,400✔
162
        Resource resource;
70,400✔
163
        // Wasser?
164
        if(mapResource == 0x20 || mapResource == 0x21)
70,400✔
165
            resource = Resource(ResourceType::Water, 7);
36,625✔
166
        else if(mapResource > 0x40 && mapResource < 0x48)
33,775✔
167
            resource = Resource(ResourceType::Coal, mapResource - 0x40);
9,815✔
168
        else if(mapResource > 0x48 && mapResource < 0x50)
23,960✔
169
            resource = Resource(ResourceType::Iron, mapResource - 0x48);
2,525✔
170
        else if(mapResource > 0x50 && mapResource < 0x58)
21,435✔
171
            resource = Resource(ResourceType::Gold, mapResource - 0x50);
1,660✔
172
        else if(mapResource > 0x58 && mapResource < 0x60)
19,775✔
173
            resource = Resource(ResourceType::Granite, mapResource - 0x58);
550✔
174
        else if(mapResource > 0x80 && mapResource < 0x90) // fish
19,225✔
175
            resource = Resource(ResourceType::Fish, 4);   // Use 4 fish
2,420✔
176
        node.resources = resource;
70,400✔
177

178
        node.reserved = false;
70,400✔
179
        node.owner = 0;
70,400✔
180
        std::fill(node.boundary_stones.begin(), node.boundary_stones.end(), 0);
70,400✔
181
        node.seaId.reset();
70,400✔
182

183
        Visibility fowVisibility;
184
        switch(exploration)
70,400✔
185
        {
186
            case Exploration::Disabled: fowVisibility = Visibility::Visible; break;
×
187
            case Exploration::Classic:
70,400✔
188
            case Exploration::FogOfWar: fowVisibility = Visibility::Invisible; break;
70,400✔
UNCOV
189
            case Exploration::FogOfWarExplored: fowVisibility = Visibility::FogOfWar; break;
×
UNCOV
190
            default: throw std::invalid_argument("Visibility for FoW");
×
191
        }
192

193
        // FOW-Zeug initialisieren
194
        for(auto& fow : node.fow)
633,600✔
195
        {
196
            fow = FoWNode();
563,200✔
197
            fow.visibility = fowVisibility;
563,200✔
198
        }
199

200
        RTTR_Assert(node.figures.empty());
70,400✔
201
    }
202
    return true;
5✔
203
}
204

205
void MapLoader::PlaceObjects(const libsiedler2::ArchivItem_Map& map)
5✔
206
{
207
    hqPositions_.clear();
5✔
208

209
    RTTR_FOREACH_PT(MapPoint, world_.GetSize())
70,805✔
210
    {
211
        using libsiedler2::MapLayer;
212
        unsigned char lc = map.getMapDataAt(MapLayer::ObjectIndex, pt.x, pt.y);
70,400✔
213
        noBase* obj = nullptr;
70,400✔
214

215
        switch(map.getMapDataAt(MapLayer::ObjectType, pt.x, pt.y))
70,400✔
216
        {
217
            // Player Startpos (provisorisch)
218
            case 0x80:
20✔
219
            {
220
                if(lc < MAX_PLAYERS)
20✔
221
                {
222
                    while(hqPositions_.size() <= lc)
40✔
223
                        hqPositions_.push_back(MapPoint::Invalid());
20✔
224
                    hqPositions_[lc] = pt;
20✔
225
                }
226
            }
227
            break;
20✔
228

229
            // Baum 1-4
230
            case 0xC4:
17,640✔
231
            {
232
                if(lc >= 0x30 && lc <= 0x3D)
17,640✔
233
                    obj = new noTree(pt, 0, 3);
5,610✔
234
                else if(lc >= 0x70 && lc <= 0x7D)
12,030✔
235
                    obj = new noTree(pt, 1, 3);
5,860✔
236
                else if(lc >= 0xB0 && lc <= 0xBD)
6,170✔
237
                    obj = new noTree(pt, 2, 3);
5,555✔
238
                else if(lc >= 0xF0 && lc <= 0xFD)
615✔
239
                    obj = new noTree(pt, 3, 3);
615✔
240
                else
UNCOV
241
                    LOG.write(_("Unknown tree1-4 at %1%: (0x%2$x)\n")) % pt % unsigned(lc);
×
242
            }
243
            break;
17,640✔
244

245
            // Baum 5-8
246
            case 0xC5:
×
247
            {
248
                if(lc >= 0x30 && lc <= 0x3D)
×
249
                    obj = new noTree(pt, 4, 3);
×
250
                else if(lc >= 0x70 && lc <= 0x7D)
×
251
                    obj = new noTree(pt, 5, 3);
×
UNCOV
252
                else if(lc >= 0xB0 && lc <= 0xBD)
×
253
                    obj = new noTree(pt, 6, 3);
×
UNCOV
254
                else if(lc >= 0xF0 && lc <= 0xFD)
×
255
                    obj = new noTree(pt, 7, 3);
×
256
                else
UNCOV
257
                    LOG.write(_("Unknown tree5-8 at %1%: (0x%2$x)\n")) % pt % unsigned(lc);
×
258
            }
UNCOV
259
            break;
×
260

261
            // Baum 9
UNCOV
262
            case 0xC6:
×
263
            {
UNCOV
264
                if(lc >= 0x30 && lc <= 0x3D)
×
265
                    obj = new noTree(pt, 8, 3);
×
266
                else
UNCOV
267
                    LOG.write(_("Unknown tree9 at %1%: (0x%2$x)\n")) % pt % unsigned(lc);
×
268
            }
UNCOV
269
            break;
×
270

271
            // Sonstiges Naturzeug ohne Funktion, nur zur Dekoration
272
            case 0xC8:
3,740✔
273
            case 0xC9: // Note: 0xC9 is actually a bug and should be 0xC8. But the random map generator produced that...
274
            {
275
                // "wasserstein" aus der map_?_z.lst
276
                if(lc == 0x0B)
3,740✔
UNCOV
277
                    obj = new noStaticObject(pt, 500 + lc);
×
278
                // Objekte aus der map_?_z.lst
279
                else if(lc <= 0x0F)
3,740✔
280
                    obj = new noEnvObject(pt, 500 + lc);
1,615✔
281
                // Objekte aus der map.lst
282
                else if(lc <= 0x14)
2,125✔
283
                    obj = new noEnvObject(pt, 542 + lc - 0x10);
1,580✔
284
                // exists in mis0bobs-mis5bobs -> take stranded ship
285
                else if(lc == 0x15)
545✔
UNCOV
286
                    obj = new noStaticObject(pt, 0, 0);
×
287
                // gate
288
                else if(lc == 0x16)
545✔
UNCOV
289
                    obj = new noStaticObject(pt, 560);
×
290
                // open gate
291
                else if(lc == 0x17)
545✔
UNCOV
292
                    obj = new noStaticObject(pt, 561);
×
293
                // Stalagmiten (mis1bobs)
294
                else if(lc <= 0x1E)
545✔
UNCOV
295
                    obj = new noStaticObject(pt, (lc - 0x18) * 2, 1);
×
296
                // toter Baum (mis1bobs)
297
                else if(lc <= 0x20)
545✔
UNCOV
298
                    obj = new noStaticObject(pt, 20 + (lc - 0x1F) * 2, 1);
×
299
                // Gerippe (mis1bobs)
300
                else if(lc == 0x21)
545✔
UNCOV
301
                    obj = new noStaticObject(pt, 30, 1);
×
302
                // Objekte aus der map.lst
303
                else if(lc <= 0x2B)
545✔
304
                    obj = new noEnvObject(pt, 550 + lc - 0x22);
545✔
305
                // tent, ruin of guardhouse, tower ruin, cross
306
                else if(lc <= 0x2E || lc == 0x30)
×
UNCOV
307
                    obj = new noStaticObject(pt, (lc - 0x2C) * 2, 2);
×
308
                // castle ruin
309
                else if(lc == 0x2F)
×
UNCOV
310
                    obj = new noStaticObject(pt, (lc - 0x2C) * 2, 2, 2);
×
311
                // small wiking with boat
312
                else if(lc == 0x31)
×
UNCOV
313
                    obj = new noStaticObject(pt, 0, 3);
×
314
                // Pile of wood
315
                else if(lc == 0x32)
×
UNCOV
316
                    obj = new noStaticObject(pt, 0, 4);
×
317
                // whale skeleton (head right)
318
                else if(lc == 0x33)
×
319
                    obj = new noStaticObject(pt, 0, 5);
×
320
                // The next 2 are non standard and only for access in RTTR (replaced in original by
321
                // whale skeleton (head left)
322
                else if(lc == 0x34)
×
UNCOV
323
                    obj = new noStaticObject(pt, 2, 5);
×
324
                // Cave
UNCOV
325
                else if(lc == 0x35)
×
UNCOV
326
                    obj = new noStaticObject(pt, 4, 5);
×
327
                else
UNCOV
328
                    LOG.write(_("Unknown nature object at %1%: (0x%2$x)\n")) % pt % unsigned(lc);
×
329
            }
330
            break;
3,740✔
331

332
            // Granit Typ 1
333
            case 0xCC:
1,370✔
334
            {
335
                if(lc >= 0x01 && lc <= 0x06)
1,370✔
336
                    obj = new noGranite(GraniteType::One, lc - 1);
1,370✔
337
                else
UNCOV
338
                    LOG.write(_("Unknown granite type2 at %1%: (0x%2$x)\n")) % pt % unsigned(lc);
×
339
            }
340
            break;
1,370✔
341

342
            // Granit Typ 2
343
            case 0xCD:
1,395✔
344
            {
345
                if(lc >= 0x01 && lc <= 0x06)
1,395✔
346
                    obj = new noGranite(GraniteType::Two, lc - 1);
1,395✔
347
                else
UNCOV
348
                    LOG.write(_("Unknown granite type2 at %1%: (0x%2$x)\n")) % pt % unsigned(lc);
×
349
            }
350
            break;
1,395✔
351

352
            // Nichts
353
            case 0: break;
46,235✔
354

UNCOV
355
            default:
×
356
#ifndef NDEBUG
UNCOV
357
                unsigned unknownObj = map.getMapDataAt(MapLayer::ObjectType, pt.x, pt.y);
×
UNCOV
358
                LOG.write(_("Unknown object at %1%: (0x%2$x: 0x%3$x)\n")) % pt % unknownObj % unsigned(lc);
×
359
#endif // !NDEBUG
UNCOV
360
                break;
×
361
        }
362

363
        world_.GetNodeInt(pt).obj = obj;
70,400✔
364
    }
365
}
5✔
366

367
void MapLoader::PlaceAnimals(const libsiedler2::ArchivItem_Map& map)
5✔
368
{
369
    // Tiere auslesen
370
    RTTR_FOREACH_PT(MapPoint, world_.GetSize())
70,805✔
371
    {
372
        Species species;
373
        using libsiedler2::MapLayer;
374
        switch(map.getMapDataAt(MapLayer::Animals, pt.x, pt.y))
70,400✔
375
        {
376
            // TODO: Which id is the polar bear?
377
            case 1:
175✔
378
                species = (RANDOM.Rand(RANDOM_CONTEXT2(0), 2) == 0) ? Species::RabbitWhite : Species::RabbitGrey;
175✔
379
                break; // Random rabbit
175✔
380
            case 2: species = Species::Fox; break;
140✔
UNCOV
381
            case 3: species = Species::Stag; break;
×
382
            case 4: species = Species::Deer; break;
140✔
UNCOV
383
            case 5: species = Species::Duck; break;
×
384
            case 6: species = Species::Sheep; break;
70✔
385
            case 0:
69,875✔
386
            case 0xFF: // 0xFF is for (really) old S2 maps
387
                continue;
69,875✔
UNCOV
388
            default:
×
389
#ifndef NDEBUG
UNCOV
390
                unsigned unknownAnimal = map.getMapDataAt(MapLayer::Animals, pt.x, pt.y);
×
UNCOV
391
                LOG.write(_("Unknown animal species at %1%: (0x%2$x)\n")) % pt % unknownAnimal;
×
392
#endif // !NDEBUG
393
                continue;
69,875✔
394
        }
395

396
        world_.AddFigure(pt, std::make_unique<noAnimal>(species, pt)).StartLiving();
525✔
397
    }
398
}
5✔
399

400
bool MapLoader::PlaceHQs(GameWorldBase& world, const std::vector<MapPoint>& hqPositions, const bool addStartWares)
176✔
401
{
402
    for(unsigned i = 0; i < world.GetNumPlayers(); ++i)
551✔
403
    {
404
        // Skip unused slots
405
        if(!world.GetPlayer(i).isUsed())
375✔
406
            continue;
13✔
407

408
        // Does the HQ have a position?
409
        if(i >= hqPositions.size() || !hqPositions[i].isValid())
362✔
410
        {
UNCOV
411
            LOG.write(_("Player %u does not have a valid start position!")) % i;
×
UNCOV
412
            return false;
×
413
        }
414

415
        auto* hq = checkedCast<nobHQ*>(BuildingFactory::CreateBuilding(world, BuildingType::Headquarters,
362✔
416
                                                                       hqPositions[i], i, world.GetPlayer(i).nation));
362✔
417
        if(addStartWares)
362✔
418
            hq->addStartWares();
8✔
419
    }
420
    return true;
176✔
421
}
422

423
bool MapLoader::InitSeasAndHarbors(World& world, const std::vector<MapPoint>& additionalHarbors)
27✔
424
{
425
    for(MapPoint pt : additionalHarbors)
185✔
426
        world.harborData.push_back(HarborPos(pt));
158✔
427
    // Clear current harbors and seas
428
    RTTR_FOREACH_PT(MapPoint, world.GetSize()) //-V807
192,697✔
429
    {
430
        MapNode& node = world.GetNodeInt(pt);
190,284✔
431
        node.seaId.reset();
190,284✔
432
        node.harborId.reset();
190,284✔
433
    }
434

435
    /// Determine all seas
436
    world.seas.clear();
27✔
437
    SeaId curSeaId(1);
27✔
438
    RTTR_FOREACH_PT(MapPoint, world.GetSize())
192,697✔
439
    {
440
        // Point is not yet assigned a sea but should be
441
        if(!world.GetNode(pt).seaId && world.IsSeaPoint(pt))
190,284✔
442
        {
443
            const auto seaSize = MeasureSea(world, pt, curSeaId);
46✔
444
            world.seas.push_back(World::Sea(seaSize));
46✔
445
            curSeaId = curSeaId.next();
46✔
446
        }
447
    }
448

449
    /// Determine seas adjacent to the harbor places
450
    HarborId curHarborId(1);
27✔
451
    for(auto it = world.harborData.begin(); it != world.harborData.end();)
201✔
452
    {
453
        helpers::StrongIdVector<bool, SeaId> hasCoastAtSea(world.seas.size(), false);
348✔
454
        bool foundCoast = false;
174✔
455
        for(const auto dir : helpers::EnumRange<Direction>{})
2,784✔
456
        {
457
            SeaId seaId;
1,044✔
458
            // Skip point at NW as often there is no path from it if the harbor is north of an island
459
            if(dir != Direction::NorthWest)
1,044✔
460
            {
461
                seaId = world.GetSeaFromCoastalPoint(world.GetNeighbour(it->pos, dir));
870✔
462
                if(seaId)
870✔
463
                {
464
                    foundCoast = true;
226✔
465
                    // Only 1 coastal point per sea
466
                    if(hasCoastAtSea[seaId])
226✔
467
                        seaId.reset();
52✔
468
                    else
469
                        hasCoastAtSea[seaId] = true;
174✔
470
                }
471
            }
472
            it->seaIds[dir] = seaId;
1,044✔
473
        }
474
        if(!foundCoast)
174✔
475
        {
476
            LOG.write("Map Bug: Found harbor without coast at %1%. Removing!\n") % it->pos;
×
477
            it = world.harborData.erase(it);
×
478
        } else
479
        {
480
            world.GetNodeInt(it->pos).harborId = curHarborId;
174✔
481
            curHarborId = curHarborId.next();
174✔
482
            ++it;
174✔
483
        }
484
    }
485

486
    // Calculate the neighbors and distances
487
    CalcHarborPosNeighbors(world);
27✔
488

489
    // Validate
490
    for(const auto startHbId : helpers::idRange<HarborId>(world.harborData.size()))
201✔
491
    {
492
        const HarborPos& startHbPos = world.harborData[startHbId];
174✔
493
        for(const std::vector<HarborPos::Neighbor>& neighbors : startHbPos.neighbors)
1,218✔
494
        {
495
            for(const HarborPos::Neighbor& neighbor : neighbors)
1,664✔
496
            {
497
                if(world.CalcHarborDistance(neighbor.id, startHbId) != neighbor.distance)
620✔
498
                {
499
                    LOG.write("Bug: Harbor distance mismatch for harbors %1%->%2%: %3% != %4%\n") % startHbId
×
500
                      % neighbor.id % world.CalcHarborDistance(neighbor.id, startHbId) % neighbor.distance;
×
501
                    return false;
×
502
                }
503
            }
504
        }
505
    }
506
    return true;
27✔
507
}
508

509
// class for finding harbor neighbors
510
struct CalcHarborPosNeighborsNode
511
{
512
    CalcHarborPosNeighborsNode() = default; //-V730
513
    CalcHarborPosNeighborsNode(const MapPoint pt, unsigned distance) : pos(pt), distance(distance) {}
232,032✔
514

515
    MapPoint pos;
516
    unsigned distance;
517
};
518

519
/// Calculate the distance from each harbor to the others
520
void MapLoader::CalcHarborPosNeighbors(World& world)
27✔
521
{
522
    for(HarborPos& harbor : world.harborData)
201✔
523
    {
524
        for(const auto dir : helpers::EnumRange<ShipDirection>{})
2,784✔
525
            harbor.neighbors[dir].clear();
1,044✔
526
    }
527
    PathConditionShip shipPathChecker(world);
27✔
528

529
    // pre-calculate sea-points, as IsSeaPoint is rather expensive
530
    std::vector<int8_t> ptIsSeaPt(world.nodes.size()); //-V656
54✔
531

532
    RTTR_FOREACH_PT(MapPoint, world.GetSize())
192,697✔
533
    {
534
        if(shipPathChecker.IsNodeOk(pt))
190,284✔
535
            ptIsSeaPt[world.GetIdx(pt)] = -1;
64,106✔
536
    }
537

538
    // FIFO queue used for a BFS
539
    std::queue<CalcHarborPosNeighborsNode> todo_list;
54✔
540

541
    for(const auto startHbId : helpers::idRange<HarborId>(world.harborData.size()))
201✔
542
    {
543
        RTTR_Assert(todo_list.empty());
174✔
544

545
        // Copy sea points to working flags. Possible values are
546
        // -1 - sea point, not already visited
547
        // 0 - visited or no sea point
548
        // 1 - Coast to a harbor
549
        std::vector<int8_t> ptToVisitOrHb(ptIsSeaPt);
348✔
550

551
        helpers::StrongIdVector<bool, HarborId> hbFound(world.harborData.size(), false);
348✔
552
        // For each sea, store the coastal point indices and their harbor
553
        helpers::StrongIdVector<std::multimap<unsigned, HarborId>, SeaId> coastToHarborPerSea(world.seas.size());
348✔
554
        std::vector<MapPoint> ownCoastalPoints;
348✔
555

556
        // mark coastal points around harbors
557
        for(const auto otherHbId : helpers::idRange<HarborId>(world.harborData.size()))
1,600✔
558
        {
559
            for(const auto dir : helpers::EnumRange<Direction>{})
22,816✔
560
            {
561
                SeaId seaId = world.GetSeaId(otherHbId, dir);
8,556✔
562
                // No sea? -> Next
563
                if(!seaId)
8,556✔
564
                    continue;
7,130✔
565
                const MapPoint coastPt = world.GetNeighbour(world.GetHarborPoint(otherHbId), dir);
1,426✔
566
                // This should not be marked for visit
567
                unsigned idx = world.GetIdx(coastPt);
1,426✔
568
                RTTR_Assert(ptToVisitOrHb[idx] != -1);
1,426✔
569
                if(otherHbId == startHbId)
1,426✔
570
                {
571
                    // This is our start harbor. Add the coast points around it to our todo list.
572
                    ownCoastalPoints.push_back(coastPt);
174✔
573
                } else
574
                {
575
                    ptToVisitOrHb[idx] = 1;
1,252✔
576
                    coastToHarborPerSea[seaId].insert(std::make_pair(idx, otherHbId));
1,252✔
577
                }
578
            }
579
        }
580

581
        for(const MapPoint& ownCoastPt : ownCoastalPoints)
348✔
582
        {
583
            // Special case: Get all harbors that share the coast point with us
584
            SeaId seaId = world.GetSeaFromCoastalPoint(ownCoastPt);
174✔
585
            auto const coastToHbs = coastToHarborPerSea[seaId].equal_range(world.GetIdx(ownCoastPt));
174✔
586
            for(auto it = coastToHbs.first; it != coastToHbs.second; ++it)
180✔
587
            {
588
                ShipDirection shipDir = world.GetShipDir(ownCoastPt, ownCoastPt);
6✔
589
                world.harborData[startHbId].neighbors[shipDir].push_back(HarborPos::Neighbor(it->second, 0));
6✔
590
                hbFound[it->second] = true;
6✔
591
            }
592
            todo_list.push(CalcHarborPosNeighborsNode(ownCoastPt, 0));
174✔
593
        }
594

595
        while(!todo_list.empty()) // as long as there are sea points on our todo list...
232,206✔
596
        {
597
            CalcHarborPosNeighborsNode curNode = todo_list.front();
232,032✔
598
            todo_list.pop();
232,032✔
599

600
            for(const auto dir : helpers::EnumRange<Direction>{})
3,712,512✔
601
            {
602
                MapPoint curPt = world.GetNeighbour(curNode.pos, dir);
1,392,192✔
603
                unsigned idx = world.GetIdx(curPt);
1,392,192✔
604

605
                const int8_t ptValue = ptToVisitOrHb[idx];
1,392,192✔
606
                // Already visited
607
                if(ptValue == 0)
1,392,192✔
608
                    continue;
1,160,334✔
609
                // Not reachable
610
                if(!shipPathChecker.IsEdgeOk(curNode.pos, dir))
231,874✔
611
                    continue;
16✔
612

613
                if(ptValue > 0) // found harbor(s)
231,858✔
614
                {
615
                    ShipDirection shipDir = world.GetShipDir(world.harborData[startHbId].pos, curPt);
597✔
616
                    SeaId seaId = world.GetSeaFromCoastalPoint(curPt);
597✔
617
                    auto const coastToHbs = coastToHarborPerSea[seaId].equal_range(idx);
597✔
618
                    for(auto it = coastToHbs.first; it != coastToHbs.second; ++it)
1,217✔
619
                    {
620
                        const HarborId otherHbId = it->second;
620✔
621
                        if(hbFound[otherHbId])
620✔
622
                            continue;
6✔
623

624
                        hbFound[otherHbId] = true;
614✔
625
                        world.harborData[startHbId].neighbors[shipDir].push_back(
1,228✔
626
                          HarborPos::Neighbor(otherHbId, curNode.distance + 1));
614✔
627

628
                        // Make this the only coastal point of this harbor for this sea
629
                        HarborPos& otherHb = world.harborData[otherHbId];
614✔
630
                        RTTR_Assert(seaId);
614✔
631
                        for(const auto hbDir : helpers::EnumRange<Direction>{})
9,824✔
632
                        {
633
                            if(otherHb.seaIds[hbDir] == seaId && world.GetNeighbour(otherHb.pos, hbDir) != curPt)
3,684✔
634
                                otherHb.seaIds[hbDir].reset();
×
635
                        }
636
                    }
637
                }
638
                todo_list.push(CalcHarborPosNeighborsNode(curPt, curNode.distance + 1));
231,858✔
639
                ptToVisitOrHb[idx] = 0; // mark as visited, so we do not go here again
231,858✔
640
            }
641
        }
642
    }
643
}
27✔
644

645
/// Vermisst ein neues Weltmeer von einem Punkt aus, indem es alle mit diesem Punkt verbundenen
646
/// Wasserpunkte mit der gleichen ID belegt und die Anzahl zur�ckgibt
647
unsigned MapLoader::MeasureSea(World& world, const MapPoint start, SeaId seaId)
46✔
648
{
649
    // Breitensuche von diesem Punkt aus durchf�hren
650
    std::vector<bool> visited(world.GetWidth() * world.GetHeight(), false);
92✔
651
    std::queue<MapPoint> todo;
46✔
652

653
    todo.push(start);
46✔
654
    visited[world.GetIdx(start)] = true;
46✔
655

656
    // Count of nodes (including start node)
657
    unsigned count = 0;
46✔
658

659
    while(!todo.empty())
64,152✔
660
    {
661
        MapPoint p = todo.front();
64,106✔
662
        todo.pop();
64,106✔
663

664
        RTTR_Assert(visited[world.GetIdx(p)]);
64,106✔
665
        world.GetNodeInt(p).seaId = seaId;
64,106✔
666

667
        for(const MapPoint neighbourPt : world.GetNeighbours(p))
448,742✔
668
        {
669
            if(visited[world.GetIdx(neighbourPt)])
384,636✔
670
                continue;
310,153✔
671
            visited[world.GetIdx(neighbourPt)] = true;
74,483✔
672

673
            // Ist das dort auch ein Meerespunkt?
674
            if(world.IsSeaPoint(neighbourPt))
74,483✔
675
                todo.push(neighbourPt);
64,060✔
676
        }
677

678
        ++count;
64,106✔
679
    }
680

681
    return count;
92✔
682
}
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