Spaces:
Running
Running
| // CS2 map calibration. `pos_x`, `pos_y` are the world coords of the radar | |
| // PNG's upper-left corner; `scale` is units-per-pixel. Multi-floor maps use | |
| // `lower_level_max_units` as a Z threshold: any player at or below it is on | |
| // the lower floor and should be drawn against the `_lower` radar. | |
| // | |
| // Source: awpy's map-data.json (https://awpycs.com/<patch>/maps.zip). | |
| export type MapData = { | |
| pos_x: number; | |
| pos_y: number; | |
| scale: number; | |
| rotate: number | null; | |
| zoom: number | null; | |
| lower_level_max_units: number; | |
| }; | |
| export const RADAR_PX = 1024; | |
| let cache: Record<string, MapData> | null = null; | |
| let inflight: Promise<Record<string, MapData>> | null = null; | |
| export async function loadMapData(fetchFn: typeof fetch = fetch): Promise<Record<string, MapData>> { | |
| if (cache) return cache; | |
| if (!inflight) { | |
| inflight = fetchFn('/maps/map-data.json') | |
| .then((r) => { | |
| if (!r.ok) throw new Error(`map-data.json ${r.status}`); | |
| return r.json() as Promise<Record<string, MapData>>; | |
| }) | |
| .then((d) => { | |
| cache = d; | |
| return d; | |
| }); | |
| } | |
| return inflight; | |
| } | |
| export type Floor = 'upper' | 'lower'; | |
| export function radarUrl(mapName: string, floor: Floor): string { | |
| return floor === 'lower' ? `/maps/${mapName}_lower.webp` : `/maps/${mapName}.webp`; | |
| } | |
| export function hasLowerFloor(map: MapData): boolean { | |
| // awpy uses -1_000_000 as the sentinel for "no lower floor". | |
| return map.lower_level_max_units > -100_000; | |
| } | |
| export function floorForZ(map: MapData, z: number): Floor { | |
| if (!hasLowerFloor(map)) return 'upper'; | |
| // de_vertigo is sky-high (lower_level_max_units = 11700 means below that = | |
| // lower B site). de_nuke / de_train use a negative threshold. The | |
| // inequality is the same in both cases. | |
| return z <= map.lower_level_max_units ? 'lower' : 'upper'; | |
| } | |
| export function worldToImage(map: MapData, x: number, y: number): { px: number; py: number } { | |
| return { | |
| px: (x - map.pos_x) / map.scale, | |
| py: (map.pos_y - y) / map.scale | |
| }; | |
| } | |
| // CS yaw: 0° = +X (east), 90° = +Y (north), CCW positive. Image space has | |
| // Y pointing down, so a screen-space rotation of `-yaw` makes an icon whose | |
| // "forward" is +X point in the player's facing direction. | |
| export function yawToScreenDeg(yaw: number): number { | |
| return -yaw; | |
| } | |