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,
|
||||
showScoreboard,
|
||||
setShowScoreboard,
|
||||
showPopups,
|
||||
setShowPopups,
|
||||
registerPlayer,
|
||||
removePlayer,
|
||||
formParty,
|
||||
|
|
@ -202,6 +204,8 @@ export function App() {
|
|||
playerCount={boardState.player_count}
|
||||
isConcluded={isConcluded}
|
||||
onOpenScoreboard={() => setShowScoreboard(true)}
|
||||
showPopups={showPopups}
|
||||
onTogglePopups={setShowPopups}
|
||||
/>
|
||||
|
||||
{/* Main Content Area */}
|
||||
|
|
@ -233,6 +237,8 @@ export function App() {
|
|||
onStepBot={stepActiveBotTurn}
|
||||
isAutoPlaying={isAutoPlaying}
|
||||
onToggleAutoPlay={() => setIsAutoPlaying((prev) => !prev)}
|
||||
showPopups={showPopups}
|
||||
onTogglePopups={setShowPopups}
|
||||
/>
|
||||
|
||||
{/* Sidebar Player Roster with Turn Order & Parties */}
|
||||
|
|
@ -278,10 +284,12 @@ export function App() {
|
|||
/>
|
||||
|
||||
{/* 3-Bout D20 Battle Modal */}
|
||||
{showPopups && (
|
||||
<BattleModal
|
||||
battle={activeBattle}
|
||||
onClose={handleCloseBattle}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Wizard Challenge Reward Selector Prompt Modal */}
|
||||
<WizardPromptModal
|
||||
|
|
@ -293,10 +301,12 @@ export function App() {
|
|||
/>
|
||||
|
||||
{/* 3-Bout D20 Wizard Challenge Modal */}
|
||||
{showPopups && (
|
||||
<WizardChallengeModal
|
||||
challenge={activeWizardChallenge}
|
||||
onClose={handleCloseWizardChallenge}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Game Conclusion Scoreboard Modal */}
|
||||
{showScoreboard && boardState.conclusion && boardState.conclusion.concluded && (
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ interface HeaderProps {
|
|||
playerCount: number;
|
||||
isConcluded?: boolean;
|
||||
onOpenScoreboard?: () => void;
|
||||
showPopups?: boolean;
|
||||
onTogglePopups?: (value?: boolean) => void;
|
||||
}
|
||||
|
||||
export const Header: React.FC<HeaderProps> = ({
|
||||
|
|
@ -22,6 +24,8 @@ export const Header: React.FC<HeaderProps> = ({
|
|||
playerCount,
|
||||
isConcluded,
|
||||
onOpenScoreboard,
|
||||
showPopups = true,
|
||||
onTogglePopups,
|
||||
}) => {
|
||||
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">
|
||||
|
|
@ -108,6 +112,33 @@ export const Header: React.FC<HeaderProps> = ({
|
|||
</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
|
||||
href="/docs"
|
||||
target="_blank"
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ interface MovementControlsProps {
|
|||
onStepBot: () => void;
|
||||
isAutoPlaying: boolean;
|
||||
onToggleAutoPlay: () => void;
|
||||
showPopups?: boolean;
|
||||
onTogglePopups?: (value?: boolean) => void;
|
||||
}
|
||||
|
||||
export const MovementControls: React.FC<MovementControlsProps> = ({
|
||||
|
|
@ -26,6 +28,8 @@ export const MovementControls: React.FC<MovementControlsProps> = ({
|
|||
onStepBot,
|
||||
isAutoPlaying,
|
||||
onToggleAutoPlay,
|
||||
showPopups = true,
|
||||
onTogglePopups,
|
||||
}) => {
|
||||
const isStarted = Boolean(boardState.turn?.game_started);
|
||||
const currentTurnId = boardState.turn.current_player_id;
|
||||
|
|
@ -382,6 +386,28 @@ export const MovementControls: React.FC<MovementControlsProps> = ({
|
|||
</button>
|
||||
</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">
|
||||
{isStarted ? 'WASD / Arrows / Numpad to move' : 'Bots can join & depart in Lobby'}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ export function useGameSocket() {
|
|||
const [activeBattle, setActiveBattle] = useState<BattleResult | null>(null);
|
||||
const [activeWizardChallenge, setActiveWizardChallenge] = useState<WizardChallengeResult | null>(null);
|
||||
const [showScoreboard, setShowScoreboard] = useState(false);
|
||||
const [showPopups, setShowPopups] = useState(true);
|
||||
|
||||
const wsRef = useRef<WebSocket | null>(null);
|
||||
const reconnectTimeoutRef = useRef<number | null>(null);
|
||||
|
|
@ -59,6 +60,20 @@ export function useGameSocket() {
|
|||
activeBattleRef.current = activeBattle;
|
||||
const activeWizardChallengeRef = useRef<WizardChallengeResult | null>(null);
|
||||
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 () => {
|
||||
try {
|
||||
|
|
@ -205,7 +220,9 @@ export function useGameSocket() {
|
|||
turn: data.turn ?? prev.turn,
|
||||
}));
|
||||
const b: BattleResult = data.battle;
|
||||
if (showPopupsRef.current) {
|
||||
setActiveBattle(b);
|
||||
}
|
||||
setLastEventMessage(`⚔️ 3-Bout D20 Battle: ${b.winner_party_name} defeated ${b.defeated_party_name}!`);
|
||||
} else if (data.event === 'wizard_challenge_resolved') {
|
||||
setBoardState((prev) => ({
|
||||
|
|
@ -215,7 +232,9 @@ export function useGameSocket() {
|
|||
turn: data.turn ?? prev.turn,
|
||||
}));
|
||||
const c: WizardChallengeResult = data.challenge_result || data.challenge;
|
||||
if (showPopupsRef.current) {
|
||||
setActiveWizardChallenge(c);
|
||||
}
|
||||
const wizName = c.wizard_name || 'Gary the Wizard';
|
||||
let rewardLabel = `+${c.score_change} score`;
|
||||
if (c.reward_chosen === 'strength') {
|
||||
|
|
@ -413,7 +432,9 @@ export function useGameSocket() {
|
|||
throw new Error(err.detail || 'Battle failed');
|
||||
}
|
||||
const result: BattleResult = await res.json();
|
||||
if (showPopupsRef.current) {
|
||||
setActiveBattle(result);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
|
|
@ -432,8 +453,10 @@ export function useGameSocket() {
|
|||
setSelectedPlayer((curr) => (curr?.id === data.player.id ? data.player : curr));
|
||||
}
|
||||
if (data.battle_triggered && data.battle_result) {
|
||||
if (showPopupsRef.current) {
|
||||
setActiveBattle(data.battle_result);
|
||||
}
|
||||
}
|
||||
if (data.game_concluded && data.game_concluded.concluded) {
|
||||
setIsAutoPlaying(false);
|
||||
setShowScoreboard(true);
|
||||
|
|
@ -490,7 +513,9 @@ export function useGameSocket() {
|
|||
throw new Error(err.detail || 'Failed to challenge wizard');
|
||||
}
|
||||
const data: WizardChallengeResult = await res.json();
|
||||
if (showPopupsRef.current) {
|
||||
setActiveWizardChallenge(data);
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
|
|
@ -515,11 +540,15 @@ export function useGameSocket() {
|
|||
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}!`);
|
||||
} else if (data.action_taken === 'battled' && 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}!`);
|
||||
} else if (data.action_taken === 'challenged_wizard' && data.wizard_challenge_result) {
|
||||
const wcr = data.wizard_challenge_result;
|
||||
if (showPopupsRef.current) {
|
||||
setActiveWizardChallenge(wcr);
|
||||
}
|
||||
const wizName = wcr.wizard_name || 'Gary the Wizard';
|
||||
setLastEventMessage(
|
||||
wcr.player_won
|
||||
|
|
@ -529,8 +558,10 @@ export function useGameSocket() {
|
|||
} 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)!`);
|
||||
} else if (data.move_result?.battle_result) {
|
||||
if (showPopupsRef.current) {
|
||||
setActiveBattle(data.move_result.battle_result);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.game_concluded && data.game_concluded.concluded) {
|
||||
setIsAutoPlaying(false);
|
||||
|
|
@ -575,6 +606,8 @@ export function useGameSocket() {
|
|||
setActiveWizardChallenge,
|
||||
showScoreboard,
|
||||
setShowScoreboard,
|
||||
showPopups,
|
||||
setShowPopups: updateShowPopups,
|
||||
registerPlayer,
|
||||
removePlayer,
|
||||
formParty,
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
[metadata]
|
||||
version = 1.4
|
||||
version = 1.5
|
||||
|
|
|
|||
Loading…
Reference in New Issue