239 lines
7.8 KiB
JavaScript
239 lines
7.8 KiB
JavaScript
|
|
/**
|
|||
|
|
* M-PALACE · 背景生成引擎
|
|||
|
|
* 接收锚点答案 + 人格侧面初始数据
|
|||
|
|
* → 从宫廷数据库匹配
|
|||
|
|
* → 生成逻辑自洽的专属背景
|
|||
|
|
*
|
|||
|
|
* 生成流程:
|
|||
|
|
* 锚点1(世界观)→ 选择 templates/ 基础模板
|
|||
|
|
* 锚点2(身份) → 确定主角定位 + 初始人格快照
|
|||
|
|
* → 查询 palace-db 四大维度 → 调用模型 → 输出世界状态
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
const fs = require('fs');
|
|||
|
|
const path = require('path');
|
|||
|
|
|
|||
|
|
const DATA_DIR = path.join(__dirname, '..', '..', 'data');
|
|||
|
|
const TEMPLATES_DIR = path.join(DATA_DIR, 'templates');
|
|||
|
|
const PALACE_DB_PATH = path.join(DATA_DIR, 'palace-db.json');
|
|||
|
|
|
|||
|
|
let _palaceDB = null;
|
|||
|
|
|
|||
|
|
function loadPalaceDB() {
|
|||
|
|
if (_palaceDB) return _palaceDB;
|
|||
|
|
_palaceDB = JSON.parse(fs.readFileSync(PALACE_DB_PATH, 'utf-8'));
|
|||
|
|
return _palaceDB;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function loadTemplate(worldview) {
|
|||
|
|
const map = {
|
|||
|
|
'ancient-china': 'ancient-china.json',
|
|||
|
|
'古代中国': 'ancient-china.json',
|
|||
|
|
'fantasy': 'fantasy-dynasty.json',
|
|||
|
|
'架空王朝': 'fantasy-dynasty.json'
|
|||
|
|
};
|
|||
|
|
const file = map[worldview] || 'ancient-china.json';
|
|||
|
|
return JSON.parse(fs.readFileSync(path.join(TEMPLATES_DIR, file), 'utf-8'));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 从模板中随机选取一项
|
|||
|
|
*/
|
|||
|
|
function pick(arr) {
|
|||
|
|
return arr[Math.floor(Math.random() * arr.length)];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 基于人格侧面决定 NPC 映射
|
|||
|
|
* 核心机密:每个 NPC 对应玩家 1~2 个人格侧面的外化
|
|||
|
|
*/
|
|||
|
|
function generateNPCMappings(scores) {
|
|||
|
|
const npcs = [];
|
|||
|
|
const dims = Object.entries(scores).sort((a, b) => b[1] - a[1]);
|
|||
|
|
|
|||
|
|
// 最强维度 → 镜像对手
|
|||
|
|
const strongest = dims[0];
|
|||
|
|
npcs.push({
|
|||
|
|
archetype: 'mirror',
|
|||
|
|
mapped_dims: [strongest[0]],
|
|||
|
|
role_hint: 'rival',
|
|||
|
|
description: '与你最相似的对手'
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 最弱维度 → 放大威胁
|
|||
|
|
const weakest = dims[dims.length - 1];
|
|||
|
|
npcs.push({
|
|||
|
|
archetype: 'shadow',
|
|||
|
|
mapped_dims: [weakest[0]],
|
|||
|
|
role_hint: 'threat',
|
|||
|
|
description: '让你恐惧的存在'
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 温情相关 → 情感锚点
|
|||
|
|
if (scores.warmth >= 40) {
|
|||
|
|
npcs.push({
|
|||
|
|
archetype: 'anchor',
|
|||
|
|
mapped_dims: ['warmth', 'loyalty'],
|
|||
|
|
role_hint: 'ally',
|
|||
|
|
description: '需要被保护的弱势角色'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 多疑相关 → 暧昧盟友
|
|||
|
|
if (scores.suspicion >= 45) {
|
|||
|
|
npcs.push({
|
|||
|
|
archetype: 'trickster',
|
|||
|
|
mapped_dims: ['suspicion', 'cunning'],
|
|||
|
|
role_hint: 'ambiguous',
|
|||
|
|
description: '行为暧昧的盟友'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 确保至少 3 个 NPC
|
|||
|
|
if (npcs.length < 3) {
|
|||
|
|
npcs.push({
|
|||
|
|
archetype: 'catalyst',
|
|||
|
|
mapped_dims: [dims[2][0]],
|
|||
|
|
role_hint: 'neutral',
|
|||
|
|
description: '推动局势变化的中立角色'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return npcs;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 基于人格分数查询 palace-db,确定初始格局
|
|||
|
|
*/
|
|||
|
|
function queryInitialSetup(scores) {
|
|||
|
|
const db = loadPalaceDB();
|
|||
|
|
|
|||
|
|
// 权力维度:基于 ambition+cunning
|
|||
|
|
const powerLevel = (scores.ambition + scores.cunning) / 2;
|
|||
|
|
const factionCount = powerLevel > 60 ? 3 : 2;
|
|||
|
|
|
|||
|
|
// 后宫地位:基于 vanity+warmth
|
|||
|
|
const statusLevel = (scores.vanity + scores.warmth) / 2;
|
|||
|
|
|
|||
|
|
// 情感关系:基于 warmth+loyalty
|
|||
|
|
const emotionIntensity = (scores.warmth + scores.loyalty) / 2;
|
|||
|
|
|
|||
|
|
// 矛盾冲突:基于 aggression+suspicion
|
|||
|
|
const conflictLevel = (scores.aggression + scores.suspicion) / 2;
|
|||
|
|
const conflictType = conflictLevel > 60 ? '争宠' : '自保';
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
factions: db.power.factions.slice(0, factionCount),
|
|||
|
|
initialEvent: pick(db.power.events),
|
|||
|
|
startingRank: db.status.ranks[Math.min(Math.floor(statusLevel / 15), db.status.ranks.length - 1)],
|
|||
|
|
emotionType: emotionIntensity > 60 ? pick(db.emotion.types.slice(0, 3)) : pick(db.emotion.types.slice(3)),
|
|||
|
|
emotionTrigger: pick(db.emotion.triggers),
|
|||
|
|
conflictType: conflictType,
|
|||
|
|
escalation: db.conflict.escalation[0]
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 构建世界生成 prompt(供模型调用)
|
|||
|
|
*/
|
|||
|
|
function buildWorldPrompt(template, role, setup, npcMappings) {
|
|||
|
|
const dynasty = template.setting.dynasty;
|
|||
|
|
const era = pick(template.setting.era_prefix);
|
|||
|
|
const roleConfig = template.roles[role] || template.roles['妃子'];
|
|||
|
|
const title = pick(roleConfig.title_options);
|
|||
|
|
|
|||
|
|
const npcDescriptions = npcMappings.map((npc, i) => {
|
|||
|
|
return `NPC${i + 1}(${npc.role_hint}型):${npc.description}`;
|
|||
|
|
}).join('\n');
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
system: [
|
|||
|
|
'你是一个古风宫斗叙事生成器。',
|
|||
|
|
'要求:文笔古风典雅,有呼吸感,不要白话流水账。',
|
|||
|
|
'使用通感手法,注重氛围营造。',
|
|||
|
|
'所有NPC的存在都有隐藏的人格映射逻辑,但不能向玩家揭示。'
|
|||
|
|
].join('\n'),
|
|||
|
|
user: [
|
|||
|
|
`请为以下设定生成宫廷世界开篇:`,
|
|||
|
|
`王朝:${dynasty}`,
|
|||
|
|
`年号:${era}`,
|
|||
|
|
`玩家身份:${role}(${title})`,
|
|||
|
|
`初始阵营格局:${setup.factions.join('、')}`,
|
|||
|
|
`开局事件:${setup.initialEvent}`,
|
|||
|
|
`情感基调:${setup.emotionType}`,
|
|||
|
|
`冲突类型:${setup.conflictType}`,
|
|||
|
|
``,
|
|||
|
|
`需要生成的NPC:`,
|
|||
|
|
npcDescriptions,
|
|||
|
|
``,
|
|||
|
|
`输出格式(JSON):`,
|
|||
|
|
`{`,
|
|||
|
|
` "dynasty_name": "王朝名",`,
|
|||
|
|
` "era_name": "年号",`,
|
|||
|
|
` "background": "一段时代背景描述(古风文笔·100-200字)",`,
|
|||
|
|
` "player_intro": "主角身世与处境(古风文笔·100-200字)",`,
|
|||
|
|
` "npcs": [{ "name": "角色名", "title": "头衔", "personality": "性格", "relation_to_player": "与主角关系", "hidden_archetype": "mirror|shadow|anchor|trickster|catalyst" }],`,
|
|||
|
|
` "opening_event": "开局事件描述(古风文笔·150-300字)",`,
|
|||
|
|
` "opening_choices": ["选项A", "选项B", "选项C"]`,
|
|||
|
|
`}`
|
|||
|
|
].join('\n'),
|
|||
|
|
meta: { dynasty, era, title, role }
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 主入口:生成完整背景(不依赖外部模型时,使用本地模板拼接)
|
|||
|
|
*/
|
|||
|
|
function generate(worldview, role, personaScores) {
|
|||
|
|
const template = loadTemplate(worldview);
|
|||
|
|
const setup = queryInitialSetup(personaScores);
|
|||
|
|
const npcMappings = generateNPCMappings(personaScores);
|
|||
|
|
const prompt = buildWorldPrompt(template, role, setup, npcMappings);
|
|||
|
|
|
|||
|
|
// 构造本地 fallback 世界(不依赖外部模型时使用)
|
|||
|
|
const dynasty = prompt.meta.dynasty;
|
|||
|
|
const era = prompt.meta.era;
|
|||
|
|
const roleConfig = template.roles[role] || template.roles['妃子'];
|
|||
|
|
const title = prompt.meta.title;
|
|||
|
|
|
|||
|
|
const localWorld = {
|
|||
|
|
dynasty_name: dynasty,
|
|||
|
|
era_name: era,
|
|||
|
|
background: `${dynasty}${era}年间,朝堂暗流涌动。${setup.factions.join('与')}明争暗斗,帝位之下,人心叵测。宫墙之内,灯火通明处未必安宁,暗影幢幢处未必无情。`,
|
|||
|
|
player_intro: `你是${dynasty}${title},${pick(roleConfig.family_options || ['名门'])}出身。入宫数载,${setup.conflictType === '争宠' ? '虽有圣眷,却树敌颇多' : '小心翼翼,只求自保平安'}。${setup.emotionType}的暗线,早已悄然铺开。`,
|
|||
|
|
npcs: npcMappings.map(function (npc, i) {
|
|||
|
|
const names = ['沈婉仪', '赵昭仪', '陈太傅', '李将军', '刘公公'];
|
|||
|
|
return {
|
|||
|
|
name: names[i] || '无名氏',
|
|||
|
|
title: pick(template.setting.era_prefix) + '年间人物',
|
|||
|
|
personality: npc.description,
|
|||
|
|
relation_to_player: npc.role_hint,
|
|||
|
|
hidden_archetype: npc.archetype
|
|||
|
|
};
|
|||
|
|
}),
|
|||
|
|
opening_event: `夜半三更,${template.setting.inner_palace}传来急召。太后身边的掌事姑姑面色如常,语气却带着不容拒绝的冷意。你知道,这一趟,去与不去,都是棋局的一部分。`,
|
|||
|
|
opening_choices: [
|
|||
|
|
'立刻更衣前往,恭顺以对',
|
|||
|
|
'称身体不适,推迟半个时辰',
|
|||
|
|
'先派人去打听太后的意图'
|
|||
|
|
]
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
world: localWorld,
|
|||
|
|
npcMappings: npcMappings,
|
|||
|
|
setup: setup,
|
|||
|
|
prompt: prompt,
|
|||
|
|
template: template
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
module.exports = {
|
|||
|
|
generate,
|
|||
|
|
loadTemplate,
|
|||
|
|
loadPalaceDB,
|
|||
|
|
generateNPCMappings,
|
|||
|
|
queryInitialSetup,
|
|||
|
|
buildWorldPrompt
|
|||
|
|
};
|