Passed
Push — dev ( 67ecf7...546614 )
by Janko
07:31
created

AttackShip::handle()   D

Complexity

Conditions 21
Paths 41

Size

Total Lines 114
Code Lines 68

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 462

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 21
eloc 68
nc 41
nop 1
dl 0
loc 114
ccs 0
cts 71
cp 0
crap 462
rs 4.1666
c 1
b 1
f 0

How to fix   Long Method    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\Action\AttackShip;
6
7
use request;
8
use RuntimeException;
9
use Stu\Component\Ship\Nbs\NbsUtilityInterface;
10
use Stu\Exception\SanityCheckException;
11
use Stu\Module\Control\ActionControllerInterface;
12
use Stu\Module\Control\GameControllerInterface;
13
use Stu\Module\Message\Lib\PrivateMessageFolderSpecialEnum;
14
use Stu\Module\Message\Lib\PrivateMessageSenderInterface;
15
use Stu\Module\Ship\Lib\Battle\AlertRedHelperInterface;
16
use Stu\Module\Ship\Lib\Battle\FightLibInterface;
17
use Stu\Module\Ship\Lib\Battle\Message\FightMessageCollectionInterface;
18
use Stu\Module\Ship\Lib\Battle\ShipAttackCycleInterface;
19
use Stu\Module\Ship\Lib\InteractionCheckerInterface;
20
use Stu\Module\Ship\Lib\ShipLoaderInterface;
21
use Stu\Module\Ship\Lib\ShipWrapperFactoryInterface;
22
use Stu\Module\Ship\View\ShowShip\ShowShip;
23
use Stu\Orm\Entity\ShipInterface;
24
use Stu\Orm\Entity\TholianWebInterface;
25
26
//TODO unit tests and request class
27
final class AttackShip implements ActionControllerInterface
28
{
29
    public const ACTION_IDENTIFIER = 'B_ATTACK_SHIP';
30
31
    private ShipLoaderInterface $shipLoader;
32
33
    private PrivateMessageSenderInterface $privateMessageSender;
34
35
    private ShipAttackCycleInterface $shipAttackCycle;
36
37
    private InteractionCheckerInterface $interactionChecker;
38
39
    private AlertRedHelperInterface $alertRedHelper;
40
41
    private NbsUtilityInterface $nbsUtility;
42
43
    private FightLibInterface $fightLib;
44
45
    private ShipWrapperFactoryInterface $shipWrapperFactory;
46
47
    public function __construct(
48
        ShipLoaderInterface $shipLoader,
49
        PrivateMessageSenderInterface $privateMessageSender,
50
        ShipAttackCycleInterface $shipAttackCycle,
51
        InteractionCheckerInterface $interactionChecker,
52
        AlertRedHelperInterface $alertRedHelper,
53
        NbsUtilityInterface $nbsUtility,
54
        FightLibInterface $fightLib,
55
        ShipWrapperFactoryInterface $shipWrapperFactory
56
    ) {
57
        $this->shipLoader = $shipLoader;
58
        $this->privateMessageSender = $privateMessageSender;
59
        $this->shipAttackCycle = $shipAttackCycle;
60
        $this->interactionChecker = $interactionChecker;
61
        $this->alertRedHelper = $alertRedHelper;
62
        $this->nbsUtility = $nbsUtility;
63
        $this->fightLib = $fightLib;
64
        $this->shipWrapperFactory = $shipWrapperFactory;
65
    }
66
67
    public function handle(GameControllerInterface $game): void
68
    {
69
        $userId = $game->getUser()->getId();
70
71
        $shipId = request::indInt('id');
72
        $targetId = request::postIntFatal('target');
73
74
        $wrappers = $this->shipLoader->getWrappersBySourceAndUserAndTarget(
75
            $shipId,
76
            $userId,
77
            $targetId
78
        );
79
80
        $wrapper = $wrappers->getSource();
81
        $ship = $wrapper->get();
82
83
        $targetWrapper = $wrappers->getTarget();
84
        if ($targetWrapper === null) {
85
            return;
86
        }
87
        $target = $targetWrapper->get();
88
89
        if ($target->getUser()->isVacationRequestOldEnough()) {
90
            $game->addInformation(_('Aktion nicht möglich, der Spieler befindet sich im Urlaubsmodus!'));
91
            return;
92
        }
93
94
        if (!$ship->hasEnoughCrew($game)) {
95
            return;
96
        }
97
        if (!$this->interactionChecker->checkPosition($target, $ship)) {
98
            throw new SanityCheckException('InteractionChecker->checkPosition failed', self::ACTION_IDENTIFIER);
99
        }
100
101
        // no attack on self or own fleet
102
        if ($this->isAttackOnSelfOrOwnFleet($ship, $target)) {
103
            return;
104
        }
105
106
        if ($this->isTargetDestroyed($target)) {
107
            $game->setView(ShowShip::VIEW_IDENTIFIER);
108
            $game->addInformation(_('Das Ziel ist bereits zerstört'));
109
            return;
110
        }
111
112
        if (!$this->fightLib->canAttackTarget($ship, $target)) {
113
            throw new SanityCheckException('Target cant be attacked', self::ACTION_IDENTIFIER);
114
        }
115
116
        if ($target->getCloakState() && !$this->nbsUtility->isTachyonActive($ship)) {
117
            throw new SanityCheckException('Attacked cloaked ship without active tachyon', self::ACTION_IDENTIFIER);
118
        }
119
120
        if ($target->getRump()->isTrumfield()) {
121
            return;
122
        }
123
124
        $epsSystemData = $wrapper->getEpsSystemData();
125
        if ($epsSystemData === null || $epsSystemData->getEps() === 0) {
126
            $game->addInformation(_('Keine Energie vorhanden'));
127
            return;
128
        }
129
130
        if ($ship->isDisabled()) {
131
            $game->addInformation(_('Das Schiff ist kampfunfähig'));
132
            return;
133
        }
134
        if ($ship->getDockedTo() !== null) {
135
            $ship->setDockedTo(null);
136
        }
137
138
        $isTargetBase = $target->isBase();
139
140
        [$attacker, $defender, $fleet, $isWebSituation] = $this->getAttackerDefender($ship, $target);
141
142
        $fightMessageCollection = $this->shipAttackCycle->cycle(
143
            $this->shipWrapperFactory->wrapShips($attacker),
144
            $this->shipWrapperFactory->wrapShips($defender),
145
            $isWebSituation
146
        );
147
148
        $this->sendPms(
149
            $userId,
150
            $ship->getSectorString(),
151
            $fightMessageCollection,
152
            !$isWebSituation && $isTargetBase
153
        );
154
155
        $informations = $fightMessageCollection->getInformationDump();
156
157
        if ($this->isActiveTractorShipWarped($ship, $target)) {
158
            //Alarm-Rot check for ship
159
            if (!$ship->isDestroyed()) {
160
                $informations->addInformationWrapper($this->alertRedHelper->doItAll($ship, null));
161
            }
162
163
            //Alarm-Rot check for traktor ship
164
            if (!$this->isTargetDestroyed($target)) {
165
                $informations->addInformationWrapper($this->alertRedHelper->doItAll($target, null));
166
            }
167
        }
168
169
        if ($ship->isDestroyed()) {
170
            $game->addInformationWrapper($informations);
171
            return;
172
        }
173
        $game->setView(ShowShip::VIEW_IDENTIFIER);
174
175
        if ($fleet) {
176
            $game->addInformation(_("Angriff durchgeführt"));
177
            $game->setTemplateVar('FIGHT_RESULTS', $informations->getInformations());
178
        } else {
179
            $game->addInformationWrapper($informations);
180
            $game->setTemplateVar('FIGHT_RESULTS', null);
181
        }
182
    }
183
184
    private function isTargetDestroyed(ShipInterface $ship): bool
185
    {
186
        return $ship->isDestroyed();
187
    }
188
189
    private function isActiveTractorShipWarped(ShipInterface $ship, ShipInterface $target): bool
190
    {
191
        $tractoringShip = $ship->getTractoringShip();
192
        if ($tractoringShip === null) {
193
            return false;
194
        }
195
196
        if ($tractoringShip !== $target) {
197
            return false;
198
        } else {
199
            return $target->getWarpState();
200
        }
201
    }
202
203
    private function isAttackOnSelfOrOwnFleet(ShipInterface $ship, ShipInterface $target): bool
204
    {
205
        if ($target === $ship) {
206
            return true;
207
        }
208
209
        $ownFleet = $ship->getFleet();
210
        $targetFleet = $target->getFleet();
211
212
        if ($ownFleet === null || $targetFleet === null) {
213
            return false;
214
        }
215
216
        return $targetFleet === $ownFleet;
217
    }
218
219
    private function sendPms(
220
        int $userId,
221
        string $sectorString,
222
        FightMessageCollectionInterface $messageCollection,
223
        bool $isTargetBase
224
    ): void {
225
        foreach ($messageCollection->getRecipientIds() as $recipientId) {
226
            $messageDump = $messageCollection->getInformationDump($recipientId);
227
228
            $pm = sprintf(
229
                _("Kampf in Sektor %s\n%s"),
230
                $sectorString,
231
                $messageDump->getInformationsAsString()
232
            );
233
234
            $this->privateMessageSender->send(
235
                $userId,
236
                $recipientId,
237
                $pm,
238
                $isTargetBase ? PrivateMessageFolderSpecialEnum::PM_SPECIAL_STATION : PrivateMessageFolderSpecialEnum::PM_SPECIAL_SHIP
239
            );
240
        }
241
    }
242
243
    /**
244
     * @return array{0: array<int, ShipInterface>, 1: array<int, ShipInterface>, 2: bool, 3: bool}
245
     */
246
    private function getAttackerDefender(ShipInterface $ship, ShipInterface $target): array
247
    {
248
        $fleet = false;
249
250
        if ($ship->isFleetLeader() && $ship->getFleet() !== null) {
251
            $attacker = $ship->getFleet()->getShips()->toArray();
252
            $fleet = true;
253
        } else {
254
            $attacker = [$ship->getId() => $ship];
255
        }
256
        if ($target->getFleet() !== null) {
257
            $defender = [];
258
259
            // only uncloaked defenders fight
260
            /**
261
             * @var ShipInterface $defShip
262
             */
263
            foreach ($target->getFleet()->getShips()->toArray() as $defShip) {
264
                if (!$defShip->getCloakState()) {
265
                    $defender[$defShip->getId()] = $defShip;
266
267
                    if (
268
                        $defShip->getDockedTo() !== null
269
                        && !$defShip->getDockedTo()->getUser()->isNpc()
270
                        && $defShip->getDockedTo()->hasActiveWeapon()
271
                    ) {
272
                        $defender[$defShip->getDockedTo()->getId()] = $defShip->getDockedTo();
273
                    }
274
                }
275
            }
276
277
            // if all defenders were cloaked, they obviously were scanned and enter the fight as a whole fleet
278
            if ($defender === []) {
279
                $defender = $target->getFleet()->getShips()->toArray();
280
            }
281
282
            $fleet = true;
283
        } else {
284
            $defender = [$target->getId() => $target];
285
286
            if (
287
                $target->getDockedTo() !== null
288
                && !$target->getDockedTo()->getUser()->isNpc()
289
                && $target->getDockedTo()->hasActiveWeapon()
290
            ) {
291
                $defender[$target->getDockedTo()->getId()] = $target->getDockedTo();
292
            }
293
        }
294
295
        $isWebSituation = false;
296
297
        //if in tholian web and defenders outside, reflect damage
298
        if ($this->isTargetingOutsideTholianWeb($ship, $target)) {
299
            $isWebSituation = true;
300
            $defender = [];
301
302
            $holdingWeb = $ship->getHoldingWeb();
303
            if ($holdingWeb === null) {
304
                throw new RuntimeException('this should not happen');
305
            }
306
307
            foreach ($holdingWeb->getCapturedShips() as $shipInWeb) {
308
                $defender[$shipInWeb->getId()] = $shipInWeb;
309
            }
310
        }
311
312
        return [
313
            $attacker,
314
            $defender,
315
            $fleet,
316
            $isWebSituation
317
        ];
318
    }
319
320
    private function isTargetingOutsideTholianWeb(ShipInterface $ship, ShipInterface $target): bool
321
    {
322
        return $ship->getHoldingWeb() !== null
323
            && $ship->getHoldingWeb()->isFinished()
324
            && ($target->getHoldingWeb() !== $ship->getHoldingWeb());
325
    }
326
327
    public function performSessionCheck(): bool
328
    {
329
        return true;
330
    }
331
}
332