From 6e1d59aef9ee44ea702ec05f5e4e9daf4d73db61 Mon Sep 17 00:00:00 2001 From: CosmicSlothOracle Date: Sun, 1 Feb 2026 01:00:57 +0000 Subject: [PATCH] Improve corruption targeting and dice flow --- src/App.tsx | 97 ++++++++++++++++++++++++++++++++- src/components/GameBoard.tsx | 103 ++++++++++++++++++++++++----------- 2 files changed, 166 insertions(+), 34 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index c1c41e78..16ecf92d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -73,6 +73,80 @@ function AppContent() { const pendingAbility = (gameState as any).pendingAbilitySelect?.type; const corruptionActive = pendingAbility === 'corruption_steal'; const maulwurfActive = pendingAbility === 'maulwurf_steal'; + const pendingAbilityRef = useRef(null); + const corruptionTargetUidRef = useRef(null); + const awaitingCorruptionRollRef = useRef(false); + const awaitingMaulwurfRollRef = useRef(false); + + useEffect(() => { + pendingAbilityRef.current = (gameState as any).pendingAbilitySelect ?? null; + }, [gameState]); + + useEffect(() => { + const handleTargetSelected = (event: Event) => { + const detail = (event as CustomEvent).detail as { targetUid?: number }; + corruptionTargetUidRef.current = detail?.targetUid ?? null; + awaitingCorruptionRollRef.current = Boolean(detail?.targetUid); + }; + + const handleMaulwurfSelect = (event: Event) => { + const detail = (event as CustomEvent).detail as { targetUid?: number }; + awaitingMaulwurfRollRef.current = Boolean(detail?.targetUid); + }; + + const handleClearSelection = () => { + corruptionTargetUidRef.current = null; + awaitingCorruptionRollRef.current = false; + awaitingMaulwurfRollRef.current = false; + }; + + window.addEventListener('pc:corruption_target_selected', handleTargetSelected as EventListener); + window.addEventListener('pc:maulwurf_select_target', handleMaulwurfSelect as EventListener); + window.addEventListener('pc:corruption_resolved', handleClearSelection as EventListener); + window.addEventListener('pc:clear_pending_selection', handleClearSelection as EventListener); + return () => { + window.removeEventListener('pc:corruption_target_selected', handleTargetSelected as EventListener); + window.removeEventListener('pc:maulwurf_select_target', handleMaulwurfSelect as EventListener); + window.removeEventListener('pc:corruption_resolved', handleClearSelection as EventListener); + window.removeEventListener('pc:clear_pending_selection', handleClearSelection as EventListener); + }; + }, []); + + useEffect(() => { + if (maulwurfActive) { + awaitingMaulwurfRollRef.current = true; + } + }, [maulwurfActive]); + + useEffect(() => { + const handleDiceResult = () => { + const pending = pendingAbilityRef.current as any; + if (!pending?.type) return; + + if (pending.type === 'corruption_steal') { + const targetUid = corruptionTargetUidRef.current; + if (!awaitingCorruptionRollRef.current || !targetUid) return; + awaitingCorruptionRollRef.current = false; + window.dispatchEvent(new CustomEvent('pc:corruption_request_roll', { + detail: { player: pending.actorPlayer ?? gameState.current, targetUid }, + })); + } + + if (pending.type === 'maulwurf_steal') { + const targetUid = pending.targetUid as number | undefined; + if (!awaitingMaulwurfRollRef.current || !targetUid) return; + awaitingMaulwurfRollRef.current = false; + window.dispatchEvent(new CustomEvent('pc:maulwurf_request_roll', { + detail: { player: pending.actorPlayer ?? gameState.current, targetUid }, + })); + } + }; + + window.addEventListener('pc:dice_result', handleDiceResult as EventListener); + return () => { + window.removeEventListener('pc:dice_result', handleDiceResult as EventListener); + }; + }, [gameState.current]); useEffect(() => { const handleCorruptionResolved = (event: Event) => { @@ -134,7 +208,7 @@ function AppContent() { title: 'Handkarte auswählen', body: 'Wähle eine Karte aus deiner Hand, um eine Aktion zu starten.', }; - }, [deckBuilderOpen, corruptionActive, selectedHandIndex]); + }, [deckBuilderOpen, corruptionActive, maulwurfActive, selectedHandIndex]); // No global image preloading required @@ -215,6 +289,25 @@ function AppContent() { // Handle game control buttons + if (data.type === 'board_card' && corruptionActive) { + const opponent = gameState.current === 1 ? 2 : 1; + if (data.player === opponent && data.lane === 'aussen') { + const targetUid = data.card?.uid ?? data.card?.id; + if (targetUid) { + log(`🎯 Corruption: Ziel gewählt (${data.card.name}).`); + corruptionTargetUidRef.current = targetUid; + awaitingCorruptionRollRef.current = true; + window.dispatchEvent(new CustomEvent('pc:corruption_pick_target', { + detail: { player: gameState.current, targetUid }, + })); + window.dispatchEvent(new CustomEvent('pc:corruption_target_selected', { + detail: { player: gameState.current, targetUid }, + })); + } + return; + } + } + if (data.type === 'button_pass_turn') { const currentPlayer = gameState.current; logger.info(`🔧 DEBUG: button_pass_turn clicked - currentPlayer: ${currentPlayer}`); @@ -407,7 +500,7 @@ function AppContent() { } catch (e) {} return; } - }, [gameState, selectedHandIndex, playCard, selectHandCard, passTurn, nextTurn, log]); + }, [gameState, selectedHandIndex, playCard, selectHandCard, passTurn, nextTurn, log, corruptionActive]); const handleCardHover = useCallback((data: any) => { setHoveredCard(data); diff --git a/src/components/GameBoard.tsx b/src/components/GameBoard.tsx index 1a2615f4..817c533b 100644 --- a/src/components/GameBoard.tsx +++ b/src/components/GameBoard.tsx @@ -56,6 +56,7 @@ const GameBoard: React.FC = ({ const [corruptionSuccessUids, setCorruptionSuccessUids] = useState>(new Set()); const [corruptionFailUids, setCorruptionFailUids] = useState>(new Set()); const corruptionResultTimers = useRef>(new Map()); + const [corruptionTargetUid, setCorruptionTargetUid] = useState(null); useEffect(() => { const currentUids = new Set(); @@ -111,6 +112,37 @@ const GameBoard: React.FC = ({ } ), []); + useEffect(() => { + const handleTargetSelected = (event: Event) => { + const detail = (event as CustomEvent).detail as { targetUid?: number }; + setCorruptionTargetUid(detail?.targetUid ?? null); + }; + + const handleMaulwurfTarget = (event: Event) => { + const detail = (event as CustomEvent).detail as { targetUid?: number }; + setCorruptionTargetUid(detail?.targetUid ?? null); + }; + + const clearTarget = () => setCorruptionTargetUid(null); + + window.addEventListener('pc:corruption_target_selected', handleTargetSelected as EventListener); + window.addEventListener('pc:maulwurf_select_target', handleMaulwurfTarget as EventListener); + window.addEventListener('pc:corruption_resolved', clearTarget as EventListener); + window.addEventListener('pc:clear_pending_selection', clearTarget as EventListener); + return () => { + window.removeEventListener('pc:corruption_target_selected', handleTargetSelected as EventListener); + window.removeEventListener('pc:maulwurf_select_target', handleMaulwurfTarget as EventListener); + window.removeEventListener('pc:corruption_resolved', clearTarget as EventListener); + window.removeEventListener('pc:clear_pending_selection', clearTarget as EventListener); + }; + }, []); + + useEffect(() => { + if (!corruptionActive && !maulwurfTargetUid) { + setCorruptionTargetUid(null); + } + }, [corruptionActive, maulwurfTargetUid]); + useEffect(() => { const handleCorruptionRoll = (event: Event) => { const detail = (event as CustomEvent).detail as { victim?: 1 | 2 }; @@ -184,31 +216,35 @@ const GameBoard: React.FC = ({ style: React.CSSProperties, data: any, options?: { selected?: boolean; showActivate?: boolean; onActivate?: () => void; highlight?: boolean } - ) => ( -
onCardClick(data)} - onMouseEnter={(event) => handleHover(card, event)} - onMouseMove={(event) => handleHover(card, event)} - onMouseLeave={() => handleHover(null)} - > - {card.name} - {options?.showActivate && options.onActivate && ( - - )} -
- ); + ) => { + const isSuccess = corruptionSuccessUids.has(card.uid); + const isFail = corruptionFailUids.has(card.uid); + return ( +
onCardClick(data)} + onMouseEnter={(event) => handleHover(card, event)} + onMouseMove={(event) => handleHover(card, event)} + onMouseLeave={() => handleHover(null)} + > + {card.name} + {options?.showActivate && options.onActivate && ( + + )} +
+ ); + }; const renderSlot = ( key: string, @@ -234,13 +270,15 @@ const GameBoard: React.FC = ({ lane: 'aussen' | 'innen', label: string, ) => { - const shouldHighlightCorruption = ( + const shouldHighlightCorruption = Boolean( lane === 'aussen' - && ( - (corruptionActive && player === corruptionTargetPlayer) - || (corruptionHold.player && player === corruptionHold.player) - ) + && ( + (corruptionActive && player === corruptionTargetPlayer) + || (corruptionHold.player && player === corruptionHold.player) + ), ); + const targetUid = maulwurfTargetUid ?? corruptionTargetUid; + const canSelectTarget = corruptionActive && player === corruptionTargetPlayer && lane === 'aussen' && !targetUid; const rects = lane === 'aussen' ? getGovernmentRects(player === 1 ? 'player' : 'opponent') : getPublicRects(player === 1 ? 'player' : 'opponent'); @@ -249,20 +287,21 @@ const GameBoard: React.FC = ({ return rects.map((rect, index) => { const style = { left: rect.x, top: rect.y, width: rect.w, height: rect.h } as React.CSSProperties; const card = cards[index]; + const isTargetCard = Boolean(targetUid && card?.uid === targetUid); if (!card) { return renderSlot( `${player}-${lane}-${index}`, style, label, () => onCardClick({ type: 'row_slot', player, lane, index }), - isCorruptionTarget, + shouldHighlightCorruption || canSelectTarget, ); } return renderCard( card, style, { type: 'board_card', player, lane, index, card }, - { highlight: isCorruptionTarget }, + { highlight: shouldHighlightCorruption || canSelectTarget || isTargetCard }, ); }); };