The eyes turn to meet magic thrown at them, even after a stride
Springing the Trap never showed its fireball: Rival stepped into the watched corridor facing the way he walked, and the ambush's fireball flew in from the mouth behind him while only the caption told of it. The director now aims at incoming magic and punches — the caster's or attacker's square, straight or through a mouth — and a stride that ends with something to watch turns once more after the walk, the slate holding the outcome until the eyes face it. The fireball comes through the wormhole at the camera, then the blow lands. The trap's first beat re-blessed for it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jm2auWk6RP71CjaAb4FMoG
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
86856781df
commit
86070b42a3
Binary file not shown.
|
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 321 KiB |
@@ -377,7 +377,17 @@
|
|||||||
const GROUNDWORK = new Set(["spellCast", "squareFilled", "creatureCreated",
|
const GROUNDWORK = new Set(["spellCast", "squareFilled", "creatureCreated",
|
||||||
"treasurePickedUp", "treasureDropped", "objectDropped"]);
|
"treasurePickedUp", "treasureDropped", "objectDropped"]);
|
||||||
const groundwork = aim?.self && step.events.some((e) => GROUNDWORK.has(e.type));
|
const groundwork = aim?.self && step.events.some((e) => GROUNDWORK.has(e.type));
|
||||||
if (camReady && !willCut && dist > 0.05 && !hurled) { targetFacing = Math.atan2(dy, dx); aimed = true; }
|
/** A stride that ends with something to watch — an ambush that
|
||||||
|
* fired as the step landed — turns once more after the walk. */
|
||||||
|
let afterArc = 0;
|
||||||
|
if (camReady && !willCut && dist > 0.05 && !hurled) {
|
||||||
|
targetFacing = Math.atan2(dy, dx);
|
||||||
|
aimed = true;
|
||||||
|
if (aim && !aim.self) {
|
||||||
|
const h = sightline(v, tx, ty, aim.x + 0.5, aim.y + 0.5);
|
||||||
|
if (h !== null) afterArc = shortestArc(targetFacing, h);
|
||||||
|
}
|
||||||
|
}
|
||||||
else if (groundwork || underfoot) {
|
else if (groundwork || underfoot) {
|
||||||
targetPitch = LOOK_DOWN;
|
targetPitch = LOOK_DOWN;
|
||||||
aimed = true;
|
aimed = true;
|
||||||
@@ -448,8 +458,8 @@
|
|||||||
// The reel's LAST step may be a stride into a corner: the walk keeps
|
// The reel's LAST step may be a stride into a corner: the walk keeps
|
||||||
// its own facing, then the camera turns once more to a view worth
|
// its own facing, then the camera turns once more to a view worth
|
||||||
// ending on.
|
// ending on.
|
||||||
let closingArc = 0;
|
let closingArc = afterArc;
|
||||||
if (atEnd && aimed && castRay(v, tx, ty, targetFacing).dist < 1.2) {
|
if (closingArc === 0 && atEnd && aimed && castRay(v, tx, ty, targetFacing).dist < 1.2) {
|
||||||
const current = viewScoreAt(tx, ty, targetFacing);
|
const current = viewScoreAt(tx, ty, targetFacing);
|
||||||
let bestA = targetFacing, bestScore = current;
|
let bestA = targetFacing, bestScore = current;
|
||||||
for (const a of [0, Math.PI / 2, Math.PI, -Math.PI / 2]) {
|
for (const a of [0, Math.PI / 2, Math.PI, -Math.PI / 2]) {
|
||||||
@@ -498,8 +508,12 @@
|
|||||||
const turnMs = (hurled ? 0 : Math.min(260, Math.abs(arc) * 180)) / speed;
|
const turnMs = (hurled ? 0 : Math.min(260, Math.abs(arc) * 180)) / speed;
|
||||||
const walkMs = (dist > 0.05 ? (hurled ? 260 : 420) : 0) / speed;
|
const walkMs = (dist > 0.05 ? (hurled ? 260 : 420) : 0) / speed;
|
||||||
const tiltMs = (Math.abs(targetPitch - fromP) > 0.01 ? 340 : 0) / speed;
|
const tiltMs = (Math.abs(targetPitch - fromP) > 0.01 ? 340 : 0) / speed;
|
||||||
const clearSlate = setSlate(aimed && (aim || underfoot) ? Math.max(turnMs, tiltMs) + 120 / speed : 0);
|
|
||||||
const closingMs = Math.min(300, Math.abs(closingArc) * 200) / speed;
|
const closingMs = Math.min(300, Math.abs(closingArc) * 200) / speed;
|
||||||
|
// The outcome waits until the eyes face it: after the turn (and,
|
||||||
|
// for a stride that turns at its end, after the walk and that turn).
|
||||||
|
const clearSlate = setSlate(aimed && (aim || underfoot)
|
||||||
|
? Math.max(turnMs, tiltMs) + (afterArc !== 0 ? walkMs + closingMs : 0) + 120 / speed
|
||||||
|
: 0);
|
||||||
const t0 = performance.now() + dieMs;
|
const t0 = performance.now() + dieMs;
|
||||||
let raf = 0;
|
let raf = 0;
|
||||||
const tick = (now: number) => {
|
const tick = (now: number) => {
|
||||||
|
|||||||
@@ -41,6 +41,12 @@ export function aimOfEvents(
|
|||||||
cell = e.targetCell ?? view.players.find((p) => p.id === e.target)?.position ?? null;
|
cell = e.targetCell ?? view.players.find((p) => p.id === e.target)?.position ?? null;
|
||||||
} else if (e.type === "attackResolved" && e.attacker === povId) {
|
} else if (e.type === "attackResolved" && e.attacker === povId) {
|
||||||
cell = view.players.find((p) => p.id === e.defender)?.position ?? null;
|
cell = view.players.find((p) => p.id === e.defender)?.position ?? null;
|
||||||
|
} else if (e.type === "spellCast" && e.target === povId && e.caster !== povId) {
|
||||||
|
// Magic thrown AT these eyes: they turn to where it came from —
|
||||||
|
// the ambusher down the corridor, or through the mouth.
|
||||||
|
cell = view.players.find((p) => p.id === e.caster)?.position ?? null;
|
||||||
|
} else if (e.type === "punched" && e.target === povId) {
|
||||||
|
cell = view.players.find((p) => p.id === e.attacker)?.position ?? null;
|
||||||
} else if (e.type === "objectThrown" && e.attacker === povId) {
|
} else if (e.type === "objectThrown" && e.attacker === povId) {
|
||||||
cell = e.landedAt;
|
cell = e.landedAt;
|
||||||
} else if (
|
} else if (
|
||||||
|
|||||||
Reference in New Issue
Block a user