using System; using System.Collections; using System.Collections.Generic; using System.Reflection; using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using UnityEngine; [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: CompilationRelaxations(8)] [assembly: AssemblyVersion("0.0.0.0")] namespace FrostValeCompat; [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInPlugin("frostvale.compat", "FrostVale Compat", "1.0.1")] public sealed class RiceWaterLevelPlugin : BaseUnityPlugin { public const string PluginGuid = "frostvale.compat"; public const string PluginName = "FrostVale Compat"; public const string PluginVersion = "1.0.1"; internal static ConfigEntry MinGroundOffset; internal static ConfigEntry MaxGroundOffset; internal static ConfigEntry BlockRiceAwayFromWater; internal static RiceWaterLevelPlugin Instance; internal static ManualLogSource Log; private void Awake() { Instance = this; Log = ((BaseUnityPlugin)this).Logger; BlockRiceAwayFromWater = ((BaseUnityPlugin)this).Config.Bind("RtDOcean Rice", "Block rice away from water level", true, "If enabled, RtDOcean rice saplings can only be placed near the configured water-level band."); MinGroundOffset = ((BaseUnityPlugin)this).Config.Bind("RtDOcean Rice", "Minimum ground offset from water", -0.35f, "Lowest allowed terrain height relative to water level, in meters."); MaxGroundOffset = ((BaseUnityPlugin)this).Config.Bind("RtDOcean Rice", "Maximum ground offset from water", 0.15f, "Highest allowed terrain height relative to water level, in meters."); Harmony.CreateAndPatchAll(typeof(RiceWaterLevelPlugin).Assembly, "frostvale.compat"); DiscordNameDisplayPatches.Patch(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"FrostVale rice water-level placement patch loaded."); } } [HarmonyPatch(typeof(Player), "TryPlacePiece")] internal static class RicePlacementPatch { private static bool Prefix(Piece piece, ref bool __result) { //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_0022: Unknown result type (might be due to invalid IL or missing references) if (!RiceWaterLevelPlugin.BlockRiceAwayFromWater.Value || !RiceWaterLevelRules.IsRiceSapling(piece)) { return true; } Vector3 position = ((Component)piece).transform.position; if (RiceWaterLevelRules.IsWithinWaterBand(position, out var groundHeight, out var waterLevel)) { return true; } __result = false; ShowMessage($"Rice must be planted at water level. Ground {groundHeight - waterLevel:0.00}m from water."); return false; } private static void ShowMessage(string message) { try { if ((Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage((MessageType)2, message, 0, (Sprite)null, false); } } catch (Exception ex) { RiceWaterLevelPlugin.Log.LogDebug((object)("Could not show rice placement message: " + ex.Message)); } } } [HarmonyPatch(typeof(Plant), "GetStatus")] internal static class RicePlantStatusPatch { private static void Postfix(Plant __instance, ref Status __result) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) if (RiceWaterLevelPlugin.BlockRiceAwayFromWater.Value && (int)__result == 0 && RiceWaterLevelRules.IsRicePlant(__instance) && !RiceWaterLevelRules.IsWithinWaterBand(((Component)__instance).transform.position, out var _, out var _)) { __result = (Status)3; } } } internal static class RiceWaterLevelRules { public static bool IsRiceSapling(Piece piece) { if ((Object)(object)piece == (Object)null) { return false; } if (!LooksLikeRice(((Object)piece).name) && !LooksLikeRice(((Object)((Component)piece).gameObject).name)) { return LooksLikeRice(piece.m_name); } return true; } public static bool IsRicePlant(Plant plant) { if ((Object)(object)plant == (Object)null) { return false; } if (!LooksLikeRice(((Object)plant).name) && !LooksLikeRice(((Object)((Component)plant).gameObject).name)) { return LooksLikeRice(plant.m_name); } return true; } public static bool IsWithinWaterBand(Vector3 position, out float groundHeight, out float waterLevel) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) ZoneSystem instance = ZoneSystem.instance; waterLevel = (((Object)(object)instance != (Object)null) ? instance.m_waterLevel : 30f); groundHeight = position.y; float num = default(float); if ((Object)(object)instance != (Object)null && instance.GetGroundHeight(position, ref num)) { groundHeight = num; } float num2 = groundHeight - waterLevel; if (num2 >= RiceWaterLevelPlugin.MinGroundOffset.Value) { return num2 <= RiceWaterLevelPlugin.MaxGroundOffset.Value; } return false; } private static bool LooksLikeRice(string value) { if (!string.IsNullOrEmpty(value) && value.IndexOf("Rice", StringComparison.OrdinalIgnoreCase) >= 0) { return value.IndexOf("_RtD", StringComparison.OrdinalIgnoreCase) >= 0; } return false; } } internal static class DiscordNameDisplayPatches { private static readonly Dictionary NamesById = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Regex NumericIdPattern = new Regex("\\b\\d{15,20}\\b", RegexOptions.Compiled); public static void Patch() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected O, but got Unknown try { Harmony harmony = new Harmony("frostvale.compat.discord-names"); PatchPrefix(harmony, "FiresDiscordIntegration.Discord.DiscordIntegrationCore", "OnPlayerJoin", typeof(DiscordPlayerNameTracker).GetMethod("PlayerNameFirstPrefix", BindingFlags.Static | BindingFlags.NonPublic)); PatchPrefix(harmony, "FiresDiscordIntegration.Discord.DiscordIntegrationCore", "OnPlayerLeave", typeof(DiscordPlayerNameTracker).GetMethod("PlayerNameFirstPrefix", BindingFlags.Static | BindingFlags.NonPublic)); PatchPrefix(harmony, "FiresDiscordIntegration.Discord.DiscordIntegrationCore", "OnClientLoginArtifacts", typeof(DiscordPlayerNameTracker).GetMethod("PlayerNameFirstPrefix", BindingFlags.Static | BindingFlags.NonPublic)); PatchPrefix(harmony, "FiresDiscordIntegration.Discord.DiscordIntegrationCore", "SendClientLogOnRequest", typeof(DiscordPlayerNameTracker).GetMethod("PlayerNameFirstPrefix", BindingFlags.Static | BindingFlags.NonPublic)); PatchPrefix(harmony, "FiresDiscordIntegration.ClientLogRelay.ClientLogRelayIntegration", "PostDisconnectLog", typeof(DiscordPlayerNameTracker).GetMethod("IdFirstPrefix", BindingFlags.Static | BindingFlags.NonPublic)); PatchPrefix(harmony, "FiresDiscordIntegration.ClientLogRelay.ClientLogRelayIntegration", "PostDisconnectLogAsync", typeof(DiscordPlayerNameTracker).GetMethod("IdFirstPrefix", BindingFlags.Static | BindingFlags.NonPublic)); PatchPrefix(harmony, "FiresDiscordIntegration.Discord.DiscordStatusTracker", "RecordJoin", typeof(DiscordPlayerNameTracker).GetMethod("PlayerNameFirstPrefix", BindingFlags.Static | BindingFlags.NonPublic)); PatchPrefix(harmony, "FiresDiscordIntegration.Discord.DiscordStatusTracker", "RecordLeave", typeof(DiscordPlayerNameTracker).GetMethod("PlayerNameFirstPrefix", BindingFlags.Static | BindingFlags.NonPublic)); PatchPrefix(harmony, "FiresDiscordIntegration.Discord.DiscordWebhookService", "SendMessage", typeof(DiscordVisibleTextRewriter).GetMethod("Prefix", BindingFlags.Static | BindingFlags.NonPublic)); PatchPrefix(harmony, "FiresDiscordIntegration.Discord.DiscordWebhookService", "SendEmbed", typeof(DiscordVisibleTextRewriter).GetMethod("Prefix", BindingFlags.Static | BindingFlags.NonPublic)); PatchPrefix(harmony, "FiresDiscordIntegration.Discord.DiscordWebhookService", "SendEmbedWithFiles", typeof(DiscordVisibleTextRewriter).GetMethod("Prefix", BindingFlags.Static | BindingFlags.NonPublic)); PatchPrefix(harmony, "FiresDiscordIntegration.Discord.DiscordWebhookService", "EnqueuePost", typeof(DiscordVisibleTextRewriter).GetMethod("Prefix", BindingFlags.Static | BindingFlags.NonPublic)); RiceWaterLevelPlugin.Log.LogInfo((object)"FrostVale Discord name-display patch loaded."); } catch (Exception ex) { RiceWaterLevelPlugin.Log.LogWarning((object)("FrostVale Discord name-display patch skipped: " + ex.Message)); } } private static void PatchPrefix(Harmony harmony, string typeName, string methodName, MethodInfo prefix) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown if (prefix == null) { return; } Type type = AccessTools.TypeByName(typeName); if (type == null) { return; } foreach (MethodInfo declaredMethod in AccessTools.GetDeclaredMethods(type)) { if (declaredMethod.Name == methodName) { harmony.Patch((MethodBase)declaredMethod, new HarmonyMethod(prefix), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } } internal static void Remember(string platformId, string playerName) { if (!string.IsNullOrWhiteSpace(platformId) && !string.IsNullOrWhiteSpace(playerName) && !IsRawId(playerName)) { NamesById[platformId.Trim()] = playerName.Trim(); } } internal static string RewriteText(string value) { if (string.IsNullOrEmpty(value) || NamesById.Count == 0) { return value; } string value2; return NumericIdPattern.Replace(value, (Match match) => (!NamesById.TryGetValue(match.Value, out value2)) ? match.Value : value2); } internal static bool IsRawId(string value) { if (!string.IsNullOrWhiteSpace(value)) { return NumericIdPattern.IsMatch(value.Trim()); } return false; } } internal static class DiscordPlayerNameTracker { private static void PlayerNameFirstPrefix(object[] __args) { if (__args != null && __args.Length >= 2) { DiscordNameDisplayPatches.Remember(__args[1] as string, __args[0] as string); } } private static void IdFirstPrefix(object[] __args) { if (__args != null && __args.Length >= 2) { DiscordNameDisplayPatches.Remember(__args[0] as string, __args[1] as string); } } } internal static class DiscordVisibleTextRewriter { private static void Prefix(object[] __args) { if (__args == null) { return; } for (int i = 0; i < __args.Length; i++) { object obj = __args[i]; if (obj is string value) { __args[i] = DiscordNameDisplayPatches.RewriteText(value); } else { RewriteObject(obj, new HashSet(ReferenceEqualityComparer.Instance)); } } } private static void RewriteObject(object value, HashSet visited) { if (value == null || value is string || value.GetType().IsPrimitive || !visited.Add(value)) { return; } if (value is IDictionary dictionary) { List list = new List(); foreach (object key in dictionary.Keys) { list.Add(key); } { foreach (object item in list) { object obj = dictionary[item]; string text = obj as string; dictionary[item] = ((text != null) ? DiscordNameDisplayPatches.RewriteText(text) : RewriteAndReturn(obj, visited)); } return; } } if (value is IList list2) { for (int i = 0; i < list2.Count; i++) { object obj2 = list2[i]; string text2 = obj2 as string; list2[i] = ((text2 != null) ? DiscordNameDisplayPatches.RewriteText(text2) : RewriteAndReturn(obj2, visited)); } return; } FieldInfo[] fields = value.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (fieldInfo.FieldType == typeof(string)) { string value2 = fieldInfo.GetValue(value) as string; fieldInfo.SetValue(value, DiscordNameDisplayPatches.RewriteText(value2)); } else if (!fieldInfo.FieldType.IsValueType) { RewriteObject(fieldInfo.GetValue(value), visited); } } } private static object RewriteAndReturn(object value, HashSet visited) { RewriteObject(value, visited); return value; } } internal sealed class ReferenceEqualityComparer : IEqualityComparer { public static readonly ReferenceEqualityComparer Instance = new ReferenceEqualityComparer(); private ReferenceEqualityComparer() { } public new bool Equals(object x, object y) { return object.ReferenceEquals(x, y); } public int GetHashCode(object obj) { return RuntimeHelpers.GetHashCode(obj); } }