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/Service/GameResponseService.php b/src/Game/Service/GameResponseService.php index 057b31d..0dfccb6 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 @@ -124,6 +128,9 @@ class GameResponseService $this->playerService->saveCurrentPwdOfPlayer($player, $newLocation); return ['result' => ['Path: ' . $newLocation]]; + case 'pwd': + $pwd = $this->playerService->getCurrentPwdOfPlayer($player); + return ['result' => ['Path: ' . $pwd]]; case 'rm': break; case 'sudo': @@ -161,6 +168,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 +224,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) 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 @@
@@ -28,6 +27,9 @@
+