tweaks and improvements
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -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' }));
|
||||
|
||||
Reference in New Issue
Block a user