tweaks and improvements

This commit is contained in:
2026-09-29 01:58:15 +02:00
parent 61df6bb0d3
commit 53a5c47549
4 changed files with 166 additions and 31 deletions
+39 -1
View File
@@ -13,6 +13,7 @@ function createHarness() {
const dialogs = [];
const exports = [];
const errors = [];
let formResult;
const project = { multiactor_actor_names: JSON.stringify({ 1: 'player', 2: 'wolf' }) };
const context = {
Project: project,
@@ -20,6 +21,7 @@ function createHarness() {
Dialog: class {
constructor(options) { this.options = options; dialogs.push(options); }
show() {}
getFormResult() { return formResult ?? defaults(this.options.form); }
},
Blockbench: {
export(options, callback) { exports.push(JSON.parse(options.content)); callback(); },
@@ -43,7 +45,7 @@ function createHarness() {
context.adTest.showAnimationDefinitionReviewDialog(group, analysis);
return dialogs.at(-1);
}
return { open, exports, errors, project, analysis };
return { open, exports, errors, project, analysis, setFormResult(result) { formResult = result; } };
}
function defaults(form) {
@@ -52,6 +54,42 @@ function defaults(form) {
.map(([key, field]) => [key, field.value]));
}
test('Save retains definition fields in the project without exporting and restores them on reopen', () => {
const h = createHarness(), dialog = h.open(), result = defaults(dialog.form);
assert.deepEqual(Array.from(dialog.buttons), ['Cancel', 'Save', 'Create Animdef']);
assert.equal(dialog.confirmIndex, 2);
result.display_name = 'Saved definition';
result.content_tags = 'saved, tags';
result.weight = '3';
result.stub_stage_1_peaked = true;
result.stub_stage_2_peaked = false;
h.setFormResult(result);
assert.equal(dialog.onButton(1), false);
assert.equal(h.exports.length, 0);
assert.deepEqual(h.errors, []);
assert.equal(h.project.saved, false);
const reopened = h.open();
assert.equal(reopened.form.display_name.value, result.display_name);
assert.equal(reopened.form.content_tags.value, result.content_tags);
assert.equal(reopened.form.weight.value, 3);
assert.equal(reopened.form.stub_stage_1_peaked.value, true);
assert.equal(reopened.form.stub_stage_2_peaked.value, false);
assert.equal(reopened.onConfirm(defaults(reopened.form)), true);
assert.equal(h.exports[0].display_name, result.display_name);
});
test('invalid Save leaves previously saved fields intact', () => {
const h = createHarness(), dialog = h.open(), result = defaults(dialog.form);
result.display_name = 'Original';
h.setFormResult(result); dialog.onButton(1);
const original = h.project.animationdirector_animdef_definitions;
h.setFormResult({ ...result, display_name: 'Invalid', weight: '0' });
dialog.onButton(1);
assert.equal(h.project.animationdirector_animdef_definitions, original);
assert.equal(h.exports.length, 0);
assert.equal(h.errors.length, 1);
});
test('metadata-only playback stages remain editable and survive definition export', () => {
const h = createHarness();
h.analysis.stages = h.analysis.stages.slice(0, 1);
+26 -1
View File
@@ -107,7 +107,7 @@ test('animation menu orders import and creation actions and has no deprecated sp
const menu = h.menus.find((entry) => entry.id === 'animationdirector_anims');
assert.deepEqual(Array.from(menu.children, (action) => action.name), [
'Automatically Import Animation', 'Manual Import', 'Actor Preferences',
'Replace Actor Model', 'Block Requirements', 'Item Props', 'Sound Library...', 'Create Animation Definition', 'Export Animations',
'Replace Actor Model', 'Block Requirements', 'Item Props', 'NoN Sound Library', 'Create Animation Definition', 'Export Animations',
]);
assert.deepEqual(Array.from(menu.children[1].children, (action) => action.name), [
'Import Actor', 'Import Animation', 'Import Animation Definition', 'Pack Folders',
@@ -171,6 +171,31 @@ test('import prefills generator fields, builds guides and retains fields without
assert.equal(h.errors.length, 0);
});
test('importing an animdef replaces saved fields instead of retaining old settings', () => {
const h = harness();
h.project.multiactor_animdef_animators = 'Old author';
h.project.animationdirector_animdef_definitions = JSON.stringify({ test: {
display_name: 'Old', weight: 8, content_tags: ['old'], addon_data: { old: true },
actors: [{ label: 'player', entity_types: ['minecraft:player'] }, { label: 'wolf', entity_types: ['minecraft:wolf'] }],
stages: [{ stage: 1, non_peak: true }, { stage: 2 }],
}, other: { display_name: 'Unchanged' } });
const replacement = { display_name: 'Replacement',
actors: [{ label: 'player', entity_types: ['minecraft:player'] }, { label: 'wolf', entity_types: ['minecraft:wolf'] }],
stages: [{ stage: 1 }, { stage: 2, non_peak: true }],
};
h.applyAnimationDefinitionImport({ id: 'test' }, importAnalysis(), replacement);
const saved = h.getSavedAnimdef('test');
assert.equal(saved.display_name, 'Replacement');
assert.equal(saved.weight, undefined);
assert.equal(saved.content_tags, undefined);
assert.equal(saved.addon_data, undefined);
assert.equal(h.getSavedAnimdef('other').display_name, 'Unchanged');
const dialog = h.dialogs.at(-1);
assert.equal(dialog.form.stub_stage_1_peaked.value, false);
assert.equal(dialog.form.stub_stage_2_peaked.value, true);
assert.equal(dialog.form.animators.value, '');
});
test('import maps explicit labels before positional fallbacks and updates actor references', () => {
const h = harness();
const source = { actors: [{ label: 'wolf', injector: 'V', injects_into: 'bottom' },
+39
View File
@@ -135,6 +135,45 @@ test('mapping retains cue IDs and manual previews, updates managed files and han
assert.equal(points[0].file, '');
});
test('reactive impact uses saved peak-stage metadata separately for each animation stage', (t) => {
const h = harness();
h.storage.set(h.SOUND_SOURCE_KEY, assets(t, { impactdry01: { sounds: ['needsofnature:dry'] },
shot_in01: { sounds: ['needsofnature:peak'] } }, { dry: 'dry', peak: 'peak' }));
const library = h.loadSoundLibrary();
h.context.Project = { animationdirector_animdef_definitions: JSON.stringify({ test: {
stages: [{ stage: 1 }, { stage: 2, non_peak: true }],
} }) };
const normal = { name: 'test.p1', animators: { effects: { sound: [{ data_points: [{ effect: 'reactiveimpact' }] }] } } };
const peaked = { name: 'test.p2', animators: { effects: { sound: [{ data_points: [{ effect: 'reactiveimpact' },
{ effect: 'reactiveimpact', file: 'manual.ogg' }, { effect: 'reactiveimpact_silent' }] }] } } };
h.mapAnimationSounds([normal, peaked]);
assert.equal(normal.animators.effects.sound[0].data_points[0].file, h.resolveSoundAudio('impactdry01'));
assert.equal(peaked.animators.effects.sound[0].data_points[0].file, h.resolveSoundAudio('shot_in01'));
assert.equal(peaked.animators.effects.sound[0].data_points[1].file, 'manual.ogg');
assert.equal(peaked.animators.effects.sound[0].data_points[2].file, '');
h.context.Animation.selected = peaked;
h.showSoundLibrary();
h.context.dialog.component.methods.preview.call({ selected: 'reactiveimpact', error: '' });
assert.equal(h.context.played[0].file, h.resolveSoundAudio('shot_in01'));
h.context.Project.animationdirector_animdef_definitions = JSON.stringify({ test: { stages: [{ stage: 2, non_peak: false }] } });
h.mapAnimationSounds([peaked]);
assert.equal(peaked.animators.effects.sound[0].data_points[0].file, h.resolveSoundAudio('impactdry01'));
assert.equal(h.resolveSoundAudio('random(needsofnature:reactiveimpact)', library, new Set(), normal), h.resolveSoundAudio('impactdry01'));
});
test('missing stage metadata defaults to a normal impact; reused peak stages do not change the source stage', (t) => {
const h = harness();
h.storage.set(h.SOUND_SOURCE_KEY, assets(t, { impactdry01: { sounds: ['needsofnature:dry'] },
shot_in01: { sounds: ['needsofnature:peak'] } }, { dry: 'dry', peak: 'peak' }));
const library = h.loadSoundLibrary();
h.context.Project = { animationdirector_animdef_definitions: JSON.stringify({ test: {
stages: [{ stage: 1 }, { stage: 2, use_stage: 1, non_peak: true }],
} }) };
assert.equal(h.resolveSoundAudio('reactiveimpact', library, new Set(), { name: 'test.p1' }), h.resolveSoundAudio('impactdry01'));
assert.equal(h.resolveSoundAudio('reactiveimpact', library, new Set(), { name: 'test.p2' }), h.resolveSoundAudio('shot_in01'));
assert.equal(h.resolveSoundAudio('reactiveimpact', library, new Set(), { name: 'other.p3' }), h.resolveSoundAudio('impactdry01'));
});
test('insertion is undoable and preserves existing cues at the same time; Apply edits selection', (t) => {
const h = harness();
h.storage.set(h.SOUND_SOURCE_KEY, assets(t, { one: { sounds: ['needsofnature:one'] } }, { one: 'one' }));