1
This commit is contained in:
@@ -0,0 +1,304 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user