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 StorageGroups v0.2.3
plugins/StorageGroups.Core.dll
Decompiled a month agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using Microsoft.CodeAnalysis; using SmartContainerRouting.Core.Model; using SmartContainerRouting.Core.Text; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")] [assembly: AssemblyCompany("jonasd")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.2.3.0")] [assembly: AssemblyInformationalVersion("0.2.3+7859651d1575cac84e5a82ef3b2ffa36c1170ea5")] [assembly: AssemblyProduct("Storage Groups")] [assembly: AssemblyTitle("StorageGroups.Core")] [assembly: AssemblyVersion("0.2.3.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] internal sealed class IsReadOnlyAttribute : Attribute { } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace SmartContainerRouting.Core { public static class CoreInfo { public static string Version => "0.2.3"; } } namespace SmartContainerRouting.Core.Versioning { public sealed class PeerVersionTracker { private sealed class Peer { public string Name { get; set; } public float FirstSeen { get; } public float LastSeen { get; set; } public bool Connected { get; set; } public string? Version { get; set; } public bool SentUnannouncedTraffic { get; set; } public float? SpawnedAt { get; set; } public Peer(string name, float firstSeen) { Name = name; FirstSeen = firstSeen; LastSeen = firstSeen; } public bool IsConfirmedOutdated(string serverVersion) { if (Version != null) { if (IsCompatible(Version, serverVersion)) { return IsOlderThan(Version, "0.2.3"); } return true; } return SentUnannouncedTraffic; } } public const float ForgetSeconds = 20f; public const float MissingModGraceSeconds = 30f; public const string MinimumSafeVersion = "0.2.3"; private readonly string _serverVersion; private readonly Dictionary<long, Peer> _peers = new Dictionary<long, Peer>(); public IReadOnlyCollection<long> PeerIds => (IReadOnlyCollection<long>)(object)_peers.Keys.ToArray(); public PeerVersionTracker(string serverVersion) { _serverVersion = serverVersion; } public static bool IsOlderThan(string? version, string minimum) { int[] array = Parse(version); int[] array2 = Parse(minimum); if (array == null || array2 == null) { return true; } for (int i = 0; i < 3; i++) { if (array[i] != array2[i]) { return array[i] < array2[i]; } } return false; static int[]? Parse(string? text) { string[] array3 = ((text != null) ? text.Split('-', '+')[0].Split(new char[1] { '.' }) : null); if (array3 == null || array3.Length < 2) { return null; } int[] array4 = new int[3]; for (int j = 0; j < 3; j++) { if (j < array3.Length && !int.TryParse(array3[j], out array4[j])) { return null; } } return array4; } } public static bool IsCompatible(string? a, string? b) { (int, int)? tuple = MajorMinor(a); if (tuple.HasValue) { (int, int) valueOrDefault = tuple.GetValueOrDefault(); tuple = MajorMinor(b); if (tuple.HasValue) { (int, int) valueOrDefault2 = tuple.GetValueOrDefault(); (int, int) tuple2 = valueOrDefault; (int, int) tuple3 = valueOrDefault2; if (tuple2.Item1 == tuple3.Item1) { return tuple2.Item2 == tuple3.Item2; } return false; } } return false; } public void SyncConnected(IEnumerable<(long PeerId, string Name)> connected, float now) { (long, string)[] array = connected.ToArray(); HashSet<long> hashSet = new HashSet<long>(array.Select(((long PeerId, string Name) peer) => peer.PeerId)); KeyValuePair<long, Peer>[] array2 = _peers.ToArray(); for (int num = 0; num < array2.Length; num++) { KeyValuePair<long, Peer> keyValuePair = array2[num]; keyValuePair.Value.Connected = hashSet.Contains(keyValuePair.Key); if (!keyValuePair.Value.Connected && now - keyValuePair.Value.LastSeen >= 20f) { _peers.Remove(keyValuePair.Key); } } (long, string)[] array3 = array; for (int num = 0; num < array3.Length; num++) { var (key, name) = array3[num]; if (!_peers.TryGetValue(key, out Peer value)) { value = new Peer(name, now); _peers[key] = value; } value.Name = name; value.Connected = true; value.LastSeen = now; } } public void Announced(long peerId, string version, float now) { if (!_peers.TryGetValue(peerId, out Peer value)) { value = new Peer(string.Empty, now); _peers[peerId] = value; } value.Version = version; value.LastSeen = now; } public void SawUnannouncedModTraffic(long peerId, float now) { if (!_peers.TryGetValue(peerId, out Peer value)) { value = new Peer(string.Empty, now); _peers[peerId] = value; } value.SentUnannouncedTraffic = true; value.LastSeen = now; } public void SawSpawned(long peerId, float now) { if (!_peers.TryGetValue(peerId, out Peer value)) { value = new Peer(string.Empty, now); _peers[peerId] = value; } Peer peer = value; float? spawnedAt = peer.SpawnedAt; float valueOrDefault = spawnedAt.GetValueOrDefault(); if (!spawnedAt.HasValue) { valueOrDefault = now; float? spawnedAt2 = valueOrDefault; peer.SpawnedAt = spawnedAt2; } } public IReadOnlyList<long> MissingModPastGrace(float now) { return (from pair in _peers.Where<KeyValuePair<long, Peer>>(delegate(KeyValuePair<long, Peer> pair) { if (pair.Value.Connected && pair.Value.Version == null && !pair.Value.SentUnannouncedTraffic) { float? spawnedAt = pair.Value.SpawnedAt; if (spawnedAt.HasValue) { float valueOrDefault = spawnedAt.GetValueOrDefault(); return now - valueOrDefault >= 30f; } } return false; }) select pair.Key into id orderby id select id).ToArray(); } public IReadOnlyList<long> RunningTheMod() { return (from pair in _peers.Where<KeyValuePair<long, Peer>>(delegate(KeyValuePair<long, Peer> pair) { if (pair.Value.Connected) { string version = pair.Value.Version; if (version != null) { return !IsOlderThan(version, "0.2.3"); } } return false; }) select pair.Key into id orderby id select id).ToArray(); } public string NameOf(long peerId) { if (!_peers.TryGetValue(peerId, out Peer value) || value.Name.Length <= 0) { return "A player"; } return value.Name; } public IReadOnlyList<string> Outdated() { return (from pair in ConfirmedOutdated() select (pair.Value.Name.Length <= 0) ? "A player" : pair.Value.Name).OrderBy<string, string>((string name) => name, StringComparer.Ordinal).ToArray(); } public IReadOnlyList<long> ConfirmedOutdatedPeerIds() { return (from pair in ConfirmedOutdated() select pair.Key).ToArray(); } private IEnumerable<KeyValuePair<long, Peer>> ConfirmedOutdated() { return _peers.Where<KeyValuePair<long, Peer>>((KeyValuePair<long, Peer> pair) => pair.Value.Connected && pair.Value.IsConfirmedOutdated(_serverVersion)); } private static (int, int)? MajorMinor(string? version) { string[] array = version?.Split(new char[1] { '.' }); if (array == null || array.Length < 2 || !int.TryParse(array[0], out var result) || !int.TryParse(array[1], out var result2)) { return null; } return (result, result2); } } } namespace SmartContainerRouting.Core.Text { public static class ContainerHoverText { public static string? BuildExtraLine(ContainerSettings settings, Func<string, string?> groupDisplayName, Func<string, bool>? groupEvictsMismatching = null, bool isWarded = false) { List<string> list = new List<string>(); string groupName = settings.GroupName; if (groupName != null) { list.Add("[" + (groupDisplayName(groupName) ?? groupName) + "]"); } string groupName2 = settings.GroupName; if (groupName2 != null && (groupEvictsMismatching?.Invoke(groupName2) ?? settings.IsEvicting)) { list.Add("Strict: only group items"); } if (isWarded) { list.Add("Routes within this ward only"); } if (list.Count != 0) { return string.Join(" | ", list); } return null; } } public static class DisplayNameSanitizer { public const int MaxLength = 32; public static string? Sanitize(string? value) { if (string.IsNullOrWhiteSpace(value)) { return null; } string text = StripMarkupCharacters(value.Trim()); if (text.Length == 0) { return null; } if (text.Length <= 32) { return text; } return text.Substring(0, 32); } private static string StripMarkupCharacters(string value) { StringBuilder stringBuilder = new StringBuilder(value.Length); foreach (char c in value) { if (c != '<' && c != '>') { stringBuilder.Append(c); } } return stringBuilder.ToString(); } } public static class GroupButtonHover { public const int MaxItems = 8; public static string Describe(string? groupName, IEnumerable<string> itemNames) { List<string> list = new List<string> { "Open group settings" }; if (groupName == null) { list.Add("No group assigned"); return string.Join("\n", list); } list.Add("Items in current group [" + groupName + "]:"); List<string> list2 = itemNames.Distinct<string>(StringComparer.Ordinal).OrderBy<string, string>((string name) => name, StringComparer.Ordinal).ToList(); if (list2.Count == 0) { list.Add("(none yet, use Add Contents)"); } else { list.AddRange(list2.Take(8)); if (list2.Count > 8) { list.Add($"+{list2.Count - 8} more"); } } return string.Join("\n", list); } } public static class GroupNameNormalizer { public static string? Normalize(string? value) { return DisplayNameSanitizer.Sanitize(value)?.ToLowerInvariant(); } } public static class GroupNameSuggestion { public static string? Suggest(IEnumerable<KeyValuePair<string, int>> stacks, GroupRegistry registry) { string text = (from entry in (from @group in (from stack in stacks select new KeyValuePair<string, int>(DisplayNameSanitizer.Sanitize(stack.Key), stack.Value) into stack where stack.Key != null select stack).GroupBy<KeyValuePair<string, int>, string>((KeyValuePair<string, int> stack) => stack.Key, StringComparer.Ordinal) select (Name: @group.Key, Total: @group.Sum((KeyValuePair<string, int> stack) => stack.Value)) into entry orderby entry.Total descending select entry).ThenBy<(string, int), string>(((string Name, int Total) entry) => entry.Name, StringComparer.Ordinal) select entry.Name).FirstOrDefault(); if (text == null || !registry.HasGroup(text)) { return text; } int num = 2; string text3; while (true) { string text2 = " " + num.ToString(CultureInfo.InvariantCulture); text3 = text[..Math.Min(text.Length, 32 - text2.Length)].TrimEnd(Array.Empty<char>()) + text2; if (!registry.HasGroup(text3)) { break; } num++; } return text3; } } public static class RouteSummary { public sealed class Neighbour { public ContainerSettings Settings { get; } public double Distance { get; } public bool HasRoom { get; } public bool SameWard { get; } public Neighbour(ContainerSettings settings, double distance, bool hasRoom, bool sameWard) { Settings = settings; Distance = distance; HasRoom = hasRoom; SameWard = sameWard; } } public const int MaxGroupLines = 6; public static string Describe(ContainerSettings source, GroupRegistry groups, IEnumerable<Neighbour> neighbours, double range, bool held) { List<string> list = new List<string>(); if (held) { list.Add("Sorting paused"); } list.Add("Routes (" + Meters(range) + ")"); List<Neighbour> source2 = neighbours.Where((Neighbour n) => n.Distance <= range && n.Settings.GroupName != null && groups.GetGroup(n.Settings.GroupName) != null).ToList(); List<string> list2 = (from entry in (from @group in source2.GroupBy<Neighbour, string>((Neighbour n) => groups.GetGroup(n.Settings.GroupName).Name, StringComparer.OrdinalIgnoreCase) select DescribeGroup(@group.Key, @group.ToList()) into entry orderby entry.Distance select entry).ThenBy<(double, string), string>(((double Distance, string Text) entry) => entry.Text, StringComparer.Ordinal) select entry.Text).ToList(); if (list2.Count == 0) { list.Add("No group chests in range"); } else { list.AddRange(list2.Take(6)); if (list2.Count > 6) { list.Add($"+{list2.Count - 6} more"); } } IEnumerable<GroupDefinition?> first = source2.Select((Neighbour n) => groups.GetGroup(n.Settings.GroupName)); string groupName = source.GroupName; object second; if (groupName != null) { GroupDefinition groupDefinition = groups.GetGroup(groupName); if (groupDefinition != null) { second = new GroupDefinition[1] { groupDefinition }; goto IL_019d; } } second = new GroupDefinition[0]; goto IL_019d; IL_019d: List<string> list3 = (from @group in first.Concat<GroupDefinition>((IEnumerable<GroupDefinition>)second) where @group.EvictMismatching select @group.Name).Distinct<string>(StringComparer.OrdinalIgnoreCase).OrderBy<string, string>((string name) => name, StringComparer.Ordinal).ToList(); if (list3.Count > 0) { list.Add("Strict: " + string.Join(", ", list3.Select((string name) => "[" + name + "]"))); } return string.Join("\n", list); } private static (double Distance, string Text) DescribeGroup(string name, IReadOnlyList<Neighbour> chests) { List<Neighbour> list = chests.Where((Neighbour c) => c.SameWard).ToList(); if (list.Count == 0) { double num = chests.Min((Neighbour c) => c.Distance); return (Distance: num, Text: "[" + name + "] " + Meters(num) + ", ward edge"); } List<Neighbour> list2 = list.Where((Neighbour c) => c.HasRoom).ToList(); if (list2.Count <= 0) { return (Distance: list.Min((Neighbour c) => c.Distance), Text: "[" + name + "] " + Meters(list.Min((Neighbour c) => c.Distance)) + ", full"); } return (Distance: list2.Min((Neighbour c) => c.Distance), Text: "[" + name + "] " + Meters(list2.Min((Neighbour c) => c.Distance))); } private static string Meters(double meters) { return Math.Round(meters).ToString(CultureInfo.InvariantCulture) + " m"; } } public static class RoutingMessage { public static string Describe(string itemName, int amount, bool isEviction, string? destinationGroup, double distanceMeters, bool insideWard, bool relayed) { List<string> list = new List<string> { Math.Round(distanceMeters).ToString(CultureInfo.InvariantCulture) + " m" }; if (insideWard) { list.Add("inside ward"); } if (relayed) { list.Add("via another player"); } string text = ((destinationGroup == null) ? "an ungrouped chest" : ("[" + destinationGroup + "]")); return string.Format("{0} {1} {2} to {3} ({4})", isEviction ? "Evicted" : "Routed", amount, itemName, text, string.Join(", ", list)); } public static string Combine(IReadOnlyList<string> lines, int maxLines = 4) { List<string> list = new List<string>(lines.Take(maxLines)); if (lines.Count > maxLines) { list.Add($"and {lines.Count - maxLines} more"); } return string.Join("\n", list); } } } namespace SmartContainerRouting.Core.Routing { public static class ContainerRouter { public static ContainerRoutingCandidate? SelectRoute(ContainerSettings source, PlacedItem item, IEnumerable<ContainerRoutingCandidate> candidates, GroupRegistry groups, double range) { if (BuiltInGroups.IsLocked(groups, source.GroupName)) { return null; } ContainerRoutingCandidate[] array = candidates.Where((ContainerRoutingCandidate candidate) => !BuiltInGroups.IsLocked(groups, candidate.Settings.GroupName)).ToArray(); if (IsEligibleDestination(source, groups, item.ItemType)) { if (!source.Priority) { return SelectNearest(item, array.Where((ContainerRoutingCandidate candidate) => candidate.Settings.Priority && IsEligible(candidate.Settings, source.GroupName)), range); } return null; } ContainerRoutingCandidate containerRoutingCandidate = SelectDestination(item, array, groups, range); if (containerRoutingCandidate != null) { return containerRoutingCandidate; } string groupName = source.GroupName; if (groupName != null) { GroupDefinition? groupDefinition = groups.GetGroup(groupName); if (groupDefinition != null && groupDefinition.EvictMismatching) { return SelectNearest(item, array.Where((ContainerRoutingCandidate candidate) => candidate.Settings.GroupName == null), range) ?? SelectNearest(item, array.Where((ContainerRoutingCandidate candidate) => IsKnownNonStrictGroup(candidate.Settings, groups)), range); } } return null; } public static bool CanAccept(ContainerSettings settings, GroupRegistry groups, string itemType, bool isReturn) { if (BuiltInGroups.IsLocked(groups, settings.GroupName)) { return false; } if (!isReturn && settings.GroupName != null && !IsEligibleDestination(settings, groups, itemType)) { return IsKnownNonStrictGroup(settings, groups); } return true; } public static ContainerRoutingCandidate? SelectLeftoverHome(PlacedItem item, IEnumerable<ContainerRoutingCandidate> candidates, GroupRegistry groups, double range) { ContainerRoutingCandidate[] array = candidates.Where((ContainerRoutingCandidate candidate) => !BuiltInGroups.IsLocked(groups, candidate.Settings.GroupName)).ToArray(); return SelectDestination(item, array, groups, range) ?? SelectNearest(item, array.Where((ContainerRoutingCandidate candidate) => candidate.Settings.GroupName == null), range) ?? SelectNearest(item, array.Where((ContainerRoutingCandidate candidate) => IsKnownNonStrictGroup(candidate.Settings, groups)), range); } public static bool WouldEvict(ContainerSettings settings, GroupRegistry groups, string itemType) { string groupName = settings.GroupName; if (groupName != null) { GroupDefinition? groupDefinition = groups.GetGroup(groupName); if (groupDefinition != null && groupDefinition.EvictMismatching) { return !IsEligibleDestination(settings, groups, itemType); } } return false; } private static bool IsKnownNonStrictGroup(ContainerSettings settings, GroupRegistry groups) { string groupName = settings.GroupName; if (groupName != null) { GroupDefinition groupDefinition = groups.GetGroup(groupName); if (groupDefinition != null && !groupDefinition.EvictMismatching) { return !BuiltInGroups.IsLocked(groupDefinition); } } return false; } public static ContainerRoutingCandidate? SelectDestination(PlacedItem item, IEnumerable<ContainerRoutingCandidate> candidates, GroupRegistry groups, double range) { string groupName = groups.GetGroupForItem(item.ItemType); if (groupName == null || BuiltInGroups.IsLocked(groups, groupName)) { return null; } ContainerRoutingCandidate[] array = candidates.Where((ContainerRoutingCandidate c) => IsEligible(c.Settings, groupName)).ToArray(); return SelectNearest(item, array.Where((ContainerRoutingCandidate c) => c.Settings.Priority), range) ?? SelectNearest(item, array, range); } public static bool IsSettled(ContainerSettings settings, GroupRegistry groups, string itemType) { if (!BuiltInGroups.IsLocked(groups, settings.GroupName)) { if (settings.Priority) { return IsEligibleDestination(settings, groups, itemType); } return false; } return true; } private static ContainerRoutingCandidate? SelectNearest(PlacedItem item, IEnumerable<ContainerRoutingCandidate> candidates, double range) { ContainerRoutingCandidate containerRoutingCandidate = null; double num = double.PositiveInfinity; foreach (ContainerRoutingCandidate candidate in candidates) { if (candidate.HasCapacityFor(item.ItemType)) { double num2 = item.Position.DistanceTo(candidate.Position); if (!(num2 > range) && (num2 < num || (num2 == num && (containerRoutingCandidate == null || string.CompareOrdinal(candidate.Id, containerRoutingCandidate.Id) < 0)))) { num = num2; containerRoutingCandidate = candidate; } } } return containerRoutingCandidate; } public static bool IsEligibleDestination(ContainerSettings settings, GroupRegistry groups, string itemType) { return IsEligible(settings, groups.GetGroupForItem(itemType)); } private static bool IsEligible(ContainerSettings settings, string? groupName) { if (groupName != null) { return string.Equals(settings.GroupName, groupName, StringComparison.OrdinalIgnoreCase); } return false; } } public sealed class ContainerRoutingCandidate { public string Id { get; } public Position Position { get; } public int FreeCapacity { get; } public IReadOnlyDictionary<string, int> PartialStackCapacity { get; } public ContainerSettings Settings { get; } public ContainerRoutingCandidate(string id, Position position, int freeCapacity, ContainerSettings settings, IReadOnlyDictionary<string, int>? partialStackCapacity = null) { Id = id; Position = position; FreeCapacity = freeCapacity; Settings = settings; PartialStackCapacity = partialStackCapacity ?? new Dictionary<string, int>(); } public bool HasCapacityFor(string itemType) { if (FreeCapacity <= 0) { if (PartialStackCapacity.TryGetValue(itemType, out var value)) { return value > 0; } return false; } return true; } public bool HasMoreCapacityThan(ContainerRoutingCandidate previous) { if (FreeCapacity > previous.FreeCapacity) { return true; } foreach (KeyValuePair<string, int> item in PartialStackCapacity) { if (item.Value > 0 && (!previous.PartialStackCapacity.TryGetValue(item.Key, out var value) || item.Value > value)) { return true; } } return false; } } public sealed class ItemTypeQuantity { public string ItemType { get; } public int Stack { get; } public ItemTypeQuantity(string itemType, int stack) { ItemType = itemType; Stack = stack; } } public sealed class LoopWatch { private const double ForgetEverySeconds = 1.0; private const int MaxKeys = 1024; private readonly int _threshold; private readonly double _window; private readonly Dictionary<string, Queue<double>> _events = new Dictionary<string, Queue<double>>(); private readonly Dictionary<string, double> _reportedAt = new Dictionary<string, double>(); private double _nextForget = double.MinValue; public int TrackedKeys => _events.Count; public LoopWatch(int threshold, double windowSeconds) { _threshold = threshold; _window = windowSeconds; } public bool Record(string key, double now) { Forget(now); if (!_events.TryGetValue(key, out Queue<double> value)) { value = new Queue<double>(); _events[key] = value; } value.Enqueue(now); while (value.Count > _threshold) { value.Dequeue(); } if (value.Count < _threshold || now - value.Peek() >= _window || (_reportedAt.TryGetValue(key, out var value2) && now - value2 < _window)) { return false; } _reportedAt[key] = now; return true; } public bool HasRecent(string key, double now) { if (_events.TryGetValue(key, out Queue<double> value)) { return value.Any((double time) => now - time < _window); } return false; } private void Forget(double now) { if (now < _nextForget && _events.Count < 1024) { return; } _nextForget = now + 1.0; if (_events.Count >= 1024) { _events.Clear(); _reportedAt.Clear(); return; } foreach (Queue<double> value in _events.Values) { while (value.Count > 0 && now - value.Peek() >= _window) { value.Dequeue(); } } string[] array = (from pair in _events where pair.Value.Count == 0 select pair.Key).ToArray(); foreach (string key in array) { _events.Remove(key); } array = (from pair in _reportedAt where now - pair.Value >= _window select pair.Key).ToArray(); foreach (string key2 in array) { _reportedAt.Remove(key2); } } } public static class NewArrivalFilter { public static Dictionary<string, int> ComputeBudget(IReadOnlyList<ItemTypeQuantity> knownStacks, IReadOnlyList<ItemTypeQuantity> currentStacks) { Dictionary<string, int> dictionary = SumByType(currentStacks); foreach (KeyValuePair<string, int> item in SumByType(knownStacks)) { int value; int num = (dictionary.TryGetValue(item.Key, out value) ? value : 0); dictionary[item.Key] = num - item.Value; } return dictionary; } public static IReadOnlyList<T> SelectWithinBudget<T>(Dictionary<string, int> budgetByType, IReadOnlyList<T> candidates, Func<T, ItemTypeQuantity> toStack) { List<T> list = new List<T>(); foreach (T candidate in candidates) { ItemTypeQuantity itemTypeQuantity = toStack(candidate); int value; int num = (budgetByType.TryGetValue(itemTypeQuantity.ItemType, out value) ? value : 0); if (num >= itemTypeQuantity.Stack) { list.Add(candidate); budgetByType[itemTypeQuantity.ItemType] = num - itemTypeQuantity.Stack; } } return list; } public static IReadOnlyList<T> SelectGenuinelyNew<T>(IReadOnlyList<ItemTypeQuantity> knownStacks, IReadOnlyList<ItemTypeQuantity> currentStacks, IReadOnlyList<T> candidates, Func<T, ItemTypeQuantity> toStack) { return SelectWithinBudget(ComputeBudget(knownStacks, currentStacks), candidates, toStack); } private static Dictionary<string, int> SumByType(IReadOnlyList<ItemTypeQuantity> stacks) { Dictionary<string, int> dictionary = new Dictionary<string, int>(); foreach (ItemTypeQuantity stack in stacks) { dictionary[stack.ItemType] = (dictionary.TryGetValue(stack.ItemType, out var value) ? value : 0) + stack.Stack; } return dictionary; } } public sealed class PlacedItem { public string ItemType { get; } public Position Position { get; } public PlacedItem(string itemType, Position position) { ItemType = itemType; Position = position; } } public readonly struct Position : IEquatable<Position> { public float X { get; } public float Y { get; } public float Z { get; } public Position(float x, float y, float z) { X = x; Y = y; Z = z; } public double DistanceTo(Position other) { float num = X - other.X; float num2 = Y - other.Y; float num3 = Z - other.Z; return Math.Sqrt(num * num + num2 * num2 + num3 * num3); } public bool Equals(Position other) { if (X.Equals(other.X) && Y.Equals(other.Y)) { return Z.Equals(other.Z); } return false; } public override bool Equals(object? obj) { if (obj is Position other) { return Equals(other); } return false; } public override int GetHashCode() { return ((17 * 31 + X.GetHashCode()) * 31 + Y.GetHashCode()) * 31 + Z.GetHashCode(); } } public sealed class RelayLedger<TPayload> { public sealed class PendingRequest { public long RequestId { get; } public string DestinationId { get; } public TPayload Payload { get; } public double SentAt { get; internal set; } public int Attempts { get; internal set; } internal PendingRequest(long requestId, string destinationId, TPayload payload, double sentAt) { RequestId = requestId; DestinationId = destinationId; Payload = payload; SentAt = sentAt; Attempts = 1; } } public sealed class DueRequests { public IReadOnlyList<PendingRequest> Resend { get; } public IReadOnlyList<PendingRequest> GiveUp { get; } internal DueRequests(IReadOnlyList<PendingRequest> resend, IReadOnlyList<PendingRequest> giveUp) { Resend = resend; GiveUp = giveUp; } } private readonly double _resendAfter; private readonly int _maxAttempts; private readonly double _cooldown; private readonly Dictionary<long, PendingRequest> _pending = new Dictionary<long, PendingRequest>(); private readonly Dictionary<string, double> _cooldownUntil = new Dictionary<string, double>(StringComparer.Ordinal); private readonly Random _random = new Random(); public int PendingCount => _pending.Count; public IEnumerable<PendingRequest> Pending => _pending.Values; public RelayLedger(double resendAfterSeconds, int maxAttempts, double cooldownSeconds) { _resendAfter = resendAfterSeconds; _maxAttempts = maxAttempts; _cooldown = cooldownSeconds; } public long Start(string destinationId, TPayload payload, double now) { long num; do { byte[] array = new byte[8]; _random.NextBytes(array); num = BitConverter.ToInt64(array, 0); } while (num == 0L || _pending.ContainsKey(num)); _pending[num] = new PendingRequest(num, destinationId, payload, now); return num; } public bool TryComplete(long requestId, out PendingRequest request) { if (_pending.TryGetValue(requestId, out request)) { _pending.Remove(requestId); return true; } return false; } public DueRequests Tick(double now) { List<PendingRequest> list = new List<PendingRequest>(); List<PendingRequest> list2 = new List<PendingRequest>(); foreach (PendingRequest value in _pending.Values) { if (!(now - value.SentAt < _resendAfter)) { if (value.Attempts >= _maxAttempts) { list2.Add(value); continue; } value.Attempts++; value.SentAt = now; list.Add(value); } } foreach (PendingRequest item in list2) { _pending.Remove(item.RequestId); MarkRejected(item.DestinationId, now); } return new DueRequests(list, list2); } public void MarkRejected(string destinationId, double now) { _cooldownUntil[destinationId] = now + _cooldown; } public bool IsCoolingDown(string destinationId, double now) { if (_cooldownUntil.TryGetValue(destinationId, out var value)) { return now < value; } return false; } } public sealed class RelayReachability { private readonly Dictionary<string, long> _unanswered = new Dictionary<string, long>(); private readonly Dictionary<string, (long Holder, uint State)> _refused = new Dictionary<string, (long, uint)>(); public int MarkedCount => _unanswered.Keys.Union(_refused.Keys).Count(); public void MarkUnanswered(string chestId, long holder) { _unanswered[chestId] = holder; } public void MarkRefused(string chestId, long holder, uint state) { _refused[chestId] = (holder, state); } public bool HasMark(string chestId) { if (!_unanswered.ContainsKey(chestId)) { return _refused.ContainsKey(chestId); } return true; } public bool IsBlocked(string chestId, long currentHolder, uint currentState) { if (!_unanswered.TryGetValue(chestId, out var value) || value != currentHolder) { if (_refused.TryGetValue(chestId, out (long, uint) value2) && value2.Item1 == currentHolder) { return value2.Item2 == currentState; } return false; } return true; } public void PlayersChanged() { Clear(); } public void GroupsChanged() { _refused.Clear(); } public void Clear() { _unanswered.Clear(); _refused.Clear(); } } public static class RelayReceipts { public const int DefaultCapacity = 64; public static bool TryFind(string? stored, long sender, long requestId, out int added) { foreach (var item in Parse(stored)) { if (item.Sender == sender && item.RequestId == requestId) { added = item.Added; return true; } } added = 0; return false; } public static string Add(string? stored, long sender, long requestId, int added, int capacity = 64) { List<(long, long, int)> list = Parse(stored).ToList(); list.Add((sender, requestId, added)); return string.Join(";", from entry in list.Skip(Math.Max(0, list.Count - capacity)) select string.Join(":", entry.Sender.ToString(CultureInfo.InvariantCulture), entry.RequestId.ToString(CultureInfo.InvariantCulture), entry.Added.ToString(CultureInfo.InvariantCulture))); } private static IEnumerable<(long Sender, long RequestId, int Added)> Parse(string? stored) { if (string.IsNullOrEmpty(stored)) { yield break; } string[] array = stored.Split(new char[1] { ';' }); for (int i = 0; i < array.Length; i++) { string[] array2 = array[i].Split(new char[1] { ':' }); if (array2.Length == 3 && long.TryParse(array2[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) && long.TryParse(array2[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2) && int.TryParse(array2[2], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result3)) { yield return (Sender: result, RequestId: result2, Added: result3); } } } } public static class WardWall { public static bool CanRoute(IEnumerable<string> sourceWardIds, IEnumerable<string> destinationWardIds) { return new HashSet<string>(sourceWardIds, StringComparer.Ordinal).SetEquals(destinationWardIds); } } } namespace SmartContainerRouting.Core.Persistence { public static class AnchorPlacement { public const float ParkX = -16300f; public const float ParkZ = -16300f; private const double ZoneSize = 64.0; public static bool NeedsParking(float x, float z) { if (Zone(x) == Zone(-16300f)) { return Zone(z) != Zone(-16300f); } return true; } private static int Zone(float position) { return (int)Math.Floor(((double)position + 32.0) / 64.0); } } public static class ContainerSettingsCodec { public static ContainerSettings Decode(Func<string, string> getString, Func<string, bool> getBool, GroupRegistry? groups = null) { string name = getString("SCR_Name"); string groupName = GroupReference.Resolve(getString("SCR_GroupId"), getString("SCR_Group"), groups); bool evictNonGroupItems = getBool("SCR_EvictNonGroup"); bool priority = getBool("SCR_Priority"); return new ContainerSettings(name, groupName, evictNonGroupItems, priority); } public static void Encode(ContainerSettings settings, Action<string, string> setString, Action<string, bool> setBool, GroupRegistry? groups = null, string? knownGroupId = null, Func<string, string>? getExisting = null) { string text = GroupReference.IdFor(settings.GroupName, groups); if (text.Length == 0 && settings.GroupName != null && !string.IsNullOrEmpty(knownGroupId)) { text = knownGroupId; } if (text.Length == 0 && groups == null && settings.GroupName != null && getExisting != null && GroupNameNormalizer.Normalize(getExisting("SCR_Group")) == settings.GroupName) { text = getExisting("SCR_GroupId") ?? string.Empty; } setString("SCR_Name", settings.Name ?? string.Empty); setString("SCR_Group", settings.GroupName ?? string.Empty); setString("SCR_GroupId", text); setBool("SCR_EvictNonGroup", settings.EvictNonGroupItems); setBool("SCR_Priority", settings.Priority); } } public sealed class GroupRegistryCache { private readonly Func<string?, GroupRegistry> _decode; private bool _hasValue; private string? _source; private GroupRegistry _registry = GroupRegistry.Empty; public GroupRegistryCache(Func<string?, GroupRegistry> decode) { _decode = decode ?? throw new ArgumentNullException("decode"); } public GroupRegistry Get(string? source) { if (!_hasValue || !string.Equals(_source, source, StringComparison.Ordinal)) { _source = source; _registry = _decode(source); _hasValue = true; } return _registry; } public void Set(string? source, GroupRegistry registry) { _source = source; _registry = registry; _hasValue = true; } public bool TryGet(string? source, out GroupRegistry registry) { if (source == null) { registry = GroupRegistry.Empty; return false; } registry = Get(source); return true; } } public static class GroupRegistryCodec { private sealed class GroupDto { public string? Id { get; set; } public string? Name { get; set; } = string.Empty; public long CreatorId { get; set; } public bool EvictMismatching { get; set; } public List<string?>? ItemTypes { get; set; } = new List<string>(); } public static string Serialize(GroupRegistry registry) { StringBuilder stringBuilder = new StringBuilder("["); bool flag = true; foreach (GroupDefinition item in registry.Groups.OrderBy<GroupDefinition, string>((GroupDefinition group) => group.Name, StringComparer.Ordinal)) { stringBuilder.Append(flag ? "{" : ",{"); Json.WriteMember(stringBuilder, "id", item.Id, first: true); Json.WriteMember(stringBuilder, "name", item.Name); Json.WriteMember(stringBuilder, "creatorId", item.CreatorId); Json.WriteMember(stringBuilder, "evictMismatching", item.EvictMismatching); Json.WriteMember(stringBuilder, "itemTypes", item.ItemTypes.OrderBy<string, string>((string item) => item, StringComparer.Ordinal)); stringBuilder.Append('}'); flag = false; } return stringBuilder.Append(']').ToString(); } public static GroupRegistry Deserialize(string? json) { TryDeserialize(json, out GroupRegistry registry); return registry; } public static bool TryDeserialize(string? json, out GroupRegistry registry) { if (string.IsNullOrWhiteSpace(json)) { registry = GroupRegistry.Empty; return true; } List<GroupDto> list; try { list = ReadGroups(Json.Parse(json)); } catch (FormatException) { registry = GroupRegistry.Empty; return false; } GroupRegistry groupRegistry = GroupRegistry.Empty; foreach (GroupDto item in list) { if (item == null) { continue; } string text = DisplayNameSanitizer.Sanitize(item.Name); if (text == null) { continue; } groupRegistry = groupRegistry.WithGroup(text, item.CreatorId, item.Id); if (!groupRegistry.HasGroup(text)) { continue; } if (item.EvictMismatching) { groupRegistry = groupRegistry.WithEvictMismatching(text, value: true); } foreach (string item2 in item.ItemTypes ?? new List<string>()) { if (!string.IsNullOrWhiteSpace(item2)) { groupRegistry = groupRegistry.WithItemAssignedToGroup(item2, text); } } } registry = groupRegistry; return true; } private static List<GroupDto?> ReadGroups(Json.JsonValue root) { if (root.Kind == Json.JsonKind.Null) { return new List<GroupDto>(); } if (root.Kind != Json.JsonKind.Array) { throw new FormatException("Expected an array of groups."); } List<GroupDto> list = new List<GroupDto>(root.Items.Count); foreach (Json.JsonValue item in root.Items) { if (item.Kind == Json.JsonKind.Null) { list.Add(null); continue; } if (item.Kind != Json.JsonKind.Object) { throw new FormatException("Expected a group object."); } GroupDto groupDto = new GroupDto(); foreach (KeyValuePair<string, Json.JsonValue> member in item.Members) { switch (member.Key) { case "id": groupDto.Id = member.Value.AsNullableString(); break; case "name": groupDto.Name = member.Value.AsNullableString(); break; case "creatorId": groupDto.CreatorId = member.Value.AsLong(); break; case "evictMismatching": groupDto.EvictMismatching = member.Value.AsBool(); break; case "itemTypes": groupDto.ItemTypes = member.Value.AsNullableStringList(); break; } } list.Add(groupDto); } return list; } } public static class GroupRegistryMutationCodec { private sealed class MutationDto { public string? Kind { get; set; } public string? GroupName { get; set; } public string? NewGroupName { get; set; } public string? ItemType { get; set; } public List<string>? ItemTypes { get; set; } public long PlayerId { get; set; } public bool EvictMismatching { get; set; } public string? GroupId { get; set; } } public static string Serialize(GroupRegistryMutation mutation) { StringBuilder stringBuilder = new StringBuilder("{"); Json.WriteMember(stringBuilder, "kind", mutation.Kind.ToString(), first: true); Json.WriteMember(stringBuilder, "groupName", mutation.GroupName); Json.WriteMember(stringBuilder, "newGroupName", mutation.NewGroupName); Json.WriteMember(stringBuilder, "itemType", mutation.ItemType); Json.WriteMember(stringBuilder, "itemTypes", mutation.ItemTypes); Json.WriteMember(stringBuilder, "playerId", mutation.PlayerId); Json.WriteMember(stringBuilder, "evictMismatching", mutation.EvictMismatching); Json.WriteMember(stringBuilder, "groupId", mutation.GroupId); return stringBuilder.Append('}').ToString(); } public static GroupRegistryMutation? Deserialize(string? json) { if (string.IsNullOrWhiteSpace(json)) { return null; } MutationDto mutationDto; try { mutationDto = ReadMutation(Json.Parse(json)); } catch (FormatException) { return null; } if (mutationDto?.Kind == null || !Enum.TryParse<GroupRegistryMutationKind>(mutationDto.Kind, out var result)) { return null; } switch (result) { case GroupRegistryMutationKind.CreateGroup: { string groupName5 = mutationDto.GroupName; if (groupName5 != null) { return GroupRegistryMutation.CreateGroup(groupName5, mutationDto.PlayerId, mutationDto.GroupId); } break; } case GroupRegistryMutationKind.DeleteGroup: { string groupName4 = mutationDto.GroupName; if (groupName4 != null) { return GroupRegistryMutation.DeleteGroup(groupName4, mutationDto.PlayerId); } break; } case GroupRegistryMutationKind.RenameGroup: { string groupName6 = mutationDto.GroupName; if (groupName6 != null) { string newGroupName = mutationDto.NewGroupName; if (newGroupName != null) { return GroupRegistryMutation.RenameGroup(groupName6, newGroupName, mutationDto.PlayerId); } } break; } case GroupRegistryMutationKind.AssignItem: { string itemType = mutationDto.ItemType; if (itemType != null) { string groupName2 = mutationDto.GroupName; if (groupName2 != null) { return GroupRegistryMutation.AssignItem(itemType, groupName2); } } break; } case GroupRegistryMutationKind.UnassignItem: { string itemType2 = mutationDto.ItemType; if (itemType2 != null) { return GroupRegistryMutation.UnassignItem(itemType2); } break; } case GroupRegistryMutationKind.ReplaceGroupItems: { string groupName3 = mutationDto.GroupName; if (groupName3 != null) { List<string> itemTypes = mutationDto.ItemTypes; if (itemTypes != null) { return GroupRegistryMutation.ReplaceGroupItems(groupName3, itemTypes, mutationDto.PlayerId); } } break; } case GroupRegistryMutationKind.SetGroupEviction: { string groupName = mutationDto.GroupName; if (groupName != null) { return GroupRegistryMutation.SetGroupEviction(groupName, mutationDto.EvictMismatching, mutationDto.PlayerId); } break; } } return null; } private static MutationDto? ReadMutation(Json.JsonValue root) { if (root.Kind == Json.JsonKind.Null) { return null; } if (root.Kind != Json.JsonKind.Object) { throw new FormatException("Expected a mutation object."); } MutationDto mutationDto = new MutationDto(); foreach (KeyValuePair<string, Json.JsonValue> member in root.Members) { switch (member.Key) { case "kind": mutationDto.Kind = member.Value.AsNullableString(); break; case "groupName": mutationDto.GroupName = member.Value.AsNullableString(); break; case "newGroupName": mutationDto.NewGroupName = member.Value.AsNullableString(); break; case "itemType": mutationDto.ItemType = member.Value.AsNullableString(); break; case "itemTypes": mutationDto.ItemTypes = member.Value.AsNullableStringList(); break; case "playerId": mutationDto.PlayerId = member.Value.AsLong(); break; case "evictMismatching": mutationDto.EvictMismatching = member.Value.AsBool(); break; case "groupId": mutationDto.GroupId = member.Value.AsNullableString(); break; } } return mutationDto; } } internal static class Json { public enum JsonKind { Null, True, False, Number, String, Array, Object } internal sealed class JsonValue { private readonly string? _text; private readonly bool _hasLoneSurrogate; public static readonly JsonValue Null = new JsonValue(JsonKind.Null); public static readonly JsonValue True = new JsonValue(JsonKind.True); public static readonly JsonValue False = new JsonValue(JsonKind.False); public JsonKind Kind { get; } public List<JsonValue> Items { get; } = new List<JsonValue>(); public List<KeyValuePair<string, JsonValue>> Members { get; } = new List<KeyValuePair<string, JsonValue>>(); private JsonValue(JsonKind kind, string? text = null, bool hasLoneSurrogate = false) { Kind = kind; _text = text; _hasLoneSurrogate = hasLoneSurrogate; } public static JsonValue Number(string text) { return new JsonValue(JsonKind.Number, text); } public static JsonValue String(string text, bool hasLoneSurrogate) { return new JsonValue(JsonKind.String, text, hasLoneSurrogate); } public static JsonValue Array() { return new JsonValue(JsonKind.Array); } public static JsonValue Object() { return new JsonValue(JsonKind.Object); } public string? AsNullableString() { if (Kind == JsonKind.Null) { return null; } if (Kind != JsonKind.String || _hasLoneSurrogate) { throw new FormatException("Expected a string."); } return _text; } public long AsLong() { if (Kind != JsonKind.Number || _text.IndexOfAny(new char[3] { '.', 'e', 'E' }) >= 0 || !long.TryParse(_text, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out var result)) { throw new FormatException("Expected an integer."); } return result; } public bool AsBool() { return Kind switch { JsonKind.True => true, JsonKind.False => false, _ => throw new FormatException("Expected true or false."), }; } public List<string?>? AsNullableStringList() { if (Kind == JsonKind.Null) { return null; } if (Kind != JsonKind.Array) { throw new FormatException("Expected an array."); } List<string> list = new List<string>(Items.Count); foreach (JsonValue item in Items) { list.Add(item.AsNullableString()); } return list; } } private sealed class Parser { private readonly string _text; private int _position; private int _depth; public Parser(string text) { _text = text; } public JsonValue ParseDocument() { SkipWhitespace(); JsonValue result = ParseValue(); SkipWhitespace(); if (_position != _text.Length) { throw Error("Unexpected content after the JSON value."); } return result; } private JsonValue ParseValue() { if (_position >= _text.Length) { throw Error("Unexpected end of input."); } switch (_text[_position]) { case '{': return ParseObject(); case '[': return ParseArray(); case '"': return ParseString(); case 't': Expect("true"); return JsonValue.True; case 'f': Expect("false"); return JsonValue.False; case 'n': Expect("null"); return JsonValue.Null; default: return ParseNumber(); } } private JsonValue ParseObject() { Enter(); _position++; JsonValue jsonValue = JsonValue.Object(); SkipWhitespace(); if (TryConsume('}')) { _depth--; return jsonValue; } do { SkipWhitespace(); if (_position >= _text.Length || _text[_position] != '"') { throw Error("Expected a property name."); } string key = ParseString().AsNullableString(); SkipWhitespace(); if (!TryConsume(':')) { throw Error("Expected ':'."); } SkipWhitespace(); jsonValue.Members.Add(new KeyValuePair<string, JsonValue>(key, ParseValue())); SkipWhitespace(); if (TryConsume('}')) { _depth--; return jsonValue; } } while (TryConsume(',')); throw Error("Expected ',' or '}'."); } private JsonValue ParseArray() { Enter(); _position++; JsonValue jsonValue = JsonValue.Array(); SkipWhitespace(); if (TryConsume(']')) { _depth--; return jsonValue; } do { SkipWhitespace(); jsonValue.Items.Add(ParseValue()); SkipWhitespace(); if (TryConsume(']')) { _depth--; return jsonValue; } } while (TryConsume(',')); throw Error("Expected ',' or ']'."); } private JsonValue ParseString() { _position++; StringBuilder stringBuilder = new StringBuilder(); while (true) { if (_position >= _text.Length) { throw Error("Unterminated string."); } char c = _text[_position++]; if (c == '"') { break; } if (c < ' ') { throw Error("Control character in string."); } if (c != '\\') { stringBuilder.Append(c); continue; } if (_position >= _text.Length) { throw Error("Unterminated escape."); } switch (_text[_position++]) { case '"': stringBuilder.Append('"'); break; case '\\': stringBuilder.Append('\\'); break; case '/': stringBuilder.Append('/'); break; case 'b': stringBuilder.Append('\b'); break; case 'f': stringBuilder.Append('\f'); break; case 'n': stringBuilder.Append('\n'); break; case 'r': stringBuilder.Append('\r'); break; case 't': stringBuilder.Append('\t'); break; case 'u': { if (_position + 4 > _text.Length || !ushort.TryParse(_text.Substring(_position, 4), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out var result)) { throw Error("Invalid \\u escape."); } stringBuilder.Append((char)result); _position += 4; break; } default: throw Error("Invalid escape."); } } string text = stringBuilder.ToString(); return JsonValue.String(text, HasLoneSurrogate(text)); } private JsonValue ParseNumber() { int position = _position; TryConsume('-'); if (!TryConsume('0') && !ConsumeDigits()) { throw Error("Invalid value."); } if (TryConsume('.') && !ConsumeDigits()) { throw Error("Invalid number."); } if (_position < _text.Length && (_text[_position] == 'e' || _text[_position] == 'E')) { _position++; if (!TryConsume('+')) { TryConsume('-'); } if (!ConsumeDigits()) { throw Error("Invalid number."); } } return JsonValue.Number(_text.Substring(position, _position - position)); } private bool ConsumeDigits() { int position = _position; while (_position < _text.Length && _text[_position] >= '0' && _text[_position] <= '9') { _position++; } return _position > position; } private void Expect(string literal) { if (string.CompareOrdinal(_text, _position, literal, 0, literal.Length) != 0) { throw Error("Invalid literal."); } _position += literal.Length; } private void Enter() { if (++_depth > 64) { throw Error("Nested too deeply."); } } private bool TryConsume(char expected) { if (_position < _text.Length && _text[_position] == expected) { _position++; return true; } return false; } private void SkipWhitespace() { while (true) { bool flag = _position < _text.Length; if (flag) { bool flag2; switch (_text[_position]) { case '\t': case '\n': case '\r': case ' ': flag2 = true; break; default: flag2 = false; break; } flag = flag2; } if (flag) { _position++; continue; } break; } } private FormatException Error(string message) { return new FormatException($"{message} (at {_position})"); } private static bool HasLoneSurrogate(string value) { for (int i = 0; i < value.Length; i++) { if (char.IsHighSurrogate(value[i]) && i + 1 < value.Length && char.IsLowSurrogate(value[i + 1])) { i++; } else if (char.IsSurrogate(value[i])) { return true; } } return false; } } private const int MaxDepth = 64; public static void WriteString(StringBuilder output, string? value) { if (value == null) { output.Append("null"); return; } output.Append('"'); for (int i = 0; i < value.Length; i++) { char c = value[i]; switch (c) { case '\\': output.Append("\\\\"); continue; case '\b': output.Append("\\b"); continue; case '\f': output.Append("\\f"); continue; case '\n': output.Append("\\n"); continue; case '\r': output.Append("\\r"); continue; case '\t': output.Append("\\t"); continue; } if (char.IsHighSurrogate(c) && i + 1 < value.Length && char.IsLowSurrogate(value[i + 1])) { AppendEscaped(output, c); AppendEscaped(output, value[++i]); } else if (char.IsSurrogate(c)) { AppendEscaped(output, '\ufffd'); } else if (c < ' ' || c > '~' || c == '"' || c == '&' || c == '\'' || c == '+' || c == '<' || c == '>' || c == '`') { AppendEscaped(output, c); } else { output.Append(c); } } output.Append('"'); } public static void WriteMember(StringBuilder output, string name, string? value, bool first = false) { AppendName(output, name, first); WriteString(output, value); } public static void WriteMember(StringBuilder output, string name, long value) { AppendName(output, name, first: false); output.Append(value.ToString(CultureInfo.InvariantCulture)); } public static void WriteMember(StringBuilder output, string name, bool value) { AppendName(output, name, first: false); output.Append(value ? "true" : "false"); } public static void WriteMember(StringBuilder output, string name, IEnumerable<string?>? values) { AppendName(output, name, first: false); if (values == null) { output.Append("null"); return; } output.Append('['); bool flag = true; foreach (string value in values) { if (!flag) { output.Append(','); } WriteString(output, value); flag = false; } output.Append(']'); } private static void AppendName(StringBuilder output, string name, bool first) { if (!first) { output.Append(','); } output.Append('"').Append(name).Append("\":"); } private static void AppendEscaped(StringBuilder output, char c) { StringBuilder stringBuilder = output.Append("\\u"); int num = c; stringBuilder.Append(num.ToString("X4", CultureInfo.InvariantCulture)); } public static JsonValue Parse(string text) { return new Parser(text).ParseDocument(); } } public static class PersistenceKeys { public const string ContainerName = "SCR_Name"; public const string ContainerGroup = "SCR_Group"; public const string ContainerGroupId = "SCR_GroupId"; public const string ContainerEvictNonGroupItems = "SCR_EvictNonGroup"; public const string ContainerPriority = "SCR_Priority"; public const string ContainerRelayReceipts = "SCR_RelayReceipts"; public const string GroupRegistryBlob = "SCR_Groups"; public const string GroupRegistryCorruptBackup = "SCR_GroupsCorruptBackup"; } } namespace SmartContainerRouting.Core.Model { public static class BuiltInGroups { public const string LockedId = "builtin:locked"; public const string LockedName = "Locked"; public const string DisplacedLockedName = "Locked (old)"; public static bool IsLocked(GroupDefinition? group) { if (group != null) { return string.Equals(group.Id, "builtin:locked", StringComparison.Ordinal); } return false; } public static bool IsLocked(GroupRegistry groups, string? groupName) { if (groupName == null) { return false; } GroupDefinition groupDefinition = groups.GetGroup(groupName); if (groupDefinition == null) { return string.Equals(GroupNameNormalizer.Normalize(groupName), GroupNameNormalizer.Normalize("Locked"), StringComparison.Ordinal); } return IsLocked(groupDefinition); } public static GroupRegistry EnsureLocked(GroupRegistry registry) { GroupRegistry groupRegistry = registry; GroupDefinition groupById = groupRegistry.GetGroupById("builtin:locked"); GroupDefinition groupDefinition = groupRegistry.GetGroup("Locked"); if (groupDefinition != null && !IsLocked(groupDefinition)) { groupRegistry = groupRegistry.WithRenamedGroup(groupDefinition.Name, FreeName(groupRegistry)); } if (groupById == null) { return groupRegistry.WithGroup("Locked", 0L, "builtin:locked"); } if (!string.Equals(groupById.Name, "Locked", StringComparison.Ordinal)) { groupRegistry = groupRegistry.WithRenamedGroup(groupById.Name, "Locked"); } if (groupById.ItemTypes.Count > 0) { groupRegistry = groupRegistry.WithGroupItemsReplaced("Locked", Array.Empty<string>()); } if (groupById.EvictMismatching) { groupRegistry = groupRegistry.WithEvictMismatching("Locked", value: false); } return groupRegistry; } private static string FreeName(GroupRegistry registry) { if (!registry.HasGroup("Locked (old)")) { return "Locked (old)"; } return (from n in Enumerable.Range(2, 2147483645) select $"Locked (old {n})").First((string name) => !registry.HasGroup(name)); } } public sealed class ContainerSettings { public string? Name { get; } public string? GroupName { get; } public bool EvictNonGroupItems { get; } public bool IsEvicting { get { if (EvictNonGroupItems) { return GroupName != null; } return false; } } public bool Priority { get; } public static ContainerSettings Default { get; } = new ContainerSettings(); public ContainerSettings(string? name = null, string? groupName = null, bool evictNonGroupItems = false, bool priority = false) { Name = NormalizeOrNull(name); GroupName = GroupNameNormalizer.Normalize(groupName); EvictNonGroupItems = evictNonGroupItems; Priority = priority; } public ContainerSettings WithName(string? name) { return new ContainerSettings(name, GroupName, EvictNonGroupItems, Priority); } public ContainerSettings WithGroupName(string? groupName) { return new ContainerSettings(Name, groupName, EvictNonGroupItems, Priority); } public ContainerSettings WithEvictNonGroupItems(bool value) { return new ContainerSettings(Name, GroupName, value, Priority); } public ContainerSettings WithPriority(bool value) { return new ContainerSettings(Name, GroupName, EvictNonGroupItems, value); } public bool Equals(ContainerSettings? other) { if (other != null && string.Equals(Name, other.Name, StringComparison.Ordinal) && string.Equals(GroupName, other.GroupName, StringComparison.Ordinal) && EvictNonGroupItems == other.EvictNonGroupItems) { return Priority == other.Priority; } return false; } public override bool Equals(object? obj) { return Equals(obj as ContainerSettings); } public override int GetHashCode() { return (((17 * 31 + (Name?.GetHashCode() ?? 0)) * 31 + (GroupName?.GetHashCode() ?? 0)) * 31 + EvictNonGroupItems.GetHashCode()) * 31 + Priority.GetHashCode(); } private static string? NormalizeOrNull(string? value) { return DisplayNameSanitizer.Sanitize(value); } } public sealed class GroupDefinition { public string Name { get; } public string Id { get; } public ItemTypeSet ItemTypes { get; } public bool EvictMismatching { get; } public long CreatorId { get; } private GroupDefinition(string id, string name, ItemTypeSet itemTypes, long creatorId, bool evictMismatching = false) { Id = id; Name = name; ItemTypes = itemTypes; CreatorId = creatorId; EvictMismatching = evictMismatching; } public static string LegacyIdFor(string name) { return "name:" + GroupNameNormalizer.Normalize(name); } public static GroupDefinition Empty(string name, long creatorId = 0L, bool evictMismatching = false, string? id = null) { if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentException("Group name must not be empty.", "name"); } return new GroupDefinition(string.IsNullOrWhiteSpace(id) ? LegacyIdFor(name) : id, name, ItemTypeSet.Empty, creatorId, evictMismatching); } internal GroupDefinition WithItem(string itemType) { return new GroupDefinition(Id, Name, ItemTypes.Add(itemType), CreatorId, EvictMismatching); } internal GroupDefinition WithoutItem(string itemType) { return new GroupDefinition(Id, Name, ItemTypes.Remove(itemType), CreatorId, EvictMismatching); } internal GroupDefinition WithItems(ItemTypeSet itemTypes) { return new GroupDefinition(Id, Name, itemTypes, CreatorId, EvictMismatching); } internal GroupDefinition WithEvictMismatching(bool value) { return new GroupDefinition(Id, Name, ItemTypes, CreatorId, value); } } public static class GroupItemAdditions { public static IReadOnlyList<string> Missing(GroupRegistry registry, string groupName, IEnumerable<string> chestItemTypes) { GroupDefinition group = registry.GetGroup(groupName); if (group == null) { return Array.Empty<string>(); } return chestItemTypes.Where((string itemType) => !string.IsNullOrEmpty(itemType) && !group.ItemTypes.Contains(itemType)).Distinct<string>(StringComparer.Ordinal).OrderBy<string, string>((string itemType) => itemType, StringComparer.Ordinal) .ToList(); } } public static class GroupItemTransfers { public static string? Describe(GroupRegistry registry, string targetGroupName, IEnumerable<string> itemTypes, Func<string, string> itemDisplayName) { string targetName = registry.GetGroup(targetGroupName)?.Name ?? targetGroupName; string[] array = (from @group in (from itemType in itemTypes.Distinct<string>(StringComparer.Ordinal) select (itemType: itemType, owner: registry.GetGroupForItem(itemType)) into pair where pair.owner != null && !string.Equals(pair.owner, targetName, StringComparison.OrdinalIgnoreCase) select pair).GroupBy<(string, string), string>(((string itemType, string owner) pair) => pair.owner, StringComparer.Ordinal).OrderBy<IGrouping<string, (string, string)>, string>((IGrouping<string, (string itemType, string owner)> @group) => @group.Key, StringComparer.Ordinal) select string.Join(", ", @group.Select(((string itemType, string owner) pair) => itemDisplayName(pair.itemType)).OrderBy<string, string>((string name) => name, StringComparer.Ordinal)) + " moved from " + @group.Key + " to " + targetName).ToArray(); if (array.Length != 0) { return string.Join("\n", array); } return null; } } public static class GroupReference { public static string? Resolve(string? storedId, string? storedName, GroupRegistry? registry) { string text = GroupNameNormalizer.Normalize(storedName); if (registry == null) { return text; } GroupDefinition groupDefinition = ((!string.IsNullOrEmpty(storedId)) ? registry.GetGroupById(storedId) : ((text == null) ? null : (registry.GetGroupById(GroupDefinition.LegacyIdFor(text)) ?? registry.GetGroup(text)))); if (groupDefinition != null) { return GroupNameNormalizer.Normalize(groupDefinition.Name); } return text; } public static bool WasDeleted(GroupRegistry? previousTrusted, GroupRegistry current, string groupName) { if (previousTrusted != null && previousTrusted.HasGroup(groupName)) { return !current.HasGroup(groupName); } return false; } public static string IdFor(string? groupName, GroupRegistry? registry) { object obj; if (groupName != null) { obj = registry?.GetGroup(groupName)?.Id; if (obj == null) { return string.Empty; } } else { obj = string.Empty; } return (string)obj; } } public sealed class GroupRegistry { private readonly Dictionary<string, GroupDefinition> _groups; public static GroupRegistry Empty { get; } = new GroupRegistry(new Dictionary<string, GroupDefinition>()); public IReadOnlyCollection<GroupDefinition> Groups => (IReadOnlyCollection<GroupDefinition>)(object)_groups.Values.ToArray(); private GroupRegistry(Dictionary<string, GroupDefinition> groups) { _groups = groups; } public bool HasGroup(string groupName) { string text = GroupNameNormalizer.Normalize(groupName); if (text != null) { return _groups.ContainsKey(text); } return false; } public GroupDefinition? GetGroup(string groupName) { string text = GroupNameNormalizer.Normalize(groupName); if (text == null || !_groups.TryGetValue(text, out GroupDefinition value)) { return null; } return value; } public GroupDefinition? GetGroupById(string? groupId) { if (!string.IsNullOrEmpty(groupId)) { return _groups.Values.FirstOrDefault((GroupDefinition group) => string.Equals(group.Id, groupId, StringComparison.Ordinal)); } return null; } public GroupRegistry WithGroup(string groupName, long creatorId = 0L, string? groupId = null) { string? obj = DisplayNameSanitizer.Sanitize(groupName) ?? throw new ArgumentException("Group name must not be empty.", "groupName"); string key = GroupNameNormalizer.Normalize(obj); GroupDefinition groupDefinition = GroupDefinition.Empty(obj, creatorId, evictMismatching: false, groupId); if (!_groups.ContainsKey(key) && GetGroupById(groupDefinition.Id) == null) { return new GroupRegistry(With(_groups, key, groupDefinition)); } return this; } public GroupRegistry WithoutGroup(string groupName) { string text = GroupNameNormalizer.Normalize(groupName); if (text == null) { return this; } if (!_groups.ContainsKey(text)) { return this; } return new GroupRegistry(Without(_groups, text)); } public GroupRegistry WithRenamedGroup(string oldName, string newName) { string text = DisplayNameSanitizer.Sanitize(newName); if (text == null) { throw new ArgumentException("Group name must not be empty.", "newName"); } string text2 = GroupNameNormalizer.Normalize(oldName); if (!_groups.TryGetValue(text2, out GroupDefinition value)) { throw new ArgumentException("Group '" + oldName + "' does not exist.", "oldName"); } if (string.Equals(value.Name, text, StringComparison.Ordinal)) { return this; } string text3 = GroupNameNormalizer.Normalize(text); if (text2 != text3 && _groups.ContainsKey(text3)) { throw new ArgumentException("Group '" + text + "' already exists.", "newName"); } GroupDefinition groupDefinition = GroupDefinition.Empty(text, value.CreatorId, value.EvictMismatching, value.Id).WithItems(value.ItemTypes); if (!(text2 == text3)) { return new GroupRegistry(With(Without(_groups, text2), text3, groupDefinition)); } return new GroupRegistry(With(_groups, text2, groupDefinition)); } public GroupRegistry WithItemAssignedToGroup(string itemType, string groupName) { if (string.IsNullOrWhiteSpace(itemType)) { throw new ArgumentException("Item type must not be empty.", "itemType"); } string text = GroupNameNormalizer.Normalize(groupName); if (text == null || !_groups.TryGetValue(text, out GroupDefinition _)) { throw new ArgumentException("Group '" + groupName + "' does not exist.", "groupName"); } Dictionary<string, GroupDefinition> dictionary = new Dictionary<string, GroupDefinition>(_groups); foreach (KeyValuePair<string, GroupDefinition> group in _groups) { if (group.Key != text && group.Value.ItemTypes.Contains(itemType)) { dictionary[group.Key] = group.Value.WithoutItem(itemType); } } dictionary[text] = dictionary[text].WithItem(itemType); return new GroupRegistry(dictionary); } public GroupRegistry WithItemUnassigned(string itemType) { Dictionary<string, GroupDefinition> dictionary = new Dictionary<string, GroupDefinition>(_groups); foreach (KeyValuePair<string, GroupDefinition> group in _groups) { if (group.Value.ItemTypes.Contains(itemType)) { dictionary[group.Key] = group.Value.WithoutItem(itemType); } } return new GroupRegistry(dictionary); } public GroupRegistry WithGroupItemsReplaced(string groupName, IEnumerable<string> itemTypes) { string text = GroupNameNormalizer.Normalize(groupName); if (text == null || !_groups.TryGetValue(text, out GroupDefinition value)) { throw new ArgumentException("Group '" + groupName + "' does not exist.", "groupName"); } HashSet<string> hashSet = new HashSet<string>(itemTypes, StringComparer.Ordinal); if (hashSet.Any(string.IsNullOrWhiteSpace)) { throw new ArgumentException("Item types must not be empty.", "itemTypes"); } Dictionary<string, GroupDefinition> dictionary = new Dictionary<string, GroupDefinition>(_groups); foreach (KeyValuePair<string, GroupDefinition> group in _groups) { if (!(group.Key != text)) { continue; } string[] array = group.Value.ItemTypes.Intersect(hashSet).ToArray(); if (array.Length != 0) { GroupDefinition groupDefinition = group.Value; string[] array2 = array; foreach (string itemType in array2) { groupDefinition = groupDefinition.WithoutItem(itemType); } dictionary[group.Key] = groupDefinition; } } dictionary[text] = GroupDefinition.Empty(value.Name, value.CreatorId, value.EvictMismatching, value.Id).WithItems(ItemTypeSet.From(hashSet)); return new GroupRegistry(dictionary); } public GroupRegistry WithEvictMismatching(string groupName, bool value) { string text = GroupNameNormalizer.Normalize(groupName); if (text == null || !_groups.TryGetValue(text, out GroupDefinition value2)) { throw new ArgumentException("Group '" + groupName + "' does not exist.", "groupName"); } if (value2.EvictMismatching != value) { return new GroupRegistry(With(_groups, text, value2.WithEvictMismatching(value))); } return this; } public string? GetGroupForItem(string itemType) { return _groups.Values.FirstOrDefault((GroupDefinition group) => group.ItemTypes.Contains(itemType))?.Name; } private static Dictionary<string, GroupDefinition> With(Dictionary<string, GroupDefinition> groups, string key, GroupDefinition group) { return new Dictionary<string, GroupDefinition>(groups) { [key] = group }; } private static Dictionary<string, GroupDefinition> Without(Dictionary<string, GroupDefinition> groups, string key) { Dictionary<string, GroupDefinition> dictionary = new Dictionary<string, GroupDefinition>(groups); dictionary.Remove(key); return dictionary; } } public enum GroupRegistryMutationKind { CreateGroup, DeleteGroup, RenameGroup, AssignItem, UnassignItem, ReplaceGroupItems, SetGroupEviction } public sealed class GroupRegistryMutation { public GroupRegistryMutationKind Kind { get; } public string? GroupName { get; } public string? NewGroupName { get; } public string? ItemType { get; } public IReadOnlyList<string>? ItemTypes { get; } public bool EvictMismatching { get; } public string? GroupId { get; } public long PlayerId { get; } private GroupRegistryMutation(GroupRegistryMutationKind kind, string? groupName, string? newGroupName, string? itemType, long playerId, IReadOnlyList<string>? itemTypes = null, bool evictMismatching = false, string? groupId = null) { GroupId = groupId; Kind = kind; GroupName = groupName; NewGroupName = newGroupName; ItemType = itemType; ItemTypes = itemTypes; EvictMismatching = evictMismatching; PlayerId = playerId; } public static GroupRegistryMutation CreateGroup(string groupName, long creatorId, string? groupId = null) { return new GroupRegistryMutation(GroupRegistryMutationKind.CreateGroup, groupName, null, null, creatorId, null, evictMismatching: false, groupId); } public static GroupRegistryMutation DeleteGroup(string groupName, long requesterId) { return new GroupRegistryMutation(GroupRegistryMutationKind.DeleteGroup, groupName, null, null, requesterId); } public static GroupRegistryMutation RenameGroup(string oldGroupName, string newGroupName, long requesterId) { return new GroupRegistryMutation(GroupRegistryMutationKind.RenameGroup, oldGroupName, newGroupName, null, requesterId); } public static GroupRegistryMutation AssignItem(string itemType, string groupName) { return new GroupRegistryMutation(GroupRegistryMutationKind.AssignItem, groupName, null, itemType, 0L); } public static GroupRegistryMutation UnassignItem(string itemType) { return new GroupRegistryMutation(GroupRegistryMutationKind.UnassignItem, null, null, itemType, 0L); } public static GroupRegistryMutation ReplaceGroupItems(string groupName, IEnumerable<string> itemTypes, long requesterId) { return new GroupRegistryMutation(GroupRegistryMutationKind.ReplaceGroupItems, groupName, null, null, requesterId, itemTypes.ToArray()); } public static GroupRegistryMutation SetGroupEviction(string groupName, bool value, long requesterId) { return new GroupRegistryMutation(GroupRegistryMutationKind.SetGroupEviction, groupName, null, null, requesterId, null, value); } public GroupRegistryMutation WithVerifiedPlayerId(long verifiedPlayerId) { return new GroupRegistryMutation(Kind, GroupName, NewGroupName, ItemType, verifiedPlayerId, ItemTypes, EvictMismatching, GroupId); } public bool IsRefusedFor(GroupRegistry registry) { switch (Kind) { case GroupRegistryMutationKind.CreateGroup: if (!string.Equals(GroupId, "builtin:locked", StringComparison.Ordinal)) { return BuiltInGroups.IsLocked(registry, GroupName); } return false; case GroupRegistryMutationKind.RenameGroup: if (!BuiltInGroups.IsLocked(registry, GroupName)) { return BuiltInGroups.IsLocked(registry, NewGroupName); } return true; case GroupRegistryMutationKind.DeleteGroup: case GroupRegistryMutationKind.AssignItem: case GroupRegistryMutationKind.SetGroupEviction: return BuiltInGroups.IsLocked(registry, GroupName); case GroupRegistryMutationKind.ReplaceGroupItems: if (BuiltInGroups.IsLocked(registry, GroupName)) { IReadOnlyList<string> itemTypes = ItemTypes; if (itemTypes != null) { return itemTypes.Count > 0; } return false; } return false; default: return false; } } public GroupRegistry Apply(GroupRegistry registry) { return Kind switch { GroupRegistryMutationKind.CreateGroup => registry.WithGroup(GroupName, PlayerId, GroupId), GroupRegistryMutationKind.DeleteGroup => registry.WithoutGroup(GroupName), GroupRegistryMutationKind.RenameGroup => registry.WithRenamedGroup(GroupName, NewGroupName), GroupRegistryMutationKind.AssignItem => registry.WithItemAssignedToGroup(ItemType, GroupName), GroupRegistryMutationKind.UnassignItem => registry.WithItemUnassigned(ItemType), GroupRegistryMutationKind.ReplaceGroupItems => registry.WithGroupItemsReplaced(GroupName, ItemTypes), GroupRegistryMutationKind.SetGroupEviction => registry.WithEvictMismatching(GroupName, EvictMismatching), _ => throw new InvalidOperationException($"Unknown mutation kind '{Kind}'."), }; } } public sealed class ItemPinCandidates { public IReadOnlyList<string> InChest { get; } public IReadOnlyList<string> CarriedOnly { get; } private ItemPinCandidates(IReadOnlyList<string> inChest, IReadOnlyList<string> carriedOnly) { InChest = inChest; CarriedOnly = carriedOnly; } public static ItemPinCandidates Build(IEnumerable<string> chestItemTypes, IEnumerable<string> carriedItemTypes) { List<string> list = DistinctSorted(chestItemTypes); HashSet<string> inChestSet = new HashSet<string>(list, StringComparer.Ordinal); List<string> carriedOnly = (from itemType in DistinctSorted(carriedItemTypes) where !inChestSet.Contains(itemType) select itemType).ToList(); return new ItemPinCandidates(list, carriedOnly); } private static List<string> DistinctSorted(IEnumerable<string> itemTypes) { return itemTypes.Where((string itemType) => !string.IsNullOrEmpty(itemType)).Distinct<string>(StringComparer.Ordinal).OrderBy<string, string>((string itemType) => itemType, StringComparer.Ordinal) .ToList(); } } public sealed class ItemTypeSet : IReadOnlyCollection<string>, IEnumerable<string>, IEnumerable { private readonly HashSet<string> _items; public static ItemTypeSet Empty { get; } = new ItemTypeSet(new HashSet<string>(StringComparer.Ordinal)); public int Count => _items.Count; private ItemTypeSet(HashSet<string> items) { _items = items; } public bool Contains(string itemType) { return _items.Contains(itemType); } internal ItemTypeSet Add(string itemType) { if (_items.Contains(itemType)) { return this; } return new ItemTypeSet(new HashSet<string>(_items, StringComparer.Ordinal) { itemType }); } internal ItemTypeSet Remove(string itemType) { if (!_items.Contains(itemType)) { return this; } HashSet<string> hashSet = new HashSet<string>(_items, StringComparer.Ordinal); hashSet.Remove(itemType); return new ItemTypeSet(hashSet); } internal static ItemTypeSet From(IEnumerable<string> itemTypes) { return new ItemTypeSet(new HashSet<string>(itemTypes, StringComparer.Ordinal)); } public IEnumerator<string> GetEnumerator() { return _items.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } }
plugins/StorageGroups.dll
Decompiled a month ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using SmartContainerRouting.Core; using SmartContainerRouting.Core.Model; using SmartContainerRouting.Core.Persistence; using SmartContainerRouting.Core.Routing; using SmartContainerRouting.Core.Text; using SmartContainerRouting.Core.Versioning; using SmartContainerRouting.Plugin.Persistence; using SmartContainerRouting.Plugin.Routing; using SmartContainerRouting.Plugin.Ui; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("jonasd")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.2.3.0")] [assembly: AssemblyInformationalVersion("0.2.3+7859651d1575cac84e5a82ef3b2ffa36c1170ea5")] [assembly: AssemblyProduct("Storage Groups")] [assembly: AssemblyTitle("StorageGroups")] [assembly: AssemblyVersion("0.2.3.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 SmartContainerRouting.Plugin { [BepInPlugin("com.jonasd.valheim.storagegroups", "Storage Groups", "0.2.3")] [BepInIncompatibility("flueno.SmartContainers")] public sealed class SmartContainerRoutingPlugin : BaseUnityPlugin { public const string PluginGuid = "com.jonasd.valheim.storagegroups"; public const string PluginName = "Storage Groups"; public const string PluginVersion = "0.2.3"; private const string SmartContainersGuid = "flueno.SmartContainers"; private Harmony? _harmony; private string? _disabledReason; private bool _disabledMessageShown; private float _nextGuardedErrorLog; internal static ManualLogSource Log { get; private set; } private void Awake() { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; _disabledReason = CheckCoreVersion(); if (_disabledReason != null) { Log.LogError((object)_disabledReason); return; } _harmony = new Harmony("com.jonasd.valheim.storagegroups"); _harmony.PatchAll(); if (Localization.instance != null) { LocalizationPatch.AddWord(Localization.instance, "scr_container_name", "Container name"); LocalizationPatch.AddWord(Localization.instance, "scr_group_name", "Group name"); } RoutingConfig.RangeMeters = ((BaseUnityPlugin)this).Config.Bind<float>("Routing", "RangeMeters", 10f, "Maximum distance, in meters, between a container that just received an item and an eligible destination container for the item to route there.").Value; RoutingConfig.ShowRoutingMessages = ((BaseUnityPlugin)this).Config.Bind<bool>("UI", "ShowRoutingMessages", false, "Show a short message for every item routed near you, and log the details. Also toggled from the Storage Groups menu."); RoutingConfig.WarnAboutPlayersWithoutMod = ((BaseUnityPlugin)this).Config.Bind<bool>("UI", "WarnAboutPlayersWithoutMod", true, "Show a message when a player joins without Storage Groups. Chests their game holds can't sort while they're around."); GroupRegistryAccess.AnchorZdoProvider = ModDataAnchorProvider.GetOrCreate; ((Component)this).gameObject.AddComponent<GroupManagementController>(); ((Component)this).gameObject.AddComponent<GroupItemsController>(); } private void Update() { if (_disabledReason != null) { ShowDisabledMessageOnce(); return; } ModDataAnchorProvider.GetOrCreate(); ContainerRoutingService.ObserveGroupRegistry(); ContainerRoutingService.ProcessPendingInventoryChanges(); ModPresence.Tick(); RouteRelay.Tick(); RoutingNotifier.Flush(); RunGuarded(GroupRegistryRpc.EnsureBuiltInGroups, "ensuring the Locked group"); RunGuarded(VersionCheck.Tick, "the version check"); } private void RunGuarded(Action action, string what) { try { action(); } catch (Exception arg) { if (Time.unscaledTime >= _nextGuardedErrorLog) { _nextGuardedErrorLog = Time.unscaledTime + 30f; Log.LogError((object)$"Storage Groups: error in {what}: {arg}"); } } } private static string? CheckCoreVersion() { string text = SafeCoreLocation(); string text2; try { text2 = ReadCoreVersion(); } catch (Exception) { text2 = "older than 0.2.0"; } if (!(text2 == "0.2.3")) { return "Storage Groups 0.2.3 is turned off: an old StorageGroups.Core.dll (" + text2 + ") was loaded from " + text + ". Delete any loose StorageGroups*.dll directly in BepInEx/plugins (keep the mod's own folder) and restart the game."; } return null; } [MethodImpl(MethodImplOptions.NoInlining)] private static string ReadCoreVersion() { return CoreInfo.Version; } [MethodImpl(MethodImplOptions.NoInlining)] private static string SafeCoreLocation() { try { return typeof(GroupRegistry).Assembly.Location; } catch (Exception) { return "an unknown location"; } } private void ShowDisabledMessageOnce() { if (!_disabledMessageShown && !((Object)(object)Player.m_localPlayer == (Object)null) && !((Object)(object)MessageHud.instance == (Object)null)) { _disabledMessageShown = true; MessageHud.instance.ShowMessage((MessageType)2, "Storage Groups is turned off: an old copy of the mod was found.\nSee the BepInEx log (LogOutput.log) for the file to delete.", 0, (Sprite)null, false, true); } } private void OnDestroy() { Harmony? harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } } namespace SmartContainerRouting.Plugin.Ui { [HarmonyPatch(typeof(Container), "Awake")] internal static class ContainerAwakePatch { private static void Postfix(Container __instance) { if (ContainerKindFilter.IsRoutable(__instance) && (Object)(object)((Component)__instance).GetComponent<ContainerRoutingBehaviour>() == (Object)null) { ((Component)__instance).gameObject.AddComponent<ContainerRoutingBehaviour>(); } } } [HarmonyPatch(typeof(Container), "GetHoverText")] internal static class ContainerHoverTextPatch { private static void Postfix(Container __instance, ref string __result) { ContainerRoutingBehaviour component = ((Component)__instance).GetComponent<ContainerRoutingBehaviour>(); if (!((Object)(object)component == (Object)null)) { string text = ContainerHoverText.BuildExtraLine(component.GetSettings(), (Func<string, string>)GroupRegistryAccess.DisplayName, (Func<string, bool>)GroupRegistryAccess.IsEvicting, ContainerAccessGuard.GetCoveringWardIds(__instance).Count > 0); if (!string.IsNullOrEmpty(text)) { __result = __result + "\n" + text; } } } } [HarmonyPatch(typeof(Container), "Load")] internal static class ContainerLoadPatch { public static bool IsLoading { get; private set; } private static void Prefix() { IsLoading = true; } private static void Postfix(Container __instance, bool __result) { IsLoading = false; ContainerRoutingBehaviour component = ((Component)__instance).GetComponent<ContainerRoutingBehaviour>(); if ((Object)(object)component == (Object)null) { return; } component.RefreshKnownItems(); if (__result) { bool flag = !component.IsRoutingReady; component.MarkRoutingReady(); if (flag) { ContainerRoutingService.QueueCapacityAvailable(component); } if (GroupRegistryAccess.TryLoad(out GroupRegistry registry)) { component.ReconcileGroup(registry); } } } private static Exception Finalizer(Exception __exception) { IsLoading = false; return __exception; } } internal sealed class ContainerPanelController : MonoBehaviour { private static ContainerPanelController? _instance; private const float TopInset = 8f; private const float Gap = 8f; private const float ColumnWidth = 140f; private const float ControlHeight = 24f; private const float PanelArtOverhang = 10f; private static readonly string[] PanelArtPaths = new string[2] { "Bkg", "selected_frame/selected (1)" }; private static readonly List<RectTransform> ScratchRects = new List<RectTransform>(); private static readonly Vector3[] ScratchCorners = (Vector3[])(object)new Vector3[4]; private const int SettleFrames = 10; private RectTransform? _row; private TMP_Text? _groupButtonLabel; private Button? _newGroupButton; private Button? _addButton; private Button? _editItemsButton; private Toggle? _pauseToggle; private Toggle? _priorityToggle; private Button? _groupButton; private InventoryGui? _gui; private ContainerRoutingBehaviour? _current; private ContainerSettings? _lastRefreshedSettings; private string? _lastRefreshedGroupDisplayName; private bool _layoutDirty; private int _framesSinceShown; public static ContainerRoutingBehaviour? Current { get { if (!((Object)(object)_instance != (Object)null)) { return null; } return _instance._current; } } public static void ShowFor(InventoryGui gui, Container container) { if (!ContainerKindFilter.IsRoutable(container)) { _instance?.Hide(); return; } ContainerRoutingBehaviour containerRoutingBehaviour = ((Component)container).GetComponent<ContainerRoutingBehaviour>(); if ((Object)(object)containerRoutingBehaviour == (Object)null) { containerRoutingBehaviour = ((Component)container).gameObject.AddComponent<ContainerRoutingBehaviour>(); } ContainerPanelController orCreate = GetOrCreate(gui); if (orCreate._current != containerRoutingBehaviour) { orCreate.ReleaseHold(); } orCreate._gui = gui; orCreate._current = containerRoutingBehaviour; orCreate._framesSinceShown = 0; RectTransform? row = orCreate._row; if (row != null) { ((Component)row).gameObject.SetActive(true); } orCreate.Refresh(); orCreate.PositionRow(); } private void Hide() { ReleaseHold(); _current = null; RectTransform? row = _row; if (row != null) { ((Component)row).gameObject.SetActive(false); } } private static ContainerPanelController GetOrCreate(InventoryGui gui) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Expected O, but got Unknown //IL_0082: 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) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_instance != (Object)null) { return _instance; } GameObject val = new GameObject("SCR_ContainerControls", new Type[3] { typeof(RectTransform), typeof(VerticalLayoutGroup), typeof(ContentSizeFitter) }); val.transform.SetParent((Transform)(object)gui.m_container, false); ContentSizeFitter component = val.GetComponent<ContentSizeFitter>(); component.horizontalFit = (FitMode)0; component.verticalFit = (FitMode)2; RectTransform component2 = val.GetComponent<RectTransform>(); component2.anchorMin = new Vector2(1f, 1f); component2.anchorMax = new Vector2(1f, 1f); component2.pivot = new Vector2(0f, 1f); component2.sizeDelta = new Vector2(140f, component2.sizeDelta.y); _instance = val.AddComponent<ContainerPanelController>(); _instance._row = component2; _instance.Build(val.GetComponent<VerticalLayoutGroup>()); return _instance; } private void Build(VerticalLayoutGroup layout) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Expected O, but got Unknown //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Expected O, but got Unknown //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Expected O, but got Unknown //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Expected O, but got Unknown ((HorizontalOrVerticalLayoutGroup)layout).spacing = 4f; ((HorizontalOrVerticalLayoutGroup)layout).childControlWidth = true; ((HorizontalOrVerticalLayoutGroup)layout).childControlHeight = true; ((HorizontalOrVerticalLayoutGroup)layout).childForceExpandWidth = true; ((HorizontalOrVerticalLayoutGroup)layout).childForceExpandHeight = false; ((LayoutGroup)layout).childAlignment = (TextAnchor)0; Button val = (_groupButton = UiFactory.CreateButton(((Component)this).transform, string.Empty, (UnityAction)delegate { GroupManagementController.Toggle(_current); })); _groupButtonLabel = ((Component)val).GetComponentInChildren<TMP_Text>(); ((Component)val).gameObject.AddComponent<HoverTooltip>().Initialize(DescribeGroupButton); _newGroupButton = UiFactory.CreateButton(((Component)this).transform, "New Group From Contents", new UnityAction(OnNewGroupClicked)); ((Component)_newGroupButton).gameObject.SetActive(false); ((Component)_newGroupButton).gameObject.AddComponent<HoverTooltip>().Initialize(delegate { ContainerRoutingBehaviour? current = _current; if (current != null) { Inventory? inventory = current.GetInventory(); if (((inventory != null) ? new int?(inventory.GetAllItems().Count) : ((int?)null)) > 0) { return "Makes a group from this chest's items\nand puts this chest in it."; } } return "Put items in the chest first."; }); _addButton = UiFactory.CreateButton(((Component)this).transform, "Add Contents", new UnityAction(OnAddContentsClicked)); ((Component)_addButton).gameObject.SetActive(false); ((Component)_addButton).gameObject.AddComponent<GroupContentsTooltip>().Initialize(delegate { ContainerRoutingBehaviour? current = _current; return (current == null) ? null : current.GetSettings().GroupName; }, null, "Adds this chest's items to the group"); _editItemsButton = UiFactory.CreateButton(((Component)this).transform, "Edit Items...", (UnityAction)delegate { GroupItemsController.Toggle(_current); }); ((Component)_editItemsButton).gameObject.SetActive(false); _pauseToggle = UiFactory.CreateToggle(((Component)this).transform, "Pause sorting", initialValue: false, delegate(bool value) { _current?.SetHeld(value); }); ((Component)_pauseToggle).gameObject.AddComponent<HoverTooltip>().Initialize(() => "Nothing moves in or out\nof this chest while ticked.\nTurns off when you close it."); _priorityToggle = UiFactory.CreateToggle(((Component)this).transform, "Priority", initialValue: false, delegate(bool value) { _current?.SetPriority(value); }); ((Component)_priorityToggle).gameObject.SetActive(false); ((Component)_priorityToggle).gameObject.AddComponent<HoverTooltip>().Initialize(() => "This chest fills first.\nThe group's other chests nearby\nmove items into it when it has room."); LayoutElement[] componentsInChildren = ((Component)this).GetComponentsInChildren<LayoutElement>(true); for (int num = 0; num < componentsInChildren.Length; num++) { componentsInChildren[num].preferredHeight = 24f; } } private void PositionRow() { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_023a: Unknown result type (might be due to invalid IL or missing references) //IL_0246: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_row == (Object)null || (Object)(object)_gui == (Object)null) { return; } RectTransform container = _gui.m_container; if (container == null) { return; } if (_layoutDirty || _framesSinceShown <= 10) { LayoutRebuilder.ForceRebuildLayoutImmediate(_row); _layoutDirty = false; } Vector3 lossyScale = ((Transform)container).lossyScale; if (lossyScale.x <= 0f || lossyScale.y <= 0f) { return; } (float left, float right, float bottom, float top) tuple = WorldBounds(container, includeChildren: false); float item = tuple.right; float item2 = tuple.top; float num = 8f * lossyScale.x; float num2 = 8f * lossyScale.y; float num3 = Mathf.Max(Mathf.Max(Mathf.Max(PanelArtRightEdge(container, item, lossyScale.x), TitleRightEdge(_gui.m_containerName)), RightEdge((Component?)(object)_gui.m_takeAllButton)), RightEdge((Component?)(object)_gui.m_stackAllButton)) + num; float num4 = item2 - 8f * lossyScale.y; Rect rect = _row.rect; float num5 = num4 - ((Rect)(ref rect)).height * lossyScale.y; float num6 = num3 + 140f * lossyScale.x; if ((Object)(object)_gui.m_containerWeight != (Object)null && ((Component)_gui.m_containerWeight).gameObject.activeInHierarchy) { Transform parent = _gui.m_containerWeight.transform.parent; RectTransform val = (RectTransform)(object)((parent is RectTransform) ? parent : null); var (num7, num8, _, num9) = WorldBounds(((Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)container) ? val : _gui.m_containerWeight.rectTransform, includeChildren: true); if (num3 < num8 && num7 < num6 && num5 < num9 + num2) { num3 = Mathf.Max(num3, num8 + num); } } Canvas componentInParent = ((Component)container).GetComponentInParent<Canvas>(); if (componentInParent != null) { Transform transform = ((Component)componentInParent.rootCanvas).transform; RectTransform val2 = (RectTransform)(object)((transform is RectTransform) ? transform : null); if (val2 != null) { float item3 = WorldBounds(val2, includeChildren: false).right; num3 = Mathf.Min(num3, item3 - num - 140f * lossyScale.x); } } _row.anchoredPosition = new Vector2((num3 - item) / lossyScale.x, -8f); } private static float PanelArtRightEdge(RectTransform panel, float panelRight, float scaleX) { float num = panelRight + 10f * scaleX; string[] panelArtPaths = PanelArtPaths; foreach (string text in panelArtPaths) { num = Mathf.Max(num, RightEdge((Component?)(object)((Transform)panel).Find(text))); } return num; } private static float RightEdge(Component? element) { if ((Object)(object)element != (Object)null && element.gameObject.activeInHierarchy) { Transform transform = element.transform; RectTransform val = (RectTransform)(object)((transform is RectTransform) ? transform : null); if (val != null) { return WorldBounds(val, includeChildren: true).right; } } return float.NegativeInfinity; } private static float TitleRightEdge(TMP_Text? title) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0053: 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) if ((Object)(object)title == (Object)null || !((Component)title).gameObject.activeInHierarchy) { return float.NegativeInfinity; } float item = WorldBounds(title.rectTransform, includeChildren: false).right; Bounds textBounds = title.textBounds; if (!(((Bounds)(ref textBounds)).size.x > 0f)) { return item; } return Mathf.Max(item, title.transform.TransformPoint(((Bounds)(ref textBounds)).max).x); } private static (float left, float right, float bottom, float top) WorldBounds(RectTransform rect, bool includeChildren) { rect.GetWorldCorners(ScratchCorners); float num = ScratchCorners[0].x; float num2 = ScratchCorners[0].y; float num3 = ScratchCorners[2].x; float num4 = ScratchCorners[2].y; if (!includeChildren) { return (left: num, right: num3, bottom: num2, top: num4); } ((Component)rect).GetComponentsInChildren<RectTransform>(false, ScratchRects); foreach (RectTransform scratchRect in ScratchRects) { scratchRect.GetWorldCorners(ScratchCorners); num = Mathf.Min(num, ScratchCorners[0].x); num2 = Mathf.Min(num2, ScratchCorners[0].y); num3 = Mathf.Max(num3, ScratchCorners[2].x); num4 = Mathf.Max(num4, ScratchCorners[2].y); } ScratchRects.Clear(); return (left: num, right: num3, bottom: num2, top: num4); } private void OnDisable() { ReleaseHold(); } private void ReleaseHold() { _current?.SetHeld(held: false); Toggle? pauseToggle = _pauseToggle; if (pauseToggle != null) { pauseToggle.SetIsOnWithoutNotify(false); } } private string? DescribeGroupButton() { ContainerRoutingBehaviour? current = _current; string text = ((current != null) ? current.GetSettings().GroupName : null); if (text != null && GroupRegistryAccess.TryLoad(out GroupRegistry registry)) { GroupDefinition val = registry.GetGroup(text); if (val != null) { if (BuiltInGroups.IsLocked(val)) { string lockedWarning = VersionCheck.LockedWarning; return "Group: Locked\nNothing moves in or out of this chest.\nPick another group to unlock it." + ((lockedWarning != null) ? ("\n\n" + lockedWarning) : string.Empty); } string text2 = GroupButtonHover.Describe(val.Name, ((IEnumerable<string>)val.ItemTypes).Select(ItemTypeResolver.GetDisplayName)); string text3 = DescribeRoutes(); return text2 + ((text3 != null) ? ("\n\n" + text3) : string.Empty); } } string text4 = GroupButtonHover.Describe((string)null, Enumerable.Empty<string>()); string text5 = DescribeRoutes(); return text4 + ((text5 != null) ? ("\n\n" + text5) : string.Empty); } private string? DescribeRoutes() { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Expected O, but got Unknown ContainerRoutingBehaviour current = _current; if ((Object)(object)current == (Object)null || !current.IsValid) { return null; } if (!GroupRegistryAccess.TryLoad(out GroupRegistry registry)) { return "Groups not loaded yet"; } Position position = current.GetPosition(); List<Neighbour> list = new List<Neighbour>(); foreach (ContainerRoutingBehaviour item in ContainerRegistry.All) { if (!((Object)(object)item == (Object)null) && item != current && item.IsValid && item.IsEligibleRoutingDestination && !item.IsHeld) { ContainerRoutingCandidate val = item.ToCandidate(); list.Add(new Neighbour(val.Settings, ((Position)(ref position)).DistanceTo(val.Position), val.FreeCapacity > 0 || val.PartialStackCapacity.Values.Any((int space) => space > 0), current.CanRouteTo(item))); } } return RouteSummary.Describe(current.GetSettings(), registry, (IEnumerable<Neighbour>)list, RoutingConfig.RangeMeters, current.IsHeld); } private void OnNewGroupClicked() { ContainerRoutingBehaviour current = _current; if ((Object)(object)current == (Object)null || !GroupRegistryAccess.TryLoad(out GroupRegistry registry)) { return; } Inventory? inventory = current.GetInventory(); List<ItemData> source = ((inventory != null) ? inventory.GetAllItems() : null) ?? new List<ItemData>(); string text = GroupNameSuggestion.Suggest(source.Select((ItemData item) => new KeyValuePair<string, int>(ItemTypeResolver.GetDisplayName(ItemTypeResolver.GetItemType(item)), item.m_stack)), registry); if (text == null) { Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)2, "Put items in the chest first", 0, (Sprite)null, false); } } else { IReadOnlyList<string> inChest = ItemPinCandidates.Build(source.Select(ItemTypeResolver.GetItemType), Enumerable.Empty<string>()).InChest; GroupManagementController.NewGroupFromContents(current, text, inChest); } } private static void SetShown(Component? control, bool shown) { if ((Object)(object)control != (Object)null && control.gameObject.activeSelf != shown) { control.gameObject.SetActive(shown); } } private static void SetShown(Button? button, bool shown, bool interactable) { if (!((Object)(object)button == (Object)null)) { if (((Component)button).gameObject.activeSelf != shown) { ((Component)button).gameObject.SetActive(shown); } ((Selectable)button).interactable = interactable; } } private void OnAddContentsClicked() { ContainerRoutingBehaviour current = _current; string text = ((current != null) ? current.GetSettings().GroupName : null); if ((Object)(object)current == (Object)null || text == null || !GroupRegistryAccess.TryLoad(out GroupRegistry registry)) { return; } Inventory? inventory = current.GetInventory(); IEnumerable<string> enumerable = ((inventory != null) ? inventory.GetAllItems().Select(ItemTypeResolver.GetItemType) : null) ?? Enumerable.Empty<string>(); IReadOnlyList<string> readOnlyList = GroupItemAdditions.Missing(registry, text, enumerable); if (readOnlyList.Count == 0) { Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)2, "Everything in this chest is already in the group", 0, (Sprite)null, false); } return; } string text2 = GroupItemTransfers.Describe(registry, text, (IEnumerable<string>)readOnlyList, (Func<string, string>)ItemTypeResolver.GetDisplayName); foreach (string item in readOnlyList) { GroupRegistryRpc.Request(GroupRegistryMutation.AssignItem(item, text)); } Button? addButton = _addButton; if (addButton != null) { ((Component)addButton).GetComponent<GroupContentsTooltip>()?.Refresh(); } if (text2 != null) { Player localPlayer2 = Player.m_localPlayer; if (localPlayer2 != null) { ((Character)localPlayer2).Message((MessageType)2, text2, 0, (Sprite)null, false); } } } private void Refresh() { if ((Object)(object)_current == (Object)null) { return; } ContainerSettings settings = _current.GetSettings(); string text = GroupRegistryAccess.DisplayName(settings.GroupName); bool flag = settings.GroupName != null; GroupRegistry registry; bool flag2 = GroupRegistryAccess.TryLoad(out registry); bool flag3 = flag2 && BuiltInGroups.IsLocked(registry, settings.GroupName); bool interactable = flag && !flag3 && (Object)(object)Player.m_localPlayer != (Object)null && flag2 && registry.HasGroup(settings.GroupName); Button? newGroupButton = _newGroupButton; bool shown = !flag; int num; if ((Object)(object)Player.m_localPlayer != (Object)null) { Inventory? inventory = _current.GetInventory(); num = ((inventory != null && inventory.GetAllItems().Count > 0) ? 1 : 0); } else { num = 0; } SetShown(newGroupButton, shown, (byte)((uint)num & (flag2 ? 1u : 0u)) != 0); SetShown(_addButton, flag && !flag3, interactable); SetShown(_editItemsButton, flag && !flag3, interactable); SetShown((Component?)(object)_pauseToggle, !flag3); SetShown((Component?)(object)_priorityToggle, flag && !flag3); if ((Object)(object)_groupButton != (Object)null) { UiFactory.SetLockedStyle(_groupButton, flag3); } if (!settings.Equals(_lastRefreshedSettings) || !(text == _lastRefreshedGroupDisplayName)) { _lastRefreshedSettings = settings; _lastRefreshedGroupDisplayName = text; _layoutDirty = true; if ((Object)(object)_groupButtonLabel != (Object)null) { _groupButtonLabel.text = "Group: " + (flag3 ? "Locked" : (text ?? "(none)")); } Toggle? priorityToggle = _priorityToggle; if (priorityToggle != null) { priorityToggle.SetIsOnWithoutNotify(settings.Priority); } } } private void Update() { Refresh(); _framesSinceShown++; PositionRow(); } } public sealed class ContainerRoutingBehaviour : MonoBehaviour { private sealed class ReferenceItemComparer : IEqualityComparer<ItemData> { public static readonly ReferenceItemComparer Instance = new ReferenceItemComparer(); public bool Equals(ItemData x, ItemData y) { return x == y; } public int GetHashCode(ItemData obj) { return RuntimeHelpers.GetHashCode(obj); } } private static readonly MethodInfo ContainerLoadMethod = AccessTools.Method(typeof(Container), "Load", (Type[])null, (Type[])null); private ZNetView? _nview; private Container? _container; private List<ItemData> _knownItems = new List<ItemData>(); private List<ItemTypeQuantity> _knownStacks = new List<ItemTypeQuantity>(); private ContainerRoutingCandidate? _knownCapacity; private bool _wasOwner; private Dictionary<ItemData, int> _knownStackSizes = new Dictionary<ItemData, int>(ReferenceItemComparer.Instance); public bool IsValid { get { if ((Object)(object)_nview != (Object)null) { return _nview.IsValid(); } return false; } } public bool IsOwner { get { if ((Object)(object)_nview != (Object)null) { return _nview.IsOwner(); } return false; } } public bool IsEligibleRoutingDestination { get { if ((Object)(object)_container != (Object)null) { return ContainerAccessGuard.IsEligibleRoutingDestination(_container); } return false; } } public bool IsRoutingReady { get; private set; } public bool IsHeld { get; private set; } public bool IsLocked { get { if (GroupRegistryAccess.TryLoad(out GroupRegistry registry)) { return BuiltInGroups.IsLocked(registry, GetSettings().GroupName); } return false; } } public bool IsLockedOrUnknown { get { if (GroupRegistryAccess.TryLoad(out GroupRegistry registry)) { return BuiltInGroups.IsLocked(registry, GetSettings().GroupName); } return true; } } public string RoutingId { get { if (!IsValid) { return ((Object)this).GetInstanceID().ToString(); } return ((object)Unsafe.As<ZDOID, ZDOID>(ref _nview.GetZDO().m_uid)/*cast due to .constrained prefix*/).ToString(); } } public ZDOID? ZdoId { get { //IL_001d: Unknown result type (might be due to invalid IL or missing references) if (!IsValid) { return null; } return _nview.GetZDO().m_uid; } } private void Awake() { _container = ((Component)this).GetComponent<Container>(); _nview = (((Object)(object)_container != (Object)null && (Object)(object)_container.m_rootObjectOverride != (Object)null) ? _container.m_rootObjectOverride : ((Component)this).GetComponent<ZNetView>()); _wasOwner = IsOwner; ContainerRegistry.Register(this); if ((Object)(object)_nview != (Object)null && _nview.IsValid()) { RouteRelay.RegisterChest(_nview, this); } Inventory inventory = GetInventory(); if (inventory != null) { SnapshotKnownItems(inventory.GetAllItems()); _knownCapacity = ToCandidate(); inventory.m_onChanged = (Action)Delegate.Combine(inventory.m_onChanged, new Action(OnInventoryChanged)); } } private void OnDestroy() { ContainerRegistry.Unregister(this); Inventory inventory = GetInventory(); if (inventory != null) { inventory.m_onChanged = (Action)Delegate.Remove(inventory.m_onChanged, new Action(OnInventoryChanged)); } } internal void RefreshIfOwnershipJustClaimed() { bool isOwner = IsOwner; if (isOwner && !_wasOwner && (Object)(object)_container != (Object)null) { ContainerLoadMethod.Invoke(_container, null); } _wasOwner = isOwner; } public List<string> CoveringWardIds() { if (!((Object)(object)_container != (Object)null)) { return new List<string>(); } return ContainerAccessGuard.GetCoveringWardIds(_container); } public bool CanRouteTo(ContainerRoutingBehaviour destination) { if ((Object)(object)_container != (Object)null && (Object)(object)destination._container != (Object)null) { return ContainerAccessGuard.CanRouteBetween(_container, destination._container); } return false; } public void SetHeld(bool held) { if (IsHeld != held) { IsHeld = held; if (!held) { ContainerRoutingService.QueueReroute(this); ContainerRoutingService.QueueCapacityAvailable(this); } } } internal void MarkRoutingReady() { IsRoutingReady = true; } public Inventory? GetInventory() { if (!((Object)(object)_container != (Object)null)) { return null; } return _container.GetInventory(); } public Position GetPosition() { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //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_0032: 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) Vector3 val = (IsValid ? _nview.GetZDO().GetPosition() : ((Component)this).transform.position); return new Position(val.x, val.y, val.z); } public ContainerRoutingCandidate ToCandidate() { //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Expected O, but got Unknown Inventory inventory = GetInventory(); Dictionary<string, int> dictionary = new Dictionary<string, int>(); if (inventory != null) { foreach (ItemData allItem in inventory.GetAllItems()) { int num = allItem.m_shared.m_maxStackSize - allItem.m_stack; if (num > 0) { string itemType = ItemTypeResolver.GetItemType(allItem); dictionary[itemType] = (dictionary.TryGetValue(itemType, out var value) ? (value + num) : num); } } } return new ContainerRoutingCandidate(RoutingId, GetPosition(), (inventory != null) ? inventory.GetEmptySlots() : 0, GetSettings(), (IReadOnlyDictionary<string, int>)dictionary); } public void RefreshKnownItems() { Inventory inventory = GetInventory(); SnapshotKnownItems((inventory != null) ? inventory.GetAllItems() : new List<ItemData>()); _knownCapacity = ToCandidate(); } private void SnapshotKnownItems(List<ItemData> items) { _knownItems = new List<ItemData>(items); _knownStacks = items.Select(ToStack).ToList(); _knownStackSizes = new Dictionary<ItemData, int>(ReferenceItemComparer.Instance); foreach (ItemData item in items) { _knownStackSizes[item] = item.m_stack; } } private void OnInventoryChanged() { try { if (!ContainerRoutingService.IsRouting && !ContainerLoadPatch.IsLoading) { ContainerRoutingService.QueueInventoryChanged(this); } HandleInventoryChanged(); } catch (Exception arg) { SmartContainerRoutingPlugin.Log.LogError((object)$"Inventory-change routing hook threw and was suppressed to avoid corrupting the in-flight inventory operation: {arg}"); } } private void HandleInventoryChanged() { if (ContainerRoutingService.IsRouting || ContainerLoadPatch.IsLoading) { return; } Inventory inventory = GetInventory(); if (inventory == null) { return; } List<ItemData> allItems = inventory.GetAllItems(); ContainerRoutingCandidate val = ToCandidate(); bool flag = _knownCapacity != null && val.HasMoreCapacityThan(_knownCapacity); List<ItemData> list = allItems.Where((ItemData item2) => !_knownItems.Contains(item2)).ToList(); List<ItemTypeQuantity> knownStacks = _knownStacks; List<ItemTypeQuantity> list2 = allItems.Select(ToStack).ToList(); List<(ItemData, int)> list3 = new List<(ItemData, int)>(); foreach (ItemData item2 in allItems) { if (_knownStackSizes.TryGetValue(item2, out var value) && item2.m_stack > value) { list3.Add((item2, item2.m_stack - value)); } } SnapshotKnownItems(allItems); _knownCapacity = val; Dictionary<string, int> dictionary = NewArrivalFilter.ComputeBudget((IReadOnlyList<ItemTypeQuantity>)knownStacks, (IReadOnlyList<ItemTypeQuantity>)list2); IReadOnlyList<ItemData> readOnlyList = NewArrivalFilter.SelectWithinBudget<ItemData>(dictionary, (IReadOnlyList<ItemData>)list, (Func<ItemData, ItemTypeQuantity>)ToStack); IReadOnlyList<(ItemData, int)> readOnlyList2 = NewArrivalFilter.SelectWithinBudget<(ItemData, int)>(dictionary, (IReadOnlyList<(ItemData, int)>)list3, (Func<(ItemData, int), ItemTypeQuantity>)(((ItemData Item, int Delta) growth) => new ItemTypeQuantity(ItemTypeResolver.GetItemType(growth.Item), growth.Delta))); LogArrivalsInStrictChest(readOnlyList.Select(ToStack).Concat(readOnlyList2.Select<(ItemData, int), ItemTypeQuantity>(((ItemData Item, int Delta) growth) => new ItemTypeQuantity(ItemTypeResolver.GetItemType(growth.Item), growth.Delta)))); foreach (ItemData item3 in readOnlyList) { ContainerRoutingService.TryRoute(this, item3, item3.m_stack); } foreach (var (item, amount) in readOnlyList2) { ContainerRoutingService.TryRoute(this, item, amount); } if (flag) { ContainerRoutingService.QueueCapacityAvailable(this); } } private void LogArrivalsInStrictChest(IEnumerable<ItemTypeQuantity> arrivals) { if (!IsOwner || !GroupRegistryAccess.TryLoad(out GroupRegistry registry)) { return; } ContainerSettings settings = GetSettings(); foreach (ItemTypeQuantity arrival in arrivals) { if (ContainerRouter.WouldEvict(settings, registry, arrival.ItemType)) { SmartContainerRoutingPlugin.Log.LogInfo((object)$"[Routing] {arrival.Stack} {arrival.ItemType} put into Strict chest {RoutingId} (group {settings.GroupName}) by hand or by another mod."); } } } private static ItemTypeQuantity ToStack(ItemData item) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown return new ItemTypeQuantity(ItemTypeResolver.GetItemType(item), item.m_stack); } public ContainerSettings GetSettings() { if (!IsValid) { return ContainerSettings.Default; } return ContainerZdoStore.Load(_nview.GetZDO()); } public void SaveSettings(ContainerSettings settings, string? knownGroupId = null) { if (ContainerOwnershipGuard.CanWrite(_nview)) { ContainerSettings settings2 = GetSettings(); ContainerZdoStore.Save(_nview.GetZDO(), settings, knownGroupId); if (settings2.GroupName != settings.GroupName || settings2.Priority != settings.Priority) { ContainerRoutingService.QueueReroute(this); ContainerRoutingService.QueueCapacityAvailable(this); } } else { if (!IsValid) { return; } GroupRegistry registry; string text = GroupReference.IdFor(settings.GroupName, GroupRegistryAccess.TryLoad(out registry) ? registry : null); if (!RouteRelay.RequestSettings(this, settings, (text.Length > 0) ? text : (knownGroupId ?? string.Empty))) { Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)2, "Can't change this chest right now:\nanother player without Storage Groups holds it.", 0, (Sprite)null, false); } } } } public void SetGroup(string? groupName, string? knownGroupId = null) { SaveSettings(GetSettings().WithGroupName(groupName), knownGroupId); } public void SetPriority(bool priority) { SaveSettings(GetSettings().WithPriority(priority)); } public void ReconcileGroup(GroupRegistry groups) { if (!IsOwner) { return; } string groupName = GetSettings().GroupName; if (groupName != null && !groups.HasGroup(groupName)) { if (BuiltInGroups.IsLocked(groups, groupName)) { GroupRegistryRpc.RequestLockedGroup(); } } else if (groupName != null && ContainerZdoStore.LacksGroupId(_nview.GetZDO())) { SetGroup(groupName); } } } internal sealed class DelegateTextReceiver : TextReceiver { private readonly Func<string> _getText; private readonly Action<string> _setText; public DelegateTextReceiver(Func<string> getText, Action<string> setText) { _getText = getText; _setText = setText; } public string GetText() { return _getText(); } public void SetText(string text) { _setText(text); } } internal sealed class GroupContentsTooltip : MonoBehaviour, IPointerEnterHandler, IEventSystemHandler, IPointerExitHandler { private const int VisibleItemLimit = 8; private const float RegistryCheckIntervalSeconds = 0.1f; private bool _hovered; private string? _lastRegistryBlob; private float _nextRegistryCheck; private Func<string?>? _groupName; private Transform? _displayParent; private GameObject? _panel; private string? _actionHint; public void Initialize(Func<string?> groupName, Transform? displayParent = null, string? actionHint = null) { _groupName = groupName; _displayParent = displayParent; _actionHint = actionHint; } public void OnPointerEnter(PointerEventData eventData) { _hovered = true; Refresh(); } public void OnPointerExit(PointerEventData eventData) { _hovered = false; Hide(); } private void OnDisable() { _hovered = false; Hide(); } private void Update() { if (_hovered && !(Time.unscaledTime < _nextRegistryCheck)) { _nextRegistryCheck = Time.unscaledTime + 0.1f; if (GroupRegistryAccess.CurrentBlob() != _lastRegistryBlob) { Refresh(); } } } public void Refresh() { //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Unknown result type (might be due to invalid IL or missing references) //IL_020b: Unknown result type (might be due to invalid IL or missing references) //IL_0210: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Unknown result type (might be due to invalid IL or missing references) //IL_0226: Unknown result type (might be due to invalid IL or missing references) //IL_0234: Unknown result type (might be due to invalid IL or missing references) //IL_0239: Unknown result type (might be due to invalid IL or missing references) Hide(); _lastRegistryBlob = GroupRegistryAccess.CurrentBlob(); string text = _groupName?.Invoke(); if (text == null || !GroupRegistryAccess.TryLoad(out GroupRegistry registry)) { return; } GroupDefinition val = registry.GetGroup(text); if (val == null) { return; } string[] array = ((IEnumerable<string>)val.ItemTypes).OrderBy<string, string>((string item) => item, StringComparer.Ordinal).ToArray(); string[] array2 = array.Take(8).ToArray(); int num = array.Length - array2.Length; string[] array3 = ((array2.Length != 0) ? array2 : new string[1] { "(empty)" }); string text2 = val.Name + "\n" + ((_actionHint == null) ? string.Empty : (_actionHint + "\n")) + array.Length + " current item types:\n" + string.Join("\n", array3); if (num > 0) { text2 = text2 + "\n+" + num + " more"; } int num2 = array3.Length + 2 + ((_actionHint != null) ? 1 : 0) + ((num > 0) ? 1 : 0); float num3 = 20f + (float)num2 * 19f; _panel = UiFactory.CreatePanel(_displayParent ?? ((Component)this).transform, "SCR_GroupContentsTooltip", new Vector2(220f, num3)); RectTransform component = _panel.GetComponent<RectTransform>(); component.pivot = new Vector2(1f, 1f); if ((Object)(object)_displayParent == (Object)null) { component.anchorMin = new Vector2(0f, 1f); component.anchorMax = new Vector2(0f, 1f); component.anchoredPosition = new Vector2(-8f, 0f); } else { Transform transform = ((Component)this).transform; RectTransform val2 = (RectTransform)(object)((transform is RectTransform) ? transform : null); if (val2 != null) { Rect rect = val2.rect; float num4 = ((Rect)(ref rect)).xMin - 8f; rect = val2.rect; ((Transform)component).position = ((Transform)val2).TransformPoint(new Vector3(num4, ((Rect)(ref rect)).yMax, 0f)); } } ((Graphic)_panel.GetComponent<Image>()).raycastTarget = false; TMP_Text obj = UiFactory.CreateText(_panel.transform, text2); obj.alignment = (TextAlignmentOptions)257; obj.fontSize = 14f; obj.textWrappingMode = (TextWrappingModes)0; obj.overflowMode = (TextOverflowModes)1; ((Graphic)obj).raycastTarget = false; ((Component)obj).gameObject.AddComponent<LayoutElement>().preferredHeight = num3 - 20f; } private void Hide() { if ((Object)(object)_panel != (Object)null) { _panel.SetActive(false); Object.Destroy((Object)(object)_panel); _panel = null; } } } internal sealed class GroupEvictionTooltip : MonoBehaviour, IPointerEnterHandler, IEventSystemHandler, IPointerExitHandler { private const string Explanation = "Strict: only group items\n\nApplies to every chest in this group. Items that aren't in the group move to a nearby chest with room. A chest in the item's group is tried first, even if that group also evicts. Otherwise, the nearest chest that isn't strict is used. A strict chest only receives its group's items."; private Transform? _displayParent; private GameObject? _panel; public void Initialize(Transform? displayParent) { _displayParent = displayParent; } public void OnPointerEnter(PointerEventData eventData) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_006b: 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_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: 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_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: 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_00b2: Unknown result type (might be due to invalid IL or missing references) Hide(); if ((Object)(object)_displayParent == (Object)null) { return; } Transform transform = ((Component)this).transform; RectTransform val = (RectTransform)(object)((transform is RectTransform) ? transform : null); if (val != null) { _panel = UiFactory.CreatePanel(_displayParent, "SCR_GroupEvictionTooltip", new Vector2(320f, 195f)); RectTransform component = _panel.GetComponent<RectTransform>(); Rect rect = val.rect; float num = ((Rect)(ref rect)).xMax + 8f; rect = val.rect; Vector3 val2 = ((Transform)val).TransformPoint(new Vector3(num, ((Rect)(ref rect)).yMax, 0f)); if (val2.x + 320f <= (float)Screen.width) { component.pivot = new Vector2(0f, 1f); ((Transform)component).position = val2; } else { component.pivot = Vector2.one; rect = val.rect; float num2 = ((Rect)(ref rect)).xMin - 8f; rect = val.rect; ((Transform)component).position = ((Transform)val).TransformPoint(new Vector3(num2, ((Rect)(ref rect)).yMax, 0f)); } ((Graphic)_panel.GetComponent<Image>()).raycastTarget = false; TMP_Text obj = UiFactory.CreateText(_panel.transform, "Strict: only group items\n\nApplies to every chest in this group. Items that aren't in the group move to a nearby chest with room. A chest in the item's group is tried first, even if that group also evicts. Otherwise, the nearest chest that isn't strict is used. A strict chest only receives its group's items."); obj.alignment = (TextAlignmentOptions)257; obj.fontSize = 16f; obj.textWrappingMode = (TextWrappingModes)1; obj.overflowMode = (TextOverflowModes)1; ((Graphic)obj).raycastTarget = false; ((Component)obj).gameObject.AddComponent<LayoutElement>().preferredHeight = 175f; } } public void OnPointerExit(PointerEventData eventData) { Hide(); } private void OnDisable() { Hide(); } private void Hide() { if ((Object)(object)_panel != (Object)null) { _panel.SetActive(false); Object.Destroy((Object)(object)_panel); _panel = null; } } } internal sealed class GroupItemsController : MonoBehaviour { private const float PanelWidth = 360f; private const float RowHeight = 32f; private const float MaxListHeight = 360f; private const float RegistryCheckIntervalSeconds = 0.1f; private const float ClearConfirmSeconds = 3f; private static GroupItemsController? _instance; private ContainerRoutingBehaviour? _source; private Transform? _canvasTransform; private GameObject? _panel; private string? _lastRegistryBlob; private float _nextRegistryCheck; private float _clearConfirmUntil; private void Awake() { _instance = this; } private void OnDestroy() { if (_instance == this) { _instance = null; } } public static void Toggle(ContainerRoutingBehaviour? source) { if (!((Object)(object)_instance == (Object)null) && !((Object)(object)Player.m_localPlayer == (Object)null)) { if ((Object)(object)_instance._panel != (Object)null) { _instance.Close(); } else if (source != null && source.GetSettings().GroupName != null) { GroupManagementController.CloseIfOpen(); _instance.Open(source); } } } public static void CloseIfOpen() { if ((Object)(object)_instance != (Object)null && (Object)(object)_instance._panel != (Object)null) { _instance.Close(); } } private void Update() { if ((Object)(object)_panel == (Object)null) { return; } if (!IsSourceChestStillOpen()) { Close(); return; } UiFactory.MatchVanillaScale(_canvasTransform); if (_clearConfirmUntil > 0f && Time.unscaledTime >= _clearConfirmUntil) { _clearConfirmUntil = 0f; Rebuild(); } if (Time.unscaledTime >= _nextRegistryCheck) { _nextRegistryCheck = Time.unscaledTime + 0.1f; string text = GroupRegistryAccess.CurrentBlob(); if (text != null && text != _lastRegistryBlob) { Rebuild(); } } } private bool IsSourceChestStillOpen() { if ((Object)(object)Player.m_localPlayer != (Object)null && (Object)(object)_source != (Object)null && InventoryGui.IsVisible() && (Object)(object)InventoryGui.instance != (Object)null && InventoryGui.instance.IsContainerOpen()) { return (Object)(object)ContainerPanelController.Current == (Object)(object)_source; } return false; } private void Open(ContainerRoutingBehaviour source) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown //IL_00b4: Unknown result type (might be due to invalid IL or missing references) _source = source; _clearConfirmUntil = 0f; GameObject val = new GameObject("SCR_GroupItemsCanvas", new Type[4] { typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster) }); val.transform.SetParent(((Component)this).transform, false); Canvas component = val.GetComponent<Canvas>(); component.renderMode = (RenderMode)0; component.overrideSorting = true; component.sortingOrder = SortingOrderAboveInventory(); _canvasTransform = val.transform; UiFactory.MatchVanillaScale(_canvasTransform); _panel = UiFactory.CreatePanel(val.transform, "SCR_GroupItemsPanel", new Vector2(360f, 200f)); PopupStyle.Apply(_panel); _panel.AddComponent<ContentSizeFitter>().verticalFit = (FitMode)2; Rebuild(); } private void Close() { if ((Object)(object)_canvasTransform != (Object)null) { Object.Destroy((Object)(object)((Component)_canvasTransform).gameObject); } _canvasTransform = null; _panel = null; _source = null; _lastRegistryBlob = null; _clearConfirmUntil = 0f; } private void Rebuild() { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Expected O, but got Unknown //IL_026e: Unknown result type (might be due to invalid IL or missing references) //IL_0278: Expected O, but got Unknown if ((Object)(object)_panel == (Object)null) { return; } foreach (Transform item in _panel.transform) { Transform val = item; if (!PopupStyle.IsBackground(val)) { ((Component)val).gameObject.SetActive(false); Object.Destroy((Object)(object)((Component)val).gameObject); } } _lastRegistryBlob = GroupRegistryAccess.CurrentBlob(); ContainerRoutingBehaviour? source = _source; string text = ((source != null) ? source.GetSettings().GroupName : null); GroupRegistry val2 = GroupRegistryAccess.Load(); if (text != null) { GroupDefinition group = val2.GetGroup(text); if (group != null) { List<(string, string)> list = ((IEnumerable<string>)group.ItemTypes).Select((string itemType) => (Type: itemType, Name: ItemTypeResolver.GetDisplayName(itemType))).OrderBy<(string, string), string>(((string Type, string Name) item) => item.Name, StringComparer.CurrentCultureIgnoreCase).ToList(); GameObject obj = UiFactory.CreateRow(_panel.transform, "Header"); ((Component)UiFactory.CreateText(obj.transform, $"{group.Name}: {list.Count} items")).gameObject.AddComponent<LayoutElement>().flexibleWidth = 1f; UiFactory.CreateButton(obj.transform, "Close", new UnityAction(Close)); if (list.Count == 0) { UiFactory.CreateText(_panel.transform, "No items yet. Use Add Contents."); return; } float height = Mathf.Min(360f, (float)list.Count * 36f); RectTransform content = UiFactory.CreateScrollList(_panel.transform, 340f, height).content; foreach (var item2 in list) { BuildItemRow((Transform)(object)content, item2.Item1, item2.Item2); } GameObject obj2 = UiFactory.CreateRow(_panel.transform, "Footer"); UiFactory.CreateButton(label: (_clearConfirmUntil > Time.unscaledTime) ? "Click again to clear all" : "Clear all", parent: obj2.transform, onClick: (UnityAction)delegate { OnClearAllClicked(group.Name); }); return; } } Close(); } private void BuildItemRow(Transform content, string itemType, string displayName) { //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Expected O, but got Unknown //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_009a: Unknown result type (might be due to invalid IL or missing references) GameObject val = UiFactory.CreateRow(content, "Row_" + itemType); LayoutElement component = val.GetComponent<LayoutElement>(); component.minHeight = 32f; component.preferredHeight = 32f; Sprite icon = ItemTypeResolver.GetIcon(itemType); float minWidth; if (icon != null) { GameObject val2 = new GameObject("Icon", new Type[3] { typeof(RectTransform), typeof(Image), typeof(LayoutElement) }); val2.transform.SetParent(val.transform, false); Image component2 = val2.GetComponent<Image>(); component2.sprite = icon; component2.preserveAspect = true; ((Graphic)component2).raycastTarget = false; LayoutElement component3 = val2.GetComponent<LayoutElement>(); minWidth = (component3.preferredWidth = 28f); component3.minWidth = minWidth; minWidth = (component3.preferredHeight = 28f); component3.minHeight = minWidth; } ((Component)UiFactory.CreateText(val.transform, displayName)).gameObject.AddComponent<LayoutElement>().flexibleWidth = 1f; LayoutElement component4 = ((Component)UiFactory.CreateButton(val.transform, "Remove", (UnityAction)delegate { GroupRegistryRpc.Request(GroupRegistryMutation.UnassignItem(itemType)); })).GetComponent<LayoutElement>(); minWidth = (component4.preferredWidth = 90f); component4.minWidth = minWidth; } private void OnClearAllClicked(string groupName) { if (_clearConfirmUntil <= Time.unscaledTime) { _clearConfirmUntil = Time.unscaledTime + 3f; Rebuild(); return; } _clearConfirmUntil = 0f; Player localPlayer = Player.m_localPlayer; long num = ((localPlayer != null) ? localPlayer.GetPlayerID() : 0); GroupRegistryRpc.Request(GroupRegistryMutation.ReplaceGroupItems(groupName, (IEnumerable<string>)Array.Empty<string>(), num)); Rebuild(); } private static int SortingOrderAboveInventory() { Canvas val = (((Object)(object)InventoryGui.instance != (Object)null) ? ((Component)InventoryGui.instance).GetComponentInParent<Canvas>() : null); if ((Object)(object)val == (Object)null) { return 1; } return ((val.isRootCanvas || val.overrideSorting) ? val : val.rootCanvas).sortingOrder + 1; } } internal sealed class GroupManagementController : MonoBehaviour { private static GroupManagementController? _instance; private const float PanelWidth = 460f; private const float InitialPanelHeight = 300f; private const float MaxContentListHeight = 240f; private GameObject? _panel; private Transform? _canvasTransform; private string? _lastRegistryBlob; private const float RegistryCheckIntervalSeconds = 0.1f; private float _nextRegistryCheck; private bool _textInputBlocked; private bool _nameEditorRequested; private readonly Dictionary<Toggle, string> _evictionToggles = new Dictionary<Toggle, string>(); private ContainerRoutingBehaviour? _source; internal static bool IsNameEditorOpen { get { if ((Object)(object)_instance != (Object)null && _instance._nameEditorRequested && (Object)(object)TextInput.instance != (Object)null && (Object)(object)TextInput.instance.m_panel != (Object)null) { return TextInput.instance.m_panel.activeInHierarchy; } return false; } } private void Awake() { _instance = this; } private void OnDestroy() { if (_instance == this) { _instance = null; } } public static void Toggle(ContainerRoutingBehaviour? source) { if (!((Object)(object)_instance == (Object)null) && !((Object)(object)Player.m_localPlayer == (Object)null)) { if ((Object)(object)_instance._panel != (Object)null) { _instance.Close(); } else if ((Object)(object)source != (Object)null) { GroupItemsController.CloseIfOpen(); _instance.Open(source); } } } private void Update() { if ((Object)(object)_panel == (Object)null) { return; } if (!IsSourceChestStillOpen()) { Close(); return; } UiFactory.MatchVanillaScale(_canvasTransform); bool flag = TextInput.IsVisible(); if (_nameEditorRequested && ((Object)(object)TextInput.instance == (Object)null || (Object)(object)TextInput.instance.m_panel == (Object)null || !TextInput.instance.m_panel.activeInHierarchy)) { _nameEditorRequested = false; } SetTextInputBlocked(flag); if ((Object)(object)_panel != (Object)null && _panel.activeSelf == flag) { _panel.SetActive(!flag); } if (!flag && Time.unscaledTime >= _nextRegistryCheck) { _nextRegistryCheck = Time.unscaledTime + 0.1f; string text = GroupRegistryAccess.CurrentBlob(); if (text != null && text != _lastRegistryBlob) { Rebuild(); } } } private bool IsSourceChestStillOpen() { if ((Object)(object)Player.m_localPlayer != (Object)null && (Object)(object)_source != (Object)null && InventoryGui.IsVisible() && (Object)(object)InventoryGui.instance != (Object)null && InventoryGui.instance.IsContainerOpen()) { return (Object)(object)ContainerPanelController.Current == (Object)(object)_source; } return false; } private static long LocalPlayerId() { Player localPlayer = Player.m_localPlayer; if (localPlayer == null) { return 0L; } return localPlayer.GetPlayerID(); } private void Close() { if ((Object)(object)_canvasTransform != (Object)null) { Object.Destroy((Object)(object)((Component)_canvasTransform).gameObject); } _canvasTransform = null; _panel = null; _source = null; _lastRegistryBlob = null; _nameEditorRequested = false; } private void Open(ContainerRoutingBehaviour source) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Expected O, but got Unknown //IL_00a9: Unknown result type (might be due to invalid IL or missing references) _source = source; GameObject val = new GameObject("SCR_GroupManagementCanvas", new Type[4] { typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster) }); val.transform.SetParent(((Component)this).transform, false); Canvas component = val.GetComponent<Canvas>(); component.renderMode = (RenderMode)0; component.overrideSorting = true; component.sortingOrder = SortingOrderAboveInventory(); _canvasTransform = val.transform; UiFactory.MatchVanillaScale(_canvasTransform); _panel = UiFactory.CreatePanel(val.transform, "SCR_GroupManagementPanel", new Vector2(460f, 300f)); PopupStyle.Apply(_panel); _panel.AddComponent<ContentSizeFitter>().verticalFit = (FitMode)2; Rebuild(); } private static int SortingOrderAboveInventory() { Canvas val = (((Object)(object)InventoryGui.instance != (Object)null) ? ((Component)InventoryGui.instance).GetComponentInParent<Canvas>() : null); if ((Object)(object)val == (Object)null) { return 1; } return ((val.isRootCanvas || val.overrideSorting) ? val : val.rootCanvas).sortingOrder + 1; } private void Rebuild(string? validationMessage = null) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Expected O, but got Unknown //IL_0184: Unknown result type (might be due to invalid IL or missing references) //IL_02ed: Unknown result type (might be due to invalid IL or missing references) //IL_02f7: Expected O, but got Unknown if ((Object)(object)_panel == (Object)null) { return; } _evictionToggles.Clear(); foreach (Transform item in _panel.transform) { Transform val = item; if (!PopupStyle.IsBackground(val)) { ((Component)val).gameObject.SetActive(false); Object.Destroy((Object)(object)((Component)val).gameObject); } } GameObject obj = UiFactory.CreateRow(_panel.transform, "Header"); ((Component)UiFactory.CreateText(obj.transform, "Storage Groups")).gameObject.AddComponent<LayoutElement>().flexibleWidth = 1f; UiFactory.CreateButton(obj.transform, "Close", new UnityAction(Close)); Toggle obj2 = UiFactory.CreateToggle(UiFactory.CreateRow(_panel.transform, "Options").transform, "Show routing messages", RoutingNotifier.Enabled, delegate(bool value) { RoutingNotifier.Enabled = value; }); LayoutElement component = ((Component)obj2).GetComponent<LayoutElement>(); component.minWidth = 260f; component.preferredWidth = 260f; TMP_Text componentInChildren = ((Component)obj2).GetComponentInChildren<TMP_Text>(); if (componentInChildren != null) { componentInChildren.fontSizeMin = 14f; componentInChildren.fontSizeMax = 18f; componentInChildren.fontSize = 18f; } if (validationMessage != null) { TMP_Text obj3 = UiFactory.CreateText(_panel.transform, validationMessage); ((Graphic)obj3).color = new Color(1f, 0.35f, 0.25f); obj3.textWrappingMode = (TextWrappingModes)1; ((Component)obj3).gameObject.AddComponent<LayoutElement>().minHeight = 44f; } _lastRegistryBlob = GroupRegistryAccess.CurrentBlob(); GroupRegistry val2 = GroupRegistryAccess.Load(); bool flag = val2.GetGroupById("builtin:locked") != null || !val2.HasGroup("Locked"); int num = val2.Groups.Count((GroupDefinition group) => !BuiltInGroups.IsLocked(group)) + (flag ? 1 : 0); float height = Mathf.Min(240f, (float)(num + 1) * 28f); RectTransform content = UiFactory.CreateScrollList(_panel.transform, 440f, height).content; if (flag) { BuildLockedRow((Transform)(object)content, val2); } foreach (GroupDefinition item2 in val2.Groups.Where((GroupDefinition group) => !BuiltInGroups.IsLocked(group)).OrderBy<GroupDefinition, string>((GroupDefinition group) => group.Name, StringComparer.Ordinal)) { BuildGroupRow((Transform)(object)content, val2, item2); } UiFactory.CreateButton(UiFactory.CreateRow((Transform)(object)content, "Row_NewGroup").transform, "+", new UnityAction(OnNewGroupClicked)); } private void BuildLockedRow(Transform content, GroupRegistry registry) { //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Expected O, but got Unknown GameObject obj = UiFactory.CreateRow(content, "Row_Locked"); bool exists = registry.GetGroupById("builtin:locked") != null; ContainerRoutingBehaviour? source = _source; bool isSelected = BuiltInGroups.IsLocked(registry, (source != null) ? source.GetSettings().GroupName : null); Button obj2 = UiFactory.CreateButton(obj.transform, "Locked", (UnityAction)delegate { ContainerRoutingBehaviour source2 = _source; if (!((Object)(object)source2 == (Object)null)) { if (isSelected) { source2.SetGroup(null); } else { if (!exists) { GroupRegistryRpc.Request(GroupRegistryMutation.CreateGroup("Locked", LocalPlayerId(), "builtin:locked")); } source2.SetHeld(held: false); source2.SetGroup("Locked", "builtin:locked"); } Rebuild(); } }); ((Component)obj2).GetComponent<LayoutElement>().flexibleWidth = 1f; UiFactory.SetLockedStyle(obj2, locked: true); UiFactory.CreateSelectionBorder(((Component)obj2).transform).SetActive(isSelected); ((Component)obj2).gameObject.AddComponent<HoverTooltip>().Initialize(delegate { string lockedWarning = VersionCheck.LockedWarning; return "Nothing moves in or out of\nchests in this group.\nBuilt in: can't be renamed,\ndeleted or given items." + ((lockedWarning != null) ? ("\n\n" + lockedWarning) : string.Empty); }, _canvasTransform); } private void BuildGroupRow(Transform content, GroupRegistry registry, GroupDefinition group) { //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Expected O, but got Unknown //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Expected O, but got Unknown //IL_01c2: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Expected O, but got Unknown GameObject obj = UiFactory.CreateRow(content, "Row_" + group.Name); ContainerRoutingBehaviour? source = _source; bool isSelected = ((source != null) ? source.GetSettings().GroupName : null) == GroupNameNormalizer.Normalize(group.Name); Button obj2 = UiFactory.CreateButton(obj.transform, $"{group.Name} ({group.ItemTypes.Count})", (UnityAction)delegate { _source?.SetGroup(isSelected ? null : group.Name); Rebuild(); }); ((Component)obj2).GetComponent<LayoutElement>().flexibleWidth = 1f; UiFactory.CreateSelectionBorder(((Component)obj2).transform).SetActive(isSelected); ((Component)obj2).gameObject.AddComponent<GroupContentsTooltip>().Initialize(() => group.Name, _canvasTransform); Toggle val = UiFactory.CreateToggle(obj.transform, "Strict", group.EvictMismatching, delegate(bool value) { SetGroupEviction(group.Name, value); }); LayoutElement component = ((Component)val).GetComponent<LayoutElement>(); component.minWidth = 100f; component.preferredWidth = 100f; ((Selectable)val).interactable = !_textInputBlocked; _evictionToggles.Add(val, group.Name); ((Component)val).gameObject.AddComponent<GroupEvictionTooltip>().Initialize(_canvasTransform); TMP_Text componentInChildren = ((Component)val).GetComponentInChildren<TMP_Text>(); if (componentInChildren != null) { componentInChildren.fontSizeMin = 14f; componentInChildren.fontSizeMax = 18f; componentInChildren.fontSize = 18f; } UiFactory.CreateButton(obj.transform, "Rename", (UnityAction)delegate { RequestGroupRename(group.Name); }); UiFactory.CreateButton(obj.transform, "X", (UnityAction)delegate { GroupRegistryRpc.Request(GroupRegistryMutation.DeleteGroup(group.Name, LocalPlayerId())); ContainerRegistry.ClearGroup(group.Name); Rebuild(); }); } public static void NewGroupFromContents(ContainerRoutingBehaviour source, string suggestedName, IReadOnlyList<string> itemTypes) { if (!((Object)(object)_instance == (Object)null) && !((Object)(object)Player.m_localPlayer == (Object)null)) { if ((Object)(object)_instance._panel != (Object)null) { _instance.Close(); } GroupItemsController.CloseIfOpen(); _instance.Open(source); _instance.RequestGroupCreate(suggestedName, itemTypes); } } public static void CloseIfOpen() { if ((Object)(object)_instance != (Object)null && (Object)(object)_instance._panel != (Object)null) { _instance.Close(); } } private void OnNewGroupClicked() { if (!((Object)(object)TextInput.instance == (Object)null)) { GroupRegistry val = GroupRegistryAccess.Load(); int i; for (i = val.Groups.Count + 1; val.HasGroup($"Group-{i}"); i++) { } string suggestedName = $"Group-{i}"; RequestGroupCreate(suggestedName); } } private void RequestGroupCreate(string suggestedName, IReadOnlyList<string>? itemTypes = null) { if ((Object)(object)TextInput.instance == (Object)null) { return; } DelegateTextReceiver delegateTextReceiver = new DelegateTextReceiver(() => suggestedName, delegate(string name) { string text = DisplayNameSanitizer.Sanitize(name); if (text == null) { Rebuild("Group name cannot be empty."); } else { GroupRegistry val = GroupRegistryAccess.Load(); if (val.HasGroup(text)) { Rebuild("A group with that name already exists."); } else { string text2 = "g:" + Guid.NewGuid().ToString("N"); GroupRegistryRpc.Request(GroupRegistryMutation.CreateGroup(text, LocalPlayerId(), text2)); _source?.SetGroup(text, text2); if (itemTypes != null && itemTypes.Count > 0) { string text3 = GroupItemTransfers.Describe(val, text, (IEnumerable<string>)itemTypes, (Func<string, string>)ItemTypeResolver.GetDisplayName); foreach (string itemType in itemTypes) { GroupRegistryRpc.Request(GroupRegistryMutation.AssignItem(itemType, text)); } if (text3 != null) { Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)2, text3, 0, (Sprite)null, false); } } } Rebuild(); } } }); TextInput.instance.RequestText((TextReceiver)(object)delegateTextReceiver, "$scr_group_name", 32); _nameEditorRequested = true; PrepareForTextInput(); ((MonoBehaviour)this).StartCoroutine(FocusNameOnNextFrame(selectAll: true)); } private void SetGroupEviction(string groupName, bool value) { if (!GroupRegistryAccess.TryLoad(out GroupRegistry registry)) { Rebuild("Group settings aren't available yet. Try again in a moment."); return; } try { registry.WithEvictMismatching(groupName, value); } catch (ArgumentException) { Rebuild("This group no longer exists."); return; } GroupRegistryRpc.Request(GroupRegistryMutation.SetGroupEviction(groupName, value, LocalPlayerId())); } private void SetTextInputBlocked(bool blocked) { if (_textInputBlocked == blocked) { return; } _textInputBlocked = blocked; if ((Object)(object)_panel == (Object)null) { return; } foreach (Toggle key in _evictionToggles.Keys) { ((Selectable)key).interactable = !blocked; } } private void RequestGroupRename(string oldName) { if ((Object)(object)TextInput.instance == (Object)null) { return; } DelegateTextReceiver delegateTextReceiver = new DelegateTextReceiver(() => oldName, delegate(string newName) { if (string.IsNullOrWhiteSpace(newName) || string.Equals(newName.Trim(), oldName, StringComparison.Ordinal)) { Rebuild(); } else { try { GroupRegistry val = GroupRegistryAccess.Load(); if (val.HasGroup(newName) && GroupNameNormalizer.Normalize(newName) != GroupNameNormalizer.Normalize(oldName)) { throw new ArgumentException("Duplicate group name."); } if (val.HasGroup(oldName)) { val.WithRenamedGroup(oldName, newName); } else if (DisplayNameSanitizer.Sanitize(newName) == null) { throw new ArgumentException("Empty group name."); } } catch (ArgumentException) { Rebuild("A group with that name already exists, or the name is empty."); return; } GroupRegistryRpc.Request(GroupRegistryMutation.RenameGroup(oldName, newName, LocalPlayerId())); Rebuild(); } }); TextInput.instance.RequestText((TextReceiver)(object)delegateTextReceiver, "$scr_group_name", 32); _nameEditorRequested = true; PrepareForTextInput(); ((MonoBehaviour)this).StartCoroutine(FocusNameOnNextFrame(selectAll: false)); } private static IEnumerator FocusNameOnNextFrame(bool selectAll) { yield return null; if ((Object)(object)TextInput.instance == (Object)null || !TextInput.IsVisible()) { yield break; } TMP_InputField componentInChildren = ((Component)TextInput.instance).GetComponentInChildren<TMP_InputField>(true); if (componentInChildren != null) { ((Selectable)componentInChildren).Select(); componentInChildren.ActivateInputField(); if (selectAll) { componentInChildren.selectionAnchorPosition = 0; componentInChildren.selectionFocusPosition = componentInChildren.text.Length; } else { componentInChildren.caretPosition = componentInChildren.text.Length; } yield break; } InputField componentInChildren2 = ((Component)TextInput.instance).GetComponentInChildren<InputField>(true); if (componentInChildren2 != null) { ((Selectable)componentInChildren2).Select(); componentInChildren2.ActivateInputField(); if (selectAll) { componentInChildren2.selectionAnchorPosition = 0; componentInChildren2.selectionFocusPosition = componentInChildren2.text.Length; } else { componentInChildren2.caretPosition = componentInChildren2.text.Length; } } } private void PrepareForTextInput() { SetTextInputBlocked(blocked: true); GameObject? panel = _panel; if (panel != null) { panel.SetActive(false); } } } public static class GroupRegistryAccess { private static readonly GroupRegistryCache Cache = new GroupRegistryCache((Func<string, GroupRegistry>)DecodeAndBackUpIfCorrupt); public static Func<ZDO?> AnchorZdoProvider { get; set; } = () => (ZDO?)null; public static GroupRegistry Load() { TryLoad(out GroupRegistry registry); return registry; } public static bool TryLoad(out GroupRegistry registry) { return Cache.TryGet(CurrentBlob(), ref registry); } public static bool TryLoadTrusted(out GroupRegistry registry) { string text = CurrentBlob(); GroupRegistry val = default(GroupRegistry); if (string.IsNullOrEmpty(text) || !GroupRegistryCodec.TryDeserialize(text, ref val)) { registry = GroupRegistry.Empty; return false; } return TryLoad(out registry); } public static string? CurrentBlob() { ZDO obj = AnchorZdoProvider(); if (obj == null) { return null; } return obj.GetString("SCR_Groups", string.Empty); } private static GroupRegistry DecodeAndBackUpIfCorrupt(string? blob) { GroupRegistry result = default(GroupRegistry); if (GroupRegistryCodec.TryDeserialize(blob, ref result)) { return result; } if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { ZDO val = AnchorZdoProvider(); if (val != null) { val.Set("SCR_GroupsCorruptBackup", blob ?? string.Empty); } } return result; } public static string? DisplayName(string? groupKey) { object obj2; if (groupKey != null) { GroupDefinition obj = Load().GetGroup(groupKey); obj2 = ((obj != null) ? obj.Name : null); if (obj2 == null) { return groupKey; } } else { obj2 = null; } return (string?)obj2; } public static bool IsEvicting(string groupKey) { GroupDefinition obj = Load().GetGroup(groupKey); if (obj == null) { return false; } return obj.EvictMismatching; } } internal sealed class HoverTooltip : MonoBehaviour, IPointerEnterHandler, IEventSystemHandler, IPointerExitHandler { private const float Width = 260f; private const float TextCheckIntervalSeconds = 0.25f; private Func<string?>? _text; private Transform? _displayParent; private GameObject? _panel; private bool _hovered; private string? _shownText; private float _nextTextCheck; public void Initialize(Func<string?> text, Transform? displayParent = null) { _text = text; _displayParent = displayParent; } public void OnPointerEnter(PointerEventData eventData) { _hovered = true; Show(_text?.Invoke()); } public void OnPointerExit(PointerEventData eventData) { _hovered = false; Hide(); } private void OnDisable() { _hovered = false; Hide(); } private void Update() { if (_hovered && !(Time.unscaledTime < _nextTextCheck)) { _nextTextCheck = Time.unscaledTime + 0.25f; string text = _text?.Invoke(); if (text != _shownText) { Show(text); } } } private void Show(string? body) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) Hide(); _shownText = body; if (body == null || body.Length <= 0) { return; } int num = body.Split(new char[1] { '\n' }).Length; float num2 = 20f + (float)num * 19f; _panel = UiFactory.CreatePanel(_displayParent ?? ((Component)this).transform, "SCR_HoverTooltip", new Vector2(260f, num2)); RectTransform component = _panel.GetComponent<RectTransform>(); component.pivot = new Vector2(1f, 1f); if ((Object)(object)_displayParent == (Object)null) { component.anchorMin = new Vector2(0f, 1f); component.anchorMax = new Vector2(0f, 1f); component.anchoredPosition = new Vector2(-8f, 0f); } else { Transform transform = ((Component)this).transform; RectTransform val = (RectTransform)(object)((transform is RectTransform) ? transform : null); if (val != null) { Rect rect = val.rect; float num3 = ((Rect)(ref rect)).xMin - 8f; rect = val.rect; ((Transform)component).position = ((Transform)val).TransformPoint(new Vector3(num3, ((Rect)(ref rect)).yMax, 0f)); } } ((Graphic)_panel.GetComponent<Image>()).raycastTarget = false; TMP_Text obj = UiFactory.CreateText(_panel.transform, body); obj.alignment = (TextAlignmentOptions)257; obj.fontSize = 14f; obj.textWrappingMode = (TextWrappingModes)0; obj.overflowMode = (TextOverflowModes)1; ((Graphic)obj).raycastTarget = false; ((Component)obj).gameObject.AddComponent<LayoutElement>().preferredHeight = num2 - 20f; } private void Hide() { if ((Object)(object)_panel != (Object)null) { _panel.SetActive(false); Object.Destroy((Object)(object)_panel); _panel = null; } } } [HarmonyPatch(typeof(InventoryGui), "Awake")] internal static class InventoryGuiAwakePatch { private static void Postfix(InventoryGui __instance) { UiFactory.CaptureVanillaStyle(__instance); } } [HarmonyPatch(typeof(InventoryGui), "Show", new Type[] { typeof(Container), typeof(int) })] internal static class InventoryGuiContainerShowPatch { private static void Postfix(InventoryGui __instance, Container container) { if (!((Object)(object)container == (Object)null)) { ContainerPanelController.ShowFor(__instance, container); } } } [HarmonyPatch(typeof(InventoryGui), "Update")] internal static class InventoryGuiNameEditInputPatch { private static void Prefix() { if (GroupManagementController.IsNameEditorOpen) { ZInput.ResetButtonStatus("Use"); ZInput.ResetButtonStatus("JoyUse"); } } } internal static class ItemTypeResolver { public static string GetItemType(ItemData item) { if (!((Object)(object)item.m_dropPrefab != (Object)null)) { return item.m_shared.m_name; } return ((Object)item.m_dropPrefab).name; } public static string GetDisplayName(string itemType) { ObjectDB instance = ObjectDB.instance; object obj; if (instance == null) { obj = null; } else { GameObject itemPrefab = instance.GetItemPrefab(itemType); obj = ((itemPrefab == null) ? null : itemPrefab.GetComponent<ItemDrop>()?.m_itemData?.m_shared?.m_name); } string text = (string)obj; if (string.IsNullOrEmpty(text)) { return itemType; } Localization instance2 = Localization.instance; return ((instance2 != null) ? instance2.Localize(text) : null) ?? itemType; } public static Sprite? GetIcon(string itemType) { GameObject val = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetItemPrefab(itemType) : null); Sprite[] array = ((!((Object)(object)val != (Object)null)) ? null : val.GetComponent<ItemDrop>()?.m_itemData?.m_shared)?.m_icons; if (array == null || array.Length <= 0) { return null; } return array[0]; } } [HarmonyPatch(typeof(Localization), "SetupLanguage")] internal static class LocalizationPatch { private static readonly MethodInfo AddWordMethod = AccessTools.Method(typeof(Localization), "AddWord", (Type[])null, (Type[])null); internal static void AddWord(Localization instance, string key, string text) { AddWordMethod.Invoke(instance, new object[2] { key, text }); } private static void Postfix(Localization __instance) { AddWord(__instance, "scr_group_name", "Group name"); } } internal static class PopupStyle { private sealed class GraphicsMirror : MonoBehaviour { private Graphic[] _sources = (Graphic[])(object)new Graphic[0]; private Graphic[] _targets = (Graphic[])(object)new Graphic[0]; public void Initialize(Graphic[] sources, Graphic[] targets) { _sources = sources; _targets = targets; LateUpdate(); } private void LateUpdate() { //IL_0062: Unknown result type (might be due to invalid IL or missing references) int num = Mathf.Min(_sources.Length, _targets.Length); for (int i = 0; i < num; i++) { Graphic val = _sources[i]; Graphic val2 = _targets[i]; if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null) { continue; } if ((Object)(object)val2.material != (Object)(object)val.material) { val2.material = val.material; } val2.color = val.color; Image val3 = (Image)(object)((val is Image) ? val : null); if (val3 != null) { Image val4 = (Image)(object)((val2 is Image) ? val2 : null); if (val4 != null && (Object)(object)val4.sprite != (Object)(object)val3.sprite) { val4.sprite = val3.sprite; } } } } } private const string BackgroundName = "SCR_VanillaBkg"; public static bool IsBackground(Transform child) { return ((Object)child).name == "SCR_VanillaBkg"; } public static void Apply(GameObject panel) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Expected O, but got Unknown //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_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_0086: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_009c: 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_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) GameObject val = FindVanillaBackground(); if (val != null) { GameObject val2 = Object.Instantiate<GameObject>(val.gameObject, panel.transform, false); ((Object)val2).name = "SCR_VanillaBkg"; val2.transform.SetAsFirstSibling(); (val2.GetComponent<LayoutElement>() ?? val2.AddComponent<LayoutElement>()).ignoreLayout = true; RectTransform val3 = (RectTransform)val2.transform; RectTransform val4 = (RectTransform)val.transform; bool flag = val4.anchorMin == Vector2.zero && val4.anchorMax == Vector2.one; val3.anchorMin = Vector2.zero; val3.anchorMax = Vector2.one; val3.pivot = new Vector2(0.5f, 0.5f); val3.offsetMin = (flag ? val4.offsetMin : Vector2.zero); val3.offsetMax = (flag ? val4.offsetMax : Vector2.zero); Graphic[] componentsInChildren = val2.GetComponentsInChildren<Graphic>(true); Graphic[] array = componentsInChildren; for (int i = 0; i < array.Length; i++) { array[i].raycastTarget = false; } val2.AddComponent<GraphicsMirror>().Initialize(val.GetComponentsInChildren<Graphic>(true), componentsInChildren); Image component = panel.GetComponent<Image>(); if (component != null) { ((Graphic)component).color = new Color(0f, 0f, 0f, 0f); } Outline component2 = panel.GetComponent<Outline>(); if (component2 != null) { ((Behaviour)component2).enabled = false; } } } private static GameObject? FindVanillaBackground() { RectTransform val = (((Object)(object)InventoryGui.instance != (Object)null) ? InventoryGui.instance.m_container : null); if ((Object)(object)val != (Object)null) { Transform val2 = ((Transform)val).Find("Bkg"); if (val2 != null) { return ((Component)val2).gameObject; } } return null; } } internal static class UiFactory { private const float MaxControlFontSize = 19.2f; private static bool _captured; private static TMP_FontAsset? _font; private static Material? _fontMaterial; private static Color _textColor = Color.white; private static float _fontSize = 14f; private static Sprite? _buttonSprite; private static Type _buttonSpriteType = (Type)0; private static Transition _buttonTransition = (Transition)1; private static ColorBlock _buttonColors = ColorBlock.defaultColorBlock; private static SpriteState _buttonSpriteState; private static Sprite? _toggleBackgroundSprite; private static Type _toggleBackgroundSpriteType = (Type)0; private static Sprite? _toggleCheckmarkSprite; private static readonly Color PanelColor = new Color(0.07f, 0.06f, 0.05f, 1f); private static readonly Color PanelEdgeColor = new Color(0.45f, 0.35f, 0.18f, 1f); public static readonly Color LockedTint = new Color(0.55f, 0.78f, 1f, 1f); private const string LockIconName = "SCR_LockIcon"; private const int LockIconPixels = 16; private static Sprite? _lockSprite; public static void CaptureVanillaStyle(InventoryGui gui) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: 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: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) if (_captured || (Object)(object)gui == (Object)null) { return; } _captured = true; if ((Object)(object)gui.m_containerName != (Object)null) { _font = gui.m_containerName.font; _fontMaterial = gui.m_containerName.fontSharedMaterial; _textColor = ((Graphic)gui.m_containerName).color; _fontSize = gui.m_containerName.fontSize; } Button[] array = (Button[])(object)new Button[3] { gui.m_takeAllButton, gui.m_stackAllButton, gui.m_dropButton }; foreach (Button val in array) { if (!((Object)(object)val == (Object)null)) { Image component = ((Component)val).GetComponent<Image>(); if (component != null && component.sprite != null) { _buttonSprite = component.sprite; _buttonSpriteType = component.type; _buttonTransition = ((Selectable)val).transition; _buttonColors = ((Selectable)val).colors; _buttonSpriteState = ((Selectable)val).spriteState; break; } } } if ((Object)(object)gui.m_pvp != (Object)null) { Image component2 = ((Component)gui.m_pvp).GetComponent<Image>(); if ((Object)(object)component2 != (Object)null) { _toggleBackgroundSprite = component2.sprite; _toggleBackgroundSpriteType = component2.type; } Graphic graphic = gui.m_pvp.graphic; Image val2 = (Image)(object)((graphic is Image) ? graphic : null); if (val2 != null) { _toggleCheckmarkSprite = val2.sprite; } } } public static GameObject CreatePanel(Transform parent, string name, Vector2 size) { //IL_002e: 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_0040: 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_0066: 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_0086: Unknown result type (might be due to invalid IL or missing references) //IL_0090: 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: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: 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_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Expected O, but got Unknown //IL_010b: Expected O, but got Unknown GameObject val = new GameObject(name, new Type[3] { typeof(RectTransform), typeof(Image), typeof(VerticalLayoutGroup) }); val.transform.SetParent(parent, false); RectTransform component = val.GetComponent<RectTransform>(); component.anchorMin = new Vector2(0.5f, 0.5f); component.anchorMax = new Vector2(0.5f, 0.5f); component.pivot = new Vector2(0.5f, 0.5f); component.anchoredPosition = Vector2.zero; component.sizeDelta = size; ((Graphic)val.GetComponent<Image>()).color = PanelColor; Outline obj = val.AddComponent<Outline>(); ((Shadow)obj).effectColor = PanelEdgeColor; ((Shadow)obj).effectDistance = new Vector2(2f, -2f); VerticalLayoutGroup component2 = val.GetComponent<VerticalLayoutGroup>(); ((LayoutGroup)component2).padding = new RectOffset(10, 10, 10, 10); ((HorizontalOrVerticalLayoutGroup)component2).spacing = 6f; ((HorizontalOrVerticalLayoutGroup)component2).childControlWidth = true; ((HorizontalOrVerticalLayoutGroup)component2).childForceExpandWidth = true; ((HorizontalOrVerticalLayoutGroup)component2).childControlHeight = true; ((HorizontalOrVerticalLayoutGroup)component2).childForceExpandHeight = false; return val; } public static GameObject CreateSelectionBorder(Transform parent) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003e: 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_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007c: 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_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Expected O, but got Unknown GameObject val = new GameObject("SelectionBorder", new Type[2] { typeof(RectTransform), typeof(LayoutElement) }); val.transform.SetParent(parent, false); RectTransform component = val.GetComponent<RectTransform>(); component.anchorMin = Vector2.zero; component.anchorMax = Vector2.one; component.offsetMin = new Vector2(1f, 1f); component.offsetMax = new Vector