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

Return-To-The-Roots / s25client / 25985372463

17 May 2026 08:03AM UTC coverage: 50.362% (+0.08%) from 50.284%
25985372463

Pull #1917

github

web-flow
Merge 5d7afa219 into 57b082981
Pull Request #1917: Fix sea path finding (for ships)

118 of 166 new or added lines in 13 files covered. (71.08%)

5 existing lines in 3 files now uncovered.

23215 of 46096 relevant lines covered (50.36%)

47651.7 hits per line

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

90.04
/libs/s25main/world/World.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/World.h"
6
#include "nodeObjs/noFlag.h"
7
#include "nodeObjs/noNothing.h"
8
#if RTTR_ENABLE_ASSERTS
9
#    include "nodeObjs/noMovable.h"
10
#endif
11
#include "FOWObjects.h"
12
#include "RoadSegment.h"
13
#include "enum_cast.hpp"
14
#include "helpers/containerUtils.h"
15
#include "helpers/pointerContainerUtils.h"
16
#include "gameTypes/ShipDirection.h"
17
#include "gameData/TerrainDesc.h"
18
#include <memory>
19
#include <set>
20
#include <stdexcept>
21

22
World::World() : noNodeObj(nullptr) {}
204✔
23

24
World::~World()
204✔
25
{
26
    Unload();
204✔
27
}
204✔
28

29
void World::Init(const MapExtent& mapSize, DescIdx<LandscapeDesc> lt)
193✔
30
{
31
    RTTR_Assert(GetSize() == MapExtent::all(0)); // Already init
193✔
32
    RTTR_Assert(mapSize.x > 0 && mapSize.y > 0); // No empty map
193✔
33
    Resize(mapSize);
193✔
34
    if(!lt)
193✔
35
        throw std::runtime_error("Invalid landscape");
×
36
    this->lt = lt;
193✔
37
    GameObject::ResetCounters();
193✔
38

39
    noNodeObj = std::make_unique<noNothing>();
193✔
40
}
193✔
41

42
void World::Unload()
205✔
43
{
44
    // Collect and destroy roads
45
    std::set<RoadSegment*> roadsegments;
205✔
46
    for(const auto& node : nodes)
373,373✔
47
    {
48
        if(!node.obj || node.obj->GetGOT() != GO_Type::Flag)
373,168✔
49
            continue;
372,544✔
50
        for(const auto dir : helpers::EnumRange<Direction>{})
9,984✔
51
        {
52
            if(static_cast<noFlag*>(node.obj)->GetRoute(dir))
3,744✔
53
            {
54
                roadsegments.insert(static_cast<noFlag*>(node.obj)->GetRoute(dir));
741✔
55
            }
56
        }
57
    }
58

59
    for(auto* roadsegment : roadsegments)
865✔
60
        delete roadsegment;
660✔
61

62
    // Objekte vernichten
63
    for(auto& node : nodes)
373,373✔
64
        deletePtr(node.obj);
373,168✔
65

66
    // Figuren vernichten
67
    for(auto& node : nodes)
373,373✔
68
        node.figures.clear();
373,168✔
69

70
    catapult_stones.clear();
205✔
71
    harborData.clear();
205✔
72
    description_ = WorldDescription();
205✔
73
    noNodeObj.reset();
205✔
74
    Resize(MapExtent::all(0));
205✔
75
}
205✔
76

77
void World::Resize(const MapExtent& newSize)
398✔
78
{
79
    MapBase::Resize(newSize);
398✔
80
    nodes.clear();
398✔
81
    militarySquares.Clear();
398✔
82
    if(GetSize().x > 0)
398✔
83
    {
84
        nodes.resize(prodOfComponents(GetSize()));
193✔
85
        militarySquares.Init(GetSize());
193✔
86
    }
87
}
398✔
88

89
noBase& World::AddFigureImpl(const MapPoint pt, std::unique_ptr<noBase> fig)
5,742✔
90
{
91
    RTTR_Assert(fig);
5,742✔
92

93
    auto& figures = GetNodeInt(pt).figures;
5,742✔
94
#if RTTR_ENABLE_ASSERTS
95
    RTTR_Assert(!helpers::containsPtr(figures, fig.get()));
5,742✔
96
    for(const MapPoint nb : GetNeighbours(pt))
40,194✔
97
        RTTR_Assert(!helpers::containsPtr(GetNode(nb).figures, fig.get())); // Added figure that is in surrounding?
34,452✔
98
#endif
99

100
    noBase& result = *fig;
5,742✔
101
    figures.push_back(std::move(fig));
5,742✔
102
    return result;
5,742✔
103
}
104

105
noBase* World::RemoveFigureImpl(const MapPoint pt, noBase& fig)
4,954✔
106
{
107
    return helpers::extractPtr(GetNodeInt(pt).figures, &fig).release();
4,954✔
108
}
109

110
noBase* World::GetNO(const MapPoint pt)
29,108✔
111
{
112
    if(GetNode(pt).obj)
29,108✔
113
        return GetNode(pt).obj;
5,738✔
114
    else
115
        return noNodeObj.get();
23,370✔
116
}
117

118
const noBase* World::GetNO(const MapPoint pt) const
12,334,279✔
119
{
120
    if(GetNode(pt).obj)
12,334,279✔
121
        return GetNode(pt).obj;
165,540✔
122
    else
123
        return noNodeObj.get();
12,168,739✔
124
}
125

126
void World::SetNO(const MapPoint pt, noBase* obj, const bool replace /* = false*/)
5,597✔
127
{
128
    RTTR_Assert(replace || obj == nullptr || GetNode(pt).obj == nullptr);
5,597✔
129
#if RTTR_ENABLE_ASSERTS
130
    RTTR_Assert(!dynamic_cast<noMovable*>(obj)); // It should be a static, non-movable object
5,597✔
131
#endif
132
    GetNodeInt(pt).obj = obj;
5,597✔
133
}
5,597✔
134

135
void World::DestroyNO(const MapPoint pt, const bool checkExists /* = true*/)
4,054✔
136
{
137
    noBase* obj = GetNodeInt(pt).obj;
4,054✔
138
    if(obj)
4,054✔
139
    {
140
        // Destroy may remove the NO already from the map or replace it (e.g. building -> fire)
141
        // So remove from map, then destroy and free
142
        GetNodeInt(pt).obj = nullptr;
1,258✔
143
        obj->Destroy();
1,258✔
144
        deletePtr(obj);
1,258✔
145
    } else
146
        RTTR_Assert(!checkExists);
2,796✔
147
}
4,054✔
148

149
/// Returns the GOT if an object or GOT_NOTHING if none
150
GO_Type World::GetGOT(const MapPoint pt) const
687✔
151
{
152
    noBase* obj = GetNode(pt).obj;
687✔
153
    if(obj)
687✔
154
        return obj->GetGOT();
685✔
155
    else
156
        return GO_Type::Nothing;
2✔
157
}
158

159
void World::ReduceResource(const MapPoint pt)
×
160
{
161
    const uint8_t curAmount = GetNodeInt(pt).resources.getAmount();
×
162
    RTTR_Assert(curAmount > 0);
×
163
    GetNodeInt(pt).resources.setAmount(curAmount - 1u);
×
164
}
×
165

166
void World::SetReserved(const MapPoint pt, const bool reserved)
124✔
167
{
168
    RTTR_Assert(GetNodeInt(pt).reserved != reserved);
124✔
169
    GetNodeInt(pt).reserved = reserved;
124✔
170
}
124✔
171

172
void World::SetVisibility(const MapPoint pt, unsigned char player, Visibility vis, unsigned fowTime)
316,920✔
173
{
174
    FoWNode& node = GetNodeInt(pt).fow[player];
316,920✔
175
    Visibility oldVis = node.visibility;
316,920✔
176
    if(oldVis == vis)
316,920✔
177
        return;
134,223✔
178

179
    node.visibility = vis;
182,697✔
180
    if(vis == Visibility::Visible)
182,697✔
181
        node.object.reset();
181,759✔
182
    else if(vis == Visibility::FogOfWar)
938✔
183
        SaveFOWNode(pt, player, fowTime);
938✔
184
    VisibilityChanged(pt, player, oldVis, vis);
182,697✔
185
}
186

187
void World::ChangeAltitude(const MapPoint pt, const unsigned char altitude)
397✔
188
{
189
    GetNodeInt(pt).altitude = altitude;
397✔
190

191
    // Schattierung neu berechnen von diesem Punkt und den Punkten drumherum
192
    RecalcShadow(pt);
397✔
193
    for(const MapPoint nb : GetNeighbours(pt))
2,779✔
194
        RecalcShadow(nb);
2,382✔
195

196
    // Abgeleiteter Klasse Bescheid sagen
197
    AltitudeChanged(pt);
397✔
198
}
397✔
199

200
bool World::IsPlayerTerritory(const MapPoint pt, const unsigned char owner) const
373,185✔
201
{
202
    const unsigned char ptOwner = GetNode(pt).owner;
373,185✔
203

204
    if(owner != 0 && ptOwner != owner)
373,185✔
205
        return false;
16,940✔
206

207
    // Neighbour nodes must belong to this player
208
    for(const MapPoint nb : GetNeighbours(pt))
2,154,603✔
209
    {
210
        if(GetNode(nb).owner != ptOwner)
1,876,063✔
211
            return false;
77,705✔
212
    }
213

214
    return true;
278,540✔
215
}
216

217
BuildingQuality World::GetBQ(const MapPoint pt, const unsigned char player) const
76,725✔
218
{
219
    return AdjustBQ(pt, player, GetNode(pt).bq);
76,725✔
220
}
221

222
BuildingQuality World::AdjustBQ(const MapPoint pt, unsigned char player, BuildingQuality nodeBQ) const
77,177✔
223
{
224
    if(nodeBQ == BuildingQuality::Nothing || !IsPlayerTerritory(pt, player + 1))
77,177✔
225
        return BuildingQuality::Nothing;
29,124✔
226
    // If we could build a building, but the buildings flag point is at the border, we can only build a flag
227
    if(nodeBQ != BuildingQuality::Flag && !IsPlayerTerritory(GetNeighbour(pt, Direction::SouthEast)))
48,053✔
228
    {
229
        // Check for close flags, that prohibit to build a flag but not a building at this spot
230
        for(const Direction dir : {Direction::West, Direction::NorthWest, Direction::NorthEast})
8,835✔
231
        {
232
            if(GetNO(GetNeighbour(pt, dir))->GetBM() == BlockingManner::Flag)
6,660✔
233
                return BuildingQuality::Nothing;
66✔
234
        }
235
        return BuildingQuality::Flag;
2,175✔
236
    } else
237
        return nodeBQ;
45,812✔
238
}
239

240
bool World::HasFigureAt(const MapPoint pt, const noBase& figure) const
826✔
241
{
242
    return helpers::containsPtr(GetNode(pt).figures, &figure);
826✔
243
}
244

245
WalkTerrain World::GetTerrain(MapPoint pt, Direction dir) const
771,686✔
246
{
247
    // Manually inlined code from GetNeighbors. Measured to greatly improve performance
248
    const MapExtent size = GetSize();
771,686✔
249
    const MapCoord yminus1 = (pt.y == 0 ? size.y : pt.y) - 1;
771,686✔
250
    const MapCoord xplus1 = pt.x == size.x - 1 ? 0 : pt.x + 1;
771,686✔
251
    const MapCoord xminus1 = (pt.x == 0 ? size.x : pt.x) - 1;
771,686✔
252
    const bool isEvenRow = (pt.y & 1) == 0;
771,686✔
253

254
    const MapPoint wPt(xminus1, pt.y);
771,686✔
255
    const MapPoint nwPt(!isEvenRow ? pt.x : xminus1, yminus1);
771,686✔
256
    const MapPoint nePt(isEvenRow ? pt.x : xplus1, yminus1);
771,686✔
257
    switch(dir)
771,686✔
258
    {
259
        case Direction::West:
70,189✔
260
        {
261
            return {GetNode(wPt).t2, GetNode(nwPt).t1};
70,189✔
262
        }
263
        case Direction::NorthWest:
102,957✔
264
        {
265
            const MapNode& node = GetNode(nwPt);
102,957✔
266
            return {node.t1, node.t2};
102,957✔
267
        }
268
        case Direction::NorthEast:
182,245✔
269
        {
270
            return {GetNode(nwPt).t2, GetNode(nePt).t1};
182,245✔
271
        }
272
        case Direction::East:
96,460✔
273
        {
274
            return {GetNode(nePt).t1, GetNode(pt).t2};
96,460✔
275
        }
276
        case Direction::SouthEast:
103,433✔
277
        {
278
            const MapNode& node = GetNode(pt);
103,433✔
279
            return {node.t2, node.t1};
103,433✔
280
        }
281
        case Direction::SouthWest:
216,402✔
282
        {
283
            return {GetNode(pt).t1, GetNode(wPt).t2};
216,402✔
284
        }
285
    }
286
    throw std::logic_error("Invalid direction");
×
287
}
288

289
helpers::EnumArray<DescIdx<TerrainDesc>, Direction> World::GetTerrainsAround(MapPoint pt) const
1,995,236✔
290
{
291
    // Manually inlined code from GetNeighbors. Measured to greatly improve performance
292
    const MapExtent size = GetSize();
1,995,236✔
293
    const MapCoord yminus1 = (pt.y == 0 ? size.y : pt.y) - 1;
1,995,236✔
294
    const MapCoord xplus1 = pt.x == size.x - 1 ? 0 : pt.x + 1;
1,995,236✔
295
    const MapCoord xminus1 = (pt.x == 0 ? size.x : pt.x) - 1;
1,995,236✔
296
    const bool isEvenRow = (pt.y & 1) == 0;
1,995,236✔
297

298
    const MapPoint wPt(xminus1, pt.y);
1,995,236✔
299
    const MapPoint nwPt(!isEvenRow ? pt.x : xminus1, yminus1);
1,995,236✔
300
    const MapPoint nePt(isEvenRow ? pt.x : xplus1, yminus1);
1,995,236✔
301

302
    const MapNode& nwNode = GetNode(nwPt);
1,995,236✔
303
    const MapNode& neNode = GetNode(nePt);
1,995,236✔
304
    const MapNode& curNode = GetNode(pt);
1,995,236✔
305
    const MapNode& wNode = GetNode(wPt);
1,995,236✔
306
    helpers::EnumArray<DescIdx<TerrainDesc>, Direction> result{nwNode.t1,  nwNode.t2,  neNode.t1,
307
                                                               curNode.t2, curNode.t1, wNode.t2};
1,995,236✔
308
    return result;
1,995,236✔
309
}
310

311
void World::SaveFOWNode(const MapPoint pt, const unsigned player, unsigned curTime)
938✔
312
{
313
    FoWNode& fow = GetNodeInt(pt).fow[player];
938✔
314
    fow.last_update_time = curTime;
938✔
315

316
    // FOW-Objekt erzeugen
317
    fow.object = GetNO(pt)->CreateFOWObject();
938✔
318

319
    // Wege speichern, aber nur richtige, keine, die gerade gebaut werden
320
    for(const auto dir : helpers::EnumRange<RoadDir>{})
9,380✔
321
        fow.roads[dir] = GetNode(pt).roads[dir];
2,814✔
322

323
    // Store ownership so FoW boundary stones can be drawn
324
    fow.owner = GetNode(pt).owner;
938✔
325
    // Grenzsteine merken
326
    fow.boundary_stones = GetNode(pt).boundary_stones;
938✔
327
}
938✔
328

329
bool World::IsSeaPoint(const MapPoint pt) const
791,149✔
330
{
331
    return World::IsOfTerrain(pt, [](const auto& desc) { return desc.Is(ETerrain::Shippable); });
3,916,235✔
332
}
333

334
bool World::IsWaterPoint(const MapPoint pt) const
2,986✔
335
{
336
    return World::IsOfTerrain(pt, [](const auto& desc) { return desc.kind == TerrainKind::Water; });
7,642✔
337
}
338

339
unsigned World::GetSeaSize(const SeaId seaId) const
1,241✔
340
{
341
    RTTR_Assert(seaId && seaId.value() <= seas.size());
1,241✔
342
    return seas[seaId].nodes_count;
1,241✔
343
}
344

345
SeaId World::GetSeaId(const HarborId harborId, const Direction dir) const
10,474✔
346
{
347
    RTTR_Assert(harborId);
10,474✔
348
    return harborData[harborId].seaIds[dir];
10,474✔
349
}
350

351
bool World::IsHarborAtSea(const HarborId harborId, const SeaId seaId) const
377✔
352
{
353
    return GetCoastalPoint(harborId, seaId).isValid();
377✔
354
}
355

356
MapPoint World::GetCoastalPoint(const HarborId harborId, const SeaId seaId) const
1,834✔
357
{
358
    RTTR_Assert(harborId);
1,834✔
359
    RTTR_Assert(seaId);
1,834✔
360

361
    // Take point at NW last as often there is no path from it if the harbor is north of an island
362
    for(auto dir : helpers::enumRange(Direction::NorthEast))
13,734✔
363
    {
364
        if(harborData[harborId].seaIds[dir] == seaId)
4,889✔
365
            return GetNeighbour(harborData[harborId].pos, dir);
1,690✔
366
    }
367

368
    // Keinen Punkt gefunden
369
    return MapPoint::Invalid();
144✔
370
}
371

372
PointRoad World::GetRoad(const MapPoint pt, RoadDir dir) const
11,216,229✔
373
{
374
    return GetNode(pt).roads[dir];
11,216,229✔
375
}
376

377
PointRoad World::GetPointRoad(MapPoint pt, Direction dir) const
1,017,433✔
378
{
379
    const RoadDir rDir = toRoadDir(pt, dir);
1,017,433✔
380
    return GetRoad(pt, rDir);
1,017,433✔
381
}
382

383
PointRoad World::GetPointFOWRoad(MapPoint pt, Direction dir, const unsigned char viewing_player) const
×
384
{
385
    const RoadDir rDir = toRoadDir(pt, dir);
×
386
    return GetNode(pt).fow[viewing_player].roads[rDir];
×
387
}
388

389
void World::AddCatapultStone(CatapultStone* cs)
×
390
{
391
    RTTR_Assert(!helpers::contains(catapult_stones, cs));
×
392
    catapult_stones.push_back(cs);
×
393
}
×
394

395
void World::RemoveCatapultStone(CatapultStone* cs)
×
396
{
397
    RTTR_Assert(helpers::contains(catapult_stones, cs));
×
398
    catapult_stones.remove(cs);
×
399
}
×
400

401
MapPoint World::GetHarborPoint(const HarborId harborId) const
2,547✔
402
{
403
    RTTR_Assert(harborId);
2,547✔
404

405
    return harborData[harborId].pos;
2,546✔
406
}
407

408
const std::vector<HarborPos::Neighbor>& World::GetHarborNeighbors(const HarborId harborId,
454✔
409
                                                                  const ShipDirection& dir) const
410
{
411
    RTTR_Assert(harborId);
454✔
412
    return harborData[harborId].neighbors[dir];
454✔
413
}
414

415
unsigned World::GetMinHarborDistance(HarborId haborId1, HarborId harborId2) const
191✔
416
{
417
    if(haborId1 == harborId2) // special case: distance to self
191✔
418
        return 0;
11✔
419
    unsigned minDistance = std::numeric_limits<unsigned>::max();
180✔
420
    for(const auto dir : helpers::EnumRange<ShipDirection>{})
2,880✔
421
    {
422
        for(const HarborPos::Neighbor& n : harborData[haborId1].neighbors[dir])
2,390✔
423
        {
424
            if(n.id == harborId2)
1,310✔
425
                minDistance = std::min(minDistance, n.distance);
147✔
426
        }
427
    }
428
    return minDistance;
180✔
429
}
430

431
unsigned World::GetHarborDistance(HarborId haborId1, HarborId harborId2, SeaId sea) const
167✔
432
{
433
    if(haborId1 == harborId2) // special case: distance to self
167✔
NEW
434
        return 0;
×
435
    for(const auto dir : helpers::EnumRange<ShipDirection>{})
1,446✔
436
    {
437
        for(const HarborPos::Neighbor& n : harborData[haborId1].neighbors[dir])
715✔
438
        {
439
            if(n.id == harborId2 && n.sea == sea)
326✔
440
                return n.distance;
167✔
441
        }
442
    }
NEW
443
    RTTR_Assert(!"Should only query actual neighbors");
×
444
    return std::numeric_limits<unsigned>::max();
445
}
446

447
SeaId World::GetSeaFromCoastalPoint(const MapPoint pt) const
2,356✔
448
{
449
    // Point itself must not be a sea
450
    if(GetNode(pt).seaId)
2,356✔
451
        return SeaId::invalidValue();
45✔
452

453
    // Should not be inside water itself
454
    if(IsWaterPoint(pt))
2,311✔
455
        return SeaId::invalidValue();
×
456

457
    // Surrounding must be valid sea
458
    for(const MapPoint nb : GetNeighbours(pt))
11,457✔
459
    {
460
        SeaId seaId = GetNode(nb).seaId;
10,387✔
461
        if(seaId)
10,387✔
462
        {
463
            // Check size (TODO: Others checks like harbor spots?)
464
            if(GetSeaSize(seaId) > 20)
1,241✔
465
                return seaId;
1,241✔
466
        }
467
    }
468

469
    return SeaId::invalidValue();
1,070✔
470
}
471

472
void World::SetRoad(const MapPoint pt, RoadDir roadDir, PointRoad type)
1,757✔
473
{
474
    GetNodeInt(pt).roads[roadDir] = type;
1,757✔
475
}
1,757✔
476

477
bool World::SetBQ(const MapPoint pt, BuildingQuality bq)
594,596✔
478
{
479
    BuildingQuality oldBQ = bq;
594,596✔
480
    std::swap(GetNodeInt(pt).bq, oldBQ);
594,596✔
481
    return oldBQ != bq;
594,596✔
482
}
483

484
void World::RecalcShadow(const MapPoint pt)
73,179✔
485
{
486
    int altitude = GetNode(pt).altitude;
73,179✔
487
    int A = GetNeighbourNode(pt, Direction::NorthEast).altitude - altitude;
73,179✔
488
    int B = GetNode(GetNeighbour2(pt, 0)).altitude - altitude;
73,179✔
489
    int C = GetNode(GetNeighbour(pt, Direction::West)).altitude - altitude;
73,179✔
490
    int D = GetNode(GetNeighbour2(pt, 11)).altitude - altitude;
73,179✔
491

492
    int shadingS2 = 64 + 9 * A - 3 * B - 6 * C - 9 * D;
73,179✔
493
    if(shadingS2 > 128)
73,179✔
494
        shadingS2 = 128;
3,487✔
495
    else if(shadingS2 < 0)
69,692✔
496
        shadingS2 = 0;
3,380✔
497
    GetNodeInt(pt).shadow = shadingS2;
73,179✔
498
}
73,179✔
499

500
void World::MakeWholeMapVisibleForAllPlayers()
×
501
{
502
    for(auto& mapNode : nodes)
×
503
    {
504
        for(auto& fowNode : mapNode.fow)
×
505
        {
506
            fowNode.visibility = Visibility::Visible;
×
507
            fowNode.object.reset();
×
508
        }
509
    }
510
}
×
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