using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("BetterChat")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Crystal")]
[assembly: AssemblyProduct("BetterChat")]
[assembly: AssemblyCopyright("Copyright © 2023 Crystal")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("a81c5907-42c8-48a8-a758-24fcacbfd3aa")]
[assembly: AssemblyFileVersion("1.4.10.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("1.4.10.0")]
namespace BetterChat;
[BepInPlugin("dev.crystal.betterchat", "Better Chat", "1.4.10.0")]
[BepInProcess("valheim.exe")]
[BepInProcess("valheim_server.exe")]
public class BetterChatPlugin : BaseUnityPlugin
{
[HarmonyPatch(typeof(Chat))]
private static class Chat_Patches
{
[HarmonyPatch("Awake")]
[HarmonyPostfix]
private static void Awake_Postfix(Chat __instance)
{
__instance.m_hideDelay = HideDelay.Value;
sChat = __instance;
Graphic[] componentsInChildren = ((Component)((Terminal)__instance).m_chatWindow).GetComponentsInChildren<Graphic>();
for (int i = 0; i < componentsInChildren.Length; i++)
{
componentsInChildren[i].raycastTarget = false;
}
}
}
[HarmonyPatch(typeof(Player))]
private static class Player_Patches
{
[HarmonyPatch("Awake")]
[HarmonyPostfix]
private static void Awake_Postfix(Player __instance)
{
Talker component = ((Component)__instance).GetComponent<Talker>();
ApplyChatDistances(component);
sTalkers.Add(component);
}
[HarmonyPatch("OnDestroy")]
[HarmonyPrefix]
private static void OnDestroy_Prefix(Player __instance)
{
sTalkers.Remove(((Component)__instance).GetComponent<Talker>());
}
}
[HarmonyPatch(typeof(Chat))]
private static class Chat_AlwaysShow_Patch
{
[HarmonyPatch("Update")]
[HarmonyPrefix]
private static bool Update_Prefix(Chat __instance)
{
sHideTimerField.SetValue(__instance, 0f);
return true;
}
}
[HarmonyPatch(typeof(Chat))]
private static class Chat_Slash_Patches
{
private enum TranspilerState
{
Searching,
Inserting,
Searching2,
Labeling,
Finishing
}
[HarmonyPatch("Update")]
[HarmonyTranspiler]
private static IEnumerable<CodeInstruction> Update_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
LocalBuilder isSlashPressed = generator.DeclareLocal(typeof(bool));
isSlashPressed.SetLocalSymInfo("isSlashPressed");
Label label = generator.DefineLabel();
yield return new CodeInstruction(OpCodes.Ldc_I4_0, (object)null);
yield return new CodeInstruction(OpCodes.Stloc, (object)isSlashPressed.LocalIndex);
TranspilerState state = TranspilerState.Searching;
foreach (CodeInstruction instruction in instructions)
{
switch (state)
{
case TranspilerState.Inserting:
if (instruction.opcode != OpCodes.Brtrue)
{
throw new InvalidOperationException(string.Format("[BetterChat] {0} encountered unexpected IL code. Unable to patch. This is most likely due to a game update changing the target code. Disable {1} in the config as a workaround until the mod can be fixed. Details: Search failed, opcode={2}", "Chat_Slash_Patches", "SlashOpensChat", instruction.opcode));
}
yield return instruction.Clone();
yield return new CodeInstruction(OpCodes.Ldc_I4, (object)47);
yield return new CodeInstruction(OpCodes.Call, (object)typeof(Input).GetMethod("GetKeyDown", new Type[1] { typeof(KeyCode) }));
yield return new CodeInstruction(OpCodes.Stloc, (object)isSlashPressed.LocalIndex);
yield return new CodeInstruction(OpCodes.Ldloc, (object)isSlashPressed.LocalIndex);
state = TranspilerState.Searching2;
break;
case TranspilerState.Labeling:
instruction.labels.Add(label);
state = TranspilerState.Finishing;
break;
}
yield return instruction;
if (state == TranspilerState.Searching && instruction.opcode == OpCodes.Call)
{
if (((MethodBase)instruction.operand).Name == "GetButtonDown")
{
state = TranspilerState.Inserting;
}
}
else if (state == TranspilerState.Searching2 && instruction.opcode == OpCodes.Callvirt && ((MethodBase)instruction.operand).Name == "ActivateInputField")
{
yield return new CodeInstruction(OpCodes.Ldloc, (object)isSlashPressed.LocalIndex);
yield return new CodeInstruction(OpCodes.Brfalse, (object)label);
yield return new CodeInstruction(OpCodes.Ldarg_0, (object)null);
yield return new CodeInstruction(OpCodes.Ldfld, (object)typeof(Terminal).GetField("m_input"));
yield return new CodeInstruction(OpCodes.Ldstr, (object)"/");
yield return new CodeInstruction(OpCodes.Call, (object)typeof(TMP_InputField).GetMethod("set_text"));
yield return new CodeInstruction(OpCodes.Ldc_I4_1, (object)null);
yield return new CodeInstruction(OpCodes.Stsfld, (object)typeof(BetterChatPlugin).GetField("sMoveCaretToEnd", BindingFlags.Static | BindingFlags.NonPublic));
state = TranspilerState.Labeling;
}
}
if (state != TranspilerState.Finishing)
{
throw new InvalidOperationException("[BetterChat] Chat_Slash_Patches encountered unexpected IL code. Unable to patch. This is most likely due to a game update changing the target code. Disable SlashOpensChat in the config as a workaround until the mod can be fixed. Details: Never reached finishing state");
}
}
[HarmonyPatch("LateUpdate")]
[HarmonyPostfix]
private static void LateUpdate_Postfix(Chat __instance)
{
if (sMoveCaretToEnd)
{
((TMP_InputField)((Terminal)__instance).m_input).MoveTextEnd(false);
sMoveCaretToEnd = false;
}
}
}
[HarmonyPatch(typeof(Chat))]
private static class Chat_Show_Patch
{
[HarmonyPatch("OnNewChatMessage")]
[HarmonyPostfix]
private static void OnNewChatMessage_Postfix(Chat __instance, GameObject go, long senderID, Vector3 pos, Type type, string user, string text)
{
sHideTimerField.SetValue(__instance, 0f);
}
}
[HarmonyPatch(typeof(Terminal))]
private static class Chat_MixedCase_Terminal_Patch
{
[HarmonyPatch("AddString", new Type[]
{
typeof(string),
typeof(string),
typeof(Type),
typeof(bool)
})]
[HarmonyTranspiler]
private static IEnumerable<CodeInstruction> AddString_Transpiler(IEnumerable<CodeInstruction> instructions)
{
return StripForcedCase(instructions);
}
}
[HarmonyPatch(typeof(Chat))]
private static class Chat_MixedCase_Chat_Patch
{
[HarmonyPatch("AddInworldText")]
[HarmonyTranspiler]
private static IEnumerable<CodeInstruction> AddInworldText_Transpiler(IEnumerable<CodeInstruction> instructions)
{
return StripForcedCase(instructions);
}
}
[HarmonyPatch(typeof(Chat))]
private static class Chat_Shout_Patch
{
[HarmonyPatch("InputText")]
[HarmonyTranspiler]
private static IEnumerable<CodeInstruction> InputText_Transpiler(IEnumerable<CodeInstruction> instructions)
{
foreach (CodeInstruction instruction in instructions)
{
if (instruction.opcode == OpCodes.Ldstr && instruction.operand.Equals("say "))
{
instruction.operand = "s ";
}
yield return instruction;
}
}
}
[HarmonyPatch(typeof(Minimap))]
private static class Minimap_Patches
{
[HarmonyPatch("UpdateDynamicPins")]
[HarmonyTranspiler]
private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
{
List<CodeInstruction> list = new List<CodeInstruction>(instructions);
for (int i = 0; i < list.Count - 1; i++)
{
if (list[i + 1].opcode == OpCodes.Call && ((MethodBase)list[i + 1].operand).Name == "UpdateShoutPins")
{
list.RemoveRange(i, 2);
break;
}
}
return list;
}
}
public const string ModId = "dev.crystal.betterchat";
public static ConfigEntry<bool> AlwaysVisible;
public static ConfigEntry<float> HideDelay;
public static ConfigEntry<bool> ForceCase;
public static ConfigEntry<bool> SlashOpensChat;
public static ConfigEntry<bool> DefaultShout;
public static ConfigEntry<bool> ShowShoutPings;
public static ConfigEntry<float> TalkDistance;
public static ConfigEntry<float> WhisperDistance;
private static Harmony sChatAwakeHarmony;
private static Harmony sPlayerHarmony;
private static Harmony sChatShowHarmony;
private static Harmony sChatAlwaysShowHarmony;
private static Harmony sChatMixedCaseHarmony;
private static Harmony sChatShoutHarmony;
private static Harmony sMinimapHarmony;
private static Harmony sChatSlashHarmony;
private static Chat sChat;
private static List<Talker> sTalkers;
private static readonly FieldInfo sHideTimerField;
private static bool sMoveCaretToEnd;
static BetterChatPlugin()
{
sTalkers = new List<Talker>();
sHideTimerField = typeof(Chat).GetField("m_hideTimer", BindingFlags.Instance | BindingFlags.NonPublic);
}
private void Awake()
{
//IL_01c6: Unknown result type (might be due to invalid IL or missing references)
//IL_01d0: Expected O, but got Unknown
//IL_01d5: Unknown result type (might be due to invalid IL or missing references)
//IL_01df: Expected O, but got Unknown
//IL_01e4: Unknown result type (might be due to invalid IL or missing references)
//IL_01ee: Expected O, but got Unknown
//IL_01f3: Unknown result type (might be due to invalid IL or missing references)
//IL_01fd: Expected O, but got Unknown
//IL_0202: Unknown result type (might be due to invalid IL or missing references)
//IL_020c: Expected O, but got Unknown
//IL_0211: Unknown result type (might be due to invalid IL or missing references)
//IL_021b: Expected O, but got Unknown
//IL_0220: Unknown result type (might be due to invalid IL or missing references)
//IL_022a: Expected O, but got Unknown
//IL_022f: Unknown result type (might be due to invalid IL or missing references)
//IL_0239: Expected O, but got Unknown
AlwaysVisible = ((BaseUnityPlugin)this).Config.Bind<bool>("Chat", "AlwaysVisible", false, "If True, the chat window will remain visible at all times. If False, the chat window will appear when new messages are received.");
AlwaysVisible.SettingChanged += AlwaysVisible_SettingChanged;
HideDelay = ((BaseUnityPlugin)this).Config.Bind<float>("Chat", "HideDelay", 10f, "The time, in seconds, to keep the chat window visible after sending or receiving a message. Minimum is 0.5. Has no effect if AlwaysVisible=true.");
HideDelay.SettingChanged += HideDelay_SettingChanged;
ForceCase = ((BaseUnityPlugin)this).Config.Bind<bool>("Chat", "ForceCase", false, "If True, shout will be in all caps and whisper will be in all lowercase (game default). If False, messages will appear as they were originally entered.");
ForceCase.SettingChanged += ForceCase_SettingChanged;
SlashOpensChat = ((BaseUnityPlugin)this).Config.Bind<bool>("Chat", "SlashOpensChat", true, "If True, pressing the slash key (/) will open the chat window and start a message.");
SlashOpensChat.SettingChanged += SlashOpensChat_SettingChanged;
DefaultShout = ((BaseUnityPlugin)this).Config.Bind<bool>("Chat", "DefaultShout", false, "If True, text entered will shout by default - type /say for talk. If False, chat will be talk by default - type /s for shout.");
DefaultShout.SettingChanged += DefaultShout_SettingChanged;
ShowShoutPings = ((BaseUnityPlugin)this).Config.Bind<bool>("Chat", "ShowShoutPings", true, "If True, pings will show on your map when players shout (game default). If False, the pings will not show. (Other players can still see your shout pings.)");
ShowShoutPings.SettingChanged += ShowShoutPings_SettingChanged;
TalkDistance = ((BaseUnityPlugin)this).Config.Bind<float>("Chat", "TalkDistance", 15f, "The maximum distance from a player at which you will receive their normal chat messages (not whisper or shout). Game default is 15. Acceptable range is 1-100.");
TalkDistance.SettingChanged += Distance_SettingChanged;
WhisperDistance = ((BaseUnityPlugin)this).Config.Bind<float>("Chat", "WhisperDistance", 4f, "The maximum distance from a player at which you will receive their whispered chat messages. Game default is 4. Acceptable range is 1-20");
WhisperDistance.SettingChanged += Distance_SettingChanged;
ClampConfig();
sChatAwakeHarmony = new Harmony("dev.crystal.betterchat_ChatAwake");
sPlayerHarmony = new Harmony("dev.crystal.betterchat_Player");
sChatShowHarmony = new Harmony("dev.crystal.betterchat_ChatShow");
sChatAlwaysShowHarmony = new Harmony("dev.crystal.betterchat_ChatAlwaysShow");
sChatMixedCaseHarmony = new Harmony("dev.crystal.betterchat_ChatMixedCase");
sChatShoutHarmony = new Harmony("dev.crystal.betterchat_ChatShout");
sMinimapHarmony = new Harmony("dev.crystal.betterchat_Minimap");
sChatSlashHarmony = new Harmony("dev.crystal.betterchat_ChatSlash");
sChatAwakeHarmony.PatchAll(typeof(Chat_Patches));
sPlayerHarmony.PatchAll(typeof(Player_Patches));
if (AlwaysVisible.Value)
{
sChatAlwaysShowHarmony.PatchAll(typeof(Chat_AlwaysShow_Patch));
}
else
{
sChatShowHarmony.PatchAll(typeof(Chat_Show_Patch));
}
if (!ForceCase.Value)
{
sChatMixedCaseHarmony.PatchAll(typeof(Chat_MixedCase_Terminal_Patch));
sChatMixedCaseHarmony.PatchAll(typeof(Chat_MixedCase_Chat_Patch));
}
if (DefaultShout.Value)
{
sChatShoutHarmony.PatchAll(typeof(Chat_Shout_Patch));
}
if (!ShowShoutPings.Value)
{
sMinimapHarmony.PatchAll(typeof(Minimap_Patches));
}
if (SlashOpensChat.Value)
{
sChatSlashHarmony.PatchAll(typeof(Chat_Slash_Patches));
}
}
private void OnDestroy()
{
sChatAwakeHarmony.UnpatchSelf();
sPlayerHarmony.UnpatchSelf();
sChatShowHarmony.UnpatchSelf();
sChatAlwaysShowHarmony.UnpatchSelf();
sChatMixedCaseHarmony.UnpatchSelf();
sChatShoutHarmony.UnpatchSelf();
sMinimapHarmony.UnpatchSelf();
sChatSlashHarmony.UnpatchSelf();
}
private static void ClampConfig()
{
if (HideDelay.Value < 0.5f)
{
HideDelay.Value = 0.5f;
}
if (HideDelay.Value > 3600f)
{
HideDelay.Value = 3600f;
}
if (TalkDistance.Value < 1f)
{
TalkDistance.Value = 1f;
}
if (TalkDistance.Value > 100f)
{
TalkDistance.Value = 100f;
}
if (WhisperDistance.Value < 1f)
{
WhisperDistance.Value = 1f;
}
if (WhisperDistance.Value > 20f)
{
WhisperDistance.Value = 20f;
}
}
private void AlwaysVisible_SettingChanged(object sender, EventArgs e)
{
if (AlwaysVisible.Value)
{
sChatShowHarmony.UnpatchSelf();
sChatAlwaysShowHarmony.PatchAll(typeof(Chat_AlwaysShow_Patch));
}
else
{
sChatAlwaysShowHarmony.UnpatchSelf();
sChatShowHarmony.PatchAll(typeof(Chat_Show_Patch));
}
}
private void HideDelay_SettingChanged(object sender, EventArgs e)
{
ClampConfig();
if ((Object)(object)sChat != (Object)null)
{
sChat.m_hideDelay = HideDelay.Value;
}
}
private void ForceCase_SettingChanged(object sender, EventArgs e)
{
if (ForceCase.Value)
{
sChatMixedCaseHarmony.UnpatchSelf();
return;
}
sChatMixedCaseHarmony.PatchAll(typeof(Chat_MixedCase_Terminal_Patch));
sChatMixedCaseHarmony.PatchAll(typeof(Chat_MixedCase_Chat_Patch));
}
private void SlashOpensChat_SettingChanged(object sender, EventArgs e)
{
if (SlashOpensChat.Value)
{
sChatSlashHarmony.PatchAll(typeof(Chat_Slash_Patches));
}
else
{
sChatSlashHarmony.UnpatchSelf();
}
}
private void DefaultShout_SettingChanged(object sender, EventArgs e)
{
if (DefaultShout.Value)
{
sChatShoutHarmony.PatchAll(typeof(Chat_Shout_Patch));
}
else
{
sChatShoutHarmony.UnpatchSelf();
}
}
private void ShowShoutPings_SettingChanged(object sender, EventArgs e)
{
if (ShowShoutPings.Value)
{
sMinimapHarmony.UnpatchSelf();
}
else
{
sMinimapHarmony.PatchAll(typeof(Minimap_Patches));
}
}
private void Distance_SettingChanged(object sender, EventArgs e)
{
ClampConfig();
foreach (Talker sTalker in sTalkers)
{
ApplyChatDistances(sTalker);
}
}
private static void ApplyChatDistances(Talker talker)
{
talker.m_visperDistance = WhisperDistance.Value;
talker.m_normalDistance = TalkDistance.Value;
}
private static IEnumerable<CodeInstruction> StripForcedCase(IEnumerable<CodeInstruction> instructions)
{
List<CodeInstruction> list = new List<CodeInstruction>(instructions);
for (int i = 0; i < list.Count; i++)
{
if (list[i].opcode == OpCodes.Callvirt)
{
MethodBase methodBase = list[i].operand as MethodBase;
if (methodBase != null && (methodBase.Name == "ToLowerInvariant" || methodBase.Name == "ToUpper"))
{
list.RemoveRange(i - 1, 3);
i -= 2;
}
}
}
return list;
}
}