diff --git a/TRRandomizerCore/Editors/RandomizerSettings.cs b/TRRandomizerCore/Editors/RandomizerSettings.cs index 71e878c8f..6b56d1083 100644 --- a/TRRandomizerCore/Editors/RandomizerSettings.cs +++ b/TRRandomizerCore/Editors/RandomizerSettings.cs @@ -83,6 +83,8 @@ public class RandomizerSettings public bool ChangeCrashSFX { get; set; } public bool ChangeEnemySFX { get; set; } public bool LinkCreatureSFX { get; set; } + public uint UncontrolledSFXCount { get; set; } + public bool UncontrolledSFXAssaultCourse { get; set; } public bool RotateStartPositionOnly { get; set; } public bool RandomizeWaterLevels { get; set; } public bool RandomizeSlotPositions { get; set; } @@ -181,6 +183,8 @@ public void ApplyConfig(Config config) ChangeCrashSFX = config.GetBool(nameof(ChangeCrashSFX), true); ChangeEnemySFX = config.GetBool(nameof(ChangeEnemySFX), true); LinkCreatureSFX = config.GetBool(nameof(LinkCreatureSFX)); + UncontrolledSFXCount = config.GetUInt(nameof(UncontrolledSFXCount), 0); + UncontrolledSFXAssaultCourse = config.GetBool(nameof(UncontrolledSFXAssaultCourse)); RandomizeStartPosition = config.GetBool(nameof(RandomizeStartPosition)); StartPositionSeed = config.GetInt(nameof(StartPositionSeed), defaultSeed); @@ -278,6 +282,8 @@ public void StoreConfig(Config config) config[nameof(ChangeCrashSFX)] = ChangeCrashSFX; config[nameof(ChangeEnemySFX)] = ChangeEnemySFX; config[nameof(LinkCreatureSFX)] = LinkCreatureSFX; + config[nameof(UncontrolledSFXCount)] = UncontrolledSFXCount; + config[nameof(UncontrolledSFXAssaultCourse)] = UncontrolledSFXAssaultCourse; config[nameof(RandomizeStartPosition)] = RandomizeStartPosition; config[nameof(StartPositionSeed)] = StartPositionSeed; diff --git a/TRRandomizerCore/Randomizers/TR2/TR2AudioRandomizer.cs b/TRRandomizerCore/Randomizers/TR2/TR2AudioRandomizer.cs index f590dbcab..cf3ab0e95 100644 --- a/TRRandomizerCore/Randomizers/TR2/TR2AudioRandomizer.cs +++ b/TRRandomizerCore/Randomizers/TR2/TR2AudioRandomizer.cs @@ -12,21 +12,26 @@ using TRLevelReader.Model; using TRLevelReader.Model.Enums; using TRModelTransporter.Handlers; +using TRRandomizerCore.Helpers; namespace TRRandomizerCore.Randomizers { public class TR2AudioRandomizer : BaseTR2Randomizer { + private const int _maxSample = 407; + private AudioRandomizer _audioRandomizer; private List> _soundEffects; private List _sfxCategories; + private List _uncontrolledLevels; public override void Randomize(int seed) { _generator = new Random(seed); LoadAudioData(); + ChooseUncontrolledLevels(); foreach (TR2ScriptedLevel lvl in Levels) { @@ -45,6 +50,23 @@ public override void Randomize(int seed) } } + private void ChooseUncontrolledLevels() + { + TR2ScriptedLevel assaultCourse = Levels.Find(l => l.Is(TR2LevelNames.ASSAULT)); + ISet exlusions = new HashSet { assaultCourse }; + + _uncontrolledLevels = Levels.RandomSelection(_generator, (int)Settings.UncontrolledSFXCount, exclusions: exlusions); + if (Settings.AssaultCourseWireframe) + { + _uncontrolledLevels.Add(assaultCourse); + } + } + + public bool IsUncontrolledLevel(TR2ScriptedLevel level) + { + return _uncontrolledLevels.Contains(level); + } + private void RandomizeMusicTriggers(TR2CombinedLevel level) { FDControl floorData = new FDControl(); @@ -178,50 +200,63 @@ private void RandomizeSoundEffects(TR2CombinedLevel level) return; } - // Run through the SoundMap for this level and get the SFX definition for each one. - // Choose a new sound effect provided the definition is in a category we want to change. - // Lara's SFX are not changed by default. - for (int internalIndex = 0; internalIndex < level.Data.SoundMap.Length; internalIndex++) + if (IsUncontrolledLevel(level.Script)) { - TRSFXDefinition definition = _soundEffects.Find(sfx => sfx.InternalIndex == internalIndex); - if (level.Data.SoundMap[internalIndex] == -1 || definition == null || definition.Creature == TRSFXCreatureCategory.Lara || !_sfxCategories.Contains(definition.PrimaryCategory)) + // Choose a random sample for each current entry and replace the entire index list. + ISet indices = new HashSet(); + while (indices.Count < level.Data.NumSampleIndices) { - continue; + indices.Add((uint)_generator.Next(0, _maxSample + 1)); } - - // The following allows choosing to keep humans making human noises, and animals animal noises. - // Other humans can use Lara's SFX. - Predicate> pred; - if (Settings.LinkCreatureSFX && definition.Creature > TRSFXCreatureCategory.Lara) + level.Data.SampleIndices = indices.ToArray(); + } + else + { + // Run through the SoundMap for this level and get the SFX definition for each one. + // Choose a new sound effect provided the definition is in a category we want to change. + // Lara's SFX are not changed by default. + for (int internalIndex = 0; internalIndex < level.Data.SoundMap.Length; internalIndex++) { - pred = sfx => + TRSFXDefinition definition = _soundEffects.Find(sfx => sfx.InternalIndex == internalIndex); + if (level.Data.SoundMap[internalIndex] == -1 || definition == null || definition.Creature == TRSFXCreatureCategory.Lara || !_sfxCategories.Contains(definition.PrimaryCategory)) { - return sfx.Categories.Contains(definition.PrimaryCategory) && - sfx != definition && - ( - sfx.Creature == definition.Creature || - (sfx.Creature == TRSFXCreatureCategory.Lara && definition.Creature == TRSFXCreatureCategory.Human) - ); - }; - } - else - { - pred = sfx => sfx.Categories.Contains(definition.PrimaryCategory) && sfx != definition; - } + continue; + } - // Try to find definitions that match - List> otherDefinitions = _soundEffects.FindAll(pred); - if (otherDefinitions.Count > 0) - { - // Pick a new definition and try to import it into the level. This should only fail if - // the JSON is misconfigured e.g. missing sample indices. In that case, we just leave - // the current sound effect as-is. - TRSFXDefinition nextDefinition = otherDefinitions[_generator.Next(0, otherDefinitions.Count)]; - short soundDetailsIndex = ImportSoundEffect(level.Data, nextDefinition); - if (soundDetailsIndex != -1) + // The following allows choosing to keep humans making human noises, and animals animal noises. + // Other humans can use Lara's SFX. + Predicate> pred; + if (Settings.LinkCreatureSFX && definition.Creature > TRSFXCreatureCategory.Lara) + { + pred = sfx => + { + return sfx.Categories.Contains(definition.PrimaryCategory) && + sfx != definition && + ( + sfx.Creature == definition.Creature || + (sfx.Creature == TRSFXCreatureCategory.Lara && definition.Creature == TRSFXCreatureCategory.Human) + ); + }; + } + else { - // Only change it if the import succeeded - level.Data.SoundMap[internalIndex] = soundDetailsIndex; + pred = sfx => sfx.Categories.Contains(definition.PrimaryCategory) && sfx != definition; + } + + // Try to find definitions that match + List> otherDefinitions = _soundEffects.FindAll(pred); + if (otherDefinitions.Count > 0) + { + // Pick a new definition and try to import it into the level. This should only fail if + // the JSON is misconfigured e.g. missing sample indices. In that case, we just leave + // the current sound effect as-is. + TRSFXDefinition nextDefinition = otherDefinitions[_generator.Next(0, otherDefinitions.Count)]; + short soundDetailsIndex = ImportSoundEffect(level.Data, nextDefinition); + if (soundDetailsIndex != -1) + { + // Only change it if the import succeeded + level.Data.SoundMap[internalIndex] = soundDetailsIndex; + } } } } diff --git a/TRRandomizerCore/Randomizers/TR3/TR3AudioRandomizer.cs b/TRRandomizerCore/Randomizers/TR3/TR3AudioRandomizer.cs index b5c6ba739..f0dd02e23 100644 --- a/TRRandomizerCore/Randomizers/TR3/TR3AudioRandomizer.cs +++ b/TRRandomizerCore/Randomizers/TR3/TR3AudioRandomizer.cs @@ -6,8 +6,10 @@ using TRFDControl.FDEntryTypes; using TRFDControl.Utilities; using TRGE.Core; +using TRLevelReader.Helpers; using TRLevelReader.Model; using TRModelTransporter.Handlers; +using TRRandomizerCore.Helpers; using TRRandomizerCore.Levels; using TRRandomizerCore.SFX; @@ -15,6 +17,7 @@ namespace TRRandomizerCore.Randomizers { public class TR3AudioRandomizer : BaseTR3Randomizer { + private const int _maxSample = 413; private const int _defaultSecretTrack = 122; private AudioRandomizer _audioRandomizer; @@ -22,12 +25,14 @@ public class TR3AudioRandomizer : BaseTR3Randomizer private List> _soundEffects; private List _sfxCategories; + private List _uncontrolledLevels; public override void Randomize(int seed) { _generator = new Random(seed); LoadAudioData(); + ChooseUncontrolledLevels(); foreach (TR3ScriptedLevel lvl in Levels) { @@ -46,6 +51,23 @@ public override void Randomize(int seed) } } + private void ChooseUncontrolledLevels() + { + TR3ScriptedLevel assaultCourse = Levels.Find(l => l.Is(TR3LevelNames.ASSAULT)); + ISet exlusions = new HashSet { assaultCourse }; + + _uncontrolledLevels = Levels.RandomSelection(_generator, (int)Settings.UncontrolledSFXCount, exclusions: exlusions); + if (Settings.AssaultCourseWireframe) + { + _uncontrolledLevels.Add(assaultCourse); + } + } + + public bool IsUncontrolledLevel(TR3ScriptedLevel level) + { + return _uncontrolledLevels.Contains(level); + } + private void RandomizeMusicTriggers(TR3CombinedLevel level) { FDControl floorData = new FDControl(); @@ -139,50 +161,63 @@ private void RandomizeSoundEffects(TR3CombinedLevel level) return; } - // Run through the SoundMap for this level and get the SFX definition for each one. - // Choose a new sound effect provided the definition is in a category we want to change. - // Lara's SFX are not changed by default. - for (int internalIndex = 0; internalIndex < level.Data.SoundMap.Length; internalIndex++) + if (IsUncontrolledLevel(level.Script)) { - TRSFXDefinition definition = _soundEffects.Find(sfx => sfx.InternalIndex == internalIndex); - if (level.Data.SoundMap[internalIndex] == -1 || definition == null || definition.Creature == TRSFXCreatureCategory.Lara || !_sfxCategories.Contains(definition.PrimaryCategory)) + // Choose a random sample for each current entry and replace the entire index list. + ISet indices = new HashSet(); + while (indices.Count < level.Data.NumSampleIndices) { - continue; + indices.Add((uint)_generator.Next(0, _maxSample + 1)); } - - // The following allows choosing to keep humans making human noises, and animals animal noises. - // Other humans can use Lara's SFX. - Predicate> pred; - if (Settings.LinkCreatureSFX && definition.Creature > TRSFXCreatureCategory.Lara) + level.Data.SampleIndices = indices.ToArray(); + } + else + { + // Run through the SoundMap for this level and get the SFX definition for each one. + // Choose a new sound effect provided the definition is in a category we want to change. + // Lara's SFX are not changed by default. + for (int internalIndex = 0; internalIndex < level.Data.SoundMap.Length; internalIndex++) { - pred = sfx => + TRSFXDefinition definition = _soundEffects.Find(sfx => sfx.InternalIndex == internalIndex); + if (level.Data.SoundMap[internalIndex] == -1 || definition == null || definition.Creature == TRSFXCreatureCategory.Lara || !_sfxCategories.Contains(definition.PrimaryCategory)) { - return sfx.Categories.Contains(definition.PrimaryCategory) && - sfx != definition && - ( - sfx.Creature == definition.Creature || - (sfx.Creature == TRSFXCreatureCategory.Lara && definition.Creature == TRSFXCreatureCategory.Human) - ); - }; - } - else - { - pred = sfx => sfx.Categories.Contains(definition.PrimaryCategory) && sfx != definition; - } + continue; + } - // Try to find definitions that match - List> otherDefinitions = _soundEffects.FindAll(pred); - if (otherDefinitions.Count > 0) - { - // Pick a new definition and try to import it into the level. This should only fail if - // the JSON is misconfigured e.g. missing sample indices. In that case, we just leave - // the current sound effect as-is. - TRSFXDefinition nextDefinition = otherDefinitions[_generator.Next(0, otherDefinitions.Count)]; - short soundDetailsIndex = ImportSoundEffect(level.Data, definition, nextDefinition); - if (soundDetailsIndex != -1) + // The following allows choosing to keep humans making human noises, and animals animal noises. + // Other humans can use Lara's SFX. + Predicate> pred; + if (Settings.LinkCreatureSFX && definition.Creature > TRSFXCreatureCategory.Lara) + { + pred = sfx => + { + return sfx.Categories.Contains(definition.PrimaryCategory) && + sfx != definition && + ( + sfx.Creature == definition.Creature || + (sfx.Creature == TRSFXCreatureCategory.Lara && definition.Creature == TRSFXCreatureCategory.Human) + ); + }; + } + else + { + pred = sfx => sfx.Categories.Contains(definition.PrimaryCategory) && sfx != definition; + } + + // Try to find definitions that match + List> otherDefinitions = _soundEffects.FindAll(pred); + if (otherDefinitions.Count > 0) { - // Only change it if the import succeeded - level.Data.SoundMap[internalIndex] = soundDetailsIndex; + // Pick a new definition and try to import it into the level. This should only fail if + // the JSON is misconfigured e.g. missing sample indices. In that case, we just leave + // the current sound effect as-is. + TRSFXDefinition nextDefinition = otherDefinitions[_generator.Next(0, otherDefinitions.Count)]; + short soundDetailsIndex = ImportSoundEffect(level.Data, definition, nextDefinition); + if (soundDetailsIndex != -1) + { + // Only change it if the import succeeded + level.Data.SoundMap[internalIndex] = soundDetailsIndex; + } } } } diff --git a/TRRandomizerCore/TRRandomizerController.cs b/TRRandomizerCore/TRRandomizerController.cs index 1667c475d..4d579c79f 100644 --- a/TRRandomizerCore/TRRandomizerController.cs +++ b/TRRandomizerCore/TRRandomizerController.cs @@ -646,6 +646,18 @@ public bool LinkCreatureSFX set => LevelRandomizer.LinkCreatureSFX = value; } + public uint UncontrolledSFXCount + { + get => LevelRandomizer.UncontrolledSFXCount; + set => LevelRandomizer.UncontrolledSFXCount = value; + } + + public bool UncontrolledSFXAssaultCourse + { + get => LevelRandomizer.UncontrolledSFXAssaultCourse; + set => LevelRandomizer.UncontrolledSFXAssaultCourse = value; + } + public bool RandomizeStartPosition { get => LevelRandomizer.RandomizeStartPosition; diff --git a/TRRandomizerView/Controls/EditorControl.xaml b/TRRandomizerView/Controls/EditorControl.xaml index e7f734a36..e51938c3e 100644 --- a/TRRandomizerView/Controls/EditorControl.xaml +++ b/TRRandomizerView/Controls/EditorControl.xaml @@ -297,6 +297,7 @@ MainDescription="Customize the audio randomization." BoolItemsSource="{Binding Data.AudioBoolItemControls, Source={StaticResource proxy}}" HasBoolItems="True" + HasAudioOptions="True" ControllerProxy="{Binding Data, Source={StaticResource proxy}}"> diff --git a/TRRandomizerView/Model/ControllerOptions.cs b/TRRandomizerView/Model/ControllerOptions.cs index 200fed505..1a3c6e580 100644 --- a/TRRandomizerView/Model/ControllerOptions.cs +++ b/TRRandomizerView/Model/ControllerOptions.cs @@ -56,6 +56,8 @@ public class ControllerOptions : INotifyPropertyChanged private bool _vfxRoom; private bool _vfxCaustics; private bool _vfxWave; + private uint _uncontrolledSFXCount; + private bool _uncontrolledSFXAssaultCourse; private List _secretBoolItemControls, _itemBoolItemControls, _enemyBoolItemControls, _textureBoolItemControls, _audioBoolItemControls, _outfitBoolItemControls, _textBoolItemControls, _startBoolItemControls, _environmentBoolItemControls; private List _selectableEnemies; @@ -131,6 +133,7 @@ private void UpdateMaximumLevelCount() HaircutLevelCount = (uint)Math.Min(HaircutLevelCount, MaximumLevelCount); InvisibleLevelCount = (uint)Math.Min(InvisibleLevelCount, MaximumLevelCount); WireframeLevelCount = (uint)Math.Min(WireframeLevelCount, MaximumLevelCount); + UncontrolledSFXCount = (uint)Math.Min(UncontrolledSFXCount, MaximumLevelCount); } public bool RandomizationPossible @@ -532,6 +535,26 @@ public BoolItemControlClass LinkCreatureSFX } } + public uint UncontrolledSFXCount + { + get => _uncontrolledSFXCount; + set + { + _uncontrolledSFXCount = value; + FirePropertyChanged(); + } + } + + public bool UncontrolledSFXAssaultCourse + { + get => _uncontrolledSFXAssaultCourse; + set + { + _uncontrolledSFXAssaultCourse = value; + FirePropertyChanged(); + } + } + public bool RandomizeSecrets { get => _randomSecretsControl.IsActive; @@ -1527,6 +1550,8 @@ public void Load(TRRandomizerController controller) ChangeCrashSFX.Value = _controller.ChangeCrashSFX; ChangeEnemySFX.Value = _controller.ChangeEnemySFX; LinkCreatureSFX.Value = _controller.LinkCreatureSFX; + UncontrolledSFXCount = _controller.UncontrolledSFXCount; + UncontrolledSFXAssaultCourse = _controller.UncontrolledSFXAssaultCourse; RandomizeItems = _controller.RandomizeItems; ItemSeed = _controller.ItemSeed; @@ -1801,6 +1826,8 @@ public void Save() _controller.ChangeCrashSFX = ChangeCrashSFX.Value; _controller.ChangeEnemySFX = ChangeEnemySFX.Value; _controller.LinkCreatureSFX = LinkCreatureSFX.Value; + _controller.UncontrolledSFXCount = UncontrolledSFXCount; + _controller.UncontrolledSFXAssaultCourse = UncontrolledSFXAssaultCourse; _controller.RandomizeItems = RandomizeItems; _controller.ItemSeed = ItemSeed; diff --git a/TRRandomizerView/Windows/AdvancedWindow.xaml b/TRRandomizerView/Windows/AdvancedWindow.xaml index 4e59fb15a..fdca7785a 100644 --- a/TRRandomizerView/Windows/AdvancedWindow.xaml +++ b/TRRandomizerView/Windows/AdvancedWindow.xaml @@ -74,6 +74,7 @@ + @@ -948,6 +949,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SetValue(HasTextureOptionsProperty, value); } + public bool HasAudioOptions + { + get => (bool)GetValue(HasAudioOptionsProperty); + set => SetValue(HasAudioOptionsProperty, value); + } + public ControllerOptions ControllerProxy { get => (ControllerOptions)GetValue(ControllerProperty);