Files
Mana-Loop/src/components/game/tabs/FloorControls.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

192 lines
7.9 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { ChevronUp, ChevronDown, Mountain, Shield, Skull, Heart, Wind, ShieldCheck } from 'lucide-react';
import { ELEMENTS } from '@/lib/game/constants';
import type { FloorControlsProps } from '@/lib/game/types';
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' },
};
export function FloorControls({
store,
climbDirection,
isGuardianFloor,
currentRoom,
currentGuardian,
isFloorCleared,
floorElemDef,
roomType,
roomConfig,
activeEquipmentSpells,
upgradeEffects,
floorElem,
totalDPS,
getEnemyName,
calcDamage,
SPELLS_DEF,
canCastSpell,
storeCurrentAction,
handleClimb,
formatSpellCost,
getSpellCostColor,
}: FloorControlsProps) {
return (
<Card className="bg-gray-900/80 border-gray-700">
<CardHeader className="pb-2">
<CardTitle className="text-amber-400 game-panel-title text-xs">Floor Navigation</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<div className="grid grid-cols-2 gap-2">
<Button
variant="outline"
size="sm"
onClick={() => handleClimb('up')}
disabled={storeCurrentAction === 'climb' || isFloorCleared || store.maxFloorReached >= 100}
className="border-gray-600 hover:bg-gray-800"
>
<ChevronUp className="w-4 h-4 mr-1" />
Climb Up
</Button>
<Button
variant="outline"
size="sm"
onClick={() => handleClimb('down')}
disabled={storeCurrentAction === 'climb' || store.currentFloor <= 1}
className="border-gray-600 hover:bg-gray-800"
>
<ChevronDown className="w-4 h-4 mr-1" />
Climb Down
</Button>
</div>
{storeCurrentAction === 'climb' && (
<div className="p-3 bg-amber-900/30 rounded border border-amber-700/50">
<div className="flex items-center gap-2 mb-2">
<Mountain className="w-4 h-4 text-amber-400" />
<span className="text-sm font-semibold text-amber-400">
Climbing {climbDirection === 'up' ? 'Up' : 'Down'}
</span>
{isGuardianFloor && (
<Badge className="bg-red-900/50 text-red-300 border-red-600">GUARDIAN</Badge>
)}
</div>
{currentGuardian && (
<div className="text-xs mb-2 p-2 bg-gray-800/50 rounded">
<div className="flex items-center gap-2">
<Skull className="w-3 h-3 text-red-400" />
<span className="font-semibold" style={{ color: floorElemDef?.color }}>
{currentGuardian.name}
</span>
</div>
</div>
)}
{!(roomType === 'swarm' || roomType === 'puzzle') && (
<div className="space-y-1 mb-2">
<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, (store.floorHP / store.floorMaxHP) * 100)}%`,
background: `linear-gradient(90deg, ${floorElemDef?.color}99, ${floorElemDef?.color})`,
boxShadow: `0 0 8px ${floorElemDef?.glow}`,
}}
/>
</div>
<div className="flex justify-between text-xs text-gray-400">
<span>{fmt(store.floorHP)} / {fmt(store.floorMaxHP)} HP</span>
<span className="text-amber-400">
DPS: {activeEquipmentSpells.length > 0 ? fmtDec(totalDPS) : '—'}
</span>
</div>
</div>
)}
{activeEquipmentSpells.length > 0 ? (
<div className="space-y-2">
{activeEquipmentSpells.map(({ spellId, equipmentId }) => {
const spellDef = SPELLS_DEF[spellId];
if (!spellDef) return null;
const spellState = store.equipmentSpellStates?.find(
s => s.spellId === spellId && s.sourceEquipment === equipmentId
);
const progress = spellState?.castProgress || 0;
const canCast = canCastSpell(spellId);
return (
<div key={`${spellId}-${equipmentId}`} className="p-2 bg-gray-800/50 rounded">
<div className="flex items-center justify-between mb-1">
<span className="text-xs font-semibold" style={{ color: spellDef.elem === 'raw' ? '#60A5FA' : ELEMENTS[spellDef.elem]?.color }}>
{spellDef.name}
</span>
<span className={`text-xs ${canCast ? 'text-green-400' : 'text-red-400'}`}>
{canCast ? '✓' : '✗'}
</span>
</div>
<div className="text-xs text-gray-400 mb-1">
{fmt(calcDamage(store, spellId))} dmg {' '}
<span style={{ color: getSpellCostColor(spellDef.cost) }}>
{formatSpellCost(spellDef.cost)}
</span>
</div>
<div className="space-y-0.5">
<div className="flex justify-between text-xs text-gray-500">
<span>Cast</span>
<span>{(progress * 100).toFixed(0)}%</span>
</div>
<div className="h-1.5 bg-gray-700 rounded-full overflow-hidden">
<div
className="h-full rounded-full transition-all duration-300"
style={{
width: `${Math.min(100, progress * 100)}%`,
background: `linear-gradient(90deg, ${spellDef.elem === 'raw' ? '#60A5FA' : ELEMENTS[spellDef.elem]?.color}99, ${spellDef.elem === 'raw' ? '#60A5FA' : ELEMENTS[spellDef.elem]?.color})`,
}}
/>
</div>
</div>
</div>
);
})}
</div>
) : (
<div className="text-xs text-gray-500 italic">No active spells. Equip staves with spell effects.</div>
)}
</div>
)}
{storeCurrentAction !== 'climb' && (
<div className="text-xs text-gray-500 text-center">
Click Climb Up/Down to begin climbing
</div>
)}
</CardContent>
</Card>
);
}
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);
}