Expand terminal filesystem, add mainframe hint cron, redirect PHP logs
- Game1 terminal: flesh out the virtual filesystem with a realistic spread of Linux directories/files (~70 dirs, ~140 files) so it no longer reads as an obviously small puzzle set, without touching any win-condition or rapport files. - Add app:hints:check command + a php-cron container (BusyBox crond) that nudges players who haven't contacted every teammate 5 minutes into a session, via a new 'hint' Mercure message type. - Log the cron command's output to var/log/cron/cron.log and rotate it (25MB / 90 days) via logrotate, run daily from the same crontab. - Redirect PHP's error_log and Symfony's prod app/deprecation logs from stderr-only into var/log/php/*.log (kept alongside stderr), with the same rotation policy. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
use App\Game\Entity\Session;
|
||||
use App\Game\Enum\GameSettingType;
|
||||
use App\Game\Enum\SessionSettingType;
|
||||
use App\Game\Enum\SessionStatus;
|
||||
use App\Game\Repository\GameSettingRepository;
|
||||
use App\Game\Repository\SessionRepository;
|
||||
use App\Game\Repository\SessionSettingRepository;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Mercure\HubInterface;
|
||||
use Symfony\Component\Mercure\Update;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'app:hints:check',
|
||||
description: 'Checks running game sessions and sends mainframe hints to players who are behind schedule.'
|
||||
)]
|
||||
final class SendMainframeHintsCommand extends Command
|
||||
{
|
||||
private const DEFAULT_TOTAL_TIME = 3600;
|
||||
|
||||
public function __construct(
|
||||
private readonly SessionRepository $sessionRepository,
|
||||
private readonly SessionSettingRepository $sessionSettingRepository,
|
||||
private readonly GameSettingRepository $gameSettingRepository,
|
||||
private readonly HubInterface $hub,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$sessions = $this->sessionRepository->findBy(['status' => SessionStatus::PLAYING]);
|
||||
$hintsSent = 0;
|
||||
|
||||
foreach ($sessions as $session) {
|
||||
if ($this->checkContactHint($session)) {
|
||||
$hintsSent++;
|
||||
}
|
||||
}
|
||||
|
||||
$output->writeln(sprintf('<info>Checked %d running session(s), sent %d hint(s).</info>', count($sessions), $hintsSent));
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* "Get in touch" hint: if 5 minutes into the game the players haven't messaged
|
||||
* everyone (a private message to each other player, plus one broadcast), nudge them.
|
||||
* Repeats every run of this command for as long as the condition still holds.
|
||||
*/
|
||||
private function checkContactHint(Session $session): bool
|
||||
{
|
||||
$elapsed = $this->getElapsedPlayingSeconds($session);
|
||||
if ($elapsed === null || $elapsed < 300) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->allPlayersHaveContactedEveryone($session)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->publishHint($session, 'Get in contact with your fellow agents to work together on defeating this AI virus.');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function getElapsedPlayingSeconds(Session $session): ?int
|
||||
{
|
||||
$timer = $session->getTimer();
|
||||
if ($timer === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$totalTimeSetting = $this->gameSettingRepository->getSetting($session->getGame(), GameSettingType::TOTAL_TIME);
|
||||
$totalTime = $totalTimeSetting ? (int)$totalTimeSetting->getValue() : self::DEFAULT_TOTAL_TIME;
|
||||
|
||||
$startedAt = $timer - $totalTime;
|
||||
|
||||
return time() - $startedAt;
|
||||
}
|
||||
|
||||
private function allPlayersHaveContactedEveryone(Session $session): bool
|
||||
{
|
||||
$players = $session->getPlayers();
|
||||
$screens = [];
|
||||
|
||||
foreach ($players as $player) {
|
||||
if ($player->getScreen() === null) {
|
||||
return false;
|
||||
}
|
||||
$screens[] = $player->getScreen();
|
||||
}
|
||||
|
||||
foreach ($players as $player) {
|
||||
$screen = $player->getScreen();
|
||||
$trackingSettingName = SessionSettingType::tryFrom('ChatTrackingForPlayer' . $screen);
|
||||
if (!$trackingSettingName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$setting = $this->sessionSettingRepository->getSetting($session, $trackingSettingName, $player);
|
||||
$tracking = $setting ? (json_decode($setting->getValue() ?? '[]', true) ?? []) : [];
|
||||
|
||||
if (!in_array(0, $tracking)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($screens as $otherScreen) {
|
||||
if ($otherScreen !== $screen && !in_array($otherScreen, $tracking)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function publishHint(Session $session, string $message): void
|
||||
{
|
||||
$topic = '/game/hub/' . $session->getId();
|
||||
try {
|
||||
$this->hub->publish(new Update($topic, json_encode([0, $message, 'hint'])));
|
||||
} catch (\Exception $e) {
|
||||
// Mercure might be down
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user