Add config-driven game host updates
This commit is contained in:
@@ -33,6 +33,10 @@ function getScoreControls(definition: GameDefinition): GameControl[] {
|
||||
return definition.controls.filter((control) => control.kind === 'control')
|
||||
}
|
||||
|
||||
function getControlScore(control: GameControl): number {
|
||||
return control.kind === 'control' && typeof control.score === 'number' ? control.score : 0
|
||||
}
|
||||
|
||||
function getRemainingScoreControls(definition: GameDefinition, state: GameSessionState): GameControl[] {
|
||||
return getScoreControls(definition).filter((control) => !state.completedControlIds.includes(control.id))
|
||||
}
|
||||
@@ -112,6 +116,46 @@ function getFocusedTarget(
|
||||
return null
|
||||
}
|
||||
|
||||
function resolveInteractiveTarget(
|
||||
definition: GameDefinition,
|
||||
modeState: ScoreOModeState,
|
||||
primaryTarget: GameControl | null,
|
||||
focusedTarget: GameControl | null,
|
||||
): GameControl | null {
|
||||
if (modeState.phase === 'start') {
|
||||
return primaryTarget
|
||||
}
|
||||
|
||||
if (definition.requiresFocusSelection) {
|
||||
return focusedTarget
|
||||
}
|
||||
|
||||
return focusedTarget || primaryTarget
|
||||
}
|
||||
|
||||
function getNearestInRangeControl(
|
||||
controls: GameControl[],
|
||||
referencePoint: LonLatPoint,
|
||||
radiusMeters: number,
|
||||
): GameControl | null {
|
||||
let nearest: GameControl | null = null
|
||||
let nearestDistance = Number.POSITIVE_INFINITY
|
||||
|
||||
for (const control of controls) {
|
||||
const distance = getApproxDistanceMeters(control.point, referencePoint)
|
||||
if (distance > radiusMeters) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (!nearest || distance < nearestDistance) {
|
||||
nearest = control
|
||||
nearestDistance = distance
|
||||
}
|
||||
}
|
||||
|
||||
return nearest
|
||||
}
|
||||
|
||||
function getGuidanceState(definition: GameDefinition, distanceMeters: number): GameSessionState['guidanceState'] {
|
||||
if (distanceMeters <= definition.punchRadiusMeters) {
|
||||
return 'ready'
|
||||
@@ -166,14 +210,15 @@ function buildPunchHintText(
|
||||
|
||||
const modeState = getModeState(state)
|
||||
if (modeState.phase === 'controls' || modeState.phase === 'finish') {
|
||||
if (!focusedTarget) {
|
||||
if (definition.requiresFocusSelection && !focusedTarget) {
|
||||
return modeState.phase === 'finish'
|
||||
? '点击地图选中终点后结束比赛'
|
||||
: '点击地图选中一个目标点'
|
||||
}
|
||||
|
||||
const targetLabel = getDisplayTargetLabel(focusedTarget)
|
||||
if (state.inRangeControlId === focusedTarget.id) {
|
||||
const displayTarget = resolveInteractiveTarget(definition, modeState, primaryTarget, focusedTarget)
|
||||
const targetLabel = getDisplayTargetLabel(displayTarget)
|
||||
if (displayTarget && state.inRangeControlId === displayTarget.id) {
|
||||
return definition.punchPolicy === 'enter'
|
||||
? `${targetLabel}内,自动打点中`
|
||||
: `${targetLabel}内,可点击打点`
|
||||
@@ -258,7 +303,7 @@ function buildPresentation(definition: GameDefinition, state: GameSessionState):
|
||||
.filter((control) => typeof control.sequence === 'number')
|
||||
.map((control) => control.sequence as number)
|
||||
const revealFullCourse = completedStart
|
||||
const interactiveTarget = modeState.phase === 'start' ? primaryTarget : focusedTarget
|
||||
const interactiveTarget = resolveInteractiveTarget(definition, modeState, primaryTarget, focusedTarget)
|
||||
const punchButtonEnabled = running
|
||||
&& definition.punchPolicy === 'enter-confirm'
|
||||
&& !!interactiveTarget
|
||||
@@ -287,6 +332,8 @@ function buildPresentation(definition: GameDefinition, state: GameSessionState):
|
||||
completedLegIndices: [],
|
||||
completedControlIds: completedControls.map((control) => control.id),
|
||||
completedControlSequences,
|
||||
skippedControlIds: [],
|
||||
skippedControlSequences: [],
|
||||
}
|
||||
|
||||
const hudPresentation: HudPresentationState = {
|
||||
@@ -348,7 +395,9 @@ function applyCompletion(
|
||||
completedControlIds,
|
||||
currentTargetControlId: null,
|
||||
inRangeControlId: null,
|
||||
score: getScoreControls(definition).filter((item) => completedControlIds.includes(item.id)).length,
|
||||
score: getScoreControls(definition)
|
||||
.filter((item) => completedControlIds.includes(item.id))
|
||||
.reduce((sum, item) => sum + getControlScore(item), 0),
|
||||
status: control.kind === 'finish' ? 'finished' : state.status,
|
||||
guidanceState: 'searching',
|
||||
}
|
||||
@@ -401,6 +450,7 @@ export class ScoreORule implements RulePlugin {
|
||||
startedAt: null,
|
||||
endedAt: null,
|
||||
completedControlIds: [],
|
||||
skippedControlIds: [],
|
||||
currentTargetControlId: startControl ? startControl.id : null,
|
||||
inRangeControlId: null,
|
||||
score: 0,
|
||||
@@ -481,12 +531,16 @@ export class ScoreORule implements RulePlugin {
|
||||
guidanceTarget = focusedTarget || nextPrimaryTarget
|
||||
if (focusedTarget && getApproxDistanceMeters(focusedTarget.point, referencePoint) <= definition.punchRadiusMeters) {
|
||||
punchTarget = focusedTarget
|
||||
} else if (!definition.requiresFocusSelection) {
|
||||
punchTarget = getNearestInRangeControl(remainingControls, referencePoint, definition.punchRadiusMeters)
|
||||
}
|
||||
} else if (modeState.phase === 'finish') {
|
||||
nextPrimaryTarget = getFinishControl(definition)
|
||||
guidanceTarget = focusedTarget || nextPrimaryTarget
|
||||
if (focusedTarget && getApproxDistanceMeters(focusedTarget.point, referencePoint) <= definition.punchRadiusMeters) {
|
||||
punchTarget = focusedTarget
|
||||
} else if (!definition.requiresFocusSelection && nextPrimaryTarget && getApproxDistanceMeters(nextPrimaryTarget.point, referencePoint) <= definition.punchRadiusMeters) {
|
||||
punchTarget = nextPrimaryTarget
|
||||
}
|
||||
} else if (targetControl) {
|
||||
guidanceTarget = targetControl
|
||||
@@ -556,7 +610,7 @@ export class ScoreORule implements RulePlugin {
|
||||
|
||||
if (event.type === 'punch_requested') {
|
||||
const focusedTarget = getFocusedTarget(definition, state)
|
||||
if ((modeState.phase === 'controls' || modeState.phase === 'finish') && !focusedTarget) {
|
||||
if (definition.requiresFocusSelection && (modeState.phase === 'controls' || modeState.phase === 'finish') && !focusedTarget) {
|
||||
return {
|
||||
nextState: state,
|
||||
presentation: buildPresentation(definition, state),
|
||||
@@ -569,7 +623,7 @@ export class ScoreORule implements RulePlugin {
|
||||
controlToPunch = definition.controls.find((control) => control.id === state.inRangeControlId) || null
|
||||
}
|
||||
|
||||
if (!controlToPunch || (focusedTarget && controlToPunch.id !== focusedTarget.id)) {
|
||||
if (!controlToPunch || (definition.requiresFocusSelection && focusedTarget && controlToPunch.id !== focusedTarget.id)) {
|
||||
return {
|
||||
nextState: state,
|
||||
presentation: buildPresentation(definition, state),
|
||||
|
||||
Reference in New Issue
Block a user