208 lines
7.0 KiB
TypeScript
208 lines
7.0 KiB
TypeScript
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
|
|
}
|
|
|
|
function resolveCompletedBoundaryEntry(scene: MapScene, baseEntry: ControlPointStyleEntry): ControlPointStyleEntry {
|
|
const completedPalette = scene.gameMode === 'score-o'
|
|
? scene.courseStyleConfig.scoreO.controls.collected
|
|
: scene.courseStyleConfig.sequential.controls.completed
|
|
|
|
return {
|
|
...baseEntry,
|
|
colorHex: completedPalette.colorHex,
|
|
labelColorHex: completedPalette.labelColorHex || baseEntry.labelColorHex,
|
|
glowStrength: 0,
|
|
}
|
|
}
|
|
|
|
function mergeControlStyleEntries(
|
|
baseEntry: ControlPointStyleEntry,
|
|
overrideEntry?: ControlPointStyleEntry | null,
|
|
): ControlPointStyleEntry {
|
|
if (!overrideEntry) {
|
|
return baseEntry
|
|
}
|
|
|
|
return {
|
|
...baseEntry,
|
|
...overrideEntry,
|
|
}
|
|
}
|
|
|
|
function mergeLegStyleEntries(
|
|
baseEntry: CourseLegStyleEntry,
|
|
overrideEntry?: CourseLegStyleEntry | null,
|
|
): CourseLegStyleEntry {
|
|
if (!overrideEntry) {
|
|
return baseEntry
|
|
}
|
|
|
|
return {
|
|
...baseEntry,
|
|
...overrideEntry,
|
|
}
|
|
}
|
|
|
|
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') {
|
|
const baseEntry = index !== undefined && scene.startStyleOverrides[index]
|
|
? scene.startStyleOverrides[index]
|
|
: scene.gameMode === 'score-o'
|
|
? scene.courseStyleConfig.scoreO.controls.start
|
|
: scene.courseStyleConfig.sequential.controls.start
|
|
const entry = scene.completedStart
|
|
? resolveCompletedBoundaryEntry(scene, baseEntry)
|
|
: baseEntry
|
|
return { entry, color: hexToRgbaColor(entry.colorHex) }
|
|
}
|
|
|
|
if (kind === 'finish') {
|
|
const baseEntry = index !== undefined && scene.finishStyleOverrides[index]
|
|
? scene.finishStyleOverrides[index]
|
|
: scene.gameMode === 'score-o'
|
|
? scene.courseStyleConfig.scoreO.controls.finish
|
|
: scene.courseStyleConfig.sequential.controls.finish
|
|
const entry = scene.completedFinish
|
|
? resolveCompletedBoundaryEntry(scene, baseEntry)
|
|
: baseEntry
|
|
return { entry, color: hexToRgbaColor(entry.colorHex) }
|
|
}
|
|
|
|
if (sequence === null) {
|
|
const entry = scene.courseStyleConfig.sequential.controls.default
|
|
return { entry, color: hexToRgbaColor(entry.colorHex) }
|
|
}
|
|
const sequenceOverride = scene.controlStyleOverridesBySequence[sequence]
|
|
const defaultOverride = scene.defaultControlStyleOverride
|
|
|
|
if (scene.gameMode === 'score-o') {
|
|
if (scene.completedControlSequences.includes(sequence)) {
|
|
const entry = mergeControlStyleEntries(
|
|
scene.courseStyleConfig.scoreO.controls.collected,
|
|
sequenceOverride || defaultOverride,
|
|
)
|
|
return { entry, color: hexToRgbaColor(entry.colorHex) }
|
|
}
|
|
|
|
if (scene.focusedControlSequences.includes(sequence)) {
|
|
const bandEntry = resolveScoreBandStyle(scene, sequence)
|
|
const baseEntry = bandEntry || scene.courseStyleConfig.scoreO.controls.default
|
|
const focusedEntry = scene.courseStyleConfig.scoreO.controls.focused
|
|
const focusedMergedEntry: ControlPointStyleEntry = {
|
|
...baseEntry,
|
|
...focusedEntry,
|
|
colorHex: baseEntry.colorHex,
|
|
}
|
|
const entry = mergeControlStyleEntries(focusedMergedEntry, sequenceOverride || defaultOverride)
|
|
return { entry, color: hexToRgbaColor(entry.colorHex) }
|
|
}
|
|
|
|
const bandEntry = resolveScoreBandStyle(scene, sequence)
|
|
const entry = mergeControlStyleEntries(
|
|
bandEntry || scene.courseStyleConfig.scoreO.controls.default,
|
|
sequenceOverride || defaultOverride,
|
|
)
|
|
return { entry, color: hexToRgbaColor(entry.colorHex) }
|
|
}
|
|
|
|
if (scene.readyControlSequences.includes(sequence) || scene.activeControlSequences.includes(sequence)) {
|
|
const entry = mergeControlStyleEntries(
|
|
scene.courseStyleConfig.sequential.controls.current,
|
|
sequenceOverride || defaultOverride,
|
|
)
|
|
return { entry, color: hexToRgbaColor(entry.colorHex) }
|
|
}
|
|
|
|
if (scene.completedControlSequences.includes(sequence)) {
|
|
const entry = mergeControlStyleEntries(
|
|
scene.courseStyleConfig.sequential.controls.completed,
|
|
sequenceOverride || defaultOverride,
|
|
)
|
|
return { entry, color: hexToRgbaColor(entry.colorHex) }
|
|
}
|
|
|
|
if (scene.skippedControlSequences.includes(sequence)) {
|
|
const entry = mergeControlStyleEntries(
|
|
scene.courseStyleConfig.sequential.controls.skipped,
|
|
sequenceOverride || defaultOverride,
|
|
)
|
|
return { entry, color: hexToRgbaColor(entry.colorHex) }
|
|
}
|
|
|
|
const entry = mergeControlStyleEntries(
|
|
scene.courseStyleConfig.sequential.controls.default,
|
|
sequenceOverride || defaultOverride,
|
|
)
|
|
return { entry, color: hexToRgbaColor(entry.colorHex) }
|
|
}
|
|
|
|
export function resolveLegStyle(scene: MapScene, index: number): ResolvedLegStyle {
|
|
if (scene.gameMode === 'score-o') {
|
|
const entry = mergeLegStyleEntries(
|
|
scene.courseStyleConfig.sequential.legs.default,
|
|
scene.legStyleOverridesByIndex[index] || scene.defaultLegStyleOverride,
|
|
)
|
|
return { entry, color: hexToRgbaColor(entry.colorHex) }
|
|
}
|
|
|
|
const completed = scene.completedLegIndices.includes(index)
|
|
const baseEntry = completed ? scene.courseStyleConfig.sequential.legs.completed : scene.courseStyleConfig.sequential.legs.default
|
|
const entry = mergeLegStyleEntries(baseEntry, scene.legStyleOverridesByIndex[index] || scene.defaultLegStyleOverride)
|
|
return { entry, color: hexToRgbaColor(entry.colorHex) }
|
|
}
|