Please disclose if your mod was created primarily using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of VikingShanties v1.0.3
plugins\VikingShanties\VikingShanties.dll
Decompiled 22 minutes agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.Networking; using VikingShanties.Systems; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("VikingShanties")] [assembly: AssemblyDescription("Dance on boats and sing sea shanties in Valheim")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("DickDangerJustice")] [assembly: AssemblyProduct("VikingShanties")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("a1b2c3d4-e5f6-7890-abcd-ef1234567890")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace VikingShanties { public static class ModConfig { public static ConfigEntry<bool> Enabled; public static ConfigEntry<KeyCode> DanceKey; public static ConfigEntry<float> Cooldown; public static ConfigEntry<string> EmoteMode; public static ConfigEntry<string> SongMode; public static ConfigEntry<float> ShantyVolume; public static ConfigEntry<float> MaxHearingDistance; public static ConfigEntry<bool> MoreDancersLouder; public static void Init(ConfigFile config) { //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Expected O, but got Unknown //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Expected O, but got Unknown //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Expected O, but got Unknown //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Expected O, but got Unknown //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Expected O, but got Unknown Enabled = config.Bind<bool>("General", "Enabled", true, "Enable or disable Viking Shanties"); DanceKey = config.Bind<KeyCode>("General", "DanceKey", (KeyCode)104, "Hotkey to start dancing and singing on a ship"); Cooldown = config.Bind<float>("General", "Cooldown", 3f, new ConfigDescription("Cooldown in seconds between dance toggles", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 30f), Array.Empty<object>())); EmoteMode = config.Bind<string>("Emote", "EmoteMode", "Cycle", new ConfigDescription("Which emote to perform on the boat", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[3] { "Cycle", "Dance", "Headbang" }), Array.Empty<object>())); SongMode = config.Bind<string>("Audio", "SongMode", "Cycle", new ConfigDescription("Which shanty to play. 'Cycle' rotates through all tracks. Other options lock to a specific song.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[1] { "Cycle" }), Array.Empty<object>())); ShantyVolume = config.Bind<float>("Audio", "ShantyVolume", 0.8f, new ConfigDescription("Base volume of shanty playback", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); MaxHearingDistance = config.Bind<float>("Audio", "MaxHearingDistance", 80f, new ConfigDescription("Maximum distance at which the shanty can be heard", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 200f), Array.Empty<object>())); MoreDancersLouder = config.Bind<bool>("Audio", "MoreDancersLouder", true, "Volume increases slightly with more dancers on the ship"); } public static void UpdateSongChoices(AudioClip[] shanties) { //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Expected O, but got Unknown if (shanties == null || shanties.Length == 0) { return; } string[] array = new string[shanties.Length + 1]; array[0] = "Cycle"; for (int i = 0; i < shanties.Length; i++) { array[i + 1] = ((Object)shanties[i]).name; } string value = SongMode.Value; ConfigFile configFile = ((ConfigEntryBase)SongMode).ConfigFile; ConfigDefinition definition = ((ConfigEntryBase)SongMode).Definition; configFile.Remove(definition); SongMode = configFile.Bind<string>(definition, "Cycle", new ConfigDescription("Which shanty to play. 'Cycle' rotates through all tracks. Other options lock to a specific song.", (AcceptableValueBase)(object)new AcceptableValueList<string>(array), Array.Empty<object>())); string[] array2 = array; for (int j = 0; j < array2.Length; j++) { if (array2[j] == value) { SongMode.Value = value; break; } } } public static int GetForcedSongIndex(AudioClip[] shanties) { if (shanties == null || SongMode.Value == "Cycle") { return -1; } for (int i = 0; i < shanties.Length; i++) { if (((Object)shanties[i]).name == SongMode.Value) { return i; } } return -1; } } [BepInPlugin("dickdangerjustice.VikingShanties", "Viking Shanties", "1.0.0")] [BepInProcess("valheim.exe")] public class Plugin : BaseUnityPlugin { [CompilerGenerated] private sealed class <LoadAudioFiles>d__20 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public Plugin <>4__this; private List<AudioClip> <clips>5__2; private List<string>.Enumerator <>7__wrap2; private string <filePath>5__4; private string <fileName>5__5; private UnityWebRequest <request>5__6; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <LoadAudioFiles>d__20(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if ((uint)(num - -4) <= 1u || num == 1) { try { if (num == -4 || num == 1) { try { } finally { <>m__Finally2(); } } } finally { <>m__Finally1(); } } <clips>5__2 = null; <>7__wrap2 = default(List<string>.Enumerator); <filePath>5__4 = null; <fileName>5__5 = null; <request>5__6 = null; <>1__state = -2; } private bool MoveNext() { //IL_0204: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Invalid comparison between Unknown and I4 //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_01c3: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Unknown result type (might be due to invalid IL or missing references) try { int num = <>1__state; Plugin plugin = <>4__this; switch (num) { default: return false; case 0: { <>1__state = -1; string path = Path.Combine(Path.GetDirectoryName(((BaseUnityPlugin)plugin).Info.Location), "Shanties"); if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } List<string> list = new List<string>(); string[] array = new string[3] { "*.mp3", "*.ogg", "*.wav" }; foreach (string searchPattern in array) { list.AddRange(Directory.GetFiles(path, searchPattern)); } if (list.Count == 0) { ((BaseUnityPlugin)plugin).Logger.LogInfo((object)"No audio files in Shanties folder — using generated tone. Drop .mp3/.ogg/.wav files into the Shanties folder for custom music!"); Shanties = (AudioClip[])(object)new AudioClip[1] { plugin.CreateTestTone() }; AudioReady = true; return false; } ((BaseUnityPlugin)plugin).Logger.LogInfo((object)$"Loading {list.Count} shanties..."); <clips>5__2 = new List<AudioClip>(); <>7__wrap2 = list.GetEnumerator(); <>1__state = -3; break; } case 1: <>1__state = -4; if ((int)<request>5__6.result == 1) { AudioClip content = DownloadHandlerAudioClip.GetContent(<request>5__6); if ((Object)(object)content != (Object)null) { ((Object)content).name = Path.GetFileNameWithoutExtension(<filePath>5__4); <clips>5__2.Add(content); ((BaseUnityPlugin)plugin).Logger.LogInfo((object)$" Loaded: {((Object)content).name} ({content.length:F1}s)"); } } else { ((BaseUnityPlugin)plugin).Logger.LogWarning((object)(" Failed to load " + <fileName>5__5 + ": " + <request>5__6.error)); } <>m__Finally2(); <request>5__6 = null; <fileName>5__5 = null; <filePath>5__4 = null; break; } if (<>7__wrap2.MoveNext()) { <filePath>5__4 = <>7__wrap2.Current; <fileName>5__5 = Path.GetFileName(<filePath>5__4); string text = "file:///" + <filePath>5__4.Replace("\\", "/"); AudioType val = (AudioType)0; switch (Path.GetExtension(<filePath>5__4).ToLower()) { case ".mp3": case ".mpeg": val = (AudioType)13; break; case ".ogg": val = (AudioType)14; break; case ".wav": val = (AudioType)20; break; } <request>5__6 = UnityWebRequestMultimedia.GetAudioClip(text, val); <>1__state = -4; <>2__current = <request>5__6.SendWebRequest(); <>1__state = 1; return true; } <>m__Finally1(); <>7__wrap2 = default(List<string>.Enumerator); Shanties = (AudioClip[])((<clips>5__2.Count > 0) ? ((Array)<clips>5__2.ToArray()) : ((Array)new AudioClip[1] { plugin.CreateTestTone() })); ModConfig.UpdateSongChoices(Shanties); AudioReady = true; ((BaseUnityPlugin)plugin).Logger.LogInfo((object)$"{Shanties.Length} shanties ready"); return false; } catch { //try-fault ((IDisposable)this).Dispose(); throw; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } private void <>m__Finally1() { <>1__state = -1; ((IDisposable)<>7__wrap2).Dispose(); } private void <>m__Finally2() { <>1__state = -3; if (<request>5__6 != null) { ((IDisposable)<request>5__6).Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } public const string PluginGuid = "dickdangerjustice.VikingShanties"; public const string PluginName = "Viking Shanties"; public const string PluginVersion = "1.0.0"; private Harmony harmony; public static Plugin Instance { get; private set; } public ManualLogSource Log => ((BaseUnityPlugin)this).Logger; public static AudioClip[] Shanties { get; private set; } public static bool AudioReady { get; private set; } private void Awake() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown Instance = this; ModConfig.Init(((BaseUnityPlugin)this).Config); harmony = new Harmony("dickdangerjustice.VikingShanties"); harmony.PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Viking Shanties v1.0.0 loaded"); ((MonoBehaviour)this).StartCoroutine(LoadAudioFiles()); } private void OnDestroy() { Harmony obj = harmony; if (obj != null) { obj.UnpatchSelf(); } } [IteratorStateMachine(typeof(<LoadAudioFiles>d__20))] private IEnumerator LoadAudioFiles() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <LoadAudioFiles>d__20(0) { <>4__this = this }; } private AudioClip CreateTestTone() { int num = 44100; int num2 = 8; int num3 = num * num2; float[] array = new float[num3]; float[] array2 = new float[8] { 220f, 247f, 262f, 294f, 262f, 247f, 220f, 196f }; float num4 = 0.5f; int num5 = (int)((float)num * num4); for (int i = 0; i < num3; i++) { int num6 = i / num5 % array2.Length; float num7 = (float)i / (float)num; float num8 = array2[num6]; float num9 = Mathf.Sin(MathF.PI * 2f * num8 * num7) * 0.5f; num9 += Mathf.Sin(MathF.PI * 2f * num8 * 1.5f * num7) * 0.15f; num9 += Mathf.Sin(MathF.PI * 2f * num8 * 2f * num7) * 0.1f; int num10 = i % num5; float num11 = 1f; int num12 = num / 50; if (num10 < num12) { num11 = (float)num10 / (float)num12; } else if (num10 > num5 - num12) { num11 = (float)(num5 - num10) / (float)num12; } array[i] = num9 * num11 * 0.6f; } AudioClip obj = AudioClip.Create("TestShanty", num3, 1, num, false); obj.SetData(array, 0); return obj; } } } namespace VikingShanties.Systems { public class BoatAttachSystem : MonoBehaviour { private Ship attachedShip; private Vector3 localPosition; private Quaternion localRotation; private Rigidbody playerBody; private static readonly Dictionary<Player, BoatAttachSystem> attachedPlayers = new Dictionary<Player, BoatAttachSystem>(); public static bool IsAttached(Player player) { if ((Object)(object)player != (Object)null) { return attachedPlayers.ContainsKey(player); } return false; } public static Ship GetAttachedShip(Player player) { if (attachedPlayers.TryGetValue(player, out var value)) { return value.attachedShip; } return null; } public static Ship FindShipUnderPlayer(Player player) { if ((Object)(object)player == (Object)(object)Player.m_localPlayer) { return Ship.GetLocalShip(); } foreach (IMonoUpdater instance in Ship.Instances) { Ship val = (Ship)(object)((instance is Ship) ? instance : null); if ((Object)(object)val != (Object)null && val.IsPlayerInBoat(player)) { return val; } } return null; } public static bool IsDriver(Player player) { if (!((Character)player).IsAttached()) { return player.GetDoodadController() != null; } return true; } public static void AttachToShip(Player player) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)player == (Object)null) && !IsAttached(player)) { Ship val = FindShipUnderPlayer(player); if (!((Object)(object)val == (Object)null) && !IsDriver(player)) { BoatAttachSystem boatAttachSystem = ((Component)player).gameObject.AddComponent<BoatAttachSystem>(); boatAttachSystem.attachedShip = val; boatAttachSystem.localPosition = ((Component)val).transform.InverseTransformPoint(((Component)player).transform.position); boatAttachSystem.localRotation = Quaternion.Inverse(((Component)val).transform.rotation) * ((Component)player).transform.rotation; boatAttachSystem.playerBody = ((Component)player).GetComponent<Rigidbody>(); attachedPlayers[player] = boatAttachSystem; } } } public static void DetachFromShip(Player player) { if (!((Object)(object)player == (Object)null) && attachedPlayers.TryGetValue(player, out var value)) { attachedPlayers.Remove(player); if ((Object)(object)value != (Object)null) { Object.Destroy((Object)(object)value); } } } private void LateUpdate() { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)attachedShip == (Object)null) { Player component = ((Component)this).GetComponent<Player>(); if ((Object)(object)component != (Object)null) { DetachFromShip(component); } return; } Vector3 val = ((Component)attachedShip).transform.TransformPoint(localPosition); Quaternion rotation = ((Component)attachedShip).transform.rotation * localRotation; ((Component)this).transform.position = val; ((Component)this).transform.rotation = rotation; if ((Object)(object)playerBody != (Object)null) { playerBody.MovePosition(val); Rigidbody component2 = ((Component)attachedShip).GetComponent<Rigidbody>(); if ((Object)(object)component2 != (Object)null) { playerBody.linearVelocity = component2.linearVelocity; } } } private void OnDestroy() { Player component = ((Component)this).GetComponent<Player>(); if ((Object)(object)component != (Object)null) { attachedPlayers.Remove(component); } } } public class CampfireAudioSystem : MonoBehaviour { [CompilerGenerated] private sealed class <FadeOutAndStop>d__18 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public CampfireAudioSystem <>4__this; public float duration; private float <startVol>5__2; private float <elapsed>5__3; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <FadeOutAndStop>d__18(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { int num = <>1__state; CampfireAudioSystem campfireAudioSystem = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; <startVol>5__2 = campfireAudioSystem.audioSource.volume; <elapsed>5__3 = 0f; break; case 1: <>1__state = -1; break; } if (<elapsed>5__3 < duration) { <elapsed>5__3 += Time.deltaTime; campfireAudioSystem.audioSource.volume = Mathf.Lerp(<startVol>5__2, 0f, <elapsed>5__3 / duration); <>2__current = null; <>1__state = 1; return true; } campfireAudioSystem.audioSource.Stop(); campfireAudioSystem.audioSource.volume = campfireAudioSystem.baseVolume; campfireAudioSystem.fadeCoroutine = null; return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private AudioSource audioSource; private int dancerCount; private float baseVolume; private Coroutine fadeCoroutine; private static readonly Dictionary<EffectArea, CampfireAudioSystem> fireAudioSystems = new Dictionary<EffectArea, CampfireAudioSystem>(); private static readonly Dictionary<Player, EffectArea> playerFires = new Dictionary<Player, EffectArea>(); public static bool IsSingingAtFire(Player player) { if ((Object)(object)player != (Object)null) { return playerFires.ContainsKey(player); } return false; } public static EffectArea GetPlayerFire(Player player) { if (playerFires.TryGetValue(player, out var value)) { return value; } return null; } public static void StartSinging(Player player, EffectArea fire, int shantyIndex) { if (!((Object)(object)player == (Object)null) && !((Object)(object)fire == (Object)null)) { playerFires[player] = fire; GetOrCreate(fire).AddDancer(shantyIndex); } } public static void StopSinging(Player player) { if (!((Object)(object)player == (Object)null) && playerFires.TryGetValue(player, out var value)) { playerFires.Remove(player); Get(value)?.RemoveDancer(); } } public static EffectArea FindNearbyFire(Player player, float range) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) EffectArea val = EffectArea.IsPointInsideArea(((Component)player).transform.position, (Type)1, range); if ((Object)(object)val == (Object)null) { return null; } string text = ((Object)((Component)val).gameObject).name.ToLower(); Transform parent = ((Component)val).transform.parent; string text2 = (((Object)(object)parent != (Object)null) ? ((Object)((Component)parent).gameObject).name.ToLower() : ""); if (text.Contains("torch") || text2.Contains("torch") || text.Contains("brazier") || text2.Contains("brazier") || text.Contains("sconce") || text2.Contains("sconce")) { return null; } return val; } private static CampfireAudioSystem GetOrCreate(EffectArea fire) { if ((Object)(object)fire == (Object)null) { return null; } if (fireAudioSystems.TryGetValue(fire, out var value) && (Object)(object)value != (Object)null) { return value; } CampfireAudioSystem campfireAudioSystem = ((Component)fire).gameObject.AddComponent<CampfireAudioSystem>(); fireAudioSystems[fire] = campfireAudioSystem; return campfireAudioSystem; } private static CampfireAudioSystem Get(EffectArea fire) { if ((Object)(object)fire != (Object)null && fireAudioSystems.TryGetValue(fire, out var value)) { return value; } return null; } private void Awake() { baseVolume = ModConfig.ShantyVolume.Value; audioSource = ((Component)this).gameObject.AddComponent<AudioSource>(); audioSource.spatialBlend = 1f; audioSource.rolloffMode = (AudioRolloffMode)1; audioSource.minDistance = 3f; audioSource.maxDistance = ModConfig.MaxHearingDistance.Value; audioSource.loop = true; audioSource.playOnAwake = false; audioSource.volume = baseVolume; audioSource.dopplerLevel = 0f; } public void AddDancer(int shantyIndex) { dancerCount++; if (fadeCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(fadeCoroutine); fadeCoroutine = null; audioSource.volume = GetTargetVolume(); } if (audioSource.isPlaying) { audioSource.volume = GetTargetVolume(); return; } AudioClip[] shanties = Plugin.Shanties; if (shanties != null && shanties.Length != 0 && Plugin.AudioReady) { int num = shantyIndex % shanties.Length; audioSource.clip = shanties[num]; audioSource.volume = GetTargetVolume(); audioSource.Play(); } } public void ChangeSong(int shantyIndex) { AudioClip[] shanties = Plugin.Shanties; if (shanties != null && shanties.Length != 0 && Plugin.AudioReady) { int num = shantyIndex % shanties.Length; audioSource.clip = shanties[num]; audioSource.Play(); } } public void RemoveDancer() { dancerCount = Mathf.Max(0, dancerCount - 1); if (dancerCount == 0) { if (audioSource.isPlaying && fadeCoroutine == null) { fadeCoroutine = ((MonoBehaviour)this).StartCoroutine(FadeOutAndStop(2f)); } } else { audioSource.volume = GetTargetVolume(); } } private float GetTargetVolume() { if (!ModConfig.MoreDancersLouder.Value) { return baseVolume; } float num = (float)(dancerCount - 1) * 0.1f; return Mathf.Clamp01(baseVolume + num); } [IteratorStateMachine(typeof(<FadeOutAndStop>d__18))] private IEnumerator FadeOutAndStop(float duration) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <FadeOutAndStop>d__18(0) { <>4__this = this, duration = duration }; } private void OnDestroy() { EffectArea component = ((Component)this).GetComponent<EffectArea>(); if ((Object)(object)component != (Object)null) { fireAudioSystems.Remove(component); } } } public class ShantyAudioSystem : MonoBehaviour { [CompilerGenerated] private sealed class <FadeOutAndStop>d__15 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public ShantyAudioSystem <>4__this; public float duration; private float <startVol>5__2; private float <elapsed>5__3; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <FadeOutAndStop>d__15(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { int num = <>1__state; ShantyAudioSystem shantyAudioSystem = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; <startVol>5__2 = shantyAudioSystem.audioSource.volume; <elapsed>5__3 = 0f; break; case 1: <>1__state = -1; break; } if (<elapsed>5__3 < duration) { <elapsed>5__3 += Time.deltaTime; shantyAudioSystem.audioSource.volume = Mathf.Lerp(<startVol>5__2, 0f, <elapsed>5__3 / duration); <>2__current = null; <>1__state = 1; return true; } shantyAudioSystem.audioSource.Stop(); shantyAudioSystem.audioSource.volume = shantyAudioSystem.baseVolume; shantyAudioSystem.fadeCoroutine = null; return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private AudioSource audioSource; private int dancerCount; private float baseVolume; private Coroutine fadeCoroutine; private static int nextShantyIndex; private static readonly Dictionary<Ship, ShantyAudioSystem> shipAudioSystems = new Dictionary<Ship, ShantyAudioSystem>(); public int DancerCount => dancerCount; public static ShantyAudioSystem GetOrCreate(Ship ship) { if ((Object)(object)ship == (Object)null) { return null; } if (shipAudioSystems.TryGetValue(ship, out var value) && (Object)(object)value != (Object)null) { return value; } ShantyAudioSystem shantyAudioSystem = ((Component)ship).gameObject.AddComponent<ShantyAudioSystem>(); shipAudioSystems[ship] = shantyAudioSystem; return shantyAudioSystem; } public static ShantyAudioSystem Get(Ship ship) { if ((Object)(object)ship != (Object)null && shipAudioSystems.TryGetValue(ship, out var value)) { return value; } return null; } private void Awake() { baseVolume = ModConfig.ShantyVolume.Value; audioSource = ((Component)this).gameObject.AddComponent<AudioSource>(); audioSource.spatialBlend = 1f; audioSource.rolloffMode = (AudioRolloffMode)1; audioSource.minDistance = 5f; audioSource.maxDistance = ModConfig.MaxHearingDistance.Value; audioSource.loop = true; audioSource.playOnAwake = false; audioSource.volume = baseVolume; audioSource.dopplerLevel = 0.3f; } public void AddDancer(int shantyIndex) { dancerCount++; if (fadeCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(fadeCoroutine); fadeCoroutine = null; audioSource.volume = GetTargetVolume(); } if (audioSource.isPlaying) { audioSource.volume = GetTargetVolume(); return; } AudioClip[] shanties = Plugin.Shanties; if (shanties != null && shanties.Length != 0 && Plugin.AudioReady) { int num = nextShantyIndex % shanties.Length; nextShantyIndex++; audioSource.clip = shanties[num]; audioSource.volume = GetTargetVolume(); audioSource.Play(); ManualLogSource obj = Plugin.Instance?.Log; if (obj != null) { obj.LogInfo((object)$"Now playing: {((Object)shanties[num]).name} ({shanties[num].length:F1}s)"); } } } public void ChangeSong(int shantyIndex) { AudioClip[] shanties = Plugin.Shanties; if (shanties != null && shanties.Length != 0 && Plugin.AudioReady) { int num = shantyIndex % shanties.Length; audioSource.clip = shanties[num]; audioSource.Play(); } } public void RemoveDancer() { dancerCount = Mathf.Max(0, dancerCount - 1); if (dancerCount == 0) { if (audioSource.isPlaying && fadeCoroutine == null) { fadeCoroutine = ((MonoBehaviour)this).StartCoroutine(FadeOutAndStop(2f)); } } else { audioSource.volume = GetTargetVolume(); } } private float GetTargetVolume() { if (!ModConfig.MoreDancersLouder.Value) { return baseVolume; } float num = (float)(dancerCount - 1) * 0.1f; return Mathf.Clamp01(baseVolume + num); } [IteratorStateMachine(typeof(<FadeOutAndStop>d__15))] private IEnumerator FadeOutAndStop(float duration) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <FadeOutAndStop>d__15(0) { <>4__this = this, duration = duration }; } private void OnDestroy() { Ship component = ((Component)this).GetComponent<Ship>(); if ((Object)(object)component != (Object)null) { shipAudioSystems.Remove(component); } } } public static class ShantyNetworkSystem { private const string RPC_DANCE_START = "VikingShanties_DanceStart"; private const string RPC_DANCE_STOP = "VikingShanties_DanceStop"; private const string ZDO_DANCING = "VikingShanties_dancing"; private const string ZDO_SHANTY_INDEX = "VikingShanties_shantyIdx"; private static bool registered; public static void Register() { if (!registered && ZRoutedRpc.instance != null) { ZRoutedRpc.instance.Register<ZDOID, int>("VikingShanties_DanceStart", (Action<long, ZDOID, int>)RPC_OnDanceStarted); ZRoutedRpc.instance.Register<ZDOID>("VikingShanties_DanceStop", (Action<long, ZDOID>)RPC_OnDanceStopped); registered = true; } } private static ZNetView GetNView(Character character) { return Traverse.Create((object)character).Field("m_nview").GetValue<ZNetView>(); } public static void SendDanceStarted(Player player, int shantyIndex) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) if (ZRoutedRpc.instance != null) { ZDOID zDOID = ((Character)player).GetZDOID(); ZNetView nView = GetNView((Character)(object)player); ZDO val = ((nView != null) ? nView.GetZDO() : null); if (val != null) { val.Set("VikingShanties_dancing", true); val.Set("VikingShanties_shantyIdx", shantyIndex); } ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "VikingShanties_DanceStart", new object[2] { zDOID, shantyIndex }); } } public static void SendDanceStopped(Player player) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) if (ZRoutedRpc.instance != null) { ZDOID zDOID = ((Character)player).GetZDOID(); ZNetView nView = GetNView((Character)(object)player); ZDO val = ((nView != null) ? nView.GetZDO() : null); if (val != null) { val.Set("VikingShanties_dancing", false); } ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "VikingShanties_DanceStop", new object[1] { zDOID }); } } private static void RPC_OnDanceStarted(long sender, ZDOID playerZdoid, int shantyIndex) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsDedicated()) { return; } ZNetScene instance = ZNetScene.instance; GameObject val = ((instance != null) ? instance.FindInstance(playerZdoid) : null); if ((Object)(object)val == (Object)null) { return; } Player component = val.GetComponent<Player>(); if (!((Object)(object)component == (Object)null)) { if ((Object)(object)component != (Object)(object)Player.m_localPlayer) { BoatAttachSystem.AttachToShip(component); } Ship val2 = BoatAttachSystem.FindShipUnderPlayer(component); if (!((Object)(object)val2 == (Object)null)) { ShantyAudioSystem.GetOrCreate(val2).AddDancer(shantyIndex); } } } private static void RPC_OnDanceStopped(long sender, ZDOID playerZdoid) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsDedicated()) { return; } ZNetScene instance = ZNetScene.instance; GameObject val = ((instance != null) ? instance.FindInstance(playerZdoid) : null); if ((Object)(object)val == (Object)null) { return; } Player component = val.GetComponent<Player>(); if (!((Object)(object)component == (Object)null)) { Ship val2 = BoatAttachSystem.GetAttachedShip(component); if ((Object)(object)val2 == (Object)null) { val2 = BoatAttachSystem.FindShipUnderPlayer(component); } if ((Object)(object)component != (Object)(object)Player.m_localPlayer) { BoatAttachSystem.DetachFromShip(component); } if ((Object)(object)val2 != (Object)null) { ShantyAudioSystem.Get(val2)?.RemoveDancer(); } } } public static void CheckZDOState(Player player) { if ((Object)(object)player == (Object)null || (Object)(object)player == (Object)(object)Player.m_localPlayer) { return; } ZNetView nView = GetNView((Character)(object)player); ZDO val = ((nView != null) ? nView.GetZDO() : null); if (val != null && val.GetBool("VikingShanties_dancing", false) && !BoatAttachSystem.IsAttached(player)) { int @int = val.GetInt("VikingShanties_shantyIdx", 0); BoatAttachSystem.AttachToShip(player); Ship val2 = BoatAttachSystem.FindShipUnderPlayer(player); if ((Object)(object)val2 != (Object)null) { ShantyAudioSystem.GetOrCreate(val2).AddDancer(@int); } } } } } namespace VikingShanties.Patches { public static class PlayerPatches { [HarmonyPatch(typeof(Player), "Update")] public static class PlayerUpdatePatch { public static void Postfix(Player __instance) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) if (!ModConfig.Enabled.Value || (Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return; } if (pendingBoatDanceFrames > 0) { pendingBoatDanceFrames--; if (pendingBoatDanceFrames == 0) { StartBoatDancing(__instance); } } else if (ZInput.GetKeyDown(ModConfig.DanceKey.Value, true)) { if (Input.GetKey((KeyCode)304) || Input.GetKey((KeyCode)303)) { HandleSkipSong(__instance); } else { HandleDanceToggle(__instance); } } } } [HarmonyPatch(typeof(Player), "SetControls")] public static class PlayerSetControlsPatch { public static void Prefix(Player __instance, ref Vector3 movedir, ref bool attack, ref bool attackHold, ref bool secondaryAttack, ref bool secondaryAttackHold, ref bool block, ref bool blockHold, ref bool jump, ref bool crouch, ref bool run, ref bool autoRun, ref bool dodge) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) if (BoatAttachSystem.IsAttached(__instance) || CampfireAudioSystem.IsSingingAtFire(__instance)) { movedir = Vector3.zero; attack = false; attackHold = false; secondaryAttack = false; secondaryAttackHold = false; block = false; blockHold = false; jump = false; crouch = false; run = false; autoRun = false; dodge = false; } } } [HarmonyPatch(typeof(Player), "Interact")] public static class PlayerInteractPatch { public static bool Prefix(Player __instance) { if (BoatAttachSystem.IsAttached(__instance)) { return false; } if (CampfireAudioSystem.IsSingingAtFire(__instance)) { return false; } return true; } } [HarmonyPatch(typeof(Player), "StopEmote")] public static class PlayerStopEmotePatch { public static void Postfix(Player __instance) { if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return; } if (BoatAttachSystem.IsAttached(__instance)) { Ship attachedShip = BoatAttachSystem.GetAttachedShip(__instance); BoatAttachSystem.DetachFromShip(__instance); ShantyNetworkSystem.SendDanceStopped(__instance); if ((Object)(object)attachedShip != (Object)null) { ShantyAudioSystem.Get(attachedShip)?.RemoveDancer(); } } if (CampfireAudioSystem.IsSingingAtFire(__instance)) { CampfireAudioSystem.StopSinging(__instance); } } } [HarmonyPatch(typeof(Player), "OnDeath")] public static class PlayerOnDeathPatch { public static void Prefix(Player __instance) { if (BoatAttachSystem.IsAttached(__instance)) { Ship attachedShip = BoatAttachSystem.GetAttachedShip(__instance); BoatAttachSystem.DetachFromShip(__instance); if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer) { ShantyNetworkSystem.SendDanceStopped(__instance); } if ((Object)(object)attachedShip != (Object)null) { ShantyAudioSystem.Get(attachedShip)?.RemoveDancer(); } } if (CampfireAudioSystem.IsSingingAtFire(__instance)) { CampfireAudioSystem.StopSinging(__instance); } } } private static float lastDanceTime; private static int nextEmoteIndex; private static int nextShantyIndex; private static int pendingBoatDanceFrames; private static readonly string[] BoatEmotes = new string[2] { "dance", "headbang" }; private const float FIRE_DETECT_RANGE = 10f; private static void HandleSkipSong(Player player) { AudioClip[] shanties = Plugin.Shanties; if (shanties == null || shanties.Length == 0) { return; } int num = PickShantyIndex(); if (BoatAttachSystem.IsAttached(player)) { Ship attachedShip = BoatAttachSystem.GetAttachedShip(player); if ((Object)(object)attachedShip != (Object)null) { ShantyAudioSystem shantyAudioSystem = ShantyAudioSystem.Get(attachedShip); if ((Object)(object)shantyAudioSystem != (Object)null) { shantyAudioSystem.ChangeSong(num); ((Character)player).Message((MessageType)2, "Now playing: " + ((Object)shanties[num % shanties.Length]).name, 0, (Sprite)null); } } } else { if (!CampfireAudioSystem.IsSingingAtFire(player)) { return; } EffectArea playerFire = CampfireAudioSystem.GetPlayerFire(player); if ((Object)(object)playerFire != (Object)null) { CampfireAudioSystem component = ((Component)playerFire).GetComponent<CampfireAudioSystem>(); if ((Object)(object)component != (Object)null) { component.ChangeSong(num); ((Character)player).Message((MessageType)2, "Now playing: " + ((Object)shanties[num % shanties.Length]).name, 0, (Sprite)null); } } } } private static void HandleDanceToggle(Player player) { if (BoatAttachSystem.IsAttached(player)) { StopBoatDancing(player); } else if (CampfireAudioSystem.IsSingingAtFire(player)) { StopFireSinging(player); } else { if (Time.time - lastDanceTime < ModConfig.Cooldown.Value) { return; } if ((Object)(object)Ship.GetLocalShip() != (Object)null) { IDoodadController doodadController = player.GetDoodadController(); if (doodadController != null && !(doodadController is Ship)) { ((Character)player).Message((MessageType)2, "Can't dance while steering!", 0, (Sprite)null); } else if (((Character)player).IsAttached() || doodadController != null) { if (((Character)player).IsAttached()) { ((Character)player).AttachStop(); } if (doodadController != null) { player.StopDoodadControl(); } lastDanceTime = Time.time; pendingBoatDanceFrames = 3; } else { StartBoatDancing(player); } } else { EffectArea val = CampfireAudioSystem.FindNearbyFire(player, 10f); if ((Object)(object)val != (Object)null) { StartFireSinging(player, val); } } } } private static string PickEmote() { string value = ModConfig.EmoteMode.Value; if (value == "Dance") { return "dance"; } if (value == "Headbang") { return "headbang"; } string result = BoatEmotes[nextEmoteIndex % BoatEmotes.Length]; nextEmoteIndex++; return result; } private static int PickShantyIndex() { AudioClip[] shanties = Plugin.Shanties; if (shanties == null || shanties.Length == 0) { return 0; } int forcedSongIndex = ModConfig.GetForcedSongIndex(shanties); if (forcedSongIndex >= 0) { return forcedSongIndex; } int result = nextShantyIndex % shanties.Length; nextShantyIndex++; return result; } private static void StartBoatDancing(Player player) { lastDanceTime = Time.time; if (((Character)player).IsAttached()) { ((Character)player).AttachStop(); } string text = PickEmote(); if (player.StartEmote(text, false)) { BoatAttachSystem.AttachToShip(player); int shantyIndex = PickShantyIndex(); ShantyNetworkSystem.SendDanceStarted(player, shantyIndex); Ship val = BoatAttachSystem.FindShipUnderPlayer(player); if ((Object)(object)val != (Object)null) { ShantyAudioSystem.GetOrCreate(val).AddDancer(shantyIndex); } } } private static void StopBoatDancing(Player player) { Traverse.Create((object)player).Method("StopEmote", Array.Empty<object>()).GetValue(); Ship attachedShip = BoatAttachSystem.GetAttachedShip(player); BoatAttachSystem.DetachFromShip(player); ShantyNetworkSystem.SendDanceStopped(player); if ((Object)(object)attachedShip != (Object)null) { ShantyAudioSystem.Get(attachedShip)?.RemoveDancer(); } } private static void StartFireSinging(Player player, EffectArea fire) { lastDanceTime = Time.time; if (((Character)player).IsAttached()) { ((Character)player).AttachStop(); } string text = PickEmote(); if (player.StartEmote(text, false)) { int shantyIndex = PickShantyIndex(); CampfireAudioSystem.StartSinging(player, fire, shantyIndex); } } private static void StopFireSinging(Player player) { Traverse.Create((object)player).Method("StopEmote", Array.Empty<object>()).GetValue(); CampfireAudioSystem.StopSinging(player); } } public static class ShipPatches { [HarmonyPatch(typeof(ZNet), "Awake")] public static class ZNetAwakePatch { public static void Postfix() { ShantyNetworkSystem.Register(); } } [HarmonyPatch(typeof(Ship), "OnDestroyed")] public static class ShipOnDestroyedPatch { public static void Prefix(Ship __instance) { List<Player> value = Traverse.Create((object)__instance).Field("m_players").GetValue<List<Player>>(); if (value == null) { return; } foreach (Player item in value) { if (BoatAttachSystem.IsAttached(item)) { BoatAttachSystem.DetachFromShip(item); } } } } } }