Passed
Pull Request — master (#1686)
by Nico
31:24
created

ShipTick::getDatabaseEntryForShipLocation()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

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