diff --git a/src/Civ13/Local/GaiaHostControl.php b/src/Civ13/Local/GaiaHostControl.php index d4b388b0..d6f81d50 100644 --- a/src/Civ13/Local/GaiaHostControl.php +++ b/src/Civ13/Local/GaiaHostControl.php @@ -44,7 +44,11 @@ public function request(string $action): PromiseInterface /** One line for Discord from a status answer. */ public static function describe(array $status): string { - $parts = [($status['listening'] ?? false) ? 'Server port is open.' : 'Server is not running.']; + $parts = [match ($status['state'] ?? null) { + 'asleep' => 'Server is asleep (frozen in memory; a join or travel wakes it).', + 'cold' => 'Server is asleep (stopped after its last save; a launcher wake or travel starts it).', + default => ($status['listening'] ?? false) ? 'Server port is open.' : 'Server is not running.', + }]; $world = $status['world'] ?? null; if (is_array($world)) { $parts[] = ($world['ready'] ?? false) ? 'World ready' : 'World not ready'; @@ -57,7 +61,17 @@ public static function describe(array $status): string if (is_string($operation['error'] ?? null) && $operation['error'] !== '') { $line .= ': '.substr($operation['error'], 0, 200); } $parts[] = $line; } - return implode(' ', array_map(static fn ($part) => str_ends_with($part, '.') ? $part : $part.'.', $parts)); + $line = implode(' ', array_map(static fn ($part) => str_ends_with($part, '.') ? $part : $part.'.', $parts)); + // A region host (Civ13 scripts/shard-group) also lists every region of its group. + $regions = []; + foreach (is_array($status['regions'] ?? null) ? array_slice($status['regions'], 0, 16) : [] as $region) { + if (!is_array($region) || !is_string($region['name'] ?? null) || !is_string($region['state'] ?? null)) { continue; } + // Letters, marks and digits in any script, cut to 64 characters, never mid-character. + $name = preg_replace('/^(.{0,64}).*$/us', '$1', preg_replace('/[^\p{L}\p{M}\p{N} \-\'().]/u', '', $region['name']) ?? '') ?? ''; + $state = preg_replace('/[^a-z]/', '', $region['state']) ?? ''; + $regions[] = "{$name}: {$state}".(is_int($region['players'] ?? null) ? ", {$region['players']} players" : ''); + } + return $regions === [] ? $line : $line."\nRegions: ".implode('; ', $regions).'.'; } private static function absolute(string $path): bool { return str_starts_with($path, '/') || (bool) preg_match('/^[A-Za-z]:[\\\\\/]/', $path); } diff --git a/tests/local/gaia-host.php b/tests/local/gaia-host.php index 27ad25be..d2e634c4 100644 --- a/tests/local/gaia-host.php +++ b/tests/local/gaia-host.php @@ -42,6 +42,12 @@ function gaiaCheck(bool $ok, string $what): void { if (!$ok) { throw new Runtime [$status, $error] = $run('status'); gaiaCheck($error === null && $status['world']['clients'] === 3, 'status answer'); gaiaCheck(GaiaHostControl::describe($status) === 'Server port is open. World ready. 3 players. Last action: stop (done).', 'status line: '.GaiaHostControl::describe($status)); + $region = ['listening' => true, 'state' => 'awake', 'world' => ['ready' => true, 'clients' => 2], + 'regions' => [['id' => 'gaia', 'name' => 'Gaia', 'state' => 'awake', 'players' => 2], ['id' => 'deep', 'name' => 'The Deep <@&1>', 'state' => 'asleep', 'players' => null], 'junk']]; + gaiaCheck(GaiaHostControl::describe($region) === "Server port is open. World ready. 2 players.\nRegions: Gaia: awake, 2 players; The Deep 1: asleep.", 'region line: '.GaiaHostControl::describe($region)); + gaiaCheck(str_starts_with(GaiaHostControl::describe(['state' => 'cold', 'listening' => false]), 'Server is asleep (stopped'), 'cold line'); + $long = GaiaHostControl::describe(['regions' => [['name' => 'की'.str_repeat('北', 70), 'state' => 'awake']]]); + gaiaCheck(mb_check_encoding($long, 'UTF-8') && str_contains($long, 'Regions: की'.str_repeat('北', 62).': awake.'), 'unicode region name: '.$long); [$value, $error] = $run('start'); gaiaCheck($value === null && $error instanceof RuntimeException && $error->getMessage() === 'Refused: the server is already running', 'refusal text'); [$value, $error] = $run('stop');