Scrutinizer GitHub App not installed

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

Install GitHub App

Failed Conditions
Pull Request — master (#1088)
by Dan
06:55
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