← All Workshops
MudEngine Part 9: The Twin Guardians
Step 8 / 11
Update the Game Component — Quest UI
The client needs three changes to support the quest:
- New signals — store quest info and vault state
- Handle
QuestEvent— display quest announcements in chat - Quest status display — show guardian pedestal status and vault entry/exit controls
- Update
RoomInfohandling — theStatemessage now includes quest data
Start by adding new types and signals, then update the message receive loop and RSX.
New types on the client
Add these alongside ChatMessage, before the Game component:
mud-engine/src/main.rs
#[derive(Clone, Debug)] struct QuestDisplay { name: String, active: bool, completed: bool, guardians: Vec<QuestGuardianDisplay>, } #[derive(Clone, Debug)] struct QuestGuardianDisplay { label: String, active: bool, }
mud-engine/src/main.rs
#[component] fn Game(name: String) -> Element { let mut players = use_signal::<HashMap<String, PlayerInfo>>(|| HashMap::new()); let mut my_id = use_signal(|| String::new()); let mut rooms = use_signal::<HashMap<usize, RoomInfo>>(|| HashMap::new()); let mut chat_messages = use_signal::<Vec<ChatMessage>>(|| Vec::new()); let mut command_input = use_signal(|| String::new()); let mut quest_display = use_signal::<Option<QuestDisplay>>(|| None); let mut vault_open = use_signal(|| false); let mut socket = use_websocket(move || { mud_ws(name.clone(), WebSocketOptions::new()) });
mud-engine/src/main.rs
ServerMessage::QuestEvent { active, message } => { vault_open.set(active); // Show the quest message in chat let mut msgs = chat_messages.write(); msgs.push(ChatMessage { from: "⚡ Quest".into(), message, room: 0, is_mine: false, }); } ServerMessage::QuestCompleted { message } => { if let Some(ref mut qd) = *quest_display.write() { qd.completed = true; } vault_open.set(false); let mut msgs = chat_messages.write(); msgs.push(ChatMessage { from: "👑 King Aldric".into(), message, room: 0, is_mine: false, }); }
mud-engine/src/main.rs
ServerMessage::State { players: p, your_id, rooms: r, } => { let mut map = players.write(); map.clear(); for pl in p { map.insert(pl.id.clone(), pl); } my_id.set(your_id); let mut room_map = rooms.write(); room_map.clear(); for room in r { room_map.insert(room.id, room); } // Build quest display from room data // Guardian rooms are 3 (Dark Forest) and 5 (Temple Courtyard) // We derive active state from player positions let sun_active = map.values().any(|pl| pl.room == 5); let moon_active = map.values().any(|pl| pl.room == 3); let all_active = sun_active && moon_active && map.len() >= 2; quest_display.set(Some(QuestDisplay { name: "Twin Guardians".into(), active: all_active, completed: false, guardians: vec![ QuestGuardianDisplay { label: "Sun Pedestal (Temple Courtyard)".into(), active: sun_active, }, QuestGuardianDisplay { label: "Moon Pedestal (Dark Forest)".into(), active: moon_active, }, ], })); if all_active { vault_open.set(true); } }
💡 Hardcoded guardian room IDs
The client code hardcodes room IDs 3 and 5 as the guardian rooms. This is fine for a workshop with a single quest, but for a production MUD you'd send the quest config as part of the State message (see the QuestInfo type in Step 4).
If you want to make it data-driven, add quest: Option<QuestInfo> to ServerMessage::State and populate it from the server's quest config during connection setup.
mud-engine/src/main.rs
// ── Quest panel (between grid and bottom-area) ── if let Some(ref quest) = *quest_display.read() { if quest.completed { div { class: "quest-panel completed", div { class: "quest-title", "👑 Quest Complete: {quest.name}" } div { class: "quest-complete-msg", "King Aldric has the Star Fragment! The realm is saved!" } } } else { div { class: "quest-panel", div { class: "quest-title", if quest.active { "🔓 " } else { "🔒 " } "{quest.name}" } div { class: "quest-phase-hint", if quest.active { "Portal open! A hero must descend, retrieve the Star Fragment, and bring it to the King." } else { "Place two heroes on the pedestals to open the portal." } } div { class: "quest-guardians", for g in &quest.guardians { div { class: if g.active { "quest-guardian active" } else { "quest-guardian" }, if g.active { "✅ " } else { "⏳ " } "{g.label}" } } } } } } // ── King Aldric NPC dialog (shown when in Town Square) ── if let Some(ref info) = my_info { if info.room == 4 { let quest_completed = quest_display.read() .as_ref().map(|q| q.completed).unwrap_or(false); let has_fragment = players.read().get( my_id.read().as_ref().unwrap_or(&String::new()) ).map(|p| p.items.contains(&"star fragment".into())).unwrap_or(false); div { class: "npc-dialog", div { class: "npc-name", "👑 King Aldric" } if quest_completed { div { class: "npc-text", ""The Star Fragment glows with ancient power! You have my eternal gratitude, brave adventurers!"" } } else if has_fragment { div { class: "npc-text highlight", ""You have it! The Star Fragment! Quick, hand it to me so I can restore the wards!"" } } else if vault_open.read().as_ref().copied().unwrap_or(false) { div { class: "npc-text", ""The portal is open! Someone must venture below and retrieve the Star Fragment!"" } } else { div { class: "npc-text", ""The Ancient Vault lies sealed. I need two brave souls to stand on the Sun and Moon Pedestals to open the way."" } } } } } // ── Quest action buttons ── if *vault_open.read() { if let Some(ref info) = my_info { if info.room == 4 { div { class: "quest-actions", button { class: "quest-btn", onclick: move |_| { let _ = socket.send(ClientMessage::Move { direction: "down".into(), }); }, "⬇ Descend into the Ancient Vault" } } } } } if let Some(ref info) = my_info { if info.room == 9 { div { class: "quest-actions", button { class: "quest-btn", onclick: move |_| { let _ = socket.send(ClientMessage::Move { direction: "up".into(), }); }, "⬆ Ascend to Town Square" } } } }
Step 8 / 11