Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/TaylorZaneKirk/MMO-Project/llms.txt

Use this file to discover all available pages before exploring further.

session_client.gd is the thin WebSocket transport layer in the Godot client. It owns opening and closing the connection, serializing outbound messages to JSON, reading inbound frames, and emitting them as typed GDScript signals. It does not interpret message content, make session decisions, or own any game state. The transport boundary is intentionally kept thin so the shared protocol can evolve without requiring changes to the client modules that call it.

Location

prototype/client/network/session_client.gd
SessionClient extends Node and is instantiated as a child of the SessionState autoload during _enter_tree(). Both LoginScreen and GameScreen reach it through SessionState.session_client.

Signals

SignalWhen emitted
connection_openedWebSocket state transitions from CONNECTING to OPEN
connection_closed(was_clean: bool)WebSocket state reaches CLOSED after a previously open connection
connection_failed(reason: String)Connection attempt fails, or a non-JSON frame is received
message_received(message_name: String, payload: Dictionary)A valid JSON frame arrives; name is extracted as message_name and payload as the inner dictionary

Connecting and disconnecting

# Open the session WebSocket
var error := session_client.connect_to_session("ws://localhost:5000/session")

# Close the session WebSocket
session_client.disconnect_from_session()

# Check connection state
if session_client.is_connected_to_session():
    pass
connect_to_session creates a fresh WebSocketPeer, clears the previous state, and calls connect_to_url. If the peer cannot connect, connection_failed is emitted immediately.

Polling

SessionClient does not use _process. Callers are responsible for driving the poll loop each frame:
func _process(_delta: float) -> void:
    SessionState.session_client.poll_client()
Both LoginScreen and GameScreen call poll_client() in their own _process implementations. poll_client() checks WebSocketPeer state transitions, fires connection_opened or connection_closed / connection_failed as appropriate, and drains all available packets in a single frame.

Sending messages

All outbound messages are JSON objects with a top-level name field and a payload dictionary. SessionClient exposes a dedicated method for each protocol message:
Methodname field in wire message
send_login_request(account_name, password, protocol_version?)"login_request"
send_movement_intent(session_id, character_id, target_tile_x, target_tile_y)"movement_intent"
send_inventory_item_use_request(session_id, character_id, slot_index, use_action)"inventory_item_use_request"
send_inventory_item_drop_request(session_id, character_id, slot_index)"inventory_item_drop_request"
send_ground_item_pickup_request(session_id, character_id, ground_item_id)"ground_item_pickup_request"
send_equipment_item_unequip_request(session_id, character_id, equipment_slot_id)"equipment_item_unequip_request"
send_world_resync_request(streams, character_revision, map_revision, reason)"world_resync_request"
Example wire format for a movement intent:
{
  "name": "movement_intent",
  "payload": {
    "session_id": "abc123",
    "character_id": "char-456",
    "target_tile_x": 12,
    "target_tile_y": 7
  }
}
All send methods call the internal _send_message(message) helper, which polls the socket once, checks that the state is STATE_OPEN, serializes the dictionary with JSON.stringify, and calls _socket.send_text. If the socket is not open, ERR_UNAVAILABLE is returned and the caller is responsible for handling the failure.

Receiving messages

Incoming frames are parsed as JSON in poll_client(). The name field becomes message_name and the payload field becomes the payload dictionary. Both are emitted via message_received. If a frame cannot be parsed as a JSON dictionary, connection_failed is emitted with a descriptive reason string.
# Subscribing to incoming messages
SessionState.session_client.message_received.connect(_on_message_received)

func _on_message_received(message_name: String, payload: Dictionary) -> void:
    match message_name:
        "world_snapshot":
            # handle snapshot
        "movement_applied":
            # handle movement
session_client.gd does not interpret message content. It passes raw message_name and payload pairs up to GameSessionPresenter, which classifies them and forwards authoritative state messages into GameStateStore. Controllers should never subscribe to session_client.message_received directly for game-state messages.

Build docs developers (and LLMs) love