• 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

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/IdRange.h"
13
#include "helpers/containerUtils.h"
14
#include "network/GameMessage_Chat.h"
15
#include "pathfinding/FreePathFinder.h"
16
#include "pathfinding/PathConditionRoad.h"
17
#include "pathfinding/RoadPathFinder.h"
18
#include "nodeObjs/noFlag.h"
19
#include "nodeObjs/noTree.h"
20
#include "gameData/TerrainDesc.h"
21
#include <limits>
22
#include <numeric>
23

24
class noRoadNode;
25

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

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

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

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

NEW
62
            for(const auto otherHarborId : helpers::idRangeAfter(curHarborId, gwb.GetNumHarborPoints()))
×
63
            {
64
                if(gwb.IsHarborAtSea(otherHarborId, seaId))
×
65
                {
66
                    hasOtherHarbor = true;
×
67
                    break;
×
68
                }
69
            }
70
            if(hasOtherHarbor)
×
71
                usableHarbors_.push_back(curHarborId);
×
72
        }
73
    }
74
}
19✔
75

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

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

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

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

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

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

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

241
BuildingQuality AIInterface::GetBuildingQuality(const MapPoint pt) const
50,482✔
242
{
243
    return gwb.GetBQ(pt, playerID_);
50,482✔
244
}
245

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

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

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

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

283
bool AIInterface::isHarborPosClose(const MapPoint pt, unsigned maxDistance, bool onlyempty) const
5,053✔
284
{
285
    for(const auto id : helpers::idRange<HarborId>(gwb.GetNumHarborPoints()))
5,053✔
286
    {
NEW
287
        const MapPoint harborPoint = gwb.GetHarborPoint(id);
×
NEW
288
        if(gwb.CalcDistance(pt, harborPoint) <= maxDistance && helpers::contains(usableHarbors_, id))
×
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, HarborId originHarborID,
×
304
                                                 ShipDirection direction) const
305
{
NEW
306
    return gwb.GetNextFreeHarborPoint(pt, originHarborID, direction, playerID_).isValid();
×
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