diff --git a/modules/lobbies/scripts/find_or_create.ts b/modules/lobbies/scripts/find_or_create.ts index a8a6f166..853fdd58 100644 --- a/modules/lobbies/scripts/find_or_create.ts +++ b/modules/lobbies/scripts/find_or_create.ts @@ -19,7 +19,7 @@ export interface Request { noWait?: boolean; createConfig: { - region: string; + region: string; tags?: Record; maxPlayers: number; maxPlayersDirect: number; @@ -60,19 +60,19 @@ export async function run( { query: { version: req.version, - regions: req.regions, + regions: req.regions, tags: req.tags, }, lobby: { lobbyId, version: req.version, - region: req.createConfig.region, + region: req.createConfig.region, tags: req.createConfig.tags, maxPlayers: req.createConfig.maxPlayers, maxPlayersDirect: req.createConfig.maxPlayersDirect, }, players: req.players, - noWait: req.noWait ?? false, + noWait: req.noWait ?? false, } ); diff --git a/modules/lobbies/scripts/list_regions.ts b/modules/lobbies/scripts/list_regions.ts index 447b21c6..3b816693 100644 --- a/modules/lobbies/scripts/list_regions.ts +++ b/modules/lobbies/scripts/list_regions.ts @@ -1,6 +1,7 @@ import { RuntimeError, ScriptContext } from "../module.gen.ts"; import { getLobbyConfig } from "../utils/lobby_config.ts"; -import { Region, regionsForBackend } from "../utils/region.ts"; +import { getSortedRegionsByProximity, Region, regionsForBackend } from "../utils/region.ts"; +import { getRequestGeoCoords } from "../utils/rivet/geo_coord.ts"; export interface Request { tags?: Record, @@ -16,8 +17,14 @@ export async function run( ): Promise { const lobbyConfig = getLobbyConfig(ctx.config, req.tags ?? {}); - const regions = regionsForBackend(lobbyConfig.backend) - - return { regions }; + const coords = getRequestGeoCoords(ctx); + const regions = regionsForBackend(lobbyConfig.backend); + if (!coords) { + return { regions }; + } + + return { + regions: getSortedRegionsByProximity(regions, coords) + } } diff --git a/modules/lobbies/utils/region.ts b/modules/lobbies/utils/region.ts index eda10bef..332d81be 100644 --- a/modules/lobbies/utils/region.ts +++ b/modules/lobbies/utils/region.ts @@ -8,7 +8,13 @@ import { REGIONS as LOCAL_DEVELOPMENT_REGIONS } from "./lobby/backend/local_deve import { UnreachableError } from "../module.gen.ts"; import { LobbyBackend } from "../config.ts"; -export interface Region { +export interface RegionGeoCoords { + latitude: number; + longitude: number; +} + + +export interface Region extends RegionGeoCoords { id: string; name: string; latitude: number; @@ -22,3 +28,46 @@ export function regionsForBackend(backend: LobbyBackend): Region[] { else throw new UnreachableError(backend); } +// Using haversine formula to calculate approximate distance +function getDistance(a: RegionGeoCoords, b: RegionGeoCoords) { + const EARTH_RADIUS = 6371; // Radius of the earth in km + const DEG_2_RAD = (Math.PI / 180); + + const deltaLatitude = DEG_2_RAD * (b.latitude - a.latitude); + const deltaLongitude = DEG_2_RAD * (b.longitude - a.longitude); + + const aLatitude = DEG_2_RAD * a.latitude; + const bLatitude = DEG_2_RAD * b.latitude; + + const sinOfDLat = Math.sin(deltaLatitude / 2); + const sinOfDLon = Math.sin(deltaLongitude / 2); + const x = sinOfDLat * sinOfDLat + sinOfDLon * sinOfDLon * Math.cos(aLatitude) * Math.cos(bLatitude); + const unitDistance = 2 * Math.atan2(Math.sqrt(x), Math.sqrt(1 - x)); + + return EARTH_RADIUS * unitDistance; +} + +export function getClosestRegion( + region: Region[], + coords: RegionGeoCoords +) { + let closestRegion: Region | null = null; + let closestDistance = Infinity; + for (const r of region) { + const distance = getDistance(r, coords); + if (distance < closestDistance) { + closestRegion = r; + closestDistance = distance; + } + } + return closestRegion; +} + +export function getSortedRegionsByProximity( + regions: Region[], + coords: RegionGeoCoords +) { + return [...regions].sort((a, b) => { + return getDistance(a, coords) - getDistance(b, coords); + }); +} \ No newline at end of file diff --git a/modules/lobbies/utils/rivet/geo_coord.ts b/modules/lobbies/utils/rivet/geo_coord.ts new file mode 100644 index 00000000..1104d40f --- /dev/null +++ b/modules/lobbies/utils/rivet/geo_coord.ts @@ -0,0 +1,44 @@ +import { ScriptContext } from "../../module.gen.ts"; +import { RegionGeoCoords } from "../region.ts"; + +export function getRequestGeoCoords( + ctx: ScriptContext +): RegionGeoCoords | undefined { + // If they aren't real servers, we're probably not in managed + // So we just return + if (!("server" in ctx.config.lobbies.backend)) { + return undefined; + } + + if (ctx.environment.get("RIVET_BACKEND_RUNTIME") !== "cloudflare_workers_platforms") { + return undefined; + } + + const topLevelTrace = ctx.trace.entries[0]; + if (!topLevelTrace || !("httpRequest" in topLevelTrace.type)) { + return undefined; + } + + const httpReq = topLevelTrace.type.httpRequest + if (!httpReq) { + return undefined; + } + + if (!httpReq.headers["x-backend-client-coords"]) { + throw new Error("Missing x-backend-client-coords header"); + } + + const [latitudeStr, longitudeStr] = httpReq.headers["x-backend-client-coords"].split(",") as [string, string]; + + const longitude = parseFloat(longitudeStr.trim()); + const latitude = parseFloat(latitudeStr.trim()); + + if (!isFinite(longitude) || !isFinite(latitude)) { + throw new Error("Invalid x-backend-client-coords header (non-finite)"); + } + + return { + latitude, + longitude + } +} \ No newline at end of file