user(); abort_unless($user !== null, 401); $me = Character::ensureForUser($user); $slug = $request->query('city'); $city = $me->city; if (is_string($slug) && $slug !== '') { $city = City::query() ->where('slug', $slug) ->orWhere('id', ctype_digit($slug) ? (int) $slug : 0) ->first() ?? $city; } // Rows are shared across users for one second, so is_me must NOT // be baked into the cached payload — it is computed per viewer // below (otherwise user B sees user A's is_me flags and could see // their own character as a remote citizen). $cacheKey = 'presence.'.$city->id.'.'.now()->timestamp; $citizens = Cache::remember($cacheKey, 1, function () use ($city) { return Character::query() ->where('city_id', $city->id) ->where(function ($q): void { $q->where('is_online', true) ->orWhere('updated_at', '>=', now()->subMinutes(10)); }) ->with('user:id,name') ->get() ->map(fn (Character $c): array => [ 'id' => $c->id, 'name' => $c->name, 'type' => $c->user_id === null ? 'child' : 'citizen', 'status' => $c->state, 'position' => [(float) $c->pos_x, (float) $c->pos_y, (float) $c->pos_z], 'is_online' => (bool) $c->is_online, 'owner_name' => $c->user?->name, ]) ->values() ->all(); }); $viewerId = (int) $me->id; $citizens = array_map( static fn (array $c): array => $c + ['is_me' => (int) $c['id'] === $viewerId], $citizens ); return response()->json([ 'city_id' => $city->id, 'server_time' => now()->toIso8601String(), 'citizens' => $citizens, ])->header('Cache-Control', 'no-store'); } }