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 EpicLootCustomEffects v1.0.11
plugins/EpicLootCustomEffects.dll
Decompiled 2 weeks agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using EpicLoot; using EpicLoot.MagicItemEffects; using EpicLoot.src.Magic.MagicItemEffects.Helpers; using HarmonyLib; using Jotunn.Entities; using Jotunn.Managers; using Jotunn.Utils; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyCompany("EpicLootCustomEffects")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.11.0")] [assembly: AssemblyInformationalVersion("1.0.11")] [assembly: AssemblyProduct("EpicLootCustomEffects")] [assembly: AssemblyTitle("EpicLootCustomEffects")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyVersion("1.0.11.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace EpicLootCustomEffects { internal sealed class SettingsBundle { public RuntimeOptions Runtime; public TargetRules Rules; public List<MagicItemEffectDefinition> Effects; } internal static class LiveSettings { internal static RuntimeOptions Current = new RuntimeOptions(); private static readonly string[] EffectNames = new string[3] { "Eclipse", "Awakening", "PrecisionStrike" }.Concat(KillerRuntime.Effects).ToArray(); private static readonly string[] EffectFiles = EffectNames; internal static readonly string[] Files = new string[4] { "runtime.json", "classification.json", "localizations/Korean.json", "localizations/English.json" }.Concat(EffectFiles.Select((string x) => "effects/" + x + ".json")).ToArray(); internal static TargetRules Rules; private const string RequestRpc = "ECE_SettingsRequest_v2"; private const string SyncRpc = "ECE_SettingsSync_v2"; private static string directory; private static string signature; private static string rejectedSignature; private static string serialized; private static string digest = ""; private static Func<string, string> read; private static CustomLocalization localization; private static Dictionary<string, string> korean; private static Dictionary<string, string> english; private static SettingsBundle local; private static SettingsBundle remote; private static ZRoutedRpc router; private static readonly MethodInfo ServerPeerMethod = AccessTools.Method(typeof(ZRoutedRpc), "GetServerPeerID", (Type[])null, (Type[])null); private static float nextPoll; private static float nextRequest; private static bool routerSeen; internal static bool HasServerSettings => remote != null; internal static bool IsRemote { get { if ((Object)(object)ZNet.instance != (Object)null) { return !ZNet.instance.IsServer(); } return false; } } private static long ServerPeer { get { if (router != null) { return (long)ServerPeerMethod.Invoke(router, null); } return 0L; } } internal static bool CanEditRules { get { if (!IsRemote) { return true; } if (remote == null) { return false; } MethodInfo methodInfo = AccessTools.Method(typeof(ZNet), "LocalPlayerIsAdminOrHost", (Type[])null, (Type[])null); if (methodInfo != null) { return (bool)methodInfo.Invoke(ZNet.instance, null); } return false; } } internal static void Initialize(string path, Func<string, string> reader) { directory = path; read = reader; localization = LocalizationManager.Instance.GetLocalization(); ReloadDisk(initial: true); signature = Signature(); } private static string Signature() { return string.Join("|", Files.Where((string name) => !name.StartsWith("localizations/", StringComparison.Ordinal)).Select(delegate(string name) { FileInfo fileInfo = new FileInfo(Path.Combine(directory, name)); return name + ":" + (fileInfo.Exists ? (fileInfo.LastWriteTimeUtc.Ticks + ":" + fileInfo.Length) : "embedded"); })); } private static void ValidateEffect(MagicItemEffectDefinition effect, string expected) { //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Invalid comparison between Unknown and I4 if (effect != null) { effect.Type = EffectIdentity.Canonical(effect.Type); } if (effect == null || effect.Type != expected) { throw new InvalidDataException("Invalid Type for " + expected); } JObject val = JObject.FromObject((object)effect); double num = Extensions.Value<double>((IEnumerable<JToken>)val["SelectionWeight"]); if (!WoundLedger.Finite(num) || num < 0.0) { throw new InvalidDataException(expected + ": SelectionWeight must be finite and nonnegative"); } if (expected == "PrecisionStrike") { if (effect.Requirements == null) { throw new InvalidDataException("PrecisionStrike: missing Requirements"); } if (effect.ValuesPerRarity == null) { throw new InvalidDataException("PrecisionStrike: use an empty ValuesPerRarity object, not null"); } FieldInfo[] fields = ((object)effect.ValuesPerRarity).GetType().GetFields(); for (int i = 0; i < fields.Length; i++) { if (fields[i].GetValue(effect.ValuesPerRarity) != null) { throw new InvalidDataException("PrecisionStrike is a valueless effect; omit rarity values"); } } return; } string[] array = new string[6] { "Magic", "Rare", "Epic", "Legendary", "Mythic", "Ancient" }; foreach (string text in array) { JToken obj = val["ValuesPerRarity"]; JToken val2 = ((obj != null) ? obj[(object)text] : null); if (val2 == null || (int)val2.Type == 10) { throw new InvalidDataException(expected + ": missing rarity " + text); } double num2 = Extensions.Value<double>((IEnumerable<JToken>)val2[(object)"MinValue"]); double num3 = Extensions.Value<double>((IEnumerable<JToken>)val2[(object)"MaxValue"]); double num4 = Extensions.Value<double>((IEnumerable<JToken>)val2[(object)"Increment"]); if (!WoundLedger.Finite(num2) || !WoundLedger.Finite(num3) || !WoundLedger.Finite(num4) || num2 < 0.0 || num3 < num2 || num4 <= 0.0) { throw new InvalidDataException(expected + ": invalid range for " + text); } } } private static void Validate(SettingsBundle bundle) { if (bundle == null || bundle.Runtime == null || bundle.Rules == null || bundle.Effects == null || bundle.Effects.Count != EffectNames.Length) { throw new InvalidDataException("Incomplete settings bundle"); } bundle.Runtime.Validate(); bundle.Rules.Validate(); for (int i = 0; i < EffectNames.Length; i++) { ValidateEffect(bundle.Effects[i], EffectNames[i]); } } private static Dictionary<string, string> ReadLanguage(string name) { Dictionary<string, string> dictionary = JsonConvert.DeserializeObject<Dictionary<string, string>>(read(name)); if (dictionary == null || dictionary.Any((KeyValuePair<string, string> p) => p.Value == null)) { throw new InvalidDataException("Invalid localization: " + name); } return dictionary; } private static void ReloadDisk(bool initial) { SettingsBundle bundle = new SettingsBundle { Runtime = JsonConvert.DeserializeObject<RuntimeOptions>(read("runtime.json")), Rules = JsonConvert.DeserializeObject<TargetRules>(read("classification.json")), Effects = EffectFiles.Select((string n) => JsonConvert.DeserializeObject<MagicItemEffectDefinition>(read("effects/" + n + ".json"))).ToList() }; Validate(bundle); if (initial) { korean = ReadLanguage("localizations/Korean.json"); english = ReadLanguage("localizations/English.json"); } local = bundle; bool flag = (Object)(object)ZNet.instance != (Object)null && !ZNet.instance.IsServer() && remote != null; Apply(flag ? remote : local); if (initial) { UpdateLanguages(); } if (!initial) { Plugin.Log.LogInfo((object)(flag ? "JSON reloaded: local display settings updated; gameplay settings remain server-authoritative." : "JSON reloaded: effect definitions, runtime settings and classification applied.")); if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { Send(0L); } } } private static void RefreshWords(Dictionary<string, string> ko, Dictionary<string, string> en) { Type type = AccessTools.TypeByName("Localization"); if (type == null) { return; } object obj = AccessTools.Property(type, "instance")?.GetValue(null, null); if (obj == null) { return; } string text = AccessTools.Method(type, "GetSelectedLanguage", (Type[])null, (Type[])null)?.Invoke(obj, null) as string; MethodInfo methodInfo = AccessTools.Method(type, "AddWord", new Type[2] { typeof(string), typeof(string) }, (Type[])null); if (methodInfo == null) { return; } Dictionary<string, string> dictionary = new Dictionary<string, string>(en); if (text == "Korean") { foreach (KeyValuePair<string, string> item in ko) { dictionary[item.Key] = item.Value; } } foreach (KeyValuePair<string, string> item2 in dictionary) { methodInfo.Invoke(obj, new object[2] { item2.Key, item2.Value }); } } private static void UpdateLanguages() { if (korean != null && english != null) { Func<string, string> expand = (string text) => text.Replace("{eclipse_duration}", Current.Eclipse.DurationSeconds.ToString("0.###", CultureInfo.InvariantCulture)).Replace("{awakening_refresh}", "2.5").Replace("{awakening_duration}", Current.Awakening.DurationSeconds.ToString("0.###", CultureInfo.InvariantCulture)) .Replace("{awakening_conversion}", Current.Awakening.DamageConversionPercent.ToString("0.###", CultureInfo.InvariantCulture)); Dictionary<string, string> dictionary = korean.ToDictionary((KeyValuePair<string, string> p) => p.Key, (KeyValuePair<string, string> p) => expand(p.Value)); Dictionary<string, string> dictionary2 = english.ToDictionary((KeyValuePair<string, string> p) => p.Key, (KeyValuePair<string, string> p) => expand(p.Value)); localization.ClearAll(); localization.AddJsonFile("Korean", JsonConvert.SerializeObject((object)dictionary)); localization.AddJsonFile("English", JsonConvert.SerializeObject((object)dictionary2)); RefreshWords(dictionary, dictionary2); } } private static void Apply(SettingsBundle bundle) { Validate(bundle); Current = local.Runtime; Rules = bundle.Rules; Plugin.Definitions.Clear(); Plugin.Definitions.AddRange(bundle.Effects); if (Plugin.Ready) { Plugin.RegisterEffect(); } serialized = JsonConvert.SerializeObject((object)bundle); using (SHA256 sHA = SHA256.Create()) { digest = BitConverter.ToString(sHA.ComputeHash(Encoding.UTF8.GetBytes(serialized))).Replace("-", ""); } if (Plugin.Ready && (Object)(object)Player.m_localPlayer != (Object)null) { AccessTools.Method(AccessTools.TypeByName("EpicLoot.EquipmentEffectCache"), "Reset", (Type[])null, (Type[])null)?.Invoke(null, new object[1] { Player.m_localPlayer }); } } private static void Send(long peer) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Expected O, but got Unknown if (router != null && serialized != null) { ZPackage val = new ZPackage(); val.Write(serialized); router.InvokeRoutedRPC(peer, "ECE_SettingsSync_v2", new object[1] { val }); } } private static void EnsureRouter() { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance == router) { return; } router = instance; remote = null; nextRequest = 0f; if (routerSeen) { Apply(local); } routerSeen = true; if (router == null) { return; } router.Register<ZPackage>("ECE_ClassificationEdit_v1", (Action<long, ZPackage>)delegate(long sender, ZPackage pkg) { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return; } try { if (!IsAdmin(sender)) { Send(sender); Plugin.Log.LogWarning((object)"Classification edit denied: sender is not a server administrator."); } else { string text = pkg.ReadString(); string text2 = pkg.ReadString(); if (text2.Length > 200000) { throw new InvalidDataException("Classification edit too large"); } if (Signature() != signature) { ReloadDisk(initial: false); signature = Signature(); } if (text != digest) { Send(sender); Plugin.Log.LogWarning((object)"Classification edit rejected: settings changed; refresh and retry."); } else { TargetRules targetRules = JsonConvert.DeserializeObject<TargetRules>(text2); targetRules.Validate(); SaveRules(targetRules); } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Classification edit rejected: " + ex.Message)); Send(sender); } }); router.Register<string>("ECE_SettingsRequest_v2", (Action<long, string>)delegate(long sender, string known) { if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer() && known != digest) { Send(sender); } }); router.Register<ZPackage>("ECE_SettingsSync_v2", (Action<long, ZPackage>)delegate(long sender, ZPackage pkg) { if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer() || sender != ServerPeer) { return; } try { SettingsBundle bundle = JsonConvert.DeserializeObject<SettingsBundle>(pkg.ReadString()); Validate(bundle); remote = bundle; Apply(bundle); Plugin.Log.LogInfo((object)"Authoritative server effect settings applied."); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Server settings rejected; keeping last valid values: " + ex.Message)); } }); } private static bool IsAdmin(long sender) { ZNet instance = ZNet.instance; ZNetPeer peer = instance.GetPeer(sender); if (peer == null || peer.m_rpc == null) { return false; } object value = AccessTools.Field(typeof(ZNet), "m_adminList").GetValue(instance); return (bool)AccessTools.Method(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null).Invoke(instance, new object[2] { value, peer.m_rpc.GetSocket().GetHostName() }); } private static void WriteJson(string name, object value) { string text = Path.Combine(directory, name); string text2 = text + ".ece-tmp"; File.WriteAllText(text2, JsonConvert.SerializeObject(value, (Formatting)1) + Environment.NewLine); if (File.Exists(text)) { File.Replace(text2, text, null); } else { File.Move(text2, text); } } private static void SaveRules(TargetRules rules) { rules.Validate(); WriteJson("classification.json", rules); ReloadDisk(initial: false); signature = Signature(); } internal static void EditRules(TargetRules rules) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown rules.Validate(); if (!CanEditRules) { throw new InvalidOperationException("Only the host or a server administrator can change classification."); } if (IsRemote) { ZPackage val = new ZPackage(); val.Write(digest); val.Write(JsonConvert.SerializeObject((object)rules)); router.InvokeRoutedRPC(ServerPeer, "ECE_ClassificationEdit_v1", new object[1] { val }); Plugin.Log.LogInfo((object)"Classification change requested; waiting for authoritative server settings."); } else { SaveRules(rules); } } internal static void EditDisplay(RuntimeOptions options) { options.Validate(); WriteJson("runtime.json", options); ReloadDisk(initial: false); signature = Signature(); } internal static void Update() { EnsureRouter(); float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup >= nextPoll) { nextPoll = realtimeSinceStartup + 0.5f; string text = Signature(); if (text != signature) { try { ReloadDisk(initial: false); signature = text; rejectedSignature = null; } catch (Exception ex) { if (rejectedSignature != text) { Plugin.Log.LogWarning((object)("JSON reload rejected; keeping last valid values: " + ex.Message)); } rejectedSignature = text; } } } if (router != null && (Object)(object)ZNet.instance != (Object)null && !ZNet.instance.IsServer() && realtimeSinceStartup >= nextRequest) { nextRequest = realtimeSinceStartup + 2f; long serverPeer = ServerPeer; if (serverPeer != 0L) { router.InvokeRoutedRPC(serverPeer, "ECE_SettingsRequest_v2", new object[1] { (remote == null) ? "" : digest }); } } } } public sealed class VitalityState { public const double Conversion = 0.2; public const double Duration = 5.0; public const double RefreshDuration = 2.5; public double Bonus { get; private set; } public double Cap { get; private set; } public double Expires { get; private set; } public bool Locked { get; private set; } public void Reset() { Bonus = 0.0; Cap = 0.0; Expires = 0.0; Locked = false; } public bool Expire(double now) { if (Bonus > 0.0 && now >= Expires) { Reset(); return true; } return false; } public bool AddDamage(double now, double damage, double weaponCap, double conversion = 0.2, double duration = 5.0, double maxCap = double.MaxValue, double refreshDuration = 2.5) { Expire(now); if (!WoundLedger.Finite(now) || !WoundLedger.Finite(damage) || !WoundLedger.Finite(weaponCap) || damage <= 0.0 || weaponCap <= 0.0 || !WoundLedger.Finite(conversion) || conversion < 0.0 || !WoundLedger.Finite(duration) || duration <= 0.0 || !WoundLedger.Finite(maxCap) || maxCap <= 0.0 || !WoundLedger.Finite(refreshDuration) || refreshDuration <= 0.0 || Locked) { return false; } double num = Math.Min(maxCap, weaponCap); Cap = ((Bonus > 0.0) ? Math.Min(Cap, num) : num); Bonus = Math.Min(Cap, Bonus + damage * conversion); Locked = Bonus >= Cap; Expires = now + (Locked ? duration : refreshDuration); return true; } } public sealed class ReceiptState { public readonly double Deadline; public bool Resolved { get; private set; } public ReceiptState(double now) { Deadline = now + 8.0; } public bool Resolve() { if (Resolved) { return false; } Resolved = true; return true; } public bool Timeout(double now) { if (now >= Deadline) { return Resolve(); } return false; } } public sealed class EclipseOptions { public bool ShowDamageText = true; public string DamageTextColor = "#C77DFF"; public string DamageTextFormat = "HP -{damage}"; public int DamageTextDecimals = 1; public double TextVerticalOffset = 0.8; public double TextHorizontalOffset = 0.65; [JsonIgnore] public double DurationSeconds => 3.0; [JsonIgnore] public double TickIntervalSeconds => 0.25; public string FormatDamage(double amount) { string text = "0" + ((DamageTextDecimals > 0) ? ("." + new string('#', DamageTextDecimals)) : ""); string text2 = amount.ToString(text, CultureInfo.InvariantCulture); if (amount > 0.0 && text2 == "0") { text2 = amount.ToString("0.###", CultureInfo.InvariantCulture); } if (amount > 0.0 && text2 == "0") { text2 = amount.ToString("0.###E+0", CultureInfo.InvariantCulture); } return DamageTextFormat.Replace("{damage}", text2); } } public sealed class AwakeningOptions { [JsonIgnore] public double DamageConversionPercent => 20.0; [JsonIgnore] public double DurationSeconds => 5.0; } public sealed class RuntimeOptions { public bool Diagnostics; public EclipseOptions Eclipse = new EclipseOptions(); [JsonIgnore] public AwakeningOptions Awakening = new AwakeningOptions(); public void Validate() { if (Eclipse == null || Awakening == null) { throw new ArgumentException("Eclipse/Awakening sections are required"); } Range(Eclipse.DurationSeconds, 0.1, 120.0, "Eclipse.DurationSeconds"); Range(Eclipse.TickIntervalSeconds, 0.05, 3.0, "Eclipse.TickIntervalSeconds"); if (Eclipse.TickIntervalSeconds > Eclipse.DurationSeconds) { throw new ArgumentException("TickIntervalSeconds exceeds duration"); } Range(Eclipse.TextVerticalOffset, -10.0, 10.0, "TextVerticalOffset"); Range(Eclipse.TextHorizontalOffset, -10.0, 10.0, "TextHorizontalOffset"); if (Eclipse.DamageTextDecimals < 0 || Eclipse.DamageTextDecimals > 3) { throw new ArgumentException("DamageTextDecimals must be 0..3"); } if (Eclipse.DamageTextColor == null || !Regex.IsMatch(Eclipse.DamageTextColor, "^#[0-9a-fA-F]{6}([0-9a-fA-F]{2})?$")) { throw new ArgumentException("DamageTextColor must be #RRGGBB or #RRGGBBAA"); } if (Eclipse.DamageTextFormat == null || Eclipse.DamageTextFormat.Length > 80 || !Eclipse.DamageTextFormat.Contains("{damage}")) { throw new ArgumentException("DamageTextFormat must contain {damage}, max 80 characters"); } Range(Awakening.DamageConversionPercent, 0.0, 1000.0, "DamageConversionPercent"); Range(Awakening.DurationSeconds, 0.1, 120.0, "Awakening.DurationSeconds"); } private static void Range(double value, double min, double max, string label) { if (!WoundLedger.Finite(value) || value < min || value > max) { throw new ArgumentException(label + " must be in " + min + ".." + max); } } } public static class LedgerCodec { public static byte[] Write(WoundLedger ledger) { if (ledger.Windows.Count == 0) { return new byte[0]; } using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream); binaryWriter.Write(3); binaryWriter.Write(ledger.Windows.Count); foreach (WoundWindow window in ledger.Windows) { binaryWriter.Write(window.UserId); binaryWriter.Write(window.ObjectId); binaryWriter.Write(window.End); binaryWriter.Write(window.IntegratedThrough); binaryWriter.Write(window.Remaining); binaryWriter.Write(window.Pending); } return memoryStream.ToArray(); } public static WoundLedger Read(byte[] bytes) { WoundLedger woundLedger = new WoundLedger(); if (bytes == null || bytes.Length == 0) { return woundLedger; } using BinaryReader binaryReader = new BinaryReader(new MemoryStream(bytes)); int num = binaryReader.ReadInt32(); if (num != 2 && num != 3) { throw new InvalidDataException("Unknown snapshot version"); } int num2 = binaryReader.ReadInt32(); if (num2 < 0 || num2 > (bytes.Length - 8) / 44) { throw new InvalidDataException("Invalid snapshot size"); } for (int i = 0; i < num2; i++) { WoundWindow woundWindow = new WoundWindow { UserId = binaryReader.ReadInt64(), ObjectId = binaryReader.ReadUInt32(), End = binaryReader.ReadDouble(), IntegratedThrough = binaryReader.ReadDouble(), Remaining = binaryReader.ReadDouble(), Pending = binaryReader.ReadDouble() }; if (!WoundLedger.Finite(woundWindow.End) || !WoundLedger.Finite(woundWindow.IntegratedThrough) || !WoundLedger.Finite(woundWindow.Remaining) || !WoundLedger.Finite(woundWindow.Pending) || woundWindow.Remaining < 0.0 || woundWindow.Pending < 0.0 || woundWindow.IntegratedThrough > woundWindow.End) { throw new InvalidDataException("Invalid wound window"); } if (woundWindow.IntegratedThrough == woundWindow.End && woundWindow.Remaining > 0.0) { throw new InvalidDataException("Expired wound contains unpaid future damage"); } woundLedger.Windows.Add(woundWindow); } if (binaryReader.BaseStream.Position != binaryReader.BaseStream.Length) { throw new InvalidDataException("Trailing snapshot bytes"); } return woundLedger; } } [BepInPlugin("local.epicloot.customeffects", "EpicLoot Custom Effects", "1.0.11")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { public const string Guid = "local.epicloot.customeffects"; public const string Version = "1.0.11"; internal const string Effect = "Eclipse"; internal const string Rpc = "ECE_DirectHit_v5"; internal const string SnapshotKey = "cw_windows_v2"; internal static ManualLogSource Log; internal static bool Ready; internal static readonly List<MagicItemEffectDefinition> Definitions = new List<MagicItemEffectDefinition>(); internal static readonly ConditionalWeakTable<HitData, HitTag> Tags = new ConditionalWeakTable<HitData, HitTag>(); internal static readonly ConditionalWeakTable<Character, Tracker> Trackers = new ConditionalWeakTable<Character, Tracker>(); internal static readonly MethodInfo ReceiveDamage = AccessTools.Method(typeof(Character), "RPC_Damage", (Type[])null, (Type[])null); internal static readonly MethodInfo FindWeakSpot = AccessTools.Method(typeof(Character), "FindWeakSpotIndex", (Type[])null, (Type[])null); internal static readonly FieldInfo HitCollider = AccessTools.Field(typeof(HitData), "m_hitCollider"); private Harmony harmony; private string settingsDirectory; internal static double Now { get { if (!((Object)(object)ZNet.instance != (Object)null)) { return Time.time; } return ZNet.instance.GetTimeSeconds(); } } private void Awake() { //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; try { settingsDirectory = Path.Combine(Paths.ConfigPath, "EpicLootCustomEffects"); ConfigStorage.Initialize(settingsDirectory, Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location), LiveSettings.Files, ReadEmbedded); LocalizationDefaults.AddPrecisionKeys(settingsDirectory, ReadEmbedded); LiveSettings.Initialize(settingsDirectory, ReadText); ((BaseUnityPlugin)this).Logger.LogDebug((object)("Settings directory: " + settingsDirectory)); try { ConfigStorage.ArchiveLegacy(settingsDirectory, Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location), LiveSettings.Files); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Legacy settings loaded, but archiving was incomplete: " + ex.Message)); } ConfigBridge.Initialize(((BaseUnityPlugin)this).Config); if (ReceiveDamage == null || FindWeakSpot == null || HitCollider == null) { throw new MissingMethodException("Required Valheim damage methods were not found"); } harmony = new Harmony("local.epicloot.customeffects"); harmony.PatchAll(typeof(Plugin).Assembly); MagicItemEffectDefinitions.OnSetupMagicItemEffectDefinitions += RegisterEffect; RegisterEffect(); Ready = true; } catch (Exception ex2) { Ready = false; if (harmony != null) { harmony.UnpatchSelf(); } ((BaseUnityPlugin)this).Logger.LogError((object)("EpicLoot Custom Effects could not start; gameplay patches were rolled back. " + ex2)); } } private string ReadText(string name) { return File.ReadAllText(Path.Combine(settingsDirectory, name)); } private string ReadEmbedded(string name) { string text = (name.StartsWith("localizations/", StringComparison.Ordinal) ? Path.GetFileName(name) : name); using Stream stream = typeof(Plugin).Assembly.GetManifestResourceStream("EpicLootCustomEffects." + text.Replace('/', '.')); if (stream == null) { throw new FileNotFoundException("Missing embedded default: " + name); } using StreamReader streamReader = new StreamReader(stream); return streamReader.ReadToEnd(); } internal static void RegisterEffect() { foreach (MagicItemEffectDefinition definition in Definitions) { MagicItemEffectDefinition val = definition; if (definition.Type.StartsWith("MinorKiller", StringComparison.Ordinal)) { string key = definition.Type.Substring("MinorKiller".Length); if (LiveSettings.Rules.Biomes[key].Length == 0) { val = JsonConvert.DeserializeObject<MagicItemEffectDefinition>(JsonConvert.SerializeObject((object)definition)); val.Requirements.NoRoll = true; } } MagicItemEffectDefinitions.Add(val); } } private void Update() { if (Ready) { EclipseDisplay.Update(); LiveSettings.Update(); ConfigBridge.Update(); VitalityRuntime.Update(); } } private void OnDestroy() { VitalityRuntime.Shutdown(); Ready = false; MagicItemEffectDefinitions.OnSetupMagicItemEffectDefinitions -= RegisterEffect; if (harmony != null) { harmony.UnpatchSelf(); } } internal static ZNetView View(Character c) { if (!((Object)(object)c == (Object)null)) { return ((Component)c).GetComponent<ZNetView>(); } return null; } internal static bool EligibleTarget(Character c) { if ((Object)(object)c != (Object)null && !c.IsPlayer() && !c.IsTamed() && !c.IsDead()) { return c.GetHealth() > 0f; } return false; } internal static bool Direct(HitData h) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Invalid comparison between Unknown and I4 //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Invalid comparison between Unknown and I4 if (h != null) { if ((int)h.m_hitType != 2) { return (int)h.m_hitType == 0; } return true; } return false; } internal static bool IsWound(HitData h) { if (h != null && Tags.TryGetValue(h, out var value)) { return value.Wound; } return false; } public static void ApplyModifierUnlessWound(HitData hit, float multiplier) { if (!IsWound(hit)) { hit.ApplyModifier(multiplier); } } public static float MinimumDamage(HitData hit) { if (!IsWound(hit)) { return 0.1f; } return 0f; } internal static void Register(Character c) { if (!Ready) { return; } ZNetView val = View(c); if ((Object)(object)val == (Object)null || !val.IsValid()) { return; } Player val2 = (Player)(object)((c is Player) ? c : null); if ((Object)(object)val2 != (Object)null) { VitalityRuntime.Register(val2, val); } if (!Trackers.TryGetValue(c, out var _)) { Tracker tracker = new Tracker(c, val); Trackers.Add(c, tracker); val.Register<ZPackage>("ECE_DirectHit_v5", (Action<long, ZPackage>)delegate(long sender, ZPackage package) { tracker.Receive(sender, package); }); } } } public sealed class WoundWindow { public long UserId; public uint ObjectId; public double End; public double IntegratedThrough; public double Remaining; public double Pending; public string Key => UserId + ":" + ObjectId; public void Advance(double now) { double num = Math.Min(now, End); if (!(num <= IntegratedThrough)) { double num2 = Remaining * Math.Min(1.0, (num - IntegratedThrough) / (End - IntegratedThrough)); Pending += num2; Remaining -= num2; IntegratedThrough = num; } } } public struct WoundPayment { public long UserId; public uint ObjectId; public double Damage; public WoundPayment(WoundWindow w) { UserId = w.UserId; ObjectId = w.ObjectId; Damage = w.Pending; } } public sealed class WoundLedger { public const double Duration = 3.0; public readonly List<WoundWindow> Windows = new List<WoundWindow>(); public static bool Finite(double v) { if (!double.IsNaN(v)) { return !double.IsInfinity(v); } return false; } public void AddHit(long uid, uint id, double now, double damage, double percent, double duration = 3.0) { if (Finite(now) && Finite(damage) && Finite(percent) && !(damage <= 0.0) && !(percent <= 0.0) && Finite(duration) && !(duration <= 0.0)) { double num = damage * (percent / 100.0); double num2 = now + duration; if (Finite(num) && !(num <= 0.0) && Finite(num2) && !(num2 <= now)) { Windows.Add(new WoundWindow { UserId = uid, ObjectId = id, End = num2, IntegratedThrough = now, Remaining = num }); } } } public List<WoundPayment> Drain(double now) { return Drain(now, null); } public List<WoundPayment> Drain(double now, string attackerKey) { List<WoundPayment> list = new List<WoundPayment>(); if (!Finite(now)) { return list; } Dictionary<string, int> dictionary = new Dictionary<string, int>(); foreach (WoundWindow window in Windows) { if (attackerKey != null && window.Key != attackerKey) { continue; } window.Advance(now); if (window.Pending > 0.0) { if (dictionary.TryGetValue(window.Key, out var value)) { WoundPayment value2 = list[value]; value2.Damage += window.Pending; list[value] = value2; } else { dictionary.Add(window.Key, list.Count); list.Add(new WoundPayment(window)); } } window.Pending = 0.0; } Windows.RemoveAll((WoundWindow w) => (attackerKey == null || w.Key == attackerKey) && now >= w.End); return list; } } internal sealed class HitTag { internal bool Wound; internal bool Bonus; internal bool Precision; internal bool Consumed; internal float Percent; internal float VitalityCap; internal long RequestId; internal double ActualDamage; internal ZDOID Attacker; } internal sealed class Tracker { private readonly Character target; private readonly ZNetView view; private WoundLedger ledger = new WoundLedger(); private bool owned; private readonly WoundClock clock = new WoundClock(); internal Tracker(Character c, ZNetView v) { target = c; view = v; } private bool EnsureOwner() { if ((Object)(object)view == (Object)null || !view.IsValid() || !view.IsOwner()) { owned = false; return false; } if (!owned) { owned = true; clock.Reset(); byte[] byteArray = view.GetZDO().GetByteArray("cw_windows_v2", (byte[])null); try { ledger = LedgerCodec.Read(byteArray); } catch (Exception ex) { ledger = new WoundLedger(); Plugin.Log.LogWarning((object)("Discarding invalid wound snapshot: " + ex.Message)); } } return true; } private void Save() { view.GetZDO().Set("cw_windows_v2", LedgerCodec.Write(ledger)); } internal void Receive(long sender, ZPackage package) { //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Expected O, but got Unknown //IL_0162: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.Ready || !EnsureOwner()) { return; } HitData val2; HitTag hitTag; try { bool flag = package.ReadBool(); bool flag2 = package.ReadBool(); float num = package.ReadSingle(); float num2 = package.ReadSingle(); long requestId = package.ReadLong(); float[] array = new float[KillerRuntime.Effects.Length]; for (int i = 0; i < array.Length; i++) { array[i] = package.ReadSingle(); if (!WoundLedger.Finite(array[i]) || array[i] < 0f || array[i] > 1000f) { return; } } ZPackage val = package.ReadPackage(); val2 = new HitData(); val2.Deserialize(ref val); Character attacker = val2.GetAttacker(); Player val3 = (Player)(object)((attacker is Player) ? attacker : null); ZNetView val4 = Plugin.View((Character)(object)val3); if ((Object)(object)val3 == (Object)null || (Object)(object)val4 == (Object)null || !val4.IsValid() || val4.GetZDO().GetOwner() != sender || !WoundLedger.Finite(num) || num < 0f || num > 1000f || !WoundLedger.Finite(num2) || num2 < 0f || num2 > 100000f || !Plugin.Direct(val2)) { return; } hitTag = new HitTag { Bonus = flag, Precision = (flag2 && !flag), Percent = num, VitalityCap = num2, RequestId = requestId, Attacker = val2.m_attacker }; Plugin.Tags.Add(val2, hitTag); if (!flag) { KillerRuntime.Apply(target, val3, val2, array); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Rejected malformed wound hit: " + ex.Message)); return; } try { using (BonusDamageContext.Enter(hitTag.Bonus)) { Plugin.ReceiveDamage.Invoke(target, new object[2] { sender, val2 }); } } finally { VitalityRuntime.SendReceipt(hitTag, target); } } internal unsafe void Record(ZDOID attacker, double amount, double percent) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) if (EnsureOwner() && Plugin.EligibleTarget(target)) { if (ConfigBridge.Diagnostics) { ManualLogSource log = Plugin.Log; string[] obj = new string[8] { "[EclipseRecord] target=", ((object)target.GetZDOID()/*cast due to .constrained prefix*/).ToString(), " attacker=", null, null, null, null, null }; ZDOID val = attacker; obj[3] = ((object)(*(ZDOID*)(&val))/*cast due to .constrained prefix*/).ToString(); obj[4] = " damage="; obj[5] = amount.ToString(); obj[6] = " percent="; obj[7] = percent.ToString(); log.LogInfo((object)string.Concat(obj)); } ledger.AddHit(((ZDOID)(ref attacker)).UserID, ((ZDOID)(ref attacker)).ID, Plugin.Now, amount, percent, LiveSettings.Current.Eclipse.DurationSeconds); Save(); } } internal void Tick() { if (!Plugin.Ready || !EnsureOwner()) { return; } if (!Plugin.EligibleTarget(target)) { if (ledger.Windows.Count > 0) { ledger.Windows.Clear(); Save(); } return; } double now = Plugin.Now; if (ledger.Windows.Count == 0) { clock.Reset(); return; } List<WoundPayment> list = clock.DrainDue(ledger, now, LiveSettings.Current.Eclipse.TickIntervalSeconds); if (list.Count == 0) { return; } Save(); foreach (WoundPayment item in list) { if (!Plugin.EligibleTarget(target) || !view.IsOwner()) { break; } Pay(item); } } private unsafe void Pay(WoundPayment payment) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_007d: 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_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_014e: 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) if (!(payment.Damage <= 0.0) && WoundLedger.Finite(payment.Damage) && view.IsOwner() && Plugin.EligibleTarget(target)) { HitData val = new HitData(); val.m_damage.m_damage = (float)Math.Min(payment.Damage, 3.4028234663852886E+38); val.m_attacker = new ZDOID(payment.UserId, payment.ObjectId); val.m_point = target.GetCenterPoint(); val.m_dodgeable = false; val.m_blockable = false; val.m_staggerMultiplier = 0f; val.m_pushForce = 0f; val.m_backstabBonus = 1f; val.m_hitType = (HitType)0; Plugin.Tags.Add(val, new HitTag { Wound = true }); float health = target.GetHealth(); target.ApplyDamage(val, false, false, (DamageModifier)0); double actual = Math.Max(0f, health - Math.Max(0f, target.GetHealth())); EclipseDisplay.Show(val.m_point, actual, val.m_attacker); if (ConfigBridge.Diagnostics) { ManualLogSource log = Plugin.Log; string[] obj = new string[8] { "[EclipsePay] target=", ((object)target.GetZDOID()/*cast due to .constrained prefix*/).ToString(), " attacker=", null, null, null, null, null }; ZDOID attacker = val.m_attacker; obj[3] = ((object)(*(ZDOID*)(&attacker))/*cast due to .constrained prefix*/).ToString(); obj[4] = " scheduled="; obj[5] = payment.Damage.ToString(); obj[6] = " actual="; obj[7] = actual.ToString(); log.LogInfo((object)string.Concat(obj)); } } } } [HarmonyPatch(typeof(Character), "Awake")] internal static class RegisterCharacterPatch { [HarmonyPostfix] private static void Postfix(Character __instance) { Plugin.Register(__instance); } } [HarmonyPatch(typeof(Character), "CustomFixedUpdate")] internal static class TickPatch { [HarmonyPostfix] private static void Postfix(Character __instance) { if (Plugin.Ready) { if (!Plugin.Trackers.TryGetValue(__instance, out var value)) { Plugin.Register(__instance); Plugin.Trackers.TryGetValue(__instance, out value); } value?.Tick(); } } } [HarmonyPatch(typeof(Character), "Damage")] internal static class RouteHitPatch { [HarmonyPrefix] [HarmonyPriority(0)] private static bool Prefix(Character __instance, HitData hit) { //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Expected O, but got Unknown //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Expected O, but got Unknown if (!Plugin.Ready || !Plugin.EligibleTarget(__instance) || !Plugin.Direct(hit) || Plugin.IsWound(hit)) { return true; } Character attacker = hit.GetAttacker(); Player val = (Player)(object)((attacker is Player) ? attacker : null); if ((Object)(object)val == (Object)null || (Object)(object)val != (Object)(object)Player.m_localPlayer) { return true; } float num = PlayerExtensions.GetTotalActiveMagicEffectValue(val, "Eclipse", 1f, (ItemData)null); bool isBonusHit = HitSource.IsBonusHit; if (!WoundLedger.Finite(num)) { num = 0f; } bool flag = !isBonusHit && PrecisionStrikeRuntime.HasEffect(val); float num2 = (isBonusHit ? 0f : VitalityRuntime.WeaponCap(val)); if (ConfigBridge.Diagnostics) { KillerRuntime.Classify(__instance, val); } float[] array = (isBonusHit ? new float[KillerRuntime.Effects.Length] : KillerRuntime.Values(val)); if (!flag && num <= 0f && num2 <= 0f && !array.Any((float x) => x > 0f)) { return true; } ZNetView val2 = Plugin.View(__instance); if ((Object)(object)val2 == (Object)null || !val2.IsValid()) { return true; } Plugin.Register(__instance); hit.m_weakSpot = (short)Plugin.FindWeakSpot.Invoke(__instance, new object[1] { Plugin.HitCollider.GetValue(hit) }); ZPackage val3 = new ZPackage(); hit.Serialize(ref val3); ZPackage val4 = new ZPackage(); long num3 = VitalityRuntime.Begin(val, __instance, hit, num2); val4.Write(isBonusHit); val4.Write(flag); val4.Write(Math.Min(Math.Max(num, 0f), 1000f)); val4.Write(num2); val4.Write(num3); float[] array2 = array; foreach (float num5 in array2) { val4.Write(num5); } val4.Write(val3); val2.InvokeRPC("ECE_DirectHit_v5", new object[1] { val4 }); return false; } } [HarmonyPatch(typeof(Character), "ApplyDamage")] internal static class CaptureDamagePatch { internal sealed class State { internal bool Capture; internal float Health; internal HitTag Tag; internal Character Target; internal double Nested; } [ThreadStatic] private static List<State> active; [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(Character __instance, HitData hit, out State __state) { __state = new State { Health = __instance.GetHealth(), Target = __instance }; if (active == null) { active = new List<State>(); } active.Add(__state); if (Plugin.Ready && Plugin.Tags.TryGetValue(hit, out var value) && !value.Wound && !value.Consumed && (!(value.Percent <= 0f) || !(value.VitalityCap <= 0f))) { ZNetView val = Plugin.View(__instance); if (!((Object)(object)val == (Object)null) && val.IsValid() && val.IsOwner() && Plugin.EligibleTarget(__instance)) { value.Consumed = true; __state.Capture = true; __state.Tag = value; } } } [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(Character __instance, State __state) { //IL_00e6: Unknown result type (might be due to invalid IL or missing references) if (__state == null) { return; } double num = Math.Max(0f, __state.Health - Math.Max(0f, __instance.GetHealth())); for (int num2 = active.IndexOf(__state) - 1; num2 >= 0; num2--) { if ((Object)(object)active[num2].Target == (Object)(object)__instance) { active[num2].Nested += num; break; } } active.Remove(__state); if (__state.Capture) { double num3 = Math.Max(0.0, num - __state.Nested); __state.Tag.ActualDamage = num3; if (!(num3 <= 0.0) && !(__state.Tag.Percent <= 0f) && Plugin.EligibleTarget(__instance) && Plugin.Trackers.TryGetValue(__instance, out var value)) { value.Record(__state.Tag.Attacker, num3, __state.Tag.Percent); } } } [HarmonyFinalizer] private static Exception Finalizer(Exception __exception, State __state) { if (__state != null && active != null) { active.Remove(__state); } return __exception; } [HarmonyTranspiler] private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Expected O, but got Unknown List<CodeInstruction> list = instructions.ToList(); MethodInfo methodInfo = AccessTools.Method(typeof(HitData), "ApplyModifier", new Type[1] { typeof(float) }, (Type[])null); MethodInfo operand = AccessTools.Method(typeof(Plugin), "ApplyModifierUnlessWound", (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(Plugin), "MinimumDamage", (Type[])null, (Type[])null); int num = 0; int num2 = 0; List<CodeInstruction> list2 = new List<CodeInstruction>(); foreach (CodeInstruction item in list) { if (CodeInstructionExtensions.Calls(item, methodInfo)) { item.opcode = OpCodes.Call; item.operand = operand; num++; } if (item.opcode == OpCodes.Ldc_R4 && item.operand is float && (float)item.operand == 0.1f) { item.opcode = OpCodes.Ldarg_1; item.operand = null; list2.Add(item); list2.Add(new CodeInstruction(OpCodes.Call, (object)methodInfo2)); num2++; } else { list2.Add(item); } } if (num != 3 || num2 != 1) { throw new InvalidOperationException("Unsupported ApplyDamage layout: expected 3 scale calls and 1 minimum threshold, found " + num + " / " + num2); } return list2; } } internal sealed class VitalityTracker { internal readonly Player Player; internal readonly ZNetView View; internal readonly VitalityState State = new VitalityState(); internal float BaseHealth; internal const string BonusKey = "ece_vitality_applied"; private static readonly int MaxHealthKey = (int)AccessTools.Field(typeof(ZDOVars), "s_maxHealth").GetValue(null); internal bool Owner { get { if ((Object)(object)View != (Object)null && View.IsValid()) { return View.IsOwner(); } return false; } } internal VitalityTracker(Player player, ZNetView view) { Player = player; View = view; float num = view.GetZDO().GetFloat("ece_vitality_applied", 0f); if (!WoundLedger.Finite(num) || num < 0f || num > 100000f) { num = 0f; } BaseHealth = Math.Max(1f, ((Character)player).GetMaxHealth() - num); if (num > 0f && view.IsOwner()) { Apply(); } } internal void Apply() { if (Owner) { float num = Math.Max(1f, BaseHealth + (float)State.Bonus); View.GetZDO().Set(MaxHealthKey, num); View.GetZDO().Set("ece_vitality_applied", (float)State.Bonus); if (((Character)Player).GetHealth() > num) { ((Character)Player).SetHealth(num); } } } internal void Expire(double now) { if (State.Expire(now)) { Apply(); } } internal void Confirm(double damage, double cap) { if (Owner && !((Character)Player).IsDead() && !(((Character)Player).GetHealth() <= 0f)) { double now = Plugin.Now; Expire(now); if (State.AddDamage(now, damage, cap)) { Apply(); } } } internal void Reset() { State.Reset(); Apply(); } internal void SetIncomingBase(ref float health) { if (Owner && WoundLedger.Finite(health)) { State.Expire(Plugin.Now); BaseHealth = health; health += (float)State.Bonus; View.GetZDO().Set("ece_vitality_applied", (float)State.Bonus); } } } internal struct DeferredHeal { internal float Amount; internal bool Show; } internal sealed class PendingVitalityHit { internal long Id; internal Player Player; internal Character Target; internal ZDOID TargetId; internal long TargetOwner; internal float Cap; internal ReceiptState Receipt; internal readonly List<DeferredHeal> Heals = new List<DeferredHeal>(); internal void Flush() { DeferredHeal[] array = Heals.ToArray(); Heals.Clear(); if (!((Object)(object)Player == (Object)null) && !((Object)(object)Player != (Object)(object)Player.m_localPlayer) && !((Character)Player).IsDead() && !(((Character)Player).GetHealth() <= 0f)) { DeferredHeal[] array2 = array; for (int i = 0; i < array2.Length; i++) { DeferredHeal deferredHeal = array2[i]; ((Character)Player).Heal(deferredHeal.Amount, deferredHeal.Show); } } } } internal static class VitalityRuntime { internal const string Effect = "Awakening"; internal const string ReceiptRpc = "ECE_VitalityReceipt_v1"; private static readonly ConditionalWeakTable<Player, VitalityTracker> Trackers = new ConditionalWeakTable<Player, VitalityTracker>(); private static readonly ConditionalWeakTable<HitData, PendingVitalityHit> Outgoing = new ConditionalWeakTable<HitData, PendingVitalityHit>(); private static readonly Dictionary<long, PendingVitalityHit> Pending = new Dictionary<long, PendingVitalityHit>(); private static long sequence = DateTime.UtcNow.Ticks; private static double nextUpdate; internal static void Register(Player player, ZNetView view) { if (!Trackers.TryGetValue(player, out var _)) { Trackers.Add(player, new VitalityTracker(player, view)); view.Register<ZPackage>("ECE_VitalityReceipt_v1", (Action<long, ZPackage>)delegate(long sender, ZPackage package) { ReceiveReceipt(player, sender, package); }); } } internal static VitalityTracker Get(Player player) { if ((Object)(object)player == (Object)null) { return null; } ZNetView val = Plugin.View((Character)(object)player); if ((Object)(object)val == (Object)null || !val.IsValid()) { return null; } Register(player, val); Trackers.TryGetValue(player, out var value); return value; } internal static float WeaponCap(Player player) { if (HitSource.IsBonusHit) { return 0f; } ItemData activeWeapon = MagicEffectsHelper.GetActiveWeapon(player); MagicItem val = ((activeWeapon == null) ? null : ItemDataExtensions.GetMagicItem(activeWeapon)); if (val == null) { return 0f; } float totalEffectValue = val.GetTotalEffectValue("Awakening", 1f, true); if (!WoundLedger.Finite(totalEffectValue) || totalEffectValue <= 0f) { return 0f; } return totalEffectValue; } internal static long Begin(Player player, Character target, HitData hit, float cap) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) if (cap <= 0f) { return 0L; } Get(player); ZNetView val = Plugin.View(target); PendingVitalityHit pendingVitalityHit = new PendingVitalityHit { Id = ++sequence, Player = player, Target = target, TargetId = target.GetZDOID(), TargetOwner = val.GetZDO().GetOwner(), Cap = cap, Receipt = new ReceiptState(Plugin.Now) }; Pending.Add(pendingVitalityHit.Id, pendingVitalityHit); Outgoing.Remove(hit); Outgoing.Add(hit, pendingVitalityHit); return pendingVitalityHit.Id; } internal static PendingVitalityHit ForHeal(HitData hit, Character attacker) { if (hit == null || !Outgoing.TryGetValue(hit, out var value) || !((Object)(object)value.Player == (Object)(object)attacker) || value.Receipt.Resolved) { return null; } return value; } public static void HealAfterConfirmation(Character attacker, float amount, bool show, HitData hit) { PendingVitalityHit pendingVitalityHit = (Plugin.Ready ? ForHeal(hit, attacker) : null); if (pendingVitalityHit != null && WoundLedger.Finite(amount) && amount > 0f) { pendingVitalityHit.Heals.Add(new DeferredHeal { Amount = amount, Show = show }); } else { attacker.Heal(amount, show); } } internal static void SendReceipt(HitTag tag, Character target) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Expected O, but got Unknown //IL_007e: Unknown result type (might be due to invalid IL or missing references) if (tag.RequestId != 0L && !(tag.VitalityCap <= 0f)) { GameObject val = (((Object)(object)ZNetScene.instance == (Object)null) ? null : ZNetScene.instance.FindInstance(tag.Attacker)); Player val2 = (((Object)(object)val == (Object)null) ? null : val.GetComponent<Player>()); ZNetView val3 = Plugin.View((Character)(object)val2); if (!((Object)(object)val3 == (Object)null) && val3.IsValid()) { Get(val2); ZPackage val4 = new ZPackage(); val4.Write(tag.RequestId); val4.Write(target.GetZDOID()); val4.Write(tag.ActualDamage); val3.InvokeRPC("ECE_VitalityReceipt_v1", new object[1] { val4 }); } } } private static void ReceiveReceipt(Player player, long sender, ZPackage package) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) ZNetView val = Plugin.View((Character)(object)player); if (!Plugin.Ready || (Object)(object)val == (Object)null || !val.IsValid() || !val.IsOwner() || (Object)(object)player != (Object)(object)Player.m_localPlayer) { return; } double num; PendingVitalityHit value; try { long key = package.ReadLong(); ZDOID val2 = package.ReadZDOID(); num = package.ReadDouble(); if (!Pending.TryGetValue(key, out value) || (Object)(object)value.Player != (Object)(object)player || value.TargetId != val2) { return; } ZNetView val3 = Plugin.View(value.Target); if ((sender != value.TargetOwner && (!((Object)(object)val3 != (Object)null) || !val3.IsValid() || val3.GetZDO().GetOwner() != sender)) || !WoundLedger.Finite(num) || num < 0.0) { return; } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Invalid vitality receipt: " + ex.Message)); return; } if (value.Receipt.Timeout(Plugin.Now)) { Pending.Remove(value.Id); value.Flush(); } else if (value.Receipt.Resolve()) { Pending.Remove(value.Id); Get(player)?.Confirm(num, value.Cap); value.Flush(); } } internal static void Update() { double now = Plugin.Now; if (now < nextUpdate) { return; } nextUpdate = now + 0.05; Player localPlayer = Player.m_localPlayer; VitalityTracker vitalityTracker = Get(localPlayer); if (vitalityTracker != null && vitalityTracker.Owner) { if (((Character)localPlayer).IsDead() || ((Character)localPlayer).GetHealth() <= 0f) { Cancel(localPlayer); if (vitalityTracker.State.Bonus > 0.0) { vitalityTracker.Reset(); } } else { vitalityTracker.Expire(now); } } List<long> list = new List<long>(); foreach (KeyValuePair<long, PendingVitalityHit> item in Pending) { if (item.Value.Receipt.Timeout(now)) { list.Add(item.Key); } } foreach (long item2 in list) { PendingVitalityHit pendingVitalityHit = Pending[item2]; Pending.Remove(item2); pendingVitalityHit.Flush(); Plugin.Log.LogWarning((object)"Vitality damage confirmation timed out; original life steal released without a new health bonus."); } } internal static void Cancel(Player player) { List<long> list = new List<long>(); foreach (KeyValuePair<long, PendingVitalityHit> item in Pending) { if ((Object)(object)item.Value.Player == (Object)(object)player) { list.Add(item.Key); } } foreach (long item2 in list) { Pending[item2].Receipt.Resolve(); Pending[item2].Heals.Clear(); Pending.Remove(item2); } } internal static void Shutdown() { Get(Player.m_localPlayer)?.Reset(); Pending.Clear(); } } [HarmonyPatch(typeof(Character), "SetMaxHealth", new Type[] { typeof(float) })] internal static class VitalityMaxHealthPatch { [HarmonyPrefix] [HarmonyPriority(0)] private static void Prefix(Character __instance, ref float health) { if (Plugin.Ready) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (!((Object)(object)val == (Object)null)) { VitalityRuntime.Get(val)?.SetIncomingBase(ref health); } } } } [HarmonyPatch(typeof(Player), "OnDeath")] internal static class VitalityDeathPatch { [HarmonyPrefix] private static void Prefix(Player __instance) { if (Plugin.Ready) { VitalityRuntime.Cancel(__instance); VitalityRuntime.Get(__instance)?.Reset(); } } } [HarmonyPatch(typeof(AddLifeSteal), "CheckAndDoLifeSteal")] internal static class VitalityLifeStealOrderPatch { [HarmonyTranspiler] private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(Character), "Heal", new Type[2] { typeof(float), typeof(bool) }, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(VitalityRuntime), "HealAfterConfirmation", (Type[])null, (Type[])null); List<CodeInstruction> list = new List<CodeInstruction>(); int num = 0; foreach (CodeInstruction instruction in instructions) { if (CodeInstructionExtensions.Calls(instruction, methodInfo)) { instruction.opcode = OpCodes.Ldarg_0; instruction.operand = null; list.Add(instruction); list.Add(new CodeInstruction(OpCodes.Call, (object)methodInfo2)); num++; } else { list.Add(instruction); } } if (num != 1) { throw new InvalidOperationException("Unsupported EpicLoot LifeSteal layout: expected one Heal call, found " + num); } return list; } } internal static class EclipseDisplay { private sealed class Opacity { internal object Field; internal PropertyInfo ColorProperty; internal float Scale; } private const string Rpc = "ECE_EclipseText_v2"; private static ZRoutedRpc router; private static bool sent; private static bool received; private static bool rendered; private static float nextWarning; private static readonly ConditionalWeakTable<object, Opacity> opacities = new ConditionalWeakTable<object, Opacity>(); private static readonly FieldInfo MaxDistance = AccessTools.Field(typeof(DamageText), "m_maxTextDistance"); internal static void Update() { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null && instance != router) { instance.Register<ZPackage>("ECE_EclipseText_v2", (Action<long, ZPackage>)Receive); router = instance; sent = (received = (rendered = false)); } } internal static void Show(Vector3 point, double actual, ZDOID attacker) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Expected O, but got Unknown //IL_005e: Unknown result type (might be due to invalid IL or missing references) _ = LiveSettings.Current.Eclipse; if (!WoundLedger.Finite(actual) || actual <= 0.0) { return; } try { Update(); if (router == null) { Warn("No routed RPC available for Eclipse text", null); return; } ZPackage val = new ZPackage(); val.Write(((ZDOID)(ref attacker)).UserID); val.Write(((ZDOID)(ref attacker)).ID); val.Write(point); val.Write((float)actual); router.InvokeRoutedRPC(0L, "ECE_EclipseText_v2", new object[1] { val }); if (!sent) { sent = true; Plugin.Log.LogDebug((object)("[EclipseText] sent first damage event: " + actual)); } } catch (Exception ex) { Warn("Could not send Eclipse text", ex); } } private static void Receive(long sender, ZPackage package) { //IL_000f: 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_002d: 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_0073: 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_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_00ce: 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_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_024f: Unknown result type (might be due to invalid IL or missing references) try { long attackerUser = package.ReadLong(); uint attackerObject = package.ReadUInt(); Vector3 val = package.ReadVector3(); float num = package.ReadSingle(); if (!WoundLedger.Finite(num) || num <= 0f || !Finite(val)) { return; } if (!received) { received = true; Plugin.Log.LogDebug((object)"[EclipseText] received first damage event"); } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } ZDOID zDOID = ((Character)localPlayer).GetZDOID(); bool own = EclipseAudience.IsOwnDamage(attackerUser, attackerObject, ((ZDOID)(ref zDOID)).UserID, ((ZDOID)(ref zDOID)).ID); if (!LiveSettings.Current.Eclipse.ShowDamageText) { return; } EclipseOptions eclipse = LiveSettings.Current.Eclipse; val += new Vector3((float)eclipse.TextHorizontalOffset, (float)eclipse.TextVerticalOffset, 0f); DamageText instance = DamageText.instance; Camera mainCamera = Utils.GetMainCamera(); if ((Object)(object)instance == (Object)null || (Object)(object)mainCamera == (Object)null || Hud.IsUserHidden()) { return; } float num2 = Vector3.Distance(((Component)mainCamera).transform.position, val); float num3 = ((MaxDistance == null) ? 60f : ((float)MaxDistance.GetValue(instance))); if (num2 > num3) { return; } IList list = (IList)AccessTools.Field(typeof(DamageText), "m_worldTexts").GetValue(instance); int count = list.Count; EclipseOriginalRenderer.Add(instance, (TextType)0, val, num2, LiveSettings.Current.Eclipse.FormatDamage(num), mySelf: false); if (list.Count > count) { object obj = list[list.Count - 1]; object value = AccessTools.Field(obj.GetType(), "m_textField").GetValue(obj); PropertyInfo propertyInfo = AccessTools.Property(value.GetType(), "color"); string hex = LiveSettings.Current.Eclipse.DamageTextColor.Substring(1); Func<int, float> func = (int i) => (float)(int)byte.Parse(hex.Substring(i, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture) / 255f; float num4 = EclipseAudience.OpacityScale(own, (hex.Length == 8) ? func(6) : 1f); propertyInfo.SetValue(value, (object)new Color(func(0), func(2), func(4), num4), null); opacities.Add(obj, new Opacity { Field = value, ColorProperty = propertyInfo, Scale = num4 }); } if (!rendered) { rendered = true; Plugin.Log.LogDebug((object)"[EclipseText] rendered first damage event (1.0.11)"); } } catch (Exception ex) { Warn("Could not render Eclipse text", ex); } } internal static void ApplyOpacity(DamageText ui) { //IL_004f: 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_0072: Unknown result type (might be due to invalid IL or missing references) try { foreach (object item in (IList)AccessTools.Field(typeof(DamageText), "m_worldTexts").GetValue(ui)) { if (opacities.TryGetValue(item, out var value)) { Color val = (Color)value.ColorProperty.GetValue(value.Field, null); val.a *= value.Scale; value.ColorProperty.SetValue(value.Field, val, null); } } } catch (Exception ex) { Warn("Could not update Eclipse opacity", ex); } } private static bool Finite(Vector3 p) { //IL_0000: 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_001c: Unknown result type (might be due to invalid IL or missing references) if (WoundLedger.Finite(p.x) && WoundLedger.Finite(p.y)) { return WoundLedger.Finite(p.z); } return false; } private static void Warn(string message, Exception ex) { if (!(Time.realtimeSinceStartup < nextWarning)) { nextWarning = Time.realtimeSinceStartup + 10f; Plugin.Log.LogWarning((object)("[EclipseText] " + message + ((ex == null) ? "" : (" : " + ex)))); } } } [HarmonyPatch(typeof(DamageText), "UpdateWorldTexts")] internal static class EclipseOpacityPatch { [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(DamageText __instance) { EclipseDisplay.ApplyOpacity(__instance); } } [HarmonyPatch(typeof(DamageText), "AddInworldText")] internal static class EclipseOriginalRenderer { [HarmonyReversePatch(/*Could not decode attribute arguments.*/)] internal static void Add(DamageText instance, TextType type, Vector3 pos, float distance, string text, bool mySelf) { throw new NotImplementedException("Original damage text renderer was not bound"); } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] internal static class EclipseTextRegistration { [HarmonyPostfix] private static void Postfix() { EclipseDisplay.Update(); } } internal static class EffectIdentity { internal static string Canonical(string type) { if (type == "CumulativeWounds") { return "Eclipse"; } if (type == "VitalityAccumulation") { return "Awakening"; } return type; } } [HarmonyPatch(typeof(ItemDataExtensions), "GetMagicItem")] internal static class LegacyEffectIdentityPatch { [HarmonyPostfix] private static void Postfix(MagicItem __result) { if (__result == null || __result.Effects == null) { return; } foreach (MagicItemEffect effect in __result.Effects) { if (effect != null) { effect.EffectType = EffectIdentity.Canonical(effect.EffectType); } } } } public sealed class TargetRules { public static readonly string[] BiomeNames = new string[9] { "Meadows", "BlackForest", "Swamp", "Mountain", "Plains", "Mistlands", "Ashlands", "DeepNorth", "Ocean" }; public bool UseGameBossFlag = true; public string[] AdditionalBosses = new string[0]; public string[] ExcludedBosses = new string[0]; public Dictionary<string, string[]> Biomes = new Dictionary<string, string[]>(); [JsonIgnore] private HashSet<string> added; [JsonIgnore] private HashSet<string> excluded; [JsonIgnore] private Dictionary<string, string> membership; public void Validate() { if (AdditionalBosses == null || ExcludedBosses == null || Biomes == null) { throw new ArgumentException("Missing target lists"); } added = List(AdditionalBosses); excluded = List(ExcludedBosses); membership = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); foreach (KeyValuePair<string, string[]> biome in Biomes) { if (!BiomeNames.Contains(biome.Key)) { throw new ArgumentException("Unknown biome: " + biome.Key); } foreach (string item in List(biome.Value)) { if (membership.ContainsKey(item)) { throw new ArgumentException("Prefab belongs to multiple biomes: " + item); } membership.Add(item, biome.Key); } } string[] biomeNames = BiomeNames; foreach (string text in biomeNames) { if (!Biomes.ContainsKey(text)) { throw new ArgumentException("Missing biome: " + text); } } } private static HashSet<string> List(string[] values) { if (values == null || values.Length > 2048) { throw new ArgumentException("Invalid prefab list (maximum 2048 names)"); } HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); foreach (string text in values) { string text2 = ((text == null) ? "" : text.Trim()); if (text2.Length == 0 || text2.Length > 160 || !Regex.IsMatch(text2, "^[\\w.\\-]+$")) { throw new ArgumentException("Use exact prefab IDs, not display names: " + text2); } hashSet.Add(text2); } return hashSet; } public string EffectFor(string prefab, bool gameBoss, bool hostile, bool excludedTarget) { if (excludedTarget) { return null; } if (added == null) { Validate(); } if (!excluded.Contains(prefab) && (added.Contains(prefab) || (UseGameBossFlag && gameBoss))) { return "GiantKiller"; } if (!hostile) { return null; } if (!membership.TryGetValue(prefab, out var value)) { return null; } return "MinorKiller" + value; } public static double Multiplier(double percent) { if (!WoundLedger.Finite(percent) || !(percent > 0.0) || !(percent <= 1000.0)) { return 1.0; } return 1.0 + percent / 100.0; } } internal static class KillerRuntime { internal static readonly string[] Effects = new string[1] { "GiantKiller" }.Concat(TargetRules.BiomeNames.Select((string x) => "MinorKiller" + x)).ToArray(); private static readonly HashSet<string> reported = new HashSet<string>(); internal static string Classify(Character target, Player player) { string text = ((Object)((Component)target).gameObject).name; if (text.EndsWith("(Clone)", StringComparison.Ordinal)) { text = text.Substring(0, text.Length - 7); } text = text.Trim(); bool hostile = target.GetBaseAI() is MonsterAI && BaseAI.IsEnemy(target, (Character)(object)player); string text2 = LiveSettings.Rules.EffectFor(text, target.IsBoss(), hostile, target.IsPlayer() || target.IsTamed() || target.IsDead()); if (ConfigBridge.Diagnostics && reported.Count < 4096 && reported.Add(text + ":" + text2)) { Plugin.Log.LogInfo((object)("[TargetClassification] " + text + " => " + (text2 ?? "unclassified/non-hostile") + "; gameBoss=" + target.IsBoss() + "; hostile=" + hostile)); } return text2; } internal static float[] Values(Player player) { return Effects.Select(delegate(string effect) { float totalActiveMagicEffectValue = PlayerExtensions.GetTotalActiveMagicEffectValue(player, effect, 1f, (ItemData)null); return (!WoundLedger.Finite(totalActiveMagicEffectValue)) ? 0f : Math.Min(1000f, Math.Max(0f, totalActiveMagicEffectValue)); }).ToArray(); } internal static void Apply(Character target, Player player, HitData hit, float[] values) { if (LiveSettings.IsRemote && !LiveSettings.HasServerSettings) { return; } string value = Classify(target, player); int num = Array.IndexOf(Effects, value); if (num >= 0) { double num2 = TargetRules.Multiplier(values[num]); if (num2 > 1.0) { hit.ApplyModifier((float)num2); } } } } internal sealed class ConfigurationManagerAttributes { public bool ReadOnly; public int Order; } internal static class ConfigBridge { private static ConfigFile config; private static bool pushing; private static float next; private static float lastEdit; private static bool collecting; private static TargetRules draftRules; private static RuntimeOptions draftDisplay; private static readonly Dictionary<string, Action> pending = new Dictionary<string, Action>(); private static readonly List<Action> refresh = new List<Action>(); private static readonly List<ConfigurationManagerAttributes> ruleTags = new List<ConfigurationManagerAttributes>(); internal static bool Diagnostics => LiveSettings.Current.Diagnostics; private static T Copy<T>(T value) { return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject((object)value)); } internal static void Initialize(ConfigFile file) { config = file; config.SaveOnConfigSet = false; Bind("01. 서버 몬스터 분류", "기본 보스 판정 사용", initial: true, server: true, () => LiveSettings.Rules.UseGameBossFlag, delegate(bool v) { Rule(delegate(TargetRules r) { r.UseGameBossFlag = v; }); }, "게임의 보스 플래그를 사용합니다. 제외 목록이 추가 목록보다 우선합니다."); Bind("01. 서버 몬스터 분류", "보스 추가 목록", "", server: true, () => Join(LiveSettings.Rules.AdditionalBosses), delegate(string v) { Rule(delegate(TargetRules r) { r.AdditionalBosses = Split(v); }); }, "보스로 취급할 정확한 프리팹 ID. 쉼표로 구분합니다. 번역된 이름은 사용하지 마세요."); Bind("01. 서버 몬스터 분류", "보스 제외 목록", "", server: true, () => Join(LiveSettings.Rules.ExcludedBosses), delegate(string v) { Rule(delegate(TargetRules r) { r.ExcludedBosses = Split(v); }); }, "보스에서 제외할 프리팹 ID. 해당 바이옴에 등록되어 있고 적대적이면 마이너 킬러 대상입니다."); string[] array = new string[9] { "초원", "검은숲", "늪", "산", "평원", "안개 지대", "잿가루 지대", "심층 북부", "바다" }; for (int num = 0; num < TargetRules.BiomeNames.Length; num++) { string biome = TargetRules.BiomeNames[num]; Bind("02. 서버 바이옴 소속", array[num], "", server: true, () => Join(LiveSettings.Rules.Biomes[biome]), delegate(string v) { Rule(delegate(TargetRules r) { r.Biomes[biome] = Split(v); }); }, "쉼표로 구분한 프리팹 ID. 전투 장소와 무관하게 이 바이옴 소속으로 판정합니다. 한 몬스터는 한 바이옴만 허용합니다. 빈 목록은 해당 효과의 새 추첨을 비활성화합니다."); } Bind("03. 개인 이클립스 표시", "피해 표시", initial: true, server: false, () => LiveSettings.Current.Eclipse.ShowDamageText, delegate(bool v) { Display(delegate(RuntimeOptions o) { o.Eclipse.ShowDamageText = v; }); }, "나에게 보이는 이클립스 피해 텍스트 표시 여부입니다."); Bind("03. 개인 이클립스 표시", "색상", "#C77DFF", server: false, () => LiveSettings.Current.Eclipse.DamageTextColor, delegate(string v) { Display(delegate(RuntimeOptions o) { o.Eclipse.DamageTextColor = v; }); }, "#RRGGBB 형식. 예: #C77DFF, #00FF00."); Bind("03. 개인 이클립스 표시", "표시 문구", "HP -{damage}", server: false, () => LiveSettings.Current.Eclipse.DamageTextFormat, delegate(string v) { Display(delegate(RuntimeOptions o) { o.Eclipse.DamageTextFormat = v; }); }, "{damage}를 반드시 포함하세요. 색상 태그 없이 일반 텍스트를 입력합니다."); Bind("03. 개인 이클립스 표시", "소수 자릿수", 1, server: false, () => LiveSettings.Current.Eclipse.DamageTextDecimals, delegate(int v) { Display(delegate(RuntimeOptions o) { o.Eclipse.DamageTextDecimals = v; }); }, "0~3자리", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 3)); Bind("03. 개인 이클립스 표시", "세로 위치", 0.8, server: false, () => LiveSettings.Current.Eclipse.TextVerticalOffset, delegate(double v) { Display(delegate(RuntimeOptions o) { o.Eclipse.TextVerticalOffset = v; }); }, "적의 중심에서 위아래 이동", (AcceptableValueBase)(object)new AcceptableValueRange<double>(-10.0, 10.0)); Bind("03. 개인 이클립스 표시", "가로 위치", 0.65, server: false, () => LiveSettings.Current.Eclipse.TextHorizontalOffset, delegate(double v) { Display(delegate(RuntimeOptions o) { o.Eclipse.TextHorizontalOffset = v; }); }, "표시의 좌우 이동", (AcceptableValueBase)(object)new AcceptableValueRange<double>(-10.0, 10.0)); Bind("04. 진단", "몬스터 분류 로그", initial: false, server: false, () => LiveSettings.Current.Diagnostics, delegate(bool v) { Display(delegate(RuntimeOptions o) { o.Diagnostics = v; }); }, "공격한 몬스터의 프리팹 ID와 분류를 BepInEx/LogOutput.log에 기록합니다. 같은 분류는 한 번만 기록됩니다."); Update(); } private static string Join(string[] x) { return string.Join(", ", x); } private static string[] Split(string x) { return (from v in (x ?? "").Split(new char[4] { ',', ';', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries) select v.Trim() into v where v.Length > 0 select v).ToArray(); } private static void Rule(Action<TargetRules> edit) { if (!collecting) { throw new InvalidOperationException("Edit outside transaction"); } if (draftRules == null) { draftRules = Copy(LiveSettings.Rules); } edit(draftRules); } private static void Display(Action<RuntimeOptions> edit) { if (!collecting) { throw new InvalidOperationException("Edit outside transaction"); } if (draftDisplay == null) { draftDisplay = Copy(LiveSettings.Current); } edit(draftDisplay); } private static void Bind<T>(string section, string key, T initial, bool server, Func<T> get, Action<T> set, string description, AcceptableValueBase range = null) { //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Expected O, but got Unknown ConfigurationManagerAttributes configurationManagerAttributes = new ConfigurationManagerAttributes { ReadOnly = (server && !LiveSettings.CanEditRules), Order = 0 }; if (server) { ruleTags.Add(configurationManagerAttributes); } ConfigEntry<T> entry = config.Bind<T>(section, key, initial, new ConfigDescription(description, range, new object[1] { configurationManagerAttributes })); Action action = delegate { pushing = true; try { entry.Value = get(); } finally { pushing = false; } }; refresh.Add(action); action(); entry.SettingChanged += delegate { if (!pushing) { T value = entry.Value; pending[section + "/" + key] = delegate { set(value); }; lastEdit = Time.realtimeSinceStartup; } }; } internal static void Update() { if (config == null || Time.realtimeSinceStartup < next) { return; } next = Time.realtimeSinceStartup + 0.5f; foreach (ConfigurationManagerAttributes ruleTag in ruleTags) { ruleTag.ReadOnly = !LiveSettings.CanEditRules; } if (pending.Count > 0) { if (Time.realtimeSinceStartup - lastEdit < 1f) { return; } try { collecting = true; draftRules = null; draftDisplay = null; foreach (Action value in pending.Values) { value(); } if (draftRules != null) { draftRules.Validate(); } if (draftDisplay != null) { draftDisplay.Validate(); } if (draftRules != null) { LiveSettings.EditRules(draftRules); } if (draftDisplay != null) { LiveSettings.EditDisplay(draftDisplay); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Configuration changes rejected: " + ex.Message)); } finally { collecting = false; pending.Clear(); draftRules = null; draftDisplay = null; } } foreach (Action item in refresh) { item(); } } } internal static class ConfigStorage { internal static void Initialize(string root, string legacy, IEnumerable<string> files, Func<string, string> embedded) { Directory.CreateDirectory(root); foreach (string file in files) { string text = Path.Combine(root, file); if (File.Exists(text)) { continue; } Directory.CreateDirectory(Path.GetDirectoryName(text)); string text2 = Candidates(root, legacy, file).FirstOrDefault(File.Exists); byte[] bytes = ((text2 == null) ? new UTF8Encoding(encoderShouldEmitUTF8Identifier: false).GetBytes(embedded(file)) : File.ReadAllBytes(text2)); string text3 = text + "." + Guid.NewGuid().ToString("N") + ".tmp"; try { File.WriteAllBytes(text3, bytes); File.Move(text3, text); } finally { if (File.Exists(text3)) { File.Delete(text3); } } } } internal static void ArchiveLegacy(string root, string legacy, IEnumerable<string> files) { string[] array = files.Concat(new string[4] { "Korean.json", "English.json", "effects/CumulativeWounds.json", "effects/VitalityAccumulation.json" }).Distinct().ToArray(); string path = Path.Combine(root, "migration-backup", DateTime.UtcNow.ToString("yyyyMMdd-HHmmss") + "-" + Guid.NewGuid().ToString("N").Substring(0, 8)); string[] array2 = array; foreach (string path2 in array2) { string text = Path.Combine(legacy, path2); if (File.Exists(text)) { string text2 = Path.Combine(path, path2); Directory.CreateDirectory(Path.GetDirectoryName(text2)); File.Move(text, text2); } } array2 = new string[2] { "effects", "localizations" }; foreach (string path3 in array2) { string path4 = Path.Combine(legacy, path3); if (Directory.Exists(path4) && !Directory.EnumerateFileSystemEntries(path4).Any()) { Directory.Delete(path4); } } } private static IEnumerable<string> Candidates(string root, string legacy, string name) { if (name.StartsWith("localizations/", StringComparison.Ordinal)) { yield return Path.Combine(root, Path.GetFileName(name)); } yield return Path.Combine(legacy, name); if (name.StartsWith("localizations/", StringComparison.Ordinal)) { yield return Path.Combine(legacy, Path.GetFileName(name)); } string old = ((name == "effects/Eclipse.json") ? "effects/CumulativeWounds.json" : ((name == "effects/Awakening.json") ? "effects/VitalityAccumulation.json" : null)); if (old != null) { yield return Path.Combine(root, old); yield return Path.Combine(legacy, old); } } } internal sealed class BonusDamageContext : IDisposable { private static readonly FieldInfo Depth = AccessTools.Field(typeof(HitSource), "_bonusDepth"); private readonly int previous; private readonly bool enabled; private BonusDamageContext(bool bonus) { enabled = bonus; if (enabled) { if (Depth == null) { throw new InvalidOperationException("Unsupported EpicLoot bonus-hit context."); } previous = (int)Depth.GetValue(null); Depth.SetValue(null, previous + 1); } } internal static IDisposable Enter(bool bonus) { return new BonusDamageContext(bonus); } public void Dispose() { if (enabled) { Depth.SetValue(null, previous); } } } [HarmonyPatch] internal static class ShardLightningAttributionPatch { private static MethodBase TargetMethod() { return AccessTools.Method(AccessTools.TypeByName("EpicLoot.MagicItemEffects.Shards.StrikeCausesLightning"), "OnDamageDealt", (Type[])null, (Type[])null); } [HarmonyTranspiler] private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Expected O, but got Unknown List<CodeInstruction> list = instructions.ToList(); MethodInfo original = AccessTools.Method(typeof(DamageInRadius), "DamageEnemiesInRadius", new Type[4] { typeof(Player), typeof(Vector3), typeof(float), typeof(DamageTypes) }, (Type[])null); MethodInfo methodInfo = AccessTools.Method(typeof(ShardLightningAttributionPatch), "DamageWithAttacker", (Type[])null, (Type[])null); int num = list.Count((CodeInstruction x) => CodeInstructionExtensions.Calls(x, original)); if (num != 1) { throw new InvalidOperationException("Unsupported EpicLoot shard-lightning call signature: expected one DamageEnemiesInRadius call, found " + num); } List<CodeInstruction> list2 = new List<CodeInstruction>(); foreach (CodeInstruction item in list) { if (CodeInstructionExtensions.Calls(item, original)) { item.opcode = OpCodes.Ldarg_2; item.operand = null; list2.Add(item); list2.Add(new CodeInstruction(OpCodes.Call, (object)methodInfo)); } else { list2.Add(item); } } return list2; } private static void DamageWithAttacker(Player originalAttacker, Vector3 center, float radius, DamageTypes damage, Character callbackAttacker) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) DamageInRadius.DamageEnemiesInRadius((Player)(((Object)(object)originalAttacker != (Object)null) ? ((object)originalAttacker) : ((object)((callbackAttacker is Player) ? callbackAttacker : null))), center, radius, damage); } } internal static class PrecisionStrikeRuntime { internal const string Effect = "PrecisionStrike"; internal static bool HasEffect(Player player) { if ((Object)(object)player == (Object)null || HitSource.IsBonusHit) { return false; } ItemData activeWeapon = MagicEffectsHelper.GetActiveWeapon(player); MagicItem val = ((activeWeapon == null) ? null : ItemDataExtensions.GetMagicItem(activeWeapon)); if (val != null) { return val.HasEffect("PrecisionStrike", false, false); } return false; } internal static bool Applies(HitData hit) { if (Plugin.Ready && hit != null && Plugin.Tags.TryGetValue(hit, out var value) && value.Precision && !value.Bonus) { return !value.Wound; } return false; } } [HarmonyPatch(typeof(HitData), "ApplyResistance")] internal static class PrecisionStrikeResistancePatch { [HarmonyPrefix] [HarmonyPriority(0)] private static void Prefix(HitData __instance, ref DamageModifiers modifiers) { if (PrecisionStrikeRuntime.Applies(__instance)) { PrecisionStrikePolicy.Promote(ref modifiers); } } } internal static class LocalizationDefaults { internal static void AddPrecisionKeys(string root, Func<string, string> embedded) { string[] array = new string[2] { "Korean", "English" }; foreach (string text in array) { string text2 = "localizations/" + text + ".json"; string text3 = Path.Combine(root, text2); Dictionary<string, string> dictionary = JsonConvert.DeserializeObject<Dictionary<string, string>>(File.ReadAllText(text3)); Dictionary<string, string> dictionary2 = JsonConvert.DeserializeObject<Dictionary<string, string>>(embedded(text2)); if (dictionary == null || dictionary2 == null) { throw new InvalidDataException("Invalid localization: " + text2); } bool flag = false; foreach (KeyValuePair<string, string> item in dictionary2) { if (item.Key.StartsWith("ece_precisionstrike_", StringComparison.Ordinal) && !dictionary.ContainsKey(item.Key)) { dictionary.Add(item.Key, item.Value); flag = true; } } if (!flag) { continue; } string text4 = text3 + "." + Guid.NewGuid().ToString("N") + ".tmp"; try { File.WriteAllText(text4, JsonConvert.SerializeObject((object)dictionary, (Formatting)1) + Environment.NewLine); File.Replace(text4, text3, null); } finally { if (File.Exists(text4)) { File.Delete(text4); } } } } } internal static class PrecisionStrikePolicy { internal static DamageModifier Weakness(DamageModifier original) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0006: Unknown result type (might be due to invalid IL or missing references) if ((int)original != 6) { return (DamageModifier)2; } return original; } internal static void Promote(ref DamageModifiers mods) { //IL_0002: 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_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //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_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) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0046: 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_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: 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_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0083: 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) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009b: 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_00a5: Unknown result type (might be due to invalid IL or missing references) mods.m_blunt = Weakness(mods.m_blunt); mods.m_slash = Weakness(mods.m_slash); mods.m_pierce = Weakness(mods.m_pierce); mods.m_chop = Weakness(mods.m_chop); mods.m_pickaxe = Weakness(mods.m_pickaxe); mods.m_fire = Weakness(mods.m_fire); mods.m_frost = Weakness(mods.m_frost); mods.m_lightning = Weakness(mods.m_lightning); mods.m_poison = Weakness(mods.m_poison); mods.m_spirit = Weakness(mods.m_spirit); } } internal static class EclipseAudience { internal static float OpacityScale(bool own, float configuredAlpha) { return configuredAlpha * (own ? 1f : 0.45f); } internal static bool IsOwnDamage(long attackerUser, uint attackerObject, long localUser, uint localObject) { if (attackerObject != 0 && localObject != 0 && attackerUser == localUser) { return attackerObject == localObject; } return false; } } public sealed class WoundClock { private readonly Dictionary<string, double> next = new Dictionary<string, double>(); public void Reset() { next.Clear(); } public List<WoundPayment> DrainDue(WoundLedger ledger, double now, double interval) { List<WoundPayment> list = new List<WoundPayment>(); if (!WoundLedger.Finite(now) || !WoundLedger.Finite(interval) || interval <= 0.0) { return list; } HashSet<string> hashSet = new HashSet<string>(ledger.Windows.Select((WoundWindow w) => w.Key)); string[] array = next.Keys.ToArray(); foreach (string text in array) { if (!hashSet.Contains(text)) { next.Remove(text); } } foreach (string key in hashSet) { if (next.TryGetValue(key, out var value) && now < value) { continue; } list.AddRange(ledger.Drain(now, key)); WoundWindow[] array2 = ledger.Windows.Where((WoundWindow w) => w.Key == key).ToArray(); if (array2.Length == 0) { next.Remove(key); continue; } next[key] = Math.Min(now + interval, array2.Min((WoundWindow w) => w.End)); } return list; } } }