Decompiled source of Shanty4Heim v1.3.2

plugins/SeaShanty4Heim.dll

Decompiled a week ago
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using Valheim.SettingsGui;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("SeaShanty4Heim")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("SeaShanty4Heim")]
[assembly: AssemblyCopyright("Copyright ©  2025")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("82659a80-5ea4-4861-89b2-1482c7681b90")]
[assembly: AssemblyFileVersion("1.2.1.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("1.2.1.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[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 SeaShanty4Heim
{
	[BepInPlugin("com.jonny.seashanty4heim", "SeaShanty4Heim", "1.3.2")]
	public class SeaShantyPlugin : BaseUnityPlugin
	{
		private sealed class NamedTrack
		{
			public readonly string PlayerName;

			public readonly string Resource;

			public readonly Regex Pattern;

			public AudioClip Clip;

			public NamedTrack(string playerName, string resource)
			{
				PlayerName = playerName;
				Resource = resource;
				Pattern = new Regex("(?<![\\p{L}\\p{N}])" + Regex.Escape(playerName) + "(?![\\p{L}\\p{N}])", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
			}
		}

		[HarmonyPatch(typeof(Player), "OnSkillLevelup")]
		private static class Patch_SkillLevelup
		{
			private static void Postfix(Player __instance)
			{
				if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && !((Object)(object)levelUpSource == (Object)null) && ShantyVolume.LevelUpLive)
				{
					levelUpSource.volume = ShantyVolume.Effective;
					levelUpSource.PlayOneShot(levelUp);
				}
			}
		}

		public const string PluginGuid = "com.jonny.seashanty4heim";

		public const string PluginName = "SeaShanty4Heim";

		public const string PluginVersion = "1.3.2";

		private const string ShantyResource = "SeaShanty4Heim.Assets.Sea_Shanty_2.wav";

		private const string LevelUpResource = "SeaShanty4Heim.Assets.Engineer.wav";

		private const string RoarResource = "SeaShanty4Heim.Assets.ShowerHandle.wav";

		private const string SourceName = "SeaShanty4Heim_Source";

		private const string DanceEmote = "dance";

		private const string HeadbangEmote = "headbang";

		private const string RoarEmote = "roar";

		private readonly NamedTrack[] shipTracks = new NamedTrack[1]
		{
			new NamedTrack("Bjarne", "SeaShanty4Heim.Assets.Bjarne.wav")
		};

		private readonly NamedTrack[] landTracks = new NamedTrack[2]
		{
			new NamedTrack("Boris", "SeaShanty4Heim.Assets.Boris.wav"),
			new NamedTrack("Tanka Jahari", "SeaShanty4Heim.Assets.chicago.wav")
		};

		private AudioClip shanty;

		private AudioClip roar;

		private bool haveAudio;

		private static AudioClip levelUp;

		private static AudioSource levelUpSource;

		private void Awake()
		{
			//IL_015e: Unknown result type (might be due to invalid IL or missing references)
			ShantyVolume.Log = ((BaseUnityPlugin)this).Logger;
			ShantyVolume.Config = ((BaseUnityPlugin)this).Config.Bind<float>("Audio", "Volume", 0.6f, "Playback volume, 0 to 1. Also adjustable in game under Settings > Audio.");
			ShantyVolume.Live = ShantyVolume.Config.Value;
			ShantyVolume.LevelUpConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Audio", "LevelUpSound", true, "Play a jingle when one of your own skills levels up. Also toggleable in game under Settings > Audio.");
			ShantyVolume.LevelUpLive = ShantyVolume.LevelUpConfig.Value;
			ShantyVolume.Master = PlatformPrefs.GetFloat("MasterVolume", 1f);
			shanty = TryLoad("SeaShanty4Heim.Assets.Sea_Shanty_2.wav", "SeaShanty2", required: true);
			haveAudio = (Object)(object)shanty != (Object)null;
			haveAudio |= LoadAll(shipTracks);
			haveAudio |= LoadAll(landTracks);
			roar = TryLoad("SeaShanty4Heim.Assets.ShowerHandle.wav", "ShowerHandle", required: false);
			haveAudio |= (Object)(object)roar != (Object)null;
			levelUp = TryLoad("SeaShanty4Heim.Assets.Engineer.wav", "Engineer", required: false);
			if ((Object)(object)levelUp != (Object)null)
			{
				levelUpSource = ((Component)this).gameObject.AddComponent<AudioSource>();
				levelUpSource.playOnAwake = false;
				levelUpSource.spatialBlend = 0f;
			}
			new Harmony("com.jonny.seashanty4heim").PatchAll();
		}

		private void Update()
		{
			if (!haveAudio)
			{
				return;
			}
			foreach (Player allPlayer in Player.GetAllPlayers())
			{
				if ((Object)(object)allPlayer == (Object)null)
				{
					continue;
				}
				AudioClip val = TrackFor(allPlayer);
				AudioSource val2 = FindSource(allPlayer);
				if ((Object)(object)val == (Object)null)
				{
					if ((Object)(object)val2 != (Object)null && (!((Object)(object)val2.clip == (Object)(object)roar) || !val2.isPlaying))
					{
						Object.Destroy((Object)(object)((Component)val2).gameObject);
					}
					continue;
				}
				if ((Object)(object)val2 == (Object)null)
				{
					val2 = CreateSource(allPlayer);
				}
				val2.volume = ShantyVolume.Effective;
				if ((Object)(object)val2.clip != (Object)(object)val || !val2.isPlaying)
				{
					val2.clip = val;
					val2.loop = (Object)(object)val != (Object)(object)roar;
					val2.timeSamples = ((!((Object)(object)val == (Object)(object)roar)) ? PlayheadFor(val) : 0);
					val2.Play();
				}
			}
		}

		private AudioClip TrackFor(Player player)
		{
			string text = CurrentEmote(player);
			if (text == null)
			{
				return null;
			}
			string playerName = player.GetPlayerName();
			bool flag = ((Character)player).InNumShipVolumes > 0;
			if (text == "roar")
			{
				if (!IsShowerHandle(playerName))
				{
					return null;
				}
				return roar;
			}
			if (text == "headbang")
			{
				if (!flag)
				{
					return null;
				}
				return Match(shipTracks, playerName);
			}
			if (text != "dance")
			{
				return null;
			}
			if (!flag)
			{
				return Match(landTracks, playerName);
			}
			return shanty;
		}

		private static string CurrentEmote(Player player)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			if (!((Character)player).InEmote())
			{
				return null;
			}
			ZDOMan instance = ZDOMan.instance;
			ZDO obj = ((instance != null) ? instance.GetZDO(((Character)player).GetZDOID()) : null);
			if (obj == null)
			{
				return null;
			}
			return obj.GetString(ZDOVars.s_emote, string.Empty);
		}

		private static bool IsShowerHandle(string playerName)
		{
			if (playerName.IndexOf("shower", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return playerName.IndexOf("handle", StringComparison.OrdinalIgnoreCase) >= 0;
			}
			return false;
		}

		private static AudioClip Match(NamedTrack[] tracks, string playerName)
		{
			if (string.IsNullOrEmpty(playerName))
			{
				return null;
			}
			foreach (NamedTrack namedTrack in tracks)
			{
				if ((Object)(object)namedTrack.Clip != (Object)null && namedTrack.Pattern.IsMatch(playerName))
				{
					return namedTrack.Clip;
				}
			}
			return null;
		}

		private bool LoadAll(NamedTrack[] tracks)
		{
			bool flag = false;
			foreach (NamedTrack namedTrack in tracks)
			{
				namedTrack.Clip = TryLoad(namedTrack.Resource, namedTrack.PlayerName, required: false);
				flag |= (Object)(object)namedTrack.Clip != (Object)null;
			}
			return flag;
		}

		private AudioSource CreateSource(Player player)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("SeaShanty4Heim_Source");
			val.transform.SetParent(((Component)player).transform, false);
			AudioSource obj = val.AddComponent<AudioSource>();
			obj.loop = true;
			obj.spatialBlend = 1f;
			obj.rolloffMode = (AudioRolloffMode)1;
			obj.minDistance = 5f;
			obj.maxDistance = 50f;
			obj.dopplerLevel = 0f;
			return obj;
		}

		private int PlayheadFor(AudioClip clip)
		{
			foreach (Player allPlayer in Player.GetAllPlayers())
			{
				AudioSource val = FindSource(allPlayer);
				if ((Object)(object)val != (Object)null && val.isPlaying && (Object)(object)val.clip == (Object)(object)clip)
				{
					return val.timeSamples;
				}
			}
			return 0;
		}

		private static AudioSource FindSource(Player player)
		{
			if ((Object)(object)player == (Object)null)
			{
				return null;
			}
			Transform val = ((Component)player).transform.Find("SeaShanty4Heim_Source");
			if (!((Object)(object)val == (Object)null))
			{
				return ((Component)val).GetComponent<AudioSource>();
			}
			return null;
		}

		private AudioClip TryLoad(string resourceName, string clipName, bool required)
		{
			try
			{
				AudioClip val = LoadEmbeddedClip(resourceName, clipName);
				((BaseUnityPlugin)this).Logger.LogInfo((object)$"Loaded {clipName}: {val.length:F0}s, {val.channels}ch @ {val.frequency}Hz.");
				return val;
			}
			catch (FileNotFoundException) when (!required)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)("No " + clipName + " track embedded in this build, skipping it."));
				return null;
			}
			catch (Exception ex2)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("Could not load " + clipName + ", it will stay silent: " + ex2.Message));
				return null;
			}
		}

		private AudioClip LoadEmbeddedClip(string resourceName, string clipName)
		{
			Assembly executingAssembly = Assembly.GetExecutingAssembly();
			using Stream stream = executingAssembly.GetManifestResourceStream(resourceName);
			if (stream == null)
			{
				throw new FileNotFoundException("embedded resource '" + resourceName + "' is missing, the assembly has: " + string.Join(", ", executingAssembly.GetManifestResourceNames()));
			}
			byte[] array = new byte[stream.Length];
			int num;
			for (int i = 0; i < array.Length; i += num)
			{
				num = stream.Read(array, i, array.Length - i);
				if (num == 0)
				{
					throw new EndOfStreamException($"resource ended after {i} of {array.Length} bytes");
				}
			}
			int channels;
			int frequency;
			float[] array2 = WavReader.Read(array, out channels, out frequency);
			AudioClip obj = AudioClip.Create(clipName, array2.Length / channels, channels, frequency, false);
			obj.SetData(array2, 0);
			return obj;
		}
	}
	public static class WavReader
	{
		public static float[] Read(byte[] wav, out int channels, out int frequency)
		{
			if (wav == null || wav.Length < 44 || Tag(wav, 0) != "RIFF" || Tag(wav, 8) != "WAVE")
			{
				throw new FormatException("not a RIFF/WAVE file");
			}
			channels = 0;
			frequency = 0;
			int num = 0;
			int num2 = -1;
			int num3 = 0;
			int num4 = 12;
			while (num4 + 8 <= wav.Length)
			{
				string text = Tag(wav, num4);
				int num5 = BitConverter.ToInt32(wav, num4 + 4);
				int num6 = num4 + 8;
				if (num5 < 0 || num6 + num5 > wav.Length)
				{
					num5 = wav.Length - num6;
				}
				if (text == "fmt " && num5 >= 16)
				{
					channels = BitConverter.ToInt16(wav, num6 + 2);
					frequency = BitConverter.ToInt32(wav, num6 + 4);
					num = BitConverter.ToInt16(wav, num6 + 14);
				}
				else if (text == "data")
				{
					num2 = num6;
					num3 = num5;
					break;
				}
				num4 = num6 + num5 + (num5 & 1);
			}
			if (channels <= 0 || frequency <= 0)
			{
				throw new FormatException("no usable fmt chunk");
			}
			if (num2 < 0)
			{
				throw new FormatException("no data chunk");
			}
			if (num != 16)
			{
				throw new FormatException($"{num}-bit audio; re-save the file as 16-bit PCM");
			}
			float[] array = new float[num3 / 2];
			for (int i = 0; i < array.Length; i++)
			{
				int num7 = num2 + i * 2;
				array[i] = (float)(short)(wav[num7] | (wav[num7 + 1] << 8)) / 32768f;
			}
			return array;
		}

		private static string Tag(byte[] wav, int at)
		{
			return Encoding.ASCII.GetString(wav, at, 4);
		}
	}
	public static class ShantyVolume
	{
		[HarmonyPatch(typeof(AudioSettings), "Initialize")]
		private static class Patch_Initialize
		{
			private static void Postfix(AudioSettings __instance)
			{
				try
				{
					Build(__instance);
				}
				catch (Exception arg)
				{
					Log.LogError((object)string.Format("Could not add the '{0}' slider: {1}", "Shanty4Heim Sound", arg));
				}
			}
		}

		[HarmonyPatch(typeof(AudioSettings), "OnOkAsync")]
		private static class Patch_OnOk
		{
			private static void Prefix()
			{
				if ((Object)(object)slider != (Object)null)
				{
					Config.Value = Mathf.Clamp01(slider.value);
				}
				if ((Object)(object)levelUpToggle != (Object)null)
				{
					LevelUpConfig.Value = levelUpToggle.isOn;
				}
				Live = Mathf.Clamp01(Config.Value);
				LevelUpLive = LevelUpConfig.Value;
			}
		}

		[HarmonyPatch(typeof(AudioSettings), "OnBack")]
		private static class Patch_OnBack
		{
			private static void Postfix()
			{
				Live = Mathf.Clamp01(Config.Value);
				LevelUpLive = LevelUpConfig.Value;
			}
		}

		[HarmonyPatch(typeof(AudioSettings), "SetGlobalVolumeExceptHaptics")]
		private static class Patch_Master
		{
			private static void Prefix(float __0)
			{
				Master = Mathf.Clamp01(__0);
			}
		}

		public const string Label = "Shanty4Heim Sound";

		public const string LevelUpLabel = "Shanty4Heim Level-up Sound";

		private const string RowName = "Shanty4HeimVolumeRow";

		private const string LevelUpRowName = "Shanty4HeimLevelUpRow";

		public static ManualLogSource Log;

		public static ConfigEntry<float> Config;

		public static ConfigEntry<bool> LevelUpConfig;

		public static float Live = 0.6f;

		public static bool LevelUpLive = true;

		public static float Master = 1f;

		private static Slider slider;

		private static Toggle levelUpToggle;

		public static float Effective => Mathf.Clamp01(Live) * Mathf.Clamp01(Master);

		private static void UpdateText(TMP_Text text, float value)
		{
			if ((Object)(object)text != (Object)null)
			{
				text.text = Mathf.Round(value * 100f) + "%";
			}
		}

		private static Transform RowOf(Transform a0, Transform b0)
		{
			Transform val = a0;
			while ((Object)(object)val != (Object)null)
			{
				Transform val2 = b0;
				while ((Object)(object)val2 != (Object)null)
				{
					if ((Object)(object)val != (Object)(object)val2 && (Object)(object)val.parent != (Object)null && (Object)(object)val.parent == (Object)(object)val2.parent)
					{
						return val;
					}
					val2 = val2.parent;
				}
				val = val.parent;
			}
			if (!((Object)(object)a0.parent != (Object)null))
			{
				return a0;
			}
			return a0.parent;
		}

		private static void Build(AudioSettings settings)
		{
			//IL_0147: Unknown result type (might be due to invalid IL or missing references)
			//IL_0151: Expected O, but got Unknown
			Traverse obj = Traverse.Create((object)settings);
			Slider value = obj.Field("m_musicVolumeSlider").GetValue<Slider>();
			Slider value2 = obj.Field("m_sfxVolumeSlider").GetValue<Slider>();
			TMP_Text value3 = obj.Field("m_musicVolumeText").GetValue<TMP_Text>();
			Toggle value4 = obj.Field("m_continousMusic").GetValue<Toggle>();
			if ((Object)(object)value == (Object)null || (Object)(object)value2 == (Object)null)
			{
				Log.LogWarning((object)"Audio settings sliders not found; leaving the settings tab alone.");
				return;
			}
			Transform val = RowOf(((Component)value).transform, ((Component)value2).transform);
			if ((Object)(object)val.parent == (Object)null)
			{
				Log.LogWarning((object)("Music row '" + ((Object)val).name + "' has no parent to add a row to."));
			}
			else
			{
				if ((Object)(object)val.parent.Find("Shanty4HeimVolumeRow") != (Object)null)
				{
					return;
				}
				Transform val2 = Object.Instantiate<Transform>(val, val.parent);
				((Object)val2).name = "Shanty4HeimVolumeRow";
				val2.SetSiblingIndex(val.GetSiblingIndex() + 1);
				slider = ((Component)val2).GetComponentInChildren<Slider>(true);
				if ((Object)(object)slider == (Object)null)
				{
					Log.LogWarning((object)("Cloned row '" + ((Object)val).name + "' had no Slider; removing it again."));
					Object.Destroy((Object)(object)((Component)val2).gameObject);
					return;
				}
				slider.onValueChanged = new SliderEvent();
				slider.SetValueWithoutNotify(Mathf.Clamp(Config.Value, slider.minValue, slider.maxValue));
				TMP_Text[] componentsInChildren = ((Component)val).GetComponentsInChildren<TMP_Text>(true);
				TMP_Text[] componentsInChildren2 = ((Component)val2).GetComponentsInChildren<TMP_Text>(true);
				int num = Array.IndexOf(componentsInChildren, value3);
				TMP_Text valueText = null;
				for (int i = 0; i < componentsInChildren2.Length; i++)
				{
					if (i == num)
					{
						valueText = componentsInChildren2[i];
					}
					else
					{
						componentsInChildren2[i].text = "Shanty4Heim Sound";
					}
				}
				UpdateText(valueText, slider.value);
				((UnityEvent<float>)(object)slider.onValueChanged).AddListener((UnityAction<float>)delegate(float num2)
				{
					Live = num2;
					UpdateText(valueText, num2);
				});
				Log.LogInfo((object)("Added the 'Shanty4Heim Sound' slider under '" + ((Object)val).name + "' " + $"({componentsInChildren2.Length} label(s), readout index {num})."));
				BuildToggle(value4, ((Component)value).transform, val2);
			}
		}

		private static void BuildToggle(Toggle musicToggle, Transform musicSlider, Transform shantyRow)
		{
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Expected O, but got Unknown
			if ((Object)(object)musicToggle == (Object)null)
			{
				Log.LogWarning((object)"Continuous music toggle not found; leaving the checkbox out.");
				return;
			}
			Transform val = RowOf(((Component)musicToggle).transform, musicSlider);
			if (((Component)val).GetComponentsInChildren<Toggle>(true).Length != 1 || (Object)(object)((Component)val).GetComponentInChildren<Slider>(true) != (Object)null)
			{
				Log.LogWarning((object)("'" + ((Object)val).name + "' does not look like a single checkbox row; leaving the checkbox out."));
				return;
			}
			Transform val2 = Object.Instantiate<Transform>(val, shantyRow.parent);
			((Object)val2).name = "Shanty4HeimLevelUpRow";
			val2.SetSiblingIndex(shantyRow.GetSiblingIndex() + 1);
			levelUpToggle = ((Component)val2).GetComponentInChildren<Toggle>(true);
			if ((Object)(object)levelUpToggle == (Object)null)
			{
				Log.LogWarning((object)("Cloned row '" + ((Object)val).name + "' had no Toggle; removing it again."));
				Object.Destroy((Object)(object)((Component)val2).gameObject);
				return;
			}
			levelUpToggle.onValueChanged = new ToggleEvent();
			levelUpToggle.SetIsOnWithoutNotify(LevelUpConfig.Value);
			((UnityEvent<bool>)(object)levelUpToggle.onValueChanged).AddListener((UnityAction<bool>)delegate(bool value)
			{
				LevelUpLive = value;
			});
			TMP_Text[] componentsInChildren = ((Component)val2).GetComponentsInChildren<TMP_Text>(true);
			for (int num = 0; num < componentsInChildren.Length; num++)
			{
				componentsInChildren[num].text = "Shanty4Heim Level-up Sound";
			}
			Log.LogInfo((object)"Added the 'Shanty4Heim Level-up Sound' checkbox under the 'Shanty4Heim Sound' slider.");
		}
	}
}