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

Return-To-The-Roots / s25client / 12323492039

13 Dec 2024 09:44PM UTC coverage: 50.144% (-0.02%) from 50.166%
12323492039

Pull #1683

github

web-flow
Merge 9ea584aa0 into 77372c5a4
Pull Request #1683: Lua: Allow setting number of players, and placing HQs. Then fix the mission on map "The snake"

9 of 40 new or added lines in 8 files covered. (22.5%)

4 existing lines in 1 file now uncovered.

22270 of 44412 relevant lines covered (50.14%)

34410.75 hits per line

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

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

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

50
GamePlayer::GamePlayer(unsigned playerId, const PlayerInfo& playerInfo, GameWorld& world)
293✔
51
    : GamePlayerInfo(playerId, playerInfo), world(world), hqPos(MapPoint::Invalid()), emergency(false)
293✔
52
{
53
    std::fill(building_enabled.begin(), building_enabled.end(), true);
293✔
54

55
    LoadStandardDistribution();
293✔
56
    useCustomBuildOrder_ = false;
293✔
57
    build_order = GetStandardBuildOrder();
293✔
58
    transportPrio = STD_TRANSPORT_PRIO;
293✔
59
    LoadStandardMilitarySettings();
293✔
60
    LoadStandardToolSettings();
293✔
61

62
    // Inventur nullen
63
    global_inventory.clear();
293✔
64

65
    // Statistiken mit 0en füllen
66
    statistic = {};
293✔
67
    statisticCurrentData = {};
293✔
68
    statisticCurrentMerchandiseData = {};
293✔
69

70
    RecalcDistribution();
293✔
71
}
293✔
72

73
void GamePlayer::LoadStandardToolSettings()
293✔
74
{
75
    // metalwork tool request
76

77
    // manually
78
    std::fill(tools_ordered.begin(), tools_ordered.end(), 0u);
293✔
79
    std::fill(tools_ordered_delta.begin(), tools_ordered_delta.end(), 0);
293✔
80

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

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

109
BuildOrders GamePlayer::GetStandardBuildOrder()
295✔
110
{
111
    BuildOrders ordering;
112

113
    // Baureihenfolge füllen
114
    unsigned curPrio = 0;
295✔
115
    for(const auto bld : helpers::enumRange<BuildingType>())
24,780✔
116
    {
117
        if(bld == BuildingType::Headquarters || !BuildingProperties::IsValid(bld))
11,800✔
118
            continue;
1,475✔
119

120
        RTTR_Assert(curPrio < ordering.size());
10,325✔
121
        ordering[curPrio] = bld;
10,325✔
122
        ++curPrio;
10,325✔
123
    }
124
    RTTR_Assert(curPrio == ordering.size());
295✔
125
    return ordering;
295✔
126
}
127

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

141
    // Waren mit mehreren möglichen Zielen erstmal nullen, kann dann im Fenster eingestellt werden
142
    for(const auto i : helpers::enumRange<GoodType>())
22,854✔
143
    {
144
        std::fill(distribution[i].percent_buildings.begin(), distribution[i].percent_buildings.end(), 0);
10,841✔
145
        distribution[i].selected_goal = 0;
10,841✔
146
    }
147

148
    // Standardverteilung der Waren
149
    for(const DistributionMapping& mapping : distributionMap)
7,911✔
150
    {
151
        distribution[std::get<0>(mapping)].percent_buildings[std::get<1>(mapping)] = std::get<2>(mapping);
7,618✔
152
    }
153
}
293✔
154

155
GamePlayer::~GamePlayer() = default;
594✔
156

157
void GamePlayer::Serialize(SerializedGameData& sgd) const
17✔
158
{
159
    // PlayerStatus speichern, ehemalig
160
    sgd.PushEnum<uint8_t>(ps);
17✔
161

162
    // Nur richtige Spieler serialisieren
163
    if(!(ps == PlayerState::Occupied || ps == PlayerState::AI))
17✔
164
        return;
3✔
165

166
    sgd.PushBool(isDefeated);
14✔
167

168
    buildings.Serialize(sgd);
14✔
169

170
    sgd.PushObjectContainer(roads, true);
14✔
171

172
    sgd.PushUnsignedInt(jobs_wanted.size());
14✔
173
    for(const JobNeeded& job : jobs_wanted)
18✔
174
    {
175
        sgd.PushEnum<uint8_t>(job.job);
4✔
176
        sgd.PushObject(job.workplace);
4✔
177
    }
178

179
    sgd.PushObjectContainer(ware_list, true);
14✔
180
    sgd.PushObjectContainer(flagworkers);
14✔
181
    sgd.PushObjectContainer(ships, true);
14✔
182

183
    helpers::pushContainer(sgd, shouldSendDefenderList);
14✔
184
    helpers::pushPoint(sgd, hqPos);
14✔
185

186
    for(const Distribution& dist : distribution)
532✔
187
    {
188
        helpers::pushContainer(sgd, dist.percent_buildings);
518✔
189
        helpers::pushContainer(sgd, dist.client_buildings);
518✔
190
        helpers::pushContainer(sgd, dist.goals);
518✔
191
        sgd.PushUnsignedInt(dist.selected_goal);
518✔
192
    }
193

194
    sgd.PushBool(useCustomBuildOrder_);
14✔
195
    helpers::pushContainer(sgd, build_order);
14✔
196
    helpers::pushContainer(sgd, transportPrio);
14✔
197
    helpers::pushContainer(sgd, militarySettings_);
14✔
198
    helpers::pushContainer(sgd, toolsSettings_);
14✔
199
    helpers::pushContainer(sgd, tools_ordered);
14✔
200
    helpers::pushContainer(sgd, global_inventory.goods);
14✔
201
    helpers::pushContainer(sgd, global_inventory.people);
14✔
202

203
    // für Statistik
204
    for(const Statistic& curStatistic : statistic)
70✔
205
    {
206
        // normale Statistik
207
        for(const auto& curData : curStatistic.data)
560✔
208
            helpers::pushContainer(sgd, curData);
504✔
209

210
        // Warenstatistik
211
        for(unsigned j = 0; j < NUM_STAT_MERCHANDISE_TYPES; ++j)
840✔
212
            helpers::pushContainer(sgd, curStatistic.merchandiseData[j]);
784✔
213

214
        sgd.PushUnsignedShort(curStatistic.currentIndex);
56✔
215
        sgd.PushUnsignedShort(curStatistic.counter);
56✔
216
    }
217
    helpers::pushContainer(sgd, statisticCurrentData);
14✔
218
    helpers::pushContainer(sgd, statisticCurrentMerchandiseData);
14✔
219

220
    // Serialize Pacts:
221
    for(const auto& playerPacts : pacts)
126✔
222
    {
223
        for(const Pact& pact : playerPacts)
336✔
224
            pact.Serialize(sgd);
224✔
225
    }
226

227
    sgd.PushBool(emergency);
14✔
228
}
229

230
void GamePlayer::Deserialize(SerializedGameData& sgd)
8✔
231
{
232
    std::fill(building_enabled.begin(), building_enabled.end(), true);
8✔
233

234
    // Ehemaligen PS auslesen
235
    auto origin_ps = sgd.Pop<PlayerState>();
8✔
236
    // Nur richtige Spieler serialisieren
237
    if(!(origin_ps == PlayerState::Occupied || origin_ps == PlayerState::AI))
8✔
238
        return;
1✔
239

240
    isDefeated = sgd.PopBool();
7✔
241
    buildings.Deserialize(sgd);
7✔
242

243
    sgd.PopObjectContainer(roads, GO_Type::Roadsegment);
7✔
244

245
    jobs_wanted.resize(sgd.PopUnsignedInt());
7✔
246
    for(JobNeeded& job : jobs_wanted)
9✔
247
    {
248
        job.job = sgd.Pop<Job>();
2✔
249
        job.workplace = sgd.PopObject<noRoadNode>();
2✔
250
    }
251

252
    if(sgd.GetGameDataVersion() < 2)
7✔
253
        buildings.Deserialize2(sgd);
×
254

255
    sgd.PopObjectContainer(ware_list, GO_Type::Ware);
7✔
256
    sgd.PopObjectContainer(flagworkers);
7✔
257
    sgd.PopObjectContainer(ships, GO_Type::Ship);
7✔
258

259
    sgd.PopContainer(shouldSendDefenderList);
7✔
260

261
    hqPos = sgd.PopMapPoint();
7✔
262

263
    for(const auto i : helpers::enumRange<GoodType>())
546✔
264
    {
265
        if(sgd.GetGameDataVersion() < 11 && wineaddon::isWineAddonGoodType(i))
259✔
266
            continue;
×
267

268
        Distribution& dist = distribution[i];
259✔
269
        helpers::popContainer(sgd, dist.percent_buildings);
259✔
270
        if(sgd.GetGameDataVersion() < 7)
259✔
271
        {
272
            dist.client_buildings.resize(sgd.PopUnsignedInt());
×
273
            helpers::popContainer(sgd, dist.client_buildings, true);
×
274
            dist.goals.resize(sgd.PopUnsignedInt());
×
275
            helpers::popContainer(sgd, dist.goals, true);
×
276
        } else
277
        {
278
            helpers::popContainer(sgd, dist.client_buildings);
259✔
279
            helpers::popContainer(sgd, dist.goals);
259✔
280
        }
281
        dist.selected_goal = sgd.PopUnsignedInt();
259✔
282
    }
283

284
    useCustomBuildOrder_ = sgd.PopBool();
7✔
285

286
    if(sgd.GetGameDataVersion() < 11)
7✔
287
    {
288
        std::vector<BuildingType> build_order_raw(build_order.size() - 3);
×
289
        helpers::popContainer(sgd, build_order_raw, true);
×
290
        build_order_raw.insert(build_order_raw.end(),
×
291
                               {BuildingType::Vineyard, BuildingType::Winery, BuildingType::Temple});
×
292
        std::copy(build_order_raw.begin(), build_order_raw.end(), build_order.begin());
×
293

294
        std::vector<uint8_t> transportPrio_raw(transportPrio.size() - 2);
×
295
        helpers::popContainer(sgd, transportPrio_raw, true);
×
296
        std::copy(transportPrio_raw.begin(), transportPrio_raw.end(), transportPrio.begin());
×
297
    } else
298
    {
299
        helpers::popContainer(sgd, build_order);
7✔
300
        helpers::popContainer(sgd, transportPrio);
7✔
301
    }
302

303
    helpers::popContainer(sgd, militarySettings_);
7✔
304
    helpers::popContainer(sgd, toolsSettings_);
7✔
305

306
    // qx:tools
307
    helpers::popContainer(sgd, tools_ordered);
7✔
308
    tools_ordered_delta = {};
7✔
309

310
    if(sgd.GetGameDataVersion() < 11)
7✔
311
    {
312
        std::vector<unsigned int> global_inventory_good_raw(global_inventory.goods.size() - 2);
×
313
        helpers::popContainer(sgd, global_inventory_good_raw, true);
×
314
        std::copy(global_inventory_good_raw.begin(), global_inventory_good_raw.end(), global_inventory.goods.begin());
×
315

316
        std::vector<unsigned int> global_inventory_people_raw(global_inventory.people.size() - 3);
×
317
        helpers::popContainer(sgd, global_inventory_people_raw, true);
×
318
        std::copy(global_inventory_people_raw.begin(), global_inventory_people_raw.end(),
×
319
                  global_inventory.people.begin());
320
    } else
321
    {
322
        helpers::popContainer(sgd, global_inventory.goods);
7✔
323
        helpers::popContainer(sgd, global_inventory.people);
7✔
324
    }
325

326
    // Visuelle Einstellungen festlegen
327

328
    // für Statistik
329
    for(Statistic& curStatistic : statistic)
35✔
330
    {
331
        // normale Statistik
332
        for(auto& curData : curStatistic.data)
280✔
333
            helpers::popContainer(sgd, curData);
252✔
334

335
        // Warenstatistik
336
        for(unsigned j = 0; j < NUM_STAT_MERCHANDISE_TYPES; ++j)
420✔
337
            helpers::popContainer(sgd, curStatistic.merchandiseData[j]);
392✔
338

339
        curStatistic.currentIndex = sgd.PopUnsignedShort();
28✔
340
        curStatistic.counter = sgd.PopUnsignedShort();
28✔
341
    }
342
    helpers::popContainer(sgd, statisticCurrentData);
7✔
343
    helpers::popContainer(sgd, statisticCurrentMerchandiseData);
7✔
344

345
    // Deserialize Pacts:
346
    for(auto& playerPacts : pacts)
63✔
347
    {
348
        for(Pact& pact : playerPacts)
168✔
349
            pact = GamePlayer::Pact(sgd);
112✔
350
    }
351

352
    emergency = sgd.PopBool();
7✔
353
}
354

355
template<class T_IsWarehouseGood>
356
nobBaseWarehouse* GamePlayer::FindWarehouse(const noRoadNode& start, const T_IsWarehouseGood& isWarehouseGood,
1,503✔
357
                                            bool to_wh, bool use_boat_roads, unsigned* length,
358
                                            const RoadSegment* forbidden) const
359
{
360
    nobBaseWarehouse* best = nullptr;
1,503✔
361

362
    unsigned best_length = std::numeric_limits<unsigned>::max();
1,503✔
363

364
    for(nobBaseWarehouse* wh : buildings.GetStorehouses())
3,440✔
365
    {
366
        // Lagerhaus geeignet?
367
        RTTR_Assert(wh);
1,941✔
368
        if(!isWarehouseGood(*wh))
1,941✔
369
            continue;
1,113✔
370

371
        if(start.GetPos() == wh->GetPos())
837✔
372
        {
373
            // We are already there -> Take it
374
            if(length)
4✔
375
                *length = 0;
×
376
            return wh;
4✔
377
        }
378

379
        // now check if there is at least a chance that the next wh is closer than current best because pathfinding
380
        // takes time
381
        if(world.CalcDistance(start.GetPos(), wh->GetPos()) > best_length)
833✔
382
            continue;
9✔
383
        // Bei der erlaubten Benutzung von Bootsstraßen Waren-Pathfinding benutzen wenns zu nem Lagerhaus gehn soll
384
        // start <-> ziel tauschen bei der wegfindung
385
        unsigned tlength;
386
        if(world.GetRoadPathFinder().FindPath(to_wh ? start : *wh, to_wh ? *wh : start, use_boat_roads, best_length,
824✔
387
                                              forbidden, &tlength))
388
        {
389
            if(tlength < best_length || !best)
364✔
390
            {
391
                best_length = tlength;
360✔
392
                best = wh;
360✔
393
            }
394
        }
395
    }
396

397
    if(length)
1,499✔
398
        *length = best_length;
370✔
399

400
    return best;
1,499✔
401
}
402

403
void GamePlayer::AddBuildingSite(noBuildingSite* bldSite)
28✔
404
{
405
    RTTR_Assert(bldSite->GetPlayer() == GetPlayerId());
28✔
406
    buildings.Add(bldSite);
28✔
407
}
28✔
408

409
void GamePlayer::RemoveBuildingSite(noBuildingSite* bldSite)
15✔
410
{
411
    RTTR_Assert(bldSite->GetPlayer() == GetPlayerId());
15✔
412
    buildings.Remove(bldSite);
15✔
413
}
15✔
414

NEW
415
bool GamePlayer::IsHQTent() const
×
416
{
NEW
417
    if(const nobHQ* hq = GetHQ())
×
NEW
418
        return hq->IsTent();
×
NEW
419
    return false;
×
420
}
421

422
void GamePlayer::SetHQIsTent(bool isTent)
3✔
423
{
424
    if(nobHQ* hq = const_cast<nobHQ*>(GetHQ()))
3✔
425
        hq->SetIsTent(isTent);
2✔
426
}
3✔
427

428
void GamePlayer::AddBuilding(noBuilding* bld, BuildingType bldType)
486✔
429
{
430
    RTTR_Assert(bld->GetPlayer() == GetPlayerId());
486✔
431
    buildings.Add(bld, bldType);
486✔
432
    ChangeStatisticValue(StatisticType::Buildings, 1);
486✔
433

434
    // Order a worker if needed
435
    const auto& description = BLD_WORK_DESC[bldType];
486✔
436
    if(description.job && description.job != Job::Private)
486✔
437
    {
438
        AddJobWanted(*description.job, bld);
102✔
439
    }
440

441
    if(bldType == BuildingType::HarborBuilding)
486✔
442
    {
443
        // Schiff durchgehen und denen Bescheid sagen
444
        for(noShip* ship : ships)
49✔
445
            ship->NewHarborBuilt(static_cast<nobHarborBuilding*>(bld));
11✔
446
    } else if(bldType == BuildingType::Headquarters)
448✔
447
        hqPos = bld->GetPos();
239✔
448
    else if(BuildingProperties::IsMilitary(bldType))
209✔
449
    {
450
        auto* milBld = static_cast<nobMilitary*>(bld);
99✔
451
        // New built? -> Calculate frontier distance
452
        if(milBld->IsNewBuilt())
99✔
453
            milBld->LookForEnemyBuildings();
93✔
454
    }
455
}
486✔
456

457
void GamePlayer::RemoveBuilding(noBuilding* bld, BuildingType bldType)
98✔
458
{
459
    RTTR_Assert(bld->GetPlayer() == GetPlayerId());
98✔
460
    buildings.Remove(bld, bldType);
98✔
461
    ChangeStatisticValue(StatisticType::Buildings, -1);
98✔
462
    if(bldType == BuildingType::HarborBuilding)
98✔
463
    { // Schiffen Bescheid sagen
464
        for(noShip* ship : ships)
27✔
465
            ship->HarborDestroyed(static_cast<nobHarborBuilding*>(bld));
12✔
466
    } else if(bldType == BuildingType::Headquarters)
83✔
467
    {
468
        hqPos = MapPoint::Invalid();
21✔
469
        for(const noBaseBuilding* bld : buildings.GetStorehouses())
21✔
470
        {
471
            if(bld->GetBuildingType() == BuildingType::Headquarters)
1✔
472
            {
473
                hqPos = bld->GetPos();
1✔
474
                break;
1✔
475
            }
476
        }
477
    }
478
    if(BuildingProperties::IsWareHouse(bldType) || BuildingProperties::IsMilitary(bldType))
98✔
479
        TestDefeat();
68✔
480
}
98✔
481

482
void GamePlayer::NewRoadConnection(RoadSegment* rs)
154✔
483
{
484
    // Zu den Straßen hinzufgen, da's ja ne neue ist
485
    roads.push_back(rs);
154✔
486

487
    // Alle Straßen müssen nun gucken, ob sie einen Weg zu einem Warehouse finden
488
    FindCarrierForAllRoads();
154✔
489

490
    // Alle Straßen müssen gucken, ob sie einen Esel bekommen können
491
    for(RoadSegment* rs : roads)
335✔
492
        rs->TryGetDonkey();
181✔
493

494
    // Alle Arbeitsplätze müssen nun gucken, ob sie einen Weg zu einem Lagerhaus mit entsprechender Arbeitskraft finden
495
    FindWarehouseForAllJobs();
154✔
496

497
    // Alle Baustellen müssen nun gucken, ob sie ihr benötigtes Baumaterial bekommen (evtl war vorher die Straße zum
498
    // Lagerhaus unterbrochen
499
    FindMaterialForBuildingSites();
154✔
500

501
    // Alle Lost-Wares müssen gucken, ob sie ein Lagerhaus finden
502
    FindClientForLostWares();
154✔
503

504
    // Alle Militärgebäude müssen ihre Truppen überprüfen und können nun ggf. neue bestellen
505
    // und müssen prüfen, ob sie evtl Gold bekommen
506
    for(nobMilitary* mil : buildings.GetMilitaryBuildings())
172✔
507
    {
508
        mil->RegulateTroops();
18✔
509
        mil->SearchCoins();
18✔
510
    }
511
}
154✔
512

513
void GamePlayer::AddRoad(RoadSegment* rs)
18✔
514
{
515
    roads.push_back(rs);
18✔
516
}
18✔
517

518
void GamePlayer::DeleteRoad(RoadSegment* rs)
101✔
519
{
520
    RTTR_Assert(helpers::contains(roads, rs));
101✔
521
    roads.remove(rs);
101✔
522
}
101✔
523

524
void GamePlayer::FindClientForLostWares()
160✔
525
{
526
    // Alle Lost-Wares müssen gucken, ob sie ein Lagerhaus finden
527
    for(Ware* ware : ware_list)
216✔
528
    {
529
        if(ware->IsLostWare())
56✔
530
        {
531
            if(ware->FindRouteToWarehouse() && ware->IsWaitingAtFlag())
×
532
                ware->CallCarrier();
×
533
        }
534
    }
535
}
160✔
536

537
void GamePlayer::RoadDestroyed()
208✔
538
{
539
    // Alle Waren, die an Flagge liegen und in Lagerhäusern, müssen gucken, ob sie ihr Ziel noch erreichen können, jetzt
540
    // wo eine Straße fehlt
541
    for(auto it = ware_list.begin(); it != ware_list.end();)
217✔
542
    {
543
        Ware* ware = *it;
9✔
544
        if(ware->IsWaitingAtFlag()) // Liegt die Flagge an einer Flagge, muss ihr Weg neu berechnet werden
9✔
545
        {
546
            RoadPathDirection last_next_dir = ware->GetNextDir();
2✔
547
            ware->RecalcRoute();
2✔
548
            // special case: ware was lost some time ago and the new goal is at this flag and not a warehouse,hq,harbor
549
            // and the "flip-route" picked so a carrier would pick up the ware carry it away from goal then back and
550
            // drop  it off at the goal was just destroyed?
551
            // -> try to pick another flip route or tell the goal about failure.
552
            noRoadNode& wareLocation = *ware->GetLocation();
2✔
553
            noBaseBuilding* wareGoal = ware->GetGoal();
2✔
554
            if(wareGoal && ware->GetNextDir() == RoadPathDirection::NorthWest
2✔
555
               && wareLocation.GetPos() == wareGoal->GetFlagPos()
2✔
556
               && ((wareGoal->GetBuildingType() != BuildingType::Storehouse
6✔
557
                    && wareGoal->GetBuildingType() != BuildingType::Headquarters
2✔
558
                    && wareGoal->GetBuildingType() != BuildingType::HarborBuilding)
×
559
                   || wareGoal->GetType() == NodalObjectType::Buildingsite))
2✔
560
            {
561
                Direction newWareDir = Direction::NorthWest;
×
562
                for(auto dir : helpers::EnumRange<Direction>{})
×
563
                {
564
                    dir += 2u; // Need to skip Direction::NorthWest and we used to start with an offset of 2. TODO:
×
565
                               // Increase gameDataVersion and just skip NW
566
                    if(wareLocation.GetRoute(dir))
×
567
                    {
568
                        newWareDir = dir;
×
569
                        break;
×
570
                    }
571
                }
572
                if(newWareDir != Direction::NorthWest)
×
573
                {
574
                    ware->SetNextDir(toRoadPathDirection(newWareDir));
×
575
                } else // no route to goal -> notify goal, try to send ware to a warehouse
576
                {
577
                    ware->NotifyGoalAboutLostWare();
×
578
                    ware->FindRouteToWarehouse();
×
579
                }
580
            }
581
            // end of special case
582

583
            // notify carriers/flags about news if there are any
584
            if(ware->GetNextDir() != last_next_dir)
2✔
585
            {
586
                // notify current flag that transport in the old direction might not longer be required
587
                ware->RemoveWareJobForDir(last_next_dir);
×
588
                if(ware->GetNextDir() != RoadPathDirection::None)
×
589
                    ware->CallCarrier();
×
590
            }
591
        } else if(ware->IsWaitingInWarehouse())
7✔
592
        {
593
            if(!ware->IsRouteToGoal())
×
594
            {
595
                // Das Ziel wird nun nich mehr beliefert
596
                ware->NotifyGoalAboutLostWare();
×
597
                // Ware aus der Warteliste des Lagerhauses entfernen
598
                static_cast<nobBaseWarehouse*>(ware->GetLocation())->CancelWare(ware);
×
599
                // Ware aus der Liste raus
600
                it = ware_list.erase(it);
×
601
                continue;
×
602
            }
603
        } else if(ware->IsWaitingForShip())
7✔
604
        {
605
            // Weg neu berechnen
606
            ware->RecalcRoute();
×
607
        }
608

609
        ++it;
9✔
610
    }
611

612
    // Alle Häfen müssen ihre Figuren den Weg überprüfen lassen
613
    for(nobHarborBuilding* hb : buildings.GetHarbors())
223✔
614
    {
615
        hb->ExamineShipRouteOfPeople();
15✔
616
    }
617
}
208✔
618

619
bool GamePlayer::FindCarrierForRoad(RoadSegment* rs) const
182✔
620
{
621
    RTTR_Assert(rs->GetF1() != nullptr && rs->GetF2() != nullptr);
182✔
622
    std::array<unsigned, 2> length;
623
    std::array<nobBaseWarehouse*, 2> best;
624

625
    // Braucht der ein Boot?
626
    if(rs->GetRoadType() == RoadType::Water)
182✔
627
    {
628
        // dann braucht man Träger UND Boot
629
        best[0] = FindWarehouse(*rs->GetF1(), FW::HasWareAndFigure(GoodType::Boat, Job::Helper, false), false, false,
×
630
                                &length[0], rs);
×
631
        // 2. Flagge des Weges
632
        best[1] = FindWarehouse(*rs->GetF2(), FW::HasWareAndFigure(GoodType::Boat, Job::Helper, false), false, false,
×
633
                                &length[1], rs);
×
634
    } else
635
    {
636
        // 1. Flagge des Weges
637
        best[0] = FindWarehouse(*rs->GetF1(), FW::HasFigure(Job::Helper, false), false, false, &length[0], rs);
182✔
638
        // 2. Flagge des Weges
639
        best[1] = FindWarehouse(*rs->GetF2(), FW::HasFigure(Job::Helper, false), false, false, &length[1], rs);
182✔
640
    }
641

642
    // überhaupt nen Weg gefunden?
643
    // Welche Flagge benutzen?
644
    if(best[0] && (!best[1] || length[0] < length[1]))
182✔
645
        best[0]->OrderCarrier(*rs->GetF1(), *rs);
64✔
646
    else if(best[1])
118✔
647
        best[1]->OrderCarrier(*rs->GetF2(), *rs);
42✔
648
    else
649
        return false;
76✔
650
    return true;
106✔
651
}
652

653
bool GamePlayer::IsWarehouseValid(nobBaseWarehouse* wh) const
×
654
{
655
    return helpers::contains(buildings.GetStorehouses(), wh);
×
656
}
657

658
void GamePlayer::RecalcDistribution()
299✔
659
{
660
    GoodType lastWare = GoodType::Nothing;
299✔
661
    for(const DistributionMapping& mapping : distributionMap)
8,073✔
662
    {
663
        if(lastWare == std::get<0>(mapping))
7,774✔
664
            continue;
5,681✔
665
        lastWare = std::get<0>(mapping);
2,093✔
666
        RecalcDistributionOfWare(std::get<0>(mapping));
2,093✔
667
    }
668
}
299✔
669

670
void GamePlayer::RecalcDistributionOfWare(const GoodType ware)
2,093✔
671
{
672
    // Punktesystem zur Verteilung, in der Liste alle Gebäude sammeln, die die Ware wollen
673
    distribution[ware].client_buildings.clear();
2,093✔
674

675
    // 1. Anteile der einzelnen Waren ausrechnen
676

677
    /// Mapping of buildings that want the current ware to its percentage
678
    using BldEntry = std::pair<BuildingType, uint8_t>;
679
    std::vector<BldEntry> bldPercentageMap;
2,093✔
680

681
    unsigned goal_count = 0;
2,093✔
682

683
    for(const auto bld : helpers::enumRange<BuildingType>())
175,812✔
684
    {
685
        uint8_t percentForCurBld = distribution[ware].percent_buildings[bld];
83,720✔
686
        if(percentForCurBld)
83,720✔
687
        {
688
            distribution[ware].client_buildings.push_back(bld);
7,773✔
689
            goal_count += percentForCurBld;
7,773✔
690
            bldPercentageMap.emplace_back(bld, percentForCurBld);
7,773✔
691
        }
692
    }
693

694
    // TODO: evtl noch die counts miteinander kürzen (ggt berechnen)
695

696
    // Array für die Gebäudtypen erstellen
697

698
    std::vector<BuildingType>& wareGoals = distribution[ware].goals;
2,093✔
699
    wareGoals.clear();
2,093✔
700
    wareGoals.reserve(goal_count);
2,093✔
701

702
    // just drop them in the list, the distribution will be handled by going through this list using a prime as step
703
    // (see GameClientPlayer::FindClientForWare)
704
    for(const BldEntry& bldEntry : bldPercentageMap)
9,866✔
705
    {
706
        for(unsigned char i = 0; i < bldEntry.second; ++i)
54,664✔
707
            wareGoals.push_back(bldEntry.first);
46,891✔
708
    }
709

710
    distribution[ware].selected_goal = 0;
2,093✔
711
}
2,093✔
712

713
void GamePlayer::FindCarrierForAllRoads()
174✔
714
{
715
    for(RoadSegment* rs : roads)
384✔
716
    {
717
        if(!rs->hasCarrier(0))
210✔
718
            FindCarrierForRoad(rs);
160✔
719
    }
720
}
174✔
721

722
void GamePlayer::FindMaterialForBuildingSites()
169✔
723
{
724
    for(noBuildingSite* bldSite : buildings.GetBuildingSites())
192✔
725
        bldSite->OrderConstructionMaterial();
23✔
726
}
169✔
727

728
void GamePlayer::AddJobWanted(const Job job, noRoadNode* workplace)
138✔
729
{
730
    // Und gleich suchen
731
    if(!FindWarehouseForJob(job, workplace))
138✔
732
    {
733
        JobNeeded jn = {job, workplace};
125✔
734
        jobs_wanted.push_back(jn);
125✔
735
    }
736
}
138✔
737

738
void GamePlayer::JobNotWanted(noRoadNode* workplace, bool all)
53✔
739
{
740
    for(auto it = jobs_wanted.begin(); it != jobs_wanted.end();)
57✔
741
    {
742
        if(it->workplace == workplace)
42✔
743
        {
744
            it = jobs_wanted.erase(it);
38✔
745
            if(!all)
38✔
746
                return;
38✔
747
        } else
748
        {
749
            ++it;
4✔
750
        }
751
    }
752
}
753

754
void GamePlayer::OneJobNotWanted(const Job job, noRoadNode* workplace)
2✔
755
{
756
    const auto it = helpers::find_if(
757
      jobs_wanted, [workplace, job](const auto& it) { return it.workplace == workplace && it.job == job; });
2✔
758
    if(it != jobs_wanted.end())
2✔
759
        jobs_wanted.erase(it);
×
760
}
2✔
761

762
void GamePlayer::SendPostMessage(std::unique_ptr<PostMsg> msg)
35✔
763
{
764
    world.GetPostMgr().SendMsg(GetPlayerId(), std::move(msg));
35✔
765
}
35✔
766

767
unsigned GamePlayer::GetToolsOrderedVisual(Tool tool) const
×
768
{
769
    return std::max(0, int(tools_ordered[tool] + tools_ordered_delta[tool]));
×
770
}
771

772
unsigned GamePlayer::GetToolsOrdered(Tool tool) const
369✔
773
{
774
    return tools_ordered[tool];
369✔
775
}
776

777
bool GamePlayer::ChangeToolOrderVisual(Tool tool, int changeAmount) const
×
778
{
779
    if(std::abs(changeAmount) > 100)
×
780
        return false;
×
781
    int newOrderAmount = int(GetToolsOrderedVisual(tool)) + changeAmount;
×
782
    if(newOrderAmount < 0 || newOrderAmount > 100)
×
783
        return false;
×
784
    tools_ordered_delta[tool] += changeAmount;
×
785
    return true;
×
786
}
787

788
unsigned GamePlayer::GetToolPriority(Tool tool) const
271✔
789
{
790
    return toolsSettings_[tool];
271✔
791
}
792

793
void GamePlayer::ToolOrderProcessed(Tool tool)
4✔
794
{
795
    if(tools_ordered[tool])
4✔
796
    {
797
        --tools_ordered[tool];
4✔
798
        world.GetNotifications().publish(ToolNote(ToolNote::OrderCompleted, GetPlayerId()));
4✔
799
    }
800
}
4✔
801

802
bool GamePlayer::FindWarehouseForJob(const Job job, noRoadNode* goal) const
157✔
803
{
804
    nobBaseWarehouse* wh = FindWarehouse(*goal, FW::HasFigure(job, true), false, false);
157✔
805

806
    if(wh)
157✔
807
    {
808
        // Es wurde ein Lagerhaus gefunden, wo es den geforderten Beruf gibt, also den Typen zur Arbeit rufen
809
        wh->OrderJob(job, goal, true);
32✔
810
        return true;
32✔
811
    }
812

813
    return false;
125✔
814
}
815

816
void GamePlayer::FindWarehouseForAllJobs()
173✔
817
{
818
    for(auto it = jobs_wanted.begin(); it != jobs_wanted.end();)
192✔
819
    {
820
        if(FindWarehouseForJob(it->job, it->workplace))
19✔
821
            it = jobs_wanted.erase(it);
19✔
822
        else
823
            ++it;
×
824
    }
825
}
173✔
826

827
void GamePlayer::FindWarehouseForAllJobs(const Job job)
207✔
828
{
829
    for(auto it = jobs_wanted.begin(); it != jobs_wanted.end();)
207✔
830
    {
831
        if(it->job == job)
×
832
        {
833
            if(FindWarehouseForJob(it->job, it->workplace))
×
834
                it = jobs_wanted.erase(it);
×
835
            else
836
                ++it;
×
837
        } else
838
            ++it;
×
839
    }
840
}
207✔
841

842
Ware* GamePlayer::OrderWare(const GoodType ware, noBaseBuilding* goal)
148✔
843
{
844
    /// Gibt es ein Lagerhaus mit dieser Ware?
845
    nobBaseWarehouse* wh = FindWarehouse(*goal, FW::HasMinWares(ware, 1), false, true);
148✔
846

847
    if(wh)
148✔
848
    {
849
        // Prüfe ob Notfallprogramm aktiv
850
        if(!emergency)
108✔
851
            return wh->OrderWare(ware, goal);
108✔
852
        else
853
        {
854
            // Wenn Notfallprogramm aktiv nur an Holzfäller und Sägewerke Bretter/Steine liefern
855
            if((ware != GoodType::Boards && ware != GoodType::Stones)
×
856
               || goal->GetBuildingType() == BuildingType::Woodcutter
×
857
               || goal->GetBuildingType() == BuildingType::Sawmill)
×
858
                return wh->OrderWare(ware, goal);
×
859
            else
860
                return nullptr;
×
861
        }
862
    } else // no warehouse can deliver the ware -> check all our wares for lost wares that might match the order
863
    {
864
        unsigned bestLength = std::numeric_limits<unsigned>::max();
40✔
865
        Ware* bestWare = nullptr;
40✔
866
        for(Ware* curWare : ware_list)
83✔
867
        {
868
            if(curWare->IsLostWare() && curWare->type == ware)
43✔
869
            {
870
                // got a lost ware with a road to goal -> find best
871
                unsigned curLength = curWare->CheckNewGoalForLostWare(*goal);
×
872
                if(curLength < bestLength)
×
873
                {
874
                    bestLength = curLength;
×
875
                    bestWare = curWare;
×
876
                }
877
            }
878
        }
879
        if(bestWare)
40✔
880
        {
881
            bestWare->SetNewGoalForLostWare(goal);
×
882
            return bestWare;
×
883
        }
884
    }
885
    return nullptr;
40✔
886
}
887

888
nofCarrier* GamePlayer::OrderDonkey(RoadSegment* road) const
3✔
889
{
890
    std::array<unsigned, 2> length;
891
    std::array<nobBaseWarehouse*, 2> best;
892

893
    // 1. Flagge des Weges
894
    best[0] = FindWarehouse(*road->GetF1(), FW::HasFigure(Job::PackDonkey, false), false, false, &length[0], road);
3✔
895
    // 2. Flagge des Weges
896
    best[1] = FindWarehouse(*road->GetF2(), FW::HasFigure(Job::PackDonkey, false), false, false, &length[1], road);
3✔
897

898
    // überhaupt nen Weg gefunden?
899
    // Welche Flagge benutzen?
900
    if(best[0] && (!best[1] || length[0] < length[1]))
3✔
901
        return best[0]->OrderDonkey(road, road->GetF1());
2✔
902
    else if(best[1])
1✔
903
        return best[1]->OrderDonkey(road, road->GetF2());
×
904
    else
905
        return nullptr;
1✔
906
}
907

908
RoadSegment* GamePlayer::FindRoadForDonkey(noRoadNode* start, noRoadNode** goal)
8✔
909
{
910
    // Bisher höchste Trägerproduktivität und die entsprechende Straße dazu
911
    unsigned best_productivity = 0;
8✔
912
    RoadSegment* best_road = nullptr;
8✔
913
    // Beste Flagge dieser Straße
914
    *goal = nullptr;
8✔
915

916
    for(RoadSegment* roadSeg : roads)
8✔
917
    {
918
        // Braucht die Straße einen Esel?
919
        if(roadSeg->NeedDonkey())
×
920
        {
921
            // Beste Flagge von diesem Weg, und beste Wegstrecke
922
            noRoadNode* current_best_goal = nullptr;
×
923
            // Weg zu beiden Flaggen berechnen
924
            unsigned length1, length2;
925
            bool isF1Reachable = world.FindHumanPathOnRoads(*start, *roadSeg->GetF1(), &length1, nullptr, roadSeg)
×
926
                                 != RoadPathDirection::None;
×
927
            bool isF2Reachable = world.FindHumanPathOnRoads(*start, *roadSeg->GetF2(), &length2, nullptr, roadSeg)
×
928
                                 != RoadPathDirection::None;
×
929

930
            // Wenn man zu einer Flagge nich kommt, die jeweils andere nehmen
931
            if(!isF1Reachable)
×
932
                current_best_goal = (isF2Reachable) ? roadSeg->GetF2() : nullptr;
×
933
            else if(!isF2Reachable)
×
934
                current_best_goal = roadSeg->GetF1();
×
935
            else
936
            {
937
                // ansonsten die kürzeste von beiden
938
                current_best_goal = (length1 < length2) ? roadSeg->GetF1() : roadSeg->GetF2();
×
939
            }
940

941
            // Kein Weg führt hin, nächste Straße bitte
942
            if(!current_best_goal)
×
943
                continue;
×
944

945
            // Jeweiligen Weg bestimmen
946
            unsigned current_best_way = (roadSeg->GetF1() == current_best_goal) ? length1 : length2;
×
947

948
            // Produktivität ausrechnen, *10 die Produktivität + die Wegstrecke, damit die
949
            // auch noch mit einberechnet wird
950
            unsigned current_productivity = 10 * roadSeg->getCarrier(0)->GetProductivity() + current_best_way;
×
951

952
            // Besser als der bisher beste?
953
            if(current_productivity > best_productivity)
×
954
            {
955
                // Dann wird der vom Thron gestoßen
956
                best_productivity = current_productivity;
×
957
                best_road = roadSeg;
×
958
                *goal = current_best_goal;
×
959
            }
960
        }
961
    }
962

963
    return best_road;
8✔
964
}
965

966
struct ClientForWare
967
{
968
    noBaseBuilding* bld;
969
    unsigned estimate; // points minus half the optimal distance
970
    unsigned points;
971

972
    ClientForWare(noBaseBuilding* bld, unsigned estimate, unsigned points)
×
973
        : bld(bld), estimate(estimate), points(points)
×
974
    {}
×
975

976
    bool operator<(const ClientForWare& b) const
×
977
    {
978
        // use estimate, points and object id (as tie breaker) for sorting
979
        if(estimate != b.estimate)
×
980
            return estimate > b.estimate;
×
981
        else if(points != b.points)
×
982
            return points > b.points;
×
983
        else
984
            return bld->GetObjId() > b.bld->GetObjId();
×
985
    }
986
};
987

988
noBaseBuilding* GamePlayer::FindClientForWare(const Ware& ware)
12✔
989
{
990
    // Wenn es eine Goldmünze ist, wird das Ziel auf eine andere Art und Weise berechnet
991
    if(ware.type == GoodType::Coins)
12✔
992
        return FindClientForCoin(ware);
×
993

994
    // Warentyp herausfinden
995
    GoodType gt = ware.type;
12✔
996
    // All food is considered fish in the distribution table
997
    Distribution& wareDistribution =
998
      (gt == GoodType::Bread || gt == GoodType::Meat) ? distribution[GoodType::Fish] : distribution[gt];
12✔
999

1000
    std::vector<ClientForWare> possibleClients;
12✔
1001

1002
    const noRoadNode* start = ware.GetLocation();
12✔
1003

1004
    // Bretter und Steine können evtl. auch Häfen für Expeditionen gebrauchen
1005
    if(gt == GoodType::Stones || gt == GoodType::Boards)
12✔
1006
    {
1007
        for(nobHarborBuilding* harbor : buildings.GetHarbors())
8✔
1008
        {
1009
            unsigned points = harbor->CalcDistributionPoints(gt);
×
1010
            if(!points)
×
1011
                continue;
×
1012

1013
            points += 10 * 30; // Verteilung existiert nicht, Expeditionen haben allerdings hohe Priorität
×
1014
            unsigned distance = world.CalcDistance(start->GetPos(), harbor->GetPos()) / 2;
×
1015
            possibleClients.push_back(ClientForWare(harbor, points > distance ? points - distance : 0, points));
×
1016
        }
1017
    }
1018

1019
    for(const auto bldType : wareDistribution.client_buildings)
41✔
1020
    {
1021
        // BuildingType::Headquarters sind Baustellen!!, da HQs ja sowieso nicht gebaut werden können
1022
        if(bldType == BuildingType::Headquarters)
29✔
1023
        {
1024
            // Bei Baustellen die Extraliste abfragen
1025
            for(noBuildingSite* bldSite : buildings.GetBuildingSites())
8✔
1026
            {
1027
                unsigned points = bldSite->CalcDistributionPoints(gt);
×
1028
                if(!points)
×
1029
                    continue;
×
1030

1031
                points += wareDistribution.percent_buildings[BuildingType::Headquarters] * 30;
×
1032
                unsigned distance = world.CalcDistance(start->GetPos(), bldSite->GetPos()) / 2;
×
1033
                possibleClients.push_back(ClientForWare(bldSite, points > distance ? points - distance : 0, points));
×
1034
            }
1035
        } else
1036
        {
1037
            // Für übrige Gebäude
1038
            for(nobUsual* bld : buildings.GetBuildings(bldType))
21✔
1039
            {
1040
                unsigned points = bld->CalcDistributionPoints(gt);
×
1041
                if(!points)
×
1042
                    continue; // Ware not needed
×
1043

1044
                if(!wareDistribution.goals.empty())
×
1045
                {
1046
                    if(bld->GetBuildingType()
×
1047
                       == static_cast<BuildingType>(wareDistribution.goals[wareDistribution.selected_goal]))
×
1048
                        points += 300;
×
1049
                    else if(points >= 300) // avoid overflows (async!)
×
1050
                        points -= 300;
×
1051
                    else
1052
                        points = 0;
×
1053
                }
1054

1055
                unsigned distance = world.CalcDistance(start->GetPos(), bld->GetPos()) / 2;
×
1056
                possibleClients.push_back(ClientForWare(bld, points > distance ? points - distance : 0, points));
×
1057
            }
1058
        }
1059
    }
1060

1061
    // sort our clients, highest score first
1062
    std::sort(possibleClients.begin(), possibleClients.end());
12✔
1063

1064
    noBaseBuilding* lastBld = nullptr;
12✔
1065
    noBaseBuilding* bestBld = nullptr;
12✔
1066
    unsigned best_points = 0;
12✔
1067
    for(auto& possibleClient : possibleClients)
12✔
1068
    {
1069
        unsigned path_length;
1070

1071
        // If our estimate is worse (or equal) best_points, the real value cannot be better.
1072
        // As our list is sorted, further entries cannot be better either, so stop searching.
1073
        if(possibleClient.estimate <= best_points)
×
1074
            break;
×
1075

1076
        // get rid of double building entries. TODO: why are there double entries!?
1077
        if(possibleClient.bld == lastBld)
×
1078
            continue;
×
1079

1080
        lastBld = possibleClient.bld;
×
1081

1082
        // Just to be sure no underflow happens...
1083
        if(possibleClient.points < best_points + 1)
×
1084
            continue;
×
1085

1086
        // Find path ONLY if it may be better. Pathfinding is limited to the worst path score that would lead to a
1087
        // better score. This eliminates the worst case scenario where all nodes in a split road network would be hit by
1088
        // the pathfinding only to conclude that there is no possible path.
1089
        if(world.FindPathForWareOnRoads(*start, *possibleClient.bld, &path_length, nullptr,
×
1090
                                        (possibleClient.points - best_points) * 2 - 1)
×
1091
           != RoadPathDirection::None)
×
1092
        {
1093
            unsigned score = possibleClient.points - (path_length / 2);
×
1094

1095
            // As we have limited our pathfinding to take a maximum of (points - best_points) * 2 - 1 steps,
1096
            // path_length / 2 can at most be points - best_points - 1, so the score will be greater than best_points.
1097
            // :)
1098
            RTTR_Assert(score > best_points);
×
1099

1100
            best_points = score;
×
1101
            bestBld = possibleClient.bld;
×
1102
        }
1103
    }
1104

1105
    if(bestBld && !wareDistribution.goals.empty())
12✔
1106
        wareDistribution.selected_goal =
×
1107
          (wareDistribution.selected_goal + 907) % unsigned(wareDistribution.goals.size());
×
1108

1109
    // Wenn kein Abnehmer gefunden wurde, muss es halt in ein Lagerhaus
1110
    if(!bestBld)
12✔
1111
        bestBld = FindWarehouseForWare(ware);
12✔
1112

1113
    return bestBld;
12✔
1114
}
1115

1116
nobBaseWarehouse* GamePlayer::FindWarehouseForWare(const Ware& ware) const
13✔
1117
{
1118
    // Check whs that collect this ware
1119
    nobBaseWarehouse* wh = FindWarehouse(*ware.GetLocation(), FW::CollectsWare(ware.type), true, true);
13✔
1120
    // If there is none, check those that accept it
1121
    if(!wh)
13✔
1122
    {
1123
        // First find the ones, that do not send it right away (IMPORTANT: This avoids sending a ware to the wh that is
1124
        // sending the ware out)
1125
        wh = FindWarehouse(*ware.GetLocation(), FW::AcceptsWareButNoSend(ware.type), true, true);
13✔
1126
        // The others only if this fails
1127
        if(!wh)
13✔
1128
            wh = FindWarehouse(*ware.GetLocation(), FW::AcceptsWare(ware.type), true, true);
4✔
1129
    }
1130
    return wh;
13✔
1131
}
1132

1133
nobBaseMilitary* GamePlayer::FindClientForCoin(const Ware& ware) const
×
1134
{
1135
    nobBaseMilitary* bb = nullptr;
×
1136
    unsigned best_points = 0, points;
×
1137

1138
    // Militärgebäude durchgehen
1139
    for(nobMilitary* milBld : buildings.GetMilitaryBuildings())
×
1140
    {
1141
        unsigned way_points;
1142

1143
        points = milBld->CalcCoinsPoints();
×
1144
        // Wenn 0, will er gar keine Münzen (Goldzufuhr gestoppt)
1145
        if(points)
×
1146
        {
1147
            // Weg dorthin berechnen
1148
            if(world.FindPathForWareOnRoads(*ware.GetLocation(), *milBld, &way_points) != RoadPathDirection::None)
×
1149
            {
1150
                // Die Wegpunkte noch davon abziehen
1151
                points -= way_points;
×
1152
                // Besser als der bisher Beste?
1153
                if(points > best_points)
×
1154
                {
1155
                    best_points = points;
×
1156
                    bb = milBld;
×
1157
                }
1158
            }
1159
        }
1160
    }
1161

1162
    // Wenn kein Abnehmer gefunden wurde, muss es halt in ein Lagerhaus
1163
    if(!bb)
×
1164
        bb = FindWarehouseForWare(ware);
×
1165

1166
    return bb;
×
1167
}
1168

1169
unsigned GamePlayer::GetBuidingSitePriority(const noBuildingSite* building_site)
×
1170
{
1171
    if(useCustomBuildOrder_)
×
1172
    {
1173
        // Spezielle Reihenfolge
1174

1175
        // Typ in der Reihenfolge suchen und Position als Priorität zurückgeben
1176
        for(unsigned i = 0; i < build_order.size(); ++i)
×
1177
        {
1178
            if(building_site->GetBuildingType() == build_order[i])
×
1179
                return i;
×
1180
        }
1181
    } else
1182
    {
1183
        // Reihenfolge der Bauaufträge, also was zuerst in Auftrag gegeben wurde, wird zuerst gebaut
1184
        unsigned i = 0;
×
1185
        for(noBuildingSite* bldSite : buildings.GetBuildingSites())
×
1186
        {
1187
            if(building_site == bldSite)
×
1188
                return i;
×
1189
            i++;
×
1190
        }
1191
    }
1192

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

1199
void GamePlayer::ConvertTransportData(const TransportOrders& transport_data)
2✔
1200
{
1201
    for(const auto ware : helpers::EnumRange<GoodType>{})
156✔
1202
        transportPrio[ware] = GetTransportPrioFromOrdering(transport_data, ware);
74✔
1203
}
2✔
1204

1205
bool GamePlayer::IsAlly(const unsigned char playerId) const
10,239✔
1206
{
1207
    // Der Spieler ist ja auch zu sich selber verbündet
1208
    if(GetPlayerId() == playerId)
10,239✔
1209
        return true;
2,606✔
1210
    else
1211
        return GetPactState(PactType::TreatyOfAlliance, playerId) == PactState::Accepted;
7,633✔
1212
}
1213

1214
bool GamePlayer::IsAttackable(const unsigned char playerId) const
797✔
1215
{
1216
    // Verbündete dürfen nicht angegriffen werden
1217
    if(IsAlly(playerId))
797✔
1218
        return false;
40✔
1219
    else
1220
        // Ansonsten darf bei bestehendem Nichtangriffspakt ebenfalls nicht angegriffen werden
1221
        return GetPactState(PactType::NonAgressionPact, playerId) != PactState::Accepted;
757✔
1222
}
1223

1224
void GamePlayer::OrderTroops(nobMilitary* goal, std::array<unsigned, NUM_SOLDIER_RANKS> counts,
31✔
1225
                             unsigned total_max) const
1226
{
1227
    // Solange Lagerhäuser nach Soldaten absuchen, bis entweder keins mehr übrig ist oder alle Soldaten bestellt sind
1228
    nobBaseWarehouse* wh;
1229
    unsigned sum = 0;
31✔
1230
    do
3✔
1231
    {
1232
        std::array<bool, NUM_SOLDIER_RANKS> desiredRanks;
1233
        for(unsigned i = 0; i < NUM_SOLDIER_RANKS; i++)
204✔
1234
            desiredRanks[i] = counts[i] > 0;
170✔
1235

1236
        wh = FindWarehouse(*goal, FW::HasAnyMatchingSoldier(desiredRanks), false, false);
34✔
1237
        if(wh)
34✔
1238
        {
1239
            wh->OrderTroops(goal, counts, total_max);
19✔
1240
            sum = std::accumulate(counts.begin(), counts.end(), 0u);
19✔
1241
        }
1242
    } while(total_max && sum && wh);
34✔
1243
}
31✔
1244

1245
void GamePlayer::RegulateAllTroops()
64✔
1246
{
1247
    for(nobMilitary* milBld : buildings.GetMilitaryBuildings())
67✔
1248
        milBld->RegulateTroops();
3✔
1249
}
64✔
1250

1251
/// Prüft von allen Militärgebäuden die Fahnen neu
1252
void GamePlayer::RecalcMilitaryFlags()
48✔
1253
{
1254
    for(nobMilitary* milBld : buildings.GetMilitaryBuildings())
72✔
1255
        milBld->LookForEnemyBuildings(nullptr);
24✔
1256
}
48✔
1257

1258
/// Sucht für Soldaten ein neues Militärgebäude, als Argument wird Referenz auf die
1259
/// entsprechende Soldatenanzahl im Lagerhaus verlangt
1260
void GamePlayer::NewSoldiersAvailable(const unsigned& soldier_count)
78✔
1261
{
1262
    RTTR_Assert(soldier_count > 0);
78✔
1263
    // solange laufen lassen, bis soldier_count = 0, d.h. der Soldat irgendwohin geschickt wurde
1264
    // Zuerst nach unbesetzten Militärgebäude schauen
1265
    for(nobMilitary* milBld : buildings.GetMilitaryBuildings())
95✔
1266
    {
1267
        if(milBld->IsNewBuilt())
17✔
1268
        {
1269
            milBld->RegulateTroops();
×
1270
            // Used that soldier? Go out
1271
            if(!soldier_count)
×
1272
                return;
×
1273
        }
1274
    }
1275

1276
    // Als nächstes Gebäude in Grenznähe
1277
    for(nobMilitary* milBld : buildings.GetMilitaryBuildings())
95✔
1278
    {
1279
        if(milBld->GetFrontierDistance() == FrontierDistance::Near)
17✔
1280
        {
1281
            milBld->RegulateTroops();
17✔
1282
            // Used that soldier? Go out
1283
            if(!soldier_count)
17✔
1284
                return;
×
1285
        }
1286
    }
1287

1288
    // Und den Rest ggf.
1289
    for(nobMilitary* milBld : buildings.GetMilitaryBuildings())
95✔
1290
    {
1291
        // already checked? -> skip
1292
        if(milBld->GetFrontierDistance() == FrontierDistance::Near || milBld->IsNewBuilt())
17✔
1293
            continue;
17✔
1294
        milBld->RegulateTroops();
×
1295
        if(!soldier_count) // used the soldier?
×
1296
            return;
×
1297
    }
1298
}
1299

1300
void GamePlayer::CallFlagWorker(const MapPoint pt, const Job job)
32✔
1301
{
1302
    auto* flag = world.GetSpecObj<noFlag>(pt);
32✔
1303
    if(!flag)
32✔
1304
        return;
2✔
1305
    /// Find wh with given job type (e.g. geologist, scout, ...)
1306
    nobBaseWarehouse* wh = FindWarehouse(*flag, FW::HasFigure(job, true), false, false);
30✔
1307

1308
    /// Wenns eins gibt, dann rufen
1309
    if(wh)
30✔
1310
        wh->OrderJob(job, flag, true);
26✔
1311
}
1312

1313
bool GamePlayer::IsFlagWorker(const nofFlagWorker* flagworker)
×
1314
{
1315
    return helpers::contains(flagworkers, flagworker);
×
1316
}
1317

1318
void GamePlayer::FlagDestroyed(noFlag* flag)
761✔
1319
{
1320
    // Alle durchgehen und ggf. sagen, dass sie keine Flagge mehr haben, wenn das ihre Flagge war, die zerstört wurde
1321
    for(auto it = flagworkers.begin(); it != flagworkers.end();)
761✔
1322
    {
1323
        if((*it)->GetFlag() == flag)
×
1324
        {
1325
            (*it)->LostWork();
×
1326
            it = flagworkers.erase(it);
×
1327
        } else
1328
            ++it;
×
1329
    }
1330
}
761✔
1331

1332
void GamePlayer::RefreshDefenderList()
52✔
1333
{
1334
    shouldSendDefenderList.clear();
52✔
1335
    // Add as many true values as set in the settings, the rest will be false
1336
    for(unsigned i = 0; i < MILITARY_SETTINGS_SCALE[2]; ++i)
312✔
1337
        shouldSendDefenderList.push_back(i < militarySettings_[2]);
260✔
1338
    // und ordentlich schütteln
1339
    RANDOM_SHUFFLE2(shouldSendDefenderList, 0);
52✔
1340
}
52✔
1341

1342
void GamePlayer::ChangeMilitarySettings(const MilitarySettings& military_settings)
52✔
1343
{
1344
    for(unsigned i = 0; i < military_settings.size(); ++i)
468✔
1345
    {
1346
        // Sicherstellen, dass im validen Bereich
1347
        RTTR_Assert(military_settings[i] <= MILITARY_SETTINGS_SCALE[i]);
416✔
1348
        this->militarySettings_[i] = military_settings[i];
416✔
1349
    }
1350
    /// Truppen müssen neu kalkuliert werden
1351
    RegulateAllTroops();
52✔
1352
    /// Die Verteidigungsliste muss erneuert werden
1353
    RefreshDefenderList();
52✔
1354
}
52✔
1355

1356
/// Setzt neue Werkzeugeinstellungen
1357
void GamePlayer::ChangeToolsSettings(const ToolSettings& tools_settings,
6✔
1358
                                     const helpers::EnumArray<int8_t, Tool>& orderChanges)
1359
{
1360
    const bool settingsChanged = toolsSettings_ != tools_settings;
6✔
1361
    toolsSettings_ = tools_settings;
6✔
1362
    if(settingsChanged)
6✔
1363
        world.GetNotifications().publish(ToolNote(ToolNote::SettingsChanged, GetPlayerId()));
4✔
1364

1365
    for(const auto tool : helpers::enumRange<Tool>())
168✔
1366
    {
1367
        tools_ordered[tool] = helpers::clamp(tools_ordered[tool] + orderChanges[tool], 0, 100);
72✔
1368
        tools_ordered_delta[tool] -= orderChanges[tool];
72✔
1369

1370
        if(orderChanges[tool] != 0)
72✔
1371
        {
1372
            LOG.write(">> Committing an order of %1% for tool #%2%(%3%)\n", LogTarget::File) % (int)orderChanges[tool]
8✔
1373
              % static_cast<unsigned>(tool) % _(WARE_NAMES[TOOL_TO_GOOD[tool]]);
8✔
1374
            world.GetNotifications().publish(ToolNote(ToolNote::OrderPlaced, GetPlayerId()));
4✔
1375
        }
1376
    }
1377
}
6✔
1378

1379
/// Setzt neue Verteilungseinstellungen
1380
void GamePlayer::ChangeDistribution(const Distributions& distribution_settings)
6✔
1381
{
1382
    unsigned idx = 0;
6✔
1383
    for(const DistributionMapping& mapping : distributionMap)
162✔
1384
    {
1385
        distribution[std::get<0>(mapping)].percent_buildings[std::get<1>(mapping)] = distribution_settings[idx++];
156✔
1386
    }
1387

1388
    RecalcDistribution();
6✔
1389
}
6✔
1390

1391
/// Setzt neue Baureihenfolge-Einstellungen
1392
void GamePlayer::ChangeBuildOrder(bool useCustomBuildOrder, const BuildOrders& order_data)
2✔
1393
{
1394
    this->useCustomBuildOrder_ = useCustomBuildOrder;
2✔
1395
    this->build_order = order_data;
2✔
1396
}
2✔
1397

1398
bool GamePlayer::ShouldSendDefender()
18✔
1399
{
1400
    // Wenn wir schon am Ende sind, muss die Verteidgungsliste erneuert werden
1401
    if(shouldSendDefenderList.empty())
18✔
1402
        RefreshDefenderList();
×
1403

1404
    bool result = shouldSendDefenderList.back();
18✔
1405
    shouldSendDefenderList.pop_back();
18✔
1406
    return result;
18✔
1407
}
1408

1409
void GamePlayer::TestDefeat()
68✔
1410
{
1411
    // Nicht schon besiegt?
1412
    // Keine Militärgebäude, keine Lagerhäuser (HQ,Häfen) -> kein Land --> verloren
1413
    if(!isDefeated && buildings.GetMilitaryBuildings().empty() && buildings.GetStorehouses().empty())
68✔
1414
        Surrender();
19✔
1415
}
68✔
1416

1417
const nobHQ* GamePlayer::GetHQ() const
3✔
1418
{
1419
    const MapPoint& hqPos = GetHQPos();
3✔
1420
    return hqPos.isValid() ? GetGameWorld().GetSpecObj<nobHQ>(hqPos) : nullptr;
3✔
1421
}
1422

1423
void GamePlayer::Surrender()
22✔
1424
{
1425
    if(isDefeated)
22✔
1426
        return;
1✔
1427

1428
    isDefeated = true;
21✔
1429

1430
    // GUI Bescheid sagen
1431
    if(world.GetGameInterface())
21✔
1432
        world.GetGameInterface()->GI_PlayerDefeated(GetPlayerId());
×
1433
}
1434

1435
void GamePlayer::SetStatisticValue(StatisticType type, unsigned value)
×
1436
{
1437
    statisticCurrentData[type] = value;
×
1438
}
×
1439

1440
void GamePlayer::ChangeStatisticValue(StatisticType type, int change)
1,534✔
1441
{
1442
    RTTR_Assert(change >= 0 || statisticCurrentData[type] >= static_cast<unsigned>(-change));
1,534✔
1443
    statisticCurrentData[type] += change;
1,534✔
1444
}
1,534✔
1445

1446
void GamePlayer::IncreaseMerchandiseStatistic(GoodType type)
4✔
1447
{
1448
    // Einsortieren...
1449
    switch(type)
4✔
1450
    {
1451
        case GoodType::Wood: statisticCurrentMerchandiseData[0]++; break;
×
1452
        case GoodType::Boards: statisticCurrentMerchandiseData[1]++; break;
×
1453
        case GoodType::Stones: statisticCurrentMerchandiseData[2]++; break;
×
1454
        case GoodType::Fish:
×
1455
        case GoodType::Bread:
1456
        case GoodType::Meat: statisticCurrentMerchandiseData[3]++; break;
×
1457
        case GoodType::Water: statisticCurrentMerchandiseData[4]++; break;
×
1458
        case GoodType::Beer: statisticCurrentMerchandiseData[5]++; break;
×
1459
        case GoodType::Coal: statisticCurrentMerchandiseData[6]++; break;
×
1460
        case GoodType::IronOre: statisticCurrentMerchandiseData[7]++; break;
×
1461
        case GoodType::Gold: statisticCurrentMerchandiseData[8]++; break;
×
1462
        case GoodType::Iron: statisticCurrentMerchandiseData[9]++; break;
×
1463
        case GoodType::Coins: statisticCurrentMerchandiseData[10]++; break;
×
1464
        case GoodType::Tongs:
3✔
1465
        case GoodType::Axe:
1466
        case GoodType::Saw:
1467
        case GoodType::PickAxe:
1468
        case GoodType::Hammer:
1469
        case GoodType::Shovel:
1470
        case GoodType::Crucible:
1471
        case GoodType::RodAndLine:
1472
        case GoodType::Scythe:
1473
        case GoodType::Cleaver:
1474
        case GoodType::Rollingpin:
1475
        case GoodType::Bow: statisticCurrentMerchandiseData[11]++; break;
3✔
1476
        case GoodType::ShieldVikings:
×
1477
        case GoodType::ShieldAfricans:
1478
        case GoodType::ShieldRomans:
1479
        case GoodType::ShieldJapanese:
1480
        case GoodType::Sword: statisticCurrentMerchandiseData[12]++; break;
×
1481
        case GoodType::Boat: statisticCurrentMerchandiseData[13]++; break;
×
1482
        default: break;
1✔
1483
    }
1484
}
4✔
1485

1486
/// Calculates current statistics
1487
void GamePlayer::CalcStatistics()
6✔
1488
{
1489
    // Waren aus der Inventur zählen
1490
    statisticCurrentData[StatisticType::Merchandise] = 0;
6✔
1491
    for(const auto i : helpers::enumRange<GoodType>())
468✔
1492
        statisticCurrentData[StatisticType::Merchandise] += global_inventory[i];
222✔
1493

1494
    // Bevölkerung aus der Inventur zählen
1495
    statisticCurrentData[StatisticType::Inhabitants] = 0;
6✔
1496
    for(const auto i : helpers::enumRange<Job>())
444✔
1497
        statisticCurrentData[StatisticType::Inhabitants] += global_inventory[i];
210✔
1498

1499
    // Militär aus der Inventur zählen
1500
    statisticCurrentData[StatisticType::Military] =
12✔
1501
      global_inventory.people[Job::Private] + global_inventory.people[Job::PrivateFirstClass] * 2
6✔
1502
      + global_inventory.people[Job::Sergeant] * 3 + global_inventory.people[Job::Officer] * 4
6✔
1503
      + global_inventory.people[Job::General] * 5;
6✔
1504

1505
    // Produktivität berechnen
1506
    statisticCurrentData[StatisticType::Productivity] = buildings.CalcAverageProductivity();
6✔
1507

1508
    // Total points for tournament games
1509
    statisticCurrentData[StatisticType::Tournament] =
12✔
1510
      statisticCurrentData[StatisticType::Military] + 3 * statisticCurrentData[StatisticType::Vanquished];
6✔
1511
}
6✔
1512

1513
void GamePlayer::StatisticStep()
6✔
1514
{
1515
    CalcStatistics();
6✔
1516

1517
    // 15-min-Statistik ein Feld weiterschieben
1518
    for(const auto i : helpers::enumRange<StatisticType>())
132✔
1519
    {
1520
        statistic[StatisticTime::T15Minutes].data[i][incrStatIndex(statistic[StatisticTime::T15Minutes].currentIndex)] =
54✔
1521
          statisticCurrentData[i];
54✔
1522
    }
1523
    for(unsigned i = 0; i < NUM_STAT_MERCHANDISE_TYPES; ++i)
90✔
1524
    {
1525
        statistic[StatisticTime::T15Minutes]
84✔
1526
          .merchandiseData[i][incrStatIndex(statistic[StatisticTime::T15Minutes].currentIndex)] =
168✔
1527
          statisticCurrentMerchandiseData[i];
84✔
1528
    }
1529
    statistic[StatisticTime::T15Minutes].currentIndex =
6✔
1530
      incrStatIndex(statistic[StatisticTime::T15Minutes].currentIndex);
6✔
1531

1532
    statistic[StatisticTime::T15Minutes].counter++;
6✔
1533

1534
    // Prüfen ob 4mal 15-min-Statistik weitergeschoben wurde, wenn ja: 1-h-Statistik weiterschieben
1535
    // und aktuellen Wert der 15min-Statistik benutzen
1536
    // gleiches für die 4h und 16h Statistik
1537
    for(const auto t : helpers::enumRange<StatisticTime>())
60✔
1538
    {
1539
        if(t == StatisticTime(helpers::MaxEnumValue_v<StatisticTime>))
24✔
1540
            break;
6✔
1541
        const auto nextT = StatisticTime(rttr::enum_cast(t) + 1);
18✔
1542
        if(statistic[t].counter == 4)
18✔
1543
        {
1544
            statistic[t].counter = 0;
×
1545
            for(const auto i : helpers::enumRange<StatisticType>())
×
1546
            {
1547
                statistic[nextT].data[i][incrStatIndex(statistic[nextT].currentIndex)] = statisticCurrentData[i];
×
1548
            }
1549

1550
            // Summe für den Zeitraum berechnen (immer 4 Zeitschritte der jeweils kleineren Statistik)
1551
            for(unsigned i = 0; i < NUM_STAT_MERCHANDISE_TYPES; ++i)
×
1552
            {
1553
                statistic[nextT].merchandiseData[i][incrStatIndex(statistic[nextT].currentIndex)] =
×
1554
                  statisticCurrentMerchandiseData[i]
×
1555
                  + statistic[t].merchandiseData[i][decrStatIndex(statistic[t].currentIndex, 1)]
×
1556
                  + statistic[t].merchandiseData[i][decrStatIndex(statistic[t].currentIndex, 2)]
×
1557
                  + statistic[t].merchandiseData[i][decrStatIndex(statistic[t].currentIndex, 3)];
×
1558
            }
1559

1560
            statistic[nextT].currentIndex = incrStatIndex(statistic[nextT].currentIndex);
×
1561
            statistic[nextT].counter++;
×
1562
        }
1563
    }
1564

1565
    // Warenstatistikzähler nullen
1566
    statisticCurrentMerchandiseData.fill(0);
6✔
1567
}
6✔
1568

1569
GamePlayer::Pact::Pact(SerializedGameData& sgd)
112✔
1570
    : duration(sgd.PopUnsignedInt()), start(sgd.PopUnsignedInt()), accepted(sgd.PopBool()), want_cancel(sgd.PopBool())
112✔
1571
{}
112✔
1572

1573
void GamePlayer::Pact::Serialize(SerializedGameData& sgd) const
224✔
1574
{
1575
    sgd.PushUnsignedInt(duration);
224✔
1576
    sgd.PushUnsignedInt(start);
224✔
1577
    sgd.PushBool(accepted);
224✔
1578
    sgd.PushBool(want_cancel);
224✔
1579
}
224✔
1580

1581
void GamePlayer::PactChanged(const PactType pt)
24✔
1582
{
1583
    // Recheck military flags as the border (to an enemy) might have changed
1584
    RecalcMilitaryFlags();
24✔
1585

1586
    // Ggf. den GUI Bescheid sagen, um Sichtbarkeiten etc. neu zu berechnen
1587
    if(pt == PactType::TreatyOfAlliance)
24✔
1588
    {
1589
        if(world.GetGameInterface())
6✔
1590
            world.GetGameInterface()->GI_TreatyOfAllianceChanged(GetPlayerId());
×
1591
    }
1592
}
24✔
1593

1594
void GamePlayer::SuggestPact(const unsigned char targetPlayerId, const PactType pt, const unsigned duration)
10✔
1595
{
1596
    // Don't try to make pact with self
1597
    if(targetPlayerId == GetPlayerId())
10✔
1598
        return;
1✔
1599

1600
    if(!pacts[targetPlayerId][pt].accepted && duration > 0)
9✔
1601
    {
1602
        pacts[targetPlayerId][pt].duration = duration;
8✔
1603
        pacts[targetPlayerId][pt].start = world.GetEvMgr().GetCurrentGF();
8✔
1604
        GamePlayer targetPlayer = world.GetPlayer(targetPlayerId);
16✔
1605
        if(targetPlayer.isHuman())
8✔
1606
            targetPlayer.SendPostMessage(std::make_unique<DiplomacyPostQuestion>(
6✔
1607
              world.GetEvMgr().GetCurrentGF(), pt, pacts[targetPlayerId][pt].start, *this, duration));
12✔
1608
        else if(world.HasLua())
2✔
1609
            world.GetLua().EventSuggestPact(pt, GetPlayerId(), targetPlayerId, duration);
2✔
1610
    }
1611
}
1612

1613
void GamePlayer::AcceptPact(const unsigned id, const PactType pt, const unsigned char targetPlayer)
21✔
1614
{
1615
    if(!pacts[targetPlayer][pt].accepted && pacts[targetPlayer][pt].duration > 0 && pacts[targetPlayer][pt].start == id)
21✔
1616
    {
1617
        MakePact(pt, targetPlayer, pacts[targetPlayer][pt].duration);
8✔
1618
        world.GetPlayer(targetPlayer).MakePact(pt, GetPlayerId(), pacts[targetPlayer][pt].duration);
8✔
1619
        PactChanged(pt);
8✔
1620
        world.GetPlayer(targetPlayer).PactChanged(pt);
8✔
1621
        if(world.HasLua())
8✔
1622
            world.GetLua().EventPactCreated(pt, GetPlayerId(), targetPlayer, pacts[targetPlayer][pt].duration);
3✔
1623
    }
1624
}
21✔
1625

1626
/// Bündnis (real, d.h. spielentscheidend) abschließen
1627
void GamePlayer::MakePact(const PactType pt, const unsigned char other_player, const unsigned duration)
16✔
1628
{
1629
    pacts[other_player][pt].accepted = true;
16✔
1630
    pacts[other_player][pt].start = world.GetEvMgr().GetCurrentGF();
16✔
1631
    pacts[other_player][pt].duration = duration;
16✔
1632
    pacts[other_player][pt].want_cancel = false;
16✔
1633

1634
    SendPostMessage(
16✔
1635
      std::make_unique<PostMsg>(world.GetEvMgr().GetCurrentGF(), pt, world.GetPlayer(other_player), true));
32✔
1636
}
16✔
1637

1638
/// Zeigt an, ob ein Pakt besteht
1639
PactState GamePlayer::GetPactState(const PactType pt, const unsigned char other_player) const
8,540✔
1640
{
1641
    // Prüfen, ob Bündnis in Kraft ist
1642
    if(pacts[other_player][pt].duration)
8,540✔
1643
    {
1644
        if(!pacts[other_player][pt].accepted)
4,686✔
1645
            return PactState::InProgress;
16✔
1646

1647
        if(pacts[other_player][pt].duration == DURATION_INFINITE
4,670✔
1648
           || world.GetEvMgr().GetCurrentGF() < pacts[other_player][pt].start + pacts[other_player][pt].duration)
4,670✔
1649
            return PactState::Accepted;
4,668✔
1650
    }
1651

1652
    return PactState::None;
3,856✔
1653
}
1654

1655
/// all allied players get a letter with the location
1656
void GamePlayer::NotifyAlliesOfLocation(const MapPoint pt)
6✔
1657
{
1658
    for(unsigned i = 0; i < world.GetNumPlayers(); ++i)
24✔
1659
    {
1660
        if(i != GetPlayerId() && IsAlly(i))
18✔
1661
            world.GetPlayer(i).SendPostMessage(std::make_unique<PostMsg>(
10✔
1662
              world.GetEvMgr().GetCurrentGF(), _("Your ally wishes to notify you of this location"),
5✔
1663
              PostCategory::Diplomacy, pt));
10✔
1664
    }
1665
}
6✔
1666

1667
/// Gibt die verbleibende Dauer zurück, die ein Bündnis noch laufen wird (DURATION_INFINITE = für immer)
1668
unsigned GamePlayer::GetRemainingPactTime(const PactType pt, const unsigned char other_player) const
125✔
1669
{
1670
    if(pacts[other_player][pt].duration)
125✔
1671
    {
1672
        if(pacts[other_player][pt].accepted)
88✔
1673
        {
1674
            if(pacts[other_player][pt].duration == DURATION_INFINITE)
76✔
1675
                return DURATION_INFINITE;
×
1676
            else if(world.GetEvMgr().GetCurrentGF() <= pacts[other_player][pt].start + pacts[other_player][pt].duration)
76✔
1677
                return ((pacts[other_player][pt].start + pacts[other_player][pt].duration)
76✔
1678
                        - world.GetEvMgr().GetCurrentGF());
76✔
1679
        }
1680
    }
1681

1682
    return 0;
49✔
1683
}
1684

1685
/// Gibt Einverständnis, dass dieser Spieler den Pakt auflösen will
1686
/// Falls dieser Spieler einen Bündnisvorschlag gemacht hat, wird dieser dagegen zurückgenommen
1687
void GamePlayer::CancelPact(const PactType pt, const unsigned char otherPlayerIdx)
8✔
1688
{
1689
    // Don't try to cancel pact with self
1690
    if(otherPlayerIdx == GetPlayerId())
8✔
1691
        return;
2✔
1692

1693
    // Besteht bereits ein Bündnis?
1694
    if(pacts[otherPlayerIdx][pt].accepted)
6✔
1695
    {
1696
        // Vermerken, dass der Spieler das Bündnis auflösen will
1697
        pacts[otherPlayerIdx][pt].want_cancel = true;
6✔
1698

1699
        // Will der andere Spieler das Bündnis auch auflösen?
1700
        GamePlayer& otherPlayer = world.GetPlayer(otherPlayerIdx);
6✔
1701
        if(otherPlayer.pacts[GetPlayerId()][pt].want_cancel)
6✔
1702
        {
1703
            // Dann wird das Bündnis aufgelöst
1704
            pacts[otherPlayerIdx][pt].accepted = false;
2✔
1705
            pacts[otherPlayerIdx][pt].duration = 0;
2✔
1706
            pacts[otherPlayerIdx][pt].want_cancel = false;
2✔
1707

1708
            otherPlayer.pacts[GetPlayerId()][pt].accepted = false;
2✔
1709
            otherPlayer.pacts[GetPlayerId()][pt].duration = 0;
2✔
1710
            otherPlayer.pacts[GetPlayerId()][pt].want_cancel = false;
2✔
1711

1712
            // Den Spielern eine Informationsnachricht schicken
1713
            world.GetPlayer(otherPlayerIdx)
2✔
1714
              .SendPostMessage(std::make_unique<PostMsg>(world.GetEvMgr().GetCurrentGF(), pt, *this, false));
2✔
1715
            SendPostMessage(
2✔
1716
              std::make_unique<PostMsg>(world.GetEvMgr().GetCurrentGF(), pt, world.GetPlayer(otherPlayerIdx), false));
4✔
1717
            PactChanged(pt);
2✔
1718
            otherPlayer.PactChanged(pt);
2✔
1719
            if(world.HasLua())
2✔
1720
                world.GetLua().EventPactCanceled(pt, GetPlayerId(), otherPlayerIdx);
1✔
1721
        } else
1722
        {
1723
            // Ansonsten den anderen Spieler fragen, ob der das auch so sieht
1724
            if(otherPlayer.isHuman())
4✔
1725
                otherPlayer.SendPostMessage(std::make_unique<DiplomacyPostQuestion>(
3✔
1726
                  world.GetEvMgr().GetCurrentGF(), pt, pacts[otherPlayerIdx][pt].start, *this));
6✔
1727
            else if(!world.HasLua() || world.GetLua().EventCancelPactRequest(pt, GetPlayerId(), otherPlayerIdx))
1✔
1728
            {
1729
                // AI accepts cancels, if there is no lua-interace
1730
                pacts[otherPlayerIdx][pt].accepted = false;
1✔
1731
                pacts[otherPlayerIdx][pt].duration = 0;
1✔
1732
                pacts[otherPlayerIdx][pt].want_cancel = false;
1✔
1733

1734
                otherPlayer.pacts[GetPlayerId()][pt].accepted = false;
1✔
1735
                otherPlayer.pacts[GetPlayerId()][pt].duration = 0;
1✔
1736
                otherPlayer.pacts[GetPlayerId()][pt].want_cancel = false;
1✔
1737

1738
                if(world.HasLua())
1✔
1739
                    world.GetLua().EventPactCanceled(pt, GetPlayerId(), otherPlayerIdx);
1✔
1740
            }
1741
        }
1742
    } else
1743
    {
1744
        // Es besteht kein Bündnis, also unseren Bündnisvorschlag wieder zurücknehmen
1745
        pacts[otherPlayerIdx][pt].duration = 0;
×
1746
    }
1747
}
1748

1749
void GamePlayer::MakeStartPacts()
57✔
1750
{
1751
    // Reset pacts
1752
    for(unsigned i = 0; i < world.GetNumPlayers(); ++i)
226✔
1753
    {
1754
        for(const auto z : helpers::enumRange<PactType>())
1,352✔
1755
            pacts[i][z] = Pact();
338✔
1756
    }
1757

1758
    // No team -> No pacts
1759
    if(team == Team::None)
57✔
1760
        return;
4✔
1761
    RTTR_Assert(isTeam(team));
53✔
1762

1763
    // Create ally- and non-aggression-pact for all players of same team
1764
    for(unsigned i = 0; i < world.GetNumPlayers(); ++i)
209✔
1765
    {
1766
        if(team != world.GetPlayer(i).team)
156✔
1767
            continue;
63✔
1768
        for(const auto z : helpers::enumRange<PactType>())
744✔
1769
        {
1770
            pacts[i][z].duration = DURATION_INFINITE;
186✔
1771
            pacts[i][z].start = 0;
186✔
1772
            pacts[i][z].accepted = true;
186✔
1773
            pacts[i][z].want_cancel = false;
186✔
1774
        }
1775
    }
1776
}
1777

1778
bool GamePlayer::IsWareRegistred(const Ware& ware)
126✔
1779
{
1780
    return helpers::contains(ware_list, &ware);
126✔
1781
}
1782

1783
bool GamePlayer::IsWareDependent(const Ware& ware)
14✔
1784
{
1785
    for(nobBaseWarehouse* wh : buildings.GetStorehouses())
41✔
1786
    {
1787
        if(wh->IsWareDependent(ware))
27✔
1788
            return true;
×
1789
    }
1790

1791
    return false;
14✔
1792
}
1793

1794
void GamePlayer::IncreaseInventoryWare(const GoodType ware, const unsigned count)
10,653✔
1795
{
1796
    global_inventory.Add(ConvertShields(ware), count);
10,653✔
1797
}
10,653✔
1798

1799
void GamePlayer::DecreaseInventoryWare(const GoodType ware, const unsigned count)
1,906✔
1800
{
1801
    global_inventory.Remove(ConvertShields(ware), count);
1,906✔
1802
}
1,906✔
1803

1804
/// Registriert ein Schiff beim Einwohnermeldeamt
1805
void GamePlayer::RegisterShip(noShip& ship)
20✔
1806
{
1807
    ships.push_back(&ship);
20✔
1808
    // Evtl bekommt das Schiffchen gleich was zu tun?
1809
    GetJobForShip(ship);
20✔
1810
}
20✔
1811

1812
struct ShipForHarbor
1813
{
1814
    noShip* ship;
1815
    uint32_t estimate;
1816

1817
    ShipForHarbor(noShip* ship, uint32_t estimate) : ship(ship), estimate(estimate) {}
17✔
1818

1819
    bool operator<(const ShipForHarbor& b) const
×
1820
    {
1821
        return (estimate < b.estimate) || (estimate == b.estimate && ship->GetObjId() < b.ship->GetObjId());
×
1822
    }
1823
};
1824

1825
/// Schiff für Hafen bestellen
1826
bool GamePlayer::OrderShip(nobHarborBuilding& hb)
31✔
1827
{
1828
    std::vector<ShipForHarbor> sfh;
62✔
1829

1830
    // we need more ships than those that are already on their way? limit search to idle ships
1831
    if(GetShipsToHarbor(hb) < hb.GetNumNeededShips())
31✔
1832
    {
1833
        for(noShip* ship : ships)
62✔
1834
        {
1835
            if(ship->IsIdling() && world.IsHarborAtSea(world.GetHarborPointID(hb.GetPos()), ship->GetSeaID()))
31✔
1836
                sfh.push_back(ShipForHarbor(ship, world.CalcDistance(hb.GetPos(), ship->GetPos())));
17✔
1837
        }
1838
    } else
1839
    {
1840
        for(noShip* ship : ships)
×
1841
        {
1842
            if((ship->IsIdling() && world.IsHarborAtSea(world.GetHarborPointID(hb.GetPos()), ship->GetSeaID()))
×
1843
               || ship->IsGoingToHarbor(hb))
×
1844
            {
1845
                sfh.push_back(ShipForHarbor(ship, world.CalcDistance(hb.GetPos(), ship->GetPos())));
×
1846
            }
1847
        }
1848
    }
1849

1850
    std::sort(sfh.begin(), sfh.end());
31✔
1851

1852
    noShip* best_ship = nullptr;
31✔
1853
    uint32_t best_distance = std::numeric_limits<uint32_t>::max();
31✔
1854
    std::vector<Direction> best_route;
62✔
1855

1856
    for(auto& it : sfh)
39✔
1857
    {
1858
        uint32_t distance;
1859
        std::vector<Direction> route;
17✔
1860

1861
        // the estimate (air-line distance) for this and all other ships in the list is already worse than what we
1862
        // found? disregard the rest
1863
        if(it.estimate >= best_distance)
17✔
1864
            break;
×
1865

1866
        noShip& ship = *it.ship;
17✔
1867

1868
        MapPoint dest = world.GetCoastalPoint(hb.GetHarborPosID(), ship.GetSeaID());
17✔
1869

1870
        // ship already there?
1871
        if(ship.GetPos() == dest)
17✔
1872
        {
1873
            hb.ShipArrived(ship);
9✔
1874
            return (true);
9✔
1875
        }
1876

1877
        if(world.FindShipPathToHarbor(ship.GetPos(), hb.GetHarborPosID(), ship.GetSeaID(), &route, &distance))
8✔
1878
        {
1879
            if(distance < best_distance)
8✔
1880
            {
1881
                best_ship = &ship;
8✔
1882
                best_distance = distance;
8✔
1883
                best_route = route;
8✔
1884
            }
1885
        }
1886
    }
1887

1888
    // only order ships not already on their way
1889
    if(best_ship && best_ship->IsIdling())
22✔
1890
    {
1891
        best_ship->GoToHarbor(hb, best_route);
8✔
1892

1893
        return (true);
8✔
1894
    }
1895

1896
    return (false);
14✔
1897
}
1898

1899
/// Meldet das Schiff wieder ab
1900
void GamePlayer::RemoveShip(noShip* ship)
×
1901
{
1902
    for(unsigned i = 0; i < ships.size(); ++i)
×
1903
    {
1904
        if(ships[i] == ship)
×
1905
        {
1906
            ships.erase(ships.begin() + i);
×
1907
            return;
×
1908
        }
1909
    }
1910
}
1911

1912
/// Versucht, für ein untätiges Schiff eine Arbeit zu suchen
1913
void GamePlayer::GetJobForShip(noShip& ship)
30✔
1914
{
1915
    // Evtl. steht irgendwo eine Expedition an und das Schiff kann diese übernehmen
1916
    nobHarborBuilding* best = nullptr;
30✔
1917
    int best_points = 0;
30✔
1918
    std::vector<Direction> best_route;
30✔
1919

1920
    // Beste Weglänge, die ein Schiff zurücklegen muss, welches gerade nichts zu tun hat
1921
    for(nobHarborBuilding* harbor : buildings.GetHarbors())
61✔
1922
    {
1923
        // Braucht der Hafen noch Schiffe?
1924
        if(harbor->GetNumNeededShips() == 0)
31✔
1925
            continue;
31✔
1926

1927
        // Anzahl der Schiffe ermitteln, die diesen Hafen bereits anfahren
1928
        unsigned ships_coming = GetShipsToHarbor(*harbor);
×
1929

1930
        // Evtl. kommen schon genug?
1931
        if(harbor->GetNumNeededShips() <= ships_coming)
×
1932
            continue;
×
1933

1934
        // liegen wir am gleichen Meer?
1935
        if(world.IsHarborAtSea(harbor->GetHarborPosID(), ship.GetSeaID()))
×
1936
        {
1937
            const MapPoint coastPt = world.GetCoastalPoint(harbor->GetHarborPosID(), ship.GetSeaID());
×
1938

1939
            // Evtl. sind wir schon da?
1940
            if(ship.GetPos() == coastPt)
×
1941
            {
1942
                harbor->ShipArrived(ship);
×
1943
                return;
×
1944
            }
1945

1946
            unsigned length;
1947
            std::vector<Direction> route;
×
1948

1949
            if(world.FindShipPathToHarbor(ship.GetPos(), harbor->GetHarborPosID(), ship.GetSeaID(), &route, &length))
×
1950
            {
1951
                // Punkte ausrechnen
1952
                int points = harbor->GetNeedForShip(ships_coming) - length;
×
1953
                if(points > best_points || !best)
×
1954
                {
1955
                    best = harbor;
×
1956
                    best_points = points;
×
1957
                    best_route = route;
×
1958
                }
1959
            }
1960
        }
1961
    }
1962

1963
    // Einen Hafen gefunden?
1964
    if(best)
30✔
1965
        // Dann bekommt das gleich der Hafen
1966
        ship.GoToHarbor(*best, best_route);
×
1967
}
1968

1969
/// Gibt die ID eines Schiffes zurück
1970
unsigned GamePlayer::GetShipID(const noShip* const ship) const
13✔
1971
{
1972
    for(unsigned i = 0; i < ships.size(); ++i)
13✔
1973
        if(ships[i] == ship)
13✔
1974
            return i;
13✔
1975

1976
    return 0xFFFFFFFF;
×
1977
}
1978

1979
/// Gibt ein Schiff anhand der ID zurück bzw. nullptr, wenn keines mit der ID existiert
1980
noShip* GamePlayer::GetShipByID(const unsigned ship_id) const
20✔
1981
{
1982
    if(ship_id >= ships.size())
20✔
1983
        return nullptr;
×
1984
    else
1985
        return ships[ship_id];
20✔
1986
}
1987

1988
/// Gibt eine Liste mit allen Häfen dieses Spieler zurück, die an ein bestimmtes Meer angrenzen
1989
void GamePlayer::GetHarborsAtSea(std::vector<nobHarborBuilding*>& harbor_buildings, const unsigned short seaId) const
182✔
1990
{
1991
    for(nobHarborBuilding* harbor : buildings.GetHarbors())
558✔
1992
    {
1993
        if(helpers::contains(harbor_buildings, harbor))
376✔
1994
            continue;
182✔
1995

1996
        if(world.IsHarborAtSea(harbor->GetHarborPosID(), seaId))
194✔
1997
            harbor_buildings.push_back(harbor);
194✔
1998
    }
1999
}
182✔
2000

2001
/// Gibt die Anzahl der Schiffe, die einen bestimmten Hafen ansteuern, zurück
2002
unsigned GamePlayer::GetShipsToHarbor(const nobHarborBuilding& hb) const
68✔
2003
{
2004
    unsigned count = 0;
68✔
2005
    for(const auto* ship : ships)
136✔
2006
    {
2007
        if(ship->IsGoingToHarbor(hb))
68✔
2008
            ++count;
4✔
2009
    }
2010

2011
    return count;
68✔
2012
}
2013

2014
/// Sucht einen Hafen in der Nähe, wo dieses Schiff seine Waren abladen kann
2015
/// gibt true zurück, falls erfolgreich
2016
bool GamePlayer::FindHarborForUnloading(noShip* ship, const MapPoint start, unsigned* goal_harborId,
5✔
2017
                                        std::vector<Direction>* route, nobHarborBuilding* exception)
2018
{
2019
    nobHarborBuilding* best = nullptr;
5✔
2020
    unsigned best_distance = 0xffffffff;
5✔
2021

2022
    for(nobHarborBuilding* hb : buildings.GetHarbors())
8✔
2023
    {
2024
        // Bestimmten Hafen ausschließen
2025
        if(hb == exception)
3✔
2026
            continue;
×
2027

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

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

2035
        // Kürzerer Weg als bisher bestes Ziel?
2036
        if(distance < best_distance)
3✔
2037
        {
2038
            best_distance = distance;
3✔
2039
            best = hb;
3✔
2040
        }
2041
    }
2042

2043
    // Hafen gefunden?
2044
    if(best)
5✔
2045
    {
2046
        // Weg dorthin suchen
2047
        route->clear();
3✔
2048
        *goal_harborId = best->GetHarborPosID();
3✔
2049
        const MapPoint coastPt = world.GetCoastalPoint(best->GetHarborPosID(), ship->GetSeaID());
3✔
2050
        if(start == coastPt
3✔
2051
           || world.FindShipPathToHarbor(start, best->GetHarborPosID(), ship->GetSeaID(), route, nullptr))
3✔
2052
            return true;
3✔
2053
    }
2054

2055
    return false;
2✔
2056
}
2057

2058
void GamePlayer::TestForEmergencyProgramm()
×
2059
{
2060
    // we are already defeated, do not even think about an emergency program - it's too late :-(
2061
    if(isDefeated)
×
2062
        return;
×
2063

2064
    // In Lagern vorhandene Bretter und Steine zählen
2065
    unsigned boards = 0;
×
2066
    unsigned stones = 0;
×
2067
    for(nobBaseWarehouse* wh : buildings.GetStorehouses())
×
2068
    {
2069
        boards += wh->GetInventory().goods[GoodType::Boards];
×
2070
        stones += wh->GetInventory().goods[GoodType::Stones];
×
2071
    }
2072

2073
    // Emergency happens, if we have less than 10 boards or stones...
2074
    bool isNewEmergency = boards <= 10 || stones <= 10;
×
2075
    // ...and no woddcutter or sawmill
2076
    isNewEmergency &=
×
2077
      buildings.GetBuildings(BuildingType::Woodcutter).empty() || buildings.GetBuildings(BuildingType::Sawmill).empty();
×
2078

2079
    // Wenn nötig, Notfallprogramm auslösen
2080
    if(isNewEmergency)
×
2081
    {
2082
        if(!emergency)
×
2083
        {
2084
            emergency = true;
×
2085
            SendPostMessage(std::make_unique<PostMsg>(
×
2086
              world.GetEvMgr().GetCurrentGF(), _("The emergency program has been activated."), PostCategory::Economy));
×
2087
        }
2088
    } else
2089
    {
2090
        // Sobald Notfall vorbei, Notfallprogramm beenden, evtl. Baustellen wieder mit Kram versorgen
2091
        if(emergency)
×
2092
        {
2093
            emergency = false;
×
2094
            SendPostMessage(std::make_unique<PostMsg>(world.GetEvMgr().GetCurrentGF(),
×
2095
                                                      _("The emergency program has been deactivated."),
×
2096
                                                      PostCategory::Economy));
×
2097
            FindMaterialForBuildingSites();
×
2098
        }
2099
    }
2100
}
2101

2102
/// Testet die Bündnisse, ob sie nicht schon abgelaufen sind
2103
void GamePlayer::TestPacts()
20✔
2104
{
2105
    for(unsigned i = 0; i < world.GetNumPlayers(); ++i)
80✔
2106
    {
2107
        if(i == GetPlayerId())
60✔
2108
            continue;
20✔
2109

2110
        for(const auto pact : helpers::enumRange<PactType>())
320✔
2111
        {
2112
            // Pact not running
2113
            if(pacts[i][pact].duration == 0)
80✔
2114
                continue;
60✔
2115
            if(GetPactState(pact, i) == PactState::None)
20✔
2116
            {
2117
                // Pact was running but is expired -> Cancel for both players
2118
                pacts[i][pact].duration = 0;
2✔
2119
                pacts[i][pact].accepted = false;
2✔
2120
                GamePlayer& otherPlayer = world.GetPlayer(i);
2✔
2121
                RTTR_Assert(otherPlayer.pacts[GetPlayerId()][pact].duration);
2✔
2122
                RTTR_Assert(otherPlayer.pacts[GetPlayerId()][pact].accepted);
2✔
2123
                otherPlayer.pacts[GetPlayerId()][pact].duration = 0;
2✔
2124
                otherPlayer.pacts[GetPlayerId()][pact].accepted = false;
2✔
2125
                // And notify
2126
                PactChanged(pact);
2✔
2127
                otherPlayer.PactChanged(pact);
2✔
2128
            }
2129
        }
2130
    }
2131
}
20✔
2132

2133
bool GamePlayer::CanBuildCatapult() const
1✔
2134
{
2135
    // Wenn AddonId::LIMIT_CATAPULTS nicht aktiv ist, bauen immer erlaubt
2136
    if(!world.GetGGS().isEnabled(AddonId::LIMIT_CATAPULTS)) //-V807
1✔
2137
        return true;
1✔
2138

2139
    BuildingCount bc = buildings.GetBuildingNums();
×
2140

2141
    unsigned max = 0;
×
2142
    // proportional?
2143
    if(world.GetGGS().getSelection(AddonId::LIMIT_CATAPULTS) == 1)
×
2144
    {
2145
        max = int(bc.buildings[BuildingType::Barracks] * 0.125 + bc.buildings[BuildingType::Guardhouse] * 0.25
×
2146
                  + bc.buildings[BuildingType::Watchtower] * 0.5 + bc.buildings[BuildingType::Fortress]
×
2147
                  + 0.111); // to avoid rounding errors
×
2148
    } else if(world.GetGGS().getSelection(AddonId::LIMIT_CATAPULTS) < 8)
×
2149
    {
2150
        const std::array<unsigned, 6> limits = {{0, 3, 5, 10, 20, 30}};
×
2151
        max = limits[world.GetGGS().getSelection(AddonId::LIMIT_CATAPULTS) - 2];
×
2152
    }
2153

2154
    return bc.buildings[BuildingType::Catapult] + bc.buildingSites[BuildingType::Catapult] < max;
×
2155
}
2156

2157
/// A ship has discovered new hostile territory --> determines if this is new
2158
/// i.e. there is a sufficient distance to older locations
2159
/// Returns true if yes and false if not
2160
bool GamePlayer::ShipDiscoveredHostileTerritory(const MapPoint location)
25✔
2161
{
2162
    // Prüfen, ob Abstand zu bisherigen Punkten nicht zu klein
2163
    for(const auto& enemies_discovered_by_ship : enemies_discovered_by_ships)
25✔
2164
    {
2165
        if(world.CalcDistance(enemies_discovered_by_ship, location) < 30)
24✔
2166
            return false;
24✔
2167
    }
2168

2169
    // Nein? Dann haben wir ein neues Territorium gefunden
2170
    enemies_discovered_by_ships.push_back(location);
1✔
2171

2172
    return true;
1✔
2173
}
2174

2175
/// For debug only
2176
bool GamePlayer::IsDependentFigure(const noFigure& fig)
153✔
2177
{
2178
    for(const nobBaseWarehouse* wh : buildings.GetStorehouses())
342✔
2179
    {
2180
        if(wh->IsDependentFigure(fig))
189✔
2181
            return true;
×
2182
    }
2183
    return false;
153✔
2184
}
2185

2186
std::vector<nobBaseWarehouse*> GamePlayer::GetWarehousesForTrading(const nobBaseWarehouse& goalWh) const
18✔
2187
{
2188
    std::vector<nobBaseWarehouse*> result;
18✔
2189

2190
    // Don't try to trade with us!
2191
    if(goalWh.GetPlayer() == GetPlayerId())
18✔
2192
        return result;
6✔
2193

2194
    const MapPoint goalFlagPos = goalWh.GetFlagPos();
12✔
2195

2196
    TradePathCache& tradePathCache = world.GetTradePathCache();
12✔
2197
    for(nobBaseWarehouse* wh : buildings.GetStorehouses())
24✔
2198
    {
2199
        // Is there a trade path from this warehouse to wh? (flag to flag)
2200
        if(tradePathCache.pathExists(wh->GetFlagPos(), goalFlagPos, GetPlayerId()))
12✔
2201
            result.push_back(wh);
4✔
2202
    }
2203

2204
    return result;
12✔
2205
}
2206

2207
struct WarehouseDistanceComparator
2208
{
2209
    // Reference warehouse position, to which we want to calc the distance
2210
    const MapPoint refWareHousePos_;
2211
    /// GameWorld
2212
    const GameWorld& gwg_;
2213

2214
    WarehouseDistanceComparator(const nobBaseWarehouse& refWareHouse, const GameWorld& world)
12✔
2215
        : refWareHousePos_(refWareHouse.GetPos()), gwg_(world)
12✔
2216
    {}
12✔
2217

2218
    bool operator()(nobBaseWarehouse* const wh1, nobBaseWarehouse* const wh2) const
×
2219
    {
2220
        unsigned dist1 = gwg_.CalcDistance(wh1->GetPos(), refWareHousePos_);
×
2221
        unsigned dist2 = gwg_.CalcDistance(wh2->GetPos(), refWareHousePos_);
×
2222
        return (dist1 < dist2) || (dist1 == dist2 && wh1->GetObjId() < wh2->GetObjId());
×
2223
    }
2224
};
2225

2226
/// Send wares to warehouse wh
2227
void GamePlayer::Trade(nobBaseWarehouse* goalWh, const boost_variant2<GoodType, Job>& what, unsigned count) const
18✔
2228
{
2229
    if(!world.GetGGS().isEnabled(AddonId::TRADE))
18✔
2230
        return;
6✔
2231

2232
    if(count == 0)
16✔
2233
        return;
2✔
2234

2235
    // Don't try to trade with us!
2236
    if(goalWh->GetPlayer() == GetPlayerId())
14✔
2237
        return;
1✔
2238

2239
    // No trades with enemies
2240
    if(!IsAlly(goalWh->GetPlayer()))
13✔
2241
        return;
1✔
2242

2243
    const MapPoint goalFlagPos = goalWh->GetFlagPos();
12✔
2244

2245
    std::vector<nobBaseWarehouse*> whs(buildings.GetStorehouses().begin(), buildings.GetStorehouses().end());
12✔
2246
    std::sort(whs.begin(), whs.end(), WarehouseDistanceComparator(*goalWh, world));
12✔
2247
    TradePathCache& tradePathCache = world.GetTradePathCache();
12✔
2248
    for(nobBaseWarehouse* wh : whs)
24✔
2249
    {
2250
        // Get available wares
2251
        const unsigned available =
2252
          boost::variant2::visit(composeVisitor([wh](GoodType gt) { return wh->GetAvailableWaresForTrading(gt); },
19✔
2253
                                                [wh](Job job) { return wh->GetAvailableFiguresForTrading(job); }),
17✔
2254
                                 what);
12✔
2255
        if(available == 0)
12✔
2256
            continue;
×
2257

2258
        const unsigned actualCount = std::min(available, count);
12✔
2259

2260
        // Find a trade path from flag to flag
2261
        TradeRoute tr(world, GetPlayerId(), wh->GetFlagPos(), goalFlagPos);
12✔
2262

2263
        // Found a path?
2264
        if(tr.IsValid())
12✔
2265
        {
2266
            // Add to cache for future searches
2267
            tradePathCache.addEntry(tr.GetTradePath(), GetPlayerId());
11✔
2268

2269
            wh->StartTradeCaravane(what, actualCount, tr, goalWh);
11✔
2270
            count -= available;
11✔
2271
            if(count == 0)
11✔
2272
                return;
×
2273
        }
2274
    }
2275
}
2276

2277
void GamePlayer::FillVisualSettings(VisualSettings& visualSettings) const
2✔
2278
{
2279
    Distributions& visDistribution = visualSettings.distribution;
2✔
2280
    unsigned visIdx = 0;
2✔
2281
    for(const DistributionMapping& mapping : distributionMap)
54✔
2282
    {
2283
        visDistribution[visIdx++] = distribution[std::get<0>(mapping)].percent_buildings[std::get<1>(mapping)];
52✔
2284
    }
2285

2286
    visualSettings.useCustomBuildOrder = useCustomBuildOrder_;
2✔
2287
    visualSettings.build_order = build_order;
2✔
2288

2289
    visualSettings.transport_order = GetOrderingFromTransportPrio(transportPrio);
2✔
2290

2291
    visualSettings.military_settings = militarySettings_;
2✔
2292
    visualSettings.tools_settings = toolsSettings_;
2✔
2293
}
2✔
2294

2295
#define INSTANTIATE_FINDWH(Cond)                                                                                \
2296
    template nobBaseWarehouse* GamePlayer::FindWarehouse(const noRoadNode&, const Cond&, bool, bool, unsigned*, \
2297
                                                         const RoadSegment*) const
2298

2299
INSTANTIATE_FINDWH(FW::HasMinWares);
2300
INSTANTIATE_FINDWH(FW::HasFigure);
2301
INSTANTIATE_FINDWH(FW::HasWareAndFigure);
2302
INSTANTIATE_FINDWH(FW::HasAnyMatchingSoldier);
2303
INSTANTIATE_FINDWH(FW::AcceptsWare);
2304
INSTANTIATE_FINDWH(FW::AcceptsFigure);
2305
INSTANTIATE_FINDWH(FW::CollectsWare);
2306
INSTANTIATE_FINDWH(FW::CollectsFigure);
2307
INSTANTIATE_FINDWH(FW::HasWareButNoCollect);
2308
INSTANTIATE_FINDWH(FW::HasFigureButNoCollect);
2309
INSTANTIATE_FINDWH(FW::AcceptsFigureButNoSend);
2310
INSTANTIATE_FINDWH(FW::NoCondition);
2311

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

© 2026 Coveralls, Inc