forked from nikita/muzika-gromche
Compare commits
24 Commits
8a24448cb6
...
d59c5a20c1
| Author | SHA1 | Date |
|---|---|---|
|
|
d59c5a20c1 | |
|
|
b1d449cf02 | |
|
|
3f06cc9aa6 | |
|
|
a5659fcb09 | |
|
|
6271a377bd | |
|
|
a4cee92d00 | |
|
|
f83f2a72ba | |
|
|
afb3e34e71 | |
|
|
ebd7811b12 | |
|
|
a64d671527 | |
|
|
7eaa5fce75 | |
|
|
da86ca6a2d | |
|
|
c4c1919df6 | |
|
|
869d982b1e | |
|
|
10839ba22c | |
|
|
398de3dc04 | |
|
|
4f432968ef | |
|
|
56cea50a65 | |
|
|
0d416c6f5a | |
|
|
c1d91839e4 | |
|
|
76189c6ad2 | |
|
|
b6f576d50d | |
|
|
a4ca1c86ec | |
|
|
38c9472cb1 |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
18
CHANGELOG.md
18
CHANGELOG.md
|
|
@ -1,7 +1,23 @@
|
|||
# Changelog
|
||||
|
||||
## MuzikaGromche 1337.9001.2
|
||||
## MuzikaGromche 1337.9001.4 - v73 Chinese New Year Edition
|
||||
|
||||
- Remastered recently added track IkWilJe using a higher quality source audio and better fitting visual effects.
|
||||
- Adjusted lyrics for PWNED (can't believe it missed an obvious joke).
|
||||
- Added a new track Paarden.
|
||||
- Added a new track DiscoKapot.
|
||||
- Added an accessibility option to reduce the intensity of overly distracting visual effects.
|
||||
- Seasonal content like New Year's songs (IkWilJe, Paarden, DiscoKapot) will only be available for selection during their respective seasons.
|
||||
- Reduced memory usage by almost 400 MB, thanks to loading audio clips on demand (not preloading all tracks at launch).
|
||||
- Added a new track PickUpSticks.
|
||||
|
||||
## MuzikaGromche 1337.9001.3 - v73 Happy New Year Edition
|
||||
|
||||
- Added a new track IkWilJe.
|
||||
|
||||
## MuzikaGromche 1337.9001.2 - v73 Rushed Edition
|
||||
|
||||
- Added a new track HighLow.
|
||||
|
||||
## MuzikaGromche 1337.9001.1 - v73 Music louder Edition
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,170 @@
|
|||
using HarmonyLib;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace MuzikaGromche;
|
||||
|
||||
internal static class AudioClipsCacheManager
|
||||
{
|
||||
// Cache of file names to loaded AudioClips.
|
||||
// Cache is cleared at the end of each round.
|
||||
static readonly Dictionary<string, AudioClip> Cache = [];
|
||||
|
||||
// In-flight requests
|
||||
static readonly Dictionary<string, (UnityWebRequest Request, List<Action<AudioClip>> Setters)> Requests = [];
|
||||
|
||||
// Not just isDone status, but also whether all requests have been processed.
|
||||
public static bool AllDone => Requests.Count == 0;
|
||||
|
||||
public static void LoadAudioTrack(IAudioTrack track)
|
||||
{
|
||||
GlobalBehaviour.Instance.StartCoroutine(LoadAudioTrackCoroutine(track));
|
||||
}
|
||||
|
||||
static IEnumerator LoadAudioTrackCoroutine(IAudioTrack track)
|
||||
{
|
||||
List<UnityWebRequest> requests = [];
|
||||
requests.Capacity = 2;
|
||||
|
||||
LoadAudioClip(requests, track.AudioType, track.FileNameIntro, clip => track.LoadedIntro = clip);
|
||||
LoadAudioClip(requests, track.AudioType, track.FileNameLoop, clip => track.LoadedLoop = clip);
|
||||
|
||||
yield return new WaitUntil(() => requests.All(request => request.isDone));
|
||||
|
||||
if (requests.All(request => request.result == UnityWebRequest.Result.Success))
|
||||
{
|
||||
foreach (var request in requests)
|
||||
{
|
||||
foreach (var (fileName, (Request, Setters)) in Requests)
|
||||
{
|
||||
if (request == Request)
|
||||
{
|
||||
Plugin.Log.LogDebug($"Audio clip loaded successfully: {fileName}");
|
||||
var clip = DownloadHandlerAudioClip.GetContent(request);
|
||||
Cache[fileName] = clip;
|
||||
foreach (var setter in Setters)
|
||||
{
|
||||
setter(clip);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var failed = Requests.Values.Where(tuple => tuple.Request.result != UnityWebRequest.Result.Success).Select(tuple => tuple.Request.GetUrl());
|
||||
Plugin.Log.LogError("Could not load audio file " + string.Join(", ", failed));
|
||||
}
|
||||
|
||||
// cleanup
|
||||
foreach (var request in requests)
|
||||
{
|
||||
// collect matching keys first to avoid mutating Requests while iterating it
|
||||
var fileNames = Requests
|
||||
.Where(kv => kv.Value.Request == request)
|
||||
.Select(kv => kv.Key)
|
||||
.ToArray();
|
||||
|
||||
foreach (var fileName in fileNames)
|
||||
{
|
||||
if (Requests.TryGetValue(fileName, out var tuple) && tuple.Request != null)
|
||||
{
|
||||
tuple.Request.Dispose();
|
||||
}
|
||||
|
||||
Requests.Remove(fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static readonly string dir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
|
||||
static void LoadAudioClip(List<UnityWebRequest> requests, AudioType audioType, string fileName, Action<AudioClip> setter)
|
||||
{
|
||||
if (Cache.TryGetValue(fileName, out var cachedClip))
|
||||
{
|
||||
Plugin.Log.LogDebug($"Found cached audio clip: {fileName}");
|
||||
setter(cachedClip);
|
||||
}
|
||||
else if (Requests.TryGetValue(fileName, out var tuple))
|
||||
{
|
||||
Plugin.Log.LogDebug($"Found existing in-flight request for audio clip: {fileName}");
|
||||
tuple.Setters.Add(setter);
|
||||
}
|
||||
else
|
||||
{
|
||||
Plugin.Log.LogDebug($"Sending request to load audio clip: {fileName}");
|
||||
var request = UnityWebRequestMultimedia.GetAudioClip($"file://{dir}/{fileName}", audioType);
|
||||
request.SendWebRequest();
|
||||
Requests[fileName] = (request, [setter]);
|
||||
requests.Add(request);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Clear()
|
||||
{
|
||||
// Iterate over LoadedClipsCache keys and values, cross join with Plugin.Tracks list,
|
||||
// find AudioTracks that reference the key (file name), and null their corresponding loaded tracks;
|
||||
// then destroy tracks and clear the cache.
|
||||
|
||||
Plugin.Log.LogDebug($"Clearing {Cache.Count} cached audio clips and {Requests.Count} pending requests");
|
||||
|
||||
if (Cache.Count > 0)
|
||||
{
|
||||
var allTracks = Plugin.Tracks.SelectMany(t => t.GetTracks()).ToArray();
|
||||
|
||||
foreach (var (fileName, clip) in Cache)
|
||||
{
|
||||
foreach (var track in allTracks)
|
||||
{
|
||||
// Null out any references to this clip on matching file names.
|
||||
if (track.FileNameIntro == fileName)
|
||||
{
|
||||
track.LoadedIntro = null;
|
||||
}
|
||||
if (track.FileNameLoop == fileName)
|
||||
{
|
||||
track.LoadedLoop = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (clip != null)
|
||||
{
|
||||
UnityEngine.Object.Destroy(clip);
|
||||
}
|
||||
}
|
||||
|
||||
Cache.Clear();
|
||||
}
|
||||
|
||||
foreach (var (fileName, (Request, Setters)) in Requests)
|
||||
{
|
||||
if (Request != null)
|
||||
{
|
||||
Request.Abort();
|
||||
Request.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
Requests.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(RoundManager))]
|
||||
static class ClearAudioClipCachePatch
|
||||
{
|
||||
[HarmonyPatch(nameof(RoundManager.DespawnPropsAtEndOfRound))]
|
||||
[HarmonyPatch(nameof(RoundManager.OnDestroy))]
|
||||
[HarmonyPrefix]
|
||||
static void OnDestroy(RoundManager __instance)
|
||||
{
|
||||
var _ = __instance;
|
||||
AudioClipsCacheManager.Clear();
|
||||
}
|
||||
}
|
||||
|
|
@ -82,7 +82,7 @@ namespace MuzikaGromche
|
|||
CachedDiscoBalls.Add(discoBall);
|
||||
discoBall.SetActive(false);
|
||||
|
||||
Debug.Log($"{nameof(MuzikaGromche)} {nameof(DiscoBallManager)} Patched tile '{tile.gameObject.name}'");
|
||||
Plugin.Log.LogDebug($"{nameof(DiscoBallManager)} Patched tile '{tile.gameObject.name}'");
|
||||
}
|
||||
|
||||
static IEnumerable<Animator> FindDiscoBallAnimators(GameObject discoBall)
|
||||
|
|
@ -109,7 +109,7 @@ namespace MuzikaGromche
|
|||
|
||||
public static void Toggle(bool on)
|
||||
{
|
||||
Debug.Log($"{nameof(MuzikaGromche)} {nameof(DiscoBallManager)} Toggle {(on ? "ON" : "OFF")} {CachedDiscoBallAnimators.Count} animators");
|
||||
Plugin.Log.LogDebug($"{nameof(DiscoBallManager)} Toggle {(on ? "ON" : "OFF")} {CachedDiscoBallAnimators.Count} animators");
|
||||
|
||||
foreach (var discoBall in CachedDiscoBalls)
|
||||
{
|
||||
|
|
@ -133,7 +133,7 @@ namespace MuzikaGromche
|
|||
|
||||
internal static void Clear()
|
||||
{
|
||||
Debug.Log($"{nameof(MuzikaGromche)} {nameof(DiscoBallManager)} Clearing {CachedDiscoBalls.Count} disco balls & {CachedDiscoBallAnimators.Count} animators");
|
||||
Plugin.Log.LogDebug($"{nameof(DiscoBallManager)} Clearing {CachedDiscoBalls.Count} disco balls & {CachedDiscoBallAnimators.Count} animators");
|
||||
CachedDiscoBallAnimators.Clear();
|
||||
CachedDiscoBalls.Clear();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ namespace MuzikaGromche
|
|||
|
||||
var jsonObject = new Dictionary<string, object>();
|
||||
var tracksList = new List<object>();
|
||||
jsonObject["version"] = PluginInfo.PLUGIN_VERSION;
|
||||
jsonObject["version"] = MyPluginInfo.PLUGIN_VERSION;
|
||||
jsonObject["tracks"] = tracksList;
|
||||
foreach (var (selectableTrack, audioTrack) in SelectTracks(tracks))
|
||||
{
|
||||
|
|
@ -56,14 +56,15 @@ namespace MuzikaGromche
|
|||
{
|
||||
["Name"] = audioTrack.Name, // may be different from selectableTrack.Name, if selectable track is a group
|
||||
["IsExplicit"] = selectableTrack.IsExplicit,
|
||||
["Season"] = selectableTrack.Season?.Name,
|
||||
["Language"] = selectableTrack.Language.Full,
|
||||
["WindUpTimer"] = audioTrack.WindUpTimer,
|
||||
["Bpm"] = audioTrack.Bpm,
|
||||
["Beats"] = audioTrack.Beats,
|
||||
["LoopOffset"] = audioTrack.LoopOffset,
|
||||
["Ext"] = audioTrack.Ext,
|
||||
["FileDurationIntro"] = audioTrack.LoadedIntro.length,
|
||||
["FileDurationLoop"] = audioTrack.LoadedLoop.length,
|
||||
["FileDurationIntro"] = audioTrack.LoadedIntro?.length ?? 0f,
|
||||
["FileDurationLoop"] = audioTrack.LoadedLoop?.length ?? 0f,
|
||||
["FileNameIntro"] = audioTrack.FileNameIntro,
|
||||
["FileNameLoop"] = audioTrack.FileNameLoop,
|
||||
["BeatsOffset"] = audioTrack.BeatsOffset,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace MuzikaGromche;
|
||||
|
||||
// A global MonoBehaviour instance to run coroutines from non-MonoBehaviour or static context.
|
||||
internal static class GlobalBehaviour
|
||||
{
|
||||
sealed class AdhocBehaviour : MonoBehaviour;
|
||||
|
||||
static AdhocBehaviour? instance = null;
|
||||
|
||||
public static MonoBehaviour Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
var go = new GameObject("MuzikaGromche_GlobalBehaviour", [
|
||||
typeof(AdhocBehaviour),
|
||||
])
|
||||
{
|
||||
hideFlags = HideFlags.HideAndDontSave
|
||||
};
|
||||
Object.DontDestroyOnLoad(go);
|
||||
instance = go.GetComponent<AdhocBehaviour>();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,14 +8,11 @@
|
|||
<AssemblyName>Ratijas.MuzikaGromche</AssemblyName>
|
||||
<Product>Muzika Gromche</Product>
|
||||
<Description>Add some content to your inverse teleporter experience on Titan!</Description>
|
||||
<Version>1337.9001.2</Version>
|
||||
<Version>1337.9001.4</Version>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<!-- NetcodePatch requires anything but 'full' -->
|
||||
<DebugType>portable</DebugType>
|
||||
|
||||
<PackageReadmeFile>../README.md</PackageReadmeFile>
|
||||
<PackageProjectUrl>https://git.vilunov.me/ratijas/muzika-gromche</PackageProjectUrl>
|
||||
<RepositoryUrl>https://git.vilunov.me/ratijas/muzika-gromche</RepositoryUrl>
|
||||
|
|
@ -32,10 +29,20 @@
|
|||
<NoWarn>$(NoWarn);CS0436</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Embedded debug -->
|
||||
<PropertyGroup>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<!-- NetcodePatch requires anything but 'full' -->
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="$(Configuration) == 'Release'">
|
||||
<PathMap>$(UserProfile)=~,$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)'))=$(PackageId)/</PathMap>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BepInEx.Analyzers" Version="1.*" PrivateAssets="all" Private="false" />
|
||||
<PackageReference Include="BepInEx.Core" Version="5.*" PrivateAssets="all" Private="false" />
|
||||
<PackageReference Include="BepInEx.PluginInfoProps" Version="1.*" PrivateAssets="all" Private="false" />
|
||||
<PackageReference Include="BepInEx.PluginInfoProps" Version="2.*" PrivateAssets="all"/>
|
||||
<PackageReference Include="UnityEngine.Modules" Version="2022.3.9" PrivateAssets="all" Private="false" />
|
||||
<PackageReference Include="BepInEx.AssemblyPublicizer.MSBuild" Version="0.4.1" PrivateAssets="all" Private="false" />
|
||||
<PackageReference Include="AinaVT-LethalConfig" Version="1.4.6" PrivateAssets="all" Private="false" />
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using BepInEx;
|
||||
using BepInEx.Configuration;
|
||||
using BepInEx.Logging;
|
||||
using HarmonyLib;
|
||||
using LethalConfig;
|
||||
using LethalConfig.ConfigItems;
|
||||
|
|
@ -7,7 +8,6 @@ using LethalConfig.ConfigItems.Options;
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
|
|
@ -16,16 +16,17 @@ using System.Security.Cryptography;
|
|||
using System.Text;
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace MuzikaGromche
|
||||
{
|
||||
[BepInPlugin(PluginInfo.PLUGIN_GUID, PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)]
|
||||
[BepInPlugin(MyPluginInfo.PLUGIN_GUID, MyPluginInfo.PLUGIN_NAME, MyPluginInfo.PLUGIN_VERSION)]
|
||||
[BepInDependency("ainavt.lc.lethalconfig", "1.4.6")]
|
||||
[BepInDependency("watergun.v72lightfix", BepInDependency.DependencyFlags.SoftDependency)]
|
||||
[BepInDependency("BMX.LobbyCompatibility", BepInDependency.DependencyFlags.SoftDependency)]
|
||||
public class Plugin : BaseUnityPlugin
|
||||
{
|
||||
private static Harmony Harmony = null!;
|
||||
internal static ManualLogSource Log = null!;
|
||||
internal new static Config Config { get; private set; } = null!;
|
||||
|
||||
// Not all lights are white by default. For example, Mineshaft's neon light is green-ish.
|
||||
|
|
@ -46,6 +47,15 @@ namespace MuzikaGromche
|
|||
[-0.5f, 0.5f, 8f, 15f, 16f, 24f, 29f, 30f, 36f, 37f, 38f, 44f, 47.5f],
|
||||
[ 0f, 0.6f, 0f, 0f, 0.4f, 0f, 0f, 0.3f, 0f, 0f, 0.3f, 0f, 0f]);
|
||||
|
||||
private static readonly Palette PalettePickUpSticks = Palette
|
||||
.Parse(["#FC933C", "#FC3C9D", "#EEA0A5", "#CA71FC", "#d01760"])
|
||||
.Use(p =>
|
||||
{
|
||||
var energetic = p * 6 + new Palette(p.Colors[0..2]); // 32 colors
|
||||
var slower = (p * 3 + new Palette([p.Colors[2]])).Stretch(2); // 16*2 colors
|
||||
return energetic + slower;
|
||||
});
|
||||
|
||||
public static readonly ISelectableTrack[] Tracks = [
|
||||
new SelectableAudioTrack
|
||||
{
|
||||
|
|
@ -458,8 +468,8 @@ namespace MuzikaGromche
|
|||
(56, "Counting crypto to\nembarrass Wall Street"),
|
||||
(80, $"Instling min3r.exe\t\t\tresolving ur private IP\n/"),
|
||||
(82, $"Instling min3r.exe\n00% [8=D ]\tHenllo ${{username = \"{Environment.UserName}\"}}\t\tresolving ur private IP\n-{PwnLyricsVariants[^3]}"),
|
||||
(84, $"Instling min3r.exe\n33% [8====D ]\t\t\tresolving ur private IP\n\\{PwnLyricsVariants[^3]}"),
|
||||
(86, $"Instling min3r.exe\n66% [8=========D ]\t\t\tresolving ur private IP\n|{PwnLyricsVariants[^2]}"),
|
||||
(84, $"Instling min3r.exe\n34% [8====D ]\t\t\tresolving ur private IP\n\\{PwnLyricsVariants[^3]}"),
|
||||
(86, $"Instling min3r.exe\n69% [8=========D ]\t\t\tresolving ur private IP\n|{PwnLyricsVariants[^2]}"),
|
||||
(88, $"Instling min3r.exe\n95% [8============D ]\t\tWhere did you download\nthis < mod / dll > from?\tresolving ur private IP\n{PwnLyricsVariants[^2]}/"),
|
||||
(90, $"Instling min3r.exe\n99% [8=============D]\t\t\tresolving ur private IP\n-{PwnLyricsVariants[^2]}"),
|
||||
(92, $"Encrpt1ng f!les.. \n99% [8=============D]\t\t\tresolving ur private IP\n\\{PwnLyricsVariants[^1]}"),
|
||||
|
|
@ -847,17 +857,192 @@ namespace MuzikaGromche
|
|||
[0f, 0.5f, 0f, 0f, 0.5f]),
|
||||
GameOverText = "[LIFE SUPPORT: REAL GONE]",
|
||||
},
|
||||
new SelectableAudioTrack
|
||||
{
|
||||
Name = "HighLow",
|
||||
AudioType = AudioType.OGGVORBIS,
|
||||
Language = Language.ENGLISH,
|
||||
WindUpTimer = 37.12f,
|
||||
Bars = 12,
|
||||
BeatsOffset = 0f,
|
||||
ColorTransitionIn = 0.75f,
|
||||
ColorTransitionOut = 0.25f,
|
||||
ColorTransitionEasing = Easing.OutExpo,
|
||||
Palette = Palette.Parse([
|
||||
"#2e2e28", "#dfa24d", "#2e2e28", "#dfa24d",
|
||||
"#2e2e28", "#dfa24d", "#2e2e28", "#dfa24d",
|
||||
]),
|
||||
LoopOffset = 0,
|
||||
FadeOutBeat = -1.5f,
|
||||
FadeOutDuration = 1.5f,
|
||||
FlickerLightsTimeSeries = [-33, 39],
|
||||
Lyrics = [
|
||||
],
|
||||
DrunknessLoopOffsetTimeSeries = new(
|
||||
[-2f, -1f, 6f],
|
||||
[ 0f, 0.5f, 0f]),
|
||||
CondensationLoopOffsetTimeSeries = new(
|
||||
[-2f, -1f, 6f],
|
||||
[ 0f, 0.5f, 0f]),
|
||||
GameOverText = "[LIFE SUPORT: NIRVANA]",
|
||||
},
|
||||
new SelectableAudioTrack
|
||||
{
|
||||
Name = "IkWilJe",
|
||||
AudioType = AudioType.OGGVORBIS,
|
||||
Language = Language.ENGLISH,
|
||||
Season = Season.NewYear,
|
||||
WindUpTimer = 43.03f,
|
||||
Beats = 13 * 4 + 2, // = 54
|
||||
BeatsOffset = 0f,
|
||||
ColorTransitionIn = 0.01f,
|
||||
ColorTransitionOut = 0.99f,
|
||||
ColorTransitionEasing = Easing.OutExpo,
|
||||
Palette = Palette.Parse([
|
||||
"#0B6623", "#FF2D2D", "#FFD700",
|
||||
"#00BFFF", "#9400D3", "#00FF7F",
|
||||
]),
|
||||
LoopOffset = 0,
|
||||
FadeOutBeat = -14f,
|
||||
FadeOutDuration = 12f,
|
||||
FlickerLightsTimeSeries = [31.45f],
|
||||
Lyrics = [],
|
||||
DrunknessLoopOffsetTimeSeries = new(
|
||||
[0f, 0.25f, 6f],
|
||||
[0f, 0.5f, 0f]),
|
||||
GameOverText = "[NEXT YEAR -- DEFINITELY]",
|
||||
},
|
||||
new SelectableAudioTrack
|
||||
{
|
||||
Name = "Paarden",
|
||||
AudioType = AudioType.OGGVORBIS,
|
||||
Language = Language.RUSSIAN,
|
||||
Season = Season.NewYear,
|
||||
WindUpTimer = 36.12f,
|
||||
Bars = 8,
|
||||
BeatsOffset = 0f,
|
||||
ColorTransitionIn = 0.25f,
|
||||
ColorTransitionOut = 0.4f,
|
||||
ColorTransitionEasing = Easing.OutCubic,
|
||||
Palette = Palette.Parse([
|
||||
"#F0FBFF", "#9ED9FF", "#0B95FF",
|
||||
"#66C7FF", "#CAE8FF", "#3BB6FF",
|
||||
]),
|
||||
LoopOffset = 0,
|
||||
FadeOutBeat = -4f,
|
||||
FadeOutDuration = 4f,
|
||||
FlickerLightsTimeSeries = [31.5f],
|
||||
Lyrics = [],
|
||||
DrunknessLoopOffsetTimeSeries = new(
|
||||
[0f, 0.25f, 6f],
|
||||
[0f, 0.5f, 0f]),
|
||||
GameOverText = "[NEXT YEAR -- DEFINITELY]",
|
||||
},
|
||||
new SelectableAudioTrack
|
||||
{
|
||||
Name = "DiscoKapot",
|
||||
AudioType = AudioType.OGGVORBIS,
|
||||
Language = Language.RUSSIAN,
|
||||
Season = Season.NewYear,
|
||||
WindUpTimer = 30.3f,
|
||||
Bars = 8,
|
||||
BeatsOffset = 0f,
|
||||
ColorTransitionIn = 0.25f,
|
||||
ColorTransitionOut = 0.6f,
|
||||
ColorTransitionEasing = Easing.InOutExpo,
|
||||
Palette = Palette.Parse([
|
||||
"#0B6623", "#FF2D2D", "#FFD700",
|
||||
"#00BFFF", "#9400D3", "#00FF7F",
|
||||
]),
|
||||
LoopOffset = 0,
|
||||
FadeOutBeat = -4f,
|
||||
FadeOutDuration = 4f,
|
||||
FlickerLightsTimeSeries = [-32, -24, -16, 16, 32],
|
||||
Lyrics = [],
|
||||
DrunknessLoopOffsetTimeSeries = new(
|
||||
[0f, 0.25f, 6f],
|
||||
[0f, 0.5f, 0f]),
|
||||
GameOverText = "[NEXT YEAR -- DEFINITELY]",
|
||||
},
|
||||
new SelectableTracksGroup
|
||||
{
|
||||
Name = "PickUpSticks",
|
||||
Language = Language.ENGLISH,
|
||||
Tracks =
|
||||
[
|
||||
new CoreAudioTrack
|
||||
{
|
||||
Name = "PickUpSticks1",
|
||||
FileNameLoop = "PickUpSticksLoop.ogg",
|
||||
AudioType = AudioType.OGGVORBIS,
|
||||
WindUpTimer = 38.5f,
|
||||
Bars = 16,
|
||||
BeatsOffset = 0.2f,
|
||||
ColorTransitionIn = 0.6f,
|
||||
ColorTransitionOut = 0.3f,
|
||||
ColorTransitionEasing = Easing.InOutCubic,
|
||||
Palette = PalettePickUpSticks,
|
||||
LoopOffset = 0,
|
||||
FadeOutBeat = -2,
|
||||
FadeOutDuration = 2,
|
||||
FlickerLightsTimeSeries = [-36, -4, 32],
|
||||
Lyrics = [],
|
||||
DrunknessLoopOffsetTimeSeries = new([0f, 0.5f, 3f, 32f, 34f, 40f], [0f, 0.5f, 0f, 0f, 0.3f, 0f]),
|
||||
CondensationLoopOffsetTimeSeries = new([23f, 28f, 31f, 34f, 38f, 52f], [0f, 0.6f, 0f, 0f, 0.7f, 0f]),
|
||||
GameOverText = "[LOVE SUPPORT: OFFLINE]",
|
||||
},
|
||||
new CoreAudioTrack
|
||||
{
|
||||
Name = "PickUpSticks2",
|
||||
FileNameLoop = "PickUpSticksLoop.ogg",
|
||||
AudioType = AudioType.OGGVORBIS,
|
||||
WindUpTimer = 38.47f,
|
||||
Bars = 16,
|
||||
BeatsOffset = 0.2f,
|
||||
ColorTransitionIn = 0.6f,
|
||||
ColorTransitionOut = 0.3f,
|
||||
ColorTransitionEasing = Easing.InOutCubic,
|
||||
Palette = PalettePickUpSticks,
|
||||
LoopOffset = 0,
|
||||
FadeOutBeat = -2,
|
||||
FadeOutDuration = 2,
|
||||
FlickerLightsTimeSeries = [-36, -4, 32],
|
||||
Lyrics = [],
|
||||
DrunknessLoopOffsetTimeSeries = new([0f, 0.5f, 3f, 32f, 34f, 40f], [0f, 0.5f, 0f, 0f, 0.3f, 0f]),
|
||||
CondensationLoopOffsetTimeSeries = new([23f, 28f, 31f, 34f, 38f, 52f], [0f, 0.5f, 0f, 0f, 0.5f, 0f]),
|
||||
GameOverText = "[LOVE SUPPORT: OFFLINE]",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
private static int GetCurrentSeed()
|
||||
{
|
||||
var seed = 0;
|
||||
var roundManager = RoundManager.Instance;
|
||||
if (roundManager != null && roundManager.dungeonGenerator != null)
|
||||
{
|
||||
seed = roundManager.dungeonGenerator.Generator.ChosenSeed;
|
||||
}
|
||||
return seed;
|
||||
}
|
||||
|
||||
public static ISelectableTrack ChooseTrack()
|
||||
{
|
||||
var seed = RoundManager.Instance.dungeonGenerator.Generator.ChosenSeed;
|
||||
var tracks = Config.SkipExplicitTracks.Value ? [.. Tracks.Where(track => !track.IsExplicit)] : Tracks;
|
||||
int[] weights = [.. tracks.Select(track => track.Weight.Value)];
|
||||
var seed = GetCurrentSeed();
|
||||
var today = DateTime.Today;
|
||||
var season = SeasonalContentManager.CurrentSeason(today);
|
||||
var tracksEnumerable = SeasonalContentManager.Filter(Tracks, season);
|
||||
if (Config.SkipExplicitTracks.Value)
|
||||
{
|
||||
tracksEnumerable = tracksEnumerable.Where(track => !track.IsExplicit);
|
||||
}
|
||||
var tracks = tracksEnumerable.ToArray();
|
||||
int[] weights = tracks.Select(track => track.Weight.Value).ToArray();
|
||||
var rwi = new RandomWeightedIndex(weights);
|
||||
var trackId = rwi.GetRandomWeightedIndex(seed);
|
||||
var track = tracks[trackId];
|
||||
Debug.Log($"{nameof(MuzikaGromche)} Seed is {seed}, chosen track is \"{track.Name}\", #{trackId} of {rwi}");
|
||||
Log.LogInfo($"Seed is {seed}, season is {season?.Name ?? "<none>"}, chosen track is \"{track.Name}\", #{trackId} of {rwi}");
|
||||
return tracks[trackId];
|
||||
}
|
||||
|
||||
|
|
@ -890,78 +1075,52 @@ namespace MuzikaGromche
|
|||
|
||||
void Awake()
|
||||
{
|
||||
Log = Logger;
|
||||
|
||||
// Sort in place by name
|
||||
Array.Sort(Tracks.Select(track => track.Name).ToArray(), Tracks);
|
||||
|
||||
string dir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
Dictionary<string, (UnityWebRequest Request, List<Action<AudioClip>> Setters)> requests = [];
|
||||
requests.EnsureCapacity(Tracks.Length * 2);
|
||||
|
||||
foreach (var track in Tracks.SelectMany(track => track.GetTracks()))
|
||||
{
|
||||
foreach (var (fileName, setter) in new (string, Action<AudioClip>)[]
|
||||
{
|
||||
(track.FileNameIntro, clip => track.LoadedIntro = clip),
|
||||
(track.FileNameLoop, clip => track.LoadedLoop = clip),
|
||||
})
|
||||
{
|
||||
if (requests.TryGetValue(fileName, out var tuple))
|
||||
{
|
||||
tuple.Setters.Add(setter);
|
||||
}
|
||||
else
|
||||
{
|
||||
var request = UnityWebRequestMultimedia.GetAudioClip($"file://{dir}/{fileName}", track.AudioType);
|
||||
request.SendWebRequest();
|
||||
requests[fileName] = (request, [setter]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (!requests.Values.All(tuple => tuple.Request.isDone)) { }
|
||||
|
||||
if (requests.Values.All(tuple => tuple.Request.result == UnityWebRequest.Result.Success))
|
||||
{
|
||||
|
||||
foreach (var (fileName, tuple) in requests)
|
||||
{
|
||||
var clip = DownloadHandlerAudioClip.GetContent(tuple.Request);
|
||||
foreach (var setter in tuple.Setters)
|
||||
{
|
||||
setter(clip);
|
||||
}
|
||||
}
|
||||
#if DEBUG
|
||||
foreach (var track in Tracks)
|
||||
{
|
||||
track.Debug();
|
||||
}
|
||||
Exporter.ExportTracksJSON(Tracks);
|
||||
GlobalBehaviour.Instance.StartCoroutine(PreloadDebugAndExport(Tracks));
|
||||
#endif
|
||||
Config = new Config(base.Config);
|
||||
DiscoBallManager.Load();
|
||||
PoweredLightsAnimators.Load();
|
||||
var harmony = new Harmony(PluginInfo.PLUGIN_NAME);
|
||||
harmony.PatchAll(typeof(GameNetworkManagerPatch));
|
||||
harmony.PatchAll(typeof(JesterPatch));
|
||||
harmony.PatchAll(typeof(EnemyAIPatch));
|
||||
harmony.PatchAll(typeof(PoweredLightsAnimatorsPatch));
|
||||
harmony.PatchAll(typeof(AllPoweredLightsPatch));
|
||||
harmony.PatchAll(typeof(DiscoBallTilePatch));
|
||||
harmony.PatchAll(typeof(DiscoBallDespawnPatch));
|
||||
harmony.PatchAll(typeof(SpawnRatePatch));
|
||||
harmony.PatchAll(typeof(DeathScreenGameOverTextResetPatch));
|
||||
harmony.PatchAll(typeof(ScreenFiltersManager.HUDManagerScreenFiltersPatch));
|
||||
NetcodePatcher();
|
||||
Compatibility.Register(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
var failed = requests.Values.Where(tuple => tuple.Request.result != UnityWebRequest.Result.Success).Select(tuple => tuple.Request.GetUrl());
|
||||
Logger.LogError("Could not load audio file " + string.Join(", ", failed));
|
||||
}
|
||||
Config = new Config(base.Config);
|
||||
DiscoBallManager.Load();
|
||||
PoweredLightsAnimators.Load();
|
||||
Harmony = new Harmony(MyPluginInfo.PLUGIN_NAME);
|
||||
Harmony.PatchAll(typeof(GameNetworkManagerPatch));
|
||||
Harmony.PatchAll(typeof(JesterPatch));
|
||||
Harmony.PatchAll(typeof(EnemyAIPatch));
|
||||
Harmony.PatchAll(typeof(PoweredLightsAnimatorsPatch));
|
||||
Harmony.PatchAll(typeof(AllPoweredLightsPatch));
|
||||
Harmony.PatchAll(typeof(DiscoBallTilePatch));
|
||||
Harmony.PatchAll(typeof(DiscoBallDespawnPatch));
|
||||
Harmony.PatchAll(typeof(SpawnRatePatch));
|
||||
Harmony.PatchAll(typeof(DeathScreenGameOverTextResetPatch));
|
||||
Harmony.PatchAll(typeof(ScreenFiltersManager.HUDManagerScreenFiltersPatch));
|
||||
Harmony.PatchAll(typeof(ClearAudioClipCachePatch));
|
||||
NetcodePatcher();
|
||||
Compatibility.Register(this);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
static IEnumerator PreloadDebugAndExport(ISelectableTrack[] tracks)
|
||||
{
|
||||
foreach (var track in tracks.SelectMany(track => track.GetTracks()))
|
||||
{
|
||||
AudioClipsCacheManager.LoadAudioTrack(track);
|
||||
}
|
||||
yield return new WaitUntil(() => AudioClipsCacheManager.AllDone);
|
||||
Log.LogDebug("All tracks preloaded, exporting to JSON");
|
||||
|
||||
foreach (var track in tracks)
|
||||
{
|
||||
track.Debug();
|
||||
}
|
||||
Exporter.ExportTracksJSON(tracks);
|
||||
AudioClipsCacheManager.Clear();
|
||||
}
|
||||
#endif
|
||||
|
||||
private static void NetcodePatcher()
|
||||
{
|
||||
var types = Assembly.GetExecutingAssembly().GetTypes();
|
||||
|
|
@ -1092,7 +1251,7 @@ namespace MuzikaGromche
|
|||
|
||||
// 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
|
||||
public interface ISelectableTrack : ISeasonalContent
|
||||
{
|
||||
// Name of the track, as shown in config entry UI; also used for default file names.
|
||||
public string Name { get; init; }
|
||||
|
|
@ -1129,21 +1288,47 @@ namespace MuzikaGromche
|
|||
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);
|
||||
public float Bpm
|
||||
{
|
||||
get
|
||||
{
|
||||
if (LoadedLoop == null || LoadedLoop.length <= 0f)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 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 { get; }
|
||||
|
||||
// Number of beats between WindUpTimer and where looped segment starts (not the loop audio).
|
||||
public int LoopOffset { get; }
|
||||
public float LoopOffsetInSeconds => (float)LoopOffset / (float)Beats * LoadedLoop.length;
|
||||
public float LoopOffsetInSeconds
|
||||
{
|
||||
get
|
||||
{
|
||||
if (LoadedLoop == null || LoadedLoop.length <= 0f)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (float)LoopOffset / (float)Beats * LoadedLoop.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 { get; }
|
||||
|
||||
public AudioClip LoadedIntro { get; internal set; }
|
||||
public AudioClip LoadedLoop { get; internal set; }
|
||||
public AudioClip? LoadedIntro { get; internal set; }
|
||||
public AudioClip? LoadedLoop { get; internal set; }
|
||||
|
||||
public string FileNameIntro { get; }
|
||||
public string FileNameLoop { get; }
|
||||
|
|
@ -1160,7 +1345,20 @@ namespace MuzikaGromche
|
|||
public float BeatsOffset { get; }
|
||||
|
||||
// Offset of beats, in seconds. Bigger offset => colors will change later.
|
||||
public float BeatsOffsetInSeconds => BeatsOffset / Beats * LoadedLoop.length;
|
||||
public float BeatsOffsetInSeconds
|
||||
{
|
||||
get
|
||||
{
|
||||
if (LoadedLoop == null || LoadedLoop.length <= 0f)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
return BeatsOffset / (float)Beats * LoadedLoop.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float FadeOutBeat { get; }
|
||||
public float FadeOutDuration { get; }
|
||||
|
|
@ -1199,8 +1397,8 @@ namespace MuzikaGromche
|
|||
int IAudioTrack.Beats => Track.Beats;
|
||||
int IAudioTrack.LoopOffset => Track.LoopOffset;
|
||||
AudioType IAudioTrack.AudioType => Track.AudioType;
|
||||
AudioClip IAudioTrack.LoadedIntro { get => Track.LoadedIntro; set => Track.LoadedIntro = value; }
|
||||
AudioClip IAudioTrack.LoadedLoop { get => Track.LoadedLoop; set => Track.LoadedLoop = value; }
|
||||
AudioClip? IAudioTrack.LoadedIntro { get => Track.LoadedIntro; set => Track.LoadedIntro = value; }
|
||||
AudioClip? IAudioTrack.LoadedLoop { get => Track.LoadedLoop; set => Track.LoadedLoop = value; }
|
||||
string IAudioTrack.FileNameIntro => Track.FileNameIntro;
|
||||
string IAudioTrack.FileNameLoop => Track.FileNameLoop;
|
||||
float IAudioTrack.BeatsOffset => Track.BeatsOffset;
|
||||
|
|
@ -1234,8 +1432,8 @@ namespace MuzikaGromche
|
|||
|
||||
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!;
|
||||
public AudioClip? LoadedIntro { get; set; } = null;
|
||||
public AudioClip? LoadedLoop { get; set; } = null;
|
||||
|
||||
private string? FileNameIntroOverride = null;
|
||||
public string FileNameIntro
|
||||
|
|
@ -1303,6 +1501,7 @@ namespace MuzikaGromche
|
|||
{
|
||||
public /* required */ Language Language { get; init; }
|
||||
public bool IsExplicit { get; init; } = false;
|
||||
public Season? Season { get; init; } = null;
|
||||
ConfigEntry<int> ISelectableTrack.Weight { get; set; } = null!;
|
||||
|
||||
IAudioTrack[] ISelectableTrack.GetTracks() => [this];
|
||||
|
|
@ -1311,7 +1510,7 @@ namespace MuzikaGromche
|
|||
|
||||
void ISelectableTrack.Debug()
|
||||
{
|
||||
Debug.Log($"{nameof(MuzikaGromche)} Track \"{Name}\", Intro={LoadedIntro.length:N4}, Loop={LoadedLoop.length:N4}");
|
||||
Plugin.Log.LogDebug($"Track \"{Name}\", Intro={LoadedIntro?.length:N4}, Loop={LoadedLoop?.length:N4}");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1320,6 +1519,7 @@ namespace MuzikaGromche
|
|||
public /* required */ string Name { get; init; } = "";
|
||||
public /* required */ Language Language { get; init; }
|
||||
public bool IsExplicit { get; init; } = false;
|
||||
public Season? Season { get; init; } = null;
|
||||
ConfigEntry<int> ISelectableTrack.Weight { get; set; } = null!;
|
||||
|
||||
public /* required */ IAudioTrack[] Tracks = [];
|
||||
|
|
@ -1337,10 +1537,10 @@ namespace MuzikaGromche
|
|||
|
||||
void ISelectableTrack.Debug()
|
||||
{
|
||||
Debug.Log($"{nameof(MuzikaGromche)} Track Group \"{Name}\", Count={Tracks.Length}");
|
||||
Plugin.Log.LogDebug($"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}");
|
||||
Plugin.Log.LogDebug($" Track {index} \"{track.Name}\", Intro={track.LoadedIntro?.length:N4}, Loop={track.LoadedLoop?.length:N4}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1707,7 +1907,7 @@ namespace MuzikaGromche
|
|||
|
||||
float timeSinceStartOfLoop = time - offset;
|
||||
|
||||
var adjustedTimeNormalized = timeSinceStartOfLoop / LoopLength;
|
||||
var adjustedTimeNormalized = (LoopLength <= 0f) ? 0f : timeSinceStartOfLoop / LoopLength;
|
||||
|
||||
var beat = adjustedTimeNormalized * Beats;
|
||||
|
||||
|
|
@ -1717,11 +1917,10 @@ namespace MuzikaGromche
|
|||
IsLooping |= timestamp.IsLooping;
|
||||
|
||||
#if DEBUG && false
|
||||
Debug.LogFormat("{0} t={1,10:N4} d={2,7:N4} {3} Time={4:N4} norm={5,6:N4} beat={6,7:N4}",
|
||||
nameof(MuzikaGromche),
|
||||
Plugin.Log.LogDebug(string.Format("t={0,10:N4} d={1,7:N4} {2} Time={3:N4} norm={4,6:N4} beat={5,7:N4}",
|
||||
Time.realtimeSinceStartup, Time.deltaTime,
|
||||
isExtrapolated ? 'E' : '_', time,
|
||||
adjustedTimeNormalized, beat);
|
||||
adjustedTimeNormalized, beat));
|
||||
#endif
|
||||
|
||||
return timestamp;
|
||||
|
|
@ -1756,9 +1955,10 @@ namespace MuzikaGromche
|
|||
LyricsRandomPerLoop = LyricsRandom.Next();
|
||||
}
|
||||
this.track = track;
|
||||
AudioState = new(track.LoadedIntro.length);
|
||||
WindUpLoopingState = new(track.WindUpTimer, track.LoadedLoop.length, track.Beats);
|
||||
LoopLoopingState = new(track.WindUpTimer + track.LoopOffsetInSeconds, track.LoadedLoop.length, track.Beats);
|
||||
AudioState = new(track.LoadedIntro?.length ?? 0f);
|
||||
var loadedLoopLength = track.LoadedLoop?.length ?? 0f;
|
||||
WindUpLoopingState = new(track.WindUpTimer, loadedLoopLength, track.Beats);
|
||||
LoopLoopingState = new(track.WindUpTimer + track.LoopOffsetInSeconds, loadedLoopLength, track.Beats);
|
||||
}
|
||||
|
||||
public List<BaseEvent> Update(AudioSource intro, AudioSource loop)
|
||||
|
|
@ -1783,7 +1983,7 @@ namespace MuzikaGromche
|
|||
LastKnownLoopOffsetBeat = loopOffsetTimestamp.Beat;
|
||||
var events = GetEvents(loopOffsetTimestamp, loopOffsetSpan, windUpOffsetTimestamp);
|
||||
#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)}");
|
||||
Plugin.Log.LogDebug($"looping? {(LoopLoopingState.IsLooping ? 'X' : '_')}{(WindUpLoopingState.IsLooping ? 'X' : '_')} Loop={loopOffsetSpan} WindUp={windUpOffsetTimestamp} Time={Time.realtimeSinceStartup:N4} events={string.Join(",", events)}");
|
||||
#endif
|
||||
return events;
|
||||
}
|
||||
|
|
@ -1842,7 +2042,8 @@ namespace MuzikaGromche
|
|||
|
||||
if (GetInterpolation(loopOffsetTimestamp, track.DrunknessLoopOffsetTimeSeries, Easing.Linear) is { } drunkness)
|
||||
{
|
||||
events.Add(new DrunkEvent(drunkness));
|
||||
var value = Config.ReduceVFXIntensity.Value ? drunkness * 0.3f : drunkness;
|
||||
events.Add(new DrunkEvent(value));
|
||||
}
|
||||
|
||||
if (GetInterpolation(loopOffsetTimestamp, track.CondensationLoopOffsetTimeSeries, Easing.Linear) is { } condensation)
|
||||
|
|
@ -2233,6 +2434,8 @@ namespace MuzikaGromche
|
|||
{
|
||||
public static ConfigEntry<bool> DisplayLyrics { get; private set; } = null!;
|
||||
|
||||
public static ConfigEntry<bool> ReduceVFXIntensity { get; private set; } = null!;
|
||||
|
||||
public static ConfigEntry<float> AudioOffset { get; private set; } = null!;
|
||||
|
||||
public static ConfigEntry<bool> SkipExplicitTracks { get; private set; } = null!;
|
||||
|
|
@ -2299,6 +2502,10 @@ namespace MuzikaGromche
|
|||
new ConfigDescription("Display lyrics in the HUD tooltip when you hear the music."));
|
||||
LethalConfigManager.AddConfigItem(new BoolCheckBoxConfigItem(DisplayLyrics, requiresRestart: false));
|
||||
|
||||
ReduceVFXIntensity = configFile.Bind("General", "Reduce Visual Effects", false,
|
||||
new ConfigDescription("Reduce intensity of certain visual effects when you hear the music."));
|
||||
LethalConfigManager.AddConfigItem(new BoolCheckBoxConfigItem(ReduceVFXIntensity, requiresRestart: false));
|
||||
|
||||
Volume = configFile.Bind("General", "Volume", VolumeDefault,
|
||||
new ConfigDescription("Volume of music played by this mod.", new AcceptableValueRange<float>(VolumeMin, VolumeMax)));
|
||||
LethalConfigManager.AddConfigItem(new FloatSliderConfigItem(Volume, requiresRestart: false));
|
||||
|
|
@ -2354,8 +2561,9 @@ namespace MuzikaGromche
|
|||
}
|
||||
|
||||
// Create slider entry for track
|
||||
var seasonal = track.Season is Season season ? $"This is seasonal content for {season.Name}.\n\n" : "";
|
||||
string warning = track.IsExplicit ? "Explicit Content/Lyrics!\n\n" : "";
|
||||
string description = $"Language: {language.Full}\n\n{warning}Random (relative) chance of selecting this track.\n\nSet to zero to effectively disable the track.";
|
||||
string description = $"Language: {language.Full}\n\n{seasonal}{warning}Random (relative) chance of selecting this track.\n\nSet to zero to effectively disable the track.";
|
||||
track.Weight = configFile.Bind(
|
||||
new ConfigDefinition(section, track.Name),
|
||||
50,
|
||||
|
|
@ -2611,7 +2819,7 @@ namespace MuzikaGromche
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.Log($"{nameof(MuzikaGromche)} Unable to parse time series: {e}");
|
||||
Plugin.Log.LogError($"Unable to parse time series: {e}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -2628,7 +2836,7 @@ namespace MuzikaGromche
|
|||
strings.Append(", ");
|
||||
}
|
||||
}
|
||||
Debug.Log($"{nameof(MuzikaGromche)} format time series {ts} {strings}");
|
||||
Plugin.Log.LogDebug($"format time series {ts} {strings}");
|
||||
return strings.ToString();
|
||||
}
|
||||
T[]? parseStringArray<T>(string str, Func<string, T> parser, bool sort = false) where T : struct
|
||||
|
|
@ -2641,7 +2849,7 @@ namespace MuzikaGromche
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.Log($"{nameof(MuzikaGromche)} Unable to parse array: {e}");
|
||||
Plugin.Log.LogError($"Unable to parse array: {e}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -2731,12 +2939,12 @@ namespace MuzikaGromche
|
|||
.FirstOrDefault(prefab => prefab.Prefab.name == JesterEnemyPrefabName);
|
||||
if (networkPrefab == null)
|
||||
{
|
||||
Debug.LogError($"{nameof(MuzikaGromche)} JesterEnemy prefab not found!");
|
||||
Plugin.Log.LogError("JesterEnemy prefab not found!");
|
||||
}
|
||||
else
|
||||
{
|
||||
networkPrefab.Prefab.AddComponent<MuzikaGromcheJesterNetworkBehaviour>();
|
||||
Debug.Log($"{nameof(MuzikaGromche)} Patched JesterEnemy");
|
||||
Plugin.Log.LogInfo("Patched JesterEnemy");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2761,7 +2969,7 @@ namespace MuzikaGromche
|
|||
var farAudioTransform = gameObject.transform.Find("FarAudio");
|
||||
if (farAudioTransform == null)
|
||||
{
|
||||
Debug.LogError($"{nameof(MuzikaGromche)} JesterEnemy->FarAudio prefab not found!");
|
||||
Plugin.Log.LogError("JesterEnemy->FarAudio prefab not found!");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -2788,7 +2996,7 @@ namespace MuzikaGromche
|
|||
|
||||
Config.Volume.SettingChanged += UpdateVolume;
|
||||
|
||||
Debug.Log($"{nameof(MuzikaGromche)} {nameof(MuzikaGromcheJesterNetworkBehaviour)} Patched JesterEnemy");
|
||||
Plugin.Log.LogInfo($"{nameof(MuzikaGromcheJesterNetworkBehaviour)} Patched JesterEnemy");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2856,9 +3064,10 @@ namespace MuzikaGromche
|
|||
[ClientRpc]
|
||||
public void SetTrackClientRpc(string name)
|
||||
{
|
||||
Debug.Log($"{nameof(MuzikaGromche)} SetTrackClientRpc {name}");
|
||||
Plugin.Log.LogInfo($"SetTrackClientRpc {name}");
|
||||
if (Plugin.FindTrackNamed(name) is { } track)
|
||||
{
|
||||
AudioClipsCacheManager.LoadAudioTrack(track);
|
||||
CurrentTrack = Config.OverrideCurrentTrack(track);
|
||||
}
|
||||
}
|
||||
|
|
@ -2868,7 +3077,7 @@ namespace MuzikaGromche
|
|||
{
|
||||
var selectableTrack = Plugin.ChooseTrack();
|
||||
var audioTrack = selectableTrack.SelectTrack(SelectedTrackIndex);
|
||||
Debug.Log($"{nameof(MuzikaGromche)} ChooseTrackServerRpc {selectableTrack.Name} #{SelectedTrackIndex} {audioTrack.Name}");
|
||||
Plugin.Log.LogInfo($"ChooseTrackServerRpc {selectableTrack.Name} #{SelectedTrackIndex} {audioTrack.Name}");
|
||||
SetTrackClientRpc(audioTrack.Name);
|
||||
SelectedTrackIndex += 1;
|
||||
}
|
||||
|
|
@ -2877,7 +3086,7 @@ namespace MuzikaGromche
|
|||
{
|
||||
double loopStartDspTime = AudioSettings.dspTime + IntroAudioSource.clip.length - IntroAudioSource.time;
|
||||
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}");
|
||||
Plugin.Log.LogDebug($"Play Intro: dspTime={AudioSettings.dspTime:N4}, intro.time={IntroAudioSource.time:N4}/{IntroAudioSource.clip.length:N4}, scheduled Loop={loopStartDspTime}");
|
||||
}
|
||||
|
||||
public void OverrideDeathScreenGameOverText()
|
||||
|
|
@ -2935,10 +3144,21 @@ namespace MuzikaGromche
|
|||
var introAudioSource = behaviour.IntroAudioSource;
|
||||
var loopAudioSource = behaviour.LoopAudioSource;
|
||||
|
||||
if (behaviour.CurrentTrack == null)
|
||||
if (behaviour.CurrentTrack == null || behaviour.CurrentTrack.LoadedIntro == null || behaviour.CurrentTrack.LoadedLoop == null)
|
||||
{
|
||||
#if DEBUG
|
||||
Debug.LogError($"{nameof(MuzikaGromche)} CurrentTrack is not set!");
|
||||
if (behaviour.CurrentTrack == null)
|
||||
{
|
||||
Plugin.Log.LogError("CurrentTrack is not set!");
|
||||
}
|
||||
else if (AudioClipsCacheManager.AllDone)
|
||||
{
|
||||
Plugin.Log.LogError("Failed to load audio clips, no in-flight requests running");
|
||||
}
|
||||
else
|
||||
{
|
||||
Plugin.Log.LogDebug($"Waiting for audio clips to load");
|
||||
}
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -205,7 +205,7 @@ namespace MuzikaGromche
|
|||
patch.ManualPatch?.Invoke(animationContainer);
|
||||
|
||||
animator.runtimeAnimatorController = patch.AnimatorController;
|
||||
Debug.Log($"{nameof(MuzikaGromche)} {nameof(PoweredLightsAnimatorsPatch)} {tilePatch.TileName}/{patch.AnimatorContainerPath}: Replaced animator controller");
|
||||
Plugin.Log.LogDebug($"{nameof(PoweredLightsAnimatorsPatch)} {tilePatch.TileName}/{patch.AnimatorContainerPath}: Replaced animator controller");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -223,7 +223,7 @@ namespace MuzikaGromche
|
|||
#pragma warning restore CS0162 // Unreachable code detected
|
||||
}
|
||||
targetObject.name = newName;
|
||||
Debug.Log($"{nameof(MuzikaGromche)} {nameof(PoweredLightsAnimatorsPatch)} {animatorContainer.name}/{relativePath}: Renamed GameObject");
|
||||
Plugin.Log.LogDebug($"{nameof(PoweredLightsAnimatorsPatch)} {animatorContainer.name}/{relativePath}: Renamed GameObject");
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace MuzikaGromche;
|
||||
|
||||
public delegate bool SeasonalContentPredicate(DateTime dateTime);
|
||||
|
||||
// I'm not really sure what to do with seasonal content yet.
|
||||
//
|
||||
// There could be two approaches:
|
||||
// - Force seasonal content tracks to be the only available tracks during their season.
|
||||
// Then seasons must be short, so they don't cut off too much content for too long.
|
||||
// - Exclude seasonal content tracks from the pool when their season is not active.
|
||||
// Considering how many tracks are there in the playlist permanently already,
|
||||
// this might not give the seasonal content enough visibility.
|
||||
//
|
||||
// Either way, seasonal content tracks would be listed in the config UI at all times,
|
||||
// which makes it confusing if you try to select only seasonal tracks outside of their season.
|
||||
|
||||
// Seasons may NOT overlap. There is at most ONE active season at any given date.
|
||||
public readonly record struct Season(string Name, string Description, SeasonalContentPredicate IsActive)
|
||||
{
|
||||
public override string ToString() => Name;
|
||||
|
||||
public static readonly Season NewYear = new("New Year", "New Year and Christmas holiday season", dateTime =>
|
||||
{
|
||||
// December 10 - February 29
|
||||
var month = dateTime.Month;
|
||||
var day = dateTime.Day;
|
||||
return (month == 12 && day >= 10) || (month == 1) || (month == 2 && day <= 29);
|
||||
});
|
||||
|
||||
// Note: it is important that this property goes last
|
||||
public static readonly Season[] All = [NewYear];
|
||||
}
|
||||
|
||||
public interface ISeasonalContent
|
||||
{
|
||||
public Season? Season { get; init; }
|
||||
}
|
||||
|
||||
public static class SeasonalContentManager
|
||||
{
|
||||
public static Season? CurrentSeason(DateTime dateTime)
|
||||
{
|
||||
foreach (var season in Season.All)
|
||||
{
|
||||
if (season.IsActive(dateTime))
|
||||
{
|
||||
return season;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Season? CurrentSeason() => CurrentSeason(DateTime.Today);
|
||||
|
||||
// Take second approach: filter out seasonal content that is not in the current season.
|
||||
public static IEnumerable<T> Filter<T>(this IEnumerable<T> items, Season? season) where T : ISeasonalContent
|
||||
{
|
||||
return items.Where(item =>
|
||||
{
|
||||
if (item.Season == null)
|
||||
{
|
||||
return true; // always available
|
||||
}
|
||||
return item.Season == season;
|
||||
});
|
||||
}
|
||||
|
||||
public static IEnumerable<T> Filter<T>(this IEnumerable<T> items, DateTime dateTime) where T : ISeasonalContent
|
||||
{
|
||||
var season = CurrentSeason(dateTime);
|
||||
return Filter(items, season);
|
||||
}
|
||||
|
||||
public static IEnumerable<T> Filter<T>(this IEnumerable<T> items) where T : ISeasonalContent
|
||||
{
|
||||
return Filter(items, DateTime.Today);
|
||||
}
|
||||
}
|
||||
|
|
@ -80,7 +80,7 @@ namespace MuzikaGromche
|
|||
var multiplier = Mathf.Lerp(minMultiplier, maxMultiplier, normalizedMultiplierTime);
|
||||
|
||||
var newWeight = Mathf.FloorToInt(weights[index] * multiplier);
|
||||
Debug.Log($"{nameof(MuzikaGromche)} {nameof(SpawnRatePatch)} Overriding spawn weight[{index}] {weights[index]} * {multiplier} => {newWeight} for t={SpawnTime}");
|
||||
Plugin.Log.LogInfo($"{nameof(SpawnRatePatch)} Overriding spawn weight[{index}] {weights[index]} * {multiplier} => {newWeight} for t={SpawnTime}");
|
||||
weights[index] = newWeight;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
10
README.md
10
README.md
|
|
@ -23,12 +23,18 @@ Muzika Gromche v1337.9001.0 has been updated to work with Lethal Company v73. Pr
|
|||
|
||||
## Playlist
|
||||
|
||||
English playlist features artists such as **Imagine Dragons, Fall Out Boy, Bon Jovi, Black Eyed Peas, LMFAO** (Party Rock Anthem / Every day I'm shufflin'), **CYBEЯIA** / "Cyberia" (Russian Hackers), and of course **Whistle** by Joel Merry / Flo Rida.
|
||||
English playlist features artists such as **Imagine Dragons, Fall Out Boy, Bon Jovi, Nirvana, Black Eyed Peas, LMFAO** (Party Rock Anthem / Every day I'm shufflin'), **CYBEЯIA** / "Cyberia" (Russian Hackers), **t.A.T.u.**, and of course **Whistle** by Joel Merry / Flo Rida.
|
||||
|
||||
Russian playlist includes **Би-2, Витас, Глюк’oZa** (Глюкоза) & **Ленинград, Дискотека Авария, Noize MC, Oxxxymiron, Сплин, Пошлая Молли.**
|
||||
|
||||
There are also a K-pop track by **aespa**, an anime opening from **One Punch Man,** and an Indian banger by **CarryMinati & Wily Frenzy.**
|
||||
|
||||
Seasonal New Year's songs:
|
||||
|
||||
- **My Chemical Romance - All I Want for Christmas Is You** (codenamed **IkWilJe**)
|
||||
- **Элизиум - Три белых коня** (codenamed **Paarden**)
|
||||
- **Дискотека Авария - Новогодняя** (codenamed **DiscoKapot**)
|
||||
|
||||
## Configuration
|
||||
|
||||
Configuration integrates with [`LethalConfig`] mod.
|
||||
|
|
@ -52,6 +58,8 @@ Any player can change the following personal preferences locally.
|
|||
|
||||
See also [mod's release thread](https://discord.com/channels/1168655651455639582/1433881654866477318) at [Lethal Company Modding](https://discord.gg/XeyYqRdRGC) Discord server (in case the invite link expires, there should be a fresh one at [lethal.wiki](https://lethal.wiki/)).
|
||||
|
||||
Check out my other mod, [HookahPlace ship 'furniture'](https://thunderstore.io/c/lethal-company/p/Ratijas/HookahPlace/)!
|
||||
|
||||
---
|
||||
|
||||
1. Actually not limited to Inverse teleporter or Titan.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,15 @@
|
|||
"version": "4.4.2",
|
||||
"commands": [
|
||||
"netcode-patch"
|
||||
]
|
||||
],
|
||||
"rollForward": false
|
||||
},
|
||||
"tcli": {
|
||||
"version": "0.2.4",
|
||||
"commands": [
|
||||
"tcli"
|
||||
],
|
||||
"rollForward": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "MuzikaGromche",
|
||||
"version_number": "1337.9001.2",
|
||||
"version_number": "1337.9001.4",
|
||||
"author": "Ratijas",
|
||||
"description": "Add some content to your inverse teleporter experience on Titan!",
|
||||
"website_url": "https://git.vilunov.me/ratijas/muzika-gromche",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
# - set token variable from .env file
|
||||
# - dotnet tool restore
|
||||
# - dotnet tcli publish --file dist/MuzikaGromche-Release.zip
|
||||
|
||||
[config]
|
||||
schemaVersion = "0.0.1"
|
||||
|
||||
[package]
|
||||
namespace = "Ratijas"
|
||||
name = "MuzikaGromche"
|
||||
description = "Add some content to your inverse teleporter experience on Titan!"
|
||||
websiteUrl = "https://git.vilunov.me/ratijas/muzika-gromche"
|
||||
containsNsfwContent = false
|
||||
|
||||
[publish]
|
||||
repository = "https://thunderstore.io"
|
||||
communities = [ "lethal-company" ]
|
||||
|
||||
[publish.categories]
|
||||
lethal-company = [ "mods", "audio", "bepinex", "clientside", "serverside", "monsters" ]
|
||||
Loading…
Reference in New Issue