Passed
Push — master ( f4068b...77f637 )
by Nico
40:27 queued 14:09
created

AttackShip::isAttackOnSelfOrOwnFleet()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 4
eloc 7
nc 3
nop 2
dl 0
loc 14
ccs 0
cts 8
cp 0
crap 20
rs 10
c 1
b 1
f 0
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\Interaction\InteractionCheckerInterface;
20
use Stu\Module\Ship\Lib\ShipLoaderInterface;
21
use Stu\Module\Ship\Lib\ShipWrapperFactoryInterface;
22
use Stu\Module\Ship\Lib\ShipWrapperInterface;
23
use Stu\Module\Ship\View\ShowShip\ShowShip;
24
use Stu\Orm\Entity\ShipInterface;
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 DistributedMessageSenderInterface $distributedMessageSender;
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
        DistributedMessageSenderInterface $distributedMessageSender,
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->distributedMessageSender = $distributedMessageSender;
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
        if ($this->isTargetDestroyed($target)) {
102
            $game->setView(ShowShip::VIEW_IDENTIFIER);
103
            $game->addInformation(_('Das Ziel ist bereits zerstört'));
104
            return;
105
        }
106
107
        if (!$this->fightLib->canAttackTarget($ship, $target)) {
108
            throw new SanityCheckException('Target cant be attacked', self::ACTION_IDENTIFIER);
109
        }
110
111
        if ($target->getCloakState() && !$this->nbsUtility->isTachyonActive($ship)) {
112
            throw new SanityCheckException('Attacked cloaked ship without active tachyon', self::ACTION_IDENTIFIER);
113
        }
114
115
        $epsSystemData = $wrapper->getEpsSystemData();
116
        if ($epsSystemData === null || $epsSystemData->getEps() === 0) {
117
            $game->addInformation(_('Keine Energie vorhanden'));
118
            return;
119
        }
120
121
        if ($ship->isDisabled()) {
122
            $game->addInformation(_('Das Schiff ist kampfunfähig'));
123
            return;
124
        }
125
        if ($ship->getDockedTo() !== null) {
126
            $ship->setDockedTo(null);
127
        }
128
129
        $isTargetBase = $target->isBase();
130
131
        [$attacker, $defender, $isFleetFight, $isWebSituation] = $this->getAttackersAndDefenders($wrapper, $targetWrapper);
132
133
        $messageCollection = $this->shipAttackCycle->cycle($attacker, $defender, $isWebSituation);
134
135
        $this->sendPms(
136
            $userId,
137
            $ship->getSectorString(),
138
            $messageCollection,
139
            !$isWebSituation && $isTargetBase
140
        );
141
142
        $informations = $messageCollection->getInformationDump();
143
144
        if ($this->isActiveTractorShipWarped($ship, $target)) {
145
            //Alarm-Rot check for ship
146
            if (!$ship->isDestroyed()) {
147
                $informations->addInformationWrapper($this->alertRedHelper->doItAll($ship));
148
            }
149
150
            //Alarm-Rot check for traktor ship
151
            if (!$this->isTargetDestroyed($target)) {
152
                $informations->addInformationWrapper($this->alertRedHelper->doItAll($target));
153
            }
154
        }
155
156
        if ($ship->isDestroyed()) {
157
            $game->addInformationWrapper($informations);
158
            return;
159
        }
160
        $game->setView(ShowShip::VIEW_IDENTIFIER);
161
162
        if ($isFleetFight) {
163
            $game->addInformation(_("Angriff durchgeführt"));
164
            $game->setTemplateVar('FIGHT_RESULTS', $informations->getInformations());
165
        } else {
166
            $game->addInformationWrapper($informations);
167
        }
168
    }
169
170
    private function isTargetDestroyed(ShipInterface $ship): bool
171
    {
172
        return $ship->isDestroyed();
173
    }
174
175
    private function isActiveTractorShipWarped(ShipInterface $ship, ShipInterface $target): bool
176
    {
177
        $tractoringShip = $ship->getTractoringShip();
178
        if ($tractoringShip === null) {
179
            return false;
180
        }
181
182
        if ($tractoringShip !== $target) {
183
            return false;
184
        } else {
185
            return $target->getWarpState();
186
        }
187
    }
188
189
    private function sendPms(
190
        int $userId,
191
        string $sectorString,
192
        MessageCollectionInterface $messageCollection,
193
        bool $isTargetBase
194
    ): void {
195
196
        $header = sprintf(
197
            _("Kampf in Sektor %s"),
198
            $sectorString
199
        );
200
201
        $this->distributedMessageSender->distributeMessageCollection(
202
            $messageCollection,
203
            $userId,
204
            $isTargetBase ? PrivateMessageFolderSpecialEnum::PM_SPECIAL_STATION : PrivateMessageFolderSpecialEnum::PM_SPECIAL_SHIP,
205
            $header
206
        );
207
    }
208
209
    /**
210
     * @return array{0: array<int, ShipWrapperInterface>, 1: array<int, ShipWrapperInterface>, 2: bool, 3: bool}
211
     */
212
    private function getAttackersAndDefenders(ShipWrapperInterface $wrapper, ShipWrapperInterface $targetWrapper): array
213
    {
214
        $ship = $wrapper->get();
215
216
        [$attacker, $defender, $isFleetFight] = $this->fightLib->getAttackersAndDefenders($wrapper, $targetWrapper);
217
218
        $isWebSituation = $this->fightLib->isTargetOutsideFinishedTholianWeb($ship, $targetWrapper->get());
219
220
        //if in tholian web and defenders outside, reflect damage
221
        if ($isWebSituation) {
222
            $holdingWeb = $ship->getHoldingWeb();
223
            if ($holdingWeb === null) {
224
                throw new RuntimeException('this should not happen');
225
            }
226
227
            $defender = $this->shipWrapperFactory->wrapShips($holdingWeb->getCapturedShips()->toArray());
228
        }
229
230
        return [
231
            $attacker,
232
            $defender,
233
            $isFleetFight,
234
            $isWebSituation
235
        ];
236
    }
237
238
    public function performSessionCheck(): bool
239
    {
240
        return true;
241
    }
242
}
243