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
Push — main ( 30fe2e...c58695 )
by Dan
04:45
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