Add config-driven game host updates
This commit is contained in:
@@ -9,7 +9,7 @@ import { lonLatToWorldTile, worldTileToLonLat, type LonLatPoint, type MapCalibra
|
||||
import { type OrienteeringCourseData } from '../../utils/orienteeringCourse'
|
||||
import { isTileWithinBounds, type RemoteMapConfig, type TileZoomBounds } from '../../utils/remoteMapConfig'
|
||||
import { GameRuntime } from '../../game/core/gameRuntime'
|
||||
import { type GameEffect } from '../../game/core/gameResult'
|
||||
import { type GameEffect, type GameResult } from '../../game/core/gameResult'
|
||||
import { buildGameDefinitionFromCourse } from '../../game/content/courseToGameDefinition'
|
||||
import { FeedbackDirector } from '../../game/feedback/feedbackDirector'
|
||||
import { EMPTY_GAME_PRESENTATION_STATE, type GamePresentationState } from '../../game/presentation/presentationState'
|
||||
@@ -170,6 +170,7 @@ export interface MapEngineViewState {
|
||||
panelAccuracyUnitText: string
|
||||
punchButtonText: string
|
||||
punchButtonEnabled: boolean
|
||||
skipButtonEnabled: boolean
|
||||
punchHintText: string
|
||||
punchFeedbackVisible: boolean
|
||||
punchFeedbackText: string
|
||||
@@ -194,6 +195,18 @@ export interface MapEngineCallbacks {
|
||||
onData: (patch: Partial<MapEngineViewState>) => void
|
||||
}
|
||||
|
||||
export interface MapEngineGameInfoRow {
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export interface MapEngineGameInfoSnapshot {
|
||||
title: string
|
||||
subtitle: string
|
||||
localRows: MapEngineGameInfoRow[]
|
||||
globalRows: MapEngineGameInfoRow[]
|
||||
}
|
||||
|
||||
const VIEW_SYNC_KEYS: Array<keyof MapEngineViewState> = [
|
||||
'buildVersion',
|
||||
'renderMode',
|
||||
@@ -273,6 +286,7 @@ const VIEW_SYNC_KEYS: Array<keyof MapEngineViewState> = [
|
||||
'panelAccuracyUnitText',
|
||||
'punchButtonText',
|
||||
'punchButtonEnabled',
|
||||
'skipButtonEnabled',
|
||||
'punchHintText',
|
||||
'punchFeedbackVisible',
|
||||
'punchFeedbackText',
|
||||
@@ -338,6 +352,19 @@ function interpolateAngleDeg(currentDeg: number, targetDeg: number, factor: numb
|
||||
return normalizeRotationDeg(currentDeg + normalizeAngleDeltaDeg(targetDeg - currentDeg) * factor)
|
||||
}
|
||||
|
||||
function formatGameSessionStatusText(status: 'idle' | 'running' | 'finished' | 'failed'): string {
|
||||
if (status === 'running') {
|
||||
return '进行中'
|
||||
}
|
||||
if (status === 'finished') {
|
||||
return '已结束'
|
||||
}
|
||||
if (status === 'failed') {
|
||||
return '已失败'
|
||||
}
|
||||
return '未开始'
|
||||
}
|
||||
|
||||
function formatRotationText(rotationDeg: number): string {
|
||||
return `${Math.round(normalizeRotationDeg(rotationDeg))}deg`
|
||||
}
|
||||
@@ -577,12 +604,21 @@ export class MapEngine {
|
||||
courseData: OrienteeringCourseData | null
|
||||
courseOverlayVisible: boolean
|
||||
cpRadiusMeters: number
|
||||
configAppId: string
|
||||
configSchemaVersion: string
|
||||
configVersion: string
|
||||
controlScoreOverrides: Record<string, number>
|
||||
defaultControlScore: number | null
|
||||
gameRuntime: GameRuntime
|
||||
telemetryRuntime: TelemetryRuntime
|
||||
gamePresentation: GamePresentationState
|
||||
gameMode: 'classic-sequential' | 'score-o'
|
||||
punchPolicy: 'enter' | 'enter-confirm'
|
||||
punchRadiusMeters: number
|
||||
requiresFocusSelection: boolean
|
||||
skipEnabled: boolean
|
||||
skipRadiusMeters: number
|
||||
skipRequiresConfirm: boolean
|
||||
autoFinishOnLastControl: boolean
|
||||
punchFeedbackTimer: number
|
||||
contentCardTimer: number
|
||||
@@ -734,6 +770,11 @@ export class MapEngine {
|
||||
this.courseData = null
|
||||
this.courseOverlayVisible = false
|
||||
this.cpRadiusMeters = 5
|
||||
this.configAppId = ''
|
||||
this.configSchemaVersion = '1'
|
||||
this.configVersion = ''
|
||||
this.controlScoreOverrides = {}
|
||||
this.defaultControlScore = null
|
||||
this.gameRuntime = new GameRuntime()
|
||||
this.telemetryRuntime = new TelemetryRuntime()
|
||||
this.telemetryRuntime.configure()
|
||||
@@ -741,6 +782,10 @@ export class MapEngine {
|
||||
this.gameMode = 'classic-sequential'
|
||||
this.punchPolicy = 'enter-confirm'
|
||||
this.punchRadiusMeters = 5
|
||||
this.requiresFocusSelection = false
|
||||
this.skipEnabled = false
|
||||
this.skipRadiusMeters = 30
|
||||
this.skipRequiresConfirm = true
|
||||
this.autoFinishOnLastControl = true
|
||||
this.punchFeedbackTimer = 0
|
||||
this.contentCardTimer = 0
|
||||
@@ -754,7 +799,7 @@ export class MapEngine {
|
||||
projectionMode: PROJECTION_MODE,
|
||||
mapReady: false,
|
||||
mapReadyText: 'BOOTING',
|
||||
mapName: 'LCX 测试地图',
|
||||
mapName: '未命名配置',
|
||||
configStatusText: '远程配置待加载',
|
||||
zoom: DEFAULT_ZOOM,
|
||||
rotationDeg: 0,
|
||||
@@ -836,6 +881,7 @@ export class MapEngine {
|
||||
gameSessionStatus: 'idle',
|
||||
gameModeText: '顺序赛',
|
||||
punchButtonEnabled: false,
|
||||
skipButtonEnabled: false,
|
||||
punchHintText: '等待进入检查点范围',
|
||||
punchFeedbackVisible: false,
|
||||
punchFeedbackText: '',
|
||||
@@ -895,6 +941,68 @@ export class MapEngine {
|
||||
return { ...this.state }
|
||||
}
|
||||
|
||||
getGameInfoSnapshot(): MapEngineGameInfoSnapshot {
|
||||
const definition = this.gameRuntime.definition
|
||||
const sessionState = this.gameRuntime.state
|
||||
const telemetryState = this.telemetryRuntime.state
|
||||
const telemetryPresentation = this.telemetryRuntime.getPresentation()
|
||||
const currentTarget = definition && sessionState
|
||||
? definition.controls.find((control) => control.id === sessionState.currentTargetControlId) || null
|
||||
: null
|
||||
const currentTargetText = currentTarget
|
||||
? `${currentTarget.label} / ${currentTarget.kind === 'start'
|
||||
? '开始点'
|
||||
: currentTarget.kind === 'finish'
|
||||
? '结束点'
|
||||
: '检查点'}`
|
||||
: '--'
|
||||
const title = this.state.mapName || (definition ? definition.title : '当前游戏')
|
||||
const subtitle = `${this.getGameModeText()} / ${formatGameSessionStatusText(this.state.gameSessionStatus)}`
|
||||
const localRows: MapEngineGameInfoRow[] = [
|
||||
{ label: '比赛名称', value: title || '--' },
|
||||
{ label: '配置版本', value: this.configVersion || '--' },
|
||||
{ label: 'Schema版本', value: this.configSchemaVersion || '--' },
|
||||
{ label: '活动ID', value: this.configAppId || '--' },
|
||||
{ label: '地图', value: this.state.mapName || '--' },
|
||||
{ label: '模式', value: this.getGameModeText() },
|
||||
{ label: '状态', value: formatGameSessionStatusText(this.state.gameSessionStatus) },
|
||||
{ label: '当前目标', value: currentTargetText },
|
||||
{ label: '进度', value: this.gamePresentation.hud.progressText || '--' },
|
||||
{ label: '当前积分', value: sessionState ? String(sessionState.score) : '0' },
|
||||
{ label: '已完成点', value: sessionState ? String(sessionState.completedControlIds.length) : '0' },
|
||||
{ label: '已跳过点', value: sessionState ? String(sessionState.skippedControlIds.length) : '0' },
|
||||
{ label: '打点规则', value: `${this.punchPolicy} / ${this.punchRadiusMeters}m` },
|
||||
{ label: '跳点规则', value: this.skipEnabled ? `${this.skipRadiusMeters}m / ${this.skipRequiresConfirm ? '确认跳过' : '直接跳过'}` : '关闭' },
|
||||
{ label: '定位源', value: this.state.locationSourceText || '--' },
|
||||
{ label: '当前位置', value: this.state.gpsCoordText || '--' },
|
||||
{ label: 'GPS精度', value: telemetryState.lastGpsAccuracyMeters == null ? '--' : `${telemetryState.lastGpsAccuracyMeters.toFixed(1)}m` },
|
||||
{ label: '目标距离', value: `${telemetryPresentation.distanceToTargetValueText}${telemetryPresentation.distanceToTargetUnitText}` || '--' },
|
||||
{ label: '当前速度', value: `${telemetryPresentation.speedText} km/h` },
|
||||
{ label: '心率源', value: this.state.heartRateSourceText || '--' },
|
||||
{ label: '当前心率', value: this.state.panelHeartRateValueText === '--' ? '--' : `${this.state.panelHeartRateValueText}${this.state.panelHeartRateUnitText}` },
|
||||
{ label: '心率设备', value: this.state.heartRateDeviceText || '--' },
|
||||
{ label: '心率分区', value: this.state.panelHeartRateZoneNameText === '--' ? '--' : `${this.state.panelHeartRateZoneNameText} ${this.state.panelHeartRateZoneRangeText}` },
|
||||
{ label: '本局用时', value: telemetryPresentation.timerText },
|
||||
{ label: '累计里程', value: telemetryPresentation.mileageText },
|
||||
{ label: '累计消耗', value: `${telemetryPresentation.caloriesValueText}${telemetryPresentation.caloriesUnitText}` },
|
||||
{ label: '提示状态', value: this.state.punchHintText || '--' },
|
||||
]
|
||||
const globalRows: MapEngineGameInfoRow[] = [
|
||||
{ label: '全球积分', value: '未接入' },
|
||||
{ label: '全球排名', value: '未接入' },
|
||||
{ label: '在线人数', value: '未接入' },
|
||||
{ label: '队伍状态', value: '未接入' },
|
||||
{ label: '实时广播', value: '未接入' },
|
||||
]
|
||||
|
||||
return {
|
||||
title,
|
||||
subtitle,
|
||||
localRows,
|
||||
globalRows,
|
||||
}
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.clearInertiaTimer()
|
||||
this.clearPreviewResetTimer()
|
||||
@@ -948,6 +1056,12 @@ export class MapEngine {
|
||||
this.setCourseHeading(null)
|
||||
}
|
||||
|
||||
clearStartSessionResidue(): void {
|
||||
this.currentGpsTrack = []
|
||||
this.courseOverlayVisible = false
|
||||
this.setCourseHeading(null)
|
||||
}
|
||||
|
||||
handleClearMapTestArtifacts(): void {
|
||||
this.clearFinishedTestOverlay()
|
||||
this.setState({
|
||||
@@ -963,6 +1077,29 @@ export class MapEngine {
|
||||
return this.gamePresentation.hud.hudTargetControlId
|
||||
}
|
||||
|
||||
isSkipAvailable(): boolean {
|
||||
const definition = this.gameRuntime.definition
|
||||
const state = this.gameRuntime.state
|
||||
if (!definition || !state || state.status !== 'running' || !definition.skipEnabled) {
|
||||
return false
|
||||
}
|
||||
|
||||
const currentTarget = definition.controls.find((control) => control.id === state.currentTargetControlId) || null
|
||||
if (!currentTarget || currentTarget.kind !== 'control' || !this.currentGpsPoint) {
|
||||
return false
|
||||
}
|
||||
|
||||
const avgLatRad = ((currentTarget.point.lat + this.currentGpsPoint.lat) / 2) * Math.PI / 180
|
||||
const dx = (this.currentGpsPoint.lon - currentTarget.point.lon) * 111320 * Math.cos(avgLatRad)
|
||||
const dy = (this.currentGpsPoint.lat - currentTarget.point.lat) * 110540
|
||||
const distanceMeters = Math.sqrt(dx * dx + dy * dy)
|
||||
return distanceMeters <= definition.skipRadiusMeters
|
||||
}
|
||||
|
||||
shouldConfirmSkipAction(): boolean {
|
||||
return !!(this.gameRuntime.definition && this.gameRuntime.definition.skipRequiresConfirm)
|
||||
}
|
||||
|
||||
getLocationControllerViewPatch(): Partial<MapEngineViewState> {
|
||||
const debugState = this.locationController.getDebugState()
|
||||
return {
|
||||
@@ -993,10 +1130,10 @@ export class MapEngine {
|
||||
return this.gameMode === 'score-o' ? '积分赛' : '顺序赛'
|
||||
}
|
||||
|
||||
loadGameDefinitionFromCourse(): GameEffect[] {
|
||||
loadGameDefinitionFromCourse(): GameResult | null {
|
||||
if (!this.courseData) {
|
||||
this.clearGameRuntime()
|
||||
return []
|
||||
return null
|
||||
}
|
||||
|
||||
const definition = buildGameDefinitionFromCourse(
|
||||
@@ -1006,18 +1143,20 @@ export class MapEngine {
|
||||
this.autoFinishOnLastControl,
|
||||
this.punchPolicy,
|
||||
this.punchRadiusMeters,
|
||||
this.requiresFocusSelection,
|
||||
this.skipEnabled,
|
||||
this.skipRadiusMeters,
|
||||
this.skipRequiresConfirm,
|
||||
this.controlScoreOverrides,
|
||||
this.defaultControlScore,
|
||||
)
|
||||
const result = this.gameRuntime.loadDefinition(definition)
|
||||
this.telemetryRuntime.loadDefinition(definition)
|
||||
this.gamePresentation = result.presentation
|
||||
this.courseOverlayVisible = true
|
||||
this.telemetryRuntime.syncGameState(this.gameRuntime.definition, result.nextState, this.getHudTargetControlId())
|
||||
this.refreshCourseHeadingFromPresentation()
|
||||
this.syncGameResultState(result)
|
||||
this.telemetryRuntime.syncGameState(this.gameRuntime.definition, result.nextState, result.presentation.hud.hudTargetControlId)
|
||||
this.updateSessionTimerLoop()
|
||||
this.setState({
|
||||
gameModeText: this.getGameModeText(),
|
||||
})
|
||||
return result.effects
|
||||
return result
|
||||
}
|
||||
|
||||
refreshCourseHeadingFromPresentation(): void {
|
||||
@@ -1083,6 +1222,7 @@ export class MapEngine {
|
||||
panelProgressText: this.gamePresentation.hud.progressText,
|
||||
punchButtonText: this.gamePresentation.hud.punchButtonText,
|
||||
punchButtonEnabled: this.gamePresentation.hud.punchButtonEnabled,
|
||||
skipButtonEnabled: this.isSkipAvailable(),
|
||||
punchHintText: this.gamePresentation.hud.punchHintText,
|
||||
}
|
||||
|
||||
@@ -1121,6 +1261,28 @@ export class MapEngine {
|
||||
}
|
||||
}
|
||||
|
||||
resetTransientGameUiState(): void {
|
||||
this.clearPunchFeedbackTimer()
|
||||
this.clearContentCardTimer()
|
||||
this.clearMapPulseTimer()
|
||||
this.clearStageFxTimer()
|
||||
this.setState({
|
||||
punchFeedbackVisible: false,
|
||||
punchFeedbackText: '',
|
||||
punchFeedbackTone: 'neutral',
|
||||
punchFeedbackFxClass: '',
|
||||
contentCardVisible: false,
|
||||
contentCardTitle: '',
|
||||
contentCardBody: '',
|
||||
contentCardFxClass: '',
|
||||
mapPulseVisible: false,
|
||||
mapPulseFxClass: '',
|
||||
stageFxVisible: false,
|
||||
stageFxClass: '',
|
||||
punchButtonFxClass: '',
|
||||
}, true)
|
||||
}
|
||||
|
||||
clearSessionTimerInterval(): void {
|
||||
if (this.sessionTimerInterval) {
|
||||
clearInterval(this.sessionTimerInterval)
|
||||
@@ -1300,6 +1462,33 @@ export class MapEngine {
|
||||
return this.resolveGameStatusText(effects)
|
||||
}
|
||||
|
||||
syncGameResultState(result: GameResult): void {
|
||||
this.gamePresentation = result.presentation
|
||||
this.refreshCourseHeadingFromPresentation()
|
||||
}
|
||||
|
||||
resolveAppliedGameStatusText(result: GameResult, fallbackStatusText?: string | null): string | null {
|
||||
return this.applyGameEffects(result.effects) || fallbackStatusText || this.resolveGameStatusText(result.effects)
|
||||
}
|
||||
|
||||
commitGameResult(
|
||||
result: GameResult,
|
||||
fallbackStatusText?: string | null,
|
||||
extraPatch: Partial<MapEngineViewState> = {},
|
||||
syncRenderer = true,
|
||||
): string | null {
|
||||
this.syncGameResultState(result)
|
||||
const gameStatusText = this.resolveAppliedGameStatusText(result, fallbackStatusText)
|
||||
this.setState({
|
||||
...this.getGameViewPatch(gameStatusText),
|
||||
...extraPatch,
|
||||
}, true)
|
||||
if (syncRenderer) {
|
||||
this.syncRenderer()
|
||||
}
|
||||
return gameStatusText
|
||||
}
|
||||
|
||||
handleStartGame(): void {
|
||||
if (!this.gameRuntime.definition || !this.gameRuntime.state) {
|
||||
this.setState({
|
||||
@@ -1312,6 +1501,10 @@ export class MapEngine {
|
||||
return
|
||||
}
|
||||
|
||||
this.feedbackDirector.reset()
|
||||
this.resetTransientGameUiState()
|
||||
this.clearStartSessionResidue()
|
||||
|
||||
if (!this.locationController.listening) {
|
||||
this.locationController.start()
|
||||
}
|
||||
@@ -1328,15 +1521,30 @@ export class MapEngine {
|
||||
})
|
||||
}
|
||||
|
||||
this.gamePresentation = this.gameRuntime.getPresentation()
|
||||
this.courseOverlayVisible = true
|
||||
this.refreshCourseHeadingFromPresentation()
|
||||
const defaultStatusText = this.currentGpsPoint
|
||||
? `顺序打点已开始 (${this.buildVersion})`
|
||||
: `顺序打点已开始,GPS定位启动中 (${this.buildVersion})`
|
||||
const gameStatusText = this.applyGameEffects(gameResult.effects) || defaultStatusText
|
||||
this.commitGameResult(gameResult, defaultStatusText)
|
||||
}
|
||||
|
||||
handleForceExitGame(): void {
|
||||
this.feedbackDirector.reset()
|
||||
|
||||
if (!this.courseData) {
|
||||
this.clearGameRuntime()
|
||||
this.resetTransientGameUiState()
|
||||
this.setState({
|
||||
...this.getGameViewPatch(`已退出当前对局 (${this.buildVersion})`),
|
||||
}, true)
|
||||
this.syncRenderer()
|
||||
return
|
||||
}
|
||||
|
||||
this.loadGameDefinitionFromCourse()
|
||||
this.resetTransientGameUiState()
|
||||
this.setState({
|
||||
...this.getGameViewPatch(gameStatusText),
|
||||
...this.getGameViewPatch(`已退出当前对局 (${this.buildVersion})`),
|
||||
}, true)
|
||||
this.syncRenderer()
|
||||
}
|
||||
@@ -1347,13 +1555,7 @@ export class MapEngine {
|
||||
type: 'punch_requested',
|
||||
at: Date.now(),
|
||||
})
|
||||
this.gamePresentation = gameResult.presentation
|
||||
this.refreshCourseHeadingFromPresentation()
|
||||
const gameStatusText = this.applyGameEffects(gameResult.effects)
|
||||
this.setState({
|
||||
...this.getGameViewPatch(gameStatusText),
|
||||
}, true)
|
||||
this.syncRenderer()
|
||||
this.commitGameResult(gameResult)
|
||||
}
|
||||
|
||||
handleLocationUpdate(longitude: number, latitude: number, accuracyMeters: number | null): void {
|
||||
@@ -1388,9 +1590,8 @@ export class MapEngine {
|
||||
lat: latitude,
|
||||
accuracyMeters,
|
||||
})
|
||||
this.gamePresentation = gameResult.presentation
|
||||
this.refreshCourseHeadingFromPresentation()
|
||||
gameStatusText = this.applyGameEffects(gameResult.effects)
|
||||
this.syncGameResultState(gameResult)
|
||||
gameStatusText = this.resolveAppliedGameStatusText(gameResult)
|
||||
}
|
||||
|
||||
if (gpsInsideMap && !this.hasGpsCenteredOnce) {
|
||||
@@ -1462,14 +1663,24 @@ export class MapEngine {
|
||||
}
|
||||
|
||||
this.gameMode = nextMode
|
||||
const effects = this.loadGameDefinitionFromCourse()
|
||||
const result = this.loadGameDefinitionFromCourse()
|
||||
const modeText = this.getGameModeText()
|
||||
const statusText = this.applyGameEffects(effects) || `已切换到${modeText} (${this.buildVersion})`
|
||||
this.setState({
|
||||
...this.getGameViewPatch(statusText),
|
||||
if (!result) {
|
||||
return
|
||||
}
|
||||
this.commitGameResult(result, `已切换到${modeText} (${this.buildVersion})`, {
|
||||
gameModeText: modeText,
|
||||
}, true)
|
||||
this.syncRenderer()
|
||||
})
|
||||
}
|
||||
|
||||
handleSkipAction(): void {
|
||||
const gameResult = this.gameRuntime.dispatch({
|
||||
type: 'skip_requested',
|
||||
at: Date.now(),
|
||||
lon: this.currentGpsPoint ? this.currentGpsPoint.lon : null,
|
||||
lat: this.currentGpsPoint ? this.currentGpsPoint.lat : null,
|
||||
})
|
||||
this.commitGameResult(gameResult)
|
||||
}
|
||||
|
||||
handleConnectHeartRate(): void {
|
||||
@@ -1625,9 +1836,18 @@ export class MapEngine {
|
||||
this.tileBoundsByZoom = config.tileBoundsByZoom
|
||||
this.courseData = config.course
|
||||
this.cpRadiusMeters = config.cpRadiusMeters
|
||||
this.configAppId = config.configAppId
|
||||
this.configSchemaVersion = config.configSchemaVersion
|
||||
this.configVersion = config.configVersion
|
||||
this.controlScoreOverrides = config.controlScoreOverrides
|
||||
this.defaultControlScore = config.defaultControlScore
|
||||
this.gameMode = config.gameMode
|
||||
this.punchPolicy = config.punchPolicy
|
||||
this.punchRadiusMeters = config.punchRadiusMeters
|
||||
this.requiresFocusSelection = config.requiresFocusSelection
|
||||
this.skipEnabled = config.skipEnabled
|
||||
this.skipRadiusMeters = config.skipRadiusMeters
|
||||
this.skipRequiresConfirm = config.skipRequiresConfirm
|
||||
this.autoFinishOnLastControl = config.autoFinishOnLastControl
|
||||
this.telemetryRuntime.configure(config.telemetryConfig)
|
||||
this.feedbackDirector.configure({
|
||||
@@ -1636,10 +1856,11 @@ export class MapEngine {
|
||||
uiEffectsConfig: config.uiEffectsConfig,
|
||||
})
|
||||
|
||||
const gameEffects = this.loadGameDefinitionFromCourse()
|
||||
const gameStatusText = this.applyGameEffects(gameEffects)
|
||||
const gameResult = this.loadGameDefinitionFromCourse()
|
||||
const gameStatusText = gameResult ? this.resolveAppliedGameStatusText(gameResult) : null
|
||||
const statePatch: Partial<MapEngineViewState> = {
|
||||
configStatusText: `远程配置已载入 / ${config.courseStatusText}`,
|
||||
mapName: config.configTitle,
|
||||
configStatusText: `配置已载入 / ${config.configTitle} / ${config.courseStatusText}`,
|
||||
projectionMode: config.projectionModeText,
|
||||
tileSource: config.tileSource,
|
||||
sensorHeadingText: formatHeadingText(this.smoothedSensorHeadingDeg === null ? null : getCompassReferenceHeadingDeg(this.northReferenceMode, this.smoothedSensorHeadingDeg)),
|
||||
@@ -1647,7 +1868,7 @@ export class MapEngine {
|
||||
northReferenceButtonText: formatNorthReferenceButtonText(this.northReferenceMode),
|
||||
northReferenceText: formatNorthReferenceText(this.northReferenceMode),
|
||||
compassNeedleDeg: formatCompassNeedleDegForMode(this.northReferenceMode, this.smoothedSensorHeadingDeg),
|
||||
...this.getGameViewPatch(),
|
||||
...this.getGameViewPatch(gameStatusText),
|
||||
}
|
||||
|
||||
if (!this.state.stageWidth || !this.state.stageHeight) {
|
||||
@@ -1869,12 +2090,10 @@ export class MapEngine {
|
||||
at: Date.now(),
|
||||
controlId: focusedControlId,
|
||||
})
|
||||
this.gamePresentation = gameResult.presentation
|
||||
this.telemetryRuntime.syncGameState(this.gameRuntime.definition, this.gameRuntime.state, this.getHudTargetControlId())
|
||||
this.setState({
|
||||
...this.getGameViewPatch(focusedControlId ? `已选择目标点 (${this.buildVersion})` : `已取消目标点选择 (${this.buildVersion})`),
|
||||
}, true)
|
||||
this.syncRenderer()
|
||||
this.commitGameResult(
|
||||
gameResult,
|
||||
focusedControlId ? `已选择目标点 (${this.buildVersion})` : `已取消目标点选择 (${this.buildVersion})`,
|
||||
)
|
||||
}
|
||||
|
||||
findFocusableControlAt(stageX: number, stageY: number): string | null | undefined {
|
||||
@@ -2472,6 +2691,8 @@ export class MapEngine {
|
||||
activeLegIndices: this.gamePresentation.map.activeLegIndices,
|
||||
completedLegIndices: this.gamePresentation.map.completedLegIndices,
|
||||
completedControlSequences: this.gamePresentation.map.completedControlSequences,
|
||||
skippedControlIds: this.gamePresentation.map.skippedControlIds,
|
||||
skippedControlSequences: this.gamePresentation.map.skippedControlSequences,
|
||||
osmReferenceEnabled: this.state.osmReferenceEnabled,
|
||||
overlayOpacity: MAP_OVERLAY_OPACITY,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user