diff --git a/assets/game1.js b/assets/game1.js index c6d93f8..4141f60 100644 --- a/assets/game1.js +++ b/assets/game1.js @@ -1,6 +1,9 @@ /* Game1 entry point built with Webpack Encore */ import './styles/game1.css'; +let sequenceFinished = false; +let stillPlayingSound = true; + function subscribeToMercure(mercurePublicUrl, topic) { try { const url = mercurePublicUrl + '?topic=' + encodeURIComponent(topic); @@ -10,6 +13,25 @@ function subscribeToMercure(mercurePublicUrl, topic) { try { const data = JSON.parse(event.data); console.log('[Mercure][game1] Update:', data); + + // data is [sendTo, message] + if (Array.isArray(data) && data.length >= 2) { + const messageContainer = document.getElementById('message-container'); + if (messageContainer) { + const msgEl = document.createElement('div'); + msgEl.className = 'message'; + msgEl.textContent = data[1]; + msgEl.style.color = '#0F0'; // Green for incoming messages + msgEl.style.marginBottom = '10px'; + messageContainer.appendChild(msgEl); + if(stillPlayingSound) + playSound(); + console.log('[Mercure][game1] sequenceFinished status:', sequenceFinished); + if (sequenceFinished) { + flashRed(); + } + } + } } catch (e) { console.log('[Mercure][game1] Raw event:', event.data); } @@ -25,6 +47,28 @@ function subscribeToMercure(mercurePublicUrl, topic) { } } +function playSound() { + const sound = document.getElementById('message-sound'); + if (sound) { + sound.currentTime = 0; + sound.play().catch(e => console.warn('[Audio] Playback failed:', e)); + } +} + +function flashRed() { + console.log('[Game1] Triggering flashRed'); + const body = document.body; + body.classList.remove('flash-red'); + void body.offsetWidth; // Trigger reflow to restart animation + body.classList.add('flash-red'); + + // Also remove it after animation finishes so it's clean for inspection + setTimeout(() => { + body.classList.remove('flash-red'); + console.log('[Game1] Removed flash-red class'); + }, 150); +} + async function fetchJson(url, options = {}) { const opts = { ...options }; const headers = new Headers(opts.headers || {}); @@ -125,16 +169,26 @@ document.addEventListener('DOMContentLoaded', async () => { const printNextMessage = () => { if (currentMessageIndex < messages.length) { + const msg = messages[currentMessageIndex]; const msgEl = document.createElement('div'); - msgEl.className = 'message'; + + let extraClass = ''; + if(msg[2]) + extraClass = msg[2]; + + msgEl.className = 'message ' + extraClass; msgEl.textContent = msg[0]; - msgEl.style.color = '#F00'; msgEl.style.marginBottom = '10px'; messageContainer.appendChild(msgEl); + playSound(); + currentMessageIndex++; setTimeout(printNextMessage, msg[1]); + if (sequenceFinished) { + flashRed(); + } } else { // After it has printed a set of messages, it has to start a timer of 2 seconds console.log('[Game1] All messages printed. Starting 2s timer to expand message-container height...'); @@ -146,6 +200,8 @@ document.addEventListener('DOMContentLoaded', async () => { // Add event listener for Enter key inputField.addEventListener('keypress', async (e) => { if (e.key === 'Enter') { + stillPlayingSound = false; + sequenceFinished = false; const message = inputField.value.trim(); if (message && apiEchoUrl) { inputField.value = ''; @@ -163,6 +219,8 @@ 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'); }, 2000); } }; diff --git a/assets/styles/game1.css b/assets/styles/game1.css index 2028486..83fa243 100644 --- a/assets/styles/game1.css +++ b/assets/styles/game1.css @@ -47,6 +47,10 @@ div#message-container { font-size: 20px; } +div.message { + color: #C0C0C0; +} + div#input { padding: 20px; } @@ -54,10 +58,32 @@ div#input { input#input-message { width: 100%; padding: 10px; - background: #000; - border: 1px solid #F00; - color: #F00; + background: #111; + border: 1px solid #A00000; + color: #C0C0C0; font-size: 18px; box-sizing: border-box; font-family: monospace; } + +.flash-red::after { + content: ''; + position: fixed; + bottom: 0; + left: 0; + right: 0; + height: 100px; + pointer-events: none; + background: linear-gradient(to top, rgba(255, 0, 0, 0.8), transparent); + animation: flash-red-anim 0.1s linear forwards; + z-index: 9999; +} + +@keyframes flash-red-anim { + 0% { + opacity: 1; + } + 100% { + opacity: 0; + } +} diff --git a/public/audio/message.mp3 b/public/audio/message.mp3 new file mode 100644 index 0000000..20b73d1 Binary files /dev/null and b/public/audio/message.mp3 differ diff --git a/src/Game/Enum/SessionSettingType.php b/src/Game/Enum/SessionSettingType.php index f55a3fc..1cdd428 100644 --- a/src/Game/Enum/SessionSettingType.php +++ b/src/Game/Enum/SessionSettingType.php @@ -11,4 +11,5 @@ enum SessionSettingType: string case RIGHTS_FOR_PLAYER2 = 'RightsForPlayer2'; case RIGHTS_FOR_PLAYER3 = 'RightsForPlayer3'; case INVITE_CODE = 'InviteCode'; + case SET_OF_DELETED_FILES = 'SetOfDeletedFiles'; } diff --git a/src/Game/Service/GameResponseService.php b/src/Game/Service/GameResponseService.php index 057b31d..8168f99 100644 --- a/src/Game/Service/GameResponseService.php +++ b/src/Game/Service/GameResponseService.php @@ -8,6 +8,8 @@ use App\Game\Entity\Player; use App\Game\Repository\SessionSettingRepository; use App\Tech\Entity\User; use Symfony\Bundle\SecurityBundle\Security; +use Symfony\Component\Mercure\HubInterface; +use Symfony\Component\Mercure\Update; class GameResponseService { @@ -15,10 +17,11 @@ class GameResponseService private Security $security, private PlayerService $playerService, private SessionSettingRepository $sessionSettingRepository, + private HubInterface $hub, ) { } - public function getGameResponse(string $raw) + public function getGameResponse(string $raw) : array { $info = json_decode($raw, true); @@ -83,8 +86,10 @@ class GameResponseService if(!in_array('chat', $rechten)) return ['result' => ['Unknown command']]; - $this->handleChatMessage($message); - break; + if($this->handleChatMessage($message, $player)) + return ['result' => ['succesfully send']]; + else + return ['result' => ['Error sending']]; case '/help': return ['result' => $this->getHelpCommand($rechten)]; case '/decode': @@ -101,7 +106,6 @@ class GameResponseService default: return ['result' => ['Unknown command']]; } - return []; } private function checkConsoleCommando(string $message, Player $player, bool $sudo = false) : array @@ -112,8 +116,15 @@ class GameResponseService case 'help': return ['result' => $this->getHelpCommand($rechten)]; case 'ls': - break; + if(!in_array('ls', $rechten)) + return ['result' => ['Unknown command']]; + + $files = $this->getAllCurrentFilesInDirectory($player); + return ['result' => $files]; case 'cd': + if(!in_array('cd', $rechten)) + return ['result' => ['Unknown command']]; + $pwd = $this->playerService->getCurrentPwdOfPlayer($player); if(!$pwd) return ['result' => ['Unknown command']]; @@ -124,14 +135,43 @@ class GameResponseService $this->playerService->saveCurrentPwdOfPlayer($player, $newLocation); return ['result' => ['Path: ' . $newLocation]]; + case 'pwd': + if(!in_array('pwd', $rechten)) + return ['result' => ['Unknown command']]; + + $pwd = $this->playerService->getCurrentPwdOfPlayer($player); + return ['result' => ['Path: ' . $pwd]]; case 'rm': - break; + if(!in_array('rm', $rechten)) + return ['result' => ['Unknown command']]; + + $pwd = $this->playerService->getCurrentPwdOfPlayer($player); + if(!$pwd) + return ['result' => ['Unknown command']]; + + if (!isset($messagePart[1])) { + return ['result' => ['Usage: rm {filename}']]; + } + + $filename = $messagePart[1]; + $fullPath = ($pwd === '/' ? '' : $pwd) . '/' . $filename; + + if(!$this->isAllowedToRemove($fullPath, $player, $sudo)) + return ['result' => ['You are not allowed to remove this file.']]; + + $this->playerService->addDeletedFileToSession($player, $fullPath); + return ['result' => ['File removed: ' . $filename]]; case 'sudo': - break; + if(!in_array('sudo', $rechten)) + return ['result' => ['Unknown command']]; + + $sudo = array_shift($messagePart); + $message = implode(' ', $messagePart); + + return $this->checkConsoleCommando($message, $player, true); default: return ['result' => ['Unknown command']]; } - return []; } private function getHelpCommand(mixed $rechten) : array @@ -161,6 +201,13 @@ class GameResponseService $messages[] = ' USAGE: /decode {message}'; $messages[] = ''; break; + case 'pwd': + $messages[] = 'pwd'; + $messages[] = ' This message will let you know what your current location is.'; + $messages[] = ' It will show you the folder you are in so you can continue navigating the server.'; + $messages[] = ' USAGE: pwd'; + $messages[] = ''; + break; case 'cat': $messages[] = 'cat'; $messages[] = ' To read a file, use cat {filename}.'; @@ -210,9 +257,42 @@ class GameResponseService return $messages; } - private function handleChatMessage(string $message) + private function handleChatMessage(string $message, Player $player) : bool { + $messageParts = explode(' ', $message); + $toSingle = false; + if (isset($messageParts[1]) && + is_numeric($messageParts[1]) && + $messageParts[1] >= 1 && + $messageParts[1] <= 3) { + + $toSingle = true; + } + + $chatMessage = array_shift($messageParts); + $sendTo = 0; + + if ($toSingle) { + $sendTo = array_shift($messageParts); + } + + $message = $player->getUser()->getUsername() . ': ' . $chatMessage; + foreach($messageParts as $messagePart) { + $message .= $messagePart . ' '; + } + + $message = trim($message); + + $activeGame = $player->getSession()?->getId(); + + if(is_null($activeGame)) + return false; + + $topic = $_ENV['MERCURE_TOPIC_BASE'] . '/game/hub-' . $activeGame; + $this->hub->publish(new Update($topic, json_encode([$sendTo, $message]))); + + return true; } private function handleDecodeMessage(string $message, Player $player) @@ -329,4 +409,113 @@ class GameResponseService return $paths; } + + private function isAllowedToRemove(string $file, Player $player, bool $sudo) : bool + { + if(!$this->fileExists($file)) + return false; + + if(str_starts_with($file, '/var/rapports/')) + return false; + + $rights = $this->getRechten($player); + + if(in_array('sudo', $rights) || $sudo) + return true; + + $sudoFiles = [ + '/var/arrest/handle.sh', + '/var/arrest/cell.sh', + '/var/marriage/divorce.sh', + ]; + + return !in_array($file, $sudoFiles); + } + + private function fileExists(string $file) + { + $files = $this->getAllPossibleFiles(); + + if(in_array($file, $files)) + return true; + + return false; + } + + private function getAllPossibleFiles() + { + $files = []; + + $files[] = '/var/arrest/handle.sh'; + $files[] = '/var/arrest/bars.sh'; + $files[] = '/var/arrest/cell.sh'; + $files[] = '/var/marriage/share.sh'; + $files[] = '/var/marriage/divorce.sh'; + $files[] = '/var/rapports/095_07-14.txt'; + $files[] = '/var/rapports/007_19-52.txt'; + $files[] = '/var/rapports/083_25-39.txt'; + $files[] = '/var/rapports/019_31-11.txt'; + $files[] = '/var/rapports/075_46-77.txt'; + $files[] = '/var/rapports/031_53-28.txt'; + $files[] = '/var/rapports/072_61-05.txt'; + $files[] = '/var/rapports/064_72-90.txt'; + $files[] = '/var/rapports/091_81-33.txt'; + $files[] = '/var/rapports/079_89-47.txt'; + $files[] = '/var/rapports/098_92-14.txt'; + $files[] = '/var/rapports/012_94-31.txt'; + $files[] = '/var/rapports/016_98-07.txt'; + $files[] = '/var/rapports/087_102-45.txt'; + $files[] = '/var/rapports/094_110-19.txt'; + $files[] = '/var/rapports/063_117-56.txt'; + $files[] = '/var/rapports/017_123-88.txt'; + $files[] = '/var/rapports/093_138-24.txt'; + $files[] = '/var/rapports/001_145-93.txt'; + $files[] = '/var/rapports/index.txt'; + + return $files; + } + + private function getAllCurrentFilesInDirectory(Player $player) : array + { + $pwd = $this->playerService->getCurrentPwdOfPlayer($player); + if (!$pwd) { + return []; + } + + $allPaths = $this->getAllPossiblePaths($player); + $allFiles = $this->getAllPossibleFiles(); + $deletedFiles = $this->playerService->getDeletedFilesOfSession($player); + + $entries = []; + + // Find directories in current pwd + foreach ($allPaths as $path) { + if ($path === $pwd) { + continue; + } + + // Check if $path is a direct child of $pwd + $parent = $this->getPrevPath($path); + if ($parent === $pwd) { + $parts = explode('/', $path); + $entries[] = [end($parts) . '/', 'dir']; + } + } + + // Find files in current pwd + foreach ($allFiles as $file) { + if (in_array($file, $deletedFiles)) { + continue; + } + + $parent = $this->getPrevPath($file); + if ($parent === $pwd) { + $parts = explode('/', $file); + $entries[] = [end($parts), 'file']; + } + } + + sort($entries); + return $entries; + } } diff --git a/src/Game/Service/PlayerService.php b/src/Game/Service/PlayerService.php index 23e8f5c..4f43c63 100644 --- a/src/Game/Service/PlayerService.php +++ b/src/Game/Service/PlayerService.php @@ -4,6 +4,7 @@ namespace App\Game\Service; use App\Game\Entity\Game; use App\Game\Entity\Player; +use App\Game\Entity\SessionSetting; use App\Game\Enum\SessionSettingType; use App\Game\Enum\SessionStatus; use App\Game\Repository\PlayerRepository; @@ -73,4 +74,33 @@ class PlayerService $this->entityManager->persist($setting); $this->entityManager->flush(); } + + public function getDeletedFilesOfSession(Player $player): array + { + $setting = $this->sessionSettingRepository->getSetting($player->getSession(), SessionSettingType::SET_OF_DELETED_FILES); + if (!$setting || !$setting->getValue()) { + return []; + } + + return json_decode($setting->getValue(), true) ?? []; + } + + public function addDeletedFileToSession(Player $player, string $filename): void + { + $setting = $this->sessionSettingRepository->getSetting($player->getSession(), SessionSettingType::SET_OF_DELETED_FILES); + if (!$setting) { + $setting = new SessionSetting(); + $setting->setSession($player->getSession()); + $setting->setName(SessionSettingType::SET_OF_DELETED_FILES); + } + + $deletedFiles = json_decode($setting->getValue() ?? '[]', true) ?? []; + if (!in_array($filename, $deletedFiles)) { + $deletedFiles[] = $filename; + $setting->setValue(json_encode($deletedFiles)); + + $this->entityManager->persist($setting); + $this->entityManager->flush(); + } + } } diff --git a/templates/game/index.html.twig b/templates/game/index.html.twig index e34ee50..b3c67f4 100644 --- a/templates/game/index.html.twig +++ b/templates/game/index.html.twig @@ -15,10 +15,9 @@