Passed
Pull Request — master (#1676)
by Nico
24:49
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 JsonMapper\JsonMapperInterface;
8
use RuntimeException;
9
use Stu\Component\Building\BuildingEnum;
10
use Stu\Component\Colony\ColonyFunctionManagerInterface;
11
use Stu\Component\Ship\RepairTaskEnum;
12
use Stu\Component\Ship\ShipAlertStateEnum;
13
use Stu\Component\Ship\ShipStateEnum;
14
use Stu\Component\Ship\System\Data\AbstractSystemData;
15
use Stu\Component\Ship\System\Data\EpsSystemData;
16
use Stu\Component\Ship\System\Data\HullSystemData;
17
use Stu\Component\Ship\System\Data\ShieldSystemData;
18
use Stu\Component\Ship\System\Data\ShipSystemDataFactoryInterface;
19
use Stu\Component\Ship\System\Data\TrackerSystemData;
20
use Stu\Component\Ship\System\Data\WarpCoreSystemData;
21
use Stu\Component\Ship\System\Data\WarpDriveSystemData;
22
use Stu\Component\Ship\System\Data\WebEmitterSystemData;
23
use Stu\Component\Ship\System\Exception\SystemNotFoundException;
24
use Stu\Component\Ship\System\ShipSystemManagerInterface;
25
use Stu\Component\Ship\System\ShipSystemTypeEnum;
26
use Stu\Module\Colony\Lib\ColonyLibFactoryInterface;
27
use Stu\Module\Commodity\CommodityTypeEnum;
28
use Stu\Module\Control\GameControllerInterface;
29
use Stu\Orm\Entity\ShipInterface;
30
use Stu\Orm\Entity\ShipSystemInterface;
31
use Stu\Orm\Repository\ColonyShipRepairRepositoryInterface;
32
use Stu\Orm\Repository\TorpedoTypeRepositoryInterface;
33
34
//TODO increase coverage
35
final class ShipWrapper implements ShipWrapperInterface
36
{
37
    private ShipInterface $ship;
38
39
    private ShipSystemManagerInterface $shipSystemManager;
40
41
    private ColonyLibFactoryInterface $colonyLibFactory;
42
43
    private ColonyShipRepairRepositoryInterface $colonyShipRepairRepository;
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 ColonyFunctionManagerInterface $colonyFunctionManager;
56
57
    private ShipStateChangerInterface $shipStateChanger;
58
59
    /**
60
     * @var array<int, AbstractSystemData>
61
     */
62
    private array $shipSystemDataCache = [];
63
64
    private ?int $epsUsage = null;
65
66
    private ?int $effectiveEpsProduction = null;
67
68
    private ?int $effectiveWarpDriveProduction = null;
69
70 15
    public function __construct(
71
        ColonyFunctionManagerInterface $colonyFunctionManager,
72
        ShipInterface $ship,
73
        ShipSystemManagerInterface $shipSystemManager,
74
        ColonyShipRepairRepositoryInterface $colonyShipRepairRepository,
75
        ColonyLibFactoryInterface $colonyLibFactory,
76
        TorpedoTypeRepositoryInterface $torpedoTypeRepository,
77
        GameControllerInterface $game,
78
        JsonMapperInterface $jsonMapper,
79
        ShipWrapperFactoryInterface $shipWrapperFactory,
80
        ShipSystemDataFactoryInterface $shipSystemDataFactory,
81
        ShipStateChangerInterface $shipStateChanger
82
    ) {
83 15
        $this->ship = $ship;
84 15
        $this->shipSystemManager = $shipSystemManager;
85 15
        $this->colonyLibFactory = $colonyLibFactory;
86 15
        $this->colonyShipRepairRepository = $colonyShipRepairRepository;
87 15
        $this->torpedoTypeRepository = $torpedoTypeRepository;
88 15
        $this->game = $game;
89 15
        $this->jsonMapper = $jsonMapper;
90 15
        $this->shipWrapperFactory = $shipWrapperFactory;
91 15
        $this->shipSystemDataFactory = $shipSystemDataFactory;
92 15
        $this->colonyFunctionManager = $colonyFunctionManager;
93 15
        $this->shipStateChanger = $shipStateChanger;
94
    }
95
96 15
    public function get(): ShipInterface
97
    {
98 15
        return $this->ship;
99
    }
100
101
    public function getShipWrapperFactory(): ShipWrapperFactoryInterface
102
    {
103
        return $this->shipWrapperFactory;
104
    }
105
106
    public function getShipSystemManager(): ShipSystemManagerInterface
107
    {
108
        return $this->shipSystemManager;
109
    }
110
111
    public function getFleetWrapper(): ?FleetWrapperInterface
112
    {
113
        if ($this->get()->getFleet() === null) {
114
            return null;
115
        }
116
117
        return $this->shipWrapperFactory->wrapFleet($this->get()->getFleet());
118
    }
119
120
    public function getEpsUsage(): int
121
    {
122
        if ($this->epsUsage === null) {
123
            $this->epsUsage = $this->reloadEpsUsage();
124
        }
125
        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...
126
    }
127
128
    public function lowerEpsUsage(int $value): void
129
    {
130
        $this->epsUsage -= $value;
131
    }
132
133
    private function reloadEpsUsage(): int
134
    {
135
        $result = 0;
136
137
        foreach ($this->shipSystemManager->getActiveSystems($this->get()) as $shipSystem) {
138
            $result += $this->shipSystemManager->getEnergyConsumption($shipSystem->getSystemType());
139
        }
140
141
        if ($this->get()->getAlertState() == ShipAlertStateEnum::ALERT_YELLOW) {
142
            $result += ShipAlertStateEnum::ALERT_YELLOW_EPS_USAGE;
143
        }
144
        if ($this->get()->getAlertState() == ShipAlertStateEnum::ALERT_RED) {
145
            $result += ShipAlertStateEnum::ALERT_RED_EPS_USAGE;
146
        }
147
148
        return $result;
149
    }
150
151
    public function getEffectiveEpsProduction(): int
152
    {
153
        if ($this->effectiveEpsProduction === null) {
154
            $warpcore = $this->getWarpCoreSystemData();
155
            if ($warpcore === null) {
156
                $prod = $this->get()->getReactorOutputCappedByReactorLoad() - $this->getEpsUsage();
157
            } else {
158
                $prod = round(($this->get()->getReactorOutputCappedByReactorLoad() - $this->getEpsUsage()) * ($warpcore->getWarpCoreSplit() / 100));
159
            }
160
            if ($prod <= 0) {
161
                return (int) $prod;
162
            }
163
164
            $eps = $this->getEpsSystemData();
165
            if (
166
                $eps !== null
167
                && $eps->getEps() + $prod > $eps->getMaxEps()
168
            ) {
169
                return $eps->getMaxEps() - $eps->getEps();
170
            }
171
            $this->effectiveEpsProduction = (int) $prod;
172
        }
173
        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...
174
    }
175
176
    public function getEffectiveWarpDriveProduction(): int
177
    {
178
179
        if ($this->ship->getRump()->getFlightEcost() === 0 || $this->ship->getRump()->getFlightEcost() === null) {
180
            $flightcost = 1;
181
        } else {
182
            $flightcost = $this->ship->getRump()->getFlightEcost();
183
        }
184
        if ($this->effectiveWarpDriveProduction === null) {
185
            $warpcore = $this->getWarpCoreSystemData();
186
            if ($warpcore === null) {
187
                $prod = ($this->get()->getReactorOutputCappedByReactorLoad() - $this->getEpsUsage()) / $flightcost;
188
            } else {
189
                $prod = (($this->get()->getReactorOutputCappedByReactorLoad() - $this->getEpsUsage()) * (1 - ($warpcore->getWarpCoreSplit() / 100))) / $flightcost;
190
            }
191
            if ($prod <= 0) {
192
                return (int) $prod;
193
            }
194
195
            $warpdrive = $this->getWarpDriveSystemData();
196
            if (
197
                $warpdrive !== null
198
                && $warpdrive->getWarpDrive() + $prod > $warpdrive->getMaxWarpDrive()
199
            ) {
200
                return $warpdrive->getMaxWarpDrive() - $warpdrive->getWarpDrive();
201
            }
202
            $this->effectiveWarpDriveProduction = (int) round($prod);
203
        }
204
        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...
205
    }
206
207
208
    public function getWarpcoreUsage(): int
209
    {
210
        return $this->getEffectiveEpsProduction() + $this->getEpsUsage() + $this->getEffectiveWarpDriveProduction();
211
    }
212
213
    public function setAlertState(int $alertState): ?string
214
    {
215
        $msg = $this->shipStateChanger->changeAlertState($this, $alertState);
216
        $this->epsUsage = $this->reloadEpsUsage();
217
218
        return $msg;
219
    }
220
221
    /**
222
     * highest damage first, then prio
223
     *
224
     * @return ShipSystemInterface[]
225
     */
226 8
    public function getDamagedSystems(): array
227
    {
228 8
        $damagedSystems = [];
229 8
        $prioArray = [];
230 8
        foreach ($this->get()->getSystems() as $system) {
231 2
            if ($system->getStatus() < 100) {
232 2
                $damagedSystems[] = $system;
233 2
                $prioArray[$system->getSystemType()] = $this->shipSystemManager->lookupSystem($system->getSystemType())->getPriority();
234
            }
235
        }
236
237
        // sort by damage and priority
238 8
        usort(
239 8
            $damagedSystems,
240 8
            function (ShipSystemInterface $a, ShipSystemInterface $b) use ($prioArray): int {
241 2
                if ($a->getStatus() === $b->getStatus()) {
242
                    return $prioArray[$b->getSystemType()] <=> $prioArray[$a->getSystemType()];
243
                }
244 2
                return ($a->getStatus() < $b->getStatus()) ? -1 : 1;
245 8
            }
246 8
        );
247
248 8
        return $damagedSystems;
249
    }
250
251
    public function isOwnedByCurrentUser(): bool
252
    {
253
        return $this->game->getUser() === $this->get()->getUser();
254
    }
255
256
    public function canLandOnCurrentColony(): bool
257
    {
258
        if ($this->get()->getRump()->getCommodity() === null) {
259
            return false;
260
        }
261
        if ($this->get()->isShuttle()) {
262
            return false;
263
        }
264
265
        $currentColony = $this->get()->getStarsystemMap() !== null ? $this->get()->getStarsystemMap()->getColony() : null;
266
267
        if ($currentColony === null) {
268
            return false;
269
        }
270
        if ($currentColony->getUser() !== $this->get()->getUser()) {
271
            return false;
272
        }
273
274
        return $this->colonyLibFactory
275
            ->createColonySurface($currentColony)
276
            ->hasAirfield();
277
    }
278
279
    public function canBeRepaired(): bool
280
    {
281
        if ($this->get()->getAlertState() !== ShipAlertStateEnum::ALERT_GREEN) {
282
            return false;
283
        }
284
285
        if ($this->get()->getShieldState()) {
286
            return false;
287
        }
288
289
        if ($this->get()->getCloakState()) {
290
            return false;
291
        }
292
293
        if (!empty($this->getDamagedSystems())) {
294
            return true;
295
        }
296
297
        return $this->get()->getHull() < $this->get()->getMaxHull();
298
    }
299
300 5
    public function getRepairDuration(): int
301
    {
302 5
        $ship = $this->get();
303
304 5
        $ticks = $this->getRepairTicks($ship);
305
306
        //check if repair station is active
307 5
        $colonyRepair = $this->colonyShipRepairRepository->getByShip($ship->getId());
308 5
        if ($colonyRepair !== null) {
309 2
            $isRepairStationBonus = $this->colonyFunctionManager->hasActiveFunction($colonyRepair->getColony(), BuildingEnum::BUILDING_FUNCTION_REPAIR_SHIPYARD);
310 2
            if ($isRepairStationBonus) {
311 1
                $ticks = (int)ceil($ticks / 2);
312
            }
313
        }
314
315 5
        return $ticks;
316
    }
317
318 3
    public function getRepairDurationPreview(): int
319
    {
320 3
        $ship = $this->get();
321
322 3
        $ticks = $this->getRepairTicks($ship);
323
324 3
        $colony = $ship->isOverColony();
325 3
        if ($colony !== null) {
326 2
            $isRepairStationBonus = $this->colonyFunctionManager->hasActiveFunction($colony, BuildingEnum::BUILDING_FUNCTION_REPAIR_SHIPYARD);
327 2
            if ($isRepairStationBonus) {
328 1
                $ticks = (int)ceil($ticks / 2);
329
            }
330
        }
331
332 3
        return $ticks;
333
    }
334
335 8
    private function getRepairTicks(ShipInterface $ship): int
336
    {
337 8
        $ticks = (int) ceil(($ship->getMaxHull() - $ship->getHull()) / $this->get()->getRepairRate());
338 8
        return max($ticks, (int) ceil(count($this->getDamagedSystems()) / 2));
339
    }
340
341
    public function getRepairCosts(): array
342
    {
343
        $neededSpareParts = 0;
344
        $neededSystemComponents = 0;
345
346
        $hull = $this->get()->getHull();
347
        $maxHull = $this->get()->getMaxHull();
348
349
        if ($hull < $maxHull) {
350
            $ticks = (int) ceil(($this->get()->getMaxHull() - $this->get()->getHull()) / $this->get()->getRepairRate());
351
            $neededSpareParts += ((int)($this->get()->getRepairRate() / RepairTaskEnum::HULL_HITPOINTS_PER_SPARE_PART)) * $ticks;
352
        }
353
354
        $damagedSystems = $this->getDamagedSystems();
355
        foreach ($damagedSystems as $system) {
356
            $systemLvl = $system->determineSystemLevel();
357
            $healingPercentage = (100 - $system->getStatus()) / 100;
358
359
            $neededSpareParts += (int)ceil($healingPercentage * RepairTaskEnum::SHIPYARD_PARTS_USAGE[$systemLvl][RepairTaskEnum::SPARE_PARTS_ONLY]);
360
            $neededSystemComponents += (int)ceil($healingPercentage * RepairTaskEnum::SHIPYARD_PARTS_USAGE[$systemLvl][RepairTaskEnum::SYSTEM_COMPONENTS_ONLY]);
361
        }
362
363
        return [
364
            new ShipRepairCost($neededSpareParts, CommodityTypeEnum::COMMODITY_SPARE_PART, CommodityTypeEnum::getDescription(CommodityTypeEnum::COMMODITY_SPARE_PART)),
365
            new ShipRepairCost($neededSystemComponents, CommodityTypeEnum::COMMODITY_SYSTEM_COMPONENT, CommodityTypeEnum::getDescription(CommodityTypeEnum::COMMODITY_SYSTEM_COMPONENT))
366
        ];
367
    }
368
369
    public function getPossibleTorpedoTypes(): array
370
    {
371
        if ($this->ship->hasShipSystem(ShipSystemTypeEnum::SYSTEM_TORPEDO_STORAGE)) {
372
            return $this->torpedoTypeRepository->getAll();
373
        }
374
375
        return $this->torpedoTypeRepository->getByLevel($this->ship->getRump()->getTorpedoLevel());
376
    }
377
378
    public function getTractoredShipWrapper(): ?ShipWrapperInterface
379
    {
380
        $tractoredShip = $this->get()->getTractoredShip();
381
        if ($tractoredShip === null) {
382
            return null;
383
        }
384
385
        return $this->shipWrapperFactory->wrapShip($tractoredShip);
386
    }
387
388
    public function getTractoringShipWrapper(): ?ShipWrapperInterface
389
    {
390
        $tractoringShip = $this->get()->getTractoringShip();
391
        if ($tractoringShip === null) {
392
            return null;
393
        }
394
395
        return $this->shipWrapperFactory->wrapShip($tractoringShip);
396
    }
397
398
    public function getDockedToShipWrapper(): ?ShipWrapperInterface
399
    {
400
        $dockedTo = $this->get()->getDockedTo();
401
        if ($dockedTo === null) {
402
            return null;
403
        }
404
405
        return $this->shipWrapperFactory->wrapShip($dockedTo);
406
    }
407
408
    public function getStateIconAndTitle(): ?array
409
    {
410
        $state = $this->get()->getState();
411
        $isBase = $this->get()->isBase();
412
        $repairDuration = $this->getRepairDuration();
413
414
        if ($state === ShipStateEnum::SHIP_STATE_REPAIR_PASSIVE) {
415
            return ['rep2', sprintf('%s wird repariert (noch %s Runden)', $isBase ? 'Station' : 'Schiff', $repairDuration)];
416
        }
417
        if ($state === ShipStateEnum::SHIP_STATE_REPAIR_ACTIVE) {
418
            return ['rep2', sprintf('%s repariert die Station', $isBase ? 'Stationscrew' : 'Schiffscrew')];
419
        }
420
        if ($state === ShipStateEnum::SHIP_STATE_ASTRO_FINALIZING) {
421
            return ['map1', 'Schiff kartographiert'];
422
        }
423
424
        return null;
425
    }
426
427
    public function canBeScrapped(): bool
428
    {
429
        $ship = $this->get();
430
431
        return $ship->isBase() && $ship->getState() !== ShipStateEnum::SHIP_STATE_UNDER_SCRAPPING;
432
    }
433
434 1
    public function getHullSystemData(): HullSystemData
435
    {
436 1
        $hullSystemData = $this->getSpecificShipSystem(
437 1
            ShipSystemTypeEnum::SYSTEM_HULL,
438 1
            HullSystemData::class
439 1
        );
440
441 1
        if ($hullSystemData === null) {
442
            throw new SystemNotFoundException('no hull installed?');
443
        }
444
445 1
        return $hullSystemData;
446
    }
447
448
    public function getShieldSystemData(): ?ShieldSystemData
449
    {
450
        return $this->getSpecificShipSystem(
451
            ShipSystemTypeEnum::SYSTEM_SHIELDS,
452
            ShieldSystemData::class
453
        );
454
    }
455
456 3
    public function getEpsSystemData(): ?EpsSystemData
457
    {
458 3
        return $this->getSpecificShipSystem(
459 3
            ShipSystemTypeEnum::SYSTEM_EPS,
460 3
            EpsSystemData::class
461 3
        );
462
    }
463
464
    public function getWarpCoreSystemData(): ?WarpCoreSystemData
465
    {
466
        return $this->getSpecificShipSystem(
467
            ShipSystemTypeEnum::SYSTEM_WARPCORE,
468
            WarpCoreSystemData::class
469
        );
470
    }
471
472
    public function getWarpDriveSystemData(): ?WarpDriveSystemData
473
    {
474
        return $this->getSpecificShipSystem(
475
            ShipSystemTypeEnum::SYSTEM_WARPDRIVE,
476
            WarpDriveSystemData::class
477
        );
478
    }
479
480
    public function getTrackerSystemData(): ?TrackerSystemData
481
    {
482
        return $this->getSpecificShipSystem(
483
            ShipSystemTypeEnum::SYSTEM_TRACKER,
484
            TrackerSystemData::class
485
        );
486
    }
487
488
    public function getWebEmitterSystemData(): ?WebEmitterSystemData
489
    {
490
        return $this->getSpecificShipSystem(
491
            ShipSystemTypeEnum::SYSTEM_THOLIAN_WEB,
492
            WebEmitterSystemData::class
493
        );
494
    }
495
496
    /**
497
     * @template T
498
     * @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...
499
     *
500
     * @return T|null
501
     */
502 4
    private function getSpecificShipSystem(int $systemType, string $className)
503
    {
504
        if (
505 4
            $systemType !== ShipSystemTypeEnum::SYSTEM_HULL
506 4
            && !$this->get()->hasShipSystem($systemType)
507
        ) {
508 1
            return null;
509
        }
510
511
        //add system to cache if not already deserialized
512 3
        if (!array_key_exists($systemType, $this->shipSystemDataCache)) {
513 3
            $systemData = $this->shipSystemDataFactory->createSystemData($systemType, $this->shipWrapperFactory);
514 3
            $systemData->setShip($this->get());
515
516 3
            $data = $systemType === ShipSystemTypeEnum::SYSTEM_HULL ? null : $this->get()->getShipSystem($systemType)->getData();
517
518 3
            if ($data === null) {
519 2
                $this->shipSystemDataCache[$systemType] = $systemData;
520
            } else {
521 1
                $this->shipSystemDataCache[$systemType] =
522 1
                    $this->jsonMapper->mapObjectFromString(
523 1
                        $data,
524 1
                        $systemData
525 1
                    );
526
            }
527
        }
528
529
        //load deserialized system from cache
530 3
        $cacheItem = $this->shipSystemDataCache[$systemType];
531 3
        if (!$cacheItem instanceof $className) {
532
            throw new RuntimeException('this should not happen');
533
        }
534
535 3
        return $cacheItem;
536
    }
537
}
538