You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

209 lines
5.0 KiB
TypeScript

import { useCallback, type CSSProperties } from 'react';
import type { NPCData } from '../game/types';
// ---- Types ----
interface NPCInteractionProps {
npc: NPCData;
heroGold: number;
potionCost: number;
healCost: number;
onViewQuests: (npc: NPCData) => void;
onBuyPotion: (npc: NPCData) => void;
onHeal: (npc: NPCData) => void;
onDismiss: () => void;
}
// ---- Styles ----
const panelStyle: CSSProperties = {
position: 'absolute',
bottom: 130,
left: '50%',
transform: 'translateX(-50%)',
minWidth: 220,
maxWidth: 320,
backgroundColor: 'rgba(15, 15, 25, 0.94)',
border: '1px solid rgba(255, 215, 0, 0.3)',
borderRadius: 12,
padding: '10px 14px 12px',
zIndex: 120,
pointerEvents: 'auto',
animation: 'npc-interact-fade-in 0.25s ease-out',
boxShadow: '0 4px 20px rgba(0, 0, 0, 0.5)',
};
const headerRow: CSSProperties = {
display: 'flex',
alignItems: 'center',
gap: 8,
marginBottom: 8,
};
const npcIconStyle: CSSProperties = {
width: 28,
height: 28,
borderRadius: 14,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: 14,
fontWeight: 700,
};
const npcNameStyle: CSSProperties = {
fontSize: 13,
fontWeight: 700,
color: '#e8e8e8',
flex: 1,
};
const npcTypeStyle: CSSProperties = {
fontSize: 9,
fontWeight: 600,
color: '#888',
textTransform: 'uppercase',
letterSpacing: 0.5,
};
const actionBtnStyle: CSSProperties = {
width: '100%',
padding: '8px 12px',
border: 'none',
borderRadius: 8,
fontSize: 12,
fontWeight: 600,
cursor: 'pointer',
marginBottom: 4,
transition: 'background-color 150ms ease',
textAlign: 'center',
};
// ---- NPC appearance ----
function npcColor(type: string): { bg: string; icon: string; text: string } {
switch (type) {
case 'quest_giver':
return { bg: 'rgba(218, 165, 32, 0.2)', icon: '!', text: 'Quest Giver' };
case 'merchant':
return { bg: 'rgba(68, 170, 85, 0.2)', icon: '$', text: 'Merchant' };
case 'healer':
return { bg: 'rgba(220, 80, 80, 0.2)', icon: '+', text: 'Healer' };
default:
return { bg: 'rgba(136, 136, 170, 0.2)', icon: '?', text: 'NPC' };
}
}
// ---- Component ----
export function NPCInteraction({
npc,
heroGold,
potionCost,
healCost,
onViewQuests,
onBuyPotion,
onHeal,
onDismiss,
}: NPCInteractionProps) {
const info = npcColor(npc.type);
const handleAction = useCallback(() => {
switch (npc.type) {
case 'quest_giver':
onViewQuests(npc);
break;
case 'merchant':
onBuyPotion(npc);
break;
case 'healer':
onHeal(npc);
break;
}
}, [npc, onViewQuests, onBuyPotion, onHeal]);
const actionLabel = (() => {
switch (npc.type) {
case 'quest_giver':
return 'View Quests';
case 'merchant':
return `Buy Potion (${potionCost}g)`;
case 'healer':
return `Heal to Full (${healCost}g)`;
default:
return 'Talk';
}
})();
const canAfford =
npc.type === 'quest_giver' ||
(npc.type === 'merchant' && heroGold >= potionCost) ||
(npc.type === 'healer' && heroGold >= healCost);
return (
<>
<style>{`
@keyframes npc-interact-fade-in {
0% { opacity: 0; transform: translateX(-50%) translateY(8px); }
100% { opacity: 1; transform: translateX(-50%) translateY(0); }
}
`}</style>
<div style={panelStyle}>
<div style={headerRow}>
<div
style={{
...npcIconStyle,
backgroundColor: info.bg,
color: npc.type === 'quest_giver' ? '#ffd700' :
npc.type === 'merchant' ? '#88dd88' :
npc.type === 'healer' ? '#ff6666' : '#aaa',
}}
>
{info.icon}
</div>
<div>
<div style={npcNameStyle}>{npc.name}</div>
<div style={npcTypeStyle}>{info.text}</div>
</div>
<button
onClick={onDismiss}
style={{
background: 'none',
border: 'none',
color: '#666',
fontSize: 16,
cursor: 'pointer',
padding: '2px 6px',
lineHeight: 1,
}}
>
{'\u2715'}
</button>
</div>
<button
style={{
...actionBtnStyle,
backgroundColor: canAfford
? (npc.type === 'quest_giver' ? 'rgba(68, 170, 255, 0.2)' :
npc.type === 'merchant' ? 'rgba(68, 200, 68, 0.2)' :
'rgba(200, 68, 68, 0.2)')
: 'rgba(100, 100, 100, 0.15)',
color: canAfford
? (npc.type === 'quest_giver' ? '#66bbff' :
npc.type === 'merchant' ? '#88dd88' :
'#ff8888')
: '#666',
opacity: canAfford ? 1 : 0.5,
cursor: canAfford ? 'pointer' : 'default',
}}
onClick={canAfford ? handleAction : undefined}
disabled={!canAfford}
>
{actionLabel}
</button>
</div>
</>
);
}