Split Track into Selectable and Audio interfaces, add support for groups
This commit is contained in:
		
							parent
							
								
									47f984cd28
								
							
						
					
					
						commit
						5649a18633
					
				|  | @ -2,6 +2,7 @@ | |||
| 
 | ||||
| ## MuzikaGromche 1337.420.9001 | ||||
| 
 | ||||
| - Added support for tracks to rotate between multiple audio variants during a round. | ||||
| 
 | ||||
| ## MuzikaGromche 1337.420.69 - It's All DiscoNnected Edition | ||||
| 
 | ||||
|  |  | |||
|  | @ -48,8 +48,8 @@ namespace MuzikaGromche | |||
|                 .Select(a => $" Trying... {a}") | ||||
|         ]; | ||||
| 
 | ||||
|         public static readonly Track[] Tracks = [ | ||||
|             new Track | ||||
|         public static readonly ISelectableTrack[] Tracks = [ | ||||
|             new SelectableAudioTrack | ||||
|             { | ||||
|                 Name = "MuzikaGromche", | ||||
|                 AudioType = AudioType.OGGVORBIS, | ||||
|  | @ -89,7 +89,7 @@ namespace MuzikaGromche | |||
|                     (63, "Muzyka Gromche\nGlaza zakryty >_<"), | ||||
|                 ], | ||||
|             }, | ||||
|             new Track | ||||
|             new SelectableAudioTrack | ||||
|             { | ||||
|                 Name = "VseVZale", | ||||
|                 AudioType = AudioType.OGGVORBIS, | ||||
|  | @ -124,7 +124,7 @@ namespace MuzikaGromche | |||
|                     (60, "Everybody shake your body"), | ||||
|                 ], | ||||
|             }, | ||||
|             new Track | ||||
|             new SelectableAudioTrack | ||||
|             { | ||||
|                 Name = "DeployDestroy", | ||||
|                 AudioType = AudioType.OGGVORBIS, | ||||
|  | @ -167,7 +167,7 @@ namespace MuzikaGromche | |||
|                     (25, "Davaj-davaj!"), | ||||
|                 ], | ||||
|             }, | ||||
|             new Track | ||||
|             new SelectableAudioTrack | ||||
|             { | ||||
|                 Name = "MoyaZhittya", | ||||
|                 AudioType = AudioType.OGGVORBIS, | ||||
|  | @ -214,7 +214,7 @@ namespace MuzikaGromche | |||
|                     ( 30, "IT'S MY"), | ||||
|                 ], | ||||
|             }, | ||||
|             new Track | ||||
|             new SelectableAudioTrack | ||||
|             { | ||||
|                 Name = "Gorgorod", | ||||
|                 AudioType = AudioType.OGGVORBIS, | ||||
|  | @ -232,7 +232,7 @@ namespace MuzikaGromche | |||
|                 FlickerLightsTimeSeries = [20], | ||||
|                 Lyrics = [], | ||||
|             }, | ||||
|             new Track | ||||
|             new SelectableAudioTrack | ||||
|             { | ||||
|                 Name = "Durochka", | ||||
|                 AudioType = AudioType.OGGVORBIS, | ||||
|  | @ -250,7 +250,7 @@ namespace MuzikaGromche | |||
|                 FlickerLightsTimeSeries = [-9], | ||||
|                 Lyrics = [], | ||||
|             }, | ||||
|             new Track | ||||
|             new SelectableAudioTrack | ||||
|             { | ||||
|                 Name = "ZmeiGorynich", | ||||
|                 AudioType = AudioType.OGGVORBIS, | ||||
|  | @ -268,7 +268,7 @@ namespace MuzikaGromche | |||
|                 FlickerLightsTimeSeries = [-5, 31], | ||||
|                 Lyrics = [], | ||||
|             }, | ||||
|             new Track | ||||
|             new SelectableAudioTrack | ||||
|             { | ||||
|                 Name = "GodMode", | ||||
|                 AudioType = AudioType.OGGVORBIS, | ||||
|  | @ -286,7 +286,7 @@ namespace MuzikaGromche | |||
|                 FlickerLightsTimeSeries = [-5], | ||||
|                 Lyrics = [], | ||||
|             }, | ||||
|             new Track | ||||
|             new SelectableAudioTrack | ||||
|             { | ||||
|                 Name = "RiseAndShine", | ||||
|                 AudioType = AudioType.OGGVORBIS, | ||||
|  | @ -304,7 +304,7 @@ namespace MuzikaGromche | |||
|                 FlickerLightsTimeSeries = [-5.5f, 31, 63.9f], | ||||
|                 Lyrics = [], | ||||
|             }, | ||||
|             new Track | ||||
|             new SelectableAudioTrack | ||||
|             { | ||||
|                 Name = "Song2", | ||||
|                 AudioType = AudioType.OGGVORBIS, | ||||
|  | @ -322,7 +322,7 @@ namespace MuzikaGromche | |||
|                 FlickerLightsTimeSeries = [2.5f], | ||||
|                 Lyrics = [], | ||||
|             }, | ||||
|             new Track | ||||
|             new SelectableAudioTrack | ||||
|             { | ||||
|                 Name = "Peretasovka", | ||||
|                 AudioType = AudioType.OGGVORBIS, | ||||
|  | @ -340,7 +340,7 @@ namespace MuzikaGromche | |||
|                 FlickerLightsTimeSeries = [-8, 31], | ||||
|                 Lyrics = [], | ||||
|             }, | ||||
|             new Track | ||||
|             new SelectableAudioTrack | ||||
|             { | ||||
|                 Name = "Yalgaar", | ||||
|                 AudioType = AudioType.OGGVORBIS, | ||||
|  | @ -358,7 +358,7 @@ namespace MuzikaGromche | |||
|                 FlickerLightsTimeSeries = [-5], | ||||
|                 Lyrics = [], | ||||
|             }, | ||||
|             new Track | ||||
|             new SelectableAudioTrack | ||||
|             { | ||||
|                 Name = "Chereshnya", | ||||
|                 AudioType = AudioType.OGGVORBIS, | ||||
|  | @ -379,7 +379,7 @@ namespace MuzikaGromche | |||
|                 FlickerLightsTimeSeries = [-5, 27, 29, 59, 61], | ||||
|                 Lyrics = [], | ||||
|             }, | ||||
|             new Track | ||||
|             new SelectableAudioTrack | ||||
|             { | ||||
|                 Name = "PWNED", | ||||
|                 AudioType = AudioType.OGGVORBIS, | ||||
|  | @ -451,7 +451,7 @@ namespace MuzikaGromche | |||
|                     (98, $"\t\t\tresolving ur private IP\nP_WNED"), | ||||
|                 ], | ||||
|             }, | ||||
|             new Track | ||||
|             new SelectableAudioTrack | ||||
|             { | ||||
|                 Name = "Kach", | ||||
|                 AudioType = AudioType.OGGVORBIS, | ||||
|  | @ -475,7 +475,7 @@ namespace MuzikaGromche | |||
|                 FlickerLightsTimeSeries = [-120.5f, -105, -89, -8, 44, 45], | ||||
|                 Lyrics = [], | ||||
|             }, | ||||
|             new Track | ||||
|             new SelectableAudioTrack | ||||
|             { | ||||
|                 Name = "BeefLiver", | ||||
|                 AudioType = AudioType.OGGVORBIS, | ||||
|  | @ -498,7 +498,7 @@ namespace MuzikaGromche | |||
|             }, | ||||
|         ]; | ||||
| 
 | ||||
|         public static Track ChooseTrack() | ||||
|         public static ISelectableTrack ChooseTrack() | ||||
|         { | ||||
|             var seed = RoundManager.Instance.dungeonGenerator.Generator.ChosenSeed; | ||||
|             var tracks = Config.SkipExplicitTracks.Value ? [.. Tracks.Where(track => !track.IsExplicit)] : Tracks; | ||||
|  | @ -510,12 +510,12 @@ namespace MuzikaGromche | |||
|             return tracks[trackId]; | ||||
|         } | ||||
| 
 | ||||
|         public static Track? FindTrackNamed(string name) | ||||
|         public static IAudioTrack? FindTrackNamed(string name) | ||||
|         { | ||||
|             return Tracks.FirstOrDefault(track => track.Name == name); | ||||
|             return Tracks.SelectMany(track => track.GetTracks()).FirstOrDefault(track => track.Name == name); | ||||
|         } | ||||
| 
 | ||||
|         internal static Track? CurrentTrack; | ||||
|         internal static IAudioTrack? CurrentTrack; | ||||
|         internal static BeatTimeState? BeatTimeState; | ||||
| 
 | ||||
|         public static void SetLightColor(Color color) | ||||
|  | @ -562,7 +562,7 @@ namespace MuzikaGromche | |||
|             Dictionary<string, (UnityWebRequest Request, List<Action<AudioClip>> Setters)> requests = []; | ||||
|             requests.EnsureCapacity(Tracks.Length * 2); | ||||
| 
 | ||||
|             foreach (var track in Tracks) | ||||
|             foreach (var track in Tracks.SelectMany(track => track.GetTracks())) | ||||
|             { | ||||
|                 foreach (var (fileName, setter) in new (string, Action<AudioClip>)[] | ||||
|                 { | ||||
|  | @ -599,7 +599,7 @@ namespace MuzikaGromche | |||
| #if DEBUG | ||||
|                 foreach (var track in Tracks) | ||||
|                 { | ||||
|                     Debug.Log($"{nameof(MuzikaGromche)} Track {track.Name} {track.LoadedIntro.length:N4} {track.LoadedLoop.length:N4}"); | ||||
|                     track.Debug(); | ||||
|                 } | ||||
| #endif | ||||
|                 Config = new Config(base.Config); | ||||
|  | @ -721,60 +721,80 @@ namespace MuzikaGromche | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public class Track | ||||
|     public struct SelectableTrackData() | ||||
|     { | ||||
|         public required string Name; | ||||
|         // Name of the track, as shown in config entry UI; also used for default file names. | ||||
|         public required string Name { get; init; } | ||||
| 
 | ||||
|         // Language of the track's lyrics. | ||||
|         public required Language Language; | ||||
|         public required Language Language { get; init; } | ||||
| 
 | ||||
|         // Whether this track has NSFW/explicit lyrics. | ||||
|         public bool IsExplicit = false; | ||||
|         public bool IsExplicit { get; init; } = false; | ||||
| 
 | ||||
|         // How often this track should be chosen, relative to the sum of weights of all tracks. | ||||
|         public ConfigEntry<int> Weight { get; internal set; } = null!; | ||||
|     } | ||||
| 
 | ||||
|     // An instance of a track which appears as a configuration entry and | ||||
|     // can be selected using weighted random from a list of selectable tracks. | ||||
|     public interface ISelectableTrack | ||||
|     { | ||||
|         // Name of the track, as shown in config entry UI; also used for default file names. | ||||
|         public string Name { get; init; } | ||||
| 
 | ||||
|         // Language of the track's lyrics. | ||||
|         public Language Language { get; init; } | ||||
| 
 | ||||
|         // Whether this track has NSFW/explicit lyrics. | ||||
|         public bool IsExplicit { get; init; } | ||||
| 
 | ||||
|         // How often this track should be chosen, relative to the sum of weights of all tracks. | ||||
|         internal ConfigEntry<int> Weight { get; set; } | ||||
| 
 | ||||
|         internal IAudioTrack[] GetTracks(); | ||||
| 
 | ||||
|         // Index is a non-negative monotonically increasing number of times | ||||
|         // this ISelectableTrack has been played for this Jester on this day. | ||||
|         // A group of tracks can use this index to rotate tracks sequentially. | ||||
|         internal IAudioTrack SelectTrack(int index); | ||||
| 
 | ||||
|         internal void Debug(); | ||||
|     } | ||||
| 
 | ||||
|     // An instance of a track which has file names, timings data, palette; can be loaded and played. | ||||
|     public interface IAudioTrack | ||||
|     { | ||||
|         // Name of the track used for default file names. | ||||
|         public string Name { get; } | ||||
| 
 | ||||
|         // Wind-up time can and should be shorter than the Intro audio track, | ||||
|         // so that the "pop" effect can be baked into the Intro audio and kept away | ||||
|         // from the looped part. This also means that the light show starts before | ||||
|         // the looped track does, so we need to sync them up as soon as we enter the Loop. | ||||
|         public required float WindUpTimer; | ||||
|         public float WindUpTimer { get; } | ||||
| 
 | ||||
|         // Estimated number of beats per minute. Not used for light show, but might come in handy. | ||||
|         public float Bpm => 60f / (LoadedLoop.length / Beats); | ||||
| 
 | ||||
|         // How many beats the loop segment has. The default strategy is to switch color of lights on each beat. | ||||
|         public int Beats; | ||||
|         public int Beats { get; } | ||||
| 
 | ||||
|         // Number of beats between WindUpTimer and where looped segment starts (not the loop audio). | ||||
|         public int LoopOffset = 0; | ||||
|         public int LoopOffset { get; } | ||||
|         public float LoopOffsetInSeconds => LoopOffset / Beats * LoadedLoop.length; | ||||
| 
 | ||||
|         // Shorthand for four beats | ||||
|         public int Bars | ||||
|         { | ||||
|             set => Beats = value * 4; | ||||
|         } | ||||
| 
 | ||||
|         // MPEG is basically mp3, and it can produce gaps at the start. | ||||
|         // WAV is OK, but takes a lot of space. Try OGGVORBIS instead. | ||||
|         public AudioType AudioType = AudioType.MPEG; | ||||
|         public AudioType AudioType { get; } | ||||
| 
 | ||||
|         public AudioClip LoadedIntro = null!; | ||||
|         public AudioClip LoadedLoop = null!; | ||||
|         public AudioClip LoadedIntro { get; internal set; } | ||||
|         public AudioClip LoadedLoop { get; internal set; } | ||||
| 
 | ||||
|         // How often this track should be chosen, relative to the sum of weights of all tracks. | ||||
|         public ConfigEntry<int> Weight = null!; | ||||
|         public string FileNameIntro { get; } | ||||
|         public string FileNameLoop { get; } | ||||
| 
 | ||||
|         private string? FileNameIntroOverride = null; | ||||
|         public string FileNameIntro | ||||
|         { | ||||
|             get => FileNameIntroOverride ?? $"{Name}Intro.{Ext}"; | ||||
|             set => FileNameIntroOverride = value; | ||||
|         } | ||||
| 
 | ||||
|         private string? FileNameLoopOverride = null; | ||||
|         public string FileNameLoop | ||||
|         { | ||||
|             get => FileNameLoopOverride ?? $"{Name}Loop.{Ext}"; | ||||
|             set => FileNameLoopOverride = value; | ||||
|         } | ||||
| 
 | ||||
|         private string Ext => AudioType switch | ||||
|         public string Ext => AudioType switch | ||||
|         { | ||||
|             AudioType.MPEG => "mp3", | ||||
|             AudioType.WAV => "wav", | ||||
|  | @ -783,28 +803,86 @@ namespace MuzikaGromche | |||
|         }; | ||||
| 
 | ||||
|         // Offset of beats. Bigger offset => colors will change later. | ||||
|         public float BeatsOffset { get; } | ||||
| 
 | ||||
|         // Offset of beats, in seconds. Bigger offset => colors will change later. | ||||
|         public float BeatsOffsetInSeconds => BeatsOffset / Beats * LoadedLoop.length; | ||||
| 
 | ||||
|         public float FadeOutBeat { get; } | ||||
|         public float FadeOutDuration { get; } | ||||
| 
 | ||||
|         // Duration of color transition, measured in beats. | ||||
|         public float ColorTransitionIn { get; } | ||||
|         public float ColorTransitionOut { get; } | ||||
| 
 | ||||
|         // Easing function for color transitions. | ||||
|         public Easing ColorTransitionEasing { get; } | ||||
| 
 | ||||
|         public float[] FlickerLightsTimeSeries { get; } | ||||
| 
 | ||||
|         public float[] LyricsTimeSeries { get; } | ||||
| 
 | ||||
|         // Lyrics line may contain multiple tab-separated alternatives. | ||||
|         // In such case, a random number chosen and updated once per loop | ||||
|         // is used to select an alternative. | ||||
|         // If the chosen alternative is an empty string, lyrics event shall be skipped. | ||||
|         public string[] LyricsLines { get; } | ||||
| 
 | ||||
|         public Palette Palette { get; } | ||||
|     } | ||||
|   | ||||
|     // Core audio track implementation with some defaults and config overrides. | ||||
|     // Suitable to declare elemnents of SelectableTracksGroup and as a base for standalone selectable tracks. | ||||
|     public class CoreAudioTrack : IAudioTrack | ||||
|     { | ||||
|         public required string Name { get; init; } | ||||
|         public required float WindUpTimer { get; init; } | ||||
|         public int Beats { get; init; } | ||||
| 
 | ||||
|         // Shorthand for four beats | ||||
|         public int Bars | ||||
|         { | ||||
|             init => Beats = value * 4; | ||||
|         } | ||||
| 
 | ||||
|         public int LoopOffset { get; init; } = 0; | ||||
|         public AudioType AudioType { get; init; } = AudioType.MPEG; | ||||
|         public AudioClip LoadedIntro { get; set; } = null!; | ||||
|         public AudioClip LoadedLoop { get; set; } = null!; | ||||
| 
 | ||||
|         private string? FileNameIntroOverride = null; | ||||
|         public string FileNameIntro | ||||
|         { | ||||
|             get => FileNameIntroOverride ?? $"{Name}Intro.{((IAudioTrack)this).Ext}"; | ||||
|             init => FileNameIntroOverride = value; | ||||
|         } | ||||
| 
 | ||||
|         private string? FileNameLoopOverride = null; | ||||
|         public string FileNameLoop | ||||
|         { | ||||
|             get => FileNameLoopOverride ?? $"{Name}Loop.{((IAudioTrack)this).Ext}"; | ||||
|             init => FileNameLoopOverride = value; | ||||
|         } | ||||
| 
 | ||||
|         public float _BeatsOffset = 0f; | ||||
|         public float BeatsOffset | ||||
|         { | ||||
|             get => Config.BeatsOffsetOverride ?? _BeatsOffset; | ||||
|             set => _BeatsOffset = value; | ||||
|             init => _BeatsOffset = value; | ||||
|         } | ||||
| 
 | ||||
|         // Offset of beats, in seconds. Bigger offset => colors will change later. | ||||
|         public float BeatsOffsetInSeconds => BeatsOffset / Beats * LoadedLoop.length; | ||||
| 
 | ||||
|         public float _FadeOutBeat = float.NaN; | ||||
|         public float FadeOutBeat | ||||
|         { | ||||
|             get => Config.FadeOutBeatOverride ?? _FadeOutBeat; | ||||
|             set => _FadeOutBeat = value; | ||||
|             init => _FadeOutBeat = value; | ||||
|         } | ||||
| 
 | ||||
|         public float _FadeOutDuration = 2f; | ||||
|         public float FadeOutDuration | ||||
|         { | ||||
|             get => Config.FadeOutDurationOverride ?? _FadeOutDuration; | ||||
|             set => _FadeOutDuration = value; | ||||
|             init => _FadeOutDuration = value; | ||||
|         } | ||||
| 
 | ||||
|         // Duration of color transition, measured in beats. | ||||
|  | @ -812,14 +890,14 @@ namespace MuzikaGromche | |||
|         public float ColorTransitionIn | ||||
|         { | ||||
|             get => Config.ColorTransitionInOverride ?? _ColorTransitionIn; | ||||
|             set => _ColorTransitionIn = value; | ||||
|             init => _ColorTransitionIn = value; | ||||
|         } | ||||
| 
 | ||||
|         public float _ColorTransitionOut = 0.25f; | ||||
|         public float ColorTransitionOut | ||||
|         { | ||||
|             get => Config.ColorTransitionOutOverride ?? _ColorTransitionOut; | ||||
|             set => _ColorTransitionOut = value; | ||||
|             init => _ColorTransitionOut = value; | ||||
|         } | ||||
| 
 | ||||
|         // Easing function for color transitions. | ||||
|  | @ -829,14 +907,14 @@ namespace MuzikaGromche | |||
|             get => Config.ColorTransitionEasingOverride != null | ||||
|                 ? Easing.FindByName(Config.ColorTransitionEasingOverride) | ||||
|                 : _ColorTransitionEasing; | ||||
|             set => _ColorTransitionEasing = value; | ||||
|             init => _ColorTransitionEasing = value; | ||||
|         } | ||||
| 
 | ||||
|         public float[] _FlickerLightsTimeSeries = []; | ||||
|         public float[] FlickerLightsTimeSeries | ||||
|         { | ||||
|             get => Config.FlickerLightsTimeSeriesOverride ?? _FlickerLightsTimeSeries; | ||||
|             set | ||||
|             init | ||||
|             { | ||||
|                 Array.Sort(value); | ||||
|                 _FlickerLightsTimeSeries = value; | ||||
|  | @ -877,6 +955,53 @@ namespace MuzikaGromche | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Standalone, top-level, selectable audio track | ||||
|     public class SelectableAudioTrack : CoreAudioTrack, ISelectableTrack | ||||
|     { | ||||
|         public required Language Language { get; init; } | ||||
|         public bool IsExplicit { get; init; } = false; | ||||
|         ConfigEntry<int> ISelectableTrack.Weight { get; set; } = null!; | ||||
| 
 | ||||
|         IAudioTrack[] ISelectableTrack.GetTracks() => [this]; | ||||
| 
 | ||||
|         IAudioTrack ISelectableTrack.SelectTrack(int index) => this; | ||||
| 
 | ||||
|         void ISelectableTrack.Debug() | ||||
|         { | ||||
|             Debug.Log($"{nameof(MuzikaGromche)} Track \"{Name}\", Intro={LoadedIntro.length:N4}, Loop={LoadedLoop.length:N4}"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public class SelectableTracksGroup : ISelectableTrack | ||||
|     { | ||||
|         public required string Name { get; init; } | ||||
|         public required Language Language { get; init; } | ||||
|         public bool IsExplicit { get; init; } = false; | ||||
|         ConfigEntry<int> ISelectableTrack.Weight { get; set; } = null!; | ||||
| 
 | ||||
|         public required IAudioTrack[] Tracks; | ||||
| 
 | ||||
|         IAudioTrack[] ISelectableTrack.GetTracks() => Tracks; | ||||
| 
 | ||||
|         IAudioTrack ISelectableTrack.SelectTrack(int index) | ||||
|         { | ||||
|             if (Tracks.Length == 0) | ||||
|             { | ||||
|                 throw new IndexOutOfRangeException("Tracks list is empty"); | ||||
|             } | ||||
|             return Mod.Index(Tracks, index); | ||||
|         } | ||||
| 
 | ||||
|         void ISelectableTrack.Debug() | ||||
|         { | ||||
|             Debug.Log($"{nameof(MuzikaGromche)} Track Group \"{Name}\", Count={Tracks.Length}"); | ||||
|             foreach (var (track, index) in Tracks.Select((x, i) => (x, i))) | ||||
|             { | ||||
|                 Debug.Log($"{nameof(MuzikaGromche)}     Track {index} \"{track.Name}\", Intro={track.LoadedIntro.length:N4}, Loop={track.LoadedLoop.length:N4}"); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     readonly record struct BeatTimestamp | ||||
|     { | ||||
|         // Number of beats in the loop audio segment. | ||||
|  | @ -1252,7 +1377,7 @@ namespace MuzikaGromche | |||
| 
 | ||||
|     class BeatTimeState | ||||
|     { | ||||
|         private readonly Track track; | ||||
|         private readonly IAudioTrack track; | ||||
| 
 | ||||
|         private readonly JesterAudioSourcesState AudioState; | ||||
| 
 | ||||
|  | @ -1270,7 +1395,7 @@ namespace MuzikaGromche | |||
| 
 | ||||
|         private bool WindUpZeroBeatEventTriggered = false; | ||||
| 
 | ||||
|         public BeatTimeState(Track track) | ||||
|         public BeatTimeState(IAudioTrack track) | ||||
|         { | ||||
|             if (LyricsRandom == null) | ||||
|             { | ||||
|  | @ -1853,7 +1978,7 @@ namespace MuzikaGromche | |||
| 
 | ||||
|             void load() | ||||
|             { | ||||
|                 var palette = Plugin.CurrentTrack?._Palette ?? Palette.DEFAULT; | ||||
|                 var palette = (Plugin.CurrentTrack as CoreAudioTrack)?._Palette ?? Palette.DEFAULT; | ||||
|                 var colors = palette.Colors; | ||||
|                 var count = Math.Min(colors.Count(), maxCustomPaletteSize); | ||||
| 
 | ||||
|  | @ -1886,7 +2011,7 @@ namespace MuzikaGromche | |||
|             const string section = "Timings"; | ||||
|             var colorTransitionRange = new AcceptableValueRange<float>(0f, 1f); | ||||
|             // Declare and initialize early to avoid "Use of unassigned local variable" | ||||
|             List<(Action<Track?> Load, Action Apply)> entries = []; | ||||
|             List<(Action<CoreAudioTrack?> Load, Action Apply)> entries = []; | ||||
|             SyncedEntry<bool> overrideTimingsSyncedEntry = null!; | ||||
|             SyncedEntry<float> fadeOutBeatSyncedEntry = null!; | ||||
|             SyncedEntry<float> fadeOutDurationSyncedEntry = null!; | ||||
|  | @ -1945,12 +2070,12 @@ namespace MuzikaGromche | |||
|             registerStruct(colorTransitionOutSyncedEntry, t => t._ColorTransitionOut, x => ColorTransitionOutOverride = x); | ||||
|             registerClass(colorTransitionEasingSyncedEntry, t => t._ColorTransitionEasing.Name, x => ColorTransitionEasingOverride = x); | ||||
| 
 | ||||
|             void register<T>(SyncedEntry<T> syncedEntry, Func<Track, T> getter, Action applier) | ||||
|             void register<T>(SyncedEntry<T> syncedEntry, Func<CoreAudioTrack, T> getter, Action applier) | ||||
|             { | ||||
|                 CSyncHackAddSyncedEntry(syncedEntry); | ||||
|                 syncedEntry.SyncHostToLocal(); | ||||
|                 syncedEntry.Changed += (sender, args) => applier(); | ||||
|                 void loader(Track? track) | ||||
|                 void loader(CoreAudioTrack? track) | ||||
|                 { | ||||
|                     // if track is null, set everything to defaults | ||||
|                     syncedEntry.LocalValue = track == null ? (T)syncedEntry.Entry.DefaultValue : getter(track); | ||||
|  | @ -1958,11 +2083,11 @@ namespace MuzikaGromche | |||
|                 entries.Add((loader, applier)); | ||||
|             } | ||||
| 
 | ||||
|             void registerStruct<T>(SyncedEntry<T> syncedEntry, Func<Track, T> getter, Action<T?> setter) where T : struct => | ||||
|             void registerStruct<T>(SyncedEntry<T> syncedEntry, Func<CoreAudioTrack, T> getter, Action<T?> setter) where T : struct => | ||||
|                 register(syncedEntry, getter, () => setter.Invoke(overrideTimingsSyncedEntry.Value ? syncedEntry.Value : null)); | ||||
|             void registerClass<T>(SyncedEntry<T> syncedEntry, Func<Track, T> getter, Action<T?> setter) where T : class => | ||||
|             void registerClass<T>(SyncedEntry<T> syncedEntry, Func<CoreAudioTrack, T> getter, Action<T?> setter) where T : class => | ||||
|                 register(syncedEntry, getter, () => setter.Invoke(overrideTimingsSyncedEntry.Value ? syncedEntry.Value : null)); | ||||
|             void registerArray<T>(SyncedEntry<string> syncedEntry, Func<Track, T[]> getter, Action<T[]?> setter, Func<string, T> parser, bool sort = false) where T : struct => | ||||
|             void registerArray<T>(SyncedEntry<string> syncedEntry, Func<CoreAudioTrack, T[]> getter, Action<T[]?> setter, Func<string, T> parser, bool sort = false) where T : struct => | ||||
|                 register(syncedEntry, | ||||
|                     (track) => string.Join(", ", getter(track)), | ||||
|                     () => | ||||
|  | @ -1996,7 +2121,7 @@ namespace MuzikaGromche | |||
|                 var track = Plugin.CurrentTrack; | ||||
|                 foreach (var entry in entries) | ||||
|                 { | ||||
|                     entry.Load(track); | ||||
|                     entry.Load(track as CoreAudioTrack); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|  | @ -2043,6 +2168,11 @@ namespace MuzikaGromche | |||
| 
 | ||||
|     class MuzikaGromcheJesterNetworkBehaviour : NetworkBehaviour | ||||
|     { | ||||
|         // Number of times a selected track has been played. | ||||
|         // Increases by 1 with each ChooseTrackServerRpc call. | ||||
|         // Resets on SettingChanged. | ||||
|         private int SelectedTrackIndex = 0; | ||||
| 
 | ||||
|         public override void OnNetworkSpawn() | ||||
|         { | ||||
|             ChooseTrackDeferred(); | ||||
|  | @ -2069,6 +2199,7 @@ namespace MuzikaGromche | |||
| 
 | ||||
|         private void ChooseTrackDeferredDelegate(object sender, EventArgs e) | ||||
|         { | ||||
|             SelectedTrackIndex = 0; | ||||
|             ChooseTrackDeferred(); | ||||
|         } | ||||
| 
 | ||||
|  | @ -2099,9 +2230,11 @@ namespace MuzikaGromche | |||
|         [ServerRpc] | ||||
|         public void ChooseTrackServerRpc() | ||||
|         { | ||||
|             var track = Plugin.ChooseTrack(); | ||||
|             Debug.Log($"{nameof(MuzikaGromche)} ChooseTrackServerRpc {track.Name}"); | ||||
|             SetTrackClientRpc(track.Name); | ||||
|             var selectableTrack = Plugin.ChooseTrack(); | ||||
|             var audioTrack = selectableTrack.SelectTrack(SelectedTrackIndex); | ||||
|             Debug.Log($"{nameof(MuzikaGromche)} ChooseTrackServerRpc {selectableTrack.Name} #{SelectedTrackIndex} {audioTrack.Name}"); | ||||
|             SetTrackClientRpc(audioTrack.Name); | ||||
|             SelectedTrackIndex += 1; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -2195,6 +2328,8 @@ namespace MuzikaGromche | |||
|             { | ||||
|                 Plugin.ResetLightColor(); | ||||
|                 DiscoBallManager.Disable(); | ||||
|                 // Rotate track groups | ||||
|                 __instance.GetComponent<MuzikaGromcheJesterNetworkBehaviour>()?.ChooseTrackServerRpc(); | ||||
|             } | ||||
| 
 | ||||
|             if (__instance.previousState == 2 && __state.previousState != 2) | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue