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 PoeTweaks v0.6.14
BepInEx/plugins/PoeTweaks/PoeTweaks.dll
Decompiled 2 hours 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.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.Threading; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Jotunn.Configs; using Jotunn.Entities; using Jotunn.Extensions; using Jotunn.Managers; using Jotunn.Utils; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using SoftReferenceableAssets; using UnityEngine; using UnityEngine.Networking; using UnityEngine.Rendering; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: InternalsVisibleTo("PoeTweaks.Tests")] [assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")] [assembly: AssemblyCompany("PoeTweaks")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.6.14.0")] [assembly: AssemblyInformationalVersion("0.6.14+04c4bd7d5750f1260599959a1c684a48413ced2d")] [assembly: AssemblyProduct("PoeTweaks")] [assembly: AssemblyTitle("PoeTweaks")] [assembly: AssemblyVersion("0.6.14.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] internal sealed class IsReadOnlyAttribute : Attribute { } [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 PoeTweaks { internal sealed class Atlas : IDisposable { [HarmonyPatch(typeof(MapTable), "OnRead", new Type[] { typeof(Switch), typeof(Humanoid), typeof(ItemData) })] private static class LocalRead { [HarmonyPriority(800)] private static void Prefix(Humanoid user, out bool __state) { __state = readingLocal; readingLocal = (object)user == Player.m_localPlayer; } private static void Finalizer(bool __state) { readingLocal = __state; } } [HarmonyPatch(typeof(Minimap), "Explore", new Type[] { typeof(Vector3), typeof(float) })] private static class StopExploration { private static bool Prefix() { if (IsOpen) { return Plugin.Atlas.rendering; } return true; } } [HarmonyPatch(typeof(Minimap), "UpdateMap")] private static class KeepView { [HarmonyAfter(new string[] { "ZenDragon.ZenMap" })] private static void Prefix(Minimap __instance, Player player) { //IL_0017: 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_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) if (IsOpen) { Offset.Invoke(__instance) = Plugin.Atlas.focus - ((Component)player).transform.position; Plugin.Atlas.HideOverlays(__instance); } } private static void Postfix(Minimap __instance, Player player) { //IL_0012: 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_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) if (IsOpen) { Plugin.Atlas.focus = ((Component)player).transform.position + Offset.Invoke(__instance); Plugin.Atlas.zoom = __instance.LargeZoom; } } } [HarmonyPatch(typeof(Minimap), "SetMapMode")] private static class CloseView { [HarmonyAfter(new string[] { "ZenDragon.ZenMap" })] private static void Postfix(Minimap __instance) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Invalid comparison between Unknown and I4 Atlas atlas = Plugin.Atlas; if (atlas != null && atlas.opened && (int)__instance.m_mode != 2) { Plugin.Atlas.Close(); } } } [HarmonyPatch(typeof(Minimap), "Update")] private static class HideViewOverlays { [HarmonyAfter(new string[] { "ZenDragon.ZenMap" })] private static void Postfix(Minimap __instance) { if (IsOpen) { Plugin.Atlas.HideOverlays(__instance); return; } Atlas atlas = Plugin.Atlas; if (atlas != null && atlas.opened) { Plugin.Atlas.Close(); } } } [HarmonyPatch] private static class StopPinActions { private static IEnumerable<MethodBase> TargetMethods() { return new MethodInfo[6] { AccessTools.Method(typeof(Minimap), "OnMapDblClick", (Type[])null, (Type[])null), AccessTools.Method(typeof(Minimap), "OnMapLeftClick", (Type[])null, (Type[])null), AccessTools.Method(typeof(Minimap), "OnMapMiddleClick", (Type[])null, (Type[])null), AccessTools.Method(typeof(Minimap), "ShowPinNameInput", (Type[])null, (Type[])null), AccessTools.Method(typeof(Minimap), "ShowPointOnMap", (Type[])null, (Type[])null), AccessTools.Method(typeof(Minimap), "UpdatePlayerMarker", (Type[])null, (Type[])null) }; } [HarmonyPriority(0)] [HarmonyAfter(new string[] { "ZenDragon.ZenMap" })] private static void Prefix(ref bool __runOriginal) { if (IsOpen) { __runOriginal = false; } } } [HarmonyPatch] private static class WorldReset { private static IEnumerable<MethodBase> TargetMethods() { return new MethodInfo[2] { AccessTools.Method(typeof(Minimap), "LoadMapData", (Type[])null, (Type[])null), AccessTools.Method(typeof(Minimap), "OnDestroy", (Type[])null, (Type[])null) }; } private static void Postfix() { Plugin.Atlas?.Reset(); } } private const string ZenMapGuid = "ZenDragon.ZenMap"; private static readonly string[] OverlayPanels = new string[5] { "IconPanel", "IconPanel2", "KeyHints", "SharedPanel", "PublicPanel" }; private static readonly int EpochKey = StringExtensionMethods.GetStableHashCode("Zen_EpochStart"); private static readonly FieldRef<Minimap, Vector3> Offset = AccessTools.FieldRefAccess<Minimap, Vector3>("m_mapOffset"); private static readonly FieldRef<Minimap, bool> Generated = AccessTools.FieldRefAccess<Minimap, bool>("m_hasGenerated"); private static readonly FieldRef<Minimap, Texture2D> Fog = AccessTools.FieldRefAccess<Minimap, Texture2D>("m_fogTexture"); private static readonly Action<Minimap, Vector3, float> Explore = AccessTools.MethodDelegate<Action<Minimap, Vector3, float>>(AccessTools.Method(typeof(Minimap), "Explore", new Type[2] { typeof(Vector3), typeof(float) }, (Type[])null), (object)null, true); private static readonly Action<Minimap, Vector3> Center = AccessTools.MethodDelegate<Action<Minimap, Vector3>>(AccessTools.Method(typeof(Minimap), "CenterMap", (Type[])null, (Type[])null), (object)null, true); [ThreadStatic] private static bool readingLocal; private readonly Func<MapTable, object> getLocation; private readonly Func<MapTable, double> ageSeconds; private readonly Func<bool> mapReady; private readonly Func<bool> uiOpen; private readonly Func<Player, bool> playerReady; private readonly Action closeLocation; private readonly FieldInfo locationPosition; private readonly FieldInfo locationRadius; private readonly Dictionary<GameObject, bool> hidden = new Dictionary<GameObject, bool>(); private Player? character; private long world; private AtlasData? data; private Vector3 focus; private float? zoom; private bool opened; private bool rendering; internal static bool IsOpen { get { if (Game.m_noMap) { return Plugin.Atlas?.opened ?? false; } return false; } } internal Atlas(Harmony harmony) { //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Expected O, but got Unknown //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Expected O, but got Unknown //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Expected O, but got Unknown //IL_01b9: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Expected O, but got Unknown Assembly assembly = ((object)Chainloader.PluginInfos["ZenDragon.ZenMap"].Instance).GetType().Assembly; Type type = assembly.GetType("ZenMap.Map", throwOnError: true); Type type2 = assembly.GetType("ZenMap.MapLocation", throwOnError: true); getLocation = AccessTools.MethodDelegate<Func<MapTable, object>>(AccessTools.Method(type, "GetLocation", (Type[])null, (Type[])null), (object)null, true); ageSeconds = AccessTools.MethodDelegate<Func<MapTable, double>>(AccessTools.Method(type, "TimeElapsedSeconds", (Type[])null, (Type[])null), (object)null, true); mapReady = AccessTools.MethodDelegate<Func<bool>>(AccessTools.PropertyGetter(type, "IsMapReady"), (object)null, true); uiOpen = AccessTools.MethodDelegate<Func<bool>>(AccessTools.PropertyGetter(AccessTools.TypeByName("Zen.Lib.UI"), "IsOpen"), (object)null, true); playerReady = AccessTools.MethodDelegate<Func<Player, bool>>(AccessTools.Method(AccessTools.TypeByName("Zen.Lib.PlayerExt"), "IsReady", (Type[])null, (Type[])null), (object)null, true); closeLocation = AccessTools.MethodDelegate<Action>(AccessTools.Method(type2, "Close", (Type[])null, (Type[])null), (object)null, true); locationPosition = AccessTools.Field(type2, "Position"); locationRadius = AccessTools.Field(type2, "Radius"); harmony.Patch((MethodBase)AccessTools.Method(type, "CheckForInput", (Type[])null, (Type[])null), new HarmonyMethod(typeof(Atlas), "MapInput", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(type, "MapTable_OnRead", (Type[])null, (Type[])null), new HarmonyMethod(typeof(Atlas), "ReadTable", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(type, "MapTable_Write", (Type[])null, (Type[])null), new HarmonyMethod(typeof(Atlas), "WriteTable", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(type2, "Show", (Type[])null, (Type[])null), new HarmonyMethod(typeof(Atlas), "ShowExistingMap", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } private AtlasData GetData(Player player) { long worldUID = ZNet.instance.GetWorldUID(); if (character != player || world != worldUID || data == null) { Reset(); character = player; world = worldUID; player.m_customData.TryGetValue(DataKey(), out var value); try { data = AtlasData.Read(value); } catch (JsonException) { Plugin.Log.LogWarning((object)"Could not read saved atlas; initializing an empty atlas."); data = new AtlasData(); } } return data; } private string DataKey() { return "valde.poe.tweaks.atlas.v1." + world.ToString(CultureInfo.InvariantCulture); } private bool TableReady(MapTable table) { ZNetView component = ((Component)table).GetComponent<ZNetView>(); if ((Object)(object)component == (Object)null || !component.IsValid() || (Object)(object)EnvMan.instance == (Object)null || (Object)(object)ZNet.instance == (Object)null) { return false; } double num = BitConverter.Int64BitsToDouble(component.GetZDO().GetLong(EpochKey, 0L)); if (num > 0.0 && !double.IsInfinity(num)) { return AtlasData.TableReady(ageSeconds(table), EnvMan.instance.m_dayLengthSec, Plugin.AtlasConfig.MinimumTableAgeDays.Value); } return false; } private static void NotReady(Player player) { ((Character)player).Message((MessageType)2, "Table is still charting. Return after " + Plugin.AtlasConfig.MinimumTableAgeDays.Value.ToString("0.##", CultureInfo.CurrentCulture) + " game days.", 0, (Sprite)null, false); } private void Open(Player player) { //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) Minimap instance = Minimap.instance; if (!Game.m_noMap || (Object)(object)instance == (Object)null || !Generated.Invoke(instance) || !mapReady()) { return; } AtlasData atlasData = GetData(player); closeLocation(); opened = true; bool noMap = Game.m_noMap; try { Game.m_noMap = false; instance.SetMapMode((MapMode)2); } finally { Game.m_noMap = noMap; } rendering = true; try { instance.Reset(); foreach (AtlasRegion region in atlasData.Regions) { Explore(instance, new Vector3(region.X, 0f, region.Z), region.Radius); } Fog.Invoke(instance).Apply(); } finally { rendering = false; } if (zoom.HasValue) { instance.LargeZoom = zoom.Value; } Offset.Invoke(instance) = focus - ((Component)player).transform.position; Center(instance, focus); HideOverlays(instance); } private void Hide(GameObject? value) { if (!((Object)(object)value == (Object)null)) { if (!hidden.ContainsKey(value)) { hidden.Add(value, value.activeSelf); } value.SetActive(false); } } private void HideOverlays(Minimap map) { Hide(map.m_smallRoot); Hide(((Component)map.m_pinRootLarge).gameObject); Hide(((Component)map.m_pinNameRootLarge).gameObject); Hide(((Component)map.m_largeMarker).gameObject); Hide(((Component)map.m_largeShipMarker).gameObject); Hide(((Component)map.m_biomeNameLarge).gameObject); string[] overlayPanels = OverlayPanels; foreach (string text in overlayPanels) { Transform obj = map.m_largeRoot.transform.Find(text); Hide((obj != null) ? ((Component)obj).gameObject : null); } foreach (GameObject hint in map.m_hints) { Hide(hint); } } private void Close() { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Invalid comparison between Unknown and I4 bool num = opened; opened = false; Minimap instance = Minimap.instance; if (num && (Object)(object)instance != (Object)null && (int)instance.m_mode == 2) { instance.SetMapMode((MapMode)0); } foreach (KeyValuePair<GameObject, bool> item in hidden) { if ((Object)(object)item.Key != (Object)null) { item.Key.SetActive(item.Value); } } hidden.Clear(); } private void Reset() { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) Close(); character = null; data = null; focus = Vector3.zero; zoom = null; } public void Dispose() { Reset(); } private static bool MapInput(ref bool __result) { if (!Game.m_noMap) { return true; } __result = false; Atlas atlas = Plugin.Atlas; Player localPlayer = Player.m_localPlayer; if (atlas == null || atlas.opened || (Object)(object)localPlayer == (Object)null || !atlas.playerReady(localPlayer) || atlas.uiOpen() || (!ZInput.GetButtonDown("Map") && !ZInput.GetButtonDown("JoyMap")) || ZInput.GetButton("JoyAltKeys")) { return false; } try { atlas.Open(localPlayer); __result = atlas.opened; } catch (Exception ex) { atlas.Close(); Plugin.Log.LogWarning((object)("Could not open atlas: " + ex.Message)); } return false; } private static bool ReadTable([HarmonyArgument(0)] MapTable table, [HarmonyArgument(1)] ref bool runNative, [HarmonyArgument(2)] ItemData? item) { //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_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: 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_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) if (!Game.m_noMap) { return true; } runNative = false; Atlas atlas = Plugin.Atlas; Player localPlayer = Player.m_localPlayer; if (!readingLocal || item != null || atlas == null || (Object)(object)localPlayer == (Object)null || !atlas.playerReady(localPlayer) || (Object)(object)Minimap.instance == (Object)null || !Generated.Invoke(Minimap.instance) || !atlas.mapReady()) { return false; } try { if (!atlas.TableReady(table)) { NotReady(localPlayer); return false; } object obj = atlas.getLocation(table); Vector3 val = (Vector3)atlas.locationPosition.GetValue(obj); float radius = (float)atlas.locationRadius.GetValue(obj); AtlasData atlasData = atlas.GetData(localPlayer); atlasData.Add(new AtlasRegion(val.x, val.z, radius)); localPlayer.m_customData[atlas.DataKey()] = atlasData.Serialize(); atlas.focus = new Vector3(val.x, 0f, val.z); atlas.Open(localPlayer); } catch (Exception ex) { atlas.Close(); Plugin.Log.LogWarning((object)("Could not copy table to atlas: " + ex.Message)); } return false; } private static bool WriteTable([HarmonyArgument(0)] MapTable table, [HarmonyArgument(1)] Humanoid user, [HarmonyArgument(3)] ref bool runNative, [HarmonyArgument(4)] ref bool result) { if (Game.m_noMap && (object)user == Player.m_localPlayer) { Atlas atlas = Plugin.Atlas; if (atlas != null && !atlas.TableReady(table)) { runNative = false; result = false; NotReady(Player.m_localPlayer); return false; } } return true; } private static void ShowExistingMap() { Plugin.Atlas?.Close(); } } internal sealed class AtlasConfig { internal ConfigEntry<float> MinimumTableAgeDays { get; } internal AtlasConfig(ConfigFile config) { AcceptableValueBase val = (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, float.MaxValue); MinimumTableAgeDays = ConfigFileExtensions.BindConfig<float>(config, "Atlas", "MinimumTableAgeDays", 1f, "Full in-game days since construction before a cartography table can reveal or copy its region. Zero disables the warmup. Afterward, ZenMap's existing age/radius curve applies.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); } } internal readonly struct AtlasRegion { public float X { get; } public float Z { get; } public float Radius { get; } [JsonConstructor] internal AtlasRegion(float x, float z, float radius) { X = x; Z = z; Radius = radius; } } internal sealed class AtlasData { private readonly List<AtlasRegion> regions = new List<AtlasRegion>(); internal IReadOnlyList<AtlasRegion> Regions => regions; internal void Add(AtlasRegion region) { if (!float.IsNaN(region.X) && !float.IsInfinity(region.X) && !float.IsNaN(region.Z) && !float.IsInfinity(region.Z) && !float.IsNaN(region.Radius) && !float.IsInfinity(region.Radius) && !(region.Radius <= 0f)) { int num = regions.FindIndex((AtlasRegion value) => value.X == region.X && value.Z == region.Z); if (num < 0) { regions.Add(region); } else if (region.Radius > regions[num].Radius) { regions[num] = region; } } } internal static AtlasData Read(string? serialized) { AtlasData atlasData = new AtlasData(); if (!string.IsNullOrEmpty(serialized)) { foreach (AtlasRegion item in JsonConvert.DeserializeObject<List<AtlasRegion>>(serialized) ?? new List<AtlasRegion>()) { atlasData.Add(item); } } return atlasData; } internal string Serialize() { return JsonConvert.SerializeObject((object)regions); } internal static bool TableReady(double ageSeconds, float dayLength, float minimumDays) { if (!double.IsNaN(ageSeconds) && !double.IsInfinity(ageSeconds) && ageSeconds >= 0.0 && !float.IsNaN(dayLength) && !float.IsInfinity(dayLength) && dayLength > 0f && !float.IsNaN(minimumDays) && !float.IsInfinity(minimumDays) && minimumDays >= 0f) { return ageSeconds >= (double)dayLength * (double)minimumDays; } return false; } } internal static class BloodMagicShield { private sealed class Caster { internal long Peer; internal long Player; } [HarmonyPatch(typeof(Game), "Start")] private static class RegisterExperience { private static void Postfix() { ZRoutedRpc.instance.Register<long, float, int, float>("valde.poe.tweaks.shield-experience", (Method<long, float, int, float>)ReceiveExperience); } } [HarmonyPatch(typeof(StatusEffect), "SetAttacker")] private static class RememberOriginalCaster { private static void Postfix(StatusEffect __instance, Character attacker) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown if (!IsBloodShield(__instance)) { return; } Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val != null && !((Object)(object)__instance.m_character == (Object)null) && __instance.m_character.IsOwner()) { Caster value = Casters.GetValue((SE_Shield)__instance, (SE_Shield _) => new Caster()); if (value.Player == 0L) { value.Peer = ((Character)val).GetOwner(); value.Player = val.GetPlayerID(); } } } } [HarmonyPatch(typeof(SE_Shield), "SetLevel")] private static class ReplenishOnRecast { private static void Prefix(SE_Shield __instance, out float __state) { __state = (IsBloodShield((StatusEffect)(object)__instance) ? Capacity.Invoke(__instance) : 0f); } private static void Postfix(SE_Shield __instance, float skillLevel, float __state) { if (!(__state <= 0f)) { Capacity.Invoke(__instance) = __state; BloodMagicShieldConfig bloodMagicShieldConfig = Plugin.BloodMagicShieldConfig; Damage.Invoke(__instance) = BloodMagicShieldRules.ReplenishedDamage(__state, Damage.Invoke(__instance), skillLevel, bloodMagicShieldConfig.ReplenishPercentAtZero.Value, bloodMagicShieldConfig.ReplenishPercentAtHundred.Value); } } } [HarmonyPatch(typeof(SE_Shield), "OnDamaged")] private static class AwardDepletion { private static void Prefix(SE_Shield __instance, HitData hit, Character attacker, out float __state) { __state = 0f; Character character = ((StatusEffect)__instance).m_character; if (IsBloodShield((StatusEffect)(object)__instance) && !((Object)(object)character == (Object)null) && character.IsOwner() && !((Object)(object)attacker == (Object)null) && !attacker.IsPlayer() && !attacker.IsTamed() && BaseAI.IsEnemy(attacker, character)) { __state = BloodMagicShieldRules.DepletedFraction(Capacity.Invoke(__instance), Damage.Invoke(__instance), hit.GetTotalDamage()); } } private static void Postfix(SE_Shield __instance, Character attacker, float __state) { if (!(__state <= 0f) && Casters.TryGetValue(__instance, out Caster value) && value.Player != 0L && value.Peer != 0L && ZRoutedRpc.instance != null) { BloodMagicShieldConfig bloodMagicShieldConfig = Plugin.BloodMagicShieldConfig; float num = __state * bloodMagicShieldConfig.XpPerFullShield.Value * (((StatusEffect)__instance).m_character.IsPlayer() ? 1f : bloodMagicShieldConfig.NonPlayerXpMultiplier.Value); if (!(num <= 0f)) { TargetExperience target = Plugin.ExperienceConfig.GetTarget(attacker); ZRoutedRpc.instance.InvokeRoutedRPC(value.Peer, "valde.poe.tweaks.shield-experience", new object[4] { value.Player, num, target.BiomeIndex, target.Multiplier }); } } } } [HarmonyPatch(typeof(SE_Shield), "IsDone")] private static class ReplaceBreakExperience { private static void Prefix(SE_Shield __instance, out SkillType __state) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Expected I4, but got Unknown //IL_0010: Unknown result type (might be due to invalid IL or missing references) __state = (SkillType)(int)__instance.m_levelUpSkillOnBreak; if ((int)__state == 10) { __instance.m_levelUpSkillOnBreak = (SkillType)0; } } private static void Finalizer(SE_Shield __instance, SkillType __state) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) __instance.m_levelUpSkillOnBreak = __state; } } [HarmonyPatch(typeof(StatusEffect), "GetIconText")] private static class RemainingShieldIcon { private static void Postfix(StatusEffect __instance, ref string __result) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Expected O, but got Unknown if (IsBloodShield(__instance)) { SE_Shield val = (SE_Shield)__instance; string text = BloodMagicShieldRules.RemainingPercent(Capacity.Invoke(val), Damage.Invoke(val)).ToString(CultureInfo.InvariantCulture) + "%"; __result = (string.IsNullOrEmpty(__result) ? text : (text + " · " + __result)); } } } [HarmonyPatch(typeof(Skill), "Raise")] private static class PreserveShieldExperienceOverflow { private static bool Prefix(Skill __instance, float factor, ref bool __result) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Invalid comparison between Unknown and I4 if (!awardingExperience || (int)__instance.m_info.m_skill != 10) { return true; } __result = BloodMagicShieldRules.RaiseWithOverflow(__instance, __instance.m_info.m_increseStep * factor * Game.m_skillGainRate, (float position) => (ExperienceScope.Current?.Scale(Player.m_localPlayer, 1f, position) ?? 1f) * SkillRecoveryPatches.MultiplierAt(Player.m_localPlayer, (SkillType)10, position)); return false; } } private const string ExperienceRpc = "valde.poe.tweaks.shield-experience"; private static readonly ConditionalWeakTable<SE_Shield, Caster> Casters = new ConditionalWeakTable<SE_Shield, Caster>(); private static readonly FieldRef<SE_Shield, float> Capacity = AccessTools.FieldRefAccess<SE_Shield, float>("m_totalAbsorbDamage"); private static readonly FieldRef<SE_Shield, float> Damage = AccessTools.FieldRefAccess<SE_Shield, float>("m_damage"); [ThreadStatic] private static bool awardingExperience; private static bool IsBloodShield(StatusEffect effect) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Invalid comparison between Unknown and I4 SE_Shield val = (SE_Shield)(object)((effect is SE_Shield) ? effect : null); if (val != null) { return (int)val.m_levelUpSkillOnBreak == 10; } return false; } private static void ReceiveExperience(long sender, long playerId, float baseExperience, int biomeIndex, float starMultiplier) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || ((Character)localPlayer).IsDead() || localPlayer.GetPlayerID() != playerId || !((Character)localPlayer).IsOwner() || baseExperience <= 0f || float.IsNaN(baseExperience) || float.IsInfinity(baseExperience) || starMultiplier < 0f || float.IsNaN(starMultiplier) || float.IsInfinity(starMultiplier) || biomeIndex < 0 || biomeIndex > 8) { return; } ExperienceScope experienceScope = ExperienceScope.Enter(localPlayer, 10); bool flag = awardingExperience; try { experienceScope.Observe(localPlayer, starMultiplier, biomeIndex); awardingExperience = true; ((Character)localPlayer).RaiseSkill((SkillType)10, baseExperience); } finally { awardingExperience = flag; experienceScope.Restore(); } } } internal sealed class BloodMagicShieldConfig { internal ConfigEntry<float> XpPerFullShield { get; } internal ConfigEntry<float> NonPlayerXpMultiplier { get; } internal ConfigEntry<float> ReplenishPercentAtZero { get; } internal ConfigEntry<float> ReplenishPercentAtHundred { get; } internal BloodMagicShieldConfig(ConfigFile config) { AcceptableValueBase val = (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10000f); XpPerFullShield = ConfigFileExtensions.BindConfig<float>(config, "Blood magic shield", "XpPerFullShield", 40f, "Base caster XP per full shield depleted by hostile creatures. Replaces shield-break XP; combat, biome, star, recovery and native XP modifiers still apply. Recasting itself grants no XP.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f); NonPlayerXpMultiplier = ConfigFileExtensions.BindConfig<float>(config, "Blood magic shield", "NonPlayerXpMultiplier", 0.2f, "Further shield-depletion XP multiplier when the protected character is not a player, including summons and tamed creatures. Player shields always use 1x. Other XP modifiers still apply.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f); ReplenishPercentAtZero = ConfigFileExtensions.BindConfig<float>(config, "Blood magic shield", "ReplenishPercentAtZero", 10f, "Percentage of existing maximum shield HP restored by recasting at Blood Magic skill 0. Refreshes duration and preserves the original caster's XP ownership.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f); ReplenishPercentAtHundred = ConfigFileExtensions.BindConfig<float>(config, "Blood magic shield", "ReplenishPercentAtHundred", 30f, "Percentage restored at Blood Magic skill 100. Interpolates linearly using the recasting player's native skill level (0 to 100); existing maximum capacity is preserved.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); } } internal static class BloodMagicShieldRules { internal static float DepletedFraction(float capacity, float damageBefore, float hitDamage) { if (!(capacity > 0f) || !(hitDamage > 0f)) { return 0f; } return Math.Min(hitDamage, Math.Max(0f, capacity - damageBefore)) / capacity; } internal static float ReplenishedDamage(float capacity, float damage, float skillLevel, float percentAtZero, float percentAtHundred) { float num = (percentAtZero + (percentAtHundred - percentAtZero) * Math.Max(0f, Math.Min(100f, skillLevel)) / 100f) / 100f; return Math.Max(0f, damage - capacity * num); } internal static int RemainingPercent(float capacity, float damage) { if (!(capacity <= 0f)) { return (int)Math.Ceiling(100.0 * Math.Max(0.0, Math.Min(1.0, 1.0 - (double)(damage / capacity)))); } return 0; } internal static bool RaiseWithOverflow(Skill skill, float experience, Func<float, float> multiplierAt) { if (skill.m_level >= 100f || experience <= 0f || float.IsNaN(experience) || float.IsInfinity(experience)) { return false; } float level = skill.m_level; double num = skill.m_accumulator; double num2 = 0.5 * Math.Pow(Math.Floor(skill.m_level + 1f), 1.5) + 0.5; float num3 = multiplierAt(skill.m_level + (float)(num / num2)); double num4 = (double)experience / (double)num3; while (skill.m_level < 100f) { num2 = 0.5 * Math.Pow(Math.Floor(skill.m_level + 1f), 1.5) + 0.5; float num5 = multiplierAt(skill.m_level + (float)(num / num2)); double num6 = (num2 - num) / (double)num5; if (num4 < num6) { num += num4 * (double)num5; break; } num4 -= num6; skill.m_level = Math.Min(100f, skill.m_level + 1f); num = 0.0; } skill.m_accumulator = ((skill.m_level >= 100f) ? 0f : ((float)num)); return skill.m_level > level; } } internal sealed class BossHealthConfig { internal ConfigEntry<float> BossHealthMultiplier { get; } internal BossHealthConfig(ConfigFile config) { AcceptableValueBase val = (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.01f, 100f); BossHealthMultiplier = ConfigFileExtensions.BindConfig<float>(config, "Bosses", "BossHealthMultiplier", 2f, "Health multiplier for newly spawned bosses from Mistlands onward. Earlier bosses use normal health; their saved old multipliers are removed while preserving health percentage. Eligible bosses retain their spawn multiplier after saves, reloads and later config changes. 1 uses normal health. Multiplayer scaling still applies.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); } } internal static class BossHealthPatches { [HarmonyPatch(typeof(Character), "Awake")] private static class RememberSpawnMultiplier { private static void Prefix(Character __instance, ref float ___m_lastHealth) { if (!__instance.IsBoss()) { return; } RemoveExcludedBonus(__instance, ref ___m_lastHealth); ZNetView val = (((Object)(object)__instance.m_nViewOverride != (Object)null) ? __instance.m_nViewOverride : ((Component)__instance).GetComponent<ZNetView>()); if (!((Object)(object)val == (Object)null) && val.IsValid() && val.IsOwner()) { ZDO zDO = val.GetZDO(); float num = default(float); if (!zDO.GetFloat(MultiplierKey, ref num)) { float num2 = ((!ReceivesBonus(__instance) || zDO.GetFloat(ZDOVars.s_maxHealth, ref num)) ? 1f : Plugin.BossHealthConfig.BossHealthMultiplier.Value); zDO.Set(MultiplierKey, (num2 > 0f && !float.IsInfinity(num2)) ? num2 : 1f); } } } } [HarmonyPatch(typeof(Character), "GetMaxHealth")] private static class NormalizeOwnedHealth { private static void Prefix(Character __instance, ref float ___m_lastHealth) { RemoveExcludedBonus(__instance, ref ___m_lastHealth); } } [HarmonyPatch(typeof(Character), "GetMaxHealthBase")] private static class ScaleBaseHealth { private static void Postfix(Character __instance, ref float __result, ref float ___m_lastHealth) { RemoveExcludedBonus(__instance, ref ___m_lastHealth); if (__instance.IsBoss() && ReceivesBonus(__instance)) { ZNetView val = (((Object)(object)__instance.m_nViewOverride != (Object)null) ? __instance.m_nViewOverride : ((Component)__instance).GetComponent<ZNetView>()); float num = default(float); if ((Object)(object)val != (Object)null && val.IsValid() && val.GetZDO().GetFloat(MultiplierKey, ref num) && num > 0f && !float.IsInfinity(num)) { __result *= num; } } } } private static readonly int MultiplierKey = StringExtensionMethods.GetStableHashCode("valde.poe.tweaks.boss-health-multiplier"); private static bool ReceivesBonus(Character character) { return BossHealthRules.ReceivesBonus(character.BossOrder(), Utils.GetPrefabName(((Component)character).gameObject)); } private static void RemoveExcludedBonus(Character character, ref float lastHealth) { if (!character.IsBoss() || ReceivesBonus(character)) { return; } ZNetView val = (((Object)(object)character.m_nViewOverride != (Object)null) ? character.m_nViewOverride : ((Component)character).GetComponent<ZNetView>()); if ((Object)(object)val == (Object)null || !val.IsValid() || !val.IsOwner()) { return; } ZDO zDO = val.GetZDO(); float num = default(float); if (!zDO.GetFloat(MultiplierKey, ref num) || num == 1f || !(num > 0f) || float.IsInfinity(num)) { return; } zDO.Set(MultiplierKey, 1f); float num2 = default(float); if (zDO.GetFloat(ZDOVars.s_maxHealth, ref num2)) { num2 /= num; zDO.Set(ZDOVars.s_maxHealth, num2); float num3 = default(float); if (zDO.GetFloat(ZDOVars.s_health, ref num3)) { num3 /= num; zDO.Set(ZDOVars.s_health, num3); lastHealth = num3; } else { lastHealth = num2; } } } } internal static class BossHealthRules { internal static bool ReceivesBonus(int bossOrder, string prefab) { bool flag = bossOrder >= 6; if (!flag) { bool flag2 = ((prefab == "FrozenKing" || prefab == "FrozenKing_p2") ? true : false); flag = flag2; } return flag; } } internal sealed class DeathAudio : IDisposable { [HarmonyPatch(typeof(Game), "Start")] private static class RegisterNotification { private static void Postfix() { try { ZRoutedRpc.instance.Register<Vector3>("valde.poe.tweaks.death-audio", (Action<long, Vector3>)Plugin.DeathAudio.ReceiveDeath); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not register death audio: " + ex.Message)); } } } [HarmonyPatch(typeof(Player), "OnDeath")] private static class AcceptedDeath { private static void Prefix(Player __instance, out Vector3? __state) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) __state = null; if (__instance == Player.m_localPlayer) { ZNetView component = ((Component)__instance).GetComponent<ZNetView>(); if ((Object)(object)component != (Object)null && component.IsValid() && component.IsOwner() && !((Character)__instance).IsDead()) { __state = ((Component)__instance).transform.position; } } } private static void Postfix(Player __instance, Vector3? __state) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) if (__state.HasValue && ((Character)__instance).IsDead()) { Plugin.DeathAudio.NotifyDeath(__state.Value); } } } private const string RpcName = "valde.poe.tweaks.death-audio"; private readonly Plugin plugin; private AudioClip? clip; private AudioSource? source; private UnityWebRequest? request; private Coroutine? loading; private bool disposed; internal DeathAudio(Plugin plugin) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Invalid comparison between Unknown and I4 this.plugin = plugin; try { if ((int)SystemInfo.graphicsDeviceType != 4) { loading = ((MonoBehaviour)plugin).StartCoroutine(LoadClip()); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not load death audio: " + ex.Message)); } } internal static bool ShouldPlay(DeathAudioAudience audience, bool isDyingPlayer, float squaredDistance, float radius) { if (!isDyingPlayer) { if (audience == DeathAudioAudience.NearbyPlayers) { return squaredDistance <= radius * radius; } return false; } return true; } private IEnumerator LoadClip() { string uriString = Path.Combine(Path.GetDirectoryName(((BaseUnityPlugin)plugin).Info.Location), "death.wav"); UnityWebRequest pending = UnityWebRequestMultimedia.GetAudioClip(new Uri(uriString), (AudioType)20); try { request = pending; yield return pending.SendWebRequest(); request = null; try { if ((int)pending.result != 1) { Plugin.Log.LogWarning((object)("Could not load bundled death.wav: " + pending.error)); } else if (!disposed) { clip = DownloadHandlerAudioClip.GetContent(pending); source = ((Component)plugin).gameObject.AddComponent<AudioSource>(); source.playOnAwake = false; source.spatialBlend = 0f; } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not decode death audio: " + ex.Message)); } } finally { ((IDisposable)pending)?.Dispose(); } } private void Play() { try { if (!disposed && !((Object)(object)clip == (Object)null) && !((Object)(object)source == (Object)null) && !((Object)(object)AudioMan.instance == (Object)null)) { source.outputAudioMixerGroup = AudioMan.instance.m_guiMixer; source.PlayOneShot(clip); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not play death audio: " + ex.Message)); } } private void NotifyDeath(Vector3 position) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) try { if (Plugin.DeathAudioConfig.Audience.Value == DeathAudioAudience.DyingPlayerOnly || ZRoutedRpc.instance == null) { Play(); return; } ZRoutedRpc.instance.InvokeRoutedRPC(0L, "valde.poe.tweaks.death-audio", new object[1] { position }); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not announce death audio: " + ex.Message)); } } private void ReceiveDeath(long sender, Vector3 position) { //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_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) try { Player localPlayer = Player.m_localPlayer; if (!disposed && !((Object)(object)ZNet.instance == (Object)null) && !ZNet.instance.IsDedicated() && !((Object)(object)localPlayer == (Object)null)) { DeathAudioAudience value = Plugin.DeathAudioConfig.Audience.Value; bool isDyingPlayer = sender == ZNet.GetUID(); Vector3 val = ((Component)localPlayer).transform.position - position; if (ShouldPlay(value, isDyingPlayer, ((Vector3)(ref val)).sqrMagnitude, Plugin.DeathAudioConfig.Radius.Value)) { Play(); } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not receive death audio: " + ex.Message)); } } public void Dispose() { disposed = true; if (loading != null) { ((MonoBehaviour)plugin).StopCoroutine(loading); } UnityWebRequest? obj = request; if (obj != null) { obj.Dispose(); } request = null; if ((Object)(object)source != (Object)null) { Object.Destroy((Object)(object)source); } if ((Object)(object)clip != (Object)null) { Object.Destroy((Object)(object)clip); } } } internal enum DeathAudioAudience { DyingPlayerOnly, NearbyPlayers } internal sealed class DeathAudioConfig { internal ConfigEntry<DeathAudioAudience> Audience { get; } internal ConfigEntry<float> Radius { get; } internal DeathAudioConfig(ConfigFile config) { Audience = ConfigFileExtensions.BindConfig<DeathAudioAudience>(config, "Death audio", "Audience", DeathAudioAudience.NearbyPlayers, "Play the bundled death sound only for the dying player, or also for nearby players.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); AcceptableValueBase val = (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, float.MaxValue); Radius = ConfigFileExtensions.BindConfig<float>(config, "Death audio", "Radius", 100f, "Distance in metres from the death position for NearbyPlayers. The dying player always hears the sound.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); } } internal sealed class ExperienceConfig { internal ConfigEntry<bool> Enabled { get; } internal ConfigEntry<float> CombatXpMultiplier { get; } internal ConfigEntry<float> NonCombatXpMultiplier { get; } internal ConfigEntry<float> DummyMultiplier { get; } internal ConfigEntry<float> BonusPerStar { get; } internal ExperienceConfig(ConfigFile config) { AcceptableValueRange<float> val = new AcceptableValueRange<float>(0f, float.MaxValue); Enabled = ConfigFileExtensions.BindConfig<bool>(config, "Experience", "Enabled", true, "Enable combat/noncombat XP multipliers and biome, dummy and star XP adjustments. Skill recovery remains separate.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); AcceptableValueBase val2 = (AcceptableValueBase)(object)val; CombatXpMultiplier = ConfigFileExtensions.BindConfig<float>(config, "Experience", "CombatXpMultiplier", 2f, "Base XP multiplier for combat weapons and blocking, multiplied by target and recovery bonuses.", true, (int?)null, val2, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val2 = (AcceptableValueBase)(object)val; NonCombatXpMultiplier = ConfigFileExtensions.BindConfig<float>(config, "Experience", "NonCombatXpMultiplier", 2f, "Base XP multiplier for all other skills, multiplied by the recovery bonus.", true, (int?)null, val2, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val2 = (AcceptableValueBase)(object)val; DummyMultiplier = ConfigFileExtensions.BindConfig<float>(config, "Experience", "DummyMultiplier", 0.2f, "XP multiplier for training dummies and archery targets, regardless of location. No star bonus.", true, (int?)null, val2, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val2 = (AcceptableValueBase)(object)val; BonusPerStar = ConfigFileExtensions.BindConfig<float>(config, "Experience", "BonusPerStar", 1f, "Additive XP bonus per enemy star: 0.5 gives 1x / 1.5x / 2x for zero / one / two stars.", true, (int?)null, val2, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); } internal float GetDummyMultiplier() { if (!Enabled.Value) { return 1f; } return DummyMultiplier.Value; } internal float GetSkillMultiplier(SkillType skill) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) if (Enabled.Value) { if (!ExperienceRules.IsCombatSkill(skill)) { return NonCombatXpMultiplier.Value; } return CombatXpMultiplier.Value; } return 1f; } internal TargetExperience GetTarget(Character target) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Invalid comparison between Unknown and I4 //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_0062: 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) if (!Enabled.Value) { return new TargetExperience(0, 1f); } if ((int)target.GetFaction() == 12) { return new TargetExperience(0, DummyMultiplier.Value); } if (target.IsPlayer() || target.IsTamed()) { return new TargetExperience(0, 1f); } Biome val = ExperienceRules.NativeBiome(Utils.GetPrefabName(((Component)target).gameObject)); return new TargetExperience(ExperienceRules.BiomeIndex(val), ((int)val == 0) ? 1f : ExperienceRules.Multiplier(1f, Math.Max(0, target.GetLevel() - 1), BonusPerStar.Value)); } } internal readonly struct TargetExperience { internal int BiomeIndex { get; } internal float Multiplier { get; } internal TargetExperience(int biomeIndex, float multiplier) { BiomeIndex = biomeIndex; Multiplier = multiplier; } } internal static class ExperiencePatches { [HarmonyPatch] private static class AttackScope { private static IEnumerable<MethodBase> TargetMethods() { return new MethodInfo[2] { AccessTools.Method(typeof(Attack), "DoMeleeAttack", (Type[])null, (Type[])null), AccessTools.Method(typeof(Attack), "DoAreaAttack", (Type[])null, (Type[])null) }; } private static void Prefix(Humanoid ___m_character, ItemData ___m_weapon, out ExperienceScope __state) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) __state = ExperienceScope.Enter(___m_character, (___m_weapon != null) ? ((int)___m_weapon.m_shared.m_skillType) : 0); } private static void Finalizer(ExperienceScope? __state) { __state?.Restore(); } } [HarmonyPatch(typeof(Projectile), "OnHit")] private static class ProjectileScope { private static void Prefix(Character ___m_owner, SkillType ___m_skill, out ExperienceScope __state) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Expected I4, but got Unknown __state = ExperienceScope.Enter(___m_owner, (int)___m_skill); } private static void Finalizer(ExperienceScope? __state) { __state?.Restore(); } } [HarmonyPatch(typeof(Aoe), "OnHit")] private static class AreaScope { private static void Prefix(Aoe __instance, Character ___m_owner, SkillType ___m_skill, out ExperienceScope? __state) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected I4, but got Unknown ExperienceScope? current = ExperienceScope.Current; __state = ((current != null && current.IsBatch(__instance)) ? null : ExperienceScope.Enter(___m_owner, (int)___m_skill)); } private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { List<CodeInstruction> list = instructions.ToList(); MethodInfo nativeAward = AccessTools.Method(typeof(Character), "RaiseSkill", (Type[])null, (Type[])null); if (list.Count((CodeInstruction val) => CodeInstructionExtensions.Calls(val, nativeAward)) != 1) { throw new InvalidOperationException("Unsupported Valheim Aoe.OnHit: expected exactly one Character.RaiseSkill call."); } foreach (CodeInstruction instruction in list) { if (CodeInstructionExtensions.Calls(instruction, nativeAward)) { yield return CodeInstructionExtensions.MoveLabelsFrom(new CodeInstruction(OpCodes.Ldarg_0, (object)null), instruction); instruction.opcode = OpCodes.Call; instruction.operand = AccessTools.Method(typeof(ExperiencePatches), "AwardAreaExperience", (Type[])null, (Type[])null); } yield return instruction; } } private static void Finalizer(ExperienceScope? __state) { __state?.Restore(); } } [HarmonyPatch(typeof(Aoe), "CheckHits")] private static class AreaBatchScope { private static void Prefix(Aoe __instance, Character ___m_owner, SkillType ___m_skill, out ExperienceScope __state) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Expected I4, but got Unknown __state = ExperienceScope.Enter(___m_owner, (int)___m_skill, __instance); } private static void Postfix(ExperienceScope? __state) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) DeferredExperience deferredExperience = __state?.TakeDeferred(); if (deferredExperience != null) { ((Character)deferredExperience.Actor).RaiseSkill((SkillType)deferredExperience.Skill, deferredExperience.Factor); } } private static void Finalizer(ExperienceScope? __state) { __state?.Restore(); } } [HarmonyPatch(typeof(Humanoid), "BlockAttack")] private static class BlockScope { private static void Prefix(Humanoid __instance, Character attacker, out ExperienceScope __state) { __state = ExperienceScope.Enter(__instance, 6); if ((Object)(object)attacker != (Object)null) { TargetExperience target = Plugin.ExperienceConfig.GetTarget(attacker); __state.Observe(__instance, target.Multiplier, target.BiomeIndex); } } private static void Finalizer(ExperienceScope? __state) { __state?.Restore(); } } [HarmonyPatch(typeof(ArcheryTarget), "OnProjectileHit")] private static class ArcheryTargetScope { private static void Prefix(Character owner, Projectile projectile, out ExperienceScope __state) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Expected I4, but got Unknown __state = ExperienceScope.Enter(owner, (int)projectile.m_skill); __state.Observe(owner, Plugin.ExperienceConfig.GetDummyMultiplier()); } private static void Finalizer(ExperienceScope? __state) { __state?.Restore(); } } [HarmonyPatch(typeof(Character), "Damage")] private static class ObserveHit { private static void Prefix(Character __instance, HitData hit, ZNetView ___m_nview) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0049: 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_0098: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) ExperienceScope current = ExperienceScope.Current; if (current == null || (Object)(object)___m_nview == (Object)null || !___m_nview.IsValid() || __instance.IsDead() || __instance.GetHealth() <= 0f || __instance.IsTeleporting() || __instance.InCutscene()) { return; } SkillType skill = (SkillType)(((int)hit.m_skill == 0) ? current.Skill : ((int)hit.m_skill)); Character attacker = hit.GetAttacker(); if (!current.Matches(attacker)) { return; } if ((Object)(object)attacker != (Object)null && !(attacker is Player) && attacker.IsTamed()) { Tameable component = ((Component)attacker).GetComponent<Tameable>(); if ((Object)(object)component != (Object)null) { skill = component.m_levelUpOwnerSkill; } } if (ExperienceRules.IsCombatSkill(skill)) { TargetExperience target = Plugin.ExperienceConfig.GetTarget(__instance); current.Observe(attacker, target.Multiplier, target.BiomeIndex); } } } [HarmonyPatch(typeof(Character), "RaiseSkill")] private static class ScaleSummonedExperience { private static void Prefix(Character __instance, Tameable ___m_tameable, ref float __1) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) ExperienceScope current = ExperienceScope.Current; if (current == null || !current.Matches(__instance) || !__instance.IsTamed()) { return; } Tameable val = (Tameable)(((Object)(object)___m_tameable != (Object)null) ? ((object)___m_tameable) : ((object)((Component)__instance).GetComponent<Tameable>())); if (!((Object)(object)val != (Object)null) || !ExperienceRules.IsCombatSkill(val.m_levelUpOwnerSkill)) { return; } MonsterAI component = ((Component)__instance).GetComponent<MonsterAI>(); GameObject val2 = (((Object)(object)component == (Object)null) ? null : component.GetFollowTarget()); if (!((Object)(object)val2 == (Object)null)) { Character component2 = val2.GetComponent<Character>(); if ((Object)(object)component2 != (Object)null) { __1 = current.Scale(__instance, __1, ExperienceRules.SkillPosition(component2.GetSkills(), val.m_levelUpOwnerSkill)); } } } } [HarmonyPatch(typeof(Player), "RaiseSkill")] private static class ScaleExperience { private static void Prefix(Player __instance, SkillType skill, ref float __1) { //IL_0000: 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 (ExperienceRules.IsCombatSkill(skill)) { ExperienceScope current = ExperienceScope.Current; if (current != null) { __1 = current.Scale(__instance, __1, ExperienceRules.SkillPosition(((Character)__instance).GetSkills(), skill)); } } } } private static void AwardAreaExperience(Character actor, SkillType skill, float factor, Aoe area) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Expected I4, but got Unknown //IL_001b: Unknown result type (might be due to invalid IL or missing references) ExperienceScope? current = ExperienceScope.Current; if (current == null || !current.TryDefer(actor, (int)skill, factor, area)) { actor.RaiseSkill(skill, factor); } } } internal static class ExperienceRules { internal static bool IsCombatSkill(SkillType skill) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Invalid comparison between Unknown and I4 //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Invalid comparison between Unknown and I4 if (skill - 1 <= 10 || (int)skill == 14) { return true; } return false; } internal static float Multiplier(float biomeMultiplier, int stars, float bonusPerStar) { return biomeMultiplier * (1f + (float)stars * bonusPerStar); } internal static int BiomeIndex(Biome biome) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Invalid comparison between Unknown and I4 //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Invalid comparison between Unknown and I4 //IL_0005: 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_001d: Expected I4, but got Unknown //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Invalid comparison between Unknown and I4 //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Invalid comparison between Unknown and I4 //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Invalid comparison between Unknown and I4 //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Invalid comparison between Unknown and I4 if ((int)biome <= 16) { switch (biome - 1) { default: if ((int)biome != 8) { if ((int)biome != 16) { break; } return 5; } return 2; case 0: return 1; case 1: return 3; case 3: return 4; case 2: break; } } else { if ((int)biome == 32) { return 7; } if ((int)biome == 64) { return 8; } if ((int)biome == 512) { return 6; } } return 0; } internal static float BiomeMultiplier(int biomeIndex, float skillPosition) { if (biomeIndex != 0) { return Math.Max(1f, 1f + (float)biomeIndex - 8f * skillPosition / 100f); } return 1f; } internal static float SkillPosition(Skills? skills, SkillType skillType) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) Skill val = ((skills != null) ? ((IEnumerable<Skill>)skills.GetSkillList()).FirstOrDefault((Func<Skill, bool>)((Skill value) => value.m_info.m_skill == skillType)) : null); if (val != null) { return Math.Min(100f, val.m_level + val.GetLevelPercentage()); } return 0f; } internal static Biome NativeBiome(string prefab) { //IL_0be3: Unknown result type (might be due to invalid IL or missing references) //IL_0be4: Unknown result type (might be due to invalid IL or missing references) //IL_0bb8: Unknown result type (might be due to invalid IL or missing references) //IL_0bc5: Unknown result type (might be due to invalid IL or missing references) //IL_0bc0: Unknown result type (might be due to invalid IL or missing references) //IL_0bbc: Unknown result type (might be due to invalid IL or missing references) //IL_0bb4: Unknown result type (might be due to invalid IL or missing references) //IL_0bd2: Unknown result type (might be due to invalid IL or missing references) //IL_0bcd: Unknown result type (might be due to invalid IL or missing references) //IL_0bd7: Unknown result type (might be due to invalid IL or missing references) //IL_0bdf: Unknown result type (might be due to invalid IL or missing references) switch (prefab) { case "Neck": case "Boar": case "Deer": case "Greyling": case "Eikthyr": case "Skeleton_Meadows": case "Skeleton_Meadows_noarcher": return (Biome)1; case "Skeleton": case "gd_king": case "Greydwarf_Shaman": case "TentaRoot": case "Greydwarf": case "Greydwarf_Elite": case "Skeleton_Poison": case "Skeleton_Hildir": case "Skeleton_Hildir_nochest": case "Troll": case "Ghost": case "Skeleton_NoArcher": return (Biome)8; case "Blob": case "Surtling": case "Bonemass": case "BlobElite": case "BogWitchKvastur": case "Skeleton_Swamps": case "Leech": case "Wraith": case "Draugr": case "Draugr_Ranged": case "Draugr_Elite": case "Abomination": case "Skeleton_Swamps_noarcher": return (Biome)2; case "Wolf": case "Fenring": case "Hatchling": case "Fenring_Cultist": case "Dragon": case "Bat": case "Ulv": case "StoneGolem": case "Skeleton_Mountains": case "Skeleton_Mountains_noarcher": case "Fenring_Cultist_Hildir": return (Biome)4; case "BlobTar": case "GoblinBruteBros": case "GoblinBruteBros_nochest": case "Goblin": case "GoblinShaman": case "Deathsquito": case "GoblinBrute": case "Lox": case "GoblinKing": case "GoblinBrute_Hildir": case "GoblinShaman_Hildir_nochest": case "GoblinShaman_Hildir": return (Biome)16; case "Tick": case "Hare": case "Dverger": case "Gjall": case "Seeker": case "DvergerMage": case "SeekerBrute": case "SeekerBrood": case "SeekerQueen": return (Biome)512; case "BlobLava": case "Asksvin": case "Volture": case "Charred_Twitcher": case "DvergerAshlands": case "Fader": case "Morgen": case "Charred_Melee": case "Charred_Mage": case "Morgen_NonSleeping": case "Charred_Archer": case "FallenValkyrie": case "BonemawSerpent": case "Charred_Melee_Dyrnwyn": return (Biome)32; case "Elaking": case "Writhan": case "DvergerDeepNorth": case "Greydwarf_Frozen": case "Greydwarf_Shaman_Frozen": case "Moose": case "Barka": case "JotunWarrior": case "ElakingMole": case "TrollFrost": case "JotunWitch": case "Skeleton_DeepNorth": case "ElakingLantern": case "JotunWarriorDualWield": return (Biome)64; case "Serpent": return (Biome)256; default: return (Biome)0; } } } internal sealed class ExperienceScope { [ThreadStatic] private static ExperienceScope? current; private readonly ExperienceScope? previous; private readonly object? actor; private readonly object? batch; private readonly Dictionary<int, float> multipliers = new Dictionary<int, float>(); private DeferredExperience? deferred; internal int Skill { get; } internal static ExperienceScope? Current => current; private ExperienceScope(object? actor, int skill, object? batch) { this.actor = actor; this.batch = batch; Skill = skill; previous = current; } internal static ExperienceScope Enter(object? actor, int skill, object? batch = null) { return current = new ExperienceScope(actor, skill, batch); } internal bool Matches(object? candidate) { return actor == candidate; } internal bool IsBatch(object candidate) { if (batch != null) { return batch == candidate; } return false; } internal bool TryDefer(object candidate, int skill, float factor, object sourceBatch) { if (!IsBatch(sourceBatch) || !Matches(candidate) || deferred != null) { return false; } deferred = new DeferredExperience(candidate, skill, factor); return true; } internal DeferredExperience? TakeDeferred() { DeferredExperience? result = deferred; deferred = null; return result; } internal void Observe(object? candidate, float targetMultiplier, int biomeIndex = 0) { if (Matches(candidate)) { multipliers[biomeIndex] = (multipliers.TryGetValue(biomeIndex, out var value) ? Math.Max(value, targetMultiplier) : targetMultiplier); } } internal float Scale(object? candidate, float factor, float skillPosition = 0f) { if (!Matches(candidate) || multipliers.Count <= 0) { return factor; } return factor * multipliers.Max((KeyValuePair<int, float> target) => target.Value * ExperienceRules.BiomeMultiplier(target.Key, skillPosition)); } internal void Restore() { current = previous; } } internal sealed class DeferredExperience { internal object Actor { get; } internal int Skill { get; } internal float Factor { get; } internal DeferredExperience(object actor, int skill, float factor) { Actor = actor; Skill = skill; Factor = factor; } } internal sealed class InventoryAudit { [HarmonyPatch(typeof(ZNet), "OnNewConnection")] private static class RegisterMessages { private static void Postfix(ZNetPeer peer) { try { Plugin.InventoryAudit.Register(peer); } catch (Exception exception) { Warn("Could not register inventory audit", exception); } } } [HarmonyPatch(typeof(PlayerProfile), "LoadPlayerData")] private static class CaptureJoin { [HarmonyPriority(0)] private static void Postfix(PlayerProfile __instance, Player player) { try { Plugin.InventoryAudit.Loaded(__instance, player); } catch (Exception exception) { Warn("Could not capture joining inventory", exception); } } } [HarmonyPatch(typeof(PlayerProfile), "SavePlayerData")] private static class CaptureSerializedInventory { private static void Postfix(PlayerProfile __instance, Player player) { try { Plugin.InventoryAudit.Saved(__instance, player); } catch (Exception exception) { Warn("Could not capture saved inventory", exception); } } } [HarmonyPatch(typeof(PlayerProfile), "Save")] private static class TrackSuccessfulSave { private static void Postfix(PlayerProfile __instance, bool __result) { Plugin.InventoryAudit.Persisted(__instance, __result); } } [HarmonyPatch(typeof(Game), "Shutdown")] private static class BeginLogoutSave { private static void Prefix(Game __instance) { if (!__instance.IsShuttingDown()) { Plugin.InventoryAudit.shutdownSaveSucceeded = false; } } } [HarmonyPatch(typeof(Game), "SavePlayerProfile")] private static class SendFinalInventory { private static void Postfix(Game __instance) { if (__instance.IsShuttingDown()) { Plugin.InventoryAudit.FinishLogout(); } } } [HarmonyPatch(typeof(ZNet), "Disconnect")] private static class DropSession { private static void Prefix(ZNetPeer peer) { if (peer != null) { Plugin.InventoryAudit.Forget(peer); } } } [HarmonyPatch(typeof(ZNet), "OnDestroy")] private static class ClearNetworkSession { private static void Postfix() { InventoryAudit inventoryAudit = Plugin.InventoryAudit; inventoryAudit.sessions.Clear(); inventoryAudit.clientRpc = null; inventoryAudit.profile = null; inventoryAudit.joinedInventory = (inventoryAudit.serializedInventory = (inventoryAudit.persistedInventory = null)); inventoryAudit.sentJoin = false; } } private const string ReportRpc = "valde.poe.tweaks.inventory-report"; private const string AckRpc = "valde.poe.tweaks.inventory-ack"; private const int MaximumItems = 4096; private readonly Dictionary<ZRpc, InventoryAuditSession> sessions = new Dictionary<ZRpc, InventoryAuditSession>(); private InventoryAuditStore? store; private ZRpc? clientRpc; private PlayerProfile? profile; private InventoryAuditSnapshot? joinedInventory; private InventoryAuditSnapshot? serializedInventory; private InventoryAuditSnapshot? persistedInventory; private bool sentJoin; private bool shutdownSaveSucceeded; private bool finishing; private int sequence; private int acknowledged; private float nextCheckpoint; private void Register(ZNetPeer peer) { peer.m_rpc.Register<ZPackage>("valde.poe.tweaks.inventory-report", (Action<ZRpc, ZPackage>)delegate(ZRpc rpc, ZPackage packet) { Receive(peer, packet); }); peer.m_rpc.Register<int>("valde.poe.tweaks.inventory-ack", (Action<ZRpc, int>)delegate(ZRpc rpc, int number) { if (rpc == clientRpc) { acknowledged = number; } }); } private static InventoryAuditSnapshot Capture(Player player) { return new InventoryAuditSnapshot(from item in ((Humanoid)player).GetInventory().GetAllItems() select new InventoryAuditItem(((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : item.m_shared.m_name, item.m_quality, item.m_stack), clean: false, DateTime.UtcNow); } private void Loaded(PlayerProfile loadedProfile, Player player) { ZNet instance = ZNet.instance; if (!((Object)(object)instance == (Object)null) && !instance.IsServer() && !((Object)(object)Game.instance == (Object)null) && player == Player.m_localPlayer) { ZRpc serverRPC = instance.GetServerRPC(); if (serverRPC != null && serverRPC != clientRpc) { InventoryAuditSnapshot inventoryAuditSnapshot = Capture(player); clientRpc = serverRPC; profile = loadedProfile; joinedInventory = inventoryAuditSnapshot; serializedInventory = joinedInventory; persistedInventory = null; sentJoin = false; shutdownSaveSucceeded = false; sequence = 0; acknowledged = 0; SendJoin(); } } } private void SendJoin() { if (!sentJoin && joinedInventory != null && Plugin.InventoryAuditConfig.Enabled.Value) { Send(InventoryAuditReportKind.Join, joinedInventory); sentJoin = true; nextCheckpoint = Time.realtimeSinceStartup + Plugin.InventoryAuditConfig.SnapshotIntervalSeconds.Value; } } internal void Tick() { if (!Plugin.InventoryAuditConfig.Enabled.Value) { sentJoin = false; } else { if (clientRpc == null || (Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer() || clientRpc != ZNet.instance.GetServerRPC() || finishing || (Object)(object)Game.instance == (Object)null || Game.instance.IsShuttingDown()) { return; } SendJoin(); if (!(Time.realtimeSinceStartup < nextCheckpoint)) { nextCheckpoint = Time.realtimeSinceStartup + Plugin.InventoryAuditConfig.SnapshotIntervalSeconds.Value; InventoryAuditSnapshot inventoryAuditSnapshot = (((Object)(object)Player.m_localPlayer != (Object)null) ? Capture(Player.m_localPlayer) : serializedInventory); if (inventoryAuditSnapshot != null) { Send(InventoryAuditReportKind.Checkpoint, inventoryAuditSnapshot); } } } } private int Send(InventoryAuditReportKind kind, InventoryAuditSnapshot snapshot) { int num = checked(++sequence); clientRpc.Invoke("valde.poe.tweaks.inventory-report", new object[1] { WriteReport(kind, num, profile.GetPlayerID(), profile.GetName(), snapshot) }); return num; } internal static ZPackage WriteReport(InventoryAuditReportKind kind, int number, long characterId, string name, InventoryAuditSnapshot snapshot) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write((int)kind); val.Write(number); val.Write(characterId); val.Write(name); val.Write(snapshot.Items.Count); foreach (InventoryAuditItem item in snapshot.Items) { val.Write(item.Item); val.Write(item.Quality); val.Write(item.Count); } return val; } internal static InventoryAuditSnapshot ReadReport(ZPackage packet, out InventoryAuditReportKind kind, out int number, out long characterId, out string name) { if (packet.Size() > 1048576) { throw new InvalidDataException("Inventory report exceeds size limit."); } kind = (InventoryAuditReportKind)packet.ReadInt(); number = packet.ReadInt(); characterId = packet.ReadLong(); name = packet.ReadString(); int num = packet.ReadInt(); if (kind < InventoryAuditReportKind.Join || kind > InventoryAuditReportKind.Logout || number <= 0 || characterId == 0L || name.Length > 256 || num < 0 || num > 4096) { throw new InvalidDataException("Invalid inventory report header."); } List<InventoryAuditItem> list = new List<InventoryAuditItem>(num); for (int i = 0; i < num; i++) { string text = packet.ReadString(); if (text.Length > 256) { throw new InvalidDataException("Inventory item identity exceeds size limit."); } list.Add(new InventoryAuditItem(text, packet.ReadInt(), packet.ReadInt())); } return new InventoryAuditSnapshot(list, kind == InventoryAuditReportKind.Logout, DateTime.UtcNow); } private void Receive(ZNetPeer peer, ZPackage packet) { try { ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null || !instance.IsServer() || !peer.IsReady() || !Plugin.InventoryAuditConfig.Enabled.Value) { return; } InventoryAuditReportKind kind; int number; long characterId; string name; InventoryAuditSnapshot inventoryAuditSnapshot = ReadReport(packet, out kind, out number, out characterId, out name); if (!sessions.TryGetValue(peer.m_rpc, out InventoryAuditSession value)) { if (kind != InventoryAuditReportKind.Join) { return; } if (store == null) { store = new InventoryAuditStore(Path.Combine(Utils.GetSaveDataPath((FileSource)2), "PoeTweaks", "inventory-audit")); } value = new InventoryAuditSession(store, instance.GetWorldUID(), peer.m_socket.GetHostName(), delegate(string message) { Plugin.Log.LogWarning((object)message); }); sessions.Add(peer.m_rpc, value); } if (value.Accept(kind, characterId, name, inventoryAuditSnapshot.Items) && kind == InventoryAuditReportKind.Logout) { peer.m_rpc.Invoke("valde.poe.tweaks.inventory-ack", new object[1] { number }); peer.m_socket.Flush(); } } catch (Exception exception) { Warn("Could not audit inventory report", exception); } } private void Saved(PlayerProfile savedProfile, Player player) { if (profile == savedProfile && player == Player.m_localPlayer) { serializedInventory = null; serializedInventory = Capture(player); } } private void Persisted(PlayerProfile savedProfile, bool success) { if (success && profile == savedProfile) { persistedInventory = serializedInventory; if ((Object)(object)Game.instance != (Object)null && Game.instance.IsShuttingDown()) { shutdownSaveSucceeded = persistedInventory != null; } } } private void FinishLogout() { //IL_0072: Unknown result type (might be due to invalid IL or missing references) if (finishing || !shutdownSaveSucceeded || persistedInventory == null || !sentJoin || !Plugin.InventoryAuditConfig.Enabled.Value || clientRpc == null) { return; } finishing = true; try { ZRpc val = clientRpc; InventoryAuditSnapshot inventoryAuditSnapshot = persistedInventory; int num = Send(InventoryAuditReportKind.Logout, inventoryAuditSnapshot); Stopwatch stopwatch = Stopwatch.StartNew(); while (stopwatch.Elapsed.TotalSeconds < (double)Plugin.InventoryAuditConfig.LogoutTimeoutSeconds.Value && val.GetSocket().IsConnected()) { val.GetSocket().Flush(); val.Update(0f); if (clientRpc != val || persistedInventory == null) { break; } if (inventoryAuditSnapshot != persistedInventory) { inventoryAuditSnapshot = persistedInventory; num = Send(InventoryAuditReportKind.Logout, inventoryAuditSnapshot); } else if (acknowledged == num) { return; } Thread.Sleep(10); } Plugin.Log.LogWarning((object)"Final inventory snapshot was not acknowledged before logout; the server may have only a potentially stale checkpoint."); } catch (Exception exception) { Warn("Could not confirm final inventory snapshot", exception); } finally { finishing = false; } } private void Forget(ZNetPeer peer) { sessions.Remove(peer.m_rpc); if (peer.m_rpc == clientRpc) { clientRpc = null; profile = null; joinedInventory = (serializedInventory = (persistedInventory = null)); sentJoin = false; } } private static void Warn(string message, Exception exception) { Plugin.Log.LogWarning((object)(message + ": " + exception.Message)); } } internal sealed class InventoryAuditConfig { internal ConfigEntry<bool> Enabled { get; } internal ConfigEntry<float> SnapshotIntervalSeconds { get; } internal ConfigEntry<float> LogoutTimeoutSeconds { get; } internal InventoryAuditConfig(ConfigFile config) { Enabled = ConfigFileExtensions.BindConfig<bool>(config, "Inventory audit", "Enabled", true, "Log inventory changes between sessions. Reports only; never refuses players or changes items.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); AcceptableValueBase val = (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 3600f); SnapshotIntervalSeconds = ConfigFileExtensions.BindConfig<float>(config, "Inventory audit", "SnapshotIntervalSeconds", 5f, "Checkpoint interval for crashes and connection loss. These comparisons are marked potentially stale.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f); LogoutTimeoutSeconds = ConfigFileExtensions.BindConfig<float>(config, "Inventory audit", "LogoutTimeoutSeconds", 2f, "Maximum seconds to wait for the server to persist the final saved inventory before leaving.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); } } internal readonly struct InventoryAuditItem { public string Item { get; } public int Quality { get; } public int Count { get; } [JsonConstructor] internal InventoryAuditItem(string item, int quality, int count) { Item = item; Quality = quality; Count = count; } } internal sealed class InventoryAuditSnapshot { public IReadOnlyList<InventoryAuditItem> Items { get; } public bool Clean { get; } public DateTime CapturedUtc { get; } [JsonConstructor] internal InventoryAuditSnapshot(IEnumerable<InventoryAuditItem> items, bool clean, DateTime capturedUtc) { if (items == null) { throw new ArgumentNullException("items"); } if (capturedUtc.Kind != DateTimeKind.Utc) { throw new ArgumentException("Inventory snapshot timestamp must be UTC.", "capturedUtc"); } List<InventoryAuditItem> source = items.ToList(); if (source.Any((InventoryAuditItem item) => string.IsNullOrWhiteSpace(item.Item) || item.Quality < 1 || item.Count < 1)) { throw new ArgumentException("Inventory items require an identity, positive quality and positive count.", "items"); } Items = (from @group in (from item in source group item by Tuple.Create(item.Item, item.Quality)).OrderBy<IGrouping<Tuple<string, int>, InventoryAuditItem>, string>((IGrouping<Tuple<string, int>, InventoryAuditItem> @group) => @group.Key.Item1, StringComparer.Ordinal).ThenBy((IGrouping<Tuple<string, int>, InventoryAuditItem> @group) => @group.Key.Item2) select new InventoryAuditItem(@group.Key.Item1, @group.Key.Item2, @group.Aggregate(0, (int total, InventoryAuditItem item) => checked(total + item.Count)))).ToList().AsReadOnly(); Clean = clean; CapturedUtc = capturedUtc; } } internal static class InventoryAuditData { internal static IReadOnlyList<InventoryAuditItem> Diff(InventoryAuditSnapshot previous, InventoryAuditSnapshot current) { Dictionary<Tuple<string, int>, int> before = previous.Items.ToDictionary((InventoryAuditItem item) => Tuple.Create(item.Item, item.Quality), (InventoryAuditItem item) => item.Count); Dictionary<Tuple<string, int>, int> after = current.Items.ToDictionary((InventoryAuditItem item) => Tuple.Create(item.Item, item.Quality), (InventoryAuditItem item) => item.Count); int value; int value2; return (from key in before.Keys.Concat(after.Keys).Distinct().OrderBy<Tuple<string, int>, string>((Tuple<string, int> key) => key.Item1, StringComparer.Ordinal) .ThenBy((Tuple<string, int> key) => key.Item2) select new InventoryAuditItem(key.Item1, key.Item2, (after.TryGetValue(key, out value) ? value : 0) - (before.TryGetValue(key, out value2) ? value2 : 0)) into item where item.Count != 0 select item).ToList().AsReadOnly(); } internal static string FormatDiff(IEnumerable<InventoryAuditItem> changes) { return string.Join(", ", changes.Select((InventoryAuditItem item) => item.Count.ToString("+0;-0", CultureInfo.InvariantCulture) + " " + JsonConvert.ToString(item.Item) + " (quality " + item.Quality.ToString(CultureInfo.InvariantCulture) + ")")); } } internal enum InventoryAuditReportKind { Join, Checkpoint, Logout } internal sealed class InventoryAuditSession { private readonly InventoryAuditStore store; private readonly long worldId; private readonly string platformId; private readonly Action<string> log; private bool joined; private bool finalized; private long characterId; internal InventoryAuditSession(InventoryAuditStore store, long worldId, string platformId, Action<string> log) { this.store = store; this.worldId = worldId; this.platformId = platformId; this.log = log; } internal bool Accept(InventoryAuditReportKind kind, long characterId, string name, IEnumerable<InventoryAuditItem> items) { if ((uint)kind > 2u) { return false; } bool num; if (kind != InventoryAuditReportKind.Join) { if (!joined || characterId != this.characterId) { goto IL_003d; } if (kind != InventoryAuditReportKind.Checkpoint) { goto IL_003f; } num = finalized; } else { num = joined; } if (num) { goto IL_003d; } goto IL_003f; IL_003d: return false; IL_003f: InventoryAuditSnapshot inventoryAuditSnapshot = new InventoryAuditSnapshot(items, kind == InventoryAuditReportKind.Logout, DateTime.UtcNow); if (kind == InventoryAuditReportKind.Join) { InventoryAuditSnapshot inventoryAuditSnapshot2 = store.Read(worldId, platformId, characterId); if (inventoryAuditSnapshot2 != null) { IReadOnlyList<InventoryAuditItem> readOnlyList = InventoryAuditData.Diff(inventoryAuditSnapshot2, inventoryAuditSnapshot); if (readOnlyList.Count != 0) { log("Inventory changed while away: character " + JsonConvert.ToString(name) + " (platform " + JsonConvert.ToString(platformId) + ", character ID " + characterId.ToString(CultureInfo.InvariantCulture) + "); previous snapshot " + (inventoryAuditSnapshot2.Clean ? "clean logout" : "potentially stale") + " at " + inventoryAuditSnapshot2.CapturedUtc.ToString("o", CultureInfo.InvariantCulture) + "; " + InventoryAuditData.FormatDiff(readOnlyList)); } } } store.Write(worldId, platformId, characterId, inventoryAuditSnapshot); joined = true; this.characterId = characterId; finalized = kind == InventoryAuditReportKind.Logout; return true; } } internal sealed class InventoryAuditStore { private readonly string directory; internal InventoryAuditStore(string directory) { this.directory = directory; } internal InventoryAuditSnapshot? Read(long worldId, string platformId, long characterId) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) string path = PathFor(worldId, platformId, characterId); try { return JsonConvert.DeserializeObject<InventoryAuditSnapshot>(File.ReadAllText(path)) ?? throw new JsonSerializationException("Inventory snapshot cannot be null."); } catch (FileNotFoundException) { return null; } catch (DirectoryNotFoundException) { return null; } } internal void Write(long worldId, string platformId, long characterId, InventoryAuditSnapshot snapshot) { string text = PathFor(worldId, platformId, characterId); Directory.CreateDirectory(directory); string text2 = text + "." + Guid.NewGuid().ToString("N") + ".tmp"; try { using (FileStream fileStream = new FileStream(text2, FileMode.CreateNew, FileAccess.Write, FileShare.None)) { byte[] bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject((object)snapshot)); fileStream.Write(bytes, 0, bytes.Length); fileStream.Flush(flushToDisk: true); } if (File.Exists(text)) { File.Replace(text2, text, null); } else { File.Move(text2, text); } } finally { if (File.Exists(text2)) { File.Delete(text2); } } } private string PathFor(long worldId, string platformId, long characterId) { if (string.IsNullOrWhiteSpace(platformId)) { throw new ArgumentException("Inventory snapshot requires a platform identity.", "platformId"); } string s = JsonConvert.SerializeObject((object)new string[3] { worldId.ToString(CultureInfo.InvariantCulture), platformId, characterId.ToString(CultureInfo.InvariantCulture) }); using SHA256 sHA = SHA256.Create(); string text = BitConverter.ToString(sHA.ComputeHash(Encoding.UTF8.GetBytes(s))).Replace("-", ""); return Path.Combine(directory, text + ".json"); } } internal sealed class NightSpawnConfig { internal ConfigEntry<bool> DisableBossNightInvaders { get; } internal NightSpawnConfig(ConfigFile config) { DisableBossNightInvaders = ConfigFileExtensions.BindConfig<bool>(config, "Spawns", "DisableBossNightInvaders", true, "Prevent the listed native boss-unlocked night invaders spawning outside their home biomes. Normal home-biome spawns, raids, fixed spawners and boss progression are preserved. Existing creatures are not removed.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); } } [HarmonyPatch(typeof(SpawnSystem), "UpdateSpawnList")] internal static class NightSpawnPatches { private static void Prefix(ref List<SpawnData> spawners, bool eventSpawners) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0066: 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_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Invalid comparison between Unknown and I4 if (eventSpawners || !Plugin.NightSpawnConfig.DisableBossNightInvaders.Value) { return; } List<SpawnData> list = null; for (int i = 0; i < spawners.Count; i++) { SpawnData val = spawners[i]; if (!val.m_enabled || (Object)(object)val.m_prefab == (Object)null) { continue; } Biome val2 = NightSpawnRules.RestrictBiomes(val.m_biome, Utils.GetPrefabName(val.m_prefab), val.m_requiredGlobalKey, val.m_spawnAtDay, val.m_spawnAtNight); if (val2 != val.m_biome) { if (list == null) { list = new List<SpawnData>(spawners); } SpawnData val3 = val.Clone(); val3.m_biome = val2; val3.m_enabled = (int)val2 > 0; list[i] = val3; } } if (list != null) { spawners = list; } } } internal static class NightSpawnRules { internal static Biome RestrictBiomes(Biome biomes, string prefab, string? requiredKey, bool spawnAtDay, bool spawnAtNight) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0164: 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_0166: 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) //IL_016e: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_016c: 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_014a: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_0160: 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_0153: Unknown result type (might be due to invalid IL or missing references) if (spawnAtDay || !spawnAtNight || requiredKey == null || !requiredKey.StartsWith("defeated_", StringComparison.Ordinal)) { return biomes; } Biome val; switch (prefab) { case "Greydwarf": case "Greydwarf_Elite": case "Greydwarf_Shaman": val = (Biome)8; break; case "Skeleton": val = (Biome)10; break; case "Draugr": val = (Biome)2; break; case "Goblin": val = (Biome)16; break; case "Seeker": case "SeekerBrood": case "Tick": val = (Biome)512; break; case "Charred_Melee": case "Charred_Archer": val = (Biome)32; break; default: val = (Biome)0; break; } Biome val2 = val; if ((int)val2 != 0) { return (Biome)(biomes & val2); } return biomes; } } [BepInPlugin("valde.poe.tweaks", "PoeTweaks", "0.6.14")] [BepInDependency("com.jotunn.jotunn", "2.30.2")] [BepInDependency("ZenDragon.ZenMap", "1.11.0")] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { internal const string Guid = "valde.poe.tweaks"; internal const string Version = "0.6.14"; private Harmony? harmony; internal static ExperienceConfig ExperienceConfig { get; private set; } internal static SkillRecoveryConfig SkillRecoveryConfig { get; private set; } internal static DeathAudioConfig DeathAudioConfig { get; private set; } internal static DeathAudio DeathAudio { get; private set; } internal static AtlasConfig AtlasConfig { get; private set; } internal static Atlas Atlas { get; private set; } internal static InventoryAuditConfig InventoryAuditConfig { get; private set; } internal static InventoryAudit InventoryAudit { get; private set; } internal static SanctuaryConfig SanctuaryConfig { get; private set; } internal static Sanctuary Sanctuary { get; private set; } internal static BossHealthConfig BossHealthConfig { get; private set; } internal static NightSpawnConfig NightSpawnConfig { get; private set; } internal static BloodMagicShieldConfig BloodMagicShieldConfig { get; private set; } internal static SailingConfig SailingConfig { get; private set; } internal static SailingMead SailingMead { get; private set; } internal static SailingMusic SailingMusic { get; private set; } internal static ManualLogSource Log { get; private set; } private void Awake() { //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Expected O, but got Unknown ExperienceConfig = new ExperienceConfig(((BaseUnityPlugin)this).Config); SkillRecoveryConfig = new SkillRecoveryConfig(((BaseUnityPlugin)this).Config); DeathAudioConfig = new DeathAudioConfig(((BaseUnityPlugin)this).Config); AtlasConfig = new AtlasConfig(((BaseUnityPlugin)this).Config); InventoryAuditConfig = new InventoryAuditConfig(((BaseUnityPlugin)this).Config); SanctuaryConfig = new SanctuaryConfig(((BaseUnityPlugin)this).Config); BossHealthConfig = new BossHealthConfig(((BaseUnityPlugin)this).Config); NightSpawnConfig = new NightSpawnConfig(((BaseUnityPlugin)this).Config); BloodMagicShieldConfig = new BloodMagicShieldConfig(((BaseUnityPlugin)this).Config); SailingConfig = new SailingConfig(((BaseUnityPlugin)this).Config); Log = ((BaseUnityPlugin)this).Logger; SailingMead = new SailingMead(); Sanctuary = new Sanctuary(((BaseUnityPlugin)this).Info.Location); InventoryAudit = new InventoryAudit(); DeathAudio = new DeathAudio(this); SailingMusic = new SailingMusic(this); harmony = new Harmony("valde.poe.tweaks"); harmony.PatchAll(typeof(Plugin).Assembly); Atlas = new Atlas(harmony); ((BaseUnityPlugin)this).Logger.LogInfo((object)"PoeTweaks loaded. Experience, skill recovery, blood magic shields, death audio, atlas, inventory audit, Sanctuary Stone, boss health, night spawn and sailing settings sync through Jotunn."); } private void Update() { try { InventoryAudit.Tick(); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not checkpoint inventory: " + ex.Message)); } } private void OnDestroy() { Harmony? obj = harmony; if (obj != null) { obj.UnpatchSelf(); } DeathAudio?.Dispose(); Atlas?.Dispose(); Sanctuary?.Dispose(); SailingMead?.Dispose(); SailingMusic?.Dispose(); } } internal sealed class SailingConfig { internal ConfigEntry<float> SpeedMultiplier { get; } internal ConfigEntry<float> AccelerationSeconds { get; } internal ConfigEntry<float> WaveHeight { get; } internal ConfigEntry<float> WaveDamageMultiplier { get; } internal ConfigEntry<bool> ShallowWaterFade { get; } internal SailingConfig(ConfigFile config) { AcceptableValueBase val = (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 20f); SpeedMultiplier = ConfigFileExtensions.BindConfig<float>(config, "Sailing", "SpeedMultiplier", 3f, "Fourth-throttle speed relative to this ship's normal ideal-wind full-sail speed. Longship and Drakkar only; the helmsman needs Storm mead. Works in every biome and ignores wind.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 120f); AccelerationSeconds = ConfigFileExtensions.BindConfig<float>(config, "Sailing", "AccelerationSeconds", 20f, "Seconds to accelerate from the speed at activation to fourth-throttle speed on unobstructed water. Collisions still slow the ship; propulsion recovers gradually.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, float.MaxValue); WaveHeight = ConfigFileExtensions.BindConfig<float>(config, "Sailing", "WaveHeight", 18f, "Height in metres of the wave carrying the ship in fourth gear, with no gameplay upper limit. Rises gradually, optionally fades in shallow water and settles over five seconds after disengaging. Zero disables the carrying wave.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f); WaveDamageMultiplier = ConfigFileExtensions.BindConfig<float>(config, "Sailing", "WaveDamageMultiplier", 0.1f, "Wave-impact damage multiplier while fourth gear is active. Does not change other damage sources or damage after disengaging.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); ShallowWaterFade = ConfigFileExtensions.BindConfig<bool>(config, "Sailing", "ShallowWaterFade", true, "Fade the custom carrying wave in shallow water. Disable to keep its full height near shore. Does not change native ocean waves.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); } } internal sealed class SailingHud : MonoBehaviour { [HarmonyPatch(typeof(Hud), "UpdateShipHud")] private static class FourthThrottle { private static void Postfix(Hud __instance, Player player) { Ship controlledShip = player.GetControlledShip(); bool flag = __instance.IsVisible() && (Object)(object)controlledShip != (Object)null && (((Component)controlledShip).GetComponent<StormSailing>()?.Active ?? false); SailingHud sailingHud = ((Component)__instance).GetComponent<SailingHud>(); if (flag && (Object)(object)sailingHud == (Object)null) { sailingHud = ((Component)__instance).gameObject.AddComponent<SailingHud>(); } if ((Object)(object)sailingHud != (Object)null) { sailingHud.fourth.SetActive(flag); } } } private GameObject fourth; private void Awake() { //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0078: 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_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) Image[] array = (from image in ((Component)this).GetComponent<Hud>().m_rudderFastForward.GetComponentsInChildren<Image>(true) orderby ((Graphic)image).rectTransform.anchoredPosition.y select image).ToArray(); Image val = array[^1]; Image val2 = array[^2]; Image val3 = Object.Instantiate<Image>(val, ((Component)val).transform.parent); ((Object)val3).name = "Poe fourth throttle"; RectTransform rectTransform = ((Graphic)val3).rectTransform; rectTransform.anchoredPosition += ((Graphic)val).rectTransform.anchoredPosition - ((Graphic)val2).rectTransform.anchoredPosition; fourth = ((Component)val3).gameObject; fourth.SetActive(false); } private void OnDestroy() { if ((Object)(object)fourth != (Object)null) { Object.Destroy((Object)(object)fourth); } } } internal sealed class SailingMead : IDisposable { internal const string ItemName = "Poe_StormMead"; internal const string BaseName = "Poe_StormMeadBase"; internal const string EffectName = "Poe_StormSailing"; internal const float DurationSeconds = 600f; internal static readonly int ExpiresKey = StringExtensionMethods.GetStableHashCode("valde.poe.tweaks.sailing-mead-expires"); private bool registered; internal SailingMead() { PrefabManager.OnVanillaPrefabsAvailable += Register; } private void Register() { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: 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_0034: Expected O, but got Unknown //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Expected O,