Decompiled source of Kvedja v1.0.0

plugins/Kvedja/Kvedja.dll

Decompiled 5 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Threading;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using Ezomic.Core;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")]
[assembly: AssemblyCompany("Thijssen Software")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright (c) 2026 Robbin Thijssen")]
[assembly: AssemblyDescription("A message of the day in the chat window when you log in.")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+c666481772c83c0634604856dacb8c56a0e1441f")]
[assembly: AssemblyProduct("Kvedja")]
[assembly: AssemblyTitle("Kvedja")]
[assembly: AssemblyVersion("1.0.0.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 Kvedja
{
	internal static class KvedjaConfig
	{
		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<string> Url;

		internal static ConfigEntry<string> Title;

		internal static ConfigEntry<Type> Voice;

		internal static ConfigEntry<float> Delay;

		internal static ConfigEntry<int> Timeout;

		internal static ConfigEntry<bool> Repeat;

		internal static ConfigEntry<bool> Verbose;

		internal static void Bind(ConfigFile cfg)
		{
			Enabled = cfg.Bind<bool>("Kvedja", "Enabled", true, "Off leaves the plugin loaded and says nothing. Nothing is fetched either, so this also stops the mod asking the site anything at all.");
			Url = cfg.Bind<string>("Kvedja", "Url", "https://longhouse.thijssensoftware.nl/api/motd", "Where the message is read from. Plain text, one line of chat per line. Empty turns the mod off as surely as Enabled does. Any server can host its own: the only requirement is that the answer is text.");
			Title = cfg.Bind<string>("Kvedja", "Title", "Longhouse", "The name in front of the message, where a player's name normally goes. It is drawn orange by the game, which is what makes the line read as somebody speaking rather than as an error.");
			Voice = cfg.Bind<Type>("Kvedja", "Voice", (Type)1, "How the line is styled. Shout is drawn yellow AND IN CAPITALS, whisper is faded and forced to lowercase; both are done by the game, not here. Normal is the only one that prints what was written.");
			Delay = cfg.Bind<float>("Kvedja", "Delay", 4f, "Seconds after you appear in the world before the message is said. Too early and it lands under the loading fade, which looks exactly like it never arrived.");
			Timeout = cfg.Bind<int>("Kvedja", "Timeout", 8, "Seconds to wait for the site before giving up. Giving up is silent: a message of the day that reports its own failure to a player is worse than no message, because the player can do nothing about it.");
			Repeat = cfg.Bind<bool>("Kvedja", "Repeat", false, "On says the message again every time you respawn, not only when you log in. Off is once per world session, which is what login means.");
			Verbose = cfg.Bind<bool>("Kvedja", "Verbose", false, "Write what was asked for and what came back to BepInEx/LogOutput.log. Off unless the message is not appearing; it is three or four lines per login.");
		}
	}
	internal static class KvedjaPatches
	{
		[HarmonyPostfix]
		[HarmonyPatch(typeof(Chat), "Awake")]
		private static void ChatAwake()
		{
			Motd.NewWorld();
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(Player), "OnSpawned")]
		private static void OnSpawned(Player __instance)
		{
			if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer))
			{
				Motd.Wanted();
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(Chat), "Update")]
		private static void ChatUpdate()
		{
			Motd.Tick();
		}
	}
	[BepInPlugin("ezomic.valheim.kvedja", "Kvedja", "1.0.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class KvedjaPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "ezomic.valheim.kvedja";

		public const string PluginName = "Kvedja";

		public const string PluginVersion = "1.0.0";

		public const string PluginAuthor = "Robbin Thijssen";

		private const string CoreGuid = "ezomic.valheim.core";

		internal static ManualLogSource Log;

		internal static bool CorePresent;

		private Harmony _harmony;

		private void Awake()
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			KvedjaConfig.Bind(((BaseUnityPlugin)this).Config);
			TryRegisterWithCore();
			_harmony = new Harmony("ezomic.valheim.kvedja");
			_harmony.PatchAll(typeof(KvedjaPatches));
			_harmony.PatchAll(typeof(Notices));
			Log.LogInfo((object)"Kvedja 1.0.0 by Robbin Thijssen - ready.");
		}

		private void TryRegisterWithCore()
		{
			CorePresent = Chainloader.PluginInfos.ContainsKey("ezomic.valheim.core");
			if (!CorePresent)
			{
				Log.LogInfo((object)"Core not installed - running standalone, without the version gate.");
			}
			else
			{
				RegisterWithCore();
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private void RegisterWithCore()
		{
			Suite.Register("ezomic.valheim.kvedja", "Kvedja", "1.0.0", ((BaseUnityPlugin)this).Config, (Requirement)1, (Assembly)null);
			Suite.Local((ConfigEntryBase[])(object)new ConfigEntryBase[8]
			{
				(ConfigEntryBase)KvedjaConfig.Enabled,
				(ConfigEntryBase)KvedjaConfig.Url,
				(ConfigEntryBase)KvedjaConfig.Title,
				(ConfigEntryBase)KvedjaConfig.Voice,
				(ConfigEntryBase)KvedjaConfig.Delay,
				(ConfigEntryBase)KvedjaConfig.Timeout,
				(ConfigEntryBase)KvedjaConfig.Repeat,
				(ConfigEntryBase)KvedjaConfig.Verbose
			});
		}

		private void OnDestroy()
		{
			if (_harmony != null)
			{
				_harmony.UnpatchSelf();
			}
		}
	}
	internal static class Motd
	{
		private static readonly HttpClient Client = new HttpClient();

		private static readonly object Gate = new object();

		private static readonly List<string> Pending = new List<string>();

		private static bool _asking;

		private static bool _saidThisSession;

		private static float _sayAt;

		private static FieldRef<Chat, float> _hideTimer;

		private static bool _triedHideTimer;

		internal static void NewWorld()
		{
			lock (Gate)
			{
				Pending.Clear();
			}
			_saidThisSession = false;
			_sayAt = 0f;
		}

		internal static void Wanted()
		{
			if (KvedjaConfig.Enabled.Value && (!_saidThisSession || KvedjaConfig.Repeat.Value))
			{
				string value = KvedjaConfig.Url.Value;
				if (!string.IsNullOrEmpty(value))
				{
					_sayAt = Time.realtimeSinceStartup + Mathf.Max(0f, KvedjaConfig.Delay.Value);
					Fetch(value);
				}
			}
		}

		private static void Fetch(string url)
		{
			lock (Gate)
			{
				if (_asking)
				{
					return;
				}
				_asking = true;
			}
			Thread thread = new Thread((ThreadStart)delegate
			{
				Ask(url);
			});
			thread.IsBackground = true;
			thread.Name = "Kvedja";
			thread.Start();
		}

		private static void Ask(string url)
		{
			try
			{
				try
				{
					ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
				}
				catch
				{
				}
				string result2;
				using (CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(Mathf.Clamp(KvedjaConfig.Timeout.Value, 1, 60))))
				{
					HttpResponseMessage result = Client.GetAsync(url, cancellationTokenSource.Token).GetAwaiter().GetResult();
					try
					{
						result.EnsureSuccessStatusCode();
						result2 = result.Content.ReadAsStringAsync().GetAwaiter().GetResult();
					}
					finally
					{
						((IDisposable)result)?.Dispose();
					}
				}
				List<string> list = new List<string>();
				string[] array = result2.Split(new char[1] { '\n' });
				for (int i = 0; i < array.Length; i++)
				{
					string text = array[i].Trim('\r', ' ', '\t');
					if (text.Length != 0)
					{
						list.Add(text);
					}
				}
				lock (Gate)
				{
					Pending.Clear();
					Pending.AddRange(list);
				}
				if (KvedjaConfig.Verbose.Value)
				{
					KvedjaPlugin.Log.LogInfo((object)("Read " + list.Count + " line(s) from " + url));
				}
			}
			catch (Exception ex)
			{
				if (KvedjaConfig.Verbose.Value)
				{
					KvedjaPlugin.Log.LogInfo((object)("No message of the day: " + ex.Message));
				}
			}
			finally
			{
				lock (Gate)
				{
					_asking = false;
				}
			}
		}

		internal static void Tick()
		{
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			if (_sayAt <= 0f || Time.realtimeSinceStartup < _sayAt)
			{
				return;
			}
			Chat instance = Chat.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			string[] array;
			lock (Gate)
			{
				if (Pending.Count == 0)
				{
					return;
				}
				array = Pending.ToArray();
				Pending.Clear();
			}
			_sayAt = 0f;
			_saidThisSession = true;
			string value = KvedjaConfig.Title.Value;
			Type value2 = KvedjaConfig.Voice.Value;
			string[] array2 = array;
			foreach (string text in array2)
			{
				((Terminal)instance).AddString(value, text, value2, false);
			}
			Show(instance);
			if (KvedjaConfig.Verbose.Value)
			{
				KvedjaPlugin.Log.LogInfo((object)("Said " + array.Length + " line(s) as " + value + "."));
			}
		}

		internal static void SayNow(string line)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			Chat instance = Chat.instance;
			if (!((Object)(object)instance == (Object)null))
			{
				((Terminal)instance).AddString(KvedjaConfig.Title.Value, line, KvedjaConfig.Voice.Value, false);
				Show(instance);
			}
		}

		private static void Show(Chat chat)
		{
			FieldRef<Chat, float> val = HideTimer();
			if (val == null)
			{
				return;
			}
			try
			{
				val.Invoke(chat) = 0f;
			}
			catch (Exception)
			{
			}
		}

		private static FieldRef<Chat, float> HideTimer()
		{
			if (_triedHideTimer)
			{
				return _hideTimer;
			}
			_triedHideTimer = true;
			try
			{
				_hideTimer = AccessTools.FieldRefAccess<Chat, float>("m_hideTimer");
			}
			catch (Exception ex)
			{
				KvedjaPlugin.Log.LogWarning((object)("Cannot reach the chat window's hide timer (" + ex.GetType().Name + "). The message is still added to the chat log; what is lost is the window opening by itself to show it."));
			}
			return _hideTimer;
		}
	}
	internal static class Notices
	{
		internal const string NoticeRpc = "Kvedja_Notice";

		[HarmonyPostfix]
		[HarmonyPatch(typeof(ZNet), "Awake")]
		private static void Register()
		{
			if (ZRoutedRpc.instance != null)
			{
				ZRoutedRpc.instance.Register<string>("Kvedja_Notice", (Action<long, string>)OnNotice);
			}
		}

		private static void OnNotice(long sender, string text)
		{
			try
			{
				if (KvedjaConfig.Enabled.Value && !string.IsNullOrEmpty(text) && !((Object)(object)ZNet.instance == (Object)null))
				{
					ZNetPeer serverPeer = ZNet.instance.GetServerPeer();
					if (serverPeer != null && serverPeer.m_uid == sender)
					{
						Motd.SayNow(text);
					}
				}
			}
			catch (Exception ex)
			{
				KvedjaPlugin.Log.LogWarning((object)("Showing a server notice failed: " + ex));
			}
		}
	}
}