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

Return-To-The-Roots / s25client / 21599995779

02 Feb 2026 05:15PM UTC coverage: 50.785% (+0.1%) from 50.648%
21599995779

Pull #1680

github

web-flow
Merge 26465ead4 into dc0ce04f6
Pull Request #1680: Add support for one-sided alliances

56 of 74 new or added lines in 15 files covered. (75.68%)

1220 existing lines in 13 files now uncovered.

22836 of 44966 relevant lines covered (50.79%)

44088.72 hits per line

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

84.56
/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)
190✔
46
{
47
    std::vector<GamePlayer> players;
190✔
48
    players.reserve(playerInfos.size());
190✔
49
    for(unsigned i = 0; i < playerInfos.size(); ++i)
589✔
50
        players.push_back(GamePlayer(i, playerInfos[i], world));
399✔
51
    return players;
190✔
52
}
53

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

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

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

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

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

84
        RecalcBQAroundPointBig(pt);
735✔
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,661✔
120
{
121
    const RoadDir rDir = toRoadDir(pt, dir);
1,661✔
122
    SetRoad(pt, rDir, type);
1,661✔
123

124
    if(gi)
1,661✔
125
        gi->GI_UpdateMinimap(pt);
×
126
}
1,661✔
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,
546✔
187
                          const std::vector<Direction>& route)
188
{
189
    // No routes with less than 2 parts. Actually invalid!
190
    if(route.size() < 2)
546✔
191
    {
192
        RTTR_Assert(false);
×
193
        return;
390✔
194
    }
195

196
    if(!GetSpecObj<noFlag>(start) || GetSpecObj<noFlag>(start)->GetPlayer() != playerId)
546✔
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);
544✔
204
    MapPoint curPt(start);
544✔
205
    for(unsigned i = 0; i + 1 < route.size(); ++i)
1,071✔
206
    {
207
        bool roadOk = roadChecker.IsEdgeOk(curPt, route[i]);
913✔
208
        curPt = GetNeighbour(curPt, route[i]);
913✔
209
        roadOk &= roadChecker.IsNodeOk(curPt);
913✔
210
        if(!roadOk)
913✔
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());
158✔
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)
158✔
223
    {
224
        // Wrong player?
225
        if(GetSpecObj<noFlag>(curPt)->GetPlayer() != playerId)
84✔
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))
74✔
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);
72✔
240
    }
241

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

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

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

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

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

264
    // Tell the economy that a new road has been built
265
    GetPlayer(playerId).NewRoadConnection(rs);
156✔
266
    GetNotifications().publish(RoadNote(RoadNote::Constructed, playerId, start, route));
156✔
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())
156✔
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
735✔
282
{
283
    const auto* obj = GetSpecObj<noStaticObject>(pt);
735✔
284
    return obj && obj->GetSize() == 0;
735✔
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)
546✔
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);
546✔
297
    startPt -= EXTRA_SPACE;
546✔
298
    areaSize += 2u * Extent(EXTRA_SPACE);
546✔
299
    // We might still be 1 node to big, make sure we have don't exceed the mapsize
300
    areaSize = elMin(areaSize, Extent(GetSize()));
546✔
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)
339,088✔
308
    {
309
        // Make map point
310
        const MapPoint curMapPt = MakeMapPoint(pt + startPt);
326,461✔
311
        const unsigned char owner = GetNode(curMapPt).owner;
326,461✔
312
        BoundaryStones& boundaryStones = GetBoundaryStones(curMapPt);
326,461✔
313

314
        // Is this a border node?
315
        if(owner && IsBorderNode(curMapPt, owner))
326,461✔
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>{})
360,744✔
319
            {
320
                if(bPos == BorderStonePos::OnPoint || IsBorderNode(GetNeighbour(curMapPt, toDirection(bPos)), owner))
120,248✔
321
                    boundaryStones[bPos] = owner;
60,094✔
322
                else
323
                    boundaryStones[bPos] = 0;
60,154✔
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);
296,399✔
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
}
546✔
380

381
void GameWorld::RecalcTerritory(const noBaseBuilding& building, TerritoryChangeReason reason)
533✔
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(
533✔
387
      (building.GetBuildingType() == BuildingType::HarborBuilding && dynamic_cast<const noBuildingSite*>(&building))
388
      || dynamic_cast<const nobBaseMilitary*>(&building));
389
    const unsigned militaryRadius = building.GetMilitaryRadius();
533✔
390
    RTTR_Assert(militaryRadius > 0u);
533✔
391

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

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

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

404
        // If nothing changed, there is nothing to do (ownerChanged was already initialized)
405
        if(oldOwner == newOwner)
227,092✔
406
            continue;
125,422✔
407

408
        SetOwner(curMapPt, newOwner);
101,670✔
409
        ptsWithChangedOwners.push_back(curMapPt);
101,670✔
410
        if(newOwner != 0)
101,670✔
411
            sizeChanges[newOwner - 1]++;
93,056✔
412
        if(oldOwner != 0)
101,670✔
413
            sizeChanges[oldOwner - 1]--;
11,731✔
414
    }
415

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

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

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

428
    // Destroy remaining roads going through non-owned and border territory
429
    for(const MapPoint& curMapPt : ptsToHandle)
125,958✔
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)
125,425✔
434
            continue;
125,424✔
435
        Direction dir;
436
        noFlag* flag = GetRoadFlag(curMapPt, dir);
123,576✔
437

438
        if(!flag)
123,576✔
439
            continue;
123,222✔
440

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

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

448
    // Notify
449
    for(const MapPoint& curMapPt : ptsWithChangedOwners)
102,203✔
450
        GetNotifications().publish(NodeNote(NodeNote::Owner, curMapPt));
101,670✔
451

452
    for(const MapPoint& pt : ptsToHandle)
125,958✔
453
    {
454
        // BQ neu berechnen
455
        RecalcBQ(pt);
125,425✔
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);
125,425✔
458
        if(GetNode(neighbourPt).bq != BuildingQuality::Nothing)
125,425✔
459
            RecalcBQ(neighbourPt);
99,150✔
460
    }
461

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

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

472
    // Notify players
473
    for(unsigned i = 0; i < GetNumPlayers(); ++i)
1,808✔
474
    {
475
        GetPlayer(i).ChangeStatisticValue(StatisticType::Country, sizeChanges[i]);
1,275✔
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)
1,275✔
479
        {
480
            GetPostMgr().SendMsg(i, std::make_unique<PostMsgWithBuilding>(GetEvMgr().GetCurrentGF(),
240✔
481
                                                                          _("Lost land by this building"),
120✔
482
                                                                          PostCategory::Military, building));
120✔
483
            GetNotifications().publish(
240✔
484
              BuildingNote(BuildingNote::LostLand, i, building.GetPos(), building.GetBuildingType()));
240✔
485
        }
486
    }
487

488
    // Notify script
489
    if(HasLua())
533✔
490
    {
491
        for(const MapPoint& pt : ptsWithChangedOwners)
8,160✔
492
        {
493
            const uint8_t newOwner = GetNode(pt).owner;
8,130✔
494
            // Event for map scripting
495
            if(newOwner != 0)
8,130✔
496
                GetLua().EventOccupied(newOwner - 1, pt);
7,588✔
497
        }
498
    }
499
}
533✔
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,
533✔
536
                                                 TerritoryChangeReason reason) const
537
{
538
    const MapPoint bldPos = building.GetPos();
533✔
539

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

546
    // Koordinaten erzeugen für TerritoryRegion
547
    const Position startPt = Position(bldPos) - radius2D;
533✔
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()));
533✔
551
    TerritoryRegion region(startPt, size, *this);
533✔
552

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

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

569
    return region;
1,066✔
570
}
571

572
void GameWorld::CleanTerritoryRegion(TerritoryRegion& region, TerritoryChangeReason reason,
533✔
573
                                     const noBaseBuilding& triggerBld) const
574
{
575
    if(GetGGS().isEnabled(AddonId::NO_ALLIED_PUSH))
533✔
576
    {
577
        const bool isHq = triggerBld.GetBuildingType() == BuildingType::Headquarters;
2✔
578
        const auto newOwnerOfTriggerBld = region.GetOwner(region.GetPosFromMapPos(triggerBld.GetPos()));
2✔
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
        // if the TerritoryChangeReason is Build
582
        const auto ownerOfTriggerBld =
583
          isHq && reason == TerritoryChangeReason::Build ? newOwnerOfTriggerBld : GetNode(triggerBld.GetPos()).owner;
2✔
584

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

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

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

611
    // "Cosmetics": Remove points that do not border to territory to avoid edges of border stones
612
    RTTR_FOREACH_PT(Position, region.size)
237,852✔
613
    {
614
        uint8_t owner = region.GetOwner(pt);
227,092✔
615
        if(!owner)
227,092✔
616
            continue;
88,010✔
617
        // Check if any neighbour is fully surrounded by player territory
618
        bool isPlayerTerritoryNear = false;
139,082✔
619
        for(const auto d : helpers::EnumRange<Direction>{})
671,776✔
620
        {
621
            Position neighbour = ::GetNeighbour(pt + region.startPt, d);
196,753✔
622
            if(region.SafeGetOwner(neighbour - region.startPt) != owner)
196,753✔
623
                continue;
23,172✔
624
            // Don't check this point as it is always true
625
            const Direction exceptDir = d + 3u;
173,581✔
626
            if(region.WillBePlayerTerritory(neighbour, owner, exceptDir))
173,581✔
627
            {
628
                isPlayerTerritoryNear = true;
139,029✔
629
                break;
139,029✔
630
            }
631
        }
632

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

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

665
void GameWorld::DestroyPlayerRests(const MapPoint pt, unsigned char newOwner, const noBaseBuilding* exception)
125,425✔
666
{
667
    noBase* no = GetNode(pt).obj;
125,425✔
668
    if(!no || no == exception)
125,425✔
669
        return;
123,537✔
670

671
    // Destroy only flags, buildings and building sites
672
    const NodalObjectType noType = no->GetType();
1,888✔
673
    if(noType != NodalObjectType::Flag && noType != NodalObjectType::Building
1,888✔
674
       && noType != NodalObjectType::Buildingsite)
1,452✔
675
        return;
1,451✔
676

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

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

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

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

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

712
        // Figuren Bescheid sagen
713
        for(noBase& object : GetFigures(nb))
16,988✔
714
        {
715
            if(object.GetType() == NodalObjectType::Figure)
274✔
716
                static_cast<noFigure&>(object).NodeFreed(pt);
162✔
717
        }
718
    }
719
}
686✔
720

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

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

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

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

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

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

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

795
    // Send the soldiers to attack
796
    unsigned curNumSoldiers = 0;
18✔
797

798
    for(PotentialAttacker& pa : potentialAttackers)
54✔
799
    {
800
        if(curNumSoldiers >= soldiers_count)
46✔
801
            break;
10✔
802
        pa.soldier->getHome()->SendAttacker(pa.soldier, *attacked_building);
36✔
803
        curNumSoldiers++;
36✔
804
    }
805

806
    if(curNumSoldiers > 0)
18✔
807
    {
808
        GetPlayer(attacked_building->GetPlayer()).OnAttackedBy(player_attacker);
17✔
809
        if(HasLua())
17✔
810
        {
NEW
811
            GetLua().EventAttack(player_attacker, attacked_building->GetPlayer(), curNumSoldiers);
×
812
        }
813
    }
814
}
815

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

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

842
    // Sort them
843
    if(strong_soldiers)
11✔
844
        std::sort(attackers.begin(), attackers.end(), CmpSeaAttacker<std::greater<>>());
11✔
845
    else
846
        std::sort(attackers.begin(), attackers.end(), CmpSeaAttacker<std::less<>>());
×
847

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

858
    if(counter > 0)
11✔
859
    {
860
        GetPlayer(attacked_building.GetPlayer()).OnAttackedBy(player_attacker);
11✔
861
        if(HasLua())
11✔
862
        {
NEW
863
            GetLua().EventAttack(player_attacker, attacked_building.GetPlayer(), counter);
×
864
        }
865
    }
866
}
867

868
TradePathCache& GameWorld::GetTradePathCache()
25✔
869
{
870
    RTTR_Assert(tradePathCache);
25✔
871
    return *tradePathCache;
25✔
872
}
873

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

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

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

898
        // Kampf
899
        if(object.GetGOT() == GO_Type::Fighting)
3,093✔
900
        {
901
            if(static_cast<const noFighting&>(object).IsActive())
135✔
902
                return false;
23✔
903
        } else if(object.GetGOT() == GO_Type::NofAttacker) // wartende Angreifer
2,958✔
904
        {
905
            if(static_cast<const nofAttacker&>(object).IsBlockingRoads())
290✔
906
                return false;
13✔
907
        }
908
    }
909

910
    // alles ok
911
    return true;
7,983✔
912
}
913

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

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

927
    // Und natürlich in unmittelbarer Umgebung suchen
928
    for(const MapPoint nb : GetNeighbours(pt))
196✔
929
    {
930
        for(auto& fieldFigure : GetFigures(nb))
724✔
931
        {
932
            if(fieldFigure.GetType() == NodalObjectType::Figure)
26✔
933
                figures.push_back(static_cast<noFigure*>(&fieldFigure));
26✔
934
        }
935
    }
936

937
    for(auto& figure : figures)
68✔
938
    {
939
        if(dir && *dir + 3u == static_cast<noFigure*>(figure)->GetCurMoveDir())
40✔
940
        {
941
            if(GetNeighbour(pt, *dir) == static_cast<noFigure*>(figure)->GetPos())
1✔
942
                continue;
1✔
943
        }
944

945
        // Derjenige muss ggf. stoppen, wenn alles auf ihn zutrifft
946
        static_cast<noFigure*>(figure)->StopIfNecessary(pt);
39✔
947
    }
948
}
28✔
949

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

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

976
bool GameWorld::ValidWaitingAroundBuildingPoint(const MapPoint pt, const MapPoint center)
530✔
977
{
978
    // Gültiger Punkt für Figuren?
979
    if(!PathConditionHuman(*this).IsNodeOk(pt))
1,060✔
980
        return false;
131✔
981

982
    // Objekte, die sich hier befinden durchgehen
983
    for(const auto& figure : GetFigures(pt))
1,642✔
984
    {
985
        // Ist hier ein anderer Soldat, der hier ebenfalls wartet?
986
        if(figure.GetGOT() == GO_Type::NofAttacker || figure.GetGOT() == GO_Type::NofAggressivedefender
91✔
987
           || figure.GetGOT() == GO_Type::NofDefender)
91✔
988
        {
989
            const auto state = static_cast<const nofActiveSoldier&>(figure).GetState();
91✔
990
            if(state == nofActiveSoldier::SoldierState::WaitingForFight
91✔
991
               || state == nofActiveSoldier::SoldierState::AttackingWaitingAroundBuilding)
91✔
992
                return false;
68✔
993
        }
994

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

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

1014
    // Objekte, die sich hier befinden durchgehen
1015
    for(const auto& figure : GetFigures(pt))
3,756✔
1016
    {
1017
        // Ist hier ein anderer Soldat, der hier ebenfalls wartet ?
1018
        const GO_Type got = figure.GetGOT();
120✔
1019
        if(got == GO_Type::NofAttacker || got == GO_Type::NofAggressivedefender || got == GO_Type::NofDefender)
120✔
1020
        {
1021
            if(static_cast<const nofActiveSoldier*>(&figure) == &soldier)
74✔
1022
                continue;
22✔
1023
            switch(static_cast<const nofActiveSoldier&>(figure).GetState())
52✔
1024
            {
1025
                default: break;
19✔
1026
                case nofActiveSoldier::SoldierState::WaitingForFight:
33✔
1027
                case nofActiveSoldier::SoldierState::AttackingWaitingAroundBuilding:
1028
                case nofActiveSoldier::SoldierState::AttackingWaitingForDefender:
1029
                case nofActiveSoldier::SoldierState::DefendingWaiting: return false;
76✔
1030
            }
1031
        } else if(got == GO_Type::Fighting) // Oder ein Kampf, der hier tobt?
46✔
1032
        {
1033
            const auto& fight = static_cast<const noFighting&>(figure);
45✔
1034
            // A soldier currently fighting shouldn't look for a new fighting spot
1035
            RTTR_Assert(!fight.IsFighter(soldier));
45✔
1036
            if(fight.IsActive())
45✔
1037
                return false;
43✔
1038
        }
1039
    }
1040
    // Liegt hier was rum auf dem man nicht kämpfen sollte?
1041
    const BlockingManner bm = GetNO(pt)->GetBM();
841✔
1042
    return bm == BlockingManner::None || bm == BlockingManner::Tree || bm == BlockingManner::Flag;
841✔
1043
}
1044

1045
bool GameWorld::IsPointCompletelyVisible(const MapPoint& pt, unsigned char player,
43,164✔
1046
                                         const noBaseBuilding* exception) const
1047
{
1048
    sortedMilitaryBlds buildings = LookForMilitaryBuildings(pt, 3);
86,328✔
1049

1050
    // Sichtbereich von Militärgebäuden
1051
    for(const nobBaseMilitary* milBld : buildings)
244,334✔
1052
    {
1053
        if(milBld->GetPlayer() == player && milBld != exception)
80,533✔
1054
        {
1055
            // Prüfen, obs auch unbesetzt ist
1056
            if(milBld->GetGOT() == GO_Type::NobMilitary)
35,249✔
1057
            {
1058
                if(static_cast<const nobMilitary*>(milBld)->IsNewBuilt())
8,726✔
1059
                    continue;
505✔
1060
            }
1061

1062
            if(CalcDistance(pt, milBld->GetPos()) <= unsigned(milBld->GetMilitaryRadius() + VISUALRANGE_MILITARY))
34,744✔
1063
                return true;
23,112✔
1064
        }
1065
    }
1066

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

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

1084
        // Nicht die Ausnahme wählen
1085
        if(bld == exception)
×
1086
            continue;
×
1087

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

1093
    // Check scouts and soldiers
1094
    const unsigned range = std::max(VISUALRANGE_SCOUT, VISUALRANGE_SOLDIER);
20,052✔
1095
    if(CheckPointsInRadius(
20,052✔
1096
         pt, range,
1097
         [this, player](auto pt, auto distance) { return this->IsScoutingFigureOnNode(pt, player, distance); }, true))
739,101✔
1098
    {
1099
        return true;
101✔
1100
    }
1101
    return IsPointScoutedByShip(pt, player);
19,951✔
1102
}
1103

1104
bool GameWorld::IsScoutingFigureOnNode(const MapPoint& pt, unsigned player, unsigned distance) const
739,101✔
1105
{
1106
    static_assert(VISUALRANGE_SCOUT >= VISUALRANGE_SOLDIER, "Visual range changed. Check loop below!");
1107

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

1138
    return false;
739,000✔
1139
}
1140

1141
bool GameWorld::IsPointScoutedByShip(const MapPoint& pt, unsigned player) const
19,951✔
1142
{
1143
    const std::vector<noShip*>& ships = GetPlayer(player).GetShips();
19,951✔
1144
    for(const noShip* ship : ships)
24,247✔
1145
    {
1146
        unsigned shipDistance = CalcDistance(pt, ship->GetPos());
4,482✔
1147
        if(shipDistance <= ship->GetVisualRange())
4,482✔
1148
            return true;
186✔
1149
    }
1150
    return false;
19,765✔
1151
}
1152

1153
void GameWorld::RecalcVisibility(const MapPoint pt, const unsigned char player, const noBaseBuilding* const exception)
43,164✔
1154
{
1155
    /// Zustand davor merken
1156
    Visibility visibility_before = GetNode(pt).fow[player].visibility;
43,164✔
1157

1158
    /// Herausfinden, ob vollständig sichtbar
1159
    bool visible = IsPointCompletelyVisible(pt, player, exception);
43,164✔
1160

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

1188
void GameWorld::MakeVisible(const MapPoint pt, const unsigned char player)
254,000✔
1189
{
1190
    SetVisibility(pt, player, Visibility::Visible);
254,000✔
1191
}
254,000✔
1192

1193
void GameWorld::RecalcVisibilitiesAroundPoint(const MapPoint pt, const MapCoord radius, const unsigned char player,
145✔
1194
                                              const noBaseBuilding* const exception)
1195
{
1196
    std::vector<MapPoint> pts = GetPointsInRadiusWithCenter(pt, radius);
290✔
1197
    for(const MapPoint& pt : pts)
34,874✔
1198
        RecalcVisibility(pt, player, exception);
34,729✔
1199
}
145✔
1200

1201
/// Setzt die Sichtbarkeiten um einen Punkt auf sichtbar (aus Performancegründen Alternative zu oberem)
1202
void GameWorld::MakeVisibleAroundPoint(const MapPoint pt, const MapCoord radius, const unsigned char player)
622✔
1203
{
1204
    std::vector<MapPoint> pts = GetPointsInRadiusWithCenter(pt, radius);
1,244✔
1205
    for(const MapPoint& curPt : pts)
222,788✔
1206
        MakeVisible(curPt, player);
222,166✔
1207
}
622✔
1208

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

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

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

1270
    // Dasselbe für die zurückgebliebenen Punkte
1271
    // Diese müssen allerdings neu berechnet werden!
1272
    t = pt;
919✔
1273
    Direction anti_moving_dir = moving_dir + 3u;
919✔
1274
    for(MapCoord i = 0; i < radius + 1; ++i)
5,596✔
1275
        t = GetNeighbour(t, anti_moving_dir);
4,677✔
1276

1277
    RecalcVisibility(t, player, nullptr);
919✔
1278
    tt = t;
919✔
1279
    dir = anti_moving_dir + 2u;
919✔
1280
    for(MapCoord i = 0; i < radius; ++i)
4,677✔
1281
    {
1282
        tt = GetNeighbour(tt, dir);
3,758✔
1283
        RecalcVisibility(tt, player, nullptr);
3,758✔
1284
    }
1285

1286
    tt = t;
919✔
1287
    dir = anti_moving_dir - 2u;
919✔
1288
    for(unsigned i = 0; i < radius; ++i)
4,677✔
1289
    {
1290
        tt = GetNeighbour(tt, dir);
3,758✔
1291
        RecalcVisibility(tt, player, nullptr);
3,758✔
1292
    }
1293
}
919✔
1294

1295
bool GameWorld::IsBorderNode(const MapPoint pt, const unsigned char owner) const
252,773✔
1296
{
1297
    return (GetNode(pt).owner == owner && !IsPlayerTerritory(pt, owner));
252,773✔
1298
}
1299

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

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

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

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

1346
    RTTR_FOREACH_PT(MapPoint, GetSize())
×
1347
    {
1348
        Resource curNodeResource = GetNode(pt).resources;
×
1349

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

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

1377
        SetResource(pt, curNodeResource);
×
1378
    }
1379
}
×
1380

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

1388
    MapPoint pos(GetHarborPoint(harbor_point));
1✔
1389
    DestroyNO(pos, false);
1✔
1390

1391
    // Hafenbaustelle errichten
1392
    auto* bs = new noBuildingSite(pos, player);
1✔
1393
    SetNO(pos, bs);
1✔
1394
    AddHarborBuildingSiteFromSea(bs);
1✔
1395

1396
    if(gi)
1✔
1397
        gi->GI_UpdateMinimap(pos);
×
1398

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

1403
    GetNotifications().publish(ExpeditionNote(ExpeditionNote::ColonyFounded, player, pos));
1✔
1404

1405
    return true;
1✔
1406
}
1407

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

1414
bool GameWorld::IsHarborBuildingSiteFromSea(const noBuildingSite* building_site) const
7✔
1415
{
1416
    return helpers::contains(harbor_building_sites_from_sea, building_site);
7✔
1417
}
1418

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

1433
MapNode& GameWorld::GetNodeWriteable(const MapPoint pt)
356,784✔
1434
{
1435
    return GetNodeInt(pt);
356,784✔
1436
}
1437

1438
void GameWorld::VisibilityChanged(const MapPoint pt, unsigned player, Visibility oldVis, Visibility newVis)
174,637✔
1439
{
1440
    GameWorldBase::VisibilityChanged(pt, player, oldVis, newVis);
174,637✔
1441
    if(oldVis == Visibility::Invisible && newVis == Visibility::Visible && HasLua())
174,637✔
1442
        GetLua().EventExplored(player, pt, GetNode(pt).owner);
13,104✔
1443
    // Minimap Bescheid sagen
1444
    if(gi)
174,637✔
1445
        gi->GI_UpdateMinimap(pt);
×
1446
}
174,637✔
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