Simplify custom world result editing controls
This commit is contained in:
@@ -7,7 +7,10 @@ import {
|
||||
normalizeCustomWorldCreatorIntent,
|
||||
normalizeCustomWorldLockState,
|
||||
} from '../services/customWorldCreatorIntent';
|
||||
import { normalizeCustomWorldOwnedSettingLayers } from '../services/customWorldOwnedSettingLayers';
|
||||
import {
|
||||
AnimationState,
|
||||
CharacterAnimationConfig,
|
||||
CharacterBackstoryChapter,
|
||||
CharacterBackstoryRevealConfig,
|
||||
CustomWorldAnchorPack,
|
||||
@@ -47,6 +50,7 @@ const DEFAULT_PLAYABLE_INITIAL_AFFINITY = 18;
|
||||
const DEFAULT_STORY_NPC_INITIAL_AFFINITY = 6;
|
||||
const ITEM_RARITIES = new Set<ItemRarity>(['common', 'uncommon', 'rare', 'epic', 'legendary']);
|
||||
const EQUIPMENT_SLOTS = new Set<EquipmentSlotId>(['weapon', 'armor', 'relic']);
|
||||
const ANIMATION_STATES = new Set<AnimationState>(Object.values(AnimationState));
|
||||
const CUSTOM_WORLD_NPC_VISUAL_RACES = new Set<CustomWorldNpcVisualRace>(['human', 'elf', 'orc', 'goblin']);
|
||||
const CUSTOM_WORLD_NPC_VISUAL_GEAR_TYPES = new Set<CustomWorldNpcVisualGearType>(['cloth', 'leather', 'metal', 'melee', 'magic', 'ranged']);
|
||||
const CUSTOM_WORLD_ROLE_ITEM_CATEGORIES = new Set([
|
||||
@@ -361,6 +365,54 @@ function normalizeCustomWorldNpcVisual(value: unknown): CustomWorldNpcVisual | u
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeCharacterAnimationConfig(
|
||||
value: unknown,
|
||||
): CharacterAnimationConfig | null {
|
||||
if (!isRecord(value)) return null;
|
||||
|
||||
const folder = toText(value.folder);
|
||||
const prefix = toText(value.prefix);
|
||||
const frames = Math.max(1, toOptionalInteger(value.frames) ?? 0);
|
||||
|
||||
if (!folder || !prefix || frames <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const startFrame = toOptionalInteger(value.startFrame);
|
||||
const extension = toText(value.extension);
|
||||
const file = toText(value.file);
|
||||
const basePath = toText(value.basePath);
|
||||
|
||||
return {
|
||||
folder,
|
||||
prefix,
|
||||
frames,
|
||||
...(startFrame ? { startFrame: Math.max(1, startFrame) } : {}),
|
||||
...(extension ? { extension } : {}),
|
||||
...(file ? { file } : {}),
|
||||
...(basePath ? { basePath } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeGeneratedAnimationMap(value: unknown) {
|
||||
if (!isRecord(value)) return undefined;
|
||||
|
||||
const entries = Object.entries(value).flatMap(([key, rawConfig]) => {
|
||||
if (!ANIMATION_STATES.has(key as AnimationState)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const config = normalizeCharacterAnimationConfig(rawConfig);
|
||||
return config ? [[key as AnimationState, config] as const] : [];
|
||||
});
|
||||
|
||||
return entries.length > 0
|
||||
? Object.fromEntries(entries) as Partial<
|
||||
Record<AnimationState, CharacterAnimationConfig>
|
||||
>
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function normalizeItemStatProfile(value: unknown): ItemStatProfile | null {
|
||||
if (!isRecord(value)) return null;
|
||||
|
||||
@@ -408,9 +460,9 @@ function normalizePlayableNpc(value: unknown, index: number): CustomWorldPlayabl
|
||||
tags: tags.length > 0 ? tags : relationshipHooks.slice(0, 5),
|
||||
} satisfies CustomWorldRoleFallbackSource;
|
||||
|
||||
return {
|
||||
id: toText(value.id, `saved-playable-${index + 1}`),
|
||||
name,
|
||||
return {
|
||||
id: toText(value.id, `saved-playable-${index + 1}`),
|
||||
name,
|
||||
title,
|
||||
role,
|
||||
description: fallbackSource.description,
|
||||
@@ -419,14 +471,18 @@ function normalizePlayableNpc(value: unknown, index: number): CustomWorldPlayabl
|
||||
motivation: fallbackSource.motivation,
|
||||
combatStyle: fallbackSource.combatStyle,
|
||||
initialAffinity: normalizeInitialAffinity(value.initialAffinity, DEFAULT_PLAYABLE_INITIAL_AFFINITY),
|
||||
relationshipHooks: fallbackSource.relationshipHooks,
|
||||
tags: fallbackSource.tags,
|
||||
backstoryReveal: normalizeBackstoryReveal(value.backstoryReveal, fallbackSource),
|
||||
skills: normalizeRoleSkills(value.skills, fallbackSource),
|
||||
initialItems: normalizeRoleInitialItems(value.initialItems, fallbackSource),
|
||||
templateCharacterId: toText(value.templateCharacterId) || undefined,
|
||||
};
|
||||
}
|
||||
relationshipHooks: fallbackSource.relationshipHooks,
|
||||
tags: fallbackSource.tags,
|
||||
backstoryReveal: normalizeBackstoryReveal(value.backstoryReveal, fallbackSource),
|
||||
skills: normalizeRoleSkills(value.skills, fallbackSource),
|
||||
initialItems: normalizeRoleInitialItems(value.initialItems, fallbackSource),
|
||||
imageSrc: toText(value.imageSrc) || undefined,
|
||||
generatedVisualAssetId: toText(value.generatedVisualAssetId) || undefined,
|
||||
generatedAnimationSetId: toText(value.generatedAnimationSetId) || undefined,
|
||||
animationMap: normalizeGeneratedAnimationMap(value.animationMap),
|
||||
templateCharacterId: toText(value.templateCharacterId) || undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeStoryNpc(value: unknown, index: number): CustomWorldNpc | null {
|
||||
if (!isRecord(value)) return null;
|
||||
@@ -450,9 +506,9 @@ function normalizeStoryNpc(value: unknown, index: number): CustomWorldNpc | null
|
||||
tags: tags.length > 0 ? tags : relationshipHooks.slice(0, 5),
|
||||
} satisfies CustomWorldRoleFallbackSource;
|
||||
|
||||
return {
|
||||
id: toText(value.id, `saved-story-${index + 1}`),
|
||||
name,
|
||||
return {
|
||||
id: toText(value.id, `saved-story-${index + 1}`),
|
||||
name,
|
||||
title,
|
||||
role,
|
||||
description: fallbackSource.description,
|
||||
@@ -461,15 +517,18 @@ function normalizeStoryNpc(value: unknown, index: number): CustomWorldNpc | null
|
||||
motivation: fallbackSource.motivation,
|
||||
combatStyle: fallbackSource.combatStyle,
|
||||
initialAffinity: normalizeInitialAffinity(value.initialAffinity, DEFAULT_STORY_NPC_INITIAL_AFFINITY),
|
||||
relationshipHooks: fallbackSource.relationshipHooks,
|
||||
tags: fallbackSource.tags,
|
||||
backstoryReveal: normalizeBackstoryReveal(value.backstoryReveal, fallbackSource),
|
||||
skills: normalizeRoleSkills(value.skills, fallbackSource),
|
||||
initialItems: normalizeRoleInitialItems(value.initialItems, fallbackSource),
|
||||
imageSrc: toText(value.imageSrc) || undefined,
|
||||
visual: normalizeCustomWorldNpcVisual(value.visual),
|
||||
};
|
||||
}
|
||||
relationshipHooks: fallbackSource.relationshipHooks,
|
||||
tags: fallbackSource.tags,
|
||||
backstoryReveal: normalizeBackstoryReveal(value.backstoryReveal, fallbackSource),
|
||||
skills: normalizeRoleSkills(value.skills, fallbackSource),
|
||||
initialItems: normalizeRoleInitialItems(value.initialItems, fallbackSource),
|
||||
imageSrc: toText(value.imageSrc) || undefined,
|
||||
generatedVisualAssetId: toText(value.generatedVisualAssetId) || undefined,
|
||||
generatedAnimationSetId: toText(value.generatedAnimationSetId) || undefined,
|
||||
animationMap: normalizeGeneratedAnimationMap(value.animationMap),
|
||||
visual: normalizeCustomWorldNpcVisual(value.visual),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeItem(value: unknown, index: number): CustomWorldItem | null {
|
||||
if (!isRecord(value)) return null;
|
||||
@@ -619,7 +678,7 @@ function normalizeProfile(value: unknown): CustomWorldProfile | null {
|
||||
.filter((entry): entry is CustomWorldLandmarkDraft => Boolean(entry))
|
||||
: [];
|
||||
|
||||
return {
|
||||
const normalizedProfile = {
|
||||
id: toText(value.id, `saved-custom-world-${Date.now().toString(36)}`),
|
||||
settingText,
|
||||
name,
|
||||
@@ -670,6 +729,14 @@ function normalizeProfile(value: unknown): CustomWorldProfile | null {
|
||||
value.generationStatus === 'key_only' || value.generationStatus === 'complete'
|
||||
? value.generationStatus
|
||||
: 'complete',
|
||||
} satisfies CustomWorldProfile;
|
||||
|
||||
return {
|
||||
...normalizedProfile,
|
||||
ownedSettingLayers: normalizeCustomWorldOwnedSettingLayers(
|
||||
value.ownedSettingLayers,
|
||||
normalizedProfile,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user