Skip to content

Commit

Permalink
LostArtefacts#326 Uncontrolled SFX
Browse files Browse the repository at this point in the history
Adds an option to have SFX randomized without categorisation, so producing a very surreal result. This can be limited to a chosen number of levels - the recommendation is to keep the value low.
  • Loading branch information
lahm86 committed Apr 25, 2022
1 parent 5e8561a commit 4492661
Show file tree
Hide file tree
Showing 8 changed files with 256 additions and 74 deletions.
6 changes: 6 additions & 0 deletions TRRandomizerCore/Editors/RandomizerSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
109 changes: 72 additions & 37 deletions TRRandomizerCore/Randomizers/TR2/TR2AudioRandomizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<TRSFXDefinition<TRSoundDetails>> _soundEffects;
private List<TRSFXGeneralCategory> _sfxCategories;
private List<TR2ScriptedLevel> _uncontrolledLevels;

public override void Randomize(int seed)
{
_generator = new Random(seed);

LoadAudioData();
ChooseUncontrolledLevels();

foreach (TR2ScriptedLevel lvl in Levels)
{
Expand All @@ -45,6 +50,23 @@ public override void Randomize(int seed)
}
}

private void ChooseUncontrolledLevels()
{
TR2ScriptedLevel assaultCourse = Levels.Find(l => l.Is(TR2LevelNames.ASSAULT));
ISet<TR2ScriptedLevel> exlusions = new HashSet<TR2ScriptedLevel> { 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();
Expand Down Expand Up @@ -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<TRSoundDetails> 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<uint> indices = new HashSet<uint>();
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<TRSFXDefinition<TRSoundDetails>> 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<TRSoundDetails> 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<TRSFXDefinition<TRSoundDetails>> 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<TRSoundDetails> 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<TRSFXDefinition<TRSoundDetails>> 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<TRSFXDefinition<TRSoundDetails>> 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<TRSoundDetails> 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;
}
}
}
}
Expand Down
109 changes: 72 additions & 37 deletions TRRandomizerCore/Randomizers/TR3/TR3AudioRandomizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,33 @@
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;

namespace TRRandomizerCore.Randomizers
{
public class TR3AudioRandomizer : BaseTR3Randomizer
{
private const int _maxSample = 413;
private const int _defaultSecretTrack = 122;

private AudioRandomizer _audioRandomizer;
private TRAudioTrack _fixedSecretTrack;

private List<TRSFXDefinition<TR3SoundDetails>> _soundEffects;
private List<TRSFXGeneralCategory> _sfxCategories;
private List<TR3ScriptedLevel> _uncontrolledLevels;

public override void Randomize(int seed)
{
_generator = new Random(seed);

LoadAudioData();
ChooseUncontrolledLevels();

foreach (TR3ScriptedLevel lvl in Levels)
{
Expand All @@ -46,6 +51,23 @@ public override void Randomize(int seed)
}
}

private void ChooseUncontrolledLevels()
{
TR3ScriptedLevel assaultCourse = Levels.Find(l => l.Is(TR3LevelNames.ASSAULT));
ISet<TR3ScriptedLevel> exlusions = new HashSet<TR3ScriptedLevel> { 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();
Expand Down Expand Up @@ -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<TR3SoundDetails> 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<uint> indices = new HashSet<uint>();
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<TRSFXDefinition<TR3SoundDetails>> 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<TR3SoundDetails> 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<TRSFXDefinition<TR3SoundDetails>> 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<TR3SoundDetails> 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<TRSFXDefinition<TR3SoundDetails>> 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<TRSFXDefinition<TR3SoundDetails>> 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<TR3SoundDetails> 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;
}
}
}
}
Expand Down
12 changes: 12 additions & 0 deletions TRRandomizerCore/TRRandomizerController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions TRRandomizerView/Controls/EditorControl.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -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}}">
</windows:AdvancedWindow>
</ctrl:ManagedSeedAdvancedControl.AdvancedWindowToOpen>
Expand Down
Loading

0 comments on commit 4492661

Please sign in to comment.