Rework story engine flow and reorganize project docs
Some checks failed
CI / verify (push) Has been cancelled
Some checks failed
CI / verify (push) Has been cancelled
This commit is contained in:
@@ -5,6 +5,7 @@ import { hasEncounterEntity } from '../../data/encounterTransition';
|
||||
import { NPC_PREVIEW_TALK_FUNCTION } from '../../data/functionCatalog';
|
||||
import {
|
||||
addInventoryItems,
|
||||
applyStoryChoiceToStanceProfile,
|
||||
buildNpcChatResultText,
|
||||
buildNpcHelpCommitActionText,
|
||||
buildNpcHelpResultText,
|
||||
@@ -12,6 +13,7 @@ import {
|
||||
buildNpcLeaveResultText,
|
||||
buildNpcSparResultText,
|
||||
createNpcBattleMonster,
|
||||
describeNpcAffinityInWords,
|
||||
generateNpcHelpReward,
|
||||
getChatAffinityOutcome,
|
||||
getNpcLootItems,
|
||||
@@ -40,6 +42,10 @@ import { applyStoryReasoningRecovery } from '../../data/storyRecovery';
|
||||
import { generateNextStep, streamNpcChatDialogue } from '../../services/ai';
|
||||
import type { StoryGenerationContext } from '../../services/aiTypes';
|
||||
import { generateQuestForNpcEncounter } from '../../services/questDirector';
|
||||
import {
|
||||
appendStoryEngineCarrierMemory,
|
||||
syncNpcNarrativeState,
|
||||
} from '../../services/storyEngine/echoMemory';
|
||||
import { createHistoryMoment } from '../../services/storyHistory';
|
||||
import type {
|
||||
Character,
|
||||
@@ -92,13 +98,13 @@ type BuildStoryContextExtras = {
|
||||
function buildCampCompanionChatResultText(
|
||||
encounter: Encounter,
|
||||
affinityGain: number,
|
||||
_nextAffinity: number,
|
||||
nextAffinity: number,
|
||||
) {
|
||||
const teamworkText =
|
||||
affinityGain > 0
|
||||
? 'You also feel a little more confident about how you will work together next.'
|
||||
: 'You at least realign your rhythm for what comes next.';
|
||||
return `${encounter.npcName}閸滃奔缍樻禍銈嗗床娴滃棔绔存潪顔藉厒濞夋洩绱?{describeNpcAffinityInWords(encounter, nextAffinity)}${teamworkText}`;
|
||||
? '你也更能感觉到,下一步和对方并肩时会顺手一些。'
|
||||
: '至少你们把接下来的节奏重新校准了一遍。';
|
||||
return `${encounter.npcName}和你交换了一番想法,${describeNpcAffinityInWords(encounter, nextAffinity)}${teamworkText}`;
|
||||
}
|
||||
|
||||
function isNpcEncounter(
|
||||
@@ -169,7 +175,7 @@ export function createStoryNpcEncounterActions({
|
||||
generateStoryForState: GenerateStoryForState;
|
||||
getStoryGenerationHostileNpcs: (
|
||||
state: GameState,
|
||||
) => GameState['sceneMonsters'];
|
||||
) => GameState['sceneHostileNpcs'];
|
||||
getTypewriterDelay: (char: string) => number;
|
||||
getAvailableOptionsForState: (
|
||||
state: GameState,
|
||||
@@ -232,10 +238,7 @@ export function createStoryNpcEncounterActions({
|
||||
const battleNpcId = state.currentBattleNpcId;
|
||||
const npcState = state.npcStates[battleNpcId];
|
||||
if (!npcState) return null;
|
||||
const activeBattleHostiles =
|
||||
state.sceneMonsters.length > 0
|
||||
? state.sceneMonsters
|
||||
: (state.sceneHostileNpcs ?? []);
|
||||
const activeBattleHostiles = state.sceneHostileNpcs;
|
||||
|
||||
if (battleMode === 'spar' && battleOutcome === 'spar_complete') {
|
||||
const nextAffinity = npcState.affinity + NPC_SPAR_AFFINITY_GAIN;
|
||||
@@ -251,7 +254,6 @@ export function createStoryNpcEncounterActions({
|
||||
currentNpcBattleOutcome: null,
|
||||
currentEncounter: restoredEncounter,
|
||||
npcInteractionActive: true,
|
||||
sceneMonsters: [],
|
||||
sceneHostileNpcs: [],
|
||||
npcStates: {
|
||||
...state.npcStates,
|
||||
@@ -259,6 +261,11 @@ export function createStoryNpcEncounterActions({
|
||||
...markNpcFirstMeaningfulContactResolved(npcState),
|
||||
affinity: nextAffinity,
|
||||
relationState: buildRelationState(nextAffinity),
|
||||
stanceProfile: applyStoryChoiceToStanceProfile(
|
||||
npcState.stanceProfile,
|
||||
'npc_chat',
|
||||
{ affinityGain: NPC_SPAR_AFFINITY_GAIN },
|
||||
),
|
||||
},
|
||||
},
|
||||
quests: progressedQuests,
|
||||
@@ -303,7 +310,8 @@ export function createStoryNpcEncounterActions({
|
||||
nextNpcInventory = removeInventoryItem(nextNpcInventory, item.id, 1);
|
||||
}
|
||||
|
||||
const nextState: GameState = incrementRuntimeStats(
|
||||
const nextState: GameState = appendStoryEngineCarrierMemory(
|
||||
incrementRuntimeStats(
|
||||
{
|
||||
...state,
|
||||
currentBattleNpcId: null,
|
||||
@@ -311,7 +319,6 @@ export function createStoryNpcEncounterActions({
|
||||
currentNpcBattleOutcome: null,
|
||||
currentEncounter: null,
|
||||
npcInteractionActive: false,
|
||||
sceneMonsters: [],
|
||||
sceneHostileNpcs: [],
|
||||
playerInventory: addInventoryItems(state.playerInventory, lootItems),
|
||||
quests: progressedQuests,
|
||||
@@ -339,6 +346,8 @@ export function createStoryNpcEncounterActions({
|
||||
{
|
||||
hostileNpcsDefeated: defeatedHostileNpcIds.length,
|
||||
},
|
||||
),
|
||||
lootItems,
|
||||
);
|
||||
|
||||
const lootText =
|
||||
@@ -638,10 +647,14 @@ export function createStoryNpcEncounterActions({
|
||||
(currentNpcState) => ({
|
||||
...markNpcFirstMeaningfulContactResolved(currentNpcState),
|
||||
helpUsed: true,
|
||||
stanceProfile: applyStoryChoiceToStanceProfile(
|
||||
currentNpcState.stanceProfile,
|
||||
'npc_help',
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
nextState = {
|
||||
nextState = appendStoryEngineCarrierMemory({
|
||||
...nextState,
|
||||
playerHp: Math.min(
|
||||
nextState.playerMaxHp,
|
||||
@@ -665,7 +678,7 @@ export function createStoryNpcEncounterActions({
|
||||
),
|
||||
)
|
||||
: nextState.playerInventory,
|
||||
};
|
||||
} as GameState, reward.items);
|
||||
|
||||
await commitNpcChatState(
|
||||
nextState,
|
||||
@@ -705,10 +718,14 @@ export function createStoryNpcEncounterActions({
|
||||
(currentNpcState) => ({
|
||||
...markNpcFirstMeaningfulContactResolved(currentNpcState),
|
||||
helpUsed: true,
|
||||
stanceProfile: applyStoryChoiceToStanceProfile(
|
||||
currentNpcState.stanceProfile,
|
||||
'npc_help',
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
nextState = {
|
||||
nextState = appendStoryEngineCarrierMemory({
|
||||
...nextState,
|
||||
playerHp: Math.min(
|
||||
nextState.playerMaxHp,
|
||||
@@ -732,7 +749,7 @@ export function createStoryNpcEncounterActions({
|
||||
),
|
||||
)
|
||||
: nextState.playerInventory,
|
||||
};
|
||||
} as GameState, reward.items);
|
||||
|
||||
await commitNpcChatState(
|
||||
nextState,
|
||||
@@ -779,6 +796,11 @@ export function createStoryNpcEncounterActions({
|
||||
affinity: nextAffinity,
|
||||
relationState: buildRelationState(nextAffinity),
|
||||
chattedCount: currentNpcState.chattedCount + 1,
|
||||
stanceProfile: applyStoryChoiceToStanceProfile(
|
||||
currentNpcState.stanceProfile,
|
||||
'npc_chat',
|
||||
{ affinityGain },
|
||||
),
|
||||
};
|
||||
},
|
||||
);
|
||||
@@ -838,8 +860,13 @@ export function createStoryNpcEncounterActions({
|
||||
updateNpcState(
|
||||
updateQuestLog(gameState, (quests) => acceptQuest(quests, quest)),
|
||||
encounter,
|
||||
(currentNpcState) =>
|
||||
markNpcFirstMeaningfulContactResolved(currentNpcState),
|
||||
(currentNpcState) => ({
|
||||
...markNpcFirstMeaningfulContactResolved(currentNpcState),
|
||||
stanceProfile: applyStoryChoiceToStanceProfile(
|
||||
currentNpcState.stanceProfile,
|
||||
'npc_quest_accept',
|
||||
),
|
||||
}),
|
||||
),
|
||||
{questsAccepted: 1},
|
||||
);
|
||||
@@ -870,8 +897,13 @@ export function createStoryNpcEncounterActions({
|
||||
acceptQuest(quests, fallbackQuest),
|
||||
),
|
||||
encounter,
|
||||
(currentNpcState) =>
|
||||
markNpcFirstMeaningfulContactResolved(currentNpcState),
|
||||
(currentNpcState) => ({
|
||||
...markNpcFirstMeaningfulContactResolved(currentNpcState),
|
||||
stanceProfile: applyStoryChoiceToStanceProfile(
|
||||
currentNpcState.stanceProfile,
|
||||
'npc_quest_accept',
|
||||
),
|
||||
}),
|
||||
),
|
||||
{questsAccepted: 1},
|
||||
);
|
||||
@@ -896,19 +928,26 @@ export function createStoryNpcEncounterActions({
|
||||
const quest = questId ? findQuestById(gameState.quests, questId) : null;
|
||||
if (!quest || quest.status !== 'completed') return true;
|
||||
|
||||
const nextState = {
|
||||
const nextState = appendStoryEngineCarrierMemory({
|
||||
...updateQuestLog(gameState, (quests) =>
|
||||
markQuestTurnedIn(quests, quest.id),
|
||||
),
|
||||
npcStates: {
|
||||
...gameState.npcStates,
|
||||
[getNpcEncounterKey(encounter)]: {
|
||||
...npcState,
|
||||
...markNpcFirstMeaningfulContactResolved(npcState),
|
||||
affinity: npcState.affinity + quest.reward.affinityBonus,
|
||||
relationState: buildRelationState(
|
||||
npcState.affinity + quest.reward.affinityBonus,
|
||||
),
|
||||
...syncNpcNarrativeState({
|
||||
encounter,
|
||||
npcState: {
|
||||
...npcState,
|
||||
...markNpcFirstMeaningfulContactResolved(npcState),
|
||||
affinity: npcState.affinity + quest.reward.affinityBonus,
|
||||
relationState: buildRelationState(
|
||||
npcState.affinity + quest.reward.affinityBonus,
|
||||
),
|
||||
},
|
||||
customWorldProfile: gameState.customWorldProfile,
|
||||
storyEngineMemory: gameState.storyEngineMemory,
|
||||
}),
|
||||
},
|
||||
},
|
||||
playerCurrency: gameState.playerCurrency + quest.reward.currency,
|
||||
@@ -916,7 +955,7 @@ export function createStoryNpcEncounterActions({
|
||||
gameState.playerInventory,
|
||||
quest.reward.items,
|
||||
),
|
||||
};
|
||||
} as GameState, quest.reward.items);
|
||||
|
||||
void commitGeneratedState(
|
||||
nextState,
|
||||
@@ -933,7 +972,6 @@ export function createStoryNpcEncounterActions({
|
||||
ambientIdleMode: undefined,
|
||||
currentEncounter: null,
|
||||
npcInteractionActive: false,
|
||||
sceneMonsters: [],
|
||||
sceneHostileNpcs: [],
|
||||
playerX: 0,
|
||||
playerFacing: 'right' as const,
|
||||
@@ -981,7 +1019,6 @@ export function createStoryNpcEncounterActions({
|
||||
},
|
||||
currentEncounter: null,
|
||||
npcInteractionActive: false,
|
||||
sceneMonsters: [battleMonster],
|
||||
sceneHostileNpcs: [battleMonster],
|
||||
playerX: 0,
|
||||
playerFacing: 'right' as const,
|
||||
@@ -1026,7 +1063,6 @@ export function createStoryNpcEncounterActions({
|
||||
},
|
||||
currentEncounter: null,
|
||||
npcInteractionActive: false,
|
||||
sceneMonsters: [battleMonster],
|
||||
sceneHostileNpcs: [battleMonster],
|
||||
playerX: 0,
|
||||
playerHp: sparPlayerMaxHp,
|
||||
|
||||
Reference in New Issue
Block a user