1.5 - option to hide popups for auto playing
Build and Publish Docker Image / Build and Push Docker Image (push) Successful in 1m6s
Details
Build and Publish Docker Image / Build and Push Docker Image (push) Successful in 1m6s
Details
This commit is contained in:
parent
65f76a00ac
commit
4c69a86726
|
|
@ -47,6 +47,8 @@ export function App() {
|
||||||
setActiveWizardChallenge,
|
setActiveWizardChallenge,
|
||||||
showScoreboard,
|
showScoreboard,
|
||||||
setShowScoreboard,
|
setShowScoreboard,
|
||||||
|
showPopups,
|
||||||
|
setShowPopups,
|
||||||
registerPlayer,
|
registerPlayer,
|
||||||
removePlayer,
|
removePlayer,
|
||||||
formParty,
|
formParty,
|
||||||
|
|
@ -202,6 +204,8 @@ export function App() {
|
||||||
playerCount={boardState.player_count}
|
playerCount={boardState.player_count}
|
||||||
isConcluded={isConcluded}
|
isConcluded={isConcluded}
|
||||||
onOpenScoreboard={() => setShowScoreboard(true)}
|
onOpenScoreboard={() => setShowScoreboard(true)}
|
||||||
|
showPopups={showPopups}
|
||||||
|
onTogglePopups={setShowPopups}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Main Content Area */}
|
{/* Main Content Area */}
|
||||||
|
|
@ -233,6 +237,8 @@ export function App() {
|
||||||
onStepBot={stepActiveBotTurn}
|
onStepBot={stepActiveBotTurn}
|
||||||
isAutoPlaying={isAutoPlaying}
|
isAutoPlaying={isAutoPlaying}
|
||||||
onToggleAutoPlay={() => setIsAutoPlaying((prev) => !prev)}
|
onToggleAutoPlay={() => setIsAutoPlaying((prev) => !prev)}
|
||||||
|
showPopups={showPopups}
|
||||||
|
onTogglePopups={setShowPopups}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Sidebar Player Roster with Turn Order & Parties */}
|
{/* Sidebar Player Roster with Turn Order & Parties */}
|
||||||
|
|
@ -278,10 +284,12 @@ export function App() {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 3-Bout D20 Battle Modal */}
|
{/* 3-Bout D20 Battle Modal */}
|
||||||
<BattleModal
|
{showPopups && (
|
||||||
battle={activeBattle}
|
<BattleModal
|
||||||
onClose={handleCloseBattle}
|
battle={activeBattle}
|
||||||
/>
|
onClose={handleCloseBattle}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Wizard Challenge Reward Selector Prompt Modal */}
|
{/* Wizard Challenge Reward Selector Prompt Modal */}
|
||||||
<WizardPromptModal
|
<WizardPromptModal
|
||||||
|
|
@ -293,10 +301,12 @@ export function App() {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 3-Bout D20 Wizard Challenge Modal */}
|
{/* 3-Bout D20 Wizard Challenge Modal */}
|
||||||
<WizardChallengeModal
|
{showPopups && (
|
||||||
challenge={activeWizardChallenge}
|
<WizardChallengeModal
|
||||||
onClose={handleCloseWizardChallenge}
|
challenge={activeWizardChallenge}
|
||||||
/>
|
onClose={handleCloseWizardChallenge}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Game Conclusion Scoreboard Modal */}
|
{/* Game Conclusion Scoreboard Modal */}
|
||||||
{showScoreboard && boardState.conclusion && boardState.conclusion.concluded && (
|
{showScoreboard && boardState.conclusion && boardState.conclusion.concluded && (
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ interface HeaderProps {
|
||||||
playerCount: number;
|
playerCount: number;
|
||||||
isConcluded?: boolean;
|
isConcluded?: boolean;
|
||||||
onOpenScoreboard?: () => void;
|
onOpenScoreboard?: () => void;
|
||||||
|
showPopups?: boolean;
|
||||||
|
onTogglePopups?: (value?: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Header: React.FC<HeaderProps> = ({
|
export const Header: React.FC<HeaderProps> = ({
|
||||||
|
|
@ -22,6 +24,8 @@ export const Header: React.FC<HeaderProps> = ({
|
||||||
playerCount,
|
playerCount,
|
||||||
isConcluded,
|
isConcluded,
|
||||||
onOpenScoreboard,
|
onOpenScoreboard,
|
||||||
|
showPopups = true,
|
||||||
|
onTogglePopups,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<header className="h-16 border-b border-slate-800 bg-slate-900/90 backdrop-blur px-6 flex items-center justify-between z-10">
|
<header className="h-16 border-b border-slate-800 bg-slate-900/90 backdrop-blur px-6 flex items-center justify-between z-10">
|
||||||
|
|
@ -108,6 +112,33 @@ export const Header: React.FC<HeaderProps> = ({
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Battle & Wizard Result Popups Toggle */}
|
||||||
|
<label
|
||||||
|
className={`text-xs font-mono font-medium px-2.5 py-1.5 rounded-lg border transition-all flex items-center gap-2 cursor-pointer select-none ${
|
||||||
|
showPopups
|
||||||
|
? 'bg-slate-800/90 text-sky-300 border-sky-500/50 shadow-sm shadow-sky-950/50 hover:bg-slate-800'
|
||||||
|
: 'bg-slate-950/60 text-slate-400 border-slate-800 hover:text-slate-300 hover:border-slate-700'
|
||||||
|
}`}
|
||||||
|
title={
|
||||||
|
showPopups
|
||||||
|
? 'Result popups enabled: Click to hide battle and wizard popups'
|
||||||
|
: 'Result popups disabled: Click to show battle and wizard popups'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={showPopups}
|
||||||
|
onChange={(e) => onTogglePopups?.(e.target.checked)}
|
||||||
|
className="w-3.5 h-3.5 rounded accent-sky-500 cursor-pointer"
|
||||||
|
/>
|
||||||
|
<span className="flex items-center gap-1.5">
|
||||||
|
<span>Popups:</span>
|
||||||
|
<strong className={showPopups ? 'text-sky-300' : 'text-slate-500'}>
|
||||||
|
{showPopups ? 'ON' : 'OFF'}
|
||||||
|
</strong>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
<a
|
<a
|
||||||
href="/docs"
|
href="/docs"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ interface MovementControlsProps {
|
||||||
onStepBot: () => void;
|
onStepBot: () => void;
|
||||||
isAutoPlaying: boolean;
|
isAutoPlaying: boolean;
|
||||||
onToggleAutoPlay: () => void;
|
onToggleAutoPlay: () => void;
|
||||||
|
showPopups?: boolean;
|
||||||
|
onTogglePopups?: (value?: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MovementControls: React.FC<MovementControlsProps> = ({
|
export const MovementControls: React.FC<MovementControlsProps> = ({
|
||||||
|
|
@ -26,6 +28,8 @@ export const MovementControls: React.FC<MovementControlsProps> = ({
|
||||||
onStepBot,
|
onStepBot,
|
||||||
isAutoPlaying,
|
isAutoPlaying,
|
||||||
onToggleAutoPlay,
|
onToggleAutoPlay,
|
||||||
|
showPopups = true,
|
||||||
|
onTogglePopups,
|
||||||
}) => {
|
}) => {
|
||||||
const isStarted = Boolean(boardState.turn?.game_started);
|
const isStarted = Boolean(boardState.turn?.game_started);
|
||||||
const currentTurnId = boardState.turn.current_player_id;
|
const currentTurnId = boardState.turn.current_player_id;
|
||||||
|
|
@ -382,6 +386,28 @@ export const MovementControls: React.FC<MovementControlsProps> = ({
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Result Popups Toggle */}
|
||||||
|
<div className="pt-2 border-t border-slate-800/80 flex items-center justify-between px-0.5">
|
||||||
|
<label
|
||||||
|
className="flex items-center gap-2 cursor-pointer select-none text-xs font-mono text-slate-400 hover:text-slate-200 transition-colors"
|
||||||
|
title={
|
||||||
|
showPopups
|
||||||
|
? 'Result popups enabled: Click to hide battle and wizard popups'
|
||||||
|
: 'Result popups disabled: Click to show battle and wizard popups'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={showPopups}
|
||||||
|
onChange={(e) => onTogglePopups?.(e.target.checked)}
|
||||||
|
className="w-3.5 h-3.5 rounded accent-sky-500 cursor-pointer"
|
||||||
|
/>
|
||||||
|
<span className="text-[11px]">
|
||||||
|
Popups: <strong className={showPopups ? 'text-sky-300' : 'text-slate-500'}>{showPopups ? 'ON' : 'OFF'}</strong>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="text-[10px] text-slate-500 font-mono text-center">
|
<div className="text-[10px] text-slate-500 font-mono text-center">
|
||||||
{isStarted ? 'WASD / Arrows / Numpad to move' : 'Bots can join & depart in Lobby'}
|
{isStarted ? 'WASD / Arrows / Numpad to move' : 'Bots can join & depart in Lobby'}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ export function useGameSocket() {
|
||||||
const [activeBattle, setActiveBattle] = useState<BattleResult | null>(null);
|
const [activeBattle, setActiveBattle] = useState<BattleResult | null>(null);
|
||||||
const [activeWizardChallenge, setActiveWizardChallenge] = useState<WizardChallengeResult | null>(null);
|
const [activeWizardChallenge, setActiveWizardChallenge] = useState<WizardChallengeResult | null>(null);
|
||||||
const [showScoreboard, setShowScoreboard] = useState(false);
|
const [showScoreboard, setShowScoreboard] = useState(false);
|
||||||
|
const [showPopups, setShowPopups] = useState(true);
|
||||||
|
|
||||||
const wsRef = useRef<WebSocket | null>(null);
|
const wsRef = useRef<WebSocket | null>(null);
|
||||||
const reconnectTimeoutRef = useRef<number | null>(null);
|
const reconnectTimeoutRef = useRef<number | null>(null);
|
||||||
|
|
@ -59,6 +60,20 @@ export function useGameSocket() {
|
||||||
activeBattleRef.current = activeBattle;
|
activeBattleRef.current = activeBattle;
|
||||||
const activeWizardChallengeRef = useRef<WizardChallengeResult | null>(null);
|
const activeWizardChallengeRef = useRef<WizardChallengeResult | null>(null);
|
||||||
activeWizardChallengeRef.current = activeWizardChallenge;
|
activeWizardChallengeRef.current = activeWizardChallenge;
|
||||||
|
const showPopupsRef = useRef(true);
|
||||||
|
showPopupsRef.current = showPopups;
|
||||||
|
|
||||||
|
const updateShowPopups = useCallback((val?: boolean | ((prev: boolean) => boolean)) => {
|
||||||
|
setShowPopups((prev) => {
|
||||||
|
const next = typeof val === 'function' ? val(prev) : typeof val === 'boolean' ? val : !prev;
|
||||||
|
showPopupsRef.current = next;
|
||||||
|
if (!next) {
|
||||||
|
setActiveBattle(null);
|
||||||
|
setActiveWizardChallenge(null);
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
const fetchBoard = useCallback(async () => {
|
const fetchBoard = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -205,7 +220,9 @@ export function useGameSocket() {
|
||||||
turn: data.turn ?? prev.turn,
|
turn: data.turn ?? prev.turn,
|
||||||
}));
|
}));
|
||||||
const b: BattleResult = data.battle;
|
const b: BattleResult = data.battle;
|
||||||
setActiveBattle(b);
|
if (showPopupsRef.current) {
|
||||||
|
setActiveBattle(b);
|
||||||
|
}
|
||||||
setLastEventMessage(`⚔️ 3-Bout D20 Battle: ${b.winner_party_name} defeated ${b.defeated_party_name}!`);
|
setLastEventMessage(`⚔️ 3-Bout D20 Battle: ${b.winner_party_name} defeated ${b.defeated_party_name}!`);
|
||||||
} else if (data.event === 'wizard_challenge_resolved') {
|
} else if (data.event === 'wizard_challenge_resolved') {
|
||||||
setBoardState((prev) => ({
|
setBoardState((prev) => ({
|
||||||
|
|
@ -215,7 +232,9 @@ export function useGameSocket() {
|
||||||
turn: data.turn ?? prev.turn,
|
turn: data.turn ?? prev.turn,
|
||||||
}));
|
}));
|
||||||
const c: WizardChallengeResult = data.challenge_result || data.challenge;
|
const c: WizardChallengeResult = data.challenge_result || data.challenge;
|
||||||
setActiveWizardChallenge(c);
|
if (showPopupsRef.current) {
|
||||||
|
setActiveWizardChallenge(c);
|
||||||
|
}
|
||||||
const wizName = c.wizard_name || 'Gary the Wizard';
|
const wizName = c.wizard_name || 'Gary the Wizard';
|
||||||
let rewardLabel = `+${c.score_change} score`;
|
let rewardLabel = `+${c.score_change} score`;
|
||||||
if (c.reward_chosen === 'strength') {
|
if (c.reward_chosen === 'strength') {
|
||||||
|
|
@ -413,7 +432,9 @@ export function useGameSocket() {
|
||||||
throw new Error(err.detail || 'Battle failed');
|
throw new Error(err.detail || 'Battle failed');
|
||||||
}
|
}
|
||||||
const result: BattleResult = await res.json();
|
const result: BattleResult = await res.json();
|
||||||
setActiveBattle(result);
|
if (showPopupsRef.current) {
|
||||||
|
setActiveBattle(result);
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -432,7 +453,9 @@ export function useGameSocket() {
|
||||||
setSelectedPlayer((curr) => (curr?.id === data.player.id ? data.player : curr));
|
setSelectedPlayer((curr) => (curr?.id === data.player.id ? data.player : curr));
|
||||||
}
|
}
|
||||||
if (data.battle_triggered && data.battle_result) {
|
if (data.battle_triggered && data.battle_result) {
|
||||||
setActiveBattle(data.battle_result);
|
if (showPopupsRef.current) {
|
||||||
|
setActiveBattle(data.battle_result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (data.game_concluded && data.game_concluded.concluded) {
|
if (data.game_concluded && data.game_concluded.concluded) {
|
||||||
setIsAutoPlaying(false);
|
setIsAutoPlaying(false);
|
||||||
|
|
@ -490,7 +513,9 @@ export function useGameSocket() {
|
||||||
throw new Error(err.detail || 'Failed to challenge wizard');
|
throw new Error(err.detail || 'Failed to challenge wizard');
|
||||||
}
|
}
|
||||||
const data: WizardChallengeResult = await res.json();
|
const data: WizardChallengeResult = await res.json();
|
||||||
setActiveWizardChallenge(data);
|
if (showPopupsRef.current) {
|
||||||
|
setActiveWizardChallenge(data);
|
||||||
|
}
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -515,11 +540,15 @@ export function useGameSocket() {
|
||||||
if (data.action_taken === 'formed_party' && data.formed_party) {
|
if (data.action_taken === 'formed_party' && data.formed_party) {
|
||||||
setLastEventMessage(`🤝 ${data.player_name} formed party "${data.formed_party.name}" under leader ${data.formed_party.leader_name}!`);
|
setLastEventMessage(`🤝 ${data.player_name} formed party "${data.formed_party.name}" under leader ${data.formed_party.leader_name}!`);
|
||||||
} else if (data.action_taken === 'battled' && data.battle_result) {
|
} else if (data.action_taken === 'battled' && data.battle_result) {
|
||||||
setActiveBattle(data.battle_result);
|
if (showPopupsRef.current) {
|
||||||
|
setActiveBattle(data.battle_result);
|
||||||
|
}
|
||||||
setLastEventMessage(`⚔️ Battle clash: ${data.battle_result.winner_party_name} defeated ${data.battle_result.defeated_party_name}!`);
|
setLastEventMessage(`⚔️ Battle clash: ${data.battle_result.winner_party_name} defeated ${data.battle_result.defeated_party_name}!`);
|
||||||
} else if (data.action_taken === 'challenged_wizard' && data.wizard_challenge_result) {
|
} else if (data.action_taken === 'challenged_wizard' && data.wizard_challenge_result) {
|
||||||
const wcr = data.wizard_challenge_result;
|
const wcr = data.wizard_challenge_result;
|
||||||
setActiveWizardChallenge(wcr);
|
if (showPopupsRef.current) {
|
||||||
|
setActiveWizardChallenge(wcr);
|
||||||
|
}
|
||||||
const wizName = wcr.wizard_name || 'Gary the Wizard';
|
const wizName = wcr.wizard_name || 'Gary the Wizard';
|
||||||
setLastEventMessage(
|
setLastEventMessage(
|
||||||
wcr.player_won
|
wcr.player_won
|
||||||
|
|
@ -529,7 +558,9 @@ export function useGameSocket() {
|
||||||
} else if (data.action_taken === 'slept' && data.sleep_result) {
|
} else if (data.action_taken === 'slept' && data.sleep_result) {
|
||||||
setLastEventMessage(`💤 ${data.player_name} took a restful sleep (+${data.sleep_result.health_gained} HP -> ${data.sleep_result.new_health} HP)!`);
|
setLastEventMessage(`💤 ${data.player_name} took a restful sleep (+${data.sleep_result.health_gained} HP -> ${data.sleep_result.new_health} HP)!`);
|
||||||
} else if (data.move_result?.battle_result) {
|
} else if (data.move_result?.battle_result) {
|
||||||
setActiveBattle(data.move_result.battle_result);
|
if (showPopupsRef.current) {
|
||||||
|
setActiveBattle(data.move_result.battle_result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.game_concluded && data.game_concluded.concluded) {
|
if (data.game_concluded && data.game_concluded.concluded) {
|
||||||
|
|
@ -575,6 +606,8 @@ export function useGameSocket() {
|
||||||
setActiveWizardChallenge,
|
setActiveWizardChallenge,
|
||||||
showScoreboard,
|
showScoreboard,
|
||||||
setShowScoreboard,
|
setShowScoreboard,
|
||||||
|
showPopups,
|
||||||
|
setShowPopups: updateShowPopups,
|
||||||
registerPlayer,
|
registerPlayer,
|
||||||
removePlayer,
|
removePlayer,
|
||||||
formParty,
|
formParty,
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
[metadata]
|
[metadata]
|
||||||
version = 1.4
|
version = 1.5
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue