2026-09-05 23:50:27 +00:00
|
|
|
|
import React, { useEffect, useState } from 'react';
|
2026-09-05 22:09:18 +00:00
|
|
|
|
import type { BattleResult } from '../types';
|
|
|
|
|
|
|
|
|
|
|
|
interface BattleModalProps {
|
|
|
|
|
|
battle: BattleResult | null;
|
|
|
|
|
|
onClose: () => void;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const BattleModal: React.FC<BattleModalProps> = ({ battle, onClose }) => {
|
2026-09-05 23:50:27 +00:00
|
|
|
|
const [countdown, setCountdown] = useState(5);
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (!battle) return;
|
|
|
|
|
|
|
|
|
|
|
|
// Reset countdown to 5 whenever a new battle modal is opened
|
|
|
|
|
|
setCountdown(5);
|
|
|
|
|
|
|
|
|
|
|
|
const timer = window.setInterval(() => {
|
|
|
|
|
|
setCountdown((prev) => {
|
|
|
|
|
|
if (prev <= 1) {
|
|
|
|
|
|
window.clearInterval(timer);
|
|
|
|
|
|
onClose();
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
return prev - 1;
|
|
|
|
|
|
});
|
|
|
|
|
|
}, 1000);
|
|
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
|
window.clearInterval(timer);
|
|
|
|
|
|
};
|
|
|
|
|
|
}, [battle, onClose]);
|
|
|
|
|
|
|
2026-09-05 22:09:18 +00:00
|
|
|
|
if (!battle) return null;
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-md p-4 animate-in fade-in duration-200">
|
|
|
|
|
|
<div className="bg-slate-900 border-2 border-amber-500/80 rounded-2xl w-full max-w-xl shadow-2xl p-6 relative overflow-hidden">
|
|
|
|
|
|
{/* Glowing cyber header banner */}
|
|
|
|
|
|
<div className="absolute top-0 left-0 right-0 h-2 bg-gradient-to-r from-amber-500 via-rose-500 to-sky-500" />
|
|
|
|
|
|
|
2026-09-05 23:50:27 +00:00
|
|
|
|
{/* 5-second automatic progress bar */}
|
|
|
|
|
|
<div className="absolute top-2 left-0 right-0 h-1 bg-slate-800">
|
|
|
|
|
|
<div
|
|
|
|
|
|
className="h-full bg-amber-400 transition-all duration-1000 ease-linear"
|
|
|
|
|
|
style={{ width: `${(countdown / 5) * 100}%` }}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="flex items-center justify-between mb-4 pb-2 border-b border-slate-800 mt-1">
|
2026-09-05 22:09:18 +00:00
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
|
<span className="text-2xl">⚔️</span>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h2 className="text-base font-bold text-slate-100 font-mono tracking-wide">
|
|
|
|
|
|
PARTY ENGAGEMENT: 3-BOUT D20 CLASH
|
|
|
|
|
|
</h2>
|
|
|
|
|
|
<p className="text-xs text-slate-400">
|
|
|
|
|
|
{battle.party1_name} vs {battle.party2_name}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={onClose}
|
|
|
|
|
|
className="text-slate-400 hover:text-slate-200 p-1 text-sm font-mono"
|
2026-09-05 23:50:27 +00:00
|
|
|
|
title="Dismiss now"
|
2026-09-05 22:09:18 +00:00
|
|
|
|
>
|
|
|
|
|
|
✕
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 3 Bouts Breakdown */}
|
|
|
|
|
|
<div className="space-y-3 mb-5">
|
|
|
|
|
|
{battle.bouts.map((bout) => (
|
|
|
|
|
|
<div
|
|
|
|
|
|
key={bout.bout_number}
|
|
|
|
|
|
className="bg-slate-950/80 border border-slate-800 rounded-xl p-3 text-xs font-mono"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="flex justify-between items-center mb-2 pb-1 border-b border-slate-800/80">
|
|
|
|
|
|
<span className="text-amber-400 font-bold">BOUT #{bout.bout_number}</span>
|
|
|
|
|
|
<span className="text-slate-300">
|
|
|
|
|
|
Winner: <strong className="text-emerald-400">{bout.winner_name || 'Tied'}</strong>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
|
|
|
|
{/* Party 1 */}
|
|
|
|
|
|
<div className="p-2 rounded-lg bg-slate-900/90 border border-slate-800">
|
|
|
|
|
|
<div className="text-slate-400 font-semibold truncate mb-1">
|
|
|
|
|
|
{battle.party1_name}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="flex items-center justify-between text-slate-300">
|
|
|
|
|
|
<span>
|
|
|
|
|
|
Str: {bout.party1_strength} × 🎲 {bout.party1_roll} =
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<strong className="text-sm text-sky-400">{bout.party1_score}</strong>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Party 2 */}
|
|
|
|
|
|
<div className="p-2 rounded-lg bg-slate-900/90 border border-slate-800">
|
|
|
|
|
|
<div className="text-slate-400 font-semibold truncate mb-1">
|
|
|
|
|
|
{battle.party2_name}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="flex items-center justify-between text-slate-300">
|
|
|
|
|
|
<span>
|
|
|
|
|
|
Str: {bout.party2_strength} × 🎲 {bout.party2_roll} =
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<strong className="text-sm text-sky-400">{bout.party2_score}</strong>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Battle Resolution Summary */}
|
|
|
|
|
|
<div className="bg-amber-950/20 border border-amber-500/40 rounded-xl p-4 text-xs font-mono space-y-2 mb-5">
|
|
|
|
|
|
<div className="flex items-center justify-between text-sm font-bold">
|
|
|
|
|
|
<span className="text-amber-300 flex items-center gap-1.5">
|
|
|
|
|
|
<span>🏆</span> VICTORY: {battle.winner_party_name}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span className="text-slate-400 text-xs font-normal">
|
|
|
|
|
|
Bouts: {battle.party1_bouts_won} - {battle.party2_bouts_won}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="text-slate-300 leading-relaxed">
|
|
|
|
|
|
• Winning Leader <strong className="text-amber-300">{battle.winner_leader_name}</strong> receives{' '}
|
|
|
|
|
|
<span className="text-emerald-400 font-bold">+2 points</span>, and all other winning squad members receive{' '}
|
|
|
|
|
|
<span className="text-emerald-400 font-bold">+1 point</span>!
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="text-slate-300 leading-relaxed">
|
|
|
|
|
|
• Defeated Leader <strong className="text-rose-400">{battle.killed_leader_name}</strong> receives{' '}
|
2026-09-05 23:50:27 +00:00
|
|
|
|
<span className="text-rose-400 font-bold">-1 point</span>.
|
|
|
|
|
|
{battle.absorbed_members.includes(battle.killed_leader_id) ? (
|
|
|
|
|
|
<span> Resistance broken! Surrendered and <strong className="text-sky-300">joined {battle.winner_party_name}</strong>.</span>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<span> Respawned at ({battle.killed_leader_respawn_position.x}, {battle.killed_leader_respawn_position.y}).</span>
|
|
|
|
|
|
)}
|
2026-09-05 22:09:18 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-09-05 23:50:27 +00:00
|
|
|
|
{battle.absorbed_members.length > 0 && !battle.absorbed_members.includes(battle.killed_leader_id) && (
|
|
|
|
|
|
<div className="text-slate-300 leading-relaxed">
|
|
|
|
|
|
• <strong className="text-emerald-400">{battle.absorbed_members.length} surviving bot(s)</strong> from the
|
|
|
|
|
|
defeated party surrendered and <strong className="text-sky-300">joined {battle.winner_party_name}</strong>!
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2026-09-05 22:09:18 +00:00
|
|
|
|
|
|
|
|
|
|
<div className="text-slate-400 text-[11px] pt-1">
|
|
|
|
|
|
New Squad Size: <strong className="text-slate-200">{battle.new_party_size} bots</strong> • Total Strength:{' '}
|
|
|
|
|
|
<strong className="text-amber-400">{battle.new_party_strength}</strong>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-09-05 23:50:27 +00:00
|
|
|
|
{/* Continue Button with 5s Auto-close Countdown */}
|
2026-09-05 22:09:18 +00:00
|
|
|
|
<button
|
|
|
|
|
|
onClick={onClose}
|
2026-09-05 23:50:27 +00:00
|
|
|
|
className="w-full bg-amber-500 hover:bg-amber-400 text-slate-950 font-bold py-2.5 rounded-xl text-xs font-mono transition-all shadow-lg shadow-amber-500/20 flex items-center justify-center gap-2"
|
2026-09-05 22:09:18 +00:00
|
|
|
|
>
|
2026-09-05 23:50:27 +00:00
|
|
|
|
<span>Acknowledge & Continue Battle</span>
|
|
|
|
|
|
<span className="bg-amber-950 text-amber-300 px-2 py-0.5 rounded-full text-[11px] font-mono">
|
|
|
|
|
|
Auto-closing in {countdown}s
|
|
|
|
|
|
</span>
|
2026-09-05 22:09:18 +00:00
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|