MudEngine Part 9: The Twin Guardians
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:
// ── 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, }
#[derive(Serialize, Deserialize)] struct TomlGame { rooms: Vec<TomlRoom>, players: Vec<TomlPlayer>, quest: Option<TomlQuest>, }
/// Runtime quest state — tracked live on the server. #[cfg(feature = "server")] #[derive(Debug, Clone)] struct QuestStatus { config: TomlQuest, previously_active: bool, completed: bool, }
#[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, }, }
#[cfg(feature = "server")] struct GameState { rooms: Vec<RoomData>, players: HashMap<String, PlayerInfo>, quest: Option<QuestStatus>, }
/// 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, }
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.