• 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

79.2
/libs/s25main/world/GameWorldBase.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/GameWorldBase.h"
6
#include "BQCalculator.h"
7
#include "GamePlayer.h"
8
#include "GlobalGameSettings.h"
9
#include "MapGeometry.h"
10
#include "RttrForeachPt.h"
11
#include "SoundManager.h"
12
#include "TradePathCache.h"
13
#include "addons/const_addons.h"
14
#include "buildings/nobHarborBuilding.h"
15
#include "buildings/nobMilitary.h"
16
#include "figures/nofPassiveSoldier.h"
17
#include "helpers/EnumRange.h"
18
#include "helpers/IdRange.h"
19
#include "helpers/containerUtils.h"
20
#include "lua/LuaInterfaceGame.h"
21
#include "notifications/NodeNote.h"
22
#include "notifications/PlayerNodeNote.h"
23
#include "pathfinding/FreePathFinder.h"
24
#include "pathfinding/RoadPathFinder.h"
25
#include "nodeObjs/noFlag.h"
26
#include "gameData/BuildingProperties.h"
27
#include "gameData/GameConsts.h"
28
#include "gameData/TerrainDesc.h"
29
#include <utility>
30

31
GameWorldBase::GameWorldBase(std::vector<GamePlayer> players, const GlobalGameSettings& gameSettings, EventManager& em)
193✔
32
    : roadPathFinder(new RoadPathFinder(*this)), freePathFinder(new FreePathFinder(*this)), players(std::move(players)),
386✔
33
      gameSettings(gameSettings), em(em), soundManager(std::make_unique<SoundManager>()), lua(nullptr), gi(nullptr)
579✔
34
{}
193✔
35

36
GameWorldBase::~GameWorldBase() = default;
193✔
37

38
void GameWorldBase::Init(const MapExtent& mapSize, DescIdx<LandscapeDesc> lt)
182✔
39
{
40
    RTTR_Assert(GetDescription().terrain.size() > 0); // Must have game data initialized
182✔
41
    World::Init(mapSize, lt);
182✔
42
    freePathFinder->Init(mapSize);
182✔
43
}
182✔
44

45
void GameWorldBase::InitAfterLoad()
165✔
46
{
47
    RTTR_FOREACH_PT(MapPoint, GetSize())
285,801✔
48
        RecalcBQ(pt);
280,228✔
49
}
165✔
50

51
GamePlayer& GameWorldBase::GetPlayer(const unsigned id)
22,272✔
52
{
53
    RTTR_Assert(id < GetNumPlayers());
22,272✔
54
    return players[id];
22,272✔
55
}
56

57
const GamePlayer& GameWorldBase::GetPlayer(const unsigned id) const
48,670✔
58
{
59
    RTTR_Assert(id < GetNumPlayers());
48,670✔
60
    return players[id];
48,670✔
61
}
62

63
unsigned GameWorldBase::GetNumPlayers() const
89,514✔
64
{
65
    return players.size();
89,514✔
66
}
67

68
bool GameWorldBase::IsSinglePlayer() const
135✔
69
{
70
    bool foundPlayer = false;
135✔
71
    for(const PlayerInfo& player : players)
307✔
72
    {
73
        if(player.ps == PlayerState::Occupied)
179✔
74
        {
75
            if(foundPlayer)
142✔
76
                return false;
7✔
77
            else
78
                foundPlayer = true;
135✔
79
        }
80
    }
81
    return true;
128✔
82
}
83

84
bool GameWorldBase::IsRoadAvailable(const bool boat_road, const MapPoint pt) const
19,094✔
85
{
86
    // Hindernisse
87
    if(GetNode(pt).obj)
19,094✔
88
    {
89
        BlockingManner bm = GetNode(pt).obj->GetBM();
2,726✔
90
        if(bm != BlockingManner::None)
2,726✔
91
            return false;
2,634✔
92
    }
93

94
    // dont build on the border
95
    if(GetNode(pt).boundary_stones[BorderStonePos::OnPoint])
16,460✔
96
        return false;
6✔
97

98
    for(const auto dir : helpers::EnumRange<Direction>{})
257,312✔
99
    {
100
        // Roads around charburner piles are not possible
101
        if(GetNO(GetNeighbour(pt, dir))->GetBM() == BlockingManner::NothingAround)
96,328✔
102
            return false;
580✔
103

104
        // Other roads at this point?
105
        if(GetPointRoad(pt, dir) != PointRoad::None)
96,328✔
106
            return false;
580✔
107
    }
108

109
    // Terrain (unterscheiden, ob Wasser und Landweg)
110
    if(!boat_road)
15,874✔
111
    {
112
        bool flagPossible = false;
15,874✔
113

114
        for(const DescIdx<TerrainDesc> tIdx : GetTerrainsAround(pt))
109,774✔
115
        {
116
            const TerrainBQ bq = GetDescription().get(tIdx).GetBQ();
94,284✔
117
            if(bq == TerrainBQ::Danger)
94,284✔
118
                return false;
384✔
119
            else if(bq != TerrainBQ::Nothing)
93,900✔
120
                flagPossible = true;
93,532✔
121
        }
122

123
        return flagPossible;
15,490✔
124
    } else
125
    {
126
        // Beim Wasserweg muss um den Punkt herum Wasser sein
127
        if(!IsWaterPoint(pt))
×
128
            return false;
×
129
    }
130

131
    return true;
×
132
}
133

134
bool GameWorldBase::RoadAlreadyBuilt(const bool /*boat_road*/, const MapPoint start,
386✔
135
                                     const std::vector<Direction>& route)
136
{
137
    MapPoint tmp(start);
386✔
138
    for(unsigned i = 0; i < route.size() - 1; ++i)
386✔
139
    {
140
        // Richtiger Weg auf diesem Punkt?
141
        if(GetPointRoad(tmp, route[i]) == PointRoad::None)
386✔
142
            return false;
386✔
143

144
        tmp = GetNeighbour(tmp, route[i]);
×
145
    }
146
    return true;
×
147
}
148

149
bool GameWorldBase::IsOnRoad(const MapPoint& pt) const
1,626,792✔
150
{
151
    // This must be fast for BQ calculation so don't use GetVisiblePointRoad
152
    for(const auto roadDir : helpers::EnumRange<RoadDir>{})
16,261,114✔
153
        if(GetRoad(pt, roadDir) != PointRoad::None)
4,878,305✔
154
            return true;
1,332✔
155
    for(const auto roadDir : helpers::EnumRange<RoadDir>{})
16,253,688✔
156
    {
157
        const Direction oppositeDir = getOppositeDir(roadDir);
4,876,091✔
158
        if(GetRoad(GetNeighbour(pt, oppositeDir), roadDir) != PointRoad::None)
4,876,091✔
159
            return true;
167✔
160
    }
161
    return false;
1,625,293✔
162
}
163

164
bool GameWorldBase::IsFlagAround(const MapPoint& pt) const
1,014✔
165
{
166
    for(const MapPoint nb : GetNeighbours(pt))
7,066✔
167
    {
168
        if(GetNO(nb)->GetBM() == BlockingManner::Flag)
6,059✔
169
            return true;
7✔
170
    }
171
    return false;
1,007✔
172
}
173

174
bool GameWorldBase::IsAnyNeighborOwned(const MapPoint& pt) const
7✔
175
{
176
    for(const MapPoint& nb : GetNeighbours(pt))
49✔
177
        if(GetNode(nb).owner)
42✔
178
            return true;
×
179
    return false;
7✔
180
}
181

182
void GameWorldBase::RecalcBQForRoad(const MapPoint pt)
1,048✔
183
{
184
    RecalcBQ(pt);
1,048✔
185

186
    for(const Direction dir : {Direction::East, Direction::SouthEast, Direction::SouthWest})
4,192✔
187
        RecalcBQ(GetNeighbour(pt, dir));
3,144✔
188
}
1,048✔
189

190
namespace {
191
bool IsMilBldOfOwner(const GameWorldBase& gwb, MapPoint pt, unsigned char owner)
1,189✔
192
{
193
    return gwb.IsMilitaryBuildingOnNode(pt, false) && (gwb.GetNode(pt).owner == owner);
1,189✔
194
}
195
} // namespace
196

197
bool GameWorldBase::IsMilitaryBuildingNearNode(const MapPoint nPt, const unsigned char player) const
20✔
198
{
199
    // Im Umkreis von 4 Punkten ein Militärgebäude suchen
200
    return CheckPointsInRadius(
40✔
201
      nPt, 4, [this, player](auto pt, auto) { return IsMilBldOfOwner(*this, pt, player + 1); }, false);
1,229✔
202
}
203

204
bool GameWorldBase::IsMilitaryBuildingOnNode(const MapPoint pt, bool attackBldsOnly) const
1,227✔
205
{
206
    const noBase* obj = GetNO(pt);
1,227✔
207
    if(obj->GetType() == NodalObjectType::Building || obj->GetType() == NodalObjectType::Buildingsite)
1,227✔
208
    {
209
        BuildingType buildingType = static_cast<const noBaseBuilding*>(obj)->GetBuildingType();
52✔
210
        if(BuildingProperties::IsMilitary(buildingType))
52✔
211
            return true;
×
212
        if(!attackBldsOnly
52✔
213
           && (buildingType == BuildingType::Headquarters || buildingType == BuildingType::HarborBuilding))
16✔
214
            return true;
1✔
215
    }
216

217
    return false;
1,226✔
218
}
219

220
sortedMilitaryBlds GameWorldBase::LookForMilitaryBuildings(const MapPoint pt, unsigned short radius) const
48,184✔
221
{
222
    return militarySquares.GetBuildingsInRange(pt, radius);
48,184✔
223
}
224

225
noFlag* GameWorldBase::GetRoadFlag(MapPoint pt, Direction& dir, const helpers::OptionalEnum<Direction> prevDir)
124,559✔
226
{
227
    // Getting a flag is const
228
    const noFlag* flag = const_cast<const GameWorldBase*>(this)->GetRoadFlag(pt, dir, prevDir);
124,559✔
229
    // However we self are not const, so we allow returning a non-const flag pointer
230
    return const_cast<noFlag*>(flag);
124,559✔
231
}
232

233
const noFlag* GameWorldBase::GetRoadFlag(MapPoint pt, Direction& dir, helpers::OptionalEnum<Direction> prevDir) const
124,597✔
234
{
235
    while(true)
236
    {
237
        // suchen, wo der Weg weitergeht
238
        helpers::OptionalEnum<Direction> nextDir;
124,597✔
239
        for(const auto i : helpers::EnumRange<Direction>{})
1,991,620✔
240
        {
241
            if(i != prevDir && GetPointRoad(pt, i) != PointRoad::None)
747,030✔
242
            {
243
                nextDir = i;
414✔
244
                break;
414✔
245
            }
246
        }
247

248
        if(!nextDir)
124,597✔
249
            return nullptr;
124,559✔
250

251
        pt = GetNeighbour(pt, *nextDir);
414✔
252

253
        // endlich am Ende des Weges und an einer Flagge angekommen?
254
        if(GetNO(pt)->GetType() == NodalObjectType::Flag)
414✔
255
        {
256
            dir = *nextDir + 3u;
376✔
257
            return GetSpecObj<noFlag>(pt);
376✔
258
        }
259
        prevDir = *nextDir + 3u;
38✔
260
    }
38✔
261
}
262

263
Position GameWorldBase::GetNodePos(const MapPoint pt) const
6✔
264
{
265
    return ::GetNodePos(pt, GetNode(pt).altitude);
6✔
266
}
267

268
void GameWorldBase::VisibilityChanged(const MapPoint pt, unsigned player, Visibility /*oldVis*/, Visibility /*newVis*/)
173,053✔
269
{
270
    GetNotifications().publish(PlayerNodeNote(PlayerNodeNote::Visibility, pt, player));
173,053✔
271
}
173,053✔
272

273
/// Verändert die Höhe eines Punktes und die damit verbundenen Schatten
274
void GameWorldBase::AltitudeChanged(const MapPoint pt)
397✔
275
{
276
    RecalcBQAroundPointBig(pt);
397✔
277
    GetNotifications().publish(NodeNote(NodeNote::Altitude, pt));
397✔
278
}
397✔
279

280
void GameWorldBase::RecalcBQAroundPoint(const MapPoint pt)
2,440✔
281
{
282
    RecalcBQ(pt);
2,440✔
283
    for(const MapPoint nb : GetNeighbours(pt))
17,080✔
284
        RecalcBQ(nb);
14,640✔
285
}
2,440✔
286

287
void GameWorldBase::RecalcBQAroundPointBig(const MapPoint pt)
2,360✔
288
{
289
    // Point and radius 1
290
    RecalcBQAroundPoint(pt);
2,360✔
291
    // And radius 2
292
    for(unsigned i = 0; i < 12; ++i)
30,680✔
293
        RecalcBQ(GetNeighbour2(pt, i));
28,320✔
294
}
2,360✔
295

296
Visibility GameWorldBase::CalcVisiblityWithAllies(const MapPoint pt, const unsigned char player) const
15,460✔
297
{
298
    const MapNode& node = GetNode(pt);
15,460✔
299
    Visibility best_visibility = node.fow[player].visibility;
15,460✔
300

301
    if(best_visibility == Visibility::Visible)
15,460✔
302
        return best_visibility;
12,251✔
303

304
    /// Teamsicht aktiviert?
305
    if(GetGGS().teamView)
3,209✔
306
    {
307
        const GamePlayer& curPlayer = GetPlayer(player);
3,209✔
308
        // Dann prüfen, ob Teammitglieder evtl. eine bessere Sicht auf diesen Punkt haben
309
        for(unsigned i = 0; i < GetNumPlayers(); ++i)
11,137✔
310
        {
311
            if(i != player && curPlayer.IsAlly(i))
7,928✔
312
            {
313
                if(node.fow[i].visibility > best_visibility)
2,841✔
314
                    best_visibility = node.fow[i].visibility;
100✔
315
            }
316
        }
317
    }
318

319
    return best_visibility;
3,209✔
320
}
321

322
bool GameWorldBase::IsCoastalPointToSeaWithHarbor(const MapPoint pt) const
217✔
323
{
324
    SeaId sea = GetSeaFromCoastalPoint(pt);
217✔
325
    if(sea)
217✔
326
    {
327
        for(const auto i : helpers::idRange<HarborId>(GetNumHarborPoints()))
62✔
328
        {
329
            if(IsHarborAtSea(HarborId(i), sea))
62✔
330
                return true;
62✔
331
        }
332
    }
333
    return false;
155✔
334
}
335

336
template<typename T_IsHarborOk>
337
HarborId GameWorldBase::GetHarborInDir(const MapPoint pt, const HarborId originHarborId, const ShipDirection& dir,
16✔
338
                                       T_IsHarborOk isHarborOk) const
339
{
340
    RTTR_Assert(originHarborId);
16✔
341

342
    const MapPoint hbPt = GetHarborPoint(originHarborId);
16✔
343

344
    // Find sea of harbor point
345
    SeaId seaId;
16✔
346
    for(const auto d : helpers::EnumRange<Direction>{})
100✔
347
    {
348
        if(GetNeighbour(hbPt, d) == pt)
52✔
349
        {
350
            seaId = GetSeaId(originHarborId, d);
16✔
351
            break;
16✔
352
        }
353
    }
354
    RTTR_Assert(seaId);
16✔
355

356
    const std::vector<HarborPos::Neighbor>& neighbors = GetHarborNeighbors(originHarborId, dir);
16✔
357

358
    for(const auto& neighbor : neighbors)
19✔
359
    {
360
        if(IsHarborAtSea(neighbor.id, seaId) && isHarborOk(neighbor.id))
13✔
361
            return neighbor.id;
10✔
362
    }
363

364
    // Nichts gefunden
365
    return HarborId::invalidValue();
6✔
366
}
367

368
/// Functor that returns true, when the owner of a point is set and different than the player
369
struct IsPointOwnerDifferent
370
{
371
    const GameWorldBase& gwb;
372
    // Owner to compare. Note that owner=0 --> No owner => owner=player+1
373
    const unsigned char cmpOwner;
374

375
    IsPointOwnerDifferent(const GameWorldBase& gwb, const unsigned char player) : gwb(gwb), cmpOwner(player + 1) {}
10✔
376

377
    bool operator()(const MapPoint pt, unsigned /*distance*/) const
541✔
378
    {
379
        const unsigned char owner = gwb.GetNode(pt).owner;
541✔
380
        return owner != 0 && owner != cmpOwner;
541✔
381
    }
382
};
383

384
/// Ist es an dieser Stelle für einen Spieler möglich einen Hafen zu bauen
385
bool GameWorldBase::IsHarborPointFree(const HarborId harborId, const unsigned char player) const
15✔
386
{
387
    MapPoint hbPos(GetHarborPoint(harborId));
15✔
388

389
    // Überprüfen, ob das Gebiet in einem bestimmten Radius entweder vom Spieler oder gar nicht besetzt ist außer wenn
390
    // der Hafen und die Flagge im Spielergebiet liegen
391
    MapPoint flagPos = GetNeighbour(hbPos, Direction::SouthEast);
15✔
392
    if(GetNode(hbPos).owner != player + 1 || GetNode(flagPos).owner != player + 1)
15✔
393
    {
394
        if(CheckPointsInRadius(hbPos, 4, IsPointOwnerDifferent(*this, player), false))
10✔
395
            return false;
1✔
396
    }
397

398
    return GetNode(hbPos).bq == BuildingQuality::Harbor;
14✔
399
}
400

401
/// Sucht freie Hafenpunkte, also wo noch ein Hafen gebaut werden kann
402
HarborId GameWorldBase::GetNextFreeHarborPoint(const MapPoint pt, const HarborId originHarborId,
16✔
403
                                               const ShipDirection& dir, const unsigned char player) const
404
{
405
    return GetHarborInDir(pt, originHarborId, dir,
406
                          [this, player](auto harborId) { return this->IsHarborPointFree(harborId, player); });
29✔
407
}
408

409
/// Bestimmt für einen beliebigen Punkt auf der Karte die Entfernung zum nächsten Hafenpunkt
410
unsigned GameWorldBase::CalcDistanceToNearestHarbor(const MapPoint pos) const
5✔
411
{
412
    unsigned min_distance = std::numeric_limits<unsigned>::max();
5✔
413
    for(const auto i : helpers::idRange<HarborId>(GetNumHarborPoints()))
45✔
414
        min_distance = std::min(min_distance, this->CalcDistance(pos, GetHarborPoint(i)));
40✔
415

416
    return min_distance;
5✔
417
}
418

419
/// returns true when a harborpoint is in SEAATTACK_DISTANCE for figures!
420
bool GameWorldBase::IsAHarborInSeaAttackDistance(const MapPoint pos) const
×
421
{
NEW
422
    for(const auto i : helpers::idRange<HarborId>(GetNumHarborPoints()))
×
423
    {
424
        if(CalcDistance(pos, GetHarborPoint(i)) < SEAATTACK_DISTANCE)
×
425
        {
426
            if(FindHumanPath(pos, GetHarborPoint(i), SEAATTACK_DISTANCE))
×
427
                return true;
×
428
        }
429
    }
430
    return false;
×
431
}
432

433
std::vector<HarborId> GameWorldBase::GetUsableTargetHarborsForAttack(const MapPoint targetPt,
33✔
434
                                                                     std::vector<bool>& use_seas,
435
                                                                     const unsigned char player_attacker) const
436
{
437
    // Walk to the flag of the bld/harbor. Important to check because in some locations where the coast is north of the
438
    // harbor this might be blocked
439
    const MapPoint flagPt = GetNeighbour(targetPt, Direction::SouthEast);
33✔
440
    std::vector<HarborId> harbor_points;
33✔
441
    // Check each possible harbor
442
    for(const auto curHbId : helpers::idRange<HarborId>(GetNumHarborPoints()))
297✔
443
    {
444
        const MapPoint harborPt = GetHarborPoint(curHbId);
264✔
445

446
        if(CalcDistance(harborPt, targetPt) > SEAATTACK_DISTANCE)
264✔
447
            continue;
202✔
448

449
        // Not attacking this harbor and harbors block?
450
        if(targetPt != harborPt && GetGGS().getSelection(AddonId::SEA_ATTACK) == 1)
70✔
451
        {
452
            // Does an enemy harbor exist at current harbor spot? -> Can't attack through this harbor spot
453
            const auto* hb = GetSpecObj<nobHarborBuilding>(harborPt);
32✔
454
            if(hb && GetPlayer(player_attacker).IsAttackable(hb->GetPlayer()))
32✔
455
                continue;
8✔
456
        }
457

458
        // add seaIds from which we can actually attack the harbor
459
        bool harborinlist = false;
62✔
460
        for(const auto dir : helpers::enumRange<Direction>())
992✔
461
        {
462
            const SeaId seaId = GetSeaId(curHbId, dir);
372✔
463
            if(!seaId)
372✔
464
                continue;
310✔
465
            // checks previously tested sea ids to skip pathfinding
466
            bool previouslytested = false;
62✔
467
            for(unsigned k = 0; k < rttr::enum_cast(dir); k++)
205✔
468
            {
469
                if(seaId == GetSeaId(curHbId, Direction(k)))
143✔
470
                {
471
                    previouslytested = true;
×
472
                    break;
×
473
                }
474
            }
475
            if(previouslytested)
62✔
476
                continue;
×
477

478
            // Can figures reach flag from coast
479
            const MapPoint coastalPt = GetCoastalPoint(curHbId, seaId);
62✔
480
            if((flagPt == coastalPt) || FindHumanPath(flagPt, coastalPt, SEAATTACK_DISTANCE))
62✔
481
            {
482
                use_seas.at(seaId.value() - 1) = true;
56✔
483
                if(!harborinlist)
56✔
484
                {
485
                    harbor_points.push_back(curHbId);
56✔
486
                    harborinlist = true;
56✔
487
                }
488
            }
489
        }
490
    }
491
    return harbor_points;
66✔
492
}
493

NEW
494
std::vector<SeaId> GameWorldBase::GetFilteredSeaIDsForAttack(const MapPoint targetPt,
×
495
                                                             const std::vector<SeaId>& usableSeas,
496
                                                             const unsigned char player_attacker) const
497
{
498
    // Walk to the flag of the bld/harbor. Important to check because in some locations where the coast is north of the
499
    // harbor this might be blocked
500
    const MapPoint flagPt = GetNeighbour(targetPt, Direction::SouthEast);
×
NEW
501
    std::vector<SeaId> confirmedSeaIds;
×
502
    // Check each possible harbor
NEW
503
    for(const auto curHbId : helpers::idRange<HarborId>(GetNumHarborPoints()))
×
504
    {
505
        const MapPoint harborPt = GetHarborPoint(curHbId);
×
506

507
        if(CalcDistance(harborPt, targetPt) > SEAATTACK_DISTANCE)
×
508
            continue;
×
509

510
        // Not attacking this harbor and harbors block?
511
        if(targetPt != harborPt && GetGGS().getSelection(AddonId::SEA_ATTACK) == 1)
×
512
        {
513
            // Does an enemy harbor exist at current harbor spot? -> Can't attack through this harbor spot
514
            const auto* hb = GetSpecObj<nobHarborBuilding>(harborPt);
×
515
            if(hb && GetPlayer(player_attacker).IsAttackable(hb->GetPlayer()))
×
516
                continue;
×
517
        }
518

519
        for(const auto dir : helpers::enumRange<Direction>())
×
520
        {
NEW
521
            const SeaId seaId = GetSeaId(curHbId, dir);
×
522
            if(!seaId)
×
523
                continue;
×
524
            // sea id is not in compare list or already confirmed? -> skip rest
525
            if(!helpers::contains(usableSeas, seaId) || helpers::contains(confirmedSeaIds, seaId))
×
526
                continue;
×
527

528
            // checks previously tested sea ids to skip pathfinding
529
            bool previouslytested = false;
×
530
            for(unsigned k = 0; k < rttr::enum_cast(dir); k++)
×
531
            {
532
                if(seaId == GetSeaId(curHbId, Direction(k)))
×
533
                {
534
                    previouslytested = true;
×
535
                    break;
×
536
                }
537
            }
538
            if(previouslytested)
×
539
                continue;
×
540

541
            // Can figures reach flag from coast
542
            MapPoint coastalPt = GetCoastalPoint(curHbId, seaId);
×
543
            if((flagPt == coastalPt) || FindHumanPath(flagPt, coastalPt, SEAATTACK_DISTANCE))
×
544
            {
545
                confirmedSeaIds.push_back(seaId);
×
546
                // all sea ids confirmed? return without changes
547
                if(confirmedSeaIds.size() == usableSeas.size())
×
548
                    return confirmedSeaIds;
×
549
            }
550
        }
551
    }
552
    return confirmedSeaIds;
×
553
}
554

555
/// Liefert Hafenpunkte im Umkreis von einem bestimmten Militärgebäude
556
std::vector<HarborId> GameWorldBase::GetHarborPointsAroundMilitaryBuilding(const MapPoint pt) const
10✔
557
{
558
    std::vector<HarborId> harbor_points;
10✔
559
    // Nach Hafenpunkten in der Nähe des angegriffenen Gebäudes suchen
560
    // Alle unsere Häfen durchgehen
561
    for(const auto i : helpers::idRange<HarborId>(GetNumHarborPoints()))
90✔
562
    {
563
        const MapPoint harborPt = GetHarborPoint(i);
80✔
564

565
        if(CalcDistance(harborPt, pt) <= SEAATTACK_DISTANCE)
80✔
566
        {
567
            // Wird ein Weg vom Militärgebäude zum Hafen gefunden bzw. Ziel = Hafen?
568
            if(pt == harborPt || FindHumanPath(pt, harborPt, SEAATTACK_DISTANCE))
20✔
569
                harbor_points.push_back(i);
20✔
570
        }
571
    }
572
    return harbor_points;
10✔
573
}
574

575
/// Gibt Anzahl oder geschätzte Stärke(rang summe + anzahl) der verfügbaren Soldaten die zu einem Schiffsangriff starten
576
/// können von einer bestimmten sea id aus
NEW
577
unsigned GameWorldBase::GetNumSoldiersForSeaAttackAtSea(const unsigned char player_attacker, SeaId sea,
×
578
                                                        bool returnCount) const
579
{
580
    // Liste alle Militärgebäude des Angreifers, die Soldaten liefern
581
    std::vector<nobHarborBuilding::SeaAttackerBuilding> buildings;
×
582
    unsigned attackercount = 0;
×
583
    // Angrenzende Häfen des Angreifers an den entsprechenden Meeren herausfinden
584
    const std::list<nobHarborBuilding*>& harbors = GetPlayer(player_attacker).GetBuildingRegister().GetHarbors();
×
585
    for(auto* harbor : harbors)
×
586
    {
587
        // Bestimmen, ob Hafen an einem der Meere liegt, über die sich auch die gegnerischen
588
        // Hafenpunkte erreichen lassen
NEW
589
        if(!IsHarborAtSea(harbor->GetHarborPosID(), sea))
×
590
            continue;
×
591

592
        std::vector<nobHarborBuilding::SeaAttackerBuilding> tmp = harbor->GetAttackerBuildingsForSeaIdAttack();
×
593
        buildings.insert(buildings.begin(), tmp.begin(), tmp.end());
×
594
    }
595

596
    // Die Soldaten aus allen Militärgebäuden sammeln
597
    for(auto& building : buildings)
×
598
    {
599
        // Soldaten holen
600
        std::vector<nofPassiveSoldier*> tmp_soldiers =
601
          building.building->GetSoldiersForAttack(building.harbor->GetPos());
×
602

603
        // Überhaupt welche gefunden?
604
        if(tmp_soldiers.empty())
×
605
            continue;
×
606

607
        // Soldaten hinzufügen
608
        for(auto& tmp_soldier : tmp_soldiers)
×
609
        {
610
            if(returnCount)
×
611
                attackercount++;
×
612
            else
613
                attackercount += (tmp_soldier->GetRank() + 1); // private is rank 0 -> increase by 1-5
×
614
        }
615
    }
616
    return attackercount;
×
617
}
618

619
/// Sucht verfügbare Soldaten, um dieses Militärgebäude mit einem Seeangriff anzugreifen
620
std::vector<GameWorldBase::PotentialSeaAttacker>
621
GameWorldBase::GetSoldiersForSeaAttack(const unsigned char player_attacker, const MapPoint pt) const
69✔
622
{
623
    std::vector<GameWorldBase::PotentialSeaAttacker> attackers;
69✔
624
    // sea attack abgeschaltet per addon?
625
    if(!GetGGS().isEnabled(AddonId::SEA_ATTACK))
69✔
626
        return attackers;
10✔
627
    // Do we have an attackble military building?
628
    const auto* milBld = GetSpecObj<nobBaseMilitary>(pt);
59✔
629
    if(!milBld || !milBld->IsAttackable(player_attacker))
59✔
630
        return attackers;
26✔
631
    std::vector<bool> use_seas(GetNumSeas());
66✔
632

633
    // Mögliche Hafenpunkte in der Nähe des Gebäudes
634
    std::vector<HarborId> defender_harbors = GetUsableTargetHarborsForAttack(pt, use_seas, player_attacker);
66✔
635

636
    // Liste alle Militärgebäude des Angreifers, die Soldaten liefern
637
    std::vector<nobHarborBuilding::SeaAttackerBuilding> buildings;
66✔
638

639
    // Angrenzende Häfen des Angreifers an den entsprechenden Meeren herausfinden
640
    const std::list<nobHarborBuilding*>& harbors = GetPlayer(player_attacker).GetBuildingRegister().GetHarbors();
33✔
641
    for(auto* harbor : harbors)
66✔
642
    {
643
        // Bestimmen, ob Hafen an einem der Meere liegt, über die sich auch die gegnerischen
644
        // Hafenpunkte erreichen lassen
645
        bool is_at_sea = false;
33✔
646
        for(const auto dir : helpers::EnumRange<Direction>{})
390✔
647
        {
648
            const SeaId seaId = GetSeaId(harbor->GetHarborPosID(), dir);
152✔
649
            if(seaId && use_seas[seaId.value() - 1])
152✔
650
            {
651
                is_at_sea = true;
23✔
652
                break;
23✔
653
            }
654
        }
655

656
        if(!is_at_sea)
33✔
657
            continue;
10✔
658

659
        std::vector<nobHarborBuilding::SeaAttackerBuilding> tmp =
660
          harbor->GetAttackerBuildingsForSeaAttack(defender_harbors);
46✔
661
        for(auto& itBld : tmp)
46✔
662
        {
663
            // Check if the building was already inserted
664
            auto oldBldIt =
665
              helpers::find_if(buildings, nobHarborBuilding::SeaAttackerBuilding::CmpBuilding(itBld.building));
23✔
666
            if(oldBldIt == buildings.end())
23✔
667
            {
668
                // Not found -> Add
669
                buildings.push_back(itBld);
23✔
670
            } else if(oldBldIt->distance > itBld.distance
×
671
                      || (oldBldIt->distance == itBld.distance
×
672
                          && oldBldIt->harbor->GetObjId() > itBld.harbor->GetObjId()))
×
673
            {
674
                // New distance is smaller (with tie breaker for async prevention) -> update
675
                *oldBldIt = itBld;
×
676
            }
677
        }
678
    }
679

680
    // Die Soldaten aus allen Militärgebäuden sammeln
681
    for(const auto& bld : buildings)
56✔
682
    {
683
        // Soldaten holen
684
        std::vector<nofPassiveSoldier*> tmp_soldiers = bld.building->GetSoldiersForAttack(bld.harbor->GetPos());
46✔
685

686
        // Soldaten hinzufügen
687
        for(nofPassiveSoldier* soldier : tmp_soldiers)
116✔
688
        {
689
            RTTR_Assert(!helpers::contains_if(attackers, PotentialSeaAttacker::CmpSoldier(soldier)));
93✔
690
            attackers.push_back(PotentialSeaAttacker(soldier, bld.harbor, bld.distance));
93✔
691
        }
692
    }
693

694
    return attackers;
33✔
695
}
696

697
void GameWorldBase::RecalcBQ(const MapPoint pt)
554,679✔
698
{
699
    BQCalculator calcBQ(*this);
554,679✔
700
    if(SetBQ(pt, calcBQ(pt, [this](auto pt) { return this->IsOnRoad(pt); })))
2,177,270✔
701
    {
702
        GetNotifications().publish(NodeNote(NodeNote::BQ, pt));
240,203✔
703
    }
704
}
554,679✔
705

706
void GameWorldBase::SetComputerBarrier(const MapPoint& pt, unsigned radius)
2✔
707
{
708
    for(const auto& pt : GetPointsInRadiusWithCenter(pt, radius))
544✔
709
        ptsInsideComputerBarriers.insert(pt);
542✔
710
}
2✔
711

712
bool GameWorldBase::IsInsideComputerBarrier(const MapPoint& pt) const
652✔
713
{
714
    return helpers::contains(ptsInsideComputerBarriers, pt);
652✔
715
}
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