The reel camera looks where its wizard aims, never at a wall by default
A9WK's clip showed the flaw: a turn owner who casts without moving left the camera staring into the wall its default heading happened to face, for the whole turn. The facing ladder grows two rungs: the eyes turn toward their OWN magic — a cast's target or cell, an attack's defender, a thrown object's landing — and when a reel opens on a stationary wizard with nothing to aim at, the camera faces the longest corridor from where they stand instead of hardcoded east. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
4c358d157e
commit
c7e245a32a
@@ -202,16 +202,48 @@
|
||||
(e.type === "knockedBack" || e.type === "shoved" || e.type === "washedBack" ||
|
||||
e.type === "retreatedInHorror") && e.player === povId);
|
||||
const actor = v.players.find((p) => p.id === step.actor);
|
||||
// Where the eyes' own magic went this step: a wizard looks where
|
||||
// they aim, so the camera turns to watch its own spells land.
|
||||
const aim = (() => {
|
||||
for (const e of step.events) {
|
||||
let cell: { x: number; y: number } | null = null;
|
||||
if (e.type === "spellCast" && e.caster === povId) {
|
||||
cell = e.targetCell ?? v.players.find((p) => p.id === e.target)?.position ?? null;
|
||||
} else if (e.type === "attackResolved" && e.attacker === povId) {
|
||||
cell = v.players.find((p) => p.id === e.defender)?.position ?? null;
|
||||
} else if (e.type === "objectThrown" && e.attacker === povId) {
|
||||
cell = e.landedAt;
|
||||
}
|
||||
if (cell && (cell.x !== me.position.x || cell.y !== me.position.y)) return cell;
|
||||
}
|
||||
return null;
|
||||
})();
|
||||
let targetFacing = camF;
|
||||
if (dist > 0.05 && !hurled) targetFacing = Math.atan2(dy, dx);
|
||||
else if (actor && actor.id !== povId &&
|
||||
let aimed = false;
|
||||
if (dist > 0.05 && !hurled) { targetFacing = Math.atan2(dy, dx); aimed = true; }
|
||||
else if (aim) {
|
||||
targetFacing = Math.atan2(aim.y + 0.5 - ty, aim.x + 0.5 - tx);
|
||||
aimed = true;
|
||||
} else if (actor && actor.id !== povId &&
|
||||
(actor.position.x !== me.position.x || actor.position.y !== me.position.y)) {
|
||||
// Turn toward the actor — but only if these eyes could actually see
|
||||
// them: an unbroken straight sight line, no warps bending it.
|
||||
const ax = actor.position.x + 0.5, ay = actor.position.y + 0.5;
|
||||
const toActor = Math.hypot(ax - tx, ay - ty);
|
||||
const ray = castRay(v, tx, ty, Math.atan2(ay - ty, ax - tx));
|
||||
if (!ray.warped && ray.dist > toActor - 0.2) targetFacing = Math.atan2(ay - ty, ax - tx);
|
||||
if (!ray.warped && ray.dist > toActor - 0.2) {
|
||||
targetFacing = Math.atan2(ay - ty, ax - tx);
|
||||
aimed = true;
|
||||
}
|
||||
}
|
||||
if (!camReady && !aimed) {
|
||||
// The reel opens on a wizard standing still: face the longest
|
||||
// corridor rather than whatever wall the default heading found.
|
||||
let deepest = -1;
|
||||
for (const a of [0, Math.PI / 2, Math.PI, -Math.PI / 2]) {
|
||||
const h = castRay(v, tx, ty, a);
|
||||
if (h.dist > deepest) { deepest = h.dist; targetFacing = a; }
|
||||
}
|
||||
}
|
||||
if (!camReady || (dist > 1.6 && !hurled)) {
|
||||
// First frame, or a leap the legs cannot explain: cut.
|
||||
|
||||
Reference in New Issue
Block a user