Added mercure to update when everyone is ready

This commit is contained in:
Frank
2026-01-08 20:11:14 +01:00
parent c4c989db4c
commit b965f0f085
5 changed files with 82 additions and 9 deletions

View File

@@ -132,16 +132,22 @@ final class GameController extends AbstractController
if ($session->getStatus() === SessionStatus::READY) { if ($session->getStatus() === SessionStatus::READY) {
$isReady = false; $isReady = false;
$readyAt = null;
if ($player) { if ($player) {
$settingName = SessionSettingType::tryFrom('ReadyAtForPlayer' . $player->getScreen()); $settingName = SessionSettingType::tryFrom('ReadyAtForPlayer' . $player->getScreen());
if ($settingName) { if ($settingName) {
$isReady = $session->getSettings()->exists(fn($i, SessionSetting $s) => $s->getName() === $settingName && $s->getPlayer() === $player); $setting = $session->getSettings()->filter(fn(SessionSetting $s) => $s->getName() === $settingName && $s->getPlayer() === $player)->first();
if ($setting) {
$isReady = true;
$readyAt = (int)$setting->getValue();
}
} }
} }
return $this->render('game/waiting.html.twig', [ return $this->render('game/waiting.html.twig', [
'session' => $session, 'session' => $session,
'isReady' => $isReady, 'isReady' => $isReady,
'readyAt' => $readyAt,
'mercure_public_url' => $this->mercurePublicUrl, 'mercure_public_url' => $this->mercurePublicUrl,
]); ]);
} }

View File

@@ -329,8 +329,12 @@ final class GameDashboardService
$this->checkAllPlayersReady($session); $this->checkAllPlayersReady($session);
$this->entityManager->flush(); $this->entityManager->flush();
try {
$topic = $this->mercureTopicBase . '/game/hub-' . $session->getId(); $topic = $this->mercureTopicBase . '/game/hub-' . $session->getId();
$this->hub->publish(new Update($topic, json_encode(['type' => 'player_ready', 'player' => $player->getScreen(), 'ready' => !$setting]))); $this->hub->publish(new Update($topic, json_encode(['type' => 'player_ready', 'player' => $player->getScreen(), 'ready' => !$setting])));
} catch (\Exception $e) {
// Mercure might be down, but we don't want to crash the game
}
return true; return true;
} }
@@ -350,6 +354,7 @@ final class GameDashboardService
$readyPlayersCount = 0; $readyPlayersCount = 0;
$now = new \DateTime(); $now = new \DateTime();
$anyReset = false;
/** @var \App\Game\Repository\SessionSettingRepository $settingRepo */ /** @var \App\Game\Repository\SessionSettingRepository $settingRepo */
$settingRepo = $this->entityManager->getRepository(SessionSetting::class); $settingRepo = $this->entityManager->getRepository(SessionSetting::class);
@@ -367,12 +372,23 @@ final class GameDashboardService
if (($now->getTimestamp() - $readyAtTimestamp) > 60) { if (($now->getTimestamp() - $readyAtTimestamp) > 60) {
$session->removeSetting($setting); $session->removeSetting($setting);
$this->entityManager->remove($setting); $this->entityManager->remove($setting);
$anyReset = true;
} else { } else {
$readyPlayersCount++; $readyPlayersCount++;
} }
} }
} }
if ($anyReset) {
$this->entityManager->flush();
try {
$topic = $this->mercureTopicBase . '/game/hub-' . $session->getId();
$this->hub->publish(new Update($topic, json_encode(['type' => 'player_ready'])));
} catch (\Exception $e) {
// Mercure might be down
}
}
if ($readyPlayersCount === $numPlayers) { if ($readyPlayersCount === $numPlayers) {
$session->setStatus(SessionStatus::PLAYING); $session->setStatus(SessionStatus::PLAYING);
$this->entityManager->persist($session); $this->entityManager->persist($session);
@@ -389,8 +405,12 @@ final class GameDashboardService
} }
} }
try {
$topic = $this->mercureTopicBase . '/game/hub-' . $session->getId(); $topic = $this->mercureTopicBase . '/game/hub-' . $session->getId();
$this->hub->publish(new Update($topic, json_encode(['type' => 'all_ready']))); $this->hub->publish(new Update($topic, json_encode(['type' => 'all_ready'])));
} catch (\Exception $e) {
// Mercure might be down, but we don't want to crash the game
}
} }
} }

View File

@@ -336,8 +336,12 @@ class GameResponseService
if(is_null($activeGame)) if(is_null($activeGame))
return false; return false;
$topic = $_ENV['MERCURE_TOPIC_BASE'] . '/game/hub-' . $activeGame; $topic = $this->mercureTopicBase . '/game/hub-' . $activeGame;
try {
$this->hub->publish(new Update($topic, json_encode([$sendTo, $message]))); $this->hub->publish(new Update($topic, json_encode([$sendTo, $message])));
} catch (\Exception $e) {
// Mercure might be down
}
$this->updateChatTracking($player, (int)$sendTo); $this->updateChatTracking($player, (int)$sendTo);
@@ -381,7 +385,11 @@ class GameResponseService
$topic = $this->mercureTopicBase . '/game/hub-' . $session->getId(); $topic = $this->mercureTopicBase . '/game/hub-' . $session->getId();
$notification = "Security Alert: One of your verify codes was shared and has been regenerated."; $notification = "Security Alert: One of your verify codes was shared and has been regenerated.";
// We send it only to this player (screen) // We send it only to this player (screen)
try {
$this->hub->publish(new Update($topic, json_encode([$screen, $notification]))); $this->hub->publish(new Update($topic, json_encode([$screen, $notification])));
} catch (\Exception $e) {
// Mercure might be down
}
} }
} }
@@ -667,7 +675,11 @@ class GameResponseService
$topic = $this->mercureTopicBase . '/game/hub-' . $session->getId(); $topic = $this->mercureTopicBase . '/game/hub-' . $session->getId();
$message = "Mainframe Help Modus: Agents Doyle, Vega and Lennox rapports have been updated with coded messages."; $message = "Mainframe Help Modus: Agents Doyle, Vega and Lennox rapports have been updated with coded messages.";
try {
$this->hub->publish(new Update($topic, json_encode([0, $message]))); $this->hub->publish(new Update($topic, json_encode([0, $message])));
} catch (\Exception $e) {
// Mercure might be down
}
} }
} }

View File

@@ -14,6 +14,15 @@
<h4>{{ session.game.name }}</h4> <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> <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"> <div class="alert alert-info">
<strong>Game Information:</strong> <strong>Game Information:</strong>
<ul class="mb-0"> <ul class="mb-0">
@@ -72,6 +81,7 @@
<div id="mercure-config" <div id="mercure-config"
data-mercure-public-url="{{ mercure_public_url|e('html_attr') }}" data-mercure-public-url="{{ mercure_public_url|e('html_attr') }}"
data-topic="{{ (mercure_topic_base ~ '/game/hub-' ~ session.id)|e('html_attr') }}" data-topic="{{ (mercure_topic_base ~ '/game/hub-' ~ session.id)|e('html_attr') }}"
data-ready-at="{{ readyAt|e('html_attr') }}"
style="display:none"> style="display:none">
</div> </div>
@@ -79,6 +89,7 @@
const config = document.getElementById('mercure-config'); const config = document.getElementById('mercure-config');
const publicUrl = config.dataset.mercurePublicUrl; const publicUrl = config.dataset.mercurePublicUrl;
const topic = config.dataset.topic; const topic = config.dataset.topic;
const readyAt = config.dataset.readyAt;
if (publicUrl && topic) { if (publicUrl && topic) {
const url = new URL(publicUrl); const url = new URL(publicUrl);
@@ -92,5 +103,23 @@
} }
}; };
} }
// 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(() => {
window.location.reload();
}, timeLeft);
} else {
// Already timed out, reload to sync with server
window.location.reload();
}
}
</script> </script>
{% endblock %} {% endblock %}

View File

@@ -310,6 +310,12 @@ class GameDashboardServiceTest extends TestCase
->method('remove') ->method('remove')
->with($setting1); ->with($setting1);
$this->entityManager->expects($this->atLeastOnce())
->method('flush');
$this->hub->expects($this->once())
->method('publish');
$this->service->checkAllPlayersReady($session); $this->service->checkAllPlayersReady($session);
$this->assertEquals(SessionStatus::READY, $session->getStatus()); $this->assertEquals(SessionStatus::READY, $session->getStatus());