ACPlayer_Webpage/lib/trackMapConfig.ts

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 };
}