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

Return-To-The-Roots / s25client / 22818081571

08 Mar 2026 09:14AM UTC coverage: 50.336% (+0.009%) from 50.327%
22818081571

push

github

web-flow
Merge pull request #1895 from Noseey/fix_ship_stuck_crash

234 of 308 new or added lines in 23 files covered. (75.97%)

20 existing lines in 3 files now uncovered.

23052 of 45796 relevant lines covered (50.34%)

43414.34 hits per line

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

90.46
/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) {}
194✔
23

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

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

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

42
void World::Unload()
195✔
43
{
44
    // Collect and destroy roads
45
    std::set<RoadSegment*> roadsegments;
195✔
46
    for(const auto& node : nodes)
332,643✔
47
    {
48
        if(!node.obj || node.obj->GetGOT() != GO_Type::Flag)
332,448✔
49
            continue;
331,869✔
50
        for(const auto dir : helpers::EnumRange<Direction>{})
9,264✔
51
        {
52
            if(static_cast<noFlag*>(node.obj)->GetRoute(dir))
3,474✔
53
            {
54
                roadsegments.insert(static_cast<noFlag*>(node.obj)->GetRoute(dir));
689✔
55
            }
56
        }
57
    }
58

59
    for(auto* roadsegment : roadsegments)
808✔
60
        delete roadsegment;
613✔
61

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

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

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

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

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

93
    auto& figures = GetNodeInt(pt).figures;
6,082✔
94
#if RTTR_ENABLE_ASSERTS
95
    RTTR_Assert(!helpers::containsPtr(figures, fig.get()));
6,082✔
96
    for(const MapPoint nb : GetNeighbours(pt))
42,574✔
97
        RTTR_Assert(!helpers::containsPtr(GetNode(nb).figures, fig.get())); // Added figure that is in surrounding?
36,492✔
98
#endif
99

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

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

110
noBase* World::GetNO(const MapPoint pt)
30,068✔
111
{
112
    if(GetNode(pt).obj)
30,068✔
113
        return GetNode(pt).obj;
6,293✔
114
    else
115
        return noNodeObj.get();
23,775✔
116
}
117

118
const noBase* World::GetNO(const MapPoint pt) const
11,787,407✔
119
{
120
    if(GetNode(pt).obj)
11,787,407✔
121
        return GetNode(pt).obj;
147,738✔
122
    else
123
        return noNodeObj.get();
11,639,669✔
124
}
125

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

135
void World::DestroyNO(const MapPoint pt, const bool checkExists /* = true*/)
3,897✔
136
{
137
    noBase* obj = GetNodeInt(pt).obj;
3,897✔
138
    if(obj)
3,897✔
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,246✔
143
        obj->Destroy();
1,246✔
144
        deletePtr(obj);
1,246✔
145
    } else
146
        RTTR_Assert(!checkExists);
2,651✔
147
}
3,897✔
148

149
/// Returns the GOT if an object or GOT_NOTHING if none
150
GO_Type World::GetGOT(const MapPoint pt) const
460✔
151
{
152
    noBase* obj = GetNode(pt).obj;
460✔
153
    if(obj)
460✔
154
        return obj->GetGOT();
458✔
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)
122✔
167
{
168
    RTTR_Assert(GetNodeInt(pt).reserved != reserved);
122✔
169
    GetNodeInt(pt).reserved = reserved;
122✔
170
}
122✔
171

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

179
    node.visibility = vis;
173,053✔
180
    if(vis == Visibility::Visible)
173,053✔
181
        node.object.reset();
172,115✔
182
    else if(vis == Visibility::FogOfWar)
938✔
183
        SaveFOWNode(pt, player, fowTime);
938✔
184
    VisibilityChanged(pt, player, oldVis, vis);
173,053✔
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
360,490✔
201
{
202
    const unsigned char ptOwner = GetNode(pt).owner;
360,490✔
203

204
    if(owner != 0 && ptOwner != owner)
360,490✔
205
        return false;
14,648✔
206

207
    // Neighbour nodes must belong to this player
208
    for(const MapPoint nb : GetNeighbours(pt))
2,104,110✔
209
    {
210
        if(GetNode(nb).owner != ptOwner)
1,831,044✔
211
            return false;
72,776✔
212
    }
213

214
    return true;
273,066✔
215
}
216

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

222
BuildingQuality World::AdjustBQ(const MapPoint pt, unsigned char player, BuildingQuality nodeBQ) const
77,158✔
223
{
224
    if(nodeBQ == BuildingQuality::Nothing || !IsPlayerTerritory(pt, player + 1))
77,158✔
225
        return BuildingQuality::Nothing;
26,268✔
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)))
50,890✔
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})
9,339✔
231
        {
232
            if(GetNO(GetNeighbour(pt, dir))->GetBM() == BlockingManner::Flag)
7,038✔
233
                return BuildingQuality::Nothing;
66✔
234
        }
235
        return BuildingQuality::Flag;
2,301✔
236
    } else
237
        return nodeBQ;
48,523✔
238
}
239

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

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

254
    const MapPoint wPt(xminus1, pt.y);
390,005✔
255
    const MapPoint nwPt(!isEvenRow ? pt.x : xminus1, yminus1);
390,005✔
256
    const MapPoint nePt(isEvenRow ? pt.x : xplus1, yminus1);
390,005✔
257
    switch(dir)
390,005✔
258
    {
259
        case Direction::West:
36,956✔
260
        {
261
            return {GetNode(wPt).t2, GetNode(nwPt).t1};
36,956✔
262
        }
263
        case Direction::NorthWest:
50,383✔
264
        {
265
            const MapNode& node = GetNode(nwPt);
50,383✔
266
            return {node.t1, node.t2};
50,383✔
267
        }
268
        case Direction::NorthEast:
88,780✔
269
        {
270
            return {GetNode(nwPt).t2, GetNode(nePt).t1};
88,780✔
271
        }
272
        case Direction::East:
46,233✔
273
        {
274
            return {GetNode(nePt).t1, GetNode(pt).t2};
46,233✔
275
        }
276
        case Direction::SouthEast:
47,533✔
277
        {
278
            const MapNode& node = GetNode(pt);
47,533✔
279
            return {node.t2, node.t1};
47,533✔
280
        }
281
        case Direction::SouthWest:
120,120✔
282
        {
283
            return {GetNode(pt).t1, GetNode(wPt).t2};
120,120✔
284
        }
285
    }
286
    throw std::logic_error("Invalid direction");
×
287
}
288

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

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

302
    const MapNode& nwNode = GetNode(nwPt);
1,547,370✔
303
    const MapNode& neNode = GetNode(nePt);
1,547,370✔
304
    const MapNode& curNode = GetNode(pt);
1,547,370✔
305
    const MapNode& wNode = GetNode(wPt);
1,547,370✔
306
    helpers::EnumArray<DescIdx<TerrainDesc>, Direction> result{nwNode.t1,  nwNode.t2,  neNode.t1,
307
                                                               curNode.t2, curNode.t1, wNode.t2};
1,547,370✔
308
    return result;
1,547,370✔
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
401,769✔
330
{
331
    return World::IsOfTerrain(pt, [](const auto& desc) { return desc.Is(ETerrain::Shippable); });
1,642,878✔
332
}
333

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

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

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

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

356
MapPoint World::GetCoastalPoint(const HarborId harborId, const SeaId seaId) const
1,203✔
357
{
358
    RTTR_Assert(harborId);
1,203✔
359
    RTTR_Assert(seaId);
1,203✔
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))
8,500✔
363
    {
364
        if(harbor_pos[harborId].seaIds[dir] == seaId)
3,011✔
365
            return GetNeighbour(harbor_pos[harborId].pos, dir);
1,167✔
366
    }
367

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

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

377
PointRoad World::GetPointRoad(MapPoint pt, Direction dir) const
975,590✔
378
{
379
    const RoadDir rDir = toRoadDir(pt, dir);
975,590✔
380
    return GetRoad(pt, rDir);
975,590✔
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,263✔
402
{
403
    RTTR_Assert(harborId);
2,263✔
404

405
    return harbor_pos[harborId].pos;
2,262✔
406
}
407

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

415
unsigned World::CalcHarborDistance(HarborId haborId1, HarborId harborId2) const
1,042✔
416
{
417
    if(haborId1 == harborId2) // special case: distance to self
1,042✔
418
        return 0;
114✔
419
    for(const auto dir : helpers::EnumRange<ShipDirection>{})
8,688✔
420
    {
421
        for(const HarborPos::Neighbor& n : harbor_pos[haborId1].neighbors[dir])
5,147✔
422
        {
423
            if(n.id == harborId2)
2,659✔
424
                return n.distance;
895✔
425
        }
426
    }
427

428
    return 0xffffffff;
33✔
429
}
430

431
SeaId World::GetSeaFromCoastalPoint(const MapPoint pt) const
2,112✔
432
{
433
    // Point itself must not be a sea
434
    if(GetNode(pt).seaId)
2,112✔
435
        return SeaId::invalidValue();
45✔
436

437
    // Should not be inside water itself
438
    if(IsWaterPoint(pt))
2,067✔
NEW
439
        return SeaId::invalidValue();
×
440

441
    // Surrounding must be valid sea
442
    for(const MapPoint nb : GetNeighbours(pt))
10,183✔
443
    {
444
        SeaId seaId = GetNode(nb).seaId;
9,240✔
445
        if(seaId)
9,240✔
446
        {
447
            // Check size (TODO: Others checks like harbor spots?)
448
            if(GetSeaSize(seaId) > 20)
1,124✔
449
                return seaId;
1,124✔
450
        }
451
    }
452

453
    return SeaId::invalidValue();
943✔
454
}
455

456
void World::SetRoad(const MapPoint pt, RoadDir roadDir, PointRoad type)
1,697✔
457
{
458
    GetNodeInt(pt).roads[roadDir] = type;
1,697✔
459
}
1,697✔
460

461
bool World::SetBQ(const MapPoint pt, BuildingQuality bq)
554,679✔
462
{
463
    BuildingQuality oldBQ = bq;
554,679✔
464
    std::swap(GetNodeInt(pt).bq, oldBQ);
554,679✔
465
    return oldBQ != bq;
554,679✔
466
}
467

468
void World::RecalcShadow(const MapPoint pt)
59,099✔
469
{
470
    int altitude = GetNode(pt).altitude;
59,099✔
471
    int A = GetNeighbourNode(pt, Direction::NorthEast).altitude - altitude;
59,099✔
472
    int B = GetNode(GetNeighbour2(pt, 0)).altitude - altitude;
59,099✔
473
    int C = GetNode(GetNeighbour(pt, Direction::West)).altitude - altitude;
59,099✔
474
    int D = GetNode(GetNeighbour2(pt, 11)).altitude - altitude;
59,099✔
475

476
    int shadingS2 = 64 + 9 * A - 3 * B - 6 * C - 9 * D;
59,099✔
477
    if(shadingS2 > 128)
59,099✔
478
        shadingS2 = 128;
2,790✔
479
    else if(shadingS2 < 0)
56,309✔
480
        shadingS2 = 0;
2,704✔
481
    GetNodeInt(pt).shadow = shadingS2;
59,099✔
482
}
59,099✔
483

484
void World::MakeWholeMapVisibleForAllPlayers()
×
485
{
486
    for(auto& mapNode : nodes)
×
487
    {
488
        for(auto& fowNode : mapNode.fow)
×
489
        {
490
            fowNode.visibility = Visibility::Visible;
×
491
            fowNode.object.reset();
×
492
        }
493
    }
494
}
×
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