/* Pregame (commandline tutorial) entry point built with Webpack Encore */ import './styles/pregame.css'; function appendMessage(container, text, extraClass = '') { const el = document.createElement('div'); el.className = ('message ' + extraClass).trim(); el.textContent = text; container.appendChild(el); window.scrollTo(0, document.body.scrollHeight); } document.addEventListener('DOMContentLoaded', () => { console.log('Pregame bundle loaded'); const messageContainer = document.getElementById('message-container'); const inputField = document.getElementById('input-message'); if (!messageContainer || !inputField) { return; } const bootMessages = [ ['Booting tutorial terminal...', 500], ['Loading command line basics...', 800], ['"Welcome agent. Lets go through the basics together.', 2000], ['Let us start with the help command. Type help in the terminal (the input field at the bottom of the screen and press enter.', 500], ]; // Same command help text as GameResponseService::getHelpCommand(), minus "scan". const helpMessages = [ 'Help function:', '', 'pwd', ' This message will let you know what your current location is.', ' It will show you the folder you are in so you can continue navigating the server.', ' USAGE: pwd', '', 'cd', ' Use cd to move to a different directory.', ' You can go into a folder by using cd {foldername}, or a folder up by using "cd ..".', ' Using cd / moves you to the root directory.', ' USAGE: cd {directory}', '', 'ls', ' To show all the files in the current directory, use ls.', ' This will print the full list of directories and files of the current location on your screen.', ' USAGE: ls', '', 'cat', ' To read a file, use cat {filename}.', ' This will print the full content of the file on the screen.', ' USAGE: cat {filename}', '', 'rm', ' Use rm to delete a file.', ' Be careful with this command. It can not be undone and we do not want to lose any valuable data.', ' USAGE: rm {filename}', '', 'sudo', ' If you do not have enough rights to execute a command, you can use sudo to execute it as root.', ' This is only possible for verified users. To verify yourself, use the /verify command.', ' USAGE: sudo {command}', '', ]; // Narrative shown after the help output finishes, introducing the next step. // Each entry is [delayBeforeShowing, text, cssClass]. const helpFollowUp = [ [2000, 'Well done, you now have a complete overview of the terminal commands this game uses.', 'message-mainframe'], [500, 'Not all commands will be available immediately. But lets go through them one by one.', 'message-mainframe'], [1000, 'Lets start with the command pwd. Enter this in the terminal (input field) now and press enter.', 'message-mainframe'], ]; const pwdFollowUp = [ [1000, 'You now know the location you are in on the terminal. /var/home/agent.', 'message-mainframe'], [500, 'You can change directories with the cd command.', 'message-mainframe'], [500, "If you type 'cd ..' in the terminal, you will go one directory up to /var/home", 'message-mainframe'], [500, 'Try this now.', 'message-mainframe'], ]; const cdFollowUp = [ [1000, 'Good. Please check the directory you are in now.', 'message-mainframe'], ]; const pwd2FollowUp = [ [1000, 'As you can see, you are now in the /var/home directory', 'message-mainframe'], [500, 'You can move between directories like this.', 'message-mainframe'], ]; const wrongPwdMessage = "That is not the command which gives you the location you are in. You can check your current folder with the pwd command."; const lsIntro = [ [1000, 'Now lets see what is actually in this directory. The ls command lists all files and folders in your current location.', 'message-mainframe'], [500, 'Type ls now.', 'message-mainframe'], ]; const lsListing = [ 'agent,dir', 'peter,dir', 'james,dir', 'william,dir', 'system,dir', ]; const lsFollowUp = [ [1000, 'You can see several folders here, including your own: agent.', 'message-mainframe'], [500, 'Lets go back to your own directory. Use cd agent to enter it.', 'message-mainframe'], [250, 'Remember, cd /var/home/agent also works to get there directly from anywhere on the server.', 'message-mainframe'], ]; const cdAgentFollowUp = [ [1000, 'Lets check if you now really are in your own home folder.', 'message-mainframe'], ]; const verifyAgentFollowUp = [ [1000, "Well done, you're right back where you started.", 'message-mainframe'], [500, 'Now lets move to the next command: cat.', 'message-mainframe'], [500, 'First, lets see what is inside this folder.', 'message-mainframe'], [500, 'Type ls to check the content of /var/home/agent.', 'message-mainframe'], ]; const lsAgentListing = [ 'HelloWorld.txt,file', 'notes.txt,file', 'schedule.txt,file', 'contacts.db,file', 'keycard.dat,file', 'backup.zip,file', ]; const lsAgentFinalListing = [ 'notes.txt,file', 'schedule.txt,file', 'contacts.db,file', 'keycard.dat,file', 'backup.zip,file', ]; const lsAgentFollowUp = [ [1000, 'There it is: HelloWorld.txt, right there in the list.', 'message-mainframe'], [500, 'Lets read what is inside. Type cat HelloWorld.txt to open the file.', 'message-mainframe'], ]; const helloWorldContent = [ 'Day 1 at the agency. My mentor says curiosity keeps you sharp -- and alive.', "- I keep a spare set of shoelaces in my desk. You'd be surprised how handy that is.", '- The coffee machine on this floor is cursed. Never trust it before 9am.', '- Jared Williams owes me a favor after the incident with the elevator. He should not have forgotten that.', '', 'If you are reading this, agent: welcome to the team. Stay sharp out there.', ]; const catFollowUp = [ [1000, 'Nice work. cat is perfect for quickly reading any file on the server.', 'message-mainframe'], ]; const rmIntro = [ [1000, "There's one more command left in the basics: rm.", 'message-mainframe'], [500, 'This command permanently deletes a file, so use it with caution.', 'message-mainframe'], [500, 'Try removing HelloWorld.txt now. Type rm HelloWorld.txt.', 'message-mainframe'], ]; const rmDeniedMessages = [ 'Permission denied: HelloWorld.txt is protected against removal.', 'You need sudo rights to remove this file.', ]; const rmFollowUp = [ [1000, 'sudo can technically be used for any command, at any moment.', 'message-mainframe'], [500, 'But from a security standpoint, it is better not to use it unless it is truly necessary.', 'message-mainframe'], [500, 'Only reach for sudo in extreme cases -- like this one, where a file is actively protected.', 'message-mainframe'], [500, 'Try it now. Type sudo rm HelloWorld.txt.', 'message-mainframe'], ]; const sudoRmFollowUp = [ [1000, 'Well done. Since you used sudo, the protection was bypassed and the file was deleted.', 'message-mainframe'], [500, 'Lets check the folder once more to confirm it is really gone. Type ls now.', 'message-mainframe'], ]; const tutorialEndFollowUp = [ [1000, 'As you can see, HelloWorld.txt is no longer in the list. It has been permanently removed.', 'message-mainframe'], [500, 'That concludes the basics of the terminal, agent. You are ready for the real mission.', 'message-mainframe'], [1000, 'Please be aware more commands can exist to help this mission succeed. These commands will show up in the help command. Good luck!', 'message-mainframe'] ]; function playSequence(items, container, onDone) { let i = 0; const step = () => { if (i >= items.length) { if (onDone) onDone(); return; } const [delay, text, cls] = items[i]; i++; setTimeout(() => { appendMessage(container, text, cls); step(); }, delay); }; step(); } // Tracks which command the tutorial is currently guiding the player towards. let tutorialStage = 'help'; let currentMessageIndex = 0; const printNextMessage = () => { if (currentMessageIndex < bootMessages.length) { const [text, delay] = bootMessages[currentMessageIndex]; appendMessage(messageContainer, text, 'message-mainframe'); currentMessageIndex++; setTimeout(printNextMessage, delay); return; } inputField.disabled = false; inputField.focus(); inputField.addEventListener('keypress', (e) => { if (e.key !== 'Enter') { return; } const value = inputField.value.trim(); inputField.value = ''; if (!value) { return; } appendMessage(messageContainer, value); if (value.toLowerCase() === 'help' || value.toLowerCase() === '/help') { // No extraClass -> default "message" color, same as the real game's help output. helpMessages.forEach((line) => appendMessage(messageContainer, line)); if (tutorialStage === 'help') { playSequence(helpFollowUp, messageContainer, () => { tutorialStage = 'pwd'; }); } return; } if (tutorialStage === 'pwd' && value.toLowerCase() === 'pwd') { appendMessage(messageContainer, '/var/home/agent'); playSequence(pwdFollowUp, messageContainer, () => { tutorialStage = 'cd'; }); return; } if (tutorialStage === 'cd' && value.toLowerCase() === 'cd ..') { playSequence(cdFollowUp, messageContainer, () => { tutorialStage = 'pwd2'; }); return; } if (tutorialStage === 'pwd2') { if (value.toLowerCase() === 'pwd') { appendMessage(messageContainer, '/var/home'); playSequence(pwd2FollowUp, messageContainer, () => { playSequence(lsIntro, messageContainer, () => { tutorialStage = 'ls'; }); }); } else { appendMessage(messageContainer, wrongPwdMessage, 'message-hint'); } return; } if (tutorialStage === 'ls' && value.toLowerCase() === 'ls') { lsListing.forEach((name) => appendMessage(messageContainer, name)); playSequence(lsFollowUp, messageContainer, () => { tutorialStage = 'cd-agent'; }); return; } if ( tutorialStage === 'cd-agent' && (value.toLowerCase() === 'cd agent' || value.toLowerCase() === 'cd /var/home/agent') ) { playSequence(cdAgentFollowUp, messageContainer, () => { tutorialStage = 'verify-agent'; }); return; } if (tutorialStage === 'verify-agent' && value.toLowerCase() === 'pwd') { appendMessage(messageContainer, '/var/home/agent'); playSequence(verifyAgentFollowUp, messageContainer, () => { tutorialStage = 'ls-agent'; }); return; } if (tutorialStage === 'ls-agent' && value.toLowerCase() === 'ls') { lsAgentListing.forEach((name) => appendMessage(messageContainer, name)); playSequence(lsAgentFollowUp, messageContainer, () => { tutorialStage = 'cat-file'; }); return; } if (tutorialStage === 'cat-file' && value.toLowerCase() === 'cat helloworld.txt') { helloWorldContent.forEach((line) => appendMessage(messageContainer, line)); playSequence(catFollowUp, messageContainer, () => { playSequence(rmIntro, messageContainer, () => { tutorialStage = 'rm'; }); }); return; } if (tutorialStage === 'rm' && value.toLowerCase() === 'rm helloworld.txt') { rmDeniedMessages.forEach((line) => appendMessage(messageContainer, line, 'message-hint')); playSequence(rmFollowUp, messageContainer, () => { tutorialStage = 'sudo-rm'; }); return; } if (tutorialStage === 'sudo-rm' && value.toLowerCase() === 'sudo rm helloworld.txt') { appendMessage(messageContainer, 'File removed: HelloWorld.txt'); playSequence(sudoRmFollowUp, messageContainer, () => { tutorialStage = 'ls-confirm'; }); return; } if (tutorialStage === 'ls-confirm' && value.toLowerCase() === 'ls') { lsAgentFinalListing.forEach((name) => appendMessage(messageContainer, name)); playSequence(tutorialEndFollowUp, messageContainer, () => { tutorialStage = 'complete'; inputField.disabled = true; }); return; } // Placeholder response until the rest of the tutorial content is defined. appendMessage(messageContainer, 'Command not recognized yet.', 'message-hint'); }); }; printNextMessage(); });