1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace Stu\Module\Ship\Action\BoardShip; |
6
|
|
|
|
7
|
|
|
use request; |
8
|
|
|
use Stu\Component\Ship\Nbs\NbsUtilityInterface; |
9
|
|
|
use Stu\Component\Ship\ShipStateEnum; |
10
|
|
|
use Stu\Exception\SanityCheckException; |
11
|
|
|
use Stu\Module\Control\ActionControllerInterface; |
12
|
|
|
use Stu\Module\Control\GameControllerInterface; |
13
|
|
|
use Stu\Module\Control\StuRandom; |
14
|
|
|
use Stu\Module\Message\Lib\DistributedMessageSenderInterface; |
15
|
|
|
use Stu\Module\Message\Lib\PrivateMessageFolderSpecialEnum; |
16
|
|
|
use Stu\Module\Prestige\Lib\CreatePrestigeLogInterface; |
17
|
|
|
use Stu\Module\Ship\Lib\Auxiliary\ShipShutdownInterface; |
18
|
|
|
use Stu\Module\Ship\Lib\Battle\FightLibInterface; |
19
|
|
|
use Stu\Module\Ship\Lib\CloseCombat\CloseCombatUtilInterface; |
20
|
|
|
use Stu\Module\Ship\Lib\Interaction\ThreatReactionInterface; |
21
|
|
|
use Stu\Module\Ship\Lib\Interaction\InteractionCheckerInterface; |
22
|
|
|
use Stu\Module\Ship\Lib\Interaction\ShipTakeoverManagerInterface; |
23
|
|
|
use Stu\Module\Ship\Lib\Message\Message; |
24
|
|
|
use Stu\Module\Ship\Lib\Message\MessageCollection; |
25
|
|
|
use Stu\Module\Ship\Lib\Message\MessageCollectionInterface; |
26
|
|
|
use Stu\Module\Ship\Lib\ShipLoaderInterface; |
27
|
|
|
use Stu\Module\Ship\Lib\ShipStateChangerInterface; |
28
|
|
|
use Stu\Module\Ship\Lib\ShipWrapperInterface; |
29
|
|
|
use Stu\Module\Ship\View\Overview\Overview; |
30
|
|
|
use Stu\Module\Ship\View\ShowShip\ShowShip; |
31
|
|
|
use Stu\Orm\Entity\ShipCrewInterface; |
32
|
|
|
use Stu\Orm\Repository\CrewRepositoryInterface; |
33
|
|
|
use Stu\Orm\Repository\ShipCrewRepositoryInterface; |
34
|
|
|
use Stu\Orm\Repository\UserRepositoryInterface; |
35
|
|
|
|
36
|
|
|
final class BoardShip implements ActionControllerInterface |
37
|
|
|
{ |
38
|
|
|
public const ACTION_IDENTIFIER = 'B_BOARD_SHIP'; |
39
|
|
|
|
40
|
|
|
private CrewRepositoryInterface $crewRepository; |
41
|
|
|
|
42
|
|
|
private ShipCrewRepositoryInterface $shipCrewRepository; |
43
|
|
|
|
44
|
|
|
private UserRepositoryInterface $userRepository; |
45
|
|
|
|
46
|
|
|
private ShipLoaderInterface $shipLoader; |
47
|
|
|
|
48
|
|
|
private InteractionCheckerInterface $interactionChecker; |
49
|
|
|
|
50
|
|
|
private NbsUtilityInterface $nbsUtility; |
51
|
|
|
|
52
|
|
|
private CloseCombatUtilInterface $closeCombatUtil; |
53
|
|
|
|
54
|
|
|
private ThreatReactionInterface $threatReaction; |
55
|
|
|
|
56
|
|
|
private FightLibInterface $fightLib; |
57
|
|
|
|
58
|
|
|
private ShipStateChangerInterface $shipStateChanger; |
59
|
|
|
|
60
|
|
|
private ShipShutdownInterface $shipShutdown; |
61
|
|
|
|
62
|
|
|
private ShipTakeoverManagerInterface $shipTakeoverManager; |
63
|
|
|
|
64
|
|
|
private CreatePrestigeLogInterface $createPrestigeLog; |
65
|
|
|
|
66
|
|
|
private DistributedMessageSenderInterface $distributedMessageSender; |
67
|
|
|
|
68
|
|
|
private StuRandom $stuRandom; |
69
|
|
|
|
70
|
|
|
public function __construct( |
71
|
|
|
CrewRepositoryInterface $crewRepository, |
72
|
|
|
ShipCrewRepositoryInterface $shipCrewRepository, |
73
|
|
|
UserRepositoryInterface $userRepository, |
74
|
|
|
ShipLoaderInterface $shipLoader, |
75
|
|
|
InteractionCheckerInterface $interactionChecker, |
76
|
|
|
NbsUtilityInterface $nbsUtility, |
77
|
|
|
CloseCombatUtilInterface $closeCombatUtil, |
78
|
|
|
ThreatReactionInterface $threatReaction, |
79
|
|
|
FightLibInterface $fightLib, |
80
|
|
|
ShipStateChangerInterface $shipStateChanger, |
81
|
|
|
ShipShutdownInterface $shipShutdown, |
82
|
|
|
ShipTakeoverManagerInterface $shipTakeoverManager, |
83
|
|
|
CreatePrestigeLogInterface $createPrestigeLog, |
84
|
|
|
DistributedMessageSenderInterface $distributedMessageSender, |
85
|
|
|
StuRandom $stuRandom |
86
|
|
|
) { |
87
|
|
|
$this->crewRepository = $crewRepository; |
88
|
|
|
$this->shipCrewRepository = $shipCrewRepository; |
89
|
|
|
$this->userRepository = $userRepository; |
90
|
|
|
$this->shipLoader = $shipLoader; |
91
|
|
|
$this->interactionChecker = $interactionChecker; |
92
|
|
|
$this->nbsUtility = $nbsUtility; |
93
|
|
|
$this->closeCombatUtil = $closeCombatUtil; |
94
|
|
|
$this->threatReaction = $threatReaction; |
95
|
|
|
$this->fightLib = $fightLib; |
96
|
|
|
$this->shipStateChanger = $shipStateChanger; |
97
|
|
|
$this->shipShutdown = $shipShutdown; |
98
|
|
|
$this->shipTakeoverManager = $shipTakeoverManager; |
99
|
|
|
$this->createPrestigeLog = $createPrestigeLog; |
100
|
|
|
$this->distributedMessageSender = $distributedMessageSender; |
101
|
|
|
$this->stuRandom = $stuRandom; |
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
public function handle(GameControllerInterface $game): void |
105
|
|
|
{ |
106
|
|
|
$game->setView(ShowShip::VIEW_IDENTIFIER); |
107
|
|
|
|
108
|
|
|
$user = $game->getUser(); |
109
|
|
|
$userId = $user->getId(); |
110
|
|
|
|
111
|
|
|
$shipId = request::getIntFatal('id'); |
112
|
|
|
$targetId = request::getIntFatal('target'); |
113
|
|
|
|
114
|
|
|
$wrappers = $this->shipLoader->getWrappersBySourceAndUserAndTarget( |
115
|
|
|
$shipId, |
116
|
|
|
$userId, |
117
|
|
|
$targetId |
118
|
|
|
); |
119
|
|
|
|
120
|
|
|
$wrapper = $wrappers->getSource(); |
121
|
|
|
$ship = $wrapper->get(); |
122
|
|
|
|
123
|
|
|
$targetWrapper = $wrappers->getTarget(); |
124
|
|
|
if ($targetWrapper === null) { |
125
|
|
|
return; |
126
|
|
|
} |
127
|
|
|
$target = $targetWrapper->get(); |
128
|
|
|
|
129
|
|
|
if (!$target->isBoardingPossible()) { |
130
|
|
|
return; |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
if ($target->getUser() === $user) { |
134
|
|
|
return; |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
if ($target->getCrewCount() === 0) { |
138
|
|
|
return; |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
if ($targetWrapper->get()->getShieldState()) { |
142
|
|
|
return; |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
if ($target->getUser()->isVacationRequestOldEnough()) { |
146
|
|
|
$game->addInformation(_('Aktion nicht möglich, der Spieler befindet sich im Urlaubsmodus!')); |
147
|
|
|
return; |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
if (!$this->interactionChecker->checkPosition($target, $ship)) { |
151
|
|
|
throw new SanityCheckException('InteractionChecker->checkPosition failed', self::ACTION_IDENTIFIER); |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
if (!$this->fightLib->canAttackTarget($ship, $target, false)) { |
155
|
|
|
throw new SanityCheckException('Target cant be attacked', self::ACTION_IDENTIFIER); |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
if ($target->getCloakState() && !$this->nbsUtility->isTachyonActive($ship)) { |
159
|
|
|
throw new SanityCheckException('Attacked cloaked ship without active tachyon', self::ACTION_IDENTIFIER); |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
if ($ship->getCrewCount() === 0) { |
163
|
|
|
$game->addInformation(_('Aktion nicht möglich, keine Crew vorhanden!')); |
164
|
|
|
return; |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
$lastTakeover = $user->getLastBoarding(); |
168
|
|
|
if ( |
169
|
|
|
$lastTakeover !== null |
170
|
|
|
&& time() < $lastTakeover + ShipTakeoverManagerInterface::BOARDING_COOLDOWN_IN_SECONDS |
171
|
|
|
) { |
172
|
|
|
$game->addInformation(sprintf( |
173
|
|
|
'Enterkommando kann erst wieder um %s entsendet werden', |
174
|
|
|
date('H:i', $lastTakeover + ShipTakeoverManagerInterface::BOARDING_COOLDOWN_IN_SECONDS) |
175
|
|
|
)); |
176
|
|
|
return; |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
$epsSystemData = $wrapper->getEpsSystemData(); |
180
|
|
|
if ($epsSystemData === null || $epsSystemData->getEps() === 0) { |
181
|
|
|
$game->addInformation(_('Keine Energie vorhanden')); |
182
|
|
|
return; |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
if ($ship->isDisabled()) { |
186
|
|
|
$game->addInformation(_('Das Schiff ist kampfunfähig')); |
187
|
|
|
return; |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
if ($this->fightLib->isTargetOutsideFinishedTholianWeb($ship, $target)) { |
191
|
|
|
$game->addInformation(_('Das Ziel ist nicht mit im Energienetz gefangen')); |
192
|
|
|
return; |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
if ($ship->getShieldState()) { |
196
|
|
|
$game->addInformation(_("Die Schilde sind aktiviert")); |
197
|
|
|
return; |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
$neededPrestige = $this->shipTakeoverManager->getPrestigeForBoardingAttempt($target); |
201
|
|
|
if ($user->getPrestige() < $neededPrestige) { |
202
|
|
|
$game->addInformation(sprintf( |
203
|
|
|
'Nicht genügend Prestige vorhanden, benötigt wird: %d', |
204
|
|
|
$neededPrestige |
205
|
|
|
)); |
206
|
|
|
return; |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
$shipName = $ship->getName(); |
210
|
|
|
$targetName = $target->getName(); |
211
|
|
|
$targetUserId = $target->getUser()->getId(); |
212
|
|
|
|
213
|
|
|
$this->threatReaction->reactToThreat( |
214
|
|
|
$wrapper, |
215
|
|
|
$targetWrapper, |
216
|
|
|
sprintf( |
217
|
|
|
"Die %s versucht die %s in Sektor %s zu entern.", |
218
|
|
|
$shipName, |
219
|
|
|
$targetName, |
220
|
|
|
$ship->getSectorString() |
221
|
|
|
) |
222
|
|
|
); |
223
|
|
|
|
224
|
|
|
if ($ship->isDestroyed()) { |
225
|
|
|
$game->setView(Overview::VIEW_IDENTIFIER); |
226
|
|
|
return; |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
if ($target->getShieldState()) { |
230
|
|
|
$game->addInformationf("Die %s hat die Schilde aktiviert. Enterkommando kann nicht entsendet werden.", $targetName); |
231
|
|
|
return; |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
$combatGroupAttacker = $this->closeCombatUtil->getCombatGroup($ship); |
235
|
|
|
$combatGroupDefender = $this->closeCombatUtil->getCombatGroup($target); |
236
|
|
|
|
237
|
|
|
$messages = new MessageCollection(); |
238
|
|
|
$message = new Message($userId, $targetUserId, [sprintf( |
239
|
|
|
'Die %s entsendet ein Enterkommando auf die %s', |
240
|
|
|
$ship->getName(), |
241
|
|
|
$target->getName() |
242
|
|
|
)]); |
243
|
|
|
|
244
|
|
|
$messages->add($message); |
245
|
|
|
|
246
|
|
|
$this->shipStateChanger->changeShipState($targetWrapper, ShipStateEnum::SHIP_STATE_NONE); |
247
|
|
|
|
248
|
|
|
$this->createPrestigeLog->createLog( |
249
|
|
|
-$neededPrestige, |
250
|
|
|
sprintf( |
251
|
|
|
'-%d Prestige erhalten für einen Enterversuch auf die %s von Spieler %s', |
252
|
|
|
$neededPrestige, |
253
|
|
|
$target->getName(), |
254
|
|
|
$target->getUser()->getName() |
255
|
|
|
), |
256
|
|
|
$user, |
257
|
|
|
time() |
258
|
|
|
); |
259
|
|
|
|
260
|
|
|
while (!empty($combatGroupAttacker) && !empty($combatGroupDefender)) { |
261
|
|
|
$this->cycleKillRound( |
262
|
|
|
$combatGroupAttacker, |
263
|
|
|
$combatGroupDefender, |
264
|
|
|
$wrapper, |
265
|
|
|
$targetWrapper, |
266
|
|
|
$messages |
267
|
|
|
); |
268
|
|
|
} |
269
|
|
|
|
270
|
|
|
$message = new Message($userId, $targetUserId); |
271
|
|
|
$messages->add($message); |
272
|
|
|
|
273
|
|
|
if (empty($combatGroupAttacker)) { |
274
|
|
|
$message->add('Der Enterversuch ist gescheitert'); |
275
|
|
|
} else if ($target->getCrewAssignments()->isEmpty()) { |
276
|
|
|
$message->add(sprintf( |
277
|
|
|
'Die Crew der %s wurde getötet. Übernahme kann nun erfolgen.', |
278
|
|
|
$target->getName() |
279
|
|
|
)); |
280
|
|
|
} else { |
281
|
|
|
$message->add(sprintf( |
282
|
|
|
'Es leben noch %d Crewman auf der %s.', |
283
|
|
|
$target->getCrewCount(), |
284
|
|
|
$target->getName() |
285
|
|
|
)); |
286
|
|
|
} |
287
|
|
|
|
288
|
|
|
$user->setLastBoarding(time()); |
289
|
|
|
$this->userRepository->save($user); |
290
|
|
|
|
291
|
|
|
$this->sendPms( |
292
|
|
|
$userId, |
293
|
|
|
$ship->getSectorString(), |
294
|
|
|
$messages, |
295
|
|
|
$target->isBase() |
296
|
|
|
); |
297
|
|
|
|
298
|
|
|
$informations = $messages->getInformationDump(); |
299
|
|
|
|
300
|
|
|
$game->addInformationWrapper($informations); |
301
|
|
|
} |
302
|
|
|
|
303
|
|
|
/** |
304
|
|
|
* @param array<ShipCrewInterface> $attackers |
305
|
|
|
* @param array<ShipCrewInterface> $defenders |
306
|
|
|
*/ |
307
|
|
|
private function cycleKillRound( |
308
|
|
|
array &$attackers, |
309
|
|
|
array &$defenders, |
310
|
|
|
ShipWrapperInterface $wrapper, |
311
|
|
|
ShipWrapperInterface $targetWrapper, |
312
|
|
|
MessageCollectionInterface $messages |
313
|
|
|
): void { |
314
|
|
|
|
315
|
|
|
$ship = $wrapper->get(); |
316
|
|
|
$target = $targetWrapper->get(); |
317
|
|
|
|
318
|
|
|
$combatValueAttacker = $this->closeCombatUtil->getCombatValue($attackers, $ship->getUser()->getFaction()); |
319
|
|
|
$combatValueDefender = $this->closeCombatUtil->getCombatValue($defenders, $target->getUser()->getFaction()); |
320
|
|
|
|
321
|
|
|
$rand = $this->stuRandom->rand( |
322
|
|
|
0, |
323
|
|
|
$combatValueAttacker + $combatValueDefender |
324
|
|
|
); |
325
|
|
|
|
326
|
|
|
$isDeathOnDefenderSide = $rand <= $combatValueAttacker; |
327
|
|
|
if ($isDeathOnDefenderSide) { |
328
|
|
|
$killedShipCrew = $this->getKilledCrew($defenders, $targetWrapper); |
329
|
|
|
} else { |
330
|
|
|
$killedShipCrew = $this->getKilledCrew($attackers, $wrapper); |
331
|
|
|
} |
332
|
|
|
|
333
|
|
|
$message = new Message(); |
334
|
|
|
$message->add(sprintf( |
335
|
|
|
'%s %s von der %s wurde im Kampf getötet', |
336
|
|
|
$killedShipCrew->getCrew()->getTypeDescription(), |
337
|
|
|
$killedShipCrew->getCrew()->getName(), |
338
|
|
|
$isDeathOnDefenderSide ? $target->getName() : $ship->getName() |
339
|
|
|
)); |
340
|
|
|
$messages->add($message); |
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
/** |
344
|
|
|
* @param array<int, ShipCrewInterface> &$combatGroup |
345
|
|
|
*/ |
346
|
|
|
private function getKilledCrew(array &$combatGroup, ShipWrapperInterface $wrapper): ShipCrewInterface |
347
|
|
|
{ |
348
|
|
|
$keys = array_keys($combatGroup); |
349
|
|
|
shuffle($keys); |
350
|
|
|
|
351
|
|
|
$randomKey = current($keys); |
352
|
|
|
|
353
|
|
|
$killedShipCrew = $combatGroup[$randomKey]; |
354
|
|
|
unset($combatGroup[$randomKey]); |
355
|
|
|
|
356
|
|
|
$ship = $wrapper->get(); |
357
|
|
|
$ship->getCrewAssignments()->removeElement($killedShipCrew); |
358
|
|
|
$this->crewRepository->delete($killedShipCrew->getCrew()); |
359
|
|
|
$this->shipCrewRepository->delete($killedShipCrew); |
360
|
|
|
|
361
|
|
|
if ($ship->getCrewAssignments()->isEmpty()) { |
362
|
|
|
$this->shipShutdown->shutdown($wrapper); |
363
|
|
|
} |
364
|
|
|
|
365
|
|
|
return $killedShipCrew; |
366
|
|
|
} |
367
|
|
|
|
368
|
|
|
private function sendPms( |
369
|
|
|
int $userId, |
370
|
|
|
string $sectorString, |
371
|
|
|
MessageCollectionInterface $messageCollection, |
372
|
|
|
bool $isTargetBase |
373
|
|
|
): void { |
374
|
|
|
|
375
|
|
|
$header = sprintf( |
376
|
|
|
_("Enterversuch in Sektor %s"), |
377
|
|
|
$sectorString |
378
|
|
|
); |
379
|
|
|
|
380
|
|
|
$this->distributedMessageSender->distributeMessageCollection( |
381
|
|
|
$messageCollection, |
382
|
|
|
$userId, |
383
|
|
|
$isTargetBase ? PrivateMessageFolderSpecialEnum::PM_SPECIAL_STATION : PrivateMessageFolderSpecialEnum::PM_SPECIAL_SHIP, |
384
|
|
|
$header |
385
|
|
|
); |
386
|
|
|
} |
387
|
|
|
|
388
|
|
|
public function performSessionCheck(): bool |
389
|
|
|
{ |
390
|
|
|
return true; |
391
|
|
|
} |
392
|
|
|
} |
393
|
|
|
|