When the last player clicked ready, toggleReady() published a redundant 'player_ready' event on top of checkAllPlayersReady()'s 'all_ready', and the client reloaded on every message with no guard and without closing the EventSource. Chrome kept the old page's script (and its EventSource) alive across the overlapping reload calls, causing repeated reconnects to the Mercure hub; Firefox apparently tore the page down fast enough to mask it. Skip the redundant publish server-side, and make the client reload idempotent by tracking whether it already fired and closing the EventSource before reloading. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
136 lines
5.6 KiB
Twig
136 lines
5.6 KiB
Twig
{% extends 'base.html.twig' %}
|
|
|
|
{% block title %}Waiting for players - {{ session.game.name }}{% endblock %}
|
|
|
|
{% block body %}
|
|
<div class="container mt-5">
|
|
<div class="row justify-content-center">
|
|
<div class="col-md-8">
|
|
<div class="card shadow-sm">
|
|
<div class="card-header bg-primary text-white">
|
|
<h3 class="card-title mb-0">Waiting for all players to be ready</h3>
|
|
</div>
|
|
<div class="card-body">
|
|
<h4>{{ session.game.name }}</h4>
|
|
<p>Welcome to the game! Please wait for all players to join and signal they are ready to start.</p>
|
|
|
|
<div class="game-info">
|
|
Please keep the following things in mind:
|
|
<ul>
|
|
<li>This game is best played in full screen mode. For windows, press F11. For Mac, press Cmd+Ctrl+F.</li>
|
|
<li>There is no need to reload the page. There is even a chance this could break the game.</li>
|
|
<li>If your internet connection is lost, you can get back in the game after internet has been fixed.</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="alert alert-info">
|
|
<strong>Game Information:</strong>
|
|
<ul class="mb-0">
|
|
<li>Number of players: {{ session.game.numberOfPlayers }}</li>
|
|
<li>Current players: {{ session.players|length }} / {{ session.game.numberOfPlayers }}</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="mt-4">
|
|
<h5>Players status:</h5>
|
|
<ul class="list-group">
|
|
{% for player in session.players %}
|
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
|
{{ player.user.username }}
|
|
{% set playerReady = false %}
|
|
{% set settingName = 'ReadyAtForPlayer' ~ player.screen %}
|
|
{% for setting in session.settings %}
|
|
{% if setting.name.value == settingName and setting.player == player %}
|
|
{% set playerReady = true %}
|
|
{% endif %}
|
|
{% endfor %}
|
|
|
|
{% if playerReady %}
|
|
<span class="badge bg-success">Ready</span>
|
|
{% else %}
|
|
<span class="badge bg-secondary">Not ready</span>
|
|
{% endif %}
|
|
</li>
|
|
{% endfor %}
|
|
</ul>
|
|
</div>
|
|
|
|
<hr>
|
|
|
|
<form method="post" class="mt-4">
|
|
<div class="form-check form-switch mb-3">
|
|
<input class="form-check-input" type="checkbox" id="toggle_ready" name="toggle_ready" onchange="this.form.submit()" {{ isReady ? 'checked' : '' }}>
|
|
<label class="form-check-label" for="toggle_ready">
|
|
<strong>I am ready to start!</strong>
|
|
</label>
|
|
</div>
|
|
<p class="text-muted small">
|
|
Note: If all players are not ready within 1 minute of you checking this, your status will be reset automatically.
|
|
</p>
|
|
</form>
|
|
|
|
<div class="mt-3">
|
|
<a href="{{ path('game_dashboard') }}" class="btn btn-outline-secondary">Back to Dashboard</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="mercure-config"
|
|
data-mercure-public-url="{{ mercure_public_url|e('html_attr') }}"
|
|
data-topic="/game/hub/{{ session.id|e('html_attr') }}"
|
|
data-ready-at="{{ readyAt|e('html_attr') }}"
|
|
style="display:none">
|
|
</div>
|
|
|
|
<script>
|
|
const config = document.getElementById('mercure-config');
|
|
const publicUrl = config.dataset.mercurePublicUrl;
|
|
const topic = config.dataset.topic;
|
|
const readyAt = config.dataset.readyAt;
|
|
|
|
let reloading = false;
|
|
const reloadOnce = (eventSource) => {
|
|
if (reloading) {
|
|
return;
|
|
}
|
|
reloading = true;
|
|
if (eventSource) {
|
|
eventSource.close();
|
|
}
|
|
window.location.reload();
|
|
};
|
|
|
|
if (publicUrl && topic) {
|
|
const url = new URL(publicUrl);
|
|
url.searchParams.append('topic', topic);
|
|
|
|
const eventSource = new EventSource(url);
|
|
eventSource.onmessage = event => {
|
|
const data = JSON.parse(event.data);
|
|
if (data.type === 'all_ready' || data.type === 'player_ready') {
|
|
reloadOnce(eventSource);
|
|
}
|
|
};
|
|
}
|
|
|
|
// Client-side timeout for ready status
|
|
if (readyAt) {
|
|
const timeoutMs = 61000; // 61 seconds (slightly more than server-side 60s)
|
|
const now = Date.now();
|
|
const readyAtMs = readyAt * 1000;
|
|
const timeElapsed = now - readyAtMs;
|
|
const timeLeft = timeoutMs - timeElapsed;
|
|
|
|
if (timeLeft > 0) {
|
|
setTimeout(() => reloadOnce(), timeLeft);
|
|
} else {
|
|
// Already timed out, reload to sync with server
|
|
reloadOnce();
|
|
}
|
|
}
|
|
</script>
|
|
{% endblock %}
|