feat: 收敛玩法运行时配置并加入故障恢复
This commit is contained in:
@@ -45,6 +45,16 @@ import {
|
||||
type GpsMarkerStyleConfig,
|
||||
type GpsMarkerStyleId,
|
||||
} from '../game/presentation/gpsMarkerStyleConfig'
|
||||
import {
|
||||
getDefaultSkipRadiusMeters,
|
||||
getGameModeDefaults,
|
||||
resolveDefaultControlScore,
|
||||
} from '../game/core/gameModeDefaults'
|
||||
import {
|
||||
type SystemSettingsConfig,
|
||||
type SettingLockKey,
|
||||
type StoredUserSettings,
|
||||
} from '../game/core/systemSettingsState'
|
||||
|
||||
export interface TileZoomBounds {
|
||||
minX: number
|
||||
@@ -79,6 +89,9 @@ export interface RemoteMapConfig {
|
||||
courseStatusText: string
|
||||
cpRadiusMeters: number
|
||||
gameMode: 'classic-sequential' | 'score-o'
|
||||
sessionCloseAfterMs: number
|
||||
sessionCloseWarningMs: number
|
||||
minCompletedControlsBeforeFinish: number
|
||||
punchPolicy: 'enter' | 'enter-confirm'
|
||||
punchRadiusMeters: number
|
||||
requiresFocusSelection: boolean
|
||||
@@ -88,7 +101,10 @@ export interface RemoteMapConfig {
|
||||
autoFinishOnLastControl: boolean
|
||||
controlScoreOverrides: Record<string, number>
|
||||
controlContentOverrides: Record<string, GameControlDisplayContentOverride>
|
||||
defaultControlContentOverride: GameControlDisplayContentOverride | null
|
||||
defaultControlPointStyleOverride: ControlPointStyleEntry | null
|
||||
controlPointStyleOverrides: Record<string, ControlPointStyleEntry>
|
||||
defaultLegStyleOverride: CourseLegStyleEntry | null
|
||||
legStyleOverrides: Record<number, CourseLegStyleEntry>
|
||||
defaultControlScore: number | null
|
||||
courseStyleConfig: CourseStyleConfig
|
||||
@@ -98,6 +114,7 @@ export interface RemoteMapConfig {
|
||||
audioConfig: GameAudioConfig
|
||||
hapticsConfig: GameHapticsConfig
|
||||
uiEffectsConfig: GameUiEffectsConfig
|
||||
systemSettingsConfig: SystemSettingsConfig
|
||||
}
|
||||
|
||||
interface ParsedGameConfig {
|
||||
@@ -111,6 +128,9 @@ interface ParsedGameConfig {
|
||||
cpRadiusMeters: number
|
||||
defaultZoom: number | null
|
||||
gameMode: 'classic-sequential' | 'score-o'
|
||||
sessionCloseAfterMs: number
|
||||
sessionCloseWarningMs: number
|
||||
minCompletedControlsBeforeFinish: number
|
||||
punchPolicy: 'enter' | 'enter-confirm'
|
||||
punchRadiusMeters: number
|
||||
requiresFocusSelection: boolean
|
||||
@@ -120,7 +140,10 @@ interface ParsedGameConfig {
|
||||
autoFinishOnLastControl: boolean
|
||||
controlScoreOverrides: Record<string, number>
|
||||
controlContentOverrides: Record<string, GameControlDisplayContentOverride>
|
||||
defaultControlContentOverride: GameControlDisplayContentOverride | null
|
||||
defaultControlPointStyleOverride: ControlPointStyleEntry | null
|
||||
controlPointStyleOverrides: Record<string, ControlPointStyleEntry>
|
||||
defaultLegStyleOverride: CourseLegStyleEntry | null
|
||||
legStyleOverrides: Record<number, CourseLegStyleEntry>
|
||||
defaultControlScore: number | null
|
||||
courseStyleConfig: CourseStyleConfig
|
||||
@@ -130,6 +153,7 @@ interface ParsedGameConfig {
|
||||
audioConfig: GameAudioConfig
|
||||
hapticsConfig: GameHapticsConfig
|
||||
uiEffectsConfig: GameUiEffectsConfig
|
||||
systemSettingsConfig: SystemSettingsConfig
|
||||
declinationDeg: number
|
||||
}
|
||||
|
||||
@@ -279,6 +303,150 @@ function parsePunchPolicy(rawValue: unknown): 'enter' | 'enter-confirm' {
|
||||
return rawValue === 'enter' ? 'enter' : 'enter-confirm'
|
||||
}
|
||||
|
||||
function parseSettingLockKey(rawValue: string): SettingLockKey | null {
|
||||
const normalized = rawValue.trim().toLowerCase()
|
||||
const table: Record<string, SettingLockKey> = {
|
||||
animationlevel: 'lockAnimationLevel',
|
||||
trackdisplaymode: 'lockTrackMode',
|
||||
trackmode: 'lockTrackMode',
|
||||
tracktaillength: 'lockTrackTailLength',
|
||||
trackcolorpreset: 'lockTrackColor',
|
||||
trackcolor: 'lockTrackColor',
|
||||
trackstyleprofile: 'lockTrackStyle',
|
||||
trackstyle: 'lockTrackStyle',
|
||||
gpsmarkervisible: 'lockGpsMarkerVisible',
|
||||
gpsmarkerstyle: 'lockGpsMarkerStyle',
|
||||
gpsmarkersize: 'lockGpsMarkerSize',
|
||||
gpsmarkercolorpreset: 'lockGpsMarkerColor',
|
||||
gpsmarkercolor: 'lockGpsMarkerColor',
|
||||
sidebuttonplacement: 'lockSideButtonPlacement',
|
||||
autorotateenabled: 'lockAutoRotate',
|
||||
autorotate: 'lockAutoRotate',
|
||||
compasstuningprofile: 'lockCompassTuning',
|
||||
compasstuning: 'lockCompassTuning',
|
||||
showcenterscaleruler: 'lockScaleRulerVisible',
|
||||
centerscaleruleranchormode: 'lockScaleRulerAnchor',
|
||||
centerruleranchor: 'lockScaleRulerAnchor',
|
||||
northreferencemode: 'lockNorthReference',
|
||||
northreference: 'lockNorthReference',
|
||||
heartratedevice: 'lockHeartRateDevice',
|
||||
}
|
||||
return table[normalized] || null
|
||||
}
|
||||
|
||||
function assignParsedSettingValue(
|
||||
target: Partial<StoredUserSettings>,
|
||||
key: string,
|
||||
rawValue: unknown,
|
||||
): void {
|
||||
const normalized = key.trim().toLowerCase()
|
||||
if (normalized === 'animationlevel') {
|
||||
if (rawValue === 'standard' || rawValue === 'lite') {
|
||||
target.animationLevel = rawValue
|
||||
}
|
||||
return
|
||||
}
|
||||
if (normalized === 'trackdisplaymode' || normalized === 'trackmode') {
|
||||
const parsed = parseTrackDisplayMode(rawValue, DEFAULT_TRACK_VISUALIZATION_CONFIG.mode)
|
||||
target.trackDisplayMode = parsed
|
||||
return
|
||||
}
|
||||
if (normalized === 'tracktaillength') {
|
||||
if (rawValue === 'short' || rawValue === 'medium' || rawValue === 'long') {
|
||||
target.trackTailLength = rawValue
|
||||
}
|
||||
return
|
||||
}
|
||||
if (normalized === 'trackcolorpreset' || normalized === 'trackcolor') {
|
||||
const parsed = parseTrackColorPreset(rawValue, DEFAULT_TRACK_VISUALIZATION_CONFIG.colorPreset)
|
||||
target.trackColorPreset = parsed
|
||||
return
|
||||
}
|
||||
if (normalized === 'trackstyleprofile' || normalized === 'trackstyle') {
|
||||
const parsed = parseTrackStyleProfile(rawValue, DEFAULT_TRACK_VISUALIZATION_CONFIG.style)
|
||||
target.trackStyleProfile = parsed
|
||||
return
|
||||
}
|
||||
if (normalized === 'gpsmarkervisible') {
|
||||
target.gpsMarkerVisible = parseBoolean(rawValue, DEFAULT_GPS_MARKER_STYLE_CONFIG.visible)
|
||||
return
|
||||
}
|
||||
if (normalized === 'gpsmarkerstyle') {
|
||||
target.gpsMarkerStyle = parseGpsMarkerStyleId(rawValue, DEFAULT_GPS_MARKER_STYLE_CONFIG.style)
|
||||
return
|
||||
}
|
||||
if (normalized === 'gpsmarkersize') {
|
||||
target.gpsMarkerSize = parseGpsMarkerSizePreset(rawValue, DEFAULT_GPS_MARKER_STYLE_CONFIG.size)
|
||||
return
|
||||
}
|
||||
if (normalized === 'gpsmarkercolorpreset' || normalized === 'gpsmarkercolor') {
|
||||
target.gpsMarkerColorPreset = parseGpsMarkerColorPreset(rawValue, DEFAULT_GPS_MARKER_STYLE_CONFIG.colorPreset)
|
||||
return
|
||||
}
|
||||
if (normalized === 'sidebuttonplacement') {
|
||||
if (rawValue === 'left' || rawValue === 'right') {
|
||||
target.sideButtonPlacement = rawValue
|
||||
}
|
||||
return
|
||||
}
|
||||
if (normalized === 'autorotateenabled' || normalized === 'autorotate') {
|
||||
target.autoRotateEnabled = parseBoolean(rawValue, true)
|
||||
return
|
||||
}
|
||||
if (normalized === 'compasstuningprofile' || normalized === 'compasstuning') {
|
||||
if (rawValue === 'smooth' || rawValue === 'balanced' || rawValue === 'responsive') {
|
||||
target.compassTuningProfile = rawValue
|
||||
}
|
||||
return
|
||||
}
|
||||
if (normalized === 'northreferencemode' || normalized === 'northreference') {
|
||||
if (rawValue === 'magnetic' || rawValue === 'true') {
|
||||
target.northReferenceMode = rawValue
|
||||
}
|
||||
return
|
||||
}
|
||||
if (normalized === 'showcenterscaleruler') {
|
||||
target.showCenterScaleRuler = parseBoolean(rawValue, false)
|
||||
return
|
||||
}
|
||||
if (normalized === 'centerscaleruleranchormode' || normalized === 'centerruleranchor') {
|
||||
if (rawValue === 'screen-center' || rawValue === 'compass-center') {
|
||||
target.centerScaleRulerAnchorMode = rawValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parseSystemSettingsConfig(rawValue: unknown): SystemSettingsConfig {
|
||||
const normalized = normalizeObjectRecord(rawValue)
|
||||
if (!Object.keys(normalized).length) {
|
||||
return { values: {}, locks: {} }
|
||||
}
|
||||
|
||||
const values: Partial<StoredUserSettings> = {}
|
||||
const locks: Partial<Record<SettingLockKey, boolean>> = {}
|
||||
for (const [key, entry] of Object.entries(normalized)) {
|
||||
const normalizedEntry = normalizeObjectRecord(entry)
|
||||
if (Object.keys(normalizedEntry).length) {
|
||||
const hasValue = Object.prototype.hasOwnProperty.call(normalizedEntry, 'value')
|
||||
const hasLocked = Object.prototype.hasOwnProperty.call(normalizedEntry, 'islocked')
|
||||
if (hasValue) {
|
||||
assignParsedSettingValue(values, key, normalizedEntry.value)
|
||||
}
|
||||
if (hasLocked) {
|
||||
const lockKey = parseSettingLockKey(key)
|
||||
if (lockKey) {
|
||||
locks[lockKey] = parseBoolean(normalizedEntry.islocked, false)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
assignParsedSettingValue(values, key, entry)
|
||||
}
|
||||
|
||||
return { values, locks }
|
||||
}
|
||||
|
||||
function parseContentExperienceOverride(
|
||||
rawValue: unknown,
|
||||
baseUrl: string,
|
||||
@@ -325,6 +493,152 @@ function parseContentExperienceOverride(
|
||||
}
|
||||
}
|
||||
|
||||
function parseControlDisplayContentOverride(
|
||||
rawValue: unknown,
|
||||
baseUrl: string,
|
||||
): GameControlDisplayContentOverride | null {
|
||||
const item = normalizeObjectRecord(rawValue)
|
||||
if (!Object.keys(item).length) {
|
||||
return null
|
||||
}
|
||||
|
||||
const titleValue = typeof item.title === 'string' ? item.title.trim() : ''
|
||||
const templateRaw = typeof item.template === 'string' ? item.template.trim().toLowerCase() : ''
|
||||
const templateValue = templateRaw === 'minimal' || templateRaw === 'story' || templateRaw === 'focus'
|
||||
? templateRaw
|
||||
: ''
|
||||
const bodyValue = typeof item.body === 'string' ? item.body.trim() : ''
|
||||
const clickTitleValue = typeof item.clickTitle === 'string' ? item.clickTitle.trim() : ''
|
||||
const clickBodyValue = typeof item.clickBody === 'string' ? item.clickBody.trim() : ''
|
||||
const autoPopupValue = item.autoPopup
|
||||
const onceValue = item.once
|
||||
const priorityNumeric = Number(item.priority)
|
||||
const ctasValue = parseContentCardCtas(item.ctas)
|
||||
const contentExperienceValue = parseContentExperienceOverride(item.contentExperience, baseUrl)
|
||||
const clickExperienceValue = parseContentExperienceOverride(item.clickExperience, baseUrl)
|
||||
const hasAutoPopup = typeof autoPopupValue === 'boolean'
|
||||
const hasOnce = typeof onceValue === 'boolean'
|
||||
const hasPriority = Number.isFinite(priorityNumeric)
|
||||
|
||||
if (
|
||||
!templateValue
|
||||
&& !titleValue
|
||||
&& !bodyValue
|
||||
&& !clickTitleValue
|
||||
&& !clickBodyValue
|
||||
&& !hasAutoPopup
|
||||
&& !hasOnce
|
||||
&& !hasPriority
|
||||
&& !ctasValue
|
||||
&& !contentExperienceValue
|
||||
&& !clickExperienceValue
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
const parsed: GameControlDisplayContentOverride = {}
|
||||
if (templateValue) {
|
||||
parsed.template = templateValue
|
||||
}
|
||||
if (titleValue) {
|
||||
parsed.title = titleValue
|
||||
}
|
||||
if (bodyValue) {
|
||||
parsed.body = bodyValue
|
||||
}
|
||||
if (clickTitleValue) {
|
||||
parsed.clickTitle = clickTitleValue
|
||||
}
|
||||
if (clickBodyValue) {
|
||||
parsed.clickBody = clickBodyValue
|
||||
}
|
||||
if (hasAutoPopup) {
|
||||
parsed.autoPopup = !!autoPopupValue
|
||||
}
|
||||
if (hasOnce) {
|
||||
parsed.once = !!onceValue
|
||||
}
|
||||
if (hasPriority) {
|
||||
parsed.priority = Math.max(0, Math.round(priorityNumeric))
|
||||
}
|
||||
if (ctasValue) {
|
||||
parsed.ctas = ctasValue
|
||||
}
|
||||
if (contentExperienceValue) {
|
||||
parsed.contentExperience = contentExperienceValue
|
||||
}
|
||||
if (clickExperienceValue) {
|
||||
parsed.clickExperience = clickExperienceValue
|
||||
}
|
||||
|
||||
return parsed
|
||||
}
|
||||
|
||||
function parseControlPointStyleOverride(
|
||||
rawValue: unknown,
|
||||
fallbackPointStyle: ControlPointStyleEntry,
|
||||
): ControlPointStyleEntry | null {
|
||||
const item = normalizeObjectRecord(rawValue)
|
||||
if (!Object.keys(item).length) {
|
||||
return null
|
||||
}
|
||||
|
||||
const rawPointStyle = getFirstDefined(item, ['pointstyle', 'style'])
|
||||
const rawPointColor = getFirstDefined(item, ['pointcolorhex', 'pointcolor', 'color', 'colorhex'])
|
||||
const rawPointSizeScale = getFirstDefined(item, ['pointsizescale', 'sizescale'])
|
||||
const rawPointAccentRingScale = getFirstDefined(item, ['pointaccentringscale', 'accentringscale'])
|
||||
const rawPointGlowStrength = getFirstDefined(item, ['pointglowstrength', 'glowstrength'])
|
||||
const rawPointLabelScale = getFirstDefined(item, ['pointlabelscale', 'labelscale'])
|
||||
const rawPointLabelColor = getFirstDefined(item, ['pointlabelcolorhex', 'pointlabelcolor', 'labelcolor', 'labelcolorhex'])
|
||||
if (
|
||||
rawPointStyle === undefined
|
||||
&& rawPointColor === undefined
|
||||
&& rawPointSizeScale === undefined
|
||||
&& rawPointAccentRingScale === undefined
|
||||
&& rawPointGlowStrength === undefined
|
||||
&& rawPointLabelScale === undefined
|
||||
&& rawPointLabelColor === undefined
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
style: parseControlPointStyleId(rawPointStyle, fallbackPointStyle.style),
|
||||
colorHex: normalizeHexColor(rawPointColor, fallbackPointStyle.colorHex),
|
||||
sizeScale: parsePositiveNumber(rawPointSizeScale, fallbackPointStyle.sizeScale || 1),
|
||||
accentRingScale: parsePositiveNumber(rawPointAccentRingScale, fallbackPointStyle.accentRingScale || 0),
|
||||
glowStrength: clamp(parseNumber(rawPointGlowStrength, fallbackPointStyle.glowStrength || 0), 0, 1.2),
|
||||
labelScale: parsePositiveNumber(rawPointLabelScale, fallbackPointStyle.labelScale || 1),
|
||||
labelColorHex: normalizeHexColor(rawPointLabelColor, fallbackPointStyle.labelColorHex || ''),
|
||||
}
|
||||
}
|
||||
|
||||
function parseLegStyleOverride(
|
||||
rawValue: unknown,
|
||||
fallbackLegStyle: CourseLegStyleEntry,
|
||||
): CourseLegStyleEntry | null {
|
||||
const normalized = normalizeObjectRecord(rawValue)
|
||||
const rawStyle = getFirstDefined(normalized, ['style'])
|
||||
const rawColor = getFirstDefined(normalized, ['color', 'colorhex'])
|
||||
const rawWidthScale = getFirstDefined(normalized, ['widthscale'])
|
||||
const rawGlowStrength = getFirstDefined(normalized, ['glowstrength'])
|
||||
if (
|
||||
rawStyle === undefined
|
||||
&& rawColor === undefined
|
||||
&& rawWidthScale === undefined
|
||||
&& rawGlowStrength === undefined
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
style: parseCourseLegStyleId(rawStyle, fallbackLegStyle.style),
|
||||
colorHex: normalizeHexColor(rawColor, fallbackLegStyle.colorHex),
|
||||
widthScale: parsePositiveNumber(rawWidthScale, fallbackLegStyle.widthScale || 1),
|
||||
glowStrength: clamp(parseNumber(rawGlowStrength, fallbackLegStyle.glowStrength || 0), 0, 1.2),
|
||||
}
|
||||
}
|
||||
|
||||
function parseGameMode(rawValue: unknown): 'classic-sequential' | 'score-o' {
|
||||
if (typeof rawValue !== 'string') {
|
||||
return 'classic-sequential'
|
||||
@@ -684,6 +998,7 @@ function parseAudioConfig(rawValue: unknown, baseUrl: string): GameAudioConfig {
|
||||
{ key: 'control_completed:finish', aliases: ['control_completed:finish', 'controlcompleted:finish', 'finish_completed', 'finishcomplete', 'finish-complete'] },
|
||||
{ key: 'punch_feedback:warning', aliases: ['punch_feedback:warning', 'punchfeedback:warning', 'warning', 'punch_warning', 'punchwarning'] },
|
||||
{ key: 'guidance:searching', aliases: ['guidance:searching', 'guidance_searching', 'searching', 'search', 'normal_search'] },
|
||||
{ key: 'guidance:distant', aliases: ['guidance:distant', 'guidance_distant', 'distant', 'far', 'far_distance'] },
|
||||
{ key: 'guidance:approaching', aliases: ['guidance:approaching', 'guidance_approaching', 'approaching', 'approach', 'near'] },
|
||||
{ key: 'guidance:ready', aliases: ['guidance:ready', 'guidance_ready', 'ready', 'punch_ready', 'can_punch'] },
|
||||
]
|
||||
@@ -705,11 +1020,29 @@ function parseAudioConfig(rawValue: unknown, baseUrl: string): GameAudioConfig {
|
||||
? parsePositiveNumber(normalized.volume, 1)
|
||||
: undefined,
|
||||
obeyMuteSwitch: normalized.obeymuteswitch !== undefined ? parseBoolean(normalized.obeymuteswitch, true) : undefined,
|
||||
distantDistanceMeters: normalized.distantdistancemeters !== undefined
|
||||
? parsePositiveNumber(normalized.distantdistancemeters, 80)
|
||||
: normalized.distantdistance !== undefined
|
||||
? parsePositiveNumber(normalized.distantdistance, 80)
|
||||
: normalized.fardistancemeters !== undefined
|
||||
? parsePositiveNumber(normalized.fardistancemeters, 80)
|
||||
: normalized.fardistance !== undefined
|
||||
? parsePositiveNumber(normalized.fardistance, 80)
|
||||
: undefined,
|
||||
approachDistanceMeters: normalized.approachdistancemeters !== undefined
|
||||
? parsePositiveNumber(normalized.approachdistancemeters, 20)
|
||||
: normalized.approachdistance !== undefined
|
||||
? parsePositiveNumber(normalized.approachdistance, 20)
|
||||
: undefined,
|
||||
readyDistanceMeters: normalized.readydistancemeters !== undefined
|
||||
? parsePositiveNumber(normalized.readydistancemeters, 5)
|
||||
: normalized.readydistance !== undefined
|
||||
? parsePositiveNumber(normalized.readydistance, 5)
|
||||
: normalized.punchreadydistancemeters !== undefined
|
||||
? parsePositiveNumber(normalized.punchreadydistancemeters, 5)
|
||||
: normalized.punchreadydistance !== undefined
|
||||
? parsePositiveNumber(normalized.punchreadydistance, 5)
|
||||
: undefined,
|
||||
cues,
|
||||
})
|
||||
}
|
||||
@@ -1120,6 +1453,7 @@ function parseHapticsConfig(rawValue: unknown): GameHapticsConfig {
|
||||
{ key: 'control_completed:finish', aliases: ['control_completed:finish', 'finish_completed', 'finishcomplete', 'finish-complete'] },
|
||||
{ key: 'punch_feedback:warning', aliases: ['punch_feedback:warning', 'warning', 'punch_warning', 'punchwarning'] },
|
||||
{ key: 'guidance:searching', aliases: ['guidance:searching', 'searching', 'search'] },
|
||||
{ key: 'guidance:distant', aliases: ['guidance:distant', 'distant', 'far', 'far_distance'] },
|
||||
{ key: 'guidance:approaching', aliases: ['guidance:approaching', 'approaching', 'approach', 'near'] },
|
||||
{ key: 'guidance:ready', aliases: ['guidance:ready', 'ready', 'punch_ready', 'can_punch'] },
|
||||
]
|
||||
@@ -1153,6 +1487,7 @@ function parseUiEffectsConfig(rawValue: unknown): GameUiEffectsConfig {
|
||||
{ key: 'control_completed:finish', aliases: ['control_completed:finish', 'finish_completed', 'finishcomplete', 'finish-complete'] },
|
||||
{ key: 'punch_feedback:warning', aliases: ['punch_feedback:warning', 'warning', 'punch_warning', 'punchwarning'] },
|
||||
{ key: 'guidance:searching', aliases: ['guidance:searching', 'searching', 'search'] },
|
||||
{ key: 'guidance:distant', aliases: ['guidance:distant', 'distant', 'far', 'far_distance'] },
|
||||
{ key: 'guidance:approaching', aliases: ['guidance:approaching', 'approaching', 'approach', 'near'] },
|
||||
{ key: 'guidance:ready', aliases: ['guidance:ready', 'ready', 'punch_ready', 'can_punch'] },
|
||||
]
|
||||
@@ -1194,6 +1529,9 @@ function parseGameConfigFromJson(text: string, gameConfigUrl: string): ParsedGam
|
||||
const rawMap = parsed.map && typeof parsed.map === 'object' && !Array.isArray(parsed.map)
|
||||
? parsed.map as Record<string, unknown>
|
||||
: null
|
||||
const rawSettings = parsed.settings && typeof parsed.settings === 'object' && !Array.isArray(parsed.settings)
|
||||
? parsed.settings as Record<string, unknown>
|
||||
: null
|
||||
const rawPlayfield = parsed.playfield && typeof parsed.playfield === 'object' && !Array.isArray(parsed.playfield)
|
||||
? parsed.playfield as Record<string, unknown>
|
||||
: null
|
||||
@@ -1241,6 +1579,9 @@ function parseGameConfigFromJson(text: string, gameConfigUrl: string): ParsedGam
|
||||
const rawScoring = rawGame && rawGame.scoring && typeof rawGame.scoring === 'object' && !Array.isArray(rawGame.scoring)
|
||||
? rawGame.scoring as Record<string, unknown>
|
||||
: null
|
||||
const rawGameSettings = rawGame && rawGame.settings && typeof rawGame.settings === 'object' && !Array.isArray(rawGame.settings)
|
||||
? rawGame.settings as Record<string, unknown>
|
||||
: null
|
||||
|
||||
const mapRoot = rawMap && typeof rawMap.tiles === 'string'
|
||||
? rawMap.tiles
|
||||
@@ -1258,9 +1599,22 @@ function parseGameConfigFromJson(text: string, gameConfigUrl: string): ParsedGam
|
||||
|
||||
const modeValue = typeof normalizedGame.mode === 'string' ? normalizedGame.mode : normalized.gamemode
|
||||
const gameMode = parseGameMode(modeValue)
|
||||
const modeDefaults = getGameModeDefaults(gameMode)
|
||||
const fallbackPointStyle = gameMode === 'score-o'
|
||||
? DEFAULT_COURSE_STYLE_CONFIG.scoreO.controls.default
|
||||
: DEFAULT_COURSE_STYLE_CONFIG.sequential.controls.default
|
||||
const fallbackLegStyle = DEFAULT_COURSE_STYLE_CONFIG.sequential.legs.default
|
||||
const rawControlDefaults = rawPlayfield && rawPlayfield.controlDefaults && typeof rawPlayfield.controlDefaults === 'object' && !Array.isArray(rawPlayfield.controlDefaults)
|
||||
? rawPlayfield.controlDefaults as Record<string, unknown>
|
||||
: null
|
||||
const rawControlOverrides = rawPlayfield && rawPlayfield.controlOverrides && typeof rawPlayfield.controlOverrides === 'object' && !Array.isArray(rawPlayfield.controlOverrides)
|
||||
? rawPlayfield.controlOverrides as Record<string, unknown>
|
||||
: null
|
||||
const defaultControlScoreFromPlayfield = rawControlDefaults
|
||||
? Number(getFirstDefined(normalizeObjectRecord(rawControlDefaults), ['score']))
|
||||
: Number.NaN
|
||||
const defaultControlContentOverride = parseControlDisplayContentOverride(rawControlDefaults, gameConfigUrl)
|
||||
const defaultControlPointStyleOverride = parseControlPointStyleOverride(rawControlDefaults, fallbackPointStyle)
|
||||
const controlScoreOverrides: Record<string, number> = {}
|
||||
const controlContentOverrides: Record<string, GameControlDisplayContentOverride> = {}
|
||||
const controlPointStyleOverrides: Record<string, ControlPointStyleEntry> = {}
|
||||
@@ -1275,92 +1629,21 @@ function parseGameConfigFromJson(text: string, gameConfigUrl: string): ParsedGam
|
||||
if (Number.isFinite(scoreValue)) {
|
||||
controlScoreOverrides[key] = scoreValue
|
||||
}
|
||||
const rawPointStyle = getFirstDefined(item as Record<string, unknown>, ['pointStyle'])
|
||||
const rawPointColor = getFirstDefined(item as Record<string, unknown>, ['pointColorHex'])
|
||||
const rawPointSizeScale = getFirstDefined(item as Record<string, unknown>, ['pointSizeScale'])
|
||||
const rawPointAccentRingScale = getFirstDefined(item as Record<string, unknown>, ['pointAccentRingScale'])
|
||||
const rawPointGlowStrength = getFirstDefined(item as Record<string, unknown>, ['pointGlowStrength'])
|
||||
const rawPointLabelScale = getFirstDefined(item as Record<string, unknown>, ['pointLabelScale'])
|
||||
const rawPointLabelColor = getFirstDefined(item as Record<string, unknown>, ['pointLabelColorHex'])
|
||||
if (
|
||||
rawPointStyle !== undefined
|
||||
|| rawPointColor !== undefined
|
||||
|| rawPointSizeScale !== undefined
|
||||
|| rawPointAccentRingScale !== undefined
|
||||
|| rawPointGlowStrength !== undefined
|
||||
|| rawPointLabelScale !== undefined
|
||||
|| rawPointLabelColor !== undefined
|
||||
) {
|
||||
const fallbackPointStyle = gameMode === 'score-o'
|
||||
? DEFAULT_COURSE_STYLE_CONFIG.scoreO.controls.default
|
||||
: DEFAULT_COURSE_STYLE_CONFIG.sequential.controls.default
|
||||
controlPointStyleOverrides[key] = {
|
||||
style: parseControlPointStyleId(rawPointStyle, fallbackPointStyle.style),
|
||||
colorHex: normalizeHexColor(rawPointColor, fallbackPointStyle.colorHex),
|
||||
sizeScale: parsePositiveNumber(rawPointSizeScale, fallbackPointStyle.sizeScale || 1),
|
||||
accentRingScale: parsePositiveNumber(rawPointAccentRingScale, fallbackPointStyle.accentRingScale || 0),
|
||||
glowStrength: clamp(parseNumber(rawPointGlowStrength, fallbackPointStyle.glowStrength || 0), 0, 1.2),
|
||||
labelScale: parsePositiveNumber(rawPointLabelScale, fallbackPointStyle.labelScale || 1),
|
||||
labelColorHex: normalizeHexColor(rawPointLabelColor, fallbackPointStyle.labelColorHex || ''),
|
||||
}
|
||||
}
|
||||
const titleValue = typeof (item as Record<string, unknown>).title === 'string'
|
||||
? ((item as Record<string, unknown>).title as string).trim()
|
||||
: ''
|
||||
const templateRaw = typeof (item as Record<string, unknown>).template === 'string'
|
||||
? ((item as Record<string, unknown>).template as string).trim().toLowerCase()
|
||||
: ''
|
||||
const templateValue = templateRaw === 'minimal' || templateRaw === 'story' || templateRaw === 'focus'
|
||||
? templateRaw
|
||||
: ''
|
||||
const bodyValue = typeof (item as Record<string, unknown>).body === 'string'
|
||||
? ((item as Record<string, unknown>).body as string).trim()
|
||||
: ''
|
||||
const clickTitleValue = typeof (item as Record<string, unknown>).clickTitle === 'string'
|
||||
? ((item as Record<string, unknown>).clickTitle as string).trim()
|
||||
: ''
|
||||
const clickBodyValue = typeof (item as Record<string, unknown>).clickBody === 'string'
|
||||
? ((item as Record<string, unknown>).clickBody as string).trim()
|
||||
: ''
|
||||
const autoPopupValue = (item as Record<string, unknown>).autoPopup
|
||||
const onceValue = (item as Record<string, unknown>).once
|
||||
const priorityNumeric = Number((item as Record<string, unknown>).priority)
|
||||
const ctasValue = parseContentCardCtas((item as Record<string, unknown>).ctas)
|
||||
const contentExperienceValue = parseContentExperienceOverride((item as Record<string, unknown>).contentExperience, gameConfigUrl)
|
||||
const clickExperienceValue = parseContentExperienceOverride((item as Record<string, unknown>).clickExperience, gameConfigUrl)
|
||||
const hasAutoPopup = typeof autoPopupValue === 'boolean'
|
||||
const hasOnce = typeof onceValue === 'boolean'
|
||||
const hasPriority = Number.isFinite(priorityNumeric)
|
||||
if (
|
||||
templateValue
|
||||
|| titleValue
|
||||
|| bodyValue
|
||||
|| clickTitleValue
|
||||
|| clickBodyValue
|
||||
|| hasAutoPopup
|
||||
|| hasOnce
|
||||
|| hasPriority
|
||||
|| ctasValue
|
||||
|| contentExperienceValue
|
||||
|| clickExperienceValue
|
||||
) {
|
||||
controlContentOverrides[key] = {
|
||||
...(templateValue ? { template: templateValue } : {}),
|
||||
...(titleValue ? { title: titleValue } : {}),
|
||||
...(bodyValue ? { body: bodyValue } : {}),
|
||||
...(clickTitleValue ? { clickTitle: clickTitleValue } : {}),
|
||||
...(clickBodyValue ? { clickBody: clickBodyValue } : {}),
|
||||
...(hasAutoPopup ? { autoPopup: !!autoPopupValue } : {}),
|
||||
...(hasOnce ? { once: !!onceValue } : {}),
|
||||
...(hasPriority ? { priority: Math.max(0, Math.round(priorityNumeric)) } : {}),
|
||||
...(ctasValue ? { ctas: ctasValue } : {}),
|
||||
...(contentExperienceValue ? { contentExperience: contentExperienceValue } : {}),
|
||||
...(clickExperienceValue ? { clickExperience: clickExperienceValue } : {}),
|
||||
}
|
||||
}
|
||||
const styleOverride = parseControlPointStyleOverride(item, fallbackPointStyle)
|
||||
if (styleOverride) {
|
||||
controlPointStyleOverrides[key] = styleOverride
|
||||
}
|
||||
const contentOverride = parseControlDisplayContentOverride(item, gameConfigUrl)
|
||||
if (contentOverride) {
|
||||
controlContentOverrides[key] = contentOverride
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const rawLegDefaults = rawPlayfield && rawPlayfield.legDefaults && typeof rawPlayfield.legDefaults === 'object' && !Array.isArray(rawPlayfield.legDefaults)
|
||||
? rawPlayfield.legDefaults as Record<string, unknown>
|
||||
: null
|
||||
const defaultLegStyleOverride = parseLegStyleOverride(rawLegDefaults, fallbackLegStyle)
|
||||
const rawLegOverrides = rawPlayfield && rawPlayfield.legOverrides && typeof rawPlayfield.legOverrides === 'object' && !Array.isArray(rawPlayfield.legOverrides)
|
||||
? rawPlayfield.legOverrides as Record<string, unknown>
|
||||
: null
|
||||
@@ -1373,23 +1656,99 @@ function parseGameConfigFromJson(text: string, gameConfigUrl: string): ParsedGam
|
||||
if (index === null || !item || typeof item !== 'object' || Array.isArray(item)) {
|
||||
continue
|
||||
}
|
||||
const normalized = normalizeObjectRecord(item)
|
||||
const rawStyle = getFirstDefined(normalized, ['style'])
|
||||
const rawColor = getFirstDefined(normalized, ['color', 'colorhex'])
|
||||
const rawWidthScale = getFirstDefined(normalized, ['widthscale'])
|
||||
const rawGlowStrength = getFirstDefined(normalized, ['glowstrength'])
|
||||
if (rawStyle === undefined && rawColor === undefined && rawWidthScale === undefined && rawGlowStrength === undefined) {
|
||||
continue
|
||||
}
|
||||
legStyleOverrides[index] = {
|
||||
style: parseCourseLegStyleId(rawStyle, DEFAULT_COURSE_STYLE_CONFIG.sequential.legs.default.style),
|
||||
colorHex: normalizeHexColor(rawColor, DEFAULT_COURSE_STYLE_CONFIG.sequential.legs.default.colorHex),
|
||||
widthScale: parsePositiveNumber(rawWidthScale, DEFAULT_COURSE_STYLE_CONFIG.sequential.legs.default.widthScale || 1),
|
||||
glowStrength: clamp(parseNumber(rawGlowStrength, DEFAULT_COURSE_STYLE_CONFIG.sequential.legs.default.glowStrength || 0), 0, 1.2),
|
||||
}
|
||||
const legOverride = parseLegStyleOverride(item, fallbackLegStyle)
|
||||
if (!legOverride) {
|
||||
continue
|
||||
}
|
||||
legStyleOverrides[index] = legOverride
|
||||
}
|
||||
}
|
||||
|
||||
const punchRadiusMeters = parsePositiveNumber(
|
||||
rawPunch && rawPunch.radiusMeters !== undefined
|
||||
? rawPunch.radiusMeters
|
||||
: normalizedGame.punchradiusmeters !== undefined
|
||||
? normalizedGame.punchradiusmeters
|
||||
: normalizedGame.punchradius !== undefined
|
||||
? normalizedGame.punchradius
|
||||
: normalized.punchradiusmeters !== undefined
|
||||
? normalized.punchradiusmeters
|
||||
: normalized.punchradius,
|
||||
5,
|
||||
)
|
||||
const sessionCloseAfterMs = parsePositiveNumber(
|
||||
rawSession && rawSession.closeAfterMs !== undefined
|
||||
? rawSession.closeAfterMs
|
||||
: rawSession && rawSession.sessionCloseAfterMs !== undefined
|
||||
? rawSession.sessionCloseAfterMs
|
||||
: normalizedGame.sessioncloseafterms !== undefined
|
||||
? normalizedGame.sessioncloseafterms
|
||||
: normalized.sessioncloseafterms,
|
||||
modeDefaults.sessionCloseAfterMs,
|
||||
)
|
||||
const sessionCloseWarningMs = parsePositiveNumber(
|
||||
rawSession && rawSession.closeWarningMs !== undefined
|
||||
? rawSession.closeWarningMs
|
||||
: rawSession && rawSession.sessionCloseWarningMs !== undefined
|
||||
? rawSession.sessionCloseWarningMs
|
||||
: normalizedGame.sessionclosewarningms !== undefined
|
||||
? normalizedGame.sessionclosewarningms
|
||||
: normalized.sessionclosewarningms,
|
||||
modeDefaults.sessionCloseWarningMs,
|
||||
)
|
||||
const minCompletedControlsBeforeFinish = Math.max(0, Math.floor(parseNumber(
|
||||
rawSession && rawSession.minCompletedControlsBeforeFinish !== undefined
|
||||
? rawSession.minCompletedControlsBeforeFinish
|
||||
: rawSession && rawSession.minControlsBeforeFinish !== undefined
|
||||
? rawSession.minControlsBeforeFinish
|
||||
: normalizedGame.mincompletedcontrolsbeforefinish !== undefined
|
||||
? normalizedGame.mincompletedcontrolsbeforefinish
|
||||
: normalizedGame.mincontrolsbeforefinish !== undefined
|
||||
? normalizedGame.mincontrolsbeforefinish
|
||||
: normalized.mincompletedcontrolsbeforefinish !== undefined
|
||||
? normalized.mincompletedcontrolsbeforefinish
|
||||
: normalized.mincontrolsbeforefinish,
|
||||
modeDefaults.minCompletedControlsBeforeFinish,
|
||||
)))
|
||||
const requiresFocusSelection = parseBoolean(
|
||||
rawPunch && rawPunch.requiresFocusSelection !== undefined
|
||||
? rawPunch.requiresFocusSelection
|
||||
: normalizedGame.requiresfocusselection !== undefined
|
||||
? normalizedGame.requiresfocusselection
|
||||
: rawPunch && (rawPunch as Record<string, unknown>).requiresfocusselection !== undefined
|
||||
? (rawPunch as Record<string, unknown>).requiresfocusselection
|
||||
: normalized.requiresfocusselection,
|
||||
modeDefaults.requiresFocusSelection,
|
||||
)
|
||||
const skipEnabled = parseBoolean(
|
||||
rawSkip && rawSkip.enabled !== undefined
|
||||
? rawSkip.enabled
|
||||
: normalizedGame.skipenabled !== undefined
|
||||
? normalizedGame.skipenabled
|
||||
: normalized.skipenabled,
|
||||
modeDefaults.skipEnabled,
|
||||
)
|
||||
const skipRadiusMeters = parsePositiveNumber(
|
||||
rawSkip && rawSkip.radiusMeters !== undefined
|
||||
? rawSkip.radiusMeters
|
||||
: normalizedGame.skipradiusmeters !== undefined
|
||||
? normalizedGame.skipradiusmeters
|
||||
: normalizedGame.skipradius !== undefined
|
||||
? normalizedGame.skipradius
|
||||
: normalized.skipradiusmeters !== undefined
|
||||
? normalized.skipradiusmeters
|
||||
: normalized.skipradius,
|
||||
getDefaultSkipRadiusMeters(gameMode, punchRadiusMeters),
|
||||
)
|
||||
const autoFinishOnLastControl = parseBoolean(
|
||||
rawSession && rawSession.autoFinishOnLastControl !== undefined
|
||||
? rawSession.autoFinishOnLastControl
|
||||
: normalizedGame.autofinishonlastcontrol !== undefined
|
||||
? normalizedGame.autofinishonlastcontrol
|
||||
: normalized.autofinishonlastcontrol,
|
||||
modeDefaults.autoFinishOnLastControl,
|
||||
)
|
||||
|
||||
return {
|
||||
title: rawApp && typeof rawApp.title === 'string' ? rawApp.title : '',
|
||||
appId: rawApp && typeof rawApp.id === 'string' ? rawApp.id : '',
|
||||
@@ -1410,6 +1769,9 @@ function parseGameConfigFromJson(text: string, gameConfigUrl: string): ParsedGam
|
||||
? parsePositiveNumber((rawMap.initialView as Record<string, unknown>).zoom, 17)
|
||||
: null,
|
||||
gameMode,
|
||||
sessionCloseAfterMs,
|
||||
sessionCloseWarningMs,
|
||||
minCompletedControlsBeforeFinish,
|
||||
punchPolicy: parsePunchPolicy(
|
||||
rawPunch && rawPunch.policy !== undefined
|
||||
? rawPunch.policy
|
||||
@@ -1417,71 +1779,34 @@ function parseGameConfigFromJson(text: string, gameConfigUrl: string): ParsedGam
|
||||
? normalizedGame.punchpolicy
|
||||
: normalized.punchpolicy,
|
||||
),
|
||||
punchRadiusMeters: parsePositiveNumber(
|
||||
rawPunch && rawPunch.radiusMeters !== undefined
|
||||
? rawPunch.radiusMeters
|
||||
: normalizedGame.punchradiusmeters !== undefined
|
||||
? normalizedGame.punchradiusmeters
|
||||
: normalizedGame.punchradius !== undefined
|
||||
? normalizedGame.punchradius
|
||||
: normalized.punchradiusmeters !== undefined
|
||||
? normalized.punchradiusmeters
|
||||
: normalized.punchradius,
|
||||
5,
|
||||
),
|
||||
requiresFocusSelection: parseBoolean(
|
||||
rawPunch && rawPunch.requiresFocusSelection !== undefined
|
||||
? rawPunch.requiresFocusSelection
|
||||
: normalizedGame.requiresfocusselection !== undefined
|
||||
? normalizedGame.requiresfocusselection
|
||||
: rawPunch && (rawPunch as Record<string, unknown>).requiresfocusselection !== undefined
|
||||
? (rawPunch as Record<string, unknown>).requiresfocusselection
|
||||
: normalized.requiresfocusselection,
|
||||
false,
|
||||
),
|
||||
skipEnabled: parseBoolean(
|
||||
rawSkip && rawSkip.enabled !== undefined
|
||||
? rawSkip.enabled
|
||||
: normalizedGame.skipenabled !== undefined
|
||||
? normalizedGame.skipenabled
|
||||
: normalized.skipenabled,
|
||||
false,
|
||||
),
|
||||
skipRadiusMeters: parsePositiveNumber(
|
||||
rawSkip && rawSkip.radiusMeters !== undefined
|
||||
? rawSkip.radiusMeters
|
||||
: normalizedGame.skipradiusmeters !== undefined
|
||||
? normalizedGame.skipradiusmeters
|
||||
: normalizedGame.skipradius !== undefined
|
||||
? normalizedGame.skipradius
|
||||
: normalized.skipradiusmeters !== undefined
|
||||
? normalized.skipradiusmeters
|
||||
: normalized.skipradius,
|
||||
30,
|
||||
),
|
||||
punchRadiusMeters,
|
||||
requiresFocusSelection,
|
||||
skipEnabled,
|
||||
skipRadiusMeters,
|
||||
skipRequiresConfirm: parseBoolean(
|
||||
rawSkip && rawSkip.requiresConfirm !== undefined
|
||||
? rawSkip.requiresConfirm
|
||||
: normalizedGame.skiprequiresconfirm !== undefined
|
||||
? normalizedGame.skiprequiresconfirm
|
||||
: normalized.skiprequiresconfirm,
|
||||
true,
|
||||
),
|
||||
autoFinishOnLastControl: parseBoolean(
|
||||
rawSession && rawSession.autoFinishOnLastControl !== undefined
|
||||
? rawSession.autoFinishOnLastControl
|
||||
: normalizedGame.autofinishonlastcontrol !== undefined
|
||||
? normalizedGame.autofinishonlastcontrol
|
||||
: normalized.autofinishonlastcontrol,
|
||||
true,
|
||||
modeDefaults.skipRequiresConfirm,
|
||||
),
|
||||
autoFinishOnLastControl,
|
||||
controlScoreOverrides,
|
||||
controlContentOverrides,
|
||||
defaultControlContentOverride,
|
||||
defaultControlPointStyleOverride,
|
||||
controlPointStyleOverrides,
|
||||
defaultLegStyleOverride,
|
||||
legStyleOverrides,
|
||||
defaultControlScore: rawScoring && rawScoring.defaultControlScore !== undefined
|
||||
? parsePositiveNumber(rawScoring.defaultControlScore, 10)
|
||||
: null,
|
||||
defaultControlScore: resolveDefaultControlScore(
|
||||
gameMode,
|
||||
Number.isFinite(defaultControlScoreFromPlayfield)
|
||||
? defaultControlScoreFromPlayfield
|
||||
: rawScoring && rawScoring.defaultControlScore !== undefined
|
||||
? parsePositiveNumber(rawScoring.defaultControlScore, modeDefaults.defaultControlScore)
|
||||
: null,
|
||||
),
|
||||
courseStyleConfig: parseCourseStyleConfig(rawGamePresentation),
|
||||
trackStyleConfig: parseTrackVisualizationConfig(getFirstDefined(normalizedGamePresentation, ['track'])),
|
||||
gpsMarkerStyleConfig: parseGpsMarkerStyleConfig(getFirstDefined(normalizedGamePresentation, ['gpsmarker', 'gps'])),
|
||||
@@ -1489,6 +1814,7 @@ function parseGameConfigFromJson(text: string, gameConfigUrl: string): ParsedGam
|
||||
audioConfig: parseAudioConfig(rawAudio, gameConfigUrl),
|
||||
hapticsConfig: parseHapticsConfig(rawHaptics),
|
||||
uiEffectsConfig: parseUiEffectsConfig(rawUiEffects),
|
||||
systemSettingsConfig: parseSystemSettingsConfig(rawGameSettings || rawSettings),
|
||||
declinationDeg: parseDeclinationValue(rawMap && rawMap.declination !== undefined ? rawMap.declination : normalized.declination),
|
||||
}
|
||||
}
|
||||
@@ -1518,6 +1844,11 @@ function parseGameConfigFromYaml(text: string, gameConfigUrl: string): ParsedGam
|
||||
}
|
||||
|
||||
const gameMode = parseGameMode(config.gamemode)
|
||||
const modeDefaults = getGameModeDefaults(gameMode)
|
||||
const punchRadiusMeters = parsePositiveNumber(
|
||||
config.punchradiusmeters !== undefined ? config.punchradiusmeters : config.punchradius,
|
||||
5,
|
||||
)
|
||||
|
||||
return {
|
||||
title: '',
|
||||
@@ -1530,24 +1861,27 @@ function parseGameConfigFromYaml(text: string, gameConfigUrl: string): ParsedGam
|
||||
cpRadiusMeters: parsePositiveNumber(config.cpradius, 5),
|
||||
defaultZoom: null,
|
||||
gameMode,
|
||||
sessionCloseAfterMs: modeDefaults.sessionCloseAfterMs,
|
||||
sessionCloseWarningMs: modeDefaults.sessionCloseWarningMs,
|
||||
minCompletedControlsBeforeFinish: modeDefaults.minCompletedControlsBeforeFinish,
|
||||
punchPolicy: parsePunchPolicy(config.punchpolicy),
|
||||
punchRadiusMeters: parsePositiveNumber(
|
||||
config.punchradiusmeters !== undefined ? config.punchradiusmeters : config.punchradius,
|
||||
5,
|
||||
),
|
||||
requiresFocusSelection: parseBoolean(config.requiresfocusselection, false),
|
||||
skipEnabled: parseBoolean(config.skipenabled, false),
|
||||
punchRadiusMeters,
|
||||
requiresFocusSelection: parseBoolean(config.requiresfocusselection, modeDefaults.requiresFocusSelection),
|
||||
skipEnabled: parseBoolean(config.skipenabled, modeDefaults.skipEnabled),
|
||||
skipRadiusMeters: parsePositiveNumber(
|
||||
config.skipradiusmeters !== undefined ? config.skipradiusmeters : config.skipradius,
|
||||
30,
|
||||
getDefaultSkipRadiusMeters(gameMode, punchRadiusMeters),
|
||||
),
|
||||
skipRequiresConfirm: parseBoolean(config.skiprequiresconfirm, true),
|
||||
autoFinishOnLastControl: parseBoolean(config.autofinishonlastcontrol, true),
|
||||
skipRequiresConfirm: parseBoolean(config.skiprequiresconfirm, modeDefaults.skipRequiresConfirm),
|
||||
autoFinishOnLastControl: parseBoolean(config.autofinishonlastcontrol, modeDefaults.autoFinishOnLastControl),
|
||||
controlScoreOverrides: {},
|
||||
controlContentOverrides: {},
|
||||
defaultControlContentOverride: null,
|
||||
defaultControlPointStyleOverride: null,
|
||||
controlPointStyleOverrides: {},
|
||||
defaultLegStyleOverride: null,
|
||||
legStyleOverrides: {},
|
||||
defaultControlScore: null,
|
||||
defaultControlScore: modeDefaults.defaultControlScore,
|
||||
courseStyleConfig: DEFAULT_COURSE_STYLE_CONFIG,
|
||||
trackStyleConfig: DEFAULT_TRACK_VISUALIZATION_CONFIG,
|
||||
gpsMarkerStyleConfig: DEFAULT_GPS_MARKER_STYLE_CONFIG,
|
||||
@@ -1567,7 +1901,21 @@ function parseGameConfigFromYaml(text: string, gameConfigUrl: string): ParsedGam
|
||||
enabled: config.audioenabled,
|
||||
masterVolume: config.audiomastervolume,
|
||||
obeyMuteSwitch: config.audioobeymuteswitch,
|
||||
distantDistanceMeters: config.audiodistantdistancemeters !== undefined
|
||||
? config.audiodistantdistancemeters
|
||||
: config.audiodistantdistance !== undefined
|
||||
? config.audiodistantdistance
|
||||
: config.audiofardistancemeters !== undefined
|
||||
? config.audiofardistancemeters
|
||||
: config.audiofardistance,
|
||||
approachDistanceMeters: config.audioapproachdistancemeters !== undefined ? config.audioapproachdistancemeters : config.audioapproachdistance,
|
||||
readyDistanceMeters: config.audioreadydistancemeters !== undefined
|
||||
? config.audioreadydistancemeters
|
||||
: config.audioreadydistance !== undefined
|
||||
? config.audioreadydistance
|
||||
: config.audiopunchreadydistancemeters !== undefined
|
||||
? config.audiopunchreadydistancemeters
|
||||
: config.audiopunchreadydistance,
|
||||
cues: {
|
||||
session_started: config.audiosessionstarted,
|
||||
'control_completed:start': config.audiostartcomplete,
|
||||
@@ -1575,6 +1923,7 @@ function parseGameConfigFromYaml(text: string, gameConfigUrl: string): ParsedGam
|
||||
'control_completed:finish': config.audiofinishcomplete,
|
||||
'punch_feedback:warning': config.audiowarning,
|
||||
'guidance:searching': config.audiosearching,
|
||||
'guidance:distant': config.audiodistant,
|
||||
'guidance:approaching': config.audioapproaching,
|
||||
'guidance:ready': config.audioready,
|
||||
},
|
||||
@@ -1589,6 +1938,7 @@ function parseGameConfigFromYaml(text: string, gameConfigUrl: string): ParsedGam
|
||||
'control_completed:finish': config.hapticsfinishcomplete,
|
||||
'punch_feedback:warning': config.hapticswarning,
|
||||
'guidance:searching': config.hapticssearching,
|
||||
'guidance:distant': config.hapticsdistant,
|
||||
'guidance:approaching': config.hapticsapproaching,
|
||||
'guidance:ready': config.hapticsready,
|
||||
},
|
||||
@@ -1605,6 +1955,7 @@ function parseGameConfigFromYaml(text: string, gameConfigUrl: string): ParsedGam
|
||||
'guidance:ready': { enabled: config.uireadyenabled, punchButtonMotion: config.uireadybuttonmotion, durationMs: config.uireadydurationms },
|
||||
},
|
||||
}),
|
||||
systemSettingsConfig: { values: {}, locks: {} },
|
||||
declinationDeg: parseDeclinationValue(config.declination),
|
||||
}
|
||||
}
|
||||
@@ -1827,6 +2178,9 @@ export async function loadRemoteMapConfig(gameConfigUrl: string): Promise<Remote
|
||||
courseStatusText,
|
||||
cpRadiusMeters: gameConfig.cpRadiusMeters,
|
||||
gameMode: gameConfig.gameMode,
|
||||
sessionCloseAfterMs: gameConfig.sessionCloseAfterMs,
|
||||
sessionCloseWarningMs: gameConfig.sessionCloseWarningMs,
|
||||
minCompletedControlsBeforeFinish: gameConfig.minCompletedControlsBeforeFinish,
|
||||
punchPolicy: gameConfig.punchPolicy,
|
||||
punchRadiusMeters: gameConfig.punchRadiusMeters,
|
||||
requiresFocusSelection: gameConfig.requiresFocusSelection,
|
||||
@@ -1836,7 +2190,10 @@ export async function loadRemoteMapConfig(gameConfigUrl: string): Promise<Remote
|
||||
autoFinishOnLastControl: gameConfig.autoFinishOnLastControl,
|
||||
controlScoreOverrides: gameConfig.controlScoreOverrides,
|
||||
controlContentOverrides: gameConfig.controlContentOverrides,
|
||||
defaultControlContentOverride: gameConfig.defaultControlContentOverride,
|
||||
defaultControlPointStyleOverride: gameConfig.defaultControlPointStyleOverride,
|
||||
controlPointStyleOverrides: gameConfig.controlPointStyleOverrides,
|
||||
defaultLegStyleOverride: gameConfig.defaultLegStyleOverride,
|
||||
legStyleOverrides: gameConfig.legStyleOverrides,
|
||||
defaultControlScore: gameConfig.defaultControlScore,
|
||||
courseStyleConfig: gameConfig.courseStyleConfig,
|
||||
@@ -1846,6 +2203,7 @@ export async function loadRemoteMapConfig(gameConfigUrl: string): Promise<Remote
|
||||
audioConfig: gameConfig.audioConfig,
|
||||
hapticsConfig: gameConfig.hapticsConfig,
|
||||
uiEffectsConfig: gameConfig.uiEffectsConfig,
|
||||
systemSettingsConfig: gameConfig.systemSettingsConfig,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user