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

AttackShip::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 8
c 0
b 0
f 0
nc 1
nop 8
dl 0
loc 18
ccs 0
cts 9
cp 0
crap 2
rs 10

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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