Decoy names in /var/home and verifyCodes.txt were a hardcoded 4-name list (Luke, Charles, William, Peter), so a real player registering under one of those names would collide with it. Now each session picks 6 decoys from a pool of 10, excluding any name already taken by a real player, and stores the pick as a session setting. Likewise the 3 "special report" rapports that get coded messages were always Doyle, Vega and Lennox. Each session now randomly assigns 3 of the 20 rapports to that role, and the win screen / mainframe-help message reference whichever agents were actually picked instead of the hardcoded names. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
681 lines
23 KiB
PHP
681 lines
23 KiB
PHP
<?php
|
||
declare(strict_types=1);
|
||
|
||
namespace App\Game\Service;
|
||
|
||
use App\Game\Entity\Game;
|
||
use App\Game\Entity\LobbyMessage;
|
||
use App\Game\Entity\Player;
|
||
use App\Game\Entity\Session;
|
||
use App\Game\Entity\SessionSetting;
|
||
use App\Game\Enum\GameStatus;
|
||
use App\Game\Enum\SessionSettingType;
|
||
use App\Game\Enum\SessionStatus;
|
||
use App\Game\Event\PushMercureMessageEvent;
|
||
use App\Game\Repository\GameRepository;
|
||
use App\Game\Repository\LobbyMessageRepository;
|
||
use App\Game\Repository\SessionRepository;
|
||
use App\Tech\Entity\User;
|
||
use Doctrine\ORM\EntityManagerInterface;
|
||
use Symfony\Component\Security\Core\User\UserInterface;
|
||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||
|
||
final class GameDashboardService
|
||
{
|
||
private const READY_TIMEOUT_SECONDS = 60;
|
||
private const LOBBY_MESSAGE_MAX_LENGTH = 500;
|
||
private const LOBBY_CHAT_GRACE_PERIOD_SECONDS = 3600;
|
||
|
||
private const DECOY_USERNAME_POOL = [
|
||
'Luke', 'Charles', 'William', 'Peter', 'Simon',
|
||
'Oliver', 'Daniel', 'Thomas', 'Edward', 'George',
|
||
];
|
||
private const DECOY_USERNAME_COUNT = 6;
|
||
|
||
// File => cover-identity agent name, mirrors assets/game1/filesystem/var/rapports/index.txt
|
||
private const RAPPORT_AGENTS = [
|
||
'/var/rapports/095_07-14.txt' => 'Mason',
|
||
'/var/rapports/007_19-52.txt' => 'Harper',
|
||
'/var/rapports/083_25-39.txt' => 'Doyle',
|
||
'/var/rapports/019_31-11.txt' => 'Vega',
|
||
'/var/rapports/075_46-77.txt' => 'Ellis',
|
||
'/var/rapports/031_53-28.txt' => 'Navarro',
|
||
'/var/rapports/072_61-05.txt' => 'Carter',
|
||
'/var/rapports/064_72-90.txt' => 'Quinn',
|
||
'/var/rapports/091_81-33.txt' => 'Raines',
|
||
'/var/rapports/079_89-47.txt' => 'Dalton',
|
||
'/var/rapports/098_92-14.txt' => 'Langston',
|
||
'/var/rapports/012_94-31.txt' => 'Donovan',
|
||
'/var/rapports/016_98-07.txt' => 'Fletcher',
|
||
'/var/rapports/087_102-45.txt' => 'Sanders',
|
||
'/var/rapports/094_110-19.txt' => 'Mitchell',
|
||
'/var/rapports/063_117-56.txt' => 'O’Connor',
|
||
'/var/rapports/017_123-88.txt' => 'Holloway',
|
||
'/var/rapports/011_130-62.txt' => 'Lennox',
|
||
'/var/rapports/093_138-24.txt' => 'Bennett',
|
||
'/var/rapports/001_145-93.txt' => 'Carver',
|
||
];
|
||
private const SPECIAL_REPORT_COUNT = 3;
|
||
|
||
public function __construct(
|
||
private readonly GameRepository $gameRepository,
|
||
private readonly SessionRepository $sessionRepository,
|
||
private readonly LobbyMessageRepository $lobbyMessageRepository,
|
||
private readonly EntityManagerInterface $entityManager,
|
||
private readonly EventDispatcherInterface $eventDispatcher,
|
||
) {
|
||
}
|
||
|
||
/**
|
||
* @return Session[]
|
||
*/
|
||
public function getSessionsForUser(UserInterface $user): array
|
||
{
|
||
return $this->sessionRepository->createQueryBuilder('s')
|
||
->innerJoin('s.players', 'p')
|
||
->where('p.user = :user')
|
||
->setParameter('user', $user)
|
||
->getQuery()
|
||
->getResult();
|
||
}
|
||
|
||
/**
|
||
* @return Game[]
|
||
*/
|
||
public function getAvailableGames(bool $isAdmin): array
|
||
{
|
||
if ($isAdmin) {
|
||
return $this->gameRepository->findAll();
|
||
}
|
||
|
||
return $this->gameRepository->findBy(['status' => GameStatus::OPEN]);
|
||
}
|
||
|
||
public function createSession(Game $game, UserInterface $user, bool $isAdmin): ?Session
|
||
{
|
||
if ($game->getStatus() !== GameStatus::OPEN && !$isAdmin) {
|
||
return null;
|
||
}
|
||
|
||
if(!$user instanceof User)
|
||
return null;
|
||
|
||
$session = new Session();
|
||
$session->setGame($game);
|
||
$session->setStatus(SessionStatus::CREATED);
|
||
$session->setTimer(0);
|
||
|
||
$player = new Player();
|
||
$player->setUser($user);
|
||
$session->addPlayer($player);
|
||
|
||
$this->entityManager->persist($session);
|
||
$this->entityManager->persist($player);
|
||
|
||
$this->entityManager->flush();
|
||
|
||
if (count($session->getPlayers()) === $session->getGame()->getNumberOfPlayers()) {
|
||
$this->startSession($session);
|
||
}
|
||
|
||
return $session;
|
||
}
|
||
|
||
public function joinSession(string $inviteCode, UserInterface $user): bool
|
||
{
|
||
if (!$user instanceof User) {
|
||
return false;
|
||
}
|
||
|
||
$setting = $this->entityManager->getRepository(SessionSetting::class)->findOneBy([
|
||
'name' => SessionSettingType::INVITE_CODE,
|
||
'value' => $inviteCode,
|
||
]);
|
||
|
||
if (!$setting) {
|
||
return false;
|
||
}
|
||
|
||
$session = $setting->getSession();
|
||
|
||
// Check if user is already in this session
|
||
foreach ($session->getPlayers() as $player) {
|
||
if ($player->getUser() === $user) {
|
||
return true; // Already joined
|
||
}
|
||
}
|
||
|
||
$playerCount = count($session->getPlayers());
|
||
if ($playerCount >= $session->getGame()->getNumberOfPlayers()) {
|
||
return false; // Session full
|
||
}
|
||
|
||
$player = new Player();
|
||
$player->setUser($user);
|
||
$session->addPlayer($player);
|
||
|
||
$this->entityManager->persist($player);
|
||
|
||
$this->entityManager->flush();
|
||
|
||
$this->publishLobbyEvent($session, 'player_joined');
|
||
|
||
if (count($session->getPlayers()) === $session->getGame()->getNumberOfPlayers()) {
|
||
$this->startSession($session);
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
public function leaveSession(Session $session, UserInterface $user): bool
|
||
{
|
||
if (!$user instanceof User) {
|
||
return false;
|
||
}
|
||
|
||
if (!in_array($session->getStatus(), [SessionStatus::CREATED, SessionStatus::READY]) || $session->getTimer() > 0) {
|
||
return false;
|
||
}
|
||
|
||
$playerToDelete = null;
|
||
foreach ($session->getPlayers() as $player) {
|
||
if ($player->getUser() === $user) {
|
||
$playerToDelete = $player;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!$playerToDelete) {
|
||
return false;
|
||
}
|
||
|
||
if ($session->getStatus() === SessionStatus::READY) {
|
||
$session->setStatus(SessionStatus::CREATED);
|
||
// Clear assignments for all remaining players since the game needs to be "started" again
|
||
foreach ($session->getPlayers() as $player) {
|
||
$player->setScreen(null);
|
||
}
|
||
}
|
||
|
||
// Remove player specific settings (like rights)
|
||
foreach ($session->getSettings() as $setting) {
|
||
if ($setting->getPlayer() === $playerToDelete) {
|
||
$session->removeSetting($setting);
|
||
$this->entityManager->remove($setting);
|
||
}
|
||
}
|
||
|
||
$session->removePlayer($playerToDelete);
|
||
$this->entityManager->remove($playerToDelete);
|
||
|
||
// If no players left, we might want to delete the session and its remaining settings
|
||
if ($session->getPlayers()->isEmpty()) {
|
||
foreach ($session->getSettings() as $setting) {
|
||
$this->entityManager->remove($setting);
|
||
}
|
||
$this->entityManager->remove($session);
|
||
}
|
||
|
||
$this->entityManager->flush();
|
||
|
||
return true;
|
||
}
|
||
|
||
private function initializePlayerSettings(Player $player): void
|
||
{
|
||
$screen = $player->getScreen();
|
||
$rightsSettingName = SessionSettingType::tryFrom('RightsForPlayer' . $screen);
|
||
|
||
if ($rightsSettingName) {
|
||
$setting = new SessionSetting();
|
||
$setting->setSession($player->getSession());
|
||
$setting->setPlayer($player);
|
||
$setting->setName($rightsSettingName);
|
||
$setting->setValue(json_encode(['chat', 'help', 'ls', 'pwd']));
|
||
$this->entityManager->persist($setting);
|
||
}
|
||
|
||
$pwdSettingName = SessionSettingType::tryFrom('PwdForPlayer' . $screen);
|
||
|
||
if ($pwdSettingName) {
|
||
$setting = new SessionSetting();
|
||
$setting->setSession($player->getSession());
|
||
$setting->setPlayer($player);
|
||
$setting->setName($pwdSettingName);
|
||
$setting->setValue('/var/home/' . $player->getUser()->getUsername());
|
||
$this->entityManager->persist($setting);
|
||
}
|
||
|
||
$chatTrackingSettingName = SessionSettingType::tryFrom('ChatTrackingForPlayer' . $screen);
|
||
|
||
if ($chatTrackingSettingName) {
|
||
$setting = new SessionSetting();
|
||
$setting->setSession($player->getSession());
|
||
$setting->setPlayer($player);
|
||
$setting->setName($chatTrackingSettingName);
|
||
$setting->setValue(json_encode([]));
|
||
$this->entityManager->persist($setting);
|
||
}
|
||
|
||
$verifyCodesSettingName = SessionSettingType::tryFrom('VerifyCodesForPlayer' . $screen);
|
||
|
||
if ($verifyCodesSettingName) {
|
||
$codes = [];
|
||
$numPlayers = $player->getSession()->getGame()->getNumberOfPlayers();
|
||
for ($i = 1; $i <= $numPlayers; $i++) {
|
||
if ($i !== $screen) {
|
||
$codes[$i] = bin2hex(random_bytes(3)); // 6 characters code
|
||
}
|
||
}
|
||
|
||
$setting = new SessionSetting();
|
||
$setting->setSession($player->getSession());
|
||
$setting->setPlayer($player);
|
||
$setting->setName($verifyCodesSettingName);
|
||
$setting->setValue(json_encode($codes));
|
||
$this->entityManager->persist($setting);
|
||
}
|
||
|
||
$verificationProgressSettingName = SessionSettingType::tryFrom('VerificationProgressForPlayer' . $screen);
|
||
|
||
if ($verificationProgressSettingName) {
|
||
$setting = new SessionSetting();
|
||
$setting->setSession($player->getSession());
|
||
$setting->setPlayer($player);
|
||
$setting->setName($verificationProgressSettingName);
|
||
$setting->setValue(json_encode([]));
|
||
$this->entityManager->persist($setting);
|
||
}
|
||
}
|
||
|
||
// Picks decoy usernames for /var/home and verifyCodes.txt, excluding any name
|
||
// already taken by a real player in this session so they can never collide.
|
||
private function initializeDecoyUsernames(Session $session): void
|
||
{
|
||
$realUsernames = array_map(
|
||
static fn (Player $p) => $p->getUser()->getUsername(),
|
||
$session->getPlayers()->toArray()
|
||
);
|
||
|
||
$availableDecoys = array_values(array_diff(self::DECOY_USERNAME_POOL, $realUsernames));
|
||
shuffle($availableDecoys);
|
||
$selected = array_slice($availableDecoys, 0, self::DECOY_USERNAME_COUNT);
|
||
|
||
$setting = new SessionSetting();
|
||
$setting->setSession($session);
|
||
$setting->setName(SessionSettingType::DECOY_USERNAMES);
|
||
$setting->setValue(json_encode($selected));
|
||
$this->entityManager->persist($setting);
|
||
}
|
||
|
||
// Picks which 3 rapports carry the coded messages this session, so it isn't
|
||
// always the same Doyle/Vega/Lennox trio.
|
||
private function initializeSpecialReportAssignments(Session $session): void
|
||
{
|
||
$files = array_keys(self::RAPPORT_AGENTS);
|
||
shuffle($files);
|
||
$chosen = array_slice($files, 0, self::SPECIAL_REPORT_COUNT);
|
||
|
||
$assignments = [];
|
||
foreach ($chosen as $index => $file) {
|
||
$assignments[] = [
|
||
'file' => $file,
|
||
'agent' => self::RAPPORT_AGENTS[$file],
|
||
'digit' => $index + 1,
|
||
];
|
||
}
|
||
|
||
$setting = new SessionSetting();
|
||
$setting->setSession($session);
|
||
$setting->setName(SessionSettingType::SPECIAL_REPORT_ASSIGNMENTS);
|
||
$setting->setValue(json_encode($assignments));
|
||
$this->entityManager->persist($setting);
|
||
}
|
||
|
||
public function startSession(Session $session): bool
|
||
{
|
||
if ($session->getStatus() !== SessionStatus::CREATED) {
|
||
return false;
|
||
}
|
||
|
||
$players = $session->getPlayers()->toArray();
|
||
if (count($players) < $session->getGame()->getNumberOfPlayers()) {
|
||
return false;
|
||
}
|
||
|
||
// Clean up any existing assignments (e.g. if we reverted from READY to CREATED)
|
||
foreach ($session->getSettings() as $setting) {
|
||
if ($setting->getPlayer() !== null) {
|
||
$session->removeSetting($setting);
|
||
$this->entityManager->remove($setting);
|
||
}
|
||
}
|
||
|
||
// Shuffle players to assign random screens
|
||
shuffle($players);
|
||
|
||
foreach ($players as $index => $player) {
|
||
$screen = $index + 1;
|
||
$player->setScreen($screen);
|
||
$this->initializePlayerSettings($player);
|
||
$this->entityManager->persist($player);
|
||
}
|
||
|
||
$this->initializeDecoyUsernames($session);
|
||
$this->initializeSpecialReportAssignments($session);
|
||
|
||
$session->setStatus(SessionStatus::READY);
|
||
$this->entityManager->persist($session);
|
||
$this->entityManager->flush();
|
||
|
||
$this->publishLobbyEvent($session, 'session_started');
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* @return LobbyMessage[]
|
||
*/
|
||
public function getLobbyMessages(Session $session): array
|
||
{
|
||
return $this->lobbyMessageRepository->findForSession($session);
|
||
}
|
||
|
||
/**
|
||
* The lobby chat is open while a session is still gathering players, and stays
|
||
* open for a grace period after the game ends so players can wrap up the
|
||
* conversation before it disappears.
|
||
*/
|
||
public function isLobbyChatOpen(Session $session): bool
|
||
{
|
||
if ($session->getStatus() === SessionStatus::CREATED) {
|
||
return true;
|
||
}
|
||
|
||
if (!in_array($session->getStatus(), [SessionStatus::WON, SessionStatus::LOST], true)) {
|
||
return false;
|
||
}
|
||
|
||
$finishedAt = $session->getFinishedAt();
|
||
if ($finishedAt === null) {
|
||
return false;
|
||
}
|
||
|
||
return (new \DateTime())->getTimestamp() - $finishedAt->getTimestamp() < self::LOBBY_CHAT_GRACE_PERIOD_SECONDS;
|
||
}
|
||
|
||
public function postLobbyMessage(Session $session, User $user, string $content): ?LobbyMessage
|
||
{
|
||
if (!$this->isLobbyChatOpen($session)) {
|
||
return null;
|
||
}
|
||
|
||
$player = null;
|
||
foreach ($session->getPlayers() as $sessionPlayer) {
|
||
if ($sessionPlayer->getUser() === $user) {
|
||
$player = $sessionPlayer;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!$player) {
|
||
return null;
|
||
}
|
||
|
||
$content = trim($content);
|
||
if ($content === '') {
|
||
return null;
|
||
}
|
||
$content = mb_substr($content, 0, self::LOBBY_MESSAGE_MAX_LENGTH);
|
||
|
||
$message = new LobbyMessage();
|
||
$message->setSession($session);
|
||
$message->setPlayer($player);
|
||
$message->setContent($content);
|
||
|
||
$this->entityManager->persist($message);
|
||
$this->entityManager->flush();
|
||
|
||
$this->publishLobbyEvent($session, 'lobby_message', [
|
||
'username' => $user->getUserIdentifier(),
|
||
'content' => $content,
|
||
'createdAt' => $message->getCreatedAt()->format(DATE_ATOM),
|
||
]);
|
||
|
||
return $message;
|
||
}
|
||
|
||
public function toggleReady(Session $session, User $user): bool
|
||
{
|
||
if ($session->getStatus() !== SessionStatus::READY) {
|
||
return false;
|
||
}
|
||
|
||
if (!$user->isVerified()) {
|
||
return false;
|
||
}
|
||
|
||
$player = null;
|
||
foreach ($session->getPlayers() as $p) {
|
||
if ($p->getUser() === $user) {
|
||
$player = $p;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!$player) {
|
||
return false;
|
||
}
|
||
|
||
$settingName = SessionSettingType::tryFrom('ReadyAtForPlayer' . $player->getScreen());
|
||
if (!$settingName) {
|
||
return false;
|
||
}
|
||
|
||
/** @var \App\Game\Repository\SessionSettingRepository $settingRepo */
|
||
$settingRepo = $this->entityManager->getRepository(SessionSetting::class);
|
||
$existingSetting = $settingRepo->getSetting($session, $settingName, $player);
|
||
$nowReady = $existingSetting === null;
|
||
|
||
if ($existingSetting) {
|
||
$session->removeSetting($existingSetting);
|
||
$this->entityManager->remove($existingSetting);
|
||
} else {
|
||
$setting = new SessionSetting();
|
||
$setting->setSession($session);
|
||
$setting->setPlayer($player);
|
||
$setting->setName($settingName);
|
||
$setting->setValue((string)(new \DateTime())->getTimestamp());
|
||
$this->entityManager->persist($setting);
|
||
}
|
||
|
||
$this->checkAllPlayersReady($session);
|
||
$this->entityManager->flush();
|
||
|
||
// If this toggle just made everyone ready, checkAllPlayersReady() already
|
||
// transitioned the session out of READY and published 'all_ready' —
|
||
// don't also publish a redundant 'player_ready'.
|
||
if ($session->getStatus() === SessionStatus::READY) {
|
||
$this->publishPlayerReady($session, $player->getScreen(), $nowReady);
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* Explicitly (and idempotently) expires the current user's own ready status once
|
||
* their 60-second window is genuinely up. Called by the ready player's own browser
|
||
* on a timer, so the "not ready" broadcast goes out as close to the deadline as
|
||
* possible instead of waiting for someone else's request to lazily discover it.
|
||
*/
|
||
public function expireOwnReadyIfDue(Session $session, User $user): void
|
||
{
|
||
if ($session->getStatus() !== SessionStatus::READY) {
|
||
return;
|
||
}
|
||
|
||
$player = null;
|
||
foreach ($session->getPlayers() as $p) {
|
||
if ($p->getUser() === $user) {
|
||
$player = $p;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!$player) {
|
||
return;
|
||
}
|
||
|
||
$settingName = SessionSettingType::tryFrom('ReadyAtForPlayer' . $player->getScreen());
|
||
if (!$settingName) {
|
||
return;
|
||
}
|
||
|
||
/** @var \App\Game\Repository\SessionSettingRepository $settingRepo */
|
||
$settingRepo = $this->entityManager->getRepository(SessionSetting::class);
|
||
$setting = $settingRepo->getSetting($session, $settingName, $player);
|
||
|
||
if (!$setting) {
|
||
return; // Already not ready
|
||
}
|
||
|
||
$readyAtTimestamp = (int)$setting->getValue();
|
||
if ((new \DateTime())->getTimestamp() - $readyAtTimestamp < self::READY_TIMEOUT_SECONDS) {
|
||
return; // Not actually due yet
|
||
}
|
||
|
||
$session->removeSetting($setting);
|
||
$this->entityManager->remove($setting);
|
||
$this->entityManager->flush();
|
||
|
||
$this->publishPlayerReady($session, $player->getScreen(), false);
|
||
}
|
||
|
||
private function publishPlayerReady(Session $session, int $screen, bool $ready): void
|
||
{
|
||
$this->publishLobbyEvent($session, 'player_ready', ['player' => $screen, 'ready' => $ready]);
|
||
}
|
||
|
||
private function publishLobbyEvent(Session $session, string $type, array $extra = []): void
|
||
{
|
||
$this->eventDispatcher->dispatch(new PushMercureMessageEvent(
|
||
$session,
|
||
array_merge(['type' => $type], $extra),
|
||
$type,
|
||
));
|
||
}
|
||
|
||
public function checkAllPlayersReady(Session $session): void
|
||
{
|
||
if ($session->getStatus() !== SessionStatus::READY) {
|
||
return;
|
||
}
|
||
|
||
$players = $session->getPlayers();
|
||
$numPlayers = $session->getGame()->getNumberOfPlayers();
|
||
|
||
if (count($players) < $numPlayers) {
|
||
return;
|
||
}
|
||
|
||
$readyPlayersCount = 0;
|
||
$now = new \DateTime();
|
||
|
||
/** @var \App\Game\Repository\SessionSettingRepository $settingRepo */
|
||
$settingRepo = $this->entityManager->getRepository(SessionSetting::class);
|
||
|
||
foreach ($players as $player) {
|
||
$settingName = SessionSettingType::tryFrom('ReadyAtForPlayer' . $player->getScreen());
|
||
if (!$settingName) {
|
||
continue;
|
||
}
|
||
|
||
$setting = $settingRepo->getSetting($session, $settingName, $player);
|
||
if (!$setting) {
|
||
continue;
|
||
}
|
||
|
||
$readyAtTimestamp = (int)$setting->getValue();
|
||
if (($now->getTimestamp() - $readyAtTimestamp) >= self::READY_TIMEOUT_SECONDS) {
|
||
$session->removeSetting($setting);
|
||
$this->entityManager->remove($setting);
|
||
$this->entityManager->flush();
|
||
|
||
$this->publishPlayerReady($session, $player->getScreen(), false);
|
||
continue;
|
||
}
|
||
|
||
$readyPlayersCount++;
|
||
}
|
||
|
||
if ($readyPlayersCount === $numPlayers) {
|
||
$session->setStatus(SessionStatus::PLAYING);
|
||
|
||
// Set the end timer
|
||
$game = $session->getGame();
|
||
/** @var \App\Game\Repository\GameSettingRepository $gameSettingRepo */
|
||
$gameSettingRepo = $this->entityManager->getRepository(\App\Game\Entity\GameSetting::class);
|
||
$totalTimeSetting = $gameSettingRepo->getSetting($game, \App\Game\Enum\GameSettingType::TOTAL_TIME);
|
||
$totalTime = $totalTimeSetting ? (int)$totalTimeSetting->getValue() : 3600; // Default to 60 minutes if not set
|
||
|
||
$session->setTimer((new \DateTime())->getTimestamp() + $totalTime);
|
||
|
||
$this->entityManager->persist($session);
|
||
|
||
// Clean up ready settings
|
||
foreach ($players as $player) {
|
||
$settingName = SessionSettingType::tryFrom('ReadyAtForPlayer' . $player->getScreen());
|
||
if ($settingName) {
|
||
$setting = $settingRepo->getSetting($session, $settingName, $player);
|
||
if ($setting) {
|
||
$session->removeSetting($setting);
|
||
$this->entityManager->remove($setting);
|
||
}
|
||
}
|
||
}
|
||
|
||
$this->entityManager->flush();
|
||
|
||
$this->eventDispatcher->dispatch(new PushMercureMessageEvent($session, ['type' => 'all_ready'], 'all_ready'));
|
||
}
|
||
}
|
||
|
||
public function generateInviteCode(Session $session, UserInterface $user, bool $isAdmin): ?string
|
||
{
|
||
// Security check: is user part of this session?
|
||
$isPlayer = false;
|
||
foreach ($session->getPlayers() as $player) {
|
||
if ($player->getUser() === $user) {
|
||
$isPlayer = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!$isPlayer && !$isAdmin) {
|
||
return null;
|
||
}
|
||
|
||
$inviteCode = bin2hex(random_bytes(4));
|
||
|
||
$setting = null;
|
||
foreach ($session->getSettings() as $s) {
|
||
if ($s->getName() === SessionSettingType::INVITE_CODE) {
|
||
$setting = $s;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!$setting) {
|
||
$setting = new SessionSetting();
|
||
$setting->setSession($session);
|
||
$setting->setName(SessionSettingType::INVITE_CODE);
|
||
}
|
||
|
||
$setting->setValue($inviteCode);
|
||
$this->entityManager->persist($setting);
|
||
$this->entityManager->flush();
|
||
|
||
return $inviteCode;
|
||
}
|
||
}
|