Decompiled source of StoneOvenAlarm v1.0.1

plugins/StoneOvenAlarm.dll

Decompiled 2 days ago
using 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.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.Networking;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("StoneOvenAlarm")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.1.0")]
[assembly: AssemblyInformationalVersion("1.0.1")]
[assembly: AssemblyProduct("StoneOvenAlarm")]
[assembly: AssemblyTitle("StoneOvenAlarm")]
[assembly: AssemblyVersion("1.0.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 StoneOvenAlarm
{
	internal class AlarmPlayer : MonoBehaviour
	{
		private const string StoneOvenPrefab = "piece_oven";

		private static readonly Dictionary<SoundPreset, string> GameSounds = new Dictionary<SoundPreset, string>
		{
			{
				SoundPreset.MagicBells,
				"sfx_secretfound"
			},
			{
				SoundPreset.Forge,
				"sfx_gui_craftitem_forge_end"
			},
			{
				SoundPreset.CraftDone,
				"sfx_gui_craftitem_end"
			},
			{
				SoundPreset.OvenDone,
				"sfx_oven_done"
			},
			{
				SoundPreset.Hugin,
				"fx_open_catbox"
			}
		};

		private readonly Dictionary<SoundPreset, AudioClip> _gameClips = new Dictionary<SoundPreset, AudioClip>();

		private AudioSource _source;

		private AudioClip _defaultClip;

		private AudioClip _customClip;

		private Coroutine _playing;

		private void Awake()
		{
			_source = ((Component)this).gameObject.AddComponent<AudioSource>();
			_source.playOnAwake = false;
			_source.spatialBlend = 0f;
			_source.ignoreListenerPause = true;
			_defaultClip = CreateDefaultChime();
			LoadCustomClip();
			Plugin.SoundFile.SettingChanged += delegate
			{
				if (!string.IsNullOrWhiteSpace(Plugin.SoundFile.Value))
				{
					Plugin.SoundPresetSetting.Value = SoundPreset.Custom;
				}
				LoadCustomClip();
			};
		}

		private void Update()
		{
			if (FilePicker.TryTakeResult(out var path))
			{
				Plugin.SoundFile.Value = path;
			}
		}

		public void OnFoodDone(CookingStation station, string itemPrefabName)
		{
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			if (!Plugin.Enabled.Value || _playing != null)
			{
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			if (!((Object)(object)localPlayer == (Object)null) && (!Plugin.StoneOvenOnly.Value || !(Utils.GetPrefabName(((Component)station).gameObject) != "piece_oven")) && !(Vector3.Distance(((Component)localPlayer).transform.position, ((Component)station).transform.position) > Plugin.MaxDistance.Value))
			{
				if (Plugin.ShowMessage.Value && (Object)(object)MessageHud.instance != (Object)null)
				{
					MessageHud.instance.ShowMessage((MessageType)2, BuildMessage(station, itemPrefabName), 0, (Sprite)null, false, true);
				}
				Play();
			}
		}

		public void Play()
		{
			if (_playing != null)
			{
				((MonoBehaviour)this).StopCoroutine(_playing);
			}
			_source.Stop();
			_playing = ((MonoBehaviour)this).StartCoroutine(PlayRepeated());
		}

		private IEnumerator PlayRepeated()
		{
			AudioClip clip = GetClip();
			int count = Mathf.Max(1, Plugin.RepeatCount.Value);
			for (int n = 0; n < count; n++)
			{
				_source.PlayOneShot(clip, Plugin.Volume.Value);
				if (n < count - 1)
				{
					yield return (object)new WaitForSecondsRealtime(clip.length + Plugin.RepeatInterval.Value);
				}
			}
			yield return (object)new WaitForSecondsRealtime(clip.length);
			_playing = null;
		}

		private AudioClip GetClip()
		{
			SoundPreset value = Plugin.SoundPresetSetting.Value;
			if (value == SoundPreset.Custom)
			{
				if (!((Object)(object)_customClip != (Object)null))
				{
					return _defaultClip;
				}
				return _customClip;
			}
			if (GameSounds.TryGetValue(value, out var value2))
			{
				if (!_gameClips.TryGetValue(value, out var value3) || (Object)(object)value3 == (Object)null)
				{
					value3 = FindGameClip(value2);
					if ((Object)(object)value3 != (Object)null)
					{
						_gameClips[value] = value3;
					}
				}
				if ((Object)(object)value3 != (Object)null)
				{
					return value3;
				}
			}
			return _defaultClip;
		}

		private static AudioClip FindGameClip(string prefabName)
		{
			if ((Object)(object)ZNetScene.instance == (Object)null)
			{
				return null;
			}
			GameObject prefab = ZNetScene.instance.GetPrefab(prefabName);
			ZSFX val = (((Object)(object)prefab != (Object)null) ? prefab.GetComponentInChildren<ZSFX>(true) : null);
			if ((Object)(object)val == (Object)null || val.m_audioClips == null || val.m_audioClips.Length == 0)
			{
				Plugin.Log.LogWarning((object)("Game sound '" + prefabName + "' not found. Using the built-in chime."));
				return null;
			}
			return val.m_audioClips[0];
		}

		private static string BuildMessage(CookingStation station, string itemPrefabName)
		{
			string newValue = itemPrefabName;
			GameObject val = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetItemPrefab(itemPrefabName) : null);
			ItemDrop val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<ItemDrop>() : null);
			if ((Object)(object)val2 != (Object)null)
			{
				newValue = val2.m_itemData.m_shared.m_name;
			}
			string text = Plugin.MessageText.Value;
			if (string.IsNullOrWhiteSpace(text))
			{
				text = "Comida pronta!";
			}
			text = text.Replace("{item}", newValue).Replace("{station}", station.m_name);
			if (Localization.instance == null)
			{
				return text;
			}
			return Localization.instance.Localize(text);
		}

		private void LoadCustomClip()
		{
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			_customClip = null;
			string text = Plugin.SoundFile.Value?.Trim().Trim('"');
			if (string.IsNullOrEmpty(text))
			{
				return;
			}
			string text2 = ResolveSoundPath(text);
			if (text2 == null)
			{
				Plugin.Log.LogWarning((object)("Sound file not found: '" + text + "'. Using the built-in chime."));
				return;
			}
			AudioType type;
			switch (Path.GetExtension(text2).ToLowerInvariant())
			{
			case ".wav":
				type = (AudioType)20;
				break;
			case ".ogg":
				type = (AudioType)14;
				break;
			case ".mp3":
				type = (AudioType)13;
				break;
			default:
				Plugin.Log.LogWarning((object)("Unsupported sound format '" + text2 + "'. Use .wav, .ogg or .mp3."));
				return;
			}
			((MonoBehaviour)this).StartCoroutine(LoadClip(text2, type));
		}

		private static string ResolveSoundPath(string setting)
		{
			if (Path.IsPathRooted(setting))
			{
				if (!File.Exists(setting))
				{
					return null;
				}
				return setting;
			}
			string directoryName = Path.GetDirectoryName(((BaseUnityPlugin)Plugin.Instance).Info.Location);
			string[] array = new string[3]
			{
				Paths.ConfigPath,
				directoryName,
				Paths.PluginPath
			};
			foreach (string text in array)
			{
				if (!string.IsNullOrEmpty(text))
				{
					string text2 = Path.Combine(text, setting);
					if (File.Exists(text2))
					{
						return text2;
					}
				}
			}
			return null;
		}

		private IEnumerator LoadClip(string path, AudioType type)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip(new Uri(path).AbsoluteUri, type);
			try
			{
				yield return request.SendWebRequest();
				if ((int)request.result != 1)
				{
					Plugin.Log.LogWarning((object)("Failed to load sound '" + path + "': " + request.error));
					yield break;
				}
				AudioClip content = DownloadHandlerAudioClip.GetContent(request);
				if ((Object)(object)content == (Object)null || content.length <= 0f)
				{
					Plugin.Log.LogWarning((object)("Sound '" + path + "' is empty or invalid."));
					yield break;
				}
				((Object)content).name = Path.GetFileNameWithoutExtension(path);
				_customClip = content;
				Plugin.Log.LogInfo((object)("Loaded custom alarm sound: " + path));
			}
			finally
			{
				((IDisposable)request)?.Dispose();
			}
		}

		private static AudioClip CreateDefaultChime()
		{
			float[] array = new float[2] { 1318.51f, 1046.5f };
			int num = 19845;
			float[] array2 = new float[num * array.Length];
			for (int i = 0; i < array.Length; i++)
			{
				float num2 = array[i];
				for (int j = 0; j < num; j++)
				{
					float num3 = (float)j / 44100f;
					float num4 = Mathf.Clamp01(num3 / 0.005f);
					float num5 = Mathf.Exp((0f - num3) * 7f);
					float num6 = Mathf.Sin(MathF.PI * 2f * num2 * num3) + 0.35f * Mathf.Sin(MathF.PI * 2f * num2 * 2.76f * num3) * Mathf.Exp((0f - num3) * 12f) + 0.2f * Mathf.Sin(MathF.PI * 2f * num2 * 5.4f * num3) * Mathf.Exp((0f - num3) * 20f);
					array2[i * num + j] = num6 * num4 * num5 * 0.5f;
				}
			}
			AudioClip obj = AudioClip.Create("StoneOvenAlarmChime", array2.Length, 1, 44100, false);
			obj.SetData(array2, 0);
			return obj;
		}
	}
	internal sealed class ConfigurationManagerAttributes
	{
		public int? Order;

		public bool? HideDefaultButton;

		public Action<ConfigEntryBase> CustomDrawer;
	}
	[HarmonyPatch(typeof(CookingStation), "SetSlotVisual")]
	internal static class CookingStationPatch
	{
		private const int StatusNotDone = 0;

		private const int StatusDone = 1;

		private const int Unknown = -1;

		private static readonly ConditionalWeakTable<CookingStation, int[]> LastStatus = new ConditionalWeakTable<CookingStation, int[]>();

		private static void Postfix(CookingStation __instance, int i, string item, object[] __args)
		{
			int num = Convert.ToInt32(__args[3]);
			int[] value = LastStatus.GetValue(__instance, CreateSlots);
			if (i >= 0 && i < value.Length)
			{
				int num2 = value[i];
				value[i] = num;
				if (num2 == 0 && num == 1 && !string.IsNullOrEmpty(item))
				{
					Plugin.Alarm?.OnFoodDone(__instance, item);
				}
			}
		}

		private static int[] CreateSlots(CookingStation station)
		{
			Transform[] slots = station.m_slots;
			int[] array = new int[(slots != null) ? slots.Length : 0];
			for (int i = 0; i < array.Length; i++)
			{
				array[i] = -1;
			}
			return array;
		}
	}
	internal static class FilePicker
	{
		[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
		private class OpenFileName
		{
			public int lStructSize;

			public IntPtr hwndOwner;

			public IntPtr hInstance;

			public string lpstrFilter;

			public string lpstrCustomFilter;

			public int nMaxCustFilter;

			public int nFilterIndex;

			public string lpstrFile;

			public int nMaxFile;

			public string lpstrFileTitle;

			public int nMaxFileTitle;

			public string lpstrInitialDir;

			public string lpstrTitle;

			public int Flags;

			public short nFileOffset;

			public short nFileExtension;

			public string lpstrDefExt;

			public IntPtr lCustData;

			public IntPtr lpfnHook;

			public string lpTemplateName;

			public IntPtr pvReserved;

			public int dwReserved;

			public int FlagsEx;
		}

		private const int OFN_NOCHANGEDIR = 8;

		private const int OFN_PATHMUSTEXIST = 2048;

		private const int OFN_FILEMUSTEXIST = 4096;

		private const int OFN_EXPLORER = 524288;

		private static volatile bool _isOpen;

		private static volatile string _result;

		public static bool IsSupported
		{
			get
			{
				//IL_0000: Unknown result type (might be due to invalid IL or missing references)
				//IL_0006: Invalid comparison between Unknown and I4
				//IL_0008: Unknown result type (might be due to invalid IL or missing references)
				//IL_000e: Invalid comparison between Unknown and I4
				if ((int)Application.platform != 2)
				{
					return (int)Application.platform == 7;
				}
				return true;
			}
		}

		public static bool IsOpen => _isOpen;

		public static void Open()
		{
			if (!_isOpen && IsSupported)
			{
				_isOpen = true;
				IntPtr owner = GetActiveWindow();
				Thread thread = new Thread((ThreadStart)delegate
				{
					Run(owner);
				});
				thread.IsBackground = true;
				thread.Name = "StoneOvenAlarm file picker";
				thread.SetApartmentState(ApartmentState.STA);
				thread.Start();
			}
		}

		public static bool TryTakeResult(out string path)
		{
			path = _result;
			if (path == null)
			{
				return false;
			}
			_result = null;
			return true;
		}

		private static void Run(IntPtr owner)
		{
			try
			{
				OpenFileName openFileName = new OpenFileName
				{
					hwndOwner = owner,
					lpstrFilter = "Audio files (*.wav, *.ogg, *.mp3)\0*.wav;*.ogg;*.mp3\0All files (*.*)\0*.*\0\0",
					lpstrFile = new string('\0', 1024),
					lpstrTitle = "Stone Oven Alarm - choose a sound",
					Flags = 530440
				};
				openFileName.nMaxFile = openFileName.lpstrFile.Length;
				openFileName.lStructSize = Marshal.SizeOf(openFileName);
				if (GetOpenFileNameW(openFileName))
				{
					string text = openFileName.lpstrFile?.TrimEnd('\0');
					if (!string.IsNullOrEmpty(text))
					{
						_result = text;
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not open the file dialog: " + ex.Message));
			}
			finally
			{
				_isOpen = false;
			}
		}

		[DllImport("user32.dll")]
		private static extern IntPtr GetActiveWindow();

		[DllImport("comdlg32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
		private static extern bool GetOpenFileNameW([In][Out] OpenFileName ofn);
	}
	[BepInPlugin("batera.StoneOvenAlarm", "StoneOvenAlarm", "1.0.1")]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "batera.StoneOvenAlarm";

		public const string PluginName = "StoneOvenAlarm";

		public const string PluginVersion = "1.0.1";

		internal static Plugin Instance;

		internal static ManualLogSource Log;

		internal static AlarmPlayer Alarm;

		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<bool> StoneOvenOnly;

		internal static ConfigEntry<float> MaxDistance;

		internal static ConfigEntry<SoundPreset> SoundPresetSetting;

		internal static ConfigEntry<string> SoundFile;

		internal static ConfigEntry<int> RepeatCount;

		internal static ConfigEntry<float> RepeatInterval;

		internal static ConfigEntry<float> Volume;

		internal static ConfigEntry<bool> ShowMessage;

		internal static ConfigEntry<string> MessageText;

		private Harmony _harmony;

		private void Awake()
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Expected O, but got Unknown
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			BindConfig();
			GameObject val = new GameObject("StoneOvenAlarm");
			Object.DontDestroyOnLoad((Object)val);
			Alarm = val.AddComponent<AlarmPlayer>();
			_harmony = new Harmony("batera.StoneOvenAlarm");
			_harmony.PatchAll(typeof(Plugin).Assembly);
			((BaseUnityPlugin)this).Logger.LogInfo((object)"StoneOvenAlarm 1.0.1 loaded");
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		private void BindConfig()
		{
			//IL_01a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ae: Expected O, but got Unknown
			//IL_0215: Unknown result type (might be due to invalid IL or missing references)
			//IL_021f: Expected O, but got Unknown
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - General", "Enabled", true, Desc("Enable or disable the alarm.", 3));
			StoneOvenOnly = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - General", "StoneOvenOnly", true, Desc("If true, only the Stone Oven triggers the alarm. If false, cooking stations (spits, iron cooking station) also do.", 2));
			MaxDistance = ((BaseUnityPlugin)this).Config.Bind<float>("1 - General", "MaxDistance", 60f, Desc("Only play the alarm if you are within this distance (meters) of the station.", 1, (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 200f)));
			Volume = ((BaseUnityPlugin)this).Config.Bind<float>("2 - Sound", "Volume", 0.6f, Desc("Alarm volume.", 6, (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f)));
			RepeatCount = ((BaseUnityPlugin)this).Config.Bind<int>("2 - Sound", "RepeatCount", 1, Desc("How many times the sound plays.", 5, (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 20)));
			RepeatInterval = ((BaseUnityPlugin)this).Config.Bind<float>("2 - Sound", "RepeatInterval", 0.4f, Desc("Pause in seconds between each repetition.", 4, (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f)));
			SoundPresetSetting = ((BaseUnityPlugin)this).Config.Bind<SoundPreset>("2 - Sound", "SoundPreset", SoundPreset.MagicBells, Desc("Alarm sound. Chime is built into the mod; MagicBells, Forge, CraftDone, OvenDone and Hugin are game sounds; Custom uses SoundFile.", 7));
			SoundFile = ((BaseUnityPlugin)this).Config.Bind<string>("2 - Sound", "SoundFile", "", new ConfigDescription("Custom sound file (.wav, .ogg or .mp3), used when SoundPreset = Custom. Click Browse to pick a file, or Reset to clear it. In the .cfg you can also type an absolute path or a file name inside the BepInEx/config folder.", (AcceptableValueBase)null, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 3,
					CustomDrawer = DrawSoundFile
				}
			}));
			((BaseUnityPlugin)this).Config.Bind<string>("2 - Sound", "TestAlarm", "", new ConfigDescription("Plays the alarm with the current settings.", (AcceptableValueBase)null, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 2,
					HideDefaultButton = true,
					CustomDrawer = DrawTestButton
				}
			}));
			ShowMessage = ((BaseUnityPlugin)this).Config.Bind<bool>("3 - Message", "ShowMessage", true, Desc("Also show a message in the center of the screen.", 2));
			MessageText = ((BaseUnityPlugin)this).Config.Bind<string>("3 - Message", "MessageText", "Comida pronta!", Desc("Message shown when the food is done. {item} = food name, {station} = station name.", 1));
		}

		private static ConfigDescription Desc(string text, int order, AcceptableValueBase range = null)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Expected O, but got Unknown
			return new ConfigDescription(text, range, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = order
				}
			});
		}

		private static void DrawSoundFile(ConfigEntryBase entry)
		{
			string value = SoundFile.Value;
			GUILayout.Label(string.IsNullOrEmpty(value) ? "(none)" : Path.GetFileName(value), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) });
			if (FilePicker.IsSupported)
			{
				bool enabled = GUI.enabled;
				GUI.enabled = enabled && !FilePicker.IsOpen;
				if (GUILayout.Button(FilePicker.IsOpen ? "Choosing..." : "Browse...", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(90f) }))
				{
					FilePicker.Open();
				}
				GUI.enabled = enabled;
			}
		}

		private static void DrawTestButton(ConfigEntryBase entry)
		{
			if (GUILayout.Button("Test alarm", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }))
			{
				Alarm?.Play();
			}
		}
	}
	public enum SoundPreset
	{
		Chime,
		MagicBells,
		Forge,
		CraftDone,
		OvenDone,
		Hugin,
		Custom
	}
}