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

Return-To-The-Roots / s25client / 22140547164

18 Feb 2026 12:54PM UTC coverage: 50.804% (-0.005%) from 50.809%
22140547164

Pull #1895

github

web-flow
Merge 2294b7ca4 into 7a79bd216
Pull Request #1895: Ships: Fix crash when doing expedition between adjacent harbor places

120 of 169 new or added lines in 20 files covered. (71.01%)

129 existing lines in 5 files now uncovered.

22810 of 44898 relevant lines covered (50.8%)

42709.3 hits per line

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

62.29
/libs/s25main/ai/AIInterface.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 "AIInterface.h"
6
#include "buildings/noBuilding.h"
7
#include "buildings/noBuildingSite.h"
8
#include "buildings/nobHQ.h"
9
#include "buildings/nobHarborBuilding.h"
10
#include "buildings/nobMilitary.h"
11
#include "buildings/nobShipYard.h"
12
#include "helpers/containerUtils.h"
13
#include "network/GameMessage_Chat.h"
14
#include "pathfinding/FreePathFinder.h"
15
#include "pathfinding/PathConditionRoad.h"
16
#include "pathfinding/RoadPathFinder.h"
17
#include "nodeObjs/noFlag.h"
18
#include "nodeObjs/noTree.h"
19
#include "gameData/TerrainDesc.h"
20
#include <limits>
21
#include <numeric>
22

23
class noRoadNode;
24

25
namespace {
26
/// Param for road-build pathfinding
27
struct Param_RoadPath
28
{
29
    /// Boat or normal road
30
    bool boat_road;
31
};
32

33
bool IsPointOK_RoadPath(const GameWorldBase& gwb, const MapPoint pt, const Direction, const void* param)
11,212✔
34
{
35
    const auto* prp = static_cast<const Param_RoadPath*>(param);
11,212✔
36
    return makePathConditionRoad(gwb, prp->boat_road).IsNodeOk(pt);
22,424✔
37
}
38

39
/// Condition for comfort road construction with a possible flag every 2 steps
40
bool IsPointOK_RoadPathEvenStep(const GameWorldBase& gwb, const MapPoint pt, const Direction dir, const void* param)
6,383✔
41
{
42
    if(!IsPointOK_RoadPath(gwb, pt, dir, param))
6,383✔
43
        return false;
1,675✔
44
    const auto* prp = static_cast<const Param_RoadPath*>(param);
4,708✔
45
    return prp->boat_road || gwb.GetBQ(pt, gwb.GetNode(pt).owner - 1) != BuildingQuality::Nothing;
4,708✔
46
}
47
} // namespace
48

49
AIInterface::AIInterface(const GameWorldBase& gwb, std::vector<gc::GameCommandPtr>& gcs, unsigned char playerID)
19✔
50
    : gwb(gwb), player_(gwb.GetPlayer(playerID)), gcs(gcs), playerID_(playerID)
19✔
51
{
52
    for(unsigned curHarborId = 1; curHarborId <= gwb.GetNumHarborPoints(); curHarborId++)
19✔
53
    {
54
        bool hasOtherHarbor = false;
×
55
        for(const auto dir : helpers::EnumRange<Direction>{})
×
56
        {
57
            const unsigned short seaId = gwb.GetSeaId(curHarborId, dir);
×
NEW
58
            if(!seaId)
×
59
                continue;
×
60

61
            for(unsigned otherHarborId = curHarborId + 1; otherHarborId <= gwb.GetNumHarborPoints(); otherHarborId++)
×
62
            {
63
                if(gwb.IsHarborAtSea(otherHarborId, seaId))
×
64
                {
65
                    hasOtherHarbor = true;
×
66
                    break;
×
67
                }
68
            }
69
            if(hasOtherHarbor)
×
70
                usableHarbors_.push_back(curHarborId);
×
71
        }
72
    }
73
}
19✔
74

75
AIInterface::~AIInterface() = default;
19✔
76

77
AISubSurfaceResource AIInterface::GetSubsurfaceResource(const MapPoint pt) const
206,856✔
78
{
79
    const Resource subres = gwb.GetNode(pt).resources;
206,856✔
80
    if(subres.getAmount() == 0u)
206,856✔
81
        return AISubSurfaceResource::Nothing;
206,856✔
82
    switch(subres.getType())
×
83
    {
84
        case ResourceType::Iron: return AISubSurfaceResource::Ironore;
×
85
        case ResourceType::Gold: return AISubSurfaceResource::Gold;
×
86
        case ResourceType::Coal: return AISubSurfaceResource::Coal;
×
87
        case ResourceType::Granite: return AISubSurfaceResource::Granite;
×
88
        case ResourceType::Fish: return AISubSurfaceResource::Fish;
×
89
        case ResourceType::Nothing:
×
90
        case ResourceType::Water: break;
×
91
    }
92
    return AISubSurfaceResource::Nothing;
×
93
}
94

95
AISurfaceResource AIInterface::GetSurfaceResource(const MapPoint pt) const
761,028✔
96
{
97
    const auto& node = gwb.GetNode(pt);
761,028✔
98
    NodalObjectType no = node.obj ? node.obj->GetType() : NodalObjectType::Nothing;
761,028✔
99
    DescIdx<TerrainDesc> t1 = node.t1;
761,028✔
100
    // valid terrain?
101
    if(gwb.GetDescription().get(t1).Is(ETerrain::Walkable))
761,028✔
102
    {
103
        if(no == NodalObjectType::Tree)
761,028✔
104
        {
105
            // exclude pineapple because it's not a real tree
106
            if(static_cast<const noTree*>(node.obj)->ProducesWood())
28,181✔
107
                return AISurfaceResource::Wood;
28,181✔
108
            else
109
                return AISurfaceResource::Blocked;
×
110
        } else if(no == NodalObjectType::Granite)
732,847✔
111
            return AISurfaceResource::Stones;
×
112
        else if(no == NodalObjectType::Nothing || no == NodalObjectType::Environment)
732,847✔
113
            return AISurfaceResource::Nothing;
724,439✔
114
        else
115
            return AISurfaceResource::Blocked;
8,408✔
116
    } else
117
        return AISurfaceResource::Blocked;
×
118
}
119

120
int AIInterface::GetResourceRating(const MapPoint pt, AIResource res) const
1,153,578✔
121
{
122
    switch(res)
1,153,578✔
123
    {
124
        case AIResource::Wood:
272,940✔
125
            if(GetSurfaceResource(pt) == AISurfaceResource::Wood)
272,940✔
126
                return RES_RADIUS[res];
13,289✔
127
            else if(IsBuildingOnNode(pt, BuildingType::Woodcutter))
259,651✔
128
                return -40;
157✔
129
            else if(IsBuildingOnNode(pt, BuildingType::Forester))
259,494✔
130
                return 20;
36✔
131
            break;
259,458✔
132
        case AIResource::Stones:
484,344✔
133
            if(GetSurfaceResource(pt) == AISurfaceResource::Stones)
484,344✔
134
                return RES_RADIUS[res];
×
135
            break;
484,344✔
136
        case AIResource::Plantspace:
×
137
            if(GetSurfaceResource(pt) == AISurfaceResource::Nothing
×
138
               && gwb.GetDescription().get(gwb.GetNode(pt).t1).IsVital())
×
139
                return RES_RADIUS[res];
×
140
            else if(IsBuildingOnNode(pt, BuildingType::Forester))
×
141
                return -40;
×
142
            else if(IsBuildingOnNode(pt, BuildingType::Farm))
×
143
                return -20;
×
144
            break;
×
145
        case AIResource::Borderland:
193,182✔
146
            if(IsOwnTerritory(pt) && !IsBorder(pt))
193,182✔
147
                return 0;
95,637✔
148
            else
149
            {
150
                const auto& desc = gwb.GetDescription();
97,545✔
151
                const auto& node = gwb.GetNode(pt);
97,545✔
152
                if(desc.get(node.t1).Is(ETerrain::Walkable) || desc.get(node.t2).Is(ETerrain::Walkable))
97,545✔
153
                    return RES_RADIUS[res];
97,545✔
154
            }
155
            break;
×
156
        case AIResource::Gold:
203,112✔
157
        case AIResource::Ironore:
158
        case AIResource::Coal:
159
        case AIResource::Granite:
160
        case AIResource::Fish:
161
            if(convertToNodeResource(GetSubsurfaceResource(pt)) == res)
203,112✔
162
                return RES_RADIUS[res];
×
163
            break;
203,112✔
164
    }
165
    return 0;
946,914✔
166
}
167

168
int AIInterface::CalcResourceValue(const MapPoint pt, AIResource res, helpers::OptionalEnum<Direction> direction,
21,096✔
169
                                   int lastval) const
170
{
171
    const unsigned resRadius = RES_RADIUS[res];
21,096✔
172
    if(!direction) // calculate complete value from scratch (3n^2+3n+1)
21,096✔
173
    {
174
        std::vector<MapPoint> pts = gwb.GetPointsInRadiusWithCenter(pt, resRadius);
9,012✔
175
        return std::accumulate(pts.begin(), pts.end(), 0, [this, res](int lhs, const auto& curPt) {
4,506✔
176
            return lhs + this->GetResourceRating(curPt, res);
693,798✔
177
        });
4,506✔
178
    } else // calculate different nodes only (4n+2 ?anyways much faster)
179
    {
180
        const auto iDirection = rttr::enum_cast(*direction);
16,590✔
181
        int returnVal = lastval;
16,590✔
182
        // add new points
183
        // first: go radius steps towards direction-1
184
        MapPoint tmpPt(pt);
16,590✔
185
        for(unsigned i = 0; i < resRadius; i++)
123,240✔
186
            tmpPt = gwb.GetNeighbour(tmpPt, *direction - 1u);
106,650✔
187
        // then clockwise around at radius distance to get all new points
188
        for(unsigned i = iDirection + 1u; i < iDirection + 3u; ++i)
49,770✔
189
        {
190
            int numSteps = resRadius;
33,180✔
191
            // add 1 extra step on the second side we check to complete the side
192
            if(i == iDirection + 2u)
33,180✔
193
                ++numSteps;
16,590✔
194
            for(MapCoord r2 = 0; r2 < numSteps; ++r2)
263,070✔
195
            {
196
                returnVal += GetResourceRating(tmpPt, res);
229,890✔
197
                tmpPt = gwb.GetNeighbour(tmpPt, convertToDirection(i));
229,890✔
198
            }
199
        }
200
        // now substract old points not in range of new point
201
        // go to old center point:
202
        tmpPt = pt;
16,590✔
203
        tmpPt = gwb.GetNeighbour(tmpPt, *direction + 3u);
16,590✔
204
        // next: go to the first old point we have to substract
205
        for(unsigned i = 0; i < RES_RADIUS[res]; i++)
123,240✔
206
            tmpPt = gwb.GetNeighbour(tmpPt, *direction + 2u);
106,650✔
207
        // now clockwise around at radius distance to remove all old points
208
        for(int i = iDirection + 4; i < iDirection + 6; ++i)
49,770✔
209
        {
210
            int numSteps = resRadius;
33,180✔
211
            if(i == iDirection + 5)
33,180✔
212
                ++numSteps;
16,590✔
213
            for(MapCoord r2 = 0; r2 < numSteps; ++r2)
263,070✔
214
            {
215
                returnVal -= GetResourceRating(tmpPt, res);
229,890✔
216
                tmpPt = gwb.GetNeighbour(tmpPt, convertToDirection(i));
229,890✔
217
            }
218
        }
219
        return returnVal;
16,590✔
220
    }
221
    // if(returnval<0&&lastval>=0&&res==AIResource::Borderland)
222
    // LOG.write(("AIInterface::CalcResourceValue - warning: negative returnvalue direction %i oldval %i\n", direction,
223
    // lastval);
224
}
225

226
bool AIInterface::FindFreePathForNewRoad(MapPoint start, MapPoint target, std::vector<Direction>* route /*= nullptr*/,
38✔
227
                                         unsigned* length /*= nullptr*/) const
228
{
229
    bool boat = false;
38✔
230
    return gwb.GetFreePathFinder().FindPathAlternatingConditions(start, target, false, 100, route, length, nullptr,
38✔
231
                                                                 IsPointOK_RoadPath, IsPointOK_RoadPathEvenStep,
232
                                                                 nullptr, (void*)&boat);
76✔
233
}
234

235
bool AIInterface::CalcBQSumDifference(const MapPoint pt1, const MapPoint pt2) const
×
236
{
237
    return GetBuildingQuality(pt2) < GetBuildingQuality(pt1);
×
238
}
239

240
BuildingQuality AIInterface::GetBuildingQuality(const MapPoint pt) const
51,190✔
241
{
242
    return gwb.GetBQ(pt, playerID_);
51,190✔
243
}
244

245
BuildingQuality AIInterface::GetBuildingQualityAnyOwner(const MapPoint pt) const
×
246
{
247
    return gwb.GetNode(pt).bq;
×
248
}
249

250
bool AIInterface::FindPathOnRoads(const noRoadNode& start, const noRoadNode& target, unsigned* length) const
85✔
251
{
252
    if(length)
85✔
253
        return gwb.GetRoadPathFinder().FindPath(start, target, false, std::numeric_limits<unsigned>::max(), nullptr,
39✔
254
                                                length);
39✔
255
    else
256
        return gwb.GetRoadPathFinder().PathExists(start, target, false);
46✔
257
}
258

259
const nobHQ* AIInterface::GetHeadquarter() const
×
260
{
261
    return gwb.GetSpecObj<nobHQ>(player_.GetHQPos());
×
262
}
263

264
bool AIInterface::isBuildingNearby(BuildingType bldType, const MapPoint pt, unsigned maxDistance) const
1✔
265
{
266
    for(const nobUsual* bld : GetBuildings(bldType))
1✔
267
    {
268
        if(gwb.CalcDistance(pt, bld->GetPos()) <= maxDistance)
×
269
            return true;
×
270
    }
271
    for(const noBuildingSite* bldSite : GetBuildingSites())
1✔
272
    {
273
        if(bldSite->GetBuildingType() == bldType)
×
274
        {
275
            if(gwb.CalcDistance(pt, bldSite->GetPos()) <= maxDistance)
×
276
                return true;
×
277
        }
278
    }
279
    return false;
1✔
280
}
281

282
bool AIInterface::isHarborPosClose(const MapPoint pt, unsigned maxDistance, bool onlyempty) const
5,053✔
283
{
284
    // skip harbordummy
285
    for(unsigned i = 1; i <= gwb.GetNumHarborPoints(); i++)
5,053✔
286
    {
NEW
287
        const MapPoint harborPoint = gwb.GetHarborPoint(i);
×
NEW
288
        if(gwb.CalcDistance(pt, harborPoint) <= maxDistance && helpers::contains(usableHarbors_, i))
×
289
        {
290
            if(!onlyempty || !IsBuildingOnNode(harborPoint, BuildingType::HarborBuilding))
×
291
                return true;
×
292
        }
293
    }
294
    return false;
5,053✔
295
}
296

297
bool AIInterface::IsExplorationDirectionPossible(const MapPoint pt, const nobHarborBuilding* originHarbor,
×
298
                                                 ShipDirection direction) const
299
{
300
    return IsExplorationDirectionPossible(pt, originHarbor->GetHarborPosID(), direction);
×
301
}
302

NEW
303
bool AIInterface::IsExplorationDirectionPossible(const MapPoint pt, unsigned originHarborID,
×
304
                                                 ShipDirection direction) const
305
{
NEW
306
    return gwb.GetNextFreeHarborPoint(pt, originHarborID, direction, playerID_) > 0;
×
307
}
308

309
bool AIInterface::SetCoinsAllowed(const nobMilitary* building, const bool enabled)
×
310
{
311
    return SetCoinsAllowed(building->GetPos(), enabled);
×
312
}
313
bool AIInterface::StartStopExpedition(const nobHarborBuilding* hb, bool start)
×
314
{
315
    return StartStopExpedition(hb->GetPos(), start);
×
316
}
317
bool AIInterface::SetShipYardMode(const nobShipYard* shipyard, bool buildShips)
×
318
{
319
    return SetShipYardMode(shipyard->GetPos(), buildShips);
×
320
}
321
bool AIInterface::DestroyBuilding(const noBuilding* building)
×
322
{
323
    return DestroyBuilding(building->GetPos());
×
324
}
325
bool AIInterface::DestroyFlag(const noFlag* flag)
×
326
{
327
    return DestroyFlag(flag->GetPos());
×
328
}
329
bool AIInterface::CallSpecialist(const noFlag* flag, Job job)
×
330
{
331
    return CallSpecialist(flag->GetPos(), job);
×
332
}
333

334
void AIInterface::Chat(const std::string& message, ChatDestination destination)
8✔
335
{
336
    pendingChatMsgs_.push_back(std::make_unique<GameMessage_Chat>(playerID_, destination, message));
8✔
337
}
8✔
338

339
std::vector<std::unique_ptr<GameMessage_Chat>> AIInterface::FetchChatMessages()
3✔
340
{
341
    std::vector<std::unique_ptr<GameMessage_Chat>> tmp;
3✔
342
    std::swap(tmp, pendingChatMsgs_);
3✔
343
    return tmp;
3✔
344
}
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