Decompiled source of Alarm Clock v0.2.1

BepInEx/plugins/AlarmClock/AlarmClock.dll

Decompiled 38 minutes ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Runtime.Versioning;
using System.Text;
using System.Xml;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyFileVersion("0.2.1.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.1", FrameworkDisplayName = ".NET Framework 4.7.1")]
[assembly: AssemblyVersion("0.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 AlarmClock
{
	internal sealed class AlarmRinger : MonoBehaviour
	{
		private const float StrikeInterval = 0.075f;

		private const float BurstLength = 1.4f;

		private const float BurstPause = 0.8f;

		private const int Voices = 4;

		private static AudioClip bell;

		private static bool bellMissing;

		private const float HighPassPerPitch = 360f;

		private readonly AudioSource[] voices = (AudioSource[])(object)new AudioSource[4];

		private GameObject bellObject;

		private AudioHighPassFilter filter;

		private int nextVoice;

		private bool secondHead;

		private float ringStarted;

		private float nextStrike;

		public bool IsRinging { get; private set; }

		public void StartRinging()
		{
			EnsureVoices();
			IsRinging = true;
			secondHead = false;
			ringStarted = Time.unscaledTime;
			nextStrike = ringStarted;
		}

		public void StopRinging()
		{
			IsRinging = false;
			AudioSource[] array = voices;
			foreach (AudioSource val in array)
			{
				if ((Object)(object)val != (Object)null)
				{
					val.Stop();
				}
			}
		}

		public bool PlayerInRange()
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			Camera main = Camera.main;
			if ((Object)(object)main != (Object)null)
			{
				Vector3 val = ((Component)main).transform.position - ((Component)this).transform.position;
				return ((Vector3)(ref val)).sqrMagnitude <= Plugin.RingRange * Plugin.RingRange;
			}
			return false;
		}

		private void Update()
		{
			if (!IsRinging)
			{
				return;
			}
			float unscaledTime = Time.unscaledTime;
			if (unscaledTime - ringStarted >= Plugin.RingDuration)
			{
				IsRinging = false;
				return;
			}
			if (GameState.sleeping && GameState.eyesFullyClosed && !GameState.recovering && !GameState.sleepingInTavern && PlayerInRange() && (Object)(object)Sleep.instance != (Object)null)
			{
				Sleep.instance.WakeUp();
			}
			if (!(unscaledTime < nextStrike))
			{
				float num = (unscaledTime - ringStarted) % 2.2f;
				if (num >= 1.4f)
				{
					nextStrike = unscaledTime + (2.2f - num);
					secondHead = false;
				}
				else
				{
					nextStrike = unscaledTime + 0.075f * Random.Range(0.94f, 1.06f);
					Strike();
				}
			}
		}

		private void Strike()
		{
			AudioClip val = Bell();
			if (!((Object)(object)val == (Object)null))
			{
				AudioSource obj = voices[nextVoice];
				nextVoice = (nextVoice + 1) % 4;
				obj.maxDistance = Plugin.RingRange;
				obj.volume = Plugin.Volume * (secondHead ? 0.75f : 1f) * Random.Range(0.9f, 1f);
				float bellPitch = Plugin.BellPitch;
				filter.cutoffFrequency = 360f * bellPitch;
				obj.pitch = bellPitch * (secondHead ? 1.015f : 1f) * Random.Range(0.99f, 1.01f);
				obj.clip = val;
				obj.Stop();
				obj.Play();
				secondHead = !secondHead;
			}
		}

		private void EnsureVoices()
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Expected O, but got Unknown
			if ((Object)(object)bellObject == (Object)null)
			{
				bellObject = new GameObject("Alarm bell");
				bellObject.transform.SetParent(((Component)this).transform, false);
			}
			for (int i = 0; i < 4; i++)
			{
				if (!((Object)(object)voices[i] != (Object)null))
				{
					AudioSource val = bellObject.AddComponent<AudioSource>();
					val.playOnAwake = false;
					val.spatialBlend = 1f;
					val.rolloffMode = (AudioRolloffMode)1;
					val.minDistance = 1f;
					val.maxDistance = Plugin.RingRange;
					val.dopplerLevel = 0f;
					voices[i] = val;
				}
			}
			if ((Object)(object)filter == (Object)null)
			{
				filter = bellObject.AddComponent<AudioHighPassFilter>();
			}
		}

		private static AudioClip Bell()
		{
			if ((Object)(object)bell == (Object)null && !bellMissing)
			{
				bell = Resources.Load<AudioClip>("Juicebox/Sounds/ding");
				if ((Object)(object)bell == (Object)null)
				{
					bellMissing = true;
					Plugin.Log.LogWarning((object)"The native bell sound Juicebox/Sounds/ding was not found. Alarms will ring silently.");
				}
			}
			return bell;
		}

		private void OnDisable()
		{
			StopRinging();
		}
	}
	internal sealed class AlarmScheduler : MonoBehaviour
	{
		public static bool TryNow(out double now)
		{
			now = 0.0;
			if (!GameState.playing || GameState.currentlyLoading || (Object)(object)Sun.sun == (Object)null)
			{
				return false;
			}
			now = AlarmTime.Absolute(GameState.day, Sun.sun.globalTime);
			return true;
		}

		private void Update()
		{
			if (!TryNow(out var now))
			{
				return;
			}
			foreach (AlarmRecord record in Plugin.Store.Records)
			{
				if (!record.Enabled)
				{
					continue;
				}
				double newNextDue;
				AlarmOutcome alarmOutcome = AlarmTime.Evaluate(record.Minute, record.NextDue, now, out newNextDue);
				if (alarmOutcome != AlarmOutcome.None)
				{
					record.NextDue = newNextDue;
					string arg = AlarmTime.Format(record.Minute);
					ChronometerAlarm alarm;
					if (alarmOutcome == AlarmOutcome.Skip)
					{
						Plugin.Log.LogInfo((object)$"Alarm {arg} on chronometer {record.InstanceId} was missed and skipped.");
					}
					else if (GameState.sleepingInTavern)
					{
						Plugin.Log.LogInfo((object)$"Alarm {arg} on chronometer {record.InstanceId} stayed silent during inn rest.");
					}
					else if (ChronometerAlarm.TryGetLive(record.InstanceId, out alarm) && alarm.Ringer.PlayerInRange())
					{
						alarm.Ringer.StartRinging();
						Plugin.Log.LogInfo((object)$"Alarm {arg} on chronometer {record.InstanceId} is ringing.");
					}
					else
					{
						Plugin.Log.LogDebug((object)$"Alarm {arg} on chronometer {record.InstanceId} was out of range and stayed silent.");
					}
				}
			}
		}
	}
	[DataContract]
	internal sealed class AlarmRecord
	{
		public const int DefaultMinute = 360;

		[DataMember]
		public int InstanceId;

		[DataMember]
		public int PrefabIndex;

		[DataMember]
		public int Minute = 360;

		[DataMember]
		public bool Enabled;

		[DataMember]
		public double NextDue;
	}
	[DataContract]
	internal sealed class AlarmSaveDocument
	{
		[DataMember(IsRequired = true)]
		public int Schema = 1;

		[DataMember(IsRequired = true)]
		public List<AlarmRecord> Alarms = new List<AlarmRecord>();
	}
	internal sealed class AlarmStore
	{
		public const string Key = "com.skeptic043.sailwind.alarmclock";

		private readonly Dictionary<int, AlarmRecord> records = new Dictionary<int, AlarmRecord>();

		public IEnumerable<AlarmRecord> Records => records.Values;

		public int Count => records.Count;

		public bool Writable { get; private set; } = true;

		public string Problem { get; private set; } = "";

		public void Reset()
		{
			records.Clear();
			Writable = true;
			Problem = "";
		}

		public bool Load(string json)
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			Reset();
			if (string.IsNullOrEmpty(json))
			{
				return true;
			}
			try
			{
				AlarmSaveDocument alarmSaveDocument;
				using (MemoryStream memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(json)))
				{
					alarmSaveDocument = (AlarmSaveDocument)((XmlObjectSerializer)new DataContractJsonSerializer(typeof(AlarmSaveDocument))).ReadObject((Stream)memoryStream);
				}
				if (alarmSaveDocument == null || alarmSaveDocument.Schema != 1 || alarmSaveDocument.Alarms == null)
				{
					throw new SerializationException("unsupported alarm save schema");
				}
				foreach (AlarmRecord alarm in alarmSaveDocument.Alarms)
				{
					if (alarm == null || alarm.InstanceId <= 0 || alarm.Minute < 0 || alarm.Minute >= 1440 || double.IsNaN(alarm.NextDue) || double.IsInfinity(alarm.NextDue))
					{
						throw new SerializationException("invalid alarm record");
					}
					if (records.ContainsKey(alarm.InstanceId))
					{
						throw new SerializationException("duplicate alarm identity");
					}
					records.Add(alarm.InstanceId, alarm);
				}
				return true;
			}
			catch (Exception ex) when (ex is SerializationException || ex is ArgumentException || ex is XmlException || ex is FormatException || ex is InvalidCastException)
			{
				records.Clear();
				Writable = false;
				Problem = ex.Message;
				return false;
			}
		}

		public string Serialize()
		{
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			AlarmSaveDocument alarmSaveDocument = new AlarmSaveDocument();
			alarmSaveDocument.Alarms.AddRange(records.Values);
			alarmSaveDocument.Alarms.Sort((AlarmRecord a, AlarmRecord b) => a.InstanceId.CompareTo(b.InstanceId));
			using MemoryStream memoryStream = new MemoryStream();
			((XmlObjectSerializer)new DataContractJsonSerializer(typeof(AlarmSaveDocument))).WriteObject((Stream)memoryStream, (object)alarmSaveDocument);
			return Encoding.UTF8.GetString(memoryStream.ToArray());
		}

		public bool TryGet(int instanceId, int prefabIndex, out AlarmRecord record)
		{
			if (records.TryGetValue(instanceId, out record) && record.PrefabIndex == prefabIndex)
			{
				return true;
			}
			record = null;
			return false;
		}

		public AlarmRecord GetOrCreate(int instanceId, int prefabIndex)
		{
			if (TryGet(instanceId, prefabIndex, out var record))
			{
				return record;
			}
			record = new AlarmRecord
			{
				InstanceId = instanceId,
				PrefabIndex = prefabIndex
			};
			records[instanceId] = record;
			return record;
		}
	}
	internal enum AlarmOutcome
	{
		None,
		Fire,
		Skip
	}
	internal static class AlarmTime
	{
		public const int MinutesPerDay = 1440;

		public const double GraceHours = 1.0;

		public const int StepMinutes = 15;

		public static double Absolute(int day, float globalTime)
		{
			return (double)day * 24.0 + (double)globalTime;
		}

		public static double NextOccurrenceAfter(int minuteOfDay, double now)
		{
			double num = (double)Normalize(minuteOfDay) / 60.0;
			double num2;
			for (num2 = Math.Floor(now / 24.0) * 24.0 + num; num2 <= now; num2 += 24.0)
			{
			}
			return num2;
		}

		public static AlarmOutcome Evaluate(int minuteOfDay, double nextDue, double now, out double newNextDue)
		{
			newNextDue = nextDue;
			if (now < nextDue)
			{
				return AlarmOutcome.None;
			}
			newNextDue = NextOccurrenceAfter(minuteOfDay, now);
			if (!(now - nextDue > 1.0))
			{
				return AlarmOutcome.Fire;
			}
			return AlarmOutcome.Skip;
		}

		public static int Step(int minuteOfDay, int steps)
		{
			int num = Normalize(minuteOfDay);
			int num2 = num / 15 * 15;
			if (steps < 0 && num2 != num)
			{
				return Normalize(num2 + (steps + 1) * 15);
			}
			return Normalize(num2 + steps * 15);
		}

		public static int WheelSteps(ref float accumulated, float wheel)
		{
			accumulated += wheel * 10f;
			if (Math.Abs(accumulated) < 0.5f)
			{
				return 0;
			}
			int num = (int)Math.Round(accumulated);
			accumulated -= num;
			return num;
		}

		public static string Format(int minuteOfDay)
		{
			int num = Normalize(minuteOfDay);
			return (num / 60).ToString("00") + ":" + (num % 60).ToString("00");
		}

		public static int Normalize(int minuteOfDay)
		{
			return (minuteOfDay % 1440 + 1440) % 1440;
		}
	}
	internal sealed class ChronometerAlarm : MonoBehaviour
	{
		private static readonly Dictionary<int, ChronometerAlarm> live = new Dictionary<int, ChronometerAlarm>();

		private static int editFrame = -10;

		private static AudioClip click;

		private ShipItem item;

		private SaveablePrefab saveable;

		private AlarmRinger ringer;

		private int registeredId;

		private bool wroteText;

		private float wheelAccumulated;

		public static bool EditingNow => Time.frameCount - editFrame <= 1;

		public AlarmRinger Ringer => ringer;

		private int InstanceId
		{
			get
			{
				if (!((Object)(object)saveable != (Object)null))
				{
					return 0;
				}
				return saveable.instanceId;
			}
		}

		private bool Owned
		{
			get
			{
				if ((Object)(object)item != (Object)null && item.sold)
				{
					return InstanceId > 0;
				}
				return false;
			}
		}

		private bool Targeted
		{
			get
			{
				if (!((Object)(object)((PickupableItem)item).held != (Object)null))
				{
					if (((GoPointerButton)item).IsLookedAt())
					{
						return !AnyHeld();
					}
					return false;
				}
				return true;
			}
		}

		public static bool TryGetLive(int instanceId, out ChronometerAlarm alarm)
		{
			return live.TryGetValue(instanceId, out alarm);
		}

		public static void StopAll()
		{
			foreach (ChronometerAlarm value in live.Values)
			{
				value.ringer.StopRinging();
			}
		}

		private void Awake()
		{
			item = ((Component)this).GetComponent<ShipItem>();
			saveable = ((Component)this).GetComponent<SaveablePrefab>();
			ringer = ((Component)this).gameObject.AddComponent<AlarmRinger>();
		}

		private void Update()
		{
			int instanceId = InstanceId;
			if (instanceId != registeredId)
			{
				Unregister();
				if (instanceId > 0)
				{
					live[instanceId] = this;
					registeredId = instanceId;
				}
			}
			if (Owned)
			{
				if (IsEditTarget())
				{
					editFrame = Time.frameCount;
					HandleScroll();
				}
				else
				{
					wheelAccumulated = 0f;
				}
				if ((Object)(object)((PickupableItem)item).held != (Object)null)
				{
					ApplyLookText();
				}
			}
		}

		private void OnDestroy()
		{
			Unregister();
		}

		private void Unregister()
		{
			if (registeredId > 0 && live.TryGetValue(registeredId, out var value) && (Object)(object)value == (Object)(object)this)
			{
				live.Remove(registeredId);
			}
			registeredId = 0;
		}

		private static bool AnyHeld()
		{
			foreach (ChronometerAlarm value in live.Values)
			{
				if ((Object)(object)((PickupableItem)value.item).held != (Object)null)
				{
					return true;
				}
			}
			return false;
		}

		public bool IsEditTarget()
		{
			if (Owned && Targeted)
			{
				return Plugin.ModifierHeld();
			}
			return false;
		}

		private AlarmRecord Record(bool create)
		{
			if (create)
			{
				return Plugin.Store.GetOrCreate(InstanceId, saveable.prefabIndex);
			}
			if (!Plugin.Store.TryGet(InstanceId, saveable.prefabIndex, out var record))
			{
				return null;
			}
			return record;
		}

		private void HandleScroll()
		{
			float axis = Input.GetAxis("Mouse ScrollWheel");
			if (axis != 0f && AlarmScheduler.TryNow(out var now))
			{
				int num = AlarmTime.WheelSteps(ref wheelAccumulated, axis);
				if (num != 0)
				{
					AlarmRecord alarmRecord = Record(create: true);
					alarmRecord.Minute = AlarmTime.Step(alarmRecord.Minute, num);
					alarmRecord.Enabled = true;
					alarmRecord.NextDue = AlarmTime.NextOccurrenceAfter(alarmRecord.Minute, now);
					PlayClick();
				}
			}
		}

		public bool OnAltActivate()
		{
			if (ringer.IsRinging)
			{
				ringer.StopRinging();
				return true;
			}
			if (!Owned || !Plugin.ModifierHeld() || !AlarmScheduler.TryNow(out var now))
			{
				return false;
			}
			AlarmRecord alarmRecord = Record(create: true);
			alarmRecord.Enabled = !alarmRecord.Enabled;
			if (alarmRecord.Enabled)
			{
				alarmRecord.NextDue = AlarmTime.NextOccurrenceAfter(alarmRecord.Minute, now);
			}
			PlayClick();
			return true;
		}

		public string Tooltip()
		{
			if (!Plugin.ControlsTooltip || !IsEditTarget())
			{
				return null;
			}
			string text = Plugin.ModifierName();
			return "hold " + text + " and scroll to set the alarm, " + text + " and R to switch it on or off";
		}

		public void ApplyLookText()
		{
			if (Owned)
			{
				AlarmRecord alarmRecord = Record(create: false);
				bool flag = alarmRecord?.Enabled ?? false;
				bool flag2 = Targeted && Plugin.ModifierHeld();
				if (ringer.IsRinging)
				{
					((GoPointerButton)item).lookText = "ringing";
				}
				else if (flag || flag2)
				{
					int minuteOfDay = alarmRecord?.Minute ?? 360;
					((GoPointerButton)item).lookText = (flag ? ("alarm " + AlarmTime.Format(minuteOfDay)) : "alarm off");
				}
				else if (wroteText)
				{
					((GoPointerButton)item).lookText = "";
				}
				wroteText = ringer.IsRinging || flag || flag2;
			}
		}

		private void PlayClick()
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)click == (Object)null)
			{
				click = Resources.Load<AudioClip>("Juicebox/Sounds/wood click");
			}
			if ((Object)(object)click != (Object)null)
			{
				AudioSource.PlayClipAtPoint(click, ((Component)this).transform.position, Plugin.Volume * 0.5f);
			}
		}
	}
	[BepInPlugin("com.skeptic043.sailwind.alarmclock", "Alarm Clock", "0.2.1")]
	public sealed class Plugin : BaseUnityPlugin
	{
		[HarmonyPatch(typeof(ShipItem), "OnLoad")]
		private static class AttachPatch
		{
			private static void Postfix(ShipItem __instance)
			{
				Guard("attach", delegate
				{
					if (__instance is ShipItemClock && (Object)(object)((Component)__instance).GetComponent<ChronometerAlarm>() == (Object)null)
					{
						((Component)__instance).gameObject.AddComponent<ChronometerAlarm>();
					}
				});
			}
		}

		[HarmonyPatch(typeof(ShipItemClock), "OnAltActivate", new Type[] { })]
		private static class AltActivatePatch
		{
			private static bool Prefix(ShipItemClock __instance)
			{
				bool consumed = false;
				Guard("alt action", delegate
				{
					ChronometerAlarm component = ((Component)__instance).GetComponent<ChronometerAlarm>();
					consumed = (Object)(object)component != (Object)null && component.OnAltActivate();
				});
				return !consumed;
			}
		}

		private static class PointerAccess
		{
			public static readonly FieldRef<GoPointer, RaycastHit> Hit = AccessTools.FieldRefAccess<GoPointer, RaycastHit>("hit");

			public static readonly FieldRef<GoPointer, GoPointerButton> PointedAt = AccessTools.FieldRefAccess<GoPointer, GoPointerButton>("pointedAtButton");

			public static readonly FieldRef<GoPointer, float> LookDistance = AccessTools.FieldRefAccess<GoPointer, float>("currentLookDistance");
		}

		[HarmonyPatch(typeof(GoPointer), "DoRaycast")]
		private static class NailedLookPatch
		{
			private static void Postfix(GoPointer __instance)
			{
				Guard("nailed look", delegate
				{
					//IL_0006: Unknown result type (might be due to invalid IL or missing references)
					//IL_000c: Invalid comparison between Unknown and I4
					//IL_006f: Unknown result type (might be due to invalid IL or missing references)
					//IL_0074: Unknown result type (might be due to invalid IL or missing references)
					if (((int)__instance.type != 2 || !GameState.inCursorMenu) && !((Object)(object)PointerAccess.PointedAt.Invoke(__instance) != (Object)null) && !((Object)(object)__instance.GetHeldItem() != (Object)null) && !GameState.sleeping && !((Object)(object)GameState.inBed != (Object)null) && !BoatCamera.on)
					{
						RaycastHit val = PointerAccess.Hit.Invoke(__instance);
						Collider val2 = ((RaycastHit)(ref val)).collider;
						if (!((Object)(object)val2 == (Object)null))
						{
							if (((Component)val2).CompareTag("ItemSubcollider"))
							{
								val2 = ((Component)((Component)val2).transform.parent).GetComponent<Collider>();
							}
							ShipItemClock val3 = (((Object)(object)val2 != (Object)null) ? ((Component)val2).GetComponent<ShipItemClock>() : null);
							if (!((Object)(object)val3 == (Object)null) && ((ShipItem)val3).nailed && !((GoPointerButton)val3).unclickable)
							{
								PointerAccess.PointedAt.Invoke(__instance) = (GoPointerButton)(object)val3;
								((GoPointerButton)val3).Look(__instance);
								PointerAccess.LookDistance.Invoke(__instance) = ((RaycastHit)(ref val)).distance;
							}
						}
					}
				});
			}
		}

		[HarmonyPatch(typeof(GameInput), "GetScrollAxis")]
		private static class ScrollPatch
		{
			private static void Postfix(ref float __result)
			{
				if (ChronometerAlarm.EditingNow)
				{
					__result = 0f;
				}
			}
		}

		[HarmonyPatch(typeof(ShipItem), "UpdateLookText")]
		private static class LookTextPatch
		{
			private static void Postfix(ShipItem __instance)
			{
				if (__instance is ShipItemClock)
				{
					Guard("look text", delegate
					{
						((Component)__instance).GetComponent<ChronometerAlarm>()?.ApplyLookText();
					});
				}
			}
		}

		private static class LookUiAccess
		{
			public static readonly FieldRef<LookUI, TextMesh> ControlsText = AccessTools.FieldRefAccess<LookUI, TextMesh>("controlsText");

			public static readonly FieldRef<LookUI, TextMesh> TooltipText = AccessTools.FieldRefAccess<LookUI, TextMesh>("hintText");

			public static readonly FieldRef<LookUI, GoPointer> Pointer = AccessTools.FieldRefAccess<LookUI, GoPointer>("pointer");

			public static readonly Action<LookUI> ShowRicon = AccessTools.MethodDelegate<Action<LookUI>>(AccessTools.Method(typeof(LookUI), "ShowRicon", (Type[])null, (Type[])null), (object)null, true);

			public static readonly Action<LookUI> HideIcons = AccessTools.MethodDelegate<Action<LookUI>>(AccessTools.Method(typeof(LookUI), "HideIcons", (Type[])null, (Type[])null), (object)null, true);
		}

		[HarmonyPatch(typeof(LookUI), "ShowLookText")]
		private static class LookPromptPatch
		{
			private static void Postfix(LookUI __instance, GoPointerButton button)
			{
				Guard("look prompt", delegate
				{
					GoPointerButton obj = button;
					ShipItemClock val = (ShipItemClock)(object)((obj is ShipItemClock) ? obj : null);
					if (val != null)
					{
						string text = ((Component)val).GetComponent<ChronometerAlarm>()?.Tooltip();
						if (text != null)
						{
							LookUiAccess.TooltipText.Invoke(__instance).text = text;
						}
						if (Settings.controlsTextEnabled)
						{
							TextMesh val2 = LookUiAccess.ControlsText.Invoke(__instance);
							GoPointer val3 = LookUiAccess.Pointer.Invoke(__instance);
							bool flag = (Object)(object)val3 != (Object)null && (Object)(object)val3.GetHeldItem() == (Object)null;
							if (Ringing((Component)(object)button) && flag)
							{
								string text2 = (((ShipItem)val).nailed ? "" : val2.text.Split(new char[1] { '\n' })[0]);
								LookUiAccess.ShowRicon(__instance);
								val2.text = text2 + "\nstop alarm";
							}
							else if ((((ShipItem)val).nailed && flag) || val2.text.EndsWith("\nstop alarm"))
							{
								LookUiAccess.HideIcons(__instance);
								val2.text = "";
							}
						}
					}
				});
			}
		}

		[HarmonyPatch(typeof(LookUI), "ShowHoldText")]
		private static class HoldPromptPatch
		{
			private static void Postfix(LookUI __instance, PickupableItem item)
			{
				Guard("hold prompt", delegate
				{
					if (Settings.controlsTextEnabled && Ringing((Component)(object)item))
					{
						LookUiAccess.ShowRicon(__instance);
						LookUiAccess.ControlsText.Invoke(__instance).text = "stop alarm";
					}
				});
			}
		}

		[HarmonyPatch(typeof(SaveLoadManager), "SaveModData")]
		private static class SavePatch
		{
			private static void Postfix()
			{
				Guard("save", delegate
				{
					if (Store.Writable)
					{
						if (Store.Count > 0)
						{
							GameState.modData["com.skeptic043.sailwind.alarmclock"] = Store.Serialize();
						}
						else
						{
							GameState.modData.Remove("com.skeptic043.sailwind.alarmclock");
						}
					}
				});
			}
		}

		[HarmonyPatch(typeof(SaveLoadManager), "LoadGame")]
		private static class LoadGamePatch
		{
			private static void Prefix()
			{
				Guard("load reset", delegate
				{
					ChronometerAlarm.StopAll();
					Store.Reset();
					GameState.modData.Remove("com.skeptic043.sailwind.alarmclock");
				});
			}
		}

		[HarmonyPatch(typeof(SaveLoadManager), "LoadModData")]
		private static class LoadModDataPatch
		{
			private static void Postfix()
			{
				Guard("load", delegate
				{
					GameState.modData.TryGetValue("com.skeptic043.sailwind.alarmclock", out var value);
					if (!Store.Load(value))
					{
						Log.LogWarning((object)("Saved alarms could not be read and were left unchanged in the save: " + Store.Problem));
					}
				});
			}
		}

		[HarmonyPatch(typeof(StartMenu), "StartNewGame")]
		private static class NewGamePatch
		{
			private static void Prefix()
			{
				Guard("new game reset", delegate
				{
					ChronometerAlarm.StopAll();
					Store.Reset();
					GameState.modData.Remove("com.skeptic043.sailwind.alarmclock");
				});
			}
		}

		public const string Id = "com.skeptic043.sailwind.alarmclock";

		private static ConfigEntry<KeyboardShortcut> modifier;

		private static ConfigEntry<float> ringRange;

		private static ConfigEntry<float> ringDuration;

		private static ConfigEntry<float> volume;

		private static ConfigEntry<float> bellPitch;

		private static ConfigEntry<bool> controlsTooltip;

		private Harmony harmony;

		private const string StopPrompt = "stop alarm";

		internal static ManualLogSource Log { get; private set; }

		internal static AlarmStore Store { get; } = new AlarmStore();

		internal static float RingRange => ringRange.Value;

		internal static float RingDuration => ringDuration.Value;

		internal static float Volume => volume.Value;

		internal static float BellPitch => bellPitch.Value;

		internal static bool ControlsTooltip => controlsTooltip.Value;

		internal static bool ModifierHeld()
		{
			//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)
			//IL_000d: 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)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			KeyboardShortcut value = modifier.Value;
			if ((int)((KeyboardShortcut)(ref value)).MainKey == 0 || !Input.GetKey(((KeyboardShortcut)(ref value)).MainKey))
			{
				return false;
			}
			foreach (KeyCode modifier in ((KeyboardShortcut)(ref value)).Modifiers)
			{
				if (!Input.GetKey(modifier))
				{
					return false;
				}
			}
			return true;
		}

		internal static string ModifierName()
		{
			//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)
			return ((object)modifier.Value/*cast due to .constrained prefix*/).ToString();
		}

		private void Awake()
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Expected O, but got Unknown
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Expected O, but got Unknown
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: Expected O, but got Unknown
			//IL_011b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0125: Expected O, but got Unknown
			//IL_0150: Unknown result type (might be due to invalid IL or missing references)
			//IL_015a: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			modifier = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Controls", "Alarm modifier", new KeyboardShortcut((KeyCode)122, Array.Empty<KeyCode>()), "Hold this key while holding or looking at a chronometer. Scroll to set the alarm time and press R to switch the alarm on or off.");
			ringRange = ((BaseUnityPlugin)this).Config.Bind<float>("Alarm", "Ring range", 25f, new ConfigDescription("How close the chronometer must be, in meters, for its alarm to ring and wake you.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 100f), Array.Empty<object>()));
			ringDuration = ((BaseUnityPlugin)this).Config.Bind<float>("Alarm", "Ring duration", 5f, new ConfigDescription("How many real seconds an alarm rings before it stops by itself.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 60f), Array.Empty<object>()));
			volume = ((BaseUnityPlugin)this).Config.Bind<float>("Alarm", "Volume", 0.3f, new ConfigDescription("Alarm bell volume.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			bellPitch = ((BaseUnityPlugin)this).Config.Bind<float>("Alarm", "Bell pitch", 2.5f, new ConfigDescription("How small the bell sounds. Higher values sound like a smaller, brighter bell. 1 is the game's original bell.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 3f), Array.Empty<object>()));
			controlsTooltip = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "Show controls tooltip", true, "While you hold the alarm modifier and look at a chronometer, show how to set the alarm below it. The alarm time is always shown.");
			try
			{
				harmony = new Harmony("com.skeptic043.sailwind.alarmclock");
				harmony.PatchAll(typeof(Plugin).Assembly);
				((Component)this).gameObject.AddComponent<AlarmScheduler>();
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Alarm Clock loaded.");
			}
			catch (Exception ex)
			{
				((Behaviour)this).enabled = false;
				try
				{
					Harmony obj = harmony;
					if (obj != null)
					{
						obj.UnpatchSelf();
					}
				}
				catch (Exception ex2)
				{
					((BaseUnityPlugin)this).Logger.LogError((object)("Alarm Clock could not remove its partial patches: " + ex2));
				}
				((BaseUnityPlugin)this).Logger.LogError((object)("Alarm Clock disabled after initialization failed. Check game and mod compatibility: " + ex));
			}
		}

		private static void Guard(string patch, Action action)
		{
			try
			{
				action();
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"Alarm Clock {patch} failed: {arg}");
			}
		}

		private static bool Ringing(Component item)
		{
			ChronometerAlarm chronometerAlarm = (((Object)(object)item != (Object)null) ? item.GetComponent<ChronometerAlarm>() : null);
			if ((Object)(object)chronometerAlarm != (Object)null)
			{
				return chronometerAlarm.Ringer.IsRinging;
			}
			return false;
		}
	}
}