Passed
Push — master ( f4068b...77f637 )
by Nico
40:27 queued 14:09
created

ShipWrapper::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 24
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 11
nc 1
nop 11
dl 0
loc 24
ccs 12
cts 12
cp 1
crap 1
rs 9.9
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Stu\Module\Ship\Lib;
6
7
use JBBCode\Parser;
8
use JsonMapper\JsonMapperInterface;
9
use RuntimeException;
10
use Stu\Component\Ship\AstronomicalMappingEnum;
11
use Stu\Component\Ship\Repair\RepairUtilInterface;
12
use Stu\Component\Ship\RepairTaskEnum;
13
use Stu\Component\Ship\ShipAlertStateEnum;
14
use Stu\Component\Ship\ShipStateEnum;
15
use Stu\Component\Ship\System\Data\AbstractSystemData;
16
use Stu\Component\Ship\System\Data\EpsSystemData;
17
use Stu\Component\Ship\System\Data\HullSystemData;
18
use Stu\Component\Ship\System\Data\ShieldSystemData;
19
use Stu\Component\Ship\System\Data\ShipSystemDataFactoryInterface;
20
use Stu\Component\Ship\System\Data\TrackerSystemData;
21
use Stu\Component\Ship\System\Data\WarpCoreSystemData;
22
use Stu\Component\Ship\System\Data\WarpDriveSystemData;
23
use Stu\Component\Ship\System\Data\WebEmitterSystemData;
24
use Stu\Component\Ship\System\Exception\SystemNotFoundException;
25
use Stu\Component\Ship\System\ShipSystemManagerInterface;
26
use Stu\Component\Ship\System\ShipSystemTypeEnum;
27
use Stu\Module\Colony\Lib\ColonyLibFactoryInterface;
28
use Stu\Module\Commodity\CommodityTypeEnum;
29
use Stu\Module\Control\GameControllerInterface;
30
use Stu\Module\Ship\Lib\Interaction\ShipTakeoverManagerInterface;
31
use Stu\Orm\Entity\ShipInterface;
32
use Stu\Orm\Entity\ShipSystemInterface;
33
use Stu\Orm\Entity\ShipTakeoverInterface;
34
use Stu\Orm\Repository\TorpedoTypeRepositoryInterface;
35
36
//TODO increase coverage
37
final class ShipWrapper implements ShipWrapperInterface
38
{
39
    private ShipInterface $ship;
40
41
    private ShipSystemManagerInterface $shipSystemManager;
42
43
    private ColonyLibFactoryInterface $colonyLibFactory;
44
45
    private TorpedoTypeRepositoryInterface $torpedoTypeRepository;
46
47
    private GameControllerInterface $game;
48
49
    private JsonMapperInterface $jsonMapper;
50
51
    private ShipWrapperFactoryInterface $shipWrapperFactory;
52
53
    private ShipSystemDataFactoryInterface $shipSystemDataFactory;
54
55
    private ShipStateChangerInterface $shipStateChanger;
56
57
    private RepairUtilInterface $repairUtil;
58
59
    private Parser $bbCodeParser;
60
61
    /**
62
     * @var array<int, AbstractSystemData>
63
     */
64
    private array $shipSystemDataCache = [];
65
66
    private ?int $epsUsage = null;
67
68
    private ?int $effectiveEpsProduction = null;
69
70
    private ?int $effectiveWarpDriveProduction = null;
71
72 14
    public function __construct(
73
        ShipInterface $ship,
74
        ShipSystemManagerInterface $shipSystemManager,
75
        ColonyLibFactoryInterface $colonyLibFactory,
76
        TorpedoTypeRepositoryInterface $torpedoTypeRepository,
77
        GameControllerInterface $game,
78
        JsonMapperInterface $jsonMapper,
79
        ShipWrapperFactoryInterface $shipWrapperFactory,
80
        ShipSystemDataFactoryInterface $shipSystemDataFactory,
81
        ShipStateChangerInterface $shipStateChanger,
82
        RepairUtilInterface $repairUtil,
83
        Parser $bbCodeParser,
84
    ) {
85 14
        $this->ship = $ship;
86 14
        $this->shipSystemManager = $shipSystemManager;
87 14
        $this->colonyLibFactory = $colonyLibFactory;
88 14
        $this->torpedoTypeRepository = $torpedoTypeRepository;
89 14
        $this->game = $game;
90 14
        $this->jsonMapper = $jsonMapper;
91 14
        $this->shipWrapperFactory = $shipWrapperFactory;
92 14
        $this->shipSystemDataFactory = $shipSystemDataFactory;
93 14
        $this->shipStateChanger = $shipStateChanger;
94 14
        $this->repairUtil = $repairUtil;
95 14
        $this->bbCodeParser = $bbCodeParser;
96
    }
97
98 14
    public function get(): ShipInterface
99
    {
100 14
        return $this->ship;
101
    }
102
103
    public function getShipWrapperFactory(): ShipWrapperFactoryInterface
104
    {
105
        return $this->shipWrapperFactory;
106
    }
107
108
    public function getShipSystemManager(): ShipSystemManagerInterface
109
    {
110
        return $this->shipSystemManager;
111
    }
112
113
    public function getFleetWrapper(): ?FleetWrapperInterface
114
    {
115
        if ($this->get()->getFleet() === null) {
116
            return null;
117
        }
118
119
        return $this->shipWrapperFactory->wrapFleet($this->get()->getFleet());
120
    }
121
122
    public function getEpsUsage(): int
123
    {
124
        if ($this->epsUsage === null) {
125
            $this->epsUsage = $this->reloadEpsUsage();
126
        }
127
        return $this->epsUsage;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->epsUsage could return the type null which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
128
    }
129
130
    public function lowerEpsUsage(int $value): void
131
    {
132
        $this->epsUsage -= $value;
133
    }
134
135
    private function reloadEpsUsage(): int
136
    {
137
        $result = 0;
138
139
        foreach ($this->shipSystemManager->getActiveSystems($this->get()) as $shipSystem) {
140
            $result += $this->shipSystemManager->getEnergyConsumption($shipSystem->getSystemType());
141
        }
142
143
        if ($this->get()->getAlertState() == ShipAlertStateEnum::ALERT_YELLOW) {
144
            $result += ShipAlertStateEnum::ALERT_YELLOW_EPS_USAGE;
145
        }
146
        if ($this->get()->getAlertState() == ShipAlertStateEnum::ALERT_RED) {
147
            $result += ShipAlertStateEnum::ALERT_RED_EPS_USAGE;
148
        }
149
150
        return $result;
151
    }
152
153
    public function getEffectiveEpsProduction(): int
154
    {
155
        if ($this->effectiveEpsProduction === null) {
156
            $warpcore = $this->getWarpCoreSystemData();
157
            if ($warpcore === null) {
158
                $prod = $this->get()->getReactorOutputCappedByReactorLoad() - $this->getEpsUsage();
159
            } else {
160
                $prod = round(($this->get()->getReactorOutputCappedByReactorLoad() - $this->getEpsUsage()) * ($warpcore->getWarpCoreSplit() / 100));
161
            }
162
            if ($prod <= 0) {
163
                return (int) $prod;
164
            }
165
166
            $eps = $this->getEpsSystemData();
167
            if (
168
                $eps !== null
169
                && $eps->getEps() + $prod > $eps->getMaxEps()
170
            ) {
171
                return $eps->getMaxEps() - $eps->getEps();
172
            }
173
            $this->effectiveEpsProduction = (int) $prod;
174
        }
175
        return $this->effectiveEpsProduction;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->effectiveEpsProduction could return the type null which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
176
    }
177
178
    public function getEffectiveWarpDriveProduction(): int
179
    {
180
181
        if ($this->ship->getRump()->getFlightEcost() === 0 || $this->ship->getRump()->getFlightEcost() === null) {
182
            $flightcost = 1;
183
        } else {
184
            $flightcost = $this->ship->getRump()->getFlightEcost();
185
        }
186
        if ($this->effectiveWarpDriveProduction === null) {
187
            $warpcore = $this->getWarpCoreSystemData();
188
            if ($warpcore === null) {
189
                $prod = ($this->get()->getReactorOutputCappedByReactorLoad() - $this->getEpsUsage()) / $flightcost;
190
            } else {
191
                $prod = (($this->get()->getReactorOutputCappedByReactorLoad() - $this->getEpsUsage()) * (1 - ($warpcore->getWarpCoreSplit() / 100))) / $flightcost;
192
            }
193
            if ($prod <= 0) {
194
                return (int) $prod;
195
            }
196
197
            $warpdrive = $this->getWarpDriveSystemData();
198
            if (
199
                $warpdrive !== null
200
                && $warpdrive->getWarpDrive() + $prod > $warpdrive->getMaxWarpDrive()
201
            ) {
202
                return $warpdrive->getMaxWarpDrive() - $warpdrive->getWarpDrive();
203
            }
204
            $this->effectiveWarpDriveProduction = (int) round($prod);
205
        }
206
        return $this->effectiveWarpDriveProduction;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->effectiveWarpDriveProduction could return the type null which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
207
    }
208
209
210
    public function getWarpcoreUsage(): int
211
    {
212
        return $this->getEffectiveEpsProduction() + $this->getEpsUsage() + $this->getEffectiveWarpDriveProduction();
213
    }
214
215
    public function setAlertState(int $alertState): ?string
216
    {
217
        $msg = $this->shipStateChanger->changeAlertState($this, $alertState);
218
        $this->epsUsage = $this->reloadEpsUsage();
219
220
        return $msg;
221
    }
222
223
    /**
224
     * highest damage first, then prio
225
     *
226
     * @return ShipSystemInterface[]
227
     */
228
    public function getDamagedSystems(): array
229
    {
230
        $damagedSystems = [];
231
        $prioArray = [];
232
        foreach ($this->get()->getSystems() as $system) {
233
            if ($system->getStatus() < 100) {
234
                $damagedSystems[] = $system;
235
                $prioArray[$system->getSystemType()] = $this->shipSystemManager->lookupSystem($system->getSystemType())->getPriority();
236
            }
237
        }
238
239
        // sort by damage and priority
240
        usort(
241
            $damagedSystems,
242
            function (ShipSystemInterface $a, ShipSystemInterface $b) use ($prioArray): int {
243
                if ($a->getStatus() === $b->getStatus()) {
244
                    return $prioArray[$b->getSystemType()] <=> $prioArray[$a->getSystemType()];
245
                }
246
                return ($a->getStatus() < $b->getStatus()) ? -1 : 1;
247
            }
248
        );
249
250
        return $damagedSystems;
251
    }
252
253
    public function isOwnedByCurrentUser(): bool
254
    {
255
        return $this->game->getUser() === $this->get()->getUser();
256
    }
257
258
    public function canLandOnCurrentColony(): bool
259
    {
260
        if ($this->get()->getRump()->getCommodity() === null) {
261
            return false;
262
        }
263
        if ($this->get()->isShuttle()) {
264
            return false;
265
        }
266
267
        $currentColony = $this->get()->getStarsystemMap() !== null ? $this->get()->getStarsystemMap()->getColony() : null;
268
269
        if ($currentColony === null) {
270
            return false;
271
        }
272
        if ($currentColony->getUser() !== $this->get()->getUser()) {
273
            return false;
274
        }
275
276
        return $this->colonyLibFactory
277
            ->createColonySurface($currentColony)
278
            ->hasAirfield();
279
    }
280
281
    public function canBeRepaired(): bool
282
    {
283
        if ($this->get()->getAlertState() !== ShipAlertStateEnum::ALERT_GREEN) {
284
            return false;
285
        }
286
287
        if ($this->get()->getShieldState()) {
288
            return false;
289
        }
290
291
        if ($this->get()->getCloakState()) {
292
            return false;
293
        }
294
295
        if (!empty($this->getDamagedSystems())) {
296
            return true;
297
        }
298
299
        return $this->get()->getHull() < $this->get()->getMaxHull();
300
    }
301
302 2
    public function getRepairDuration(): int
303
    {
304 2
        return $this->repairUtil->getRepairDuration($this);
305
    }
306
307
    public function getRepairDurationPreview(): int
308
    {
309
        return $this->repairUtil->getRepairDurationPreview($this);
310
    }
311
312
    public function getRepairCosts(): array
313
    {
314
        $neededSpareParts = 0;
315
        $neededSystemComponents = 0;
316
317
        $hull = $this->get()->getHull();
318
        $maxHull = $this->get()->getMaxHull();
319
320
        if ($hull < $maxHull) {
321
            $ticks = (int) ceil(($this->get()->getMaxHull() - $this->get()->getHull()) / $this->get()->getRepairRate());
322
            $neededSpareParts += ((int)($this->get()->getRepairRate() / RepairTaskEnum::HULL_HITPOINTS_PER_SPARE_PART)) * $ticks;
323
        }
324
325
        $damagedSystems = $this->getDamagedSystems();
326
        foreach ($damagedSystems as $system) {
327
            $systemLvl = $system->determineSystemLevel();
328
            $healingPercentage = (100 - $system->getStatus()) / 100;
329
330
            $neededSpareParts += (int)ceil($healingPercentage * RepairTaskEnum::SHIPYARD_PARTS_USAGE[$systemLvl][RepairTaskEnum::SPARE_PARTS_ONLY]);
331
            $neededSystemComponents += (int)ceil($healingPercentage * RepairTaskEnum::SHIPYARD_PARTS_USAGE[$systemLvl][RepairTaskEnum::SYSTEM_COMPONENTS_ONLY]);
332
        }
333
334
        return [
335
            new ShipRepairCost($neededSpareParts, CommodityTypeEnum::COMMODITY_SPARE_PART, CommodityTypeEnum::getDescription(CommodityTypeEnum::COMMODITY_SPARE_PART)),
336
            new ShipRepairCost($neededSystemComponents, CommodityTypeEnum::COMMODITY_SYSTEM_COMPONENT, CommodityTypeEnum::getDescription(CommodityTypeEnum::COMMODITY_SYSTEM_COMPONENT))
337
        ];
338
    }
339
340
    public function getPossibleTorpedoTypes(): array
341
    {
342
        if ($this->ship->hasShipSystem(ShipSystemTypeEnum::SYSTEM_TORPEDO_STORAGE)) {
343
            return $this->torpedoTypeRepository->getAll();
344
        }
345
346
        return $this->torpedoTypeRepository->getByLevel($this->ship->getRump()->getTorpedoLevel());
347
    }
348
349
    public function getTractoredShipWrapper(): ?ShipWrapperInterface
350
    {
351
        $tractoredShip = $this->get()->getTractoredShip();
352
        if ($tractoredShip === null) {
353
            return null;
354
        }
355
356
        return $this->shipWrapperFactory->wrapShip($tractoredShip);
357
    }
358
359
    public function getTractoringShipWrapper(): ?ShipWrapperInterface
360
    {
361
        $tractoringShip = $this->get()->getTractoringShip();
362
        if ($tractoringShip === null) {
363
            return null;
364
        }
365
366
        return $this->shipWrapperFactory->wrapShip($tractoringShip);
367
    }
368
369
    public function getDockedToShipWrapper(): ?ShipWrapperInterface
370
    {
371
        $dockedTo = $this->get()->getDockedTo();
372
        if ($dockedTo === null) {
373
            return null;
374
        }
375
376
        return $this->shipWrapperFactory->wrapShip($dockedTo);
377
    }
378
379 7
    public function getStateIconAndTitle(): ?array
380
    {
381 7
        $ship = $this->get();
382
383 7
        $state = $ship->getState();
384
385 7
        if ($state === ShipStateEnum::SHIP_STATE_REPAIR_ACTIVE) {
386 2
            $isBase = $ship->isBase();
387 2
            return ['rep2', sprintf('%s repariert die Station', $isBase ? 'Stationscrew' : 'Schiffscrew')];
388
        }
389
390 5
        if ($state === ShipStateEnum::SHIP_STATE_REPAIR_PASSIVE) {
391 2
            $isBase = $ship->isBase();
392 2
            $repairDuration = $this->getRepairDuration();
393 2
            return ['rep2', sprintf('%s wird repariert (noch %d Runden)', $isBase ? 'Station' : 'Schiff', $repairDuration)];
394
        }
395
396 3
        $currentTurn = $this->game->getCurrentRound()->getTurn();
397 3
        if ($state === ShipStateEnum::SHIP_STATE_ASTRO_FINALIZING) {
398 1
            return ['map1', sprintf(
399 1
                'Schiff kartographiert (noch %d Runden)',
400 1
                $ship->getAstroStartTurn() + AstronomicalMappingEnum::TURNS_TO_FINISH - $currentTurn
401 1
            )];
402
        }
403
404 2
        $takeover = $ship->getTakeoverActive();
405
        if (
406 2
            $state === ShipStateEnum::SHIP_STATE_ACTIVE_TAKEOVER
407 2
            && $takeover !== null
408
        ) {
409 1
            $targetNamePlainText = $this->bbCodeParser->parse($takeover->getTargetShip()->getName())->getAsText();
410 1
            return ['take2', sprintf(
411 1
                'Schiff übernimmt die "%s" (noch %d Runden)',
412 1
                $targetNamePlainText,
413 1
                $this->getTakeoverTicksLeft($takeover)
414 1
            )];
415
        }
416
417 1
        $takeover = $ship->getTakeoverPassive();
418 1
        if ($takeover !== null) {
419 1
            $sourceUserNamePlainText = $this->bbCodeParser->parse($takeover->getSourceShip()->getUser()->getName())->getAsText();
420 1
            return ['untake2', sprintf(
421 1
                'Schiff wird von Spieler "%s" übernommen (noch %d Runden)',
422 1
                $sourceUserNamePlainText,
423 1
                $this->getTakeoverTicksLeft($takeover)
424 1
            )];
425
        }
426
427
        return null;
428
    }
429
430 2
    public function getTakeoverTicksLeft(ShipTakeoverInterface $takeover = null): int
431
    {
432 2
        $takeover = $takeover ?? $this->get()->getTakeoverActive();
433 2
        if ($takeover === null) {
434
            throw new RuntimeException('should not call when active takeover is null');
435
        }
436
437 2
        $currentTurn = $this->game->getCurrentRound()->getTurn();
438
439 2
        return $takeover->getStartTurn() + ShipTakeoverManagerInterface::TURNS_TO_TAKEOVER - $currentTurn;
440
    }
441
442
    public function canBeScrapped(): bool
443
    {
444
        $ship = $this->get();
445
446
        return $ship->isBase() && $ship->getState() !== ShipStateEnum::SHIP_STATE_UNDER_SCRAPPING;
447
    }
448
449 1
    public function getHullSystemData(): HullSystemData
450
    {
451 1
        $hullSystemData = $this->getSpecificShipSystem(
452 1
            ShipSystemTypeEnum::SYSTEM_HULL,
453 1
            HullSystemData::class
454 1
        );
455
456 1
        if ($hullSystemData === null) {
457
            throw new SystemNotFoundException('no hull installed?');
458
        }
459
460 1
        return $hullSystemData;
461
    }
462
463
    public function getShieldSystemData(): ?ShieldSystemData
464
    {
465
        return $this->getSpecificShipSystem(
466
            ShipSystemTypeEnum::SYSTEM_SHIELDS,
467
            ShieldSystemData::class
468
        );
469
    }
470
471 3
    public function getEpsSystemData(): ?EpsSystemData
472
    {
473 3
        return $this->getSpecificShipSystem(
474 3
            ShipSystemTypeEnum::SYSTEM_EPS,
475 3
            EpsSystemData::class
476 3
        );
477
    }
478
479
    public function getWarpCoreSystemData(): ?WarpCoreSystemData
480
    {
481
        return $this->getSpecificShipSystem(
482
            ShipSystemTypeEnum::SYSTEM_WARPCORE,
483
            WarpCoreSystemData::class
484
        );
485
    }
486
487
    public function getWarpDriveSystemData(): ?WarpDriveSystemData
488
    {
489
        return $this->getSpecificShipSystem(
490
            ShipSystemTypeEnum::SYSTEM_WARPDRIVE,
491
            WarpDriveSystemData::class
492
        );
493
    }
494
495
    public function getTrackerSystemData(): ?TrackerSystemData
496
    {
497
        return $this->getSpecificShipSystem(
498
            ShipSystemTypeEnum::SYSTEM_TRACKER,
499
            TrackerSystemData::class
500
        );
501
    }
502
503
    public function getWebEmitterSystemData(): ?WebEmitterSystemData
504
    {
505
        return $this->getSpecificShipSystem(
506
            ShipSystemTypeEnum::SYSTEM_THOLIAN_WEB,
507
            WebEmitterSystemData::class
508
        );
509
    }
510
511
    /**
512
     * @template T
513
     * @param class-string<T> $className
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<T> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<T>.
Loading history...
514
     *
515
     * @return T|null
516
     */
517 4
    private function getSpecificShipSystem(int $systemType, string $className)
518
    {
519
        if (
520 4
            $systemType !== ShipSystemTypeEnum::SYSTEM_HULL
521 4
            && !$this->get()->hasShipSystem($systemType)
522
        ) {
523 1
            return null;
524
        }
525
526
        //add system to cache if not already deserialized
527 3
        if (!array_key_exists($systemType, $this->shipSystemDataCache)) {
528 3
            $systemData = $this->shipSystemDataFactory->createSystemData($systemType, $this->shipWrapperFactory);
529 3
            $systemData->setShip($this->get());
530
531 3
            $data = $systemType === ShipSystemTypeEnum::SYSTEM_HULL ? null : $this->get()->getShipSystem($systemType)->getData();
532
533 3
            if ($data === null) {
534 2
                $this->shipSystemDataCache[$systemType] = $systemData;
535
            } else {
536 1
                $this->shipSystemDataCache[$systemType] =
537 1
                    $this->jsonMapper->mapObjectFromString(
538 1
                        $data,
539 1
                        $systemData
540 1
                    );
541
            }
542
        }
543
544
        //load deserialized system from cache
545 3
        $cacheItem = $this->shipSystemDataCache[$systemType];
546 3
        if (!$cacheItem instanceof $className) {
547
            throw new RuntimeException('this should not happen');
548
        }
549
550 3
        return $cacheItem;
551
    }
552
}
553