初始仓库迁移
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
2026-04-04 23:57:06 +08:00
parent 80986b790d
commit c49c64896a
18446 changed files with 532435 additions and 2 deletions

View File

@@ -0,0 +1,252 @@
import type {QuestGenerationContext} from '../services/aiTypes';
import type {
EquipmentLoadout,
GameState,
RuntimeItemGenerationChannel,
RuntimeItemGenerationContext,
ScenePresetInfo,
} from '../types';
import {getCharacterCombatTags, getTimedBuildBuffTags, normalizeBuildTags} from './buildTags';
type GapDefinition = {
id: string;
tags: string[];
};
const BUILD_GAP_DEFINITIONS: GapDefinition[] = [
{id: 'survival_gap', tags: ['守御', '护体', '回复', '续战']},
{id: 'mana_gap', tags: ['法力', '冷却', '护持', '过载']},
{id: 'finisher_gap', tags: ['爆发', '重击', '追击', '压制']},
{id: 'mobility_gap', tags: ['突进', '快袭', '风行', '游击']},
{id: 'control_gap', tags: ['控场', '符阵', '镇邪', '反击']},
];
function dedupeStrings(values: Array<string | null | undefined | false>) {
return [...new Set(
values
.map(value => typeof value === 'string' ? value.trim() : '')
.filter(Boolean),
)];
}
function collectLoadoutBuildTags(loadout: EquipmentLoadout | null | undefined) {
if (!loadout) return [] as string[];
return normalizeBuildTags([
...(loadout.weapon?.buildProfile?.tags ?? []),
loadout.weapon?.buildProfile?.role ?? '',
...(loadout.armor?.buildProfile?.tags ?? []),
loadout.armor?.buildProfile?.role ?? '',
...(loadout.relic?.buildProfile?.tags ?? []),
loadout.relic?.buildProfile?.role ?? '',
], 6);
}
function buildSceneTags(scene: Pick<ScenePresetInfo, 'name' | 'description' | 'treasureHints'> | null) {
if (!scene) return [] as string[];
const seedParts = dedupeStrings([
scene.name,
...(scene.treasureHints ?? []),
]);
return seedParts
.flatMap(part => part.split(/[\s/]+/u))
.map(part => part.trim())
.filter(part => part.length >= 2)
.slice(0, 8);
}
function buildRecentStorySummary(lines: string[]) {
if (lines.length <= 0) return '最近没有形成稳定的事件线索。';
return lines.join(' / ');
}
function buildRecentStoryLines(storyHistory: GameState['storyHistory']) {
return storyHistory
.slice(-4)
.map(moment => moment.text.trim())
.filter(Boolean)
.slice(-3);
}
function derivePlayerBuildGaps(playerBuildTags: string[]) {
const tagSet = new Set(playerBuildTags);
return BUILD_GAP_DEFINITIONS
.filter(definition => definition.tags.filter(tag => tagSet.has(tag)).length <= 0)
.map(definition => definition.id)
.slice(0, 3);
}
function buildBaseRuntimeContext(params: {
worldType: GameState['worldType'];
customWorldProfile: GameState['customWorldProfile'];
scene: Pick<ScenePresetInfo, 'id' | 'name' | 'description' | 'treasureHints'> | null;
encounter: GameState['currentEncounter'];
relatedNpcState: GameState['npcStates'][string] | null;
storyHistory: GameState['storyHistory'];
playerCharacterId: string;
playerBuildTags: string[];
playerEquipmentTags: string[];
generationChannel: RuntimeItemGenerationChannel;
}) {
const {
worldType,
customWorldProfile,
scene,
encounter,
relatedNpcState,
storyHistory,
playerCharacterId,
playerBuildTags,
playerEquipmentTags,
generationChannel,
} = params;
const recentStoryLines = buildRecentStoryLines(storyHistory);
return {
worldType,
customWorldProfile,
sceneId: scene?.id ?? null,
sceneName: scene?.name ?? null,
sceneDescription: scene?.description ?? null,
sceneTags: buildSceneTags(scene),
treasureHints: [...(scene?.treasureHints ?? [])],
encounter: encounter ?? null,
encounterNpcId: encounter?.id ?? encounter?.characterId ?? encounter?.monsterPresetId ?? encounter?.npcName ?? null,
encounterNpcName: encounter?.npcName ?? null,
encounterContextText: encounter?.context ?? null,
relatedNpcState,
relatedScene: scene,
recentStorySummary: buildRecentStorySummary(recentStoryLines),
recentActions: recentStoryLines,
playerCharacterId,
playerBuildTags,
playerBuildGaps: derivePlayerBuildGaps(playerBuildTags),
playerEquipmentTags,
generationChannel,
} satisfies RuntimeItemGenerationContext;
}
export function buildLooseRuntimeItemGenerationContext(params: {
worldType: GameState['worldType'];
customWorldProfile?: GameState['customWorldProfile'];
scene?: Pick<ScenePresetInfo, 'id' | 'name' | 'description' | 'treasureHints'> | null;
encounter?: GameState['currentEncounter'];
relatedNpcState?: GameState['npcStates'][string] | null;
storyHistory?: GameState['storyHistory'];
playerCharacterId?: string;
playerBuildTags?: string[];
playerEquipmentTags?: string[];
generationChannel: RuntimeItemGenerationChannel;
}) {
return buildBaseRuntimeContext({
worldType: params.worldType,
customWorldProfile: params.customWorldProfile ?? null,
scene: params.scene ?? null,
encounter: params.encounter ?? null,
relatedNpcState: params.relatedNpcState ?? null,
storyHistory: params.storyHistory ?? [],
playerCharacterId: params.playerCharacterId ?? 'runtime-loose-player',
playerBuildTags: params.playerBuildTags ?? [],
playerEquipmentTags: params.playerEquipmentTags ?? [],
generationChannel: params.generationChannel,
});
}
export function buildRuntimeItemGenerationContext(params: {
state: GameState;
generationChannel: RuntimeItemGenerationChannel;
encounter?: GameState['currentEncounter'];
scene?: GameState['currentScenePreset'];
}) {
const {state, generationChannel} = params;
const encounter = params.encounter ?? state.currentEncounter;
const scene = params.scene ?? state.currentScenePreset;
const relatedNpcState = encounter
? state.npcStates[encounter.id ?? encounter.npcName] ?? null
: null;
const playerBuildTags = state.playerCharacter
? normalizeBuildTags([
...getCharacterCombatTags(state.playerCharacter),
...collectLoadoutBuildTags(state.playerEquipment),
...getTimedBuildBuffTags(state.activeBuildBuffs),
], 6)
: [];
return buildBaseRuntimeContext({
worldType: state.worldType,
customWorldProfile: state.customWorldProfile,
scene,
encounter,
relatedNpcState,
storyHistory: state.storyHistory,
playerCharacterId: state.playerCharacter?.id ?? 'unknown-player',
playerBuildTags,
playerEquipmentTags: collectLoadoutBuildTags(state.playerEquipment),
generationChannel,
});
}
export function buildQuestRuntimeItemGenerationContext(params: {
context: QuestGenerationContext;
generationChannel?: RuntimeItemGenerationChannel;
issuerNpcId: string;
issuerNpcName: string;
roleText: string;
scene?: Pick<ScenePresetInfo, 'id' | 'name' | 'description' | 'treasureHints'> | null;
}) {
const {
context,
issuerNpcId,
issuerNpcName,
roleText,
scene,
generationChannel = 'quest_reward',
} = params;
const playerBuildTags = context.playerCharacter
? normalizeBuildTags([
...getCharacterCombatTags(context.playerCharacter),
...collectLoadoutBuildTags(context.playerEquipment),
], 6)
: [];
return buildBaseRuntimeContext({
worldType: context.worldType,
customWorldProfile: context.customWorldProfile ?? null,
scene: scene ?? (
context.currentSceneName
? {
id: context.currentSceneId ?? '',
name: context.currentSceneName,
description: context.currentSceneDescription ?? '',
treasureHints: [],
}
: null
),
encounter: {
id: issuerNpcId,
kind: 'npc',
npcName: issuerNpcName,
npcDescription: roleText,
npcAvatar: '',
context: roleText,
},
relatedNpcState: context.issuerAffinity == null
? null
: {
affinity: context.issuerAffinity,
helpUsed: false,
chattedCount: 0,
giftsGiven: 0,
inventory: [],
recruited: false,
revealedFacts: [],
},
storyHistory: context.recentStoryMoments ?? [],
playerCharacterId: context.playerCharacter?.id ?? 'quest-player',
playerBuildTags,
playerEquipmentTags: collectLoadoutBuildTags(context.playerEquipment),
generationChannel,
});
}