Passed
Push — master ( f4068b...77f637 )
by Nico
40:27 queued 14:09
created

ShipWrapper::getDockedToShipWrapper()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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