From 312dbf7e924e11f211940420d4bccdc288f571e6 Mon Sep 17 00:00:00 2001 From: L1Z0 <2grif484d@mozmail.com> Date: Tue, 29 Sep 2026 15:36:03 +0200 Subject: [PATCH] tweak --- AnimationDirector.js | 26 +++++++++++++++++- test/animdef-generator.test.js | 49 ++++++++++++++++++++++++++++++++-- 2 files changed, 72 insertions(+), 3 deletions(-) diff --git a/AnimationDirector.js b/AnimationDirector.js index 233aac8..866211a 100644 --- a/AnimationDirector.js +++ b/AnimationDirector.js @@ -426,6 +426,7 @@ type: 'select', options: { '': 'None', V: 'V', A: 'A', M: 'M' }, value: metadata.injector || '', + description: 'Selecting an injector adds gender.male to required actor tags. You can remove that tag afterward.', condition: fieldCondition, }; form[`${base}_injects_into`] = { @@ -486,6 +487,25 @@ }); } + function createInjectorGenderDefaultHandler(form) { + const roles = new Map(Object.entries(form).filter(([key]) => key.endsWith('_injector')) + .map(([key, field]) => [key, field.value || ''])); + return (dialog, result) => { + const updates = {}; + for (const [key, previous] of roles) { + if (!result || !(key in result)) continue; + const role = result[key]; + roles.set(key, role); + if (role === previous || !['V', 'A', 'M'].includes(role)) continue; + const tagsKey = key.replace(/_injector$/, '_actor_tags'); + const tags = uniqueTrimmedStrings(parseCommaSeparatedStrings(result[tagsKey])); + if (tags.includes('gender.male')) continue; + result[tagsKey] = updates[tagsKey] = [...tags, 'gender.male'].join(', '); + } + if (Object.keys(updates).length) dialog.form.setValues(updates, false); + }; + } + function validateActorAnimdefMetadata(metadata, actorLabel) { for (const entityType of metadata.entity_types || []) { if (!RESOURCE_ID_PATTERN.test(entityType)) { @@ -1650,6 +1670,7 @@ }; } + const updateInjectorGenderDefault = createInjectorGenderDefaultHandler(form); const dialog = new Dialog({ id: 'edit_actor_preferences', title: 'Actor Preferences', @@ -1658,9 +1679,10 @@ buttons: ['Cancel', 'Save & Apply'], cancelIndex: 0, confirmIndex: 1, - onFormChange: (result) => { + onFormChange(result) { showBoneTextureOverrides = !!result?.show_bone_texture_overrides; showAnimdefMetadata = !!result?.show_animdef_metadata; + updateInjectorGenderDefault(this, result); }, onConfirm: (result) => { let editing = false; @@ -4088,11 +4110,13 @@ return false; } }; + const updateInjectorGenderDefault = createInjectorGenderDefaultHandler(form); const dialog = new Dialog({ id: 'create_afw_animation_definition_review', title: `Animation Definition: ${group.id}`, width: 760, form, + onFormChange(result) { updateInjectorGenderDefault(this, result); }, buttons: ['Cancel', 'Save', 'Create Animdef'], cancelIndex: 0, confirmIndex: 2, diff --git a/test/animdef-generator.test.js b/test/animdef-generator.test.js index 6200d76..976c955 100644 --- a/test/animdef-generator.test.js +++ b/test/animdef-generator.test.js @@ -8,7 +8,7 @@ function createHarness() { const file = path.join(__dirname, '..', 'AnimationDirector.js'); const source = fs.readFileSync(file, 'utf8').replace( ' Plugin.register(PLUGIN_ID, {', - ' globalThis.adTest = { showAnimationDefinitionReviewDialog };\n Plugin.register(PLUGIN_ID, {' + ' globalThis.adTest = { showAnimationDefinitionReviewDialog, createInjectorGenderDefaultHandler };\n Plugin.register(PLUGIN_ID, {' ); const dialogs = []; const exports = []; @@ -45,7 +45,8 @@ function createHarness() { context.adTest.showAnimationDefinitionReviewDialog(group, analysis); return dialogs.at(-1); } - return { open, exports, errors, project, analysis, setFormResult(result) { formResult = result; } }; + return { open, exports, errors, project, analysis, createInjectorGenderDefaultHandler: context.adTest.createInjectorGenderDefaultHandler, + setFormResult(result) { formResult = result; } }; } function defaults(form) { @@ -54,6 +55,50 @@ function defaults(form) { .map(([key, field]) => [key, field.value])); } +test('selecting an injector adds an editable male tag without enforcing it on export', () => { + for (const role of ['V', 'A', 'M']) { + const h = createHarness(), dialog = h.open(), result = defaults(dialog.form), updates = []; + const instance = { form: { setValues(values, notify) { updates.push(values); assert.equal(notify, false); } } }; + result.stub_actor_1_actor_tags = 'custom.tag'; + result.stub_actor_1_injector = role; + dialog.onFormChange.call(instance, result); + assert.equal(result.stub_actor_1_actor_tags, 'custom.tag, gender.male'); + assert.equal(updates.length, 1); + result.stub_actor_1_actor_tags = 'custom.tag'; + dialog.onFormChange.call(instance, result); + result.display_name = 'Unrelated edit'; + dialog.onFormChange.call(instance, result); + assert.equal(updates.length, 1, 'Manual removal survives unrelated changes'); + result.stub_actor_2_entity_types = 'minecraft:wolf'; + dialog.onConfirm(result); + assert.equal(h.errors.length, 0); + assert.equal(h.exports[0].actors[0].injector, role); + assert.deepEqual(h.exports[0].actors[0].actor_tags, ['custom.tag']); + } +}); + +test('injector defaults preserve loaded roles, existing tags, and actor preferences edits', () => { + const h = createHarness(); + const form = { + preferences_animdef_1_injector: { value: 'V' }, + preferences_animdef_2_injector: { value: '' }, + }; + const update = h.createInjectorGenderDefaultHandler(form), writes = []; + const dialog = { form: { setValues(values) { writes.push(values); } } }; + const result = { preferences_animdef_1_injector: 'V', preferences_animdef_1_actor_tags: 'custom.tag', + preferences_animdef_2_injector: '', preferences_animdef_2_actor_tags: 'gender.male, other.tag' }; + update(dialog, result); + assert.equal(writes.length, 0, 'Opening an imported/saved role does not add a tag'); + result.preferences_animdef_2_injector = 'A'; update(dialog, result); + assert.equal(writes.length, 0, 'Existing male tags are not duplicated'); + result.preferences_animdef_2_injector = ''; update(dialog, result); + assert.equal(result.preferences_animdef_2_actor_tags, 'gender.male, other.tag', 'Deselecting a role does not remove explicit tags'); + result.preferences_animdef_2_actor_tags = 'other.tag'; + result.preferences_animdef_2_injector = 'M'; update(dialog, result); + assert.equal(result.preferences_animdef_2_actor_tags, 'other.tag, gender.male'); + assert.equal(result.preferences_animdef_1_actor_tags, 'custom.tag', 'Only the changed actor is affected'); +}); + 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']);