feat: Implement game restart, battlefield styling with art crops and tapped stacks, and initial draw fixes.
Some checks failed
Build and Deploy / build (push) Failing after 1m10s

This commit is contained in:
2025-12-18 20:26:42 +01:00
parent ca7b5bf7fa
commit bc5eda5e2a
35 changed files with 1337 additions and 634 deletions

View File

@@ -82,7 +82,7 @@ define(['./workbox-5a5d9309'], (function (workbox) { 'use strict';
"revision": "3ca0b8505b4bec776b69afdba2768812"
}, {
"url": "index.html",
"revision": "0.lcefu74575c"
"revision": "0.56865l1cj5s"
}], {});
workbox.cleanupOutdatedCaches();
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {

View File

@@ -443,20 +443,42 @@ export const CubeManager: React.FC<CubeManagerProps> = ({ packs, setPacks, avail
const handleReset = () => {
if (window.confirm("Are you sure you want to clear this session? All parsed cards and generated packs will be lost.")) {
setPacks([]);
setInputText('');
setRawScryfallData(null);
setProcessedData(null);
setAvailableLands([]);
setSelectedSets([]);
localStorage.removeItem('cube_inputText');
localStorage.removeItem('cube_rawScryfallData');
localStorage.removeItem('cube_selectedSets');
localStorage.removeItem('cube_viewMode');
localStorage.removeItem('cube_gameTypeFilter');
setViewMode('list');
setGameTypeFilter('all');
// We keep filters and settings as they are user preferences
try {
console.log("Clearing session...");
// 1. Reset Parent State (App.tsx)
setPacks([]);
setAvailableLands([]);
// 2. Explicitly clear parent persistence keys to ensure they are gone immediately
localStorage.removeItem('generatedPacks');
localStorage.removeItem('availableLands');
// 3. Reset Local State
setInputText('');
setRawScryfallData(null);
setProcessedData(null);
setSelectedSets([]);
// 4. Clear Local Persistence
localStorage.removeItem('cube_inputText');
localStorage.removeItem('cube_rawScryfallData');
localStorage.removeItem('cube_selectedSets');
localStorage.removeItem('cube_viewMode');
localStorage.removeItem('cube_gameTypeFilter');
// We can optionally clear source mode, or leave it. Let's leave it for UX continuity or clear it?
// Let's clear it to full reset.
// localStorage.removeItem('cube_sourceMode');
// 5. Reset UI Filters/Views to defaults
setViewMode('list');
setGameTypeFilter('all');
showToast("Session cleared successfully.", "success");
} catch (error) {
console.error("Error clearing session:", error);
showToast("Failed to clear session fully.", "error");
}
}
};

View File

@@ -15,9 +15,10 @@ interface CardComponentProps {
onDragEnd?: (e: React.DragEvent) => void;
style?: React.CSSProperties;
className?: string;
viewMode?: 'normal' | 'cutout';
}
export const CardComponent: React.FC<CardComponentProps> = ({ card, onDragStart, onClick, onContextMenu, onMouseEnter, onMouseLeave, onDrop, onDrag, onDragEnd, style, className }) => {
export const CardComponent: React.FC<CardComponentProps> = ({ card, onDragStart, onClick, onContextMenu, onMouseEnter, onMouseLeave, onDrop, onDrag, onDragEnd, style, className, viewMode = 'normal' }) => {
const { registerCard, unregisterCard } = useGesture();
const cardRef = useRef<HTMLDivElement>(null);
@@ -28,6 +29,16 @@ export const CardComponent: React.FC<CardComponentProps> = ({ card, onDragStart,
return () => unregisterCard(card.instanceId);
}, [card.instanceId]);
// Robustly resolve Art Crop
let imageSrc = card.imageUrl;
if (viewMode === 'cutout' && card.definition) {
if (card.definition.image_uris?.art_crop) {
imageSrc = card.definition.image_uris.art_crop;
} else if (card.definition.card_faces?.[0]?.image_uris?.art_crop) {
imageSrc = card.definition.card_faces[0].image_uris.art_crop;
}
}
return (
<div
ref={cardRef}
@@ -55,7 +66,7 @@ export const CardComponent: React.FC<CardComponentProps> = ({ card, onDragStart,
onMouseLeave={onMouseLeave}
className={`
relative rounded-lg shadow-md cursor-pointer transition-transform hover:scale-105 select-none
${card.tapped ? 'rotate-90' : ''}
${card.tapped ? 'rotate-45' : ''}
${card.zone === 'hand' ? 'w-32 h-44 -ml-12 first:ml-0 hover:z-10 hover:-translate-y-4' : 'w-24 h-32'}
${className || ''}
`}
@@ -64,7 +75,7 @@ export const CardComponent: React.FC<CardComponentProps> = ({ card, onDragStart,
<div className="w-full h-full relative overflow-hidden rounded-lg bg-slate-800 border-2 border-slate-700">
{!card.faceDown ? (
<img
src={card.imageUrl}
src={imageSrc}
alt={card.name}
className="w-full h-full object-cover"
draggable={false}

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,5 @@
import React, { createContext, useContext, useRef, useState, useEffect } from 'react';
import { socketService } from '../../services/SocketService';
import React, { createContext, useContext, useRef, useState } from 'react';
interface GestureContextType {
registerCard: (id: string, element: HTMLElement) => void;

View File

@@ -26,10 +26,16 @@ export const SmartButton: React.FC<SmartButtonProps> = ({ gameState, playerId, o
actionType = 'CANCEL_YIELD';
} else if (isMyPriority) {
if (gameState.step === 'declare_attackers') {
const count = contextData?.attackers?.length || 0;
label = count > 0 ? `Attack with ${count}` : "Skip Combat";
colorClass = "bg-red-600 hover:bg-red-500 text-white shadow-[0_0_15px_rgba(239,68,68,0.5)] animate-pulse";
actionType = 'DECLARE_ATTACKERS';
if (gameState.attackersDeclared) {
label = "Pass (to Blockers)";
colorClass = "bg-emerald-600 hover:bg-emerald-500 text-white shadow-[0_0_15px_rgba(16,185,129,0.5)] animate-pulse";
actionType = 'PASS_PRIORITY';
} else {
const count = contextData?.attackers?.length || 0;
label = count > 0 ? `Attack with ${count}` : "Skip Combat";
colorClass = "bg-red-600 hover:bg-red-500 text-white shadow-[0_0_15px_rgba(239,68,68,0.5)] animate-pulse";
actionType = 'DECLARE_ATTACKERS';
}
} else if (gameState.step === 'declare_blockers') {
// Todo: blockers context
label = "Declare Blockers";

View File

@@ -215,7 +215,7 @@ export const LobbyManager: React.FC<LobbyManagerProps> = ({ generatedPacks, avai
}
}, [activeRoom]);
// Reconnection logic
// Reconnection logic (Initial Mount)
React.useEffect(() => {
const savedRoomId = localStorage.getItem('active_room_id');
if (savedRoomId && !activeRoom && playerId) {
@@ -246,6 +246,29 @@ export const LobbyManager: React.FC<LobbyManagerProps> = ({ generatedPacks, avai
}
}, []);
// Auto-Rejoin on Socket Reconnect (e.g. Server Restart)
React.useEffect(() => {
const socket = socketService.socket;
const onConnect = () => {
if (activeRoom && playerId) {
console.log("Socket reconnected. Attempting to restore session for room:", activeRoom.id);
socketService.emitPromise('rejoin_room', { roomId: activeRoom.id, playerId })
.then((response: any) => {
if (response.success) {
console.log("Session restored successfully.");
} else {
console.warn("Failed to restore session:", response.message);
}
})
.catch(err => console.error("Session restore error:", err));
}
};
socket.on('connect', onConnect);
return () => { socket.off('connect', onConnect); };
}, [activeRoom, playerId]);
// Listener for room updates to switch view
React.useEffect(() => {
const socket = socketService.socket;

View File

@@ -107,9 +107,11 @@ export class PackGeneratorService {
layout: layout,
colors: cardData.colors || [],
image: useLocalImages
? `${window.location.origin}/cards/images/${cardData.set}/${cardData.id}.jpg`
? `${window.location.origin}/cards/images/${cardData.set}/art_full/${cardData.id}.jpg`
: (cardData.image_uris?.normal || cardData.card_faces?.[0]?.image_uris?.normal || ''),
imageArtCrop: cardData.image_uris?.art_crop || cardData.card_faces?.[0]?.image_uris?.art_crop || '',
imageArtCrop: useLocalImages
? `${window.location.origin}/cards/images/${cardData.set}/art_crop/${cardData.id}.jpg`
: (cardData.image_uris?.art_crop || cardData.card_faces?.[0]?.image_uris?.art_crop || ''),
set: cardData.set_name,
setCode: cardData.set,
setType: setType,

View File

@@ -39,8 +39,12 @@ export interface CardInstance {
baseToughness?: number; // Base Toughness
position: { x: number; y: number; z: number }; // For freeform placement
typeLine?: string;
types?: string[];
supertypes?: string[];
subtypes?: string[];
oracleText?: string;
manaCost?: string;
definition?: any;
}
export interface PlayerState {
@@ -68,4 +72,6 @@ export interface GameState {
stack?: StackObject[];
activePlayerId?: string; // Explicitly tracked in strict
priorityPlayerId?: string;
attackersDeclared?: boolean;
blockersDeclared?: boolean;
}