Scrutinizer GitHub App not installed

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

Install GitHub App

Passed
Pull Request — master (#1014)
by Dan
04:58
created

SmrPlanet   F

Complexity

Total Complexity 290

Size/Duplication

Total Lines 1309
Duplicated Lines 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 663
c 5
b 0
f 0
dl 0
loc 1309
rs 1.937
wmc 290

131 Methods

Rating   Name   Duplication   Size   Complexity  
A getStructureTypes() 0 2 1
A decreaseBonds() 0 5 2
A removePassword() 0 2 1
A getAccuracyBonus() 0 2 1
A creditCurrentAttackersForKill() 0 9 2
A getCompletionModifier() 0 4 1
A setPassword() 0 6 2
A setArmour() 0 7 2
A swapMountedWeapons() 0 17 4
A stopBuilding() 0 15 5
A getConstructionExp() 0 3 1
A getWeapons() 0 6 2
A moveMountedWeaponUp() 0 5 2
A hasShields() 0 2 1
A getAttackHREF() 0 2 1
A getOwnerID() 0 2 1
A doCDDamage() 0 4 1
A getMountedWeapons() 0 16 4
A getPlayers() 0 2 1
A getStockpile() 0 18 5
B getBuildRestriction() 0 26 9
A getOwner() 0 2 1
A getCurrentlyBuilding() 0 38 5
A getPassword() 0 2 1
A hasArmour() 0 2 1
A getTypeImage() 0 2 1
A __construct() 0 29 3
A getRemainingStockpile() 0 2 1
A getMaxArmour() 0 2 1
A getDisplayName() 0 2 1
A hasStockpile() 0 6 3
A getMaxCDs() 0 2 1
A getOtherTraders() 0 4 1
A setCDs() 0 7 2
A decreaseArmour() 0 8 3
A getBondConfirmationHREF() 0 2 1
A getCancelHREF() 0 5 1
A hasFriendlyTraders() 0 11 4
A getPlanet() 0 5 3
A isInhabitable() 0 2 1
A setName() 0 6 2
A getMaxLevel() 0 2 1
A increaseCDs() 0 8 3
A getGalaxyPlanets() 0 12 3
A getTypeID() 0 2 1
A bond() 0 11 1
B getInterestRate() 0 18 8
A increaseArmour() 0 8 3
A getCDs() 0 2 2
A exists() 0 2 1
A getMaxShields() 0 2 1
A getCredits() 0 2 1
A getBonds() 0 2 1
A increaseShields() 0 8 3
A decreaseStockpile() 0 5 2
A decreaseCDs() 0 8 3
A decreaseCredits() 0 6 2
A getCombatName() 0 2 1
A setMaturity() 0 9 3
A hasEnemyTraders() 0 11 4
A getGalaxy() 0 2 1
C doWeaponDamage() 0 42 12
A getMaxMountedWeapons() 0 2 1
A hasWeapons() 0 2 1
A getBuilding() 0 6 2
A savePlanets() 0 4 3
A getTypeName() 0 2 1
A hasCDs() 0 2 1
A getConstructionTime() 0 4 1
A getBuildings() 0 15 3
A removePlanet() 0 13 1
A accuracy() 0 6 2
B checkForExcessDefense() 0 17 8
A checkBondMaturity() 0 18 6
A getLevel() 0 2 1
A startBuilding() 0 27 4
A attackedBy() 0 21 5
A hasMountedWeapon() 0 3 1
A killPlanetByPlayer() 0 9 1
A doDelayedUpdates() 0 7 1
A shootPlayers() 0 25 6
A getSectorID() 0 2 1
A getMaxAttackers() 0 2 1
A getFinancesHREF() 0 2 1
A removeOwner() 0 2 1
A getTypeDescription() 0 2 1
A moveMountedWeaponDown() 0 5 2
A setTypeID() 0 20 5
A refreshCache() 0 4 3
A getArmour() 0 2 2
A getGame() 0 2 1
A increaseBonds() 0 5 2
A getLandHREF() 0 2 1
A setBonds() 0 9 3
A createPlanet() 0 15 3
A checkForDowngrade() 0 28 6
A hasOtherTraders() 0 2 1
A hasBuilding() 0 2 1
A addMountedWeapon() 0 4 1
A increaseStockpile() 0 5 2
A hasMenuOption() 0 3 1
A hasOwner() 0 2 1
A getInhabitableTime() 0 2 1
A isDestroyed() 0 2 3
A countPlayers() 0 2 1
A hasCurrentlyBuilding() 0 2 1
A decreaseShields() 0 8 3
A clearCache() 0 2 1
A getMaxBuildings() 0 7 2
A increaseCredits() 0 8 2
A doArmourDamage() 0 4 1
A doShieldDamage() 0 4 1
A getMaturity() 0 2 1
A getBuildHREF() 0 5 1
A getGameID() 0 2 1
A getExamineHREF() 0 2 1
A removeMountedWeapon() 0 4 1
A hasPlayers() 0 2 1
A getShields() 0 2 2
A setShields() 0 7 2
A getBondTime() 0 2 1
A setCredits() 0 12 4
A hasStructureType() 0 2 1
A getMaxLanded() 0 2 1
A setOwnerID() 0 6 2
C update() 0 69 14
A __sleep() 0 2 1
A increaseBuilding() 0 2 1
A setStockpile() 0 9 3
A setBuilding() 0 10 3
A destroyBuilding() 0 2 1

How to fix   Complexity   

Complex Class

Complex classes like SmrPlanet often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SmrPlanet, and based on these observations, apply Extract Interface, too.

1
<?php declare(strict_types=1);
2
3
// This file defines more than just one class, which is not handled by
4
// the autoloader. So we must include it explicitly.
5
require_once('SmrPlanetType.class.php');
6
7
class SmrPlanet {
8
	protected static $CACHE_PLANETS = array();
9
10
	const DAMAGE_NEEDED_FOR_DOWNGRADE_CHANCE = 100;
11
	const CHANCE_TO_DOWNGRADE = 15; // percent
12
	const TIME_TO_CREDIT_BUST = 10800; // 3 hours
13
	const TIME_ATTACK_NEWS_COOLDOWN = 3600; // 1 hour
14
	const MAX_STOCKPILE = 600;
15
16
	protected MySqlDatabase $db;
17
	protected string $SQL;
18
19
	protected bool $exists;
20
	protected int $sectorID;
21
	protected int $gameID;
22
	protected string $planetName;
23
	protected int $ownerID;
24
	protected string $password;
25
	protected int $shields;
26
	protected int $armour;
27
	protected int $drones;
28
	protected int $credits;
29
	protected int $bonds;
30
	protected int $maturity;
31
	protected array $stockpile;
32
	protected array $buildings;
33
	protected int $inhabitableTime;
34
	protected array $currentlyBuilding;
35
	protected array $mountedWeapons;
36
	protected int $typeID;
37
	protected SmrPlanetType $typeInfo;
38
39
	protected bool $hasChanged = false;
40
	protected bool $hasChangedFinancial = false; // for credits, bond, maturity
41
	protected bool $hasChangedStockpile = false;
42
	protected array $hasChangedWeapons = array();
43
	protected array $hasChangedBuildings = array();
44
	protected array $hasStoppedBuilding = array();
45
	protected bool $isNew = false;
46
47
	protected int $delayedShieldsDelta = 0;
48
	protected int $delayedCDsDelta = 0;
49
	protected int $delayedArmourDelta = 0;
50
51
	public function __sleep() {
52
		return ['sectorID', 'gameID', 'planetName', 'ownerID', 'typeID'];
53
	}
54
55
	public static function refreshCache() : void {
56
		foreach (self::$CACHE_PLANETS as $gameID => &$gamePlanets) {
57
			foreach ($gamePlanets as $sectorID => &$planet) {
58
				$planet = self::getPlanet($gameID, $sectorID, true);
59
			}
60
		}
61
	}
62
63
	public static function clearCache() : void {
64
		self::$CACHE_PLANETS = array();
65
	}
66
67
	public static function savePlanets() : void {
68
		foreach (self::$CACHE_PLANETS as $gamePlanets) {
69
			foreach ($gamePlanets as $planet) {
70
				$planet->update();
71
			}
72
		}
73
	}
74
75
	public static function getGalaxyPlanets(int $gameID, int $galaxyID, bool $forceUpdate = false) : array {
76
		$db = MySqlDatabase::getInstance();
77
		$db->query('SELECT planet.*, sector_id FROM sector LEFT JOIN planet USING (game_id, sector_id) WHERE game_id = ' . $db->escapeNumber($gameID) . ' AND galaxy_id = ' . $db->escapeNumber($galaxyID));
78
		$galaxyPlanets = [];
79
		while ($db->nextRecord()) {
80
			$sectorID = $db->getInt('sector_id');
81
			$planet = self::getPlanet($gameID, $sectorID, $forceUpdate, $db);
82
			if ($planet->exists()) {
83
				$galaxyPlanets[$sectorID] = $planet;
84
			}
85
		}
86
		return $galaxyPlanets;
87
	}
88
89
	public static function getPlanet(int $gameID, int $sectorID, bool $forceUpdate = false, MySqlDatabase $db = null) : self {
90
		if ($forceUpdate || !isset(self::$CACHE_PLANETS[$gameID][$sectorID])) {
91
			self::$CACHE_PLANETS[$gameID][$sectorID] = new SmrPlanet($gameID, $sectorID, $db);
92
		}
93
		return self::$CACHE_PLANETS[$gameID][$sectorID];
94
	}
95
96
	public static function createPlanet(int $gameID, int $sectorID, int $typeID = 1, int $inhabitableTime = null) : self {
97
		if (self::getPlanet($gameID, $sectorID)->exists()) {
98
			throw new Exception('Planet already exists in sector ' . $sectorID . ' game ' . $gameID);
99
		}
100
101
		if ($inhabitableTime === null) {
102
			$minTime = SmrGame::getGame($gameID)->getStartTime();
103
			$inhabitableTime = $minTime + pow(rand(45, 85), 3);
104
		}
105
106
		// insert planet into db
107
		$db = MySqlDatabase::getInstance();
108
		$db->query('INSERT INTO planet (game_id, sector_id, inhabitable_time, planet_type_id)
109
				VALUES (' . $db->escapeNumber($gameID) . ', ' . $db->escapeNumber($sectorID) . ', ' . $db->escapeNumber($inhabitableTime) . ', ' . $db->escapeNumber($typeID) . ')');
110
		return self::getPlanet($gameID, $sectorID, true);
111
	}
112
113
	public static function removePlanet(int $gameID, int $sectorID) : void {
114
		$db = MySqlDatabase::getInstance();
115
		$SQL = 'game_id = ' . $db->escapeNumber($gameID) . ' AND sector_id = ' . $db->escapeNumber($sectorID);
116
		$db->query('DELETE FROM planet WHERE ' . $SQL);
117
		$db->query('DELETE FROM planet_has_weapon WHERE ' . $SQL);
118
		$db->query('DELETE FROM planet_has_cargo WHERE ' . $SQL);
119
		$db->query('DELETE FROM planet_has_building WHERE ' . $SQL);
120
		$db->query('DELETE FROM planet_is_building WHERE ' . $SQL);
121
		//kick everyone from planet
122
		$db->query('UPDATE player SET land_on_planet = \'FALSE\' WHERE ' . $SQL);
123
124
		self::$CACHE_PLANETS[$gameID][$sectorID] = null;
125
		unset(self::$CACHE_PLANETS[$gameID][$sectorID]);
126
	}
127
128
	protected function __construct(int $gameID, int $sectorID, MySqlDatabase $db = null) {
129
		$this->db = MySqlDatabase::getInstance();
130
		$this->SQL = 'game_id = ' . $this->db->escapeNumber($gameID) . ' AND sector_id = ' . $this->db->escapeNumber($sectorID);
131
132
		if ($db !== null) {
133
			$this->exists = $db->hasField('planet_type_id');
134
		} else {
135
			$db = $this->db;
136
			$db->query('SELECT * FROM planet WHERE ' . $this->SQL);
137
			$this->exists = $db->nextRecord();
138
		}
139
140
		if ($this->exists) {
141
			$this->gameID = $gameID;
142
			$this->sectorID = $sectorID;
143
			$this->planetName = stripslashes($db->getField('planet_name'));
144
			$this->ownerID = $db->getInt('owner_id');
145
			$this->password = $db->getField('password');
146
			$this->shields = $db->getInt('shields');
147
			$this->armour = $db->getInt('armour');
148
			$this->drones = $db->getInt('drones');
149
			$this->credits = $db->getInt('credits');
150
			$this->bonds = $db->getInt('bonds');
151
			$this->maturity = $db->getInt('maturity');
152
			$this->inhabitableTime = $db->getInt('inhabitable_time');
153
			$this->typeID = $db->getInt('planet_type_id');
154
155
			$this->typeInfo = SmrPlanetType::getTypeInfo($this->getTypeID());
156
			$this->checkBondMaturity();
157
		}
158
	}
159
160
	public function getInterestRate() : float {
161
		$level = $this->getLevel();
162
		if ($level < 9) {
163
			return .0404;
164
		} elseif ($level < 19) {
165
			return .0609;
166
		} elseif ($level < 29) {
167
			return .1236;
168
		} elseif ($level < 39) {
169
			return .050625;
170
		} elseif ($level < 49) {
171
			return .0404;
172
		} elseif ($level < 59) {
173
			return .030225;
174
		} elseif ($level < 69) {
175
			return .0201;
176
		} else {
177
			return .018081;
178
		}
179
	}
180
181
	public function checkBondMaturity(bool $partial = false) : void {
182
		if ($this->getMaturity() > 0 && ($partial === true || $this->getMaturity() < SmrSession::getTime())) {
183
			// calc the interest for the time
184
			$interest = $this->getBonds() * $this->getInterestRate();
185
186
			if ($partial === true && $this->getMaturity() > SmrSession::getTime()) {
187
				// Adjust interest based upon how much of the bond duration has passed.
188
				$interest -= ($interest / $this->getBondTime()) * ($this->getMaturity() - SmrSession::getTime());
189
			}
190
191
			// transfer money to free avail cash
192
			$this->increaseCredits($this->getBonds() + IFloor($interest));
193
194
			// reset bonds
195
			$this->setBonds(0);
196
197
			// reset maturity
198
			$this->setMaturity(0);
199
		}
200
	}
201
202
	public function getBondTime() : int {
203
		return IRound(BOND_TIME / $this->getGame()->getGameSpeed());
204
	}
205
206
	public function bond() : void {
207
		$this->checkBondMaturity(true);
208
209
		// add it to bond
210
		$this->increaseBonds($this->getCredits());
211
212
		// set free cash to 0
213
		$this->setCredits(0);
214
215
		// initialize time
216
		$this->setMaturity(SmrSession::getTime() + $this->getBondTime());
217
	}
218
219
	public function getGameID() : int {
220
		return $this->gameID;
221
	}
222
223
	public function getGame() : SmrGame {
224
		return SmrGame::getGame($this->gameID);
225
	}
226
227
	public function getSectorID() : int {
228
		return $this->sectorID;
229
	}
230
231
	public function getGalaxy() : SmrGalaxy {
232
		return SmrGalaxy::getGalaxyContaining($this->getGameID(), $this->getSectorID());
233
	}
234
235
	public function getOwnerID() : int {
236
		return $this->ownerID;
237
	}
238
239
	public function hasOwner() : bool {
240
		return $this->ownerID != 0;
241
	}
242
243
	public function removeOwner() : void {
244
		$this->setOwnerID(0);
245
	}
246
247
	public function setOwnerID(int $claimerID) : void {
248
		if ($this->ownerID === $claimerID) {
249
			return;
250
		}
251
		$this->ownerID = $claimerID;
252
		$this->hasChanged = true;
253
	}
254
255
	public function getOwner() : SmrPlayer {
256
		return SmrPlayer::getPlayer($this->getOwnerID(), $this->getGameID());
257
	}
258
259
	public function getPassword() : string {
260
		return $this->password;
261
	}
262
263
	public function setPassword(string $password) : void {
264
		if ($this->password === $password) {
265
			return;
266
		}
267
		$this->password = $password;
268
		$this->hasChanged = true;
269
	}
270
271
	public function removePassword() : void {
272
		$this->setPassword('');
273
	}
274
275
	public function getCredits() : int {
276
		return $this->credits;
277
	}
278
279
	public function setCredits(int $num) : void {
280
		if ($this->credits === $num) {
281
			return;
282
		}
283
		if ($num < 0) {
284
			throw new Exception('You cannot set negative credits.');
285
		}
286
		if ($num > MAX_MONEY) {
287
			throw new Exception('You cannot set more than the max credits.');
288
		}
289
		$this->credits = $num;
290
		$this->hasChangedFinancial = true;
291
	}
292
293
	/**
294
	 * Increases planet credits up to the maximum allowed credits.
295
	 * Returns the amount that was actually added to handle overflow.
296
	 */
297
	public function increaseCredits(int $num) : int {
298
		if ($num === 0) {
299
			return 0;
300
		}
301
		$newTotal = min($this->credits + $num, MAX_MONEY);
302
		$actualAdded = $newTotal - $this->credits;
303
		$this->setCredits($newTotal);
304
		return $actualAdded;
305
	}
306
307
	public function decreaseCredits(int $num) : void {
308
		if ($num === 0) {
309
			return;
310
		}
311
		$newTotal = $this->credits - $num;
312
		$this->setCredits($newTotal);
313
	}
314
315
	public function getMaturity() : int {
316
		return $this->maturity;
317
	}
318
319
	public function setMaturity(int $num) : void {
320
		if ($this->maturity === $num) {
321
			return;
322
		}
323
		if ($num < 0) {
324
			throw new Exception('You cannot set negative maturity.');
325
		}
326
		$this->maturity = $num;
327
		$this->hasChangedFinancial = true;
328
	}
329
330
	public function getBonds() : int {
331
		return $this->bonds;
332
	}
333
334
	public function setBonds(int $num) : void {
335
		if ($this->bonds === $num) {
336
			return;
337
		}
338
		if ($num < 0) {
339
			throw new Exception('You cannot set negative bonds.');
340
		}
341
		$this->bonds = $num;
342
		$this->hasChangedFinancial = true;
343
	}
344
345
	public function increaseBonds(int $num) : void {
346
		if ($num === 0) {
347
			return;
348
		}
349
		$this->setBonds($this->getBonds() + $num);
350
	}
351
352
	public function decreaseBonds(int $num) : void {
353
		if ($num === 0) {
354
			return;
355
		}
356
		$this->setBonds($this->getBonds() - $num);
357
	}
358
359
	public function checkForExcessDefense() : void {
360
		if ($this->getShields() > $this->getMaxShields()) {
361
			$this->setShields($this->getMaxShields());
362
		}
363
		if ($this->getCDs() > $this->getMaxCDs()) {
364
			$this->setCDs($this->getMaxCDs());
365
		}
366
		if ($this->getArmour() > $this->getMaxArmour()) {
367
				$this->setArmour($this->getMaxArmour());
368
		}
369
		// Remove a random (0-indexed) mounted weapon, if over max mount slots
370
		while ($this->getMountedWeapons() && max(array_keys($this->getMountedWeapons())) >= $this->getMaxMountedWeapons()) {
371
			$removeID = array_rand($this->getMountedWeapons());
372
			$this->removeMountedWeapon($removeID);
373
			foreach ($this->getMountedWeapons() as $orderID => $weapon) {
374
				if ($orderID > $removeID) {
375
					$this->moveMountedWeaponUp($orderID);
376
				}
377
			}
378
		}
379
	}
380
381
	public function getShields(bool $delayed = false) : int {
382
		return $this->shields + ($delayed ? $this->delayedShieldsDelta : 0);
383
	}
384
385
	public function hasShields(bool $delayed = false) : bool {
386
		return $this->getShields($delayed) > 0;
387
	}
388
389
	public function setShields(int $shields) : void {
390
		$shields = max(0, min($shields, $this->getMaxShields()));
391
		if ($this->shields === $shields) {
392
			return;
393
		}
394
		$this->shields = $shields;
395
		$this->hasChanged = true;
396
	}
397
398
	public function decreaseShields(int $number, bool $delayed = false) : void {
399
		if ($number === 0) {
400
			return;
401
		}
402
		if ($delayed === false) {
403
			$this->setShields($this->getShields() - $number);
404
		} else {
405
			$this->delayedShieldsDelta -= $number;
406
		}
407
	}
408
409
	public function increaseShields(int $number, bool $delayed = false) : void {
410
		if ($number === 0) {
411
			return;
412
		}
413
		if ($delayed === false) {
414
			$this->setShields($this->getShields() + $number);
415
		} else {
416
			$this->delayedShieldsDelta += $number;
417
		}
418
	}
419
420
	public function getMaxShields() : int {
421
		return $this->getBuilding(PLANET_GENERATOR) * PLANET_GENERATOR_SHIELDS;
422
	}
423
424
	public function getArmour(bool $delayed = false) : int {
425
		return $this->armour + ($delayed ? $this->delayedArmourDelta : 0);
426
	}
427
428
	public function hasArmour(bool $delayed = false) : bool {
429
		return $this->getArmour($delayed) > 0;
430
	}
431
432
	public function setArmour(int $armour) : void {
433
		$armour = max(0, min($armour, $this->getMaxArmour()));
434
		if ($this->armour === $armour) {
435
			return;
436
		}
437
		$this->armour = $armour;
438
		$this->hasChanged = true;
439
	}
440
441
	public function decreaseArmour(int $number, bool $delayed = false) : void {
442
		if ($number === 0) {
443
			return;
444
		}
445
		if ($delayed === false) {
446
			$this->setArmour($this->getArmour() - $number);
447
		} else {
448
			$this->delayedArmourDelta -= $number;
449
		}
450
	}
451
452
	public function increaseArmour(int $number, bool $delayed = false) : void {
453
		if ($number === 0) {
454
			return;
455
		}
456
		if ($delayed === false) {
457
			$this->setArmour($this->getArmour() + $number);
458
		} else {
459
			$this->delayedArmourDelta += $number;
460
		}
461
	}
462
463
	public function getMaxArmour() : int {
464
		return $this->getBuilding(PLANET_BUNKER) * PLANET_BUNKER_ARMOUR;
465
	}
466
467
	public function getCDs(bool $delayed = false) : int {
468
		return $this->drones + ($delayed ? $this->delayedCDsDelta : 0);
469
	}
470
471
	public function hasCDs(bool $delayed = false) : bool {
472
		return $this->getCDs($delayed) > 0;
473
	}
474
475
	public function setCDs(int $combatDrones) : void {
476
		$combatDrones = max(0, min($combatDrones, $this->getMaxCDs()));
477
		if ($this->drones === $combatDrones) {
478
			return;
479
		}
480
		$this->drones = $combatDrones;
481
		$this->hasChanged = true;
482
	}
483
484
	public function decreaseCDs(int $number, bool $delayed = false) : void {
485
		if ($number === 0) {
486
			return;
487
		}
488
		if ($delayed === false) {
489
			$this->setCDs($this->getCDs() - $number);
490
		} else {
491
			$this->delayedCDsDelta -= $number;
492
		}
493
	}
494
495
	public function increaseCDs(int $number, bool $delayed = false) : void {
496
		if ($number === 0) {
497
			return;
498
		}
499
		if ($delayed === false) {
500
			$this->setCDs($this->getCDs() + $number);
501
		} else {
502
			$this->delayedCDsDelta += $number;
503
		}
504
	}
505
506
	public function getMaxCDs() : int {
507
		return $this->getBuilding(PLANET_HANGAR) * PLANET_HANGAR_DRONES;
508
	}
509
510
	public function getMaxMountedWeapons() : int {
511
		return $this->getBuilding(PLANET_WEAPON_MOUNT);
512
	}
513
514
	public function getMountedWeapons() : array {
515
		if (!isset($this->mountedWeapons)) {
516
			$this->mountedWeapons = [];
517
			if ($this->hasBuilding(PLANET_WEAPON_MOUNT)) {
518
				$this->db->query('SELECT * FROM planet_has_weapon JOIN weapon_type USING (weapon_type_id) WHERE ' . $this->SQL);
519
				while ($this->db->nextRecord()) {
520
					$weaponTypeID = $this->db->getInt('weapon_type_id');
521
					$orderID = $this->db->getInt('order_id');
522
					$weapon = SmrWeapon::getWeapon($weaponTypeID, $this->db);
523
					$weapon->setBonusAccuracy($this->db->getBoolean('bonus_accuracy'));
524
					$weapon->setBonusDamage($this->db->getBoolean('bonus_damage'));
525
					$this->mountedWeapons[$orderID] = $weapon;
526
				}
527
			}
528
		}
529
		return $this->mountedWeapons;
530
	}
531
532
	public function hasMountedWeapon(int $orderID) : bool {
533
		$this->getMountedWeapons(); // Make sure array is initialized
534
		return isset($this->mountedWeapons[$orderID]);
535
	}
536
537
	public function addMountedWeapon(SmrWeapon $weapon, int $orderID) : void {
538
		$this->getMountedWeapons(); // Make sure array is initialized
539
		$this->mountedWeapons[$orderID] = $weapon;
540
		$this->hasChangedWeapons[$orderID] = true;
541
	}
542
543
	public function removeMountedWeapon(int $orderID) : void {
544
		$this->getMountedWeapons(); // Make sure array is initialized
545
		unset($this->mountedWeapons[$orderID]);
546
		$this->hasChangedWeapons[$orderID] = true;
547
	}
548
549
	private function swapMountedWeapons(int $orderID1, int $orderID2) : void {
550
		$this->getMountedWeapons(); // Make sure array is initialized
551
		if (isset($this->mountedWeapons[$orderID1])) {
552
			$saveWeapon = $this->mountedWeapons[$orderID1];
553
		}
554
		if (isset($this->mountedWeapons[$orderID2])) {
555
			$this->mountedWeapons[$orderID1] = $this->mountedWeapons[$orderID2];
556
		} else {
557
			unset($this->mountedWeapons[$orderID1]);
558
		}
559
		if (isset($saveWeapon)) {
560
			$this->mountedWeapons[$orderID2] = $saveWeapon;
561
		} else {
562
			unset($this->mountedWeapons[$orderID2]);
563
		}
564
		$this->hasChangedWeapons[$orderID1] = true;
565
		$this->hasChangedWeapons[$orderID2] = true;
566
	}
567
568
	public function moveMountedWeaponUp(int $orderID) : void {
569
		if ($orderID == 0) {
570
			throw new Exception('Cannot move this weapon up!');
571
		}
572
		$this->swapMountedWeapons($orderID - 1, $orderID);
573
	}
574
575
	public function moveMountedWeaponDown(int $orderID) : void {
576
		if ($orderID == $this->getMaxMountedWeapons() - 1) {
577
			throw new Exception('Cannot move this weapon down!');
578
		}
579
		$this->swapMountedWeapons($orderID + 1, $orderID);
580
	}
581
582
583
	public function isDestroyed(bool $delayed = false) : bool {
584
		return !$this->hasCDs($delayed) && !$this->hasShields($delayed) && !$this->hasArmour($delayed);
585
	}
586
587
	public function exists() : bool {
588
		return $this->exists;
589
	}
590
591
	public function getStockpile(int $goodID = null) : int|array {
592
		if (!isset($this->stockpile)) {
593
			// initialize cargo array
594
			$this->stockpile = array();
595
			// get supplies from db
596
			$this->db->query('SELECT good_id, amount FROM planet_has_cargo WHERE ' . $this->SQL);
597
			// adding cargo and amount to array
598
			while ($this->db->nextRecord()) {
599
				$this->stockpile[$this->db->getInt('good_id')] = $this->db->getInt('amount');
600
			}
601
		}
602
		if ($goodID === null) {
603
			return $this->stockpile;
604
		}
605
		if (isset($this->stockpile[$goodID])) {
606
			return $this->stockpile[$goodID];
607
		}
608
		return 0;
609
	}
610
611
	public function hasStockpile(int $goodID = null) : bool {
612
		if ($goodID === null) {
613
			$stockpile = $this->getStockpile();
614
			return count($stockpile) > 0 && max($stockpile) > 0;
615
		} else {
616
			return $this->getStockpile($goodID) > 0;
617
		}
618
	}
619
620
	public function setStockpile(int $goodID, int $amount) : void {
621
		if ($this->getStockpile($goodID) === $amount) {
622
			return;
623
		}
624
		if ($amount < 0) {
625
			throw new Exception('Trying to set negative stockpile.');
626
		}
627
		$this->stockpile[$goodID] = $amount;
628
		$this->hasChangedStockpile = true;
629
	}
630
631
	public function decreaseStockpile(int $goodID, int $amount) : void {
632
		if ($amount < 0) {
633
			throw new Exception('Trying to decrease negative stockpile.');
634
		}
635
		$this->setStockpile($goodID, $this->getStockpile($goodID) - $amount);
636
	}
637
638
	public function increaseStockpile(int $goodID, int $amount) : void {
639
		if ($amount < 0) {
640
			throw new Exception('Trying to increase negative stockpile.');
641
		}
642
		$this->setStockpile($goodID, $this->getStockpile($goodID) + $amount);
643
	}
644
645
	public function getBuildings() : array {
646
		if (!isset($this->buildings)) {
647
			$this->buildings = array();
648
649
			// get buildingss from db
650
			$this->db->query('SELECT construction_id, amount FROM planet_has_building WHERE ' . $this->SQL);
651
			// adding building and amount to array
652
			while ($this->db->nextRecord()) {
653
				$this->buildings[$this->db->getInt('construction_id')] = $this->db->getInt('amount');
654
			}
655
656
			// Update building counts if construction has finished
657
			$this->getCurrentlyBuilding();
658
		}
659
		return $this->buildings;
660
	}
661
662
	public function getBuilding(int $buildingTypeID) : int {
663
		$buildings = $this->getBuildings();
664
		if (isset($buildings[$buildingTypeID])) {
665
			return $buildings[$buildingTypeID];
666
		}
667
		return 0;
668
	}
669
670
	public function hasBuilding(int $buildingTypeID) : bool {
671
		return $this->getBuilding($buildingTypeID) > 0;
672
	}
673
674
	public function setBuilding(int $buildingTypeID, int $number) : void {
675
		if ($this->getBuilding($buildingTypeID) === $number) {
676
			return;
677
		}
678
		if ($number < 0) {
679
			throw new Exception('Cannot set negative number of buildings.');
680
		}
681
682
		$this->buildings[$buildingTypeID] = $number;
683
		$this->hasChangedBuildings[$buildingTypeID] = true;
684
	}
685
686
	public function increaseBuilding(int $buildingTypeID, int $number) : void {
687
		$this->setBuilding($buildingTypeID, $this->getBuilding($buildingTypeID) + $number);
688
	}
689
690
	public function destroyBuilding(int $buildingTypeID, int $number) : void {
691
		$this->setBuilding($buildingTypeID, $this->getBuilding($buildingTypeID) - $number);
692
	}
693
694
	public function getCurrentlyBuilding() : array {
695
		if (!isset($this->currentlyBuilding)) {
696
			$this->currentlyBuilding = array();
697
698
			$this->db->query('SELECT * FROM planet_is_building WHERE ' . $this->SQL);
699
			while ($this->db->nextRecord()) {
700
				$this->currentlyBuilding[$this->db->getInt('building_slot_id')] = array(
701
					'BuildingSlotID' => $this->db->getInt('building_slot_id'),
702
					'ConstructionID' => $this->db->getInt('construction_id'),
703
					'ConstructorID' => $this->db->getInt('constructor_id'),
704
					'Finishes' => $this->db->getInt('time_complete'),
705
					'TimeRemaining' => $this->db->getInt('time_complete') - SmrSession::getTime(),
706
				);
707
			}
708
709
			// Check if construction has completed
710
			foreach ($this->currentlyBuilding as $id => $building) {
711
				if ($building['TimeRemaining'] <= 0) {
712
					unset($this->currentlyBuilding[$id]);
713
					$expGain = $this->getConstructionExp($building['ConstructionID']);
714
					$player = SmrPlayer::getPlayer($building['ConstructorID'], $this->getGameID());
715
					$player->increaseHOF(1, array('Planet', 'Buildings', 'Built'), HOF_ALLIANCE);
716
					$player->increaseExperience($expGain);
717
					$player->increaseHOF($expGain, array('Planet', 'Buildings', 'Experience'), HOF_ALLIANCE);
718
					$this->hasStoppedBuilding[] = $building['BuildingSlotID'];
719
					$this->increaseBuilding($building['ConstructionID'], 1);
720
721
					// WARNING: The above modifications to the player/planet are dangerous because
722
					// they may not be part of the current sector lock. But since they might not be,
723
					// we may as well just update now to avoid either a) needing to remember to call
724
					// this explicitly in all appropriate engine files or b) inconsistent exp display
725
					// if this is called during the template display only and therefore unsaved.
726
					$player->update();
727
					$this->update();
728
				}
729
			}
730
		}
731
		return $this->currentlyBuilding;
732
	}
733
734
	public function getMaxBuildings(int $buildingTypeID = null) : int|array {
735
		if ($buildingTypeID === null) {
736
			$structs = $this->typeInfo::STRUCTURES;
0 ignored issues
show
Bug introduced by
The constant SmrPlanetType::STRUCTURES was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
737
			return array_combine(array_keys($structs),
738
			                     array_column($structs, 'max_amount'));
739
		}
740
		return $this->getStructureTypes($buildingTypeID)->maxAmount();
741
	}
742
743
	public function getTypeID() : int {
744
		return $this->typeID;
745
	}
746
747
	public function setTypeID(int $num) : void {
748
		if (isset($this->typeID) && $this->typeID === $num) {
749
			return;
750
		}
751
		$this->typeID = $num;
752
		$this->db->query('UPDATE planet SET planet_type_id = ' . $this->db->escapeNumber($num) . ' WHERE ' . $this->SQL);
753
		$this->typeInfo = SmrPlanetType::getTypeInfo($this->getTypeID());
754
755
		//trim buildings first
756
		foreach ($this->getBuildings() as $id => $amt) {
757
			if ($this->getMaxBuildings($id) < $amt) {
758
				$this->destroyBuilding($id, $amt - $this->getMaxBuildings($id));
759
			}
760
		}
761
762
		//trim excess defenses
763
		$this->checkForExcessDefense();
764
765
		$this->hasChanged = true;
766
		$this->update();
767
	}
768
769
	public function getTypeImage() : string {
770
		return $this->typeInfo->imageLink();
771
	}
772
773
	public function getTypeName() : string {
774
		return $this->typeInfo->name();
775
	}
776
777
	public function getTypeDescription() : string {
778
		return $this->typeInfo->description();
779
	}
780
781
	public function getMaxAttackers() : int {
782
		return $this->typeInfo->maxAttackers();
783
	}
784
785
	public function getMaxLanded() : int {
786
		return $this->typeInfo->maxLanded();
787
	}
788
789
	public function getStructureTypes(int $structureID = null) : SmrPlanetStructureType|array {
790
		return $this->typeInfo->structureTypes($structureID);
791
	}
792
793
	public function hasStructureType(int $structureID) : bool {
794
		return isset($this->getStructureTypes()[$structureID]);
795
	}
796
797
	/**
798
	 * Specifies which menu options the planet has.
799
	 */
800
	public function hasMenuOption(string $option) : bool {
801
		// We do not set options that are unavailable
802
		return in_array($option, $this->typeInfo->menuOptions());
803
	}
804
805
	public function doDelayedUpdates() : void {
806
		$this->setShields($this->getShields(true));
807
		$this->delayedShieldsDelta = 0;
808
		$this->setCDs($this->getCDs(true));
809
		$this->delayedCDsDelta = 0;
810
		$this->setArmour($this->getArmour(true));
811
		$this->delayedArmourDelta = 0;
812
	}
813
814
	public function update() : void {
815
		if (!$this->exists()) {
816
			return;
817
		}
818
		$this->doDelayedUpdates();
819
		if ($this->hasChanged) {
820
			$this->db->query('UPDATE planet SET
821
									owner_id = ' . $this->db->escapeNumber($this->ownerID) . ',
822
									password = '.$this->db->escapeString($this->password) . ',
823
									planet_name = ' . $this->db->escapeString($this->planetName) . ',
824
									shields = ' . $this->db->escapeNumber($this->shields) . ',
825
									armour = ' . $this->db->escapeNumber($this->armour) . ',
826
									drones = ' . $this->db->escapeNumber($this->drones) . '
827
								WHERE ' . $this->SQL);
828
			$this->hasChanged = false;
829
		}
830
831
		// Separate update for financial since these can be modified by looking
832
		// at the planet list (i.e. you might not have sector lock and could
833
		// cause a race condition with events happening in the planet sector).
834
		if ($this->hasChangedFinancial) {
835
			$this->db->query('UPDATE planet SET
836
									credits = ' . $this->db->escapeNumber($this->credits) . ',
837
									bonds = ' . $this->db->escapeNumber($this->bonds) . ',
838
									maturity = ' . $this->db->escapeNumber($this->maturity) . '
839
								WHERE ' . $this->SQL);
840
			$this->hasChangedFinancial = false;
841
		}
842
843
		if ($this->hasChangedStockpile) {
844
			// write stockpile info
845
			foreach ($this->getStockpile() as $id => $amount) {
846
				if ($amount != 0) {
847
					$this->db->query('REPLACE INTO planet_has_cargo (game_id, sector_id, good_id, amount) ' .
848
										 'VALUES(' . $this->db->escapeNumber($this->getGameID()) . ', ' . $this->db->escapeNumber($this->getSectorID()) . ', ' . $this->db->escapeNumber($id) . ', ' . $this->db->escapeNumber($amount) . ')');
849
				} else {
850
					$this->db->query('DELETE FROM planet_has_cargo WHERE ' . $this->SQL . '
851
										AND good_id = ' . $this->db->escapeNumber($id));
852
				}
853
			}
854
		}
855
856
		if (count($this->hasChangedWeapons) > 0) {
857
			foreach (array_keys($this->hasChangedWeapons) as $orderID) {
858
				if (isset($this->mountedWeapons[$orderID])) {
859
					$this->db->query('REPLACE INTO planet_has_weapon (game_id, sector_id, order_id, weapon_type_id, bonus_accuracy, bonus_damage) VALUES (' . $this->db->escapeNumber($this->getGameID()) . ',' . $this->db->escapeNumber($this->getSectorID()) . ',' . $this->db->escapeNumber($orderID) . ',' . $this->db->escapeNumber($this->mountedWeapons[$orderID]->getWeaponTypeID()) . ',' . $this->db->escapeBoolean($this->mountedWeapons[$orderID]->hasBonusAccuracy()) . ',' . $this->db->escapeBoolean($this->mountedWeapons[$orderID]->hasBonusDamage()) . ')');
860
				} else {
861
					$this->db->query('DELETE FROM planet_has_weapon WHERE ' . $this->SQL . ' AND order_id=' . $this->db->escapeNumber($orderID));
862
				}
863
			}
864
			$this->hasChangedWeapons = [];
865
		}
866
867
		if (count($this->hasStoppedBuilding) > 0) {
868
			$this->db->query('DELETE FROM planet_is_building WHERE ' . $this->SQL . '
869
								AND building_slot_id IN (' . $this->db->escapeArray($this->hasStoppedBuilding) . ') LIMIT ' . count($this->hasStoppedBuilding));
870
			$this->hasStoppedBuilding = array();
871
		}
872
		// write building info
873
		foreach ($this->hasChangedBuildings as $id => $hasChanged) {
874
			if ($hasChanged === true) {
875
				if ($this->hasBuilding($id)) {
876
					$this->db->query('REPLACE INTO planet_has_building (game_id, sector_id, construction_id, amount) ' .
877
										'VALUES(' . $this->db->escapeNumber($this->gameID) . ', ' . $this->db->escapeNumber($this->sectorID) . ', ' . $this->db->escapeNumber($id) . ', ' . $this->db->escapeNumber($this->getBuilding($id)) . ')');
878
				} else {
879
					$this->db->query('DELETE FROM planet_has_building WHERE ' . $this->SQL . '
880
										AND construction_id = ' . $this->db->escapeNumber($id));
881
				}
882
				$this->hasChangedBuildings[$id] = false;
883
			}
884
		}
885
	}
886
887
	public function getLevel() : float {
888
		return array_sum($this->getBuildings()) / 3;
889
	}
890
891
	public function getMaxLevel() : float {
892
		return array_sum($this->getMaxBuildings()) / 3;
893
	}
894
895
	public function accuracy() : float {
896
		if ($this->hasWeapons()) {
897
			$weapons = $this->getWeapons();
898
			return $weapons[0]->getModifiedPlanetAccuracy($this);
899
		}
900
		return 0;
901
	}
902
903
	/**
904
	 * Returns the accuracy bonus for mounted weaons (as a percent)
905
	 */
906
	public function getAccuracyBonus() : int {
907
		return 5 * $this->getBuilding(PLANET_RADAR);
908
	}
909
910
	public function getRemainingStockpile(int $id) : int {
911
		return self::MAX_STOCKPILE - $this->getStockpile($id);
912
	}
913
914
	/**
915
	 * Returns true if there is a building in progress
916
	 */
917
	public function hasCurrentlyBuilding() : bool {
918
		return count($this->getCurrentlyBuilding()) > 0;
919
	}
920
921
	/**
922
	 * Returns the reason a build cannot be performed, or false if there is
923
	 * no restriction.
924
	 */
925
	public function getBuildRestriction(AbstractSmrPlayer $constructor, int $constructionID) : string|false {
926
		if ($this->hasCurrentlyBuilding()) {
927
			return 'There is already a building in progress!';
928
		}
929
		if ($this->getBuilding($constructionID) >= $this->getMaxBuildings($constructionID)) {
930
			return 'This planet has reached the maximum buildings of that type.';
931
		}
932
		$cost = $this->getStructureTypes($constructionID)->creditCost();
933
		if ($constructor->getCredits() < $cost) {
934
			return 'You do not have enough credits.';
935
		}
936
		if ($constructor->getTurns() < TURNS_TO_BUILD) {
937
			return 'You do not have enough turns to build.';
938
		}
939
		foreach ($this->getStructureTypes($constructionID)->hardwareCost() as $hardwareID) {
940
			if (!$constructor->getShip()->getHardware($hardwareID)) {
941
				return 'You do not have the hardware needed for this type of building!';
942
			}
943
		}
944
		// take the goods that are needed
945
		foreach ($this->getStructureTypes($constructionID)->goods() as $goodID => $amount) {
946
			if ($this->getStockpile($goodID) < $amount) {
947
				return 'There are not enough goods available.';
948
			}
949
		}
950
		return false;
951
	}
952
953
	// Modifier for planet building based on the number of buildings.
954
	// The average value of this modifier should roughly be 1.
955
	private function getCompletionModifier(int $constructionID) : float {
956
		$currentBuildings = $this->getBuilding($constructionID);
957
		$maxBuildings = $this->getMaxBuildings($constructionID);
958
		return 0.01 + 2.97 * pow($currentBuildings / $maxBuildings, 2);
959
	}
960
961
	// Amount of exp gained to build the next building of this type
962
	private function getConstructionExp(int $constructionID) : int {
963
		$expGain = $this->getStructureTypes($constructionID)->expGain();
964
		return $expGain;
965
	}
966
967
	// Amount of time (in seconds) to build the next building of this type
968
	public function getConstructionTime(int $constructionID) : int {
969
		$baseTime = $this->getStructureTypes($constructionID)->baseTime();
970
		$constructionTime = ICeil($baseTime * $this->getCompletionModifier($constructionID) / $this->getGame()->getGameSpeed());
971
		return $constructionTime;
972
	}
973
974
	/**
975
	 * @throws \Smr\UserException 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 \Smr\UserException('Unable to start building: ' . $restriction);
0 ignored issues
show
Bug introduced by
The function UserException 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

980
			throw /** @scrutinizer ignore-call */ \Smr\UserException('Unable to start building: ' . $restriction);
Loading history...
981
		}
982
983
		// gets the time for the buildings
984
		$timeComplete = SmrSession::getTime() + $this->getConstructionTime($constructionID);
985
		$this->db->query('INSERT INTO planet_is_building (game_id, sector_id, construction_id, constructor_id, time_complete) ' .
986
						'VALUES (' . $this->db->escapeNumber($this->getGameID()) . ', ' . $this->db->escapeNumber($this->getSectorID()) . ', ' . $this->db->escapeNumber($constructionID) . ', ' . $this->db->escapeNumber($constructor->getAccountID()) . ',' . $this->db->escapeNumber($timeComplete) . ')');
987
988
		$this->currentlyBuilding[$this->db->getInsertID()] = array(
989
			'BuildingSlotID' => $this->db->getInsertID(),
990
			'ConstructionID' => $constructionID,
991
			'ConstructorID' => $constructor->getAccountID(),
992
			'Finishes' => $timeComplete,
993
			'TimeRemaining' => $timeComplete - SmrSession::getTime()
994
		);
995
996
		// Consume the required resources
997
		$constructor->decreaseCredits($this->getStructureTypes($constructionID)->creditCost());
998
		$constructor->takeTurns(TURNS_TO_BUILD);
999
		foreach ($this->getStructureTypes($constructionID)->goods() as $goodID => $amount) {
1000
			$this->decreaseStockpile($goodID, $amount);
1001
		}
1002
		foreach ($this->getStructureTypes($constructionID)->hardwareCost() as $hardwareID) {
1003
			$constructor->getShip()->setHardware($hardwareID, 0);
1004
		}
1005
	}
1006
1007
	public function stopBuilding(int $constructionID) : bool {
1008
		$matchingBuilding = false;
1009
		$latestFinish = 0;
1010
		foreach ($this->getCurrentlyBuilding() as $key => $building) {
1011
			if ($building['ConstructionID'] == $constructionID && $building['Finishes'] > $latestFinish) {
1012
				$latestFinish = $building['Finishes'];
1013
				$matchingBuilding = $building;
1014
			}
1015
		}
1016
		if ($matchingBuilding) {
1017
			$this->hasStoppedBuilding[] = $matchingBuilding['BuildingSlotID'];
1018
			unset($this->currentlyBuilding[$matchingBuilding['BuildingSlotID']]);
1019
			return true;
1020
		}
1021
		return false;
1022
	}
1023
1024
	public function setName(string $name) : void {
1025
		if ($this->planetName === $name) {
1026
			return;
1027
		}
1028
		$this->planetName = $name;
1029
		$this->hasChanged = true;
1030
	}
1031
1032
	/**
1033
	 * Returns the name of the planet, suitably escaped for HTML display.
1034
	 */
1035
	public function getDisplayName() : string {
1036
		return htmlentities($this->planetName);
1037
	}
1038
1039
	/**
1040
	 * Returns the name of the planet, intended for combat messages.
1041
	 */
1042
	public function getCombatName() : string {
1043
		return '<span style="color:yellow;font-variant:small-caps">' . $this->getDisplayName() . ' (#' . $this->getSectorID() . ')</span>';
1044
	}
1045
1046
	public function isInhabitable() : bool {
1047
		return $this->inhabitableTime <= SmrSession::getTime();
1048
	}
1049
1050
	public function getInhabitableTime() : int {
1051
		return $this->inhabitableTime;
1052
	}
1053
1054
	public function getExamineHREF() : string {
1055
		return SmrSession::getNewHREF(create_container('skeleton.php', 'planet_examine.php'));
1056
	}
1057
1058
	public function getLandHREF() : string {
1059
		return SmrSession::getNewHREF(create_container('planet_land_processing.php'));
1060
	}
1061
1062
	public function getAttackHREF() : string {
1063
		return SmrSession::getNewHREF(create_container('planet_attack_processing.php'));
1064
	}
1065
1066
	public function getBuildHREF(int $structureID) : string {
1067
		$container = create_container('planet_construction_processing.php');
1068
		$container['construction_id'] = $structureID;
1069
		$container['action'] = 'Build';
1070
		return SmrSession::getNewHREF($container);
1071
	}
1072
1073
	public function getCancelHREF(int $structureID) : string {
1074
		$container = create_container('planet_construction_processing.php');
1075
		$container['construction_id'] = $structureID;
1076
		$container['action'] = 'Cancel';
1077
		return SmrSession::getNewHREF($container);
1078
	}
1079
1080
	public function getFinancesHREF() : string {
1081
		return SmrSession::getNewHREF(create_container('planet_financial_processing.php'));
1082
	}
1083
1084
	public function getBondConfirmationHREF() : string {
1085
		return SmrSession::getNewHREF(create_container('skeleton.php', 'planet_bond_confirmation.php'));
1086
	}
1087
1088
	public function attackedBy(AbstractSmrPlayer $trigger, array $attackers) : void {
1089
		$trigger->increaseHOF(1, array('Combat', 'Planet', 'Number Of Triggers'), HOF_PUBLIC);
1090
		foreach ($attackers as $attacker) {
1091
			$attacker->increaseHOF(1, array('Combat', 'Planet', 'Number Of Attacks'), HOF_PUBLIC);
1092
			$this->db->query('REPLACE INTO player_attacks_planet (game_id, account_id, sector_id, time, level) VALUES ' .
1093
					'(' . $this->db->escapeNumber($this->getGameID()) . ', ' . $this->db->escapeNumber($attacker->getAccountID()) . ', ' . $this->db->escapeNumber($this->getSectorID()) . ', ' . $this->db->escapeNumber(SmrSession::getTime()) . ', ' . $this->db->escapeNumber($this->getLevel()) . ')');
1094
		}
1095
1096
		// Add each unique attack to news unless it was already added recently.
1097
		// Note: Attack uniqueness determined by planet owner.
1098
		$owner = $this->getOwner();
1099
		$this->db->query('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(SmrSession::getTime() - self::TIME_ATTACK_NEWS_COOLDOWN) . ' LIMIT 1');
1100
		if ($this->db->getNumRows() == 0) {
1101
			if (count($attackers) >= 5) {
1102
				$text = count($attackers) . ' members of ' . $trigger->getAllianceBBLink() . ' have been spotted attacking ' .
1103
					$this->getDisplayName() . ' in sector ' . Globals::getSectorBBLink($this->getSectorID()) . '. The planet is owned by ' . $owner->getBBLink();
1104
				if ($owner->hasAlliance()) {
1105
					$text .= ', a member of ' . $owner->getAllianceBBLink();
1106
				}
1107
				$text .= '.';
1108
				$this->db->query('INSERT INTO news (game_id, time, news_message, type,killer_id,killer_alliance,dead_id,dead_alliance) VALUES (' . $this->db->escapeNumber($this->getGameID()) . ', ' . $this->db->escapeNumber(SmrSession::getTime()) . ', ' . $this->db->escapeString($text) . ', \'BREAKING\',' . $this->db->escapeNumber($trigger->getAccountID()) . ',' . $this->db->escapeNumber($trigger->getAllianceID()) . ',' . $this->db->escapeNumber($owner->getAccountID()) . ',' . $this->db->escapeNumber($owner->getAllianceID()) . ')');
1109
			}
1110
		}
1111
	}
1112
1113
1114
	public function getPlayers() : array {
1115
		return SmrPlayer::getPlanetPlayers($this->getGameID(), $this->getSectorID());
1116
	}
1117
1118
	public function countPlayers() : int {
1119
		return count($this->getPlayers());
1120
	}
1121
1122
	public function hasPlayers() : bool {
1123
		return $this->countPlayers() > 0;
1124
	}
1125
1126
	public function getOtherTraders(AbstractSmrPlayer $player) : array {
1127
		$players = SmrPlayer::getPlanetPlayers($this->getGameID(), $this->getSectorID()); //Do not use & because we unset something and only want that in what we return
1128
		unset($players[$player->getAccountID()]);
1129
		return $players;
1130
	}
1131
1132
	public function hasOtherTraders(AbstractSmrPlayer $player) : bool {
1133
		return count($this->getOtherTraders($player)) > 0;
1134
	}
1135
1136
	public function hasEnemyTraders(AbstractSmrPlayer $player) : bool {
1137
		if (!$this->hasOtherTraders($player)) {
1138
			return false;
1139
		}
1140
		$otherPlayers = $this->getOtherTraders($player);
1141
		foreach ($otherPlayers as $otherPlayer) {
1142
			if (!$player->traderNAPAlliance($otherPlayer)) {
1143
				return true;
1144
			}
1145
		}
1146
		return false;
1147
	}
1148
1149
	public function hasFriendlyTraders(AbstractSmrPlayer $player) : bool {
1150
		if (!$this->hasOtherTraders($player)) {
1151
			return false;
1152
		}
1153
		$otherPlayers = $this->getOtherTraders($player);
1154
		foreach ($otherPlayers as $otherPlayer) {
1155
			if ($player->traderNAPAlliance($otherPlayer)) {
1156
				return true;
1157
			}
1158
		}
1159
		return false;
1160
	}
1161
1162
	public function getWeapons() : array {
1163
		$weapons = $this->getMountedWeapons();
1164
		for ($i = 0; $i < $this->getBuilding(PLANET_TURRET); ++$i) {
1165
			$weapons[] = SmrWeapon::getWeapon(WEAPON_PLANET_TURRET);
1166
		}
1167
		return $weapons;
1168
	}
1169
1170
	public function hasWeapons() : bool {
1171
		return count($this->getWeapons()) > 0;
1172
	}
1173
1174
	public function shootPlayers(array $targetPlayers) : array {
1175
		$results = array('Planet' => $this, 'TotalDamage' => 0, 'TotalDamagePerTargetPlayer' => array());
1176
		foreach ($targetPlayers as $targetPlayer) {
1177
			$results['TotalDamagePerTargetPlayer'][$targetPlayer->getAccountID()] = 0;
1178
		}
1179
		if ($this->isDestroyed()) {
1180
			$results['DeadBeforeShot'] = true;
1181
			return $results;
1182
		}
1183
		$results['DeadBeforeShot'] = false;
1184
		$weapons = $this->getWeapons();
1185
		foreach ($weapons as $orderID => $weapon) {
1186
			$results['Weapons'][$orderID] = $weapon->shootPlayerAsPlanet($this, array_rand_value($targetPlayers));
1187
			if ($results['Weapons'][$orderID]['Hit']) {
1188
				$results['TotalDamage'] += $results['Weapons'][$orderID]['ActualDamage']['TotalDamage'];
1189
				$results['TotalDamagePerTargetPlayer'][$results['Weapons'][$orderID]['TargetPlayer']->getAccountID()] += $results['Weapons'][$orderID]['ActualDamage']['TotalDamage'];
1190
			}
1191
		}
1192
		if ($this->hasCDs()) {
1193
			$thisCDs = new SmrCombatDrones($this->getGameID(), $this->getCDs(), true);
1194
			$results['Drones'] = $thisCDs->shootPlayerAsPlanet($this, array_rand_value($targetPlayers));
1195
			$results['TotalDamage'] += $results['Drones']['ActualDamage']['TotalDamage'];
1196
			$results['TotalDamagePerTargetPlayer'][$results['Drones']['TargetPlayer']->getAccountID()] += $results['Drones']['ActualDamage']['TotalDamage'];
1197
		}
1198
		return $results;
1199
	}
1200
1201
	/**
1202
	 * Returns an array of structure losses due to damage taken.
1203
	 */
1204
	public function checkForDowngrade(int $damage) : array {
1205
		$results = [];
1206
		// For every 70 damage there is a 15% chance of destroying a structure.
1207
		// Which structure is destroyed depends on the ratio of buildings and
1208
		// the time it takes to build them.
1209
		$numChances = floor($damage / self::DAMAGE_NEEDED_FOR_DOWNGRADE_CHANCE);
1210
		for ($i = 0; $i < $numChances; $i++) {
1211
			// Stop if the planet has no more buildlings
1212
			if ($this->getLevel() == 0) {
1213
				break;
1214
			}
1215
			//15% chance to destroy something
1216
			if (rand(1, 100) <= self::CHANCE_TO_DOWNGRADE) {
1217
				$chanceFactors = [];
1218
				foreach ($this->getStructureTypes() as $structureID => $structure) {
1219
					$chanceFactors[$structureID] = ($this->getBuilding($structureID) / $this->getMaxBuildings($structureID)) / $structure->baseTime();
1220
				}
1221
				$destroyID = getWeightedRandom($chanceFactors);
1222
				$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

1222
				$this->destroyBuilding(/** @scrutinizer ignore-type */ $destroyID, 1);
Loading history...
1223
				$this->checkForExcessDefense();
1224
				if (isset($results[$destroyID])) {
1225
					$results[$destroyID] += 1;
1226
				} else {
1227
					$results[$destroyID] = 1;
1228
				}
1229
			}
1230
		}
1231
		return $results;
1232
	}
1233
1234
	public function doWeaponDamage(array $damage, bool $delayed) : array {
1235
		$alreadyDead = $this->isDestroyed(true);
1236
		$shieldDamage = 0;
1237
		$cdDamage = 0;
1238
		$armourDamage = 0;
1239
		if (!$alreadyDead) {
1240
			if ($damage['Shield'] || !$this->hasShields(true)) {
1241
				$shieldDamage = $this->doShieldDamage(min($damage['MaxDamage'], $damage['Shield']), $delayed);
1242
				$damage['Shield'] -= $shieldDamage;
1243
				$damage['MaxDamage'] -= $shieldDamage;
1244
1245
				if (!$this->hasShields(true) && ($shieldDamage == 0 || $damage['Rollover'])) {
1246
					if ($this->hasCDs(true)) {
1247
						$cdDamage = $this->doCDDamage(min($damage['MaxDamage'], $damage['Armour']), $delayed);
1248
						$damage['Armour'] -= $cdDamage;
1249
						$damage['MaxDamage'] -= $cdDamage;
1250
					}
1251
					if ($this->hasArmour(true) && ($cdDamage == 0 || $damage['Rollover'])) {
1252
						$armourDamage = $this->doArmourDamage(min($damage['MaxDamage'], $damage['Armour']), $delayed);
1253
						$damage['Armour'] -= $armourDamage;
1254
						$damage['MaxDamage'] -= $armourDamage;
1255
					}
1256
				}
1257
1258
			} else { // hit drones behind shields - we should only use this reduced damage branch if we cannot hit shields.
1259
				$cdDamage = $this->doCDDamage(IFloor(min($damage['MaxDamage'], $damage['Armour']) * DRONES_BEHIND_SHIELDS_DAMAGE_PERCENT), $delayed);
1260
			}
1261
		}
1262
1263
		$return = array(
1264
			'KillingShot' => !$alreadyDead && $this->isDestroyed(true),
1265
			'TargetAlreadyDead' => $alreadyDead,
1266
			'Shield' => $shieldDamage,
1267
			'Armour' => $armourDamage,
1268
			'HasShields' => $this->hasShields(true),
1269
			'HasArmour' => $this->hasArmour(true),
1270
			'CDs' => $cdDamage,
1271
			'NumCDs' => $cdDamage / CD_ARMOUR,
1272
			'HasCDs' => $this->hasCDs(true),
1273
			'TotalDamage' => $shieldDamage + $cdDamage + $armourDamage
1274
		);
1275
		return $return;
1276
	}
1277
1278
	protected function doShieldDamage(int $damage, bool $delayed) : int {
1279
		$actualDamage = min($this->getShields(true), $damage);
1280
		$this->decreaseShields($actualDamage, $delayed);
1281
		return $actualDamage;
1282
	}
1283
1284
	protected function doCDDamage(int $damage, bool $delayed) : int {
1285
		$actualDamage = min($this->getCDs(true), IFloor($damage / CD_ARMOUR));
1286
		$this->decreaseCDs($actualDamage, $delayed);
1287
		return $actualDamage * CD_ARMOUR;
1288
	}
1289
1290
	protected function doArmourDamage(int $damage, bool $delayed) : int {
1291
		$actualDamage = min($this->getArmour(true), $damage);
1292
		$this->decreaseArmour($actualDamage, $delayed);
1293
		return $actualDamage;
1294
	}
1295
1296
	public function creditCurrentAttackersForKill() : void {
1297
		//get all players involved for HoF
1298
		$this->db->query('SELECT account_id,level FROM player_attacks_planet WHERE ' . $this->SQL . ' AND time > ' . $this->db->escapeNumber(SmrSession::getTime() - self::TIME_TO_CREDIT_BUST));
1299
		while ($this->db->nextRecord()) {
1300
			$currPlayer = SmrPlayer::getPlayer($this->db->getInt('account_id'), $this->getGameID());
1301
			$currPlayer->increaseHOF($this->db->getInt('level'), array('Combat', 'Planet', 'Levels'), HOF_PUBLIC);
1302
			$currPlayer->increaseHOF(1, array('Combat', 'Planet', 'Completed'), HOF_PUBLIC);
1303
		}
1304
		$this->db->query('DELETE FROM player_attacks_planet WHERE ' . $this->SQL);
1305
	}
1306
1307
	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

1307
	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...
1308
		$return = array();
1309
		$this->creditCurrentAttackersForKill();
1310
1311
		//kick everyone from planet
1312
		$this->db->query('UPDATE player SET land_on_planet = \'FALSE\' WHERE ' . $this->SQL);
1313
		$this->removeOwner();
1314
		$this->removePassword();
1315
		return $return;
1316
	}
1317
1318
}
1319