Idiot enforces its card (rev 23) + attack sight-line tracing
Rules rev 23 — IDIOT, per the card and FAQ: - No handling items: pick up / drop of treasures and objects refused (dropping was the exploit: capturing a stolen treasure on your own home, or dropping your own treasure underfoot for an instant cure) - No punching, no thrown dagger / large rock (attacks on players) - No effect on a victim already carrying one of their own treasures Ungated (permissive): counteractions are now castable while idiotized (the one thing the card expressly allows — the gate wrongly blocked them), and goal-aiding spells (IDIOT_AIDS: destroy-wall, teleport, mad-dash, ...) per the FAQ's 'you could, however, destroy a wall'. Sight tracing: while an LOS attack sits on the stack, the board draws the line it traveled — straight when direct, leg by leg through both warp mouths (with pulsing rings) when the maze's wraparound carried it. Engine traceSight/traceSightFor/stackSightTrace; overlay in Board.svelte; shown live and in replays. Verified against H3PC's cross-board Idiot cast. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0138A8CjeQRpvzKxuMfz1Bqc
This commit is contained in:
co-authored by
Claude Fable 5
parent
7372bfc5e8
commit
cdeb3d5103
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { Component } from "svelte";
|
||||
import type { GameView } from "@wizwar/engine";
|
||||
import type { GameView, SightTrace } from "@wizwar/engine";
|
||||
import type { Side } from "@wizwar/engine";
|
||||
import type { BoardFx } from "./fx";
|
||||
import { colorIndexOf as sharedColorIndex, wizardColor } from "./colors";
|
||||
@@ -28,6 +28,7 @@
|
||||
ghostSlots = null,
|
||||
onGhostClick,
|
||||
effects = null,
|
||||
sightTrace = null,
|
||||
}: {
|
||||
view: GameView;
|
||||
edgeSelectMode?: boolean;
|
||||
@@ -54,6 +55,9 @@
|
||||
onGhostClick?: (origin: { x: number; y: number }) => void;
|
||||
/** Short-lived spell flourishes; purely cosmetic. */
|
||||
effects?: BoardFx[] | null;
|
||||
/** The sight line an attack in progress traveled — proof against "how
|
||||
* can he even see me?", drawn leg by leg through any warp mouth. */
|
||||
sightTrace?: { from: { x: number; y: number }; to: { x: number; y: number }; trace: SightTrace } | null;
|
||||
} = $props();
|
||||
|
||||
|
||||
@@ -601,6 +605,23 @@
|
||||
{/if}
|
||||
{/each}
|
||||
{/if}
|
||||
<!-- the sight line an attack traveled, leg by leg through any warp mouth -->
|
||||
{#if sightTrace}
|
||||
{@const A = { x: sightTrace.from.x * CELL + CELL / 2, y: sightTrace.from.y * CELL + CELL * 0.36 }}
|
||||
{@const B = { x: sightTrace.to.x * CELL + CELL / 2, y: sightTrace.to.y * CELL + CELL * 0.36 }}
|
||||
<g class="sight-layer" aria-hidden="true">
|
||||
{#if sightTrace.trace.kind === "direct"}
|
||||
<line x1={A.x} y1={A.y} x2={B.x} y2={B.y} class="sight-line" />
|
||||
{:else}
|
||||
{@const entry = { x: sightTrace.trace.entry.x * CELL, y: sightTrace.trace.entry.y * CELL }}
|
||||
{@const exit = { x: sightTrace.trace.exit.x * CELL, y: sightTrace.trace.exit.y * CELL }}
|
||||
<line x1={A.x} y1={A.y} x2={entry.x} y2={entry.y} class="sight-line" />
|
||||
<line x1={exit.x} y1={exit.y} x2={B.x} y2={B.y} class="sight-line" />
|
||||
<circle cx={entry.x} cy={entry.y} r={7} class="sight-mouth" />
|
||||
<circle cx={exit.x} cy={exit.y} r={7} class="sight-mouth" />
|
||||
{/if}
|
||||
</g>
|
||||
{/if}
|
||||
<!-- spell flourishes: one sprite component per effect (src/fx-sprites/) -->
|
||||
<g class="fx-layer" aria-hidden="true">
|
||||
{#each effects ?? [] as fx (fx.id)}
|
||||
@@ -757,6 +778,24 @@
|
||||
.ghost-slot:hover { fill: rgba(122, 162, 122, 0.22); }
|
||||
|
||||
.fx-layer { pointer-events: none; }
|
||||
.sight-layer { pointer-events: none; }
|
||||
.sight-line {
|
||||
stroke: #c9a72a;
|
||||
stroke-width: 2.5;
|
||||
stroke-linecap: round;
|
||||
stroke-dasharray: 7 6;
|
||||
opacity: 0.85;
|
||||
animation: sight-march 0.8s linear infinite;
|
||||
}
|
||||
.sight-mouth {
|
||||
fill: none;
|
||||
stroke: #c9a72a;
|
||||
stroke-width: 2;
|
||||
animation: warp-pulse 1.6s ease-in-out infinite;
|
||||
}
|
||||
@keyframes sight-march {
|
||||
to { stroke-dashoffset: -13; }
|
||||
}
|
||||
.mover { transition: transform 260ms cubic-bezier(0.25, 0.8, 0.35, 1); }
|
||||
.mover.snap { transition: none; }
|
||||
|
||||
@@ -783,6 +822,7 @@
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.marked-cell, .marked-sector, .ghost-slot, .warp-dest { animation: none; }
|
||||
.sight-line, .sight-mouth { animation: none; }
|
||||
.fx-layer { display: none; }
|
||||
.firewall, :global(.token-art.ghosted) { animation: none; }
|
||||
.mover { transition: none; }
|
||||
|
||||
Reference in New Issue
Block a user