Rework story engine flow and reorganize project docs
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
2026-04-06 23:19:00 +08:00
parent d678929064
commit ddcb5d5c8c
241 changed files with 19805 additions and 2478 deletions

View File

@@ -16,9 +16,9 @@ import {
} from '../../data/characterPresets';
import { getEquipmentBonuses } from '../../data/equipmentEffects';
import {
getClosestMonster,
getClosestHostileNpc,
getFacingTowardPlayer,
settleMonsterAnimations,
settleHostileNpcAnimations,
} from '../../data/hostileNpcs';
import { getFunctionEffect } from '../../data/stateFunctions';
import type {
@@ -27,7 +27,7 @@ import type {
CombatDelivery,
CompanionState,
GameState,
SceneMonster,
SceneHostileNpc,
StoryOption,
} from '../../types';
import {
@@ -209,7 +209,7 @@ function buildCombatTurnOrder(
});
});
state.sceneMonsters.forEach(monster => {
state.sceneHostileNpcs.forEach(monster => {
actorTimings.set(getCombatActorKey('monster', monster.id), {
actor: 'monster',
id: monster.id,
@@ -226,7 +226,7 @@ function buildCombatTurnOrder(
if (item.actor === 'companion') {
return state.companions.some(companion => companion.npcId === item.id && isCompanionAlive(companion));
}
return state.sceneMonsters.some(monster => monster.id === item.id && monster.hp > 0);
return state.sceneHostileNpcs.some(monster => monster.id === item.id && monster.hp > 0);
});
if (availableActors.length === 0) break;
@@ -278,7 +278,7 @@ function tickSkillCooldowns(character: Character, cooldowns: Record<string, numb
);
}
export function getFacingForPlayer(playerX: number, monster: SceneMonster | null) {
export function getFacingForPlayer(playerX: number, monster: SceneHostileNpc | null) {
if (!monster) return 'right' as const;
return monster.xMeters >= playerX ? 'right' : 'left';
}
@@ -295,8 +295,8 @@ export function getSkillStrikeX(skill: CharacterSkillDefinition, attackerX: numb
: getMeleeStrikeX(attackerX, defenderX);
}
export function resetCombatPresentation(monsters: SceneMonster[], playerX: number) {
return settleMonsterAnimations(monsters).map(monster => ({
export function resetCombatPresentation(monsters: SceneHostileNpc[], playerX: number) {
return settleHostileNpcAnimations(monsters).map(monster => ({
...monster,
facing: getFacingTowardPlayer(monster.xMeters, playerX),
characterAnimation: undefined,
@@ -349,18 +349,12 @@ export function buildBattlePlan({
resetStageMs: number;
minTurnCount: number;
}): BattlePlan {
const resolvedSceneMonsters =
state.sceneMonsters.length > 0
? state.sceneMonsters
: (state.sceneHostileNpcs ?? []);
const battleState: GameState = {
...state,
sceneMonsters: resolvedSceneMonsters,
sceneHostileNpcs: resolvedSceneMonsters,
};
const targetMonster = getClosestMonster(
const targetMonster = getClosestHostileNpc(
battleState.playerX,
battleState.sceneMonsters,
battleState.sceneHostileNpcs,
);
if (!targetMonster) {
return {
@@ -369,7 +363,6 @@ export function buildBattlePlan({
finalState: {
...battleState,
inBattle: false,
sceneMonsters: [],
sceneHostileNpcs: [],
companions: resetCompanionCombatPresentation(state.companions),
animationState: AnimationState.IDLE,
@@ -398,7 +391,7 @@ export function buildBattlePlan({
cooldowns: Record<string, number>;
}>();
battleState.sceneMonsters.forEach(monster => {
battleState.sceneHostileNpcs.forEach(monster => {
const npcCharacterId = monster.encounter?.characterId ?? null;
const npcCharacter = npcCharacterId ? getCharacterById(npcCharacterId) : null;
if (!npcCharacter) return;
@@ -413,12 +406,8 @@ export function buildBattlePlan({
let simulatedState: GameState = {
...applyRecoveryEffectToState(battleState, character, option.functionId),
companions: resetCompanionCombatPresentation(battleState.companions),
sceneMonsters: resetCombatPresentation(
battleState.sceneMonsters,
battleState.playerX,
),
sceneHostileNpcs: resetCombatPresentation(
battleState.sceneMonsters,
battleState.sceneHostileNpcs,
battleState.playerX,
),
activeCombatEffects: [],
@@ -429,7 +418,7 @@ export function buildBattlePlan({
const turns: BattlePlanStep[] = [];
for (const [turnIndex, turn] of turnOrder.entries()) {
const currentTarget = getClosestMonster(simulatedState.playerX, simulatedState.sceneMonsters);
const currentTarget = getClosestHostileNpc(simulatedState.playerX, simulatedState.sceneHostileNpcs);
if (!currentTarget) break;
if (turn.actor === 'player') {
@@ -465,7 +454,7 @@ export function buildBattlePlan({
const damage = isNpcSpar ? 1 : damageResult!.damage;
const wouldEndSpar = isNpcSpar && currentTarget.hp - damage <= 1;
const resolvedMonsters = simulatedState.sceneMonsters.map(monster =>
const resolvedMonsters = simulatedState.sceneHostileNpcs.map(monster =>
monster.id === currentTarget.id
? {
...monster,
@@ -479,7 +468,7 @@ export function buildBattlePlan({
const remainingMonsters = defeated
? resolvedMonsters.filter(monster => !(monster.id === currentTarget.id && monster.hp <= 0))
: resolvedMonsters;
const nextTarget = getClosestMonster(originalPlayerX, remainingMonsters);
const nextTarget = getClosestHostileNpc(originalPlayerX, remainingMonsters);
simulatedState = {
...simulatedState,
@@ -494,7 +483,7 @@ export function buildBattlePlan({
activeCombatEffects: [],
playerMana: Math.max(0, simulatedState.playerMana - selectedSkill.manaCost),
playerSkillCooldowns: appliedCooldowns,
sceneMonsters: remainingMonsters.map(monster => ({
sceneHostileNpcs: remainingMonsters.map(monster => ({
...monster,
characterAnimation: undefined,
combatMode: undefined,
@@ -537,7 +526,7 @@ export function buildBattlePlan({
if (!companionCharacter) continue;
const companionX = getCompanionAnchorX(simulatedState.playerX, simulatedState.companions, companion.npcId);
const targetMonster = getClosestMonster(companionX, simulatedState.sceneMonsters);
const targetMonster = getClosestHostileNpc(companionX, simulatedState.sceneHostileNpcs);
if (!targetMonster) break;
const cooledDown = tickSkillCooldowns(companionCharacter, companion.skillCooldowns);
@@ -584,7 +573,7 @@ export function buildBattlePlan({
const damage = isNpcSpar ? 1 : damageResult!.damage;
const wouldEndSpar = isNpcSpar && targetMonster.hp - damage <= 1;
const resolvedMonsters = simulatedState.sceneMonsters.map(monster =>
const resolvedMonsters = simulatedState.sceneHostileNpcs.map(monster =>
monster.id === targetMonster.id
? {
...monster,
@@ -609,7 +598,7 @@ export function buildBattlePlan({
skillCooldowns: appliedCooldowns,
}),
),
sceneMonsters: remainingMonsters.map(monster => ({
sceneHostileNpcs: remainingMonsters.map(monster => ({
...monster,
characterAnimation: undefined,
combatMode: undefined,
@@ -643,7 +632,7 @@ export function buildBattlePlan({
continue;
}
const actingMonster = simulatedState.sceneMonsters.find(monster => monster.id === turn.id && monster.hp > 0);
const actingMonster = simulatedState.sceneHostileNpcs.find(monster => monster.id === turn.id && monster.hp > 0);
if (!actingMonster) continue;
const randomTarget = chooseRandomPartyTarget(simulatedState);
@@ -698,7 +687,7 @@ export function buildBattlePlan({
simulatedState = {
...damagedState,
companions: resetCompanionCombatPresentation(damagedState.companions),
sceneMonsters: simulatedState.sceneMonsters.map(monster => ({
sceneHostileNpcs: simulatedState.sceneHostileNpcs.map(monster => ({
...monster,
xMeters: monster.id === actingMonster.id ? originalMonsterX : monster.xMeters,
animation: 'idle' as const,
@@ -753,7 +742,7 @@ export function buildBattlePlan({
simulatedState = {
...damagedState,
companions: resetCompanionCombatPresentation(damagedState.companions),
sceneMonsters: simulatedState.sceneMonsters.map(monster => ({
sceneHostileNpcs: simulatedState.sceneHostileNpcs.map(monster => ({
...monster,
xMeters: monster.id === actingMonster.id ? originalMonsterX : monster.xMeters,
animation: 'idle' as const,
@@ -800,12 +789,8 @@ export function buildBattlePlan({
scrollWorld: false,
inBattle: simulatedState.currentNpcBattleOutcome === 'spar_complete'
? false
: simulatedState.sceneMonsters.length > 0,
sceneMonsters: resetCombatPresentation(simulatedState.sceneMonsters, simulatedState.playerX),
sceneHostileNpcs: resetCombatPresentation(
simulatedState.sceneMonsters,
simulatedState.playerX,
),
: simulatedState.sceneHostileNpcs.length > 0,
sceneHostileNpcs: resetCombatPresentation(simulatedState.sceneHostileNpcs, simulatedState.playerX),
},
};
}