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

Completed
Push — master ( b7d7a4...31205a )
by Dan
30s queued 15s
created

AbstractSmrPlayer   F

Complexity

Total Complexity 651

Size/Duplication

Total Lines 3132
Duplicated Lines 0 %

Importance

Changes 6
Bugs 2 Features 0
Metric Value
eloc 1646
c 6
b 2
f 0
dl 0
loc 3132
rs 0.8
wmc 651

287 Methods

Rating   Name   Duplication   Size   Complexity  
A getNextLevelExperience() 0 6 2
A getMaxLevel() 0 2 1
A increaseExperience() 0 10 3
A increaseAssists() 0 6 2
A getPlanetPlayers() 0 12 4
A setZoom() 0 8 2
A getSharingPlayers() 0 26 5
A getPlayerByPlayerID() 0 7 2
A setForceDropMessages() 0 6 2
A getLevelID() 0 15 4
A sendMessageFromAllianceAmbassador() 0 7 2
A getBBLink() 0 2 1
A getNextLevelPercentRemaining() 0 2 1
A decreaseZoom() 0 5 2
A getPlayer() 0 5 3
A sendMessageFromRace() 0 7 2
A getAccount() 0 2 1
A getLastSectorID() 0 2 1
A setLastSectorID() 0 6 2
A getHome() 0 9 2
A getZoom() 0 2 1
A sendMessageFromPort() 0 5 1
A createPlayer() 0 28 3
A sendGlobalMessage() 0 22 4
A getSector() 0 2 1
A getDeaths() 0 2 1
A getDisplayName() 0 10 3
A refreshCache() 0 4 3
A getGameID() 0 2 1
A getSafeAttackRating() 0 2 1
A getSectorID() 0 2 1
A sendMessageFromFedClerk() 0 3 1
A getCustomShipName() 0 10 3
A isNPC() 0 2 1
A decreaseAlignment() 0 9 3
A getKills() 0 2 1
A getSectorPlayers() 0 12 4
A getNextLevelPercentAcquired() 0 5 2
A getThisLevelExperience() 0 3 1
A clearCache() 0 3 1
A savePlayers() 0 4 3
A getCredits() 0 2 1
A getScoutMessageGroupLimit() 0 7 4
C sendMessage() 0 55 14
A setSectorID() 0 16 2
A setNewbieTurns() 0 6 2
A getSectorPlayersByAlliances() 0 8 3
A __construct() 0 54 3
B doMessageSending() 0 48 7
A getAssists() 0 2 1
A increaseZoom() 0 5 2
A getGalaxyPlayers() 0 16 3
A getExperience() 0 2 1
A getLevelName() 0 6 2
A setGroupScoutMessages() 0 6 2
A setShipTypeID() 0 6 2
A setKills() 0 6 2
A getAttackColour() 0 2 1
A getPlayerID() 0 2 1
A increaseAlignment() 0 9 3
A getGame() 0 2 1
A isIgnoreGlobals() 0 2 1
A setCustomShipName() 0 3 1
A setIgnoreGlobals() 0 6 2
A getPlanet() 0 6 2
A sendMessageFromCasino() 0 7 2
A getNewbieTurns() 0 2 1
A getSectorPort() 0 2 1
A setCredits() 0 12 4
A getAccountID() 0 2 1
A hasCustomShipName() 0 2 1
A setBank() 0 12 4
A setDeaths() 0 6 2
A sendMessageToBox() 0 3 1
A increaseBank() 0 9 3
A setMessagesRead() 0 3 1
A getBank() 0 2 1
A sendMessageFromPlanet() 0 5 1
A setExperience() 0 16 4
A setPlayerName() 0 3 1
A getShip() 0 2 1
A updateNewbieStatus() 0 5 2
A getAlignment() 0 2 1
A getSectorPlanet() 0 2 1
A sendMessageFromAllianceCommand() 0 3 1
A increaseKills() 0 5 2
A isLandedOnPlanet() 0 2 1
A getGroupScoutMessages() 0 2 1
A setLandedOnPlanet() 0 6 2
A getAlliancePlayers() 0 12 4
A hasFederalProtection() 0 20 6
A getShipTypeID() 0 2 1
A setAttackColour() 0 6 2
A canFight() 0 5 4
A sendMessageFromOpAnnounce() 0 6 2
A decreaseBank() 0 9 3
A isDead() 0 2 1
A getPlayerName() 0 2 1
A increaseDeaths() 0 5 2
A canBeProtectedByRace() 0 14 4
A decreaseExperience() 0 10 3
A setAlignment() 0 6 2
A increaseCredits() 0 9 3
A getLinkedDisplayName() 0 6 2
A hasNewbieTurns() 0 2 1
A setDead() 0 6 2
A decreaseCredits() 0 9 3
A sendMessageFromAdmin() 0 7 2
A hasNewbieStatus() 0 2 1
A getPlayerByPlayerName() 0 7 2
A updateMission() 0 17 2
A getRaceName() 0 2 1
A traderNAPAlliance() 0 2 1
A getTurnsColor() 0 11 4
A getLastTurnUpdate() 0 2 1
A setRelations() 0 12 3
A isOnCouncil() 0 2 1
A getActiveMissions() 0 8 3
A getClaimableBounties() 0 12 2
A getRaceID() 0 2 1
A hasMission() 0 2 1
A isDisplayWeapons() 0 2 1
A giveStartingTurns() 0 4 1
A sameAlliance() 0 2 5
A getTimeUntilMaxTurns() 0 7 1
A getCurrentBountyAmount() 0 3 1
A getKillsRank() 0 2 1
A planetNAPAlliance() 0 2 1
A getExamineTraderHREF() 0 4 1
A markMissionsRead() 0 11 3
C actionTaken() 0 35 12
A canSee() 0 11 4
A joinAlliance() 0 21 4
A forceNAPAlliance() 0 2 1
A hasMilitaryPayment() 0 2 1
A setCombatDronesKamikazeOnMines() 0 6 2
A getAllianceBBLink() 0 2 2
A getAvailableMissions() 0 15 5
A rebuildMission() 0 20 4
A setDisplayWeapons() 0 6 2
A getMaxTurns() 0 2 1
A getTickers() 0 15 3
A hasTicker() 0 2 1
A isFlagship() 0 2 2
A getLeaveNewbieProtectionHREF() 0 2 1
A decreaseHOF() 0 8 3
A getMissions() 0 18 3
A setLastPort() 0 6 2
A giveTurns() 0 6 3
A increaseRelationsByTrade() 0 7 2
A setMilitaryPayment() 0 6 2
A getRelations() 0 12 3
A computeRanking() 0 13 1
A getTraderSearchHREF() 0 4 1
A deletePlottedCourse() 0 3 1
A shootPlanet() 0 2 1
A setNewbieWarning() 0 6 2
A getBounty() 0 5 2
A meetsAlignmentRestriction() 0 8 3
A update() 0 2 1
A getRelation() 0 3 1
A killPlayer() 0 41 3
A setPlottedCourse() 0 9 3
A getBounties() 0 3 1
A getHOF() 0 13 4
A getPureRelation() 0 3 1
A getToggleWeaponHidingHREF() 0 5 1
B claimMissionReward() 0 29 8
A getLastActive() 0 2 1
B getPlottedCourse() 0 28 8
A hasBounties() 0 2 1
A shootPort() 0 2 1
A setBountiesClaimable() 0 5 3
A traderDefendTraderAlliance() 0 2 1
A sendAllianceInvitation() 0 7 2
A decreaseCurrentBountySmrCredits() 0 5 2
A getLastPort() 0 2 1
A increaseCurrentBountyAmount() 0 5 2
A createBounty() 0 10 1
A updateLastCPLAction() 0 2 1
A getColouredRaceNameOrDefault() 0 6 2
A isPresident() 0 2 1
A canSeeAny() 0 7 3
C setHOF() 0 45 12
A getPureRelations() 0 3 1
A getMission() 0 6 2
A hasTickers() 0 2 1
A setRaceID() 0 6 2
A setBounty() 0 3 1
A getNextBountyID() 0 6 2
A killPlayerByForces() 0 36 2
A setTurns() 0 7 2
A getAttackTraderHREF() 0 2 1
A isPartOfCourse() 0 10 3
A updateLastNewsUpdate() 0 2 1
A deleteMission() 0 8 2
A setAllianceJoinable() 0 6 2
A shootPlayer() 0 2 1
A getDeathsRank() 0 2 1
A addDestinationButton() 0 23 5
A incrementAllianceVsKills() 0 3 1
A killPlayerByPort() 0 33 2
A getAllianceID() 0 2 1
A getStoredDestinations() 0 14 3
A getAllianceRole() 0 16 4
A getAllianceDisplayName() 0 5 2
A shootForces() 0 2 1
A hasBounty() 0 3 1
A getExperienceRank() 0 2 1
A setAllianceID() 0 11 4
A sharedForceAlliance() 0 2 1
A traderAttackTraderAlliance() 0 2 1
A hasPlottedCourse() 0 2 1
A getLastNewsUpdate() 0 2 1
A takeTurns() 0 17 3
A getBountyAmount() 0 3 1
A setPlayerNameByPlayer() 0 3 1
B leaveAlliance() 0 22 7
A setCurrentBountyAmount() 0 7 2
A addMission() 0 24 2
A getColouredRaceName() 0 2 1
A declineMission() 0 3 1
A isGPEditor() 0 2 1
A hasTurns() 0 2 1
A getTurns() 0 2 1
A traderAttackPortAlliance() 0 2 1
A setupMissionStep() 0 17 5
A isDisplayMissions() 0 2 1
A increaseCurrentBountySmrCredits() 0 5 2
A deleteDestinationButton() 0 17 5
A hasAlliance() 0 2 1
A increaseMilitaryPayment() 0 5 2
A getHOFData() 0 17 5
A getJumpInfo() 0 10 2
A setCurrentBountySmrCredits() 0 7 2
A getAlliance() 0 2 1
A setLastActive() 0 6 2
A getPureRelationsData() 0 11 4
A setLastCPLAction() 0 6 2
F killPlayerByPlayer() 0 150 19
A isForceDropMessages() 0 2 1
A isAllianceLeader() 0 2 1
A getGPWriter() 0 9 3
A setDisplayMissions() 0 6 2
A updateTurns() 0 15 3
A equals() 0 2 3
A getVisibleGoods() 0 9 3
A getPlanetKickHREF() 0 4 1
A shootPlayers() 0 2 1
A hasVisitedSector() 0 9 3
B save() 0 64 9
A getLastCPLAction() 0 2 1
A decreaseCurrentBountyAmount() 0 5 2
A getTurnsGained() 0 5 1
A decreaseRelationsByTrade() 0 3 1
A getAssistsRank() 0 2 1
A isCombatDronesKamikazeOnMines() 0 2 1
A __sleep() 0 2 1
A decreaseMilitaryPayment() 0 5 2
A getBountiesData() 0 13 3
A decreaseRelations() 0 9 3
A traderMAPAlliance() 0 2 2
A setLastNewsUpdate() 0 6 2
B moveDestinationButton() 0 27 9
A getAllianceJoinable() 0 2 1
A hasCurrentBounty() 0 8 4
A traderAttackForceAlliance() 0 2 1
A setLastTurnUpdate() 0 6 2
A getMilitaryPayment() 0 2 1
A getCurrentBounty() 0 8 4
A isNameChanged() 0 2 1
A getCurrentBountySmrCredits() 0 3 1
A killPlayerByPlanet() 0 36 2
A setBountyAmount() 0 4 1
A getTurnsLevel() 0 11 4
A increaseRelations() 0 9 3
A getAllianceRosterHREF() 0 2 1
A isDraftLeader() 0 9 3
A traderAttackPlanetAlliance() 0 2 1
A setNameChanged() 0 3 1
A getTicker() 0 6 2
A incrementAllianceVsDeaths() 0 3 1
A getNewbieWarning() 0 2 1
A increaseHOF() 0 8 3
A getHOFVis() 0 8 3
A saveHOF() 0 13 5
A doHOFSave() 0 16 6

How to fix   Complexity   

Complex Class

Complex classes like AbstractSmrPlayer 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 AbstractSmrPlayer, and based on these observations, apply Extract Interface, too.

1
<?php declare(strict_types=1);
2
require_once('missions.inc');
3
4
// Exception thrown when a player cannot be found in the database
5
class PlayerNotFoundException extends Exception {}
6
7
abstract class AbstractSmrPlayer {
8
9
	const TIME_FOR_FEDERAL_BOUNTY_ON_PR = 10800;
10
	const TIME_FOR_ALLIANCE_SWITCH = 0;
11
12
	const HOF_CHANGED = 1;
13
	const HOF_NEW = 2;
14
15
	protected static $CACHE_SECTOR_PLAYERS = array();
16
	protected static $CACHE_PLANET_PLAYERS = array();
17
	protected static $CACHE_ALLIANCE_PLAYERS = array();
18
	protected static $CACHE_PLAYERS = array();
19
20
	protected $db;
21
	protected $SQL;
22
23
	protected $accountID;
24
	protected $gameID;
25
	protected $playerName;
26
	protected $playerID;
27
	protected $sectorID;
28
	protected $lastSectorID;
29
	protected $newbieTurns;
30
	protected $dead;
31
	protected $npc;
32
	protected $newbieStatus;
33
	protected $newbieWarning;
34
	protected $landedOnPlanet;
35
	protected $lastActive;
36
	protected $raceID;
37
	protected $credits;
38
	protected $alignment;
39
	protected $experience;
40
	protected $level;
41
	protected $allianceID;
42
	protected $shipID;
43
	protected $kills;
44
	protected $deaths;
45
	protected $assists;
46
	protected $stats;
47
	protected $pureRelations;
48
	protected $relations;
49
	protected $militaryPayment;
50
	protected $bounties;
51
	protected $turns;
52
	protected $lastCPLAction;
53
	protected $missions;
54
55
	protected $tickers;
56
	protected $lastTurnUpdate;
57
	protected $lastNewsUpdate;
58
	protected $attackColour;
59
	protected $allianceJoinable;
60
	protected $lastPort;
61
	protected $bank;
62
	protected $zoom;
63
	protected $displayMissions;
64
	protected $displayWeapons;
65
	protected $ignoreGlobals;
66
	protected $plottedCourse;
67
	protected $plottedCourseFrom;
68
	protected $nameChanged;
69
	protected $combatDronesKamikazeOnMines;
70
	protected $customShipName;
71
	protected $storedDestinations;
72
73
	protected $visitedSectors;
74
	protected $allianceRoles = array(
75
		0 => 0
76
	);
77
78
	protected $draftLeader;
79
	protected $gpWriter;
80
	protected $HOF;
81
	protected static $HOFVis;
82
83
	protected $hasChanged = false;
84
	protected array $hasHOFChanged = [];
85
	protected static $hasHOFVisChanged = array();
86
	protected $hasBountyChanged = array();
87
88
	public static function refreshCache() {
89
		foreach (self::$CACHE_PLAYERS as $gameID => &$gamePlayers) {
90
			foreach ($gamePlayers as $accountID => &$player) {
91
				$player = self::getPlayer($accountID, $gameID, true);
92
			}
93
		}
94
	}
95
96
	public static function clearCache() {
97
		self::$CACHE_PLAYERS = array();
98
		self::$CACHE_SECTOR_PLAYERS = array();
99
	}
100
101
	public static function savePlayers() {
102
		foreach (self::$CACHE_PLAYERS as $gamePlayers) {
103
			foreach ($gamePlayers as $player) {
104
				$player->save();
105
			}
106
		}
107
	}
108
109
	public static function getSectorPlayersByAlliances($gameID, $sectorID, array $allianceIDs, $forceUpdate = false) {
110
		$players = self::getSectorPlayers($gameID, $sectorID, $forceUpdate); // Don't use & as we do an unset
111
		foreach ($players as $accountID => $player) {
112
			if (!in_array($player->getAllianceID(), $allianceIDs)) {
113
				unset($players[$accountID]);
114
			}
115
		}
116
		return $players;
117
	}
118
119
	/**
120
	 * Returns the same players as getSectorPlayers (e.g. not on planets),
121
	 * but for an entire galaxy rather than a single sector. This is useful
122
	 * for reducing the number of queries in galaxy-wide processing.
123
	 */
124
	public static function getGalaxyPlayers($gameID, $galaxyID, $forceUpdate = false) {
125
		$db = new SmrMySqlDatabase();
126
		$db->query('SELECT player.*, sector_id FROM sector LEFT JOIN player USING(game_id, sector_id) WHERE game_id = ' . $db->escapeNumber($gameID) . ' AND land_on_planet = ' . $db->escapeBoolean(false) . ' AND (last_cpl_action > ' . $db->escapeNumber(TIME - TIME_BEFORE_INACTIVE) . ' OR newbie_turns = 0) AND galaxy_id = ' . $db->escapeNumber($galaxyID));
127
		$galaxyPlayers = [];
128
		while ($db->nextRecord()) {
129
			$sectorID = $db->getInt('sector_id');
130
			if (!$db->hasField('account_id')) {
131
				self::$CACHE_SECTOR_PLAYERS[$gameID][$sectorID] = [];
132
			} else {
133
				$accountID = $db->getInt('account_id');
134
				$player = self::getPlayer($accountID, $gameID, $forceUpdate, $db);
135
				self::$CACHE_SECTOR_PLAYERS[$gameID][$sectorID][$accountID] = $player;
136
				$galaxyPlayers[$sectorID][$accountID] = $player;
137
			}
138
		}
139
		return $galaxyPlayers;
140
	}
141
142
	public static function getSectorPlayers($gameID, $sectorID, $forceUpdate = false) {
143
		if ($forceUpdate || !isset(self::$CACHE_SECTOR_PLAYERS[$gameID][$sectorID])) {
144
			$db = new SmrMySqlDatabase();
145
			$db->query('SELECT * FROM player WHERE sector_id = ' . $db->escapeNumber($sectorID) . ' AND game_id=' . $db->escapeNumber($gameID) . ' AND land_on_planet = ' . $db->escapeBoolean(false) . ' AND (last_cpl_action > ' . $db->escapeNumber(TIME - TIME_BEFORE_INACTIVE) . ' OR newbie_turns = 0) AND account_id NOT IN (' . $db->escapeArray(Globals::getHiddenPlayers()) . ') ORDER BY last_cpl_action DESC');
146
			$players = array();
147
			while ($db->nextRecord()) {
148
				$accountID = $db->getInt('account_id');
149
				$players[$accountID] = self::getPlayer($accountID, $gameID, $forceUpdate, $db);
150
			}
151
			self::$CACHE_SECTOR_PLAYERS[$gameID][$sectorID] = $players;
152
		}
153
		return self::$CACHE_SECTOR_PLAYERS[$gameID][$sectorID];
154
	}
155
156
	public static function getPlanetPlayers($gameID, $sectorID, $forceUpdate = false) {
157
		if ($forceUpdate || !isset(self::$CACHE_PLANET_PLAYERS[$gameID][$sectorID])) {
158
			$db = new SmrMySqlDatabase();
159
			$db->query('SELECT * FROM player WHERE sector_id = ' . $db->escapeNumber($sectorID) . ' AND game_id=' . $db->escapeNumber($gameID) . ' AND land_on_planet = ' . $db->escapeBoolean(true) . ' AND account_id NOT IN (' . $db->escapeArray(Globals::getHiddenPlayers()) . ') ORDER BY last_cpl_action DESC');
160
			$players = array();
161
			while ($db->nextRecord()) {
162
				$accountID = $db->getInt('account_id');
163
				$players[$accountID] = self::getPlayer($accountID, $gameID, $forceUpdate, $db);
164
			}
165
			self::$CACHE_PLANET_PLAYERS[$gameID][$sectorID] = $players;
166
		}
167
		return self::$CACHE_PLANET_PLAYERS[$gameID][$sectorID];
168
	}
169
170
	public static function getAlliancePlayers($gameID, $allianceID, $forceUpdate = false) {
171
		if ($forceUpdate || !isset(self::$CACHE_ALLIANCE_PLAYERS[$gameID][$allianceID])) {
172
			$db = new SmrMySqlDatabase();
173
			$db->query('SELECT * FROM player WHERE alliance_id = ' . $db->escapeNumber($allianceID) . ' AND game_id=' . $db->escapeNumber($gameID) . ' ORDER BY experience DESC');
174
			$players = array();
175
			while ($db->nextRecord()) {
176
				$accountID = $db->getInt('account_id');
177
				$players[$accountID] = self::getPlayer($accountID, $gameID, $forceUpdate, $db);
178
			}
179
			self::$CACHE_ALLIANCE_PLAYERS[$gameID][$allianceID] = $players;
180
		}
181
		return self::$CACHE_ALLIANCE_PLAYERS[$gameID][$allianceID];
182
	}
183
184
	public static function getPlayer($accountID, $gameID, $forceUpdate = false, $db = null) {
185
		if ($forceUpdate || !isset(self::$CACHE_PLAYERS[$gameID][$accountID])) {
186
			self::$CACHE_PLAYERS[$gameID][$accountID] = new SmrPlayer($gameID, $accountID, $db);
187
		}
188
		return self::$CACHE_PLAYERS[$gameID][$accountID];
189
	}
190
191
	public static function getPlayerByPlayerID($playerID, $gameID, $forceUpdate = false) {
192
		$db = new SmrMySqlDatabase();
193
		$db->query('SELECT * FROM player WHERE game_id = ' . $db->escapeNumber($gameID) . ' AND player_id = ' . $db->escapeNumber($playerID) . ' LIMIT 1');
194
		if ($db->nextRecord()) {
195
			return self::getPlayer($db->getInt('account_id'), $gameID, $forceUpdate, $db);
196
		}
197
		throw new PlayerNotFoundException('Player ID not found.');
198
	}
199
200
	public static function getPlayerByPlayerName($playerName, $gameID, $forceUpdate = false) {
201
		$db = new SmrMySqlDatabase();
202
		$db->query('SELECT * FROM player WHERE game_id = ' . $db->escapeNumber($gameID) . ' AND player_name = ' . $db->escapeString($playerName) . ' LIMIT 1');
203
		if ($db->nextRecord()) {
204
			return self::getPlayer($db->getInt('account_id'), $gameID, $forceUpdate, $db);
205
		}
206
		throw new PlayerNotFoundException('Player Name not found.');
207
	}
208
209
	protected function __construct($gameID, $accountID, $db = null) {
210
		$this->db = new SmrMySqlDatabase();
211
		$this->SQL = 'account_id = ' . $this->db->escapeNumber($accountID) . ' AND game_id = ' . $this->db->escapeNumber($gameID);
212
213
		if (isset($db)) {
214
			$playerExists = true;
215
		} else {
216
			$db = $this->db;
217
			$this->db->query('SELECT * FROM player WHERE ' . $this->SQL . ' LIMIT 1');
218
			$playerExists = $db->nextRecord();
219
		}
220
221
		if ($playerExists) {
222
			$this->accountID = (int)$accountID;
223
			$this->gameID = (int)$gameID;
224
			$this->playerName = $db->getField('player_name');
225
			$this->playerID = $db->getInt('player_id');
226
			$this->sectorID = $db->getInt('sector_id');
227
			$this->lastSectorID = $db->getInt('last_sector_id');
228
			$this->turns = $db->getInt('turns');
229
			$this->lastTurnUpdate = $db->getInt('last_turn_update');
230
			$this->newbieTurns = $db->getInt('newbie_turns');
231
			$this->lastNewsUpdate = $db->getInt('last_news_update');
232
			$this->attackColour = $db->getField('attack_warning');
233
			$this->dead = $db->getBoolean('dead');
234
			$this->npc = $db->getBoolean('npc');
235
			$this->newbieStatus = $db->getBoolean('newbie_status');
236
			$this->landedOnPlanet = $db->getBoolean('land_on_planet');
237
			$this->lastActive = $db->getInt('last_active');
238
			$this->lastCPLAction = $db->getInt('last_cpl_action');
239
			$this->raceID = $db->getInt('race_id');
240
			$this->credits = $db->getInt('credits');
241
			$this->experience = $db->getInt('experience');
242
			$this->alignment = $db->getInt('alignment');
243
			$this->militaryPayment = $db->getInt('military_payment');
244
			$this->allianceID = $db->getInt('alliance_id');
245
			$this->allianceJoinable = $db->getInt('alliance_join');
246
			$this->shipID = $db->getInt('ship_type_id');
247
			$this->kills = $db->getInt('kills');
248
			$this->deaths = $db->getInt('deaths');
249
			$this->assists = $db->getInt('assists');
250
			$this->lastPort = $db->getInt('last_port');
251
			$this->bank = $db->getInt('bank');
252
			$this->zoom = $db->getInt('zoom');
253
			$this->displayMissions = $db->getBoolean('display_missions');
254
			$this->displayWeapons = $db->getBoolean('display_weapons');
255
			$this->forceDropMessages = $db->getBoolean('force_drop_messages');
0 ignored issues
show
Bug Best Practice introduced by
The property forceDropMessages does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
256
			$this->groupScoutMessages = $db->getField('group_scout_messages');
0 ignored issues
show
Bug Best Practice introduced by
The property groupScoutMessages does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
257
			$this->ignoreGlobals = $db->getBoolean('ignore_globals');
258
			$this->newbieWarning = $db->getBoolean('newbie_warning');
259
			$this->nameChanged = $db->getBoolean('name_changed');
260
			$this->combatDronesKamikazeOnMines = $db->getBoolean('combat_drones_kamikaze_on_mines');
261
		} else {
262
			throw new PlayerNotFoundException('Invalid accountID: ' . $accountID . ' OR gameID:' . $gameID);
263
		}
264
	}
265
266
	/**
267
	 * Insert a new player into the database. Returns the new player object.
268
	 */
269
	public static function createPlayer($accountID, $gameID, $playerName, $raceID, $isNewbie, $npc=false) {
270
		// Put the player in a sector with an HQ
271
		$startSectorID = self::getHome($gameID, $raceID);
272
273
		$db = new SmrMySqlDatabase();
274
		$db->lockTable('player');
275
276
		// Player names must be unique within each game
277
		$db->query('SELECT 1 FROM player WHERE game_id = ' . $db->escapeNumber($gameID) . ' AND player_name = ' . $db->escapeString($playerName) . ' LIMIT 1');
278
		if ($db->nextRecord() > 0) {
279
			$db->unlock();
280
			create_error('The player name already exists.');
281
		}
282
283
		// get last registered player id in that game and increase by one.
284
		$db->query('SELECT MAX(player_id) FROM player WHERE game_id = ' . $db->escapeNumber($gameID));
285
		if ($db->nextRecord()) {
286
			$playerID = $db->getInt('MAX(player_id)') + 1;
287
		} else {
288
			$playerID = 1;
289
		}
290
291
		$db->query('INSERT INTO player (account_id, game_id, player_id, player_name, race_id, sector_id, last_cpl_action, last_active, npc, newbie_status)
292
					VALUES(' . $db->escapeNumber($accountID) . ', ' . $db->escapeNumber($gameID) . ', ' . $db->escapeNumber($playerID) . ', ' . $db->escapeString($playerName) . ', ' . $db->escapeNumber($raceID) . ', ' . $db->escapeNumber($startSectorID) . ', ' . $db->escapeNumber(TIME) . ', ' . $db->escapeNumber(TIME) . ',' . $db->escapeBoolean($npc) . ',' . $db->escapeBoolean($isNewbie) . ')');
293
294
		$db->unlock();
295
296
		return SmrPlayer::getPlayer($accountID, $gameID);
297
	}
298
299
	/**
300
	 * Get array of players whose info can be accessed by this player.
301
	 * Skips players who are not in the same alliance as this player.
302
	 */
303
	public function getSharingPlayers($forceUpdate = false) {
304
		$results = array($this);
305
306
		// Only return this player if not in an alliance
307
		if (!$this->hasAlliance()) {
308
			return $results;
309
		}
310
311
		// Get other players who are sharing info for this game.
312
		// NOTE: game_id=0 means that player shares info for all games.
313
		$this->db->query('SELECT from_account_id FROM account_shares_info WHERE to_account_id=' . $this->db->escapeNumber($this->getAccountID()) . ' AND (game_id=0 OR game_id=' . $this->db->escapeNumber($this->getGameID()) . ')');
314
		while ($this->db->nextRecord()) {
315
			try {
316
				$otherPlayer = SmrPlayer::getPlayer($this->db->getInt('from_account_id'),
317
				                                    $this->getGameID(), $forceUpdate);
318
			} catch (PlayerNotFoundException $e) {
319
				// Skip players that have not joined this game
320
				continue;
321
			}
322
323
			// players must be in the same alliance
324
			if ($this->sameAlliance($otherPlayer)) {
325
				$results[] = $otherPlayer;
326
			}
327
		}
328
		return $results;
329
	}
330
331
	public function getZoom() {
332
		return $this->zoom;
333
	}
334
335
	protected function setZoom($zoom) {
336
		// Set the zoom level between [1, 9]
337
		$zoom = max(1, min(9, $zoom));
338
		if ($this->zoom == $zoom) {
339
			return;
340
		}
341
		$this->zoom = $zoom;
342
		$this->hasChanged = true;
343
	}
344
345
	public function increaseZoom($zoom) {
346
		if ($zoom < 0) {
347
			throw new Exception('Trying to increase negative zoom.');
348
		}
349
		$this->setZoom($this->getZoom() + $zoom);
350
	}
351
352
	public function decreaseZoom($zoom) {
353
		if ($zoom < 0) {
354
			throw new Exception('Trying to decrease negative zoom.');
355
		}
356
		$this->setZoom($this->getZoom() - $zoom);
357
	}
358
359
	public function getAttackColour() {
360
		return $this->attackColour;
361
	}
362
363
	public function setAttackColour($colour) {
364
		if ($this->attackColour == $colour) {
365
			return;
366
		}
367
		$this->attackColour = $colour;
368
		$this->hasChanged = true;
369
	}
370
371
	public function isIgnoreGlobals() {
372
		return $this->ignoreGlobals;
373
	}
374
375
	public function setIgnoreGlobals($bool) {
376
		if ($this->ignoreGlobals == $bool) {
377
			return;
378
		}
379
		$this->ignoreGlobals = $bool;
380
		$this->hasChanged = true;
381
	}
382
383
	public function getAccount() {
384
		return SmrAccount::getAccount($this->getAccountID());
385
	}
386
387
	public function getAccountID() {
388
		return $this->accountID;
389
	}
390
391
	public function getGameID() {
392
		return $this->gameID;
393
	}
394
395
	public function getGame() {
396
		return SmrGame::getGame($this->gameID);
397
	}
398
399
	public function getNewbieTurns() {
400
		return $this->newbieTurns;
401
	}
402
403
	public function hasNewbieTurns() {
404
		return $this->getNewbieTurns() > 0;
405
	}
406
	public function setNewbieTurns($newbieTurns) {
407
		if ($this->newbieTurns == $newbieTurns) {
408
			return;
409
		}
410
		$this->newbieTurns = $newbieTurns;
411
		$this->hasChanged = true;
412
	}
413
414
	public function getShip($forceUpdate = false) {
415
		return SmrShip::getShip($this, $forceUpdate);
416
	}
417
418
	public function getShipTypeID() {
419
		return $this->shipID;
420
	}
421
422
	public function setShipTypeID($shipID) {
423
		if ($this->shipID == $shipID) {
424
			return;
425
		}
426
		$this->shipID = $shipID;
427
		$this->hasChanged = true;
428
	}
429
430
	public function hasCustomShipName() {
431
		return $this->getCustomShipName() !== false;
432
	}
433
434
	public function getCustomShipName() {
435
		if (!isset($this->customShipName)) {
436
			$this->db->query('SELECT * FROM ship_has_name WHERE ' . $this->SQL . ' LIMIT 1');
437
			if ($this->db->nextRecord()) {
438
				$this->customShipName = $this->db->getField('ship_name');
439
			} else {
440
				$this->customShipName = false;
441
			}
442
		}
443
		return $this->customShipName;
444
	}
445
446
	public function setCustomShipName(string $name) {
447
		$this->db->query('REPLACE INTO ship_has_name (game_id, account_id, ship_name)
448
			VALUES (' . $this->db->escapeNumber($this->getGameID()) . ', ' . $this->db->escapeNumber($this->getAccountID()) . ', ' . $this->db->escapeString($name) . ')');
449
	}
450
451
	/**
452
	 * Get planet owned by this player.
453
	 * Returns false if this player does not own a planet.
454
	 */
455
	public function getPlanet() {
456
		$this->db->query('SELECT * FROM planet WHERE game_id=' . $this->db->escapeNumber($this->getGameID()) . ' AND owner_id=' . $this->db->escapeNumber($this->getAccountID()));
457
		if ($this->db->nextRecord()) {
458
			return SmrPlanet::getPlanet($this->getGameID(), $this->db->getInt('sector_id'), false, $this->db);
459
		} else {
460
			return false;
461
		}
462
	}
463
464
	public function getSectorPlanet() {
465
		return SmrPlanet::getPlanet($this->getGameID(), $this->getSectorID());
466
	}
467
468
	public function getSectorPort() {
469
		return SmrPort::getPort($this->getGameID(), $this->getSectorID());
470
	}
471
472
	public function getSectorID() {
473
		return $this->sectorID;
474
	}
475
476
	public function getSector() {
477
		return SmrSector::getSector($this->getGameID(), $this->getSectorID());
478
	}
479
480
	public function setSectorID($sectorID) {
481
		if ($this->sectorID == $sectorID) {
482
			return;
483
		}
484
485
		$port = SmrPort::getPort($this->getGameID(), $this->getSectorID());
486
		$port->addCachePort($this->getAccountID()); //Add port of sector we were just in, to make sure it is left totally up to date.
487
488
		$this->setLastSectorID($this->getSectorID());
489
		$this->actionTaken('LeaveSector', array('Sector'=>$this->getSector()));
490
		$this->sectorID = $sectorID;
491
		$this->actionTaken('EnterSector', array('Sector'=>$this->getSector()));
492
		$this->hasChanged = true;
493
494
		$port = SmrPort::getPort($this->getGameID(), $sectorID);
495
		$port->addCachePort($this->getAccountID()); //Add the port of sector we are now in.
496
	}
497
498
	public function getLastSectorID() {
499
		return $this->lastSectorID;
500
	}
501
502
	public function setLastSectorID($lastSectorID) {
503
		if ($this->lastSectorID == $lastSectorID) {
504
			return;
505
		}
506
		$this->lastSectorID = $lastSectorID;
507
		$this->hasChanged = true;
508
	}
509
510
	static public function getHome($gameID, $raceID) {
511
		// get his home sector
512
		$hq_id = GOVERNMENT + $raceID;
513
		$raceHqSectors = SmrSector::getLocationSectors($gameID, $hq_id);
514
		if (!empty($raceHqSectors)) {
515
			// If race has multiple HQ's for some reason, use the first one
516
			return key($raceHqSectors);
517
		} else {
518
			return 1;
519
		}
520
	}
521
522
	public function isDead() {
523
		return $this->dead;
524
	}
525
526
	public function isNPC() {
527
		return $this->npc;
528
	}
529
530
	/**
531
	 * Does the player have Newbie status?
532
	 */
533
	public function hasNewbieStatus() {
534
		return $this->newbieStatus;
535
	}
536
537
	/**
538
	 * Update the player's newbie status if it has changed.
539
	 * This function queries the account, so use sparingly.
540
	 */
541
	public function updateNewbieStatus() {
542
		$accountNewbieStatus = !$this->getAccount()->isVeteran();
543
		if ($this->newbieStatus != $accountNewbieStatus) {
544
			$this->newbieStatus = $accountNewbieStatus;
545
			$this->hasChanged = true;
546
		}
547
	}
548
549
	/**
550
	 * Has this player been designated as the alliance flagship?
551
	 */
552
	public function isFlagship() {
553
		return $this->hasAlliance() && $this->getAlliance()->getFlagshipID() == $this->getAccountID();
554
	}
555
556
	public function isPresident() {
557
		return Council::getPresidentID($this->getGameID(), $this->getRaceID()) == $this->getAccountID();
558
	}
559
560
	public function isOnCouncil() {
561
		return Council::isOnCouncil($this->getGameID(), $this->getRaceID(), $this->getAccountID());
562
	}
563
564
	public function isDraftLeader() {
565
		if (!isset($this->draftLeader)) {
566
			$this->draftLeader = false;
567
			$this->db->query('SELECT 1 FROM draft_leaders WHERE ' . $this->SQL . ' LIMIT 1');
568
			if ($this->db->nextRecord()) {
569
				$this->draftLeader = true;
570
			}
571
		}
572
		return $this->draftLeader;
573
	}
574
575
	public function getGPWriter() {
576
		if (!isset($this->gpWriter)) {
577
			$this->gpWriter = false;
578
			$this->db->query('SELECT position FROM galactic_post_writer WHERE ' . $this->SQL);
579
			if ($this->db->nextRecord()) {
580
				$this->gpWriter = $this->db->getField('position');
581
			}
582
		}
583
		return $this->gpWriter;
584
	}
585
586
	public function isGPEditor() {
587
		return $this->getGPWriter() == 'editor';
588
	}
589
590
	public function isForceDropMessages() {
591
		return $this->forceDropMessages;
592
	}
593
594
	public function setForceDropMessages($bool) {
595
		if ($this->forceDropMessages == $bool) {
596
			return;
597
		}
598
		$this->forceDropMessages = $bool;
0 ignored issues
show
Bug Best Practice introduced by
The property forceDropMessages does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
599
		$this->hasChanged = true;
600
	}
601
602
	public function getScoutMessageGroupLimit() {
603
		if ($this->groupScoutMessages == 'ALWAYS') {
604
			return 0;
605
		} elseif ($this->groupScoutMessages == 'AUTO') {
606
			return MESSAGES_PER_PAGE;
607
		} elseif ($this->groupScoutMessages == 'NEVER') {
608
			return PHP_INT_MAX;
609
		}
610
	}
611
612
	public function getGroupScoutMessages() {
613
		return $this->groupScoutMessages;
614
	}
615
616
	public function setGroupScoutMessages($setting) {
617
		if ($this->groupScoutMessages == $setting) {
618
			return;
619
		}
620
		$this->groupScoutMessages = $setting;
0 ignored issues
show
Bug Best Practice introduced by
The property groupScoutMessages does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
621
		$this->hasChanged = true;
622
	}
623
624
	protected static function doMessageSending($senderID, $receiverID, $gameID, $messageTypeID, $message, $expires, $senderDelete = false, $unread = true) {
625
		$message = trim($message);
626
		$db = new SmrMySqlDatabase();
627
		// send him the message
628
		$db->query('INSERT INTO message
629
			(account_id,game_id,message_type_id,message_text,
630
			sender_id,send_time,expire_time,sender_delete) VALUES(' .
631
			$db->escapeNumber($receiverID) . ',' .
632
			$db->escapeNumber($gameID) . ',' .
633
			$db->escapeNumber($messageTypeID) . ',' .
634
			$db->escapeString($message) . ',' .
635
			$db->escapeNumber($senderID) . ',' .
636
			$db->escapeNumber(TIME) . ',' .
637
			$db->escapeNumber($expires) . ',' .
638
			$db->escapeBoolean($senderDelete) . ')'
639
		);
640
		// Keep track of the message_id so it can be returned
641
		$insertID = $db->getInsertID();
642
643
		if ($unread === true) {
644
			// give him the message icon
645
			$db->query('REPLACE INTO player_has_unread_messages (game_id, account_id, message_type_id) VALUES
646
						(' . $db->escapeNumber($gameID) . ', ' . $db->escapeNumber($receiverID) . ', ' . $db->escapeNumber($messageTypeID) . ')');
647
		}
648
649
		switch ($messageTypeID) {
650
			case MSG_PLAYER:
651
				$receiverAccount = SmrAccount::getAccount($receiverID);
652
				if ($receiverAccount->isValidated() && $receiverAccount->isReceivingMessageNotifications($messageTypeID) && !$receiverAccount->isLoggedIn()) {
653
					require_once(get_file_loc('message.functions.inc'));
654
					$sender = getMessagePlayer($senderID, $gameID, $messageTypeID);
655
					if ($sender instanceof SmrPlayer) {
656
						$sender = $sender->getDisplayName();
657
					}
658
					$mail = setupMailer();
659
					$mail->Subject = 'Message Notification';
660
					$mail->setFrom('[email protected]', 'SMR Notifications');
661
					$bbifiedMessage = 'From: ' . $sender . ' Date: ' . date($receiverAccount->getShortDateFormat() . ' ' . $receiverAccount->getShortTimeFormat(), TIME) . "<br/>\r\n<br/>\r\n" . bbifyMessage($message, true);
662
					$mail->msgHTML($bbifiedMessage);
663
					$mail->AltBody = strip_tags($bbifiedMessage);
664
					$mail->addAddress($receiverAccount->getEmail(), $receiverAccount->getHofName());
665
					$mail->send();
666
					$receiverAccount->decreaseMessageNotifications($messageTypeID, 1);
667
				}
668
			break;
669
		}
670
671
		return $insertID;
672
	}
673
674
	public function sendMessageToBox($boxTypeID, $message) {
675
		// send him the message
676
		SmrAccount::doMessageSendingToBox($this->getAccountID(), $boxTypeID, $message, $this->getGameID());
677
	}
678
679
	public function sendGlobalMessage($message, $canBeIgnored = true) {
680
		if ($canBeIgnored) {
681
			if ($this->getAccount()->isMailBanned()) {
682
				create_error('You are currently banned from sending messages');
683
			}
684
		}
685
		$this->sendMessageToBox(BOX_GLOBALS, $message);
686
687
		// send to all online player
688
		$db = new SmrMySqlDatabase();
689
		$db->query('SELECT account_id
690
					FROM active_session
691
					JOIN player USING (game_id, account_id)
692
					WHERE active_session.last_accessed >= ' . $db->escapeNumber(TIME - SmrSession::TIME_BEFORE_EXPIRY) . '
693
						AND game_id = ' . $db->escapeNumber($this->getGameID()) . '
694
						AND ignore_globals = \'FALSE\'
695
						AND account_id != ' . $db->escapeNumber($this->getAccountID()));
696
697
		while ($db->nextRecord()) {
698
			$this->sendMessage($db->getInt('account_id'), MSG_GLOBAL, $message, $canBeIgnored);
699
		}
700
		$this->sendMessage($this->getAccountID(), MSG_GLOBAL, $message, $canBeIgnored, false);
701
	}
702
703
	public function sendMessage($receiverID, $messageTypeID, $message, $canBeIgnored = true, $unread = true, $expires = false, $senderDelete = false) {
704
		//get expire time
705
		if ($canBeIgnored) {
706
			if ($this->getAccount()->isMailBanned()) {
707
				create_error('You are currently banned from sending messages');
708
			}
709
			// Don't send messages to players ignoring us
710
			$this->db->query('SELECT account_id FROM message_blacklist WHERE account_id=' . $this->db->escapeNumber($receiverID) . ' AND blacklisted_id=' . $this->db->escapeNumber($this->getAccountID()) . ' LIMIT 1');
711
			if ($this->db->nextRecord()) {
712
				return;
713
			}
714
		}
715
716
		$message = word_filter($message);
717
718
		// If expires not specified, use default based on message type
719
		if ($expires === false) {
720
			switch ($messageTypeID) {
721
				case MSG_GLOBAL: //We don't send globals to the box here or it gets done loads of times.
722
					$expires = 3600; // 1h
723
				break;
724
				case MSG_PLAYER:
725
					$expires = 86400 * 31;
726
				break;
727
				case MSG_PLANET:
728
					$expires = 86400 * 7;
729
				break;
730
				case MSG_SCOUT:
731
					$expires = 86400 * 3;
732
				break;
733
				case MSG_POLITICAL:
734
					$expires = 86400 * 31;
735
				break;
736
				case MSG_ALLIANCE:
737
					$expires = 86400 * 31;
738
				break;
739
				case MSG_ADMIN:
740
					$expires = 86400 * 365;
741
				break;
742
				case MSG_CASINO:
743
					$expires = 86400 * 31;
744
				break;
745
				default:
746
					$expires = 86400 * 7;
747
			}
748
			$expires += TIME;
749
		}
750
751
		// Do not put scout messages in the sender's sent box
752
		if ($messageTypeID == MSG_SCOUT) {
753
			$senderDelete = true;
754
		}
755
756
		// send him the message and return the message_id
757
		return self::doMessageSending($this->getAccountID(), $receiverID, $this->getGameID(), $messageTypeID, $message, $expires, $senderDelete, $unread);
758
	}
759
760
	public function sendMessageFromOpAnnounce($receiverID, $message, $expires = false) {
761
		// get expire time if not set
762
		if ($expires === false) {
763
			$expires = TIME + 86400 * 14;
764
		}
765
		self::doMessageSending(ACCOUNT_ID_OP_ANNOUNCE, $receiverID, $this->getGameID(), MSG_ALLIANCE, $message, $expires);
766
	}
767
768
	public function sendMessageFromAllianceCommand($receiverID, $message) {
769
		$expires = TIME + 86400 * 365;
770
		self::doMessageSending(ACCOUNT_ID_ALLIANCE_COMMAND, $receiverID, $this->getGameID(), MSG_PLAYER, $message, $expires);
771
	}
772
773
	public static function sendMessageFromPlanet($gameID, $receiverID, $message) {
774
		//get expire time
775
		$expires = TIME + 86400 * 31;
776
		// send him the message
777
		self::doMessageSending(ACCOUNT_ID_PLANET, $receiverID, $gameID, MSG_PLANET, $message, $expires);
778
	}
779
780
	public static function sendMessageFromPort($gameID, $receiverID, $message) {
781
		//get expire time
782
		$expires = TIME + 86400 * 31;
783
		// send him the message
784
		self::doMessageSending(ACCOUNT_ID_PORT, $receiverID, $gameID, MSG_PLAYER, $message, $expires);
785
	}
786
787
	public static function sendMessageFromFedClerk($gameID, $receiverID, $message) {
788
		$expires = TIME + 86400 * 365;
789
		self::doMessageSending(ACCOUNT_ID_FED_CLERK, $receiverID, $gameID, MSG_PLAYER, $message, $expires);
790
	}
791
792
	public static function sendMessageFromAdmin($gameID, $receiverID, $message, $expires = false) {
793
		//get expire time
794
		if ($expires === false) {
795
			$expires = TIME + 86400 * 365;
796
		}
797
		// send him the message
798
		self::doMessageSending(ACCOUNT_ID_ADMIN, $receiverID, $gameID, MSG_ADMIN, $message, $expires);
799
	}
800
801
	public static function sendMessageFromAllianceAmbassador($gameID, $receiverID, $message, $expires = false) {
802
		//get expire time
803
		if ($expires === false) {
804
			$expires = TIME + 86400 * 31;
805
		}
806
		// send him the message
807
		self::doMessageSending(ACCOUNT_ID_ALLIANCE_AMBASSADOR, $receiverID, $gameID, MSG_ALLIANCE, $message, $expires);
808
	}
809
810
	public static function sendMessageFromCasino($gameID, $receiverID, $message, $expires = false) {
811
		//get expire time
812
		if ($expires === false) {
813
			$expires = TIME + 86400 * 7;
814
		}
815
		// send him the message
816
		self::doMessageSending(ACCOUNT_ID_CASINO, $receiverID, $gameID, MSG_CASINO, $message, $expires);
817
	}
818
819
	public static function sendMessageFromRace($raceID, $gameID, $receiverID, $message, $expires = false) {
820
		//get expire time
821
		if ($expires === false) {
822
			$expires = TIME + 86400 * 5;
823
		}
824
		// send him the message
825
		self::doMessageSending(ACCOUNT_ID_GROUP_RACES + $raceID, $receiverID, $gameID, MSG_POLITICAL, $message, $expires);
826
	}
827
828
	public function setMessagesRead($messageTypeID) {
829
		$this->db->query('DELETE FROM player_has_unread_messages
830
							WHERE '.$this->SQL . ' AND message_type_id = ' . $this->db->escapeNumber($messageTypeID));
831
	}
832
833
	public function getSafeAttackRating() {
834
		return max(0, min(8, $this->getAlignment() / 150 + 4));
835
	}
836
837
	public function hasFederalProtection() {
838
		$sector = SmrSector::getSector($this->getGameID(), $this->getSectorID());
839
		if (!$sector->offersFederalProtection()) {
840
			return false;
841
		}
842
843
		$ship = $this->getShip();
844
		if ($ship->hasIllegalGoods()) {
845
			return false;
846
		}
847
848
		if ($ship->getAttackRating() <= $this->getSafeAttackRating()) {
849
			foreach ($sector->getFedRaceIDs() as $fedRaceID) {
850
				if ($this->canBeProtectedByRace($fedRaceID)) {
851
					return true;
852
				}
853
			}
854
		}
855
856
		return false;
857
	}
858
859
	public function canBeProtectedByRace($raceID) {
860
		if (!isset($this->canFed)) {
861
			$this->canFed = array();
0 ignored issues
show
Bug Best Practice introduced by
The property canFed does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
862
			$RACES = Globals::getRaces();
863
			foreach ($RACES as $raceID2 => $raceName) {
864
				$this->canFed[$raceID2] = $this->getRelation($raceID2) >= ALIGN_FED_PROTECTION;
865
			}
866
			$this->db->query('SELECT race_id, allowed FROM player_can_fed
867
								WHERE ' . $this->SQL . ' AND expiry > ' . $this->db->escapeNumber(TIME));
868
			while ($this->db->nextRecord()) {
869
				$this->canFed[$this->db->getInt('race_id')] = $this->db->getBoolean('allowed');
870
			}
871
		}
872
		return $this->canFed[$raceID];
873
	}
874
875
	/**
876
	 * Returns a boolean identifying if the player can currently
877
	 * participate in battles.
878
	 */
879
	public function canFight() {
880
		return !($this->hasNewbieTurns() ||
881
		         $this->isDead() ||
882
		         $this->isLandedOnPlanet() ||
883
		         $this->hasFederalProtection());
884
	}
885
886
	public function setDead($bool) {
887
		if ($this->dead == $bool) {
888
			return;
889
		}
890
		$this->dead = $bool;
891
		$this->hasChanged = true;
892
	}
893
894
	public function getKills() {
895
		return $this->kills;
896
	}
897
898
	public function increaseKills($kills) {
899
		if ($kills < 0) {
900
			throw new Exception('Trying to increase negative kills.');
901
		}
902
		$this->setKills($this->kills + $kills);
903
	}
904
905
	public function setKills($kills) {
906
		if ($this->kills == $kills) {
907
			return;
908
		}
909
		$this->kills = $kills;
910
		$this->hasChanged = true;
911
	}
912
913
	public function getDeaths() {
914
		return $this->deaths;
915
	}
916
917
	public function increaseDeaths($deaths) {
918
		if ($deaths < 0) {
919
			throw new Exception('Trying to increase negative deaths.');
920
		}
921
		$this->setDeaths($this->getDeaths() + $deaths);
922
	}
923
924
	public function setDeaths($deaths) {
925
		if ($this->deaths == $deaths) {
926
			return;
927
		}
928
		$this->deaths = $deaths;
929
		$this->hasChanged = true;
930
	}
931
932
	public function getAssists() {
933
		return $this->assists;
934
	}
935
936
	public function increaseAssists($assists) {
937
		if ($assists < 1) {
938
			throw new Exception('Must increase by a positive number.');
939
		}
940
		$this->assists += $assists;
941
		$this->hasChanged = true;
942
	}
943
944
	public function getAlignment() {
945
		return $this->alignment;
946
	}
947
948
	public function increaseAlignment($align) {
949
		if ($align < 0) {
950
			throw new Exception('Trying to increase negative align.');
951
		}
952
		if ($align == 0) {
953
			return;
954
		}
955
		$align += $this->alignment;
956
		$this->setAlignment($align);
957
	}
958
959
	public function decreaseAlignment($align) {
960
		if ($align < 0) {
961
			throw new Exception('Trying to decrease negative align.');
962
		}
963
		if ($align == 0) {
964
			return;
965
		}
966
		$align = $this->alignment - $align;
967
		$this->setAlignment($align);
968
	}
969
970
	public function setAlignment($align) {
971
		if ($this->alignment == $align) {
972
			return;
973
		}
974
		$this->alignment = $align;
975
		$this->hasChanged = true;
976
	}
977
978
	public function getCredits() {
979
		return $this->credits;
980
	}
981
982
	public function getBank() {
983
		return $this->bank;
984
	}
985
986
	public function increaseBank($credits) {
987
		if ($credits < 0) {
988
			throw new Exception('Trying to increase negative credits.');
989
		}
990
		if ($credits == 0) {
991
			return;
992
		}
993
		$credits += $this->bank;
994
		$this->setBank($credits);
995
	}
996
997
	public function decreaseBank($credits) {
998
		if ($credits < 0) {
999
			throw new Exception('Trying to decrease negative credits.');
1000
		}
1001
		if ($credits == 0) {
1002
			return;
1003
		}
1004
		$credits = $this->bank - $credits;
1005
		$this->setBank($credits);
1006
	}
1007
1008
	public function setBank($credits) {
1009
		if ($this->bank == $credits) {
1010
			return;
1011
		}
1012
		if ($credits < 0) {
1013
			throw new Exception('Trying to set negative credits.');
1014
		}
1015
		if ($credits > MAX_MONEY) {
1016
			throw new Exception('Trying to set more than max credits.');
1017
		}
1018
		$this->bank = $credits;
1019
		$this->hasChanged = true;
1020
	}
1021
1022
	public function getExperience() {
1023
		return $this->experience;
1024
	}
1025
1026
	/**
1027
	 * Returns the percent progress towards the next level.
1028
	 * This value is rounded because it is used primarily in HTML img widths.
1029
	 */
1030
	public function getNextLevelPercentAcquired() : int {
1031
		if ($this->getNextLevelExperience() == $this->getThisLevelExperience()) {
1032
			return 100;
1033
		}
1034
		return max(0, min(100, IRound(($this->getExperience() - $this->getThisLevelExperience()) / ($this->getNextLevelExperience() - $this->getThisLevelExperience()) * 100)));
1035
	}
1036
1037
	public function getNextLevelPercentRemaining() {
1038
		return 100 - $this->getNextLevelPercentAcquired();
1039
	}
1040
1041
	public function getNextLevelExperience() {
1042
		$LEVELS_REQUIREMENTS = Globals::getLevelRequirements();
1043
		if (!isset($LEVELS_REQUIREMENTS[$this->getLevelID() + 1])) {
1044
			return $this->getThisLevelExperience(); //Return current level experience if on last level.
1045
		}
1046
		return $LEVELS_REQUIREMENTS[$this->getLevelID() + 1]['Requirement'];
1047
	}
1048
1049
	public function getThisLevelExperience() {
1050
		$LEVELS_REQUIREMENTS = Globals::getLevelRequirements();
1051
		return $LEVELS_REQUIREMENTS[$this->getLevelID()]['Requirement'];
1052
	}
1053
1054
	public function setExperience($experience) {
1055
		if ($this->experience == $experience) {
1056
			return;
1057
		}
1058
		if ($experience < MIN_EXPERIENCE) {
1059
			$experience = MIN_EXPERIENCE;
1060
		}
1061
		if ($experience > MAX_EXPERIENCE) {
1062
			$experience = MAX_EXPERIENCE;
1063
		}
1064
		$this->experience = $experience;
1065
		$this->hasChanged = true;
1066
1067
		// Since exp has changed, invalidate the player level so that it can
1068
		// be recomputed next time it is queried (in case it has changed).
1069
		$this->level = null;
1070
	}
1071
1072
	public function increaseCredits($credits) {
1073
		if ($credits < 0) {
1074
			throw new Exception('Trying to increase negative credits.');
1075
		}
1076
		if ($credits == 0) {
1077
			return;
1078
		}
1079
		$credits += $this->credits;
1080
		$this->setCredits($credits);
1081
	}
1082
	public function decreaseCredits($credits) {
1083
		if ($credits < 0) {
1084
			throw new Exception('Trying to decrease negative credits.');
1085
		}
1086
		if ($credits == 0) {
1087
			return;
1088
		}
1089
		$credits = $this->credits - $credits;
1090
		$this->setCredits($credits);
1091
	}
1092
	public function setCredits($credits) {
1093
		if ($this->credits == $credits) {
1094
			return;
1095
		}
1096
		if ($credits < 0) {
1097
			throw new Exception('Trying to set negative credits.');
1098
		}
1099
		if ($credits > MAX_MONEY) {
1100
			$credits = MAX_MONEY;
1101
		}
1102
		$this->credits = $credits;
1103
		$this->hasChanged = true;
1104
	}
1105
1106
	public function increaseExperience($experience) {
1107
		if ($experience < 0) {
1108
			throw new Exception('Trying to increase negative experience.');
1109
		}
1110
		if ($experience == 0) {
1111
			return;
1112
		}
1113
		$newExperience = $this->experience + $experience;
1114
		$this->setExperience($newExperience);
1115
		$this->increaseHOF($experience, array('Experience', 'Total', 'Gain'), HOF_PUBLIC);
1116
	}
1117
	public function decreaseExperience($experience) {
1118
		if ($experience < 0) {
1119
			throw new Exception('Trying to decrease negative experience.');
1120
		}
1121
		if ($experience == 0) {
1122
			return;
1123
		}
1124
		$newExperience = $this->experience - $experience;
1125
		$this->setExperience($newExperience);
1126
		$this->increaseHOF($experience, array('Experience', 'Total', 'Loss'), HOF_PUBLIC);
1127
	}
1128
1129
	public function isLandedOnPlanet() {
1130
		return $this->landedOnPlanet;
1131
	}
1132
1133
	public function setLandedOnPlanet($bool) {
1134
		if ($this->landedOnPlanet == $bool) {
1135
			return;
1136
		}
1137
		$this->landedOnPlanet = $bool;
1138
		$this->hasChanged = true;
1139
	}
1140
1141
	/**
1142
	 * Returns the numerical level of the player (e.g. 1-50).
1143
	 */
1144
	public function getLevelID() {
1145
		// The level is cached for performance reasons unless `setExperience`
1146
		// is called and the player's experience changes.
1147
		if ($this->level === null) {
1148
			$LEVELS_REQUIREMENTS = Globals::getLevelRequirements();
1149
			foreach ($LEVELS_REQUIREMENTS as $level_id => $require) {
1150
				if ($this->getExperience() >= $require['Requirement']) {
1151
					continue;
1152
				}
1153
				$this->level = $level_id - 1;
1154
				return $this->level;
1155
			}
1156
			$this->level = max(array_keys($LEVELS_REQUIREMENTS));
1157
		}
1158
		return $this->level;
1159
	}
1160
1161
	public function getLevelName() {
1162
		$level_name = Globals::getLevelRequirements()[$this->getLevelID()]['Name'];
1163
		if ($this->isPresident()) {
1164
			$level_name = '<img src="images/council_president.png" title="' . Globals::getRaceName($this->getRaceID()) . ' President" height="12" width="16" />&nbsp;' . $level_name;
1165
		}
1166
		return $level_name;
1167
	}
1168
1169
	public function getMaxLevel() {
1170
		return max(array_keys(Globals::getLevelRequirements()));
1171
	}
1172
1173
	public function getPlayerID() {
1174
		return $this->playerID;
1175
	}
1176
1177
	/**
1178
	 * Returns the player name.
1179
	 * Use getDisplayName or getLinkedDisplayName for HTML-safe versions.
1180
	 */
1181
	public function getPlayerName() {
1182
		return $this->playerName;
1183
	}
1184
1185
	public function setPlayerName($name) {
1186
		$this->playerName = $name;
1187
		$this->hasChanged = true;
1188
	}
1189
1190
	/**
1191
	 * Returns the decorated player name, suitable for HTML display.
1192
	 */
1193
	public function getDisplayName($includeAlliance = false) {
1194
		$name = htmlentities($this->playerName) . ' (' . $this->getPlayerID() . ')';
1195
		$return = get_colored_text($this->getAlignment(), $name);
1196
		if ($this->isNPC()) {
1197
			$return .= ' <span class="npcColour">[NPC]</span>';
1198
		}
1199
		if ($includeAlliance) {
1200
			$return .= ' (' . $this->getAllianceDisplayName() . ')';
1201
		}
1202
		return $return;
1203
	}
1204
1205
	public function getBBLink() {
1206
			return '[player=' . $this->getPlayerID() . ']';
1207
	}
1208
1209
	public function getLinkedDisplayName($includeAlliance = true) {
1210
		$return = '<a href="' . $this->getTraderSearchHREF() . '">' . $this->getDisplayName() . '</a>';
1211
		if ($includeAlliance) {
1212
			$return .= ' (' . $this->getAllianceDisplayName(true) . ')';
1213
		}
1214
		return $return;
1215
	}
1216
1217
	/**
1218
	 * Use this method when the player is changing their own name.
1219
	 * This will flag the player as having used their free name change.
1220
	 */
1221
	public function setPlayerNameByPlayer($playerName) {
1222
		$this->setPlayerName($playerName);
1223
		$this->setNameChanged(true);
1224
	}
1225
1226
	public function isNameChanged() {
1227
		return $this->nameChanged;
1228
	}
1229
1230
	public function setNameChanged($bool) {
1231
		$this->nameChanged = $bool;
1232
		$this->hasChanged = true;
1233
	}
1234
1235
	public function getRaceID() {
1236
		return $this->raceID;
1237
	}
1238
1239
	public function getRaceName() {
1240
		return Globals::getRaceName($this->getRaceID());
1241
	}
1242
1243
	public static function getColouredRaceNameOrDefault($otherRaceID, AbstractSmrPlayer $player = null, $linked = false) {
1244
		$relations = 0;
1245
		if ($player !== null) {
1246
			$relations = $player->getRelation($otherRaceID);
1247
		}
1248
		return Globals::getColouredRaceName($otherRaceID, $relations, $linked);
1249
	}
1250
1251
	public function getColouredRaceName($otherRaceID, $linked = false) {
1252
		return self::getColouredRaceNameOrDefault($otherRaceID, $this, $linked);
1253
	}
1254
1255
	public function setRaceID($raceID) {
1256
		if ($this->raceID == $raceID) {
1257
			return;
1258
		}
1259
		$this->raceID = $raceID;
1260
		$this->hasChanged = true;
1261
	}
1262
1263
	public function isAllianceLeader($forceUpdate = false) {
1264
		return $this->getAccountID() == $this->getAlliance($forceUpdate)->getLeaderID();
1265
	}
1266
1267
	public function getAlliance($forceUpdate = false) {
1268
		return SmrAlliance::getAlliance($this->getAllianceID(), $this->getGameID(), $forceUpdate);
1269
	}
1270
1271
	public function getAllianceID() {
1272
		return $this->allianceID;
1273
	}
1274
1275
	public function hasAlliance() {
1276
		return $this->getAllianceID() != 0;
1277
	}
1278
1279
	protected function setAllianceID($ID) {
1280
		if ($this->allianceID == $ID) {
1281
			return;
1282
		}
1283
		$this->allianceID = $ID;
1284
		if ($this->allianceID != 0) {
1285
			$status = $this->hasNewbieStatus() ? 'NEWBIE' : 'VETERAN';
1286
			$this->db->query('INSERT IGNORE INTO player_joined_alliance (account_id,game_id,alliance_id,status) ' .
1287
				'VALUES (' . $this->db->escapeNumber($this->getAccountID()) . ',' . $this->db->escapeNumber($this->getGameID()) . ',' . $this->db->escapeNumber($this->getAllianceID()) . ',' . $this->db->escapeString($status) . ')');
1288
		}
1289
		$this->hasChanged = true;
1290
	}
1291
1292
	public function getAllianceBBLink() {
1293
		return $this->hasAlliance() ? $this->getAlliance()->getAllianceBBLink() : $this->getAllianceDisplayName();
1294
	}
1295
1296
	public function getAllianceDisplayName($linked = false, $includeAllianceID = false) {
1297
		if ($this->hasAlliance()) {
1298
			return $this->getAlliance()->getAllianceDisplayName($linked, $includeAllianceID);
1299
		} else {
1300
			return 'No Alliance';
1301
		}
1302
	}
1303
1304
	public function getAllianceRole($allianceID = false) {
1305
		if ($allianceID === false) {
1306
			$allianceID = $this->getAllianceID();
1307
		}
1308
		if (!isset($this->allianceRoles[$allianceID])) {
1309
			$this->allianceRoles[$allianceID] = 0;
1310
			$this->db->query('SELECT role_id
1311
						FROM player_has_alliance_role
1312
						WHERE ' . $this->SQL . '
1313
						AND alliance_id=' . $this->db->escapeNumber($allianceID) . '
1314
						LIMIT 1');
1315
			if ($this->db->nextRecord()) {
1316
				$this->allianceRoles[$allianceID] = $this->db->getInt('role_id');
1317
			}
1318
		}
1319
		return $this->allianceRoles[$allianceID];
1320
	}
1321
1322
	public function leaveAlliance(AbstractSmrPlayer $kickedBy = null) {
1323
		$allianceID = $this->getAllianceID();
1324
		$alliance = $this->getAlliance();
1325
		if ($kickedBy != null) {
1326
			$kickedBy->sendMessage($this->getAccountID(), MSG_PLAYER, 'You were kicked out of the alliance!', false);
1327
			$this->actionTaken('PlayerKicked', array('Alliance' => $alliance, 'Player' => $kickedBy));
1328
			$kickedBy->actionTaken('KickPlayer', array('Alliance' => $alliance, 'Player' => $this));
1329
		} elseif ($this->isAllianceLeader()) {
1330
			$this->actionTaken('DisbandAlliance', array('Alliance' => $alliance));
1331
		} else {
1332
			$this->actionTaken('LeaveAlliance', array('Alliance' => $alliance));
1333
			if ($alliance->getLeaderID() != 0 && $alliance->getLeaderID() != ACCOUNT_ID_NHL) {
1334
				$this->sendMessage($alliance->getLeaderID(), MSG_PLAYER, 'I left your alliance!', false);
1335
			}
1336
		}
1337
1338
		$this->setAllianceID(0);
1339
		$this->db->query('DELETE FROM player_has_alliance_role WHERE ' . $this->SQL);
1340
1341
		if (!$this->isAllianceLeader() && $allianceID != NHA_ID) { // Don't have a delay for switching alliance after leaving NHA, or for disbanding an alliance.
1342
			$this->setAllianceJoinable(TIME + self::TIME_FOR_ALLIANCE_SWITCH);
1343
			$alliance->getLeader()->setAllianceJoinable(TIME + self::TIME_FOR_ALLIANCE_SWITCH); //We set the joinable time for leader here, that way a single player alliance won't cause a player to wait before switching.
1344
		}
1345
	}
1346
1347
	/**
1348
	 * Join an alliance (used for both Leader and New Member roles)
1349
	 */
1350
	public function joinAlliance($allianceID) {
1351
		$this->setAllianceID($allianceID);
1352
		$alliance = $this->getAlliance();
1353
1354
		if (!$this->isAllianceLeader()) {
1355
			// Do not throw an exception if the NHL account doesn't exist.
1356
			try {
1357
				$this->sendMessage($alliance->getLeaderID(), MSG_PLAYER, 'I joined your alliance!', false);
1358
			} catch (AccountNotFoundException $e) {
1359
				if ($alliance->getLeaderID() != ACCOUNT_ID_NHL) {
1360
					throw $e;
1361
				}
1362
			}
1363
1364
			$roleID = ALLIANCE_ROLE_NEW_MEMBER;
1365
		} else {
1366
			$roleID = ALLIANCE_ROLE_LEADER;
1367
		}
1368
		$this->db->query('INSERT INTO player_has_alliance_role (game_id, account_id, role_id, alliance_id) VALUES (' . $this->db->escapeNumber($this->getGameID()) . ', ' . $this->db->escapeNumber($this->getAccountID()) . ', ' . $this->db->escapeNumber($roleID) . ',' . $this->db->escapeNumber($this->getAllianceID()) . ')');
1369
1370
		$this->actionTaken('JoinAlliance', array('Alliance' => $alliance));
1371
	}
1372
1373
	public function getAllianceJoinable() {
1374
		return $this->allianceJoinable;
1375
	}
1376
1377
	private function setAllianceJoinable($time) {
1378
		if ($this->allianceJoinable == $time) {
1379
			return;
1380
		}
1381
		$this->allianceJoinable = $time;
1382
		$this->hasChanged = true;
1383
	}
1384
1385
	/**
1386
	 * Invites player with $accountID to this player's alliance.
1387
	 */
1388
	public function sendAllianceInvitation(int $accountID, string $message, int $expires) : void {
1389
		if (!$this->hasAlliance()) {
1390
			throw new Exception('Must be in an alliance to send alliance invitations');
1391
		}
1392
		// Send message to invited player
1393
		$messageID = $this->sendMessage($accountID, MSG_PLAYER, $message, false, true, $expires, true);
1394
		SmrInvitation::send($this->getAllianceID(), $this->getGameID(), $accountID, $this->getAccountID(), $messageID, $expires);
1395
	}
1396
1397
	public function isCombatDronesKamikazeOnMines() {
1398
		return $this->combatDronesKamikazeOnMines;
1399
	}
1400
1401
	public function setCombatDronesKamikazeOnMines($bool) {
1402
		if ($this->combatDronesKamikazeOnMines == $bool) {
1403
			return;
1404
		}
1405
		$this->combatDronesKamikazeOnMines = $bool;
1406
		$this->hasChanged = true;
1407
	}
1408
1409
	protected function getPureRelationsData() {
1410
		if (!isset($this->pureRelations)) {
1411
			//get relations
1412
			$RACES = Globals::getRaces();
1413
			$this->pureRelations = array();
1414
			foreach ($RACES as $raceID => $raceName) {
1415
				$this->pureRelations[$raceID] = 0;
1416
			}
1417
			$this->db->query('SELECT race_id,relation FROM player_has_relation WHERE ' . $this->SQL . ' LIMIT ' . count($RACES));
1418
			while ($this->db->nextRecord()) {
1419
				$this->pureRelations[$this->db->getInt('race_id')] = $this->db->getInt('relation');
1420
			}
1421
		}
1422
	}
1423
1424
	public function getPureRelations() {
1425
		$this->getPureRelationsData();
1426
		return $this->pureRelations;
1427
	}
1428
1429
	/**
1430
	 * Get personal relations with a race
1431
	 */
1432
	public function getPureRelation($raceID) {
1433
		$rels = $this->getPureRelations();
1434
		return $rels[$raceID];
1435
	}
1436
1437
	public function getRelations() {
1438
		if (!isset($this->relations)) {
1439
			//get relations
1440
			$RACES = Globals::getRaces();
1441
			$raceRelations = Globals::getRaceRelations($this->getGameID(), $this->getRaceID());
1442
			$pureRels = $this->getPureRelations(); // make sure they're initialised.
1443
			$this->relations = array();
1444
			foreach ($RACES as $raceID => $raceName) {
1445
				$this->relations[$raceID] = $pureRels[$raceID] + $raceRelations[$raceID];
1446
			}
1447
		}
1448
		return $this->relations;
1449
	}
1450
1451
	/**
1452
	 * Get total relations with a race (personal + political)
1453
	 */
1454
	public function getRelation($raceID) {
1455
		$rels = $this->getRelations();
1456
		return $rels[$raceID];
1457
	}
1458
1459
	/**
1460
	 * Increases personal relations from trading $numGoods units with the race
1461
	 * of the port given by $raceID.
1462
	 */
1463
	public function increaseRelationsByTrade($numGoods, $raceID) {
1464
		$relations = ICeil(min($numGoods, 300) / 30);
1465
		//Cap relations to a max of 1 after 500 have been reached
1466
		if ($this->getPureRelation($raceID) + $relations >= 500) {
1467
			$relations = max(1, min($relations, 500 - $this->getPureRelation($raceID)));
1468
		}
1469
		$this->increaseRelations($relations, $raceID);
1470
	}
1471
1472
	public function decreaseRelationsByTrade($numGoods, $raceID) {
1473
		$relations = ICeil(min($numGoods, 300) / 30);
1474
		$this->decreaseRelations($relations, $raceID);
1475
	}
1476
1477
	public function increaseRelations($relations, $raceID) {
1478
		if ($relations < 0) {
1479
			throw new Exception('Trying to increase negative relations.');
1480
		}
1481
		if ($relations == 0) {
1482
			return;
1483
		}
1484
		$relations += $this->getPureRelation($raceID);
1485
		$this->setRelations($relations, $raceID);
1486
	}
1487
1488
	public function decreaseRelations($relations, $raceID) {
1489
		if ($relations < 0) {
1490
			throw new Exception('Trying to decrease negative relations.');
1491
		}
1492
		if ($relations == 0) {
1493
			return;
1494
		}
1495
		$relations = $this->getPureRelation($raceID) - $relations;
1496
		$this->setRelations($relations, $raceID);
1497
	}
1498
1499
	public function setRelations($relations, $raceID) {
1500
		$this->getRelations();
1501
		if ($this->pureRelations[$raceID] == $relations) {
1502
			return;
1503
		}
1504
		if ($relations < MIN_RELATIONS) {
1505
			$relations = MIN_RELATIONS;
1506
		}
1507
		$relationsDiff = IRound($relations - $this->pureRelations[$raceID]);
1508
		$this->pureRelations[$raceID] = $relations;
1509
		$this->relations[$raceID] += $relationsDiff;
1510
		$this->db->query('REPLACE INTO player_has_relation (account_id,game_id,race_id,relation) values (' . $this->db->escapeNumber($this->getAccountID()) . ',' . $this->db->escapeNumber($this->getGameID()) . ',' . $this->db->escapeNumber($raceID) . ',' . $this->db->escapeNumber($this->pureRelations[$raceID]) . ')');
1511
	}
1512
1513
	public function getLastNewsUpdate() {
1514
		return $this->lastNewsUpdate;
1515
	}
1516
1517
	private function setLastNewsUpdate($time) {
1518
		if ($this->lastNewsUpdate == $time) {
1519
			return;
1520
		}
1521
		$this->lastNewsUpdate = $time;
1522
		$this->hasChanged = true;
1523
	}
1524
1525
	public function updateLastNewsUpdate() {
1526
		$this->setLastNewsUpdate(TIME);
1527
	}
1528
1529
	public function getLastPort() {
1530
		return $this->lastPort;
1531
	}
1532
1533
	public function setLastPort($lastPort) {
1534
		if ($this->lastPort == $lastPort) {
1535
			return;
1536
		}
1537
		$this->lastPort = $lastPort;
1538
		$this->hasChanged = true;
1539
	}
1540
1541
	public function getPlottedCourse() {
1542
		if (!isset($this->plottedCourse)) {
1543
			// check if we have a course plotted
1544
			$this->db->query('SELECT course FROM player_plotted_course WHERE ' . $this->SQL . ' LIMIT 1');
1545
1546
			if ($this->db->nextRecord()) {
1547
				// get the course back
1548
				$this->plottedCourse = unserialize($this->db->getField('course'));
1549
			} else {
1550
				$this->plottedCourse = false;
1551
			}
1552
		}
1553
1554
		// Update the plotted course if we have moved since the last query
1555
		if ($this->plottedCourse !== false && (!isset($this->plottedCourseFrom) || $this->plottedCourseFrom != $this->getSectorID())) {
1556
			$this->plottedCourseFrom = $this->getSectorID();
1557
1558
			if ($this->plottedCourse->getNextOnPath() == $this->getSectorID()) {
1559
				// We have walked into the next sector of the course
1560
				$this->plottedCourse->followPath();
1561
				$this->setPlottedCourse($this->plottedCourse);
1562
			} elseif ($this->plottedCourse->isInPath($this->getSectorID())) {
1563
				// We have skipped to some later sector in the course
1564
				$this->plottedCourse->skipToSector($this->getSectorID());
1565
				$this->setPlottedCourse($this->plottedCourse);
1566
			}
1567
		}
1568
		return $this->plottedCourse;
1569
	}
1570
1571
	public function setPlottedCourse(Distance $plottedCourse) {
1572
		$hadPlottedCourse = $this->hasPlottedCourse();
1573
		$this->plottedCourse = $plottedCourse;
1574
		if ($this->plottedCourse->getTotalSectors() > 0) {
1575
			$this->db->query('REPLACE INTO player_plotted_course
1576
				(account_id, game_id, course)
1577
				VALUES(' . $this->db->escapeNumber($this->getAccountID()) . ', ' . $this->db->escapeNumber($this->getGameID()) . ', ' . $this->db->escapeBinary(serialize($this->plottedCourse)) . ')');
1578
		} elseif ($hadPlottedCourse) {
1579
			$this->deletePlottedCourse();
1580
		}
1581
	}
1582
1583
	public function hasPlottedCourse() {
1584
		return $this->getPlottedCourse() !== false;
1585
	}
1586
1587
	public function isPartOfCourse($sectorOrSectorID) {
1588
		if (!$this->hasPlottedCourse()) {
1589
			return false;
1590
		}
1591
		if ($sectorOrSectorID instanceof SmrSector) {
1592
			$sectorID = $sectorOrSectorID->getSectorID();
1593
		} else {
1594
			$sectorID = $sectorOrSectorID;
1595
		}
1596
		return $this->getPlottedCourse()->isInPath($sectorID);
1597
	}
1598
1599
	public function deletePlottedCourse() {
1600
		$this->plottedCourse = false;
1601
		$this->db->query('DELETE FROM player_plotted_course WHERE ' . $this->SQL . ' LIMIT 1');
1602
	}
1603
1604
	// Computes the turn cost and max misjump between current and target sector
1605
	public function getJumpInfo(SmrSector $targetSector) {
1606
		$path = Plotter::findDistanceToX($targetSector, $this->getSector(), true);
1607
		if ($path === false) {
1608
			create_error('Unable to plot from ' . $this->getSectorID() . ' to ' . $targetSector->getSectorID() . '.');
1609
		}
1610
		$distance = $path->getRelativeDistance();
1611
1612
		$turnCost = max(TURNS_JUMP_MINIMUM, IRound($distance * TURNS_PER_JUMP_DISTANCE));
1613
		$maxMisjump = max(0, IRound(($distance - $turnCost) * MISJUMP_DISTANCE_DIFF_FACTOR / (1 + $this->getLevelID() * MISJUMP_LEVEL_FACTOR)));
1614
		return array('turn_cost' => $turnCost, 'max_misjump' => $maxMisjump);
1615
	}
1616
1617
	public function __sleep() {
1618
		return array('accountID', 'gameID', 'sectorID', 'alignment', 'playerID', 'playerName');
1619
	}
1620
1621
	public function &getStoredDestinations() {
1622
		if (!isset($this->storedDestinations)) {
1623
			$this->storedDestinations = array();
1624
			$this->db->query('SELECT * FROM player_stored_sector WHERE ' . $this->SQL);
1625
			while ($this->db->nextRecord()) {
1626
				$this->storedDestinations[] = array(
1627
					'Label' => $this->db->getField('label'),
1628
					'SectorID' => $this->db->getInt('sector_id'),
1629
					'OffsetTop' => $this->db->getInt('offset_top'),
1630
					'OffsetLeft' => $this->db->getInt('offset_left')
1631
				);
1632
			}
1633
		}
1634
		return $this->storedDestinations;
1635
	}
1636
1637
	public function moveDestinationButton($sectorID, $offsetTop, $offsetLeft) {
1638
1639
		if (!is_numeric($offsetLeft) || !is_numeric($offsetTop)) {
1640
			create_error('The position of the saved sector must be numeric!.');
1641
		}
1642
		$offsetTop = round($offsetTop);
1643
		$offsetLeft = round($offsetLeft);
1644
1645
		if ($offsetLeft < 0 || $offsetLeft > 500 || $offsetTop < 0 || $offsetTop > 300) {
1646
			create_error('The saved sector must be in the box!');
1647
		}
1648
1649
		$storedDestinations =& $this->getStoredDestinations();
1650
		foreach ($storedDestinations as &$sd) {
1651
			if ($sd['SectorID'] == $sectorID) {
1652
				$sd['OffsetTop'] = $offsetTop;
1653
				$sd['OffsetLeft'] = $offsetLeft;
1654
				$this->db->query('
1655
					UPDATE player_stored_sector
1656
						SET offset_left = ' . $this->db->escapeNumber($offsetLeft) . ', offset_top=' . $this->db->escapeNumber($offsetTop) . '
1657
					WHERE ' . $this->SQL . ' AND sector_id = ' . $this->db->escapeNumber($sectorID)
1658
				);
1659
				return true;
1660
			}
1661
		}
1662
1663
		create_error('You do not have a saved sector for #' . $sectorID);
1664
	}
1665
1666
	public function addDestinationButton($sectorID, $label) {
1667
1668
		if (!is_numeric($sectorID) || !SmrSector::sectorExists($this->getGameID(), $sectorID)) {
1669
			create_error('You want to add a non-existent sector?');
1670
		}
1671
1672
		// sector already stored ?
1673
		foreach ($this->getStoredDestinations() as $sd) {
1674
			if ($sd['SectorID'] == $sectorID) {
1675
				create_error('Sector already stored!');
1676
			}
1677
		}
1678
1679
		$this->storedDestinations[] = array(
1680
			'Label' => $label,
1681
			'SectorID' => (int)$sectorID,
1682
			'OffsetTop' => 1,
1683
			'OffsetLeft' => 1
1684
		);
1685
1686
		$this->db->query('
1687
			INSERT INTO player_stored_sector (account_id, game_id, sector_id, label, offset_top, offset_left)
1688
			VALUES (' . $this->db->escapeNumber($this->getAccountID()) . ', ' . $this->db->escapeNumber($this->getGameID()) . ', ' . $this->db->escapeNumber($sectorID) . ',' . $this->db->escapeString($label, true) . ',1,1)'
1689
		);
1690
	}
1691
1692
	public function deleteDestinationButton($sectorID) {
1693
		if (!is_numeric($sectorID) || $sectorID < 1) {
1694
			create_error('You want to remove a non-existent sector?');
1695
		}
1696
1697
		foreach ($this->getStoredDestinations() as $key => $sd) {
1698
			if ($sd['SectorID'] == $sectorID) {
1699
				$this->db->query('
1700
					DELETE FROM player_stored_sector
1701
					WHERE ' . $this->SQL . '
1702
					AND sector_id = ' . $this->db->escapeNumber($sectorID)
1703
				);
1704
				unset($this->storedDestinations[$key]);
1705
				return true;
1706
			}
1707
		}
1708
		return false;
1709
	}
1710
1711
	public function getTickers() {
1712
		if (!isset($this->tickers)) {
1713
			$this->tickers = array();
1714
			//get ticker info
1715
			$this->db->query('SELECT type,time,expires,recent FROM player_has_ticker WHERE ' . $this->SQL . ' AND expires > ' . $this->db->escapeNumber(TIME));
1716
			while ($this->db->nextRecord()) {
1717
				$this->tickers[$this->db->getField('type')] = [
1718
					'Type' => $this->db->getField('type'),
1719
					'Time' => $this->db->getInt('time'),
1720
					'Expires' => $this->db->getInt('expires'),
1721
					'Recent' => $this->db->getField('recent'),
1722
				];
1723
			}
1724
		}
1725
		return $this->tickers;
1726
	}
1727
1728
	public function hasTickers() {
1729
		return count($this->getTickers()) > 0;
1730
	}
1731
1732
	public function getTicker($tickerType) {
1733
		$tickers = $this->getTickers();
1734
		if (isset($tickers[$tickerType])) {
1735
			return $tickers[$tickerType];
1736
		}
1737
		return false;
1738
	}
1739
1740
	public function hasTicker($tickerType) {
1741
		return $this->getTicker($tickerType) !== false;
1742
	}
1743
1744
	public function &shootPlayer(AbstractSmrPlayer $targetPlayer) {
1745
		return $this->getShip()->shootPlayer($targetPlayer);
1746
	}
1747
1748
	public function &shootForces(SmrForce $forces) {
1749
		return $this->getShip()->shootForces($forces);
1750
	}
1751
1752
	public function &shootPort(SmrPort $port) {
1753
		return $this->getShip()->shootPort($port);
1754
	}
1755
1756
	public function &shootPlanet(SmrPlanet $planet, $delayed) {
1757
		return $this->getShip()->shootPlanet($planet, $delayed);
1758
	}
1759
1760
	public function &shootPlayers(array $targetPlayers) {
1761
		return $this->getShip()->shootPlayers($targetPlayers);
1762
	}
1763
1764
	public function getMilitaryPayment() {
1765
		return $this->militaryPayment;
1766
	}
1767
1768
	public function hasMilitaryPayment() {
1769
		return $this->getMilitaryPayment() > 0;
1770
	}
1771
1772
	public function setMilitaryPayment($amount) {
1773
		if ($this->militaryPayment == $amount) {
1774
			return;
1775
		}
1776
		$this->militaryPayment = $amount;
1777
		$this->hasChanged = true;
1778
	}
1779
1780
	public function increaseMilitaryPayment($amount) {
1781
		if ($amount < 0) {
1782
			throw new Exception('Trying to increase negative military payment.');
1783
		}
1784
		$this->setMilitaryPayment($this->getMilitaryPayment() + $amount);
1785
	}
1786
1787
	public function decreaseMilitaryPayment($amount) {
1788
		if ($amount < 0) {
1789
			throw new Exception('Trying to decrease negative military payment.');
1790
		}
1791
		$this->setMilitaryPayment($this->getMilitaryPayment() - $amount);
1792
	}
1793
1794
	protected function getBountiesData() {
1795
		if (!isset($this->bounties)) {
1796
			$this->bounties = array();
1797
			$this->db->query('SELECT * FROM bounty WHERE ' . $this->SQL);
1798
			while ($this->db->nextRecord()) {
1799
				$this->bounties[$this->db->getInt('bounty_id')] = array(
1800
							'Amount' => $this->db->getInt('amount'),
1801
							'SmrCredits' => $this->db->getInt('smr_credits'),
1802
							'Type' => $this->db->getField('type'),
1803
							'Claimer' => $this->db->getInt('claimer_id'),
1804
							'Time' => $this->db->getInt('time'),
1805
							'ID' => $this->db->getInt('bounty_id'),
1806
							'New' => false);
1807
			}
1808
		}
1809
	}
1810
1811
	// Get bounties that can be claimed by this player
1812
	// Type must be 'HQ' or 'UG'
1813
	public function getClaimableBounties($type) {
1814
		$bounties = array();
1815
		$this->db->query('SELECT * FROM bounty WHERE claimer_id=' . $this->db->escapeNumber($this->getAccountID()) . ' AND game_id=' . $this->db->escapeNumber($this->getGameID()) . ' AND type=' . $this->db->escapeString($type));
1816
		while ($this->db->nextRecord()) {
1817
			$bounties[] = array(
1818
				'player' => SmrPlayer::getPlayer($this->db->getInt('account_id'), $this->getGameID()),
1819
				'bounty_id' => $this->db->getInt('bounty_id'),
1820
				'credits' => $this->db->getInt('amount'),
1821
				'smr_credits' => $this->db->getInt('smr_credits'),
1822
			);
1823
		}
1824
		return $bounties;
1825
	}
1826
1827
	public function getBounties() : array {
1828
		$this->getBountiesData();
1829
		return $this->bounties;
1830
	}
1831
1832
	public function hasBounties() : bool {
1833
		return count($this->getBounties()) > 0;
1834
	}
1835
1836
	protected function getBounty(int $bountyID) : array {
1837
		if (!$this->hasBounty($bountyID)) {
1838
			throw new Exception('BountyID does not exist: ' . $bountyID);
1839
		}
1840
		return $this->bounties[$bountyID];
1841
	}
1842
1843
	public function hasBounty(int $bountyID) : bool {
1844
		$bounties = $this->getBounties();
1845
		return isset($bounties[$bountyID]);
1846
	}
1847
1848
	protected function getBountyAmount(int $bountyID) : int {
1849
		$bounty = $this->getBounty($bountyID);
1850
		return $bounty['Amount'];
1851
	}
1852
1853
	protected function createBounty(string $type) : array {
1854
		$bounty = array('Amount' => 0,
1855
						'SmrCredits' => 0,
1856
						'Type' => $type,
1857
						'Claimer' => 0,
1858
						'Time' => TIME,
1859
						'ID' => $this->getNextBountyID(),
1860
						'New' => true);
1861
		$this->setBounty($bounty);
1862
		return $bounty;
1863
	}
1864
1865
	protected function getNextBountyID() : int {
1866
		$keys = array_keys($this->getBounties());
1867
		if (count($keys) > 0) {
1868
			return max($keys) + 1;
1869
		} else {
1870
			return 0;
1871
		}
1872
	}
1873
1874
	protected function setBounty(array $bounty) : void {
1875
		$this->bounties[$bounty['ID']] = $bounty;
1876
		$this->hasBountyChanged[$bounty['ID']] = true;
1877
	}
1878
1879
	protected function setBountyAmount(int $bountyID, int $amount) : void {
1880
		$bounty = $this->getBounty($bountyID);
1881
		$bounty['Amount'] = $amount;
1882
		$this->setBounty($bounty);
1883
	}
1884
1885
	public function getCurrentBounty(string $type) : array {
1886
		$bounties = $this->getBounties();
1887
		foreach ($bounties as $bounty) {
1888
			if ($bounty['Claimer'] == 0 && $bounty['Type'] == $type) {
1889
				return $bounty;
1890
			}
1891
		}
1892
		return $this->createBounty($type);
1893
	}
1894
1895
	public function hasCurrentBounty(string $type) : bool {
1896
		$bounties = $this->getBounties();
1897
		foreach ($bounties as $bounty) {
1898
			if ($bounty['Claimer'] == 0 && $bounty['Type'] == $type) {
1899
				return true;
1900
			}
1901
		}
1902
		return false;
1903
	}
1904
1905
	protected function getCurrentBountyAmount(string $type) : int {
1906
		$bounty = $this->getCurrentBounty($type);
1907
		return $bounty['Amount'];
1908
	}
1909
1910
	protected function setCurrentBountyAmount(string $type, int $amount) : void {
1911
		$bounty = $this->getCurrentBounty($type);
1912
		if ($bounty['Amount'] == $amount) {
1913
			return;
1914
		}
1915
		$bounty['Amount'] = $amount;
1916
		$this->setBounty($bounty);
1917
	}
1918
1919
	public function increaseCurrentBountyAmount(string $type, int $amount) : void {
1920
		if ($amount < 0) {
1921
			throw new Exception('Trying to increase negative current bounty.');
1922
		}
1923
		$this->setCurrentBountyAmount($type, $this->getCurrentBountyAmount($type) + $amount);
1924
	}
1925
1926
	public function decreaseCurrentBountyAmount(string $type, int $amount) : void {
1927
		if ($amount < 0) {
1928
			throw new Exception('Trying to decrease negative current bounty.');
1929
		}
1930
		$this->setCurrentBountyAmount($type, $this->getCurrentBountyAmount($type) - $amount);
1931
	}
1932
1933
	protected function getCurrentBountySmrCredits(string $type) : int {
1934
		$bounty = $this->getCurrentBounty($type);
1935
		return $bounty['SmrCredits'];
1936
	}
1937
1938
	protected function setCurrentBountySmrCredits(string $type, int $credits) : void {
1939
		$bounty = $this->getCurrentBounty($type);
1940
		if ($bounty['SmrCredits'] == $credits) {
1941
			return;
1942
		}
1943
		$bounty['SmrCredits'] = $credits;
1944
		$this->setBounty($bounty);
1945
	}
1946
1947
	public function increaseCurrentBountySmrCredits(string $type, int $credits) : void {
1948
		if ($credits < 0) {
1949
			throw new Exception('Trying to increase negative current bounty.');
1950
		}
1951
		$this->setCurrentBountySmrCredits($type, $this->getCurrentBountySmrCredits($type) + $credits);
1952
	}
1953
1954
	public function decreaseCurrentBountySmrCredits(string $type, int $credits) : void {
1955
		if ($credits < 0) {
1956
			throw new Exception('Trying to decrease negative current bounty.');
1957
		}
1958
		$this->setCurrentBountySmrCredits($type, $this->getCurrentBountySmrCredits($type) - $credits);
1959
	}
1960
1961
	public function setBountiesClaimable(AbstractSmrPlayer $claimer) : void {
1962
		foreach ($this->getBounties() as $bounty) {
1963
			if ($bounty['Claimer'] == 0) {
1964
				$bounty['Claimer'] = $claimer->getAccountID();
1965
				$this->setBounty($bounty);
1966
			}
1967
		}
1968
	}
1969
1970
	protected function getHOFData() {
1971
		if (!isset($this->HOF)) {
1972
			//Get Player HOF
1973
			$this->db->query('SELECT type,amount FROM player_hof WHERE ' . $this->SQL);
1974
			$this->HOF = array();
1975
			while ($this->db->nextRecord()) {
1976
				$hof =& $this->HOF;
1977
				$typeList = explode(':', $this->db->getField('type'));
1978
				foreach ($typeList as $type) {
1979
					if (!isset($hof[$type])) {
1980
						$hof[$type] = array();
1981
					}
1982
					$hof =& $hof[$type];
1983
				}
1984
				$hof = $this->db->getFloat('amount');
1985
			}
1986
			self::getHOFVis();
1987
		}
1988
	}
1989
1990
	public static function getHOFVis() {
1991
		if (!isset(self::$HOFVis)) {
1992
			//Get Player HOF Vis
1993
			$db = new SmrMySqlDatabase();
1994
			$db->query('SELECT type,visibility FROM hof_visibility');
1995
			self::$HOFVis = array();
1996
			while ($db->nextRecord()) {
1997
				self::$HOFVis[$db->getField('type')] = $db->getField('visibility');
1998
			}
1999
		}
2000
	}
2001
2002
	public function getHOF(array $typeList = null) {
2003
		$this->getHOFData();
2004
		if ($typeList == null) {
2005
			return $this->HOF;
2006
		}
2007
		$hof = $this->HOF;
2008
		foreach ($typeList as $type) {
2009
			if (!isset($hof[$type])) {
2010
				return 0;
2011
			}
2012
			$hof = $hof[$type];
2013
		}
2014
		return $hof;
2015
	}
2016
2017
	public function increaseHOF($amount, array $typeList, $visibility) {
2018
		if ($amount < 0) {
2019
			throw new Exception('Trying to increase negative HOF: ' . implode(':', $typeList));
2020
		}
2021
		if ($amount == 0) {
2022
			return;
2023
		}
2024
		$this->setHOF($this->getHOF($typeList) + $amount, $typeList, $visibility);
2025
	}
2026
2027
	public function decreaseHOF($amount, array $typeList, $visibility) {
2028
		if ($amount < 0) {
2029
			throw new Exception('Trying to decrease negative HOF: ' . implode(':', $typeList));
2030
		}
2031
		if ($amount == 0) {
2032
			return;
2033
		}
2034
		$this->setHOF($this->getHOF($typeList) - $amount, $typeList, $visibility);
2035
	}
2036
2037
	public function setHOF($amount, array $typeList, $visibility) {
2038
		if (is_array($this->getHOF($typeList))) {
2039
			throw new Exception('Trying to overwrite a HOF type: ' . implode(':', $typeList));
2040
		}
2041
		if ($this->isNPC()) {
2042
			// Don't store HOF for NPCs.
2043
			return;
2044
		}
2045
		if ($this->getHOF($typeList) == $amount) {
2046
			return;
2047
		}
2048
		if ($amount < 0) {
2049
			$amount = 0;
2050
		}
2051
		$this->getHOF();
2052
2053
		$hofType = implode(':', $typeList);
2054
		if (!isset(self::$HOFVis[$hofType])) {
2055
			self::$hasHOFVisChanged[$hofType] = self::HOF_NEW;
2056
		} elseif (self::$HOFVis[$hofType] != $visibility) {
2057
			self::$hasHOFVisChanged[$hofType] = self::HOF_CHANGED;
2058
		}
2059
		self::$HOFVis[$hofType] = $visibility;
2060
2061
		$hof =& $this->HOF;
2062
		$hofChanged =& $this->hasHOFChanged;
2063
		$new = false;
2064
		foreach ($typeList as $type) {
2065
			if (!isset($hofChanged[$type])) {
2066
				$hofChanged[$type] = array();
2067
			}
2068
			if (!isset($hof[$type])) {
2069
				$hof[$type] = array();
2070
				$new = true;
2071
			}
2072
			$hof =& $hof[$type];
2073
			$hofChanged =& $hofChanged[$type];
2074
		}
2075
		if ($hofChanged == null) {
2076
			$hofChanged = self::HOF_CHANGED;
2077
			if ($new) {
2078
				$hofChanged = self::HOF_NEW;
2079
			}
2080
		}
2081
		$hof = $amount;
2082
	}
2083
2084
	public function getExperienceRank() {
2085
		return $this->computeRanking('experience', $this->getExperience());
2086
	}
2087
2088
	public function getKillsRank() {
2089
		return $this->computeRanking('kills', $this->getKills());
2090
	}
2091
2092
	public function getDeathsRank() {
2093
		return $this->computeRanking('deaths', $this->getDeaths());
2094
	}
2095
2096
	public function getAssistsRank() {
2097
		return $this->computeRanking('assists', $this->getAssists());
2098
	}
2099
2100
	private function computeRanking($dbField, $playerAmount) {
2101
		$this->db->query('SELECT count(*) FROM player
2102
			WHERE game_id = ' . $this->db->escapeNumber($this->getGameID()) . '
2103
			AND (
2104
				'.$dbField . ' > ' . $this->db->escapeNumber($playerAmount) . '
2105
				OR (
2106
					'.$dbField . ' = ' . $this->db->escapeNumber($playerAmount) . '
2107
					AND player_name <= ' . $this->db->escapeString($this->getPlayerName()) . '
2108
				)
2109
			)');
2110
		$this->db->nextRecord();
2111
		$rank = $this->db->getInt('count(*)');
2112
		return $rank;
2113
	}
2114
2115
	public function killPlayer($sectorID) {
2116
		$sector = SmrSector::getSector($this->getGameID(), $sectorID);
2117
		//msg taken care of in trader_att_proc.php
2118
		// forget plotted course
2119
		$this->deletePlottedCourse();
2120
2121
		$sector->diedHere($this);
2122
2123
		// if we are in an alliance we increase their deaths
2124
		if ($this->hasAlliance()) {
2125
			$this->db->query('UPDATE alliance SET alliance_deaths = alliance_deaths + 1
2126
							WHERE game_id = ' . $this->db->escapeNumber($this->getGameID()) . ' AND alliance_id = ' . $this->db->escapeNumber($this->getAllianceID()) . ' LIMIT 1');
2127
		}
2128
2129
		// record death stat
2130
		$this->increaseHOF(1, array('Dying', 'Deaths'), HOF_PUBLIC);
2131
		//record cost of ship lost
2132
		$this->increaseHOF($this->getShip()->getCost(), array('Dying', 'Money', 'Cost Of Ships Lost'), HOF_PUBLIC);
2133
		// reset turns since last death
2134
		$this->setHOF(0, array('Movement', 'Turns Used', 'Since Last Death'), HOF_ALLIANCE);
2135
2136
		// 1/4 of ship value -> insurance
2137
		$newCredits = IRound($this->getShip()->getCost() / 4);
2138
		$old_speed = $this->getShip()->getSpeed();
2139
2140
		if ($newCredits < 100000) {
2141
			$newCredits = 100000;
2142
		}
2143
		$this->setCredits($newCredits);
2144
2145
		$this->setSectorID($this::getHome($this->getGameID(), $this->getRaceID()));
2146
		$this->increaseDeaths(1);
2147
		$this->setLandedOnPlanet(false);
2148
		$this->setDead(true);
2149
		$this->setNewbieWarning(true);
2150
		$this->getShip()->getPod($this->hasNewbieStatus());
2151
2152
		// Update turns due to ship change
2153
		$new_speed = $this->getShip()->getSpeed();
2154
		$this->setTurns(IRound($this->turns / $old_speed * $new_speed));
2155
		$this->setNewbieTurns(100);
2156
	}
2157
2158
	public function &killPlayerByPlayer(AbstractSmrPlayer $killer) {
2159
		$return = array();
2160
		$msg = $this->getBBLink();
2161
2162
		if ($this->hasCustomShipName()) {
2163
			$named_ship = strip_tags($this->getCustomShipName(), '<font><span><img>');
2164
			$msg .= ' flying <span class="yellow">' . $named_ship . '</span>';
2165
		}
2166
		$msg .= ' was destroyed by ' . $killer->getBBLink();
2167
		if ($killer->hasCustomShipName()) {
2168
			$named_ship = strip_tags($killer->getCustomShipName(), '<font><span><img>');
2169
			$msg .= ' flying <span class="yellow">' . $named_ship . '</span>';
2170
		}
2171
		$msg .= ' in Sector&nbsp;' . Globals::getSectorBBLink($this->getSectorID());
2172
		$this->getSector()->increaseBattles(1);
2173
		$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(TIME) . ',' . $this->db->escapeString($msg, true) . ',\'regular\',' . $this->db->escapeNumber($killer->getAccountID()) . ',' . $this->db->escapeNumber($killer->getAllianceID()) . ',' . $this->db->escapeNumber($this->getAccountID()) . ',' . $this->db->escapeNumber($this->getAllianceID()) . ')');
2174
2175
		self::sendMessageFromFedClerk($this->getGameID(), $this->getAccountID(), 'You were <span class="red">DESTROYED</span> by ' . $killer->getBBLink() . ' in sector ' . Globals::getSectorBBLink($this->getSectorID()));
2176
		self::sendMessageFromFedClerk($this->getGameID(), $killer->getAccountID(), 'You <span class="red">DESTROYED</span>&nbsp;' . $this->getBBLink() . ' in sector ' . Globals::getSectorBBLink($this->getSectorID()));
2177
2178
		// Dead player loses between 5% and 25% experience
2179
		$expLossPercentage = 0.15 + 0.10 * ($this->getLevelID() - $killer->getLevelID()) / $this->getMaxLevel();
2180
		$return['DeadExp'] = max(0, IFloor($this->getExperience() * $expLossPercentage));
2181
		$this->decreaseExperience($return['DeadExp']);
2182
2183
		// Killer gains 50% of the lost exp
2184
		$return['KillerExp'] = max(0, ICeil(0.5 * $return['DeadExp']));
2185
		$killer->increaseExperience($return['KillerExp']);
2186
2187
		$return['KillerCredits'] = $this->getCredits();
2188
		$killer->increaseCredits($return['KillerCredits']);
2189
2190
		// The killer may change alignment
2191
		$relations = Globals::getRaceRelations($this->getGameID(), $this->getRaceID());
2192
		$relation = $relations[$killer->getRaceID()];
2193
2194
		$alignChangePerRelation = 0.1;
2195
		if ($relation >= RELATIONS_PEACE || $relation <= RELATIONS_WAR) {
2196
			$alignChangePerRelation = 0.04;
2197
		}
2198
2199
		$return['KillerAlign'] = -$relation * $alignChangePerRelation; //Lose relations when killing a peaceful race
2200
		if ($return['KillerAlign'] > 0) {
2201
			$killer->increaseAlignment($return['KillerAlign']);
2202
		} else {
2203
			$killer->decreaseAlignment(-$return['KillerAlign']);
2204
		}
2205
		// War setting gives them military pay
2206
		if ($relation <= RELATIONS_WAR) {
2207
			$killer->increaseMilitaryPayment(-IFloor($relation * 100 * pow($return['KillerExp'] / 2, 0.25)));
2208
		}
2209
2210
		//check for federal bounty being offered for current port raiders;
2211
		$this->db->query('DELETE FROM player_attacks_port WHERE time < ' . $this->db->escapeNumber(TIME - self::TIME_FOR_FEDERAL_BOUNTY_ON_PR));
2212
		$query = 'SELECT 1
2213
					FROM player_attacks_port
2214
					JOIN port USING(game_id, sector_id)
2215
					JOIN player USING(game_id, account_id)
2216
					WHERE armour > 0 AND ' . $this->SQL . ' LIMIT 1';
2217
		$this->db->query($query);
2218
		if ($this->db->nextRecord()) {
2219
			$bounty = IFloor(DEFEND_PORT_BOUNTY_PER_LEVEL * $this->getLevelID());
2220
			$this->increaseCurrentBountyAmount('HQ', $bounty);
2221
		}
2222
2223
		// Killer get marked as claimer of podded player's bounties even if they don't exist
2224
		$this->setBountiesClaimable($killer);
2225
2226
		// If the alignment difference is greater than 200 then a bounty may be set
2227
		$alignmentDiff = abs($this->getAlignment() - $killer->getAlignment());
2228
		$return['BountyGained'] = array(
2229
			'Type' => 'None',
2230
			'Amount' => 0
2231
		);
2232
		if ($alignmentDiff >= 200) {
2233
			// If the podded players alignment makes them deputy or member then set bounty
2234
			if ($this->getAlignment() >= 100) {
2235
				$return['BountyGained']['Type'] = 'HQ';
2236
			} elseif ($this->getAlignment() <= 100) {
2237
				$return['BountyGained']['Type'] = 'UG';
2238
			}
2239
2240
			if ($return['BountyGained']['Type'] != 'None') {
2241
				$return['BountyGained']['Amount'] = IFloor(pow($alignmentDiff, 2.56));
2242
				$killer->increaseCurrentBountyAmount($return['BountyGained']['Type'], $return['BountyGained']['Amount']);
2243
			}
2244
		}
2245
2246
		if ($this->isNPC()) {
2247
			$killer->increaseHOF($return['KillerExp'], array('Killing', 'NPC', 'Experience', 'Gained'), HOF_PUBLIC);
2248
			$killer->increaseHOF($this->getExperience(), array('Killing', 'NPC', 'Experience', 'Of Traders Killed'), HOF_PUBLIC);
2249
2250
			$killer->increaseHOF($return['DeadExp'], array('Killing', 'Experience', 'Lost By NPCs Killed'), HOF_PUBLIC);
2251
2252
			$killer->increaseHOF($return['KillerCredits'], array('Killing', 'NPC', 'Money', 'Lost By Traders Killed'), HOF_PUBLIC);
2253
			$killer->increaseHOF($return['KillerCredits'], array('Killing', 'NPC', 'Money', 'Gain'), HOF_PUBLIC);
2254
			$killer->increaseHOF($this->getShip()->getCost(), array('Killing', 'NPC', 'Money', 'Cost Of Ships Killed'), HOF_PUBLIC);
2255
2256
			if ($return['KillerAlign'] > 0) {
2257
				$killer->increaseHOF($return['KillerAlign'], array('Killing', 'NPC', 'Alignment', 'Gain'), HOF_PUBLIC);
2258
			} else {
2259
				$killer->increaseHOF(-$return['KillerAlign'], array('Killing', 'NPC', 'Alignment', 'Loss'), HOF_PUBLIC);
2260
			}
2261
2262
			$killer->increaseHOF($return['BountyGained']['Amount'], array('Killing', 'NPC', 'Money', 'Bounty Gained'), HOF_PUBLIC);
2263
2264
			$killer->increaseHOF(1, array('Killing', 'NPC Kills'), HOF_PUBLIC);
2265
		} else {
2266
			$killer->increaseHOF($return['KillerExp'], array('Killing', 'Experience', 'Gained'), HOF_PUBLIC);
2267
			$killer->increaseHOF($this->getExperience(), array('Killing', 'Experience', 'Of Traders Killed'), HOF_PUBLIC);
2268
2269
			$killer->increaseHOF($return['DeadExp'], array('Killing', 'Experience', 'Lost By Traders Killed'), HOF_PUBLIC);
2270
2271
			$killer->increaseHOF($return['KillerCredits'], array('Killing', 'Money', 'Lost By Traders Killed'), HOF_PUBLIC);
2272
			$killer->increaseHOF($return['KillerCredits'], array('Killing', 'Money', 'Gain'), HOF_PUBLIC);
2273
			$killer->increaseHOF($this->getShip()->getCost(), array('Killing', 'Money', 'Cost Of Ships Killed'), HOF_PUBLIC);
2274
2275
			if ($return['KillerAlign'] > 0) {
2276
				$killer->increaseHOF($return['KillerAlign'], array('Killing', 'Alignment', 'Gain'), HOF_PUBLIC);
2277
			} else {
2278
				$killer->increaseHOF(-$return['KillerAlign'], array('Killing', 'Alignment', 'Loss'), HOF_PUBLIC);
2279
			}
2280
2281
			$killer->increaseHOF($return['BountyGained']['Amount'], array('Killing', 'Money', 'Bounty Gained'), HOF_PUBLIC);
2282
2283
			if ($this->getShip()->getAttackRatingWithMaxCDs() <= MAX_ATTACK_RATING_NEWBIE && $this->hasNewbieStatus() && !$killer->hasNewbieStatus()) { //Newbie kill
2284
				$killer->increaseHOF(1, array('Killing', 'Newbie Kills'), HOF_PUBLIC);
2285
			} else {
2286
				$killer->increaseKills(1);
2287
				$killer->increaseHOF(1, array('Killing', 'Kills'), HOF_PUBLIC);
2288
2289
				if ($killer->hasAlliance()) {
2290
					$this->db->query('UPDATE alliance SET alliance_kills=alliance_kills+1 WHERE alliance_id=' . $this->db->escapeNumber($killer->getAllianceID()) . ' AND game_id=' . $this->db->escapeNumber($killer->getGameID()) . ' LIMIT 1');
2291
				}
2292
2293
				// alliance vs. alliance stats
2294
				$this->incrementAllianceVsDeaths($killer->getAllianceID());
2295
			}
2296
		}
2297
2298
		$this->increaseHOF($return['BountyGained']['Amount'], array('Dying', 'Players', 'Money', 'Bounty Gained By Killer'), HOF_PUBLIC);
2299
		$this->increaseHOF($return['KillerExp'], array('Dying', 'Players', 'Experience', 'Gained By Killer'), HOF_PUBLIC);
2300
		$this->increaseHOF($return['DeadExp'], array('Dying', 'Experience', 'Lost'), HOF_PUBLIC);
2301
		$this->increaseHOF($return['DeadExp'], array('Dying', 'Players', 'Experience', 'Lost'), HOF_PUBLIC);
2302
		$this->increaseHOF($return['KillerCredits'], array('Dying', 'Players', 'Money Lost'), HOF_PUBLIC);
2303
		$this->increaseHOF($this->getShip()->getCost(), array('Dying', 'Players', 'Money', 'Cost Of Ships Lost'), HOF_PUBLIC);
2304
		$this->increaseHOF(1, array('Dying', 'Players', 'Deaths'), HOF_PUBLIC);
2305
2306
		$this->killPlayer($this->getSectorID());
2307
		return $return;
2308
	}
2309
2310
	public function &killPlayerByForces(SmrForce $forces) {
2311
		$return = array();
2312
		$owner = $forces->getOwner();
2313
		// send a message to the person who died
2314
		self::sendMessageFromFedClerk($this->getGameID(), $owner->getAccountID(), 'Your forces <span class="red">DESTROYED </span>' . $this->getBBLink() . ' in sector ' . Globals::getSectorBBLink($forces->getSectorID()));
2315
		self::sendMessageFromFedClerk($this->getGameID(), $this->getAccountID(), 'You were <span class="red">DESTROYED</span> by ' . $owner->getBBLink() . '\'s forces in sector ' . Globals::getSectorBBLink($this->getSectorID()));
2316
2317
		$news_message = $this->getBBLink();
2318
		if ($this->hasCustomShipName()) {
2319
			$named_ship = strip_tags($this->getCustomShipName(), '<font><span><img>');
2320
			$news_message .= ' flying <span class="yellow">' . $named_ship . '</span>';
2321
		}
2322
		$news_message .= ' was destroyed by ' . $owner->getBBLink() . '\'s forces in sector ' . Globals::getSectorBBLink($forces->getSectorID());
2323
		// insert the news entry
2324
		$this->db->query('INSERT INTO news (game_id, time, news_message,killer_id,killer_alliance,dead_id,dead_alliance)
2325
						VALUES(' . $this->db->escapeNumber($this->getGameID()) . ', ' . $this->db->escapeNumber(TIME) . ', ' . $this->db->escapeString($news_message) . ',' . $this->db->escapeNumber($owner->getAccountID()) . ',' . $this->db->escapeNumber($owner->getAllianceID()) . ',' . $this->db->escapeNumber($this->getAccountID()) . ',' . $this->db->escapeNumber($this->getAllianceID()) . ')');
2326
2327
		// Player loses 15% experience
2328
		$expLossPercentage = .15;
2329
		$return['DeadExp'] = IFloor($this->getExperience() * $expLossPercentage);
2330
		$this->decreaseExperience($return['DeadExp']);
2331
2332
		$return['LostCredits'] = $this->getCredits();
2333
2334
		// alliance vs. alliance stats
2335
		$this->incrementAllianceVsDeaths(ALLIANCE_VS_FORCES);
2336
		$owner->incrementAllianceVsKills(ALLIANCE_VS_FORCES);
2337
2338
		$this->increaseHOF($return['DeadExp'], array('Dying', 'Experience', 'Lost'), HOF_PUBLIC);
2339
		$this->increaseHOF($return['DeadExp'], array('Dying', 'Forces', 'Experience Lost'), HOF_PUBLIC);
2340
		$this->increaseHOF($return['LostCredits'], array('Dying', 'Forces', 'Money Lost'), HOF_PUBLIC);
2341
		$this->increaseHOF($this->getShip()->getCost(), array('Dying', 'Forces', 'Cost Of Ships Lost'), HOF_PUBLIC);
2342
		$this->increaseHOF(1, array('Dying', 'Forces', 'Deaths'), HOF_PUBLIC);
2343
2344
		$this->killPlayer($forces->getSectorID());
2345
		return $return;
2346
	}
2347
2348
	public function &killPlayerByPort(SmrPort $port) {
2349
		$return = array();
2350
		// send a message to the person who died
2351
		self::sendMessageFromFedClerk($this->getGameID(), $this->getAccountID(), 'You were <span class="red">DESTROYED</span> by the defenses of ' . $port->getDisplayName());
2352
2353
		$news_message = $this->getBBLink();
2354
		if ($this->hasCustomShipName()) {
2355
			$named_ship = strip_tags($this->getCustomShipName(), '<font><span><img>');
2356
			$news_message .= ' flying <span class="yellow">' . $named_ship . '</span>';
2357
		}
2358
		$news_message .= ' was destroyed while invading ' . $port->getDisplayName() . '.';
2359
		// insert the news entry
2360
		$this->db->query('INSERT INTO news (game_id, time, news_message,killer_id,dead_id,dead_alliance)
2361
						VALUES(' . $this->db->escapeNumber($this->getGameID()) . ', ' . $this->db->escapeNumber(TIME) . ', ' . $this->db->escapeString($news_message) . ',' . $this->db->escapeNumber(ACCOUNT_ID_PORT) . ',' . $this->db->escapeNumber($this->getAccountID()) . ',' . $this->db->escapeNumber($this->getAllianceID()) . ')');
2362
2363
		// Player loses between 15% and 20% experience
2364
		$expLossPercentage = .20 - .05 * ($port->getLevel() - 1) / ($port->getMaxLevel() - 1);
2365
		$return['DeadExp'] = max(0, IFloor($this->getExperience() * $expLossPercentage));
2366
		$this->decreaseExperience($return['DeadExp']);
2367
2368
		$return['LostCredits'] = $this->getCredits();
2369
2370
		// alliance vs. alliance stats
2371
		$this->incrementAllianceVsDeaths(ALLIANCE_VS_PORTS);
2372
2373
		$this->increaseHOF($return['DeadExp'], array('Dying', 'Experience', 'Lost'), HOF_PUBLIC);
2374
		$this->increaseHOF($return['DeadExp'], array('Dying', 'Ports', 'Experience Lost'), HOF_PUBLIC);
2375
		$this->increaseHOF($return['LostCredits'], array('Dying', 'Ports', 'Money Lost'), HOF_PUBLIC);
2376
		$this->increaseHOF($this->getShip()->getCost(), array('Dying', 'Ports', 'Cost Of Ships Lost'), HOF_PUBLIC);
2377
		$this->increaseHOF(1, array('Dying', 'Ports', 'Deaths'), HOF_PUBLIC);
2378
2379
		$this->killPlayer($port->getSectorID());
2380
		return $return;
2381
	}
2382
2383
	public function &killPlayerByPlanet(SmrPlanet $planet) {
2384
		$return = array();
2385
		// send a message to the person who died
2386
		$planetOwner = $planet->getOwner();
2387
		self::sendMessageFromFedClerk($this->getGameID(), $planetOwner->getAccountID(), 'Your planet <span class="red">DESTROYED</span>&nbsp;' . $this->getBBLink() . ' in sector ' . Globals::getSectorBBLink($planet->getSectorID()));
2388
		self::sendMessageFromFedClerk($this->getGameID(), $this->getAccountID(), 'You were <span class="red">DESTROYED</span> by the planetary defenses of ' . $planet->getCombatName());
2389
2390
		$news_message = $this->getBBLink();
2391
		if ($this->hasCustomShipName()) {
2392
			$named_ship = strip_tags($this->getCustomShipName(), '<font><span><img>');
2393
			$news_message .= ' flying <span class="yellow">' . $named_ship . '</span>';
2394
		}
2395
		$news_message .= ' was destroyed by ' . $planet->getCombatName() . '\'s planetary defenses in sector ' . Globals::getSectorBBLink($planet->getSectorID()) . '.';
2396
		// insert the news entry
2397
		$this->db->query('INSERT INTO news (game_id, time, news_message,killer_id,killer_alliance,dead_id,dead_alliance)
2398
						VALUES(' . $this->db->escapeNumber($this->getGameID()) . ', ' . $this->db->escapeNumber(TIME) . ', ' . $this->db->escapeString($news_message) . ',' . $this->db->escapeNumber($planetOwner->getAccountID()) . ',' . $this->db->escapeNumber($planetOwner->getAllianceID()) . ',' . $this->db->escapeNumber($this->getAccountID()) . ',' . $this->db->escapeNumber($this->getAllianceID()) . ')');
2399
2400
		// Player loses between 15% and 20% experience
2401
		$expLossPercentage = .20 - .05 * $planet->getLevel() / $planet->getMaxLevel();
2402
		$return['DeadExp'] = max(0, IFloor($this->getExperience() * $expLossPercentage));
2403
		$this->decreaseExperience($return['DeadExp']);
2404
2405
		$return['LostCredits'] = $this->getCredits();
2406
2407
		// alliance vs. alliance stats
2408
		$this->incrementAllianceVsDeaths(ALLIANCE_VS_PLANETS);
2409
		$planetOwner->incrementAllianceVsKills(ALLIANCE_VS_PLANETS);
2410
2411
		$this->increaseHOF($return['DeadExp'], array('Dying', 'Experience', 'Lost'), HOF_PUBLIC);
2412
		$this->increaseHOF($return['DeadExp'], array('Dying', 'Planets', 'Experience Lost'), HOF_PUBLIC);
2413
		$this->increaseHOF($return['LostCredits'], array('Dying', 'Planets', 'Money Lost'), HOF_PUBLIC);
2414
		$this->increaseHOF($this->getShip()->getCost(), array('Dying', 'Planets', 'Cost Of Ships Lost'), HOF_PUBLIC);
2415
		$this->increaseHOF(1, array('Dying', 'Planets', 'Deaths'), HOF_PUBLIC);
2416
2417
		$this->killPlayer($planet->getSectorID());
2418
		return $return;
2419
	}
2420
2421
	public function incrementAllianceVsKills($otherID) {
2422
		$values = [$this->getGameID(), $this->getAllianceID(), $otherID, 1];
2423
		$this->db->query('INSERT INTO alliance_vs_alliance (game_id, alliance_id_1, alliance_id_2, kills) VALUES (' . $this->db->escapeArray($values) . ') ON DUPLICATE KEY UPDATE kills = kills + 1');
2424
	}
2425
2426
	public function incrementAllianceVsDeaths($otherID) {
2427
		$values = [$this->getGameID(), $otherID, $this->getAllianceID(), 1];
2428
		$this->db->query('INSERT INTO alliance_vs_alliance (game_id, alliance_id_1, alliance_id_2, kills) VALUES (' . $this->db->escapeArray($values) . ') ON DUPLICATE KEY UPDATE kills = kills + 1');
2429
	}
2430
2431
	public function getTurnsLevel() {
2432
		if (!$this->hasTurns()) {
2433
			return 'NONE';
2434
		}
2435
		if ($this->getTurns() <= 25) {
2436
			return 'LOW';
2437
		}
2438
		if ($this->getTurns() <= 75) {
2439
			return 'MEDIUM';
2440
		}
2441
		return 'HIGH';
2442
	}
2443
2444
	/**
2445
	 * Returns the CSS class color to use when displaying the player's turns
2446
	 */
2447
	public function getTurnsColor() {
2448
		switch ($this->getTurnsLevel()) {
2449
			case 'NONE':
2450
			case 'LOW':
2451
				return 'red';
2452
			break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
2453
			case 'MEDIUM':
2454
				return 'yellow';
2455
			break;
2456
			default:
2457
				return 'green';
2458
		}
2459
	}
2460
2461
	public function getTurns() {
2462
		return $this->turns;
2463
	}
2464
2465
	public function hasTurns() {
2466
		return $this->turns > 0;
2467
	}
2468
2469
	public function getMaxTurns() {
2470
		return $this->getGame()->getMaxTurns();
2471
	}
2472
2473
	public function setTurns($turns) {
2474
		if ($this->turns == $turns) {
2475
			return;
2476
		}
2477
		// Make sure turns are in range [0, MaxTurns]
2478
		$this->turns = max(0, min($turns, $this->getMaxTurns()));
2479
		$this->hasChanged = true;
2480
	}
2481
2482
	public function takeTurns($take, $takeNewbie = 0) {
2483
		if ($take < 0 || $takeNewbie < 0) {
2484
			throw new Exception('Trying to take negative turns.');
2485
		}
2486
		$take = ICeil($take);
2487
		// Only take up to as many newbie turns as we have remaining
2488
		$takeNewbie = min($this->getNewbieTurns(), $takeNewbie);
2489
2490
		$this->setTurns($this->getTurns() - $take);
2491
		$this->setNewbieTurns($this->getNewbieTurns() - $takeNewbie);
2492
		$this->increaseHOF($take, array('Movement', 'Turns Used', 'Since Last Death'), HOF_ALLIANCE);
2493
		$this->increaseHOF($take, array('Movement', 'Turns Used', 'Total'), HOF_ALLIANCE);
2494
		$this->increaseHOF($takeNewbie, array('Movement', 'Turns Used', 'Newbie'), HOF_ALLIANCE);
2495
2496
		// Player has taken an action
2497
		$this->setLastActive(TIME);
2498
		$this->updateLastCPLAction();
2499
	}
2500
2501
	public function giveTurns(int $give, $giveNewbie = 0) {
2502
		if ($give < 0 || $giveNewbie < 0) {
2503
			throw new Exception('Trying to give negative turns.');
2504
		}
2505
		$this->setTurns($this->getTurns() + $give);
2506
		$this->setNewbieTurns($this->getNewbieTurns() + $giveNewbie);
2507
	}
2508
2509
	/**
2510
	 * Calculate the time in seconds between the given time and when the
2511
	 * player will be at max turns.
2512
	 */
2513
	public function getTimeUntilMaxTurns($time, $forceUpdate = false) {
2514
		$timeDiff = $time - $this->getLastTurnUpdate();
2515
		$turnsDiff = $this->getMaxTurns() - $this->getTurns();
2516
		$ship = $this->getShip($forceUpdate);
2517
		$maxTurnsTime = ICeil(($turnsDiff * 3600 / $ship->getRealSpeed())) - $timeDiff;
2518
		// If already at max turns, return 0
2519
		return max(0, $maxTurnsTime);
2520
	}
2521
2522
	/**
2523
	 * Grant the player their starting turns.
2524
	 */
2525
	public function giveStartingTurns() {
2526
		$startTurns = IFloor($this->getShip()->getRealSpeed() * $this->getGame()->getStartTurnHours());
2527
		$this->giveTurns($startTurns);
2528
		$this->setLastTurnUpdate($this->getGame()->getStartTime());
2529
	}
2530
2531
	// Turns only update when player is active.
2532
	// Calculate turns gained between given time and the last turn update
2533
	public function getTurnsGained($time, $forceUpdate = false) : int {
2534
		$timeDiff = $time - $this->getLastTurnUpdate();
2535
		$ship = $this->getShip($forceUpdate);
2536
		$extraTurns = IFloor($timeDiff * $ship->getRealSpeed() / 3600);
2537
		return $extraTurns;
2538
	}
2539
2540
	public function updateTurns() {
2541
		// is account validated?
2542
		if (!$this->getAccount()->isValidated()) {
2543
			return;
2544
		}
2545
2546
		// how many turns would he get right now?
2547
		$extraTurns = $this->getTurnsGained(TIME);
2548
2549
		// do we have at least one turn to give?
2550
		if ($extraTurns > 0) {
2551
			// recalc the time to avoid rounding errors
2552
			$newLastTurnUpdate = $this->getLastTurnUpdate() + ICeil($extraTurns * 3600 / $this->getShip()->getRealSpeed());
2553
			$this->setLastTurnUpdate($newLastTurnUpdate);
2554
			$this->giveTurns($extraTurns);
2555
		}
2556
	}
2557
2558
	public function getLastTurnUpdate() {
2559
		return $this->lastTurnUpdate;
2560
	}
2561
2562
	public function setLastTurnUpdate($time) {
2563
		if ($this->lastTurnUpdate == $time) {
2564
			return;
2565
		}
2566
		$this->lastTurnUpdate = $time;
2567
		$this->hasChanged = true;
2568
	}
2569
2570
	public function getLastActive() {
2571
		return $this->lastActive;
2572
	}
2573
2574
	public function setLastActive($lastActive) {
2575
		if ($this->lastActive == $lastActive) {
2576
			return;
2577
		}
2578
		$this->lastActive = $lastActive;
2579
		$this->hasChanged = true;
2580
	}
2581
2582
	public function getLastCPLAction() {
2583
		return $this->lastCPLAction;
2584
	}
2585
2586
	public function setLastCPLAction($time) {
2587
		if ($this->lastCPLAction == $time) {
2588
			return;
2589
		}
2590
		$this->lastCPLAction = $time;
2591
		$this->hasChanged = true;
2592
	}
2593
2594
	public function updateLastCPLAction() {
2595
		$this->setLastCPLAction(TIME);
2596
	}
2597
2598
	public function setNewbieWarning($bool) {
2599
		if ($this->newbieWarning == $bool) {
2600
			return;
2601
		}
2602
		$this->newbieWarning = $bool;
2603
		$this->hasChanged = true;
2604
	}
2605
2606
	public function getNewbieWarning() {
2607
		return $this->newbieWarning;
2608
	}
2609
2610
	public function isDisplayMissions() {
2611
		return $this->displayMissions;
2612
	}
2613
2614
	public function setDisplayMissions($bool) {
2615
		if ($this->displayMissions == $bool) {
2616
			return;
2617
		}
2618
		$this->displayMissions = $bool;
2619
		$this->hasChanged = true;
2620
	}
2621
2622
	public function getMissions() {
2623
		if (!isset($this->missions)) {
2624
			$this->db->query('SELECT * FROM player_has_mission WHERE ' . $this->SQL);
2625
			$this->missions = array();
2626
			while ($this->db->nextRecord()) {
2627
				$missionID = $this->db->getInt('mission_id');
2628
				$this->missions[$missionID] = array(
2629
					'On Step' => $this->db->getInt('on_step'),
2630
					'Progress' => $this->db->getInt('progress'),
2631
					'Unread' => $this->db->getBoolean('unread'),
2632
					'Expires' => $this->db->getInt('step_fails'),
2633
					'Sector' => $this->db->getInt('mission_sector'),
2634
					'Starting Sector' => $this->db->getInt('starting_sector')
2635
				);
2636
				$this->rebuildMission($missionID);
2637
			}
2638
		}
2639
		return $this->missions;
2640
	}
2641
2642
	public function getActiveMissions() {
2643
		$missions = $this->getMissions();
2644
		foreach ($missions as $missionID => $mission) {
2645
			if ($mission['On Step'] >= count(MISSIONS[$missionID]['Steps'])) {
2646
				unset($missions[$missionID]);
2647
			}
2648
		}
2649
		return $missions;
2650
	}
2651
2652
	protected function getMission($missionID) {
2653
		$missions = $this->getMissions();
2654
		if (isset($missions[$missionID])) {
2655
			return $missions[$missionID];
2656
		}
2657
		return false;
2658
	}
2659
2660
	protected function hasMission($missionID) {
2661
		return $this->getMission($missionID) !== false;
2662
	}
2663
2664
	protected function updateMission($missionID) {
2665
		$this->getMissions();
2666
		if (isset($this->missions[$missionID])) {
2667
			$mission = $this->missions[$missionID];
2668
			$this->db->query('
2669
				UPDATE player_has_mission
2670
				SET on_step = ' . $this->db->escapeNumber($mission['On Step']) . ',
2671
					progress = ' . $this->db->escapeNumber($mission['Progress']) . ',
2672
					unread = ' . $this->db->escapeBoolean($mission['Unread']) . ',
2673
					starting_sector = ' . $this->db->escapeNumber($mission['Starting Sector']) . ',
2674
					mission_sector = ' . $this->db->escapeNumber($mission['Sector']) . ',
2675
					step_fails = ' . $this->db->escapeNumber($mission['Expires']) . '
2676
				WHERE ' . $this->SQL . ' AND mission_id = ' . $this->db->escapeNumber($missionID) . ' LIMIT 1'
2677
			);
2678
			return true;
2679
		}
2680
		return false;
2681
	}
2682
2683
	private function setupMissionStep($missionID) {
2684
		$mission =& $this->missions[$missionID];
2685
		if ($mission['On Step'] >= count(MISSIONS[$missionID]['Steps'])) {
2686
			// Nothing to do if this mission is already completed
2687
			return;
2688
		}
2689
		$step = MISSIONS[$missionID]['Steps'][$mission['On Step']];
2690
		if (isset($step['PickSector'])) {
2691
			$realX = Plotter::getX($step['PickSector']['Type'], $step['PickSector']['X'], $this->getGameID());
2692
			if ($realX === false) {
2693
				throw new Exception('Invalid PickSector definition in mission: ' . $missionID);
2694
			}
2695
			$path = Plotter::findDistanceToX($realX, $this->getSector(), true, null, $this);
2696
			if ($path === false) {
2697
				throw new Exception('Cannot find location: ' . $missionID);
2698
			}
2699
			$mission['Sector'] = $path->getEndSectorID();
2700
		}
2701
	}
2702
2703
	/**
2704
	 * Declining a mission will permanently hide it from the player
2705
	 * by adding it in its completed state.
2706
	 */
2707
	public function declineMission($missionID) {
2708
		$finishedStep = count(MISSIONS[$missionID]['Steps']);
2709
		$this->addMission($missionID, $finishedStep);
2710
	}
2711
2712
	public function addMission($missionID, $step = 0) {
2713
		$this->getMissions();
2714
2715
		if (isset($this->missions[$missionID])) {
2716
			return;
2717
		}
2718
		$sector = 0;
2719
2720
		$mission = array(
2721
			'On Step' => $step,
2722
			'Progress' => 0,
2723
			'Unread' => true,
2724
			'Expires' => (TIME + 86400),
2725
			'Sector' => $sector,
2726
			'Starting Sector' => $this->getSectorID()
2727
		);
2728
2729
		$this->missions[$missionID] =& $mission;
2730
		$this->setupMissionStep($missionID);
2731
		$this->rebuildMission($missionID);
2732
2733
		$this->db->query('
2734
			REPLACE INTO player_has_mission (game_id,account_id,mission_id,on_step,progress,unread,starting_sector,mission_sector,step_fails)
2735
			VALUES ('.$this->db->escapeNumber($this->gameID) . ',' . $this->db->escapeNumber($this->accountID) . ',' . $this->db->escapeNumber($missionID) . ',' . $this->db->escapeNumber($mission['On Step']) . ',' . $this->db->escapeNumber($mission['Progress']) . ',' . $this->db->escapeBoolean($mission['Unread']) . ',' . $this->db->escapeNumber($mission['Starting Sector']) . ',' . $this->db->escapeNumber($mission['Sector']) . ',' . $this->db->escapeNumber($mission['Expires']) . ')'
2736
		);
2737
	}
2738
2739
	private function rebuildMission($missionID) {
2740
		$mission = $this->missions[$missionID];
2741
		$this->missions[$missionID]['Name'] = MISSIONS[$missionID]['Name'];
2742
2743
		if ($mission['On Step'] >= count(MISSIONS[$missionID]['Steps'])) {
2744
			// If we have completed this mission just use false to indicate no current task.
2745
			$currentStep = false;
2746
		} else {
2747
			$currentStep = MISSIONS[$missionID]['Steps'][$mission['On Step']];
2748
			$currentStep['Text'] = str_replace(array('<Race>', '<Sector>', '<Starting Sector>', '<trader>'), array($this->getRaceID(), $mission['Sector'], $mission['Starting Sector'], $this->playerName), $currentStep['Text']);
2749
			if (isset($currentStep['Task'])) {
2750
				$currentStep['Task'] = str_replace(array('<Race>', '<Sector>', '<Starting Sector>', '<trader>'), array($this->getRaceID(), $mission['Sector'], $mission['Starting Sector'], $this->playerName), $currentStep['Task']);
2751
			}
2752
			if (isset($currentStep['Level'])) {
2753
				$currentStep['Level'] = str_replace('<Player Level>', $this->getLevelID(), $currentStep['Level']);
2754
			} else {
2755
				$currentStep['Level'] = 0;
2756
			}
2757
		}
2758
		$this->missions[$missionID]['Task'] = $currentStep;
2759
	}
2760
2761
	public function deleteMission($missionID) {
2762
		$this->getMissions();
2763
		if (isset($this->missions[$missionID])) {
2764
			unset($this->missions[$missionID]);
2765
			$this->db->query('DELETE FROM player_has_mission WHERE ' . $this->SQL . ' AND mission_id = ' . $this->db->escapeNumber($missionID) . ' LIMIT 1');
2766
			return true;
2767
		}
2768
		return false;
2769
	}
2770
2771
	public function markMissionsRead() {
2772
		$this->getMissions();
2773
		$unreadMissions = array();
2774
		foreach ($this->missions as $missionID => &$mission) {
2775
			if ($mission['Unread']) {
2776
				$unreadMissions[] = $missionID;
2777
				$mission['Unread'] = false;
2778
				$this->updateMission($missionID);
2779
			}
2780
		}
2781
		return $unreadMissions;
2782
	}
2783
2784
	public function claimMissionReward($missionID) {
2785
		$this->getMissions();
2786
		$mission =& $this->missions[$missionID];
2787
		if ($mission === false) {
2788
			throw new Exception('Unknown mission: ' . $missionID);
2789
		}
2790
		if ($mission['Task'] === false || $mission['Task']['Step'] != 'Claim') {
2791
			throw new Exception('Cannot claim mission: ' . $missionID . ', for step: ' . $mission['On Step']);
2792
		}
2793
		$mission['On Step']++;
2794
		$mission['Unread'] = true;
2795
		foreach ($mission['Task']['Rewards'] as $rewardItem => $amount) {
2796
			switch ($rewardItem) {
2797
				case 'Credits':
2798
					$this->increaseCredits($amount);
2799
				break;
2800
				case 'Experience':
2801
					$this->increaseExperience($amount);
2802
				break;
2803
			}
2804
		}
2805
		$rewardText = $mission['Task']['Rewards']['Text'];
2806
		if ($mission['On Step'] < count(MISSIONS[$missionID]['Steps'])) {
2807
			// If we haven't finished the mission yet then 
2808
			$this->setupMissionStep($missionID);
2809
		}
2810
		$this->rebuildMission($missionID);
2811
		$this->updateMission($missionID);
2812
		return $rewardText;
2813
	}
2814
2815
	public function getAvailableMissions() {
2816
		$availableMissions = array();
2817
		foreach (MISSIONS as $missionID => $mission) {
2818
			if ($this->hasMission($missionID)) {
2819
				continue;
2820
			}
2821
			$realX = Plotter::getX($mission['HasX']['Type'], $mission['HasX']['X'], $this->getGameID());
2822
			if ($realX === false) {
2823
				throw new Exception('Invalid HasX definition in mission: ' . $missionID);
2824
			}
2825
			if ($this->getSector()->hasX($realX)) {
2826
				$availableMissions[$missionID] = $mission;
2827
			}
2828
		}
2829
		return $availableMissions;
2830
	}
2831
2832
	public function actionTaken($actionID, array $values) {
2833
		if (!in_array($actionID, MISSION_ACTIONS)) {
2834
			throw new Exception('Unknown action: ' . $actionID);
2835
		}
2836
// TODO: Reenable this once tested.		if($this->getAccount()->isLoggingEnabled())
2837
			switch ($actionID) {
2838
				case 'WalkSector':
2839
					$this->getAccount()->log(LOG_TYPE_MOVEMENT, 'Walks to sector: ' . $values['Sector']->getSectorID(), $this->getSectorID());
2840
				break;
2841
				case 'JoinAlliance':
2842
					$this->getAccount()->log(LOG_TYPE_ALLIANCE, 'joined alliance: ' . $values['Alliance']->getAllianceName(), $this->getSectorID());
2843
				break;
2844
				case 'LeaveAlliance':
2845
					$this->getAccount()->log(LOG_TYPE_ALLIANCE, 'left alliance: ' . $values['Alliance']->getAllianceName(), $this->getSectorID());
2846
				break;
2847
				case 'DisbandAlliance':
2848
					$this->getAccount()->log(LOG_TYPE_ALLIANCE, 'disbanded alliance ' . $values['Alliance']->getAllianceName(), $this->getSectorID());
2849
				break;
2850
				case 'KickPlayer':
2851
					$this->getAccount()->log(LOG_TYPE_ALLIANCE, 'kicked ' . $values['Player']->getAccount()->getLogin() . ' (' . $values['Player']->getPlayerName() . ') from alliance ' . $values['Alliance']->getAllianceName(), 0);
2852
				break;
2853
				case 'PlayerKicked':
2854
					$this->getAccount()->log(LOG_TYPE_ALLIANCE, 'was kicked from alliance ' . $values['Alliance']->getAllianceName() . ' by ' . $values['Player']->getAccount()->getLogin() . ' (' . $values['Player']->getPlayerName() . ')', 0);
2855
				break;
2856
2857
			}
2858
		$this->getMissions();
2859
		foreach ($this->missions as $missionID => &$mission) {
2860
			if ($mission['Task'] !== false && $mission['Task']['Step'] == $actionID) {
2861
				if (checkMissionRequirements($values, $mission, $this) === true) {
2862
					$mission['On Step']++;
2863
					$mission['Unread'] = true;
2864
					$this->setupMissionStep($missionID);
2865
					$this->rebuildMission($missionID);
2866
					$this->updateMission($missionID);
2867
				}
2868
			}
2869
		}
2870
	}
2871
2872
	public function canSeeAny(array $otherPlayerArray) {
2873
		foreach ($otherPlayerArray as $otherPlayer) {
2874
			if ($this->canSee($otherPlayer)) {
2875
				return true;
2876
			}
2877
		}
2878
		return false;
2879
	}
2880
2881
	public function canSee(AbstractSmrPlayer $otherPlayer) {
2882
		if (!$otherPlayer->getShip()->isCloaked()) {
2883
			return true;
2884
		}
2885
		if ($this->sameAlliance($otherPlayer)) {
2886
			return true;
2887
		}
2888
		if ($this->getExperience() >= $otherPlayer->getExperience()) {
2889
			return true;
2890
		}
2891
		return false;
2892
	}
2893
2894
	public function equals(AbstractSmrPlayer $otherPlayer = null) {
2895
		return $otherPlayer !== null && $this->getAccountID() == $otherPlayer->getAccountID() && $this->getGameID() == $otherPlayer->getGameID();
2896
	}
2897
2898
	public function sameAlliance(AbstractSmrPlayer $otherPlayer = null) {
2899
		return $this->equals($otherPlayer) || (!is_null($otherPlayer) && $this->getGameID() == $otherPlayer->getGameID() && $this->hasAlliance() && $this->getAllianceID() == $otherPlayer->getAllianceID());
2900
	}
2901
2902
	public function sharedForceAlliance(AbstractSmrPlayer $otherPlayer = null) {
2903
		return $this->sameAlliance($otherPlayer);
2904
	}
2905
2906
	public function forceNAPAlliance(AbstractSmrPlayer $otherPlayer = null) {
2907
		return $this->sameAlliance($otherPlayer);
2908
	}
2909
2910
	public function planetNAPAlliance(AbstractSmrPlayer $otherPlayer = null) {
2911
		return $this->sameAlliance($otherPlayer);
2912
	}
2913
2914
	public function traderNAPAlliance(AbstractSmrPlayer $otherPlayer = null) {
2915
		return $this->sameAlliance($otherPlayer);
2916
	}
2917
2918
	public function traderMAPAlliance(AbstractSmrPlayer $otherPlayer = null) {
2919
		return $this->traderAttackTraderAlliance($otherPlayer) && $this->traderDefendTraderAlliance($otherPlayer);
2920
	}
2921
2922
	public function traderAttackTraderAlliance(AbstractSmrPlayer $otherPlayer = null) {
2923
		return $this->sameAlliance($otherPlayer);
2924
	}
2925
2926
	public function traderDefendTraderAlliance(AbstractSmrPlayer $otherPlayer = null) {
2927
		return $this->sameAlliance($otherPlayer);
2928
	}
2929
2930
	public function traderAttackForceAlliance(AbstractSmrPlayer $otherPlayer = null) {
2931
		return $this->sameAlliance($otherPlayer);
2932
	}
2933
2934
	public function traderAttackPortAlliance(AbstractSmrPlayer $otherPlayer = null) {
2935
		return $this->sameAlliance($otherPlayer);
2936
	}
2937
2938
	public function traderAttackPlanetAlliance(AbstractSmrPlayer $otherPlayer = null) {
2939
		return $this->sameAlliance($otherPlayer);
2940
	}
2941
2942
	public function meetsAlignmentRestriction($restriction) {
2943
		if ($restriction < 0) {
2944
			return $this->getAlignment() <= $restriction;
2945
		}
2946
		if ($restriction > 0) {
2947
			return $this->getAlignment() >= $restriction;
2948
		}
2949
		return true;
2950
	}
2951
2952
	// Get an array of goods that are visible to the player
2953
	public function getVisibleGoods() {
2954
		$goods = Globals::getGoods();
2955
		$visibleGoods = array();
2956
		foreach ($goods as $key => $good) {
2957
			if ($this->meetsAlignmentRestriction($good['AlignRestriction'])) {
2958
				$visibleGoods[$key] = $good;
2959
			}
2960
		}
2961
		return $visibleGoods;
2962
	}
2963
2964
	/**
2965
	 * Will retrieve all visited sectors, use only when you are likely to check a large number of these
2966
	 */
2967
	public function hasVisitedSector($sectorID) {
2968
		if (!isset($this->visitedSectors)) {
2969
			$this->visitedSectors = array();
2970
			$this->db->query('SELECT sector_id FROM player_visited_sector WHERE ' . $this->SQL);
2971
			while ($this->db->nextRecord()) {
2972
				$this->visitedSectors[$this->db->getInt('sector_id')] = false;
2973
			}
2974
		}
2975
		return !isset($this->visitedSectors[$sectorID]);
2976
	}
2977
2978
	public function getLeaveNewbieProtectionHREF() {
2979
		return SmrSession::getNewHREF(create_container('leave_newbie_processing.php'));
2980
	}
2981
2982
	public function getExamineTraderHREF() {
2983
		$container = create_container('skeleton.php', 'trader_examine.php');
2984
		$container['target'] = $this->getAccountID();
2985
		return SmrSession::getNewHREF($container);
2986
	}
2987
2988
	public function getAttackTraderHREF() {
2989
		return Globals::getAttackTraderHREF($this->getAccountID());
2990
	}
2991
2992
	public function getPlanetKickHREF() {
2993
		$container = create_container('planet_kick_processing.php', 'trader_attack_processing.php');
2994
		$container['account_id'] = $this->getAccountID();
2995
		return SmrSession::getNewHREF($container);
2996
	}
2997
2998
	public function getTraderSearchHREF() {
2999
		$container = create_container('skeleton.php', 'trader_search_result.php');
3000
		$container['player_id'] = $this->getPlayerID();
3001
		return SmrSession::getNewHREF($container);
3002
	}
3003
3004
	public function getAllianceRosterHREF() {
3005
		return Globals::getAllianceRosterHREF($this->getAllianceID());
3006
	}
3007
3008
	public function getToggleWeaponHidingHREF($ajax = false) {
3009
		$container = create_container('toggle_processing.php');
3010
		$container['toggle'] = 'WeaponHiding';
3011
		$container['AJAX'] = $ajax;
3012
		return SmrSession::getNewHREF($container);
3013
	}
3014
3015
	public function isDisplayWeapons() {
3016
		return $this->displayWeapons;
3017
	}
3018
3019
	/**
3020
	 * Should weapons be displayed in the right panel?
3021
	 * This updates the player database directly because it is used with AJAX,
3022
	 * which does not acquire a sector lock.
3023
	 */
3024
	public function setDisplayWeapons($bool) {
3025
		if ($this->displayWeapons == $bool) {
3026
			return;
3027
		}
3028
		$this->displayWeapons = $bool;
3029
		$this->db->query('UPDATE player SET display_weapons=' . $this->db->escapeBoolean($this->displayWeapons) . ' WHERE ' . $this->SQL);
3030
	}
3031
3032
	public function update() {
3033
		$this->save();
3034
	}
3035
3036
	public function save() {
3037
		if ($this->hasChanged === true) {
3038
			$this->db->query('UPDATE player SET player_name=' . $this->db->escapeString($this->playerName) .
3039
				', player_id=' . $this->db->escapeNumber($this->playerID) .
3040
				', sector_id=' . $this->db->escapeNumber($this->sectorID) .
3041
				', last_sector_id=' . $this->db->escapeNumber($this->lastSectorID) .
3042
				', turns=' . $this->db->escapeNumber($this->turns) .
3043
				', last_turn_update=' . $this->db->escapeNumber($this->lastTurnUpdate) .
3044
				', newbie_turns=' . $this->db->escapeNumber($this->newbieTurns) .
3045
				', last_news_update=' . $this->db->escapeNumber($this->lastNewsUpdate) .
3046
				', attack_warning=' . $this->db->escapeString($this->attackColour) .
3047
				', dead=' . $this->db->escapeBoolean($this->dead) .
3048
				', newbie_status=' . $this->db->escapeBoolean($this->newbieStatus) .
3049
				', land_on_planet=' . $this->db->escapeBoolean($this->landedOnPlanet) .
3050
				', last_active=' . $this->db->escapeNumber($this->lastActive) .
3051
				', last_cpl_action=' . $this->db->escapeNumber($this->lastCPLAction) .
3052
				', race_id=' . $this->db->escapeNumber($this->raceID) .
3053
				', credits=' . $this->db->escapeNumber($this->credits) .
3054
				', experience=' . $this->db->escapeNumber($this->experience) .
3055
				', alignment=' . $this->db->escapeNumber($this->alignment) .
3056
				', military_payment=' . $this->db->escapeNumber($this->militaryPayment) .
3057
				', alliance_id=' . $this->db->escapeNumber($this->allianceID) .
3058
				', alliance_join=' . $this->db->escapeNumber($this->allianceJoinable) .
3059
				', ship_type_id=' . $this->db->escapeNumber($this->shipID) .
3060
				', kills=' . $this->db->escapeNumber($this->kills) .
3061
				', deaths=' . $this->db->escapeNumber($this->deaths) .
3062
				', assists=' . $this->db->escapeNumber($this->assists) .
3063
				', last_port=' . $this->db->escapeNumber($this->lastPort) .
3064
				', bank=' . $this->db->escapeNumber($this->bank) .
3065
				', zoom=' . $this->db->escapeNumber($this->zoom) .
3066
				', display_missions=' . $this->db->escapeBoolean($this->displayMissions) .
3067
				', force_drop_messages=' . $this->db->escapeBoolean($this->forceDropMessages) .
3068
				', group_scout_messages=' . $this->db->escapeString($this->groupScoutMessages) .
3069
				', ignore_globals=' . $this->db->escapeBoolean($this->ignoreGlobals) .
3070
				', newbie_warning = ' . $this->db->escapeBoolean($this->newbieWarning) .
3071
				', name_changed = ' . $this->db->escapeBoolean($this->nameChanged) .
3072
				', combat_drones_kamikaze_on_mines = ' . $this->db->escapeBoolean($this->combatDronesKamikazeOnMines) .
3073
				' WHERE ' . $this->SQL . ' LIMIT 1');
3074
			$this->hasChanged = false;
3075
		}
3076
		foreach ($this->hasBountyChanged as $key => &$bountyChanged) {
3077
			if ($bountyChanged === true) {
3078
				$bountyChanged = false;
3079
				$bounty = $this->getBounty($key);
3080
				if ($bounty['New'] === true) {
3081
					if ($bounty['Amount'] > 0 || $bounty['SmrCredits'] > 0) {
3082
						$this->db->query('INSERT INTO bounty (account_id,game_id,type,amount,smr_credits,claimer_id,time) VALUES (' . $this->db->escapeNumber($this->getAccountID()) . ',' . $this->db->escapeNumber($this->getGameID()) . ',' . $this->db->escapeString($bounty['Type']) . ',' . $this->db->escapeNumber($bounty['Amount']) . ',' . $this->db->escapeNumber($bounty['SmrCredits']) . ',' . $this->db->escapeNumber($bounty['Claimer']) . ',' . $this->db->escapeNumber($bounty['Time']) . ')');
3083
					}
3084
				} else {
3085
					if ($bounty['Amount'] > 0 || $bounty['SmrCredits'] > 0) {
3086
						$this->db->query('UPDATE bounty
3087
							SET amount=' . $this->db->escapeNumber($bounty['Amount']) . ',
3088
							smr_credits=' . $this->db->escapeNumber($bounty['SmrCredits']) . ',
3089
							type=' . $this->db->escapeString($bounty['Type']) . ',
3090
							claimer_id=' . $this->db->escapeNumber($bounty['Claimer']) . ',
3091
							time=' . $this->db->escapeNumber($bounty['Time']) . '
3092
							WHERE bounty_id=' . $this->db->escapeNumber($bounty['ID']) . ' AND ' . $this->SQL . ' LIMIT 1');
3093
					} else {
3094
						$this->db->query('DELETE FROM bounty WHERE bounty_id=' . $this->db->escapeNumber($bounty['ID']) . ' AND ' . $this->SQL . ' LIMIT 1');
3095
					}
3096
				}
3097
			}
3098
		}
3099
		$this->saveHOF();
3100
	}
3101
3102
	public function saveHOF() {
3103
		if (count($this->hasHOFChanged) > 0) {
3104
			$this->doHOFSave($this->hasHOFChanged);
3105
			$this->hasHOFChanged = [];
3106
		}
3107
		if (!empty(self::$hasHOFVisChanged)) {
3108
			foreach (self::$hasHOFVisChanged as $hofType => $changeType) {
3109
				if ($changeType == self::HOF_NEW) {
3110
					$this->db->query('INSERT INTO hof_visibility (type, visibility) VALUES (' . $this->db->escapeString($hofType) . ',' . $this->db->escapeString(self::$HOFVis[$hofType]) . ')');
3111
				} else {
3112
					$this->db->query('UPDATE hof_visibility SET visibility = ' . $this->db->escapeString(self::$HOFVis[$hofType]) . ' WHERE type = ' . $this->db->escapeString($hofType) . ' LIMIT 1');
3113
				}
3114
				unset(self::$hasHOFVisChanged[$hofType]);
3115
			}
3116
		}
3117
	}
3118
3119
	/**
3120
	 * This should only be called by `saveHOF` (and recursively) to
3121
	 * ensure that the `hasHOFChanged` attribute is properly cleared.
3122
	 */
3123
	protected function doHOFSave(array $hasChangedList, array $typeList = array()) {
3124
		foreach ($hasChangedList as $type => $hofChanged) {
3125
			$tempTypeList = $typeList;
3126
			$tempTypeList[] = $type;
3127
			if (is_array($hofChanged)) {
3128
				$this->doHOFSave($hofChanged, $tempTypeList);
3129
			} else {
3130
				$amount = $this->getHOF($tempTypeList);
3131
				if ($hofChanged == self::HOF_NEW) {
3132
					if ($amount > 0) {
3133
						$this->db->query('INSERT INTO player_hof (account_id,game_id,type,amount) VALUES (' . $this->db->escapeNumber($this->getAccountID()) . ',' . $this->db->escapeNumber($this->getGameID()) . ',' . $this->db->escapeArray($tempTypeList, false, true, ':', false) . ',' . $this->db->escapeNumber($amount) . ')');
3134
					}
3135
				} elseif ($hofChanged == self::HOF_CHANGED) {
3136
					$this->db->query('UPDATE player_hof
3137
						SET amount=' . $this->db->escapeNumber($amount) . '
3138
						WHERE ' . $this->SQL . ' AND type = ' . $this->db->escapeArray($tempTypeList, false, true, ':', false) . ' LIMIT 1');
3139
				}
3140
			}
3141
		}
3142
	}
3143
3144
}
3145