← All Workshops

MudEngine Part 9: The Twin Guardians

Step 4 / 11

Quest Data Models

We need new types to deserialize the quest config from game.toml and track quest state at runtime. Also add a QuestEvent variant to ServerMessage for real-time announcements.

Add these types alongside the existing ones in src/main.rs:

mud-engine/src/main.rs
// ── Quest TOML config ──

#[derive(Serialize, Deserialize, Debug, Clone)]
struct TomlQuestGuardian {
    name: String,
    room_id: usize,
    label: String,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
struct TomlQuestReward {
    source_room_id: usize,
    target_room_id: usize,
    direction: String,
    target_name: String,
    target_description: String,
    target_items: Vec<String>,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
struct TomlQuest {
    name: String,
    retrieval_item: String,
    completion_room: usize,
    guardians: Vec<TomlQuestGuardian>,
    reward: TomlQuestReward,
}
mud-engine/src/main.rs
#[derive(Serialize, Deserialize)]
struct TomlGame {
    rooms: Vec<TomlRoom>,
    players: Vec<TomlPlayer>,
    quest: Option<TomlQuest>,
}
mud-engine/src/main.rs
/// Runtime quest state — tracked live on the server.
#[cfg(feature = "server")]
#[derive(Debug, Clone)]
struct QuestStatus {
    config: TomlQuest,
    previously_active: bool,
    completed: bool,
}
mud-engine/src/main.rs
#[derive(Serialize, Deserialize, Debug, Clone)]
enum ServerMessage {
    State {
        players: Vec<PlayerInfo>,
        your_id: String,
        rooms: Vec<RoomInfo>,
    },
    PlayerJoined(PlayerInfo),
    PlayerMoved(PlayerInfo),
    PlayerLeft { id: String },
    Chat {
        from: String,
        message: String,
        room: usize,
    },
    ItemTaken {
        player_id: String,
        player_name: String,
        item: String,
    },
    ItemDropped {
        player_id: String,
        player_name: String,
        item: String,
        room: usize,
    },
    QuestEvent {
        active: bool,
        message: String,
    },
    QuestCompleted {
        message: String,
    },
}
mud-engine/src/main.rs
#[cfg(feature = "server")]
struct GameState {
    rooms: Vec<RoomData>,
    players: HashMap<String, PlayerInfo>,
    quest: Option<QuestStatus>,
}
mud-engine/src/main.rs
/// Sent to clients — now carries quest info so the UI can show quest status
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
struct RoomInfo {
    id: usize,
    name: String,
    description: String,
    items: Vec<String>,
}

/// Quest info sent to the client in the State message
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
struct QuestInfo {
    name: String,
    active: bool,
    completed: bool,
    guardians: Vec<QuestGuardianInfo>,
    reward: QuestRewardInfo,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
struct QuestGuardianInfo {
    name: String,
    room_id: usize,
    label: String,
    active: bool,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
struct QuestRewardInfo {
    source_room_id: usize,
    target_room_id: usize,
    direction: String,
    target_name: String,
    target_description: String,
}
💡 QuestInfo vs TomlQuest

TomlQuest is the raw deserialized config — it mirrors the TOML structure exactly.

QuestInfo and its sub-types (QuestGuardianInfo, QuestRewardInfo) are client-facing. They include runtime state like active (is this guardian currently occupied?) that the client needs to render the quest UI.

QuestStatus is server-only runtime state — it holds the config plus previously_active so the server can detect transitions (quest just activated or just deactivated) and broadcast the right events.

The separation mirrors the pattern from Part 7: TOML types ↔ runtime types ↔ client-facing types.

Step 4 / 11