Edge cards light their targets; own casts never wrench the neck
Raising an edge-target card now makes every wall and door face breathe faint gold in the pane — the cockpit's answer to the board's edge handles — with hover still brightening the chosen face. And the director's own-magic aim gains a quarter-turn cap: a deed you aimed behind you via the keymap stays behind you, while deeds done TO you may still spin the world. 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
a4a871592b
commit
e2dfbd14e3
@@ -1914,7 +1914,8 @@
|
|||||||
onbody={(id) => (fpvBody = id)}
|
onbody={(id) => (fpvBody = id)}
|
||||||
onWarpStep={(creatureId) => dispatch(creatureId
|
onWarpStep={(creatureId) => dispatch(creatureId
|
||||||
? { type: "creatureWarpStep", creatureId }
|
? { type: "creatureWarpStep", creatureId }
|
||||||
: { type: "warpStep" })} />
|
: { type: "warpStep" })}
|
||||||
|
edgeSelect={edgeSelectMode && yourMoment} />
|
||||||
{/if}
|
{/if}
|
||||||
<Board
|
<Board
|
||||||
{view}
|
{view}
|
||||||
|
|||||||
@@ -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, onbody = null, onWarpStep = null }: {
|
let { view, batch, onhide, onstride = null, canStride = false, ontarget = null, onfacing = null, litCells = null, edgeSelect = false, onpickup = null, onCreatureMove = null, onCreatureAttack = null, onbody = null, onWarpStep = 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. */
|
||||||
@@ -34,6 +34,8 @@
|
|||||||
onfacing?: ((side: Side) => void) | null;
|
onfacing?: ((side: Side) => void) | null;
|
||||||
/** Cell-card eligibility, shared with the board's dimming aid. */
|
/** Cell-card eligibility, shared with the board's dimming aid. */
|
||||||
litCells?: Set<string> | null;
|
litCells?: Set<string> | null;
|
||||||
|
/** An edge-target card is raised: wall faces offer themselves. */
|
||||||
|
edgeSelect?: boolean;
|
||||||
/** The at-your-feet tray's grab: the pane cannot look down, so what
|
/** The at-your-feet tray's grab: the pane cannot look down, so what
|
||||||
* lies in your own square shows as clickable icons instead. */
|
* lies in your own square shows as clickable icons instead. */
|
||||||
onpickup?: ((w: { kind: "treasure" | "object"; id: string }) => void) | null;
|
onpickup?: ((w: { kind: "treasure" | "object"; id: string }) => void) | null;
|
||||||
@@ -346,11 +348,19 @@
|
|||||||
return takeCutaway(m.position.x + 0.5, m.position.y + 0.5);
|
return takeCutaway(m.position.x + 0.5, m.position.y + 0.5);
|
||||||
} else if (aim && !aim.self) {
|
} else if (aim && !aim.self) {
|
||||||
const ax = aim.x + 0.5, ay = aim.y + 0.5;
|
const ax = aim.x + 0.5, ay = aim.y + 0.5;
|
||||||
|
const wantFacing = Math.atan2(ay - ty, ax - tx);
|
||||||
|
// Your own magic turns your head — but never wrenches it: a deed
|
||||||
|
// you aimed BEHIND you via the keymap stays behind you. Things
|
||||||
|
// done TO you may still spin the world.
|
||||||
|
if (actor === povId && Math.abs(shortestArc(camF, wantFacing)) > Math.PI * 0.55) {
|
||||||
|
// deliberately unaimed
|
||||||
|
} else {
|
||||||
const toAim = Math.hypot(ax - tx, ay - ty);
|
const toAim = Math.hypot(ax - tx, ay - ty);
|
||||||
const sight = castRay(v, tx, ty, Math.atan2(ay - ty, ax - tx));
|
const sight = castRay(v, tx, ty, wantFacing);
|
||||||
if (sight.warped || sight.dist < toAim - 0.4) return takeCutaway(ax, ay);
|
if (sight.warped || sight.dist < toAim - 0.4) return takeCutaway(ax, ay);
|
||||||
targetFacing = Math.atan2(ay - ty, ax - tx);
|
targetFacing = wantFacing;
|
||||||
aimed = true;
|
aimed = true;
|
||||||
|
}
|
||||||
} else if (performance.now() >= manualUntil) {
|
} else if (performance.now() >= manualUntil) {
|
||||||
// Another wizard acting in your sight line turns your head.
|
// Another wizard acting in your sight line turns your head.
|
||||||
const other = actor !== povId ? v.players.find((p) => p.id === actor && p.alive) : null;
|
const other = actor !== povId ? v.players.find((p) => p.id === actor && p.alive) : null;
|
||||||
@@ -455,7 +465,7 @@
|
|||||||
<FirstPerson {view} povId={cutawayShot ? "" : (possessedId ?? povId)}
|
<FirstPerson {view} povId={cutawayShot ? "" : (possessedId ?? povId)}
|
||||||
x={cam.x} y={cam.y} facing={cam.facing} width={960} height={400}
|
x={cam.x} y={cam.y} facing={cam.facing} width={960} height={400}
|
||||||
fx={fpFx} posOverride={actorPos}
|
fx={fpFx} posOverride={actorPos}
|
||||||
ontarget={handleTarget} {litCells} />
|
ontarget={handleTarget} {litCells} {edgeSelect} />
|
||||||
<button class="drive" onclick={() => manualTurn(1)} aria-label="turn right">›</button>
|
<button class="drive" onclick={() => manualTurn(1)} aria-label="turn right">›</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="bezel-bottom">
|
<div class="bezel-bottom">
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
rubble = [],
|
rubble = [],
|
||||||
ontarget,
|
ontarget,
|
||||||
litCells = null,
|
litCells = null,
|
||||||
|
edgeSelect = false,
|
||||||
}: {
|
}: {
|
||||||
view: GameView;
|
view: GameView;
|
||||||
povId: string;
|
povId: string;
|
||||||
@@ -45,6 +46,9 @@
|
|||||||
/** Squares a selected cell-target card may aim at: the pane dims the
|
/** Squares a selected cell-target card may aim at: the pane dims the
|
||||||
* ineligible ground exactly as the board dims its squares. */
|
* ineligible ground exactly as the board dims its squares. */
|
||||||
litCells?: Set<string> | null;
|
litCells?: Set<string> | null;
|
||||||
|
/** An edge-target card is raised: every wall and door face glows
|
||||||
|
* faintly, the pane's answer to the board's edge handles. */
|
||||||
|
edgeSelect?: boolean;
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
const FOV = Math.PI / 2.9;
|
const FOV = Math.PI / 2.9;
|
||||||
@@ -386,6 +390,12 @@
|
|||||||
ctx.fillStyle = `rgba(120,70,220,${haze})`;
|
ctx.fillStyle = `rgba(120,70,220,${haze})`;
|
||||||
ctx.fillRect(col, top, 1, wallH);
|
ctx.fillRect(col, top, 1, wallH);
|
||||||
}
|
}
|
||||||
|
if (edgeSelect && !hit.frame && (hit.kind === "wall" || hit.kind === "door") && hit.edge) {
|
||||||
|
// The raised edge-card's invitation: faces breathe gold.
|
||||||
|
const offer = 0.10 + 0.05 * Math.sin(time / 420 + hit.worldU * 3);
|
||||||
|
ctx.fillStyle = `rgba(201,167,42,${Math.max(0, offer)})`;
|
||||||
|
ctx.fillRect(col, top, 1, wallH);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sprites, far to near, sliced against the depth buffer.
|
// Sprites, far to near, sliced against the depth buffer.
|
||||||
|
|||||||
Reference in New Issue
Block a user