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

Return-To-The-Roots / s25client / 21600910629

02 Feb 2026 05:42PM UTC coverage: 50.721% (-0.03%) from 50.754%
21600910629

Pull #1683

github

web-flow
Merge b0763ef2d into dc0ce04f6
Pull Request #1683: Lua: Allow setting number of players, and placing HQs. Then fix the mission on map "The snake"

1 of 29 new or added lines in 7 files covered. (3.45%)

5 existing lines in 2 files now uncovered.

22796 of 44944 relevant lines covered (50.72%)

41333.25 hits per line

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

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

35
class noBase;
36

37
MapLoader::MapLoader(GameWorldBase& world) : world_(world) {}
6✔
38

39
bool MapLoader::Load(const libsiedler2::ArchivItem_Map& map, Exploration exploration)
4✔
40
{
41
    GameDataLoader gdLoader(world_.GetDescriptionWriteable());
8✔
42
    if(!gdLoader.Load())
4✔
43
        return false;
×
44

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

50
    if(!InitNodes(map, exploration))
4✔
51
        return false;
×
52
    PlaceObjects(map);
4✔
53
    PlaceAnimals(map);
4✔
54
    if(!InitSeasAndHarbors(world_))
4✔
55
        return false;
×
56

57
    /// Schatten
58
    InitShadows(world_);
4✔
59

60
    // If we have explored FoW, create the FoW objects
61
    if(exploration == Exploration::FogOfWarExplored)
4✔
62
        SetMapExplored(world_);
×
63

64
    return true;
4✔
65
}
66

67
bool MapLoader::Load(const boost::filesystem::path& mapFilePath)
2✔
68
{
69
    // Map laden
70
    libsiedler2::Archiv mapArchiv;
4✔
71

72
    // Karteninformationen laden
73
    if(libsiedler2::loader::LoadMAP(mapFilePath, mapArchiv) != 0)
2✔
74
        return false;
×
75

76
    const libsiedler2::ArchivItem_Map& map = *static_cast<libsiedler2::ArchivItem_Map*>(mapArchiv[0]);
2✔
77

78
    if(!Load(map, world_.GetGGS().exploration))
2✔
79
        return false;
×
80
    if(!PlaceHQs(world_.GetGGS().randomStartPosition))
2✔
81
        return false;
×
82

83
    world_.CreateTradeGraphs();
2✔
84

85
    return true;
2✔
86
}
87

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

99
bool MapLoader::PlaceHQs(bool randomStartPos)
2✔
100
{
101
    return PlaceHQs(world_, hqPositions_, randomStartPos);
2✔
102
}
103

104
void MapLoader::InitShadows(World& world)
4✔
105
{
106
    RTTR_FOREACH_PT(MapPoint, world.GetSize())
56,644✔
107
        world.RecalcShadow(pt);
56,320✔
108
}
4✔
109

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

124
DescIdx<TerrainDesc> MapLoader::getTerrainFromS2(uint8_t s2Id) const
112,640✔
125
{
126
    return world_.GetDescription().terrain.find([s2Id, landscape = world_.GetLandscapeType()](const TerrainDesc& t) {
112,640✔
127
        return t.s2Id == s2Id && t.landscape == landscape;
1,153,728✔
128
    });
112,640✔
129
}
130

131
bool MapLoader::InitNodes(const libsiedler2::ArchivItem_Map& map, Exploration exploration)
4✔
132
{
133
    using libsiedler2::MapLayer;
134
    // Init node data (everything except the objects, figures and BQ)
135
    RTTR_FOREACH_PT(MapPoint, world_.GetSize())
56,644✔
136
    {
137
        MapNode& node = world_.GetNodeInt(pt);
56,320✔
138

139
        std::fill(node.roads.begin(), node.roads.end(), PointRoad::None);
56,320✔
140
        node.altitude = map.getMapDataAt(MapLayer::Altitude, pt.x, pt.y);
56,320✔
141
        unsigned char t1 = map.getMapDataAt(MapLayer::Terrain1, pt.x, pt.y),
56,320✔
142
                      t2 = map.getMapDataAt(MapLayer::Terrain2, pt.x, pt.y);
56,320✔
143

144
        // Hafenplatz?
145
        if((t1 & libsiedler2::HARBOR_MASK) != 0)
56,320✔
146
            world_.harbor_pos.push_back(HarborPos(pt));
×
147

148
        // Will be set later
149
        node.harborId = 0;
56,320✔
150

151
        node.t1 = getTerrainFromS2(t1 & 0x3F); // Only lower 6 bits
56,320✔
152
        node.t2 = getTerrainFromS2(t2 & 0x3F); // Only lower 6 bits
56,320✔
153
        if(!node.t1 || !node.t2)
56,320✔
154
            return false;
×
155

156
        unsigned char mapResource = map.getMapDataAt(MapLayer::Resources, pt.x, pt.y);
56,320✔
157
        Resource resource;
56,320✔
158
        // Wasser?
159
        if(mapResource == 0x20 || mapResource == 0x21)
56,320✔
160
            resource = Resource(ResourceType::Water, 7);
29,300✔
161
        else if(mapResource > 0x40 && mapResource < 0x48)
27,020✔
162
            resource = Resource(ResourceType::Coal, mapResource - 0x40);
7,852✔
163
        else if(mapResource > 0x48 && mapResource < 0x50)
19,168✔
164
            resource = Resource(ResourceType::Iron, mapResource - 0x48);
2,020✔
165
        else if(mapResource > 0x50 && mapResource < 0x58)
17,148✔
166
            resource = Resource(ResourceType::Gold, mapResource - 0x50);
1,328✔
167
        else if(mapResource > 0x58 && mapResource < 0x60)
15,820✔
168
            resource = Resource(ResourceType::Granite, mapResource - 0x58);
440✔
169
        else if(mapResource > 0x80 && mapResource < 0x90) // fish
15,380✔
170
            resource = Resource(ResourceType::Fish, 4);   // Use 4 fish
1,936✔
171
        node.resources = resource;
56,320✔
172

173
        node.reserved = false;
56,320✔
174
        node.owner = 0;
56,320✔
175
        std::fill(node.boundary_stones.begin(), node.boundary_stones.end(), 0);
56,320✔
176
        node.seaId = 0;
56,320✔
177

178
        Visibility fowVisibility;
179
        switch(exploration)
56,320✔
180
        {
181
            case Exploration::Disabled: fowVisibility = Visibility::Visible; break;
×
182
            case Exploration::Classic:
56,320✔
183
            case Exploration::FogOfWar: fowVisibility = Visibility::Invisible; break;
56,320✔
184
            case Exploration::FogOfWarExplored: fowVisibility = Visibility::FogOfWar; break;
×
185
            default: throw std::invalid_argument("Visibility for FoW");
×
186
        }
187

188
        // FOW-Zeug initialisieren
189
        for(auto& fow : node.fow)
506,880✔
190
        {
191
            fow = FoWNode();
450,560✔
192
            fow.visibility = fowVisibility;
450,560✔
193
        }
194

195
        RTTR_Assert(node.figures.empty());
56,320✔
196
    }
197
    return true;
4✔
198
}
199

200
void MapLoader::PlaceObjects(const libsiedler2::ArchivItem_Map& map)
4✔
201
{
202
    hqPositions_.clear();
4✔
203

204
    RTTR_FOREACH_PT(MapPoint, world_.GetSize())
56,644✔
205
    {
206
        using libsiedler2::MapLayer;
207
        unsigned char lc = map.getMapDataAt(MapLayer::ObjectIndex, pt.x, pt.y);
56,320✔
208
        noBase* obj = nullptr;
56,320✔
209

210
        switch(map.getMapDataAt(MapLayer::ObjectType, pt.x, pt.y))
56,320✔
211
        {
212
            // Player Startpos (provisorisch)
213
            case 0x80:
16✔
214
            {
215
                if(lc < MAX_PLAYERS)
16✔
216
                {
217
                    while(hqPositions_.size() <= lc)
32✔
218
                        hqPositions_.push_back(MapPoint::Invalid());
16✔
219
                    hqPositions_[lc] = pt;
16✔
220
                }
221
            }
222
            break;
16✔
223

224
            // Baum 1-4
225
            case 0xC4:
14,112✔
226
            {
227
                if(lc >= 0x30 && lc <= 0x3D)
14,112✔
228
                    obj = new noTree(pt, 0, 3);
4,488✔
229
                else if(lc >= 0x70 && lc <= 0x7D)
9,624✔
230
                    obj = new noTree(pt, 1, 3);
4,688✔
231
                else if(lc >= 0xB0 && lc <= 0xBD)
4,936✔
232
                    obj = new noTree(pt, 2, 3);
4,444✔
233
                else if(lc >= 0xF0 && lc <= 0xFD)
492✔
234
                    obj = new noTree(pt, 3, 3);
492✔
235
                else
236
                    LOG.write(_("Unknown tree1-4 at %1%: (0x%2$x)\n")) % pt % unsigned(lc);
×
237
            }
238
            break;
14,112✔
239

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

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

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

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

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

347
            // Nichts
348
            case 0: break;
36,988✔
349

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

358
        world_.GetNodeInt(pt).obj = obj;
56,320✔
359
    }
360
}
4✔
361

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

391
        world_.AddFigure(pt, std::make_unique<noAnimal>(species, pt)).StartLiving();
420✔
392
    }
393
}
4✔
394

395
bool MapLoader::PlaceHQs(GameWorldBase& world, std::vector<MapPoint> hqPositions, bool randomStartPos)
157✔
396
{
397
    // random locations? -> randomize them :)
398
    if(randomStartPos)
157✔
399
    {
400
        RANDOM_SHUFFLE2(hqPositions, 0);
×
401
    }
402

403
    for(unsigned i = 0; i < world.GetNumPlayers(); ++i)
490✔
404
    {
405
        // Skip unused slots
406
        if(!world.GetPlayer(i).isUsed())
333✔
407
            continue;
13✔
408

409
        // Does the HQ have a position?
410
        if(i >= hqPositions.size() || !hqPositions[i].isValid())
320✔
411
        {
NEW
412
            LOG.write(_("Player %u does not have a valid start position!\n")) % i;
×
NEW
413
            if(world.HasLua()) // HQ can be placed in the script, so don't signal error
×
NEW
414
                continue;
×
415
            else
NEW
416
                return false;
×
417
        }
418

419
        BuildingFactory::CreateBuilding(world, BuildingType::Headquarters, hqPositions[i], i,
320✔
420
                                        world.GetPlayer(i).nation);
320✔
421
    }
422
    return true;
157✔
423
}
424

425
bool MapLoader::InitSeasAndHarbors(World& world, const std::vector<MapPoint>& additionalHarbors)
24✔
426
{
427
    for(MapPoint pt : additionalHarbors)
171✔
428
        world.harbor_pos.push_back(HarborPos(pt));
147✔
429
    // Clear current harbors and seas
430
    RTTR_FOREACH_PT(MapPoint, world.GetSize()) //-V807
170,970✔
431
    {
432
        MapNode& node = world.GetNodeInt(pt);
168,764✔
433
        node.seaId = 0u;
168,764✔
434
        node.harborId = 0;
168,764✔
435
    }
436

437
    /// Weltmeere vermessen
438
    world.seas.clear();
24✔
439
    RTTR_FOREACH_PT(MapPoint, world.GetSize())
170,970✔
440
    {
441
        // Noch kein Meer an diesem Punkt  Aber trotzdem Teil eines noch nicht vermessenen Meeres?
442
        if(!world.GetNode(pt).seaId && world.IsSeaPoint(pt))
168,764✔
443
        {
444
            unsigned sea_size = MeasureSea(world, pt, world.seas.size() + 1);
41✔
445
            world.seas.push_back(World::Sea(sea_size));
41✔
446
        }
447
    }
448

449
    /// Die Meere herausfinden, an die die Hafenpunkte grenzen
450
    unsigned curHarborId = 1;
24✔
451
    for(auto it = world.harbor_pos.begin() + 1; it != world.harbor_pos.end();)
179✔
452
    {
453
        std::vector<bool> hasCoastAtSea(world.seas.size() + 1, false);
310✔
454
        bool foundCoast = false;
155✔
455
        for(const auto dir : helpers::EnumRange<Direction>{})
2,480✔
456
        {
457
            // Skip point at NW as often there is no path from it if the harbor is north of an island
458
            unsigned short seaId =
459
              (dir == Direction::NorthWest) ? 0 : world.GetSeaFromCoastalPoint(world.GetNeighbour(it->pos, dir));
930✔
460
            // Only 1 coastal point per sea
461
            if(hasCoastAtSea[seaId])
930✔
462
                seaId = 0;
620✔
463
            else
464
                hasCoastAtSea[seaId] = true;
310✔
465

466
            it->seaIds[dir] = seaId;
930✔
467
            if(seaId)
930✔
468
                foundCoast = true;
155✔
469
        }
470
        if(!foundCoast)
155✔
471
        {
472
            LOG.write("Map Bug: Found harbor without coast at %1%. Removing!\n") % it->pos;
×
473
            it = world.harbor_pos.erase(it);
×
474
        } else
475
        {
476
            world.GetNodeInt(it->pos).harborId = curHarborId++;
155✔
477
            ++it;
155✔
478
        }
479
    }
480

481
    // Calculate the neighbors and distances
482
    CalcHarborPosNeighbors(world);
24✔
483

484
    // Validate
485
    for(unsigned startHbId = 1; startHbId < world.harbor_pos.size(); ++startHbId)
179✔
486
    {
487
        const HarborPos& startHbPos = world.harbor_pos[startHbId];
155✔
488
        for(const std::vector<HarborPos::Neighbor>& neighbors : startHbPos.neighbors)
1,085✔
489
        {
490
            for(const HarborPos::Neighbor& neighbor : neighbors)
1,472✔
491
            {
492
                if(world.CalcHarborDistance(neighbor.id, startHbId) != neighbor.distance)
542✔
493
                {
494
                    LOG.write("Bug: Harbor distance mismatch for harbors %1%->%2%: %3% != %4%\n") % startHbId
×
495
                      % neighbor.id % world.CalcHarborDistance(neighbor.id, startHbId) % neighbor.distance;
×
496
                    return false;
×
497
                }
498
            }
499
        }
500
    }
501
    return true;
24✔
502
}
503

504
// class for finding harbor neighbors
505
struct CalcHarborPosNeighborsNode
506
{
507
    CalcHarborPosNeighborsNode() = default; //-V730
508
    CalcHarborPosNeighborsNode(const MapPoint pt, unsigned distance) : pos(pt), distance(distance) {}
214,743✔
509

510
    MapPoint pos;
511
    unsigned distance;
512
};
513

514
/// Calculate the distance from each harbor to the others
515
void MapLoader::CalcHarborPosNeighbors(World& world)
24✔
516
{
517
    for(HarborPos& harbor : world.harbor_pos)
203✔
518
    {
519
        for(const auto dir : helpers::EnumRange<ShipDirection>{})
2,864✔
520
            harbor.neighbors[dir].clear();
1,074✔
521
    }
522
    PathConditionShip shipPathChecker(world);
24✔
523

524
    // pre-calculate sea-points, as IsSeaPoint is rather expensive
525
    std::vector<int8_t> ptIsSeaPt(world.nodes.size()); //-V656
48✔
526

527
    RTTR_FOREACH_PT(MapPoint, world.GetSize())
170,970✔
528
    {
529
        if(shipPathChecker.IsNodeOk(pt))
168,764✔
530
            ptIsSeaPt[world.GetIdx(pt)] = -1;
59,096✔
531
    }
532

533
    // FIFO queue used for a BFS
534
    std::queue<CalcHarborPosNeighborsNode> todo_list;
48✔
535

536
    for(unsigned startHbId = 1; startHbId < world.harbor_pos.size(); ++startHbId)
179✔
537
    {
538
        RTTR_Assert(todo_list.empty());
155✔
539

540
        // Copy sea points to working flags. Possible values are
541
        // -1 - sea point, not already visited
542
        // 0 - visited or no sea point
543
        // 1 - Coast to a harbor
544
        std::vector<int8_t> ptToVisitOrHb(ptIsSeaPt);
310✔
545

546
        std::vector<bool> hbFound(world.harbor_pos.size(), false);
310✔
547
        // For each sea, store the coastal point indices and their harbor
548
        std::vector<std::multimap<unsigned, unsigned>> coastToHarborPerSea(world.seas.size() + 1);
310✔
549
        std::vector<MapPoint> ownCoastalPoints;
310✔
550

551
        // mark coastal points around harbors
552
        for(unsigned otherHbId = 1; otherHbId < world.harbor_pos.size(); ++otherHbId)
1,396✔
553
        {
554
            for(const auto dir : helpers::EnumRange<Direction>{})
19,856✔
555
            {
556
                unsigned seaId = world.GetSeaId(otherHbId, dir);
7,446✔
557
                // No sea? -> Next
558
                if(!seaId)
7,446✔
559
                    continue;
6,205✔
560
                const MapPoint coastPt = world.GetNeighbour(world.GetHarborPoint(otherHbId), dir);
1,241✔
561
                // This should not be marked for visit
562
                unsigned idx = world.GetIdx(coastPt);
1,241✔
563
                RTTR_Assert(ptToVisitOrHb[idx] != -1);
1,241✔
564
                if(otherHbId == startHbId)
1,241✔
565
                {
566
                    // This is our start harbor. Add the coast points around it to our todo list.
567
                    ownCoastalPoints.push_back(coastPt);
155✔
568
                } else
569
                {
570
                    ptToVisitOrHb[idx] = 1;
1,086✔
571
                    coastToHarborPerSea[seaId].insert(std::make_pair(idx, otherHbId));
1,086✔
572
                }
573
            }
574
        }
575

576
        for(const MapPoint& ownCoastPt : ownCoastalPoints)
310✔
577
        {
578
            // Special case: Get all harbors that share the coast point with us
579
            unsigned short seaId = world.GetSeaFromCoastalPoint(ownCoastPt);
155✔
580
            auto const coastToHbs = coastToHarborPerSea[seaId].equal_range(world.GetIdx(ownCoastPt));
155✔
581
            for(auto it = coastToHbs.first; it != coastToHbs.second; ++it)
159✔
582
            {
583
                ShipDirection shipDir = world.GetShipDir(ownCoastPt, ownCoastPt);
4✔
584
                world.harbor_pos[startHbId].neighbors[shipDir].push_back(HarborPos::Neighbor(it->second, 0));
4✔
585
                hbFound[it->second] = true;
4✔
586
            }
587
            todo_list.push(CalcHarborPosNeighborsNode(ownCoastPt, 0));
155✔
588
        }
589

590
        while(!todo_list.empty()) // as long as there are sea points on our todo list...
214,898✔
591
        {
592
            CalcHarborPosNeighborsNode curNode = todo_list.front();
214,743✔
593
            todo_list.pop();
214,743✔
594

595
            for(const auto dir : helpers::EnumRange<Direction>{})
3,435,888✔
596
            {
597
                MapPoint curPt = world.GetNeighbour(curNode.pos, dir);
1,288,458✔
598
                unsigned idx = world.GetIdx(curPt);
1,288,458✔
599

600
                const int8_t ptValue = ptToVisitOrHb[idx];
1,288,458✔
601
                // Already visited
602
                if(ptValue == 0)
1,288,458✔
603
                    continue;
1,073,870✔
604
                // Not reachable
605
                if(!shipPathChecker.IsEdgeOk(curNode.pos, dir))
214,604✔
606
                    continue;
16✔
607

608
                if(ptValue > 0) // found harbor(s)
214,588✔
609
                {
610
                    ShipDirection shipDir = world.GetShipDir(world.harbor_pos[startHbId].pos, curPt);
524✔
611
                    unsigned seaId = world.GetSeaFromCoastalPoint(curPt);
524✔
612
                    auto const coastToHbs = coastToHarborPerSea[seaId].equal_range(idx);
524✔
613
                    for(auto it = coastToHbs.first; it != coastToHbs.second; ++it)
1,066✔
614
                    {
615
                        unsigned otherHbId = it->second;
542✔
616
                        if(hbFound[otherHbId])
542✔
617
                            continue;
4✔
618

619
                        hbFound[otherHbId] = true;
538✔
620
                        world.harbor_pos[startHbId].neighbors[shipDir].push_back(
1,076✔
621
                          HarborPos::Neighbor(otherHbId, curNode.distance + 1));
538✔
622

623
                        // Make this the only coastal point of this harbor for this sea
624
                        HarborPos& otherHb = world.harbor_pos[otherHbId];
538✔
625
                        RTTR_Assert(seaId);
538✔
626
                        for(const auto hbDir : helpers::EnumRange<Direction>{})
8,608✔
627
                        {
628
                            if(otherHb.seaIds[hbDir] == seaId && world.GetNeighbour(otherHb.pos, hbDir) != curPt)
3,228✔
629
                                otherHb.seaIds[hbDir] = 0;
×
630
                        }
631
                    }
632
                }
633
                todo_list.push(CalcHarborPosNeighborsNode(curPt, curNode.distance + 1));
214,588✔
634
                ptToVisitOrHb[idx] = 0; // mark as visited, so we do not go here again
214,588✔
635
            }
636
        }
637
    }
638
}
24✔
639

640
/// Vermisst ein neues Weltmeer von einem Punkt aus, indem es alle mit diesem Punkt verbundenen
641
/// Wasserpunkte mit der gleichen ID belegt und die Anzahl zur�ckgibt
642
unsigned MapLoader::MeasureSea(World& world, const MapPoint start, unsigned short seaId)
41✔
643
{
644
    // Breitensuche von diesem Punkt aus durchf�hren
645
    std::vector<bool> visited(world.GetWidth() * world.GetHeight(), false);
82✔
646
    std::queue<MapPoint> todo;
41✔
647

648
    todo.push(start);
41✔
649
    visited[world.GetIdx(start)] = true;
41✔
650

651
    // Count of nodes (including start node)
652
    unsigned count = 0;
41✔
653

654
    while(!todo.empty())
59,137✔
655
    {
656
        MapPoint p = todo.front();
59,096✔
657
        todo.pop();
59,096✔
658

659
        RTTR_Assert(visited[world.GetIdx(p)]);
59,096✔
660
        world.GetNodeInt(p).seaId = seaId;
59,096✔
661

662
        for(const MapPoint neighbourPt : world.GetNeighbours(p))
413,672✔
663
        {
664
            if(visited[world.GetIdx(neighbourPt)])
354,576✔
665
                continue;
286,087✔
666
            visited[world.GetIdx(neighbourPt)] = true;
68,489✔
667

668
            // Ist das dort auch ein Meerespunkt?
669
            if(world.IsSeaPoint(neighbourPt))
68,489✔
670
                todo.push(neighbourPt);
59,055✔
671
        }
672

673
        ++count;
59,096✔
674
    }
675

676
    return count;
82✔
677
}
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