forked from nikita/muzika-gromche
Compare commits
No commits in common. "dev" and "master" have entirely different histories.
|
|
@ -1,15 +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
|
||||
|
||||
# IDE0305: Simplify collection initialization
|
||||
dotnet_style_prefer_collection_expression = never
|
||||
|
||||
# IDE0031: Use null propagation
|
||||
# Unity overrides equality operator, so gameObject == null also accounts for internal state of the backing C++ object
|
||||
# Read more:
|
||||
# - https://blog.lslabs.dev/posts/null_check_equality_unity
|
||||
# - https://blog.lslabs.dev/posts/unity_script_duality
|
||||
dotnet_style_null_propagation = false
|
||||
BIN
Assets/ArcaneIntro.ogg (Stored with Git LFS)
BIN
Assets/ArcaneIntro.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/ArcaneLoop.ogg (Stored with Git LFS)
BIN
Assets/ArcaneLoop.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/AttentionPls1Intro.ogg (Stored with Git LFS)
BIN
Assets/AttentionPls1Intro.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/AttentionPls2Intro.ogg (Stored with Git LFS)
BIN
Assets/AttentionPls2Intro.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/AttentionPlsLoop.ogg (Stored with Git LFS)
BIN
Assets/AttentionPlsLoop.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/BbIXODaHETIntro.ogg (Stored with Git LFS)
BIN
Assets/BbIXODaHETIntro.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/BbIXODaHETLoop.ogg (Stored with Git LFS)
BIN
Assets/BbIXODaHETLoop.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/BeefLiver1Intro.ogg (Stored with Git LFS)
BIN
Assets/BeefLiver1Intro.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/BeefLiver3Intro.ogg (Stored with Git LFS)
BIN
Assets/BeefLiver3Intro.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/BeefLiver4Intro.ogg (Stored with Git LFS)
BIN
Assets/BeefLiver4Intro.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/BeefLiver4Loop.ogg (Stored with Git LFS)
BIN
Assets/BeefLiver4Loop.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/BeefLiverLoop.ogg (Stored with Git LFS)
BIN
Assets/BeefLiverLoop.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/Beha1Intro.ogg (Stored with Git LFS)
BIN
Assets/Beha1Intro.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/Beha2Intro.ogg (Stored with Git LFS)
BIN
Assets/Beha2Intro.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/Beha3Intro.ogg (Stored with Git LFS)
BIN
Assets/Beha3Intro.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/BehaLoop.ogg (Stored with Git LFS)
BIN
Assets/BehaLoop.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/ChereshnyaIntro.ogg (Stored with Git LFS)
BIN
Assets/ChereshnyaIntro.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/ChereshnyaLoop.ogg (Stored with Git LFS)
BIN
Assets/ChereshnyaLoop.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/DeployDestroyIntro.ogg (Stored with Git LFS)
BIN
Assets/DeployDestroyIntro.ogg (Stored with Git LFS)
Binary file not shown.
Binary file not shown.
BIN
Assets/DeployDestroyLoop.ogg (Stored with Git LFS)
BIN
Assets/DeployDestroyLoop.ogg (Stored with Git LFS)
Binary file not shown.
Binary file not shown.
BIN
Assets/DiscoKapotIntro.ogg (Stored with Git LFS)
BIN
Assets/DiscoKapotIntro.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/DiscoKapotLoop.ogg (Stored with Git LFS)
BIN
Assets/DiscoKapotLoop.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/DurochkaIntro.ogg (Stored with Git LFS)
BIN
Assets/DurochkaIntro.ogg (Stored with Git LFS)
Binary file not shown.
Binary file not shown.
BIN
Assets/DurochkaLoop.ogg (Stored with Git LFS)
BIN
Assets/DurochkaLoop.ogg (Stored with Git LFS)
Binary file not shown.
Binary file not shown.
BIN
Assets/GodModeIntro.ogg (Stored with Git LFS)
BIN
Assets/GodModeIntro.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/GodModeLoop.ogg (Stored with Git LFS)
BIN
Assets/GodModeLoop.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/GorgorodIntro.ogg (Stored with Git LFS)
BIN
Assets/GorgorodIntro.ogg (Stored with Git LFS)
Binary file not shown.
Binary file not shown.
BIN
Assets/GorgorodLoop.ogg (Stored with Git LFS)
BIN
Assets/GorgorodLoop.ogg (Stored with Git LFS)
Binary file not shown.
Binary file not shown.
BIN
Assets/HighLowIntro.ogg (Stored with Git LFS)
BIN
Assets/HighLowIntro.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/HighLowLoop.ogg (Stored with Git LFS)
BIN
Assets/HighLowLoop.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/IkWilJeIntro.ogg (Stored with Git LFS)
BIN
Assets/IkWilJeIntro.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/IkWilJeLoop.ogg (Stored with Git LFS)
BIN
Assets/IkWilJeLoop.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/KachIntro.ogg (Stored with Git LFS)
BIN
Assets/KachIntro.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/KachLoop.ogg (Stored with Git LFS)
BIN
Assets/KachLoop.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/MoyaZhittyaIntro.ogg (Stored with Git LFS)
BIN
Assets/MoyaZhittyaIntro.ogg (Stored with Git LFS)
Binary file not shown.
Binary file not shown.
BIN
Assets/MoyaZhittyaLoop.ogg (Stored with Git LFS)
BIN
Assets/MoyaZhittyaLoop.ogg (Stored with Git LFS)
Binary file not shown.
Binary file not shown.
BIN
Assets/MuzikaGromcheIntro.ogg (Stored with Git LFS)
BIN
Assets/MuzikaGromcheIntro.ogg (Stored with Git LFS)
Binary file not shown.
Binary file not shown.
BIN
Assets/MuzikaGromcheLoop.ogg (Stored with Git LFS)
BIN
Assets/MuzikaGromcheLoop.ogg (Stored with Git LFS)
Binary file not shown.
Binary file not shown.
BIN
Assets/OnePartiyaUdarIntro.ogg (Stored with Git LFS)
BIN
Assets/OnePartiyaUdarIntro.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/OnePartiyaUdarLoop.ogg (Stored with Git LFS)
BIN
Assets/OnePartiyaUdarLoop.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/PWNEDIntro.ogg (Stored with Git LFS)
BIN
Assets/PWNEDIntro.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/PWNEDLoop.ogg (Stored with Git LFS)
BIN
Assets/PWNEDLoop.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/PaardenIntro.ogg (Stored with Git LFS)
BIN
Assets/PaardenIntro.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/PaardenLoop.ogg (Stored with Git LFS)
BIN
Assets/PaardenLoop.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/PeretasovkaIntro.ogg (Stored with Git LFS)
BIN
Assets/PeretasovkaIntro.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/PeretasovkaLoop.ogg (Stored with Git LFS)
BIN
Assets/PeretasovkaLoop.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/PickUpSticks1Intro.ogg (Stored with Git LFS)
BIN
Assets/PickUpSticks1Intro.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/PickUpSticks2Intro.ogg (Stored with Git LFS)
BIN
Assets/PickUpSticks2Intro.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/PickUpSticksLoop.ogg (Stored with Git LFS)
BIN
Assets/PickUpSticksLoop.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/ReelGoonIntro.ogg (Stored with Git LFS)
BIN
Assets/ReelGoonIntro.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/ReelGoonLoop.ogg (Stored with Git LFS)
BIN
Assets/ReelGoonLoop.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/RiseAndShineIntro.ogg (Stored with Git LFS)
BIN
Assets/RiseAndShineIntro.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/RiseAndShineLoop.ogg (Stored with Git LFS)
BIN
Assets/RiseAndShineLoop.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/Song2Intro.ogg (Stored with Git LFS)
BIN
Assets/Song2Intro.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/Song2Loop.ogg (Stored with Git LFS)
BIN
Assets/Song2Loop.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/TwoFastTuFuriousIntro.ogg (Stored with Git LFS)
BIN
Assets/TwoFastTuFuriousIntro.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/TwoFastTuFuriousLoop.ogg (Stored with Git LFS)
BIN
Assets/TwoFastTuFuriousLoop.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/VseVZaleIntro.ogg (Stored with Git LFS)
BIN
Assets/VseVZaleIntro.ogg (Stored with Git LFS)
Binary file not shown.
Binary file not shown.
BIN
Assets/VseVZaleLoop.ogg (Stored with Git LFS)
BIN
Assets/VseVZaleLoop.ogg (Stored with Git LFS)
Binary file not shown.
Binary file not shown.
BIN
Assets/WhistleIntro.ogg (Stored with Git LFS)
BIN
Assets/WhistleIntro.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/WhistleLoop.ogg (Stored with Git LFS)
BIN
Assets/WhistleLoop.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/YalgaarIntro.ogg (Stored with Git LFS)
BIN
Assets/YalgaarIntro.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/YalgaarLoop.ogg (Stored with Git LFS)
BIN
Assets/YalgaarLoop.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/ZmeiGorynichIntro.ogg (Stored with Git LFS)
BIN
Assets/ZmeiGorynichIntro.ogg (Stored with Git LFS)
Binary file not shown.
BIN
Assets/ZmeiGorynichLoop.ogg (Stored with Git LFS)
BIN
Assets/ZmeiGorynichLoop.ogg (Stored with Git LFS)
Binary file not shown.
128
CHANGELOG.md
128
CHANGELOG.md
|
|
@ -1,128 +0,0 @@
|
|||
# Changelog
|
||||
|
||||
## MuzikaGromche 1337.9001.69
|
||||
|
||||
- Show real Artist & Song info in the config.
|
||||
|
||||
## MuzikaGromche 1337.9001.68 - LocalHost hotfix
|
||||
|
||||
- Fixed occasionally broken playback of v1337.9001.67, sorry about that.
|
||||
- Turns out, client-side vanilla-compat mode can never be perfectly timed, so don't expect much without a modded host.
|
||||
- Removed an existing track Yalgaar.
|
||||
- Merged two config options into one: Reduce Visual Effects & Display Lyrics.
|
||||
- Added a new track Arcane.
|
||||
|
||||
## MuzikaGromche 1337.9001.67 - LocalHost Edition
|
||||
|
||||
- Added a new track TwoFastTuFurious (from the same artist as PickUpSticks), thematic to the upcoming Valentine's Day.
|
||||
- Added support for client-side playback while playing with an unmodded/vanilla host.
|
||||
- Tweaked the amount of visual flare at the Factory's start room (main tile).
|
||||
|
||||
## 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
|
||||
|
||||
- Raised the default audio volume, and added a configuration slider.
|
||||
- Tweaked color palette, lyrics and visual effects for MoyaZhittya and some other tracks.
|
||||
|
||||
## MuzikaGromche 1337.9001.0 - v73 Music quieter Edition
|
||||
|
||||
- Updated netcode-patch to support Lethal Company v73.
|
||||
- Remastered all the audio tracks to target a consistent loudness level which allows you hear your teammates.
|
||||
- Remastered track Song2 to fix cut points.
|
||||
- Shortened intro of track Peretasovka to match vanilla timings.
|
||||
- Added multiple intro variants for BeefLiver.
|
||||
- Added a new track BbIXODaHET.
|
||||
- Added a new track Whistle. Now it can fully replace WhistleJester!
|
||||
- Added a new track ReelGoon.
|
||||
|
||||
## MuzikaGromche 1337.420.9004 - Life Support Edition
|
||||
|
||||
- Override Death Screen / Game Over text in certain cases.
|
||||
- Added a new track AttentionPls featuring multiple intro variants and new visual effects.
|
||||
|
||||
## MuzikaGromche 1337.420.9003 - Lights Out Edition
|
||||
|
||||
- Fixed wrong colors during fade out transition, e.g. in Mineshaft tunnel tiles.
|
||||
|
||||
## MuzikaGromche 1337.420.9002 - Anime Edition
|
||||
|
||||
- Added a new track OnePartiyaUdar in Japanese language.
|
||||
- Remastered recently added tracks at conventional 44100 Hz for better stitching.
|
||||
- Improved playback experience: use precise DSP time and up-front scheduing for seamless audio stitching, add custom Audio Sources to improve reliability.
|
||||
- Removed remaining CSync code and package references even from debug builds.
|
||||
- Downgraded LobbyCompatibility to optional dependency.
|
||||
- Toggled config option to increase certain spawn rate to ON by default.
|
||||
- Fixed resetting to wrong initial colors, e.g. in Mineshaft tunnel tiles.
|
||||
|
||||
## MuzikaGromche 1337.420.9001 - Multiverse Edition
|
||||
|
||||
- Added support for tracks to rotate between multiple audio variants during a round.
|
||||
- Added a new track Beha with three different variants of intro.
|
||||
|
||||
## MuzikaGromche 1337.420.69 - It's All DiscoNnected Edition
|
||||
|
||||
- Fixed harmless but annoying errors in BepInEx console output.
|
||||
- Improve smoothness of color animations.
|
||||
- Added a new track BeefLiver.
|
||||
|
||||
## MuzikaGromche 1337.69.420 - It's All Connected Edition
|
||||
|
||||
- Fix certain object hanging around after being disabled.
|
||||
- CSync proved to be unreliable for config syncing, so rewrote track selection to custom netcode.
|
||||
|
||||
## MuzikaGromche 13.37.9001 - Chromaberrated Edition
|
||||
|
||||
- Fixed more missing flickering behaviours for some animators controllers.
|
||||
- Fixed some powered lights not fully turning off or flickering when there are multiple Light components per container.
|
||||
- Improved performance by pre-loading certain assets at the start of round instead of at a timing-critical frame update.
|
||||
- Added an opt-in config option to increase certain spawn rate to experience content of this mod more often.
|
||||
|
||||
## MuzikaGromche 13.37.1337 - Photosensitivity Warning Edition
|
||||
|
||||
- Added LobbyCompatibility to dependencies to avoid desync issues.
|
||||
- Fixed lyrics not being displayed in some situations.
|
||||
- Fixed visual issues with the fade out effect.
|
||||
- Fixed visual glitch at the last beat of a loop.
|
||||
- Fixed timings of one of the tracks.
|
||||
- Removed unnecessary "Enable Color Animations" config option.
|
||||
- Fixed missing flickering behaviours for some animators controllers.
|
||||
|
||||
## MuzikaGromche 13.37.911 - Sri Lanka Bus hotfix
|
||||
|
||||
- Fixed certain event sometimes not working due to wrong method call.
|
||||
- Added support for pre-v70 Mansion Main tile.
|
||||
|
||||
## MuzikaGromche 13.37.420 - Sri Lanka Bus Edition
|
||||
|
||||
Completely rewritten by Ratijas, with tons of new content.
|
||||
|
||||
- Added lots of new tracks.
|
||||
- Fixed gaps in old tracks.
|
||||
- New code synchronizes light show to the beat.
|
||||
- Timings, animation curves, color palettes and events fine-tuned for each track by visual artist [Just Nothing](https://t.me/REALJUSTNOTHING).
|
||||
- Configurable Audio Delay for those with Bluetooth headset.
|
||||
- Configurable chance of randomly choosing each tracks.
|
||||
- Added lyrics to *some* of the tracks, and a configuration toggle.
|
||||
- Certain tiles are patched by [WaterGun](https://www.youtube.com/channel/UCCxCFfmrnqkFZ8i9FsXBJVA) to add some visual flare.
|
||||
|
||||
## MuzikaGromche 13.37.6 - Christmas Special
|
||||
|
||||
Last known version released by Oflor. Added special timed content for New Year and Christmas.
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<Project>
|
||||
<Target Name="NetcodePatch" AfterTargets="PostBuildEvent">
|
||||
<Exec Command="dotnet netcode-patch -uv 2022.3.62 -nv 1.12.0 "$(TargetPath)" @(ReferencePathWithRefAssemblies->'"%(Identity)"', ' ')"/>
|
||||
</Target>
|
||||
</Project>
|
||||
8
Justfile
8
Justfile
|
|
@ -11,7 +11,7 @@ build-debug:
|
|||
clean:
|
||||
rm -rf dist MuzikaGromche/bin MuzikaGromche/obj
|
||||
|
||||
plugin_dir := "$HOME/.config/r2modmanPlus-local/LethalCompany/profiles" / imperium_profile / "BepInEx/plugins/Ratijas-MuzikaGromche/"
|
||||
plugin_dir := "$HOME/.config/r2modmanPlus-local/LethalCompany/profiles" / imperium_profile / "BepInEx/plugins/Oflor-MuzikaGromche/"
|
||||
|
||||
install-imperium:
|
||||
rm -rf "{{ plugin_dir }}"
|
||||
|
|
@ -26,9 +26,3 @@ bump version_number:
|
|||
jq --indent 4 --arg v "{{ version_number }}" '.version_number = $v' < manifest.json > manifest.json.copy
|
||||
mv manifest.json.copy manifest.json
|
||||
sed -i 's/<Version>.*<\/Version>/<Version>{{ version_number }}<\/Version>/' MuzikaGromche/MuzikaGromche.csproj
|
||||
|
||||
ogg track_name:
|
||||
dotnet msbuild /t:wav2ogg /p:TrackName="{{ track_name }}"
|
||||
|
||||
ogg1 track_name:
|
||||
dotnet msbuild /t:wav2ogg1 /p:TrackName="{{ track_name }}"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MuzikaGromche", "MuzikaGromche\MuzikaGromche.csproj", "{72633315-F098-4E09-B32B-9224376CD9A5}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{72633315-F098-4E09-B32B-9224376CD9A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{72633315-F098-4E09-B32B-9224376CD9A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{72633315-F098-4E09-B32B-9224376CD9A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{72633315-F098-4E09-B32B-9224376CD9A5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
<Solution>
|
||||
<Project Path="MuzikaGromche/MuzikaGromche.csproj" />
|
||||
</Solution>
|
||||
|
|
@ -2,13 +2,9 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<!-- Copy this file to MuzikaGromche.props.user and uncomment one of two paths below: -->
|
||||
|
||||
<!-- On Linux: -->
|
||||
<!-- <LethalCompanyDir>$(HOME)/.local/share/Steam/steamapps/common/Lethal Company/</LethalCompanyDir> -->
|
||||
<!-- <WavExportDir>\home\ratijas\Music\SFX\Export</WavExportDir> -->
|
||||
|
||||
<!-- On Windows: -->
|
||||
<!-- <LethalCompanyDir>C:/Program Files (x86)/Steam/steamapps/common/Lethal Company/</LethalCompanyDir> -->
|
||||
<!-- <WavExportDir>D:\Code\MuzikaGromcheAudio\Export</WavExportDir> -->
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,170 +0,0 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
using BepInEx;
|
||||
using BepInEx.Bootstrap;
|
||||
using LobbyCompatibility.Enums;
|
||||
using LobbyCompatibility.Features;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace MuzikaGromche
|
||||
{
|
||||
internal static class Compatibility
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void Register(BaseUnityPlugin plugin)
|
||||
{
|
||||
if (Chainloader.PluginInfos.ContainsKey("BMX.LobbyCompatibility"))
|
||||
{
|
||||
RegisterLobbyCompatibility(plugin.Info.Metadata);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void RegisterLobbyCompatibility(BepInPlugin plugin)
|
||||
{
|
||||
PluginHelper.RegisterPlugin(plugin.GUID, plugin.Version, CompatibilityLevel.Everyone, VersionStrictness.Patch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
using System.Collections;
|
||||
using HarmonyLib;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MuzikaGromche
|
||||
{
|
||||
static class DeathScreenGameOverTextManager
|
||||
{
|
||||
private const string GameOverTextVanilla = "[LIFE SUPPORT: OFFLINE]";
|
||||
|
||||
public const string GameOverTextModdedDefault = "[ MUZIKA: GROMCHE ]";
|
||||
|
||||
public static void Clear()
|
||||
{
|
||||
SetTextImpl(GameOverTextVanilla);
|
||||
}
|
||||
|
||||
public static void SetText(string? text)
|
||||
{
|
||||
SetTextImpl(text ?? GameOverTextModdedDefault);
|
||||
}
|
||||
|
||||
public static IEnumerator SetTextAndClear(string? text)
|
||||
{
|
||||
SetText(text);
|
||||
// Game Over animation duration is about 4.25 seconds
|
||||
yield return new WaitForSeconds(5f);
|
||||
Clear();
|
||||
}
|
||||
|
||||
private static void SetTextImpl(string text)
|
||||
{
|
||||
GameObject textGameObject = GameObject.Find("Systems/UI/Canvas/DeathScreen/GameOverText");
|
||||
if (textGameObject == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
TextMeshProUGUI tmp = textGameObject.GetComponent<TextMeshProUGUI>();
|
||||
if (tmp == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var transform = textGameObject.GetComponent<RectTransform>();
|
||||
if (transform == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Default transform width is 645.8 which only fit the default message and no extra characters
|
||||
Vector2 size = transform.sizeDelta;
|
||||
if (Mathf.Approximately(size.x, 645.8f))
|
||||
{
|
||||
size.x += 100f;
|
||||
transform.sizeDelta = size;
|
||||
}
|
||||
tmp.text = text;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(RoundManager))]
|
||||
static class DeathScreenGameOverTextResetPatch
|
||||
{
|
||||
[HarmonyPatch(nameof(RoundManager.DespawnPropsAtEndOfRound))]
|
||||
[HarmonyPatch(nameof(RoundManager.OnDestroy))]
|
||||
[HarmonyPrefix]
|
||||
static void OnDestroy(RoundManager __instance)
|
||||
{
|
||||
var _ = __instance;
|
||||
DeathScreenGameOverTextManager.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
using BepInEx.Logging;
|
||||
|
||||
namespace MuzikaGromche;
|
||||
|
||||
#if DEBUG
|
||||
|
||||
// A logger with an API similar to ManualLogSource.
|
||||
// This logger caches last posted messagee and uses it to deduplicate subsequent messages.
|
||||
// Use Clear() to forget deduplicated message.
|
||||
internal class DedupManualLogSource
|
||||
{
|
||||
public ManualLogSource Source;
|
||||
|
||||
object? lastData = null;
|
||||
|
||||
public DedupManualLogSource(ManualLogSource source)
|
||||
{
|
||||
Source = source;
|
||||
}
|
||||
|
||||
public void Log(LogLevel level, object data)
|
||||
{
|
||||
if (lastData != data)
|
||||
{
|
||||
lastData = data;
|
||||
Source.Log(level, data);
|
||||
}
|
||||
}
|
||||
|
||||
public void LogFatal(object data)
|
||||
{
|
||||
Log(LogLevel.Fatal, data);
|
||||
}
|
||||
|
||||
public void LogError(object data)
|
||||
{
|
||||
Log(LogLevel.Error, data);
|
||||
}
|
||||
|
||||
public void LogWarning(object data)
|
||||
{
|
||||
Log(LogLevel.Warning, data);
|
||||
}
|
||||
|
||||
public void LogMessage(object data)
|
||||
{
|
||||
Log(LogLevel.Message, data);
|
||||
}
|
||||
|
||||
public void LogInfo(object data)
|
||||
{
|
||||
Log(LogLevel.Info, data);
|
||||
}
|
||||
|
||||
public void LogDebug(object data)
|
||||
{
|
||||
Log(LogLevel.Debug, data);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
lastData = null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -1,165 +0,0 @@
|
|||
using DunGen;
|
||||
using HarmonyLib;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MuzikaGromche
|
||||
{
|
||||
public static class DiscoBallManager
|
||||
{
|
||||
// A struct holding a disco ball container object and the name of a tile for which it was designed.
|
||||
private readonly record struct TilePatch(string TileName, GameObject DiscoBallContainer)
|
||||
{
|
||||
// We are specifically looking for cloned tiles, not the original prototypes.
|
||||
public readonly string TileCloneName = $"{TileName}(Clone)";
|
||||
}
|
||||
|
||||
private static TilePatch[] Patches = [];
|
||||
|
||||
private static readonly List<GameObject> CachedDiscoBalls = [];
|
||||
private static readonly List<Animator> CachedDiscoBallAnimators = [];
|
||||
|
||||
private static readonly string[] AnimatorContainersNames = [
|
||||
"DiscoBallProp/AnimContainer",
|
||||
"DiscoBallProp1/AnimContainer",
|
||||
"DiscoBallProp2/AnimContainer",
|
||||
"DiscoBallProp3/AnimContainer",
|
||||
"DiscoBallProp4/AnimContainer",
|
||||
"DiscoBallProp5/AnimContainer",
|
||||
];
|
||||
|
||||
public static void Load()
|
||||
{
|
||||
const string BundleFileName = "muzikagromche_discoball";
|
||||
string bundlePath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), BundleFileName);
|
||||
var assetBundle = AssetBundle.LoadFromFile(bundlePath)
|
||||
?? throw new NullReferenceException("Failed to load bundle");
|
||||
|
||||
(string PrefabPath, string TileName)[] patchDescriptors =
|
||||
[
|
||||
("Assets/LethalCompany/Mods/MuzikaGromche/DiscoBallContainerManor.prefab", "ManorStartRoomSmall"),
|
||||
("Assets/LethalCompany/Mods/MuzikaGromche/DiscoBallContainerManorOLD.prefab", "ManorStartRoom"),
|
||||
("Assets/LethalCompany/Mods/MuzikaGromche/DiscoBallContainerFactory.prefab", "StartRoom"),
|
||||
("Assets/LethalCompany/Mods/MuzikaGromche/DiscoBallContainerMineShaft.prefab", "MineshaftStartTile"),
|
||||
("Assets/LethalCompany/Mods/MuzikaGromche/DiscoBallContainerLargeForkTileB.prefab", "LargeForkTileB"),
|
||||
("Assets/LethalCompany/Mods/MuzikaGromche/DiscoBallContainerBirthdayRoomTile.prefab", "BirthdayRoomTile"),
|
||||
];
|
||||
|
||||
Patches = [.. patchDescriptors.Select(d =>
|
||||
new TilePatch(d.TileName, assetBundle.LoadAsset<GameObject>(d.PrefabPath))
|
||||
)];
|
||||
}
|
||||
|
||||
internal static void Patch(Tile tile)
|
||||
{
|
||||
var query = from patch in Patches
|
||||
where tile.gameObject.name == patch.TileCloneName
|
||||
select patch;
|
||||
|
||||
// Should be just one, but FirstOrDefault() isn't usable with structs
|
||||
foreach (var patch in query)
|
||||
{
|
||||
Patch(tile, patch);
|
||||
}
|
||||
}
|
||||
|
||||
static void Patch(Tile tile, TilePatch patch)
|
||||
{
|
||||
var discoBall = UnityEngine.Object.Instantiate(patch.DiscoBallContainer, tile.transform);
|
||||
if (discoBall == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var animator in FindDiscoBallAnimators(discoBall))
|
||||
{
|
||||
CachedDiscoBallAnimators.Add(animator);
|
||||
}
|
||||
CachedDiscoBalls.Add(discoBall);
|
||||
discoBall.SetActive(false);
|
||||
|
||||
Plugin.Log.LogDebug($"{nameof(DiscoBallManager)} Patched tile '{tile.gameObject.name}'");
|
||||
}
|
||||
|
||||
static IEnumerable<Animator> FindDiscoBallAnimators(GameObject discoBall)
|
||||
{
|
||||
foreach (var animatorContainerName in AnimatorContainersNames)
|
||||
{
|
||||
var transform = discoBall.transform.Find(animatorContainerName);
|
||||
if (transform == null)
|
||||
{
|
||||
// Not all prefabs have all possible animators, and it's OK
|
||||
continue;
|
||||
}
|
||||
|
||||
var animator = transform.gameObject?.GetComponent<Animator>();
|
||||
if (animator == null)
|
||||
{
|
||||
// This would be weird
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return animator;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Toggle(bool on)
|
||||
{
|
||||
Plugin.Log.LogDebug($"{nameof(DiscoBallManager)} Toggle {(on ? "ON" : "OFF")} {CachedDiscoBallAnimators.Count} animators");
|
||||
|
||||
foreach (var discoBall in CachedDiscoBalls)
|
||||
{
|
||||
discoBall.SetActive(on);
|
||||
}
|
||||
foreach (var animator in CachedDiscoBallAnimators)
|
||||
{
|
||||
animator?.SetBool("on", on);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Enable()
|
||||
{
|
||||
Toggle(true);
|
||||
}
|
||||
|
||||
public static void Disable()
|
||||
{
|
||||
Toggle(false);
|
||||
}
|
||||
|
||||
internal static void Clear()
|
||||
{
|
||||
Plugin.Log.LogDebug($"{nameof(DiscoBallManager)} Clearing {CachedDiscoBalls.Count} disco balls & {CachedDiscoBallAnimators.Count} animators");
|
||||
CachedDiscoBallAnimators.Clear();
|
||||
CachedDiscoBalls.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(Tile))]
|
||||
static class DiscoBallTilePatch
|
||||
{
|
||||
[HarmonyPatch(nameof(Tile.AddTriggerVolume))]
|
||||
[HarmonyPostfix]
|
||||
static void OnAddTriggerVolume(Tile __instance)
|
||||
{
|
||||
DiscoBallManager.Patch(__instance);
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(RoundManager))]
|
||||
static class DiscoBallDespawnPatch
|
||||
{
|
||||
[HarmonyPatch(nameof(RoundManager.DespawnPropsAtEndOfRound))]
|
||||
[HarmonyPatch(nameof(RoundManager.OnDestroy))]
|
||||
[HarmonyPrefix]
|
||||
static void OnDestroy(RoundManager __instance)
|
||||
{
|
||||
var _ = __instance;
|
||||
DiscoBallManager.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MuzikaGromche
|
||||
{
|
||||
#if DEBUG
|
||||
// Dumps list of tracks as a JSON on launch.
|
||||
static class Exporter
|
||||
{
|
||||
public static void ExportTracksJSON(ISelectableTrack[] tracks)
|
||||
{
|
||||
// Same directory where save files are located:
|
||||
// C:\Users\user\AppData\LocalLow\ZeekerssRBLX\Lethal Company\
|
||||
string directory = Application.persistentDataPath;
|
||||
string fileName = "MuzikaGromcheTracks.json";
|
||||
string filePath = Path.Join(directory, fileName);
|
||||
|
||||
var jsonObject = new Dictionary<string, object>();
|
||||
var tracksList = new List<object>();
|
||||
jsonObject["version"] = MyPluginInfo.PLUGIN_VERSION;
|
||||
jsonObject["tracks"] = tracksList;
|
||||
foreach (var (selectableTrack, audioTrack) in SelectTracks(tracks))
|
||||
{
|
||||
tracksList.Add(SerializeTrack(selectableTrack, audioTrack));
|
||||
}
|
||||
|
||||
using StreamWriter sw = new(filePath);
|
||||
using JsonWriter writer = new JsonTextWriter(sw)
|
||||
{
|
||||
Formatting = Formatting.Indented,
|
||||
};
|
||||
JsonSerializer serializer = new()
|
||||
{
|
||||
NullValueHandling = NullValueHandling.Include,
|
||||
};
|
||||
serializer.Serialize(writer, jsonObject);
|
||||
}
|
||||
|
||||
private static IEnumerable<(ISelectableTrack selectableTrack, IAudioTrack audioTrack)> SelectTracks(ISelectableTrack[] tracks)
|
||||
{
|
||||
foreach (var selectableTrack in tracks)
|
||||
{
|
||||
foreach (var audioTrack in selectableTrack.GetTracks())
|
||||
{
|
||||
yield return (selectableTrack, audioTrack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<string, object?> SerializeTrack(ISelectableTrack selectableTrack, IAudioTrack audioTrack)
|
||||
{
|
||||
var obj = new Dictionary<string, object?>
|
||||
{
|
||||
["Enabled"] = selectableTrack.Enabled,
|
||||
["Name"] = audioTrack.Name, // may be different from selectableTrack.Name, if selectable track is a group
|
||||
["Artist"] = selectableTrack.Artist,
|
||||
["Song"] = selectableTrack.Song,
|
||||
["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 ?? 0f,
|
||||
["FileDurationLoop"] = audioTrack.LoadedLoop?.length ?? 0f,
|
||||
["FileNameIntro"] = audioTrack.FileNameIntro,
|
||||
["FileNameLoop"] = audioTrack.FileNameLoop,
|
||||
["BeatsOffset"] = audioTrack.BeatsOffset,
|
||||
["FadeOutBeat"] = audioTrack.FadeOutBeat,
|
||||
["FadeOutDuration"] = audioTrack.FadeOutDuration,
|
||||
["ColorTransitionIn"] = audioTrack.ColorTransitionIn,
|
||||
["ColorTransitionOut"] = audioTrack.ColorTransitionOut,
|
||||
["ColorTransitionEasing"] = audioTrack.ColorTransitionEasing.Name,
|
||||
["FlickerLightsTimeSeries"] = audioTrack.FlickerLightsTimeSeries,
|
||||
["Lyrics"] = SerializeTimeSeries(new TimeSeries<string>(audioTrack.LyricsTimeSeries, audioTrack.LyricsLines)),
|
||||
["DrunknessLoopOffsetTimeSeries"] = SerializeTimeSeries(audioTrack.DrunknessLoopOffsetTimeSeries),
|
||||
["CondensationLoopOffsetTimeSeries"] = SerializeTimeSeries(audioTrack.CondensationLoopOffsetTimeSeries),
|
||||
["Palette"] = audioTrack.Palette.Colors.Select(SerializeColor).ToList(),
|
||||
["GameOverText"] = audioTrack.GameOverText,
|
||||
};
|
||||
return obj;
|
||||
}
|
||||
|
||||
private static object SerializeTimeSeries<T>(TimeSeries<T> timeSeries)
|
||||
{
|
||||
return timeSeries.Beats.Zip(timeSeries.Values, (one, two) => new object?[] { one, two }).ToList();
|
||||
}
|
||||
|
||||
private static string SerializeColor(Color color)
|
||||
{
|
||||
string colorHex = $"#{ColorUtility.ToHtmlStringRGB(color)}";
|
||||
return colorHex;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
// ReSharper disable once CheckNamespace
|
||||
#pragma warning disable IDE0130 // Namespace does not match folder structure
|
||||
namespace System.Runtime.CompilerServices
|
||||
{
|
||||
internal static class IsExternalInit;
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -2,79 +2,41 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<Title>MuzikaGromche</Title>
|
||||
<PackageId>MuzikaGromche</PackageId>
|
||||
<RootNamespace>MuzikaGromche</RootNamespace>
|
||||
<AssemblyName>Ratijas.MuzikaGromche</AssemblyName>
|
||||
<Product>Muzika Gromche</Product>
|
||||
<Description>Add some content to your inverse teleporter experience on Titan!</Description>
|
||||
<Version>1337.9001.69</Version>
|
||||
<AssemblyName>MuzikaGromche</AssemblyName>
|
||||
<Description>Opa che tut u nas</Description>
|
||||
<Version>13.37.6</Version>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<PackageReadmeFile>../README.md</PackageReadmeFile>
|
||||
<PackageProjectUrl>https://git.vilunov.me/ratijas/muzika-gromche</PackageProjectUrl>
|
||||
<RepositoryUrl>https://git.vilunov.me/ratijas/muzika-gromche</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
|
||||
<!-- NuGet Information -->
|
||||
<RestoreAdditionalProjectSources>
|
||||
https://api.nuget.org/v3/index.json;
|
||||
https://nuget.bepinex.dev/v3/index.json;
|
||||
https://nuget.windows10ce.com/nuget/v3/index.json
|
||||
</RestoreAdditionalProjectSources>
|
||||
|
||||
<!-- Prevent Publicizer Warnings from Showing -->
|
||||
<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="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" />
|
||||
<PackageReference Include="TeamBMX.LobbyCompatibility" Version="1.*" PrivateAssets="all" Private="false" />
|
||||
<PackageReference Include="BepInEx.Analyzers" Version="1.*" PrivateAssets="all"/>
|
||||
<PackageReference Include="BepInEx.Core" Version="5.*"/>
|
||||
<PackageReference Include="BepInEx.PluginInfoProps" Version="1.*"/>
|
||||
<PackageReference Include="UnityEngine.Modules" Version="2022.3.9" IncludeAssets="compile"/>
|
||||
<PackageReference Include="BepInEx.AssemblyPublicizer.MSBuild" Version="0.4.1" PrivateAssets="all" />
|
||||
<!--
|
||||
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
|
||||
-->
|
||||
<PackageReference Include="Sigurd.BepInEx.CSync" Version="5.0.1" Publicize="true" />
|
||||
<PackageReference Include="AinaVT-LethalConfig" Version="1.4.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Assembly-CSharp" Publicize="true" Private="false">
|
||||
<Reference Include="Assembly-CSharp" Publicize="true">
|
||||
<HintPath>$(LethalCompanyDir)Lethal Company_Data\Managed\Assembly-CSharp.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Unity.Collections" Private="false">
|
||||
<Reference Include="Unity.Collections">
|
||||
<HintPath>$(LethalCompanyDir)Lethal Company_Data\Managed\Unity.Collections.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Unity.Netcode.Runtime" Publicize="true" Private="false">
|
||||
<Reference Include="Unity.Netcode.Runtime" Publicize="true">
|
||||
<HintPath>$(LethalCompanyDir)Lethal Company_Data\Managed\Unity.Netcode.Runtime.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Unity.TextMeshPro" Publicize="true" Private="false">
|
||||
<HintPath>$(LethalCompanyDir)Lethal Company_Data\Managed\Unity.TextMeshPro.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.UI" Publicize="true" Private="false">
|
||||
<HintPath>$(LethalCompanyDir)Lethal Company_Data\Managed\UnityEngine.UI.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Unity.RenderPipelines.Core.Runtime" Publicize="true" Private="false">
|
||||
<HintPath>$(LethalCompanyDir)Lethal Company_Data\Managed\Unity.RenderPipelines.Core.Runtime.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json" Publicize="false" Private="false">
|
||||
<HintPath>$(LethalCompanyDir)Lethal Company_Data\Managed\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework.TrimEnd(`0123456789`))' == 'net'">
|
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.2" PrivateAssets="all" Private="false" />
|
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.2" PrivateAssets="all"/>
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="Bundle" AfterTargets="Build">
|
||||
|
|
@ -85,12 +47,9 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackagedResources Include="$(SolutionDir)README.md" />
|
||||
<PackagedResources Include="$(SolutionDir)CHANGELOG.md" />
|
||||
<PackagedResources Include="$(SolutionDir)icon.png" />
|
||||
<PackagedResources Include="$(SolutionDir)manifest.json" />
|
||||
<PackagedResources Include="$(ProjectDir)UnityAssets\muzikagromche_discoball" />
|
||||
<PackagedResources Include="$(ProjectDir)UnityAssets\muzikagromche_poweredlightsanimators" />
|
||||
<PackagedResources Include="$(TargetDir)$(AssemblyName).dll" />
|
||||
<PackagedResources Include="$(TargetDir)MuzikaGromche.dll" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -114,25 +73,4 @@
|
|||
DestinationFolder="$(SolutionDir)dist\"
|
||||
/>
|
||||
</Target>
|
||||
|
||||
<!--
|
||||
Usage:
|
||||
Set WavExportDir in props.user file.
|
||||
Run
|
||||
> dotnet msbuild /t:wav2ogg /p:TrackName=GodMode
|
||||
-->
|
||||
<Target Name="wav2ogg">
|
||||
<ItemGroup>
|
||||
<TrackNames Include="$(TrackName)Intro" />
|
||||
<TrackNames Include="$(TrackName)Loop" />
|
||||
</ItemGroup>
|
||||
<Exec Command="ffmpeg -bitexact -y -i $(WavExportDir)%(TrackNames.Identity).wav $(SolutionDir)Assets\%(TrackNames.Identity).ogg" />
|
||||
</Target>
|
||||
|
||||
<Target Name="wav2ogg1">
|
||||
<ItemGroup>
|
||||
<TrackNames Include="$(TrackName)" />
|
||||
</ItemGroup>
|
||||
<Exec Command="ffmpeg -bitexact -y -i $(WavExportDir)%(TrackNames.Identity).wav $(SolutionDir)Assets\%(TrackNames.Identity).ogg" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,346 +0,0 @@
|
|||
using DunGen;
|
||||
using HarmonyLib;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MuzikaGromche
|
||||
{
|
||||
static class PoweredLightsAnimators
|
||||
{
|
||||
private const string PoweredLightTag = "PoweredLight";
|
||||
|
||||
private delegate void ManualPatch(GameObject animatorContainer);
|
||||
|
||||
private readonly record struct AnimatorPatch(
|
||||
string AnimatorContainerPath,
|
||||
RuntimeAnimatorController AnimatorController,
|
||||
bool AddTagAndAnimator,
|
||||
ManualPatch? ManualPatch);
|
||||
|
||||
private readonly record struct TilePatch(string TileName, AnimatorPatch[] Patches)
|
||||
{
|
||||
// We are specifically looking for cloned tiles, not the original prototypes.
|
||||
public readonly string TileCloneName = $"{TileName}(Clone)";
|
||||
}
|
||||
|
||||
private readonly record struct AnimatorPatchDescriptor(
|
||||
string AnimatorContainerPath,
|
||||
string AnimatorControllerAssetPath,
|
||||
bool AddTagAndAnimator = false,
|
||||
ManualPatch? ManualPatch = null)
|
||||
{
|
||||
public AnimatorPatch Load(AssetBundle assetBundle)
|
||||
{
|
||||
var animationController = assetBundle.LoadAsset<RuntimeAnimatorController>(AnimatorControllerAssetPath)
|
||||
?? throw new FileNotFoundException($"RuntimeAnimatorController not found: {AnimatorControllerAssetPath}", AnimatorControllerAssetPath);
|
||||
return new(AnimatorContainerPath, animationController, AddTagAndAnimator, ManualPatch);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly record struct TilePatchDescriptor(string[] TileNames, AnimatorPatchDescriptor[] Descriptors)
|
||||
{
|
||||
public TilePatchDescriptor(string TileName, AnimatorPatchDescriptor[] Descriptors)
|
||||
: this([TileName], Descriptors)
|
||||
{
|
||||
}
|
||||
|
||||
public TilePatch[] Load(AssetBundle assetBundle)
|
||||
{
|
||||
var patches = Descriptors.Select(d => d.Load(assetBundle)).ToArray();
|
||||
return [.. TileNames.Select(tileName => new TilePatch(tileName, patches))];
|
||||
}
|
||||
}
|
||||
|
||||
private static IDictionary<string, TilePatch> Patches = new Dictionary<string, TilePatch>();
|
||||
|
||||
private static AudioClip AudioClipOn = null!;
|
||||
private static AudioClip AudioClipOff = null!;
|
||||
private static AudioClip AudioClipFlicker = null!;
|
||||
|
||||
public static void Load()
|
||||
{
|
||||
const string BundleFileName = "muzikagromche_poweredlightsanimators";
|
||||
string bundlePath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), BundleFileName);
|
||||
var assetBundle = AssetBundle.LoadFromFile(bundlePath)
|
||||
?? throw new NullReferenceException("Failed to load bundle");
|
||||
|
||||
AudioClipOn = assetBundle.LoadAsset<AudioClip>("Assets/LethalCompany/Mods/MuzikaGromche/AudioClips/LightOn.ogg");
|
||||
AudioClipOff = assetBundle.LoadAsset<AudioClip>("Assets/LethalCompany/Mods/MuzikaGromche/AudioClips/LightOff.ogg");
|
||||
AudioClipFlicker = assetBundle.LoadAsset<AudioClip>("Assets/LethalCompany/Mods/MuzikaGromche/AudioClips/LightFlicker.ogg");
|
||||
|
||||
const string BasePath = "Assets/LethalCompany/Mods/MuzikaGromche/AnimatorControllers/";
|
||||
|
||||
const string PointLight4 = $"{BasePath}Point Light (4) (Patched).controller";
|
||||
const string MineshaftSpotlight = $"{BasePath}MineshaftSpotlight (Patched).controller";
|
||||
const string LightbulbsLine = $"{BasePath}lightbulbsLineMesh (Patched).controller";
|
||||
const string CeilingFan = $"{BasePath}CeilingFan (originally GameObject) (Patched).controller";
|
||||
const string LEDHangingLight = $"{BasePath}LEDHangingLight (Patched).controller";
|
||||
const string MineshaftStartTileSpotlight = $"{BasePath}MineshaftStartTileSpotlight (New).controller";
|
||||
|
||||
TilePatchDescriptor[] descriptors =
|
||||
[
|
||||
// any version
|
||||
new("KitchenTile", [
|
||||
new("PoweredLightTypeB", PointLight4),
|
||||
new("PoweredLightTypeB (1)", PointLight4),
|
||||
]),
|
||||
// < v70
|
||||
new("ManorStartRoom", [
|
||||
new("ManorStartRoom/Chandelier/PoweredLightTypeB (1)", PointLight4),
|
||||
new("ManorStartRoom/Chandelier2/PoweredLightTypeB", PointLight4),
|
||||
]),
|
||||
// v70+
|
||||
new("ManorStartRoomSmall", [
|
||||
new("ManorStartRoomMesh/Chandelier/PoweredLightTypeB (1)", PointLight4),
|
||||
new("ManorStartRoomMesh/Chandelier2/PoweredLightTypeB", PointLight4),
|
||||
]),
|
||||
new("NarrowHallwayTile2x2", [
|
||||
new("MineshaftSpotlight (1)", MineshaftSpotlight),
|
||||
new("MineshaftSpotlight (2)", MineshaftSpotlight),
|
||||
]),
|
||||
new("BirthdayRoomTile", [
|
||||
new("Lights/MineshaftSpotlight", MineshaftSpotlight),
|
||||
]),
|
||||
new("BathroomTileContainer", [
|
||||
new("MineshaftSpotlight", MineshaftSpotlight),
|
||||
new("LightbulbLine/lightbulbsLineMesh", LightbulbsLine),
|
||||
]),
|
||||
new(["BedroomTile", "BedroomTileB"], [
|
||||
new("CeilingFanAnimContainer", CeilingFan),
|
||||
new("MineshaftSpotlight (1)", MineshaftSpotlight),
|
||||
]),
|
||||
new("GarageTile", [
|
||||
new("HangingLEDBarLight (3)", LEDHangingLight),
|
||||
new("HangingLEDBarLight (4)", LEDHangingLight),
|
||||
// This HangingLEDBarLight's IndirectLight is wrongly named, so animator couldn't find it
|
||||
// renamed by WaterGun-V70PoweredLights_Fix-1.1.0
|
||||
// ManualPatch: RenameGameObjectPatch("IndirectLight (1)", "IndirectLight")),
|
||||
]),
|
||||
new("PoolTile", [
|
||||
new("PoolLights/HangingLEDBarLight", LEDHangingLight),
|
||||
new("PoolLights/HangingLEDBarLight (4)", LEDHangingLight),
|
||||
new("PoolLights/HangingLEDBarLight (5)", LEDHangingLight),
|
||||
]),
|
||||
new("MineshaftStartTile", [
|
||||
new("Cylinder.001 (1)", MineshaftStartTileSpotlight, AddTagAndAnimator: true),
|
||||
new("Cylinder.001 (2)", MineshaftStartTileSpotlight, AddTagAndAnimator: true),
|
||||
]),
|
||||
];
|
||||
|
||||
Patches = descriptors
|
||||
.SelectMany(d => d.Load(assetBundle))
|
||||
.ToDictionary(d => d.TileCloneName, d => d);
|
||||
}
|
||||
|
||||
public static void Patch(Tile tile)
|
||||
{
|
||||
if (tile == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tile));
|
||||
}
|
||||
|
||||
if (Patches.TryGetValue(tile.gameObject.name, out var tilePatch))
|
||||
{
|
||||
foreach (var patch in tilePatch.Patches)
|
||||
{
|
||||
Transform animationContainerTransform = tile.gameObject.transform.Find(patch.AnimatorContainerPath);
|
||||
if (animationContainerTransform == null)
|
||||
{
|
||||
#if DEBUG
|
||||
throw new NullReferenceException($"{tilePatch.TileName}/{patch.AnimatorContainerPath} Animation Container not found");
|
||||
#endif
|
||||
#pragma warning disable CS0162 // Unreachable code detected
|
||||
continue;
|
||||
#pragma warning restore CS0162 // Unreachable code detected
|
||||
}
|
||||
|
||||
GameObject animationContainer = animationContainerTransform.gameObject;
|
||||
Animator animator = animationContainer.GetComponent<Animator>();
|
||||
|
||||
if (patch.AddTagAndAnimator)
|
||||
{
|
||||
animationContainer.tag = PoweredLightTag;
|
||||
if (animator == null)
|
||||
{
|
||||
animator = animationContainer.AddComponent<Animator>();
|
||||
}
|
||||
if (!animationContainer.TryGetComponent<PlayAudioAnimationEvent>(out var audioScript))
|
||||
{
|
||||
audioScript = animationContainer.AddComponent<PlayAudioAnimationEvent>();
|
||||
audioScript.audioClip = AudioClipOn;
|
||||
audioScript.audioClip2 = AudioClipOff;
|
||||
audioScript.audioClip3 = AudioClipFlicker;
|
||||
}
|
||||
if (!animationContainer.TryGetComponent<AudioSource>(out var audioSource))
|
||||
{
|
||||
// Copy from an existing AudioSource of another light animator
|
||||
var otherSource = tile.gameObject.GetComponentInChildren<AudioSource>();
|
||||
if (otherSource != null)
|
||||
{
|
||||
audioSource = animationContainer.AddComponent<AudioSource>();
|
||||
audioSource.spatialBlend = 1;
|
||||
audioSource.playOnAwake = false;
|
||||
audioSource.outputAudioMixerGroup = otherSource.outputAudioMixerGroup;
|
||||
audioSource.spread = otherSource.spread;
|
||||
audioSource.rolloffMode = otherSource.rolloffMode;
|
||||
audioSource.maxDistance = otherSource.maxDistance;
|
||||
audioSource.SetCustomCurve(AudioSourceCurveType.CustomRolloff, otherSource.GetCustomCurve(AudioSourceCurveType.CustomRolloff));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (animator == null)
|
||||
{
|
||||
#if DEBUG
|
||||
throw new NullReferenceException($"{tilePatch.TileName}/{patch.AnimatorContainerPath} Animation Component not found");
|
||||
#endif
|
||||
#pragma warning disable CS0162 // Unreachable code detected
|
||||
continue;
|
||||
#pragma warning restore CS0162 // Unreachable code detected
|
||||
}
|
||||
|
||||
patch.ManualPatch?.Invoke(animationContainer);
|
||||
|
||||
animator.runtimeAnimatorController = patch.AnimatorController;
|
||||
Plugin.Log.LogDebug($"{nameof(PoweredLightsAnimatorsPatch)} {tilePatch.TileName}/{patch.AnimatorContainerPath}: Replaced animator controller");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static ManualPatch RenameGameObjectPatch(string relativePath, string newName) => animatorContainer =>
|
||||
{
|
||||
var targetObject = animatorContainer.transform.Find(relativePath)?.gameObject;
|
||||
if (targetObject == null)
|
||||
{
|
||||
#if DEBUG
|
||||
throw new NullReferenceException($"{animatorContainer.name}/{relativePath}: GameObject not found!");
|
||||
#endif
|
||||
#pragma warning disable CS0162 // Unreachable code detected
|
||||
return;
|
||||
#pragma warning restore CS0162 // Unreachable code detected
|
||||
}
|
||||
targetObject.name = newName;
|
||||
Plugin.Log.LogDebug($"{nameof(PoweredLightsAnimatorsPatch)} {animatorContainer.name}/{relativePath}: Renamed GameObject");
|
||||
};
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(Tile))]
|
||||
static class PoweredLightsAnimatorsPatch
|
||||
{
|
||||
[HarmonyPatch(nameof(Tile.AddTriggerVolume))]
|
||||
[HarmonyPostfix]
|
||||
static void OnAddTriggerVolume(Tile __instance)
|
||||
{
|
||||
PoweredLightsAnimators.Patch(__instance);
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(RoundManager))]
|
||||
static class AllPoweredLightsPatch
|
||||
{
|
||||
// Vanilla method assumes that GameObjects with tag "PoweredLight" only contain a single Light component each.
|
||||
// This is, however, not true for certains double-light setups, such as:
|
||||
// - double PointLight (even though one of them is 'Point' another is 'Spot') inside CeilingFanAnimContainer in BedroomTile/BedroomTileB;
|
||||
// - MineshaftSpotlight when it has not only `Point Light` but also `IndirectLight` in BirthdayRoomTile;
|
||||
// - (maybe more?)
|
||||
// In order to fix that, replace singular GetComponentInChildren<Light> with plural GetComponentsInChildren<Light> version.
|
||||
[HarmonyPatch(nameof(RoundManager.RefreshLightsList))]
|
||||
[HarmonyPrefix]
|
||||
static bool OnRefreshLightsList(RoundManager __instance)
|
||||
{
|
||||
RefreshLightsListPatched(__instance);
|
||||
|
||||
var behaviour = __instance.gameObject.GetComponent<PoweredLightsBehaviour>() ?? __instance.gameObject.AddComponent<PoweredLightsBehaviour>();
|
||||
behaviour.Refresh();
|
||||
|
||||
// Skip the original method
|
||||
return false;
|
||||
}
|
||||
|
||||
static void RefreshLightsListPatched(RoundManager self)
|
||||
{
|
||||
// Reusable list to reduce allocations
|
||||
List<Light> lights = [];
|
||||
|
||||
self.allPoweredLights.Clear();
|
||||
self.allPoweredLightsAnimators.Clear();
|
||||
|
||||
GameObject[] gameObjects = GameObject.FindGameObjectsWithTag("PoweredLight");
|
||||
if (gameObjects == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var gameObject in gameObjects)
|
||||
{
|
||||
Animator animator = gameObject.GetComponentInChildren<Animator>();
|
||||
if (!(animator == null))
|
||||
{
|
||||
self.allPoweredLightsAnimators.Add(animator);
|
||||
// Patched section: Use list instead of singular GetComponentInChildren<Light>
|
||||
gameObject.GetComponentsInChildren(includeInactive: true, lights);
|
||||
self.allPoweredLights.AddRange(lights);
|
||||
}
|
||||
}
|
||||
foreach (var animator in self.allPoweredLightsAnimators)
|
||||
{
|
||||
animator.SetFloat("flickerSpeed", UnityEngine.Random.Range(0.6f, 1.4f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class PoweredLightsBehaviour : MonoBehaviour
|
||||
{
|
||||
private struct LightData
|
||||
{
|
||||
public Light Light;
|
||||
public Color InitialColor;
|
||||
}
|
||||
|
||||
private readonly List<LightData> AllPoweredLights = [];
|
||||
|
||||
public static PoweredLightsBehaviour Instance { get; private set; } = null!;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (Instance == null)
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
AllPoweredLights.Clear();
|
||||
foreach (var light in RoundManager.Instance.allPoweredLights)
|
||||
{
|
||||
AllPoweredLights.Add(new LightData
|
||||
{
|
||||
Light = light,
|
||||
InitialColor = light.color,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void SetLightColor(SetLightsColorEvent e)
|
||||
{
|
||||
foreach (var data in AllPoweredLights)
|
||||
{
|
||||
var color = e.GetColor(data.InitialColor);
|
||||
data.Light.color = color;
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetLightColor()
|
||||
{
|
||||
foreach (var data in AllPoweredLights)
|
||||
{
|
||||
data.Light.color = data.InitialColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,146 +0,0 @@
|
|||
using System.Collections;
|
||||
using HarmonyLib;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MuzikaGromche
|
||||
{
|
||||
static class ScreenFiltersManager
|
||||
{
|
||||
private const float VibilityThreshold = 0.01f;
|
||||
|
||||
private static bool drunknessChangedThisFrame = false;
|
||||
private static float drunkness = 0f;
|
||||
public static float Drunkness
|
||||
{
|
||||
get => drunkness;
|
||||
set
|
||||
{
|
||||
drunkness = value;
|
||||
drunknessChangedThisFrame = true;
|
||||
ScheduleUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
private static bool helmetCondensationDropsChangedThisFrame = false;
|
||||
private static float helmetCondensationDrops = 0f;
|
||||
public static float HelmetCondensationDrops
|
||||
{
|
||||
get => helmetCondensationDrops;
|
||||
set
|
||||
{
|
||||
helmetCondensationDrops = value;
|
||||
helmetCondensationDropsChangedThisFrame = true;
|
||||
ScheduleUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
private static Coroutine? scheduledUpdate = null;
|
||||
|
||||
private static void ScheduleUpdate()
|
||||
{
|
||||
CancelScheduledUpdate();
|
||||
scheduledUpdate = HUDManager.Instance.StartCoroutine(ScheduledUpdate());
|
||||
}
|
||||
|
||||
private static void CancelScheduledUpdate()
|
||||
{
|
||||
if (scheduledUpdate != null)
|
||||
{
|
||||
HUDManager.Instance.StopCoroutine(scheduledUpdate);
|
||||
scheduledUpdate = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerator ScheduledUpdate()
|
||||
{
|
||||
try
|
||||
{
|
||||
yield return new WaitForEndOfFrame();
|
||||
Update();
|
||||
}
|
||||
finally
|
||||
{
|
||||
scheduledUpdate = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void Update()
|
||||
{
|
||||
CancelScheduledUpdate();
|
||||
var hud = HUDManager.Instance;
|
||||
|
||||
if (!drunknessChangedThisFrame)
|
||||
{
|
||||
// animated roll-off
|
||||
drunkness = Mathf.Clamp(drunkness - Time.deltaTime / 2f, 0f, 1f);
|
||||
}
|
||||
drunknessChangedThisFrame = false;
|
||||
if (drunkness > VibilityThreshold)
|
||||
{
|
||||
var moddedDrunknessFilterWeight = StartOfRound.Instance.drunknessSideEffect.Evaluate(drunkness);
|
||||
var moddedGasImageAlphaAlpha = moddedDrunknessFilterWeight * 1.5f;
|
||||
// set the final value to the greatest of the two, so that we don't accidentally undo TZP's visual effect.
|
||||
hud.drunknessFilter.weight = Mathf.Max(hud.drunknessFilter.weight, moddedDrunknessFilterWeight);
|
||||
hud.gasImageAlpha.alpha = Mathf.Max(hud.gasImageAlpha.alpha, moddedGasImageAlphaAlpha);
|
||||
// Image alpha only makes sense if the animator is running
|
||||
hud.gasHelmetAnimator.SetBool("gasEmitting", value: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
ClearDrunkness();
|
||||
}
|
||||
|
||||
if (!helmetCondensationDropsChangedThisFrame)
|
||||
{
|
||||
// animated roll-off
|
||||
helmetCondensationDrops = Mathf.Clamp(helmetCondensationDrops - Time.deltaTime / 6f, 0f, 1f);
|
||||
}
|
||||
helmetCondensationDropsChangedThisFrame = false;
|
||||
if (helmetCondensationDrops > VibilityThreshold)
|
||||
{
|
||||
// HelmetCondensationDrops
|
||||
Color color = hud.helmetCondensationMaterial.color;
|
||||
// set the final value to the greatest of the two, so that we don't accidentally undo steam's visual effect.
|
||||
color.a = Mathf.Clamp(Mathf.Max(color.a, helmetCondensationDrops), 0f, 0.27f);
|
||||
hud.helmetCondensationMaterial.color = color;
|
||||
}
|
||||
else
|
||||
{
|
||||
ClearCondensation();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Clear()
|
||||
{
|
||||
ClearDrunkness();
|
||||
ClearCondensation();
|
||||
}
|
||||
|
||||
private static void ClearDrunkness()
|
||||
{
|
||||
drunkness = 0f;
|
||||
// Only the stop animation if vanilla doesn't animate TZP right now.
|
||||
if (GameNetworkManager.Instance.localPlayerController.drunkness == 0f)
|
||||
{
|
||||
HUDManager.Instance.gasHelmetAnimator.SetBool("gasEmitting", value: false);
|
||||
}
|
||||
// Vanilla will set drunknessFilter.weight and gasImageAlpha.alpha on the next Update anyway.
|
||||
}
|
||||
|
||||
private static void ClearCondensation()
|
||||
{
|
||||
helmetCondensationDrops = 0f;
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(HUDManager))]
|
||||
internal static class HUDManagerScreenFiltersPatch
|
||||
{
|
||||
[HarmonyPatch(nameof(HUDManager.SetScreenFilters))]
|
||||
[HarmonyPostfix]
|
||||
static void SetScreenFiltersPostfix(HUDManager __instance)
|
||||
{
|
||||
Update();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
using HarmonyLib;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MuzikaGromche
|
||||
{
|
||||
[HarmonyPatch(typeof(RoundManager))]
|
||||
static class SpawnRatePatch
|
||||
{
|
||||
const string JesterEnemyName = "Jester";
|
||||
|
||||
// GetRandomWeightedIndex is not only called from AssignRandomEnemyToVent,
|
||||
// so in order to differentiate it from other calls, prefix assigns these
|
||||
// global variables, and postfix cleans them up.
|
||||
|
||||
// If set to null, do not override spawn chances.
|
||||
// Otherwise, it is an index of Jester in RoundManager.currentLevel.Enemies
|
||||
static int? EnemyIndex = null;
|
||||
static float SpawnTime = 0f;
|
||||
|
||||
[HarmonyPatch(nameof(RoundManager.AssignRandomEnemyToVent))]
|
||||
[HarmonyPrefix]
|
||||
static void AssignRandomEnemyToVentPrefix(RoundManager __instance, EnemyVent vent, float spawnTime)
|
||||
{
|
||||
if (!Config.OverrideSpawnRates.Value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var index = __instance.currentLevel.Enemies.FindIndex(enemy => enemy.enemyType.enemyName == JesterEnemyName);
|
||||
if (index == -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnemyIndex = index;
|
||||
SpawnTime = spawnTime;
|
||||
}
|
||||
|
||||
[HarmonyPatch(nameof(RoundManager.AssignRandomEnemyToVent))]
|
||||
[HarmonyPostfix]
|
||||
static void AssignRandomEnemyToVentPostfix(RoundManager __instance, EnemyVent vent, float spawnTime)
|
||||
{
|
||||
EnemyIndex = null;
|
||||
SpawnTime = 0f;
|
||||
}
|
||||
|
||||
[HarmonyPatch(nameof(RoundManager.GetRandomWeightedIndex))]
|
||||
[HarmonyPrefix]
|
||||
static void GetRandomWeightedIndexPostfix(RoundManager __instance, int[] weights, System.Random randomSeed)
|
||||
{
|
||||
if (EnemyIndex is int index)
|
||||
{
|
||||
if (__instance.EnemyCannotBeSpawned(index))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 0 == 6:00 AM
|
||||
// 60 == 7:00 AM
|
||||
// 100 == 7:40 AM (Cycle #1)
|
||||
// 120 == 8:00 AM
|
||||
// 180 == 9:00 AM (Cycle #2)
|
||||
// 300 == 11:00 AM (Cycle #3)
|
||||
// 420 == 1:00 PM (Cycle #4)
|
||||
// 540 == 3:00 PM (Cycle #5)
|
||||
// 660 ~= 5:00 PM
|
||||
// 780 ~= 7:00 PM
|
||||
// 900 ~= 9:00 PM
|
||||
// 1020 ~= 11:00 PM
|
||||
// 1080 == 12:00 AM
|
||||
const float minMultiplierTime = 200f; // 9:20 AM
|
||||
const float maxMultiplierTime = 500f; // 2:00 PM
|
||||
var normalizedMultiplierTime = Mathf.Clamp((SpawnTime - minMultiplierTime) / (maxMultiplierTime - minMultiplierTime), 0f, 1f);
|
||||
// Start slowly, then escalate it quickly
|
||||
normalizedMultiplierTime = Easing.InCubic.Eval(normalizedMultiplierTime);
|
||||
|
||||
const float minMultiplier = 1f;
|
||||
const float maxMultiplier = 15f;
|
||||
var multiplier = Mathf.Lerp(minMultiplier, maxMultiplier, normalizedMultiplierTime);
|
||||
|
||||
var newWeight = Mathf.FloorToInt(weights[index] * multiplier);
|
||||
Plugin.Log.LogInfo($"{nameof(SpawnRatePatch)} Overriding spawn weight[{index}] {weights[index]} * {multiplier} => {newWeight} for t={SpawnTime}");
|
||||
weights[index] = newWeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue