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 ZoneSavior v1.0.7
ZoneSavior.dll
Decompiled a day 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.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using JetBrains.Annotations; using Microsoft.CodeAnalysis; using ServerSync; using Splatform; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Rendering; using UnityEngine.UI; using YamlDotNet.Core; using YamlDotNet.Core.Events; using YamlDotNet.Core.ObjectPool; using YamlDotNet.Core.Tokens; using YamlDotNet.Helpers; using YamlDotNet.Serialization; using YamlDotNet.Serialization.BufferedDeserialization; using YamlDotNet.Serialization.BufferedDeserialization.TypeDiscriminators; using YamlDotNet.Serialization.Callbacks; using YamlDotNet.Serialization.Converters; using YamlDotNet.Serialization.EventEmitters; using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.Serialization.NodeDeserializers; using YamlDotNet.Serialization.NodeTypeResolvers; using YamlDotNet.Serialization.ObjectFactories; using YamlDotNet.Serialization.ObjectGraphTraversalStrategies; using YamlDotNet.Serialization.ObjectGraphVisitors; using YamlDotNet.Serialization.Schemas; using YamlDotNet.Serialization.TypeInspectors; using YamlDotNet.Serialization.TypeResolvers; using YamlDotNet.Serialization.Utilities; using YamlDotNet.Serialization.ValueDeserializers; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("ZoneSavior")] [assembly: AssemblyDescription("Dedicated-server zone archiving, zone bundle restore, zone UI, and per-zone WearNTear limits for Valheim.")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("sighsorry")] [assembly: AssemblyProduct("ZoneSavior")] [assembly: AssemblyCopyright("Copyright 2026 sighsorry")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("B276E8F6-7F9F-4A0F-8E7D-757383FF7CB1")] [assembly: AssemblyFileVersion("1.0.7")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.7.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] [CompilerGenerated] internal sealed class <>z__ReadOnlySingleElementList<T> : IEnumerable, ICollection, IList, IEnumerable<T>, IReadOnlyCollection<T>, IReadOnlyList<T>, ICollection<T>, IList<T> { private sealed class Enumerator : IDisposable, IEnumerator, IEnumerator<T> { object IEnumerator.Current => _item; T IEnumerator<T>.Current => _item; public Enumerator(T item) { _item = item; } bool IEnumerator.MoveNext() { if (!_moveNextCalled) { return _moveNextCalled = true; } return false; } void IEnumerator.Reset() { _moveNextCalled = false; } void IDisposable.Dispose() { } } int ICollection.Count => 1; bool ICollection.IsSynchronized => false; object ICollection.SyncRoot => this; object IList.this[int index] { get { if (index != 0) { throw new IndexOutOfRangeException(); } return _item; } set { throw new NotSupportedException(); } } bool IList.IsFixedSize => true; bool IList.IsReadOnly => true; int IReadOnlyCollection<T>.Count => 1; T IReadOnlyList<T>.this[int index] { get { if (index != 0) { throw new IndexOutOfRangeException(); } return _item; } } int ICollection<T>.Count => 1; bool ICollection<T>.IsReadOnly => true; T IList<T>.this[int index] { get { if (index != 0) { throw new IndexOutOfRangeException(); } return _item; } set { throw new NotSupportedException(); } } public <>z__ReadOnlySingleElementList(T item) { _item = item; } IEnumerator IEnumerable.GetEnumerator() { return new Enumerator(_item); } void ICollection.CopyTo(Array array, int index) { array.SetValue(_item, index); } int IList.Add(object value) { throw new NotSupportedException(); } void IList.Clear() { throw new NotSupportedException(); } bool IList.Contains(object value) { return EqualityComparer<T>.Default.Equals(_item, (T)value); } int IList.IndexOf(object value) { if (!EqualityComparer<T>.Default.Equals(_item, (T)value)) { return -1; } return 0; } void IList.Insert(int index, object value) { throw new NotSupportedException(); } void IList.Remove(object value) { throw new NotSupportedException(); } void IList.RemoveAt(int index) { throw new NotSupportedException(); } IEnumerator<T> IEnumerable<T>.GetEnumerator() { return new Enumerator(_item); } void ICollection<T>.Add(T item) { throw new NotSupportedException(); } void ICollection<T>.Clear() { throw new NotSupportedException(); } bool ICollection<T>.Contains(T item) { return EqualityComparer<T>.Default.Equals(_item, item); } void ICollection<T>.CopyTo(T[] array, int arrayIndex) { array[arrayIndex] = _item; } bool ICollection<T>.Remove(T item) { throw new NotSupportedException(); } int IList<T>.IndexOf(T item) { if (!EqualityComparer<T>.Default.Equals(_item, item)) { return -1; } return 0; } void IList<T>.Insert(int index, T item) { throw new NotSupportedException(); } void IList<T>.RemoveAt(int index) { throw new NotSupportedException(); } } namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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] [Microsoft.CodeAnalysis.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] [Microsoft.CodeAnalysis.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; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class ExtensionMarkerAttribute : Attribute { private readonly string <Name>k__BackingField; public string Name => <Name>k__BackingField; public ExtensionMarkerAttribute(string name) { <Name>k__BackingField = name; } } } namespace ZoneSavior { internal static class AutoArchiveCommands { [CompilerGenerated] private static class <>O { public static ConsoleEvent <0>__HandleCommand; public static Action<long, ZPackage> <1>__RPC_HandleRequest; public static Action<long, ZPackage> <2>__RPC_HandleResult; } private const string ScanCommand = "zs_scan"; private const string StatusCommand = "zs_status"; private const string DebugZoneCommand = "zs_debugzone"; private const string RequestRpcName = "sighsorry.ZoneSavior_AutoArchiveCommandRequest"; private const string ResultRpcName = "sighsorry.ZoneSavior_AutoArchiveCommandResult"; private static readonly Regex ZoneSpecPattern = new Regex("^\\s*\\(\\s*(-?\\d+)\\s*,\\s*(-?\\d+)\\s*\\)\\s*$", RegexOptions.Compiled); private static ManualLogSource _logger = null; private static bool _initialized; private static readonly ZoneRpcRegistrar RpcRegistrar = new ZoneRpcRegistrar(); public static void Initialize(ManualLogSource logger) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Expected O, but got Unknown //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Expected O, but got Unknown //IL_00aa: 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_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Expected O, but got Unknown if (!_initialized) { _initialized = true; _logger = logger; object obj = <>O.<0>__HandleCommand; if (obj == null) { ConsoleEvent val = HandleCommand; <>O.<0>__HandleCommand = val; obj = (object)val; } new ConsoleCommand("zs_scan", "[steamID] [dry|save|reset] - Runs an auto archive scan, optionally filtered to one Steam owner.", (ConsoleEvent)obj, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj2 = <>O.<0>__HandleCommand; if (obj2 == null) { ConsoleEvent val2 = HandleCommand; <>O.<0>__HandleCommand = val2; obj2 = (object)val2; } new ConsoleCommand("zs_status", "- Writes a YAML report with recent auto archive runs.", (ConsoleEvent)obj2, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj3 = <>O.<0>__HandleCommand; if (obj3 == null) { ConsoleEvent val3 = HandleCommand; <>O.<0>__HandleCommand = val3; obj3 = (object)val3; } new ConsoleCommand("zs_debugzone", "(x,z) - Writes a YAML report explaining auto archive eligibility for one zone.", (ConsoleEvent)obj3, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); RegisterRpcs(); } } internal static void RegisterRpcs() { RpcRegistrar.EnsureRegistered(delegate(ZRoutedRpc routedRpc) { routedRpc.Register<ZPackage>("sighsorry.ZoneSavior_AutoArchiveCommandRequest", (Action<long, ZPackage>)RPC_HandleRequest); routedRpc.Register<ZPackage>("sighsorry.ZoneSavior_AutoArchiveCommandResult", (Action<long, ZPackage>)RPC_HandleResult); }); } private static void HandleCommand(ConsoleEventArgs args) { EnsureCommandReady(); DispatchRequest(new AutoArchiveCommandRequest { Command = ((args.Args.Length != 0) ? args.Args[0] : ""), Args = args.Args.ToList() }, args.Context); } private static void DispatchRequest(AutoArchiveCommandRequest request, Terminal? context) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown Terminal context2 = context; if (ZNet.instance.IsServer()) { StartRequest(request, delegate(AutoArchiveCommandResult result) { ShowResult(result, context2); }); return; } RegisterRpcs(); ZPackage val = new ZPackage(); val.Write(ZoneBundleSerialization.Serialize(request)); ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "sighsorry.ZoneSavior_AutoArchiveCommandRequest", new object[1] { val }); Terminal obj = context2; if (obj != null) { obj.AddString(request.Command + " request sent to server."); } } private static void RPC_HandleRequest(long sender, ZPackage package) { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return; } try { if (!IsAuthorizedSender(sender)) { SendResult(sender, AutoArchiveCommandResult.Fail("Admin only.")); return; } StartRequest(ZoneBundleSerialization.Deserialize<AutoArchiveCommandRequest>(package.ReadString()), delegate(AutoArchiveCommandResult result) { SendResult(sender, result); }); } catch (Exception ex) { _logger.LogError((object)$"Auto archive command RPC failed: {ex}"); SendResult(sender, AutoArchiveCommandResult.Fail(ex.Message)); } } private static void SendResult(long target, AutoArchiveCommandResult result) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Expected O, but got Unknown if (ZRoutedRpc.instance != null) { ZPackage val = new ZPackage(); val.Write(ZoneBundleSerialization.Serialize(result)); ZRoutedRpc.instance.InvokeRoutedRPC(target, "sighsorry.ZoneSavior_AutoArchiveCommandResult", new object[1] { val }); } } private static void RPC_HandleResult(long sender, ZPackage package) { if (!((Object)(object)ZNet.instance != (Object)null) || !ZNet.instance.IsServer()) { ShowResult(ZoneBundleSerialization.Deserialize<AutoArchiveCommandResult>(package.ReadString()), (Terminal?)(object)Console.instance); } } private static void StartRequest(AutoArchiveCommandRequest request, Action<AutoArchiveCommandResult> onComplete) { onComplete(ExecuteRequest(request)); } private static AutoArchiveCommandResult ExecuteRequest(AutoArchiveCommandRequest request) { List<string> messages = new List<string>(); try { string text = request.Command; string[] array = request.Args?.ToArray() ?? Array.Empty<string>(); if (string.IsNullOrWhiteSpace(text) && array.Length != 0) { text = array[0]; } switch (text) { case "zs_scan": ExecuteScan(array, messages); break; case "zs_status": ExecuteStatus(messages); break; case "zs_debugzone": ExecuteDebugZone(array, messages); break; default: throw new InvalidOperationException("Unsupported auto archive command '" + text + "'."); } return AutoArchiveCommandResult.Ok(messages); } catch (Exception ex) { _logger.LogError((object)$"Auto archive command '{request.Command}' failed: {ex}"); return AutoArchiveCommandResult.Fail(ex.Message); } } private static void ExecuteScan(string[] args, List<string> messages) { IEnumerable<string> args2 = args.Skip(1); List<long> list = null; string targetLabel = ""; if (args.Length > 1 && !IsArchiveModeToken(args[1])) { list = ResolveTargetPlayerIds(args[1], out targetLabel); args2 = args.Skip(2); } ParseMode(args2, out var dryRun, out var reset); if (!((list == null) ? AutoArchiveService.QueueManualScan(dryRun, reset) : AutoArchiveService.QueueManualScan(dryRun, reset, list))) { messages.Add("Auto archive scan could not be started. World may not be ready or another scan is running."); } else { messages.Add((list == null) ? "Auto archive scan started." : ("Auto archive scan started for " + targetLabel + ".")); } } private static void ExecuteStatus(List<string> messages) { AutoArchiveStatusReport autoArchiveStatusReport = BuildStatusReport(); string text = WriteStatusReport(autoArchiveStatusReport); messages.Add($"Archive status report: {autoArchiveStatusReport.Runs.Count}/{autoArchiveStatusReport.TotalRuns} run(s)."); if (autoArchiveStatusReport.Runs.Count > 0) { ArchiveRunRecord archiveRunRecord = autoArchiveStatusReport.Runs[0]; messages.Add($"Latest: {archiveRunRecord.RunId}, dry={archiveRunRecord.DryRun}, reset={archiveRunRecord.ResetAfterSave}, candidates={archiveRunRecord.CandidateZones}, processed={archiveRunRecord.ProcessedZones}, clusters={archiveRunRecord.Clusters.Count}."); } messages.Add("Wrote YAML: " + text); } private static AutoArchiveStatusReport BuildStatusReport() { List<ArchiveRunRecord> runs = AutoArchiveStore.State.Runs.OrderByDescending((ArchiveRunRecord run) => run.CreatedUtc).ToList(); AutoArchiveStatusReport autoArchiveStatusReport = new AutoArchiveStatusReport(); ZNet instance = ZNet.instance; autoArchiveStatusReport.World = ((instance != null) ? instance.GetWorldName() : null) ?? "unknown"; autoArchiveStatusReport.CreatedAt = ZoneSaviorTimestamp.Format(DateTime.UtcNow); autoArchiveStatusReport.TotalRuns = AutoArchiveStore.State.Runs.Count; autoArchiveStatusReport.Runs = runs; return autoArchiveStatusReport; } private static string WriteStatusReport(AutoArchiveStatusReport report) { string text = Path.Combine(ZoneSaviorPlugin.DataStorageFullPath, "Diagnostics"); Directory.CreateDirectory(text); string path = "archive_status_" + DateTime.UtcNow.ToString("yyyyMMdd_HHmmss", CultureInfo.InvariantCulture) + ".yml"; string text2 = Path.Combine(text, path); File.WriteAllText(text2, ZoneBundleSerialization.Serialize(report)); return text2; } private static void ExecuteDebugZone(string[] args, List<string> messages) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0029: 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) Vector2i val = ParseZoneSpec(args.Skip(1)); AutoArchiveZoneDebugReport autoArchiveZoneDebugReport = BuildZoneDebugReport(val); string text = WriteZoneDebugReport(autoArchiveZoneDebugReport); messages.Add($"Archive debug zone ({val.x},{val.y}): zdo={autoArchiveZoneDebugReport.Summary.TotalZdos}, candidatePieces={autoArchiveZoneDebugReport.Summary.AutoArchiveCandidatePieces}, creators={autoArchiveZoneDebugReport.Summary.CandidateCreators}, wouldCandidate={autoArchiveZoneDebugReport.Summary.WouldBeCandidateZone}."); messages.Add("Reason: " + autoArchiveZoneDebugReport.Summary.Reason); messages.Add("Wrote YAML: " + text); } private static AutoArchiveZoneDebugReport BuildZoneDebugReport(Vector2i zone) { //IL_003d: 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_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) if (ZDOMan.instance == null || (Object)(object)ZNetScene.instance == (Object)null || (Object)(object)ZoneSystem.instance == (Object)null) { throw new InvalidOperationException("World ZDO systems are not ready."); } List<ZDO> list = new List<ZDO>(); ZDOMan.instance.FindObjects(zone, list); DateTime utcNow = DateTime.UtcNow; AutoArchiveZoneDebugReport autoArchiveZoneDebugReport = new AutoArchiveZoneDebugReport(); ZNet instance = ZNet.instance; autoArchiveZoneDebugReport.World = ((instance != null) ? instance.GetWorldName() : null) ?? "unknown"; autoArchiveZoneDebugReport.CreatedAt = ZoneSaviorTimestamp.Format(utcNow); autoArchiveZoneDebugReport.Zone = ZoneSaviorZones.ToModel(zone); autoArchiveZoneDebugReport.Settings = new AutoArchiveZoneDebugSettings { DryRun = AutoArchiveConfig.DryRun, ResetAfterSave = AutoArchiveConfig.ResetAfterSave, InactiveDays = AutoArchiveConfig.InactiveDays, MinimumPiecesPerCluster = AutoArchiveConfig.MinimumPiecesPerCluster, MaxZonesPerRun = AutoArchiveConfig.MaxZonesPerRun }; AutoArchiveZoneDebugReport autoArchiveZoneDebugReport2 = autoArchiveZoneDebugReport; HashSet<ZDOID> hashSet = new HashSet<ZDOID>(); foreach (ZDO item in list) { if (item != null && item.IsValid() && hashSet.Add(item.m_uid)) { AutoArchiveZoneDebugObject autoArchiveZoneDebugObject = BuildZoneDebugObject(zone, item); autoArchiveZoneDebugReport2.Objects.Add(autoArchiveZoneDebugObject); AddExclusionCounts(autoArchiveZoneDebugReport2.ExclusionCounts, autoArchiveZoneDebugObject.ExclusionReasons); } } List<long> list2 = (from playerId in (from entry in autoArchiveZoneDebugReport2.Objects where entry.AutoArchiveCandidatePiece select entry.CreatorPlayerId into playerId where playerId != 0 select playerId).Distinct() orderby playerId select playerId).ToList(); autoArchiveZoneDebugReport2.Creators = list2.Select((long playerId) => BuildCreatorDebug(playerId, utcNow)).ToList(); bool flag = autoArchiveZoneDebugReport2.Creators.Count > 0 && autoArchiveZoneDebugReport2.Creators.All((AutoArchiveZoneDebugCreator creator) => creator.Eligible); autoArchiveZoneDebugReport2.Summary = new AutoArchiveZoneDebugSummary { TotalZdos = autoArchiveZoneDebugReport2.Objects.Count, InRequestedZone = autoArchiveZoneDebugReport2.Objects.Count((AutoArchiveZoneDebugObject entry) => entry.InRequestedZone), WearNTear = autoArchiveZoneDebugReport2.Objects.Count((AutoArchiveZoneDebugObject entry) => entry.HasWearNTear), PlayerBuildRecipe = autoArchiveZoneDebugReport2.Objects.Count((AutoArchiveZoneDebugObject entry) => entry.HasBuildRecipe), AutoArchiveCandidatePieces = autoArchiveZoneDebugReport2.Objects.Count((AutoArchiveZoneDebugObject entry) => entry.AutoArchiveCandidatePiece), CandidateCreators = list2.Count, ObjectDbReady = ((Object)(object)ObjectDB.instance != (Object)null), WouldBeCandidateZone = (autoArchiveZoneDebugReport2.Objects.Any((AutoArchiveZoneDebugObject entry) => entry.AutoArchiveCandidatePiece) && flag) }; autoArchiveZoneDebugReport2.Summary.Reason = BuildDebugSummaryReason(autoArchiveZoneDebugReport2, flag); return autoArchiveZoneDebugReport2; } private static AutoArchiveZoneDebugObject BuildZoneDebugObject(Vector2i requestedZone, ZDO zdo) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) ZoneStructureInfo zoneStructureInfo = ZoneStructureClassifier.Inspect(zdo, requestedZone); AutoArchiveZoneDebugObject autoArchiveZoneDebugObject = new AutoArchiveZoneDebugObject(); autoArchiveZoneDebugObject.ZdoId = zoneStructureInfo.ZdoId; autoArchiveZoneDebugObject.PrefabHash = zoneStructureInfo.PrefabHash; autoArchiveZoneDebugObject.Prefab = zoneStructureInfo.Prefab; autoArchiveZoneDebugObject.Position = new float[3] { Round(zoneStructureInfo.Position.x), Round(zoneStructureInfo.Position.y), Round(zoneStructureInfo.Position.z) }; autoArchiveZoneDebugObject.ObjectZone = ZoneSaviorZones.ToModel(zoneStructureInfo.ObjectZone); autoArchiveZoneDebugObject.CreatorPlayerId = zoneStructureInfo.CreatorPlayerId; autoArchiveZoneDebugObject.CreatorName = zoneStructureInfo.CreatorName; autoArchiveZoneDebugObject.HasPrefab = zoneStructureInfo.HasPrefab; autoArchiveZoneDebugObject.HasZNetView = zoneStructureInfo.HasZNetView; autoArchiveZoneDebugObject.HasWearNTear = zoneStructureInfo.HasWearNTear; autoArchiveZoneDebugObject.HasPiece = zoneStructureInfo.HasPiece; autoArchiveZoneDebugObject.HasBuildRecipe = zoneStructureInfo.HasBuildRecipe; autoArchiveZoneDebugObject.InRequestedZone = zoneStructureInfo.InRequestedZone; autoArchiveZoneDebugObject.AutoArchiveCandidatePiece = zoneStructureInfo.AutoArchiveCandidatePiece; autoArchiveZoneDebugObject.ExclusionReasons = zoneStructureInfo.ExclusionReasons; return autoArchiveZoneDebugObject; } private static AutoArchiveZoneDebugCreator BuildCreatorDebug(long playerId, DateTime utcNow) { AutoArchiveCreatorEligibility autoArchiveCreatorEligibility = AutoArchiveStore.EvaluateCreatorArchiveEligibility(playerId, utcNow, AutoArchiveConfig.InactiveDays, recordUnknownPlayer: false); return new AutoArchiveZoneDebugCreator { PlayerId = autoArchiveCreatorEligibility.PlayerId, PlatformId = autoArchiveCreatorEligibility.PlatformId, Names = autoArchiveCreatorEligibility.Names, RecordedInActivity = autoArchiveCreatorEligibility.RecordedInActivity, UnknownActivityRecord = autoArchiveCreatorEligibility.UnknownActivityRecord, Protected = autoArchiveCreatorEligibility.Protected, Eligible = autoArchiveCreatorEligibility.Eligible, Reason = autoArchiveCreatorEligibility.Reason }; } private static string BuildDebugSummaryReason(AutoArchiveZoneDebugReport report, bool allCreatorsEligible) { if (report.Summary.AutoArchiveCandidatePieces == 0) { return "No WearNTear with a non-zero creator and a registered player build recipe was found in this zone."; } if (!allCreatorsEligible) { string text = string.Join("; ", from creator in report.Creators where !creator.Eligible select creator.Reason); return "At least one candidate creator is not archive-eligible: " + text; } if (report.Summary.AutoArchiveCandidatePieces < AutoArchiveConfig.MinimumPiecesPerCluster) { return $"Zone has candidate pieces, but this single-zone piece count is below Minimum Pieces Per Cluster ({report.Summary.AutoArchiveCandidatePieces}/{AutoArchiveConfig.MinimumPiecesPerCluster}). Cluster adjacency may still change the final action."; } return "This zone would be an auto archive candidate before cluster adjacency and max-zones-per-run checks."; } private static string WriteZoneDebugReport(AutoArchiveZoneDebugReport report) { string text = Path.Combine(ZoneSaviorPlugin.DataStorageFullPath, "Diagnostics"); Directory.CreateDirectory(text); string path = string.Format("archive_debug_zone_{0}_{1}_{2}.yml", report.Zone.X, report.Zone.Z, DateTime.UtcNow.ToString("yyyyMMdd_HHmmss", CultureInfo.InvariantCulture)); string text2 = Path.Combine(text, path); File.WriteAllText(text2, ZoneBundleSerialization.Serialize(report)); return text2; } private static Vector2i ParseZoneSpec(IEnumerable<string> values) { //IL_0072: Unknown result type (might be due to invalid IL or missing references) string input = string.Join(" ", values).Trim(); Match match = ZoneSpecPattern.Match(input); if (!match.Success || !int.TryParse(match.Groups[1].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || !int.TryParse(match.Groups[2].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2)) { throw new InvalidOperationException("Syntax: zs_debugzone (x,z)"); } return new Vector2i(result, result2); } private static void AddExclusionCounts(Dictionary<string, int> counts, IEnumerable<string> reasons) { foreach (string reason in reasons) { counts.TryGetValue(reason, out var value); counts[reason] = value + 1; } } private static float Round(float value) { return Mathf.Round(value * 1000f) / 1000f; } private static void ParseMode(IEnumerable<string> args, out bool? dryRun, out bool? reset) { dryRun = null; reset = null; foreach (string arg in args) { if (IsArchiveModeToken(arg, "dry") || IsArchiveModeToken(arg, "dry-run")) { dryRun = true; reset = false; continue; } if (IsArchiveModeToken(arg, "save")) { dryRun = false; reset = false; continue; } if (IsArchiveModeToken(arg, "reset")) { dryRun = false; reset = true; continue; } throw new InvalidOperationException("Unknown archive mode '" + arg + "'. Use dry, save, or reset."); } } private static bool IsArchiveModeToken(string arg) { if (!IsArchiveModeToken(arg, "dry") && !IsArchiveModeToken(arg, "dry-run") && !IsArchiveModeToken(arg, "save")) { return IsArchiveModeToken(arg, "reset"); } return true; } private static bool IsArchiveModeToken(string arg, string expected) { return string.Equals(arg, expected, StringComparison.OrdinalIgnoreCase); } private static List<long> ResolveTargetPlayerIds(string target, out string targetLabel) { if (AutoArchiveStore.TryGetPlayerIdsBySteamId(target, out List<long> playerIds, out string normalizedSteamId)) { targetLabel = "steamID " + normalizedSteamId + " (playerID " + string.Join(", ", playerIds) + ")"; return playerIds; } if (ZoneSaviorSteamIds.LooksLikeSteamId(target)) { throw new InvalidOperationException("No known playerID is linked to SteamID " + target + ". The player must have joined while ZoneSavior activity tracking was active."); } throw new InvalidOperationException("Syntax: zs_scan [steamID] [dry|save|reset]"); } private static void EnsureCommandReady() { if ((Object)(object)ZNet.instance == (Object)null) { throw new InvalidOperationException("World is not ready."); } if (!ZNet.instance.IsServer() && ZRoutedRpc.instance == null) { throw new InvalidOperationException("Server RPC is not ready."); } if (!ZNet.instance.IsServer() || (ZNet.instance.IsServer() && (Object)(object)Player.m_localPlayer == (Object)null) || ZNet.instance.LocalPlayerIsAdminOrHost()) { return; } throw new InvalidOperationException("Admin only."); } private static bool IsAuthorizedSender(long sender) { ZNetPeer peer = ZNet.instance.GetPeer(sender); object obj; if (peer == null) { obj = null; } else { ZRpc rpc = peer.m_rpc; if (rpc == null) { obj = null; } else { ISocket socket = rpc.m_socket; obj = ((socket != null) ? socket.GetHostName() : null); } } if (obj == null) { obj = ""; } string text = (string)obj; if (text.Length > 0) { return ZNet.instance.IsAdmin(text); } return false; } private static void ShowResult(AutoArchiveCommandResult result, Terminal? terminal) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) MessageType val = (MessageType)(result.Success ? 1 : 2); foreach (string item in (result.Messages.Count > 0) ? result.Messages : new List<string>(1) { result.Success ? "Done." : "Command failed." }) { _logger.LogInfo((object)item); if (terminal != null) { terminal.AddString(item); } if ((Object)(object)Player.m_localPlayer != (Object)null) { ((Character)Player.m_localPlayer).Message(val, item, 0, (Sprite)null); } } } } internal sealed class AutoArchiveCommandRequest { public string Command { get; set; } = ""; public List<string> Args { get; set; } = new List<string>(); } internal sealed class AutoArchiveCommandResult { public bool Success { get; set; } public List<string> Messages { get; set; } = new List<string>(); public static AutoArchiveCommandResult Ok(IEnumerable<string> messages) { return new AutoArchiveCommandResult { Success = true, Messages = messages.ToList() }; } public static AutoArchiveCommandResult Fail(string message) { return new AutoArchiveCommandResult { Success = false, Messages = new List<string>(1) { message } }; } } internal sealed class AutoArchiveState { public int Version { get; set; } = 1; [YamlIgnore] public DateTime LastScanUtc { get; set; } = DateTime.MinValue; public string LastScanAt { get { return ZoneSaviorTimestamp.Format(LastScanUtc); } set { LastScanUtc = ZoneSaviorTimestamp.ParseUtc(value); } } [YamlIgnore] public DateTime LastAutoScanUtc { get; set; } = DateTime.MinValue; public string LastAutoScanAt { get { return ZoneSaviorTimestamp.Format(LastAutoScanUtc); } set { LastAutoScanUtc = ZoneSaviorTimestamp.ParseUtc(value); } } public List<PlayerActivityRecord> Players { get; set; } = new List<PlayerActivityRecord>(); public List<ArchiveRunRecord> Runs { get; set; } = new List<ArchiveRunRecord>(); } internal sealed class PlayerActivityRecord { public string PlatformId { get; set; } = ""; public List<long> PlayerIds { get; set; } = new List<long>(); public List<string> Names { get; set; } = new List<string>(); [YamlIgnore] public DateTime LastSeenUtc { get; set; } = DateTime.MinValue; public string LastSeenAt { get { return ZoneSaviorTimestamp.Format(LastSeenUtc); } set { LastSeenUtc = ZoneSaviorTimestamp.ParseUtc(value); } } } internal sealed class ArchiveRunRecord { public string RunId { get; set; } = ""; [YamlIgnore] public DateTime CreatedUtc { get; set; } = DateTime.MinValue; public string CreatedAt { get { return ZoneSaviorTimestamp.Format(CreatedUtc); } set { CreatedUtc = ZoneSaviorTimestamp.ParseUtc(value); } } public bool Manual { get; set; } public bool DryRun { get; set; } public bool ResetAfterSave { get; set; } public List<long> TargetPlayerIds { get; set; } = new List<long>(); public int ScannedZdos { get; set; } public int StructureZdos { get; set; } public int CandidateZones { get; set; } public int ProcessedZones { get; set; } public List<ArchiveClusterRecord> Clusters { get; set; } = new List<ArchiveClusterRecord>(); public List<string> Messages { get; set; } = new List<string>(); } internal sealed class AutoArchiveStatusReport { public int Version { get; set; } = 1; public string World { get; set; } = ""; public string CreatedAt { get; set; } = ""; public int TotalRuns { get; set; } public List<ArchiveRunRecord> Runs { get; set; } = new List<ArchiveRunRecord>(); } internal sealed class ArchiveClusterRecord { public string Tag { get; set; } = ""; public string Status { get; set; } = ""; public string Reason { get; set; } = ""; public int PieceCount { get; set; } public int TerrainLoaded { get; set; } public int TerrainCaptured { get; set; } public List<long> Creators { get; set; } = new List<long>(); public List<ZoneBundleZone> Zones { get; set; } = new List<ZoneBundleZone>(); } internal sealed class AutoArchiveScanOptions { public bool Manual { get; set; } public bool DryRun { get; set; } public bool ResetAfterSave { get; set; } public List<long> TargetPlayerIds { get; set; } = new List<long>(); } internal sealed class AutoArchiveCreatorEligibility { public long PlayerId { get; set; } public string PlatformId { get; set; } = ""; public List<string> Names { get; set; } = new List<string>(); public bool RecordedInActivity { get; set; } public bool UnknownActivityRecord { get; set; } public bool Protected { get; set; } public bool Eligible { get; set; } public string Reason { get; set; } = ""; } internal sealed class ZoneBundleArchiveResult { public bool Success { get; set; } public string Message { get; set; } = ""; public string Tag { get; set; } = ""; public string ManifestPath { get; set; } = ""; public int ZoneCount { get; set; } public int EntryCount { get; set; } public int MonsterCount { get; set; } public int TerrainLoaded { get; set; } public int TerrainCaptured { get; set; } } internal sealed class ZoneBundleResetResult { public bool Success { get; set; } public string Message { get; set; } = ""; public int ZoneCount { get; set; } public int RemovedCount { get; set; } public int RemainingWearNTearCount { get; set; } } internal sealed class AutoArchiveZoneDebugReport { public int Version { get; set; } = 1; public string World { get; set; } = ""; public string CreatedAt { get; set; } = ""; public ZoneBundleZone Zone { get; set; } = new ZoneBundleZone(); public AutoArchiveZoneDebugSettings Settings { get; set; } = new AutoArchiveZoneDebugSettings(); public AutoArchiveZoneDebugSummary Summary { get; set; } = new AutoArchiveZoneDebugSummary(); public List<AutoArchiveZoneDebugCreator> Creators { get; set; } = new List<AutoArchiveZoneDebugCreator>(); public Dictionary<string, int> ExclusionCounts { get; set; } = new Dictionary<string, int>(); public List<AutoArchiveZoneDebugObject> Objects { get; set; } = new List<AutoArchiveZoneDebugObject>(); } internal sealed class AutoArchiveZoneDebugSettings { public bool DryRun { get; set; } public bool ResetAfterSave { get; set; } public int InactiveDays { get; set; } public int MinimumPiecesPerCluster { get; set; } public int MaxZonesPerRun { get; set; } } internal sealed class AutoArchiveZoneDebugSummary { public int TotalZdos { get; set; } public int InRequestedZone { get; set; } public int WearNTear { get; set; } public int PlayerBuildRecipe { get; set; } public int AutoArchiveCandidatePieces { get; set; } public int CandidateCreators { get; set; } public bool ObjectDbReady { get; set; } public bool WouldBeCandidateZone { get; set; } public string Reason { get; set; } = ""; } internal sealed class AutoArchiveZoneDebugCreator { public long PlayerId { get; set; } public string PlatformId { get; set; } = ""; public List<string> Names { get; set; } = new List<string>(); public bool RecordedInActivity { get; set; } public bool UnknownActivityRecord { get; set; } public bool Protected { get; set; } public bool Eligible { get; set; } public string Reason { get; set; } = ""; } internal sealed class AutoArchiveZoneDebugObject { public string ZdoId { get; set; } = ""; public int PrefabHash { get; set; } public string Prefab { get; set; } = ""; public float[] Position { get; set; } = new float[3]; public ZoneBundleZone ObjectZone { get; set; } = new ZoneBundleZone(); public long CreatorPlayerId { get; set; } public string CreatorName { get; set; } = ""; public bool HasPrefab { get; set; } public bool HasZNetView { get; set; } public bool HasWearNTear { get; set; } public bool HasPiece { get; set; } public bool HasBuildRecipe { get; set; } public bool InRequestedZone { get; set; } public bool AutoArchiveCandidatePiece { get; set; } public List<string> ExclusionReasons { get; set; } = new List<string>(); } internal sealed class AutoArchiveScanPolicy { public bool DryRun { get; } public bool ResetAfterSave { get; } public HashSet<long> TargetPlayerIds { get; } public bool IsTargetOverride => TargetPlayerIds.Count > 0; public bool UsesMinimumClusterSize => !IsTargetOverride; public bool RequiresCreatorEligibility => !IsTargetOverride; public bool BlocksMixedOwnerReset { get { if (IsTargetOverride) { return ResetAfterSave; } return false; } } private AutoArchiveScanPolicy(AutoArchiveScanOptions options) { DryRun = options.DryRun; ResetAfterSave = options.ResetAfterSave; TargetPlayerIds = options.TargetPlayerIds.ToHashSet(); } public static AutoArchiveScanPolicy FromOptions(AutoArchiveScanOptions options) { return new AutoArchiveScanPolicy(options); } public bool IncludesTargetCreator(IEnumerable<long> creators) { return creators.Any(TargetPlayerIds.Contains); } public bool IsSmallCluster(ArchiveClusterRecord record) { if (UsesMinimumClusterSize) { return record.PieceCount < AutoArchiveConfig.MinimumPiecesPerCluster; } return false; } public string DryRunCandidateReason() { if (!IsTargetOverride) { return "candidate only; dry run is enabled"; } return "target override candidate; dry run is enabled"; } } internal static class AutoArchiveScanner { private sealed class AutoArchiveZoneInfo { public Vector2i Zone { get; } public int PieceCount { get; set; } public HashSet<long> Creators { get; } = new HashSet<long>(); public Dictionary<long, int> CreatorPieceCounts { get; } = new Dictionary<long, int>(); public AutoArchiveZoneInfo(Vector2i zone) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) Zone = zone; } public void AddCreator(long creator) { PieceCount++; Creators.Add(creator); CreatorPieceCounts.TryGetValue(creator, out var value); CreatorPieceCounts[creator] = value + 1; } } [CompilerGenerated] private sealed class <>c__DisplayClass3_0 { public ZoneBundleArchiveResult saveResult; internal void <Run>b__4(ZoneBundleArchiveResult result) { saveResult = result; } } [CompilerGenerated] private sealed class <>c__DisplayClass3_1 { public ZoneBundleResetResult resetResult; internal void <Run>b__5(ZoneBundleResetResult result) { resetResult = result; } } [CompilerGenerated] private sealed class <Run>d__3 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public AutoArchiveScanOptions options; public Action<ArchiveRunRecord> onComplete; private <>c__DisplayClass3_0 <>8__1; private <>c__DisplayClass3_1 <>8__2; private DateTime <utcNow>5__2; private ArchiveRunRecord <run>5__3; private Dictionary<Vector2i, AutoArchiveZoneInfo> <zoneInfos>5__4; private AutoArchiveScanPolicy <policy>5__5; private Dictionary<Vector2i, AutoArchiveZoneInfo> <candidates>5__6; private int <processedZones>5__7; private int <clusterIndex>5__8; private IEnumerator<List<Vector2i>> <>7__wrap8; private List<Vector2i> <cluster>5__10; private ArchiveClusterRecord <record>5__11; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <Run>d__3(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if (num == -3 || (uint)(num - 2) <= 5u) { try { } finally { <>m__Finally1(); } } <>8__1 = null; <>8__2 = null; <run>5__3 = null; <zoneInfos>5__4 = null; <policy>5__5 = null; <candidates>5__6 = null; <>7__wrap8 = null; <cluster>5__10 = null; <record>5__11 = null; <>1__state = -2; } private bool MoveNext() { try { switch (<>1__state) { default: return false; case 0: <>1__state = -1; <utcNow>5__2 = DateTime.UtcNow; <run>5__3 = new ArchiveRunRecord { RunId = <utcNow>5__2.ToString("yyyyMMdd_HHmmss", CultureInfo.InvariantCulture), CreatedUtc = <utcNow>5__2, Manual = options.Manual, DryRun = options.DryRun, ResetAfterSave = options.ResetAfterSave, TargetPlayerIds = options.TargetPlayerIds.ToList() }; if (!IsWorldReady()) { <run>5__3.Messages.Add("World is not ready."); onComplete(<run>5__3); return false; } PlayerActivityTracker.TrackOnlinePlayers(<utcNow>5__2); <zoneInfos>5__4 = new Dictionary<Vector2i, AutoArchiveZoneInfo>(); <>2__current = ScanZdosBySector(<utcNow>5__2, <zoneInfos>5__4, <run>5__3); <>1__state = 1; return true; case 1: { <>1__state = -1; <policy>5__5 = AutoArchiveScanPolicy.FromOptions(options); <candidates>5__6 = BuildCandidateZones(<zoneInfos>5__4, <utcNow>5__2, <policy>5__5, <run>5__3); <run>5__3.CandidateZones = <candidates>5__6.Count; List<List<Vector2i>> source = BuildClusters(<candidates>5__6); <processedZones>5__7 = 0; <clusterIndex>5__8 = 0; <>7__wrap8 = (from cluster in source orderby cluster.Min((Vector2i zone) => zone.x), cluster.Min((Vector2i zone) => zone.y) select cluster).GetEnumerator(); <>1__state = -3; break; } case 2: <>1__state = -3; break; case 3: <>1__state = -3; if (<>8__1.saveResult == null) { <record>5__11.Status = "failed"; <record>5__11.Reason = "save failed: archive coroutine did not return a result"; <run>5__3.Clusters.Add(<record>5__11); _logger.LogError((object)("Auto archive save failed for tag '" + <record>5__11.Tag + "': archive coroutine did not return a result.")); <>2__current = null; <>1__state = 4; return true; } <record>5__11.TerrainLoaded = <>8__1.saveResult.TerrainLoaded; <record>5__11.TerrainCaptured = <>8__1.saveResult.TerrainCaptured; if (!<>8__1.saveResult.Success) { <record>5__11.Status = "failed"; <record>5__11.Reason = <>8__1.saveResult.Message; <run>5__3.Clusters.Add(<record>5__11); break; } <record>5__11.Status = "saved"; <record>5__11.Reason = <>8__1.saveResult.Message; if (options.ResetAfterSave) { <>8__2 = new <>c__DisplayClass3_1(); <>8__2.resetResult = null; <>2__current = ZoneBundleCommands.ResetGeneratedZonesAsync(<cluster>5__10, delegate(ZoneBundleResetResult result) { <>8__2.resetResult = result; }); <>1__state = 5; return true; } goto IL_0800; case 4: <>1__state = -3; break; case 5: <>1__state = -3; if (<>8__2.resetResult == null) { <record>5__11.Status = "saved-reset-failed"; <record>5__11.Reason = "reset failed: reset coroutine did not return a result"; <run>5__3.Clusters.Add(<record>5__11); _logger.LogError((object)("Auto archive reset failed for tag '" + <record>5__11.Tag + "': reset coroutine did not return a result.")); <>2__current = null; <>1__state = 6; return true; } <record>5__11.Status = (<>8__2.resetResult.Success ? "reset" : "saved-reset-failed"); <record>5__11.Reason = <>8__2.resetResult.Message; <>8__2 = null; goto IL_0800; case 6: <>1__state = -3; break; case 7: { <>1__state = -3; <>8__1 = null; <record>5__11 = null; <cluster>5__10 = null; break; } IL_0800: <run>5__3.Clusters.Add(<record>5__11); <processedZones>5__7 += <cluster>5__10.Count; <>2__current = null; <>1__state = 7; return true; } while (<>7__wrap8.MoveNext()) { <cluster>5__10 = <>7__wrap8.Current; <>8__1 = new <>c__DisplayClass3_0(); <clusterIndex>5__8++; <record>5__11 = BuildClusterRecord(<cluster>5__10, <candidates>5__6); bool flag = <policy>5__5.IsSmallCluster(<record>5__11); if (<processedZones>5__7 + <cluster>5__10.Count > AutoArchiveConfig.MaxZonesPerRun) { <record>5__11.Status = "skipped"; <record>5__11.Reason = $"max zones per run would be exceeded ({<processedZones>5__7 + <cluster>5__10.Count}/{AutoArchiveConfig.MaxZonesPerRun})"; <run>5__3.Clusters.Add(<record>5__11); continue; } if (<policy>5__5.RequiresCreatorEligibility && !AllCreatorsEligible(<record>5__11.Creators, <utcNow>5__2, out string reason)) { <record>5__11.Status = "skipped"; <record>5__11.Reason = reason; <run>5__3.Clusters.Add(<record>5__11); continue; } if (flag) { if (!options.ResetAfterSave) { <record>5__11.Status = "skipped"; <record>5__11.Reason = $"piece count {<record>5__11.PieceCount} is below minimum {AutoArchiveConfig.MinimumPiecesPerCluster}; reset mode is not enabled"; <run>5__3.Clusters.Add(<record>5__11); continue; } if (options.DryRun) { <record>5__11.Status = "dry-run-reset-without-save"; <record>5__11.Reason = $"small inactive cluster would be reset without saving ({<record>5__11.PieceCount}/{AutoArchiveConfig.MinimumPiecesPerCluster} pieces)"; <run>5__3.Clusters.Add(<record>5__11); <processedZones>5__7 += <cluster>5__10.Count; continue; } ZoneBundleResetResult zoneBundleResetResult = ZoneBundleCommands.ResetGeneratedZones(<cluster>5__10); <record>5__11.Status = (zoneBundleResetResult.Success ? "reset-without-save" : "reset-without-save-failed"); <record>5__11.Reason = zoneBundleResetResult.Message; <run>5__3.Clusters.Add(<record>5__11); <processedZones>5__7 += <cluster>5__10.Count; <>2__current = null; <>1__state = 2; return true; } <record>5__11.Tag = ZoneBundleCommands.MakeUniqueAutoArchiveTag(BuildTag(<clusterIndex>5__8, <cluster>5__10, <candidates>5__6)); if (options.DryRun) { <record>5__11.Status = "dry-run"; <record>5__11.Reason = <policy>5__5.DryRunCandidateReason(); <run>5__3.Clusters.Add(<record>5__11); <processedZones>5__7 += <cluster>5__10.Count; continue; } <>8__1.saveResult = null; <>2__current = ZoneBundleCommands.SaveZonesAsync(<cluster>5__10, <record>5__11.Tag, delegate(ZoneBundleArchiveResult result) { <>8__1.saveResult = result; }, 0L); <>1__state = 3; return true; } <>m__Finally1(); <>7__wrap8 = null; <run>5__3.ProcessedZones = <processedZones>5__7; onComplete(<run>5__3); return false; } catch { //try-fault ((IDisposable)this).Dispose(); throw; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } private void <>m__Finally1() { <>1__state = -1; if (<>7__wrap8 != null) { <>7__wrap8.Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class <ScanZdosBySector>d__5 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public DateTime utcNow; public Dictionary<Vector2i, AutoArchiveZoneInfo> zoneInfos; public ArchiveRunRecord run; private int <batchSize>5__2; private int <processedSinceYield>5__3; private List<ZDO>[] <sectors>5__4; private int <sectorIndex>5__5; private List<ZDO> <sector>5__6; private int <objectIndex>5__7; private List<List<ZDO>>.Enumerator <>7__wrap7; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <ScanZdosBySector>d__5(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if (num == -3 || num == 2) { try { } finally { <>m__Finally1(); } } <sectors>5__4 = null; <sector>5__6 = null; <>7__wrap7 = default(List<List<ZDO>>.Enumerator); <>1__state = -2; } private bool MoveNext() { try { List<List<ZDO>> list; switch (<>1__state) { default: return false; case 0: <>1__state = -1; <batchSize>5__2 = Math.Max(1, AutoArchiveConfig.ScannerBatchSize); <processedSinceYield>5__3 = 0; <sectors>5__4 = ZDOMan.instance.m_objectsBySector; <sectorIndex>5__5 = 0; goto IL_0138; case 1: <>1__state = -1; goto IL_00fb; case 2: { <>1__state = -3; goto IL_022d; } IL_0138: if (<sectors>5__4 != null && <sectorIndex>5__5 < <sectors>5__4.Length) { <sector>5__6 = <sectors>5__4[<sectorIndex>5__5]; if (<sector>5__6 != null && <sector>5__6.Count != 0) { <objectIndex>5__7 = 0; goto IL_010b; } goto IL_0128; } list = ZDOMan.instance.m_objectsByOutsideSector?.Values.ToList() ?? new List<List<ZDO>>(); <>7__wrap7 = list.GetEnumerator(); <>1__state = -3; goto IL_025a; IL_00fb: <objectIndex>5__7++; goto IL_010b; IL_0128: <sectorIndex>5__5++; goto IL_0138; IL_025a: do { if (<>7__wrap7.MoveNext()) { <sector>5__6 = <>7__wrap7.Current; continue; } <>m__Finally1(); <>7__wrap7 = default(List<List<ZDO>>.Enumerator); return false; } while (<sector>5__6 == null || <sector>5__6.Count == 0); <sectorIndex>5__5 = 0; goto IL_023d; IL_010b: if (<objectIndex>5__7 < <sector>5__6.Count) { ProcessScanZdo(<sector>5__6[<objectIndex>5__7], utcNow, zoneInfos, run); <processedSinceYield>5__3++; if (<processedSinceYield>5__3 >= <batchSize>5__2) { <processedSinceYield>5__3 = 0; <>2__current = null; <>1__state = 1; return true; } goto IL_00fb; } <sector>5__6 = null; goto IL_0128; IL_022d: <sectorIndex>5__5++; goto IL_023d; IL_023d: if (<sectorIndex>5__5 < <sector>5__6.Count) { ProcessScanZdo(<sector>5__6[<sectorIndex>5__5], utcNow, zoneInfos, run); <processedSinceYield>5__3++; if (<processedSinceYield>5__3 >= <batchSize>5__2) { <processedSinceYield>5__3 = 0; <>2__current = null; <>1__state = 2; return true; } goto IL_022d; } <sector>5__6 = null; goto IL_025a; } } catch { //try-fault ((IDisposable)this).Dispose(); throw; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } private void <>m__Finally1() { <>1__state = -1; ((IDisposable)<>7__wrap7).Dispose(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private static readonly Vector2i[] NeighborOffsets = (Vector2i[])(object)new Vector2i[8] { new Vector2i(-1, -1), new Vector2i(0, -1), new Vector2i(1, -1), new Vector2i(-1, 0), new Vector2i(1, 0), new Vector2i(-1, 1), new Vector2i(0, 1), new Vector2i(1, 1) }; private static ManualLogSource _logger = null; public static void Initialize(ManualLogSource logger) { _logger = logger; } [IteratorStateMachine(typeof(<Run>d__3))] public static IEnumerator Run(AutoArchiveScanOptions options, Action<ArchiveRunRecord> onComplete) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <Run>d__3(0) { options = options, onComplete = onComplete }; } private static bool TryReadPlayerStructure(ZDO zdo, DateTime utcNow, out Vector2i zone, out long creator) { //IL_0001: 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_0034: Unknown result type (might be due to invalid IL or missing references) zone = default(Vector2i); creator = 0L; if (!ZoneStructureClassifier.TryInspectAutoArchiveCandidate(zdo, out ZoneStructureInfo info)) { return false; } creator = info.CreatorPlayerId; AutoArchiveStore.RecordUnknownPlayer(creator, utcNow, info.CreatorName); zone = info.ObjectZone; return true; } [IteratorStateMachine(typeof(<ScanZdosBySector>d__5))] private static IEnumerator ScanZdosBySector(DateTime utcNow, Dictionary<Vector2i, AutoArchiveZoneInfo> zoneInfos, ArchiveRunRecord run) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <ScanZdosBySector>d__5(0) { utcNow = utcNow, zoneInfos = zoneInfos, run = run }; } private static void ProcessScanZdo(ZDO zdo, DateTime utcNow, Dictionary<Vector2i, AutoArchiveZoneInfo> zoneInfos, ArchiveRunRecord run) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) run.ScannedZdos++; if (TryReadPlayerStructure(zdo, utcNow, out var zone, out var creator)) { run.StructureZdos++; if (!zoneInfos.TryGetValue(zone, out AutoArchiveZoneInfo value)) { value = (zoneInfos[zone] = new AutoArchiveZoneInfo(zone)); } value.AddCreator(creator); } } private static Dictionary<Vector2i, AutoArchiveZoneInfo> BuildCandidateZones(Dictionary<Vector2i, AutoArchiveZoneInfo> zoneInfos, DateTime utcNow, AutoArchiveScanPolicy policy, ArchiveRunRecord run) { //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) Dictionary<Vector2i, AutoArchiveZoneInfo> dictionary = new Dictionary<Vector2i, AutoArchiveZoneInfo>(); foreach (AutoArchiveZoneInfo value in zoneInfos.Values) { string reason; if (policy.IsTargetOverride) { if (policy.IncludesTargetCreator(value.Creators)) { if (policy.BlocksMixedOwnerReset && HasNonTargetCreators(value.Creators, policy.TargetPlayerIds, out List<long> nonTargetCreators)) { run.Messages.Add($"Skipped zone ({value.Zone.x},{value.Zone.y}): target reset blocked for mixed-owner zone; non-target creator(s): {FormatCreatorList(nonTargetCreators)}"); } else { dictionary[value.Zone] = value; } } } else if (!AllCreatorsEligible(value.Creators, utcNow, out reason)) { run.Messages.Add($"Skipped zone ({value.Zone.x},{value.Zone.y}): {reason}"); } else { dictionary[value.Zone] = value; } } return dictionary; } private static bool HasNonTargetCreators(IEnumerable<long> creators, HashSet<long> targetPlayerIds, out List<long> nonTargetCreators) { HashSet<long> targetPlayerIds2 = targetPlayerIds; nonTargetCreators = (from creator in creators.Where((long creator) => creator != 0L && !targetPlayerIds2.Contains(creator)).Distinct() orderby creator select creator).ToList(); return nonTargetCreators.Count > 0; } private static string FormatCreatorList(IEnumerable<long> creators) { return string.Join(", ", creators.Select((long creator) => creator.ToString(CultureInfo.InvariantCulture))); } private static List<List<Vector2i>> BuildClusters(Dictionary<Vector2i, AutoArchiveZoneInfo> candidates) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0047: 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_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0076: 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_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) List<List<Vector2i>> list = new List<List<Vector2i>>(); HashSet<Vector2i> hashSet = candidates.Keys.ToHashSet(); Vector2i item2 = default(Vector2i); while (hashSet.Count > 0) { Vector2i item = hashSet.First(); hashSet.Remove(item); List<Vector2i> list2 = new List<Vector2i>(); Queue<Vector2i> queue = new Queue<Vector2i>(); queue.Enqueue(item); while (queue.Count > 0) { Vector2i val = queue.Dequeue(); list2.Add(val); Vector2i[] neighborOffsets = NeighborOffsets; foreach (Vector2i val2 in neighborOffsets) { ((Vector2i)(ref item2))..ctor(val.x + val2.x, val.y + val2.y); if (hashSet.Contains(item2)) { hashSet.Remove(item2); queue.Enqueue(item2); } } } list.Add(list2); } return list; } private static ArchiveClusterRecord BuildClusterRecord(List<Vector2i> cluster, Dictionary<Vector2i, AutoArchiveZoneInfo> candidates) { //IL_008b: 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_0092: Unknown result type (might be due to invalid IL or missing references) ArchiveClusterRecord archiveClusterRecord = new ArchiveClusterRecord { Zones = (from zone in cluster orderby zone.y, zone.x select zone).Select(ZoneSaviorZones.ToModel).ToList() }; HashSet<long> hashSet = new HashSet<long>(); foreach (Vector2i item in cluster) { AutoArchiveZoneInfo autoArchiveZoneInfo = candidates[item]; archiveClusterRecord.PieceCount += autoArchiveZoneInfo.PieceCount; foreach (long creator in autoArchiveZoneInfo.Creators) { hashSet.Add(creator); } } archiveClusterRecord.Creators = hashSet.OrderBy((long id) => id).ToList(); return archiveClusterRecord; } private static bool AllCreatorsEligible(IEnumerable<long> creators, DateTime utcNow, out string reason) { foreach (long creator in creators) { if (!AutoArchiveStore.IsCreatorArchiveEligible(creator, utcNow, AutoArchiveConfig.InactiveDays, out reason)) { return false; } } reason = ""; return true; } private static string BuildTag(int index, List<Vector2i> cluster, Dictionary<Vector2i, AutoArchiveZoneInfo> candidates) { string arg = BuildOwnerSegment(BuildCreatorPieceCounts(cluster, candidates)); return $"auto_{arg}_c{index:D3}"; } private static Dictionary<long, int> BuildCreatorPieceCounts(List<Vector2i> cluster, Dictionary<Vector2i, AutoArchiveZoneInfo> candidates) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) Dictionary<long, int> dictionary = new Dictionary<long, int>(); foreach (Vector2i item in cluster) { foreach (KeyValuePair<long, int> creatorPieceCount in candidates[item].CreatorPieceCounts) { dictionary.TryGetValue(creatorPieceCount.Key, out var value); dictionary[creatorPieceCount.Key] = value + creatorPieceCount.Value; } } return dictionary; } private static string BuildOwnerSegment(IReadOnlyDictionary<long, int> creatorPieceCounts) { IReadOnlyDictionary<long, int> creatorPieceCounts2 = creatorPieceCounts; List<long> list = (from creator in creatorPieceCounts2.Keys.Where((long creator) => creator != 0).Distinct() orderby creator select creator).ToList(); if (list.Count == 0) { return "unknown"; } int value; string text = BuildOwnerToken((from owner in list orderby creatorPieceCounts2.TryGetValue(owner, out value) ? value : 0 descending, owner select owner).First()); if (list.Count == 1) { return text; } return $"{text}_plus{list.Count - 1}_{BuildOwnerHash(list)}"; } private static string BuildOwnerToken(long playerId) { string text = ResolvePlayerName(playerId); string text2 = ResolveSteamId(playerId); if (!string.Equals(text2, "unknown", StringComparison.Ordinal)) { return text + "_s" + text2; } return text; } private static string ResolvePlayerName(long playerId) { if (!AutoArchiveStore.TryGetPlayerRecord(playerId, out PlayerActivityRecord record)) { return "unknown"; } return ZoneSaviorPaths.SanitizeTagToken(record.Names.LastOrDefault((string candidate) => !string.IsNullOrWhiteSpace(candidate) && !string.Equals(candidate, "unknown", StringComparison.OrdinalIgnoreCase) && !string.Equals(candidate, "manual", StringComparison.OrdinalIgnoreCase)) ?? "unknown"); } private static string ResolveSteamId(long playerId) { if (!AutoArchiveStore.TryGetPlayerRecord(playerId, out PlayerActivityRecord record) || string.IsNullOrWhiteSpace(record.PlatformId) || !record.PlatformId.StartsWith("steam:", StringComparison.OrdinalIgnoreCase)) { return "unknown"; } string text = ZoneSaviorSteamIds.Normalize(record.PlatformId); if (!string.IsNullOrWhiteSpace(text)) { return text; } string text2 = new string((from character in record.PlatformId.Substring("steam:".Length) where char.IsLetterOrDigit(character) || character == '-' || character == '_' select character).ToArray()); if (!string.IsNullOrWhiteSpace(text2)) { return text2; } return "unknown"; } private static string BuildOwnerHash(IEnumerable<long> ownerIds) { uint num = 2166136261u; foreach (long ownerId in ownerIds) { byte[] bytes = BitConverter.GetBytes(ownerId); foreach (byte b in bytes) { num ^= b; num *= 16777619; } } return num.ToString("x8", CultureInfo.InvariantCulture); } private static bool IsWorldReady() { if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer() && ZDOMan.instance != null && (Object)(object)ZNetScene.instance != (Object)null) { return (Object)(object)ZoneSystem.instance != (Object)null; } return false; } private static string FormatDate(DateTime value) { if (!(value == DateTime.MinValue)) { return value.ToString("O", CultureInfo.InvariantCulture); } return "-"; } } internal static class AutoArchiveService { [CompilerGenerated] private sealed class <>c__DisplayClass15_0 { public ArchiveRunRecord completed; internal void <RunScan>b__0(ArchiveRunRecord run) { completed = run; } } [CompilerGenerated] private sealed class <RunScan>d__15 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public AutoArchiveScanOptions options; private <>c__DisplayClass15_0 <>8__1; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <RunScan>d__15(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>8__1 = null; <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>8__1 = new <>c__DisplayClass15_0(); <>8__1.completed = null; <>2__current = AutoArchiveScanner.Run(options, delegate(ArchiveRunRecord run) { <>8__1.completed = run; }); <>1__state = 1; return true; case 1: <>1__state = -1; if (<>8__1.completed != null) { AutoArchiveStore.RecordRun(<>8__1.completed); AutoArchiveStore.Flush(force: true); _logger.LogInfo((object)$"Auto archive scan finished: {<>8__1.completed.CandidateZones} candidate zone(s), {<>8__1.completed.ProcessedZones} processed zone(s), {<>8__1.completed.Clusters.Count} cluster record(s)."); } _scanRunning = false; _scanCoroutine = null; return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private static readonly TimeSpan TrackInterval = TimeSpan.FromMinutes(10.0); private static readonly TimeSpan FlushInterval = TimeSpan.FromMinutes(5.0); private static ManualLogSource _logger = null; private static bool _initialized; private static bool _scanRunning; private static DateTime _lastTrackUtc = DateTime.MinValue; private static DateTime _lastFlushUtc = DateTime.MinValue; private static Coroutine? _scanCoroutine; public static bool IsScanRunning => _scanRunning; public static void Initialize(ManualLogSource logger) { if (!_initialized) { _initialized = true; _logger = logger; PlayerActivityTracker.Initialize(logger); AutoArchiveScanner.Initialize(logger); } } public static void Update() { if (!IsServerReady()) { return; } DateTime utcNow = DateTime.UtcNow; if (utcNow - _lastTrackUtc >= TrackInterval) { _lastTrackUtc = utcNow; PlayerActivityTracker.TrackOnlinePlayers(utcNow); } if (utcNow - _lastFlushUtc >= FlushInterval) { _lastFlushUtc = utcNow; AutoArchiveStore.Flush(); } if (AutoArchiveConfig.Enabled && !_scanRunning) { TimeSpan timeSpan = TimeSpan.FromMinutes(AutoArchiveConfig.ScanIntervalMinutes); DateTime dateTime = DateTime.SpecifyKind(AutoArchiveStore.State.LastAutoScanUtc, DateTimeKind.Utc); if (!(dateTime != DateTime.MinValue) || !(utcNow - dateTime < timeSpan)) { QueueScan(new AutoArchiveScanOptions { Manual = false, DryRun = AutoArchiveConfig.DryRun, ResetAfterSave = AutoArchiveConfig.ResetAfterSave }); } } } public static bool QueueManualScan(bool? dryRunOverride = null, bool? resetAfterSaveOverride = null, IEnumerable<long>? targetPlayerIds = null) { if (!IsServerReady() || _scanRunning) { return false; } QueueScan(new AutoArchiveScanOptions { Manual = true, DryRun = (dryRunOverride ?? AutoArchiveConfig.DryRun), ResetAfterSave = (resetAfterSaveOverride ?? AutoArchiveConfig.ResetAfterSave), TargetPlayerIds = ((targetPlayerIds == null) ? new List<long>() : new List<long>(targetPlayerIds)) }); return true; } public static void Shutdown() { if (_scanCoroutine != null && (Object)(object)ZoneSaviorPlugin.Instance != (Object)null) { ((MonoBehaviour)ZoneSaviorPlugin.Instance).StopCoroutine(_scanCoroutine); _scanCoroutine = null; } _scanRunning = false; } private static void QueueScan(AutoArchiveScanOptions options) { _scanRunning = true; _logger.LogInfo((object)$"Starting auto archive scan (manual: {options.Manual}, dry run: {options.DryRun}, reset: {options.ResetAfterSave}, targets: {options.TargetPlayerIds.Count})."); _scanCoroutine = ((MonoBehaviour)ZoneSaviorPlugin.Instance).StartCoroutine(RunScan(options)); } [IteratorStateMachine(typeof(<RunScan>d__15))] private static IEnumerator RunScan(AutoArchiveScanOptions options) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <RunScan>d__15(0) { options = options }; } private static bool IsServerReady() { if ((Object)(object)ZoneSaviorPlugin.Instance != (Object)null && (Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer() && ZDOMan.instance != null && (Object)(object)ZNetScene.instance != (Object)null) { return (Object)(object)ZoneSystem.instance != (Object)null; } return false; } } internal static class AutoArchiveStore { private const string UnknownPlatformPrefix = "unknown:"; private const int MaxRunHistory = 50; private static readonly TimeSpan InternalWriteReloadGrace = TimeSpan.FromSeconds(2.0); private static readonly ISerializer Serializer = new SerializerBuilder().WithNamingConvention(UnderscoredNamingConvention.Instance).Build(); private static readonly IDeserializer Deserializer = new DeserializerBuilder().IgnoreUnmatchedProperties().WithNamingConvention(UnderscoredNamingConvention.Instance).Build(); private static readonly object Sync = new object(); private static ManualLogSource _logger = null; private static bool _initialized; private static bool _dirty; private static DateTime _lastFlushUtc = DateTime.MinValue; private static DateTime _lastInternalWriteUtc = DateTime.MinValue; private static readonly Dictionary<string, PlayerActivityRecord> PlayersByPlatform = new Dictionary<string, PlayerActivityRecord>(StringComparer.Ordinal); private static readonly Dictionary<long, PlayerActivityRecord> PlayersById = new Dictionary<long, PlayerActivityRecord>(); public static string FilePath => Path.Combine(ZoneSaviorPlugin.DataStorageFullPath, "activity.yml"); public static AutoArchiveState State { get; private set; } = new AutoArchiveState(); public static void Initialize(ManualLogSource logger) { if (!_initialized) { _initialized = true; _logger = logger; Load(); } } public static void Load() { lock (Sync) { if (!File.Exists(FilePath)) { State = new AutoArchiveState(); RebuildIndexes(); _dirty = true; Flush(force: true); return; } try { if (!TryReadStateFromDisk(out AutoArchiveState state, out string error)) { throw new InvalidDataException(error); } State = state; RebuildIndexes(); _dirty = false; } catch (Exception arg) { _logger.LogError((object)$"Failed to load auto archive activity file, using a blank state: {arg}"); State = new AutoArchiveState(); RebuildIndexes(); _dirty = true; } } } public static void Flush(bool force = false) { lock (Sync) { if (_dirty || force) { DateTime utcNow = DateTime.UtcNow; if (force || !(utcNow - _lastFlushUtc < TimeSpan.FromSeconds(60.0))) { Directory.CreateDirectory(Path.GetDirectoryName(FilePath)); File.WriteAllText(FilePath, Serializer.Serialize(State)); _lastFlushUtc = utcNow; _lastInternalWriteUtc = utcNow; _dirty = false; } } } } public static bool TryReloadFromDiskIfSafe(bool scanRunning, out string message) { lock (Sync) { if (_dirty) { message = "Activity reload skipped: runtime activity state has unsaved changes."; return false; } if (scanRunning) { message = "Activity reload skipped: auto archive scan is running."; return false; } if (DateTime.UtcNow - _lastInternalWriteUtc < InternalWriteReloadGrace) { message = "Activity reload skipped: change was caused by a recent ZoneSavior activity flush."; return false; } if (!File.Exists(FilePath)) { message = "Activity reload skipped: activity.yml does not exist."; return false; } if (!TryReadStateFromDisk(out AutoArchiveState state, out string error)) { message = "Activity reload skipped: " + error; return false; } State = state; RebuildIndexes(); _dirty = false; message = "Activity reload complete."; return true; } } public static void RecordPlayerSeen(string platformId, long playerId, string name, DateTime utcNow) { if (string.IsNullOrWhiteSpace(platformId)) { platformId = ((playerId != 0L) ? UnknownPlatformId(playerId) : "unknown"); } lock (Sync) { PlayerActivityRecord value; PlayerActivityRecord playerActivityRecord = (PlayersByPlatform.TryGetValue(platformId, out value) ? value : null); if (playerActivityRecord == null) { playerActivityRecord = new PlayerActivityRecord { PlatformId = platformId }; State.Players.Add(playerActivityRecord); IndexPlayer(playerActivityRecord); } playerActivityRecord.LastSeenUtc = utcNow; AddDistinct(playerActivityRecord.Names, name); if (playerId != 0L) { AddDistinct(playerActivityRecord.PlayerIds, playerId); PlayersById[playerId] = playerActivityRecord; MergePlayerIdRecords(playerActivityRecord, playerId); } _dirty = true; } } public static void RecordUnknownPlayer(long playerId, DateTime utcNow, string observedName = "") { if (playerId == 0L) { return; } lock (Sync) { if (PlayersById.TryGetValue(playerId, out PlayerActivityRecord value)) { if (value.LastSeenUtc == DateTime.MinValue) { value.LastSeenUtc = utcNow; _dirty = true; } if (AddObservedName(value, observedName)) { _dirty = true; } } else { GetOrCreateUnknownPlayer(playerId, utcNow, observedName); _dirty = true; } } } public static bool TryGetPlayerRecord(long playerId, out PlayerActivityRecord record) { lock (Sync) { record = (PlayersById.TryGetValue(playerId, out PlayerActivityRecord value) ? value : null); return record != null; } } public static bool TryGetPlayerIdsBySteamId(string steamId, out List<long> playerIds, out string normalizedSteamId) { normalizedSteamId = ZoneSaviorSteamIds.Normalize(steamId); playerIds = new List<long>(); if (string.IsNullOrWhiteSpace(normalizedSteamId)) { return false; } lock (Sync) { foreach (PlayerActivityRecord player in State.Players) { if (!ZoneSaviorSteamIds.TryNormalizePlatformId(player.PlatformId, out string steamId2) || !string.Equals(steamId2, normalizedSteamId, StringComparison.Ordinal)) { continue; } foreach (long item in player.PlayerIds.Where((long id) => id != 0)) { AddDistinct(playerIds, item); } } } return playerIds.Count > 0; } public static AutoArchiveCreatorEligibility EvaluateCreatorArchiveEligibility(long playerId, DateTime utcNow, int inactiveDays, bool recordUnknownPlayer, string observedName = "") { AutoArchiveCreatorEligibility autoArchiveCreatorEligibility = new AutoArchiveCreatorEligibility { PlayerId = playerId }; if (playerId == 0L) { autoArchiveCreatorEligibility.Reason = "creatorless"; return autoArchiveCreatorEligibility; } if (ZoneLimitConfiguration.IsArchiveProtected(playerId, out string reason)) { autoArchiveCreatorEligibility.Protected = true; autoArchiveCreatorEligibility.Reason = reason; return autoArchiveCreatorEligibility; } if (!TryGetPlayerRecord(playerId, out PlayerActivityRecord record)) { autoArchiveCreatorEligibility.PlatformId = UnknownPlatformId(playerId); autoArchiveCreatorEligibility.Names = (string.IsNullOrWhiteSpace(observedName) ? new List<string>(1) { "not_recorded_in_activity" } : new List<string>(1) { observedName.Trim() }); autoArchiveCreatorEligibility.UnknownActivityRecord = true; if (recordUnknownPlayer) { RecordUnknownPlayer(playerId, utcNow, observedName); autoArchiveCreatorEligibility.Reason = $"player {playerId} was first discovered by scanner"; } else { autoArchiveCreatorEligibility.Reason = $"player {playerId} is not recorded in activity"; } return autoArchiveCreatorEligibility; } autoArchiveCreatorEligibility.RecordedInActivity = true; autoArchiveCreatorEligibility.PlatformId = record.PlatformId; autoArchiveCreatorEligibility.Names = record.Names?.ToList() ?? new List<string>(); bool flag2 = (autoArchiveCreatorEligibility.UnknownActivityRecord = IsUnknown(record)); DateTime lastSeenUtc = record.LastSeenUtc; if (lastSeenUtc == DateTime.MinValue) { autoArchiveCreatorEligibility.Reason = $"player {playerId} has no usable activity timestamp"; return autoArchiveCreatorEligibility; } TimeSpan timeSpan = utcNow - DateTime.SpecifyKind(lastSeenUtc, DateTimeKind.Utc); if (timeSpan.TotalDays < (double)inactiveDays) { autoArchiveCreatorEligibility.Reason = (flag2 ? $"unknown player {playerId} discovered {timeSpan.TotalDays:F1}/{inactiveDays} days ago" : $"player {playerId} last seen {timeSpan.TotalDays:F1}/{inactiveDays} days ago"); return autoArchiveCreatorEligibility; } autoArchiveCreatorEligibility.Eligible = true; autoArchiveCreatorEligibility.Reason = (flag2 ? $"unknown player {playerId} inactive since discovery for {timeSpan.TotalDays:F1} days" : $"player {playerId} inactive for {timeSpan.TotalDays:F1} days"); return autoArchiveCreatorEligibility; } public static bool IsCreatorArchiveEligible(long playerId, DateTime utcNow, int inactiveDays, out string reason) { AutoArchiveCreatorEligibility autoArchiveCreatorEligibility = EvaluateCreatorArchiveEligibility(playerId, utcNow, inactiveDays, recordUnknownPlayer: true); reason = autoArchiveCreatorEligibility.Reason; return autoArchiveCreatorEligibility.Eligible; } public static void RecordRun(ArchiveRunRecord run) { lock (Sync) { State.LastScanUtc = run.CreatedUtc; if (!run.Manual) { State.LastAutoScanUtc = run.CreatedUtc; } State.Runs.Add(run); if (State.Runs.Count > 50) { State.Runs.RemoveRange(0, State.Runs.Count - 50); } _dirty = true; } } private static PlayerActivityRecord GetOrCreateUnknownPlayer(long playerId, DateTime utcNow, string observedName = "") { string text = UnknownPlatformId(playerId); if (PlayersByPlatform.TryGetValue(text, out PlayerActivityRecord value)) { AddObservedName(value, observedName); return value; } List<string> list = new List<string>(); AddObservedName(list, observedName); if (list.Count == 0) { list.Add("unknown"); } value = new PlayerActivityRecord { PlatformId = text, LastSeenUtc = utcNow, PlayerIds = new List<long>(1) { playerId }, Names = list }; State.Players.Add(value); IndexPlayer(value); return value; } private static bool AddObservedName(PlayerActivityRecord record, string observedName) { return AddObservedName(record.Names, observedName); } private static bool AddObservedName(List<string> names, string observedName) { if (string.IsNullOrWhiteSpace(observedName) || string.Equals(observedName, "unknown", StringComparison.OrdinalIgnoreCase)) { return false; } string item = observedName.Trim(); if (names.Contains(item)) { return false; } names.Add(item); return true; } private static void MergePlayerIdRecords(PlayerActivityRecord target, long playerId) { PlayerActivityRecord target2 = target; foreach (PlayerActivityRecord item in State.Players.Where((PlayerActivityRecord player) => player != target2 && player.PlayerIds.Contains(playerId)).ToList()) { foreach (long playerId2 in item.PlayerIds) { AddDistinct(target2.PlayerIds, playerId2); } foreach (string name in item.Names) { AddDistinct(target2.Names, name); } if (item.LastSeenUtc > target2.LastSeenUtc) { target2.LastSeenUtc = item.LastSeenUtc; } State.Players.Remove(item); } RebuildPlayerIndexes(); } private static void RebuildIndexes() { RebuildPlayerIndexes(); } private static void RebuildPlayerIndexes() { PlayersByPlatform.Clear(); PlayersById.Clear(); foreach (PlayerActivityRecord player in State.Players) { IndexPlayer(player); } } private static void IndexPlayer(PlayerActivityRecord record) { if (!string.IsNullOrWhiteSpace(record.PlatformId)) { PlayersByPlatform[record.PlatformId] = record; } foreach (long playerId in record.PlayerIds) { if (playerId != 0L) { PlayersById[playerId] = record; } } } private static string UnknownPlatformId(long playerId) { return "unknown:" + playerId; } private static bool IsUnknown(PlayerActivityRecord record) { return record.PlatformId.StartsWith("unknown:", StringComparison.Ordinal); } private static bool TryReadStateFromDisk(out AutoArchiveState state, out string error) { state = new AutoArchiveState(); error = ""; try { string input = File.ReadAllText(FilePath); state = Deserializer.Deserialize<AutoArchiveState>(input) ?? new AutoArchiveState(); NormalizeState(state); return true; } catch (Exception ex) { error = ex.Message; return false; } } private static void NormalizeState(AutoArchiveState state) { AutoArchiveState autoArchiveState = state; if (autoArchiveState.Players == null) { List<PlayerActivityRecord> list2 = (autoArchiveState.Players = new List<PlayerActivityRecord>()); } autoArchiveState = state; if (autoArchiveState.Runs == null) { List<ArchiveRunRecord> list4 = (autoArchiveState.Runs = new List<ArchiveRunRecord>()); } } private static void AddDistinct<T>(List<T> values, T value) { if (value != null && !values.Contains(value)) { values.Add(value); } } } internal sealed class BuildCounterHud : MonoBehaviour { private const float FadeDuration = 0.15f; private static BuildCounterHud? _instance; private CanvasGroup? _canvasGroup; private TextMeshProUGUI? _text; private float _hideAt = float.MinValue; public static void ShowCount(int count, int limit) { if (!((Object)(object)Hud.instance == (Object)null)) { EnsureInstance(); _instance?.Show($"{count}/{limit}"); } } private static void EnsureInstance() { if ((Object)(object)Hud.instance == (Object)null) { return; } if ((Object)(object)_instance != (Object)null && Object.op_Implicit((Object)(object)_instance)) { _instance.EnsureElements(); return; } _instance = ((Component)Hud.instance).GetComponent<BuildCounterHud>(); if ((Object)(object)_instance == (Object)null) { _instance = ((Component)Hud.instance).gameObject.AddComponent<BuildCounterHud>(); } _instance.EnsureElements(); } private void Update() { if ((Object)(object)_canvasGroup == (Object)null) { return; } if (Time.unscaledTime <= _hideAt) { _canvasGroup.alpha = 1f; return; } _canvasGroup.alpha = Mathf.MoveTowards(_canvasGroup.alpha, 0f, Time.unscaledDeltaTime / 0.15f); if (_canvasGroup.alpha <= 0f && (Object)(object)_text != (Object)null) { ((TMP_Text)_text).text = string.Empty; } } private void Show(string value) { EnsureElements(); if (!((Object)(object)_text == (Object)null) && !((Object)(object)_canvasGroup == (Object)null)) { ((TMP_Text)_text).text = value; _canvasGroup.alpha = 1f; _hideAt = Time.unscaledTime + ClientConfig.CounterVisibleSeconds; } } private void EnsureElements() { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Expected O, but got Unknown //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: 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_0169: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_text != (Object)null) || !((Object)(object)_canvasGroup != (Object)null)) { TMP_Text val = Hud.instance?.m_buildSelection; if (!((Object)(object)val == (Object)null)) { GameObject val2 = new GameObject("ZoneSaviorCounter", new Type[4] { typeof(RectTransform), typeof(CanvasRenderer), typeof(CanvasGroup), typeof(TextMeshProUGUI) }); val2.transform.SetParent(((Component)Hud.instance).transform, false); RectTransform val3 = (RectTransform)val2.transform; val3.anchorMin = new Vector2(0.5f, 1f); val3.anchorMax = new Vector2(0.5f, 1f); val3.pivot = new Vector2(0.5f, 1f); val3.anchoredPosition = new Vector2(0f, -18f); val3.sizeDelta = new Vector2(280f, 34f); _canvasGroup = val2.GetComponent<CanvasGroup>(); _canvasGroup.alpha = 0f; _text = val2.GetComponent<TextMeshProUGUI>(); ((TMP_Text)_text).font = val.font; ((TMP_Text)_text).fontSharedMaterial = val.fontSharedMaterial; ((TMP_Text)_text).fontSize = val.fontSize; ((Graphic)_text).color = ((Graphic)val).color; ((TMP_Text)_text).alignment = (TextAlignmentOptions)514; ((TMP_Text)_text).textWrappingMode = (TextWrappingModes)0; ((TMP_Text)_text).overflowMode = (TextOverflowModes)0; ((Graphic)_text).raycastTarget = false; ((TMP_Text)_text).text = string.Empty; } } } } internal static class ZoneSaviorTimestamp { public static string Now() { return Format(DateTime.UtcNow); } public static string Format(DateTime utc) { if (utc == DateTime.MinValue) { return ""; } return new DateTimeOffset(DateTime.SpecifyKind(utc, DateTimeKind.Utc)).ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss zzz", CultureInfo.InvariantCulture); } public static DateTime ParseUtc(string value) { if (string.IsNullOrWhiteSpace(value)) { return DateTime.MinValue; } if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.AllowWhiteSpaces, out var result) || DateTimeOffset.TryParse(value, CultureInfo.CurrentCulture, DateTimeStyles.AllowWhiteSpaces, out result)) { return DateTime.SpecifyKind(result.UtcDateTime, DateTimeKind.Utc); } if (DateTime.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeLocal, out var result2) || DateTime.TryParse(value, CultureInfo.CurrentCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeLocal, out result2)) { return DateTime.SpecifyKind(result2, DateTimeKind.Utc); } return DateTime.MinValue; } public static bool IsExpired(string timestamp, DateTime utcNow) { DateTime dateTime = ParseUtc(timestamp); if (dateTime != DateTime.MinValue) { return dateTime <= DateTime.SpecifyKind(utcNow, DateTimeKind.Utc); } return false; } } internal static class VeiledRecipesCompat { private const string PluginGuid = "sighsorry.VeiledRecipes"; private const string ApiTypeName = "VeiledRecipes.VeiledRecipesCompat"; private static readonly BindingFlags PublicStaticFlags = BindingFlags.Static | BindingFlags.Public; private static ManualLogSource? _logger; private static bool _initialized; private static bool _registered; private static MethodInfo? _registerKnownPieceOverrideMethod; private static readonly Func<Piece, bool> KnownPieceOverride = AdminTerrainTool.IsProxyPiece; public static void Initialize(ManualLogSource logger) { _logger = logger; EnsureInitialized(); RegisterKnownPieceOverride(); } private static void EnsureInitialized() { if (!_initialized) { _initialized = true; if (Chainloader.PluginInfos.TryGetValue("sighsorry.VeiledRecipes", out var value)) { _registerKnownPieceOverrideMethod = ((((object)value.Instance)?.GetType().Assembly)?.GetType("VeiledRecipes.VeiledRecipesCompat", throwOnError: false))?.GetMethod("RegisterKnownPieceOverride", PublicStaticFlags, null, new Type[1] { typeof(Func<Piece, bool>) }, null); } } } private static void RegisterKnownPieceOverride() { if (_registered || _registerKnownPieceOverrideMethod == null) { return; } try { _registerKnownPieceOverrideMethod.Invoke(null, new object[1] { KnownPieceOverride }); _registered = true; ManualLogSource? logger = _logger; if (logger != null) { logger.LogDebug((object)"Registered ZoneSavior admin terrain tools with VeiledRecipes."); } } catch (Exception ex) { ManualLogSource? logger2 = _logger; if (logger2 != null) { logger2.LogDebug((object)("Could not register ZoneSavior admin terrain tools with VeiledRecipes: " + ex.Message)); } } } } internal static class AdminTerrainInfinityHammerCompat { private static ManualLogSource? _logger; private static bool _available; private static bool _patched; private static bool _saveDataConfigured; private static Type? _configurationType; public static void Initialize(ManualLogSource logger, Harmony harmony) { _logger = logger; if (!_patched) { _configurationType = FindLoadedType("InfinityHammer.Configuration"); _available = _configurationType != null; if (!_available) { _logger.LogDebug((object)"Infinity Hammer compat skipped: Infinity Hammer is not loaded."); return; } int num = 0; num += PatchMethod(harmony, "InfinityHammer.BaseSelection:PostProcessPlaced", new Type[1] { typeof(GameObject) }, "InfinityHammerPostProcessPrefix", prefix: true); num += PatchMethod(harmony, "InfinityHammer.BaseSelection:PostProcessPlaced", new Type[1] { typeof(GameObject) }, "InfinityHammerPostProcessPostfix", prefix: false); num += PatchMethod(harmony, "InfinityHammer.NoCreator:Set", new Type[2] { typeof(ZNetView), typeof(Piece) }, "InfinityHammerNoCreatorPrefix", prefix: true); _patched = num > 0; EnsureSavesTerrainData(); _logger.LogInfo((object)$"Infinity Hammer compat initialized. Patched {num} method(s)."); } } public static void Update() { if (_available) { EnsureSavesTerrainData(); } } public static void Shutdown() { _patched = false; _saveDataConfigured = false; _configurationType = null; } private static Type? FindLoadedType(string fullName) { foreach (PluginInfo value in Chainloader.PluginInfos.Values) { Type type = ((object)value.Instance)?.GetType().Assembly.GetType(fullName, throwOnError: false); if (type != null) { return type; } } return null; } private static int PatchMethod(Harmony harmony, string fullMethodName, Type[] parameters, string patchMethodName, bool prefix) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(fullMethodName, parameters, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(AdminTerrainInfinityHammerCompat), patchMethodName, (Type[])null, (Type[])null); if (methodInfo == null || methodInfo2 == null) { ManualLogSource? logger = _logger; if (logger != null) { logger.LogDebug((object)("Infinity Hammer compat skipped method: " + fullMethodName)); } return 0; } HarmonyMethod val = new HarmonyMethod(methodInfo2); if (prefix) { harmony.Patch((MethodBase)methodInfo, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } return 1; } private static void InfinityHammerPostProcessPrefix(GameObject obj) { AdminTerrainTool.PrepareInfinityHammerPlaced(obj); } private static void InfinityHammerPostProcessPostfix(GameObject obj) { AdminTerrainTool.ApplyInfinityHammerPlaced(obj); } private static bool InfinityHammerNoCreatorPrefix(ZNetView view, Piece piece) { return AdminTerrainTool.ShouldRunInfinityHammerNoCreator(view, piece); } private static void EnsureSavesTerrainData() { if (!_saveDataConfigured && (_configurationType?.GetField("SavedObjectData", BindingFlags.Static | BindingFlags.Public))?.GetValue(null) is HashSet<string> hashSet) { hashSet.Add("ZoneSaviorTerrainProxy".ToLowerInvariant()); hashSet.Add("ZoneSaviorTerrainProxySlope".ToLowerInvariant()); hashSet.Add("ZoneSaviorPaintProxy".ToLowerInvariant()); _saveDataConfigured = true; } } } internal static class AdminTerrainTool { private enum PreviewKind { None, Circle, Width, Slope } private enum TerrainProxyMode { Circle, Slope, Paint } private readonly struct SlopePlacement { public Vector3 Center { get; } public Quaternion Rotation { get; } public float Width { get; } public float Length { get; } public float HeightDelta { get; } public SlopePlacement(Vector3 center, Quaternion rotation, float width, float length, float heightDelta) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) Center = center; Rotation = rotation; Width = width; Length = length; HeightDelta = heightDelta; } public Vector3 GetWorldPoint(float localX, float localZ) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: 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_005e: Unknown result type (might be due to invalid IL or missing references) Vector3 result = Center + Rotation * new Vector3(localX, 0f, localZ); result.y = Center.y + Mathf.Clamp(localZ / Mathf.Max(Length, 0.001f), -0.5f, 0.5f) * HeightDelta; return result; } } private readonly struct TerrainProxySettings { public TerrainProxyMode Mode { get; } public float Radius { get; } public float Width { get; } public float Length { get; } public float SlopeHeightDelta { get; } public float TerrainEdgeSoftness { get; } public AdminTerrainPaintType PaintType { get; } public bool ModifiesHeight { get { TerrainProxyMode mode = Mode; if ((uint)mode <= 1u) { return true; } return false; } } public bool ModifiesPaint => Mode == TerrainProxyMode.Paint; public bool HasCircleFootprint => Mode != TerrainProxyMode.Slope; public float TerrainRadius { get { if (Mode != TerrainProxyMode.Slope) { return Radius; } return Mathf.Sqrt(Width * Width + Length * Length) * 0.5f; } } public float SearchRadius => TerrainRadius; public TerrainProxySettings(TerrainProxyMode mode, float radius, float width, float length, float slopeHeightDelta, float terrainEdgeSoftness, AdminTerrainPaintType paintType) { Mode = mode; Radius = Mathf.Clamp(radius, 0.5f, 128f); Width = Mathf.Clamp(width, 0.5f, 256f); Length = Mathf.Clamp(length, 0.5f, 256f); SlopeHeightDelta = Mathf.Clamp(slopeHeightDelta, -1024f, 1024f); TerrainEdgeSoftness = Mathf.Clamp01(terrainEdgeSoftness); PaintType = paintType; } public float GetNormalizedDistance(float x, float z) { if (Mode == TerrainProxyMode.Slope) { return Mathf.Max(Mathf.Abs(x) / (Width * 0.5f), Mathf.Abs(z) / (Length * 0.5f)); } return Mathf.Sqrt(x * x + z * z) / Radius; } public float GetTargetHeight(Vector3 center, float heightmapWorldY, float localZ) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) float num = center.y; if (Mode == TerrainProxyMode.Slope) { num += Mathf.Clamp(localZ / Length, -0.5f, 0.5f) * SlopeHeightDelta; } return num - heightmapWorldY; } public float GetLevelFalloff(float x, float z) { if (Mode != 0 || TerrainEdgeSoftness <= 0f)