outpost->sync(); // Kills leave the feed after 6 seconds; the hurt flash goes. U::setInterval(function () { $this->feed = array_values(array_filter($this->feed, fn($k) => self::now() - $k['at'] < 6000)); $this->refresh(); }, 1000); } public function join(): void { $name = trim($this->name); if ($name === '') { // the input stops at 16 letters, and the game server cuts any more $this->error = 'Pick a callsign.'; return; } $this->error = null; U::window()->outpost->join($name); } public function bots(int $level): void { U::window()->outpost->level($level); } #[On('outpost-level')] public function levelChanged(array $level): void { $this->level = (int) $level[0]; } #[On('outpost-paused')] public function pausedChanged(array $paused): void { $this->paused = (bool) $paused[0]; } #[On('outpost-status')] public function connection(string $json): void { $this->status = json_decode($json); } #[On('outpost-welcome')] public function welcomed(string $json): void { $this->me = json_decode($json, true); [$this->hp, $this->alive] = [100, true]; } #[On('outpost-roster')] public function roster(string $json): void { $this->players = array_column(json_decode($json, true), null, 'id'); } #[On('outpost-hp')] public function health(array $health): void { [$hp, $alive, $shield] = $health + [2 => 0]; if ($hp < $this->hp) { $this->hurtAt = self::now(); } [$this->hp, $this->alive, $this->shield] = [(int) $hp, (bool) $alive, (int) $shield]; } #[On('outpost-kill')] public function killed(string $json): void { $kill = json_decode($json, true); $who = fn($id) => $this->players[$id] ?? ['name' => '?', 'team' => 0]; $this->feed[] = [ 'killer' => $who($kill['killer']), 'victim' => $who($kill['victim']), 'gun' => self::GUNS[$kill['gun'] ?? 0] ?? 'rifle', 'head' => $kill['head'], 'at' => self::now(), ]; $this->feed = array_slice($this->feed, -5); } #[On('outpost-scores')] public function tab(array $held): void { $this->scores = (bool) $held[0]; } /** Kills by team. */ private function teamScores(): array { $scores = [0, 0]; foreach ($this->players as $p) { $scores[$p['team']] += $p['kills']; } return $scores; } /** Players on `$team`, most kills first. */ private function team(int $team): array { $players = array_filter($this->players, fn($p) => $p['team'] === $team); uasort($players, fn($a, $b) => [$b['kills'], $a['deaths']] <=> [$a['kills'], $b['deaths']]); return $players; } /** Now, in ms: a float, as the browser's PHP is 32-bit. */ private static function now(): float { return microtime(true) * 1000; } }; ?> @php($score = $this->teamScores())
{{ $score[0] }} Raiders vs Guards {{ $score[1] }}
@if ($status === 'closed')

OUTPOST

Can't reach the game server. Start it with npm run outpost:server, then reload.

@elseif ($me === null)

OUTPOST

Team deathmatch. You'll join the smaller side.

@if ($error)

{{ $error }}

@endif {{-- The bots' level, for the whole match: whoever changes it, everyone sees. --}}
Bots @foreach ($levels as $i => $label) @endforeach For everyone in the match
call($this, [...get_defined_vars(), 'slot' => $__slot, ...( ['levels' => self::LEVELS])]); ?>
@else @php([$class, $label] = self::TEAMS[$me['team']])
$hp <= 25])> ✚{{ $hp }} {{ $players[$me['id']]['name'] ?? $name }} · {{ $label }}
@if (!$alive)
You were killed. Back in a moment…
@elseif ($shield > 0)
Look around · {{ $shield }}Unseen. You can't move or shoot yet.
@endif @if ($paused)

Paused · click to play

{{-- The bots' level, for the whole match: whoever changes it, everyone sees. --}}
Bots @foreach ($levels as $i => $label) @endforeach For everyone in the match
call($this, [...get_defined_vars(), 'slot' => $__slot, ...( ['levels' => self::LEVELS])]); ?>
@endif @if (self::now() - $hurtAt < 600)
@endif @endif @if ($scores)
@foreach (self::TEAMS as $team => [$class, $label]) @foreach ($this->team($team) as $id => $p) $id === ($me['id'] ?? null), 'down' => !$p['alive']])> @endforeach
{{ $label }}{{ $score[$team] }}KD
{{ $p['name'] }}{{ $p['kills'] }}{{ $p['deaths'] }}
@endforeach
@endif