tweaks and improvements
This commit is contained in:
+62
-29
@@ -28,7 +28,7 @@
|
||||
const SOUND_SOURCE_KEY = 'animationdirector_sound_source';
|
||||
const SOUND_SELECTION_BLACKLIST = new Set(['needsofnature:a', 'needsofnature:funkytown']);
|
||||
const SOUND_PREVIEW_PROPERTY = 'animationdirector_audio_preview';
|
||||
const SOUND_EVENTS = ['select_animation', 'load_project', 'select_project', 'update_keyframe_selection'];
|
||||
const SOUND_EVENTS = ['select_animation', 'load_project', 'select_project', 'update_keyframe_selection', 'undo', 'redo'];
|
||||
let soundLibraryAction, soundLibraryDialog, soundLibraryListener, soundPreviewProperty, soundAudition;
|
||||
let soundLibrary, soundLibraryGeneration = 0;
|
||||
const soundAudioUrls = new Map(), soundMappedPoints = new WeakMap();
|
||||
@@ -2826,11 +2826,19 @@
|
||||
return output;
|
||||
}
|
||||
|
||||
function exportAnimationDefinitionStub(group, definition, actorMetadata) {
|
||||
function saveAnimationDefinitionFields(group, definition, actorMetadata) {
|
||||
const undo = typeof Undo !== 'undefined';
|
||||
if (undo) Undo.initEdit({ animdef_edit: true, actor_preferences_edit: true });
|
||||
const mergedMetadata = { ...getProjectActorAnimdefMetadata(), ...actorMetadata };
|
||||
setProjectActorAnimdefMetadata(mergedMetadata);
|
||||
setProjectAnimdefAnimators(definition.animators || []);
|
||||
saveAnimdef(group.id, definition);
|
||||
if (undo) Undo.finishEdit('Save animation definition');
|
||||
mapAnimationSounds(typeof Animation === 'undefined' ? [] : Animation.all);
|
||||
}
|
||||
|
||||
function exportAnimationDefinitionStub(group, definition, actorMetadata) {
|
||||
saveAnimationDefinitionFields(group, definition, actorMetadata);
|
||||
Blockbench.export(
|
||||
{
|
||||
type: 'AFW Animation Definition',
|
||||
@@ -2981,6 +2989,7 @@
|
||||
const props = importPropPreviews(group.id, definition, sourcePaths, readPropPreviews());
|
||||
cancelBlockRequirementUpdate();
|
||||
rebuildBlockRequirementGuides(group.id, entry, definition, true, props.entries);
|
||||
mapAnimationSounds(typeof Animation === 'undefined' ? [] : Animation.all);
|
||||
if (props.warnings.length) Blockbench.showQuickMessage(`Item props: ${props.warnings.join(' ')}`, 6000);
|
||||
void refreshPropPreviews(true);
|
||||
if (blockRequirementPanel?.vue) blockRequirementPanel.vue.load(group.id);
|
||||
@@ -3041,7 +3050,7 @@
|
||||
type: 'info',
|
||||
text:
|
||||
`Creating ${group.id}.json with ${analysis.actorIndices.length} actor(s) and ${analysis.stages.length} stage(s). ` +
|
||||
'Save it under data/<namespace>/afw_animdefs/. Your choices are stored in this Blockbench project for later exports.',
|
||||
'Save stores your fields in this Blockbench project. Create Animdef also exports JSON for data/<namespace>/afw_animdefs/.',
|
||||
},
|
||||
};
|
||||
if (analysis.warnings.length) {
|
||||
@@ -3071,7 +3080,7 @@
|
||||
form.animators = {
|
||||
label: 'Animators',
|
||||
type: 'text',
|
||||
value: (saved.animators || getProjectAnimdefAnimators()).join(', '),
|
||||
value: (saved.animators || (Object.keys(saved).length ? [] : getProjectAnimdefAnimators())).join(', '),
|
||||
placeholder: 'Animator One, Animator Two',
|
||||
description: 'Optional comma-separated individual animation credits. Saved in this Blockbench project.',
|
||||
};
|
||||
@@ -3363,15 +3372,7 @@
|
||||
};
|
||||
});
|
||||
|
||||
const dialog = new Dialog({
|
||||
id: 'create_afw_animation_definition_review',
|
||||
title: `Create Animation Definition: ${group.id}`,
|
||||
width: 760,
|
||||
form,
|
||||
buttons: ['Cancel', 'Create Animdef'],
|
||||
cancelIndex: 0,
|
||||
confirmIndex: 1,
|
||||
onConfirm: (result) => {
|
||||
const submit = (result, exportFile) => {
|
||||
try {
|
||||
const actorMetadata = {};
|
||||
const actors = [];
|
||||
@@ -3525,9 +3526,14 @@
|
||||
}
|
||||
definition.actors = actors;
|
||||
definition.stages = stages;
|
||||
confirmUnresolvedActorTypes(unresolvedLabels, () =>
|
||||
exportAnimationDefinitionStub(group, definition, actorMetadata)
|
||||
);
|
||||
if (exportFile) {
|
||||
confirmUnresolvedActorTypes(unresolvedLabels, () =>
|
||||
exportAnimationDefinitionStub(group, definition, actorMetadata)
|
||||
);
|
||||
} else {
|
||||
saveAnimationDefinitionFields(group, definition, actorMetadata);
|
||||
Blockbench.showQuickMessage(`Saved animation definition fields for ${group.id}.`);
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
Blockbench.showMessageBox({
|
||||
@@ -3537,6 +3543,21 @@
|
||||
});
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const dialog = new Dialog({
|
||||
id: 'create_afw_animation_definition_review',
|
||||
title: `Create Animation Definition: ${group.id}`,
|
||||
width: 760,
|
||||
form,
|
||||
buttons: ['Cancel', 'Save', 'Create Animdef'],
|
||||
cancelIndex: 0,
|
||||
confirmIndex: 2,
|
||||
onConfirm: (result) => submit(result, true),
|
||||
onButton(index) {
|
||||
if (index === 1) {
|
||||
submit(dialog.getFormResult(), false);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
});
|
||||
dialog.show();
|
||||
@@ -5413,28 +5434,36 @@
|
||||
return soundLibrary;
|
||||
}
|
||||
|
||||
function soundCueId(cue) {
|
||||
function isPeakedSoundStage(animation) {
|
||||
if (typeof Project === 'undefined' || !Project) return false;
|
||||
const parsed = parseAfwStageAnimationName(animation);
|
||||
if (!parsed) return false;
|
||||
const stages = getSavedAnimdef(parsed.id).stages;
|
||||
return Array.isArray(stages) && stages.some((stage) => stage.stage === parsed.stageNumber && stage.non_peak === true);
|
||||
}
|
||||
|
||||
function soundCueId(cue, animation) {
|
||||
const text = String(cue || '').trim();
|
||||
const bare = text.replace(/^(?:needsofnature|minecraft):/, '');
|
||||
if (bare.toLowerCase() === 'reactiveimpact_silent') return null;
|
||||
const semantic = { reactiveimpact: 'impactdry01',
|
||||
const semantic = { reactiveimpact: isPeakedSoundStage(animation) ? 'shot_in01' : 'impactdry01',
|
||||
impactdry: 'impactdry01', impactwet: 'impactwet01', birth: 'pop' };
|
||||
if (semantic[bare.toLowerCase()]) return `needsofnature:${semantic[bare.toLowerCase()]}`;
|
||||
const id = text.includes(':') ? text : `needsofnature:${text}`;
|
||||
return importResourceId(id) ? id : null;
|
||||
}
|
||||
|
||||
function resolveSoundAudio(cue, library = ensureSoundLibrary(), visiting = new Set()) {
|
||||
function resolveSoundAudio(cue, library = ensureSoundLibrary(), visiting = new Set(), animation = null) {
|
||||
if (/^random\(/i.test(String(cue).trim())) {
|
||||
const match = String(cue).trim().match(/^random\((.*)\)$/i);
|
||||
if (!match) return null;
|
||||
for (const option of match[1].split(',').slice(0, 256)) {
|
||||
const audio = resolveSoundAudio(option, library, visiting);
|
||||
const audio = resolveSoundAudio(option, library, visiting, animation);
|
||||
if (audio) return audio;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
const id = soundCueId(cue);
|
||||
const id = soundCueId(cue, animation);
|
||||
if (!id || visiting.has(id) || visiting.size >= 16) return null;
|
||||
const event = library.events.get(id);
|
||||
if (!event) return null;
|
||||
@@ -5446,7 +5475,7 @@
|
||||
const resource = importResourceId(name);
|
||||
if (!resource) continue;
|
||||
if (object.type === 'event') {
|
||||
const audio = resolveSoundAudio(name, library, next);
|
||||
const audio = resolveSoundAudio(name, library, next, animation);
|
||||
if (audio) return audio;
|
||||
continue;
|
||||
}
|
||||
@@ -5468,15 +5497,18 @@
|
||||
|
||||
function mapAnimationSounds(animations) {
|
||||
for (const animation of animations || []) {
|
||||
const peaked = isPeakedSoundStage(animation);
|
||||
for (const keyframe of animation?.animators?.effects?.sound || []) {
|
||||
for (const point of keyframe.data_points || []) {
|
||||
if (point.file && point.file !== point[SOUND_PREVIEW_PROPERTY]) continue;
|
||||
const signature = `${soundLibraryGeneration}:${point.effect}:${point.sound}:${point.file}`;
|
||||
const signature = `${soundLibraryGeneration}:${peaked}:${point.effect}:${point.sound}:${point.file}`;
|
||||
if (soundMappedPoints.get(point) === signature) continue;
|
||||
const audio = resolveSoundAudio(point.effect) || (point.sound ? resolveSoundAudio(point.sound) : null);
|
||||
const library = ensureSoundLibrary();
|
||||
const audio = resolveSoundAudio(point.effect, library, new Set(), animation)
|
||||
|| (point.sound ? resolveSoundAudio(point.sound, library, new Set(), animation) : null);
|
||||
point.file = audio || '';
|
||||
point[SOUND_PREVIEW_PROPERTY] = point.file;
|
||||
soundMappedPoints.set(point, `${soundLibraryGeneration}:${point.effect}:${point.sound}:${point.file}`);
|
||||
soundMappedPoints.set(point, `${soundLibraryGeneration}:${peaked}:${point.effect}:${point.sound}:${point.file}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5516,7 +5548,7 @@
|
||||
stopSoundAudition();
|
||||
ensureSoundLibrary();
|
||||
soundLibraryDialog?.delete();
|
||||
soundLibraryDialog = new Dialog({ id: 'animationdirector_sound_library', title: 'Sound Library', width: 700,
|
||||
soundLibraryDialog = new Dialog({ id: 'animationdirector_sound_library', title: 'NoN Sound Library', width: 700,
|
||||
buttons: ['Close'], onConfirm: stopSoundAudition, onCancel: stopSoundAudition,
|
||||
component: {
|
||||
data() { return { query: '', category: 'all', selected: '', page: 0,
|
||||
@@ -5549,7 +5581,7 @@
|
||||
},
|
||||
preview() {
|
||||
stopSoundAudition(); this.error = '';
|
||||
const file = resolveSoundAudio(this.selected);
|
||||
const file = resolveSoundAudio(this.selected, ensureSoundLibrary(), new Set(), typeof Animation === 'undefined' ? null : Animation.selected);
|
||||
if (!file) { this.error = this.selected === 'reactiveimpact_silent' ? 'Silent cue: gameplay effects only.' : 'No audio found. Select the NoN JAR or an extracted assets folder.'; return; }
|
||||
soundAudition = new Audio(file);
|
||||
soundAudition.volume = Math.max(0, Math.min(1, typeof settings === 'undefined' ? 1 : (settings.volume?.value ?? 100) / 100));
|
||||
@@ -5638,6 +5670,7 @@
|
||||
function beginFiguraSoundPlayback() {
|
||||
installFiguraSoundCompat();
|
||||
if (typeof Animation === 'undefined') return;
|
||||
mapAnimationSounds(Animation.all);
|
||||
for (const animation of Animation.all) {
|
||||
const effects = animation.animators?.effects;
|
||||
if (effects) for (const patch of figuraSoundPatches) {
|
||||
@@ -5661,7 +5694,7 @@
|
||||
const panel = document.getElementById('panel_keyframe');
|
||||
if (panel && !document.getElementById('animationdirector_sound_library_button')) {
|
||||
const button = document.createElement('button');
|
||||
button.id = 'animationdirector_sound_library_button'; button.textContent = 'Sound Library...';
|
||||
button.id = 'animationdirector_sound_library_button'; button.textContent = 'NoN Sound Library';
|
||||
button.style.cssText = 'margin:8px;width:calc(100% - 16px)';
|
||||
button.addEventListener('click', showSoundLibrary); panel.appendChild(button);
|
||||
}
|
||||
@@ -5688,7 +5721,7 @@ You can import or export complete AFW animation IDs as one file containing all s
|
||||
});
|
||||
}
|
||||
soundLibraryAction = new Action('animationdirector_sound_library', {
|
||||
name: 'Sound Library...', icon: 'library_music', category: 'animation',
|
||||
name: 'NoN Sound Library', icon: 'library_music', category: 'animation',
|
||||
description: 'Browse NoN and pack sounds, audition audio and add sound cues to the timeline.',
|
||||
click: showSoundLibrary,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user