forked from nikita/muzika-gromche
Compare commits
No commits in common. "df796965f2b9f77a64d03955a5d3387bbf5ed675" and "8b2f4428bb97118b42d9c6e7c4dcd4482846dbdd" have entirely different histories.
df796965f2
...
8b2f4428bb
|
@ -1,5 +0,0 @@
|
||||||
[*.cs]
|
|
||||||
|
|
||||||
# IDE0290: Use primary constructor
|
|
||||||
# Primary constructors are far from perfect: they can't have readonly fields, while fields can be used anywhere in the class body.
|
|
||||||
csharp_style_prefer_primary_constructors = false
|
|
BIN
Assets/BeefLiverLoop.ogg (Stored with Git LFS)
BIN
Assets/BeefLiverLoop.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/BeefLiverStart.ogg (Stored with Git LFS)
BIN
Assets/BeefLiverStart.ogg (Stored with Git LFS)
Binary file not shown.
|
@ -1,11 +1,5 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## MuzikaGromche 1337.420.69 - It's All DiscoNnected Edition
|
|
||||||
|
|
||||||
- Fix harmless but annoying errors in BepInEx console output.
|
|
||||||
- Improve smoothness of color animations.
|
|
||||||
- Add a new track.
|
|
||||||
|
|
||||||
## MuzikaGromche 1337.69.420 - It's All Connected Edition
|
## MuzikaGromche 1337.69.420 - It's All Connected Edition
|
||||||
|
|
||||||
- Fix certain object hanging around after being disabled.
|
- Fix certain object hanging around after being disabled.
|
||||||
|
|
|
@ -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.69</Version>
|
<Version>1337.69.420</Version>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
@ -41,7 +41,6 @@
|
||||||
<!--
|
<!--
|
||||||
Publicize internal methods, so we could generate config entries for tracks at runtime instead
|
Publicize internal methods, so we could generate config entries for tracks at runtime instead
|
||||||
of generating code at compile time. See https://github.com/lc-sigurd/CSync/issues/11
|
of generating code at compile time. See https://github.com/lc-sigurd/CSync/issues/11
|
||||||
It is an optional dependency now, but there is no sane way to mark it as such.
|
|
||||||
-->
|
-->
|
||||||
<PackageReference Include="Sigurd.BepInEx.CSync" Version="5.0.1" Publicize="true" PrivateAssets="all" Private="false" />
|
<PackageReference Include="Sigurd.BepInEx.CSync" Version="5.0.1" Publicize="true" PrivateAssets="all" Private="false" />
|
||||||
<PackageReference Include="AinaVT-LethalConfig" Version="1.4.6" PrivateAssets="all" Private="false" />
|
<PackageReference Include="AinaVT-LethalConfig" Version="1.4.6" PrivateAssets="all" Private="false" />
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
using BepInEx;
|
using BepInEx;
|
||||||
using BepInEx.Configuration;
|
using BepInEx.Configuration;
|
||||||
|
using CSync.Extensions;
|
||||||
|
using CSync.Lib;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
using LethalConfig;
|
using LethalConfig;
|
||||||
using LethalConfig.ConfigItems;
|
using LethalConfig.ConfigItems;
|
||||||
|
@ -19,17 +21,10 @@ using Unity.Netcode;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.Networking;
|
using UnityEngine.Networking;
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
using CSync.Extensions;
|
|
||||||
using CSync.Lib;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace MuzikaGromche
|
namespace MuzikaGromche
|
||||||
{
|
{
|
||||||
[BepInPlugin(PluginInfo.PLUGIN_GUID, PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)]
|
[BepInPlugin(PluginInfo.PLUGIN_GUID, PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)]
|
||||||
#if DEBUG
|
|
||||||
[BepInDependency("com.sigurd.csync", "5.0.1")]
|
[BepInDependency("com.sigurd.csync", "5.0.1")]
|
||||||
#endif
|
|
||||||
[BepInDependency("ainavt.lc.lethalconfig", "1.4.6")]
|
[BepInDependency("ainavt.lc.lethalconfig", "1.4.6")]
|
||||||
[BepInDependency("watergun.v72lightfix", BepInDependency.DependencyFlags.SoftDependency)]
|
[BepInDependency("watergun.v72lightfix", BepInDependency.DependencyFlags.SoftDependency)]
|
||||||
[BepInDependency("BMX.LobbyCompatibility", BepInDependency.DependencyFlags.HardDependency)]
|
[BepInDependency("BMX.LobbyCompatibility", BepInDependency.DependencyFlags.HardDependency)]
|
||||||
|
@ -475,27 +470,6 @@ namespace MuzikaGromche
|
||||||
FlickerLightsTimeSeries = [-120.5f, -105, -89, -8, 44, 45],
|
FlickerLightsTimeSeries = [-120.5f, -105, -89, -8, 44, 45],
|
||||||
Lyrics = [],
|
Lyrics = [],
|
||||||
},
|
},
|
||||||
new Track
|
|
||||||
{
|
|
||||||
Name = "BeefLiver",
|
|
||||||
AudioType = AudioType.OGGVORBIS,
|
|
||||||
Language = Language.ENGLISH,
|
|
||||||
WindUpTimer = 39.35f,
|
|
||||||
Bars = 12,
|
|
||||||
BeatsOffset = 0.2f,
|
|
||||||
ColorTransitionIn = 0.4f,
|
|
||||||
ColorTransitionOut = 0.4f,
|
|
||||||
ColorTransitionEasing = Easing.OutExpo,
|
|
||||||
Palette = Palette.Parse([
|
|
||||||
"#FFEBEB", "#FFEBEB", "#445782", "#EBA602",
|
|
||||||
"#5EEBB9", "#8EE3DC", "#A23045", "#262222",
|
|
||||||
]),
|
|
||||||
LoopOffset = 0,
|
|
||||||
FadeOutBeat = -3,
|
|
||||||
FadeOutDuration = 3,
|
|
||||||
FlickerLightsTimeSeries = [-48, -40, -4.5f, 44],
|
|
||||||
Lyrics = [],
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
public static Track ChooseTrack()
|
public static Track ChooseTrack()
|
||||||
|
@ -555,9 +529,6 @@ namespace MuzikaGromche
|
||||||
|
|
||||||
void Awake()
|
void Awake()
|
||||||
{
|
{
|
||||||
// Sort in place by name
|
|
||||||
Array.Sort(Tracks.Select(track => track.Name).ToArray(), Tracks);
|
|
||||||
|
|
||||||
string dir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
string dir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||||
UnityWebRequest[] requests = new UnityWebRequest[Tracks.Length * 2];
|
UnityWebRequest[] requests = new UnityWebRequest[Tracks.Length * 2];
|
||||||
for (int i = 0; i < Tracks.Length; i++)
|
for (int i = 0; i < Tracks.Length; i++)
|
||||||
|
@ -578,9 +549,6 @@ namespace MuzikaGromche
|
||||||
Track track = Tracks[i];
|
Track track = Tracks[i];
|
||||||
track.LoadedStart = DownloadHandlerAudioClip.GetContent(requests[i * 2]);
|
track.LoadedStart = DownloadHandlerAudioClip.GetContent(requests[i * 2]);
|
||||||
track.LoadedLoop = DownloadHandlerAudioClip.GetContent(requests[i * 2 + 1]);
|
track.LoadedLoop = DownloadHandlerAudioClip.GetContent(requests[i * 2 + 1]);
|
||||||
#if DEBUG
|
|
||||||
Debug.Log($"{nameof(MuzikaGromche)} Track {track.Name} {track.LoadedStart.length:N4} {track.LoadedLoop.length:N4}");
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
Config = new Config(base.Config);
|
Config = new Config(base.Config);
|
||||||
DiscoBallManager.Load();
|
DiscoBallManager.Load();
|
||||||
|
@ -857,15 +825,11 @@ namespace MuzikaGromche
|
||||||
// Beat relative to the popup. Always less than LoopBeats. When not IsLooping, can be unbounded negative.
|
// Beat relative to the popup. Always less than LoopBeats. When not IsLooping, can be unbounded negative.
|
||||||
public readonly float Beat;
|
public readonly float Beat;
|
||||||
|
|
||||||
// Additional metadata describing whether this timestamp is based on extrapolated source data.
|
public BeatTimestamp(int loopBeats, bool isLooping, float beat)
|
||||||
public readonly bool IsExtrapolated;
|
|
||||||
|
|
||||||
public BeatTimestamp(int loopBeats, bool isLooping, float beat, bool isExtrapolated)
|
|
||||||
{
|
{
|
||||||
LoopBeats = loopBeats;
|
LoopBeats = loopBeats;
|
||||||
IsLooping = isLooping || beat >= HalfLoopBeats;
|
IsLooping = isLooping || beat >= HalfLoopBeats;
|
||||||
Beat = isLooping || beat >= LoopBeats ? Mod.Positive(beat, LoopBeats) : beat;
|
Beat = isLooping || beat >= LoopBeats ? Mod.Positive(beat, LoopBeats) : beat;
|
||||||
IsExtrapolated = isExtrapolated;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BeatTimestamp operator +(BeatTimestamp self, float delta)
|
public static BeatTimestamp operator +(BeatTimestamp self, float delta)
|
||||||
|
@ -878,7 +842,7 @@ namespace MuzikaGromche
|
||||||
// Shouldn't be needed though, as deltas are usually short enough.
|
// Shouldn't be needed though, as deltas are usually short enough.
|
||||||
// But don't try to chain many short negative deltas!
|
// But don't try to chain many short negative deltas!
|
||||||
}
|
}
|
||||||
return new BeatTimestamp(self.LoopBeats, self.IsLooping, self.Beat + delta, self.IsExtrapolated);
|
return new BeatTimestamp(self.LoopBeats, self.IsLooping, self.Beat + delta);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BeatTimestamp operator -(BeatTimestamp self, float delta)
|
public static BeatTimestamp operator -(BeatTimestamp self, float delta)
|
||||||
|
@ -890,12 +854,12 @@ namespace MuzikaGromche
|
||||||
{
|
{
|
||||||
// There is no way it wraps or affects IsLooping state
|
// There is no way it wraps or affects IsLooping state
|
||||||
var beat = Mathf.Floor(Beat);
|
var beat = Mathf.Floor(Beat);
|
||||||
return new BeatTimestamp(LoopBeats, IsLooping, beat, IsExtrapolated);
|
return new BeatTimestamp(LoopBeats, IsLooping, beat);
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly override string ToString()
|
public readonly override string ToString()
|
||||||
{
|
{
|
||||||
return $"{nameof(BeatTimestamp)}({(IsLooping ? 'Y' : 'n')}{(IsExtrapolated ? 'E' : '_')} {Beat:N4}/{LoopBeats})";
|
return $"{nameof(BeatTimestamp)}({(IsLooping ? 'Y' : 'n')} {Beat:N4}/{LoopBeats})";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -908,16 +872,13 @@ namespace MuzikaGromche
|
||||||
public readonly float BeatFromExclusive;
|
public readonly float BeatFromExclusive;
|
||||||
// Closed upper bound
|
// Closed upper bound
|
||||||
public readonly float BeatToInclusive;
|
public readonly float BeatToInclusive;
|
||||||
// Additional metadata describing whether this timestamp is based on extrapolated source data.
|
|
||||||
public readonly bool IsExtrapolated;
|
|
||||||
|
|
||||||
public BeatTimeSpan(int loopBeats, bool isLooping, float beatFromExclusive, float beatToInclusive, bool isExtrapolated)
|
public BeatTimeSpan(int loopBeats, bool isLooping, float beatFromExclusive, float beatToInclusive)
|
||||||
{
|
{
|
||||||
LoopBeats = loopBeats;
|
LoopBeats = loopBeats;
|
||||||
IsLooping = isLooping || beatToInclusive >= HalfLoopBeats;
|
IsLooping = isLooping || beatToInclusive >= HalfLoopBeats;
|
||||||
BeatFromExclusive = wrap(beatFromExclusive);
|
BeatFromExclusive = wrap(beatFromExclusive);
|
||||||
BeatToInclusive = wrap(beatToInclusive);
|
BeatToInclusive = wrap(beatToInclusive);
|
||||||
IsExtrapolated = isExtrapolated;
|
|
||||||
|
|
||||||
float wrap(float beat)
|
float wrap(float beat)
|
||||||
{
|
{
|
||||||
|
@ -927,20 +888,20 @@ namespace MuzikaGromche
|
||||||
|
|
||||||
public static BeatTimeSpan Between(BeatTimestamp timestampFromExclusive, BeatTimestamp timestampToInclusive)
|
public static BeatTimeSpan Between(BeatTimestamp timestampFromExclusive, BeatTimestamp timestampToInclusive)
|
||||||
{
|
{
|
||||||
var isExtrapolated = timestampFromExclusive.IsExtrapolated || timestampToInclusive.IsExtrapolated;
|
return new BeatTimeSpan(timestampToInclusive.LoopBeats, timestampToInclusive.IsLooping, timestampFromExclusive.Beat, timestampToInclusive.Beat);
|
||||||
return new BeatTimeSpan(timestampToInclusive.LoopBeats, timestampToInclusive.IsLooping, timestampFromExclusive.Beat, timestampToInclusive.Beat, isExtrapolated);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static BeatTimeSpan Between(float beatFromExclusive, BeatTimestamp timestampToInclusive)
|
public static BeatTimeSpan Between(float beatFromExclusive, BeatTimestamp timestampToInclusive)
|
||||||
{
|
{
|
||||||
return new BeatTimeSpan(timestampToInclusive.LoopBeats, timestampToInclusive.IsLooping, beatFromExclusive, timestampToInclusive.Beat, timestampToInclusive.IsExtrapolated);
|
return new BeatTimeSpan(timestampToInclusive.LoopBeats, timestampToInclusive.IsLooping, beatFromExclusive, timestampToInclusive.Beat);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BeatTimeSpan Empty = new();
|
public static BeatTimeSpan Empty = new();
|
||||||
|
|
||||||
public readonly BeatTimestamp ToTimestamp()
|
public readonly BeatTimestamp ToTimestamp()
|
||||||
{
|
{
|
||||||
return new(LoopBeats, IsLooping, BeatToInclusive, IsExtrapolated);
|
return new(LoopBeats, IsLooping, BeatToInclusive);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The beat will not be wrapped.
|
// The beat will not be wrapped.
|
||||||
|
@ -962,7 +923,7 @@ namespace MuzikaGromche
|
||||||
// before wrapping (happens earlier) and after wrapping (happens later).
|
// before wrapping (happens earlier) and after wrapping (happens later).
|
||||||
|
|
||||||
// Check the "happens later" part first.
|
// Check the "happens later" part first.
|
||||||
var laterSpan = new BeatTimeSpan(LoopBeats, isLooping: false, beatFromExclusive: /* epsilon to make zero inclusive */ -0.001f, beatToInclusive: BeatToInclusive, IsExtrapolated);
|
var laterSpan = new BeatTimeSpan(LoopBeats, isLooping: false, beatFromExclusive: /* epsilon to make zero inclusive */ -0.001f, beatToInclusive: BeatToInclusive);
|
||||||
var laterIndex = laterSpan.GetLastIndex(timeSeries);
|
var laterIndex = laterSpan.GetLastIndex(timeSeries);
|
||||||
if (laterIndex != null)
|
if (laterIndex != null)
|
||||||
{
|
{
|
||||||
|
@ -1048,144 +1009,94 @@ namespace MuzikaGromche
|
||||||
|
|
||||||
public readonly override string ToString()
|
public readonly override string ToString()
|
||||||
{
|
{
|
||||||
return $"{nameof(BeatTimeSpan)}({(IsLooping ? 'Y' : 'n')}{(IsExtrapolated ? 'E' : '_')}, {BeatFromExclusive:N4}..{BeatToInclusive:N4}/{LoopBeats}{(IsEmpty() ? " Empty!" : "")})";
|
return $"{nameof(BeatTimeSpan)}({(IsLooping ? 'Y' : 'n')}, {BeatFromExclusive:N4}..{BeatToInclusive:N4}/{LoopBeats}{(IsEmpty() ? " Empty!" : "")})";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ExtrapolatedAudioSourceState
|
class BeatTimeState
|
||||||
{
|
{
|
||||||
// AudioSource.isPlaying
|
// The object is newly created, the Start audio began to play but its time hasn't adjanced from 0.0f yet.
|
||||||
public bool IsPlaying { get; private set; }
|
private bool hasStarted = false;
|
||||||
|
|
||||||
// AudioSource.time, possibly extrapolated
|
// The time span after Zero state (when the Start audio has advanced from 0.0f) but before the WindUpTimer+Loop/2.
|
||||||
public float Time => ExtrapolatedTime;
|
private bool windUpOffsetIsLooping = false;
|
||||||
|
|
||||||
// The object is newly created, the AudioSource began to play (possibly delayed) but its time hasn't advanced from 0.0f yet.
|
// The time span after Zero state (when the Start audio has advanced from 0.0f) but before the WindUpTimer+LoopOffset+Loop/2.
|
||||||
// Time can not be extrapolated when HasStarted is false.
|
private bool loopOffsetIsLooping = false;
|
||||||
public bool HasStarted { get; private set; } = false;
|
|
||||||
|
|
||||||
public bool IsExtrapolated => LastKnownNonExtrapolatedTime != ExtrapolatedTime;
|
private bool windUpZeroBeatEventTriggered = false;
|
||||||
|
|
||||||
private float ExtrapolatedTime = 0f;
|
private readonly Track track;
|
||||||
|
|
||||||
private float LastKnownNonExtrapolatedTime = 0f;
|
private float loopOffsetBeat = float.NegativeInfinity;
|
||||||
|
|
||||||
// Any wall clock based measurements of when this state was recorded
|
private static System.Random lyricsRandom = null!;
|
||||||
private float LastKnownRealtime = 0f;
|
|
||||||
|
|
||||||
private const float MaxExtrapolationInterval = 0.5f;
|
private int lyricsRandomPerLoop;
|
||||||
|
|
||||||
public void Update(AudioSource audioSource, float realtime)
|
public BeatTimeState(Track track)
|
||||||
{
|
{
|
||||||
IsPlaying = audioSource.isPlaying;
|
if (lyricsRandom == null)
|
||||||
HasStarted |= audioSource.time != 0f;
|
|
||||||
|
|
||||||
if (LastKnownNonExtrapolatedTime != audioSource.time)
|
|
||||||
{
|
{
|
||||||
LastKnownRealtime = realtime;
|
lyricsRandom = new System.Random(RoundManager.Instance.playersManager.randomMapSeed + 1337);
|
||||||
LastKnownNonExtrapolatedTime = ExtrapolatedTime = audioSource.time;
|
lyricsRandomPerLoop = lyricsRandom.Next();
|
||||||
}
|
}
|
||||||
// Frames are rendering faster than AudioSource updates its playback time state
|
this.track = track;
|
||||||
else if (IsPlaying && HasStarted && Config.ExtrapolateTime)
|
}
|
||||||
|
|
||||||
|
public List<BaseEvent> Update(AudioSource start, AudioSource loop)
|
||||||
{
|
{
|
||||||
|
hasStarted |= start.time != 0;
|
||||||
|
if (hasStarted)
|
||||||
|
{
|
||||||
|
var loopTimestamp = UpdateStateForLoopOffset(start, loop);
|
||||||
|
var loopOffsetSpan = BeatTimeSpan.Between(loopOffsetBeat, loopTimestamp);
|
||||||
|
|
||||||
|
// Do not go back in time
|
||||||
|
if (!loopOffsetSpan.IsEmpty())
|
||||||
|
{
|
||||||
|
if (loopOffsetSpan.BeatFromExclusive > loopOffsetSpan.BeatToInclusive)
|
||||||
|
{
|
||||||
|
lyricsRandomPerLoop = lyricsRandom.Next();
|
||||||
|
}
|
||||||
|
|
||||||
|
var windUpOffsetTimestamp = UpdateStateForWindUpOffset(start, loop);
|
||||||
|
loopOffsetBeat = loopTimestamp.Beat;
|
||||||
|
var events = GetEvents(loopOffsetSpan, windUpOffsetTimestamp);
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
Debug.Assert(LastKnownNonExtrapolatedTime == audioSource.time); // implied
|
Debug.Log($"{nameof(MuzikaGromche)} looping? {(loopOffsetIsLooping ? 'X' : '_')}{(windUpOffsetIsLooping ? 'X' : '_')} Loop={loopOffsetSpan} WindUp={windUpOffsetTimestamp} events={string.Join(",", events)}");
|
||||||
#endif
|
#endif
|
||||||
var deltaTime = realtime - LastKnownRealtime;
|
return events;
|
||||||
if (0 < deltaTime && deltaTime < MaxExtrapolationInterval)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Events other than colors start rotating at 0=WindUpTimer+LoopOffset.
|
||||||
|
private BeatTimestamp UpdateStateForLoopOffset(AudioSource start, AudioSource loop)
|
||||||
{
|
{
|
||||||
ExtrapolatedTime = LastKnownNonExtrapolatedTime + deltaTime;
|
var offset = BaseOffset() + track.LoopOffsetInSeconds;
|
||||||
}
|
var timestamp = GetTimestampRelativeToGivenOffset(start, loop, offset, loopOffsetIsLooping);
|
||||||
}
|
loopOffsetIsLooping |= timestamp.IsLooping;
|
||||||
|
return timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Finish()
|
// Colors start rotating at 0=WindUpTimer
|
||||||
|
private BeatTimestamp UpdateStateForWindUpOffset(AudioSource start, AudioSource loop)
|
||||||
{
|
{
|
||||||
IsPlaying = false;
|
var offset = BaseOffset();
|
||||||
|
var timestamp = GetTimestampRelativeToGivenOffset(start, loop, offset, windUpOffsetIsLooping);
|
||||||
|
windUpOffsetIsLooping |= timestamp.IsLooping;
|
||||||
|
return timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
private float BaseOffset()
|
||||||
{
|
{
|
||||||
return $"{nameof(ExtrapolatedAudioSourceState)}({(IsPlaying ? 'P' : '_')}{(HasStarted ? 'S' : '0')} "
|
return Config.AudioOffset.Value + track.BeatsOffsetInSeconds + track.WindUpTimer;
|
||||||
+ (IsExtrapolated
|
|
||||||
? $"{LastKnownRealtime:N4}, {LastKnownNonExtrapolatedTime:N4} => {ExtrapolatedTime:N4}"
|
|
||||||
: $"{LastKnownRealtime:N4}, {LastKnownNonExtrapolatedTime:N4}"
|
|
||||||
) + ")";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class JesterAudioSourcesState
|
BeatTimestamp GetTimestampRelativeToGivenOffset(AudioSource start, AudioSource loop, float offset, bool isLooping)
|
||||||
{
|
|
||||||
private readonly float StartClipLength;
|
|
||||||
|
|
||||||
// Neither start.isPlaying or loop.isPlaying are reliable indicators of which track is actually playing right now:
|
|
||||||
// start.isPlaying would be true during the loop when Jester chases a player,
|
|
||||||
// loop.isPlaying would be true when it is played delyaed but hasn't actually started playing yet.
|
|
||||||
private readonly ExtrapolatedAudioSourceState Start = new();
|
|
||||||
|
|
||||||
private readonly ExtrapolatedAudioSourceState Loop = new();
|
|
||||||
|
|
||||||
// If true, use Start state as a reference, otherwise use Loop.
|
|
||||||
private bool ReferenceIsStart = true;
|
|
||||||
|
|
||||||
public bool HasStarted => Start.HasStarted;
|
|
||||||
|
|
||||||
public bool IsExtrapolated => ReferenceIsStart ? Start.IsExtrapolated : Loop.IsExtrapolated;
|
|
||||||
|
|
||||||
// Time from the start of the start clip. It wraps when the loop AudioSource loops:
|
|
||||||
// [...start...][...loop...]
|
|
||||||
// ^ |
|
|
||||||
// `----------'
|
|
||||||
public float Time => ReferenceIsStart
|
|
||||||
? Start.Time
|
|
||||||
: StartClipLength + Loop.Time;
|
|
||||||
|
|
||||||
public JesterAudioSourcesState(float startClipLength)
|
|
||||||
{
|
|
||||||
StartClipLength = startClipLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Update(AudioSource start, AudioSource loop, float realtime)
|
|
||||||
{
|
|
||||||
// It doesn't make sense to update start state after loop has started (because start.isPlaying occasionally becomes true).
|
|
||||||
// But always makes sense to update loop, so we can check if it has actually started.
|
|
||||||
Loop.Update(loop, realtime);
|
|
||||||
|
|
||||||
if (!Loop.HasStarted)
|
|
||||||
{
|
|
||||||
#if DEBUG
|
|
||||||
Debug.Assert(ReferenceIsStart);
|
|
||||||
#endif
|
|
||||||
Start.Update(start, realtime);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ReferenceIsStart = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This class tracks looping state of the playback, so that the timestamps can be correctly wrapped only when needed.
|
|
||||||
// [... ...time... ...]
|
|
||||||
// ^ |
|
|
||||||
// `---|---' loop
|
|
||||||
// ^ IsLooping becomes true and stays true forever.
|
|
||||||
class AudioLoopingState
|
|
||||||
{
|
|
||||||
public bool IsLooping { get; private set; } = false;
|
|
||||||
|
|
||||||
private readonly float StartOfLoop;
|
|
||||||
private readonly float LoopLength;
|
|
||||||
private readonly int Beats;
|
|
||||||
|
|
||||||
public AudioLoopingState(float startOfLoop, float loopLength, int beats)
|
|
||||||
{
|
|
||||||
StartOfLoop = startOfLoop;
|
|
||||||
LoopLength = loopLength;
|
|
||||||
Beats = beats;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BeatTimestamp Update(float time, bool isExtrapolated, float additionalOffset)
|
|
||||||
{
|
{
|
||||||
// If popped, calculate which beat the music is currently at.
|
// If popped, calculate which beat the music is currently at.
|
||||||
// In order to do that we should choose one of two strategies:
|
// In order to do that we should choose one of two strategies:
|
||||||
|
@ -1198,114 +1109,42 @@ namespace MuzikaGromche
|
||||||
// NOTE 2: There is a weird state when Jester has popped and chases a player:
|
// NOTE 2: There is a weird state when Jester has popped and chases a player:
|
||||||
// Start/farAudio isPlaying is true but stays exactly at zero time, so we need to ignore that.
|
// Start/farAudio isPlaying is true but stays exactly at zero time, so we need to ignore that.
|
||||||
|
|
||||||
var offset = StartOfLoop + additionalOffset;
|
var timeFromTheVeryStart = start.isPlaying && start.time != 0f
|
||||||
|
// [1] Start source is still playing
|
||||||
|
? start.time
|
||||||
|
// [2] Start source has finished
|
||||||
|
: track.LoadedStart.length + loop.time;
|
||||||
|
|
||||||
float timeSinceStartOfLoop = time - offset;
|
float adjustedTimeFromOffset = timeFromTheVeryStart - offset;
|
||||||
|
|
||||||
var adjustedTimeNormalized = timeSinceStartOfLoop / LoopLength;
|
var adjustedTimeNormalized = adjustedTimeFromOffset / track.LoadedLoop.length;
|
||||||
|
|
||||||
var beat = adjustedTimeNormalized * Beats;
|
var beat = adjustedTimeNormalized * track.Beats;
|
||||||
|
|
||||||
// Let it infer the isLooping flag from the beat
|
// Let it infer the isLooping flag from the beat
|
||||||
var timestamp = new BeatTimestamp(Beats, IsLooping, beat, isExtrapolated);
|
var timestamp = new BeatTimestamp(track.Beats, isLooping, beat);
|
||||||
|
|
||||||
IsLooping |= timestamp.IsLooping;
|
|
||||||
|
|
||||||
#if DEBUG && false
|
#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}",
|
var color = ColorFromPaletteAtTimestamp(timestamp);
|
||||||
|
Debug.LogFormat("{0} t={1,10:N4} d={2,7:N4} Start[{3}{4,8:N4} zero? {5}] Loop[{6}{7,8:N4}] norm={8,6:N4} beat={9,7:N4} color={10}",
|
||||||
nameof(MuzikaGromche),
|
nameof(MuzikaGromche),
|
||||||
Time.realtimeSinceStartup, Time.deltaTime,
|
Time.realtimeSinceStartup, Time.deltaTime,
|
||||||
isExtrapolated ? 'E' : '_', time,
|
(start.isPlaying ? '+' : ' '), start.time, (start.time == 0f ? 'Y' : 'n'),
|
||||||
adjustedTimeNormalized, beat);
|
(loop.isPlaying ? '+' : ' '), loop.time,
|
||||||
|
adjustedTimeNormalized, beat, color);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return timestamp;
|
return timestamp;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
class BeatTimeState
|
|
||||||
{
|
|
||||||
private readonly Track track;
|
|
||||||
|
|
||||||
private readonly JesterAudioSourcesState AudioState;
|
|
||||||
|
|
||||||
// Colors wrap from WindUpTimer
|
|
||||||
private readonly AudioLoopingState WindUpLoopingState;
|
|
||||||
|
|
||||||
// Events other than colors wrap from WindUpTimer+LoopOffset.
|
|
||||||
private readonly AudioLoopingState LoopLoopingState;
|
|
||||||
|
|
||||||
private float LastKnownLoopOffsetBeat = float.NegativeInfinity;
|
|
||||||
|
|
||||||
private static System.Random LyricsRandom = null!;
|
|
||||||
|
|
||||||
private int LyricsRandomPerLoop;
|
|
||||||
|
|
||||||
private bool WindUpZeroBeatEventTriggered = false;
|
|
||||||
|
|
||||||
public BeatTimeState(Track track)
|
|
||||||
{
|
|
||||||
if (LyricsRandom == null)
|
|
||||||
{
|
|
||||||
LyricsRandom = new System.Random(RoundManager.Instance.playersManager.randomMapSeed + 1337);
|
|
||||||
LyricsRandomPerLoop = LyricsRandom.Next();
|
|
||||||
}
|
|
||||||
this.track = track;
|
|
||||||
AudioState = new(track.LoadedStart.length);
|
|
||||||
WindUpLoopingState = new(track.WindUpTimer, track.LoadedLoop.length, track.Beats);
|
|
||||||
LoopLoopingState = new(track.WindUpTimer + track.LoopOffsetInSeconds, track.LoadedLoop.length, track.Beats);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<BaseEvent> Update(AudioSource start, AudioSource loop)
|
|
||||||
{
|
|
||||||
var time = Time.realtimeSinceStartup;
|
|
||||||
AudioState.Update(start, loop, time);
|
|
||||||
|
|
||||||
if (AudioState.HasStarted)
|
|
||||||
{
|
|
||||||
var loopTimestamp = Update(LoopLoopingState);
|
|
||||||
var loopOffsetSpan = BeatTimeSpan.Between(LastKnownLoopOffsetBeat, loopTimestamp);
|
|
||||||
|
|
||||||
// Do not go back in time
|
|
||||||
if (!loopOffsetSpan.IsEmpty())
|
|
||||||
{
|
|
||||||
if (loopOffsetSpan.BeatFromExclusive > loopOffsetSpan.BeatToInclusive)
|
|
||||||
{
|
|
||||||
LyricsRandomPerLoop = LyricsRandom.Next();
|
|
||||||
}
|
|
||||||
|
|
||||||
var windUpOffsetTimestamp = Update(WindUpLoopingState);
|
|
||||||
LastKnownLoopOffsetBeat = loopTimestamp.Beat;
|
|
||||||
var events = GetEvents(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)}");
|
|
||||||
#endif
|
|
||||||
return events;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
private BeatTimestamp Update(AudioLoopingState loopingState)
|
|
||||||
{
|
|
||||||
return loopingState.Update(AudioState.Time, AudioState.IsExtrapolated, AdditionalOffset());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Timings that may be changes through config
|
|
||||||
private float AdditionalOffset()
|
|
||||||
{
|
|
||||||
return Config.AudioOffset.Value + track.BeatsOffsetInSeconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<BaseEvent> GetEvents(BeatTimeSpan loopOffsetSpan, BeatTimestamp windUpOffsetTimestamp)
|
private List<BaseEvent> GetEvents(BeatTimeSpan loopOffsetSpan, BeatTimestamp windUpOffsetTimestamp)
|
||||||
{
|
{
|
||||||
List<BaseEvent> events = [];
|
List<BaseEvent> events = [];
|
||||||
|
|
||||||
if (windUpOffsetTimestamp.Beat >= 0f && !WindUpZeroBeatEventTriggered)
|
if (windUpOffsetTimestamp.Beat >= 0f && !windUpZeroBeatEventTriggered)
|
||||||
{
|
{
|
||||||
events.Add(new WindUpZeroBeatEvent());
|
events.Add(new WindUpZeroBeatEvent());
|
||||||
WindUpZeroBeatEventTriggered = true;
|
windUpZeroBeatEventTriggered = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GetColorEvent(loopOffsetSpan, windUpOffsetTimestamp) is { } colorEvent)
|
if (GetColorEvent(loopOffsetSpan, windUpOffsetTimestamp) is { } colorEvent)
|
||||||
|
@ -1327,7 +1166,7 @@ namespace MuzikaGromche
|
||||||
{
|
{
|
||||||
var line = track.LyricsLines[i];
|
var line = track.LyricsLines[i];
|
||||||
var alternatives = line.Split('\t');
|
var alternatives = line.Split('\t');
|
||||||
var randomIndex = LyricsRandomPerLoop % alternatives.Length;
|
var randomIndex = lyricsRandomPerLoop % alternatives.Length;
|
||||||
var alternative = alternatives[randomIndex];
|
var alternative = alternatives[randomIndex];
|
||||||
if (alternative != "")
|
if (alternative != "")
|
||||||
{
|
{
|
||||||
|
@ -1594,7 +1433,6 @@ namespace MuzikaGromche
|
||||||
readonly public int TotalWeights { get; }
|
readonly public int TotalWeights { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
static class SyncedEntryExtensions
|
static class SyncedEntryExtensions
|
||||||
{
|
{
|
||||||
// Update local values on clients. Even though the clients couldn't
|
// Update local values on clients. Even though the clients couldn't
|
||||||
|
@ -1607,12 +1445,8 @@ namespace MuzikaGromche
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
class Config
|
class Config : SyncedConfig2<Config>
|
||||||
#if DEBUG
|
|
||||||
: SyncedConfig2<Config>
|
|
||||||
#endif
|
|
||||||
{
|
{
|
||||||
public static ConfigEntry<bool> DisplayLyrics { get; private set; } = null!;
|
public static ConfigEntry<bool> DisplayLyrics { get; private set; } = null!;
|
||||||
|
|
||||||
|
@ -1622,7 +1456,6 @@ namespace MuzikaGromche
|
||||||
|
|
||||||
public static ConfigEntry<bool> OverrideSpawnRates { get; private set; } = null!;
|
public static ConfigEntry<bool> OverrideSpawnRates { get; private set; } = null!;
|
||||||
|
|
||||||
public static bool ExtrapolateTime { get; private set; } = true;
|
|
||||||
public static bool ShouldSkipWindingPhase { get; private set; } = false;
|
public static bool ShouldSkipWindingPhase { get; private set; } = false;
|
||||||
|
|
||||||
public static Palette? PaletteOverride { get; private set; } = null;
|
public static Palette? PaletteOverride { get; private set; } = null;
|
||||||
|
@ -1636,10 +1469,7 @@ namespace MuzikaGromche
|
||||||
public static float? ColorTransitionOutOverride { get; private set; } = null;
|
public static float? ColorTransitionOutOverride { get; private set; } = null;
|
||||||
public static string? ColorTransitionEasingOverride { get; private set; } = null;
|
public static string? ColorTransitionEasingOverride { get; private set; } = null;
|
||||||
|
|
||||||
internal Config(ConfigFile configFile)
|
internal Config(ConfigFile configFile) : base(PluginInfo.PLUGIN_GUID)
|
||||||
#if DEBUG
|
|
||||||
: base(PluginInfo.PLUGIN_GUID)
|
|
||||||
#endif
|
|
||||||
{
|
{
|
||||||
DisplayLyrics = configFile.Bind("General", "Display Lyrics", true,
|
DisplayLyrics = configFile.Bind("General", "Display Lyrics", true,
|
||||||
new ConfigDescription("Display lyrics in the HUD tooltip when you hear the music."));
|
new ConfigDescription("Display lyrics in the HUD tooltip when you hear the music."));
|
||||||
|
@ -1659,7 +1489,6 @@ namespace MuzikaGromche
|
||||||
LethalConfigManager.AddConfigItem(new BoolCheckBoxConfigItem(OverrideSpawnRates, Default(new BoolCheckBoxOptions())));
|
LethalConfigManager.AddConfigItem(new BoolCheckBoxConfigItem(OverrideSpawnRates, Default(new BoolCheckBoxOptions())));
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
SetupEntriesForExtrapolation(configFile);
|
|
||||||
SetupEntriesToSkipWinding(configFile);
|
SetupEntriesToSkipWinding(configFile);
|
||||||
SetupEntriesForPaletteOverride(configFile);
|
SetupEntriesForPaletteOverride(configFile);
|
||||||
SetupEntriesForTimingsOverride(configFile);
|
SetupEntriesForTimingsOverride(configFile);
|
||||||
|
@ -1708,12 +1537,9 @@ namespace MuzikaGromche
|
||||||
LethalConfigManager.AddConfigItem(new IntSliderConfigItem(track.Weight, Default(new IntSliderOptions())));
|
LethalConfigManager.AddConfigItem(new IntSliderConfigItem(track.Weight, Default(new IntSliderOptions())));
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
ConfigManager.Register(this);
|
ConfigManager.Register(this);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
// HACK because CSync doesn't provide an API to register a list of config entries
|
// HACK because CSync doesn't provide an API to register a list of config entries
|
||||||
// See https://github.com/lc-sigurd/CSync/issues/11
|
// See https://github.com/lc-sigurd/CSync/issues/11
|
||||||
private void CSyncHackAddSyncedEntry(SyncedEntryBase entryBase)
|
private void CSyncHackAddSyncedEntry(SyncedEntryBase entryBase)
|
||||||
|
@ -1721,7 +1547,6 @@ namespace MuzikaGromche
|
||||||
// This is basically what ConfigFile.PopulateEntryContainer does
|
// This is basically what ConfigFile.PopulateEntryContainer does
|
||||||
EntryContainer.Add(entryBase.BoxedEntry.ToSyncedEntryIdentifier(), entryBase);
|
EntryContainer.Add(entryBase.BoxedEntry.ToSyncedEntryIdentifier(), entryBase);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
public static CanModifyResult CanModifyIfHost()
|
public static CanModifyResult CanModifyIfHost()
|
||||||
{
|
{
|
||||||
|
@ -1757,23 +1582,6 @@ namespace MuzikaGromche
|
||||||
return CanModifyResult.True();
|
return CanModifyResult.True();
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
private void SetupEntriesForExtrapolation(ConfigFile configFile)
|
|
||||||
{
|
|
||||||
var syncedEntry = configFile.BindSyncedEntry("General", "Extrapolate Audio Playback Time", true,
|
|
||||||
new ConfigDescription("AudioSource only updates its playback position about 20 times per second.\n\nUse extrapolation technique to predict playback time between updates for smoother color animations."));
|
|
||||||
LethalConfigManager.AddConfigItem(new BoolCheckBoxConfigItem(syncedEntry.Entry, Default(new BoolCheckBoxOptions())));
|
|
||||||
CSyncHackAddSyncedEntry(syncedEntry);
|
|
||||||
syncedEntry.Changed += (sender, args) => apply();
|
|
||||||
syncedEntry.SyncHostToLocal();
|
|
||||||
apply();
|
|
||||||
|
|
||||||
void apply()
|
|
||||||
{
|
|
||||||
ExtrapolateTime = syncedEntry.Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetupEntriesToSkipWinding(ConfigFile configFile)
|
private void SetupEntriesToSkipWinding(ConfigFile configFile)
|
||||||
{
|
{
|
||||||
var syncedEntry = configFile.BindSyncedEntry("General", "Skip Winding Phase", false,
|
var syncedEntry = configFile.BindSyncedEntry("General", "Skip Winding Phase", false,
|
||||||
|
@ -1885,7 +1693,7 @@ namespace MuzikaGromche
|
||||||
fadeOutBeatSyncedEntry = configFile.BindSyncedEntry(section, "Fade Out Beat", 0f,
|
fadeOutBeatSyncedEntry = configFile.BindSyncedEntry(section, "Fade Out Beat", 0f,
|
||||||
new ConfigDescription("The beat at which to start fading out", new AcceptableValueRange<float>(-1000f, 0)));
|
new ConfigDescription("The beat at which to start fading out", new AcceptableValueRange<float>(-1000f, 0)));
|
||||||
fadeOutDurationSyncedEntry = configFile.BindSyncedEntry(section, "Fade Out Duration", 0f,
|
fadeOutDurationSyncedEntry = configFile.BindSyncedEntry(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, 100)));
|
||||||
flickerLightsTimeSeriesSyncedEntry = configFile.BindSyncedEntry(section, "Flicker Lights Time Series", "",
|
flickerLightsTimeSeriesSyncedEntry = configFile.BindSyncedEntry(section, "Flicker Lights Time Series", "",
|
||||||
new ConfigDescription("Time series of beat offsets when to flicker the lights."));
|
new ConfigDescription("Time series of beat offsets when to flicker the lights."));
|
||||||
lyricsTimeSeriesSyncedEntry = configFile.BindSyncedEntry(section, "Lyrics Time Series", "",
|
lyricsTimeSeriesSyncedEntry = configFile.BindSyncedEntry(section, "Lyrics Time Series", "",
|
||||||
|
@ -1981,7 +1789,6 @@ namespace MuzikaGromche
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
private T Default<T>(T options) where T : BaseOptions
|
private T Default<T>(T options) where T : BaseOptions
|
||||||
{
|
{
|
||||||
|
@ -2021,30 +1828,15 @@ namespace MuzikaGromche
|
||||||
ChooseTrackDeferred();
|
ChooseTrackDeferred();
|
||||||
foreach (var track in Plugin.Tracks)
|
foreach (var track in Plugin.Tracks)
|
||||||
{
|
{
|
||||||
track.Weight.SettingChanged += ChooseTrackDeferredDelegate;
|
track.Weight.SettingChanged += (_, _) => ChooseTrackDeferred();
|
||||||
}
|
}
|
||||||
Config.SkipExplicitTracks.SettingChanged += ChooseTrackDeferredDelegate;
|
Config.SkipExplicitTracks.SettingChanged += (_, _) => ChooseTrackDeferred();
|
||||||
base.OnNetworkSpawn();
|
base.OnNetworkSpawn();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnNetworkDespawn()
|
|
||||||
{
|
|
||||||
foreach (var track in Plugin.Tracks)
|
|
||||||
{
|
|
||||||
track.Weight.SettingChanged -= ChooseTrackDeferredDelegate;
|
|
||||||
}
|
|
||||||
Config.SkipExplicitTracks.SettingChanged -= ChooseTrackDeferredDelegate;
|
|
||||||
base.OnNetworkDespawn();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Batch multiple weights changes in a single network RPC
|
// Batch multiple weights changes in a single network RPC
|
||||||
private Coroutine? DeferredCoroutine = null;
|
private Coroutine? DeferredCoroutine = null;
|
||||||
|
|
||||||
private void ChooseTrackDeferredDelegate(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
ChooseTrackDeferred();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ChooseTrackDeferred()
|
private void ChooseTrackDeferred()
|
||||||
{
|
{
|
||||||
if (DeferredCoroutine != null)
|
if (DeferredCoroutine != null)
|
||||||
|
@ -2176,7 +1968,7 @@ namespace MuzikaGromche
|
||||||
__instance.farAudio = __state.farAudio;
|
__instance.farAudio = __state.farAudio;
|
||||||
|
|
||||||
var time = __instance.farAudio.time;
|
var time = __instance.farAudio.time;
|
||||||
var delay = Plugin.CurrentTrack.LoadedStart.length - time;
|
var delay = Plugin.CurrentTrack!.LoadedStart.length - time;
|
||||||
|
|
||||||
// Override screamingSFX with Loop, delayed by the remaining time of the Start audio
|
// Override screamingSFX with Loop, delayed by the remaining time of the Start audio
|
||||||
__instance.creatureVoice.Stop();
|
__instance.creatureVoice.Stop();
|
||||||
|
@ -2189,9 +1981,9 @@ namespace MuzikaGromche
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manage the timeline: switch color of the lights according to the current playback/beat position.
|
// Manage the timeline: switch color of the lights according to the current playback/beat position.
|
||||||
if ((__instance.previousState == 1 || __instance.previousState == 2) && Plugin.BeatTimeState is { } beatTimeState)
|
if ((__instance.previousState == 1 || __instance.previousState == 2) && Plugin.BeatTimeState != null)
|
||||||
{
|
{
|
||||||
var events = beatTimeState.Update(start: __instance.farAudio, loop: __instance.creatureVoice);
|
var events = Plugin.BeatTimeState.Update(start: __instance.farAudio, loop: __instance.creatureVoice);
|
||||||
foreach (var ev in events)
|
foreach (var ev in events)
|
||||||
{
|
{
|
||||||
switch (ev)
|
switch (ev)
|
||||||
|
|
|
@ -9,7 +9,7 @@ To keep it a surprise, it is adviced that you do not read the detailed descripti
|
||||||
|
|
||||||
Muzika Gromche is compatible with *Almost Vanilla™* gameplay and [*High Quota Mindset*](https://youtu.be/18RUCgQldGg?t=2553). It slightly changes certain timers, so won't be compatible with leaderboards. If you are a streamer™, be aware that it does play *copyrighted content.*
|
Muzika Gromche is compatible with *Almost Vanilla™* gameplay and [*High Quota Mindset*](https://youtu.be/18RUCgQldGg?t=2553). It slightly changes certain timers, so won't be compatible with leaderboards. If you are a streamer™, be aware that it does play *copyrighted content.*
|
||||||
|
|
||||||
Muzika Gromche works with all Lethal Company versions from v72 all the way back to v40, and is likely to work on all future versions as long as dependencies ([`LethalConfig`] and [`LobbyCompatibility`]) are working.
|
Muzika Gromche works with all Lethal Company versions from v72 all the way back to v40, and is likely to work on all future versions as long as dependencies ([`CSync`] and [`LethalConfig`]) are working.
|
||||||
|
|
||||||
Speaking of dependencies, [`V70PoweredLights_Fix`] is not strictly required, but it doesn't hurt to have it installed on any version, and it makes this mod more enjoyable on new Mansion tiles.
|
Speaking of dependencies, [`V70PoweredLights_Fix`] is not strictly required, but it doesn't hurt to have it installed on any version, and it makes this mod more enjoyable on new Mansion tiles.
|
||||||
|
|
||||||
|
@ -38,5 +38,4 @@ Any player can change their personal preferences locally.
|
||||||
|
|
||||||
[`CSync`]: https://thunderstore.io/c/lethal-company/p/Sigurd/CSync/
|
[`CSync`]: https://thunderstore.io/c/lethal-company/p/Sigurd/CSync/
|
||||||
[`LethalConfig`]: https://thunderstore.io/c/lethal-company/p/AinaVT/LethalConfig/
|
[`LethalConfig`]: https://thunderstore.io/c/lethal-company/p/AinaVT/LethalConfig/
|
||||||
[`LobbyCompatibility`]: https://thunderstore.io/c/lethal-company/p/BMX/LobbyCompatibility/
|
|
||||||
[`V70PoweredLights_Fix`]: https://thunderstore.io/c/lethal-company/p/WaterGun/V70PoweredLights_Fix/
|
[`V70PoweredLights_Fix`]: https://thunderstore.io/c/lethal-company/p/WaterGun/V70PoweredLights_Fix/
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "MuzikaGromche",
|
"name": "MuzikaGromche",
|
||||||
"version_number": "1337.420.69",
|
"version_number": "1337.69.420",
|
||||||
"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",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"BepInEx-BepInExPack-5.4.2100",
|
"BepInEx-BepInExPack-5.4.2100",
|
||||||
|
"Sigurd-CSync-5.0.1",
|
||||||
"AinaVT-LethalConfig-1.4.6",
|
"AinaVT-LethalConfig-1.4.6",
|
||||||
"WaterGun-V70PoweredLights_Fix-1.0.0",
|
"WaterGun-V70PoweredLights_Fix-1.0.0",
|
||||||
"BMX-LobbyCompatibility-1.5.1"
|
"BMX-LobbyCompatibility-1.5.1"
|
||||||
|
|
Loading…
Reference in New Issue