import { type MapScene } from './mapRenderer' import { type ControlPointStyleEntry, type CourseLegStyleEntry, type ScoreBandStyleEntry } from '../../game/presentation/courseStyleConfig' export type RgbaColor = [number, number, number, number] export interface ResolvedControlStyle { entry: ControlPointStyleEntry color: RgbaColor } export interface ResolvedLegStyle { entry: CourseLegStyleEntry color: RgbaColor } export function hexToRgbaColor(hex: string, alphaOverride?: number): RgbaColor { const fallback: RgbaColor = [1, 1, 1, alphaOverride !== undefined ? alphaOverride : 1] if (typeof hex !== 'string' || !hex || hex.charAt(0) !== '#') { return fallback } const normalized = hex.slice(1) if (normalized.length !== 6 && normalized.length !== 8) { return fallback } const red = parseInt(normalized.slice(0, 2), 16) const green = parseInt(normalized.slice(2, 4), 16) const blue = parseInt(normalized.slice(4, 6), 16) const alpha = normalized.length === 8 ? parseInt(normalized.slice(6, 8), 16) : 255 if (!Number.isFinite(red) || !Number.isFinite(green) || !Number.isFinite(blue) || !Number.isFinite(alpha)) { return fallback } return [ red / 255, green / 255, blue / 255, alphaOverride !== undefined ? alphaOverride : alpha / 255, ] } function resolveScoreBandStyle(scene: MapScene, sequence: number): ScoreBandStyleEntry | null { const score = scene.controlScoresBySequence[sequence] if (score === undefined) { return null } const bands = scene.courseStyleConfig.scoreO.controls.scoreBands for (let index = 0; index < bands.length; index += 1) { const band = bands[index] if (score >= band.min && score <= band.max) { return band } } return null } export function resolveControlStyle(scene: MapScene, kind: 'start' | 'control' | 'finish', sequence: number | null, index?: number): ResolvedControlStyle { if (kind === 'start') { if (index !== undefined && scene.startStyleOverrides[index]) { const entry = scene.startStyleOverrides[index] return { entry, color: hexToRgbaColor(entry.colorHex) } } const entry = scene.gameMode === 'score-o' ? scene.courseStyleConfig.scoreO.controls.start : scene.courseStyleConfig.sequential.controls.start return { entry, color: hexToRgbaColor(entry.colorHex) } } if (kind === 'finish') { if (index !== undefined && scene.finishStyleOverrides[index]) { const entry = scene.finishStyleOverrides[index] return { entry, color: hexToRgbaColor(entry.colorHex) } } const entry = scene.gameMode === 'score-o' ? scene.courseStyleConfig.scoreO.controls.finish : scene.courseStyleConfig.sequential.controls.finish return { entry, color: hexToRgbaColor(entry.colorHex) } } if (sequence === null) { const entry = scene.courseStyleConfig.sequential.controls.default return { entry, color: hexToRgbaColor(entry.colorHex) } } if (scene.controlStyleOverridesBySequence[sequence]) { const entry = scene.controlStyleOverridesBySequence[sequence] return { entry, color: hexToRgbaColor(entry.colorHex) } } if (scene.gameMode === 'score-o') { if (scene.completedControlSequences.includes(sequence)) { const entry = scene.courseStyleConfig.scoreO.controls.collected return { entry, color: hexToRgbaColor(entry.colorHex) } } if (scene.focusedControlSequences.includes(sequence)) { const entry = scene.courseStyleConfig.scoreO.controls.focused return { entry, color: hexToRgbaColor(entry.colorHex) } } const bandEntry = resolveScoreBandStyle(scene, sequence) const entry = bandEntry || scene.courseStyleConfig.scoreO.controls.default return { entry, color: hexToRgbaColor(entry.colorHex) } } if (scene.readyControlSequences.includes(sequence) || scene.activeControlSequences.includes(sequence)) { const entry = scene.courseStyleConfig.sequential.controls.current return { entry, color: hexToRgbaColor(entry.colorHex) } } if (scene.completedControlSequences.includes(sequence)) { const entry = scene.courseStyleConfig.sequential.controls.completed return { entry, color: hexToRgbaColor(entry.colorHex) } } if (scene.skippedControlSequences.includes(sequence)) { const entry = scene.courseStyleConfig.sequential.controls.skipped return { entry, color: hexToRgbaColor(entry.colorHex) } } const entry = scene.courseStyleConfig.sequential.controls.default return { entry, color: hexToRgbaColor(entry.colorHex) } } export function resolveLegStyle(scene: MapScene, index: number): ResolvedLegStyle { if (scene.legStyleOverridesByIndex[index]) { const entry = scene.legStyleOverridesByIndex[index] return { entry, color: hexToRgbaColor(entry.colorHex) } } if (scene.gameMode === 'score-o') { const entry = scene.courseStyleConfig.sequential.legs.default return { entry, color: hexToRgbaColor(entry.colorHex) } } const completed = scene.completedLegIndices.includes(index) const entry = completed ? scene.courseStyleConfig.sequential.legs.completed : scene.courseStyleConfig.sequential.legs.default return { entry, color: hexToRgbaColor(entry.colorHex) } }