|
|
@@ -13,10 +13,12 @@ import { type OrienteeringCourseData } from '../../utils/orienteeringCourse'
|
|
|
import { isTileWithinBounds, type RemoteMapConfig, type TileZoomBounds } from '../../utils/remoteMapConfig'
|
|
|
import { formatAnimationLevelText, resolveAnimationLevel, type AnimationLevel } from '../../utils/animationLevel'
|
|
|
import { GameRuntime } from '../../game/core/gameRuntime'
|
|
|
+import { type GameControlDisplayContentOverride } from '../../game/core/gameDefinition'
|
|
|
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'
|
|
|
+import { buildResultSummarySnapshot, type ResultSummarySnapshot } from '../../game/result/resultSummary'
|
|
|
import { TelemetryRuntime } from '../../game/telemetry/telemetryRuntime'
|
|
|
import { getHeartRateToneSampleBpm, type HeartRateTone } from '../../game/telemetry/telemetryConfig'
|
|
|
|
|
|
@@ -257,6 +259,8 @@ export interface MapEngineGameInfoSnapshot {
|
|
|
globalRows: MapEngineGameInfoRow[]
|
|
|
}
|
|
|
|
|
|
+export type MapEngineResultSnapshot = ResultSummarySnapshot
|
|
|
+
|
|
|
const VIEW_SYNC_KEYS: Array<keyof MapEngineViewState> = [
|
|
|
'animationLevel',
|
|
|
'buildVersion',
|
|
|
@@ -868,6 +872,7 @@ export class MapEngine {
|
|
|
configSchemaVersion: string
|
|
|
configVersion: string
|
|
|
controlScoreOverrides: Record<string, number>
|
|
|
+ controlContentOverrides: Record<string, GameControlDisplayContentOverride>
|
|
|
defaultControlScore: number | null
|
|
|
gameRuntime: GameRuntime
|
|
|
telemetryRuntime: TelemetryRuntime
|
|
|
@@ -882,6 +887,8 @@ export class MapEngine {
|
|
|
autoFinishOnLastControl: boolean
|
|
|
punchFeedbackTimer: number
|
|
|
contentCardTimer: number
|
|
|
+ currentContentCardPriority: number
|
|
|
+ shownContentCardKeys: Record<string, true>
|
|
|
mapPulseTimer: number
|
|
|
stageFxTimer: number
|
|
|
sessionTimerInterval: number
|
|
|
@@ -1076,8 +1083,8 @@ export class MapEngine {
|
|
|
showPunchFeedback: (text, tone, motionClass) => {
|
|
|
this.showPunchFeedback(text, tone, motionClass)
|
|
|
},
|
|
|
- showContentCard: (title, body, motionClass) => {
|
|
|
- this.showContentCard(title, body, motionClass)
|
|
|
+ showContentCard: (title, body, motionClass, options) => {
|
|
|
+ this.showContentCard(title, body, motionClass, options)
|
|
|
},
|
|
|
setPunchButtonFxClass: (className) => {
|
|
|
this.setPunchButtonFxClass(className)
|
|
|
@@ -1118,6 +1125,7 @@ export class MapEngine {
|
|
|
this.configSchemaVersion = '1'
|
|
|
this.configVersion = ''
|
|
|
this.controlScoreOverrides = {}
|
|
|
+ this.controlContentOverrides = {}
|
|
|
this.defaultControlScore = null
|
|
|
this.gameRuntime = new GameRuntime()
|
|
|
this.telemetryRuntime = new TelemetryRuntime()
|
|
|
@@ -1134,6 +1142,8 @@ export class MapEngine {
|
|
|
this.gpsLockEnabled = false
|
|
|
this.punchFeedbackTimer = 0
|
|
|
this.contentCardTimer = 0
|
|
|
+ this.currentContentCardPriority = 0
|
|
|
+ this.shownContentCardKeys = {}
|
|
|
this.mapPulseTimer = 0
|
|
|
this.stageFxTimer = 0
|
|
|
this.sessionTimerInterval = 0
|
|
|
@@ -1405,6 +1415,15 @@ export class MapEngine {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ getResultSceneSnapshot(): MapEngineResultSnapshot {
|
|
|
+ return buildResultSummarySnapshot(
|
|
|
+ this.gameRuntime.definition,
|
|
|
+ this.gameRuntime.state,
|
|
|
+ this.telemetryRuntime.getPresentation(),
|
|
|
+ this.state.mapName || (this.gameRuntime.definition ? this.gameRuntime.definition.title : '本局结果'),
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
destroy(): void {
|
|
|
this.clearInertiaTimer()
|
|
|
this.clearPreviewResetTimer()
|
|
|
@@ -1586,6 +1605,7 @@ export class MapEngine {
|
|
|
this.skipRadiusMeters,
|
|
|
this.skipRequiresConfirm,
|
|
|
this.controlScoreOverrides,
|
|
|
+ this.controlContentOverrides,
|
|
|
this.defaultControlScore,
|
|
|
)
|
|
|
const result = this.gameRuntime.loadDefinition(definition)
|
|
|
@@ -1723,6 +1743,12 @@ export class MapEngine {
|
|
|
panelProgressFxClass: '',
|
|
|
panelDistanceFxClass: '',
|
|
|
}, true)
|
|
|
+ this.currentContentCardPriority = 0
|
|
|
+ }
|
|
|
+
|
|
|
+ resetSessionContentExperienceState(): void {
|
|
|
+ this.shownContentCardKeys = {}
|
|
|
+ this.currentContentCardPriority = 0
|
|
|
}
|
|
|
|
|
|
clearSessionTimerInterval(): void {
|
|
|
@@ -1878,7 +1904,22 @@ export class MapEngine {
|
|
|
}, 1400) as unknown as number
|
|
|
}
|
|
|
|
|
|
- showContentCard(title: string, body: string, motionClass = ''): void {
|
|
|
+ showContentCard(title: string, body: string, motionClass = '', options?: { contentKey?: string; autoPopup?: boolean; once?: boolean; priority?: number }): void {
|
|
|
+ const autoPopup = !options || options.autoPopup !== false
|
|
|
+ const once = !!(options && options.once)
|
|
|
+ const priority = options && typeof options.priority === 'number' ? options.priority : 0
|
|
|
+ const contentKey = options && options.contentKey ? options.contentKey : ''
|
|
|
+
|
|
|
+ if (!autoPopup) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (once && contentKey && this.shownContentCardKeys[contentKey]) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (this.state.contentCardVisible && priority < this.currentContentCardPriority) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
this.clearContentCardTimer()
|
|
|
this.setState({
|
|
|
contentCardVisible: true,
|
|
|
@@ -1886,8 +1927,13 @@ export class MapEngine {
|
|
|
contentCardBody: body,
|
|
|
contentCardFxClass: motionClass,
|
|
|
}, true)
|
|
|
+ this.currentContentCardPriority = priority
|
|
|
+ if (once && contentKey) {
|
|
|
+ this.shownContentCardKeys[contentKey] = true
|
|
|
+ }
|
|
|
this.contentCardTimer = setTimeout(() => {
|
|
|
this.contentCardTimer = 0
|
|
|
+ this.currentContentCardPriority = 0
|
|
|
this.setState({
|
|
|
contentCardVisible: false,
|
|
|
contentCardFxClass: '',
|
|
|
@@ -1897,6 +1943,7 @@ export class MapEngine {
|
|
|
|
|
|
closeContentCard(): void {
|
|
|
this.clearContentCardTimer()
|
|
|
+ this.currentContentCardPriority = 0
|
|
|
this.setState({
|
|
|
contentCardVisible: false,
|
|
|
contentCardFxClass: '',
|
|
|
@@ -1955,11 +2002,19 @@ export class MapEngine {
|
|
|
}
|
|
|
|
|
|
if (this.gameRuntime.state.status !== 'idle') {
|
|
|
- return
|
|
|
+ if (this.gameRuntime.state.status === 'finished' || this.gameRuntime.state.status === 'failed') {
|
|
|
+ const reloadedResult = this.loadGameDefinitionFromCourse()
|
|
|
+ if (!reloadedResult || !this.gameRuntime.state) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ return
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
this.feedbackDirector.reset()
|
|
|
this.resetTransientGameUiState()
|
|
|
+ this.resetSessionContentExperienceState()
|
|
|
this.clearStartSessionResidue()
|
|
|
|
|
|
if (!this.locationController.listening) {
|
|
|
@@ -1985,9 +2040,10 @@ export class MapEngine {
|
|
|
}
|
|
|
|
|
|
this.courseOverlayVisible = true
|
|
|
+ const gameModeText = this.gameMode === 'score-o' ? '积分赛' : '顺序打点'
|
|
|
const defaultStatusText = this.currentGpsPoint
|
|
|
- ? `顺序打点已开始 (${this.buildVersion})`
|
|
|
- : `顺序打点已开始,GPS定位启动中 (${this.buildVersion})`
|
|
|
+ ? `${gameModeText}已开始 (${this.buildVersion})`
|
|
|
+ : `${gameModeText}已开始,GPS定位启动中 (${this.buildVersion})`
|
|
|
this.commitGameResult(gameResult, defaultStatusText)
|
|
|
}
|
|
|
|
|
|
@@ -2000,6 +2056,7 @@ export class MapEngine {
|
|
|
if (!this.courseData) {
|
|
|
this.clearGameRuntime()
|
|
|
this.resetTransientGameUiState()
|
|
|
+ this.resetSessionContentExperienceState()
|
|
|
this.feedbackDirector.handleEffects([{ type: 'session_cancelled' }])
|
|
|
this.setState({
|
|
|
gpsTracking: false,
|
|
|
@@ -2012,6 +2069,7 @@ export class MapEngine {
|
|
|
|
|
|
this.loadGameDefinitionFromCourse()
|
|
|
this.resetTransientGameUiState()
|
|
|
+ this.resetSessionContentExperienceState()
|
|
|
this.feedbackDirector.handleEffects([{ type: 'session_cancelled' }])
|
|
|
this.setState({
|
|
|
gpsTracking: false,
|
|
|
@@ -2384,6 +2442,7 @@ export class MapEngine {
|
|
|
this.configSchemaVersion = config.configSchemaVersion
|
|
|
this.configVersion = config.configVersion
|
|
|
this.controlScoreOverrides = config.controlScoreOverrides
|
|
|
+ this.controlContentOverrides = config.controlContentOverrides
|
|
|
this.defaultControlScore = config.defaultControlScore
|
|
|
this.gameMode = config.gameMode
|
|
|
this.punchPolicy = config.punchPolicy
|