完善文创展示控制与结果层基础

This commit is contained in:
2026-03-27 09:41:48 +08:00
parent 33edfba392
commit 0e025c3426
14 changed files with 1153 additions and 38 deletions

View File

@@ -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