← All Workshops

MudEngine Part 6: Multiplayer

Step 4 / 12

Shared Message Types

Both the client and the server need the same message types. These enums are what we send over the WebSocket connection.

ClientMessage flows from the player to the server:

  • Move { direction } — the player pressed a movement button

ServerMessage flows from the server to all connected players:

  • State — initial state sent when a player first joins (full player list + their own ID)
  • PlayerJoined — a new player entered the world
  • PlayerMoved — a player changed rooms
  • PlayerLeft — a player disconnected

We also define a PlayerInfo struct that holds a player's ID, name, and current room index.

Add these types at the top of src/main.rs, right after the imports:

mud-engine/src/main.rs
use std::collections::HashMap;
use dioxus::prelude::*;
use dioxus_fullstack::{use_websocket, Websocket, WebSocketOptions};
use serde::{Deserialize, Serialize};

mod components;

use crate::components::button::{Button, ButtonVariant};
use crate::components::card::{Card, CardHeader, CardTitle, CardContent};
use crate::components::separator::Separator;

fn main() {
    dioxus::launch(App);
}

// ── Shared message types ──

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
struct PlayerInfo {
    id: String,
    name: String,
    room: usize,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
enum ClientMessage {
    Move { direction: String },
}

#[derive(Serialize, Deserialize, Debug, Clone)]
enum ServerMessage {
    State {
        players: Vec<PlayerInfo>,
        your_id: String,
    },
    PlayerJoined(PlayerInfo),
    PlayerMoved(PlayerInfo),
    PlayerLeft { id: String },
}

// ── Static room data ──

static ROOM_NAMES: &[&str] = &[
    "Forest Path", "Hilltop", "Abandoned Tower",
    "Dark Forest", "Town Square", "Temple Courtyard",
    "Riverbank", "Old Bridge", "Graveyard",
];

static ROOM_DESCS: &[&str] = &[
    "A winding path leads through ancient oaks. Sunlight filters through the canopy.",
    "From this vantage point you can see the entire valley. A cool breeze carries the scent of pine.",
    "A crumbling stone tower stands alone. Vines crawl up its walls and crows nest in the windows.",
    "Twisted trees block out most of the light. Strange sounds echo through the undergrowth.",
    "A bustling town square with a fountain at its center. Cobblestones gleam from the morning rain.",
    "Ancient stone pillars surround a quiet courtyard. Moss clings to weathered statues.",
    "A slow-moving river borders a muddy bank. Frogs croak from the reeds.",
    "A weathered stone bridge crosses the river. Moss covers the ancient masonry.",
    "Rows of moss-covered headstones stretch into the fog. An iron gate creaks in the wind.",
];
🎯 Why derive Clone + PartialEq?
  • Serialize/Deserialize — messages travel over the WebSocket as JSON; serde handles the encoding
  • Clone — messages arrive on the client and need to be stored in signals (like Signal<HashMap<String, PlayerInfo>>)
  • PartialEq — required by Dioxus props, though our game component doesn't use props directly
  • Debug — useful for logging and debugging

The message enums are "tagged" — serde encodes the variant name as a string field, making it easy to inspect with browser dev tools.

Step 4 / 12