using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Text.RegularExpressions; 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.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyVersion("0.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.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 FrostValeCompat { [BepInPlugin("frostvale.compat", "FrostVale Compat", "1.1.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class RiceWaterLevelPlugin : BaseUnityPlugin { public const string PluginGuid = "frostvale.compat"; public const string PluginName = "FrostVale Compat"; public const string PluginVersion = "1.1.0"; internal static ConfigEntry MinGroundOffset; internal static ConfigEntry MaxGroundOffset; internal static ConfigEntry BlockRiceAwayFromWater; internal static ConfigEntry EnableCreatureGemDrops; internal static ConfigEntry CreatureGemDefaultChance; 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."); EnableCreatureGemDrops = ((BaseUnityPlugin)this).Config.Bind("Jewelcrafting Creature Drops", "Enable star-scaled creature gem drops", true, "If enabled, FrostVale adds Jewelcrafting gems to non-boss creature drops based on CLLC star level."); CreatureGemDefaultChance = ((BaseUnityPlugin)this).Config.Bind("Jewelcrafting Creature Drops", "Default low-star unrefined drop chance", 1.5f, "Percent chance used for 0-3 star unrefined gem drops."); Harmony.CreateAndPatchAll(typeof(RiceWaterLevelPlugin).Assembly, "frostvale.compat"); DiscordNameDisplayPatches.Patch(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"FrostVale compatibility patches loaded."); } } [HarmonyPatch(typeof(Player), "TryPlacePiece")] internal static class RicePlacementPatch { private static bool Prefix(Piece piece, ref bool __result) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_002f: 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_0032: 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; } return LooksLikeRice(((Object)piece).name) || LooksLikeRice(((Object)((Component)piece).gameObject).name) || LooksLikeRice(piece.m_name); } public static bool IsRicePlant(Plant plant) { if ((Object)(object)plant == (Object)null) { return false; } return LooksLikeRice(((Object)plant).name) || LooksLikeRice(((Object)((Component)plant).gameObject).name) || LooksLikeRice(plant.m_name); } public static bool IsWithinWaterBand(Vector3 position, out float groundHeight, out float waterLevel) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //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; return num2 >= RiceWaterLevelPlugin.MinGroundOffset.Value && num2 <= RiceWaterLevelPlugin.MaxGroundOffset.Value; } private static bool LooksLikeRice(string value) { return !string.IsNullOrEmpty(value) && value.IndexOf("Rice", StringComparison.OrdinalIgnoreCase) >= 0 && value.IndexOf("_RtD", StringComparison.OrdinalIgnoreCase) >= 0; } } 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_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: 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_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0063: 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) ? value2 : match.Value); } internal static bool IsRawId(string value) { return !string.IsNullOrWhiteSpace(value) && NumericIdPattern.IsMatch(value.Trim()); } } 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; } } [HarmonyPatch(typeof(CharacterDrop), "GenerateDropList")] internal static class JewelcraftingCreatureDropPatch { private static readonly FieldRef CharacterRef = AccessTools.FieldRefAccess("m_character"); private static readonly string[] UnrefinedGemPrefabs = new string[7] { "Uncut_Green_Stone", "Uncut_Orange_Stone", "Uncut_Black_Stone", "Uncut_Red_Stone", "Uncut_Blue_Stone", "Uncut_Purple_Stone", "Uncut_Yellow_Stone" }; private static readonly string[] SimpleGemPrefabs = new string[7] { "Simple_Green_Socket", "Simple_Orange_Socket", "Simple_Black_Socket", "Simple_Red_Socket", "Simple_Blue_Socket", "Simple_Purple_Socket", "Simple_Yellow_Socket" }; private static readonly string[] AdvancedGemPrefabs = new string[7] { "Advanced_Green_Socket", "Advanced_Orange_Socket", "Advanced_Black_Socket", "Advanced_Red_Socket", "Advanced_Blue_Socket", "Advanced_Purple_Socket", "Advanced_Yellow_Socket" }; private static readonly HashSet MissingPrefabWarnings = new HashSet(StringComparer.OrdinalIgnoreCase); private static void Postfix(CharacterDrop __instance, ref List> __result) { if (!RiceWaterLevelPlugin.EnableCreatureGemDrops.Value || (Object)(object)__instance == (Object)null || __result == null) { return; } Character val = CharacterRef.Invoke(__instance); if ((Object)(object)val == (Object)null) { val = ((Component)__instance).GetComponent(); } if (!((Object)(object)val == (Object)null) && !val.IsBoss()) { switch (Math.Max(0, Math.Min(5, val.GetLevel() - 1))) { case 0: AddDefaultGatedDrops(__result, UnrefinedGemPrefabs, 1); break; case 1: AddDefaultGatedDrops(__result, UnrefinedGemPrefabs, 2); break; case 2: AddDefaultGatedDrops(__result, UnrefinedGemPrefabs, 3); break; case 3: AddRandomDrops(__result, SimpleGemPrefabs, 0, 1); AddDefaultGatedDrops(__result, UnrefinedGemPrefabs, 2); break; case 4: AddRandomDrops(__result, SimpleGemPrefabs, 0, 2); AddRandomDrops(__result, UnrefinedGemPrefabs, 1, 3); break; default: AddRandomDrops(__result, AdvancedGemPrefabs, 0, 2); AddRandomDrops(__result, UnrefinedGemPrefabs, 2, 4); break; } } } private static void AddDefaultGatedDrops(List> drops, string[] prefabNames, int maxAmount) { float num = Mathf.Clamp(RiceWaterLevelPlugin.CreatureGemDefaultChance.Value, 0f, 100f); if (!(Random.Range(0f, 100f) >= num)) { AddRandomDrops(drops, prefabNames, 1, maxAmount); } } private static void AddRandomDrops(List> drops, string[] prefabNames, int minAmount, int maxAmount) { int num = Random.Range(minAmount, maxAmount + 1); for (int i = 0; i < num; i++) { GameObject randomPrefab = GetRandomPrefab(prefabNames); if ((Object)(object)randomPrefab != (Object)null) { drops.Add(new KeyValuePair(randomPrefab, 1)); } } } private static GameObject GetRandomPrefab(string[] prefabNames) { if (prefabNames == null || prefabNames.Length == 0) { return null; } string text = prefabNames[Random.Range(0, prefabNames.Length)]; GameObject val = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetItemPrefab(text) : null); if ((Object)(object)val == (Object)null && (Object)(object)ZNetScene.instance != (Object)null) { val = ZNetScene.instance.GetPrefab(text); } if ((Object)(object)val == (Object)null && MissingPrefabWarnings.Add(text)) { RiceWaterLevelPlugin.Log.LogWarning((object)("FrostVale Jewelcrafting drop prefab not found: " + text)); } return val; } } internal sealed class ReferenceEqualityComparer : IEqualityComparer { public static readonly ReferenceEqualityComparer Instance = new ReferenceEqualityComparer(); private ReferenceEqualityComparer() { } public new bool Equals(object x, object y) { return x == y; } public int GetHashCode(object obj) { return RuntimeHelpers.GetHashCode(obj); } } }