From 1afdb2af2556590e2f9f5b9f3616ff5ef38c5126 Mon Sep 17 00:00:00 2001 From: woahwhattheheck Date: Sun, 6 Sep 2026 15:24:40 -0400 Subject: [PATCH] fix(abilities): stop Golden Wyrm turning around to heal an adjacent ally animation2() re-orients the caster at its target before every cast. That is right for an attack, but Visible Stigmata reaches out to an ally already standing beside the Golden Wyrm, so there is nothing to turn towards. Because the Wyrm occupies three hexes, faceHex() measures from one fixed end of that span. An ally on a frontal diagonal hex can therefore compare as being behind the reference hex and flip the sprite, leaving the Wyrm with its back to the unit it is healing. Let an ability decline the re-facing with `_facesTarget: false` and set it on Visible Stigmata. The caster still resets to its default player facing, so it faces forward rather than spinning. Abilities that say nothing are unchanged. fixes #1965 --- src/__tests__/utility/ability-facing.ts | 21 +++++++++++++++++++++ src/abilities/Golden-Wyrm.ts | 6 ++++++ src/ability.ts | 7 +++++-- src/utility/ability-facing.ts | 22 ++++++++++++++++++++++ 4 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 src/__tests__/utility/ability-facing.ts mode change 100755 => 100644 src/abilities/Golden-Wyrm.ts create mode 100644 src/utility/ability-facing.ts diff --git a/src/__tests__/utility/ability-facing.ts b/src/__tests__/utility/ability-facing.ts new file mode 100644 index 000000000..01821264c --- /dev/null +++ b/src/__tests__/utility/ability-facing.ts @@ -0,0 +1,21 @@ +import { expect, describe, test } from '@jest/globals'; +import { shouldFaceTargetDuringCast } from '../../utility/ability-facing'; + +describe('shouldFaceTargetDuringCast', () => { + test('faces the target when the ability says nothing', () => { + // Every existing ability is in this shape, so none of them change. + expect(shouldFaceTargetDuringCast({})).toBe(true); + }); + + test('faces the target when the ability opts in explicitly', () => { + expect(shouldFaceTargetDuringCast({ _facesTarget: true })).toBe(true); + }); + + test('leaves the caster alone only on an explicit opt out', () => { + expect(shouldFaceTargetDuringCast({ _facesTarget: false })).toBe(false); + }); + + test('treats an undefined flag as opting in', () => { + expect(shouldFaceTargetDuringCast({ _facesTarget: undefined })).toBe(true); + }); +}); diff --git a/src/abilities/Golden-Wyrm.ts b/src/abilities/Golden-Wyrm.ts old mode 100755 new mode 100644 index 7813dd9eb..467db4b0b --- a/src/abilities/Golden-Wyrm.ts +++ b/src/abilities/Golden-Wyrm.ts @@ -322,6 +322,12 @@ export default (G: Game) => { _targetTeam: Team.Ally, + /* Reaching out to heal an ally standing next to the Golden Wyrm should + not spin it around. Its own hexes span three columns, so an ally on a + frontal diagonal hex can compare as being behind it and make it turn its + back on the unit it is healing. */ + _facesTarget: false, + _maxTransferAmount: 50, require: function () { diff --git a/src/ability.ts b/src/ability.ts index ea4eaac47..03afd05f8 100644 --- a/src/ability.ts +++ b/src/ability.ts @@ -10,6 +10,7 @@ import Game from './game'; import { Player, ScoreEvent } from './player'; import { Point } from './utility/pointfacade'; import { getVisibilityAwareDelay } from './utility/time'; +import { shouldFaceTargetDuringCast } from './utility/ability-facing'; /** * Ability Class @@ -142,6 +143,7 @@ export class Ability { _targets: Hex[]; _addOffenseBuff: () => void; _maxTransferAmount: number; + _facesTarget?: boolean; _confirmTarget: (c: Creature) => boolean; _damaged: boolean; _executeHealthThreshold: number; @@ -562,8 +564,9 @@ export class Ability { this.creature.facePlayerDefault(); - // Force creatures to face towards their target - if (args[0]) { + // Force creatures to face towards their target, unless the ability opts + // out because turning the caster around would be wrong for it. + if (args[0] && shouldFaceTargetDuringCast(this)) { if (args[0] instanceof Creature) { this.creature.faceHex(args[0]); } else if (args[0] instanceof Array) { diff --git a/src/utility/ability-facing.ts b/src/utility/ability-facing.ts new file mode 100644 index 000000000..b635b07c4 --- /dev/null +++ b/src/utility/ability-facing.ts @@ -0,0 +1,22 @@ +/** + * `Ability.animation2()` turns the caster to face its target before playing the + * cast animation. That is right for an attack, but wrong for abilities that + * reach out to a unit already beside the caster: the caster spins on the spot, + * and for a multi-hex creature a target on a frontal diagonal hex can even read + * as being behind it, so it turns its back on the ally it is helping. + * + * An ability opts out by declaring `_facesTarget: false`. + */ +export type FacingAwareAbility = { + _facesTarget?: boolean; +}; + +/** + * Whether an ability should re-orient its caster towards the target. + * + * Facing is the default; only an explicit `false` opts out, so abilities that + * say nothing keep the behaviour they have always had. + */ +export function shouldFaceTargetDuringCast(ability: FacingAwareAbility): boolean { + return ability._facesTarget !== false; +}