feedbackConfig.ts 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. export type FeedbackCueKey =
  2. | 'session_started'
  3. | 'session_finished'
  4. | 'control_completed:start'
  5. | 'control_completed:control'
  6. | 'control_completed:finish'
  7. | 'punch_feedback:warning'
  8. | 'guidance:searching'
  9. | 'guidance:approaching'
  10. | 'guidance:ready'
  11. export type HapticPattern = 'short' | 'long'
  12. export type UiPunchFeedbackMotion = 'none' | 'pop' | 'success' | 'warning'
  13. export type UiContentCardMotion = 'none' | 'pop' | 'finish'
  14. export type UiPunchButtonMotion = 'none' | 'ready' | 'warning'
  15. export type UiMapPulseMotion = 'none' | 'ready' | 'control' | 'finish'
  16. export type UiStageMotion = 'none' | 'finish'
  17. export interface HapticCueConfig {
  18. enabled: boolean
  19. pattern: HapticPattern
  20. }
  21. export interface UiCueConfig {
  22. enabled: boolean
  23. punchFeedbackMotion: UiPunchFeedbackMotion
  24. contentCardMotion: UiContentCardMotion
  25. punchButtonMotion: UiPunchButtonMotion
  26. mapPulseMotion: UiMapPulseMotion
  27. stageMotion: UiStageMotion
  28. durationMs: number
  29. }
  30. export interface GameHapticsConfig {
  31. enabled: boolean
  32. cues: Record<FeedbackCueKey, HapticCueConfig>
  33. }
  34. export interface GameUiEffectsConfig {
  35. enabled: boolean
  36. cues: Record<FeedbackCueKey, UiCueConfig>
  37. }
  38. export interface PartialHapticCueConfig {
  39. enabled?: boolean
  40. pattern?: HapticPattern
  41. }
  42. export interface PartialUiCueConfig {
  43. enabled?: boolean
  44. punchFeedbackMotion?: UiPunchFeedbackMotion
  45. contentCardMotion?: UiContentCardMotion
  46. punchButtonMotion?: UiPunchButtonMotion
  47. mapPulseMotion?: UiMapPulseMotion
  48. stageMotion?: UiStageMotion
  49. durationMs?: number
  50. }
  51. export interface GameHapticsConfigOverrides {
  52. enabled?: boolean
  53. cues?: Partial<Record<FeedbackCueKey, PartialHapticCueConfig>>
  54. }
  55. export interface GameUiEffectsConfigOverrides {
  56. enabled?: boolean
  57. cues?: Partial<Record<FeedbackCueKey, PartialUiCueConfig>>
  58. }
  59. export const DEFAULT_GAME_HAPTICS_CONFIG: GameHapticsConfig = {
  60. enabled: true,
  61. cues: {
  62. session_started: { enabled: false, pattern: 'short' },
  63. session_finished: { enabled: true, pattern: 'long' },
  64. 'control_completed:start': { enabled: true, pattern: 'short' },
  65. 'control_completed:control': { enabled: true, pattern: 'short' },
  66. 'control_completed:finish': { enabled: true, pattern: 'long' },
  67. 'punch_feedback:warning': { enabled: true, pattern: 'short' },
  68. 'guidance:searching': { enabled: false, pattern: 'short' },
  69. 'guidance:approaching': { enabled: false, pattern: 'short' },
  70. 'guidance:ready': { enabled: true, pattern: 'short' },
  71. },
  72. }
  73. export const DEFAULT_GAME_UI_EFFECTS_CONFIG: GameUiEffectsConfig = {
  74. enabled: true,
  75. cues: {
  76. session_started: { enabled: false, punchFeedbackMotion: 'none', contentCardMotion: 'none', punchButtonMotion: 'none', mapPulseMotion: 'none', stageMotion: 'none', durationMs: 0 },
  77. session_finished: { enabled: false, punchFeedbackMotion: 'none', contentCardMotion: 'none', punchButtonMotion: 'none', mapPulseMotion: 'none', stageMotion: 'none', durationMs: 0 },
  78. 'control_completed:start': { enabled: true, punchFeedbackMotion: 'success', contentCardMotion: 'pop', punchButtonMotion: 'none', mapPulseMotion: 'control', stageMotion: 'none', durationMs: 0 },
  79. 'control_completed:control': { enabled: true, punchFeedbackMotion: 'success', contentCardMotion: 'pop', punchButtonMotion: 'none', mapPulseMotion: 'control', stageMotion: 'none', durationMs: 0 },
  80. 'control_completed:finish': { enabled: true, punchFeedbackMotion: 'success', contentCardMotion: 'finish', punchButtonMotion: 'none', mapPulseMotion: 'finish', stageMotion: 'finish', durationMs: 0 },
  81. 'punch_feedback:warning': { enabled: true, punchFeedbackMotion: 'warning', contentCardMotion: 'none', punchButtonMotion: 'warning', mapPulseMotion: 'none', stageMotion: 'none', durationMs: 560 },
  82. 'guidance:searching': { enabled: false, punchFeedbackMotion: 'none', contentCardMotion: 'none', punchButtonMotion: 'none', mapPulseMotion: 'none', stageMotion: 'none', durationMs: 0 },
  83. 'guidance:approaching': { enabled: false, punchFeedbackMotion: 'none', contentCardMotion: 'none', punchButtonMotion: 'none', mapPulseMotion: 'none', stageMotion: 'none', durationMs: 0 },
  84. 'guidance:ready': { enabled: true, punchFeedbackMotion: 'none', contentCardMotion: 'none', punchButtonMotion: 'ready', mapPulseMotion: 'ready', stageMotion: 'none', durationMs: 900 },
  85. },
  86. }
  87. function clampDuration(value: number, fallback: number): number {
  88. return Number.isFinite(value) && value >= 0 ? value : fallback
  89. }
  90. function mergeHapticCue(baseCue: HapticCueConfig, override?: PartialHapticCueConfig): HapticCueConfig {
  91. return {
  92. enabled: override && override.enabled !== undefined ? !!override.enabled : baseCue.enabled,
  93. pattern: override && override.pattern ? override.pattern : baseCue.pattern,
  94. }
  95. }
  96. function mergeUiCue(baseCue: UiCueConfig, override?: PartialUiCueConfig): UiCueConfig {
  97. return {
  98. enabled: override && override.enabled !== undefined ? !!override.enabled : baseCue.enabled,
  99. punchFeedbackMotion: override && override.punchFeedbackMotion ? override.punchFeedbackMotion : baseCue.punchFeedbackMotion,
  100. contentCardMotion: override && override.contentCardMotion ? override.contentCardMotion : baseCue.contentCardMotion,
  101. punchButtonMotion: override && override.punchButtonMotion ? override.punchButtonMotion : baseCue.punchButtonMotion,
  102. mapPulseMotion: override && override.mapPulseMotion ? override.mapPulseMotion : baseCue.mapPulseMotion,
  103. stageMotion: override && override.stageMotion ? override.stageMotion : baseCue.stageMotion,
  104. durationMs: clampDuration(Number(override && override.durationMs), baseCue.durationMs),
  105. }
  106. }
  107. export function mergeGameHapticsConfig(overrides?: GameHapticsConfigOverrides | null): GameHapticsConfig {
  108. const cues: GameHapticsConfig['cues'] = {
  109. session_started: mergeHapticCue(DEFAULT_GAME_HAPTICS_CONFIG.cues.session_started, overrides && overrides.cues ? overrides.cues.session_started : undefined),
  110. session_finished: mergeHapticCue(DEFAULT_GAME_HAPTICS_CONFIG.cues.session_finished, overrides && overrides.cues ? overrides.cues.session_finished : undefined),
  111. 'control_completed:start': mergeHapticCue(DEFAULT_GAME_HAPTICS_CONFIG.cues['control_completed:start'], overrides && overrides.cues ? overrides.cues['control_completed:start'] : undefined),
  112. 'control_completed:control': mergeHapticCue(DEFAULT_GAME_HAPTICS_CONFIG.cues['control_completed:control'], overrides && overrides.cues ? overrides.cues['control_completed:control'] : undefined),
  113. 'control_completed:finish': mergeHapticCue(DEFAULT_GAME_HAPTICS_CONFIG.cues['control_completed:finish'], overrides && overrides.cues ? overrides.cues['control_completed:finish'] : undefined),
  114. 'punch_feedback:warning': mergeHapticCue(DEFAULT_GAME_HAPTICS_CONFIG.cues['punch_feedback:warning'], overrides && overrides.cues ? overrides.cues['punch_feedback:warning'] : undefined),
  115. 'guidance:searching': mergeHapticCue(DEFAULT_GAME_HAPTICS_CONFIG.cues['guidance:searching'], overrides && overrides.cues ? overrides.cues['guidance:searching'] : undefined),
  116. 'guidance:approaching': mergeHapticCue(DEFAULT_GAME_HAPTICS_CONFIG.cues['guidance:approaching'], overrides && overrides.cues ? overrides.cues['guidance:approaching'] : undefined),
  117. 'guidance:ready': mergeHapticCue(DEFAULT_GAME_HAPTICS_CONFIG.cues['guidance:ready'], overrides && overrides.cues ? overrides.cues['guidance:ready'] : undefined),
  118. }
  119. return {
  120. enabled: overrides && overrides.enabled !== undefined ? !!overrides.enabled : DEFAULT_GAME_HAPTICS_CONFIG.enabled,
  121. cues,
  122. }
  123. }
  124. export function mergeGameUiEffectsConfig(overrides?: GameUiEffectsConfigOverrides | null): GameUiEffectsConfig {
  125. const cues: GameUiEffectsConfig['cues'] = {
  126. session_started: mergeUiCue(DEFAULT_GAME_UI_EFFECTS_CONFIG.cues.session_started, overrides && overrides.cues ? overrides.cues.session_started : undefined),
  127. session_finished: mergeUiCue(DEFAULT_GAME_UI_EFFECTS_CONFIG.cues.session_finished, overrides && overrides.cues ? overrides.cues.session_finished : undefined),
  128. 'control_completed:start': mergeUiCue(DEFAULT_GAME_UI_EFFECTS_CONFIG.cues['control_completed:start'], overrides && overrides.cues ? overrides.cues['control_completed:start'] : undefined),
  129. 'control_completed:control': mergeUiCue(DEFAULT_GAME_UI_EFFECTS_CONFIG.cues['control_completed:control'], overrides && overrides.cues ? overrides.cues['control_completed:control'] : undefined),
  130. 'control_completed:finish': mergeUiCue(DEFAULT_GAME_UI_EFFECTS_CONFIG.cues['control_completed:finish'], overrides && overrides.cues ? overrides.cues['control_completed:finish'] : undefined),
  131. 'punch_feedback:warning': mergeUiCue(DEFAULT_GAME_UI_EFFECTS_CONFIG.cues['punch_feedback:warning'], overrides && overrides.cues ? overrides.cues['punch_feedback:warning'] : undefined),
  132. 'guidance:searching': mergeUiCue(DEFAULT_GAME_UI_EFFECTS_CONFIG.cues['guidance:searching'], overrides && overrides.cues ? overrides.cues['guidance:searching'] : undefined),
  133. 'guidance:approaching': mergeUiCue(DEFAULT_GAME_UI_EFFECTS_CONFIG.cues['guidance:approaching'], overrides && overrides.cues ? overrides.cues['guidance:approaching'] : undefined),
  134. 'guidance:ready': mergeUiCue(DEFAULT_GAME_UI_EFFECTS_CONFIG.cues['guidance:ready'], overrides && overrides.cues ? overrides.cues['guidance:ready'] : undefined),
  135. }
  136. return {
  137. enabled: overrides && overrides.enabled !== undefined ? !!overrides.enabled : DEFAULT_GAME_UI_EFFECTS_CONFIG.enabled,
  138. cues,
  139. }
  140. }