Passed
Pull Request — master (#1686)
by Nico
31:24
created

ShipWrapper::getEpsSystemData()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 0
dl 0
loc 5
ccs 4
cts 4
cp 1
crap 1
rs 10
c 0
b 0
f 0
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
            $neededSpareParts += ((int)($this->get()->getRepairRate() / RepairTaskEnum::HULL_HITPOINTS_PER_SPARE_PART)) * $ticks;
307
        }
308
309
        $damagedSystems = $this->getDamagedSystems();
310
        foreach ($damagedSystems as $system) {
311
            $systemLvl = $system->determineSystemLevel();
312
            $healingPercentage = (100 - $system->getStatus()) / 100;
313
314
            $neededSpareParts += (int)ceil($healingPercentage * RepairTaskEnum::SHIPYARD_PARTS_USAGE[$systemLvl][RepairTaskEnum::SPARE_PARTS_ONLY]);
315
            $neededSystemComponents += (int)ceil($healingPercentage * RepairTaskEnum::SHIPYARD_PARTS_USAGE[$systemLvl][RepairTaskEnum::SYSTEM_COMPONENTS_ONLY]);
316
        }
317
318
        return [
319
            new ShipRepairCost($neededSpareParts, CommodityTypeEnum::COMMODITY_SPARE_PART, CommodityTypeEnum::getDescription(CommodityTypeEnum::COMMODITY_SPARE_PART)),
320
            new ShipRepairCost($neededSystemComponents, CommodityTypeEnum::COMMODITY_SYSTEM_COMPONENT, CommodityTypeEnum::getDescription(CommodityTypeEnum::COMMODITY_SYSTEM_COMPONENT))
321
        ];
322
    }
323
324
    public function getPossibleTorpedoTypes(): array
325
    {
326
        if ($this->ship->hasShipSystem(ShipSystemTypeEnum::SYSTEM_TORPEDO_STORAGE)) {
327
            return $this->torpedoTypeRepository->getAll();
328
        }
329
330
        return $this->torpedoTypeRepository->getByLevel($this->ship->getRump()->getTorpedoLevel());
331
    }
332
333
    public function getTractoredShipWrapper(): ?ShipWrapperInterface
334
    {
335
        $tractoredShip = $this->get()->getTractoredShip();
336
        if ($tractoredShip === null) {
337
            return null;
338
        }
339
340
        return $this->shipWrapperFactory->wrapShip($tractoredShip);
341
    }
342
343
    public function getTractoringShipWrapper(): ?ShipWrapperInterface
344
    {
345
        $tractoringShip = $this->get()->getTractoringShip();
346
        if ($tractoringShip === null) {
347
            return null;
348
        }
349
350
        return $this->shipWrapperFactory->wrapShip($tractoringShip);
351
    }
352
353
    public function getDockedToShipWrapper(): ?ShipWrapperInterface
354
    {
355
        $dockedTo = $this->get()->getDockedTo();
356
        if ($dockedTo === null) {
357
            return null;
358
        }
359
360
        return $this->shipWrapperFactory->wrapShip($dockedTo);
361
    }
362
363 7
    public function getStateIconAndTitle(): ?array
364
    {
365 7
        $ship = $this->get();
366
367 7
        $state = $ship->getState();
368
369 7
        if ($state === ShipStateEnum::SHIP_STATE_REPAIR_ACTIVE) {
370 2
            $isBase = $ship->isBase();
371 2
            return ['rep2', sprintf('%s repariert die Station', $isBase ? 'Stationscrew' : 'Schiffscrew')];
372
        }
373
374 5
        if ($state === ShipStateEnum::SHIP_STATE_REPAIR_PASSIVE) {
375 2
            $isBase = $ship->isBase();
376 2
            $repairDuration = $this->getRepairDuration();
377 2
            return ['rep2', sprintf('%s wird repariert (noch %d Runden)', $isBase ? 'Station' : 'Schiff', $repairDuration)];
378
        }
379
380 3
        $currentTurn = $this->game->getCurrentRound()->getTurn();
381 3
        if ($state === ShipStateEnum::SHIP_STATE_ASTRO_FINALIZING) {
382 1
            return ['map1', sprintf(
383 1
                'Schiff kartographiert (noch %d Runden)',
384 1
                $ship->getAstroStartTurn() + AstronomicalMappingEnum::TURNS_TO_FINISH - $currentTurn
385 1
            )];
386
        }
387
388 2
        $takeover = $ship->getTakeoverActive();
389
        if (
390 2
            $state === ShipStateEnum::SHIP_STATE_ACTIVE_TAKEOVER
391 2
            && $takeover !== null
392
        ) {
393 1
            $targetNamePlainText = $this->bbCodeParser->parse($takeover->getTargetShip()->getName())->getAsText();
394 1
            return ['take2', sprintf(
395 1
                'Schiff übernimmt die "%s" (noch %d Runden)',
396 1
                $targetNamePlainText,
397 1
                $this->getTakeoverTicksLeft($takeover)
398 1
            )];
399
        }
400
401 1
        $takeover = $ship->getTakeoverPassive();
402 1
        if ($takeover !== null) {
403 1
            $sourceUserNamePlainText = $this->bbCodeParser->parse($takeover->getSourceShip()->getUser()->getName())->getAsText();
404 1
            return ['untake2', sprintf(
405 1
                'Schiff wird von Spieler "%s" übernommen (noch %d Runden)',
406 1
                $sourceUserNamePlainText,
407 1
                $this->getTakeoverTicksLeft($takeover)
408 1
            )];
409
        }
410
411
        return null;
412
    }
413
414 2
    public function getTakeoverTicksLeft(ShipTakeoverInterface $takeover = null): int
415
    {
416 2
        $takeover = $takeover ?? $this->get()->getTakeoverActive();
417 2
        if ($takeover === null) {
418
            throw new RuntimeException('should not call when active takeover is null');
419
        }
420
421 2
        $currentTurn = $this->game->getCurrentRound()->getTurn();
422
423 2
        return $takeover->getStartTurn() + ShipTakeoverManagerInterface::TURNS_TO_TAKEOVER - $currentTurn;
424
    }
425
426
    public function canBeScrapped(): bool
427
    {
428
        $ship = $this->get();
429
430
        return $ship->isBase() && $ship->getState() !== ShipStateEnum::SHIP_STATE_UNDER_SCRAPPING;
431
    }
432
433 1
    public function getHullSystemData(): HullSystemData
434
    {
435 1
        $hullSystemData = $this->getSpecificShipSystem(
436 1
            ShipSystemTypeEnum::SYSTEM_HULL,
437 1
            HullSystemData::class
438 1
        );
439
440 1
        if ($hullSystemData === null) {
441
            throw new SystemNotFoundException('no hull installed?');
442
        }
443
444 1
        return $hullSystemData;
445
    }
446
447
    public function getShieldSystemData(): ?ShieldSystemData
448
    {
449
        return $this->getSpecificShipSystem(
450
            ShipSystemTypeEnum::SYSTEM_SHIELDS,
451
            ShieldSystemData::class
452
        );
453
    }
454
455 3
    public function getEpsSystemData(): ?EpsSystemData
456
    {
457 3
        return $this->getSpecificShipSystem(
458 3
            ShipSystemTypeEnum::SYSTEM_EPS,
459 3
            EpsSystemData::class
460 3
        );
461
    }
462
463
    public function getWarpDriveSystemData(): ?WarpDriveSystemData
464
    {
465
        return $this->getSpecificShipSystem(
466
            ShipSystemTypeEnum::SYSTEM_WARPDRIVE,
467
            WarpDriveSystemData::class
468
        );
469
    }
470
471
    public function getTrackerSystemData(): ?TrackerSystemData
472
    {
473
        return $this->getSpecificShipSystem(
474
            ShipSystemTypeEnum::SYSTEM_TRACKER,
475
            TrackerSystemData::class
476
        );
477
    }
478
479
    public function getWebEmitterSystemData(): ?WebEmitterSystemData
480
    {
481
        return $this->getSpecificShipSystem(
482
            ShipSystemTypeEnum::SYSTEM_THOLIAN_WEB,
483
            WebEmitterSystemData::class
484
        );
485
    }
486
487
    /**
488
     * @template T
489
     * @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...
490
     *
491
     * @return T|null
492
     */
493 4
    private function getSpecificShipSystem(ShipSystemTypeEnum $systemType, string $className)
494
    {
495
        if (
496 4
            $systemType !== ShipSystemTypeEnum::SYSTEM_HULL
497 4
            && !$this->get()->hasShipSystem($systemType)
498
        ) {
499 1
            return null;
500
        }
501
502
        //add system to cache if not already deserialized
503 3
        if (!array_key_exists($systemType->value, $this->shipSystemDataCache)) {
504 3
            $systemData = $this->shipSystemDataFactory->createSystemData($systemType, $this->shipWrapperFactory);
505 3
            $systemData->setShip($this->get());
506
507 3
            $data = $systemType === ShipSystemTypeEnum::SYSTEM_HULL ? null : $this->get()->getShipSystem($systemType)->getData();
508
509 3
            if ($data === null) {
510 2
                $this->shipSystemDataCache[$systemType->value] = $systemData;
511
            } else {
512 1
                $this->shipSystemDataCache[$systemType->value] =
513 1
                    $this->jsonMapper->mapObjectFromString(
514 1
                        $data,
515 1
                        $systemData
516 1
                    );
517
            }
518
        }
519
520
        //load deserialized system from cache
521 3
        $cacheItem = $this->shipSystemDataCache[$systemType->value];
522 3
        if (!$cacheItem instanceof $className) {
523
            throw new RuntimeException('this should not happen');
524
        }
525
526 3
        return $cacheItem;
527
    }
528
}
529