Files
Mana-Loop/src/components/game/tabs/RoomDisplay.tsx
T
Refactoring Agent dc38445225
Build and Publish Mana Loop Docker Image / build-and-publish (push) Failing after 1m11s
refactor: extract components from SpireTab.tsx to reduce below 400 lines
2026-05-01 17:02:32 +02:00

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);
}