128 lines
4.2 KiB
TypeScript
128 lines
4.2 KiB
TypeScript
import type {
|
|
ChapterState,
|
|
GameState,
|
|
StorySignal,
|
|
WorldMutation,
|
|
} from '../../types';
|
|
|
|
function dedupeStrings(values: Array<string | null | undefined>, limit = 6) {
|
|
return [...new Set(values.map((value) => value?.trim() ?? '').filter(Boolean))]
|
|
.slice(0, limit);
|
|
}
|
|
|
|
export function resolveWorldMutations(params: {
|
|
state: GameState;
|
|
signals: StorySignal[];
|
|
chapterState: ChapterState | null | undefined;
|
|
}) {
|
|
const currentSceneId = params.state.currentScenePreset?.id;
|
|
const activeThreadIds = params.state.storyEngineMemory?.activeThreadIds ?? [];
|
|
const mutations: WorldMutation[] = [];
|
|
|
|
if (currentSceneId && params.chapterState) {
|
|
mutations.push({
|
|
id: `mutation:scene:${currentSceneId}:${params.chapterState.stage}`,
|
|
mutationType: 'scene_text',
|
|
targetId: currentSceneId,
|
|
reason: `${params.chapterState.title}正在改写这片地界的表面气氛。`,
|
|
relatedThreadIds: params.chapterState.primaryThreadIds,
|
|
});
|
|
}
|
|
|
|
if (currentSceneId && params.signals.some((signal) => signal.signalType === 'win_battle')) {
|
|
mutations.push({
|
|
id: `mutation:pressure:${currentSceneId}:battle`,
|
|
mutationType: 'enemy_pressure',
|
|
targetId: currentSceneId,
|
|
reason: '这一带的敌意正在因交锋结果重新聚拢。',
|
|
relatedThreadIds: dedupeStrings(activeThreadIds, 4),
|
|
});
|
|
}
|
|
|
|
if (params.signals.some((signal) => signal.signalType === 'obtain_carrier')) {
|
|
mutations.push({
|
|
id: `mutation:attitude:${currentSceneId ?? 'scene'}:carrier`,
|
|
mutationType: 'npc_attitude',
|
|
targetId: currentSceneId ?? 'scene',
|
|
reason: '关键载体已经落到你手里,相关角色的口风会开始变化。',
|
|
relatedThreadIds: dedupeStrings(activeThreadIds, 4),
|
|
});
|
|
}
|
|
|
|
if (params.chapterState?.stage === 'climax' && currentSceneId) {
|
|
mutations.push({
|
|
id: `mutation:route:${currentSceneId}:climax`,
|
|
mutationType: 'route_unlock',
|
|
targetId: currentSceneId,
|
|
reason: '章节高潮逼近,新的通路或对峙点开始显影。',
|
|
relatedThreadIds: params.chapterState.primaryThreadIds,
|
|
});
|
|
}
|
|
|
|
return mutations;
|
|
}
|
|
|
|
export function applyWorldMutationsToGameState(params: {
|
|
state: GameState;
|
|
mutations: WorldMutation[];
|
|
}) {
|
|
const knownMutations = [
|
|
...(params.state.storyEngineMemory?.worldMutations ?? []),
|
|
...params.mutations,
|
|
];
|
|
if (knownMutations.length <= 0) {
|
|
return params.state;
|
|
}
|
|
|
|
const currentSceneId = params.state.currentScenePreset?.id ?? null;
|
|
const relevantMutations = currentSceneId
|
|
? knownMutations.filter((mutation) => mutation.targetId === currentSceneId)
|
|
: knownMutations;
|
|
const latestSceneMutation = relevantMutations
|
|
.filter((mutation) => mutation.mutationType === 'scene_text')
|
|
.at(-1);
|
|
const pressureMutationCount = relevantMutations.filter(
|
|
(mutation) => mutation.mutationType === 'enemy_pressure',
|
|
).length;
|
|
const attitudeMutation = relevantMutations
|
|
.filter((mutation) => mutation.mutationType === 'npc_attitude')
|
|
.at(-1);
|
|
const currentPressureLevel =
|
|
pressureMutationCount >= 3
|
|
? 'extreme'
|
|
: pressureMutationCount === 2
|
|
? 'high'
|
|
: pressureMutationCount === 1
|
|
? 'medium'
|
|
: params.state.currentScenePreset?.currentPressureLevel ?? 'low';
|
|
|
|
return {
|
|
...params.state,
|
|
currentScenePreset: params.state.currentScenePreset
|
|
? {
|
|
...params.state.currentScenePreset,
|
|
mutationStateText:
|
|
[latestSceneMutation?.reason, attitudeMutation?.reason]
|
|
.filter(Boolean)
|
|
.join(' ')
|
|
?? params.state.currentScenePreset.mutationStateText
|
|
?? null,
|
|
currentPressureLevel,
|
|
description: [
|
|
params.state.currentScenePreset.description,
|
|
latestSceneMutation?.reason,
|
|
]
|
|
.filter(Boolean)
|
|
.join(' '),
|
|
npcs: params.state.currentScenePreset.npcs?.map((npc) => ({
|
|
...npc,
|
|
description:
|
|
attitudeMutation && !npc.hostile
|
|
? `${npc.description} ${attitudeMutation.reason}`
|
|
: npc.description,
|
|
})),
|
|
}
|
|
: params.state.currentScenePreset,
|
|
};
|
|
}
|