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

SmrSector::leavingSector()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 10
c 1
b 0
f 0
nc 2
nop 2
dl 0
loc 16
rs 9.9332
1
<?php declare(strict_types=1);
2
3
use Smr\Database;
4
use Smr\DatabaseRecord;
5
use Smr\Exceptions\CachedPortNotFound;
6
use Smr\Exceptions\SectorNotFound;
7
use Smr\HardwareType;
8
use Smr\MovementType;
9
use Smr\Pages\Player\LocalMap;
10
11
class SmrSector {
12
13
	/** @var array<int, array<int, self>> */
14
	protected static array $CACHE_SECTORS = [];
15
	/** @var array<int, array<int, array<int, self>>> */
16
	protected static array $CACHE_GALAXY_SECTORS = [];
17
	/** @var array<int, array<int, array<int, self>>> */
18
	protected static array $CACHE_LOCATION_SECTORS = [];
19
20
	protected Database $db;
21
	protected readonly string $SQL;
22
23
	protected int $battles;
24
	protected int $galaxyID;
25
	/** @var array<int, bool> */
26
	protected array $visited = [];
27
	/** @var array<string, int> */
28
	protected array $links = [];
29
	protected int $warp;
30
31
	protected bool $hasChanged = false;
32
	protected bool $isNew = false;
33
34
	/**
35
	 * Maps the SmrSector link direction names to database columns.
36
	 */
37
	protected const LINK_DIR_MAPPING = [
38
		'Up' => 'link_up',
39
		'Down' => 'link_down',
40
		'Left' => 'link_left',
41
		'Right' => 'link_right',
42
	];
43
44
	/**
45
	 * Constructs the sector to determine if it exists.
46
	 * Returns a boolean value.
47
	 */
48
	public static function sectorExists(int $gameID, int $sectorID): bool {
49
		try {
50
			self::getSector($gameID, $sectorID);
51
			return true;
52
		} catch (SectorNotFound) {
53
			return false;
54
		}
55
	}
56
57
	/**
58
	 * @return array<int, self>
59
	 */
60
	public static function getGalaxySectors(int $gameID, int $galaxyID, bool $forceUpdate = false): array {
61
		if ($forceUpdate || !isset(self::$CACHE_GALAXY_SECTORS[$gameID][$galaxyID])) {
62
			$db = Database::getInstance();
63
			$dbResult = $db->read('SELECT * FROM sector WHERE game_id = ' . $db->escapeNumber($gameID) . ' AND galaxy_id=' . $db->escapeNumber($galaxyID) . ' ORDER BY sector_id ASC');
64
			$sectors = [];
65
			foreach ($dbResult->records() as $dbRecord) {
66
				$sectorID = $dbRecord->getInt('sector_id');
67
				$sectors[$sectorID] = self::getSector($gameID, $sectorID, $forceUpdate, $dbRecord);
68
			}
69
			self::$CACHE_GALAXY_SECTORS[$gameID][$galaxyID] = $sectors;
70
		}
71
		return self::$CACHE_GALAXY_SECTORS[$gameID][$galaxyID];
72
	}
73
74
	/**
75
	 * @return array<int, self>
76
	 */
77
	public static function getLocationSectors(int $gameID, int $locationTypeID, bool $forceUpdate = false): array {
78
		if ($forceUpdate || !isset(self::$CACHE_LOCATION_SECTORS[$gameID][$locationTypeID])) {
79
			$db = Database::getInstance();
80
			$dbResult = $db->read('SELECT * FROM location JOIN sector USING (game_id, sector_id) WHERE location_type_id = ' . $db->escapeNumber($locationTypeID) . ' AND game_id=' . $db->escapeNumber($gameID) . ' ORDER BY sector_id ASC');
81
			$sectors = [];
82
			foreach ($dbResult->records() as $dbRecord) {
83
				$sectorID = $dbRecord->getInt('sector_id');
84
				$sectors[$sectorID] = self::getSector($gameID, $sectorID, $forceUpdate, $dbRecord);
85
			}
86
			self::$CACHE_LOCATION_SECTORS[$gameID][$locationTypeID] = $sectors;
87
		}
88
		return self::$CACHE_LOCATION_SECTORS[$gameID][$locationTypeID];
89
	}
90
91
	public static function getSector(int $gameID, int $sectorID, bool $forceUpdate = false, DatabaseRecord $dbRecord = null): self {
92
		if (!isset(self::$CACHE_SECTORS[$gameID][$sectorID]) || $forceUpdate) {
93
			self::$CACHE_SECTORS[$gameID][$sectorID] = new self($gameID, $sectorID, false, $dbRecord);
94
		}
95
		return self::$CACHE_SECTORS[$gameID][$sectorID];
96
	}
97
98
	public static function clearCache(): void {
99
		self::$CACHE_LOCATION_SECTORS = [];
100
		self::$CACHE_GALAXY_SECTORS = [];
101
		self::$CACHE_SECTORS = [];
102
	}
103
104
	public static function saveSectors(): void {
105
		foreach (self::$CACHE_SECTORS as $gameSectors) {
106
			foreach ($gameSectors as $sector) {
107
				$sector->update();
108
			}
109
		}
110
	}
111
112
	public static function createSector(int $gameID, int $sectorID): self {
113
		if (!isset(self::$CACHE_SECTORS[$gameID][$sectorID])) {
114
			$s = new self($gameID, $sectorID, true);
115
			self::$CACHE_SECTORS[$gameID][$sectorID] = $s;
116
		}
117
		return self::$CACHE_SECTORS[$gameID][$sectorID];
118
	}
119
120
	protected function __construct(
121
		protected readonly int $gameID,
122
		protected readonly int $sectorID,
123
		bool $create = false,
124
		DatabaseRecord $dbRecord = null
125
	) {
126
		$this->db = Database::getInstance();
127
		$this->SQL = 'game_id = ' . $this->db->escapeNumber($gameID) . ' AND sector_id = ' . $this->db->escapeNumber($sectorID);
0 ignored issues
show
Bug introduced by
The property SQL is declared read-only in SmrSector.
Loading history...
128
129
		// Do we already have a database record for this sector?
130
		if ($dbRecord === null) {
131
			$dbResult = $this->db->read('SELECT * FROM sector WHERE ' . $this->SQL);
132
			if ($dbResult->hasRecord()) {
133
				$dbRecord = $dbResult->record();
134
			}
135
		}
136
		$sectorExists = $dbRecord !== null;
137
138
		if ($sectorExists) {
139
			$this->galaxyID = $dbRecord->getInt('galaxy_id');
140
			$this->battles = $dbRecord->getInt('battles');
141
142
			foreach (self::LINK_DIR_MAPPING as $dir => $dbColumn) {
143
				$link = $dbRecord->getInt($dbColumn);
144
				if ($link !== 0) {
145
					$this->links[$dir] = $link;
146
				}
147
			}
148
			$this->warp = $dbRecord->getInt('warp');
149
		} elseif ($create) {
150
			$this->battles = 0;
151
			$this->warp = 0;
152
			$this->isNew = true;
153
		} else {
154
			throw new SectorNotFound('No sector ' . $sectorID . ' in game ' . $gameID);
155
		}
156
	}
157
158
	public function update(): void {
159
		if ($this->isNew) {
160
			$this->db->insert('sector', [
161
				'sector_id' => $this->db->escapeNumber($this->getSectorID()),
162
				'game_id' => $this->db->escapeNumber($this->getGameID()),
163
				'galaxy_id' => $this->db->escapeNumber($this->getGalaxyID()),
164
				'link_up' => $this->db->escapeNumber($this->getLinkUp()),
165
				'link_down' => $this->db->escapeNumber($this->getLinkDown()),
166
				'link_left' => $this->db->escapeNumber($this->getLinkLeft()),
167
				'link_right' => $this->db->escapeNumber($this->getLinkRight()),
168
				'warp' => $this->db->escapeNumber($this->getWarp()),
169
			]);
170
		} elseif ($this->hasChanged) {
171
			$this->db->write('UPDATE sector SET battles = ' . $this->db->escapeNumber($this->getBattles()) .
172
									', galaxy_id=' . $this->db->escapeNumber($this->getGalaxyID()) .
173
									', link_up=' . $this->db->escapeNumber($this->getLinkUp()) .
174
									', link_right=' . $this->db->escapeNumber($this->getLinkRight()) .
175
									', link_down=' . $this->db->escapeNumber($this->getLinkDown()) .
176
									', link_left=' . $this->db->escapeNumber($this->getLinkLeft()) .
177
									', warp=' . $this->db->escapeNumber($this->getWarp()) .
178
								' WHERE ' . $this->SQL);
179
		}
180
		$this->isNew = false;
181
		$this->hasChanged = false;
182
	}
183
184
	public function markVisited(AbstractSmrPlayer $player): void {
185
		if ($this->hasPort()) {
186
			$this->getPort()->addCachePort($player->getAccountID());
187
		}
188
189
		//now delete the entry from visited
190
		if (!$this->isVisited($player)) {
191
			$this->db->write('DELETE FROM player_visited_sector WHERE ' . $this->SQL . '
192
								 AND account_id = ' . $this->db->escapeNumber($player->getAccountID()));
193
		}
194
		$this->visited[$player->getAccountID()] = true;
195
	}
196
197
	public function offersFederalProtection(): bool {
198
		foreach ($this->getLocations() as $location) {
199
			if ($location->isFed()) {
200
				return true;
201
			}
202
		}
203
		return false;
204
	}
205
206
	/**
207
	 * @return array<int, int>
208
	 */
209
	public function getFedRaceIDs(): array {
210
		$raceIDs = [];
211
		foreach ($this->getLocations() as $location) {
212
			if ($location->isFed()) {
213
				$raceIDs[$location->getRaceID()] = $location->getRaceID();
214
			}
215
		}
216
		return $raceIDs;
217
	}
218
219
	public function enteringSector(AbstractSmrPlayer $player, MovementType $movementType): void {
220
		// send scout messages to user
221
		$message = 'Your forces have spotted ' . $player->getBBLink() . ' ';
222
		$message .= match ($movementType) {
223
			MovementType::Jump => 'jumping into',
224
			MovementType::Warp => 'warping into',
225
			MovementType::Walk => 'entering',
226
		};
227
		$message .= ' sector ' . Globals::getSectorBBLink($this->getSectorID());
228
229
		$forces = $this->getForces();
230
		foreach ($forces as $force) {
231
			$force->ping($message, $player);
232
		}
233
	}
234
235
	public function leavingSector(AbstractSmrPlayer $player, MovementType $movementType): void {
236
		// send scout messages to user
237
		$message = 'Your forces have spotted ' . $player->getBBLink() . ' ';
238
		$message .= match ($movementType) {
239
			MovementType::Jump => 'jumping from',
240
			MovementType::Warp => 'warping from',
241
			MovementType::Walk => 'leaving',
242
		};
243
		$message .= ' sector ' . Globals::getSectorBBLink($this->getSectorID());
244
245
		// iterate over all scout drones in sector
246
		foreach ($this->getForces() as $force) {
247
			$force->ping($message, $player);
248
		}
249
		$this->db->write('UPDATE sector_has_forces SET refresher = 0 WHERE ' . $this->SQL . '
250
								AND refresher = ' . $this->db->escapeNumber($player->getAccountID()));
251
	}
252
253
	public function diedHere(AbstractSmrPlayer $player): void {
254
		// iterate over all scout drones in sector
255
		foreach ($this->getForces() as $force) {
256
			// send scout messages to user
257
			$message = 'Your forces have spotted that ' . $player->getBBLink() . ' has been <span class="red">DESTROYED</span> in sector ' . Globals::getSectorBBLink($this->sectorID);
258
			$force->ping($message, $player);
259
		}
260
	}
261
262
	public function getSQL(): string {
263
		return $this->SQL;
264
	}
265
266
	public function getGameID(): int {
267
		return $this->gameID;
268
	}
269
270
	public function getSectorID(): int {
271
		return $this->sectorID;
272
	}
273
274
	public function getGalaxyID(): int {
275
		return $this->galaxyID;
276
	}
277
278
	public function setGalaxyID(int $galaxyID): void {
279
		if (isset($this->galaxyID) && $this->galaxyID == $galaxyID) {
280
			return;
281
		}
282
		$this->galaxyID = $galaxyID;
283
		$this->hasChanged = true;
284
	}
285
286
	/**
287
	 * Returns the number of linked sectors (excluding warps)
288
	 */
289
	public function getNumberOfLinks(): int {
290
		return count($this->links);
291
	}
292
293
	/**
294
	 * Returns the number of linked sectors (including warps)
295
	 */
296
	public function getNumberOfConnections(): int {
297
		$links = $this->getNumberOfLinks();
298
		if ($this->hasWarp()) {
299
			$links++;
300
		}
301
		return $links;
302
	}
303
304
	public function getGalaxy(): SmrGalaxy {
305
		return SmrGalaxy::getGalaxy($this->getGameID(), $this->getGalaxyID());
306
	}
307
308
	public function getNeighbourID(string $dir): int {
309
		if ($this->hasLink($dir)) {
310
			return $this->getLink($dir);
311
		}
312
		$galaxy = $this->getGalaxy();
313
		$neighbour = $this->getSectorID();
314
		switch ($dir) {
315
			case 'Up':
316
				$neighbour -= $galaxy->getWidth();
317
				if ($neighbour < $galaxy->getStartSector()) {
318
					$neighbour += $galaxy->getSize();
319
				}
320
				break;
321
			case 'Down':
322
				$neighbour += $galaxy->getWidth();
323
				if ($neighbour > $galaxy->getEndSector()) {
324
					$neighbour -= $galaxy->getSize();
325
				}
326
				break;
327
			case 'Left':
328
				$neighbour -= 1;
329
				if ((1 + $neighbour - $galaxy->getStartSector()) % $galaxy->getWidth() == 0) {
330
					$neighbour += $galaxy->getWidth();
331
				}
332
				break;
333
			case 'Right':
334
				$neighbour += 1;
335
				if (($neighbour - $galaxy->getStartSector()) % $galaxy->getWidth() == 0) {
336
					$neighbour -= $galaxy->getWidth();
337
				}
338
				break;
339
			default:
340
				throw new Exception($dir . ': is not a valid direction');
341
		}
342
		return $neighbour;
343
	}
344
345
	public function getSectorDirection(int $sectorID): string {
346
		if ($sectorID == $this->getSectorID()) {
347
			return 'Current';
348
		}
349
		$dir = array_search($sectorID, $this->getLinks());
350
		if ($dir !== false) {
351
			return $dir;
352
		}
353
		if ($sectorID == $this->getWarp()) {
354
			return 'Warp';
355
		}
356
		return 'None';
357
	}
358
359
	public function getNeighbourSector(string $dir): self {
360
		return self::getSector($this->getGameID(), $this->getNeighbourID($dir));
361
	}
362
363
	/**
364
	 * @return array<string, int>
365
	 */
366
	public function getLinks(): array {
367
		return $this->links;
368
	}
369
370
	public function isLinked(int $sectorID): bool {
371
		return in_array($sectorID, $this->links) || $sectorID == $this->getWarp();
372
	}
373
374
	public function getLink(string $name): int {
375
		return $this->links[$name] ?? 0;
376
	}
377
378
	public function hasLink(string $name): bool {
379
		return $this->getLink($name) != 0;
380
	}
381
382
	public function getLinkSector(string $name): self {
383
		return self::getSector($this->getGameID(), $this->getLink($name));
384
	}
385
386
	/**
387
	 * Cannot be used for Warps
388
	 */
389
	public function setLink(string $name, int $linkID): void {
390
		if ($this->getLink($name) == $linkID) {
391
			return;
392
		}
393
		if ($linkID == $this->sectorID) {
394
			throw new Exception('Sector must not link to itself!');
395
		}
396
		if ($linkID == 0) {
397
			unset($this->links[$name]);
398
		} else {
399
			$this->links[$name] = $linkID;
400
		}
401
		$this->hasChanged = true;
402
	}
403
404
	/**
405
	 * Cannot be used for Warps
406
	 */
407
	public function setLinkSector(string $dir, SmrSector $linkSector): void {
408
		$this->setLink($dir, $linkSector->getSectorID());
409
		$linkSector->setLink(self::oppositeDir($dir), $this->getSectorID());
410
	}
411
412
	/**
413
	 * Cannot be used for Warps
414
	 */
415
	public function enableLink(string $dir): void {
416
		$linkSector = $this->getNeighbourSector($dir);
417
		// Handle single width/height galaxies (don't link sector to itself)
418
		if (!$this->equals($linkSector)) {
419
			$this->setLinkSector($dir, $linkSector);
420
		}
421
	}
422
423
	/**
424
	 * Cannot be used for Warps
425
	 */
426
	public function disableLink(string $dir): void {
427
		$this->setLink($dir, 0);
428
		$this->getNeighbourSector($dir)->setLink(self::oppositeDir($dir), 0);
429
	}
430
431
	/**
432
	 * Cannot be used for Warps
433
	 */
434
	public function toggleLink(string $dir): void {
435
		if ($this->hasLink($dir)) {
436
			$this->disableLink($dir);
437
		} else {
438
			$this->enableLink($dir);
439
		}
440
	}
441
442
	public static function oppositeDir(string $dir): string {
443
		return match ($dir) {
444
			'Up' => 'Down',
445
			'Down' => 'Up',
446
			'Left' => 'Right',
447
			'Right' => 'Left',
448
			default => throw new Exception('Invalid direction: ' . $dir),
449
		};
450
	}
451
452
	public function getLinkUp(): int {
453
		return $this->getLink('Up');
454
	}
455
456
	public function setLinkUp(int $linkID): void {
457
		$this->setLink('Up', $linkID);
458
	}
459
460
	public function hasLinkUp(): bool {
461
		return $this->hasLink('Up');
462
	}
463
464
	public function getLinkDown(): int {
465
		return $this->getLink('Down');
466
	}
467
468
	public function setLinkDown(int $linkID): void {
469
		$this->setLink('Down', $linkID);
470
	}
471
472
	public function hasLinkDown(): bool {
473
		return $this->hasLink('Down');
474
	}
475
476
	public function getLinkLeft(): int {
477
		return $this->getLink('Left');
478
	}
479
480
	public function hasLinkLeft(): bool {
481
		return $this->hasLink('Left');
482
	}
483
484
	public function setLinkLeft(int $linkID): void {
485
		$this->setLink('Left', $linkID);
486
	}
487
488
	public function getLinkRight(): int {
489
		return $this->getLink('Right');
490
	}
491
492
	public function hasLinkRight(): bool {
493
		return $this->hasLink('Right');
494
	}
495
496
	public function setLinkRight(int $linkID): void {
497
		$this->setLink('Right', $linkID);
498
	}
499
500
	/**
501
	 * Returns the warp sector if the sector has a warp; returns 0 otherwise.
502
	 */
503
	public function getWarp(): int {
504
		return $this->warp;
505
	}
506
507
	public function getWarpSector(): self {
508
		return self::getSector($this->getGameID(), $this->getWarp());
509
	}
510
511
	public function hasWarp(): bool {
512
		return $this->getWarp() != 0;
513
	}
514
515
	/**
516
	 * Set the warp sector for both $this and $warp to ensure
517
	 * a consistent 2-way warp.
518
	 */
519
	public function setWarp(SmrSector $warp): void {
520
		if ($this->getWarp() == $warp->getSectorID() &&
521
		    $warp->getWarp() == $this->getSectorID()) {
522
			// Warps are already set correctly!
523
			return;
524
		}
525
526
		if ($this->equals($warp)) {
527
			throw new Exception('Sector must not warp to itself!');
528
		}
529
530
		// Can only have 1 warp per sector
531
		foreach ([[$warp, $this], [$this, $warp]] as [$A, $B]) {
532
			if ($A->hasWarp() && $A->getWarp() != $B->getSectorID()) {
533
				throw new Exception('Sector ' . $A->getSectorID() . ' already has a warp (to ' . $A->getWarp() . ')!');
534
			}
535
		}
536
537
		$this->warp = $warp->getSectorID();
538
		$this->hasChanged = true;
539
540
		if ($warp->getWarp() != $this->getSectorID()) {
541
			// Set the other side if needed
542
			$warp->setWarp($this);
543
		}
544
	}
545
546
	/**
547
	 * Remove the warp sector for both sides of the warp.
548
	 */
549
	public function removeWarp(): void {
550
		if (!$this->hasWarp()) {
551
			return;
552
		}
553
554
		$warp = $this->getWarpSector();
555
		if ($warp->hasWarp() && $warp->getWarp() != $this->getSectorID()) {
556
			throw new Exception('Warp sectors do not match');
557
		}
558
559
		$this->warp = 0;
560
		$this->hasChanged = true;
561
562
		if ($warp->hasWarp()) {
563
			$warp->removeWarp();
564
		}
565
	}
566
567
	public function hasPort(): bool {
568
		return $this->getPort()->exists();
569
	}
570
571
	public function getPort(): SmrPort {
572
		return SmrPort::getPort($this->getGameID(), $this->getSectorID());
573
	}
574
575
	public function createPort(): SmrPort {
576
		return SmrPort::createPort($this->getGameID(), $this->getSectorID());
577
	}
578
579
	public function removePort(): void {
580
		SmrPort::removePort($this->getGameID(), $this->getSectorID());
581
	}
582
583
	public function hasCachedPort(AbstractSmrPlayer $player = null): bool {
584
		if ($player === null) {
585
			return false;
586
		}
587
		try {
588
			$this->getCachedPort($player);
589
			return true;
590
		} catch (CachedPortNotFound) {
591
			return false;
592
		}
593
	}
594
595
	public function getCachedPort(AbstractSmrPlayer $player): SmrPort {
596
		return SmrPort::getCachedPort($this->getGameID(), $this->getSectorID(), $player->getAccountID());
597
	}
598
599
	public function hasAnyLocationsWithAction(): bool {
600
		$locations = SmrLocation::getSectorLocations($this->getGameID(), $this->getSectorID());
601
		$hasAction = false;
602
		foreach ($locations as $location) {
603
			if ($location->hasAction()) {
604
				$hasAction = true;
605
			}
606
		}
607
		return $hasAction;
608
	}
609
610
	public function hasLocation(int $locationTypeID = null): bool {
611
		$locations = $this->getLocations();
612
		if (count($locations) == 0) {
613
			return false;
614
		}
615
		if ($locationTypeID === null) {
616
			return true;
617
		}
618
		foreach ($locations as $location) {
619
			if ($location->getTypeID() == $locationTypeID) {
620
				return true;
621
			}
622
		}
623
		return false;
624
	}
625
626
	/**
627
	 * @return array<int, SmrLocation>
628
	 */
629
	public function getLocations(): array {
630
		return SmrLocation::getSectorLocations($this->getGameID(), $this->getSectorID());
631
	}
632
633
	public function addLocation(SmrLocation $location): void {
634
		SmrLocation::addSectorLocation($this->getGameID(), $this->getSectorID(), $location);
635
	}
636
637
	public function removeAllLocations(): void {
638
		SmrLocation::removeSectorLocations($this->getGameID(), $this->getSectorID());
639
	}
640
641
	public function hasPlanet(): bool {
642
		return $this->getPlanet()->exists();
643
	}
644
645
	public function getPlanet(): SmrPlanet {
646
		return SmrPlanet::getPlanet($this->getGameID(), $this->getSectorID());
647
	}
648
649
	public function createPlanet(int $type = 1): SmrPlanet {
650
		return SmrPlanet::createPlanet($this->getGameID(), $this->getSectorID(), $type);
651
	}
652
653
	public function removePlanet(): void {
654
		SmrPlanet::removePlanet($this->getGameID(), $this->getSectorID());
655
	}
656
657
	/**
658
	 * Removes ports, planets, locations, and warps from this sector.
659
	 * NOTE: This should only be used by the universe generator!
660
	 */
661
	public function removeAllFixtures(): void {
662
		if ($this->hasPort()) {
663
			$this->removePort();
664
		}
665
		if ($this->hasPlanet()) {
666
			$this->removePlanet();
667
		}
668
		if ($this->hasLocation()) {
669
			$this->removeAllLocations();
670
		}
671
		if ($this->hasWarp()) {
672
			$this->removeWarp();
673
		}
674
	}
675
676
	public function hasForces(): bool {
677
		return count($this->getForces()) > 0;
678
	}
679
680
	public function hasEnemyForces(AbstractSmrPlayer $player = null): bool {
681
		if ($player == null || !$this->hasForces()) {
682
			return false;
683
		}
684
		foreach ($this->getForces() as $force) {
685
			if (!$player->forceNAPAlliance($force->getOwner())) {
686
				return true;
687
			}
688
		}
689
		return false;
690
	}
691
692
	/**
693
	 * @return array<SmrForce>
694
	 */
695
	public function getEnemyForces(AbstractSmrPlayer $player): array {
696
		$enemyForces = [];
697
		foreach ($this->getForces() as $force) {
698
			if (!$player->forceNAPAlliance($force->getOwner())) {
699
				$enemyForces[] = $force;
700
			}
701
		}
702
		return $enemyForces;
703
	}
704
705
	/**
706
	 * Returns true if any forces in this sector belong to $player.
707
	 */
708
	public function hasPlayerForces(AbstractSmrPlayer $player): bool {
709
		foreach ($this->getForces() as $force) {
710
			if ($player->getAccountID() == $force->getOwnerID()) {
711
				return true;
712
			}
713
		}
714
		return false;
715
	}
716
717
	public function hasFriendlyForces(AbstractSmrPlayer $player = null): bool {
718
		if ($player == null || !$this->hasForces()) {
719
			return false;
720
		}
721
		foreach ($this->getForces() as $force) {
722
			if ($player->forceNAPAlliance($force->getOwner())) {
723
				return true;
724
			}
725
		}
726
		return false;
727
	}
728
729
	/**
730
	 * @return array<SmrForce>
731
	 */
732
	public function getFriendlyForces(AbstractSmrPlayer $player): array {
733
		$friendlyForces = [];
734
		foreach ($this->getForces() as $force) {
735
			if ($player->forceNAPAlliance($force->getOwner())) {
736
				$friendlyForces[] = $force;
737
			}
738
		}
739
		return $friendlyForces;
740
	}
741
742
	/**
743
	 * @return array<int, SmrForce>
744
	 */
745
	public function getForces(): array {
746
		return SmrForce::getSectorForces($this->getGameID(), $this->getSectorID());
747
	}
748
749
	/**
750
	 * @return array<int, SmrPlayer>
751
	 */
752
	public function getPlayers(): array {
753
		return SmrPlayer::getSectorPlayers($this->getGameID(), $this->getSectorID());
754
	}
755
756
	public function hasPlayers(): bool {
757
		return count($this->getPlayers()) > 0;
758
	}
759
760
	/**
761
	 * @return array<int, SmrPlayer>
762
	 */
763
	public function getOtherTraders(AbstractSmrPlayer $player): array {
764
		$players = SmrPlayer::getSectorPlayers($this->getGameID(), $this->getSectorID()); //Do not use & because we unset something and only want that in what we return
765
		unset($players[$player->getAccountID()]);
766
		return $players;
767
	}
768
769
	public function hasOtherTraders(AbstractSmrPlayer $player): bool {
770
		return count($this->getOtherTraders($player)) > 0;
771
	}
772
773
	public function hasEnemyTraders(AbstractSmrPlayer $player = null): bool {
774
		if ($player == null || !$this->hasOtherTraders($player)) {
775
			return false;
776
		}
777
		$otherPlayers = $this->getOtherTraders($player);
778
		foreach ($otherPlayers as $otherPlayer) {
779
			if (!$player->traderNAPAlliance($otherPlayer)
780
				&& !$otherPlayer->hasNewbieTurns()
781
				&& !$otherPlayer->hasFederalProtection()) {
782
				return true;
783
			}
784
		}
785
		return false;
786
	}
787
788
	public function hasFriendlyTraders(AbstractSmrPlayer $player = null): bool {
789
		if ($player == null || !$this->hasOtherTraders($player)) {
790
			return false;
791
		}
792
		$otherPlayers = $this->getOtherTraders($player);
793
		foreach ($otherPlayers as $otherPlayer) {
794
			if ($player->traderNAPAlliance($otherPlayer)) {
795
				return true;
796
			}
797
		}
798
		return false;
799
	}
800
801
	/**
802
	 * Is the $player's alliance flagship in this sector?
803
	 */
804
	public function hasAllianceFlagship(AbstractSmrPlayer $player = null): bool {
805
		if ($player === null || !$player->hasAlliance() || !$player->getAlliance()->hasFlagship()) {
806
			return false;
807
		}
808
		$flagshipID = $player->getAlliance()->getFlagshipID();
809
		foreach ($this->getPlayers() as $sectorPlayer) {
810
			if ($sectorPlayer->getAccountID() == $flagshipID) {
811
				return true;
812
			}
813
		}
814
		return false;
815
	}
816
817
	public function hasProtectedTraders(AbstractSmrPlayer $player = null): bool {
818
		if ($player == null || !$this->hasOtherTraders($player)) {
819
			return false;
820
		}
821
		$otherPlayers = $this->getOtherTraders($player);
822
		foreach ($otherPlayers as $otherPlayer) {
823
			if (!$player->traderNAPAlliance($otherPlayer)
824
				&& ($otherPlayer->hasNewbieTurns() || $otherPlayer->hasFederalProtection())) {
825
				return true;
826
			}
827
		}
828
		return false;
829
	}
830
831
	/**
832
	 * @return array<AbstractSmrPlayer>
833
	 */
834
	public function getFightingTradersAgainstForces(AbstractSmrPlayer $attackingPlayer, bool $bump): array {
0 ignored issues
show
Unused Code introduced by
The parameter $bump 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

834
	public function getFightingTradersAgainstForces(AbstractSmrPlayer $attackingPlayer, /** @scrutinizer ignore-unused */ bool $bump): array {

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

Loading history...
835
		// Whether bumping or attacking, only the current player fires at forces
836
		return [$attackingPlayer];
837
	}
838
839
	/**
840
	 * @return array<int, AbstractSmrPlayer>
841
	 */
842
	public function getFightingTradersAgainstPort(AbstractSmrPlayer $attackingPlayer, SmrPort $defendingPort, bool $allEligible = false): array {
0 ignored issues
show
Unused Code introduced by
The parameter $defendingPort 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

842
	public function getFightingTradersAgainstPort(AbstractSmrPlayer $attackingPlayer, /** @scrutinizer ignore-unused */ SmrPort $defendingPort, bool $allEligible = false): array {

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

Loading history...
843
		$fightingPlayers = [];
844
		$alliancePlayers = SmrPlayer::getSectorPlayersByAlliances($this->getGameID(), $this->getSectorID(), [$attackingPlayer->getAllianceID()]);
845
		foreach ($alliancePlayers as $accountID => $player) {
846
			if ($player->canFight()) {
847
				if ($attackingPlayer->traderAttackPortAlliance($player)) {
848
					$fightingPlayers[$accountID] = $alliancePlayers[$accountID];
849
				}
850
			}
851
		}
852
		if ($allEligible) {
853
			return $fightingPlayers;
854
		}
855
		return self::limitFightingTraders($fightingPlayers, $attackingPlayer, MAXIMUM_PORT_FLEET_SIZE);
856
	}
857
858
	/**
859
	 * @return array<int, AbstractSmrPlayer>
860
	 */
861
	public function getFightingTradersAgainstPlanet(AbstractSmrPlayer $attackingPlayer, SmrPlanet $defendingPlanet, bool $allEligible = false): array {
862
		$fightingPlayers = [];
863
		$alliancePlayers = SmrPlayer::getSectorPlayersByAlliances($this->getGameID(), $this->getSectorID(), [$attackingPlayer->getAllianceID()]);
864
		if (count($alliancePlayers) > 0) {
865
			$planetOwner = $defendingPlanet->getOwner();
866
			foreach ($alliancePlayers as $accountID => $player) {
867
				if ($player->canFight()) {
868
					if ($attackingPlayer->traderAttackPlanetAlliance($player) && !$planetOwner->planetNAPAlliance($player)) {
869
						$fightingPlayers[$accountID] = $alliancePlayers[$accountID];
870
					}
871
				}
872
			}
873
		}
874
		if ($allEligible) {
875
			return $fightingPlayers;
876
		}
877
		return self::limitFightingTraders($fightingPlayers, $attackingPlayer, min($defendingPlanet->getMaxAttackers(), MAXIMUM_PLANET_FLEET_SIZE));
878
	}
879
880
	/**
881
	 * @return array<string, array<int, AbstractSmrPlayer>>
882
	 */
883
	public function getFightingTraders(AbstractSmrPlayer $attackingPlayer, AbstractSmrPlayer $defendingPlayer, bool $checkForCloak = false, bool $allEligible = false): array {
884
		if ($attackingPlayer->traderNAPAlliance($defendingPlayer)) {
885
			throw new Exception('These traders are NAPed.');
886
		}
887
		$fightingPlayers = ['Attackers' => [], 'Defenders' => []];
888
		$alliancePlayers = SmrPlayer::getSectorPlayersByAlliances($this->getGameID(), $this->getSectorID(), [$attackingPlayer->getAllianceID(), $defendingPlayer->getAllianceID()]);
889
		foreach ($alliancePlayers as $accountID => $player) {
890
			if ($player->canFight()) {
891
				if ($attackingPlayer->traderAttackTraderAlliance($player) && !$defendingPlayer->traderDefendTraderAlliance($player) && !$defendingPlayer->traderNAPAlliance($player)) {
892
					$fightingPlayers['Attackers'][$accountID] = $player;
893
				} elseif ($defendingPlayer->traderDefendTraderAlliance($player) && !$attackingPlayer->traderAttackTraderAlliance($player) && !$attackingPlayer->traderNAPAlliance($player) && ($checkForCloak === false || $attackingPlayer->canSee($player))) {
894
					$fightingPlayers['Defenders'][$accountID] = $player;
895
				}
896
			}
897
		}
898
		if ($allEligible) {
899
			return $fightingPlayers;
900
		}
901
		$fightingPlayers['Attackers'] = self::limitFightingTraders($fightingPlayers['Attackers'], $attackingPlayer, MAXIMUM_PVP_FLEET_SIZE);
902
		$fightingPlayers['Defenders'] = self::limitFightingTraders($fightingPlayers['Defenders'], $defendingPlayer, MAXIMUM_PVP_FLEET_SIZE);
903
		return $fightingPlayers;
904
	}
905
906
	/**
907
	 * @param array<int, AbstractSmrPlayer> $fightingPlayers
908
	 * @return array<int, AbstractSmrPlayer>
909
	 */
910
	public static function limitFightingTraders(array $fightingPlayers, AbstractSmrPlayer $keepPlayer, int $maximumFleetSize): array {
911
		// Cap fleets to the required size
912
		$fleet_size = count($fightingPlayers);
913
		if ($fleet_size > $maximumFleetSize) {
914
			// We use random key to stop the same people being capped all the time
915
			for ($j = 0; $j < $fleet_size - $maximumFleetSize; ++$j) {
916
				do {
917
					$key = array_rand($fightingPlayers);
918
				} while ($keepPlayer->equals($fightingPlayers[$key]));
919
				unset($fightingPlayers[$key]);
920
			}
921
		}
922
		return $fightingPlayers;
923
	}
924
925
	/**
926
	 * @return array<string, array<int, AbstractSmrPlayer>>
927
	 */
928
	public function getPotentialFightingTraders(AbstractSmrPlayer $attackingPlayer): array {
929
		$fightingPlayers = ['Attackers' => [], 'Defenders' => []];
930
		$alliancePlayers = SmrPlayer::getSectorPlayersByAlliances($this->getGameID(), $this->getSectorID(), [$attackingPlayer->getAllianceID()]);
931
		foreach ($alliancePlayers as $accountID => $player) {
932
			if ($player->canFight()) {
933
				if ($attackingPlayer->traderAttackTraderAlliance($player)) {
934
					$fightingPlayers['Attackers'][$accountID] = $player;
935
				}
936
			}
937
		}
938
		return $fightingPlayers;
939
	}
940
941
	public function getBattles(): int {
942
		return $this->battles;
943
	}
944
945
	public function setBattles(int $amount): void {
946
		if ($this->battles == $amount) {
947
			return;
948
		}
949
		$this->battles = $amount;
950
		$this->hasChanged = true;
951
	}
952
953
	public function decreaseBattles(int $amount): void {
954
		$this->setBattles($this->battles - $amount);
955
	}
956
957
	public function increaseBattles(int $amount): void {
958
		$this->setBattles($this->battles + $amount);
959
	}
960
961
	public function equals(SmrSector $otherSector): bool {
962
		return $otherSector->getSectorID() == $this->getSectorID() && $otherSector->getGameID() == $this->getGameID();
963
	}
964
965
	public function isLinkedSector(SmrSector $otherSector): bool {
966
		return $otherSector->getGameID() == $this->getGameID() && $this->isLinked($otherSector->getSectorID());
967
	}
968
969
	public function isVisited(AbstractSmrPlayer $player = null): bool {
970
		if ($player === null) {
971
			return true;
972
		}
973
		if (!isset($this->visited[$player->getAccountID()])) {
974
			$dbResult = $this->db->read('SELECT 1 FROM player_visited_sector WHERE ' . $this->SQL . ' AND account_id=' . $this->db->escapeNumber($player->getAccountID()));
975
			$this->visited[$player->getAccountID()] = !$dbResult->hasRecord();
976
		}
977
		return $this->visited[$player->getAccountID()];
978
	}
979
980
	public function getLocalMapMoveHREF(AbstractSmrPlayer $player): string {
981
		return Globals::getSectorMoveHREF($player, $this->getSectorID(), new LocalMap());
982
	}
983
984
	public function getCurrentSectorMoveHREF(AbstractSmrPlayer $player): string {
985
		return Globals::getCurrentSectorMoveHREF($player, $this->getSectorID());
986
	}
987
988
	public function getGalaxyMapHREF(): string {
989
		return '?sector_id=' . $this->getSectorID();
990
	}
991
992
	public function getSectorScanHREF(AbstractSmrPlayer $player): string {
993
		return Globals::getSectorScanHREF($player, $this->getSectorID());
994
	}
995
996
	public function hasX(mixed $x, AbstractSmrPlayer $player = null): bool {
997
		if ($x instanceof SmrSector) {
998
			return $this->equals($x);
999
		}
1000
		if ($x == 'Port') {
1001
			return $this->hasPort();
1002
		}
1003
		if ($x == 'Location') {
1004
			return $this->hasLocation();
1005
		}
1006
		if ($x instanceof SmrLocation) {
1007
			return $this->hasLocation($x->getTypeID());
1008
		}
1009
		if ($x instanceof SmrGalaxy) {
1010
			return $x->contains($this);
1011
		}
1012
1013
		if (is_array($x) && $x['Type'] == 'Good') { //Check if it's possible for port to have X, hacky but nice performance gains
1014
			if ($this->hasPort() && $this->getPort()->hasX($x)) {
1015
				return true;
1016
			}
1017
		}
1018
1019
		//Check if it's possible for location to have X, hacky but nice performance gains
1020
		if ($x instanceof SmrWeaponType || $x instanceof SmrShipType || $x instanceof HardwareType || (is_string($x) && ($x == 'Bank' || $x == 'Bar' || $x == 'Fed' || $x == 'SafeFed' || $x == 'HQ' || $x == 'UG' || $x == 'Hardware' || $x == 'Ship' || $x == 'Weapon'))) {
1021
			foreach ($this->getLocations() as $loc) {
1022
				if ($loc->hasX($x, $player)) {
1023
					return true;
1024
				}
1025
			}
1026
		}
1027
		return false;
1028
	}
1029
1030
}
1031