Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of LogicalExperience v1.0.3
LogicalExperience.dll
Decompiled 3 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using JetBrains.Annotations; using Microsoft.CodeAnalysis; using ServerSync; using TMPro; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyTitle("LogicalExperience")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("LogicalExperience")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("15d5e884-6c20-4fdc-991f-5450fd320b5c")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [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.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] [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] [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 LogicalExperience { [BepInPlugin("yourname.LogicalExperience", "Logical Experience", "1.0.3")] [BepInProcess("valheim.exe")] [BepInProcess("valheim_server.exe")] public class LogicalExperiencePlugin : BaseUnityPlugin { public const string ModGUID = "yourname.LogicalExperience"; public const string ModName = "Logical Experience"; public const string ModVersion = "1.0.3"; internal static LogicalExperiencePlugin Instance; private ConfigSync _configSync; private readonly Harmony _harmony = new Harmony("yourname.LogicalExperience"); private ConfigReloadWatcher _watcher; public static ConfigEntry<bool> LockConfig; public static ConfigEntry<bool> ShowXpPopups; public static ConfigEntry<bool> ShowXpTopLeft; public static ConfigEntry<bool> ToggleRide; public static ConfigEntry<bool> ToggleSwim; public static ConfigEntry<bool> ToggleJump; public static ConfigEntry<bool> ToggleDodge; public static ConfigEntry<bool> ToggleBlock; public static ConfigEntry<bool> ToggleCrossbow; public static ConfigEntry<bool> ToggleSneak; public static ConfigEntry<bool> ToggleBlood; public static ConfigEntry<bool> ToggleEmbers; public static ConfigEntry<bool> ToggleCrafting; public static ConfigEntry<bool> ToggleCooking; public static ConfigEntry<bool> ToggleFarming; public static ConfigEntry<bool> ToggleFishing; public static ConfigEntry<bool> ToggleWood; public static ConfigEntry<bool> TogglePickaxe; public static ConfigEntry<bool> ToggleKillXp; private void Awake() { Instance = this; try { _configSync = new ConfigSync("yourname.LogicalExperience") { DisplayName = "Logical Experience", CurrentVersion = "1.0.3", MinimumRequiredVersion = "1.0.3", ModRequired = false }; LockConfig = BindSynced(_configSync, "1 - General", "Lock Configuration", value: true, "If enabled, whatever the host/server config says it applies. Clients receive these values and cannot override them."); _configSync.AddLockingConfigEntry<bool>(LockConfig); ShowXpPopups = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - General", "Show Kill XP Popups", true, "Client-side: show floating +XPxp numbers for kill bonuses. Not enforced by the server."); ShowXpTopLeft = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - General", "Show TopLeft XP Log", false, "Client-side: show TopLeft skill XP messages for testing. Off by default to avoid clutter/overhead."); ToggleRide = BindSynced(_configSync, "2 - Skills", "Ride", value: true, "On = x2 Riding XP\nOff = Vanilla"); ToggleSwim = BindSynced(_configSync, "2 - Skills", "Swim", value: true, "On = x2 Swimming XP\nOff = Vanilla"); ToggleJump = BindSynced(_configSync, "2 - Skills", "Jump", value: true, "On = +0–3 Jump XP from weight\nOff = Vanilla"); ToggleDodge = BindSynced(_configSync, "2 - Skills", "Dodge", value: true, "On = x10 Dodge / perfect dodge XP\nOff = Vanilla"); ToggleBlock = BindSynced(_configSync, "2 - Skills", "Block", value: true, "On = x1.5 Block / parry XP\nOff = Vanilla"); ToggleCrossbow = BindSynced(_configSync, "2 - Skills", "Crossbows", value: true, "On = x2 Crossbow hit XP +1 reload\nOff = Vanilla"); ToggleSneak = BindSynced(_configSync, "2 - Skills", "Sneak Backstab", value: true, "On = +3 Sneak XP on backstab\nOff = Vanilla"); ToggleBlood = BindSynced(_configSync, "2 - Skills", "Blood Magic", value: true, "On = +3 Blood Magic XP on cast\nOff = Vanilla"); ToggleEmbers = BindSynced(_configSync, "2 - Skills", "Staff of Embers Burn", value: true, "On = +0.1 Elemental XP/s while burning\nOff = Vanilla"); ToggleCrafting = BindSynced(_configSync, "2 - Skills", "Crafting", value: true, "On = x4 Crafting XP + station bonuses\nOff = Vanilla"); ToggleCooking = BindSynced(_configSync, "2 - Skills", "Cooking", value: true, "On = x4 Cooking XP + cauldron/ketill bonuses\nOff = Vanilla"); ToggleFarming = BindSynced(_configSync, "2 - Skills", "Farming", value: true, "On = x2 Farming XP\nOff = Vanilla"); ToggleFishing = BindSynced(_configSync, "2 - Skills", "Fishing", value: true, "On = x2 Fishing XP + fish bonuses\nOff = Vanilla"); ToggleWood = BindSynced(_configSync, "2 - Skills", "Wood Cutting", value: true, "On = Tree/log type bonuses\nOff = Vanilla"); TogglePickaxe = BindSynced(_configSync, "2 - Skills", "Pickaxes", value: true, "On = Pickaxe tier bonuses\nOff = Vanilla"); ToggleKillXp = BindSynced(_configSync, "2 - Skills", "Kill XP Bonus", value: true, "On = Extra kill XP (tier + stars)\nOff = Vanilla"); try { _harmony.PatchAll(typeof(XpPatches)); ((BaseUnityPlugin)this).Logger.LogInfo((object)"[Logical Experience] All Harmony patches applied."); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("[Logical Experience] PatchAll failed: " + ex.Message)); ((BaseUnityPlugin)this).Logger.LogWarning((object)"[Logical Experience] Attempting essential patches only…"); TryPatchEssential(); } _watcher = ((Component)this).gameObject.AddComponent<ConfigReloadWatcher>(); _watcher.Initialize(((BaseUnityPlugin)this).Config, _configSync); ((BaseUnityPlugin)this).Logger.LogInfo((object)"[Logical Experience] Loaded v1.0.3"); } catch (Exception arg) { ((BaseUnityPlugin)this).Logger.LogError((object)string.Format("[{0}] Awake failed: {1}", "Logical Experience", arg)); } } private void TryPatchEssential() { Safe(typeof(Skills), "RaiseSkill", "RaiseSkill_Prefix"); Safe(typeof(Skills), "RaiseSkill", "RaiseSkill_ShowFloat"); Safe(typeof(Character), "Damage", "Character_Damage_TrackPrefix"); Safe(typeof(Character), "Damage", "Character_Damage_Track"); Safe(typeof(Character), "Damage", "Character_Damage_Backstab"); Safe(typeof(Character), "OnDeath", "Character_OnDeath"); Safe(typeof(Projectile), "OnHit", "Projectile_OnHit"); void Safe(Type type, string name, string patchName, Type[] args = null) { //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Expected O, but got Unknown //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Expected O, but got Unknown try { MethodInfo methodInfo = ((args == null) ? AccessTools.Method(type, name, (Type[])null, (Type[])null) : AccessTools.Method(type, name, args, (Type[])null)); MethodInfo methodInfo2 = AccessTools.Method(typeof(XpPatches), patchName, (Type[])null, (Type[])null); if (methodInfo == null || methodInfo2 == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("[Logical Experience] Skip " + type.Name + "." + name + " (not found)")); } else { if (patchName.Contains("Prefix") || methodInfo2.Name.Contains("Prefix") || patchName.Contains("_Prefix") || methodInfo2.Name.EndsWith("Prefix")) { _harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { _harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } ((BaseUnityPlugin)this).Logger.LogInfo((object)("[Logical Experience] Essential: " + type.Name + "." + name)); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("[Logical Experience] Essential failed " + type.Name + "." + name + ": " + ex.Message)); } } } private void OnDestroy() { _harmony.UnpatchSelf(); } public static bool IsEnabled(ConfigEntry<bool> entry) { return entry?.Value ?? true; } internal ConfigEntry<T> BindSynced<T>(ConfigSync configSync, string group, string name, T value, string description, bool synchronizedSetting = true) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown return BindSynced(configSync, group, name, value, new ConfigDescription(description, (AcceptableValueBase)null, Array.Empty<object>()), synchronizedSetting); } internal ConfigEntry<T> BindSynced<T>(ConfigSync configSync, string group, string name, T value, ConfigDescription description, bool synchronizedSetting = true) { ConfigEntry<T> val = ((BaseUnityPlugin)this).Config.Bind<T>(group, name, value, description); SyncedConfigEntry<T> syncedConfigEntry = configSync.AddConfigEntry<T>(val); syncedConfigEntry.SynchronizedConfig = synchronizedSetting; return val; } internal static void Log(string msg) { } internal static void LogAlways(string msg) { if ((Object)(object)Instance != (Object)null) { ((BaseUnityPlugin)Instance).Logger.LogInfo((object)("[Logical Experience] " + msg)); } } } internal sealed class ConfigReloadWatcher : MonoBehaviour { private const float PollIntervalSeconds = 30f; private ConfigFile _config; private ConfigSync _configSync; private string _path; private DateTime _lastWriteUtc; private float _pollElapsed; internal void Initialize(ConfigFile config, ConfigSync configSync) { _config = config; _configSync = configSync; _path = config.ConfigFilePath; _lastWriteUtc = ReadWriteTime(); _pollElapsed = 0f; } private void Update() { _pollElapsed += Time.unscaledDeltaTime; if (!(_pollElapsed < 30f)) { _pollElapsed = 0f; PollFile(); } } private void PollFile() { if (_config == null || string.IsNullOrEmpty(_path) || !File.Exists(_path)) { return; } DateTime dateTime = ReadWriteTime(); if (!(dateTime <= _lastWriteUtc)) { _lastWriteUtc = dateTime; if (_configSync == null || _configSync.IsSourceOfTruth) { _config.Reload(); LogicalExperiencePlugin.LogAlways("Config file changed on disk — reloaded."); } } } private DateTime ReadWriteTime() { try { return File.GetLastWriteTimeUtc(_path); } catch { return DateTime.MinValue; } } } internal static class XpData { private static readonly Dictionary<string, float> KillXpByPrefab = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase) { { "Greyling", 0.2f }, { "Neck", 0.2f }, { "Boar", 0.2f }, { "Deer", 0.2f }, { "Eikthyr", 5f }, { "Greydwarf", 0.4f }, { "Greydwarf_Elite", 0.8f }, { "Greydwarf_Shaman", 0.8f }, { "Skeleton", 0.8f }, { "Skeleton_Poison", 1f }, { "Troll", 2f }, { "Ghost", 1f }, { "TheElder", 10f }, { "Draugr", 1f }, { "Draugr_Elite", 1.5f }, { "Draugr_Ranged", 1f }, { "Blob", 1f }, { "BlobElite", 2f }, { "Abomination", 3f }, { "Wraith", 2f }, { "Surtling", 1.5f }, { "Writhan", 2f }, { "Bonemass", 15f }, { "Wolf", 1.5f }, { "Fenring", 2f }, { "Cultist", 2f }, { "Drake", 1.5f }, { "Hatchling", 1.5f }, { "StoneGolem", 3f }, { "Ulv", 1f }, { "Moder", 20f }, { "Goblin", 1.5f }, { "GoblinBrute", 3f }, { "GoblinShaman", 3f }, { "Lox", 3f }, { "Deathsquito", 1.5f }, { "Growth", 2f }, { "Yagluth", 25f }, { "Seeker", 2f }, { "SeekerBrood", 1f }, { "SeekerBrute", 3f }, { "Gjall", 4f }, { "Tick", 1f }, { "Dverger", 4f }, { "DvergerMage", 4f }, { "TheQueen", 30f }, { "Charred_Melee", 3f }, { "Charred_Archer", 2.5f }, { "Charred_Mage", 4f }, { "Charred_Twitcher", 2f }, { "Asksvin", 3f }, { "Morgen", 5f }, { "FallenValkyrie", 5f }, { "Fader", 40f }, { "Gammeltroll", 8f }, { "Hexen", 5f }, { "Barka", 7f }, { "Elaking", 3f }, { "EyelessOne", 5f }, { "Krigen", 4f }, { "KallFimbulbringer", 50f }, { "Kall", 50f } }; private static readonly Dictionary<string, float> TreeXp = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase) { { "Beech", 0f }, { "Birch", 1f }, { "Fir", 0.5f }, { "Oak", 1.5f }, { "Pine", 1f }, { "Ancient", 2f }, { "Yggdrasil", 3f }, { "Ashwood", 3f }, { "Blackwood", 3f }, { "Scorched", 3f } }; private static readonly Dictionary<string, float> FishBonus = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase) { { "Fish1", 0.5f }, { "Fish2", 0.5f }, { "Fish3", 1f }, { "Fish4", 1f }, { "Fish5", 1.5f }, { "Fish6", 2f }, { "Fish7", 2.5f }, { "Fish8", 2.5f }, { "Fish9", 3f }, { "Fish10", 3f }, { "Fish12", 3f } }; public static float GetKillBaseXp(Character character) { if ((Object)(object)character == (Object)null) { return 0.5f; } string text = ((Object)((Component)character).gameObject).name; int num = text.IndexOf("(Clone)", StringComparison.Ordinal); if (num > 0) { text = text.Substring(0, num); } if (KillXpByPrefab.TryGetValue(text, out var value)) { return value; } float maxHealth = character.GetMaxHealth(); if (maxHealth <= 30f) { return 0.2f; } if (maxHealth <= 80f) { return 0.5f; } if (maxHealth <= 200f) { return 1f; } if (maxHealth <= 500f) { return 2f; } if (maxHealth <= 1000f) { return 4f; } if (maxHealth <= 2000f) { return 6f; } if (maxHealth <= 5000f) { return 8f; } return 15f; } public static float GetTreeXp(string treePrefabName) { if (string.IsNullOrEmpty(treePrefabName)) { return 0f; } string text = null; float num = 0f; int num2 = -1; foreach (KeyValuePair<string, float> item in TreeXp) { if (treePrefabName.IndexOf(item.Key, StringComparison.OrdinalIgnoreCase) >= 0 && item.Key.Length > num2) { num2 = item.Key.Length; text = item.Key; num = item.Value; } } return (num2 >= 0) ? num : 0f; } public static float GetFishRarity(string fishName) { if (string.IsNullOrEmpty(fishName)) { return 0.5f; } if (FishBonus.TryGetValue(fishName, out var value)) { return value; } string text = fishName.ToLowerInvariant(); if (text.Contains("troll") || text.Contains("serpent") || text.Contains("giant") || text.Contains("deep") || text.Contains("north") || text.Contains("blood")) { return 3f; } if (text.Contains("tuna") || text.Contains("swordfish") || text.Contains("coral")) { return 2f; } return 1f; } } [HarmonyPatch] internal static class XpPatches { private struct KillInfo { public Player Player; public SkillType Skill; public float Time; } private static readonly Dictionary<ZDOID, KillInfo> LastHit = new Dictionary<ZDOID, KillInfo>(); private static readonly Dictionary<ZDOID, float> RecentKills = new Dictionary<ZDOID, float>(); private static readonly Dictionary<ZDOID, KillInfo> EmberIgnite = new Dictionary<ZDOID, KillInfo>(); private static readonly Dictionary<int, float> RecentKillInstances = new Dictionary<int, float>(); private static bool _internalAward = false; private const float RideXpMult = 2f; private const float SwimXpMult = 2f; private const float DodgeXpMult = 10f; private const float BlockXpMult = 1.5f; private const float CrossbowXpMult = 2f; private const float CrossbowReloadXp = 1f; private const float SneakBackstabXp = 3f; private const float BloodStaffCastXp = 3f; private const float JumpWeightMax = 1000f; private const float JumpWeightBonusMax = 3f; private const float CookingXpMult = 4f; private const float CraftingXpMult = 4f; private const float FarmingXpMult = 2f; private const float FishingXpMult = 2f; private const float KillBonusMultiplier = 1f; private static float _raiseFactorFinal; private static float _accBeforeRaise; private static float _levelBeforeRaise; private static float _preHitHealth; private static bool _suppressNextWeaponXp; private static float _nextEmberBurnPoll; private static float _lastMineBonusTime = -999f; private static readonly Dictionary<long, bool> CrossbowWasLoaded = new Dictionary<long, bool>(); private static readonly HashSet<string> BloodStaffPrefabs = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "StaffRedTroll", "StaffGreenRoots", "StaffShield", "StaffSkeleton", "StaffSpiritCaller", "StaffFrostOrbs" }; private static float _lastBloodStaffXpTime = -999f; private static float _prevEitr = -1f; private static void Award(Player player, SkillType skill, float amount, string reason = null, Vector3? worldPos = null) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || amount <= 0f) { return; } Skills skills = ((Character)player).GetSkills(); if (!((Object)(object)skills == (Object)null)) { _internalAward = true; try { ApplyXpWithOverflow(skills, player, skill, amount); NotifyXpTopLeft(skills, player, skill, amount); } finally { _internalAward = false; } if (reason != null && reason.StartsWith("KILL", StringComparison.OrdinalIgnoreCase) && LogicalExperiencePlugin.ShowXpPopups != null && LogicalExperiencePlugin.ShowXpPopups.Value && ((Character)player).IsOwner()) { Vector3 pos = (worldPos.HasValue ? (worldPos.Value + Vector3.up * 0.6f) : (((Component)player).transform.position + Vector3.up * 1.5f)); ShowFloatingXp(pos, amount); } } } private static void ApplyXpWithOverflow(Skills skills, Player player, SkillType skillType, float amount) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) if (amount <= 0f) { return; } MethodInfo methodInfo = AccessTools.Method(typeof(Skills), "GetSkill", new Type[1] { typeof(SkillType) }, (Type[])null); Skill skill = (Skill)((methodInfo != null) ? /*isinst with value type is only supported in some contexts*/: null); FieldInfo accField = AccessTools.Field(typeof(Skill), "m_accumulator"); FieldInfo levelField = AccessTools.Field(typeof(Skill), "m_level"); MethodInfo nextReq = AccessTools.Method(typeof(Skill), "GetNextLevelRequirement", (Type[])null, (Type[])null); if (skill == null || accField == null || levelField == null) { ((Character)player).RaiseSkill(skillType, amount); return; } float num = (float)levelField.GetValue(skill); float num2 = (float)accField.GetValue(skill); if (num >= 100f) { return; } float num3; if (nextReq != null) { try { num3 = (float)nextReq.Invoke(skill, null); } catch { num3 = Mathf.Pow(num + 1f, 1.5f) / 2f + 0.5f; } } else { num3 = Mathf.Pow(num + 1f, 1.5f) / 2f + 0.5f; } ((Character)player).RaiseSkill(skillType, amount); float num4 = (float)levelField.GetValue(skill); float num5 = (float)accField.GetValue(skill); if (num4 <= num) { return; } float num6 = amount - (num3 - num2); for (float num7 = num + 1f; num7 < num4; num7 += 1f) { if (!(num6 > 0f)) { break; } float num8 = ReqFor(num7); num6 -= num8; } levelField.SetValue(skill, (num4 >= 100f) ? 100f : num4); if (num4 >= 100f) { accField.SetValue(skill, 0f); } else { accField.SetValue(skill, Mathf.Max(0f, num6)); } float ReqFor(float level) { levelField.SetValue(skill, level); accField.SetValue(skill, 0f); if (nextReq != null) { try { return (float)nextReq.Invoke(skill, null); } catch { return Mathf.Pow(level + 1f, 1.5f) / 2f + 0.5f; } } return Mathf.Pow(level + 1f, 1.5f) / 2f + 0.5f; } } private static string FormatXpLabel(float amount) { if (amount >= 10f || Mathf.Abs(amount - Mathf.Round(amount)) < 0.05f) { return $"+{Mathf.RoundToInt(amount)}xp"; } return $"+{amount:F1}xp"; } private static void ShowFloatingXp(Vector3 pos, float amount) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) try { if (!((Object)(object)DamageText.instance == (Object)null)) { Vector3 val = pos + Random.insideUnitSphere * 0.15f; val.y = pos.y; string text = FormatXpLabel(amount); DamageText.instance.ShowText((TextType)7, val, text, true); } } catch { try { if ((Object)(object)DamageText.instance != (Object)null) { string text2 = FormatXpLabel(amount); DamageText.instance.ShowText((TextType)7, pos, text2, true); } } catch { } } } [HarmonyPrefix] [HarmonyPatch(typeof(Skills), "RaiseSkill")] private static void RaiseSkill_Prefix(Skills __instance, SkillType skillType, ref float factor) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000e: 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) //IL_0030: 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) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Invalid comparison between Unknown and I4 //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Invalid comparison between Unknown and I4 //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Invalid comparison between Unknown and I4 //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Expected I4, but got Unknown //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Invalid comparison between Unknown and I4 _accBeforeRaise = ReadSkillAccumulator(__instance, skillType); _levelBeforeRaise = ReadSkillLevel(__instance, skillType); if (_internalAward) { _raiseFactorFinal = factor; return; } if ((int)skillType <= 6) { if ((int)skillType != 2) { if ((int)skillType == 6 && LogicalExperiencePlugin.IsEnabled(LogicalExperiencePlugin.ToggleBlock)) { factor *= 1.5f; } } else if (_suppressNextWeaponXp) { factor = 0f; _suppressNextWeaponXp = false; } } else if ((int)skillType != 14) { switch (skillType - 100) { case 10: if (LogicalExperiencePlugin.IsEnabled(LogicalExperiencePlugin.ToggleRide)) { factor *= 2f; } break; case 3: if (LogicalExperiencePlugin.IsEnabled(LogicalExperiencePlugin.ToggleSwim)) { factor *= 2f; } break; case 8: if (LogicalExperiencePlugin.IsEnabled(LogicalExperiencePlugin.ToggleDodge)) { factor *= 10f; } break; case 5: if (LogicalExperiencePlugin.IsEnabled(LogicalExperiencePlugin.ToggleCooking)) { factor *= 4f; } break; case 7: if (LogicalExperiencePlugin.IsEnabled(LogicalExperiencePlugin.ToggleCrafting)) { factor *= 4f; } break; case 6: if (LogicalExperiencePlugin.IsEnabled(LogicalExperiencePlugin.ToggleFarming)) { factor *= 2f; } break; case 4: if (LogicalExperiencePlugin.IsEnabled(LogicalExperiencePlugin.ToggleFishing)) { factor *= 2f; } break; } } else if (LogicalExperiencePlugin.IsEnabled(LogicalExperiencePlugin.ToggleCrossbow)) { factor *= 2f; } _raiseFactorFinal = factor; } [HarmonyPostfix] [HarmonyPatch(typeof(Skills), "RaiseSkill")] private static void RaiseSkill_ShowFloat(Skills __instance, SkillType skillType) { //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Invalid comparison between Unknown and I4 //IL_00cf: Unknown result type (might be due to invalid IL or missing references) if (_internalAward) { return; } Player val = null; if ((Object)(object)__instance != (Object)null) { FieldInfo fieldInfo = AccessTools.Field(typeof(Skills), "m_player"); if (fieldInfo != null) { object? value = fieldInfo.GetValue(__instance); val = (Player)((value is Player) ? value : null); } if ((Object)(object)val == (Object)null) { val = ((Component)__instance).GetComponent<Player>(); } } if ((Object)(object)val == (Object)null || !((Character)val).IsOwner()) { return; } RestoreOverflowAfterRaise(__instance, skillType, _raiseFactorFinal, _accBeforeRaise); float num = ReadSkillAccumulator(__instance, skillType); float num2 = num - _accBeforeRaise; if (num2 <= 0.001f) { num2 = _raiseFactorFinal; } if (num2 > 0.001f) { NotifyXpTopLeft(__instance, val, skillType, num2); } if ((int)skillType == 100 && _raiseFactorFinal > 0f && LogicalExperiencePlugin.IsEnabled(LogicalExperiencePlugin.ToggleJump)) { float num3 = 0f; try { num3 = ((Humanoid)val).GetInventory().GetTotalWeight(); } catch { } float num4 = 1000f; float num5 = 3f; float num6 = Mathf.Lerp(0f, num5, Mathf.Clamp01(num3 / num4)); if (num6 > 0.01f) { Award(val, (SkillType)100, num6, $"jump weight +{num6:F2}"); } } } private static void RestoreOverflowAfterRaise(Skills skills, SkillType skillType, float amount, float accBefore) { //IL_0062: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)skills == (Object)null || amount <= 0f) { return; } try { MethodInfo methodInfo = AccessTools.Method(typeof(Skills), "GetSkill", new Type[1] { typeof(SkillType) }, (Type[])null); Skill val = (Skill)((methodInfo != null) ? /*isinst with value type is only supported in some contexts*/: null); FieldInfo fieldInfo = AccessTools.Field(typeof(Skill), "m_accumulator"); FieldInfo fieldInfo2 = AccessTools.Field(typeof(Skill), "m_level"); if (val == null || fieldInfo == null || fieldInfo2 == null) { return; } float num = (float)fieldInfo2.GetValue(val); if (num <= _levelBeforeRaise) { return; } float num2 = accBefore + amount; for (float num3 = _levelBeforeRaise; num3 < num; num3 += 1f) { float num4 = Mathf.Pow(num3 + 1f, 1.5f) / 2f + 0.5f; num2 -= num4; } float num5 = num; int num6 = 0; for (; num5 < 100f; num5 += 1f) { if (!(num2 > 0f)) { break; } if (num6++ >= 100) { break; } float num7 = Mathf.Pow(num5 + 1f, 1.5f) / 2f + 0.5f; if (num2 < num7) { break; } num2 -= num7; } fieldInfo2.SetValue(val, Mathf.Min(100f, Mathf.Max(num, num5))); fieldInfo.SetValue(val, (num5 >= 100f) ? 0f : Mathf.Max(0f, num2)); } catch { } } private static float ReadSkillAccumulator(Skills skills, SkillType skillType) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)skills == (Object)null) { return 0f; } try { MethodInfo methodInfo = AccessTools.Method(typeof(Skills), "GetSkill", new Type[1] { typeof(SkillType) }, (Type[])null); Skill val = (Skill)((methodInfo != null) ? /*isinst with value type is only supported in some contexts*/: null); FieldInfo fieldInfo = AccessTools.Field(typeof(Skill), "m_accumulator"); if (val != null && fieldInfo != null) { return (float)fieldInfo.GetValue(val); } } catch { } return 0f; } private static float ReadSkillLevel(Skills skills, SkillType skillType) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)skills == (Object)null) { return 0f; } try { MethodInfo methodInfo = AccessTools.Method(typeof(Skills), "GetSkill", new Type[1] { typeof(SkillType) }, (Type[])null); Skill val = (Skill)((methodInfo != null) ? /*isinst with value type is only supported in some contexts*/: null); FieldInfo fieldInfo = AccessTools.Field(typeof(Skill), "m_level"); if (val != null && fieldInfo != null) { return (float)fieldInfo.GetValue(val); } } catch { } return 0f; } private static void NotifyXpTopLeft(Skills skills, Player player, SkillType skillType, float gained) { //IL_020e: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) if (LogicalExperiencePlugin.ShowXpTopLeft == null || !LogicalExperiencePlugin.ShowXpTopLeft.Value || (Object)(object)player == (Object)null || gained <= 0.001f) { return; } string arg = ""; try { MethodInfo methodInfo = AccessTools.Method(typeof(Skills), "GetSkill", new Type[1] { typeof(SkillType) }, (Type[])null); Skill val = (Skill)((methodInfo != null) ? /*isinst with value type is only supported in some contexts*/: null); if (val != null) { FieldInfo fieldInfo = AccessTools.Field(typeof(Skill), "m_accumulator"); FieldInfo fieldInfo2 = AccessTools.Field(typeof(Skill), "m_level"); MethodInfo methodInfo2 = AccessTools.Method(typeof(Skill), "GetNextLevelRequirement", (Type[])null, (Type[])null); float num = ((fieldInfo != null) ? ((float)fieldInfo.GetValue(val)) : 0f); float num2 = ((fieldInfo2 != null) ? ((float)fieldInfo2.GetValue(val)) : 0f); float num3 = 0f; if (methodInfo2 != null) { try { num3 = (float)methodInfo2.Invoke(val, null); } catch { num3 = Mathf.Pow(num2 + 1f, 1.5f) / 2f + 0.5f; } } else { num3 = Mathf.Pow(num2 + 1f, 1.5f) / 2f + 0.5f; } arg = $" [{num:F2}/{num3:F2}] Lv {(int)num2}"; } } catch { } string arg2 = ((gained >= 10f || Mathf.Abs(gained - Mathf.Round(gained)) < 0.05f) ? gained.ToString("F0") : gained.ToString("F2")); try { ((Character)player).Message((MessageType)1, $"{skillType} +{arg2}{arg}", 0, (Sprite)null, false); } catch { } } [HarmonyPrefix] [HarmonyPatch(typeof(Character), "Damage")] private static void Character_Damage_Backstab(Character __instance, HitData hit) { //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) if (hit == null) { return; } Character attacker = hit.GetAttacker(); Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val == null || !((Character)val).IsOwner() || (Object)(object)__instance == (Object)null || __instance.IsDead() || __instance.IsPlayer() || hit.m_backstabBonus <= 1.01f) { return; } BaseAI val2 = null; try { val2 = __instance.GetBaseAI(); } catch { } if ((Object)(object)val2 == (Object)null) { val2 = ((Component)__instance).GetComponent<BaseAI>(); } if (!((Object)(object)val2 == (Object)null)) { bool flag = true; try { flag = val2.IsAlerted(); } catch { return; } if (!flag && LogicalExperiencePlugin.IsEnabled(LogicalExperiencePlugin.ToggleSneak)) { Vector3 value = ((Component)__instance).transform.position + Vector3.up * 1.2f; Award(val, (SkillType)101, 3f, "backstab", value); } } } [HarmonyPrefix] [HarmonyPatch(typeof(Character), "Damage")] private static void Character_Damage_TrackPrefix(Character __instance, HitData hit) { _preHitHealth = 0f; if (!((Object)(object)__instance == (Object)null) && hit != null) { _preHitHealth = __instance.GetHealth(); } } [HarmonyPostfix] [HarmonyPatch(typeof(Character), "Damage")] private static void Character_Damage_Track(Character __instance, HitData hit) { //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Invalid comparison between Unknown and I4 //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Invalid comparison between Unknown and I4 //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Invalid comparison between Unknown and I4 //IL_01c1: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Invalid comparison between Unknown and I4 //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_023f: Unknown result type (might be due to invalid IL or missing references) //IL_0237: Unknown result type (might be due to invalid IL or missing references) //IL_023c: Unknown result type (might be due to invalid IL or missing references) if (hit == null || (Object)(object)__instance == (Object)null || __instance.IsPlayer()) { return; } Character attacker = hit.GetAttacker(); Player val = (Player)(object)((attacker is Player) ? attacker : null); bool flag = false; try { flag = hit.m_damage.m_fire > 0f; } catch { } if ((Object)(object)val == (Object)null || !((Character)val).IsOwner()) { if (!((Object)(object)attacker != (Object)null) || attacker.IsPlayer()) { return; } Player summonOwner = GetSummonOwner(attacker); if (!((Object)(object)summonOwner != (Object)null) || !((Character)summonOwner).IsOwner() || !LogicalExperiencePlugin.IsEnabled(LogicalExperiencePlugin.ToggleBlood) || !LogicalExperiencePlugin.IsEnabled(LogicalExperiencePlugin.ToggleKillXp)) { return; } bool flag2 = _preHitHealth > 0f; bool flag3 = __instance.IsDead() || __instance.GetHealth() <= 0f; if (flag2 && flag3) { SkillType bloodMagicSkill = GetBloodMagicSkill(); if ((int)bloodMagicSkill > 0) { GiveKillBonus(__instance, summonOwner, bloodMagicSkill); } } return; } SkillType val2 = (SkillType)0; ItemData val3 = null; try { val3 = ((Humanoid)val).GetCurrentWeapon(); } catch { } if (val3?.m_shared != null) { val2 = val3.m_shared.m_skillType; } bool flag4 = IsStaffOfEmbers(val3); if ((int)val2 == 0 && flag4) { val2 = GetElementalMagicSkill(); } if ((int)val2 == 0) { val2 = (SkillType)11; } if (IsNonHostileTarget(__instance, val)) { if ((int)val2 == 2) { _suppressNextWeaponXp = true; } return; } RecordLastHit(__instance, val, val2); if (LogicalExperiencePlugin.IsEnabled(LogicalExperiencePlugin.ToggleEmbers) && (flag4 || (IsElementalSkill(val2) && IsStaffOfEmbers(val3)))) { RecordEmberIgnite(__instance, val); } bool flag5 = _preHitHealth > 0f; bool flag6 = __instance.IsDead() || __instance.GetHealth() <= 0f; if (flag5 && flag6) { if (flag4 || (flag && HasRecentEmberIgnite(__instance))) { val2 = GetElementalMagicSkill(); } GiveKillBonus(__instance, val, val2); } } private static bool IsStaffOfEmbers(ItemData weapon) { if (weapon == null) { return false; } string text = ""; try { if ((Object)(object)weapon.m_dropPrefab != (Object)null) { text = ((Object)weapon.m_dropPrefab).name ?? ""; } } catch { } if (string.IsNullOrEmpty(text)) { try { text = ((weapon.m_shared != null) ? (weapon.m_shared.m_name ?? "") : ""); } catch { return false; } } text = text.Replace("$item_", "").Replace("(Clone)", "").ToLowerInvariant(); if (text.Contains("torch") || text.Contains("campfire") || text.Contains("hearth")) { return false; } if (text.Contains("stafffireball") || text == "stafffireball") { return true; } if (text.Contains("staff") && (text.Contains("ember") || text.Contains("fireball") || text.Contains("fire"))) { return true; } if (text.Contains("staff") && text.Contains("fire")) { return true; } return false; } private static SkillType GetElementalMagicSkill() { //IL_0040: 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_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) try { return (SkillType)Enum.Parse(typeof(SkillType), "ElementalMagic"); } catch { try { return (SkillType)Enum.Parse(typeof(SkillType), "Elemental magic"); } catch { return (SkillType)0; } } } private static Player GetSummonOwner(Character minion) { if ((Object)(object)minion == (Object)null || minion.IsPlayer()) { return null; } try { ZDO val = null; FieldInfo fieldInfo = AccessTools.Field(typeof(Character), "m_nview"); if (fieldInfo != null) { object? value = fieldInfo.GetValue(minion); ZNetView val2 = (ZNetView)((value is ZNetView) ? value : null); if ((Object)(object)val2 != (Object)null && val2.IsValid()) { val = val2.GetZDO(); } } if (val == null) { ZNetView component = ((Component)minion).GetComponent<ZNetView>(); if ((Object)(object)component != (Object)null && component.IsValid()) { val = component.GetZDO(); } } if (val == null) { return null; } string[] array = new string[3] { "owner", "tamer", "spawn_master" }; foreach (string text in array) { long num = 0L; try { num = val.GetLong(text, 0L); } catch { continue; } if (num == 0) { continue; } try { foreach (Player allPlayer in Player.GetAllPlayers()) { if ((Object)(object)allPlayer != (Object)null && allPlayer.GetPlayerID() == num) { return allPlayer; } } } catch { } } } catch { } try { if (!minion.IsTamed() || (Object)(object)Player.m_localPlayer == (Object)null) { return null; } string text2 = (((Object)minion).name ?? "").Replace("(Clone)", "").ToLowerInvariant(); if (text2.Contains("boar") || text2.Contains("wolf") || text2.Contains("lox") || text2.Contains("asksvin") || text2.Contains("chicken") || text2.Contains("neck") || text2.Contains("hare") || text2.Contains("deer") || text2.Contains("ship") || text2.Contains("cart") || text2.Contains("bjorn")) { return null; } if (text2.Contains("skeleton") || text2.Contains("spirit") || text2.Contains("ghost") || text2.Contains("wraith") || (text2.Contains("troll") && text2.Contains("summon"))) { return Player.m_localPlayer; } } catch { } return null; } private static void RecordEmberIgnite(Character target, Player player) { //IL_001d: 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) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Invalid comparison between Unknown and I4 //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)target == (Object)null || (Object)(object)player == (Object)null) { return; } SkillType elementalMagicSkill = GetElementalMagicSkill(); if ((int)elementalMagicSkill != 0) { ZDOID zDOID = target.GetZDOID(); if (!EmberIgnite.TryGetValue(zDOID, out var value) || !(Time.time - value.Time <= 12f)) { EmberIgnite[zDOID] = new KillInfo { Player = player, Skill = elementalMagicSkill, Time = Time.time }; LastHit[zDOID] = EmberIgnite[zDOID]; } } } private static bool HasRecentEmberIgnite(Character target) { //IL_0011: 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_001c: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)target == (Object)null) { return false; } ZDOID zDOID = target.GetZDOID(); if (!EmberIgnite.TryGetValue(zDOID, out var value)) { return false; } return Time.time - value.Time <= 12f; } private static void PollEmberBurnXp(Player player) { //IL_0176: Unknown result type (might be due to invalid IL or missing references) //IL_0193: 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) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Invalid comparison between Unknown and I4 //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || !((Character)player).IsOwner() || !LogicalExperiencePlugin.IsEnabled(LogicalExperiencePlugin.ToggleEmbers) || EmberIgnite.Count == 0 || Time.time < _nextEmberBurnPoll) { return; } _nextEmberBurnPoll = Time.time + 1f; SkillType elementalMagicSkill = GetElementalMagicSkill(); if ((int)elementalMagicSkill == 0) { return; } List<ZDOID> list = new List<ZDOID>(EmberIgnite.Keys); foreach (ZDOID item in list) { if (!EmberIgnite.TryGetValue(item, out var value) || (Object)(object)value.Player != (Object)(object)player) { continue; } if (Time.time - value.Time > 12f) { EmberIgnite.Remove(item); continue; } Character val = null; try { if ((Object)(object)ZNetScene.instance != (Object)null) { GameObject val2 = ZNetScene.instance.FindInstance(item); if ((Object)(object)val2 != (Object)null) { val = val2.GetComponent<Character>(); } } } catch { } if ((Object)(object)val == (Object)null || val.IsDead()) { EmberIgnite.Remove(item); } else if (CharacterHasBurning(val)) { Award(player, elementalMagicSkill, 0.1f, "embers burn"); } } } private static bool IsElementalSkill(SkillType skill) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: 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_000b: Unknown result type (might be due to invalid IL or missing references) SkillType elementalMagicSkill = GetElementalMagicSkill(); return (int)elementalMagicSkill != 0 && skill == elementalMagicSkill; } private static bool CharacterHasBurning(Character c) { if ((Object)(object)c == (Object)null) { return false; } try { SEMan sEMan = c.GetSEMan(); if (sEMan == null) { return false; } FieldInfo fieldInfo = AccessTools.Field(typeof(SEMan), "m_statusEffects"); if (fieldInfo == null) { return false; } if (!(fieldInfo.GetValue(sEMan) is IEnumerable enumerable)) { return false; } foreach (object item in enumerable) { if (item == null) { continue; } string text = item.GetType().Name; try { FieldInfo fieldInfo2 = AccessTools.Field(item.GetType(), "m_name"); if (fieldInfo2 != null) { object value = fieldInfo2.GetValue(item); if (value != null) { text = text + " " + value; } } } catch { } if (text.IndexOf("burn", StringComparison.OrdinalIgnoreCase) < 0) { continue; } return true; } } catch { } return false; } [HarmonyPostfix] [HarmonyPatch(typeof(Projectile), "OnHit")] private static void Projectile_OnHit_Embers(Projectile __instance, Collider collider, Vector3 hitPoint, bool water) { //IL_01c6: Unknown result type (might be due to invalid IL or missing references) //IL_01cb: Unknown result type (might be due to invalid IL or missing references) //IL_01cd: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: Invalid comparison between Unknown and I4 //IL_01db: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null || (Object)(object)collider == (Object)null || water) { return; } Player val = null; try { FieldInfo fieldInfo = AccessTools.Field(typeof(Projectile), "m_owner"); if (fieldInfo != null) { object value = fieldInfo.GetValue(__instance); Player val2 = (Player)((value is Player) ? value : null); if (val2 != null) { val = val2; } else { Character val3 = (Character)((value is Character) ? value : null); if (val3 != null) { val = (Player)(object)((val3 is Player) ? val3 : null); } } } } catch { } if ((Object)(object)val == (Object)null) { try { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null && ((Character)localPlayer).IsOwner() && IsStaffOfEmbers(((Humanoid)localPlayer).GetCurrentWeapon())) { val = localPlayer; } } catch { } } if ((Object)(object)val == (Object)null || !((Character)val).IsOwner()) { return; } bool flag = IsStaffOfEmbers(((Humanoid)val).GetCurrentWeapon()); string text = ""; try { text = ((Object)__instance).name ?? ""; } catch { } text = text.Replace("(Clone)", "").ToLowerInvariant(); bool flag2 = text.Contains("fireball") || text.Contains("ember") || text.Contains("stafffire") || text.Contains("fire_ball"); if (!flag && !flag2) { return; } Character componentInParent = ((Component)collider).GetComponentInParent<Character>(); if (!((Object)(object)componentInParent == (Object)null) && !componentInParent.IsPlayer() && LogicalExperiencePlugin.IsEnabled(LogicalExperiencePlugin.ToggleEmbers)) { RecordEmberIgnite(componentInParent, val); SkillType elementalMagicSkill = GetElementalMagicSkill(); if ((int)elementalMagicSkill > 0) { RecordLastHit(componentInParent, val, elementalMagicSkill); } } } private static bool IsNonHostileTarget(Character victim, Player player) { if ((Object)(object)victim == (Object)null) { return true; } try { if (victim.IsTamed()) { return true; } } catch { } return false; } private static void RecordLastHit(Character target, Player player, SkillType skill) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)target == (Object)null) && !((Object)(object)player == (Object)null)) { LastHit[target.GetZDOID()] = new KillInfo { Player = player, Skill = skill, Time = Time.time }; } } private static void GiveKillBonus(Character victim, Player player, SkillType skill) { //IL_01db: Unknown result type (might be due to invalid IL or missing references) //IL_01f7: Unknown result type (might be due to invalid IL or missing references) //IL_0204: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Unknown result type (might be due to invalid IL or missing references) //IL_003c: 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) //IL_021a: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || !LogicalExperiencePlugin.IsEnabled(LogicalExperiencePlugin.ToggleKillXp)) { return; } ZDOID val = (((Object)(object)victim != (Object)null) ? victim.GetZDOID() : ZDOID.None); if (val != ZDOID.None && RecentKills.TryGetValue(val, out var value) && Time.time - value < 3f) { return; } int num = 0; if ((Object)(object)victim != (Object)null) { num = ((Object)victim).GetInstanceID(); if (RecentKillInstances.TryGetValue(num, out var value2) && Time.time - value2 < 3f) { return; } RecentKillInstances[num] = Time.time; } if (val != ZDOID.None) { RecentKills[val] = Time.time; } float killBaseXp = XpData.GetKillBaseXp(victim); int num2 = Mathf.Max(0, victim.GetLevel() - 1); float num3 = 1f; if (num2 >= 1) { num3 *= 2f; } if (num2 >= 2) { num3 *= 2f; } float num4 = killBaseXp * num3 * 1f; if (num4 <= 0f) { return; } Vector3 value3 = ((Component)player).transform.position + Vector3.up * 1.5f; string arg = "enemy"; if ((Object)(object)victim != (Object)null) { try { value3 = ((Component)victim).transform.position + Vector3.up * 1.2f; arg = ((Object)victim).name.Replace("(Clone)", ""); } catch { } } Award(player, skill, num4, $"KILL {arg} base={killBaseXp:F1} x{num3}", value3); if (val != ZDOID.None) { LastHit.Remove(val); } } [HarmonyPostfix] [HarmonyPatch(typeof(Character), "OnDeath")] private static void Character_OnDeath(Character __instance) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: 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_0063: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)__instance == (Object)null) && !__instance.IsPlayer()) { ZDOID zDOID = __instance.GetZDOID(); if (LastHit.TryGetValue(zDOID, out var value) && (Object)(object)value.Player != (Object)null && Time.time - value.Time <= 8f) { GiveKillBonus(__instance, value.Player, value.Skill); } } } [HarmonyPostfix] [HarmonyPatch(typeof(TreeBase), "Damage")] private static void TreeBase_Damage(TreeBase __instance, HitData hit) { AwardWoodBonus(((Object)(object)__instance != (Object)null) ? ((Object)__instance).name : null, hit); } [HarmonyPostfix] [HarmonyPatch(typeof(TreeLog), "Damage")] private static void TreeLog_Damage(TreeLog __instance, HitData hit) { AwardWoodBonus(((Object)(object)__instance != (Object)null) ? ((Object)__instance).name : null, hit); } private static void AwardWoodBonus(string prefabName, HitData hit) { if (hit == null || string.IsNullOrEmpty(prefabName)) { return; } Character attacker = hit.GetAttacker(); Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val != null && ((Character)val).IsOwner() && LogicalExperiencePlugin.IsEnabled(LogicalExperiencePlugin.ToggleWood)) { float treeXp = XpData.GetTreeXp(prefabName); if (treeXp > 0.01f) { Award(val, (SkillType)13, treeXp, "tree " + prefabName); } } } [HarmonyPostfix] [HarmonyPatch(typeof(MineRock5), "Damage")] private static void MineRock5_Damage(MineRock5 __instance, HitData hit) { if (!((Object)(object)__instance == (Object)null)) { AwardMineBonus((Component)(object)__instance, hit); } } [HarmonyPostfix] [HarmonyPatch(typeof(MineRock), "Damage")] private static void MineRock_Damage(MineRock __instance, HitData hit) { if (!((Object)(object)__instance == (Object)null)) { AwardMineBonus((Component)(object)__instance, hit); } } [HarmonyPostfix] [HarmonyPatch(typeof(Destructible), "Damage")] private static void Destructible_Damage(Destructible __instance, HitData hit) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Invalid comparison between Unknown and I4 if ((Object)(object)__instance == (Object)null || hit == null) { return; } Character attacker = hit.GetAttacker(); Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val != null) { ItemData currentWeapon = ((Humanoid)val).GetCurrentWeapon(); if (currentWeapon?.m_shared != null && (int)currentWeapon.m_shared.m_skillType == 12) { AwardMineBonus((Component)(object)__instance, hit); } } } private static void AwardMineBonus(Component component, HitData hit) { //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Invalid comparison between Unknown and I4 if (hit == null || Time.time - _lastMineBonusTime < 0.05f) { return; } Character attacker = hit.GetAttacker(); Player val = (Player)(object)((attacker is Player) ? attacker : null); if ((Object)(object)val == (Object)null) { try { val = Player.m_localPlayer; } catch { } } if ((Object)(object)val == (Object)null || !((Character)val).IsOwner()) { return; } ItemData val2 = null; try { val2 = ((Humanoid)val).GetCurrentWeapon(); } catch { } if (val2?.m_shared != null && (int)val2.m_shared.m_skillType == 12 && LogicalExperiencePlugin.IsEnabled(LogicalExperiencePlugin.TogglePickaxe)) { float pickaxeTierBonus = GetPickaxeTierBonus(val2); if (!(pickaxeTierBonus <= 0.01f)) { _lastMineBonusTime = Time.time; Award(val, (SkillType)12, pickaxeTierBonus, $"pickaxe tier +{pickaxeTierBonus:F0}"); } } } private static float GetPickaxeTierBonus(ItemData weapon) { if (weapon == null) { return 0f; } try { int toolTier = weapon.m_shared.m_toolTier; if (toolTier <= 0) { return 0f; } return toolTier switch { 1 => 1f, 2 => 2f, _ => 3f, }; } catch { } string text = ""; try { if ((Object)(object)weapon.m_dropPrefab != (Object)null) { text = ((Object)weapon.m_dropPrefab).name; } else if (weapon.m_shared != null) { text = weapon.m_shared.m_name ?? ""; } } catch { } text = text.Replace("$item_", "").Replace("(Clone)", "").ToLowerInvariant(); if (text.Contains("antler")) { return 0f; } if (text.Contains("bronze")) { return 1f; } if (text.Contains("iron") && !text.Contains("black")) { return 2f; } if (text.Contains("black") || text.Contains("flametal") || text.Contains("blood")) { return 3f; } return 0f; } [HarmonyPostfix] [HarmonyPatch(typeof(InventoryGui), "DoCrafting")] private static void InventoryGui_DoCrafting(InventoryGui __instance, Player player) { //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Invalid comparison between Unknown and I4 if ((Object)(object)player == (Object)null || !((Character)player).IsOwner() || (Object)(object)__instance == (Object)null) { return; } object? obj = AccessTools.Field(typeof(InventoryGui), "m_craftRecipe")?.GetValue(__instance); Recipe val = (Recipe)((obj is Recipe) ? obj : null); if ((Object)(object)val == (Object)null) { return; } bool flag = false; if ((Object)(object)val.m_item != (Object)null) { SharedData val2 = val.m_item.m_itemData?.m_shared; if (val2 != null) { flag = (int)val2.m_itemType == 2 || val2.m_food > 0f || val2.m_foodStamina > 0f || val2.m_foodEitr > 0f; } } string craftingStationKey = GetCraftingStationKey(player, val); bool flag2 = IsCookingStationName(craftingStationKey); if (flag || flag2) { if (LogicalExperiencePlugin.IsEnabled(LogicalExperiencePlugin.ToggleCooking)) { float cookingStationBonus = GetCookingStationBonus(craftingStationKey); if (cookingStationBonus > 0.01f) { Award(player, (SkillType)105, cookingStationBonus, $"cook station +{cookingStationBonus:F0}"); } } } else if (LogicalExperiencePlugin.IsEnabled(LogicalExperiencePlugin.ToggleCrafting)) { float craftingStationBonus = GetCraftingStationBonus(craftingStationKey); if (craftingStationBonus > 0.01f) { Award(player, (SkillType)107, craftingStationBonus, $"craft station +{craftingStationBonus:F0}"); } } } private static string GetCraftingStationKey(Player player, Recipe recipe) { try { CraftingStation currentCraftingStation = player.GetCurrentCraftingStation(); if ((Object)(object)currentCraftingStation != (Object)null) { string text = ""; try { text = (((Object)(object)((Component)currentCraftingStation).gameObject != (Object)null) ? ((Object)((Component)currentCraftingStation).gameObject).name : ""); } catch { } try { text = text + " " + ((Object)currentCraftingStation).name; } catch { } try { if (currentCraftingStation.m_name != null) { text = text + " " + currentCraftingStation.m_name; } } catch { } try { string prefabName = Utils.GetPrefabName(((Component)currentCraftingStation).gameObject); if (!string.IsNullOrEmpty(prefabName)) { text = text + " " + prefabName; } } catch { } if (!string.IsNullOrEmpty(text.Trim())) { return text.ToLowerInvariant(); } } } catch { } try { if ((Object)(object)recipe?.m_craftingStation != (Object)null) { CraftingStation craftingStation = recipe.m_craftingStation; string text2 = ""; try { text2 = (((Object)(object)((Component)craftingStation).gameObject != (Object)null) ? ((Object)((Component)craftingStation).gameObject).name : ""); } catch { } try { text2 = text2 + " " + ((Object)craftingStation).name; } catch { } try { if (craftingStation.m_name != null) { text2 = text2 + " " + craftingStation.m_name; } } catch { } return text2.ToLowerInvariant(); } } catch { } return ""; } private static float GetCraftingStationBonus(string key) { if (string.IsNullOrEmpty(key)) { return 1f; } if (key.Contains("galdr") || key.Contains("blackforge") || key.Contains("black_forge") || key.Contains("magetable") || key.Contains("mage_table")) { return 3f; } if (key.Contains("forge") || key.Contains("artisan")) { return 2f; } if (key.Contains("workbench") || key.Contains("piece_workbench") || key.Contains("craftingstation")) { return 1f; } if (key.Contains("stonecutter")) { return 1f; } if (key.Contains("cauldron") || key.Contains("mead") || key.Contains("foodprep")) { return 0f; } return 1f; } private static bool IsCookingStationName(string key) { if (string.IsNullOrEmpty(key)) { return false; } return key.Contains("mead") || key.Contains("cauldron") || key.Contains("foodprep") || key.Contains("food_prep") || key.Contains("preparation") || key.Contains("prep_table") || key.Contains("preptable"); } private static float GetCookingStationBonus(string key) { if (string.IsNullOrEmpty(key)) { return 0f; } if (key.Contains("mead")) { return 5f; } if (key.Contains("cauldron") || key.Contains("foodprep") || key.Contains("food_prep") || key.Contains("preparation") || key.Contains("prep_table") || key.Contains("preptable")) { return 1f; } return 0f; } [HarmonyPostfix] [HarmonyPatch(typeof(ItemDrop), "Pickup")] private static void ItemDrop_Pickup(ItemDrop __instance, Humanoid character) { Player val = (Player)(object)((character is Player) ? character : null); if (val != null && ((Character)val).IsOwner() && __instance?.m_itemData?.m_shared != null) { string name = ((Object)__instance).name; if ((name.StartsWith("Fish", StringComparison.OrdinalIgnoreCase) || name.IndexOf("Fish", StringComparison.OrdinalIgnoreCase) >= 0) && LogicalExperiencePlugin.IsEnabled(LogicalExperiencePlugin.ToggleFishing)) { float fishRarity = XpData.GetFishRarity(name); float amount = fishRarity; Award(val, (SkillType)104, amount, $"fish {name} rarity={fishRarity:F2}"); } } } [HarmonyPostfix] [HarmonyPatch(typeof(Player), "Update")] private static void Player_Update_CrossbowReload(Player __instance) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Invalid comparison between Unknown and I4 if ((Object)(object)__instance == (Object)null || !((Character)__instance).IsOwner()) { return; } PollEmberBurnXp(__instance); ItemData val = null; try { val = ((Humanoid)__instance).GetCurrentWeapon(); } catch { return; } if (val?.m_shared == null || (int)val.m_shared.m_skillType != 14) { return; } bool flag = false; try { flag = ((Humanoid)__instance).IsWeaponLoaded(); } catch { try { FieldInfo fieldInfo = AccessTools.Field(typeof(ItemData), "m_loaded"); if (fieldInfo != null) { flag = (bool)fieldInfo.GetValue(val); } } catch { return; } } long playerID = __instance.GetPlayerID(); bool value; bool flag2 = CrossbowWasLoaded.TryGetValue(playerID, out value) && value; CrossbowWasLoaded[playerID] = flag; if (flag && !flag2 && LogicalExperiencePlugin.IsEnabled(LogicalExperiencePlugin.ToggleCrossbow)) { Award(__instance, (SkillType)14, 1f, "crossbow reload"); } } private static bool IsBloodStaff(ItemData weapon) { if (weapon == null) { return false; } string text = null; try { if ((Object)(object)weapon.m_dropPrefab != (Object)null) { text = ((Object)weapon.m_dropPrefab).name; } } catch { } if (string.IsNullOrEmpty(text)) { try { text = ((weapon.m_shared != null) ? weapon.m_shared.m_name : null); } catch { return false; } } if (string.IsNullOrEmpty(text)) { return false; } string text2 = text.Replace("$item_", "").Replace("(Clone)", "").Trim(); if (BloodStaffPrefabs.Contains(text2)) { return true; } foreach (string bloodStaffPrefab in BloodStaffPrefabs) { if (text2.IndexOf(bloodStaffPrefab, StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } return false; } private static SkillType GetBloodMagicSkill() { //IL_0040: 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_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) try { return (SkillType)Enum.Parse(typeof(SkillType), "BloodMagic"); } catch { try { return (SkillType)Enum.Parse(typeof(SkillType), "Blood magic"); } catch { return (SkillType)0; } } } private static void TryAwardBloodStaffCast(Player player) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Invalid comparison between Unknown and I4 //IL_007a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || !((Character)player).IsOwner() || Time.time - _lastBloodStaffXpTime < 1f || !IsBloodStaff(((Humanoid)player).GetCurrentWeapon())) { return; } SkillType bloodMagicSkill = GetBloodMagicSkill(); if ((int)bloodMagicSkill != 0) { _lastBloodStaffXpTime = Time.time; if (LogicalExperiencePlugin.IsEnabled(LogicalExperiencePlugin.ToggleBlood)) { Award(player, bloodMagicSkill, 3f, "blood staff cast"); } } } [HarmonyPostfix] [HarmonyPatch(typeof(Player), "Update")] private static void Player_Update_BloodStaffEitr(Player __instance) { if ((Object)(object)__instance == (Object)null || !((Character)__instance).IsOwner()) { return; } if (!IsBloodStaff(((Humanoid)__instance).GetCurrentWeapon())) { _prevEitr = -1f; return; } float num = 0f; try { num = __instance.GetEitr(); } catch { try { FieldInfo fieldInfo = AccessTools.Field(typeof(Player), "m_eitr"); if (fieldInfo != null) { num = (float)fieldInfo.GetValue(__instance); } } catch { return; } } if (_prevEitr < 0f) { _prevEitr = num; return; } float num2 = _prevEitr - num; _prevEitr = num; if (num2 >= 8f) { TryAwardBloodStaffCast(__instance); } } [HarmonyPostfix] [HarmonyPatch(typeof(Character), "UseEitr")] private static void Character_UseEitr_BloodStaff(Character __instance, float eitr) { if (!(eitr < 8f)) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (val != null) { TryAwardBloodStaffCast(val); } } } } } namespace ServerSync { [PublicAPI] public abstract class OwnConfigEntryBase { public object? LocalBaseValue; public bool SynchronizedConfig = true; public abstract ConfigEntryBase BaseConfig { get; } } [PublicAPI] public class SyncedConfigEntry<T> : OwnConfigEntryBase { public readonly ConfigEntry<T> SourceConfig; public override ConfigEntryBase BaseConfig => (ConfigEntryBase)(object)SourceConfig; public T Value { get { return SourceConfig.Value; } set { SourceConfig.Value = value; } } public SyncedConfigEntry(ConfigEntry<T> sourceConfig) { SourceConfig = sourceConfig; } public void AssignLocalValue(T value) { if (LocalBaseValue == null) { Value = value; } else { LocalBaseValue = value; } } } public abstract class CustomSyncedValueBase { public object? LocalBaseValue; public readonly string Identifier; public readonly Type Type; private object? boxedValue; protected bool localIsOwner; public readonly int Priority; public object? BoxedValue { get { return boxedValue; } set { boxedValue = value; this.ValueChanged?.Invoke(); } } public event Action? ValueChanged; protected CustomSyncedValueBase(ConfigSync configSync, string identifier, Type type, int priority) { Priority = priority; Identifier = identifier; Type = type; configSync.AddCustomValue(this); localIsOwner = configSync.IsSourceOfTruth; configSync.SourceOfTruthChanged += delegate(bool truth) { localIsOwner = truth; }; } } [PublicAPI] public sealed class CustomSyncedValue<T> : CustomSyncedValueBase { public T Value { get { return (T)base.BoxedValue; } set { base.BoxedValue = value; } } public CustomSyncedValue(ConfigSync configSync, string identifier, T value = default(T), int priority = 0) : base(configSync, identifier, typeof(T), priority) { Value = value; } public void AssignLocalValue(T value) { if (localIsOwner) { Value = value; } else { LocalBaseValue = value; } } } internal class ConfigurationManagerAttributes { [UsedImplicitly] public bool? ReadOnly = false; } [PublicAPI] public class ConfigSync { [HarmonyPatch(typeof(ZRpc), "HandlePackage")] private static class SnatchCurrentlyHandlingRPC { public static ZRpc? currentRpc; [HarmonyPrefix] private static void Prefix(ZRpc __instance) { currentRpc = __instance; } } [HarmonyPatch(typeof(ZNet), "Awake")] internal static class RegisterRPCPatch { [HarmonyPostfix] private static void Postfix(ZNet __instance) { isServer = __instance.IsServer(); foreach (ConfigSync configSync2 in configSyncs) { ZRoutedRpc.instance.Register<ZPackage>(configSync2.Name + " ConfigSync", (Action<long, ZPackage>)configSync2.RPC_FromOtherClientConfigSync); if (isServer) { configSync2.InitialSyncDone = true; Debug.Log((object)("Registered '" + configSync2.Name + " ConfigSync' RPC - waiting for incoming connections")); } } if (isServer) { ((MonoBehaviour)__instance).StartCoroutine(WatchAdminListChanges()); } static void SendAdmin(List<ZNetPeer> peers, bool isAdmin) { ZPackage package = ConfigsToPackage(null, null, new PackageEntry[1] { new PackageEntry { section = "Internal", key = "lockexempt", type = typeof(bool), value = isAdmin } }); ConfigSync configSync = configSyncs.First(); if (configSync != null) { ((MonoBehaviour)ZNet.instance).StartCoroutine(configSync.sendZPackage(peers, package)); } } static IEnumerator WatchAdminListChanges() { MethodInfo listContainsId = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList adminList = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); List<string> CurrentList = new List<string>(adminList.GetList()); while (true) { yield return (object)new WaitForSeconds(30f); if (!adminList.GetList().SequenceEqual(CurrentList)) { CurrentList = new List<string>(adminList.GetList()); List<ZNetPeer> adminPeer = ZNet.instance.GetPeers().Where(delegate(ZNetPeer p) { string hostName = p.m_rpc.GetSocket().GetHostName(); return ((object)listContainsId == null) ? adminList.Contains(hostName) : ((bool)listContainsId.Invoke(ZNet.instance, new object[2] { adminList, hostName })); }).ToList(); List<ZNetPeer> nonAdminPeer = ZNet.instance.GetPeers().Except(adminPeer).ToList(); SendAdmin(nonAdminPeer, isAdmin: false); SendAdmin(adminPeer, isAdmin: true); } } } } } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] private static class RegisterClientRPCPatch { [HarmonyPostfix] private static void Postfix(ZNet __instance, ZNetPeer peer) { if (__instance.IsServer()) { return; } foreach (ConfigSync configSync in configSyncs) { peer.m_rpc.Register<ZPackage>(configSync.Name + " ConfigSync", (Action<ZRpc, ZPackage>)configSync.RPC_FromServerConfigSync); } } } private class ParsedConfigs { public readonly Dictionary<OwnConfigEntryBase, object?> configValues = new Dictionary<OwnConfigEntryBase, object>(); public readonly Dictionary<CustomSyncedValueBase, object?> customValues = new Dictionary<CustomSyncedValueBase, object>(); } [HarmonyPatch(typeof(ZNet), "Shutdown")] private class ResetConfigsOnShutdown { [HarmonyPostfix] private static void Postfix() { ProcessingServerUpdate = true; foreach (ConfigSync configSync in configSyncs) { configSync.resetConfigsFromServer(); configSync.IsSourceOfTruth = true; configSync.InitialSyncDone = false; } ProcessingServerUpdate = false; } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] private class SendConfigsAfterLogin { private class BufferingSocket : ZPlayFabSocket, ISocket { public volatile bool finished = false; public volatile int versionMatchQueued = -1; public readonly List<ZPackage> Package = new List<ZPackage>(); public readonly ISocket Original; public BufferingSocket(ISocket original) { Original = original; } public bool IsConnected() { return Original.IsConnected(); } public ZPackage Recv() { return Original.Recv(); } public int GetSendQueueSize() { return Original.GetSendQueueSize(); } public int GetCurrentSendRate() { return Original.GetCurrentSendRate(); } public bool IsHost() { return Original.IsHost(); } public void Dispose() { Original.Dispose(); } public bool GotNewData() { return Original.GotNewData(); } public void Close() { Original.Close(); } public string GetEndPointString() { return Original.GetEndPointString(); } public void GetAndResetStats(out int totalSent, out int totalRecv) { Original.GetAndResetStats(ref totalSent, ref totalRecv); } public void GetConnectionQuality(out float localQuality, out float remoteQuality, out int ping, out float outByteSec, out float inByteSec) { Original.GetConnectionQuality(ref localQuality, ref remoteQuality, ref ping, ref outByteSec, ref inByteSec); } public ISocket Accept() { return Original.Accept(); } public int GetHostPort() { return Original.GetHostPort(); } public bool Flush() { return Original.Flush(); } public string GetHostName() { return Original.GetHostName(); } public void VersionMatch() { if (finished) { Original.VersionMatch(); } else { versionMatchQueued = Package.Count; } } public void Send(ZPackage pkg) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown int pos = pkg.GetPos(); pkg.SetPos(0); int num = pkg.ReadInt(); if ((num == StringExtensionMethods.GetStableHashCode("PeerInfo") || num == StringExtensionMethods.GetStableHashCode("RoutedRPC") || num == StringExtensionMethods.GetStableHashCode("ZDOData")) && !finished) { ZPackage val = new ZPackage(pkg.GetArray()); val.SetPos(pos); Package.Add(val); } else { pkg.SetPos(pos); Original.Send(pkg); } } } [HarmonyPriority(800)] [HarmonyPrefix] private static void Prefix(ref Dictionary<Assembly, BufferingSocket>? __state, ZNet __instance, ZRpc rpc) { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Invalid comparison between Unknown and I4 if (!__instance.IsServer()) { return; } BufferingSocket bufferingSocket = new BufferingSocket(rpc.GetSocket()); AccessTools.DeclaredField(typeof(ZRpc), "m_socket").SetValue(rpc, bufferingSocket); object? obj = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); ZNetPeer val = (ZNetPeer)((obj is ZNetPeer) ? obj : null); if (val != null && (int)ZNet.m_onlineBackend > 0) { FieldInfo fieldInfo = AccessTools.DeclaredField(typeof(ZNetPeer), "m_socket"); object? value = fieldInfo.GetValue(val); ZPlayFabSocket val2 = (ZPlayFabSocket)((value is ZPlayFabSocket) ? value : null); if (val2 != null) { typeof(ZPlayFabSocket).GetField("m_remotePlayerId").SetValue(bufferingSocket, val2.m_remotePlayerId); } fieldInfo.SetValue(val, bufferingSocket); } if (__state == null) { __state = new Dictionary<Assembly, BufferingSocket>(); } __state[Assembly.GetExecutingAssembly()] = bufferingSocket; } [HarmonyPostfix] private static void Postfix(Dictionary<Assembly, BufferingSocket> __state, ZNet __instance, ZRpc rpc) { ZNetPeer peer; if (__instance.IsServer()) { object obj = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); peer = (ZNetPeer)((obj is ZNetPeer) ? obj : null); if (peer == null) { SendBufferedData(); } else { ((MonoBehaviour)__instance).StartCoroutine(sendAsync()); } } void SendBufferedData() { if (rpc.GetSocket() is BufferingSocket bufferingSocket) { AccessTools.DeclaredField(typeof(ZRpc), "m_socket").SetValue(rpc, bufferingSocket.Original); object? obj2 = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); ZNetPeer val = (ZNetPeer)((obj2 is ZNetPeer) ? obj2 : null); if (val != null) { AccessTools.DeclaredField(typeof(ZNetPeer), "m_socket").SetValue(val, bufferingSocket.Original); } } BufferingSocket bufferingSocket2 = __state[Assembly.GetExecutingAssembly()]; bufferingSocket2.finished = true; for (int i = 0; i < bufferingSocket2.Package.Count; i++) { if (i == bufferingSocket2.versionMatchQueued) { bufferingSocket2.Original.VersionMatch(); } bufferingSocket2.Original.Send(bufferingSocket2.Package[i]); } if (bufferingSocket2.Package.Count == bufferingSocket2.versionMatchQueued) { bufferingSocket2.Original.VersionMatch(); } } IEnumerator sendAsync() { foreach (ConfigSync configSync in configSyncs) { List<PackageEntry> entries = new List<PackageEntry>(); if (configSync.CurrentVersion != null) { entries.Add(new PackageEntry { section = "Internal", key = "serverversion", type = typeof(string), value = configSync.CurrentVersion }); } MethodInfo listContainsId = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList adminList = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); entries.Add(new PackageEntry { section = "Internal", key = "lockexempt", type = typeof(bool), value = (((object)listContainsId == null) ? ((object)adminList.Contains(rpc.GetSocket().GetHostName())) : listContainsId.Invoke(ZNet.instance, new object[2] { adminList, rpc.GetSocket().GetHostName() })) }); ZPackage package = ConfigsToPackage(configSync.allConfigs.Select((OwnConfigEntryBase c) => c.BaseConfig), configSync.allCustomValues, entries, partial: false); yield return ((MonoBehaviour)__instance).StartCoroutine(configSync.sendZPackage(new List<ZNetPeer> { peer }, package)); } SendBufferedData(); } } } private class PackageEntry { public string section = null; public string key = null; public Type type = null; public object? value; } [HarmonyPatch(typeof(ConfigEntryBase), "GetSerializedValue")] private static class PreventSavingServerInfo { [HarmonyPrefix] private static bool Prefix(ConfigEntryBase __instance, ref string __result) { OwnConfigEntryBase ownConfigEntryBase = configData(__instance); if (ownConfigEntryBase == null || isWritableConfig(ownConfigEntryBase)) { return true; } __result = TomlTypeConverter.ConvertToString(ownConfigEntryBase.LocalBaseValue, __instance.SettingType); return false; } } [HarmonyPatch(typeof(ConfigEntryBase), "SetSerializedValue")] private static class PreventConfigRereadChangingValues { [HarmonyPrefix] private static bool Prefix(ConfigEntryBase __instance, string value) { OwnConfigEntryBase ownConfigEntryBase = configData(__instance); if (ownConfigEntryBase == null || ownConfigEntryBase.LocalBaseValue == null) { return true; } try { ownConfigEntryBase.LocalBaseValue = TomlTypeConverter.ConvertToValue(value, __instance.SettingType); } catch (Exception ex) { Debug.LogWarning((object)$"Config value of setting \"{__instance.Definition}\" could not be parsed and will be ignored. Reason: {ex.Message}; Value: {value}"); } return false; } } private class InvalidDeserializationTypeException : Exception { public string expected = null; public string received = null; public string field = ""; } public static bool ProcessingServerUpdate; public readonly string Name; public string? DisplayName; public string? CurrentVersion; public string? MinimumRequiredVersion; public bool ModRequired = false; private bool? forceConfigLocking; private bool isSourceOfTruth = true; private static readonly HashSet<ConfigSync> configSyncs; private readonly HashSet<OwnConfigEntryBase> allConfigs = new HashSet<OwnConfigEntryBase>(); private HashSet<CustomSyncedValueBase> allCustomValues = new HashSet<CustomSyncedValueBase>(); private static bool isServer; private static bool lockExempt; private OwnConfigEntryBase? lockedConfig = null; private const byte PARTIAL_CONFIGS = 1; private const byte FRAGMENTED_CONFIG = 2; private const byte COMPRESSED_CONFIG = 4; private readonly Dictionary<string, SortedDictionary<int, byte[]>> configValueCache = new Dictionary<string, SortedDictionary<int, byte[]>>(); private readonly List<KeyValuePair<long, string>> cacheExpirations = new List<KeyValuePair<long, string>>(); private static long packageCounter; public bool IsLocked { get { bool? flag = forceConfigLocking; bool num; if (!flag.HasValue) { if (lockedConfig == null) { goto IL_0052; } num = ((IConvertible)lockedConfig.BaseConfig.BoxedValue).ToInt32(CultureInfo.InvariantCulture) != 0; } else { num = flag == true; } if (!num) { goto IL_0052; } int result = ((!lockExempt) ? 1 : 0); goto IL_0053; IL_0052: result = 0; goto IL_0053; IL_0053: return (byte)result != 0; } set { forceConfigLocking = value; } } public bool IsAdmin => lockExempt || isSourceOfTruth; public bool IsSourceOfTruth { get { return isSourceOfTruth; } private set { if (value != isSourceOfTruth) { isSourceOfTruth = value; this.SourceOfTruthChanged?.Invoke(value); } } } public bool InitialSyncDone { get; private set; } = false; public event Action<bool>? SourceOfTruthChanged; private event Action? lockedConfigChanged; static ConfigSync() { ProcessingServerUpdate = false; configSyncs = new HashSet<ConfigSync>(); lockExempt = false; packageCounter = 0L; RuntimeHelpers.RunClassConstructor(typeof(VersionCheck).TypeHandle); } public ConfigSync(string name) { Name = name; configSyncs.Add(this); new VersionCheck(this); } public SyncedConfigEntry<T> AddConfigEntry<T>(ConfigEntry<T> configEntry) { OwnConfigEntryBase ownConfigEntryBase = configData((ConfigEntryBase)(object)configEntry); SyncedConfigEntry<T> syncedEntry = ownConfigEntryBase as SyncedConfigEntry<T>; if (syncedEntry == null) { syncedEntry = new SyncedConfigEntry<T>(configEntry); AccessTools.DeclaredField(typeof(ConfigDescription), "<Tags>k__BackingField").SetValue(((ConfigEntryBase)configEntry).Description, new object[1] { new ConfigurationManagerAttributes() }.Concat(((ConfigEntryBase)configEntry).Description.Tags ?? Array.Empty<object>()).Concat(new SyncedConfigEntry<T>[1] { syncedEntry }).ToArray()); configEntry.SettingChanged += delegate { if (!ProcessingServerUpdate && syncedEntry.SynchronizedConfig) { Broadcast(0L, (ConfigEntryBase)configEntry); } }; allConfigs.Add(syncedEntry); } return syncedEntry; } public SyncedConfigEntry<T> AddLockingConfigEntry<T>(ConfigEntry<T> lockingConfig) where T : IConvertible { if (lockedConfig != null) { throw new Exception("Cannot initialize locking ConfigEntry twice"); } lockedConfig = AddConfigEntry<T>(lockingConfig); lockingConfig.SettingChanged += delegate { this.lockedConfigChanged?.Invoke(); }; return (SyncedConfigEntry<T>)lockedConfig; } internal void AddCustomValue(CustomSyncedValueBase customValue) { if (allCustomValues.Select((CustomSyncedValueBase v) => v.Identifier).Concat(new string[1] { "serverversion" }).Contains(customValue.Identifier)) { throw new Exception("Cannot have multiple settings with the same name or with a reserved name (serverversion)"); } allCustomValues.Add(customValue); allCustomValues = new HashSet<CustomSyncedValueBase>(allCustomValues.OrderByDescending((CustomSyncedValueBase v) => v.Priority)); customValue.ValueChanged += delegate { if (!ProcessingServerUpdate) { Broadcast(0L, customValue); } }; } private void RPC_FromServerConfigSync(ZRpc rpc, ZPackage package) { lockedConfigChanged += serverLockedSettingChanged; IsSourceOfTruth = false; if (HandleConfigSyncRPC(0L, package, clientUpdate: false)) { InitialSyncDone = true; } } private void RPC_FromOtherClientConfigSync(long sender, ZPackage package) { HandleConfigSyncRPC(sender, package, clientUpdate: true); } private bool HandleConfigSyncRPC(long sender, ZPackage package, bool clientUpdate) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected O, but got Unknown //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_0257: Expected O, but got Unknown //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Expected O, but got Unknown try { if (isServer && IsLocked) { ZRpc? currentRpc = SnatchCurrentlyHandlingRPC.currentRpc; object obj; if (currentRpc == null) { obj = null; } else { ISocket socket = currentRpc.GetSocket(); obj = ((socket != null) ? socket.GetHostName() : null); } string text = (string)obj; if (text != null) { MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList val = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); if (!(((object)methodInfo == null) ? val.Contains(text) : ((bool)methodInfo.Invoke(ZNet.instance, new object[2] { val, text })))) { return false; } } } cacheExpirations.RemoveAll(delegate(KeyValuePair<long, string> kv) { if (kv.Key < DateTimeOffset.Now.Ticks) { configValueCache.Remove(kv.Value); return true; } return false; }); byte b = package.ReadByte(); if ((b & 2) != 0) { long num = package.ReadLong(); string text2 = sender.ToString() + num; if (!configValueCache.TryGetValue(text2, out SortedDictionary<int, byte[]> value)) { value = new SortedDictionary<int, byte[]>(); configValueCache[text2] = value; cacheExpirations.Add(new KeyValuePair<long, string>(DateTimeOffset.Now.AddSeconds(60.0).Ticks, text2)); } int key = package.ReadInt(); int num2 = package.ReadInt(); value.Add(key, package.ReadByteArray()); if (value.Count < num2) { return false; } configValueCache.Remove(text2); package = new ZPackage(value.Values.SelectMany((byte[] a) => a).ToArray()); b = package.ReadByte(); } ProcessingServerUpdate = true; if ((b & 4) != 0) { byte[] buffer = package.ReadByteArray(); MemoryStream stream = new MemoryStream(buffer); MemoryStream memoryStream = new MemoryStream(); using (DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress)) { deflateStream.CopyTo(memoryStream); } package = new ZPackage(memoryStream.ToArray()); b = package.ReadByte(); } if ((b & 1) == 0) { resetConfigsFromServer(); } ParsedConfigs parsedConfigs = ReadConfigsFromPackage(package); ConfigFile val2 = null; bool saveOnConfigSet = false; foreach (KeyValuePair<OwnConfigEntryBase, object> configValue in parsedConfigs.configValues) { if (!isServer && configValue.Key.LocalBaseValue == null) { configValue.Key.LocalBaseValue = configValue.Key.BaseConfig.BoxedValue; } if (val2 == null) { val2 = configValue.Key.BaseConfig.ConfigFile; saveOnConfigSet = val2.SaveOnConfigSet; val2.SaveOnConfigSet = false; } configValue.Key.BaseConfig.BoxedValue = configValue.Value; } if (val2 != null) { val2.SaveOnConfigSet = saveOnConfigSet; val2.Save(); } foreach (KeyValuePair<CustomSyncedValueBase, object> customValue in parsedConfigs.customValues) { if (!isServer) { CustomSyncedValueBase key2 = customValue.Key; if (key2.LocalBaseValue == null) { key2.LocalBaseValue = customValue.Key.BoxedValue; } } customValue.Key.BoxedValue = customValue.Value; } Debug.Log((object)string.Format("Received {0} configs and {1} custom values from {2} for mod {3}", parsedConfigs.configValues.Count, parsedConfigs.customValues.Count, (isServer || clientUpdate) ? $"client {sender}" : "the server", DisplayName ?? Name)); if (!isServer) { serverLockedSettingChanged(); } return true; } finally { ProcessingServerUpdate = false; } } private ParsedConfigs ReadConfigsFromPackage(ZPackage package) { ParsedConfigs parsedConfigs = new ParsedConfigs(); Dictionary<string, OwnConfigEntryBase> dictionary = allConfigs.Where((OwnConfigEntryBase c) => c.SynchronizedConfig).ToDictionary((OwnConfigEntryBase c) => c.BaseConfig.Definition.Section + "_" + c.BaseConfig.Definition.Key, (OwnConfigEntryBase c) => c); Dictionary<string, CustomSyncedValueBase> dictionary2 = allCustomValues.ToDictionary((CustomSyncedValueBase c) => c.Identifier, (CustomSyncedValueBase c) => c); int num = package.ReadInt(); for (int num2 = 0; num2 < num; num2++) { string text = package.ReadString(); string text2 = package.ReadString(); string text3 = package.ReadString(); Type type = Type.GetType(text3); if (text3 == "" || type != null) { object obj; try { obj = ((text3 == "") ? null : ReadValueWithTypeFromZPackage(package, type)); } catch (InvalidDeserializationTypeException ex) { Debug.LogWarning((object)("Got unexpected struct internal type " + ex.received + " for field " + ex.field + " struct " + text3 + " for " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ", expecting " + ex.expected)); continue; } OwnConfigEntryBase value2; if (text == "Internal") { CustomSyncedValueBase value; if (text2 == "serverversion") { if (obj?.ToString() != CurrentVersion) { Debug.LogWarning((object)("Received server version is not equal: server version = " + (obj?.ToString() ?? "null") + "; local version = " + (CurrentVersion ?? "unknown"))); } } else if (text2 == "lockexempt") { if (obj is bool flag) { lockExempt = flag; } } else if (dictionary2.TryGetValue(text2, out value)) { if ((text3 == "" && (!value.Type.IsValueType || Nullable.GetUnderlyingType(value.Type) != null)) || GetZPackageTypeString(value.Type) == text3) { parsedConfigs.customValues[value] = obj; continue; } Debug.LogWarning((object)("Got unexpected type " + text3 + " for internal value " + text2 + " for mod " + (DisplayName ?? Name) + ", expecting " + value.Type.AssemblyQualifiedName)); } } else if (dictionary.TryGetValue(text + "_" + text2, out value2)) { Type type2 = configType(value2.BaseConfig); if ((text3 == "" && (!type2.IsValueType || Nullable.GetUnderlyingType(type2) != null)) || GetZPackageTypeString(type2) == text3) { parsedConfigs.configValues[value2] = obj; continue; } Debug.LogWarning((object)("Got unexpected type " + text3 + " for " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ", expecting " + type2.AssemblyQualifiedName)); } else { Debug.LogWarning((object)("Received unknown config entry " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ". This may happen if client and server versions of the mod do not match.")); } continue; } Debug.LogWarning((object)("Got invalid type " + text3 + ", abort reading of received configs")); return new ParsedConfigs(); } return parsedConfigs; } private static bool isWritableConfig(OwnConfigEntryBase config) { ConfigSync configSync = configSyncs.FirstOrDefault((ConfigSync cs) => cs.allConfigs.Contains(config)); if (configSync == null) { return true; } return configSync.IsSourceOfTruth || !config.SynchronizedConfig || config.LocalBaseValue == null || (!configSync.IsLocked && (config != configSync.lockedConfig || lockExempt)); } private void serverLockedSettingChanged() { foreach (OwnConfigEntryBase allConfig in allConfigs) { configAttribute<ConfigurationManagerAttributes>(allConfig.BaseConfig).ReadOnly = !i