169 lines
4.3 KiB
TypeScript
169 lines
4.3 KiB
TypeScript
// lib/trackMapConfig.ts
|
|
// Parse and use AC track map.ini configuration files
|
|
|
|
export interface TrackMapConfig {
|
|
width: number;
|
|
height: number;
|
|
margin: number;
|
|
scaleFactor: number;
|
|
xOffset: number;
|
|
zOffset: number;
|
|
drawingSize: number;
|
|
}
|
|
|
|
// Cache for parsed configs
|
|
const configCache = new Map<string, TrackMapConfig | null>();
|
|
|
|
/**
|
|
* Clean track name from database format (removes CSP prefix)
|
|
* Example: "csp/2100/../spa" -> "spa"
|
|
*/
|
|
export function cleanTrackPath(track: string): string {
|
|
// Remove CSP prefix pattern: csp/XXXX/../
|
|
const cleaned = track.replace(/^csp\/\d+\/\.\.\//, '');
|
|
return cleaned;
|
|
}
|
|
|
|
/**
|
|
* Fetch and parse a track's map.ini file
|
|
*/
|
|
export async function getTrackMapConfig(
|
|
track: string,
|
|
trackConfig: string = ''
|
|
): Promise<TrackMapConfig | null> {
|
|
// Clean track name first
|
|
const cleanTrack = cleanTrackPath(track);
|
|
const cleanConfig = trackConfig || '';
|
|
|
|
const cacheKey = `${cleanTrack}|${cleanConfig}`;
|
|
|
|
// Check cache first
|
|
if (configCache.has(cacheKey)) {
|
|
return configCache.get(cacheKey)!;
|
|
}
|
|
|
|
try {
|
|
// Try to fetch from openwheels.racing first
|
|
let configUrl = cleanConfig && cleanConfig !== 'default'
|
|
? `https://files.openwheels.racing/img/tracks/${cleanTrack}/${cleanConfig}/map.ini`
|
|
: `https://files.openwheels.racing/img/tracks/${cleanTrack}/map.ini`;
|
|
|
|
let response = await fetch(configUrl);
|
|
|
|
// Fallback to local public directory
|
|
if (!response.ok) {
|
|
const localPath = cleanConfig && cleanConfig !== 'default'
|
|
? `/tracks/${cleanTrack}/${cleanConfig}/map.ini`
|
|
: `/tracks/${cleanTrack}/map.ini`;
|
|
|
|
response = await fetch(localPath);
|
|
}
|
|
|
|
if (!response.ok) {
|
|
console.log(`[TrackMap] No map.ini found for ${cleanTrack}/${cleanConfig}`);
|
|
configCache.set(cacheKey, null);
|
|
return null;
|
|
}
|
|
|
|
const iniText = await response.text();
|
|
const config = parseMapIni(iniText);
|
|
|
|
console.log(`[TrackMap] Loaded config for ${cleanTrack}/${cleanConfig}:`, config);
|
|
configCache.set(cacheKey, config);
|
|
return config;
|
|
} catch (error) {
|
|
console.warn(`[TrackMap] Failed to load map.ini for ${cleanTrack}:`, error);
|
|
configCache.set(cacheKey, null);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse map.ini text format
|
|
*/
|
|
function parseMapIni(iniText: string): TrackMapConfig {
|
|
const lines = iniText.split('\n');
|
|
const config: Partial<TrackMapConfig> = {};
|
|
|
|
for (const line of lines) {
|
|
const trimmed = line.trim();
|
|
|
|
// Skip comments and empty lines
|
|
if (!trimmed || trimmed.startsWith(';') || trimmed.startsWith('[')) {
|
|
continue;
|
|
}
|
|
|
|
// Parse KEY=VALUE
|
|
const [key, value] = trimmed.split('=').map(s => s.trim());
|
|
if (!key || !value) continue;
|
|
|
|
const numValue = parseFloat(value);
|
|
|
|
switch (key.toUpperCase()) {
|
|
case 'WIDTH':
|
|
config.width = numValue;
|
|
break;
|
|
case 'HEIGHT':
|
|
config.height = numValue;
|
|
break;
|
|
case 'MARGIN':
|
|
config.margin = numValue;
|
|
break;
|
|
case 'SCALE_FACTOR':
|
|
config.scaleFactor = numValue;
|
|
break;
|
|
case 'X_OFFSET':
|
|
config.xOffset = numValue;
|
|
break;
|
|
case 'Z_OFFSET':
|
|
config.zOffset = numValue;
|
|
break;
|
|
case 'DRAWING_SIZE':
|
|
config.drawingSize = numValue;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Return with defaults if any values are missing
|
|
return {
|
|
width: config.width || 1000,
|
|
height: config.height || 1000,
|
|
margin: config.margin || 20,
|
|
scaleFactor: config.scaleFactor || 1.0,
|
|
xOffset: config.xOffset || 0,
|
|
zOffset: config.zOffset || 0,
|
|
drawingSize: config.drawingSize || 10,
|
|
};
|
|
}
|
|
|
|
|
|
export function worldToMapCoords(
|
|
worldX: number,
|
|
worldY: number,
|
|
worldZ: number,
|
|
config: TrackMapConfig
|
|
): { x: number; y: number } {
|
|
var aspectRatio = config.width / config.height;
|
|
|
|
// Add offsets to player position
|
|
console.log("config" , config);
|
|
console.log("aspectRatio:", aspectRatio);
|
|
if (aspectRatio < 1) {
|
|
worldX = worldX * aspectRatio;
|
|
} else {
|
|
worldZ = worldZ / aspectRatio;
|
|
}
|
|
|
|
var x = (worldX) + config.xOffset;
|
|
var y = (worldZ) + config.zOffset;
|
|
|
|
y /= config.scaleFactor;
|
|
x /= config.scaleFactor;
|
|
|
|
// Percentages
|
|
x = (x * 100) / (config.width);
|
|
y = (y * 100) / (config.height);
|
|
|
|
return { x, y };
|
|
}
|