Passed
Push — master ( db7b85...c86096 )
by Nico
49:13 queued 24:57
created

AlertRedHelper::skipShipOnLocation()   B

Complexity

Conditions 10
Paths 6

Size

Total Lines 50
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 110

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
eloc 27
nc 6
nop 4
dl 0
loc 50
ccs 0
cts 29
cp 0
crap 110
rs 7.6666
c 1
b 0
f 0

How to fix   Complexity   

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
declare(strict_types=1);
4
5
namespace Stu\Module\Ship\Lib\Battle;
6
7
use Stu\Component\Player\PlayerRelationDeterminatorInterface;
8
use Stu\Lib\Information\InformationWrapper;
9
use Stu\Module\Control\GameControllerInterface;
10
use Stu\Module\Logging\LoggerUtilFactoryInterface;
11
use Stu\Module\Logging\LoggerUtilInterface;
12
use Stu\Module\Message\Lib\PrivateMessageFolderSpecialEnum;
13
use Stu\Module\Message\Lib\PrivateMessageSenderInterface;
14
use Stu\Module\PlayerSetting\Lib\UserEnum;
15
use Stu\Module\Ship\Lib\ShipWrapperFactoryInterface;
16
use Stu\Module\Ship\Lib\ShipWrapperInterface;
17
use Stu\Orm\Entity\ShipInterface;
18
use Stu\Orm\Entity\UserInterface;
19
use Stu\Orm\Repository\ShipRepositoryInterface;
20
21
//TODO create unit tests
22
final class AlertRedHelper implements AlertRedHelperInterface
23
{
24
    private ShipRepositoryInterface $shipRepository;
25
26
    private ShipAttackCycleInterface $shipAttackCycle;
27
28
    private ShipWrapperFactoryInterface $shipWrapperFactory;
29
30
    private PrivateMessageSenderInterface $privateMessageSender;
31
32
    private LoggerUtilInterface $loggerUtil;
33
34
    private PlayerRelationDeterminatorInterface $playerRelationDeterminator;
35
36
    public function __construct(
37
        PrivateMessageSenderInterface $privateMessageSender,
38
        ShipRepositoryInterface $shipRepository,
39
        ShipAttackCycleInterface $shipAttackCycle,
40
        ShipWrapperFactoryInterface $shipWrapperFactory,
41
        PlayerRelationDeterminatorInterface $playerRelationDeterminator,
42
        LoggerUtilFactoryInterface $loggerUtilFactory
43
    ) {
44
        $this->privateMessageSender = $privateMessageSender;
45
        $this->shipRepository = $shipRepository;
46
        $this->shipAttackCycle = $shipAttackCycle;
47
        $this->shipWrapperFactory = $shipWrapperFactory;
48
        $this->loggerUtil = $loggerUtilFactory->getLoggerUtil();
49
        $this->playerRelationDeterminator = $playerRelationDeterminator;
50
    }
51
52
    public function doItAll(
53
        ShipInterface $ship,
54
        ?GameControllerInterface $game = null,
55
        ?ShipInterface $tractoringShip = null
56
    ): ?InformationWrapper {
57
        //$this->loggerUtil->init('ARH', LoggerEnum::LEVEL_ERROR);
58
59
        $informations = new InformationWrapper();
60
61
        $shipsToShuffle = $this->checkForAlertRedShips($ship, $informations, $tractoringShip);
62
        shuffle($shipsToShuffle);
63
64
        $ships = $this->getShips($ship);
65
66
        foreach ($shipsToShuffle as $alertShip) {
67
            $leader = $this->getLeader($ships);
68
            if ($leader !== null) {
69
                $this->loggerUtil->log(sprintf('leaderId: %d', $leader->getId()));
70
            } else {
71
                $this->loggerUtil->log('leader is null');
72
            }
73
74
            if ($leader === null) {
75
                break;
76
            }
77
78
            $this->performAttackCycle($alertShip, $leader, $informations);
79
        }
80
81
        if ($game !== null) {
82
            $game->addInformationMergeDown($informations->getInformations());
83
            return null;
84
        } else {
85
            return $informations;
86
        }
87
    }
88
89
    /**
90
     * @param ShipWrapperInterface[] $wrappers
91
     */
92
    private function getLeader(array $wrappers): ?ShipInterface
93
    {
94
        $nonDestroyedShips = [];
95
96
        foreach ($wrappers as $wrapper) {
97
            $ship = $wrapper->get();
98
99
            if (!$ship->isDestroyed()) {
100
                if ($ship->isFleetLeader()) {
101
                    return $ship;
102
                }
103
                $nonDestroyedShips[] = $ship;
104
            }
105
        }
106
107
        if ($nonDestroyedShips !== []) {
108
            return current($nonDestroyedShips);
109
        }
110
111
        return null;
112
    }
113
114
    /**
115
     * @return ShipWrapperInterface[]
116
     */
117
    private function getShips(ShipInterface $leadShip): array
118
    {
119
        if ($leadShip->getFleet() !== null) {
120
            return $this->shipWrapperFactory->wrapShips($leadShip->getFleet()->getShips()->toArray());
121
        } else {
122
            return $this->shipWrapperFactory->wrapShips([$leadShip]);
123
        }
124
    }
125
126
    public function checkForAlertRedShips(
127
        ShipInterface $leadShip,
128
        InformationWrapper $informations,
129
        ?ShipInterface $tractoringShip = null
130
    ): array {
131
        $leadShipUser = $leadShip->getUser();
132
133
        if ($leadShipUser->getId() === UserEnum::USER_NOONE) {
134
            return [];
135
        }
136
        if ($this->allFleetShipsWarped($leadShip)) {
137
            return [];
138
        }
139
        if ($this->allFleetShipsCloaked($leadShip)) {
140
            return [];
141
        }
142
143
        $shipsToShuffle = [];
144
145
        // get ships inside or outside systems
146
        $shipsOnLocation = $this->shipRepository->getShipsForAlertRed($leadShip);
147
148
        $fleetIds = [];
149
        $fleetCount = 0;
150
        $singleShipCount = 0;
151
        $usersToInformAboutTrojanHorse = [];
152
153
        foreach ($shipsOnLocation as $shipOnLocation) {
154
155
            if ($this->skipShipOnLocation($leadShip, $shipOnLocation, $tractoringShip, $usersToInformAboutTrojanHorse)) {
156
                continue;
157
            }
158
159
            $fleet = $shipOnLocation->getFleet();
160
161
            if ($fleet === null) {
162
                $singleShipCount++;
163
                $shipsToShuffle[$shipOnLocation->getId()] = $shipOnLocation;
164
            } else {
165
                $fleetIdEintrag = $fleetIds[$fleet->getId()] ?? null;
166
                if ($fleetIdEintrag === null) {
167
                    $fleetCount++;
168
                    $shipsToShuffle[$fleet->getLeadShip()->getId()] = $fleet->getLeadShip();
169
                    $fleetIds[$fleet->getId()] = [];
170
                }
171
            }
172
        }
173
174
        $this->informAboutTrojanHorse($usersToInformAboutTrojanHorse);
175
        $this->addAlertRedInfo($leadShip, $singleShipCount, $fleetCount, $informations);
176
177
        return $shipsToShuffle;
178
    }
179
180
    /**
181
     * @param array<int, string> $users
182
     */
183
    private function informAboutTrojanHorse(array $users): void
184
    {
185
        foreach ($users as $userId => $txt) {
186
            $this->privateMessageSender->send(
187
                UserEnum::USER_NOONE,
188
                $userId,
189
                $txt
190
            );
191
        }
192
    }
193
194
    private function addAlertRedInfo(
195
        ShipInterface $leadShip,
196
        int $singleShipCount,
197
        int $fleetCount,
198
        InformationWrapper $informations
199
    ): void {
200
        if ($fleetCount == 1) {
201
            $informations->addInformation(sprintf(
202
                _('In Sektor %d|%d befindet sich 1 Flotte auf [b][color=red]Alarm-Rot![/color][/b]') . "\n",
203
                $leadShip->getPosX(),
204
                $leadShip->getPosY()
205
            ));
206
        }
207
        if ($fleetCount > 1) {
208
            $informations->addInformation(sprintf(
209
                _('In Sektor %d|%d befinden sich %d Flotte auf [b][color=red]Alarm-Rot![/color][/b]') . "\n",
210
                $leadShip->getPosX(),
211
                $leadShip->getPosY(),
212
                $fleetCount
213
            ));
214
        }
215
        if ($singleShipCount == 1) {
216
            $informations->addInformation(sprintf(
217
                _('In Sektor %d|%d befindet sich 1 Einzelschiff auf [b][color=red]Alarm-Rot![/color][/b]') . "\n",
218
                $leadShip->getPosX(),
219
                $leadShip->getPosY()
220
            ));
221
        }
222
        if ($singleShipCount > 1) {
223
            $informations->addInformation(sprintf(
224
                _('In Sektor %d|%d befinden sich %d Einzelschiffe auf [b][color=red]Alarm-Rot![/color][/b]') . "\n",
225
                $leadShip->getPosX(),
226
                $leadShip->getPosY(),
227
                $singleShipCount
228
            ));
229
        }
230
    }
231
232
    private function allFleetShipsWarped(ShipInterface $leadShip): bool
233
    {
234
        if ($leadShip->getFleet() !== null) {
235
            foreach ($leadShip->getFleet()->getShips() as $fleetShip) {
236
                if (!$fleetShip->getWarpState()) {
237
                    return false;
238
                }
239
            }
240
        } elseif (!$leadShip->getWarpState()) {
241
            return false;
242
        }
243
244
        return true;
245
    }
246
247
    private function allFleetShipsCloaked(ShipInterface $leadShip): bool
248
    {
249
        if ($leadShip->getFleet() !== null) {
250
            foreach ($leadShip->getFleet()->getShips() as $fleetShip) {
251
                if (!$fleetShip->getCloakState()) {
252
                    return false;
253
                }
254
            }
255
        } elseif (!$leadShip->getCloakState()) {
256
            return false;
257
        }
258
259
        return true;
260
    }
261
262
    /** @param array<int, string> $usersToInformAboutTrojanHorse */
263
    private function skipShipOnLocation(
264
        ShipInterface $leadShip,
265
        ShipInterface $shipOnLocation,
266
        ?ShipInterface $tractoringShip,
267
        array &$usersToInformAboutTrojanHorse
268
    ): bool {
269
        $user = $shipOnLocation->getUser();
270
        $leadShipUser = $leadShip->getUser();
271
272
        //ships of friends from tractoring ship dont attack
273
        if ($tractoringShip !== null && $this->playerRelationDeterminator->isFriend($user, $tractoringShip->getUser())) {
274
            $userId = $user->getId();
275
276
            if (
277
                !array_key_exists($userId, $usersToInformAboutTrojanHorse)
278
                && $user !== $leadShipUser
279
                && !$this->playerRelationDeterminator->isFriend($user, $leadShipUser)
280
            ) {
281
                $txt = sprintf(
282
                    _('Die %s von Spieler %s ist in Sektor %s eingeflogen und hat dabei die %s von Spieler %s gezogen'),
283
                    $tractoringShip->getName(),
284
                    $tractoringShip->getUser()->getName(),
285
                    $tractoringShip->getSectorString(),
286
                    $leadShip->getName(),
287
                    $leadShipUser->getName()
288
                );
289
                $usersToInformAboutTrojanHorse[$userId] = $txt;
290
            }
291
            return true;
292
        }
293
294
        //ships of friends dont attack
295
        if ($this->playerRelationDeterminator->isFriend($user, $leadShipUser)) {
296
            return true;
297
        }
298
299
        //ships in finished tholian web dont attack
300
        if ($shipOnLocation->getHoldingWeb() !== null && $shipOnLocation->getHoldingWeb()->isFinished()) {
301
            return true;
302
        }
303
304
        if ($this->skipDueToPirateProtection(
305
            $leadShipUser,
306
            $user,
307
            $shipOnLocation
308
        )) {
309
            return true;
310
        }
311
312
        return false;
313
    }
314
315
    private function skipDueToPirateProtection(UserInterface $leadShipUser, UserInterface $user, ShipInterface $shipOnLocation): bool
316
    {
317
        //pirates don't attack if user is protected
318
        $pirateWrath = $leadShipUser->getPirateWrath();
319
        if (
320
            $shipOnLocation->getUserId() === UserEnum::USER_NPC_KAZON
321
            && $pirateWrath !== null
322
            && $pirateWrath->getProtectionTimeout() > time()
323
        ) {
324
            return true;
325
        }
326
327
        //players don't attack pirates if protection is active
328
        $pirateWrath = $user->getPirateWrath();
329
        if (
330
            $leadShipUser->getId() === UserEnum::USER_NPC_KAZON
331
            && $pirateWrath !== null
332
            && $pirateWrath->getProtectionTimeout() > time()
333
        ) {
334
            return true;
335
        }
336
337
        return false;
338
    }
339
340
    public function performAttackCycle(
341
        ShipInterface $alertShip,
342
        ShipInterface $leadShip,
343
        InformationWrapper $informations,
344
        bool $isColonyDefense = false
345
    ): void {
346
        $alert_user_id = $alertShip->getUser()->getId();
347
        $lead_user_id = $leadShip->getUser()->getId();
348
        $isAlertShipBase = $alertShip->isBase();
349
350
        if ($alertShip->getFleet() !== null) {
351
            $attacker = [];
352
353
            // only uncloaked and unwarped ships enter fight
354
            foreach ($alertShip->getFleet()->getShips()->toArray() as $fleetShip) {
355
                if (!$fleetShip->getCloakState() && !$fleetShip->getWarpState()) {
356
                    $attacker[$fleetShip->getId()] = $fleetShip;
357
                }
358
            }
359
        } else {
360
            $attacker = [$alertShip->getId() => $alertShip];
361
        }
362
        if ($leadShip->isFleetLeader()) {
363
            $this->loggerUtil->log('leadShip is FleetLeader');
364
            $defender = [];
365
366
            // only uncloaked ships enter fight
367
            foreach ($leadShip->getFleet()->getShips()->toArray() as $defShip) {
368
                if (!$defShip->getCloakState()) {
369
                    $defender[$defShip->getId()] = $defShip;
370
                }
371
            }
372
            // if whole flying fleet cloaked, nothing happens
373
            if ($defender === []) {
374
                return;
375
            }
376
        } else {
377
            // if flying ship is cloaked, nothing happens
378
            if ($leadShip->getCloakState()) {
379
                return;
380
            }
381
382
            $defender = [$leadShip->getId() => $leadShip];
383
        }
384
385
        //$this->loggerUtil->log(sprintf('before_shipAttackCycle, attackerCount: %d, defenderCount: %d', count($attacker), count($defender)));
386
387
        $messageCollection = $this->shipAttackCycle->cycle(
388
            $this->shipWrapperFactory->wrapShips($attacker),
389
            $this->shipWrapperFactory->wrapShips($defender),
390
            false,
391
            true
392
        );
393
394
        $fightInformations = $messageCollection->getInformationDump();
395
396
        if (empty($fightInformations->getInformations())) {
397
            //$this->loggerUtil->init('ARH', LoggerEnum::LEVEL_ERROR);
398
            //$this->loggerUtil->log(sprintf('attackerCount: %d, defenderCount: %d', count($attacker), count($defender)));
399
        }
400
401
        $pm = sprintf(
402
            _("Eigene Schiffe auf [b][color=red]%s[/color][/b], Kampf in Sektor %s\n%s"),
403
            $isColonyDefense ? 'Kolonie-Verteidigung' : 'Alarm-Rot',
404
            $leadShip->getSectorString(),
405
            $fightInformations->getInformationsAsString()
406
        );
407
        $href = sprintf(_('ship.php?SHOW_SHIP=1&id=%d'), $alertShip->getId());
408
        $this->privateMessageSender->send(
409
            $lead_user_id,
410
            $alert_user_id,
411
            $pm,
412
            $isAlertShipBase ? PrivateMessageFolderSpecialEnum::PM_SPECIAL_STATION : PrivateMessageFolderSpecialEnum::PM_SPECIAL_SHIP,
413
            $alertShip->isDestroyed() ? null : $href
414
        );
415
        $pm = sprintf(
416
            _("Fremde Schiffe auf [b][color=red]%s[/color][/b], Kampf in Sektor %s\n%s"),
417
            $isColonyDefense ? 'Kolonie-Verteidigung' : 'Alarm-Rot',
418
            $leadShip->getSectorString(),
419
            $fightInformations->getInformationsAsString()
420
        );
421
        $this->privateMessageSender->send(
422
            $alert_user_id,
423
            $lead_user_id,
424
            $pm,
425
            PrivateMessageFolderSpecialEnum::PM_SPECIAL_SHIP
426
        );
427
428
        if ($leadShip->isDestroyed()) {
429
            $informations->addInformationWrapper($fightInformations);
430
            return;
431
        }
432
433
        $informations->addInformation(sprintf(
434
            _('[b][color=red]%s[/color][/b] fremder Schiffe auf Feld %d|%d, Angriff durchgeführt') . "\n",
435
            $isColonyDefense ? 'Kolonie-Verteidigung' : 'Alarm-Rot',
436
            $leadShip->getPosX(),
437
            $leadShip->getPosY()
438
        ));
439
        $informations->addInformationWrapper($fightInformations);
440
    }
441
}
442