Passed
Push — dev ( ac26ef...6b8aca )
by Nico
08:29
created

ShipWrapper::getEffectiveWarpDriveProduction()   B

Complexity

Conditions 8
Paths 14

Size

Total Lines 29
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 72

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 19
c 1
b 0
f 0
nc 14
nop 0
dl 0
loc 29
ccs 0
cts 17
cp 0
crap 72
rs 8.4444
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 $prod;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $prod could return the type double which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
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()) / $this->ship->getRump()->getFlightEcost();
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
    public function getWarpcoreUsage(): int
208
    {
209
        return $this->getEffectiveEpsProduction() + $this->getEpsUsage();
210
    }
211
212
    public function setAlertState(int $alertState): ?string
213
    {
214
        $msg = $this->shipStateChanger->changeAlertState($this, $alertState);
215
        $this->epsUsage = $this->reloadEpsUsage();
216
217
        return $msg;
218
    }
219
220
    /**
221
     * highest damage first, then prio
222
     *
223
     * @return ShipSystemInterface[]
224
     */
225 8
    public function getDamagedSystems(): array
226
    {
227 8
        $damagedSystems = [];
228 8
        $prioArray = [];
229 8
        foreach ($this->get()->getSystems() as $system) {
230 2
            if ($system->getStatus() < 100) {
231 2
                $damagedSystems[] = $system;
232 2
                $prioArray[$system->getSystemType()] = $this->shipSystemManager->lookupSystem($system->getSystemType())->getPriority();
233
            }
234
        }
235
236
        // sort by damage and priority
237 8
        usort(
238 8
            $damagedSystems,
239 8
            function (ShipSystemInterface $a, ShipSystemInterface $b) use ($prioArray): int {
240 2
                if ($a->getStatus() === $b->getStatus()) {
241
                    return $prioArray[$b->getSystemType()] <=> $prioArray[$a->getSystemType()];
242
                }
243 2
                return ($a->getStatus() < $b->getStatus()) ? -1 : 1;
244 8
            }
245 8
        );
246
247 8
        return $damagedSystems;
248
    }
249
250
    public function isOwnedByCurrentUser(): bool
251
    {
252
        return $this->game->getUser() === $this->get()->getUser();
253
    }
254
255
    public function canLandOnCurrentColony(): bool
256
    {
257
        if ($this->get()->getRump()->getCommodity() === null) {
258
            return false;
259
        }
260
        if ($this->get()->isShuttle()) {
261
            return false;
262
        }
263
264
        $currentColony = $this->get()->getStarsystemMap() !== null ? $this->get()->getStarsystemMap()->getColony() : null;
265
266
        if ($currentColony === null) {
267
            return false;
268
        }
269
        if ($currentColony->getUser() !== $this->get()->getUser()) {
270
            return false;
271
        }
272
273
        return $this->colonyLibFactory
274
            ->createColonySurface($currentColony)
275
            ->hasAirfield();
276
    }
277
278
    public function canBeRepaired(): bool
279
    {
280
        if ($this->get()->getAlertState() !== ShipAlertStateEnum::ALERT_GREEN) {
281
            return false;
282
        }
283
284
        if ($this->get()->getShieldState()) {
285
            return false;
286
        }
287
288
        if ($this->get()->getCloakState()) {
289
            return false;
290
        }
291
292
        if (!empty($this->getDamagedSystems())) {
293
            return true;
294
        }
295
296
        return $this->get()->getHull() < $this->get()->getMaxHull();
297
    }
298
299 5
    public function getRepairDuration(): int
300
    {
301 5
        $ship = $this->get();
302
303 5
        $ticks = $this->getRepairTicks($ship);
304
305
        //check if repair station is active
306 5
        $colonyRepair = $this->colonyShipRepairRepository->getByShip($ship->getId());
307 5
        if ($colonyRepair !== null) {
308 2
            $isRepairStationBonus = $this->colonyFunctionManager->hasActiveFunction($colonyRepair->getColony(), BuildingEnum::BUILDING_FUNCTION_REPAIR_SHIPYARD);
309 2
            if ($isRepairStationBonus) {
310 1
                $ticks = (int)ceil($ticks / 2);
311
            }
312
        }
313
314 5
        return $ticks;
315
    }
316
317 3
    public function getRepairDurationPreview(): int
318
    {
319 3
        $ship = $this->get();
320
321 3
        $ticks = $this->getRepairTicks($ship);
322
323 3
        $colony = $ship->isOverColony();
324 3
        if ($colony !== null) {
325 2
            $isRepairStationBonus = $this->colonyFunctionManager->hasActiveFunction($colony, BuildingEnum::BUILDING_FUNCTION_REPAIR_SHIPYARD);
326 2
            if ($isRepairStationBonus) {
327 1
                $ticks = (int)ceil($ticks / 2);
328
            }
329
        }
330
331 3
        return $ticks;
332
    }
333
334 8
    private function getRepairTicks(ShipInterface $ship): int
335
    {
336 8
        $ticks = (int) ceil(($ship->getMaxHull() - $ship->getHull()) / $this->get()->getRepairRate());
337 8
        return max($ticks, (int) ceil(count($this->getDamagedSystems()) / 2));
338
    }
339
340
    public function getRepairCosts(): array
341
    {
342
        $neededSpareParts = 0;
343
        $neededSystemComponents = 0;
344
345
        $hull = $this->get()->getHull();
346
        $maxHull = $this->get()->getMaxHull();
347
348
        if ($hull < $maxHull) {
349
            $ticks = (int) ceil(($this->get()->getMaxHull() - $this->get()->getHull()) / $this->get()->getRepairRate());
350
            $neededSpareParts += ((int)($this->get()->getRepairRate() / RepairTaskEnum::HULL_HITPOINTS_PER_SPARE_PART)) * $ticks;
351
        }
352
353
        $damagedSystems = $this->getDamagedSystems();
354
        foreach ($damagedSystems as $system) {
355
            $systemLvl = $system->determineSystemLevel();
356
            $healingPercentage = (100 - $system->getStatus()) / 100;
357
358
            $neededSpareParts += (int)ceil($healingPercentage * RepairTaskEnum::SHIPYARD_PARTS_USAGE[$systemLvl][RepairTaskEnum::SPARE_PARTS_ONLY]);
359
            $neededSystemComponents += (int)ceil($healingPercentage * RepairTaskEnum::SHIPYARD_PARTS_USAGE[$systemLvl][RepairTaskEnum::SYSTEM_COMPONENTS_ONLY]);
360
        }
361
362
        return [
363
            new ShipRepairCost($neededSpareParts, CommodityTypeEnum::COMMODITY_SPARE_PART, CommodityTypeEnum::getDescription(CommodityTypeEnum::COMMODITY_SPARE_PART)),
364
            new ShipRepairCost($neededSystemComponents, CommodityTypeEnum::COMMODITY_SYSTEM_COMPONENT, CommodityTypeEnum::getDescription(CommodityTypeEnum::COMMODITY_SYSTEM_COMPONENT))
365
        ];
366
    }
367
368
    public function getPossibleTorpedoTypes(): array
369
    {
370
        if ($this->ship->hasShipSystem(ShipSystemTypeEnum::SYSTEM_TORPEDO_STORAGE)) {
371
            return $this->torpedoTypeRepository->getAll();
372
        }
373
374
        return $this->torpedoTypeRepository->getByLevel($this->ship->getRump()->getTorpedoLevel());
375
    }
376
377
    public function getTractoredShipWrapper(): ?ShipWrapperInterface
378
    {
379
        $tractoredShip = $this->get()->getTractoredShip();
380
        if ($tractoredShip === null) {
381
            return null;
382
        }
383
384
        return $this->shipWrapperFactory->wrapShip($tractoredShip);
385
    }
386
387
    public function getTractoringShipWrapper(): ?ShipWrapperInterface
388
    {
389
        $tractoringShip = $this->get()->getTractoringShip();
390
        if ($tractoringShip === null) {
391
            return null;
392
        }
393
394
        return $this->shipWrapperFactory->wrapShip($tractoringShip);
395
    }
396
397
    public function getStateIconAndTitle(): ?array
398
    {
399
        $state = $this->get()->getState();
400
        $isBase = $this->get()->isBase();
401
        $repairDuration = $this->getRepairDuration();
402
403
        if ($state === ShipStateEnum::SHIP_STATE_REPAIR_PASSIVE) {
404
            return ['rep2', sprintf('%s wird repariert (noch %s Runden)', $isBase ? 'Station' : 'Schiff', $repairDuration)];
405
        }
406
        if ($state === ShipStateEnum::SHIP_STATE_REPAIR_ACTIVE) {
407
            return ['rep2', sprintf('%s repariert die Station', $isBase ? 'Stationscrew' : 'Schiffscrew')];
408
        }
409
        if ($state === ShipStateEnum::SHIP_STATE_SYSTEM_MAPPING) {
410
            return ['map1', 'Schiff kartographiert'];
411
        }
412
413
        return null;
414
    }
415
416
    public function canBeScrapped(): bool
417
    {
418
        $ship = $this->get();
419
420
        return $ship->isBase() && $ship->getState() !== ShipStateEnum::SHIP_STATE_UNDER_SCRAPPING;
421
    }
422
423 1
    public function getHullSystemData(): HullSystemData
424
    {
425 1
        $hullSystemData = $this->getSpecificShipSystem(
426 1
            ShipSystemTypeEnum::SYSTEM_HULL,
427 1
            HullSystemData::class
428 1
        );
429
430 1
        if ($hullSystemData === null) {
431
            throw new SystemNotFoundException('no hull installed?');
432
        }
433
434 1
        return $hullSystemData;
435
    }
436
437
    public function getShieldSystemData(): ?ShieldSystemData
438
    {
439
        return $this->getSpecificShipSystem(
440
            ShipSystemTypeEnum::SYSTEM_SHIELDS,
441
            ShieldSystemData::class
442
        );
443
    }
444
445 3
    public function getEpsSystemData(): ?EpsSystemData
446
    {
447 3
        return $this->getSpecificShipSystem(
448 3
            ShipSystemTypeEnum::SYSTEM_EPS,
449 3
            EpsSystemData::class
450 3
        );
451
    }
452
453
    public function getWarpCoreSystemData(): ?WarpCoreSystemData
454
    {
455
        return $this->getSpecificShipSystem(
456
            ShipSystemTypeEnum::SYSTEM_WARPCORE,
457
            WarpCoreSystemData::class
458
        );
459
    }
460
461
    public function getWarpDriveSystemData(): ?WarpDriveSystemData
462
    {
463
        return $this->getSpecificShipSystem(
464
            ShipSystemTypeEnum::SYSTEM_WARPDRIVE,
465
            WarpDriveSystemData::class
466
        );
467
    }
468
469
    public function getTrackerSystemData(): ?TrackerSystemData
470
    {
471
        return $this->getSpecificShipSystem(
472
            ShipSystemTypeEnum::SYSTEM_TRACKER,
473
            TrackerSystemData::class
474
        );
475
    }
476
477
    public function getWebEmitterSystemData(): ?WebEmitterSystemData
478
    {
479
        return $this->getSpecificShipSystem(
480
            ShipSystemTypeEnum::SYSTEM_THOLIAN_WEB,
481
            WebEmitterSystemData::class
482
        );
483
    }
484
485
    /**
486
     * @template T
487
     * @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...
488
     *
489
     * @return T|null
490
     */
491 4
    private function getSpecificShipSystem(int $systemType, string $className)
492
    {
493
        if (
494 4
            $systemType !== ShipSystemTypeEnum::SYSTEM_HULL
495 4
            && !$this->get()->hasShipSystem($systemType)
496
        ) {
497 1
            return null;
498
        }
499
500
        //add system to cache if not already deserialized
501 3
        if (!array_key_exists($systemType, $this->shipSystemDataCache)) {
502 3
            $systemData = $this->shipSystemDataFactory->createSystemData($systemType, $this->shipWrapperFactory);
503 3
            $systemData->setShip($this->get());
504
505 3
            $data = $systemType === ShipSystemTypeEnum::SYSTEM_HULL ? null : $this->get()->getShipSystem($systemType)->getData();
506
507 3
            if ($data === null) {
508 2
                $this->shipSystemDataCache[$systemType] = $systemData;
509
            } else {
510 1
                $this->shipSystemDataCache[$systemType] =
511 1
                    $this->jsonMapper->mapObjectFromString(
512 1
                        $data,
513 1
                        $systemData
514 1
                    );
515
            }
516
        }
517
518
        //load deserialized system from cache
519 3
        $cacheItem = $this->shipSystemDataCache[$systemType];
520 3
        if (!$cacheItem instanceof $className) {
521
            throw new RuntimeException('this should not happen');
522
        }
523
524 3
        return $cacheItem;
525
    }
526
}