Files
feuerwerk/src/core/graph/nodes/action/animationNodes.ts
T
adminandClaude Opus 5.5 d6fd000160
game-maker / gate (push) Successful in 3m11s
game-maker / suite (push) Failing after 12m9s
Let a behavior hear the end of an animation it played itself
A behavior that played an animation on the parent of its owner never
heard it finish. On animation finished listened to the owner and to
what stands below it, and the parent stands above.

- Play animation notes on the player which behavior asked for it.
- On animation finished hears, as before, every animation on the
  owner or below it, and now also every animation the behavior
  played itself, on whatever object. Its output object names that
  object.
- Two behaviors that both played the same animation on the same
  object each hear its end once.
- A behavior that is called by Run behavior or heard by an On node
  counts as the behavior it sits in.
- The description of the node and the engine prompt say so.

Tests:
- Gate on melektaus-atx: 218 files, 3392 tests, CHECK EXIT 0.
- Two new unit tests: the behavior hears the animation on the parent
  of its owner while a bystander does not, and two starters hear it
  once each.
- Both counter-probes bite.
- e2e 6,17-26 on melektaus-atx: 480 checks, all pass.
- No new e2e check, and nothing was looked at in the browser. The
  rule for called and heard behaviors has no test of its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 22:37:04 +02:00

88 lines
3.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { pauseAnimation, playAnimation, stopAnimation } from '../../../model/animation';
import type { NodeDef, PortDef } from '../../NodeRegistry';
import { asBoolean, asEntity, asNumber, asString } from '../../values';
import { bool, exec, num, obj, withDefault } from '../shared';
/**
* The `animation` input of the animation nodes; unconnected it shows the animation picker, the
* way `soundPort` shows the sound picker. An `Animation` literal or a list item is what feeds it.
*/
export const animationPort = (): PortDef => ({
key: 'animation',
label: 'animation',
type: 'string',
field: { key: 'animation', label: 'Animation', kind: 'animation', default: '' },
});
export const PlayAnimation: NodeDef = {
type: 'PlayAnimation',
label: 'Play animation',
category: 'action',
description:
'Plays an animation on an object: its rows reach the object and what is placed inside it, so every ' +
'instance plays on its own. A paused one runs on from where it stands; one already running keeps its ' +
'frame but wins again over the others on the object. Blend fades numbers and colors over that many ' +
'seconds from what they held when it took over; everything else switches at once. Blend out is for ' +
'Once (reset): when its frames run out it fades back to where it started over that many seconds. ' +
'Restart starts one already running or paused over from its first frame – blended in from where it ' +
'stands when Blend is set.',
inputs: [
exec(),
obj('object'),
animationPort(),
withDefault(num('blend', 'blend (s)'), 0),
withDefault(num('blendOut', 'blend out (s)'), 0),
withDefault(bool('restart'), false),
],
outputs: [exec()],
params: [],
run: (ctx, inputs) => {
const target = asEntity(inputs.object);
const animation = asString(inputs.animation);
if (target && animation)
playAnimation(
target,
animation,
Math.max(0, asNumber(inputs.blend)),
Math.max(0, asNumber(inputs.blendOut)),
asBoolean(inputs.restart),
ctx.self,
);
return { next: 'exec' };
},
};
export const StopAnimation: NodeDef = {
type: 'StopAnimation',
label: 'Stop animation',
category: 'action',
description:
'Ends an animation on an object where it stands – the values it set stay. With a blend it fades out ' +
'over that many seconds instead, back to what another animation sets there or else to what the ' +
'values were when it started. Leave Animation at None to end every one.',
inputs: [exec(), obj('object'), animationPort(), withDefault(num('blend', 'blend (s)'), 0)],
outputs: [exec()],
params: [],
run: (_ctx, inputs) => {
const target = asEntity(inputs.object);
if (target) stopAnimation(target, asString(inputs.animation), Math.max(0, asNumber(inputs.blend)));
return { next: 'exec' };
},
};
export const PauseAnimation: NodeDef = {
type: 'PauseAnimation',
label: 'Pause animation',
category: 'action',
description:
'Holds an animation on an object at its frame until Play animation lets it run on. Leave Animation at None to hold every one.',
inputs: [exec(), obj('object'), animationPort()],
outputs: [exec()],
params: [],
run: (_ctx, inputs) => {
const target = asEntity(inputs.object);
if (target) pauseAnimation(target, asString(inputs.animation));
return { next: 'exec' };
},
};