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

Return-To-The-Roots / s25client / 25211055249

01 May 2026 10:23AM UTC coverage: 50.263% (+0.03%) from 50.232%
25211055249

Pull #1919

github

web-flow
Merge e0e9bf987 into 37e4166c0
Pull Request #1919: Add military warehouse focus

53 of 93 new or added lines in 7 files covered. (56.99%)

16 existing lines in 4 files now uncovered.

23137 of 46032 relevant lines covered (50.26%)

43428.64 hits per line

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

73.0
/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
namespace {
54
constexpr std::array<GoodType, 3> MILITARY_WAREHOUSE_GOODS = {GoodType::Beer, GoodType::Sword,
55
                                                              GoodType::ShieldRomans};
56

57
bool IsMilitaryWarehouseGood(const GoodType good)
17✔
58
{
59
    const GoodType normalizedGood = ConvertShields(good);
17✔
60
    return normalizedGood == GoodType::Beer || normalizedGood == GoodType::Sword
16✔
61
           || normalizedGood == GoodType::ShieldRomans;
33✔
62
}
63
} // namespace
64

65
GamePlayer::GamePlayer(unsigned playerId, const PlayerInfo& playerInfo, GameWorld& world)
420✔
66
    : GamePlayerInfo(playerId, playerInfo), world(world), hqPos(MapPoint::Invalid()),
420✔
67
      militaryWarehousePos(MapPoint::Invalid()), emergency(false)
840✔
68
{
69
    std::fill(building_enabled.begin(), building_enabled.end(), true);
420✔
70

71
    LoadStandardDistribution();
420✔
72
    useCustomBuildOrder_ = false;
420✔
73
    build_order = GetStandardBuildOrder();
420✔
74
    transportPrio = STD_TRANSPORT_PRIO;
420✔
75
    LoadStandardMilitarySettings();
420✔
76
    LoadStandardToolSettings();
420✔
77

78
    // Inventur nullen
79
    global_inventory.clear();
420✔
80

81
    // Statistiken mit 0en füllen
82
    statistic = {};
420✔
83
    statisticCurrentData = {};
420✔
84
    statisticCurrentMerchandiseData = {};
420✔
85

86
    RecalcDistribution();
420✔
87
}
420✔
88

89
void GamePlayer::LoadStandardToolSettings()
420✔
90
{
91
    // metalwork tool request
92

93
    // manually
94
    std::fill(tools_ordered.begin(), tools_ordered.end(), 0u);
420✔
95
    std::fill(tools_ordered_delta.begin(), tools_ordered_delta.end(), 0);
420✔
96

97
    // percentage (tool-settings-window-slider, in 10th percent)
98
    toolsSettings_[Tool::Tongs] = 1;
420✔
99
    toolsSettings_[Tool::Hammer] = 4;
420✔
100
    toolsSettings_[Tool::Axe] = 2;
420✔
101
    toolsSettings_[Tool::Saw] = 5;
420✔
102
    toolsSettings_[Tool::PickAxe] = 7;
420✔
103
    toolsSettings_[Tool::Shovel] = 1;
420✔
104
    toolsSettings_[Tool::Crucible] = 3;
420✔
105
    toolsSettings_[Tool::RodAndLine] = 1;
420✔
106
    toolsSettings_[Tool::Scythe] = 2;
420✔
107
    toolsSettings_[Tool::Cleaver] = 1;
420✔
108
    toolsSettings_[Tool::Rollingpin] = 2;
420✔
109
    toolsSettings_[Tool::Bow] = 1;
420✔
110
}
420✔
111

112
void GamePlayer::LoadStandardMilitarySettings()
420✔
113
{
114
    // military settings (military-window-slider, in 10th percent)
115
    militarySettings_[0] = MILITARY_SETTINGS_SCALE[0]; //-V525
420✔
116
    militarySettings_[1] = 3;
420✔
117
    militarySettings_[2] = MILITARY_SETTINGS_SCALE[2];
420✔
118
    militarySettings_[3] = 3;
420✔
119
    militarySettings_[4] = 0;
420✔
120
    militarySettings_[5] = 1;
420✔
121
    militarySettings_[6] = MILITARY_SETTINGS_SCALE[6];
420✔
122
    militarySettings_[7] = MILITARY_SETTINGS_SCALE[7];
420✔
123
}
420✔
124

125
BuildOrders GamePlayer::GetStandardBuildOrder()
422✔
126
{
127
    BuildOrders ordering;
128

129
    // Baureihenfolge füllen
130
    unsigned curPrio = 0;
422✔
131
    for(const auto bld : helpers::enumRange<BuildingType>())
35,448✔
132
    {
133
        if(bld == BuildingType::Headquarters || !BuildingProperties::IsValid(bld))
16,880✔
134
            continue;
844✔
135

136
        RTTR_Assert(curPrio < ordering.size());
16,036✔
137
        ordering[curPrio] = bld;
16,036✔
138
        ++curPrio;
16,036✔
139
    }
140
    RTTR_Assert(curPrio == ordering.size());
422✔
141
    return ordering;
422✔
142
}
143

144
void GamePlayer::LoadStandardDistribution()
420✔
145
{
146
    // Verteilung mit Standardwerten füllen bei Waren mit nur einem Ziel (wie z.B. Mehl, Holz...)
147
    distribution[GoodType::Flour].client_buildings.push_back(BuildingType::Bakery);
420✔
148
    distribution[GoodType::Gold].client_buildings.push_back(BuildingType::Mint);
420✔
149
    distribution[GoodType::IronOre].client_buildings.push_back(BuildingType::Ironsmelter);
420✔
150
    distribution[GoodType::Ham].client_buildings.push_back(BuildingType::Slaughterhouse);
420✔
151
    distribution[GoodType::Stones].client_buildings.push_back(
420✔
152
      BuildingType::Headquarters); // BuildingType::Headquarters = Baustellen!
420✔
153
    distribution[GoodType::Stones].client_buildings.push_back(BuildingType::Catapult);
420✔
154
    distribution[GoodType::Grapes].client_buildings.push_back(BuildingType::Winery);
420✔
155
    distribution[GoodType::Wine].client_buildings.push_back(BuildingType::Temple);
420✔
156
    distribution[GoodType::Skins].client_buildings.push_back(BuildingType::Tannery);
420✔
157
    distribution[GoodType::Leather].client_buildings.push_back(BuildingType::LeatherWorks);
420✔
158

159
    // Waren mit mehreren möglichen Zielen erstmal nullen, kann dann im Fenster eingestellt werden
160
    for(const auto i : helpers::enumRange<GoodType>())
35,280✔
161
    {
162
        std::fill(distribution[i].percent_buildings.begin(), distribution[i].percent_buildings.end(), 0);
16,800✔
163
        distribution[i].selected_goal = 0;
16,800✔
164
    }
165

166
    // Standardverteilung der Waren
167
    for(const DistributionMapping& mapping : distributionMap)
12,600✔
168
    {
169
        distribution[std::get<0>(mapping)].percent_buildings[std::get<1>(mapping)] = std::get<2>(mapping);
12,180✔
170
    }
171
}
420✔
172

173
GamePlayer::~GamePlayer() = default;
848✔
174

175
void GamePlayer::Serialize(SerializedGameData& sgd) const
17✔
176
{
177
    // PlayerStatus speichern, ehemalig
178
    sgd.PushEnum<uint8_t>(ps);
17✔
179

180
    // Nur richtige Spieler serialisieren
181
    if(ps != PlayerState::Occupied && ps != PlayerState::AI)
17✔
182
        return;
3✔
183

184
    sgd.PushBool(isDefeated);
14✔
185

186
    buildings.Serialize(sgd);
14✔
187

188
    sgd.PushObjectContainer(roads, true);
14✔
189

190
    sgd.PushUnsignedInt(jobs_wanted.size());
14✔
191
    for(const JobNeeded& job : jobs_wanted)
20✔
192
    {
193
        sgd.PushEnum<uint8_t>(job.job);
6✔
194
        sgd.PushObject(job.workplace);
6✔
195
    }
196

197
    sgd.PushObjectContainer(ware_list, true);
14✔
198
    sgd.PushObjectContainer(flagworkers);
14✔
199
    sgd.PushObjectContainer(ships, true);
14✔
200

201
    helpers::pushContainer(sgd, shouldSendDefenderList);
14✔
202
    helpers::pushPoint(sgd, hqPos);
14✔
203
    helpers::pushPoint(sgd, GetMilitaryWarehousePos());
14✔
204

205
    for(const Distribution& dist : distribution)
574✔
206
    {
207
        helpers::pushContainer(sgd, dist.percent_buildings);
560✔
208
        helpers::pushContainer(sgd, dist.client_buildings);
560✔
209
        helpers::pushContainer(sgd, dist.goals);
560✔
210
        sgd.PushUnsignedInt(dist.selected_goal);
560✔
211
    }
212

213
    sgd.PushBool(useCustomBuildOrder_);
14✔
214
    helpers::pushContainer(sgd, build_order);
14✔
215
    helpers::pushContainer(sgd, transportPrio);
14✔
216
    helpers::pushContainer(sgd, militarySettings_);
14✔
217
    helpers::pushContainer(sgd, toolsSettings_);
14✔
218
    helpers::pushContainer(sgd, tools_ordered);
14✔
219
    helpers::pushContainer(sgd, global_inventory.goods);
14✔
220
    helpers::pushContainer(sgd, global_inventory.people);
14✔
221
    helpers::pushContainer(sgd, global_inventory.armoredSoldiers);
14✔
222

223
    // für Statistik
224
    for(const Statistic& curStatistic : statistic)
70✔
225
    {
226
        // normale Statistik
227
        for(const auto& curData : curStatistic.data)
560✔
228
            helpers::pushContainer(sgd, curData);
504✔
229

230
        // Warenstatistik
231
        for(unsigned j = 0; j < NUM_STAT_MERCHANDISE_TYPES; ++j)
840✔
232
            helpers::pushContainer(sgd, curStatistic.merchandiseData[j]);
784✔
233

234
        sgd.PushUnsignedShort(curStatistic.currentIndex);
56✔
235
        sgd.PushUnsignedShort(curStatistic.counter);
56✔
236
    }
237
    helpers::pushContainer(sgd, statisticCurrentData);
14✔
238
    helpers::pushContainer(sgd, statisticCurrentMerchandiseData);
14✔
239

240
    // Serialize Pacts:
241
    for(const auto& playerPacts : pacts)
126✔
242
    {
243
        for(const Pact& pact : playerPacts)
336✔
244
            pact.Serialize(sgd);
224✔
245
    }
246

247
    sgd.PushBool(emergency);
14✔
248
}
249

250
void GamePlayer::Deserialize(SerializedGameData& sgd)
8✔
251
{
252
    std::fill(building_enabled.begin(), building_enabled.end(), true);
8✔
253

254
    // Ehemaligen PS auslesen
255
    auto origin_ps = sgd.Pop<PlayerState>();
8✔
256
    // Nur richtige Spieler serialisieren
257
    if(origin_ps != PlayerState::Occupied && origin_ps != PlayerState::AI)
8✔
258
        return;
1✔
259

260
    isDefeated = sgd.PopBool();
7✔
261
    buildings.Deserialize(sgd);
7✔
262

263
    sgd.PopObjectContainer(roads, GO_Type::Roadsegment);
7✔
264

265
    jobs_wanted.resize(sgd.PopUnsignedInt());
7✔
266
    for(JobNeeded& job : jobs_wanted)
10✔
267
    {
268
        job.job = sgd.Pop<Job>();
3✔
269
        job.workplace = sgd.PopObject<noRoadNode>();
3✔
270
    }
271

272
    if(sgd.GetGameDataVersion() < 2)
7✔
273
        buildings.Deserialize2(sgd);
×
274

275
    sgd.PopObjectContainer(ware_list, GO_Type::Ware);
7✔
276
    sgd.PopObjectContainer(flagworkers);
7✔
277
    sgd.PopObjectContainer(ships, GO_Type::Ship);
7✔
278

279
    sgd.PopContainer(shouldSendDefenderList);
7✔
280

281
    hqPos = sgd.PopMapPoint();
7✔
282
    militaryWarehousePos = sgd.GetGameDataVersion() >= 15 ? sgd.PopMapPoint() : hqPos;
7✔
283

284
    for(const auto i : helpers::enumRange<GoodType>())
588✔
285
    {
286
        if(sgd.GetGameDataVersion() < 11 && wineaddon::isWineAddonGoodType(i))
280✔
287
            continue;
×
288

289
        if(sgd.GetGameDataVersion() < 12 && leatheraddon::isLeatherAddonGoodType(i))
280✔
290
            continue;
×
291

292
        Distribution& dist = distribution[i];
280✔
293
        helpers::popContainer(sgd, dist.percent_buildings);
280✔
294
        // Set standard value otherwise its zero and Slaughterhouse never gets ham
295
        // because the ham distribution was not there in earlier versions
296
        if(sgd.GetGameDataVersion() < 12 && i == GoodType::Ham)
280✔
297
            dist.percent_buildings[BuildingType::Slaughterhouse] = 8;
×
298

299
        if(sgd.GetGameDataVersion() < 7)
280✔
300
        {
301
            dist.client_buildings.resize(sgd.PopUnsignedInt());
×
302
            helpers::popContainer(sgd, dist.client_buildings, true);
×
303
            dist.goals.resize(sgd.PopUnsignedInt());
×
304
            helpers::popContainer(sgd, dist.goals, true);
×
305
        } else
306
        {
307
            helpers::popContainer(sgd, dist.client_buildings);
280✔
308
            helpers::popContainer(sgd, dist.goals);
280✔
309
        }
310
        dist.selected_goal = sgd.PopUnsignedInt();
280✔
311
    }
312

313
    useCustomBuildOrder_ = sgd.PopBool();
7✔
314

315
    if(sgd.GetGameDataVersion() < 12)
7✔
316
    {
317
        auto countOfNotAvailableBuildingsInSaveGame =
318
          sgd.GetGameDataVersion() < 11 ? numWineAndLeatherAddonBuildings : numLeatherAddonBuildings;
×
319
        std::vector<BuildingType> build_order_raw(build_order.size() - countOfNotAvailableBuildingsInSaveGame);
×
320
        helpers::popContainer(sgd, build_order_raw, true);
×
321

322
        if(sgd.GetGameDataVersion() < 11)
×
323
        {
324
            build_order_raw.insert(build_order_raw.end(),
×
325
                                   {BuildingType::Vineyard, BuildingType::Winery, BuildingType::Temple});
×
326
        }
327

328
        if(sgd.GetGameDataVersion() < 12)
×
329
        {
330
            build_order_raw.insert(build_order_raw.end(),
×
331
                                   {BuildingType::Skinner, BuildingType::Tannery, BuildingType::LeatherWorks});
×
332
        }
333

334
        std::copy(build_order_raw.begin(), build_order_raw.end(), build_order.begin());
×
335

336
        auto countOfNotAvailableGoodsInSaveGame =
337
          sgd.GetGameDataVersion() < 11 ? numWineAndLeatherAddonGoods : numLeatherAddonGoods;
×
338
        std::vector<uint8_t> transportPrio_raw(transportPrio.size() - countOfNotAvailableGoodsInSaveGame);
×
339
        helpers::popContainer(sgd, transportPrio_raw, true);
×
340
        std::copy(transportPrio_raw.begin(), transportPrio_raw.end(), transportPrio.begin());
×
341
        std::transform(transportPrio.begin(), transportPrio.end() - countOfNotAvailableGoodsInSaveGame,
×
342
                       transportPrio.begin(),
343
                       [](uint8_t& prio) { return prio < transportPrioOfLeatherworks ? prio : prio + 1; });
×
344
    } else
345
    {
346
        helpers::popContainer(sgd, build_order);
7✔
347
        helpers::popContainer(sgd, transportPrio);
7✔
348
    }
349

350
    helpers::popContainer(sgd, militarySettings_);
7✔
351
    helpers::popContainer(sgd, toolsSettings_);
7✔
352

353
    // qx:tools
354
    helpers::popContainer(sgd, tools_ordered);
7✔
355
    tools_ordered_delta = {};
7✔
356

357
    if(sgd.GetGameDataVersion() < 12)
7✔
358
    {
359
        auto countOfNotAvailableGoodsInSaveGame =
360
          sgd.GetGameDataVersion() < 11 ? numWineAndLeatherAddonGoods : numLeatherAddonGoods;
×
361
        std::vector<unsigned int> global_inventory_good_raw(global_inventory.goods.size()
×
362
                                                            - countOfNotAvailableGoodsInSaveGame);
×
363
        helpers::popContainer(sgd, global_inventory_good_raw, true);
×
364
        std::copy(global_inventory_good_raw.begin(), global_inventory_good_raw.end(), global_inventory.goods.begin());
×
365

366
        auto countOfNotAvailableJobsInSaveGame =
367
          sgd.GetGameDataVersion() < 11 ? numWineAndLeatherAddonJobs : numLeatherAddonJobs;
×
368
        std::vector<unsigned int> global_inventory_people_raw(global_inventory.people.size()
×
369
                                                              - countOfNotAvailableJobsInSaveGame);
×
370
        helpers::popContainer(sgd, global_inventory_people_raw, true);
×
371
        std::copy(global_inventory_people_raw.begin(), global_inventory_people_raw.end(),
×
372
                  global_inventory.people.begin());
373

374
        std::fill(global_inventory.armoredSoldiers.begin(), global_inventory.armoredSoldiers.end(), 0);
×
375
    } else
376
    {
377
        helpers::popContainer(sgd, global_inventory.goods);
7✔
378
        helpers::popContainer(sgd, global_inventory.people);
7✔
379
        helpers::popContainer(sgd, global_inventory.armoredSoldiers);
7✔
380
    }
381

382
    // Visuelle Einstellungen festlegen
383

384
    // für Statistik
385
    for(Statistic& curStatistic : statistic)
35✔
386
    {
387
        // normale Statistik
388
        for(auto& curData : curStatistic.data)
280✔
389
            helpers::popContainer(sgd, curData);
252✔
390

391
        // Warenstatistik
392
        for(unsigned j = 0; j < NUM_STAT_MERCHANDISE_TYPES; ++j)
420✔
393
            helpers::popContainer(sgd, curStatistic.merchandiseData[j]);
392✔
394

395
        curStatistic.currentIndex = sgd.PopUnsignedShort();
28✔
396
        curStatistic.counter = sgd.PopUnsignedShort();
28✔
397
    }
398
    helpers::popContainer(sgd, statisticCurrentData);
7✔
399
    helpers::popContainer(sgd, statisticCurrentMerchandiseData);
7✔
400

401
    // Deserialize Pacts:
402
    for(auto& playerPacts : pacts)
63✔
403
    {
404
        for(Pact& pact : playerPacts)
168✔
405
            pact = GamePlayer::Pact(sgd);
112✔
406
    }
407

408
    emergency = sgd.PopBool();
7✔
409
}
410

411
template<class T_IsWarehouseGood>
412
nobBaseWarehouse* GamePlayer::FindWarehouse(const noRoadNode& start, const T_IsWarehouseGood& isWarehouseGood,
4,691✔
413
                                            bool to_wh, bool use_boat_roads, unsigned* length,
414
                                            const RoadSegment* forbidden) const
415
{
416
    nobBaseWarehouse* best = nullptr;
4,691✔
417

418
    unsigned best_length = std::numeric_limits<unsigned>::max();
4,691✔
419

420
    for(nobBaseWarehouse* wh : buildings.GetStorehouses())
15,631✔
421
    {
422
        // Lagerhaus geeignet?
423
        RTTR_Assert(wh);
10,945✔
424
        if(!isWarehouseGood(*wh))
10,945✔
425
            continue;
10,507✔
426

427
        if(start.GetPos() == wh->GetPos())
451✔
428
        {
429
            // We are already there -> Take it
430
            if(length)
5✔
431
                *length = 0;
×
432
            return wh;
5✔
433
        }
434

435
        // now check if there is at least a chance that the next wh is closer than current best because pathfinding
436
        // takes time
437
        if(world.CalcDistance(start.GetPos(), wh->GetPos()) > best_length)
446✔
438
            continue;
13✔
439
        // Bei der erlaubten Benutzung von Bootsstraßen Waren-Pathfinding benutzen wenns zu nem Lagerhaus gehn soll
440
        // start <-> ziel tauschen bei der wegfindung
441
        unsigned tlength;
442
        if(world.GetRoadPathFinder().FindPath(to_wh ? start : *wh, to_wh ? *wh : start, use_boat_roads, best_length,
433✔
443
                                              forbidden, &tlength))
444
        {
445
            if(tlength < best_length || !best)
351✔
446
            {
447
                best_length = tlength;
347✔
448
                best = wh;
347✔
449
            }
450
        }
451
    }
452

453
    if(length)
4,686✔
454
        *length = best_length;
514✔
455

456
    return best;
4,686✔
457
}
458

459
void GamePlayer::AddBuildingSite(noBuildingSite* bldSite)
28✔
460
{
461
    RTTR_Assert(bldSite->GetPlayer() == GetPlayerId());
28✔
462
    buildings.Add(bldSite);
28✔
463
}
28✔
464

465
void GamePlayer::RemoveBuildingSite(noBuildingSite* bldSite)
12✔
466
{
467
    RTTR_Assert(bldSite->GetPlayer() == GetPlayerId());
12✔
468
    buildings.Remove(bldSite);
12✔
469
}
12✔
470

471
bool GamePlayer::IsHQTent() const
8✔
472
{
473
    if(const nobHQ* hq = GetHQ())
8✔
474
        return hq->IsTent();
8✔
475
    return false;
×
476
}
477

478
void GamePlayer::SetHQIsTent(bool isTent)
6✔
479
{
480
    if(nobHQ* hq = GetHQ())
6✔
481
        hq->SetIsTent(isTent);
5✔
482
}
6✔
483

484
void GamePlayer::AddBuilding(noBuilding* bld, BuildingType bldType)
663✔
485
{
486
    RTTR_Assert(bld->GetPlayer() == GetPlayerId());
663✔
487
    buildings.Add(bld, bldType);
663✔
488
    ChangeStatisticValue(StatisticType::Buildings, 1);
663✔
489

490
    // Order a worker if needed
491
    const auto& description = BLD_WORK_DESC[bldType];
663✔
492
    if(description.job && !isSoldierJob(*description.job))
663✔
493
    {
494
        AddJobWanted(*description.job, bld);
108✔
495
    }
496

497
    if(bldType == BuildingType::HarborBuilding)
663✔
498
    {
499
        // Schiff durchgehen und denen Bescheid sagen
500
        for(noShip* ship : ships)
52✔
501
            ship->NewHarborBuilt(static_cast<nobHarborBuilding*>(bld));
11✔
502
    } else if(bldType == BuildingType::Headquarters)
622✔
503
    {
504
        // If there is more than one HQ, keep the original position.
505
        if(!hqPos.isValid())
375✔
506
            hqPos = bld->GetPos();
366✔
507
    } else if(BuildingProperties::IsMilitary(bldType))
247✔
508
    {
509
        auto* milBld = static_cast<nobMilitary*>(bld);
126✔
510
        // New built? -> Calculate frontier distance
511
        if(milBld->IsNewBuilt())
126✔
512
            milBld->LookForEnemyBuildings();
116✔
513
    }
514
}
663✔
515

516
void GamePlayer::RemoveBuilding(noBuilding* bld, BuildingType bldType)
115✔
517
{
518
    RTTR_Assert(bld->GetPlayer() == GetPlayerId());
115✔
519
    buildings.Remove(bld, bldType);
115✔
520
    ChangeStatisticValue(StatisticType::Buildings, -1);
115✔
521
    if(bldType == BuildingType::HarborBuilding)
115✔
522
    { // Schiffen Bescheid sagen
523
        for(noShip* ship : ships)
27✔
524
            ship->HarborDestroyed(static_cast<nobHarborBuilding*>(bld));
12✔
525
    } else if(bldType == BuildingType::Headquarters && bld->GetPos() == hqPos)
100✔
526
    {
527
        hqPos = MapPoint::Invalid();
30✔
528
        for(const noBaseBuilding* bld : buildings.GetStorehouses())
30✔
529
        {
530
            if(bld->GetBuildingType() == BuildingType::Headquarters)
×
531
            {
532
                hqPos = bld->GetPos();
×
533
                break;
×
534
            }
535
        }
536
    }
537
    if(BuildingProperties::IsWareHouse(bldType) && bld->GetPos() == militaryWarehousePos)
115✔
NEW
538
        militaryWarehousePos = hqPos;
×
539
    if(BuildingProperties::IsWareHouse(bldType) || BuildingProperties::IsMilitary(bldType))
115✔
540
        TestDefeat();
82✔
541
}
115✔
542

543
void GamePlayer::NewRoadConnection(RoadSegment* rs)
158✔
544
{
545
    // Zu den Straßen hinzufgen, da's ja ne neue ist
546
    roads.push_back(rs);
158✔
547

548
    // Alle Straßen müssen nun gucken, ob sie einen Weg zu einem Warehouse finden
549
    FindCarrierForAllRoads();
158✔
550

551
    // Alle Straßen müssen gucken, ob sie einen Esel bekommen können
552
    for(RoadSegment* rs : roads)
343✔
553
        rs->TryGetDonkey();
185✔
554

555
    // Alle Arbeitsplätze müssen nun gucken, ob sie einen Weg zu einem Lagerhaus mit entsprechender Arbeitskraft finden
556
    FindWarehouseForAllJobs();
158✔
557

558
    // Alle Baustellen müssen nun gucken, ob sie ihr benötigtes Baumaterial bekommen (evtl war vorher die Straße zum
559
    // Lagerhaus unterbrochen
560
    FindMaterialForBuildingSites();
158✔
561

562
    // Alle Lost-Wares müssen gucken, ob sie ein Lagerhaus finden
563
    FindClientForLostWares();
158✔
564

565
    // Alle Militärgebäude müssen ihre Truppen überprüfen und können nun ggf. neue bestellen
566
    // und müssen prüfen, ob sie evtl Gold bekommen
567
    for(nobMilitary* mil : buildings.GetMilitaryBuildings())
175✔
568
    {
569
        mil->RegulateTroops();
17✔
570
        mil->SearchCoins();
17✔
571
    }
572
}
158✔
573

574
void GamePlayer::AddRoad(RoadSegment* rs)
21✔
575
{
576
    roads.push_back(rs);
21✔
577
}
21✔
578

579
void GamePlayer::DeleteRoad(RoadSegment* rs)
103✔
580
{
581
    RTTR_Assert(helpers::contains(roads, rs));
103✔
582
    roads.remove(rs);
103✔
583
}
103✔
584

585
void GamePlayer::FindClientForLostWares()
164✔
586
{
587
    // Alle Lost-Wares müssen gucken, ob sie ein Lagerhaus finden
588
    for(Ware* ware : ware_list)
174✔
589
    {
590
        if(ware->IsLostWare())
10✔
591
        {
592
            if(ware->FindRouteToWarehouse() && ware->IsWaitingAtFlag())
×
593
                ware->CallCarrier();
×
594
        }
595
    }
596
}
164✔
597

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

644
            // notify carriers/flags about news if there are any
645
            if(ware->GetNextDir() != last_next_dir)
2✔
646
            {
647
                // notify current flag that transport in the old direction might not longer be required
648
                ware->RemoveWareJobForDir(last_next_dir);
×
649
                if(ware->GetNextDir() != RoadPathDirection::None)
×
650
                    ware->CallCarrier();
×
651
            }
652
        } else if(ware->IsWaitingInWarehouse())
8✔
653
        {
654
            if(!ware->IsRouteToGoal())
×
655
            {
656
                // Das Ziel wird nun nich mehr beliefert
657
                ware->NotifyGoalAboutLostWare();
×
658
                // Ware aus der Warteliste des Lagerhauses entfernen
659
                static_cast<nobBaseWarehouse*>(ware->GetLocation())->CancelWare(ware);
×
660
                // Ware aus der Liste raus
661
                it = ware_list.erase(it);
×
662
                continue;
×
663
            }
664
        } else if(ware->IsWaitingForShip())
8✔
665
        {
666
            // Weg neu berechnen
667
            ware->RecalcRoute();
1✔
668
        }
669

670
        ++it;
10✔
671
    }
672

673
    // Alle Häfen müssen ihre Figuren den Weg überprüfen lassen
674
    for(nobHarborBuilding* hb : buildings.GetHarbors())
240✔
675
    {
676
        hb->ExamineShipRouteOfPeople();
17✔
677
    }
678
}
223✔
679

680
bool GamePlayer::FindCarrierForRoad(RoadSegment& rs) const
256✔
681
{
682
    RTTR_Assert(rs.GetF1() != nullptr && rs.GetF2() != nullptr);
256✔
683
    std::array<unsigned, 2> length;
684
    std::array<nobBaseWarehouse*, 2> best;
685

686
    // Braucht der ein Boot?
687
    if(rs.GetRoadType() == RoadType::Water)
256✔
688
    {
689
        // dann braucht man Träger UND Boot
690
        best[0] = FindWarehouse(*rs.GetF1(), FW::HasWareAndFigure(GoodType::Boat, Job::Helper, false), false, false,
×
691
                                length.data(), &rs);
692
        // 2. Flagge des Weges
693
        best[1] = FindWarehouse(*rs.GetF2(), FW::HasWareAndFigure(GoodType::Boat, Job::Helper, false), false, false,
×
694
                                &length[1], &rs);
×
695
    } else
696
    {
697
        // 1. Flagge des Weges
698
        best[0] = FindWarehouse(*rs.GetF1(), FW::HasFigure(Job::Helper, false), false, false, length.data(), &rs);
256✔
699
        // 2. Flagge des Weges
700
        best[1] = FindWarehouse(*rs.GetF2(), FW::HasFigure(Job::Helper, false), false, false, &length[1], &rs);
256✔
701
    }
702

703
    // überhaupt nen Weg gefunden?
704
    // Welche Flagge benutzen?
705
    if(best[0] && (!best[1] || length[0] < length[1]))
256✔
706
        best[0]->OrderCarrier(*rs.GetF1(), rs);
26✔
707
    else if(best[1])
230✔
708
        best[1]->OrderCarrier(*rs.GetF2(), rs);
31✔
709
    else
710
        return false;
199✔
711
    return true;
57✔
712
}
713

714
bool GamePlayer::IsWarehouseValid(nobBaseWarehouse* wh) const
×
715
{
716
    return helpers::contains(buildings.GetStorehouses(), wh);
×
717
}
718

719
void GamePlayer::RecalcDistribution()
426✔
720
{
721
    GoodType lastWare = GoodType::Nothing;
426✔
722
    for(const DistributionMapping& mapping : distributionMap)
12,780✔
723
    {
724
        if(lastWare == std::get<0>(mapping))
12,354✔
725
            continue;
8,946✔
726
        lastWare = std::get<0>(mapping);
3,408✔
727
        RecalcDistributionOfWare(std::get<0>(mapping));
3,408✔
728
    }
729
}
426✔
730

731
void GamePlayer::RecalcDistributionOfWare(const GoodType ware)
3,408✔
732
{
733
    // Punktesystem zur Verteilung, in der Liste alle Gebäude sammeln, die die Ware wollen
734
    distribution[ware].client_buildings.clear();
3,408✔
735

736
    // 1. Anteile der einzelnen Waren ausrechnen
737

738
    /// Mapping of buildings that want the current ware to its percentage
739
    using BldEntry = std::pair<BuildingType, uint8_t>;
740
    std::vector<BldEntry> bldPercentageMap;
3,408✔
741

742
    unsigned goal_count = 0;
3,408✔
743

744
    for(const auto bld : helpers::enumRange<BuildingType>())
286,272✔
745
    {
746
        uint8_t percentForCurBld = distribution[ware].percent_buildings[bld];
136,320✔
747
        if(percentForCurBld)
136,320✔
748
        {
749
            distribution[ware].client_buildings.push_back(bld);
12,354✔
750
            goal_count += percentForCurBld;
12,354✔
751
            bldPercentageMap.emplace_back(bld, percentForCurBld);
12,354✔
752
        }
753
    }
754

755
    // TODO: evtl noch die counts miteinander kürzen (ggt berechnen)
756

757
    // Array für die Gebäudtypen erstellen
758

759
    std::vector<BuildingType>& wareGoals = distribution[ware].goals;
3,408✔
760
    wareGoals.clear();
3,408✔
761
    wareGoals.reserve(goal_count);
3,408✔
762

763
    // just drop them in the list, the distribution will be handled by going through this list using a prime as step
764
    // (see GameClientPlayer::FindClientForWare)
765
    for(const BldEntry& bldEntry : bldPercentageMap)
15,762✔
766
    {
767
        for(unsigned char i = 0; i < bldEntry.second; ++i)
80,213✔
768
            wareGoals.push_back(bldEntry.first);
67,859✔
769
    }
770

771
    distribution[ware].selected_goal = 0;
3,408✔
772
}
3,408✔
773

774
void GamePlayer::FindCarrierForAllRoads()
262✔
775
{
776
    for(RoadSegment* rs : roads)
598✔
777
    {
778
        if(!rs->hasCarrier(0))
336✔
779
            FindCarrierForRoad(*rs);
231✔
780
    }
781
}
262✔
782

783
void GamePlayer::FindMaterialForBuildingSites()
308✔
784
{
785
    for(noBuildingSite* bldSite : buildings.GetBuildingSites())
332✔
786
        bldSite->OrderConstructionMaterial();
24✔
787
}
308✔
788

789
void GamePlayer::AddJobWanted(const Job job, noRoadNode* workplace)
144✔
790
{
791
    if(!FindWarehouseForJob(job, *workplace))
144✔
792
    {
793
        JobNeeded jn = {job, workplace};
134✔
794
        jobs_wanted.push_back(jn);
134✔
795
    }
796
}
144✔
797

798
void GamePlayer::JobNotWanted(noRoadNode* workplace, bool all)
56✔
799
{
800
    for(auto it = jobs_wanted.begin(); it != jobs_wanted.end();)
60✔
801
    {
802
        if(it->workplace == workplace)
45✔
803
        {
804
            it = jobs_wanted.erase(it);
41✔
805
            if(!all)
41✔
806
                return;
41✔
807
        } else
808
        {
809
            ++it;
4✔
810
        }
811
    }
812
}
813

814
void GamePlayer::OneJobNotWanted(const Job job, noRoadNode* workplace)
2✔
815
{
816
    const auto it = helpers::find_if(
817
      jobs_wanted, [workplace, job](const auto& it) { return it.workplace == workplace && it.job == job; });
2✔
818
    if(it != jobs_wanted.end())
2✔
819
        jobs_wanted.erase(it);
×
820
}
2✔
821

822
void GamePlayer::SendPostMessage(std::unique_ptr<PostMsg> msg)
35✔
823
{
824
    world.GetPostMgr().SendMsg(GetPlayerId(), std::move(msg));
35✔
825
}
35✔
826

827
unsigned GamePlayer::GetToolsOrderedVisual(Tool tool) const
×
828
{
829
    return std::max(0, int(tools_ordered[tool] + tools_ordered_delta[tool]));
×
830
}
831

832
unsigned GamePlayer::GetToolsOrdered(Tool tool) const
369✔
833
{
834
    return tools_ordered[tool];
369✔
835
}
836

837
bool GamePlayer::ChangeToolOrderVisual(Tool tool, int changeAmount) const
×
838
{
839
    if(std::abs(changeAmount) > 100)
×
840
        return false;
×
841
    int newOrderAmount = int(GetToolsOrderedVisual(tool)) + changeAmount;
×
842
    if(newOrderAmount < 0 || newOrderAmount > 100)
×
843
        return false;
×
844
    tools_ordered_delta[tool] += changeAmount;
×
845
    return true;
×
846
}
847

848
unsigned GamePlayer::GetToolPriority(Tool tool) const
271✔
849
{
850
    return toolsSettings_[tool];
271✔
851
}
852

853
void GamePlayer::ToolOrderProcessed(Tool tool)
4✔
854
{
855
    if(tools_ordered[tool])
4✔
856
    {
857
        --tools_ordered[tool];
4✔
858
        world.GetNotifications().publish(ToolNote(ToolNote::OrderCompleted, GetPlayerId()));
4✔
859
    }
860
}
4✔
861

862
bool GamePlayer::FindWarehouseForJob(const Job job, noRoadNode& goal) const
218✔
863
{
864
    // Optimization: return early if building is isolated
865
    if(goal.GetType() == NodalObjectType::Building || goal.GetType() == NodalObjectType::Buildingsite)
218✔
866
    {
867
        if(!static_cast<noBaseBuilding&>(goal).IsConnected())
218✔
868
            return false;
134✔
869
    }
870

871
    nobBaseWarehouse* wh = FindWarehouse(goal, FW::HasFigure(job, true), false, false);
84✔
872

873
    if(wh)
84✔
874
    {
875
        // Es wurde ein Lagerhaus gefunden, wo es den geforderten Beruf gibt, also den Typen zur Arbeit rufen
876
        wh->OrderJob(job, goal, true);
17✔
877
        return true;
17✔
878
    }
879

880
    return false;
67✔
881
}
882

883
void GamePlayer::FindWarehouseForAllJobs()
261✔
884
{
885
    for(auto it = jobs_wanted.begin(); it != jobs_wanted.end();)
335✔
886
    {
887
        if(FindWarehouseForJob(it->job, *it->workplace))
74✔
888
            it = jobs_wanted.erase(it);
7✔
889
        else
890
            ++it;
67✔
891
    }
892
}
261✔
893

894
void GamePlayer::FindWarehouseForAllJobs(const Job job)
2,630✔
895
{
896
    for(auto it = jobs_wanted.begin(); it != jobs_wanted.end();)
2,630✔
897
    {
898
        if(it->job == job)
×
899
        {
900
            if(FindWarehouseForJob(it->job, *it->workplace))
×
901
                it = jobs_wanted.erase(it);
×
902
            else
903
                ++it;
×
904
        } else
905
            ++it;
×
906
    }
907
}
2,630✔
908

909
Ware* GamePlayer::OrderWare(const GoodType ware, noBaseBuilding& goal)
112✔
910
{
911
    /// Gibt es ein Lagerhaus mit dieser Ware?
912
    nobBaseWarehouse* wh = FindWarehouse(goal, FW::HasMinWares(ware, 1), false, true);
112✔
913

914
    if(wh)
112✔
915
    {
916
        // Prüfe ob Notfallprogramm aktiv
917
        if(!emergency)
84✔
918
            return wh->OrderWare(ware, goal);
84✔
919
        else
920
        {
921
            // Wenn Notfallprogramm aktiv nur an Holzfäller und Sägewerke Bretter/Steine liefern
922
            if((ware != GoodType::Boards && ware != GoodType::Stones)
×
923
               || goal.GetBuildingType() == BuildingType::Woodcutter || goal.GetBuildingType() == BuildingType::Sawmill)
×
924
                return wh->OrderWare(ware, goal);
×
925
            else
926
                return nullptr;
×
927
        }
928
    } else // no warehouse can deliver the ware -> check all our wares for lost wares that might match the order
929
    {
930
        unsigned bestLength = std::numeric_limits<unsigned>::max();
28✔
931
        Ware* bestWare = nullptr;
28✔
932
        for(Ware* curWare : ware_list)
28✔
933
        {
934
            if(curWare->IsLostWare() && curWare->type == ware)
×
935
            {
936
                // got a lost ware with a road to goal -> find best
937
                unsigned curLength = curWare->CheckNewGoalForLostWare(goal);
×
938
                if(curLength < bestLength)
×
939
                {
940
                    bestLength = curLength;
×
941
                    bestWare = curWare;
×
942
                }
943
            }
944
        }
945
        if(bestWare)
28✔
946
        {
947
            bestWare->SetNewGoalForLostWare(goal);
×
948
            return bestWare;
×
949
        }
950
    }
951
    return nullptr;
28✔
952
}
953

954
nofCarrier* GamePlayer::OrderDonkey(RoadSegment& road) const
1✔
955
{
956
    std::array<unsigned, 2> length;
957
    std::array<nobBaseWarehouse*, 2> best;
958

959
    // 1. Flagge des Weges
960
    best[0] = FindWarehouse(*road.GetF1(), FW::HasFigure(Job::PackDonkey, false), false, false, length.data(), &road);
1✔
961
    // 2. Flagge des Weges
962
    best[1] = FindWarehouse(*road.GetF2(), FW::HasFigure(Job::PackDonkey, false), false, false, &length[1], &road);
1✔
963

964
    // überhaupt nen Weg gefunden?
965
    // Welche Flagge benutzen?
966
    if(best[0] && (!best[1] || length[0] < length[1]))
1✔
967
        return best[0]->OrderDonkey(road, *road.GetF1());
×
968
    else if(best[1])
1✔
969
        return best[1]->OrderDonkey(road, *road.GetF2());
×
970
    else
971
        return nullptr;
1✔
972
}
973

974
RoadSegment* GamePlayer::FindRoadForDonkey(noRoadNode& start, noRoadNode** goal)
75✔
975
{
976
    // Bisher höchste Trägerproduktivität und die entsprechende Straße dazu
977
    unsigned best_productivity = 0;
75✔
978
    RoadSegment* best_road = nullptr;
75✔
979
    // Beste Flagge dieser Straße
980
    *goal = nullptr;
75✔
981

982
    for(RoadSegment* roadSeg : roads)
88✔
983
    {
984
        // Braucht die Straße einen Esel?
985
        if(roadSeg->NeedDonkey())
13✔
986
        {
987
            // Beste Flagge von diesem Weg, und beste Wegstrecke
988
            noRoadNode* current_best_goal = nullptr;
×
989
            // Weg zu beiden Flaggen berechnen
990
            unsigned length1, length2;
991
            bool isF1Reachable = world.FindHumanPathOnRoads(start, *roadSeg->GetF1(), &length1, nullptr, roadSeg)
×
992
                                 != RoadPathDirection::None;
×
993
            bool isF2Reachable = world.FindHumanPathOnRoads(start, *roadSeg->GetF2(), &length2, nullptr, roadSeg)
×
994
                                 != RoadPathDirection::None;
×
995

996
            // Wenn man zu einer Flagge nich kommt, die jeweils andere nehmen
997
            if(!isF1Reachable)
×
998
                current_best_goal = (isF2Reachable) ? roadSeg->GetF2() : nullptr;
×
999
            else if(!isF2Reachable)
×
1000
                current_best_goal = roadSeg->GetF1();
×
1001
            else
1002
            {
1003
                // ansonsten die kürzeste von beiden
1004
                current_best_goal = (length1 < length2) ? roadSeg->GetF1() : roadSeg->GetF2();
×
1005
            }
1006

1007
            // Kein Weg führt hin, nächste Straße bitte
1008
            if(!current_best_goal)
×
1009
                continue;
×
1010

1011
            // Jeweiligen Weg bestimmen
1012
            unsigned current_best_way = (roadSeg->GetF1() == current_best_goal) ? length1 : length2;
×
1013

1014
            // Produktivität ausrechnen, *10 die Produktivität + die Wegstrecke, damit die
1015
            // auch noch mit einberechnet wird
1016
            unsigned current_productivity = 10 * roadSeg->getCarrier(0)->GetProductivity() + current_best_way;
×
1017

1018
            // Besser als der bisher beste?
1019
            if(current_productivity > best_productivity)
×
1020
            {
1021
                // Dann wird der vom Thron gestoßen
1022
                best_productivity = current_productivity;
×
1023
                best_road = roadSeg;
×
1024
                *goal = current_best_goal;
×
1025
            }
1026
        }
1027
    }
1028

1029
    return best_road;
75✔
1030
}
1031

1032
struct ClientForWare
1033
{
1034
    noBaseBuilding* bld;
1035
    unsigned estimate; // points minus half the optimal distance
1036
    unsigned points;
1037

1038
    ClientForWare(noBaseBuilding* bld, unsigned estimate, unsigned points)
×
1039
        : bld(bld), estimate(estimate), points(points)
×
1040
    {}
×
1041

1042
    bool operator<(const ClientForWare& b) const noexcept
×
1043
    {
1044
        // use estimate, points and object id (as tie breaker) for sorting
1045
        if(estimate != b.estimate)
×
1046
            return estimate > b.estimate;
×
1047
        else if(points != b.points)
×
1048
            return points > b.points;
×
1049
        else
1050
            return bld->GetObjId() > b.bld->GetObjId();
×
1051
    }
1052
};
1053

1054
noBaseBuilding* GamePlayer::FindClientForWare(const Ware& ware)
13✔
1055
{
1056
    // If the ware is a coin or an armor, the goal is determined by another logic
1057
    if(ware.type == GoodType::Coins)
13✔
1058
        return FindClientForCoin(ware);
×
1059
    else if(ware.type == GoodType::Armor)
13✔
1060
        return FindClientForArmor(ware);
×
1061

1062
    // Warentyp herausfinden
1063
    GoodType gt = ware.type;
13✔
1064
    // All food is considered fish in the distribution table
1065
    Distribution& wareDistribution =
1066
      (gt == GoodType::Bread || gt == GoodType::Meat) ? distribution[GoodType::Fish] : distribution[gt];
13✔
1067

1068
    std::vector<ClientForWare> possibleClients;
13✔
1069

1070
    const noRoadNode* start = ware.GetLocation();
13✔
1071

1072
    // Bretter und Steine können evtl. auch Häfen für Expeditionen gebrauchen
1073
    if(gt == GoodType::Stones || gt == GoodType::Boards)
13✔
1074
    {
1075
        for(nobHarborBuilding* harbor : buildings.GetHarbors())
8✔
1076
        {
1077
            unsigned points = harbor->CalcDistributionPoints(gt);
×
1078
            if(!points)
×
1079
                continue;
×
1080

1081
            points += 10 * 30; // Verteilung existiert nicht, Expeditionen haben allerdings hohe Priorität
×
1082
            unsigned distance = world.CalcDistance(start->GetPos(), harbor->GetPos()) / 2;
×
1083
            possibleClients.push_back(ClientForWare(harbor, points > distance ? points - distance : 0, points));
×
1084
        }
1085
    }
1086

1087
    for(const auto bldType : wareDistribution.client_buildings)
50✔
1088
    {
1089
        // BuildingType::Headquarters sind Baustellen!!, da HQs ja sowieso nicht gebaut werden können
1090
        if(bldType == BuildingType::Headquarters)
37✔
1091
        {
1092
            // Bei Baustellen die Extraliste abfragen
1093
            for(noBuildingSite* bldSite : buildings.GetBuildingSites())
8✔
1094
            {
1095
                // Optimization: Ignore if unconnected
1096
                if(!bldSite->IsConnected())
×
1097
                    continue;
×
1098

1099
                unsigned points = bldSite->CalcDistributionPoints(gt);
×
1100
                if(!points)
×
1101
                    continue;
×
1102

1103
                points += wareDistribution.percent_buildings[BuildingType::Headquarters] * 30;
×
1104
                unsigned distance = world.CalcDistance(start->GetPos(), bldSite->GetPos()) / 2;
×
1105
                possibleClients.push_back(ClientForWare(bldSite, points > distance ? points - distance : 0, points));
×
1106
            }
1107
        } else
1108
        {
1109
            // Für übrige Gebäude
1110
            for(nobUsual* bld : buildings.GetBuildings(bldType))
29✔
1111
            {
1112
                // Optimization: Ignore if unconnected
1113
                if(!bld->IsConnected())
×
1114
                    continue;
×
1115

1116
                unsigned points = bld->CalcDistributionPoints(gt);
×
1117
                if(!points)
×
1118
                    continue; // Ware not needed
×
1119

1120
                if(!wareDistribution.goals.empty())
×
1121
                {
1122
                    if(bld->GetBuildingType()
×
1123
                       == static_cast<BuildingType>(wareDistribution.goals[wareDistribution.selected_goal]))
×
1124
                        points += 300;
×
1125
                    else if(points >= 300) // avoid overflows (async!)
×
1126
                        points -= 300;
×
1127
                    else
1128
                        points = 0;
×
1129
                }
1130

1131
                unsigned distance = world.CalcDistance(start->GetPos(), bld->GetPos()) / 2;
×
1132
                possibleClients.push_back(ClientForWare(bld, points > distance ? points - distance : 0, points));
×
1133
            }
1134
        }
1135
    }
1136

1137
    // sort our clients, highest score first
1138
    helpers::sort(possibleClients);
13✔
1139

1140
    noBaseBuilding* lastBld = nullptr;
13✔
1141
    noBaseBuilding* bestBld = nullptr;
13✔
1142
    unsigned best_points = 0;
13✔
1143
    for(auto& possibleClient : possibleClients)
13✔
1144
    {
1145
        unsigned path_length;
1146

1147
        // If our estimate is worse (or equal) best_points, the real value cannot be better.
1148
        // As our list is sorted, further entries cannot be better either, so stop searching.
1149
        if(possibleClient.estimate <= best_points)
×
1150
            break;
×
1151

1152
        // get rid of double building entries. TODO: why are there double entries!?
1153
        if(possibleClient.bld == lastBld)
×
1154
            continue;
×
1155

1156
        lastBld = possibleClient.bld;
×
1157

1158
        // Just to be sure no underflow happens...
1159
        if(possibleClient.points < best_points + 1)
×
1160
            continue;
×
1161

1162
        // Find path ONLY if it may be better. Pathfinding is limited to the worst path score that would lead to a
1163
        // better score. This eliminates the worst case scenario where all nodes in a split road network would be hit by
1164
        // the pathfinding only to conclude that there is no possible path.
1165
        if(world.FindPathForWareOnRoads(*start, *possibleClient.bld, &path_length, nullptr,
×
1166
                                        (possibleClient.points - best_points) * 2 - 1)
×
1167
           != RoadPathDirection::None)
×
1168
        {
1169
            unsigned score = possibleClient.points - (path_length / 2);
×
1170

1171
            // As we have limited our pathfinding to take a maximum of (points - best_points) * 2 - 1 steps,
1172
            // path_length / 2 can at most be points - best_points - 1, so the score will be greater than best_points.
1173
            // :)
1174
            RTTR_Assert(score > best_points);
×
1175

1176
            best_points = score;
×
1177
            bestBld = possibleClient.bld;
×
1178
        }
1179
    }
1180

1181
    if(bestBld && !wareDistribution.goals.empty())
13✔
1182
        wareDistribution.selected_goal =
×
1183
          (wareDistribution.selected_goal + 907) % unsigned(wareDistribution.goals.size());
×
1184

1185
    // Wenn kein Abnehmer gefunden wurde, muss es halt in ein Lagerhaus
1186
    if(!bestBld)
13✔
1187
        bestBld = FindWarehouseForWare(ware);
13✔
1188

1189
    return bestBld;
13✔
1190
}
1191

1192
nobBaseWarehouse* GamePlayer::FindWarehouseForWare(const Ware& ware) const
17✔
1193
{
1194
    if(IsMilitaryWarehouseGood(ware.type))
17✔
1195
    {
1196
        const nobBaseWarehouse* militaryWh = GetMilitaryWarehouse();
1✔
1197
        if(militaryWh && FW::AcceptsWare(ware.type)(*militaryWh))
1✔
1198
        {
1199
            if(ware.GetLocation()->GetPos() == militaryWh->GetPos())
1✔
1200
                return const_cast<nobBaseWarehouse*>(militaryWh);
1✔
1201

1202
            unsigned pathCosts = 0;
1✔
1203
            if(world.FindPathForWareOnRoads(*ware.GetLocation(), *militaryWh, &pathCosts) != RoadPathDirection::None)
1✔
1204
                return const_cast<nobBaseWarehouse*>(militaryWh);
1✔
1205
        }
1206
    }
1207

1208
    // Check whs that collect this ware
1209
    nobBaseWarehouse* wh = FindWarehouse(*ware.GetLocation(), FW::CollectsWare(ware.type), true, true);
16✔
1210
    // If there is none, check those that accept it
1211
    if(!wh)
16✔
1212
    {
1213
        // First find the ones, that do not send it right away (IMPORTANT: This avoids sending a ware to the wh that is
1214
        // sending the ware out)
1215
        wh = FindWarehouse(*ware.GetLocation(), FW::AcceptsWareButNoSend(ware.type), true, true);
16✔
1216
        // The others only if this fails
1217
        if(!wh)
16✔
1218
            wh = FindWarehouse(*ware.GetLocation(), FW::AcceptsWare(ware.type), true, true);
5✔
1219
    }
1220
    return wh;
16✔
1221
}
1222

1223
template<class T_GetPriority, class T_Buildings>
1224
typename T_Buildings::value_type GamePlayer::FindClientImpl(const Ware& ware, T_GetPriority&& getPriority,
×
1225
                                                            const T_Buildings& buildings) const
1226
{
1227
    // TODO(replay): Unify with FindClientForWare: Sort by estimated priority first for less path finding.
1228
    // See PR #1720
1229
    typename T_Buildings::value_type bestClient = nullptr;
×
1230
    unsigned bestPoints = 0;
×
1231
    const auto& wareLocation = *ware.GetLocation();
×
1232
    const MapPoint warePos = wareLocation.GetPos();
×
1233
    for(auto* bld : buildings)
×
1234
    {
1235
        // Optimization: Ignore if unconnected
1236
        if(!bld->IsConnected())
×
1237
            continue;
×
1238
        const auto points = getPriority(*bld);
×
1239

1240
        // Consider costs for reaching the building
1241
        // We want only: points - pathCosts > bestPoints, i.e. 0 <= pathCosts < points - bestPoints
1242
        // So only check if difference is strictly positive, which covers the points==0 case
1243
        if(points > bestPoints)
×
1244
        {
1245
            const unsigned maxPathCosts = points - bestPoints - 1;
×
1246
            const unsigned distance = world.CalcDistance(warePos, bld->GetPos());
×
1247
            if(distance > maxPathCosts)
×
1248
                continue;
×
1249
            unsigned pathCosts;
1250
            if(world.FindPathForWareOnRoads(wareLocation, *bld, &pathCosts, nullptr, maxPathCosts)
×
1251
               != RoadPathDirection::None)
×
1252
            {
1253
                RTTR_Assert(points > pathCosts && points - pathCosts > bestPoints);
×
1254
                bestPoints = points - pathCosts;
×
1255
                bestClient = bld;
×
1256
            }
1257
        }
1258
    }
1259
    return bestClient;
×
1260
}
1261

1262
nobBaseMilitary* GamePlayer::FindClientForCoin(const Ware& ware) const
×
1263
{
1264
    nobBaseMilitary* result = FindClientImpl(
×
1265
      ware, [](const nobMilitary& bld) { return bld.CalcCoinsPoints(); }, buildings.GetMilitaryBuildings());
×
1266
    // Send to warehouse if no military building needs the coins
1267
    if(!result)
×
1268
        result = FindWarehouseForWare(ware);
×
1269

1270
    return result;
×
1271
}
1272

1273
nobBaseMilitary* GamePlayer::FindClientForArmor(const Ware& ware) const
×
1274
{
1275
    nobBaseMilitary* result = FindClientImpl(
×
1276
      ware, [](const nobMilitary& bld) { return bld.CalcArmorPoints(); }, buildings.GetMilitaryBuildings());
×
1277
    // Send to warehouse if no military building needs the coins
1278
    if(!result)
×
1279
        result = FindWarehouseForWare(ware);
×
1280

1281
    return result;
×
1282
}
1283

1284
unsigned GamePlayer::GetBuidingSitePriority(const noBuildingSite* building_site)
×
1285
{
1286
    if(useCustomBuildOrder_)
×
1287
    {
1288
        // Spezielle Reihenfolge
1289

1290
        // Typ in der Reihenfolge suchen und Position als Priorität zurückgeben
1291
        for(unsigned i = 0; i < build_order.size(); ++i)
×
1292
        {
1293
            if(building_site->GetBuildingType() == build_order[i])
×
1294
                return i;
×
1295
        }
1296
    } else
1297
    {
1298
        // Reihenfolge der Bauaufträge, also was zuerst in Auftrag gegeben wurde, wird zuerst gebaut
1299
        unsigned i = 0;
×
1300
        for(noBuildingSite* bldSite : buildings.GetBuildingSites())
×
1301
        {
1302
            if(building_site == bldSite)
×
1303
                return i;
×
1304
            i++;
×
1305
        }
1306
    }
1307

1308
    LOG.write("GameClientPlayer::GetBuidingSitePriority: ERROR: Buildingsite or type of it not found in the list!\n");
×
1309
    RTTR_Assert(false);
×
1310
    // We may want to multiply this value so don't return the absolute max value
1311
    return std::numeric_limits<unsigned>::max() / 1000;
1312
}
1313

1314
void GamePlayer::ConvertTransportData(const TransportOrders& transport_data)
2✔
1315
{
1316
    for(const auto ware : helpers::EnumRange<GoodType>{})
168✔
1317
        transportPrio[ware] = GetTransportPrioFromOrdering(transport_data, ware);
80✔
1318
}
2✔
1319

1320
bool GamePlayer::IsAlly(const unsigned char playerId) const
12,363✔
1321
{
1322
    // Der Spieler ist ja auch zu sich selber verbündet
1323
    if(GetPlayerId() == playerId)
12,363✔
1324
        return true;
4,029✔
1325
    else
1326
        return GetPactState(PactType::TreatyOfAlliance, playerId) == PactState::Accepted;
8,334✔
1327
}
1328

1329
bool GamePlayer::IsAttackable(const unsigned char playerId) const
930✔
1330
{
1331
    // Verbündete dürfen nicht angegriffen werden
1332
    if(IsAlly(playerId))
930✔
1333
        return false;
44✔
1334
    else
1335
        // Ansonsten darf bei bestehendem Nichtangriffspakt ebenfalls nicht angegriffen werden
1336
        return GetPactState(PactType::NonAgressionPact, playerId) != PactState::Accepted;
886✔
1337
}
1338

1339
void GamePlayer::OrderTroops(nobMilitary& goal, std::array<unsigned, NUM_SOLDIER_RANKS> counts,
33✔
1340
                             unsigned total_max) const
1341
{
1342
    // Solange Lagerhäuser nach Soldaten absuchen, bis entweder keins mehr übrig ist oder alle Soldaten bestellt sind
1343
    nobBaseWarehouse* wh;
1344
    unsigned sum = 0;
33✔
1345
    do
4✔
1346
    {
1347
        std::array<bool, NUM_SOLDIER_RANKS> desiredRanks;
1348
        for(unsigned i = 0; i < NUM_SOLDIER_RANKS; i++)
222✔
1349
            desiredRanks[i] = counts[i] > 0;
185✔
1350

1351
        wh = FindWarehouse(goal, FW::HasAnyMatchingSoldier(desiredRanks), false, false);
37✔
1352
        if(wh)
37✔
1353
        {
1354
            wh->OrderTroops(goal, counts, total_max);
18✔
1355
            sum = std::accumulate(counts.begin(), counts.end(), 0u);
18✔
1356
        }
1357
    } while(total_max && sum && wh);
37✔
1358
}
33✔
1359

1360
void GamePlayer::RegulateAllTroops()
90✔
1361
{
1362
    for(nobMilitary* milBld : buildings.GetMilitaryBuildings())
96✔
1363
        milBld->RegulateTroops();
6✔
1364
}
90✔
1365

1366
/// Prüft von allen Militärgebäuden die Fahnen neu
1367
void GamePlayer::RecalcMilitaryFlags()
48✔
1368
{
1369
    for(nobMilitary* milBld : buildings.GetMilitaryBuildings())
72✔
1370
        milBld->LookForEnemyBuildings(nullptr);
24✔
1371
}
48✔
1372

1373
/// Sucht für Soldaten ein neues Militärgebäude, als Argument wird Referenz auf die
1374
/// entsprechende Soldatenanzahl im Lagerhaus verlangt
1375
void GamePlayer::NewSoldiersAvailable(const unsigned& soldier_count)
192✔
1376
{
1377
    RTTR_Assert(soldier_count > 0);
192✔
1378
    // solange laufen lassen, bis soldier_count = 0, d.h. der Soldat irgendwohin geschickt wurde
1379
    // Zuerst nach unbesetzten Militärgebäude schauen
1380
    for(nobMilitary* milBld : buildings.GetMilitaryBuildings())
215✔
1381
    {
1382
        if(milBld->IsNewBuilt())
23✔
1383
        {
1384
            milBld->RegulateTroops();
5✔
1385
            // Used that soldier? Go out
1386
            if(!soldier_count)
5✔
1387
                return;
×
1388
        }
1389
    }
1390

1391
    // Als nächstes Gebäude in Grenznähe
1392
    for(nobMilitary* milBld : buildings.GetMilitaryBuildings())
215✔
1393
    {
1394
        if(milBld->GetFrontierDistance() == FrontierDistance::Near)
23✔
1395
        {
1396
            milBld->RegulateTroops();
23✔
1397
            // Used that soldier? Go out
1398
            if(!soldier_count)
23✔
1399
                return;
×
1400
        }
1401
    }
1402

1403
    // Und den Rest ggf.
1404
    for(nobMilitary* milBld : buildings.GetMilitaryBuildings())
215✔
1405
    {
1406
        // already checked? -> skip
1407
        if(milBld->GetFrontierDistance() == FrontierDistance::Near || milBld->IsNewBuilt())
23✔
1408
            continue;
23✔
1409
        milBld->RegulateTroops();
×
1410
        if(!soldier_count) // used the soldier?
×
1411
            return;
×
1412
    }
1413
}
1414

1415
void GamePlayer::CallFlagWorker(const MapPoint pt, const Job job)
32✔
1416
{
1417
    auto* flag = world.GetSpecObj<noFlag>(pt);
32✔
1418
    if(!flag)
32✔
1419
        return;
2✔
1420
    /// Find wh with given job type (e.g. geologist, scout, ...)
1421
    nobBaseWarehouse* wh = FindWarehouse(*flag, FW::HasFigure(job, true), false, false);
30✔
1422

1423
    if(wh)
30✔
1424
        wh->OrderJob(job, *flag, true);
26✔
1425
}
1426

1427
bool GamePlayer::IsFlagWorker(const nofFlagWorker* flagworker)
×
1428
{
1429
    return helpers::contains(flagworkers, flagworker);
×
1430
}
1431

1432
void GamePlayer::FlagDestroyed(noFlag* flag)
775✔
1433
{
1434
    // Alle durchgehen und ggf. sagen, dass sie keine Flagge mehr haben, wenn das ihre Flagge war, die zerstört wurde
1435
    for(auto it = flagworkers.begin(); it != flagworkers.end();)
775✔
1436
    {
1437
        if((*it)->GetFlag() == flag)
×
1438
        {
1439
            (*it)->LostWork();
×
1440
            it = flagworkers.erase(it);
×
1441
        } else
1442
            ++it;
×
1443
    }
1444
}
775✔
1445

1446
void GamePlayer::RefreshDefenderList()
75✔
1447
{
1448
    shouldSendDefenderList.clear();
75✔
1449
    // Add as many true values as set in the settings, the rest will be false
1450
    for(unsigned i = 0; i < MILITARY_SETTINGS_SCALE[2]; ++i)
450✔
1451
        shouldSendDefenderList.push_back(i < militarySettings_[2]);
375✔
1452
    // und ordentlich schütteln
1453
    RANDOM_SHUFFLE2(shouldSendDefenderList, 0);
75✔
1454
}
75✔
1455

1456
void GamePlayer::ChangeMilitarySettings(const MilitarySettings& military_settings)
75✔
1457
{
1458
    for(unsigned i = 0; i < military_settings.size(); ++i)
675✔
1459
    {
1460
        // Sicherstellen, dass im validen Bereich
1461
        RTTR_Assert(military_settings[i] <= MILITARY_SETTINGS_SCALE[i]);
600✔
1462
        this->militarySettings_[i] = military_settings[i];
600✔
1463
    }
1464
    /// Truppen müssen neu kalkuliert werden
1465
    RegulateAllTroops();
75✔
1466
    /// Die Verteidigungsliste muss erneuert werden
1467
    RefreshDefenderList();
75✔
1468
}
75✔
1469

1470
/// Setzt neue Werkzeugeinstellungen
1471
void GamePlayer::ChangeToolsSettings(const ToolSettings& tools_settings,
6✔
1472
                                     const helpers::EnumArray<int8_t, Tool>& orderChanges)
1473
{
1474
    const bool settingsChanged = toolsSettings_ != tools_settings;
6✔
1475
    toolsSettings_ = tools_settings;
6✔
1476
    if(settingsChanged)
6✔
1477
        world.GetNotifications().publish(ToolNote(ToolNote::SettingsChanged, GetPlayerId()));
4✔
1478

1479
    for(const auto tool : helpers::enumRange<Tool>())
168✔
1480
    {
1481
        tools_ordered[tool] = helpers::clamp(tools_ordered[tool] + orderChanges[tool], 0, 100);
72✔
1482
        tools_ordered_delta[tool] -= orderChanges[tool];
72✔
1483

1484
        if(orderChanges[tool] != 0)
72✔
1485
        {
1486
            LOG.write(">> Committing an order of %1% for tool #%2%(%3%)\n", LogTarget::File) % (int)orderChanges[tool]
8✔
1487
              % static_cast<unsigned>(tool) % _(WARE_NAMES[TOOL_TO_GOOD[tool]]);
8✔
1488
            world.GetNotifications().publish(ToolNote(ToolNote::OrderPlaced, GetPlayerId()));
4✔
1489
        }
1490
    }
1491
}
6✔
1492

1493
/// Setzt neue Verteilungseinstellungen
1494
void GamePlayer::ChangeDistribution(const Distributions& distribution_settings)
6✔
1495
{
1496
    unsigned idx = 0;
6✔
1497
    for(const DistributionMapping& mapping : distributionMap)
180✔
1498
    {
1499
        distribution[std::get<0>(mapping)].percent_buildings[std::get<1>(mapping)] = distribution_settings[idx++];
174✔
1500
    }
1501

1502
    RecalcDistribution();
6✔
1503
}
6✔
1504

1505
/// Setzt neue Baureihenfolge-Einstellungen
1506
void GamePlayer::ChangeBuildOrder(bool useCustomBuildOrder, const BuildOrders& order_data)
2✔
1507
{
1508
    this->useCustomBuildOrder_ = useCustomBuildOrder;
2✔
1509
    this->build_order = order_data;
2✔
1510
}
2✔
1511

1512
bool GamePlayer::ShouldSendDefender()
46✔
1513
{
1514
    // Wenn wir schon am Ende sind, muss die Verteidgungsliste erneuert werden
1515
    if(shouldSendDefenderList.empty())
46✔
1516
        RefreshDefenderList();
×
1517

1518
    bool result = shouldSendDefenderList.back();
46✔
1519
    shouldSendDefenderList.pop_back();
46✔
1520
    return result;
46✔
1521
}
1522

1523
void GamePlayer::TestDefeat()
82✔
1524
{
1525
    // Nicht schon besiegt?
1526
    // Keine Militärgebäude, keine Lagerhäuser (HQ,Häfen) -> kein Land --> verloren
1527
    if(!isDefeated && buildings.GetMilitaryBuildings().empty() && buildings.GetStorehouses().empty())
82✔
1528
        Surrender();
29✔
1529
}
82✔
1530

1531
const nobHQ* GamePlayer::GetHQ() const
70✔
1532
{
1533
    return hqPos.isValid() ? GetGameWorld().GetSpecObj<nobHQ>(hqPos) : nullptr;
70✔
1534
}
1535

1536
nobHQ* GamePlayer::GetHQ()
62✔
1537
{
1538
    return const_cast<nobHQ*>(std::as_const(*this).GetHQ());
62✔
1539
}
1540

1541
const nobBaseWarehouse* GamePlayer::GetMilitaryWarehouse() const
3✔
1542
{
1543
    const MapPoint whPos = GetMilitaryWarehousePos();
3✔
1544
    auto* wh = whPos.isValid() ? GetGameWorld().GetSpecObj<nobBaseWarehouse>(whPos) : nullptr;
3✔
1545
    if(wh && wh->GetPlayer() == GetPlayerId())
3✔
1546
        return wh;
3✔
NEW
1547
    return GetHQ();
×
1548
}
1549

1550
bool GamePlayer::IsMilitaryWarehouse(const nobBaseWarehouse& wh) const
2✔
1551
{
1552
    const nobBaseWarehouse* militaryWh = GetMilitaryWarehouse();
2✔
1553
    return militaryWh && militaryWh->GetPos() == wh.GetPos();
2✔
1554
}
1555

1556
void GamePlayer::SetMilitaryWarehouse(const MapPoint pt)
2✔
1557
{
1558
    auto* newMilitaryWh = GetGameWorld().GetSpecObj<nobBaseWarehouse>(pt);
2✔
1559
    if(!newMilitaryWh || newMilitaryWh->GetPlayer() != GetPlayerId())
2✔
NEW
1560
        return;
×
1561

1562
    militaryWarehousePos = pt;
2✔
1563
    for(nobBaseWarehouse* wh : buildings.GetStorehouses())
6✔
1564
    {
1565
        const bool isMilitaryWh = wh->GetPos() == militaryWarehousePos;
4✔
1566
        for(const GoodType good : MILITARY_WAREHOUSE_GOODS)
16✔
1567
        {
1568
            InventorySetting desiredSetting = wh->GetInventorySetting(good);
12✔
1569
            if(isMilitaryWh)
12✔
1570
                desiredSetting = EInventorySetting::Collect;
6✔
1571
            else if(desiredSetting.IsSet(EInventorySetting::Collect))
6✔
NEW
1572
                desiredSetting = InventorySetting();
×
1573

1574
            if(wh->GetInventorySetting(good) != desiredSetting)
12✔
1575
                wh->SetInventorySetting(good, desiredSetting);
6✔
1576
            if(wh->GetInventorySettingVisual(good) != desiredSetting)
12✔
1577
                wh->SetInventorySettingVisual(good, desiredSetting);
7✔
1578
        }
1579
    }
1580
}
1581

1582
void GamePlayer::Surrender()
32✔
1583
{
1584
    if(isDefeated)
32✔
1585
        return;
1✔
1586

1587
    isDefeated = true;
31✔
1588

1589
    // GUI Bescheid sagen
1590
    if(world.GetGameInterface())
31✔
1591
        world.GetGameInterface()->GI_PlayerDefeated(GetPlayerId());
×
1592
}
1593

1594
void GamePlayer::SetStatisticValue(StatisticType type, unsigned value)
×
1595
{
1596
    statisticCurrentData[type] = value;
×
1597
}
×
1598

1599
void GamePlayer::ChangeStatisticValue(StatisticType type, int change)
2,166✔
1600
{
1601
    RTTR_Assert(change >= 0 || statisticCurrentData[type] >= static_cast<unsigned>(-change));
2,166✔
1602
    statisticCurrentData[type] += change;
2,166✔
1603
}
2,166✔
1604

1605
void GamePlayer::IncreaseMerchandiseStatistic(GoodType type)
4✔
1606
{
1607
    // Einsortieren...
1608
    switch(type)
4✔
1609
    {
1610
        case GoodType::Wood: statisticCurrentMerchandiseData[0]++; break;
×
1611
        case GoodType::Boards: statisticCurrentMerchandiseData[1]++; break;
×
1612
        case GoodType::Stones: statisticCurrentMerchandiseData[2]++; break;
×
1613
        case GoodType::Fish:
×
1614
        case GoodType::Bread:
1615
        case GoodType::Meat: statisticCurrentMerchandiseData[3]++; break;
×
1616
        case GoodType::Water: statisticCurrentMerchandiseData[4]++; break;
×
1617
        case GoodType::Beer: statisticCurrentMerchandiseData[5]++; break;
×
1618
        case GoodType::Coal: statisticCurrentMerchandiseData[6]++; break;
×
1619
        case GoodType::IronOre: statisticCurrentMerchandiseData[7]++; break;
×
1620
        case GoodType::Gold: statisticCurrentMerchandiseData[8]++; break;
×
1621
        case GoodType::Iron: statisticCurrentMerchandiseData[9]++; break;
×
1622
        case GoodType::Coins: statisticCurrentMerchandiseData[10]++; break;
×
1623
        case GoodType::Tongs:
3✔
1624
        case GoodType::Axe:
1625
        case GoodType::Saw:
1626
        case GoodType::PickAxe:
1627
        case GoodType::Hammer:
1628
        case GoodType::Shovel:
1629
        case GoodType::Crucible:
1630
        case GoodType::RodAndLine:
1631
        case GoodType::Scythe:
1632
        case GoodType::Cleaver:
1633
        case GoodType::Rollingpin:
1634
        case GoodType::Bow: statisticCurrentMerchandiseData[11]++; break;
3✔
1635
        case GoodType::ShieldVikings:
×
1636
        case GoodType::ShieldAfricans:
1637
        case GoodType::ShieldRomans:
1638
        case GoodType::ShieldJapanese:
1639
        case GoodType::Sword: statisticCurrentMerchandiseData[12]++; break;
×
1640
        case GoodType::Boat: statisticCurrentMerchandiseData[13]++; break;
×
1641
        default: break;
1✔
1642
    }
1643
}
4✔
1644

1645
/// Calculates current statistics
1646
void GamePlayer::CalcStatistics()
6✔
1647
{
1648
    // Waren aus der Inventur zählen
1649
    statisticCurrentData[StatisticType::Merchandise] = 0;
6✔
1650
    for(const auto i : helpers::enumRange<GoodType>())
504✔
1651
        statisticCurrentData[StatisticType::Merchandise] += global_inventory[i];
240✔
1652

1653
    // Bevölkerung aus der Inventur zählen
1654
    statisticCurrentData[StatisticType::Inhabitants] = 0;
6✔
1655
    for(const auto i : helpers::enumRange<Job>())
480✔
1656
        statisticCurrentData[StatisticType::Inhabitants] += global_inventory[i];
228✔
1657

1658
    // Militär aus der Inventur zählen
1659
    statisticCurrentData[StatisticType::Military] =
12✔
1660
      global_inventory.people[Job::Private] + global_inventory.people[Job::PrivateFirstClass] * 2
6✔
1661
      + global_inventory.people[Job::Sergeant] * 3 + global_inventory.people[Job::Officer] * 4
6✔
1662
      + global_inventory.people[Job::General] * 5;
6✔
1663

1664
    // Produktivität berechnen
1665
    statisticCurrentData[StatisticType::Productivity] = buildings.CalcAverageProductivity();
6✔
1666

1667
    // Total points for tournament games
1668
    statisticCurrentData[StatisticType::Tournament] =
12✔
1669
      statisticCurrentData[StatisticType::Military] + 3 * statisticCurrentData[StatisticType::Vanquished];
6✔
1670
}
6✔
1671

1672
void GamePlayer::StatisticStep()
6✔
1673
{
1674
    CalcStatistics();
6✔
1675

1676
    // 15-min-Statistik ein Feld weiterschieben
1677
    for(const auto i : helpers::enumRange<StatisticType>())
132✔
1678
    {
1679
        statistic[StatisticTime::T15Minutes].data[i][incrStatIndex(statistic[StatisticTime::T15Minutes].currentIndex)] =
54✔
1680
          statisticCurrentData[i];
54✔
1681
    }
1682
    for(unsigned i = 0; i < NUM_STAT_MERCHANDISE_TYPES; ++i)
90✔
1683
    {
1684
        statistic[StatisticTime::T15Minutes]
84✔
1685
          .merchandiseData[i][incrStatIndex(statistic[StatisticTime::T15Minutes].currentIndex)] =
168✔
1686
          statisticCurrentMerchandiseData[i];
84✔
1687
    }
1688
    statistic[StatisticTime::T15Minutes].currentIndex =
6✔
1689
      incrStatIndex(statistic[StatisticTime::T15Minutes].currentIndex);
6✔
1690

1691
    statistic[StatisticTime::T15Minutes].counter++;
6✔
1692

1693
    // Prüfen ob 4mal 15-min-Statistik weitergeschoben wurde, wenn ja: 1-h-Statistik weiterschieben
1694
    // und aktuellen Wert der 15min-Statistik benutzen
1695
    // gleiches für die 4h und 16h Statistik
1696
    for(const auto t : helpers::enumRange<StatisticTime>())
60✔
1697
    {
1698
        if(t == StatisticTime(helpers::MaxEnumValue_v<StatisticTime>))
24✔
1699
            break;
6✔
1700
        const auto nextT = StatisticTime(rttr::enum_cast(t) + 1);
18✔
1701
        if(statistic[t].counter == 4)
18✔
1702
        {
1703
            statistic[t].counter = 0;
×
1704
            for(const auto i : helpers::enumRange<StatisticType>())
×
1705
            {
1706
                statistic[nextT].data[i][incrStatIndex(statistic[nextT].currentIndex)] = statisticCurrentData[i];
×
1707
            }
1708

1709
            // Summe für den Zeitraum berechnen (immer 4 Zeitschritte der jeweils kleineren Statistik)
1710
            for(unsigned i = 0; i < NUM_STAT_MERCHANDISE_TYPES; ++i)
×
1711
            {
1712
                statistic[nextT].merchandiseData[i][incrStatIndex(statistic[nextT].currentIndex)] =
×
1713
                  statisticCurrentMerchandiseData[i]
×
1714
                  + statistic[t].merchandiseData[i][decrStatIndex(statistic[t].currentIndex, 1)]
×
1715
                  + statistic[t].merchandiseData[i][decrStatIndex(statistic[t].currentIndex, 2)]
×
1716
                  + statistic[t].merchandiseData[i][decrStatIndex(statistic[t].currentIndex, 3)];
×
1717
            }
1718

1719
            statistic[nextT].currentIndex = incrStatIndex(statistic[nextT].currentIndex);
×
1720
            statistic[nextT].counter++;
×
1721
        }
1722
    }
1723

1724
    // Warenstatistikzähler nullen
1725
    statisticCurrentMerchandiseData.fill(0);
6✔
1726
}
6✔
1727

1728
GamePlayer::Pact::Pact(SerializedGameData& sgd)
112✔
1729
    : duration(sgd.PopUnsignedInt()), start(sgd.PopUnsignedInt()), accepted(sgd.PopBool()), want_cancel(sgd.PopBool())
112✔
1730
{}
112✔
1731

1732
void GamePlayer::Pact::Serialize(SerializedGameData& sgd) const
224✔
1733
{
1734
    sgd.PushUnsignedInt(duration);
224✔
1735
    sgd.PushUnsignedInt(start);
224✔
1736
    sgd.PushBool(accepted);
224✔
1737
    sgd.PushBool(want_cancel);
224✔
1738
}
224✔
1739

1740
void GamePlayer::PactChanged(const PactType pt)
24✔
1741
{
1742
    // Recheck military flags as the border (to an enemy) might have changed
1743
    RecalcMilitaryFlags();
24✔
1744

1745
    // Ggf. den GUI Bescheid sagen, um Sichtbarkeiten etc. neu zu berechnen
1746
    if(pt == PactType::TreatyOfAlliance)
24✔
1747
    {
1748
        if(world.GetGameInterface())
6✔
1749
            world.GetGameInterface()->GI_TreatyOfAllianceChanged(GetPlayerId());
×
1750
    }
1751
}
24✔
1752

1753
void GamePlayer::SuggestPact(const unsigned char targetPlayerId, const PactType pt, const unsigned duration)
10✔
1754
{
1755
    // Don't try to make pact with self
1756
    if(targetPlayerId == GetPlayerId())
10✔
1757
        return;
1✔
1758

1759
    if(!pacts[targetPlayerId][pt].accepted && duration > 0)
9✔
1760
    {
1761
        pacts[targetPlayerId][pt].duration = duration;
8✔
1762
        pacts[targetPlayerId][pt].start = world.GetEvMgr().GetCurrentGF();
8✔
1763
        GamePlayer targetPlayer = world.GetPlayer(targetPlayerId);
16✔
1764
        if(targetPlayer.isHuman())
8✔
1765
            targetPlayer.SendPostMessage(std::make_unique<DiplomacyPostQuestion>(
6✔
1766
              world.GetEvMgr().GetCurrentGF(), pt, pacts[targetPlayerId][pt].start, *this, duration));
12✔
1767
        else if(world.HasLua())
2✔
1768
            world.GetLua().EventSuggestPact(pt, GetPlayerId(), targetPlayerId, duration);
2✔
1769
    }
1770
}
1771

1772
void GamePlayer::AcceptPact(const unsigned id, const PactType pt, const unsigned char targetPlayer)
21✔
1773
{
1774
    if(!pacts[targetPlayer][pt].accepted && pacts[targetPlayer][pt].duration > 0 && pacts[targetPlayer][pt].start == id)
21✔
1775
    {
1776
        MakePact(pt, targetPlayer, pacts[targetPlayer][pt].duration);
8✔
1777
        world.GetPlayer(targetPlayer).MakePact(pt, GetPlayerId(), pacts[targetPlayer][pt].duration);
8✔
1778
        PactChanged(pt);
8✔
1779
        world.GetPlayer(targetPlayer).PactChanged(pt);
8✔
1780
        if(world.HasLua())
8✔
1781
            world.GetLua().EventPactCreated(pt, GetPlayerId(), targetPlayer, pacts[targetPlayer][pt].duration);
3✔
1782
    }
1783
}
21✔
1784

1785
/// Bündnis (real, d.h. spielentscheidend) abschließen
1786
void GamePlayer::MakePact(const PactType pt, const unsigned char other_player, const unsigned duration)
16✔
1787
{
1788
    pacts[other_player][pt].accepted = true;
16✔
1789
    pacts[other_player][pt].start = world.GetEvMgr().GetCurrentGF();
16✔
1790
    pacts[other_player][pt].duration = duration;
16✔
1791
    pacts[other_player][pt].want_cancel = false;
16✔
1792

1793
    SendPostMessage(
16✔
1794
      std::make_unique<PostMsg>(world.GetEvMgr().GetCurrentGF(), pt, world.GetPlayer(other_player), true));
32✔
1795
}
16✔
1796

1797
/// Zeigt an, ob ein Pakt besteht
1798
PactState GamePlayer::GetPactState(const PactType pt, const unsigned char other_player) const
9,370✔
1799
{
1800
    // Prüfen, ob Bündnis in Kraft ist
1801
    if(pacts[other_player][pt].duration)
9,370✔
1802
    {
1803
        if(!pacts[other_player][pt].accepted)
5,040✔
1804
            return PactState::InProgress;
16✔
1805

1806
        if(pacts[other_player][pt].duration == DURATION_INFINITE
5,024✔
1807
           || world.GetEvMgr().GetCurrentGF() < pacts[other_player][pt].start + pacts[other_player][pt].duration)
5,024✔
1808
            return PactState::Accepted;
5,022✔
1809
    }
1810

1811
    return PactState::None;
4,332✔
1812
}
1813

1814
/// all allied players get a letter with the location
1815
void GamePlayer::NotifyAlliesOfLocation(const MapPoint pt)
6✔
1816
{
1817
    for(unsigned i = 0; i < world.GetNumPlayers(); ++i)
24✔
1818
    {
1819
        if(i != GetPlayerId() && IsAlly(i))
18✔
1820
            world.GetPlayer(i).SendPostMessage(std::make_unique<PostMsg>(
10✔
1821
              world.GetEvMgr().GetCurrentGF(), _("Your ally wishes to notify you of this location"),
5✔
1822
              PostCategory::Diplomacy, pt));
10✔
1823
    }
1824
}
6✔
1825

1826
/// Gibt die verbleibende Dauer zurück, die ein Bündnis noch laufen wird (DURATION_INFINITE = für immer)
1827
unsigned GamePlayer::GetRemainingPactTime(const PactType pt, const unsigned char other_player) const
125✔
1828
{
1829
    if(pacts[other_player][pt].duration)
125✔
1830
    {
1831
        if(pacts[other_player][pt].accepted)
88✔
1832
        {
1833
            if(pacts[other_player][pt].duration == DURATION_INFINITE)
76✔
1834
                return DURATION_INFINITE;
×
1835
            else if(world.GetEvMgr().GetCurrentGF() <= pacts[other_player][pt].start + pacts[other_player][pt].duration)
76✔
1836
                return ((pacts[other_player][pt].start + pacts[other_player][pt].duration)
76✔
1837
                        - world.GetEvMgr().GetCurrentGF());
76✔
1838
        }
1839
    }
1840

1841
    return 0;
49✔
1842
}
1843

1844
/// Gibt Einverständnis, dass dieser Spieler den Pakt auflösen will
1845
/// Falls dieser Spieler einen Bündnisvorschlag gemacht hat, wird dieser dagegen zurückgenommen
1846
void GamePlayer::CancelPact(const PactType pt, const unsigned char otherPlayerIdx)
8✔
1847
{
1848
    // Don't try to cancel pact with self
1849
    if(otherPlayerIdx == GetPlayerId())
8✔
1850
        return;
2✔
1851

1852
    // Besteht bereits ein Bündnis?
1853
    if(pacts[otherPlayerIdx][pt].accepted)
6✔
1854
    {
1855
        // Vermerken, dass der Spieler das Bündnis auflösen will
1856
        pacts[otherPlayerIdx][pt].want_cancel = true;
6✔
1857

1858
        // Will der andere Spieler das Bündnis auch auflösen?
1859
        GamePlayer& otherPlayer = world.GetPlayer(otherPlayerIdx);
6✔
1860
        if(otherPlayer.pacts[GetPlayerId()][pt].want_cancel)
6✔
1861
        {
1862
            // Dann wird das Bündnis aufgelöst
1863
            pacts[otherPlayerIdx][pt].accepted = false;
2✔
1864
            pacts[otherPlayerIdx][pt].duration = 0;
2✔
1865
            pacts[otherPlayerIdx][pt].want_cancel = false;
2✔
1866

1867
            otherPlayer.pacts[GetPlayerId()][pt].accepted = false;
2✔
1868
            otherPlayer.pacts[GetPlayerId()][pt].duration = 0;
2✔
1869
            otherPlayer.pacts[GetPlayerId()][pt].want_cancel = false;
2✔
1870

1871
            // Den Spielern eine Informationsnachricht schicken
1872
            world.GetPlayer(otherPlayerIdx)
2✔
1873
              .SendPostMessage(std::make_unique<PostMsg>(world.GetEvMgr().GetCurrentGF(), pt, *this, false));
2✔
1874
            SendPostMessage(
2✔
1875
              std::make_unique<PostMsg>(world.GetEvMgr().GetCurrentGF(), pt, world.GetPlayer(otherPlayerIdx), false));
4✔
1876
            PactChanged(pt);
2✔
1877
            otherPlayer.PactChanged(pt);
2✔
1878
            if(world.HasLua())
2✔
1879
                world.GetLua().EventPactCanceled(pt, GetPlayerId(), otherPlayerIdx);
1✔
1880
        } else
1881
        {
1882
            // Ansonsten den anderen Spieler fragen, ob der das auch so sieht
1883
            if(otherPlayer.isHuman())
4✔
1884
                otherPlayer.SendPostMessage(std::make_unique<DiplomacyPostQuestion>(
3✔
1885
                  world.GetEvMgr().GetCurrentGF(), pt, pacts[otherPlayerIdx][pt].start, *this));
6✔
1886
            else if(!world.HasLua() || world.GetLua().EventCancelPactRequest(pt, GetPlayerId(), otherPlayerIdx))
1✔
1887
            {
1888
                // AI accepts cancels, if there is no lua-interace
1889
                pacts[otherPlayerIdx][pt].accepted = false;
1✔
1890
                pacts[otherPlayerIdx][pt].duration = 0;
1✔
1891
                pacts[otherPlayerIdx][pt].want_cancel = false;
1✔
1892

1893
                otherPlayer.pacts[GetPlayerId()][pt].accepted = false;
1✔
1894
                otherPlayer.pacts[GetPlayerId()][pt].duration = 0;
1✔
1895
                otherPlayer.pacts[GetPlayerId()][pt].want_cancel = false;
1✔
1896

1897
                if(world.HasLua())
1✔
1898
                    world.GetLua().EventPactCanceled(pt, GetPlayerId(), otherPlayerIdx);
1✔
1899
            }
1900
        }
1901
    } else
1902
    {
1903
        // Es besteht kein Bündnis, also unseren Bündnisvorschlag wieder zurücknehmen
1904
        pacts[otherPlayerIdx][pt].duration = 0;
×
1905
    }
1906
}
1907

1908
void GamePlayer::MakeStartPacts()
66✔
1909
{
1910
    // Reset pacts
1911
    for(unsigned i = 0; i < world.GetNumPlayers(); ++i)
262✔
1912
    {
1913
        for(const auto z : helpers::enumRange<PactType>())
1,568✔
1914
            pacts[i][z] = Pact();
392✔
1915
    }
1916

1917
    // No team -> No pacts
1918
    if(team == Team::None)
66✔
1919
        return;
4✔
1920
    RTTR_Assert(isTeam(team));
62✔
1921

1922
    // Create ally- and non-aggression-pact for all players of same team
1923
    for(unsigned i = 0; i < world.GetNumPlayers(); ++i)
245✔
1924
    {
1925
        if(team != world.GetPlayer(i).team)
183✔
1926
            continue;
75✔
1927
        for(const auto z : helpers::enumRange<PactType>())
864✔
1928
        {
1929
            pacts[i][z].duration = DURATION_INFINITE;
216✔
1930
            pacts[i][z].start = 0;
216✔
1931
            pacts[i][z].accepted = true;
216✔
1932
            pacts[i][z].want_cancel = false;
216✔
1933
        }
1934
    }
1935
}
1936

1937
bool GamePlayer::IsWareRegistred(const Ware& ware)
148✔
1938
{
1939
    return helpers::contains(ware_list, &ware);
148✔
1940
}
1941

1942
bool GamePlayer::IsWareDependent(const Ware& ware)
15✔
1943
{
1944
    for(nobBaseWarehouse* wh : buildings.GetStorehouses())
44✔
1945
    {
1946
        if(wh->IsWareDependent(ware))
29✔
1947
            return true;
×
1948
    }
1949

1950
    return false;
15✔
1951
}
1952

1953
void GamePlayer::IncreaseInventoryWare(const GoodType ware, const unsigned count)
1,651✔
1954
{
1955
    global_inventory.Add(ConvertShields(ware), count);
1,651✔
1956
}
1,651✔
1957

1958
void GamePlayer::DecreaseInventoryWare(const GoodType ware, const unsigned count)
2,380✔
1959
{
1960
    global_inventory.Remove(ConvertShields(ware), count);
2,380✔
1961
}
2,380✔
1962

1963
/// Registriert ein Schiff beim Einwohnermeldeamt
1964
void GamePlayer::RegisterShip(noShip& ship)
22✔
1965
{
1966
    ships.push_back(&ship);
22✔
1967
    // Evtl bekommt das Schiffchen gleich was zu tun?
1968
    GetJobForShip(ship);
22✔
1969
}
22✔
1970

1971
struct ShipForHarbor
1972
{
1973
    noShip* ship;
1974
    uint32_t estimate;
1975

1976
    ShipForHarbor(noShip* ship, uint32_t estimate) : ship(ship), estimate(estimate) {}
19✔
1977

1978
    bool operator<(const ShipForHarbor& b) const noexcept
×
1979
    {
1980
        return (estimate < b.estimate) || (estimate == b.estimate && ship->GetObjId() < b.ship->GetObjId());
×
1981
    }
1982
};
1983

1984
/// Schiff für Hafen bestellen
1985
bool GamePlayer::OrderShip(nobHarborBuilding& hb)
33✔
1986
{
1987
    std::vector<ShipForHarbor> sfh;
66✔
1988

1989
    // we need more ships than those that are already on their way? limit search to idle ships
1990
    if(GetShipsToHarbor(hb) < hb.GetNumNeededShips())
33✔
1991
    {
1992
        for(noShip* ship : ships)
66✔
1993
        {
1994
            if(ship->IsIdling() && world.IsHarborAtSea(world.GetHarborPointID(hb.GetPos()), ship->GetSeaID()))
33✔
1995
                sfh.push_back(ShipForHarbor(ship, world.CalcDistance(hb.GetPos(), ship->GetPos())));
19✔
1996
        }
1997
    } else
1998
    {
1999
        for(noShip* ship : ships)
×
2000
        {
2001
            if((ship->IsIdling() && world.IsHarborAtSea(world.GetHarborPointID(hb.GetPos()), ship->GetSeaID()))
×
2002
               || ship->IsGoingToHarbor(hb))
×
2003
            {
2004
                sfh.push_back(ShipForHarbor(ship, world.CalcDistance(hb.GetPos(), ship->GetPos())));
×
2005
            }
2006
        }
2007
    }
2008

2009
    helpers::sort(sfh);
33✔
2010

2011
    noShip* best_ship = nullptr;
33✔
2012
    uint32_t best_distance = std::numeric_limits<uint32_t>::max();
33✔
2013
    std::vector<Direction> best_route;
66✔
2014

2015
    for(auto& it : sfh)
43✔
2016
    {
2017
        uint32_t distance;
2018
        std::vector<Direction> route;
19✔
2019

2020
        // the estimate (air-line distance) for this and all other ships in the list is already worse than what we
2021
        // found? disregard the rest
2022
        if(it.estimate >= best_distance)
19✔
2023
            break;
×
2024

2025
        noShip& ship = *it.ship;
19✔
2026

2027
        if(world.FindShipPathToHarbor(ship.GetPos(), hb.GetHarborPosID(), ship.GetSeaID(), &route, &distance))
19✔
2028
        {
2029
            // ship already there?
2030
            if(distance == 0)
19✔
2031
            {
2032
                hb.ShipArrived(ship);
9✔
2033
                return true;
9✔
2034
            }
2035

2036
            if(distance < best_distance)
10✔
2037
            {
2038
                best_ship = &ship;
10✔
2039
                best_distance = distance;
10✔
2040
                best_route = route;
10✔
2041
            }
2042
        }
2043
    }
2044

2045
    // only order ships not already on their way
2046
    if(best_ship && best_ship->IsIdling())
24✔
2047
    {
2048
        best_ship->GoToHarbor(hb, best_route);
10✔
2049

2050
        return true;
10✔
2051
    }
2052

2053
    return false;
14✔
2054
}
2055

2056
/// Meldet das Schiff wieder ab
2057
void GamePlayer::RemoveShip(noShip* ship)
×
2058
{
2059
    auto it = helpers::find(ships, ship);
×
2060
    RTTR_Assert(it != ships.end());
×
2061
    ships.erase(it);
×
2062
}
×
2063

2064
/// Versucht, für ein untätiges Schiff eine Arbeit zu suchen
2065
void GamePlayer::GetJobForShip(noShip& ship)
32✔
2066
{
2067
    // Evtl. steht irgendwo eine Expedition an und das Schiff kann diese übernehmen
2068
    nobHarborBuilding* best = nullptr;
32✔
2069
    int best_points = 0;
32✔
2070
    std::vector<Direction> best_route;
32✔
2071

2072
    // Beste Weglänge, die ein Schiff zurücklegen muss, welches gerade nichts zu tun hat
2073
    for(nobHarborBuilding* harbor : buildings.GetHarbors())
66✔
2074
    {
2075
        // Braucht der Hafen noch Schiffe?
2076
        if(harbor->GetNumNeededShips() == 0)
34✔
2077
            continue;
34✔
2078

2079
        // Anzahl der Schiffe ermitteln, die diesen Hafen bereits anfahren
2080
        unsigned ships_coming = GetShipsToHarbor(*harbor);
×
2081

2082
        // Evtl. kommen schon genug?
2083
        if(harbor->GetNumNeededShips() <= ships_coming)
×
2084
            continue;
×
2085

2086
        // liegen wir am gleichen Meer?
2087
        if(world.IsHarborAtSea(harbor->GetHarborPosID(), ship.GetSeaID()))
×
2088
        {
2089
            unsigned length;
2090
            std::vector<Direction> route;
×
2091

2092
            if(world.FindShipPathToHarbor(ship.GetPos(), harbor->GetHarborPosID(), ship.GetSeaID(), &route, &length))
×
2093
            {
2094
                // ship already there?
2095
                if(length == 0)
×
2096
                {
2097
                    harbor->ShipArrived(ship);
×
2098
                    return;
×
2099
                }
2100

2101
                // Punkte ausrechnen
2102
                int points = harbor->GetNeedForShip(ships_coming) - length;
×
2103
                if(points > best_points || !best)
×
2104
                {
2105
                    best = harbor;
×
2106
                    best_points = points;
×
2107
                    best_route = route;
×
2108
                }
2109
            }
2110
        }
2111
    }
2112

2113
    // Einen Hafen gefunden?
2114
    if(best)
32✔
2115
        // Dann bekommt das gleich der Hafen
2116
        ship.GoToHarbor(*best, best_route);
×
2117
}
2118

2119
unsigned GamePlayer::GetShipID(const noShip& ship) const
16✔
2120
{
2121
    return static_cast<unsigned>(helpers::indexOf(ships, &ship));
16✔
2122
}
2123

2124
/// Gibt ein Schiff anhand der ID zurück bzw. nullptr, wenn keines mit der ID existiert
2125
noShip* GamePlayer::GetShipByID(const unsigned ship_id) const
24✔
2126
{
2127
    if(ship_id >= ships.size())
24✔
2128
        return nullptr;
×
2129
    else
2130
        return ships[ship_id];
24✔
2131
}
2132

2133
void GamePlayer::AddHarborsAtSea(std::vector<nobHarborBuilding*>& harborBuildings, const SeaId seaId) const
237✔
2134
{
2135
    for(nobHarborBuilding* harbor : buildings.GetHarbors())
720✔
2136
    {
2137
        if(helpers::contains(harborBuildings, harbor))
483✔
2138
            continue;
230✔
2139

2140
        if(world.IsHarborAtSea(harbor->GetHarborPosID(), seaId))
253✔
2141
            harborBuildings.push_back(harbor);
253✔
2142
    }
2143
}
237✔
2144

2145
/// Gibt die Anzahl der Schiffe, die einen bestimmten Hafen ansteuern, zurück
2146
unsigned GamePlayer::GetShipsToHarbor(const nobHarborBuilding& hb) const
73✔
2147
{
2148
    unsigned count = 0;
73✔
2149
    for(const auto* ship : ships)
146✔
2150
    {
2151
        if(ship->IsGoingToHarbor(hb))
73✔
2152
            ++count;
5✔
2153
    }
2154

2155
    return count;
73✔
2156
}
2157

2158
/// Sucht einen Hafen in der Nähe, wo dieses Schiff seine Waren abladen kann
2159
/// gibt true zurück, falls erfolgreich
2160
bool GamePlayer::FindHarborForUnloading(noShip* ship, const MapPoint start, HarborId* goalHarborId,
5✔
2161
                                        std::vector<Direction>* route, nobHarborBuilding* exception)
2162
{
2163
    nobHarborBuilding* best = nullptr;
5✔
2164
    unsigned best_distance = 0xffffffff;
5✔
2165

2166
    for(nobHarborBuilding* hb : buildings.GetHarbors())
8✔
2167
    {
2168
        // Bestimmten Hafen ausschließen
2169
        if(hb == exception)
3✔
2170
            continue;
×
2171

2172
        // Prüfen, ob Hafen an das Meer, wo sich das Schiff gerade befindet, angrenzt
2173
        if(!world.IsHarborAtSea(hb->GetHarborPosID(), ship->GetSeaID()))
3✔
2174
            continue;
×
2175

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

2179
        // Kürzerer Weg als bisher bestes Ziel?
2180
        if(distance < best_distance)
3✔
2181
        {
2182
            best_distance = distance;
3✔
2183
            best = hb;
3✔
2184
        }
2185
    }
2186

2187
    // Hafen gefunden?
2188
    if(best)
5✔
2189
    {
2190
        // Weg dorthin suchen
2191
        route->clear();
3✔
2192
        *goalHarborId = best->GetHarborPosID();
3✔
2193
        if(world.FindShipPathToHarbor(start, best->GetHarborPosID(), ship->GetSeaID(), route, nullptr))
3✔
2194
            return true;
3✔
2195
    }
2196

2197
    return false;
2✔
2198
}
2199

2200
void GamePlayer::TestForEmergencyProgramm()
×
2201
{
2202
    // we are already defeated, do not even think about an emergency program - it's too late :-(
2203
    if(isDefeated)
×
2204
        return;
×
2205

2206
    // In Lagern vorhandene Bretter und Steine zählen
2207
    unsigned boards = 0;
×
2208
    unsigned stones = 0;
×
2209
    for(nobBaseWarehouse* wh : buildings.GetStorehouses())
×
2210
    {
2211
        boards += wh->GetInventory().goods[GoodType::Boards];
×
2212
        stones += wh->GetInventory().goods[GoodType::Stones];
×
2213
    }
2214

2215
    // Emergency happens, if we have less than 10 boards or stones...
2216
    bool isNewEmergency = boards <= 10 || stones <= 10;
×
2217
    // ...and no woddcutter or sawmill
2218
    isNewEmergency &=
×
2219
      buildings.GetBuildings(BuildingType::Woodcutter).empty() || buildings.GetBuildings(BuildingType::Sawmill).empty();
×
2220

2221
    // Wenn nötig, Notfallprogramm auslösen
2222
    if(isNewEmergency)
×
2223
    {
2224
        if(!emergency)
×
2225
        {
2226
            emergency = true;
×
2227
            SendPostMessage(std::make_unique<PostMsg>(
×
2228
              world.GetEvMgr().GetCurrentGF(), _("The emergency program has been activated."), PostCategory::Economy));
×
2229
        }
2230
    } else
2231
    {
2232
        // Sobald Notfall vorbei, Notfallprogramm beenden, evtl. Baustellen wieder mit Kram versorgen
2233
        if(emergency)
×
2234
        {
2235
            emergency = false;
×
2236
            SendPostMessage(std::make_unique<PostMsg>(world.GetEvMgr().GetCurrentGF(),
×
2237
                                                      _("The emergency program has been deactivated."),
×
2238
                                                      PostCategory::Economy));
×
2239
            FindMaterialForBuildingSites();
×
2240
        }
2241
    }
2242
}
2243

2244
/// Testet die Bündnisse, ob sie nicht schon abgelaufen sind
2245
void GamePlayer::TestPacts()
20✔
2246
{
2247
    for(unsigned i = 0; i < world.GetNumPlayers(); ++i)
80✔
2248
    {
2249
        if(i == GetPlayerId())
60✔
2250
            continue;
20✔
2251

2252
        for(const auto pact : helpers::enumRange<PactType>())
320✔
2253
        {
2254
            // Pact not running
2255
            if(pacts[i][pact].duration == 0)
80✔
2256
                continue;
60✔
2257
            if(GetPactState(pact, i) == PactState::None)
20✔
2258
            {
2259
                // Pact was running but is expired -> Cancel for both players
2260
                pacts[i][pact].duration = 0;
2✔
2261
                pacts[i][pact].accepted = false;
2✔
2262
                GamePlayer& otherPlayer = world.GetPlayer(i);
2✔
2263
                RTTR_Assert(otherPlayer.pacts[GetPlayerId()][pact].duration);
2✔
2264
                RTTR_Assert(otherPlayer.pacts[GetPlayerId()][pact].accepted);
2✔
2265
                otherPlayer.pacts[GetPlayerId()][pact].duration = 0;
2✔
2266
                otherPlayer.pacts[GetPlayerId()][pact].accepted = false;
2✔
2267
                // And notify
2268
                PactChanged(pact);
2✔
2269
                otherPlayer.PactChanged(pact);
2✔
2270
            }
2271
        }
2272
    }
2273
}
20✔
2274

2275
bool GamePlayer::CanBuildCatapult() const
1✔
2276
{
2277
    // Wenn AddonId::LIMIT_CATAPULTS nicht aktiv ist, bauen immer erlaubt
2278
    if(!world.GetGGS().isEnabled(AddonId::LIMIT_CATAPULTS)) //-V807
1✔
2279
        return true;
1✔
2280

2281
    BuildingCount bc = buildings.GetBuildingNums();
×
2282

2283
    unsigned max = 0;
×
2284
    // proportional?
2285
    if(world.GetGGS().getSelection(AddonId::LIMIT_CATAPULTS) == 1)
×
2286
    {
2287
        max = int(bc.buildings[BuildingType::Barracks] * 0.125 + bc.buildings[BuildingType::Guardhouse] * 0.25
×
2288
                  + bc.buildings[BuildingType::Watchtower] * 0.5 + bc.buildings[BuildingType::Fortress]
×
2289
                  + 0.111); // to avoid rounding errors
×
2290
    } else if(world.GetGGS().getSelection(AddonId::LIMIT_CATAPULTS) < 8)
×
2291
    {
2292
        const std::array<unsigned, 6> limits = {{0, 3, 5, 10, 20, 30}};
×
2293
        max = limits[world.GetGGS().getSelection(AddonId::LIMIT_CATAPULTS) - 2];
×
2294
    }
2295

2296
    return bc.buildings[BuildingType::Catapult] + bc.buildingSites[BuildingType::Catapult] < max;
×
2297
}
2298

2299
/// A ship has discovered new hostile territory --> determines if this is new
2300
/// i.e. there is a sufficient distance to older locations
2301
/// Returns true if yes and false if not
2302
bool GamePlayer::ShipDiscoveredHostileTerritory(const MapPoint location)
25✔
2303
{
2304
    // Prüfen, ob Abstand zu bisherigen Punkten nicht zu klein
2305
    for(const auto& enemies_discovered_by_ship : enemies_discovered_by_ships)
25✔
2306
    {
2307
        if(world.CalcDistance(enemies_discovered_by_ship, location) < 30)
24✔
2308
            return false;
24✔
2309
    }
2310

2311
    // Nein? Dann haben wir ein neues Territorium gefunden
2312
    enemies_discovered_by_ships.push_back(location);
1✔
2313

2314
    return true;
1✔
2315
}
2316

2317
/// For debug only
2318
bool GamePlayer::IsDependentFigure(const noFigure& fig)
259✔
2319
{
2320
    for(const nobBaseWarehouse* wh : buildings.GetStorehouses())
625✔
2321
    {
2322
        if(wh->IsDependentFigure(fig))
366✔
2323
            return true;
×
2324
    }
2325
    return false;
259✔
2326
}
2327

2328
std::vector<nobBaseWarehouse*> GamePlayer::GetWarehousesForTrading(const nobBaseWarehouse& goalWh) const
18✔
2329
{
2330
    std::vector<nobBaseWarehouse*> result;
18✔
2331

2332
    // Don't try to trade with us!
2333
    if(goalWh.GetPlayer() == GetPlayerId())
18✔
2334
        return result;
6✔
2335

2336
    const MapPoint goalFlagPos = goalWh.GetFlagPos();
12✔
2337

2338
    TradePathCache& tradePathCache = world.GetTradePathCache();
12✔
2339
    for(nobBaseWarehouse* wh : buildings.GetStorehouses())
24✔
2340
    {
2341
        // Is there a trade path from this warehouse to wh? (flag to flag)
2342
        if(tradePathCache.pathExists(wh->GetFlagPos(), goalFlagPos, GetPlayerId()))
12✔
2343
            result.push_back(wh);
4✔
2344
    }
2345

2346
    return result;
12✔
2347
}
2348

2349
struct WarehouseDistanceComparator
2350
{
2351
    // Reference warehouse position, to which we want to calc the distance
2352
    const MapPoint refWareHousePos_;
2353
    /// GameWorld
2354
    const GameWorld& gwg_;
2355

2356
    WarehouseDistanceComparator(const nobBaseWarehouse& refWareHouse, const GameWorld& world)
15✔
2357
        : refWareHousePos_(refWareHouse.GetPos()), gwg_(world)
15✔
2358
    {}
15✔
2359

2360
    bool operator()(nobBaseWarehouse* const wh1, nobBaseWarehouse* const wh2) const
×
2361
    {
2362
        unsigned dist1 = gwg_.CalcDistance(wh1->GetPos(), refWareHousePos_);
×
2363
        unsigned dist2 = gwg_.CalcDistance(wh2->GetPos(), refWareHousePos_);
×
2364
        return (dist1 < dist2) || (dist1 == dist2 && wh1->GetObjId() < wh2->GetObjId());
×
2365
    }
2366
};
2367

2368
/// Send wares to warehouse wh
2369
void GamePlayer::Trade(nobBaseWarehouse* goalWh, const boost_variant2<GoodType, Job>& what, unsigned count) const
23✔
2370
{
2371
    if(!world.GetGGS().isEnabled(AddonId::TRADE))
23✔
2372
        return;
11✔
2373

2374
    if(count == 0)
20✔
2375
        return;
3✔
2376

2377
    // Don't try to trade with us!
2378
    if(goalWh->GetPlayer() == GetPlayerId())
17✔
2379
        return;
1✔
2380

2381
    // No trades with enemies
2382
    if(!IsAlly(goalWh->GetPlayer()))
16✔
2383
        return;
1✔
2384

2385
    const MapPoint goalFlagPos = goalWh->GetFlagPos();
15✔
2386

2387
    std::vector<nobBaseWarehouse*> whs(buildings.GetStorehouses().begin(), buildings.GetStorehouses().end());
15✔
2388
    helpers::sort(whs, WarehouseDistanceComparator(*goalWh, world));
15✔
2389
    TradePathCache& tradePathCache = world.GetTradePathCache();
15✔
2390
    for(nobBaseWarehouse* wh : whs)
27✔
2391
    {
2392
        // Get available wares
2393
        const unsigned available =
2394
          boost::variant2::visit(composeVisitor([wh](GoodType gt) { return wh->GetAvailableWaresForTrading(gt); },
22✔
2395
                                                [wh](Job job) { return wh->GetAvailableFiguresForTrading(job); }),
23✔
2396
                                 what);
15✔
2397
        if(available == 0)
15✔
2398
            continue;
×
2399

2400
        const unsigned actualCount = std::min(available, count);
15✔
2401

2402
        // Find a trade path from flag to flag
2403
        TradeRoute tr(world, GetPlayerId(), wh->GetFlagPos(), goalFlagPos);
15✔
2404

2405
        // Found a path?
2406
        if(tr.IsValid())
15✔
2407
        {
2408
            // Add to cache for future searches
2409
            tradePathCache.addEntry(tr.GetTradePath(), GetPlayerId());
14✔
2410

2411
            wh->StartTradeCaravane(what, actualCount, tr, goalWh);
14✔
2412
            count -= available;
14✔
2413
            if(count == 0)
14✔
2414
                return;
3✔
2415
        }
2416
    }
2417
}
2418

2419
bool GamePlayer::IsBuildingEnabled(BuildingType type) const
1,036✔
2420
{
2421
    return building_enabled[type] || (isHuman() && world.GetGameInterface()->GI_GetCheats().areAllBuildingsEnabled());
1,036✔
2422
}
2423

2424
void GamePlayer::FillVisualSettings(VisualSettings& visualSettings) const
2✔
2425
{
2426
    Distributions& visDistribution = visualSettings.distribution;
2✔
2427
    unsigned visIdx = 0;
2✔
2428
    for(const DistributionMapping& mapping : distributionMap)
60✔
2429
    {
2430
        visDistribution[visIdx++] = distribution[std::get<0>(mapping)].percent_buildings[std::get<1>(mapping)];
58✔
2431
    }
2432

2433
    visualSettings.useCustomBuildOrder = useCustomBuildOrder_;
2✔
2434
    visualSettings.build_order = build_order;
2✔
2435

2436
    visualSettings.transport_order = GetOrderingFromTransportPrio(transportPrio);
2✔
2437

2438
    visualSettings.military_settings = militarySettings_;
2✔
2439
    visualSettings.tools_settings = toolsSettings_;
2✔
2440
}
2✔
2441

2442
#define INSTANTIATE_FINDWH(Cond)                                                                                \
2443
    template nobBaseWarehouse* GamePlayer::FindWarehouse(const noRoadNode&, const Cond&, bool, bool, unsigned*, \
2444
                                                         const RoadSegment*) const
2445

2446
INSTANTIATE_FINDWH(FW::HasMinWares);
2447
INSTANTIATE_FINDWH(FW::HasFigure);
2448
INSTANTIATE_FINDWH(FW::HasWareAndFigure);
2449
INSTANTIATE_FINDWH(FW::HasAnyMatchingSoldier);
2450
INSTANTIATE_FINDWH(FW::AcceptsWare);
2451
INSTANTIATE_FINDWH(FW::AcceptsFigure);
2452
INSTANTIATE_FINDWH(FW::CollectsWare);
2453
INSTANTIATE_FINDWH(FW::CollectsFigure);
2454
INSTANTIATE_FINDWH(FW::HasWareButNoCollect);
2455
INSTANTIATE_FINDWH(FW::HasFigureButNoCollect);
2456
INSTANTIATE_FINDWH(FW::AcceptsFigureButNoSend);
2457
INSTANTIATE_FINDWH(FW::NoCondition);
2458

2459
#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