Files
Genarrative/src/components/rpg-creation-editor/rpgCreationResultFormMapper.ts
2026-04-21 18:27:46 +08:00

305 lines
7.8 KiB
TypeScript

import type {
RpgCreationEditorTarget,
RpgCreationEntityEditorModalProps,
} from './RpgCreationEntityEditorModalImpl';
import type {
CustomWorldLandmark,
CustomWorldNpc,
CustomWorldPlayableNpc,
CustomWorldProfile,
} from '../../types';
/**
* 工作包 C 第一轮先把编辑器目标分发需要的 profile 变更收口到 mapper。
* 后续继续拆 section 表单时,提交 patch 和字段清洗会逐步下沉到这里。
*/
function slugify(value: string) {
const normalized = value
.trim()
.toLowerCase()
.replace(/[^a-z0-9\u4e00-\u9fa5]+/g, '-')
.replace(/^-+|-+$/g, '');
return normalized || 'entry';
}
function createEntryId(prefix: string, label: string, seed: number) {
return `${prefix}-${slugify(label || `${prefix}-${seed}`)}-${seed.toString(36)}`;
}
const BACKSTORY_UNLOCK_AFFINITY_EASED = 6;
const BACKSTORY_UNLOCK_AFFINITY_FRIENDLY = 12;
const BACKSTORY_UNLOCK_AFFINITY_TRUSTED = 18;
const BACKSTORY_UNLOCK_AFFINITY_CLOSE = 24;
export function createPlayableNpcDraft(
profile: CustomWorldProfile,
): CustomWorldPlayableNpc {
const seed = Date.now() + profile.playableNpcs.length;
return {
id: createEntryId(
'playable-npc',
`角色-${profile.playableNpcs.length + 1}`,
seed,
),
name: `自定义角色${profile.playableNpcs.length + 1}`,
title: '自定义身份',
role: '世界中的行动者',
description: '',
backstory: '',
personality: '',
motivation: '',
combatStyle: '',
initialAffinity: 18,
relationshipHooks: ['首次接触', '合作空间'],
relations: [],
tags: ['自定义'],
backstoryReveal: {
publicSummary: '',
chapters: [
{
id: 'surface',
title: '表层来意',
affinityRequired: BACKSTORY_UNLOCK_AFFINITY_EASED,
teaser: '',
content: '',
contextSnippet: '',
},
{
id: 'scar',
title: '旧事裂痕',
affinityRequired: BACKSTORY_UNLOCK_AFFINITY_FRIENDLY,
teaser: '',
content: '',
contextSnippet: '',
},
{
id: 'hidden',
title: '隐藏执念',
affinityRequired: BACKSTORY_UNLOCK_AFFINITY_TRUSTED,
teaser: '',
content: '',
contextSnippet: '',
},
{
id: 'final',
title: '最终底牌',
affinityRequired: BACKSTORY_UNLOCK_AFFINITY_CLOSE,
teaser: '',
content: '',
contextSnippet: '',
},
],
},
skills: [
{ id: 'skill-1', name: '基础起手', summary: '', style: '起手压制' },
{ id: 'skill-2', name: '常用变招', summary: '', style: '机动周旋' },
{ id: 'skill-3', name: '压箱底牌', summary: '', style: '爆发终结' },
],
initialItems: [
{
id: 'item-1',
name: '随身武具',
category: '武器',
quantity: 1,
rarity: 'rare',
description: '',
tags: ['自定义'],
},
{
id: 'item-2',
name: '补给包',
category: '消耗品',
quantity: 2,
rarity: 'uncommon',
description: '',
tags: ['自定义'],
},
{
id: 'item-3',
name: '私人物件',
category: '专属物品',
quantity: 1,
rarity: 'rare',
description: '',
tags: ['自定义'],
},
],
templateCharacterId: profile.playableNpcs[0]?.templateCharacterId,
};
}
export function createStoryNpcDraft(
profile: Pick<CustomWorldProfile, 'storyNpcs'>,
): CustomWorldNpc {
const seed = Date.now() + profile.storyNpcs.length;
return {
id: createEntryId(
'story-npc',
`场景角色-${profile.storyNpcs.length + 1}`,
seed,
),
name: `自定义场景角色${profile.storyNpcs.length + 1}`,
title: '自定义头衔',
role: '自定义身份',
description: '',
backstory: '',
personality: '',
motivation: '',
combatStyle: '',
initialAffinity: 6,
relationshipHooks: ['合作', '互动'],
relations: [],
tags: ['自定义'],
backstoryReveal: {
publicSummary: '',
chapters: [
{
id: 'surface',
title: '表层来意',
affinityRequired: BACKSTORY_UNLOCK_AFFINITY_EASED,
teaser: '',
content: '',
contextSnippet: '',
},
{
id: 'scar',
title: '旧事裂痕',
affinityRequired: BACKSTORY_UNLOCK_AFFINITY_FRIENDLY,
teaser: '',
content: '',
contextSnippet: '',
},
{
id: 'hidden',
title: '隐藏执念',
affinityRequired: BACKSTORY_UNLOCK_AFFINITY_TRUSTED,
teaser: '',
content: '',
contextSnippet: '',
},
{
id: 'final',
title: '最终底牌',
affinityRequired: BACKSTORY_UNLOCK_AFFINITY_CLOSE,
teaser: '',
content: '',
contextSnippet: '',
},
],
},
skills: [
{ id: 'skill-1', name: '基础起手', summary: '', style: '起手压制' },
{ id: 'skill-2', name: '常用变招', summary: '', style: '机动周旋' },
{ id: 'skill-3', name: '压箱底牌', summary: '', style: '爆发终结' },
],
initialItems: [
{
id: 'item-1',
name: '随身武具',
category: '武器',
quantity: 1,
rarity: 'rare',
description: '',
tags: ['自定义'],
},
{
id: 'item-2',
name: '补给包',
category: '消耗品',
quantity: 2,
rarity: 'uncommon',
description: '',
tags: ['自定义'],
},
{
id: 'item-3',
name: '私人物件',
category: '专属物品',
quantity: 1,
rarity: 'rare',
description: '',
tags: ['自定义'],
},
],
} satisfies CustomWorldNpc;
}
export function createLandmarkDraft(
profile: CustomWorldProfile,
): CustomWorldLandmark {
const seed = Date.now() + profile.landmarks.length;
const previousLandmark = profile.landmarks[profile.landmarks.length - 1];
return {
id: createEntryId(
'landmark',
`scene-${profile.landmarks.length + 1}`,
seed,
),
name: `自定义场景${profile.landmarks.length + 1}`,
description: '',
dangerLevel: '中',
imageSrc: undefined,
sceneNpcIds: profile.storyNpcs.slice(0, 3).map((npc) => npc.id),
connections: previousLandmark
? [
{
targetLandmarkId: previousLandmark.id,
relativePosition: 'south',
summary: `南侧可回到${previousLandmark.name}`,
},
]
: [],
};
}
export function buildEditorTargetRendererParams(
props: RpgCreationEntityEditorModalProps,
) {
const { profile, target, onClose, onProfileChange } = props;
return {
onClose,
onProfileChange,
profile,
target,
};
}
export function resolveEditablePlayableNpc(
profile: CustomWorldProfile,
target: Extract<RpgCreationEditorTarget, { kind: 'playable' }>,
) {
if (target.mode === 'create') {
return createPlayableNpcDraft(profile);
}
return profile.playableNpcs.find((item) => item.id === target.id) ?? null;
}
export function resolveEditableStoryNpc(
profile: CustomWorldProfile,
target: Extract<RpgCreationEditorTarget, { kind: 'story' }>,
) {
if (target.mode === 'create') {
return createStoryNpcDraft(profile);
}
return profile.storyNpcs.find((item) => item.id === target.id) ?? null;
}
export function resolveEditableLandmark(
profile: CustomWorldProfile,
target: Extract<RpgCreationEditorTarget, { kind: 'landmark' }>,
) {
if (target.mode === 'create') {
return createLandmarkDraft(profile);
}
return profile.landmarks.find((item) => item.id === target.id) ?? null;
}