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

Passed
Push — master ( 44453e...299ad6 )
by Dan
04:44
created

AbstractSmrShip::getOldHardware()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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

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