Passed
Push — master ( 288b46...98b0e3 )
by Nico
31:30 queued 08:00
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\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