Fable will fix it! Pt 2
This commit is contained in:
152
frontend/src/components/scene/DeepControlPanel.svelte
Normal file
152
frontend/src/components/scene/DeepControlPanel.svelte
Normal file
@@ -0,0 +1,152 @@
|
||||
<script>
|
||||
import { apiRequest } from '../../lib/api';
|
||||
|
||||
export let state;
|
||||
|
||||
let error = '';
|
||||
let challengeTargetId = '';
|
||||
let challengeStakes = '';
|
||||
let selectedObstacles = {};
|
||||
let captainSelectId = '';
|
||||
|
||||
$: openChallenges = (state.challenges || []).filter(c => c.status === 'open');
|
||||
$: challengedObstacleIds = new Set(openChallenges.flatMap(c => JSON.parse(c.obstacle_ids || '[]')));
|
||||
$: scenePirats = state.players.filter(p => p.role === 'pirat');
|
||||
|
||||
async function createChallenge() {
|
||||
error = '';
|
||||
const ids = Object.keys(selectedObstacles).filter(id => selectedObstacles[id]);
|
||||
try {
|
||||
await apiRequest(`/game/${state.game.id}/player/${state.player.id}/challenge/create`, 'POST', {
|
||||
target_player_id: challengeTargetId,
|
||||
obstacle_ids: JSON.stringify(ids),
|
||||
stakes: challengeStakes
|
||||
});
|
||||
challengeTargetId = '';
|
||||
challengeStakes = '';
|
||||
selectedObstacles = {};
|
||||
} catch(e) {
|
||||
error = e.message;
|
||||
}
|
||||
}
|
||||
|
||||
async function setCaptain() {
|
||||
error = '';
|
||||
try {
|
||||
await apiRequest(`/game/${state.game.id}/player/${state.player.id}/set-captain`, 'POST', {
|
||||
target_player_id: captainSelectId
|
||||
});
|
||||
captainSelectId = '';
|
||||
} catch(e) {
|
||||
error = e.message;
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleObjective(targetPlayerId, type) {
|
||||
error = '';
|
||||
try {
|
||||
await apiRequest(`/game/${state.game.id}/player/${targetPlayerId}/objective/toggle`, 'POST', { type });
|
||||
} catch(e) {
|
||||
error = e.message;
|
||||
}
|
||||
}
|
||||
|
||||
async function endScene() {
|
||||
error = '';
|
||||
try {
|
||||
await apiRequest(`/game/${state.game.id}/scene/end`, 'POST');
|
||||
} catch(e) {
|
||||
error = e.message;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="card glass-panel deep-panel-card" style="max-height: 80vh; overflow-y: auto;">
|
||||
<h3 class="deep-text text-center">🌊 Deep Control Panel</h3>
|
||||
|
||||
{#if error}
|
||||
<div class="alert alert-danger">{error}</div>
|
||||
{/if}
|
||||
|
||||
<!-- Call a Challenge -->
|
||||
<div class="sheet-group margin-top">
|
||||
<h4 style="color: var(--gold);">⚔️ Call a Challenge</h4>
|
||||
<p class="info-text" style="font-size: 0.85rem;">Establish the stakes, pick a Pi-Rat, and apply one or more Obstacles. They play one card per Obstacle — beating one passes; each failure breeds a complication.</p>
|
||||
<select class="select-field" bind:value={challengeTargetId} style="width: 100%; margin-bottom: 0.5rem;">
|
||||
<option value="">Challenge which Pi-Rat?</option>
|
||||
{#each scenePirats.filter(p => !p.is_dead) as p}
|
||||
<option value={p.id}>{p.name} (Rank {p.rank})</option>
|
||||
{/each}
|
||||
</select>
|
||||
<div style="margin-bottom: 0.5rem;">
|
||||
{#each state.obstacles as obs}
|
||||
{@const taken = challengedObstacleIds.has(obs.id)}
|
||||
<label class="checkbox-label" style="{taken ? 'opacity: 0.5;' : ''}">
|
||||
<input type="checkbox" bind:checked={selectedObstacles[obs.id]} disabled={taken}>
|
||||
<span>{obs.title}{taken ? ' (in a challenge)' : ''}</span>
|
||||
</label>
|
||||
{/each}
|
||||
</div>
|
||||
<input type="text" class="form-control" placeholder="Stakes (optional): what happens on success/failure?" bind:value={challengeStakes} style="width: 100%; margin-bottom: 0.5rem;">
|
||||
<button class="btn btn-primary btn-full" on:click={createChallenge}
|
||||
disabled={!challengeTargetId || !Object.values(selectedObstacles).some(v => v)}>
|
||||
Call Challenge
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Captain Assignment -->
|
||||
<div class="sheet-group margin-top">
|
||||
<h4 style="color: var(--gold);">🏴☠️ Captaincy</h4>
|
||||
<p class="info-text" style="font-size: 0.85rem;">The Captain holds power until they step down, lose a duel, die, or the ship is lost. Reassign after a leadership duel or resignation.</p>
|
||||
<div style="display: flex; gap: 0.5rem;">
|
||||
<select class="select-field" bind:value={captainSelectId} style="flex: 1;">
|
||||
<option value="">No Captain</option>
|
||||
{#each scenePirats.filter(p => !p.is_dead) as p}
|
||||
<option value={p.id}>{p.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<button class="btn btn-secondary" on:click={setCaptain}>Set</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sheet-group margin-top">
|
||||
<h4 style="color: var(--gold);">Crew Objectives</h4>
|
||||
<div class="objectives-checklist">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" checked={state.game.completed_crew_1} on:change={() => toggleObjective(state.player.id, 'crew_1')}>
|
||||
<span>Steal a Ship</span>
|
||||
</label>
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" checked={state.game.completed_crew_2} on:change={() => toggleObjective(state.player.id, 'crew_2')}>
|
||||
<span>Choose a Captain</span>
|
||||
</label>
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" checked={state.game.completed_crew_3} on:change={() => toggleObjective(state.player.id, 'crew_3')}>
|
||||
<span>Commit Piracy</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sheet-group margin-top">
|
||||
<h4 style="color: var(--gold);">Pi-Rat Personal Objectives</h4>
|
||||
<p class="info-text">You control completion. Only mark in sequence (1 -> 2 -> 3).</p>
|
||||
{#each scenePirats as p}
|
||||
<div style="background: rgba(0,0,0,0.2); padding: 0.5rem; border-radius: 4px; margin-bottom: 0.5rem;">
|
||||
<strong>{p.name}</strong>
|
||||
<div class="objectives-checklist" style="margin-top: 0.25rem;">
|
||||
<label class="checkbox-label"><input type="checkbox" checked={p.completed_personal_1} on:change={() => toggleObjective(p.id, 'personal_1')}> <span>1. Gat</span></label>
|
||||
<label class="checkbox-label"><input type="checkbox" checked={p.completed_personal_2} on:change={() => toggleObjective(p.id, 'personal_2')}> <span>2. Name</span></label>
|
||||
<label class="checkbox-label"><input type="checkbox" checked={p.completed_personal_3} on:change={() => toggleObjective(p.id, 'personal_3')}> <span>3. Die/Retire</span></label>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<!-- End Scene Button -->
|
||||
<div class="scene-upkeep-controls text-center" style="margin-top: 1rem; border-top: 1px solid rgba(255,255,255,0.1); padding-top: 1rem;">
|
||||
<p class="info-text">End the scene once every Pi-Rat has faced a Challenge and had a chance at an Objective, or the Obstacle List is empty.</p>
|
||||
<button on:click={endScene} class="btn btn-danger btn-large btn-full glow-effect">
|
||||
End Scene & Proceed to Ranking
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user