Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions src/__tests__/ui/queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,4 +112,70 @@ describe('Queue', () => {

expect(div.querySelector('.delay-preview')).toBeNull();
});

test('leaps into its new slot when a unit is delayed', () => {
const div = document.createElement('div');
const queue = new Queue(div);
const animate = Element.prototype.animate as unknown as jest.Mock;

queue.setQueue(
{
queue: [creature({ id: 1 }), creature({ id: 2 }), creature({ id: 3 })],
nextQueue: [],
} as unknown as CreatureQueue,
1,
);

animate.mockClear();

queue.setQueue(
{
queue: [creature({ id: 1, delayed: true }), creature({ id: 2 }), creature({ id: 3 })],
nextQueue: [],
} as unknown as CreatureQueue,
1,
);

// The leap is the only animation with a raised midpoint at offset 0.5.
const leaps = animate.mock.calls
.map((call) => call[0] as Keyframe[])
.filter((frames) =>
frames.some(
(frame) =>
frame.offset === 0.5 && /translateY\(-\d+px\)/.test(String(frame.transform)),
),
);

expect(leaps).toHaveLength(1);
});

test('slides without leaping when the queue merely shuffles forward', () => {
const div = document.createElement('div');
const queue = new Queue(div);
const animate = Element.prototype.animate as unknown as jest.Mock;

queue.setQueue(
{
queue: [creature({ id: 1 }), creature({ id: 2 }), creature({ id: 3 })],
nextQueue: [],
} as unknown as CreatureQueue,
1,
);

animate.mockClear();

queue.setQueue(
{
queue: [creature({ id: 2 }), creature({ id: 3 })],
nextQueue: [],
} as unknown as CreatureQueue,
1,
);

const leaps = animate.mock.calls
.map((call) => call[0] as Keyframe[])
.filter((frames) => frames.some((frame) => frame.offset === 0.5));

expect(leaps).toHaveLength(0);
});
});
46 changes: 46 additions & 0 deletions src/ui/queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { getAvatarSet } from '../style/avatar-styles';

const CONST = {
animDurationMS: 500,
delayLeapHeightPx: 60,
};

export class Queue {
Expand Down Expand Up @@ -235,6 +236,9 @@ export class Queue {
const hash = newV.getHash();
if (oldVDict.hasOwnProperty(hash)) {
newV.el = oldVDict[hash].el;
// Keep where this vignette used to sit so an update can tell a
// backwards move (a delay) from an ordinary shuffle forwards.
newV.previousQueuePosition = oldVDict[hash].queuePosition;
}
}

Expand Down Expand Up @@ -295,6 +299,7 @@ export class Queue {

class Vignette {
queuePosition = -1;
previousQueuePosition = -1;
turnNumber = -1;
el: HTMLElement;
eventHandlers: QueueEventHandlers = {};
Expand Down Expand Up @@ -555,6 +560,11 @@ class CreatureVignette extends Vignette {

animateUpdate(queuePosition: number, x: number) {
const scale = this.isActiveCreature ? 1.25 : 1.0;

if (this.isMovingBackFromDelay(queuePosition)) {
return this.animateDelayLeap(x, scale);
}

const keyframes = [{ transform: `translateX(${x}px) translateY(0px) scale(${scale})` }];
const animation = this.el.animate(keyframes, {
duration: CONST.animDurationMS,
Expand All @@ -564,6 +574,42 @@ class CreatureVignette extends Vignette {
return animation;
}

/**
* Delaying is the only thing that sends a unit backwards through the
* current turn's queue - everything else shuffles it forwards as units
* ahead of it act or die. So a backwards move here is the delay landing.
*/
private isMovingBackFromDelay(queuePosition: number) {
return (
this.creature.isDelayed &&
this.turnNumberIsCurrentTurn &&
this.previousQueuePosition >= 0 &&
queuePosition > this.previousQueuePosition
);
}

/**
* Hop into the new slot instead of sliding along the row. A flat slide
* reads as the whole queue shuffling; the arc reads as this one unit
* taking itself to the back.
*/
private animateDelayLeap(x: number, scale: number) {
const keyframes = [
{
transform: `translateX(${x}px) translateY(${-CONST.delayLeapHeightPx}px) scale(${scale})`,
easing: 'ease-out',
offset: 0.5,
},
{ transform: `translateX(${x}px) translateY(0px) scale(${scale})`, easing: 'ease-in' },
];
const animation = this.el.animate(keyframes, {
duration: CONST.animDurationMS,
fill: 'forwards',
});
animation.commitStyles();
return animation;
}

animateDelete(queuePosition: number, x: number) {
if (this.creature.dead) {
return this.animateKill(x);
Expand Down