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

Return-To-The-Roots / s25client / 29573564406

17 Jul 2026 10:28AM UTC coverage: 50.441% (-0.02%) from 50.465%
29573564406

Pull #1961

github

web-flow
Merge 9dac6095c into 4b3146470
Pull Request #1961: fixed emergency protocol by removing sheduled wares requested before ...

0 of 14 new or added lines in 1 file covered. (0.0%)

6 existing lines in 4 files now uncovered.

23278 of 46149 relevant lines covered (50.44%)

47753.44 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

116
    // Baureihenfolge füllen
117
    unsigned curPrio = 0;
428✔
118
    for(const auto bld : helpers::enumRange<BuildingType>())
35,952✔
119
    {
120
        if(bld == BuildingType::Headquarters || !BuildingProperties::IsValid(bld))
17,120✔
121
            continue;
856✔
122

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

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

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

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

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

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

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

171
    sgd.PushBool(isDefeated);
14✔
172

173
    buildings.Serialize(sgd);
14✔
174

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

265
    sgd.PopContainer(shouldSendDefenderList);
7✔
266

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

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

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

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

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

298
    useCustomBuildOrder_ = sgd.PopBool();
7✔
299

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

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

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

319
        std::copy(build_order_raw.begin(), build_order_raw.end(), build_order.begin());
×
320

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

335
    helpers::popContainer(sgd, militarySettings_);
7✔
336
    helpers::popContainer(sgd, toolsSettings_);
7✔
337

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

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

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

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

367
    // Visuelle Einstellungen festlegen
368

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

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

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

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

393
    emergency = sgd.PopBool();
7✔
394
}
395

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

403
    unsigned best_length = std::numeric_limits<unsigned>::max();
4,729✔
404

405
    for(nobBaseWarehouse* wh : buildings.GetStorehouses())
15,753✔
406
    {
407
        // Lagerhaus geeignet?
408
        RTTR_Assert(wh);
11,029✔
409
        if(!isWarehouseGood(*wh))
11,029✔
410
            continue;
10,567✔
411

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

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

438
    if(length)
4,724✔
439
        *length = best_length;
538✔
440

441
    return best;
4,724✔
442
}
443

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

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

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

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

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

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

482
    if(bldType == BuildingType::HarborBuilding)
682✔
483
    {
484
        // Schiff durchgehen und denen Bescheid sagen
485
        for(noShip* ship : ships)
62✔
486
            ship->NewHarborBuilt(static_cast<nobHarborBuilding*>(bld));
13✔
487
    } else if(bldType == BuildingType::Headquarters)
633✔
488
    {
489
        // If there is more than one HQ, keep the original position.
490
        if(!hqPos.isValid())
381✔
491
            hqPos = bld->GetPos();
372✔
492
    } else if(BuildingProperties::IsMilitary(bldType))
252✔
493
    {
494
        auto* milBld = static_cast<nobMilitary*>(bld);
130✔
495
        // New built? -> Calculate frontier distance
496
        if(milBld->IsNewBuilt())
130✔
497
            milBld->LookForEnemyBuildings();
120✔
498
    }
499
}
682✔
500

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

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

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

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

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

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

545
    // Alle Lost-Wares müssen gucken, ob sie ein Lagerhaus finden
546
    FindClientForLostWares();
160✔
547

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

557
void GamePlayer::AddRoad(RoadSegment* rs)
23✔
558
{
559
    roads.push_back(rs);
23✔
560
}
23✔
561

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

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

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

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

653
        ++it;
12✔
654
    }
655

656
    // Alle Häfen müssen ihre Figuren den Weg überprüfen lassen
657
    for(nobHarborBuilding* hb : buildings.GetHarbors())
246✔
658
    {
659
        hb->ExamineShipRouteOfPeople();
20✔
660
    }
661
}
226✔
662

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

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

686
    // überhaupt nen Weg gefunden?
687
    // Welche Flagge benutzen?
688
    if(best[0] && (!best[1] || length[0] < length[1]))
268✔
689
        best[0]->OrderCarrier(*rs.GetF1(), rs);
28✔
690
    else if(best[1])
240✔
691
        best[1]->OrderCarrier(*rs.GetF2(), rs);
35✔
692
    else
693
        return false;
205✔
694
    return true;
63✔
695
}
696

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

702
void GamePlayer::RecalcDistribution()
432✔
703
{
704
    GoodType lastWare = GoodType::Nothing;
432✔
705
    for(const DistributionMapping& mapping : distributionMap)
12,960✔
706
    {
707
        if(lastWare == std::get<0>(mapping))
12,528✔
708
            continue;
9,072✔
709
        lastWare = std::get<0>(mapping);
3,456✔
710
        RecalcDistributionOfWare(std::get<0>(mapping));
3,456✔
711
    }
712
}
432✔
713

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

719
    // 1. Anteile der einzelnen Waren ausrechnen
720

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

725
    unsigned goal_count = 0;
3,456✔
726

727
    for(const auto bld : helpers::enumRange<BuildingType>())
290,304✔
728
    {
729
        uint8_t percentForCurBld = distribution[ware].percent_buildings[bld];
138,240✔
730
        if(percentForCurBld)
138,240✔
731
        {
732
            distribution[ware].client_buildings.push_back(bld);
12,528✔
733
            goal_count += percentForCurBld;
12,528✔
734
            bldPercentageMap.emplace_back(bld, percentForCurBld);
12,528✔
735
        }
736
    }
737

738
    // TODO: evtl noch die counts miteinander kürzen (ggt berechnen)
739

740
    // Array für die Gebäudtypen erstellen
741

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

746
    // just drop them in the list, the distribution will be handled by going through this list using a prime as step
747
    // (see GameClientPlayer::FindClientForWare)
748
    for(const BldEntry& bldEntry : bldPercentageMap)
15,984✔
749
    {
750
        for(unsigned char i = 0; i < bldEntry.second; ++i)
80,871✔
751
            wareGoals.push_back(bldEntry.first);
68,343✔
752
    }
753

754
    distribution[ware].selected_goal = 0;
3,456✔
755
}
3,456✔
756

757
void GamePlayer::FindCarrierForAllRoads()
272✔
758
{
759
    for(RoadSegment* rs : roads)
629✔
760
    {
761
        if(!rs->hasCarrier(0))
357✔
762
            FindCarrierForRoad(*rs);
240✔
763
    }
764
}
272✔
765

766
void GamePlayer::FindMaterialForBuildingSites()
311✔
767
{
768
    for(noBuildingSite* bldSite : buildings.GetBuildingSites())
335✔
769
        bldSite->OrderConstructionMaterial();
24✔
770
}
311✔
771

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

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

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

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

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

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

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

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

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

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

854
    nobBaseWarehouse* wh = FindWarehouse(goal, FW::HasFigure(job, true), false, false);
85✔
855

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

863
    return false;
67✔
864
}
865

866
void GamePlayer::FindWarehouseForAllJobs()
271✔
867
{
868
    for(auto it = jobs_wanted.begin(); it != jobs_wanted.end();)
346✔
869
    {
870
        if(FindWarehouseForJob(it->job, *it->workplace))
75✔
871
            it = jobs_wanted.erase(it);
8✔
872
        else
873
            ++it;
67✔
874
    }
875
}
271✔
876

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

NEW
892
static bool IsWareFineWithEmergencyProtocol(GoodType goodType, const noBaseBuilding& goal){
×
NEW
893
    return (goodType != GoodType::Boards && goodType != GoodType::Stones) || goal.GetBuildingType() == BuildingType::Woodcutter || goal.GetBuildingType() == BuildingType::Sawmill;
×
894
}
895

896
Ware* GamePlayer::OrderWare(const GoodType ware, noBaseBuilding& goal)
112✔
897
{
898
    /// Gibt es ein Lagerhaus mit dieser Ware?
899
    nobBaseWarehouse* wh = FindWarehouse(goal, FW::HasMinWares(ware, 1), false, true);
112✔
900

901
    if(wh)
112✔
902
    {
903
        // Prüfe ob Notfallprogramm aktiv
904
        if(!emergency)
84✔
905
            return wh->OrderWare(ware, goal);
84✔
906
        else
907
        {
908
            // Wenn Notfallprogramm aktiv nur an Holzfäller und Sägewerke Bretter/Steine liefern
NEW
909
            if(IsWareFineWithEmergencyProtocol(ware,goal))
×
910
                return wh->OrderWare(ware, goal);
×
911
            else
912
                return nullptr;
×
913
        }
914
    } else // no warehouse can deliver the ware -> check all our wares for lost wares that might match the order
915
    {
916
        unsigned bestLength = std::numeric_limits<unsigned>::max();
28✔
917
        Ware* bestWare = nullptr;
28✔
918
        for(Ware* curWare : ware_list)
28✔
919
        {
920
            if(curWare->IsLostWare() && curWare->type == ware)
×
921
            {
922
                // got a lost ware with a road to goal -> find best
923
                unsigned curLength = curWare->CheckNewGoalForLostWare(goal);
×
924
                if(curLength < bestLength)
×
925
                {
926
                    bestLength = curLength;
×
927
                    bestWare = curWare;
×
928
                }
929
            }
930
        }
931
        if(bestWare)
28✔
932
        {
933
            bestWare->SetNewGoalForLostWare(goal);
×
934
            return bestWare;
×
935
        }
936
    }
937
    return nullptr;
28✔
938
}
939

940
nofCarrier* GamePlayer::OrderDonkey(RoadSegment& road) const
1✔
941
{
942
    std::array<unsigned, 2> length;
943
    std::array<nobBaseWarehouse*, 2> best;
944

945
    // 1. Flagge des Weges
946
    best[0] = FindWarehouse(*road.GetF1(), FW::HasFigure(Job::PackDonkey, false), false, false, length.data(), &road);
1✔
947
    // 2. Flagge des Weges
948
    best[1] = FindWarehouse(*road.GetF2(), FW::HasFigure(Job::PackDonkey, false), false, false, &length[1], &road);
1✔
949

950
    // überhaupt nen Weg gefunden?
951
    // Welche Flagge benutzen?
952
    if(best[0] && (!best[1] || length[0] < length[1]))
1✔
953
        return best[0]->OrderDonkey(road, *road.GetF1());
×
954
    else if(best[1])
1✔
955
        return best[1]->OrderDonkey(road, *road.GetF2());
×
956
    else
957
        return nullptr;
1✔
958
}
959

960
RoadSegment* GamePlayer::FindRoadForDonkey(noRoadNode& start, noRoadNode** goal)
75✔
961
{
962
    // Bisher höchste Trägerproduktivität und die entsprechende Straße dazu
963
    unsigned best_productivity = 0;
75✔
964
    RoadSegment* best_road = nullptr;
75✔
965
    // Beste Flagge dieser Straße
966
    *goal = nullptr;
75✔
967

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

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

993
            // Kein Weg führt hin, nächste Straße bitte
994
            if(!current_best_goal)
×
995
                continue;
×
996

997
            // Jeweiligen Weg bestimmen
998
            unsigned current_best_way = (roadSeg->GetF1() == current_best_goal) ? length1 : length2;
×
999

1000
            // Produktivität ausrechnen, *10 die Produktivität + die Wegstrecke, damit die
1001
            // auch noch mit einberechnet wird
1002
            unsigned current_productivity = 10 * roadSeg->getCarrier(0)->GetProductivity() + current_best_way;
×
1003

1004
            // Besser als der bisher beste?
1005
            if(current_productivity > best_productivity)
×
1006
            {
1007
                // Dann wird der vom Thron gestoßen
1008
                best_productivity = current_productivity;
×
1009
                best_road = roadSeg;
×
1010
                *goal = current_best_goal;
×
1011
            }
1012
        }
1013
    }
1014

1015
    return best_road;
75✔
1016
}
1017

1018
struct ClientForWare
1019
{
1020
    noBaseBuilding* bld;
1021
    unsigned estimate; // points minus half the optimal distance
1022
    unsigned points;
1023

1024
    ClientForWare(noBaseBuilding* bld, unsigned estimate, unsigned points)
×
1025
        : bld(bld), estimate(estimate), points(points)
×
1026
    {}
×
1027

1028
    bool operator<(const ClientForWare& b) const noexcept
×
1029
    {
1030
        // use estimate, points and object id (as tie breaker) for sorting
1031
        if(estimate != b.estimate)
×
1032
            return estimate > b.estimate;
×
1033
        else if(points != b.points)
×
1034
            return points > b.points;
×
1035
        else
1036
            return bld->GetObjId() > b.bld->GetObjId();
×
1037
    }
1038
};
1039

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

1048
    // Warentyp herausfinden
1049
    GoodType gt = ware.type;
12✔
1050
    // All food is considered fish in the distribution table
1051
    Distribution& wareDistribution =
1052
      (gt == GoodType::Bread || gt == GoodType::Meat) ? distribution[GoodType::Fish] : distribution[gt];
12✔
1053

1054
    std::vector<ClientForWare> possibleClients;
12✔
1055

1056
    const noRoadNode* start = ware.GetLocation();
12✔
1057

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

1067
            points += 10 * 30; // Verteilung existiert nicht, Expeditionen haben allerdings hohe Priorität
×
1068
            unsigned distance = world.CalcDistance(start->GetPos(), harbor->GetPos()) / 2;
×
1069
            possibleClients.push_back(ClientForWare(harbor, points > distance ? points - distance : 0, points));
×
1070
        }
1071
    }
1072

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

1085
                unsigned points = bldSite->CalcDistributionPoints(gt);
×
1086
                if(!points)
×
1087
                    continue;
×
1088

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

1102
                unsigned points = bld->CalcDistributionPoints(gt);
×
1103
                if(!points)
×
1104
                    continue; // Ware not needed
×
1105

1106
                if(!wareDistribution.goals.empty())
×
1107
                {
1108
                    if(bld->GetBuildingType()
×
1109
                       == static_cast<BuildingType>(wareDistribution.goals[wareDistribution.selected_goal]))
×
1110
                        points += 300;
×
1111
                    else if(points >= 300) // avoid overflows (async!)
×
1112
                        points -= 300;
×
1113
                    else
1114
                        points = 0;
×
1115
                }
1116

1117
                unsigned distance = world.CalcDistance(start->GetPos(), bld->GetPos()) / 2;
×
1118
                possibleClients.push_back(ClientForWare(bld, points > distance ? points - distance : 0, points));
×
1119
            }
1120
        }
1121
    }
1122

1123
    // sort our clients, highest score first
1124
    helpers::sort(possibleClients);
12✔
1125

1126
    noBaseBuilding* lastBld = nullptr;
12✔
1127
    noBaseBuilding* bestBld = nullptr;
12✔
1128
    unsigned best_points = 0;
12✔
1129
    for(auto& possibleClient : possibleClients)
12✔
1130
    {
1131
        unsigned path_length;
1132

1133
        // If our estimate is worse (or equal) best_points, the real value cannot be better.
1134
        // As our list is sorted, further entries cannot be better either, so stop searching.
1135
        if(possibleClient.estimate <= best_points)
×
1136
            break;
×
1137

1138
        // get rid of double building entries. TODO: why are there double entries!?
1139
        if(possibleClient.bld == lastBld)
×
1140
            continue;
×
1141

1142
        lastBld = possibleClient.bld;
×
1143

1144
        // Just to be sure no underflow happens...
1145
        if(possibleClient.points < best_points + 1)
×
1146
            continue;
×
1147

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

1157
            // As we have limited our pathfinding to take a maximum of (points - best_points) * 2 - 1 steps,
1158
            // path_length / 2 can at most be points - best_points - 1, so the score will be greater than best_points.
1159
            // :)
1160
            RTTR_Assert(score > best_points);
×
1161

1162
            best_points = score;
×
1163
            bestBld = possibleClient.bld;
×
1164
        }
1165
    }
1166

1167
    if(bestBld && !wareDistribution.goals.empty())
12✔
1168
        wareDistribution.selected_goal =
×
1169
          (wareDistribution.selected_goal + 907) % unsigned(wareDistribution.goals.size());
×
1170

1171
    // Wenn kein Abnehmer gefunden wurde, muss es halt in ein Lagerhaus
1172
    if(!bestBld)
12✔
1173
        bestBld = FindWarehouseForWare(ware);
12✔
1174

1175
    return bestBld;
12✔
1176
}
1177

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

1195
template<class T_GetPriority, class T_Buildings>
1196
typename T_Buildings::value_type GamePlayer::FindClientImpl(const Ware& ware, T_GetPriority&& getPriority,
×
1197
                                                            const T_Buildings& buildings) const
1198
{
1199
    // TODO(replay): Unify with FindClientForWare: Sort by estimated priority first for less path finding.
1200
    // See PR #1720
1201
    typename T_Buildings::value_type bestClient = nullptr;
×
1202
    unsigned bestPoints = 0;
×
1203
    const auto& wareLocation = *ware.GetLocation();
×
1204
    const MapPoint warePos = wareLocation.GetPos();
×
1205
    for(auto* bld : buildings)
×
1206
    {
1207
        // Optimization: Ignore if unconnected
1208
        if(!bld->IsConnected())
×
1209
            continue;
×
1210
        const auto points = getPriority(*bld);
×
1211

1212
        // Consider costs for reaching the building
1213
        // We want only: points - pathCosts > bestPoints, i.e. 0 <= pathCosts < points - bestPoints
1214
        // So only check if difference is strictly positive, which covers the points==0 case
1215
        if(points > bestPoints)
×
1216
        {
1217
            const unsigned maxPathCosts = points - bestPoints - 1;
×
1218
            const unsigned distance = world.CalcDistance(warePos, bld->GetPos());
×
1219
            if(distance > maxPathCosts)
×
1220
                continue;
×
1221
            unsigned pathCosts;
1222
            if(world.FindPathForWareOnRoads(wareLocation, *bld, &pathCosts, nullptr, maxPathCosts)
×
1223
               != RoadPathDirection::None)
×
1224
            {
1225
                RTTR_Assert(points > pathCosts && points - pathCosts > bestPoints);
×
1226
                bestPoints = points - pathCosts;
×
1227
                bestClient = bld;
×
1228
            }
1229
        }
1230
    }
1231
    return bestClient;
×
1232
}
1233

1234
nobBaseMilitary* GamePlayer::FindClientForCoin(const Ware& ware) const
×
1235
{
1236
    nobBaseMilitary* result = FindClientImpl(
×
1237
      ware, [](const nobMilitary& bld) { return bld.CalcCoinsPoints(); }, buildings.GetMilitaryBuildings());
×
1238
    // Send to warehouse if no military building needs the coins
1239
    if(!result)
×
1240
        result = FindWarehouseForWare(ware);
×
1241

1242
    return result;
×
1243
}
1244

1245
nobBaseMilitary* GamePlayer::FindClientForArmor(const Ware& ware) const
×
1246
{
1247
    nobBaseMilitary* result = FindClientImpl(
×
1248
      ware, [](const nobMilitary& bld) { return bld.CalcArmorPoints(); }, buildings.GetMilitaryBuildings());
×
1249
    // Send to warehouse if no military building needs the coins
1250
    if(!result)
×
1251
        result = FindWarehouseForWare(ware);
×
1252

1253
    return result;
×
1254
}
1255

1256
unsigned GamePlayer::GetBuidingSitePriority(const noBuildingSite* building_site)
×
1257
{
1258
    if(useCustomBuildOrder_)
×
1259
    {
1260
        // Spezielle Reihenfolge
1261

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

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

1286
void GamePlayer::ConvertTransportData(const TransportOrders& transport_data)
2✔
1287
{
1288
    for(const auto ware : helpers::EnumRange<GoodType>{})
168✔
1289
        transportPrio[ware] = GetTransportPrioFromOrdering(transport_data, ware);
80✔
1290
}
2✔
1291

1292
bool GamePlayer::IsAlly(const unsigned char playerId) const
12,365✔
1293
{
1294
    // Der Spieler ist ja auch zu sich selber verbündet
1295
    if(GetPlayerId() == playerId)
12,365✔
1296
        return true;
4,029✔
1297
    else
1298
        return GetPactState(PactType::TreatyOfAlliance, playerId) == PactState::Accepted;
8,336✔
1299
}
1300

1301
bool GamePlayer::IsAttackable(const unsigned char playerId) const
936✔
1302
{
1303
    // Verbündete dürfen nicht angegriffen werden
1304
    if(IsAlly(playerId))
936✔
1305
        return false;
44✔
1306
    else
1307
        // Ansonsten darf bei bestehendem Nichtangriffspakt ebenfalls nicht angegriffen werden
1308
        return GetPactState(PactType::NonAgressionPact, playerId) != PactState::Accepted;
892✔
1309
}
1310

1311
void GamePlayer::OrderTroops(nobMilitary& goal, std::array<unsigned, NUM_SOLDIER_RANKS> counts,
36✔
1312
                             unsigned total_max) const
1313
{
1314
    // Solange Lagerhäuser nach Soldaten absuchen, bis entweder keins mehr übrig ist oder alle Soldaten bestellt sind
1315
    nobBaseWarehouse* wh;
1316
    unsigned sum = 0;
36✔
1317
    do
5✔
1318
    {
1319
        std::array<bool, NUM_SOLDIER_RANKS> desiredRanks;
1320
        for(unsigned i = 0; i < NUM_SOLDIER_RANKS; i++)
246✔
1321
            desiredRanks[i] = counts[i] > 0;
205✔
1322

1323
        wh = FindWarehouse(goal, FW::HasAnyMatchingSoldier(desiredRanks), false, false);
41✔
1324
        if(wh)
41✔
1325
        {
1326
            wh->OrderTroops(goal, counts, total_max);
19✔
1327
            sum = std::accumulate(counts.begin(), counts.end(), 0u);
19✔
1328
        }
1329
    } while(total_max && sum && wh);
41✔
1330
}
36✔
1331

1332
void GamePlayer::RegulateAllTroops()
96✔
1333
{
1334
    for(nobMilitary* milBld : buildings.GetMilitaryBuildings())
104✔
1335
        milBld->RegulateTroops();
8✔
1336
}
96✔
1337

1338
void GamePlayer::RecalcMilitaryFlags()
48✔
1339
{
1340
    for(nobMilitary* milBld : buildings.GetMilitaryBuildings())
72✔
1341
        milBld->LookForEnemyBuildings(nullptr);
24✔
1342
}
48✔
1343

1344
void GamePlayer::NewSoldiersAvailable(const unsigned& soldier_count)
194✔
1345
{
1346
    RTTR_Assert(soldier_count > 0);
194✔
1347
    // solange laufen lassen, bis soldier_count = 0, d.h. der Soldat irgendwohin geschickt wurde
1348
    // Zuerst nach unbesetzten Militärgebäude schauen
1349
    for(nobMilitary* milBld : buildings.GetMilitaryBuildings())
218✔
1350
    {
1351
        if(milBld->IsNewBuilt())
24✔
1352
        {
1353
            milBld->RegulateTroops();
6✔
1354
            // Used that soldier? Go out
1355
            if(!soldier_count)
6✔
1356
                return;
×
1357
        }
1358
    }
1359

1360
    // Als nächstes Gebäude in Grenznähe
1361
    for(nobMilitary* milBld : buildings.GetMilitaryBuildings())
218✔
1362
    {
1363
        if(milBld->GetFrontierDistance() == FrontierDistance::Near)
24✔
1364
        {
1365
            milBld->RegulateTroops();
24✔
1366
            // Used that soldier? Go out
1367
            if(!soldier_count)
24✔
1368
                return;
×
1369
        }
1370
    }
1371

1372
    // Und den Rest ggf.
1373
    for(nobMilitary* milBld : buildings.GetMilitaryBuildings())
218✔
1374
    {
1375
        // already checked? -> skip
1376
        if(milBld->GetFrontierDistance() == FrontierDistance::Near || milBld->IsNewBuilt())
24✔
1377
            continue;
24✔
1378
        milBld->RegulateTroops();
×
1379
        if(!soldier_count) // used the soldier?
×
1380
            return;
×
1381
    }
1382
}
1383

1384
void GamePlayer::CallFlagWorker(const MapPoint pt, const Job job)
32✔
1385
{
1386
    auto* flag = world.GetSpecObj<noFlag>(pt);
32✔
1387
    if(!flag)
32✔
1388
        return;
2✔
1389
    /// Find wh with given job type (e.g. geologist, scout, ...)
1390
    nobBaseWarehouse* wh = FindWarehouse(*flag, FW::HasFigure(job, true), false, false);
30✔
1391

1392
    if(wh)
30✔
1393
        wh->OrderJob(job, *flag, true);
26✔
1394
}
1395

1396
bool GamePlayer::IsFlagWorker(const nofFlagWorker* flagworker)
×
1397
{
1398
    return helpers::contains(flagworkers, flagworker);
×
1399
}
1400

1401
void GamePlayer::FlagDestroyed(noFlag* flag)
777✔
1402
{
1403
    // Alle durchgehen und ggf. sagen, dass sie keine Flagge mehr haben, wenn das ihre Flagge war, die zerstört wurde
1404
    for(auto it = flagworkers.begin(); it != flagworkers.end();)
777✔
1405
    {
1406
        if((*it)->GetFlag() == flag)
×
1407
        {
1408
            (*it)->LostWork();
×
1409
            it = flagworkers.erase(it);
×
1410
        } else
1411
            ++it;
×
1412
    }
1413
}
777✔
1414

1415
void GamePlayer::RefreshDefenderList()
80✔
1416
{
1417
    shouldSendDefenderList.clear();
80✔
1418
    // Add as many true values as set in the settings, the rest will be false
1419
    for(unsigned i = 0; i < MILITARY_SETTINGS_SCALE[2]; ++i)
480✔
1420
        shouldSendDefenderList.push_back(i < militarySettings_[2]);
400✔
1421
    // und ordentlich schütteln
1422
    RANDOM_SHUFFLE2(shouldSendDefenderList, 0);
80✔
1423
}
80✔
1424

1425
void GamePlayer::ChangeMilitarySettings(const MilitarySettings& military_settings)
80✔
1426
{
1427
    for(unsigned i = 0; i < military_settings.size(); ++i)
720✔
1428
    {
1429
        // Sicherstellen, dass im validen Bereich
1430
        RTTR_Assert(military_settings[i] <= MILITARY_SETTINGS_SCALE[i]);
640✔
1431
        this->militarySettings_[i] = military_settings[i];
640✔
1432
    }
1433
    /// Truppen müssen neu kalkuliert werden
1434
    RegulateAllTroops();
80✔
1435
    /// Die Verteidigungsliste muss erneuert werden
1436
    RefreshDefenderList();
80✔
1437
}
80✔
1438

1439
void GamePlayer::ChangeToolsSettings(const ToolSettings& tools_settings,
6✔
1440
                                     const helpers::EnumArray<int8_t, Tool>& orderChanges)
1441
{
1442
    const bool settingsChanged = toolsSettings_ != tools_settings;
6✔
1443
    toolsSettings_ = tools_settings;
6✔
1444
    if(settingsChanged)
6✔
1445
        world.GetNotifications().publish(ToolNote(ToolNote::SettingsChanged, GetPlayerId()));
4✔
1446

1447
    for(const auto tool : helpers::enumRange<Tool>())
168✔
1448
    {
1449
        tools_ordered[tool] = helpers::clamp(tools_ordered[tool] + orderChanges[tool], 0, 100);
72✔
1450
        tools_ordered_delta[tool] -= orderChanges[tool];
72✔
1451

1452
        if(orderChanges[tool] != 0)
72✔
1453
        {
1454
            LOG.write(">> Committing an order of %1% for tool #%2%(%3%)\n", LogTarget::File) % (int)orderChanges[tool]
8✔
1455
              % static_cast<unsigned>(tool) % _(WARE_NAMES[TOOL_TO_GOOD[tool]]);
8✔
1456
            world.GetNotifications().publish(ToolNote(ToolNote::OrderPlaced, GetPlayerId()));
4✔
1457
        }
1458
    }
1459
}
6✔
1460

1461
void GamePlayer::ChangeDistribution(const Distributions& distribution_settings)
6✔
1462
{
1463
    unsigned idx = 0;
6✔
1464
    for(const DistributionMapping& mapping : distributionMap)
180✔
1465
    {
1466
        distribution[std::get<0>(mapping)].percent_buildings[std::get<1>(mapping)] = distribution_settings[idx++];
174✔
1467
    }
1468

1469
    RecalcDistribution();
6✔
1470
}
6✔
1471

1472
void GamePlayer::ChangeBuildOrder(bool useCustomBuildOrder, const BuildOrders& order_data)
2✔
1473
{
1474
    this->useCustomBuildOrder_ = useCustomBuildOrder;
2✔
1475
    this->build_order = order_data;
2✔
1476
}
2✔
1477

1478
bool GamePlayer::ShouldSendDefender()
46✔
1479
{
1480
    // Wenn wir schon am Ende sind, muss die Verteidgungsliste erneuert werden
1481
    if(shouldSendDefenderList.empty())
46✔
1482
        RefreshDefenderList();
×
1483

1484
    bool result = shouldSendDefenderList.back();
46✔
1485
    shouldSendDefenderList.pop_back();
46✔
1486
    return result;
46✔
1487
}
1488

1489
void GamePlayer::TestDefeat()
84✔
1490
{
1491
    // Nicht schon besiegt?
1492
    // Keine Militärgebäude, keine Lagerhäuser (HQ,Häfen) -> kein Land --> verloren
1493
    if(!isDefeated && buildings.GetMilitaryBuildings().empty() && buildings.GetStorehouses().empty())
84✔
1494
        Surrender();
29✔
1495
}
84✔
1496

1497
const nobHQ* GamePlayer::GetHQ() const
71✔
1498
{
1499
    return hqPos.isValid() ? GetGameWorld().GetSpecObj<nobHQ>(hqPos) : nullptr;
71✔
1500
}
1501

1502
nobHQ* GamePlayer::GetHQ()
63✔
1503
{
1504
    return const_cast<nobHQ*>(std::as_const(*this).GetHQ());
63✔
1505
}
1506

1507
void GamePlayer::Surrender()
32✔
1508
{
1509
    if(isDefeated)
32✔
1510
        return;
1✔
1511

1512
    isDefeated = true;
31✔
1513

1514
    // GUI Bescheid sagen
1515
    if(world.GetGameInterface())
31✔
1516
        world.GetGameInterface()->GI_PlayerDefeated(GetPlayerId());
×
1517
}
1518

1519
void GamePlayer::SetStatisticValue(StatisticType type, unsigned value)
×
1520
{
1521
    statisticCurrentData[type] = value;
×
1522
}
×
1523

1524
void GamePlayer::ChangeStatisticValue(StatisticType type, int change)
2,214✔
1525
{
1526
    RTTR_Assert(change >= 0 || statisticCurrentData[type] >= static_cast<unsigned>(-change));
2,214✔
1527
    statisticCurrentData[type] += change;
2,214✔
1528
}
2,214✔
1529

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

1570
void GamePlayer::CalcStatistics()
6✔
1571
{
1572
    // Waren aus der Inventur zählen
1573
    statisticCurrentData[StatisticType::Merchandise] = 0;
6✔
1574
    for(const auto i : helpers::enumRange<GoodType>())
504✔
1575
        statisticCurrentData[StatisticType::Merchandise] += global_inventory[i];
240✔
1576

1577
    // Bevölkerung aus der Inventur zählen
1578
    statisticCurrentData[StatisticType::Inhabitants] = 0;
6✔
1579
    for(const auto i : helpers::enumRange<Job>())
480✔
1580
        statisticCurrentData[StatisticType::Inhabitants] += global_inventory[i];
228✔
1581

1582
    // Militär aus der Inventur zählen
1583
    statisticCurrentData[StatisticType::Military] =
12✔
1584
      global_inventory.people[Job::Private] + global_inventory.people[Job::PrivateFirstClass] * 2
6✔
1585
      + global_inventory.people[Job::Sergeant] * 3 + global_inventory.people[Job::Officer] * 4
6✔
1586
      + global_inventory.people[Job::General] * 5;
6✔
1587

1588
    // Produktivität berechnen
1589
    statisticCurrentData[StatisticType::Productivity] = buildings.CalcAverageProductivity();
6✔
1590

1591
    // Total points for tournament games
1592
    statisticCurrentData[StatisticType::Tournament] =
12✔
1593
      statisticCurrentData[StatisticType::Military] + 3 * statisticCurrentData[StatisticType::Vanquished];
6✔
1594
}
6✔
1595

1596
void GamePlayer::StatisticStep()
6✔
1597
{
1598
    CalcStatistics();
6✔
1599

1600
    // 15-min-Statistik ein Feld weiterschieben
1601
    for(const auto i : helpers::enumRange<StatisticType>())
132✔
1602
    {
1603
        statistic[StatisticTime::T15Minutes].data[i][incrStatIndex(statistic[StatisticTime::T15Minutes].currentIndex)] =
54✔
1604
          statisticCurrentData[i];
54✔
1605
    }
1606
    for(unsigned i = 0; i < NUM_STAT_MERCHANDISE_TYPES; ++i)
90✔
1607
    {
1608
        statistic[StatisticTime::T15Minutes]
84✔
1609
          .merchandiseData[i][incrStatIndex(statistic[StatisticTime::T15Minutes].currentIndex)] =
168✔
1610
          statisticCurrentMerchandiseData[i];
84✔
1611
    }
1612
    statistic[StatisticTime::T15Minutes].currentIndex =
6✔
1613
      incrStatIndex(statistic[StatisticTime::T15Minutes].currentIndex);
6✔
1614

1615
    statistic[StatisticTime::T15Minutes].counter++;
6✔
1616

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

1633
            // Summe für den Zeitraum berechnen (immer 4 Zeitschritte der jeweils kleineren Statistik)
1634
            for(unsigned i = 0; i < NUM_STAT_MERCHANDISE_TYPES; ++i)
×
1635
            {
1636
                statistic[nextT].merchandiseData[i][incrStatIndex(statistic[nextT].currentIndex)] =
×
1637
                  statisticCurrentMerchandiseData[i]
×
1638
                  + statistic[t].merchandiseData[i][decrStatIndex(statistic[t].currentIndex, 1)]
×
1639
                  + statistic[t].merchandiseData[i][decrStatIndex(statistic[t].currentIndex, 2)]
×
1640
                  + statistic[t].merchandiseData[i][decrStatIndex(statistic[t].currentIndex, 3)];
×
1641
            }
1642

1643
            statistic[nextT].currentIndex = incrStatIndex(statistic[nextT].currentIndex);
×
1644
            statistic[nextT].counter++;
×
1645
        }
1646
    }
1647

1648
    // Warenstatistikzähler nullen
1649
    statisticCurrentMerchandiseData.fill(0);
6✔
1650
}
6✔
1651

1652
GamePlayer::Pact::Pact(SerializedGameData& sgd)
112✔
1653
    : duration(sgd.PopUnsignedInt()), start(sgd.PopUnsignedInt()), accepted(sgd.PopBool()), want_cancel(sgd.PopBool())
112✔
1654
{}
112✔
1655

1656
void GamePlayer::Pact::Serialize(SerializedGameData& sgd) const
224✔
1657
{
1658
    sgd.PushUnsignedInt(duration);
224✔
1659
    sgd.PushUnsignedInt(start);
224✔
1660
    sgd.PushBool(accepted);
224✔
1661
    sgd.PushBool(want_cancel);
224✔
1662
}
224✔
1663

1664
void GamePlayer::PactChanged(const PactType pt)
24✔
1665
{
1666
    // Recheck military flags as the border (to an enemy) might have changed
1667
    RecalcMilitaryFlags();
24✔
1668

1669
    // Ggf. den GUI Bescheid sagen, um Sichtbarkeiten etc. neu zu berechnen
1670
    if(pt == PactType::TreatyOfAlliance)
24✔
1671
    {
1672
        if(world.GetGameInterface())
6✔
1673
            world.GetGameInterface()->GI_TreatyOfAllianceChanged(GetPlayerId());
×
1674
    }
1675
}
24✔
1676

1677
void GamePlayer::SuggestPact(const unsigned char targetPlayerId, const PactType pt, const unsigned duration)
10✔
1678
{
1679
    // Don't try to make pact with self
1680
    if(targetPlayerId == GetPlayerId())
10✔
1681
        return;
1✔
1682

1683
    if(!pacts[targetPlayerId][pt].accepted && duration > 0)
9✔
1684
    {
1685
        pacts[targetPlayerId][pt].duration = duration;
8✔
1686
        pacts[targetPlayerId][pt].start = world.GetEvMgr().GetCurrentGF();
8✔
1687
        GamePlayer targetPlayer = world.GetPlayer(targetPlayerId);
16✔
1688
        if(targetPlayer.isHuman())
8✔
1689
            targetPlayer.SendPostMessage(std::make_unique<DiplomacyPostQuestion>(
6✔
1690
              world.GetEvMgr().GetCurrentGF(), pt, pacts[targetPlayerId][pt].start, *this, duration));
12✔
1691
        else if(world.HasLua())
2✔
1692
            world.GetLua().EventSuggestPact(pt, GetPlayerId(), targetPlayerId, duration);
2✔
1693
    }
1694
}
1695

1696
void GamePlayer::AcceptPact(const unsigned id, const PactType pt, const unsigned char targetPlayer)
21✔
1697
{
1698
    if(!pacts[targetPlayer][pt].accepted && pacts[targetPlayer][pt].duration > 0 && pacts[targetPlayer][pt].start == id)
21✔
1699
    {
1700
        MakePact(pt, targetPlayer, pacts[targetPlayer][pt].duration);
8✔
1701
        world.GetPlayer(targetPlayer).MakePact(pt, GetPlayerId(), pacts[targetPlayer][pt].duration);
8✔
1702
        PactChanged(pt);
8✔
1703
        world.GetPlayer(targetPlayer).PactChanged(pt);
8✔
1704
        if(world.HasLua())
8✔
1705
            world.GetLua().EventPactCreated(pt, GetPlayerId(), targetPlayer, pacts[targetPlayer][pt].duration);
3✔
1706
    }
1707
}
21✔
1708

1709
void GamePlayer::MakePact(const PactType pt, const unsigned char other_player, const unsigned duration)
16✔
1710
{
1711
    pacts[other_player][pt].accepted = true;
16✔
1712
    pacts[other_player][pt].start = world.GetEvMgr().GetCurrentGF();
16✔
1713
    pacts[other_player][pt].duration = duration;
16✔
1714
    pacts[other_player][pt].want_cancel = false;
16✔
1715

1716
    SendPostMessage(
16✔
1717
      std::make_unique<PostMsg>(world.GetEvMgr().GetCurrentGF(), pt, world.GetPlayer(other_player), true));
32✔
1718
}
16✔
1719

1720
PactState GamePlayer::GetPactState(const PactType pt, const unsigned char other_player) const
9,378✔
1721
{
1722
    // Prüfen, ob Bündnis in Kraft ist
1723
    if(pacts[other_player][pt].duration)
9,378✔
1724
    {
1725
        if(!pacts[other_player][pt].accepted)
5,040✔
1726
            return PactState::InProgress;
16✔
1727

1728
        if(pacts[other_player][pt].duration == DURATION_INFINITE
5,024✔
1729
           || world.GetEvMgr().GetCurrentGF() < pacts[other_player][pt].start + pacts[other_player][pt].duration)
5,024✔
1730
            return PactState::Accepted;
5,022✔
1731
    }
1732

1733
    return PactState::None;
4,340✔
1734
}
1735

1736
void GamePlayer::NotifyAlliesOfLocation(const MapPoint pt)
6✔
1737
{
1738
    for(unsigned i = 0; i < world.GetNumPlayers(); ++i)
24✔
1739
    {
1740
        if(i != GetPlayerId() && IsAlly(i))
18✔
1741
            world.GetPlayer(i).SendPostMessage(std::make_unique<PostMsg>(
10✔
1742
              world.GetEvMgr().GetCurrentGF(), _("Your ally wishes to notify you of this location"),
5✔
1743
              PostCategory::Diplomacy, pt));
10✔
1744
    }
1745
}
6✔
1746

1747
unsigned GamePlayer::GetRemainingPactTime(const PactType pt, const unsigned char other_player) const
125✔
1748
{
1749
    if(pacts[other_player][pt].duration)
125✔
1750
    {
1751
        if(pacts[other_player][pt].accepted)
88✔
1752
        {
1753
            if(pacts[other_player][pt].duration == DURATION_INFINITE)
76✔
1754
                return DURATION_INFINITE;
×
1755
            else if(world.GetEvMgr().GetCurrentGF() <= pacts[other_player][pt].start + pacts[other_player][pt].duration)
76✔
1756
                return ((pacts[other_player][pt].start + pacts[other_player][pt].duration)
76✔
1757
                        - world.GetEvMgr().GetCurrentGF());
76✔
1758
        }
1759
    }
1760

1761
    return 0;
49✔
1762
}
1763

1764
void GamePlayer::CancelPact(const PactType pt, const unsigned char otherPlayerIdx)
8✔
1765
{
1766
    // Don't try to cancel pact with self
1767
    if(otherPlayerIdx == GetPlayerId())
8✔
1768
        return;
2✔
1769

1770
    // Besteht bereits ein Bündnis?
1771
    if(pacts[otherPlayerIdx][pt].accepted)
6✔
1772
    {
1773
        // Vermerken, dass der Spieler das Bündnis auflösen will
1774
        pacts[otherPlayerIdx][pt].want_cancel = true;
6✔
1775

1776
        // Will der andere Spieler das Bündnis auch auflösen?
1777
        GamePlayer& otherPlayer = world.GetPlayer(otherPlayerIdx);
6✔
1778
        if(otherPlayer.pacts[GetPlayerId()][pt].want_cancel)
6✔
1779
        {
1780
            // Dann wird das Bündnis aufgelöst
1781
            pacts[otherPlayerIdx][pt].accepted = false;
2✔
1782
            pacts[otherPlayerIdx][pt].duration = 0;
2✔
1783
            pacts[otherPlayerIdx][pt].want_cancel = false;
2✔
1784

1785
            otherPlayer.pacts[GetPlayerId()][pt].accepted = false;
2✔
1786
            otherPlayer.pacts[GetPlayerId()][pt].duration = 0;
2✔
1787
            otherPlayer.pacts[GetPlayerId()][pt].want_cancel = false;
2✔
1788

1789
            // Den Spielern eine Informationsnachricht schicken
1790
            world.GetPlayer(otherPlayerIdx)
2✔
1791
              .SendPostMessage(std::make_unique<PostMsg>(world.GetEvMgr().GetCurrentGF(), pt, *this, false));
2✔
1792
            SendPostMessage(
2✔
1793
              std::make_unique<PostMsg>(world.GetEvMgr().GetCurrentGF(), pt, world.GetPlayer(otherPlayerIdx), false));
4✔
1794
            PactChanged(pt);
2✔
1795
            otherPlayer.PactChanged(pt);
2✔
1796
            if(world.HasLua())
2✔
1797
                world.GetLua().EventPactCanceled(pt, GetPlayerId(), otherPlayerIdx);
1✔
1798
        } else
1799
        {
1800
            // Ansonsten den anderen Spieler fragen, ob der das auch so sieht
1801
            if(otherPlayer.isHuman())
4✔
1802
                otherPlayer.SendPostMessage(std::make_unique<DiplomacyPostQuestion>(
3✔
1803
                  world.GetEvMgr().GetCurrentGF(), pt, pacts[otherPlayerIdx][pt].start, *this));
6✔
1804
            else if(!world.HasLua() || world.GetLua().EventCancelPactRequest(pt, GetPlayerId(), otherPlayerIdx))
1✔
1805
            {
1806
                // AI accepts cancels, if there is no lua-interace
1807
                pacts[otherPlayerIdx][pt].accepted = false;
1✔
1808
                pacts[otherPlayerIdx][pt].duration = 0;
1✔
1809
                pacts[otherPlayerIdx][pt].want_cancel = false;
1✔
1810

1811
                otherPlayer.pacts[GetPlayerId()][pt].accepted = false;
1✔
1812
                otherPlayer.pacts[GetPlayerId()][pt].duration = 0;
1✔
1813
                otherPlayer.pacts[GetPlayerId()][pt].want_cancel = false;
1✔
1814

1815
                if(world.HasLua())
1✔
1816
                    world.GetLua().EventPactCanceled(pt, GetPlayerId(), otherPlayerIdx);
1✔
1817
            }
1818
        }
1819
    } else
1820
    {
1821
        // Es besteht kein Bündnis, also unseren Bündnisvorschlag wieder zurücknehmen
1822
        pacts[otherPlayerIdx][pt].duration = 0;
×
1823
    }
1824
}
1825

1826
void GamePlayer::MakeStartPacts()
66✔
1827
{
1828
    // Reset pacts
1829
    for(unsigned i = 0; i < world.GetNumPlayers(); ++i)
262✔
1830
    {
1831
        for(const auto z : helpers::enumRange<PactType>())
1,568✔
1832
            pacts[i][z] = Pact();
392✔
1833
    }
1834

1835
    // No team -> No pacts
1836
    if(team == Team::None)
66✔
1837
        return;
4✔
1838
    RTTR_Assert(isTeam(team));
62✔
1839

1840
    // Create ally- and non-aggression-pact for all players of same team
1841
    for(unsigned i = 0; i < world.GetNumPlayers(); ++i)
245✔
1842
    {
1843
        if(team != world.GetPlayer(i).team)
183✔
1844
            continue;
75✔
1845
        for(const auto z : helpers::enumRange<PactType>())
864✔
1846
        {
1847
            pacts[i][z].duration = DURATION_INFINITE;
216✔
1848
            pacts[i][z].start = 0;
216✔
1849
            pacts[i][z].accepted = true;
216✔
1850
            pacts[i][z].want_cancel = false;
216✔
1851
        }
1852
    }
1853
}
1854

1855
bool GamePlayer::IsWareRegistred(const Ware& ware)
160✔
1856
{
1857
    return helpers::contains(ware_list, &ware);
160✔
1858
}
1859

1860
bool GamePlayer::IsWareDependent(const Ware& ware)
21✔
1861
{
1862
    for(nobBaseWarehouse* wh : buildings.GetStorehouses())
62✔
1863
    {
1864
        if(wh->IsWareDependent(ware))
41✔
1865
            return true;
×
1866
    }
1867

1868
    return false;
21✔
1869
}
1870

1871
void GamePlayer::IncreaseInventoryWare(const GoodType ware, const unsigned count)
1,676✔
1872
{
1873
    global_inventory.Add(ConvertShields(ware), count);
1,676✔
1874
}
1,676✔
1875

1876
void GamePlayer::DecreaseInventoryWare(const GoodType ware, const unsigned count)
2,465✔
1877
{
1878
    global_inventory.Remove(ConvertShields(ware), count);
2,465✔
1879
}
2,465✔
1880

1881
void GamePlayer::RegisterShip(noShip& ship)
23✔
1882
{
1883
    ships.push_back(&ship);
23✔
1884
    GetJobForShip(ship);
23✔
1885
}
23✔
1886

1887
struct ShipForHarbor
1888
{
1889
    noShip* ship;
1890
    uint32_t estimate;
1891

1892
    ShipForHarbor(noShip* ship, uint32_t estimate) : ship(ship), estimate(estimate) {}
20✔
1893

1894
    bool operator<(const ShipForHarbor& b) const noexcept
×
1895
    {
1896
        return (estimate < b.estimate) || (estimate == b.estimate && ship->GetObjId() < b.ship->GetObjId());
×
1897
    }
1898
};
1899

1900
bool GamePlayer::OrderShip(nobHarborBuilding& hb)
41✔
1901
{
1902
    std::vector<ShipForHarbor> sfh;
82✔
1903

1904
    // we need more ships than those that are already on their way? limit search to idle ships
1905
    if(GetShipsToHarbor(hb) < hb.GetNumNeededShips())
41✔
1906
    {
1907
        for(noShip* ship : ships)
82✔
1908
        {
1909
            if(ship->IsIdling() && world.IsHarborAtSea(world.GetHarborPointID(hb.GetPos()), ship->GetSeaID()))
41✔
1910
                sfh.push_back(ShipForHarbor(ship, world.CalcDistance(hb.GetPos(), ship->GetPos())));
20✔
1911
        }
1912
    } else
1913
    {
1914
        for(noShip* ship : ships)
×
1915
        {
1916
            if((ship->IsIdling() && world.IsHarborAtSea(world.GetHarborPointID(hb.GetPos()), ship->GetSeaID()))
×
1917
               || ship->IsGoingToHarbor(hb))
×
1918
            {
1919
                sfh.push_back(ShipForHarbor(ship, world.CalcDistance(hb.GetPos(), ship->GetPos())));
×
1920
            }
1921
        }
1922
    }
1923

1924
    helpers::sort(sfh);
41✔
1925

1926
    noShip* best_ship = nullptr;
41✔
1927
    uint32_t best_distance = std::numeric_limits<uint32_t>::max();
41✔
1928
    std::vector<Direction> best_route;
82✔
1929

1930
    for(auto& it : sfh)
52✔
1931
    {
1932
        uint32_t distance;
1933
        std::vector<Direction> route;
20✔
1934

1935
        // the estimate (air-line distance) for this and all other ships in the list is already worse than what we
1936
        // found? disregard the rest
1937
        if(it.estimate >= best_distance)
20✔
1938
            break;
×
1939

1940
        noShip& ship = *it.ship;
20✔
1941

1942
        if(world.FindShipPathToHarbor(ship.GetPos(), hb.GetHarborPosID(), ship.GetSeaID(), &route, &distance))
20✔
1943
        {
1944
            // ship already there?
1945
            if(distance == 0)
20✔
1946
            {
1947
                hb.ShipArrived(ship);
9✔
1948
                return true;
9✔
1949
            }
1950

1951
            if(distance < best_distance)
11✔
1952
            {
1953
                best_ship = &ship;
11✔
1954
                best_distance = distance;
11✔
1955
                best_route = route;
11✔
1956
            }
1957
        }
1958
    }
1959

1960
    // only order ships not already on their way
1961
    if(best_ship && best_ship->IsIdling())
32✔
1962
    {
1963
        best_ship->GoToHarbor(hb, best_route);
11✔
1964

1965
        return true;
11✔
1966
    }
1967

1968
    return false;
21✔
1969
}
1970

1971
void GamePlayer::RemoveShip(noShip* ship)
×
1972
{
1973
    auto it = helpers::find(ships, ship);
×
1974
    RTTR_Assert(it != ships.end());
×
1975
    ships.erase(it);
×
1976
}
×
1977

1978
void GamePlayer::GetJobForShip(noShip& ship)
33✔
1979
{
1980
    nobHarborBuilding* best = nullptr;
33✔
1981
    int best_points = 0;
33✔
1982
    std::vector<Direction> best_route;
33✔
1983

1984
    // Beste Weglänge, die ein Schiff zurücklegen muss, welches gerade nichts zu tun hat
1985
    for(nobHarborBuilding* harbor : buildings.GetHarbors())
68✔
1986
    {
1987
        // Braucht der Hafen noch Schiffe?
1988
        if(harbor->GetNumNeededShips() == 0)
35✔
1989
            continue;
35✔
1990

1991
        // Anzahl der Schiffe ermitteln, die diesen Hafen bereits anfahren
1992
        unsigned ships_coming = GetShipsToHarbor(*harbor);
×
1993

1994
        // Evtl. kommen schon genug?
1995
        if(harbor->GetNumNeededShips() <= ships_coming)
×
1996
            continue;
×
1997

1998
        // liegen wir am gleichen Meer?
1999
        if(world.IsHarborAtSea(harbor->GetHarborPosID(), ship.GetSeaID()))
×
2000
        {
2001
            unsigned length;
2002
            std::vector<Direction> route;
×
2003

2004
            if(world.FindShipPathToHarbor(ship.GetPos(), harbor->GetHarborPosID(), ship.GetSeaID(), &route, &length))
×
2005
            {
2006
                // ship already there?
2007
                if(length == 0)
×
2008
                {
2009
                    harbor->ShipArrived(ship);
×
2010
                    return;
×
2011
                }
2012

2013
                // Punkte ausrechnen
2014
                int points = harbor->GetNeedForShip(ships_coming) - length;
×
2015
                if(points > best_points || !best)
×
2016
                {
2017
                    best = harbor;
×
2018
                    best_points = points;
×
2019
                    best_route = route;
×
2020
                }
2021
            }
2022
        }
2023
    }
2024

2025
    // Einen Hafen gefunden?
2026
    if(best)
33✔
2027
        ship.GoToHarbor(*best, best_route);
×
2028
}
2029

2030
unsigned GamePlayer::GetShipID(const noShip& ship) const
16✔
2031
{
2032
    return static_cast<unsigned>(helpers::indexOf(ships, &ship));
16✔
2033
}
2034

2035
noShip* GamePlayer::GetShipByID(const unsigned ship_id) const
25✔
2036
{
2037
    if(ship_id >= ships.size())
25✔
2038
        return nullptr;
×
2039
    else
2040
        return ships[ship_id];
25✔
2041
}
2042

2043
unsigned GamePlayer::GetShipsToHarbor(const nobHarborBuilding& hb) const
90✔
2044
{
2045
    unsigned count = 0;
90✔
2046
    for(const auto* ship : ships)
180✔
2047
    {
2048
        if(ship->IsGoingToHarbor(hb))
90✔
2049
            ++count;
6✔
2050
    }
2051

2052
    return count;
90✔
2053
}
2054

2055
bool GamePlayer::FindHarborForUnloading(noShip* ship, const MapPoint start, HarborId* goalHarborId,
6✔
2056
                                        std::vector<Direction>* route, nobHarborBuilding* exception)
2057
{
2058
    nobHarborBuilding* best = nullptr;
6✔
2059
    unsigned best_distance = 0xffffffff;
6✔
2060

2061
    for(nobHarborBuilding* hb : buildings.GetHarbors())
9✔
2062
    {
2063
        // Bestimmten Hafen ausschließen
2064
        if(hb == exception)
3✔
2065
            continue;
×
2066

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

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

2074
        // Kürzerer Weg als bisher bestes Ziel?
2075
        if(distance < best_distance)
3✔
2076
        {
2077
            best_distance = distance;
3✔
2078
            best = hb;
3✔
2079
        }
2080
    }
2081

2082
    // Hafen gefunden?
2083
    if(best)
6✔
2084
    {
2085
        // Weg dorthin suchen
2086
        route->clear();
3✔
2087
        *goalHarborId = best->GetHarborPosID();
3✔
2088
        if(world.FindShipPathToHarbor(start, best->GetHarborPosID(), ship->GetSeaID(), route, nullptr))
3✔
2089
            return true;
3✔
2090
    }
2091

2092
    return false;
3✔
2093
}
2094

NEW
2095
void GamePlayer::CancelWaresForEmergencyProtocol()
×
2096
{
NEW
2097
    for(auto it = ware_list.begin(); it != ware_list.end();){
×
NEW
2098
        Ware * ware = *it;
×
2099
        // checks if this ware is
NEW
2100
        if(ware->IsWaitingInWarehouse() && ware->GetGoal() && !IsWareFineWithEmergencyProtocol(ware->type,*ware->GetGoal()))
×
2101
        {
NEW
2102
            ware->NotifyGoalAboutLostWare();
×
NEW
2103
            static_cast<nobBaseWarehouse*>(ware->GetLocation())->CancelWare(ware);
×
NEW
2104
            it = ware_list.erase(it);
×
NEW
2105
            continue;
×
2106
        }
NEW
2107
        it++;
×
2108
    }
NEW
2109
}
×
2110

UNCOV
2111
void GamePlayer::TestForEmergencyProgramm()
×
2112
{
2113
    // we are already defeated, do not even think about an emergency program - it's too late :-(
2114
    if(isDefeated)
×
2115
        return;
×
2116

2117
    // In Lagern vorhandene Bretter und Steine zählen
2118
    unsigned boards = 0;
×
2119
    unsigned stones = 0;
×
2120
    for(nobBaseWarehouse* wh : buildings.GetStorehouses())
×
2121
    {
2122
        boards += wh->GetInventory().goods[GoodType::Boards];
×
2123
        stones += wh->GetInventory().goods[GoodType::Stones];
×
2124
    }
2125

2126
    // Emergency happens, if we have less than 10 boards or stones...
2127
    bool isNewEmergency = boards <= 10 || stones <= 10;
×
2128
    // ...and no woddcutter or sawmill
2129
    isNewEmergency &=
×
2130
      buildings.GetBuildings(BuildingType::Woodcutter).empty() || buildings.GetBuildings(BuildingType::Sawmill).empty();
×
2131

2132
    // Wenn nötig, Notfallprogramm auslösen
2133
    if(isNewEmergency)
×
2134
    {
2135
        if(!emergency)
×
2136
        {
2137
            emergency = true;
×
2138
            SendPostMessage(std::make_unique<PostMsg>(
×
2139
              world.GetEvMgr().GetCurrentGF(), _("The emergency program has been activated."), PostCategory::Economy));
×
2140

2141
            //Handle wares already ordered
NEW
2142
            CancelWaresForEmergencyProtocol();
×
2143
        }
2144
    } else
2145
    {
2146
        // Sobald Notfall vorbei, Notfallprogramm beenden, evtl. Baustellen wieder mit Kram versorgen
2147
        if(emergency)
×
2148
        {
2149
            emergency = false;
×
2150
            SendPostMessage(std::make_unique<PostMsg>(world.GetEvMgr().GetCurrentGF(),
×
2151
                                                      _("The emergency program has been deactivated."),
×
2152
                                                      PostCategory::Economy));
×
2153
            FindMaterialForBuildingSites();
×
2154
        }
2155
    }
2156
}
2157

2158
void GamePlayer::TestPacts()
20✔
2159
{
2160
    for(unsigned i = 0; i < world.GetNumPlayers(); ++i)
80✔
2161
    {
2162
        if(i == GetPlayerId())
60✔
2163
            continue;
20✔
2164

2165
        for(const auto pact : helpers::enumRange<PactType>())
320✔
2166
        {
2167
            // Pact not running
2168
            if(pacts[i][pact].duration == 0)
80✔
2169
                continue;
60✔
2170
            if(GetPactState(pact, i) == PactState::None)
20✔
2171
            {
2172
                // Pact was running but is expired -> Cancel for both players
2173
                pacts[i][pact].duration = 0;
2✔
2174
                pacts[i][pact].accepted = false;
2✔
2175
                GamePlayer& otherPlayer = world.GetPlayer(i);
2✔
2176
                RTTR_Assert(otherPlayer.pacts[GetPlayerId()][pact].duration);
2✔
2177
                RTTR_Assert(otherPlayer.pacts[GetPlayerId()][pact].accepted);
2✔
2178
                otherPlayer.pacts[GetPlayerId()][pact].duration = 0;
2✔
2179
                otherPlayer.pacts[GetPlayerId()][pact].accepted = false;
2✔
2180
                // And notify
2181
                PactChanged(pact);
2✔
2182
                otherPlayer.PactChanged(pact);
2✔
2183
            }
2184
        }
2185
    }
2186
}
20✔
2187

2188
bool GamePlayer::CanBuildCatapult() const
1✔
2189
{
2190
    // Wenn AddonId::LIMIT_CATAPULTS nicht aktiv ist, bauen immer erlaubt
2191
    if(!world.GetGGS().isEnabled(AddonId::LIMIT_CATAPULTS)) //-V807
1✔
2192
        return true;
1✔
2193

2194
    BuildingCount bc = buildings.GetBuildingNums();
×
2195

2196
    unsigned max = 0;
×
2197
    // proportional?
2198
    if(world.GetGGS().getSelection(AddonId::LIMIT_CATAPULTS) == 1)
×
2199
    {
2200
        max = int(bc.buildings[BuildingType::Barracks] * 0.125 + bc.buildings[BuildingType::Guardhouse] * 0.25
×
2201
                  + bc.buildings[BuildingType::Watchtower] * 0.5 + bc.buildings[BuildingType::Fortress]
×
2202
                  + 0.111); // to avoid rounding errors
×
2203
    } else if(world.GetGGS().getSelection(AddonId::LIMIT_CATAPULTS) < 8)
×
2204
    {
2205
        const std::array<unsigned, 6> limits = {{0, 3, 5, 10, 20, 30}};
×
2206
        max = limits[world.GetGGS().getSelection(AddonId::LIMIT_CATAPULTS) - 2];
×
2207
    }
2208

2209
    return bc.buildings[BuildingType::Catapult] + bc.buildingSites[BuildingType::Catapult] < max;
×
2210
}
2211

2212
bool GamePlayer::ShipDiscoveredHostileTerritory(const MapPoint location)
25✔
2213
{
2214
    // Prüfen, ob Abstand zu bisherigen Punkten nicht zu klein
2215
    for(const auto& enemies_discovered_by_ship : enemies_discovered_by_ships)
25✔
2216
    {
2217
        if(world.CalcDistance(enemies_discovered_by_ship, location) < 30)
24✔
2218
            return false;
24✔
2219
    }
2220

2221
    // Nein? Dann haben wir ein neues Territorium gefunden
2222
    enemies_discovered_by_ships.push_back(location);
1✔
2223

2224
    return true;
1✔
2225
}
2226

2227
bool GamePlayer::IsDependentFigure(const noFigure& fig)
259✔
2228
{
2229
    for(const nobBaseWarehouse* wh : buildings.GetStorehouses())
625✔
2230
    {
2231
        if(wh->IsDependentFigure(fig))
366✔
2232
            return true;
×
2233
    }
2234
    return false;
259✔
2235
}
2236

2237
std::vector<nobBaseWarehouse*> GamePlayer::GetWarehousesForTrading(const nobBaseWarehouse& goalWh) const
18✔
2238
{
2239
    std::vector<nobBaseWarehouse*> result;
18✔
2240

2241
    // Don't try to trade with us!
2242
    if(goalWh.GetPlayer() == GetPlayerId())
18✔
2243
        return result;
6✔
2244

2245
    const MapPoint goalFlagPos = goalWh.GetFlagPos();
12✔
2246

2247
    TradePathCache& tradePathCache = world.GetTradePathCache();
12✔
2248
    for(nobBaseWarehouse* wh : buildings.GetStorehouses())
24✔
2249
    {
2250
        // Is there a trade path from this warehouse to wh? (flag to flag)
2251
        if(tradePathCache.pathExists(wh->GetFlagPos(), goalFlagPos, GetPlayerId()))
12✔
2252
            result.push_back(wh);
4✔
2253
    }
2254

2255
    return result;
12✔
2256
}
2257

2258
struct WarehouseDistanceComparator
2259
{
2260
    // Reference warehouse position, to which we want to calc the distance
2261
    const MapPoint refWareHousePos_;
2262
    /// GameWorld
2263
    const GameWorld& gwg_;
2264

2265
    WarehouseDistanceComparator(const nobBaseWarehouse& refWareHouse, const GameWorld& world)
15✔
2266
        : refWareHousePos_(refWareHouse.GetPos()), gwg_(world)
15✔
2267
    {}
15✔
2268

2269
    bool operator()(nobBaseWarehouse* const wh1, nobBaseWarehouse* const wh2) const
×
2270
    {
2271
        unsigned dist1 = gwg_.CalcDistance(wh1->GetPos(), refWareHousePos_);
×
2272
        unsigned dist2 = gwg_.CalcDistance(wh2->GetPos(), refWareHousePos_);
×
2273
        return (dist1 < dist2) || (dist1 == dist2 && wh1->GetObjId() < wh2->GetObjId());
×
2274
    }
2275
};
2276

2277
void GamePlayer::Trade(nobBaseWarehouse* goalWh, const boost_variant2<GoodType, Job>& what, unsigned count) const
23✔
2278
{
2279
    if(!world.GetGGS().isEnabled(AddonId::TRADE))
23✔
2280
        return;
11✔
2281

2282
    if(count == 0)
20✔
2283
        return;
3✔
2284

2285
    // Don't try to trade with us!
2286
    if(goalWh->GetPlayer() == GetPlayerId())
17✔
2287
        return;
1✔
2288

2289
    // No trades with enemies
2290
    if(!IsAlly(goalWh->GetPlayer()))
16✔
2291
        return;
1✔
2292

2293
    const MapPoint goalFlagPos = goalWh->GetFlagPos();
15✔
2294

2295
    std::vector<nobBaseWarehouse*> whs(buildings.GetStorehouses().begin(), buildings.GetStorehouses().end());
15✔
2296
    helpers::sort(whs, WarehouseDistanceComparator(*goalWh, world));
15✔
2297
    TradePathCache& tradePathCache = world.GetTradePathCache();
15✔
2298
    for(nobBaseWarehouse* wh : whs)
27✔
2299
    {
2300
        // Get available wares
2301
        const unsigned available =
2302
          boost::variant2::visit(composeVisitor([wh](GoodType gt) { return wh->GetAvailableWaresForTrading(gt); },
22✔
2303
                                                [wh](Job job) { return wh->GetAvailableFiguresForTrading(job); }),
23✔
2304
                                 what);
15✔
2305
        if(available == 0)
15✔
2306
            continue;
×
2307

2308
        const unsigned actualCount = std::min(available, count);
15✔
2309

2310
        // Find a trade path from flag to flag
2311
        TradeRoute tr(world, GetPlayerId(), wh->GetFlagPos(), goalFlagPos);
15✔
2312

2313
        // Found a path?
2314
        if(tr.IsValid())
15✔
2315
        {
2316
            // Add to cache for future searches
2317
            tradePathCache.addEntry(tr.GetTradePath(), GetPlayerId());
14✔
2318

2319
            wh->StartTradeCaravane(what, actualCount, tr, goalWh);
14✔
2320
            count -= available;
14✔
2321
            if(count == 0)
14✔
2322
                return;
3✔
2323
        }
2324
    }
2325
}
2326

2327
bool GamePlayer::IsBuildingEnabled(BuildingType type) const
1,036✔
2328
{
2329
    return building_enabled[type] || (isHuman() && world.GetGameInterface()->GI_GetCheats().areAllBuildingsEnabled());
1,036✔
2330
}
2331

2332
void GamePlayer::FillVisualSettings(VisualSettings& visualSettings) const
2✔
2333
{
2334
    Distributions& visDistribution = visualSettings.distribution;
2✔
2335
    unsigned visIdx = 0;
2✔
2336
    for(const DistributionMapping& mapping : distributionMap)
60✔
2337
    {
2338
        visDistribution[visIdx++] = distribution[std::get<0>(mapping)].percent_buildings[std::get<1>(mapping)];
58✔
2339
    }
2340

2341
    visualSettings.useCustomBuildOrder = useCustomBuildOrder_;
2✔
2342
    visualSettings.build_order = build_order;
2✔
2343

2344
    visualSettings.transport_order = GetOrderingFromTransportPrio(transportPrio);
2✔
2345

2346
    visualSettings.military_settings = militarySettings_;
2✔
2347
    visualSettings.tools_settings = toolsSettings_;
2✔
2348
}
2✔
2349

2350
#define INSTANTIATE_FINDWH(Cond)                                                                                \
2351
    template nobBaseWarehouse* GamePlayer::FindWarehouse(const noRoadNode&, const Cond&, bool, bool, unsigned*, \
2352
                                                         const RoadSegment*) const
2353

2354
INSTANTIATE_FINDWH(FW::HasMinWares);
2355
INSTANTIATE_FINDWH(FW::HasFigure);
2356
INSTANTIATE_FINDWH(FW::HasWareAndFigure);
2357
INSTANTIATE_FINDWH(FW::HasAnyMatchingSoldier);
2358
INSTANTIATE_FINDWH(FW::AcceptsWare);
2359
INSTANTIATE_FINDWH(FW::AcceptsFigure);
2360
INSTANTIATE_FINDWH(FW::CollectsWare);
2361
INSTANTIATE_FINDWH(FW::CollectsFigure);
2362
INSTANTIATE_FINDWH(FW::HasWareButNoCollect);
2363
INSTANTIATE_FINDWH(FW::HasFigureButNoCollect);
2364
INSTANTIATE_FINDWH(FW::AcceptsFigureButNoSend);
2365
INSTANTIATE_FINDWH(FW::NoCondition);
2366

2367
#undef INSTANTIATE_FINDWH
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc