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 — master ( ab9888...f5ba33 )
by Dan
25s queued 18s
created

AbstractSmrShip::getTypeID()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 2
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
9
	// Player exp gained for each point of damage done
10
	const EXP_PER_DAMAGE_PLAYER = 0.375;
11
	const EXP_PER_DAMAGE_PLANET = 1.0; // note that planet damage is reduced
12
	const EXP_PER_DAMAGE_PORT   = 0.15;
13
	const EXP_PER_DAMAGE_FORCE  = 0.075;
14
15
	const STARTER_SHIPS = [
16
		RACE_NEUTRAL => SHIP_TYPE_GALACTIC_SEMI,
17
		RACE_ALSKANT => SHIP_TYPE_SMALL_TIMER,
18
		RACE_CREONTI => SHIP_TYPE_MEDIUM_CARGO_HULK,
19
		RACE_HUMAN => SHIP_TYPE_LIGHT_FREIGHTER,
20
		RACE_IKTHORNE => SHIP_TYPE_TINY_DELIGHT,
21
		RACE_SALVENE => SHIP_TYPE_HATCHLINGS_DUE,
22
		RACE_THEVIAN => SHIP_TYPE_SWIFT_VENTURE,
23
		RACE_WQHUMAN => SHIP_TYPE_SLIP_FREIGHTER,
24
		RACE_NIJARIN => SHIP_TYPE_REDEEMER,
25
	];
26
27
	protected AbstractSmrPlayer $player;
28
29
	protected int $gameID;
30
	protected SmrShipType $shipType;
31
32
	protected array $weapons = [];
33
	protected array $cargo = [];
34
	protected array $hardware = [];
35
	protected bool $isCloaked = false;
36
	protected array|false $illusionShip = false;
37
38
	protected bool $hasChangedWeapons = false;
39
	protected bool $hasChangedCargo = false;
40
	protected array $hasChangedHardware = [];
41
	protected bool $hasChangedCloak = false;
42
	protected bool $hasChangedIllusion = false;
43
44
	public function __construct(AbstractSmrPlayer $player) {
45
		$this->player = $player;
46
		$this->gameID = $player->getGameID();
47
		$this->regenerateShipType();
48
	}
49
50
	protected function regenerateShipType() : void {
51
		$this->shipType = SmrShipType::get($this->player->getShipTypeID());
52
	}
53
54
	public function checkForExcess() : void {
55
		$this->checkForExcessHardware();
56
		$this->checkForExcessWeapons();
57
		$this->checkForExcessCargo();
58
	}
59
60
	public function checkForExcessWeapons() : void {
61
		while ($this->hasWeapons() && ($this->getPowerUsed() > $this->getType()->getMaxPower() || $this->getNumWeapons() > $this->getHardpoints())) {
62
			//erase the first weapon 1 at a time until we are okay
63
			$this->removeLastWeapon();
64
		}
65
	}
66
67
	public function checkForExcessCargo() : void {
68
		if ($this->hasCargo()) {
69
			$excess = array_sum($this->getCargo()) - $this->getCargoHolds();
70
			foreach ($this->getCargo() as $goodID => $amount) {
71
				if ($excess > 0) {
72
					$decreaseAmount = min($amount, $excess);
73
					$this->decreaseCargo($goodID, $decreaseAmount);
74
					$excess -= $decreaseAmount;
75
				} else {
76
					// No more excess cargo
77
					break;
78
				}
79
			}
80
		}
81
	}
82
83
	public function checkForExcessHardware() : void {
84
		//check hardware to see if anything needs to be removed
85
		foreach ($this->getHardware() as $hardwareTypeID => $amount) {
86
			$max = $this->shipType->getMaxHardware($hardwareTypeID);
87
			$this->setHardware($hardwareTypeID, min($amount, $max));
88
		}
89
	}
90
91
	/**
92
	 * Set all hardware to its maximum value for this ship.
93
	 */
94
	public function setHardwareToMax() : void {
95
		foreach ($this->shipType->getAllMaxHardware() as $hardwareTypeID => $max) {
96
			$this->setHardware($hardwareTypeID, $max);
97
		}
98
	}
99
100
	public function getPowerUsed() : int {
101
		$power = 0;
102
		foreach ($this->weapons as $weapon) {
103
			$power += $weapon->getPowerLevel();
104
		}
105
		return $power;
106
	}
107
108
	public function getRemainingPower() : int {
109
		return $this->getType()->getMaxPower() - $this->getPowerUsed();
110
	}
111
112
	/**
113
	 * given power level of new weapon, return whether there is enough power available to install it on this ship
114
	 */
115
	public function checkPowerAvailable(int $powerLevel) : bool {
116
		return $this->getRemainingPower() >= $powerLevel;
117
	}
118
119
	public function hasIllegalGoods() : bool {
120
		return $this->hasCargo(GOODS_SLAVES) || $this->hasCargo(GOODS_WEAPONS) || $this->hasCargo(GOODS_NARCOTICS);
121
	}
122
123
	public function getDisplayAttackRating() : int {
124
		if ($this->hasActiveIllusion()) {
125
			return $this->getIllusionAttack();
126
		} else {
127
			return $this->getAttackRating();
128
		}
129
	}
130
131
	public function getDisplayDefenseRating() : int {
132
		if ($this->hasActiveIllusion()) {
133
			return $this->getIllusionDefense();
134
		} else {
135
			return $this->getDefenseRating();
136
		}
137
	}
138
139
	public function getDisplayName() : string {
140
		if ($this->hasActiveIllusion()) {
141
			return $this->getIllusionShipName();
142
		} else {
143
			return $this->getName();
144
		}
145
	}
146
147
	public function getAttackRating() : int {
148
		return IRound(($this->getTotalShieldDamage() + $this->getTotalArmourDamage() + $this->getCDs() * 2) / 40);
149
	}
150
151
	public function getAttackRatingWithMaxCDs() : int {
152
		return IRound(($this->getTotalShieldDamage() + $this->getTotalArmourDamage() + $this->getMaxCDs() * .7) / 40);
153
	}
154
155
	public function getDefenseRating() : int {
156
		return IRound((($this->getShields() + $this->getArmour()) / 100) + (($this->getCDs() * 3) / 100));
157
	}
158
159
	public function getMaxDefenseRating() : int {
160
		return IRound((($this->getMaxShields() + $this->getMaxArmour()) / 100) + (($this->getMaxCDs() * 3) / 100));
161
	}
162
163
	public function getShieldLow() : int { return IFloor($this->getShields() / 100) * 100; }
164
	public function getShieldHigh() : int { return $this->getShieldLow() + 100; }
165
	public function getArmourLow() : int { return IFloor($this->getArmour() / 100) * 100; }
166
	public function getArmourHigh() : int { return $this->getArmourLow() + 100; }
167
	public function getCDsLow() : int { return IFloor($this->getCDs() / 100) * 100; }
168
	public function getCDsHigh() : int { return $this->getCDsLow() + 100; }
169
170
171
172
	public function addWeapon(SmrWeapon $weapon) : SmrWeapon|false {
173
		if ($this->hasOpenWeaponSlots() && $this->checkPowerAvailable($weapon->getPowerLevel())) {
174
			array_push($this->weapons, $weapon);
175
			$this->hasChangedWeapons = true;
176
			return $weapon;
177
		}
178
		return false;
179
	}
180
181
	public function moveWeaponUp(int $orderID) : void {
182
		$replacement = $orderID - 1;
183
		if ($replacement < 0) {
184
			// Shift everything up by one and put the selected weapon at the bottom
185
			array_push($this->weapons, array_shift($this->weapons));
186
		} else {
187
			// Swap the selected weapon with the one above it
188
			$temp = $this->weapons[$replacement];
189
			$this->weapons[$replacement] = $this->weapons[$orderID];
190
			$this->weapons[$orderID] = $temp;
191
		}
192
		$this->hasChangedWeapons = true;
193
	}
194
195
	public function moveWeaponDown(int $orderID) : void {
196
		$replacement = $orderID + 1;
197
		if ($replacement >= count($this->weapons)) {
198
			// Shift everything down by one and put the selected weapon at the top
199
			array_unshift($this->weapons, array_pop($this->weapons));
200
		} else {
201
			// Swap the selected weapon with the one below it
202
			$temp = $this->weapons[$replacement];
203
			$this->weapons[$replacement] = $this->weapons[$orderID];
204
			$this->weapons[$orderID] = $temp;
205
		}
206
		$this->hasChangedWeapons = true;
207
	}
208
209
	public function setWeaponLocations(array $orderArray) : void {
210
		$weapons = $this->weapons;
211
		foreach ($orderArray as $newOrder => $oldOrder) {
212
			$this->weapons[$newOrder] = $weapons[$oldOrder];
213
		}
214
		$this->hasChangedWeapons = true;
215
	}
216
217
	public function removeLastWeapon() : void {
218
		$this->removeWeapon($this->getNumWeapons() - 1);
219
	}
220
221
	public function removeWeapon(int $orderID) : void {
222
		// Remove the specified weapon, then reindex the array
223
		unset($this->weapons[$orderID]);
224
		$this->weapons = array_values($this->weapons);
225
		$this->hasChangedWeapons = true;
226
	}
227
228
	public function removeAllWeapons() : void {
229
		$this->weapons = array();
230
		$this->hasChangedWeapons = true;
231
	}
232
233
	public function removeAllCargo() : void {
234
		foreach ($this->cargo as $goodID => $amount) {
235
			$this->setCargo($goodID, 0);
236
		}
237
	}
238
239
	public function removeAllHardware() : void {
240
		foreach (array_keys($this->hardware) as $hardwareTypeID) {
241
			$this->hasChangedHardware[$hardwareTypeID] = true;
242
		}
243
		$this->hardware = [];
244
		$this->decloak();
245
		$this->disableIllusion();
246
	}
247
248
	public function getPod(bool $isNewbie = false) : void {
249
		$this->removeAllWeapons();
250
		$this->removeAllCargo();
251
		$this->removeAllHardware();
252
253
		if ($isNewbie) {
254
			$this->setShields(75);
255
			$this->setArmour(150);
256
			$this->setCargoHolds(40);
257
			$this->setTypeID(SHIP_TYPE_NEWBIE_MERCHANT_VESSEL);
258
		} else {
259
			$this->setShields(50);
260
			$this->setArmour(50);
261
			$this->setCargoHolds(5);
262
			$this->setTypeID(SHIP_TYPE_ESCAPE_POD);
263
		}
264
	}
265
266
	public function giveStarterShip() : void {
267
		if ($this->player->hasNewbieStatus()) {
268
			$shipID = SHIP_TYPE_NEWBIE_MERCHANT_VESSEL;
269
			$amount_shields = 75;
270
			$amount_armour = 150;
271
		} else {
272
			$shipID = self::STARTER_SHIPS[$this->player->getRaceID()];
273
			$amount_shields = 50;
274
			$amount_armour = 50;
275
		}
276
		$this->setTypeID($shipID);
277
		$this->setShields($amount_shields);
278
		$this->setArmour($amount_armour);
279
		$this->setCargoHolds(40);
280
		$this->addWeapon(SmrWeapon::getWeapon(WEAPON_TYPE_LASER));
281
	}
282
283
	public function hasJump() : bool {
284
		return $this->getHardware(HARDWARE_JUMP) > 0;
285
	}
286
287
	public function hasDCS() : bool {
288
		return $this->getHardware(HARDWARE_DCS) > 0;
289
	}
290
291
	public function hasScanner() : bool {
292
		return $this->getHardware(HARDWARE_SCANNER) > 0;
293
	}
294
295
	public function hasCloak() : bool {
296
		return $this->getHardware(HARDWARE_CLOAK) > 0;
297
	}
298
299
	public function isCloaked() : bool {
300
		return $this->isCloaked;
301
	}
302
303
	public function decloak() : void {
304
		if ($this->isCloaked === false) {
305
			return;
306
		}
307
		$this->isCloaked = false;
308
		$this->hasChangedCloak = true;
309
	}
310
311
	public function enableCloak() : void {
312
		if ($this->hasCloak() === false) {
313
			throw new Exception('Ship does not have the supported hardware!');
314
		}
315
		if ($this->isCloaked === true) {
316
			return;
317
		}
318
		$this->isCloaked = true;
319
		$this->hasChangedCloak = true;
320
	}
321
322
	public function hasIllusion() : bool {
323
		return $this->getHardware(HARDWARE_ILLUSION) > 0;
324
	}
325
326
	public function getIllusionShip() : array|false {
327
		return $this->illusionShip;
328
	}
329
330
	public function hasActiveIllusion() : bool {
331
		return $this->getIllusionShip() !== false;
332
	}
333
334
	public function setIllusion(int $shipTypeID, int $attack, int $defense) : void {
335
		if ($this->hasIllusion() === false) {
336
			throw new Exception('Ship does not have the supported hardware!');
337
		}
338
		$newIllusionShip = [
339
			'ID' => $shipTypeID,
340
			'Attack' => $attack,
341
			'Defense' => $defense,
342
		];
343
		if ($this->getIllusionShip() === $newIllusionShip) {
344
			return;
345
		}
346
		$this->illusionShip = $newIllusionShip;
347
		$this->hasChangedIllusion = true;
348
	}
349
350
	public function disableIllusion() : void {
351
		if ($this->getIllusionShip() === false) {
352
			return;
353
		}
354
		$this->illusionShip = false;
355
		$this->hasChangedIllusion = true;
356
	}
357
358
	public function getIllusionShipID() : int {
359
		return $this->getIllusionShip()['ID'];
360
	}
361
362
	public function getIllusionShipName() : string {
363
		return SmrShipType::get($this->getIllusionShip()['ID'])->getName();
364
	}
365
366
	public function getIllusionAttack() : int {
367
		return $this->getIllusionShip()['Attack'];
368
	}
369
370
	public function getIllusionDefense() : int {
371
		return $this->getIllusionShip()['Defense'];
372
	}
373
374
	public function getPlayer() : AbstractSmrPlayer {
375
		return $this->player;
376
	}
377
378
	public function getAccountID() : int {
379
		return $this->getPlayer()->getAccountID();
380
	}
381
382
	public function getGameID() : int {
383
		return $this->gameID;
384
	}
385
386
	public function getGame() : SmrGame {
387
		return SmrGame::getGame($this->gameID);
388
	}
389
390
	/**
391
	 * Switch to a new ship, updating player turns accordingly.
392
	 */
393
	public function setTypeID(int $shipTypeID) : void {
394
		$oldSpeed = $this->shipType->getSpeed();
395
		$this->getPlayer()->setShipTypeID($shipTypeID);
396
		$this->regenerateShipType();
397
		$this->checkForExcess();
398
399
		// Update the player's turns to account for the speed change
400
		$newSpeed = $this->shipType->getSpeed();
401
		$oldTurns = $this->getPlayer()->getTurns();
402
		$this->getPlayer()->setTurns(IRound($oldTurns * $newSpeed / $oldSpeed));
403
	}
404
405
	public function getType() : SmrShipType {
406
		return $this->shipType;
407
	}
408
409
	public function getTypeID() : int {
410
		return $this->shipType->getTypeID();
411
	}
412
413
	public function getClassID() : int {
414
		return $this->shipType->getClassID();
415
	}
416
417
	public function getName() : string {
418
		return $this->shipType->getName();
419
	}
420
421
	public function getCost() : int {
422
		return $this->shipType->getCost();
423
	}
424
425
	public function getHardpoints() : int {
426
		return $this->shipType->getHardpoints();
427
	}
428
429
	/**
430
	 * Trade-in value of the ship
431
	 */
432
	public function getRefundValue() : int {
433
		return IFloor($this->getCost() * SHIP_REFUND_PERCENT);
434
	}
435
436
	public function getCostToUpgrade(int $otherShipTypeID) : int {
437
		$otherShipType = SmrShipType::get($otherShipTypeID);
438
		return $otherShipType->getCost() - $this->getRefundValue();
439
	}
440
441
	public function getCostToUpgradeAndUNO(int $otherShipTypeID) : int {
442
		return $this->getCostToUpgrade($otherShipTypeID) + $this->getCostToUNOAgainstShip($otherShipTypeID);
443
	}
444
445
	protected function getCostToUNOAgainstShip(int $otherShipTypeID) : int {
446
		$otherShipType = SmrShipType::get($otherShipTypeID);
447
		$cost = 0;
448
		$hardwareTypes = array(HARDWARE_SHIELDS, HARDWARE_ARMOUR, HARDWARE_CARGO);
449
		foreach ($hardwareTypes as $hardwareTypeID) {
450
			$cost += max(0, $otherShipType->getMaxHardware($hardwareTypeID) - $this->getHardware($hardwareTypeID)) * Globals::getHardwareCost($hardwareTypeID);
451
		}
452
		return $cost;
453
	}
454
455
	public function getCostToUNO() : int {
456
		return $this->getCostToUNOAgainstShip($this->getTypeID());
457
	}
458
459
	/**
460
	 * Returns the ship speed modified by the game speed.
461
	 */
462
	public function getRealSpeed() : float {
463
		return $this->shipType->getSpeed() * $this->getGame()->getGameSpeed();
464
	}
465
466
	public function getHardware(int $hardwareTypeID = null) : array|int {
467
		if ($hardwareTypeID === null) {
468
			return $this->hardware;
469
		}
470
		return $this->hardware[$hardwareTypeID] ?? 0;
471
	}
472
473
	public function setHardware(int $hardwareTypeID, int $amount) : void {
474
		if ($this->getHardware($hardwareTypeID) === $amount) {
475
			return;
476
		}
477
		$this->hardware[$hardwareTypeID] = $amount;
478
		$this->hasChangedHardware[$hardwareTypeID] = true;
479
	}
480
481
	public function increaseHardware(int $hardwareTypeID, int $amount) : void {
482
		$this->setHardware($hardwareTypeID, $this->getHardware($hardwareTypeID) + $amount);
483
	}
484
485
	public function hasMaxHardware(int $hardwareTypeID) : bool {
486
		return $this->getHardware($hardwareTypeID) == $this->shipType->getMaxHardware($hardwareTypeID);
487
	}
488
489
	public function getShields() : int {
490
		return $this->getHardware(HARDWARE_SHIELDS);
491
	}
492
493
	public function setShields(int $amount) : void {
494
		$this->setHardware(HARDWARE_SHIELDS, $amount);
495
	}
496
497
	public function decreaseShields(int $amount) : void {
498
		$this->setShields($this->getShields() - $amount);
499
	}
500
501
	public function increaseShields(int $amount) : void {
502
		$this->setShields($this->getShields() + $amount);
503
	}
504
505
	public function hasShields() : bool {
506
		return $this->getShields() > 0;
507
	}
508
509
	public function hasMaxShields() : bool {
510
		return $this->hasMaxHardware(HARDWARE_SHIELDS);
511
	}
512
513
	public function getMaxShields() : int {
514
		return $this->shipType->getMaxHardware(HARDWARE_SHIELDS);
515
	}
516
517
	public function getArmour() : int {
518
		return $this->getHardware(HARDWARE_ARMOUR);
519
	}
520
521
	public function setArmour(int $amount) : void {
522
		$this->setHardware(HARDWARE_ARMOUR, $amount);
523
	}
524
525
	public function decreaseArmour(int $amount) : void {
526
		$this->setArmour($this->getArmour() - $amount);
527
	}
528
529
	public function increaseArmour(int $amount) : void {
530
		$this->setArmour($this->getArmour() + $amount);
531
	}
532
533
	public function hasArmour() : bool {
534
		return $this->getArmour() > 0;
535
	}
536
537
	public function hasMaxArmour() : bool {
538
		return $this->hasMaxHardware(HARDWARE_ARMOUR);
539
	}
540
541
	public function getMaxArmour() : int {
542
		return $this->shipType->getMaxHardware(HARDWARE_ARMOUR);
543
	}
544
545
	public function isDead() : bool {
546
		return !$this->hasArmour() && !$this->hasShields();
547
	}
548
549
	public function hasMaxCDs() : bool {
550
		return $this->hasMaxHardware(HARDWARE_COMBAT);
551
	}
552
553
	public function hasMaxSDs() : bool {
554
		return $this->hasMaxHardware(HARDWARE_SCOUT);
555
	}
556
557
	public function hasMaxMines() : bool {
558
		return $this->hasMaxHardware(HARDWARE_MINE);
559
	}
560
561
	public function hasCDs() : bool {
562
		return $this->getCDs() > 0;
563
	}
564
565
	public function hasSDs() : bool {
566
		return $this->getSDs() > 0;
567
	}
568
569
	public function hasMines() : bool {
570
		return $this->getMines() > 0;
571
	}
572
573
	public function getCDs() : int {
574
		return $this->getHardware(HARDWARE_COMBAT);
575
	}
576
577
	public function setCDs(int $amount) : void {
578
		$this->setHardware(HARDWARE_COMBAT, $amount);
579
	}
580
581
	public function decreaseCDs(int $amount) : void {
582
		$this->setCDs($this->getCDs() - $amount);
583
	}
584
585
	public function increaseCDs(int $amount) : void {
586
		$this->setCDs($this->getCDs() + $amount);
587
	}
588
589
	public function getMaxCDs() : int {
590
		return $this->shipType->getMaxHardware(HARDWARE_COMBAT);
591
	}
592
593
	public function getSDs() : int {
594
		return $this->getHardware(HARDWARE_SCOUT);
595
	}
596
597
	public function setSDs(int $amount) : void {
598
		$this->setHardware(HARDWARE_SCOUT, $amount);
599
	}
600
601
	public function decreaseSDs(int $amount) : void {
602
		$this->setSDs($this->getSDs() - $amount);
603
	}
604
605
	public function increaseSDs(int $amount) : void {
606
		$this->setSDs($this->getSDs() + $amount);
607
	}
608
609
	public function getMaxSDs() : int {
610
		return $this->shipType->getMaxHardware(HARDWARE_SCOUT);
611
	}
612
613
	public function getMines() : int {
614
		return $this->getHardware(HARDWARE_MINE);
615
	}
616
617
	public function setMines(int $amount) : void {
618
		$this->setHardware(HARDWARE_MINE, $amount);
619
	}
620
621
	public function decreaseMines(int $amount) : void {
622
		$this->setMines($this->getMines() - $amount);
623
	}
624
625
	public function increaseMines(int $amount) : void {
626
		$this->setMines($this->getMines() + $amount);
627
	}
628
629
	public function getMaxMines() : int {
630
		return $this->shipType->getMaxHardware(HARDWARE_MINE);
631
	}
632
633
	public function getCargoHolds() : int {
634
		return $this->getHardware(HARDWARE_CARGO);
635
	}
636
637
	public function setCargoHolds(int $amount) : void {
638
		$this->setHardware(HARDWARE_CARGO, $amount);
639
	}
640
641
	public function getCargo(int $goodID = null) : int|array {
642
		if ($goodID === null) {
643
			return $this->cargo;
644
		}
645
		return $this->cargo[$goodID] ?? 0;
646
	}
647
648
	public function hasCargo(int $goodID = null) : bool {
649
		if ($goodID === null) {
650
			return $this->getUsedHolds() > 0;
651
		}
652
		return $this->getCargo($goodID) > 0;
653
	}
654
655
	public function setCargo(int $goodID, int $amount) : void {
656
		if ($this->getCargo($goodID) === $amount) {
657
			return;
658
		}
659
		$this->cargo[$goodID] = $amount;
660
		$this->hasChangedCargo = true;
661
		// Sort cargo by goodID to make sure it shows up in the correct order
662
		// before the next page is loaded.
663
		ksort($this->cargo);
664
	}
665
666
	public function decreaseCargo(int $goodID, int $amount) : void {
667
		if ($amount < 0) {
668
			throw new Exception('Trying to decrease negative cargo.');
669
		}
670
		$this->setCargo($goodID, $this->getCargo($goodID) - $amount);
671
	}
672
673
	public function increaseCargo(int $goodID, int $amount) : void {
674
		if ($amount < 0) {
675
			throw new Exception('Trying to increase negative cargo.');
676
		}
677
		$this->setCargo($goodID, $this->getCargo($goodID) + $amount);
678
	}
679
680
	public function getEmptyHolds() : int {
681
		return $this->getCargoHolds() - $this->getUsedHolds();
682
	}
683
684
	public function getUsedHolds() : int {
685
		return array_sum($this->getCargo());
686
	}
687
688
	public function hasMaxCargoHolds() : bool {
689
		return $this->hasMaxHardware(HARDWARE_CARGO);
690
	}
691
692
	public function getMaxCargoHolds() : int {
693
		return $this->shipType->getMaxHardware(HARDWARE_CARGO);
694
	}
695
696
	public function hasWeapons() : bool {
697
		return $this->getNumWeapons() > 0;
698
	}
699
700
	public function getWeapons() : array {
701
		return $this->weapons;
702
	}
703
704
	public function canAttack() : bool {
705
		return $this->hasWeapons() || $this->hasCDs();
706
	}
707
708
	public function getNumWeapons() : int {
709
		return count($this->getWeapons());
710
	}
711
712
	public function getOpenWeaponSlots() : int {
713
		return $this->getHardpoints() - $this->getNumWeapons();
714
	}
715
716
	public function hasOpenWeaponSlots() : bool {
717
		return $this->getOpenWeaponSlots() > 0;
718
	}
719
720
	public function getTotalShieldDamage() : int {
721
		$shieldDamage = 0;
722
		foreach ($this->getWeapons() as $weapon) {
723
			$shieldDamage += $weapon->getShieldDamage();
724
		}
725
		return $shieldDamage;
726
	}
727
728
	public function getTotalArmourDamage() : int {
729
		$armourDamage = 0;
730
		foreach ($this->getWeapons() as $weapon) {
731
			$armourDamage += $weapon->getArmourDamage();
732
		}
733
		return $armourDamage;
734
	}
735
736
	public function isFederal() : bool {
737
		return $this->getTypeID() === SHIP_TYPE_FEDERAL_DISCOVERY ||
738
		       $this->getTypeID() === SHIP_TYPE_FEDERAL_WARRANT ||
739
		       $this->getTypeID() === SHIP_TYPE_FEDERAL_ULTIMATUM;
740
	}
741
742
	public function isUnderground() : bool {
743
		return $this->getTypeID() === SHIP_TYPE_THIEF ||
744
		       $this->getTypeID() === SHIP_TYPE_ASSASSIN ||
745
		       $this->getTypeID() === SHIP_TYPE_DEATH_CRUISER;
746
	}
747
748
	public function shootPlayers(array $targetPlayers) : array {
749
		$thisPlayer = $this->getPlayer();
750
		$results = array('Player' => $thisPlayer, 'TotalDamage' => 0, 'Weapons' => []);
751
		if ($thisPlayer->isDead()) {
752
			$results['DeadBeforeShot'] = true;
753
			return $results;
754
		}
755
		$results['DeadBeforeShot'] = false;
756
		foreach ($this->weapons as $orderID => $weapon) {
757
			$results['Weapons'][$orderID] = $weapon->shootPlayer($thisPlayer, array_rand_value($targetPlayers));
758
			if ($results['Weapons'][$orderID]['Hit']) {
759
				$results['TotalDamage'] += $results['Weapons'][$orderID]['ActualDamage']['TotalDamage'];
760
			}
761
		}
762
		if ($this->hasCDs()) {
763
			$thisCDs = new SmrCombatDrones($this->getCDs());
764
			$results['Drones'] = $thisCDs->shootPlayer($thisPlayer, array_rand_value($targetPlayers));
765
			$results['TotalDamage'] += $results['Drones']['ActualDamage']['TotalDamage'];
766
		}
767
		$thisPlayer->increaseExperience(IRound($results['TotalDamage'] * self::EXP_PER_DAMAGE_PLAYER));
768
		$thisPlayer->increaseHOF($results['TotalDamage'], array('Combat', 'Player', 'Damage Done'), HOF_PUBLIC);
769
		$thisPlayer->increaseHOF(1, array('Combat', 'Player', 'Shots'), HOF_PUBLIC);
770
		return $results;
771
	}
772
773
	public function shootForces(SmrForce $forces) : array {
774
		$thisPlayer = $this->getPlayer();
775
		$results = array('Player' => $thisPlayer, 'TotalDamage' => 0, 'Weapons' => []);
776
		if ($thisPlayer->isDead()) {
777
			$results['DeadBeforeShot'] = true;
778
			return $results;
779
		}
780
		$results['DeadBeforeShot'] = false;
781
		foreach ($this->weapons as $orderID => $weapon) {
782
			$results['Weapons'][$orderID] = $weapon->shootForces($thisPlayer, $forces);
783
			if ($results['Weapons'][$orderID]['Hit']) {
784
				$results['TotalDamage'] += $results['Weapons'][$orderID]['ActualDamage']['TotalDamage'];
785
				$thisPlayer->increaseHOF($results['Weapons'][$orderID]['ActualDamage']['NumMines'], array('Combat', 'Forces', 'Mines', 'Killed'), HOF_PUBLIC);
786
				$thisPlayer->increaseHOF($results['Weapons'][$orderID]['ActualDamage']['Mines'], array('Combat', 'Forces', 'Mines', 'Damage Done'), HOF_PUBLIC);
787
				$thisPlayer->increaseHOF($results['Weapons'][$orderID]['ActualDamage']['NumCDs'], array('Combat', 'Forces', 'Combat Drones', 'Killed'), HOF_PUBLIC);
788
				$thisPlayer->increaseHOF($results['Weapons'][$orderID]['ActualDamage']['CDs'], array('Combat', 'Forces', 'Combat Drones', 'Damage Done'), HOF_PUBLIC);
789
				$thisPlayer->increaseHOF($results['Weapons'][$orderID]['ActualDamage']['NumSDs'], array('Combat', 'Forces', 'Scout Drones', 'Killed'), HOF_PUBLIC);
790
				$thisPlayer->increaseHOF($results['Weapons'][$orderID]['ActualDamage']['SDs'], array('Combat', 'Forces', 'Scout Drones', 'Damage Done'), HOF_PUBLIC);
791
				$thisPlayer->increaseHOF($results['Weapons'][$orderID]['ActualDamage']['NumMines'] + $results['Weapons'][$orderID]['ActualDamage']['NumCDs'] + $results['Weapons'][$orderID]['ActualDamage']['NumSDs'], array('Combat', 'Forces', 'Killed'), HOF_PUBLIC);
792
			}
793
		}
794
		if ($this->hasCDs()) {
795
			$thisCDs = new SmrCombatDrones($this->getCDs());
796
			$results['Drones'] = $thisCDs->shootForces($thisPlayer, $forces);
797
			$results['TotalDamage'] += $results['Drones']['ActualDamage']['TotalDamage'];
798
			$thisPlayer->increaseHOF($results['Drones']['ActualDamage']['NumMines'], array('Combat', 'Forces', 'Mines', 'Killed'), HOF_PUBLIC);
799
			$thisPlayer->increaseHOF($results['Drones']['ActualDamage']['Mines'], array('Combat', 'Forces', 'Mines', 'Damage Done'), HOF_PUBLIC);
800
			$thisPlayer->increaseHOF($results['Drones']['ActualDamage']['NumCDs'], array('Combat', 'Forces', 'Combat Drones', 'Killed'), HOF_PUBLIC);
801
			$thisPlayer->increaseHOF($results['Drones']['ActualDamage']['CDs'], array('Combat', 'Forces', 'Combat Drones', 'Damage Done'), HOF_PUBLIC);
802
			$thisPlayer->increaseHOF($results['Drones']['ActualDamage']['NumSDs'], array('Combat', 'Forces', 'Scout Drones', 'Killed'), HOF_PUBLIC);
803
			$thisPlayer->increaseHOF($results['Drones']['ActualDamage']['SDs'], array('Combat', 'Forces', 'Scout Drones', 'Damage Done'), HOF_PUBLIC);
804
			$thisPlayer->increaseHOF($results['Drones']['ActualDamage']['NumMines'] + $results['Drones']['ActualDamage']['NumCDs'] + $results['Drones']['ActualDamage']['NumSDs'], array('Combat', 'Forces', 'Killed'), HOF_PUBLIC);
805
		}
806
		$thisPlayer->increaseExperience(IRound($results['TotalDamage'] * self::EXP_PER_DAMAGE_FORCE));
807
		$thisPlayer->increaseHOF($results['TotalDamage'], array('Combat', 'Forces', 'Damage Done'), HOF_PUBLIC);
808
		$thisPlayer->increaseHOF(1, array('Combat', 'Forces', 'Shots'), HOF_PUBLIC);
809
		return $results;
810
	}
811
812
	public function shootPort(SmrPort $port) : array {
813
		$thisPlayer = $this->getPlayer();
814
		$results = array('Player' => $thisPlayer, 'TotalDamage' => 0, 'Weapons' => []);
815
		if ($thisPlayer->isDead()) {
816
			$results['DeadBeforeShot'] = true;
817
			return $results;
818
		}
819
		$results['DeadBeforeShot'] = false;
820
		foreach ($this->weapons as $orderID => $weapon) {
821
			$results['Weapons'][$orderID] = $weapon->shootPort($thisPlayer, $port);
822
			if ($results['Weapons'][$orderID]['Hit']) {
823
				$results['TotalDamage'] += $results['Weapons'][$orderID]['ActualDamage']['TotalDamage'];
824
			}
825
		}
826
		if ($this->hasCDs()) {
827
			$thisCDs = new SmrCombatDrones($this->getCDs());
828
			$results['Drones'] = $thisCDs->shootPort($thisPlayer, $port);
829
			$results['TotalDamage'] += $results['Drones']['ActualDamage']['TotalDamage'];
830
		}
831
		$thisPlayer->increaseExperience(IRound($results['TotalDamage'] * self::EXP_PER_DAMAGE_PORT));
832
		$thisPlayer->increaseHOF($results['TotalDamage'], array('Combat', 'Port', 'Damage Done'), HOF_PUBLIC);
833
//		$thisPlayer->increaseHOF(1,array('Combat','Port','Shots')); //in SmrPortt::attackedBy()
834
835
		// Change alignment if we reach a damage threshold.
836
		// Increase if player and port races are at war; decrease otherwise.
837
		if ($results['TotalDamage'] >= SmrPort::DAMAGE_NEEDED_FOR_ALIGNMENT_CHANGE) {
838
			$relations = Globals::getRaceRelations($thisPlayer->getGameID(), $thisPlayer->getRaceID());
839
			if ($relations[$port->getRaceID()] <= RELATIONS_WAR) {
840
				$thisPlayer->increaseAlignment(1);
841
				$thisPlayer->increaseHOF(1, array('Combat', 'Port', 'Alignment', 'Gain'), HOF_PUBLIC);
842
			} else {
843
				$thisPlayer->decreaseAlignment(1);
844
				$thisPlayer->increaseHOF(1, array('Combat', 'Port', 'Alignment', 'Loss'), HOF_PUBLIC);
845
			}
846
		}
847
		return $results;
848
	}
849
850
	public function shootPlanet(SmrPlanet $planet, bool $delayed) : array {
851
		$thisPlayer = $this->getPlayer();
852
		$results = array('Player' => $thisPlayer, 'TotalDamage' => 0, 'Weapons' => []);
853
		if ($thisPlayer->isDead()) {
854
			$results['DeadBeforeShot'] = true;
855
			return $results;
856
		}
857
		$results['DeadBeforeShot'] = false;
858
		foreach ($this->weapons as $orderID => $weapon) {
859
			$results['Weapons'][$orderID] = $weapon->shootPlanet($thisPlayer, $planet, $delayed);
860
			if ($results['Weapons'][$orderID]['Hit']) {
861
				$results['TotalDamage'] += $results['Weapons'][$orderID]['ActualDamage']['TotalDamage'];
862
			}
863
		}
864
		if ($this->hasCDs()) {
865
			$thisCDs = new SmrCombatDrones($this->getCDs());
866
			$results['Drones'] = $thisCDs->shootPlanet($thisPlayer, $planet, $delayed);
867
			$results['TotalDamage'] += $results['Drones']['ActualDamage']['TotalDamage'];
868
		}
869
		$thisPlayer->increaseExperience(IRound($results['TotalDamage'] * self::EXP_PER_DAMAGE_PLANET));
870
		$thisPlayer->increaseHOF($results['TotalDamage'], array('Combat', 'Planet', 'Damage Done'), HOF_PUBLIC);
871
//		$thisPlayer->increaseHOF(1,array('Combat','Planet','Shots')); //in SmrPlanet::attackedBy()
872
		return $results;
873
	}
874
875
	public function doWeaponDamage(array $damage) : array {
876
		$alreadyDead = $this->getPlayer()->isDead();
877
		$armourDamage = 0;
878
		$cdDamage = 0;
879
		$shieldDamage = 0;
880
		if (!$alreadyDead) {
881
			// Even if the weapon doesn't do any damage, it was fired at the
882
			// player, so alert them that they're under attack.
883
			$this->getPlayer()->setUnderAttack(true);
884
885
			$shieldDamage = $this->doShieldDamage(min($damage['MaxDamage'], $damage['Shield']));
886
			$damage['MaxDamage'] -= $shieldDamage;
887
			if (!$this->hasShields() && ($shieldDamage == 0 || $damage['Rollover'])) {
888
				$cdDamage = $this->doCDDamage(min($damage['MaxDamage'], $damage['Armour']));
889
				$damage['Armour'] -= $cdDamage;
890
				$damage['MaxDamage'] -= $cdDamage;
891
				if (!$this->hasCDs() && ($cdDamage == 0 || $damage['Rollover'])) {
892
					$armourDamage = $this->doArmourDamage(min($damage['MaxDamage'], $damage['Armour']));
893
				}
894
			}
895
		}
896
		return array(
897
						'KillingShot' => !$alreadyDead && $this->isDead(),
898
						'TargetAlreadyDead' => $alreadyDead,
899
						'Shield' => $shieldDamage,
900
						'CDs' => $cdDamage,
901
						'NumCDs' => $cdDamage / CD_ARMOUR,
902
						'Armour' => $armourDamage,
903
						'HasCDs' => $this->hasCDs(),
904
						'TotalDamage' => $shieldDamage + $cdDamage + $armourDamage
905
		);
906
	}
907
908
	public function doMinesDamage(array $damage) : array {
909
		$alreadyDead = $this->getPlayer()->isDead();
910
		$armourDamage = 0;
911
		$cdDamage = 0;
912
		$shieldDamage = 0;
913
		if (!$alreadyDead) {
914
			$shieldDamage = $this->doShieldDamage(min($damage['MaxDamage'], $damage['Shield']));
915
			$damage['MaxDamage'] -= $shieldDamage;
916
			if (!$this->hasShields() && ($shieldDamage == 0 || $damage['Rollover'])) { //skip CDs if it's mines
917
				$armourDamage = $this->doArmourDamage(min($damage['MaxDamage'], $damage['Armour']));
918
			}
919
		}
920
		return array(
921
						'KillingShot' => !$alreadyDead && $this->isDead(),
922
						'TargetAlreadyDead' => $alreadyDead,
923
						'Shield' => $shieldDamage,
924
						'CDs' => $cdDamage,
925
						'NumCDs' => $cdDamage / CD_ARMOUR,
926
						'Armour' => $armourDamage,
927
						'HasCDs' => $this->hasCDs(),
928
						'TotalDamage' => $shieldDamage + $cdDamage + $armourDamage
929
		);
930
	}
931
932
	protected function doShieldDamage(int $damage) : int {
933
		$actualDamage = min($this->getShields(), $damage);
934
		$this->decreaseShields($actualDamage);
935
		return $actualDamage;
936
	}
937
938
	protected function doCDDamage(int $damage) : int {
939
		$actualDamage = min($this->getCDs(), IFloor($damage / CD_ARMOUR));
940
		$this->decreaseCDs($actualDamage);
941
		return $actualDamage * CD_ARMOUR;
942
	}
943
944
	protected function doArmourDamage(int $damage) : int {
945
		$actualDamage = min($this->getArmour(), $damage);
946
		$this->decreaseArmour($actualDamage);
947
		return $actualDamage;
948
	}
949
950
	/**
951
	 * Returns the maneuverability rating for this ship.
952
	 */
953
	public function getMR() : int {
954
		//700 - [ (ship hit points / 25) + (ship stat factors) ]
955
		//Minimum value of 0 because negative values cause issues with calculations calling this routine
956
		return max(0, IRound(
957
						700 -
958
						(
959
							(
960
								$this->getShields()
961
								+$this->getArmour()
962
								+$this->getCDs() * 3
963
							) / 25
964
							+(
965
								$this->getCargoHolds() / 100
966
								-$this->shipType->getSpeed() * 5
967
								+($this->getHardpoints()/*+$ship['Increases']['Ship Power']*/) * 5
968
								/*+(
969
									$ship['Increases']['Mines']
970
									+$ship['Increases']['Scout Drones']
971
								)/12*/
972
								+$this->getCDs() / 5
973
							)
974
						)
975
					)
976
					);
977
	}
978
979
}
980