The facing ribbon rides the mount
While the pane possesses a creature, the keymap's golden ribbon moves to the mount's token — same shallow triangle, sized to the smaller body, turning with the borrowed eyes — and leaves the wizard's brow until the rider returns. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015RCWSTnb1KYTPyL4GmhGnF
This commit is contained in:
co-authored by
Claude Fable 5
parent
714c5bef80
commit
e499df22d7
@@ -56,6 +56,8 @@
|
|||||||
let fpBatch = $state<{ n: number; events: GameEvent[]; view: GameView } | null>(null);
|
let fpBatch = $state<{ n: number; events: GameEvent[]; view: GameView } | null>(null);
|
||||||
/** The pane camera's compass quadrant, worn by the keymap token. */
|
/** The pane camera's compass quadrant, worn by the keymap token. */
|
||||||
let fpvFacing = $state<Side | null>(null);
|
let fpvFacing = $state<Side | null>(null);
|
||||||
|
/** The creature the pane is riding, if any: the ribbon moves to it. */
|
||||||
|
let fpvBody = $state<string | null>(null);
|
||||||
let fxCancels: (() => void)[] = [];
|
let fxCancels: (() => void)[] = [];
|
||||||
/** A trap drawn by YOU earns a modal; buried log lines get missed. */
|
/** A trap drawn by YOU earns a modal; buried log lines get missed. */
|
||||||
let trapNotice = $state<string | null>(null);
|
let trapNotice = $state<string | null>(null);
|
||||||
@@ -1908,7 +1910,8 @@
|
|||||||
? { type: "pickUpTreasure", treasureId: w.id }
|
? { type: "pickUpTreasure", treasureId: w.id }
|
||||||
: { type: "pickUpObject", instanceId: w.id })}
|
: { type: "pickUpObject", instanceId: w.id })}
|
||||||
onCreatureMove={(creatureId, direction) => dispatch({ type: "moveCreature", creatureId, direction })}
|
onCreatureMove={(creatureId, direction) => dispatch({ type: "moveCreature", creatureId, direction })}
|
||||||
onCreatureAttack={(creatureId, targetId) => dispatch({ type: "creatureAttack", creatureId, targetId })} />
|
onCreatureAttack={(creatureId, targetId) => dispatch({ type: "creatureAttack", creatureId, targetId })}
|
||||||
|
onbody={(id) => (fpvBody = id)} />
|
||||||
{/if}
|
{/if}
|
||||||
<Board
|
<Board
|
||||||
{view}
|
{view}
|
||||||
@@ -1931,6 +1934,7 @@
|
|||||||
{litCells}
|
{litCells}
|
||||||
{sightTrace}
|
{sightTrace}
|
||||||
povFacing={prefs.liveFp && view.you && !net.spectating ? fpvFacing : null}
|
povFacing={prefs.liveFp && view.you && !net.spectating ? fpvFacing : null}
|
||||||
|
povCreatureId={prefs.liveFp && !net.spectating ? fpvBody : null}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
onEdgeClick,
|
onEdgeClick,
|
||||||
onPlayerClick,
|
onPlayerClick,
|
||||||
povFacing = null,
|
povFacing = null,
|
||||||
|
povCreatureId = null,
|
||||||
onCreatureClick,
|
onCreatureClick,
|
||||||
onWarpClick,
|
onWarpClick,
|
||||||
onCellPeek,
|
onCellPeek,
|
||||||
@@ -47,6 +48,8 @@
|
|||||||
/** Which way YOUR first-person camera faces: a wedge on your token
|
/** Which way YOUR first-person camera faces: a wedge on your token
|
||||||
* keeps the keymap and the pane pointing the same way. */
|
* keeps the keymap and the pane pointing the same way. */
|
||||||
povFacing?: "N" | "E" | "S" | "W" | null;
|
povFacing?: "N" | "E" | "S" | "W" | null;
|
||||||
|
/** While the pane rides a creature, the ribbon moves to ITS token. */
|
||||||
|
povCreatureId?: string | null;
|
||||||
onCreatureClick?: (creatureId: string) => void;
|
onCreatureClick?: (creatureId: string) => void;
|
||||||
onWarpClick?: (cell: { x: number; y: number }, side: Side) => void;
|
onWarpClick?: (cell: { x: number; y: number }, side: Side) => void;
|
||||||
/** Long-press on a square: read the card behind whatever occupies it. */
|
/** Long-press on a square: read the card behind whatever occupies it. */
|
||||||
@@ -592,7 +595,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</g>
|
</g>
|
||||||
{#if povFacing && p.id === view.you}
|
{#if povFacing && !povCreatureId && p.id === view.you}
|
||||||
<!-- A short wide ribbon hugging the token's leading edge: base as
|
<!-- A short wide ribbon hugging the token's leading edge: base as
|
||||||
wide as the token, rising a fifth of that outward. -->
|
wide as the token, rising a fifth of that outward. -->
|
||||||
{@const fa = povFacing === "E" ? 0 : povFacing === "S" ? Math.PI / 2 : povFacing === "W" ? Math.PI : -Math.PI / 2}
|
{@const fa = povFacing === "E" ? 0 : povFacing === "S" ? Math.PI / 2 : povFacing === "W" ? Math.PI : -Math.PI / 2}
|
||||||
@@ -653,6 +656,19 @@
|
|||||||
<text x="0" y="4" class="creature-label">{c.kind === "fire-imp" ? "I" : c.kind === "democratic-monster" ? "D" : c.kind[0]?.toUpperCase()}</text>
|
<text x="0" y="4" class="creature-label">{c.kind === "fire-imp" ? "I" : c.kind === "democratic-monster" ? "D" : c.kind[0]?.toUpperCase()}</text>
|
||||||
{/if}
|
{/if}
|
||||||
</g>
|
</g>
|
||||||
|
{#if povFacing && povCreatureId === c.id}
|
||||||
|
<!-- The rider's facing ribbon, worn by the mount. -->
|
||||||
|
{@const fa2 = povFacing === "E" ? 0 : povFacing === "S" ? Math.PI / 2 : povFacing === "W" ? Math.PI : -Math.PI / 2}
|
||||||
|
{@const ch = CELL * 0.26}
|
||||||
|
{@const cfx = Math.cos(fa2)}
|
||||||
|
{@const cfy = Math.sin(fa2)}
|
||||||
|
{@const cpx = -cfy}
|
||||||
|
{@const cpy = cfx}
|
||||||
|
<polygon
|
||||||
|
points={`${cfx * ch + cpx * ch},${cfy * ch + cpy * ch} ${cfx * ch - cpx * ch},${cfy * ch - cpy * ch} ${cfx * ch * 1.5},${cfy * ch * 1.5}`}
|
||||||
|
class="pov-wedge"
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
</g>
|
</g>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
import { untrack } from "svelte";
|
import { untrack } from "svelte";
|
||||||
import type { GameEvent, GameView, Side } from "@wizwar/engine";
|
import type { GameEvent, GameView, Side } from "@wizwar/engine";
|
||||||
|
|
||||||
let { view, batch, onhide, onstride = null, canStride = false, ontarget = null, onfacing = null, litCells = null, onpickup = null, onCreatureMove = null, onCreatureAttack = null }: {
|
let { view, batch, onhide, onstride = null, canStride = false, ontarget = null, onfacing = null, litCells = null, onpickup = null, onCreatureMove = null, onCreatureAttack = null, onbody = null }: {
|
||||||
view: GameView;
|
view: GameView;
|
||||||
/** The latest live event batch, numbered so each plays once, with
|
/** The latest live event batch, numbered so each plays once, with
|
||||||
* the view the server sent alongside it. */
|
* the view the server sent alongside it. */
|
||||||
@@ -40,6 +40,9 @@
|
|||||||
/** Possession: ride a creature's eyes and drive it from the pane. */
|
/** Possession: ride a creature's eyes and drive it from the pane. */
|
||||||
onCreatureMove?: ((creatureId: string, side: Side) => void) | null;
|
onCreatureMove?: ((creatureId: string, side: Side) => void) | null;
|
||||||
onCreatureAttack?: ((creatureId: string, targetId: string) => void) | null;
|
onCreatureAttack?: ((creatureId: string, targetId: string) => void) | null;
|
||||||
|
/** Reports which creature (if any) the pane is riding, so the keymap
|
||||||
|
* can move the facing ribbon to the mount's token. */
|
||||||
|
onbody?: ((creatureId: string | null) => void) | null;
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
const povId = $derived(view.you);
|
const povId = $derived(view.you);
|
||||||
@@ -363,6 +366,7 @@
|
|||||||
raf = requestAnimationFrame(tick);
|
raf = requestAnimationFrame(tick);
|
||||||
return () => cancelAnimationFrame(raf);
|
return () => cancelAnimationFrame(raf);
|
||||||
});
|
});
|
||||||
|
$effect(() => { onbody?.(possessedId); });
|
||||||
let lastQuad = -99;
|
let lastQuad = -99;
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
const q = ((Math.round(cam.facing / (Math.PI / 2)) % 4) + 4) % 4;
|
const q = ((Math.round(cam.facing / (Math.PI / 2)) % 4) + 4) % 4;
|
||||||
|
|||||||
Reference in New Issue
Block a user