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:
|
clean:
|
||||||
rm -rf dist MuzikaGromche/bin MuzikaGromche/obj
|
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:
|
install-imperium:
|
||||||
rm -rf "{{ plugin_dir }}"
|
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
|
jq --indent 4 --arg v "{{ version_number }}" '.version_number = $v' < manifest.json > manifest.json.copy
|
||||||
mv manifest.json.copy manifest.json
|
mv manifest.json.copy manifest.json
|
||||||
sed -i 's/<Version>.*<\/Version>/<Version>{{ version_number }}<\/Version>/' MuzikaGromche/MuzikaGromche.csproj
|
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>
|
<Project>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!-- Copy this file to MuzikaGromche.props.user and uncomment one of two paths below: -->
|
<!-- Copy this file to MuzikaGromche.props.user and uncomment one of two paths below: -->
|
||||||
|
|
||||||
<!-- On Linux: -->
|
<!-- On Linux: -->
|
||||||
<!-- <LethalCompanyDir>$(HOME)/.local/share/Steam/steamapps/common/Lethal Company/</LethalCompanyDir> -->
|
<!-- <LethalCompanyDir>$(HOME)/.local/share/Steam/steamapps/common/Lethal Company/</LethalCompanyDir> -->
|
||||||
<!-- <WavExportDir>\home\ratijas\Music\SFX\Export</WavExportDir> -->
|
|
||||||
|
|
||||||
<!-- On Windows: -->
|
<!-- On Windows: -->
|
||||||
<!-- <LethalCompanyDir>C:/Program Files (x86)/Steam/steamapps/common/Lethal Company/</LethalCompanyDir> -->
|
<!-- <LethalCompanyDir>C:/Program Files (x86)/Steam/steamapps/common/Lethal Company/</LethalCompanyDir> -->
|
||||||
<!-- <WavExportDir>D:\Code\MuzikaGromcheAudio\Export</WavExportDir> -->
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</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>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.1</TargetFramework>
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
<Title>MuzikaGromche</Title>
|
<AssemblyName>MuzikaGromche</AssemblyName>
|
||||||
<PackageId>MuzikaGromche</PackageId>
|
<Description>Opa che tut u nas</Description>
|
||||||
<RootNamespace>MuzikaGromche</RootNamespace>
|
<Version>13.37.6</Version>
|
||||||
<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>
|
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<LangVersion>latest</LangVersion>
|
<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>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BepInEx.Analyzers" Version="1.*" PrivateAssets="all" Private="false" />
|
<PackageReference Include="BepInEx.Analyzers" Version="1.*" PrivateAssets="all"/>
|
||||||
<PackageReference Include="BepInEx.Core" Version="5.*" PrivateAssets="all" Private="false" />
|
<PackageReference Include="BepInEx.Core" Version="5.*"/>
|
||||||
<PackageReference Include="BepInEx.PluginInfoProps" Version="2.*" PrivateAssets="all"/>
|
<PackageReference Include="BepInEx.PluginInfoProps" Version="1.*"/>
|
||||||
<PackageReference Include="UnityEngine.Modules" Version="2022.3.9" PrivateAssets="all" Private="false" />
|
<PackageReference Include="UnityEngine.Modules" Version="2022.3.9" IncludeAssets="compile"/>
|
||||||
<PackageReference Include="BepInEx.AssemblyPublicizer.MSBuild" Version="0.4.1" PrivateAssets="all" Private="false" />
|
<PackageReference Include="BepInEx.AssemblyPublicizer.MSBuild" Version="0.4.1" PrivateAssets="all" />
|
||||||
<PackageReference Include="AinaVT-LethalConfig" Version="1.4.6" PrivateAssets="all" Private="false" />
|
<!--
|
||||||
<PackageReference Include="TeamBMX.LobbyCompatibility" Version="1.*" PrivateAssets="all" Private="false" />
|
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>
|
||||||
|
|
||||||
<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>
|
<HintPath>$(LethalCompanyDir)Lethal Company_Data\Managed\Assembly-CSharp.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="Unity.Collections" Private="false">
|
<Reference Include="Unity.Collections">
|
||||||
<HintPath>$(LethalCompanyDir)Lethal Company_Data\Managed\Unity.Collections.dll</HintPath>
|
<HintPath>$(LethalCompanyDir)Lethal Company_Data\Managed\Unity.Collections.dll</HintPath>
|
||||||
</Reference>
|
</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>
|
<HintPath>$(LethalCompanyDir)Lethal Company_Data\Managed\Unity.Netcode.Runtime.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="Unity.TextMeshPro" Publicize="true" Private="false">
|
|
||||||
<HintPath>$(LethalCompanyDir)Lethal Company_Data\Managed\Unity.TextMeshPro.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="UnityEngine.UI" Publicize="true" Private="false">
|
|
||||||
<HintPath>$(LethalCompanyDir)Lethal Company_Data\Managed\UnityEngine.UI.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="Unity.RenderPipelines.Core.Runtime" Publicize="true" Private="false">
|
|
||||||
<HintPath>$(LethalCompanyDir)Lethal Company_Data\Managed\Unity.RenderPipelines.Core.Runtime.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="Newtonsoft.Json" Publicize="false" Private="false">
|
|
||||||
<HintPath>$(LethalCompanyDir)Lethal Company_Data\Managed\Newtonsoft.Json.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="'$(TargetFramework.TrimEnd(`0123456789`))' == 'net'">
|
<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>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="Bundle" AfterTargets="Build">
|
<Target Name="Bundle" AfterTargets="Build">
|
||||||
|
|
@ -85,12 +47,9 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackagedResources Include="$(SolutionDir)README.md" />
|
<PackagedResources Include="$(SolutionDir)README.md" />
|
||||||
<PackagedResources Include="$(SolutionDir)CHANGELOG.md" />
|
|
||||||
<PackagedResources Include="$(SolutionDir)icon.png" />
|
<PackagedResources Include="$(SolutionDir)icon.png" />
|
||||||
<PackagedResources Include="$(SolutionDir)manifest.json" />
|
<PackagedResources Include="$(SolutionDir)manifest.json" />
|
||||||
<PackagedResources Include="$(ProjectDir)UnityAssets\muzikagromche_discoball" />
|
<PackagedResources Include="$(TargetDir)MuzikaGromche.dll" />
|
||||||
<PackagedResources Include="$(ProjectDir)UnityAssets\muzikagromche_poweredlightsanimators" />
|
|
||||||
<PackagedResources Include="$(TargetDir)$(AssemblyName).dll" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
@ -114,25 +73,4 @@
|
||||||
DestinationFolder="$(SolutionDir)dist\"
|
DestinationFolder="$(SolutionDir)dist\"
|
||||||
/>
|
/>
|
||||||
</Target>
|
</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>
|
</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