202 lines
7.0 KiB
TypeScript
202 lines
7.0 KiB
TypeScript
'use client';
|
||
|
||
import type { FloorState, EnemyState, RoomType } from '@/lib/game/types';
|
||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||
import { Badge } from '@/components/ui/badge';
|
||
import { Progress } from '@/components/ui/progress';
|
||
import { getSpireRoomTypeDisplay } from '@/lib/game/utils/spire-utils';
|
||
import { getModifierDisplay, getModifierDescription } from '@/lib/game/utils/enemy-generator';
|
||
import { ELEMENTS } from '@/lib/game/constants';
|
||
import { fmt } from '@/lib/game/stores';
|
||
|
||
interface RoomDisplayProps {
|
||
floorState: FloorState;
|
||
floor: number;
|
||
}
|
||
|
||
function EnemyRow({ enemy, floor }: { enemy: EnemyState; floor: number }) {
|
||
const elemDef = ELEMENTS[enemy.element];
|
||
const hpPercent = enemy.maxHP > 0 ? (enemy.hp / enemy.maxHP) * 100 : 0;
|
||
const barrierVal = enemy.barrier ?? 0;
|
||
const hasBarrier = barrierVal > 0;
|
||
const barrierPercent = hasBarrier ? barrierVal * 100 : 0;
|
||
|
||
return (
|
||
<div className="space-y-1 p-2 bg-gray-800/50 rounded border border-gray-700/50">
|
||
<div className="flex items-center justify-between">
|
||
<div className="flex items-center gap-2">
|
||
{elemDef && (
|
||
<span style={{ color: elemDef.color }}>{elemDef.sym}</span>
|
||
)}
|
||
<span className="text-sm font-medium text-gray-200">{enemy.name}</span>
|
||
</div>
|
||
<span className="text-xs text-gray-500">
|
||
{fmt(enemy.hp)} / {fmt(enemy.maxHP)}
|
||
</span>
|
||
</div>
|
||
|
||
{/* HP bar */}
|
||
<div className="relative">
|
||
<Progress
|
||
value={hpPercent}
|
||
className="h-2 bg-gray-700"
|
||
style={{ '--progress-bg': elemDef?.color || '#EF4444' } as React.CSSProperties}
|
||
/>
|
||
{hasBarrier && (
|
||
<div
|
||
className="absolute top-0 left-0 h-2 rounded-full bg-blue-400/40"
|
||
style={{ width: `${barrierPercent}%` }}
|
||
/>
|
||
)}
|
||
</div>
|
||
|
||
{/* Enemy stats */}
|
||
<div className="flex items-center gap-2 flex-wrap">
|
||
{enemy.armor > 0 && (
|
||
<Badge variant="outline" className="text-[10px] border-amber-600 text-amber-400">
|
||
⛰️ {Math.round(enemy.armor * 100)}% armor
|
||
</Badge>
|
||
)}
|
||
{enemy.dodgeChance > 0 && (
|
||
<Badge variant="outline" className="text-[10px] border-green-600 text-green-400">
|
||
💨 {Math.round(enemy.dodgeChance * 100)}% dodge
|
||
</Badge>
|
||
)}
|
||
{hasBarrier && (
|
||
<Badge variant="outline" className="text-[10px] border-blue-600 text-blue-400">
|
||
🛡️ Barrier
|
||
</Badge>
|
||
)}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export function RoomDisplay({ floorState, floor }: RoomDisplayProps) {
|
||
// Guard against null/undefined/stale floorState
|
||
if (!floorState || !floorState.roomType) {
|
||
return (
|
||
<Card className="bg-gray-900/80 border-gray-700">
|
||
<CardHeader className="pb-2">
|
||
<CardTitle className="text-sm text-gray-400">Loading room...</CardTitle>
|
||
</CardHeader>
|
||
</Card>
|
||
);
|
||
}
|
||
|
||
const roomDisplay = getSpireRoomTypeDisplay(floorState.roomType as RoomType);
|
||
|
||
// Handle special room types (cast to string for extended types)
|
||
const rt = floorState.roomType as string;
|
||
|
||
if (rt === 'recovery') {
|
||
const progress = floorState.recoveryProgress || 0;
|
||
const required = floorState.recoveryRequired || 1;
|
||
return (
|
||
<Card className="bg-gray-900/80 border-green-800/40">
|
||
<CardHeader className="pb-2">
|
||
<CardTitle className="text-sm flex items-center gap-2" style={{ color: '#10B981' }}>
|
||
💚 Recovery Room
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<p className="text-xs text-gray-400 mb-2">
|
||
Rest and recover. Spend 1 hour to gain 5x mana regen & conversion rates.
|
||
</p>
|
||
<Progress
|
||
value={(progress / required) * 100}
|
||
className="h-2 bg-gray-800"
|
||
style={{ '--progress-bg': '#10B981' } as React.CSSProperties}
|
||
/>
|
||
</CardContent>
|
||
</Card>
|
||
);
|
||
}
|
||
|
||
if (rt === 'library') {
|
||
return (
|
||
<Card className="bg-gray-900/80 border-indigo-800/40">
|
||
<CardHeader className="pb-2">
|
||
<CardTitle className="text-sm flex items-center gap-2" style={{ color: '#6366F1' }}>
|
||
📚 Ancient Library
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<p className="text-xs text-gray-400">
|
||
Study a random discipline at 10x XP speed (no mana cost). Spend 1 hour to gain knowledge.
|
||
</p>
|
||
</CardContent>
|
||
</Card>
|
||
);
|
||
}
|
||
|
||
if (rt === 'treasure') {
|
||
return (
|
||
<Card className="bg-gray-900/80 border-amber-800/40">
|
||
<CardHeader className="pb-2">
|
||
<CardTitle className="text-sm flex items-center gap-2" style={{ color: '#F59E0B' }}>
|
||
💎 Treasure Room
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<p className="text-xs text-gray-400">
|
||
A hidden cache of resources awaits. Claim your reward!
|
||
</p>
|
||
</CardContent>
|
||
</Card>
|
||
);
|
||
}
|
||
|
||
if (floorState.roomType === 'puzzle') {
|
||
const puzzleId = floorState.puzzleId || 'unknown';
|
||
const progress = floorState.puzzleProgress || 0;
|
||
const required = floorState.puzzleRequired || 1;
|
||
return (
|
||
<Card className="bg-gray-900/80 border-purple-800/40">
|
||
<CardHeader className="pb-2">
|
||
<CardTitle className="text-sm flex items-center gap-2" style={{ color: '#8B5CF6' }}>
|
||
🧩 Puzzle Room — {puzzleId.replace(/_/g, ' ')}
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<p className="text-xs text-gray-400 mb-2">
|
||
Solve the puzzle. Higher attunement levels speed up progress.
|
||
</p>
|
||
<Progress
|
||
value={(progress / required) * 100}
|
||
className="h-2 bg-gray-800"
|
||
style={{ '--progress-bg': '#8B5CF6' } as React.CSSProperties}
|
||
/>
|
||
<div className="text-xs text-gray-500 mt-1">
|
||
Progress: {Math.round(progress * 100)} / {Math.round(required * 100)}
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
);
|
||
}
|
||
|
||
// Combat rooms (combat, swarm, speed, guardian)
|
||
const enemies = floorState.enemies || [];
|
||
const isGuardian = floorState.roomType === 'guardian';
|
||
|
||
return (
|
||
<Card className={`bg-gray-900/80 ${isGuardian ? 'border-red-800/40' : 'border-gray-700'}`}>
|
||
<CardHeader className="pb-2">
|
||
<CardTitle className="text-sm flex items-center gap-2" style={{ color: roomDisplay.color }}>
|
||
{roomDisplay.icon} {roomDisplay.label}
|
||
{isGuardian && <Badge className="bg-red-900/50 text-red-300 text-xs">BOSS</Badge>}
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="space-y-2">
|
||
{enemies.length === 0 ? (
|
||
<div className="text-xs text-gray-500 italic">Room cleared!</div>
|
||
) : (
|
||
enemies.map((enemy) => (
|
||
<EnemyRow key={enemy.id} enemy={enemy} floor={floor} />
|
||
))
|
||
)}
|
||
</CardContent>
|
||
</Card>
|
||
);
|
||
}
|