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

Return-To-The-Roots / s25client / 22044569181

15 Feb 2026 10:50PM UTC coverage: 50.34% (-0.5%) from 50.826%
22044569181

Pull #1720

github

web-flow
Merge 4dbe54b70 into 6db06730b
Pull Request #1720: Add leather addon

274 of 1055 new or added lines in 65 files covered. (25.97%)

286 existing lines in 28 files now uncovered.

23017 of 45723 relevant lines covered (50.34%)

43559.46 hits per line

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

72.44
/libs/s25main/GamePlayer.cpp
1
// Copyright (C) 2005 - 2026 Settlers Freaks (sf-team at siedler25.org)
2
//
3
// SPDX-License-Identifier: GPL-2.0-or-later
4

5
#include "GamePlayer.h"
6
#include "AddonHelperFunctions.h"
7
#include "Cheats.h"
8
#include "EventManager.h"
9
#include "FindWhConditions.h"
10
#include "GameInterface.h"
11
#include "GlobalGameSettings.h"
12
#include "LeatherLoader.h"
13
#include "RoadSegment.h"
14
#include "SerializedGameData.h"
15
#include "TradePathCache.h"
16
#include "Ware.h"
17
#include "WineLoader.h"
18
#include "addons/const_addons.h"
19
#include "buildings/noBuildingSite.h"
20
#include "buildings/nobHQ.h"
21
#include "buildings/nobHarborBuilding.h"
22
#include "buildings/nobMilitary.h"
23
#include "buildings/nobUsual.h"
24
#include "figures/nofCarrier.h"
25
#include "figures/nofFlagWorker.h"
26
#include "helpers/containerUtils.h"
27
#include "helpers/mathFuncs.h"
28
#include "lua/LuaInterfaceGame.h"
29
#include "notifications/ToolNote.h"
30
#include "pathfinding/RoadPathFinder.h"
31
#include "postSystem/DiplomacyPostQuestion.h"
32
#include "postSystem/PostManager.h"
33
#include "random/Random.h"
34
#include "world/GameWorld.h"
35
#include "world/TradeRoute.h"
36
#include "nodeObjs/noFlag.h"
37
#include "nodeObjs/noShip.h"
38
#include "gameTypes/BuildingCount.h"
39
#include "gameTypes/GoodTypes.h"
40
#include "gameTypes/JobTypes.h"
41
#include "gameTypes/PactTypes.h"
42
#include "gameTypes/VisualSettings.h"
43
#include "gameData/BuildingConsts.h"
44
#include "gameData/BuildingProperties.h"
45
#include "gameData/GoodConsts.h"
46
#include "gameData/SettingTypeConv.h"
47
#include "gameData/ShieldConsts.h"
48
#include "gameData/ToolConsts.h"
49
#include "s25util/Log.h"
50
#include <limits>
51
#include <numeric>
52

53
GamePlayer::GamePlayer(unsigned playerId, const PlayerInfo& playerInfo, GameWorld& world)
399✔
54
    : GamePlayerInfo(playerId, playerInfo), world(world), hqPos(MapPoint::Invalid()), emergency(false)
399✔
55
{
56
    std::fill(building_enabled.begin(), building_enabled.end(), true);
399✔
57

58
    LoadStandardDistribution();
399✔
59
    useCustomBuildOrder_ = false;
399✔
60
    build_order = GetStandardBuildOrder();
399✔
61
    transportPrio = STD_TRANSPORT_PRIO;
399✔
62
    LoadStandardMilitarySettings();
399✔
63
    LoadStandardToolSettings();
399✔
64

65
    // Inventur nullen
66
    global_inventory.clear();
399✔
67

68
    // Statistiken mit 0en füllen
69
    statistic = {};
399✔
70
    statisticCurrentData = {};
399✔
71
    statisticCurrentMerchandiseData = {};
399✔
72

73
    RecalcDistribution();
399✔
74
}
399✔
75

76
void GamePlayer::LoadStandardToolSettings()
399✔
77
{
78
    // metalwork tool request
79

80
    // manually
81
    std::fill(tools_ordered.begin(), tools_ordered.end(), 0u);
399✔
82
    std::fill(tools_ordered_delta.begin(), tools_ordered_delta.end(), 0);
399✔
83

84
    // percentage (tool-settings-window-slider, in 10th percent)
85
    toolsSettings_[Tool::Tongs] = 1;
399✔
86
    toolsSettings_[Tool::Hammer] = 4;
399✔
87
    toolsSettings_[Tool::Axe] = 2;
399✔
88
    toolsSettings_[Tool::Saw] = 5;
399✔
89
    toolsSettings_[Tool::PickAxe] = 7;
399✔
90
    toolsSettings_[Tool::Shovel] = 1;
399✔
91
    toolsSettings_[Tool::Crucible] = 3;
399✔
92
    toolsSettings_[Tool::RodAndLine] = 1;
399✔
93
    toolsSettings_[Tool::Scythe] = 2;
399✔
94
    toolsSettings_[Tool::Cleaver] = 1;
399✔
95
    toolsSettings_[Tool::Rollingpin] = 2;
399✔
96
    toolsSettings_[Tool::Bow] = 1;
399✔
97
}
399✔
98

99
void GamePlayer::LoadStandardMilitarySettings()
399✔
100
{
101
    // military settings (military-window-slider, in 10th percent)
102
    militarySettings_[0] = MILITARY_SETTINGS_SCALE[0]; //-V525
399✔
103
    militarySettings_[1] = 3;
399✔
104
    militarySettings_[2] = MILITARY_SETTINGS_SCALE[2];
399✔
105
    militarySettings_[3] = 3;
399✔
106
    militarySettings_[4] = 0;
399✔
107
    militarySettings_[5] = 1;
399✔
108
    militarySettings_[6] = MILITARY_SETTINGS_SCALE[6];
399✔
109
    militarySettings_[7] = MILITARY_SETTINGS_SCALE[7];
399✔
110
}
399✔
111

112
BuildOrders GamePlayer::GetStandardBuildOrder()
401✔
113
{
114
    BuildOrders ordering;
115

116
    // Baureihenfolge füllen
117
    unsigned curPrio = 0;
401✔
118
    for(const auto bld : helpers::enumRange<BuildingType>())
33,684✔
119
    {
120
        if(bld == BuildingType::Headquarters || !BuildingProperties::IsValid(bld))
16,040✔
121
            continue;
802✔
122

123
        RTTR_Assert(curPrio < ordering.size());
15,238✔
124
        ordering[curPrio] = bld;
15,238✔
125
        ++curPrio;
15,238✔
126
    }
127
    RTTR_Assert(curPrio == ordering.size());
401✔
128
    return ordering;
401✔
129
}
130

131
void GamePlayer::LoadStandardDistribution()
399✔
132
{
133
    // Verteilung mit Standardwerten füllen bei Waren mit nur einem Ziel (wie z.B. Mehl, Holz...)
134
    distribution[GoodType::Flour].client_buildings.push_back(BuildingType::Bakery);
399✔
135
    distribution[GoodType::Gold].client_buildings.push_back(BuildingType::Mint);
399✔
136
    distribution[GoodType::IronOre].client_buildings.push_back(BuildingType::Ironsmelter);
399✔
137
    distribution[GoodType::Ham].client_buildings.push_back(BuildingType::Slaughterhouse);
399✔
138
    distribution[GoodType::Stones].client_buildings.push_back(
399✔
139
      BuildingType::Headquarters); // BuildingType::Headquarters = Baustellen!
399✔
140
    distribution[GoodType::Stones].client_buildings.push_back(BuildingType::Catapult);
399✔
141
    distribution[GoodType::Grapes].client_buildings.push_back(BuildingType::Winery);
399✔
142
    distribution[GoodType::Wine].client_buildings.push_back(BuildingType::Temple);
399✔
143
    distribution[GoodType::Skins].client_buildings.push_back(BuildingType::Tannery);
399✔
144
    distribution[GoodType::Leather].client_buildings.push_back(BuildingType::LeatherWorks);
399✔
145

146
    // Waren mit mehreren möglichen Zielen erstmal nullen, kann dann im Fenster eingestellt werden
147
    for(const auto i : helpers::enumRange<GoodType>())
33,516✔
148
    {
149
        std::fill(distribution[i].percent_buildings.begin(), distribution[i].percent_buildings.end(), 0);
15,960✔
150
        distribution[i].selected_goal = 0;
15,960✔
151
    }
152

153
    // Standardverteilung der Waren
154
    for(const DistributionMapping& mapping : distributionMap)
11,970✔
155
    {
156
        distribution[std::get<0>(mapping)].percent_buildings[std::get<1>(mapping)] = std::get<2>(mapping);
11,571✔
157
    }
158
}
399✔
159

160
GamePlayer::~GamePlayer() = default;
806✔
161

162
void GamePlayer::Serialize(SerializedGameData& sgd) const
17✔
163
{
164
    // PlayerStatus speichern, ehemalig
165
    sgd.PushEnum<uint8_t>(ps);
17✔
166

167
    // Nur richtige Spieler serialisieren
168
    if(ps != PlayerState::Occupied && ps != PlayerState::AI)
17✔
169
        return;
3✔
170

171
    sgd.PushBool(isDefeated);
14✔
172

173
    buildings.Serialize(sgd);
14✔
174

175
    sgd.PushObjectContainer(roads, true);
14✔
176

177
    sgd.PushUnsignedInt(jobs_wanted.size());
14✔
178
    for(const JobNeeded& job : jobs_wanted)
18✔
179
    {
180
        sgd.PushEnum<uint8_t>(job.job);
4✔
181
        sgd.PushObject(job.workplace);
4✔
182
    }
183

184
    sgd.PushObjectContainer(ware_list, true);
14✔
185
    sgd.PushObjectContainer(flagworkers);
14✔
186
    sgd.PushObjectContainer(ships, true);
14✔
187

188
    helpers::pushContainer(sgd, shouldSendDefenderList);
14✔
189
    helpers::pushPoint(sgd, hqPos);
14✔
190

191
    for(const Distribution& dist : distribution)
574✔
192
    {
193
        helpers::pushContainer(sgd, dist.percent_buildings);
560✔
194
        helpers::pushContainer(sgd, dist.client_buildings);
560✔
195
        helpers::pushContainer(sgd, dist.goals);
560✔
196
        sgd.PushUnsignedInt(dist.selected_goal);
560✔
197
    }
198

199
    sgd.PushBool(useCustomBuildOrder_);
14✔
200
    helpers::pushContainer(sgd, build_order);
14✔
201
    helpers::pushContainer(sgd, transportPrio);
14✔
202
    helpers::pushContainer(sgd, militarySettings_);
14✔
203
    helpers::pushContainer(sgd, toolsSettings_);
14✔
204
    helpers::pushContainer(sgd, tools_ordered);
14✔
205
    helpers::pushContainer(sgd, global_inventory.goods);
14✔
206
    helpers::pushContainer(sgd, global_inventory.people);
14✔
207
    helpers::pushContainer(sgd, global_inventory.armoredSoldiers);
14✔
208

209
    // für Statistik
210
    for(const Statistic& curStatistic : statistic)
70✔
211
    {
212
        // normale Statistik
213
        for(const auto& curData : curStatistic.data)
560✔
214
            helpers::pushContainer(sgd, curData);
504✔
215

216
        // Warenstatistik
217
        for(unsigned j = 0; j < NUM_STAT_MERCHANDISE_TYPES; ++j)
840✔
218
            helpers::pushContainer(sgd, curStatistic.merchandiseData[j]);
784✔
219

220
        sgd.PushUnsignedShort(curStatistic.currentIndex);
56✔
221
        sgd.PushUnsignedShort(curStatistic.counter);
56✔
222
    }
223
    helpers::pushContainer(sgd, statisticCurrentData);
14✔
224
    helpers::pushContainer(sgd, statisticCurrentMerchandiseData);
14✔
225

226
    // Serialize Pacts:
227
    for(const auto& playerPacts : pacts)
126✔
228
    {
229
        for(const Pact& pact : playerPacts)
336✔
230
            pact.Serialize(sgd);
224✔
231
    }
232

233
    sgd.PushBool(emergency);
14✔
234
}
235

236
void GamePlayer::Deserialize(SerializedGameData& sgd)
8✔
237
{
238
    std::fill(building_enabled.begin(), building_enabled.end(), true);
8✔
239

240
    // Ehemaligen PS auslesen
241
    auto origin_ps = sgd.Pop<PlayerState>();
8✔
242
    // Nur richtige Spieler serialisieren
243
    if(origin_ps != PlayerState::Occupied && origin_ps != PlayerState::AI)
8✔
244
        return;
1✔
245

246
    isDefeated = sgd.PopBool();
7✔
247
    buildings.Deserialize(sgd);
7✔
248

249
    sgd.PopObjectContainer(roads, GO_Type::Roadsegment);
7✔
250

251
    jobs_wanted.resize(sgd.PopUnsignedInt());
7✔
252
    for(JobNeeded& job : jobs_wanted)
9✔
253
    {
254
        job.job = sgd.Pop<Job>();
2✔
255
        job.workplace = sgd.PopObject<noRoadNode>();
2✔
256
    }
257

258
    if(sgd.GetGameDataVersion() < 2)
7✔
259
        buildings.Deserialize2(sgd);
×
260

261
    sgd.PopObjectContainer(ware_list, GO_Type::Ware);
7✔
262
    sgd.PopObjectContainer(flagworkers);
7✔
263
    sgd.PopObjectContainer(ships, GO_Type::Ship);
7✔
264

265
    sgd.PopContainer(shouldSendDefenderList);
7✔
266

267
    hqPos = sgd.PopMapPoint();
7✔
268

269
    for(const auto i : helpers::enumRange<GoodType>())
588✔
270
    {
271
        if(sgd.GetGameDataVersion() < 11 && wineaddon::isWineAddonGoodType(i))
280✔
272
            continue;
×
273

274
        if(sgd.GetGameDataVersion() < 12 && leatheraddon::isLeatherAddonGoodType(i))
280✔
NEW
275
            continue;
×
276

277
        Distribution& dist = distribution[i];
280✔
278
        helpers::popContainer(sgd, dist.percent_buildings);
280✔
279
        // Set standard value otherwise its zero and Slaughterhouse never gets ham
280
        // because the ham distribution was not there in earlier versions
281
        if(sgd.GetGameDataVersion() < 12 && i == GoodType::Ham)
280✔
282
        {
NEW
283
            dist.percent_buildings[BuildingType::Slaughterhouse] = 8;
×
284
        }
285

286
        if(sgd.GetGameDataVersion() < 7)
280✔
287
        {
288
            dist.client_buildings.resize(sgd.PopUnsignedInt());
×
289
            helpers::popContainer(sgd, dist.client_buildings, true);
×
290
            dist.goals.resize(sgd.PopUnsignedInt());
×
291
            helpers::popContainer(sgd, dist.goals, true);
×
292
        } else
293
        {
294
            helpers::popContainer(sgd, dist.client_buildings);
280✔
295
            helpers::popContainer(sgd, dist.goals);
280✔
296
        }
297
        dist.selected_goal = sgd.PopUnsignedInt();
280✔
298
    }
299

300
    useCustomBuildOrder_ = sgd.PopBool();
7✔
301

302
    if(sgd.GetGameDataVersion() < 12)
7✔
303
    {
304
        auto countOfNotAvailableBuildingsInSaveGame =
NEW
305
          sgd.GetGameDataVersion() < 11 ? numWineAndLeatherAddonBuildings : numLeatherAddonBuildings;
×
NEW
306
        std::vector<BuildingType> build_order_raw(build_order.size() - countOfNotAvailableBuildingsInSaveGame);
×
307
        helpers::popContainer(sgd, build_order_raw, true);
×
308

NEW
309
        if(sgd.GetGameDataVersion() < 11)
×
NEW
310
            build_order_raw.insert(build_order_raw.end(),
×
NEW
311
                                   {BuildingType::Vineyard, BuildingType::Winery, BuildingType::Temple});
×
312

NEW
313
        if(sgd.GetGameDataVersion() < 12)
×
NEW
314
            build_order_raw.insert(build_order_raw.end(),
×
NEW
315
                                   {BuildingType::Skinner, BuildingType::Tannery, BuildingType::LeatherWorks});
×
316

UNCOV
317
        std::copy(build_order_raw.begin(), build_order_raw.end(), build_order.begin());
×
318

319
        auto countOfNotAvailableGoodsInSaveGame =
NEW
320
          sgd.GetGameDataVersion() < 11 ? numWineAndLeatherAddonGoods : numLeatherAddonGoods;
×
NEW
321
        std::vector<uint8_t> transportPrio_raw(transportPrio.size() - countOfNotAvailableGoodsInSaveGame);
×
322
        helpers::popContainer(sgd, transportPrio_raw, true);
×
323
        std::copy(transportPrio_raw.begin(), transportPrio_raw.end(), transportPrio.begin());
×
NEW
324
        std::transform(transportPrio.begin(), transportPrio.end() - countOfNotAvailableGoodsInSaveGame,
×
325
                       transportPrio.begin(),
NEW
326
                       [](uint8_t& prio) { return prio < transportPrioOfLeatherworks ? prio : prio + 1; });
×
327
    } else
328
    {
329
        helpers::popContainer(sgd, build_order);
7✔
330
        helpers::popContainer(sgd, transportPrio);
7✔
331
    }
332

333
    helpers::popContainer(sgd, militarySettings_);
7✔
334
    helpers::popContainer(sgd, toolsSettings_);
7✔
335

336
    // qx:tools
337
    helpers::popContainer(sgd, tools_ordered);
7✔
338
    tools_ordered_delta = {};
7✔
339

340
    if(sgd.GetGameDataVersion() < 12)
7✔
341
    {
342
        auto countOfNotAvailableGoodsInSaveGame =
NEW
343
          sgd.GetGameDataVersion() < 11 ? numWineAndLeatherAddonGoods : numLeatherAddonGoods;
×
NEW
344
        std::vector<unsigned int> global_inventory_good_raw(global_inventory.goods.size()
×
NEW
345
                                                            - countOfNotAvailableGoodsInSaveGame);
×
346
        helpers::popContainer(sgd, global_inventory_good_raw, true);
×
347
        std::copy(global_inventory_good_raw.begin(), global_inventory_good_raw.end(), global_inventory.goods.begin());
×
348

349
        auto countOfNotAvailableJobsInSaveGame =
NEW
350
          sgd.GetGameDataVersion() < 11 ? numWineAndLeatherAddonJobs : numLeatherAddonJobs;
×
NEW
351
        std::vector<unsigned int> global_inventory_people_raw(global_inventory.people.size()
×
NEW
352
                                                              - countOfNotAvailableJobsInSaveGame);
×
353
        helpers::popContainer(sgd, global_inventory_people_raw, true);
×
354
        std::copy(global_inventory_people_raw.begin(), global_inventory_people_raw.end(),
×
355
                  global_inventory.people.begin());
356

NEW
357
        std::fill(global_inventory.armoredSoldiers.begin(), global_inventory.armoredSoldiers.end(), 0);
×
358
    } else
359
    {
360
        helpers::popContainer(sgd, global_inventory.goods);
7✔
361
        helpers::popContainer(sgd, global_inventory.people);
7✔
362
        helpers::popContainer(sgd, global_inventory.armoredSoldiers);
7✔
363
    }
364

365
    // Visuelle Einstellungen festlegen
366

367
    // für Statistik
368
    for(Statistic& curStatistic : statistic)
35✔
369
    {
370
        // normale Statistik
371
        for(auto& curData : curStatistic.data)
280✔
372
            helpers::popContainer(sgd, curData);
252✔
373

374
        // Warenstatistik
375
        for(unsigned j = 0; j < NUM_STAT_MERCHANDISE_TYPES; ++j)
420✔
376
            helpers::popContainer(sgd, curStatistic.merchandiseData[j]);
392✔
377

378
        curStatistic.currentIndex = sgd.PopUnsignedShort();
28✔
379
        curStatistic.counter = sgd.PopUnsignedShort();
28✔
380
    }
381
    helpers::popContainer(sgd, statisticCurrentData);
7✔
382
    helpers::popContainer(sgd, statisticCurrentMerchandiseData);
7✔
383

384
    // Deserialize Pacts:
385
    for(auto& playerPacts : pacts)
63✔
386
    {
387
        for(Pact& pact : playerPacts)
168✔
388
            pact = GamePlayer::Pact(sgd);
112✔
389
    }
390

391
    emergency = sgd.PopBool();
7✔
392
}
393

394
template<class T_IsWarehouseGood>
395
nobBaseWarehouse* GamePlayer::FindWarehouse(const noRoadNode& start, const T_IsWarehouseGood& isWarehouseGood,
4,318✔
396
                                            bool to_wh, bool use_boat_roads, unsigned* length,
397
                                            const RoadSegment* forbidden) const
398
{
399
    nobBaseWarehouse* best = nullptr;
4,318✔
400

401
    unsigned best_length = std::numeric_limits<unsigned>::max();
4,318✔
402

403
    for(nobBaseWarehouse* wh : buildings.GetStorehouses())
14,817✔
404
    {
405
        // Lagerhaus geeignet?
406
        RTTR_Assert(wh);
10,504✔
407
        if(!isWarehouseGood(*wh))
10,504✔
408
            continue;
8,045✔
409

410
        if(start.GetPos() == wh->GetPos())
2,468✔
411
        {
412
            // We are already there -> Take it
413
            if(length)
5✔
414
                *length = 0;
×
415
            return wh;
5✔
416
        }
417

418
        // now check if there is at least a chance that the next wh is closer than current best because pathfinding
419
        // takes time
420
        if(world.CalcDistance(start.GetPos(), wh->GetPos()) > best_length)
2,463✔
421
            continue;
9✔
422
        // Bei der erlaubten Benutzung von Bootsstraßen Waren-Pathfinding benutzen wenns zu nem Lagerhaus gehn soll
423
        // start <-> ziel tauschen bei der wegfindung
424
        unsigned tlength;
425
        if(world.GetRoadPathFinder().FindPath(to_wh ? start : *wh, to_wh ? *wh : start, use_boat_roads, best_length,
2,454✔
426
                                              forbidden, &tlength))
427
        {
428
            if(tlength < best_length || !best)
439✔
429
            {
430
                best_length = tlength;
435✔
431
                best = wh;
435✔
432
            }
433
        }
434
    }
435

436
    if(length)
4,313✔
437
        *length = best_length;
378✔
438

439
    return best;
4,313✔
440
}
441

442
void GamePlayer::AddBuildingSite(noBuildingSite* bldSite)
28✔
443
{
444
    RTTR_Assert(bldSite->GetPlayer() == GetPlayerId());
28✔
445
    buildings.Add(bldSite);
28✔
446
}
28✔
447

448
void GamePlayer::RemoveBuildingSite(noBuildingSite* bldSite)
15✔
449
{
450
    RTTR_Assert(bldSite->GetPlayer() == GetPlayerId());
15✔
451
    buildings.Remove(bldSite);
15✔
452
}
15✔
453

454
bool GamePlayer::IsHQTent() const
8✔
455
{
456
    if(const nobHQ* hq = GetHQ())
8✔
457
        return hq->IsTent();
8✔
458
    return false;
×
459
}
460

461
void GamePlayer::SetHQIsTent(bool isTent)
6✔
462
{
463
    if(nobHQ* hq = GetHQ())
6✔
464
        hq->SetIsTent(isTent);
5✔
465
}
6✔
466

467
void GamePlayer::AddBuilding(noBuilding* bld, BuildingType bldType)
632✔
468
{
469
    RTTR_Assert(bld->GetPlayer() == GetPlayerId());
632✔
470
    buildings.Add(bld, bldType);
632✔
471
    ChangeStatisticValue(StatisticType::Buildings, 1);
632✔
472

473
    // Order a worker if needed
474
    const auto& description = BLD_WORK_DESC[bldType];
632✔
475
    if(description.job && !isSoldierJob(*description.job))
632✔
476
    {
477
        AddJobWanted(*description.job, bld);
111✔
478
    }
479

480
    if(bldType == BuildingType::HarborBuilding)
632✔
481
    {
482
        // Schiff durchgehen und denen Bescheid sagen
483
        for(noShip* ship : ships)
51✔
484
            ship->NewHarborBuilt(static_cast<nobHarborBuilding*>(bld));
11✔
485
    } else if(bldType == BuildingType::Headquarters)
592✔
486
    {
487
        // If there is more than one HQ, keep the original position.
488
        if(!hqPos.isValid())
354✔
489
            hqPos = bld->GetPos();
345✔
490
    } else if(BuildingProperties::IsMilitary(bldType))
238✔
491
    {
492
        auto* milBld = static_cast<nobMilitary*>(bld);
116✔
493
        // New built? -> Calculate frontier distance
494
        if(milBld->IsNewBuilt())
116✔
495
            milBld->LookForEnemyBuildings();
106✔
496
    }
497
}
632✔
498

499
void GamePlayer::RemoveBuilding(noBuilding* bld, BuildingType bldType)
115✔
500
{
501
    RTTR_Assert(bld->GetPlayer() == GetPlayerId());
115✔
502
    buildings.Remove(bld, bldType);
115✔
503
    ChangeStatisticValue(StatisticType::Buildings, -1);
115✔
504
    if(bldType == BuildingType::HarborBuilding)
115✔
505
    { // Schiffen Bescheid sagen
506
        for(noShip* ship : ships)
27✔
507
            ship->HarborDestroyed(static_cast<nobHarborBuilding*>(bld));
12✔
508
    } else if(bldType == BuildingType::Headquarters)
100✔
509
    {
510
        hqPos = MapPoint::Invalid();
31✔
511
        for(const noBaseBuilding* bld : buildings.GetStorehouses())
31✔
512
        {
513
            if(bld->GetBuildingType() == BuildingType::Headquarters)
1✔
514
            {
515
                hqPos = bld->GetPos();
1✔
516
                break;
1✔
517
            }
518
        }
519
    }
520
    if(BuildingProperties::IsWareHouse(bldType) || BuildingProperties::IsMilitary(bldType))
115✔
521
        TestDefeat();
82✔
522
}
115✔
523

524
void GamePlayer::NewRoadConnection(RoadSegment* rs)
157✔
525
{
526
    // Zu den Straßen hinzufgen, da's ja ne neue ist
527
    roads.push_back(rs);
157✔
528

529
    // Alle Straßen müssen nun gucken, ob sie einen Weg zu einem Warehouse finden
530
    FindCarrierForAllRoads();
157✔
531

532
    // Alle Straßen müssen gucken, ob sie einen Esel bekommen können
533
    for(RoadSegment* rs : roads)
341✔
534
        rs->TryGetDonkey();
184✔
535

536
    // Alle Arbeitsplätze müssen nun gucken, ob sie einen Weg zu einem Lagerhaus mit entsprechender Arbeitskraft finden
537
    FindWarehouseForAllJobs();
157✔
538

539
    // Alle Baustellen müssen nun gucken, ob sie ihr benötigtes Baumaterial bekommen (evtl war vorher die Straße zum
540
    // Lagerhaus unterbrochen
541
    FindMaterialForBuildingSites();
157✔
542

543
    // Alle Lost-Wares müssen gucken, ob sie ein Lagerhaus finden
544
    FindClientForLostWares();
157✔
545

546
    // Alle Militärgebäude müssen ihre Truppen überprüfen und können nun ggf. neue bestellen
547
    // und müssen prüfen, ob sie evtl Gold bekommen
548
    for(nobMilitary* mil : buildings.GetMilitaryBuildings())
176✔
549
    {
550
        mil->RegulateTroops();
19✔
551
        mil->SearchCoins();
19✔
552
    }
553
}
157✔
554

555
void GamePlayer::AddRoad(RoadSegment* rs)
18✔
556
{
557
    roads.push_back(rs);
18✔
558
}
18✔
559

560
void GamePlayer::DeleteRoad(RoadSegment* rs)
103✔
561
{
562
    RTTR_Assert(helpers::contains(roads, rs));
103✔
563
    roads.remove(rs);
103✔
564
}
103✔
565

566
void GamePlayer::FindClientForLostWares()
163✔
567
{
568
    // Alle Lost-Wares müssen gucken, ob sie ein Lagerhaus finden
569
    for(Ware* ware : ware_list)
219✔
570
    {
571
        if(ware->IsLostWare())
56✔
572
        {
573
            if(ware->FindRouteToWarehouse() && ware->IsWaitingAtFlag())
×
574
                ware->CallCarrier();
×
575
        }
576
    }
577
}
163✔
578

579
void GamePlayer::RoadDestroyed()
223✔
580
{
581
    // Alle Waren, die an Flagge liegen und in Lagerhäusern, müssen gucken, ob sie ihr Ziel noch erreichen können, jetzt
582
    // wo eine Straße fehlt
583
    for(auto it = ware_list.begin(); it != ware_list.end();)
233✔
584
    {
585
        Ware* ware = *it;
10✔
586
        if(ware->IsWaitingAtFlag()) // Liegt die Flagge an einer Flagge, muss ihr Weg neu berechnet werden
10✔
587
        {
588
            RoadPathDirection last_next_dir = ware->GetNextDir();
2✔
589
            ware->RecalcRoute();
2✔
590
            // special case: ware was lost some time ago and the new goal is at this flag and not a warehouse,hq,harbor
591
            // and the "flip-route" picked so a carrier would pick up the ware carry it away from goal then back and
592
            // drop  it off at the goal was just destroyed?
593
            // -> try to pick another flip route or tell the goal about failure.
594
            noRoadNode& wareLocation = *ware->GetLocation();
2✔
595
            noBaseBuilding* wareGoal = ware->GetGoal();
2✔
596
            if(wareGoal && ware->GetNextDir() == RoadPathDirection::NorthWest
2✔
597
               && wareLocation.GetPos() == wareGoal->GetFlagPos()
2✔
598
               && ((wareGoal->GetBuildingType() != BuildingType::Storehouse
6✔
599
                    && wareGoal->GetBuildingType() != BuildingType::Headquarters
2✔
600
                    && wareGoal->GetBuildingType() != BuildingType::HarborBuilding)
×
601
                   || wareGoal->GetType() == NodalObjectType::Buildingsite))
2✔
602
            {
603
                Direction newWareDir = Direction::NorthWest;
×
604
                for(auto dir : helpers::EnumRange<Direction>{})
×
605
                {
606
                    dir += 2u; // Need to skip Direction::NorthWest and we used to start with an offset of 2. TODO:
×
607
                               // Increase gameDataVersion and just skip NW
608
                    if(wareLocation.GetRoute(dir))
×
609
                    {
610
                        newWareDir = dir;
×
611
                        break;
×
612
                    }
613
                }
614
                if(newWareDir != Direction::NorthWest)
×
615
                {
616
                    ware->SetNextDir(toRoadPathDirection(newWareDir));
×
617
                } else // no route to goal -> notify goal, try to send ware to a warehouse
618
                {
619
                    ware->NotifyGoalAboutLostWare();
×
620
                    ware->FindRouteToWarehouse();
×
621
                }
622
            }
623
            // end of special case
624

625
            // notify carriers/flags about news if there are any
626
            if(ware->GetNextDir() != last_next_dir)
2✔
627
            {
628
                // notify current flag that transport in the old direction might not longer be required
629
                ware->RemoveWareJobForDir(last_next_dir);
×
630
                if(ware->GetNextDir() != RoadPathDirection::None)
×
631
                    ware->CallCarrier();
×
632
            }
633
        } else if(ware->IsWaitingInWarehouse())
8✔
634
        {
635
            if(!ware->IsRouteToGoal())
×
636
            {
637
                // Das Ziel wird nun nich mehr beliefert
638
                ware->NotifyGoalAboutLostWare();
×
639
                // Ware aus der Warteliste des Lagerhauses entfernen
640
                static_cast<nobBaseWarehouse*>(ware->GetLocation())->CancelWare(ware);
×
641
                // Ware aus der Liste raus
642
                it = ware_list.erase(it);
×
643
                continue;
×
644
            }
645
        } else if(ware->IsWaitingForShip())
8✔
646
        {
647
            // Weg neu berechnen
648
            ware->RecalcRoute();
1✔
649
        }
650

651
        ++it;
10✔
652
    }
653

654
    // Alle Häfen müssen ihre Figuren den Weg überprüfen lassen
655
    for(nobHarborBuilding* hb : buildings.GetHarbors())
240✔
656
    {
657
        hb->ExamineShipRouteOfPeople();
17✔
658
    }
659
}
223✔
660

661
bool GamePlayer::FindCarrierForRoad(RoadSegment& rs) const
186✔
662
{
663
    RTTR_Assert(rs.GetF1() != nullptr && rs.GetF2() != nullptr);
186✔
664
    std::array<unsigned, 2> length;
665
    std::array<nobBaseWarehouse*, 2> best;
666

667
    // Braucht der ein Boot?
668
    if(rs.GetRoadType() == RoadType::Water)
186✔
669
    {
670
        // dann braucht man Träger UND Boot
671
        best[0] = FindWarehouse(*rs.GetF1(), FW::HasWareAndFigure(GoodType::Boat, Job::Helper, false), false, false,
×
672
                                length.data(), &rs);
673
        // 2. Flagge des Weges
674
        best[1] = FindWarehouse(*rs.GetF2(), FW::HasWareAndFigure(GoodType::Boat, Job::Helper, false), false, false,
×
675
                                &length[1], &rs);
×
676
    } else
677
    {
678
        // 1. Flagge des Weges
679
        best[0] = FindWarehouse(*rs.GetF1(), FW::HasFigure(Job::Helper, false), false, false, length.data(), &rs);
186✔
680
        // 2. Flagge des Weges
681
        best[1] = FindWarehouse(*rs.GetF2(), FW::HasFigure(Job::Helper, false), false, false, &length[1], &rs);
186✔
682
    }
683

684
    // überhaupt nen Weg gefunden?
685
    // Welche Flagge benutzen?
686
    if(best[0] && (!best[1] || length[0] < length[1]))
186✔
687
        best[0]->OrderCarrier(*rs.GetF1(), rs);
65✔
688
    else if(best[1])
121✔
689
        best[1]->OrderCarrier(*rs.GetF2(), rs);
42✔
690
    else
691
        return false;
79✔
692
    return true;
107✔
693
}
694

695
bool GamePlayer::IsWarehouseValid(nobBaseWarehouse* wh) const
×
696
{
697
    return helpers::contains(buildings.GetStorehouses(), wh);
×
698
}
699

700
void GamePlayer::RecalcDistribution()
405✔
701
{
702
    GoodType lastWare = GoodType::Nothing;
405✔
703
    for(const DistributionMapping& mapping : distributionMap)
12,150✔
704
    {
705
        if(lastWare == std::get<0>(mapping))
11,745✔
706
            continue;
8,505✔
707
        lastWare = std::get<0>(mapping);
3,240✔
708
        RecalcDistributionOfWare(std::get<0>(mapping));
3,240✔
709
    }
710
}
405✔
711

712
void GamePlayer::RecalcDistributionOfWare(const GoodType ware)
3,240✔
713
{
714
    // Punktesystem zur Verteilung, in der Liste alle Gebäude sammeln, die die Ware wollen
715
    distribution[ware].client_buildings.clear();
3,240✔
716

717
    // 1. Anteile der einzelnen Waren ausrechnen
718

719
    /// Mapping of buildings that want the current ware to its percentage
720
    using BldEntry = std::pair<BuildingType, uint8_t>;
721
    std::vector<BldEntry> bldPercentageMap;
3,240✔
722

723
    unsigned goal_count = 0;
3,240✔
724

725
    for(const auto bld : helpers::enumRange<BuildingType>())
272,160✔
726
    {
727
        uint8_t percentForCurBld = distribution[ware].percent_buildings[bld];
129,600✔
728
        if(percentForCurBld)
129,600✔
729
        {
730
            distribution[ware].client_buildings.push_back(bld);
11,745✔
731
            goal_count += percentForCurBld;
11,745✔
732
            bldPercentageMap.emplace_back(bld, percentForCurBld);
11,745✔
733
        }
734
    }
735

736
    // TODO: evtl noch die counts miteinander kürzen (ggt berechnen)
737

738
    // Array für die Gebäudtypen erstellen
739

740
    std::vector<BuildingType>& wareGoals = distribution[ware].goals;
3,240✔
741
    wareGoals.clear();
3,240✔
742
    wareGoals.reserve(goal_count);
3,240✔
743

744
    // just drop them in the list, the distribution will be handled by going through this list using a prime as step
745
    // (see GameClientPlayer::FindClientForWare)
746
    for(const BldEntry& bldEntry : bldPercentageMap)
14,985✔
747
    {
748
        for(unsigned char i = 0; i < bldEntry.second; ++i)
77,095✔
749
            wareGoals.push_back(bldEntry.first);
65,350✔
750
    }
751

752
    distribution[ware].selected_goal = 0;
3,240✔
753
}
3,240✔
754

755
void GamePlayer::FindCarrierForAllRoads()
180✔
756
{
757
    for(RoadSegment* rs : roads)
396✔
758
    {
759
        if(!rs->hasCarrier(0))
216✔
760
            FindCarrierForRoad(*rs);
164✔
761
    }
762
}
180✔
763

764
void GamePlayer::FindMaterialForBuildingSites()
176✔
765
{
766
    for(noBuildingSite* bldSite : buildings.GetBuildingSites())
199✔
767
        bldSite->OrderConstructionMaterial();
23✔
768
}
176✔
769

770
void GamePlayer::AddJobWanted(const Job job, noRoadNode* workplace)
147✔
771
{
772
    if(!FindWarehouseForJob(job, *workplace))
147✔
773
    {
774
        JobNeeded jn = {job, workplace};
134✔
775
        jobs_wanted.push_back(jn);
134✔
776
    }
777
}
147✔
778

779
void GamePlayer::JobNotWanted(noRoadNode* workplace, bool all)
56✔
780
{
781
    for(auto it = jobs_wanted.begin(); it != jobs_wanted.end();)
60✔
782
    {
783
        if(it->workplace == workplace)
45✔
784
        {
785
            it = jobs_wanted.erase(it);
41✔
786
            if(!all)
41✔
787
                return;
41✔
788
        } else
789
        {
790
            ++it;
4✔
791
        }
792
    }
793
}
794

795
void GamePlayer::OneJobNotWanted(const Job job, noRoadNode* workplace)
2✔
796
{
797
    const auto it = helpers::find_if(
798
      jobs_wanted, [workplace, job](const auto& it) { return it.workplace == workplace && it.job == job; });
2✔
799
    if(it != jobs_wanted.end())
2✔
800
        jobs_wanted.erase(it);
×
801
}
2✔
802

803
void GamePlayer::SendPostMessage(std::unique_ptr<PostMsg> msg)
35✔
804
{
805
    world.GetPostMgr().SendMsg(GetPlayerId(), std::move(msg));
35✔
806
}
35✔
807

808
unsigned GamePlayer::GetToolsOrderedVisual(Tool tool) const
×
809
{
810
    return std::max(0, int(tools_ordered[tool] + tools_ordered_delta[tool]));
×
811
}
812

813
unsigned GamePlayer::GetToolsOrdered(Tool tool) const
369✔
814
{
815
    return tools_ordered[tool];
369✔
816
}
817

818
bool GamePlayer::ChangeToolOrderVisual(Tool tool, int changeAmount) const
×
819
{
820
    if(std::abs(changeAmount) > 100)
×
821
        return false;
×
822
    int newOrderAmount = int(GetToolsOrderedVisual(tool)) + changeAmount;
×
823
    if(newOrderAmount < 0 || newOrderAmount > 100)
×
824
        return false;
×
825
    tools_ordered_delta[tool] += changeAmount;
×
826
    return true;
×
827
}
828

829
unsigned GamePlayer::GetToolPriority(Tool tool) const
271✔
830
{
831
    return toolsSettings_[tool];
271✔
832
}
833

834
void GamePlayer::ToolOrderProcessed(Tool tool)
4✔
835
{
836
    if(tools_ordered[tool])
4✔
837
    {
838
        --tools_ordered[tool];
4✔
839
        world.GetNotifications().publish(ToolNote(ToolNote::OrderCompleted, GetPlayerId()));
4✔
840
    }
841
}
4✔
842

843
bool GamePlayer::FindWarehouseForJob(const Job job, noRoadNode& goal) const
166✔
844
{
845
    // Optimization: return early if building is isolated
846
    if(goal.GetType() == NodalObjectType::Building || goal.GetType() == NodalObjectType::Buildingsite)
166✔
847
    {
848
        if(!static_cast<noBaseBuilding&>(goal).IsConnected())
166✔
849
            return false;
134✔
850
    }
851

852
    nobBaseWarehouse* wh = FindWarehouse(goal, FW::HasFigure(job, true), false, false);
32✔
853

854
    if(wh)
32✔
855
    {
856
        // Es wurde ein Lagerhaus gefunden, wo es den geforderten Beruf gibt, also den Typen zur Arbeit rufen
857
        wh->OrderJob(job, goal, true);
32✔
858
        return true;
32✔
859
    }
860

UNCOV
861
    return false;
×
862
}
863

864
void GamePlayer::FindWarehouseForAllJobs()
179✔
865
{
866
    for(auto it = jobs_wanted.begin(); it != jobs_wanted.end();)
198✔
867
    {
868
        if(FindWarehouseForJob(it->job, *it->workplace))
19✔
869
            it = jobs_wanted.erase(it);
19✔
870
        else
UNCOV
871
            ++it;
×
872
    }
873
}
179✔
874

875
void GamePlayer::FindWarehouseForAllJobs(const Job job)
340✔
876
{
877
    for(auto it = jobs_wanted.begin(); it != jobs_wanted.end();)
340✔
878
    {
879
        if(it->job == job)
×
880
        {
UNCOV
881
            if(FindWarehouseForJob(it->job, *it->workplace))
×
UNCOV
882
                it = jobs_wanted.erase(it);
×
883
            else
UNCOV
884
                ++it;
×
885
        } else
UNCOV
886
            ++it;
×
887
    }
888
}
340✔
889

890
Ware* GamePlayer::OrderWare(const GoodType ware, noBaseBuilding& goal)
113✔
891
{
892
    /// Gibt es ein Lagerhaus mit dieser Ware?
893
    nobBaseWarehouse* wh = FindWarehouse(goal, FW::HasMinWares(ware, 1), false, true);
113✔
894

895
    if(wh)
113✔
896
    {
897
        // Prüfe ob Notfallprogramm aktiv
898
        if(!emergency)
108✔
899
            return wh->OrderWare(ware, goal);
108✔
900
        else
901
        {
902
            // Wenn Notfallprogramm aktiv nur an Holzfäller und Sägewerke Bretter/Steine liefern
UNCOV
903
            if((ware != GoodType::Boards && ware != GoodType::Stones)
×
UNCOV
904
               || goal.GetBuildingType() == BuildingType::Woodcutter || goal.GetBuildingType() == BuildingType::Sawmill)
×
UNCOV
905
                return wh->OrderWare(ware, goal);
×
906
            else
UNCOV
907
                return nullptr;
×
908
        }
909
    } else // no warehouse can deliver the ware -> check all our wares for lost wares that might match the order
910
    {
911
        unsigned bestLength = std::numeric_limits<unsigned>::max();
5✔
912
        Ware* bestWare = nullptr;
5✔
913
        for(Ware* curWare : ware_list)
5✔
914
        {
915
            if(curWare->IsLostWare() && curWare->type == ware)
×
916
            {
917
                // got a lost ware with a road to goal -> find best
UNCOV
918
                unsigned curLength = curWare->CheckNewGoalForLostWare(goal);
×
UNCOV
919
                if(curLength < bestLength)
×
920
                {
921
                    bestLength = curLength;
×
922
                    bestWare = curWare;
×
923
                }
924
            }
925
        }
926
        if(bestWare)
5✔
927
        {
UNCOV
928
            bestWare->SetNewGoalForLostWare(goal);
×
UNCOV
929
            return bestWare;
×
930
        }
931
    }
932
    return nullptr;
5✔
933
}
934

935
nofCarrier* GamePlayer::OrderDonkey(RoadSegment& road) const
3✔
936
{
937
    std::array<unsigned, 2> length;
938
    std::array<nobBaseWarehouse*, 2> best;
939

940
    // 1. Flagge des Weges
941
    best[0] = FindWarehouse(*road.GetF1(), FW::HasFigure(Job::PackDonkey, false), false, false, length.data(), &road);
3✔
942
    // 2. Flagge des Weges
943
    best[1] = FindWarehouse(*road.GetF2(), FW::HasFigure(Job::PackDonkey, false), false, false, &length[1], &road);
3✔
944

945
    // überhaupt nen Weg gefunden?
946
    // Welche Flagge benutzen?
947
    if(best[0] && (!best[1] || length[0] < length[1]))
3✔
948
        return best[0]->OrderDonkey(road, *road.GetF1());
2✔
949
    else if(best[1])
1✔
UNCOV
950
        return best[1]->OrderDonkey(road, *road.GetF2());
×
951
    else
952
        return nullptr;
1✔
953
}
954

955
RoadSegment* GamePlayer::FindRoadForDonkey(noRoadNode& start, noRoadNode** goal)
10✔
956
{
957
    // Bisher höchste Trägerproduktivität und die entsprechende Straße dazu
958
    unsigned best_productivity = 0;
10✔
959
    RoadSegment* best_road = nullptr;
10✔
960
    // Beste Flagge dieser Straße
961
    *goal = nullptr;
10✔
962

963
    for(RoadSegment* roadSeg : roads)
12✔
964
    {
965
        // Braucht die Straße einen Esel?
966
        if(roadSeg->NeedDonkey())
2✔
967
        {
968
            // Beste Flagge von diesem Weg, und beste Wegstrecke
UNCOV
969
            noRoadNode* current_best_goal = nullptr;
×
970
            // Weg zu beiden Flaggen berechnen
971
            unsigned length1, length2;
972
            bool isF1Reachable = world.FindHumanPathOnRoads(start, *roadSeg->GetF1(), &length1, nullptr, roadSeg)
×
973
                                 != RoadPathDirection::None;
×
974
            bool isF2Reachable = world.FindHumanPathOnRoads(start, *roadSeg->GetF2(), &length2, nullptr, roadSeg)
×
UNCOV
975
                                 != RoadPathDirection::None;
×
976

977
            // Wenn man zu einer Flagge nich kommt, die jeweils andere nehmen
978
            if(!isF1Reachable)
×
UNCOV
979
                current_best_goal = (isF2Reachable) ? roadSeg->GetF2() : nullptr;
×
UNCOV
980
            else if(!isF2Reachable)
×
UNCOV
981
                current_best_goal = roadSeg->GetF1();
×
982
            else
983
            {
984
                // ansonsten die kürzeste von beiden
UNCOV
985
                current_best_goal = (length1 < length2) ? roadSeg->GetF1() : roadSeg->GetF2();
×
986
            }
987

988
            // Kein Weg führt hin, nächste Straße bitte
UNCOV
989
            if(!current_best_goal)
×
990
                continue;
×
991

992
            // Jeweiligen Weg bestimmen
993
            unsigned current_best_way = (roadSeg->GetF1() == current_best_goal) ? length1 : length2;
×
994

995
            // Produktivität ausrechnen, *10 die Produktivität + die Wegstrecke, damit die
996
            // auch noch mit einberechnet wird
997
            unsigned current_productivity = 10 * roadSeg->getCarrier(0)->GetProductivity() + current_best_way;
×
998

999
            // Besser als der bisher beste?
UNCOV
1000
            if(current_productivity > best_productivity)
×
1001
            {
1002
                // Dann wird der vom Thron gestoßen
UNCOV
1003
                best_productivity = current_productivity;
×
UNCOV
1004
                best_road = roadSeg;
×
UNCOV
1005
                *goal = current_best_goal;
×
1006
            }
1007
        }
1008
    }
1009

1010
    return best_road;
10✔
1011
}
1012

1013
struct ClientForWare
1014
{
1015
    noBaseBuilding* bld;
1016
    unsigned estimate; // points minus half the optimal distance
1017
    unsigned points;
1018

1019
    ClientForWare(noBaseBuilding* bld, unsigned estimate, unsigned points)
×
1020
        : bld(bld), estimate(estimate), points(points)
×
1021
    {}
×
1022

UNCOV
1023
    bool operator<(const ClientForWare& b) const
×
1024
    {
1025
        // use estimate, points and object id (as tie breaker) for sorting
UNCOV
1026
        if(estimate != b.estimate)
×
UNCOV
1027
            return estimate > b.estimate;
×
UNCOV
1028
        else if(points != b.points)
×
UNCOV
1029
            return points > b.points;
×
1030
        else
UNCOV
1031
            return bld->GetObjId() > b.bld->GetObjId();
×
1032
    }
1033
};
1034

1035
noBaseBuilding* GamePlayer::FindClientForWare(const Ware& ware)
12✔
1036
{
1037
    // If the ware is a coin or an armor, the goal is determined by another logic
1038
    if(ware.type == GoodType::Coins)
12✔
UNCOV
1039
        return FindClientForCoin(ware);
×
1040
    else if(ware.type == GoodType::Armor)
12✔
UNCOV
1041
        return FindClientForArmor(ware);
×
1042

1043
    // Warentyp herausfinden
1044
    GoodType gt = ware.type;
12✔
1045
    // All food is considered fish in the distribution table
1046
    Distribution& wareDistribution =
1047
      (gt == GoodType::Bread || gt == GoodType::Meat) ? distribution[GoodType::Fish] : distribution[gt];
12✔
1048

1049
    std::vector<ClientForWare> possibleClients;
12✔
1050

1051
    const noRoadNode* start = ware.GetLocation();
12✔
1052

1053
    // Bretter und Steine können evtl. auch Häfen für Expeditionen gebrauchen
1054
    if(gt == GoodType::Stones || gt == GoodType::Boards)
12✔
1055
    {
1056
        for(nobHarborBuilding* harbor : buildings.GetHarbors())
8✔
1057
        {
UNCOV
1058
            unsigned points = harbor->CalcDistributionPoints(gt);
×
UNCOV
1059
            if(!points)
×
UNCOV
1060
                continue;
×
1061

UNCOV
1062
            points += 10 * 30; // Verteilung existiert nicht, Expeditionen haben allerdings hohe Priorität
×
UNCOV
1063
            unsigned distance = world.CalcDistance(start->GetPos(), harbor->GetPos()) / 2;
×
UNCOV
1064
            possibleClients.push_back(ClientForWare(harbor, points > distance ? points - distance : 0, points));
×
1065
        }
1066
    }
1067

1068
    for(const auto bldType : wareDistribution.client_buildings)
49✔
1069
    {
1070
        // BuildingType::Headquarters sind Baustellen!!, da HQs ja sowieso nicht gebaut werden können
1071
        if(bldType == BuildingType::Headquarters)
37✔
1072
        {
1073
            // Bei Baustellen die Extraliste abfragen
1074
            for(noBuildingSite* bldSite : buildings.GetBuildingSites())
8✔
1075
            {
1076
                // Optimization: Ignore if unconnected
UNCOV
1077
                if(!bldSite->IsConnected())
×
UNCOV
1078
                    continue;
×
1079

UNCOV
1080
                unsigned points = bldSite->CalcDistributionPoints(gt);
×
UNCOV
1081
                if(!points)
×
1082
                    continue;
×
1083

1084
                points += wareDistribution.percent_buildings[BuildingType::Headquarters] * 30;
×
UNCOV
1085
                unsigned distance = world.CalcDistance(start->GetPos(), bldSite->GetPos()) / 2;
×
1086
                possibleClients.push_back(ClientForWare(bldSite, points > distance ? points - distance : 0, points));
×
1087
            }
1088
        } else
1089
        {
1090
            // Für übrige Gebäude
1091
            for(nobUsual* bld : buildings.GetBuildings(bldType))
29✔
1092
            {
1093
                // Optimization: Ignore if unconnected
1094
                if(!bld->IsConnected())
×
UNCOV
1095
                    continue;
×
1096

1097
                unsigned points = bld->CalcDistributionPoints(gt);
×
1098
                if(!points)
×
UNCOV
1099
                    continue; // Ware not needed
×
1100

UNCOV
1101
                if(!wareDistribution.goals.empty())
×
1102
                {
UNCOV
1103
                    if(bld->GetBuildingType()
×
UNCOV
1104
                       == static_cast<BuildingType>(wareDistribution.goals[wareDistribution.selected_goal]))
×
UNCOV
1105
                        points += 300;
×
UNCOV
1106
                    else if(points >= 300) // avoid overflows (async!)
×
UNCOV
1107
                        points -= 300;
×
1108
                    else
UNCOV
1109
                        points = 0;
×
1110
                }
1111

UNCOV
1112
                unsigned distance = world.CalcDistance(start->GetPos(), bld->GetPos()) / 2;
×
UNCOV
1113
                possibleClients.push_back(ClientForWare(bld, points > distance ? points - distance : 0, points));
×
1114
            }
1115
        }
1116
    }
1117

1118
    // sort our clients, highest score first
1119
    std::sort(possibleClients.begin(), possibleClients.end());
12✔
1120

1121
    noBaseBuilding* lastBld = nullptr;
12✔
1122
    noBaseBuilding* bestBld = nullptr;
12✔
1123
    unsigned best_points = 0;
12✔
1124
    for(auto& possibleClient : possibleClients)
12✔
1125
    {
1126
        unsigned path_length;
1127

1128
        // If our estimate is worse (or equal) best_points, the real value cannot be better.
1129
        // As our list is sorted, further entries cannot be better either, so stop searching.
UNCOV
1130
        if(possibleClient.estimate <= best_points)
×
1131
            break;
×
1132

1133
        // get rid of double building entries. TODO: why are there double entries!?
UNCOV
1134
        if(possibleClient.bld == lastBld)
×
1135
            continue;
×
1136

UNCOV
1137
        lastBld = possibleClient.bld;
×
1138

1139
        // Just to be sure no underflow happens...
1140
        if(possibleClient.points < best_points + 1)
×
UNCOV
1141
            continue;
×
1142

1143
        // Find path ONLY if it may be better. Pathfinding is limited to the worst path score that would lead to a
1144
        // better score. This eliminates the worst case scenario where all nodes in a split road network would be hit by
1145
        // the pathfinding only to conclude that there is no possible path.
UNCOV
1146
        if(world.FindPathForWareOnRoads(*start, *possibleClient.bld, &path_length, nullptr,
×
UNCOV
1147
                                        (possibleClient.points - best_points) * 2 - 1)
×
1148
           != RoadPathDirection::None)
×
1149
        {
UNCOV
1150
            unsigned score = possibleClient.points - (path_length / 2);
×
1151

1152
            // As we have limited our pathfinding to take a maximum of (points - best_points) * 2 - 1 steps,
1153
            // path_length / 2 can at most be points - best_points - 1, so the score will be greater than best_points.
1154
            // :)
UNCOV
1155
            RTTR_Assert(score > best_points);
×
1156

UNCOV
1157
            best_points = score;
×
UNCOV
1158
            bestBld = possibleClient.bld;
×
1159
        }
1160
    }
1161

1162
    if(bestBld && !wareDistribution.goals.empty())
12✔
UNCOV
1163
        wareDistribution.selected_goal =
×
UNCOV
1164
          (wareDistribution.selected_goal + 907) % unsigned(wareDistribution.goals.size());
×
1165

1166
    // Wenn kein Abnehmer gefunden wurde, muss es halt in ein Lagerhaus
1167
    if(!bestBld)
12✔
1168
        bestBld = FindWarehouseForWare(ware);
12✔
1169

1170
    return bestBld;
12✔
1171
}
1172

1173
nobBaseWarehouse* GamePlayer::FindWarehouseForWare(const Ware& ware) const
16✔
1174
{
1175
    // Check whs that collect this ware
1176
    nobBaseWarehouse* wh = FindWarehouse(*ware.GetLocation(), FW::CollectsWare(ware.type), true, true);
16✔
1177
    // If there is none, check those that accept it
1178
    if(!wh)
16✔
1179
    {
1180
        // First find the ones, that do not send it right away (IMPORTANT: This avoids sending a ware to the wh that is
1181
        // sending the ware out)
1182
        wh = FindWarehouse(*ware.GetLocation(), FW::AcceptsWareButNoSend(ware.type), true, true);
16✔
1183
        // The others only if this fails
1184
        if(!wh)
16✔
1185
            wh = FindWarehouse(*ware.GetLocation(), FW::AcceptsWare(ware.type), true, true);
5✔
1186
    }
1187
    return wh;
16✔
1188
}
1189

1190
nobBaseMilitary* GamePlayer::FindClientForCoin(const Ware& ware) const
×
1191
{
UNCOV
1192
    nobBaseMilitary* bb = nullptr;
×
1193
    unsigned best_points = 0, points;
×
1194

1195
    // Militärgebäude durchgehen
UNCOV
1196
    for(nobMilitary* milBld : buildings.GetMilitaryBuildings())
×
1197
    {
1198
        // Optimization: Ignore if unconnected
UNCOV
1199
        if(!milBld->IsConnected())
×
UNCOV
1200
            continue;
×
1201

UNCOV
1202
        points = milBld->CalcCoinsPoints();
×
1203
        // Wenn 0, will er gar keine Münzen (Goldzufuhr gestoppt)
UNCOV
1204
        if(points)
×
1205
        {
1206
            unsigned way_points;
1207

1208
            // Weg dorthin berechnen
UNCOV
1209
            if(world.FindPathForWareOnRoads(*ware.GetLocation(), *milBld, &way_points) != RoadPathDirection::None)
×
1210
            {
1211
                // Die Wegpunkte noch davon abziehen
NEW
1212
                points -= way_points;
×
1213
                // Besser als der bisher Beste?
NEW
1214
                if(points > best_points)
×
1215
                {
NEW
1216
                    best_points = points;
×
NEW
1217
                    bb = milBld;
×
1218
                }
1219
            }
1220
        }
1221
    }
1222

1223
    // Wenn kein Abnehmer gefunden wurde, muss es halt in ein Lagerhaus
NEW
1224
    if(!bb)
×
NEW
1225
        bb = FindWarehouseForWare(ware);
×
1226

NEW
1227
    return bb;
×
1228
}
1229

NEW
1230
nobBaseMilitary* GamePlayer::FindClientForArmor(const Ware& ware) const
×
1231
{
NEW
1232
    nobBaseMilitary* bb = nullptr;
×
NEW
1233
    unsigned best_points = 0, points;
×
1234

NEW
1235
    for(nobMilitary* milBld : buildings.GetMilitaryBuildings())
×
1236
    {
1237
        unsigned way_points;
1238

NEW
1239
        points = milBld->CalcArmorPoints();
×
1240
        // If 0, it does not want any armor (armor delivery stopped)
NEW
1241
        if(points)
×
1242
        {
1243
            // Find the nearest building
UNCOV
1244
            if(world.FindPathForWareOnRoads(*ware.GetLocation(), *milBld, &way_points) != RoadPathDirection::None)
×
1245
            {
UNCOV
1246
                points -= way_points;
×
UNCOV
1247
                if(points > best_points)
×
1248
                {
UNCOV
1249
                    best_points = points;
×
1250
                    bb = milBld;
×
1251
                }
1252
            }
1253
        }
1254
    }
1255

UNCOV
1256
    if(!bb)
×
UNCOV
1257
        bb = FindWarehouseForWare(ware);
×
1258

1259
    return bb;
×
1260
}
1261

1262
unsigned GamePlayer::GetBuidingSitePriority(const noBuildingSite* building_site)
×
1263
{
UNCOV
1264
    if(useCustomBuildOrder_)
×
1265
    {
1266
        // Spezielle Reihenfolge
1267

1268
        // Typ in der Reihenfolge suchen und Position als Priorität zurückgeben
UNCOV
1269
        for(unsigned i = 0; i < build_order.size(); ++i)
×
1270
        {
UNCOV
1271
            if(building_site->GetBuildingType() == build_order[i])
×
UNCOV
1272
                return i;
×
1273
        }
1274
    } else
1275
    {
1276
        // Reihenfolge der Bauaufträge, also was zuerst in Auftrag gegeben wurde, wird zuerst gebaut
UNCOV
1277
        unsigned i = 0;
×
UNCOV
1278
        for(noBuildingSite* bldSite : buildings.GetBuildingSites())
×
1279
        {
UNCOV
1280
            if(building_site == bldSite)
×
UNCOV
1281
                return i;
×
UNCOV
1282
            i++;
×
1283
        }
1284
    }
1285

UNCOV
1286
    LOG.write("GameClientPlayer::GetBuidingSitePriority: ERROR: Buildingsite or type of it not found in the list!\n");
×
UNCOV
1287
    RTTR_Assert(false);
×
1288
    // We may want to multiply this value so don't return the absolute max value
1289
    return std::numeric_limits<unsigned>::max() / 1000;
1290
}
1291

1292
void GamePlayer::ConvertTransportData(const TransportOrders& transport_data)
2✔
1293
{
1294
    for(const auto ware : helpers::EnumRange<GoodType>{})
168✔
1295
        transportPrio[ware] = GetTransportPrioFromOrdering(transport_data, ware);
80✔
1296
}
2✔
1297

1298
bool GamePlayer::IsAlly(const unsigned char playerId) const
12,228✔
1299
{
1300
    // Der Spieler ist ja auch zu sich selber verbündet
1301
    if(GetPlayerId() == playerId)
12,228✔
1302
        return true;
3,917✔
1303
    else
1304
        return GetPactState(PactType::TreatyOfAlliance, playerId) == PactState::Accepted;
8,311✔
1305
}
1306

1307
bool GamePlayer::IsAttackable(const unsigned char playerId) const
897✔
1308
{
1309
    // Verbündete dürfen nicht angegriffen werden
1310
    if(IsAlly(playerId))
897✔
1311
        return false;
44✔
1312
    else
1313
        // Ansonsten darf bei bestehendem Nichtangriffspakt ebenfalls nicht angegriffen werden
1314
        return GetPactState(PactType::NonAgressionPact, playerId) != PactState::Accepted;
853✔
1315
}
1316

1317
void GamePlayer::OrderTroops(nobMilitary& goal, std::array<unsigned, NUM_SOLDIER_RANKS> counts,
33✔
1318
                             unsigned total_max) const
1319
{
1320
    // Solange Lagerhäuser nach Soldaten absuchen, bis entweder keins mehr übrig ist oder alle Soldaten bestellt sind
1321
    nobBaseWarehouse* wh;
1322
    unsigned sum = 0;
33✔
1323
    do
3✔
1324
    {
1325
        std::array<bool, NUM_SOLDIER_RANKS> desiredRanks;
1326
        for(unsigned i = 0; i < NUM_SOLDIER_RANKS; i++)
216✔
1327
            desiredRanks[i] = counts[i] > 0;
180✔
1328

1329
        wh = FindWarehouse(goal, FW::HasAnyMatchingSoldier(desiredRanks), false, false);
36✔
1330
        if(wh)
36✔
1331
        {
1332
            wh->OrderTroops(goal, counts, total_max);
19✔
1333
            sum = std::accumulate(counts.begin(), counts.end(), 0u);
19✔
1334
        }
1335
    } while(total_max && sum && wh);
36✔
1336
}
33✔
1337

1338
void GamePlayer::RegulateAllTroops()
89✔
1339
{
1340
    for(nobMilitary* milBld : buildings.GetMilitaryBuildings())
92✔
1341
        milBld->RegulateTroops();
3✔
1342
}
89✔
1343

1344
/// Prüft von allen Militärgebäuden die Fahnen neu
1345
void GamePlayer::RecalcMilitaryFlags()
48✔
1346
{
1347
    for(nobMilitary* milBld : buildings.GetMilitaryBuildings())
72✔
1348
        milBld->LookForEnemyBuildings(nullptr);
24✔
1349
}
48✔
1350

1351
/// Sucht für Soldaten ein neues Militärgebäude, als Argument wird Referenz auf die
1352
/// entsprechende Soldatenanzahl im Lagerhaus verlangt
1353
void GamePlayer::NewSoldiersAvailable(const unsigned& soldier_count)
152✔
1354
{
1355
    RTTR_Assert(soldier_count > 0);
152✔
1356
    // solange laufen lassen, bis soldier_count = 0, d.h. der Soldat irgendwohin geschickt wurde
1357
    // Zuerst nach unbesetzten Militärgebäude schauen
1358
    for(nobMilitary* milBld : buildings.GetMilitaryBuildings())
169✔
1359
    {
1360
        if(milBld->IsNewBuilt())
17✔
1361
        {
UNCOV
1362
            milBld->RegulateTroops();
×
1363
            // Used that soldier? Go out
UNCOV
1364
            if(!soldier_count)
×
UNCOV
1365
                return;
×
1366
        }
1367
    }
1368

1369
    // Als nächstes Gebäude in Grenznähe
1370
    for(nobMilitary* milBld : buildings.GetMilitaryBuildings())
169✔
1371
    {
1372
        if(milBld->GetFrontierDistance() == FrontierDistance::Near)
17✔
1373
        {
1374
            milBld->RegulateTroops();
17✔
1375
            // Used that soldier? Go out
1376
            if(!soldier_count)
17✔
UNCOV
1377
                return;
×
1378
        }
1379
    }
1380

1381
    // Und den Rest ggf.
1382
    for(nobMilitary* milBld : buildings.GetMilitaryBuildings())
169✔
1383
    {
1384
        // already checked? -> skip
1385
        if(milBld->GetFrontierDistance() == FrontierDistance::Near || milBld->IsNewBuilt())
17✔
1386
            continue;
17✔
UNCOV
1387
        milBld->RegulateTroops();
×
1388
        if(!soldier_count) // used the soldier?
×
UNCOV
1389
            return;
×
1390
    }
1391
}
1392

1393
void GamePlayer::CallFlagWorker(const MapPoint pt, const Job job)
32✔
1394
{
1395
    auto* flag = world.GetSpecObj<noFlag>(pt);
32✔
1396
    if(!flag)
32✔
1397
        return;
2✔
1398
    /// Find wh with given job type (e.g. geologist, scout, ...)
1399
    nobBaseWarehouse* wh = FindWarehouse(*flag, FW::HasFigure(job, true), false, false);
30✔
1400

1401
    if(wh)
30✔
1402
        wh->OrderJob(job, *flag, true);
26✔
1403
}
1404

UNCOV
1405
bool GamePlayer::IsFlagWorker(const nofFlagWorker* flagworker)
×
1406
{
UNCOV
1407
    return helpers::contains(flagworkers, flagworker);
×
1408
}
1409

1410
void GamePlayer::FlagDestroyed(noFlag* flag)
775✔
1411
{
1412
    // Alle durchgehen und ggf. sagen, dass sie keine Flagge mehr haben, wenn das ihre Flagge war, die zerstört wurde
1413
    for(auto it = flagworkers.begin(); it != flagworkers.end();)
775✔
1414
    {
UNCOV
1415
        if((*it)->GetFlag() == flag)
×
1416
        {
UNCOV
1417
            (*it)->LostWork();
×
UNCOV
1418
            it = flagworkers.erase(it);
×
1419
        } else
UNCOV
1420
            ++it;
×
1421
    }
1422
}
775✔
1423

1424
void GamePlayer::RefreshDefenderList()
65✔
1425
{
1426
    shouldSendDefenderList.clear();
65✔
1427
    // Add as many true values as set in the settings, the rest will be false
1428
    for(unsigned i = 0; i < MILITARY_SETTINGS_SCALE[2]; ++i)
390✔
1429
        shouldSendDefenderList.push_back(i < militarySettings_[2]);
325✔
1430
    // und ordentlich schütteln
1431
    RANDOM_SHUFFLE2(shouldSendDefenderList, 0);
65✔
1432
}
65✔
1433

1434
void GamePlayer::ChangeMilitarySettings(const MilitarySettings& military_settings)
65✔
1435
{
1436
    for(unsigned i = 0; i < military_settings.size(); ++i)
585✔
1437
    {
1438
        // Sicherstellen, dass im validen Bereich
1439
        RTTR_Assert(military_settings[i] <= MILITARY_SETTINGS_SCALE[i]);
520✔
1440
        this->militarySettings_[i] = military_settings[i];
520✔
1441
    }
1442
    /// Truppen müssen neu kalkuliert werden
1443
    RegulateAllTroops();
65✔
1444
    /// Die Verteidigungsliste muss erneuert werden
1445
    RefreshDefenderList();
65✔
1446
}
65✔
1447

1448
/// Setzt neue Werkzeugeinstellungen
1449
void GamePlayer::ChangeToolsSettings(const ToolSettings& tools_settings,
6✔
1450
                                     const helpers::EnumArray<int8_t, Tool>& orderChanges)
1451
{
1452
    const bool settingsChanged = toolsSettings_ != tools_settings;
6✔
1453
    toolsSettings_ = tools_settings;
6✔
1454
    if(settingsChanged)
6✔
1455
        world.GetNotifications().publish(ToolNote(ToolNote::SettingsChanged, GetPlayerId()));
4✔
1456

1457
    for(const auto tool : helpers::enumRange<Tool>())
168✔
1458
    {
1459
        tools_ordered[tool] = helpers::clamp(tools_ordered[tool] + orderChanges[tool], 0, 100);
72✔
1460
        tools_ordered_delta[tool] -= orderChanges[tool];
72✔
1461

1462
        if(orderChanges[tool] != 0)
72✔
1463
        {
1464
            LOG.write(">> Committing an order of %1% for tool #%2%(%3%)\n", LogTarget::File) % (int)orderChanges[tool]
8✔
1465
              % static_cast<unsigned>(tool) % _(WARE_NAMES[TOOL_TO_GOOD[tool]]);
8✔
1466
            world.GetNotifications().publish(ToolNote(ToolNote::OrderPlaced, GetPlayerId()));
4✔
1467
        }
1468
    }
1469
}
6✔
1470

1471
/// Setzt neue Verteilungseinstellungen
1472
void GamePlayer::ChangeDistribution(const Distributions& distribution_settings)
6✔
1473
{
1474
    unsigned idx = 0;
6✔
1475
    for(const DistributionMapping& mapping : distributionMap)
180✔
1476
    {
1477
        distribution[std::get<0>(mapping)].percent_buildings[std::get<1>(mapping)] = distribution_settings[idx++];
174✔
1478
    }
1479

1480
    RecalcDistribution();
6✔
1481
}
6✔
1482

1483
/// Setzt neue Baureihenfolge-Einstellungen
1484
void GamePlayer::ChangeBuildOrder(bool useCustomBuildOrder, const BuildOrders& order_data)
2✔
1485
{
1486
    this->useCustomBuildOrder_ = useCustomBuildOrder;
2✔
1487
    this->build_order = order_data;
2✔
1488
}
2✔
1489

1490
bool GamePlayer::ShouldSendDefender()
35✔
1491
{
1492
    // Wenn wir schon am Ende sind, muss die Verteidgungsliste erneuert werden
1493
    if(shouldSendDefenderList.empty())
35✔
UNCOV
1494
        RefreshDefenderList();
×
1495

1496
    bool result = shouldSendDefenderList.back();
35✔
1497
    shouldSendDefenderList.pop_back();
35✔
1498
    return result;
35✔
1499
}
1500

1501
void GamePlayer::TestDefeat()
82✔
1502
{
1503
    // Nicht schon besiegt?
1504
    // Keine Militärgebäude, keine Lagerhäuser (HQ,Häfen) -> kein Land --> verloren
1505
    if(!isDefeated && buildings.GetMilitaryBuildings().empty() && buildings.GetStorehouses().empty())
82✔
1506
        Surrender();
29✔
1507
}
82✔
1508

1509
nobHQ* GamePlayer::GetHQ() const
14✔
1510
{
1511
    const MapPoint& hqPos = GetHQPos();
14✔
1512
    return const_cast<nobHQ*>(hqPos.isValid() ? GetGameWorld().GetSpecObj<nobHQ>(hqPos) : nullptr);
14✔
1513
}
1514

1515
void GamePlayer::Surrender()
32✔
1516
{
1517
    if(isDefeated)
32✔
1518
        return;
1✔
1519

1520
    isDefeated = true;
31✔
1521

1522
    // GUI Bescheid sagen
1523
    if(world.GetGameInterface())
31✔
1524
        world.GetGameInterface()->GI_PlayerDefeated(GetPlayerId());
×
1525
}
1526

1527
void GamePlayer::SetStatisticValue(StatisticType type, unsigned value)
×
1528
{
1529
    statisticCurrentData[type] = value;
×
1530
}
×
1531

1532
void GamePlayer::ChangeStatisticValue(StatisticType type, int change)
2,065✔
1533
{
1534
    RTTR_Assert(change >= 0 || statisticCurrentData[type] >= static_cast<unsigned>(-change));
2,065✔
1535
    statisticCurrentData[type] += change;
2,065✔
1536
}
2,065✔
1537

1538
void GamePlayer::IncreaseMerchandiseStatistic(GoodType type)
4✔
1539
{
1540
    // Einsortieren...
1541
    switch(type)
4✔
1542
    {
UNCOV
1543
        case GoodType::Wood: statisticCurrentMerchandiseData[0]++; break;
×
UNCOV
1544
        case GoodType::Boards: statisticCurrentMerchandiseData[1]++; break;
×
UNCOV
1545
        case GoodType::Stones: statisticCurrentMerchandiseData[2]++; break;
×
UNCOV
1546
        case GoodType::Fish:
×
1547
        case GoodType::Bread:
UNCOV
1548
        case GoodType::Meat: statisticCurrentMerchandiseData[3]++; break;
×
1549
        case GoodType::Water: statisticCurrentMerchandiseData[4]++; break;
×
UNCOV
1550
        case GoodType::Beer: statisticCurrentMerchandiseData[5]++; break;
×
UNCOV
1551
        case GoodType::Coal: statisticCurrentMerchandiseData[6]++; break;
×
UNCOV
1552
        case GoodType::IronOre: statisticCurrentMerchandiseData[7]++; break;
×
1553
        case GoodType::Gold: statisticCurrentMerchandiseData[8]++; break;
×
1554
        case GoodType::Iron: statisticCurrentMerchandiseData[9]++; break;
×
UNCOV
1555
        case GoodType::Coins: statisticCurrentMerchandiseData[10]++; break;
×
1556
        case GoodType::Tongs:
3✔
1557
        case GoodType::Axe:
1558
        case GoodType::Saw:
1559
        case GoodType::PickAxe:
1560
        case GoodType::Hammer:
1561
        case GoodType::Shovel:
1562
        case GoodType::Crucible:
1563
        case GoodType::RodAndLine:
1564
        case GoodType::Scythe:
1565
        case GoodType::Cleaver:
1566
        case GoodType::Rollingpin:
1567
        case GoodType::Bow: statisticCurrentMerchandiseData[11]++; break;
3✔
UNCOV
1568
        case GoodType::ShieldVikings:
×
1569
        case GoodType::ShieldAfricans:
1570
        case GoodType::ShieldRomans:
1571
        case GoodType::ShieldJapanese:
UNCOV
1572
        case GoodType::Sword: statisticCurrentMerchandiseData[12]++; break;
×
UNCOV
1573
        case GoodType::Boat: statisticCurrentMerchandiseData[13]++; break;
×
1574
        default: break;
1✔
1575
    }
1576
}
4✔
1577

1578
/// Calculates current statistics
1579
void GamePlayer::CalcStatistics()
6✔
1580
{
1581
    // Waren aus der Inventur zählen
1582
    statisticCurrentData[StatisticType::Merchandise] = 0;
6✔
1583
    for(const auto i : helpers::enumRange<GoodType>())
504✔
1584
        statisticCurrentData[StatisticType::Merchandise] += global_inventory[i];
240✔
1585

1586
    // Bevölkerung aus der Inventur zählen
1587
    statisticCurrentData[StatisticType::Inhabitants] = 0;
6✔
1588
    for(const auto i : helpers::enumRange<Job>())
480✔
1589
        statisticCurrentData[StatisticType::Inhabitants] += global_inventory[i];
228✔
1590

1591
    // Militär aus der Inventur zählen
1592
    statisticCurrentData[StatisticType::Military] =
12✔
1593
      global_inventory.people[Job::Private] + global_inventory.people[Job::PrivateFirstClass] * 2
6✔
1594
      + global_inventory.people[Job::Sergeant] * 3 + global_inventory.people[Job::Officer] * 4
6✔
1595
      + global_inventory.people[Job::General] * 5;
6✔
1596

1597
    // Produktivität berechnen
1598
    statisticCurrentData[StatisticType::Productivity] = buildings.CalcAverageProductivity();
6✔
1599

1600
    // Total points for tournament games
1601
    statisticCurrentData[StatisticType::Tournament] =
12✔
1602
      statisticCurrentData[StatisticType::Military] + 3 * statisticCurrentData[StatisticType::Vanquished];
6✔
1603
}
6✔
1604

1605
void GamePlayer::StatisticStep()
6✔
1606
{
1607
    CalcStatistics();
6✔
1608

1609
    // 15-min-Statistik ein Feld weiterschieben
1610
    for(const auto i : helpers::enumRange<StatisticType>())
132✔
1611
    {
1612
        statistic[StatisticTime::T15Minutes].data[i][incrStatIndex(statistic[StatisticTime::T15Minutes].currentIndex)] =
54✔
1613
          statisticCurrentData[i];
54✔
1614
    }
1615
    for(unsigned i = 0; i < NUM_STAT_MERCHANDISE_TYPES; ++i)
90✔
1616
    {
1617
        statistic[StatisticTime::T15Minutes]
84✔
1618
          .merchandiseData[i][incrStatIndex(statistic[StatisticTime::T15Minutes].currentIndex)] =
168✔
1619
          statisticCurrentMerchandiseData[i];
84✔
1620
    }
1621
    statistic[StatisticTime::T15Minutes].currentIndex =
6✔
1622
      incrStatIndex(statistic[StatisticTime::T15Minutes].currentIndex);
6✔
1623

1624
    statistic[StatisticTime::T15Minutes].counter++;
6✔
1625

1626
    // Prüfen ob 4mal 15-min-Statistik weitergeschoben wurde, wenn ja: 1-h-Statistik weiterschieben
1627
    // und aktuellen Wert der 15min-Statistik benutzen
1628
    // gleiches für die 4h und 16h Statistik
1629
    for(const auto t : helpers::enumRange<StatisticTime>())
60✔
1630
    {
1631
        if(t == StatisticTime(helpers::MaxEnumValue_v<StatisticTime>))
24✔
1632
            break;
6✔
1633
        const auto nextT = StatisticTime(rttr::enum_cast(t) + 1);
18✔
1634
        if(statistic[t].counter == 4)
18✔
1635
        {
UNCOV
1636
            statistic[t].counter = 0;
×
UNCOV
1637
            for(const auto i : helpers::enumRange<StatisticType>())
×
1638
            {
UNCOV
1639
                statistic[nextT].data[i][incrStatIndex(statistic[nextT].currentIndex)] = statisticCurrentData[i];
×
1640
            }
1641

1642
            // Summe für den Zeitraum berechnen (immer 4 Zeitschritte der jeweils kleineren Statistik)
UNCOV
1643
            for(unsigned i = 0; i < NUM_STAT_MERCHANDISE_TYPES; ++i)
×
1644
            {
UNCOV
1645
                statistic[nextT].merchandiseData[i][incrStatIndex(statistic[nextT].currentIndex)] =
×
UNCOV
1646
                  statisticCurrentMerchandiseData[i]
×
UNCOV
1647
                  + statistic[t].merchandiseData[i][decrStatIndex(statistic[t].currentIndex, 1)]
×
UNCOV
1648
                  + statistic[t].merchandiseData[i][decrStatIndex(statistic[t].currentIndex, 2)]
×
UNCOV
1649
                  + statistic[t].merchandiseData[i][decrStatIndex(statistic[t].currentIndex, 3)];
×
1650
            }
1651

UNCOV
1652
            statistic[nextT].currentIndex = incrStatIndex(statistic[nextT].currentIndex);
×
UNCOV
1653
            statistic[nextT].counter++;
×
1654
        }
1655
    }
1656

1657
    // Warenstatistikzähler nullen
1658
    statisticCurrentMerchandiseData.fill(0);
6✔
1659
}
6✔
1660

1661
GamePlayer::Pact::Pact(SerializedGameData& sgd)
112✔
1662
    : duration(sgd.PopUnsignedInt()), start(sgd.PopUnsignedInt()), accepted(sgd.PopBool()), want_cancel(sgd.PopBool())
112✔
1663
{}
112✔
1664

1665
void GamePlayer::Pact::Serialize(SerializedGameData& sgd) const
224✔
1666
{
1667
    sgd.PushUnsignedInt(duration);
224✔
1668
    sgd.PushUnsignedInt(start);
224✔
1669
    sgd.PushBool(accepted);
224✔
1670
    sgd.PushBool(want_cancel);
224✔
1671
}
224✔
1672

1673
void GamePlayer::PactChanged(const PactType pt)
24✔
1674
{
1675
    // Recheck military flags as the border (to an enemy) might have changed
1676
    RecalcMilitaryFlags();
24✔
1677

1678
    // Ggf. den GUI Bescheid sagen, um Sichtbarkeiten etc. neu zu berechnen
1679
    if(pt == PactType::TreatyOfAlliance)
24✔
1680
    {
1681
        if(world.GetGameInterface())
6✔
UNCOV
1682
            world.GetGameInterface()->GI_TreatyOfAllianceChanged(GetPlayerId());
×
1683
    }
1684
}
24✔
1685

1686
void GamePlayer::SuggestPact(const unsigned char targetPlayerId, const PactType pt, const unsigned duration)
10✔
1687
{
1688
    // Don't try to make pact with self
1689
    if(targetPlayerId == GetPlayerId())
10✔
1690
        return;
1✔
1691

1692
    if(!pacts[targetPlayerId][pt].accepted && duration > 0)
9✔
1693
    {
1694
        pacts[targetPlayerId][pt].duration = duration;
8✔
1695
        pacts[targetPlayerId][pt].start = world.GetEvMgr().GetCurrentGF();
8✔
1696
        GamePlayer targetPlayer = world.GetPlayer(targetPlayerId);
16✔
1697
        if(targetPlayer.isHuman())
8✔
1698
            targetPlayer.SendPostMessage(std::make_unique<DiplomacyPostQuestion>(
6✔
1699
              world.GetEvMgr().GetCurrentGF(), pt, pacts[targetPlayerId][pt].start, *this, duration));
12✔
1700
        else if(world.HasLua())
2✔
1701
            world.GetLua().EventSuggestPact(pt, GetPlayerId(), targetPlayerId, duration);
2✔
1702
    }
1703
}
1704

1705
void GamePlayer::AcceptPact(const unsigned id, const PactType pt, const unsigned char targetPlayer)
21✔
1706
{
1707
    if(!pacts[targetPlayer][pt].accepted && pacts[targetPlayer][pt].duration > 0 && pacts[targetPlayer][pt].start == id)
21✔
1708
    {
1709
        MakePact(pt, targetPlayer, pacts[targetPlayer][pt].duration);
8✔
1710
        world.GetPlayer(targetPlayer).MakePact(pt, GetPlayerId(), pacts[targetPlayer][pt].duration);
8✔
1711
        PactChanged(pt);
8✔
1712
        world.GetPlayer(targetPlayer).PactChanged(pt);
8✔
1713
        if(world.HasLua())
8✔
1714
            world.GetLua().EventPactCreated(pt, GetPlayerId(), targetPlayer, pacts[targetPlayer][pt].duration);
3✔
1715
    }
1716
}
21✔
1717

1718
/// Bündnis (real, d.h. spielentscheidend) abschließen
1719
void GamePlayer::MakePact(const PactType pt, const unsigned char other_player, const unsigned duration)
16✔
1720
{
1721
    pacts[other_player][pt].accepted = true;
16✔
1722
    pacts[other_player][pt].start = world.GetEvMgr().GetCurrentGF();
16✔
1723
    pacts[other_player][pt].duration = duration;
16✔
1724
    pacts[other_player][pt].want_cancel = false;
16✔
1725

1726
    SendPostMessage(
16✔
1727
      std::make_unique<PostMsg>(world.GetEvMgr().GetCurrentGF(), pt, world.GetPlayer(other_player), true));
32✔
1728
}
16✔
1729

1730
/// Zeigt an, ob ein Pakt besteht
1731
PactState GamePlayer::GetPactState(const PactType pt, const unsigned char other_player) const
9,314✔
1732
{
1733
    // Prüfen, ob Bündnis in Kraft ist
1734
    if(pacts[other_player][pt].duration)
9,314✔
1735
    {
1736
        if(!pacts[other_player][pt].accepted)
5,053✔
1737
            return PactState::InProgress;
16✔
1738

1739
        if(pacts[other_player][pt].duration == DURATION_INFINITE
5,037✔
1740
           || world.GetEvMgr().GetCurrentGF() < pacts[other_player][pt].start + pacts[other_player][pt].duration)
5,037✔
1741
            return PactState::Accepted;
5,035✔
1742
    }
1743

1744
    return PactState::None;
4,263✔
1745
}
1746

1747
/// all allied players get a letter with the location
1748
void GamePlayer::NotifyAlliesOfLocation(const MapPoint pt)
6✔
1749
{
1750
    for(unsigned i = 0; i < world.GetNumPlayers(); ++i)
24✔
1751
    {
1752
        if(i != GetPlayerId() && IsAlly(i))
18✔
1753
            world.GetPlayer(i).SendPostMessage(std::make_unique<PostMsg>(
10✔
1754
              world.GetEvMgr().GetCurrentGF(), _("Your ally wishes to notify you of this location"),
5✔
1755
              PostCategory::Diplomacy, pt));
10✔
1756
    }
1757
}
6✔
1758

1759
/// Gibt die verbleibende Dauer zurück, die ein Bündnis noch laufen wird (DURATION_INFINITE = für immer)
1760
unsigned GamePlayer::GetRemainingPactTime(const PactType pt, const unsigned char other_player) const
125✔
1761
{
1762
    if(pacts[other_player][pt].duration)
125✔
1763
    {
1764
        if(pacts[other_player][pt].accepted)
88✔
1765
        {
1766
            if(pacts[other_player][pt].duration == DURATION_INFINITE)
76✔
UNCOV
1767
                return DURATION_INFINITE;
×
1768
            else if(world.GetEvMgr().GetCurrentGF() <= pacts[other_player][pt].start + pacts[other_player][pt].duration)
76✔
1769
                return ((pacts[other_player][pt].start + pacts[other_player][pt].duration)
76✔
1770
                        - world.GetEvMgr().GetCurrentGF());
76✔
1771
        }
1772
    }
1773

1774
    return 0;
49✔
1775
}
1776

1777
/// Gibt Einverständnis, dass dieser Spieler den Pakt auflösen will
1778
/// Falls dieser Spieler einen Bündnisvorschlag gemacht hat, wird dieser dagegen zurückgenommen
1779
void GamePlayer::CancelPact(const PactType pt, const unsigned char otherPlayerIdx)
8✔
1780
{
1781
    // Don't try to cancel pact with self
1782
    if(otherPlayerIdx == GetPlayerId())
8✔
1783
        return;
2✔
1784

1785
    // Besteht bereits ein Bündnis?
1786
    if(pacts[otherPlayerIdx][pt].accepted)
6✔
1787
    {
1788
        // Vermerken, dass der Spieler das Bündnis auflösen will
1789
        pacts[otherPlayerIdx][pt].want_cancel = true;
6✔
1790

1791
        // Will der andere Spieler das Bündnis auch auflösen?
1792
        GamePlayer& otherPlayer = world.GetPlayer(otherPlayerIdx);
6✔
1793
        if(otherPlayer.pacts[GetPlayerId()][pt].want_cancel)
6✔
1794
        {
1795
            // Dann wird das Bündnis aufgelöst
1796
            pacts[otherPlayerIdx][pt].accepted = false;
2✔
1797
            pacts[otherPlayerIdx][pt].duration = 0;
2✔
1798
            pacts[otherPlayerIdx][pt].want_cancel = false;
2✔
1799

1800
            otherPlayer.pacts[GetPlayerId()][pt].accepted = false;
2✔
1801
            otherPlayer.pacts[GetPlayerId()][pt].duration = 0;
2✔
1802
            otherPlayer.pacts[GetPlayerId()][pt].want_cancel = false;
2✔
1803

1804
            // Den Spielern eine Informationsnachricht schicken
1805
            world.GetPlayer(otherPlayerIdx)
2✔
1806
              .SendPostMessage(std::make_unique<PostMsg>(world.GetEvMgr().GetCurrentGF(), pt, *this, false));
2✔
1807
            SendPostMessage(
2✔
1808
              std::make_unique<PostMsg>(world.GetEvMgr().GetCurrentGF(), pt, world.GetPlayer(otherPlayerIdx), false));
4✔
1809
            PactChanged(pt);
2✔
1810
            otherPlayer.PactChanged(pt);
2✔
1811
            if(world.HasLua())
2✔
1812
                world.GetLua().EventPactCanceled(pt, GetPlayerId(), otherPlayerIdx);
1✔
1813
        } else
1814
        {
1815
            // Ansonsten den anderen Spieler fragen, ob der das auch so sieht
1816
            if(otherPlayer.isHuman())
4✔
1817
                otherPlayer.SendPostMessage(std::make_unique<DiplomacyPostQuestion>(
3✔
1818
                  world.GetEvMgr().GetCurrentGF(), pt, pacts[otherPlayerIdx][pt].start, *this));
6✔
1819
            else if(!world.HasLua() || world.GetLua().EventCancelPactRequest(pt, GetPlayerId(), otherPlayerIdx))
1✔
1820
            {
1821
                // AI accepts cancels, if there is no lua-interace
1822
                pacts[otherPlayerIdx][pt].accepted = false;
1✔
1823
                pacts[otherPlayerIdx][pt].duration = 0;
1✔
1824
                pacts[otherPlayerIdx][pt].want_cancel = false;
1✔
1825

1826
                otherPlayer.pacts[GetPlayerId()][pt].accepted = false;
1✔
1827
                otherPlayer.pacts[GetPlayerId()][pt].duration = 0;
1✔
1828
                otherPlayer.pacts[GetPlayerId()][pt].want_cancel = false;
1✔
1829

1830
                if(world.HasLua())
1✔
1831
                    world.GetLua().EventPactCanceled(pt, GetPlayerId(), otherPlayerIdx);
1✔
1832
            }
1833
        }
1834
    } else
1835
    {
1836
        // Es besteht kein Bündnis, also unseren Bündnisvorschlag wieder zurücknehmen
UNCOV
1837
        pacts[otherPlayerIdx][pt].duration = 0;
×
1838
    }
1839
}
1840

1841
void GamePlayer::MakeStartPacts()
66✔
1842
{
1843
    // Reset pacts
1844
    for(unsigned i = 0; i < world.GetNumPlayers(); ++i)
262✔
1845
    {
1846
        for(const auto z : helpers::enumRange<PactType>())
1,568✔
1847
            pacts[i][z] = Pact();
392✔
1848
    }
1849

1850
    // No team -> No pacts
1851
    if(team == Team::None)
66✔
1852
        return;
4✔
1853
    RTTR_Assert(isTeam(team));
62✔
1854

1855
    // Create ally- and non-aggression-pact for all players of same team
1856
    for(unsigned i = 0; i < world.GetNumPlayers(); ++i)
245✔
1857
    {
1858
        if(team != world.GetPlayer(i).team)
183✔
1859
            continue;
75✔
1860
        for(const auto z : helpers::enumRange<PactType>())
864✔
1861
        {
1862
            pacts[i][z].duration = DURATION_INFINITE;
216✔
1863
            pacts[i][z].start = 0;
216✔
1864
            pacts[i][z].accepted = true;
216✔
1865
            pacts[i][z].want_cancel = false;
216✔
1866
        }
1867
    }
1868
}
1869

1870
bool GamePlayer::IsWareRegistred(const Ware& ware)
165✔
1871
{
1872
    return helpers::contains(ware_list, &ware);
165✔
1873
}
1874

1875
bool GamePlayer::IsWareDependent(const Ware& ware)
14✔
1876
{
1877
    for(nobBaseWarehouse* wh : buildings.GetStorehouses())
41✔
1878
    {
1879
        if(wh->IsWareDependent(ware))
27✔
UNCOV
1880
            return true;
×
1881
    }
1882

1883
    return false;
14✔
1884
}
1885

1886
void GamePlayer::IncreaseInventoryWare(const GoodType ware, const unsigned count)
16,367✔
1887
{
1888
    global_inventory.Add(ConvertShields(ware), count);
16,367✔
1889
}
16,367✔
1890

1891
void GamePlayer::DecreaseInventoryWare(const GoodType ware, const unsigned count)
2,533✔
1892
{
1893
    global_inventory.Remove(ConvertShields(ware), count);
2,533✔
1894
}
2,533✔
1895

1896
/// Registriert ein Schiff beim Einwohnermeldeamt
1897
void GamePlayer::RegisterShip(noShip& ship)
21✔
1898
{
1899
    ships.push_back(&ship);
21✔
1900
    // Evtl bekommt das Schiffchen gleich was zu tun?
1901
    GetJobForShip(ship);
21✔
1902
}
21✔
1903

1904
struct ShipForHarbor
1905
{
1906
    noShip* ship;
1907
    uint32_t estimate;
1908

1909
    ShipForHarbor(noShip* ship, uint32_t estimate) : ship(ship), estimate(estimate) {}
18✔
1910

UNCOV
1911
    bool operator<(const ShipForHarbor& b) const
×
1912
    {
1913
        return (estimate < b.estimate) || (estimate == b.estimate && ship->GetObjId() < b.ship->GetObjId());
×
1914
    }
1915
};
1916

1917
/// Schiff für Hafen bestellen
1918
bool GamePlayer::OrderShip(nobHarborBuilding& hb)
32✔
1919
{
1920
    std::vector<ShipForHarbor> sfh;
64✔
1921

1922
    // we need more ships than those that are already on their way? limit search to idle ships
1923
    if(GetShipsToHarbor(hb) < hb.GetNumNeededShips())
32✔
1924
    {
1925
        for(noShip* ship : ships)
64✔
1926
        {
1927
            if(ship->IsIdling() && world.IsHarborAtSea(world.GetHarborPointID(hb.GetPos()), ship->GetSeaID()))
32✔
1928
                sfh.push_back(ShipForHarbor(ship, world.CalcDistance(hb.GetPos(), ship->GetPos())));
18✔
1929
        }
1930
    } else
1931
    {
UNCOV
1932
        for(noShip* ship : ships)
×
1933
        {
UNCOV
1934
            if((ship->IsIdling() && world.IsHarborAtSea(world.GetHarborPointID(hb.GetPos()), ship->GetSeaID()))
×
UNCOV
1935
               || ship->IsGoingToHarbor(hb))
×
1936
            {
1937
                sfh.push_back(ShipForHarbor(ship, world.CalcDistance(hb.GetPos(), ship->GetPos())));
×
1938
            }
1939
        }
1940
    }
1941

1942
    std::sort(sfh.begin(), sfh.end());
32✔
1943

1944
    noShip* best_ship = nullptr;
32✔
1945
    uint32_t best_distance = std::numeric_limits<uint32_t>::max();
32✔
1946
    std::vector<Direction> best_route;
64✔
1947

1948
    for(auto& it : sfh)
41✔
1949
    {
1950
        uint32_t distance;
1951
        std::vector<Direction> route;
18✔
1952

1953
        // the estimate (air-line distance) for this and all other ships in the list is already worse than what we
1954
        // found? disregard the rest
1955
        if(it.estimate >= best_distance)
18✔
UNCOV
1956
            break;
×
1957

1958
        noShip& ship = *it.ship;
18✔
1959

1960
        MapPoint dest = world.GetCoastalPoint(hb.GetHarborPosID(), ship.GetSeaID());
18✔
1961

1962
        // ship already there?
1963
        if(ship.GetPos() == dest)
18✔
1964
        {
1965
            hb.ShipArrived(ship);
9✔
1966
            return (true);
9✔
1967
        }
1968

1969
        if(world.FindShipPathToHarbor(ship.GetPos(), hb.GetHarborPosID(), ship.GetSeaID(), &route, &distance))
9✔
1970
        {
1971
            if(distance < best_distance)
9✔
1972
            {
1973
                best_ship = &ship;
9✔
1974
                best_distance = distance;
9✔
1975
                best_route = route;
9✔
1976
            }
1977
        }
1978
    }
1979

1980
    // only order ships not already on their way
1981
    if(best_ship && best_ship->IsIdling())
23✔
1982
    {
1983
        best_ship->GoToHarbor(hb, best_route);
9✔
1984

1985
        return (true);
9✔
1986
    }
1987

1988
    return (false);
14✔
1989
}
1990

1991
/// Meldet das Schiff wieder ab
UNCOV
1992
void GamePlayer::RemoveShip(noShip* ship)
×
1993
{
UNCOV
1994
    for(unsigned i = 0; i < ships.size(); ++i)
×
1995
    {
UNCOV
1996
        if(ships[i] == ship)
×
1997
        {
UNCOV
1998
            ships.erase(ships.begin() + i);
×
UNCOV
1999
            return;
×
2000
        }
2001
    }
2002
}
2003

2004
/// Versucht, für ein untätiges Schiff eine Arbeit zu suchen
2005
void GamePlayer::GetJobForShip(noShip& ship)
31✔
2006
{
2007
    // Evtl. steht irgendwo eine Expedition an und das Schiff kann diese übernehmen
2008
    nobHarborBuilding* best = nullptr;
31✔
2009
    int best_points = 0;
31✔
2010
    std::vector<Direction> best_route;
31✔
2011

2012
    // Beste Weglänge, die ein Schiff zurücklegen muss, welches gerade nichts zu tun hat
2013
    for(nobHarborBuilding* harbor : buildings.GetHarbors())
64✔
2014
    {
2015
        // Braucht der Hafen noch Schiffe?
2016
        if(harbor->GetNumNeededShips() == 0)
33✔
2017
            continue;
33✔
2018

2019
        // Anzahl der Schiffe ermitteln, die diesen Hafen bereits anfahren
2020
        unsigned ships_coming = GetShipsToHarbor(*harbor);
×
2021

2022
        // Evtl. kommen schon genug?
UNCOV
2023
        if(harbor->GetNumNeededShips() <= ships_coming)
×
UNCOV
2024
            continue;
×
2025

2026
        // liegen wir am gleichen Meer?
UNCOV
2027
        if(world.IsHarborAtSea(harbor->GetHarborPosID(), ship.GetSeaID()))
×
2028
        {
2029
            const MapPoint coastPt = world.GetCoastalPoint(harbor->GetHarborPosID(), ship.GetSeaID());
×
2030

2031
            // Evtl. sind wir schon da?
UNCOV
2032
            if(ship.GetPos() == coastPt)
×
2033
            {
UNCOV
2034
                harbor->ShipArrived(ship);
×
UNCOV
2035
                return;
×
2036
            }
2037

2038
            unsigned length;
2039
            std::vector<Direction> route;
×
2040

UNCOV
2041
            if(world.FindShipPathToHarbor(ship.GetPos(), harbor->GetHarborPosID(), ship.GetSeaID(), &route, &length))
×
2042
            {
2043
                // Punkte ausrechnen
UNCOV
2044
                int points = harbor->GetNeedForShip(ships_coming) - length;
×
UNCOV
2045
                if(points > best_points || !best)
×
2046
                {
UNCOV
2047
                    best = harbor;
×
UNCOV
2048
                    best_points = points;
×
2049
                    best_route = route;
×
2050
                }
2051
            }
2052
        }
2053
    }
2054

2055
    // Einen Hafen gefunden?
2056
    if(best)
31✔
2057
        // Dann bekommt das gleich der Hafen
UNCOV
2058
        ship.GoToHarbor(*best, best_route);
×
2059
}
2060

2061
/// Gibt die ID eines Schiffes zurück
2062
unsigned GamePlayer::GetShipID(const noShip* const ship) const
13✔
2063
{
2064
    for(unsigned i = 0; i < ships.size(); ++i)
13✔
2065
        if(ships[i] == ship)
13✔
2066
            return i;
13✔
2067

UNCOV
2068
    return 0xFFFFFFFF;
×
2069
}
2070

2071
/// Gibt ein Schiff anhand der ID zurück bzw. nullptr, wenn keines mit der ID existiert
2072
noShip* GamePlayer::GetShipByID(const unsigned ship_id) const
20✔
2073
{
2074
    if(ship_id >= ships.size())
20✔
UNCOV
2075
        return nullptr;
×
2076
    else
2077
        return ships[ship_id];
20✔
2078
}
2079

2080
/// Gibt eine Liste mit allen Häfen dieses Spieler zurück, die an ein bestimmtes Meer angrenzen
2081
void GamePlayer::GetHarborsAtSea(std::vector<nobHarborBuilding*>& harbor_buildings, const unsigned short seaId) const
196✔
2082
{
2083
    for(nobHarborBuilding* harbor : buildings.GetHarbors())
638✔
2084
    {
2085
        if(helpers::contains(harbor_buildings, harbor))
442✔
2086
            continue;
215✔
2087

2088
        if(world.IsHarborAtSea(harbor->GetHarborPosID(), seaId))
227✔
2089
            harbor_buildings.push_back(harbor);
227✔
2090
    }
2091
}
196✔
2092

2093
/// Gibt die Anzahl der Schiffe, die einen bestimmten Hafen ansteuern, zurück
2094
unsigned GamePlayer::GetShipsToHarbor(const nobHarborBuilding& hb) const
71✔
2095
{
2096
    unsigned count = 0;
71✔
2097
    for(const auto* ship : ships)
142✔
2098
    {
2099
        if(ship->IsGoingToHarbor(hb))
71✔
2100
            ++count;
5✔
2101
    }
2102

2103
    return count;
71✔
2104
}
2105

2106
/// Sucht einen Hafen in der Nähe, wo dieses Schiff seine Waren abladen kann
2107
/// gibt true zurück, falls erfolgreich
2108
bool GamePlayer::FindHarborForUnloading(noShip* ship, const MapPoint start, unsigned* goal_harborId,
5✔
2109
                                        std::vector<Direction>* route, nobHarborBuilding* exception)
2110
{
2111
    nobHarborBuilding* best = nullptr;
5✔
2112
    unsigned best_distance = 0xffffffff;
5✔
2113

2114
    for(nobHarborBuilding* hb : buildings.GetHarbors())
8✔
2115
    {
2116
        // Bestimmten Hafen ausschließen
2117
        if(hb == exception)
3✔
UNCOV
2118
            continue;
×
2119

2120
        // Prüfen, ob Hafen an das Meer, wo sich das Schiff gerade befindet, angrenzt
2121
        if(!world.IsHarborAtSea(hb->GetHarborPosID(), ship->GetSeaID()))
3✔
UNCOV
2122
            continue;
×
2123

2124
        // Distanz ermitteln zwischen Schiff und Hafen, Schiff kann natürlich auch über Kartenränder fahren
2125
        unsigned distance = world.CalcDistance(ship->GetPos(), hb->GetPos());
3✔
2126

2127
        // Kürzerer Weg als bisher bestes Ziel?
2128
        if(distance < best_distance)
3✔
2129
        {
2130
            best_distance = distance;
3✔
2131
            best = hb;
3✔
2132
        }
2133
    }
2134

2135
    // Hafen gefunden?
2136
    if(best)
5✔
2137
    {
2138
        // Weg dorthin suchen
2139
        route->clear();
3✔
2140
        *goal_harborId = best->GetHarborPosID();
3✔
2141
        const MapPoint coastPt = world.GetCoastalPoint(best->GetHarborPosID(), ship->GetSeaID());
3✔
2142
        if(start == coastPt
3✔
2143
           || world.FindShipPathToHarbor(start, best->GetHarborPosID(), ship->GetSeaID(), route, nullptr))
3✔
2144
            return true;
3✔
2145
    }
2146

2147
    return false;
2✔
2148
}
2149

2150
void GamePlayer::TestForEmergencyProgramm()
×
2151
{
2152
    // we are already defeated, do not even think about an emergency program - it's too late :-(
2153
    if(isDefeated)
×
UNCOV
2154
        return;
×
2155

2156
    // In Lagern vorhandene Bretter und Steine zählen
2157
    unsigned boards = 0;
×
2158
    unsigned stones = 0;
×
2159
    for(nobBaseWarehouse* wh : buildings.GetStorehouses())
×
2160
    {
UNCOV
2161
        boards += wh->GetInventory().goods[GoodType::Boards];
×
UNCOV
2162
        stones += wh->GetInventory().goods[GoodType::Stones];
×
2163
    }
2164

2165
    // Emergency happens, if we have less than 10 boards or stones...
2166
    bool isNewEmergency = boards <= 10 || stones <= 10;
×
2167
    // ...and no woddcutter or sawmill
2168
    isNewEmergency &=
×
2169
      buildings.GetBuildings(BuildingType::Woodcutter).empty() || buildings.GetBuildings(BuildingType::Sawmill).empty();
×
2170

2171
    // Wenn nötig, Notfallprogramm auslösen
UNCOV
2172
    if(isNewEmergency)
×
2173
    {
UNCOV
2174
        if(!emergency)
×
2175
        {
UNCOV
2176
            emergency = true;
×
UNCOV
2177
            SendPostMessage(std::make_unique<PostMsg>(
×
UNCOV
2178
              world.GetEvMgr().GetCurrentGF(), _("The emergency program has been activated."), PostCategory::Economy));
×
2179
        }
2180
    } else
2181
    {
2182
        // Sobald Notfall vorbei, Notfallprogramm beenden, evtl. Baustellen wieder mit Kram versorgen
UNCOV
2183
        if(emergency)
×
2184
        {
UNCOV
2185
            emergency = false;
×
UNCOV
2186
            SendPostMessage(std::make_unique<PostMsg>(world.GetEvMgr().GetCurrentGF(),
×
UNCOV
2187
                                                      _("The emergency program has been deactivated."),
×
UNCOV
2188
                                                      PostCategory::Economy));
×
UNCOV
2189
            FindMaterialForBuildingSites();
×
2190
        }
2191
    }
2192
}
2193

2194
/// Testet die Bündnisse, ob sie nicht schon abgelaufen sind
2195
void GamePlayer::TestPacts()
20✔
2196
{
2197
    for(unsigned i = 0; i < world.GetNumPlayers(); ++i)
80✔
2198
    {
2199
        if(i == GetPlayerId())
60✔
2200
            continue;
20✔
2201

2202
        for(const auto pact : helpers::enumRange<PactType>())
320✔
2203
        {
2204
            // Pact not running
2205
            if(pacts[i][pact].duration == 0)
80✔
2206
                continue;
60✔
2207
            if(GetPactState(pact, i) == PactState::None)
20✔
2208
            {
2209
                // Pact was running but is expired -> Cancel for both players
2210
                pacts[i][pact].duration = 0;
2✔
2211
                pacts[i][pact].accepted = false;
2✔
2212
                GamePlayer& otherPlayer = world.GetPlayer(i);
2✔
2213
                RTTR_Assert(otherPlayer.pacts[GetPlayerId()][pact].duration);
2✔
2214
                RTTR_Assert(otherPlayer.pacts[GetPlayerId()][pact].accepted);
2✔
2215
                otherPlayer.pacts[GetPlayerId()][pact].duration = 0;
2✔
2216
                otherPlayer.pacts[GetPlayerId()][pact].accepted = false;
2✔
2217
                // And notify
2218
                PactChanged(pact);
2✔
2219
                otherPlayer.PactChanged(pact);
2✔
2220
            }
2221
        }
2222
    }
2223
}
20✔
2224

2225
bool GamePlayer::CanBuildCatapult() const
1✔
2226
{
2227
    // Wenn AddonId::LIMIT_CATAPULTS nicht aktiv ist, bauen immer erlaubt
2228
    if(!world.GetGGS().isEnabled(AddonId::LIMIT_CATAPULTS)) //-V807
1✔
2229
        return true;
1✔
2230

UNCOV
2231
    BuildingCount bc = buildings.GetBuildingNums();
×
2232

UNCOV
2233
    unsigned max = 0;
×
2234
    // proportional?
UNCOV
2235
    if(world.GetGGS().getSelection(AddonId::LIMIT_CATAPULTS) == 1)
×
2236
    {
UNCOV
2237
        max = int(bc.buildings[BuildingType::Barracks] * 0.125 + bc.buildings[BuildingType::Guardhouse] * 0.25
×
UNCOV
2238
                  + bc.buildings[BuildingType::Watchtower] * 0.5 + bc.buildings[BuildingType::Fortress]
×
UNCOV
2239
                  + 0.111); // to avoid rounding errors
×
UNCOV
2240
    } else if(world.GetGGS().getSelection(AddonId::LIMIT_CATAPULTS) < 8)
×
2241
    {
UNCOV
2242
        const std::array<unsigned, 6> limits = {{0, 3, 5, 10, 20, 30}};
×
UNCOV
2243
        max = limits[world.GetGGS().getSelection(AddonId::LIMIT_CATAPULTS) - 2];
×
2244
    }
2245

UNCOV
2246
    return bc.buildings[BuildingType::Catapult] + bc.buildingSites[BuildingType::Catapult] < max;
×
2247
}
2248

2249
/// A ship has discovered new hostile territory --> determines if this is new
2250
/// i.e. there is a sufficient distance to older locations
2251
/// Returns true if yes and false if not
2252
bool GamePlayer::ShipDiscoveredHostileTerritory(const MapPoint location)
25✔
2253
{
2254
    // Prüfen, ob Abstand zu bisherigen Punkten nicht zu klein
2255
    for(const auto& enemies_discovered_by_ship : enemies_discovered_by_ships)
25✔
2256
    {
2257
        if(world.CalcDistance(enemies_discovered_by_ship, location) < 30)
24✔
2258
            return false;
24✔
2259
    }
2260

2261
    // Nein? Dann haben wir ein neues Territorium gefunden
2262
    enemies_discovered_by_ships.push_back(location);
1✔
2263

2264
    return true;
1✔
2265
}
2266

2267
/// For debug only
2268
bool GamePlayer::IsDependentFigure(const noFigure& fig)
236✔
2269
{
2270
    for(const nobBaseWarehouse* wh : buildings.GetStorehouses())
580✔
2271
    {
2272
        if(wh->IsDependentFigure(fig))
344✔
UNCOV
2273
            return true;
×
2274
    }
2275
    return false;
236✔
2276
}
2277

2278
std::vector<nobBaseWarehouse*> GamePlayer::GetWarehousesForTrading(const nobBaseWarehouse& goalWh) const
18✔
2279
{
2280
    std::vector<nobBaseWarehouse*> result;
18✔
2281

2282
    // Don't try to trade with us!
2283
    if(goalWh.GetPlayer() == GetPlayerId())
18✔
2284
        return result;
6✔
2285

2286
    const MapPoint goalFlagPos = goalWh.GetFlagPos();
12✔
2287

2288
    TradePathCache& tradePathCache = world.GetTradePathCache();
12✔
2289
    for(nobBaseWarehouse* wh : buildings.GetStorehouses())
24✔
2290
    {
2291
        // Is there a trade path from this warehouse to wh? (flag to flag)
2292
        if(tradePathCache.pathExists(wh->GetFlagPos(), goalFlagPos, GetPlayerId()))
12✔
2293
            result.push_back(wh);
4✔
2294
    }
2295

2296
    return result;
12✔
2297
}
2298

2299
struct WarehouseDistanceComparator
2300
{
2301
    // Reference warehouse position, to which we want to calc the distance
2302
    const MapPoint refWareHousePos_;
2303
    /// GameWorld
2304
    const GameWorld& gwg_;
2305

2306
    WarehouseDistanceComparator(const nobBaseWarehouse& refWareHouse, const GameWorld& world)
15✔
2307
        : refWareHousePos_(refWareHouse.GetPos()), gwg_(world)
15✔
2308
    {}
15✔
2309

UNCOV
2310
    bool operator()(nobBaseWarehouse* const wh1, nobBaseWarehouse* const wh2) const
×
2311
    {
UNCOV
2312
        unsigned dist1 = gwg_.CalcDistance(wh1->GetPos(), refWareHousePos_);
×
UNCOV
2313
        unsigned dist2 = gwg_.CalcDistance(wh2->GetPos(), refWareHousePos_);
×
UNCOV
2314
        return (dist1 < dist2) || (dist1 == dist2 && wh1->GetObjId() < wh2->GetObjId());
×
2315
    }
2316
};
2317

2318
/// Send wares to warehouse wh
2319
void GamePlayer::Trade(nobBaseWarehouse* goalWh, const boost_variant2<GoodType, Job>& what, unsigned count) const
23✔
2320
{
2321
    if(!world.GetGGS().isEnabled(AddonId::TRADE))
23✔
2322
        return;
11✔
2323

2324
    if(count == 0)
20✔
2325
        return;
3✔
2326

2327
    // Don't try to trade with us!
2328
    if(goalWh->GetPlayer() == GetPlayerId())
17✔
2329
        return;
1✔
2330

2331
    // No trades with enemies
2332
    if(!IsAlly(goalWh->GetPlayer()))
16✔
2333
        return;
1✔
2334

2335
    const MapPoint goalFlagPos = goalWh->GetFlagPos();
15✔
2336

2337
    std::vector<nobBaseWarehouse*> whs(buildings.GetStorehouses().begin(), buildings.GetStorehouses().end());
15✔
2338
    std::sort(whs.begin(), whs.end(), WarehouseDistanceComparator(*goalWh, world));
15✔
2339
    TradePathCache& tradePathCache = world.GetTradePathCache();
15✔
2340
    for(nobBaseWarehouse* wh : whs)
27✔
2341
    {
2342
        // Get available wares
2343
        const unsigned available =
2344
          boost::variant2::visit(composeVisitor([wh](GoodType gt) { return wh->GetAvailableWaresForTrading(gt); },
22✔
2345
                                                [wh](Job job) { return wh->GetAvailableFiguresForTrading(job); }),
23✔
2346
                                 what);
15✔
2347
        if(available == 0)
15✔
UNCOV
2348
            continue;
×
2349

2350
        const unsigned actualCount = std::min(available, count);
15✔
2351

2352
        // Find a trade path from flag to flag
2353
        TradeRoute tr(world, GetPlayerId(), wh->GetFlagPos(), goalFlagPos);
15✔
2354

2355
        // Found a path?
2356
        if(tr.IsValid())
15✔
2357
        {
2358
            // Add to cache for future searches
2359
            tradePathCache.addEntry(tr.GetTradePath(), GetPlayerId());
14✔
2360

2361
            wh->StartTradeCaravane(what, actualCount, tr, goalWh);
14✔
2362
            count -= available;
14✔
2363
            if(count == 0)
14✔
2364
                return;
3✔
2365
        }
2366
    }
2367
}
2368

2369
bool GamePlayer::IsBuildingEnabled(BuildingType type) const
951✔
2370
{
2371
    return building_enabled[type] || (isHuman() && world.GetGameInterface()->GI_GetCheats().areAllBuildingsEnabled());
951✔
2372
}
2373

2374
void GamePlayer::FillVisualSettings(VisualSettings& visualSettings) const
2✔
2375
{
2376
    Distributions& visDistribution = visualSettings.distribution;
2✔
2377
    unsigned visIdx = 0;
2✔
2378
    for(const DistributionMapping& mapping : distributionMap)
60✔
2379
    {
2380
        visDistribution[visIdx++] = distribution[std::get<0>(mapping)].percent_buildings[std::get<1>(mapping)];
58✔
2381
    }
2382

2383
    visualSettings.useCustomBuildOrder = useCustomBuildOrder_;
2✔
2384
    visualSettings.build_order = build_order;
2✔
2385

2386
    visualSettings.transport_order = GetOrderingFromTransportPrio(transportPrio);
2✔
2387

2388
    visualSettings.military_settings = militarySettings_;
2✔
2389
    visualSettings.tools_settings = toolsSettings_;
2✔
2390
}
2✔
2391

2392
#define INSTANTIATE_FINDWH(Cond)                                                                                \
2393
    template nobBaseWarehouse* GamePlayer::FindWarehouse(const noRoadNode&, const Cond&, bool, bool, unsigned*, \
2394
                                                         const RoadSegment*) const
2395

2396
INSTANTIATE_FINDWH(FW::HasMinWares);
2397
INSTANTIATE_FINDWH(FW::HasFigure);
2398
INSTANTIATE_FINDWH(FW::HasWareAndFigure);
2399
INSTANTIATE_FINDWH(FW::HasAnyMatchingSoldier);
2400
INSTANTIATE_FINDWH(FW::AcceptsWare);
2401
INSTANTIATE_FINDWH(FW::AcceptsFigure);
2402
INSTANTIATE_FINDWH(FW::CollectsWare);
2403
INSTANTIATE_FINDWH(FW::CollectsFigure);
2404
INSTANTIATE_FINDWH(FW::HasWareButNoCollect);
2405
INSTANTIATE_FINDWH(FW::HasFigureButNoCollect);
2406
INSTANTIATE_FINDWH(FW::AcceptsFigureButNoSend);
2407
INSTANTIATE_FINDWH(FW::NoCondition);
2408

2409
#undef INSTANTIATE_FINDWH
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