Files
cmr-mini/miniprogram/engine/renderer/courseStyleResolver.ts

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