Passed
Push — dev ( b53f20...068554 )
by Janko
09:40
created

SpacecraftWrapper   F

Complexity

Total Complexity 62

Size/Duplication

Total Lines 397
Duplicated Lines 0 %

Test Coverage

Coverage 62.37%

Importance

Changes 0
Metric Value
eloc 173
c 0
b 0
f 0
dl 0
loc 397
ccs 116
cts 186
cp 0.6237
rs 3.44
wmc 62

32 Methods

Rating   Name   Duplication   Size   Complexity  
A reloadEpsUsage() 0 16 4
A setAlertState() 0 7 1
A getReactorUsage() 0 8 2
A __construct() 0 13 1
B getReactorWrapper() 0 35 6
A getSpacecraftWrapperFactory() 0 4 1
A lowerEpsUsage() 0 4 1
A getEpsUsage() 0 7 2
A get() 0 4 1
A getSpacecraftSystemManager() 0 4 1
A getRepairCosts() 0 11 1
A getSpecificShipSystem() 0 8 1
A getPossibleTorpedoTypes() 0 8 2
A getCrewStyle() 0 11 3
A getSensorRange() 0 10 2
A __toString() 0 12 1
A getShieldSystemData() 0 6 1
A getWarpDriveSystemData() 0 6 1
A getEpsSystemData() 0 6 1
A isSelectable() 0 5 2
A getDamagedSystems() 0 24 5
A getLssSystemData() 0 6 1
A canFire() 0 13 4
A canBeRepaired() 0 20 5
A getTakeoverTicksLeft() 0 11 2
A getRepairDurationPreview() 0 4 1
A getHullSystemData() 0 13 2
A getProjectileLauncherSystemData() 0 6 1
A getTractoredShipWrapper() 0 9 2
A getStateIconAndTitle() 0 4 1
A getRepairDuration() 0 4 1
A getShieldRegenerationRate() 0 6 2

How to fix   Complexity   

Complex Class

Complex classes like SpacecraftWrapper 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 SpacecraftWrapper, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Stu\Module\Spacecraft\Lib;
6
7
use Doctrine\Common\Collections\ArrayCollection;
8
use Doctrine\Common\Collections\Collection;
9
use Override;
0 ignored issues
show
Bug introduced by
The type Override was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
10
use RuntimeException;
11
use Stu\Component\Spacecraft\Repair\RepairUtilInterface;
12
use Stu\Component\Spacecraft\SpacecraftAlertStateEnum;
13
use Stu\Component\Spacecraft\System\Data\AbstractSystemData;
14
use Stu\Component\Spacecraft\System\Data\EpsSystemData;
15
use Stu\Component\Spacecraft\System\Data\FusionCoreSystemData;
16
use Stu\Component\Spacecraft\System\Data\HullSystemData;
17
use Stu\Component\Spacecraft\System\Data\LssSystemData;
18
use Stu\Component\Spacecraft\System\Data\ProjectileLauncherSystemData;
19
use Stu\Component\Spacecraft\System\Data\ShieldSystemData;
20
use Stu\Component\Spacecraft\System\Data\SingularityCoreSystemData;
21
use Stu\Component\Spacecraft\System\Data\WarpCoreSystemData;
22
use Stu\Component\Spacecraft\System\Data\WarpDriveSystemData;
23
use Stu\Component\Spacecraft\System\Exception\SystemNotFoundException;
24
use Stu\Component\Spacecraft\System\SpacecraftSystemManagerInterface;
25
use Stu\Component\Spacecraft\System\SpacecraftSystemTypeEnum;
26
use Stu\Component\Spacecraft\System\SystemDataDeserializerInterface;
27
use Stu\Module\Commodity\CommodityTypeEnum;
0 ignored issues
show
Bug introduced by
The type Stu\Module\Commodity\CommodityTypeEnum was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
28
use Stu\Module\Control\GameControllerInterface;
29
use Stu\Module\Spacecraft\Lib\Interaction\ShipTakeoverManagerInterface;
30
use Stu\Module\Spacecraft\Lib\ReactorWrapper;
31
use Stu\Module\Spacecraft\Lib\ReactorWrapperInterface;
32
use Stu\Module\Spacecraft\Lib\ShipRepairCost;
33
use Stu\Module\Spacecraft\Lib\SpacecraftStateChangerInterface;
34
use Stu\Module\Spacecraft\Lib\SpacecraftWrapperFactoryInterface;
35
use Stu\Module\Ship\Lib\ShipWrapperInterface;
36
use Stu\Module\Spacecraft\Lib\Ui\StateIconAndTitle;
37
use Stu\Orm\Entity\SpacecraftSystemInterface;
38
use Stu\Orm\Entity\ShipTakeoverInterface;
39
use Stu\Orm\Entity\SpacecraftInterface;
40
use Stu\Orm\Repository\TorpedoTypeRepositoryInterface;
41
42
//TODO increase coverage
43
/**
44
 * @template T of SpacecraftInterface
45
 */
46
abstract class SpacecraftWrapper implements SpacecraftWrapperInterface
47
{
48
    /** @var Collection<int, AbstractSystemData> */
49
    private Collection $shipSystemDataCache;
50
51
    private ?ReactorWrapperInterface $reactorWrapper = null;
52
53
    private ?int $epsUsage = null;
54
55
    /**
56
     * @param T $spacecraft
57
     */
58 63
    public function __construct(
59
        protected SpacecraftInterface $spacecraft,
60
        private SpacecraftSystemManagerInterface $spacecraftSystemManager,
61
        private SystemDataDeserializerInterface $systemDataDeserializer,
62
        private TorpedoTypeRepositoryInterface $torpedoTypeRepository,
63
        protected GameControllerInterface $game,
64
        protected SpacecraftWrapperFactoryInterface $spacecraftWrapperFactory,
65
        private SpacecraftStateChangerInterface $spacecraftStateChanger,
66
        private RepairUtilInterface $repairUtil,
67
        private StateIconAndTitle $stateIconAndTitle
68
    ) {
69
70 63
        $this->shipSystemDataCache = new ArrayCollection();
71
    }
72
73
    #[Override]
74
    public function get(): SpacecraftInterface
75
    {
76
        return $this->spacecraft;
77
    }
78
79
    #[Override]
80
    public function getSpacecraftWrapperFactory(): SpacecraftWrapperFactoryInterface
81
    {
82
        return $this->spacecraftWrapperFactory;
83
    }
84
85
    #[Override]
86
    public function getSpacecraftSystemManager(): SpacecraftSystemManagerInterface
87
    {
88
        return $this->spacecraftSystemManager;
89
    }
90
91 3
    #[Override]
92
    public function getEpsUsage(): int
93
    {
94 3
        if ($this->epsUsage === null) {
95 3
            $this->epsUsage = $this->reloadEpsUsage();
96
        }
97 3
        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...
98
    }
99
100
    #[Override]
101
    public function lowerEpsUsage(int $value): void
102
    {
103
        $this->epsUsage -= $value;
104
    }
105
106 3
    private function reloadEpsUsage(): int
107
    {
108 3
        $result = 0;
109
110 3
        foreach ($this->spacecraftSystemManager->getActiveSystems($this->spacecraft) as $shipSystem) {
111 3
            $result += $this->spacecraftSystemManager->getEnergyConsumption($shipSystem->getSystemType());
112
        }
113
114 3
        if ($this->spacecraft->getAlertState() == SpacecraftAlertStateEnum::ALERT_YELLOW) {
115
            $result += SpacecraftStateChangerInterface::ALERT_YELLOW_EPS_USAGE;
116
        }
117 3
        if ($this->spacecraft->getAlertState() == SpacecraftAlertStateEnum::ALERT_RED) {
118
            $result += SpacecraftStateChangerInterface::ALERT_RED_EPS_USAGE;
119
        }
120
121 3
        return $result;
122
    }
123
124 2
    public function getReactorUsage(): int
125
    {
126 2
        $reactor = $this->reactorWrapper;
127 2
        if ($reactor === null) {
128
            throw new RuntimeException('this should not happen');
129
        }
130
131 2
        return $this->getEpsUsage() + $reactor->getUsage();
132
    }
133
134 9
    #[Override]
135
    public function getReactorWrapper(): ?ReactorWrapperInterface
136
    {
137 9
        if ($this->reactorWrapper === null) {
138 9
            $ship = $this->spacecraft;
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->spacecraft of type Stu\Orm\Entity\SpacecraftInterface is incompatible with the declared type Stu\Module\Spacecraft\Lib\T of property $spacecraft.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
139 9
            $reactorSystemData = null;
140
141
142 9
            if ($ship->hasSpacecraftSystem(SpacecraftSystemTypeEnum::WARPCORE)) {
143 5
                $reactorSystemData = $this->getSpecificShipSystem(
144 5
                    SpacecraftSystemTypeEnum::WARPCORE,
145 5
                    WarpCoreSystemData::class
146 5
                );
147
            }
148 9
            if ($ship->hasSpacecraftSystem(SpacecraftSystemTypeEnum::SINGULARITY_REACTOR)) {
149
                $reactorSystemData = $this->getSpecificShipSystem(
150
                    SpacecraftSystemTypeEnum::SINGULARITY_REACTOR,
151
                    SingularityCoreSystemData::class
152
                );
153
            }
154 9
            if ($ship->hasSpacecraftSystem(SpacecraftSystemTypeEnum::FUSION_REACTOR)) {
155
                $reactorSystemData = $this->getSpecificShipSystem(
156
                    SpacecraftSystemTypeEnum::FUSION_REACTOR,
157
                    FusionCoreSystemData::class
158
                );
159
            }
160
161 9
            if ($reactorSystemData === null) {
162 6
                return null;
163
            }
164
165 5
            $this->reactorWrapper = new ReactorWrapper($this, $reactorSystemData);
166
        }
167
168 5
        return $this->reactorWrapper;
169
    }
170
171
    #[Override]
172
    public function setAlertState(SpacecraftAlertStateEnum $alertState): ?string
173
    {
174
        $msg = $this->spacecraftStateChanger->changeAlertState($this, $alertState);
175
        $this->epsUsage = $this->reloadEpsUsage();
176
177
        return $msg;
178
    }
179
180 1
    #[Override]
181
    public function getSensorRange(): int
182
    {
183 1
        $lssSystemData = $this->getLssSystemData();
184 1
        if ($lssSystemData === null) {
185
            return 0;
186
        }
187
188 1
        return (int) (ceil($lssSystemData->getSensorRange()
189 1
            * $this->get()->getSpacecraftSystem(SpacecraftSystemTypeEnum::LSS)->getStatus() / 100));
190
    }
191
192 7
    #[Override]
193
    public function getShieldRegenerationRate(): int
194
    {
195 7
        $regenerationPercentage = $this->get()->isSystemHealthy(SpacecraftSystemTypeEnum::SHIELDS) ? 10 : 0;
196
197 7
        return (int) ceil(($this->get()->getMaxShield() / 100) * $regenerationPercentage);
198
    }
199
200
    /**
201
     * highest damage first, then prio
202
     *
203
     * @return SpacecraftSystemInterface[]
204
     */
205 3
    #[Override]
206
    public function getDamagedSystems(): array
207
    {
208 3
        $damagedSystems = [];
209 3
        $prioArray = [];
210 3
        foreach ($this->spacecraft->getSystems() as $system) {
211 2
            if ($system->getStatus() < 100) {
212
                $damagedSystems[] = $system;
213
                $prioArray[$system->getSystemType()->value] = $this->spacecraftSystemManager->lookupSystem($system->getSystemType())->getPriority();
214
            }
215
        }
216
217
        // sort by damage and priority
218 3
        usort(
219 3
            $damagedSystems,
220 3
            function (SpacecraftSystemInterface $a, SpacecraftSystemInterface $b) use ($prioArray): int {
221
                if ($a->getStatus() === $b->getStatus()) {
222
                    return $prioArray[$b->getSystemType()->value] <=> $prioArray[$a->getSystemType()->value];
223
                }
224
                return ($a->getStatus() < $b->getStatus()) ? -1 : 1;
225 3
            }
226 3
        );
227
228 3
        return $damagedSystems;
229
    }
230
231 9
    #[Override]
232
    public function isSelectable(): bool
233
    {
234 9
        return $this->game->getUser() === $this->spacecraft->getUser()
235 9
            && $this->spacecraft->getType()->getModuleView() !== null;
236
    }
237
238 2
    #[Override]
239
    public function canBeRepaired(): bool
240
    {
241 2
        if ($this->spacecraft->getAlertState() !== SpacecraftAlertStateEnum::ALERT_GREEN) {
242
            return false;
243
        }
244
245 2
        if ($this->spacecraft->getShieldState()) {
246
            return false;
247
        }
248
249 2
        if ($this->spacecraft->isCloaked()) {
250
            return false;
251
        }
252
253 2
        if ($this->getDamagedSystems() !== []) {
254
            return true;
255
        }
256
257 2
        return $this->spacecraft->getHull() < $this->spacecraft->getMaxHull();
258
    }
259
260 4
    #[Override]
261
    public function canFire(): bool
262
    {
263 4
        $ship = $this->spacecraft;
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->spacecraft of type Stu\Orm\Entity\SpacecraftInterface is incompatible with the declared type Stu\Module\Spacecraft\Lib\T of property $spacecraft.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
264 4
        if (!$ship->getNbs()) {
265 1
            return false;
266
        }
267 3
        if (!$ship->hasActiveWeapon()) {
268 1
            return false;
269
        }
270
271 2
        $epsSystem = $this->getEpsSystemData();
272 2
        return $epsSystem !== null && $epsSystem->getEps() !== 0;
273
    }
274
275
    #[Override]
276
    public function getRepairDuration(): int
277
    {
278
        return $this->repairUtil->getRepairDuration($this);
279
    }
280
281
    #[Override]
282
    public function getRepairDurationPreview(): int
283
    {
284
        return $this->repairUtil->getRepairDurationPreview($this);
285
    }
286
287
    #[Override]
288
    public function getRepairCosts(): array
289
    {
290
        $neededParts = $this->repairUtil->determineSpareParts($this, false);
291
292
        $neededSpareParts = $neededParts[CommodityTypeEnum::COMMODITY_SPARE_PART];
293
        $neededSystemComponents = $neededParts[CommodityTypeEnum::COMMODITY_SYSTEM_COMPONENT];
294
295
        return [
296
            new ShipRepairCost($neededSpareParts, CommodityTypeEnum::COMMODITY_SPARE_PART, CommodityTypeEnum::getDescription(CommodityTypeEnum::COMMODITY_SPARE_PART)),
297
            new ShipRepairCost($neededSystemComponents, CommodityTypeEnum::COMMODITY_SYSTEM_COMPONENT, CommodityTypeEnum::getDescription(CommodityTypeEnum::COMMODITY_SYSTEM_COMPONENT))
298
        ];
299
    }
300
301
    #[Override]
302
    public function getPossibleTorpedoTypes(): array
303
    {
304
        if ($this->spacecraft->hasSpacecraftSystem(SpacecraftSystemTypeEnum::TORPEDO_STORAGE)) {
305
            return $this->torpedoTypeRepository->getAll();
306
        }
307
308
        return $this->torpedoTypeRepository->getByLevel($this->spacecraft->getRump()->getTorpedoLevel());
309
    }
310
311
    #[Override]
312
    public function getTractoredShipWrapper(): ?ShipWrapperInterface
313
    {
314
        $tractoredShip = $this->spacecraft->getTractoredShip();
315
        if ($tractoredShip === null) {
316
            return null;
317
        }
318
319
        return $this->spacecraftWrapperFactory->wrapShip($tractoredShip);
320
    }
321
322 4
    #[Override]
323
    public function getStateIconAndTitle(): ?array
324
    {
325 4
        return $this->stateIconAndTitle->getStateIconAndTitle($this);
326
    }
327
328
    #[Override]
329
    public function getTakeoverTicksLeft(?ShipTakeoverInterface $takeover = null): int
330
    {
331
        $takeover ??= $this->spacecraft->getTakeoverActive();
332
        if ($takeover === null) {
333
            throw new RuntimeException('should not call when active takeover is null');
334
        }
335
336
        $currentTurn = $this->game->getCurrentRound()->getTurn();
337
338
        return $takeover->getStartTurn() + ShipTakeoverManagerInterface::TURNS_TO_TAKEOVER - $currentTurn;
339
    }
340
341 6
    #[Override]
342
    public function getCrewStyle(): string
343
    {
344 6
        $ship = $this->spacecraft;
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->spacecraft of type Stu\Orm\Entity\SpacecraftInterface is incompatible with the declared type Stu\Module\Spacecraft\Lib\T of property $spacecraft.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
345 6
        $excessCrew = $ship->getExcessCrewCount();
346
347 6
        if ($excessCrew === 0) {
348 5
            return "";
349
        }
350
351 3
        return $excessCrew > 0 ? "color: green;" : "color: red;";
352
    }
353
354 9
    #[Override]
355
    public function getHullSystemData(): HullSystemData
356
    {
357 9
        $hullSystemData = $this->getSpecificShipSystem(
358 9
            SpacecraftSystemTypeEnum::HULL,
359 9
            HullSystemData::class
360 9
        );
361
362 9
        if ($hullSystemData === null) {
363
            throw new SystemNotFoundException('no hull installed?');
364
        }
365
366 9
        return $hullSystemData;
367
    }
368
369 8
    #[Override]
370
    public function getShieldSystemData(): ?ShieldSystemData
371
    {
372 8
        return $this->getSpecificShipSystem(
373 8
            SpacecraftSystemTypeEnum::SHIELDS,
374 8
            ShieldSystemData::class
375 8
        );
376
    }
377
378 18
    #[Override]
379
    public function getEpsSystemData(): ?EpsSystemData
380
    {
381 18
        return $this->getSpecificShipSystem(
382 18
            SpacecraftSystemTypeEnum::EPS,
383 18
            EpsSystemData::class
384 18
        );
385
    }
386
387 2
    #[Override]
388
    public function getLssSystemData(): ?LssSystemData
389
    {
390 2
        return $this->getSpecificShipSystem(
391 2
            SpacecraftSystemTypeEnum::LSS,
392 2
            LssSystemData::class
393 2
        );
394
    }
395
396 4
    #[Override]
397
    public function getWarpDriveSystemData(): ?WarpDriveSystemData
398
    {
399 4
        return $this->getSpecificShipSystem(
400 4
            SpacecraftSystemTypeEnum::WARPDRIVE,
401 4
            WarpDriveSystemData::class
402 4
        );
403
    }
404
405 1
    #[Override]
406
    public function getProjectileLauncherSystemData(): ?ProjectileLauncherSystemData
407
    {
408 1
        return $this->getSpecificShipSystem(
409 1
            SpacecraftSystemTypeEnum::TORPEDO,
410 1
            ProjectileLauncherSystemData::class
411 1
        );
412
    }
413
414
    /**
415
     * @template T2
416
     * @param class-string<T2> $className
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<T2> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<T2>.
Loading history...
417
     *
418
     * @return T2|null
419
     */
420 22
    protected function getSpecificShipSystem(SpacecraftSystemTypeEnum $systemType, string $className)
421
    {
422 22
        return $this->systemDataDeserializer->getSpecificShipSystem(
423 22
            $this->spacecraft,
424 22
            $systemType,
425 22
            $className,
426 22
            $this->shipSystemDataCache,
427 22
            $this->spacecraftWrapperFactory
428 22
        );
429
    }
430
431
    #[Override]
432
    public function __toString(): string
433
    {
434
        $systems = implode(",\n", $this->spacecraft->getSystems()
435
            ->filter(fn($system): bool => $system->getData() !== null)
436
            ->map(fn($system): string => $system->__toString())
437
            ->toArray());
438
439
        return sprintf(
440
            "spacecraft: {%s,\n  systems: [\n%s\n}\n]",
441
            $this->spacecraft->__toString(),
442
            $systems
443
        );
444
    }
445
}
446