# Multiplayer System - Web Frontend **Status:** Planned **Phase:** 6 (Multiplayer Sessions) **Last Updated:** November 18, 2025 --- ## Overview The Web Frontend handles the UI/UX for multiplayer sessions, including lobby screens, active session displays, combat interfaces, and realtime synchronization via JavaScript/HTMX patterns. **Frontend Responsibilities:** - Render multiplayer session creation forms - Display lobby with player list and ready status - Show active session UI (timer, party status, combat) - Handle realtime updates via Appwrite Realtime WebSocket - Submit player actions to API backend - Display session completion and rewards **Technical Stack:** - **Templates**: Jinja2 - **Interactivity**: HTMX for AJAX interactions - **Realtime**: Appwrite JavaScript SDK for WebSocket subscriptions - **Styling**: Custom CSS (responsive design) --- ## UI/UX Design ### Session Creation Screen (Host) **Route:** `/multiplayer/create` **Template:** `templates/multiplayer/create.html` ```html {% extends "base.html" %} {% block content %}

Create Multiplayer Session Premium

Invite your friends to a 2-hour co-op adventure!

AI will generate a custom campaign for your party based on your characters' levels and the selected difficulty.

{% endblock %} ``` **HTMX Pattern:** - Form submission via `hx-post` to API endpoint - Response replaces `#session-result` div - API returns session details and redirects to lobby ### Lobby Screen **Route:** `/multiplayer/lobby/{session_id}` **Template:** `templates/multiplayer/lobby.html` ```html {% extends "base.html" %} {% block content %}

Multiplayer Lobby

Session Code: {{ session.invite_code }}

Invite Link:

Difficulty: {{ session.campaign.difficulty|title }}

Duration: 2 hours

{% for member in session.party_members %}
{% if member.is_host %} 👑 {% endif %} {{ member.username }} {% if member.is_host %}(Host){% endif %} {{ member.character_snapshot.name }} - {{ member.character_snapshot.player_class.name }} Lvl {{ member.character_snapshot.level }} {% if member.is_ready %} ✅ Ready {% else %} ⏳ Not Ready {% endif %}
{% endfor %} {% for i in range(session.max_players - session.party_members|length) %}
[Waiting for player...]
{% endfor %}
{% if current_user.user_id == session.host_user_id %} {% else %} {% endif %}
{% endblock %} ``` **Realtime Pattern:** - JavaScript subscribes to Appwrite Realtime for session updates - Updates player ready status dynamically - Redirects to active session when host starts - No page reload required ### Active Session Screen **Route:** `/multiplayer/session/{session_id}` **Template:** `templates/multiplayer/session.html` ```html {% extends "base.html" %} {% block content %}

{{ session.campaign.title }}

⏱️ {{ time_remaining }} Remaining

Campaign Progress: ({{ session.current_encounter_index }}/{{ session.campaign.encounters|length }} encounters)

Party:

{% for member in session.party_members %}
{{ member.character_snapshot.name }} HP: {{ member.character_snapshot.current_hp }}/{{ member.character_snapshot.max_hp }} {% if not member.is_connected %} ⚠️ Disconnected {% endif %}
{% endfor %}

Narrative:

{% for entry in session.conversation_history %}
{{ entry.role|title }}: {{ entry.content }}
{% endfor %}
{% if session.combat_encounter %}

Combat: {{ current_encounter.title }}

Turn Order:

    {% for combatant_id in session.combat_encounter.turn_order %}
  1. {{ get_combatant_name(combatant_id) }} {% if is_current_user_turn(combatant_id) %} (Your Turn!) {% endif %}
  2. {% endfor %}
{% if is_current_user_turn() %}

Your Action:

{% else %}

Waiting for other players...

{% endif %}
{% endif %}
{% endblock %} ``` **Realtime Pattern:** - Subscribe to session document changes - Update timer countdown locally - Update party HP dynamically - Reload combat UI section when turn changes - Redirect to completion screen when session ends ### Session Complete Screen **Route:** `/multiplayer/complete/{session_id}` **Template:** `templates/multiplayer/complete.html` ```html {% extends "base.html" %} {% block content %}

Campaign Complete!

🎉 {{ session.campaign.title }} - VICTORY! 🎉

{{ session.campaign.description }}

Time: {{ session_duration }} (Completion Bonus: +{{ completion_bonus_percent }}%)

Rewards:

{% if leveled_up %}

🎊 Level Up! {{ current_character.name }} reached level {{ current_character.level }}!

{% endif %}

Party Members:

View Full Session Log Return to Main Menu
{% endblock %} ``` --- ## HTMX Integration Patterns ### Form Submissions All multiplayer actions use HTMX for seamless AJAX submissions: **Pattern:** ```html
``` **Benefits:** - No page reload - Partial page updates - Progressive enhancement (works without JS) ### Realtime Updates Combine HTMX with Appwrite Realtime for optimal UX: **Pattern:** ```javascript // Listen for realtime events client.subscribe(`channel`, response => { // Update via HTMX partial reload htmx.ajax('GET', '/partial-url', { target: '#target-div', swap: 'outerHTML' }); }); ``` ### Polling Fallback For browsers without WebSocket support, use HTMX polling: ```html
``` --- ## Template Organization ### Directory Structure ``` templates/multiplayer/ ├── create.html # Session creation form ├── lobby.html # Lobby screen ├── session.html # Active session UI ├── complete.html # Session complete screen ├── partials/ │ ├── party_list.html # Reusable party member list │ ├── combat_ui.html # Combat interface partial │ └── timer.html # Timer component └── components/ ├── invite_link.html # Invite link copy widget └── ready_toggle.html # Ready status toggle button ``` ### Partial Template Pattern **Example:** `templates/multiplayer/partials/party_list.html` ```html
{% for member in party_members %}
{% if member.is_host %} 👑 {% endif %} {{ member.username }} {{ member.character_snapshot.name }} - {{ member.character_snapshot.player_class.name }} Lvl {{ member.character_snapshot.level }} {% if member.is_ready %}✅ Ready{% else %}⏳ Not Ready{% endif %}
{% endfor %}
``` **Usage:** ```html {% include 'multiplayer/partials/party_list.html' with party_members=session.party_members %} ``` --- ## JavaScript/Appwrite Realtime ### Setup **Base template** (`templates/base.html`): ```html ``` ### Subscription Patterns **Session Updates:** ```javascript const client = new Appwrite.Client() .setEndpoint('{{ config.appwrite_endpoint }}') .setProject('{{ config.appwrite_project_id }}'); // Subscribe to specific session client.subscribe( `databases.{{ db_id }}.collections.multiplayer_sessions.documents.{{ session_id }}`, response => { console.log('Session updated:', response.payload); // Handle different event types switch(response.events[0]) { case 'databases.*.collections.*.documents.*.update': handleSessionUpdate(response.payload); break; } } ); ``` **Combat Updates:** ```javascript client.subscribe( `databases.{{ db_id }}.collections.combat_encounters.documents.{{ encounter_id }}`, response => { updateCombatUI(response.payload); } ); ``` ### Error Handling ```javascript client.subscribe(channel, response => { // Handle updates }, error => { console.error('Realtime error:', error); // Fallback to polling startPolling(); }); function startPolling() { setInterval(() => { htmx.ajax('GET', '/api/v1/sessions/multiplayer/{{ session_id }}', { target: '#session-container', swap: 'outerHTML' }); }, 5000); } ``` --- ## View Layer Implementation ### Flask View Functions **Create Session View:** ```python @multiplayer_bp.route('/create', methods=['GET', 'POST']) @require_auth def create_session(): """Render session creation form.""" if request.method == 'POST': # Forward to API backend response = requests.post( f"{API_BASE_URL}/api/v1/sessions/multiplayer/create", json=request.form.to_dict(), headers={"Authorization": f"Bearer {session['auth_token']}"} ) if response.status_code == 200: session_data = response.json()['result'] return redirect(url_for('multiplayer.lobby', session_id=session_data['session_id'])) else: flash('Failed to create session', 'error') return render_template('multiplayer/create.html') @multiplayer_bp.route('/lobby/') @require_auth def lobby(session_id): """Display lobby screen.""" # Fetch session from API response = requests.get( f"{API_BASE_URL}/api/v1/sessions/multiplayer/{session_id}", headers={"Authorization": f"Bearer {session['auth_token']}"} ) session_data = response.json()['result'] return render_template('multiplayer/lobby.html', session=session_data) ``` --- ## Testing Checklist ### Manual Testing Tasks - [ ] Session creation form submits correctly - [ ] Invite link copies to clipboard - [ ] Lobby updates when players join - [ ] Ready status toggles work - [ ] Host can start session when all ready - [ ] Timer displays and counts down correctly - [ ] Party HP updates during combat - [ ] Combat actions submit correctly - [ ] Turn order highlights current player - [ ] Realtime updates work across multiple browsers - [ ] Session completion screen displays rewards - [ ] Disconnection handling shows warnings --- ## Related Documentation - **[/api/docs/MULTIPLAYER.md](../../api/docs/MULTIPLAYER.md)** - Backend API endpoints and business logic - **[TEMPLATES.md](TEMPLATES.md)** - Template structure and conventions - **[HTMX_PATTERNS.md](HTMX_PATTERNS.md)** - HTMX integration patterns - **[TESTING.md](TESTING.md)** - Manual testing guide --- **Document Version:** 1.0 (Microservices Split) **Created:** November 18, 2025 **Last Updated:** November 18, 2025