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

Return-To-The-Roots / s25client / 12323492039

13 Dec 2024 09:44PM UTC coverage: 50.144% (-0.02%) from 50.166%
12323492039

Pull #1683

github

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

9 of 40 new or added lines in 8 files covered. (22.5%)

4 existing lines in 1 file now uncovered.

22270 of 44412 relevant lines covered (50.14%)

34410.75 hits per line

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

77.23
/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)
122✔
396
{
397
    // random locations? -> randomize them :)
398
    if(randomStartPos)
122✔
399
    {
400
        RANDOM_SHUFFLE2(hqPositions, 0);
×
401
    }
402

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

409
        // Does the HQ have a position?
410
        if(i >= hqPositions.size() || !hqPositions[i].isValid())
238✔
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
                return false;
×
415
        }
416

417
        BuildingFactory::CreateBuilding(world, BuildingType::Headquarters, hqPositions[i], i,
238✔
418
                                        world.GetPlayer(i).nation);
238✔
419
    }
420
    return true;
122✔
421
}
422

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

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

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

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

479
    // Calculate the neighbors and distances
480
    CalcHarborPosNeighbors(world);
23✔
481

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

502
// class for finding harbor neighbors
503
struct CalcHarborPosNeighborsNode
504
{
505
    CalcHarborPosNeighborsNode() = default; //-V730
506
    CalcHarborPosNeighborsNode(const MapPoint pt, unsigned distance) : pos(pt), distance(distance) {}
213,975✔
507

508
    MapPoint pos;
509
    unsigned distance;
510
};
511

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

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

525
    RTTR_FOREACH_PT(MapPoint, world.GetSize())
170,507✔
526
    {
527
        if(shipPathChecker.IsNodeOk(pt))
168,324✔
528
            ptIsSeaPt[world.GetIdx(pt)] = -1;
58,908✔
529
    }
530

531
    // FIFO queue used for a BFS
532
    std::queue<CalcHarborPosNeighborsNode> todo_list;
46✔
533

534
    for(unsigned startHbId = 1; startHbId < world.harbor_pos.size(); ++startHbId)
174✔
535
    {
536
        RTTR_Assert(todo_list.empty());
151✔
537

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

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

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

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

588
        while(!todo_list.empty()) // as long as there are sea points on our todo list...
214,126✔
589
        {
590
            CalcHarborPosNeighborsNode curNode = todo_list.front();
213,975✔
591
            todo_list.pop();
213,975✔
592

593
            for(const auto dir : helpers::EnumRange<Direction>{})
3,423,600✔
594
            {
595
                MapPoint curPt = world.GetNeighbour(curNode.pos, dir);
1,283,850✔
596
                unsigned idx = world.GetIdx(curPt);
1,283,850✔
597

598
                const int8_t ptValue = ptToVisitOrHb[idx];
1,283,850✔
599
                // Already visited
600
                if(ptValue == 0)
1,283,850✔
601
                    continue;
1,070,026✔
602
                // Not reachable
603
                if(!shipPathChecker.IsEdgeOk(curNode.pos, dir))
213,840✔
604
                    continue;
16✔
605

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

617
                        hbFound[otherHbId] = true;
526✔
618
                        world.harbor_pos[startHbId].neighbors[shipDir].push_back(
1,052✔
619
                          HarborPos::Neighbor(otherHbId, curNode.distance + 1));
526✔
620

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

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

646
    todo.push(start);
40✔
647
    visited[world.GetIdx(start)] = true;
40✔
648

649
    // Count of nodes (including start node)
650
    unsigned count = 0;
40✔
651

652
    while(!todo.empty())
58,948✔
653
    {
654
        MapPoint p = todo.front();
58,908✔
655
        todo.pop();
58,908✔
656

657
        RTTR_Assert(visited[world.GetIdx(p)]);
58,908✔
658
        world.GetNodeInt(p).seaId = seaId;
58,908✔
659

660
        for(const MapPoint neighbourPt : world.GetNeighbours(p))
412,356✔
661
        {
662
            if(visited[world.GetIdx(neighbourPt)])
353,448✔
663
                continue;
285,198✔
664
            visited[world.GetIdx(neighbourPt)] = true;
68,250✔
665

666
            // Ist das dort auch ein Meerespunkt?
667
            if(world.IsSeaPoint(neighbourPt))
68,250✔
668
                todo.push(neighbourPt);
58,868✔
669
        }
670

671
        ++count;
58,908✔
672
    }
673

674
    return count;
80✔
675
}
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