We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.
1 | <?php declare(strict_types=1); |
||||
2 | |||||
3 | use Smr\BuyerRestriction; |
||||
4 | use Smr\DatabaseRecord; |
||||
5 | use Smr\Pages\Player\ShopWeaponProcessor; |
||||
6 | |||||
7 | /** |
||||
8 | * Defines a concrete realization of a weapon type for ships/planets. |
||||
9 | */ |
||||
10 | class SmrWeapon extends AbstractSmrCombatWeapon { |
||||
11 | |||||
12 | use Traits\RaceID; |
||||
13 | |||||
14 | protected const BONUS_DAMAGE = 15; // additive bonus |
||||
15 | protected const BONUS_ACCURACY = 4; // additive bonus |
||||
16 | |||||
17 | protected const HIGHEST_POWER_LEVEL = 5; // must track the highest power level in db |
||||
18 | |||||
19 | protected readonly SmrWeaponType $weaponType; |
||||
20 | protected bool $bonusAccuracy = false; // default |
||||
21 | protected bool $bonusDamage = false; // default |
||||
22 | protected bool $damageRollover = false; // fixed for all SmrWeapons |
||||
23 | |||||
24 | public static function getWeapon(int $weaponTypeID, DatabaseRecord $dbRecord = null): self { |
||||
25 | return new self($weaponTypeID, $dbRecord); |
||||
26 | } |
||||
27 | |||||
28 | protected function __construct( |
||||
29 | protected readonly int $weaponTypeID, |
||||
30 | DatabaseRecord $dbRecord = null |
||||
31 | ) { |
||||
32 | $this->weaponType = SmrWeaponType::getWeaponType($weaponTypeID, $dbRecord); |
||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
33 | $this->raceID = $this->weaponType->getRaceID(); |
||||
34 | } |
||||
35 | |||||
36 | public function hasBonusAccuracy(): bool { |
||||
37 | return $this->bonusAccuracy; |
||||
38 | } |
||||
39 | |||||
40 | public function setBonusAccuracy(bool $bonusAccuracy): void { |
||||
41 | $this->bonusAccuracy = $bonusAccuracy; |
||||
42 | } |
||||
43 | |||||
44 | public function hasBonusDamage(): bool { |
||||
45 | return $this->bonusDamage; |
||||
46 | } |
||||
47 | |||||
48 | public function setBonusDamage(bool $bonusDamage): void { |
||||
49 | $this->bonusDamage = $bonusDamage; |
||||
50 | } |
||||
51 | |||||
52 | private function hasEnhancements(): bool { |
||||
53 | return $this->getNumberOfEnhancements() > 0; |
||||
54 | } |
||||
55 | |||||
56 | private function getNumberOfEnhancements(): int { |
||||
57 | return (int)$this->bonusAccuracy + (int)$this->bonusDamage; |
||||
58 | } |
||||
59 | |||||
60 | /** |
||||
61 | * Return weapon name suitable for HTML display. |
||||
62 | * The name is displayed in green with pluses if enhancements are present. |
||||
63 | */ |
||||
64 | public function getName(): string { |
||||
65 | $name = $this->weaponType->getName(); |
||||
66 | if ($this->hasEnhancements()) { |
||||
67 | $name = '<span class="green">' . $name . str_repeat('+', $this->getNumberOfEnhancements()) . '</span>'; |
||||
68 | } |
||||
69 | return $name; |
||||
70 | } |
||||
71 | |||||
72 | /** |
||||
73 | * Return the weapon base accuracy. |
||||
74 | */ |
||||
75 | public function getBaseAccuracy(): int { |
||||
76 | $baseAccuracy = $this->weaponType->getAccuracy(); |
||||
77 | if ($this->bonusAccuracy) { |
||||
78 | $baseAccuracy += self::BONUS_ACCURACY; |
||||
79 | } |
||||
80 | return $baseAccuracy; |
||||
81 | } |
||||
82 | |||||
83 | /** |
||||
84 | * Return the weapon shield damage. |
||||
85 | */ |
||||
86 | public function getShieldDamage(): int { |
||||
87 | $shieldDamage = $this->weaponType->getShieldDamage(); |
||||
88 | if ($this->bonusDamage && $shieldDamage > 0) { |
||||
89 | $shieldDamage += self::BONUS_DAMAGE; |
||||
90 | } |
||||
91 | return $shieldDamage; |
||||
92 | } |
||||
93 | |||||
94 | /** |
||||
95 | * Return the weapon armour damage. |
||||
96 | */ |
||||
97 | public function getArmourDamage(): int { |
||||
98 | $armourDamage = $this->weaponType->getArmourDamage(); |
||||
99 | if ($this->bonusDamage && $armourDamage > 0) { |
||||
100 | $armourDamage += self::BONUS_DAMAGE; |
||||
101 | } |
||||
102 | return $armourDamage; |
||||
103 | } |
||||
104 | |||||
105 | public function getBuyHREF(SmrLocation $location): string { |
||||
106 | $container = new ShopWeaponProcessor($location->getTypeID(), $this); |
||||
107 | return $container->href(); |
||||
108 | } |
||||
109 | |||||
110 | public function getSellHREF(SmrLocation $location, int $orderID): string { |
||||
111 | $container = new ShopWeaponProcessor($location->getTypeID(), $this, $orderID); |
||||
112 | return $container->href(); |
||||
113 | } |
||||
114 | |||||
115 | public function getWeaponTypeID(): int { |
||||
116 | return $this->weaponTypeID; |
||||
117 | } |
||||
118 | |||||
119 | /** |
||||
120 | * Weapon cost is increased by 100% for each enhancement present |
||||
121 | */ |
||||
122 | public function getCost(): int { |
||||
123 | return $this->weaponType->getCost() * (1 + $this->getNumberOfEnhancements()); |
||||
124 | } |
||||
125 | |||||
126 | public function getPowerLevel(): int { |
||||
127 | return $this->weaponType->getPowerLevel(); |
||||
128 | } |
||||
129 | |||||
130 | public function getBuyerRestriction(): BuyerRestriction { |
||||
131 | return $this->weaponType->getBuyerRestriction(); |
||||
132 | } |
||||
133 | |||||
134 | /** |
||||
135 | * Ships are only allowed to equip one of each type of Unique weapon |
||||
136 | */ |
||||
137 | public function isUniqueType(): bool { |
||||
138 | return $this->getPowerLevel() === self::HIGHEST_POWER_LEVEL; |
||||
139 | } |
||||
140 | |||||
141 | protected function getWeightedRandomForPlayer(AbstractSmrPlayer $player): WeightedRandom { |
||||
142 | return WeightedRandom::getWeightedRandomForPlayer($player, 'Weapon', $this->getWeaponTypeID()); |
||||
143 | } |
||||
144 | |||||
145 | /** |
||||
146 | * Given $weaponAccuracy as a percent, decide if the weapon hits. |
||||
147 | */ |
||||
148 | protected function checkHit(AbstractSmrPlayer $player, float $weaponAccuracy): bool { |
||||
149 | // Skip weighting factor for guaranteed hits/misses. |
||||
150 | return match (true) { |
||||
151 | $weaponAccuracy >= 100 => true, |
||||
152 | $weaponAccuracy <= 0 => false, |
||||
153 | default => $this->getWeightedRandomForPlayer($player)->flipWeightedCoin($weaponAccuracy), |
||||
154 | }; |
||||
155 | } |
||||
156 | |||||
157 | public static function getPlayerLevelAccuracyMod(AbstractSmrPlayer $player): float { |
||||
158 | return ($player->getLevelID() * $player->getLevelID() / 60 + $player->getLevelID() / 2 + 2) / 100; |
||||
159 | } |
||||
160 | |||||
161 | public function getModifiedAccuracy(AbstractSmrPlayer $weaponPlayer): float { |
||||
162 | $modifiedAccuracy = $this->getBaseAccuracy(); |
||||
163 | $modifiedAccuracy += $this->getBaseAccuracy() * self::getPlayerLevelAccuracyMod($weaponPlayer); |
||||
164 | return $modifiedAccuracy; |
||||
165 | } |
||||
166 | |||||
167 | public function getModifiedAccuracyAgainstForces(AbstractSmrPlayer $weaponPlayer, SmrForce $forces): float { |
||||
0 ignored issues
–
show
The parameter
$forces is not used and could be removed.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() |
|||||
168 | return $this->getModifiedAccuracy($weaponPlayer); |
||||
169 | } |
||||
170 | |||||
171 | public function getModifiedAccuracyAgainstPort(AbstractSmrPlayer $weaponPlayer, SmrPort $port): float { |
||||
172 | $modifiedAccuracy = $this->getModifiedAccuracy($weaponPlayer); |
||||
173 | $modifiedAccuracy -= $this->getBaseAccuracy() * $port->getLevel() / 50; |
||||
174 | return $modifiedAccuracy; |
||||
175 | } |
||||
176 | |||||
177 | public function getModifiedAccuracyAgainstPlanet(AbstractSmrPlayer $weaponPlayer, SmrPlanet $planet): float { |
||||
178 | $modifiedAccuracy = $this->getModifiedAccuracy($weaponPlayer); |
||||
179 | $modifiedAccuracy -= $this->getBaseAccuracy() * $planet->getLevel() / 350; |
||||
180 | return $modifiedAccuracy; |
||||
181 | } |
||||
182 | |||||
183 | public function getModifiedAccuracyAgainstPlayer(AbstractSmrPlayer $weaponPlayer, AbstractSmrPlayer $targetPlayer): float { |
||||
184 | $modifiedAccuracy = $this->getModifiedAccuracy($weaponPlayer); |
||||
185 | $modifiedAccuracy -= $this->getBaseAccuracy() * self::getPlayerLevelAccuracyMod($targetPlayer) / 2; |
||||
186 | |||||
187 | $weaponShip = $weaponPlayer->getShip(); |
||||
188 | $targetShip = $targetPlayer->getShip(); |
||||
189 | $mrDiff = $targetShip->getMR() - $weaponShip->getMR(); |
||||
190 | if ($mrDiff > 0) { |
||||
191 | $modifiedAccuracy -= $this->getBaseAccuracy() * ($mrDiff / MR_FACTOR) / 100; |
||||
192 | } |
||||
193 | |||||
194 | return $modifiedAccuracy; |
||||
195 | } |
||||
196 | |||||
197 | public function getModifiedPortAccuracy(AbstractSmrPort $port): float { |
||||
0 ignored issues
–
show
The parameter
$port is not used and could be removed.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() |
|||||
198 | return $this->getBaseAccuracy(); |
||||
199 | } |
||||
200 | |||||
201 | public function getModifiedPortAccuracyAgainstPlayer(AbstractSmrPort $port, AbstractSmrPlayer $targetPlayer): float { |
||||
202 | $modifiedAccuracy = $this->getModifiedPortAccuracy($port); |
||||
203 | $modifiedAccuracy -= $this->getBaseAccuracy() * self::getPlayerLevelAccuracyMod($targetPlayer); |
||||
204 | return $modifiedAccuracy; |
||||
205 | } |
||||
206 | |||||
207 | public function getModifiedPlanetAccuracy(SmrPlanet $planet): float { |
||||
208 | $modifiedAccuracy = $this->getBaseAccuracy(); |
||||
209 | if ($this->getWeaponTypeID() == WEAPON_PLANET_TURRET) { |
||||
210 | $modifiedAccuracy += $planet->getLevel() / 2; |
||||
211 | } else { |
||||
212 | $modifiedAccuracy += $planet->getAccuracyBonus(); |
||||
213 | } |
||||
214 | return $modifiedAccuracy; |
||||
215 | } |
||||
216 | |||||
217 | public function getModifiedPlanetAccuracyAgainstPlayer(SmrPlanet $planet, AbstractSmrPlayer $targetPlayer): float { |
||||
218 | $modifiedAccuracy = $this->getModifiedPlanetAccuracy($planet); |
||||
219 | $modifiedAccuracy -= $this->getBaseAccuracy() * self::getPlayerLevelAccuracyMod($targetPlayer); |
||||
220 | return $modifiedAccuracy; |
||||
221 | } |
||||
222 | |||||
223 | public function getModifiedDamageAgainstForces(AbstractSmrPlayer $weaponPlayer, SmrForce $forces): array { |
||||
224 | if (!$this->canShootForces()) { |
||||
225 | // If we can't shoot forces then just return a damageless array and don't waste resources calculated any damage mods. |
||||
226 | return ['Shield' => 0, 'Armour' => 0, 'Rollover' => $this->isDamageRollover()]; |
||||
227 | } |
||||
228 | return $this->getDamage(); |
||||
229 | } |
||||
230 | |||||
231 | public function getModifiedDamageAgainstPort(AbstractSmrPlayer $weaponPlayer, SmrPort $port): array { |
||||
232 | if (!$this->canShootPorts()) { |
||||
233 | // If we can't shoot forces then just return a damageless array and don't waste resources calculated any damage mods. |
||||
234 | return ['Shield' => 0, 'Armour' => 0, 'Rollover' => $this->isDamageRollover()]; |
||||
235 | } |
||||
236 | return $this->getDamage(); |
||||
237 | } |
||||
238 | |||||
239 | public function getModifiedDamageAgainstPlanet(AbstractSmrPlayer $weaponPlayer, SmrPlanet $planet): array { |
||||
240 | if (!$this->canShootPlanets()) { |
||||
241 | // If we can't shoot forces then just return a damageless array and don't waste resources calculated any damage mods. |
||||
242 | return ['Shield' => 0, 'Armour' => 0, 'Rollover' => $this->isDamageRollover()]; |
||||
243 | } |
||||
244 | $damage = $this->getDamage(); |
||||
245 | |||||
246 | $planetMod = self::PLANET_DAMAGE_MOD; |
||||
247 | $damage['Shield'] = ICeil($damage['Shield'] * $planetMod); |
||||
0 ignored issues
–
show
The function
ICeil was not found. Maybe you did not declare it correctly or list all dependencies?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
248 | $damage['Armour'] = ICeil($damage['Armour'] * $planetMod); |
||||
249 | |||||
250 | return $damage; |
||||
251 | } |
||||
252 | |||||
253 | public function getModifiedForceDamageAgainstPlayer(SmrForce $forces, AbstractSmrPlayer $targetPlayer): never { |
||||
254 | throw new Exception('This weapon should not be used in this context'); |
||||
255 | } |
||||
256 | |||||
257 | public function getModifiedDamageAgainstPlayer(AbstractSmrPlayer $weaponPlayer, AbstractSmrPlayer $targetPlayer): array { |
||||
258 | if (!$this->canShootTraders()) { // If we can't shoot traders then just return a damageless array and don't waste resources calculating any damage mods. |
||||
259 | return ['Shield' => 0, 'Armour' => 0, 'Rollover' => $this->isDamageRollover()]; |
||||
260 | } |
||||
261 | return $this->getDamage(); |
||||
262 | } |
||||
263 | |||||
264 | public function getModifiedPortDamageAgainstPlayer(AbstractSmrPort $port, AbstractSmrPlayer $targetPlayer): array { |
||||
265 | if (!$this->canShootTraders()) { // If we can't shoot traders then just return a damageless array and don't waste resources calculating any damage mods. |
||||
266 | return ['Shield' => 0, 'Armour' => 0, 'Rollover' => $this->isDamageRollover()]; |
||||
267 | } |
||||
268 | return $this->getDamage(); |
||||
269 | } |
||||
270 | |||||
271 | public function getModifiedPlanetDamageAgainstPlayer(SmrPlanet $planet, AbstractSmrPlayer $targetPlayer): array { |
||||
272 | if (!$this->canShootTraders()) { // If we can't shoot traders then just return a damageless array and don't waste resources calculating any damage mods. |
||||
273 | return ['Shield' => 0, 'Armour' => 0, 'Rollover' => $this->isDamageRollover()]; |
||||
274 | } |
||||
275 | return $this->getDamage(); |
||||
276 | } |
||||
277 | |||||
278 | public function shootForces(AbstractSmrPlayer $weaponPlayer, SmrForce $forces): array { |
||||
279 | $return = ['Weapon' => $this, 'TargetForces' => $forces, 'Hit' => false]; |
||||
280 | $modifiedAccuracy = $this->getModifiedAccuracyAgainstForces($weaponPlayer, $forces); |
||||
281 | if ($this->checkHit($weaponPlayer, $modifiedAccuracy)) { |
||||
282 | $return['Hit'] = true; |
||||
283 | return $this->doPlayerDamageToForce($return, $weaponPlayer, $forces); |
||||
284 | } |
||||
285 | return $return; |
||||
286 | } |
||||
287 | |||||
288 | /** |
||||
289 | * @return array<string, mixed> |
||||
290 | */ |
||||
291 | public function shootPort(AbstractSmrPlayer $weaponPlayer, SmrPort $port): array { |
||||
292 | $return = ['Weapon' => $this, 'TargetPort' => $port, 'Hit' => false]; |
||||
293 | $modifiedAccuracy = $this->getModifiedAccuracyAgainstPort($weaponPlayer, $port); |
||||
294 | if ($this->checkHit($weaponPlayer, $modifiedAccuracy)) { |
||||
295 | $return['Hit'] = true; |
||||
296 | return $this->doPlayerDamageToPort($return, $weaponPlayer, $port); |
||||
297 | } |
||||
298 | return $return; |
||||
299 | } |
||||
300 | |||||
301 | /** |
||||
302 | * @return array<string, mixed> |
||||
303 | */ |
||||
304 | public function shootPlanet(AbstractSmrPlayer $weaponPlayer, SmrPlanet $planet): array { |
||||
305 | $return = ['Weapon' => $this, 'TargetPlanet' => $planet, 'Hit' => false]; |
||||
306 | $modifiedAccuracy = $this->getModifiedAccuracyAgainstPlanet($weaponPlayer, $planet); |
||||
307 | if ($this->checkHit($weaponPlayer, $modifiedAccuracy)) { |
||||
308 | $return['Hit'] = true; |
||||
309 | return $this->doPlayerDamageToPlanet($return, $weaponPlayer, $planet); |
||||
310 | } |
||||
311 | return $return; |
||||
312 | } |
||||
313 | |||||
314 | public function shootPlayer(AbstractSmrPlayer $weaponPlayer, AbstractSmrPlayer $targetPlayer): array { |
||||
315 | $return = ['Weapon' => $this, 'TargetPlayer' => $targetPlayer, 'Hit' => false]; |
||||
316 | $modifiedAccuracy = $this->getModifiedAccuracyAgainstPlayer($weaponPlayer, $targetPlayer); |
||||
317 | if ($this->checkHit($weaponPlayer, $modifiedAccuracy)) { |
||||
318 | $return['Hit'] = true; |
||||
319 | return $this->doPlayerDamageToPlayer($return, $weaponPlayer, $targetPlayer); |
||||
320 | } |
||||
321 | return $return; |
||||
322 | } |
||||
323 | |||||
324 | public function shootPlayerAsForce(SmrForce $forces, AbstractSmrPlayer $targetPlayer): never { |
||||
325 | throw new Exception('This weapon should not be used in this context'); |
||||
326 | } |
||||
327 | |||||
328 | /** |
||||
329 | * @return array<string, mixed> |
||||
330 | */ |
||||
331 | public function shootPlayerAsPort(AbstractSmrPort $port, AbstractSmrPlayer $targetPlayer): array { |
||||
332 | $return = ['Weapon' => $this, 'TargetPlayer' => $targetPlayer, 'Hit' => false]; |
||||
333 | $modifiedAccuracy = $this->getModifiedPortAccuracyAgainstPlayer($port, $targetPlayer); |
||||
334 | if ($this->checkHit($targetPlayer, $modifiedAccuracy)) { |
||||
335 | $return['Hit'] = true; |
||||
336 | return $this->doPortDamageToPlayer($return, $port, $targetPlayer); |
||||
337 | } |
||||
338 | return $return; |
||||
339 | } |
||||
340 | |||||
341 | /** |
||||
342 | * @return array<string, mixed> |
||||
343 | */ |
||||
344 | public function shootPlayerAsPlanet(SmrPlanet $planet, AbstractSmrPlayer $targetPlayer): array { |
||||
345 | $return = ['Weapon' => $this, 'TargetPlayer' => $targetPlayer, 'Hit' => false]; |
||||
346 | $modifiedAccuracy = $this->getModifiedPlanetAccuracyAgainstPlayer($planet, $targetPlayer); |
||||
347 | if ($this->checkHit($targetPlayer, $modifiedAccuracy)) { |
||||
348 | $return['Hit'] = true; |
||||
349 | return $this->doPlanetDamageToPlayer($return, $planet, $targetPlayer); |
||||
350 | } |
||||
351 | return $return; |
||||
352 | } |
||||
353 | |||||
354 | } |
||||
355 |