265 lines
9.5 KiB
TypeScript
265 lines
9.5 KiB
TypeScript
'use client';
|
|
|
|
import { useCallback } from 'react';
|
|
import { useShallow } from 'zustand/react/shallow';
|
|
import { usePrestigeStore, useGameStore } from '@/lib/game/stores';
|
|
import { PRESTIGE_DEF } from '@/lib/game/constants/prestige';
|
|
import { Card, CardContent } from '@/components/ui/card';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
|
import { SectionHeader } from '@/components/ui/section-header';
|
|
import { DebugName } from '@/components/game/debug/debug-context';
|
|
import { fmt } from '@/lib/game/stores';
|
|
import {
|
|
AlertDialog,
|
|
AlertDialogAction,
|
|
AlertDialogCancel,
|
|
AlertDialogContent,
|
|
AlertDialogDescription,
|
|
AlertDialogFooter,
|
|
AlertDialogHeader,
|
|
AlertDialogTitle,
|
|
AlertDialogTrigger,
|
|
} from '@/components/ui/alert-dialog';
|
|
|
|
// ─── Stat Cell ────────────────────────────────────────────────────────────────
|
|
|
|
function PrestigeStatCell({ value, label, color }: { value: string | number; label: string; color: string }) {
|
|
return (
|
|
<div className="text-center">
|
|
<div className={`text-2xl font-bold ${color}`}>{value}</div>
|
|
<div className="text-xs text-gray-400 mt-0.5">{label}</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// ─── Insight Summary ──────────────────────────────────────────────────────────
|
|
|
|
function InsightSummary({ insight, totalInsight, loopCount, loopInsight }: {
|
|
insight: number;
|
|
totalInsight: number;
|
|
loopCount: number;
|
|
loopInsight: number;
|
|
}) {
|
|
return (
|
|
<Card className="bg-gray-900/60 border-gray-700">
|
|
<CardContent className="py-4">
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
<PrestigeStatCell value={fmt(insight)} label="Insight Available" color="text-amber-400" />
|
|
<PrestigeStatCell value={fmt(totalInsight)} label="Total Insight Earned" color="text-gray-200" />
|
|
<PrestigeStatCell value={loopCount} label="Loops Completed" color="text-gray-200" />
|
|
<PrestigeStatCell value={fmt(loopInsight)} label="This Loop's Insight" color="text-purple-400" />
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
// ─── Pacts Card ───────────────────────────────────────────────────────────────
|
|
|
|
function PactsCard({ signedPacts, pactSlots, defeatedGuardians }: {
|
|
signedPacts: number[];
|
|
pactSlots: number;
|
|
defeatedGuardians: number[];
|
|
}) {
|
|
return (
|
|
<Card className="bg-gray-900/60 border-gray-700">
|
|
<SectionHeader title="📜 Pacts" />
|
|
<CardContent className="pt-0">
|
|
<p className="text-xs text-gray-400 mb-2">
|
|
Guardian pacts signed. Slots: {signedPacts.length}/{pactSlots}
|
|
</p>
|
|
{defeatedGuardians.length > 0 && (
|
|
<p className="text-xs text-gray-500 mb-1">
|
|
Defeated guardians: {defeatedGuardians.map((f) => `F${f}`).join(', ')}
|
|
</p>
|
|
)}
|
|
{signedPacts.length === 0 ? (
|
|
<p className="text-xs text-gray-500 italic">No pacts signed yet.</p>
|
|
) : (
|
|
<div className="space-y-1">
|
|
{signedPacts.map((f) => (
|
|
<div key={f} className="text-xs text-green-400">
|
|
✓ Floor {f} — Pact signed
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
// ─── Upgrade Card ─────────────────────────────────────────────────────────────
|
|
|
|
interface UpgradeCardProps {
|
|
id: string;
|
|
name: string;
|
|
desc: string;
|
|
max: number;
|
|
cost: number;
|
|
currentLevel: number;
|
|
insight: number;
|
|
onPurchase: (id: string) => void;
|
|
}
|
|
|
|
function UpgradeCard({ id, name, desc, max, cost, currentLevel, insight, onPurchase }: UpgradeCardProps) {
|
|
const isMaxed = currentLevel >= max;
|
|
const canAfford = insight >= cost;
|
|
const disabled = isMaxed || !canAfford;
|
|
|
|
return (
|
|
<Card className={`bg-gray-900/60 ${isMaxed ? 'border-amber-700/50' : 'border-gray-700'}`}>
|
|
<CardContent className="p-4 space-y-2">
|
|
<div className="flex items-center justify-between">
|
|
<span className="font-medium text-sm text-gray-100">{name}</span>
|
|
<Badge
|
|
variant="outline"
|
|
className={isMaxed ? 'border-amber-600 text-amber-400' : 'border-gray-600 text-gray-400'}
|
|
>
|
|
{currentLevel}/{max}
|
|
</Badge>
|
|
</div>
|
|
<p className="text-xs text-gray-400">{desc}</p>
|
|
<div className="flex items-center justify-between pt-1">
|
|
<span className="text-xs text-gray-500">
|
|
Cost: <span className={canAfford ? 'text-amber-400' : 'text-red-400'}>{fmt(cost)}</span> insight
|
|
</span>
|
|
<Button
|
|
size="sm"
|
|
disabled={disabled}
|
|
onClick={() => onPurchase(id)}
|
|
className="h-7 px-3 text-xs"
|
|
>
|
|
{isMaxed ? 'Maxed' : 'Buy'}
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
// ─── Reset Loop Section ──────────────────────────────────────────────────────
|
|
|
|
function ResetLoopSection({ loopInsight, onReset }: { loopInsight: number; onReset: () => void }) {
|
|
return (
|
|
<Card className="bg-gray-900/60 border-red-900/50">
|
|
<CardContent className="py-4">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h3 className="text-lg font-semibold text-red-400">Reset Loop</h3>
|
|
<p className="text-xs text-gray-400 mt-1">
|
|
End the current loop and gain {fmt(loopInsight)} insight. Your prestige upgrades and pacts are preserved.
|
|
</p>
|
|
</div>
|
|
<AlertDialog>
|
|
<AlertDialogTrigger asChild>
|
|
<Button variant="destructive" size="sm" className="flex-shrink-0 ml-4">
|
|
Reset Loop
|
|
</Button>
|
|
</AlertDialogTrigger>
|
|
<AlertDialogContent>
|
|
<AlertDialogHeader>
|
|
<AlertDialogTitle>Reset the Loop?</AlertDialogTitle>
|
|
<AlertDialogDescription>
|
|
This will end your current loop and award you <strong className="text-amber-400">{fmt(loopInsight)} insight</strong>.
|
|
Your prestige upgrades and pacts will be preserved.
|
|
<br /><br />
|
|
Day, hour, mana, floor progress, and combat state will be reset.
|
|
</AlertDialogDescription>
|
|
</AlertDialogHeader>
|
|
<AlertDialogFooter>
|
|
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
<AlertDialogAction onClick={onReset}>
|
|
Confirm Reset
|
|
</AlertDialogAction>
|
|
</AlertDialogFooter>
|
|
</AlertDialogContent>
|
|
</AlertDialog>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
// ─── Main Component ───────────────────────────────────────────────────────────
|
|
|
|
export function PrestigeTab() {
|
|
const {
|
|
insight,
|
|
totalInsight,
|
|
loopInsight,
|
|
loopCount,
|
|
prestigeUpgrades,
|
|
pactSlots,
|
|
signedPacts,
|
|
defeatedGuardians,
|
|
doPrestige,
|
|
} = usePrestigeStore(useShallow((s) => ({
|
|
insight: s.insight,
|
|
totalInsight: s.totalInsight,
|
|
loopInsight: s.loopInsight,
|
|
loopCount: s.loopCount,
|
|
prestigeUpgrades: s.prestigeUpgrades,
|
|
pactSlots: s.pactSlots,
|
|
signedPacts: s.signedPacts,
|
|
defeatedGuardians: s.defeatedGuardians,
|
|
doPrestige: s.doPrestige,
|
|
})));
|
|
|
|
const startNewLoop = useGameStore((s) => s.startNewLoop);
|
|
|
|
const handlePurchase = useCallback((id: string) => {
|
|
doPrestige(id);
|
|
}, [doPrestige]);
|
|
|
|
const handleResetLoop = useCallback(() => {
|
|
startNewLoop();
|
|
}, [startNewLoop]);
|
|
|
|
const upgradeEntries = Object.entries(PRESTIGE_DEF);
|
|
|
|
return (
|
|
<DebugName name="PrestigeTab">
|
|
<div className="space-y-4">
|
|
<InsightSummary
|
|
insight={insight}
|
|
totalInsight={totalInsight}
|
|
loopCount={loopCount}
|
|
loopInsight={loopInsight}
|
|
/>
|
|
|
|
<PactsCard signedPacts={signedPacts} pactSlots={pactSlots} defeatedGuardians={defeatedGuardians} />
|
|
|
|
<Card className="bg-gray-900/60 border-gray-700">
|
|
<SectionHeader title="⬆️ Prestige Upgrades" />
|
|
<CardContent className="pt-0">
|
|
<ScrollArea className="h-[400px] pr-2">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
{upgradeEntries.map(([id, def]) => (
|
|
<UpgradeCard
|
|
key={id}
|
|
id={id}
|
|
name={def.name}
|
|
desc={def.desc}
|
|
max={def.max}
|
|
cost={def.cost}
|
|
currentLevel={prestigeUpgrades[id] || 0}
|
|
insight={insight}
|
|
onPurchase={handlePurchase}
|
|
/>
|
|
))}
|
|
</div>
|
|
</ScrollArea>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<ResetLoopSection loopInsight={loopInsight} onReset={handleResetLoop} />
|
|
</div>
|
|
</DebugName>
|
|
);
|
|
}
|
|
|
|
PrestigeTab.displayName = 'PrestigeTab';
|