Passed
Push — master ( 0a90eb...366c47 )
by Nico
23:43
created

ShipTick::doRepairStation()   B

Complexity

Conditions 9
Paths 30

Size

Total Lines 70
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 90

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 39
nc 30
nop 1
dl 0
loc 70
ccs 0
cts 43
cp 0
crap 90
rs 7.7404
c 1
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Stu\Module\Tick\Ship;
4
5
use RuntimeException;
6
use Stu\Component\Ship\AstronomicalMappingEnum;
7
use Stu\Component\Ship\Repair\RepairUtilInterface;
8
use Stu\Component\Ship\ShipAlertStateEnum;
9
use Stu\Component\Ship\ShipStateEnum;
10
use Stu\Component\Ship\System\Data\EpsSystemData;
11
use Stu\Component\Ship\System\ShipSystemManagerInterface;
12
use Stu\Component\Ship\System\ShipSystemTypeEnum;
13
use Stu\Component\Station\StationUtilityInterface;
14
use Stu\Module\Control\GameControllerInterface;
15
use Stu\Module\Database\Lib\CreateDatabaseEntryInterface;
16
use Stu\Module\Message\Lib\PrivateMessageFolderSpecialEnum;
17
use Stu\Module\Message\Lib\PrivateMessageSenderInterface;
18
use Stu\Module\PlayerSetting\Lib\UserEnum;
19
use Stu\Module\Ship\Lib\AstroEntryLibInterface;
20
use Stu\Module\Ship\Lib\Crew\ShipLeaverInterface;
21
use Stu\Module\Ship\Lib\Interaction\ShipTakeoverManagerInterface;
22
use Stu\Module\Ship\Lib\ReactorWrapper;
23
use Stu\Module\Ship\Lib\ReactorWrapperInterface;
24
use Stu\Module\Ship\Lib\ShipWrapperInterface;
25
use Stu\Module\Ship\View\ShowShip\ShowShip;
26
use Stu\Orm\Entity\DatabaseEntryInterface;
27
use Stu\Orm\Entity\ShipInterface;
28
use Stu\Orm\Entity\UserInterface;
29
use Stu\Orm\Repository\DatabaseUserRepositoryInterface;
30
use Stu\Orm\Repository\ShipRepositoryInterface;
31
32
final class ShipTick implements ShipTickInterface
33
{
34
    private PrivateMessageSenderInterface $privateMessageSender;
35
36
    private ShipRepositoryInterface $shipRepository;
37
38
    private ShipSystemManagerInterface $shipSystemManager;
39
40
    private ShipLeaverInterface $shipLeaver;
41
42
    private GameControllerInterface $game;
43
44
    private AstroEntryLibInterface $astroEntryLib;
45
46
    private DatabaseUserRepositoryInterface $databaseUserRepository;
47
48
    private CreateDatabaseEntryInterface $createDatabaseEntry;
49
50
    private StationUtilityInterface $stationUtility;
51
52
    private RepairUtilInterface $repairUtil;
53
54
    private ShipTakeoverManagerInterface $shipTakeoverManager;
55
56
    /**
57
     * @var array<string>
58
     */
59
    private array $msg = [];
60
61
    public function __construct(
62
        PrivateMessageSenderInterface $privateMessageSender,
63
        ShipRepositoryInterface $shipRepository,
64
        ShipSystemManagerInterface $shipSystemManager,
65
        ShipLeaverInterface $shipLeaver,
66
        GameControllerInterface $game,
67
        AstroEntryLibInterface $astroEntryLib,
68
        DatabaseUserRepositoryInterface $databaseUserRepository,
69
        CreateDatabaseEntryInterface $createDatabaseEntry,
70
        StationUtilityInterface $stationUtility,
71
        RepairUtilInterface $repairUtil,
72
        ShipTakeoverManagerInterface $shipTakeoverManager
73
    ) {
74
        $this->privateMessageSender = $privateMessageSender;
75
        $this->shipRepository = $shipRepository;
76
        $this->shipSystemManager = $shipSystemManager;
77
        $this->shipLeaver = $shipLeaver;
78
        $this->game = $game;
79
        $this->astroEntryLib = $astroEntryLib;
80
        $this->databaseUserRepository = $databaseUserRepository;
81
        $this->createDatabaseEntry = $createDatabaseEntry;
82
        $this->stationUtility = $stationUtility;
83
        $this->repairUtil = $repairUtil;
84
        $this->shipTakeoverManager = $shipTakeoverManager;
85
    }
86
87
    public function work(ShipWrapperInterface $wrapper): void
88
    {
89
        $ship = $wrapper->get();
90
91
        // do construction stuff
92
        if ($this->doConstructionStuff($ship)) {
93
            $this->shipRepository->save($ship);
94
            $this->sendMessages($ship);
95
            return;
96
        }
97
98
        // repair station
99
        if ($ship->isBase() && $ship->getState() === ShipStateEnum::SHIP_STATE_REPAIR_PASSIVE) {
100
            $this->doRepairStation($wrapper);
101
        }
102
103
        // leave ship
104
        if ($ship->getCrewCount() > 0 && !$ship->isSystemHealthy(ShipSystemTypeEnum::SYSTEM_LIFE_SUPPORT)) {
105
            $this->msg[] = _('Die Lebenserhaltung ist ausgefallen:');
106
            $this->msg[] = $this->shipLeaver->evacuate($wrapper);
107
            $this->sendMessages($ship);
108
            return;
109
        }
110
111
        $eps = $wrapper->getEpsSystemData();
112
        $reactor = $wrapper->getReactorWrapper();
113
        if ($eps === null) {
114
            return;
115
        }
116
117
        $hasEnoughCrew = $ship->hasEnoughCrew();
118
119
        // not enough crew
120
        if (!$hasEnoughCrew) {
121
            $this->msg[] = _('Zu wenig Crew an Bord, Schiff ist nicht voll funktionsfähig! Systeme werden deaktiviert!');
122
123
            //deactivate all systems except life support
124
            foreach ($this->shipSystemManager->getActiveSystems($ship) as $system) {
125
                if ($system->getSystemType() != ShipSystemTypeEnum::SYSTEM_LIFE_SUPPORT) {
126
                    $this->shipSystemManager->deactivate($wrapper, $system->getSystemType(), true);
127
                }
128
            }
129
        }
130
131
        $reactorUsageForWarpdrive = $this->loadWarpdrive(
132
            $wrapper,
133
            $hasEnoughCrew
134
        );
135
136
        $availableEps = $this->getAvailableEps(
137
            $wrapper,
138
            $eps,
139
            $reactor,
140
            $hasEnoughCrew,
141
            $reactorUsageForWarpdrive
142
        );
143
144
        //try to save energy by reducing alert state
145
        if ($wrapper->getEpsUsage() > $availableEps) {
146
            $malus = $wrapper->getEpsUsage() - $availableEps;
147
            $alertUsage = $ship->getAlertState()->value - 1;
148
149
            if ($alertUsage > 0) {
150
                $preState = $ship->getAlertState();
151
                $reduce = (int)min($malus, $alertUsage);
152
153
                $ship->setAlertState(ShipAlertStateEnum::from($preState->value - $reduce));
154
                $this->msg[] = sprintf(
155
                    _('Wechsel von %s auf %s wegen Energiemangel'),
156
                    $preState->getDescription(),
157
                    $ship->getAlertState()->getDescription()
158
                );
159
            }
160
        }
161
162
        //try to save energy by deactivating systems from low to high priority
163
        if ($wrapper->getEpsUsage() > $availableEps) {
164
            $activeSystems = $this->shipSystemManager->getActiveSystems($ship, true);
165
166
            foreach ($activeSystems as $system) {
167
                $energyConsumption = $this->shipSystemManager->getEnergyConsumption($system->getSystemType());
168
                if ($energyConsumption < 1) {
169
                    continue;
170
                }
171
172
                //echo "- eps: ".$eps." - usage: ".$wrapper->getEpsUsage()."\n";
173
                if ($availableEps - $wrapper->getEpsUsage() - $energyConsumption < 0) {
174
                    //echo "-- hit system: ".$system->getDescription()."\n";
175
176
                    $this->shipSystemManager->deactivate($wrapper, $system->getSystemType(), true);
177
178
                    $wrapper->lowerEpsUsage($energyConsumption);
179
                    $this->msg[] = $system->getSystemType()->getDescription() . ' deaktiviert wegen Energiemangel';
180
181
                    if ($ship->getCrewCount() > 0 && $system->getSystemType() == ShipSystemTypeEnum::SYSTEM_LIFE_SUPPORT) {
182
                        $this->msg[] = _('Die Lebenserhaltung ist ausgefallen:');
183
                        $this->msg[] = $this->shipLeaver->evacuate($wrapper);
184
                        $this->sendMessages($ship);
185
                        return;
186
                    }
187
                }
188
                if ($wrapper->getEpsUsage() <= $availableEps) {
189
                    break;
190
                }
191
            }
192
        }
193
194
        $newEps = $availableEps - $wrapper->getEpsUsage();
195
        $batteryReload = $ship->isBase()
196
            && $eps->reloadBattery()
197
            && $newEps > $eps->getEps()
198
            ? min(
199
                (int) ceil($eps->getMaxBattery() / 10),
200
                $newEps - $eps->getEps(),
201
                $eps->getMaxBattery() - $eps->getBattery()
202
            ) : 0;
203
204
        $newEps -= $batteryReload;
205
        if ($newEps > $eps->getMaxEps()) {
206
            $newEps = $eps->getMaxEps();
207
        }
208
209
210
        $usedEnergy = $wrapper->getEpsUsage() + $batteryReload + ($newEps - $eps->getEps()) + $reactorUsageForWarpdrive;
211
212
        //echo "--- Generated Id ".$ship->getId()." - eps: ".$eps." - usage: ".$wrapper->getEpsUsage()." - old eps: ".$ship->getEps()." - wk: ".$wkuse."\n";
213
        $eps->setEps($newEps)
214
            ->setBattery($eps->getBattery() + $batteryReload)
215
            ->update();
216
217
        if ($usedEnergy > 0 && $reactor !== null) {
218
            $reactor->changeLoad(-$usedEnergy);
219
        }
220
221
        $this->checkForFinishedTakeover($ship);
222
        $this->checkForFinishedAstroMapping($ship);
223
224
        //update tracker status
225
        $this->doTrackerDeviceStuff($wrapper);
226
227
        $this->shipRepository->save($ship);
228
229
        $this->sendMessages($ship);
230
    }
231
232
    private function getAvailableEps(
233
        ShipWrapperInterface $wrapper,
234
        EpsSystemData $eps,
235
        ?ReactorWrapperInterface $reactor,
236
        bool $hasEnoughCrew,
237
        int $reactorUsageForWarpdrive
238
    ): int {
239
        if ($hasEnoughCrew && $reactor !== null) {
240
241
            return $eps->getEps() + $reactor->getEpsProduction() +  $this->getCarryOver(
242
                $wrapper,
243
                $reactor,
244
                $reactorUsageForWarpdrive
245
            );
246
        }
247
248
        return $eps->getEps();
249
    }
250
251
    private function getCarryOver(
252
        ShipWrapperInterface $wrapper,
253
        ReactorWrapperInterface $reactor,
254
        int $reactorUsageForWarpdrive
255
    ): int {
256
        $warpdrive = $wrapper->getWarpDriveSystemData();
257
        if ($warpdrive === null || !$warpdrive->getAutoCarryOver()) {
258
            return 0;
259
        }
260
261
        return $reactor->getOutputCappedByLoad() - $reactor->getEpsProduction() - $reactorUsageForWarpdrive;
262
    }
263
264
    private function loadWarpdrive(ShipWrapperInterface $wrapper, bool $hasEnoughCrew): int
265
    {
266
        if (!$hasEnoughCrew) {
267
            return 0;
268
        }
269
270
        $reactor = $wrapper->getReactorWrapper();
271
        $warpdrive = $wrapper->getWarpDriveSystemData();
272
        if ($warpdrive === null || $reactor === null) {
273
            return 0;
274
        }
275
276
        $effectiveWarpdriveProduction = $reactor->getEffectiveWarpDriveProduction();
277
        if ($effectiveWarpdriveProduction === 0) {
278
            return 0;
279
        }
280
281
        $currentLoad = $warpdrive->getWarpDrive();
282
283
        $warpdrive->setWarpDrive($currentLoad + $effectiveWarpdriveProduction)->update();
284
285
        return $effectiveWarpdriveProduction * $wrapper->get()->getRump()->getFlightECost();
286
    }
287
288
    private function doConstructionStuff(ShipInterface $ship): bool
289
    {
290
        $progress =  $this->stationUtility->getConstructionProgress($ship);
291
292
        if ($progress === null) {
293
            return false;
294
        }
295
296
        if ($progress->getRemainingTicks() === 0) {
297
            return false;
298
        }
299
300
        $isUnderConstruction = $ship->getState() === ShipStateEnum::SHIP_STATE_UNDER_CONSTRUCTION;
301
302
        if (!$this->stationUtility->hasEnoughDockedWorkbees($ship, $ship->getRump())) {
303
            $neededWorkbees = $isUnderConstruction ? $ship->getRump()->getNeededWorkbees() :
304
                (int)ceil($ship->getRump()->getNeededWorkbees() / 2);
305
306
            $this->msg[] = sprintf(
307
                _('Nicht genügend Workbees (%d/%d) angedockt um %s weiterführen zu können'),
308
                $this->stationUtility->getDockedWorkbeeCount($ship),
309
                $neededWorkbees,
310
                $isUnderConstruction ? 'den Bau' : 'die Demontage'
311
            );
312
            return true;
313
        }
314
315
        if ($progress->getRemainingTicks() === 1) {
316
            if ($isUnderConstruction) {
317
                $this->stationUtility->finishStation($ship, $progress);
318
            } else {
319
                $this->stationUtility->finishScrapping($ship, $progress);
320
            }
321
322
            $this->msg[] = sprintf(
323
                _('%s: %s bei %s fertiggestellt'),
324
                $ship->getRump()->getName(),
325
                $isUnderConstruction ? 'Bau' : 'Demontage',
326
                $ship->getSectorString()
327
            );
328
        } else {
329
            $this->stationUtility->reduceRemainingTicks($progress);
330
331
            if ($isUnderConstruction) {
332
                // raise hull
333
                $increase = intdiv($ship->getMaxHull(), 2 * $ship->getRump()->getBuildtime());
334
                $ship->setHuell($ship->getHull() + $increase);
335
            }
336
        }
337
338
        return true;
339
    }
340
341
    private function doRepairStation(ShipWrapperInterface $wrapper): void
342
    {
343
        $station = $wrapper->get();
344
345
        if (!$this->stationUtility->hasEnoughDockedWorkbees($station, $station->getRump())) {
346
            $neededWorkbees = (int)ceil($station->getRump()->getNeededWorkbees() / 5);
347
348
            $this->msg[] = sprintf(
349
                _('Nicht genügend Workbees (%d/%d) angedockt um die Reparatur weiterführen zu können'),
350
                $this->stationUtility->getDockedWorkbeeCount($station),
351
                $neededWorkbees
352
            );
353
            return;
354
        }
355
356
        $neededParts = $this->repairUtil->determineSpareParts($wrapper);
357
358
        // parts stored?
359
        if (!$this->repairUtil->enoughSparePartsOnEntity($neededParts, $station, $station)) {
360
            return;
361
        }
362
363
        //repair hull
364
        $station->setHuell($station->getHull() + $station->getRepairRate());
365
        if ($station->getHull() > $station->getMaxHull()) {
366
            $station->setHuell($station->getMaxHull());
367
        }
368
369
        //repair station systems
370
        $damagedSystems = $wrapper->getDamagedSystems();
371
        if (!empty($damagedSystems)) {
372
            $firstSystem = $damagedSystems[0];
373
            $firstSystem->setStatus(100);
374
375
            if ($station->getCrewCount() > 0) {
376
                $firstSystem->setMode($this->shipSystemManager->lookupSystem($firstSystem->getSystemType())->getDefaultMode());
377
            }
378
379
            // maximum of two systems get repaired
380
            if (count($damagedSystems) > 1) {
381
                $secondSystem = $damagedSystems[1];
382
                $secondSystem->setStatus(100);
383
384
                if ($station->getCrewCount() > 0) {
385
                    $secondSystem->setMode($this->shipSystemManager->lookupSystem($secondSystem->getSystemType())->getDefaultMode());
386
                }
387
            }
388
        }
389
390
        // consume spare parts
391
        $this->repairUtil->consumeSpareParts($neededParts, $station);
392
393
        if (!$wrapper->canBeRepaired()) {
394
            $station->setHuell($station->getMaxHull());
395
            $station->setState(ShipStateEnum::SHIP_STATE_NONE);
396
397
            $shipOwnerMessage = sprintf(
398
                "Die Reparatur der %s wurde in Sektor %s fertiggestellt",
399
                $station->getName(),
400
                $station->getSectorString()
401
            );
402
403
            $this->privateMessageSender->send(
404
                UserEnum::USER_NOONE,
405
                $station->getUser()->getId(),
406
                $shipOwnerMessage,
407
                PrivateMessageFolderSpecialEnum::PM_SPECIAL_STATION
408
            );
409
        }
410
        $this->shipRepository->save($station);
411
    }
412
413
    private function checkForFinishedTakeover(ShipInterface $ship): void
414
    {
415
        $takeover = $ship->getTakeoverActive();
416
        if ($takeover === null) {
417
            return;
418
        }
419
420
        if ($this->shipTakeoverManager->isTakeoverReady($takeover)) {
421
            $this->shipTakeoverManager->finishTakeover($takeover);
422
        }
423
    }
424
425
    private function checkForFinishedAstroMapping(ShipInterface $ship): void
426
    {
427
        /** @var null|DatabaseEntryInterface $databaseEntry */
428
        [$message, $databaseEntry] = $this->getDatabaseEntryForShipLocation($ship);
429
430
        if (
431
            $ship->getState() === ShipStateEnum::SHIP_STATE_ASTRO_FINALIZING
432
            && $databaseEntry !== null
433
            && $this->game->getCurrentRound()->getTurn() >= ($ship->getAstroStartTurn() + AstronomicalMappingEnum::TURNS_TO_FINISH)
434
        ) {
435
            $this->astroEntryLib->finish($ship);
436
437
            $this->msg[] = sprintf(
438
                _('Die Kartographierung %s wurde vollendet'),
439
                $message
440
            );
441
442
            $userId = $ship->getUser()->getId();
443
            $databaseEntryId = $databaseEntry->getId();
444
445
            if (!$this->databaseUserRepository->exists($userId, $databaseEntryId)) {
446
                $entry = $this->createDatabaseEntry->createDatabaseEntryForUser($ship->getUser(), $databaseEntryId);
447
448
                if ($entry !== null) {
449
                    $this->msg[] = sprintf(
450
                        _('Neuer Datenbankeintrag: %s (+%d Punkte)'),
451
                        $entry->getDescription(),
452
                        $entry->getCategory()->getPoints()
453
                    );
454
                }
455
            }
456
        }
457
    }
458
459
    /**
460
     * @return array{0: string|null, 1: DatabaseEntryInterface|null}
461
     */
462
    private function getDatabaseEntryForShipLocation(ShipInterface $ship): array
463
    {
464
        $system = $ship->getSystem();
465
        if ($system !== null) {
466
            return [
467
                'des Systems ' . $system->getName(),
468
                $system->getDatabaseEntry()
469
            ];
470
        }
471
472
        $mapRegion = $ship->getMapRegion();
473
        if ($mapRegion !== null) {
474
            return [
475
                'der Region ' . $mapRegion->getDescription(),
476
                $mapRegion->getDatabaseEntry()
477
            ];
478
        }
479
480
        return [null, null];
481
    }
482
483
    private function doTrackerDeviceStuff(ShipWrapperInterface $wrapper): void
484
    {
485
        $ship = $wrapper->get();
486
        $tracker = $wrapper->getTrackerSystemData();
487
488
        if ($tracker === null || $tracker->targetId === null) {
489
            return;
490
        }
491
492
        $targetWrapper = $tracker->getTargetWrapper();
493
        if ($targetWrapper === null) {
494
            throw new RuntimeException('should not happen');
495
        }
496
497
        $target = $targetWrapper->get();
498
        $remainingTicks = $tracker->getRemainingTicks();
499
500
        $reduceByTicks = max(1, (int)ceil((abs($ship->getCx() - $target->getCx()) +  abs($ship->getCy() - $target->getCy())) / 50));
501
502
        //reduce remaining ticks
503
        if ($remainingTicks > $reduceByTicks) {
504
            $tracker->setRemainingTicks($remainingTicks - $reduceByTicks)->update();
505
        } else {
506
            $this->shipSystemManager->deactivate($wrapper, ShipSystemTypeEnum::SYSTEM_TRACKER, true);
507
508
            if ($target->getUser() !== $ship->getUser()) {
509
                //send pm to target owner
510
                $this->privateMessageSender->send(
511
                    UserEnum::USER_NOONE,
512
                    $target->getUser()->getId(),
513
                    sprintf(
514
                        'Die Crew der %s hat einen Transponder gefunden und deaktiviert. %s',
515
                        $target->getName(),
516
                        $this->getTrackerSource($ship->getUser())
517
                    ),
518
                    PrivateMessageFolderSpecialEnum::PM_SPECIAL_SHIP
519
                );
520
521
                //send pm to tracker owner
522
                $this->privateMessageSender->send(
523
                    UserEnum::USER_NOONE,
524
                    $ship->getUser()->getId(),
525
                    sprintf(
526
                        'Die %s hat die Verbindung zum Tracker verloren',
527
                        $ship->getName()
528
                    ),
529
                    PrivateMessageFolderSpecialEnum::PM_SPECIAL_SHIP
530
                );
531
            }
532
        }
533
    }
534
535
    private function getTrackerSource(UserInterface $user): string
536
    {
537
        switch (random_int(0, 2)) {
538
            case 0:
539
                return _('Der Ursprung kann nicht identifiziert werden');
540
            case 1:
541
                return sprintf(_('Der Ursprung lässt auf %s schließen'), $user->getName());
542
            case 2:
543
                return sprintf(_('Der Ursprung lässt darauf schließen, dass er %s-Herkunft ist'), $user->getFaction()->getName());
544
            default:
545
                return '';
546
        }
547
    }
548
549
    private function sendMessages(ShipInterface $ship): void
550
    {
551
        if ($this->msg === []) {
552
            return;
553
        }
554
        $text = "Tickreport der " . $ship->getName() . "\n";
555
        foreach ($this->msg as $msg) {
556
            $text .= $msg . "\n";
557
        }
558
559
        $href = sprintf('ship.php?%s=1&id=%d', ShowShip::VIEW_IDENTIFIER, $ship->getId());
560
561
        $this->privateMessageSender->send(
562
            UserEnum::USER_NOONE,
563
            $ship->getUser()->getId(),
564
            $text,
565
            $ship->isBase() ? PrivateMessageFolderSpecialEnum::PM_SPECIAL_STATION : PrivateMessageFolderSpecialEnum::PM_SPECIAL_SHIP,
566
            $href
567
        );
568
569
        $this->msg = [];
570
    }
571
}
572