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

Completed
Push — master ( ef1d69...78c0d4 )
by Dan
22s queued 17s
created

AbstractSmrShip::decreaseMines()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 2
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php declare(strict_types=1);
2
3
/**
4
 * Properties and methods for a ship instance.
5
 * Does not include the database layer (see SmrShip).
6
 */
7
class AbstractSmrShip {
8
	protected static array $CACHE_BASE_SHIPS = [];
9
10
	// Player exp gained for each point of damage done
11
	const EXP_PER_DAMAGE_PLAYER = 0.375;
12
	const EXP_PER_DAMAGE_PLANET = 1.0; // note that planet damage is reduced
13
	const EXP_PER_DAMAGE_PORT   = 0.15;
14
	const EXP_PER_DAMAGE_FORCE  = 0.075;
15
16
	const STARTER_SHIPS = [
17
		RACE_NEUTRAL => SHIP_TYPE_GALACTIC_SEMI,
18
		RACE_ALSKANT => SHIP_TYPE_SMALL_TIMER,
19
		RACE_CREONTI => SHIP_TYPE_MEDIUM_CARGO_HULK,
20
		RACE_HUMAN => SHIP_TYPE_LIGHT_FREIGHTER,
21
		RACE_IKTHORNE => SHIP_TYPE_TINY_DELIGHT,
22
		RACE_SALVENE => SHIP_TYPE_HATCHLINGS_DUE,
23
		RACE_THEVIAN => SHIP_TYPE_SWIFT_VENTURE,
24
		RACE_WQHUMAN => SHIP_TYPE_SLIP_FREIGHTER,
25
		RACE_NIJARIN => SHIP_TYPE_REDEEMER,
26
	];
27
28
	protected AbstractSmrPlayer $player;
29
30
	protected int $gameID;
31
	protected array $baseShip;
32
33
	protected array $weapons = [];
34
	protected array $cargo = [];
35
	protected array $hardware = [];
36
	protected bool $isCloaked = false;
37
	protected array|false $illusionShip = false;
38
39
	protected bool $hasChangedWeapons = false;
40
	protected bool $hasChangedCargo = false;
41
	protected array $hasChangedHardware = [];
42
	protected bool $hasChangedCloak = false;
43
	protected bool $hasChangedIllusion = false;
44
45
	public static function getBaseShip(int $shipTypeID, bool $forceUpdate = false) : array {
46
		if ($forceUpdate || !isset(self::$CACHE_BASE_SHIPS[$shipTypeID])) {
47
			// determine ship
48
			$db = Smr\Database::getInstance();
49
			$db->query('SELECT * FROM ship_type WHERE ship_type_id = ' . $db->escapeNumber($shipTypeID) . ' LIMIT 1');
50
			if ($db->nextRecord()) {
51
				self::$CACHE_BASE_SHIPS[$shipTypeID] = self::buildBaseShip($db);
52
			} else {
53
				throw new Exception('Invalid shipTypeID: ' . $shipTypeID);
54
			}
55
		}
56
		return self::$CACHE_BASE_SHIPS[$shipTypeID];
57
	}
58
59
	protected static function buildBaseShip(Smr\Database $db) : array {
60
		$ship = array();
61
		$ship['Type'] = 'Ship';
62
		$ship['Name'] = $db->getField('ship_name');
63
		$ship['ShipTypeID'] = $db->getInt('ship_type_id');
64
		$ship['ShipClassID'] = $db->getInt('ship_class_id');
65
		$ship['RaceID'] = $db->getInt('race_id');
66
		$ship['Hardpoint'] = $db->getInt('hardpoint');
67
		$ship['Speed'] = $db->getInt('speed');
68
		$ship['Cost'] = $db->getInt('cost');
69
		$ship['AlignRestriction'] = $db->getInt('buyer_restriction');
70
		$ship['Level'] = $db->getInt('lvl_needed');
71
72
		$maxPower = 0;
73
		switch ($ship['Hardpoint']) {
74
			default:
75
				$maxPower += 1 * $ship['Hardpoint'] - 10;
76
			case 10:
77
				$maxPower += 2;
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
78
			case 9:
79
				$maxPower += 2;
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
80
			case 8:
81
				$maxPower += 2;
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
82
			case 7:
83
				$maxPower += 2;
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
84
			case 6:
85
				$maxPower += 3;
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
86
			case 5:
87
				$maxPower += 3;
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
88
			case 4:
89
				$maxPower += 3;
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
90
			case 3:
91
				$maxPower += 4;
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
92
			case 2:
93
				$maxPower += 4;
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
94
			case 1:
95
				$maxPower += 5;
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
96
			case 0:
97
				$maxPower += 0;
98
		}
99
		$ship['MaxPower'] = $maxPower;
100
101
102
		// get supported hardware from db
103
		$db2 = Smr\Database::getInstance();
104
		$db2->query('SELECT hardware_type_id, max_amount FROM ship_type_support_hardware ' .
105
			'WHERE ship_type_id = ' . $db2->escapeNumber($ship['ShipTypeID']) . ' ORDER BY hardware_type_id');
106
107
		while ($db2->nextRecord()) {
108
			// adding hardware to array
109
			$ship['MaxHardware'][$db2->getInt('hardware_type_id')] = $db2->getInt('max_amount');
110
		}
111
112
		$ship['BaseMR'] = IRound(
113
								700 -
114
								(
115
									(
116
										$ship['MaxHardware'][HARDWARE_SHIELDS]
117
										+$ship['MaxHardware'][HARDWARE_ARMOUR]
118
										+$ship['MaxHardware'][HARDWARE_COMBAT] * 3
119
									) / 25
120
									+(
121
										$ship['MaxHardware'][HARDWARE_CARGO] / 100
122
										-$ship['Speed'] * 5
123
										+$ship['Hardpoint'] * 5
124
										+$ship['MaxHardware'][HARDWARE_COMBAT] / 5
125
									)
126
								)
127
							);
128
		return $ship;
129
	}
130
131
	public static function getAllBaseShips() : array {
132
		// determine ship
133
		$db = Smr\Database::getInstance();
134
		$db->query('SELECT * FROM ship_type ORDER BY ship_type_id ASC');
135
		while ($db->nextRecord()) {
136
			if (!isset(self::$CACHE_BASE_SHIPS[$db->getInt('ship_type_id')])) {
137
				self::$CACHE_BASE_SHIPS[$db->getInt('ship_type_id')] = self::buildBaseShip($db);
138
			}
139
		}
140
		return self::$CACHE_BASE_SHIPS;
141
	}
142
143
	public function __construct(AbstractSmrPlayer $player) {
144
		$this->player = $player;
145
		$this->gameID = $player->getGameID();
146
		$this->regenerateBaseShip();
147
	}
148
149
	protected function regenerateBaseShip() : void {
150
		$this->baseShip = AbstractSmrShip::getBaseShip($this->player->getShipTypeID());
151
	}
152
153
	public function checkForExcess() : void {
154
		$this->checkForExcessHardware();
155
		$this->checkForExcessWeapons();
156
		$this->checkForExcessCargo();
157
	}
158
159
	public function checkForExcessWeapons() : void {
160
		while ($this->hasWeapons() && ($this->getPowerUsed() > $this->getMaxPower() || $this->getNumWeapons() > $this->getHardpoints())) {
161
			//erase the first weapon 1 at a time until we are okay
162
			$this->removeLastWeapon();
163
		}
164
	}
165
166
	public function checkForExcessCargo() : void {
167
		if ($this->hasCargo()) {
168
			$excess = array_sum($this->getCargo()) - $this->getCargoHolds();
169
			foreach ($this->getCargo() as $goodID => $amount) {
170
				if ($excess > 0) {
171
					$decreaseAmount = min($amount, $excess);
172
					$this->decreaseCargo($goodID, $decreaseAmount);
173
					$excess -= $decreaseAmount;
174
				} else {
175
					// No more excess cargo
176
					break;
177
				}
178
			}
179
		}
180
	}
181
182
	public function checkForExcessHardware() : void {
183
		//check hardware to see if anything needs to be removed
184
		foreach ($this->getHardware() as $hardwareTypeID => $amount) {
185
			if ($amount > ($max = $this->getMaxHardware($hardwareTypeID))) {
186
				$this->setHardware($hardwareTypeID, $max);
0 ignored issues
show
Bug introduced by
It seems like $max can also be of type array; however, parameter $amount of AbstractSmrShip::setHardware() 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

186
				$this->setHardware($hardwareTypeID, /** @scrutinizer ignore-type */ $max);
Loading history...
187
			}
188
		}
189
	}
190
191
	/**
192
	 * Set all hardware to its maximum value for this ship.
193
	 */
194
	public function setHardwareToMax() : void {
195
		foreach ($this->getMaxHardware() as $key => $max) {
196
			$this->setHardware($key, $max);
197
		}
198
	}
199
200
	public function getPowerUsed() : int {
201
		$power = 0;
202
		foreach ($this->weapons as $weapon) {
203
			$power += $weapon->getPowerLevel();
204
		}
205
		return $power;
206
	}
207
208
	public function getRemainingPower() : int {
209
		return $this->getMaxPower() - $this->getPowerUsed();
210
	}
211
212
	/**
213
	 * given power level of new weapon, return whether there is enough power available to install it on this ship
214
	 */
215
	public function checkPowerAvailable(int $powerLevel) : bool {
216
		return $this->getRemainingPower() >= $powerLevel;
217
	}
218
219
	public function getMaxPower() : int {
220
		return $this->baseShip['MaxPower'];
221
	}
222
223
	public function hasIllegalGoods() : bool {
224
		return $this->hasCargo(GOODS_SLAVES) || $this->hasCargo(GOODS_WEAPONS) || $this->hasCargo(GOODS_NARCOTICS);
225
	}
226
227
	public function getDisplayAttackRating() : int {
228
		if ($this->hasActiveIllusion()) {
229
			return $this->getIllusionAttack();
230
		} else {
231
			return $this->getAttackRating();
232
		}
233
	}
234
235
	public function getDisplayDefenseRating() : int {
236
		if ($this->hasActiveIllusion()) {
237
			return $this->getIllusionDefense();
238
		} else {
239
			return $this->getDefenseRating();
240
		}
241
	}
242
243
	public function getDisplayName() : string {
244
		if ($this->hasActiveIllusion()) {
245
			return $this->getIllusionShipName();
246
		} else {
247
			return $this->getName();
248
		}
249
	}
250
251
	public function getAttackRating() : int {
252
		return IRound(($this->getTotalShieldDamage() + $this->getTotalArmourDamage() + $this->getCDs() * 2) / 40);
253
	}
254
255
	public function getAttackRatingWithMaxCDs() : int {
256
		return IRound(($this->getTotalShieldDamage() + $this->getTotalArmourDamage() + $this->getMaxCDs() * .7) / 40);
257
	}
258
259
	public function getDefenseRating() : int {
260
		return IRound((($this->getShields() + $this->getArmour()) / 100) + (($this->getCDs() * 3) / 100));
261
	}
262
263
	public function getMaxDefenseRating() : int {
264
		return IRound((($this->getMaxShields() + $this->getMaxArmour()) / 100) + (($this->getMaxCDs() * 3) / 100));
265
	}
266
267
	public function getShieldLow() : int { return IFloor($this->getShields() / 100) * 100; }
268
	public function getShieldHigh() : int { return $this->getShieldLow() + 100; }
269
	public function getArmourLow() : int { return IFloor($this->getArmour() / 100) * 100; }
270
	public function getArmourHigh() : int { return $this->getArmourLow() + 100; }
271
	public function getCDsLow() : int { return IFloor($this->getCDs() / 100) * 100; }
272
	public function getCDsHigh() : int { return $this->getCDsLow() + 100; }
273
274
275
276
	public function addWeapon(SmrWeapon $weapon) : SmrWeapon|false {
277
		if ($this->hasOpenWeaponSlots() && $this->checkPowerAvailable($weapon->getPowerLevel())) {
278
			array_push($this->weapons, $weapon);
279
			$this->hasChangedWeapons = true;
280
			return $weapon;
281
		}
282
		return false;
283
	}
284
285
	public function moveWeaponUp(int $orderID) : void {
286
		$replacement = $orderID - 1;
287
		if ($replacement < 0) {
288
			// Shift everything up by one and put the selected weapon at the bottom
289
			array_push($this->weapons, array_shift($this->weapons));
290
		} else {
291
			// Swap the selected weapon with the one above it
292
			$temp = $this->weapons[$replacement];
293
			$this->weapons[$replacement] = $this->weapons[$orderID];
294
			$this->weapons[$orderID] = $temp;
295
		}
296
		$this->hasChangedWeapons = true;
297
	}
298
299
	public function moveWeaponDown(int $orderID) : void {
300
		$replacement = $orderID + 1;
301
		if ($replacement >= count($this->weapons)) {
302
			// Shift everything down by one and put the selected weapon at the top
303
			array_unshift($this->weapons, array_pop($this->weapons));
304
		} else {
305
			// Swap the selected weapon with the one below it
306
			$temp = $this->weapons[$replacement];
307
			$this->weapons[$replacement] = $this->weapons[$orderID];
308
			$this->weapons[$orderID] = $temp;
309
		}
310
		$this->hasChangedWeapons = true;
311
	}
312
313
	public function setWeaponLocations(array $orderArray) : void {
314
		$weapons = $this->weapons;
315
		foreach ($orderArray as $newOrder => $oldOrder) {
316
			$this->weapons[$newOrder] = $weapons[$oldOrder];
317
		}
318
		$this->hasChangedWeapons = true;
319
	}
320
321
	public function removeLastWeapon() : void {
322
		$this->removeWeapon($this->getNumWeapons() - 1);
323
	}
324
325
	public function removeWeapon(int $orderID) : void {
326
		// Remove the specified weapon, then reindex the array
327
		unset($this->weapons[$orderID]);
328
		$this->weapons = array_values($this->weapons);
329
		$this->hasChangedWeapons = true;
330
	}
331
332
	public function removeAllWeapons() : void {
333
		$this->weapons = array();
334
		$this->hasChangedWeapons = true;
335
	}
336
337
	public function removeAllCargo() : void {
338
		foreach ($this->cargo as $goodID => $amount) {
339
			$this->setCargo($goodID, 0);
340
		}
341
	}
342
343
	public function removeAllHardware() : void {
344
		foreach (array_keys($this->hardware) as $hardwareTypeID) {
345
			$this->hasChangedHardware[$hardwareTypeID] = true;
346
		}
347
		$this->hardware = [];
348
		$this->decloak();
349
		$this->disableIllusion();
350
	}
351
352
	public function getPod(bool $isNewbie = false) : void {
353
		$this->removeAllWeapons();
354
		$this->removeAllCargo();
355
		$this->removeAllHardware();
356
357
		if ($isNewbie) {
358
			$this->setShields(75);
359
			$this->setArmour(150);
360
			$this->setCargoHolds(40);
361
			$this->setShipTypeID(SHIP_TYPE_NEWBIE_MERCHANT_VESSEL);
362
		} else {
363
			$this->setShields(50);
364
			$this->setArmour(50);
365
			$this->setCargoHolds(5);
366
			$this->setShipTypeID(SHIP_TYPE_ESCAPE_POD);
367
		}
368
	}
369
370
	public function giveStarterShip() : void {
371
		if ($this->player->hasNewbieStatus()) {
372
			$shipID = SHIP_TYPE_NEWBIE_MERCHANT_VESSEL;
373
			$amount_shields = 75;
374
			$amount_armour = 150;
375
		} else {
376
			$shipID = self::STARTER_SHIPS[$this->player->getRaceID()];
377
			$amount_shields = 50;
378
			$amount_armour = 50;
379
		}
380
		$this->setShipTypeID($shipID);
381
		$this->setShields($amount_shields);
382
		$this->setArmour($amount_armour);
383
		$this->setCargoHolds(40);
384
		$this->addWeapon(SmrWeapon::getWeapon(WEAPON_TYPE_LASER));
385
	}
386
387
	public function hasJump() : bool {
388
		return $this->getHardware(HARDWARE_JUMP) > 0;
389
	}
390
391
	public function canHaveJump() : bool {
392
		return $this->getMaxHardware(HARDWARE_JUMP) > 0;
393
	}
394
395
	public function hasDCS() : bool {
396
		return $this->getHardware(HARDWARE_DCS) > 0;
397
	}
398
399
	public function canHaveDCS() : bool {
400
		return $this->getMaxHardware(HARDWARE_DCS) > 0;
401
	}
402
403
	public function hasScanner() : bool {
404
		return $this->getHardware(HARDWARE_SCANNER) > 0;
405
	}
406
407
	public function canHaveScanner() : bool {
408
		return $this->getMaxHardware(HARDWARE_SCANNER) > 0;
409
	}
410
411
	public function hasCloak() : bool {
412
		return $this->getHardware(HARDWARE_CLOAK) > 0;
413
	}
414
415
	public function canHaveCloak() : bool {
416
		return $this->getMaxHardware(HARDWARE_CLOAK) > 0;
417
	}
418
419
	public function isCloaked() : bool {
420
		return $this->isCloaked;
421
	}
422
423
	public function decloak() : void {
424
		if ($this->isCloaked === false) {
425
			return;
426
		}
427
		$this->isCloaked = false;
428
		$this->hasChangedCloak = true;
429
	}
430
431
	public function enableCloak() : void {
432
		if ($this->hasCloak() === false) {
433
			throw new Exception('Ship does not have the supported hardware!');
434
		}
435
		if ($this->isCloaked === true) {
436
			return;
437
		}
438
		$this->isCloaked = true;
439
		$this->hasChangedCloak = true;
440
	}
441
442
	public function hasIllusion() : bool {
443
		return $this->getHardware(HARDWARE_ILLUSION) > 0;
444
	}
445
446
	public function canHaveIllusion() : bool {
447
		return $this->getMaxHardware(HARDWARE_ILLUSION) > 0;
448
	}
449
450
	public function getIllusionShip() : array|false {
451
		return $this->illusionShip;
452
	}
453
454
	public function hasActiveIllusion() : bool {
455
		return $this->getIllusionShip() !== false;
456
	}
457
458
	public function setIllusion(int $ship_id, int $attack, int $defense) : void {
459
		if ($this->hasIllusion() === false) {
460
			throw new Exception('Ship does not have the supported hardware!');
461
		}
462
		$newIllusionShip = [
463
			'ID' => $ship_id,
464
			'Attack' => $attack,
465
			'Defense' => $defense,
466
		];
467
		if ($this->getIllusionShip() === $newIllusionShip) {
468
			return;
469
		}
470
		$this->illusionShip = $newIllusionShip;
471
		$this->hasChangedIllusion = true;
472
	}
473
474
	public function disableIllusion() : void {
475
		if ($this->getIllusionShip() === false) {
476
			return;
477
		}
478
		$this->illusionShip = false;
479
		$this->hasChangedIllusion = true;
480
	}
481
482
	public function getIllusionShipID() : int {
483
		return $this->getIllusionShip()['ID'];
484
	}
485
486
	public function getIllusionShipName() : string {
487
		return self::getBaseShip($this->getIllusionShip()['ID'])['Name'];
488
	}
489
490
	public function getIllusionAttack() : int {
491
		return $this->getIllusionShip()['Attack'];
492
	}
493
494
	public function getIllusionDefense() : int {
495
		return $this->getIllusionShip()['Defense'];
496
	}
497
498
	public function getPlayer() : AbstractSmrPlayer {
499
		return $this->player;
500
	}
501
502
	public function getAccountID() : int {
503
		return $this->getPlayer()->getAccountID();
504
	}
505
506
	public function getGameID() : int {
507
		return $this->gameID;
508
	}
509
510
	public function getGame() : SmrGame {
511
		return SmrGame::getGame($this->gameID);
512
	}
513
514
	public function getShipTypeID() : int {
515
		return $this->baseShip['ShipTypeID'];
516
	}
517
518
	public function getShipClassID() : int {
519
		return $this->baseShip['ShipClassID'];
520
	}
521
522
	/**
523
	 * Switch to a new ship, updating player turns accordingly.
524
	 */
525
	public function setShipTypeID(int $shipTypeID) : void {
526
		$oldSpeed = $this->getSpeed();
527
		$this->getPlayer()->setShipTypeID($shipTypeID);
528
		$this->regenerateBaseShip();
529
		$newSpeed = $this->getSpeed();
530
531
		// Update the player's turns to account for the speed change
532
		$oldTurns = $this->getPlayer()->getTurns();
533
		$this->getPlayer()->setTurns(IRound($oldTurns * $newSpeed / $oldSpeed));
534
	}
535
536
	public function getName() : string {
537
		return $this->baseShip['Name'];
538
	}
539
540
	public function getCost() : int {
541
		return $this->baseShip['Cost'];
542
	}
543
544
	public function getCostToUpgrade(int $upgradeShipID) : int {
545
		$upgadeBaseShip = AbstractSmrShip::getBaseShip($upgradeShipID);
546
		return $upgadeBaseShip['Cost'] - IFloor($this->getCost() * SHIP_REFUND_PERCENT);
547
	}
548
549
	public function getCostToUpgradeAndUNO(int $upgradeShipID) : int {
550
		return $this->getCostToUpgrade($upgradeShipID) + $this->getCostToUNOAgainstShip($upgradeShipID);
551
	}
552
553
	protected function getCostToUNOAgainstShip(int $shipID) : int {
554
		$baseShip = AbstractSmrShip::getBaseShip($shipID);
555
		$cost = 0;
556
		$hardwareTypes = array(HARDWARE_SHIELDS, HARDWARE_ARMOUR, HARDWARE_CARGO);
557
		foreach ($hardwareTypes as $hardwareTypeID) {
558
			$cost += max(0, $baseShip['MaxHardware'][$hardwareTypeID] - $this->getHardware($hardwareTypeID)) * Globals::getHardwareCost($hardwareTypeID);
559
		}
560
		return $cost;
561
	}
562
563
	public function getCostToUNO() : int {
564
		return $this->getCostToUNOAgainstShip($this->getShipTypeID());
565
	}
566
567
	/**
568
	 * Returns the base ship speed (unmodified by the game speed).
569
	 */
570
	public function getSpeed() : int {
571
		return $this->baseShip['Speed'];
572
	}
573
574
	/**
575
	 * Returns the ship speed modified by the game speed.
576
	 */
577
	public function getRealSpeed() : float {
578
		return $this->getSpeed() * $this->getGame()->getGameSpeed();
579
	}
580
581
	public function getHardware(int $hardwareTypeID = null) : array|int {
582
		if ($hardwareTypeID === null) {
583
			return $this->hardware;
584
		}
585
		return $this->hardware[$hardwareTypeID] ?? 0;
586
	}
587
588
	public function setHardware(int $hardwareTypeID, int $amount) : void {
589
		if ($this->getHardware($hardwareTypeID) === $amount) {
590
			return;
591
		}
592
		$this->hardware[$hardwareTypeID] = $amount;
593
		$this->hasChangedHardware[$hardwareTypeID] = true;
594
	}
595
596
	public function increaseHardware(int $hardwareTypeID, int $amount) : void {
597
		$this->setHardware($hardwareTypeID, $this->getHardware($hardwareTypeID) + $amount);
598
	}
599
600
	public function hasMaxHardware(int $hardwareTypeID) : bool {
601
		return $this->getHardware($hardwareTypeID) == $this->getMaxHardware($hardwareTypeID);
602
	}
603
604
	public function getMaxHardware(int $hardwareTypeID = null) : array|int {
605
		if ($hardwareTypeID === null) {
606
			return $this->baseShip['MaxHardware'];
607
		}
608
		return $this->baseShip['MaxHardware'][$hardwareTypeID];
609
	}
610
611
	public function getShields() : int {
612
		return $this->getHardware(HARDWARE_SHIELDS);
613
	}
614
615
	public function setShields(int $amount) : void {
616
		$this->setHardware(HARDWARE_SHIELDS, $amount);
617
	}
618
619
	public function decreaseShields(int $amount) : void {
620
		$this->setShields($this->getShields() - $amount);
621
	}
622
623
	public function increaseShields(int $amount) : void {
624
		$this->setShields($this->getShields() + $amount);
625
	}
626
627
	public function hasShields() : bool {
628
		return $this->getShields() > 0;
629
	}
630
631
	public function hasMaxShields() : bool {
632
		return $this->hasMaxHardware(HARDWARE_SHIELDS);
633
	}
634
635
	public function getMaxShields() : int {
636
		return $this->getMaxHardware(HARDWARE_SHIELDS);
637
	}
638
639
	public function getArmour() : int {
640
		return $this->getHardware(HARDWARE_ARMOUR);
641
	}
642
643
	public function setArmour(int $amount) : void {
644
		$this->setHardware(HARDWARE_ARMOUR, $amount);
645
	}
646
647
	public function decreaseArmour(int $amount) : void {
648
		$this->setArmour($this->getArmour() - $amount);
649
	}
650
651
	public function increaseArmour(int $amount) : void {
652
		$this->setArmour($this->getArmour() + $amount);
653
	}
654
655
	public function hasArmour() : bool {
656
		return $this->getArmour() > 0;
657
	}
658
659
	public function hasMaxArmour() : bool {
660
		return $this->hasMaxHardware(HARDWARE_ARMOUR);
661
	}
662
663
	public function getMaxArmour() : int {
664
		return $this->getMaxHardware(HARDWARE_ARMOUR);
665
	}
666
667
	public function isDead() : bool {
668
		return !$this->hasArmour() && !$this->hasShields();
669
	}
670
671
	public function hasMaxCDs() : bool {
672
		return $this->hasMaxHardware(HARDWARE_COMBAT);
673
	}
674
675
	public function hasMaxSDs() : bool {
676
		return $this->hasMaxHardware(HARDWARE_SCOUT);
677
	}
678
679
	public function hasMaxMines() : bool {
680
		return $this->hasMaxHardware(HARDWARE_MINE);
681
	}
682
683
	public function hasCDs() : bool {
684
		return $this->getCDs() > 0;
685
	}
686
687
	public function hasSDs() : bool {
688
		return $this->getSDs() > 0;
689
	}
690
691
	public function hasMines() : bool {
692
		return $this->getMines() > 0;
693
	}
694
695
	public function getCDs() : int {
696
		return $this->getHardware(HARDWARE_COMBAT);
697
	}
698
699
	public function setCDs(int $amount) : void {
700
		$this->setHardware(HARDWARE_COMBAT, $amount);
701
	}
702
703
	public function decreaseCDs(int $amount) : void {
704
		$this->setCDs($this->getCDs() - $amount);
705
	}
706
707
	public function increaseCDs(int $amount) : void {
708
		$this->setCDs($this->getCDs() + $amount);
709
	}
710
711
	public function getMaxCDs() : int {
712
		return $this->getMaxHardware(HARDWARE_COMBAT);
713
	}
714
715
	public function getSDs() : int {
716
		return $this->getHardware(HARDWARE_SCOUT);
717
	}
718
719
	public function setSDs(int $amount) : void {
720
		$this->setHardware(HARDWARE_SCOUT, $amount);
721
	}
722
723
	public function decreaseSDs(int $amount) : void {
724
		$this->setSDs($this->getSDs() - $amount);
725
	}
726
727
	public function increaseSDs(int $amount) : void {
728
		$this->setSDs($this->getSDs() + $amount);
729
	}
730
731
	public function getMaxSDs() : int {
732
		return $this->getMaxHardware(HARDWARE_SCOUT);
733
	}
734
735
	public function getMines() : int {
736
		return $this->getHardware(HARDWARE_MINE);
737
	}
738
739
	public function setMines(int $amount) : void {
740
		$this->setHardware(HARDWARE_MINE, $amount);
741
	}
742
743
	public function decreaseMines(int $amount) : void {
744
		$this->setMines($this->getMines() - $amount);
745
	}
746
747
	public function increaseMines(int $amount) : void {
748
		$this->setMines($this->getMines() + $amount);
749
	}
750
751
	public function getMaxMines() : int {
752
		return $this->getMaxHardware(HARDWARE_MINE);
753
	}
754
755
	public function getCargoHolds() : int {
756
		return $this->getHardware(HARDWARE_CARGO);
757
	}
758
759
	public function setCargoHolds(int $amount) : void {
760
		$this->setHardware(HARDWARE_CARGO, $amount);
761
	}
762
763
	public function getCargo(int $goodID = null) : int|array {
764
		if ($goodID === null) {
765
			return $this->cargo;
766
		}
767
		return $this->cargo[$goodID] ?? 0;
768
	}
769
770
	public function hasCargo(int $goodID = null) : bool {
771
		if ($goodID === null) {
772
			return $this->getUsedHolds() > 0;
773
		}
774
		return $this->getCargo($goodID) > 0;
775
	}
776
777
	public function setCargo(int $goodID, int $amount) : void {
778
		if ($this->getCargo($goodID) === $amount) {
779
			return;
780
		}
781
		$this->cargo[$goodID] = $amount;
782
		$this->hasChangedCargo = true;
783
		// Sort cargo by goodID to make sure it shows up in the correct order
784
		// before the next page is loaded.
785
		ksort($this->cargo);
786
	}
787
788
	public function decreaseCargo(int $goodID, int $amount) : void {
789
		if ($amount < 0) {
790
			throw new Exception('Trying to decrease negative cargo.');
791
		}
792
		$this->setCargo($goodID, $this->getCargo($goodID) - $amount);
793
	}
794
795
	public function increaseCargo(int $goodID, int $amount) : void {
796
		if ($amount < 0) {
797
			throw new Exception('Trying to increase negative cargo.');
798
		}
799
		$this->setCargo($goodID, $this->getCargo($goodID) + $amount);
800
	}
801
802
	public function getEmptyHolds() : int {
803
		return $this->getCargoHolds() - $this->getUsedHolds();
804
	}
805
806
	public function getUsedHolds() : int {
807
		return array_sum($this->getCargo());
808
	}
809
810
	public function hasMaxCargoHolds() : bool {
811
		return $this->hasMaxHardware(HARDWARE_CARGO);
812
	}
813
814
	public function getMaxCargoHolds() : int {
815
		return $this->getMaxHardware(HARDWARE_CARGO);
816
	}
817
818
	public function hasWeapons() : bool {
819
		return $this->getNumWeapons() > 0;
820
	}
821
822
	public function getWeapons() : array {
823
		return $this->weapons;
824
	}
825
826
	public function canAttack() : bool {
827
		return $this->hasWeapons() || $this->hasCDs();
828
	}
829
830
	public function getNumWeapons() : int {
831
		return count($this->getWeapons());
832
	}
833
834
	public function getOpenWeaponSlots() : int {
835
		return $this->getHardpoints() - $this->getNumWeapons();
836
	}
837
838
	public function hasOpenWeaponSlots() : bool {
839
		return $this->getOpenWeaponSlots() > 0;
840
	}
841
842
	public function getHardpoints() : int {
843
		return $this->baseShip['Hardpoint'];
844
	}
845
846
	public function getTotalShieldDamage() : int {
847
		$shieldDamage = 0;
848
		foreach ($this->getWeapons() as $weapon) {
849
			$shieldDamage += $weapon->getShieldDamage();
850
		}
851
		return $shieldDamage;
852
	}
853
854
	public function getTotalArmourDamage() : int {
855
		$armourDamage = 0;
856
		foreach ($this->getWeapons() as $weapon) {
857
			$armourDamage += $weapon->getArmourDamage();
858
		}
859
		return $armourDamage;
860
	}
861
862
	public function isFederal() : bool {
863
		return $this->getShipTypeID() === SHIP_TYPE_FEDERAL_DISCOVERY ||
864
		       $this->getShipTypeID() === SHIP_TYPE_FEDERAL_WARRANT ||
865
		       $this->getShipTypeID() === SHIP_TYPE_FEDERAL_ULTIMATUM;
866
	}
867
868
	public function isUnderground() : bool {
869
		return $this->getShipTypeID() === SHIP_TYPE_THIEF ||
870
		       $this->getShipTypeID() === SHIP_TYPE_ASSASSIN ||
871
		       $this->getShipTypeID() === SHIP_TYPE_DEATH_CRUISER;
872
	}
873
874
	public function shootPlayers(array $targetPlayers) : array {
875
		$thisPlayer = $this->getPlayer();
876
		$results = array('Player' => $thisPlayer, 'TotalDamage' => 0, 'Weapons' => []);
877
		if ($thisPlayer->isDead()) {
878
			$results['DeadBeforeShot'] = true;
879
			return $results;
880
		}
881
		$results['DeadBeforeShot'] = false;
882
		foreach ($this->weapons as $orderID => $weapon) {
883
			$results['Weapons'][$orderID] = $weapon->shootPlayer($thisPlayer, array_rand_value($targetPlayers));
884
			if ($results['Weapons'][$orderID]['Hit']) {
885
				$results['TotalDamage'] += $results['Weapons'][$orderID]['ActualDamage']['TotalDamage'];
886
			}
887
		}
888
		if ($this->hasCDs()) {
889
			$thisCDs = new SmrCombatDrones($this->getCDs());
890
			$results['Drones'] = $thisCDs->shootPlayer($thisPlayer, array_rand_value($targetPlayers));
891
			$results['TotalDamage'] += $results['Drones']['ActualDamage']['TotalDamage'];
892
		}
893
		$thisPlayer->increaseExperience(IRound($results['TotalDamage'] * self::EXP_PER_DAMAGE_PLAYER));
894
		$thisPlayer->increaseHOF($results['TotalDamage'], array('Combat', 'Player', 'Damage Done'), HOF_PUBLIC);
895
		$thisPlayer->increaseHOF(1, array('Combat', 'Player', 'Shots'), HOF_PUBLIC);
896
		return $results;
897
	}
898
899
	public function shootForces(SmrForce $forces) : array {
900
		$thisPlayer = $this->getPlayer();
901
		$results = array('Player' => $thisPlayer, 'TotalDamage' => 0, 'Weapons' => []);
902
		if ($thisPlayer->isDead()) {
903
			$results['DeadBeforeShot'] = true;
904
			return $results;
905
		}
906
		$results['DeadBeforeShot'] = false;
907
		foreach ($this->weapons as $orderID => $weapon) {
908
			$results['Weapons'][$orderID] = $weapon->shootForces($thisPlayer, $forces);
909
			if ($results['Weapons'][$orderID]['Hit']) {
910
				$results['TotalDamage'] += $results['Weapons'][$orderID]['ActualDamage']['TotalDamage'];
911
				$thisPlayer->increaseHOF($results['Weapons'][$orderID]['ActualDamage']['NumMines'], array('Combat', 'Forces', 'Mines', 'Killed'), HOF_PUBLIC);
912
				$thisPlayer->increaseHOF($results['Weapons'][$orderID]['ActualDamage']['Mines'], array('Combat', 'Forces', 'Mines', 'Damage Done'), HOF_PUBLIC);
913
				$thisPlayer->increaseHOF($results['Weapons'][$orderID]['ActualDamage']['NumCDs'], array('Combat', 'Forces', 'Combat Drones', 'Killed'), HOF_PUBLIC);
914
				$thisPlayer->increaseHOF($results['Weapons'][$orderID]['ActualDamage']['CDs'], array('Combat', 'Forces', 'Combat Drones', 'Damage Done'), HOF_PUBLIC);
915
				$thisPlayer->increaseHOF($results['Weapons'][$orderID]['ActualDamage']['NumSDs'], array('Combat', 'Forces', 'Scout Drones', 'Killed'), HOF_PUBLIC);
916
				$thisPlayer->increaseHOF($results['Weapons'][$orderID]['ActualDamage']['SDs'], array('Combat', 'Forces', 'Scout Drones', 'Damage Done'), HOF_PUBLIC);
917
				$thisPlayer->increaseHOF($results['Weapons'][$orderID]['ActualDamage']['NumMines'] + $results['Weapons'][$orderID]['ActualDamage']['NumCDs'] + $results['Weapons'][$orderID]['ActualDamage']['NumSDs'], array('Combat', 'Forces', 'Killed'), HOF_PUBLIC);
918
			}
919
		}
920
		if ($this->hasCDs()) {
921
			$thisCDs = new SmrCombatDrones($this->getCDs());
922
			$results['Drones'] = $thisCDs->shootForces($thisPlayer, $forces);
923
			$results['TotalDamage'] += $results['Drones']['ActualDamage']['TotalDamage'];
924
			$thisPlayer->increaseHOF($results['Drones']['ActualDamage']['NumMines'], array('Combat', 'Forces', 'Mines', 'Killed'), HOF_PUBLIC);
925
			$thisPlayer->increaseHOF($results['Drones']['ActualDamage']['Mines'], array('Combat', 'Forces', 'Mines', 'Damage Done'), HOF_PUBLIC);
926
			$thisPlayer->increaseHOF($results['Drones']['ActualDamage']['NumCDs'], array('Combat', 'Forces', 'Combat Drones', 'Killed'), HOF_PUBLIC);
927
			$thisPlayer->increaseHOF($results['Drones']['ActualDamage']['CDs'], array('Combat', 'Forces', 'Combat Drones', 'Damage Done'), HOF_PUBLIC);
928
			$thisPlayer->increaseHOF($results['Drones']['ActualDamage']['NumSDs'], array('Combat', 'Forces', 'Scout Drones', 'Killed'), HOF_PUBLIC);
929
			$thisPlayer->increaseHOF($results['Drones']['ActualDamage']['SDs'], array('Combat', 'Forces', 'Scout Drones', 'Damage Done'), HOF_PUBLIC);
930
			$thisPlayer->increaseHOF($results['Drones']['ActualDamage']['NumMines'] + $results['Drones']['ActualDamage']['NumCDs'] + $results['Drones']['ActualDamage']['NumSDs'], array('Combat', 'Forces', 'Killed'), HOF_PUBLIC);
931
		}
932
		$thisPlayer->increaseExperience(IRound($results['TotalDamage'] * self::EXP_PER_DAMAGE_FORCE));
933
		$thisPlayer->increaseHOF($results['TotalDamage'], array('Combat', 'Forces', 'Damage Done'), HOF_PUBLIC);
934
		$thisPlayer->increaseHOF(1, array('Combat', 'Forces', 'Shots'), HOF_PUBLIC);
935
		return $results;
936
	}
937
938
	public function shootPort(SmrPort $port) : array {
939
		$thisPlayer = $this->getPlayer();
940
		$results = array('Player' => $thisPlayer, 'TotalDamage' => 0, 'Weapons' => []);
941
		if ($thisPlayer->isDead()) {
942
			$results['DeadBeforeShot'] = true;
943
			return $results;
944
		}
945
		$results['DeadBeforeShot'] = false;
946
		foreach ($this->weapons as $orderID => $weapon) {
947
			$results['Weapons'][$orderID] = $weapon->shootPort($thisPlayer, $port);
948
			if ($results['Weapons'][$orderID]['Hit']) {
949
				$results['TotalDamage'] += $results['Weapons'][$orderID]['ActualDamage']['TotalDamage'];
950
			}
951
		}
952
		if ($this->hasCDs()) {
953
			$thisCDs = new SmrCombatDrones($this->getCDs());
954
			$results['Drones'] = $thisCDs->shootPort($thisPlayer, $port);
955
			$results['TotalDamage'] += $results['Drones']['ActualDamage']['TotalDamage'];
956
		}
957
		$thisPlayer->increaseExperience(IRound($results['TotalDamage'] * self::EXP_PER_DAMAGE_PORT));
958
		$thisPlayer->increaseHOF($results['TotalDamage'], array('Combat', 'Port', 'Damage Done'), HOF_PUBLIC);
959
//		$thisPlayer->increaseHOF(1,array('Combat','Port','Shots')); //in SmrPortt::attackedBy()
960
961
		// Change alignment if we reach a damage threshold.
962
		// Increase if player and port races are at war; decrease otherwise.
963
		if ($results['TotalDamage'] >= SmrPort::DAMAGE_NEEDED_FOR_ALIGNMENT_CHANGE) {
964
			$relations = Globals::getRaceRelations($thisPlayer->getGameID(), $thisPlayer->getRaceID());
965
			if ($relations[$port->getRaceID()] <= RELATIONS_WAR) {
966
				$thisPlayer->increaseAlignment(1);
967
				$thisPlayer->increaseHOF(1, array('Combat', 'Port', 'Alignment', 'Gain'), HOF_PUBLIC);
968
			} else {
969
				$thisPlayer->decreaseAlignment(1);
970
				$thisPlayer->increaseHOF(1, array('Combat', 'Port', 'Alignment', 'Loss'), HOF_PUBLIC);
971
			}
972
		}
973
		return $results;
974
	}
975
976
	public function shootPlanet(SmrPlanet $planet, bool $delayed) : array {
977
		$thisPlayer = $this->getPlayer();
978
		$results = array('Player' => $thisPlayer, 'TotalDamage' => 0, 'Weapons' => []);
979
		if ($thisPlayer->isDead()) {
980
			$results['DeadBeforeShot'] = true;
981
			return $results;
982
		}
983
		$results['DeadBeforeShot'] = false;
984
		foreach ($this->weapons as $orderID => $weapon) {
985
			$results['Weapons'][$orderID] = $weapon->shootPlanet($thisPlayer, $planet, $delayed);
986
			if ($results['Weapons'][$orderID]['Hit']) {
987
				$results['TotalDamage'] += $results['Weapons'][$orderID]['ActualDamage']['TotalDamage'];
988
			}
989
		}
990
		if ($this->hasCDs()) {
991
			$thisCDs = new SmrCombatDrones($this->getCDs());
992
			$results['Drones'] = $thisCDs->shootPlanet($thisPlayer, $planet, $delayed);
993
			$results['TotalDamage'] += $results['Drones']['ActualDamage']['TotalDamage'];
994
		}
995
		$thisPlayer->increaseExperience(IRound($results['TotalDamage'] * self::EXP_PER_DAMAGE_PLANET));
996
		$thisPlayer->increaseHOF($results['TotalDamage'], array('Combat', 'Planet', 'Damage Done'), HOF_PUBLIC);
997
//		$thisPlayer->increaseHOF(1,array('Combat','Planet','Shots')); //in SmrPlanet::attackedBy()
998
		return $results;
999
	}
1000
1001
	public function doWeaponDamage(array $damage) : array {
1002
		$alreadyDead = $this->getPlayer()->isDead();
1003
		$armourDamage = 0;
1004
		$cdDamage = 0;
1005
		$shieldDamage = 0;
1006
		if (!$alreadyDead) {
1007
			// Even if the weapon doesn't do any damage, it was fired at the
1008
			// player, so alert them that they're under attack.
1009
			$this->getPlayer()->setUnderAttack(true);
1010
1011
			$shieldDamage = $this->doShieldDamage(min($damage['MaxDamage'], $damage['Shield']));
1012
			$damage['MaxDamage'] -= $shieldDamage;
1013
			if (!$this->hasShields() && ($shieldDamage == 0 || $damage['Rollover'])) {
1014
				$cdDamage = $this->doCDDamage(min($damage['MaxDamage'], $damage['Armour']));
1015
				$damage['Armour'] -= $cdDamage;
1016
				$damage['MaxDamage'] -= $cdDamage;
1017
				if (!$this->hasCDs() && ($cdDamage == 0 || $damage['Rollover'])) {
1018
					$armourDamage = $this->doArmourDamage(min($damage['MaxDamage'], $damage['Armour']));
1019
				}
1020
			}
1021
		}
1022
		return array(
1023
						'KillingShot' => !$alreadyDead && $this->isDead(),
1024
						'TargetAlreadyDead' => $alreadyDead,
1025
						'Shield' => $shieldDamage,
1026
						'CDs' => $cdDamage,
1027
						'NumCDs' => $cdDamage / CD_ARMOUR,
1028
						'Armour' => $armourDamage,
1029
						'HasCDs' => $this->hasCDs(),
1030
						'TotalDamage' => $shieldDamage + $cdDamage + $armourDamage
1031
		);
1032
	}
1033
1034
	public function doMinesDamage(array $damage) : array {
1035
		$alreadyDead = $this->getPlayer()->isDead();
1036
		$armourDamage = 0;
1037
		$cdDamage = 0;
1038
		$shieldDamage = 0;
1039
		if (!$alreadyDead) {
1040
			$shieldDamage = $this->doShieldDamage(min($damage['MaxDamage'], $damage['Shield']));
1041
			$damage['MaxDamage'] -= $shieldDamage;
1042
			if (!$this->hasShields() && ($shieldDamage == 0 || $damage['Rollover'])) { //skip CDs if it's mines
1043
				$armourDamage = $this->doArmourDamage(min($damage['MaxDamage'], $damage['Armour']));
1044
			}
1045
		}
1046
		return array(
1047
						'KillingShot' => !$alreadyDead && $this->isDead(),
1048
						'TargetAlreadyDead' => $alreadyDead,
1049
						'Shield' => $shieldDamage,
1050
						'CDs' => $cdDamage,
1051
						'NumCDs' => $cdDamage / CD_ARMOUR,
1052
						'Armour' => $armourDamage,
1053
						'HasCDs' => $this->hasCDs(),
1054
						'TotalDamage' => $shieldDamage + $cdDamage + $armourDamage
1055
		);
1056
	}
1057
1058
	protected function doShieldDamage(int $damage) : int {
1059
		$actualDamage = min($this->getShields(), $damage);
1060
		$this->decreaseShields($actualDamage);
1061
		return $actualDamage;
1062
	}
1063
1064
	protected function doCDDamage(int $damage) : int {
1065
		$actualDamage = min($this->getCDs(), IFloor($damage / CD_ARMOUR));
1066
		$this->decreaseCDs($actualDamage);
1067
		return $actualDamage * CD_ARMOUR;
1068
	}
1069
1070
	protected function doArmourDamage(int $damage) : int {
1071
		$actualDamage = min($this->getArmour(), $damage);
1072
		$this->decreaseArmour($actualDamage);
1073
		return $actualDamage;
1074
	}
1075
1076
	/**
1077
	 * Returns the maneuverability rating for this ship.
1078
	 */
1079
	public function getMR() : int {
1080
		//700 - [ (ship hit points / 25) + (ship stat factors) ]
1081
		//Minimum value of 0 because negative values cause issues with calculations calling this routine
1082
		return max(0, IRound(
1083
						700 -
1084
						(
1085
							(
1086
								$this->getShields()
1087
								+$this->getArmour()
1088
								+$this->getCDs() * 3
1089
							) / 25
1090
							+(
1091
								$this->getCargoHolds() / 100
1092
								-$this->getSpeed() * 5
1093
								+($this->getHardpoints()/*+$ship['Increases']['Ship Power']*/) * 5
1094
								/*+(
1095
									$ship['Increases']['Mines']
1096
									+$ship['Increases']['Scout Drones']
1097
								)/12*/
1098
								+$this->getCDs() / 5
1099
							)
1100
						)
1101
					)
1102
					);
1103
	}
1104
1105
}
1106