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

Return-To-The-Roots / s25client / 22020293377

14 Feb 2026 04:03PM UTC coverage: 50.827% (+0.001%) from 50.826%
22020293377

Pull #1889

github

web-flow
Merge 3cbfeb95c into e6489dca6
Pull Request #1889: Pathfinding: reject unconnected buildings / construction sites in specific scenarios

9 of 17 new or added lines in 2 files covered. (52.94%)

5 existing lines in 2 files now uncovered.

22799 of 44856 relevant lines covered (50.83%)

41781.22 hits per line

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

59.38
/libs/s25main/buildings/noBaseBuilding.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 "noBaseBuilding.h"
6
#include "GameInterface.h"
7
#include "GamePlayer.h"
8
#include "GlobalGameSettings.h"
9
#include "Loader.h"
10
#include "SerializedGameData.h"
11
#include "Ware.h"
12
#include "addons/const_addons.h"
13
#include "nobBaseWarehouse.h"
14
#include "notifications/BuildingNote.h"
15
#include "world/GameWorld.h"
16
#include "nodeObjs/noExtension.h"
17
#include "nodeObjs/noFlag.h"
18
#include "gameData/BuildingConsts.h"
19
#include "gameData/DoorConsts.h"
20
#include "gameData/MapConsts.h"
21
#include "s25util/Log.h"
22

23
noBaseBuilding::noBaseBuilding(const NodalObjectType nop, const BuildingType type, const MapPoint pos,
607✔
24
                               const unsigned char player)
607✔
25
    : noRoadNode(nop, pos, player), bldType_(type), nation(world->GetPlayer(player).nation), door_point_x(1000000),
1,214✔
26
      door_point_y(DOOR_CONSTS[world->GetPlayer(player).nation][type])
607✔
27
{
28
    MapPoint flagPt = GetFlagPos();
607✔
29
    // Evtl Flagge setzen, wenn noch keine da ist
30
    if(world->GetNO(flagPt)->GetType() != NodalObjectType::Flag)
607✔
31
    {
32
        world->DestroyNO(flagPt, false);
554✔
33
        world->SetNO(flagPt, new noFlag(flagPt, player));
554✔
34
    }
35

36
    // Straßeneingang setzen (wenn nicht schon vorhanden z.b. durch vorherige Baustelle!)
37
    if(world->GetPointRoad(pos, Direction::SouthEast) == PointRoad::None)
607✔
38
    {
39
        world->SetPointRoad(pos, Direction::SouthEast, PointRoad::Normal);
601✔
40

41
        // Straßenverbindung erstellen zwischen Flagge und Haus
42
        // immer von Flagge ZU Gebäude (!)
43
        std::vector<Direction> route(1, Direction::NorthWest);
1,202✔
44
        // Straße zuweisen
45
        auto* rs = new RoadSegment(RoadType::Normal, world->GetSpecObj<noRoadNode>(flagPt), this, route);
601✔
46
        world->GetSpecObj<noRoadNode>(flagPt)->SetRoute(Direction::NorthWest, rs); // der Flagge
601✔
47
        SetRoute(Direction::SouthEast, rs);                                        // dem Gebäude
601✔
48
    } else
49
    {
50
        // vorhandene Straße der Flagge nutzen
51
        auto* flag = world->GetSpecObj<noFlag>(flagPt);
6✔
52

53
        RTTR_Assert(flag->GetRoute(Direction::NorthWest));
6✔
54
        SetRoute(Direction::SouthEast, flag->GetRoute(Direction::NorthWest));
6✔
55
        GetRoute(Direction::SouthEast)->SetF2(this);
6✔
56
    }
57

58
    // Werde/Bin ich (mal) ein großes Schloss? Dann müssen die Anbauten gesetzt werden
59
    if(GetSize() == BuildingQuality::Castle || GetSize() == BuildingQuality::Harbor)
607✔
60
    {
61
        for(const Direction i : {Direction::West, Direction::NorthWest, Direction::NorthEast})
1,584✔
62
        {
63
            const MapPoint neighbor = world->GetNeighbour(pos, i);
1,188✔
64

65
            if(type == BuildingType::Headquarters)
1,188✔
66
            {
67
                const NodalObjectType neighborNoType = world->GetNO(neighbor)->GetType();
993✔
68
                // Don't replace nearby static objects or trees. Needed for "build headquarters" cheat to work like in
69
                // the original. This situation shouldn't happen any other way (can't normally build big buildings right
70
                // next to static objects or trees). Trees which be remain because of this will be replaced by
71
                // extensions instead of stumps if they are cut while still right next to the HQ.
72
                if(neighborNoType == NodalObjectType::Object || neighborNoType == NodalObjectType::Tree)
993✔
73
                    continue;
×
74
            }
75

76
            world->DestroyNO(neighbor, false);
1,188✔
77
            world->SetNO(neighbor, new noExtension(this));
1,188✔
78
        }
79
    }
80
}
607✔
81

82
noBaseBuilding::~noBaseBuilding() = default;
618✔
83

84
void noBaseBuilding::Destroy()
111✔
85
{
86
    DestroyAllRoads();
111✔
87
    world->GetNotifications().publish(BuildingNote(BuildingNote::Destroyed, player, pos, bldType_));
111✔
88

89
    if(world->GetGameInterface())
111✔
90
        world->GetGameInterface()->GI_UpdateMinimap(pos);
×
91

92
    // evtl Anbauten wieder abreißen
93
    DestroyBuildingExtensions();
111✔
94

95
    // Baukosten zurückerstatten (nicht bei Baustellen)
96
    const GlobalGameSettings& settings = world->GetGGS();
111✔
97
    if((GetGOT() != GO_Type::Buildingsite)
111✔
98
       && (settings.isEnabled(AddonId::REFUND_MATERIALS) || settings.isEnabled(AddonId::REFUND_ON_EMERGENCY)))
111✔
99
    {
100
        // lebt unsere Flagge noch?
101
        noFlag* flag = GetFlag();
×
102
        if(flag)
×
103
        {
104
            unsigned percent_index = 0;
×
105

106
            // wenn Rückerstattung aktiv ist, entsprechende Prozentzahl wählen
107
            if(settings.isEnabled(AddonId::REFUND_MATERIALS))
×
108
                percent_index = settings.getSelection(AddonId::REFUND_MATERIALS);
×
109
            // wenn Rückerstattung bei Notprogramm aktiv ist, 50% zurückerstatten
110
            else if(world->GetPlayer(player).hasEmergency() && settings.isEnabled(AddonId::REFUND_ON_EMERGENCY))
×
111
                percent_index = 2;
×
112

113
            // wieviel kriegt man von jeder Ware wieder?
114
            const std::array<unsigned, 5> percents = {0, 25, 50, 75, 100};
×
115
            const unsigned percent = 10 * percents[percent_index];
×
116

117
            // zurückgaben berechnen (abgerundet)
118
            unsigned boards = (percent * BUILDING_COSTS[bldType_].boards) / 1000;
×
119
            unsigned stones = (percent * BUILDING_COSTS[bldType_].stones) / 1000;
×
120

121
            std::array<GoodType, 2> goods = {GoodType::Boards, GoodType::Stones};
×
122
            bool which = false;
×
123
            while(flag->HasSpaceForWare() && (boards > 0 || stones > 0))
×
124
            {
125
                if((!which && boards > 0) || (which && stones > 0))
×
126
                {
127
                    // Ware erzeugen
128
                    auto ware = std::make_unique<Ware>(goods[which], nullptr, flag);
×
129
                    ware->WaitAtFlag(flag);
×
130
                    // Inventur anpassen
131
                    world->GetPlayer(player).IncreaseInventoryWare(goods[which], 1);
×
132
                    // Abnehmer für Ware finden
133
                    ware->SetGoal(world->GetPlayer(player).FindClientForWare(*ware));
×
134
                    // Ware soll ihren weiteren Weg berechnen
135
                    ware->RecalcRoute();
×
136
                    // Ware ablegen
137
                    flag->AddWare(std::move(ware));
×
138

139
                    if(!which)
×
140
                        --boards;
×
141
                    else
142
                        --stones;
×
143
                }
144

145
                which = !which;
×
146
            }
147
        }
148
    }
149

150
    noRoadNode::Destroy();
111✔
151
}
111✔
152

153
void noBaseBuilding::Serialize(SerializedGameData& sgd) const
23✔
154
{
155
    noRoadNode::Serialize(sgd);
23✔
156

157
    sgd.PushEnum<uint8_t>(bldType_);
23✔
158
    sgd.PushEnum<uint8_t>(nation);
23✔
159
    sgd.PushSignedInt(door_point_x);
23✔
160
    sgd.PushSignedInt(door_point_y);
23✔
161
}
23✔
162

163
noBaseBuilding::noBaseBuilding(SerializedGameData& sgd, const unsigned obj_id)
11✔
164
    : noRoadNode(sgd, obj_id), bldType_(sgd.Pop<BuildingType>()), nation(sgd.Pop<Nation>()),
33✔
165
      door_point_x(sgd.PopSignedInt()), door_point_y(sgd.PopSignedInt())
11✔
166
{}
11✔
167

168
int noBaseBuilding::GetDoorPointX()
×
169
{
170
    // Did we calculate the door point yet?
171
    if(door_point_x == 1000000)
×
172
    {
173
        // The door is on the line between the building and flag point. The position of the line is set by the y-offset
174
        // this is why we need the x-offset here according to the equation x = m*y + n
175
        // with n=0 (as door point is relative to building pos) and m = dx/dy
176
        const Position bldPos = world->GetNodePos(pos);
×
177
        const Position flagPos = world->GetNodePos(GetFlagPos());
×
178
        Position diff = flagPos - bldPos;
×
179

180
        // We could have crossed the map border which results in unreasonable diffs
181
        // clamp the diff to [-w/2,w/2],[-h/2, h/2] (maximum diffs)
182
        const int mapWidth = world->GetWidth() * TR_W;
×
183
        const int mapHeight = world->GetHeight() * TR_H;
×
184

185
        if(diff.x < -mapWidth / 2)
×
186
            diff.x += mapWidth;
×
187
        else if(diff.x > mapWidth / 2)
×
188
            diff.x -= mapWidth;
×
189
        if(diff.y < -mapHeight / 2)
×
190
            diff.y += mapHeight;
×
191
        else if(diff.y > mapHeight / 2)
×
192
            diff.y -= mapHeight;
×
193

194
        door_point_x = (door_point_y * diff.x) / diff.y;
×
195
    }
196

197
    return door_point_x;
×
198
}
199

200
noFlag* noBaseBuilding::GetFlag() const
1,981✔
201
{
202
    return world->GetSpecObj<noFlag>(GetFlagPos());
1,981✔
203
}
204

205
MapPoint noBaseBuilding::GetFlagPos() const
3,065✔
206
{
207
    return world->GetNeighbour(pos, Direction::SouthEast);
3,065✔
208
}
209

210
bool noBaseBuilding::IsConnected() const
42✔
211
{
212
    const helpers::EnumArray<RoadSegment*, Direction> routes = this->GetFlag()->getRoutes();
42✔
213

214
    // Check paths in all directions
215
    for(const auto dir : helpers::EnumRange<Direction>{})
286✔
216
    {
217
        const auto* route = routes[dir];
101✔
218
        if(route && dir != Direction::NorthWest)
101✔
219
            return true;
42✔
220
    }
221

NEW
222
    return false;
×
223
}
224

225
void noBaseBuilding::WareNotNeeded(Ware* ware)
21✔
226
{
227
    if(!ware)
21✔
228
    {
229
        RTTR_Assert(false);
×
230
        LOG.write("Warning: Trying to remove non-existing ware. Please report this replay!\n");
231
        return;
232
    }
233

234
    if(ware->IsWaitingInWarehouse())
21✔
235
    {
236
        // Bestellung im Lagerhaus stornieren
237
        world->GetPlayer(player).RemoveWare(*ware);
10✔
238
        static_cast<nobBaseWarehouse*>(ware->GetLocation())->CancelWare(ware);
10✔
239
    } else
240
        ware->GoalDestroyed();
11✔
241
}
242

243
void noBaseBuilding::DestroyBuildingExtensions()
111✔
244
{
245
    // Nur bei großen Gebäuden gibts diese Anbauten
246
    if(GetSize() == BuildingQuality::Castle || GetSize() == BuildingQuality::Harbor)
111✔
247
    {
248
        for(const Direction i : {Direction::West, Direction::NorthWest, Direction::NorthEast})
224✔
249
        {
250
            const MapPoint neighbor = world->GetNeighbour(pos, i);
168✔
251
            if(world->GetNO(neighbor)->GetType() == NodalObjectType::Extension)
168✔
252
                world->DestroyNO(neighbor);
168✔
253
        }
254
    }
255
}
111✔
256

257
BuildingQuality noBaseBuilding::GetSize() const
1,289✔
258
{
259
    return BUILDING_SIZE[bldType_];
1,289✔
260
}
261

262
BlockingManner noBaseBuilding::GetBM() const
13,663✔
263
{
264
    return BlockingManner::Building;
13,663✔
265
}
266

267
/// Gibt ein Bild zurück für das normale Gebäude
268
ITexture& noBaseBuilding::GetBuildingImage() const
×
269
{
270
    return GetBuildingImage(bldType_, nation);
×
271
}
272

273
ITexture& noBaseBuilding::GetBuildingImage(BuildingType type, Nation nation) //-V688
×
274
{
275
    return LOADER.building_cache[nation][type].building;
×
276
}
277

278
/// Gibt ein Bild zurück für die Tür des Gebäudes
279
ITexture& noBaseBuilding::GetDoorImage() const
×
280
{
281
    return LOADER.building_cache[nation][bldType_].door;
×
282
}
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