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

Return-To-The-Roots / s25client / 12744223927

13 Jan 2025 09:26AM UTC coverage: 50.258% (+0.03%) from 50.224%
12744223927

push

github

Flamefire
Fix improved alliances for close start positions

With the addon enabled the territory for the HQ won't be calculated
correctly when they are too close.
The code runs into this condtion that reset the owner for territory
gained by a newly placed HQ:
`ownerOfTriggerBld == oldOwner && oldOwner > 0 && reason == TerritoryChangeReason::Build`
When the HQs are too close the position of the newly placed HQ already
belongs to a player and as ownership wasn't yet set for the position of
the HQ `ownerOfTriggerBld` wrongly refers to that player.

Fix by explicitely handling the case of "building" an HQ by setting
`ownerOfTriggerBld` to the new owner of the point, which is the owner of the HQ.

Fixes #1733

9 of 10 new or added lines in 1 file covered. (90.0%)

4 existing lines in 1 file now uncovered.

22325 of 44421 relevant lines covered (50.26%)

36882.97 hits per line

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

84.15
/libs/s25main/world/GameWorld.cpp
1
// Copyright (C) 2005 - 2024 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/containerUtils.h"
20
#include "helpers/mathFuncs.h"
21
#include "helpers/reverse.h"
22
#include "lua/LuaInterfaceGame.h"
23
#include "notifications/BuildingNote.h"
24
#include "notifications/ExpeditionNote.h"
25
#include "notifications/NodeNote.h"
26
#include "notifications/RoadNote.h"
27
#include "pathfinding/PathConditionHuman.h"
28
#include "pathfinding/PathConditionRoad.h"
29
#include "postSystem/PostMsgWithBuilding.h"
30
#include "world/MapGeometry.h"
31
#include "world/TerritoryRegion.h"
32
#include "nodeObjs/noFighting.h"
33
#include "nodeObjs/noFlag.h"
34
#include "nodeObjs/noShip.h"
35
#include "nodeObjs/noStaticObject.h"
36
#include "gameData/BuildingConsts.h"
37
#include "gameData/BuildingProperties.h"
38
#include "gameData/MilitaryConsts.h"
39
#include "gameData/TerrainDesc.h"
40
#include <algorithm>
41
#include <functional>
42
#include <set>
43
#include <stdexcept>
44

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

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

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

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

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

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

84
        RecalcBQAroundPointBig(pt);
734✔
85
    }
86
}
87

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

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

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

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

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

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

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

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

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

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

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

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

149
    DestroyNO(pt, false);
26✔
150

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

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

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

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

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

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

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

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

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

219
    curPt = GetNeighbour(curPt, route.back());
156✔
220

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

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

246
    MapPoint end(start);
154✔
247
    for(auto i : route)
729✔
248
    {
249
        SetPointRoad(end, i, boat_road ? PointRoad::Boat : PointRoad::Normal);
575✔
250
        RecalcBQForRoad(end);
575✔
251
        end = GetNeighbour(end, i);
575✔
252

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

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

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

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

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

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

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

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

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

307
    RTTR_FOREACH_PT(Position, areaSize)
252,228✔
308
    {
309
        // Make map point
310
        const MapPoint curMapPt = MakeMapPoint(pt + startPt);
242,670✔
311
        const unsigned char owner = GetNode(curMapPt).owner;
242,670✔
312
        BoundaryStones& boundaryStones = GetBoundaryStones(curMapPt);
242,670✔
313

314
        // Is this a border node?
315
        if(owner && IsBorderNode(curMapPt, owner))
242,670✔
316
        {
317
            // Check which neighbors are also border nodes and place the half-way stones to them
318
            for(const auto bPos : helpers::EnumRange<BorderStonePos>{})
284,928✔
319
            {
320
                if(bPos == BorderStonePos::OnPoint || IsBorderNode(GetNeighbour(curMapPt, toDirection(bPos)), owner))
94,976✔
321
                    boundaryStones[bPos] = owner;
47,463✔
322
                else
323
                    boundaryStones[bPos] = 0;
47,513✔
324
            }
325

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

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

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

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

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

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

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

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

392
    const TerritoryRegion region = CreateTerritoryRegion(building, militaryRadius + ADD_RADIUS, reason);
834✔
393

394
    std::vector<MapPoint> ptsWithChangedOwners;
834✔
395
    std::vector<int> sizeChanges(GetNumPlayers());
834✔
396

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

404
        // If nothing changed, there is nothing to do (ownerChanged was already initialized)
405
        if(oldOwner == newOwner)
171,911✔
406
            continue;
98,328✔
407

408
        SetOwner(curMapPt, newOwner);
73,583✔
409
        ptsWithChangedOwners.push_back(curMapPt);
73,583✔
410
        if(newOwner != 0)
73,583✔
411
            sizeChanges[newOwner - 1]++;
67,146✔
412
        if(oldOwner != 0)
73,583✔
413
            sizeChanges[oldOwner - 1]--;
9,206✔
414
    }
415

416
    const std::vector<MapPoint> ptsToHandle = GetAllNeighboursUnion(ptsWithChangedOwners);
834✔
417

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

424
        if(gi)
91,211✔
425
            gi->GI_UpdateMinimap(curMapPt);
×
426
    }
427

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

438
        if(!flag)
89,802✔
439
            continue;
89,549✔
440

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

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

448
    // Notify
449
    for(const MapPoint& curMapPt : ptsWithChangedOwners)
74,000✔
450
        GetNotifications().publish(NodeNote(NodeNote::Owner, curMapPt));
73,583✔
451

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

462
    RecalcBorderStones(region.startPt, region.size);
417✔
463

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

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

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

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

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

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

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

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

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

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

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

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

569
    return region;
834✔
570
}
571

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

583
        RTTR_FOREACH_PT(Position, region.size)
241✔
584
        {
585
            const MapPoint curMapPt = MakeMapPoint(pt + region.startPt);
230✔
586
            const auto oldOwner = GetNode(curMapPt).owner;
230✔
587
            const auto newOwner = region.GetOwner(pt);
230✔
588

589
            // If nothing changed, there is nothing to do (ownerChanged was already initialized)
590
            if(oldOwner == newOwner)
230✔
591
                continue;
110✔
592

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

609
    // "Cosmetics": Remove points that do not border to territory to avoid edges of border stones
610
    RTTR_FOREACH_PT(Position, region.size)
180,126✔
611
    {
612
        uint8_t owner = region.GetOwner(pt);
171,911✔
613
        if(!owner)
171,911✔
614
            continue;
61,932✔
615
        // Check if any neighbour is fully surrounded by player territory
616
        bool isPlayerTerritoryNear = false;
109,979✔
617
        for(const auto d : helpers::EnumRange<Direction>{})
530,010✔
618
        {
619
            Position neighbour = ::GetNeighbour(pt + region.startPt, d);
154,973✔
620
            if(region.SafeGetOwner(neighbour - region.startPt) != owner)
154,973✔
621
                continue;
18,070✔
622
            // Don't check this point as it is always true
623
            const Direction exceptDir = d + 3u;
136,903✔
624
            if(region.WillBePlayerTerritory(neighbour, owner, exceptDir))
136,903✔
625
            {
626
                isPlayerTerritoryNear = true;
109,926✔
627
                break;
109,926✔
628
            }
629
        }
630

631
        // All good?
632
        if(isPlayerTerritoryNear)
109,979✔
633
            continue;
109,926✔
634
        // No neighbouring player territory found. Look for another
635
        uint8_t newOwner = 0;
53✔
636
        for(const auto d : helpers::EnumRange<Direction>{})
398✔
637
        {
638
            Position neighbour = ::GetNeighbour(pt + region.startPt, d);
140✔
639
            uint8_t nbOwner = region.SafeGetOwner(neighbour - region.startPt);
140✔
640
            // No or same player?
641
            if(!nbOwner || nbOwner == owner)
140✔
642
                continue;
47✔
643
            // Don't check this point as it would always fail
644
            const Direction exceptDir = d + 3u;
93✔
645
            // First one found gets it
646
            if(region.WillBePlayerTerritory(neighbour, nbOwner, exceptDir))
93✔
647
            {
648
                newOwner = nbOwner;
47✔
649
                break;
47✔
650
            }
651
        }
652
        region.SetOwner(pt, newOwner);
53✔
653
    }
654
}
417✔
655

656
void GameWorld::CreateTradeGraphs()
12✔
657
{
658
    // Only if trade is enabled
659
    if(GetGGS().isEnabled(AddonId::TRADE))
12✔
660
        tradePathCache = std::make_unique<TradePathCache>(*this);
7✔
661
}
12✔
662

663
void GameWorld::DestroyPlayerRests(const MapPoint pt, unsigned char newOwner, const noBaseBuilding* exception)
91,211✔
664
{
665
    noBase* no = GetNode(pt).obj;
91,211✔
666
    if(!no || no == exception)
91,211✔
667
        return;
89,772✔
668

669
    // Destroy only flags, buildings and building sites
670
    const NodalObjectType noType = no->GetType();
1,439✔
671
    if(noType != NodalObjectType::Flag && noType != NodalObjectType::Building
1,439✔
672
       && noType != NodalObjectType::Buildingsite)
1,113✔
673
        return;
1,112✔
674

675
    // is the building on a node with a different owner? Border is allways destroyed.
676
    if(static_cast<noRoadNode*>(no)->GetPlayer() + 1 == newOwner && !IsBorderNode(pt, newOwner))
327✔
677
        return;
284✔
678

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

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

698
    DestroyNO(pt, false);
43✔
699
}
700

701
void GameWorld::RoadNodeAvailable(const MapPoint pt)
661✔
702
{
703
    // Figuren direkt daneben
704
    for(const MapPoint nb : GetNeighbours(pt))
4,627✔
705
    {
706
        // Nochmal prüfen, ob er nun wirklich verfügbar ist (evtl blocken noch mehr usw.)
707
        if(!IsRoadNodeForFigures(pt))
3,966✔
708
            continue;
×
709

710
        // Figuren Bescheid sagen
711
        for(noBase& object : GetFigures(nb))
16,386✔
712
        {
713
            if(object.GetType() == NodalObjectType::Figure)
261✔
714
                static_cast<noFigure&>(object).NodeFreed(pt);
159✔
715
        }
716
    }
717
}
661✔
718

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

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

734
    // Militärgebäude in der Nähe finden
735
    sortedMilitaryBlds buildings = LookForMilitaryBuildings(pt, 3);
34✔
736

737
    // Liste von verfügbaren Soldaten, geordnet einfügen, damit man dann starke oder schwache Soldaten nehmen kann
738
    std::list<PotentialAttacker> potentialAttackers;
34✔
739

740
    for(const auto* building : buildings)
217✔
741
    {
742
        // Muss ein Gebäude von uns sein und darf nur ein "normales Militärgebäude" sein (kein HQ etc.)
743
        if(building->GetPlayer() != player_attacker || !BuildingProperties::IsMilitary(building->GetBuildingType()))
83✔
744
            continue;
66✔
745

746
        const auto& milBld = static_cast<const nobMilitary&>(*building);
17✔
747
        unsigned numSoldiersForCurrentBld = milBld.GetNumSoldiersForAttack(pt);
17✔
748
        if(!numSoldiersForCurrentBld)
17✔
749
            continue;
1✔
750

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

793
    // Send the soldiers to attack
794
    unsigned curNumSoldiers = 0;
17✔
795

796
    for(PotentialAttacker& pa : potentialAttackers)
50✔
797
    {
798
        if(curNumSoldiers >= soldiers_count)
42✔
799
            break;
9✔
800
        pa.soldier->getHome()->SendAttacker(pa.soldier, *attacked_building);
33✔
801
        curNumSoldiers++;
33✔
802
    }
803

804
    if(curNumSoldiers > 0 && HasLua())
17✔
805
    {
806
        GetLua().EventAttack(player_attacker, attacked_building->GetPlayer(), curNumSoldiers);
×
807
    }
808
}
809

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

828
void GameWorld::AttackViaSea(const unsigned char player_attacker, const MapPoint pt,
35✔
829
                             const unsigned short soldiers_count, const bool strong_soldiers)
830
{
831
    // Verfügbare Soldaten herausfinden
832
    std::vector<GameWorldBase::PotentialSeaAttacker> attackers = GetSoldiersForSeaAttack(player_attacker, pt);
35✔
833
    if(attackers.empty())
35✔
834
        return;
24✔
835

836
    // Sort them
837
    if(strong_soldiers)
11✔
838
        std::sort(attackers.begin(), attackers.end(), CmpSeaAttacker<std::greater<>>());
11✔
839
    else
840
        std::sort(attackers.begin(), attackers.end(), CmpSeaAttacker<std::less<>>());
×
841

842
    auto& attacked_building = *GetSpecObj<nobBaseMilitary>(pt);
11✔
843
    unsigned counter = 0;
11✔
844
    for(GameWorldBase::PotentialSeaAttacker& pa : attackers)
30✔
845
    {
846
        if(counter >= soldiers_count)
27✔
847
            break;
8✔
848
        pa.soldier->getHome()->SendAttacker(pa.soldier, attacked_building, pa.harbor);
19✔
849
        counter++;
19✔
850
    }
851

852
    if(counter > 0 && HasLua())
11✔
853
    {
854
        GetLua().EventAttack(player_attacker, attacked_building.GetPlayer(), counter);
×
855
    }
856
}
857

858
TradePathCache& GameWorld::GetTradePathCache()
25✔
859
{
860
    RTTR_Assert(tradePathCache);
25✔
861
    return *tradePathCache;
25✔
862
}
863

864
void GameWorld::setEconHandler(std::unique_ptr<EconomyModeHandler> handler)
3✔
865
{
866
    RTTR_Assert_Msg(!econHandler, "Can't reset the economy mode handler ATM");
3✔
867
    econHandler = std::move(handler);
3✔
868
}
3✔
869

870
bool GameWorld::IsRoadNodeForFigures(const MapPoint pt)
7,840✔
871
{
872
    // Figuren durchgehen, bei Kämpfen und wartenden Angreifern sowie anderen wartenden Figuren stoppen!
873
    for(const noBase& object : GetFigures(pt))
37,344✔
874
    {
875
        // andere wartende Figuren
876
        /*
877
                ATTENTION! This leads to figures on the same node blocking each other. -> Ghost jams
878

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

888
        // Kampf
889
        if(object.GetGOT() == GO_Type::Fighting)
3,008✔
890
        {
891
            if(static_cast<const noFighting&>(object).IsActive())
122✔
892
                return false;
16✔
893
        } else if(object.GetGOT() == GO_Type::NofAttacker) // wartende Angreifer
2,886✔
894
        {
895
            if(static_cast<const nofAttacker&>(object).IsBlockingRoads())
279✔
896
                return false;
12✔
897
        }
898
    }
899

900
    // alles ok
901
    return true;
7,824✔
902
}
903

904
/// Lässt alle Figuren, die auf diesen Punkt  auf Wegen zulaufen, anhalten auf dem Weg (wegen einem Kampf)
905
void GameWorld::StopOnRoads(const MapPoint pt, const helpers::OptionalEnum<Direction> dir)
25✔
906
{
907
    // Figuren drumherum sammeln (auch von dem Punkt hier aus)
908
    std::vector<noFigure*> figures;
50✔
909

910
    // Auch vom Ausgangspunkt aus, da sie im GameWorld wegem Zeichnen auch hier hängen können!
911
    for(auto& fieldFigure : GetFigures(pt))
132✔
912
    {
913
        if(fieldFigure.GetType() == NodalObjectType::Figure)
16✔
914
            figures.push_back(static_cast<noFigure*>(&fieldFigure));
12✔
915
    }
916

917
    // Und natürlich in unmittelbarer Umgebung suchen
918
    for(const MapPoint nb : GetNeighbours(pt))
175✔
919
    {
920
        for(auto& fieldFigure : GetFigures(nb))
648✔
921
        {
922
            if(fieldFigure.GetType() == NodalObjectType::Figure)
24✔
923
                figures.push_back(static_cast<noFigure*>(&fieldFigure));
24✔
924
        }
925
    }
926

927
    for(auto& figure : figures)
61✔
928
    {
929
        if(dir && *dir + 3u == static_cast<noFigure*>(figure)->GetCurMoveDir())
36✔
930
        {
931
            if(GetNeighbour(pt, *dir) == static_cast<noFigure*>(figure)->GetPos())
1✔
932
                continue;
1✔
933
        }
934

935
        // Derjenige muss ggf. stoppen, wenn alles auf ihn zutrifft
936
        static_cast<noFigure*>(figure)->StopIfNecessary(pt);
35✔
937
    }
938
}
25✔
939

940
void GameWorld::Armageddon()
1✔
941
{
942
    RTTR_FOREACH_PT(MapPoint, GetSize())
821✔
943
    {
944
        auto* flag = GetSpecObj<noFlag>(pt);
800✔
945
        if(flag)
800✔
946
        {
947
            flag->DestroyAttachedBuilding();
2✔
948
            DestroyNO(pt, false);
2✔
949
        }
950
    }
951
}
1✔
952

953
void GameWorld::Armageddon(const unsigned char player)
2✔
954
{
955
    RTTR_FOREACH_PT(MapPoint, GetSize())
1,622✔
956
    {
957
        auto* flag = GetSpecObj<noFlag>(pt);
1,568✔
958
        if(flag && flag->GetPlayer() == player)
1,568✔
959
        {
960
            flag->DestroyAttachedBuilding();
2✔
961
            DestroyNO(pt, false);
2✔
962
        }
963
    }
964
}
2✔
965

966
bool GameWorld::ValidWaitingAroundBuildingPoint(const MapPoint pt, const MapPoint center)
411✔
967
{
968
    // Gültiger Punkt für Figuren?
969
    if(!PathConditionHuman(*this).IsNodeOk(pt))
822✔
970
        return false;
71✔
971

972
    // Objekte, die sich hier befinden durchgehen
973
    for(const auto& figure : GetFigures(pt))
1,400✔
974
    {
975
        // Ist hier ein anderer Soldat, der hier ebenfalls wartet?
976
        if(figure.GetGOT() == GO_Type::NofAttacker || figure.GetGOT() == GO_Type::NofAggressivedefender
87✔
977
           || figure.GetGOT() == GO_Type::NofDefender)
87✔
978
        {
979
            const auto state = static_cast<const nofActiveSoldier&>(figure).GetState();
87✔
980
            if(state == nofActiveSoldier::SoldierState::WaitingForFight
87✔
981
               || state == nofActiveSoldier::SoldierState::AttackingWaitingAroundBuilding)
87✔
982
                return false;
67✔
983
        }
984

985
        // Oder ein Kampf, der hier tobt?
986
        if(figure.GetGOT() == GO_Type::Fighting)
20✔
987
            return false;
×
988
    }
989
    // object wall or impassable terrain increasing my path to target length to a higher value than the direct distance?
990
    return FindHumanPath(pt, center, CalcDistance(pt, center)) != boost::none;
273✔
991
}
992

993
bool GameWorld::IsValidPointForFighting(MapPoint pt, const nofActiveSoldier& soldier,
1,094✔
994
                                        bool avoid_military_building_flags)
995
{
996
    // Is this a flag of a military building?
997
    if(avoid_military_building_flags && GetNO(pt)->GetGOT() == GO_Type::Flag)
1,094✔
998
    {
999
        GO_Type got = GetNO(GetNeighbour(pt, Direction::NorthWest))->GetGOT();
11✔
1000
        if(got == GO_Type::NobMilitary || got == GO_Type::NobHarborbuilding || got == GO_Type::NobHq)
11✔
1001
            return false;
11✔
1002
    }
1003

1004
    // Objekte, die sich hier befinden durchgehen
1005
    for(const auto& figure : GetFigures(pt))
4,420✔
1006
    {
1007
        // Ist hier ein anderer Soldat, der hier ebenfalls wartet ?
1008
        const GO_Type got = figure.GetGOT();
117✔
1009
        if(got == GO_Type::NofAttacker || got == GO_Type::NofAggressivedefender || got == GO_Type::NofDefender)
117✔
1010
        {
1011
            if(static_cast<const nofActiveSoldier*>(&figure) == &soldier)
72✔
1012
                continue;
23✔
1013
            switch(static_cast<const nofActiveSoldier&>(figure).GetState())
49✔
1014
            {
1015
                default: break;
19✔
1016
                case nofActiveSoldier::SoldierState::WaitingForFight:
30✔
1017
                case nofActiveSoldier::SoldierState::AttackingWaitingAroundBuilding:
1018
                case nofActiveSoldier::SoldierState::AttackingWaitingForDefender:
1019
                case nofActiveSoldier::SoldierState::DefendingWaiting: return false;
73✔
1020
            }
1021
        } else if(got == GO_Type::Fighting) // Oder ein Kampf, der hier tobt?
45✔
1022
        {
1023
            const auto& fight = static_cast<const noFighting&>(figure);
44✔
1024
            // A soldier currently fighting shouldn't look for a new fighting spot
1025
            RTTR_Assert(!fight.IsFighter(soldier));
44✔
1026
            if(fight.IsActive())
44✔
1027
                return false;
43✔
1028
        }
1029
    }
1030
    // Liegt hier was rum auf dem man nicht kämpfen sollte?
1031
    const BlockingManner bm = GetNO(pt)->GetBM();
1,010✔
1032
    return bm == BlockingManner::None || bm == BlockingManner::Tree || bm == BlockingManner::Flag;
1,010✔
1033
}
1034

1035
bool GameWorld::IsPointCompletelyVisible(const MapPoint& pt, unsigned char player,
38,830✔
1036
                                         const noBaseBuilding* exception) const
1037
{
1038
    sortedMilitaryBlds buildings = LookForMilitaryBuildings(pt, 3);
77,660✔
1039

1040
    // Sichtbereich von Militärgebäuden
1041
    for(const nobBaseMilitary* milBld : buildings)
217,084✔
1042
    {
1043
        if(milBld->GetPlayer() == player && milBld != exception)
73,296✔
1044
        {
1045
            // Prüfen, obs auch unbesetzt ist
1046
            if(milBld->GetGOT() == GO_Type::NobMilitary)
35,136✔
1047
            {
1048
                if(static_cast<const nobMilitary*>(milBld)->IsNewBuilt())
8,613✔
1049
                    continue;
505✔
1050
            }
1051

1052
            if(CalcDistance(pt, milBld->GetPos()) <= unsigned(milBld->GetMilitaryRadius() + VISUALRANGE_MILITARY))
34,631✔
1053
                return true;
22,999✔
1054
        }
1055
    }
1056

1057
    // Sichtbereich von Hafenbaustellen
1058
    for(const noBuildingSite* bldSite : harbor_building_sites_from_sea)
15,831✔
1059
    {
1060
        if(bldSite->GetPlayer() == player && bldSite != exception)
×
1061
        {
1062
            if(CalcDistance(pt, bldSite->GetPos()) <= unsigned(HARBOR_RADIUS + VISUALRANGE_MILITARY))
×
1063
                return true;
×
1064
        }
1065
    }
1066

1067
    // Sichtbereich von Spähtürmen
1068
    for(const nobUsual* bld : GetPlayer(player).GetBuildingRegister().GetBuildings(BuildingType::LookoutTower)) //-V807
15,831✔
1069
    {
1070
        // Ist Späturm überhaupt besetzt?
1071
        if(!bld->HasWorker())
×
1072
            continue;
×
1073

1074
        // Nicht die Ausnahme wählen
1075
        if(bld == exception)
×
1076
            continue;
×
1077

1078
        // Liegt Spähturm innerhalb des Sichtradius?
1079
        if(CalcDistance(pt, bld->GetPos()) <= VISUALRANGE_LOOKOUTTOWER)
×
1080
            return true;
×
1081
    }
1082

1083
    // Check scouts and soldiers
1084
    const unsigned range = std::max(VISUALRANGE_SCOUT, VISUALRANGE_SOLDIER);
15,831✔
1085
    if(CheckPointsInRadius(
15,831✔
1086
         pt, range,
1087
         [this, player](auto pt, auto distance) { return this->IsScoutingFigureOnNode(pt, player, distance); }, true))
582,924✔
1088
    {
1089
        return true;
101✔
1090
    }
1091
    return IsPointScoutedByShip(pt, player);
15,730✔
1092
}
1093

1094
bool GameWorld::IsScoutingFigureOnNode(const MapPoint& pt, unsigned player, unsigned distance) const
582,924✔
1095
{
1096
    static_assert(VISUALRANGE_SCOUT >= VISUALRANGE_SOLDIER, "Visual range changed. Check loop below!");
1097

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

1128
    return false;
582,823✔
1129
}
1130

1131
bool GameWorld::IsPointScoutedByShip(const MapPoint& pt, unsigned player) const
15,730✔
1132
{
1133
    const std::vector<noShip*>& ships = GetPlayer(player).GetShips();
15,730✔
1134
    for(const noShip* ship : ships)
20,026✔
1135
    {
1136
        unsigned shipDistance = CalcDistance(pt, ship->GetPos());
4,482✔
1137
        if(shipDistance <= ship->GetVisualRange())
4,482✔
1138
            return true;
186✔
1139
    }
1140
    return false;
15,544✔
1141
}
1142

1143
void GameWorld::RecalcVisibility(const MapPoint pt, const unsigned char player, const noBaseBuilding* const exception)
38,830✔
1144
{
1145
    /// Zustand davor merken
1146
    Visibility visibility_before = GetNode(pt).fow[player].visibility;
38,830✔
1147

1148
    /// Herausfinden, ob vollständig sichtbar
1149
    bool visible = IsPointCompletelyVisible(pt, player, exception);
38,830✔
1150

1151
    // Vollständig sichtbar --> vollständig sichtbar logischerweise
1152
    if(visible)
38,830✔
1153
        MakeVisible(pt, player);
23,286✔
1154
    else
1155
    {
1156
        // nicht mehr sichtbar
1157
        // Je nach vorherigen Zustand und Einstellung entscheiden
1158
        switch(GetGGS().exploration)
15,544✔
1159
        {
1160
            case Exploration::Disabled:
14,606✔
1161
            case Exploration::Classic:
1162
                // einmal sichtbare Bereiche bleiben erhalten
1163
                // nichts zu tun
1164
                break;
14,606✔
1165
            case Exploration::FogOfWar:
938✔
1166
            case Exploration::FogOfWarExplored:
1167
                // wenn es mal sichtbar war, nun im Nebel des Krieges
1168
                if(visibility_before == Visibility::Visible)
938✔
1169
                {
1170
                    SetVisibility(pt, player, Visibility::FogOfWar, GetEvMgr().GetCurrentGF());
936✔
1171
                }
1172
                break;
938✔
1173
            default: throw std::logic_error("Invalid exploration value");
×
1174
        }
1175
    }
1176
}
38,830✔
1177

1178
void GameWorld::MakeVisible(const MapPoint pt, const unsigned char player)
203,485✔
1179
{
1180
    SetVisibility(pt, player, Visibility::Visible);
203,485✔
1181
}
203,485✔
1182

1183
void GameWorld::RecalcVisibilitiesAroundPoint(const MapPoint pt, const MapCoord radius, const unsigned char player,
134✔
1184
                                              const noBaseBuilding* const exception)
1185
{
1186
    std::vector<MapPoint> pts = GetPointsInRadiusWithCenter(pt, radius);
268✔
1187
    for(const MapPoint& pt : pts)
30,604✔
1188
        RecalcVisibility(pt, player, exception);
30,470✔
1189
}
134✔
1190

1191
/// Setzt die Sichtbarkeiten um einen Punkt auf sichtbar (aus Performancegründen Alternative zu oberem)
1192
void GameWorld::MakeVisibleAroundPoint(const MapPoint pt, const MapCoord radius, const unsigned char player)
503✔
1193
{
1194
    std::vector<MapPoint> pts = GetPointsInRadiusWithCenter(pt, radius);
1,006✔
1195
    for(const MapPoint& curPt : pts)
172,342✔
1196
        MakeVisible(curPt, player);
171,839✔
1197
}
503✔
1198

1199
/// Bestimmt bei der Bewegung eines spähenden Objekts die Sichtbarkeiten an
1200
/// den Rändern neu
1201
void GameWorld::RecalcMovingVisibilities(const MapPoint pt, const unsigned char player, const MapCoord radius,
904✔
1202
                                         const Direction moving_dir, MapPoint* enemy_territory)
1203
{
1204
    // Neue Sichtbarkeiten zuerst setzen
1205
    // Zum Eckpunkt der beiden neuen sichtbaren Kanten gehen
1206
    MapPoint t(pt);
904✔
1207
    for(MapCoord i = 0; i < radius; ++i)
4,632✔
1208
        t = GetNeighbour(t, moving_dir);
3,728✔
1209

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

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

1260
    // Dasselbe für die zurückgebliebenen Punkte
1261
    // Diese müssen allerdings neu berechnet werden!
1262
    t = pt;
904✔
1263
    Direction anti_moving_dir = moving_dir + 3u;
904✔
1264
    for(MapCoord i = 0; i < radius + 1; ++i)
5,536✔
1265
        t = GetNeighbour(t, anti_moving_dir);
4,632✔
1266

1267
    RecalcVisibility(t, player, nullptr);
904✔
1268
    tt = t;
904✔
1269
    dir = anti_moving_dir + 2u;
904✔
1270
    for(MapCoord i = 0; i < radius; ++i)
4,632✔
1271
    {
1272
        tt = GetNeighbour(tt, dir);
3,728✔
1273
        RecalcVisibility(tt, player, nullptr);
3,728✔
1274
    }
1275

1276
    tt = t;
904✔
1277
    dir = anti_moving_dir - 2u;
904✔
1278
    for(unsigned i = 0; i < radius; ++i)
4,632✔
1279
    {
1280
        tt = GetNeighbour(tt, dir);
3,728✔
1281
        RecalcVisibility(tt, player, nullptr);
3,728✔
1282
    }
1283
}
904✔
1284

1285
bool GameWorld::IsBorderNode(const MapPoint pt, const unsigned char owner) const
201,939✔
1286
{
1287
    return (GetNode(pt).owner == owner && !IsPlayerTerritory(pt, owner));
201,939✔
1288
}
1289

1290
/**
1291
 *  Konvertiert Ressourcen zwischen Typen hin und her oder löscht sie.
1292
 *  Für Spiele ohne Gold.
1293
 */
1294
void GameWorld::ConvertMineResourceTypes(ResourceType from, ResourceType to)
×
1295
{
1296
    // LOG.write(("Convert map resources from %i to %i\n", from, to);
1297
    if(from == to)
×
1298
        return;
×
1299

1300
    RTTR_FOREACH_PT(MapPoint, GetSize())
×
1301
    {
1302
        Resource resources = GetNode(pt).resources;
×
1303
        // Gibt es Ressourcen dieses Typs?
1304
        // Wenn ja, dann umwandeln bzw löschen
1305
        if(resources.getType() == from)
×
1306
        {
1307
            resources.setType(to);
×
1308
            SetResource(pt, resources);
×
1309
        }
1310
    }
1311
}
1312

1313
void GameWorld::SetupResources()
×
1314
{
1315
    ResourceType target;
1316
    switch(GetGGS().getSelection(AddonId::CHANGE_GOLD_DEPOSITS))
×
1317
    {
1318
        case 0:
×
1319
        default: target = ResourceType::Gold; break;
×
1320
        case 1: target = ResourceType::Nothing; break;
×
1321
        case 2: target = ResourceType::Iron; break;
×
1322
        case 3: target = ResourceType::Coal; break;
×
1323
        case 4: target = ResourceType::Granite; break;
×
1324
    }
1325
    ConvertMineResourceTypes(ResourceType::Gold, target);
×
1326
    PlaceAndFixWater();
×
1327
}
×
1328

1329
/**
1330
 * Fills water depending on terrain and Addon setting
1331
 */
1332
void GameWorld::PlaceAndFixWater()
×
1333
{
1334
    bool waterEverywhere = GetGGS().getSelection(AddonId::EXHAUSTIBLE_WATER) == 1;
×
1335

1336
    RTTR_FOREACH_PT(MapPoint, GetSize())
×
1337
    {
1338
        Resource curNodeResource = GetNode(pt).resources;
×
1339

1340
        if(curNodeResource.getType() == ResourceType::Nothing)
×
1341
        {
1342
            if(!waterEverywhere)
×
1343
                continue;
×
1344
        } else if(curNodeResource.getType() != ResourceType::Water)
×
1345
        {
1346
            // do not override maps resource.
1347
            continue;
×
1348
        }
1349

1350
        uint8_t minHumidity = 100;
×
1351
        for(const DescIdx<TerrainDesc> tIdx : GetTerrainsAround(pt))
×
1352
        {
1353
            const uint8_t curHumidity = GetDescription().get(tIdx).humidity;
×
1354
            if(curHumidity < minHumidity)
×
1355
            {
1356
                minHumidity = curHumidity;
×
1357
                if(minHumidity == 0)
×
1358
                    break;
×
1359
            }
1360
        }
1361
        if(minHumidity)
×
1362
            curNodeResource =
×
1363
              Resource(ResourceType::Water, waterEverywhere ? 7 : helpers::iround<uint8_t>(minHumidity * 7. / 100.));
×
1364
        else
1365
            curNodeResource = Resource(ResourceType::Nothing, 0);
×
1366

1367
        SetResource(pt, curNodeResource);
×
1368
    }
1369
}
×
1370

1371
/// Gründet vom Schiff aus eine neue Kolonie
1372
bool GameWorld::FoundColony(const unsigned harbor_point, const unsigned char player, const unsigned short seaId)
2✔
1373
{
1374
    // Ist es hier überhaupt noch möglich, eine Kolonie zu gründen?
1375
    if(!IsHarborAtSea(harbor_point, seaId) || !IsHarborPointFree(harbor_point, player))
2✔
1376
        return false;
1✔
1377

1378
    MapPoint pos(GetHarborPoint(harbor_point));
1✔
1379
    DestroyNO(pos, false);
1✔
1380

1381
    // Hafenbaustelle errichten
1382
    auto* bs = new noBuildingSite(pos, player);
1✔
1383
    SetNO(pos, bs);
1✔
1384
    AddHarborBuildingSiteFromSea(bs);
1✔
1385

1386
    if(gi)
1✔
1387
        gi->GI_UpdateMinimap(pos);
×
1388

1389
    RecalcTerritory(*bs, TerritoryChangeReason::Build);
1✔
1390
    // BQ neu berechnen (evtl durch RecalcTerritory noch nicht geschehen)
1391
    RecalcBQAroundPointBig(pos);
1✔
1392

1393
    GetNotifications().publish(ExpeditionNote(ExpeditionNote::ColonyFounded, player, pos));
1✔
1394

1395
    return true;
1✔
1396
}
1397

1398
void GameWorld::RemoveHarborBuildingSiteFromSea(noBuildingSite* building_site)
1✔
1399
{
1400
    RTTR_Assert(building_site->GetBuildingType() == BuildingType::HarborBuilding);
1✔
1401
    harbor_building_sites_from_sea.remove(building_site);
1✔
1402
}
1✔
1403

1404
bool GameWorld::IsHarborBuildingSiteFromSea(const noBuildingSite* building_site) const
7✔
1405
{
1406
    return helpers::contains(harbor_building_sites_from_sea, building_site);
7✔
1407
}
1408

1409
std::vector<unsigned> GameWorld::GetUnexploredHarborPoints(const unsigned hbIdToSkip, const unsigned seaId,
8✔
1410
                                                           unsigned playerId) const
1411
{
1412
    std::vector<unsigned> hps;
8✔
1413
    for(unsigned i = 1; i <= GetNumHarborPoints(); ++i)
72✔
1414
    {
1415
        if(i == hbIdToSkip || !IsHarborAtSea(i, seaId))
64✔
1416
            continue;
40✔
1417
        if(CalcVisiblityWithAllies(GetHarborPoint(i), playerId) != Visibility::Visible)
24✔
1418
            hps.push_back(i);
4✔
1419
    }
1420
    return hps;
8✔
1421
}
1422

1423
MapNode& GameWorld::GetNodeWriteable(const MapPoint pt)
270,910✔
1424
{
1425
    return GetNodeInt(pt);
270,910✔
1426
}
1427

1428
void GameWorld::VisibilityChanged(const MapPoint pt, unsigned player, Visibility oldVis, Visibility newVis)
131,848✔
1429
{
1430
    GameWorldBase::VisibilityChanged(pt, player, oldVis, newVis);
131,848✔
1431
    if(oldVis == Visibility::Invisible && newVis == Visibility::Visible && HasLua())
131,848✔
1432
        GetLua().EventExplored(player, pt, GetNode(pt).owner);
12,168✔
1433
    // Minimap Bescheid sagen
1434
    if(gi)
131,848✔
1435
        gi->GI_UpdateMinimap(pt);
×
1436
}
131,848✔
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