Wire up decode puzzle chain and lock terminal on file removal
Connects the previously-disconnected /decode command to per-player messages: player 1 unlocks sudo for everyone, player 2 unlocks a scan command that reveals which files the AI virus has locked, player 3 learns those files should be removed. Also adds a retaliation mechanic: successfully removing a file locks the player's terminal. The AI virus locks it out in red immediately; the mainframe reveals a 12-character recovery code in green after 30 seconds, which can be submitted via /unlock to restore access early, otherwise the terminal auto-recovers after 45 seconds.
This commit is contained in:
+130
-7
@@ -77,6 +77,112 @@ function flashRed() {
|
||||
}, 150);
|
||||
}
|
||||
|
||||
let lockRevealTimer = null;
|
||||
let lockExpireTimer = null;
|
||||
let lockCountdownTimer = null;
|
||||
let currentLockedAt = null;
|
||||
|
||||
function lockMessageClass(messageType) {
|
||||
if (messageType === 'virus') return 'message-virus';
|
||||
if (messageType === 'mainframe') return 'message-mainframe';
|
||||
return '';
|
||||
}
|
||||
|
||||
function appendResultMessage(container, text, messageType) {
|
||||
const msgEl = document.createElement('div');
|
||||
msgEl.className = ('message ' + lockMessageClass(messageType)).trim();
|
||||
msgEl.textContent = text;
|
||||
msgEl.style.marginBottom = '10px';
|
||||
container.appendChild(msgEl);
|
||||
}
|
||||
|
||||
function setInputDisabled(disabled) {
|
||||
const inputField = document.getElementById('input-message');
|
||||
if (inputField) {
|
||||
inputField.disabled = disabled;
|
||||
}
|
||||
}
|
||||
|
||||
function clearLockTimers() {
|
||||
if (lockRevealTimer) { clearTimeout(lockRevealTimer); lockRevealTimer = null; }
|
||||
if (lockExpireTimer) { clearTimeout(lockExpireTimer); lockExpireTimer = null; }
|
||||
if (lockCountdownTimer) { clearInterval(lockCountdownTimer); lockCountdownTimer = null; }
|
||||
}
|
||||
|
||||
function clearLock() {
|
||||
clearLockTimers();
|
||||
currentLockedAt = null;
|
||||
document.body.classList.remove('locked');
|
||||
const banner = document.getElementById('lock-banner');
|
||||
if (banner) banner.style.display = 'none';
|
||||
setInputDisabled(false);
|
||||
}
|
||||
|
||||
function updateLockCountdown(unlockAtMs) {
|
||||
const countdownEl = document.getElementById('lock-countdown');
|
||||
if (!countdownEl) return;
|
||||
const remaining = Math.max(0, Math.ceil((unlockAtMs - Date.now()) / 1000));
|
||||
countdownEl.textContent = remaining + 's';
|
||||
}
|
||||
|
||||
async function fetchLockReveal(apiEchoUrl, messageContainer) {
|
||||
if (!apiEchoUrl) return;
|
||||
try {
|
||||
const response = await fetchJson(apiEchoUrl, {
|
||||
method: 'POST',
|
||||
body: { message: '', ts: new Date().toISOString() },
|
||||
});
|
||||
const result = response && response.result;
|
||||
if (result && Array.isArray(result.result)) {
|
||||
result.result.forEach(text => appendResultMessage(messageContainer, text, result.messageType));
|
||||
window.scrollTo(0, document.body.scrollHeight);
|
||||
}
|
||||
if (result && result.locked === false) {
|
||||
clearLock();
|
||||
return;
|
||||
}
|
||||
// Code has been revealed (or already was), let the player try /unlock
|
||||
setInputDisabled(false);
|
||||
} catch (e) {
|
||||
console.error('[Game1] Failed to fetch lock reveal:', e);
|
||||
}
|
||||
}
|
||||
|
||||
function applyLock(lockData, apiEchoUrl, messageContainer) {
|
||||
if (currentLockedAt === lockData.lockedAt) {
|
||||
return; // already tracking this lock, avoid re-fetching/duplicating messages
|
||||
}
|
||||
currentLockedAt = lockData.lockedAt;
|
||||
|
||||
clearLockTimers();
|
||||
|
||||
const banner = document.getElementById('lock-banner');
|
||||
if (banner) banner.style.display = 'flex';
|
||||
document.body.classList.add('locked');
|
||||
|
||||
const revealAtMs = lockData.revealAt * 1000;
|
||||
const unlockAtMs = lockData.unlockAt * 1000;
|
||||
const now = Date.now();
|
||||
|
||||
if (now < revealAtMs) {
|
||||
setInputDisabled(true);
|
||||
lockRevealTimer = setTimeout(() => fetchLockReveal(apiEchoUrl, messageContainer), revealAtMs - now);
|
||||
} else {
|
||||
fetchLockReveal(apiEchoUrl, messageContainer);
|
||||
}
|
||||
|
||||
lockExpireTimer = setTimeout(() => clearLock(), Math.max(0, unlockAtMs - now));
|
||||
|
||||
updateLockCountdown(unlockAtMs);
|
||||
lockCountdownTimer = setInterval(() => {
|
||||
updateLockCountdown(unlockAtMs);
|
||||
if (Date.now() >= unlockAtMs) {
|
||||
clearInterval(lockCountdownTimer);
|
||||
lockCountdownTimer = null;
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
async function fetchJson(url, options = {}) {
|
||||
const opts = { ...options };
|
||||
const headers = new Headers(opts.headers || {});
|
||||
@@ -133,6 +239,9 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
const apiEchoUrl = cfgEl.dataset.apiEchoUrl;
|
||||
const apiCheckFinishedUrl = cfgEl.dataset.apiCheckFinishedUrl;
|
||||
const lostUrl = cfgEl.dataset.lostUrl;
|
||||
const lockLockedAt = cfgEl.dataset.lockLockedAt;
|
||||
const lockRevealAt = cfgEl.dataset.lockRevealAt;
|
||||
const lockUnlockAt = cfgEl.dataset.lockUnlockAt;
|
||||
|
||||
if (mercurePublicUrl && topic) {
|
||||
subscribeToMercure(mercurePublicUrl, topic, screen);
|
||||
@@ -277,15 +386,20 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
});
|
||||
console.log('[API][game1] message sent →', response);
|
||||
if (response && response.result && Array.isArray(response.result.result)) {
|
||||
response.result.result.forEach(text => {
|
||||
const msgEl = document.createElement('div');
|
||||
msgEl.className = 'message';
|
||||
msgEl.textContent = text;
|
||||
msgEl.style.marginBottom = '10px';
|
||||
messageContainer.appendChild(msgEl);
|
||||
});
|
||||
response.result.result.forEach(text => appendResultMessage(messageContainer, text, response.result.messageType));
|
||||
window.scrollTo(0, document.body.scrollHeight);
|
||||
}
|
||||
if (response && response.result) {
|
||||
if (response.result.locked === true) {
|
||||
applyLock({
|
||||
lockedAt: response.result.lockedAt,
|
||||
revealAt: response.result.revealAt,
|
||||
unlockAt: response.result.unlockAt,
|
||||
}, apiEchoUrl, messageContainer);
|
||||
} else if (response.result.locked === false) {
|
||||
clearLock();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[API][game1] Failed to send message:', err);
|
||||
}
|
||||
@@ -296,6 +410,15 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
console.log('[Game1] message-container height changed to 400vh and input enabled');
|
||||
sequenceFinished = true;
|
||||
console.log('[Game1] sequenceFinished is now TRUE');
|
||||
|
||||
// Restore an in-progress lock after a page refresh
|
||||
if (lockUnlockAt && parseInt(lockUnlockAt, 10) * 1000 > Date.now()) {
|
||||
applyLock({
|
||||
lockedAt: parseInt(lockLockedAt, 10),
|
||||
revealAt: parseInt(lockRevealAt, 10),
|
||||
unlockAt: parseInt(lockUnlockAt, 10),
|
||||
}, apiEchoUrl, messageContainer);
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -55,6 +55,48 @@ div.message {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
div.message-virus {
|
||||
color: #F00;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
div.message-mainframe {
|
||||
color: #0F0;
|
||||
}
|
||||
|
||||
div#lock-banner {
|
||||
position: fixed;
|
||||
top: 68px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
padding: 12px 20px;
|
||||
background-color: #200;
|
||||
border-top: 1px solid #F00;
|
||||
border-bottom: 1px solid #F00;
|
||||
color: #F00;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
letter-spacing: 1px;
|
||||
z-index: 99;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
animation: lock-banner-pulse 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes lock-banner-pulse {
|
||||
0%, 100% {
|
||||
background-color: #200;
|
||||
}
|
||||
50% {
|
||||
background-color: #400;
|
||||
}
|
||||
}
|
||||
|
||||
body.locked div#message-container {
|
||||
padding-top: 130px;
|
||||
}
|
||||
|
||||
div#input {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use App\Game\Repository\GameRepository;
|
||||
use App\Game\Repository\PlayerRepository;
|
||||
use App\Game\Repository\SessionRepository;
|
||||
use App\Game\Service\GameDashboardService;
|
||||
use App\Game\Service\GameResponseService;
|
||||
use App\Tech\Entity\User;
|
||||
use App\Game\Service\PlayerService;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
@@ -115,7 +116,8 @@ final class GameController extends AbstractController
|
||||
Request $request,
|
||||
Security $security,
|
||||
PlayerRepository $playerRepository,
|
||||
GameDashboardService $dashboardService
|
||||
GameDashboardService $dashboardService,
|
||||
GameResponseService $gameResponseService
|
||||
): Response
|
||||
{
|
||||
$user = $security->getUser();
|
||||
@@ -157,11 +159,13 @@ final class GameController extends AbstractController
|
||||
|
||||
$screen = $player ? $player->getScreen() : 0;
|
||||
$session_id = $session->getId();
|
||||
$lock = $player ? $gameResponseService->getPublicLockState($player) : null;
|
||||
|
||||
return $this->render('game/index.html.twig', [
|
||||
'session' => $session,
|
||||
'screen' => $screen,
|
||||
'session_id' => $session_id,
|
||||
'lock' => $lock,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace App\Game\Enum;
|
||||
|
||||
enum DecodeMessage: string
|
||||
{
|
||||
case TEST = 'This is a test decoding message.';
|
||||
case SECRET = 'The secret code is 42.';
|
||||
case WELCOME = 'Welcome to the system, agent.';
|
||||
case PLAYER_1 = 'Sudo is now available';
|
||||
case PLAYER_2 = 'AI virus protects its own files by replacing them';
|
||||
case PLAYER_3 = 'The locked up bash files should be removed to lock it up';
|
||||
}
|
||||
|
||||
@@ -74,4 +74,14 @@ enum SessionSettingType: string
|
||||
case FEEDBACK_ENTERTAINING = 'FeedbackEntertaining';
|
||||
case FEEDBACK_THEME = 'FeedbackTheme';
|
||||
case FEEDBACK_TEXT = 'FeedbackText';
|
||||
case LOCK_FOR_PLAYER1 = 'LockForPlayer1';
|
||||
case LOCK_FOR_PLAYER2 = 'LockForPlayer2';
|
||||
case LOCK_FOR_PLAYER3 = 'LockForPlayer3';
|
||||
case LOCK_FOR_PLAYER4 = 'LockForPlayer4';
|
||||
case LOCK_FOR_PLAYER5 = 'LockForPlayer5';
|
||||
case LOCK_FOR_PLAYER6 = 'LockForPlayer6';
|
||||
case LOCK_FOR_PLAYER7 = 'LockForPlayer7';
|
||||
case LOCK_FOR_PLAYER8 = 'LockForPlayer8';
|
||||
case LOCK_FOR_PLAYER9 = 'LockForPlayer9';
|
||||
case LOCK_FOR_PLAYER10 = 'LockForPlayer10';
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Game\Service;
|
||||
use App\Game\Enum\DecodeMessage;
|
||||
use App\Game\Enum\SessionSettingType;
|
||||
use App\Game\Entity\Player;
|
||||
use App\Game\Entity\Session;
|
||||
use App\Game\Entity\SessionSetting;
|
||||
use App\Game\Repository\SessionSettingRepository;
|
||||
use App\Tech\Entity\User;
|
||||
@@ -15,6 +16,10 @@ use Symfony\Component\Mercure\Update;
|
||||
|
||||
class GameResponseService
|
||||
{
|
||||
private const LOCK_REVEAL_AFTER_SECONDS = 30;
|
||||
private const LOCK_DURATION_SECONDS = 45;
|
||||
private const LOCK_PASSCODE_LENGTH = 12;
|
||||
|
||||
public function __construct(
|
||||
private Security $security,
|
||||
private PlayerService $playerService,
|
||||
@@ -25,6 +30,25 @@ class GameResponseService
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Public, code-free view of a player's current lock state, safe to expose on page load
|
||||
* (e.g. so the UI can restore the lock banner/countdown after a refresh).
|
||||
*/
|
||||
public function getPublicLockState(Player $player): ?array
|
||||
{
|
||||
$lock = $this->getLockState($player);
|
||||
|
||||
if ($lock === null || time() >= $lock['unlockAt']) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'lockedAt' => $lock['lockedAt'],
|
||||
'revealAt' => $lock['revealAt'],
|
||||
'unlockAt' => $lock['unlockAt'],
|
||||
];
|
||||
}
|
||||
|
||||
public function getGameResponse(string $raw) : array
|
||||
{
|
||||
$info = json_decode($raw, true);
|
||||
@@ -47,13 +71,15 @@ class GameResponseService
|
||||
|
||||
$this->logSessionActivity($player, 'PLAYER: ' . $message);
|
||||
|
||||
$data = [];
|
||||
$data = $this->handleLockedPlayer($message, $player);
|
||||
|
||||
if(str_starts_with($message, '/')) {
|
||||
if ($data === null) {
|
||||
if (str_starts_with($message, '/')) {
|
||||
$data = $this->checkGameCommando($message, $player);
|
||||
} else {
|
||||
$data = $this->checkConsoleCommando($message, $player);
|
||||
}
|
||||
}
|
||||
|
||||
$responseLog = '';
|
||||
if (isset($data['result']) && is_array($data['result'])) {
|
||||
@@ -202,7 +228,19 @@ class GameResponseService
|
||||
return ['result' => ['You are not allowed to remove this file.']];
|
||||
|
||||
$this->playerService->addDeletedFileToSession($player, $fullPath);
|
||||
return ['result' => ['File removed: ' . $filename]];
|
||||
$lock = $this->triggerLock($player);
|
||||
|
||||
return [
|
||||
'result' => [
|
||||
'File removed: ' . $filename,
|
||||
'AI VIRUS: Intrusion detected. Locking down your terminal...',
|
||||
],
|
||||
'messageType' => 'virus',
|
||||
'locked' => true,
|
||||
'lockedAt' => $lock['lockedAt'],
|
||||
'revealAt' => $lock['revealAt'],
|
||||
'unlockAt' => $lock['unlockAt'],
|
||||
];
|
||||
case 'sudo':
|
||||
if(!in_array('sudo', $rechten))
|
||||
return ['result' => ['Unknown command']];
|
||||
@@ -211,6 +249,17 @@ class GameResponseService
|
||||
$message = implode(' ', $messagePart);
|
||||
|
||||
return $this->checkConsoleCommando($message, $player, true);
|
||||
case 'scan':
|
||||
if(!in_array('scan', $rechten))
|
||||
return ['result' => ['Unknown command']];
|
||||
|
||||
$result = ['Running quarantine scan...', 'Locked files detected:'];
|
||||
foreach ($this->getLockedFiles() as $lockedFile) {
|
||||
$result[] = ' ' . $lockedFile;
|
||||
}
|
||||
$result[] = 'These files are protected by the AI virus. Use sudo rm {file} to remove them.';
|
||||
|
||||
return ['result' => $result];
|
||||
default:
|
||||
return ['result' => ['Unknown command']];
|
||||
}
|
||||
@@ -287,6 +336,12 @@ class GameResponseService
|
||||
$messages[] = ' USAGE: sudo {command}';
|
||||
$messages[] = '';
|
||||
break;
|
||||
case 'scan':
|
||||
$messages[] = 'scan';
|
||||
$messages[] = ' Runs a quarantine scan that reveals which files are locked by the AI virus.';
|
||||
$messages[] = ' USAGE: scan';
|
||||
$messages[] = '';
|
||||
break;
|
||||
case 'verify':
|
||||
$messages[] = '/verify';
|
||||
$messages[] = ' You can verify yourself by using this command.';
|
||||
@@ -470,24 +525,212 @@ class GameResponseService
|
||||
}
|
||||
}
|
||||
|
||||
private function handleDecodeMessage(string $message, Player $player)
|
||||
private function handleDecodeMessage(string $message, Player $player): string
|
||||
{
|
||||
$userNumber = $player->getScreen();
|
||||
|
||||
preg_match('/\d+/', $message, $matches);
|
||||
preg_match('/\d/', $message, $matches);
|
||||
$num = $matches[0] ?? null;
|
||||
$randomString = $this->generateRandomString(250, 500);
|
||||
|
||||
if(is_null($num) || $num != $userNumber)
|
||||
if (is_null($num) || (int)$num !== $userNumber) {
|
||||
return $randomString;
|
||||
}
|
||||
|
||||
$decodeMessage = match ($userNumber) {
|
||||
1 => DecodeMessage::PLAYER_1,
|
||||
2 => DecodeMessage::PLAYER_2,
|
||||
3 => DecodeMessage::PLAYER_3,
|
||||
default => null,
|
||||
};
|
||||
|
||||
if ($decodeMessage === null) {
|
||||
return $randomString;
|
||||
}
|
||||
|
||||
if ($decodeMessage === DecodeMessage::PLAYER_1) {
|
||||
$this->grantRightToAllPlayers($player->getSession(), 'sudo');
|
||||
}
|
||||
|
||||
if ($decodeMessage === DecodeMessage::PLAYER_2) {
|
||||
$this->grantRightToAllPlayers($player->getSession(), 'scan');
|
||||
}
|
||||
|
||||
foreach (DecodeMessage::cases() as $decodeMessage) {
|
||||
if ($decodeMessage->name === $message) {
|
||||
return $decodeMessage->value;
|
||||
}
|
||||
|
||||
private function grantRightToAllPlayers(Session $session, string $right): void
|
||||
{
|
||||
$updated = false;
|
||||
|
||||
foreach ($session->getPlayers() as $sessionPlayer) {
|
||||
$rightsSettingName = SessionSettingType::tryFrom('RightsForPlayer' . $sessionPlayer->getScreen());
|
||||
if (!$rightsSettingName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return $randomString;
|
||||
$setting = $this->sessionSettingRepository->getSetting($session, $rightsSettingName, $sessionPlayer);
|
||||
if (!$setting) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rights = json_decode($setting->getValue() ?? '[]', true) ?? [];
|
||||
if (!in_array($right, $rights)) {
|
||||
$rights[] = $right;
|
||||
$setting->setValue(json_encode($rights));
|
||||
$this->entityManager->persist($setting);
|
||||
$updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($updated) {
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns null if the player isn't locked (or their lock already expired), otherwise
|
||||
* a result array to short-circuit normal command handling.
|
||||
*/
|
||||
private function handleLockedPlayer(string $message, Player $player): ?array
|
||||
{
|
||||
$lock = $this->getLockState($player);
|
||||
|
||||
if ($lock === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$now = time();
|
||||
|
||||
if ($now >= $lock['unlockAt']) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$trimmed = trim($message);
|
||||
|
||||
if (stripos($trimmed, '/unlock ') === 0) {
|
||||
$attempt = trim(substr($trimmed, 8));
|
||||
|
||||
if ($now >= $lock['revealAt'] && hash_equals($lock['code'], $attempt)) {
|
||||
$this->setLockUnlockAt($player, $now);
|
||||
|
||||
return [
|
||||
'result' => ['ACCESS RESTORED. Welcome back, agent.'],
|
||||
'messageType' => 'mainframe',
|
||||
'locked' => false,
|
||||
];
|
||||
}
|
||||
|
||||
return $this->lockStatusResponse('ACCESS DENIED: Incorrect passcode.', 'virus', $lock);
|
||||
}
|
||||
|
||||
if ($now >= $lock['revealAt']) {
|
||||
return $this->lockStatusResponse(
|
||||
'MAINFRAME: Recovery code acquired: ' . $lock['code'] . '. Use /unlock ' . $lock['code'] . ' to restore access.',
|
||||
'mainframe',
|
||||
$lock
|
||||
);
|
||||
}
|
||||
|
||||
return $this->lockStatusResponse(
|
||||
'AI VIRUS: SYSTEM LOCKED. Countermeasures engaged. Stand by for mainframe recovery protocol.',
|
||||
'virus',
|
||||
$lock
|
||||
);
|
||||
}
|
||||
|
||||
private function lockStatusResponse(string $text, string $messageType, array $lock): array
|
||||
{
|
||||
return [
|
||||
'result' => [$text],
|
||||
'messageType' => $messageType,
|
||||
'locked' => true,
|
||||
'lockedAt' => $lock['lockedAt'],
|
||||
'revealAt' => $lock['revealAt'],
|
||||
'unlockAt' => $lock['unlockAt'],
|
||||
];
|
||||
}
|
||||
|
||||
private function getLockState(Player $player): ?array
|
||||
{
|
||||
$settingName = SessionSettingType::tryFrom('LockForPlayer' . $player->getScreen());
|
||||
if (!$settingName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$setting = $this->sessionSettingRepository->getSetting($player->getSession(), $settingName, $player);
|
||||
if (!$setting || !$setting->getValue()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$state = json_decode($setting->getValue(), true);
|
||||
if (!is_array($state) || !isset($state['lockedAt'], $state['revealAt'], $state['unlockAt'], $state['code'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $state;
|
||||
}
|
||||
|
||||
private function triggerLock(Player $player): array
|
||||
{
|
||||
$now = time();
|
||||
$state = [
|
||||
'lockedAt' => $now,
|
||||
'revealAt' => $now + self::LOCK_REVEAL_AFTER_SECONDS,
|
||||
'unlockAt' => $now + self::LOCK_DURATION_SECONDS,
|
||||
'code' => $this->generatePasscode(),
|
||||
];
|
||||
|
||||
$settingName = SessionSettingType::tryFrom('LockForPlayer' . $player->getScreen());
|
||||
if ($settingName) {
|
||||
$setting = $this->sessionSettingRepository->getSetting($player->getSession(), $settingName, $player);
|
||||
if (!$setting) {
|
||||
$setting = new SessionSetting();
|
||||
$setting->setSession($player->getSession());
|
||||
$setting->setPlayer($player);
|
||||
$setting->setName($settingName);
|
||||
}
|
||||
|
||||
$setting->setValue(json_encode($state));
|
||||
$this->entityManager->persist($setting);
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
return $state;
|
||||
}
|
||||
|
||||
private function setLockUnlockAt(Player $player, int $unlockAt): void
|
||||
{
|
||||
$settingName = SessionSettingType::tryFrom('LockForPlayer' . $player->getScreen());
|
||||
if (!$settingName) {
|
||||
return;
|
||||
}
|
||||
|
||||
$setting = $this->sessionSettingRepository->getSetting($player->getSession(), $settingName, $player);
|
||||
if (!$setting || !$setting->getValue()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$state = json_decode($setting->getValue(), true);
|
||||
if (!is_array($state)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$state['unlockAt'] = $unlockAt;
|
||||
$setting->setValue(json_encode($state));
|
||||
$this->entityManager->persist($setting);
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
private function generatePasscode(): string
|
||||
{
|
||||
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
$charactersLength = strlen($characters);
|
||||
$code = '';
|
||||
for ($i = 0; $i < self::LOCK_PASSCODE_LENGTH; $i++) {
|
||||
$code .= $characters[random_int(0, $charactersLength - 1)];
|
||||
}
|
||||
return $code;
|
||||
}
|
||||
|
||||
private function generateRandomString(int $min, int $max): string
|
||||
@@ -770,13 +1013,16 @@ class GameResponseService
|
||||
if(in_array('sudo', $rights) || $sudo)
|
||||
return true;
|
||||
|
||||
$sudoFiles = [
|
||||
return !in_array($file, $this->getLockedFiles());
|
||||
}
|
||||
|
||||
private function getLockedFiles() : array
|
||||
{
|
||||
return [
|
||||
'/var/arrest/handle.sh',
|
||||
'/var/arrest/cell.sh',
|
||||
'/var/marriage/divorce.sh',
|
||||
];
|
||||
|
||||
return !in_array($file, $sudoFiles);
|
||||
}
|
||||
|
||||
private function fileExists(string $file, Player $player) : bool
|
||||
|
||||
@@ -21,12 +21,21 @@
|
||||
data-api-check-finished-url="{{ path('game_api_check_finished', {session: session.id})|e('html_attr') }}"
|
||||
data-lost-url="{{ path('game_lost', {session: session.id})|e('html_attr') }}"
|
||||
data-screen="{{ screen|e('html_attr') }}"
|
||||
{% if lock %}
|
||||
data-lock-locked-at="{{ lock.lockedAt }}"
|
||||
data-lock-reveal-at="{{ lock.revealAt }}"
|
||||
data-lock-unlock-at="{{ lock.unlockAt }}"
|
||||
{% endif %}
|
||||
style="display:none">
|
||||
</div>
|
||||
|
||||
<div id="game-timer" data-end-time="{{ session.timer }}">
|
||||
--:--:--
|
||||
</div>
|
||||
<div id="lock-banner" style="display:none">
|
||||
<div id="lock-banner-text">AI VIRUS: SYSTEM LOCKED</div>
|
||||
<div id="lock-countdown">--</div>
|
||||
</div>
|
||||
<div id="message-container">
|
||||
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user