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 (#1006)
by Dan
15:04
created

SmrPlanet::__sleep()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 2
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
// 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 $db;
17
	protected $SQL;
18
19
	protected $sectorID;
20
	protected $gameID;
21
	protected $planetName;
22
	protected $ownerID;
23
	protected $password;
24
	protected $shields;
25
	protected $armour;
26
	protected $drones;
27
	protected $credits;
28
	protected $bonds;
29
	protected $maturity;
30
	protected $stockpile;
31
	protected $buildings;
32
	protected $inhabitableTime;
33
	protected $currentlyBuilding;
34
	protected $mountedWeapons;
35
	protected $typeID;
36
	protected $typeInfo;
37
38
	protected $hasChanged = false;
39
	protected $hasChangedFinancial = false; // for credits, bond, maturity
40
	protected $hasChangedStockpile = false;
41
	protected $hasChangedWeapons = array();
42
	protected $hasChangedBuildings = array();
43
	protected $hasStoppedBuilding = array();
44
	protected $isNew = false;
45
46
	protected $delayedShieldsDelta = 0;
47
	protected $delayedCDsDelta = 0;
48
	protected $delayedArmourDelta = 0;
49
50
	public function __sleep() {
51
		return ['sectorID', 'gameID', 'planetName', 'ownerID', 'typeID'];
52
	}
53
54
	public static function refreshCache() {
55
		foreach (self::$CACHE_PLANETS as $gameID => &$gamePlanets) {
56
			foreach ($gamePlanets as $sectorID => &$planet) {
57
				$planet = self::getPlanet($gameID, $sectorID, true);
58
			}
59
		}
60
	}
61
62
	public static function clearCache() {
63
		self::$CACHE_PLANETS = array();
64
	}
65
66
	public static function savePlanets() {
67
		foreach (self::$CACHE_PLANETS as $gamePlanets) {
68
			foreach ($gamePlanets as $planet) {
69
				$planet->update();
70
			}
71
		}
72
	}
73
74
	public static function getGalaxyPlanets($gameID, $galaxyID, $forceUpdate = false) {
75
		$db = MySqlDatabase::getInstance();
76
		$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));
77
		$galaxyPlanets = [];
78
		while ($db->nextRecord()) {
79
			$sectorID = $db->getInt('sector_id');
80
			$planet = self::getPlanet($gameID, $sectorID, $forceUpdate, $db);
81
			if ($planet->exists()) {
82
				$galaxyPlanets[$sectorID] = $planet;
83
			}
84
		}
85
		return $galaxyPlanets;
86
	}
87
88
	public static function getPlanet($gameID, $sectorID, $forceUpdate = false, $db = null) {
89
		if ($forceUpdate || !isset(self::$CACHE_PLANETS[$gameID][$sectorID])) {
90
			self::$CACHE_PLANETS[$gameID][$sectorID] = new SmrPlanet($gameID, $sectorID, $db);
91
		}
92
		return self::$CACHE_PLANETS[$gameID][$sectorID];
93
	}
94
95
	public static function createPlanet($gameID, $sectorID, $type = 1) {
96
		if (!self::getPlanet($gameID, $sectorID)->exists()) {
97
			$minTime = SmrGame::getGame($gameID)->getStartTime();
98
			$inhabitableTime = $minTime + pow(rand(45, 85), 3);
99
100
			// insert planet into db
101
			$db = MySqlDatabase::getInstance();
102
			$db->query('INSERT INTO planet (game_id, sector_id, inhabitable_time, planet_type_id)
103
				VALUES (' . $db->escapeNumber($gameID) . ', ' . $db->escapeNumber($sectorID) . ', ' . $db->escapeNumber($inhabitableTime) . ', ' . $db->escapeNumber($type) . ')');
104
		}
105
		return self::getPlanet($gameID, $sectorID, true);
106
	}
107
108
	public static function removePlanet($gameID, $sectorID) {
109
		$db = MySqlDatabase::getInstance();
110
		$SQL = 'game_id = ' . $db->escapeNumber($gameID) . ' AND sector_id = ' . $db->escapeNumber($sectorID);
111
		$db->query('DELETE FROM planet WHERE ' . $SQL);
112
		$db->query('DELETE FROM planet_has_weapon WHERE ' . $SQL);
113
		$db->query('DELETE FROM planet_has_cargo WHERE ' . $SQL);
114
		$db->query('DELETE FROM planet_has_building WHERE ' . $SQL);
115
		$db->query('DELETE FROM planet_is_building WHERE ' . $SQL);
116
		//kick everyone from planet
117
		$db->query('UPDATE player SET land_on_planet = \'FALSE\' WHERE ' . $SQL);
118
119
		self::$CACHE_PLANETS[$gameID][$sectorID] = null;
120
		unset(self::$CACHE_PLANETS[$gameID][$sectorID]);
121
	}
122
123
	protected function __construct($gameID, $sectorID, $db = null) {
124
		$this->db = MySqlDatabase::getInstance();
125
		$this->SQL = 'game_id = ' . $this->db->escapeNumber($gameID) . ' AND sector_id = ' . $this->db->escapeNumber($sectorID);
126
127
		if (isset($db)) {
128
			$planetExists = $db->hasField('planet_type_id');
129
		} else {
130
			$db = $this->db;
131
			$db->query('SELECT * FROM planet WHERE ' . $this->SQL);
132
			$planetExists = $db->nextRecord();
133
		}
134
135
		if ($planetExists) {
136
			$this->gameID = (int)$gameID;
137
			$this->sectorID = (int)$sectorID;
138
			$this->planetName = stripslashes($db->getField('planet_name'));
139
			$this->ownerID = $db->getInt('owner_id');
140
			$this->password = $db->getField('password');
141
			$this->shields = $db->getInt('shields');
142
			$this->armour = $db->getInt('armour');
143
			$this->drones = $db->getInt('drones');
144
			$this->credits = $db->getInt('credits');
145
			$this->bonds = $db->getInt('bonds');
146
			$this->maturity = $db->getInt('maturity');
147
			$this->inhabitableTime = $db->getInt('inhabitable_time');
148
			$this->typeID = $db->getInt('planet_type_id');
149
150
			$this->typeInfo = SmrPlanetType::getTypeInfo($this->getTypeID());
151
			$this->checkBondMaturity();
152
		}
153
	}
154
155
	public function getInterestRate() {
156
		$level = $this->getLevel();
157
		if ($level < 9) {
158
			return .0404;
159
		} elseif ($level < 19) {
160
			return .0609;
161
		} elseif ($level < 29) {
162
			return .1236;
163
		} elseif ($level < 39) {
164
			return .050625;
165
		} elseif ($level < 49) {
166
			return .0404;
167
		} elseif ($level < 59) {
168
			return .030225;
169
		} elseif ($level < 69) {
170
			return .0201;
171
		} else {
172
			return .018081;
173
		}
174
	}
175
176
	public function checkBondMaturity($partial = false) {
177
		if ($this->getMaturity() > 0 && ($partial === true || $this->getMaturity() < SmrSession::getTime())) {
178
			// calc the interest for the time
179
			$interest = $this->getBonds() * $this->getInterestRate();
180
181
			if ($partial === true && $this->getMaturity() > SmrSession::getTime()) {
182
				// Adjust interest based upon how much of the bond duration has passed.
183
				$interest -= ($interest / $this->getBondTime()) * ($this->getMaturity() - SmrSession::getTime());
184
			}
185
186
			// transfer money to free avail cash
187
			$this->increaseCredits($this->getBonds() + IFloor($interest));
188
189
			// reset bonds
190
			$this->setBonds(0);
191
192
			// reset maturity
193
			$this->setMaturity(0);
194
		}
195
	}
196
197
	public function getBondTime() : int {
198
		return IRound(BOND_TIME / $this->getGame()->getGameSpeed());
199
	}
200
201
	public function bond() {
202
		$this->checkBondMaturity(true);
203
204
		// add it to bond
205
		$this->increaseBonds($this->getCredits());
206
207
		// set free cash to 0
208
		$this->setCredits(0);
209
210
		// initialize time
211
		$this->setMaturity(SmrSession::getTime() + $this->getBondTime());
212
	}
213
214
	public function getGameID() {
215
		return $this->gameID;
216
	}
217
218
	public function getGame() {
219
		return SmrGame::getGame($this->gameID);
220
	}
221
222
	public function getSectorID() {
223
		return $this->sectorID;
224
	}
225
226
	public function getGalaxy() {
227
		return SmrGalaxy::getGalaxyContaining($this->getGameID(), $this->getSectorID());
228
	}
229
230
	public function getOwnerID() {
231
		return $this->ownerID;
232
	}
233
234
	public function hasOwner() {
235
		return $this->ownerID != 0;
236
	}
237
238
	public function removeOwner() {
239
		$this->setOwnerID(0);
240
	}
241
242
	public function setOwnerID($claimerID) {
243
		if ($this->ownerID == $claimerID) {
244
			return;
245
		}
246
		$this->ownerID = $claimerID;
247
		$this->hasChanged = true;
248
	}
249
250
	public function getOwner() {
251
		return SmrPlayer::getPlayer($this->getOwnerID(), $this->getGameID());
252
	}
253
254
	public function getPassword() {
255
		return $this->password;
256
	}
257
258
	public function setPassword($password) {
259
		if ($this->password == $password) {
260
			return;
261
		}
262
		$this->password = $password;
263
		$this->hasChanged = true;
264
	}
265
266
	public function removePassword() {
267
		$this->setPassword('');
268
	}
269
270
	public function getCredits() {
271
		return $this->credits;
272
	}
273
274
	public function setCredits(int $num) : void {
275
		if ($this->credits == $num) {
276
			return;
277
		}
278
		if ($num < 0) {
279
			throw new Exception('You cannot set negative credits.');
280
		}
281
		if ($num > MAX_MONEY) {
282
			throw new Exception('You cannot set more than the max credits.');
283
		}
284
		$this->credits = $num;
285
		$this->hasChangedFinancial = true;
286
	}
287
288
	/**
289
	 * Increases planet credits up to the maximum allowed credits.
290
	 * Returns the amount that was actually added to handle overflow.
291
	 */
292
	public function increaseCredits(int $num) : int {
293
		if ($num == 0) {
294
			return 0;
295
		}
296
		$newTotal = min($this->credits + $num, MAX_MONEY);
297
		$actualAdded = $newTotal - $this->credits;
298
		$this->setCredits($newTotal);
299
		return $actualAdded;
300
	}
301
302
	public function decreaseCredits(int $num) : void {
303
		if ($num == 0) {
304
			return;
305
		}
306
		$newTotal = $this->credits - $num;
307
		$this->setCredits($newTotal);
308
	}
309
310
	public function getMaturity() {
311
		return $this->maturity;
312
	}
313
314
	public function setMaturity($num) {
315
		if ($this->maturity == $num) {
316
			return;
317
		}
318
		if ($num < 0) {
319
			throw new Exception('You cannot set negative maturity.');
320
		}
321
		$this->maturity = $num;
322
		$this->hasChangedFinancial = true;
323
	}
324
325
	public function getBonds() {
326
		return $this->bonds;
327
	}
328
329
	public function setBonds($num) {
330
		if ($this->bonds == $num) {
331
			return;
332
		}
333
		if ($num < 0) {
334
			throw new Exception('You cannot set negative bonds.');
335
		}
336
		$this->bonds = $num;
337
		$this->hasChangedFinancial = true;
338
	}
339
340
	public function increaseBonds($num) {
341
		if ($num == 0) {
342
			return;
343
		}
344
		$this->setBonds($this->getBonds() + $num);
345
	}
346
347
	public function decreaseBonds($num) {
348
		if ($num == 0) {
349
			return;
350
		}
351
		$this->setBonds($this->getBonds() - $num);
352
	}
353
354
	public function checkForExcessDefense() {
355
		if ($this->getShields() > $this->getMaxShields()) {
356
			$this->setShields($this->getMaxShields());
357
		}
358
		if ($this->getCDs() > $this->getMaxCDs()) {
359
			$this->setCDs($this->getMaxCDs());
360
		}
361
		if ($this->getArmour() > $this->getMaxArmour()) {
362
				$this->setArmour($this->getMaxArmour());
363
		}
364
		// Remove a random (0-indexed) mounted weapon, if over max mount slots
365
		while ($this->getMountedWeapons() && max(array_keys($this->getMountedWeapons())) >= $this->getMaxMountedWeapons()) {
366
			$removeID = array_rand($this->getMountedWeapons());
367
			$this->removeMountedWeapon($removeID);
368
			foreach ($this->getMountedWeapons() as $orderID => $weapon) {
369
				if ($orderID > $removeID) {
370
					$this->moveMountedWeaponUp($orderID);
371
				}
372
			}
373
		}
374
	}
375
376
	public function getShields($delayed = false) {
377
		return $this->shields + ($delayed ? $this->delayedShieldsDelta : 0);
378
	}
379
380
	public function hasShields($delayed = false) {
381
		return $this->getShields($delayed) > 0;
382
	}
383
384
	public function setShields($shields) {
385
		$shields = max(0, min($shields, $this->getMaxShields()));
386
		if ($this->shields == $shields) {
387
			return;
388
		}
389
		$this->shields = $shields;
390
		$this->hasChanged = true;
391
	}
392
393
	public function decreaseShields($number, $delayed = false) {
394
		if ($number == 0) {
395
			return;
396
		}
397
		if ($delayed === false) {
398
			$this->setShields($this->getShields() - $number);
399
		} else {
400
			$this->delayedShieldsDelta -= $number;
401
		}
402
	}
403
404
	public function increaseShields($number, $delayed = false) {
405
		if ($number == 0) {
406
			return;
407
		}
408
		if ($delayed === false) {
409
			$this->setShields($this->getShields() + $number);
410
		} else {
411
			$this->delayedShieldsDelta += $number;
412
		}
413
	}
414
415
	public function getMaxShields() {
416
		return $this->getBuilding(PLANET_GENERATOR) * PLANET_GENERATOR_SHIELDS;
417
	}
418
419
	public function getArmour($delayed = false) {
420
		return $this->armour + ($delayed ? $this->delayedArmourDelta : 0);
421
	}
422
423
	public function hasArmour($delayed = false) {
424
		return $this->getArmour($delayed) > 0;
425
	}
426
427
	public function setArmour($armour) {
428
		$armour = max(0, min($armour, $this->getMaxArmour()));
429
		if ($this->armour == $armour) {
430
			return;
431
		}
432
		$this->armour = $armour;
433
		$this->hasChanged = true;
434
	}
435
436
	public function decreaseArmour($number, $delayed = false) {
437
		if ($number == 0) {
438
			return;
439
		}
440
		if ($delayed === false) {
441
			$this->setArmour($this->getArmour() - $number);
442
		} else {
443
			$this->delayedArmourDelta -= $number;
444
		}
445
	}
446
447
	public function increaseArmour($number, $delayed = false) {
448
		if ($number == 0) {
449
			return;
450
		}
451
		if ($delayed === false) {
452
			$this->setArmour($this->getArmour() + $number);
453
		} else {
454
			$this->delayedArmourDelta += $number;
455
		}
456
	}
457
458
	public function getMaxArmour() {
459
		return $this->getBuilding(PLANET_BUNKER) * PLANET_BUNKER_ARMOUR;
460
	}
461
462
	public function getCDs($delayed = false) {
463
		return $this->drones + ($delayed ? $this->delayedCDsDelta : 0);
464
	}
465
466
	public function hasCDs($delayed = false) {
467
		return $this->getCDs($delayed) > 0;
468
	}
469
470
	public function setCDs($combatDrones) {
471
		$combatDrones = max(0, min($combatDrones, $this->getMaxCDs()));
472
		if ($this->drones == $combatDrones) {
473
			return;
474
		}
475
		$this->drones = $combatDrones;
476
		$this->hasChanged = true;
477
	}
478
479
	public function decreaseCDs($number, $delayed = false) {
480
		if ($number == 0) {
481
			return;
482
		}
483
		if ($delayed === false) {
484
			$this->setCDs($this->getCDs() - $number);
485
		} else {
486
			$this->delayedCDsDelta -= $number;
487
		}
488
	}
489
490
	public function increaseCDs($number, $delayed = false) {
491
		if ($number == 0) {
492
			return;
493
		}
494
		if ($delayed === false) {
495
			$this->setCDs($this->getCDs() + $number);
496
		} else {
497
			$this->delayedCDsDelta += $number;
498
		}
499
	}
500
501
	public function getMaxCDs() {
502
		return $this->getBuilding(PLANET_HANGAR) * PLANET_HANGAR_DRONES;
503
	}
504
505
	public function getMaxMountedWeapons() {
506
		return $this->getBuilding(PLANET_WEAPON_MOUNT);
507
	}
508
509
	public function getMountedWeapons() {
510
		if (!isset($this->mountedWeapons)) {
511
			$this->mountedWeapons = [];
512
			if ($this->hasBuilding(PLANET_WEAPON_MOUNT)) {
513
				$this->db->query('SELECT * FROM planet_has_weapon JOIN weapon_type USING (weapon_type_id) WHERE ' . $this->SQL);
514
				while ($this->db->nextRecord()) {
515
					$weaponTypeID = $this->db->getInt('weapon_type_id');
516
					$orderID = $this->db->getInt('order_id');
517
					$weapon = SmrWeapon::getWeapon($weaponTypeID, $this->db);
518
					$weapon->setBonusAccuracy($this->db->getBoolean('bonus_accuracy'));
519
					$weapon->setBonusDamage($this->db->getBoolean('bonus_damage'));
520
					$this->mountedWeapons[$orderID] = $weapon;
521
				}
522
			}
523
		}
524
		return $this->mountedWeapons;
525
	}
526
527
	public function hasMountedWeapon($orderID) {
528
		$this->getMountedWeapons(); // Make sure array is initialized
529
		return isset($this->mountedWeapons[$orderID]);
530
	}
531
532
	public function addMountedWeapon(SmrWeapon $weapon, $orderID) {
533
		$this->getMountedWeapons(); // Make sure array is initialized
534
		$this->mountedWeapons[$orderID] = $weapon;
535
		$this->hasChangedWeapons[$orderID] = true;
536
	}
537
538
	public function removeMountedWeapon($orderID) {
539
		$this->getMountedWeapons(); // Make sure array is initialized
540
		unset($this->mountedWeapons[$orderID]);
541
		$this->hasChangedWeapons[$orderID] = true;
542
	}
543
544
	private function swapMountedWeapons($orderID1, $orderID2) {
545
		$this->getMountedWeapons(); // Make sure array is initialized
546
		if (isset($this->mountedWeapons[$orderID1])) {
547
			$saveWeapon = $this->mountedWeapons[$orderID1];
548
		}
549
		if (isset($this->mountedWeapons[$orderID2])) {
550
			$this->mountedWeapons[$orderID1] = $this->mountedWeapons[$orderID2];
551
		} else {
552
			unset($this->mountedWeapons[$orderID1]);
553
		}
554
		if (isset($saveWeapon)) {
555
			$this->mountedWeapons[$orderID2] = $saveWeapon;
556
		} else {
557
			unset($this->mountedWeapons[$orderID2]);
558
		}
559
		$this->hasChangedWeapons[$orderID1] = true;
560
		$this->hasChangedWeapons[$orderID2] = true;
561
	}
562
563
	public function moveMountedWeaponUp($orderID) {
564
		if ($orderID == 0) {
565
			throw new Exception('Cannot move this weapon up!');
566
		}
567
		$this->swapMountedWeapons($orderID - 1, $orderID);
568
	}
569
570
	public function moveMountedWeaponDown($orderID) {
571
		if ($orderID == $this->getMaxMountedWeapons() - 1) {
572
			throw new Exception('Cannot move this weapon down!');
573
		}
574
		$this->swapMountedWeapons($orderID + 1, $orderID);
575
	}
576
577
578
	public function isDestroyed($delayed = false) {
579
		return !$this->hasCDs($delayed) && !$this->hasShields($delayed) && !$this->hasArmour($delayed);
580
	}
581
582
	public function exists() {
583
		return $this->getGameID() != null && $this->getSectorID() !== null;
584
	}
585
586
	public function getStockpile($goodID = false) {
587
		if (!isset($this->stockpile)) {
588
			// initialize cargo array
589
			$this->stockpile = array();
590
			// get supplies from db
591
			$this->db->query('SELECT good_id, amount FROM planet_has_cargo WHERE ' . $this->SQL);
592
			// adding cargo and amount to array
593
			while ($this->db->nextRecord()) {
594
				$this->stockpile[$this->db->getInt('good_id')] = $this->db->getInt('amount');
595
			}
596
		}
597
		if ($goodID === false) {
598
			return $this->stockpile;
599
		}
600
		if (isset($this->stockpile[$goodID])) {
601
			return $this->stockpile[$goodID];
602
		}
603
		return 0;
604
	}
605
606
	public function hasStockpile($goodID = false) {
607
		if ($goodID === false) {
608
			$stockpile = $this->getStockpile($goodID);
609
			return count($stockpile) > 0 && max($stockpile) > 0;
0 ignored issues
show
Bug introduced by
It seems like $stockpile can also be of type integer; however, parameter $value of count() does only seem to accept Countable|array, 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

609
			return count(/** @scrutinizer ignore-type */ $stockpile) > 0 && max($stockpile) > 0;
Loading history...
610
		} else {
611
			return $this->getStockpile($goodID) > 0;
612
		}
613
	}
614
615
	public function setStockpile($goodID, $amount) {
616
		if ($amount < 0) {
617
			throw new Exception('Trying to set negative stockpile.');
618
		}
619
		if ($this->getStockpile($goodID) == $amount) {
620
			return;
621
		}
622
		$this->stockpile[$goodID] = $amount;
623
		$this->hasChangedStockpile = true;
624
	}
625
626
	public function decreaseStockpile($goodID, $amount) {
627
		if ($amount < 0) {
628
			throw new Exception('Trying to decrease negative stockpile.');
629
		}
630
		$this->setStockpile($goodID, $this->getStockpile($goodID) - $amount);
631
	}
632
633
	public function increaseStockpile($goodID, $amount) {
634
		if ($amount < 0) {
635
			throw new Exception('Trying to increase negative stockpile.');
636
		}
637
		$this->setStockpile($goodID, $this->getStockpile($goodID) + $amount);
638
	}
639
640
	public function getBuildings() {
641
		if (!isset($this->buildings)) {
642
			$this->buildings = array();
643
644
			// get buildingss from db
645
			$this->db->query('SELECT construction_id, amount FROM planet_has_building WHERE ' . $this->SQL);
646
			// adding building and amount to array
647
			while ($this->db->nextRecord()) {
648
				$this->buildings[$this->db->getInt('construction_id')] = $this->db->getInt('amount');
649
			}
650
651
			// Update building counts if construction has finished
652
			$this->getCurrentlyBuilding();
653
		}
654
		return $this->buildings;
655
	}
656
657
	public function getBuilding($buildingTypeID) {
658
		$buildings = $this->getBuildings();
659
		if (isset($buildings[$buildingTypeID])) {
660
			return $buildings[$buildingTypeID];
661
		}
662
		return 0;
663
	}
664
665
	public function hasBuilding($buildingTypeID) {
666
		return $this->getBuilding($buildingTypeID) > 0;
667
	}
668
669
	public function setBuilding($buildingTypeID, $number) {
670
		if ($this->getBuilding($buildingTypeID) == $number) {
671
			return;
672
		}
673
674
		$this->buildings[$buildingTypeID] = $number;
675
		$this->hasChangedBuildings[$buildingTypeID] = true;
676
	}
677
678
	public function increaseBuilding($buildingTypeID, $number) {
679
		$this->setBuilding($buildingTypeID, $this->getBuilding($buildingTypeID) + $number);
680
	}
681
682
	public function destroyBuilding($buildingTypeID, $number) {
683
		if (!$this->hasBuilding($buildingTypeID)) {
684
			throw new Exception('Trying to destroy a nonexistent building');
685
		}
686
		$this->setBuilding($buildingTypeID, $this->getBuilding($buildingTypeID) - $number);
687
	}
688
689
	public function getCurrentlyBuilding() {
690
		if (!isset($this->currentlyBuilding)) {
691
			$this->currentlyBuilding = array();
692
693
			$this->db->query('SELECT * FROM planet_is_building WHERE ' . $this->SQL);
694
			while ($this->db->nextRecord()) {
695
				$this->currentlyBuilding[$this->db->getInt('building_slot_id')] = array(
696
					'BuildingSlotID' => $this->db->getInt('building_slot_id'),
697
					'ConstructionID' => $this->db->getInt('construction_id'),
698
					'ConstructorID' => $this->db->getInt('constructor_id'),
699
					'Finishes' => $this->db->getInt('time_complete'),
700
					'TimeRemaining' => $this->db->getInt('time_complete') - SmrSession::getTime(),
701
				);
702
			}
703
704
			// Check if construction has completed
705
			foreach ($this->currentlyBuilding as $id => $building) {
706
				if ($building['TimeRemaining'] <= 0) {
707
					unset($this->currentlyBuilding[$id]);
708
					$expGain = $this->getConstructionExp($building['ConstructionID']);
709
					$player = SmrPlayer::getPlayer($building['ConstructorID'], $this->getGameID());
710
					$player->increaseHOF(1, array('Planet', 'Buildings', 'Built'), HOF_ALLIANCE);
711
					$player->increaseExperience($expGain);
712
					$player->increaseHOF($expGain, array('Planet', 'Buildings', 'Experience'), HOF_ALLIANCE);
713
					$this->hasStoppedBuilding[] = $building['BuildingSlotID'];
714
					$this->increaseBuilding($building['ConstructionID'], 1);
715
716
					// WARNING: The above modifications to the player/planet are dangerous because
717
					// they may not be part of the current sector lock. But since they might not be,
718
					// we may as well just update now to avoid either a) needing to remember to call
719
					// this explicitly in all appropriate engine files or b) inconsistent exp display
720
					// if this is called during the template display only and therefore unsaved.
721
					$player->update();
722
					$this->update();
723
				}
724
			}
725
		}
726
		return $this->currentlyBuilding;
727
	}
728
729
	public function getMaxBuildings($buildingTypeID = false) {
730
		if ($buildingTypeID === false) {
731
			$structs = $this->typeInfo::STRUCTURES;
732
			return array_combine(array_keys($structs),
733
			                     array_column($structs, 'max_amount'));
734
		}
735
		return $this->getStructureTypes($buildingTypeID)->maxAmount();
736
	}
737
738
	public function getTypeID() {
739
		return $this->typeID;
740
	}
741
742
	public function setTypeID($num) {
743
		if (isset($this->typeID) && $this->typeID == $num) {
744
			return;
745
		}
746
		$this->typeID = $num;
747
		$this->db->query('UPDATE planet SET planet_type_id = ' . $this->db->escapeNumber($num) . ' WHERE ' . $this->SQL);
748
		$this->typeInfo = SmrPlanetType::getTypeInfo($this->getTypeID());
749
750
		//trim buildings first
751
		foreach ($this->getBuildings() as $id => $amt) {
752
			if ($this->getMaxBuildings($id) < $amt) {
753
				$this->destroyBuilding($id, $amt - $this->getMaxBuildings($id));
754
			}
755
		}
756
757
		//trim excess defenses
758
		$this->checkForExcessDefense();
759
760
		$this->hasChanged = true;
761
		$this->update();
762
	}
763
764
	public function getTypeImage() {
765
		return $this->typeInfo->imageLink();
766
	}
767
768
	public function getTypeName() {
769
		return $this->typeInfo->name();
770
	}
771
772
	public function getTypeDescription() {
773
		return $this->typeInfo->description();
774
	}
775
776
	public function getMaxAttackers() {
777
		return $this->typeInfo->maxAttackers();
778
	}
779
780
	public function getMaxLanded() {
781
		return $this->typeInfo->maxLanded();
782
	}
783
784
	public function getStructureTypes($structureID = false) {
785
		return $this->typeInfo->structureTypes($structureID);
786
	}
787
788
	public function hasStructureType($structureID) {
789
		return isset($this->getStructureTypes()[$structureID]);
790
	}
791
792
	/**
793
	 * Specifies which menu options the planet has.
794
	 */
795
	public function hasMenuOption($option) {
796
		// We do not set options that are unavailable
797
		return in_array($option, $this->typeInfo->menuOptions());
798
	}
799
800
	public function doDelayedUpdates() {
801
		$this->setShields($this->getShields(true));
802
		$this->delayedShieldsDelta = 0;
803
		$this->setCDs($this->getCDs(true));
804
		$this->delayedCDsDelta = 0;
805
		$this->setArmour($this->getArmour(true));
806
		$this->delayedArmourDelta = 0;
807
	}
808
809
	public function update() {
810
		if (!$this->exists()) {
811
			return;
812
		}
813
		$this->doDelayedUpdates();
814
		if ($this->hasChanged) {
815
			$this->db->query('UPDATE planet SET
816
									owner_id = ' . $this->db->escapeNumber($this->ownerID) . ',
817
									password = '.$this->db->escapeString($this->password) . ',
818
									planet_name = ' . $this->db->escapeString($this->planetName) . ',
819
									shields = ' . $this->db->escapeNumber($this->shields) . ',
820
									armour = ' . $this->db->escapeNumber($this->armour) . ',
821
									drones = ' . $this->db->escapeNumber($this->drones) . '
822
								WHERE ' . $this->SQL);
823
			$this->hasChanged = false;
824
		}
825
826
		// Separate update for financial since these can be modified by looking
827
		// at the planet list (i.e. you might not have sector lock and could
828
		// cause a race condition with events happening in the planet sector).
829
		if ($this->hasChangedFinancial) {
830
			$this->db->query('UPDATE planet SET
831
									credits = ' . $this->db->escapeNumber($this->credits) . ',
832
									bonds = ' . $this->db->escapeNumber($this->bonds) . ',
833
									maturity = ' . $this->db->escapeNumber($this->maturity) . '
834
								WHERE ' . $this->SQL);
835
			$this->hasChangedFinancial = false;
836
		}
837
838
		if ($this->hasChangedStockpile) {
839
			// write stockpile info
840
			foreach ($this->getStockpile() as $id => $amount) {
841
				if ($amount != 0) {
842
					$this->db->query('REPLACE INTO planet_has_cargo (game_id, sector_id, good_id, amount) ' .
843
										 'VALUES(' . $this->db->escapeNumber($this->getGameID()) . ', ' . $this->db->escapeNumber($this->getSectorID()) . ', ' . $this->db->escapeNumber($id) . ', ' . $this->db->escapeNumber($amount) . ')');
844
				} else {
845
					$this->db->query('DELETE FROM planet_has_cargo WHERE ' . $this->SQL . '
846
										AND good_id = ' . $this->db->escapeNumber($id));
847
				}
848
			}
849
		}
850
851
		if (count($this->hasChangedWeapons) > 0) {
852
			foreach (array_keys($this->hasChangedWeapons) as $orderID) {
853
				if (isset($this->mountedWeapons[$orderID])) {
854
					$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()) . ')');
855
				} else {
856
					$this->db->query('DELETE FROM planet_has_weapon WHERE ' . $this->SQL . ' AND order_id=' . $this->db->escapeNumber($orderID));
857
				}
858
			}
859
			$this->hasChangedWeapons = [];
860
		}
861
862
		if (count($this->hasStoppedBuilding) > 0) {
863
			$this->db->query('DELETE FROM planet_is_building WHERE ' . $this->SQL . '
864
								AND building_slot_id IN (' . $this->db->escapeArray($this->hasStoppedBuilding) . ') LIMIT ' . count($this->hasStoppedBuilding));
865
			$this->hasStoppedBuilding = array();
866
		}
867
		// write building info
868
		foreach ($this->hasChangedBuildings as $id => $hasChanged) {
869
			if ($hasChanged === true) {
870
				if ($this->hasBuilding($id)) {
871
					$this->db->query('REPLACE INTO planet_has_building (game_id, sector_id, construction_id, amount) ' .
872
										'VALUES(' . $this->db->escapeNumber($this->gameID) . ', ' . $this->db->escapeNumber($this->sectorID) . ', ' . $this->db->escapeNumber($id) . ', ' . $this->db->escapeNumber($this->getBuilding($id)) . ')');
873
				} else {
874
					$this->db->query('DELETE FROM planet_has_building WHERE ' . $this->SQL . '
875
										AND construction_id = ' . $this->db->escapeNumber($id));
876
				}
877
				$this->hasChangedBuildings[$id] = false;
878
			}
879
		}
880
	}
881
882
	public function getLevel() {
883
		return array_sum($this->getBuildings()) / 3;
884
	}
885
886
	public function getMaxLevel() {
887
		return array_sum($this->getMaxBuildings()) / 3;
888
	}
889
890
	public function accuracy() {
891
		if ($this->hasWeapons()) {
892
			$weapons = $this->getWeapons();
893
			return $weapons[0]->getModifiedPlanetAccuracy($this);
894
		}
895
		return 0;
896
	}
897
898
	/**
899
	 * Returns the accuracy bonus for mounted weaons (as a percent)
900
	 */
901
	public function getAccuracyBonus() {
902
		return 5 * $this->getBuilding(PLANET_RADAR);
903
	}
904
905
	public function getRemainingStockpile($id) {
906
		return self::MAX_STOCKPILE - $this->getStockpile($id);
907
	}
908
909
	/**
910
	 * Returns true if there is a building in progress
911
	 */
912
	public function hasCurrentlyBuilding() {
913
		return count($this->getCurrentlyBuilding()) > 0;
914
	}
915
916
	public function canBuild(AbstractSmrPlayer $constructor, $constructionID) {
917
		if ($this->hasCurrentlyBuilding()) {
918
			return 'There is already a building in progress!';
919
		}
920
		if ($this->getBuilding($constructionID) >= $this->getMaxBuildings($constructionID)) {
921
			return 'This planet has reached the maximum buildings of that type.';
922
		}
923
		$cost = $this->getStructureTypes($constructionID)->creditCost();
924
		if ($constructor->getCredits() < $cost) {
925
			return 'You do not have enough credits.';
926
		}
927
		foreach ($this->getStructureTypes($constructionID)->hardwareCost() as $hardwareID) {
928
			if (!$constructor->getShip()->getHardware($hardwareID)) {
929
				return 'You do not have the hardware needed for this type of building!';
930
			}
931
		}
932
		// take the goods that are needed
933
		foreach ($this->getStructureTypes($constructionID)->goods() as $goodID => $amount) {
934
			if ($this->getStockpile($goodID) < $amount) {
935
				return 'There are not enough goods available.';
936
			}
937
		}
938
		return true;
939
	}
940
941
	// Modifier for planet building based on the number of buildings.
942
	// The average value of this modifier should roughly be 1.
943
	private function getCompletionModifier($constructionID) {
944
		$currentBuildings = $this->getBuilding($constructionID);
945
		$maxBuildings = $this->getMaxBuildings($constructionID);
946
		return 0.01 + 2.97 * pow($currentBuildings / $maxBuildings, 2);
947
	}
948
949
	// Amount of exp gained to build the next building of this type
950
	private function getConstructionExp($constructionID) {
951
		$expGain = $this->getStructureTypes($constructionID)->expGain();
952
		return $expGain;
953
	}
954
955
	// Amount of time (in seconds) to build the next building of this type
956
	public function getConstructionTime($constructionID) : int {
957
		$baseTime = $this->getStructureTypes($constructionID)->baseTime();
958
		$constructionTime = ICeil($baseTime * $this->getCompletionModifier($constructionID) / $this->getGame()->getGameSpeed());
959
		return $constructionTime;
960
	}
961
962
	public function startBuilding(AbstractSmrPlayer $constructor, $constructionID) {
963
		if (($message = $this->canBuild($constructor, $constructionID)) !== true) {
964
			throw new Exception('Unable to start building: ' . $message);
965
		}
966
		$constructor->decreaseCredits($this->getStructureTypes($constructionID)->creditCost());
967
		// take the goods that are needed
968
		foreach ($this->getStructureTypes($constructionID)->goods() as $goodID => $amount) {
969
			$this->decreaseStockpile($goodID, $amount);
970
		}
971
		foreach ($this->getStructureTypes($constructionID)->hardwareCost() as $hardwareID) {
972
			$constructor->getShip()->setHardware($hardwareID, 0);
973
		}
974
975
		// gets the time for the buildings
976
		$timeComplete = SmrSession::getTime() + $this->getConstructionTime($constructionID);
977
		$this->db->query('INSERT INTO planet_is_building (game_id, sector_id, construction_id, constructor_id, time_complete) ' .
978
						'VALUES (' . $this->db->escapeNumber($this->getGameID()) . ', ' . $this->db->escapeNumber($this->getSectorID()) . ', ' . $this->db->escapeNumber($constructionID) . ', ' . $this->db->escapeNumber($constructor->getAccountID()) . ',' . $this->db->escapeNumber($timeComplete) . ')');
979
980
		$this->currentlyBuilding[$this->db->getInsertID()] = array(
981
			'BuildingSlotID' => $this->db->getInsertID(),
982
			'ConstructionID' => $constructionID,
983
			'ConstructorID' => $constructor->getAccountID(),
984
			'Finishes' => $timeComplete,
985
			'TimeRemaining' => $timeComplete - SmrSession::getTime()
986
		);
987
	}
988
989
	public function stopBuilding($constructionID) {
990
		$matchingBuilding = false;
991
		$latestFinish = 0;
992
		foreach ($this->getCurrentlyBuilding() as $key => $building) {
993
			if ($building['ConstructionID'] == $constructionID && $building['Finishes'] > $latestFinish) {
994
				$latestFinish = $building['Finishes'];
995
				$matchingBuilding = $building;
996
			}
997
		}
998
		if ($matchingBuilding) {
999
			$this->hasStoppedBuilding[] = $matchingBuilding['BuildingSlotID'];
1000
			unset($this->currentlyBuilding[$matchingBuilding['BuildingSlotID']]);
1001
			return true;
1002
		}
1003
		return false;
1004
	}
1005
1006
	public function setName($name) {
1007
		if ($this->planetName == $name) {
1008
			return;
1009
		}
1010
		$this->planetName = $name;
1011
		$this->hasChanged = true;
1012
	}
1013
1014
	/**
1015
	 * Returns the name of the planet, suitably escaped for HTML display.
1016
	 */
1017
	public function getDisplayName() {
1018
		return htmlentities($this->planetName);
1019
	}
1020
1021
	/**
1022
	 * Returns the name of the planet, intended for combat messages.
1023
	 */
1024
	public function getCombatName() {
1025
		return '<span style="color:yellow;font-variant:small-caps">' . $this->getDisplayName() . ' (#' . $this->getSectorID() . ')</span>';
1026
	}
1027
1028
	public function isInhabitable() {
1029
		return $this->inhabitableTime <= SmrSession::getTime();
1030
	}
1031
1032
	public function getInhabitableTime() {
1033
		return $this->inhabitableTime;
1034
	}
1035
1036
	public function getExamineHREF() {
1037
		return SmrSession::getNewHREF(create_container('skeleton.php', 'planet_examine.php'));
1038
	}
1039
1040
	public function getLandHREF() {
1041
		return SmrSession::getNewHREF(create_container('planet_land_processing.php'));
1042
	}
1043
1044
	public function getAttackHREF() {
1045
		return SmrSession::getNewHREF(create_container('planet_attack_processing.php'));
1046
	}
1047
1048
	public function getBuildHREF($structureID) {
1049
		$container = create_container('planet_construction_processing.php');
1050
		$container['construction_id'] = $structureID;
1051
		$container['action'] = 'Build';
1052
		return SmrSession::getNewHREF($container);
1053
	}
1054
1055
	public function getCancelHREF($structureID) {
1056
		$container = create_container('planet_construction_processing.php');
1057
		$container['construction_id'] = $structureID;
1058
		$container['action'] = 'Cancel';
1059
		return SmrSession::getNewHREF($container);
1060
	}
1061
1062
	public function getFinancesHREF() {
1063
		return SmrSession::getNewHREF(create_container('planet_financial_processing.php'));
1064
	}
1065
1066
	public function getBondConfirmationHREF() {
1067
		return SmrSession::getNewHREF(create_container('skeleton.php', 'planet_bond_confirmation.php'));
1068
	}
1069
1070
	public function attackedBy(AbstractSmrPlayer $trigger, array $attackers) {
1071
		$trigger->increaseHOF(1, array('Combat', 'Planet', 'Number Of Triggers'), HOF_PUBLIC);
1072
		foreach ($attackers as $attacker) {
1073
			$attacker->increaseHOF(1, array('Combat', 'Planet', 'Number Of Attacks'), HOF_PUBLIC);
1074
			$this->db->query('REPLACE INTO player_attacks_planet (game_id, account_id, sector_id, time, level) VALUES ' .
1075
					'(' . $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()) . ')');
1076
		}
1077
1078
		// Add each unique attack to news unless it was already added recently.
1079
		// Note: Attack uniqueness determined by planet owner.
1080
		$owner = $this->getOwner();
1081
		$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');
1082
		if ($this->db->getNumRows() == 0) {
1083
			if (count($attackers) >= 5) {
1084
				$text = count($attackers) . ' members of ' . $trigger->getAllianceBBLink() . ' have been spotted attacking ' .
1085
					$this->getDisplayName() . ' in sector ' . Globals::getSectorBBLink($this->getSectorID()) . '. The planet is owned by ' . $owner->getBBLink();
1086
				if ($owner->hasAlliance()) {
1087
					$text .= ', a member of ' . $owner->getAllianceBBLink();
1088
				}
1089
				$text .= '.';
1090
				$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()) . ')');
1091
			}
1092
		}
1093
	}
1094
1095
1096
1097
	public function getPlayers() {
1098
		return SmrPlayer::getPlanetPlayers($this->getGameID(), $this->getSectorID());
1099
	}
1100
1101
	public function countPlayers() {
1102
		return count($this->getPlayers());
1103
	}
1104
1105
	public function hasPlayers() {
1106
		return count($this->getPlayers()) > 0;
1107
	}
1108
1109
	public function getOtherTraders(AbstractSmrPlayer $player) {
1110
		$players = SmrPlayer::getPlanetPlayers($this->getGameID(), $this->getSectorID()); //Do not use & because we unset something and only want that in what we return
1111
		unset($players[$player->getAccountID()]);
1112
		return $players;
1113
	}
1114
1115
	public function hasOtherTraders(AbstractSmrPlayer $player) {
1116
		return count($this->getOtherTraders($player)) > 0;
1117
	}
1118
1119
	public function hasEnemyTraders(AbstractSmrPlayer $player) {
1120
		if (!$this->hasOtherTraders($player)) {
1121
			return false;
1122
		}
1123
		$otherPlayers = $this->getOtherTraders($player);
1124
		foreach ($otherPlayers as $otherPlayer) {
1125
			if (!$player->traderNAPAlliance($otherPlayer)) {
1126
				return true;
1127
			}
1128
		}
1129
		return false;
1130
	}
1131
1132
	public function hasFriendlyTraders(AbstractSmrPlayer $player) {
1133
		if (!$this->hasOtherTraders($player)) {
1134
			return false;
1135
		}
1136
		$otherPlayers = $this->getOtherTraders($player);
1137
		foreach ($otherPlayers as $otherPlayer) {
1138
			if ($player->traderNAPAlliance($otherPlayer)) {
1139
				return true;
1140
			}
1141
		}
1142
		return false;
1143
	}
1144
1145
	public function getWeapons() {
1146
		$weapons = $this->getMountedWeapons();
1147
		for ($i = 0; $i < $this->getBuilding(PLANET_TURRET); ++$i) {
1148
			$weapons[] = SmrWeapon::getWeapon(WEAPON_PLANET_TURRET);
1149
		}
1150
		return $weapons;
1151
	}
1152
1153
	public function hasWeapons() {
1154
		return count($this->getWeapons()) > 0;
1155
	}
1156
1157
	public function shootPlayers(array $targetPlayers) {
1158
		$results = array('Planet' => $this, 'TotalDamage' => 0, 'TotalDamagePerTargetPlayer' => array());
1159
		foreach ($targetPlayers as $targetPlayer) {
1160
			$results['TotalDamagePerTargetPlayer'][$targetPlayer->getAccountID()] = 0;
1161
		}
1162
		if ($this->isDestroyed()) {
1163
			$results['DeadBeforeShot'] = true;
1164
			return $results;
1165
		}
1166
		$results['DeadBeforeShot'] = false;
1167
		$weapons = $this->getWeapons();
1168
		foreach ($weapons as $orderID => $weapon) {
1169
			$results['Weapons'][$orderID] = $weapon->shootPlayerAsPlanet($this, array_rand_value($targetPlayers));
1170
			if ($results['Weapons'][$orderID]['Hit']) {
1171
				$results['TotalDamage'] += $results['Weapons'][$orderID]['ActualDamage']['TotalDamage'];
1172
				$results['TotalDamagePerTargetPlayer'][$results['Weapons'][$orderID]['TargetPlayer']->getAccountID()] += $results['Weapons'][$orderID]['ActualDamage']['TotalDamage'];
1173
			}
1174
		}
1175
		if ($this->hasCDs()) {
1176
			$thisCDs = new SmrCombatDrones($this->getGameID(), $this->getCDs(), true);
1177
			$results['Drones'] = $thisCDs->shootPlayerAsPlanet($this, array_rand_value($targetPlayers));
1178
			$results['TotalDamage'] += $results['Drones']['ActualDamage']['TotalDamage'];
1179
			$results['TotalDamagePerTargetPlayer'][$results['Drones']['TargetPlayer']->getAccountID()] += $results['Drones']['ActualDamage']['TotalDamage'];
1180
		}
1181
		return $results;
1182
	}
1183
1184
	/**
1185
	 * Returns an array of structure losses due to damage taken.
1186
	 */
1187
	public function checkForDowngrade($damage) : array {
1188
		$results = [];
1189
		// For every 70 damage there is a 15% chance of destroying a structure.
1190
		// Which structure is destroyed depends on the ratio of buildings and
1191
		// the time it takes to build them.
1192
		$numChances = floor($damage / self::DAMAGE_NEEDED_FOR_DOWNGRADE_CHANCE);
1193
		for ($i = 0; $i < $numChances; $i++) {
1194
			// Stop if the planet has no more buildlings
1195
			if ($this->getLevel() == 0) {
1196
				break;
1197
			}
1198
			//15% chance to destroy something
1199
			if (rand(1, 100) <= self::CHANCE_TO_DOWNGRADE) {
1200
				$chanceFactors = [];
1201
				foreach ($this->getStructureTypes() as $structureID => $structure) {
1202
					$chanceFactors[$structureID] = ($this->getBuilding($structureID) / $this->getMaxBuildings($structureID)) / $structure->baseTime();
1203
				}
1204
				$destroyID = getWeightedRandom($chanceFactors);
1205
				$this->destroyBuilding($destroyID, 1);
1206
				$this->checkForExcessDefense();
1207
				if (isset($results[$destroyID])) {
1208
					$results[$destroyID] += 1;
1209
				} else {
1210
					$results[$destroyID] = 1;
1211
				}
1212
			}
1213
		}
1214
		return $results;
1215
	}
1216
1217
	public function doWeaponDamage(array $damage, $delayed) {
1218
		$alreadyDead = $this->isDestroyed(true);
1219
		$shieldDamage = 0;
1220
		$cdDamage = 0;
1221
		$armourDamage = 0;
1222
		if (!$alreadyDead) {
1223
			if ($damage['Shield'] || !$this->hasShields(true)) {
1224
				$shieldDamage = $this->doShieldDamage(min($damage['MaxDamage'], $damage['Shield']), $delayed);
1225
				$damage['Shield'] -= $shieldDamage;
1226
				$damage['MaxDamage'] -= $shieldDamage;
1227
1228
				if (!$this->hasShields(true) && ($shieldDamage == 0 || $damage['Rollover'])) {
1229
					if ($this->hasCDs(true)) {
1230
						$cdDamage = $this->doCDDamage(min($damage['MaxDamage'], $damage['Armour']), $delayed);
1231
						$damage['Armour'] -= $cdDamage;
1232
						$damage['MaxDamage'] -= $cdDamage;
1233
					}
1234
					if ($this->hasArmour(true) && ($cdDamage == 0 || $damage['Rollover'])) {
1235
						$armourDamage = $this->doArmourDamage(min($damage['MaxDamage'], $damage['Armour']), $delayed);
1236
						$damage['Armour'] -= $armourDamage;
1237
						$damage['MaxDamage'] -= $armourDamage;
1238
					}
1239
				}
1240
1241
			} else { // hit drones behind shields - we should only use this reduced damage branch if we cannot hit shields.
1242
				$cdDamage = $this->doCDDamage(IFloor(min($damage['MaxDamage'], $damage['Armour']) * DRONES_BEHIND_SHIELDS_DAMAGE_PERCENT), $delayed);
1243
			}
1244
		}
1245
1246
		$return = array(
1247
			'KillingShot' => !$alreadyDead && $this->isDestroyed(true),
1248
			'TargetAlreadyDead' => $alreadyDead,
1249
			'Shield' => $shieldDamage,
1250
			'Armour' => $armourDamage,
1251
			'HasShields' => $this->hasShields(true),
1252
			'HasArmour' => $this->hasArmour(true),
1253
			'CDs' => $cdDamage,
1254
			'NumCDs' => $cdDamage / CD_ARMOUR,
1255
			'HasCDs' => $this->hasCDs(true),
1256
			'TotalDamage' => $shieldDamage + $cdDamage + $armourDamage
1257
		);
1258
		return $return;
1259
	}
1260
1261
	protected function doShieldDamage($damage, $delayed) {
1262
		$actualDamage = min($this->getShields(true), $damage);
1263
		$this->decreaseShields($actualDamage, $delayed);
1264
		return $actualDamage;
1265
	}
1266
1267
	protected function doCDDamage($damage, $delayed) {
1268
		$actualDamage = min($this->getCDs(true), IFloor($damage / CD_ARMOUR));
1269
		$this->decreaseCDs($actualDamage, $delayed);
1270
		return $actualDamage * CD_ARMOUR;
1271
	}
1272
1273
	protected function doArmourDamage($damage, $delayed) {
1274
		$actualDamage = min($this->getArmour(true), $damage);
1275
		$this->decreaseArmour($actualDamage, $delayed);
1276
		return $actualDamage;
1277
	}
1278
1279
	public function creditCurrentAttackersForKill() {
1280
		//get all players involved for HoF
1281
		$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));
1282
		while ($this->db->nextRecord()) {
1283
			$currPlayer = SmrPlayer::getPlayer($this->db->getInt('account_id'), $this->getGameID());
1284
			$currPlayer->increaseHOF($this->db->getInt('level'), array('Combat', 'Planet', 'Levels'), HOF_PUBLIC);
1285
			$currPlayer->increaseHOF(1, array('Combat', 'Planet', 'Completed'), HOF_PUBLIC);
1286
		}
1287
		$this->db->query('DELETE FROM player_attacks_planet WHERE ' . $this->SQL);
1288
	}
1289
1290
	public function killPlanetByPlayer(AbstractSmrPlayer $killer) {
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

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

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...
1291
		$return = array();
1292
		$this->creditCurrentAttackersForKill();
1293
1294
		//kick everyone from planet
1295
		$this->db->query('UPDATE player SET land_on_planet = \'FALSE\' WHERE ' . $this->SQL);
1296
		$this->removeOwner();
1297
		$this->removePassword();
1298
		return $return;
1299
	}
1300
}
1301