Passed
Push — master ( 288b46...98b0e3 )
by Nico
31:30 queued 08:00
created

AttackShip::getAttackerDefender()   C

Complexity

Conditions 16
Paths 24

Size

Total Lines 71
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 272

Importance

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