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

ShipTick::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 24
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 11
nc 1
nop 11
dl 0
loc 24
ccs 0
cts 12
cp 0
crap 2
rs 9.9
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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