feat: Actualizar roles y facciones a Francia Ocupada
- Cambiar nombre del juego de 'La Resistencia' a 'Francia Ocupada' - Actualizar roles: Marlene, Capitán Philippe, Partisano, Comandante Schmidt, Francotirador, Agente Doble, Infiltrado, Colaboracionista - Actualizar facciones: Aliados vs Alemanes - Implementar timer de votación de líder con auto-avance - Eliminar componentes de debug
This commit is contained in:
@@ -5,7 +5,7 @@ import './globals.css'
|
||||
const inter = Inter({ subsets: ['latin'] })
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'La Resistencia: WWII',
|
||||
title: 'Francia Ocupada: WWII',
|
||||
description: 'Juego de deducción social ambientado en la Segunda Guerra Mundial',
|
||||
}
|
||||
|
||||
|
||||
@@ -173,7 +173,7 @@ export default function Home() {
|
||||
<div className="flex items-center gap-4">
|
||||
<Image src="/assets/images/ui/logo.png" alt="Logo" width={150} height={50} className="object-contain filter drop-shadow hidden md:block" />
|
||||
<h1 className="text-2xl font-bold tracking-widest uppercase text-yellow-600">
|
||||
La Resistencia
|
||||
Francia Ocupada
|
||||
</h1>
|
||||
</div>
|
||||
{view === 'lobby' && (
|
||||
|
||||
@@ -63,26 +63,6 @@ export default function GameBoard({ gameState, currentPlayerId, actions }: GameB
|
||||
actions.voteMission(vote);
|
||||
};
|
||||
|
||||
// Componente de Debug para mostrar facción
|
||||
const DebugInfo = () => (
|
||||
<div className="fixed top-2 left-2 bg-black/80 text-white p-3 rounded-lg text-xs z-50 border border-yellow-500 font-mono shadow-xl pointer-events-none">
|
||||
<div className="font-bold text-yellow-400 mb-1 flex items-center gap-2">
|
||||
<span>🐛 DEBUG INFO</span>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<div>Fase: <span className="text-cyan-400 font-bold">{gameState.phase}</span></div>
|
||||
<div>ID: <span className="text-green-400">{currentPlayerId}</span></div>
|
||||
<div>Facción: <span className={`font-bold ${currentPlayer?.faction === 'spies' ? 'text-red-500' : 'text-blue-400'}`}>
|
||||
{currentPlayer?.faction || 'UNDEFINED'}
|
||||
</span></div>
|
||||
<div>Rol: <span className="text-purple-400">{currentPlayer?.role || 'UNDEFINED'}</span></div>
|
||||
{gameState.currentLeaderId && (
|
||||
<div>Líder: <span className="text-yellow-400">{gameState.currentLeaderId === currentPlayerId ? 'TÚ' : gameState.currentLeaderId}</span></div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
// Coordenadas porcentuales de los hexágonos de misión en el mapa
|
||||
const missionCoords = [
|
||||
@@ -109,12 +89,14 @@ export default function GameBoard({ gameState, currentPlayerId, actions }: GameB
|
||||
Guerra Total
|
||||
</h1>
|
||||
|
||||
{/* Audio Auto-Play */}
|
||||
<audio
|
||||
src="/assets/audio/Intro.ogg"
|
||||
autoPlay
|
||||
onEnded={() => isHost && actions.finishIntro()}
|
||||
/>
|
||||
{/* Audio Auto-Play - Solo para el host */}
|
||||
{isHost && (
|
||||
<audio
|
||||
src="/assets/audio/Intro.ogg"
|
||||
autoPlay
|
||||
onEnded={() => actions.finishIntro()}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isHost && (
|
||||
<button
|
||||
@@ -132,31 +114,31 @@ export default function GameBoard({ gameState, currentPlayerId, actions }: GameB
|
||||
|
||||
if (gameState.phase === 'reveal_role' as any) {
|
||||
// Determinar imagen basada en el rol
|
||||
// Mapeo básico:
|
||||
// Merlin -> good_merlin.png
|
||||
// Percival -> good_percival.png
|
||||
// Servant -> good_soldier_X.png (random)
|
||||
// Assassin -> evil_assassin.png
|
||||
// Morgana -> evil_morgana.png
|
||||
// Mordred -> evil_mordred.png
|
||||
// Oberon -> evil_oberon.png
|
||||
// Minion -> evil_minion_X.png
|
||||
// Mapeo actualizado:
|
||||
// Marlene -> good_merlin.png
|
||||
// Capitán Philippe -> good_percival.png
|
||||
// Partisano -> good_soldier_X.png (random)
|
||||
// Francotirador -> evil_assassin.png
|
||||
// Agente Doble -> evil_morgana.png
|
||||
// Comandante Schmidt -> evil_mordred.png
|
||||
// Infiltrado -> evil_oberon.png
|
||||
// Colaboracionista -> evil_minion_X.png
|
||||
|
||||
let roleImage = '/assets/images/characters/good_soldier_1.png'; // Default
|
||||
|
||||
const role = currentPlayer?.role;
|
||||
if (role === 'merlin') roleImage = '/assets/images/characters/good_merlin.png';
|
||||
else if (role === 'assassin') roleImage = '/assets/images/characters/evil_assassin.png';
|
||||
else if (role === 'percival') roleImage = '/assets/images/characters/good_percival.png';
|
||||
else if (role === 'morgana') roleImage = '/assets/images/characters/evil_morgana.png';
|
||||
else if (role === 'mordred') roleImage = '/assets/images/characters/evil_mordred.png';
|
||||
else if (role === 'oberon') roleImage = '/assets/images/characters/evil_oberon.png';
|
||||
else if (role === 'loyal_servant') {
|
||||
if (role === 'marlene') roleImage = '/assets/images/characters/good_merlin.png';
|
||||
else if (role === 'francotirador') roleImage = '/assets/images/characters/evil_assassin.png';
|
||||
else if (role === 'capitan_philippe') roleImage = '/assets/images/characters/good_percival.png';
|
||||
else if (role === 'agente_doble') roleImage = '/assets/images/characters/evil_morgana.png';
|
||||
else if (role === 'comandante_schmidt') roleImage = '/assets/images/characters/evil_mordred.png';
|
||||
else if (role === 'infiltrado') roleImage = '/assets/images/characters/evil_oberon.png';
|
||||
else if (role === 'partisano') {
|
||||
// Random soldier 1-5
|
||||
const idx = (currentPlayerId.charCodeAt(0) % 5) + 1;
|
||||
roleImage = `/assets/images/characters/good_soldier_${idx}.png`;
|
||||
}
|
||||
else if (role === 'minion') {
|
||||
else if (role === 'colaboracionista') {
|
||||
// Random minion 1-3
|
||||
const idx = (currentPlayerId.charCodeAt(0) % 3) + 1;
|
||||
roleImage = `/assets/images/characters/evil_minion_${idx}.png`;
|
||||
@@ -228,8 +210,7 @@ export default function GameBoard({ gameState, currentPlayerId, actions }: GameB
|
||||
if (gameState.phase === 'roll_call' as any) {
|
||||
return (
|
||||
<div className="relative w-full h-screen flex flex-col items-center justify-center bg-black overflow-hidden text-white font-mono">
|
||||
{/* Debug Info */}
|
||||
<DebugInfo />
|
||||
|
||||
|
||||
<div className="absolute inset-0 z-0">
|
||||
<Image src="/assets/images/ui/bg_roll_call.png" alt="Resistance HQ" fill className="object-cover" />
|
||||
@@ -251,8 +232,6 @@ export default function GameBoard({ gameState, currentPlayerId, actions }: GameB
|
||||
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-8">
|
||||
{gameState.players.map((p, i) => {
|
||||
// Asignar avatar determinista basado en charCode
|
||||
const avatarIdx = (p.name.length % 3) + 1;
|
||||
return (
|
||||
<motion.div
|
||||
key={p.id}
|
||||
@@ -263,7 +242,7 @@ export default function GameBoard({ gameState, currentPlayerId, actions }: GameB
|
||||
>
|
||||
<div className="w-32 h-32 rounded-full border-4 border-gray-400 overflow-hidden relative shadow-2xl bg-black">
|
||||
<Image
|
||||
src={`/assets/images/characters/avatar_${avatarIdx}.png`}
|
||||
src={`/assets/images/characters/${p.avatar}`}
|
||||
alt="Avatar"
|
||||
fill
|
||||
className="object-cover grayscale contrast-125"
|
||||
@@ -283,8 +262,6 @@ export default function GameBoard({ gameState, currentPlayerId, actions }: GameB
|
||||
|
||||
return (
|
||||
<div className="relative w-full h-screen flex flex-col items-center overflow-hidden">
|
||||
{/* Debug Info */}
|
||||
<DebugInfo />
|
||||
|
||||
<div className="absolute inset-0 z-0 opacity-40">
|
||||
<Image src="/assets/images/ui/bg_game.png" alt="Game Background" fill className="object-cover" />
|
||||
@@ -310,8 +287,12 @@ export default function GameBoard({ gameState, currentPlayerId, actions }: GameB
|
||||
return (
|
||||
<div
|
||||
key={idx}
|
||||
className="absolute w-[12%] aspect-square flex items-center justify-center transform -translate-x-1/2 -translate-y-1/2"
|
||||
style={{ left: coord.left, top: coord.top }}
|
||||
className="absolute w-[10%] aspect-square flex items-center justify-center"
|
||||
style={{
|
||||
left: coord.left,
|
||||
top: coord.top,
|
||||
transform: 'translate(-50%, -50%)'
|
||||
}}
|
||||
>
|
||||
{/* Marcador de Ronda Actual */}
|
||||
{isCurrent && (
|
||||
@@ -335,17 +316,21 @@ export default function GameBoard({ gameState, currentPlayerId, actions }: GameB
|
||||
{result === true && (
|
||||
<motion.div
|
||||
initial={{ scale: 0 }} animate={{ scale: 1 }}
|
||||
className="absolute inset-0 z-20"
|
||||
className="absolute inset-0 z-20 flex items-center justify-center"
|
||||
>
|
||||
<Image src="/assets/images/tokens/marker_score_blue.png" alt="Success" fill className="object-contain drop-shadow-lg" />
|
||||
<div className="w-[80%] h-[80%] relative">
|
||||
<Image src="/assets/images/tokens/marker_score_blue.png" alt="Success" fill className="object-contain drop-shadow-lg" />
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
{result === false && (
|
||||
<motion.div
|
||||
initial={{ scale: 0 }} animate={{ scale: 1 }}
|
||||
className="absolute inset-0 z-20"
|
||||
className="absolute inset-0 z-20 flex items-center justify-center"
|
||||
>
|
||||
<Image src="/assets/images/tokens/marker_score_red.png" alt="Fail" fill className="object-contain drop-shadow-lg" />
|
||||
<div className="w-[80%] h-[80%] relative">
|
||||
<Image src="/assets/images/tokens/marker_score_red.png" alt="Fail" fill className="object-contain drop-shadow-lg" />
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</div>
|
||||
@@ -633,6 +618,7 @@ export default function GameBoard({ gameState, currentPlayerId, actions }: GameB
|
||||
{gameState.phase === 'mission_result' as any && (
|
||||
<MissionResult
|
||||
gameState={gameState}
|
||||
isHost={isHost}
|
||||
onContinue={() => isHost && actions.finishMissionResult()}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -4,12 +4,13 @@ import { GameState } from '../../../shared/types';
|
||||
interface MissionResultProps {
|
||||
gameState: GameState;
|
||||
onContinue: () => void;
|
||||
isHost: boolean;
|
||||
}
|
||||
|
||||
export default function MissionResult({ gameState, onContinue }: MissionResultProps) {
|
||||
export default function MissionResult({ gameState, onContinue, isHost }: MissionResultProps) {
|
||||
// Obtener la última misión del historial
|
||||
const lastMission = gameState.missionHistory[gameState.missionHistory.length - 1];
|
||||
|
||||
|
||||
if (!lastMission) {
|
||||
return (
|
||||
<div className="fixed inset-0 flex items-center justify-center bg-black/90 z-50">
|
||||
@@ -27,15 +28,15 @@ export default function MissionResult({ gameState, onContinue }: MissionResultPr
|
||||
animate={{ opacity: 1 }}
|
||||
>
|
||||
<motion.h2
|
||||
className={`text - 6xl md: text - 7xl font - bold mb - 8 ${ isSuccess ? 'text-blue-500' : 'text-red-500' } `}
|
||||
className={`text-6xl md:text-7xl font-bold mb-8 ${isSuccess ? 'text-blue-500' : 'text-red-500'}`}
|
||||
initial={{ scale: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ type: 'spring', stiffness: 200, delay: 0.2 }}
|
||||
>
|
||||
{isSuccess ? '¡MISIÓN EXITOSA!' : 'MISIÓN FALLIDA'}
|
||||
</motion.h2>
|
||||
|
||||
<motion.div
|
||||
|
||||
<motion.div
|
||||
className="text-white text-3xl mb-8 bg-black/50 p-6 rounded-xl"
|
||||
initial={{ y: 50, opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
@@ -53,22 +54,33 @@ export default function MissionResult({ gameState, onContinue }: MissionResultPr
|
||||
>
|
||||
<p>Misión {gameState.currentRound} de 5</p>
|
||||
<p className="text-gray-400 text-sm mt-2">
|
||||
Resistencia: {gameState.missionHistory.filter(m => m.isSuccess).length} |
|
||||
Resistencia: {gameState.missionHistory.filter(m => m.isSuccess).length} |
|
||||
Espías: {gameState.missionHistory.filter(m => !m.isSuccess).length}
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<motion.button
|
||||
onClick={onContinue}
|
||||
className="bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white font-bold py-4 px-8 rounded-lg text-xl shadow-lg transform transition-all hover:scale-105"
|
||||
initial={{ y: 50, opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
transition={{ delay: 1.5 }}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
>
|
||||
CONTINUAR →
|
||||
</motion.button>
|
||||
{isHost ? (
|
||||
<motion.button
|
||||
onClick={onContinue}
|
||||
className="bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white font-bold py-4 px-8 rounded-lg text-xl shadow-lg transform transition-all hover:scale-105"
|
||||
initial={{ y: 50, opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
transition={{ delay: 1.5 }}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
>
|
||||
CONTINUAR →
|
||||
</motion.button>
|
||||
) : (
|
||||
<motion.div
|
||||
className="text-white text-lg font-mono bg-black/50 px-6 py-3 rounded-full border border-white/20 animate-pulse"
|
||||
initial={{ y: 50, opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
transition={{ delay: 1.5 }}
|
||||
>
|
||||
Esperando al comandante...
|
||||
</motion.div>
|
||||
)}
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { useEffect } from 'react';
|
||||
import Image from 'next/image';
|
||||
|
||||
interface MissionRevealProps {
|
||||
votes: boolean[];
|
||||
@@ -7,11 +8,11 @@ interface MissionRevealProps {
|
||||
}
|
||||
|
||||
export default function MissionReveal({ votes, onFinished }: MissionRevealProps) {
|
||||
// Timer de seguridad: 10 segundos y avanza
|
||||
// Timer de seguridad: 5 segundos y avanza
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
if (onFinished) onFinished();
|
||||
}, 10000);
|
||||
}, 5000);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [onFinished]);
|
||||
@@ -30,20 +31,22 @@ export default function MissionReveal({ votes, onFinished }: MissionRevealProps)
|
||||
{votes.map((vote, idx) => (
|
||||
<motion.div
|
||||
key={idx}
|
||||
className={`w-48 h-72 rounded-xl flex items-center justify-center text-7xl font-bold text-white shadow-2xl border-4 ${vote
|
||||
? 'bg-gradient-to-br from-blue-600 to-blue-900 border-blue-400 shadow-blue-500/50'
|
||||
: 'bg-gradient-to-br from-red-600 to-red-900 border-red-400 shadow-red-500/50'
|
||||
}`}
|
||||
className="w-48 h-72 rounded-xl flex items-center justify-center shadow-2xl relative overflow-hidden"
|
||||
initial={{ scale: 0, rotateY: 180 }}
|
||||
animate={{ scale: 1, rotateY: 0 }}
|
||||
transition={{
|
||||
delay: idx * 0.8, // Más lento para drama
|
||||
delay: idx * 0.3,
|
||||
type: "spring",
|
||||
stiffness: 200,
|
||||
damping: 20
|
||||
}}
|
||||
>
|
||||
{vote ? '✓' : '✗'}
|
||||
<Image
|
||||
src={vote ? '/assets/images/tokens/vote_approve.png' : '/assets/images/tokens/vote_reject.png'}
|
||||
alt={vote ? 'Éxito' : 'Sabotaje'}
|
||||
fill
|
||||
className="object-contain"
|
||||
/>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
@@ -52,7 +55,7 @@ export default function MissionReveal({ votes, onFinished }: MissionRevealProps)
|
||||
className="text-white text-xl font-mono mt-8"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: votes.length * 0.8 + 1 }}
|
||||
transition={{ delay: votes.length * 0.3 + 0.5 }}
|
||||
>
|
||||
<span className="animate-pulse">Analizando resultado estratégico...</span>
|
||||
</motion.div>
|
||||
|
||||
Reference in New Issue
Block a user