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