Scrutinizer GitHub App not installed

We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.

Install GitHub App

Failed Conditions
Pull Request — main (#1498)
by Dan
04:47
created

AbstractSmrShip::shootPlayers()   B

Complexity

Conditions 6
Paths 14

Size

Total Lines 28
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 22
c 0
b 0
f 0
nc 14
nop 1
dl 0
loc 28
rs 8.9457
1
<?php declare(strict_types=1);
2
3
use Smr\ShipClass;
4
5
/**
6
 * Properties and methods for a ship instance.
7
 * Does not include the database layer (see SmrShip).
8
 */
9
class AbstractSmrShip {
10
11
	// Player exp gained for each point of damage done
12
	protected const EXP_PER_DAMAGE_PLAYER = 0.375;
13
	protected const EXP_PER_DAMAGE_PLANET = 1.0; // note that planet damage is reduced
14
	protected const EXP_PER_DAMAGE_PORT = 0.15;
15
	protected const EXP_PER_DAMAGE_FORCE = 0.075;
16
17
	protected const STARTER_SHIPS = [
18
		RACE_NEUTRAL => SHIP_TYPE_GALACTIC_SEMI,
19
		RACE_ALSKANT => SHIP_TYPE_SMALL_TIMER,
20
		RACE_CREONTI => SHIP_TYPE_MEDIUM_CARGO_HULK,
21
		RACE_HUMAN => SHIP_TYPE_LIGHT_FREIGHTER,
22
		RACE_IKTHORNE => SHIP_TYPE_TINY_DELIGHT,
23
		RACE_SALVENE => SHIP_TYPE_HATCHLINGS_DUE,
24
		RACE_THEVIAN => SHIP_TYPE_SWIFT_VENTURE,
25
		RACE_WQHUMAN => SHIP_TYPE_SLIP_FREIGHTER,
26
		RACE_NIJARIN => SHIP_TYPE_REDEEMER,
27
	];
28
29
	protected AbstractSmrPlayer $player;
30
31
	protected int $gameID;
32
	protected SmrShipType $shipType;
33
34
	/** @var array<int, SmrWeapon> */
35
	protected array $weapons = [];
36
	/** @var array<int, int> */
37
	protected array $cargo = [];
38
	/** @var array<int, int> */
39
	protected array $hardware = [];
40
	protected bool $isCloaked = false;
41
	/** @var array<string, int>|false */
42
	protected array|false $illusionShip = false;
43
44
	protected bool $hasChangedWeapons = false;
45
	protected bool $hasChangedCargo = false;
46
	/** @var array<int, bool> */
47
	protected array $hasChangedHardware = [];
48
	protected bool $hasChangedCloak = false;
49
	protected bool $hasChangedIllusion = false;
50
51
	public function __construct(AbstractSmrPlayer $player) {
52
		$this->player = $player;
53
		$this->gameID = $player->getGameID();
54
		$this->regenerateShipType();
55
	}
56
57
	protected function regenerateShipType(): void {
58
		$this->shipType = SmrShipType::get($this->player->getShipTypeID());
59
	}
60
61
	public function checkForExcess(): void {
62
		$this->checkForExcessHardware();
63
		$this->checkForExcessWeapons();
64
		$this->checkForExcessCargo();
65
	}
66
67
	public function checkForExcessWeapons(): void {
68
		while ($this->hasWeapons() && ($this->getPowerUsed() > $this->getType()->getMaxPower() || $this->getNumWeapons() > $this->getHardpoints())) {
69
			//erase the first weapon 1 at a time until we are okay
70
			$this->removeLastWeapon();
71
		}
72
	}
73
74
	public function checkForExcessCargo(): void {
75
		if ($this->hasCargo()) {
76
			$excess = array_sum($this->getCargo()) - $this->getCargoHolds();
77
			foreach ($this->getCargo() as $goodID => $amount) {
78
				if ($excess > 0) {
79
					$decreaseAmount = min($amount, $excess);
80
					$this->decreaseCargo($goodID, $decreaseAmount);
81
					$excess -= $decreaseAmount;
82
				} else {
83
					// No more excess cargo
84
					break;
85
				}
86
			}
87
		}
88
	}
89
90
	public function checkForExcessHardware(): void {
91
		//check hardware to see if anything needs to be removed
92
		foreach ($this->getHardware() as $hardwareTypeID => $amount) {
93
			$max = $this->shipType->getMaxHardware($hardwareTypeID);
94
			$this->setHardware($hardwareTypeID, min($amount, $max));
95
		}
96
	}
97
98
	/**
99
	 * Set all hardware to its maximum value for this ship.
100
	 */
101
	public function setHardwareToMax(): void {
102
		foreach ($this->shipType->getAllMaxHardware() as $hardwareTypeID => $max) {
103
			$this->setHardware($hardwareTypeID, $max);
104
		}
105
	}
106
107
	public function getPowerUsed(): int {
108
		$power = 0;
109
		foreach ($this->weapons as $weapon) {
110
			$power += $weapon->getPowerLevel();
111
		}
112
		return $power;
113
	}
114
115
	public function getRemainingPower(): int {
116
		return $this->getType()->getMaxPower() - $this->getPowerUsed();
117
	}
118
119
	/**
120
	 * given power level of new weapon, return whether there is enough power available to install it on this ship
121
	 */
122
	public function checkPowerAvailable(int $powerLevel): bool {
123
		return $this->getRemainingPower() >= $powerLevel;
124
	}
125
126
	public function hasIllegalGoods(): bool {
127
		return $this->hasCargo(GOODS_SLAVES) || $this->hasCargo(GOODS_WEAPONS) || $this->hasCargo(GOODS_NARCOTICS);
128
	}
129
130
	public function getDisplayAttackRating(): int {
131
		if ($this->hasActiveIllusion()) {
132
			return $this->getIllusionAttack();
133
		}
134
		return $this->getAttackRating();
135
	}
136
137
	public function getDisplayDefenseRating(): int {
138
		if ($this->hasActiveIllusion()) {
139
			return $this->getIllusionDefense();
140
		}
141
		return $this->getDefenseRating();
142
	}
143
144
	public function getDisplayName(): string {
145
		if ($this->hasActiveIllusion()) {
146
			return $this->getIllusionShipName();
147
		}
148
		return $this->getName();
149
	}
150
151
	public function getAttackRating(): int {
152
		return IRound(($this->getTotalShieldDamage() + $this->getTotalArmourDamage() + $this->getCDs() * 2) / 40);
0 ignored issues
show
Bug introduced by
The function IRound 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 ignore-call  annotation

152
		return /** @scrutinizer ignore-call */ IRound(($this->getTotalShieldDamage() + $this->getTotalArmourDamage() + $this->getCDs() * 2) / 40);
Loading history...
153
	}
154
155
	public function getAttackRatingWithMaxCDs(): int {
156
		return IRound(($this->getTotalShieldDamage() + $this->getTotalArmourDamage() + $this->getMaxCDs() * 2) / 40);
0 ignored issues
show
Bug introduced by
The function IRound 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 ignore-call  annotation

156
		return /** @scrutinizer ignore-call */ IRound(($this->getTotalShieldDamage() + $this->getTotalArmourDamage() + $this->getMaxCDs() * 2) / 40);
Loading history...
157
	}
158
159
	public function getDefenseRating(): int {
160
		return IRound(($this->getShields() + $this->getArmour() + $this->getCDs() * CD_ARMOUR) / 100);
0 ignored issues
show
Bug introduced by
The function IRound 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 ignore-call  annotation

160
		return /** @scrutinizer ignore-call */ IRound(($this->getShields() + $this->getArmour() + $this->getCDs() * CD_ARMOUR) / 100);
Loading history...
161
	}
162
163
	public function getMaxDefenseRating(): int {
164
		return IRound(($this->getMaxShields() + $this->getMaxArmour() + $this->getMaxCDs() * CD_ARMOUR) / 100);
0 ignored issues
show
Bug introduced by
The function IRound 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 ignore-call  annotation

164
		return /** @scrutinizer ignore-call */ IRound(($this->getMaxShields() + $this->getMaxArmour() + $this->getMaxCDs() * CD_ARMOUR) / 100);
Loading history...
165
	}
166
167
	public function getShieldLow(): int { return IFloor($this->getShields() / 100) * 100; }
0 ignored issues
show
Bug introduced by
The function IFloor 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 ignore-call  annotation

167
	public function getShieldLow(): int { return /** @scrutinizer ignore-call */ IFloor($this->getShields() / 100) * 100; }
Loading history...
168
	public function getShieldHigh(): int { return $this->getShieldLow() + 100; }
169
	public function getArmourLow(): int { return IFloor($this->getArmour() / 100) * 100; }
0 ignored issues
show
Bug introduced by
The function IFloor 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 ignore-call  annotation

169
	public function getArmourLow(): int { return /** @scrutinizer ignore-call */ IFloor($this->getArmour() / 100) * 100; }
Loading history...
170
	public function getArmourHigh(): int { return $this->getArmourLow() + 100; }
171
	public function getCDsLow(): int { return IFloor($this->getCDs() / 100) * 100; }
0 ignored issues
show
Bug introduced by
The function IFloor 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 ignore-call  annotation

171
	public function getCDsLow(): int { return /** @scrutinizer ignore-call */ IFloor($this->getCDs() / 100) * 100; }
Loading history...
172
	public function getCDsHigh(): int { return $this->getCDsLow() + 100; }
173
174
175
176
	public function addWeapon(SmrWeapon $weapon): SmrWeapon|false {
177
		if ($this->hasOpenWeaponSlots() && $this->checkPowerAvailable($weapon->getPowerLevel())) {
178
			$this->weapons[] = $weapon;
179
			$this->hasChangedWeapons = true;
180
			return $weapon;
181
		}
182
		return false;
183
	}
184
185
	public function moveWeaponUp(int $orderID): void {
186
		$replacement = $orderID - 1;
187
		if ($replacement < 0) {
188
			// Shift everything up by one and put the selected weapon at the bottom
189
			$this->weapons[] = array_shift($this->weapons);
190
		} else {
191
			// Swap the selected weapon with the one above it
192
			$temp = $this->weapons[$replacement];
193
			$this->weapons[$replacement] = $this->weapons[$orderID];
194
			$this->weapons[$orderID] = $temp;
195
		}
196
		$this->hasChangedWeapons = true;
197
	}
198
199
	public function moveWeaponDown(int $orderID): void {
200
		$replacement = $orderID + 1;
201
		if ($replacement >= count($this->weapons)) {
202
			// Shift everything down by one and put the selected weapon at the top
203
			array_unshift($this->weapons, array_pop($this->weapons));
204
		} else {
205
			// Swap the selected weapon with the one below it
206
			$temp = $this->weapons[$replacement];
207
			$this->weapons[$replacement] = $this->weapons[$orderID];
208
			$this->weapons[$orderID] = $temp;
209
		}
210
		$this->hasChangedWeapons = true;
211
	}
212
213
	/**
214
	 * @param array<int, int> $orderArray
215
	 */
216
	public function setWeaponLocations(array $orderArray): void {
217
		$weapons = $this->weapons;
218
		foreach ($orderArray as $newOrder => $oldOrder) {
219
			$this->weapons[$newOrder] = $weapons[$oldOrder];
220
		}
221
		$this->hasChangedWeapons = true;
222
	}
223
224
	public function removeLastWeapon(): void {
225
		$this->removeWeapon($this->getNumWeapons() - 1);
226
	}
227
228
	public function removeWeapon(int $orderID): void {
229
		// Remove the specified weapon, then reindex the array
230
		unset($this->weapons[$orderID]);
231
		$this->weapons = array_values($this->weapons);
232
		$this->hasChangedWeapons = true;
233
	}
234
235
	public function removeAllWeapons(): void {
236
		$this->weapons = [];
237
		$this->hasChangedWeapons = true;
238
	}
239
240
	public function removeAllCargo(): void {
241
		foreach ($this->cargo as $goodID => $amount) {
242
			$this->setCargo($goodID, 0);
243
		}
244
	}
245
246
	public function removeAllHardware(): void {
247
		foreach (array_keys($this->hardware) as $hardwareTypeID) {
248
			$this->hasChangedHardware[$hardwareTypeID] = true;
249
		}
250
		$this->hardware = [];
251
		$this->decloak();
252
		$this->disableIllusion();
253
	}
254
255
	public function getPod(bool $isNewbie = false): void {
256
		$this->removeAllWeapons();
257
		$this->removeAllCargo();
258
		$this->removeAllHardware();
259
260
		if ($isNewbie) {
261
			$this->setShields(75);
262
			$this->setArmour(150);
263
			$this->setCargoHolds(40);
264
			$this->setTypeID(SHIP_TYPE_NEWBIE_MERCHANT_VESSEL);
265
		} else {
266
			$this->setShields(50);
267
			$this->setArmour(50);
268
			$this->setCargoHolds(5);
269
			$this->setTypeID(SHIP_TYPE_ESCAPE_POD);
270
		}
271
	}
272
273
	public function giveStarterShip(): void {
274
		if ($this->player->hasNewbieStatus()) {
275
			$shipID = SHIP_TYPE_NEWBIE_MERCHANT_VESSEL;
276
			$amount_shields = 75;
277
			$amount_armour = 150;
278
		} else {
279
			$shipID = self::STARTER_SHIPS[$this->player->getRaceID()];
280
			$amount_shields = 50;
281
			$amount_armour = 50;
282
		}
283
		$this->setTypeID($shipID);
284
		$this->setShields($amount_shields);
285
		$this->setArmour($amount_armour);
286
		$this->setCargoHolds(40);
287
		$this->addWeapon(SmrWeapon::getWeapon(WEAPON_TYPE_LASER));
288
	}
289
290
	public function hasJump(): bool {
291
		return $this->getHardware(HARDWARE_JUMP) > 0;
292
	}
293
294
	public function hasDCS(): bool {
295
		return $this->getHardware(HARDWARE_DCS) > 0;
296
	}
297
298
	public function hasScanner(): bool {
299
		return $this->getHardware(HARDWARE_SCANNER) > 0;
300
	}
301
302
	public function hasCloak(): bool {
303
		return $this->getHardware(HARDWARE_CLOAK) > 0;
304
	}
305
306
	public function isCloaked(): bool {
307
		return $this->isCloaked;
308
	}
309
310
	public function decloak(): void {
311
		if ($this->isCloaked === false) {
312
			return;
313
		}
314
		$this->isCloaked = false;
315
		$this->hasChangedCloak = true;
316
	}
317
318
	public function enableCloak(): void {
319
		if ($this->hasCloak() === false) {
320
			throw new Exception('Ship does not have the supported hardware!');
321
		}
322
		if ($this->isCloaked === true) {
323
			return;
324
		}
325
		$this->isCloaked = true;
326
		$this->hasChangedCloak = true;
327
	}
328
329
	public function hasIllusion(): bool {
330
		return $this->getHardware(HARDWARE_ILLUSION) > 0;
331
	}
332
333
	/**
334
	 * @return array<string, int>|false
335
	 */
336
	public function getIllusionShip(): array|false {
337
		return $this->illusionShip;
338
	}
339
340
	public function hasActiveIllusion(): bool {
341
		return $this->getIllusionShip() !== false;
342
	}
343
344
	public function setIllusion(int $shipTypeID, int $attack, int $defense): void {
345
		if ($this->hasIllusion() === false) {
346
			throw new Exception('Ship does not have the supported hardware!');
347
		}
348
		$newIllusionShip = [
349
			'ID' => $shipTypeID,
350
			'Attack' => $attack,
351
			'Defense' => $defense,
352
		];
353
		if ($this->getIllusionShip() === $newIllusionShip) {
354
			return;
355
		}
356
		$this->illusionShip = $newIllusionShip;
357
		$this->hasChangedIllusion = true;
358
	}
359
360
	public function disableIllusion(): void {
361
		if ($this->getIllusionShip() === false) {
362
			return;
363
		}
364
		$this->illusionShip = false;
365
		$this->hasChangedIllusion = true;
366
	}
367
368
	public function getIllusionShipID(): int {
369
		return $this->getIllusionShip()['ID'];
370
	}
371
372
	public function getIllusionShipName(): string {
373
		return SmrShipType::get($this->getIllusionShip()['ID'])->getName();
374
	}
375
376
	public function getIllusionAttack(): int {
377
		return $this->getIllusionShip()['Attack'];
378
	}
379
380
	public function getIllusionDefense(): int {
381
		return $this->getIllusionShip()['Defense'];
382
	}
383
384
	public function getPlayer(): AbstractSmrPlayer {
385
		return $this->player;
386
	}
387
388
	public function getAccountID(): int {
389
		return $this->getPlayer()->getAccountID();
390
	}
391
392
	public function getGameID(): int {
393
		return $this->gameID;
394
	}
395
396
	public function getGame(): SmrGame {
397
		return SmrGame::getGame($this->gameID);
398
	}
399
400
	/**
401
	 * Switch to a new ship, updating player turns accordingly.
402
	 */
403
	public function setTypeID(int $shipTypeID): void {
404
		$oldSpeed = $this->shipType->getSpeed();
405
		$this->getPlayer()->setShipTypeID($shipTypeID);
406
		$this->regenerateShipType();
407
		$this->checkForExcess();
408
409
		// Update the player's turns to account for the speed change
410
		$newSpeed = $this->shipType->getSpeed();
411
		$oldTurns = $this->getPlayer()->getTurns();
412
		$this->getPlayer()->setTurns(IRound($oldTurns * $newSpeed / $oldSpeed));
0 ignored issues
show
Bug introduced by
The function IRound 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 ignore-call  annotation

412
		$this->getPlayer()->setTurns(/** @scrutinizer ignore-call */ IRound($oldTurns * $newSpeed / $oldSpeed));
Loading history...
413
	}
414
415
	public function getType(): SmrShipType {
416
		return $this->shipType;
417
	}
418
419
	public function getTypeID(): int {
420
		return $this->shipType->getTypeID();
421
	}
422
423
	public function getClass(): ShipClass {
424
		return $this->shipType->getClass();
425
	}
426
427
	public function getName(): string {
428
		return $this->shipType->getName();
429
	}
430
431
	public function getCost(): int {
432
		return $this->shipType->getCost();
433
	}
434
435
	public function getHardpoints(): int {
436
		return $this->shipType->getHardpoints();
437
	}
438
439
	/**
440
	 * Trade-in value of the ship
441
	 */
442
	public function getRefundValue(): int {
443
		return IFloor($this->getCost() * SHIP_REFUND_PERCENT);
0 ignored issues
show
Bug introduced by
The function IFloor 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 ignore-call  annotation

443
		return /** @scrutinizer ignore-call */ IFloor($this->getCost() * SHIP_REFUND_PERCENT);
Loading history...
444
	}
445
446
	public function getCostToUpgrade(int $otherShipTypeID): int {
447
		$otherShipType = SmrShipType::get($otherShipTypeID);
448
		return $otherShipType->getCost() - $this->getRefundValue();
449
	}
450
451
	public function getCostToUpgradeAndUNO(int $otherShipTypeID): int {
452
		return $this->getCostToUpgrade($otherShipTypeID) + $this->getCostToUNOAgainstShip($otherShipTypeID);
453
	}
454
455
	protected function getCostToUNOAgainstShip(int $otherShipTypeID): int {
456
		$otherShipType = SmrShipType::get($otherShipTypeID);
457
		$cost = 0;
458
		$hardwareTypes = [HARDWARE_SHIELDS, HARDWARE_ARMOUR, HARDWARE_CARGO];
459
		foreach ($hardwareTypes as $hardwareTypeID) {
460
			$cost += max(0, $otherShipType->getMaxHardware($hardwareTypeID) - $this->getHardware($hardwareTypeID)) * Globals::getHardwareCost($hardwareTypeID);
461
		}
462
		return $cost;
463
	}
464
465
	public function getCostToUNO(): int {
466
		return $this->getCostToUNOAgainstShip($this->getTypeID());
467
	}
468
469
	/**
470
	 * Returns the ship speed modified by the game speed.
471
	 */
472
	public function getRealSpeed(): float {
473
		return $this->shipType->getSpeed() * $this->getGame()->getGameSpeed();
474
	}
475
476
	/**
477
	 * @return ($hardwareTypeID is null ? array<int, int> : int)
0 ignored issues
show
Documentation Bug introduced by
The doc comment ($hardwareTypeID at position 1 could not be parsed: Unknown type name '$hardwareTypeID' at position 1 in ($hardwareTypeID.
Loading history...
478
	 */
479
	public function getHardware(int $hardwareTypeID = null): array|int {
480
		if ($hardwareTypeID === null) {
481
			return $this->hardware;
482
		}
483
		return $this->hardware[$hardwareTypeID] ?? 0;
484
	}
485
486
	public function setHardware(int $hardwareTypeID, int $amount): void {
487
		if ($this->getHardware($hardwareTypeID) === $amount) {
488
			return;
489
		}
490
		$this->hardware[$hardwareTypeID] = $amount;
491
		$this->hasChangedHardware[$hardwareTypeID] = true;
492
	}
493
494
	public function increaseHardware(int $hardwareTypeID, int $amount): void {
495
		$this->setHardware($hardwareTypeID, $this->getHardware($hardwareTypeID) + $amount);
496
	}
497
498
	public function hasMaxHardware(int $hardwareTypeID): bool {
499
		return $this->getHardware($hardwareTypeID) == $this->shipType->getMaxHardware($hardwareTypeID);
500
	}
501
502
	public function getShields(): int {
503
		return $this->getHardware(HARDWARE_SHIELDS);
504
	}
505
506
	public function setShields(int $amount): void {
507
		$this->setHardware(HARDWARE_SHIELDS, $amount);
508
	}
509
510
	public function decreaseShields(int $amount): void {
511
		$this->setShields($this->getShields() - $amount);
512
	}
513
514
	public function increaseShields(int $amount): void {
515
		$this->setShields($this->getShields() + $amount);
516
	}
517
518
	public function hasShields(): bool {
519
		return $this->getShields() > 0;
520
	}
521
522
	public function hasMaxShields(): bool {
523
		return $this->hasMaxHardware(HARDWARE_SHIELDS);
524
	}
525
526
	public function getMaxShields(): int {
527
		return $this->shipType->getMaxHardware(HARDWARE_SHIELDS);
528
	}
529
530
	public function getArmour(): int {
531
		return $this->getHardware(HARDWARE_ARMOUR);
532
	}
533
534
	public function setArmour(int $amount): void {
535
		$this->setHardware(HARDWARE_ARMOUR, $amount);
536
	}
537
538
	public function decreaseArmour(int $amount): void {
539
		$this->setArmour($this->getArmour() - $amount);
540
	}
541
542
	public function increaseArmour(int $amount): void {
543
		$this->setArmour($this->getArmour() + $amount);
544
	}
545
546
	public function hasArmour(): bool {
547
		return $this->getArmour() > 0;
548
	}
549
550
	public function hasMaxArmour(): bool {
551
		return $this->hasMaxHardware(HARDWARE_ARMOUR);
552
	}
553
554
	public function getMaxArmour(): int {
555
		return $this->shipType->getMaxHardware(HARDWARE_ARMOUR);
556
	}
557
558
	public function isDead(): bool {
559
		return !$this->hasArmour() && !$this->hasShields();
560
	}
561
562
	public function hasMaxCDs(): bool {
563
		return $this->hasMaxHardware(HARDWARE_COMBAT);
564
	}
565
566
	public function hasMaxSDs(): bool {
567
		return $this->hasMaxHardware(HARDWARE_SCOUT);
568
	}
569
570
	public function hasMaxMines(): bool {
571
		return $this->hasMaxHardware(HARDWARE_MINE);
572
	}
573
574
	public function hasCDs(): bool {
575
		return $this->getCDs() > 0;
576
	}
577
578
	public function hasSDs(): bool {
579
		return $this->getSDs() > 0;
580
	}
581
582
	public function hasMines(): bool {
583
		return $this->getMines() > 0;
584
	}
585
586
	public function getCDs(): int {
587
		return $this->getHardware(HARDWARE_COMBAT);
588
	}
589
590
	public function setCDs(int $amount): void {
591
		$this->setHardware(HARDWARE_COMBAT, $amount);
592
	}
593
594
	public function decreaseCDs(int $amount): void {
595
		$this->setCDs($this->getCDs() - $amount);
596
	}
597
598
	public function increaseCDs(int $amount): void {
599
		$this->setCDs($this->getCDs() + $amount);
600
	}
601
602
	public function getMaxCDs(): int {
603
		return $this->shipType->getMaxHardware(HARDWARE_COMBAT);
604
	}
605
606
	public function getSDs(): int {
607
		return $this->getHardware(HARDWARE_SCOUT);
608
	}
609
610
	public function setSDs(int $amount): void {
611
		$this->setHardware(HARDWARE_SCOUT, $amount);
612
	}
613
614
	public function decreaseSDs(int $amount): void {
615
		$this->setSDs($this->getSDs() - $amount);
616
	}
617
618
	public function increaseSDs(int $amount): void {
619
		$this->setSDs($this->getSDs() + $amount);
620
	}
621
622
	public function getMaxSDs(): int {
623
		return $this->shipType->getMaxHardware(HARDWARE_SCOUT);
624
	}
625
626
	public function getMines(): int {
627
		return $this->getHardware(HARDWARE_MINE);
628
	}
629
630
	public function setMines(int $amount): void {
631
		$this->setHardware(HARDWARE_MINE, $amount);
632
	}
633
634
	public function decreaseMines(int $amount): void {
635
		$this->setMines($this->getMines() - $amount);
636
	}
637
638
	public function increaseMines(int $amount): void {
639
		$this->setMines($this->getMines() + $amount);
640
	}
641
642
	public function getMaxMines(): int {
643
		return $this->shipType->getMaxHardware(HARDWARE_MINE);
644
	}
645
646
	public function getCargoHolds(): int {
647
		return $this->getHardware(HARDWARE_CARGO);
648
	}
649
650
	public function setCargoHolds(int $amount): void {
651
		$this->setHardware(HARDWARE_CARGO, $amount);
652
	}
653
654
	/**
655
	 * @return ($goodID is null ? array<int, int> : int)
0 ignored issues
show
Documentation Bug introduced by
The doc comment ($goodID at position 1 could not be parsed: Unknown type name '$goodID' at position 1 in ($goodID.
Loading history...
656
	 */
657
	public function getCargo(int $goodID = null): int|array {
658
		if ($goodID === null) {
659
			return $this->cargo;
660
		}
661
		return $this->cargo[$goodID] ?? 0;
662
	}
663
664
	public function hasCargo(int $goodID = null): bool {
665
		if ($goodID === null) {
666
			return $this->getUsedHolds() > 0;
667
		}
668
		return $this->getCargo($goodID) > 0;
669
	}
670
671
	public function setCargo(int $goodID, int $amount): void {
672
		if ($this->getCargo($goodID) === $amount) {
673
			return;
674
		}
675
		$this->cargo[$goodID] = $amount;
676
		$this->hasChangedCargo = true;
677
		// Sort cargo by goodID to make sure it shows up in the correct order
678
		// before the next page is loaded.
679
		ksort($this->cargo);
680
	}
681
682
	public function decreaseCargo(int $goodID, int $amount): void {
683
		if ($amount < 0) {
684
			throw new Exception('Trying to decrease negative cargo.');
685
		}
686
		$this->setCargo($goodID, $this->getCargo($goodID) - $amount);
687
	}
688
689
	public function increaseCargo(int $goodID, int $amount): void {
690
		if ($amount < 0) {
691
			throw new Exception('Trying to increase negative cargo.');
692
		}
693
		$this->setCargo($goodID, $this->getCargo($goodID) + $amount);
694
	}
695
696
	public function getEmptyHolds(): int {
697
		return $this->getCargoHolds() - $this->getUsedHolds();
698
	}
699
700
	public function getUsedHolds(): int {
701
		return array_sum($this->getCargo());
702
	}
703
704
	public function hasMaxCargoHolds(): bool {
705
		return $this->hasMaxHardware(HARDWARE_CARGO);
706
	}
707
708
	public function getMaxCargoHolds(): int {
709
		return $this->shipType->getMaxHardware(HARDWARE_CARGO);
710
	}
711
712
	public function hasWeapons(): bool {
713
		return $this->getNumWeapons() > 0;
714
	}
715
716
	/**
717
	 * @return array<int, SmrWeapon>
718
	 */
719
	public function getWeapons(): array {
720
		return $this->weapons;
721
	}
722
723
	public function canAttack(): bool {
724
		return $this->hasWeapons() || $this->hasCDs();
725
	}
726
727
	public function getNumWeapons(): int {
728
		return count($this->getWeapons());
729
	}
730
731
	public function getOpenWeaponSlots(): int {
732
		return $this->getHardpoints() - $this->getNumWeapons();
733
	}
734
735
	public function hasOpenWeaponSlots(): bool {
736
		return $this->getOpenWeaponSlots() > 0;
737
	}
738
739
	public function getTotalShieldDamage(): int {
740
		$shieldDamage = 0;
741
		foreach ($this->getWeapons() as $weapon) {
742
			$shieldDamage += $weapon->getShieldDamage();
743
		}
744
		return $shieldDamage;
745
	}
746
747
	public function getTotalArmourDamage(): int {
748
		$armourDamage = 0;
749
		foreach ($this->getWeapons() as $weapon) {
750
			$armourDamage += $weapon->getArmourDamage();
751
		}
752
		return $armourDamage;
753
	}
754
755
	public function isFederal(): bool {
756
		return $this->getTypeID() === SHIP_TYPE_FEDERAL_DISCOVERY ||
757
		       $this->getTypeID() === SHIP_TYPE_FEDERAL_WARRANT ||
758
		       $this->getTypeID() === SHIP_TYPE_FEDERAL_ULTIMATUM;
759
	}
760
761
	public function isUnderground(): bool {
762
		return $this->getTypeID() === SHIP_TYPE_THIEF ||
763
		       $this->getTypeID() === SHIP_TYPE_ASSASSIN ||
764
		       $this->getTypeID() === SHIP_TYPE_DEATH_CRUISER;
765
	}
766
767
	/**
768
	 * @param array<AbstractSmrPlayer> $targetPlayers
769
	 * @return array<string, mixed>
770
	 */
771
	public function shootPlayers(array $targetPlayers): array {
772
		$thisPlayer = $this->getPlayer();
773
		$results = ['Player' => $thisPlayer, 'TotalDamage' => 0, 'Weapons' => []];
774
		foreach ($targetPlayers as $targetPlayer) {
775
			$results['TotalDamagePerTargetPlayer'][$targetPlayer->getAccountID()] = 0;
776
		}
777
		if ($thisPlayer->isDead()) {
778
			$results['DeadBeforeShot'] = true;
779
			return $results;
780
		}
781
		$results['DeadBeforeShot'] = false;
782
		foreach ($this->weapons as $orderID => $weapon) {
783
			$results['Weapons'][$orderID] = $weapon->shootPlayer($thisPlayer, array_rand_value($targetPlayers));
0 ignored issues
show
Bug introduced by
The function array_rand_value 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 ignore-call  annotation

783
			$results['Weapons'][$orderID] = $weapon->shootPlayer($thisPlayer, /** @scrutinizer ignore-call */ array_rand_value($targetPlayers));
Loading history...
784
			if ($results['Weapons'][$orderID]['Hit']) {
785
				$results['TotalDamage'] += $results['Weapons'][$orderID]['ActualDamage']['TotalDamage'];
786
				$results['TotalDamagePerTargetPlayer'][$results['Weapons'][$orderID]['TargetPlayer']->getAccountID()] += $results['Weapons'][$orderID]['ActualDamage']['TotalDamage'];
787
			}
788
		}
789
		if ($this->hasCDs()) {
790
			$thisCDs = new SmrCombatDrones($this->getCDs());
791
			$results['Drones'] = $thisCDs->shootPlayer($thisPlayer, array_rand_value($targetPlayers));
792
			$results['TotalDamage'] += $results['Drones']['ActualDamage']['TotalDamage'];
793
			$results['TotalDamagePerTargetPlayer'][$results['Drones']['TargetPlayer']->getAccountID()] += $results['Drones']['ActualDamage']['TotalDamage'];
794
		}
795
		$thisPlayer->increaseExperience(IRound($results['TotalDamage'] * self::EXP_PER_DAMAGE_PLAYER));
0 ignored issues
show
Bug introduced by
The function IRound 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 ignore-call  annotation

795
		$thisPlayer->increaseExperience(/** @scrutinizer ignore-call */ IRound($results['TotalDamage'] * self::EXP_PER_DAMAGE_PLAYER));
Loading history...
796
		$thisPlayer->increaseHOF($results['TotalDamage'], ['Combat', 'Player', 'Damage Done'], HOF_PUBLIC);
797
		$thisPlayer->increaseHOF(1, ['Combat', 'Player', 'Shots'], HOF_PUBLIC);
798
		return $results;
799
	}
800
801
	/**
802
	 * @return array<string, mixed>
803
	 */
804
	public function shootForces(SmrForce $forces): array {
805
		$thisPlayer = $this->getPlayer();
806
		$results = ['Player' => $thisPlayer, 'TotalDamage' => 0, 'Weapons' => []];
807
		if ($thisPlayer->isDead()) {
808
			$results['DeadBeforeShot'] = true;
809
			return $results;
810
		}
811
		$results['DeadBeforeShot'] = false;
812
		foreach ($this->weapons as $orderID => $weapon) {
813
			$results['Weapons'][$orderID] = $weapon->shootForces($thisPlayer, $forces);
814
			if ($results['Weapons'][$orderID]['Hit']) {
815
				$results['TotalDamage'] += $results['Weapons'][$orderID]['ActualDamage']['TotalDamage'];
816
				$thisPlayer->increaseHOF($results['Weapons'][$orderID]['ActualDamage']['NumMines'], ['Combat', 'Forces', 'Mines', 'Killed'], HOF_PUBLIC);
817
				$thisPlayer->increaseHOF($results['Weapons'][$orderID]['ActualDamage']['Mines'], ['Combat', 'Forces', 'Mines', 'Damage Done'], HOF_PUBLIC);
818
				$thisPlayer->increaseHOF($results['Weapons'][$orderID]['ActualDamage']['NumCDs'], ['Combat', 'Forces', 'Combat Drones', 'Killed'], HOF_PUBLIC);
819
				$thisPlayer->increaseHOF($results['Weapons'][$orderID]['ActualDamage']['CDs'], ['Combat', 'Forces', 'Combat Drones', 'Damage Done'], HOF_PUBLIC);
820
				$thisPlayer->increaseHOF($results['Weapons'][$orderID]['ActualDamage']['NumSDs'], ['Combat', 'Forces', 'Scout Drones', 'Killed'], HOF_PUBLIC);
821
				$thisPlayer->increaseHOF($results['Weapons'][$orderID]['ActualDamage']['SDs'], ['Combat', 'Forces', 'Scout Drones', 'Damage Done'], HOF_PUBLIC);
822
				$thisPlayer->increaseHOF($results['Weapons'][$orderID]['ActualDamage']['NumMines'] + $results['Weapons'][$orderID]['ActualDamage']['NumCDs'] + $results['Weapons'][$orderID]['ActualDamage']['NumSDs'], ['Combat', 'Forces', 'Killed'], HOF_PUBLIC);
823
			}
824
		}
825
		if ($this->hasCDs()) {
826
			$thisCDs = new SmrCombatDrones($this->getCDs());
827
			$results['Drones'] = $thisCDs->shootForces($thisPlayer, $forces);
828
			$results['TotalDamage'] += $results['Drones']['ActualDamage']['TotalDamage'];
829
			$thisPlayer->increaseHOF($results['Drones']['ActualDamage']['NumMines'], ['Combat', 'Forces', 'Mines', 'Killed'], HOF_PUBLIC);
830
			$thisPlayer->increaseHOF($results['Drones']['ActualDamage']['Mines'], ['Combat', 'Forces', 'Mines', 'Damage Done'], HOF_PUBLIC);
831
			$thisPlayer->increaseHOF($results['Drones']['ActualDamage']['NumCDs'], ['Combat', 'Forces', 'Combat Drones', 'Killed'], HOF_PUBLIC);
832
			$thisPlayer->increaseHOF($results['Drones']['ActualDamage']['CDs'], ['Combat', 'Forces', 'Combat Drones', 'Damage Done'], HOF_PUBLIC);
833
			$thisPlayer->increaseHOF($results['Drones']['ActualDamage']['NumSDs'], ['Combat', 'Forces', 'Scout Drones', 'Killed'], HOF_PUBLIC);
834
			$thisPlayer->increaseHOF($results['Drones']['ActualDamage']['SDs'], ['Combat', 'Forces', 'Scout Drones', 'Damage Done'], HOF_PUBLIC);
835
			$thisPlayer->increaseHOF($results['Drones']['ActualDamage']['NumMines'] + $results['Drones']['ActualDamage']['NumCDs'] + $results['Drones']['ActualDamage']['NumSDs'], ['Combat', 'Forces', 'Killed'], HOF_PUBLIC);
836
		}
837
		$thisPlayer->increaseExperience(IRound($results['TotalDamage'] * self::EXP_PER_DAMAGE_FORCE));
0 ignored issues
show
Bug introduced by
The function IRound 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 ignore-call  annotation

837
		$thisPlayer->increaseExperience(/** @scrutinizer ignore-call */ IRound($results['TotalDamage'] * self::EXP_PER_DAMAGE_FORCE));
Loading history...
838
		$thisPlayer->increaseHOF($results['TotalDamage'], ['Combat', 'Forces', 'Damage Done'], HOF_PUBLIC);
839
		$thisPlayer->increaseHOF(1, ['Combat', 'Forces', 'Shots'], HOF_PUBLIC);
840
		return $results;
841
	}
842
843
	/**
844
	 * @return array<string, mixed>
845
	 */
846
	public function shootPort(SmrPort $port): array {
847
		$thisPlayer = $this->getPlayer();
848
		$results = ['Player' => $thisPlayer, 'TotalDamage' => 0, 'Weapons' => []];
849
		if ($thisPlayer->isDead()) {
850
			$results['DeadBeforeShot'] = true;
851
			return $results;
852
		}
853
		$results['DeadBeforeShot'] = false;
854
		foreach ($this->weapons as $orderID => $weapon) {
855
			$results['Weapons'][$orderID] = $weapon->shootPort($thisPlayer, $port);
856
			if ($results['Weapons'][$orderID]['Hit']) {
857
				$results['TotalDamage'] += $results['Weapons'][$orderID]['ActualDamage']['TotalDamage'];
858
			}
859
		}
860
		if ($this->hasCDs()) {
861
			$thisCDs = new SmrCombatDrones($this->getCDs());
862
			$results['Drones'] = $thisCDs->shootPort($thisPlayer, $port);
863
			$results['TotalDamage'] += $results['Drones']['ActualDamage']['TotalDamage'];
864
		}
865
		$thisPlayer->increaseExperience(IRound($results['TotalDamage'] * self::EXP_PER_DAMAGE_PORT));
0 ignored issues
show
Bug introduced by
The function IRound 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 ignore-call  annotation

865
		$thisPlayer->increaseExperience(/** @scrutinizer ignore-call */ IRound($results['TotalDamage'] * self::EXP_PER_DAMAGE_PORT));
Loading history...
866
		$thisPlayer->increaseHOF($results['TotalDamage'], ['Combat', 'Port', 'Damage Done'], HOF_PUBLIC);
867
		//$thisPlayer->increaseHOF(1,array('Combat','Port','Shots')); //in SmrPort::attackedBy()
868
869
		// Change alignment if we reach a damage threshold.
870
		// Increase if player and port races are at war; decrease otherwise.
871
		if ($results['TotalDamage'] >= SmrPort::DAMAGE_NEEDED_FOR_ALIGNMENT_CHANGE) {
872
			$relations = Globals::getRaceRelations($thisPlayer->getGameID(), $thisPlayer->getRaceID());
873
			if ($relations[$port->getRaceID()] <= RELATIONS_WAR) {
874
				$thisPlayer->increaseAlignment(1);
875
				$thisPlayer->increaseHOF(1, ['Combat', 'Port', 'Alignment', 'Gain'], HOF_PUBLIC);
876
			} else {
877
				$thisPlayer->decreaseAlignment(1);
878
				$thisPlayer->increaseHOF(1, ['Combat', 'Port', 'Alignment', 'Loss'], HOF_PUBLIC);
879
			}
880
		}
881
		return $results;
882
	}
883
884
	/**
885
	 * @return array<string, mixed>
886
	 */
887
	public function shootPlanet(SmrPlanet $planet): array {
888
		$thisPlayer = $this->getPlayer();
889
		$results = ['Player' => $thisPlayer, 'TotalDamage' => 0, 'Weapons' => []];
890
		if ($thisPlayer->isDead()) {
891
			$results['DeadBeforeShot'] = true;
892
			return $results;
893
		}
894
		$results['DeadBeforeShot'] = false;
895
		foreach ($this->weapons as $orderID => $weapon) {
896
			$results['Weapons'][$orderID] = $weapon->shootPlanet($thisPlayer, $planet);
897
			if ($results['Weapons'][$orderID]['Hit']) {
898
				$results['TotalDamage'] += $results['Weapons'][$orderID]['ActualDamage']['TotalDamage'];
899
			}
900
		}
901
		if ($this->hasCDs()) {
902
			$thisCDs = new SmrCombatDrones($this->getCDs());
903
			$results['Drones'] = $thisCDs->shootPlanet($thisPlayer, $planet);
904
			$results['TotalDamage'] += $results['Drones']['ActualDamage']['TotalDamage'];
905
		}
906
		$thisPlayer->increaseExperience(IRound($results['TotalDamage'] * self::EXP_PER_DAMAGE_PLANET));
0 ignored issues
show
Bug introduced by
The function IRound 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 ignore-call  annotation

906
		$thisPlayer->increaseExperience(/** @scrutinizer ignore-call */ IRound($results['TotalDamage'] * self::EXP_PER_DAMAGE_PLANET));
Loading history...
907
		$thisPlayer->increaseHOF($results['TotalDamage'], ['Combat', 'Planet', 'Damage Done'], HOF_PUBLIC);
908
		//$thisPlayer->increaseHOF(1,array('Combat','Planet','Shots')); //in SmrPlanet::attackedBy()
909
		return $results;
910
	}
911
912
	/**
913
	 * @param array<string, int|bool> $damage
914
	 * @return array<string, int|bool>
915
	 */
916
	public function takeDamage(array $damage): array {
917
		$alreadyDead = $this->getPlayer()->isDead();
918
		$armourDamage = 0;
919
		$cdDamage = 0;
920
		$shieldDamage = 0;
921
		if (!$alreadyDead) {
922
			// Even if the weapon doesn't do any damage, it was fired at the
923
			// player, so alert them that they're under attack.
924
			$this->getPlayer()->setUnderAttack(true);
925
926
			$shieldDamage = $this->takeDamageToShields($damage['Shield']);
0 ignored issues
show
Bug introduced by
It seems like $damage['Shield'] can also be of type boolean; however, parameter $damage of AbstractSmrShip::takeDamageToShields() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

926
			$shieldDamage = $this->takeDamageToShields(/** @scrutinizer ignore-type */ $damage['Shield']);
Loading history...
927
			if (!$this->hasShields() && ($shieldDamage == 0 || $damage['Rollover'])) {
928
				$cdMaxDamage = $damage['Armour'] - $shieldDamage;
929
				$cdDamage = $this->takeDamageToCDs($cdMaxDamage);
930
				if (!$this->hasCDs() && ($cdDamage == 0 || $damage['Rollover'])) {
931
					$armourMaxDamage = $damage['Armour'] - $shieldDamage - $cdDamage;
932
					$armourDamage = $this->takeDamageToArmour($armourMaxDamage);
933
				}
934
			}
935
		}
936
		return [
937
						'KillingShot' => !$alreadyDead && $this->isDead(),
938
						'TargetAlreadyDead' => $alreadyDead,
939
						'Shield' => $shieldDamage,
940
						'CDs' => $cdDamage,
941
						'NumCDs' => $cdDamage / CD_ARMOUR,
942
						'HasCDs' => $this->hasCDs(),
943
						'Armour' => $armourDamage,
944
						'TotalDamage' => $shieldDamage + $cdDamage + $armourDamage,
945
		];
946
	}
947
948
	/**
949
	 * @param array<string, int|bool> $damage
950
	 * @return array<string, int|bool>
951
	 */
952
	public function takeDamageFromMines(array $damage): array {
953
		$alreadyDead = $this->getPlayer()->isDead();
954
		$armourDamage = 0;
955
		$cdDamage = 0;
956
		$shieldDamage = 0;
957
		if (!$alreadyDead) {
958
			$shieldDamage = $this->takeDamageToShields($damage['Shield']);
0 ignored issues
show
Bug introduced by
It seems like $damage['Shield'] can also be of type boolean; however, parameter $damage of AbstractSmrShip::takeDamageToShields() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

958
			$shieldDamage = $this->takeDamageToShields(/** @scrutinizer ignore-type */ $damage['Shield']);
Loading history...
959
			if (!$this->hasShields() && ($shieldDamage == 0 || $damage['Rollover'])) { //skip CDs if it's mines
960
				$armourMaxDamage = $damage['Armour'] - $shieldDamage;
961
				$armourDamage = $this->takeDamageToArmour($armourMaxDamage);
962
			}
963
		}
964
		return [
965
						'KillingShot' => !$alreadyDead && $this->isDead(),
966
						'TargetAlreadyDead' => $alreadyDead,
967
						'Shield' => $shieldDamage,
968
						'CDs' => $cdDamage,
969
						'NumCDs' => $cdDamage / CD_ARMOUR,
970
						'HasCDs' => $this->hasCDs(),
971
						'Armour' => $armourDamage,
972
						'TotalDamage' => $shieldDamage + $cdDamage + $armourDamage,
973
		];
974
	}
975
976
	protected function takeDamageToShields(int $damage): int {
977
		$actualDamage = min($this->getShields(), $damage);
978
		$this->decreaseShields($actualDamage);
979
		return $actualDamage;
980
	}
981
982
	protected function takeDamageToCDs(int $damage): int {
983
		$actualDamage = min($this->getCDs(), IFloor($damage / CD_ARMOUR));
0 ignored issues
show
Bug introduced by
The function IFloor 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 ignore-call  annotation

983
		$actualDamage = min($this->getCDs(), /** @scrutinizer ignore-call */ IFloor($damage / CD_ARMOUR));
Loading history...
984
		$this->decreaseCDs($actualDamage);
985
		return $actualDamage * CD_ARMOUR;
986
	}
987
988
	protected function takeDamageToArmour(int $damage): int {
989
		$actualDamage = min($this->getArmour(), $damage);
990
		$this->decreaseArmour($actualDamage);
991
		return $actualDamage;
992
	}
993
994
	/**
995
	 * Returns the maneuverability rating for this ship.
996
	 */
997
	public function getMR(): int {
998
		return $this->shipType->getBaseManeuverability();
999
	}
1000
1001
	public function update(): void {
1002
		throw new Exception('Can only call update on SmrShip objects');
1003
	}
1004
1005
}
1006