courseStyleResolver.ts 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. import { type MapScene } from './mapRenderer'
  2. import { type ControlPointStyleEntry, type CourseLegStyleEntry, type ScoreBandStyleEntry } from '../../game/presentation/courseStyleConfig'
  3. export type RgbaColor = [number, number, number, number]
  4. export interface ResolvedControlStyle {
  5. entry: ControlPointStyleEntry
  6. color: RgbaColor
  7. }
  8. export interface ResolvedLegStyle {
  9. entry: CourseLegStyleEntry
  10. color: RgbaColor
  11. }
  12. export function hexToRgbaColor(hex: string, alphaOverride?: number): RgbaColor {
  13. const fallback: RgbaColor = [1, 1, 1, alphaOverride !== undefined ? alphaOverride : 1]
  14. if (typeof hex !== 'string' || !hex || hex.charAt(0) !== '#') {
  15. return fallback
  16. }
  17. const normalized = hex.slice(1)
  18. if (normalized.length !== 6 && normalized.length !== 8) {
  19. return fallback
  20. }
  21. const red = parseInt(normalized.slice(0, 2), 16)
  22. const green = parseInt(normalized.slice(2, 4), 16)
  23. const blue = parseInt(normalized.slice(4, 6), 16)
  24. const alpha = normalized.length === 8 ? parseInt(normalized.slice(6, 8), 16) : 255
  25. if (!Number.isFinite(red) || !Number.isFinite(green) || !Number.isFinite(blue) || !Number.isFinite(alpha)) {
  26. return fallback
  27. }
  28. return [
  29. red / 255,
  30. green / 255,
  31. blue / 255,
  32. alphaOverride !== undefined ? alphaOverride : alpha / 255,
  33. ]
  34. }
  35. function resolveScoreBandStyle(scene: MapScene, sequence: number): ScoreBandStyleEntry | null {
  36. const score = scene.controlScoresBySequence[sequence]
  37. if (score === undefined) {
  38. return null
  39. }
  40. const bands = scene.courseStyleConfig.scoreO.controls.scoreBands
  41. for (let index = 0; index < bands.length; index += 1) {
  42. const band = bands[index]
  43. if (score >= band.min && score <= band.max) {
  44. return band
  45. }
  46. }
  47. return null
  48. }
  49. export function resolveControlStyle(scene: MapScene, kind: 'start' | 'control' | 'finish', sequence: number | null, index?: number): ResolvedControlStyle {
  50. if (kind === 'start') {
  51. if (index !== undefined && scene.startStyleOverrides[index]) {
  52. const entry = scene.startStyleOverrides[index]
  53. return { entry, color: hexToRgbaColor(entry.colorHex) }
  54. }
  55. const entry = scene.gameMode === 'score-o'
  56. ? scene.courseStyleConfig.scoreO.controls.start
  57. : scene.courseStyleConfig.sequential.controls.start
  58. return { entry, color: hexToRgbaColor(entry.colorHex) }
  59. }
  60. if (kind === 'finish') {
  61. if (index !== undefined && scene.finishStyleOverrides[index]) {
  62. const entry = scene.finishStyleOverrides[index]
  63. return { entry, color: hexToRgbaColor(entry.colorHex) }
  64. }
  65. const entry = scene.gameMode === 'score-o'
  66. ? scene.courseStyleConfig.scoreO.controls.finish
  67. : scene.courseStyleConfig.sequential.controls.finish
  68. return { entry, color: hexToRgbaColor(entry.colorHex) }
  69. }
  70. if (sequence === null) {
  71. const entry = scene.courseStyleConfig.sequential.controls.default
  72. return { entry, color: hexToRgbaColor(entry.colorHex) }
  73. }
  74. if (scene.controlStyleOverridesBySequence[sequence]) {
  75. const entry = scene.controlStyleOverridesBySequence[sequence]
  76. return { entry, color: hexToRgbaColor(entry.colorHex) }
  77. }
  78. if (scene.gameMode === 'score-o') {
  79. if (scene.completedControlSequences.includes(sequence)) {
  80. const entry = scene.courseStyleConfig.scoreO.controls.collected
  81. return { entry, color: hexToRgbaColor(entry.colorHex) }
  82. }
  83. if (scene.focusedControlSequences.includes(sequence)) {
  84. const entry = scene.courseStyleConfig.scoreO.controls.focused
  85. return { entry, color: hexToRgbaColor(entry.colorHex) }
  86. }
  87. const bandEntry = resolveScoreBandStyle(scene, sequence)
  88. const entry = bandEntry || scene.courseStyleConfig.scoreO.controls.default
  89. return { entry, color: hexToRgbaColor(entry.colorHex) }
  90. }
  91. if (scene.readyControlSequences.includes(sequence) || scene.activeControlSequences.includes(sequence)) {
  92. const entry = scene.courseStyleConfig.sequential.controls.current
  93. return { entry, color: hexToRgbaColor(entry.colorHex) }
  94. }
  95. if (scene.completedControlSequences.includes(sequence)) {
  96. const entry = scene.courseStyleConfig.sequential.controls.completed
  97. return { entry, color: hexToRgbaColor(entry.colorHex) }
  98. }
  99. if (scene.skippedControlSequences.includes(sequence)) {
  100. const entry = scene.courseStyleConfig.sequential.controls.skipped
  101. return { entry, color: hexToRgbaColor(entry.colorHex) }
  102. }
  103. const entry = scene.courseStyleConfig.sequential.controls.default
  104. return { entry, color: hexToRgbaColor(entry.colorHex) }
  105. }
  106. export function resolveLegStyle(scene: MapScene, index: number): ResolvedLegStyle {
  107. if (scene.legStyleOverridesByIndex[index]) {
  108. const entry = scene.legStyleOverridesByIndex[index]
  109. return { entry, color: hexToRgbaColor(entry.colorHex) }
  110. }
  111. if (scene.gameMode === 'score-o') {
  112. const entry = scene.courseStyleConfig.sequential.legs.default
  113. return { entry, color: hexToRgbaColor(entry.colorHex) }
  114. }
  115. const completed = scene.completedLegIndices.includes(index)
  116. const entry = completed ? scene.courseStyleConfig.sequential.legs.completed : scene.courseStyleConfig.sequential.legs.default
  117. return { entry, color: hexToRgbaColor(entry.colorHex) }
  118. }