File size: 2,255 Bytes
31d3580
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95e3d2a
31d3580
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15d8696
31d3580
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// 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;
}