SpacecraftWrapper   C
last analyzed

Complexity

Total Complexity 55

Size/Duplication

Total Lines 328
Duplicated Lines 0 %

Test Coverage

Coverage 59.57%

Importance

Changes 0
Metric Value
eloc 138
dl 0
loc 328
ccs 84
cts 141
cp 0.5957
rs 6
c 0
b 0
f 0
wmc 55

28 Methods

Rating   Name   Duplication   Size   Complexity  
A reloadEpsUsage() 0 11 3
A getReactorUsage() 0 8 2
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 __construct() 0 14 1
A getReactorWrapper() 0 8 2
A canMan() 0 8 3
A getSpecificShipSystem() 0 8 1
A getRepairCosts() 0 11 1
A getPossibleTorpedoTypes() 0 8 2
A getCrewStyle() 0 11 3
A isUnalerted() 0 5 2
A __toString() 0 12 1
A setAlertState() 0 7 1
A isSelectable() 0 5 2
A getDamagedSystems() 0 24 5
A canFire() 0 13 4
A getTakeoverTicksLeft() 0 11 2
A getRepairDurationPreview() 0 4 1
A getAlertState() 0 4 1
A getTractoredShipWrapper() 0 9 2
A getStateIconAndTitle() 0 4 1
A getRepairDuration() 0 4 1
A getShieldRegenerationRate() 0 15 3
A canBeRepaired() 0 20 5

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 BadMethodCallException;
8
use Doctrine\Common\Collections\ArrayCollection;
9
use Doctrine\Common\Collections\Collection;
10
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...
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\SpacecraftSystemManagerInterface;
15
use Stu\Component\Spacecraft\System\SpacecraftSystemTypeEnum;
16
use Stu\Component\Spacecraft\System\SystemDataDeserializerInterface;
17
use Stu\Module\Colony\Lib\ColonyLibFactoryInterface;
18
use Stu\Module\Commodity\CommodityTypeConstants;
0 ignored issues
show
Bug introduced by
The type Stu\Module\Commodity\CommodityTypeConstants 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...
19
use Stu\Module\Control\GameControllerInterface;
20
use Stu\Module\Spacecraft\Lib\Interaction\ShipTakeoverManagerInterface;
21
use Stu\Module\Spacecraft\Lib\ReactorWrapperInterface;
22
use Stu\Module\Spacecraft\Lib\ShipRepairCost;
23
use Stu\Module\Spacecraft\Lib\SpacecraftStateChangerInterface;
24
use Stu\Module\Spacecraft\Lib\SpacecraftWrapperFactoryInterface;
25
use Stu\Module\Ship\Lib\ShipWrapperInterface;
26
use Stu\Module\Spacecraft\Lib\Reactor\ReactorWrapperFactoryInterface;
27
use Stu\Module\Spacecraft\Lib\Ui\StateIconAndTitle;
28
use Stu\Orm\Entity\SpacecraftSystem;
29
use Stu\Orm\Entity\ShipTakeover;
30
use Stu\Orm\Entity\Spacecraft;
31
use Stu\Orm\Repository\TorpedoTypeRepositoryInterface;
32
33
//TODO increase coverage
34
/**
35
 * @template T of Spacecraft
36
 */
37
abstract class SpacecraftWrapper implements SpacecraftWrapperInterface
38
{
39
    use SpacecraftWrapperSystemDataTrait;
40
41
    /** @var Collection<int, AbstractSystemData> */
42
    private Collection $shipSystemDataCache;
43
44
    private ?ReactorWrapperInterface $reactorWrapper = null;
45
46
    private ?int $epsUsage = null;
47
48
    /**
49
     * @param T $spacecraft
50
     */
51 67
    public function __construct(
52
        protected readonly Spacecraft $spacecraft,
53
        private readonly SpacecraftSystemManagerInterface $spacecraftSystemManager,
54
        private readonly SystemDataDeserializerInterface $systemDataDeserializer,
55
        private readonly TorpedoTypeRepositoryInterface $torpedoTypeRepository,
56
        protected readonly GameControllerInterface $game,
57
        protected readonly SpacecraftWrapperFactoryInterface $spacecraftWrapperFactory,
58
        private readonly ReactorWrapperFactoryInterface $reactorWrapperFactory,
59
        private readonly SpacecraftStateChangerInterface $spacecraftStateChanger,
60
        private readonly RepairUtilInterface $repairUtil,
61
        private readonly StateIconAndTitle $stateIconAndTitle,
62
        protected readonly ColonyLibFactoryInterface $colonyLibFactory
63
    ) {
64 67
        $this->shipSystemDataCache = new ArrayCollection();
65
    }
66
67
    #[Override]
68
    public function get(): Spacecraft
69
    {
70
        return $this->spacecraft;
71
    }
72
73
    #[Override]
74
    public function getSpacecraftWrapperFactory(): SpacecraftWrapperFactoryInterface
75
    {
76
        return $this->spacecraftWrapperFactory;
77
    }
78
79
    #[Override]
80
    public function getSpacecraftSystemManager(): SpacecraftSystemManagerInterface
81
    {
82
        return $this->spacecraftSystemManager;
83
    }
84
85 4
    #[Override]
86
    public function getEpsUsage(): int
87
    {
88 4
        if ($this->epsUsage === null) {
89 4
            $this->epsUsage = $this->reloadEpsUsage();
90
        }
91 4
        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...
92
    }
93
94
    #[Override]
95
    public function lowerEpsUsage(int $value): void
96
    {
97
        $this->epsUsage -= $value;
98
    }
99
100 4
    private function reloadEpsUsage(): int
101
    {
102 4
        $result = 0;
103
104 4
        foreach ($this->spacecraftSystemManager->getActiveSystems($this->spacecraft) as $shipSystem) {
105 3
            $result += $this->spacecraftSystemManager->getEnergyConsumption($shipSystem->getSystemType());
106
        }
107
108 4
        return $this->get()->hasComputer()
109 3
            ? $result + $this->getComputerSystemDataMandatory()->getAlertState()->getEpsUsage()
110 4
            : $result;
111
    }
112
113 2
    public function getReactorUsage(): int
114
    {
115 2
        $reactor = $this->reactorWrapper;
116 2
        if ($reactor === null) {
117
            throw new BadMethodCallException('this should not happen');
118
        }
119
120 2
        return $this->getEpsUsage() + $reactor->getUsage();
121
    }
122
123 10
    #[Override]
124
    public function getReactorWrapper(): ?ReactorWrapperInterface
125
    {
126 10
        if ($this->reactorWrapper === null) {
127 10
            $this->reactorWrapper = $this->reactorWrapperFactory->createReactorWrapper($this);
128
        }
129
130 10
        return $this->reactorWrapper;
131
    }
132
133 4
    #[Override]
134
    public function getAlertState(): SpacecraftAlertStateEnum
135
    {
136 4
        return $this->getComputerSystemDataMandatory()->getAlertState();
137
    }
138
139
    #[Override]
140
    public function setAlertState(SpacecraftAlertStateEnum $alertState): ?string
141
    {
142
        $msg = $this->spacecraftStateChanger->changeAlertState($this, $alertState);
143
        $this->epsUsage = $this->reloadEpsUsage();
144
145
        return $msg;
146
    }
147
148 3
    #[Override]
149
    public function isUnalerted(): bool
150
    {
151 3
        return !$this->spacecraft->hasSpacecraftSystem(SpacecraftSystemTypeEnum::COMPUTER)
152 3
            || $this->getComputerSystemDataMandatory()->isAlertGreen();
153
    }
154
155 3
    #[Override]
156
    public function getShieldRegenerationRate(): int
157
    {
158 3
        $regenerationPercentage = $this->get()->isSystemHealthy(SpacecraftSystemTypeEnum::SHIELDS) ? 10 : 0;
159
160 3
        $shield = $this->get()->getCondition()->getShield();
161 3
        $maxshield = $this->get()->getMaxShield();
162
163 3
        $result = (int) ceil(($maxshield / 100) * $regenerationPercentage);
164
165 3
        if ($result > $maxshield - $shield) {
166 1
            $result = $maxshield - $shield;
167
        }
168
169 3
        return $result;
170
    }
171
172
    /**
173
     * highest damage first, then prio
174
     *
175
     * @return SpacecraftSystem[]
176
     */
177 2
    #[Override]
178
    public function getDamagedSystems(): array
179
    {
180 2
        $damagedSystems = [];
181 2
        $prioArray = [];
182 2
        foreach ($this->spacecraft->getSystems() as $system) {
183 1
            if ($system->getStatus() < 100) {
184
                $damagedSystems[] = $system;
185
                $prioArray[$system->getSystemType()->value] = $system->getSystemType()->getPriority();
186
            }
187
        }
188
189
        // sort by damage and priority
190 2
        usort(
191 2
            $damagedSystems,
192 2
            function (SpacecraftSystem $a, SpacecraftSystem $b) use ($prioArray): int {
193
                if ($a->getStatus() === $b->getStatus()) {
194
                    return $prioArray[$b->getSystemType()->value] <=> $prioArray[$a->getSystemType()->value];
195
                }
196
                return ($a->getStatus() < $b->getStatus()) ? -1 : 1;
197 2
            }
198 2
        );
199
200 2
        return $damagedSystems;
201
    }
202
203 10
    #[Override]
204
    public function isSelectable(): bool
205
    {
206 10
        return $this->game->getUser() === $this->spacecraft->getUser()
207 10
            && $this->spacecraft->getType()->getModuleView() !== null;
208
    }
209
210 2
    #[Override]
211
    public function canBeRepaired(): bool
212
    {
213 2
        if (!$this->isUnalerted()) {
214 1
            return false;
215
        }
216
217 1
        if ($this->spacecraft->isShielded()) {
218
            return false;
219
        }
220
221 1
        if ($this->spacecraft->isCloaked()) {
222
            return false;
223
        }
224
225 1
        if ($this->getDamagedSystems() !== []) {
226
            return true;
227
        }
228
229 1
        return $this->spacecraft->getCondition()->getHull() < $this->spacecraft->getMaxHull();
230
    }
231
232 4
    #[Override]
233
    public function canFire(): bool
234
    {
235 4
        $ship = $this->spacecraft;
236 4
        if (!$ship->getNbs()) {
237 1
            return false;
238
        }
239 3
        if (!$ship->hasActiveWeapon()) {
240 1
            return false;
241
        }
242
243 2
        $epsSystem = $this->getEpsSystemData();
244 2
        return $epsSystem !== null && $epsSystem->getEps() !== 0;
245
    }
246
247 2
    #[Override]
248
    public function canMan(): bool
249
    {
250 2
        $buildplan = $this->spacecraft->getBuildplan();
251
252 2
        return $buildplan !== null
253 2
            && $buildplan->getCrew() > 0
254 2
            && $this->spacecraft->hasSpacecraftSystem(SpacecraftSystemTypeEnum::LIFE_SUPPORT);
255
    }
256
257
    #[Override]
258
    public function getRepairDuration(): int
259
    {
260
        return $this->repairUtil->getRepairDuration($this);
261
    }
262
263
    #[Override]
264
    public function getRepairDurationPreview(): int
265
    {
266
        return $this->repairUtil->getRepairDurationPreview($this);
267
    }
268
269
    #[Override]
270
    public function getRepairCosts(): array
271
    {
272
        $neededParts = $this->repairUtil->determineSpareParts($this, false);
273
274
        $neededSpareParts = $neededParts[CommodityTypeConstants::COMMODITY_SPARE_PART];
275
        $neededSystemComponents = $neededParts[CommodityTypeConstants::COMMODITY_SYSTEM_COMPONENT];
276
277
        return [
278
            new ShipRepairCost($neededSpareParts, CommodityTypeConstants::COMMODITY_SPARE_PART, CommodityTypeConstants::getDescription(CommodityTypeConstants::COMMODITY_SPARE_PART)),
279
            new ShipRepairCost($neededSystemComponents, CommodityTypeConstants::COMMODITY_SYSTEM_COMPONENT, CommodityTypeConstants::getDescription(CommodityTypeConstants::COMMODITY_SYSTEM_COMPONENT))
280
        ];
281
    }
282
283
    #[Override]
284
    public function getPossibleTorpedoTypes(): array
285
    {
286
        if ($this->spacecraft->hasSpacecraftSystem(SpacecraftSystemTypeEnum::TORPEDO_STORAGE)) {
287
            return $this->torpedoTypeRepository->getAll();
288
        }
289
290
        return $this->torpedoTypeRepository->getByLevel($this->spacecraft->getRump()->getTorpedoLevel());
291
    }
292
293
    #[Override]
294
    public function getTractoredShipWrapper(): ?ShipWrapperInterface
295
    {
296
        $tractoredShip = $this->spacecraft->getTractoredShip();
297
        if ($tractoredShip === null) {
298
            return null;
299
        }
300
301
        return $this->spacecraftWrapperFactory->wrapShip($tractoredShip);
302
    }
303
304 4
    #[Override]
305
    public function getStateIconAndTitle(): ?array
306
    {
307 4
        return $this->stateIconAndTitle->getStateIconAndTitle($this);
308
    }
309
310
    #[Override]
311
    public function getTakeoverTicksLeft(?ShipTakeover $takeover = null): int
312
    {
313
        $takeover ??= $this->spacecraft->getTakeoverActive();
314
        if ($takeover === null) {
315
            throw new BadMethodCallException('should not call when active takeover is null');
316
        }
317
318
        $currentTurn = $this->game->getCurrentRound()->getTurn();
319
320
        return $takeover->getStartTurn() + ShipTakeoverManagerInterface::TURNS_TO_TAKEOVER - $currentTurn;
321
    }
322
323 6
    #[Override]
324
    public function getCrewStyle(): string
325
    {
326 6
        $ship = $this->spacecraft;
327 6
        $excessCrew = $ship->getExcessCrewCount();
328
329 6
        if ($excessCrew === 0) {
330 5
            return "";
331
        }
332
333 3
        return $excessCrew > 0 ? "color: green;" : "color: red;";
334
    }
335
336
    /**
337
     * @template T2
338
     * @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...
339
     *
340
     * @return T2|null
341
     */
342 27
    public function getSpecificShipSystem(SpacecraftSystemTypeEnum $systemType, string $className)
343
    {
344 27
        return $this->systemDataDeserializer->getSpecificShipSystem(
345 27
            $this->spacecraft,
346 27
            $systemType,
347 27
            $className,
348 27
            $this->shipSystemDataCache,
349 27
            $this->spacecraftWrapperFactory
350 27
        );
351
    }
352
353
    #[Override]
354
    public function __toString(): string
355
    {
356
        $systems = implode(",\n", $this->spacecraft->getSystems()
357
            ->filter(fn($system): bool => $system->getData() !== null)
358
            ->map(fn($system): string => $system->__toString())
359
            ->toArray());
360
361
        return sprintf(
362
            "spacecraft: {%s,\n  systems: [\n%s\n}\n]",
363
            $this->spacecraft->__toString(),
364
            $systems
365
        );
366
    }
367
}
368