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>
88 lines
3.3 KiB
TypeScript
88 lines
3.3 KiB
TypeScript
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' };
|
||
},
|
||
};
|