195 lines
7.5 KiB
TypeScript
195 lines
7.5 KiB
TypeScript
'use client';
|
|
|
|
import { Progress } from '@/components/ui/progress';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
|
import { Shield, Wind, Heart, ShieldCheck, Skull } from 'lucide-react';
|
|
import type { RoomDisplayProps } from '@/lib/game/types';
|
|
import { ELEMENTS } from '@/lib/game/constants';
|
|
|
|
const ROOM_TYPE_CONFIG: Record<string, { label: string; icon: string; color: string }> = {
|
|
combat: { label: 'Combat', icon: '⚔️', color: '#EF4444' },
|
|
swarm: { label: 'Swarm', icon: '🐝', color: '#F59E0B' },
|
|
speed: { label: 'Speed', icon: '💨', color: '#3B82F6' },
|
|
guardian: { label: 'Guardian', icon: '🛡️', color: '#EF4444' },
|
|
puzzle: { label: 'Puzzle', icon: '🧩', color: '#8B5CF6' },
|
|
} as const;
|
|
|
|
export function RoomDisplay({
|
|
roomType,
|
|
roomConfig,
|
|
primaryEnemy,
|
|
swarmEnemies,
|
|
puzzleId,
|
|
puzzleProgress,
|
|
simpleMode,
|
|
floorElemDef,
|
|
floorHP,
|
|
floorMaxHP,
|
|
totalDPS,
|
|
currentAction,
|
|
activeEquipmentSpells,
|
|
}: RoomDisplayProps) {
|
|
// Puzzle Room Display
|
|
if (roomType === 'puzzle') {
|
|
return (
|
|
<div className="p-3 bg-purple-900/20 rounded border border-purple-700">
|
|
<div className="flex items-center gap-2 mb-2">
|
|
<span className="text-lg">🧩</span>
|
|
<span className="text-sm font-semibold text-purple-300">
|
|
{puzzleId ? puzzleId.replace(/_/g, ' ').toUpperCase() : 'Puzzle Room'}
|
|
</span>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<div className="flex justify-between text-xs text-gray-400">
|
|
<span>Progress</span>
|
|
<span>{((puzzleProgress || 0) * 100).toFixed(0)}%</span>
|
|
</div>
|
|
<Progress value={Math.min(100, (puzzleProgress || 0) * 100)} className="h-2 bg-gray-800" />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Single Enemy Display (Combat/Speed/Guardian - non-swarm)
|
|
if ((roomType === 'combat' || roomType === 'speed' || roomType === 'guardian') && primaryEnemy) {
|
|
return (
|
|
<div className="p-3 bg-gray-800/50 rounded border border-gray-700">
|
|
<div className="flex items-center justify-between mb-2">
|
|
<div className="flex items-center gap-2">
|
|
<Skull className="w-4 h-4 text-red-400" />
|
|
<span className="text-sm font-semibold text-gray-200">
|
|
{primaryEnemy.name || 'Unknown Enemy'}
|
|
</span>
|
|
</div>
|
|
<Badge variant="outline" className="text-xs">
|
|
{ELEMENTS[primaryEnemy.element]?.sym} {ELEMENTS[primaryEnemy.element]?.name}
|
|
</Badge>
|
|
</div>
|
|
|
|
{/* Enemy HP Bar */}
|
|
<div className="space-y-1 mb-2">
|
|
<div className="h-2 bg-gray-800 rounded-full overflow-hidden">
|
|
<div
|
|
className="h-full rounded-full transition-all duration-300"
|
|
style={{
|
|
width: `${Math.max(0, (primaryEnemy.hp / primaryEnemy.maxHP) * 100)}%`,
|
|
background: `linear-gradient(90deg, ${floorElemDef?.color}99, ${floorElemDef?.color})`,
|
|
}}
|
|
/>
|
|
</div>
|
|
<div className="flex justify-between text-xs text-gray-400 game-mono">
|
|
<span>{fmt(primaryEnemy.hp)} / {fmt(primaryEnemy.maxHP)} HP</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Enemy Properties */}
|
|
<EnemyProperties enemy={primaryEnemy} />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Swarm Enemies Display
|
|
if (roomType === 'swarm' && swarmEnemies && swarmEnemies.length > 0) {
|
|
return (
|
|
<div className="space-y-2">
|
|
<div className="text-xs text-gray-400 font-semibold">
|
|
Swarm Enemies ({swarmEnemies.length})
|
|
</div>
|
|
{swarmEnemies.map((enemy: any, index: number) => (
|
|
<div key={enemy.id || `swarm-${index}`} className="p-2 bg-gray-800/50 rounded border border-gray-700">
|
|
<div className="flex items-center justify-between mb-1">
|
|
<div className="flex items-center gap-2">
|
|
<Skull className="w-3 h-3 text-red-400" />
|
|
<span className="text-xs font-semibold text-gray-300">
|
|
{enemy.name || `Enemy ${index + 1}`}
|
|
</span>
|
|
</div>
|
|
<Badge variant="outline" className="text-xs py-0">
|
|
{ELEMENTS[enemy.element]?.sym} {fmt(enemy.hp)}/{fmt(enemy.maxHP)} HP
|
|
</Badge>
|
|
</div>
|
|
<div className="h-1.5 bg-gray-800 rounded-full overflow-hidden">
|
|
<div
|
|
className="h-full rounded-full transition-all duration-300"
|
|
style={{
|
|
width: `${Math.max(0, (enemy.hp / enemy.maxHP) * 100)}%`,
|
|
background: `linear-gradient(90deg, ${ELEMENTS[enemy.element]?.color}99, ${ELEMENTS[enemy.element]?.color})`,
|
|
}}
|
|
/>
|
|
</div>
|
|
<EnemyProperties enemy={enemy} small />
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Floor HP Bar (non-swarm, non-puzzle)
|
|
return (
|
|
<div className="space-y-1">
|
|
<div className="h-3 bg-gray-800 rounded-full overflow-hidden">
|
|
<div
|
|
className="h-full rounded-full transition-all duration-300"
|
|
style={{
|
|
width: `${Math.max(0, (floorHP / floorMaxHP) * 100)}%`,
|
|
background: `linear-gradient(90deg, ${floorElemDef?.color}99, ${floorElemDef?.color})`,
|
|
boxShadow: `0 0 10px ${floorElemDef?.glow}`,
|
|
}}
|
|
/>
|
|
</div>
|
|
<div className="flex justify-between text-xs text-gray-400 game-mono">
|
|
<span>{fmt(floorHP)} / {fmt(floorMaxHP)} HP</span>
|
|
<span>DPS: {currentAction === 'climb' && activeEquipmentSpells.length > 0 ? fmtDec(totalDPS) : '—'}</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function EnemyProperties({ enemy, small }: { enemy: any; small?: boolean }) {
|
|
const props = [];
|
|
if (enemy.armor > 0) props.push({ type: 'armor', label: `${(enemy.armor * 100).toFixed(0)}% Armor`, icon: Shield });
|
|
if (enemy.dodgeChance > 0) props.push({ type: 'dodge', label: `${(enemy.dodgeChance * 100).toFixed(0)}% Dodge`, icon: Wind });
|
|
if (enemy.healthRegen > 0) props.push({ type: 'regen', label: `${(enemy.healthRegen * 100).toFixed(1)}% Regen`, icon: Heart });
|
|
if (enemy.barrier > 0) props.push({ type: 'barrier', label: `${(enemy.barrier * 100).toFixed(0)}% Barrier`, icon: ShieldCheck });
|
|
|
|
if (props.length === 0) return null;
|
|
|
|
return (
|
|
<div className={`flex flex-wrap gap-2 mt-2 ${small ? 'mt-1' : ''}`}>
|
|
{props.map((p, i) => {
|
|
const Icon = p.icon;
|
|
return (
|
|
<Tooltip key={i}>
|
|
<TooltipTrigger>
|
|
<Badge variant="outline" className={`text-xs py-0 ${small ? 'text-xs py-0' : ''}`}>
|
|
<Icon className={`w-${small ? 2 : 3} h-${small ? 2 : 3} mr-1`} />
|
|
{p.label}
|
|
</Badge>
|
|
</TooltipTrigger>
|
|
<TooltipContent>
|
|
<p>Reduces incoming damage by {(enemy.armor * 100).toFixed(0)}%</p>
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function fmt(value: number): string {
|
|
if (value >= 1e12) return (value / 1e12).toFixed(2) + 't';
|
|
if (value >= 1e9) return (value / 1e9).toFixed(2) + 'b';
|
|
if (value >= 1e6) return (value / 1e6).toFixed(2) + 'm';
|
|
if (value >= 1e3) return (value / 1e3).toFixed(2) + 'k';
|
|
return value.toFixed(0);
|
|
}
|
|
|
|
function fmtDec(value: number): string {
|
|
if (value >= 1e12) return (value / 1e12).toFixed(2) + 't';
|
|
if (value >= 1e9) return (value / 1e9).toFixed(2) + 'b';
|
|
if (value >= 1e6) return (value / 1e6).toFixed(2) + 'm';
|
|
if (value >= 1e3) return (value / 1e3).toFixed(2) + 'k';
|
|
return value.toFixed(0);
|
|
}
|