shellquest/LEVELS.md
2025-04-01 12:15:40 +02:00

641 lines
16 KiB
Markdown

# LEVELS.md - Guide to Adding New Levels
## Introduction
ShellQuest 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!