• 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

48.18
/libs/s25main/world/MapSerializer.cpp
1
// Copyright (C) 2005 - 2026 Settlers Freaks (sf-team at siedler25.org)
2
//
3
// SPDX-License-Identifier: GPL-2.0-or-later
4

5
#include "world/MapSerializer.h"
6
#include "CatapultStone.h"
7
#include "Game.h"
8
#include "SerializedGameData.h"
9
#include "buildings/noBuildingSite.h"
10
#include "helpers/IdRange.h"
11
#include "helpers/Range.h"
12
#include "lua/GameDataLoader.h"
13
#include "world/GameWorldBase.h"
14
#include "s25util/warningSuppression.h"
15
#include <boost/container/static_vector.hpp>
16
#include <mygettext/mygettext.h>
17

18
void MapSerializer::Serialize(const GameWorldBase& world, SerializedGameData& sgd)
6✔
19
{
20
    // Headinformationen
21
    helpers::pushPoint(sgd, world.GetSize());
6✔
22
    sgd.PushString(world.GetDescription().get(world.GetLandscapeType()).name);
6✔
23

24
    sgd.PushUnsignedInt(GameObject::GetObjIDCounter());
6✔
25

26
    // Alle Weltpunkte serialisieren
27
    const unsigned numPlayers = world.GetNumPlayers();
6✔
28
    for(const auto& node : world.nodes)
5,366✔
29
    {
30
        node.Serialize(sgd, numPlayers, world.GetDescription());
5,360✔
31
    }
32

33
    // Katapultsteine serialisieren
34
    sgd.PushObjectContainer(world.catapult_stones, true);
6✔
35
    // Meeresinformationen serialisieren
36
    sgd.PushUnsignedInt(world.seas.size());
6✔
37
    for(const auto& sea : world.seas)
6✔
38
    {
39
        sgd.PushUnsignedInt(sea.nodes_count);
×
40
    }
41
    // Hafenpositionen serialisieren
42
    sgd.PushUnsignedInt(world.harborData.size());
6✔
43
    for(const auto& curHarborPos : world.harborData)
6✔
44
    {
45
        helpers::pushPoint(sgd, curHarborPos.pos);
×
46
        helpers::pushContainer(sgd, curHarborPos.seaIds);
×
47
        for(const auto& curNeighbors : curHarborPos.neighbors)
×
48
        {
49
            sgd.PushUnsignedInt(curNeighbors.size());
×
50

51
            for(const auto& c : curNeighbors)
×
52
            {
53
                sgd.PushUnsignedInt(c.id.value());
×
NEW
54
                sgd.PushUnsignedInt(c.sea.value());
×
UNCOV
55
                sgd.PushUnsignedInt(c.distance);
×
56
            }
57
        }
58
    }
59

60
    sgd.PushObjectContainer(world.harbor_building_sites_from_sea, true);
6✔
61

62
    if(!world.HasLua())
6✔
63
        sgd.PushLongString("");
4✔
64
    else
65
    {
66
        sgd.PushLongString(world.GetLua().getScript());
2✔
67
        Serializer luaSaveState;
4✔
68
        try
69
        {
70
            if(!world.GetLua().Serialize(luaSaveState))
2✔
71
                throw SerializedGameData::Error(_("Failed to save lua state!"));
×
72
        } catch(const std::exception& e)
×
73
        {
74
            throw SerializedGameData::Error(std::string(_("Failed to save lua state!")) + _("Error: ") + e.what());
×
75
        }
76
        sgd.PushUnsignedInt(0xC0DEBA5E); // Start Lua identifier
2✔
77
        sgd.PushUnsignedInt(luaSaveState.GetLength());
2✔
78
        sgd.PushRawData(luaSaveState.GetData(), luaSaveState.GetLength());
2✔
79
        sgd.PushUnsignedInt(0xC001C0DE); // End Lua identifier
2✔
80
    }
81
}
6✔
82

83
void MapSerializer::Deserialize(GameWorldBase& world, SerializedGameData& sgd, Game& game,
3✔
84
                                ILocalGameState& localgameState)
85
{
86
    // Initialisierungen
87
    GameDataLoader gdLoader(world.GetDescriptionWriteable());
6✔
88
    if(!gdLoader.Load())
3✔
89
        throw SerializedGameData::Error(_("Failed to load game data!"));
×
90

91
    // Headinformationen
92
    const auto size = helpers::popPoint<MapExtent>(sgd);
3✔
93
    DescIdx<LandscapeDesc> lt;
3✔
94
    if(sgd.GetGameDataVersion() < 3)
3✔
95
    {
96
        uint8_t gfxSet = sgd.PopUnsignedChar();
×
97
        lt = world.GetDescription().landscapes.find([gfxSet](const LandscapeDesc& l) { return l.s2Id == gfxSet; });
×
98
    } else
99
    {
100
        std::string sLandscape = sgd.PopString();
6✔
101
        lt = world.GetDescription().landscapes.getIndex(sLandscape);
3✔
102
        if(!lt)
3✔
103
            throw SerializedGameData::Error(std::string("Invalid landscape: ") + sLandscape);
×
104
    }
105
    world.Init(size, lt);
3✔
106
    GameObject::ResetCounters(sgd.PopUnsignedInt());
3✔
107

108
    std::vector<DescIdx<TerrainDesc>> landscapeTerrains;
6✔
109
    if(sgd.GetGameDataVersion() < 3)
3✔
110
    {
111
        // Assumes the order of the terrain in the description file is the same as in the prior RTTR versions
112
        landscapeTerrains =
113
          world.GetDescription().terrain.findAll([lt](const TerrainDesc& t) { return t.landscape == lt; });
×
114
    }
115
    // Alle Weltpunkte
116
    MapPoint curPos(0, 0);
3✔
117
    const unsigned numPlayers = world.GetNumPlayers();
3✔
118
    for(auto& node : world.nodes)
2,683✔
119
    {
120
        node.Deserialize(sgd, numPlayers, world.GetDescription(), landscapeTerrains);
2,680✔
121
        curPos.x++;
2,680✔
122
        if(curPos.x >= world.GetWidth())
2,680✔
123
        {
124
            curPos.x = 0;
74✔
125
            curPos.y++;
74✔
126
        }
127
    }
128

129
    sgd.PopObjectContainer(world.catapult_stones, GO_Type::Catapultstone);
3✔
130

131
    world.seas.resize(sgd.PopUnsignedInt());
3✔
132
    for(auto& sea : world.seas)
3✔
133
        sea.nodes_count = sgd.PopUnsignedInt();
×
134

135
    // Deserialize harbor data
136
    const unsigned numHarborPositions = sgd.PopUnsignedInt();
3✔
137
    world.harborData.clear();
3✔
138
    world.harborData.reserve(numHarborPositions);
3✔
139
    for(const auto i : helpers::range<unsigned>(numHarborPositions))
12✔
140
    {
141
        RTTR_UNUSED(i);
142
        world.harborData.emplace_back(sgd.PopMapPoint());
×
143
        auto& curHarborPos = world.harborData.back();
×
144
        helpers::popContainer(sgd, curHarborPos.seaIds);
×
145
        for(auto& neighbor : curHarborPos.neighbors)
×
146
        {
147
            const unsigned numNeighbors = sgd.PopUnsignedInt();
×
148
            neighbor.reserve(numNeighbors);
×
149
            for(const auto j : helpers::range<unsigned>(numNeighbors))
×
150
            {
151
                RTTR_UNUSED(j);
152
                const auto id = HarborId(sgd.PopUnsignedInt());
×
NEW
153
                SeaId sea;
×
NEW
154
                if(sgd.GetGameDataVersion() >= 15)
×
NEW
155
                    sea = SeaId(sgd.PopUnsignedInt());
×
156
                const auto distance = sgd.PopUnsignedInt();
×
NEW
157
                neighbor.emplace_back(id, sea, distance);
×
158
            }
159
        }
160
    }
161
    if(sgd.GetGameDataVersion() < 13 && !world.harborData.empty())
3✔
162
    {
163
        // Workaround for save games without increased game data version after introducing the change
164
        if(!world.harborData.front().pos.isValid())
×
165
            world.harborData.erase(world.harborData.begin());
×
166
    }
167
    if(sgd.GetGameDataVersion() < 15)
3✔
168
    {
NEW
169
        const auto getSeas = [&world](const HarborId& hbId) {
×
170
            boost::container::static_vector<SeaId, helpers::NumEnumValues_v<Direction>> seas;
NEW
171
            for(const auto sea : world.harborData[hbId].seaIds)
×
172
            {
NEW
173
                if(sea)
×
NEW
174
                    seas.push_back(sea);
×
175
            }
NEW
176
            return seas;
×
NEW
177
        };
×
178
        // Determine seas for neighbors
NEW
179
        for(const auto startHbId : helpers::idRange<HarborId>(world.GetNumHarborPoints()))
×
180
        {
NEW
181
            const auto mySeas = getSeas(startHbId);
×
NEW
182
            for(auto& neighborsPerDir : world.harborData[startHbId].neighbors)
×
183
            {
NEW
184
                for(auto& neighbor : neighborsPerDir)
×
185
                {
186
                    // Easy case: Either harbor is only at a single sea, so that must be the one
NEW
187
                    if(mySeas.size() == 1u)
×
188
                    {
NEW
189
                        neighbor.sea = mySeas.front();
×
NEW
190
                        continue;
×
191
                    }
NEW
192
                    const auto otherSeas = getSeas(neighbor.id);
×
NEW
193
                    if(otherSeas.size() == 1u)
×
194
                    {
NEW
195
                        neighbor.sea = otherSeas.front();
×
NEW
196
                        continue;
×
197
                    }
198
                    // Find the sea where this distance matches
NEW
199
                    for(const auto sea : mySeas)
×
200
                    {
NEW
201
                        if(!helpers::contains(otherSeas, sea))
×
NEW
202
                            continue;
×
NEW
203
                        unsigned len = 0;
×
NEW
204
                        const auto startPoint = world.GetCoastalPoint(startHbId, sea);
×
NEW
205
                        const auto nbPoint = world.GetCoastalPoint(neighbor.id, sea);
×
NEW
206
                        if(neighbor.distance == 0 && startPoint == nbPoint)
×
207
                        {
NEW
208
                            neighbor.sea = sea;
×
NEW
209
                            break;
×
210
                        }
NEW
211
                        if(world.FindShipPath(world.GetCoastalPoint(startHbId, sea),
×
212
                                              world.GetCoastalPoint(neighbor.id, sea), neighbor.distance, nullptr, &len)
NEW
213
                           && len == neighbor.distance)
×
214
                        {
NEW
215
                            neighbor.sea = sea;
×
NEW
216
                            break;
×
217
                        }
218
                    }
NEW
219
                    RTTR_Assert(neighbor.sea);
×
220
                }
221
            }
222
        }
223
    }
224

225
    sgd.PopObjectContainer(world.harbor_building_sites_from_sea, GO_Type::Buildingsite);
3✔
226

227
    const std::string luaScript = sgd.PopLongString();
6✔
228
    if(!luaScript.empty())
3✔
229
    {
230
        if(sgd.PopUnsignedInt() != 0xC0DEBA5E)
1✔
231
            throw SerializedGameData::Error(_("Invalid id for lua data"));
×
232
        // If there is a script, there is also save data. Store reference to that
233
        const auto luaSaveSize = sgd.PopUnsignedInt();
1✔
234
        Serializer luaSaveState(sgd.PopAndDiscard(luaSaveSize), luaSaveSize);
2✔
235
        if(sgd.PopUnsignedInt() != 0xC001C0DE)
1✔
236
            throw SerializedGameData::Error(_("Invalid end-id for lua data"));
×
237

238
        // Now init and load lua
239
        auto lua = std::make_unique<LuaInterfaceGame>(game, localgameState);
1✔
240
        if(!lua->loadScriptString(luaScript))
1✔
241
            throw SerializedGameData::Error(_("Lua script failed to load."));
×
242
        if(!lua->CheckScriptVersion())
1✔
243
            throw SerializedGameData::Error(_("Wrong version for lua script."));
×
244
        try
245
        {
246
            if(!lua->Deserialize(luaSaveState))
1✔
247
                throw SerializedGameData::Error(_("Lua load callback returned failure!"));
×
248
        } catch(const std::exception& e)
×
249
        {
250
            throw SerializedGameData::Error(std::string(_("Failed to load lua state!")) + _("Error: ") + e.what());
×
251
        }
252
        game.SetLua(std::move(lua));
1✔
253
    }
254
}
3✔
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