Passed
Push — master ( 4cbcfa...1ee8de )
by Nico
53:18 queued 29:25
created

ShipWrapper   F

Complexity

Total Complexity 84

Size/Duplication

Total Lines 507
Duplicated Lines 0 %

Test Coverage

Coverage 32.68%

Importance

Changes 2
Bugs 1 Features 0
Metric Value
eloc 227
dl 0
loc 507
ccs 84
cts 257
cp 0.3268
rs 2
c 2
b 1
f 0
wmc 84

33 Methods

Rating   Name   Duplication   Size   Complexity  
A lowerEpsUsage() 0 3 1
A getShipSystemManager() 0 3 1
A __construct() 0 24 1
A getFleetWrapper() 0 7 2
A getEpsUsage() 0 6 2
A get() 0 3 1
A getShipWrapperFactory() 0 3 1
A reloadEpsUsage() 0 16 4
A getReactorUsage() 0 8 2
A getRepairDurationPreview() 0 3 1
A getDamagedSystems() 0 23 5
A isOwnedByCurrentUser() 0 3 1
A getRepairDuration() 0 3 1
A canBeRepaired() 0 19 5
A setAlertState() 0 6 1
A canLandOnCurrentColony() 0 21 6
A getReactorWrapper() 0 34 6
A getPossibleTorpedoTypes() 0 7 2
A getTractoredShipWrapper() 0 8 2
A getTractoringShipWrapper() 0 8 2
A getRepairCosts() 0 32 5
A getTakeoverTicksLeft() 0 10 2
A getCrewStyle() 0 10 3
B getSpecificShipSystem() 0 34 7
A getShieldSystemData() 0 5 1
A getHullSystemData() 0 12 2
A getEpsSystemData() 0 5 1
A getWarpDriveSystemData() 0 5 1
A getTrackerSystemData() 0 5 1
A getDockedToShipWrapper() 0 8 2
A canBeScrapped() 0 5 2
B getStateIconAndTitle() 0 49 9
A getWebEmitterSystemData() 0 5 1

How to fix   Complexity   

Complex Class

Complex classes like ShipWrapper often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ShipWrapper, and based on these observations, apply Extract Interface, too.

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