1
This commit is contained in:
@@ -229,7 +229,92 @@ describe('buildBattlePlan', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('does not turn recovery fallback into a random player attack', () => {
|
||||
it('keeps battle_attack_basic as a single basic attack instead of randomly selecting another skill', () => {
|
||||
const state = {
|
||||
...createBaseState(),
|
||||
playerMana: 20,
|
||||
sceneHostileNpcs: [
|
||||
{
|
||||
id: 'monster-1',
|
||||
name: '山狼',
|
||||
action: '压低身体',
|
||||
description: '测试敌人',
|
||||
animation: 'idle' as const,
|
||||
xMeters: 3,
|
||||
yOffset: 0,
|
||||
facing: 'left' as const,
|
||||
attackRange: 1,
|
||||
speed: 1,
|
||||
hp: 80,
|
||||
maxHp: 80,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const plan = buildBattlePlan({
|
||||
state,
|
||||
option: {
|
||||
...createBattleOption(),
|
||||
functionId: 'battle_attack_basic',
|
||||
},
|
||||
character: createTestCharacter(),
|
||||
totalSequenceMs: 900,
|
||||
turnVisualMs: 820,
|
||||
resetStageMs: 260,
|
||||
minTurnCount: 1,
|
||||
});
|
||||
|
||||
const playerTurns = plan.turns.filter((turn) => turn.actor === 'player');
|
||||
|
||||
expect(playerTurns).toHaveLength(1);
|
||||
expect(playerTurns[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
selectedSkillId: 'battle-basic-attack',
|
||||
}),
|
||||
);
|
||||
expect(plan.finalState.playerMana).toBe(state.playerMana);
|
||||
});
|
||||
|
||||
it('resolves one full speed-ordered round when combat continues', () => {
|
||||
const state = {
|
||||
...createBaseState(),
|
||||
sceneHostileNpcs: [
|
||||
{
|
||||
id: 'monster-1',
|
||||
name: '山狼',
|
||||
action: '压低身体',
|
||||
description: '测试敌人',
|
||||
animation: 'idle' as const,
|
||||
xMeters: 3,
|
||||
yOffset: 0,
|
||||
facing: 'left' as const,
|
||||
attackRange: 1,
|
||||
speed: 1,
|
||||
hp: 120,
|
||||
maxHp: 120,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const plan = buildBattlePlan({
|
||||
state,
|
||||
option: {
|
||||
...createBattleOption(),
|
||||
functionId: 'battle_attack_basic',
|
||||
},
|
||||
character: createTestCharacter(),
|
||||
totalSequenceMs: 6000,
|
||||
turnVisualMs: 820,
|
||||
resetStageMs: 260,
|
||||
minTurnCount: 6,
|
||||
});
|
||||
|
||||
expect(plan.turns.map((turn) => turn.actor)).toEqual(['player', 'monster']);
|
||||
expect(plan.finalState.inBattle).toBe(true);
|
||||
expect(plan.finalState.sceneHostileNpcs[0]?.hp).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('keeps recovery as a player turn without converting it into an attack', () => {
|
||||
const state = {
|
||||
...createBaseState(),
|
||||
playerHp: 40,
|
||||
@@ -265,8 +350,77 @@ describe('buildBattlePlan', () => {
|
||||
minTurnCount: 1,
|
||||
});
|
||||
|
||||
expect(plan.turns.some((turn) => turn.actor === 'player')).toBe(false);
|
||||
expect(plan.preparedState.playerHp).toBeGreaterThan(state.playerHp);
|
||||
expect(plan.preparedState.playerMana).toBeGreaterThan(state.playerMana);
|
||||
const playerTurn = plan.turns.find((turn) => turn.actor === 'player');
|
||||
|
||||
expect(playerTurn).toEqual(
|
||||
expect.objectContaining({
|
||||
actor: 'player',
|
||||
actionKind: 'recover',
|
||||
selectedSkillId: null,
|
||||
damage: 0,
|
||||
}),
|
||||
);
|
||||
expect(plan.finalState.playerHp).toBeGreaterThan(state.playerHp);
|
||||
expect(plan.finalState.playerMana).toBeGreaterThan(state.playerMana);
|
||||
});
|
||||
|
||||
it('includes companion turns in fight mode and orders the round by speed', () => {
|
||||
const state = {
|
||||
...createBaseState(),
|
||||
currentNpcBattleMode: 'fight' as const,
|
||||
companions: [
|
||||
{
|
||||
npcId: 'companion-1',
|
||||
characterId: 'archer-hero',
|
||||
joinedAtAffinity: 10,
|
||||
hp: 60,
|
||||
maxHp: 60,
|
||||
mana: 20,
|
||||
maxMana: 20,
|
||||
skillCooldowns: {},
|
||||
},
|
||||
],
|
||||
sceneHostileNpcs: [
|
||||
{
|
||||
id: 'monster-1',
|
||||
name: '山狼',
|
||||
action: '压低身体',
|
||||
description: '测试敌人',
|
||||
animation: 'idle' as const,
|
||||
xMeters: 3,
|
||||
yOffset: 0,
|
||||
facing: 'left' as const,
|
||||
attackRange: 1,
|
||||
speed: 0.5,
|
||||
hp: 120,
|
||||
maxHp: 120,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const plan = buildBattlePlan({
|
||||
state,
|
||||
option: {
|
||||
...createBattleOption(),
|
||||
functionId: 'battle_attack_basic',
|
||||
},
|
||||
character: createTestCharacter(),
|
||||
totalSequenceMs: 6000,
|
||||
turnVisualMs: 820,
|
||||
resetStageMs: 260,
|
||||
minTurnCount: 6,
|
||||
});
|
||||
|
||||
expect(plan.turns.map((turn) => turn.actor)).toEqual([
|
||||
'companion',
|
||||
'player',
|
||||
'monster',
|
||||
]);
|
||||
expect(plan.turns[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
actor: 'companion',
|
||||
companionNpcId: 'companion-1',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -185,4 +185,62 @@ describe('escapeFlow', () => {
|
||||
expect(result.scrollWorld).toBe(false);
|
||||
expect(result.playerFacing).toBe('right');
|
||||
});
|
||||
|
||||
it('plays left exit and right-facing entry when escape targets a scene start', async () => {
|
||||
const state = {
|
||||
...createState(),
|
||||
currentScenePreset: {
|
||||
id: 'scene-bridge',
|
||||
name: 'Bridge',
|
||||
description: 'Bridge',
|
||||
imageSrc: '/bridge.png',
|
||||
worldType: WorldType.WUXIA,
|
||||
connectedSceneIds: [],
|
||||
connections: [],
|
||||
npcs: [],
|
||||
treasureHints: [],
|
||||
},
|
||||
};
|
||||
const targetScene = {
|
||||
...state.currentScenePreset!,
|
||||
id: 'scene-east',
|
||||
name: 'East Street',
|
||||
};
|
||||
const option = {
|
||||
...createEscapeOption(),
|
||||
runtimePayload: {
|
||||
escapeTargetSceneId: targetScene.id,
|
||||
escapeEntry: 'from_left',
|
||||
},
|
||||
};
|
||||
const finalState = buildEscapeAfterSequence(state, option, targetScene);
|
||||
const committedStates: GameState[] = [];
|
||||
|
||||
const result = await playEscapeSequenceWithStorySync({
|
||||
setGameState: (nextState: GameState) => {
|
||||
committedStates.push(nextState);
|
||||
},
|
||||
state,
|
||||
option,
|
||||
finalState,
|
||||
sleepMs: async () => {
|
||||
await Promise.resolve();
|
||||
},
|
||||
});
|
||||
|
||||
expect(committedStates[0]).toEqual(expect.objectContaining({
|
||||
playerFacing: 'left',
|
||||
animationState: AnimationState.RUN,
|
||||
scrollWorld: true,
|
||||
}));
|
||||
expect(committedStates.some((committedState) =>
|
||||
committedState.currentScenePreset?.id === 'scene-east' &&
|
||||
committedState.playerX < 0 &&
|
||||
committedState.playerFacing === 'right',
|
||||
)).toBe(true);
|
||||
expect(result.currentScenePreset?.id).toBe('scene-east');
|
||||
expect(result.playerX).toBe(0);
|
||||
expect(result.playerFacing).toBe('right');
|
||||
expect(result.scrollWorld).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
import type { Dispatch, SetStateAction } from 'react';
|
||||
|
||||
import {
|
||||
buildEncounterEntryState,
|
||||
hasEncounterEntity,
|
||||
interpolateEncounterTransitionState,
|
||||
} from '../../data/encounterTransition';
|
||||
import {
|
||||
getFacingTowardPlayer,
|
||||
settleHostileNpcAnimations,
|
||||
} from '../../data/hostileNpcs';
|
||||
import {
|
||||
CALL_OUT_ENTRY_X_METERS,
|
||||
createSceneEncounterPreview,
|
||||
resolveSceneEncounterPreview,
|
||||
} from '../../data/sceneEncounterPreviews';
|
||||
import { getFunctionEffect } from '../../data/stateFunctions';
|
||||
import {
|
||||
AnimationState,
|
||||
@@ -15,6 +25,9 @@ import {
|
||||
const ESCAPE_RUN_MS = 5000;
|
||||
const ESCAPE_TICK_MS = 250;
|
||||
const ESCAPE_TURN_PAUSE_MS = 180;
|
||||
const ESCAPE_ENTRY_MS = 900;
|
||||
const ESCAPE_ENTRY_TICK_MS = 90;
|
||||
const ESCAPE_PLAYER_ENTRY_X = -1.4;
|
||||
|
||||
export type EscapePlaybackSync = {
|
||||
waitForStoryResponse?: Promise<void>;
|
||||
@@ -22,7 +35,7 @@ export type EscapePlaybackSync = {
|
||||
|
||||
type SetGameStateFn = Dispatch<SetStateAction<GameState>> | ((state: GameState) => void);
|
||||
|
||||
function sleep(ms: number) {
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise(resolve => window.setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
@@ -35,6 +48,10 @@ function resetCombatPresentation(monsters: SceneHostileNpc[], playerX: number) {
|
||||
}));
|
||||
}
|
||||
|
||||
function lerpMeters(start: number, end: number, progress: number) {
|
||||
return Number((start + ((end - start) * progress)).toFixed(2));
|
||||
}
|
||||
|
||||
export function getEscapeSettlePlayerX(state: GameState, option: StoryOption) {
|
||||
const escapeDistance = getFunctionEffect(option.functionId).escapeDistance ?? 5;
|
||||
const settleOffset = Math.max(1, Math.min(1.4, escapeDistance * 0.24));
|
||||
@@ -44,18 +61,22 @@ export function getEscapeSettlePlayerX(state: GameState, option: StoryOption) {
|
||||
export function buildEscapeAfterSequence(
|
||||
state: GameState,
|
||||
option: StoryOption,
|
||||
nextScenePreset: GameState['currentScenePreset'] = state.currentScenePreset,
|
||||
) {
|
||||
const escapePlayerX = getEscapeSettlePlayerX(state, option);
|
||||
|
||||
return {
|
||||
const shouldResetToSceneStart =
|
||||
nextScenePreset?.id !== state.currentScenePreset?.id ||
|
||||
option.runtimePayload?.escapeReturnToSceneStart === true;
|
||||
const baseState = {
|
||||
...state,
|
||||
currentScenePreset: nextScenePreset ?? state.currentScenePreset,
|
||||
currentEncounter: null,
|
||||
npcInteractionActive: false,
|
||||
currentBattleNpcId: null,
|
||||
currentNpcBattleMode: null,
|
||||
currentNpcBattleOutcome: null,
|
||||
sceneHostileNpcs: [],
|
||||
playerX: escapePlayerX,
|
||||
playerX: shouldResetToSceneStart ? 0 : escapePlayerX,
|
||||
playerFacing: 'right' as const,
|
||||
animationState: AnimationState.IDLE,
|
||||
playerActionMode: 'idle' as const,
|
||||
@@ -66,6 +87,66 @@ export function buildEscapeAfterSequence(
|
||||
sparPlayerHpBefore: null,
|
||||
sparPlayerMaxHpBefore: null,
|
||||
} satisfies GameState;
|
||||
const previewState = shouldResetToSceneStart
|
||||
? ({
|
||||
...baseState,
|
||||
...createSceneEncounterPreview(baseState),
|
||||
} satisfies GameState)
|
||||
: baseState;
|
||||
|
||||
return hasEncounterEntity(previewState)
|
||||
? resolveSceneEncounterPreview(previewState)
|
||||
: baseState;
|
||||
}
|
||||
|
||||
async function playEscapeEntrySequence(params: {
|
||||
setGameState: SetGameStateFn;
|
||||
finalState: GameState;
|
||||
sleepMs: (ms: number) => Promise<void>;
|
||||
}) {
|
||||
const entryState = buildEncounterEntryState(
|
||||
{
|
||||
...params.finalState,
|
||||
playerX: ESCAPE_PLAYER_ENTRY_X,
|
||||
playerFacing: 'right',
|
||||
animationState: AnimationState.RUN,
|
||||
playerActionMode: 'idle',
|
||||
scrollWorld: true,
|
||||
},
|
||||
CALL_OUT_ENTRY_X_METERS,
|
||||
);
|
||||
const runTicks = Math.max(1, Math.ceil(ESCAPE_ENTRY_MS / ESCAPE_ENTRY_TICK_MS));
|
||||
const tickDurationMs = Math.max(1, Math.round(ESCAPE_ENTRY_MS / runTicks));
|
||||
let currentState = entryState;
|
||||
|
||||
params.setGameState(currentState);
|
||||
|
||||
for (let tick = 1; tick <= runTicks; tick += 1) {
|
||||
const progress = tick / runTicks;
|
||||
const interpolatedState = interpolateEncounterTransitionState(
|
||||
entryState,
|
||||
params.finalState,
|
||||
progress,
|
||||
);
|
||||
currentState = {
|
||||
...interpolatedState,
|
||||
playerX: lerpMeters(
|
||||
ESCAPE_PLAYER_ENTRY_X,
|
||||
params.finalState.playerX,
|
||||
progress,
|
||||
),
|
||||
playerFacing: 'right',
|
||||
animationState:
|
||||
progress < 1 ? AnimationState.RUN : params.finalState.animationState,
|
||||
playerActionMode: 'idle',
|
||||
scrollWorld: progress < 1,
|
||||
};
|
||||
params.setGameState(currentState);
|
||||
await params.sleepMs(tickDurationMs);
|
||||
}
|
||||
|
||||
params.setGameState(params.finalState);
|
||||
return params.finalState;
|
||||
}
|
||||
|
||||
export async function playEscapeSequenceWithStorySync(params: {
|
||||
@@ -93,6 +174,9 @@ export async function playEscapeSequenceWithStorySync(params: {
|
||||
const settlePlayerX = finalState.playerX;
|
||||
let storyResponseReady = !sync?.waitForStoryResponse;
|
||||
let elapsedMs = 0;
|
||||
const shouldPlayEntry =
|
||||
finalState.currentScenePreset?.id !== state.currentScenePreset?.id ||
|
||||
option.runtimePayload?.escapeReturnToSceneStart === true;
|
||||
|
||||
void sync?.waitForStoryResponse?.then(() => {
|
||||
storyResponseReady = true;
|
||||
@@ -127,19 +211,39 @@ export async function playEscapeSequenceWithStorySync(params: {
|
||||
await sleepMs(ESCAPE_TICK_MS);
|
||||
}
|
||||
|
||||
currentState = {
|
||||
...finalState,
|
||||
playerX: settlePlayerX,
|
||||
playerFacing: 'left',
|
||||
animationState: AnimationState.IDLE,
|
||||
playerActionMode: 'idle',
|
||||
activeCombatEffects: [],
|
||||
scrollWorld: false,
|
||||
sceneHostileNpcs: resetCombatPresentation(finalState.sceneHostileNpcs, settlePlayerX),
|
||||
};
|
||||
const settledExitState: GameState = shouldPlayEntry
|
||||
? {
|
||||
...finalState,
|
||||
playerX: 0,
|
||||
playerFacing: 'right' as const,
|
||||
animationState: AnimationState.IDLE,
|
||||
playerActionMode: 'idle' as const,
|
||||
activeCombatEffects: [],
|
||||
scrollWorld: false,
|
||||
sceneHostileNpcs: resetCombatPresentation(finalState.sceneHostileNpcs, 0),
|
||||
}
|
||||
: {
|
||||
...finalState,
|
||||
playerX: settlePlayerX,
|
||||
playerFacing: 'left' as const,
|
||||
animationState: AnimationState.IDLE,
|
||||
playerActionMode: 'idle' as const,
|
||||
activeCombatEffects: [],
|
||||
scrollWorld: false,
|
||||
sceneHostileNpcs: resetCombatPresentation(finalState.sceneHostileNpcs, settlePlayerX),
|
||||
};
|
||||
currentState = settledExitState;
|
||||
setGameState(currentState);
|
||||
await sleepMs(ESCAPE_TURN_PAUSE_MS);
|
||||
|
||||
if (shouldPlayEntry) {
|
||||
return playEscapeEntrySequence({
|
||||
setGameState,
|
||||
finalState: settledExitState,
|
||||
sleepMs,
|
||||
});
|
||||
}
|
||||
|
||||
currentState = {
|
||||
...currentState,
|
||||
playerFacing: 'right',
|
||||
|
||||
@@ -52,6 +52,20 @@ function sleep(ms: number) {
|
||||
}
|
||||
|
||||
function getSkillById(character: Character, skillId: string) {
|
||||
if (skillId === 'battle-basic-attack') {
|
||||
return {
|
||||
id: 'battle-basic-attack',
|
||||
name: '普通攻击',
|
||||
animation: AnimationState.ATTACK,
|
||||
damage: 0,
|
||||
manaCost: 0,
|
||||
cooldownTurns: 0,
|
||||
range: 1,
|
||||
style: 'steady',
|
||||
delivery: 'melee',
|
||||
} satisfies CharacterSkillDefinition;
|
||||
}
|
||||
|
||||
return character.skills.find(skill => skill.id === skillId) ?? null;
|
||||
}
|
||||
|
||||
@@ -192,6 +206,47 @@ async function playBattleSequence(params: CombatPlaybackParams & {
|
||||
};
|
||||
setGameState(currentState);
|
||||
|
||||
if (step.actionKind === 'recover') {
|
||||
currentState = {
|
||||
...currentState,
|
||||
animationState: AnimationState.IDLE,
|
||||
playerActionMode: 'idle',
|
||||
playerHp: step.playerHpAfterAction,
|
||||
playerMana: step.playerManaAfterAction,
|
||||
playerSkillCooldowns: step.appliedCooldowns,
|
||||
activeCombatEffects: [],
|
||||
};
|
||||
setGameState(currentState);
|
||||
await sleep(resetStageMs);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (step.actionKind === 'inventory') {
|
||||
currentState = {
|
||||
...currentState,
|
||||
animationState: AnimationState.ACQUIRE,
|
||||
playerActionMode: 'idle',
|
||||
playerHp: step.playerHpAfterAction,
|
||||
playerMana: step.playerManaAfterAction,
|
||||
playerSkillCooldowns: step.appliedCooldowns,
|
||||
playerInventory:
|
||||
step.playerInventoryAfterAction ?? currentState.playerInventory,
|
||||
activeCombatEffects: [],
|
||||
};
|
||||
setGameState(currentState);
|
||||
await sleep(Math.max(180, resetStageMs));
|
||||
|
||||
currentState = {
|
||||
...currentState,
|
||||
animationState: AnimationState.IDLE,
|
||||
playerActionMode: 'idle',
|
||||
activeCombatEffects: [],
|
||||
};
|
||||
setGameState(currentState);
|
||||
await sleep(resetStageMs);
|
||||
continue;
|
||||
}
|
||||
|
||||
const skill = step.selectedSkillId ? getSkillById(character, step.selectedSkillId) : null;
|
||||
if (!skill) {
|
||||
await sleep(resetStageMs);
|
||||
@@ -353,7 +408,7 @@ async function playBattleSequence(params: CombatPlaybackParams & {
|
||||
};
|
||||
setGameState(currentState);
|
||||
|
||||
if (step.delivery === 'melee' && step.strikeOffsetX > 0) {
|
||||
if (step.delivery === 'melee' && Math.abs(step.strikeOffsetX) > 0.01) {
|
||||
currentState = {
|
||||
...currentState,
|
||||
companions: updateCompanionState(
|
||||
@@ -740,4 +795,3 @@ export function createCombatPlayback(params: CombatPlaybackParams) {
|
||||
playResolvedChoice,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -240,6 +240,50 @@ describe('buildResolvedChoiceState', () => {
|
||||
expect(resolved.afterSequence.playerFacing).toBe('right');
|
||||
});
|
||||
|
||||
it('moves escape result to explicit target scene and resets player to scene start', () => {
|
||||
const state = {
|
||||
...createBaseState(),
|
||||
currentScenePreset: scenes[0] as GameState['currentScenePreset'],
|
||||
sceneHostileNpcs: [
|
||||
{
|
||||
id: 'monster-1',
|
||||
name: 'Wolf',
|
||||
action: 'growls',
|
||||
description: 'A wolf',
|
||||
animation: 'idle' as const,
|
||||
xMeters: 3,
|
||||
yOffset: 0,
|
||||
facing: 'left' as const,
|
||||
attackRange: 1,
|
||||
speed: 1,
|
||||
hp: 10,
|
||||
maxHp: 10,
|
||||
renderKind: 'npc' as const,
|
||||
},
|
||||
],
|
||||
};
|
||||
const option = {
|
||||
...createOption('battle_escape_breakout'),
|
||||
runtimePayload: {
|
||||
escapeTargetSceneId: 'scene-3',
|
||||
escapeEntry: 'from_left',
|
||||
},
|
||||
};
|
||||
|
||||
const resolved = buildResolvedChoiceState({
|
||||
state,
|
||||
option,
|
||||
character: createTestCharacter(),
|
||||
buildBattlePlan: vi.fn(),
|
||||
});
|
||||
|
||||
expect(resolved.optionKind).toBe('escape');
|
||||
expect(resolved.afterSequence.currentScenePreset?.id).toBe('scene-3');
|
||||
expect(resolved.afterSequence.playerX).toBe(0);
|
||||
expect(resolved.afterSequence.playerFacing).toBe('right');
|
||||
expect(resolved.afterSequence.inBattle).toBe(false);
|
||||
});
|
||||
|
||||
it('keeps idle follow-up generation separate from combat planning', () => {
|
||||
const state = {
|
||||
...createBaseState(),
|
||||
|
||||
@@ -58,15 +58,17 @@ function getSceneTargetForFunction(
|
||||
): GameState['currentScenePreset'] {
|
||||
if (!worldType) return currentScenePreset;
|
||||
|
||||
if (option.functionId === 'idle_travel_next_scene') {
|
||||
const targetSceneId =
|
||||
typeof option.runtimePayload?.targetSceneId === 'string'
|
||||
? option.runtimePayload.targetSceneId
|
||||
const targetSceneId =
|
||||
typeof option.runtimePayload?.targetSceneId === 'string'
|
||||
? option.runtimePayload.targetSceneId
|
||||
: typeof option.runtimePayload?.escapeTargetSceneId === 'string'
|
||||
? option.runtimePayload.escapeTargetSceneId
|
||||
: null;
|
||||
if (targetSceneId) {
|
||||
return getScenePresetById(worldType, targetSceneId) ?? currentScenePreset;
|
||||
}
|
||||
if (targetSceneId) {
|
||||
return getScenePresetById(worldType, targetSceneId) ?? currentScenePreset;
|
||||
}
|
||||
|
||||
if (option.functionId === 'idle_travel_next_scene') {
|
||||
return getTravelScenePreset(worldType, currentScenePreset?.id) ?? currentScenePreset;
|
||||
}
|
||||
|
||||
@@ -114,7 +116,7 @@ export function buildResolvedChoiceState(params: {
|
||||
return {
|
||||
optionKind,
|
||||
battlePlan: null,
|
||||
afterSequence: buildEscapeAfterSequence(state, option),
|
||||
afterSequence: buildEscapeAfterSequence(state, option, nextScenePreset),
|
||||
} satisfies ResolvedChoiceState;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user