@@ -44,6 +44,7 @@ import {
|
||||
createNpcBattleMonster,
|
||||
normalizeNpcPersistentState,
|
||||
} from '../data/npcInteractions';
|
||||
import { normalizePlayerProgressionState } from '../data/playerProgression';
|
||||
import { getSceneHostileNpcPresetIds } from '../data/scenePresets';
|
||||
import type { CharacterChatTarget } from '../hooks/useStoryGeneration';
|
||||
import { getResourceLabelsForWorld } from '../services/customWorldPresentation';
|
||||
@@ -64,6 +65,7 @@ import {
|
||||
} from './BackstoryArchive';
|
||||
import { CharacterAnimator } from './CharacterAnimator';
|
||||
import {
|
||||
buildCharacterSkillRenderId,
|
||||
getCharacterDetailSpriteStyle,
|
||||
getContributionVisualStyle,
|
||||
getSkillDeliveryLabel,
|
||||
@@ -71,8 +73,10 @@ import {
|
||||
} from './CharacterInfoHelpers';
|
||||
import {
|
||||
CharacterAttributeGrid,
|
||||
CharacterIdentityBadges,
|
||||
CharacterSkillsList,
|
||||
MultiplierContributionList,
|
||||
PlayerLevelProgress,
|
||||
StatusRow,
|
||||
} from './CharacterInfoShared';
|
||||
import { GENERIC_NPC_SCENE_SCALE } from './game-canvas/GameCanvasShared';
|
||||
@@ -137,7 +141,8 @@ function resolveSkillPreviewMonsterId(gameState: GameState) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const sceneMonsterId = getSceneHostileNpcPresetIds(gameState.currentScenePreset)[0] ?? null;
|
||||
const sceneMonsterId =
|
||||
getSceneHostileNpcPresetIds(gameState.currentScenePreset)[0] ?? null;
|
||||
if (sceneMonsterId) {
|
||||
return sceneMonsterId;
|
||||
}
|
||||
@@ -469,6 +474,45 @@ export function AdventureEntityModal({
|
||||
privateChatUnlockAffinity != null &&
|
||||
companionNpcState.affinity >= privateChatUnlockAffinity,
|
||||
);
|
||||
const normalizedPlayerProgression = normalizePlayerProgressionState(
|
||||
gameState.playerProgression ?? null,
|
||||
);
|
||||
const selectedNpcLevel =
|
||||
npcEncounter?.levelProfile?.level ??
|
||||
npcBattleState?.levelProfile?.level ??
|
||||
null;
|
||||
const selectionRoleLabel =
|
||||
selection?.kind === 'player'
|
||||
? '队长'
|
||||
: selection?.kind === 'companion'
|
||||
? '同行'
|
||||
: selection?.kind === 'npc' && npcEncounter && npcState
|
||||
? getNpcBadge(
|
||||
npcEncounter,
|
||||
npcState.affinity,
|
||||
Boolean(npcBattleState),
|
||||
)
|
||||
: null;
|
||||
const selectionLevelText =
|
||||
selection?.kind === 'player'
|
||||
? `Lv.${normalizedPlayerProgression.level}`
|
||||
: selection?.kind === 'companion'
|
||||
? `参考 Lv.${normalizedPlayerProgression.level}`
|
||||
: typeof selectedNpcLevel === 'number'
|
||||
? `Lv.${selectedNpcLevel}`
|
||||
: null;
|
||||
const selectionRoleTone: 'amber' | 'sky' | 'rose' | 'emerald' | 'zinc' =
|
||||
selection?.kind === 'player'
|
||||
? 'amber'
|
||||
: selection?.kind === 'companion'
|
||||
? 'sky'
|
||||
: selection?.kind === 'npc' && npcEncounter && npcState
|
||||
? npcEncounter.hostile ||
|
||||
Boolean(npcBattleState) ||
|
||||
npcState.affinity < 0
|
||||
? 'rose'
|
||||
: 'emerald'
|
||||
: 'zinc';
|
||||
|
||||
const title =
|
||||
selection?.kind === 'player'
|
||||
@@ -673,7 +717,10 @@ export function AdventureEntityModal({
|
||||
)
|
||||
: [];
|
||||
const selectedSkill =
|
||||
displayedSkills.find((skill) => skill.id === selectedSkillId) ?? null;
|
||||
displayedSkills.find(
|
||||
(skill, index) =>
|
||||
buildCharacterSkillRenderId(skill, index) === selectedSkillId,
|
||||
) ?? null;
|
||||
const selectedSkillPreviewWorldType = gameState.worldType ?? null;
|
||||
const selectedSkillPreviewMonsterId = selectedSkillPreviewWorldType
|
||||
? resolveSkillPreviewMonsterId(gameState)
|
||||
@@ -686,23 +733,30 @@ export function AdventureEntityModal({
|
||||
inventory.find((item) => item.id === selectedItemId) ?? null;
|
||||
const selectedSkillOwnerName =
|
||||
detailCharacter?.name ?? npcEncounter?.npcName ?? title;
|
||||
const recentChronicleEntries = gameState.storyEngineMemory?.chronicle?.slice(-3) ?? [];
|
||||
const recentCarrierEchoes = (gameState.storyEngineMemory?.recentCarrierIds ?? [])
|
||||
.map((carrierId) =>
|
||||
gameState.playerInventory.find((item) => item.id === carrierId)?.runtimeMetadata?.storyFingerprint?.visibleClue
|
||||
?? gameState.playerInventory.find((item) => item.id === carrierId)?.name
|
||||
?? '',
|
||||
const recentChronicleEntries =
|
||||
gameState.storyEngineMemory?.chronicle?.slice(-3) ?? [];
|
||||
const recentCarrierEchoes = (
|
||||
gameState.storyEngineMemory?.recentCarrierIds ?? []
|
||||
)
|
||||
.map(
|
||||
(carrierId) =>
|
||||
gameState.playerInventory.find((item) => item.id === carrierId)
|
||||
?.runtimeMetadata?.storyFingerprint?.visibleClue ??
|
||||
gameState.playerInventory.find((item) => item.id === carrierId)?.name ??
|
||||
'',
|
||||
)
|
||||
.filter(Boolean)
|
||||
.slice(0, 3);
|
||||
const sceneResidues = gameState.currentScenePreset?.narrativeResidues?.slice(0, 3) ?? [];
|
||||
const selectedCompanionResolution =
|
||||
detailCharacter
|
||||
? gameState.storyEngineMemory?.companionResolutions?.find(
|
||||
(resolution) => resolution.characterId === detailCharacter.id,
|
||||
) ?? null
|
||||
: null;
|
||||
const relatedConsequences = (gameState.storyEngineMemory?.consequenceLedger ?? [])
|
||||
const sceneResidues =
|
||||
gameState.currentScenePreset?.narrativeResidues?.slice(0, 3) ?? [];
|
||||
const selectedCompanionResolution = detailCharacter
|
||||
? (gameState.storyEngineMemory?.companionResolutions?.find(
|
||||
(resolution) => resolution.characterId === detailCharacter.id,
|
||||
) ?? null)
|
||||
: null;
|
||||
const relatedConsequences = (
|
||||
gameState.storyEngineMemory?.consequenceLedger ?? []
|
||||
)
|
||||
.filter((record) =>
|
||||
detailCharacter
|
||||
? record.relatedIds.includes(detailCharacter.id)
|
||||
@@ -761,6 +815,14 @@ export function AdventureEntityModal({
|
||||
{title}
|
||||
</div>
|
||||
<div className="mt-1 text-sm text-zinc-400">{subtitle}</div>
|
||||
{selectionRoleLabel ? (
|
||||
<CharacterIdentityBadges
|
||||
roleLabel={selectionRoleLabel}
|
||||
levelText={selectionLevelText}
|
||||
roleTone={selectionRoleTone}
|
||||
className="mt-2"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
@@ -780,7 +842,9 @@ export function AdventureEntityModal({
|
||||
{selection.kind === 'player' && playerCharacter ? (
|
||||
playerCharacter.visual ? (
|
||||
<MedievalNpcAnimator
|
||||
visualSpec={buildMedievalNpcVisualFromCustomWorldVisual(playerCharacter.visual)}
|
||||
visualSpec={buildMedievalNpcVisualFromCustomWorldVisual(
|
||||
playerCharacter.visual,
|
||||
)}
|
||||
scale={2.08}
|
||||
/>
|
||||
) : (
|
||||
@@ -798,7 +862,9 @@ export function AdventureEntityModal({
|
||||
companionCharacter ? (
|
||||
companionCharacter.visual ? (
|
||||
<MedievalNpcAnimator
|
||||
visualSpec={buildMedievalNpcVisualFromCustomWorldVisual(companionCharacter.visual)}
|
||||
visualSpec={buildMedievalNpcVisualFromCustomWorldVisual(
|
||||
companionCharacter.visual,
|
||||
)}
|
||||
scale={2.08}
|
||||
/>
|
||||
) : (
|
||||
@@ -815,7 +881,9 @@ export function AdventureEntityModal({
|
||||
) : npcCharacter ? (
|
||||
npcCharacter.visual ? (
|
||||
<MedievalNpcAnimator
|
||||
visualSpec={buildMedievalNpcVisualFromCustomWorldVisual(npcCharacter.visual)}
|
||||
visualSpec={buildMedievalNpcVisualFromCustomWorldVisual(
|
||||
npcCharacter.visual,
|
||||
)}
|
||||
scale={2.08}
|
||||
/>
|
||||
) : (
|
||||
@@ -824,7 +892,9 @@ export function AdventureEntityModal({
|
||||
character={npcCharacter}
|
||||
className="h-full w-full"
|
||||
imageClassName="object-bottom"
|
||||
style={getCharacterDetailSpriteStyle(npcCharacter)}
|
||||
style={getCharacterDetailSpriteStyle(
|
||||
npcCharacter,
|
||||
)}
|
||||
/>
|
||||
)
|
||||
) : hostileNpcPreset ? (
|
||||
@@ -842,15 +912,6 @@ export function AdventureEntityModal({
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
{selection.kind === 'npc' && npcEncounter && npcState && (
|
||||
<div className="mt-3 rounded-full border border-rose-400/25 bg-rose-500/10 px-3 py-1 text-[10px] tracking-[0.18em] text-rose-100">
|
||||
{getNpcBadge(
|
||||
npcEncounter,
|
||||
npcState.affinity,
|
||||
Boolean(npcBattleState),
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<p className="mt-3 text-sm leading-relaxed text-zinc-300">
|
||||
{description}
|
||||
</p>
|
||||
@@ -942,17 +1003,24 @@ export function AdventureEntityModal({
|
||||
<div className="space-y-3">
|
||||
{selectedCompanionResolution && (
|
||||
<div className="rounded-xl border border-emerald-400/18 bg-emerald-500/8 px-3 py-2 text-xs text-emerald-100/85">
|
||||
队友收束:{selectedCompanionResolution.resolutionType} · {selectedCompanionResolution.summary}
|
||||
队友收束:
|
||||
{selectedCompanionResolution.resolutionType} ·{' '}
|
||||
{selectedCompanionResolution.summary}
|
||||
</div>
|
||||
)}
|
||||
{relatedConsequences.length > 0 && (
|
||||
<div className="space-y-1">
|
||||
{relatedConsequences.map((record, index) => (
|
||||
<div
|
||||
key={record.id || `consequence-${record.title}-${index}`}
|
||||
key={
|
||||
record.id ||
|
||||
`consequence-${record.title}-${index}`
|
||||
}
|
||||
className="rounded-xl border border-white/8 bg-black/20 px-3 py-2 text-xs text-zinc-400"
|
||||
>
|
||||
<span className="text-white">{record.title}</span>
|
||||
<span className="text-white">
|
||||
{record.title}
|
||||
</span>
|
||||
{':'}
|
||||
{record.summary}
|
||||
</div>
|
||||
@@ -963,7 +1031,10 @@ export function AdventureEntityModal({
|
||||
<div className="space-y-1">
|
||||
{recentChronicleEntries.map((entry, index) => (
|
||||
<div
|
||||
key={entry.id || `chronicle-${entry.title}-${index}`}
|
||||
key={
|
||||
entry.id ||
|
||||
`chronicle-${entry.title}-${index}`
|
||||
}
|
||||
className="rounded-xl border border-white/8 bg-black/20 px-3 py-2"
|
||||
>
|
||||
<div className="text-sm font-medium text-white">
|
||||
@@ -985,10 +1056,15 @@ export function AdventureEntityModal({
|
||||
<div className="space-y-1">
|
||||
{sceneResidues.map((residue, index) => (
|
||||
<div
|
||||
key={residue.id || `residue-${residue.title}-${index}`}
|
||||
key={
|
||||
residue.id ||
|
||||
`residue-${residue.title}-${index}`
|
||||
}
|
||||
className="rounded-xl border border-white/8 bg-black/20 px-3 py-2 text-xs text-zinc-400"
|
||||
>
|
||||
<span className="text-white">{residue.title}</span>
|
||||
<span className="text-white">
|
||||
{residue.title}
|
||||
</span>
|
||||
{':'}
|
||||
{residue.visibleClue}
|
||||
</div>
|
||||
@@ -1001,6 +1077,22 @@ export function AdventureEntityModal({
|
||||
|
||||
<Section title="属性">
|
||||
<div className="space-y-4">
|
||||
{selection.kind === 'player' ? (
|
||||
<div className="rounded-xl border border-amber-300/18 bg-amber-500/8 px-3 py-3">
|
||||
<div className="mb-2 text-[10px] uppercase tracking-[0.18em] text-amber-100/75">
|
||||
等级
|
||||
</div>
|
||||
<PlayerLevelProgress
|
||||
level={normalizedPlayerProgression.level}
|
||||
currentLevelXp={
|
||||
normalizedPlayerProgression.currentLevelXp
|
||||
}
|
||||
xpToNextLevel={
|
||||
normalizedPlayerProgression.xpToNextLevel
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="space-y-3">
|
||||
<StatusRow
|
||||
label={resourceLabels.hp}
|
||||
|
||||
Reference in New Issue
Block a user