1
0
Fork 0

Rewrite track choosing event to custom netcode

This commit is contained in:
ivan tkachenko 2025-08-07 20:00:13 +03:00
parent 1aa8c1ddfa
commit 0dca416958
5 changed files with 138 additions and 14 deletions

View File

@ -3,6 +3,7 @@
## MuzikaGromche 1337.69.420 ## MuzikaGromche 1337.69.420
- Fix certain object hanging around after being disabled. - 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 ## MuzikaGromche 13.37.9001 - Chromaberrated Edition

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

@ -13,6 +13,9 @@
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<!-- NetcodePatch requires anything but 'full' -->
<DebugType>portable</DebugType>
<PackageReadmeFile>../README.md</PackageReadmeFile> <PackageReadmeFile>../README.md</PackageReadmeFile>
<PackageProjectUrl>https://git.vilunov.me/ratijas/muzika-gromche</PackageProjectUrl> <PackageProjectUrl>https://git.vilunov.me/ratijas/muzika-gromche</PackageProjectUrl>
<RepositoryUrl>https://git.vilunov.me/ratijas/muzika-gromche</RepositoryUrl> <RepositoryUrl>https://git.vilunov.me/ratijas/muzika-gromche</RepositoryUrl>

View File

@ -9,6 +9,7 @@ using LethalConfig.ConfigItems.Options;
using LobbyCompatibility.Attributes; using LobbyCompatibility.Attributes;
using LobbyCompatibility.Enums; using LobbyCompatibility.Enums;
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -16,6 +17,7 @@ using System.Net.NetworkInformation;
using System.Net.Sockets; using System.Net.Sockets;
using System.Reflection; using System.Reflection;
using System.Security.Cryptography; using System.Security.Cryptography;
using Unity.Netcode;
using UnityEngine; using UnityEngine;
using UnityEngine.Networking; using UnityEngine.Networking;
@ -482,6 +484,11 @@ namespace MuzikaGromche
return tracks[trackId]; return tracks[trackId];
} }
public static Track? FindTrackNamed(string name)
{
return Tracks.FirstOrDefault(track => track.Name == name);
}
internal static Track? CurrentTrack; internal static Track? CurrentTrack;
internal static BeatTimeState? BeatTimeState; internal static BeatTimeState? BeatTimeState;
@ -547,6 +554,7 @@ namespace MuzikaGromche
DiscoBallManager.Load(); DiscoBallManager.Load();
PoweredLightsAnimators.Load(); PoweredLightsAnimators.Load();
var harmony = new Harmony(PluginInfo.PLUGIN_NAME); var harmony = new Harmony(PluginInfo.PLUGIN_NAME);
harmony.PatchAll(typeof(GameNetworkManagerPatch));
harmony.PatchAll(typeof(JesterPatch)); harmony.PatchAll(typeof(JesterPatch));
harmony.PatchAll(typeof(EnemyAIPatch)); harmony.PatchAll(typeof(EnemyAIPatch));
harmony.PatchAll(typeof(PoweredLightsAnimatorsPatch)); harmony.PatchAll(typeof(PoweredLightsAnimatorsPatch));
@ -554,6 +562,7 @@ namespace MuzikaGromche
harmony.PatchAll(typeof(DiscoBallTilePatch)); harmony.PatchAll(typeof(DiscoBallTilePatch));
harmony.PatchAll(typeof(DiscoBallDespawnPatch)); harmony.PatchAll(typeof(DiscoBallDespawnPatch));
harmony.PatchAll(typeof(SpawnRatePatch)); harmony.PatchAll(typeof(SpawnRatePatch));
NetcodePatcher();
} }
else else
{ {
@ -561,6 +570,23 @@ namespace MuzikaGromche
Logger.LogError("Could not load audio file " + string.Join(", ", failed)); Logger.LogError("Could not load audio file " + string.Join(", ", failed));
} }
} }
private static void NetcodePatcher()
{
var types = Assembly.GetExecutingAssembly().GetTypes();
foreach (var type in types)
{
var methods = type.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
foreach (var method in methods)
{
var attributes = method.GetCustomAttributes(typeof(RuntimeInitializeOnLoadMethodAttribute), false);
if (attributes.Length > 0)
{
method.Invoke(null, null);
}
}
}
}
}; };
public readonly record struct Language(string Short, string Full) public readonly record struct Language(string Short, string Full)
@ -680,7 +706,7 @@ namespace MuzikaGromche
public AudioClip LoadedLoop = null!; public AudioClip LoadedLoop = null!;
// How often this track should be chosen, relative to the sum of weights of all tracks. // How often this track should be chosen, relative to the sum of weights of all tracks.
public SyncedEntry<int> Weight = null!; public ConfigEntry<int> Weight = null!;
public string FileNameStart => $"{Name}Start.{Ext}"; public string FileNameStart => $"{Name}Start.{Ext}";
public string FileNameLoop => $"{Name}Loop.{Ext}"; public string FileNameLoop => $"{Name}Loop.{Ext}";
@ -1428,7 +1454,7 @@ namespace MuzikaGromche
public static ConfigEntry<bool> SkipExplicitTracks { get; private set; } = null!; public static ConfigEntry<bool> SkipExplicitTracks { get; private set; } = null!;
public static SyncedEntry<bool> OverrideSpawnRates { get; private set; } = null!; public static ConfigEntry<bool> OverrideSpawnRates { get; private set; } = null!;
public static bool ShouldSkipWindingPhase { get; private set; } = false; public static bool ShouldSkipWindingPhase { get; private set; } = false;
@ -1456,12 +1482,11 @@ namespace MuzikaGromche
SkipExplicitTracks = configFile.Bind("General", "Skip Explicit Tracks", false, SkipExplicitTracks = configFile.Bind("General", "Skip Explicit Tracks", false,
new ConfigDescription("When choosing tracks at random, skip the ones with Explicit Content/Lyrics.")); new ConfigDescription("When choosing tracks at random, skip the ones with Explicit Content/Lyrics."));
LethalConfigManager.AddConfigItem(new BoolCheckBoxConfigItem(SkipExplicitTracks, requiresRestart: false)); LethalConfigManager.AddConfigItem(new BoolCheckBoxConfigItem(SkipExplicitTracks, Default(new BoolCheckBoxOptions())));
OverrideSpawnRates = configFile.BindSyncedEntry("General", "Override Spawn Rates", false, OverrideSpawnRates = configFile.Bind("General", "Override Spawn Rates", false,
new ConfigDescription("Deviate from vanilla spawn rates to experience content of this mod more often.")); new ConfigDescription("Deviate from vanilla spawn rates to experience content of this mod more often."));
LethalConfigManager.AddConfigItem(new BoolCheckBoxConfigItem(OverrideSpawnRates.Entry, Default(new BoolCheckBoxOptions()))); LethalConfigManager.AddConfigItem(new BoolCheckBoxConfigItem(OverrideSpawnRates, Default(new BoolCheckBoxOptions())));
CSyncHackAddSyncedEntry(OverrideSpawnRates);
#if DEBUG #if DEBUG
SetupEntriesToSkipWinding(configFile); SetupEntriesToSkipWinding(configFile);
@ -1489,11 +1514,11 @@ namespace MuzikaGromche
if (CanModifyWeightsNow()) if (CanModifyWeightsNow())
{ {
var tracks = Plugin.Tracks.Where(t => t.Language.Equals(language)).ToList(); var tracks = Plugin.Tracks.Where(t => t.Language.Equals(language)).ToList();
var isOff = tracks.All(t => t.Weight.LocalValue == 0); var isOff = tracks.All(t => t.Weight.Value == 0);
var newWeight = isOff ? 50 : 0; var newWeight = isOff ? 50 : 0;
foreach (var t in tracks) foreach (var t in tracks)
{ {
t.Weight.LocalValue = newWeight; t.Weight.Value = newWeight;
} }
} }
}); });
@ -1504,13 +1529,12 @@ namespace MuzikaGromche
// Create slider entry for track // Create slider entry for track
string warning = track.IsExplicit ? "Explicit Content/Lyrics!\n\n" : ""; string warning = track.IsExplicit ? "Explicit Content/Lyrics!\n\n" : "";
string description = $"Language: {language.Full}\n\n{warning}Random (relative) chance of selecting this track.\n\nSet to zero to effectively disable the track."; string description = $"Language: {language.Full}\n\n{warning}Random (relative) chance of selecting this track.\n\nSet to zero to effectively disable the track.";
track.Weight = configFile.BindSyncedEntry( track.Weight = configFile.Bind(
new ConfigDefinition(section, track.Name), new ConfigDefinition(section, track.Name),
50, 50,
new ConfigDescription(description, chanceRange, track)); new ConfigDescription(description, chanceRange, track));
LethalConfigManager.AddConfigItem(new IntSliderConfigItem(track.Weight.Entry, Default(new IntSliderOptions()))); LethalConfigManager.AddConfigItem(new IntSliderConfigItem(track.Weight, Default(new IntSliderOptions())));
CSyncHackAddSyncedEntry(track.Weight);
} }
ConfigManager.Register(this); ConfigManager.Register(this);
@ -1774,6 +1798,78 @@ namespace MuzikaGromche
} }
} }
[HarmonyPatch(typeof(GameNetworkManager))]
static class GameNetworkManagerPatch
{
const string JesterEnemyPrefabName = "JesterEnemy";
[HarmonyPatch(nameof(GameNetworkManager.Start))]
[HarmonyPrefix]
static void StartPrefix(GameNetworkManager __instance)
{
var networkPrefab = NetworkManager.Singleton.NetworkConfig.Prefabs.Prefabs
.FirstOrDefault(prefab => prefab.Prefab.name == JesterEnemyPrefabName);
if (networkPrefab == null)
{
Debug.LogError($"{nameof(MuzikaGromche)} JesterEnemy prefab not found!");
}
else
{
Debug.Log($"{nameof(MuzikaGromche)} Patching {nameof(JesterAI)} with {nameof(MuzikaGromcheJesterNetworkBehaviour)} component");
networkPrefab.Prefab.AddComponent<MuzikaGromcheJesterNetworkBehaviour>();
}
}
}
class MuzikaGromcheJesterNetworkBehaviour : NetworkBehaviour
{
public override void OnNetworkSpawn()
{
ChooseTrackDeferred();
foreach (var track in Plugin.Tracks)
{
track.Weight.SettingChanged += (_, _) => ChooseTrackDeferred();
}
Config.SkipExplicitTracks.SettingChanged += (_, _) => ChooseTrackDeferred();
base.OnNetworkSpawn();
}
// Batch multiple weights changes in a single network RPC
private Coroutine? DeferredCoroutine = null;
private void ChooseTrackDeferred()
{
if (DeferredCoroutine != null)
{
StopCoroutine(DeferredCoroutine);
DeferredCoroutine = null;
}
DeferredCoroutine = StartCoroutine(ChooseTrackDeferredCoroutine());
}
private IEnumerator ChooseTrackDeferredCoroutine()
{
yield return new WaitForEndOfFrame();
DeferredCoroutine = null;
ChooseTrackServerRpc();
}
[ClientRpc]
public void SetTrackClientRpc(string name)
{
Debug.Log($"{nameof(MuzikaGromche)} SetTrackClientRpc {name}");
Plugin.CurrentTrack = Plugin.FindTrackNamed(name);
}
[ServerRpc]
public void ChooseTrackServerRpc()
{
var track = Plugin.ChooseTrack();
Debug.Log($"{nameof(MuzikaGromche)} ChooseTrackServerRpc {track.Name}");
SetTrackClientRpc(track.Name);
}
}
// farAudio is during windup, Start overrides popGoesTheWeaselTheme // farAudio is during windup, Start overrides popGoesTheWeaselTheme
// creatureVoice is when popped, Loop overrides screamingSFX // creatureVoice is when popped, Loop overrides screamingSFX
[HarmonyPatch(typeof(JesterAI))] [HarmonyPatch(typeof(JesterAI))]
@ -1820,6 +1916,14 @@ namespace MuzikaGromche
[HarmonyPostfix] [HarmonyPostfix]
static void JesterUpdatePostfix(JesterAI __instance, State __state) static void JesterUpdatePostfix(JesterAI __instance, State __state)
{ {
if (Plugin.CurrentTrack == null)
{
#if DEBUG
Debug.Log($"{nameof(MuzikaGromche)} CurrentTrack is not set!");
#endif
return;
}
if (__instance.previousState == 1 && __state.previousState != 1) if (__instance.previousState == 1 && __state.previousState != 1)
{ {
// if just started winding up // if just started winding up
@ -1828,7 +1932,6 @@ namespace MuzikaGromche
__instance.creatureVoice.Stop(); __instance.creatureVoice.Stop();
// ...and start modded music // ...and start modded music
Plugin.CurrentTrack = Plugin.ChooseTrack();
Plugin.BeatTimeState = new BeatTimeState(Plugin.CurrentTrack); Plugin.BeatTimeState = new BeatTimeState(Plugin.CurrentTrack);
// Set up custom popup timer, which is shorter than Start audio // Set up custom popup timer, which is shorter than Start audio
__instance.popUpTimer = Plugin.CurrentTrack.WindUpTimer; __instance.popUpTimer = Plugin.CurrentTrack.WindUpTimer;
@ -1878,9 +1981,9 @@ namespace MuzikaGromche
} }
// Manage the timeline: switch color of the lights according to the current playback/beat position. // Manage the timeline: switch color of the lights according to the current playback/beat position.
if (__instance.previousState == 1 || __instance.previousState == 2) if ((__instance.previousState == 1 || __instance.previousState == 2) && Plugin.BeatTimeState != null)
{ {
var events = Plugin.BeatTimeState!.Update(start: __instance.farAudio, loop: __instance.creatureVoice); var events = Plugin.BeatTimeState.Update(start: __instance.farAudio, loop: __instance.creatureVoice);
foreach (var ev in events) foreach (var ev in events)
{ {
switch (ev) switch (ev)

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"
]
}
}
}