mirror of
https://github.com/tcsenpai/shellquest.git
synced 2025-06-06 03:05:31 +00:00
648 lines
17 KiB
Markdown
648 lines
17 KiB
Markdown
You're right! Let's enhance the LEVELS.md guide with more detailed examples of how to implement complex level mechanics like file system navigation, process management, and other Linux-like features.
|
|
|
|
````markdown:LEVELS.md
|
|
# LEVELS.md - Guide to Adding New Levels
|
|
|
|
## Introduction
|
|
|
|
Terminal Escape is designed to be easily expandable with new levels. This guide explains how to create and integrate new levels into the game.
|
|
|
|
## Level Structure
|
|
|
|
Each level is defined as an object that implements the `Level` interface. A level consists of:
|
|
|
|
1. **Basic Information**: ID, name, and description
|
|
2. **State Management**: Methods to initialize and manage level-specific state
|
|
3. **User Interface**: Methods to render the level and handle user input
|
|
4. **Hints**: An array of progressive hints for players who get stuck
|
|
|
|
## Creating a New Level
|
|
|
|
### Step 1: Create a new file
|
|
|
|
Create a new file in the `src/levels` directory, e.g., `levelX.ts` where X is the next level number.
|
|
|
|
### Step 2: Implement the Level interface
|
|
|
|
```typescript
|
|
import { Level, LevelResult, registerLevel } from '../core/levelSystem';
|
|
import { getCurrentGameState } from '../core/gameState';
|
|
import { levelUI } from '../ui/levelRenderer'; // Optional, for enhanced UI
|
|
|
|
const level: Level = {
|
|
id: X, // Replace X with the next level number
|
|
name: 'Your Level Name',
|
|
description: 'Brief description of your level',
|
|
|
|
async initialize() {
|
|
const gameState = getCurrentGameState();
|
|
if (!gameState) return;
|
|
|
|
// Initialize level state if not already present
|
|
if (!gameState.levelStates[this.id]) {
|
|
gameState.levelStates[this.id] = {
|
|
// Define your level-specific state here
|
|
// For example:
|
|
attempts: 0,
|
|
someFlag: false,
|
|
// Add any other state variables your level needs
|
|
};
|
|
}
|
|
},
|
|
|
|
async render() {
|
|
const gameState = getCurrentGameState();
|
|
if (!gameState) return;
|
|
|
|
// Make sure level state is initialized
|
|
if (!gameState.levelStates[this.id]) {
|
|
await this.initialize();
|
|
}
|
|
|
|
const levelState = gameState.levelStates[this.id];
|
|
|
|
// Display level information and UI
|
|
console.log('Your level description and UI goes here');
|
|
console.log('');
|
|
|
|
// Display available commands
|
|
console.log('Commands: "command1", "command2", etc.');
|
|
},
|
|
|
|
async handleInput(input: string): Promise<LevelResult> {
|
|
const gameState = getCurrentGameState();
|
|
if (!gameState) {
|
|
return { completed: false };
|
|
}
|
|
|
|
// Make sure level state is initialized
|
|
if (!gameState.levelStates[this.id]) {
|
|
await this.initialize();
|
|
}
|
|
|
|
const levelState = gameState.levelStates[this.id];
|
|
const command = input.trim();
|
|
|
|
// Split command into parts
|
|
const parts = command.split(' ');
|
|
const cmd = parts[0].toLowerCase();
|
|
|
|
// Handle different commands
|
|
if (cmd === 'command1') {
|
|
// Do something
|
|
return {
|
|
completed: false,
|
|
message: 'Response to command1'
|
|
};
|
|
}
|
|
|
|
// Handle level completion
|
|
if (cmd === 'win_command') {
|
|
return {
|
|
completed: true,
|
|
message: 'Congratulations! You completed the level.',
|
|
nextAction: 'next_level'
|
|
};
|
|
}
|
|
|
|
// Default response for unknown commands
|
|
return {
|
|
completed: false,
|
|
message: 'Unknown command. Try something else.'
|
|
};
|
|
},
|
|
|
|
hints: [
|
|
'First hint - very subtle',
|
|
'Second hint - more direct',
|
|
'Third hint - almost gives away the solution'
|
|
]
|
|
};
|
|
|
|
export function registerLevelX() { // Replace X with the level number
|
|
registerLevel(level);
|
|
}
|
|
````
|
|
|
|
### Step 3: Update the levels index file
|
|
|
|
Edit `src/levels/index.ts` to import and register your new level:
|
|
|
|
```typescript
|
|
import { registerLevel1 } from "./level1";
|
|
import { registerLevel2 } from "./level2";
|
|
// ... other levels
|
|
import { registerLevelX } from "./levelX"; // Add your new level
|
|
|
|
export function registerAllLevels() {
|
|
registerLevel1();
|
|
registerLevel2();
|
|
// ... other levels
|
|
registerLevelX(); // Register your new level
|
|
|
|
console.log("All levels registered successfully.");
|
|
}
|
|
```
|
|
|
|
## Implementing Complex Level Mechanics
|
|
|
|
### File System Navigation
|
|
|
|
To implement a file system level (like Level 2), you need to:
|
|
|
|
1. Create a virtual file system structure in your level state
|
|
2. Implement commands like `ls`, `cd`, and `cat`
|
|
3. Track the current directory and handle path navigation
|
|
|
|
Here's an example of a file system state structure:
|
|
|
|
```typescript
|
|
// In initialize()
|
|
gameState.levelStates[this.id] = {
|
|
currentDir: "/home/user",
|
|
fileSystem: {
|
|
"/home/user": {
|
|
type: "dir",
|
|
contents: ["Documents", "Pictures", ".hidden"],
|
|
},
|
|
"/home/user/Documents": {
|
|
type: "dir",
|
|
contents: ["notes.txt"],
|
|
},
|
|
"/home/user/Documents/notes.txt": {
|
|
type: "file",
|
|
content: "This is a text file.",
|
|
},
|
|
// Add more directories and files
|
|
},
|
|
};
|
|
```
|
|
|
|
Handling file system commands:
|
|
|
|
```typescript
|
|
// In handleInput()
|
|
if (cmd === "ls") {
|
|
// List directory contents
|
|
return {
|
|
completed: false,
|
|
message: fileSystem[levelState.currentDir].contents.join("\n"),
|
|
};
|
|
}
|
|
|
|
if (cmd === "cd" && parts.length > 1) {
|
|
const target = parts[1];
|
|
|
|
if (target === "..") {
|
|
// Go up one directory
|
|
const pathParts = levelState.currentDir.split("/");
|
|
if (pathParts.length > 2) {
|
|
pathParts.pop();
|
|
levelState.currentDir = pathParts.join("/");
|
|
return {
|
|
completed: false,
|
|
message: `Changed directory to ${levelState.currentDir}`,
|
|
};
|
|
}
|
|
} else {
|
|
// Go to specified directory
|
|
const newPath = `${levelState.currentDir}/${target}`;
|
|
|
|
if (fileSystem[newPath] && fileSystem[newPath].type === "dir") {
|
|
levelState.currentDir = newPath;
|
|
return {
|
|
completed: false,
|
|
message: `Changed directory to ${levelState.currentDir}`,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cmd === "cat" && parts.length > 1) {
|
|
const target = parts[1];
|
|
const path = `${levelState.currentDir}/${target}`;
|
|
|
|
if (fileSystem[path] && fileSystem[path].type === "file") {
|
|
return {
|
|
completed: false,
|
|
message: fileSystem[path].content,
|
|
};
|
|
}
|
|
}
|
|
```
|
|
|
|
### Process Management
|
|
|
|
For a process management level (like Level 3), you can:
|
|
|
|
1. Create a list of processes with properties like PID, name, CPU usage, etc.
|
|
2. Implement commands like `ps`, `kill`, and `start`
|
|
3. Track process states and check for completion conditions
|
|
|
|
Example process state:
|
|
|
|
```typescript
|
|
// In initialize()
|
|
gameState.levelStates[this.id] = {
|
|
processes: [
|
|
{ pid: 1, name: "systemd", cpu: 0.1, memory: 4.2, status: "running" },
|
|
{ pid: 423, name: "sshd", cpu: 0.0, memory: 1.1, status: "running" },
|
|
{
|
|
pid: 842,
|
|
name: "malware.bin",
|
|
cpu: 99.7,
|
|
memory: 85.5,
|
|
status: "running",
|
|
},
|
|
{ pid: 1024, name: "firewall", cpu: 0.1, memory: 1.8, status: "stopped" },
|
|
],
|
|
firewallStarted: false,
|
|
malwareKilled: false,
|
|
};
|
|
```
|
|
|
|
Handling process commands:
|
|
|
|
```typescript
|
|
// In handleInput()
|
|
if (cmd === "ps") {
|
|
// Format and display process list
|
|
let output = "PID NAME CPU% MEM% STATUS\n";
|
|
output += "--------------------------------------------\n";
|
|
|
|
levelState.processes.forEach((proc) => {
|
|
output += `${proc.pid.toString().padEnd(7)}${proc.name.padEnd(13)}${proc.cpu
|
|
.toFixed(1)
|
|
.padEnd(8)}${proc.memory.toFixed(1).padEnd(8)}${proc.status}\n`;
|
|
});
|
|
|
|
return {
|
|
completed: false,
|
|
message: output,
|
|
};
|
|
}
|
|
|
|
if (cmd === "kill" && parts.length > 1) {
|
|
const pid = parseInt(parts[1]);
|
|
const process = levelState.processes.find((p) => p.pid === pid);
|
|
|
|
if (process) {
|
|
process.status = "stopped";
|
|
|
|
if (process.name === "malware.bin") {
|
|
levelState.malwareKilled = true;
|
|
|
|
// Check if level is completed
|
|
if (levelState.firewallStarted) {
|
|
return {
|
|
completed: true,
|
|
message: "System secured! Malware stopped and firewall running.",
|
|
nextAction: "next_level",
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cmd === "start" && parts.length > 1) {
|
|
const pid = parseInt(parts[1]);
|
|
const process = levelState.processes.find((p) => p.pid === pid);
|
|
|
|
if (process) {
|
|
process.status = "running";
|
|
|
|
if (process.name === "firewall") {
|
|
levelState.firewallStarted = true;
|
|
|
|
// Check if level is completed
|
|
if (levelState.malwareKilled) {
|
|
return {
|
|
completed: true,
|
|
message: "System secured! Malware stopped and firewall running.",
|
|
nextAction: "next_level",
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### File Permissions
|
|
|
|
For a permissions-based level (like Level 4):
|
|
|
|
1. Create files with permission attributes
|
|
2. Implement commands like `chmod` and `sudo`
|
|
3. Check permissions before allowing file access
|
|
|
|
Example permissions state:
|
|
|
|
```typescript
|
|
// In initialize()
|
|
gameState.levelStates[this.id] = {
|
|
files: [
|
|
{
|
|
name: "README.txt",
|
|
permissions: "rw-r--r--",
|
|
owner: "user",
|
|
group: "user",
|
|
},
|
|
{
|
|
name: "secret_data.db",
|
|
permissions: "----------",
|
|
owner: "root",
|
|
group: "root",
|
|
},
|
|
{
|
|
name: "change_permissions.sh",
|
|
permissions: "r--------",
|
|
owner: "user",
|
|
group: "user",
|
|
},
|
|
],
|
|
currentUser: "user",
|
|
sudoAvailable: false,
|
|
};
|
|
```
|
|
|
|
Handling permission commands:
|
|
|
|
```typescript
|
|
// In handleInput()
|
|
if (cmd === "cat" && parts.length > 1) {
|
|
const fileName = parts[1];
|
|
const file = levelState.files.find((f) => f.name === fileName);
|
|
|
|
if (file) {
|
|
// Check if user has read permission
|
|
const canRead =
|
|
(levelState.currentUser === file.owner && file.permissions[0] === "r") ||
|
|
(levelState.currentUser !== file.owner &&
|
|
levelState.currentUser === file.group &&
|
|
file.permissions[3] === "r") ||
|
|
(levelState.currentUser !== file.owner &&
|
|
levelState.currentUser !== file.group &&
|
|
file.permissions[6] === "r") ||
|
|
levelState.currentUser === "root"; // root can read anything
|
|
|
|
if (!canRead) {
|
|
return {
|
|
completed: false,
|
|
message: `Permission denied: Cannot read ${fileName}`,
|
|
};
|
|
}
|
|
|
|
// Return file content based on the file name
|
|
if (fileName === "README.txt") {
|
|
return {
|
|
completed: false,
|
|
message:
|
|
"This system contains important data. You need to access secret_data.db to proceed.",
|
|
};
|
|
}
|
|
// Handle other files...
|
|
}
|
|
}
|
|
|
|
if (cmd === "chmod" && parts.length > 2) {
|
|
const permissions = parts[1]; // e.g., +x
|
|
const fileName = parts[2];
|
|
const file = levelState.files.find((f) => f.name === fileName);
|
|
|
|
if (file) {
|
|
// Check if user owns the file
|
|
if (
|
|
levelState.currentUser !== file.owner &&
|
|
levelState.currentUser !== "root"
|
|
) {
|
|
return {
|
|
completed: false,
|
|
message: `Permission denied: Only the owner can change permissions of ${fileName}`,
|
|
};
|
|
}
|
|
|
|
// Handle different chmod formats
|
|
if (permissions === "+x") {
|
|
// Make file executable
|
|
let newPermissions = file.permissions.split("");
|
|
newPermissions[2] = "x"; // Owner execute
|
|
file.permissions = newPermissions.join("");
|
|
|
|
if (fileName === "change_permissions.sh") {
|
|
levelState.scriptExecutable = true;
|
|
}
|
|
|
|
return {
|
|
completed: false,
|
|
message: `Changed permissions of ${fileName} to ${file.permissions}`,
|
|
};
|
|
}
|
|
// Handle other permission changes...
|
|
}
|
|
}
|
|
```
|
|
|
|
### Network Configuration
|
|
|
|
For a network-based level (like Level 5):
|
|
|
|
1. Create network interfaces, firewall rules, and DNS settings
|
|
2. Implement commands like `ifconfig`, `ping`, and `firewall-cmd`
|
|
3. Track network state and check for connectivity
|
|
|
|
Example network state:
|
|
|
|
```typescript
|
|
// In initialize()
|
|
gameState.levelStates[this.id] = {
|
|
interfaces: [
|
|
{ name: "lo", status: "UP", ip: "127.0.0.1", netmask: "255.0.0.0" },
|
|
{ name: "eth0", status: "DOWN", ip: "", netmask: "" },
|
|
],
|
|
firewall: {
|
|
enabled: true,
|
|
rules: [
|
|
{ port: 80, protocol: "tcp", action: "DENY" },
|
|
{ port: 443, protocol: "tcp", action: "DENY" },
|
|
],
|
|
},
|
|
dns: {
|
|
configured: false,
|
|
server: "",
|
|
},
|
|
gateway: {
|
|
configured: false,
|
|
address: "",
|
|
},
|
|
};
|
|
```
|
|
|
|
Handling network commands:
|
|
|
|
```typescript
|
|
// In handleInput()
|
|
if (cmd === "ifconfig") {
|
|
if (parts.length === 1) {
|
|
// Show all interfaces
|
|
let output = "Network Interfaces:\n";
|
|
output += "NAME STATUS IP NETMASK\n";
|
|
output += "----------------------------------------\n";
|
|
|
|
levelState.interfaces.forEach((iface) => {
|
|
output += `${iface.name.padEnd(7)}${iface.status.padEnd(
|
|
9
|
|
)}${iface.ip.padEnd(14)}${iface.netmask}\n`;
|
|
});
|
|
|
|
return {
|
|
completed: false,
|
|
message: output,
|
|
};
|
|
} else if (parts.length >= 4) {
|
|
// Configure an interface
|
|
const ifaceName = parts[1];
|
|
const ip = parts[2];
|
|
const netmask = parts[3];
|
|
|
|
const iface = levelState.interfaces.find((i) => i.name === ifaceName);
|
|
|
|
if (iface) {
|
|
iface.ip = ip;
|
|
iface.netmask = netmask;
|
|
|
|
return {
|
|
completed: false,
|
|
message: `Configured ${ifaceName} with IP ${ip} and netmask ${netmask}.`,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cmd === "ifup" && parts.length > 1) {
|
|
const ifaceName = parts[1];
|
|
const iface = levelState.interfaces.find((i) => i.name === ifaceName);
|
|
|
|
if (iface) {
|
|
iface.status = "UP";
|
|
|
|
return {
|
|
completed: false,
|
|
message: `Interface ${ifaceName} is now UP.`,
|
|
};
|
|
}
|
|
}
|
|
|
|
if (cmd === "firewall-cmd") {
|
|
if (parts.includes("--disable")) {
|
|
levelState.firewall.enabled = false;
|
|
|
|
return {
|
|
completed: false,
|
|
message: "Firewall disabled.",
|
|
};
|
|
}
|
|
|
|
if (parts.includes("--allow") && parts.length > 2) {
|
|
const port = parseInt(parts[parts.indexOf("--allow") + 1]);
|
|
|
|
// Find the rule for this port
|
|
const rule = levelState.firewall.rules.find((r) => r.port === port);
|
|
|
|
if (rule) {
|
|
rule.action = "ALLOW";
|
|
|
|
return {
|
|
completed: false,
|
|
message: `Port ${port} is now allowed through the firewall.`,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## Using the Enhanced UI
|
|
|
|
The game includes a `levelUI` helper in `src/ui/levelRenderer.ts` that provides styled UI components for your levels:
|
|
|
|
```typescript
|
|
import { levelUI } from "../ui/levelRenderer";
|
|
|
|
// In render()
|
|
levelUI.title("Welcome to My Level");
|
|
levelUI.paragraph("This is a description of the level.");
|
|
levelUI.spacer();
|
|
|
|
// Display a terminal
|
|
levelUI.terminal(
|
|
"$ ls -la\ntotal 12\ndrwxr-xr-x 2 user user 4096 Jan 1 12:00 ."
|
|
);
|
|
|
|
// Display a file system
|
|
const items = [
|
|
{ name: "Documents", type: "dir" },
|
|
{ name: "notes.txt", type: "file" },
|
|
];
|
|
levelUI.fileSystem("/home/user", items);
|
|
|
|
// Display a process table
|
|
levelUI.processTable(levelState.processes);
|
|
|
|
// Display available commands
|
|
levelUI.commands(["ls", "cd [dir]", "cat [file]"]);
|
|
```
|
|
|
|
## Level State Management
|
|
|
|
The level state is stored in `gameState.levelStates[levelId]`. This is where you should store any level-specific data that needs to persist between renders or commands.
|
|
|
|
## Level Results
|
|
|
|
When handling user input, your level should return a `LevelResult` object:
|
|
|
|
```typescript
|
|
interface LevelResult {
|
|
completed: boolean; // Whether the level is completed
|
|
message?: string; // Optional message to display to the user
|
|
nextAction?: "next_level" | "main_menu" | "continue"; // What to do next
|
|
}
|
|
```
|
|
|
|
- Set `completed: true` when the player solves the level
|
|
- Use `nextAction: 'next_level'` to proceed to the next level
|
|
- Use `nextAction: 'main_menu'` to return to the main menu
|
|
- Use `nextAction: 'continue'` or omit to stay in the current level
|
|
|
|
## Best Practices
|
|
|
|
1. **Initialization**: Always check if the level state exists and initialize it if not
|
|
2. **Progressive Difficulty**: Make your level challenging but fair
|
|
3. **Clear Instructions**: Make sure players understand what they need to do
|
|
4. **Meaningful Feedback**: Provide helpful responses to player commands
|
|
5. **Multiple Solutions**: When possible, allow multiple ways to solve the puzzle
|
|
6. **Thematic Consistency**: Try to maintain the Linux/tech theme of the game
|
|
7. **Hints**: Provide at least 3 hints of increasing specificity
|
|
|
|
## Example Level Ideas
|
|
|
|
- **Network Security**: Configure a firewall to block specific attacks
|
|
- **Cryptography**: Decode encrypted messages using various ciphers
|
|
- **Database Challenge**: Use SQL-like commands to extract information
|
|
- **Git Simulation**: Navigate and manipulate a git repository
|
|
- **Container Escape**: Escape from a simulated container environment
|
|
|
|
## Testing Your Level
|
|
|
|
Always test your level thoroughly to ensure:
|
|
|
|
- It can be completed
|
|
- All commands work as expected
|
|
- The level state initializes correctly
|
|
- Transitions to the next level work properly
|
|
|
|
Happy level creating!
|
|
|
|
```
|
|
|
|
This enhanced guide provides much more detailed examples of how to implement complex level mechanics like file system navigation, process management, permissions, and network configuration. It should help anyone who wants to create new levels with sophisticated Linux-like features.
|
|
```
|