forked from nikita/muzika-gromche
				
			Compare commits
	
		
			No commits in common. "ceaac4e01b870f2897c6abd9e165cc36c6135994" and "585ef604ffcdbcb23a39db6f57e3ca5dfb34bc87" have entirely different histories.
		
	
	
		
			ceaac4e01b
			...
			585ef604ff
		
	
		
							
								
								
									
										
											BIN
										
									
								
								Assets/AttentionPls1Intro.ogg (Stored with Git LFS)
								
								
								
								
							
							
						
						
									
										
											BIN
										
									
								
								Assets/AttentionPls1Intro.ogg (Stored with Git LFS)
								
								
								
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Assets/AttentionPls2Intro.ogg (Stored with Git LFS)
								
								
								
								
							
							
						
						
									
										
											BIN
										
									
								
								Assets/AttentionPls2Intro.ogg (Stored with Git LFS)
								
								
								
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Assets/AttentionPlsLoop.ogg (Stored with Git LFS)
								
								
								
								
							
							
						
						
									
										
											BIN
										
									
								
								Assets/AttentionPlsLoop.ogg (Stored with Git LFS)
								
								
								
								
							
										
											Binary file not shown.
										
									
								
							|  | @ -1,10 +1,5 @@ | ||||||
| # Changelog | # Changelog | ||||||
| 
 | 
 | ||||||
| ## MuzikaGromche 1337.420.9004 - Life Support Edition |  | ||||||
| 
 |  | ||||||
| - Override Death Screen / Game Over text in certain cases. |  | ||||||
| - Added a new track AttentionPls featuring multiple intro variants and new visual effects. |  | ||||||
| 
 |  | ||||||
| ## MuzikaGromche 1337.420.9003 - Lights Out Edition | ## MuzikaGromche 1337.420.9003 - Lights Out Edition | ||||||
| 
 | 
 | ||||||
| - Fixed wrong colors during fade out transition, e.g. in Mineshaft tunnel tiles. | - Fixed wrong colors during fade out transition, e.g. in Mineshaft tunnel tiles. | ||||||
|  |  | ||||||
|  | @ -1,72 +0,0 @@ | ||||||
| using System.Collections; |  | ||||||
| using HarmonyLib; |  | ||||||
| using TMPro; |  | ||||||
| using UnityEngine; |  | ||||||
| 
 |  | ||||||
| namespace MuzikaGromche |  | ||||||
| { |  | ||||||
|     static class DeathScreenGameOverTextManager |  | ||||||
|     { |  | ||||||
|         private const string GameOverTextVanilla = "[LIFE SUPPORT: OFFLINE]"; |  | ||||||
| 
 |  | ||||||
|         public const string GameOverTextModdedDefault = "[ MUZIKA: GROMCHE ]"; |  | ||||||
| 
 |  | ||||||
|         public static void Clear() |  | ||||||
|         { |  | ||||||
|             SetTextImpl(GameOverTextVanilla); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         public static void SetText(string? text) |  | ||||||
|         { |  | ||||||
|             SetTextImpl(text ?? GameOverTextModdedDefault); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         public static IEnumerator SetTextAndClear(string? text) |  | ||||||
|         { |  | ||||||
|             SetText(text); |  | ||||||
|             // Game Over animation duration is about 4.25 seconds |  | ||||||
|             yield return new WaitForSeconds(5f); |  | ||||||
|             Clear(); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         private static void SetTextImpl(string text) |  | ||||||
|         { |  | ||||||
|             GameObject textGameObject = GameObject.Find("Systems/UI/Canvas/DeathScreen/GameOverText"); |  | ||||||
|             if (textGameObject == null) |  | ||||||
|             { |  | ||||||
|                 return; |  | ||||||
|             } |  | ||||||
|             TextMeshProUGUI tmp = textGameObject.GetComponent<TextMeshProUGUI>(); |  | ||||||
|             if (tmp == null) |  | ||||||
|             { |  | ||||||
|                 return; |  | ||||||
|             } |  | ||||||
|             var transform = textGameObject.GetComponent<RectTransform>(); |  | ||||||
|             if (transform == null) |  | ||||||
|             { |  | ||||||
|                 return; |  | ||||||
|             } |  | ||||||
|             // Default transform width is 645.8 which only fit the default message and no extra characters |  | ||||||
|             Vector2 size = transform.sizeDelta; |  | ||||||
|             if (Mathf.Approximately(size.x, 645.8f)) |  | ||||||
|             { |  | ||||||
|                 size.x += 100f; |  | ||||||
|                 transform.sizeDelta = size; |  | ||||||
|             } |  | ||||||
|             tmp.text = text; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     [HarmonyPatch(typeof(RoundManager))] |  | ||||||
|     static class DeathScreenGameOverTextResetPatch |  | ||||||
|     { |  | ||||||
|         [HarmonyPatch(nameof(RoundManager.DespawnPropsAtEndOfRound))] |  | ||||||
|         [HarmonyPatch(nameof(RoundManager.OnDestroy))] |  | ||||||
|         [HarmonyPrefix] |  | ||||||
|         static void OnDestroy(RoundManager __instance) |  | ||||||
|         { |  | ||||||
|             var _ = __instance; |  | ||||||
|             DeathScreenGameOverTextManager.Clear(); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -8,7 +8,7 @@ | ||||||
|         <AssemblyName>Ratijas.MuzikaGromche</AssemblyName> |         <AssemblyName>Ratijas.MuzikaGromche</AssemblyName> | ||||||
|         <Product>Muzika Gromche</Product> |         <Product>Muzika Gromche</Product> | ||||||
|         <Description>Add some content to your inverse teleporter experience on Titan!</Description> |         <Description>Add some content to your inverse teleporter experience on Titan!</Description> | ||||||
|         <Version>1337.420.9004</Version> |         <Version>1337.420.9003</Version> | ||||||
|         <AllowUnsafeBlocks>true</AllowUnsafeBlocks> |         <AllowUnsafeBlocks>true</AllowUnsafeBlocks> | ||||||
|         <LangVersion>latest</LangVersion> |         <LangVersion>latest</LangVersion> | ||||||
|         <Nullable>enable</Nullable> |         <Nullable>enable</Nullable> | ||||||
|  | @ -52,15 +52,6 @@ | ||||||
|         <Reference Include="Unity.Netcode.Runtime" Publicize="true" Private="false"> |         <Reference Include="Unity.Netcode.Runtime" Publicize="true" Private="false"> | ||||||
|             <HintPath>$(LethalCompanyDir)Lethal Company_Data\Managed\Unity.Netcode.Runtime.dll</HintPath> |             <HintPath>$(LethalCompanyDir)Lethal Company_Data\Managed\Unity.Netcode.Runtime.dll</HintPath> | ||||||
|         </Reference> |         </Reference> | ||||||
|         <Reference Include="Unity.TextMeshPro" Publicize="true" Private="False"> |  | ||||||
|             <HintPath>$(LethalCompanyDir)Lethal Company_Data\Managed\Unity.TextMeshPro.dll</HintPath>  |  | ||||||
|         </Reference> |  | ||||||
|         <Reference Include="UnityEngine.UI" Publicize="true" Private="False"> |  | ||||||
|             <HintPath>$(LethalCompanyDir)Lethal Company_Data\Managed\UnityEngine.UI.dll</HintPath>  |  | ||||||
|         </Reference> |  | ||||||
|         <Reference Include="Unity.RenderPipelines.Core.Runtime" Publicize="true" Private="False"> |  | ||||||
|             <HintPath>$(LethalCompanyDir)Lethal Company_Data\Managed\Unity.RenderPipelines.Core.Runtime.dll</HintPath>  |  | ||||||
|         </Reference> |  | ||||||
|     </ItemGroup> |     </ItemGroup> | ||||||
| 
 | 
 | ||||||
|     <ItemGroup Condition="'$(TargetFramework.TrimEnd(`0123456789`))' == 'net'"> |     <ItemGroup Condition="'$(TargetFramework.TrimEnd(`0123456789`))' == 'net'"> | ||||||
|  |  | ||||||
|  | @ -13,7 +13,6 @@ using System.Net.NetworkInformation; | ||||||
| using System.Net.Sockets; | using System.Net.Sockets; | ||||||
| using System.Reflection; | using System.Reflection; | ||||||
| using System.Security.Cryptography; | using System.Security.Cryptography; | ||||||
| using System.Text; |  | ||||||
| using Unity.Netcode; | using Unity.Netcode; | ||||||
| using UnityEngine; | using UnityEngine; | ||||||
| using UnityEngine.Networking; | using UnityEngine.Networking; | ||||||
|  | @ -57,9 +56,6 @@ namespace MuzikaGromche | ||||||
|                 ColorTransitionOut = 0.25f, |                 ColorTransitionOut = 0.25f, | ||||||
|                 ColorTransitionEasing = Easing.OutExpo, |                 ColorTransitionEasing = Easing.OutExpo, | ||||||
|                 FlickerLightsTimeSeries = [-5, 29, 61], |                 FlickerLightsTimeSeries = [-5, 29, 61], | ||||||
|                 DrunknessLoopOffsetTimeSeries = new( |  | ||||||
|                     [-2f, 0.0f, 1.0f, 03f, 30f,  32f,  33f, 35f, 62f], |  | ||||||
|                     [ 0f, 0.4f, 0.6f, 0f,   0f, 0.5f, 0.7f, 0f,   0f]), |  | ||||||
|                 Palette = Palette.Parse(["#B300FF", "#FFF100", "#00FF51", "#474747", "#FF00B3", "#0070FF"]), |                 Palette = Palette.Parse(["#B300FF", "#FFF100", "#00FF51", "#474747", "#FF00B3", "#0070FF"]), | ||||||
|                 Lyrics = [ |                 Lyrics = [ | ||||||
|                     (-68, "Devchata pljashut pod spidami"), |                     (-68, "Devchata pljashut pod spidami"), | ||||||
|  | @ -136,9 +132,6 @@ namespace MuzikaGromche | ||||||
|                 ColorTransitionOut = 0.25f, |                 ColorTransitionOut = 0.25f, | ||||||
|                 ColorTransitionEasing = Easing.OutExpo, |                 ColorTransitionEasing = Easing.OutExpo, | ||||||
|                 FlickerLightsTimeSeries = [-101, -93, -77, -61, -37, -5, 27], |                 FlickerLightsTimeSeries = [-101, -93, -77, -61, -37, -5, 27], | ||||||
|                 DrunknessLoopOffsetTimeSeries = new( |  | ||||||
|                     [-48f, -46f, -42f, 16f, 19f, 23f], |  | ||||||
|                     [  0f, 0.7f,   0f,  0f, 0.3f, 0f]), |  | ||||||
|                 Palette = Palette.Parse(["#217F87", "#BAFF00", "#73BE25", "#78AB4E", "#FFFF00"]), |                 Palette = Palette.Parse(["#217F87", "#BAFF00", "#73BE25", "#78AB4E", "#FFFF00"]), | ||||||
|                 Lyrics = [ |                 Lyrics = [ | ||||||
|                     (-111, "Deploy Destroy, porjadok eto otstoj"), |                     (-111, "Deploy Destroy, porjadok eto otstoj"), | ||||||
|  | @ -267,7 +260,6 @@ namespace MuzikaGromche | ||||||
|                 FadeOutDuration = 4, |                 FadeOutDuration = 4, | ||||||
|                 FlickerLightsTimeSeries = [-5, 31], |                 FlickerLightsTimeSeries = [-5, 31], | ||||||
|                 Lyrics = [], |                 Lyrics = [], | ||||||
|                 GameOverText = "[MUZIKA GROMCHE: K-POP]", |  | ||||||
|             }, |             }, | ||||||
|             new SelectableAudioTrack |             new SelectableAudioTrack | ||||||
|             { |             { | ||||||
|  | @ -286,7 +278,6 @@ namespace MuzikaGromche | ||||||
|                 FadeOutDuration = 4, |                 FadeOutDuration = 4, | ||||||
|                 FlickerLightsTimeSeries = [-5], |                 FlickerLightsTimeSeries = [-5], | ||||||
|                 Lyrics = [], |                 Lyrics = [], | ||||||
|                 GameOverText = "[COULD'VE BEEN: IMMORTAL]", |  | ||||||
|             }, |             }, | ||||||
|             new SelectableAudioTrack |             new SelectableAudioTrack | ||||||
|             { |             { | ||||||
|  | @ -305,7 +296,6 @@ namespace MuzikaGromche | ||||||
|                 FadeOutDuration = 4, |                 FadeOutDuration = 4, | ||||||
|                 FlickerLightsTimeSeries = [-5.5f, 31, 63.9f], |                 FlickerLightsTimeSeries = [-5.5f, 31, 63.9f], | ||||||
|                 Lyrics = [], |                 Lyrics = [], | ||||||
|                 GameOverText = "[ HEY, YOUNG BLOOD ]", |  | ||||||
|             }, |             }, | ||||||
|             new SelectableAudioTrack |             new SelectableAudioTrack | ||||||
|             { |             { | ||||||
|  | @ -453,7 +443,6 @@ namespace MuzikaGromche | ||||||
|                     (96, $"\t\t\tresolving ur private IP\n/{PwnLyricsVariants[^1]}"), |                     (96, $"\t\t\tresolving ur private IP\n/{PwnLyricsVariants[^1]}"), | ||||||
|                     (98, $"\t\t\tresolving ur private IP\nP_WNED"), |                     (98, $"\t\t\tresolving ur private IP\nP_WNED"), | ||||||
|                 ], |                 ], | ||||||
|                 GameOverText = "[HACK3D BY: RUSSI4NS]", |  | ||||||
|             }, |             }, | ||||||
|             new SelectableAudioTrack |             new SelectableAudioTrack | ||||||
|             { |             { | ||||||
|  | @ -478,7 +467,6 @@ namespace MuzikaGromche | ||||||
|                 FadeOutDuration = 6, |                 FadeOutDuration = 6, | ||||||
|                 FlickerLightsTimeSeries = [-120.5f, -105, -89, -8, 44, 45], |                 FlickerLightsTimeSeries = [-120.5f, -105, -89, -8, 44, 45], | ||||||
|                 Lyrics = [], |                 Lyrics = [], | ||||||
|                 GameOverText = "[DIDN'T PUMP IT: LOUDER]", |  | ||||||
|             }, |             }, | ||||||
|             new SelectableAudioTrack |             new SelectableAudioTrack | ||||||
|             { |             { | ||||||
|  | @ -590,57 +578,6 @@ namespace MuzikaGromche | ||||||
|                 FlickerLightsTimeSeries = [-68.5f, -16.5f, 30.5f], |                 FlickerLightsTimeSeries = [-68.5f, -16.5f, 30.5f], | ||||||
|                 Lyrics = [], |                 Lyrics = [], | ||||||
|             }, |             }, | ||||||
|             new SelectableTracksGroup |  | ||||||
|             { |  | ||||||
|                 Name = "AttentionPls", |  | ||||||
|                 Language = Language.RUSSIAN, |  | ||||||
|                 IsExplicit = true, |  | ||||||
|                 Tracks = |  | ||||||
|                 [ |  | ||||||
|                     new SelectableAudioTrack |  | ||||||
|                     { |  | ||||||
|                         Name = "AttentionPls1", |  | ||||||
|                         FileNameLoop = "AttentionPlsLoop.ogg", |  | ||||||
|                         AudioType = AudioType.OGGVORBIS, |  | ||||||
|                         Language = Language.RUSSIAN, |  | ||||||
|                         WindUpTimer = 39.19f, |  | ||||||
|                         Bars = 8, |  | ||||||
|                         BeatsOffset = 0.3f, |  | ||||||
|                         ColorTransitionIn = 0.4f, |  | ||||||
|                         ColorTransitionOut = 0.4f, |  | ||||||
|                         ColorTransitionEasing = Easing.OutExpo, |  | ||||||
|                         Palette = Palette.Parse(["#FCEB3C", "#FC3C9D", "#65C7FA", "#89FC8F", "#FEE9E9", "#FCEB3C", "#89FC8F", "#FC3C9D"]), |  | ||||||
|                         LoopOffset = 0, |  | ||||||
|                         FadeOutBeat = -6, |  | ||||||
|                         FadeOutDuration = 5, |  | ||||||
|                         FlickerLightsTimeSeries = [-8, 31], |  | ||||||
|                         Lyrics = [], |  | ||||||
|                         DrunknessLoopOffsetTimeSeries = new([7f, 12f, 15f], [0f, 0.90f, 0f]), |  | ||||||
|                         CondensationLoopOffsetTimeSeries = new([23f, 28f, 31f], [0f, 0.4f, 0f]), |  | ||||||
|                     }, |  | ||||||
|                     new SelectableAudioTrack |  | ||||||
|                     { |  | ||||||
|                         Name = "AttentionPls2", |  | ||||||
|                         FileNameLoop = "AttentionPlsLoop.ogg", |  | ||||||
|                         AudioType = AudioType.OGGVORBIS, |  | ||||||
|                         Language = Language.RUSSIAN, |  | ||||||
|                         WindUpTimer = 39.19f, |  | ||||||
|                         Bars = 8, |  | ||||||
|                         BeatsOffset = 0.3f, |  | ||||||
|                         ColorTransitionIn = 0.4f, |  | ||||||
|                         ColorTransitionOut = 0.4f, |  | ||||||
|                         ColorTransitionEasing = Easing.OutExpo, |  | ||||||
|                         Palette = Palette.Parse(["#FCEB3C", "#FC3C9D", "#65C7FA", "#89FC8F", "#FEE9E9", "#FCEB3C", "#89FC8F", "#FC3C9D"]), |  | ||||||
|                         LoopOffset = 0, |  | ||||||
|                         FadeOutBeat = -6, |  | ||||||
|                         FadeOutDuration = 5, |  | ||||||
|                         FlickerLightsTimeSeries = [-8, 31], |  | ||||||
|                         Lyrics = [], |  | ||||||
|                         DrunknessLoopOffsetTimeSeries = new([7f, 12f, 15f], [0f, 0.90f, 0f]), |  | ||||||
|                         CondensationLoopOffsetTimeSeries = new([23f, 28f, 31f], [0f, 0.4f, 0f]), |  | ||||||
|                     }, |  | ||||||
|                 ], |  | ||||||
|             }, |  | ||||||
|         ]; |         ]; | ||||||
| 
 | 
 | ||||||
|         public static ISelectableTrack ChooseTrack() |         public static ISelectableTrack ChooseTrack() | ||||||
|  | @ -743,8 +680,6 @@ namespace MuzikaGromche | ||||||
|                 harmony.PatchAll(typeof(DiscoBallTilePatch)); |                 harmony.PatchAll(typeof(DiscoBallTilePatch)); | ||||||
|                 harmony.PatchAll(typeof(DiscoBallDespawnPatch)); |                 harmony.PatchAll(typeof(DiscoBallDespawnPatch)); | ||||||
|                 harmony.PatchAll(typeof(SpawnRatePatch)); |                 harmony.PatchAll(typeof(SpawnRatePatch)); | ||||||
|                 harmony.PatchAll(typeof(DeathScreenGameOverTextResetPatch)); |  | ||||||
|                 harmony.PatchAll(typeof(ScreenFiltersManager.HUDManagerScreenFiltersPatch)); |  | ||||||
|                 NetcodePatcher(); |                 NetcodePatcher(); | ||||||
|                 Compatibility.Register(this); |                 Compatibility.Register(this); | ||||||
|             } |             } | ||||||
|  | @ -854,35 +789,6 @@ namespace MuzikaGromche | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public readonly struct TimeSeries<T> |  | ||||||
|     { |  | ||||||
|         public TimeSeries() : this([], []) { } |  | ||||||
| 
 |  | ||||||
|         public TimeSeries(float[] beats, T[] values) |  | ||||||
|         { |  | ||||||
|             if (beats.Length != values.Length) |  | ||||||
|             { |  | ||||||
|                 throw new ArgumentOutOfRangeException($"Time series length mismatch: {beats.Length} != {values.Length}"); |  | ||||||
|             } |  | ||||||
|             var dict = new SortedDictionary<float, T>(); |  | ||||||
|             for (int i = 0; i < values.Length; i++) |  | ||||||
|             { |  | ||||||
|                 dict.Add(beats[i], values[i]); |  | ||||||
|             } |  | ||||||
|             Beats = [.. dict.Keys]; |  | ||||||
|             Values = [.. dict.Values]; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         public readonly int Length => Beats.Length; |  | ||||||
|         public readonly float[] Beats { get; } = []; |  | ||||||
|         public readonly T[] Values { get; } = []; |  | ||||||
| 
 |  | ||||||
|         public override string ToString() |  | ||||||
|         { |  | ||||||
|             return $"{nameof(TimeSeries<T>)}([{string.Join(", ", Beats)}], [{string.Join(", ", Values)}])"; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // An instance of a track which appears as a configuration entry and |     // An instance of a track which appears as a configuration entry and | ||||||
|     // can be selected using weighted random from a list of selectable tracks. |     // can be selected using weighted random from a list of selectable tracks. | ||||||
|     public interface ISelectableTrack |     public interface ISelectableTrack | ||||||
|  | @ -975,12 +881,7 @@ namespace MuzikaGromche | ||||||
|         // If the chosen alternative is an empty string, lyrics event shall be skipped. |         // If the chosen alternative is an empty string, lyrics event shall be skipped. | ||||||
|         public string[] LyricsLines { get; } |         public string[] LyricsLines { get; } | ||||||
| 
 | 
 | ||||||
|         public TimeSeries<float> DrunknessLoopOffsetTimeSeries { get; } |  | ||||||
|         public TimeSeries<float> CondensationLoopOffsetTimeSeries { get; } |  | ||||||
| 
 |  | ||||||
|         public Palette Palette { get; } |         public Palette Palette { get; } | ||||||
| 
 |  | ||||||
|         public string? GameOverText { get => null; } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // A proxy audio track with default implementation for every IAudioTrack method that simply forwards requests to the inner IAudioTrack. |     // A proxy audio track with default implementation for every IAudioTrack method that simply forwards requests to the inner IAudioTrack. | ||||||
|  | @ -1005,10 +906,7 @@ namespace MuzikaGromche | ||||||
|         float[] IAudioTrack.FlickerLightsTimeSeries => Track.FlickerLightsTimeSeries; |         float[] IAudioTrack.FlickerLightsTimeSeries => Track.FlickerLightsTimeSeries; | ||||||
|         float[] IAudioTrack.LyricsTimeSeries => Track.LyricsTimeSeries; |         float[] IAudioTrack.LyricsTimeSeries => Track.LyricsTimeSeries; | ||||||
|         string[] IAudioTrack.LyricsLines => Track.LyricsLines; |         string[] IAudioTrack.LyricsLines => Track.LyricsLines; | ||||||
|         TimeSeries<float> IAudioTrack.DrunknessLoopOffsetTimeSeries => Track.DrunknessLoopOffsetTimeSeries; |  | ||||||
|         TimeSeries<float> IAudioTrack.CondensationLoopOffsetTimeSeries => Track.CondensationLoopOffsetTimeSeries; |  | ||||||
|         Palette IAudioTrack.Palette => Track.Palette; |         Palette IAudioTrack.Palette => Track.Palette; | ||||||
|         string? IAudioTrack.GameOverText => Track.GameOverText; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Core audio track implementation with some defaults and config overrides. |     // Core audio track implementation with some defaults and config overrides. | ||||||
|  | @ -1083,12 +981,7 @@ namespace MuzikaGromche | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         public TimeSeries<float> DrunknessLoopOffsetTimeSeries { get; init; } = new(); |  | ||||||
|         public TimeSeries<float> CondensationLoopOffsetTimeSeries { get; init; } = new(); |  | ||||||
| 
 |  | ||||||
|         public Palette Palette { get; set; } = Palette.DEFAULT; |         public Palette Palette { get; set; } = Palette.DEFAULT; | ||||||
| 
 |  | ||||||
|         public string? GameOverText { get; init; } = null; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Standalone, top-level, selectable audio track |     // Standalone, top-level, selectable audio track | ||||||
|  | @ -1294,19 +1187,9 @@ namespace MuzikaGromche | ||||||
|             return null; |             return null; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         public readonly float Duration(bool longest = false) |         public readonly float Duration() | ||||||
|         { |         { | ||||||
|             if (longest) |             if (IsEmpty()) | ||||||
|             { |  | ||||||
|                 var to = BeatToInclusive; |  | ||||||
|                 if (BeatFromExclusive >= 0f && BeatToInclusive >= 0f && to < BeatFromExclusive) |  | ||||||
|                 { |  | ||||||
|                     // wrapped |  | ||||||
|                     to += LoopBeats; |  | ||||||
|                 } |  | ||||||
|                 return Mathf.Max(0f, to - BeatFromExclusive); |  | ||||||
|             } |  | ||||||
|             else if (IsEmpty()) |  | ||||||
|             { |             { | ||||||
|                 return 0f; |                 return 0f; | ||||||
|             } |             } | ||||||
|  | @ -1561,8 +1444,8 @@ namespace MuzikaGromche | ||||||
| 
 | 
 | ||||||
|             if (AudioState.HasStarted) |             if (AudioState.HasStarted) | ||||||
|             { |             { | ||||||
|                 var loopOffsetTimestamp = Update(LoopLoopingState); |                 var loopTimestamp = Update(LoopLoopingState); | ||||||
|                 var loopOffsetSpan = BeatTimeSpan.Between(LastKnownLoopOffsetBeat, loopOffsetTimestamp); |                 var loopOffsetSpan = BeatTimeSpan.Between(LastKnownLoopOffsetBeat, loopTimestamp); | ||||||
| 
 | 
 | ||||||
|                 // Do not go back in time |                 // Do not go back in time | ||||||
|                 if (!loopOffsetSpan.IsEmpty()) |                 if (!loopOffsetSpan.IsEmpty()) | ||||||
|  | @ -1573,8 +1456,8 @@ namespace MuzikaGromche | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
|                     var windUpOffsetTimestamp = Update(WindUpLoopingState); |                     var windUpOffsetTimestamp = Update(WindUpLoopingState); | ||||||
|                     LastKnownLoopOffsetBeat = loopOffsetTimestamp.Beat; |                     LastKnownLoopOffsetBeat = loopTimestamp.Beat; | ||||||
|                     var events = GetEvents(loopOffsetTimestamp, loopOffsetSpan, windUpOffsetTimestamp); |                     var events = GetEvents(loopOffsetSpan, windUpOffsetTimestamp); | ||||||
| #if DEBUG | #if DEBUG | ||||||
|                     Debug.Log($"{nameof(MuzikaGromche)} looping? {(LoopLoopingState.IsLooping ? 'X' : '_')}{(WindUpLoopingState.IsLooping ? 'X' : '_')} Loop={loopOffsetSpan} WindUp={windUpOffsetTimestamp} Time={Time.realtimeSinceStartup:N4} events={string.Join(",", events)}"); |                     Debug.Log($"{nameof(MuzikaGromche)} looping? {(LoopLoopingState.IsLooping ? 'X' : '_')}{(WindUpLoopingState.IsLooping ? 'X' : '_')} Loop={loopOffsetSpan} WindUp={windUpOffsetTimestamp} Time={Time.realtimeSinceStartup:N4} events={string.Join(",", events)}"); | ||||||
| #endif | #endif | ||||||
|  | @ -1590,13 +1473,13 @@ namespace MuzikaGromche | ||||||
|             return loopingState.Update(AudioState.Time, AudioState.IsExtrapolated, AdditionalOffset()); |             return loopingState.Update(AudioState.Time, AudioState.IsExtrapolated, AdditionalOffset()); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Timings that may be changed through config |         // Timings that may be changes through config | ||||||
|         private float AdditionalOffset() |         private float AdditionalOffset() | ||||||
|         { |         { | ||||||
|             return Config.AudioOffset.Value + track.BeatsOffsetInSeconds; |             return Config.AudioOffset.Value + track.BeatsOffsetInSeconds; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         private List<BaseEvent> GetEvents(BeatTimestamp loopOffsetTimestamp, BeatTimeSpan loopOffsetSpan, BeatTimestamp windUpOffsetTimestamp) |         private List<BaseEvent> GetEvents(BeatTimeSpan loopOffsetSpan, BeatTimestamp windUpOffsetTimestamp) | ||||||
|         { |         { | ||||||
|             List<BaseEvent> events = []; |             List<BaseEvent> events = []; | ||||||
| 
 | 
 | ||||||
|  | @ -1617,6 +1500,7 @@ namespace MuzikaGromche | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             // TODO: quick editor |             // TODO: quick editor | ||||||
|  |             // loopOffsetSpan.GetLastIndex(Config.LyricsTimeSeries) | ||||||
|             if (Config.DisplayLyrics.Value) |             if (Config.DisplayLyrics.Value) | ||||||
|             { |             { | ||||||
|                 var index = loopOffsetSpan.GetLastIndex(track.LyricsTimeSeries); |                 var index = loopOffsetSpan.GetLastIndex(track.LyricsTimeSeries); | ||||||
|  | @ -1633,16 +1517,6 @@ namespace MuzikaGromche | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             if (GetInterpolation(loopOffsetTimestamp, track.DrunknessLoopOffsetTimeSeries, Easing.Linear) is { } drunkness) |  | ||||||
|             { |  | ||||||
|                 events.Add(new DrunkEvent(drunkness)); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             if (GetInterpolation(loopOffsetTimestamp, track.CondensationLoopOffsetTimeSeries, Easing.Linear) is { } condensation) |  | ||||||
|             { |  | ||||||
|                 events.Add(new CondensationEvent(condensation)); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             return events; |             return events; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -1746,84 +1620,6 @@ namespace MuzikaGromche | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|         private float? GetInterpolation(BeatTimestamp timestamp, TimeSeries<float> timeSeries, Easing easing) |  | ||||||
|         { |  | ||||||
|             if (timeSeries.Length == 0) |  | ||||||
|             { |  | ||||||
|                 return null; |  | ||||||
|             } |  | ||||||
|             else if (timeSeries.Length == 1) |  | ||||||
|             { |  | ||||||
|                 return timeSeries.Values[0]; |  | ||||||
|             } |  | ||||||
|             else |  | ||||||
|             { |  | ||||||
|                 int? indexOfPrevious = null; |  | ||||||
|                 // Find index of the previous time. If looped, wrap backwards. In either case it is possibly missing. |  | ||||||
|                 for (int i = timeSeries.Length - 1; i >= 0; i--) |  | ||||||
|                 { |  | ||||||
|                     if (timeSeries.Beats[i] <= timestamp.Beat) |  | ||||||
|                     { |  | ||||||
|                         indexOfPrevious = i; |  | ||||||
|                         break; |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 if (indexOfPrevious == null && timestamp.IsLooping) |  | ||||||
|                 { |  | ||||||
|                     indexOfPrevious = timeSeries.Length - 1; |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 // Find index of the next time. If looped, wrap forward. |  | ||||||
|                 int? indexOfNext = null; |  | ||||||
|                 for (int i = 0; i < timeSeries.Length; i++) |  | ||||||
|                 { |  | ||||||
|                     if (timeSeries.Beats[i] >= timestamp.Beat) |  | ||||||
|                     { |  | ||||||
|                         indexOfNext = i; |  | ||||||
|                         break; |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 if (indexOfNext == null && timestamp.IsLooping) |  | ||||||
|                 { |  | ||||||
|                     for (int i = 0; i < timeSeries.Length; i++) |  | ||||||
|                     { |  | ||||||
|                         if (timeSeries.Beats[i] >= 0f) |  | ||||||
|                         { |  | ||||||
|                             indexOfNext = i; |  | ||||||
|                             break; |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 switch (indexOfPrevious, indexOfNext) |  | ||||||
|                 { |  | ||||||
|                     case (null, null): |  | ||||||
|                         return null; |  | ||||||
| 
 |  | ||||||
|                     case (null, { } index): |  | ||||||
|                         return timeSeries.Values[index]; |  | ||||||
| 
 |  | ||||||
|                     case ({ } index, null): |  | ||||||
|                         return timeSeries.Values[index]; |  | ||||||
| 
 |  | ||||||
|                     case ({ } prev, { } next) when prev == next || timeSeries.Beats[prev] == timeSeries.Beats[next]: |  | ||||||
|                         return timeSeries.Values[prev]; |  | ||||||
| 
 |  | ||||||
|                     case ({ } prev, { } next): |  | ||||||
|                         var prevBeat = timeSeries.Beats[prev]; |  | ||||||
|                         var nextBeat = timeSeries.Beats[next]; |  | ||||||
|                         var prevTimestamp = new BeatTimestamp(timestamp.LoopBeats, isLooping: false, prevBeat, false); |  | ||||||
|                         var nextTimestamp = new BeatTimestamp(timestamp.LoopBeats, isLooping: false, nextBeat, false); |  | ||||||
|                         var t = BeatTimeSpan.Between(prevTimestamp, timestamp).Duration(longest: true) |  | ||||||
|                             / BeatTimeSpan.Between(prevTimestamp, nextTimestamp).Duration(longest: true); |  | ||||||
|                         var prevVal = timeSeries.Values[prev]; |  | ||||||
|                         var nextVal = timeSeries.Values[next]; |  | ||||||
|                         var val = Mathf.Lerp(prevVal, nextVal, easing.Eval(t)); |  | ||||||
|                         return val; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     abstract class BaseEvent; |     abstract class BaseEvent; | ||||||
|  | @ -1899,20 +1695,6 @@ namespace MuzikaGromche | ||||||
|         public override string ToString() => "WindUp"; |         public override string ToString() => "WindUp"; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     abstract class HUDEvent : BaseEvent; |  | ||||||
| 
 |  | ||||||
|     class DrunkEvent(float drunkness) : HUDEvent |  | ||||||
|     { |  | ||||||
|         public readonly float Drunkness = drunkness; |  | ||||||
|         public override string ToString() => $"Drunk({Drunkness:N2})"; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     class CondensationEvent(float condensation) : HUDEvent |  | ||||||
|     { |  | ||||||
|         public readonly float Condensation = condensation; |  | ||||||
|         public override string ToString() => $"Condensation({Condensation:N2})"; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // Default C#/.NET remainder operator % returns negative result for negative input |     // Default C#/.NET remainder operator % returns negative result for negative input | ||||||
|     // which is unsuitable as an index for an array. |     // which is unsuitable as an index for an array. | ||||||
|     static class Mod |     static class Mod | ||||||
|  | @ -2047,8 +1829,6 @@ namespace MuzikaGromche | ||||||
|         private static string? ColorTransitionEasingOverride = null; |         private static string? ColorTransitionEasingOverride = null; | ||||||
|         private static float[]? FlickerLightsTimeSeriesOverride = null; |         private static float[]? FlickerLightsTimeSeriesOverride = null; | ||||||
|         private static float[]? LyricsTimeSeriesOverride = null; |         private static float[]? LyricsTimeSeriesOverride = null; | ||||||
|         private static TimeSeries<float>? DrunknessLoopOffsetTimeSeriesOverride = null; |  | ||||||
|         private static TimeSeries<float>? CondensationLoopOffsetTimeSeriesOverride = null; |  | ||||||
|         private static Palette? PaletteOverride = null; |         private static Palette? PaletteOverride = null; | ||||||
| 
 | 
 | ||||||
|         private class AudioTrackWithConfigOverride(IAudioTrack track) : ProxyAudioTrack(track), IAudioTrack |         private class AudioTrackWithConfigOverride(IAudioTrack track) : ProxyAudioTrack(track), IAudioTrack | ||||||
|  | @ -2073,9 +1853,6 @@ namespace MuzikaGromche | ||||||
| 
 | 
 | ||||||
|             float[] IAudioTrack.LyricsTimeSeries => LyricsTimeSeriesOverride ?? Track.LyricsTimeSeries; |             float[] IAudioTrack.LyricsTimeSeries => LyricsTimeSeriesOverride ?? Track.LyricsTimeSeries; | ||||||
| 
 | 
 | ||||||
|             TimeSeries<float> IAudioTrack.DrunknessLoopOffsetTimeSeries => DrunknessLoopOffsetTimeSeriesOverride ?? Track.DrunknessLoopOffsetTimeSeries; |  | ||||||
|             TimeSeries<float> IAudioTrack.CondensationLoopOffsetTimeSeries => CondensationLoopOffsetTimeSeriesOverride ?? Track.CondensationLoopOffsetTimeSeries; |  | ||||||
| 
 |  | ||||||
|             Palette IAudioTrack.Palette => PaletteOverride ?? Track.Palette; |             Palette IAudioTrack.Palette => PaletteOverride ?? Track.Palette; | ||||||
|         } |         } | ||||||
| #endif | #endif | ||||||
|  | @ -2100,8 +1877,6 @@ namespace MuzikaGromche | ||||||
|             LethalConfigManager.AddConfigItem(new BoolCheckBoxConfigItem(OverrideSpawnRates, Default(new BoolCheckBoxOptions()))); |             LethalConfigManager.AddConfigItem(new BoolCheckBoxConfigItem(OverrideSpawnRates, Default(new BoolCheckBoxOptions()))); | ||||||
| 
 | 
 | ||||||
| #if DEBUG | #if DEBUG | ||||||
|             SetupEntriesForGameOverText(configFile); |  | ||||||
|             SetupEntriesForScreenFilters(configFile); |  | ||||||
|             SetupEntriesForExtrapolation(configFile); |             SetupEntriesForExtrapolation(configFile); | ||||||
|             SetupEntriesToSkipWinding(configFile); |             SetupEntriesToSkipWinding(configFile); | ||||||
|             SetupEntriesForPaletteOverride(configFile); |             SetupEntriesForPaletteOverride(configFile); | ||||||
|  | @ -2296,8 +2071,6 @@ namespace MuzikaGromche | ||||||
|             ConfigEntry<float> fadeOutDurationEntry = null!; |             ConfigEntry<float> fadeOutDurationEntry = null!; | ||||||
|             ConfigEntry<string> flickerLightsTimeSeriesEntry = null!; |             ConfigEntry<string> flickerLightsTimeSeriesEntry = null!; | ||||||
|             ConfigEntry<string> lyricsTimeSeriesEntry = null!; |             ConfigEntry<string> lyricsTimeSeriesEntry = null!; | ||||||
|             ConfigEntry<string> drunknessTimeSeriesEntry = null!; |  | ||||||
|             ConfigEntry<string> condensationTimeSeriesEntry = null!; |  | ||||||
|             ConfigEntry<float> beatsOffsetEntry = null!; |             ConfigEntry<float> beatsOffsetEntry = null!; | ||||||
|             ConfigEntry<float> colorTransitionInEntry = null!; |             ConfigEntry<float> colorTransitionInEntry = null!; | ||||||
|             ConfigEntry<float> colorTransitionOutEntry = null!; |             ConfigEntry<float> colorTransitionOutEntry = null!; | ||||||
|  | @ -2318,13 +2091,9 @@ namespace MuzikaGromche | ||||||
|             fadeOutDurationEntry = configFile.Bind(section, "Fade Out Duration", 0f, |             fadeOutDurationEntry = configFile.Bind(section, "Fade Out Duration", 0f, | ||||||
|                 new ConfigDescription("Duration of fading out", new AcceptableValueRange<float>(0, 10))); |                 new ConfigDescription("Duration of fading out", new AcceptableValueRange<float>(0, 10))); | ||||||
|             flickerLightsTimeSeriesEntry = configFile.Bind(section, "Flicker Lights Time Series", "", |             flickerLightsTimeSeriesEntry = configFile.Bind(section, "Flicker Lights Time Series", "", | ||||||
|                 new ConfigDescription("Time series of loop offset beats when to flicker the lights.")); |                 new ConfigDescription("Time series of beat offsets when to flicker the lights.")); | ||||||
|             lyricsTimeSeriesEntry = configFile.Bind(section, "Lyrics Time Series", "", |             lyricsTimeSeriesEntry = configFile.Bind(section, "Lyrics Time Series", "", | ||||||
|                 new ConfigDescription("Time series of loop offset beats when to show lyrics lines.")); |                 new ConfigDescription("Time series of beat offsets when to show lyrics lines.")); | ||||||
|             drunknessTimeSeriesEntry = configFile.Bind(section, "Drunkness", "", |  | ||||||
|                 new ConfigDescription("Time series of loop offset beats which are keyframes for the drunkness effect. Format: 'time1: value1, time2: value2")); |  | ||||||
|             condensationTimeSeriesEntry = configFile.Bind(section, "Helmet Condensation Drops", "", |  | ||||||
|                 new ConfigDescription("Time series of loop offset beats which are keyframes for the Helmet Condensation Drops effect. Format: 'time1: value1, time2: value2")); |  | ||||||
|             beatsOffsetEntry = configFile.Bind(section, "Beats Offset", 0f, |             beatsOffsetEntry = configFile.Bind(section, "Beats Offset", 0f, | ||||||
|                 new ConfigDescription("How much to offset the whole beat. More is later", new AcceptableValueRange<float>(-0.5f, 0.5f))); |                 new ConfigDescription("How much to offset the whole beat. More is later", new AcceptableValueRange<float>(-0.5f, 0.5f))); | ||||||
|             colorTransitionInEntry = configFile.Bind(section, "Color Transition In", 0.25f, |             colorTransitionInEntry = configFile.Bind(section, "Color Transition In", 0.25f, | ||||||
|  | @ -2339,8 +2108,6 @@ namespace MuzikaGromche | ||||||
|             LethalConfigManager.AddConfigItem(new FloatSliderConfigItem(fadeOutDurationEntry, floatSliderOptions)); |             LethalConfigManager.AddConfigItem(new FloatSliderConfigItem(fadeOutDurationEntry, floatSliderOptions)); | ||||||
|             LethalConfigManager.AddConfigItem(new TextInputFieldConfigItem(flickerLightsTimeSeriesEntry, Default(new TextInputFieldOptions()))); |             LethalConfigManager.AddConfigItem(new TextInputFieldConfigItem(flickerLightsTimeSeriesEntry, Default(new TextInputFieldOptions()))); | ||||||
|             LethalConfigManager.AddConfigItem(new TextInputFieldConfigItem(lyricsTimeSeriesEntry, Default(new TextInputFieldOptions()))); |             LethalConfigManager.AddConfigItem(new TextInputFieldConfigItem(lyricsTimeSeriesEntry, Default(new TextInputFieldOptions()))); | ||||||
|             LethalConfigManager.AddConfigItem(new TextInputFieldConfigItem(drunknessTimeSeriesEntry, Default(new TextInputFieldOptions()))); |  | ||||||
|             LethalConfigManager.AddConfigItem(new TextInputFieldConfigItem(condensationTimeSeriesEntry, Default(new TextInputFieldOptions()))); |  | ||||||
|             LethalConfigManager.AddConfigItem(new FloatSliderConfigItem(beatsOffsetEntry, floatSliderOptions)); |             LethalConfigManager.AddConfigItem(new FloatSliderConfigItem(beatsOffsetEntry, floatSliderOptions)); | ||||||
|             LethalConfigManager.AddConfigItem(new FloatSliderConfigItem(colorTransitionInEntry, floatSliderOptions)); |             LethalConfigManager.AddConfigItem(new FloatSliderConfigItem(colorTransitionInEntry, floatSliderOptions)); | ||||||
|             LethalConfigManager.AddConfigItem(new FloatSliderConfigItem(colorTransitionOutEntry, floatSliderOptions)); |             LethalConfigManager.AddConfigItem(new FloatSliderConfigItem(colorTransitionOutEntry, floatSliderOptions)); | ||||||
|  | @ -2350,8 +2117,6 @@ namespace MuzikaGromche | ||||||
|             registerStruct(fadeOutDurationEntry, t => t.FadeOutDuration, x => FadeOutDurationOverride = x); |             registerStruct(fadeOutDurationEntry, t => t.FadeOutDuration, x => FadeOutDurationOverride = x); | ||||||
|             registerArray(flickerLightsTimeSeriesEntry, t => t.FlickerLightsTimeSeries, xs => FlickerLightsTimeSeriesOverride = xs, float.Parse, sort: true); |             registerArray(flickerLightsTimeSeriesEntry, t => t.FlickerLightsTimeSeries, xs => FlickerLightsTimeSeriesOverride = xs, float.Parse, sort: true); | ||||||
|             registerArray(lyricsTimeSeriesEntry, t => t.LyricsTimeSeries, xs => LyricsTimeSeriesOverride = xs, float.Parse, sort: true); |             registerArray(lyricsTimeSeriesEntry, t => t.LyricsTimeSeries, xs => LyricsTimeSeriesOverride = xs, float.Parse, sort: true); | ||||||
|             registerTimeSeries(drunknessTimeSeriesEntry, t => t.DrunknessLoopOffsetTimeSeries, xs => DrunknessLoopOffsetTimeSeriesOverride = xs, float.Parse, f => f.ToString()); |  | ||||||
|             registerTimeSeries(condensationTimeSeriesEntry, t => t.CondensationLoopOffsetTimeSeries, xs => CondensationLoopOffsetTimeSeriesOverride = xs, float.Parse, f => f.ToString()); |  | ||||||
|             registerStruct(beatsOffsetEntry, t => t.BeatsOffset, x => BeatsOffsetOverride = x); |             registerStruct(beatsOffsetEntry, t => t.BeatsOffset, x => BeatsOffsetOverride = x); | ||||||
|             registerStruct(colorTransitionInEntry, t => t.ColorTransitionIn, x => ColorTransitionInOverride = x); |             registerStruct(colorTransitionInEntry, t => t.ColorTransitionIn, x => ColorTransitionInOverride = x); | ||||||
|             registerStruct(colorTransitionOutEntry, t => t.ColorTransitionOut, x => ColorTransitionOutOverride = x); |             registerStruct(colorTransitionOutEntry, t => t.ColorTransitionOut, x => ColorTransitionOutOverride = x); | ||||||
|  | @ -2385,76 +2150,7 @@ namespace MuzikaGromche | ||||||
|                         } |                         } | ||||||
|                         setter.Invoke(overrideTimingsEntry.Value ? values : null); |                         setter.Invoke(overrideTimingsEntry.Value ? values : null); | ||||||
|                     }); |                     }); | ||||||
|             void registerTimeSeries<T>(ConfigEntry<string> entry, Func<IAudioTrack, TimeSeries<T>> getter, Action<TimeSeries<T>?> setter, Func<string, T> parser, Func<T, string> formatter) => |  | ||||||
|                 register(entry, |  | ||||||
|                     (track) => |  | ||||||
|                     { |  | ||||||
|                         var ts = getter(track); |  | ||||||
|                         return formatTimeSeries(ts, formatter); |  | ||||||
|                     }, |  | ||||||
|                     () => |  | ||||||
|                     { |  | ||||||
|                         var ts = parseTimeSeries(entry.Value, parser); |  | ||||||
|                         if (ts is { } timeSeries) |  | ||||||
|                         { |  | ||||||
|                             entry.Value = formatTimeSeries(timeSeries, formatter); |  | ||||||
|                         } |  | ||||||
|                         setter.Invoke(overrideTimingsEntry.Value ? ts : null); |  | ||||||
|                     }); |  | ||||||
| 
 | 
 | ||||||
|             // current restriction is that formatted value can not contain commas or semicolons. |  | ||||||
|             TimeSeries<T>? parseTimeSeries<T>(string str, Func<string, T> parser) |  | ||||||
|             { |  | ||||||
|                 try |  | ||||||
|                 { |  | ||||||
|                     if (string.IsNullOrWhiteSpace(str)) |  | ||||||
|                     { |  | ||||||
|                         return null; |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     List<float> beats = []; |  | ||||||
|                     List<T> values = []; |  | ||||||
|                     foreach (var pair in str.Split(",")) |  | ||||||
|                     { |  | ||||||
|                         if (string.IsNullOrWhiteSpace(pair)) |  | ||||||
|                         { |  | ||||||
|                             continue; |  | ||||||
|                         } |  | ||||||
|                         var keyvalue = pair.Split(":"); |  | ||||||
|                         if (keyvalue.Length != 2) |  | ||||||
|                         { |  | ||||||
|                             throw new FormatException($"Pair must be separated by exactly one semicolon: '{pair}'"); |  | ||||||
|                         } |  | ||||||
|                         var beat = float.Parse(keyvalue[0].Trim()); |  | ||||||
|                         var val = parser(keyvalue[1].Trim()); |  | ||||||
|                         beats.Add(beat); |  | ||||||
|                         values.Add(val); |  | ||||||
|                     } |  | ||||||
|                     var ts = new TimeSeries<T>(beats.ToArray(), values.ToArray()); |  | ||||||
|                     return ts; |  | ||||||
|                 } |  | ||||||
|                 catch (Exception e) |  | ||||||
|                 { |  | ||||||
|                     Debug.Log($"{nameof(MuzikaGromche)} Unable to parse time series: {e}"); |  | ||||||
|                     return null; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             string formatTimeSeries<T>(TimeSeries<T> ts, Func<T, string> formatter) |  | ||||||
|             { |  | ||||||
|                 StringBuilder strings = new(); |  | ||||||
|                 for (int i = 0; i < ts.Length; i++) |  | ||||||
|                 { |  | ||||||
|                     var beat = ts.Beats[i]; |  | ||||||
|                     var value = formatter(ts.Values[i]); |  | ||||||
|                     strings.Append($"{beat}: {value}"); |  | ||||||
|                     if (i != ts.Length - 1) |  | ||||||
|                     { |  | ||||||
|                         strings.Append(", "); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 Debug.Log($"{nameof(MuzikaGromche)} format time series {ts} {strings}"); |  | ||||||
|                 return strings.ToString(); |  | ||||||
|             } |  | ||||||
|             T[]? parseStringArray<T>(string str, Func<string, T> parser, bool sort = false) where T : struct |             T[]? parseStringArray<T>(string str, Func<string, T> parser, bool sort = false) where T : struct | ||||||
|             { |             { | ||||||
|                 try |                 try | ||||||
|  | @ -2486,59 +2182,6 @@ namespace MuzikaGromche | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|         private void SetupEntriesForGameOverText(ConfigFile configFile) |  | ||||||
|         { |  | ||||||
|             const string section = "Game Over"; |  | ||||||
|             var gameOverTextConfigEntry = configFile.Bind(section, "Game Over Text", DeathScreenGameOverTextManager.GameOverTextModdedDefault, |  | ||||||
|                 new ConfigDescription("Custom Game Over text to show.")); |  | ||||||
|             LethalConfigManager.AddConfigItem(new GenericButtonConfigItem(section, "Game Over Animation", |  | ||||||
|                 "Run Death Screen / Game Over animation 3 times.", "Trigger", () => |  | ||||||
|                 { |  | ||||||
|                     HUDManager.Instance.StartCoroutine(AnimateGameOverText(gameOverTextConfigEntry.Value)); |  | ||||||
|                 })); |  | ||||||
|             LethalConfigManager.AddConfigItem(new TextInputFieldConfigItem(gameOverTextConfigEntry, requiresRestart: false)); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         static IEnumerator AnimateGameOverText(string text) |  | ||||||
|         { |  | ||||||
|             yield return new WaitForSeconds(1f); |  | ||||||
|             for (int i = 0; i < 3; i++) |  | ||||||
|             { |  | ||||||
|                 DeathScreenGameOverTextManager.SetText(text); |  | ||||||
|                 HUDManager.Instance.gameOverAnimator.SetTrigger("gameOver"); |  | ||||||
|                 yield return new WaitForSeconds(5f); |  | ||||||
|                 HUDManager.Instance.gameOverAnimator.SetTrigger("revive"); |  | ||||||
|                 yield return new WaitForSeconds(1f); |  | ||||||
|             } |  | ||||||
|             DeathScreenGameOverTextManager.Clear(); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         private void SetupEntriesForScreenFilters(ConfigFile configFile) |  | ||||||
|         { |  | ||||||
|             const string section = "Screen Filters"; |  | ||||||
| 
 |  | ||||||
|             var drunkConfigEntry = configFile.Bind(section, "Drunkness Level", 0f, |  | ||||||
|                 new ConfigDescription("Override drunkness level in Screen Filters Manager.")); |  | ||||||
|             LethalConfigManager.AddConfigItem(new FloatSliderConfigItem(drunkConfigEntry, requiresRestart: false)); |  | ||||||
|             drunkConfigEntry.SettingChanged += (sender, args) => |  | ||||||
|             { |  | ||||||
|                 ScreenFiltersManager.Drunkness = drunkConfigEntry.Value; |  | ||||||
|             }; |  | ||||||
| 
 |  | ||||||
|             var condensationConfigEntry = configFile.Bind(section, "Condensation Level", 0f, |  | ||||||
|                 new ConfigDescription("Override drunkness level in Screen Filters Manager.")); |  | ||||||
|             LethalConfigManager.AddConfigItem(new FloatSliderConfigItem(condensationConfigEntry, new FloatSliderOptions() |  | ||||||
|             { |  | ||||||
|                 Min = 0f, |  | ||||||
|                 Max = 0.27f, |  | ||||||
|                 RequiresRestart = false, |  | ||||||
|             })); |  | ||||||
|             condensationConfigEntry.SettingChanged += (sender, args) => |  | ||||||
|             { |  | ||||||
|                 ScreenFiltersManager.HelmetCondensationDrops = condensationConfigEntry.Value; |  | ||||||
|             }; |  | ||||||
|         } |  | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
|         private T Default<T>(T options) where T : BaseOptions |         private T Default<T>(T options) where T : BaseOptions | ||||||
|  | @ -2692,16 +2335,6 @@ namespace MuzikaGromche | ||||||
|             LoopAudioSource.PlayScheduled(loopStartDspTime); |             LoopAudioSource.PlayScheduled(loopStartDspTime); | ||||||
|             Debug.Log($"{nameof(MuzikaGromche)} Play Intro: dspTime={AudioSettings.dspTime:N4}, intro.time={IntroAudioSource.time:N4}/{IntroAudioSource.clip.length:N4}, scheduled Loop={loopStartDspTime}"); |             Debug.Log($"{nameof(MuzikaGromche)} Play Intro: dspTime={AudioSettings.dspTime:N4}, intro.time={IntroAudioSource.time:N4}/{IntroAudioSource.clip.length:N4}, scheduled Loop={loopStartDspTime}"); | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|         public void OverrideDeathScreenGameOverText() |  | ||||||
|         { |  | ||||||
|             if (CurrentTrack == null) |  | ||||||
|             { |  | ||||||
|                 // Playing as a client with a host who doesn't have the mod |  | ||||||
|                 return; |  | ||||||
|             } |  | ||||||
|             StartCoroutine(DeathScreenGameOverTextManager.SetTextAndClear(CurrentTrack.GameOverText)); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     [HarmonyPatch(typeof(JesterAI))] |     [HarmonyPatch(typeof(JesterAI))] | ||||||
|  | @ -2816,7 +2449,6 @@ namespace MuzikaGromche | ||||||
|             { |             { | ||||||
|                 PoweredLightsBehaviour.Instance.ResetLightColor(); |                 PoweredLightsBehaviour.Instance.ResetLightColor(); | ||||||
|                 DiscoBallManager.Disable(); |                 DiscoBallManager.Disable(); | ||||||
|                 ScreenFiltersManager.Clear(); |  | ||||||
|                 // Rotate track groups |                 // Rotate track groups | ||||||
|                 behaviour.ChooseTrackServerRpc(); |                 behaviour.ChooseTrackServerRpc(); | ||||||
|                 behaviour.BeatTimeState = null; |                 behaviour.BeatTimeState = null; | ||||||
|  | @ -2826,7 +2458,6 @@ namespace MuzikaGromche | ||||||
|             else if ((__instance.previousState == 1 || __instance.previousState == 2) && behaviour.BeatTimeState is { } beatTimeState) |             else if ((__instance.previousState == 1 || __instance.previousState == 2) && behaviour.BeatTimeState is { } beatTimeState) | ||||||
|             { |             { | ||||||
|                 var events = beatTimeState.Update(introAudioSource, loopAudioSource); |                 var events = beatTimeState.Update(introAudioSource, loopAudioSource); | ||||||
|                 var localPlayerCanHearMusic = Plugin.LocalPlayerCanHearMusic(__instance); |  | ||||||
|                 foreach (var ev in events) |                 foreach (var ev in events) | ||||||
|                 { |                 { | ||||||
|                     switch (ev) |                     switch (ev) | ||||||
|  | @ -2843,33 +2474,16 @@ namespace MuzikaGromche | ||||||
|                             RoundManager.Instance.FlickerLights(true); |                             RoundManager.Instance.FlickerLights(true); | ||||||
|                             break; |                             break; | ||||||
| 
 | 
 | ||||||
|                         case LyricsEvent e when localPlayerCanHearMusic: |                         case LyricsEvent e: | ||||||
|  |                             if (Plugin.LocalPlayerCanHearMusic(__instance)) | ||||||
|  |                             { | ||||||
|                                 Plugin.DisplayLyrics(e.Text); |                                 Plugin.DisplayLyrics(e.Text); | ||||||
|                             break; |                             } | ||||||
| 
 |  | ||||||
|                         case DrunkEvent e when localPlayerCanHearMusic: |  | ||||||
|                             ScreenFiltersManager.Drunkness = e.Drunkness; |  | ||||||
|                             break; |  | ||||||
| 
 |  | ||||||
|                         case CondensationEvent e when localPlayerCanHearMusic: |  | ||||||
|                             ScreenFiltersManager.HelmetCondensationDrops = e.Condensation; |  | ||||||
|                             break; |                             break; | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|         [HarmonyPatch(nameof(JesterAI.killPlayerAnimation))] |  | ||||||
|         [HarmonyPrefix] |  | ||||||
|         static void JesterKillPlayerAnimationPrefix(JesterAI __instance, int playerId) |  | ||||||
|         { |  | ||||||
|             // Note on cast to int: base game already downcasts ulong to int anyway |  | ||||||
|             if (playerId == (int)GameNetworkManager.Instance.localPlayerController.playerClientId) |  | ||||||
|             { |  | ||||||
|                 var behaviour = __instance.GetComponent<MuzikaGromcheJesterNetworkBehaviour>(); |  | ||||||
|                 behaviour.OverrideDeathScreenGameOverText(); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     [HarmonyPatch(typeof(EnemyAI))] |     [HarmonyPatch(typeof(EnemyAI))] | ||||||
|  | @ -2885,10 +2499,8 @@ namespace MuzikaGromche | ||||||
|             { |             { | ||||||
|                 PoweredLightsBehaviour.Instance.ResetLightColor(); |                 PoweredLightsBehaviour.Instance.ResetLightColor(); | ||||||
|                 DiscoBallManager.Disable(); |                 DiscoBallManager.Disable(); | ||||||
|                 DeathScreenGameOverTextManager.Clear(); |  | ||||||
|                 ScreenFiltersManager.Clear(); |  | ||||||
|                 // Just in case if players have spawned multiple Jesters, |                 // Just in case if players have spawned multiple Jesters, | ||||||
|                 // Don't reset Config.CurrentTrack to null, |                 // Don't reset Plugin.CurrentTrack to null, | ||||||
|                 // so that the latest chosen track remains set. |                 // so that the latest chosen track remains set. | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -1,146 +0,0 @@ | ||||||
| using System.Collections; |  | ||||||
| using HarmonyLib; |  | ||||||
| using UnityEngine; |  | ||||||
| 
 |  | ||||||
| namespace MuzikaGromche |  | ||||||
| { |  | ||||||
|     static class ScreenFiltersManager |  | ||||||
|     { |  | ||||||
|         private const float VibilityThreshold = 0.01f; |  | ||||||
| 
 |  | ||||||
|         private static bool drunknessChangedThisFrame = false; |  | ||||||
|         private static float drunkness = 0f; |  | ||||||
|         public static float Drunkness |  | ||||||
|         { |  | ||||||
|             get => drunkness; |  | ||||||
|             set |  | ||||||
|             { |  | ||||||
|                 drunkness = value; |  | ||||||
|                 drunknessChangedThisFrame = true; |  | ||||||
|                 ScheduleUpdate(); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         private static bool helmetCondensationDropsChangedThisFrame = false; |  | ||||||
|         private static float helmetCondensationDrops = 0f; |  | ||||||
|         public static float HelmetCondensationDrops |  | ||||||
|         { |  | ||||||
|             get => helmetCondensationDrops; |  | ||||||
|             set |  | ||||||
|             { |  | ||||||
|                 helmetCondensationDrops = value; |  | ||||||
|                 helmetCondensationDropsChangedThisFrame = true; |  | ||||||
|                 ScheduleUpdate(); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         private static Coroutine? scheduledUpdate = null; |  | ||||||
| 
 |  | ||||||
|         private static void ScheduleUpdate() |  | ||||||
|         { |  | ||||||
|             CancelScheduledUpdate(); |  | ||||||
|             scheduledUpdate = HUDManager.Instance.StartCoroutine(ScheduledUpdate()); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         private static void CancelScheduledUpdate() |  | ||||||
|         { |  | ||||||
|             if (scheduledUpdate != null) |  | ||||||
|             { |  | ||||||
|                 HUDManager.Instance.StopCoroutine(scheduledUpdate); |  | ||||||
|                 scheduledUpdate = null; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         private static IEnumerator ScheduledUpdate() |  | ||||||
|         { |  | ||||||
|             try |  | ||||||
|             { |  | ||||||
|                 yield return new WaitForEndOfFrame(); |  | ||||||
|                 Update(); |  | ||||||
|             } |  | ||||||
|             finally |  | ||||||
|             { |  | ||||||
|                 scheduledUpdate = null; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         private static void Update() |  | ||||||
|         { |  | ||||||
|             CancelScheduledUpdate(); |  | ||||||
|             var hud = HUDManager.Instance; |  | ||||||
| 
 |  | ||||||
|             if (!drunknessChangedThisFrame) |  | ||||||
|             { |  | ||||||
|                 // animated roll-off |  | ||||||
|                 drunkness = Mathf.Clamp(drunkness - Time.deltaTime / 2f, 0f, 1f); |  | ||||||
|             } |  | ||||||
|             drunknessChangedThisFrame = false; |  | ||||||
|             if (drunkness > VibilityThreshold) |  | ||||||
|             { |  | ||||||
|                 var moddedDrunknessFilterWeight = StartOfRound.Instance.drunknessSideEffect.Evaluate(drunkness); |  | ||||||
|                 var moddedGasImageAlphaAlpha = moddedDrunknessFilterWeight * 1.5f; |  | ||||||
|                 // set the final value to the greatest of the two, so that we don't accidentally undo TZP's visual effect. |  | ||||||
|                 hud.drunknessFilter.weight = Mathf.Max(hud.drunknessFilter.weight, moddedDrunknessFilterWeight); |  | ||||||
|                 hud.gasImageAlpha.alpha = Mathf.Max(hud.gasImageAlpha.alpha, moddedGasImageAlphaAlpha); |  | ||||||
|                 // Image alpha only makes sense if the animator is running |  | ||||||
|                 hud.gasHelmetAnimator.SetBool("gasEmitting", value: true); |  | ||||||
|             } |  | ||||||
|             else |  | ||||||
|             { |  | ||||||
|                 ClearDrunkness(); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             if (!helmetCondensationDropsChangedThisFrame) |  | ||||||
|             { |  | ||||||
|                 // animated roll-off |  | ||||||
|                 helmetCondensationDrops = Mathf.Clamp(helmetCondensationDrops - Time.deltaTime / 6f, 0f, 1f); |  | ||||||
|             } |  | ||||||
|             helmetCondensationDropsChangedThisFrame = false; |  | ||||||
|             if (helmetCondensationDrops > VibilityThreshold) |  | ||||||
|             { |  | ||||||
|                 // HelmetCondensationDrops |  | ||||||
|                 Color color = hud.helmetCondensationMaterial.color; |  | ||||||
|                 // set the final value to the greatest of the two, so that we don't accidentally undo steam's visual effect. |  | ||||||
|                 color.a = Mathf.Clamp(Mathf.Max(color.a, helmetCondensationDrops), 0f, 0.27f); |  | ||||||
|                 hud.helmetCondensationMaterial.color = color; |  | ||||||
|             } |  | ||||||
|             else |  | ||||||
|             { |  | ||||||
|                 ClearCondensation(); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         public static void Clear() |  | ||||||
|         { |  | ||||||
|             ClearDrunkness(); |  | ||||||
|             ClearCondensation(); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         private static void ClearDrunkness() |  | ||||||
|         { |  | ||||||
|             drunkness = 0f; |  | ||||||
|             // Only the stop animation if vanilla doesn't animate TZP right now. |  | ||||||
|             if (GameNetworkManager.Instance.localPlayerController.drunkness == 0f) |  | ||||||
|             { |  | ||||||
|                 HUDManager.Instance.gasHelmetAnimator.SetBool("gasEmitting", value: false); |  | ||||||
|             } |  | ||||||
|             // Vanilla will set drunknessFilter.weight and gasImageAlpha.alpha on the next Update anyway. |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         private static void ClearCondensation() |  | ||||||
|         { |  | ||||||
|             helmetCondensationDrops = 0f; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         [HarmonyPatch(typeof(HUDManager))] |  | ||||||
|         internal static class HUDManagerScreenFiltersPatch |  | ||||||
|         { |  | ||||||
|             [HarmonyPatch(nameof(HUDManager.SetScreenFilters))] |  | ||||||
|             [HarmonyPostfix] |  | ||||||
|             static void SetScreenFiltersPostfix(HUDManager __instance) |  | ||||||
|             { |  | ||||||
|                 Update(); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| { | { | ||||||
|     "name": "MuzikaGromche", |     "name": "MuzikaGromche", | ||||||
|     "version_number": "1337.420.9004", |     "version_number": "1337.420.9003", | ||||||
|     "author": "Ratijas", |     "author": "Ratijas", | ||||||
|     "description": "Add some content to your inverse teleporter experience on Titan!", |     "description": "Add some content to your inverse teleporter experience on Titan!", | ||||||
|     "website_url": "https://git.vilunov.me/ratijas/muzika-gromche", |     "website_url": "https://git.vilunov.me/ratijas/muzika-gromche", | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue