Extending the Server

Guide for adding new features and extending PipeWorks MUD Server.

Adding New Commands

To add a new command (e.g., examine):

1. Add Method to GameEngine

In src/mud_server/core/engine.py:

def examine(self, username: str, target: str) -> str:
    """Examine an item or object in detail."""
    player = self.db.get_player(username)
    if not player:
        return "You don't exist."

    # Check inventory
    if target in player["inventory"]:
        item = self.world.get_item(target)
        return f"You examine the {item.name}: {item.description}"

    # Check room
    room = self.world.get_room(player["current_room"])
    if target in room.items:
        item = self.world.get_item(target)
        return f"You examine the {item.name}: {item.description}"

    return f"You don't see '{target}' here."

2. Add Command Handler

In src/mud_server/api/routes.py:

# In the command parser (around line 250):
elif cmd in ["examine", "ex"]:
    if not args:
        return JSONResponse({"result": "Examine what?"})
    target = args[0]
    result = engine.examine(username, target)
    return JSONResponse({"result": result})

3. Restart Server

The new command is now available to players.

Extending World Data

World data is defined in data/world_data.json.

Adding Room Properties

  1. Update JSON schema:

{
  "rooms": {
    "library": {
      "id": "library",
      "name": "Ancient Library",
      "description": "Dusty books line the shelves.",
      "exits": {"south": "spawn"},
      "items": ["book"],
      "properties": {
        "light_level": "dim",
        "temperature": "cool"
      }
    }
  }
}
  1. Update dataclass in src/mud_server/core/world.py:

@dataclass
class Room:
    id: str
    name: str
    description: str
    exits: dict[str, str]
    items: list[str]
    properties: dict[str, str] = field(default_factory=dict)
  1. Update World loader to parse new fields

  2. Use in game logic:

room = self.world.get_room(player["current_room"])
if room.properties.get("light_level") == "dim":
    return "It's too dark to see clearly."

Adding Database Tables

To add a new table (e.g., for achievements):

Define Schema

In src/mud_server/db/database.py:

def init_db():
    # ... existing code ...

    cursor.execute("""
        CREATE TABLE IF NOT EXISTS achievements (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            character_id INTEGER NOT NULL,
            achievement_id TEXT NOT NULL,
            earned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (character_id) REFERENCES characters (id)
        )
    """)

Add CRUD Functions

def add_achievement(character_id: int, achievement_id: str):
    """Record an achievement for a character."""
    with get_db() as conn:
        cursor = conn.cursor()
        cursor.execute(
            "INSERT INTO achievements (character_id, achievement_id) VALUES (?, ?)",
            (character_id, achievement_id)
        )
        conn.commit()

def get_achievements(character_id: int) -> list[dict]:
    """Get all achievements for a character."""
    with get_db() as conn:
        cursor = conn.cursor()
        cursor.execute(
            "SELECT * FROM achievements WHERE character_id = ?",
            (character_id,)
        )
        return [dict(row) for row in cursor.fetchall()]

Add API Endpoint

In src/mud_server/api/routes.py:

@app.get("/api/achievements/{character_id}")
async def get_player_achievements(character_id: int):
    """Get all achievements for a character."""
    if not validate_session(request):
        raise HTTPException(status_code=401, detail="Not authenticated")

    achievements = db.get_achievements(character_id)
    return achievements

Add Client Wrapper

In src/mud_server/client/api/game.py:

def get_achievements(self, character_id: int) -> dict:
    """Fetch achievements for a character."""
    response = self._get(f"/api/achievements/{character_id}")
    return self._parse_response(response)

Adding API Endpoints

Define Pydantic Model

In src/mud_server/api/models.py:

class AchievementResponse(BaseModel):
    achievement_id: str
    earned_at: str
    description: str

Add Route

In src/mud_server/api/routes.py:

@app.get("/api/achievements/{username}", response_model=list[AchievementResponse])
async def get_player_achievements(username: str, request: Request):
    """Get all achievements for a player."""
    if not validate_session(request):
        raise HTTPException(status_code=401, detail="Not authenticated")

    achievements = db.get_achievements(username)
    return achievements

Testing Strategy

Unit Tests

Test individual functions:

def test_move_command():
    """Test player movement between rooms."""
    engine = GameEngine(world, db)
    result = engine.move("test_user", "north")
    assert "moved north" in result.lower()

Integration Tests

Test component interactions:

def test_pickup_and_inventory():
    """Test item pickup and inventory display."""
    engine.pickup_item("test_user", "torch")
    inventory = engine.get_inventory("test_user")
    assert "torch" in inventory

API Tests

Test HTTP endpoints:

def test_command_endpoint(client):
    """Test /api/command endpoint."""
    response = client.post("/api/command", json={
        "username": "test_user",
        "command": "look"
    })
    assert response.status_code == 200
    assert "result" in response.json()

Key Implementation Principles

1. Determinism First

All game logic should be:

  • Reproducible - Same inputs yield same outputs

  • Testable - Write unit tests for all mechanics

  • Traceable - Log important state changes

  • Debuggable - Use seeds for random generation

2. Separation of Concerns

  • Client Layer - UI and user interaction only

  • API Layer - HTTP routing and validation

  • Game Layer - Core mechanics and state

  • Database Layer - Persistence only

3. Data-Driven Design

Store configuration in data files, not code:

  • World definitions (data/world_data.json)

  • Environment variables for server config

  • Database for runtime state only

4. API Stability

When adding features:

  • Don’t break existing endpoints - add new ones

  • Use semantic versioning - document breaking changes

  • Return consistent structure - always include success/message/data

  • Document with Pydantic - models serve as documentation

Best Practices

Code Organization

Keep code organized by feature:

src/mud_server/
├── core/
│   ├── engine.py         # Main game logic
│   ├── world.py          # World data structures
│   └── mechanics/        # Specific game mechanics
├── db/
│   ├── database.py       # Core database operations
│   └── migrations/       # Schema migrations
├── api/
│   ├── server.py
│   ├── routes.py
│   ├── models.py
│   └── ...
└── client/
    ├── app.py
    ├── api/              # API client wrappers
    ├── tabs/             # UI components
    └── ui/               # Utilities

Migration Strategy

When adding new features:

  1. Keep existing features working

  2. Add new systems alongside old ones

  3. Use feature flags for testing

  4. Gradual rollout to users

  5. Preserve data - don’t lose player progress

Contributing

See the Contributing Guide for:

  • Code style guidelines

  • Pull request process

  • Testing requirements

  • Documentation standards