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

SmrPlanet::setTypeID()   A
last analyzed

Complexity

Conditions 5
Paths 4

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 11
dl 0
loc 20
rs 9.6111
c 0
b 0
f 0
cc 5
nc 4
nop 1
1
<?php declare(strict_types=1);
2
3
use Smr\Database;
4
use Smr\DatabaseRecord;
5
use Smr\Epoch;
6
use Smr\Exceptions\UserError;
7
use Smr\Pages\Player\AttackPlanetProcessor;
8
use Smr\Pages\Player\ExaminePlanet;
9
use Smr\Pages\Player\Planet\BondConfirm;
10
use Smr\Pages\Player\Planet\ConstructionProcessor;
11
use Smr\Pages\Player\Planet\FinancialProcessor;
12
use Smr\Pages\Player\Planet\LandProcessor;
13
use Smr\PlanetTypes\PlanetType;
14
15
class SmrPlanet {
16
17
	/** @var array<int, array<int, self>> */
18
	protected static array $CACHE_PLANETS = [];
19
20
	public const DAMAGE_NEEDED_FOR_DOWNGRADE_CHANCE = 100;
21
	protected const CHANCE_TO_DOWNGRADE = 15; // percent
22
	protected const TIME_TO_CREDIT_BUST = 10800; // 3 hours
23
	protected const TIME_ATTACK_NEWS_COOLDOWN = 3600; // 1 hour
24
	public const MAX_STOCKPILE = 600;
25
26
	protected Database $db;
27
	protected readonly string $SQL;
28
29
	protected bool $exists;
30
	protected string $planetName;
31
	protected int $ownerID;
32
	protected string $password;
33
	protected int $shields;
34
	protected int $armour;
35
	protected int $drones;
36
	protected int $credits;
37
	protected int $bonds;
38
	protected int $maturity;
39
	/** @var array<int, int> */
40
	protected array $stockpile;
41
	/** @var array<int, int> */
42
	protected array $buildings;
43
	protected int $inhabitableTime;
44
	/** @var array<int, array<string, int>> */
45
	protected array $currentlyBuilding;
46
	/** @var array<int, SmrWeapon> */
47
	protected array $mountedWeapons;
48
	protected int $typeID;
49
	protected PlanetType $typeInfo;
50
51
	protected bool $hasChanged = false;
52
	protected bool $hasChangedFinancial = false; // for credits, bond, maturity
53
	protected bool $hasChangedStockpile = false;
54
	/** @var array<int, bool> */
55
	protected array $hasChangedWeapons = [];
56
	/** @var array<int, bool> */
57
	protected array $hasChangedBuildings = [];
58
	/** @var array<int> */
59
	protected array $hasStoppedBuilding = [];
60
	protected bool $isNew = false;
61
62
	public function __sleep() {
63
		return ['sectorID', 'gameID', 'planetName', 'ownerID', 'typeID'];
64
	}
65
66
	public static function clearCache(): void {
67
		self::$CACHE_PLANETS = [];
68
	}
69
70
	public static function savePlanets(): void {
71
		foreach (self::$CACHE_PLANETS as $gamePlanets) {
72
			foreach ($gamePlanets as $planet) {
73
				$planet->update();
74
			}
75
		}
76
	}
77
78
	/**
79
	 * @return array<int, self>
80
	 */
81
	public static function getGalaxyPlanets(int $gameID, int $galaxyID, bool $forceUpdate = false): array {
82
		$db = Database::getInstance();
83
		$dbResult = $db->read('SELECT planet.* FROM planet LEFT JOIN sector USING (game_id, sector_id) WHERE game_id = ' . $db->escapeNumber($gameID) . ' AND galaxy_id = ' . $db->escapeNumber($galaxyID));
84
		$galaxyPlanets = [];
85
		foreach ($dbResult->records() as $dbRecord) {
86
			$sectorID = $dbRecord->getInt('sector_id');
87
			$galaxyPlanets[$sectorID] = self::getPlanet($gameID, $sectorID, $forceUpdate, $dbRecord);
88
		}
89
		return $galaxyPlanets;
90
	}
91
92
	public static function getPlanet(int $gameID, int $sectorID, bool $forceUpdate = false, DatabaseRecord $dbRecord = null): self {
93
		if ($forceUpdate || !isset(self::$CACHE_PLANETS[$gameID][$sectorID])) {
94
			self::$CACHE_PLANETS[$gameID][$sectorID] = new self($gameID, $sectorID, $dbRecord);
95
		}
96
		return self::$CACHE_PLANETS[$gameID][$sectorID];
97
	}
98
99
	public static function createPlanet(int $gameID, int $sectorID, int $typeID = 1, int $inhabitableTime = null): self {
100
		if (self::getPlanet($gameID, $sectorID)->exists()) {
101
			throw new Exception('Planet already exists in sector ' . $sectorID . ' game ' . $gameID);
102
		}
103
104
		if ($inhabitableTime === null) {
105
			$minTime = SmrGame::getGame($gameID)->getStartTime();
106
			$inhabitableTime = $minTime + pow(rand(45, 85), 3);
107
		}
108
109
		// insert planet into db
110
		$db = Database::getInstance();
111
		$db->insert('planet', [
112
			'game_id' => $db->escapeNumber($gameID),
113
			'sector_id' => $db->escapeNumber($sectorID),
114
			'inhabitable_time' => $db->escapeNumber($inhabitableTime),
115
			'planet_type_id' => $db->escapeNumber($typeID),
116
		]);
117
		return self::getPlanet($gameID, $sectorID, true);
118
	}
119
120
	public static function removePlanet(int $gameID, int $sectorID): void {
121
		$db = Database::getInstance();
122
		$SQL = 'game_id = ' . $db->escapeNumber($gameID) . ' AND sector_id = ' . $db->escapeNumber($sectorID);
123
		$db->write('DELETE FROM planet WHERE ' . $SQL);
124
		$db->write('DELETE FROM planet_has_weapon WHERE ' . $SQL);
125
		$db->write('DELETE FROM planet_has_cargo WHERE ' . $SQL);
126
		$db->write('DELETE FROM planet_has_building WHERE ' . $SQL);
127
		$db->write('DELETE FROM planet_is_building WHERE ' . $SQL);
128
		//kick everyone from planet
129
		$db->write('UPDATE player SET land_on_planet = \'FALSE\' WHERE ' . $SQL);
130
131
		self::$CACHE_PLANETS[$gameID][$sectorID] = null;
132
		unset(self::$CACHE_PLANETS[$gameID][$sectorID]);
133
	}
134
135
	protected function __construct(
136
		protected readonly int $gameID,
137
		protected readonly int $sectorID,
138
		DatabaseRecord $dbRecord = null
139
	) {
140
		$this->db = Database::getInstance();
141
		$this->SQL = 'game_id = ' . $this->db->escapeNumber($gameID) . ' AND sector_id = ' . $this->db->escapeNumber($sectorID);
0 ignored issues
show
Bug introduced by
The property SQL is declared read-only in SmrPlanet.
Loading history...
142
143
		if ($dbRecord === null) {
144
			$dbResult = $this->db->read('SELECT * FROM planet WHERE ' . $this->SQL);
145
			if ($dbResult->hasRecord()) {
146
				$dbRecord = $dbResult->record();
147
			}
148
		}
149
		$this->exists = $dbRecord !== null;
150
151
		if ($this->exists) {
152
			$this->planetName = $dbRecord->getString('planet_name');
153
			$this->ownerID = $dbRecord->getInt('owner_id');
154
			$this->password = $dbRecord->getString('password');
155
			$this->shields = $dbRecord->getInt('shields');
156
			$this->armour = $dbRecord->getInt('armour');
157
			$this->drones = $dbRecord->getInt('drones');
158
			$this->credits = $dbRecord->getInt('credits');
159
			$this->bonds = $dbRecord->getInt('bonds');
160
			$this->maturity = $dbRecord->getInt('maturity');
161
			$this->inhabitableTime = $dbRecord->getInt('inhabitable_time');
162
			$this->typeID = $dbRecord->getInt('planet_type_id');
163
164
			$this->typeInfo = PlanetType::getTypeInfo($this->getTypeID());
165
			$this->checkBondMaturity();
166
		}
167
	}
168
169
	public function getInterestRate(): float {
170
		$level = $this->getLevel();
171
		return match (true) {
172
			$level < 9 => .0404,
173
			$level < 19 => .0609,
174
			$level < 29 => .1236,
175
			$level < 39 => .050625,
176
			$level < 49 => .0404,
177
			$level < 59 => .030225,
178
			$level < 69 => .0201,
179
			default => .018081,
180
		};
181
	}
182
183
	public function checkBondMaturity(bool $partial = false): void {
184
		if ($this->getMaturity() > 0 && ($partial === true || $this->getMaturity() < Epoch::time())) {
185
			// calc the interest for the time
186
			$interest = $this->getBonds() * $this->getInterestRate();
187
188
			if ($partial === true && $this->getMaturity() > Epoch::time()) {
189
				// Adjust interest based upon how much of the bond duration has passed.
190
				$interest -= ($interest / $this->getBondTime()) * ($this->getMaturity() - Epoch::time());
191
			}
192
193
			// transfer money to free avail cash
194
			$this->increaseCredits($this->getBonds() + IFloor($interest));
0 ignored issues
show
Bug introduced by
The function IFloor was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

194
			$this->increaseCredits($this->getBonds() + /** @scrutinizer ignore-call */ IFloor($interest));
Loading history...
195
196
			// reset bonds
197
			$this->setBonds(0);
198
199
			// reset maturity
200
			$this->setMaturity(0);
201
		}
202
	}
203
204
	public function getBondTime(): int {
205
		return IRound(BOND_TIME / $this->getGame()->getGameSpeed());
0 ignored issues
show
Bug introduced by
The function IRound was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

205
		return /** @scrutinizer ignore-call */ IRound(BOND_TIME / $this->getGame()->getGameSpeed());
Loading history...
206
	}
207
208
	public function bond(): void {
209
		$this->checkBondMaturity(true);
210
211
		// add it to bond
212
		$this->increaseBonds($this->getCredits());
213
214
		// set free cash to 0
215
		$this->setCredits(0);
216
217
		// initialize time
218
		$this->setMaturity(Epoch::time() + $this->getBondTime());
219
	}
220
221
	public function getGameID(): int {
222
		return $this->gameID;
223
	}
224
225
	public function getGame(): SmrGame {
226
		return SmrGame::getGame($this->gameID);
227
	}
228
229
	public function getSectorID(): int {
230
		return $this->sectorID;
231
	}
232
233
	public function getGalaxy(): SmrGalaxy {
234
		return SmrGalaxy::getGalaxyContaining($this->getGameID(), $this->getSectorID());
235
	}
236
237
	public function getOwnerID(): int {
238
		return $this->ownerID;
239
	}
240
241
	public function hasOwner(): bool {
242
		return $this->ownerID != 0;
243
	}
244
245
	public function removeOwner(): void {
246
		$this->setOwnerID(0);
247
	}
248
249
	public function setOwnerID(int $claimerID): void {
250
		if ($this->ownerID === $claimerID) {
251
			return;
252
		}
253
		$this->ownerID = $claimerID;
254
		$this->hasChanged = true;
255
	}
256
257
	public function getOwner(): AbstractSmrPlayer {
258
		return SmrPlayer::getPlayer($this->getOwnerID(), $this->getGameID());
259
	}
260
261
	public function getPassword(): string {
262
		return $this->password;
263
	}
264
265
	public function setPassword(string $password): void {
266
		if ($this->password === $password) {
267
			return;
268
		}
269
		$this->password = $password;
270
		$this->hasChanged = true;
271
	}
272
273
	public function removePassword(): void {
274
		$this->setPassword('');
275
	}
276
277
	public function getCredits(): int {
278
		return $this->credits;
279
	}
280
281
	public function setCredits(int $num): void {
282
		if ($this->credits === $num) {
283
			return;
284
		}
285
		if ($num < 0) {
286
			throw new Exception('You cannot set negative credits.');
287
		}
288
		if ($num > MAX_MONEY) {
289
			throw new Exception('You cannot set more than the max credits.');
290
		}
291
		$this->credits = $num;
292
		$this->hasChangedFinancial = true;
293
	}
294
295
	/**
296
	 * Increases planet credits up to the maximum allowed credits.
297
	 * Returns the amount that was actually added to handle overflow.
298
	 */
299
	public function increaseCredits(int $num): int {
300
		if ($num === 0) {
301
			return 0;
302
		}
303
		$newTotal = min($this->credits + $num, MAX_MONEY);
304
		$actualAdded = $newTotal - $this->credits;
305
		$this->setCredits($newTotal);
306
		return $actualAdded;
307
	}
308
309
	public function decreaseCredits(int $num): void {
310
		if ($num === 0) {
311
			return;
312
		}
313
		$newTotal = $this->credits - $num;
314
		$this->setCredits($newTotal);
315
	}
316
317
	public function getMaturity(): int {
318
		return $this->maturity;
319
	}
320
321
	public function setMaturity(int $num): void {
322
		if ($this->maturity === $num) {
323
			return;
324
		}
325
		if ($num < 0) {
326
			throw new Exception('You cannot set negative maturity.');
327
		}
328
		$this->maturity = $num;
329
		$this->hasChangedFinancial = true;
330
	}
331
332
	public function getBonds(): int {
333
		return $this->bonds;
334
	}
335
336
	public function setBonds(int $num): void {
337
		if ($this->bonds === $num) {
338
			return;
339
		}
340
		if ($num < 0) {
341
			throw new Exception('You cannot set negative bonds.');
342
		}
343
		$this->bonds = $num;
344
		$this->hasChangedFinancial = true;
345
	}
346
347
	public function increaseBonds(int $num): void {
348
		if ($num === 0) {
349
			return;
350
		}
351
		$this->setBonds($this->getBonds() + $num);
352
	}
353
354
	public function decreaseBonds(int $num): void {
355
		if ($num === 0) {
356
			return;
357
		}
358
		$this->setBonds($this->getBonds() - $num);
359
	}
360
361
	public function checkForExcessDefense(): void {
362
		if ($this->getShields() > $this->getMaxShields()) {
363
			$this->setShields($this->getMaxShields());
364
		}
365
		if ($this->getCDs() > $this->getMaxCDs()) {
366
			$this->setCDs($this->getMaxCDs());
367
		}
368
		if ($this->getArmour() > $this->getMaxArmour()) {
369
				$this->setArmour($this->getMaxArmour());
370
		}
371
		// Remove a random (0-indexed) mounted weapon, if over max mount slots
372
		while ($this->getMountedWeapons() && max(array_keys($this->getMountedWeapons())) >= $this->getMaxMountedWeapons()) {
373
			$removeID = array_rand($this->getMountedWeapons());
374
			$this->removeMountedWeapon($removeID);
375
			foreach ($this->getMountedWeapons() as $orderID => $weapon) {
376
				if ($orderID > $removeID) {
377
					$this->moveMountedWeaponUp($orderID);
378
				}
379
			}
380
		}
381
	}
382
383
	public function getShields(): int {
384
		return $this->shields;
385
	}
386
387
	public function hasShields(): bool {
388
		return $this->getShields() > 0;
389
	}
390
391
	public function setShields(int $shields): void {
392
		$shields = max(0, min($shields, $this->getMaxShields()));
393
		if ($this->shields === $shields) {
394
			return;
395
		}
396
		$this->shields = $shields;
397
		$this->hasChanged = true;
398
	}
399
400
	public function decreaseShields(int $number): void {
401
		if ($number === 0) {
402
			return;
403
		}
404
		$this->setShields($this->getShields() - $number);
405
	}
406
407
	public function increaseShields(int $number): void {
408
		if ($number === 0) {
409
			return;
410
		}
411
		$this->setShields($this->getShields() + $number);
412
	}
413
414
	public function getMaxShields(): int {
415
		return $this->getBuilding(PLANET_GENERATOR) * PLANET_GENERATOR_SHIELDS;
416
	}
417
418
	public function getArmour(): int {
419
		return $this->armour;
420
	}
421
422
	public function hasArmour(): bool {
423
		return $this->getArmour() > 0;
424
	}
425
426
	public function setArmour(int $armour): void {
427
		$armour = max(0, min($armour, $this->getMaxArmour()));
428
		if ($this->armour === $armour) {
429
			return;
430
		}
431
		$this->armour = $armour;
432
		$this->hasChanged = true;
433
	}
434
435
	public function decreaseArmour(int $number): void {
436
		if ($number === 0) {
437
			return;
438
		}
439
		$this->setArmour($this->getArmour() - $number);
440
	}
441
442
	public function increaseArmour(int $number): void {
443
		if ($number === 0) {
444
			return;
445
		}
446
		$this->setArmour($this->getArmour() + $number);
447
	}
448
449
	public function getMaxArmour(): int {
450
		return $this->getBuilding(PLANET_BUNKER) * PLANET_BUNKER_ARMOUR;
451
	}
452
453
	public function getCDs(): int {
454
		return $this->drones;
455
	}
456
457
	public function hasCDs(): bool {
458
		return $this->getCDs() > 0;
459
	}
460
461
	public function setCDs(int $combatDrones): void {
462
		$combatDrones = max(0, min($combatDrones, $this->getMaxCDs()));
463
		if ($this->drones === $combatDrones) {
464
			return;
465
		}
466
		$this->drones = $combatDrones;
467
		$this->hasChanged = true;
468
	}
469
470
	public function decreaseCDs(int $number): void {
471
		if ($number === 0) {
472
			return;
473
		}
474
		$this->setCDs($this->getCDs() - $number);
475
	}
476
477
	public function increaseCDs(int $number): void {
478
		if ($number === 0) {
479
			return;
480
		}
481
		$this->setCDs($this->getCDs() + $number);
482
	}
483
484
	public function getMaxCDs(): int {
485
		return $this->getBuilding(PLANET_HANGAR) * PLANET_HANGAR_DRONES;
486
	}
487
488
	public function getMaxMountedWeapons(): int {
489
		return $this->getBuilding(PLANET_WEAPON_MOUNT);
490
	}
491
492
	/**
493
	 * @return array<int, SmrWeapon>
494
	 */
495
	public function getMountedWeapons(): array {
496
		if (!isset($this->mountedWeapons)) {
497
			$this->mountedWeapons = [];
498
			if ($this->hasBuilding(PLANET_WEAPON_MOUNT)) {
499
				$dbResult = $this->db->read('SELECT * FROM planet_has_weapon JOIN weapon_type USING (weapon_type_id) WHERE ' . $this->SQL);
500
				foreach ($dbResult->records() as $dbRecord) {
501
					$weaponTypeID = $dbRecord->getInt('weapon_type_id');
502
					$orderID = $dbRecord->getInt('order_id');
503
					$weapon = SmrWeapon::getWeapon($weaponTypeID, $dbRecord);
504
					$weapon->setBonusAccuracy($dbRecord->getBoolean('bonus_accuracy'));
505
					$weapon->setBonusDamage($dbRecord->getBoolean('bonus_damage'));
506
					$this->mountedWeapons[$orderID] = $weapon;
507
				}
508
			}
509
		}
510
		return $this->mountedWeapons;
511
	}
512
513
	public function hasMountedWeapon(int $orderID): bool {
514
		$this->getMountedWeapons(); // Make sure array is initialized
515
		return isset($this->mountedWeapons[$orderID]);
516
	}
517
518
	public function addMountedWeapon(SmrWeapon $weapon, int $orderID): void {
519
		$this->getMountedWeapons(); // Make sure array is initialized
520
		$this->mountedWeapons[$orderID] = $weapon;
521
		$this->hasChangedWeapons[$orderID] = true;
522
	}
523
524
	public function removeMountedWeapon(int $orderID): void {
525
		$this->getMountedWeapons(); // Make sure array is initialized
526
		unset($this->mountedWeapons[$orderID]);
527
		$this->hasChangedWeapons[$orderID] = true;
528
	}
529
530
	private function swapMountedWeapons(int $orderID1, int $orderID2): void {
531
		$this->getMountedWeapons(); // Make sure array is initialized
532
		if (isset($this->mountedWeapons[$orderID1])) {
533
			$saveWeapon = $this->mountedWeapons[$orderID1];
534
		}
535
		if (isset($this->mountedWeapons[$orderID2])) {
536
			$this->mountedWeapons[$orderID1] = $this->mountedWeapons[$orderID2];
537
		} else {
538
			unset($this->mountedWeapons[$orderID1]);
539
		}
540
		if (isset($saveWeapon)) {
541
			$this->mountedWeapons[$orderID2] = $saveWeapon;
542
		} else {
543
			unset($this->mountedWeapons[$orderID2]);
544
		}
545
		$this->hasChangedWeapons[$orderID1] = true;
546
		$this->hasChangedWeapons[$orderID2] = true;
547
	}
548
549
	public function moveMountedWeaponUp(int $orderID): void {
550
		if ($orderID == 0) {
551
			throw new Exception('Cannot move this weapon up!');
552
		}
553
		$this->swapMountedWeapons($orderID - 1, $orderID);
554
	}
555
556
	public function moveMountedWeaponDown(int $orderID): void {
557
		if ($orderID == $this->getMaxMountedWeapons() - 1) {
558
			throw new Exception('Cannot move this weapon down!');
559
		}
560
		$this->swapMountedWeapons($orderID + 1, $orderID);
561
	}
562
563
564
	public function isDestroyed(): bool {
565
		return !$this->hasCDs() && !$this->hasShields() && !$this->hasArmour();
566
	}
567
568
	public function exists(): bool {
569
		return $this->exists;
570
	}
571
572
	/**
573
	 * @return ($goodID is null ? array<int, int> : int)
0 ignored issues
show
Documentation Bug introduced by
The doc comment ($goodID at position 1 could not be parsed: Unknown type name '$goodID' at position 1 in ($goodID.
Loading history...
574
	 */
575
	public function getStockpile(int $goodID = null): int|array {
576
		if (!isset($this->stockpile)) {
577
			// initialize cargo array
578
			$this->stockpile = [];
579
			// get supplies from db
580
			$dbResult = $this->db->read('SELECT good_id, amount FROM planet_has_cargo WHERE ' . $this->SQL);
581
			// adding cargo and amount to array
582
			foreach ($dbResult->records() as $dbRecord) {
583
				$this->stockpile[$dbRecord->getInt('good_id')] = $dbRecord->getInt('amount');
584
			}
585
		}
586
		if ($goodID === null) {
587
			return $this->stockpile;
588
		}
589
		if (isset($this->stockpile[$goodID])) {
590
			return $this->stockpile[$goodID];
591
		}
592
		return 0;
593
	}
594
595
	public function hasStockpile(int $goodID = null): bool {
596
		if ($goodID === null) {
597
			$stockpile = $this->getStockpile();
598
			return count($stockpile) > 0 && max($stockpile) > 0;
599
		}
600
		return $this->getStockpile($goodID) > 0;
601
	}
602
603
	public function setStockpile(int $goodID, int $amount): void {
604
		if ($this->getStockpile($goodID) === $amount) {
605
			return;
606
		}
607
		if ($amount < 0) {
608
			throw new Exception('Trying to set negative stockpile.');
609
		}
610
		$this->stockpile[$goodID] = $amount;
611
		$this->hasChangedStockpile = true;
612
	}
613
614
	public function decreaseStockpile(int $goodID, int $amount): void {
615
		if ($amount < 0) {
616
			throw new Exception('Trying to decrease negative stockpile.');
617
		}
618
		$this->setStockpile($goodID, $this->getStockpile($goodID) - $amount);
619
	}
620
621
	public function increaseStockpile(int $goodID, int $amount): void {
622
		if ($amount < 0) {
623
			throw new Exception('Trying to increase negative stockpile.');
624
		}
625
		$this->setStockpile($goodID, $this->getStockpile($goodID) + $amount);
626
	}
627
628
	/**
629
	 * @return array<int, int>
630
	 */
631
	public function getBuildings(): array {
632
		if (!isset($this->buildings)) {
633
			$this->buildings = [];
634
635
			// get buildingss from db
636
			$dbResult = $this->db->read('SELECT construction_id, amount FROM planet_has_building WHERE ' . $this->SQL);
637
			// adding building and amount to array
638
			foreach ($dbResult->records() as $dbRecord) {
639
				$this->buildings[$dbRecord->getInt('construction_id')] = $dbRecord->getInt('amount');
640
			}
641
642
			// Update building counts if construction has finished
643
			$this->getCurrentlyBuilding();
644
		}
645
		return $this->buildings;
646
	}
647
648
	public function getBuilding(int $buildingTypeID): int {
649
		$buildings = $this->getBuildings();
650
		if (isset($buildings[$buildingTypeID])) {
651
			return $buildings[$buildingTypeID];
652
		}
653
		return 0;
654
	}
655
656
	public function hasBuilding(int $buildingTypeID): bool {
657
		return $this->getBuilding($buildingTypeID) > 0;
658
	}
659
660
	public function setBuilding(int $buildingTypeID, int $number): void {
661
		if ($this->getBuilding($buildingTypeID) === $number) {
662
			return;
663
		}
664
		if ($number < 0) {
665
			throw new Exception('Cannot set negative number of buildings.');
666
		}
667
668
		$this->buildings[$buildingTypeID] = $number;
669
		$this->hasChangedBuildings[$buildingTypeID] = true;
670
	}
671
672
	public function increaseBuilding(int $buildingTypeID, int $number): void {
673
		$this->setBuilding($buildingTypeID, $this->getBuilding($buildingTypeID) + $number);
674
	}
675
676
	public function destroyBuilding(int $buildingTypeID, int $number): void {
677
		$this->setBuilding($buildingTypeID, $this->getBuilding($buildingTypeID) - $number);
678
	}
679
680
	/**
681
	 * @return array<int, array<string, int>>
682
	 */
683
	public function getCurrentlyBuilding(): array {
684
		if (!isset($this->currentlyBuilding)) {
685
			$this->currentlyBuilding = [];
686
687
			$dbResult = $this->db->read('SELECT * FROM planet_is_building WHERE ' . $this->SQL);
688
			foreach ($dbResult->records() as $dbRecord) {
689
				$this->currentlyBuilding[$dbRecord->getInt('building_slot_id')] = [
690
					'BuildingSlotID' => $dbRecord->getInt('building_slot_id'),
691
					'ConstructionID' => $dbRecord->getInt('construction_id'),
692
					'ConstructorID' => $dbRecord->getInt('constructor_id'),
693
					'Finishes' => $dbRecord->getInt('time_complete'),
694
					'TimeRemaining' => $dbRecord->getInt('time_complete') - Epoch::time(),
695
				];
696
			}
697
698
			// Check if construction has completed
699
			foreach ($this->currentlyBuilding as $id => $building) {
700
				if ($building['TimeRemaining'] <= 0) {
701
					unset($this->currentlyBuilding[$id]);
702
					$expGain = $this->getConstructionExp($building['ConstructionID']);
703
					$player = SmrPlayer::getPlayer($building['ConstructorID'], $this->getGameID());
704
					$player->increaseHOF(1, ['Planet', 'Buildings', 'Built'], HOF_ALLIANCE);
705
					$player->increaseExperience($expGain);
706
					$player->increaseHOF($expGain, ['Planet', 'Buildings', 'Experience'], HOF_ALLIANCE);
707
					$this->hasStoppedBuilding[] = $building['BuildingSlotID'];
708
					$this->increaseBuilding($building['ConstructionID'], 1);
709
710
					// WARNING: The above modifications to the player/planet are dangerous because
711
					// they may not be part of the current sector lock. But since they might not be,
712
					// we may as well just update now to avoid either a) needing to remember to call
713
					// this explicitly in all appropriate engine files or b) inconsistent exp display
714
					// if this is called during the template display only and therefore unsaved.
715
					$player->update();
716
					$this->update();
717
				}
718
			}
719
		}
720
		return $this->currentlyBuilding;
721
	}
722
723
	/**
724
	 * @return ($buildingTypeID is null ? array<int, int> : int)
0 ignored issues
show
Documentation Bug introduced by
The doc comment ($buildingTypeID at position 1 could not be parsed: Unknown type name '$buildingTypeID' at position 1 in ($buildingTypeID.
Loading history...
725
	 */
726
	public function getMaxBuildings(int $buildingTypeID = null): int|array {
727
		if ($buildingTypeID === null) {
728
			$maxBuildings = [];
729
			foreach ($this->getStructureTypes() as $ID => $type) {
730
				$maxBuildings[$ID] = $type->maxAmount();
731
			}
732
			return $maxBuildings;
733
		}
734
		return $this->getStructureTypes($buildingTypeID)->maxAmount();
735
	}
736
737
	public function getTypeID(): int {
738
		return $this->typeID;
739
	}
740
741
	public function setTypeID(int $num): void {
742
		if (isset($this->typeID) && $this->typeID === $num) {
743
			return;
744
		}
745
		$this->typeID = $num;
746
		$this->db->write('UPDATE planet SET planet_type_id = ' . $this->db->escapeNumber($num) . ' WHERE ' . $this->SQL);
747
		$this->typeInfo = PlanetType::getTypeInfo($this->getTypeID());
748
749
		//trim buildings first
750
		foreach ($this->getBuildings() as $id => $amt) {
751
			if ($this->getMaxBuildings($id) < $amt) {
752
				$this->destroyBuilding($id, $amt - $this->getMaxBuildings($id));
753
			}
754
		}
755
756
		//trim excess defenses
757
		$this->checkForExcessDefense();
758
759
		$this->hasChanged = true;
760
		$this->update();
761
	}
762
763
	public function getTypeImage(): string {
764
		return $this->typeInfo->imageLink();
765
	}
766
767
	public function getTypeName(): string {
768
		return $this->typeInfo->name();
769
	}
770
771
	public function getTypeDescription(): string {
772
		return $this->typeInfo->description();
773
	}
774
775
	public function getMaxAttackers(): int {
776
		return $this->typeInfo->maxAttackers();
777
	}
778
779
	public function getMaxLanded(): int {
780
		return $this->typeInfo->maxLanded();
781
	}
782
783
	/**
784
	 * @return ($structureID is null ? array<int, SmrPlanetStructureType> : SmrPlanetStructureType)
0 ignored issues
show
Documentation Bug introduced by
The doc comment ($structureID at position 1 could not be parsed: Unknown type name '$structureID' at position 1 in ($structureID.
Loading history...
785
	 */
786
	public function getStructureTypes(int $structureID = null): SmrPlanetStructureType|array {
787
		return $this->typeInfo->structureTypes($structureID);
788
	}
789
790
	public function hasStructureType(int $structureID): bool {
791
		return isset($this->getStructureTypes()[$structureID]);
792
	}
793
794
	/**
795
	 * Specifies which menu options the planet has.
796
	 */
797
	public function hasMenuOption(string $option): bool {
798
		// We do not set options that are unavailable
799
		return in_array($option, $this->typeInfo->menuOptions());
800
	}
801
802
	public function update(): void {
803
		if (!$this->exists()) {
804
			return;
805
		}
806
		if ($this->hasChanged) {
807
			$this->db->write('UPDATE planet SET
808
									owner_id = ' . $this->db->escapeNumber($this->ownerID) . ',
809
									password = ' . $this->db->escapeString($this->password) . ',
810
									planet_name = ' . $this->db->escapeString($this->planetName) . ',
811
									shields = ' . $this->db->escapeNumber($this->shields) . ',
812
									armour = ' . $this->db->escapeNumber($this->armour) . ',
813
									drones = ' . $this->db->escapeNumber($this->drones) . '
814
								WHERE ' . $this->SQL);
815
			$this->hasChanged = false;
816
		}
817
818
		// Separate update for financial since these can be modified by looking
819
		// at the planet list (i.e. you might not have sector lock and could
820
		// cause a race condition with events happening in the planet sector).
821
		if ($this->hasChangedFinancial) {
822
			$this->db->write('UPDATE planet SET
823
									credits = ' . $this->db->escapeNumber($this->credits) . ',
824
									bonds = ' . $this->db->escapeNumber($this->bonds) . ',
825
									maturity = ' . $this->db->escapeNumber($this->maturity) . '
826
								WHERE ' . $this->SQL);
827
			$this->hasChangedFinancial = false;
828
		}
829
830
		if ($this->hasChangedStockpile) {
831
			// write stockpile info
832
			foreach ($this->getStockpile() as $id => $amount) {
833
				if ($amount != 0) {
834
					$this->db->replace('planet_has_cargo', [
835
						'game_id' => $this->db->escapeNumber($this->getGameID()),
836
						'sector_id' => $this->db->escapeNumber($this->getSectorID()),
837
						'good_id' => $this->db->escapeNumber($id),
838
						'amount' => $this->db->escapeNumber($amount),
839
					]);
840
				} else {
841
					$this->db->write('DELETE FROM planet_has_cargo WHERE ' . $this->SQL . '
842
										AND good_id = ' . $this->db->escapeNumber($id));
843
				}
844
			}
845
		}
846
847
		if (count($this->hasChangedWeapons) > 0) {
848
			foreach (array_keys($this->hasChangedWeapons) as $orderID) {
849
				if (isset($this->mountedWeapons[$orderID])) {
850
					$this->db->replace('planet_has_weapon', [
851
						'game_id' => $this->db->escapeNumber($this->getGameID()),
852
						'sector_id' => $this->db->escapeNumber($this->getSectorID()),
853
						'order_id' => $this->db->escapeNumber($orderID),
854
						'weapon_type_id' => $this->db->escapeNumber($this->mountedWeapons[$orderID]->getWeaponTypeID()),
855
						'bonus_accuracy' => $this->db->escapeBoolean($this->mountedWeapons[$orderID]->hasBonusAccuracy()),
856
						'bonus_damage' => $this->db->escapeBoolean($this->mountedWeapons[$orderID]->hasBonusDamage()),
857
					]);
858
				} else {
859
					$this->db->write('DELETE FROM planet_has_weapon WHERE ' . $this->SQL . ' AND order_id=' . $this->db->escapeNumber($orderID));
860
				}
861
			}
862
			$this->hasChangedWeapons = [];
863
		}
864
865
		if (count($this->hasStoppedBuilding) > 0) {
866
			$this->db->write('DELETE FROM planet_is_building WHERE ' . $this->SQL . '
867
								AND building_slot_id IN (' . $this->db->escapeArray($this->hasStoppedBuilding) . ') LIMIT ' . count($this->hasStoppedBuilding));
868
			$this->hasStoppedBuilding = [];
869
		}
870
		// write building info
871
		foreach ($this->hasChangedBuildings as $id => $hasChanged) {
872
			if ($hasChanged === true) {
873
				if ($this->hasBuilding($id)) {
874
					$this->db->replace('planet_has_building', [
875
						'game_id' => $this->db->escapeNumber($this->gameID),
876
						'sector_id' => $this->db->escapeNumber($this->sectorID),
877
						'construction_id' => $this->db->escapeNumber($id),
878
						'amount' => $this->db->escapeNumber($this->getBuilding($id)),
879
					]);
880
				} else {
881
					$this->db->write('DELETE FROM planet_has_building WHERE ' . $this->SQL . '
882
										AND construction_id = ' . $this->db->escapeNumber($id));
883
				}
884
				$this->hasChangedBuildings[$id] = false;
885
			}
886
		}
887
	}
888
889
	public function getLevel(): float {
890
		return array_sum($this->getBuildings()) / 3;
891
	}
892
893
	public function getMaxLevel(): float {
894
		return array_sum($this->getMaxBuildings()) / 3;
895
	}
896
897
	/**
898
	 * Returns the modified accuracy of turrets on this planet.
899
	 * Only used for display purposes.
900
	 */
901
	public function getTurretAccuracy(): float {
902
		return SmrWeapon::getWeapon(WEAPON_PLANET_TURRET)->getModifiedPlanetAccuracy($this);
903
	}
904
905
	/**
906
	 * Returns the accuracy bonus for mounted weaons (as a percent)
907
	 */
908
	public function getAccuracyBonus(): int {
909
		return 5 * $this->getBuilding(PLANET_RADAR);
910
	}
911
912
	public function getRemainingStockpile(int $id): int {
913
		return self::MAX_STOCKPILE - $this->getStockpile($id);
914
	}
915
916
	/**
917
	 * Returns true if there is a building in progress
918
	 */
919
	public function hasCurrentlyBuilding(): bool {
920
		return count($this->getCurrentlyBuilding()) > 0;
921
	}
922
923
	/**
924
	 * Returns the reason a build cannot be performed, or false if there is
925
	 * no restriction.
926
	 */
927
	public function getBuildRestriction(AbstractSmrPlayer $constructor, int $constructionID): string|false {
928
		if ($this->hasCurrentlyBuilding()) {
929
			return 'There is already a building in progress!';
930
		}
931
		if ($this->getBuilding($constructionID) >= $this->getMaxBuildings($constructionID)) {
932
			return 'This planet has reached the maximum buildings of that type.';
933
		}
934
		$cost = $this->getStructureTypes($constructionID)->creditCost();
935
		if ($constructor->getCredits() < $cost) {
936
			return 'You do not have enough credits.';
937
		}
938
		if ($constructor->getTurns() < TURNS_TO_BUILD) {
939
			return 'You do not have enough turns to build.';
940
		}
941
		foreach ($this->getStructureTypes($constructionID)->hardwareCost() as $hardwareID) {
942
			if (!$constructor->getShip()->getHardware($hardwareID)) {
943
				return 'You do not have the hardware needed for this type of building!';
944
			}
945
		}
946
		// take the goods that are needed
947
		foreach ($this->getStructureTypes($constructionID)->goods() as $goodID => $amount) {
948
			if ($this->getStockpile($goodID) < $amount) {
949
				return 'There are not enough goods available.';
950
			}
951
		}
952
		return false;
953
	}
954
955
	// Modifier for planet building based on the number of buildings.
956
	// The average value of this modifier should roughly be 1.
957
	private function getCompletionModifier(int $constructionID): float {
958
		$currentBuildings = $this->getBuilding($constructionID);
959
		$maxBuildings = $this->getMaxBuildings($constructionID);
960
		return 0.01 + 2.97 * pow($currentBuildings / $maxBuildings, 2);
961
	}
962
963
	// Amount of exp gained to build the next building of this type
964
	private function getConstructionExp(int $constructionID): int {
965
		return $this->getStructureTypes($constructionID)->expGain();
966
	}
967
968
	// Amount of time (in seconds) to build the next building of this type
969
	public function getConstructionTime(int $constructionID): int {
970
		$baseTime = $this->getStructureTypes($constructionID)->baseTime();
971
		return ICeil($baseTime * $this->getCompletionModifier($constructionID) / $this->getGame()->getGameSpeed());
0 ignored issues
show
Bug introduced by
The function ICeil was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

971
		return /** @scrutinizer ignore-call */ ICeil($baseTime * $this->getCompletionModifier($constructionID) / $this->getGame()->getGameSpeed());
Loading history...
972
	}
973
974
	/**
975
	 * @throws \Smr\Exceptions\UserError If the player cannot build the structure.
976
	 */
977
	public function startBuilding(AbstractSmrPlayer $constructor, int $constructionID): void {
978
		$restriction = $this->getBuildRestriction($constructor, $constructionID);
979
		if ($restriction !== false) {
980
			throw new UserError('Unable to start building: ' . $restriction);
981
		}
982
983
		// gets the time for the buildings
984
		$timeComplete = Epoch::time() + $this->getConstructionTime($constructionID);
985
		$insertID = $this->db->insert('planet_is_building', [
986
			'game_id' => $this->db->escapeNumber($this->getGameID()),
987
			'sector_id' => $this->db->escapeNumber($this->getSectorID()),
988
			'construction_id' => $this->db->escapeNumber($constructionID),
989
			'constructor_id' => $this->db->escapeNumber($constructor->getAccountID()),
990
			'time_complete' => $this->db->escapeNumber($timeComplete),
991
		]);
992
993
		$this->currentlyBuilding[$insertID] = [
994
			'BuildingSlotID' => $insertID,
995
			'ConstructionID' => $constructionID,
996
			'ConstructorID' => $constructor->getAccountID(),
997
			'Finishes' => $timeComplete,
998
			'TimeRemaining' => $timeComplete - Epoch::time(),
999
		];
1000
1001
		// Consume the required resources
1002
		$constructor->decreaseCredits($this->getStructureTypes($constructionID)->creditCost());
1003
		$constructor->takeTurns(TURNS_TO_BUILD);
1004
		foreach ($this->getStructureTypes($constructionID)->goods() as $goodID => $amount) {
1005
			$this->decreaseStockpile($goodID, $amount);
1006
		}
1007
		foreach ($this->getStructureTypes($constructionID)->hardwareCost() as $hardwareID) {
1008
			$constructor->getShip()->setHardware($hardwareID, 0);
1009
		}
1010
	}
1011
1012
	public function stopBuilding(int $constructionID): bool {
1013
		$matchingBuilding = false;
1014
		$latestFinish = 0;
1015
		foreach ($this->getCurrentlyBuilding() as $building) {
1016
			if ($building['ConstructionID'] == $constructionID && $building['Finishes'] > $latestFinish) {
1017
				$latestFinish = $building['Finishes'];
1018
				$matchingBuilding = $building;
1019
			}
1020
		}
1021
		if ($matchingBuilding) {
1022
			$this->hasStoppedBuilding[] = $matchingBuilding['BuildingSlotID'];
1023
			unset($this->currentlyBuilding[$matchingBuilding['BuildingSlotID']]);
1024
			return true;
1025
		}
1026
		return false;
1027
	}
1028
1029
	public function setName(string $name): void {
1030
		if ($this->planetName === $name) {
1031
			return;
1032
		}
1033
		$this->planetName = $name;
1034
		$this->hasChanged = true;
1035
	}
1036
1037
	/**
1038
	 * Returns the name of the planet, suitably escaped for HTML display.
1039
	 */
1040
	public function getDisplayName(): string {
1041
		return htmlentities($this->planetName);
1042
	}
1043
1044
	/**
1045
	 * Returns the name of the planet, intended for combat messages.
1046
	 */
1047
	public function getCombatName(): string {
1048
		return '<span style="color:yellow;font-variant:small-caps">' . $this->getDisplayName() . ' (#' . $this->getSectorID() . ')</span>';
1049
	}
1050
1051
	public function isInhabitable(): bool {
1052
		return $this->inhabitableTime <= Epoch::time();
1053
	}
1054
1055
	public function getInhabitableTime(): int {
1056
		return $this->inhabitableTime;
1057
	}
1058
1059
	public function getExamineHREF(): string {
1060
		return (new ExaminePlanet())->href();
1061
	}
1062
1063
	public function getLandHREF(): string {
1064
		return (new LandProcessor())->href();
1065
	}
1066
1067
	public function getAttackHREF(): string {
1068
		return (new AttackPlanetProcessor())->href();
1069
	}
1070
1071
	public function getBuildHREF(int $structureID): string {
1072
		$container = new ConstructionProcessor('Build', $structureID);
1073
		return $container->href();
1074
	}
1075
1076
	public function getCancelHREF(int $structureID): string {
1077
		$container = new ConstructionProcessor('Cancel', $structureID);
1078
		return $container->href();
1079
	}
1080
1081
	public function getFinancesHREF(): string {
1082
		return (new FinancialProcessor())->href();
1083
	}
1084
1085
	public function getBondConfirmationHREF(): string {
1086
		return (new BondConfirm())->href();
1087
	}
1088
1089
	/**
1090
	 * @param array<AbstractSmrPlayer> $attackers
1091
	 */
1092
	public function attackedBy(AbstractSmrPlayer $trigger, array $attackers): void {
1093
		$trigger->increaseHOF(1, ['Combat', 'Planet', 'Number Of Triggers'], HOF_PUBLIC);
1094
		foreach ($attackers as $attacker) {
1095
			$attacker->increaseHOF(1, ['Combat', 'Planet', 'Number Of Attacks'], HOF_PUBLIC);
1096
			$this->db->replace('player_attacks_planet', [
1097
				'game_id' => $this->db->escapeNumber($this->getGameID()),
1098
				'account_id' => $this->db->escapeNumber($attacker->getAccountID()),
1099
				'sector_id' => $this->db->escapeNumber($this->getSectorID()),
1100
				'time' => $this->db->escapeNumber(Epoch::time()),
1101
				'level' => $this->db->escapeNumber($this->getLevel()),
1102
			]);
1103
		}
1104
1105
		// Add each unique attack to news unless it was already added recently.
1106
		// Note: Attack uniqueness determined by planet owner.
1107
		$owner = $this->getOwner();
1108
		$dbResult = $this->db->read('SELECT 1 FROM news WHERE type = \'BREAKING\' AND game_id = ' . $this->db->escapeNumber($trigger->getGameID()) . ' AND dead_id=' . $this->db->escapeNumber($owner->getAccountID()) . ' AND time > ' . $this->db->escapeNumber(Epoch::time() - self::TIME_ATTACK_NEWS_COOLDOWN) . ' LIMIT 1');
1109
		if (!$dbResult->hasRecord()) {
1110
			if (count($attackers) >= 5) {
1111
				$text = count($attackers) . ' members of ' . $trigger->getAllianceBBLink() . ' have been spotted attacking ' .
1112
					$this->getDisplayName() . ' in sector ' . Globals::getSectorBBLink($this->getSectorID()) . '. The planet is owned by ' . $owner->getBBLink();
1113
				if ($owner->hasAlliance()) {
1114
					$text .= ', a member of ' . $owner->getAllianceBBLink();
1115
				}
1116
				$text .= '.';
1117
				$this->db->insert('news', [
1118
					'game_id' => $this->db->escapeNumber($this->getGameID()),
1119
					'time' => $this->db->escapeNumber(Epoch::time()),
1120
					'news_message' => $this->db->escapeString($text),
1121
					'type' => $this->db->escapeString('breaking'),
1122
					'killer_id' => $this->db->escapeNumber($trigger->getAccountID()),
1123
					'killer_alliance' => $this->db->escapeNumber($trigger->getAllianceID()),
1124
					'dead_id' => $this->db->escapeNumber($owner->getAccountID()),
1125
					'dead_alliance' => $this->db->escapeNumber($owner->getAllianceID()),
1126
				]);
1127
			}
1128
		}
1129
	}
1130
1131
1132
	/**
1133
	 * @return array<int, SmrPlayer>
1134
	 */
1135
	public function getPlayers(): array {
1136
		return SmrPlayer::getPlanetPlayers($this->getGameID(), $this->getSectorID());
1137
	}
1138
1139
	public function countPlayers(): int {
1140
		return count($this->getPlayers());
1141
	}
1142
1143
	public function hasPlayers(): bool {
1144
		return $this->countPlayers() > 0;
1145
	}
1146
1147
	/**
1148
	 * @return array<int, SmrPlayer>
1149
	 */
1150
	public function getOtherTraders(AbstractSmrPlayer $player): array {
1151
		$players = SmrPlayer::getPlanetPlayers($this->getGameID(), $this->getSectorID()); //Do not use & because we unset something and only want that in what we return
1152
		unset($players[$player->getAccountID()]);
1153
		return $players;
1154
	}
1155
1156
	public function hasOtherTraders(AbstractSmrPlayer $player): bool {
1157
		return count($this->getOtherTraders($player)) > 0;
1158
	}
1159
1160
	public function hasEnemyTraders(AbstractSmrPlayer $player): bool {
1161
		if (!$this->hasOtherTraders($player)) {
1162
			return false;
1163
		}
1164
		$otherPlayers = $this->getOtherTraders($player);
1165
		foreach ($otherPlayers as $otherPlayer) {
1166
			if (!$player->traderNAPAlliance($otherPlayer)) {
1167
				return true;
1168
			}
1169
		}
1170
		return false;
1171
	}
1172
1173
	public function hasFriendlyTraders(AbstractSmrPlayer $player): bool {
1174
		if (!$this->hasOtherTraders($player)) {
1175
			return false;
1176
		}
1177
		$otherPlayers = $this->getOtherTraders($player);
1178
		foreach ($otherPlayers as $otherPlayer) {
1179
			if ($player->traderNAPAlliance($otherPlayer)) {
1180
				return true;
1181
			}
1182
		}
1183
		return false;
1184
	}
1185
1186
	/**
1187
	 * @return array<SmrWeapon>
1188
	 */
1189
	public function getWeapons(): array {
1190
		$weapons = $this->getMountedWeapons();
1191
		return array_pad(
1192
			$weapons,
1193
			count($weapons) + $this->getBuilding(PLANET_TURRET),
1194
			SmrWeapon::getWeapon(WEAPON_PLANET_TURRET)
1195
		);
1196
	}
1197
1198
	/**
1199
	 * @param array<AbstractSmrPlayer> $targetPlayers
1200
	 * @return array<string, mixed>
1201
	 */
1202
	public function shootPlayers(array $targetPlayers): array {
1203
		$results = ['Planet' => $this, 'TotalDamage' => 0, 'TotalDamagePerTargetPlayer' => []];
1204
		foreach ($targetPlayers as $targetPlayer) {
1205
			$results['TotalDamagePerTargetPlayer'][$targetPlayer->getAccountID()] = 0;
1206
		}
1207
		if ($this->isDestroyed()) {
1208
			$results['DeadBeforeShot'] = true;
1209
			return $results;
1210
		}
1211
		$results['DeadBeforeShot'] = false;
1212
		$weapons = $this->getWeapons();
1213
		foreach ($weapons as $orderID => $weapon) {
1214
			$results['Weapons'][$orderID] = $weapon->shootPlayerAsPlanet($this, array_rand_value($targetPlayers));
0 ignored issues
show
Bug introduced by
The function array_rand_value was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1214
			$results['Weapons'][$orderID] = $weapon->shootPlayerAsPlanet($this, /** @scrutinizer ignore-call */ array_rand_value($targetPlayers));
Loading history...
1215
			if ($results['Weapons'][$orderID]['Hit']) {
1216
				$results['TotalDamage'] += $results['Weapons'][$orderID]['ActualDamage']['TotalDamage'];
1217
				$results['TotalDamagePerTargetPlayer'][$results['Weapons'][$orderID]['TargetPlayer']->getAccountID()] += $results['Weapons'][$orderID]['ActualDamage']['TotalDamage'];
1218
			}
1219
		}
1220
		if ($this->hasCDs()) {
1221
			$thisCDs = new SmrCombatDrones($this->getCDs(), true);
1222
			$results['Drones'] = $thisCDs->shootPlayerAsPlanet($this, array_rand_value($targetPlayers));
1223
			$results['TotalDamage'] += $results['Drones']['ActualDamage']['TotalDamage'];
1224
			$results['TotalDamagePerTargetPlayer'][$results['Drones']['TargetPlayer']->getAccountID()] += $results['Drones']['ActualDamage']['TotalDamage'];
1225
		}
1226
		return $results;
1227
	}
1228
1229
	/**
1230
	 * Returns an array of structure losses due to damage taken.
1231
	 *
1232
	 * @return array<int, int>
1233
	 */
1234
	public function checkForDowngrade(int $damage): array {
1235
		$results = [];
1236
		// For every 70 damage there is a 15% chance of destroying a structure.
1237
		// Which structure is destroyed depends on the ratio of buildings and
1238
		// the time it takes to build them.
1239
		$numChances = floor($damage / self::DAMAGE_NEEDED_FOR_DOWNGRADE_CHANCE);
1240
		for ($i = 0; $i < $numChances; $i++) {
1241
			// Stop if the planet has no more buildlings
1242
			if ($this->getLevel() == 0) {
1243
				break;
1244
			}
1245
			//15% chance to destroy something
1246
			if (rand(1, 100) <= self::CHANCE_TO_DOWNGRADE) {
1247
				$chanceFactors = [];
1248
				foreach ($this->getStructureTypes() as $structureID => $structure) {
1249
					$chanceFactors[$structureID] = ($this->getBuilding($structureID) / $this->getMaxBuildings($structureID)) / $structure->baseTime();
1250
				}
1251
				$destroyID = getWeightedRandom($chanceFactors);
1252
				$this->destroyBuilding($destroyID, 1);
0 ignored issues
show
Bug introduced by
It seems like $destroyID can also be of type string; however, parameter $buildingTypeID of SmrPlanet::destroyBuilding() 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

1252
				$this->destroyBuilding(/** @scrutinizer ignore-type */ $destroyID, 1);
Loading history...
1253
				$this->checkForExcessDefense();
1254
				if (isset($results[$destroyID])) {
1255
					$results[$destroyID] += 1;
1256
				} else {
1257
					$results[$destroyID] = 1;
1258
				}
1259
			}
1260
		}
1261
		return $results;
1262
	}
1263
1264
	/**
1265
	 * @param WeaponDamageData $damage
0 ignored issues
show
Bug introduced by
The type WeaponDamageData was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
1266
	 * @return array<string, int|bool>
1267
	 */
1268
	public function takeDamage(array $damage): array {
1269
		$alreadyDead = $this->isDestroyed();
1270
		$shieldDamage = 0;
1271
		$cdDamage = 0;
1272
		$armourDamage = 0;
1273
		if (!$alreadyDead) {
1274
			$shieldDamage = $this->takeDamageToShields($damage['Shield']);
1275
			if ($shieldDamage == 0 || $damage['Rollover']) {
1276
				$cdMaxDamage = $damage['Armour'] - $shieldDamage;
1277
				if ($shieldDamage == 0 && $this->hasShields()) {
1278
					$cdMaxDamage = IFloor($cdMaxDamage * DRONES_BEHIND_SHIELDS_DAMAGE_PERCENT);
0 ignored issues
show
Bug introduced by
The function IFloor was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1278
					$cdMaxDamage = /** @scrutinizer ignore-call */ IFloor($cdMaxDamage * DRONES_BEHIND_SHIELDS_DAMAGE_PERCENT);
Loading history...
1279
				}
1280
				$cdDamage = $this->takeDamageToCDs($cdMaxDamage);
1281
				if (!$this->hasShields() && ($cdDamage == 0 || $damage['Rollover'])) {
1282
					$armourMaxDamage = $damage['Armour'] - $shieldDamage - $cdDamage;
1283
					$armourDamage = $this->takeDamageToArmour($armourMaxDamage);
1284
				}
1285
			}
1286
		}
1287
1288
		return [
1289
			'KillingShot' => !$alreadyDead && $this->isDestroyed(),
1290
			'TargetAlreadyDead' => $alreadyDead,
1291
			'Shield' => $shieldDamage,
1292
			'CDs' => $cdDamage,
1293
			'NumCDs' => $cdDamage / CD_ARMOUR,
1294
			'HasCDs' => $this->hasCDs(),
1295
			'Armour' => $armourDamage,
1296
			'TotalDamage' => $shieldDamage + $cdDamage + $armourDamage,
1297
		];
1298
	}
1299
1300
	protected function takeDamageToShields(int $damage): int {
1301
		$actualDamage = min($this->getShields(), $damage);
1302
		$this->decreaseShields($actualDamage);
1303
		return $actualDamage;
1304
	}
1305
1306
	protected function takeDamageToCDs(int $damage): int {
1307
		$actualDamage = min($this->getCDs(), IFloor($damage / CD_ARMOUR));
0 ignored issues
show
Bug introduced by
The function IFloor was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1307
		$actualDamage = min($this->getCDs(), /** @scrutinizer ignore-call */ IFloor($damage / CD_ARMOUR));
Loading history...
1308
		$this->decreaseCDs($actualDamage);
1309
		return $actualDamage * CD_ARMOUR;
1310
	}
1311
1312
	protected function takeDamageToArmour(int $damage): int {
1313
		$actualDamage = min($this->getArmour(), $damage);
1314
		$this->decreaseArmour($actualDamage);
1315
		return $actualDamage;
1316
	}
1317
1318
	public function creditCurrentAttackersForKill(): void {
1319
		//get all players involved for HoF
1320
		$dbResult = $this->db->read('SELECT account_id,level FROM player_attacks_planet WHERE ' . $this->SQL . ' AND time > ' . $this->db->escapeNumber(Epoch::time() - self::TIME_TO_CREDIT_BUST));
1321
		foreach ($dbResult->records() as $dbRecord) {
1322
			$currPlayer = SmrPlayer::getPlayer($dbRecord->getInt('account_id'), $this->getGameID());
1323
			$currPlayer->increaseHOF($dbRecord->getFloat('level'), ['Combat', 'Planet', 'Levels'], HOF_PUBLIC);
1324
			$currPlayer->increaseHOF(1, ['Combat', 'Planet', 'Completed'], HOF_PUBLIC);
1325
		}
1326
		$this->db->write('DELETE FROM player_attacks_planet WHERE ' . $this->SQL);
1327
	}
1328
1329
	/**
1330
	 * @return array<string, mixed>
1331
	 */
1332
	public function killPlanetByPlayer(AbstractSmrPlayer $killer): array {
0 ignored issues
show
Unused Code introduced by
The parameter $killer is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

1332
	public function killPlanetByPlayer(/** @scrutinizer ignore-unused */ AbstractSmrPlayer $killer): array {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1333
		$this->creditCurrentAttackersForKill();
1334
1335
		//kick everyone from planet
1336
		$this->db->write('UPDATE player SET land_on_planet = \'FALSE\' WHERE ' . $this->SQL);
1337
		$this->removeOwner();
1338
		$this->removePassword();
1339
		return [];
1340
	}
1341
1342
}
1343