1
0
Fork 0

Compare commits

..

No commits in common. "df796965f2b9f77a64d03955a5d3387bbf5ed675" and "8b2f4428bb97118b42d9c6e7c4dcd4482846dbdd" have entirely different histories.

8 changed files with 99 additions and 325 deletions

View File

@ -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)

Binary file not shown.

BIN
Assets/BeefLiverStart.ogg (Stored with Git LFS)

Binary file not shown.

View File

@ -1,11 +1,5 @@
# 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
- Fix certain object hanging around after being disabled.

View File

@ -8,7 +8,7 @@
<AssemblyName>Ratijas.MuzikaGromche</AssemblyName>
<Product>Muzika Gromche</Product>
<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>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
@ -41,7 +41,6 @@
<!--
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
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="AinaVT-LethalConfig" Version="1.4.6" PrivateAssets="all" Private="false" />

View File

@ -1,5 +1,7 @@
using BepInEx;
using BepInEx.Configuration;
using CSync.Extensions;
using CSync.Lib;
using HarmonyLib;
using LethalConfig;
using LethalConfig.ConfigItems;
@ -19,17 +21,10 @@ using Unity.Netcode;
using UnityEngine;
using UnityEngine.Networking;
#if DEBUG
using CSync.Extensions;
using CSync.Lib;
#endif
namespace MuzikaGromche
{
[BepInPlugin(PluginInfo.PLUGIN_GUID, PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)]
#if DEBUG
[BepInDependency("com.sigurd.csync", "5.0.1")]
#endif
[BepInDependency("ainavt.lc.lethalconfig", "1.4.6")]
[BepInDependency("watergun.v72lightfix", BepInDependency.DependencyFlags.SoftDependency)]
[BepInDependency("BMX.LobbyCompatibility", BepInDependency.DependencyFlags.HardDependency)]
@ -475,27 +470,6 @@ namespace MuzikaGromche
FlickerLightsTimeSeries = [-120.5f, -105, -89, -8, 44, 45],
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()
@ -555,9 +529,6 @@ namespace MuzikaGromche
void Awake()
{
// Sort in place by name
Array.Sort(Tracks.Select(track => track.Name).ToArray(), Tracks);
string dir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
UnityWebRequest[] requests = new UnityWebRequest[Tracks.Length * 2];
for (int i = 0; i < Tracks.Length; i++)
@ -578,9 +549,6 @@ namespace MuzikaGromche
Track track = Tracks[i];
track.LoadedStart = DownloadHandlerAudioClip.GetContent(requests[i * 2]);
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);
DiscoBallManager.Load();
@ -857,15 +825,11 @@ namespace MuzikaGromche
// Beat relative to the popup. Always less than LoopBeats. When not IsLooping, can be unbounded negative.
public readonly float Beat;
// Additional metadata describing whether this timestamp is based on extrapolated source data.
public readonly bool IsExtrapolated;
public BeatTimestamp(int loopBeats, bool isLooping, float beat, bool isExtrapolated)
public BeatTimestamp(int loopBeats, bool isLooping, float beat)
{
LoopBeats = loopBeats;
IsLooping = isLooping || beat >= HalfLoopBeats;
Beat = isLooping || beat >= LoopBeats ? Mod.Positive(beat, LoopBeats) : beat;
IsExtrapolated = isExtrapolated;
}
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.
// 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)
@ -890,12 +854,12 @@ namespace MuzikaGromche
{
// There is no way it wraps or affects IsLooping state
var beat = Mathf.Floor(Beat);
return new BeatTimestamp(LoopBeats, IsLooping, beat, IsExtrapolated);
return new BeatTimestamp(LoopBeats, IsLooping, beat);
}
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;
// Closed upper bound
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;
IsLooping = isLooping || beatToInclusive >= HalfLoopBeats;
BeatFromExclusive = wrap(beatFromExclusive);
BeatToInclusive = wrap(beatToInclusive);
IsExtrapolated = isExtrapolated;
float wrap(float beat)
{
@ -927,20 +888,20 @@ namespace MuzikaGromche
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, isExtrapolated);
return new BeatTimeSpan(timestampToInclusive.LoopBeats, timestampToInclusive.IsLooping, timestampFromExclusive.Beat, timestampToInclusive.Beat);
}
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 readonly BeatTimestamp ToTimestamp()
{
return new(LoopBeats, IsLooping, BeatToInclusive, IsExtrapolated);
return new(LoopBeats, IsLooping, BeatToInclusive);
}
// The beat will not be wrapped.
@ -962,7 +923,7 @@ namespace MuzikaGromche
// before wrapping (happens earlier) and after wrapping (happens later).
// 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);
if (laterIndex != null)
{
@ -1048,144 +1009,94 @@ namespace MuzikaGromche
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
public bool IsPlaying { get; private set; }
// The object is newly created, the Start audio began to play but its time hasn't adjanced from 0.0f yet.
private bool hasStarted = false;
// AudioSource.time, possibly extrapolated
public float Time => ExtrapolatedTime;
// The time span after Zero state (when the Start audio has advanced from 0.0f) but before the WindUpTimer+Loop/2.
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.
// Time can not be extrapolated when HasStarted is false.
public bool HasStarted { get; private set; } = false;
// The time span after Zero state (when the Start audio has advanced from 0.0f) but before the WindUpTimer+LoopOffset+Loop/2.
private bool loopOffsetIsLooping = 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 float LastKnownRealtime = 0f;
private static System.Random lyricsRandom = null!;
private const float MaxExtrapolationInterval = 0.5f;
private int lyricsRandomPerLoop;
public void Update(AudioSource audioSource, float realtime)
public BeatTimeState(Track track)
{
IsPlaying = audioSource.isPlaying;
HasStarted |= audioSource.time != 0f;
if (LastKnownNonExtrapolatedTime != audioSource.time)
if (lyricsRandom == null)
{
LastKnownRealtime = realtime;
LastKnownNonExtrapolatedTime = ExtrapolatedTime = audioSource.time;
lyricsRandom = new System.Random(RoundManager.Instance.playersManager.randomMapSeed + 1337);
lyricsRandomPerLoop = lyricsRandom.Next();
}
// Frames are rendering faster than AudioSource updates its playback time state
else if (IsPlaying && HasStarted && Config.ExtrapolateTime)
this.track = track;
}
public List<BaseEvent> Update(AudioSource start, AudioSource loop)
{
hasStarted |= start.time != 0;
if (hasStarted)
{
#if DEBUG
Debug.Assert(LastKnownNonExtrapolatedTime == audioSource.time); // implied
#endif
var deltaTime = realtime - LastKnownRealtime;
if (0 < deltaTime && deltaTime < MaxExtrapolationInterval)
var loopTimestamp = UpdateStateForLoopOffset(start, loop);
var loopOffsetSpan = BeatTimeSpan.Between(loopOffsetBeat, loopTimestamp);
// Do not go back in time
if (!loopOffsetSpan.IsEmpty())
{
ExtrapolatedTime = LastKnownNonExtrapolatedTime + deltaTime;
if (loopOffsetSpan.BeatFromExclusive > loopOffsetSpan.BeatToInclusive)
{
lyricsRandomPerLoop = lyricsRandom.Next();
}
var windUpOffsetTimestamp = UpdateStateForWindUpOffset(start, loop);
loopOffsetBeat = loopTimestamp.Beat;
var events = GetEvents(loopOffsetSpan, windUpOffsetTimestamp);
#if DEBUG
Debug.Log($"{nameof(MuzikaGromche)} looping? {(loopOffsetIsLooping ? 'X' : '_')}{(windUpOffsetIsLooping ? 'X' : '_')} Loop={loopOffsetSpan} WindUp={windUpOffsetTimestamp} events={string.Join(",", events)}");
#endif
return events;
}
}
return [];
}
public void Finish()
// Events other than colors start rotating at 0=WindUpTimer+LoopOffset.
private BeatTimestamp UpdateStateForLoopOffset(AudioSource start, AudioSource loop)
{
IsPlaying = false;
var offset = BaseOffset() + track.LoopOffsetInSeconds;
var timestamp = GetTimestampRelativeToGivenOffset(start, loop, offset, loopOffsetIsLooping);
loopOffsetIsLooping |= timestamp.IsLooping;
return timestamp;
}
public override string ToString()
// Colors start rotating at 0=WindUpTimer
private BeatTimestamp UpdateStateForWindUpOffset(AudioSource start, AudioSource loop)
{
return $"{nameof(ExtrapolatedAudioSourceState)}({(IsPlaying ? 'P' : '_')}{(HasStarted ? 'S' : '0')} "
+ (IsExtrapolated
? $"{LastKnownRealtime:N4}, {LastKnownNonExtrapolatedTime:N4} => {ExtrapolatedTime:N4}"
: $"{LastKnownRealtime:N4}, {LastKnownNonExtrapolatedTime:N4}"
) + ")";
var offset = BaseOffset();
var timestamp = GetTimestampRelativeToGivenOffset(start, loop, offset, windUpOffsetIsLooping);
windUpOffsetIsLooping |= timestamp.IsLooping;
return timestamp;
}
}
class JesterAudioSourcesState
{
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)
private float BaseOffset()
{
StartClipLength = startClipLength;
return Config.AudioOffset.Value + track.BeatsOffsetInSeconds + track.WindUpTimer;
}
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)
BeatTimestamp GetTimestampRelativeToGivenOffset(AudioSource start, AudioSource loop, float offset, bool isLooping)
{
// If popped, calculate which beat the music is currently at.
// 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:
// 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
var timestamp = new BeatTimestamp(Beats, IsLooping, beat, isExtrapolated);
IsLooping |= timestamp.IsLooping;
var timestamp = new BeatTimestamp(track.Beats, isLooping, beat);
#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),
Time.realtimeSinceStartup, Time.deltaTime,
isExtrapolated ? 'E' : '_', time,
adjustedTimeNormalized, beat);
(start.isPlaying ? '+' : ' '), start.time, (start.time == 0f ? 'Y' : 'n'),
(loop.isPlaying ? '+' : ' '), loop.time,
adjustedTimeNormalized, beat, color);
#endif
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)
{
List<BaseEvent> events = [];
if (windUpOffsetTimestamp.Beat >= 0f && !WindUpZeroBeatEventTriggered)
if (windUpOffsetTimestamp.Beat >= 0f && !windUpZeroBeatEventTriggered)
{
events.Add(new WindUpZeroBeatEvent());
WindUpZeroBeatEventTriggered = true;
windUpZeroBeatEventTriggered = true;
}
if (GetColorEvent(loopOffsetSpan, windUpOffsetTimestamp) is { } colorEvent)
@ -1327,7 +1166,7 @@ namespace MuzikaGromche
{
var line = track.LyricsLines[i];
var alternatives = line.Split('\t');
var randomIndex = LyricsRandomPerLoop % alternatives.Length;
var randomIndex = lyricsRandomPerLoop % alternatives.Length;
var alternative = alternatives[randomIndex];
if (alternative != "")
{
@ -1594,7 +1433,6 @@ namespace MuzikaGromche
readonly public int TotalWeights { get; }
}
#if DEBUG
static class SyncedEntryExtensions
{
// Update local values on clients. Even though the clients couldn't
@ -1607,12 +1445,8 @@ namespace MuzikaGromche
};
}
}
#endif
class Config
#if DEBUG
: SyncedConfig2<Config>
#endif
class Config : SyncedConfig2<Config>
{
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 bool ExtrapolateTime { get; private set; } = true;
public static bool ShouldSkipWindingPhase { get; private set; } = false;
public static Palette? PaletteOverride { get; private set; } = null;
@ -1636,10 +1469,7 @@ namespace MuzikaGromche
public static float? ColorTransitionOutOverride { get; private set; } = null;
public static string? ColorTransitionEasingOverride { get; private set; } = null;
internal Config(ConfigFile configFile)
#if DEBUG
: base(PluginInfo.PLUGIN_GUID)
#endif
internal Config(ConfigFile configFile) : base(PluginInfo.PLUGIN_GUID)
{
DisplayLyrics = configFile.Bind("General", "Display Lyrics", true,
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())));
#if DEBUG
SetupEntriesForExtrapolation(configFile);
SetupEntriesToSkipWinding(configFile);
SetupEntriesForPaletteOverride(configFile);
SetupEntriesForTimingsOverride(configFile);
@ -1708,12 +1537,9 @@ namespace MuzikaGromche
LethalConfigManager.AddConfigItem(new IntSliderConfigItem(track.Weight, Default(new IntSliderOptions())));
}
#if DEBUG
ConfigManager.Register(this);
#endif
}
#if DEBUG
// HACK because CSync doesn't provide an API to register a list of config entries
// See https://github.com/lc-sigurd/CSync/issues/11
private void CSyncHackAddSyncedEntry(SyncedEntryBase entryBase)
@ -1721,7 +1547,6 @@ namespace MuzikaGromche
// This is basically what ConfigFile.PopulateEntryContainer does
EntryContainer.Add(entryBase.BoxedEntry.ToSyncedEntryIdentifier(), entryBase);
}
#endif
public static CanModifyResult CanModifyIfHost()
{
@ -1757,23 +1582,6 @@ namespace MuzikaGromche
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)
{
var syncedEntry = configFile.BindSyncedEntry("General", "Skip Winding Phase", false,
@ -1885,7 +1693,7 @@ namespace MuzikaGromche
fadeOutBeatSyncedEntry = configFile.BindSyncedEntry(section, "Fade Out Beat", 0f,
new ConfigDescription("The beat at which to start fading out", new AcceptableValueRange<float>(-1000f, 0)));
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", "",
new ConfigDescription("Time series of beat offsets when to flicker the lights."));
lyricsTimeSeriesSyncedEntry = configFile.BindSyncedEntry(section, "Lyrics Time Series", "",
@ -1981,7 +1789,6 @@ namespace MuzikaGromche
}
}
}
#endif
private T Default<T>(T options) where T : BaseOptions
{
@ -2021,30 +1828,15 @@ namespace MuzikaGromche
ChooseTrackDeferred();
foreach (var track in Plugin.Tracks)
{
track.Weight.SettingChanged += ChooseTrackDeferredDelegate;
track.Weight.SettingChanged += (_, _) => ChooseTrackDeferred();
}
Config.SkipExplicitTracks.SettingChanged += ChooseTrackDeferredDelegate;
Config.SkipExplicitTracks.SettingChanged += (_, _) => ChooseTrackDeferred();
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
private Coroutine? DeferredCoroutine = null;
private void ChooseTrackDeferredDelegate(object sender, EventArgs e)
{
ChooseTrackDeferred();
}
private void ChooseTrackDeferred()
{
if (DeferredCoroutine != null)
@ -2176,7 +1968,7 @@ namespace MuzikaGromche
__instance.farAudio = __state.farAudio;
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
__instance.creatureVoice.Stop();
@ -2189,9 +1981,9 @@ namespace MuzikaGromche
}
// 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)
{
switch (ev)

View File

@ -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 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.
@ -38,5 +38,4 @@ Any player can change their personal preferences locally.
[`CSync`]: https://thunderstore.io/c/lethal-company/p/Sigurd/CSync/
[`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/

View File

@ -1,11 +1,12 @@
{
"name": "MuzikaGromche",
"version_number": "1337.420.69",
"version_number": "1337.69.420",
"author": "Ratijas",
"description": "Add some content to your inverse teleporter experience on Titan!",
"website_url": "https://git.vilunov.me/ratijas/muzika-gromche",
"dependencies": [
"BepInEx-BepInExPack-5.4.2100",
"Sigurd-CSync-5.0.1",
"AinaVT-LethalConfig-1.4.6",
"WaterGun-V70PoweredLights_Fix-1.0.0",
"BMX-LobbyCompatibility-1.5.1"