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 Runestone Base Protection v1.1.0
plugins/LinkedRunestoneProtection/BaseProtection.dll
Decompiled 4 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BaseProtection.Core; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using Jotunn.Configs; using Jotunn.Entities; using Jotunn.Managers; using Jotunn.Utils; using Microsoft.CodeAnalysis; using SoftReferenceableAssets; using TMPro; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("BaseProtection")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.1.0.0")] [assembly: AssemblyInformationalVersion("1.1.0")] [assembly: AssemblyProduct("Runestone Base Protection")] [assembly: AssemblyTitle("Runestone Base Protection")] [assembly: AssemblyVersion("1.1.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace BaseProtection.Core { public sealed class CreatureOverrideParseResult { public Dictionary<string, bool> Blocked { get; } = new Dictionary<string, bool>(StringComparer.Ordinal); public List<string> Warnings { get; } = new List<string>(); } public static class CreatureOverrideParser { public static CreatureOverrideParseResult Parse(string? raw) { CreatureOverrideParseResult creatureOverrideParseResult = new CreatureOverrideParseResult(); if (string.IsNullOrWhiteSpace(raw)) { return creatureOverrideParseResult; } string[] array = raw.Split(new char[3] { ';', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length == 0) { continue; } int num = text.IndexOf('='); if (num <= 0 || num == text.Length - 1 || text.IndexOf('=', num + 1) >= 0) { creatureOverrideParseResult.Warnings.Add("'" + text + "' must be prefab=Allow or prefab=Block."); continue; } string text2 = text.Substring(0, num).Trim(); string text3 = text.Substring(num + 1).Trim(); if (text2.Length == 0 || (!text3.Equals("Allow", StringComparison.OrdinalIgnoreCase) && !text3.Equals("Block", StringComparison.OrdinalIgnoreCase))) { creatureOverrideParseResult.Warnings.Add("'" + text + "' must be prefab=Allow or prefab=Block."); continue; } if (creatureOverrideParseResult.Blocked.ContainsKey(text2)) { creatureOverrideParseResult.Warnings.Add("'" + text2 + "' appears more than once; the last value wins."); } creatureOverrideParseResult.Blocked[text2] = text3.Equals("Block", StringComparison.OrdinalIgnoreCase); } return creatureOverrideParseResult; } } public readonly struct Point2 { public double X { get; } public double Z { get; } public Point2(double x, double z) { X = x; Z = z; } } public sealed class FieldGeometry { private const double Epsilon = 1E-08; private readonly Point2[] _stones; private readonly double _minX; private readonly double _maxX; private readonly double _minZ; private readonly double _maxZ; public IReadOnlyList<Point2> Stones => _stones; public bool IsCircle => _stones.Length == 2; public double Area { get { if (!IsCircle) { return Math.Abs(SignedArea(_stones)); } return Math.PI * DistanceSquared(_stones[0], _stones[1]) / 4.0; } } private FieldGeometry(Point2[] stones) { _stones = stones; if (stones.Length == 2) { Point2 point = Midpoint(stones[0], stones[1]); double num = Math.Sqrt(DistanceSquared(stones[0], stones[1])) / 2.0; _minX = point.X - num; _maxX = point.X + num; _minZ = point.Z - num; _maxZ = point.Z + num; return; } _minX = (_maxX = stones[0].X); _minZ = (_maxZ = stones[0].Z); for (int i = 1; i < stones.Length; i++) { _minX = Math.Min(_minX, stones[i].X); _maxX = Math.Max(_maxX, stones[i].X); _minZ = Math.Min(_minZ, stones[i].Z); _maxZ = Math.Max(_maxZ, stones[i].Z); } } public static bool TryCreate(IReadOnlyList<Point2> stones, double maxSpan, double maxArea, out FieldGeometry? field, out string error) { field = null; error = string.Empty; if (stones == null || stones.Count < 2 || stones.Count > 8) { error = "A field requires two to eight stones."; return false; } if (maxSpan <= 0.0 || maxArea <= 0.0) { error = "Field limits must be positive."; return false; } Point2[] array = new Point2[stones.Count]; for (int i = 0; i < array.Length; i++) { array[i] = stones[i]; if (!IsFinite(array[i].X) || !IsFinite(array[i].Z)) { error = "Stone positions must be finite."; return false; } for (int j = 0; j < i; j++) { if (DistanceSquared(array[i], array[j]) <= 1E-08) { error = "Stones cannot occupy the same position."; return false; } } } for (int k = 0; k < array.Length; k++) { int num = ((array.Length == 2) ? 1 : ((k + 1) % array.Length)); if (DistanceSquared(array[k], array[num]) > maxSpan * maxSpan) { error = ((array.Length == 2) ? "The two stones exceed the maximum diameter." : "A polygon edge exceeds the maximum length."); return false; } } if (array.Length > 2) { if (CountCrossings(array) != 0) { error = "The polygon crosses itself."; return false; } if (Math.Abs(SignedArea(array)) <= 1E-08) { error = "The polygon has no area."; return false; } } FieldGeometry fieldGeometry = new FieldGeometry(array); if (fieldGeometry.Area > maxArea) { error = "The field exceeds the maximum area."; return false; } field = fieldGeometry; return true; } public bool Contains(Point2 point) { double num = Math.Sqrt(1E-08); if (point.X < _minX - num || point.X > _maxX + num || point.Z < _minZ - num || point.Z > _maxZ + num) { return false; } if (IsCircle) { return DistanceSquared(Midpoint(_stones[0], _stones[1]), point) <= DistanceSquared(_stones[0], _stones[1]) / 4.0 + 1E-08; } bool flag = false; int num2 = 0; int num3 = _stones.Length - 1; while (num2 < _stones.Length) { Point2 a = _stones[num3]; Point2 b = _stones[num2]; if (PointOnSegment(point, a, b)) { return true; } if (a.Z > point.Z != b.Z > point.Z && point.X < (b.X - a.X) * (point.Z - a.Z) / (b.Z - a.Z) + a.X) { flag = !flag; } num3 = num2++; } return flag; } public bool TrySteerOutside(Point2 position, Point2 requestedDirection, double detectionRange, int preferredSide, out Point2 direction, out double boundaryDistance) { direction = requestedDirection; boundaryDistance = double.PositiveInfinity; if (!TryGetOutwardNormal(position, detectionRange, out var outward, out boundaryDistance)) { return false; } double num = Math.Sqrt(requestedDirection.X * requestedDirection.X + requestedDirection.Z * requestedDirection.Z); if (num <= 1E-08) { return false; } double num2 = requestedDirection.X / num; double num3 = requestedDirection.Z / num; if (num2 * outward.X + num3 * outward.Z >= 0.0) { return false; } double num4 = 0.0 - outward.Z; double x = outward.X; int num5 = ((preferredSide >= 0) ? 1 : (-1)); double num6 = detectionRange * 0.75; double num7 = Math.Max(-0.4, Math.Min(0.4, (num6 - boundaryDistance) / num6)); double num8 = Math.Sqrt(1.0 + num7 * num7); direction = new Point2((outward.X * num7 + num4 * (double)num5) / num8, (outward.Z * num7 + x * (double)num5) / num8); return true; } private bool TryGetOutwardNormal(Point2 position, double detectionRange, out Point2 outward, out double distance) { outward = new Point2(0.0, 0.0); distance = double.PositiveInfinity; if (!IsFinite(position.X) || !IsFinite(position.Z) || !IsFinite(detectionRange) || detectionRange <= 0.0) { return false; } if (position.X < _minX - detectionRange || position.X > _maxX + detectionRange || position.Z < _minZ - detectionRange || position.Z > _maxZ + detectionRange) { return false; } if (IsCircle) { Point2 point = Midpoint(_stones[0], _stones[1]); double num = position.X - point.X; double num2 = position.Z - point.Z; double num3 = Math.Sqrt(num * num + num2 * num2); double num4 = Math.Sqrt(DistanceSquared(_stones[0], _stones[1])) / 2.0; distance = num3 - num4; if (distance <= 1E-08 || distance > detectionRange) { return false; } outward = new Point2(num / num3, num2 / num3); return true; } if (Contains(position)) { return false; } double num5 = detectionRange * detectionRange; bool flag = false; double num6 = 0.0; double num7 = 0.0; for (int i = 0; i < _stones.Length; i++) { Point2 point2 = ClosestPointOnSegment(position, _stones[i], _stones[(i + 1) % _stones.Length]); double num8 = position.X - point2.X; double num9 = position.Z - point2.Z; double num10 = num8 * num8 + num9 * num9; if (!(num10 > num5)) { num5 = num10; num6 = num8; num7 = num9; flag = true; } } if (!flag) { return false; } distance = Math.Sqrt(num5); if (distance <= 1E-08) { return false; } outward = new Point2(num6 / distance, num7 / distance); return true; } public double DistanceTo(Point2 point) { if (Contains(point)) { return 0.0; } if (IsCircle) { Point2 a = Midpoint(_stones[0], _stones[1]); double num = Math.Sqrt(DistanceSquared(_stones[0], _stones[1])) / 2.0; return Math.Max(0.0, Math.Sqrt(DistanceSquared(a, point)) - num); } double num2 = double.PositiveInfinity; for (int i = 0; i < _stones.Length; i++) { num2 = Math.Min(num2, DistanceToSegmentSquared(point, _stones[i], _stones[(i + 1) % _stones.Length])); } return Math.Sqrt(num2); } public double DistanceTo(FieldGeometry other) { if (Overlaps(other)) { return 0.0; } if (IsCircle && other.IsCircle) { Point2 a = Midpoint(_stones[0], _stones[1]); Point2 b = Midpoint(other._stones[0], other._stones[1]); double num = Math.Sqrt(DistanceSquared(_stones[0], _stones[1])) / 2.0; double num2 = Math.Sqrt(DistanceSquared(other._stones[0], other._stones[1])) / 2.0; return Math.Max(0.0, Math.Sqrt(DistanceSquared(a, b)) - num - num2); } if (IsCircle) { return CirclePolygonDistance(this, other); } if (other.IsCircle) { return CirclePolygonDistance(other, this); } double num3 = double.PositiveInfinity; Point2[] stones = _stones; foreach (Point2 point in stones) { num3 = Math.Min(num3, other.DistanceTo(point)); } stones = other._stones; foreach (Point2 point2 in stones) { num3 = Math.Min(num3, DistanceTo(point2)); } return num3; } private static double CirclePolygonDistance(FieldGeometry circle, FieldGeometry polygon) { Point2 p = Midpoint(circle._stones[0], circle._stones[1]); double num = Math.Sqrt(DistanceSquared(circle._stones[0], circle._stones[1])) / 2.0; double num2 = double.PositiveInfinity; for (int i = 0; i < polygon._stones.Length; i++) { num2 = Math.Min(num2, DistanceToSegmentSquared(p, polygon._stones[i], polygon._stones[(i + 1) % polygon._stones.Length])); } return Math.Max(0.0, Math.Sqrt(num2) - num); } public bool Overlaps(FieldGeometry other) { if (IsCircle && other.IsCircle) { double num = Math.Sqrt(DistanceSquared(_stones[0], _stones[1])) / 2.0; double num2 = Math.Sqrt(DistanceSquared(other._stones[0], other._stones[1])) / 2.0; double num3 = num + num2; return DistanceSquared(Midpoint(_stones[0], _stones[1]), Midpoint(other._stones[0], other._stones[1])) <= num3 * num3 + 1E-08; } if (IsCircle) { return CircleOverlapsPolygon(this, other); } if (other.IsCircle) { return CircleOverlapsPolygon(other, this); } Point2[] stones = _stones; foreach (Point2 point in stones) { if (other.Contains(point)) { return true; } } stones = other._stones; foreach (Point2 point2 in stones) { if (Contains(point2)) { return true; } } for (int j = 0; j < _stones.Length; j++) { for (int k = 0; k < other._stones.Length; k++) { if (SegmentsIntersect(_stones[j], _stones[(j + 1) % _stones.Length], other._stones[k], other._stones[(k + 1) % other._stones.Length])) { return true; } } } return false; } private static bool CircleOverlapsPolygon(FieldGeometry circle, FieldGeometry polygon) { Point2 point = Midpoint(circle._stones[0], circle._stones[1]); double num = DistanceSquared(circle._stones[0], circle._stones[1]) / 4.0; if (polygon.Contains(point)) { return true; } Point2[] stones = polygon._stones; foreach (Point2 point2 in stones) { if (circle.Contains(point2)) { return true; } } for (int j = 0; j < polygon._stones.Length; j++) { if (DistanceToSegmentSquared(point, polygon._stones[j], polygon._stones[(j + 1) % polygon._stones.Length]) <= num + 1E-08) { return true; } } return false; } private static double SignedArea(IReadOnlyList<Point2> points) { double num = 0.0; for (int i = 0; i < points.Count; i++) { Point2 point = points[(i + 1) % points.Count]; num += points[i].X * point.Z - point.X * points[i].Z; } return num / 2.0; } internal static int CountCrossings(IReadOnlyList<Point2> points) { int num = 0; for (int i = 0; i < points.Count; i++) { for (int j = i + 2; j < points.Count; j++) { if ((i != 0 || j != points.Count - 1) && SegmentsIntersect(points[i], points[(i + 1) % points.Count], points[j], points[(j + 1) % points.Count])) { num++; } } } return num; } private static Point2 Midpoint(Point2 a, Point2 b) { return new Point2((a.X + b.X) / 2.0, (a.Z + b.Z) / 2.0); } private static double DistanceSquared(Point2 a, Point2 b) { return (a.X - b.X) * (a.X - b.X) + (a.Z - b.Z) * (a.Z - b.Z); } private static bool IsFinite(double x) { if (!double.IsNaN(x)) { return !double.IsInfinity(x); } return false; } private static double Cross(Point2 a, Point2 b, Point2 c) { return (b.X - a.X) * (c.Z - a.Z) - (b.Z - a.Z) * (c.X - a.X); } private static bool PointOnSegment(Point2 p, Point2 a, Point2 b) { if (Math.Abs(Cross(a, b, p)) <= 1E-08 && p.X >= Math.Min(a.X, b.X) - 1E-08 && p.X <= Math.Max(a.X, b.X) + 1E-08 && p.Z >= Math.Min(a.Z, b.Z) - 1E-08) { return p.Z <= Math.Max(a.Z, b.Z) + 1E-08; } return false; } private static bool SegmentsIntersect(Point2 a, Point2 b, Point2 c, Point2 d) { double num = Cross(a, b, c); double num2 = Cross(a, b, d); double num3 = Cross(c, d, a); double num4 = Cross(c, d, b); if (num * num2 < -1E-08 && num3 * num4 < -1E-08) { return true; } if (!PointOnSegment(c, a, b) && !PointOnSegment(d, a, b) && !PointOnSegment(a, c, d)) { return PointOnSegment(b, c, d); } return true; } private static double DistanceToSegmentSquared(Point2 p, Point2 a, Point2 b) { return DistanceSquared(p, ClosestPointOnSegment(p, a, b)); } private static Point2 ClosestPointOnSegment(Point2 p, Point2 a, Point2 b) { double num = b.X - a.X; double num2 = b.Z - a.Z; double num3 = num * num + num2 * num2; if (num3 <= 1E-08) { return a; } double num4 = Math.Max(0.0, Math.Min(1.0, ((p.X - a.X) * num + (p.Z - a.Z) * num2) / num3)); return new Point2(a.X + num4 * num, a.Z + num4 * num2); } } public static class PolygonOrdering { public static int[] Untangle(IReadOnlyList<Point2> stones) { if (stones == null) { throw new ArgumentNullException("stones"); } int[] array = new int[stones.Count]; for (int i = 0; i < array.Length; i++) { array[i] = i; } if (array.Length < 4) { return array; } for (int j = 0; j < array.Length * array.Length; j++) { int num = FieldGeometry.CountCrossings(Points(stones, array)); if (num == 0) { break; } int[] array2 = null; int num2 = num; for (int k = 0; k < array.Length; k++) { for (int l = k + 2; l < array.Length; l++) { if (k != 0 || l != array.Length - 1) { int[] array3 = (int[])array.Clone(); Array.Reverse((Array)array3, k + 1, l - k); int num3 = FieldGeometry.CountCrossings(Points(stones, array3)); if (num3 < num2) { array2 = array3; num2 = num3; } } } } if (array2 == null) { break; } array = array2; } return array; } private static Point2[] Points(IReadOnlyList<Point2> stones, int[] order) { Point2[] array = new Point2[order.Length]; for (int i = 0; i < order.Length; i++) { array[i] = stones[order[i]]; } return array; } public static IReadOnlyList<int[]> FourthStoneInsertions(IReadOnlyList<Point2> stones) { if (stones == null || stones.Count != 4) { throw new ArgumentException("Exactly four stone positions are required.", "stones"); } List<(int[], double)> list = new List<(int[], double)>(); for (int i = 0; i < 4; i++) { int[] array = new int[4]; int num = 0; for (int j = 0; j < 4; j++) { array[j] = ((j == i) ? 3 : num++); } Point2 a = stones[(i + 2) % 3]; Point2 b = stones[i % 3]; double item = Distance(a, stones[3]) + Distance(stones[3], b) - Distance(a, b); list.Add((array, item)); } list.Sort(((int[] Order, double AddedPerimeter) left, (int[] Order, double AddedPerimeter) right) => left.AddedPerimeter.CompareTo(right.AddedPerimeter)); int[][] array2 = new int[4][]; for (int num2 = 0; num2 < array2.Length; num2++) { array2[num2] = list[num2].Item1; } return array2; } private static double Distance(Point2 a, Point2 b) { double num = a.X - b.X; double num2 = a.Z - b.Z; return Math.Sqrt(num * num + num2 * num2); } } public enum SpawnSource { Biome, Raid, LocationPoint, PhysicalSpawner, BossSummon } public enum LocationPointRule { MatchWorkbench, Block, Allow } public enum PhysicalSpawnerRule { Allow, Block } public sealed class SpawnPolicy { private readonly Dictionary<string, bool> _biomeCreatureBlocked = new Dictionary<string, bool>(StringComparer.Ordinal); public LocationPointRule LocationPoints { get; set; } public PhysicalSpawnerRule PhysicalSpawners { get; set; } public void SetBiomeCreatureBlocked(string prefabName, bool blocked) { if (string.IsNullOrWhiteSpace(prefabName)) { throw new ArgumentException("A prefab name is required.", "prefabName"); } _biomeCreatureBlocked[prefabName] = blocked; } public bool ShouldBlock(SpawnSource source, string? prefabName = null, bool locationAllowsPlayerBase = false) { switch (source) { case SpawnSource.Biome: { bool value = default(bool); return prefabName == null || !_biomeCreatureBlocked.TryGetValue(prefabName, out value) || value; } case SpawnSource.Raid: return true; case SpawnSource.LocationPoint: if (LocationPoints != LocationPointRule.Block) { if (LocationPoints == LocationPointRule.MatchWorkbench) { return !locationAllowsPlayerBase; } return false; } return true; case SpawnSource.PhysicalSpawner: return PhysicalSpawners == PhysicalSpawnerRule.Block; case SpawnSource.BossSummon: return false; default: throw new ArgumentOutOfRangeException("source"); } } } public static class StoneAlignment { public const int Directions = 16; public static bool TrySnap(Point2 origin, Point2 candidate, out Point2 snapped) { snapped = candidate; double num = candidate.X - origin.X; double num2 = candidate.Z - origin.Z; if (num * num + num2 * num2 < 1E-06) { return false; } double num3 = Math.PI / 8.0; double num4 = Math.Round(Math.Atan2(num2, num) / num3) * num3; double num5 = Math.Cos(num4); double num6 = Math.Sin(num4); double num7 = num * num5 + num2 * num6; snapped = new Point2(origin.X + num7 * num5, origin.Z + num7 * num6); return true; } public static bool TrySnapCorner(Point2 selected, Point2 last, Point2 candidate, double maxSpan, out Point2 snapped) { snapped = candidate; if (maxSpan <= 0.0 || Math.Abs(selected.X - last.X) + Math.Abs(selected.Z - last.Z) < 1E-06) { return false; } double num = last.X - selected.X; double num2 = last.Z - selected.Z; double num3 = Math.Min(5.0, Math.Max(2.0, maxSpan * 0.1)); double num4 = num3 * num3 + 1E-09; bool result = false; double num5 = Math.PI / 8.0; for (int i = 0; i < 16; i++) { double num6 = Math.Cos((double)i * num5); double num7 = Math.Sin((double)i * num5); for (int j = 0; j < 16; j++) { double num8 = Math.Cos((double)j * num5); double num9 = Math.Sin((double)j * num5); double num10 = num6 * num9 - num7 * num8; if (Math.Abs(num10) < 1E-06) { continue; } double num11 = (num * num9 - num2 * num8) / num10; double num12 = (num * num7 - num2 * num6) / num10; if (!(num11 < 0.5) && !(num12 < 0.5) && !(num11 > maxSpan + 1E-06)) { Point2 point = new Point2(selected.X + num11 * num6, selected.Z + num11 * num7); double num13 = point.X - candidate.X; double num14 = point.Z - candidate.Z; double num15 = num13 * num13 + num14 * num14; if (!(num15 >= num4)) { num4 = num15; snapped = point; result = true; } } } } return result; } public static bool TrySnapCorner(Point2 selected, IReadOnlyList<Point2> draftStones, Point2 candidate, double maxSpan, out Point2 snapped) { snapped = candidate; bool result = false; double num = double.PositiveInfinity; foreach (Point2 draftStone in draftStones) { if (TrySnapCorner(selected, draftStone, candidate, maxSpan, out var snapped2)) { double num2 = snapped2.X - candidate.X; double num3 = snapped2.Z - candidate.Z; double num4 = num2 * num2 + num3 * num3; if (!(num4 >= num)) { num = num4; snapped = snapped2; result = true; } } } return result; } } } namespace BaseProtection.Plugin { public enum BoundaryDisplayMode { AlwaysShow, ShowWithHammer, NeverShow } [BepInPlugin("valheim.runestone.baseprotection", "Runestone Base Protection", "1.1.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public sealed class BaseProtectionPlugin : BaseUnityPlugin { public const string ModGuid = "valheim.runestone.baseprotection"; public const string ModName = "Runestone Base Protection"; public const string ModVersion = "1.1.0"; public const string StonePrefabName = "piece_baseprotection_runestone"; internal static BaseProtectionPlugin Instance; private Harmony? _harmony; private SoftReference<GameObject>? _traderRuneReference; private Piece? _stonePiece; private bool _featuresWereAvailable; private float _nextRefresh; private string _lastLimitWarning = ""; internal ConfigEntry<float> MaxEdge; internal ConfigEntry<float> MaxArea; internal ConfigEntry<float> FieldGap; internal ConfigEntry<float> ForeignStructureBuffer; internal ConfigEntry<int> MaxFieldsPerPlayer; internal ConfigEntry<float> MaxTotalAreaPerPlayer; internal ConfigEntry<LocationPointRule> LocationPoints; internal ConfigEntry<PhysicalSpawnerRule> PhysicalSpawners; internal ConfigEntry<string> BiomeCreatureOverrides; internal ConfigEntry<bool> FieldProvidesWorkbenchBuilding; internal ConfigEntry<bool> FieldProvidesForgeBuilding; internal ConfigEntry<bool> FieldProvidesStonecutterBuilding; internal ConfigEntry<bool> FieldProvidesArtisanTableBuilding; internal ConfigEntry<bool> FieldProvidesBlackForgeBuilding; internal ConfigEntry<bool> PreventRainWearInFields; internal ConfigEntry<bool> RepelHostileMobs; internal ConfigEntry<BoundaryDisplayMode> BoundaryVisibility; internal static bool FeaturesAvailable { get { if ((Object)(object)ZNet.instance != (Object)null) { if (!ZNet.instance.IsServer()) { return ModCompatibility.IsModuleOnServer("valheim.runestone.baseprotection"); } return true; } return false; } } internal void LogInfo(string message) { ((BaseUnityPlugin)this).Logger.LogInfo((object)message); } internal void LogError(Exception error) { ((BaseUnityPlugin)this).Logger.LogError((object)error); } internal void LogWarning(string message) { ((BaseUnityPlugin)this).Logger.LogWarning((object)message); } private void Awake() { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Expected O, but got Unknown Instance = this; MigratePreviousConfiguration(); BindConfiguration(); ((BaseUnityPlugin)this).Config.SettingChanged += delegate { ValidateConfigRelationships(); }; ValidateConfigRelationships(); PrefabManager.OnVanillaPrefabsAvailable += RegisterStone; _harmony = new Harmony("valheim.runestone.baseprotection"); _harmony.PatchAll(typeof(BaseProtectionPlugin).Assembly); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Runestone Base Protection 1.1.0 loaded"); } private void MigratePreviousConfiguration() { string text = Path.Combine(Paths.ConfigPath, "fearl.valheim.baseprotection.cfg"); string configFilePath = ((BaseUnityPlugin)this).Config.ConfigFilePath; if (File.Exists(configFilePath) || !File.Exists(text)) { return; } try { File.Move(text, configFilePath); try { ((BaseUnityPlugin)this).Config.Reload(); } catch { File.Move(configFilePath, text); throw; } ((BaseUnityPlugin)this).Logger.LogInfo((object)("Renamed existing settings to " + Path.GetFileName(configFilePath) + ".")); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not rename the existing settings file: " + ex.Message)); } } private void Update() { NetworkCommands.CheckPendingLink(); bool featuresAvailable = FeaturesAvailable; if ((Object)(object)_stonePiece != (Object)null) { _stonePiece.m_enabled = featuresAvailable; } if (!featuresAvailable) { if (_featuresWereAvailable) { FieldVisuals.Clear(); FieldRegistry.ClearCachedFields(); RunestoneInteraction.ClearSelection(); } _featuresWereAvailable = false; return; } _featuresWereAvailable = true; RunestoneInteraction.UpdateSelectionCache(); NetworkCommands.TryRegister(); bool flag = BoundaryVisibility.Value switch { BoundaryDisplayMode.AlwaysShow => true, BoundaryDisplayMode.NeverShow => false, _ => ShouldShowBoundary(), }; FieldVisuals.SetVisibility(flag); if (!(Time.time < _nextRefresh)) { _nextRefresh = Time.time + 1f; StoneAccess.AdoptServerOwnership(); FieldRegistry.Refresh(); FieldVisuals.Refresh(FieldRegistry.VisibleOutlines, flag); } } private static bool ShouldShowBoundary() { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || !((Character)localPlayer).InPlaceMode()) { return false; } ItemData rightItem = ((Humanoid)localPlayer).RightItem; object obj; if (rightItem == null) { obj = null; } else { GameObject dropPrefab = rightItem.m_dropPrefab; obj = ((dropPrefab != null) ? ((Object)dropPrefab).name : null); } return (string?)obj == "Hammer"; } private void OnDestroy() { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) PrefabManager.OnVanillaPrefabsAvailable -= RegisterStone; Harmony? harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } FieldVisuals.Clear(); _traderRuneReference?.Release(); } private void BindConfiguration() { MaxEdge = ((BaseUnityPlugin)this).Config.Bind<float>("Fields", "Maximum edge length (m)", 200f, Description("Maximum length of a finalized polygon edge, or the diameter of a two-stone circle.", (AcceptableValueBase?)(object)new SafeFloatRange(1f, 10000f, 200f))); MaxArea = ((BaseUnityPlugin)this).Config.Bind<float>("Fields", "Maximum area (m2)", 40000f, Description("Largest area one finalized field may cover.", (AcceptableValueBase?)(object)new SafeFloatRange(1f, 100000000f, 40000f))); FieldGap = ((BaseUnityPlugin)this).Config.Bind<float>("Fields", "Minimum field gap (m)", 5f, Description("Minimum separation between active fields; 0 allows touching but not overlap.", (AcceptableValueBase?)(object)new SafeFloatRange(0f, 10000f, 5f))); ForeignStructureBuffer = ((BaseUnityPlugin)this).Config.Bind<float>("Anti-grief", "Foreign structure buffer (m)", 20f, Description("A field cannot approach another player's structures closer than this; 0 disables the buffer.", (AcceptableValueBase?)(object)new SafeFloatRange(0f, 10000f, 20f))); MaxFieldsPerPlayer = ((BaseUnityPlugin)this).Config.Bind<int>("Anti-grief", "Maximum fields per player", 4, Description("Maximum active fields owned by one player; 0 prevents activation.", (AcceptableValueBase?)(object)new SafeIntegerRange(0, 1000))); MaxTotalAreaPerPlayer = ((BaseUnityPlugin)this).Config.Bind<float>("Anti-grief", "Maximum total area per player (m2)", 160000f, Description("Maximum combined area of one player's active fields; 0 prevents activation.", (AcceptableValueBase?)(object)new SafeFloatRange(0f, 1E+09f, 160000f))); LocationPoints = ((BaseUnityPlugin)this).Config.Bind<LocationPointRule>("Spawning", "Location spawn points", LocationPointRule.MatchWorkbench, Description("MatchWorkbench honors each location point's spawnInPlayerBase flag. Does not control physical spawner objects.")); PhysicalSpawners = ((BaseUnityPlugin)this).Config.Bind<PhysicalSpawnerRule>("Spawning", "Physical spawner objects", PhysicalSpawnerRule.Allow, Description("Controls destructible spawners such as nests and bone piles; Allow matches workbench behavior.")); BiomeCreatureOverrides = ((BaseUnityPlugin)this).Config.Bind<string>("Spawning", "Biome creature overrides", "", Description("Optional semicolon-separated prefab=Allow or prefab=Block entries, e.g. Boar=Allow;Goblin=Allow. Use exact internal prefab names. Unlisted biome creatures are blocked; raids are unaffected by this list.")); FieldProvidesWorkbenchBuilding = ((BaseUnityPlugin)this).Config.Bind<bool>("Building", "Field provides workbench building range", false, Description("If true, standing inside an active field provides workbench range for hammer placement, removal, and structure repair, including targets just outside the field. Item crafting, item repair, and placing station upgrades still require a real station.")); FieldProvidesForgeBuilding = BindBuildStation("forge"); FieldProvidesStonecutterBuilding = BindBuildStation("stonecutter"); FieldProvidesArtisanTableBuilding = BindBuildStation("artisan table"); FieldProvidesBlackForgeBuilding = BindBuildStation("black forge"); PreventRainWearInFields = ((BaseUnityPlugin)this).Config.Bind<bool>("Building", "Prevent rain and water wear inside active fields", false, Description("If true, exposed structures inside an active field do not weather from rain or water. This does not repair existing damage or prevent attacks, support failure, snow, ash, or lava damage.")); RepelHostileMobs = ((BaseUnityPlugin)this).Config.Bind<bool>("Defense", "Repel ordinary hostile creatures", false, Description("If true, ordinary hostile creatures approaching an active field turn away at its boundary. Bosses, raid creatures, tamed creatures, and creatures already inside a field are unaffected. This changes AI movement; it is not a physical barrier.")); BoundaryVisibility = ((BaseUnityPlugin)this).Config.Bind<BoundaryDisplayMode>("Visuals", "Boundary visibility", BoundaryDisplayMode.ShowWithHammer, "Client-only display preference: always show field outlines, show them only with the build hammer, or never show them."); ConfigEntry<bool> BindBuildStation(string stationName) { return ((BaseUnityPlugin)this).Config.Bind<bool>("Building", "Field provides " + stationName + " building range", false, Description("If true, standing inside an active field satisfies the " + stationName + " range requirement for hammer placement, removal, and structure repair. Item crafting, item repair, and placing station upgrades still require a real station.")); } static ConfigDescription Description(string text, AcceptableValueBase? accepted = null) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown return new ConfigDescription(text, accepted, new object[1] { (object)new ConfigurationManagerAttributes { IsAdminOnly = true, IsAdvanced = false } }); } } private void ValidateConfigRelationships() { string text = ((MaxTotalAreaPerPlayer.Value < MaxArea.Value) ? "Maximum total area per player is smaller than maximum area per field; the per-player limit will be the effective cap." : ""); if (!(text == _lastLimitWarning)) { _lastLimitWarning = text; if (text.Length != 0) { ((BaseUnityPlugin)this).Logger.LogWarning((object)text); } } } private static void RegisterStone() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Expected O, but got Unknown //IL_014b: Unknown result type (might be due to invalid IL or missing references) PieceConfig val = new PieceConfig { Name = "Linked Runestone", Description = "Select a stone, then hold Ctrl while placing to align or snap a corner. Link two to eight stones; Shift+E to finish.", PieceTable = PieceTables.Hammer, CraftingStation = CraftingStations.Workbench, Category = PieceCategories.Misc }; val.AddRequirement("Stone", 10, true); CustomPiece val2 = new CustomPiece("piece_baseprotection_runestone", "guard_stone_test", val); GameObject piecePrefab = val2.PiecePrefab; PrivateArea[] componentsInChildren = piecePrefab.GetComponentsInChildren<PrivateArea>(true); for (int i = 0; i < componentsInChildren.Length; i++) { Object.DestroyImmediate((Object)(object)componentsInChildren[i]); } EffectArea[] componentsInChildren2 = piecePrefab.GetComponentsInChildren<EffectArea>(true); for (int i = 0; i < componentsInChildren2.Length; i++) { Object.DestroyImmediate((Object)(object)componentsInChildren2[i]); } CircleProjector[] componentsInChildren3 = piecePrefab.GetComponentsInChildren<CircleProjector>(true); for (int i = 0; i < componentsInChildren3.Length; i++) { Object.DestroyImmediate((Object)(object)componentsInChildren3[i]); } Instance.UseTraderRuneVisual(piecePrefab); StaticTarget[] componentsInChildren4 = piecePrefab.GetComponentsInChildren<StaticTarget>(true); foreach (StaticTarget obj in componentsInChildren4) { obj.m_primaryTarget = false; obj.m_randomTarget = false; } WearNTear component = piecePrefab.GetComponent<WearNTear>(); if ((Object)(object)component != (Object)null) { component.m_noRoofWear = false; } piecePrefab.AddComponent<RunestoneInteraction>(); AddSnapPoints(piecePrefab); val2.Piece.m_targetNonPlayerBuilt = false; if (!Application.isBatchMode) { try { Sprite val3 = RenderManager.Instance.Render(piecePrefab, RenderManager.IsometricRotation); if ((Object)(object)val3 != (Object)null) { val2.Piece.m_icon = val3; } else { ((BaseUnityPlugin)Instance).Logger.LogWarning((object)"Could not render the runestone icon; using the prefab icon."); } } catch (Exception arg) { ((BaseUnityPlugin)Instance).Logger.LogWarning((object)$"Could not render the runestone icon: {arg}"); } } PieceManager.Instance.AddPiece(val2); Instance._stonePiece = val2.Piece; PrefabManager.OnVanillaPrefabsAvailable -= RegisterStone; ((BaseUnityPlugin)Instance).Logger.LogInfo((object)"Registered linked runestone piece"); } private void UseTraderRuneVisual(GameObject prefab) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) try { if (!AssetManager.Instance.IsReady()) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"TraderRune assets are not ready; retaining the original stone visual."); return; } SoftReference<GameObject> softReference = AssetManager.Instance.GetSoftReference<GameObject>("TraderRune"); if (!softReference.IsValid) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"TraderRune prefab was not found; retaining the original stone visual."); return; } softReference.Load(); softReference.HoldReference(); _traderRuneReference = softReference; GameObject asset = softReference.Asset; if ((Object)(object)asset == (Object)null || asset.GetComponentsInChildren<MeshRenderer>(true).Length == 0) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"TraderRune has no visible mesh; retaining the original stone visual."); return; } Renderer[] componentsInChildren = prefab.GetComponentsInChildren<Renderer>(true); for (int i = 0; i < componentsInChildren.Length; i++) { componentsInChildren[i].enabled = false; } ParticleSystem[] componentsInChildren2 = prefab.GetComponentsInChildren<ParticleSystem>(true); foreach (ParticleSystem obj in componentsInChildren2) { obj.Stop(true, (ParticleSystemStopBehavior)0); EmissionModule emission = obj.emission; ((EmissionModule)(ref emission)).enabled = false; } CopyTraderVisual(asset.transform, prefab.transform); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Using the trader barrier runestone visual."); } catch (Exception arg) { ((BaseUnityPlugin)this).Logger.LogError((object)$"Could not use the TraderRune visual: {arg}"); } } private static void CopyTraderVisual(Transform source, Transform destination) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0065: 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_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Expected O, but got Unknown GameObject val = new GameObject("TraderRuneVisual_" + ((Object)source).name); val.transform.SetParent(destination, false); val.transform.localPosition = (((Object)(object)source.parent == (Object)null) ? Vector3.zero : source.localPosition); val.transform.localRotation = (((Object)(object)source.parent == (Object)null) ? Quaternion.identity : source.localRotation); val.transform.localScale = (((Object)(object)source.parent == (Object)null) ? Vector3.one : source.localScale); MeshFilter component = ((Component)source).GetComponent<MeshFilter>(); MeshRenderer component2 = ((Component)source).GetComponent<MeshRenderer>(); if ((Object)(object)component != (Object)null && (Object)(object)component2 != (Object)null) { val.AddComponent<MeshFilter>().sharedMesh = component.sharedMesh; MeshRenderer obj = val.AddComponent<MeshRenderer>(); ((Renderer)obj).sharedMaterials = ((Renderer)component2).sharedMaterials; ((Renderer)obj).shadowCastingMode = ((Renderer)component2).shadowCastingMode; ((Renderer)obj).receiveShadows = ((Renderer)component2).receiveShadows; } foreach (Transform item in source) { CopyTraderVisual(item, val.transform); } } private static void AddSnapPoints(GameObject prefab) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_002b: 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_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) int num = LayerMask.NameToLayer("piece_nonsolid"); if (num >= 0) { GameObject val = new GameObject("BaseProtectionSnapDetector"); val.transform.SetParent(prefab.transform, false); val.layer = num; BoxCollider obj = val.AddComponent<BoxCollider>(); obj.center = new Vector3(0f, 0.7f, 0f); obj.size = new Vector3(1.5f, 1.4f, 1.5f); } else { ((BaseUnityPlugin)Instance).Logger.LogWarning((object)"piece_nonsolid layer unavailable; nearby piece snapping may fail."); } Vector3[] array = (Vector3[])(object)new Vector3[4] { new Vector3(-0.75f, 0f, -0.75f), new Vector3(0.75f, 0f, -0.75f), new Vector3(0.75f, 0f, 0.75f), new Vector3(-0.75f, 0f, 0.75f) }; for (int i = 0; i <= array.Length; i++) { GameObject val2 = new GameObject((i == 0) ? "Center" : $"Corner {i}") { tag = "snappoint" }; val2.transform.SetParent(prefab.transform, false); val2.transform.localPosition = ((i == 0) ? Vector3.zero : array[i - 1]); } } } internal static class VirtualBuildStationContext { [ThreadStatic] internal static CraftingStation? Station; [ThreadStatic] internal static bool RemovingPiece; [ThreadStatic] internal static bool RepairingPiece; private static bool MatchesEnabledStation(CraftingStation required, string prefabName, ConfigEntry<bool> enabled) { if (!enabled.Value) { return false; } GameObject prefab = PrefabManager.Instance.GetPrefab(prefabName); CraftingStation val = ((prefab != null) ? prefab.GetComponent<CraftingStation>() : null); if ((Object)(object)val != (Object)null) { return required.m_name == val.m_name; } return false; } private static bool HasEnabledRequirement(Piece piece) { CraftingStation val = piece?.m_craftingStation; if ((Object)(object)val == (Object)null) { return false; } BaseProtectionPlugin instance = BaseProtectionPlugin.Instance; if (!MatchesEnabledStation(val, CraftingStations.Workbench, instance.FieldProvidesWorkbenchBuilding) && !MatchesEnabledStation(val, CraftingStations.Forge, instance.FieldProvidesForgeBuilding) && !MatchesEnabledStation(val, CraftingStations.Stonecutter, instance.FieldProvidesStonecutterBuilding) && !MatchesEnabledStation(val, CraftingStations.ArtisanTable, instance.FieldProvidesArtisanTableBuilding)) { return MatchesEnabledStation(val, CraftingStations.BlackForge, instance.FieldProvidesBlackForgeBuilding); } return true; } internal static CraftingStation? For(Player player, Piece piece, RequirementMode mode) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0033: Unknown result type (might be due to invalid IL or missing references) if ((int)mode == 1 || (Object)(object)piece == (Object)null || piece.m_isUpgrade || (Object)(object)((Component)piece).GetComponent<StationExtension>() != (Object)null || !HasEnabledRequirement(piece)) { return null; } if (!FieldRegistry.Contains(((Component)player).transform.position)) { return null; } return piece.m_craftingStation; } internal static bool CanMaintain(Player player, Piece piece) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) if ((RemovingPiece || RepairingPiece) && HasEnabledRequirement(piece)) { return FieldRegistry.Contains(((Component)player).transform.position); } return false; } } [HarmonyPatch(typeof(Player), "HaveRequirements", new Type[] { typeof(Piece), typeof(RequirementMode) })] internal static class BuildingRequirementPatch { private static void Prefix(Player __instance, Piece piece, RequirementMode mode, out CraftingStation? __state) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) __state = VirtualBuildStationContext.Station; VirtualBuildStationContext.Station = VirtualBuildStationContext.For(__instance, piece, mode); } private static void Postfix(CraftingStation? __state) { VirtualBuildStationContext.Station = __state; } private static Exception? Finalizer(Exception? __exception, CraftingStation? __state) { VirtualBuildStationContext.Station = __state; return __exception; } } [HarmonyPatch(typeof(CraftingStation), "HaveBuildStationInRange")] internal static class VirtualBuildStationPatch { private static void Postfix(string name, ref CraftingStation __result) { CraftingStation station = VirtualBuildStationContext.Station; if ((Object)(object)__result == (Object)null && (Object)(object)station != (Object)null && station.m_name == name) { __result = station; } } } [HarmonyPatch(typeof(Player), "RemovePiece")] internal static class VirtualBuildStationRemoveContextPatch { private static void Prefix(out bool __state) { __state = VirtualBuildStationContext.RemovingPiece; VirtualBuildStationContext.RemovingPiece = true; } private static void Postfix(bool __state) { VirtualBuildStationContext.RemovingPiece = __state; } private static Exception? Finalizer(Exception? __exception, bool __state) { VirtualBuildStationContext.RemovingPiece = __state; return __exception; } } [HarmonyPatch(typeof(Player), "Repair", new Type[] { typeof(ItemData), typeof(Piece) })] internal static class VirtualBuildStationRepairContextPatch { private static void Prefix(out bool __state) { __state = VirtualBuildStationContext.RepairingPiece; VirtualBuildStationContext.RepairingPiece = true; } private static void Postfix(bool __state) { VirtualBuildStationContext.RepairingPiece = __state; } private static Exception? Finalizer(Exception? __exception, bool __state) { VirtualBuildStationContext.RepairingPiece = __state; return __exception; } } [HarmonyPatch(typeof(Player), "CheckCanRemovePiece", new Type[] { typeof(Piece) })] internal static class VirtualBuildStationMaintainCheckPatch { private static bool Prefix(Player __instance, Piece piece, ref bool __result) { if (StoneAccess.IsStone((Component)(object)piece) && !StoneAccess.CanLocalEdit(__instance, piece)) { __result = false; RunestoneInteraction.Notify("Only the stone owner or a server admin can remove it."); return false; } if (!VirtualBuildStationContext.CanMaintain(__instance, piece)) { return true; } __result = true; return false; } } [HarmonyPatch(typeof(Hud), "SetupPieceInfo")] internal static class BuildingRequirementHudPatch { private static void Postfix(Hud __instance, Piece piece) { //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || (Object)(object)piece == (Object)null || (Object)(object)VirtualBuildStationContext.For(localPlayer, piece, (RequirementMode)2) == (Object)null) { return; } int num = piece.m_resources.Length; GameObject[] requirementItems = __instance.m_requirementItems; if (num >= requirementItems.Length || (Object)(object)requirementItems[num] == (Object)null) { return; } Transform transform = requirementItems[num].transform; Transform obj = transform.Find("res_amount"); TMP_Text val = ((obj != null) ? ((Component)obj).GetComponent<TMP_Text>() : null); if (!((Object)(object)val == (Object)null) && !(val.text != Localization.instance.Localize("$menu_none"))) { val.text = "Runestone"; ((Graphic)val).color = Color.white; Transform obj2 = transform.Find("res_icon"); Image val2 = ((obj2 != null) ? ((Component)obj2).GetComponent<Image>() : null); if ((Object)(object)val2 != (Object)null) { ((Graphic)val2).color = Color.white; } } } } internal sealed class ActiveField { internal string GroupId = ""; internal long OwnerId; internal FieldGeometry Geometry; internal Vector3[] Vertices; } internal sealed class FieldOutline { internal string GroupId = ""; internal Point2[] Stones; internal bool IsDraft; } internal static class FieldRegistry { internal const string GroupKey = "baseprotection_group"; internal const string OrderKey = "baseprotection_order"; internal const string ActiveKey = "baseprotection_active"; internal const string ShapeKey = "baseprotection_shape"; private static readonly HashSet<RunestoneInteraction> LoadedStones = new HashSet<RunestoneInteraction>(); private static readonly List<ActiveField> Fields = new List<ActiveField>(); private static readonly List<FieldOutline> Outlines = new List<FieldOutline>(); private static readonly List<ActiveField> ServerFields = new List<ActiveField>(); private static bool _hasServerSnapshot; internal static IReadOnlyList<ActiveField> ActiveFields => Fields; internal static IReadOnlyList<FieldOutline> VisibleOutlines => Outlines; internal static bool HasServerSnapshot => _hasServerSnapshot; internal static void ClearCachedFields() { Fields.Clear(); Outlines.Clear(); ClearServerSnapshot(); } internal static void ClearServerSnapshot() { ServerFields.Clear(); _hasServerSnapshot = false; Fields.Clear(); } internal static void ApplyServerSnapshot(IReadOnlyList<ActiveField> fields) { ServerFields.Clear(); ServerFields.AddRange(fields); _hasServerSnapshot = true; if (!((Object)(object)ZNet.instance == (Object)null) && !ZNet.instance.IsServer()) { Fields.Clear(); Fields.AddRange(ServerFields); } } internal static bool TryGetGroupOutline(string group, out Point2[] points) { foreach (FieldOutline outline in Outlines) { if (outline.GroupId == group) { points = outline.Stones; return true; } } points = Array.Empty<Point2>(); return false; } internal static void Add(RunestoneInteraction stone) { LoadedStones.Add(stone); } internal static void Remove(RunestoneInteraction stone) { LoadedStones.Remove(stone); } internal static void Refresh() { Fields.Clear(); Outlines.Clear(); if ((Object)(object)ZNet.instance == (Object)null || ZDOMan.instance == null) { return; } Dictionary<string, List<ZDO>> dictionary = new Dictionary<string, List<ZDO>>(StringComparer.Ordinal); RunestoneInteraction[] array = LoadedStones.ToArray(); foreach (RunestoneInteraction runestoneInteraction in array) { if ((Object)(object)runestoneInteraction == (Object)null) { continue; } ZNetView component = ((Component)runestoneInteraction).GetComponent<ZNetView>(); ZDO val = ((component != null) ? component.GetZDO() : null); if (val == null) { continue; } string text = val.GetString("baseprotection_group", ""); if (!string.IsNullOrEmpty(text)) { if (!dictionary.TryGetValue(text, out var value)) { value = (dictionary[text] = new List<ZDO>()); } value.Add(val); } } foreach (KeyValuePair<string, List<ZDO>> item in dictionary) { Point2[] points2; ZDO val2 = ((IEnumerable<ZDO>)item.Value).FirstOrDefault((Func<ZDO, bool>)((ZDO z) => TryDecodeShape(z.GetString("baseprotection_shape", ""), out points2))); if (val2 != null && TryDecodeShape(val2.GetString("baseprotection_shape", ""), out Point2[] points)) { bool flag = val2.GetInt("baseprotection_active", 1) == 0; Outlines.Add(new FieldOutline { GroupId = item.Key, Stones = points, IsDraft = flag }); if (!flag && FieldGeometry.TryCreate(points, BaseProtectionPlugin.Instance.MaxEdge.Value, BaseProtectionPlugin.Instance.MaxArea.Value, out FieldGeometry field, out string _)) { Fields.Add(new ActiveField { GroupId = item.Key, OwnerId = val2.GetLong(ZDOVars.s_creator, 0L), Geometry = field, Vertices = item.Value.Select((ZDO z) => z.GetPosition()).ToArray() }); } } else { ActiveField activeField = Build(item.Key, item.Value); if (activeField != null) { Fields.Add(activeField); Outlines.Add(new FieldOutline { GroupId = item.Key, Stones = activeField.Geometry.Stones.ToArray(), IsDraft = false }); } } } if (ZNet.instance.IsServer()) { Fields.Clear(); Fields.AddRange(BuildAll(GetAllStoneZdos())); NetworkCommands.PublishFieldSnapshot(Fields); } else if (_hasServerSnapshot) { Fields.Clear(); Fields.AddRange(ServerFields); } } internal static ActiveField? Build(string group, IReadOnlyList<ZDO> zdos) { if (zdos.Count < 2 || zdos.Count > 8 || zdos.Any((ZDO z) => z.GetInt("baseprotection_active", 1) == 0)) { return null; } ZDO[] array = zdos.OrderBy((ZDO z) => z.GetInt("baseprotection_order", -1)).ToArray(); long owner = array[0].GetLong(ZDOVars.s_creator, 0L); if (owner == 0L || array.Any((ZDO z) => z.GetLong(ZDOVars.s_creator, 0L) != owner)) { return null; } for (int num = 0; num < array.Length; num++) { int num2 = array[num].GetInt("baseprotection_order", -1); if (num2 < 0 || (num > 0 && num2 == array[num - 1].GetInt("baseprotection_order", -1))) { return null; } } Vector3[] array2 = array.Select((ZDO z) => z.GetPosition()).ToArray(); Point2[] stones = array2.Select((Vector3 v) => new Point2(v.x, v.z)).ToArray(); BaseProtectionPlugin instance = BaseProtectionPlugin.Instance; if (!FieldGeometry.TryCreate(stones, instance.MaxEdge.Value, instance.MaxArea.Value, out FieldGeometry field, out string _)) { return null; } return new ActiveField { GroupId = group, OwnerId = owner, Geometry = field, Vertices = array2 }; } internal static bool Contains(Vector3 position) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) if (!BaseProtectionPlugin.FeaturesAvailable) { return false; } Point2 point = new Point2(position.x, position.z); foreach (ActiveField field in Fields) { if (field.Geometry.Contains(point)) { return true; } } return false; } internal static List<ZDO> GetAllStoneZdos() { List<ZDO> list = new List<ZDO>(); if (ZDOMan.instance == null) { return list; } int num = 0; int num2 = 0; while (!ZDOMan.instance.GetAllZDOsWithPrefabIterative("piece_baseprotection_runestone", list, ref num)) { if (++num2 > 4096) { throw new InvalidOperationException("Stone scan did not complete."); } } return list; } internal static List<ActiveField> BuildAll(IReadOnlyList<ZDO> allStones) { List<ActiveField> list = new List<ActiveField>(); foreach (IGrouping<string, ZDO> item in from z in allStones where !string.IsNullOrEmpty(z.GetString("baseprotection_group", "")) group z by z.GetString("baseprotection_group", "")) { ActiveField activeField = Build(item.Key, item.ToArray()); if (activeField != null) { list.Add(activeField); } } return list; } internal static string EncodeShape(IEnumerable<Vector3> positions) { return string.Join(";", positions.Select((Vector3 p) => p.x.ToString("R", CultureInfo.InvariantCulture) + "," + p.z.ToString("R", CultureInfo.InvariantCulture))); } internal static bool TryDecodeShape(string encoded, out Point2[] points) { points = Array.Empty<Point2>(); if (string.IsNullOrEmpty(encoded)) { return false; } string[] array = encoded.Split(new char[1] { ';' }); if (array.Length < 2 || array.Length > 8) { return false; } Point2[] array2 = new Point2[array.Length]; for (int i = 0; i < array.Length; i++) { string[] array3 = array[i].Split(new char[1] { ',' }); if (array3.Length != 2 || !double.TryParse(array3[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || !double.TryParse(array3[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result2)) { return false; } array2[i] = new Point2(result, result2); } points = array2; return true; } } internal static class FieldVisuals { private static readonly Dictionary<string, LineRenderer> Lines = new Dictionary<string, LineRenderer>(); private static Material? _material; internal static void Refresh(IReadOnlyList<FieldOutline> fields, bool visible) { //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Expected O, but got Unknown //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) if (Application.isBatchMode || (Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)ZoneSystem.instance == (Object)null) { Clear(); return; } HashSet<string> hashSet = new HashSet<string>(); foreach (FieldOutline field in fields) { hashSet.Add(field.GroupId); if (!Lines.TryGetValue(field.GroupId, out LineRenderer value) || (Object)(object)value == (Object)null) { if ((Object)(object)_material == (Object)null) { Shader val = Shader.Find("Sprites/Default"); if ((Object)(object)val == (Object)null) { return; } _material = new Material(val); } value = new GameObject("BaseProtection field outline").AddComponent<LineRenderer>(); ((Renderer)value).sharedMaterial = _material; value.useWorldSpace = true; value.startWidth = 0.18f; value.endWidth = 0.18f; value.numCapVertices = 4; Lines[field.GroupId] = value; } value.loop = !field.IsDraft || field.Stones.Length == 2; value.startColor = (field.IsDraft ? new Color(1f, 0.75f, 0.2f, 0.8f) : new Color(0.25f, 0.9f, 1f, 0.8f)); value.endColor = value.startColor; List<Vector3> list = Outline(field); value.positionCount = list.Count; value.SetPositions(list.ToArray()); } foreach (string item in new List<string>(Lines.Keys)) { if (!hashSet.Contains(item)) { if ((Object)(object)Lines[item] != (Object)null) { Object.Destroy((Object)(object)((Component)Lines[item]).gameObject); } Lines.Remove(item); } } SetVisibility(visible); } internal static void SetVisibility(bool visible) { foreach (LineRenderer value in Lines.Values) { if ((Object)(object)value != (Object)null && ((Component)value).gameObject.activeSelf != visible) { ((Component)value).gameObject.SetActive(visible); } } } internal static void Clear() { foreach (LineRenderer value in Lines.Values) { if ((Object)(object)value != (Object)null) { Object.Destroy((Object)(object)((Component)value).gameObject); } } Lines.Clear(); if ((Object)(object)_material != (Object)null) { Object.Destroy((Object)(object)_material); } _material = null; } private static List<Vector3> Outline(FieldOutline field) { //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) //IL_01db: Unknown result type (might be due to invalid IL or missing references) List<Vector3> list = new List<Vector3>(); Point2[] stones = field.Stones; if (stones.Length == 2) { Point2 point = stones[0]; Point2 point2 = stones[1]; double num = (point.X + point2.X) / 2.0; double num2 = (point.Z + point2.Z) / 2.0; double num3 = Math.Sqrt((point.X - point2.X) * (point.X - point2.X) + (point.Z - point2.Z) * (point.Z - point2.Z)) / 2.0; for (int i = 0; i < 96; i++) { double num4 = (double)(i * 2) * Math.PI / 96.0; list.Add(OnGround(num + Math.Cos(num4) * num3, num2 + Math.Sin(num4) * num3)); } } else { int num5 = (field.IsDraft ? (stones.Length - 1) : stones.Length); for (int j = 0; j < num5; j++) { Point2 point3 = stones[j]; Point2 point4 = stones[(j + 1) % stones.Length]; double num6 = Math.Sqrt((point3.X - point4.X) * (point3.X - point4.X) + (point3.Z - point4.Z) * (point3.Z - point4.Z)); int num7 = Math.Max(1, (int)Math.Ceiling(num6 / 2.0)); for (int k = 0; k < num7; k++) { double num8 = (double)k / (double)num7; list.Add(OnGround(point3.X + (point4.X - point3.X) * num8, point3.Z + (point4.Z - point3.Z) * num8)); } } if (field.IsDraft) { list.Add(OnGround(stones[^1].X, stones[^1].Z)); } } return list; } private static Vector3 OnGround(double x, double z) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor((float)x, 0f, (float)z); val.y = ZoneSystem.instance.GetGroundHeight(val) + 0.2f; return val; } } [HarmonyPatch(typeof(BaseAI), "MoveTowards")] internal static class MobRepulsionPatch { private const float DetectionDistance = 8f; internal static void Prefix(BaseAI __instance, ref Vector3 dir) { //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Invalid comparison between Unknown and I4 //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Invalid comparison between Unknown and I4 //IL_00b9: 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_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Unknown result type (might be due to invalid IL or missing references) //IL_01b5: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)BaseProtectionPlugin.Instance == (Object)null || !BaseProtectionPlugin.Instance.RepelHostileMobs.Value || !BaseProtectionPlugin.FeaturesAvailable) { return; } MonsterAI val = (MonsterAI)(object)((__instance is MonsterAI) ? __instance : null); if (val == null || FieldRegistry.ActiveFields.Count == 0) { return; } Character component = ((Component)val).GetComponent<Character>(); if ((Object)(object)component == (Object)null || !component.IsOwner() || component.IsBoss() || component.IsTamed() || val.IsEventCreature() || (int)component.GetFaction() == 11 || ((int)component.GetFaction() == 10 && !((BaseAI)val).IsAggravated())) { return; } float num = Mathf.Sqrt(dir.x * dir.x + dir.z * dir.z); if (num < 0.001f) { return; } Vector3 position = ((Component)component).transform.position; if (FieldRegistry.Contains(position) || !IsHostileToAnyPlayer(component)) { return; } Point2 position2 = new Point2(position.x, position.z); Point2 point = new Point2(dir.x, dir.z); float num2 = 8f + Mathf.Clamp(component.GetRadius(), 0f, 8f); int preferredSide = (((((Object)val).GetInstanceID() & 1) != 0) ? 1 : (-1)); double num3 = double.PositiveInfinity; Point2 point2 = point; foreach (ActiveField activeField in FieldRegistry.ActiveFields) { if (activeField.Geometry.TrySteerOutside(position2, point, num2, preferredSide, out var direction, out var boundaryDistance) && !(boundaryDistance >= num3)) { num3 = boundaryDistance; point2 = direction; } } if (!double.IsPositiveInfinity(num3)) { dir = new Vector3((float)(point2.X * (double)num), dir.y, (float)(point2.Z * (double)num)); } } private static bool IsHostileToAnyPlayer(Character character) { foreach (Player allPlayer in Player.GetAllPlayers()) { if ((Object)(object)allPlayer != (Object)null && BaseAI.IsEnemy(character, (Character)(object)allPlayer)) { return true; } } return false; } } [HarmonyPatch(typeof(BaseAI), "MoveTowardsSwoop")] internal static class MobRepulsionSwoopPatch { private static void Prefix(BaseAI __instance, ref Vector3 dir) { MobRepulsionPatch.Prefix(__instance, ref dir); } } internal static class NetworkCommands { private const string LinkRpc = "BaseProtection.Link.v1"; private const string UnlinkRpc = "BaseProtection.Unlink.v1"; private const string FinalizeRpc = "BaseProtection.Finalize.v1"; private const string FinishWithStoneRpc = "BaseProtection.FinishWithStone.v1"; private const string ReplyRpc = "BaseProtection.Reply.v1"; private const string FieldSnapshotRequestRpc = "BaseProtection.FieldSnapshotRequest.v1"; private const string FieldSnapshotRpc = "BaseProtection.FieldSnapshot.v1"; private const int FieldSnapshotProtocol = 1; private const int MaximumSnapshotFields = 16384; private const int MaximumSnapshotBytes = 4194304; private const int MaximumGroupIdLength = 128; private const double MaximumCoordinate = 1000000.0; private const float SnapshotRequestCooldown = 5f; private const float SnapshotRequestRetention = 60f; private const int MaximumTrackedSnapshotPeers = 512; private static ZRoutedRpc? _registered; private static ZNet? _snapshotNetwork; private static long _snapshotSource; private static float _lastSnapshotRequestTime = -1f; private static long _lastAppliedSnapshotRevision = -1L; private static long _serverSnapshotRevision; private static byte[]? _serverSnapshotBody; private static readonly Dictionary<long, float> LastSnapshotRequestByPeer = new Dictionary<long, float>(); private static float _nextSnapshotRequestCleanup; private static float _linkRequestTime = -1f; private static bool TryGetServerPeerId(out long peerId) { peerId = 0L; if ((Object)(object)ZNet.instance == (Object)null) { return false; } if (ZNet.instance.IsServer()) { peerId = ZNet.GetUID(); return true; } ZNetPeer serverPeer = ZNet.instance.GetServerPeer(); if (serverPeer == null) { return false; } peerId = serverPeer.m_uid; return true; } internal static void TryRegister() { ZRoutedRpc instance = ZRoutedRpc.instance; ZNet instance2 = ZNet.instance; if (instance == null || (Object)(object)instance2 == (Object)null) { return; } if (instance != _registered) { instance.Register<ZDOID, ZDOID>("BaseProtection.Link.v1", (Action<long, ZDOID, ZDOID>)OnLink); instance.Register<ZDOID>("BaseProtection.Unlink.v1", (Action<long, ZDOID>)OnUnlink); instance.Register<ZDOID>("BaseProtection.Finalize.v1", (Action<long, ZDOID>)OnFinalize); instance.Register<ZDOID, ZDOID>("BaseProtection.FinishWithStone.v1", (Action<long, ZDOID, ZDOID>)OnFinishWithStone); instance.Register<string>("BaseProtection.Reply.v1", (Action<long, string>)OnReply); instance.Register("BaseProtection.FieldSnapshotRequest.v1", (Action<long>)OnFieldSnapshotRequest); instance.Register<ZPackage>("BaseProtection.FieldSnapshot.v1", (Action<long, ZPackage>)OnFieldSnapshot); _registered = instance; } if ((Object)(object)instance2 != (Object)(object)_snapshotNetwork) { _snapshotNetwork = instance2; _snapshotSource = 0L; _lastSnapshotRequestTime = -1f; _lastAppliedSnapshotRevision = -1L; _serverSnapshotRevision = 0L; _serverSnapshotBody = null; LastSnapshotRequestByPeer.Clear(); _nextSnapshotRequestCleanup = 0f; if (!instance2.IsServer()) { FieldRegistry.ClearServerSnapshot(); } } if (instance2.IsServer()) { return; } if (!TryGetServerPeerId(out var peerId)) { if (_snapshotSource != 0L) { _snapshotSource = 0L; _lastSnapshotRequestTime = -1f; _lastAppliedSnapshotRevision = -1L; FieldRegistry.ClearServerSnapshot(); } return; } if (_snapshotSource != peerId) { _snapshotSource = peerId; _lastSnapshotRequestTime = -1f; _lastAppliedSnapshotRevision = -1L; FieldRegistry.ClearServerSnapshot(); } if (!FieldRegistry.HasServerSnapshot && (!(_lastSnapshotRequestTime >= 0f) || !(Time.time - _lastSnapshotRequestTime < 5f))) { _lastSnapshotRequestTime = Time.time; instance.InvokeRoutedRPC(peerId, "BaseProtection.FieldSnapshotRequest.v1", Array.Empty<object>()); } } internal static void PublishFieldSnapshot(IReadOnlyList<ActiveField> fields) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer() && _registered != null) { if (!TryEncodeFieldSnapshot(fields, out byte[] body)) { ZPackage val = new ZPackage(); val.Write(0); body = val.GetArray(); } if (_serverSnapshotBody == null || !_serverSnapshotBody.SequenceEqual(body)) { _serverSnapshotBody = body; _serverSnapshotRevision++; _registered.InvokeRoutedRPC(0L, "BaseProtection.FieldSnapshot.v1", new object[1] { CreateFieldSnapshotPackage() }); } } } private static ZPackage CreateFieldSnapshotPackage() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(1); val.Write(_serverSnapshotRevision); val.Write(_serverSnapshotBody ?? Array.Empty<byte>()); return val; } private static bool TryEncodeFieldSnapshot(IReadOnlyList<ActiveField> fields, out byte[] body) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown body = Array.Empty<byte>(); if (fields.Count > 16384) { BaseProtectionPlugin.Instance.LogWarning("Too many active fields to synchronize."); return false; } ZPackage val = new ZPackage(); val.Write(fields.Count); foreach (ActiveField item in fields.OrderBy<ActiveField, string>((ActiveField f) => f.GroupId, StringComparer.Ordinal)) { IReadOnlyList<Point2> stones = item.Geometry.Stones; if (item.GroupId.Length == 0 || item.GroupId.Length > 128 || item.OwnerId == 0L || stones.Count < 2 || stones.Count > 8) { BaseProtectionPlugin.Instance.LogWarning("An active field has invalid snapshot data."); return false; } val.Write(item.GroupId); val.Write(item.OwnerId); val.Write(stones.Count); foreach (Point2 item2 in stones) { val.Write(item2.X); val.Write(item2.Z); } } if (val.Size() > 4194304) { BaseProtectionPlugin.Instance.LogWarning("The active-field snapshot exceeds the network size limit."); return false; } body = val.GetArray(); return true; } private static void OnFieldSnapshotRequest(long sender) { ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null || !instance.IsServer() || !StoneRoutedPeerContext.Matches(sender) || instance.GetPeer(sender) == null || ZDOMan.instance == null) { return; } float time = Time.time; CleanSnapshotRequestPeers(instance, time); if (LastSnapshotRequestByPeer.TryGetValue(sender, out var value) && time >= value && time - value < 5f) { return; } LastSnapshotRequestByPeer[sender] = time; if (_serverSnapshotBody == null) { FieldRegistry.Refresh(); } if (_serverSnapshotBody != null) { ZRoutedRpc? registered = _registered; if (registered != null) { registered.InvokeRoutedRPC(sender, "BaseProtection.FieldSnapshot.v1", new object[1] { CreateFieldSnapshotPackage() }); } } } private static void CleanSnapshotRequestPeers(ZNet network, float now) { if (now < _nextSnapshotRequestCleanup && LastSnapshotRequestByPeer.Count < 512) { return; } KeyValuePair<long, float>[] array = LastSnapshotRequestByPeer.ToArray(); for (int i = 0; i < array.Length; i++) { KeyValuePair<long, float> keyValuePair = array[i]; if (now < keyValuePair.Value || now - keyValuePair.Value >= 60f || network.GetPeer(keyValuePair.Key) == null) { LastSnapshotRequestByPeer.Remove(keyValuePair.Key); } } while (LastSnapshotRequestByPeer.Count >= 512) { long key = LastSnapshotRequestByPeer.Aggregate((KeyValuePair<long, float> left, KeyValuePair<long, float> right) => (!(left.Value <= right.Value)) ? right : left).Key; LastSnapshotRequestByPeer.Remove(key); } _nextSnapshotRequestCleanup = now + 60f; } private static void OnFieldSnapshot(long sender, ZPackage package) { if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer() || !TryGetServerPeerId(out var peerId) || sender != peerId || package == null || package.Size() > 4194336) { return; } try { if (package.ReadInt() != 1) { return; } long num = package.ReadLong(); if (num < _lastAppliedSnapshotRevision) { return; } int num2 = package.ReadInt(); if (num2 >= 0 && num2 <= 4194304 && package.Size() - package.GetPos() == num2) { byte[] array = package.ReadByteArray(num2); if (array.Length == num2 && TryDecodeFieldSnapshot(array, out List<ActiveField> fields)) { _snapshotSource = sender; _lastAppliedSnapshotRevision = num; FieldRegistry.ApplyServerSnapshot(fields); } } } catch (Exception ex) { BaseProtectionPlugin.Instance.LogWarning("Discarded malformed field snapshot: " + ex.Message); } } private static bool TryDecodeFieldSnapshot(byte[] body, out List<ActiveField> fields) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Expected O, but got Unknown //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) fields = new List<ActiveField>(); ZPackage val = new ZPackage(body); int num = val.ReadInt(); if (num < 0 || num > 16384) { return false; } HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); for (int i = 0; i < num; i++) { string text = val.ReadString(); long num2 = val.ReadLong(); int num3 = val.ReadInt(); if (text.Length == 0 || text.Length > 128 || !hashSet.Add(text) || num2 == 0L || num3 < 2 || num3 > 8) { return false; } Point2[] array = new Point2[num3]; Vector3[] array2 = (Vector3[])(object)new Vector3[num3]; for (int j = 0; j < num3; j++) { double num4 = val.ReadDouble(); double num5 = val.ReadDouble(); if (double.IsNaN(num4) || double.IsInfinity(num4) || Math.Abs(num4) > 1000000.0 || double.IsNaN(num5) || double.IsInfinity(num5) || Math.Abs(num5) > 1000000.0) { return false; } array[j] = new Point2(num4, num5); array2[j] = new Vector3((float)num4, 0f, (float)num5); } if (!FieldGeometry.TryCreate(array, 10000.0, 100000000.0, out FieldGeometry field, out string _)) { return false; } fields.Add(new ActiveField { GroupId = text, OwnerId = num2, Geometry = field, Vertices = array2 }); } return val.GetPos() == val.Size(); } internal static void RequestLink(ZDOID first, ZDOID second) { //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) TryRegister(); if (_registered == null || !TryGetServerPeerId(out var peerId)) { RunestoneInteraction.Notify("Cannot reach the host to link runestones yet."); return; } if ((Object)(object)ZNet.instance != (Object)null && !ZNet.instance.IsServer()) { _linkRequestTime = Time.time; RunestoneInteraction.Notify("Link request sent to host..."); } BaseProtectionPlugin.Instance.LogInfo($"Sending runestone link request to peer {peerId}."); _registered.InvokeRoutedRPC(peerId, "BaseProtection.Link.v1", new object[2] { first, second }); } internal static void CheckPendingLink() { if (!(_linkRequestTime < 0f) && !(Time.time - _linkRequestTime < 5f)) { _linkRequestTime = -1f; RunestoneInteraction.Notify("No reply from host. Check both players' mod versions and BepInEx logs."); } } internal static void RequestUnlink(ZDOID stone) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) TryRegister(); if (_registered != null && TryGetServerPeerId(out var peerId)) { _registered.InvokeRoutedRPC(peerId, "BaseProtection.Unlink.v1", new object[1] { stone }); } else { RunestoneInteraction.Notify("Cannot reach the host to unlink this runestone yet."); } } internal static void RequestFinalize(ZDOID stone) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) TryRegister(); if (_registered != null && TryGetServerPeerId(out var peerId)) { _registered.InvokeRoutedRPC(peerId, "BaseProtection.Finalize.v1", new object[1] { stone }); } else { RunestoneInteraction.Notify("Cannot reach the host to finish this field yet."); } } internal static void RequestFinishWithStone(ZDOID draftStone, ZDOID newStone) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) TryRegister(); if (_registered != null && TryGetServerPeerId(out var peerId)) { _registered.InvokeRoutedRPC(peerId, "BaseProtection.FinishWithStone.v1", new object[2] { draftStone, newStone }); } else { RunestoneInteraction.Notify("Cannot reach the host to finish this field yet."); } } private static void OnReply(long sender, string text) { if (_registered == null || !TryGetServerPeerId(out var peerId) || sender != peerId) { BaseProtectionPlugin.Instance.LogWarning($"Ignoring runestone reply from unexpected peer {sender}."); return; } _linkRequestTime = -1f; RunestoneInteraction.OnServerReply(text); } private static void Reply(long recipient, string text) { BaseProtectionPlugin.Instance.LogInfo($"Runestone reply to peer {recipient}: {text}"); ZRoutedRpc? registered = _registered; if (registered != null) { registered.InvokeRoutedRPC(recipient, "BaseProtection.Reply.v1", new object[1] { text }); } } private static bool TryGetOwner(long sender, out long playerId) { //IL_0053: Unknown result type (might be due to invalid IL or missing references) playerId = 0L; if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || !StoneRoutedPeerContext.Matches(sender)) { return false; } ZNetPeer peer = ZNet.instance.GetPeer(sender); if (peer != null) { if (((ZDOID)(ref peer.m_characterID)).UserID != sender) { return false; } ZDOMan instance = ZDOMan.instance; ZDO val = ((instance != null) ? instance.GetZDO(peer.m_characterID) : null); playerId = ((val != null) ? val.GetLong(ZDOVars.s_playerID, 0L) : 0); } else if (sender == ZNet.GetUID() && (Object)(object)Player.m_localPlayer != (Object)null) { playerId = Player.m_localPlayer.GetPlayerID(); } return playerId != 0; } private static Vector3? GetRequesterPosition(long sender) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) ZNet instance = ZNet.instance; ZNetPeer val = ((instance != null) ? instance.GetPeer(sender) : null); if (val != null) { ZDOMan instance2 = ZDOMan.instance; if (instance2 == null) { return null; } ZDO zDO = instance2.GetZDO(val.m_characterID); if (zDO == null) { return null; } return zDO.GetPosition(); } if (sender == ZNet.GetUID()) { Player localPlayer = Player.m_localPlayer; if (localPlayer == null) { return null; } return ((Component)localPlayer).transform.position; } return null; } private static bool RequireOwner(long sender, out long playerId) { if (TryGetOwner(sender, out playerId)) { return true; } BaseProtectionPlugin.Instance.LogWarning($"Runestone request from peer {sender} has no player ID."); Reply(sender, "The host could not identify your player. Rejoin and try again."); return false; } private static bool IsStone(ZDO? zdo) { if (zdo != null) { return zdo.GetPrefab() == StringExtensionMethods.GetStableHashCode("piece_baseprotection_runestone"); } return false; } private static void OnLink(long sender, ZDOID sourceId, ZDOID targetId) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) BaseProtectionPlugin.Instance.LogInfo($"Received runestone link request from peer {sender}."); if (!RequireOwner(sender, out var playerId)) { return; } try { ZDO source = ZDOMan.instance.GetZDO(sourceId); ZDO val = ZDOMan.instance.GetZDO(targetId); if (!IsStone(source) || !IsStone(val) || sourceId == targetId) { Reply(sender, "Both targets must be different linked runestones."); return; } if (source.GetLong(ZDOVars.s_creator, 0L) != playerId || val.GetLong(ZDOVars.s_creator, 0L) != playerId) { Reply(sender, "You can only link stones that you placed."); return; } string sourceGroup = source.GetString("baseprotection_group", ""); string text = val.GetString("baseprotection_group", ""); if (sourceGroup.Length != 0 && text.Length != 0) { Reply(sender, "Both stones are already linked. Select an unlinked stone to add to a field."); return; } Vector3? requesterPosition = GetRequesterPosition(sender); if (!requesterPosition.HasValue || Vector3.Distance(requesterPosition.Value, val.GetPosition()) > 6f) { Reply(sender, "Stand beside the target stone to link it."); return; } if (text.Length != 0) { ZDO val2 = val; val = source; source = val2; sourceGroup = text; } List<ZDO> allStoneZdos = FieldRegistry.GetAllStoneZdos(); List<ZDO> list = (string.IsNullOrEmpty(sourceGroup) ? new List<ZDO> { source } : (from z in allStoneZdos where z.GetString("baseprotection_group", "") == sourceGroup orderby z.GetInt("baseprotection_order", -1) select z).ToList()); if (!list.Any((ZDO z) => z.m_uid == source.m_uid) || list.Count >= 8 || list.Any((ZDO z) => z.GetLong(ZDOVars.s_creator, 0L) != playerId)) { Reply(sender, "This field already has eight stones or has invalid link data."); return; } list.Add(val); string text2 = (string.IsNullOrEmpty(sourceGroup) ? Guid.NewGuid().ToString("N") : sourceGroup); WriteGroup(list, text2, active: false); FieldRegistry.Refresh(); Reply(sender, $"Draft has {list.Count} stones. Shift+E a draft stone to finish."); } catch (Exception error) { BaseProtectionPlugin.Instance.LogError(error); Reply(sender, "Linking failed; check the server log."); } } private static string? ValidateProposal(string groupId, long owner, List<ZDO> members, List<ZDO> allStones) { BaseProtectionPlugin instance = BaseProtectionPlugin.Instance; Vector3[] array = members.Select((ZDO z) => z.GetPosition()).ToArray(); if (!FieldGeometry.TryCreate(array.Select((Vector3 v) => new Point2(v.x, v.z)).ToArray(), instance.MaxEdge.Value, instance.MaxArea.Value, out FieldGeometry field, out string error)) { return error; } ActiveField[] array2 = (from f in FieldRegistry.BuildAll(allStones) where f.GroupId != groupId select f).ToArray(); ActiveField[] array3 = array2; foreach (ActiveField activeField in array3) { if (field.Overlaps(activeField.Geometry) || field.DistanceTo(activeField.Geometry) < (double)Math.Max(0f, instance.FieldGap.Value)) { return "This field is too close to another field."; } } ActiveField[] array4 = array2.Where((ActiveField f) => f.OwnerId == owner).ToArray(); if (array4.Length + 1 > instance.MaxFieldsPerPlayer.Value) { return "You have reached your field limit."; } double num2 = array4.Sum((ActiveField f) => f.Geometry.Area); float value = instance.MaxTotalAreaPerPlayer.Value; if (num2 + field.Area > (double)value) { return $"Field needs {field.Area:0} m²; your other fields use {num2:0} m²; " + $"the per-player limit is {value:0} m²."; } if (ForeignStructureNear(field, array, owner, Math.Max(0f, instance.ForeignStructureBuffer.Value))) { return "The field is too close to another player's structure."; } return null; } private static bool ForeignStructureNear(FieldGeometry geometry, Vector3[] vertices, long owner, float buffer) { //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_0182: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_020f: Unknown result type (might be due to invalid IL or missing references) //IL_0214: Unknown result type (might be due to invalid IL or missing references) //IL_0217: Unknown result type (might be due to invalid IL or missing references) //IL_021f: Unknown result type (might be due to invalid IL or missing references) if (buffer <= 0f) { return false; } if ((Object)(object)ZNetScene.instance == (Object)null || (Object)(object)ZoneSystem.instance == (Object)null) { return true; } List<ZDO> list = new List<ZDO>(); List<ZDO> list2 = new List<ZDO>(); HashSet<ZDOID> hashSet = new HashSet<ZDOID>(); HashSet<Vector2s> hashSet2 = new HashSet<Vector2s>(); float num = vertices.Min((Vector3 v) => v.x) - buffer; float num2 = vertices.Max((Vector3 v) => v.x) + buffer; float num3 = vertices.Min((Vector3 v) => v.z) - buffer; float num4 = vertices.Max((Vector3 v) => v.z) + buffer; for (float num5 = num; num5 <= num2 + 64f; num5 += 64f) { for (float num6 = num3; num6 <= num4 + 64f; num6 += 64f) { hashSet2.Add(ZoneSystem.GetZone(new Vector3(num5, 0f, num6))); if (hashSet2.Count > 4096) { return true; } } } foreach (Vector3 val in vertices) { hashSet2.Add(ZoneSystem.GetZone(val)); } foreach (Vector2s item in hashSet2) { list.Clear(); list2.Clear(); ZDOMan.instance.FindSectorObjects(item, SimulationDistance.OriginalDistance, list, list2); foreach (ZDO item2 in list) { if (!hashSet.Add(item2.m_uid)) { continue; } long num8 = item2.GetLong(ZDOVars.s_creator, 0L); if (num8 == 0L || num8 == owner) { continue; } GameObject prefab = ZNetScene.instance.GetPrefab(item2.GetPrefab()); if (!((Object)(object)prefab == (Object)null) && !((Object)(object)prefab.GetComponent<Piece>() == (Object)null)) { Vector3 position = item2.GetPosition(); if (geometry.DistanceTo(new Point2(position.x, position.z)) <= (double)buffer) { return true; } } } } return false; } private static void WriteGroup(IReadOnlyList<ZDO> members, string group, bool active) { string text = FieldRegistry.EncodeShape(members.Select((ZDO z) => z.GetPosition())); for (int num = 0; num < members.Count; num++) { ZDO obj = members[num]; obj.SetOwner(ZNet.GetUID()); obj.Set("baseprotection_group", group); obj.Set("baseprotection_order", num); obj.Set("baseprotection_active", active ? 1 : 0); obj.Set("baseprotection_shape", text); } } internal static void RepairAfterDestroyed(string group, long owner, bool wasActive) { try { List<ZDO> allStoneZdos = FieldRegistry.GetAllStoneZdos(); List<ZDO> members = (from z in allStoneZdos where z.GetString("baseprotection_group", "") == @group orderby z.GetInt("baseprotection_order", -1) select z).ToList(); if (members.Count < 2) { foreach (ZDO item in members) { item.SetOwner(ZNet.GetUID()); item.Set("baseprotection_group", ""); item.Set("baseprotection_order", -1); item.Set("baseprotection_active", 0); item.Set("baseprotection_shape", ""); } FieldRegistry.Refresh(); return; } int[] source = PolygonOrdering.Untangle((from z in members select z.GetPosition() into v select new Point2(v.x, v.z)).ToArray()); members = source.Select((int index) => members[index]).ToList(); bool flag = wasActive && members.All((ZDO z) => z.GetLong(ZDOVars.s_creator, 0L) == owner) && ValidateProposal(group, owner, members, allStoneZdos) == null; WriteGroup(members, group, flag); FieldRegistry.Refresh(); if (!flag && wasActive) { BaseProtectionPlugin.Instance.LogWarning("Runestone field " + group + " became an inactive draft after a stone was destroyed."); } } catch (Exception error) { BaseProtectionPlugin.Instance.LogError(error); } } private static void OnFinalize(long sender, ZDOID stoneId) { //IL_0021: 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: Unknown result type (might be due to invalid IL or missing references) if (!RequireOwner(sender, out var playerId)) { return; } try { ZDO zDO = ZDOMan.instance.GetZDO(stoneId); if (!IsStone(zDO) || zDO.GetLong(ZDOVars.s_creator, 0L) != playerId) { Reply(sender, "You can only finish your own runestone field."); return; } Vector3? requesterPosition = GetRequesterPosition(sender); if (!requesterPosition.HasValue || Vector3.Distance(requesterPosition.Value, zDO.GetPosition()) > 6f) { Reply(sender, "Stand beside a draft stone to finish the field."); return; } string group = zDO.GetString("baseprotection_group", ""); if (string.IsNullOrEmpty(group) || zDO.GetInt("baseprotection_active", 1) != 0) { Reply(sender, "This stone is not part of an unfinished draft."); return; } List<ZDO> allStoneZdos = FieldRegistry.GetAllStoneZdos(); List<ZDO> members = (from z in allStoneZdos where z.GetString("baseprotection_group", "") == @group orderby z.GetInt("baseprotection_order", -1) select z).ToList(); if (members.Count < 2 || members.Count > 8 || members.Any((ZDO z) => z.GetLong(ZDOVars.s_creator, 0L) != playerId || z.GetInt("baseprotection_active", 1) != 0)) { Reply(sender, "The draft has invalid stone data."); return; } int[] source = PolygonOrdering.Untangle((from z in members select z.GetPosition() into v select new Point2(v.x, v.z)).ToArray()); bool flag = !source.Select((int value, int index) => value == index).All((bool equal) => equal); if (flag) { members = source.Select((int index) => members[index]).ToList(); } string text = ValidateProposal(group, playerId, members, allStoneZdos); if (text != null) { Reply(sender, text); return; } WriteGroup(members, group, active: true); FieldRegistry.Refresh(); Reply(sender, flag ? $"Field active with {members.Count} stones; polygon order adjusted." : $"Field active with {members.Count} stones."); } catch (Exception error) { BaseProtectionPlugin.Instance.LogError(error); Reply(sender, "Finishing failed; check the server log."); } } private static void OnFinishWithStone(long sender, ZDOID draftStoneId, ZDOID newStoneId) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0029: 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_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0103: 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 (!RequireOwner(sender, out var playerId)) { return; } try { ZDO zDO = ZDOMan.instance.GetZDO(draftStoneId); ZDO zDO2 = ZDOMan.instance.GetZDO(newStoneId); if (!IsStone(zDO) || !IsStone(zDO2) || draftStoneId == newStoneId || zDO.GetLong(ZDOVars.s_creator, 0L) != playerId || zDO2.GetLong(ZDOVars.s_creator, 0L) != playerId) { Reply(sender, "Both stones must be different runestones that you placed."); return; } string group = zDO.GetString("baseprotection_group", ""); if (group.Length == 0 || zDO.GetInt("baseprotection_active", 1) != 0 || zDO2.GetString("baseprotection_group", "").Length != 0) { Reply(sender, "Select a draft stone, then Shift+E an unlinked stone."); return; } Vector3? requesterPosition = GetRequesterPosition(sender); if (!requesterPosition.HasValue || Vector3.Distance(requesterPosition.Value, zDO2.GetPosition()) > 6f) { Reply(sender, "Stand beside the new stone to add and finish the draft."); return; } List<ZDO> allStoneZdos = FieldRegistry.GetAllStoneZdos(); List<ZDO> members = (from z in allStoneZdos where z.GetString("baseprotection_group", "") == @group orderby z.GetInt("baseprotection_order", -1) select z).ToList(); if (members.Count < 2 || members.Count >= 8 || !members.Any((ZDO z) => z.m_uid == draftStoneId) || members.Any((ZDO z) => z.GetLong(ZDOVars.s_creator, 0L) != playerId || z.GetInt("baseprotection_active", 1) != 0)) { Reply(sender, "The draft is full or has invalid stone data."); return; } members.Add(zDO2); int[] source = PolygonOrdering.Untangle((from z in members select z.GetPosition() into v select new Point2(v.x, v.z)).ToArray()); bool flag = !source.Select((int value, int index) => value == index).All((bool equal) => equal); if (flag) { members = source.Select((int index) => members[index]).ToList(); } string text = ValidateProposal(group, playerId, members, allStoneZdos); WriteGroup(members, group, text == null); FieldRegistry.Refresh(); if (text != null) { Reply(sender, $"Draft has {members.Count} stones; could not finish: {text}"); } else { Reply(sender, flag ? $"Field active with {members.Count} stones; polygon order adjusted." : $"Field active with {members.Count} stones."); } } catch (Exception error) { BaseProtectionPlugin.Instance.LogError(error); Reply(sender, "Adding and finishing failed; check the server log.")