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

Return-To-The-Roots / s25client / 24151579386

08 Apr 2026 06:24PM UTC coverage: 50.371% (+0.03%) from 50.337%
24151579386

push

github

Flow86
Use `helpers::sort` consistently

14 of 18 new or added lines in 12 files covered. (77.78%)

840 existing lines in 36 files now uncovered.

23069 of 45798 relevant lines covered (50.37%)

45802.94 hits per line

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

84.36
/libs/s25main/world/GameWorld.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/GameWorld.h"
6
#include "EventManager.h"
7
#include "GameInterface.h"
8
#include "GamePlayer.h"
9
#include "GlobalGameSettings.h"
10
#include "RttrForeachPt.h"
11
#include "TradePathCache.h"
12
#include "addons/const_addons.h"
13
#include "buildings/noBuildingSite.h"
14
#include "buildings/nobMilitary.h"
15
#include "buildings/nobUsual.h"
16
#include "figures/nofAttacker.h"
17
#include "figures/nofPassiveSoldier.h"
18
#include "figures/nofScout_Free.h"
19
#include "helpers/IdRange.h"
20
#include "helpers/containerUtils.h"
21
#include "helpers/mathFuncs.h"
22
#include "helpers/reverse.h"
23
#include "lua/LuaInterfaceGame.h"
24
#include "notifications/BuildingNote.h"
25
#include "notifications/ExpeditionNote.h"
26
#include "notifications/NodeNote.h"
27
#include "notifications/RoadNote.h"
28
#include "pathfinding/PathConditionHuman.h"
29
#include "pathfinding/PathConditionRoad.h"
30
#include "postSystem/PostMsgWithBuilding.h"
31
#include "world/MapGeometry.h"
32
#include "world/TerritoryRegion.h"
33
#include "nodeObjs/noFighting.h"
34
#include "nodeObjs/noFlag.h"
35
#include "nodeObjs/noShip.h"
36
#include "nodeObjs/noStaticObject.h"
37
#include "gameData/BuildingConsts.h"
38
#include "gameData/BuildingProperties.h"
39
#include "gameData/MilitaryConsts.h"
40
#include "gameData/TerrainDesc.h"
41
#include <algorithm>
42
#include <functional>
43
#include <set>
44
#include <stdexcept>
45

46
inline std::vector<GamePlayer> CreatePlayers(const std::vector<PlayerInfo>& playerInfos, GameWorld& world)
199✔
47
{
48
    std::vector<GamePlayer> players;
199✔
49
    players.reserve(playerInfos.size());
199✔
50
    for(unsigned i = 0; i < playerInfos.size(); ++i)
616✔
51
        players.push_back(GamePlayer(i, playerInfos[i], world));
417✔
52
    return players;
199✔
53
}
54

55
GameWorld::GameWorld(const std::vector<PlayerInfo>& players, const GlobalGameSettings& gameSettings, EventManager& em)
199✔
56
    : GameWorldBase(CreatePlayers(players, *this), gameSettings, em)
199✔
57
{
58
    GameObject::AttachWorld(this);
199✔
59
}
199✔
60

61
GameWorld::~GameWorld()
199✔
62
{
63
    GameObject::DetachWorld(this);
199✔
64
}
199✔
65

66
MilitarySquares& GameWorld::GetMilitarySquares()
606✔
67
{
68
    return militarySquares;
606✔
69
}
70

71
void GameWorld::SetFlag(const MapPoint pt, const unsigned char player)
776✔
72
{
73
    if(GetBQ(pt, player) == BuildingQuality::Nothing)
776✔
74
        return;
34✔
75
    // There must be no other flag around that point
76
    if(IsFlagAround(pt))
742✔
77
        return;
4✔
78

79
    // Gucken, nicht, dass schon eine Flagge dasteht
80
    if(GetNO(pt)->GetType() != NodalObjectType::Flag)
738✔
81
    {
82
        DestroyNO(pt, false);
738✔
83
        SetNO(pt, new noFlag(pt, player));
738✔
84

85
        RecalcBQAroundPointBig(pt);
738✔
86
    }
87
}
88

89
void GameWorld::DestroyFlag(const MapPoint pt, unsigned char playerId)
709✔
90
{
91
    // Let's see if there is a flag
92
    if(GetNO(pt)->GetType() == NodalObjectType::Flag)
709✔
93
    {
94
        auto* flag = GetSpecObj<noFlag>(pt);
709✔
95
        if(flag->GetPlayer() != playerId)
709✔
96
            return;
×
97

98
        // Get the attached building if existing
99
        noBase* building = GetNO(GetNeighbour(pt, Direction::NorthWest));
709✔
100

101
        // Is this a military building?
102
        if(building->GetGOT() == GO_Type::NobMilitary)
709✔
103
        {
104
            // Maybe demolition of the building is not allowed?
105
            if(!static_cast<nobMilitary*>(building)->IsDemolitionAllowed())
×
106
                return; // Abort the whole thing
×
107
        }
108

109
        // Demolish, also the building
110
        flag->DestroyAttachedBuilding();
709✔
111

112
        DestroyNO(pt, false);
709✔
113
        RecalcBQAroundPointBig(pt);
709✔
114
    }
115

116
    if(gi)
709✔
117
        gi->GI_FlagDestroyed(pt);
×
118
}
119

120
void GameWorld::SetPointRoad(MapPoint pt, const Direction dir, const PointRoad type)
1,718✔
121
{
122
    const RoadDir rDir = toRoadDir(pt, dir);
1,718✔
123
    SetRoad(pt, rDir, type);
1,718✔
124

125
    if(gi)
1,718✔
126
        gi->GI_UpdateMinimap(pt);
×
127
}
1,718✔
128

129
void GameWorld::SetBuildingSite(const BuildingType type, const MapPoint pt, const unsigned char player)
30✔
130
{
131
    if(!GetPlayer(player).IsBuildingEnabled(type))
30✔
132
        return;
×
133

134
    // Gucken, ob das Gebäude hier überhaupt noch gebaut wrden kann
135
    if(!canUseBq(GetBQ(pt, player), BUILDING_SIZE[type]))
30✔
136
        return;
3✔
137

138
    // Wenn das ein Militärgebäude ist und andere Militärgebäude bereits in der Nähe sind, darf dieses nicht gebaut
139
    // werden
140
    if(BuildingProperties::IsMilitary(type))
27✔
141
    {
142
        if(IsMilitaryBuildingNearNode(pt, player))
7✔
143
            return;
1✔
144
    }
145

146
    // Prüfen ob Katapult und ob Katapult erlaubt ist
147
    if(type == BuildingType::Catapult && !GetPlayer(player).CanBuildCatapult())
26✔
148
        return;
×
149

150
    DestroyNO(pt, false);
26✔
151

152
    // Baustelle setzen
153
    SetNO(pt, new noBuildingSite(type, pt, player));
26✔
154
    if(gi)
26✔
155
        gi->GI_UpdateMinimap(pt);
×
156

157
    // Bauplätze drumrum neu berechnen
158
    RecalcBQAroundPointBig(pt);
26✔
159
}
160

161
void GameWorld::DestroyBuilding(const MapPoint pt, const unsigned char player)
6✔
162
{
163
    // Steht da auch ein Gebäude oder eine Baustelle, nicht dass wir aus Verzögerung Feuer abreißen wollen, das geht
164
    // schief
165
    if(GetNO(pt)->GetType() == NodalObjectType::Building || GetNO(pt)->GetType() == NodalObjectType::Buildingsite)
6✔
166
    {
167
        auto* nbb = GetSpecObj<noBaseBuilding>(pt);
5✔
168

169
        // Ist das Gebäude auch von dem Spieler, der es abreißen will?
170
        if(nbb->GetPlayer() != player)
5✔
171
            return;
×
172

173
        // Militärgebäude?
174
        if(nbb->GetGOT() == GO_Type::NobMilitary)
5✔
175
        {
176
            // Darf das Gebäude abgerissen werden?
177
            if(!static_cast<nobMilitary*>(nbb)->IsDemolitionAllowed())
×
178
                return;
×
179
        }
180

181
        DestroyNO(pt);
5✔
182
        // Bauplätze drumrum neu berechnen
183
        RecalcBQAroundPointBig(pt);
5✔
184
    }
185
}
186

187
void GameWorld::BuildRoad(const unsigned char playerId, const bool boat_road, const MapPoint start,
547✔
188
                          const std::vector<Direction>& route)
189
{
190
    // No routes with less than 2 parts. Actually invalid!
191
    if(route.size() < 2)
547✔
192
    {
193
        RTTR_Assert(false);
×
194
        return;
390✔
195
    }
196

197
    if(!GetSpecObj<noFlag>(start) || GetSpecObj<noFlag>(start)->GetPlayer() != playerId)
547✔
198
    {
199
        GetNotifications().publish(RoadNote(RoadNote::ConstructionFailed, playerId, start, route));
2✔
200
        return;
2✔
201
    }
202

203
    // See if the road can still be built at all
204
    PathConditionRoad<GameWorldBase> roadChecker(*this, boat_road);
545✔
205
    MapPoint curPt(start);
545✔
206
    for(unsigned i = 0; i + 1 < route.size(); ++i)
1,073✔
207
    {
208
        bool roadOk = roadChecker.IsEdgeOk(curPt, route[i]);
914✔
209
        curPt = GetNeighbour(curPt, route[i]);
914✔
210
        roadOk &= roadChecker.IsNodeOk(curPt);
914✔
211
        if(!roadOk)
914✔
212
        {
213
            // No? Then check whether the desired road is already there
214
            if(!RoadAlreadyBuilt(boat_road, start, route))
386✔
215
                GetNotifications().publish(RoadNote(RoadNote::ConstructionFailed, playerId, start, route));
386✔
216
            return;
386✔
217
        }
218
    }
219

220
    curPt = GetNeighbour(curPt, route.back());
159✔
221

222
    // Check whether there is a flag at the end or whether one can be built
223
    if(GetNO(curPt)->GetGOT() == GO_Type::Flag)
159✔
224
    {
225
        // Wrong player?
226
        if(GetSpecObj<noFlag>(curPt)->GetPlayer() != playerId)
85✔
227
        {
228
            GetNotifications().publish(RoadNote(RoadNote::ConstructionFailed, playerId, start, route));
×
229
            return;
×
230
        }
231
    } else
232
    {
233
        // Check if we can build a flag there
234
        if(GetBQ(curPt, playerId) == BuildingQuality::Nothing || IsFlagAround(curPt))
74✔
235
        {
236
            GetNotifications().publish(RoadNote(RoadNote::ConstructionFailed, playerId, start, route));
2✔
237
            return;
2✔
238
        }
239
        // no flag so far, but possible to place one -> Do it
240
        SetFlag(curPt, playerId);
72✔
241
    }
242

243
    // Destroy possible decorative objects at start
244
    if(HasRemovableObjForRoad(start))
157✔
245
        DestroyNO(start);
×
246

247
    MapPoint end(start);
157✔
248
    for(auto i : route)
738✔
249
    {
250
        SetPointRoad(end, i, boat_road ? PointRoad::Boat : PointRoad::Normal);
581✔
251
        RecalcBQForRoad(end);
581✔
252
        end = GetNeighbour(end, i);
581✔
253

254
        // Destroy possible decorative objects at end
255
        if(HasRemovableObjForRoad(end))
581✔
256
            DestroyNO(end);
91✔
257
    }
258

259
    auto* rs = new RoadSegment(boat_road ? RoadType::Water : RoadType::Normal, GetSpecObj<noFlag>(start),
157✔
260
                               GetSpecObj<noFlag>(end), route);
157✔
261

262
    GetSpecObj<noFlag>(start)->SetRoute(route.front(), rs);
157✔
263
    GetSpecObj<noFlag>(end)->SetRoute(route.back() + 3u, rs);
157✔
264

265
    // Tell the economy that a new road has been built
266
    GetPlayer(playerId).NewRoadConnection(rs);
157✔
267
    GetNotifications().publish(RoadNote(RoadNote::Constructed, playerId, start, route));
157✔
268

269
    // Add flags on land roads for human players if addon is enabled
270
    if(GetGGS().isEnabled(AddonId::AUTOFLAGS) && rs->GetRoadType() != RoadType::Water && GetPlayer(playerId).isHuman())
157✔
271
    {
272
        MapPoint roadPt = GetSpecObj<noFlag>(start)->GetPos();
×
273
        for(const Direction curDir : route)
×
274
        {
275
            roadPt = GetNeighbour(roadPt, curDir);
×
276
            if(!IsFlagAround(roadPt))
×
277
                SetFlag(roadPt, playerId);
×
278
        }
279
    }
280
}
281

282
bool GameWorld::HasRemovableObjForRoad(const MapPoint pt) const
738✔
283
{
284
    const auto* obj = GetSpecObj<noStaticObject>(pt);
738✔
285
    return obj && obj->GetSize() == 0;
738✔
286
}
287

288
// When defined the game tries to remove "blocks" of border stones that look ugly (TODO: Example?)
289
// DISABLED: This currently leads to bugs. If you enable/fix this, please add tests and document the conditions this
290
// tries to fix
291
// #define PREVENT_BORDER_STONE_BLOCKING
292

293
void GameWorld::RecalcBorderStones(Position startPt, Extent areaSize)
587✔
294
{
295
    // Add a bit extra space as this influences also border stones around the region
296
    // But not so much we wrap completely around the map (+1 to round up, /2 to have extra space centered)
297
    Position EXTRA_SPACE = elMin(Position::all(3), (GetSize() - areaSize + Position::all(1)) / 2);
587✔
298
    startPt -= EXTRA_SPACE;
587✔
299
    areaSize += 2u * Extent(EXTRA_SPACE);
587✔
300
    // We might still be 1 node to big, make sure we have don't exceed the mapsize
301
    areaSize = elMin(areaSize, Extent(GetSize()));
587✔
302

303
#ifdef PREVENT_BORDER_STONE_BLOCKING
304
    // Store how many neighbors a border stone has
305
    std::vector<uint8_t> neighbors(areaSize.x * areaSize.y, 0);
306
#endif
307

308
    RTTR_FOREACH_PT(Position, areaSize)
350,117✔
309
    {
310
        // Make map point
311
        const MapPoint curMapPt = MakeMapPoint(pt + startPt);
336,926✔
312
        const unsigned char owner = GetNode(curMapPt).owner;
336,926✔
313
        BoundaryStones& boundaryStones = GetBoundaryStones(curMapPt);
336,926✔
314

315
        // Is this a border node?
316
        if(owner && IsBorderNode(curMapPt, owner))
336,926✔
317
        {
318
            // Check which neighbors are also border nodes and place the half-way stones to them
319
            for(const auto bPos : helpers::EnumRange<BorderStonePos>{})
377,508✔
320
            {
321
                if(bPos == BorderStonePos::OnPoint || IsBorderNode(GetNeighbour(curMapPt, toDirection(bPos)), owner))
125,836✔
322
                    boundaryStones[bPos] = owner;
62,887✔
323
                else
324
                    boundaryStones[bPos] = 0;
62,949✔
325
            }
326

327
#ifdef PREVENT_BORDER_STONE_BLOCKING
328
            // Count number of border nodes with same owner
329
            int idx = pt.y * width + pt.x;
330
            for(const MapPoint nb : GetNeighbours(curMapPt))
331
            {
332
                if(GetNode(nb).boundary_stones[0] == owner)
333
                    ++neighbors[idx];
334
            }
335
#endif
336
        } else
337
        {
338
            // Not a border node -> Delete all border stones
339
            std::fill(boundaryStones.begin(), boundaryStones.end(), 0);
305,467✔
340
        }
341
    }
342

343
#ifdef PREVENT_BORDER_STONE_BLOCKING
344
    // Do a second pass and delete some stones with 3 or more neighbors to avoid blocks of stones
345
    RTTR_FOREACH_PT(Position, areaSize)
346
    {
347
        const MapPoint curMapPt = MakeMapPoint(pt + startPt);
348

349
        // Do we have a stone here?
350
        const unsigned char owner = GetNode(curMapPt).boundary_stones[BorderStonePos::OnPoint];
351
        if(!owner)
352
            continue;
353

354
        int idx = pt.y * width + pt.x;
355
        if(neighbors[idx] < 3)
356
            continue;
357

358
        for(unsigned dir = 0; dir < 3 && neighbors[idx] > 2; ++dir)
359
        {
360
            // Do we have a border stone of the same owner on the node in that direction?
361
            BoundaryStones& nbBoundStones = GetBoundaryStones(GetNeighbour(curMapPt, dir + 3));
362

363
            if(nbBoundStones[0] != owner)
364
                continue;
365

366
            Position pa = ::GetNeighbour(pt, Direction(dir + 3));
367
            if(pa.x < 0 || pa.x >= areaSize.x || pa.y < 0 || pa.y >= areaSize.y)
368
                continue;
369
            // If that one has to many stones too, we delete the connection stone
370
            int idxNb = pa.y * width + pa.x;
371
            if(neighbors[idxNb] > 2)
372
            {
373
                nbBoundStones[dir + 1] = 0;
374
                --neighbors[idx];
375
                --neighbors[idxNb];
376
            }
377
        }
378
    }
379
#endif
380
}
587✔
381

382
void GameWorld::RecalcTerritory(const noBaseBuilding& building, TerritoryChangeReason reason)
574✔
383
{
384
    // Additional radius to eliminate border stones or odd remaining territory parts
385
    static const int ADD_RADIUS = 2;
386
    // Get the military radius this building affects. Bld is either a military building or a harbor building site
387
    RTTR_Assert(
574✔
388
      (building.GetBuildingType() == BuildingType::HarborBuilding && dynamic_cast<const noBuildingSite*>(&building))
389
      || dynamic_cast<const nobBaseMilitary*>(&building));
390
    const unsigned militaryRadius = building.GetMilitaryRadius();
574✔
391
    RTTR_Assert(militaryRadius > 0u);
574✔
392

393
    const TerritoryRegion region = CreateTerritoryRegion(building, militaryRadius + ADD_RADIUS, reason);
1,148✔
394

395
    std::vector<MapPoint> ptsWithChangedOwners;
1,148✔
396
    std::vector<int> sizeChanges(GetNumPlayers());
1,148✔
397

398
    // Copy owners from territory region to map and do the bookkeeping
399
    RTTR_FOREACH_PT(Position, region.size)
247,589✔
400
    {
401
        const MapPoint curMapPt = MakeMapPoint(pt + region.startPt);
236,307✔
402
        const uint8_t oldOwner = GetNode(curMapPt).owner;
236,307✔
403
        const uint8_t newOwner = region.GetOwner(pt);
236,307✔
404

405
        // If nothing changed, there is nothing to do (ownerChanged was already initialized)
406
        if(oldOwner == newOwner)
236,307✔
407
            continue;
131,588✔
408

409
        SetOwner(curMapPt, newOwner);
104,719✔
410
        ptsWithChangedOwners.push_back(curMapPt);
104,719✔
411
        if(newOwner != 0)
104,719✔
412
            sizeChanges[newOwner - 1]++;
96,105✔
413
        if(oldOwner != 0)
104,719✔
414
            sizeChanges[oldOwner - 1]--;
13,030✔
415
    }
416

417
    const std::vector<MapPoint> ptsToHandle = GetAllNeighboursUnion(ptsWithChangedOwners);
1,148✔
418

419
    // Destroy everything from old player on all nodes where the owner has changed
420
    for(const MapPoint& curMapPt : ptsToHandle)
130,092✔
421
    {
422
        // Do not destroy the triggering building or its flag
423
        DestroyPlayerRests(curMapPt, GetNode(curMapPt).owner, &building);
129,518✔
424

425
        if(gi)
129,518✔
426
            gi->GI_UpdateMinimap(curMapPt);
×
427
    }
428

429
    // Destroy remaining roads going through non-owned and border territory
430
    for(const MapPoint& curMapPt : ptsToHandle)
130,092✔
431
    {
432
        // Skip if there is an object. We are looking only for roads going through, not ending here
433
        // (objects here are already destroyed and if the road ended there it would have been as well)
434
        if(GetNode(curMapPt).obj)
129,518✔
435
            continue;
129,517✔
436
        Direction dir;
437
        noFlag* flag = GetRoadFlag(curMapPt, dir);
126,440✔
438

439
        if(!flag)
126,440✔
440
            continue;
126,067✔
441

442
        const uint8_t owner = GetNode(curMapPt).owner;
373✔
443
        if(flag->GetPlayer() + 1 == owner && IsPlayerTerritory(curMapPt, owner))
373✔
444
            continue;
372✔
445

446
        flag->DestroyRoad(dir);
1✔
447
    }
448

449
    // Notify
450
    for(const MapPoint& curMapPt : ptsWithChangedOwners)
105,293✔
451
        GetNotifications().publish(NodeNote(NodeNote::Owner, curMapPt));
104,719✔
452

453
    for(const MapPoint& pt : ptsToHandle)
130,092✔
454
    {
455
        // BQ neu berechnen
456
        RecalcBQ(pt);
129,518✔
457
        // ggf den noch darüber, falls es eine Flagge war (kann ja ein Gebäude entstehen)
458
        const MapPoint neighbourPt = GetNeighbour(pt, Direction::NorthWest);
129,518✔
459
        if(GetNode(neighbourPt).bq != BuildingQuality::Nothing)
129,518✔
460
            RecalcBQ(neighbourPt);
102,514✔
461
    }
462

463
    RecalcBorderStones(region.startPt, region.size);
574✔
464

465
    // Recalc visibilities if building was destroyed
466
    // Otherwise just set everything to visible
467
    const unsigned visualRadius = militaryRadius + VISUALRANGE_MILITARY;
574✔
468
    if(reason == TerritoryChangeReason::Destroyed)
574✔
469
        RecalcVisibilitiesAroundPoint(building.GetPos(), visualRadius, building.GetPlayer(), &building);
65✔
470
    else
471
        MakeVisibleAroundPoint(building.GetPos(), visualRadius, building.GetPlayer());
509✔
472

473
    // Notify players
474
    for(unsigned i = 0; i < GetNumPlayers(); ++i)
1,941✔
475
    {
476
        GetPlayer(i).ChangeStatisticValue(StatisticType::Country, sizeChanges[i]);
1,367✔
477

478
        // Negatives Wachstum per Post dem/der jeweiligen Landesherren/dame melden, nur bei neugebauten Gebäuden
479
        if(reason == TerritoryChangeReason::Build && sizeChanges[i] < 0)
1,367✔
480
        {
481
            GetPostMgr().SendMsg(i, std::make_unique<PostMsgWithBuilding>(GetEvMgr().GetCurrentGF(),
294✔
482
                                                                          _("Lost land by this building"),
147✔
483
                                                                          PostCategory::Military, building));
147✔
484
            GetNotifications().publish(
294✔
485
              BuildingNote(BuildingNote::LostLand, i, building.GetPos(), building.GetBuildingType()));
294✔
486
        }
487
    }
488

489
    // Notify script
490
    if(HasLua())
574✔
491
    {
492
        for(const MapPoint& pt : ptsWithChangedOwners)
7,616✔
493
        {
494
            const uint8_t newOwner = GetNode(pt).owner;
7,588✔
495
            // Event for map scripting
496
            if(newOwner != 0)
7,588✔
497
                GetLua().EventOccupied(newOwner - 1, pt);
7,046✔
498
        }
499
    }
500
}
574✔
501

502
bool GameWorld::DoesDestructionChangeTerritory(const noBaseBuilding& building) const
×
503
{
504
    // Get the military radius this building affects. Bld is either a military building or a harbor building site
505
    RTTR_Assert(
×
506
      (building.GetBuildingType() == BuildingType::HarborBuilding && dynamic_cast<const noBuildingSite*>(&building))
507
      || dynamic_cast<const nobBaseMilitary*>(&building));
508
    const unsigned militaryRadius = building.GetMilitaryRadius();
×
509
    RTTR_Assert(militaryRadius > 0u);
×
510

511
    const TerritoryRegion region = CreateTerritoryRegion(building, militaryRadius, TerritoryChangeReason::Destroyed);
×
512

513
    // Look for a node that changed its owner and is important. If any -> return true
514
    RTTR_FOREACH_PT(Position, region.size)
×
515
    {
516
        MapPoint curMapPt = MakeMapPoint(pt + region.startPt);
×
517
        const MapNode& node = GetNode(curMapPt);
×
518
        if(node.owner == region.GetOwner(pt))
×
519
            continue;
×
520
        // AI can ignore water/snow/lava/swamp terrain (because it wouldn't help win the game)
521
        // So we check if any terrain is usable and if it is -> Land is important
522
        if(GetDescription().get(node.t1).Is(ETerrain::Walkable) && GetDescription().get(node.t2).Is(ETerrain::Walkable))
×
523
            return true;
×
524
        // also check neighboring nodes since border will still count as player territory but not allow any buildings!
525
        for(const MapPoint neighbourPt : GetNeighbours(curMapPt))
×
526
        {
527
            const MapNode& nNode = GetNode(neighbourPt);
×
528
            if(GetDescription().get(nNode.t1).Is(ETerrain::Walkable)
×
529
               || GetDescription().get(nNode.t2).Is(ETerrain::Walkable))
×
530
                return true;
×
531
        }
532
    }
533
    return false;
×
534
}
535

536
TerritoryRegion GameWorld::CreateTerritoryRegion(const noBaseBuilding& building, unsigned radius,
574✔
537
                                                 TerritoryChangeReason reason) const
538
{
539
    const MapPoint bldPos = building.GetPos();
574✔
540

541
    // Span at most half the map size (assert even sizes, given due to layout)
542
    RTTR_Assert(GetWidth() % 2 == 0);
574✔
543
    RTTR_Assert(GetHeight() % 2 == 0);
574✔
544
    Extent halfSize(GetSize() / 2u);
574✔
545
    Extent radius2D = elMin(Extent::all(radius), halfSize);
574✔
546

547
    // Koordinaten erzeugen für TerritoryRegion
548
    const Position startPt = Position(bldPos) - radius2D;
574✔
549
    // If we want to check the same number of points right of bld as left we need a +1.
550
    // But we can't check more than the whole map.
551
    const Extent size = elMin(2u * radius2D + Extent(1, 1), Extent(GetSize()));
574✔
552
    TerritoryRegion region(startPt, size, *this);
574✔
553

554
    // Alle Gebäude ihr Terrain in der Nähe neu berechnen
555
    sortedMilitaryBlds buildings = LookForMilitaryBuildings(bldPos, 3);
1,148✔
556
    for(const nobBaseMilitary* milBld : buildings)
4,586✔
557
    {
558
        if(reason != TerritoryChangeReason::Destroyed || milBld != &building)
1,432✔
559
            region.CalcTerritoryOfBuilding(*milBld);
1,432✔
560
    }
561

562
    // Baustellen von Häfen mit einschließen
563
    for(const noBuildingSite* bldSite : harbor_building_sites_from_sea)
575✔
564
    {
565
        if(reason != TerritoryChangeReason::Destroyed || bldSite != &building)
1✔
566
            region.CalcTerritoryOfBuilding(*bldSite);
1✔
567
    }
568
    CleanTerritoryRegion(region, reason, building);
574✔
569

570
    return region;
1,148✔
571
}
572

573
void GameWorld::CleanTerritoryRegion(TerritoryRegion& region, TerritoryChangeReason reason,
574✔
574
                                     const noBaseBuilding& triggerBld) const
575
{
576
    if(GetGGS().isEnabled(AddonId::NO_ALLIED_PUSH))
574✔
577
    {
578
        const bool isHq = triggerBld.GetBuildingType() == BuildingType::Headquarters;
2✔
579
        const auto newOwnerOfTriggerBld = region.GetOwner(region.GetPosFromMapPos(triggerBld.GetPos()));
2✔
580
        // An HQ can be placed independently of the current owner.
581
        // So ensure the HQ position is always considered to belong to the HQ owner
582
        // if the TerritoryChangeReason is Build
583
        const auto ownerOfTriggerBld =
584
          isHq && reason == TerritoryChangeReason::Build ? newOwnerOfTriggerBld : GetNode(triggerBld.GetPos()).owner;
2✔
585

586
        RTTR_FOREACH_PT(Position, region.size)
482✔
587
        {
588
            const MapPoint curMapPt = MakeMapPoint(pt + region.startPt);
460✔
589
            const auto oldOwner = GetNode(curMapPt).owner;
460✔
590
            const auto newOwner = region.GetOwner(pt);
460✔
591

592
            // If nothing changed, there is nothing to do (ownerChanged was already initialized)
593
            if(oldOwner == newOwner)
460✔
594
                continue;
187✔
595

596
            const bool ownersAllied = oldOwner > 0 && newOwner > 0 && GetPlayer(oldOwner - 1).IsAlly(newOwner - 1);
273✔
597
            if(
273✔
598
              // rule 1: only take territory from an ally if that ally loses a building
599
              // special case: headquarter can take territory
600
              (ownersAllied && (ownerOfTriggerBld != oldOwner || reason == TerritoryChangeReason::Build) && !isHq) ||
273✔
601
              // rule 2: do not gain territory when you lose a building (captured or destroyed)
602
              (ownerOfTriggerBld == newOwner && reason != TerritoryChangeReason::Build) ||
273✔
603
              // rule 3: do not lose territory when you gain a building (newBuilt or capture)
604
              ((ownerOfTriggerBld == oldOwner && oldOwner > 0 && reason == TerritoryChangeReason::Build)
153✔
605
               || (newOwnerOfTriggerBld == oldOwner && reason == TerritoryChangeReason::Captured)))
273✔
606
            {
607
                region.SetOwner(pt, oldOwner);
×
608
            }
609
        }
610
    }
611

612
    // "Cosmetics": Remove points that do not border to territory to avoid edges of border stones
613
    RTTR_FOREACH_PT(Position, region.size)
247,589✔
614
    {
615
        uint8_t owner = region.GetOwner(pt);
236,307✔
616
        if(!owner)
236,307✔
617
            continue;
88,428✔
618
        // Check if any neighbour is fully surrounded by player territory
619
        bool isPlayerTerritoryNear = false;
147,879✔
620
        for(const auto d : helpers::EnumRange<Direction>{})
713,168✔
621
        {
622
            Position neighbour = ::GetNeighbour(pt + region.startPt, d);
208,636✔
623
            if(region.SafeGetOwner(neighbour - region.startPt) != owner)
208,636✔
624
                continue;
24,341✔
625
            // Don't check this point as it is always true
626
            const Direction exceptDir = d + 3u;
184,295✔
627
            if(region.WillBePlayerTerritory(neighbour, owner, exceptDir))
184,295✔
628
            {
629
                isPlayerTerritoryNear = true;
147,810✔
630
                break;
147,810✔
631
            }
632
        }
633

634
        // All good?
635
        if(isPlayerTerritoryNear)
147,879✔
636
            continue;
147,810✔
637
        // No neighbouring player territory found. Look for another
638
        uint8_t newOwner = 0;
69✔
639
        for(const auto d : helpers::EnumRange<Direction>{})
502✔
640
        {
641
            Position neighbour = ::GetNeighbour(pt + region.startPt, d);
176✔
642
            uint8_t nbOwner = region.SafeGetOwner(neighbour - region.startPt);
176✔
643
            // No or same player?
644
            if(!nbOwner || nbOwner == owner)
176✔
645
                continue;
59✔
646
            // Don't check this point as it would always fail
647
            const Direction exceptDir = d + 3u;
117✔
648
            // First one found gets it
649
            if(region.WillBePlayerTerritory(neighbour, nbOwner, exceptDir))
117✔
650
            {
651
                newOwner = nbOwner;
63✔
652
                break;
63✔
653
            }
654
        }
655
        region.SetOwner(pt, newOwner);
69✔
656
    }
657
}
574✔
658

659
void GameWorld::CreateTradeGraphs()
16✔
660
{
661
    // Only if trade is enabled
662
    if(GetGGS().isEnabled(AddonId::TRADE))
16✔
663
        tradePathCache = std::make_unique<TradePathCache>(*this);
10✔
664
}
16✔
665

666
void GameWorld::DestroyPlayerRests(const MapPoint pt, unsigned char newOwner, const noBaseBuilding* exception)
129,518✔
667
{
668
    noBase* no = GetNode(pt).obj;
129,518✔
669
    if(!no || no == exception)
129,518✔
670
        return;
126,405✔
671

672
    // Destroy only flags, buildings and building sites
673
    const NodalObjectType noType = no->GetType();
3,113✔
674
    if(noType != NodalObjectType::Flag && noType != NodalObjectType::Building
3,113✔
675
       && noType != NodalObjectType::Buildingsite)
2,656✔
676
        return;
2,655✔
677

678
    // is the building on a node with a different owner? Border is allways destroyed.
679
    if(static_cast<noRoadNode*>(no)->GetPlayer() + 1 == newOwner && !IsBorderNode(pt, newOwner))
458✔
680
        return;
406✔
681

682
    // Do not destroy military buildings that hold territory on their own
683
    // Normally they will be on players territory but it can happen that they don't
684
    // Examples: Improved alliances or expedition building sites
685
    const noBase* noCheckMil = (noType == NodalObjectType::Flag) ? GetNO(GetNeighbour(pt, Direction::NorthWest)) : no;
52✔
686
    const GO_Type goType = noCheckMil->GetGOT();
52✔
687
    if(goType == GO_Type::NobHq || goType == GO_Type::NobHarborbuilding
52✔
688
       || (goType == GO_Type::NobMilitary && !static_cast<const nobMilitary*>(noCheckMil)->IsNewBuilt())
52✔
689
       || (noCheckMil->GetType() == NodalObjectType::Buildingsite
105✔
690
           && static_cast<const noBuildingSite*>(noCheckMil)->IsHarborBuildingSiteFromSea()))
1✔
691
    {
692
        // LOG.write(("DestroyPlayerRests of hq, military, harbor or colony-harbor in construction stopped at x, %i y,
693
        // %i type, %i \n", x, y, noType);
694
        return;
×
695
    }
696

697
    // If it is a flag, destroy the building
698
    if(noType == NodalObjectType::Flag && (!exception || no != exception->GetFlag()))
52✔
699
        static_cast<noFlag*>(no)->DestroyAttachedBuilding();
14✔
700

701
    DestroyNO(pt, false);
52✔
702
}
703

704
void GameWorld::RoadNodeAvailable(const MapPoint pt)
694✔
705
{
706
    // Figuren direkt daneben
707
    for(const MapPoint nb : GetNeighbours(pt))
4,858✔
708
    {
709
        // Nochmal prüfen, ob er nun wirklich verfügbar ist (evtl blocken noch mehr usw.)
710
        if(!IsRoadNodeForFigures(pt))
4,164✔
711
            continue;
6✔
712

713
        // Figuren Bescheid sagen
714
        for(noBase& object : GetFigures(nb))
17,204✔
715
        {
716
            if(object.GetType() == NodalObjectType::Figure)
286✔
717
                static_cast<noFigure&>(object).NodeFreed(pt);
170✔
718
        }
719
    }
720
}
694✔
721

722
/// Kleine Klasse für Angriffsfunktion für einen potentielle angreifenden Soldaten
723
struct PotentialAttacker
724
{
725
    nofPassiveSoldier* soldier;
726
    /// Weglänge zum Angriffsziel
727
    unsigned distance;
728
};
729

730
void GameWorld::Attack(const unsigned char player_attacker, const MapPoint pt, const unsigned short soldiers_count,
34✔
731
                       const bool strong_soldiers)
732
{
733
    auto* attacked_building = GetSpecObj<nobBaseMilitary>(pt);
34✔
734
    if(!attacked_building || !attacked_building->IsAttackable(player_attacker))
34✔
735
        return;
5✔
736

737
    // Militärgebäude in der Nähe finden
738
    sortedMilitaryBlds buildings = LookForMilitaryBuildings(pt, 3);
58✔
739

740
    // Liste von verfügbaren Soldaten, geordnet einfügen, damit man dann starke oder schwache Soldaten nehmen kann
741
    std::list<PotentialAttacker> potentialAttackers;
58✔
742

743
    for(const auto* building : buildings)
349✔
744
    {
745
        // Muss ein Gebäude von uns sein und darf nur ein "normales Militärgebäude" sein (kein HQ etc.)
746
        if(building->GetPlayer() != player_attacker || !BuildingProperties::IsMilitary(building->GetBuildingType()))
131✔
747
            continue;
102✔
748

749
        const auto& milBld = static_cast<const nobMilitary&>(*building);
29✔
750
        unsigned numSoldiersForCurrentBld = milBld.GetNumSoldiersForAttack(pt);
29✔
751
        if(!numSoldiersForCurrentBld)
29✔
752
            continue;
1✔
753

754
        // Take soldier(s)
755
        unsigned curNumSoldiers = 0;
28✔
756
        const unsigned distance = CalcDistance(building->GetPos(), pt);
28✔
757
        if(strong_soldiers)
28✔
758
        {
759
            // Strong soldiers first
760
            for(auto& curSoldier : helpers::reverse(milBld.GetTroops()))
76✔
761
            {
762
                if(curNumSoldiers >= numSoldiersForCurrentBld)
76✔
763
                    break;
14✔
764
                ++curNumSoldiers;
62✔
765
                /* Insert new soldier before current one if:
766
                        new soldiers rank is greater
767
                        OR new soldiers rank is equal AND new soldiers distance is smaller */
768
                const auto itInsertPos = helpers::find_if(
769
                  potentialAttackers, [curRank = curSoldier.GetRank(), distance](const PotentialAttacker& pa) {
62✔
770
                      return pa.soldier->GetRank() < curRank
117✔
771
                             || (pa.soldier->GetRank() == curRank && pa.distance > distance);
117✔
772
                  });
62✔
773
                potentialAttackers.emplace(itInsertPos, PotentialAttacker{&curSoldier, distance});
62✔
774
            }
775
        } else
776
        {
777
            // Weak soldiers first
778
            for(auto& curSoldier : milBld.GetTroops())
130✔
779
            {
780
                if(curNumSoldiers >= numSoldiersForCurrentBld)
65✔
781
                    break;
14✔
782
                ++curNumSoldiers;
51✔
783
                /* Insert new soldier before current one if:
784
                           new soldiers rank is less
785
                           OR new soldiers rank is equal AND new soldiers distance is smaller */
786
                const auto itInsertPos = helpers::find_if(
787
                  potentialAttackers, [curRank = curSoldier.GetRank(), distance](const PotentialAttacker& pa) {
51✔
788
                      return pa.soldier->GetRank() > curRank
86✔
789
                             || (pa.soldier->GetRank() == curRank && pa.distance > distance);
86✔
790
                  });
51✔
791
                potentialAttackers.emplace(itInsertPos, PotentialAttacker{&curSoldier, distance});
51✔
792
            }
793
        } // End weak/strong check
794
    }
795

796
    // Send the soldiers to attack
797
    unsigned curNumSoldiers = 0;
29✔
798

799
    for(PotentialAttacker& pa : potentialAttackers)
100✔
800
    {
801
        if(curNumSoldiers >= soldiers_count)
84✔
802
            break;
13✔
803
        pa.soldier->getHome()->SendAttacker(pa.soldier, *attacked_building);
71✔
804
        curNumSoldiers++;
71✔
805
    }
806

807
    if(curNumSoldiers > 0 && HasLua())
29✔
808
    {
809
        GetLua().EventAttack(player_attacker, attacked_building->GetPlayer(), curNumSoldiers);
×
810
    }
811
}
812

813
/// Compare sea attackers by their rank, armor, then by their distance
814
template<class T_RankCmp>
815
struct CmpSeaAttacker : private T_RankCmp
816
{
817
    bool operator()(const GameWorldBase::PotentialSeaAttacker& lhs, const GameWorldBase::PotentialSeaAttacker& rhs)
64✔
818
    {
819
        // Sort after rank, then armor then distance then objId
820
        if(lhs.soldier->GetRank() == rhs.soldier->GetRank())
64✔
821
        {
822
            if(lhs.soldier->HasArmor() == rhs.soldier->HasArmor())
24✔
823
            {
824
                if(lhs.distance == rhs.distance)
24✔
825
                    return (lhs.soldier->GetObjId() < rhs.soldier->GetObjId()); // tie breaker
24✔
826
                else
827
                    return lhs.distance < rhs.distance;
×
828
            } else
829
                return T_RankCmp::operator()(lhs.soldier->HasArmor() ? 1 : 0, rhs.soldier->HasArmor() ? 1 : 0);
×
830
        } else
831
            return T_RankCmp::operator()(lhs.soldier->GetRank(), rhs.soldier->GetRank());
40✔
832
    }
833
};
834

835
void GameWorld::AttackViaSea(const unsigned char player_attacker, const MapPoint pt,
35✔
836
                             const unsigned short soldiers_count, const bool strong_soldiers)
837
{
838
    // Verfügbare Soldaten herausfinden
839
    std::vector<GameWorldBase::PotentialSeaAttacker> attackers = GetSoldiersForSeaAttack(player_attacker, pt);
35✔
840
    if(attackers.empty())
35✔
841
        return;
24✔
842

843
    // Sort them
844
    if(strong_soldiers)
11✔
845
        helpers::sort(attackers, CmpSeaAttacker<std::greater<>>());
11✔
846
    else
NEW
847
        helpers::sort(attackers, CmpSeaAttacker<std::less<>>());
×
848

849
    auto& attacked_building = *GetSpecObj<nobBaseMilitary>(pt);
11✔
850
    unsigned counter = 0;
11✔
851
    for(GameWorldBase::PotentialSeaAttacker& pa : attackers)
30✔
852
    {
853
        if(counter >= soldiers_count)
27✔
854
            break;
8✔
855
        pa.soldier->getHome()->SendAttacker(pa.soldier, attacked_building, pa.harbor);
19✔
856
        counter++;
19✔
857
    }
858

859
    if(counter > 0 && HasLua())
11✔
860
    {
861
        GetLua().EventAttack(player_attacker, attacked_building.GetPlayer(), counter);
×
862
    }
863
}
864

865
TradePathCache& GameWorld::GetTradePathCache()
28✔
866
{
867
    RTTR_Assert(tradePathCache);
28✔
868
    return *tradePathCache;
28✔
869
}
870

871
void GameWorld::setEconHandler(std::unique_ptr<EconomyModeHandler> handler)
3✔
872
{
873
    RTTR_Assert_Msg(!econHandler, "Can't reset the economy mode handler ATM");
3✔
874
    econHandler = std::move(handler);
3✔
875
}
3✔
876

877
bool GameWorld::IsRoadNodeForFigures(const MapPoint pt)
8,413✔
878
{
879
    // Figuren durchgehen, bei Kämpfen und wartenden Angreifern sowie anderen wartenden Figuren stoppen!
880
    for(const noBase& object : GetFigures(pt))
39,668✔
881
    {
882
        // andere wartende Figuren
883
        /*
884
                ATTENTION! This leads to figures on the same node blocking each other. -> Ghost jams
885

886
                if((*it)->GetType() == NodalObjectType::Figure)
887
                {
888
                    noFigure * fig = static_cast<noFigure*>(*it);
889
                    // Figuren dürfen sich nicht gegenüber stehen, sonst warten sie ja ewig aufeinander
890
                    // Außerdem muss auch die Position stimmen, sonst spinnt der ggf. rum, da
891
                    if(fig->IsWaitingForFreeNode() && (fig->GetDir()+3)%6 != dir)
892
                        return false;
893
                }*/
894

895
        // Kampf
896
        if(object.GetGOT() == GO_Type::Fighting)
3,043✔
897
        {
898
            if(static_cast<const noFighting&>(object).IsActive())
148✔
899
                return false;
35✔
900
        } else if(object.GetGOT() == GO_Type::NofAttacker) // wartende Angreifer
2,895✔
901
        {
902
            if(static_cast<const nofAttacker&>(object).IsBlockingRoads())
488✔
903
                return false;
25✔
904
        }
905
    }
906

907
    // alles ok
908
    return true;
8,378✔
909
}
910

911
/// Lässt alle Figuren, die auf diesen Punkt  auf Wegen zulaufen, anhalten auf dem Weg (wegen einem Kampf)
912
void GameWorld::StopOnRoads(const MapPoint pt, const helpers::OptionalEnum<Direction> dir)
41✔
913
{
914
    // Figuren drumherum sammeln (auch von dem Punkt hier aus)
915
    std::vector<noFigure*> figures;
82✔
916

917
    // Auch vom Ausgangspunkt aus, da sie im GameWorld wegem Zeichnen auch hier hängen können!
918
    for(auto& fieldFigure : GetFigures(pt))
218✔
919
    {
920
        if(fieldFigure.GetType() == NodalObjectType::Figure)
27✔
921
            figures.push_back(static_cast<noFigure*>(&fieldFigure));
25✔
922
    }
923

924
    // Und natürlich in unmittelbarer Umgebung suchen
925
    for(const MapPoint nb : GetNeighbours(pt))
287✔
926
    {
927
        for(auto& fieldFigure : GetFigures(nb))
1,032✔
928
        {
929
            if(fieldFigure.GetType() == NodalObjectType::Figure)
24✔
930
                figures.push_back(static_cast<noFigure*>(&fieldFigure));
23✔
931
        }
932
    }
933

934
    for(auto& figure : figures)
89✔
935
    {
936
        if(dir && *dir + 3u == static_cast<noFigure*>(figure)->GetCurMoveDir())
48✔
937
        {
938
            if(GetNeighbour(pt, *dir) == static_cast<noFigure*>(figure)->GetPos())
1✔
939
                continue;
1✔
940
        }
941

942
        // Derjenige muss ggf. stoppen, wenn alles auf ihn zutrifft
943
        static_cast<noFigure*>(figure)->StopIfNecessary(pt);
47✔
944
    }
945
}
41✔
946

947
void GameWorld::Armageddon()
1✔
948
{
949
    RTTR_FOREACH_PT(MapPoint, GetSize())
821✔
950
    {
951
        auto* flag = GetSpecObj<noFlag>(pt);
800✔
952
        if(flag)
800✔
953
        {
954
            flag->DestroyAttachedBuilding();
2✔
955
            DestroyNO(pt, false);
2✔
956
        }
957
    }
958
}
1✔
959

960
void GameWorld::Armageddon(const unsigned char player)
2✔
961
{
962
    RTTR_FOREACH_PT(MapPoint, GetSize())
1,622✔
963
    {
964
        auto* flag = GetSpecObj<noFlag>(pt);
1,568✔
965
        if(flag && flag->GetPlayer() == player)
1,568✔
966
        {
967
            flag->DestroyAttachedBuilding();
2✔
968
            DestroyNO(pt, false);
2✔
969
        }
970
    }
971
}
2✔
972

973
bool GameWorld::ValidWaitingAroundBuildingPoint(const MapPoint pt, const MapPoint center)
968✔
974
{
975
    // Gültiger Punkt für Figuren?
976
    if(!PathConditionHuman(*this).IsNodeOk(pt))
1,936✔
977
        return false;
210✔
978

979
    // Objekte, die sich hier befinden durchgehen
980
    for(const auto& figure : GetFigures(pt))
3,112✔
981
    {
982
        // Ist hier ein anderer Soldat, der hier ebenfalls wartet?
983
        if(figure.GetGOT() == GO_Type::NofAttacker || figure.GetGOT() == GO_Type::NofAggressivedefender
226✔
984
           || figure.GetGOT() == GO_Type::NofDefender)
226✔
985
        {
986
            const auto state = static_cast<const nofActiveSoldier&>(figure).GetState();
224✔
987
            if(state == nofActiveSoldier::SoldierState::WaitingForFight
224✔
988
               || state == nofActiveSoldier::SoldierState::AttackingWaitingAroundBuilding)
222✔
989
                return false;
184✔
990
        }
991

992
        // Oder ein Kampf, der hier tobt?
993
        if(figure.GetGOT() == GO_Type::Fighting)
40✔
994
            return false;
×
995
    }
996
    // object wall or impassable terrain increasing my path to target length to a higher value than the direct distance?
997
    return FindHumanPath(pt, center, CalcDistance(pt, center)) != boost::none;
574✔
998
}
999

1000
bool GameWorld::IsValidPointForFighting(MapPoint pt, const nofActiveSoldier& soldier,
1,181✔
1001
                                        bool avoid_military_building_flags)
1002
{
1003
    // Is this a flag of a military building?
1004
    if(avoid_military_building_flags && GetNO(pt)->GetGOT() == GO_Type::Flag)
1,181✔
1005
    {
1006
        GO_Type got = GetNO(GetNeighbour(pt, Direction::NorthWest))->GetGOT();
8✔
1007
        if(got == GO_Type::NobMilitary || got == GO_Type::NobHarborbuilding || got == GO_Type::NobHq)
8✔
1008
            return false;
8✔
1009
    }
1010

1011
    // Objekte, die sich hier befinden durchgehen
1012
    for(const auto& figure : GetFigures(pt))
4,802✔
1013
    {
1014
        // Ist hier ein anderer Soldat, der hier ebenfalls wartet ?
1015
        const GO_Type got = figure.GetGOT();
209✔
1016
        if(got == GO_Type::NofAttacker || got == GO_Type::NofAggressivedefender || got == GO_Type::NofDefender)
209✔
1017
        {
1018
            if(static_cast<const nofActiveSoldier*>(&figure) == &soldier)
111✔
1019
                continue;
36✔
1020
            switch(static_cast<const nofActiveSoldier&>(figure).GetState())
75✔
1021
            {
1022
                default: break;
16✔
1023
                case nofActiveSoldier::SoldierState::WaitingForFight:
59✔
1024
                case nofActiveSoldier::SoldierState::AttackingWaitingAroundBuilding:
1025
                case nofActiveSoldier::SoldierState::AttackingWaitingForDefender:
1026
                case nofActiveSoldier::SoldierState::DefendingWaiting: return false;
154✔
1027
            }
1028
        } else if(got == GO_Type::Fighting) // Oder ein Kampf, der hier tobt?
98✔
1029
        {
1030
            const auto& fight = static_cast<const noFighting&>(figure);
97✔
1031
            // A soldier currently fighting shouldn't look for a new fighting spot
1032
            RTTR_Assert(!fight.IsFighter(soldier));
97✔
1033
            if(fight.IsActive())
97✔
1034
                return false;
95✔
1035
        }
1036
    }
1037
    // Liegt hier was rum auf dem man nicht kämpfen sollte?
1038
    const BlockingManner bm = GetNO(pt)->GetBM();
1,019✔
1039
    return bm == BlockingManner::None || bm == BlockingManner::Tree || bm == BlockingManner::Flag;
1,019✔
1040
}
1041

1042
bool GameWorld::IsPointCompletelyVisible(const MapPoint& pt, unsigned char player,
47,174✔
1043
                                         const noBaseBuilding* exception) const
1044
{
1045
    sortedMilitaryBlds buildings = LookForMilitaryBuildings(pt, 3);
94,348✔
1046

1047
    // Sichtbereich von Militärgebäuden
1048
    for(const nobBaseMilitary* milBld : buildings)
269,050✔
1049
    {
1050
        if(milBld->GetPlayer() == player && milBld != exception)
90,856✔
1051
        {
1052
            // Prüfen, obs auch unbesetzt ist
1053
            if(milBld->GetGOT() == GO_Type::NobMilitary)
39,278✔
1054
            {
1055
                if(static_cast<const nobMilitary*>(milBld)->IsNewBuilt())
10,117✔
1056
                    continue;
505✔
1057
            }
1058

1059
            if(CalcDistance(pt, milBld->GetPos()) <= unsigned(milBld->GetMilitaryRadius() + VISUALRANGE_MILITARY))
38,773✔
1060
                return true;
27,092✔
1061
        }
1062
    }
1063

1064
    // Sichtbereich von Hafenbaustellen
1065
    for(const noBuildingSite* bldSite : harbor_building_sites_from_sea)
20,082✔
1066
    {
1067
        if(bldSite->GetPlayer() == player && bldSite != exception)
×
1068
        {
1069
            if(CalcDistance(pt, bldSite->GetPos()) <= unsigned(HARBOR_RADIUS + VISUALRANGE_MILITARY))
×
1070
                return true;
×
1071
        }
1072
    }
1073

1074
    // Sichtbereich von Spähtürmen
1075
    for(const nobUsual* bld : GetPlayer(player).GetBuildingRegister().GetBuildings(BuildingType::LookoutTower)) //-V807
20,082✔
1076
    {
1077
        // Ist Späturm überhaupt besetzt?
1078
        if(!bld->HasWorker())
×
1079
            continue;
×
1080

1081
        // Nicht die Ausnahme wählen
1082
        if(bld == exception)
×
1083
            continue;
×
1084

1085
        // Liegt Spähturm innerhalb des Sichtradius?
1086
        if(CalcDistance(pt, bld->GetPos()) <= VISUALRANGE_LOOKOUTTOWER)
×
1087
            return true;
×
1088
    }
1089

1090
    // Check scouts and soldiers
1091
    const unsigned range = std::max(VISUALRANGE_SCOUT, VISUALRANGE_SOLDIER);
20,082✔
1092
    if(CheckPointsInRadius(
20,082✔
1093
         pt, range,
1094
         [this, player](auto pt, auto distance) { return this->IsScoutingFigureOnNode(pt, player, distance); }, true))
740,211✔
1095
    {
1096
        return true;
101✔
1097
    }
1098
    return IsPointScoutedByShip(pt, player);
19,981✔
1099
}
1100

1101
bool GameWorld::IsScoutingFigureOnNode(const MapPoint& pt, unsigned player, unsigned distance) const
740,211✔
1102
{
1103
    static_assert(VISUALRANGE_SCOUT >= VISUALRANGE_SOLDIER, "Visual range changed. Check loop below!");
1104

1105
    // Späher/Soldaten in der Nähe prüfen und direkt auf dem Punkt
1106
    for(const noBase& obj : GetFigures(pt))
2,968,168✔
1107
    {
1108
        const GO_Type got = obj.GetGOT();
3,763✔
1109
        // Check for scout. Note: no need to check for distance as scouts have higher distance than soldiers
1110
        if(got == GO_Type::NofScoutFree)
3,763✔
1111
        {
1112
            // Prüfen, ob er auch am Erkunden ist und an der Position genau und ob es vom richtigen Spieler ist
1113
            if(static_cast<const nofScout_Free&>(obj).GetPlayer() == player)
×
1114
                return true;
101✔
1115
            else
1116
                continue;
×
1117
        } else if(distance <= VISUALRANGE_SOLDIER)
3,763✔
1118
        {
1119
            // Soldaten?
1120
            if(got == GO_Type::NofAttacker || got == GO_Type::NofAggressivedefender)
1,860✔
1121
            {
1122
                if(static_cast<const nofActiveSoldier&>(obj).GetPlayer() == player)
142✔
1123
                    return true;
93✔
1124
            }
1125
            // Kämpfe (wo auch Soldaten drin sind)
1126
            else if(got == GO_Type::Fighting)
1,718✔
1127
            {
1128
                // Prüfen, ob da ein Soldat vom angegebenen Spieler dabei ist
1129
                if(static_cast<const noFighting&>(obj).IsSoldierOfPlayer(player))
8✔
1130
                    return true;
8✔
1131
            }
1132
        }
1133
    }
1134

1135
    return false;
740,110✔
1136
}
1137

1138
bool GameWorld::IsPointScoutedByShip(const MapPoint& pt, unsigned player) const
19,981✔
1139
{
1140
    const std::vector<noShip*>& ships = GetPlayer(player).GetShips();
19,981✔
1141
    for(const noShip* ship : ships)
24,295✔
1142
    {
1143
        unsigned shipDistance = CalcDistance(pt, ship->GetPos());
4,500✔
1144
        if(shipDistance <= ship->GetVisualRange())
4,500✔
1145
            return true;
186✔
1146
    }
1147
    return false;
19,795✔
1148
}
1149

1150
void GameWorld::RecalcVisibility(const MapPoint pt, const unsigned char player, const noBaseBuilding* const exception)
47,174✔
1151
{
1152
    /// Zustand davor merken
1153
    Visibility visibility_before = GetNode(pt).fow[player].visibility;
47,174✔
1154

1155
    /// Herausfinden, ob vollständig sichtbar
1156
    bool visible = IsPointCompletelyVisible(pt, player, exception);
47,174✔
1157

1158
    // Vollständig sichtbar --> vollständig sichtbar logischerweise
1159
    if(visible)
47,174✔
1160
        MakeVisible(pt, player);
27,379✔
1161
    else
1162
    {
1163
        // nicht mehr sichtbar
1164
        // Je nach vorherigen Zustand und Einstellung entscheiden
1165
        switch(GetGGS().exploration)
19,795✔
1166
        {
1167
            case Exploration::Disabled:
18,857✔
1168
            case Exploration::Classic:
1169
                // einmal sichtbare Bereiche bleiben erhalten
1170
                // nichts zu tun
1171
                break;
18,857✔
1172
            case Exploration::FogOfWar:
938✔
1173
            case Exploration::FogOfWarExplored:
1174
                // wenn es mal sichtbar war, nun im Nebel des Krieges
1175
                if(visibility_before == Visibility::Visible)
938✔
1176
                {
1177
                    SetVisibility(pt, player, Visibility::FogOfWar, GetEvMgr().GetCurrentGF());
936✔
1178
                }
1179
                break;
938✔
1180
            default: throw std::logic_error("Invalid exploration value");
×
1181
        }
1182
    }
1183
}
47,174✔
1184

1185
void GameWorld::MakeVisible(const MapPoint pt, const unsigned char player)
280,859✔
1186
{
1187
    SetVisibility(pt, player, Visibility::Visible);
280,859✔
1188
}
280,859✔
1189

1190
void GameWorld::RecalcVisibilitiesAroundPoint(const MapPoint pt, const MapCoord radius, const unsigned char player,
153✔
1191
                                              const noBaseBuilding* const exception)
1192
{
1193
    std::vector<MapPoint> pts = GetPointsInRadiusWithCenter(pt, radius);
306✔
1194
    for(const MapPoint& pt : pts)
37,482✔
1195
        RecalcVisibility(pt, player, exception);
37,329✔
1196
}
153✔
1197

1198
/// Setzt die Sichtbarkeiten um einen Punkt auf sichtbar (aus Performancegründen Alternative zu oberem)
1199
void GameWorld::MakeVisibleAroundPoint(const MapPoint pt, const MapCoord radius, const unsigned char player)
749✔
1200
{
1201
    std::vector<MapPoint> pts = GetPointsInRadiusWithCenter(pt, radius);
1,498✔
1202
    for(const MapPoint& curPt : pts)
244,384✔
1203
        MakeVisible(curPt, player);
243,635✔
1204
}
749✔
1205

1206
/// Bestimmt bei der Bewegung eines spähenden Objekts die Sichtbarkeiten an
1207
/// den Rändern neu
1208
void GameWorld::RecalcMovingVisibilities(const MapPoint pt, const unsigned char player, const MapCoord radius,
1,201✔
1209
                                         const Direction moving_dir, MapPoint* enemy_territory)
1210
{
1211
    // Neue Sichtbarkeiten zuerst setzen
1212
    // Zum Eckpunkt der beiden neuen sichtbaren Kanten gehen
1213
    MapPoint t(pt);
1,201✔
1214
    for(MapCoord i = 0; i < radius; ++i)
5,523✔
1215
        t = GetNeighbour(t, moving_dir);
4,322✔
1216

1217
    // Und zu beiden Abzweigungen weiter gehen und Punkte auf visible setzen
1218
    MakeVisible(t, player);
1,201✔
1219
    MapPoint tt(t);
1,201✔
1220
    Direction dir = moving_dir + 2u;
1,201✔
1221
    for(MapCoord i = 0; i < radius; ++i)
5,523✔
1222
    {
1223
        tt = GetNeighbour(tt, dir);
4,322✔
1224
        // Sichtbarkeit und für FOW-Gebiet vorherigen Besitzer merken
1225
        // (d.h. der dort  zuletzt war, als es für Spieler player sichtbar war)
1226
        Visibility old_vis = CalcVisiblityWithAllies(tt, player);
4,322✔
1227
        unsigned char old_owner = GetNode(tt).fow[player].owner;
4,322✔
1228
        MakeVisible(tt, player);
4,322✔
1229
        // Neues feindliches Gebiet entdeckt?
1230
        // Muss vorher undaufgedeckt oder FOW gewesen sein, aber in dem Fall darf dort vorher noch kein
1231
        // Territorium entdeckt worden sein
1232
        unsigned char current_owner = GetNode(tt).owner;
4,322✔
1233
        if(current_owner
4,322✔
1234
           && (old_vis == Visibility::Invisible || (old_vis == Visibility::FogOfWar && old_owner != current_owner)))
2,282✔
1235
        {
1236
            if(GetPlayer(player).IsAttackable(current_owner - 1) && enemy_territory)
62✔
1237
            {
1238
                *enemy_territory = tt;
58✔
1239
            }
1240
        }
1241
    }
1242

1243
    tt = t;
1,201✔
1244
    dir = moving_dir - 2u;
1,201✔
1245
    for(MapCoord i = 0; i < radius; ++i)
5,523✔
1246
    {
1247
        tt = GetNeighbour(tt, dir);
4,322✔
1248
        // Sichtbarkeit und für FOW-Gebiet vorherigen Besitzer merken
1249
        // (d.h. der dort  zuletzt war, als es für Spieler player sichtbar war)
1250
        Visibility old_vis = CalcVisiblityWithAllies(tt, player);
4,322✔
1251
        unsigned char old_owner = GetNode(tt).fow[player].owner;
4,322✔
1252
        MakeVisible(tt, player);
4,322✔
1253
        // Neues feindliches Gebiet entdeckt?
1254
        // Muss vorher undaufgedeckt oder FOW gewesen sein, aber in dem Fall darf dort vorher noch kein
1255
        // Territorium entdeckt worden sein
1256
        unsigned char current_owner = GetNode(tt).owner;
4,322✔
1257
        if(current_owner
4,322✔
1258
           && (old_vis == Visibility::Invisible || (old_vis == Visibility::FogOfWar && old_owner != current_owner)))
2,445✔
1259
        {
1260
            if(GetPlayer(player).IsAttackable(current_owner - 1) && enemy_territory)
164✔
1261
            {
1262
                *enemy_territory = tt;
160✔
1263
            }
1264
        }
1265
    }
1266

1267
    // Dasselbe für die zurückgebliebenen Punkte
1268
    // Diese müssen allerdings neu berechnet werden!
1269
    t = pt;
1,201✔
1270
    Direction anti_moving_dir = moving_dir + 3u;
1,201✔
1271
    for(MapCoord i = 0; i < radius + 1; ++i)
6,724✔
1272
        t = GetNeighbour(t, anti_moving_dir);
5,523✔
1273

1274
    RecalcVisibility(t, player, nullptr);
1,201✔
1275
    tt = t;
1,201✔
1276
    dir = anti_moving_dir + 2u;
1,201✔
1277
    for(MapCoord i = 0; i < radius; ++i)
5,523✔
1278
    {
1279
        tt = GetNeighbour(tt, dir);
4,322✔
1280
        RecalcVisibility(tt, player, nullptr);
4,322✔
1281
    }
1282

1283
    tt = t;
1,201✔
1284
    dir = anti_moving_dir - 2u;
1,201✔
1285
    for(unsigned i = 0; i < radius; ++i)
5,523✔
1286
    {
1287
        tt = GetNeighbour(tt, dir);
4,322✔
1288
        RecalcVisibility(tt, player, nullptr);
4,322✔
1289
    }
1290
}
1,201✔
1291

1292
bool GameWorld::IsBorderNode(const MapPoint pt, const unsigned char owner) const
265,409✔
1293
{
1294
    return (GetNode(pt).owner == owner && !IsPlayerTerritory(pt, owner));
265,409✔
1295
}
1296

1297
/**
1298
 *  Konvertiert Ressourcen zwischen Typen hin und her oder löscht sie.
1299
 *  Für Spiele ohne Gold.
1300
 */
1301
void GameWorld::ConvertMineResourceTypes(ResourceType from, ResourceType to)
×
1302
{
1303
    // LOG.write(("Convert map resources from %i to %i\n", from, to);
1304
    if(from == to)
×
1305
        return;
×
1306

1307
    RTTR_FOREACH_PT(MapPoint, GetSize())
×
1308
    {
1309
        Resource resources = GetNode(pt).resources;
×
1310
        // Gibt es Ressourcen dieses Typs?
1311
        // Wenn ja, dann umwandeln bzw löschen
1312
        if(resources.getType() == from)
×
1313
        {
1314
            resources.setType(to);
×
1315
            SetResource(pt, resources);
×
1316
        }
1317
    }
1318
}
1319

1320
void GameWorld::SetupResources()
×
1321
{
1322
    ResourceType target;
1323
    switch(GetGGS().getSelection(AddonId::CHANGE_GOLD_DEPOSITS))
×
1324
    {
1325
        case 0:
×
1326
        default: target = ResourceType::Gold; break;
×
1327
        case 1: target = ResourceType::Nothing; break;
×
1328
        case 2: target = ResourceType::Iron; break;
×
1329
        case 3: target = ResourceType::Coal; break;
×
1330
        case 4: target = ResourceType::Granite; break;
×
1331
    }
1332
    ConvertMineResourceTypes(ResourceType::Gold, target);
×
1333
    PlaceAndFixWater();
×
1334
}
×
1335

1336
/**
1337
 * Fills water depending on terrain and Addon setting
1338
 */
1339
void GameWorld::PlaceAndFixWater()
×
1340
{
1341
    bool waterEverywhere = GetGGS().getSelection(AddonId::EXHAUSTIBLE_WATER) == 1;
×
1342

1343
    RTTR_FOREACH_PT(MapPoint, GetSize())
×
1344
    {
1345
        Resource curNodeResource = GetNode(pt).resources;
×
1346

1347
        if(curNodeResource.getType() == ResourceType::Nothing)
×
1348
        {
1349
            if(!waterEverywhere)
×
1350
                continue;
×
1351
        } else if(curNodeResource.getType() != ResourceType::Water)
×
1352
        {
1353
            // do not override maps resource.
1354
            continue;
×
1355
        }
1356

1357
        uint8_t minHumidity = 100;
×
1358
        for(const DescIdx<TerrainDesc> tIdx : GetTerrainsAround(pt))
×
1359
        {
1360
            const uint8_t curHumidity = GetDescription().get(tIdx).humidity;
×
1361
            if(curHumidity < minHumidity)
×
1362
            {
1363
                minHumidity = curHumidity;
×
1364
                if(minHumidity == 0)
×
1365
                    break;
×
1366
            }
1367
        }
1368
        if(minHumidity)
×
1369
            curNodeResource =
×
1370
              Resource(ResourceType::Water, waterEverywhere ? 7 : helpers::iround<uint8_t>(minHumidity * 7. / 100.));
×
1371
        else
1372
            curNodeResource = Resource(ResourceType::Nothing, 0);
×
1373

1374
        SetResource(pt, curNodeResource);
×
1375
    }
1376
}
×
1377

1378
/// Gründet vom Schiff aus eine neue Kolonie
1379
bool GameWorld::FoundColony(const HarborId harbor, const unsigned char player, const SeaId seaId)
2✔
1380
{
1381
    // Ist es hier überhaupt noch möglich, eine Kolonie zu gründen?
1382
    if(!IsHarborAtSea(harbor, seaId) || !IsHarborPointFree(harbor, player))
2✔
1383
        return false;
1✔
1384

1385
    MapPoint pos(GetHarborPoint(harbor));
1✔
1386
    DestroyNO(pos, false);
1✔
1387

1388
    // Hafenbaustelle errichten
1389
    auto* bs = new noBuildingSite(pos, player);
1✔
1390
    SetNO(pos, bs);
1✔
1391
    AddHarborBuildingSiteFromSea(bs);
1✔
1392

1393
    if(gi)
1✔
1394
        gi->GI_UpdateMinimap(pos);
×
1395

1396
    RecalcTerritory(*bs, TerritoryChangeReason::Build);
1✔
1397
    // BQ neu berechnen (evtl durch RecalcTerritory noch nicht geschehen)
1398
    RecalcBQAroundPointBig(pos);
1✔
1399

1400
    GetNotifications().publish(ExpeditionNote(ExpeditionNote::ColonyFounded, player, pos));
1✔
1401

1402
    return true;
1✔
1403
}
1404

1405
void GameWorld::RemoveHarborBuildingSiteFromSea(noBuildingSite* building_site)
1✔
1406
{
1407
    RTTR_Assert(building_site->GetBuildingType() == BuildingType::HarborBuilding);
1✔
1408
    harbor_building_sites_from_sea.remove(building_site);
1✔
1409
}
1✔
1410

1411
bool GameWorld::IsHarborBuildingSiteFromSea(const noBuildingSite* building_site) const
4✔
1412
{
1413
    return helpers::contains(harbor_building_sites_from_sea, building_site);
4✔
1414
}
1415

1416
std::vector<HarborId> GameWorld::GetUnexploredHarborPoints(const HarborId hbIdToSkip, const SeaId seaId,
8✔
1417
                                                           unsigned playerId) const
1418
{
1419
    std::vector<HarborId> hps;
8✔
1420
    for(const auto i : helpers::idRange<HarborId>(GetNumHarborPoints()))
72✔
1421
    {
1422
        if(i == hbIdToSkip || !IsHarborAtSea(i, seaId))
64✔
1423
            continue;
40✔
1424
        if(CalcVisiblityWithAllies(GetHarborPoint(i), playerId) != Visibility::Visible)
24✔
1425
            hps.push_back(i);
4✔
1426
    }
1427
    return hps;
8✔
1428
}
1429

1430
MapNode& GameWorld::GetNodeWriteable(const MapPoint pt)
362,192✔
1431
{
1432
    return GetNodeInt(pt);
362,192✔
1433
}
1434

1435
void GameWorld::VisibilityChanged(const MapPoint pt, unsigned player, Visibility oldVis, Visibility newVis)
178,717✔
1436
{
1437
    GameWorldBase::VisibilityChanged(pt, player, oldVis, newVis);
178,717✔
1438
    if(oldVis == Visibility::Invisible && newVis == Visibility::Visible && HasLua())
178,717✔
1439
        GetLua().EventExplored(player, pt, GetNode(pt).owner);
12,168✔
1440
    // Minimap Bescheid sagen
1441
    if(gi)
178,717✔
1442
        gi->GI_UpdateMinimap(pt);
×
1443
}
178,717✔
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