1
0
Fork 0

Compare commits

..

No commits in common. "dev" and "master" have entirely different histories.
dev ... master

64 changed files with 233 additions and 3006 deletions

View File

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

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

View File

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

View File

@ -1,5 +0,0 @@
<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/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 }}"

View File

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

View File

@ -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);
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,66 +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.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" 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="1.*" PrivateAssets="all" Private="false" /> <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" />
<!-- <!--
Publicize internal methods, so we could generate config entries for tracks at runtime instead Publicize internal methods, so we could generate config entries for tracks at runtime instead
of generating code at compile time. See https://github.com/lc-sigurd/CSync/issues/11 of generating code at compile time. See https://github.com/lc-sigurd/CSync/issues/11
It is an optional dependency now, but there is no sane way to mark it as such.
--> -->
<PackageReference Include="Sigurd.BepInEx.CSync" Version="5.0.1" Publicize="true" PrivateAssets="all" Private="false" /> <PackageReference Include="Sigurd.BepInEx.CSync" Version="5.0.1" Publicize="true" />
<PackageReference Include="AinaVT-LethalConfig" Version="1.4.6" PrivateAssets="all" Private="false" /> <PackageReference Include="AinaVT-LethalConfig" Version="1.4.6" />
<PackageReference Include="TeamBMX.LobbyCompatibility" Version="1.*" PrivateAssets="all" Private="false" />
</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>
</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">
@ -72,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>
@ -101,18 +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>
</Project> </Project>

File diff suppressed because it is too large Load Diff

View File

@ -1,290 +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
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

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

7
NuGet.Config Normal file
View File

@ -0,0 +1,7 @@
<?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,42 +1 @@
# Muzika Gromche! Adds some content to your reverse teleports on Titan
_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/

View File

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

View File

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