diff --git a/Editors/Audio/AudioEditor/Commands/AddDialogueEventByPasteCommand.cs b/Editors/Audio/AudioEditor/Commands/AddDialogueEventByPasteCommand.cs new file mode 100644 index 000000000..3bb0b409f --- /dev/null +++ b/Editors/Audio/AudioEditor/Commands/AddDialogueEventByPasteCommand.cs @@ -0,0 +1,64 @@ +using System.Collections.Generic; +using System.Data; +using Editors.Audio.AudioEditor.Core; +using Editors.Audio.AudioEditor.Core.AudioProjectMutation; +using Editors.Audio.AudioEditor.Presentation.Shared; +using Editors.Audio.AudioEditor.Presentation.Shared.Table; +using Editors.Audio.Shared.AudioProject.Models; +using Editors.Audio.Shared.Storage; +using HircSettings = Editors.Audio.Shared.AudioProject.Models.HircSettings; + +namespace Editors.Audio.AudioEditor.Commands +{ + public class AddDialogueEventByPasteCommand( + IAudioEditorStateService audioEditorStateService, + IAudioRepository audioRepository, + IDialogueEventService dialogueEventService) : IAudioProjectMutationUICommand + { + private readonly IAudioEditorStateService _audioEditorStateService = audioEditorStateService; + private readonly IAudioRepository _audioRepository = audioRepository; + private readonly IDialogueEventService _dialogueEventService = dialogueEventService; + + public MutationType Action => MutationType.AddByPaste; + public AudioProjectTreeNodeType NodeType => AudioProjectTreeNodeType.DialogueEvent; + + public void Execute(DataRow row) + { + var audioProject = _audioEditorStateService.AudioProject; + var copiedFromAudioProjectExplorerNode = _audioEditorStateService.CopiedFromAudioProjectExplorerNode; + + HircSettings hircSettings = null; + var audioFiles = new List(); + + var dialogueEventName = copiedFromAudioProjectExplorerNode.Name; + var dialogueEvent = _audioEditorStateService.AudioProject.GetDialogueEvent(dialogueEventName); + var statePathName = TableHelpers.GetStatePathNameFromRow(row, _audioRepository, dialogueEventName); + var statePath = dialogueEvent.GetStatePath(statePathName); + var soundBank = _audioEditorStateService.AudioProject.GetSoundBank(copiedFromAudioProjectExplorerNode.Parent.Parent.Name); + + if (statePath.TargetHircTypeIsSound()) + { + var sound = soundBank.GetSound(statePath.TargetHircId); + hircSettings = sound.HircSettings; + audioFiles.Add(audioProject.GetAudioFile(sound.SourceId)); + } + else if (statePath.TargetHircTypeIsRandomSequenceContainer()) + { + var randomSequenceContainer = soundBank.GetRandomSequenceContainer(statePath.TargetHircId); + hircSettings = randomSequenceContainer.HircSettings; + audioFiles = audioProject.GetAudioFiles(soundBank, randomSequenceContainer); + } + + var statePathList = new List>(); + foreach (DataColumn dataColumn in row.Table.Columns) + { + var columnNameWithQualifier = TableHelpers.DeduplicateUnderscores(dataColumn.ColumnName); + var stateGroupName = TableHelpers.GetStateGroupFromStateGroupWithQualifier(_audioRepository, dialogueEventName, columnNameWithQualifier); + var stateName = TableHelpers.GetValueFromRow(row, dataColumn.ColumnName); + statePathList.Add(new KeyValuePair(stateGroupName, stateName)); + } + + _dialogueEventService.AddStatePath(_audioEditorStateService.SelectedAudioProjectExplorerNode.Name, audioFiles, hircSettings, statePathList); + } + } +} diff --git a/Editors/Audio/AudioEditor/Commands/AddDialogueEventCommand.cs b/Editors/Audio/AudioEditor/Commands/AddDialogueEventCommand.cs index 21993410e..a0f225e97 100644 --- a/Editors/Audio/AudioEditor/Commands/AddDialogueEventCommand.cs +++ b/Editors/Audio/AudioEditor/Commands/AddDialogueEventCommand.cs @@ -26,17 +26,17 @@ public void Execute(DataRow row) var audioFiles = _audioEditorStateService.AudioFiles; var settings = _audioEditorStateService.HircSettings; - var stateLookupByStateGroup = new Dictionary(); + var statePathList = new List>(); var stateGroupsWithQualifiers = _audioRepository.QualifiedStateGroupByStateGroupByDialogueEvent[dialogueEventName]; foreach (var stateGroupWithQualifier in stateGroupsWithQualifiers) { var stateGroupName = TableHelpers.GetStateGroupFromStateGroupWithQualifier(_audioRepository, dialogueEventName, stateGroupWithQualifier.Key); var columnName = TableHelpers.DuplicateUnderscores(stateGroupWithQualifier.Key); var stateName = TableHelpers.GetValueFromRow(row, columnName); - stateLookupByStateGroup.Add(stateGroupName, stateName); + statePathList.Add(new KeyValuePair(stateGroupName, stateName)); } - _dialogueEventService.AddStatePath(dialogueEventName, audioFiles, settings, stateLookupByStateGroup); + _dialogueEventService.AddStatePath(dialogueEventName, audioFiles, settings, statePathList); } } } diff --git a/Editors/Audio/AudioEditor/Commands/AudioProjectMutationUICommandFactory.cs b/Editors/Audio/AudioEditor/Commands/AudioProjectMutationUICommandFactory.cs index c6d87069c..bc74c0652 100644 --- a/Editors/Audio/AudioEditor/Commands/AudioProjectMutationUICommandFactory.cs +++ b/Editors/Audio/AudioEditor/Commands/AudioProjectMutationUICommandFactory.cs @@ -10,6 +10,7 @@ namespace Editors.Audio.AudioEditor.Commands public enum MutationType { Add, + AddByPaste, Remove } diff --git a/Editors/Audio/AudioEditor/Commands/PasteViewerRowsCommand.cs b/Editors/Audio/AudioEditor/Commands/PasteViewerRowsCommand.cs index c0c9ce08f..ae90c072b 100644 --- a/Editors/Audio/AudioEditor/Commands/PasteViewerRowsCommand.cs +++ b/Editors/Audio/AudioEditor/Commands/PasteViewerRowsCommand.cs @@ -20,7 +20,7 @@ public void Execute(List copiedRows) var selectedAudioProjectExplorerNode = _audioEditorStateService.SelectedAudioProjectExplorerNode; foreach (var row in copiedRows) { - _audioProjectMutationUICommandFactory.Create(MutationType.Add, selectedAudioProjectExplorerNode.Type).Execute(row); + _audioProjectMutationUICommandFactory.Create(MutationType.AddByPaste, selectedAudioProjectExplorerNode.Type).Execute(row); _eventHub.Publish(new ViewerTableRowAddRequestedEvent(row)); _eventHub.Publish(new EditorAddRowButtonEnablementUpdateRequestedEvent()); } diff --git a/Editors/Audio/AudioEditor/Core/AudioEditorFileService.cs b/Editors/Audio/AudioEditor/Core/AudioEditorFileService.cs index a1f656598..43a7061d4 100644 --- a/Editors/Audio/AudioEditor/Core/AudioEditorFileService.cs +++ b/Editors/Audio/AudioEditor/Core/AudioEditorFileService.cs @@ -76,8 +76,11 @@ public void Load(AudioProjectFile audioProject, string fileName, string filePath var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName); + // We add the Audio Project name as a suffix to all SoundBank names so if the Audio Project name has changed we need to update them + _audioEditorIntegrityService.UpdateSoundBankNames(audioProject, fileNameWithoutExtension); + // We create a 'dirty' Audio Project to display the whole model in the Audio Project Explorer rather than - // just the clean data from the loaded Audio Project as when it's saved any unused parts are removed. + // just the clean data from the loaded Audio Project as any unused parts are removed when it's saved var currentGame = _applicationSettingsService.CurrentSettings.CurrentGame; var dirtyAudioProject = AudioProjectFile.Create(audioProject, currentGame, fileNameWithoutExtension); diff --git a/Editors/Audio/AudioEditor/Core/AudioEditorIntegrityService.cs b/Editors/Audio/AudioEditor/Core/AudioEditorIntegrityService.cs index c15bca6af..66e287c1c 100644 --- a/Editors/Audio/AudioEditor/Core/AudioEditorIntegrityService.cs +++ b/Editors/Audio/AudioEditor/Core/AudioEditorIntegrityService.cs @@ -14,10 +14,11 @@ namespace Editors.Audio.AudioEditor.Core { public interface IAudioEditorIntegrityService { + void UpdateSoundBankNames(AudioProjectFile audioProject, string audioProjectNameWithoutExtension); void CheckDialogueEventInformationIntegrity(List dialogueEventData); void CheckAudioProjectDialogueEventIntegrity(AudioProjectFile audioProject); void CheckAudioProjectWavFilesIntegrity(AudioProjectFile audioProject); - void CheckAudioProjectDataIntegrity(AudioProjectFile audioProject, string audioProjectFileNameWithoutExtension); + void CheckAudioProjectDataIntegrity(AudioProjectFile audioProject, string audioProjectNameWithoutExtension); void CheckMergingSoundBanksIdIntegrity(); } @@ -26,6 +27,20 @@ public class AudioEditorIntegrityService(IPackFileService packFileService, IAudi private readonly IPackFileService _packFileService = packFileService; private readonly IAudioRepository _audioRepository = audioRepository; + public void UpdateSoundBankNames(AudioProjectFile audioProject, string audioProjectNameWithoutExtension) + { + foreach (var soundBank in audioProject.SoundBanks) + { + var soundBankAudioProjectName = Wh3SoundBankInformation.GetAudioProjectNameFromSoundBankWithAudioProjectName(soundBank.Name); + if (audioProjectNameWithoutExtension != soundBankAudioProjectName) + { + var gameSoundBankName = Wh3SoundBankInformation.GetSoundBankNameFromSoundBankWithAudioProjectName(soundBank.Name); + var correctSoundBankName = $"{gameSoundBankName}_{audioProjectNameWithoutExtension}"; + soundBank.Name = correctSoundBankName; + } + } + } + public void CheckDialogueEventInformationIntegrity(List information) { var exclusions = new List { "New_Dialogue_Event", "Battle_Individual_Melee_Weapon_Hit" }; @@ -130,7 +145,7 @@ public void CheckAudioProjectWavFilesIntegrity(AudioProjectFile audioProject) } } - public void CheckAudioProjectDataIntegrity(AudioProjectFile audioProject, string audioProjectFileNameWithoutExtension) + public void CheckAudioProjectDataIntegrity(AudioProjectFile audioProject, string audioProjectNameWithoutExtension) { var usedHircIds = new HashSet(); var usedSourceIds = new HashSet(); @@ -200,7 +215,7 @@ public void CheckAudioProjectDataIntegrity(AudioProjectFile audioProject, string foreach (var soundBank in audioProject.SoundBanks) { - ResolveSoundBankDataIntegrity(audioProject, audioProjectFileNameWithoutExtension, soundBank); + ResolveSoundBankDataIntegrity(audioProject, audioProjectNameWithoutExtension, soundBank); if (soundBank.ActionEvents != null) ResolveActionEventDataIntegrity(usedHircIds, usedSourceIds, soundBank); @@ -215,7 +230,7 @@ public void CheckAudioProjectDataIntegrity(AudioProjectFile audioProject, string ResolveStateGroupDataIntegrity(audioProject); } - private static void ResolveSoundBankDataIntegrity(AudioProjectFile audioProject, string audioProjectFileNameWithoutExtension, SoundBank soundBank) + private static void ResolveSoundBankDataIntegrity(AudioProjectFile audioProject, string audioProjectNameWithoutExtension, SoundBank soundBank) { if (soundBank == null) throw new InvalidOperationException("SoundBank should not be null."); @@ -223,9 +238,9 @@ private static void ResolveSoundBankDataIntegrity(AudioProjectFile audioProject, if (string.IsNullOrWhiteSpace(soundBank.Name)) throw new InvalidOperationException("SoundBank.Name should not be null or empty."); - var gameSoundBankName = Wh3SoundBankInformation.GetSoundBankNameFromPrefix(soundBank.Name); + var gameSoundBankName = Wh3SoundBankInformation.GetSoundBankNameFromSoundBankWithAudioProjectName(soundBank.Name); var gameSoundBank = Wh3SoundBankInformation.GetSoundBank(gameSoundBankName); - var correctSoundBankName = $"{gameSoundBankName}_{audioProjectFileNameWithoutExtension}"; + var correctSoundBankName = $"{gameSoundBankName}_{audioProjectNameWithoutExtension}"; if (soundBank.Name != correctSoundBankName) throw new InvalidOperationException($"SoundBank.Name is incorrect. Expected '{correctSoundBankName}'."); @@ -573,6 +588,17 @@ public void CheckMergingSoundBanksIdIntegrity() hasClashes = true; messageBuilder.AppendLine($"Language: {languageName}"); + var conflictingBnks = clashingIdsById.Values + .SelectMany(bnkNames => bnkNames) + .Concat(clashingSourceIdsById.Values.SelectMany(bnkNames => bnkNames)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .OrderBy(name => name, StringComparer.OrdinalIgnoreCase); + + foreach (var conflictingBnk in conflictingBnks) + messageBuilder.AppendLine(conflictingBnk); + + messageBuilder.AppendLine(); + foreach (var sourceBnk in idsByBnk.Keys .Union(sourceIdsByBnk.Keys, StringComparer.OrdinalIgnoreCase) .OrderBy(name => name, StringComparer.OrdinalIgnoreCase)) diff --git a/Editors/Audio/AudioEditor/Core/AudioEditorStateService.cs b/Editors/Audio/AudioEditor/Core/AudioEditorStateService.cs index 244f0df8d..7bcfad2b2 100644 --- a/Editors/Audio/AudioEditor/Core/AudioEditorStateService.cs +++ b/Editors/Audio/AudioEditor/Core/AudioEditorStateService.cs @@ -14,6 +14,7 @@ public interface IAudioEditorStateService // Audio Project Explorer AudioProjectTreeNode SelectedAudioProjectExplorerNode { get; set; } + AudioProjectTreeNode CopiedFromAudioProjectExplorerNode { get; set; } // Audio Project Editor bool ShowModdedStatesOnly { get; set; } @@ -30,6 +31,7 @@ public interface IAudioEditorStateService public void StoreAudioProjectFileName(string audioProjectFileName); public void StoreAudioProjectFilePath(string audioProjectFilePath); void StoreSelectedAudioProjectExplorerNode(AudioProjectTreeNode node); + void StoreCopiedFromAudioProjectExplorerNode(AudioProjectTreeNode node); void StoreModdedStatesOnly(bool moddedStatesOnly); void StoreHircSettings(HircSettings hircSettings); void StoreAudioFiles(List audioFiles); @@ -44,6 +46,7 @@ public class AudioEditorStateService : IAudioEditorStateService public string AudioProjectFileName { get; set; } public string AudioProjectFilePath { get; set; } public AudioProjectTreeNode SelectedAudioProjectExplorerNode { get; set; } + public AudioProjectTreeNode CopiedFromAudioProjectExplorerNode { get; set; } public bool ShowModdedStatesOnly { get; set; } public HircSettings HircSettings { get; set; } public List AudioFiles { get; set; } = []; @@ -58,6 +61,8 @@ public class AudioEditorStateService : IAudioEditorStateService public void StoreSelectedAudioProjectExplorerNode(AudioProjectTreeNode node) => SelectedAudioProjectExplorerNode = node; + public void StoreCopiedFromAudioProjectExplorerNode(AudioProjectTreeNode node) => CopiedFromAudioProjectExplorerNode = node; + public void StoreModdedStatesOnly(bool showModdedStatesOnly) => ShowModdedStatesOnly = showModdedStatesOnly; public void StoreHircSettings(HircSettings hircSettings) => HircSettings = hircSettings; @@ -74,6 +79,7 @@ public void Reset() AudioProjectFileName = null; AudioProjectFilePath = null; SelectedAudioProjectExplorerNode = null; + CopiedFromAudioProjectExplorerNode = null; ShowModdedStatesOnly = false; HircSettings = null; AudioFiles.Clear(); diff --git a/Editors/Audio/AudioEditor/Core/AudioProjectMutation/ActionEventService.cs b/Editors/Audio/AudioEditor/Core/AudioProjectMutation/ActionEventService.cs index 6c8277f68..12b3cc2cd 100644 --- a/Editors/Audio/AudioEditor/Core/AudioProjectMutation/ActionEventService.cs +++ b/Editors/Audio/AudioEditor/Core/AudioProjectMutation/ActionEventService.cs @@ -42,8 +42,8 @@ public void AddActionEvent(string actionEventTypeName, string actionEventName, L usedSourceIds.UnionWith(languageSourceIds); var gameSoundBankName = Wh3SoundBankInformation.GetName(Wh3ActionEventInformation.GetSoundBank(actionEventTypeName)); - var audioProjectFileNameWithoutExtension = Path.GetFileNameWithoutExtension(_audioEditorStateService.AudioProjectFileName); - var soundBankName = $"{gameSoundBankName}_{audioProjectFileNameWithoutExtension}"; + var audioProjectNameWithoutExtension = Path.GetFileNameWithoutExtension(_audioEditorStateService.AudioProjectFileName); + var soundBankName = $"{gameSoundBankName}_{audioProjectNameWithoutExtension}"; var soundBank = _audioEditorStateService.AudioProject.GetSoundBank(soundBankName); var actionEventType = Wh3ActionEventInformation.GetActionEventType(actionEventTypeName); @@ -104,8 +104,8 @@ public void RemoveActionEvent(string actionEventNodeName, string actionEventName { var audioProject = _audioEditorStateService.AudioProject; var gameSoundBankName = Wh3SoundBankInformation.GetName(Wh3ActionEventInformation.GetSoundBank(actionEventNodeName)); - var audioProjectFileNameWithoutExtension = Path.GetFileNameWithoutExtension(_audioEditorStateService.AudioProjectFileName); - var soundBankName = $"{gameSoundBankName}_{audioProjectFileNameWithoutExtension}"; + var audioProjectNameWithoutExtension = Path.GetFileNameWithoutExtension(_audioEditorStateService.AudioProjectFileName); + var soundBankName = $"{gameSoundBankName}_{audioProjectNameWithoutExtension}"; var soundBank = audioProject.GetSoundBank(soundBankName); var actionEvent = soundBank.GetActionEvent(actionEventName); diff --git a/Editors/Audio/AudioEditor/Core/AudioProjectMutation/DialogueEventService.cs b/Editors/Audio/AudioEditor/Core/AudioProjectMutation/DialogueEventService.cs index 7a24d0f5b..c095906af 100644 --- a/Editors/Audio/AudioEditor/Core/AudioProjectMutation/DialogueEventService.cs +++ b/Editors/Audio/AudioEditor/Core/AudioProjectMutation/DialogueEventService.cs @@ -12,7 +12,7 @@ namespace Editors.Audio.AudioEditor.Core.AudioProjectMutation { public interface IDialogueEventService { - void AddStatePath(string dialogueEventName, List audioFiles, HircSettings hircSettings, Dictionary stateLookupByStateGroup); + void AddStatePath(string dialogueEventName, List audioFiles, HircSettings hircSettings, List> statePathList); bool RemoveStatePath(string dialogueEventName, string statePathName); } @@ -22,7 +22,7 @@ public class DialogueEventService(IAudioEditorStateService audioEditorStateServi private readonly IAudioRepository _audioRepository = audioRepository; private readonly IStatePathFactory _statePathFactory = statePathFactory; - public void AddStatePath(string dialogueEventName, List audioFiles, HircSettings hircSettings, Dictionary stateLookupByStateGroup) + public void AddStatePath(string dialogueEventName, List audioFiles, HircSettings hircSettings, List> statePathList) { var usedHircIds = new HashSet(); var usedSourceIds = new HashSet(); @@ -41,13 +41,13 @@ public void AddStatePath(string dialogueEventName, List audioFiles, H usedSourceIds.UnionWith(languageSourceIds); var gameSoundBankName = Wh3SoundBankInformation.GetName(Wh3DialogueEventInformation.GetSoundBank(dialogueEventName)); - var audioProjectFileNameWithoutExtension = Path.GetFileNameWithoutExtension(_audioEditorStateService.AudioProjectFileName); - var soundBankName = $"{gameSoundBankName}_{audioProjectFileNameWithoutExtension}"; + var audioProjectNameWithoutExtension = Path.GetFileNameWithoutExtension(_audioEditorStateService.AudioProjectFileName); + var soundBankName = $"{gameSoundBankName}_{audioProjectNameWithoutExtension}"; var soundBank = _audioEditorStateService.AudioProject.GetSoundBank(soundBankName); var dialogueEvent = _audioEditorStateService.AudioProject.GetDialogueEvent(dialogueEventName); var actorMixerId = Wh3DialogueEventInformation.GetActorMixerId(dialogueEvent.Name); - var statePathFactoryResult = _statePathFactory.Create(stateLookupByStateGroup, audioFiles, hircSettings, usedHircIds, usedSourceIds, soundBank.Language, actorMixerId: actorMixerId); + var statePathFactoryResult = _statePathFactory.Create(statePathList, audioFiles, hircSettings, usedHircIds, usedSourceIds, soundBank.Language, actorMixerId: actorMixerId); dialogueEvent.StatePaths.InsertAlphabetically(statePathFactoryResult.StatePath); if (statePathFactoryResult.StatePath.TargetHircTypeIsSound()) @@ -88,8 +88,8 @@ public bool RemoveStatePath(string dialogueEventName, string statePathName) { var audioProject = _audioEditorStateService.AudioProject; var gameSoundBankName = Wh3SoundBankInformation.GetName(Wh3DialogueEventInformation.GetSoundBank(dialogueEventName)); - var audioProjectFileNameWithoutExtension = Path.GetFileNameWithoutExtension(_audioEditorStateService.AudioProjectFileName); - var soundBankName = $"{gameSoundBankName}_{audioProjectFileNameWithoutExtension}"; + var audioProjectNameWithoutExtension = Path.GetFileNameWithoutExtension(_audioEditorStateService.AudioProjectFileName); + var soundBankName = $"{gameSoundBankName}_{audioProjectNameWithoutExtension}"; var soundBank = audioProject.GetSoundBank(soundBankName); diff --git a/Editors/Audio/AudioEditor/Presentation/AudioProjectEditor/Table/EditorActionEventTableService.cs b/Editors/Audio/AudioEditor/Presentation/AudioProjectEditor/Table/EditorActionEventTableService.cs index b3295593e..2dcc632b8 100644 --- a/Editors/Audio/AudioEditor/Presentation/AudioProjectEditor/Table/EditorActionEventTableService.cs +++ b/Editors/Audio/AudioEditor/Presentation/AudioProjectEditor/Table/EditorActionEventTableService.cs @@ -60,6 +60,7 @@ public void ConfigureDataGrid(List schema) foreach (var columnName in schema) { + // We don't allow editing because the Event name must match the path of the movie var eventColumn = DataGridTemplates.CreateColumnTemplate(columnName, columnWidth, isReadOnly: true); eventColumn.CellTemplate = DataGridTemplates.CreateReadOnlyTextBlockTemplate(columnName); _eventHub.Publish(new EditorDataGridColumnAddRequestedEvent(eventColumn)); @@ -69,7 +70,7 @@ public void ConfigureDataGrid(List schema) { foreach (var columnName in schema) { - var eventColumn = DataGridTemplates.CreateColumnTemplate(columnName, columnWidth, isReadOnly: true); + var eventColumn = DataGridTemplates.CreateColumnTemplate(columnName, columnWidth); eventColumn.CellTemplate = DataGridTemplates.CreateEditableEventTextBoxTemplate(_eventHub, columnName); _eventHub.Publish(new EditorDataGridColumnAddRequestedEvent(eventColumn)); } diff --git a/Editors/Audio/AudioEditor/Presentation/AudioProjectViewer/AudioProjectViewerViewModel.cs b/Editors/Audio/AudioEditor/Presentation/AudioProjectViewer/AudioProjectViewerViewModel.cs index 3ff8ccdaa..f8d93cba5 100644 --- a/Editors/Audio/AudioEditor/Presentation/AudioProjectViewer/AudioProjectViewerViewModel.cs +++ b/Editors/Audio/AudioEditor/Presentation/AudioProjectViewer/AudioProjectViewerViewModel.cs @@ -168,6 +168,7 @@ [RelayCommand] public void CopyRows() return; _audioEditorStateService.StoreCopiedViewerRows(SelectedRows); + _audioEditorStateService.StoreCopiedFromAudioProjectExplorerNode(_audioEditorStateService.SelectedAudioProjectExplorerNode); SetPasteEnablement(); } @@ -215,9 +216,17 @@ partial void OnSelectedRowsChanged(List value) SetCopyEnablement(); } - [RelayCommand] public void RemoveRow() => _uiCommandFactory.Create().Execute(SelectedRows); + [RelayCommand] public void RemoveRow() + { + _uiCommandFactory.Create().Execute(SelectedRows); + SetPasteEnablement(); + } - [RelayCommand] public void EditRow() => _uiCommandFactory.Create().Execute(SelectedRows); + [RelayCommand] public void EditRow() + { + _uiCommandFactory.Create().Execute(SelectedRows); + SetPasteEnablement(); + } private void Load(AudioProjectTreeNodeType selectedNodeType) { @@ -245,6 +254,7 @@ public void SetCopyEnablement() public void SetPasteEnablement() { + // We only set the Context Menu visible when the selected node is a Dialogue Event so unless it is we don't proceed if (!IsContextMenuPasteVisible) { IsPasteEnabled = false; @@ -257,15 +267,22 @@ public void SetPasteEnablement() return; } + // Guard against cases where the copied row has been subsequently deleted + if (_audioEditorStateService.CopiedViewerRows.Any(copied => copied == null || copied.RowState == DataRowState.Detached || copied.Table == null)) + { + IsPasteEnabled = false; + return; + } + var viewerColumns = Table.Columns .Cast() - .Select(col => col.ColumnName) + .Select(column => column.ColumnName) .ToList(); var firstRow = _audioEditorStateService.CopiedViewerRows[0]; var rowColumns = firstRow.Table.Columns .Cast() - .Select(col => col.ColumnName) + .Select(column => column.ColumnName) .ToList(); var schemaMatches = viewerColumns.Count == rowColumns.Count && viewerColumns.All(column => rowColumns.Contains(column)); diff --git a/Editors/Audio/AudioEditor/Presentation/AudioProjectViewer/Table/ViewerActionEventTableService.cs b/Editors/Audio/AudioEditor/Presentation/AudioProjectViewer/Table/ViewerActionEventTableService.cs index 28865ebdd..6c2414fb1 100644 --- a/Editors/Audio/AudioEditor/Presentation/AudioProjectViewer/Table/ViewerActionEventTableService.cs +++ b/Editors/Audio/AudioEditor/Presentation/AudioProjectViewer/Table/ViewerActionEventTableService.cs @@ -61,8 +61,8 @@ public void InitialiseTable(DataTable table) { var actionEventName = _audioEditorStateService.SelectedAudioProjectExplorerNode.Name; var gameSoundBank = Wh3SoundBankInformation.GetName(Wh3ActionEventInformation.GetSoundBank(actionEventName)); - var audioProjectFileNameWithoutExtension = Path.GetFileNameWithoutExtension(_audioEditorStateService.AudioProjectFileName); - var soundBankName = $"{gameSoundBank}_{audioProjectFileNameWithoutExtension}"; + var audioProjectNameWithoutExtension = Path.GetFileNameWithoutExtension(_audioEditorStateService.AudioProjectFileName); + var soundBankName = $"{gameSoundBank}_{audioProjectNameWithoutExtension}"; var soundBank = _audioEditorStateService.AudioProject.GetSoundBank(soundBankName); foreach (var actionEvent in soundBank.ActionEvents) { diff --git a/Editors/Audio/AudioEditor/Presentation/Shared/Table/DataGridTemplates.cs b/Editors/Audio/AudioEditor/Presentation/Shared/Table/DataGridTemplates.cs index f57d7cb31..f764347ca 100644 --- a/Editors/Audio/AudioEditor/Presentation/Shared/Table/DataGridTemplates.cs +++ b/Editors/Audio/AudioEditor/Presentation/Shared/Table/DataGridTemplates.cs @@ -67,7 +67,11 @@ public static DataTemplate CreateStatesComboBoxTemplate(IEventHub eventHub, stri // Ensure a default value of "Any" if (string.IsNullOrEmpty(comboBox.Text) && states.Contains("Any")) + { comboBox.Text = "Any"; + // Update the Add Row button again after we set the missing value to "Any" + eventHub.Publish(new EditorAddRowButtonEnablementUpdateRequestedEvent()); + } if (comboBox.Template.FindName("PART_EditableTextBox", comboBox) is TextBox textBox) { diff --git a/Editors/Audio/AudioEditor/Presentation/Shared/Table/TableHelpers.cs b/Editors/Audio/AudioEditor/Presentation/Shared/Table/TableHelpers.cs index 150d4a983..bf2d727da 100644 --- a/Editors/Audio/AudioEditor/Presentation/Shared/Table/TableHelpers.cs +++ b/Editors/Audio/AudioEditor/Presentation/Shared/Table/TableHelpers.cs @@ -40,7 +40,7 @@ public static List GetStatesForStateGroupColumn(IAudioEditorStateService .ToList(); } - return states; + return states.Distinct().ToList(); } public static string GetStateGroupFromStateGroupWithQualifier(IAudioRepository audioRepository, string dialogueEvent, string stateGroupNameWithQualifier) diff --git a/Editors/Audio/Shared/AudioProject/Compiler/AudioProjectCompilerService.cs b/Editors/Audio/Shared/AudioProject/Compiler/AudioProjectCompilerService.cs index b71f17b32..8331aecde 100644 --- a/Editors/Audio/Shared/AudioProject/Compiler/AudioProjectCompilerService.cs +++ b/Editors/Audio/Shared/AudioProject/Compiler/AudioProjectCompilerService.cs @@ -38,13 +38,13 @@ public void Compile(AudioProjectFile audioProject, string audioProjectFileName, var audioFiles = new List(); var sounds = new List(); - var audioProjectFileNameWithoutExtension = Path.GetFileNameWithoutExtension(audioProjectFileName); + var audioProjectNameWithoutExtension = Path.GetFileNameWithoutExtension(audioProjectFileName); ClearTempAudioFiles(); - SetSoundBankData(audioProject, audioProjectFileNameWithoutExtension, audioFiles, sounds); + SetSoundBankData(audioProject, audioProjectNameWithoutExtension, audioFiles, sounds); GenerateWems(audioProject, audioFiles, sounds); GenerateSoundBanks(audioProject); - GenerateDatFiles(audioProject, audioProjectFileNameWithoutExtension); + GenerateDatFiles(audioProject, audioProjectNameWithoutExtension); MemoryOptimiser.Optimise(); } @@ -62,7 +62,7 @@ private static void ClearTempAudioFiles() } } - private void SetSoundBankData(AudioProjectFile audioProject, string audioProjectFileNameWithoutExtension, List audioFiles, List sounds) + private void SetSoundBankData(AudioProjectFile audioProject, string audioProjectNameWithoutExtension, List audioFiles, List sounds) { _logger.Here().Information($"Setting SoundBank data"); @@ -84,8 +84,8 @@ private void SetSoundBankData(AudioProjectFile audioProject, string audioProject // 3) campaign_vo_0_audio_mixer.bnk // So the dialogue events from campaign_vo_0_audio_mixer.bnk will be what take priority as they're loaded last. - var soundBankNameBase = soundBank.Name.Replace($"_{audioProjectFileNameWithoutExtension}", string.Empty); - soundBank.TestingFileName = $"{soundBankNameBase}_1_{audioProjectFileNameWithoutExtension}_for_testing.bnk"; + var soundBankNameBase = soundBank.Name.Replace($"_{audioProjectNameWithoutExtension}", string.Empty); + soundBank.TestingFileName = $"{soundBankNameBase}_1_{audioProjectNameWithoutExtension}_for_testing.bnk"; soundBank.MergingFileName = $"{soundBank.Name}_for_merging.bnk"; if (soundBank.Language == Wh3LanguageInformation.GetLanguageAsString(Wh3Language.Sfx)) @@ -237,7 +237,7 @@ private void GenerateSoundBanks(AudioProjectFile audioProject) } } - private void GenerateDatFiles(AudioProjectFile audioProject, string audioProjectFileNameWithoutExtension) + private void GenerateDatFiles(AudioProjectFile audioProject, string audioProjectNameWithoutExtension) { // The .dat file is seems to only necessary for Action Events for movies, quest battles or anything triggered via common.trigger_soundevent() // but without testing all the different types of Action Event sounds it's safer to just make a .dat for all. @@ -246,17 +246,17 @@ private void GenerateDatFiles(AudioProjectFile audioProject, string audioProject if (actionEvents.Count != 0 && audioProject.StateGroups != null && audioProject.StateGroups.Count != 0) { _logger.Here().Information($"Generating event data .dat"); - _datGeneratorService.GenerateEventDatFile(audioProjectFileNameWithoutExtension, actionEvents, audioProject.StateGroups); + _datGeneratorService.GenerateEventDatFile(audioProjectNameWithoutExtension, actionEvents, audioProject.StateGroups); } else if (actionEvents.Count != 0 && audioProject.StateGroups == null) { _logger.Here().Information($"Generating event data .dat"); - _datGeneratorService.GenerateEventDatFile(audioProjectFileNameWithoutExtension, actionEvents: actionEvents); + _datGeneratorService.GenerateEventDatFile(audioProjectNameWithoutExtension, actionEvents: actionEvents); } else if (actionEvents.Count == 0 && audioProject.StateGroups != null && audioProject.StateGroups.Count != 0) { _logger.Here().Information($"Generating event data .dat"); - _datGeneratorService.GenerateEventDatFile(audioProjectFileNameWithoutExtension, stateGroups: audioProject.StateGroups); + _datGeneratorService.GenerateEventDatFile(audioProjectNameWithoutExtension, stateGroups: audioProject.StateGroups); } } } diff --git a/Editors/Audio/Shared/AudioProject/Factories/StatePathFactory.cs b/Editors/Audio/Shared/AudioProject/Factories/StatePathFactory.cs index 2219512f6..62a085967 100644 --- a/Editors/Audio/Shared/AudioProject/Factories/StatePathFactory.cs +++ b/Editors/Audio/Shared/AudioProject/Factories/StatePathFactory.cs @@ -15,7 +15,7 @@ public record StatePathFactoryResult public interface IStatePathFactory { StatePathFactoryResult Create( - Dictionary stateLookupByStateGroup, + List> statePathList, List audioFiles, HircSettings hircSettings, HashSet usedHircIds, @@ -30,7 +30,7 @@ public class StatePathFactory(ISoundFactory soundFactory, IRandomSequenceContain private readonly IRandomSequenceContainerFactory _randomSequenceContainerFactory = randomSequenceContainerFactory; public StatePathFactoryResult Create( - Dictionary stateLookupByStateGroup, + List> statePathList, List audioFiles, HircSettings hircSettings, HashSet usedHircIds, @@ -41,7 +41,7 @@ public StatePathFactoryResult Create( var statePathFactoryResult = new StatePathFactoryResult(); var statePathNodes = new List(); - foreach (var kvp in stateLookupByStateGroup) + foreach (var kvp in statePathList) { var stateGroupName = kvp.Key; var stateGroup = StateGroup.Create(stateGroupName); diff --git a/Editors/Audio/Shared/AudioProject/Models/AudioProjectFile.cs b/Editors/Audio/Shared/AudioProject/Models/AudioProjectFile.cs index 9140b9291..c88051afb 100644 --- a/Editors/Audio/Shared/AudioProject/Models/AudioProjectFile.cs +++ b/Editors/Audio/Shared/AudioProject/Models/AudioProjectFile.cs @@ -16,13 +16,13 @@ public class AudioProjectFile public List StateGroups { get; set; } public List AudioFiles { get; set; } - public static AudioProjectFile Create(GameTypeEnum currentGame, string language, string nameWithoutExtension, List audioFiles = null) + public static AudioProjectFile Create(GameTypeEnum currentGame, string language, string audioProjectNameWithoutExtension, List audioFiles = null) { if (currentGame == GameTypeEnum.Warhammer3) return new AudioProjectFile { Language = language, - SoundBanks = CreateSoundBanks(nameWithoutExtension, language), + SoundBanks = CreateSoundBanks(audioProjectNameWithoutExtension, language), StateGroups = CreateStateGroups(), AudioFiles = audioFiles ?? [] }; @@ -30,9 +30,9 @@ public static AudioProjectFile Create(GameTypeEnum currentGame, string language, return null; } - public static AudioProjectFile Create(AudioProjectFile cleanAudioProject, GameTypeEnum currentGame, string nameWithoutExtension) + public static AudioProjectFile Create(AudioProjectFile cleanAudioProject, GameTypeEnum currentGame, string audioProjectNameWithoutExtension) { - var dirtyAudioProject = Create(currentGame, cleanAudioProject.Language, nameWithoutExtension, cleanAudioProject.AudioFiles); + var dirtyAudioProject = Create(currentGame, cleanAudioProject.Language, audioProjectNameWithoutExtension, cleanAudioProject.AudioFiles); MergeCleanIntoDirtySoundBanks(dirtyAudioProject.SoundBanks, cleanAudioProject.SoundBanks); AddCleanToDirtyStateGroups(dirtyAudioProject.StateGroups, cleanAudioProject.StateGroups); return dirtyAudioProject; @@ -178,6 +178,20 @@ public HashSet GetSoundBankSoundIds(string soundBankName) public AudioFile GetAudioFile(uint sourceId) => AudioFiles.FirstOrDefault(audioFile => audioFile.Id == sourceId); + public List GetAudioFiles(SoundBank soundBank, RandomSequenceContainer randomSequenceContainer) + { + var audioFiles = new List(); + var orderedSounds = soundBank.GetSounds(randomSequenceContainer.Children) + .OrderBy(sound => sound.PlaylistOrder) + .ToList(); + foreach (var orderedSound in orderedSounds) + { + var audioFile = GetAudioFile(orderedSound.SourceId); + audioFiles.Add(audioFile); + } + return audioFiles; + } + public List GetSoundBanksWithActionEvents() { return SoundBanks.Where(soundBank => soundBank.ActionEvents != null && soundBank.ActionEvents.Count != 0).ToList(); diff --git a/Editors/Audio/Shared/Dat/DatGeneratorService.cs b/Editors/Audio/Shared/Dat/DatGeneratorService.cs index bc5204eba..7f5763ff6 100644 --- a/Editors/Audio/Shared/Dat/DatGeneratorService.cs +++ b/Editors/Audio/Shared/Dat/DatGeneratorService.cs @@ -8,14 +8,14 @@ namespace Editors.Audio.Shared.Dat { public interface IDatGeneratorService { - void GenerateEventDatFile(string audioProjectFileNameWithoutExtension, List actionEvents = null, List stateGroups = null); + void GenerateEventDatFile(string audioProjectNameWithoutExtension, List actionEvents = null, List stateGroups = null); } public class DatGeneratorService(IFileSaveService fileSaveService) : IDatGeneratorService { private readonly IFileSaveService _fileSaveService = fileSaveService; - public void GenerateEventDatFile(string audioProjectFileNameWithoutExtension, List actionEvents = null, List stateGroups = null) + public void GenerateEventDatFile(string audioProjectNameWithoutExtension, List actionEvents = null, List stateGroups = null) { var datFile = new SoundDatFile(); @@ -38,7 +38,7 @@ public void GenerateEventDatFile(string audioProjectFileNameWithoutExtension, Li } } - var datFileName = $"event_data__{audioProjectFileNameWithoutExtension}.dat"; + var datFileName = $"event_data__{audioProjectNameWithoutExtension}.dat"; var datFilePath = $"audio\\wwise\\{datFileName}"; SaveDatFileToPack(datFile, datFileName, datFilePath); } diff --git a/Editors/Audio/Shared/GameInformation/Warhammer3/Wh3SoundBankInformation.cs b/Editors/Audio/Shared/GameInformation/Warhammer3/Wh3SoundBankInformation.cs index 65146e39d..c71d193ab 100644 --- a/Editors/Audio/Shared/GameInformation/Warhammer3/Wh3SoundBankInformation.cs +++ b/Editors/Audio/Shared/GameInformation/Warhammer3/Wh3SoundBankInformation.cs @@ -53,7 +53,7 @@ public class Wh3SoundBankInformation public static Wh3Language? GetRequiredLanguage(Wh3SoundBank soundBank) => Information.First(definition => definition.GameSoundBank == soundBank).RequiredLanguage; - public static string GetSoundBankNameFromPrefix(string value) + public static string GetSoundBankNameFromSoundBankWithAudioProjectName(string value) { if (string.IsNullOrWhiteSpace(value)) return null; @@ -65,5 +65,22 @@ public static string GetSoundBankNameFromPrefix(string value) return matchingDefinition?.Name; } + + public static string GetAudioProjectNameFromSoundBankWithAudioProjectName(string value) + { + if (string.IsNullOrWhiteSpace(value)) + return null; + + var matchingDefinition = Wh3SoundBankInformation.Information + .OrderByDescending(soundBankDefinition => soundBankDefinition.Name.Length) + .FirstOrDefault(soundBankDefinition => + value.StartsWith($"{soundBankDefinition.Name}_", StringComparison.OrdinalIgnoreCase)); + + if (matchingDefinition == null) + return null; + + return value.Substring(matchingDefinition.Name.Length + 1); + } + } }