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

Return-To-The-Roots / s25client / 21283273387

23 Jan 2026 10:39AM UTC coverage: 50.757% (+0.09%) from 50.663%
21283273387

Pull #1679

github

web-flow
Merge c392aa0ea into 12da8bf44
Pull Request #1679: Add all classic S2 cheats and a few more

94 of 155 new or added lines in 12 files covered. (60.65%)

7 existing lines in 5 files now uncovered.

22798 of 44916 relevant lines covered (50.76%)

41539.59 hits per line

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

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

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

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

63
    // Inventur nullen
64
    global_inventory.clear();
375 ✔
65

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

71
    RecalcDistribution();
375 ✔
72
}
375 ✔
73

74
void GamePlayer::LoadStandardToolSettings()
375 ✔
75
{
76
    // metalwork tool request
77

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

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

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

110
BuildOrders GamePlayer::GetStandardBuildOrder()
377 ✔
111
{
112
    BuildOrders ordering;
113

114
    // Baureihenfolge füllen
115
    unsigned curPrio = 0;
377 ✔
116
    for(const auto bld : helpers::enumRange<BuildingType>())
31,668 ✔
117
    {
118
        if(bld == BuildingType::Headquarters || !BuildingProperties::IsValid(bld))
15,080 ✔
119
            continue;
1,885 ✔
120

121
        RTTR_Assert(curPrio < ordering.size());
13,195 ✔
122
        ordering[curPrio] = bld;
13,195 ✔
123
        ++curPrio;
13,195 ✔
124
    }
125
    RTTR_Assert(curPrio == ordering.size());
377 ✔
126
    return ordering;
377 ✔
127
}
128

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

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

149
    // Standardverteilung der Waren
150
    for(const DistributionMapping& mapping : distributionMap)
10,125 ✔
151
    {
152
        distribution[std::get<0>(mapping)].percent_buildings[std::get<1>(mapping)] = std::get<2>(mapping);
9,750 ✔
153
    }
154
}
375 ✔
155

156
GamePlayer::~GamePlayer() = default;
758 ✔
157

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

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

167
    sgd.PushBool(isDefeated);
14 ✔
168

169
    buildings.Serialize(sgd);
14 ✔
170

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

260
    sgd.PopContainer(shouldSendDefenderList);
7 ✔
261

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

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

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

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

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

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

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

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

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

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

327
    // Visuelle Einstellungen festlegen
328

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

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

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

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

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

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

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

365
    for(nobBaseWarehouse* wh : buildings.GetStorehouses())
3,506 ✔
366
    {
367
        // Lagerhaus geeignet?
368
        RTTR_Assert(wh);
1,987 ✔
369
        if(!isWarehouseGood(*wh))
1,987 ✔
370
            continue;
1,144 ✔
371

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

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

398
    if(length)
1,519 ✔
399
        *length = best_length;
374 ✔
400

401
    return best;
1,519 ✔
402
}
403

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

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

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

423
void GamePlayer::SetHQIsTent(bool isTent)
6 ✔
424
{
425
    if(nobHQ* hq = GetHQ())
6 ✔
426
        hq->SetIsTent(isTent);
5 ✔
427
}
6 ✔
428

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

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

442
    if(bldType == BuildingType::HarborBuilding)
582 ✔
443
    {
444
        // Schiff durchgehen und denen Bescheid sagen
445
        for(noShip* ship : ships)
51 ✔
446
            ship->NewHarborBuilt(static_cast<nobHarborBuilding*>(bld));
11 ✔
447
    } else if(bldType == BuildingType::Headquarters)
542 ✔
448
    {
449
        // If there is more than one HQ, keep the original position.
450
        if(!hqPos.isValid())
330 ✔
451
            hqPos = bld->GetPos();
321 ✔
452
    } else if(BuildingProperties::IsMilitary(bldType))
212 ✔
453
    {
454
        auto* milBld = static_cast<nobMilitary*>(bld);
101 ✔
455
        // New built? -> Calculate frontier distance
456
        if(milBld->IsNewBuilt())
101 ✔
457
            milBld->LookForEnemyBuildings();
95 ✔
458
    }
459
}
582 ✔
460

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

486
void GamePlayer::NewRoadConnection(RoadSegment* rs)
156 ✔
487
{
488
    // Zu den Straßen hinzufgen, da's ja ne neue ist
489
    roads.push_back(rs);
156 ✔
490

491
    // Alle Straßen müssen nun gucken, ob sie einen Weg zu einem Warehouse finden
492
    FindCarrierForAllRoads();
156 ✔
493

494
    // Alle Straßen müssen gucken, ob sie einen Esel bekommen können
495
    for(RoadSegment* rs : roads)
339 ✔
496
        rs->TryGetDonkey();
183 ✔
497

498
    // Alle Arbeitsplätze müssen nun gucken, ob sie einen Weg zu einem Lagerhaus mit entsprechender Arbeitskraft finden
499
    FindWarehouseForAllJobs();
156 ✔
500

501
    // Alle Baustellen müssen nun gucken, ob sie ihr benötigtes Baumaterial bekommen (evtl war vorher die Straße zum
502
    // Lagerhaus unterbrochen
503
    FindMaterialForBuildingSites();
156 ✔
504

505
    // Alle Lost-Wares müssen gucken, ob sie ein Lagerhaus finden
506
    FindClientForLostWares();
156 ✔
507

508
    // Alle Militärgebäude müssen ihre Truppen überprüfen und können nun ggf. neue bestellen
509
    // und müssen prüfen, ob sie evtl Gold bekommen
510
    for(nobMilitary* mil : buildings.GetMilitaryBuildings())
175 ✔
511
    {
512
        mil->RegulateTroops();
19 ✔
513
        mil->SearchCoins();
19 ✔
514
    }
515
}
156 ✔
516

517
void GamePlayer::AddRoad(RoadSegment* rs)
18 ✔
518
{
519
    roads.push_back(rs);
18 ✔
520
}
18 ✔
521

522
void GamePlayer::DeleteRoad(RoadSegment* rs)
103 ✔
523
{
524
    RTTR_Assert(helpers::contains(roads, rs));
103 ✔
525
    roads.remove(rs);
103 ✔
526
}
103 ✔
527

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

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

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

613
        ++it;
10 ✔
614
    }
615

616
    // Alle Häfen müssen ihre Figuren den Weg überprüfen lassen
617
    for(nobHarborBuilding* hb : buildings.GetHarbors())
237 ✔
618
    {
619
        hb->ExamineShipRouteOfPeople();
17 ✔
620
    }
621
}
220 ✔
622

623
bool GamePlayer::FindCarrierForRoad(RoadSegment* rs) const
184 ✔
624
{
625
    RTTR_Assert(rs->GetF1() != nullptr && rs->GetF2() != nullptr);
184 ✔
626
    std::array<unsigned, 2> length;
627
    std::array<nobBaseWarehouse*, 2> best;
628

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

646
    // überhaupt nen Weg gefunden?
647
    // Welche Flagge benutzen?
648
    if(best[0] && (!best[1] || length[0] < length[1]))
184 ✔
649
        best[0]->OrderCarrier(*rs->GetF1(), *rs);
64 ✔
650
    else if(best[1])
120 ✔
651
        best[1]->OrderCarrier(*rs->GetF2(), *rs);
42 ✔
652
    else
653
        return false;
78 ✔
654
    return true;
106 ✔
655
}
656

657
bool GamePlayer::IsWarehouseValid(nobBaseWarehouse* wh) const
×
658
{
659
    return helpers::contains(buildings.GetStorehouses(), wh);
×
660
}
661

662
void GamePlayer::RecalcDistribution()
381 ✔
663
{
664
    GoodType lastWare = GoodType::Nothing;
381 ✔
665
    for(const DistributionMapping& mapping : distributionMap)
10,287 ✔
666
    {
667
        if(lastWare == std::get<0>(mapping))
9,906 ✔
668
            continue;
7,239 ✔
669
        lastWare = std::get<0>(mapping);
2,667 ✔
670
        RecalcDistributionOfWare(std::get<0>(mapping));
2,667 ✔
671
    }
672
}
381 ✔
673

674
void GamePlayer::RecalcDistributionOfWare(const GoodType ware)
2,667 ✔
675
{
676
    // Punktesystem zur Verteilung, in der Liste alle Gebäude sammeln, die die Ware wollen
677
    distribution[ware].client_buildings.clear();
2,667 ✔
678

679
    // 1. Anteile der einzelnen Waren ausrechnen
680

681
    /// Mapping of buildings that want the current ware to its percentage
682
    using BldEntry = std::pair<BuildingType, uint8_t>;
683
    std::vector<BldEntry> bldPercentageMap;
2,667 ✔
684

685
    unsigned goal_count = 0;
2,667 ✔
686

687
    for(const auto bld : helpers::enumRange<BuildingType>())
224,028 ✔
688
    {
689
        uint8_t percentForCurBld = distribution[ware].percent_buildings[bld];
106,680 ✔
690
        if(percentForCurBld)
106,680 ✔
691
        {
692
            distribution[ware].client_buildings.push_back(bld);
9,906 ✔
693
            goal_count += percentForCurBld;
9,906 ✔
694
            bldPercentageMap.emplace_back(bld, percentForCurBld);
9,906 ✔
695
        }
696
    }
697

698
    // TODO: evtl noch die counts miteinander kürzen (ggt berechnen)
699

700
    // Array für die Gebäudtypen erstellen
701

702
    std::vector<BuildingType>& wareGoals = distribution[ware].goals;
2,667 ✔
703
    wareGoals.clear();
2,667 ✔
704
    wareGoals.reserve(goal_count);
2,667 ✔
705

706
    // just drop them in the list, the distribution will be handled by going through this list using a prime as step
707
    // (see GameClientPlayer::FindClientForWare)
708
    for(const BldEntry& bldEntry : bldPercentageMap)
12,573 ✔
709
    {
710
        for(unsigned char i = 0; i < bldEntry.second; ++i)
66,414 ✔
711
            wareGoals.push_back(bldEntry.first);
56,508 ✔
712
    }
713

714
    distribution[ware].selected_goal = 0;
2,667 ✔
715
}
2,667 ✔
716

717
void GamePlayer::FindCarrierForAllRoads()
176 ✔
718
{
719
    for(RoadSegment* rs : roads)
388 ✔
720
    {
721
        if(!rs->hasCarrier(0))
212 ✔
722
            FindCarrierForRoad(rs);
162 ✔
723
    }
724
}
176 ✔
725

726
void GamePlayer::FindMaterialForBuildingSites()
171 ✔
727
{
728
    for(noBuildingSite* bldSite : buildings.GetBuildingSites())
194 ✔
729
        bldSite->OrderConstructionMaterial();
23 ✔
730
}
171 ✔
731

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

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

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

766
void GamePlayer::SendPostMessage(std::unique_ptr<PostMsg> msg)
35 ✔
767
{
768
    world.GetPostMgr().SendMsg(GetPlayerId(), std::move(msg));
35 ✔
769
}
35 ✔
770

771
unsigned GamePlayer::GetToolsOrderedVisual(Tool tool) const
×
772
{
773
    return std::max(0, int(tools_ordered[tool] + tools_ordered_delta[tool]));
×
774
}
775

776
unsigned GamePlayer::GetToolsOrdered(Tool tool) const
369 ✔
777
{
778
    return tools_ordered[tool];
369 ✔
779
}
780

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

792
unsigned GamePlayer::GetToolPriority(Tool tool) const
271 ✔
793
{
794
    return toolsSettings_[tool];
271 ✔
795
}
796

797
void GamePlayer::ToolOrderProcessed(Tool tool)
4 ✔
798
{
799
    if(tools_ordered[tool])
4 ✔
800
    {
801
        --tools_ordered[tool];
4 ✔
802
        world.GetNotifications().publish(ToolNote(ToolNote::OrderCompleted, GetPlayerId()));
4 ✔
803
    }
804
}
4 ✔
805

806
bool GamePlayer::FindWarehouseForJob(const Job job, noRoadNode* goal) const
157 ✔
807
{
808
    nobBaseWarehouse* wh = FindWarehouse(*goal, FW::HasFigure(job, true), false, false);
157 ✔
809

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

817
    return false;
125 ✔
818
}
819

820
void GamePlayer::FindWarehouseForAllJobs()
175 ✔
821
{
822
    for(auto it = jobs_wanted.begin(); it != jobs_wanted.end();)
194 ✔
823
    {
824
        if(FindWarehouseForJob(it->job, it->workplace))
19 ✔
825
            it = jobs_wanted.erase(it);
19 ✔
826
        else
827
            ++it;
×
828
    }
829
}
175 ✔
830

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

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

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

892
nofCarrier* GamePlayer::OrderDonkey(RoadSegment* road) const
3 ✔
893
{
894
    std::array<unsigned, 2> length;
895
    std::array<nobBaseWarehouse*, 2> best;
896

897
    // 1. Flagge des Weges
898
    best[0] = FindWarehouse(*road->GetF1(), FW::HasFigure(Job::PackDonkey, false), false, false, length.data(), road);
3 ✔
899
    // 2. Flagge des Weges
900
    best[1] = FindWarehouse(*road->GetF2(), FW::HasFigure(Job::PackDonkey, false), false, false, &length[1], road);
3 ✔
901

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

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

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

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

945
            // Kein Weg führt hin, nächste Straße bitte
946
            if(!current_best_goal)
×
947
                continue;
×
948

949
            // Jeweiligen Weg bestimmen
950
            unsigned current_best_way = (roadSeg->GetF1() == current_best_goal) ? length1 : length2;
×
951

952
            // Produktivität ausrechnen, *10 die Produktivität + die Wegstrecke, damit die
953
            // auch noch mit einberechnet wird
954
            unsigned current_productivity = 10 * roadSeg->getCarrier(0)->GetProductivity() + current_best_way;
×
955

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

967
    return best_road;
8 ✔
968
}
969

970
struct ClientForWare
971
{
972
    noBaseBuilding* bld;
973
    unsigned estimate; // points minus half the optimal distance
974
    unsigned points;
975

976
    ClientForWare(noBaseBuilding* bld, unsigned estimate, unsigned points)
×
977
        : bld(bld), estimate(estimate), points(points)
×
978
    {}
×
979

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

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

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

1004
    std::vector<ClientForWare> possibleClients;
12 ✔
1005

1006
    const noRoadNode* start = ware.GetLocation();
12 ✔
1007

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

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

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

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

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

1059
                unsigned distance = world.CalcDistance(start->GetPos(), bld->GetPos()) / 2;
×
1060
                possibleClients.push_back(ClientForWare(bld, points > distance ? points - distance : 0, points));
×
1061
            }
1062
        }
1063
    }
1064

1065
    // sort our clients, highest score first
1066
    std::sort(possibleClients.begin(), possibleClients.end());
12 ✔
1067

1068
    noBaseBuilding* lastBld = nullptr;
12 ✔
1069
    noBaseBuilding* bestBld = nullptr;
12 ✔
1070
    unsigned best_points = 0;
12 ✔
1071
    for(auto& possibleClient : possibleClients)
12 ✔
1072
    {
1073
        unsigned path_length;
1074

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

1080
        // get rid of double building entries. TODO: why are there double entries!?
1081
        if(possibleClient.bld == lastBld)
×
1082
            continue;
×
1083

1084
        lastBld = possibleClient.bld;
×
1085

1086
        // Just to be sure no underflow happens...
1087
        if(possibleClient.points < best_points + 1)
×
1088
            continue;
×
1089

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

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

1104
            best_points = score;
×
1105
            bestBld = possibleClient.bld;
×
1106
        }
1107
    }
1108

1109
    if(bestBld && !wareDistribution.goals.empty())
12 ✔
1110
        wareDistribution.selected_goal =
×
1111
          (wareDistribution.selected_goal + 907) % unsigned(wareDistribution.goals.size());
×
1112

1113
    // Wenn kein Abnehmer gefunden wurde, muss es halt in ein Lagerhaus
1114
    if(!bestBld)
12 ✔
1115
        bestBld = FindWarehouseForWare(ware);
12 ✔
1116

1117
    return bestBld;
12 ✔
1118
}
1119

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

1137
nobBaseMilitary* GamePlayer::FindClientForCoin(const Ware& ware) const
×
1138
{
1139
    nobBaseMilitary* bb = nullptr;
×
1140
    unsigned best_points = 0, points;
×
1141

1142
    // Militärgebäude durchgehen
1143
    for(nobMilitary* milBld : buildings.GetMilitaryBuildings())
×
1144
    {
1145
        unsigned way_points;
1146

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

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

1170
    return bb;
×
1171
}
1172

1173
unsigned GamePlayer::GetBuidingSitePriority(const noBuildingSite* building_site)
×
1174
{
1175
    if(useCustomBuildOrder_)
×
1176
    {
1177
        // Spezielle Reihenfolge
1178

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

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

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

1209
bool GamePlayer::IsAlly(const unsigned char playerId) const
10,354 ✔
1210
{
1211
    // Der Spieler ist ja auch zu sich selber verbündet
1212
    if(GetPlayerId() == playerId)
10,354 ✔
1213
        return true;
2,639 ✔
1214
    else
1215
        return GetPactState(PactType::TreatyOfAlliance, playerId) == PactState::Accepted;
7,715 ✔
1216
}
1217

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

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

1240
        wh = FindWarehouse(*goal, FW::HasAnyMatchingSoldier(desiredRanks), false, false);
36 ✔
1241
        if(wh)
36 ✔
1242
        {
1243
            wh->OrderTroops(goal, counts, total_max);
19 ✔
1244
            sum = std::accumulate(counts.begin(), counts.end(), 0u);
19 ✔
1245
        }
1246
    } while(total_max && sum && wh);
36 ✔
1247
}
33 ✔
1248

1249
void GamePlayer::RegulateAllTroops()
66 ✔
1250
{
1251
    for(nobMilitary* milBld : buildings.GetMilitaryBuildings())
69 ✔
1252
        milBld->RegulateTroops();
3 ✔
1253
}
66 ✔
1254

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

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

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

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

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

1312
    /// Wenns eins gibt, dann rufen
1313
    if(wh)
30 ✔
1314
        wh->OrderJob(job, flag, true);
26 ✔
1315
}
1316

1317
bool GamePlayer::IsFlagWorker(const nofFlagWorker* flagworker)
×
1318
{
1319
    return helpers::contains(flagworkers, flagworker);
×
1320
}
1321

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

1336
void GamePlayer::RefreshDefenderList()
54 ✔
1337
{
1338
    shouldSendDefenderList.clear();
54 ✔
1339
    // Add as many true values as set in the settings, the rest will be false
1340
    for(unsigned i = 0; i < MILITARY_SETTINGS_SCALE[2]; ++i)
324 ✔
1341
        shouldSendDefenderList.push_back(i < militarySettings_[2]);
270 ✔
1342
    // und ordentlich schütteln
1343
    RANDOM_SHUFFLE2(shouldSendDefenderList, 0);
54 ✔
1344
}
54 ✔
1345

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

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

1369
    for(const auto tool : helpers::enumRange<Tool>())
168 ✔
1370
    {
1371
        tools_ordered[tool] = helpers::clamp(tools_ordered[tool] + orderChanges[tool], 0, 100);
72 ✔
1372
        tools_ordered_delta[tool] -= orderChanges[tool];
72 ✔
1373

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

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

1392
    RecalcDistribution();
6 ✔
1393
}
6 ✔
1394

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

1402
bool GamePlayer::ShouldSendDefender()
18 ✔
1403
{
1404
    // Wenn wir schon am Ende sind, muss die Verteidgungsliste erneuert werden
1405
    if(shouldSendDefenderList.empty())
18 ✔
1406
        RefreshDefenderList();
×
1407

1408
    bool result = shouldSendDefenderList.back();
18 ✔
1409
    shouldSendDefenderList.pop_back();
18 ✔
1410
    return result;
18 ✔
1411
}
1412

1413
void GamePlayer::TestDefeat()
78 ✔
1414
{
1415
    // Nicht schon besiegt?
1416
    // Keine Militärgebäude, keine Lagerhäuser (HQ,Häfen) -> kein Land --> verloren
1417
    if(!isDefeated && buildings.GetMilitaryBuildings().empty() && buildings.GetStorehouses().empty())
78 ✔
1418
        Surrender();
29 ✔
1419
}
78 ✔
1420

1421
nobHQ* GamePlayer::GetHQ() const
14 ✔
1422
{
1423
    const MapPoint& hqPos = GetHQPos();
14 ✔
1424
    return const_cast<nobHQ*>(hqPos.isValid() ? GetGameWorld().GetSpecObj<nobHQ>(hqPos) : nullptr);
14 ✔
1425
}
1426

1427
void GamePlayer::Surrender()
32 ✔
1428
{
1429
    if(isDefeated)
32 ✔
1430
        return;
1 ✔
1431

1432
    isDefeated = true;
31 ✔
1433

1434
    // GUI Bescheid sagen
1435
    if(world.GetGameInterface())
31 ✔
1436
        world.GetGameInterface()->GI_PlayerDefeated(GetPlayerId());
×
1437
}
1438

1439
void GamePlayer::SetStatisticValue(StatisticType type, unsigned value)
×
1440
{
1441
    statisticCurrentData[type] = value;
×
1442
}
×
1443

1444
void GamePlayer::ChangeStatisticValue(StatisticType type, int change)
1,922 ✔
1445
{
1446
    RTTR_Assert(change >= 0 || statisticCurrentData[type] >= static_cast<unsigned>(-change));
1,922 ✔
1447
    statisticCurrentData[type] += change;
1,922 ✔
1448
}
1,922 ✔
1449

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

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

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

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

1509
    // Produktivität berechnen
1510
    statisticCurrentData[StatisticType::Productivity] = buildings.CalcAverageProductivity();
6 ✔
1511

1512
    // Total points for tournament games
1513
    statisticCurrentData[StatisticType::Tournament] =
12 ✔
1514
      statisticCurrentData[StatisticType::Military] + 3 * statisticCurrentData[StatisticType::Vanquished];
6 ✔
1515
}
6 ✔
1516

1517
void GamePlayer::StatisticStep()
6 ✔
1518
{
1519
    CalcStatistics();
6 ✔
1520

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

1536
    statistic[StatisticTime::T15Minutes].counter++;
6 ✔
1537

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

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

1564
            statistic[nextT].currentIndex = incrStatIndex(statistic[nextT].currentIndex);
×
1565
            statistic[nextT].counter++;
×
1566
        }
1567
    }
1568

1569
    // Warenstatistikzähler nullen
1570
    statisticCurrentMerchandiseData.fill(0);
6 ✔
1571
}
6 ✔
1572

1573
GamePlayer::Pact::Pact(SerializedGameData& sgd)
112 ✔
1574
    : duration(sgd.PopUnsignedInt()), start(sgd.PopUnsignedInt()), accepted(sgd.PopBool()), want_cancel(sgd.PopBool())
112 ✔
1575
{}
112 ✔
1576

1577
void GamePlayer::Pact::Serialize(SerializedGameData& sgd) const
224 ✔
1578
{
1579
    sgd.PushUnsignedInt(duration);
224 ✔
1580
    sgd.PushUnsignedInt(start);
224 ✔
1581
    sgd.PushBool(accepted);
224 ✔
1582
    sgd.PushBool(want_cancel);
224 ✔
1583
}
224 ✔
1584

1585
void GamePlayer::PactChanged(const PactType pt)
24 ✔
1586
{
1587
    // Recheck military flags as the border (to an enemy) might have changed
1588
    RecalcMilitaryFlags();
24 ✔
1589

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

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

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

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

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

1638
    SendPostMessage(
16 ✔
1639
      std::make_unique<PostMsg>(world.GetEvMgr().GetCurrentGF(), pt, world.GetPlayer(other_player), true));
32 ✔
1640
}
16 ✔
1641

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

1651
        if(pacts[other_player][pt].duration == DURATION_INFINITE
4,670 ✔
1652
           || world.GetEvMgr().GetCurrentGF() < pacts[other_player][pt].start + pacts[other_player][pt].duration)
4,670 ✔
1653
            return PactState::Accepted;
4,668 ✔
1654
    }
1655

1656
    return PactState::None;
3,945 ✔
1657
}
1658

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

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

1686
    return 0;
49 ✔
1687
}
1688

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

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

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

1712
            otherPlayer.pacts[GetPlayerId()][pt].accepted = false;
2 ✔
1713
            otherPlayer.pacts[GetPlayerId()][pt].duration = 0;
2 ✔
1714
            otherPlayer.pacts[GetPlayerId()][pt].want_cancel = false;
2 ✔
1715

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

1738
                otherPlayer.pacts[GetPlayerId()][pt].accepted = false;
1 ✔
1739
                otherPlayer.pacts[GetPlayerId()][pt].duration = 0;
1 ✔
1740
                otherPlayer.pacts[GetPlayerId()][pt].want_cancel = false;
1 ✔
1741

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

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

1762
    // No team -> No pacts
1763
    if(team == Team::None)
57 ✔
1764
        return;
4 ✔
1765
    RTTR_Assert(isTeam(team));
53 ✔
1766

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

1782
bool GamePlayer::IsWareRegistred(const Ware& ware)
128 ✔
1783
{
1784
    return helpers::contains(ware_list, &ware);
128 ✔
1785
}
1786

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

1795
    return false;
14 ✔
1796
}
1797

1798
void GamePlayer::IncreaseInventoryWare(const GoodType ware, const unsigned count)
14,133 ✔
1799
{
1800
    global_inventory.Add(ConvertShields(ware), count);
14,133 ✔
1801
}
14,133 ✔
1802

1803
void GamePlayer::DecreaseInventoryWare(const GoodType ware, const unsigned count)
2,279 ✔
1804
{
1805
    global_inventory.Remove(ConvertShields(ware), count);
2,279 ✔
1806
}
2,279 ✔
1807

1808
/// Registriert ein Schiff beim Einwohnermeldeamt
1809
void GamePlayer::RegisterShip(noShip& ship)
21 ✔
1810
{
1811
    ships.push_back(&ship);
21 ✔
1812
    // Evtl bekommt das Schiffchen gleich was zu tun?
1813
    GetJobForShip(ship);
21 ✔
1814
}
21 ✔
1815

1816
struct ShipForHarbor
1817
{
1818
    noShip* ship;
1819
    uint32_t estimate;
1820

1821
    ShipForHarbor(noShip* ship, uint32_t estimate) : ship(ship), estimate(estimate) {}
18 ✔
1822

1823
    bool operator<(const ShipForHarbor& b) const
×
1824
    {
1825
        return (estimate < b.estimate) || (estimate == b.estimate && ship->GetObjId() < b.ship->GetObjId());
×
1826
    }
1827
};
1828

1829
/// Schiff für Hafen bestellen
1830
bool GamePlayer::OrderShip(nobHarborBuilding& hb)
32 ✔
1831
{
1832
    std::vector<ShipForHarbor> sfh;
64 ✔
1833

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

1854
    std::sort(sfh.begin(), sfh.end());
32 ✔
1855

1856
    noShip* best_ship = nullptr;
32 ✔
1857
    uint32_t best_distance = std::numeric_limits<uint32_t>::max();
32 ✔
1858
    std::vector<Direction> best_route;
64 ✔
1859

1860
    for(auto& it : sfh)
41 ✔
1861
    {
1862
        uint32_t distance;
1863
        std::vector<Direction> route;
18 ✔
1864

1865
        // the estimate (air-line distance) for this and all other ships in the list is already worse than what we
1866
        // found? disregard the rest
1867
        if(it.estimate >= best_distance)
18 ✔
1868
            break;
×
1869

1870
        noShip& ship = *it.ship;
18 ✔
1871

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

1874
        // ship already there?
1875
        if(ship.GetPos() == dest)
18 ✔
1876
        {
1877
            hb.ShipArrived(ship);
9 ✔
1878
            return (true);
9 ✔
1879
        }
1880

1881
        if(world.FindShipPathToHarbor(ship.GetPos(), hb.GetHarborPosID(), ship.GetSeaID(), &route, &distance))
9 ✔
1882
        {
1883
            if(distance < best_distance)
9 ✔
1884
            {
1885
                best_ship = &ship;
9 ✔
1886
                best_distance = distance;
9 ✔
1887
                best_route = route;
9 ✔
1888
            }
1889
        }
1890
    }
1891

1892
    // only order ships not already on their way
1893
    if(best_ship && best_ship->IsIdling())
23 ✔
1894
    {
1895
        best_ship->GoToHarbor(hb, best_route);
9 ✔
1896

1897
        return (true);
9 ✔
1898
    }
1899

1900
    return (false);
14 ✔
1901
}
1902

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

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

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

1931
        // Anzahl der Schiffe ermitteln, die diesen Hafen bereits anfahren
1932
        unsigned ships_coming = GetShipsToHarbor(*harbor);
×
1933

1934
        // Evtl. kommen schon genug?
1935
        if(harbor->GetNumNeededShips() <= ships_coming)
×
1936
            continue;
×
1937

1938
        // liegen wir am gleichen Meer?
1939
        if(world.IsHarborAtSea(harbor->GetHarborPosID(), ship.GetSeaID()))
×
1940
        {
1941
            const MapPoint coastPt = world.GetCoastalPoint(harbor->GetHarborPosID(), ship.GetSeaID());
×
1942

1943
            // Evtl. sind wir schon da?
1944
            if(ship.GetPos() == coastPt)
×
1945
            {
1946
                harbor->ShipArrived(ship);
×
1947
                return;
×
1948
            }
1949

1950
            unsigned length;
1951
            std::vector<Direction> route;
×
1952

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

1967
    // Einen Hafen gefunden?
1968
    if(best)
31 ✔
1969
        // Dann bekommt das gleich der Hafen
1970
        ship.GoToHarbor(*best, best_route);
×
1971
}
1972

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

1980
    return 0xFFFFFFFF;
×
1981
}
1982

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

1992
/// Gibt eine Liste mit allen Häfen dieses Spieler zurück, die an ein bestimmtes Meer angrenzen
1993
void GamePlayer::GetHarborsAtSea(std::vector<nobHarborBuilding*>& harbor_buildings, const unsigned short seaId) const
198 ✔
1994
{
1995
    for(nobHarborBuilding* harbor : buildings.GetHarbors())
642 ✔
1996
    {
1997
        if(helpers::contains(harbor_buildings, harbor))
444 ✔
1998
            continue;
216 ✔
1999

2000
        if(world.IsHarborAtSea(harbor->GetHarborPosID(), seaId))
228 ✔
2001
            harbor_buildings.push_back(harbor);
228 ✔
2002
    }
2003
}
198 ✔
2004

2005
/// Gibt die Anzahl der Schiffe, die einen bestimmten Hafen ansteuern, zurück
2006
unsigned GamePlayer::GetShipsToHarbor(const nobHarborBuilding& hb) const
71 ✔
2007
{
2008
    unsigned count = 0;
71 ✔
2009
    for(const auto* ship : ships)
142 ✔
2010
    {
2011
        if(ship->IsGoingToHarbor(hb))
71 ✔
2012
            ++count;
5 ✔
2013
    }
2014

2015
    return count;
71 ✔
2016
}
2017

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

2026
    for(nobHarborBuilding* hb : buildings.GetHarbors())
8 ✔
2027
    {
2028
        // Bestimmten Hafen ausschließen
2029
        if(hb == exception)
3 ✔
2030
            continue;
×
2031

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

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

2039
        // Kürzerer Weg als bisher bestes Ziel?
2040
        if(distance < best_distance)
3 ✔
2041
        {
2042
            best_distance = distance;
3 ✔
2043
            best = hb;
3 ✔
2044
        }
2045
    }
2046

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

2059
    return false;
2 ✔
2060
}
2061

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

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

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

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

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

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

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

2143
    BuildingCount bc = buildings.GetBuildingNums();
×
2144

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

2158
    return bc.buildings[BuildingType::Catapult] + bc.buildingSites[BuildingType::Catapult] < max;
×
2159
}
2160

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

2173
    // Nein? Dann haben wir ein neues Territorium gefunden
2174
    enemies_discovered_by_ships.push_back(location);
1 ✔
2175

2176
    return true;
1 ✔
2177
}
2178

2179
/// For debug only
2180
bool GamePlayer::IsDependentFigure(const noFigure& fig)
157 ✔
2181
{
2182
    for(const nobBaseWarehouse* wh : buildings.GetStorehouses())
350 ✔
2183
    {
2184
        if(wh->IsDependentFigure(fig))
193 ✔
2185
            return true;
×
2186
    }
2187
    return false;
157 ✔
2188
}
2189

2190
std::vector<nobBaseWarehouse*> GamePlayer::GetWarehousesForTrading(const nobBaseWarehouse& goalWh) const
18 ✔
2191
{
2192
    std::vector<nobBaseWarehouse*> result;
18 ✔
2193

2194
    // Don't try to trade with us!
2195
    if(goalWh.GetPlayer() == GetPlayerId())
18 ✔
2196
        return result;
6 ✔
2197

2198
    const MapPoint goalFlagPos = goalWh.GetFlagPos();
12 ✔
2199

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

2208
    return result;
12 ✔
2209
}
2210

2211
struct WarehouseDistanceComparator
2212
{
2213
    // Reference warehouse position, to which we want to calc the distance
2214
    const MapPoint refWareHousePos_;
2215
    /// GameWorld
2216
    const GameWorld& gwg_;
2217

2218
    WarehouseDistanceComparator(const nobBaseWarehouse& refWareHouse, const GameWorld& world)
12 ✔
2219
        : refWareHousePos_(refWareHouse.GetPos()), gwg_(world)
12 ✔
2220
    {}
12 ✔
2221

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

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

2236
    if(count == 0)
16 ✔
2237
        return;
2 ✔
2238

2239
    // Don't try to trade with us!
2240
    if(goalWh->GetPlayer() == GetPlayerId())
14 ✔
2241
        return;
1 ✔
2242

2243
    // No trades with enemies
2244
    if(!IsAlly(goalWh->GetPlayer()))
13 ✔
2245
        return;
1 ✔
2246

2247
    const MapPoint goalFlagPos = goalWh->GetFlagPos();
12 ✔
2248

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

2262
        const unsigned actualCount = std::min(available, count);
12 ✔
2263

2264
        // Find a trade path from flag to flag
2265
        TradeRoute tr(world, GetPlayerId(), wh->GetFlagPos(), goalFlagPos);
12 ✔
2266

2267
        // Found a path?
2268
        if(tr.IsValid())
12 ✔
2269
        {
2270
            // Add to cache for future searches
2271
            tradePathCache.addEntry(tr.GetTradePath(), GetPlayerId());
11 ✔
2272

2273
            wh->StartTradeCaravane(what, actualCount, tr, goalWh);
11 ✔
2274
            count -= available;
11 ✔
2275
            if(count == 0)
11 ✔
2276
                return;
×
2277
        }
2278
    }
2279
}
2280

2281
bool GamePlayer::IsBuildingEnabled(BuildingType type) const
947 ✔
2282
{
2283
    return building_enabled[type] || (isHuman() && world.GetGameInterface()->GI_GetCheats().areAllBuildingsEnabled());
947 ✔
2284
}
2285

2286
void GamePlayer::FillVisualSettings(VisualSettings& visualSettings) const
2 ✔
2287
{
2288
    Distributions& visDistribution = visualSettings.distribution;
2 ✔
2289
    unsigned visIdx = 0;
2 ✔
2290
    for(const DistributionMapping& mapping : distributionMap)
54 ✔
2291
    {
2292
        visDistribution[visIdx++] = distribution[std::get<0>(mapping)].percent_buildings[std::get<1>(mapping)];
52 ✔
2293
    }
2294

2295
    visualSettings.useCustomBuildOrder = useCustomBuildOrder_;
2 ✔
2296
    visualSettings.build_order = build_order;
2 ✔
2297

2298
    visualSettings.transport_order = GetOrderingFromTransportPrio(transportPrio);
2 ✔
2299

2300
    visualSettings.military_settings = militarySettings_;
2 ✔
2301
    visualSettings.tools_settings = toolsSettings_;
2 ✔
2302
}
2 ✔
2303

2304
#define INSTANTIATE_FINDWH(Cond)                                                                                \
2305
    template nobBaseWarehouse* GamePlayer::FindWarehouse(const noRoadNode&, const Cond&, bool, bool, unsigned*, \
2306
                                                         const RoadSegment*) const
2307

2308
INSTANTIATE_FINDWH(FW::HasMinWares);
2309
INSTANTIATE_FINDWH(FW::HasFigure);
2310
INSTANTIATE_FINDWH(FW::HasWareAndFigure);
2311
INSTANTIATE_FINDWH(FW::HasAnyMatchingSoldier);
2312
INSTANTIATE_FINDWH(FW::AcceptsWare);
2313
INSTANTIATE_FINDWH(FW::AcceptsFigure);
2314
INSTANTIATE_FINDWH(FW::CollectsWare);
2315
INSTANTIATE_FINDWH(FW::CollectsFigure);
2316
INSTANTIATE_FINDWH(FW::HasWareButNoCollect);
2317
INSTANTIATE_FINDWH(FW::HasFigureButNoCollect);
2318
INSTANTIATE_FINDWH(FW::AcceptsFigureButNoSend);
2319
INSTANTIATE_FINDWH(FW::NoCondition);
2320

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

© 2026 Coveralls, Inc