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

Return-To-The-Roots / s25client / 12323579135

13 Dec 2024 09:52PM UTC coverage: 50.201% (+0.04%) from 50.166%
12323579135

Pull #1680

github

web-flow
Merge b285d7ed2 into 77372c5a4
Pull Request #1680: Add support for one-sided alliances

64 of 81 new or added lines in 15 files covered. (79.01%)

5 existing lines in 2 files now uncovered.

22304 of 44429 relevant lines covered (50.2%)

34742.73 hits per line

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

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

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

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

65
MilitarySquares& GameWorld::GetMilitarySquares()
460✔
66
{
67
    return militarySquares;
460✔
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,547✔
120
{
121
    const RoadDir rDir = toRoadDir(pt, dir);
1,547✔
122
    SetRoad(pt, rDir, type);
1,547✔
123

124
    if(gi)
1,547✔
125
        gi->GI_UpdateMinimap(pt);
×
126
}
1,547✔
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)
441✔
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);
441✔
297
    startPt -= EXTRA_SPACE;
441✔
298
    areaSize += 2u * Extent(EXTRA_SPACE);
441✔
299
    // We might still be 1 node to big, make sure we have don't exceed the mapsize
300
    areaSize = elMin(areaSize, Extent(GetSize()));
441✔
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)
264,059✔
308
    {
309
        // Make map point
310
        const MapPoint curMapPt = MakeMapPoint(pt + startPt);
254,109✔
311
        const unsigned char owner = GetNode(curMapPt).owner;
254,109✔
312
        BoundaryStones& boundaryStones = GetBoundaryStones(curMapPt);
254,109✔
313

314
        // Is this a border node?
315
        if(owner && IsBorderNode(curMapPt, owner))
254,109✔
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>{})
301,488✔
319
            {
320
                if(bPos == BorderStonePos::OnPoint || IsBorderNode(GetNeighbour(curMapPt, toDirection(bPos)), owner))
100,496✔
321
                    boundaryStones[bPos] = owner;
50,220✔
322
                else
323
                    boundaryStones[bPos] = 0;
50,276✔
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);
228,985✔
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
}
441✔
380

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

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

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

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

404
        // If nothing changed, there is nothing to do (ownerChanged was already initialized)
405
        if(oldOwner == newOwner)
179,872✔
406
            continue;
102,211✔
407

408
        SetOwner(curMapPt, newOwner);
77,661✔
409
        ptsWithChangedOwners.push_back(curMapPt);
77,661✔
410
        if(newOwner != 0)
77,661✔
411
            sizeChanges[newOwner - 1]++;
71,357✔
412
        if(oldOwner != 0)
77,661✔
413
            sizeChanges[oldOwner - 1]--;
9,019✔
414
    }
415

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

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

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

428
    // Destroy remaining roads going through non-owned and border territory
429
    for(const MapPoint& curMapPt : ptsToHandle)
96,667✔
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)
96,239✔
434
            continue;
96,238✔
435
        Direction dir;
436
        noFlag* flag = GetRoadFlag(curMapPt, dir);
94,786✔
437

438
        if(!flag)
94,786✔
439
            continue;
94,521✔
440

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

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

448
    // Notify
449
    for(const MapPoint& curMapPt : ptsWithChangedOwners)
78,089✔
450
        GetNotifications().publish(NodeNote(NodeNote::Owner, curMapPt));
77,661✔
451

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

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

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

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

488
    // Notify script
489
    if(HasLua())
428✔
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
}
428✔
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,
428✔
536
                                                 TerritoryChangeReason reason) const
537
{
538
    const MapPoint bldPos = building.GetPos();
428✔
539

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

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

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

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

569
    return region;
856✔
570
}
571

572
void GameWorld::CleanTerritoryRegion(TerritoryRegion& region, TerritoryChangeReason reason,
428✔
573
                                     const noBaseBuilding& triggerBld) const
574
{
575
    if(GetGGS().isEnabled(AddonId::NO_ALLIED_PUSH))
428✔
576
    {
577
        const unsigned char ownerOfTriggerBld = GetNode(triggerBld.GetPos()).owner;
×
578
        const unsigned char newOwnerOfTriggerBld = region.GetOwner(region.GetPosFromMapPos(triggerBld.GetPos()));
×
579

580
        RTTR_FOREACH_PT(Position, region.size)
×
581
        {
582
            const MapPoint curMapPt = MakeMapPoint(pt + region.startPt);
×
583
            const unsigned char oldOwner = GetNode(curMapPt).owner;
×
584
            const unsigned char newOwner = region.GetOwner(pt);
×
585

586
            // If nothing changed, there is nothing to do (ownerChanged was already initialized)
587
            if(oldOwner == newOwner)
×
588
                continue;
×
589

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

607
    // "Cosmetics": Remove points that do not border to territory to avoid edges of border stones
608
    RTTR_FOREACH_PT(Position, region.size)
188,425✔
609
    {
610
        uint8_t owner = region.GetOwner(pt);
179,872✔
611
        if(!owner)
179,872✔
612
            continue;
65,356✔
613
        // Check if any neighbour is fully surrounded by player territory
614
        bool isPlayerTerritoryNear = false;
114,516✔
615
        for(const auto d : helpers::EnumRange<Direction>{})
553,294✔
616
        {
617
            Position neighbour = ::GetNeighbour(pt + region.startPt, d);
162,078✔
618
            if(region.SafeGetOwner(neighbour - region.startPt) != owner)
162,078✔
619
                continue;
19,125✔
620
            // Don't check this point as it is always true
621
            const Direction exceptDir = d + 3u;
142,953✔
622
            if(region.WillBePlayerTerritory(neighbour, owner, exceptDir))
142,953✔
623
            {
624
                isPlayerTerritoryNear = true;
114,463✔
625
                break;
114,463✔
626
            }
627
        }
628

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

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

661
void GameWorld::DestroyPlayerRests(const MapPoint pt, unsigned char newOwner, const noBaseBuilding* exception)
96,239✔
662
{
663
    noBase* no = GetNode(pt).obj;
96,239✔
664
    if(!no || no == exception)
96,239✔
665
        return;
94,757✔
666

667
    // Destroy only flags, buildings and building sites
668
    const NodalObjectType noType = no->GetType();
1,482✔
669
    if(noType != NodalObjectType::Flag && noType != NodalObjectType::Building
1,482✔
670
       && noType != NodalObjectType::Buildingsite)
1,145✔
671
        return;
1,144✔
672

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

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

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

696
    DestroyNO(pt, false);
42✔
697
}
698

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

708
        // Figuren Bescheid sagen
709
        for(noBase& object : GetFigures(nb))
16,336✔
710
        {
711
            if(object.GetType() == NodalObjectType::Figure)
260✔
712
                static_cast<noFigure&>(object).NodeFreed(pt);
159✔
713
        }
714
    }
715
}
659✔
716

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

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

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

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

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

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

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

791
    // Send the soldiers to attack
792
    unsigned curNumSoldiers = 0;
17✔
793

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

802
    if(curNumSoldiers > 0)
17✔
803
    {
804
        GetPlayer(attacked_building->GetPlayer()).OnAttackedBy(player_attacker);
16✔
805
        if(HasLua())
16✔
806
        {
NEW
807
            GetLua().EventAttack(player_attacker, attacked_building->GetPlayer(), curNumSoldiers);
×
808
        }
809
    }
810
}
811

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

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

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

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

854
    if(counter > 0)
11✔
855
    {
856
        GetPlayer(attacked_building.GetPlayer()).OnAttackedBy(player_attacker);
11✔
857
        if(HasLua())
11✔
858
        {
NEW
859
            GetLua().EventAttack(player_attacker, attacked_building.GetPlayer(), counter);
×
860
        }
861
    }
862
}
863

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

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

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

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

894
        // Kampf
895
        if(object.GetGOT() == GO_Type::Fighting)
2,999✔
896
        {
897
            if(static_cast<const noFighting&>(object).IsActive())
122✔
898
                return false;
16✔
899
        } else if(object.GetGOT() == GO_Type::NofAttacker) // wartende Angreifer
2,877✔
900
        {
901
            if(static_cast<const nofAttacker&>(object).IsBlockingRoads())
279✔
902
                return false;
12✔
903
        }
904
    }
905

906
    // alles ok
907
    return true;
7,809✔
908
}
909

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

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

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

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

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

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

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

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

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

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

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

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

1041
bool GameWorld::IsPointCompletelyVisible(const MapPoint& pt, unsigned char player,
38,361✔
1042
                                         const noBaseBuilding* exception) const
1043
{
1044
    sortedMilitaryBlds buildings = LookForMilitaryBuildings(pt, 3);
76,722✔
1045

1046
    // Sichtbereich von Militärgebäuden
1047
    for(const nobBaseMilitary* milBld : buildings)
214,739✔
1048
    {
1049
        if(milBld->GetPlayer() == player && milBld != exception)
72,827✔
1050
        {
1051
            // Prüfen, obs auch unbesetzt ist
1052
            if(milBld->GetGOT() == GO_Type::NobMilitary)
35,136✔
1053
            {
1054
                if(static_cast<const nobMilitary*>(milBld)->IsNewBuilt())
8,613✔
1055
                    continue;
505✔
1056
            }
1057

1058
            if(CalcDistance(pt, milBld->GetPos()) <= unsigned(milBld->GetMilitaryRadius() + VISUALRANGE_MILITARY))
34,631✔
1059
                return true;
22,999✔
1060
        }
1061
    }
1062

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

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

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

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

1089
    // Check scouts and soldiers
1090
    const unsigned range = std::max(VISUALRANGE_SCOUT, VISUALRANGE_SOLDIER);
15,362✔
1091
    if(CheckPointsInRadius(
15,362✔
1092
         pt, range,
1093
         [this, player](auto pt, auto distance) { return this->IsScoutingFigureOnNode(pt, player, distance); }, true))
565,571✔
1094
    {
1095
        return true;
101✔
1096
    }
1097
    return IsPointScoutedByShip(pt, player);
15,261✔
1098
}
1099

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

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

1134
    return false;
565,470✔
1135
}
1136

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

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

1154
    /// Herausfinden, ob vollständig sichtbar
1155
    bool visible = IsPointCompletelyVisible(pt, player, exception);
38,361✔
1156

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

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

1189
void GameWorld::RecalcVisibilitiesAroundPoint(const MapPoint pt, const MapCoord radius, const unsigned char player,
133✔
1190
                                              const noBaseBuilding* const exception)
1191
{
1192
    std::vector<MapPoint> pts = GetPointsInRadiusWithCenter(pt, radius);
266✔
1193
    for(const MapPoint& pt : pts)
30,134✔
1194
        RecalcVisibility(pt, player, exception);
30,001✔
1195
}
133✔
1196

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

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

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

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

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

1273
    RecalcVisibility(t, player, nullptr);
904✔
1274
    tt = t;
904✔
1275
    dir = anti_moving_dir + 2u;
904✔
1276
    for(MapCoord i = 0; i < radius; ++i)
4,632✔
1277
    {
1278
        tt = GetNeighbour(tt, dir);
3,728✔
1279
        RecalcVisibility(tt, player, nullptr);
3,728✔
1280
    }
1281

1282
    tt = t;
904✔
1283
    dir = anti_moving_dir - 2u;
904✔
1284
    for(unsigned i = 0; i < radius; ++i)
4,632✔
1285
    {
1286
        tt = GetNeighbour(tt, dir);
3,728✔
1287
        RecalcVisibility(tt, player, nullptr);
3,728✔
1288
    }
1289
}
904✔
1290

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

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

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

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

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

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

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

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

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

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

1384
    MapPoint pos(GetHarborPoint(harbor_point));
1✔
1385
    DestroyNO(pos, false);
1✔
1386

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

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

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

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

1401
    return true;
1✔
1402
}
1403

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

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

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

1429
MapNode& GameWorld::GetNodeWriteable(const MapPoint pt)
269,778✔
1430
{
1431
    return GetNodeInt(pt);
269,778✔
1432
}
1433

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