完善样式系统与调试链路底座

This commit is contained in:
2026-03-30 18:19:05 +08:00
parent 2c0fd4c549
commit 3b9117427e
40 changed files with 7526 additions and 389 deletions

View File

@@ -0,0 +1,142 @@
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) }
}