Refine NPC interactions and runtime item generation

This commit is contained in:
2026-04-05 17:13:07 +08:00
parent c49c64896a
commit 89cecda7da
58 changed files with 4199 additions and 1562 deletions

View File

@@ -1,5 +1,6 @@
import type { ReactNode } from 'react';
import { resolveCustomWorldNpcMonsterPreset } from '../data/customWorldNpcMonsters';
import {
buildBodyPath,
buildMedievalNpcVisual,
@@ -24,9 +25,23 @@ import {
} from '../data/medievalNpcVisuals';
import { type CustomWorldNpc, type CustomWorldNpcVisual } from '../types';
import { buildDefaultCustomWorldNpcVisual } from './customWorldNpcVisualDefaults';
import { HostileNpcAnimator } from './HostileNpcAnimator';
import { MedievalNpcAnimator } from './MedievalNpcAnimator';
type EditableNpcSource = Pick<CustomWorldNpc, 'id' | 'name' | 'role' | 'description'>;
type EditableNpcSource = Pick<CustomWorldNpc, 'id' | 'name' | 'role' | 'description'>
& Partial<
Pick<
CustomWorldNpc,
| 'title'
| 'backstory'
| 'personality'
| 'motivation'
| 'combatStyle'
| 'initialAffinity'
| 'relationshipHooks'
| 'tags'
>
>;
type GearSlot = 'headgear' | 'mainHand' | 'offHand';
function buildCustomWorldNpcEncounter(npc: EditableNpcSource) {
@@ -286,16 +301,31 @@ export function CustomWorldNpcPortrait({
scale?: number;
}) {
const previewSpec = buildPreviewSpec(npc, visual ? sanitizeCustomWorldNpcVisual(visual) : undefined);
const monsterPreset = visual
? null
: resolveCustomWorldNpcMonsterPreset(npc);
return (
<div className={`relative overflow-hidden rounded-2xl border border-white/10 bg-[radial-gradient(circle_at_top,rgba(56,189,248,0.16),transparent_48%),linear-gradient(180deg,rgba(19,24,39,0.96),rgba(8,10,17,0.92))] ${className}`}>
<div className="absolute inset-0 opacity-10 [background-image:linear-gradient(rgba(255,255,255,0.16)_1px,transparent_1px),linear-gradient(90deg,rgba(255,255,255,0.16)_1px,transparent_1px)] [background-size:16px_16px]" />
<div className="relative flex h-full min-h-[7rem] items-center justify-center p-3">
<MedievalNpcAnimator
visualSpec={previewSpec}
scale={scale}
className="origin-center drop-shadow-[0_12px_18px_rgba(0,0,0,0.38)]"
/>
{monsterPreset ? (
<div
className="origin-center drop-shadow-[0_12px_18px_rgba(0,0,0,0.38)]"
style={{
transform: `scale(${Math.max(1, scale * 0.72)})`,
transformOrigin: 'center',
}}
>
<HostileNpcAnimator hostileNpc={monsterPreset} />
</div>
) : (
<MedievalNpcAnimator
visualSpec={previewSpec}
scale={scale}
className="origin-center drop-shadow-[0_12px_18px_rgba(0,0,0,0.38)]"
/>
)}
</div>
</div>
);