feat: 收敛玩法运行时配置并加入故障恢复

This commit is contained in:
2026-04-01 13:04:26 +08:00
parent 1635a11780
commit 3ef841ecc7
73 changed files with 8820 additions and 2122 deletions

View File

@@ -126,7 +126,7 @@ export class CourseLabelRenderer {
const offsetX = this.getMetric(scene, controlRadiusMeters * LABEL_OFFSET_X_RATIO)
const offsetY = this.getMetric(scene, controlRadiusMeters * LABEL_OFFSET_Y_RATIO)
if (scene.controlVisualMode === 'multi-target') {
if (scene.gameMode === 'score-o' || scene.controlVisualMode === 'multi-target') {
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
@@ -139,7 +139,7 @@ export class CourseLabelRenderer {
ctx.fillStyle = this.getScoreLabelColor(scene, control.sequence)
ctx.translate(control.point.x, control.point.y)
ctx.rotate(scene.rotationRad)
ctx.fillText(String(control.sequence), 0, scoreOffsetY)
ctx.fillText(this.getControlLabelText(scene, control.sequence), 0, scoreOffsetY)
ctx.restore()
}
} else {
@@ -388,6 +388,16 @@ export class CourseLabelRenderer {
: rgbaToCss(resolvedStyle.color, 0.98)
}
getControlLabelText(scene: MapScene, sequence: number): string {
if (scene.gameMode === 'score-o') {
const score = scene.controlScoresBySequence[sequence]
if (typeof score === 'number' && Number.isFinite(score)) {
return String(score)
}
}
return String(sequence)
}
clearCanvas(ctx: any): void {
ctx.setTransform(1, 0, 0, 1, 0, 0)
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)

View File

@@ -13,6 +13,47 @@ export interface ResolvedLegStyle {
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) !== '#') {
@@ -59,24 +100,26 @@ function resolveScoreBandStyle(scene: MapScene, sequence: number): ScoreBandStyl
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'
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') {
if (index !== undefined && scene.finishStyleOverrides[index]) {
const entry = scene.finishStyleOverrides[index]
return { entry, color: hexToRgbaColor(entry.colorHex) }
}
const entry = scene.gameMode === 'score-o'
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) }
}
@@ -84,59 +127,81 @@ export function resolveControlStyle(scene: MapScene, kind: 'start' | 'control' |
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) }
}
const sequenceOverride = scene.controlStyleOverridesBySequence[sequence]
const defaultOverride = scene.defaultControlStyleOverride
if (scene.gameMode === 'score-o') {
if (scene.completedControlSequences.includes(sequence)) {
const entry = scene.courseStyleConfig.scoreO.controls.collected
const entry = mergeControlStyleEntries(
scene.courseStyleConfig.scoreO.controls.collected,
sequenceOverride || defaultOverride,
)
return { entry, color: hexToRgbaColor(entry.colorHex) }
}
if (scene.focusedControlSequences.includes(sequence)) {
const entry = scene.courseStyleConfig.scoreO.controls.focused
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 = bandEntry || scene.courseStyleConfig.scoreO.controls.default
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 = scene.courseStyleConfig.sequential.controls.current
const entry = mergeControlStyleEntries(
scene.courseStyleConfig.sequential.controls.current,
sequenceOverride || defaultOverride,
)
return { entry, color: hexToRgbaColor(entry.colorHex) }
}
if (scene.completedControlSequences.includes(sequence)) {
const entry = scene.courseStyleConfig.sequential.controls.completed
const entry = mergeControlStyleEntries(
scene.courseStyleConfig.sequential.controls.completed,
sequenceOverride || defaultOverride,
)
return { entry, color: hexToRgbaColor(entry.colorHex) }
}
if (scene.skippedControlSequences.includes(sequence)) {
const entry = scene.courseStyleConfig.sequential.controls.skipped
const entry = mergeControlStyleEntries(
scene.courseStyleConfig.sequential.controls.skipped,
sequenceOverride || defaultOverride,
)
return { entry, color: hexToRgbaColor(entry.colorHex) }
}
const entry = scene.courseStyleConfig.sequential.controls.default
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.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
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 entry = completed ? scene.courseStyleConfig.sequential.legs.completed : scene.courseStyleConfig.sequential.legs.default
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) }
}

View File

@@ -42,9 +42,11 @@ export interface MapScene {
gameMode: 'classic-sequential' | 'score-o'
courseStyleConfig: CourseStyleConfig
controlScoresBySequence: Record<number, number>
defaultControlStyleOverride: ControlPointStyleEntry | null
controlStyleOverridesBySequence: Record<number, ControlPointStyleEntry>
startStyleOverrides: ControlPointStyleEntry[]
finishStyleOverrides: ControlPointStyleEntry[]
defaultLegStyleOverride: CourseLegStyleEntry | null
legStyleOverridesByIndex: Record<number, CourseLegStyleEntry>
controlVisualMode: 'single-target' | 'multi-target'
showCourseLegs: boolean