Compare commits

..

105 Commits
master ... dev

Author SHA1 Message Date
ivan tkachenko 6a9ea8d4af Release v1337.420.9001 2025-08-14 19:17:35 +03:00
ivan tkachenko 42c6179ba5 Add new track Beha with three variants of intro 2025-08-14 19:13:20 +03:00
ivan tkachenko 5649a18633 Split Track into Selectable and Audio interfaces, add support for groups 2025-08-14 18:48:54 +03:00
ivan tkachenko 47f984cd28 Allow tracks to share common audio clip files
Send one request per file name. File names can be explicitly overridden.
2025-08-14 15:38:59 +03:00
ivan tkachenko fc3a62e511 Rename Start segment to Intro to reduce some confusion
Confusingly, "start" may refer to too many things in different places,
while "intro" would unambiguously refer to an audio clip that plays
first before the loop starts.
2025-08-14 15:11:46 +03:00
ivan tkachenko 5f0c890682 Remove unused method 2025-08-14 15:09:31 +03:00
ivan tkachenko 59a069f51b Bump version 2025-08-14 15:09:27 +03:00
ivan tkachenko df796965f2 Release v1337.420.69 2025-08-11 22:28:57 +03:00
ivan tkachenko 26f9d2cf9f Print tracks length in debug builds, and remove unnecessary non-null assertion 2025-08-11 22:28:32 +03:00
ivan tkachenko a950093f8e Sort tracks by name, so they are easier to find in the config 2025-08-11 22:28:32 +03:00
ivan tkachenko 8842005898 Add new track BeefLiver 2025-08-11 22:28:31 +03:00
ivan tkachenko b4ae4bad41 Config: More usable range for fading out 2025-08-11 22:28:31 +03:00
ivan tkachenko 69e64397a0 Extrapolate AudioSource playback time to get smoother transitions
AudioSource only updates about 25 times per second, meaning that even at
30 fps some adjacent frames would be calculated as having exact same
timestamps and render duplicated colors. At 100+ fps more than 2/3 of
the frames would be duplicates.

As a drive-by change, split complex logic of BeatTimeState into smaller
classes. Most of the time the state needs to maintain some boolean flag
which it flips once and stays that way, like HasStarted, IsLooping.
2025-08-11 22:28:31 +03:00
ivan tkachenko 3d0795f04d Drop CSync as a dependency from Release builds
Since the rewrite of track selection to a custom netcode, CSync is only
needed for debug/development builds now.
2025-08-11 22:28:31 +03:00
ivan tkachenko 4abd0fb612 Fix stale event handlers causing errors in console 2025-08-11 22:28:30 +03:00
ivan tkachenko dd3c9647e3 Bump version 2025-08-11 22:28:29 +03:00
ivan tkachenko 8b2f4428bb Release v1337.69.420 2025-08-07 20:27:58 +03:00
ivan tkachenko 0dca416958 Rewrite track choosing event to custom netcode 2025-08-07 20:27:57 +03:00
ivan tkachenko 1aa8c1ddfa Fix Disco Ball hanging around after being disabled 2025-08-07 20:27:57 +03:00
ivan tkachenko 75d0ee2c1d Bump version 2025-08-07 20:27:57 +03:00
ivan tkachenko 2e938dfc8d Release v13.37.9001 2025-08-05 05:10:48 +03:00
ivan tkachenko 1ffdd5d97e Add spawn rate patch to make the event more likely 2025-08-05 05:10:21 +03:00
ivan tkachenko 276fbbec22 Clean up mention of removed config option "Enable Color Animations"
Amends 2a28a36a69
2025-08-05 05:10:11 +03:00
ivan tkachenko 05749ff122 Add Animator and Audio to MineshaftStartTile 2025-08-03 00:31:07 +03:00
ivan tkachenko f131ad7148 Fix NarrowHallwayTile2x2 mineshaft lights flickering 2025-08-03 00:31:07 +03:00
ivan tkachenko f50989b5ae Refactor: Optimize DiscoBallManager to create and cache at start of round 2025-08-03 00:31:06 +03:00
ivan tkachenko 72adb9e713 Refactor: Fix up visibility and static modifiers, and other minor things 2025-08-02 16:25:45 +03:00
ivan tkachenko 76e9ca3595 Refactor: Make State an internal class of JesterPatch class 2025-08-02 16:12:44 +03:00
ivan tkachenko b6f2ca355b Refactor: Factor out displaying lyrics as a tip in its own method 2025-08-02 15:54:07 +03:00
ivan tkachenko 78370da460 Fix LEDHangingLight (GarageTile & PoolTile) lights flickering 2025-08-02 15:50:59 +03:00
ivan tkachenko 4d84a2d001 Fix multiple Light components per animator
Add them all to the allPoweredLights list,
not just the whatever first one was found.
2025-08-02 15:50:59 +03:00
ivan tkachenko 0eb02698eb Fix KitchenTile lights flickering 2025-08-02 01:04:12 +03:00
ivan tkachenko c7b67b9042 Update manifest, README and project files 2025-08-02 01:04:11 +03:00
ivan tkachenko f53f837e3f Bundle CHANGELOG.md 2025-08-01 23:10:36 +03:00
ivan tkachenko 86644388f3 Bump version 2025-08-01 23:10:35 +03:00
ivan tkachenko c0e7185321 Release v13.37.1337 2025-08-01 16:49:42 +03:00
ivan tkachenko 9062f386de Fix/add light flickering with animator controllers 2025-08-01 16:48:16 +03:00
ivan tkachenko 3a2eaad493 Add more light flickering to the track Kach 2025-08-01 02:55:27 +03:00
ivan tkachenko b70e868ac4 Rename DiscoBall asset bundle
There is going to be another bundle, so we want some distinctive names.
2025-07-31 21:44:52 +03:00
ivan tkachenko bacb9f07c7 Use StartOfRound.Instance.audioListener for lyrics events
Probably doesn't make a difference, but it's nice to be able to
calculate audio source<->listener distance directly.
2025-07-30 20:09:17 +03:00
ivan tkachenko 2a28a36a69 Config: Remove EnableColorAnimations toggle
Turns out, it doesn't really affect anything. AMD on Linux would lag anyway.
2025-07-30 18:56:34 +03:00
ivan tkachenko 841ccc74ed Fix color transition from a negative beat 2025-07-30 18:56:33 +03:00
ivan tkachenko 8729515537 Fix timings of fade out and lyrics for DeployDestroy 2025-07-30 18:56:33 +03:00
ivan tkachenko 991e2a56b7 Fix color right before wrapping
The buggy Split method was erroneously creating a looping span despite
explicitly passing `isLooping: false` parameter because with
`beatToInclusive: LoopBeats` wrapping will occur regardless. This
messed up with Duration calculations, and eventually caused the last
beat default to transition with t=0, when it should really be static.
2025-07-30 18:56:33 +03:00
ivan tkachenko c689198588 Fix fading out: set pure black at the end 2025-07-30 18:56:33 +03:00
ivan tkachenko 667368d719 Add specialized color transition event to improve debug output 2025-07-30 18:37:59 +03:00
ivan tkachenko 6a0be0d780 Enable nullable reference types 2025-07-30 18:37:58 +03:00
ivan tkachenko 0573091162 Auto formatting 2025-07-30 18:37:58 +03:00
ivan tkachenko 2ef0fc3bd9 Fix up all logs to use nameof() instead of hardcoded string 2025-07-30 18:37:58 +03:00
ivan tkachenko ce437aa86c Events: Mark BaseEvent as abstract
It's not useful on its own
2025-07-30 18:37:57 +03:00
ivan tkachenko 7ed299ead8 Fix AudioSource distance check for lyrics event
It was checking maxDistance of a non-overridden loop clip during windup.
2025-07-30 18:37:56 +03:00
ivan tkachenko f959a4ebb2 Setup LobbyCompatibility as a dependency
This should help to avoid desync issues.
2025-07-30 01:29:07 +03:00
ivan tkachenko 7a5013524d Prevent Publicizer Warnings from Showing 2025-07-30 00:08:08 +03:00
ivan tkachenko 14a57fcae7 Mark referenced packages with Private attributes
Apparently, this is considered a good practice. Although Private="false"
is supposed to not copy the dependency into the output directory, which
didn't happen anyway?
2025-07-30 00:08:08 +03:00
ivan tkachenko 47876b18bf Fix up csproj XML formatting 2025-07-29 23:45:20 +03:00
ivan tkachenko 5abad0b1ba Bump version 2025-07-29 23:45:19 +03:00
ivan tkachenko 1cdbdf2f09 Release v13.37.911 2025-07-21 19:16:25 +03:00
ivan tkachenko 45a73793fb Add support for pre-v70 Mansion Main tile. 2025-07-21 19:07:36 +03:00
ivan tkachenko 581d9701bd Remove redundant call to private method FlickerPoweredLights
FlickerPoweredLights is a private coroutine.

FlickerLights is the public method that internally starts and stores the
FlickerPoweredLights coroutine.
2025-07-21 19:07:36 +03:00
ivan tkachenko 49ac86e6f9 Add compatibility section to the README
readme
2025-07-21 19:07:35 +03:00
ivan tkachenko 0f8ab1a75b Add Changelog 2025-07-21 19:07:35 +03:00
ivan tkachenko dda00ce228 Bump version 2025-07-21 19:07:35 +03:00
ivan tkachenko 39a8255532 Fix LethalConfig dependency string
Apparently, this is different from BepInEx plugin GUID.
2025-07-21 02:06:14 +03:00
ivan tkachenko b7eb4ce60b Update README 2025-07-21 01:56:49 +03:00
ivan tkachenko d6a2bf21b1 Bump version 2025-07-21 01:13:40 +03:00
ivan tkachenko 730f125d62 Patch Jester destructor to reset the light show
It is needed to despawn Jester via Imperium's Object Explorer.
2025-07-21 01:06:45 +03:00
ivan tkachenko 8e065d3e51 Add config option to skip tracks marked as Explicit Content/Lyrics
Unfortunately it is configurable by host only, and there is no sane way
to make work from clients.
2025-07-21 00:55:40 +03:00
ivan tkachenko 2a33457661 Harmony: Use nameof() instead of hardcoded strings 2025-07-21 00:32:43 +03:00
ivan tkachenko 0fbf0b04f4 Add V70PoweredLights_Fix to the package dependencies
Not strictly required, but makes this mod shine brighter.
2025-07-21 00:32:43 +03:00
ivan tkachenko 0c5d4f7158 Add DiscoBall to Main on all interiors, BirthdayRoom and factory (belt room) 2025-07-21 00:32:43 +03:00
ivan tkachenko 9e066372c5 Add support for lyrics randomization 2025-07-21 00:32:42 +03:00
ivan tkachenko ca977625db Sort imports the way VisualStudio likes it
Apparently, Sublime Text's Sort Lines command ordered them in a weird
way, inconsistent with VS, VS Code and human logic.
2025-07-20 23:04:41 +03:00
ivan tkachenko 7d1cac6e2e Add lyrics, flickering and fade out transitions to many tracks 2025-07-20 23:04:40 +03:00
ivan tkachenko 2229fa3545 Add debug-only config for lyrics time series 2025-07-20 23:04:40 +03:00
ivan tkachenko 118eecbb59 Add support for fading out, and debug-only config for flickering lights 2025-07-20 23:04:40 +03:00
ivan tkachenko b8824dbbfb Config: Most synced options have something in common 2025-07-20 23:04:40 +03:00
ivan tkachenko 3e751c0d8d Config: reduce repetition
It may look complicated, but it reduced references to each individual
entry from almost ten to just 4.
2025-07-20 23:04:39 +03:00
ivan tkachenko 601ecf8887 Reworked state management system, automatic wrapping of timestamps and spans
Add lyrics for MoyaZhittya
2025-07-20 23:04:39 +03:00
ivan tkachenko d13c617895 Apply audio offsets early to simplify math 2025-07-18 02:40:55 +03:00
ivan tkachenko e1f19b3919 Add track Kach with custom palette and timings 2025-07-18 02:40:55 +03:00
ivan tkachenko ba0162b3e1 Add track PWNED with custom palette and timings 2025-07-18 02:40:54 +03:00
ivan tkachenko ed8804b7a7 Add track Chereshnya with custom palette and timings 2025-07-18 02:40:53 +03:00
ivan tkachenko 9be9eaaf80 Extend loop of the track VseVZale
Now includes second phase OOOoooo OOooo
2025-07-18 02:40:53 +03:00
ivan tkachenko 0683a18491 Port track VseVZale to OGG format
No gap now.
2025-07-18 02:40:52 +03:00
ivan tkachenko 6204888453 Port track DeployDestroy to OGG format
No gap now.
2025-07-18 02:40:52 +03:00
ivan tkachenko c15637b347 Port track Durochka to OGG format, add custom transitions
No gap now.
2025-07-18 02:40:51 +03:00
ivan tkachenko 42c1f29a16 Port track Gorgorod to OGG format
No gap now.
2025-07-18 02:40:50 +03:00
ivan tkachenko 8a193fa408 Port track MoyaZhittya to OGG format
No gap now.
2025-07-18 02:40:50 +03:00
ivan tkachenko 4ee20adea7 Port track MuzikaGromche to OGG format
Twice as longer, loops better, no gap.
2025-07-18 02:40:49 +03:00
ivan tkachenko 2df7d28d43 New operators for Palette
With these it would be easier to create more complicated timelines
without repeating yourself over and over again.
2025-07-18 02:40:49 +03:00
ivan tkachenko 43d1565dbe MSBuild: Add platform-agnostic task to convert WAV to OGG 2025-07-18 02:40:48 +03:00
ivan tkachenko f5dab20d67 Add track Yalgaar with custom palette and timings 2025-07-18 02:40:48 +03:00
ivan tkachenko 38cfb5f5e7 Add track Peretasovka with custom palette and timings 2025-07-18 02:40:47 +03:00
ivan tkachenko b86c50a848 Add track Song2 with custom palette and timings 2025-07-18 02:40:47 +03:00
ivan tkachenko 694bc61dae Add tracks GodMode and RiseAndShine with custom palette and timings 2025-07-18 02:40:46 +03:00
ivan tkachenko 909efa720f Add track ZmeiGorynich with custom palette and timings 2025-07-18 02:36:43 +03:00
ivan tkachenko a8761bf679 Add support for interpolated color transitions for lights, with debug-only synced overrides 2025-07-17 22:36:52 +03:00
ivan tkachenko ad77530b6d Add support for per-track palettes, and debug-only synced palette override
Palettes are contributed by @REALJUSTNOTHING
2025-07-17 22:35:19 +03:00
ivan tkachenko 34d8da1562 Add configurable global audio offset, useful for Bluetooth headsets 2025-07-17 22:35:19 +03:00
ivan tkachenko b73c7ee3cb Sync playback to the actual beat count rather than relying on BPM 2025-07-17 22:34:38 +03:00
ivan tkachenko 0d4f180a37 Add debug-only ability to change weights of tracks while on a moon
And drop obsoleted debug code. With the new on-the-fly track weights
configuration, hardcoding one in build is not necessary anymore.
2025-07-16 03:06:43 +03:00
ivan tkachenko 829c44e347 Add debug-only synced config option to skip wind-up phase 2025-07-16 03:06:42 +03:00
ivan tkachenko b15e93ac34 Factor out CSync hack into a separate method
We gonna register more synced entries in debug-only builds, but marking
nullable fields with the [SyncedEntryField] attribute is not an option.
2025-07-15 22:47:02 +03:00
ivan tkachenko f158e7728c Fix language section toggle for non-host
There is no point in checking for synchronized value before flipping
local value.
2025-07-15 21:51:38 +03:00
ivan tkachenko 2b42899779 Reorder some statements to make them visually more grouped together
Postfix patch went from 5 if-blocks down to only 3 \o/

There is no need to stop the creatureVoice and start it delayed in two
separate condition blocks. Also, the code should only rely on state
transitions, and not on AudioSource.isPlaying property.
2025-07-14 14:44:17 +03:00
64 changed files with 3028 additions and 255 deletions

5
.editorconfig Normal file
View File

@ -0,0 +1,5 @@
[*.cs]
# IDE0290: Use primary constructor
# Primary constructors are far from perfect: they can't have readonly fields, while fields can be used anywhere in the class body.
csharp_style_prefer_primary_constructors = false

BIN
Assets/BeefLiverIntro.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Assets/BeefLiverLoop.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Assets/Beha1Intro.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Assets/Beha2Intro.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Assets/Beha3Intro.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Assets/BehaLoop.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Assets/ChereshnyaIntro.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Assets/ChereshnyaLoop.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Assets/DeployDestroyIntro.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Assets/DeployDestroyLoop.mp3 (Stored with Git LFS)

Binary file not shown.

BIN
Assets/DeployDestroyLoop.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Assets/DeployDestroyStart.mp3 (Stored with Git LFS)

Binary file not shown.

BIN
Assets/DurochkaIntro.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Assets/DurochkaLoop.mp3 (Stored with Git LFS)

Binary file not shown.

BIN
Assets/DurochkaLoop.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Assets/DurochkaStart.mp3 (Stored with Git LFS)

Binary file not shown.

BIN
Assets/GodModeIntro.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Assets/GodModeLoop.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Assets/GorgorodIntro.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Assets/GorgorodLoop.mp3 (Stored with Git LFS)

Binary file not shown.

BIN
Assets/GorgorodLoop.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Assets/GorgorodStart.mp3 (Stored with Git LFS)

Binary file not shown.

BIN
Assets/KachIntro.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Assets/KachLoop.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Assets/MoyaZhittyaIntro.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Assets/MoyaZhittyaLoop.mp3 (Stored with Git LFS)

Binary file not shown.

BIN
Assets/MoyaZhittyaLoop.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Assets/MoyaZhittyaStart.mp3 (Stored with Git LFS)

Binary file not shown.

BIN
Assets/MuzikaGromcheIntro.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Assets/MuzikaGromcheLoop.mp3 (Stored with Git LFS)

Binary file not shown.

BIN
Assets/MuzikaGromcheLoop.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Assets/MuzikaGromcheStart.mp3 (Stored with Git LFS)

Binary file not shown.

BIN
Assets/PWNEDIntro.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Assets/PWNEDLoop.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Assets/PeretasovkaIntro.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Assets/PeretasovkaLoop.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Assets/RiseAndShineIntro.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Assets/RiseAndShineLoop.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Assets/Song2Intro.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Assets/Song2Loop.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Assets/VseVZaleIntro.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Assets/VseVZaleLoop.mp3 (Stored with Git LFS)

Binary file not shown.

BIN
Assets/VseVZaleLoop.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Assets/VseVZaleStart.mp3 (Stored with Git LFS)

Binary file not shown.

BIN
Assets/YalgaarIntro.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Assets/YalgaarLoop.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Assets/ZmeiGorynichIntro.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Assets/ZmeiGorynichLoop.ogg (Stored with Git LFS) Normal file

Binary file not shown.

56
CHANGELOG.md Normal file
View File

@ -0,0 +1,56 @@
# Changelog
## 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.

5
Directory.Build.targets Normal file
View File

@ -0,0 +1,5 @@
<Project>
<Target Name="NetcodePatch" AfterTargets="PostBuildEvent">
<Exec Command="dotnet netcode-patch -nv 1.5.2 &quot;$(TargetPath)&quot; @(ReferencePathWithRefAssemblies->'&quot;%(Identity)&quot;', ' ')"/>
</Target>
</Project>

View File

@ -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/Oflor-MuzikaGromche/" plugin_dir := "$HOME/.config/r2modmanPlus-local/LethalCompany/profiles" / imperium_profile / "BepInEx/plugins/Ratijas-MuzikaGromche/"
install-imperium: install-imperium:
rm -rf "{{ plugin_dir }}" rm -rf "{{ plugin_dir }}"

View File

@ -2,9 +2,13 @@
<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>

View File

@ -0,0 +1,165 @@
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);
Debug.Log($"{nameof(MuzikaGromche)} {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)
{
Debug.Log($"{nameof(MuzikaGromche)} {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()
{
Debug.Log($"{nameof(MuzikaGromche)} {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();
}
}
}

View File

@ -2,41 +2,66 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework> <TargetFramework>netstandard2.1</TargetFramework>
<AssemblyName>MuzikaGromche</AssemblyName> <Title>MuzikaGromche</Title>
<Description>Opa che tut u nas</Description> <PackageId>MuzikaGromche</PackageId>
<Version>13.37.6</Version> <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.420.9001</Version>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<!-- NetcodePatch requires anything but 'full' -->
<DebugType>portable</DebugType>
<PackageReadmeFile>../README.md</PackageReadmeFile>
<PackageProjectUrl>https://git.vilunov.me/ratijas/muzika-gromche</PackageProjectUrl>
<RepositoryUrl>https://git.vilunov.me/ratijas/muzika-gromche</RepositoryUrl>
<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> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BepInEx.Analyzers" Version="1.*" PrivateAssets="all"/> <PackageReference Include="BepInEx.Analyzers" Version="1.*" PrivateAssets="all" Private="false" />
<PackageReference Include="BepInEx.Core" Version="5.*"/> <PackageReference Include="BepInEx.Core" Version="5.*" PrivateAssets="all" Private="false" />
<PackageReference Include="BepInEx.PluginInfoProps" Version="1.*"/> <PackageReference Include="BepInEx.PluginInfoProps" Version="1.*" PrivateAssets="all" Private="false" />
<PackageReference Include="UnityEngine.Modules" Version="2022.3.9" IncludeAssets="compile"/> <PackageReference Include="UnityEngine.Modules" Version="2022.3.9" PrivateAssets="all" Private="false" />
<PackageReference Include="BepInEx.AssemblyPublicizer.MSBuild" Version="0.4.1" PrivateAssets="all" /> <PackageReference Include="BepInEx.AssemblyPublicizer.MSBuild" Version="0.4.1" PrivateAssets="all" Private="false" />
<!-- <!--
Publicize internal methods, so we could generate config entries for tracks at runtime instead Publicize internal methods, so we could generate config entries for tracks at runtime instead
of generating code at compile time. See https://github.com/lc-sigurd/CSync/issues/11 of generating code at compile time. See https://github.com/lc-sigurd/CSync/issues/11
It is an optional dependency now, but there is no sane way to mark it as such.
--> -->
<PackageReference Include="Sigurd.BepInEx.CSync" Version="5.0.1" Publicize="true" /> <PackageReference Include="Sigurd.BepInEx.CSync" Version="5.0.1" Publicize="true" PrivateAssets="all" Private="false" />
<PackageReference Include="AinaVT-LethalConfig" Version="1.4.6" /> <PackageReference Include="AinaVT-LethalConfig" Version="1.4.6" PrivateAssets="all" Private="false" />
<PackageReference Include="TeamBMX.LobbyCompatibility" Version="1.*" PrivateAssets="all" Private="false" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Assembly-CSharp" Publicize="true"> <Reference Include="Assembly-CSharp" Publicize="true" Private="false">
<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"> <Reference Include="Unity.Collections" Private="false">
<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"> <Reference Include="Unity.Netcode.Runtime" Publicize="true" Private="false">
<HintPath>$(LethalCompanyDir)Lethal Company_Data\Managed\Unity.Netcode.Runtime.dll</HintPath> <HintPath>$(LethalCompanyDir)Lethal Company_Data\Managed\Unity.Netcode.Runtime.dll</HintPath>
</Reference> </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"/> <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.2" PrivateAssets="all" Private="false" />
</ItemGroup> </ItemGroup>
<Target Name="Bundle" AfterTargets="Build"> <Target Name="Bundle" AfterTargets="Build">
@ -47,9 +72,12 @@
<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="$(TargetDir)MuzikaGromche.dll" /> <PackagedResources Include="$(ProjectDir)UnityAssets\muzikagromche_discoball" />
<PackagedResources Include="$(ProjectDir)UnityAssets\muzikagromche_poweredlightsanimators" />
<PackagedResources Include="$(TargetDir)$(AssemblyName).dll" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -73,4 +101,18 @@
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>
</Project> </Project>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,290 @@
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
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;
Debug.Log($"{nameof(MuzikaGromche)} {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;
Debug.Log($"{nameof(MuzikaGromche)} {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);
// 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));
}
}
}
}

View File

@ -0,0 +1,88 @@
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);
Debug.Log($"{nameof(MuzikaGromche)} {nameof(SpawnRatePatch)} Overriding spawn weight[{index}] {weights[index]} * {multiplier} => {newWeight} for t={SpawnTime}");
weights[index] = newWeight;
}
}
}
}

Binary file not shown.

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="BepInEx" value="https://nuget.bepinex.dev/v3/index.json" />
<add key="AAron Thunderstore" value="https://nuget.windows10ce.com/nuget/v3/index.json" />
</packageSources>
</configuration>

View File

@ -1 +1,42 @@
Adds some content to your reverse teleports on Titan # Muzika Gromche!
_Add some content to your Inverse teleporter experience on Titan!<sup>1</sup>_
This mod's name literally means "cranck music louder".
To keep it a surprise, it is adviced that you do not read the detailed description below.
## Compatibility
Muzika Gromche is compatible with *Almost Vanilla™* gameplay and [*High Quota Mindset*](https://youtu.be/18RUCgQldGg?t=2553). It slightly changes certain timers, so won't be compatible with leaderboards. If you are a streamer™, be aware that it does play *copyrighted content.*
Muzika Gromche works with all Lethal Company versions from v72 all the way back to v40, and is likely to work on all future versions as long as dependencies ([`LethalConfig`] and [`LobbyCompatibility`]) are working.
Speaking of dependencies, [`V70PoweredLights_Fix`] is not strictly required, but it doesn't hurt to have it installed on any version, and it makes this mod more enjoyable on new Mansion tiles.
## Configuration
Configuration integrates with [`LethalConfig`] mod.
If you are just trying out this mod for the first time, or want to experience it more often, consider toggling ON the "Override Spawn Rates" config entry. Otherwise it might take a frustratingly long time to find out what you need to find out.
Track selection options are only configurable by host player and only while orbiting.
Any player can change their personal preferences locally.
- If you are playing with a Bluetooth headset, adjust Audio Offset to -0.2 seconds.
- Display Lyrics toggle: show lyrics in a popup whenever player hears music.
## Authors & Special Thanks
- Oflor: Original author, wrote the code and sliced the first tracks.
- [Ratijas](https://t.me/ratijas): Rewrote the code to sync the lights to the beat, added configuration options and many features, fixed gaps in existing tracks and sliced many new ones.
- [Just Nothing](https://t.me/REALJUSTNOTHING): Visual artist; contributed palettes, timings and animation curves.
- [WaterGun](https://www.youtube.com/channel/UCCxCFfmrnqkFZ8i9FsXBJVA): Created [`V70PoweredLights_Fix`] mod, patched certain tiles with amazing lightshow.
---
1. Actually not limited to Inverse teleporter or Titan.
[`CSync`]: https://thunderstore.io/c/lethal-company/p/Sigurd/CSync/
[`LethalConfig`]: https://thunderstore.io/c/lethal-company/p/AinaVT/LethalConfig/
[`LobbyCompatibility`]: https://thunderstore.io/c/lethal-company/p/BMX/LobbyCompatibility/
[`V70PoweredLights_Fix`]: https://thunderstore.io/c/lethal-company/p/WaterGun/V70PoweredLights_Fix/

12
dotnet-tools.json Normal file
View File

@ -0,0 +1,12 @@
{
"version": 1,
"isRoot": true,
"tools": {
"evaisa.netcodepatcher.cli": {
"version": "4.3.0",
"commands": [
"netcode-patch"
]
}
}
}

View File

@ -1,12 +1,13 @@
{ {
"name": "MuzikaGromche", "name": "MuzikaGromche",
"version_number": "13.37.6", "version_number": "1337.420.9001",
"author": "Oflor", "author": "Ratijas",
"description": "Glaza zakryvaj", "description": "Add some content to your inverse teleporter experience on Titan!",
"website_url": "https://git.vilunov.me/nikita/muzika-gromche", "website_url": "https://git.vilunov.me/ratijas/muzika-gromche",
"dependencies": [ "dependencies": [
"BepInEx-BepInExPack-5.4.2100", "BepInEx-BepInExPack-5.4.2100",
"Sigurd-CSync-5.0.1", "AinaVT-LethalConfig-1.4.6",
"ainavt.lc.lethalconfig-1.4.6" "WaterGun-V70PoweredLights_Fix-1.0.0",
"BMX-LobbyCompatibility-1.5.1"
] ]
} }