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; +}