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 Magpie v0.1.14
plugins/Magpie/Magpie.Core.dll
Decompiled 21 hours agousing System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using ExtraPotions.Magpie.Config; using ExtraPotions.Magpie.Items; using ExtraPotions.Magpie.Loot; using ExtraPotions.Magpie.StepAside; using Microsoft.CodeAnalysis; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("ExtraPotions")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright (C) 2026 expDARE (ExtraPotions Meadworks)")] [assembly: AssemblyFileVersion("0.1.13.0")] [assembly: AssemblyInformationalVersion("0.1.13+254080b9351ec46e3f31fa4a55580d9d8cf592f1")] [assembly: AssemblyProduct("Magpie.Core")] [assembly: AssemblyTitle("Magpie.Core")] [assembly: AssemblyVersion("0.1.13.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ExtraPotions.Magpie { public static class MagpieCore { public const string ProductId = "com.meadworks.magpie"; public const string LootMarkKey = "magpie.loot"; public const string CheatedMarkKey = "magpie.cheated"; public const string InstanceTokenKey = "magpie.id"; public const string MergeInKey = "magpie.merge_in"; public const string MergeOutKey = "magpie.merge_out"; } } namespace ExtraPotions.Magpie.Text { public static class MagpieTextKeys { public const string Namespace = "magpie"; public const string LootMultiplier = "loot_multiplier"; public const string ModulePickup = "module_pickup"; public const string ModuleMerge = "module_merge"; public const string ModuleFloat = "module_float"; public const string ModuleLoot = "module_loot"; public const string StepAsidePickup = "step_aside_pickup"; public const string StepAsideMerge = "step_aside_merge"; public const string StepAsideFloat = "step_aside_float"; public const string StepAsideLoot = "step_aside_loot"; public const string ImportDone = "import_done"; public const string ImportSyncIncomplete = "import_sync_incomplete"; public static IReadOnlyList<string> All { get; } = new string[11] { "loot_multiplier", "module_pickup", "module_merge", "module_float", "module_loot", "step_aside_pickup", "step_aside_merge", "step_aside_float", "step_aside_loot", "import_done", "import_sync_incomplete" }; public static string Token(string key) { return "$meadworks_magpie_" + key; } } } namespace ExtraPotions.Magpie.StepAside { [Flags] public enum MagpieModule { None = 0, Pickup = 1, Merge = 2, Float = 4, Loot = 8, All = 0xF } public readonly struct ModuleSource { public string Guid { get; } public string Name { get; } public ModuleSource(string guid, string name) { Guid = guid; Name = name; } } public static class ModuleStepAside { public const string SwiftPickupGuid = "user.swiftpickup"; public const string FloatingItemsGuid = "com.ab5olutezer0.valheim.floatingitems"; public const string LegacyFloatingItemsGuid = "com.michal.valheim.floatingitems"; public const string SwiftPickupConfigFile = "user.swiftpickup.cfg"; private static readonly ModuleSource Swift = new ModuleSource("user.swiftpickup", "SwiftPickup"); private static readonly ModuleSource Floating = new ModuleSource("com.ab5olutezer0.valheim.floatingitems", "FloatingItems"); private static readonly ModuleSource FloatingLegacy = new ModuleSource("com.michal.valheim.floatingitems", "FloatingItems"); public static readonly MagpieModule[] Modules = new MagpieModule[4] { MagpieModule.Pickup, MagpieModule.Merge, MagpieModule.Float, MagpieModule.Loot }; public static string ModuleId(MagpieModule module) { return module switch { MagpieModule.Pickup => "pickup", MagpieModule.Merge => "merge", MagpieModule.Float => "float", MagpieModule.Loot => "loot", _ => throw new ArgumentException("Expected exactly one Magpie module.", "module"), }; } public static string TextKey(MagpieModule module) { return module switch { MagpieModule.Pickup => "step_aside_pickup", MagpieModule.Merge => "step_aside_merge", MagpieModule.Float => "step_aside_float", MagpieModule.Loot => "step_aside_loot", _ => throw new ArgumentException("Expected exactly one Magpie module.", "module"), }; } public static ModuleSource? InstalledSource(MagpieModule module, IEnumerable<string> installed, bool? mergeNearbyDrops) { HashSet<string> hashSet = new HashSet<string>(installed ?? Enumerable.Empty<string>(), StringComparer.Ordinal); foreach (ModuleSource item in Sources(module, mergeNearbyDrops)) { if (hashSet.Contains(item.Guid)) { return item; } } return null; } public static IReadOnlyList<ModuleSource> Sources(MagpieModule module, bool? mergeNearbyDrops) { switch (module) { case MagpieModule.Pickup: case MagpieModule.Loot: return new ModuleSource[1] { Swift }; case MagpieModule.Merge: if (mergeNearbyDrops == false) { return Array.Empty<ModuleSource>(); } return new ModuleSource[1] { Swift }; case MagpieModule.Float: return new ModuleSource[2] { Floating, FloatingLegacy }; default: return Array.Empty<ModuleSource>(); } } public static MagpieModule Yielding(IEnumerable<string> installed, bool? mergeNearbyDrops) { HashSet<string> set = new HashSet<string>(installed ?? Enumerable.Empty<string>(), StringComparer.Ordinal); MagpieModule magpieModule = MagpieModule.None; MagpieModule[] modules = Modules; foreach (MagpieModule magpieModule2 in modules) { if (Sources(magpieModule2, mergeNearbyDrops).Any((ModuleSource s) => set.Contains(s.Guid))) { magpieModule |= magpieModule2; } } return magpieModule; } public static bool? ReadMergeNearbyDrops(string? configText) { if (configText == null) { return null; } string a = ""; string[] array = configText.Split('\n'); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length == 0 || text[0] == '#') { continue; } if (text[0] == '[' && text[text.Length - 1] == ']') { a = text.Substring(1, text.Length - 2).Trim(); continue; } int num = text.IndexOf('='); if (num > 0 && string.Equals(a, "Merge", StringComparison.OrdinalIgnoreCase) && string.Equals(text.Substring(0, num).Trim(), "MergeNearbyDrops", StringComparison.OrdinalIgnoreCase)) { if (!bool.TryParse(text.Substring(num + 1).Trim(), out var result)) { return null; } return result; } } return null; } } } namespace ExtraPotions.Magpie.Pickup { public enum MagnetAction { Outside, Pull, Take } public readonly struct MagnetStep { public MagnetAction Action { get; } public float Move { get; } public MagnetStep(MagnetAction action, float move) { Action = action; Move = move; } } public static class Magnet { public const float TakeDistance = 0.3f; public static MagnetStep Step(float distance, float radius, float speed, float deltaTime) { if (float.IsNaN(distance) || float.IsInfinity(distance) || distance > radius) { return new MagnetStep(MagnetAction.Outside, 0f); } if (distance < 0.3f) { return new MagnetStep(MagnetAction.Take, 0f); } float val = Math.Max(0f, speed) * Math.Max(0f, deltaTime); return new MagnetStep(MagnetAction.Pull, Math.Min(val, distance)); } } public sealed class PickupLine<T> { public string Key { get; } public int Amount { get; internal set; } public T Payload { get; } public double Opened { get; } public PickupLine(string key, int amount, T payload, double opened) { Key = key; Amount = amount; Payload = payload; Opened = opened; } } public sealed class PickupMessageGroup<T> { public const double Window = 1.0; private readonly List<PickupLine<T>> open = new List<PickupLine<T>>(); private readonly List<bool> immediate = new List<bool>(); public int Pending => open.Count; public void Add(string key, int amount, double now, T payload, bool grouping) { if (amount <= 0) { return; } if (grouping) { for (int i = 0; i < open.Count; i++) { if (!immediate[i] && open[i].Key == key) { open[i].Amount += amount; return; } } } open.Add(new PickupLine<T>(key, amount, payload, now)); immediate.Add(!grouping); } public IReadOnlyList<PickupLine<T>> Flush(double now) { List<PickupLine<T>> list = new List<PickupLine<T>>(); int num = 0; while (num < open.Count) { if (immediate[num] || now - open[num].Opened >= 1.0) { list.Add(open[num]); open.RemoveAt(num); immediate.RemoveAt(num); } else { num++; } } return list; } public void Clear() { open.Clear(); immediate.Clear(); } } public readonly struct PickupFacts { public bool AutoPickup { get; } public bool IsPiece { get; } public bool Valid { get; } public bool HaveUnique { get; } public bool Ignored { get; } public bool Owned { get; } public bool InTar { get; } public bool Fits { get; } public PickupFacts(bool autoPickup, bool isPiece, bool valid, bool haveUnique, bool ignored, bool owned, bool inTar, bool fits) { AutoPickup = autoPickup; IsPiece = isPiece; Valid = valid; HaveUnique = haveUnique; Ignored = ignored; Owned = owned; InTar = inTar; Fits = fits; } } public enum PickupAction { Skip, RequestOwnership, Hold, Pull } public static class PickupRules { public static PickupAction Decide(PickupFacts item) { if (!item.AutoPickup || item.IsPiece || !item.Valid || item.HaveUnique || item.Ignored) { return PickupAction.Skip; } if (!item.Owned) { return PickupAction.RequestOwnership; } if (item.InTar || !item.Fits) { return PickupAction.Hold; } return PickupAction.Pull; } public static bool CanTakeNow(bool valid, bool owned) { return valid && owned; } } public sealed class PickupScheduler { private double nextScan = double.NegativeInfinity; private int remaining; private bool unlimited; private int lastTakeFrame = int.MinValue; public bool ScanDue(double now, float interval) { if (now + 1E-06 < nextScan) { return false; } nextScan = now + Math.Max(0.0, interval); return true; } public void BeginScan(int budget) { unlimited = budget <= 0; remaining = Math.Max(0, budget); } public bool TryTake(int frame) { if (frame == lastTakeFrame || (!unlimited && remaining <= 0)) { return false; } lastTakeFrame = frame; if (!unlimited) { remaining--; } return true; } public void Reset() { nextScan = double.NegativeInfinity; remaining = 0; unlimited = false; lastTakeFrame = int.MinValue; } } } namespace ExtraPotions.Magpie.Merging { public readonly struct Point3 { public float X { get; } public float Y { get; } public float Z { get; } public Point3(float x, float y, float z) { X = x; Y = y; Z = z; } public float DistanceSquared(Point3 other) { float num = X - other.X; float num2 = Y - other.Y; float num3 = Z - other.Z; return num * num + num2 * num2 + num3 * num3; } } public sealed class GroundItem { public long Id { get; } public ItemIdentity Identity { get; } public int Stack { get; } public int MaxStack { get; } public Point3 Position { get; } public long Created { get; } public double SpawnTime { get; } public bool OwnedLocally { get; } public bool Valid { get; } public bool Moving { get; } public double AgeSeconds { get; } public bool Pulled { get; } public bool PendingIntent { get; } public string? Token { get; } public GroundItem(long id, ItemIdentity identity, int stack, int maxStack, Point3 position, long created, double spawnTime, bool ownedLocally, bool valid, bool moving, double ageSeconds, bool pulled, bool pendingIntent, string? token) { Id = id; Identity = identity; Stack = stack; MaxStack = maxStack; Position = position; Created = created; SpawnTime = spawnTime; OwnedLocally = ownedLocally; Valid = valid; Moving = moving; AgeSeconds = ageSeconds; Pulled = pulled; PendingIntent = pendingIntent; Token = token; } } public enum BarrierState { Pass, Unavailable } public enum MergeRole { None, SinglePlayer, Server, RemoteClient } public static class MergeBarrier { public static BarrierState Decide(MergeRole role) { if (role != MergeRole.SinglePlayer && role != MergeRole.Server) { return BarrierState.Unavailable; } return BarrierState.Pass; } public static bool MayMerge(MergeRole role) { return Decide(role) == BarrierState.Pass; } } public sealed class MergeIntent { public const string Version = "v1"; public const string VersionWithFlag = "v2"; public const int MaxSourceNonces = 8; public string Nonce { get; } public string SourceToken { get; } public int Moved { get; } public double WorldTime { get; } public bool TracksPhase2 { get; } public bool Phase2Saved { get; } public int? PreMergeStack { get; } public bool? DestroysSource { get; } public MergeIntent(string nonce, string sourceToken, int moved, double worldTime) : this(nonce, sourceToken, moved, worldTime, tracksPhase2: true, phase2Saved: false) { } private MergeIntent(string nonce, string sourceToken, int moved, double worldTime, bool tracksPhase2, bool phase2Saved, int? preMergeStack = null, bool? destroysSource = null) { if (!ValidField(nonce)) { throw new ArgumentException("Nonce must be non-empty and contain no ';'.", "nonce"); } if (!ValidField(sourceToken)) { throw new ArgumentException("Source token must be non-empty and contain no ';'.", "sourceToken"); } if (moved <= 0) { throw new ArgumentException("Moved amount must be above zero.", "moved"); } if (double.IsNaN(worldTime) || double.IsInfinity(worldTime)) { throw new ArgumentException("World time must be finite.", "worldTime"); } if (preMergeStack.HasValue && preMergeStack.Value < 1) { throw new ArgumentException("Pre-merge stack must be at least 1.", "preMergeStack"); } if (!preMergeStack.HasValue && destroysSource.HasValue) { throw new ArgumentException("A removal bit requires a pre-merge stack.", "destroysSource"); } Nonce = nonce; SourceToken = sourceToken; Moved = moved; WorldTime = worldTime; TracksPhase2 = tracksPhase2; Phase2Saved = phase2Saved; PreMergeStack = preMergeStack; DestroysSource = destroysSource; } public MergeIntent WithPhase2Saved() { return new MergeIntent(Nonce, SourceToken, Moved, WorldTime, tracksPhase2: true, phase2Saved: true, PreMergeStack, DestroysSource); } public MergeIntent WithBaseline(int preMergeStack, bool destroysSource) { return new MergeIntent(Nonce, SourceToken, Moved, WorldTime, tracksPhase2: true, Phase2Saved, preMergeStack, destroysSource); } public string Format() { string text = string.Join(";", TracksPhase2 ? "v2" : "v1", Nonce, SourceToken, Moved.ToString(CultureInfo.InvariantCulture), WorldTime.ToString("R", CultureInfo.InvariantCulture)); if (!TracksPhase2) { return text; } text = text + ";" + (Phase2Saved ? "1" : "0"); if (!PreMergeStack.HasValue) { return text; } return text + ";" + PreMergeStack.Value.ToString(CultureInfo.InvariantCulture) + ";" + ((DestroysSource == true) ? "d" : "s"); } public static MergeIntent? Parse(string? text) { if (string.IsNullOrEmpty(text)) { return null; } string[] array = text.Split(';'); if (array.Length == 5 && array[0] == "v1") { return From(array, tracksPhase2: false, phase2Saved: false, null, null); } if (array.Length == 6 && array[0] == "v2" && Flag(array[5])) { return From(array, tracksPhase2: true, array[5] == "1", null, null); } if (array.Length == 8 && array[0] == "v2" && Flag(array[5]) && (array[7] == "d" || array[7] == "s") && int.TryParse(array[6], NumberStyles.None, CultureInfo.InvariantCulture, out var result) && result > 0) { return From(array, tracksPhase2: true, array[5] == "1", result, array[7] == "d"); } return null; } private static bool Flag(string text) { if (!(text == "0")) { return text == "1"; } return true; } public static string AppendNonce(string? record, string nonce) { if (!ValidField(nonce) || nonce.IndexOf(',') >= 0) { throw new ArgumentException("Nonce must be non-empty and contain no ';' or ','.", "nonce"); } List<string> list = new List<string>(Split(record)); if (list.Contains(nonce)) { return string.Join(",", list); } list.Add(nonce); if (list.Count > 8) { list.RemoveRange(0, list.Count - 8); } return string.Join(",", list); } public static bool HasNonce(string? record, string nonce) { return Array.IndexOf(Split(record), nonce) >= 0; } private static MergeIntent? From(string[] parts, bool tracksPhase2, bool phase2Saved, int? preMergeStack, bool? destroysSource) { if (!ValidField(parts[1]) || !ValidField(parts[2])) { return null; } if (!int.TryParse(parts[3], NumberStyles.None, CultureInfo.InvariantCulture, out var result) || result <= 0) { return null; } if (!double.TryParse(parts[4], NumberStyles.Float, CultureInfo.InvariantCulture, out var result2) || double.IsNaN(result2) || double.IsInfinity(result2)) { return null; } return new MergeIntent(parts[1], parts[2], result, result2, tracksPhase2, phase2Saved, preMergeStack, destroysSource); } private static string[] Split(string? record) { if (!string.IsNullOrEmpty(record)) { return record.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries); } return Array.Empty<string>(); } private static bool ValidField(string? value) { if (!string.IsNullOrEmpty(value)) { return value.IndexOf(';') < 0; } return false; } } public enum MergeStep { EnsureSourceToken, WriteTarget, AwaitBarrier, DestroySource, ReduceSource, ClearIntent } public sealed class MergePlan { private readonly MergeStep[] steps; public GroundItem Target { get; } public GroundItem Source { get; } public int Moved { get; } public string Nonce { get; } public double WorldTime { get; } public int TargetStackAfter => Target.Stack + Moved; public int SourceStackAfter => Source.Stack - Moved; public bool DestroysSource => SourceStackAfter == 0; public double TargetSpawnTimeAfter => Math.Max(Target.SpawnTime, Source.SpawnTime); public bool NeedsSourceToken => string.IsNullOrEmpty(Source.Token); public IReadOnlyList<MergeStep> Steps => steps; internal MergePlan(GroundItem target, GroundItem source, int moved, string nonce, double worldTime) { Target = target; Source = source; Moved = moved; Nonce = nonce; WorldTime = worldTime; List<MergeStep> list = new List<MergeStep>(); if (NeedsSourceToken) { list.Add(MergeStep.EnsureSourceToken); } list.Add(MergeStep.WriteTarget); list.Add(MergeStep.AwaitBarrier); list.Add(DestroysSource ? MergeStep.DestroySource : MergeStep.ReduceSource); list.Add(MergeStep.ClearIntent); steps = list.ToArray(); } public MergeIntent Intent(string sourceToken) { return new MergeIntent(Nonce, sourceToken, Moved, WorldTime).WithBaseline(Target.Stack, DestroysSource); } } public static class MergePlanner { private sealed class SpatialGroup { private readonly double cellSize; private readonly List<(GroundItem Item, int Order)> all = new List<(GroundItem, int)>(); private List<(GroundItem Item, int Order)>? unindexed; private Dictionary<(int X, int Y, int Z), List<(GroundItem Item, int Order)>>? cells; internal SpatialGroup(float radius) { cellSize = 2.0 * (double)radius; } internal void Add(GroundItem item, int order) { all.Add((item, order)); } internal GroundItem? Source(GroundItem target, float radiusSquared, HashSet<long> used, int minimum, out int nearby) { nearby = 0; if (all.Count < minimum) { return null; } GroundItem source = null; int sourceOrder = int.MaxValue; if (!Cell(target.Position, out (int, int, int) cell)) { foreach (var item in all) { Consider(item, target, radiusSquared, used, ref nearby, ref source, ref sourceOrder); } return source; } if (cells == null) { cells = new Dictionary<(int, int, int), List<(GroundItem, int)>>(); unindexed = new List<(GroundItem, int)>(); foreach (var item2 in all) { if (!Cell(item2.Item.Position, out (int, int, int) cell2)) { unindexed.Add(item2); continue; } if (!cells.TryGetValue(cell2, out List<(GroundItem, int)> value)) { cells.Add(cell2, value = new List<(GroundItem, int)>()); } value.Add(item2); } } for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) { for (int k = -1; k <= 1; k++) { if (!cells.TryGetValue((cell.Item1 + i, cell.Item2 + j, cell.Item3 + k), out List<(GroundItem, int)> value2)) { continue; } foreach (var item3 in value2) { Consider(item3, target, radiusSquared, used, ref nearby, ref source, ref sourceOrder); } } } } foreach (var item4 in unindexed) { Consider(item4, target, radiusSquared, used, ref nearby, ref source, ref sourceOrder); } return source; } private static void Consider((GroundItem Item, int Order) entry, GroundItem target, float radiusSquared, HashSet<long> used, ref int nearby, ref GroundItem? source, ref int sourceOrder) { var (groundItem, _) = entry; if (groundItem.Id != target.Id && groundItem.Position.DistanceSquared(target.Position) <= radiusSquared) { nearby++; if (!used.Contains(groundItem.Id) && groundItem.Stack < groundItem.MaxStack && (source == null || groundItem.Stack < source.Stack || (groundItem.Stack == source.Stack && (groundItem.Created > source.Created || (groundItem.Created == source.Created && (groundItem.Id > source.Id || (groundItem.Id == source.Id && entry.Order < sourceOrder))))))) { source = groundItem; sourceOrder = entry.Order; } } } private bool Cell(Point3 position, out (int X, int Y, int Z) cell) { double num = Math.Floor((double)position.X / cellSize); double num2 = Math.Floor((double)position.Y / cellSize); double num3 = Math.Floor((double)position.Z / cellSize); cell = default((int, int, int)); if (!(num > -2147483648.0) || !(num < 2147483647.0) || !(num2 > -2147483648.0) || !(num2 < 2147483647.0) || !(num3 > -2147483648.0) || !(num3 < 2147483647.0)) { return false; } cell = (X: (int)num, Y: (int)num2, Z: (int)num3); return true; } } private static readonly ItemIdentityKey Keys = new ItemIdentityKey(); public static IReadOnlyList<MergePlan> Plan(IReadOnlyList<GroundItem> items, MergeSettings settings, double worldTime, Func<string> nonce) { if (items == null) { throw new ArgumentNullException("items"); } if (settings == null) { throw new ArgumentNullException("settings"); } if (nonce == null) { throw new ArgumentNullException("nonce"); } List<(GroundItem, string)> list = (from item in items where item != null && Eligible(item, settings) select (Item: item, Key: Keys.Key(item.Identity)) into entry where entry.Key != null orderby entry.Item.Stack descending, entry.Item.Created, entry.Item.Id select entry).ToList(); Dictionary<string, SpatialGroup> dictionary = null; bool flag = false; float radiusSquared = settings.Radius * settings.Radius; HashSet<long> used = new HashSet<long>(); List<MergePlan> list2 = new List<MergePlan>(); foreach (var target in list) { if (list2.Count >= settings.MaxMergesPerScan) { break; } if (used.Contains(target.Item1.Id) || target.Item1.Stack >= target.Item1.MaxStack) { continue; } if (flag && dictionary == null) { dictionary = new Dictionary<string, SpatialGroup>(StringComparer.Ordinal); for (int num = 0; num < list.Count; num++) { (GroundItem, string) tuple = list[num]; if (!dictionary.TryGetValue(tuple.Item2, out var value)) { dictionary.Add(tuple.Item2, value = new SpatialGroup(settings.Radius)); } value.Add(tuple.Item1, num); } } GroundItem groundItem; int nearby; if (dictionary != null) { groundItem = dictionary[target.Item2].Source(target.Item1, radiusSquared, used, settings.MinCount, out nearby); } else { List<GroundItem> list3 = (from other in list where other.Item.Id != target.Item1.Id && string.Equals(other.Key, target.Item2, StringComparison.Ordinal) && other.Item.Position.DistanceSquared(target.Item1.Position) <= radiusSquared select other.Item).ToList(); nearby = list3.Count; groundItem = ((nearby + 1 < settings.MinCount) ? null : (from other in list3 where !used.Contains(other.Id) && other.Stack < other.MaxStack orderby other.Stack, other.Created descending, other.Id descending select other).FirstOrDefault()); } if (nearby + 1 < settings.MinCount || groundItem == null) { flag = list.Count > 64; continue; } int num2 = Math.Min(groundItem.Stack, target.Item1.MaxStack - target.Item1.Stack); if (num2 > 0) { used.Add(target.Item1.Id); used.Add(groundItem.Id); list2.Add(new MergePlan(target.Item1, groundItem, num2, nonce(), worldTime)); } } return list2; } public static bool Eligible(GroundItem item, MergeSettings settings) { if (item.Valid && item.OwnedLocally && item.MaxStack > 1 && item.Stack > 0 && !item.Moving && !item.Pulled && !item.PendingIntent && item.AgeSeconds >= settings.SettleSeconds && item.Identity != null) { return !settings.Ignores(item.Identity.Prefab); } return false; } } public enum SourceObservation { NotFound, FoundWithoutNonce, FoundWithNonce } public enum ReconcileAction { Wait, RollBackTarget, Clear, FinishDestroy } public static class MergeReconcile { public const double ReconcileAfterSeconds = 10.0; public const double BarrierTimeoutSeconds = 10.0; public const double ClearAfterSeconds = 60.0; public const float SearchMargin = 2f; public static ReconcileAction Decide(MergeIntent intent, double now, bool areaReady, SourceObservation source) { if (intent.Phase2Saved && intent.DestroysSource == true && source == SourceObservation.FoundWithoutNonce) { return ReconcileAction.FinishDestroy; } if (!areaReady || now - intent.WorldTime < 10.0) { return ReconcileAction.Wait; } if (source == SourceObservation.FoundWithNonce || intent.Phase2Saved) { return ReconcileAction.Clear; } if (intent.TracksPhase2 || source == SourceObservation.FoundWithoutNonce) { return ReconcileAction.RollBackTarget; } return ReconcileAction.Clear; } public static int StackAfterRollback(int savedStack, int moved, int? preMergeStack) { if (savedStack < 1) { savedStack = 1; } if (moved < 1) { return savedStack; } if (!preMergeStack.HasValue) { return Math.Max(1, savedStack - moved); } int value = preMergeStack.Value; int num = value + moved; if (savedStack == num) { return Math.Max(1, savedStack - moved); } int num2 = savedStack - moved; if (num2 >= value) { return num2; } return Math.Max(1, Math.Min(savedStack, value)); } public static float SearchRadius(MergeSettings settings) { return settings.Radius + 2f; } } public sealed class MergeSettings { public const float DefaultRadius = 3f; public const int DefaultMinCount = 2; public const int DefaultMaxMergesPerScan = 2; public const double DefaultSettleSeconds = 2.0; private readonly HashSet<string> ignore; public float Radius { get; } public int MinCount { get; } public int MaxMergesPerScan { get; } public double SettleSeconds { get; } public MergeSettings(float radius = 3f, int minCount = 2, int maxMergesPerScan = 2, double settleSeconds = 2.0, IEnumerable<string>? ignore = null) { Radius = ((float.IsNaN(radius) || float.IsInfinity(radius)) ? 3f : Math.Min(10f, Math.Max(0.5f, radius))); MinCount = Math.Min(20, Math.Max(2, minCount)); MaxMergesPerScan = Math.Min(20, Math.Max(1, maxMergesPerScan)); SettleSeconds = ((double.IsNaN(settleSeconds) || double.IsInfinity(settleSeconds)) ? 2.0 : Math.Min(30.0, Math.Max(0.0, settleSeconds))); this.ignore = new HashSet<string>(from name in ignore ?? Enumerable.Empty<string>() select (name ?? "").Trim() into name where name.Length > 0 select name, StringComparer.OrdinalIgnoreCase); } public bool Ignores(string prefab) { return ignore.Contains(prefab ?? ""); } } } namespace ExtraPotions.Magpie.Loot { public enum LootSource { Unknown, Creature, Destructible, RockOrTree, Pickable, Spawner, PlayerDrop, ContainerSpill, TombstoneSpill, PieceRefund, StationOutput, ShipSpill } [Flags] public enum LootSources { None = 0, Creatures = 1, Destructibles = 2, Pickables = 4, Spawners = 8, Default = 0xF } public readonly struct LootExtra { public int Amount { get; } public LootExtra(int amount) { Amount = amount; } } public static class LootPolicy { public const int MinMultiplier = 1; public const int MaxMultiplier = 20; public static bool IsWorldLoot(LootSource source) { if ((uint)(source - 1) <= 4u) { return true; } return false; } public static LootSources FlagFor(LootSource source) { switch (source) { case LootSource.Creature: return LootSources.Creatures; case LootSource.Destructible: case LootSource.RockOrTree: return LootSources.Destructibles; case LootSource.Pickable: return LootSources.Pickables; case LootSource.Spawner: return LootSources.Spawners; default: return LootSources.None; } } public static bool ShouldMark(LootSource source, LootSources enabled, int multiplier, float wildbloodCreatureMultiplier) { if (ClampMultiplier(multiplier) <= 1 || !IsWorldLoot(source)) { return false; } LootSources lootSources = FlagFor(source); if ((enabled & lootSources) != lootSources) { return false; } if (source == LootSource.Creature && wildbloodCreatureMultiplier > 1f) { return false; } return true; } public static int EffectiveMultiplier(int configured, bool allowed) { if (!allowed) { return 1; } return ClampMultiplier(configured); } public static int Picked(int before, int after) { return Math.Max(0, after - before); } public static bool ExtraCheated(bool takenCheated) { return takenCheated; } public static int ClampMultiplier(int multiplier) { return Math.Min(20, Math.Max(1, multiplier)); } public static LootExtra Extra(bool marked, int maxStack, int pickedAmount, int multiplier, bool stackableOnly) { int num = ClampMultiplier(multiplier); if (!marked || num <= 1 || pickedAmount <= 0 || (stackableOnly && maxStack <= 1)) { return new LootExtra(0); } long num2 = (long)pickedAmount * (long)(num - 1); return new LootExtra((int)((num2 > int.MaxValue) ? int.MaxValue : num2)); } public static IReadOnlyList<int> SplitIntoStacks(int amount, int maxStack) { int num = Math.Max(1, maxStack); List<int> list = new List<int>(); for (int num2 = Math.Max(0, amount); num2 > 0; num2 -= num) { list.Add(Math.Min(num, num2)); } return list; } public static int OverflowDrop(bool added, int stackLeft) { if (added || stackLeft <= 0) { return 0; } return stackLeft; } } } namespace ExtraPotions.Magpie.Items { public static class CheatedMark { public const MagpieModule Modules = MagpieModule.Pickup | MagpieModule.Merge | MagpieModule.Loot; public static bool Active(MagpieModule on) { return (on & (MagpieModule.Pickup | MagpieModule.Merge | MagpieModule.Loot)) != 0; } public static int? OnSave(bool active, bool cheated, int stored) { if (!active || !cheated || stored == 1) { return null; } return 1; } public static bool OnLoad(bool active, bool cheated, int stored) { if (!cheated) { if (active) { return stored == 1; } return false; } return true; } } public interface IItemIdentityKey<TItem> { string? Key(TItem? item); bool Same(TItem? left, TItem? right); } public sealed class ItemIdentity { private readonly KeyValuePair<string, string>[] customData; public string Prefab { get; } public int Quality { get; } public int Variant { get; } public long CrafterId { get; } public string CrafterName { get; } public float Durability { get; } public int WorldLevel { get; } public IReadOnlyList<KeyValuePair<string, string>> CustomData => customData; public bool Cheated { get; } public bool Loot { get; } public bool IsValid { get { if (!string.IsNullOrWhiteSpace(Prefab) && Quality >= 0 && Variant >= 0 && WorldLevel >= 0 && !float.IsNaN(Durability)) { return !float.IsInfinity(Durability); } return false; } } public ItemIdentity(string prefab, int quality, int variant, long crafterId, string crafterName, float durability, int worldLevel, IReadOnlyDictionary<string, string>? customData, bool cheated, bool loot) { Prefab = prefab ?? ""; Quality = quality; Variant = variant; CrafterId = crafterId; CrafterName = crafterName ?? ""; Durability = ((durability == 0f) ? 0f : durability); WorldLevel = worldLevel; this.customData = (customData ?? new Dictionary<string, string>()).Select<KeyValuePair<string, string>, KeyValuePair<string, string>>((KeyValuePair<string, string> pair) => new KeyValuePair<string, string>(pair.Key ?? "", pair.Value ?? "")).OrderBy<KeyValuePair<string, string>, string>((KeyValuePair<string, string> pair) => pair.Key, StringComparer.Ordinal).ToArray(); Cheated = cheated; Loot = loot; } internal string? CanonicalKey() { if (!IsValid) { return null; } CultureInfo invariantCulture = CultureInfo.InvariantCulture; List<string> values = new List<string> { "v1", "p=" + Escape(Prefab), "q=" + Quality.ToString(invariantCulture), "v=" + Variant.ToString(invariantCulture), "ci=" + CrafterId.ToString(invariantCulture), "cn=" + Escape(CrafterName), "d=" + BitConverter.SingleToInt32Bits(Durability).ToString("x8", invariantCulture), "wl=" + WorldLevel.ToString(invariantCulture), "ch=" + (Cheated ? "1" : "0"), "lt=" + (Loot ? "1" : "0"), "cd=" + string.Join(",", customData.Select((KeyValuePair<string, string> pair) => Escape(pair.Key) + "=" + Escape(pair.Value))) }; return string.Join(";", values); } private static string Escape(string value) { return value.Replace("\\", "\\\\").Replace(";", "\\;").Replace(",", "\\,") .Replace("=", "\\="); } } public sealed class ItemIdentityKey : IItemIdentityKey<ItemIdentity> { public string? Key(ItemIdentity? item) { return item?.CanonicalKey(); } public bool Same(ItemIdentity? left, ItemIdentity? right) { string text = Key(left); if (text != null) { return string.Equals(text, Key(right), StringComparison.Ordinal); } return false; } } } namespace ExtraPotions.Magpie.Conversion { public enum ImportSource { SwiftPickup, FloatingItems, LegacyFloatingItems } public sealed class ImportPlan { public ImportSource Source { get; } public IReadOnlyDictionary<string, object> Values { get; } public IReadOnlyList<string> Notes { get; } internal ImportPlan(ImportSource source, IDictionary<string, object> values, IList<string> notes) { Source = source; Values = new ReadOnlyDictionary<string, object>(new Dictionary<string, object>(values, StringComparer.Ordinal)); Notes = Array.AsReadOnly(notes.ToArray()); } public override string ToString() { return string.Join("\n", new string[1] { "Source: " + Source }.Concat(Values.Select<KeyValuePair<string, object>, string>((KeyValuePair<string, object> pair) => "Set " + pair.Key + " = " + Convert.ToString(pair.Value, CultureInfo.InvariantCulture))).Concat(Notes)); } } public static class SourceImport { private const string MeadworksMenuKey = "F8"; public static string FileName(ImportSource source) { return source switch { ImportSource.SwiftPickup => "user.swiftpickup.cfg", ImportSource.FloatingItems => "com.ab5olutezer0.valheim.floatingitems.cfg", ImportSource.LegacyFloatingItems => "com.michal.valheim.floatingitems.cfg", _ => throw new ArgumentOutOfRangeException("source"), }; } public static string SourceId(ImportSource source) { return source switch { ImportSource.SwiftPickup => "user.swiftpickup", ImportSource.FloatingItems => "com.ab5olutezer0.valheim.floatingitems", ImportSource.LegacyFloatingItems => "com.michal.valheim.floatingitems", _ => throw new ArgumentOutOfRangeException("source"), }; } private static SettingSpec? Setting(ImportSource source, string key) { if (source != ImportSource.SwiftPickup) { if (!(key == "General.ExcludedItems")) { return null; } return MagpieSettings.ExcludedItems; } return key switch { "General.Enabled" => MagpieSettings.AutoPickupAtStart, "General.PickupRange" => MagpieSettings.PickupRange, "General.ScanIntervalSeconds" => MagpieSettings.ScanInterval, "General.MaxPickupsPerScan" => MagpieSettings.MaxPickupsPerScan, "General.MagnetSpeed" => MagpieSettings.MagnetSpeed, "General.IgnoreItems" => MagpieSettings.IgnoreItems, "General.ToggleKey" => MagpieSettings.PickupToggleKey, "Loot.LootMultiplier" => MagpieSettings.LootMultiplier, "Merge.MergeNearbyDrops" => MagpieSettings.MergeEnabled, "Merge.MergeRadius" => MagpieSettings.MergeRadius, "Merge.MergeMinCount" => MagpieSettings.MergeMinCount, "Merge.MaxMergesPerScan" => MagpieSettings.MaxMergesPerScan, "Performance.SkipPickupEffects" => MagpieSettings.SkipPickupEffects, "Performance.ShowPickupMessages" => MagpieSettings.ShowPickupMessages, _ => null, }; } public static ImportPlan Plan(ImportSource source, string text) { FileName(source); if (text == null || text.Length > 1048576) { throw new ArgumentException("Import source exceeds 1 MiB or is missing."); } Dictionary<string, object> dictionary = new Dictionary<string, object>(StringComparer.Ordinal); List<string> list = new List<string>(); string text2 = ""; string[] array = text.Split('\n'); for (int i = 0; i < array.Length; i++) { string text3 = array[i].Trim().TrimStart('\ufeff'); if (text3.Length == 0 || text3[0] == '#' || text3[0] == ';') { continue; } if (text3[0] == '[' && text3[text3.Length - 1] == ']') { text2 = text3.Substring(1, text3.Length - 2).Trim(); continue; } int num = text3.IndexOf('='); if (num <= 0) { list.Add("Skipped unrecognized source line."); continue; } string text4 = text2 + "." + text3.Substring(0, num).Trim(); string text5 = text3.Substring(num + 1).Trim(); SettingSpec settingSpec = Setting(source, text4); if (settingSpec == null) { list.Add("Skipped unknown key: " + text4); continue; } if (dictionary.ContainsKey(settingSpec.Id)) { throw new FormatException("Duplicate source setting: " + text4); } object value; if (settingSpec.Default is bool) { if (!bool.TryParse(text5, out var result)) { throw new FormatException("Invalid boolean: " + text4); } value = result; } else if (settingSpec.Default is int) { if (!long.TryParse(text5, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2)) { throw new FormatException("Invalid integer: " + text4); } int num2 = (int)Math.Max(settingSpec.Minimum.Value, Math.Min(settingSpec.Maximum.Value, result2)); if (num2 != result2) { list.Add("Clamped " + text4 + " to " + num2.ToString(CultureInfo.InvariantCulture)); } value = num2; } else if (settingSpec.Default is float) { if (!double.TryParse(text5, NumberStyles.Float, CultureInfo.InvariantCulture, out var result3) || double.IsNaN(result3) || double.IsInfinity(result3)) { throw new FormatException("Invalid finite number: " + text4); } float num3 = (float)Math.Max(settingSpec.Minimum.Value, Math.Min(settingSpec.Maximum.Value, result3)); if (result3 < settingSpec.Minimum.Value || result3 > settingSpec.Maximum.Value) { list.Add("Clamped " + text4 + " to " + num3.ToString("R", CultureInfo.InvariantCulture)); } value = num3; } else if (settingSpec == MagpieSettings.PickupToggleKey && string.Equals(text5, "F8", StringComparison.OrdinalIgnoreCase)) { value = ""; list.Add("Pickup Toggle Key left unbound: F8 is the Meadworks menu key."); } else { value = text5; } dictionary.Add(settingSpec.Id, value); } return new ImportPlan(source, dictionary, list); } } } namespace ExtraPotions.Magpie.Config { public static class MagpieSettings { private const string General = "1 - General"; private const string Pickup = "2 - Pickup"; private const string Messages = "3 - Messages"; private const string Merge = "4 - Merge"; private const string Float = "5 - Float"; private const string Cheats = "6 - Cheats"; private const string Interface = "7 - Interface"; private const SettingScope L = SettingScope.Local; private const SettingScope SA = SettingScope.ServerAuthoritative; private const SettingScope C = SettingScope.Cheat; public static readonly SettingSpec Enabled = new SettingSpec("1 - General", "Enabled", SettingScope.ServerAuthoritative, true, "Turns all of Magpie on or off. Off: the game's own auto pickup runs and no item is merged, floated or marked."); public static readonly SettingSpec PickupEnabled = new SettingSpec("1 - General", "Pickup Enabled", SettingScope.ServerAuthoritative, true, "Magpie pickup (fast, smooth magnet). Off: the game's own auto pickup runs."); public static readonly SettingSpec MergeEnabled = new SettingSpec("1 - General", "Merge Enabled", SettingScope.ServerAuthoritative, true, "Identical items lying near each other merge into one stack."); public static readonly SettingSpec FloatEnabled = new SettingSpec("1 - General", "Float Enabled", SettingScope.ServerAuthoritative, true, "Dropped items float on water and tar like wood. A change also applies to items already lying in the world."); public static readonly SettingSpec AutoPickupAtStart = new SettingSpec("2 - Pickup", "Auto Pickup At Start", SettingScope.Local, true, "Auto pickup state when the game starts; the game's auto pickup key (and the optional toggle key) changes it in play."); public static readonly SettingSpec PickupRange = new SettingSpec("2 - Pickup", "Pickup Range", SettingScope.Local, 3.5f, "Radius in meters around the player in which items glide in. The server's Max Pickup Range caps it; range bonuses from other mods still scale it.", 0.5, 20.0); public static readonly SettingSpec MagnetSpeed = new SettingSpec("2 - Pickup", "Magnet Speed", SettingScope.Local, 15f, "Speed in meters per second at which items glide to the player (the game uses 15).", 1.0, 50.0); public static readonly SettingSpec ScanInterval = new SettingSpec("2 - Pickup", "Scan Interval", SettingScope.Local, 0.15f, "Seconds between searches for items in range.", 0.05, 1.0); public static readonly SettingSpec MaxPickupsPerScan = new SettingSpec("2 - Pickup", "Max Pickups Per Scan", SettingScope.Local, 2, "Items taken per search at most; 0 means no limit. Never more than one item per frame.", 0.0, 20.0); public static readonly SettingSpec IgnoreItems = new SettingSpec("2 - Pickup", "Ignore Items", SettingScope.Local, "", "Comma-separated prefab names that are never picked up automatically (picking them up by hand still works)."); public static readonly SettingSpec MaxPickupRange = new SettingSpec("2 - Pickup", "Max Pickup Range", SettingScope.ServerAuthoritative, 10f, "Largest Pickup Range any player may use, in meters.", 2.0, 20.0); public static readonly SettingSpec ShowPickupMessages = new SettingSpec("3 - Messages", "Show Pickup Messages", SettingScope.Local, true, "Shows the top-left message for each item taken automatically."); public static readonly SettingSpec GroupPickupMessages = new SettingSpec("3 - Messages", "Group Pickup Messages", SettingScope.Local, true, "Items of the same kind taken within one second show as one line with the summed amount."); public static readonly SettingSpec SkipPickupEffects = new SettingSpec("3 - Messages", "Skip Pickup Effects", SettingScope.Local, true, "No particles or sound for each item taken automatically."); public static readonly SettingSpec MergeRadius = new SettingSpec("4 - Merge", "Merge Radius", SettingScope.ServerAuthoritative, 3f, "Distance in meters within which identical items merge.", 0.5, 10.0); public static readonly SettingSpec MergeMinCount = new SettingSpec("4 - Merge", "Merge Min Count", SettingScope.ServerAuthoritative, 2, "Identical items needed within the radius before they merge.", 2.0, 20.0); public static readonly SettingSpec MaxMergesPerScan = new SettingSpec("4 - Merge", "Max Merges Per Scan", SettingScope.ServerAuthoritative, 2, "Merges started per scan at most.", 1.0, 20.0); public static readonly SettingSpec MergeScanInterval = new SettingSpec("4 - Merge", "Merge Scan Interval", SettingScope.ServerAuthoritative, 0.5f, "Seconds between merge scans.", 0.1, 5.0); public static readonly SettingSpec MergeSettleTime = new SettingSpec("4 - Merge", "Merge Settle Time", SettingScope.ServerAuthoritative, 2f, "Seconds an item must lie still before it may merge.", 0.0, 30.0); public static readonly SettingSpec MergeIgnoreItems = new SettingSpec("4 - Merge", "Merge Ignore Items", SettingScope.ServerAuthoritative, "", "Comma-separated prefab names that never merge."); public static readonly SettingSpec ExcludedItems = new SettingSpec("5 - Float", "Excluded Items", SettingScope.ServerAuthoritative, "", "Comma-separated prefab names that keep sinking, for example \"Coins, IronScrap\". A change also applies to items already lying in the world."); public static readonly SettingSpec LootMultiplier = new SettingSpec("6 - Cheats", "Loot Multiplier", SettingScope.Cheat, 1, "Cheat: world loot you pick up is multiplied by this number (1 = off). Only drops from creatures, destructibles, rocks, trees, pickables and loot spawners; the extras are not marked as cheated. In multiplayer the server decides.", 1.0, 20.0); public static readonly SettingSpec LootSources = new SettingSpec("6 - Cheats", "Loot Sources", SettingScope.Cheat, ExtraPotions.Magpie.Loot.LootSources.Default, "Cheat: which kinds of world loot the multiplier applies to. Player drops, chest and tombstone spills, refunds and station output never count."); public static readonly SettingSpec StackableOnly = new SettingSpec("6 - Cheats", "Stackable Only", SettingScope.Cheat, true, "Cheat: only stackable loot is multiplied; weapons, armor and other single items stay one item."); public static readonly SettingSpec PickupToggleKey = new SettingSpec("7 - Interface", "Pickup Toggle Key", SettingScope.Local, "", "Optional key that turns auto pickup on or off, like the game's own auto pickup key. Merging and the loot multiplier are not affected.", null, null, shortcut: true); public static readonly SettingSpec PickupTogglePadChord = new SettingSpec("7 - Interface", "Pickup Toggle Pad Chord", SettingScope.Local, "", "Optional gamepad chord that toggles auto pickup, for example LB+X. Unbound by default; typing and game windows block it. Merging and loot are unaffected."); public static readonly SettingSpec LockedPlayerSettings = new SettingSpec("7 - Interface", "Locked Player Settings", SettingScope.ServerAuthoritative, "", "Comma-separated Magpie Local setting IDs (Section.Key) that the server forces to its own values. Empty: personal settings remain independent."); public static IReadOnlyList<SettingSpec> All { get; } = new SettingSpec[27] { Enabled, PickupEnabled, MergeEnabled, FloatEnabled, AutoPickupAtStart, PickupRange, MagnetSpeed, ScanInterval, MaxPickupsPerScan, IgnoreItems, MaxPickupRange, ShowPickupMessages, GroupPickupMessages, SkipPickupEffects, MergeRadius, MergeMinCount, MaxMergesPerScan, MergeScanInterval, MergeSettleTime, MergeIgnoreItems, ExcludedItems, LootMultiplier, LootSources, StackableOnly, PickupToggleKey, PickupTogglePadChord, LockedPlayerSettings }; public static SettingSpec? Find(string? id) { if (id != null) { return All.FirstOrDefault((SettingSpec setting) => string.Equals(setting.Id, id.Trim(), StringComparison.OrdinalIgnoreCase)); } return null; } } public sealed class NameList { public static readonly NameList Empty = new NameList(new HashSet<string>(StringComparer.OrdinalIgnoreCase)); private readonly HashSet<string> names; public int Count => names.Count; public IEnumerable<string> Names => names; private NameList(HashSet<string> names) { this.names = names; } public bool Contains(string? name) { string text = (name ?? "").Trim(); if (text.Length > 0) { return names.Contains(text); } return false; } public static NameList Parse(string? text) { HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); string[] array = (text ?? "").Split(','); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim(); if (text2.Length > 0) { hashSet.Add(text2); } } if (hashSet.Count != 0) { return new NameList(hashSet); } return Empty; } } public static class PickupRadius { public static float Effective(float pickupRange, float maxPickupRange, float currentPlayerRange, float basePlayerRange) { float val = MagpieSettings.PickupRange.Clamp(pickupRange); float val2 = MagpieSettings.MaxPickupRange.Clamp(maxPickupRange); return Math.Min(val, val2) * Factor(currentPlayerRange, basePlayerRange); } public static float Factor(float current, float baseRange) { if (!Usable(current) || !Usable(baseRange)) { return 1f; } return current / baseRange; } private static bool Usable(float value) { if (!float.IsNaN(value) && !float.IsInfinity(value)) { return value > 0f; } return false; } } public sealed class LockList { private readonly HashSet<string> locked; public static LockList Empty { get; } = new LockList(Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>()); public IReadOnlyList<string> Locked { get; } public IReadOnlyList<string> Unknown { get; } public IReadOnlyList<string> NotLockable { get; } private LockList(IReadOnlyList<string> lockedIds, IReadOnlyList<string> unknown, IReadOnlyList<string> notLockable) { Locked = lockedIds; Unknown = unknown; NotLockable = notLockable; locked = new HashSet<string>(lockedIds, StringComparer.OrdinalIgnoreCase); } public static LockList Parse(string? text) { List<string> list = new List<string>(); List<string> list2 = new List<string>(); List<string> list3 = new List<string>(); string[] array = (text ?? "").Split(','); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim(); if (text2.Length != 0) { SettingSpec settingSpec = MagpieSettings.Find(text2); List<string> list4 = ((settingSpec == null) ? list2 : (settingSpec.Lockable ? list : list3)); string text3 = settingSpec?.Id ?? text2; if (!list4.Contains<string>(text3, StringComparer.OrdinalIgnoreCase)) { list4.Add(text3); } } } if (list.Count + list2.Count + list3.Count != 0) { return new LockList(list, list2, list3); } return Empty; } public bool IsLocked(string? id) { if (id != null) { return locked.Contains(id.Trim()); } return false; } public string Describe() { string text = Locked.Count + " locked"; if (Unknown.Count > 0) { text = text + "; unknown: " + string.Join(", ", Unknown); } if (NotLockable.Count > 0) { text = text + "; not lockable: " + string.Join(", ", NotLockable); } return text; } } public static class LockedValues { public static string Encode(IEnumerable<KeyValuePair<string, string>> values) { if (values == null) { throw new ArgumentNullException("values"); } return string.Join(";", values.Select<KeyValuePair<string, string>, string>((KeyValuePair<string, string> v) => Escape(v.Key) + "=" + Escape(v.Value ?? ""))); } public static IReadOnlyDictionary<string, string> Decode(string? text) { Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); if (string.IsNullOrEmpty(text)) { return dictionary; } foreach (string item in Split(text, ';')) { List<string> list = Split(item, '=').ToList(); if (list.Count == 2) { string text2 = Unescape(list[0]); string text3 = Unescape(list[1]); if (!string.IsNullOrWhiteSpace(text2) && text3 != null && !dictionary.ContainsKey(text2.Trim())) { dictionary.Add(text2.Trim(), text3); } } } return dictionary; } public static string Compose(LockList list, Func<string, string?> ownText) { if (list == null) { throw new ArgumentNullException("list"); } if (ownText == null) { throw new ArgumentNullException("ownText"); } return Encode(from id in list.Locked select new KeyValuePair<string, string>(id, ownText(id)) into p where p.Value != null select new KeyValuePair<string, string>(p.Key, p.Value)); } private static string Escape(string text) { StringBuilder stringBuilder = new StringBuilder(text.Length); foreach (char c in text) { if (c == '\\' || c == ';' || c == '=') { stringBuilder.Append('\\'); } stringBuilder.Append(c); } return stringBuilder.ToString(); } private static IEnumerable<string> Split(string text, char separator) { int num = 0; for (int i = 0; i < text.Length; i++) { if (text[i] == '\\') { i++; } else if (text[i] == separator) { yield return text.Substring(num, i - num); num = i + 1; } } yield return text.Substring(num); } private static string? Unescape(string text) { StringBuilder stringBuilder = new StringBuilder(text.Length); for (int i = 0; i < text.Length; i++) { if (text[i] == '\\' && ++i >= text.Length) { return null; } stringBuilder.Append(text[i]); } return stringBuilder.ToString(); } } public readonly struct PlayerLock { public bool Locked { get; } public string? ServerText { get; } public PlayerLock(bool locked, string? serverText) { Locked = locked; ServerText = (locked ? serverText : null); } public override string ToString() { if (!Locked) { return "Own"; } return "Locked (" + ServerText + ")"; } } public static class PlayerLocks { public static PlayerLock Evaluate(string id, bool serverBinds, LockList list, IReadOnlyDictionary<string, string> values) { if (list == null) { throw new ArgumentNullException("list"); } if (values == null) { throw new ArgumentNullException("values"); } if (!serverBinds || !list.IsLocked(id) || !values.TryGetValue(id.Trim(), out string value)) { return new PlayerLock(locked: false, null); } return new PlayerLock(locked: true, value); } } public enum SettingScope { Local, ServerAuthoritative, Cheat } public sealed class SettingSpec { public string Section { get; } public string Key { get; } public string Id => Section + "." + Key; public SettingScope Scope { get; } public bool Lockable => Scope == SettingScope.Local; public object Default { get; } public Type ValueType => Default.GetType(); public double? Minimum { get; } public double? Maximum { get; } public string Description { get; } public bool Shortcut { get; } public SettingSpec(string section, string key, SettingScope scope, object defaultValue, string description, double? minimum = null, double? maximum = null, bool shortcut = false) { Section = section; Key = key; Scope = scope; Default = defaultValue ?? throw new ArgumentNullException("defaultValue"); Description = description; Minimum = minimum; Maximum = maximum; Shortcut = shortcut; } public T Clamp<T>(T value) { if (value is float) { object obj = value; float num = (float)((obj is float) ? obj : null); if (float.IsNaN(num) || float.IsInfinity(num)) { return (T)Default; } return (T)(object)(float)Limit(num); } if (value is int) { object obj2 = value; int num2 = (int)((obj2 is int) ? obj2 : null); return (T)(object)(int)Limit(num2); } return value; } private double Limit(double value) { if (Minimum.HasValue && value < Minimum.Value) { value = Minimum.Value; } if (Maximum.HasValue && value > Maximum.Value) { value = Maximum.Value; } return value; } } }
plugins/Magpie/Magpie.dll
Decompiled 21 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; using System.Security.Permissions; using System.Text; using System.Threading; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using ExtraPotions.Magpie.Config; using ExtraPotions.Magpie.Conversion; using ExtraPotions.Magpie.Items; using ExtraPotions.Magpie.Loot; using ExtraPotions.Magpie.Merging; using ExtraPotions.Magpie.Pickup; using ExtraPotions.Magpie.Runtime.Adopted.FloatingItems; using ExtraPotions.Magpie.Runtime.Loot; using ExtraPotions.Magpie.Runtime.Merge; using ExtraPotions.Magpie.Runtime.Pickup; using ExtraPotions.Magpie.Runtime.Settings; using ExtraPotions.Magpie.StepAside; using ExtraPotions.Meadworks.Crucible; using HarmonyLib; using Jotunn.Managers; using Jotunn.Utils; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("ExtraPotions")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright (C) 2026 expDARE (ExtraPotions Meadworks)")] [assembly: AssemblyFileVersion("0.1.13.0")] [assembly: AssemblyInformationalVersion("0.1.13+254080b9351ec46e3f31fa4a55580d9d8cf592f1")] [assembly: AssemblyProduct("Magpie")] [assembly: AssemblyTitle("Magpie")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.1.13.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ExtraPotions.Magpie.Runtime { internal static class MagpiePadChord { internal static PadButton[] Parse(string? text) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrWhiteSpace(text) || text.Length > 256) { return Array.Empty<PadButton>(); } string[] array = text.Split('+'); if (array.Length > 16) { return Array.Empty<PadButton>(); } List<PadButton> list = new List<PadButton>(); string[] array2 = array; PadButton item = default(PadButton); for (int i = 0; i < array2.Length; i++) { if (!GamepadMap.TryParse(array2[i], ref item) || list.Contains(item)) { return Array.Empty<PadButton>(); } list.Add(item); } return list.ToArray(); } internal static bool Pressed(IReadOnlyList<PadButton> chord, bool active, bool allowed, Func<PadButton, bool> held, Func<PadButton, bool> down, Action<PadButton> consume) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_005d: 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_0066: Unknown result type (might be due to invalid IL or missing references) if (!active || !allowed || chord.Count == 0) { return false; } bool flag = false; foreach (PadButton item in chord) { if (!held(item)) { return false; } flag |= down(item); } if (!flag) { return false; } foreach (PadButton item2 in chord) { consume(item2); } return true; } } internal sealed class MagpieImport : IDisposable { private sealed class Snapshot { internal string SourceHash = ""; internal string TargetHash = ""; internal string Session = ""; internal long Generation; internal long World; internal SessionRole Role; internal bool Consumed; } private readonly ConfigFile config; private readonly MagpieConfig settings; private readonly List<IDisposable> registrations = new List<IDisposable>(); private readonly ConditionalWeakTable<ImportPlan, Snapshot> previews = new ConditionalWeakTable<ImportPlan, Snapshot>(); private bool disposed; internal MagpieImport(ConfigFile config, MagpieConfig settings) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Expected O, but got Unknown this.config = config; this.settings = settings; try { foreach (ImportSource value in Enum.GetValues(typeof(ImportSource))) { ImportSource selected = value; registrations.Add(MeadworksConversion.Register(new ConversionRegistration("magpie." + ((object)value/*cast due to .constrained prefix*/).ToString().ToLowerInvariant(), "com.meadworks.magpie", SourceImport.SourceId(value), ((object)value/*cast due to .constrained prefix*/).ToString(), (Func<bool>)(() => !disposed && Plugin.IsReady && Plugin.Session.IsAuthority && File.Exists(SourcePath(selected))), (Func<object>)(() => Preview(selected)), (Func<object, IReadOnlyDictionary<string, string>, bool, object>)((object document, IReadOnlyDictionary<string, string> resolutions, bool backup) => Apply((ImportPlan)(((document is ImportPlan) ? document : null) ?? throw new ArgumentException("Wrong Magpie preview.")), backup))))); } } catch { Dispose(); throw; } } private static string SourcePath(ImportSource source) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) return Path.Combine(Paths.ConfigPath, SourceImport.FileName(source)); } private static string Hash(byte[] bytes) { using SHA256 sHA = SHA256.Create(); return Convert.ToBase64String(sHA.ComputeHash(bytes)); } private static string Read(string path, out string hash) { using FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); if (fileStream.Length > 1048576) { throw new InvalidDataException("Import source exceeds 1 MiB."); } using MemoryStream memoryStream = new MemoryStream(); fileStream.CopyTo(memoryStream); byte[] array = memoryStream.ToArray(); hash = Hash(array); using StreamReader streamReader = new StreamReader(new MemoryStream(array)); return streamReader.ReadToEnd(); } private string TargetHash() { string text = (File.Exists(config.ConfigFilePath) ? Hash(File.ReadAllBytes(config.ConfigFilePath)) : "missing"); IEnumerable<string> values = from key in config.Keys.OrderBy<ConfigDefinition, string>((ConfigDefinition key) => key.Section, StringComparer.Ordinal).ThenBy<ConfigDefinition, string>((ConfigDefinition key) => key.Key, StringComparer.Ordinal) select key.Section + "." + key.Key + "=" + TomlTypeConverter.ConvertToString(config[key].BoxedValue, config[key].SettingType); return Hash(Encoding.UTF8.GetBytes(text + "\n" + string.Join("\n", values))); } private void AuthorityAdmission() { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Invalid comparison between Unknown and I4 if (disposed || !Plugin.IsReady || !Plugin.Session.IsAuthority || (int)Plugin.Session.Role == 3) { throw new InvalidOperationException("Preview and apply Magpie imports on the current authority."); } } private ImportPlan Preview(ImportSource source) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) AuthorityAdmission(); SessionSnapshot session = Plugin.Session; Snapshot snapshot = new Snapshot { Generation = session.Generation, World = session.WorldId, Session = session.SessionId, Role = session.Role, TargetHash = TargetHash() }; ImportPlan val = SourceImport.Plan(source, Read(SourcePath(source), out snapshot.SourceHash)); Values(val, out List<ConfigEntryBase> _, requirePermission: false); Admission(val, snapshot); previews.Add(val, snapshot); return val; } private void Admission(ImportPlan plan, Snapshot snapshot) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) AuthorityAdmission(); SessionSnapshot session = Plugin.Session; if (snapshot.Consumed || snapshot.Generation != session.Generation || snapshot.World != session.WorldId || snapshot.Session != session.SessionId || snapshot.Role != session.Role || snapshot.TargetHash != TargetHash()) { throw new InvalidOperationException("Import target or session changed; preview again."); } Read(SourcePath(plan.Source), out string hash); if (hash != snapshot.SourceHash) { throw new InvalidOperationException("Import source changed; preview again."); } } private Dictionary<string, object> Values(ImportPlan plan, out List<ConfigEntryBase> entries, bool requirePermission = true) { //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) entries = new List<ConfigEntryBase>(); Dictionary<string, object> dictionary = new Dictionary<string, object>(StringComparer.Ordinal); ConfigEntryMetadata val3 = default(ConfigEntryMetadata); foreach (KeyValuePair<string, object> pair in plan.Values) { SettingSpec val = MagpieSettings.All.Single((SettingSpec setting) => setting.Id == pair.Key); ConfigEntryBase val2 = settings.Entry(val); if (!MeadworksConfig.TryGetMetadata(val2, ref val3) || val3.ProductId != "com.meadworks.magpie" || (requirePermission && (!val3.Decision.Allowed || val3.Decision.RequiresServerAmendment || settings.IsLocked(val)))) { throw new InvalidOperationException("Import setting is blocked: " + pair.Key); } object obj = (val.Shortcut ? ((object)KeyboardShortcut.Deserialize((string)pair.Value)) : pair.Value); if (!val2.SettingType.IsInstanceOfType(obj) || (val2.Description.AcceptableValues != null && !val2.Description.AcceptableValues.IsValid(obj))) { throw new ArgumentException("Invalid imported value: " + pair.Key); } entries.Add(val2); dictionary.Add(pair.Key, obj); } ((IValidatedConversionTarget)MeadworksConversion.Target(config)).Validate((IReadOnlyDictionary<string, object>)dictionary); return dictionary; } private object Apply(ImportPlan plan, bool backup) { if (!backup || !previews.TryGetValue(plan, out Snapshot snapshot)) { throw new InvalidOperationException("Current preview and backup required."); } Admission(plan, snapshot); List<ConfigEntryBase> entries; Dictionary<string, object> values = Values(plan, out entries); if (entries.Count == 0) { return string.Format(CultureInfo.CurrentCulture, MagpieText.Text("import_done"), 0); } IConversionTarget target = MeadworksConversion.Target(config); bool savedLocally = false; ConfigAmendResult val; try { val = MeadworksConfig.AmendBatch("com.meadworks.magpie", (IReadOnlyList<ConfigEntryBase>)entries, (Action)delegate { Admission(plan, snapshot); Values(plan, out List<ConfigEntryBase> _); target.Backup(); target.WriteAtomic((IReadOnlyDictionary<string, object>)values); savedLocally = true; snapshot.Consumed = true; }, "Magpie import"); } catch (Exception innerException) when (savedLocally) { throw new IOException(MagpieText.Text("import_sync_incomplete"), innerException); } if (!val.Applied) { throw new InvalidOperationException("Magpie import was refused before writing."); } return string.Format(CultureInfo.CurrentCulture, MagpieText.Text("import_done"), values.Count); } public void Dispose() { if (disposed) { return; } disposed = true; foreach (IDisposable registration in registrations) { registration.Dispose(); } registrations.Clear(); } } internal static class MagpieRuntime { private static bool stepAsideDecided; public static MagpieConfig? Settings { get; private set; } public static MeadworksLog? Log { get; private set; } public static MagpieModule SteppedAside { get; private set; } public static bool ProductOn { get { ConfigBlockReason val = default(ConfigBlockReason); if (Plugin.Ready && Settings != null && Settings.Flag(MagpieSettings.Enabled)) { return MeadworksAuthority.ProductActive("com.meadworks.magpie", ref val); } return false; } } public static void Initialize(MagpieConfig settings, MeadworksLog log) { Settings = settings; Log = log; } public static void Shutdown() { Settings = null; stepAsideDecided = false; SteppedAside = (MagpieModule)0; } public static void DecideStepAside() { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_005f: 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_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_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_0091: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Expected O, but got Unknown if (stepAsideDecided) { return; } stepAsideDecided = true; bool? flag = ModuleStepAside.ReadMergeNearbyDrops(ReadSwiftPickupConfig()); Dictionary<string, PluginInfo>.KeyCollection keys = Chainloader.PluginInfos.Keys; MagpieModule yielding = (MagpieModule)0; MagpieModule[] modules = ModuleStepAside.Modules; foreach (MagpieModule val in modules) { ModuleSource? val2 = ModuleStepAside.InstalledSource(val, (IEnumerable<string>)keys, flag); if (val2.HasValue) { yielding |= val; string text = ModuleStepAside.ModuleId(val); ModuleSource value = val2.Value; string guid = ((ModuleSource)(ref value)).Guid; value = val2.Value; MeadworksStepAside.ReportModule("com.meadworks.magpie", text, new SourceMod(guid, ((ModuleSource)(ref value)).Name), "magpie", ModuleStepAside.TextKey(val)); } } SteppedAside = yielding; if ((int)yielding == 0) { return; } MeadworksLog? log = Log; if (log != null) { log.Info("Stepping aside for installed source mods: " + string.Join(", ", from m in ModuleStepAside.Modules where (yielding & m) > 0 select MagpieText.Text(NameKey(m))) + "."); } } public static bool ModuleOn(MagpieModule module) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected I4, but got Unknown //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Invalid comparison between Unknown and I4 if (!ProductOn || (SteppedAside & module) != 0) { return false; } switch (module - 1) { default: if ((int)module != 8) { break; } return true; case 0: return Settings.Flag(MagpieSettings.PickupEnabled); case 1: return Settings.Flag(MagpieSettings.MergeEnabled); case 3: return Settings.Flag(MagpieSettings.FloatEnabled); case 2: break; } return false; } private static string NameKey(MagpieModule module) { //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_0018: Expected I4, but got Unknown return (module - 1) switch { 0 => "module_pickup", 1 => "module_merge", 3 => "module_float", _ => "module_loot", }; } private static string? ReadSwiftPickupConfig() { try { string path = Path.Combine(Paths.ConfigPath, "user.swiftpickup.cfg"); return File.Exists(path) ? File.ReadAllText(path) : null; } catch (Exception ex) { MeadworksLog? log = Log; if (log != null) { log.Debug("SwiftPickup config not readable: " + ex.Message); } return null; } } } internal static class MagpieText { private static MeadworksTranslations? catalog; public static void Initialize(BepInPlugin metadata, ManualLogSource log) { //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Expected O, but got Unknown Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); Assembly assembly = typeof(MagpieText).Assembly; foreach (string item in MeadworksLanguages.Supported) { using Stream stream = assembly.GetManifestResourceStream("Magpie.localization." + item + ".json"); if (stream != null) { using StreamReader streamReader = new StreamReader(stream); dictionary.Add(item, streamReader.ReadToEnd()); } } catalog = MeadworksTranslations.Register(metadata, new ProductTranslations("magpie", (IReadOnlyDictionary<string, string>)dictionary), (string)null, log); } public static string Text(string key) { MeadworksTranslations? obj = catalog; return ((obj != null) ? obj.Text(key) : null) ?? key; } public static void Tick() { MeadworksTranslations? obj = catalog; if (obj != null) { obj.Update(); } } public static void Shutdown() { MeadworksTranslations? obj = catalog; if (obj != null) { obj.Dispose(); } catalog = null; } } [BepInPlugin("com.meadworks.magpie", "Magpie", "0.1.13")] [BepInDependency("com.meadworks.crucible", "0.3.31")] [BepInDependency("com.meadworks.crucible.ui", "0.3.21")] [BepInDependency("com.jotunn.jotunn", "2.30.2")] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { public const string Guid = "com.meadworks.magpie"; public const string Name = "Magpie"; public const string Version = "0.1.13"; public const string Family = "Hoard"; private ProductRegistration? registration; private MeadworksLog? log; private Harmony? harmony; private MagpieConfig? settings; private MagpieImport? imports; private string? padText; private PadButton[] padChord = Array.Empty<PadButton>(); private Player? startPlayer; private ZNet? startNetwork; private long startGeneration = -1L; private long startWorld; private string? startSession; public static bool Ready { get; private set; } public static bool AllowsUi { get; private set; } private void Awake() { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Expected O, but got Unknown //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Expected O, but got Unknown log = MeadworksLog.For(((BaseUnityPlugin)this).Logger, "Magpie"); try { if (!Plugin.IsReady) { throw new InvalidOperationException("Required Crucible is not ready"); } registration = Plugin.Services.Register(new ProductMetadata("com.meadworks.magpie", "Magpie", new Version("0.1.13"), new Version(0, 1), new Version(0, 1), (MultiplayerPolicy)2, "Hoard")); AllowsUi = MeadworksHeadless.AllowsUi; MagpieText.Initialize(((BaseUnityPlugin)this).Info.Metadata, ((BaseUnityPlugin)this).Logger); settings = new MagpieConfig(((BaseUnityPlugin)this).Config, log); imports = new MagpieImport(((BaseUnityPlugin)this).Config, settings); MagpieRuntime.Initialize(settings, log); MagpiePickup.Register(registration); registration.AddCapability<Func<object, int, int>>("meadworks.loot.multiplier", new Version(1, 0), (Func<object, int, int>)LootPickup.MultiplyExternalCreatureLoot); harmony = new Harmony("com.meadworks.magpie"); harmony.PatchAll(typeof(Plugin).Assembly); Ready = true; log.Info(AllowsUi ? "Magpie registered." : "Magpie registered on a headless process; no messages or effects are created."); } catch (Exception ex) { Shutdown(); log.Error((object)("Magpie initialization rejected: " + ex.GetType().Name + ": " + ex.Message)); } } private void Start() { if (!Ready) { return; } try { MagpieRuntime.DecideStepAside(); } catch (Exception ex) { MeadworksLog? obj = log; if (obj != null) { obj.Error((object)("Magpie step-aside decision failed: " + ex)); } } } private void Update() { if (Ready && settings != null) { MagpieText.Tick(); settings.Tick(Time.unscaledTime); if (AllowsUi) { InitializePickupState(); Toggle(); PickupEngine.FlushMessages(); } MergeEngine.Tick(); FloatLive.Tick(); } } private void Toggle() { //IL_0097: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; ZNet instance = ZNet.instance; long generation = Plugin.Session.Generation; long worldId = Plugin.Session.WorldId; string sessionId = Plugin.Session.SessionId; if (!MagpieRuntime.ModuleOn((MagpieModule)1) || !ToggleOwner(localPlayer, instance, generation, worldId, sessionId) || !MeadworksInput.Allows((InputContext)0) || !ToggleOwner(localPlayer, instance, generation, worldId, sessionId)) { return; } string text = settings.Get<string>(MagpieSettings.PickupTogglePadChord); if (text != padText) { padText = text; padChord = MagpiePadChord.Parse(text); } bool flag = MeadworksInput.Pressed(settings.ToggleKey, (InputContext)0); if (!ToggleOwner(localPlayer, instance, generation, worldId, sessionId)) { return; } bool flag2 = MeadworksInput.Allows((InputContext)0); if (flag2 && ToggleOwner(localPlayer, instance, generation, worldId, sessionId)) { bool flag3 = MagpiePadChord.Pressed((IReadOnlyList<PadButton>)padChord, MeadworksInput.GamepadActive, flag2, (Func<PadButton, bool>)MeadworksInput.PadHeld, (Func<PadButton, bool>)MeadworksInput.PadDown, (Action<PadButton>)MeadworksInput.PadConsume); if ((flag || flag3) && ToggleOwner(localPlayer, instance, generation, worldId, sessionId)) { Player.m_enableAutoPickup = !Player.m_enableAutoPickup; ((Character)localPlayer).Message((MessageType)1, "$hud_autopickup:" + (Player.m_enableAutoPickup ? "$hud_on" : "$hud_off"), 0, (Sprite)null, false); } } } private void InitializePickupState() { Player localPlayer = Player.m_localPlayer; ZNet instance = ZNet.instance; long generation = Plugin.Session.Generation; long worldId = Plugin.Session.WorldId; string sessionId = Plugin.Session.SessionId; if (!ToggleOwner(localPlayer, instance, generation, worldId, sessionId, readyForInput: false)) { ClearStartOwner(); } else if ((startPlayer != localPlayer || startNetwork != instance || startGeneration != generation || startWorld != worldId || !(startSession == sessionId)) && MagpieRuntime.ModuleOn((MagpieModule)1) && settings != null && settings.StartReceiptReady && ToggleOwner(localPlayer, instance, generation, worldId, sessionId)) { bool enableAutoPickup = settings.Get<bool>(MagpieSettings.AutoPickupAtStart); if (ToggleOwner(localPlayer, instance, generation, worldId, sessionId)) { Player.m_enableAutoPickup = enableAutoPickup; startPlayer = localPlayer; startNetwork = instance; startGeneration = generation; startWorld = worldId; startSession = sessionId; } } } private void ClearStartOwner() { startPlayer = null; startNetwork = null; startGeneration = -1L; startWorld = 0L; startSession = null; } internal static bool ToggleOwner(Player player, ZNet network, long generation, long world, string session, bool readyForInput = true) { if ((Object)(object)player != (Object)null && player == Player.m_localPlayer && (Object)(object)network != (Object)null && network == ZNet.instance && Plugin.IsReady && Plugin.Session.AuthorityReady && ZNet.m_world != null && world != 0L && network.GetWorldUID() == world && Plugin.Session.WorldId == world && Plugin.Session.Generation == generation && Plugin.Session.SessionId == session && (Object)(object)((Character)player).m_nview != (Object)null && ((Character)player).m_nview.IsValid() && ((Character)player).m_nview.IsOwner()) { if (readyForInput) { if (!((Character)player).IsDead()) { return !((Character)player).IsTeleporting(); } return false; } return true; } return false; } private void OnDestroy() { Shutdown(); } private void Shutdown() { Ready = false; imports?.Dispose(); imports = null; padText = null; padChord = Array.Empty<PadButton>(); ClearStartOwner(); settings?.Shutdown(); settings = null; Harmony? obj = harmony; if (obj != null) { obj.UnpatchSelf(); } harmony = null; MagpiePickup.Reset(); PickupEngine.Reset(); MergeEngine.Reset(); MagpieRuntime.Shutdown(); MagpieText.Shutdown(); ProductRegistration? obj2 = registration; if (obj2 != null) { obj2.Dispose(); } registration = null; } } } namespace ExtraPotions.Magpie.Runtime.Settings { internal sealed class MagpieConfig { private readonly Dictionary<SettingSpec, ConfigEntryBase> entries = new Dictionary<SettingSpec, ConfigEntryBase>(); private readonly Dictionary<SettingSpec, (string Text, NameList List)> lists = new Dictionary<SettingSpec, (string, NameList)>(); internal const string LockedValuesKey = "Locked Player Values"; private readonly ConfigFile file; private readonly MeadworksLog log; private readonly ConfigEntry<string> lockedValues; private readonly MagpieSyncReceipt receipt = new MagpieSyncReceipt(); private readonly HashSet<SettingSpec> trackedLocks = new HashSet<SettingSpec>(); private LockList lockList = LockList.Empty; private IReadOnlyDictionary<string, string> values = new Dictionary<string, string>(); private string? lockText; private string? valuesText; private float nextRefresh; private bool stopped; public KeyboardShortcut ToggleKey { get { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0032: 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) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) PlayerLock val = Lock(MagpieSettings.PickupToggleKey); if (((PlayerLock)(ref val)).Locked) { try { return KeyboardShortcut.Deserialize(((PlayerLock)(ref val)).ServerText); } catch { } } return Entry<KeyboardShortcut>(MagpieSettings.PickupToggleKey).Value; } } internal bool StartReceiptReady { get { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Invalid comparison between Unknown and I4 if (Plugin.IsReady) { if ((int)Plugin.Session.Role == 3) { return ServerBinds(); } return true; } return false; } } public MagpieConfig(ConfigFile file, MeadworksLog log) { //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) this.file = file; this.log = log; try { foreach (SettingSpec item in MagpieSettings.All) { entries.Add(item, Bind(file, item)); } foreach (KeyValuePair<SettingSpec, ConfigEntryBase> entry in entries) { if ((int)entry.Key.Scope != 0) { MeadworksBlockedOptions.Track("com.meadworks.magpie", entry.Value, Map(entry.Key.Scope), true); } } lockedValues = MeadworksConfig.BindHidden<string>(file, MagpieSettings.LockedPlayerSettings.Section, "Locked Player Values", "", "Managed by Magpie: the server's serialized values for Locked Player Settings.", (ConfigScope)1, (AcceptableValueBase)null); SynchronizationManager.OnConfigurationSynchronized += OnSynchronized; Refresh(force: true); } catch { Shutdown(); throw; } } internal ConfigEntryBase Entry(SettingSpec spec) { return entries[spec]; } public ConfigEntry<T> Entry<T>(SettingSpec spec) { return (ConfigEntry<T>)(object)entries[spec]; } public T Get<T>(SettingSpec spec) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_005d: 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_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) ConfigEntry<T> val = Entry<T>(spec); if ((int)spec.Scope == 0) { PlayerLock val2 = Lock(spec); if (((PlayerLock)(ref val2)).Locked) { try { return spec.Clamp<T>((T)TomlTypeConverter.ConvertToValue(((PlayerLock)(ref val2)).ServerText, typeof(T))); } catch { } } return spec.Clamp<T>(val.Value); } return spec.Clamp<T>(MeadworksConfig.Effective<T>(val, "com.meadworks.magpie", Map(spec.Scope), (T)spec.Default, true)); } public bool Flag(SettingSpec spec) { return Get<bool>(spec); } public NameList List(SettingSpec spec) { string text = Get<string>(spec) ?? ""; if (lists.TryGetValue(spec, out (string, NameList) value) && value.Item1 == text) { return value.Item2; } NameList val = NameList.Parse(text); lists[spec] = (text, val); return val; } public int LootMultiplier() { ConfigEntry<int> val = Entry<int>(MagpieSettings.LootMultiplier); if (MagpieSettings.LootMultiplier.Clamp<int>(val.Value) <= 1) { return 1; } bool flag = MeadworksBlockedOptions.TryUse("com.meadworks.magpie", ((ConfigEntryBase)val).Definition.Key, (ConfigScope)2, MagpieText.Text("loot_multiplier"), true); return LootPolicy.EffectiveMultiplier(Get<int>(MagpieSettings.LootMultiplier), flag); } public static ConfigScope Map(SettingScope scope) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected I4, but got Unknown //IL_001f: Unknown result type (might be due to invalid IL or missing references) return (ConfigScope)((int)scope switch { 0 => 0, 1 => 1, 2 => 2, _ => throw new ArgumentOutOfRangeException("scope", scope, "Unknown setting scope."), }); } private ConfigEntryBase Bind(ConfigFile file, SettingSpec spec) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Expected O, but got Unknown //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) if (spec.Shortcut) { return (ConfigEntryBase)(object)MeadworksConfig.Bind<KeyboardShortcut>(file, spec.Section, spec.Key, KeyboardShortcut.Deserialize((string)spec.Default), new ConfigDescription(spec.Description, (AcceptableValueBase)null, Dependencies(spec)), Map(spec.Scope)); } object obj = spec.Default; if (!(obj is bool value)) { if (!(obj is int value2)) { if (!(obj is float value3)) { if (!(obj is string value4)) { if (obj is LootSources value5) { return (ConfigEntryBase)(object)Bind<LootSources>(file, spec, value5, null); } throw new NotSupportedException(spec.Id + ": unsupported value type " + spec.ValueType.Name + "."); } return (ConfigEntryBase)(object)Bind(file, spec, value4, null); } return (ConfigEntryBase)(object)Bind(file, spec, value3, (AcceptableValueBase?)(object)new AcceptableValueRange<float>((float)spec.Minimum.Value, (float)spec.Maximum.Value)); } return (ConfigEntryBase)(object)Bind(file, spec, value2, (AcceptableValueBase?)(object)new AcceptableValueRange<int>((int)spec.Minimum.Value, (int)spec.Maximum.Value)); } return (ConfigEntryBase)(object)Bind(file, spec, value, null); } private ConfigEntry<T> Bind<T>(ConfigFile file, SettingSpec spec, T value, AcceptableValueBase? acceptable) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 //IL_0026: 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) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown if ((int)spec.Scope != 2) { return MeadworksConfig.Bind<T>(file, spec.Section, spec.Key, value, new ConfigDescription(spec.Description, acceptable, Dependencies(spec)), Map(spec.Scope)); } return MeadworksConfig.BindCheat<T>(file, spec.Section, spec.Key, value, spec.Description, acceptable); } private object[] Dependencies(SettingSpec spec) { //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Expected O, but got Unknown SettingSpec parent = null; bool grouped = spec == MagpieSettings.GroupPickupMessages; if (grouped || spec == MagpieSettings.AutoPickupAtStart || spec == MagpieSettings.PickupRange || spec == MagpieSettings.MagnetSpeed || spec == MagpieSettings.ScanInterval || spec == MagpieSettings.MaxPickupsPerScan || spec == MagpieSettings.IgnoreItems || spec == MagpieSettings.MaxPickupRange || spec == MagpieSettings.ShowPickupMessages || spec == MagpieSettings.SkipPickupEffects || spec == MagpieSettings.PickupToggleKey || spec == MagpieSettings.PickupTogglePadChord) { parent = MagpieSettings.PickupEnabled; } else if (spec == MagpieSettings.MergeRadius || spec == MagpieSettings.MergeMinCount || spec == MagpieSettings.MaxMergesPerScan || spec == MagpieSettings.MergeScanInterval || spec == MagpieSettings.MergeSettleTime || spec == MagpieSettings.MergeIgnoreItems) { parent = MagpieSettings.MergeEnabled; } else if (spec == MagpieSettings.ExcludedItems) { parent = MagpieSettings.FloatEnabled; } if (parent == null) { return Array.Empty<object>(); } return new object[1] { (object)new ConfigDependency((Func<bool>)(() => Flag(MagpieSettings.Enabled) && Flag(parent) && (!grouped || Flag(MagpieSettings.ShowPickupMessages))), (Func<string>)(() => "Requires " + MagpieSettings.Enabled.Section + ": Enabled and " + parent.Key + (grouped ? (" and " + MagpieSettings.ShowPickupMessages.Key) : "") + "."), (Func<bool>)null) }; } internal bool IsLocked(SettingSpec spec) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) if (spec.Lockable) { PlayerLock val = Lock(spec); return ((PlayerLock)(ref val)).Locked; } return false; } private PlayerLock Lock(SettingSpec spec) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) return PlayerLocks.Evaluate(spec.Id, ServerBinds(), lockList, values); } private bool ServerBinds() { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Invalid comparison between Unknown and I4 //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Invalid comparison between Unknown and I4 if (Plugin.IsReady && (int)Plugin.Session.Role == 3 && receipt.CurrentGeneration == Plugin.Session.Generation) { return (int)MeadworksAuthority.Decide("com.meadworks.magpie", (ConfigScope)1, true).Source == 1; } return false; } internal void Tick(float now) { if (!stopped && !(now < nextRefresh)) { nextRefresh = now + 0.5f; Refresh(force: false); } } private void Refresh(bool force) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Invalid comparison between Unknown and I4 if (stopped) { return; } try { if (Plugin.IsReady && (int)Plugin.Session.Role != 3) { string text = LockedValues.Compose(LockList.Parse(Entry<string>(MagpieSettings.LockedPlayerSettings).Value), (Func<string, string>)delegate(string id) { SettingSpec val = MagpieSettings.Find(id); return (val == null) ? null : Entry(val).GetSerializedValue(); }); if (text != lockedValues.Value) { MeadworksConfig.Amend<string>(lockedValues, "com.meadworks.magpie", (ConfigScope)1, text, (string)null, true); } } string text2 = Get<string>(MagpieSettings.LockedPlayerSettings); if (force || text2 != lockText) { lockText = text2; lockList = LockList.Parse(text2); if (lockList.Unknown.Count != 0 || lockList.NotLockable.Count != 0) { log.Warning("Locked Player Settings: " + lockList.Describe()); } } string text3 = MeadworksConfig.Effective<string>(lockedValues, "com.meadworks.magpie", (ConfigScope)1, "", true); if (force || text3 != valuesText) { valuesText = text3; values = LockedValues.Decode(text3); } foreach (SettingSpec item in MagpieSettings.All.Where((SettingSpec setting) => setting.Lockable)) { if (IsLocked(item)) { if (trackedLocks.Add(item)) { MeadworksBlockedOptions.Track("com.meadworks.magpie", Entry(item), (ConfigScope)1, true); } } else if (trackedLocks.Remove(item)) { MeadworksBlockedOptions.Untrack(Entry(item)); } } } catch (Exception ex) { log.Warning("Configuration refresh failed: " + ex.GetType().Name + ": " + ex.Message); } } private void OnSynchronized(object sender, ConfigurationSynchronizationEventArgs args) { if (!stopped && args.UpdatedPluginGUIDs != null && args.UpdatedPluginGUIDs.Contains("com.meadworks.magpie") && Plugin.IsReady) { receipt.Observe(); Refresh(force: true); } } internal void Shutdown() { if (stopped) { return; } stopped = true; SynchronizationManager.OnConfigurationSynchronized -= OnSynchronized; foreach (ConfigEntryBase value in entries.Values) { MeadworksBlockedOptions.Untrack(value); } trackedLocks.Clear(); lockList = LockList.Empty; values = new Dictionary<string, string>(); } } internal sealed class MagpieSyncReceipt { private ZNet network; private ZRpc server; private string epoch = ""; private long world; internal long CurrentGeneration { get { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Invalid comparison between Unknown and I4 if (!Plugin.IsReady || (Object)(object)network == (Object)null || server == null || !server.IsConnected() || network != ZNet.instance || network.IsServer() || server != network.GetServerRPC()) { return -1L; } SessionSnapshot session = Plugin.Session; if ((int)session.Role != 3 || !session.AuthorityReady || session.WorldId == 0L || string.IsNullOrEmpty(session.SessionId)) { return -1L; } if (epoch.Length == 0) { epoch = session.SessionId; world = session.WorldId; } if (!(epoch == session.SessionId) || world != session.WorldId) { return -1L; } return session.Generation; } } internal void Observe() { //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Invalid comparison between Unknown and I4 network = ZNet.instance; server = (((Object)(object)network != (Object)null && !network.IsServer()) ? network.GetServerRPC() : null); epoch = ""; world = 0L; if (server != null && server.IsConnected() && Plugin.IsReady) { SessionSnapshot session = Plugin.Session; if ((int)session.Role == 3 && !string.IsNullOrEmpty(session.SessionId)) { epoch = session.SessionId; world = session.WorldId; } } } } } namespace ExtraPotions.Magpie.Runtime.Pickup { internal static class MagpiePickup { private sealed class Owner { internal Player Player; internal ZNet Network; internal long Generation; internal long World; internal string Session = ""; internal bool Current() { if (thread != 0 && thread == Thread.CurrentThread.ManagedThreadId && Plugin.AllowsUi && MagpieRuntime.ModuleOn((MagpieModule)1) && Player.m_enableAutoPickup) { return Plugin.ToggleOwner(Player, Network, Generation, World, Session); } return false; } } internal const string Capability = "meadworks.pickup"; private static int thread; internal static void Register(ProductRegistration registration) { thread = Thread.CurrentThread.ManagedThreadId; registration.AddCapability<Tuple<Func<object, float>, Func<object, object, bool>, Func<object, object, bool>>>("meadworks.pickup", new Version(1, 0), Tuple.Create<Func<object, float>, Func<object, object, bool>, Func<object, object, bool>>(Radius, Wanted, TryTake)); } internal static void Reset() { thread = 0; } private static Owner? Capture(object opaque) { if (thread != 0 && thread == Thread.CurrentThread.ManagedThreadId) { Player val = (Player)((opaque is Player) ? opaque : null); if (val != null && Plugin.IsReady) { SessionSnapshot session = Plugin.Session; Owner owner = new Owner { Player = val, Network = ZNet.instance, Generation = session.Generation, World = session.WorldId, Session = session.SessionId }; if (!owner.Current()) { return null; } return owner; } } return null; } private static float Radius(object opaque) { try { Owner owner = Capture(opaque); if (owner == null) { return 0f; } float num = PickupEngine.Radius(owner.Player); return (owner.Current() && !float.IsNaN(num) && !float.IsInfinity(num) && num > 0f) ? num : 0f; } catch { return 0f; } } private static bool Target(ItemDrop item) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)item == (Object)null || (Object)(object)item.m_nview == (Object)null || !item.m_nview.IsValid() || ZDOMan.instance == null) { return false; } ZDO zDO = item.m_nview.GetZDO(); if (zDO != null && zDO.IsValid()) { return ZDOMan.instance.GetZDO(zDO.m_uid) == zDO; } return false; } private static bool Wanted(object opaque, object target) { try { Owner owner = Capture(opaque); int result; if (owner != null) { ItemDrop val = (ItemDrop)((target is ItemDrop) ? target : null); if (val != null && Target(val) && PickupEngine.Wanted(owner.Player, val) && owner.Current()) { result = (Target(val) ? 1 : 0); goto IL_003b; } } result = 0; goto IL_003b; IL_003b: return (byte)result != 0; } catch { return false; } } private static bool TryTake(object opaque, object target) { try { Owner owner = Capture(opaque); int result; if (owner != null) { ItemDrop item = (ItemDrop)((target is ItemDrop) ? target : null); if (item != null && Target(item)) { result = (PickupEngine.TryTakeExternal(owner.Player, item, () => owner.Current() && Target(item)) ? 1 : 0); goto IL_0060; } } result = 0; goto IL_0060; IL_0060: return (byte)result != 0; } catch { return false; } } } internal static class PickupEngine { private struct Tracked { public ItemDrop Item; public FloatingTerrainDummy? Dummy; } private const float FallbackBaseRange = 2f; private static readonly Collider[] Hits = (Collider[])(object)new Collider[128]; private static readonly List<Tracked> Items = new List<Tracked>(); private static readonly HashSet<ItemDrop> Seen = new HashSet<ItemDrop>(); private static readonly HashSet<ItemDrop> PulledNow = new HashSet<ItemDrop>(); private static readonly PickupScheduler Scheduler = new PickupScheduler(); private static readonly PickupMessageGroup<ItemData> Messages = new PickupMessageGroup<ItemData>(); private static readonly EffectList NoEffects = new EffectList(); private static float baseRange = -1f; private static bool taking; private static bool flushing; public static bool Replaces(Player player) { if ((Object)(object)player != (Object)null && (Object)(object)player == (Object)(object)Player.m_localPlayer) { return MagpieRuntime.ModuleOn((MagpieModule)1); } return false; } public static bool IsPulled(ItemDrop item) { return PulledNow.Contains(item); } public static void Step(Player player, float dt) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) PulledNow.Clear(); MagpieConfig settings = MagpieRuntime.Settings; if (!Player.m_enableAutoPickup || ((Character)player).IsTeleporting() || ((Character)player).IsDead()) { Items.Clear(); return; } Vector3 center = ((Component)player).transform.position + Vector3.up; float radius = Radius(player); if (Scheduler.ScanDue((double)Time.time, settings.Get<float>(MagpieSettings.ScanInterval))) { Collect(player, center, radius, settings.List(MagpieSettings.IgnoreItems)); Scheduler.BeginScan(settings.Get<int>(MagpieSettings.MaxPickupsPerScan)); } float speed = settings.Get<float>(MagpieSettings.MagnetSpeed); for (int num = Items.Count - 1; num >= 0; num--) { Tracked tracked = Items[num]; if ((Object)(object)tracked.Item == (Object)null || !Advance(player, tracked, center, radius, speed, dt, settings)) { Items.RemoveAt(num); } } } public static void FlushMessages() { Player localPlayer = Player.m_localPlayer; IReadOnlyList<PickupLine<ItemData>> readOnlyList = Messages.Flush((double)Time.time); if ((Object)(object)localPlayer == (Object)null || readOnlyList.Count == 0) { return; } flushing = true; try { foreach (PickupLine<ItemData> item in readOnlyList) { ((Character)localPlayer).ShowPickupMessage(item.Payload, item.Amount); } } finally { flushing = false; } } public static void Reset() { Items.Clear(); PulledNow.Clear(); Messages.Clear(); Scheduler.Reset(); } public static bool AllowMessage(ItemData item, int amount) { if (!taking || flushing || item == null) { return true; } MagpieConfig settings = MagpieRuntime.Settings; if (settings == null) { return true; } if (!settings.Flag(MagpieSettings.ShowPickupMessages)) { return false; } if (!settings.Flag(MagpieSettings.GroupPickupMessages)) { return true; } Messages.Add(item.m_shared.m_name, amount, (double)Time.time, item, true); return false; } private static void Collect(Player player, Vector3 center, float radius, NameList ignore) { //IL_0014: 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_0059: Unknown result type (might be due to invalid IL or missing references) Items.Clear(); Seen.Clear(); int num = Physics.OverlapSphereNonAlloc(center, radius, Hits, player.m_autoPickupMask); for (int i = 0; i < num; i++) { FloatingTerrainDummy dummy; ItemDrop val = ItemOf(Hits[i], out dummy); if (!((Object)(object)val == (Object)null) && Seen.Add(val) && (int)PickupRules.Decide(Facts(player, val, ignore, fits: true)) != 0) { Items.Add(new Tracked { Item = val, Dummy = dummy }); } } } private static bool Advance(Player player, Tracked tracked, Vector3 center, float radius, float speed, float dt, MagpieConfig settings) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected I4, but got Unknown //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Invalid comparison between Unknown and I4 //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0100: 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) ItemDrop item = tracked.Item; PickupAction val = PickupRules.Decide(Facts(player, item, settings.List(MagpieSettings.IgnoreItems), fits: true)); switch ((int)val) { case 0: return false; case 1: item.RequestOwn(); return true; case 2: return true; default: { item.Load(); if (!Fits(player, item)) { return true; } MagnetStep val2 = Magnet.Step(Vector3.Distance(((Component)item).transform.position, center), radius, speed, dt); MagnetAction action = ((MagnetStep)(ref val2)).Action; if ((int)action != 0) { if ((int)action == 2) { if (!Scheduler.TryTake(Time.frameCount)) { return true; } Take(player, item, settings); return false; } Vector3 val3 = center - ((Component)item).transform.position; Vector3 val4 = ((Vector3)(ref val3)).normalized * ((MagnetStep)(ref val2)).Move; Transform transform = ((Component)item).transform; transform.position += val4; if ((Object)(object)tracked.Dummy != (Object)null) { Transform transform2 = ((Component)tracked.Dummy).transform; transform2.position += val4; } PulledNow.Add(item); return true; } return false; } } } internal static float Radius(Player player) { MagpieConfig settings = MagpieRuntime.Settings; return PickupRadius.Effective(settings.Get<float>(MagpieSettings.PickupRange), settings.Get<float>(MagpieSettings.MaxPickupRange), player.m_autoPickupRange, BaseRange()); } internal static bool Wanted(Player player, ItemDrop item) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Invalid comparison between Unknown and I4 if ((Object)(object)item != (Object)null && item.m_itemData != null) { return (int)PickupRules.Decide(Facts(player, item, MagpieRuntime.Settings.List(MagpieSettings.IgnoreItems), fits: true)) > 0; } return false; } internal static bool TryTakeExternal(Player player, ItemDrop item, Func<bool> current) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Invalid comparison between Unknown and I4 if (taking || !current() || (Object)(object)item == (Object)null || item.m_itemData == null) { return false; } MagpieConfig settings = MagpieRuntime.Settings; if ((int)PickupRules.Decide(Facts(player, item, settings.List(MagpieSettings.IgnoreItems), fits: true)) != 3) { return false; } item.Load(); if (!Fits(player, item) || !current() || !Scheduler.TryTake(Time.frameCount)) { return false; } return Take(player, item, settings); } private static bool Take(Player player, ItemDrop item, MagpieConfig settings) { //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0091: 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) ZNetView nview = item.m_nview; if (!PickupRules.CanTakeNow((Object)(object)nview != (Object)null && nview.IsValid(), item.CanPickup(true))) { return false; } bool flag = settings.Flag(MagpieSettings.SkipPickupEffects) || !Plugin.AllowsUi; EffectList pickupEffects = ((Humanoid)player).m_pickupEffects; if (flag) { ((Humanoid)player).m_pickupEffects = NoEffects; } taking = true; bool flag2; try { flag2 = ((Humanoid)player).Pickup(((Component)item).gameObject, true, true); if (flag2 && !flag) { player.m_autopickupEffects.Create(((Component)player).transform.position, Quaternion.identity, (Transform)null, 1f, -1, default(ZDOID)); } } finally { taking = false; ((Humanoid)player).m_pickupEffects = pickupEffects; } MeadworksLog? log = MagpieRuntime.Log; if (log != null) { log.Debug("Took " + item.m_itemData?.m_shared?.m_name + "."); } return flag2; } private static PickupFacts Facts(Player player, ItemDrop item, NameList ignore, bool fits) { //IL_0073: Unknown result type (might be due to invalid IL or missing references) ZNetView nview = item.m_nview; bool flag = (Object)(object)nview != (Object)null && nview.IsValid(); return new PickupFacts(item.m_autoPickup, item.IsPiece(), flag, item.m_itemData != null && ((Humanoid)player).HaveUniqueKey(item.m_itemData.m_shared.m_name), ignore.Contains(Utils.GetPrefabName(((Component)item).gameObject)), flag && item.CanPickup(true), flag && item.InTar(), fits); } private static bool Fits(Player player, ItemDrop item) { Inventory inventory = ((Humanoid)player).GetInventory(); if (inventory.CanAddItem(item.m_itemData, -1)) { return item.m_itemData.GetWeight(-1) + inventory.GetTotalWeight() <= player.GetMaxCarryWeight(); } return false; } private static ItemDrop? ItemOf(Collider collider, out FloatingTerrainDummy? dummy) { dummy = null; Rigidbody val = (((Object)(object)collider != (Object)null) ? collider.attachedRigidbody : null); if ((Object)(object)val == (Object)null) { return null; } ItemDrop component = ((Component)val).GetComponent<ItemDrop>(); if ((Object)(object)component != (Object)null) { return component; } dummy = ((Component)val).GetComponent<FloatingTerrainDummy>(); if (!((Object)(object)dummy != (Object)null) || !((Object)(object)dummy.m_parent != (Object)null)) { return null; } return ((Component)dummy.m_parent).GetComponent<ItemDrop>(); } private static float BaseRange() { if (baseRange > 0f) { return baseRange; } GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab("Player") : null); Player val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<Player>() : null); if ((Object)(object)val2 == (Object)null) { return 2f; } baseRange = ((val2.m_autoPickupRange > 0f) ? val2.m_autoPickupRange : 2f); return baseRange; } } [HarmonyPatch(typeof(Player), "AutoPickup")] internal static class AutoPickupPatch { [HarmonyPrefix] private static bool Prefix(Player __instance, float dt) { if (!PickupEngine.Replaces(__instance)) { return true; } PickupEngine.Step(__instance, dt); return false; } } [HarmonyPatch(typeof(Character), "ShowPickupMessage")] internal static class PickupMessagePatch { [HarmonyPrefix] private static bool Prefix(ItemData item, int amount) { return PickupEngine.AllowMessage(item, amount); } } } namespace ExtraPotions.Magpie.Runtime.Merge { internal static class MergeEngine { private static readonly List<ItemDrop> Owned = new List<ItemDrop>(); private static readonly List<GroundItem> Ground = new List<GroundItem>(); private static double nextScan; private static long nonceCounter; public static int Merged { get; private set; } public static int RolledBack { get; private set; } public static void Tick() { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Expected O, but got Unknown if ((Object)(object)ZNet.instance == (Object)null || (Object)(object)ZNetScene.instance == (Object)null || !MagpieRuntime.ModuleOn((MagpieModule)2) || !MergeBarrier.MayMerge(Role())) { return; } MagpieConfig settings = MagpieRuntime.Settings; double num = Time.time; if (num < nextScan) { return; } nextScan = num + (double)settings.Get<float>(MagpieSettings.MergeScanInterval); MergeSettings val = new MergeSettings(settings.Get<float>(MagpieSettings.MergeRadius), settings.Get<int>(MagpieSettings.MergeMinCount), settings.Get<int>(MagpieSettings.MaxMergesPerScan), (double)settings.Get<float>(MagpieSettings.MergeSettleTime), settings.List(MagpieSettings.MergeIgnoreItems).Names); double timeSeconds = ZNet.instance.GetTimeSeconds(); Collect(); Reconcile(timeSeconds, val); foreach (MergePlan item in MergePlanner.Plan((IReadOnlyList<GroundItem>)Ground, val, timeSeconds, (Func<string>)NewNonce)) { Execute(item); } } public static void Reset() { Owned.Clear(); Ground.Clear(); nextScan = 0.0; } private static MergeRole Role() { ZNet instance = ZNet.instance; if (!((Object)(object)instance == (Object)null)) { if (instance.IsServer()) { if (instance.GetPeers().Count == 0 && !instance.IsDedicated()) { return (MergeRole)1; } return (MergeRole)2; } return (MergeRole)3; } return (MergeRole)0; } private static string NewNonce() { long num = ++nonceCounter; return num.ToString(CultureInfo.InvariantCulture) + "-" + Guid.NewGuid().ToString("N").Substring(0, 12); } private static void Collect() { Owned.Clear(); Ground.Clear(); foreach (ItemDrop s_instance in ItemDrop.s_instances) { if (!((Object)(object)s_instance == (Object)null) && s_instance.m_itemData != null) { ZNetView nview = s_instance.m_nview; if (!((Object)(object)nview == (Object)null) && nview.IsValid() && nview.IsOwner()) { Ground.Add(ToGround(Owned.Count, s_instance, nview.GetZDO())); Owned.Add(s_instance); } } } } private static GroundItem ToGround(int id, ItemDrop item, ZDO zdo) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0083: 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) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Expected O, but got Unknown ItemData itemData = item.m_itemData; Vector3 position = ((Component)item).transform.position; Rigidbody body = item.m_body; int num; if ((Object)(object)body != (Object)null && !body.isKinematic) { Vector3 linearVelocity = body.linearVelocity; num = ((((Vector3)(ref linearVelocity)).sqrMagnitude > 0.01f) ? 1 : 0); } else { num = 0; } bool flag = (byte)num != 0; string text = zdo.GetString("magpie.id", ""); return new GroundItem((long)id, Identity(item, itemData), itemData.m_stack, itemData.m_shared.m_maxStackSize, new Point3(position.x, position.y, position.z), (long)((ZDOID)(ref zdo.m_uid)).ID, (double)SpawnTicks(zdo), true, true, flag, item.GetTimeSinceSpawned(), PickupEngine.IsPulled(item), zdo.GetString("magpie.merge_in", "").Length > 0, (text.Length > 0) ? text : null); } private static ItemIdentity Identity(ItemDrop item, ItemData data) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Expected O, but got Unknown return new ItemIdentity(Utils.GetPrefabName(((Component)item).gameObject), data.m_quality, data.m_variant, data.m_crafterID, data.m_crafterName ?? "", data.m_durability, data.m_worldLevel, (IReadOnlyDictionary<string, string>)data.m_customData, CheatedMarks.IsCheated(item, data), LootMarks.IsMarked(item)); } private static long SpawnTicks(ZDO zdo) { return zdo.GetLong(ZDOVars.s_spawnTime, 0L); } private static void Execute(MergePlan plan) { //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) ItemDrop val = Owned[(int)plan.Target.Id]; ItemDrop val2 = Owned[(int)plan.Source.Id]; if (!StillOwned(val) || !StillOwned(val2)) { return; } ZDO zDO = val.m_nview.GetZDO(); ZDO zDO2 = val2.m_nview.GetZDO(); InstanceIdentityResult val3 = MeadworksInstanceIdentity.Ensure(zDO2, "magpie.id", false); if (!val3.HasToken) { return; } InstanceToken value = val3.Token.Value; MergeIntent val4 = plan.Intent(((InstanceToken)(ref value)).Value); val.m_itemData.m_stack = plan.TargetStackAfter; zDO.Set("magpie.merge_in", val4.Format()); long num = Math.Max(SpawnTicks(zDO), SpawnTicks(zDO2)); if (num > 0) { zDO.Set(ZDOVars.s_spawnTime, num); } val.Save(); if (!StillOwned(val2) || !StillOwned(val)) { MeadworksLog? log = MagpieRuntime.Log; if (log != null) { log.Verbose("Merge phase 2 deferred: ownership changed; reconcile decides."); } return; } string text = val4.WithPhase2Saved().Format(); if (plan.DestroysSource) { zDO.Set("magpie.merge_in", text); val.Save(); ZNetScene.instance.Destroy(((Component)val2).gameObject); } else { val2.m_itemData.m_stack = plan.SourceStackAfter; zDO2.Set("magpie.merge_out", MergeIntent.AppendNonce(zDO2.GetString("magpie.merge_out", ""), plan.Nonce)); zDO.Set("magpie.merge_in", text); val2.Save(); val.Save(); } Merged++; MeadworksLog? log2 = MagpieRuntime.Log; if (log2 != null) { log2.Debug($"Merged {plan.Moved} {plan.Target.Identity.Prefab} into one stack ({plan.TargetStackAfter})."); } } private static void Reconcile(double world, MergeSettings settings) { //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0088: 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_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Expected I4, but got Unknown //IL_0140: Unknown result type (might be due to invalid IL or missing references) float radius = MergeReconcile.SearchRadius(settings); foreach (ItemDrop item in Owned) { if (!StillOwned(item)) { continue; } ZDO zDO = item.m_nview.GetZDO(); string text = zDO.GetString("magpie.merge_in", ""); if (text.Length == 0) { continue; } MergeIntent val = MergeIntent.Parse(text); if (val == null) { zDO.Set("magpie.merge_in", ""); continue; } Vector3 position = ((Component)item).transform.position; bool flag = ZNetScene.instance.IsAreaReady(position); ReconcileAction val2 = MergeReconcile.Decide(val, world, flag, Observe(val, position, radius)); switch (val2 - 1) { case 0: { item.m_itemData.m_stack = MergeReconcile.StackAfterRollback(item.m_itemData.m_stack, val.Moved, val.PreMergeStack); zDO.Set("magpie.merge_in", ""); item.Save(); RolledBack++; MeadworksLog? log2 = MagpieRuntime.Log; if (log2 != null) { log2.Verbose($"Merge rolled back: {val.Moved} removed from the target (nonce {val.Nonce})."); } break; } case 2: { ItemDrop val3 = FindSource(val, position, radius); if ((Object)(object)val3 != (Object)null && StillOwned(val3)) { ZNetScene.instance.Destroy(((Component)val3).gameObject); MeadworksLog? log = MagpieRuntime.Log; if (log != null) { log.Verbose("Merge removal finished for nonce " + val.Nonce + "."); } } break; } case 1: zDO.Set("magpie.merge_in", ""); break; } } } private static SourceObservation Observe(MergeIntent intent, Vector3 near, float radius) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) ItemDrop val = FindSource(intent, near, radius); if (!((Object)(object)val == (Object)null)) { if (MergeIntent.HasNonce(val.m_nview.GetZDO().GetString("magpie.merge_out", ""), intent.Nonce)) { return (SourceObservation)2; } return (SourceObservation)1; } return (SourceObservation)0; } private static ItemDrop FindSource(MergeIntent intent, Vector3 near, float radius) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: 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_004e: Unknown result type (might be due to invalid IL or missing references) float num = radius * radius; foreach (ItemDrop s_instance in ItemDrop.s_instances) { ZNetView val = (((Object)(object)s_instance != (Object)null) ? s_instance.m_nview : null); if (!((Object)(object)val == (Object)null) && val.IsValid()) { Vector3 val2 = ((Component)s_instance).transform.position - near; if (!(((Vector3)(ref val2)).sqrMagnitude > num) && !(val.GetZDO().GetString("magpie.id", "") != intent.SourceToken)) { return s_instance; } } } return null; } private static bool StillOwned(ItemDrop item) { if ((Object)(object)item != (Object)null && (Object)(object)item.m_nview != (Object)null && item.m_nview.IsValid()) { return item.m_nview.IsOwner(); } return false; } } } namespace ExtraPotions.Magpie.Runtime.Loot { internal static class CheatedMarks { public static bool Active() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) MagpieModule val = (MagpieModule)0; MagpieModule[] modules = ModuleStepAside.Modules; foreach (MagpieModule val2 in modules) { if ((0xB & val2) != 0 && MagpieRuntime.ModuleOn(val2)) { val |= val2; } } return CheatedMark.Active(val); } public static int Stored(ZDO? zdo) { if (zdo == null) { return 0; } return zdo.GetInt("magpie.cheated", 0); } public static bool IsCheated(ItemDrop item, ItemData data) { ZNetView val = (((Object)(object)item != (Object)null) ? item.m_nview : null); ZDO zdo = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null); return CheatedMark.OnLoad(Active(), data.m_cheated, Stored(zdo)); } public static void AfterSave(ItemDrop item) { ZNetView nview = item.m_nview; ItemData itemData = item.m_itemData; if (!((Object)(object)nview == (Object)null) && nview.IsValid() && nview.IsOwner() && itemData != null) { ZDO zDO = nview.GetZDO(); int? num = CheatedMark.OnSave(Active(), itemData.m_cheated, Stored(zDO)); if (num.HasValue) { zDO.Set("magpie.cheated", num.Value); } } } public static void AfterLoad(ItemDrop item, ZDO? zdo) { ItemData itemData = item.m_itemData; if (itemData != null && zdo != null) { itemData.m_cheated = CheatedMark.OnLoad(Active(), itemData.m_cheated, Stored(zdo)); } } } [HarmonyPatch(typeof(ItemDrop), "Save")] internal static class CheatedMarkSavePatch { [HarmonyPostfix] private static void Postfix(ItemDrop __instance) { try { CheatedMarks.AfterSave(__instance); } catch (Exception ex) { MeadworksLog? log = MagpieRuntime.Log; if (log != null) { log.Error((object)("Cheated mark save failed: " + ex)); } } } } [HarmonyPatch(typeof(ItemDrop), "Load")] internal static class CheatedMarkLoadPatch { [HarmonyPostfix] private static void Postfix(ItemDrop __instance) { try { ZNetView nview = __instance.m_nview; if ((Object)(object)nview != (Object)null && nview.IsValid()) { CheatedMarks.AfterLoad(__instance, nview.GetZDO()); } } catch (Exception ex) { MeadworksLog? log = MagpieRuntime.Log; if (log != null) { log.Error((object)("Cheated mark load failed: " + ex)); } } } } [HarmonyPatch(typeof(ItemDrop), "LoadFromExternalZDO")] internal static class CheatedMarkExternalLoadPatch { [HarmonyPostfix] private static void Postfix(ItemDrop __instance, ZDO zdo) { try { CheatedMarks.AfterLoad(__instance, zdo); } catch (Exception ex) { MeadworksLog? log = MagpieRuntime.Log; if (log != null) { log.Error((object)("Cheated mark external load failed: " + ex)); } } } } internal static class LootMarks { public static LootSource Current { get; private set; } public static LootSource Open(LootSource source) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) LootSource current = Current; Current = source; return current; } public static void Close(LootSource previous) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) Current = previous; } public unsafe static void MarkIfLoot(ItemDrop item) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Invalid comparison between Unknown and I4 //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) LootSource current = Current; if ((int)current == 0 || !MagpieRuntime.ModuleOn((MagpieModule)8)) { return; } MagpieConfig? settings = MagpieRuntime.Settings; int num = settings.Get<int>(MagpieSettings.LootMultiplier); LootSources val = settings.Get<LootSources>(MagpieSettings.LootSources); float num2 = (((int)current == 1) ? WildbloodMultiplier.Read() : 1f); if (!LootPolicy.ShouldMark(current, val, num, num2)) { return; } ZNetView nview = item.m_nview; if ((Object)(object)nview == (Object)null || !nview.IsValid() || !nview.IsOwner()) { MeadworksLog? log = MagpieRuntime.Log; if (log != null) { log.Debug("Loot mark skipped: no owned ZDO during Awake (" + ((object)(*(LootSource*)(¤t))/*cast due to .constrained prefix*/).ToString() + ")."); } } else { nview.GetZDO().Set("magpie.loot", 1); } } public static bool IsMarked(ItemDrop item) { ZNetView val = (((Object)(object)item != (Object)null) ? item.m_nview : null); if ((Object)(object)val != (Object)null && val.IsValid()) { return val.GetZDO().GetInt("magpie.loot", 0) == 1; } return false; } } [HarmonyPatch(typeof(ItemDrop), "Awake")] internal static class LootMarkAwakePatch { [HarmonyPostfix] private static void Postfix(ItemDrop __instance) { try { LootMarks.MarkIfLoot(__instance); } catch (Exception ex) { MeadworksLog? log = MagpieRuntime.Log; if (log != null) { log.Error((object)("Loot mark failed: " + ex)); } } } } [HarmonyPatch(typeof(CharacterDrop), "DropItems")] internal static class CreatureLootScope { private static void Prefix(out LootSource __state) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Expected I4, but got Unknown __state = (LootSource)(int)LootMarks.Open((LootSource)1); } private static void Finalizer(LootSource __state) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) LootMarks.Close(__state); } } [HarmonyPatch(typeof(DropOnDestroyed), "OnDestroyed")] internal static class DestructibleLootScope { private static void Prefix(out LootSource __state) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Expected I4, but got Unknown __state = (LootSource)(int)LootMarks.Open((LootSource)2); } private static void Finalizer(LootSource __state) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) LootMarks.Close(__state); } } [HarmonyPatch(typeof(MineRock), "RPC_Hit")] internal static class MineRockLootScope { private static void Prefix(out LootSource __state) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Expected I4, but got Unknown __state = (LootSource)(int)LootMarks.Open((LootSource)3); } private static void Finalizer(LootSource __state) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) LootMarks.Close(__state); } } [HarmonyPatch(typeof(MineRock5), "DamageArea")] internal static class MineRock5LootScope { private static void Prefix(out LootSource __state) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Expected I4, but got Unknown __state = (LootSource)(int)LootMarks.Open((LootSource)3); } private static void Finalizer(LootSource __state) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) LootMarks.Close(__state); } } [HarmonyPatch(typeof(TreeBase), "RPC_Damage")] internal static class TreeLootScope { private static void Prefix(out LootSource __state) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Expected I4, but got Unknown __state = (LootSource)(int)LootMarks.Open((LootSource)3); } private static void Finalizer(LootSource __state) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) LootMarks.Close(__state); } } [HarmonyPatch(typeof(TreeLog), "Destroy")] internal static class TreeLogLootScope { private static void Prefix(out LootSource __state) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Expected I4, but got Unknown __state = (LootSource)(int)LootMarks.Open((LootSource)3); } private static void Finalizer(LootSource __state) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) LootMarks.Close(__state); } } [HarmonyPatch(typeof(Pickable), "Drop")] internal static class PickableLootScope { private static void Prefix(out LootSource __state) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Expected I4, but got Unknown __state = (LootSource)(int)LootMarks.Open((LootSource)4); } private static void Finalizer(LootSource __state) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) LootMarks.Close(__state); } } [HarmonyPatch(typeof(LootSpawner), "UpdateSpawner")] internal static class SpawnerLootScope { private static void Prefix(out LootSource __state) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Expected I4, but got Unknown __state = (LootSource)(int)LootMarks.Open((LootSource)5); } private static void Finalizer(LootSource __state) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) LootMarks.Close(__state); } } [HarmonyPatch(typeof(Humanoid), "Pickup")] internal static class LootPickup { internal sealed class Take { public ItemData Data; public int Before; } internal static int MultiplyExternalCreatureLoot(object opaque, int amount) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) if (amount > 0) { ItemData val = (ItemData)((opaque is ItemData) ? opaque : null); if (val != null && val.m_shared != null && MagpieRuntime.ModuleOn((MagpieModule)8)) { MagpieConfig settings = MagpieRuntime.Settings; if (settings == null || (settings.Get<LootSources>(MagpieSettings.LootSources) & 1) == 0) { return amount; } if (val.m_shared.m_maxStackSize <= 1) { return amount; } int num = settings.LootMultiplier(); if (LootPolicy.ShouldMark(LootMarks.Current, settings.Get<LootSources>(MagpieSettings.LootSources), num, 1f)) { return amount; } long num2 = amount; LootExtra val2 = LootPolicy.Extra(true, val.m_shared.m_maxStackSize, amount, num, true); long num3 = num2 + ((LootExtra)(ref val2)).Amount; if (num3 <= int.MaxValue) { return (int)num3; } return int.MaxValue; } } return amount; } [HarmonyPrefix] private static void Prefix(Humanoid __instance, GameObject go, out Take? __state) { __state = null; try { if ((Object)(object)__instance == (Object)null || (Object)(object)__instance != (Object)(object)Player.m_localPlayer || (Object)(object)go == (Object)null || !MagpieRuntime.ModuleOn((MagpieModule)8)) { return; } ItemDrop component = go.GetComponent<ItemDrop>(); if (!((Object)(object)component == (Object)null) && LootMarks.IsMarked(component)) { component.Load(); ItemData itemData = component.m_itemData; if (itemData != null) { __state = new Take { Data = itemData.Clone(), Before = Count(__instance, itemData) }; } } } catch (Exception ex) { MeadworksLog? log = MagpieRuntime.Log; if (log != null) { log.Error((object)("Loot pickup prefix failed: " + ex)); } } } [HarmonyPostfix] private static void Postfix(Humanoid __instance, bool __result, Take? __state) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: 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) if (!__result || __state == null) { return; } try { MagpieConfig settings = MagpieRuntime.Settings; int num = settings.LootMultiplier(); ItemData data = __state.Data; int maxStackSize = data.m_shared.m_maxStackSize; int num2 = LootPolicy.Picked(__state.Before, Count(__instance, data)); LootExtra extra = LootPolicy.Extra(true, maxStackSize, num2, num, settings.Get<bool>(MagpieSettings.StackableOnly)); if (((LootExtra)(ref extra)).Amount > 0) { Give(__instance, data, extra); MeadworksLog? log = MagpieRuntime.Log; if (log != null) { log.Debug($"Loot multiplier x{num}: {data.m_shared.m_name} +{((LootExtra)(ref extra)).Amount}."); } } } catch (Exception ex) { MeadworksLog? log2 = MagpieRuntime.Log; if (log2 != null) { log2.Error((object)("Loot pickup postfix failed: " + ex)); } } } private static void Give(Humanoid player, ItemData template, LootExtra extra) { //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) Inventory inventory = player.GetInventory(); Transform transform = ((Component)player).transform; foreach (int item in LootPolicy.SplitIntoStacks(((LootExtra)(ref extra)).Amount, template.m_shared.m_maxStackSize)) { ItemData val = template.Clone(); val.m_stack = item; val.m_cheated = LootPolicy.ExtraCheated(template.m_cheated); val.m_equipped = false; int num = LootPolicy.OverflowDrop(inventory.CanAddItem(val, item) && inventory.AddItem(val), val.m_stack); if (num > 0) { ItemDrop.DropItem(val, num, transform.position + transform.forward + Vector3.up, transform.rotation); } } } private static int Count(Humanoid player, ItemData data) { return player.GetInventory().CountItems(data.m_shared.m_name, data.m_quality, true); } } internal static class WildbloodMultiplier { internal const string Capability = "meadworks.loot.creature_multiplier"; private static readonly Version Version = new Version(1, 0); internal static float Read() { try { Func<float> func = default(Func<float>); if (!Plugin.IsReady || !Plugin.Services.Capabilities.TryResolve<Func<float>>("meadworks.loot.creature_multiplier", Version, ref func)) { return 1f; } float num = func(); return (float.IsNaN(num) || float.IsInfinity(num)) ? 1f : num; } catch { return 1f; } } } } namespace ExtraPotions.Magpie.Runtime.Adopted.FloatingItems { internal static class Buoyancy { private const string TemplateItem = "Wood"; private static Floating? _template; private static bool _templateMissingLogged; public static bool TryAdd(GameObject item, bool impactEffects) { if (item.GetComponent<IWaterInteractable>() != null) { return false; } if ((Object)(object)item.GetComponent<Rigidbody>() == (Object)null || (Object)(object)item.GetComponent<ZNetView>() == (Object)null || (Object)(object)item.GetComponentInChildren<Collider>() == (Object)null) { return false; } Floating val = item.AddComponent<Floating>(); FloatLive.Track(val); Floating val2 = Template(); if ((Object)(object)val2 != (Object)null) { CopySettings(val2, val, impactEffects); } return true; } private static void CopySettings(Floating from, Floating to, bool impactEffects) { to.m_waterLevelOffset = from.m_waterLevelOffset; to.m_forceDistance = from.m_forceDistance; to.m_force = from.m_force; to.m_balanceForceFraction = from.m_balanceForceFraction; to.m_damping = from.m_damping; if (impactEffects) { to.m_impactEffects = from.m_impactEffects; } } private static Floating? Template() { if ((Object)(object)_template != (Object)null) { return _template; } ObjectDB instance = ObjectDB.instance; if ((Object)(object)instance == (Object)null) { return null; } _template = FindTemplate(instance); if ((Object)(object)_template != (Object)null) { MeadworksLog? log = MagpieRuntime.Log; if (log != null) { log.Debug($"Float template: {((Object)_template).name} (offset {_template.m_waterLevelOffset}, force {_template.m_force}, " + $"distance {_template.m_forceDistance}, damping {_template.m_damping})."); } } else if (!_templateMissingLogged) { _templateMissingLogged = true; MeadworksLog? log2 = MagpieRuntime.Log; if (log2 != null) { log2.Warning("No game item has a Floating component; floating items use the component defaults."); } } return _template; } private static Floating? FindTemplate(ObjectDB db) { GameObject itemPrefab = db.GetItemPrefab("Wood"); Floating result = default(Floating); if ((Object)(object)itemPrefab != (Object)null && itemPrefab.TryGetComponent<Floating>(ref result)) { return result; } foreach (GameObject item in db.m_items) { if ((Object)(object)item != (Object)null && item.TryGetComponent<Floating>(ref result)) { return result; } } return null; } } [HarmonyPatch(typeof(ItemDrop), "Awake")] internal static class FloatPatch { [HarmonyPrefix] private static void Prefix(ItemDrop __instance) { try { if (MagpieRuntime.ModuleOn((MagpieModule)4)) { GameObject gameObject = ((Component)__instance).gameObject; if (!MagpieRuntime.Settings.List(MagpieSettings.ExcludedItems).Contains(Utils.GetPrefabName(gameObject))) { Buoyancy.TryAdd(gameObject, Plugin.AllowsUi); } } } catch (Exception arg) { MeadworksLog? log = MagpieRuntime.Log; if (log != null) { log.Error((object)$"Could not add floating to '{((Object)__instance).name}': {arg}"); } } } } internal static class FloatLive { [HarmonyPatch(typeof(Floating), "CustomFixedUpdate")] private static class Gate { [HarmonyPrefix] private static bool Prefix(Floating __instance) { if (Added.Contains(__instance)) { return Allows(((Component)__instance).gameObject); } return true; } } private static readonly HashSet<Floating> Added = new HashSet<Floating>(); private static readonly FieldRef<ItemDrop, Floating> CachedFloat = AccessTools.FieldRefAccess<ItemDrop, Floating>("m_floating"); private static readonly List<ItemDrop> Instances = (List<ItemDrop>)AccessTools.Field(typeof(ItemDrop), "s_instances").GetValue(null); private static bool seen; private static bool lastOn; private static string? lastExcluded; internal static void Track(Floating floating) { if ((Object)(object)floating != (Object)null) { Added.Add(floating); } } internal static bool Allows(GameObject item) { if (MagpieRuntime.ModuleOn((MagpieModule)4)) { return !MagpieRuntime.Settings.List(MagpieSettings.ExcludedItems).Contains(Utils.GetPrefabName(item)); } return false; } internal static void Tick() { if (MagpieRuntime.Settings == null) { return; } bool flag = MagpieRuntime.ModuleOn((MagpieModule)4); string text = MagpieRuntime.Settings.Get<string>(MagpieSettings.ExcludedItems); if (seen && flag == lastOn && text == lastExcluded) { return; } bool num = !seen; seen = true; lastOn = flag; lastExcluded = text; Added.RemoveWhere((Floating f) => (Object)(object)f == (Object)null); if (num || !flag || Instances == null) { return; } foreach (ItemDrop instance in Instances) { if (!((Object)(object)instance == (Object)null) && !((Object)(object)CachedFloat.Invoke(instance) != (Object)null) && Allows(((Component)instance).gameObject) && Buoyancy.TryAdd(((Component)instance).gameObject, Plugin.AllowsUi)) { CachedFloat.Invoke(instance) = ((Component)instance).GetComponent<Floating>(); } } } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } }