Passed
Push — master ( f4068b...77f637 )
by Nico
40:27 queued 14:09
created

BoardShip::sendPms()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 2
eloc 8
nc 1
nop 4
dl 0
loc 17
ccs 0
cts 10
cp 0
crap 6
rs 10
c 1
b 0
f 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Stu\Module\Ship\Action\BoardShip;
6
7
use request;
8
use Stu\Component\Ship\Nbs\NbsUtilityInterface;
9
use Stu\Component\Ship\ShipStateEnum;
10
use Stu\Exception\SanityCheckException;
11
use Stu\Module\Control\ActionControllerInterface;
12
use Stu\Module\Control\GameControllerInterface;
13
use Stu\Module\Control\StuRandom;
14
use Stu\Module\Message\Lib\DistributedMessageSenderInterface;
15
use Stu\Module\Message\Lib\PrivateMessageFolderSpecialEnum;
16
use Stu\Module\Prestige\Lib\CreatePrestigeLogInterface;
17
use Stu\Module\Ship\Lib\Auxiliary\ShipShutdownInterface;
18
use Stu\Module\Ship\Lib\Battle\FightLibInterface;
19
use Stu\Module\Ship\Lib\CloseCombat\CloseCombatUtilInterface;
20
use Stu\Module\Ship\Lib\Interaction\ThreatReactionInterface;
21
use Stu\Module\Ship\Lib\Interaction\InteractionCheckerInterface;
22
use Stu\Module\Ship\Lib\Interaction\ShipTakeoverManagerInterface;
23
use Stu\Module\Ship\Lib\Message\Message;
24
use Stu\Module\Ship\Lib\Message\MessageCollection;
25
use Stu\Module\Ship\Lib\Message\MessageCollectionInterface;
26
use Stu\Module\Ship\Lib\ShipLoaderInterface;
27
use Stu\Module\Ship\Lib\ShipStateChangerInterface;
28
use Stu\Module\Ship\Lib\ShipWrapperInterface;
29
use Stu\Module\Ship\View\Overview\Overview;
30
use Stu\Module\Ship\View\ShowShip\ShowShip;
31
use Stu\Orm\Entity\ShipCrewInterface;
32
use Stu\Orm\Repository\CrewRepositoryInterface;
33
use Stu\Orm\Repository\ShipCrewRepositoryInterface;
34
use Stu\Orm\Repository\UserRepositoryInterface;
35
36
final class BoardShip implements ActionControllerInterface
37
{
38
    public const ACTION_IDENTIFIER = 'B_BOARD_SHIP';
39
40
    private CrewRepositoryInterface $crewRepository;
41
42
    private ShipCrewRepositoryInterface $shipCrewRepository;
43
44
    private UserRepositoryInterface $userRepository;
45
46
    private ShipLoaderInterface $shipLoader;
47
48
    private InteractionCheckerInterface $interactionChecker;
49
50
    private NbsUtilityInterface $nbsUtility;
51
52
    private CloseCombatUtilInterface $closeCombatUtil;
53
54
    private ThreatReactionInterface $threatReaction;
55
56
    private FightLibInterface $fightLib;
57
58
    private ShipStateChangerInterface $shipStateChanger;
59
60
    private ShipShutdownInterface $shipShutdown;
61
62
    private ShipTakeoverManagerInterface $shipTakeoverManager;
63
64
    private CreatePrestigeLogInterface $createPrestigeLog;
65
66
    private DistributedMessageSenderInterface $distributedMessageSender;
67
68
    private StuRandom $stuRandom;
69
70
    public function __construct(
71
        CrewRepositoryInterface $crewRepository,
72
        ShipCrewRepositoryInterface $shipCrewRepository,
73
        UserRepositoryInterface $userRepository,
74
        ShipLoaderInterface $shipLoader,
75
        InteractionCheckerInterface $interactionChecker,
76
        NbsUtilityInterface $nbsUtility,
77
        CloseCombatUtilInterface $closeCombatUtil,
78
        ThreatReactionInterface $threatReaction,
79
        FightLibInterface $fightLib,
80
        ShipStateChangerInterface $shipStateChanger,
81
        ShipShutdownInterface $shipShutdown,
82
        ShipTakeoverManagerInterface $shipTakeoverManager,
83
        CreatePrestigeLogInterface $createPrestigeLog,
84
        DistributedMessageSenderInterface $distributedMessageSender,
85
        StuRandom $stuRandom
86
    ) {
87
        $this->crewRepository = $crewRepository;
88
        $this->shipCrewRepository = $shipCrewRepository;
89
        $this->userRepository = $userRepository;
90
        $this->shipLoader = $shipLoader;
91
        $this->interactionChecker = $interactionChecker;
92
        $this->nbsUtility = $nbsUtility;
93
        $this->closeCombatUtil = $closeCombatUtil;
94
        $this->threatReaction = $threatReaction;
95
        $this->fightLib = $fightLib;
96
        $this->shipStateChanger = $shipStateChanger;
97
        $this->shipShutdown = $shipShutdown;
98
        $this->shipTakeoverManager = $shipTakeoverManager;
99
        $this->createPrestigeLog = $createPrestigeLog;
100
        $this->distributedMessageSender = $distributedMessageSender;
101
        $this->stuRandom = $stuRandom;
102
    }
103
104
    public function handle(GameControllerInterface $game): void
105
    {
106
        $game->setView(ShowShip::VIEW_IDENTIFIER);
107
108
        $user = $game->getUser();
109
        $userId = $user->getId();
110
111
        $shipId = request::getIntFatal('id');
112
        $targetId = request::getIntFatal('target');
113
114
        $wrappers = $this->shipLoader->getWrappersBySourceAndUserAndTarget(
115
            $shipId,
116
            $userId,
117
            $targetId
118
        );
119
120
        $wrapper = $wrappers->getSource();
121
        $ship = $wrapper->get();
122
123
        $targetWrapper = $wrappers->getTarget();
124
        if ($targetWrapper === null) {
125
            return;
126
        }
127
        $target = $targetWrapper->get();
128
129
        if (!$target->isBoardingPossible()) {
130
            return;
131
        }
132
133
        if ($target->getUser() === $user) {
134
            return;
135
        }
136
137
        if ($target->getCrewCount() === 0) {
138
            return;
139
        }
140
141
        if ($targetWrapper->get()->getShieldState()) {
142
            return;
143
        }
144
145
        if ($target->getUser()->isVacationRequestOldEnough()) {
146
            $game->addInformation(_('Aktion nicht möglich, der Spieler befindet sich im Urlaubsmodus!'));
147
            return;
148
        }
149
150
        if (!$this->interactionChecker->checkPosition($target, $ship)) {
151
            throw new SanityCheckException('InteractionChecker->checkPosition failed', self::ACTION_IDENTIFIER);
152
        }
153
154
        if (!$this->fightLib->canAttackTarget($ship, $target, false)) {
155
            throw new SanityCheckException('Target cant be attacked', self::ACTION_IDENTIFIER);
156
        }
157
158
        if ($target->getCloakState() && !$this->nbsUtility->isTachyonActive($ship)) {
159
            throw new SanityCheckException('Attacked cloaked ship without active tachyon', self::ACTION_IDENTIFIER);
160
        }
161
162
        if ($ship->getCrewCount() === 0) {
163
            $game->addInformation(_('Aktion nicht möglich, keine Crew vorhanden!'));
164
            return;
165
        }
166
167
        $lastTakeover = $user->getLastBoarding();
168
        if (
169
            $lastTakeover !== null
170
            && time() < $lastTakeover +  ShipTakeoverManagerInterface::BOARDING_COOLDOWN_IN_SECONDS
171
        ) {
172
            $game->addInformation(sprintf(
173
                'Enterkommando kann erst wieder um %s entsendet werden',
174
                date('H:i', $lastTakeover +  ShipTakeoverManagerInterface::BOARDING_COOLDOWN_IN_SECONDS)
175
            ));
176
            return;
177
        }
178
179
        $epsSystemData = $wrapper->getEpsSystemData();
180
        if ($epsSystemData === null || $epsSystemData->getEps() === 0) {
181
            $game->addInformation(_('Keine Energie vorhanden'));
182
            return;
183
        }
184
185
        if ($ship->isDisabled()) {
186
            $game->addInformation(_('Das Schiff ist kampfunfähig'));
187
            return;
188
        }
189
190
        if ($this->fightLib->isTargetOutsideFinishedTholianWeb($ship, $target)) {
191
            $game->addInformation(_('Das Ziel ist nicht mit im Energienetz gefangen'));
192
            return;
193
        }
194
195
        if ($ship->getShieldState()) {
196
            $game->addInformation(_("Die Schilde sind aktiviert"));
197
            return;
198
        }
199
200
        $neededPrestige = $this->shipTakeoverManager->getPrestigeForBoardingAttempt($target);
201
        if ($user->getPrestige() < $neededPrestige) {
202
            $game->addInformation(sprintf(
203
                'Nicht genügend Prestige vorhanden, benötigt wird: %d',
204
                $neededPrestige
205
            ));
206
            return;
207
        }
208
209
        $shipName = $ship->getName();
210
        $targetName = $target->getName();
211
        $targetUserId = $target->getUser()->getId();
212
213
        $this->threatReaction->reactToThreat(
214
            $wrapper,
215
            $targetWrapper,
216
            sprintf(
217
                "Die %s versucht die %s in Sektor %s zu entern.",
218
                $shipName,
219
                $targetName,
220
                $ship->getSectorString()
221
            )
222
        );
223
224
        if ($ship->isDestroyed()) {
225
            $game->setView(Overview::VIEW_IDENTIFIER);
226
            return;
227
        }
228
229
        if ($target->getShieldState()) {
230
            $game->addInformationf("Die %s hat die Schilde aktiviert. Enterkommando kann nicht entsendet werden.", $targetName);
231
            return;
232
        }
233
234
        $combatGroupAttacker = $this->closeCombatUtil->getCombatGroup($ship);
235
        $combatGroupDefender = $this->closeCombatUtil->getCombatGroup($target);
236
237
        $messages = new MessageCollection();
238
        $message = new Message($userId, $targetUserId, [sprintf(
239
            'Die %s entsendet ein Enterkommando auf die %s',
240
            $ship->getName(),
241
            $target->getName()
242
        )]);
243
244
        $messages->add($message);
245
246
        $this->shipStateChanger->changeShipState($targetWrapper, ShipStateEnum::SHIP_STATE_NONE);
247
248
        $this->createPrestigeLog->createLog(
249
            -$neededPrestige,
250
            sprintf(
251
                '-%d Prestige erhalten für einen Enterversuch auf die %s von Spieler %s',
252
                $neededPrestige,
253
                $target->getName(),
254
                $target->getUser()->getName()
255
            ),
256
            $user,
257
            time()
258
        );
259
260
        while (!empty($combatGroupAttacker) && !empty($combatGroupDefender)) {
261
            $this->cycleKillRound(
262
                $combatGroupAttacker,
263
                $combatGroupDefender,
264
                $wrapper,
265
                $targetWrapper,
266
                $messages
267
            );
268
        }
269
270
        $message = new Message($userId, $targetUserId);
271
        $messages->add($message);
272
273
        if (empty($combatGroupAttacker)) {
274
            $message->add('Der Enterversuch ist gescheitert');
275
        } else if ($target->getCrewAssignments()->isEmpty()) {
276
            $message->add(sprintf(
277
                'Die Crew der %s wurde getötet. Übernahme kann nun erfolgen.',
278
                $target->getName()
279
            ));
280
        } else {
281
            $message->add(sprintf(
282
                'Es leben noch %d Crewman auf der %s.',
283
                $target->getCrewCount(),
284
                $target->getName()
285
            ));
286
        }
287
288
        $user->setLastBoarding(time());
289
        $this->userRepository->save($user);
290
291
        $this->sendPms(
292
            $userId,
293
            $ship->getSectorString(),
294
            $messages,
295
            $target->isBase()
296
        );
297
298
        $informations = $messages->getInformationDump();
299
300
        $game->addInformationWrapper($informations);
301
    }
302
303
    /**
304
     * @param array<ShipCrewInterface> $attackers
305
     * @param array<ShipCrewInterface> $defenders
306
     */
307
    private function cycleKillRound(
308
        array &$attackers,
309
        array &$defenders,
310
        ShipWrapperInterface $wrapper,
311
        ShipWrapperInterface $targetWrapper,
312
        MessageCollectionInterface $messages
313
    ): void {
314
315
        $ship = $wrapper->get();
316
        $target = $targetWrapper->get();
317
318
        $combatValueAttacker = $this->closeCombatUtil->getCombatValue($attackers, $ship->getUser()->getFaction());
319
        $combatValueDefender = $this->closeCombatUtil->getCombatValue($defenders, $target->getUser()->getFaction());
320
321
        $rand = $this->stuRandom->rand(
322
            0,
323
            $combatValueAttacker + $combatValueDefender
324
        );
325
326
        $isDeathOnDefenderSide = $rand <= $combatValueAttacker;
327
        if ($isDeathOnDefenderSide) {
328
            $killedShipCrew = $this->getKilledCrew($defenders, $targetWrapper);
329
        } else {
330
            $killedShipCrew = $this->getKilledCrew($attackers, $wrapper);
331
        }
332
333
        $message = new Message();
334
        $message->add(sprintf(
335
            '%s %s von der %s wurde im Kampf getötet',
336
            $killedShipCrew->getCrew()->getTypeDescription(),
337
            $killedShipCrew->getCrew()->getName(),
338
            $isDeathOnDefenderSide ? $target->getName() : $ship->getName()
339
        ));
340
        $messages->add($message);
341
    }
342
343
    /**
344
     * @param array<int, ShipCrewInterface> &$combatGroup
345
     */
346
    private function getKilledCrew(array &$combatGroup, ShipWrapperInterface $wrapper): ShipCrewInterface
347
    {
348
        $keys = array_keys($combatGroup);
349
        shuffle($keys);
350
351
        $randomKey = current($keys);
352
353
        $killedShipCrew = $combatGroup[$randomKey];
354
        unset($combatGroup[$randomKey]);
355
356
        $ship = $wrapper->get();
357
        $ship->getCrewAssignments()->removeElement($killedShipCrew);
358
        $this->crewRepository->delete($killedShipCrew->getCrew());
359
        $this->shipCrewRepository->delete($killedShipCrew);
360
361
        if ($ship->getCrewAssignments()->isEmpty()) {
362
            $this->shipShutdown->shutdown($wrapper);
363
        }
364
365
        return $killedShipCrew;
366
    }
367
368
    private function sendPms(
369
        int $userId,
370
        string $sectorString,
371
        MessageCollectionInterface $messageCollection,
372
        bool $isTargetBase
373
    ): void {
374
375
        $header = sprintf(
376
            _("Enterversuch in Sektor %s"),
377
            $sectorString
378
        );
379
380
        $this->distributedMessageSender->distributeMessageCollection(
381
            $messageCollection,
382
            $userId,
383
            $isTargetBase ? PrivateMessageFolderSpecialEnum::PM_SPECIAL_STATION : PrivateMessageFolderSpecialEnum::PM_SPECIAL_SHIP,
384
            $header
385
        );
386
    }
387
388
    public function performSessionCheck(): bool
389
    {
390
        return true;
391
    }
392
}
393