We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.
1 | <?php declare(strict_types=1); |
||
2 | |||
3 | use Smr\Chess\ChessGame; |
||
4 | use Smr\Container\DiContainer; |
||
5 | use Smr\Database; |
||
6 | use Smr\Epoch; |
||
7 | use Smr\Exceptions\UserError; |
||
8 | use Smr\Messages; |
||
9 | use Smr\Race; |
||
10 | use Smr\SectorLock; |
||
11 | use Smr\Session; |
||
12 | use Smr\Template; |
||
13 | use Smr\VoteLink; |
||
14 | use Smr\VoteSite; |
||
15 | |||
16 | function parseBoolean(mixed $check): bool { |
||
17 | // Only negative strings are not implicitly converted to the correct bool |
||
18 | if (is_string($check) && (strcasecmp($check, 'NO') == 0 || strcasecmp($check, 'FALSE') == 0)) { |
||
19 | return false; |
||
20 | } |
||
21 | return (bool)$check; |
||
22 | } |
||
23 | |||
24 | function linkCombatLog(int $logID): string { |
||
25 | $container = Page::create('combat_log_viewer_verify.php'); |
||
26 | $container['log_id'] = $logID; |
||
27 | return '<a href="' . $container->href() . '"><img src="images/notify.gif" width="14" height="11" border="0" title="View the combat log" /></a>'; |
||
28 | } |
||
29 | |||
30 | /** |
||
31 | * Converts a BBCode tag into some other text depending on the tag and value. |
||
32 | * This is called in two stages: first with action BBCODE_CHECK (where the |
||
33 | * returned value must be a boolean) and second, if the first check passes, |
||
34 | * with action BBCODE_OUTPUT. |
||
35 | * |
||
36 | * @param array<string, string> $tagParams |
||
37 | */ |
||
38 | function smrBBCode(\Nbbc\BBCode $bbParser, int $action, string $tagName, string $default, array $tagParams, string $tagContent): bool|string { |
||
39 | global $overrideGameID, $disableBBLinks; |
||
40 | $session = Session::getInstance(); |
||
41 | try { |
||
42 | switch ($tagName) { |
||
43 | case 'combatlog': |
||
44 | if ($action == \Nbbc\BBCode::BBCODE_CHECK) { |
||
45 | return is_numeric($default); |
||
46 | } |
||
47 | $logID = (int)$default; |
||
48 | return linkCombatLog($logID); |
||
49 | |||
50 | case 'player': |
||
51 | if ($action == \Nbbc\BBCode::BBCODE_CHECK) { |
||
52 | return is_numeric($default); |
||
53 | } |
||
54 | $playerID = (int)$default; |
||
55 | $bbPlayer = SmrPlayer::getPlayerByPlayerID($playerID, $overrideGameID); |
||
56 | $showAlliance = isset($tagParams['showalliance']) ? parseBoolean($tagParams['showalliance']) : false; |
||
57 | if ($disableBBLinks === false && $overrideGameID == $session->getGameID()) { |
||
58 | return $bbPlayer->getLinkedDisplayName($showAlliance); |
||
59 | } |
||
60 | return $bbPlayer->getDisplayName($showAlliance); |
||
61 | |||
62 | case 'alliance': |
||
63 | if ($action == \Nbbc\BBCode::BBCODE_CHECK) { |
||
64 | return is_numeric($default); |
||
65 | } |
||
66 | $allianceID = (int)$default; |
||
67 | $alliance = SmrAlliance::getAlliance($allianceID, $overrideGameID); |
||
68 | if ($disableBBLinks === false && $overrideGameID == $session->getGameID()) { |
||
69 | if ($session->hasGame() && $alliance->getAllianceID() == $session->getPlayer()->getAllianceID()) { |
||
70 | $container = Page::create('alliance_mod.php'); |
||
71 | } else { |
||
72 | $container = Page::create('alliance_roster.php'); |
||
73 | } |
||
74 | $container['alliance_id'] = $alliance->getAllianceID(); |
||
75 | return create_link($container, $alliance->getAllianceDisplayName()); |
||
76 | } |
||
77 | return $alliance->getAllianceDisplayName(); |
||
78 | |||
79 | case 'race': |
||
80 | $raceNameID = $default; |
||
81 | foreach (Race::getAllNames() as $raceID => $raceName) { |
||
82 | if ((is_numeric($raceNameID) && $raceNameID == $raceID) |
||
83 | || $raceNameID == $raceName) { |
||
84 | if ($action == \Nbbc\BBCode::BBCODE_CHECK) { |
||
85 | return true; |
||
86 | } |
||
87 | $linked = $disableBBLinks === false && $overrideGameID == $session->getGameID(); |
||
88 | $player = $session->hasGame() ? $session->getPlayer() : null; |
||
89 | return AbstractSmrPlayer::getColouredRaceNameOrDefault($raceID, $player, $linked); |
||
90 | } |
||
91 | } |
||
92 | break; |
||
93 | |||
94 | case 'servertimetouser': |
||
95 | if ($action == \Nbbc\BBCode::BBCODE_CHECK) { |
||
96 | return true; |
||
97 | } |
||
98 | $time = strtotime($default); |
||
99 | if ($time !== false) { |
||
100 | $time += $session->getAccount()->getOffset() * 3600; |
||
101 | return date($session->getAccount()->getDateTimeFormat(), $time); |
||
102 | } |
||
103 | break; |
||
104 | |||
105 | case 'chess': |
||
106 | if ($action == \Nbbc\BBCode::BBCODE_CHECK) { |
||
107 | return is_numeric($default); |
||
108 | } |
||
109 | $chessGameID = (int)$default; |
||
110 | $chessGame = ChessGame::getChessGame($chessGameID); |
||
111 | return '<a href="' . $chessGame->getPlayGameHREF() . '">chess game (' . $chessGame->getChessGameID() . ')</a>'; |
||
112 | |||
113 | case 'sector': |
||
114 | if ($action == \Nbbc\BBCode::BBCODE_CHECK) { |
||
115 | return is_numeric($default); |
||
116 | } |
||
117 | |||
118 | $sectorID = (int)$default; |
||
119 | $sectorTag = '<span class="sectorColour">#' . $sectorID . '</span>'; |
||
120 | |||
121 | if ($disableBBLinks === false |
||
122 | && $session->hasGame() |
||
123 | && $session->getGameID() == $overrideGameID |
||
124 | && SmrSector::sectorExists($overrideGameID, $sectorID)) { |
||
125 | return '<a href="' . Globals::getPlotCourseHREF($session->getPlayer()->getSectorID(), $sectorID) . '">' . $sectorTag . '</a>'; |
||
126 | } |
||
127 | return $sectorTag; |
||
128 | |||
129 | case 'join_alliance': |
||
130 | if ($action == \Nbbc\BBCode::BBCODE_CHECK) { |
||
131 | return is_numeric($default); |
||
132 | } |
||
133 | $allianceID = (int)$default; |
||
134 | $alliance = SmrAlliance::getAlliance($allianceID, $overrideGameID); |
||
135 | $container = Page::create('alliance_invite_accept_processing.php'); |
||
136 | $container['alliance_id'] = $alliance->getAllianceID(); |
||
137 | return '<div class="buttonA"><a class="buttonA" href="' . $container->href() . '">Join ' . $alliance->getAllianceDisplayName() . '</a></div>'; |
||
138 | } |
||
139 | } catch (Throwable) { |
||
140 | // If there's an error, we will silently display the original text |
||
141 | } |
||
142 | if ($action == \Nbbc\BBCode::BBCODE_CHECK) { |
||
143 | return false; |
||
144 | } |
||
145 | return htmlspecialchars($tagParams['_tag']) . $tagContent . htmlspecialchars($tagParams['_endtag']); |
||
146 | } |
||
147 | |||
148 | function inify(string $text): string { |
||
149 | return str_replace(',', '', html_entity_decode($text)); |
||
150 | } |
||
151 | |||
152 | function bbifyMessage(string $message, bool $noLinks = false): string { |
||
153 | static $bbParser; |
||
154 | if (!isset($bbParser)) { |
||
155 | $bbParser = new \Nbbc\BBCode(); |
||
156 | $bbParser->setEnableSmileys(false); |
||
157 | $bbParser->removeRule('wiki'); |
||
158 | $bbParser->removeRule('img'); |
||
159 | $bbParser->setURLTarget('_blank'); |
||
160 | $bbParser->setURLTargetable('override'); |
||
161 | $bbParser->setEscapeContent(false); // don't escape HTML, needed for News etc. |
||
162 | $smrRule = [ |
||
163 | 'mode' => \Nbbc\BBCode::BBCODE_MODE_CALLBACK, |
||
164 | 'method' => 'smrBBCode', |
||
165 | 'class' => 'link', |
||
166 | 'allow_in' => ['listitem', 'block', 'columns', 'inline'], |
||
167 | 'end_tag' => \Nbbc\BBCode::BBCODE_PROHIBIT, |
||
168 | 'content' => \Nbbc\BBCode::BBCODE_PROHIBIT, |
||
169 | ]; |
||
170 | $bbParser->addRule('combatlog', $smrRule); |
||
171 | $bbParser->addRule('player', $smrRule); |
||
172 | $bbParser->addRule('alliance', $smrRule); |
||
173 | $bbParser->addRule('race', $smrRule); |
||
174 | $bbParser->addRule('servertimetouser', $smrRule); |
||
175 | $bbParser->addRule('chess', $smrRule); |
||
176 | $bbParser->addRule('sector', $smrRule); |
||
177 | $bbParser->addRule('join_alliance', $smrRule); |
||
178 | } |
||
179 | |||
180 | global $disableBBLinks; |
||
181 | $disableBBLinks = $noLinks; |
||
182 | |||
183 | if (str_contains($message, '[')) { //We have BBCode so let's do a full parse. |
||
184 | $message = $bbParser->parse($message); |
||
185 | } else { //Otherwise just convert newlines |
||
186 | $message = nl2br($message, true); |
||
187 | } |
||
188 | return $message; |
||
189 | } |
||
190 | |||
191 | function create_error(string $message): never { |
||
192 | throw new UserError($message); |
||
193 | } |
||
194 | |||
195 | function handleUserError(string $message): never { |
||
196 | if ($_SERVER['SCRIPT_NAME'] !== LOADER_URI) { |
||
197 | header('Location: /error.php?msg=' . urlencode($message)); |
||
198 | exit; |
||
199 | } |
||
200 | |||
201 | // If we're throwing an error, we don't care what data was stored in the |
||
202 | // Template from the original page. |
||
203 | DiContainer::getContainer()->reset(Template::class); |
||
204 | |||
205 | $session = Session::getInstance(); |
||
206 | if ($session->hasGame()) { |
||
207 | $container = Page::create('current_sector.php'); |
||
208 | $errorMsg = '<span class="red bold">ERROR: </span>' . $message; |
||
209 | $container['errorMsg'] = $errorMsg; |
||
210 | } else { |
||
211 | $container = Page::create('error.php'); |
||
212 | $container['message'] = $message; |
||
213 | } |
||
214 | |||
215 | if ($session->ajax) { |
||
216 | // To avoid the page just not refreshing when an error is encountered |
||
217 | // during ajax updates, use javascript to auto-redirect to the |
||
218 | // appropriate error page. |
||
219 | $errorHREF = $container->href(); |
||
220 | // json_encode the HREF as a safety precaution |
||
221 | $template = Template::getInstance(); |
||
222 | $template->addJavascriptForAjax('EVAL', 'location.href = ' . json_encode($errorHREF)); |
||
223 | } |
||
224 | $container->go(); |
||
225 | } |
||
226 | |||
227 | function create_link(Page|string $container, string $text, string $class = null): string { |
||
228 | return '<a' . ($class === null ? '' : ' class="' . $class . '"') . ' href="' . (is_string($container) ? $container : $container->href()) . '">' . $text . '</a>'; |
||
229 | } |
||
230 | |||
231 | function create_submit_link(Page $container, string $text): string { |
||
232 | return '<a href="' . $container->href() . '" class="submitStyle">' . $text . '</a>'; |
||
233 | } |
||
234 | |||
235 | function get_colored_text_range(float $value, float $maxValue, string $text = null, float $minValue = 0): string { |
||
236 | if ($text === null) { |
||
237 | $text = number_format($value); |
||
238 | } |
||
239 | if ($maxValue - $minValue == 0) { |
||
240 | return $text; |
||
241 | } |
||
242 | $normalisedValue = IRound(510 * max(0, min($maxValue, $value) - $minValue) / ($maxValue - $minValue)) - 255; |
||
243 | if ($normalisedValue < 0) { |
||
244 | $r_component = 'ff'; |
||
245 | $g_component = dechex(255 + $normalisedValue); |
||
246 | if (strlen($g_component) == 1) { |
||
247 | $g_component = '0' . $g_component; |
||
248 | } |
||
249 | } elseif ($normalisedValue > 0) { |
||
250 | $g_component = 'ff'; |
||
251 | $r_component = dechex(255 - $normalisedValue); |
||
252 | if (strlen($r_component) == 1) { |
||
253 | $r_component = '0' . $r_component; |
||
254 | } |
||
255 | } else { |
||
256 | $r_component = 'ff'; |
||
257 | $g_component = 'ff'; |
||
258 | } |
||
259 | $colour = $r_component . $g_component . '00'; |
||
260 | return '<span style="color:#' . $colour . '">' . $text . '</span>'; |
||
261 | } |
||
262 | |||
263 | function get_colored_text(float $value, string $text = null): string { |
||
264 | return get_colored_text_range($value, 300, $text, -300); |
||
265 | } |
||
266 | |||
267 | function word_filter(string $string): string { |
||
268 | static $words; |
||
269 | |||
270 | if (!is_array($words)) { |
||
271 | $db = Database::getInstance(); |
||
272 | $dbResult = $db->read('SELECT word_value, word_replacement FROM word_filter'); |
||
273 | $words = []; |
||
274 | foreach ($dbResult->records() as $dbRecord) { |
||
275 | $row = $dbRecord->getRow(); |
||
276 | $words[] = ['word_value' => '/' . str_replace('/', '\/', $row['word_value']) . '/i', 'word_replacement' => $row['word_replacement']]; |
||
277 | } |
||
278 | } |
||
279 | |||
280 | foreach ($words as $word) { |
||
281 | $string = preg_replace($word['word_value'], $word['word_replacement'], $string); |
||
282 | } |
||
283 | |||
284 | return $string; |
||
285 | } |
||
286 | |||
287 | // choose correct pluralization based on amount |
||
288 | function pluralise(int|float $amount, string $word, bool $includeAmount = true): string { |
||
289 | $result = $word; |
||
290 | if ($amount != 1) { |
||
291 | $result .= 's'; |
||
292 | } |
||
293 | if ($includeAmount) { |
||
294 | $result = $amount . ' ' . $result; |
||
295 | } |
||
296 | return $result; |
||
297 | } |
||
298 | |||
299 | /** |
||
300 | * This function is a hack around the old style http forward mechanism. |
||
301 | * It is also responsible for setting most of the global variables |
||
302 | * (see loader.php for the initialization of the globals). |
||
303 | */ |
||
304 | function do_voodoo(): never { |
||
305 | $session = Session::getInstance(); |
||
306 | $var = $session->getCurrentVar(); |
||
307 | |||
308 | if (!defined('AJAX_CONTAINER')) { |
||
309 | define('AJAX_CONTAINER', isset($var['AJAX']) && $var['AJAX'] === true); |
||
310 | } |
||
311 | |||
312 | if (!AJAX_CONTAINER && $session->ajax && $session->hasChangedSN()) { |
||
313 | exit; |
||
314 | } |
||
315 | //ob_clean(); |
||
316 | |||
317 | // create account object |
||
318 | $account = $session->getAccount(); |
||
319 | |||
320 | if ($session->hasGame()) { |
||
321 | // Get the nominal player information (this may change after locking). |
||
322 | // We don't force a reload here in case we don't need to lock. |
||
323 | $player = $session->getPlayer(); |
||
324 | $sectorID = $player->getSectorID(); |
||
325 | |||
326 | if (!$session->ajax //AJAX should never do anything that requires a lock. |
||
327 | //&& ($var->file == 'current_sector.php' || $var->file == 'map_local.php') //Neither should CS or LM and they gets loaded a lot so should reduce lag issues with big groups. |
||
328 | ) { |
||
329 | // We skip locking if we've already failed to display error page |
||
330 | $lock = SectorLock::getInstance(); |
||
331 | if (!$lock->hasFailed() && $lock->acquireForPlayer($player)) { |
||
332 | // Reload var info in case it changed between grabbing lock. |
||
333 | $session->fetchVarInfo(); |
||
334 | if ($session->hasCurrentVar() === false) { |
||
335 | if (ENABLE_DEBUG) { |
||
336 | $db = Database::getInstance(); |
||
337 | $db->insert('debug', [ |
||
338 | 'debug_type' => $db->escapeString('SPAM'), |
||
339 | 'account_id' => $db->escapeNumber($account->getAccountID()), |
||
340 | 'value' => 0, |
||
341 | 'value_2' => 0, |
||
342 | ]); |
||
343 | } |
||
344 | throw new UserError('Please do not spam click!'); |
||
345 | } |
||
346 | $var = $session->getCurrentVar(); |
||
347 | |||
348 | // Reload player now that we have a lock. |
||
349 | $player = $session->getPlayer(true); |
||
350 | if ($player->getSectorID() != $sectorID) { |
||
351 | // Player sector changed after reloading! Release lock and try again. |
||
352 | $lock->release(); |
||
353 | do_voodoo(); |
||
354 | } |
||
355 | } |
||
356 | } |
||
357 | |||
358 | // update turns on that player |
||
359 | $player->updateTurns(); |
||
360 | |||
361 | // Check if we need to redirect to a different page |
||
362 | if (!$var->skipRedirect && !$session->ajax) { |
||
363 | if ($player->getGame()->hasEnded()) { |
||
364 | Page::create('game_leave_processing.php', ['forward_to' => 'game_play.php', 'errorMsg' => 'The game has ended.'], skipRedirect: true)->go(); |
||
365 | } |
||
366 | if ($player->isDead()) { |
||
367 | Page::create('death_processing.php', skipRedirect: true)->go(); |
||
368 | } |
||
369 | if ($player->getNewbieWarning() && $player->getNewbieTurns() <= NEWBIE_TURNS_WARNING_LIMIT) { |
||
370 | Page::create('newbie_warning_processing.php', skipRedirect: true)->go(); |
||
371 | } |
||
372 | } |
||
373 | } |
||
374 | |||
375 | // Execute the engine files. |
||
376 | // This is where the majority of the page-specific work is performed. |
||
377 | $var->process(); |
||
378 | |||
379 | // Populate the template |
||
380 | $template = Template::getInstance(); |
||
381 | if (isset($player)) { |
||
382 | $template->assign('UnderAttack', $player->removeUnderAttack()); |
||
383 | } |
||
384 | |||
385 | //Nothing below this point should require the lock. |
||
386 | saveAllAndReleaseLock(); |
||
387 | |||
388 | $template->assign('TemplateBody', $var->file); |
||
389 | if (isset($player)) { |
||
390 | $template->assign('ThisSector', $player->getSector()); |
||
391 | $template->assign('ThisPlayer', $player); |
||
392 | $template->assign('ThisShip', $player->getShip()); |
||
393 | } |
||
394 | $template->assign('ThisAccount', $account); |
||
395 | if ($account->getCssLink() != null) { |
||
396 | $template->assign('ExtraCSSLink', $account->getCssLink()); |
||
397 | } |
||
398 | doSkeletonAssigns($template); |
||
399 | |||
400 | // Set ajax refresh time |
||
401 | $ajaxRefresh = $account->isUseAJAX(); |
||
402 | if ($ajaxRefresh) { |
||
403 | // If we can refresh, specify the refresh interval in millisecs |
||
404 | if (isset($player) && $player->canFight()) { |
||
405 | $ajaxRefresh = AJAX_UNPROTECTED_REFRESH_TIME; |
||
406 | } else { |
||
407 | $ajaxRefresh = AJAX_DEFAULT_REFRESH_TIME; |
||
408 | } |
||
409 | } |
||
410 | $template->assign('AJAX_ENABLE_REFRESH', $ajaxRefresh); |
||
411 | |||
412 | $template->display('skeleton.php', $session->ajax || AJAX_CONTAINER); |
||
413 | |||
414 | $session->update(); |
||
415 | |||
416 | exit; |
||
0 ignored issues
–
show
|
|||
417 | } |
||
418 | |||
419 | function saveAllAndReleaseLock(bool $updateSession = true): void { |
||
420 | // Only save if we have a lock. |
||
421 | $lock = SectorLock::getInstance(); |
||
422 | if ($lock->isActive()) { |
||
423 | SmrSector::saveSectors(); |
||
424 | SmrShip::saveShips(); |
||
425 | SmrPlayer::savePlayers(); |
||
426 | // Skip any caching classes that haven't even been loaded yet |
||
427 | if (class_exists(SmrForce::class, false)) { |
||
428 | SmrForce::saveForces(); |
||
429 | } |
||
430 | if (class_exists(SmrPort::class, false)) { |
||
431 | SmrPort::savePorts(); |
||
432 | } |
||
433 | if (class_exists(SmrPlanet::class, false)) { |
||
434 | SmrPlanet::savePlanets(); |
||
435 | } |
||
436 | if (class_exists(WeightedRandom::class, false)) { |
||
437 | WeightedRandom::saveWeightedRandoms(); |
||
438 | } |
||
439 | if ($updateSession) { |
||
440 | //Update session here to make sure current page $var is up to date before releasing lock. |
||
441 | Session::getInstance()->update(); |
||
442 | } |
||
443 | $lock->release(); |
||
444 | } |
||
445 | } |
||
446 | |||
447 | function doTickerAssigns(Template $template, AbstractSmrPlayer $player, Database $db): void { |
||
448 | //any ticker news? |
||
449 | if ($player->hasTickers()) { |
||
450 | $ticker = []; |
||
451 | $max = Epoch::time() - 60; |
||
452 | $dateFormat = $player->getAccount()->getDateTimeFormat(); |
||
453 | if ($player->hasTicker('NEWS')) { |
||
454 | //get recent news (5 mins) |
||
455 | $dbResult = $db->read('SELECT time,news_message FROM news WHERE game_id = ' . $db->escapeNumber($player->getGameID()) . ' AND time >= ' . $max . ' ORDER BY time DESC LIMIT 4'); |
||
456 | foreach ($dbResult->records() as $dbRecord) { |
||
457 | $ticker[] = [ |
||
458 | 'Time' => date($dateFormat, $dbRecord->getInt('time')), |
||
459 | 'Message' => $dbRecord->getString('news_message'), |
||
460 | ]; |
||
461 | } |
||
462 | } |
||
463 | if ($player->hasTicker('SCOUT')) { |
||
464 | $dbResult = $db->read('SELECT message_text,send_time FROM message |
||
465 | WHERE account_id=' . $db->escapeNumber($player->getAccountID()) . ' |
||
466 | AND game_id=' . $db->escapeNumber($player->getGameID()) . ' |
||
467 | AND message_type_id=' . $db->escapeNumber(MSG_SCOUT) . ' |
||
468 | AND send_time>=' . $db->escapeNumber($max) . ' |
||
469 | AND sender_id NOT IN (SELECT account_id FROM player_has_ticker WHERE type=' . $db->escapeString('BLOCK') . ' AND expires > ' . $db->escapeNumber(Epoch::time()) . ' AND game_id = ' . $db->escapeNumber($player->getGameID()) . ') AND receiver_delete = \'FALSE\' |
||
470 | ORDER BY send_time DESC |
||
471 | LIMIT 4'); |
||
472 | foreach ($dbResult->records() as $dbRecord) { |
||
473 | $ticker[] = [ |
||
474 | 'Time' => date($dateFormat, $dbRecord->getInt('send_time')), |
||
475 | 'Message' => $dbRecord->getString('message_text'), |
||
476 | ]; |
||
477 | } |
||
478 | } |
||
479 | $template->assign('Ticker', $ticker); |
||
480 | } |
||
481 | } |
||
482 | |||
483 | function doSkeletonAssigns(Template $template): void { |
||
484 | $session = Session::getInstance(); |
||
485 | $account = $session->getAccount(); |
||
486 | $db = Database::getInstance(); |
||
487 | |||
488 | $template->assign('CSSLink', $account->getCssUrl()); |
||
489 | $template->assign('CSSColourLink', $account->getCssColourUrl()); |
||
490 | |||
491 | $template->assign('FontSize', $account->getFontSize() - 20); |
||
492 | $template->assign('timeDisplay', date($account->getDateTimeFormatSplit(), Epoch::time())); |
||
493 | |||
494 | $container = Page::create('hall_of_fame_new.php'); |
||
495 | $template->assign('HallOfFameLink', $container->href()); |
||
496 | |||
497 | $template->assign('AccountID', $account->getAccountID()); |
||
498 | $template->assign('PlayGameLink', Page::create('game_leave_processing.php', ['forward_to' => 'game_play.php'])->href()); |
||
499 | |||
500 | $template->assign('LogoutLink', Page::create('logoff.php')->href()); |
||
501 | |||
502 | $container = Page::create('game_leave_processing.php', ['forward_to' => 'admin/admin_tools.php']); |
||
503 | $template->assign('AdminToolsLink', $container->href()); |
||
504 | |||
505 | $container = Page::create('preferences.php'); |
||
506 | $template->assign('PreferencesLink', $container->href()); |
||
507 | |||
508 | $container = Page::create('album_edit.php'); |
||
509 | $template->assign('EditPhotoLink', $container->href()); |
||
510 | |||
511 | $container = Page::create('bug_report.php'); |
||
512 | $template->assign('ReportABugLink', $container->href()); |
||
513 | |||
514 | $container = Page::create('contact.php'); |
||
515 | $template->assign('ContactFormLink', $container->href()); |
||
516 | |||
517 | $container = Page::create('chat_rules.php'); |
||
518 | $template->assign('IRCLink', $container->href()); |
||
519 | |||
520 | $container = Page::create('donation.php'); |
||
521 | $template->assign('DonateLink', $container->href()); |
||
522 | |||
523 | if ($session->hasGame()) { |
||
524 | $player = $session->getPlayer(); |
||
525 | $template->assign('GameName', SmrGame::getGame($session->getGameID())->getName()); |
||
526 | $template->assign('GameID', $session->getGameID()); |
||
527 | |||
528 | $template->assign('PlotCourseLink', Globals::getPlotCourseHREF()); |
||
529 | |||
530 | $template->assign('TraderLink', Globals::getTraderStatusHREF()); |
||
531 | |||
532 | $template->assign('PoliticsLink', Globals::getPoliticsHREF()); |
||
533 | |||
534 | $container = Page::create('combat_log_list.php'); |
||
535 | $template->assign('CombatLogsLink', $container->href()); |
||
536 | |||
537 | $template->assign('PlanetLink', Globals::getPlanetListHREF($player->getAllianceID())); |
||
538 | |||
539 | $container = Page::create('forces_list.php'); |
||
540 | $template->assign('ForcesLink', $container->href()); |
||
541 | |||
542 | $template->assign('MessagesLink', Globals::getViewMessageBoxesHREF()); |
||
543 | |||
544 | $container = Page::create('news_read_current.php'); |
||
545 | $template->assign('ReadNewsLink', $container->href()); |
||
546 | |||
547 | $container = Page::create('galactic_post_current.php'); |
||
548 | $template->assign('GalacticPostLink', $container->href()); |
||
549 | |||
550 | $container = Page::create('trader_search.php'); |
||
551 | $template->assign('SearchForTraderLink', $container->href()); |
||
552 | |||
553 | $container = Page::create('rankings_player_experience.php'); |
||
554 | $template->assign('RankingsLink', $container->href()); |
||
555 | |||
556 | $container = Page::create('hall_of_fame_new.php'); |
||
557 | $container['game_id'] = $player->getGameID(); |
||
558 | $template->assign('CurrentHallOfFameLink', $container->href()); |
||
559 | |||
560 | $unreadMessages = []; |
||
561 | $dbResult = $db->read('SELECT message_type_id,COUNT(*) FROM player_has_unread_messages WHERE ' . $player->getSQL() . ' GROUP BY message_type_id'); |
||
562 | $container = Page::create('message_view.php'); |
||
563 | foreach ($dbResult->records() as $dbRecord) { |
||
564 | $messageTypeID = $dbRecord->getInt('message_type_id'); |
||
565 | $container['folder_id'] = $messageTypeID; |
||
566 | $unreadMessages[] = [ |
||
567 | 'href' => $container->href(), |
||
568 | 'num' => $dbRecord->getInt('COUNT(*)'), |
||
569 | 'alt' => Messages::getMessageTypeNames($messageTypeID), |
||
570 | 'img' => Messages::getMessageTypeImage($messageTypeID), |
||
571 | ]; |
||
572 | } |
||
573 | $template->assign('UnreadMessages', $unreadMessages); |
||
574 | |||
575 | $container = Page::create('trader_search_result.php'); |
||
576 | $container['player_id'] = $player->getPlayerID(); |
||
577 | $template->assign('PlayerNameLink', $container->href()); |
||
578 | |||
579 | if (is_array(Globals::getHiddenPlayers()) && in_array($player->getAccountID(), Globals::getHiddenPlayers())) { |
||
580 | $template->assign('PlayerInvisible', true); |
||
581 | } |
||
582 | |||
583 | // ******* Hardware ******* |
||
584 | $container = Page::create('configure_hardware.php'); |
||
585 | $template->assign('HardwareLink', $container->href()); |
||
586 | |||
587 | // ******* Forces ******* |
||
588 | $template->assign('ForceDropLink', Page::create('forces_drop.php')->href()); |
||
589 | |||
590 | $ship = $player->getShip(); |
||
591 | $var = Session::getInstance()->getCurrentVar(); |
||
592 | if ($ship->hasMines()) { |
||
593 | $container = Page::create('forces_drop_processing.php'); |
||
594 | $container['owner_id'] = $player->getAccountID(); |
||
595 | $container['drop_mines'] = 1; |
||
596 | $container['referrer'] = $var->file; |
||
597 | $template->assign('DropMineLink', $container->href()); |
||
598 | } |
||
599 | if ($ship->hasCDs()) { |
||
600 | $container = Page::create('forces_drop_processing.php'); |
||
601 | $container['owner_id'] = $player->getAccountID(); |
||
602 | $container['drop_combat_drones'] = 1; |
||
603 | $container['referrer'] = $var->file; |
||
604 | $template->assign('DropCDLink', $container->href()); |
||
605 | } |
||
606 | |||
607 | if ($ship->hasSDs()) { |
||
608 | $container = Page::create('forces_drop_processing.php'); |
||
609 | $container['owner_id'] = $player->getAccountID(); |
||
610 | $container['drop_scout_drones'] = 1; |
||
611 | $container['referrer'] = $var->file; |
||
612 | $template->assign('DropSDLink', $container->href()); |
||
613 | } |
||
614 | |||
615 | $template->assign('CargoJettisonLink', Page::create('cargo_dump.php')->href()); |
||
616 | |||
617 | $template->assign('WeaponReorderLink', Page::create('weapon_reorder.php')->href()); |
||
618 | |||
619 | } |
||
620 | |||
621 | // ------- VOTING -------- |
||
622 | $voteLinks = []; |
||
623 | foreach (VoteSite::cases() as $site) { |
||
624 | $link = new VoteLink($site, $account->getAccountID(), $session->getGameID()); |
||
625 | $voteLinks[] = [ |
||
626 | 'img' => $link->getImg(), |
||
627 | 'url' => $link->getUrl(), |
||
628 | 'sn' => $link->getSN(), |
||
629 | ]; |
||
630 | } |
||
631 | $template->assign('VoteLinks', $voteLinks); |
||
632 | |||
633 | // Determine the minimum time until the next vote across all sites |
||
634 | $minVoteWait = VoteLink::getMinTimeUntilFreeTurns($account->getAccountID(), $session->getGameID()); |
||
635 | if ($minVoteWait <= 0) { |
||
636 | $template->assign('TimeToNextVote', 'now'); |
||
637 | } else { |
||
638 | $template->assign('TimeToNextVote', 'in ' . format_time($minVoteWait, true)); |
||
639 | } |
||
640 | |||
641 | // ------- VERSION -------- |
||
642 | $dbResult = $db->read('SELECT * FROM version ORDER BY went_live DESC LIMIT 1'); |
||
643 | $version = ''; |
||
644 | if ($dbResult->hasRecord()) { |
||
645 | $dbRecord = $dbResult->record(); |
||
646 | $container = Page::create('changelog_view.php'); |
||
647 | $version = create_link($container, 'v' . $dbRecord->getInt('major_version') . '.' . $dbRecord->getInt('minor_version') . '.' . $dbRecord->getInt('patch_level')); |
||
648 | } |
||
649 | |||
650 | $template->assign('Version', $version); |
||
651 | $template->assign('CurrentYear', date('Y', Epoch::time())); |
||
652 | } |
||
653 | |||
654 | /** |
||
655 | * Convert an integer number of seconds into a human-readable time. |
||
656 | * Seconds are omitted to avoid frequent and disruptive ajax updates. |
||
657 | * Use short=true to use 1-letter units (e.g. "1h and 3m"). |
||
658 | * If seconds is negative, will append "ago" to the result. |
||
659 | * If seconds is zero, will return only "now". |
||
660 | * If seconds is <60, will prefix "less than" or "<" (HTML-safe). |
||
661 | */ |
||
662 | function format_time(int $seconds, bool $short = false): string { |
||
663 | if ($seconds == 0) { |
||
664 | return 'now'; |
||
665 | } |
||
666 | |||
667 | if ($seconds < 0) { |
||
668 | $past = true; |
||
669 | $seconds = abs($seconds); |
||
670 | } else { |
||
671 | $past = false; |
||
672 | } |
||
673 | |||
674 | $minutes = ceil($seconds / 60); |
||
675 | $hours = 0; |
||
676 | $days = 0; |
||
677 | $weeks = 0; |
||
678 | if ($minutes >= 60) { |
||
679 | $hours = floor($minutes / 60); |
||
680 | $minutes = $minutes % 60; |
||
681 | } |
||
682 | if ($hours >= 24) { |
||
683 | $days = floor($hours / 24); |
||
684 | $hours = $hours % 24; |
||
685 | } |
||
686 | if ($days >= 7) { |
||
687 | $weeks = floor($days / 7); |
||
688 | $days = $days % 7; |
||
689 | } |
||
690 | $times = [ |
||
691 | 'week' => $weeks, |
||
692 | 'day' => $days, |
||
693 | 'hour' => $hours, |
||
694 | 'minute' => $minutes, |
||
695 | ]; |
||
696 | $parts = []; |
||
697 | foreach ($times as $unit => $amount) { |
||
698 | if ($amount > 0) { |
||
699 | if ($short) { |
||
700 | $parts[] = $amount . $unit[0]; |
||
701 | } else { |
||
702 | $parts[] = pluralise($amount, $unit); |
||
703 | } |
||
704 | } |
||
705 | } |
||
706 | |||
707 | if (count($parts) == 1) { |
||
708 | $result = $parts[0]; |
||
709 | } else { |
||
710 | // e.g. 5h, 10m and 30s |
||
711 | $result = implode(', ', array_slice($parts, 0, -1)) . ' and ' . end($parts); |
||
712 | } |
||
713 | |||
714 | if ($seconds < 60) { |
||
715 | $result = ($short ? '<' : 'less than ') . $result; |
||
716 | } |
||
717 | |||
718 | if ($past) { |
||
719 | $result .= ' ago'; |
||
720 | } |
||
721 | return $result; |
||
722 | } |
||
723 | |||
724 | function number_colour_format(float $number, bool $justSign = false): string { |
||
725 | $formatted = '<span'; |
||
726 | if ($number > 0) { |
||
727 | $formatted .= ' class="green">+'; |
||
728 | } elseif ($number < 0) { |
||
729 | $formatted .= ' class="red">-'; |
||
730 | } else { |
||
731 | $formatted .= '>'; |
||
732 | } |
||
733 | if ($justSign === false) { |
||
734 | $decimalPlaces = 0; |
||
735 | $pos = strpos((string)$number, '.'); |
||
736 | if ($pos !== false) { |
||
737 | $decimalPlaces = strlen(substr((string)$number, $pos + 1)); |
||
738 | } |
||
739 | $formatted .= number_format(abs($number), $decimalPlaces); |
||
740 | } |
||
741 | $formatted .= '</span>'; |
||
742 | return $formatted; |
||
743 | } |
||
744 | |||
745 | |||
746 | /** |
||
747 | * Randomly choose an array key weighted by the array values. |
||
748 | * Probabilities are relative to the total weight. For example: |
||
749 | * |
||
750 | * array( |
||
751 | * 'A' => 1, // 10% chance |
||
752 | * 'B' => 3, // 30% chance |
||
753 | * 'C' => 6, // 60% chance |
||
754 | * ); |
||
755 | * |
||
756 | * @template T of array-key |
||
757 | * @param array<T, float> $choices |
||
758 | * @return T |
||
759 | */ |
||
760 | function getWeightedRandom(array $choices): string|int { |
||
761 | // Normalize the weights so that their sum is 1 |
||
762 | $sumWeight = array_sum($choices); |
||
763 | foreach ($choices as $key => $weight) { |
||
764 | $choices[$key] = $weight / $sumWeight; |
||
765 | } |
||
766 | |||
767 | // Generate a random number between 0 and 1 |
||
768 | $rand = rand() / getrandmax(); |
||
769 | |||
770 | // Subtract weights from the random number until it is negative, |
||
771 | // then return the key associated with that weight. |
||
772 | foreach ($choices as $key => $weight) { |
||
773 | $rand -= $weight; |
||
774 | if ($rand <= 0) { |
||
775 | return $key; |
||
776 | } |
||
777 | } |
||
778 | throw new Exception('Internal error computing weights'); |
||
779 | } |
||
780 |
In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.