Restore locked files if not all removed within 60 seconds
Removing one of the 3 AI-virus-protected files now starts a 60-second window (tracked session-wide via LockedFilesRemovalDeadline). If the other locked files aren't also removed before it expires, the virus restores whichever ones were deleted and broadcasts a red warning to the whole session, forcing players to coordinate the removal instead of picking them off one at a time. Also extends the Mercure broadcast payload with an optional 3rd "messageType" element so pushed messages can render red (virus) or green (mainframe) instead of always defaulting to green.
This commit is contained in:
+2
-6
@@ -14,7 +14,7 @@ function subscribeToMercure(mercurePublicUrl, topic, myScreen) {
|
|||||||
const data = JSON.parse(event.data);
|
const data = JSON.parse(event.data);
|
||||||
console.log('[Mercure][game1] Update:', data);
|
console.log('[Mercure][game1] Update:', data);
|
||||||
|
|
||||||
// data is [sendTo, message]
|
// data is [sendTo, message, messageType?] - messageType defaults to 'mainframe' (green)
|
||||||
if (Array.isArray(data) && data.length >= 2) {
|
if (Array.isArray(data) && data.length >= 2) {
|
||||||
const sendTo = parseInt(data[0]);
|
const sendTo = parseInt(data[0]);
|
||||||
// Filter: 0 means everyone, otherwise must match myScreen
|
// Filter: 0 means everyone, otherwise must match myScreen
|
||||||
@@ -25,11 +25,7 @@ function subscribeToMercure(mercurePublicUrl, topic, myScreen) {
|
|||||||
|
|
||||||
const messageContainer = document.getElementById('message-container');
|
const messageContainer = document.getElementById('message-container');
|
||||||
if (messageContainer) {
|
if (messageContainer) {
|
||||||
const msgEl = document.createElement('div');
|
appendResultMessage(messageContainer, data[1], data[2] || 'mainframe');
|
||||||
msgEl.className = 'message';
|
|
||||||
msgEl.textContent = data[1];
|
|
||||||
msgEl.style.color = '#0F0'; // Green for incoming messages
|
|
||||||
messageContainer.appendChild(msgEl);
|
|
||||||
window.scrollTo(0, document.body.scrollHeight);
|
window.scrollTo(0, document.body.scrollHeight);
|
||||||
if(stillPlayingSound)
|
if(stillPlayingSound)
|
||||||
playSound();
|
playSound();
|
||||||
|
|||||||
@@ -84,4 +84,5 @@ enum SessionSettingType: string
|
|||||||
case LOCK_FOR_PLAYER8 = 'LockForPlayer8';
|
case LOCK_FOR_PLAYER8 = 'LockForPlayer8';
|
||||||
case LOCK_FOR_PLAYER9 = 'LockForPlayer9';
|
case LOCK_FOR_PLAYER9 = 'LockForPlayer9';
|
||||||
case LOCK_FOR_PLAYER10 = 'LockForPlayer10';
|
case LOCK_FOR_PLAYER10 = 'LockForPlayer10';
|
||||||
|
case LOCKED_FILES_REMOVAL_DEADLINE = 'LockedFilesRemovalDeadline';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,6 +69,8 @@ class GameResponseService
|
|||||||
if(!$player)
|
if(!$player)
|
||||||
return ['error' => 'You are not in a game.'];
|
return ['error' => 'You are not in a game.'];
|
||||||
|
|
||||||
|
$this->enforceLockedFilesRemovalDeadline($player->getSession());
|
||||||
|
|
||||||
$this->logSessionActivity($player, 'PLAYER: ' . $message);
|
$this->logSessionActivity($player, 'PLAYER: ' . $message);
|
||||||
|
|
||||||
$data = $this->handleLockedPlayer($message, $player);
|
$data = $this->handleLockedPlayer($message, $player);
|
||||||
@@ -228,6 +230,7 @@ class GameResponseService
|
|||||||
return ['result' => ['You are not allowed to remove this file.']];
|
return ['result' => ['You are not allowed to remove this file.']];
|
||||||
|
|
||||||
$this->playerService->addDeletedFileToSession($player, $fullPath);
|
$this->playerService->addDeletedFileToSession($player, $fullPath);
|
||||||
|
$this->startLockedFilesRemovalDeadline($player->getSession(), $fullPath);
|
||||||
$lock = $this->triggerLock($player);
|
$lock = $this->triggerLock($player);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@@ -1026,6 +1029,77 @@ class GameResponseService
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts (if not already running) the 60-second window within which all locked files
|
||||||
|
* must be removed, or the ones already removed get restored by the virus.
|
||||||
|
*/
|
||||||
|
private function startLockedFilesRemovalDeadline(Session $session, string $removedFile): void
|
||||||
|
{
|
||||||
|
if (!in_array($removedFile, $this->getLockedFiles())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$setting = $this->sessionSettingRepository->getSetting($session, SessionSettingType::LOCKED_FILES_REMOVAL_DEADLINE);
|
||||||
|
if ($setting && $setting->getValue()) {
|
||||||
|
return; // Window already running
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$setting) {
|
||||||
|
$setting = new SessionSetting();
|
||||||
|
$setting->setSession($session);
|
||||||
|
$setting->setName(SessionSettingType::LOCKED_FILES_REMOVAL_DEADLINE);
|
||||||
|
}
|
||||||
|
|
||||||
|
$setting->setValue((string)(time() + 60));
|
||||||
|
$this->entityManager->persist($setting);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lazily checked on every player interaction: if the 60-second window to remove all
|
||||||
|
* locked files has expired without all of them being removed, restore whichever ones
|
||||||
|
* were removed and clear the window.
|
||||||
|
*/
|
||||||
|
private function enforceLockedFilesRemovalDeadline(Session $session): void
|
||||||
|
{
|
||||||
|
$setting = $this->sessionSettingRepository->getSetting($session, SessionSettingType::LOCKED_FILES_REMOVAL_DEADLINE);
|
||||||
|
if (!$setting || !$setting->getValue()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (time() < (int)$setting->getValue()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$player = $session->getPlayers()->first() ?: null;
|
||||||
|
$lockedFiles = $this->getLockedFiles();
|
||||||
|
|
||||||
|
if ($player) {
|
||||||
|
$deletedFiles = $this->playerService->getDeletedFilesOfSession($player);
|
||||||
|
$stillLocked = array_intersect($lockedFiles, $deletedFiles);
|
||||||
|
|
||||||
|
if (count($stillLocked) < count($lockedFiles)) {
|
||||||
|
foreach ($stillLocked as $file) {
|
||||||
|
$this->playerService->removeDeletedFileFromSession($player, $file);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($stillLocked)) {
|
||||||
|
$topic = '/game/hub/' . $session->getId();
|
||||||
|
$message = 'AI VIRUS: Integrity check complete. Restored files that were not fully purged in time.';
|
||||||
|
try {
|
||||||
|
$this->hub->publish(new Update($topic, json_encode([0, $message, 'virus'])));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// Mercure might be down
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$setting->setValue(null);
|
||||||
|
$this->entityManager->persist($setting);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
}
|
||||||
|
|
||||||
private function fileExists(string $file, Player $player) : bool
|
private function fileExists(string $file, Player $player) : bool
|
||||||
{
|
{
|
||||||
$files = $this->getAllPossibleFiles($player);
|
$files = $this->getAllPossibleFiles($player);
|
||||||
|
|||||||
@@ -93,4 +93,21 @@ class PlayerService
|
|||||||
$this->entityManager->flush();
|
$this->entityManager->flush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function removeDeletedFileFromSession(Player $player, string $filename): void
|
||||||
|
{
|
||||||
|
$setting = $this->sessionSettingRepository->getSetting($player->getSession(), SessionSettingType::SET_OF_DELETED_FILES);
|
||||||
|
if (!$setting || !$setting->getValue()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$deletedFiles = json_decode($setting->getValue(), true) ?? [];
|
||||||
|
$newDeletedFiles = array_values(array_diff($deletedFiles, [$filename]));
|
||||||
|
|
||||||
|
if (count($newDeletedFiles) !== count($deletedFiles)) {
|
||||||
|
$setting->setValue(json_encode($newDeletedFiles));
|
||||||
|
$this->entityManager->persist($setting);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user