We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.
Total Complexity | 266 |
Total Lines | 1325 |
Duplicated Lines | 0 % |
Changes | 1 | ||
Bugs | 0 | Features | 0 |
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); |
||
15 | class SmrPlanet { |
||
16 | |||
17 | /** @var array<int, array<int, self>> */ |
||
18 | protected static array $CACHE_PLANETS = []; |
||
19 | |||
20 | public const DAMAGE_NEEDED_FOR_DOWNGRADE_CHANCE = 100; |
||
21 | protected const CHANCE_TO_DOWNGRADE = 15; // percent |
||
22 | protected const TIME_TO_CREDIT_BUST = 10800; // 3 hours |
||
23 | protected const TIME_ATTACK_NEWS_COOLDOWN = 3600; // 1 hour |
||
24 | public const MAX_STOCKPILE = 600; |
||
25 | |||
26 | protected Database $db; |
||
27 | protected readonly string $SQL; |
||
28 | |||
29 | protected bool $exists; |
||
30 | protected string $planetName; |
||
31 | protected int $ownerID; |
||
32 | protected string $password; |
||
33 | protected int $shields; |
||
34 | protected int $armour; |
||
35 | protected int $drones; |
||
36 | protected int $credits; |
||
37 | protected int $bonds; |
||
38 | protected int $maturity; |
||
39 | /** @var array<int, int> */ |
||
40 | protected array $stockpile; |
||
41 | /** @var array<int, int> */ |
||
42 | protected array $buildings; |
||
43 | protected int $inhabitableTime; |
||
44 | /** @var array<int, array<string, int>> */ |
||
45 | protected array $currentlyBuilding; |
||
46 | /** @var array<int, SmrWeapon> */ |
||
47 | protected array $mountedWeapons; |
||
48 | protected int $typeID; |
||
49 | protected PlanetType $typeInfo; |
||
50 | |||
51 | protected bool $hasChanged = false; |
||
52 | protected bool $hasChangedFinancial = false; // for credits, bond, maturity |
||
53 | protected bool $hasChangedStockpile = false; |
||
54 | /** @var array<int, bool> */ |
||
55 | protected array $hasChangedWeapons = []; |
||
56 | /** @var array<int, bool> */ |
||
57 | protected array $hasChangedBuildings = []; |
||
58 | /** @var array<int> */ |
||
59 | protected array $hasStoppedBuilding = []; |
||
60 | protected bool $isNew = false; |
||
61 | |||
62 | public function __sleep() { |
||
63 | return ['sectorID', 'gameID', 'planetName', 'ownerID', 'typeID']; |
||
64 | } |
||
65 | |||
66 | public static function clearCache(): void { |
||
67 | self::$CACHE_PLANETS = []; |
||
68 | } |
||
69 | |||
70 | public static function savePlanets(): void { |
||
71 | foreach (self::$CACHE_PLANETS as $gamePlanets) { |
||
72 | foreach ($gamePlanets as $planet) { |
||
73 | $planet->update(); |
||
74 | } |
||
75 | } |
||
76 | } |
||
77 | |||
78 | /** |
||
79 | * @return array<int, self> |
||
80 | */ |
||
81 | public static function getGalaxyPlanets(int $gameID, int $galaxyID, bool $forceUpdate = false): array { |
||
82 | $db = Database::getInstance(); |
||
83 | $dbResult = $db->read('SELECT planet.* FROM planet LEFT JOIN sector USING (game_id, sector_id) WHERE game_id = ' . $db->escapeNumber($gameID) . ' AND galaxy_id = ' . $db->escapeNumber($galaxyID)); |
||
84 | $galaxyPlanets = []; |
||
85 | foreach ($dbResult->records() as $dbRecord) { |
||
86 | $sectorID = $dbRecord->getInt('sector_id'); |
||
87 | $galaxyPlanets[$sectorID] = self::getPlanet($gameID, $sectorID, $forceUpdate, $dbRecord); |
||
88 | } |
||
89 | return $galaxyPlanets; |
||
90 | } |
||
91 | |||
92 | public static function getPlanet(int $gameID, int $sectorID, bool $forceUpdate = false, DatabaseRecord $dbRecord = null): self { |
||
93 | if ($forceUpdate || !isset(self::$CACHE_PLANETS[$gameID][$sectorID])) { |
||
94 | self::$CACHE_PLANETS[$gameID][$sectorID] = new self($gameID, $sectorID, $dbRecord); |
||
95 | } |
||
96 | return self::$CACHE_PLANETS[$gameID][$sectorID]; |
||
97 | } |
||
98 | |||
99 | public static function createPlanet(int $gameID, int $sectorID, int $typeID = 1, int $inhabitableTime = null): self { |
||
100 | if (self::getPlanet($gameID, $sectorID)->exists()) { |
||
101 | throw new Exception('Planet already exists in sector ' . $sectorID . ' game ' . $gameID); |
||
102 | } |
||
103 | |||
104 | if ($inhabitableTime === null) { |
||
105 | $minTime = SmrGame::getGame($gameID)->getStartTime(); |
||
106 | $inhabitableTime = $minTime + pow(rand(45, 85), 3); |
||
107 | } |
||
108 | |||
109 | // insert planet into db |
||
110 | $db = Database::getInstance(); |
||
111 | $db->insert('planet', [ |
||
112 | 'game_id' => $db->escapeNumber($gameID), |
||
113 | 'sector_id' => $db->escapeNumber($sectorID), |
||
114 | 'inhabitable_time' => $db->escapeNumber($inhabitableTime), |
||
115 | 'planet_type_id' => $db->escapeNumber($typeID), |
||
116 | ]); |
||
117 | return self::getPlanet($gameID, $sectorID, true); |
||
118 | } |
||
119 | |||
120 | public static function removePlanet(int $gameID, int $sectorID): void { |
||
121 | $db = Database::getInstance(); |
||
122 | $SQL = 'game_id = ' . $db->escapeNumber($gameID) . ' AND sector_id = ' . $db->escapeNumber($sectorID); |
||
123 | $db->write('DELETE FROM planet WHERE ' . $SQL); |
||
124 | $db->write('DELETE FROM planet_has_weapon WHERE ' . $SQL); |
||
125 | $db->write('DELETE FROM planet_has_cargo WHERE ' . $SQL); |
||
126 | $db->write('DELETE FROM planet_has_building WHERE ' . $SQL); |
||
127 | $db->write('DELETE FROM planet_is_building WHERE ' . $SQL); |
||
128 | //kick everyone from planet |
||
129 | $db->write('UPDATE player SET land_on_planet = \'FALSE\' WHERE ' . $SQL); |
||
130 | |||
131 | self::$CACHE_PLANETS[$gameID][$sectorID] = null; |
||
132 | unset(self::$CACHE_PLANETS[$gameID][$sectorID]); |
||
133 | } |
||
134 | |||
135 | protected function __construct( |
||
136 | protected readonly int $gameID, |
||
137 | protected readonly int $sectorID, |
||
138 | DatabaseRecord $dbRecord = null |
||
139 | ) { |
||
140 | $this->db = Database::getInstance(); |
||
141 | $this->SQL = 'game_id = ' . $this->db->escapeNumber($gameID) . ' AND sector_id = ' . $this->db->escapeNumber($sectorID); |
||
|
|||
142 | |||
143 | if ($dbRecord === null) { |
||
144 | $dbResult = $this->db->read('SELECT * FROM planet WHERE ' . $this->SQL); |
||
145 | if ($dbResult->hasRecord()) { |
||
146 | $dbRecord = $dbResult->record(); |
||
147 | } |
||
148 | } |
||
149 | $this->exists = $dbRecord !== null; |
||
150 | |||
151 | if ($this->exists) { |
||
152 | $this->planetName = $dbRecord->getString('planet_name'); |
||
153 | $this->ownerID = $dbRecord->getInt('owner_id'); |
||
154 | $this->password = $dbRecord->getString('password'); |
||
155 | $this->shields = $dbRecord->getInt('shields'); |
||
156 | $this->armour = $dbRecord->getInt('armour'); |
||
157 | $this->drones = $dbRecord->getInt('drones'); |
||
158 | $this->credits = $dbRecord->getInt('credits'); |
||
159 | $this->bonds = $dbRecord->getInt('bonds'); |
||
160 | $this->maturity = $dbRecord->getInt('maturity'); |
||
161 | $this->inhabitableTime = $dbRecord->getInt('inhabitable_time'); |
||
162 | $this->typeID = $dbRecord->getInt('planet_type_id'); |
||
163 | |||
164 | $this->typeInfo = PlanetType::getTypeInfo($this->getTypeID()); |
||
165 | $this->checkBondMaturity(); |
||
166 | } |
||
167 | } |
||
168 | |||
169 | public function getInterestRate(): float { |
||
170 | $level = $this->getLevel(); |
||
171 | return match (true) { |
||
172 | $level < 9 => .0404, |
||
173 | $level < 19 => .0609, |
||
174 | $level < 29 => .1236, |
||
175 | $level < 39 => .050625, |
||
176 | $level < 49 => .0404, |
||
177 | $level < 59 => .030225, |
||
178 | $level < 69 => .0201, |
||
179 | default => .018081, |
||
180 | }; |
||
181 | } |
||
182 | |||
183 | public function checkBondMaturity(bool $partial = false): void { |
||
184 | if ($this->getMaturity() > 0 && ($partial === true || $this->getMaturity() < Epoch::time())) { |
||
185 | // calc the interest for the time |
||
186 | $interest = $this->getBonds() * $this->getInterestRate(); |
||
187 | |||
188 | if ($partial === true && $this->getMaturity() > Epoch::time()) { |
||
189 | // Adjust interest based upon how much of the bond duration has passed. |
||
190 | $interest -= ($interest / $this->getBondTime()) * ($this->getMaturity() - Epoch::time()); |
||
191 | } |
||
192 | |||
193 | // transfer money to free avail cash |
||
194 | $this->increaseCredits($this->getBonds() + IFloor($interest)); |
||
195 | |||
196 | // reset bonds |
||
197 | $this->setBonds(0); |
||
198 | |||
199 | // reset maturity |
||
200 | $this->setMaturity(0); |
||
201 | } |
||
202 | } |
||
203 | |||
204 | public function getBondTime(): int { |
||
205 | return IRound(BOND_TIME / $this->getGame()->getGameSpeed()); |
||
206 | } |
||
207 | |||
208 | public function bond(): void { |
||
209 | $this->checkBondMaturity(true); |
||
210 | |||
211 | // add it to bond |
||
212 | $this->increaseBonds($this->getCredits()); |
||
213 | |||
214 | // set free cash to 0 |
||
215 | $this->setCredits(0); |
||
216 | |||
217 | // initialize time |
||
218 | $this->setMaturity(Epoch::time() + $this->getBondTime()); |
||
219 | } |
||
220 | |||
221 | public function getGameID(): int { |
||
222 | return $this->gameID; |
||
223 | } |
||
224 | |||
225 | public function getGame(): SmrGame { |
||
226 | return SmrGame::getGame($this->gameID); |
||
227 | } |
||
228 | |||
229 | public function getSectorID(): int { |
||
230 | return $this->sectorID; |
||
231 | } |
||
232 | |||
233 | public function getGalaxy(): SmrGalaxy { |
||
234 | return SmrGalaxy::getGalaxyContaining($this->getGameID(), $this->getSectorID()); |
||
235 | } |
||
236 | |||
237 | public function getOwnerID(): int { |
||
238 | return $this->ownerID; |
||
239 | } |
||
240 | |||
241 | public function hasOwner(): bool { |
||
242 | return $this->ownerID != 0; |
||
243 | } |
||
244 | |||
245 | public function removeOwner(): void { |
||
246 | $this->setOwnerID(0); |
||
247 | } |
||
248 | |||
249 | public function setOwnerID(int $claimerID): void { |
||
250 | if ($this->ownerID === $claimerID) { |
||
251 | return; |
||
252 | } |
||
253 | $this->ownerID = $claimerID; |
||
254 | $this->hasChanged = true; |
||
255 | } |
||
256 | |||
257 | public function getOwner(): AbstractSmrPlayer { |
||
258 | return SmrPlayer::getPlayer($this->getOwnerID(), $this->getGameID()); |
||
259 | } |
||
260 | |||
261 | public function getPassword(): string { |
||
262 | return $this->password; |
||
263 | } |
||
264 | |||
265 | public function setPassword(string $password): void { |
||
266 | if ($this->password === $password) { |
||
267 | return; |
||
268 | } |
||
269 | $this->password = $password; |
||
270 | $this->hasChanged = true; |
||
271 | } |
||
272 | |||
273 | public function removePassword(): void { |
||
274 | $this->setPassword(''); |
||
275 | } |
||
276 | |||
277 | public function getCredits(): int { |
||
278 | return $this->credits; |
||
279 | } |
||
280 | |||
281 | public function setCredits(int $num): void { |
||
282 | if ($this->credits === $num) { |
||
283 | return; |
||
284 | } |
||
285 | if ($num < 0) { |
||
286 | throw new Exception('You cannot set negative credits.'); |
||
287 | } |
||
288 | if ($num > MAX_MONEY) { |
||
289 | throw new Exception('You cannot set more than the max credits.'); |
||
290 | } |
||
291 | $this->credits = $num; |
||
292 | $this->hasChangedFinancial = true; |
||
293 | } |
||
294 | |||
295 | /** |
||
296 | * Increases planet credits up to the maximum allowed credits. |
||
297 | * Returns the amount that was actually added to handle overflow. |
||
298 | */ |
||
299 | public function increaseCredits(int $num): int { |
||
300 | if ($num === 0) { |
||
301 | return 0; |
||
302 | } |
||
303 | $newTotal = min($this->credits + $num, MAX_MONEY); |
||
304 | $actualAdded = $newTotal - $this->credits; |
||
305 | $this->setCredits($newTotal); |
||
306 | return $actualAdded; |
||
307 | } |
||
308 | |||
309 | public function decreaseCredits(int $num): void { |
||
310 | if ($num === 0) { |
||
311 | return; |
||
312 | } |
||
313 | $newTotal = $this->credits - $num; |
||
314 | $this->setCredits($newTotal); |
||
315 | } |
||
316 | |||
317 | public function getMaturity(): int { |
||
318 | return $this->maturity; |
||
319 | } |
||
320 | |||
321 | public function setMaturity(int $num): void { |
||
322 | if ($this->maturity === $num) { |
||
323 | return; |
||
324 | } |
||
325 | if ($num < 0) { |
||
326 | throw new Exception('You cannot set negative maturity.'); |
||
327 | } |
||
328 | $this->maturity = $num; |
||
329 | $this->hasChangedFinancial = true; |
||
330 | } |
||
331 | |||
332 | public function getBonds(): int { |
||
333 | return $this->bonds; |
||
334 | } |
||
335 | |||
336 | public function setBonds(int $num): void { |
||
337 | if ($this->bonds === $num) { |
||
338 | return; |
||
339 | } |
||
340 | if ($num < 0) { |
||
341 | throw new Exception('You cannot set negative bonds.'); |
||
342 | } |
||
343 | $this->bonds = $num; |
||
344 | $this->hasChangedFinancial = true; |
||
345 | } |
||
346 | |||
347 | public function increaseBonds(int $num): void { |
||
348 | if ($num === 0) { |
||
349 | return; |
||
350 | } |
||
351 | $this->setBonds($this->getBonds() + $num); |
||
352 | } |
||
353 | |||
354 | public function decreaseBonds(int $num): void { |
||
355 | if ($num === 0) { |
||
356 | return; |
||
357 | } |
||
358 | $this->setBonds($this->getBonds() - $num); |
||
359 | } |
||
360 | |||
361 | public function checkForExcessDefense(): void { |
||
362 | if ($this->getShields() > $this->getMaxShields()) { |
||
363 | $this->setShields($this->getMaxShields()); |
||
364 | } |
||
365 | if ($this->getCDs() > $this->getMaxCDs()) { |
||
366 | $this->setCDs($this->getMaxCDs()); |
||
367 | } |
||
368 | if ($this->getArmour() > $this->getMaxArmour()) { |
||
369 | $this->setArmour($this->getMaxArmour()); |
||
370 | } |
||
371 | // Remove a random (0-indexed) mounted weapon, if over max mount slots |
||
372 | while ($this->getMountedWeapons() && max(array_keys($this->getMountedWeapons())) >= $this->getMaxMountedWeapons()) { |
||
373 | $removeID = array_rand($this->getMountedWeapons()); |
||
374 | $this->removeMountedWeapon($removeID); |
||
375 | foreach ($this->getMountedWeapons() as $orderID => $weapon) { |
||
376 | if ($orderID > $removeID) { |
||
377 | $this->moveMountedWeaponUp($orderID); |
||
378 | } |
||
379 | } |
||
380 | } |
||
381 | } |
||
382 | |||
383 | public function getShields(): int { |
||
384 | return $this->shields; |
||
385 | } |
||
386 | |||
387 | public function hasShields(): bool { |
||
388 | return $this->getShields() > 0; |
||
389 | } |
||
390 | |||
391 | public function setShields(int $shields): void { |
||
392 | $shields = max(0, min($shields, $this->getMaxShields())); |
||
393 | if ($this->shields === $shields) { |
||
394 | return; |
||
395 | } |
||
396 | $this->shields = $shields; |
||
397 | $this->hasChanged = true; |
||
398 | } |
||
399 | |||
400 | public function decreaseShields(int $number): void { |
||
401 | if ($number === 0) { |
||
402 | return; |
||
403 | } |
||
404 | $this->setShields($this->getShields() - $number); |
||
405 | } |
||
406 | |||
407 | public function increaseShields(int $number): void { |
||
408 | if ($number === 0) { |
||
409 | return; |
||
410 | } |
||
411 | $this->setShields($this->getShields() + $number); |
||
412 | } |
||
413 | |||
414 | public function getMaxShields(): int { |
||
415 | return $this->getBuilding(PLANET_GENERATOR) * PLANET_GENERATOR_SHIELDS; |
||
416 | } |
||
417 | |||
418 | public function getArmour(): int { |
||
419 | return $this->armour; |
||
420 | } |
||
421 | |||
422 | public function hasArmour(): bool { |
||
423 | return $this->getArmour() > 0; |
||
424 | } |
||
425 | |||
426 | public function setArmour(int $armour): void { |
||
427 | $armour = max(0, min($armour, $this->getMaxArmour())); |
||
428 | if ($this->armour === $armour) { |
||
429 | return; |
||
430 | } |
||
431 | $this->armour = $armour; |
||
432 | $this->hasChanged = true; |
||
433 | } |
||
434 | |||
435 | public function decreaseArmour(int $number): void { |
||
436 | if ($number === 0) { |
||
437 | return; |
||
438 | } |
||
439 | $this->setArmour($this->getArmour() - $number); |
||
440 | } |
||
441 | |||
442 | public function increaseArmour(int $number): void { |
||
443 | if ($number === 0) { |
||
444 | return; |
||
445 | } |
||
446 | $this->setArmour($this->getArmour() + $number); |
||
447 | } |
||
448 | |||
449 | public function getMaxArmour(): int { |
||
450 | return $this->getBuilding(PLANET_BUNKER) * PLANET_BUNKER_ARMOUR; |
||
451 | } |
||
452 | |||
453 | public function getCDs(): int { |
||
454 | return $this->drones; |
||
455 | } |
||
456 | |||
457 | public function hasCDs(): bool { |
||
458 | return $this->getCDs() > 0; |
||
459 | } |
||
460 | |||
461 | public function setCDs(int $combatDrones): void { |
||
462 | $combatDrones = max(0, min($combatDrones, $this->getMaxCDs())); |
||
463 | if ($this->drones === $combatDrones) { |
||
464 | return; |
||
465 | } |
||
466 | $this->drones = $combatDrones; |
||
467 | $this->hasChanged = true; |
||
468 | } |
||
469 | |||
470 | public function decreaseCDs(int $number): void { |
||
471 | if ($number === 0) { |
||
472 | return; |
||
473 | } |
||
474 | $this->setCDs($this->getCDs() - $number); |
||
475 | } |
||
476 | |||
477 | public function increaseCDs(int $number): void { |
||
478 | if ($number === 0) { |
||
479 | return; |
||
480 | } |
||
481 | $this->setCDs($this->getCDs() + $number); |
||
482 | } |
||
483 | |||
484 | public function getMaxCDs(): int { |
||
485 | return $this->getBuilding(PLANET_HANGAR) * PLANET_HANGAR_DRONES; |
||
486 | } |
||
487 | |||
488 | public function getMaxMountedWeapons(): int { |
||
489 | return $this->getBuilding(PLANET_WEAPON_MOUNT); |
||
490 | } |
||
491 | |||
492 | /** |
||
493 | * @return array<int, SmrWeapon> |
||
494 | */ |
||
495 | public function getMountedWeapons(): array { |
||
496 | if (!isset($this->mountedWeapons)) { |
||
497 | $this->mountedWeapons = []; |
||
498 | if ($this->hasBuilding(PLANET_WEAPON_MOUNT)) { |
||
499 | $dbResult = $this->db->read('SELECT * FROM planet_has_weapon JOIN weapon_type USING (weapon_type_id) WHERE ' . $this->SQL); |
||
500 | foreach ($dbResult->records() as $dbRecord) { |
||
501 | $weaponTypeID = $dbRecord->getInt('weapon_type_id'); |
||
502 | $orderID = $dbRecord->getInt('order_id'); |
||
503 | $weapon = SmrWeapon::getWeapon($weaponTypeID, $dbRecord); |
||
504 | $weapon->setBonusAccuracy($dbRecord->getBoolean('bonus_accuracy')); |
||
505 | $weapon->setBonusDamage($dbRecord->getBoolean('bonus_damage')); |
||
506 | $this->mountedWeapons[$orderID] = $weapon; |
||
507 | } |
||
508 | } |
||
509 | } |
||
510 | return $this->mountedWeapons; |
||
511 | } |
||
512 | |||
513 | public function hasMountedWeapon(int $orderID): bool { |
||
514 | $this->getMountedWeapons(); // Make sure array is initialized |
||
515 | return isset($this->mountedWeapons[$orderID]); |
||
516 | } |
||
517 | |||
518 | public function addMountedWeapon(SmrWeapon $weapon, int $orderID): void { |
||
519 | $this->getMountedWeapons(); // Make sure array is initialized |
||
520 | $this->mountedWeapons[$orderID] = $weapon; |
||
521 | $this->hasChangedWeapons[$orderID] = true; |
||
522 | } |
||
523 | |||
524 | public function removeMountedWeapon(int $orderID): void { |
||
525 | $this->getMountedWeapons(); // Make sure array is initialized |
||
526 | unset($this->mountedWeapons[$orderID]); |
||
527 | $this->hasChangedWeapons[$orderID] = true; |
||
528 | } |
||
529 | |||
530 | private function swapMountedWeapons(int $orderID1, int $orderID2): void { |
||
531 | $this->getMountedWeapons(); // Make sure array is initialized |
||
532 | if (isset($this->mountedWeapons[$orderID1])) { |
||
533 | $saveWeapon = $this->mountedWeapons[$orderID1]; |
||
534 | } |
||
535 | if (isset($this->mountedWeapons[$orderID2])) { |
||
536 | $this->mountedWeapons[$orderID1] = $this->mountedWeapons[$orderID2]; |
||
537 | } else { |
||
538 | unset($this->mountedWeapons[$orderID1]); |
||
539 | } |
||
540 | if (isset($saveWeapon)) { |
||
541 | $this->mountedWeapons[$orderID2] = $saveWeapon; |
||
542 | } else { |
||
543 | unset($this->mountedWeapons[$orderID2]); |
||
544 | } |
||
545 | $this->hasChangedWeapons[$orderID1] = true; |
||
546 | $this->hasChangedWeapons[$orderID2] = true; |
||
547 | } |
||
548 | |||
549 | public function moveMountedWeaponUp(int $orderID): void { |
||
550 | if ($orderID == 0) { |
||
551 | throw new Exception('Cannot move this weapon up!'); |
||
552 | } |
||
553 | $this->swapMountedWeapons($orderID - 1, $orderID); |
||
554 | } |
||
555 | |||
556 | public function moveMountedWeaponDown(int $orderID): void { |
||
557 | if ($orderID == $this->getMaxMountedWeapons() - 1) { |
||
558 | throw new Exception('Cannot move this weapon down!'); |
||
559 | } |
||
560 | $this->swapMountedWeapons($orderID + 1, $orderID); |
||
561 | } |
||
562 | |||
563 | |||
564 | public function isDestroyed(): bool { |
||
565 | return !$this->hasCDs() && !$this->hasShields() && !$this->hasArmour(); |
||
566 | } |
||
567 | |||
568 | public function exists(): bool { |
||
569 | return $this->exists; |
||
570 | } |
||
571 | |||
572 | /** |
||
573 | * @return ($goodID is null ? array<int, int> : int) |
||
574 | */ |
||
575 | public function getStockpile(int $goodID = null): int|array { |
||
576 | if (!isset($this->stockpile)) { |
||
577 | // initialize cargo array |
||
578 | $this->stockpile = []; |
||
579 | // get supplies from db |
||
580 | $dbResult = $this->db->read('SELECT good_id, amount FROM planet_has_cargo WHERE ' . $this->SQL); |
||
581 | // adding cargo and amount to array |
||
582 | foreach ($dbResult->records() as $dbRecord) { |
||
583 | $this->stockpile[$dbRecord->getInt('good_id')] = $dbRecord->getInt('amount'); |
||
584 | } |
||
585 | } |
||
586 | if ($goodID === null) { |
||
587 | return $this->stockpile; |
||
588 | } |
||
589 | if (isset($this->stockpile[$goodID])) { |
||
590 | return $this->stockpile[$goodID]; |
||
591 | } |
||
592 | return 0; |
||
593 | } |
||
594 | |||
595 | public function hasStockpile(int $goodID = null): bool { |
||
596 | if ($goodID === null) { |
||
597 | $stockpile = $this->getStockpile(); |
||
598 | return count($stockpile) > 0 && max($stockpile) > 0; |
||
599 | } |
||
600 | return $this->getStockpile($goodID) > 0; |
||
601 | } |
||
602 | |||
603 | public function setStockpile(int $goodID, int $amount): void { |
||
604 | if ($this->getStockpile($goodID) === $amount) { |
||
605 | return; |
||
606 | } |
||
607 | if ($amount < 0) { |
||
608 | throw new Exception('Trying to set negative stockpile.'); |
||
609 | } |
||
610 | $this->stockpile[$goodID] = $amount; |
||
611 | $this->hasChangedStockpile = true; |
||
612 | } |
||
613 | |||
614 | public function decreaseStockpile(int $goodID, int $amount): void { |
||
615 | if ($amount < 0) { |
||
616 | throw new Exception('Trying to decrease negative stockpile.'); |
||
617 | } |
||
618 | $this->setStockpile($goodID, $this->getStockpile($goodID) - $amount); |
||
619 | } |
||
620 | |||
621 | public function increaseStockpile(int $goodID, int $amount): void { |
||
622 | if ($amount < 0) { |
||
623 | throw new Exception('Trying to increase negative stockpile.'); |
||
624 | } |
||
625 | $this->setStockpile($goodID, $this->getStockpile($goodID) + $amount); |
||
626 | } |
||
627 | |||
628 | /** |
||
629 | * @return array<int, int> |
||
630 | */ |
||
631 | public function getBuildings(): array { |
||
632 | if (!isset($this->buildings)) { |
||
633 | $this->buildings = []; |
||
634 | |||
635 | // get buildingss from db |
||
636 | $dbResult = $this->db->read('SELECT construction_id, amount FROM planet_has_building WHERE ' . $this->SQL); |
||
637 | // adding building and amount to array |
||
638 | foreach ($dbResult->records() as $dbRecord) { |
||
639 | $this->buildings[$dbRecord->getInt('construction_id')] = $dbRecord->getInt('amount'); |
||
640 | } |
||
641 | |||
642 | // Update building counts if construction has finished |
||
643 | $this->getCurrentlyBuilding(); |
||
644 | } |
||
645 | return $this->buildings; |
||
646 | } |
||
647 | |||
648 | public function getBuilding(int $buildingTypeID): int { |
||
649 | $buildings = $this->getBuildings(); |
||
650 | if (isset($buildings[$buildingTypeID])) { |
||
651 | return $buildings[$buildingTypeID]; |
||
652 | } |
||
653 | return 0; |
||
654 | } |
||
655 | |||
656 | public function hasBuilding(int $buildingTypeID): bool { |
||
657 | return $this->getBuilding($buildingTypeID) > 0; |
||
658 | } |
||
659 | |||
660 | public function setBuilding(int $buildingTypeID, int $number): void { |
||
661 | if ($this->getBuilding($buildingTypeID) === $number) { |
||
662 | return; |
||
663 | } |
||
664 | if ($number < 0) { |
||
665 | throw new Exception('Cannot set negative number of buildings.'); |
||
666 | } |
||
667 | |||
668 | $this->buildings[$buildingTypeID] = $number; |
||
669 | $this->hasChangedBuildings[$buildingTypeID] = true; |
||
670 | } |
||
671 | |||
672 | public function increaseBuilding(int $buildingTypeID, int $number): void { |
||
673 | $this->setBuilding($buildingTypeID, $this->getBuilding($buildingTypeID) + $number); |
||
674 | } |
||
675 | |||
676 | public function destroyBuilding(int $buildingTypeID, int $number): void { |
||
677 | $this->setBuilding($buildingTypeID, $this->getBuilding($buildingTypeID) - $number); |
||
678 | } |
||
679 | |||
680 | /** |
||
681 | * @return array<int, array<string, int>> |
||
682 | */ |
||
683 | public function getCurrentlyBuilding(): array { |
||
684 | if (!isset($this->currentlyBuilding)) { |
||
685 | $this->currentlyBuilding = []; |
||
686 | |||
687 | $dbResult = $this->db->read('SELECT * FROM planet_is_building WHERE ' . $this->SQL); |
||
688 | foreach ($dbResult->records() as $dbRecord) { |
||
689 | $this->currentlyBuilding[$dbRecord->getInt('building_slot_id')] = [ |
||
690 | 'BuildingSlotID' => $dbRecord->getInt('building_slot_id'), |
||
691 | 'ConstructionID' => $dbRecord->getInt('construction_id'), |
||
692 | 'ConstructorID' => $dbRecord->getInt('constructor_id'), |
||
693 | 'Finishes' => $dbRecord->getInt('time_complete'), |
||
694 | 'TimeRemaining' => $dbRecord->getInt('time_complete') - Epoch::time(), |
||
695 | ]; |
||
696 | } |
||
697 | |||
698 | // Check if construction has completed |
||
699 | foreach ($this->currentlyBuilding as $id => $building) { |
||
700 | if ($building['TimeRemaining'] <= 0) { |
||
701 | unset($this->currentlyBuilding[$id]); |
||
702 | $expGain = $this->getConstructionExp($building['ConstructionID']); |
||
703 | $player = SmrPlayer::getPlayer($building['ConstructorID'], $this->getGameID()); |
||
704 | $player->increaseHOF(1, ['Planet', 'Buildings', 'Built'], HOF_ALLIANCE); |
||
705 | $player->increaseExperience($expGain); |
||
706 | $player->increaseHOF($expGain, ['Planet', 'Buildings', 'Experience'], HOF_ALLIANCE); |
||
707 | $this->hasStoppedBuilding[] = $building['BuildingSlotID']; |
||
708 | $this->increaseBuilding($building['ConstructionID'], 1); |
||
709 | |||
710 | // WARNING: The above modifications to the player/planet are dangerous because |
||
711 | // they may not be part of the current sector lock. But since they might not be, |
||
712 | // we may as well just update now to avoid either a) needing to remember to call |
||
713 | // this explicitly in all appropriate engine files or b) inconsistent exp display |
||
714 | // if this is called during the template display only and therefore unsaved. |
||
715 | $player->update(); |
||
716 | $this->update(); |
||
717 | } |
||
718 | } |
||
719 | } |
||
720 | return $this->currentlyBuilding; |
||
721 | } |
||
722 | |||
723 | /** |
||
724 | * @return ($buildingTypeID is null ? array<int, int> : int) |
||
725 | */ |
||
726 | public function getMaxBuildings(int $buildingTypeID = null): int|array { |
||
727 | if ($buildingTypeID === null) { |
||
728 | $maxBuildings = []; |
||
729 | foreach ($this->getStructureTypes() as $ID => $type) { |
||
730 | $maxBuildings[$ID] = $type->maxAmount(); |
||
731 | } |
||
732 | return $maxBuildings; |
||
733 | } |
||
734 | return $this->getStructureTypes($buildingTypeID)->maxAmount(); |
||
735 | } |
||
736 | |||
737 | public function getTypeID(): int { |
||
738 | return $this->typeID; |
||
739 | } |
||
740 | |||
741 | public function setTypeID(int $num): void { |
||
742 | if (isset($this->typeID) && $this->typeID === $num) { |
||
743 | return; |
||
744 | } |
||
745 | $this->typeID = $num; |
||
746 | $this->db->write('UPDATE planet SET planet_type_id = ' . $this->db->escapeNumber($num) . ' WHERE ' . $this->SQL); |
||
747 | $this->typeInfo = PlanetType::getTypeInfo($this->getTypeID()); |
||
748 | |||
749 | //trim buildings first |
||
750 | foreach ($this->getBuildings() as $id => $amt) { |
||
751 | if ($this->getMaxBuildings($id) < $amt) { |
||
752 | $this->destroyBuilding($id, $amt - $this->getMaxBuildings($id)); |
||
753 | } |
||
754 | } |
||
755 | |||
756 | //trim excess defenses |
||
757 | $this->checkForExcessDefense(); |
||
758 | |||
759 | $this->hasChanged = true; |
||
760 | $this->update(); |
||
761 | } |
||
762 | |||
763 | public function getTypeImage(): string { |
||
764 | return $this->typeInfo->imageLink(); |
||
765 | } |
||
766 | |||
767 | public function getTypeName(): string { |
||
768 | return $this->typeInfo->name(); |
||
769 | } |
||
770 | |||
771 | public function getTypeDescription(): string { |
||
772 | return $this->typeInfo->description(); |
||
773 | } |
||
774 | |||
775 | public function getMaxAttackers(): int { |
||
776 | return $this->typeInfo->maxAttackers(); |
||
777 | } |
||
778 | |||
779 | public function getMaxLanded(): int { |
||
780 | return $this->typeInfo->maxLanded(); |
||
781 | } |
||
782 | |||
783 | /** |
||
784 | * @return ($structureID is null ? array<int, SmrPlanetStructureType> : SmrPlanetStructureType) |
||
785 | */ |
||
786 | public function getStructureTypes(int $structureID = null): SmrPlanetStructureType|array { |
||
787 | return $this->typeInfo->structureTypes($structureID); |
||
788 | } |
||
789 | |||
790 | public function hasStructureType(int $structureID): bool { |
||
791 | return isset($this->getStructureTypes()[$structureID]); |
||
792 | } |
||
793 | |||
794 | /** |
||
795 | * Specifies which menu options the planet has. |
||
796 | */ |
||
797 | public function hasMenuOption(string $option): bool { |
||
798 | // We do not set options that are unavailable |
||
799 | return in_array($option, $this->typeInfo->menuOptions()); |
||
800 | } |
||
801 | |||
802 | public function update(): void { |
||
803 | if (!$this->exists()) { |
||
804 | return; |
||
805 | } |
||
806 | if ($this->hasChanged) { |
||
807 | $this->db->write('UPDATE planet SET |
||
808 | owner_id = ' . $this->db->escapeNumber($this->ownerID) . ', |
||
809 | password = ' . $this->db->escapeString($this->password) . ', |
||
810 | planet_name = ' . $this->db->escapeString($this->planetName) . ', |
||
811 | shields = ' . $this->db->escapeNumber($this->shields) . ', |
||
812 | armour = ' . $this->db->escapeNumber($this->armour) . ', |
||
813 | drones = ' . $this->db->escapeNumber($this->drones) . ' |
||
814 | WHERE ' . $this->SQL); |
||
815 | $this->hasChanged = false; |
||
816 | } |
||
817 | |||
818 | // Separate update for financial since these can be modified by looking |
||
819 | // at the planet list (i.e. you might not have sector lock and could |
||
820 | // cause a race condition with events happening in the planet sector). |
||
821 | if ($this->hasChangedFinancial) { |
||
822 | $this->db->write('UPDATE planet SET |
||
823 | credits = ' . $this->db->escapeNumber($this->credits) . ', |
||
824 | bonds = ' . $this->db->escapeNumber($this->bonds) . ', |
||
825 | maturity = ' . $this->db->escapeNumber($this->maturity) . ' |
||
826 | WHERE ' . $this->SQL); |
||
827 | $this->hasChangedFinancial = false; |
||
828 | } |
||
829 | |||
830 | if ($this->hasChangedStockpile) { |
||
831 | // write stockpile info |
||
832 | foreach ($this->getStockpile() as $id => $amount) { |
||
833 | if ($amount != 0) { |
||
834 | $this->db->replace('planet_has_cargo', [ |
||
835 | 'game_id' => $this->db->escapeNumber($this->getGameID()), |
||
836 | 'sector_id' => $this->db->escapeNumber($this->getSectorID()), |
||
837 | 'good_id' => $this->db->escapeNumber($id), |
||
838 | 'amount' => $this->db->escapeNumber($amount), |
||
839 | ]); |
||
840 | } else { |
||
841 | $this->db->write('DELETE FROM planet_has_cargo WHERE ' . $this->SQL . ' |
||
842 | AND good_id = ' . $this->db->escapeNumber($id)); |
||
843 | } |
||
844 | } |
||
845 | } |
||
846 | |||
847 | if (count($this->hasChangedWeapons) > 0) { |
||
848 | foreach (array_keys($this->hasChangedWeapons) as $orderID) { |
||
849 | if (isset($this->mountedWeapons[$orderID])) { |
||
850 | $this->db->replace('planet_has_weapon', [ |
||
851 | 'game_id' => $this->db->escapeNumber($this->getGameID()), |
||
852 | 'sector_id' => $this->db->escapeNumber($this->getSectorID()), |
||
853 | 'order_id' => $this->db->escapeNumber($orderID), |
||
854 | 'weapon_type_id' => $this->db->escapeNumber($this->mountedWeapons[$orderID]->getWeaponTypeID()), |
||
855 | 'bonus_accuracy' => $this->db->escapeBoolean($this->mountedWeapons[$orderID]->hasBonusAccuracy()), |
||
856 | 'bonus_damage' => $this->db->escapeBoolean($this->mountedWeapons[$orderID]->hasBonusDamage()), |
||
857 | ]); |
||
858 | } else { |
||
859 | $this->db->write('DELETE FROM planet_has_weapon WHERE ' . $this->SQL . ' AND order_id=' . $this->db->escapeNumber($orderID)); |
||
860 | } |
||
861 | } |
||
862 | $this->hasChangedWeapons = []; |
||
863 | } |
||
864 | |||
865 | if (count($this->hasStoppedBuilding) > 0) { |
||
866 | $this->db->write('DELETE FROM planet_is_building WHERE ' . $this->SQL . ' |
||
867 | AND building_slot_id IN (' . $this->db->escapeArray($this->hasStoppedBuilding) . ') LIMIT ' . count($this->hasStoppedBuilding)); |
||
868 | $this->hasStoppedBuilding = []; |
||
869 | } |
||
870 | // write building info |
||
871 | foreach ($this->hasChangedBuildings as $id => $hasChanged) { |
||
872 | if ($hasChanged === true) { |
||
873 | if ($this->hasBuilding($id)) { |
||
874 | $this->db->replace('planet_has_building', [ |
||
875 | 'game_id' => $this->db->escapeNumber($this->gameID), |
||
876 | 'sector_id' => $this->db->escapeNumber($this->sectorID), |
||
877 | 'construction_id' => $this->db->escapeNumber($id), |
||
878 | 'amount' => $this->db->escapeNumber($this->getBuilding($id)), |
||
879 | ]); |
||
880 | } else { |
||
881 | $this->db->write('DELETE FROM planet_has_building WHERE ' . $this->SQL . ' |
||
882 | AND construction_id = ' . $this->db->escapeNumber($id)); |
||
883 | } |
||
884 | $this->hasChangedBuildings[$id] = false; |
||
885 | } |
||
886 | } |
||
887 | } |
||
888 | |||
889 | public function getLevel(): float { |
||
890 | return array_sum($this->getBuildings()) / 3; |
||
891 | } |
||
892 | |||
893 | public function getMaxLevel(): float { |
||
894 | return array_sum($this->getMaxBuildings()) / 3; |
||
895 | } |
||
896 | |||
897 | /** |
||
898 | * Returns the modified accuracy of turrets on this planet. |
||
899 | * Only used for display purposes. |
||
900 | */ |
||
901 | public function getTurretAccuracy(): float { |
||
902 | return SmrWeapon::getWeapon(WEAPON_PLANET_TURRET)->getModifiedPlanetAccuracy($this); |
||
903 | } |
||
904 | |||
905 | /** |
||
906 | * Returns the accuracy bonus for mounted weaons (as a percent) |
||
907 | */ |
||
908 | public function getAccuracyBonus(): int { |
||
909 | return 5 * $this->getBuilding(PLANET_RADAR); |
||
910 | } |
||
911 | |||
912 | public function getRemainingStockpile(int $id): int { |
||
913 | return self::MAX_STOCKPILE - $this->getStockpile($id); |
||
914 | } |
||
915 | |||
916 | /** |
||
917 | * Returns true if there is a building in progress |
||
918 | */ |
||
919 | public function hasCurrentlyBuilding(): bool { |
||
920 | return count($this->getCurrentlyBuilding()) > 0; |
||
921 | } |
||
922 | |||
923 | /** |
||
924 | * Returns the reason a build cannot be performed, or false if there is |
||
925 | * no restriction. |
||
926 | */ |
||
927 | public function getBuildRestriction(AbstractSmrPlayer $constructor, int $constructionID): string|false { |
||
928 | if ($this->hasCurrentlyBuilding()) { |
||
929 | return 'There is already a building in progress!'; |
||
930 | } |
||
931 | if ($this->getBuilding($constructionID) >= $this->getMaxBuildings($constructionID)) { |
||
932 | return 'This planet has reached the maximum buildings of that type.'; |
||
933 | } |
||
934 | $cost = $this->getStructureTypes($constructionID)->creditCost(); |
||
935 | if ($constructor->getCredits() < $cost) { |
||
936 | return 'You do not have enough credits.'; |
||
937 | } |
||
938 | if ($constructor->getTurns() < TURNS_TO_BUILD) { |
||
939 | return 'You do not have enough turns to build.'; |
||
940 | } |
||
941 | foreach ($this->getStructureTypes($constructionID)->hardwareCost() as $hardwareID) { |
||
942 | if (!$constructor->getShip()->getHardware($hardwareID)) { |
||
943 | return 'You do not have the hardware needed for this type of building!'; |
||
944 | } |
||
945 | } |
||
946 | // take the goods that are needed |
||
947 | foreach ($this->getStructureTypes($constructionID)->goods() as $goodID => $amount) { |
||
948 | if ($this->getStockpile($goodID) < $amount) { |
||
949 | return 'There are not enough goods available.'; |
||
950 | } |
||
951 | } |
||
952 | return false; |
||
953 | } |
||
954 | |||
955 | // Modifier for planet building based on the number of buildings. |
||
956 | // The average value of this modifier should roughly be 1. |
||
957 | private function getCompletionModifier(int $constructionID): float { |
||
958 | $currentBuildings = $this->getBuilding($constructionID); |
||
959 | $maxBuildings = $this->getMaxBuildings($constructionID); |
||
960 | return 0.01 + 2.97 * pow($currentBuildings / $maxBuildings, 2); |
||
961 | } |
||
962 | |||
963 | // Amount of exp gained to build the next building of this type |
||
964 | private function getConstructionExp(int $constructionID): int { |
||
965 | return $this->getStructureTypes($constructionID)->expGain(); |
||
966 | } |
||
967 | |||
968 | // Amount of time (in seconds) to build the next building of this type |
||
969 | public function getConstructionTime(int $constructionID): int { |
||
970 | $baseTime = $this->getStructureTypes($constructionID)->baseTime(); |
||
971 | return ICeil($baseTime * $this->getCompletionModifier($constructionID) / $this->getGame()->getGameSpeed()); |
||
972 | } |
||
973 | |||
974 | /** |
||
975 | * @throws \Smr\Exceptions\UserError If the player cannot build the structure. |
||
976 | */ |
||
977 | public function startBuilding(AbstractSmrPlayer $constructor, int $constructionID): void { |
||
978 | $restriction = $this->getBuildRestriction($constructor, $constructionID); |
||
979 | if ($restriction !== false) { |
||
980 | throw new UserError('Unable to start building: ' . $restriction); |
||
981 | } |
||
982 | |||
983 | // gets the time for the buildings |
||
984 | $timeComplete = Epoch::time() + $this->getConstructionTime($constructionID); |
||
985 | $insertID = $this->db->insert('planet_is_building', [ |
||
986 | 'game_id' => $this->db->escapeNumber($this->getGameID()), |
||
987 | 'sector_id' => $this->db->escapeNumber($this->getSectorID()), |
||
988 | 'construction_id' => $this->db->escapeNumber($constructionID), |
||
989 | 'constructor_id' => $this->db->escapeNumber($constructor->getAccountID()), |
||
990 | 'time_complete' => $this->db->escapeNumber($timeComplete), |
||
991 | ]); |
||
992 | |||
993 | $this->currentlyBuilding[$insertID] = [ |
||
994 | 'BuildingSlotID' => $insertID, |
||
995 | 'ConstructionID' => $constructionID, |
||
996 | 'ConstructorID' => $constructor->getAccountID(), |
||
997 | 'Finishes' => $timeComplete, |
||
998 | 'TimeRemaining' => $timeComplete - Epoch::time(), |
||
999 | ]; |
||
1000 | |||
1001 | // Consume the required resources |
||
1002 | $constructor->decreaseCredits($this->getStructureTypes($constructionID)->creditCost()); |
||
1003 | $constructor->takeTurns(TURNS_TO_BUILD); |
||
1004 | foreach ($this->getStructureTypes($constructionID)->goods() as $goodID => $amount) { |
||
1005 | $this->decreaseStockpile($goodID, $amount); |
||
1006 | } |
||
1007 | foreach ($this->getStructureTypes($constructionID)->hardwareCost() as $hardwareID) { |
||
1008 | $constructor->getShip()->setHardware($hardwareID, 0); |
||
1009 | } |
||
1010 | } |
||
1011 | |||
1012 | public function stopBuilding(int $constructionID): bool { |
||
1013 | $matchingBuilding = false; |
||
1014 | $latestFinish = 0; |
||
1015 | foreach ($this->getCurrentlyBuilding() as $building) { |
||
1016 | if ($building['ConstructionID'] == $constructionID && $building['Finishes'] > $latestFinish) { |
||
1017 | $latestFinish = $building['Finishes']; |
||
1018 | $matchingBuilding = $building; |
||
1019 | } |
||
1020 | } |
||
1021 | if ($matchingBuilding) { |
||
1022 | $this->hasStoppedBuilding[] = $matchingBuilding['BuildingSlotID']; |
||
1023 | unset($this->currentlyBuilding[$matchingBuilding['BuildingSlotID']]); |
||
1024 | return true; |
||
1025 | } |
||
1026 | return false; |
||
1027 | } |
||
1028 | |||
1029 | public function setName(string $name): void { |
||
1030 | if ($this->planetName === $name) { |
||
1031 | return; |
||
1032 | } |
||
1033 | $this->planetName = $name; |
||
1034 | $this->hasChanged = true; |
||
1035 | } |
||
1036 | |||
1037 | /** |
||
1038 | * Returns the name of the planet, suitably escaped for HTML display. |
||
1039 | */ |
||
1040 | public function getDisplayName(): string { |
||
1041 | return htmlentities($this->planetName); |
||
1042 | } |
||
1043 | |||
1044 | /** |
||
1045 | * Returns the name of the planet, intended for combat messages. |
||
1046 | */ |
||
1047 | public function getCombatName(): string { |
||
1048 | return '<span style="color:yellow;font-variant:small-caps">' . $this->getDisplayName() . ' (#' . $this->getSectorID() . ')</span>'; |
||
1049 | } |
||
1050 | |||
1051 | public function isInhabitable(): bool { |
||
1052 | return $this->inhabitableTime <= Epoch::time(); |
||
1053 | } |
||
1054 | |||
1055 | public function getInhabitableTime(): int { |
||
1056 | return $this->inhabitableTime; |
||
1057 | } |
||
1058 | |||
1059 | public function getExamineHREF(): string { |
||
1060 | return (new ExaminePlanet())->href(); |
||
1061 | } |
||
1062 | |||
1063 | public function getLandHREF(): string { |
||
1064 | return (new LandProcessor())->href(); |
||
1065 | } |
||
1066 | |||
1067 | public function getAttackHREF(): string { |
||
1068 | return (new AttackPlanetProcessor())->href(); |
||
1069 | } |
||
1070 | |||
1071 | public function getBuildHREF(int $structureID): string { |
||
1072 | $container = new ConstructionProcessor('Build', $structureID); |
||
1073 | return $container->href(); |
||
1074 | } |
||
1075 | |||
1076 | public function getCancelHREF(int $structureID): string { |
||
1077 | $container = new ConstructionProcessor('Cancel', $structureID); |
||
1078 | return $container->href(); |
||
1079 | } |
||
1080 | |||
1081 | public function getFinancesHREF(): string { |
||
1082 | return (new FinancialProcessor())->href(); |
||
1083 | } |
||
1084 | |||
1085 | public function getBondConfirmationHREF(): string { |
||
1086 | return (new BondConfirm())->href(); |
||
1087 | } |
||
1088 | |||
1089 | /** |
||
1090 | * @param array<AbstractSmrPlayer> $attackers |
||
1091 | */ |
||
1092 | public function attackedBy(AbstractSmrPlayer $trigger, array $attackers): void { |
||
1093 | $trigger->increaseHOF(1, ['Combat', 'Planet', 'Number Of Triggers'], HOF_PUBLIC); |
||
1094 | foreach ($attackers as $attacker) { |
||
1095 | $attacker->increaseHOF(1, ['Combat', 'Planet', 'Number Of Attacks'], HOF_PUBLIC); |
||
1096 | $this->db->replace('player_attacks_planet', [ |
||
1097 | 'game_id' => $this->db->escapeNumber($this->getGameID()), |
||
1098 | 'account_id' => $this->db->escapeNumber($attacker->getAccountID()), |
||
1099 | 'sector_id' => $this->db->escapeNumber($this->getSectorID()), |
||
1100 | 'time' => $this->db->escapeNumber(Epoch::time()), |
||
1101 | 'level' => $this->db->escapeNumber($this->getLevel()), |
||
1102 | ]); |
||
1103 | } |
||
1104 | |||
1105 | // Add each unique attack to news unless it was already added recently. |
||
1106 | // Note: Attack uniqueness determined by planet owner. |
||
1107 | $owner = $this->getOwner(); |
||
1108 | $dbResult = $this->db->read('SELECT 1 FROM news WHERE type = \'BREAKING\' AND game_id = ' . $this->db->escapeNumber($trigger->getGameID()) . ' AND dead_id=' . $this->db->escapeNumber($owner->getAccountID()) . ' AND time > ' . $this->db->escapeNumber(Epoch::time() - self::TIME_ATTACK_NEWS_COOLDOWN) . ' LIMIT 1'); |
||
1109 | if (!$dbResult->hasRecord()) { |
||
1110 | if (count($attackers) >= 5) { |
||
1111 | $text = count($attackers) . ' members of ' . $trigger->getAllianceBBLink() . ' have been spotted attacking ' . |
||
1112 | $this->getDisplayName() . ' in sector ' . Globals::getSectorBBLink($this->getSectorID()) . '. The planet is owned by ' . $owner->getBBLink(); |
||
1113 | if ($owner->hasAlliance()) { |
||
1114 | $text .= ', a member of ' . $owner->getAllianceBBLink(); |
||
1115 | } |
||
1116 | $text .= '.'; |
||
1117 | $this->db->insert('news', [ |
||
1118 | 'game_id' => $this->db->escapeNumber($this->getGameID()), |
||
1119 | 'time' => $this->db->escapeNumber(Epoch::time()), |
||
1120 | 'news_message' => $this->db->escapeString($text), |
||
1121 | 'type' => $this->db->escapeString('breaking'), |
||
1122 | 'killer_id' => $this->db->escapeNumber($trigger->getAccountID()), |
||
1123 | 'killer_alliance' => $this->db->escapeNumber($trigger->getAllianceID()), |
||
1124 | 'dead_id' => $this->db->escapeNumber($owner->getAccountID()), |
||
1125 | 'dead_alliance' => $this->db->escapeNumber($owner->getAllianceID()), |
||
1126 | ]); |
||
1127 | } |
||
1128 | } |
||
1129 | } |
||
1130 | |||
1131 | |||
1132 | /** |
||
1133 | * @return array<int, SmrPlayer> |
||
1134 | */ |
||
1135 | public function getPlayers(): array { |
||
1137 | } |
||
1138 | |||
1139 | public function countPlayers(): int { |
||
1140 | return count($this->getPlayers()); |
||
1141 | } |
||
1142 | |||
1143 | public function hasPlayers(): bool { |
||
1144 | return $this->countPlayers() > 0; |
||
1145 | } |
||
1146 | |||
1147 | /** |
||
1148 | * @return array<int, SmrPlayer> |
||
1149 | */ |
||
1150 | public function getOtherTraders(AbstractSmrPlayer $player): array { |
||
1154 | } |
||
1155 | |||
1156 | public function hasOtherTraders(AbstractSmrPlayer $player): bool { |
||
1157 | return count($this->getOtherTraders($player)) > 0; |
||
1158 | } |
||
1159 | |||
1160 | public function hasEnemyTraders(AbstractSmrPlayer $player): bool { |
||
1161 | if (!$this->hasOtherTraders($player)) { |
||
1162 | return false; |
||
1163 | } |
||
1164 | $otherPlayers = $this->getOtherTraders($player); |
||
1165 | foreach ($otherPlayers as $otherPlayer) { |
||
1166 | if (!$player->traderNAPAlliance($otherPlayer)) { |
||
1167 | return true; |
||
1168 | } |
||
1169 | } |
||
1170 | return false; |
||
1171 | } |
||
1172 | |||
1173 | public function hasFriendlyTraders(AbstractSmrPlayer $player): bool { |
||
1184 | } |
||
1185 | |||
1186 | /** |
||
1187 | * @return array<SmrWeapon> |
||
1188 | */ |
||
1189 | public function getWeapons(): array { |
||
1190 | $weapons = $this->getMountedWeapons(); |
||
1191 | return array_pad( |
||
1192 | $weapons, |
||
1193 | count($weapons) + $this->getBuilding(PLANET_TURRET), |
||
1194 | SmrWeapon::getWeapon(WEAPON_PLANET_TURRET) |
||
1195 | ); |
||
1196 | } |
||
1197 | |||
1198 | /** |
||
1199 | * @param array<AbstractSmrPlayer> $targetPlayers |
||
1200 | * @return array<string, mixed> |
||
1201 | */ |
||
1202 | public function shootPlayers(array $targetPlayers): array { |
||
1203 | $results = ['Planet' => $this, 'TotalDamage' => 0, 'TotalDamagePerTargetPlayer' => []]; |
||
1204 | foreach ($targetPlayers as $targetPlayer) { |
||
1205 | $results['TotalDamagePerTargetPlayer'][$targetPlayer->getAccountID()] = 0; |
||
1206 | } |
||
1207 | if ($this->isDestroyed()) { |
||
1208 | $results['DeadBeforeShot'] = true; |
||
1209 | return $results; |
||
1210 | } |
||
1211 | $results['DeadBeforeShot'] = false; |
||
1212 | $weapons = $this->getWeapons(); |
||
1213 | foreach ($weapons as $orderID => $weapon) { |
||
1214 | $results['Weapons'][$orderID] = $weapon->shootPlayerAsPlanet($this, array_rand_value($targetPlayers)); |
||
1215 | if ($results['Weapons'][$orderID]['Hit']) { |
||
1216 | $results['TotalDamage'] += $results['Weapons'][$orderID]['ActualDamage']['TotalDamage']; |
||
1217 | $results['TotalDamagePerTargetPlayer'][$results['Weapons'][$orderID]['TargetPlayer']->getAccountID()] += $results['Weapons'][$orderID]['ActualDamage']['TotalDamage']; |
||
1218 | } |
||
1219 | } |
||
1220 | if ($this->hasCDs()) { |
||
1221 | $thisCDs = new SmrCombatDrones($this->getCDs(), true); |
||
1222 | $results['Drones'] = $thisCDs->shootPlayerAsPlanet($this, array_rand_value($targetPlayers)); |
||
1223 | $results['TotalDamage'] += $results['Drones']['ActualDamage']['TotalDamage']; |
||
1224 | $results['TotalDamagePerTargetPlayer'][$results['Drones']['TargetPlayer']->getAccountID()] += $results['Drones']['ActualDamage']['TotalDamage']; |
||
1225 | } |
||
1226 | return $results; |
||
1227 | } |
||
1228 | |||
1229 | /** |
||
1230 | * Returns an array of structure losses due to damage taken. |
||
1231 | * |
||
1232 | * @return array<int, int> |
||
1233 | */ |
||
1234 | public function checkForDowngrade(int $damage): array { |
||
1262 | } |
||
1263 | |||
1264 | /** |
||
1265 | * @param WeaponDamageData $damage |
||
1266 | * @return array<string, int|bool> |
||
1267 | */ |
||
1268 | public function takeDamage(array $damage): array { |
||
1269 | $alreadyDead = $this->isDestroyed(); |
||
1270 | $shieldDamage = 0; |
||
1271 | $cdDamage = 0; |
||
1272 | $armourDamage = 0; |
||
1273 | if (!$alreadyDead) { |
||
1274 | $shieldDamage = $this->takeDamageToShields($damage['Shield']); |
||
1275 | if ($shieldDamage == 0 || $damage['Rollover']) { |
||
1276 | $cdMaxDamage = $damage['Armour'] - $shieldDamage; |
||
1277 | if ($shieldDamage == 0 && $this->hasShields()) { |
||
1278 | $cdMaxDamage = IFloor($cdMaxDamage * DRONES_BEHIND_SHIELDS_DAMAGE_PERCENT); |
||
1279 | } |
||
1280 | $cdDamage = $this->takeDamageToCDs($cdMaxDamage); |
||
1281 | if (!$this->hasShields() && ($cdDamage == 0 || $damage['Rollover'])) { |
||
1282 | $armourMaxDamage = $damage['Armour'] - $shieldDamage - $cdDamage; |
||
1283 | $armourDamage = $this->takeDamageToArmour($armourMaxDamage); |
||
1284 | } |
||
1285 | } |
||
1286 | } |
||
1287 | |||
1288 | return [ |
||
1289 | 'KillingShot' => !$alreadyDead && $this->isDestroyed(), |
||
1290 | 'TargetAlreadyDead' => $alreadyDead, |
||
1291 | 'Shield' => $shieldDamage, |
||
1292 | 'CDs' => $cdDamage, |
||
1293 | 'NumCDs' => $cdDamage / CD_ARMOUR, |
||
1294 | 'HasCDs' => $this->hasCDs(), |
||
1295 | 'Armour' => $armourDamage, |
||
1296 | 'TotalDamage' => $shieldDamage + $cdDamage + $armourDamage, |
||
1297 | ]; |
||
1298 | } |
||
1299 | |||
1300 | protected function takeDamageToShields(int $damage): int { |
||
1301 | $actualDamage = min($this->getShields(), $damage); |
||
1302 | $this->decreaseShields($actualDamage); |
||
1303 | return $actualDamage; |
||
1304 | } |
||
1305 | |||
1306 | protected function takeDamageToCDs(int $damage): int { |
||
1307 | $actualDamage = min($this->getCDs(), IFloor($damage / CD_ARMOUR)); |
||
1308 | $this->decreaseCDs($actualDamage); |
||
1309 | return $actualDamage * CD_ARMOUR; |
||
1310 | } |
||
1311 | |||
1312 | protected function takeDamageToArmour(int $damage): int { |
||
1313 | $actualDamage = min($this->getArmour(), $damage); |
||
1314 | $this->decreaseArmour($actualDamage); |
||
1315 | return $actualDamage; |
||
1316 | } |
||
1317 | |||
1318 | public function creditCurrentAttackersForKill(): void { |
||
1319 | //get all players involved for HoF |
||
1320 | $dbResult = $this->db->read('SELECT account_id,level FROM player_attacks_planet WHERE ' . $this->SQL . ' AND time > ' . $this->db->escapeNumber(Epoch::time() - self::TIME_TO_CREDIT_BUST)); |
||
1321 | foreach ($dbResult->records() as $dbRecord) { |
||
1322 | $currPlayer = SmrPlayer::getPlayer($dbRecord->getInt('account_id'), $this->getGameID()); |
||
1323 | $currPlayer->increaseHOF($dbRecord->getFloat('level'), ['Combat', 'Planet', 'Levels'], HOF_PUBLIC); |
||
1324 | $currPlayer->increaseHOF(1, ['Combat', 'Planet', 'Completed'], HOF_PUBLIC); |
||
1325 | } |
||
1326 | $this->db->write('DELETE FROM player_attacks_planet WHERE ' . $this->SQL); |
||
1327 | } |
||
1328 | |||
1329 | /** |
||
1330 | * @return array<string, mixed> |
||
1331 | */ |
||
1332 | public function killPlanetByPlayer(AbstractSmrPlayer $killer): array { |
||
1333 | $this->creditCurrentAttackersForKill(); |
||
1334 | |||
1335 | //kick everyone from planet |
||
1336 | $this->db->write('UPDATE player SET land_on_planet = \'FALSE\' WHERE ' . $this->SQL); |
||
1337 | $this->removeOwner(); |
||
1338 | $this->removePassword(); |
||
1339 | return []; |
||
1340 | } |
||
1341 | |||
1342 | } |
||
1343 |