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 AdvancedPlanting v0.4.7
AdvancedPlanting.dll
Decompiled a day ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Threading; using AdvancedPlanting.Core; using AdvancedPlanting.UI; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.Rendering; using UnityEngine.UI; using Valheim.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("assembly_guiutils")] [assembly: IgnoresAccessChecksTo("assembly_utils")] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("kepqa")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright (c) 2026 kepqa")] [assembly: AssemblyFileVersion("0.4.7.0")] [assembly: AssemblyInformationalVersion("0.4.7+634f9821e72cc99f55d00900f66b35f1c375ee61")] [assembly: AssemblyProduct("AdvancedPlanting")] [assembly: AssemblyTitle("AdvancedPlanting")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.4.7.0")] [module: UnverifiableCode] namespace AdvancedPlanting { public sealed class CurveRenderer { private static readonly string[] ShaderCandidates = new string[5] { "Sprites/Default", "Particles/Standard Unlit", "Legacy Shaders/Particles/Alpha Blended", "Hidden/Internal-Colored", "Standard" }; private static readonly Color ValidRing = new Color(0.35f, 0.95f, 0.45f, 0.85f); private static readonly Color BlockedRing = new Color(1f, 0.3f, 0.25f, 0.9f); private static readonly Color PendingRing = new Color(0.85f, 0.85f, 0.8f, 0.6f); private static readonly Color SnapColor = new Color(0.4f, 0.85f, 1f, 1f); private const int RingSegments = 16; private readonly ModConfig _config; private readonly List<LineRenderer> _lines = new List<LineRenderer>(); private readonly List<LineRenderer> _markers = new List<LineRenderer>(); private readonly List<LineRenderer> _rings = new List<LineRenderer>(); private readonly Vector3[] _ringBuffer = (Vector3[])(object)new Vector3[16]; private Material _material; private GameObject _root; private bool _warnedShader; public CurveRenderer(ModConfig config) { _config = config; } public void Apply(List<List<Vector3>> rowLines, List<Vector3> controlPoints, int snapIndex, List<PlantSlot> slots, float growRadius, bool warning) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: 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_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_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_0130: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Unknown result type (might be due to invalid IL or missing references) //IL_01cd: Unknown result type (might be due to invalid IL or missing references) //IL_01d4: Unknown result type (might be due to invalid IL or missing references) //IL_0270: Unknown result type (might be due to invalid IL or missing references) //IL_0275: Unknown result type (might be due to invalid IL or missing references) //IL_028e: Unknown result type (might be due to invalid IL or missing references) //IL_0293: Unknown result type (might be due to invalid IL or missing references) //IL_0297: Unknown result type (might be due to invalid IL or missing references) //IL_0285: Unknown result type (might be due to invalid IL or missing references) //IL_029c: Unknown result type (might be due to invalid IL or missing references) //IL_029e: Unknown result type (might be due to invalid IL or missing references) //IL_02a0: Unknown result type (might be due to invalid IL or missing references) //IL_02a5: Unknown result type (might be due to invalid IL or missing references) //IL_02aa: Unknown result type (might be due to invalid IL or missing references) //IL_02cf: Unknown result type (might be due to invalid IL or missing references) //IL_02d1: Unknown result type (might be due to invalid IL or missing references) //IL_02d3: Unknown result type (might be due to invalid IL or missing references) //IL_02d8: Unknown result type (might be due to invalid IL or missing references) //IL_02dc: Unknown result type (might be due to invalid IL or missing references) //IL_02e1: Unknown result type (might be due to invalid IL or missing references) //IL_02e8: Unknown result type (might be due to invalid IL or missing references) //IL_02ed: Unknown result type (might be due to invalid IL or missing references) //IL_02f2: Unknown result type (might be due to invalid IL or missing references) //IL_02ba: Unknown result type (might be due to invalid IL or missing references) //IL_02bc: Unknown result type (might be due to invalid IL or missing references) //IL_02c1: Unknown result type (might be due to invalid IL or missing references) //IL_02c6: Unknown result type (might be due to invalid IL or missing references) //IL_0318: Unknown result type (might be due to invalid IL or missing references) //IL_031a: Unknown result type (might be due to invalid IL or missing references) //IL_0323: Unknown result type (might be due to invalid IL or missing references) //IL_0328: Unknown result type (might be due to invalid IL or missing references) //IL_0331: Unknown result type (might be due to invalid IL or missing references) //IL_0336: Unknown result type (might be due to invalid IL or missing references) //IL_033d: Unknown result type (might be due to invalid IL or missing references) //IL_0342: Unknown result type (might be due to invalid IL or missing references) //IL_0347: Unknown result type (might be due to invalid IL or missing references) //IL_03ac: Unknown result type (might be due to invalid IL or missing references) //IL_03b1: Unknown result type (might be due to invalid IL or missing references) //IL_03b2: Unknown result type (might be due to invalid IL or missing references) //IL_03b9: Unknown result type (might be due to invalid IL or missing references) //IL_03a5: Unknown result type (might be due to invalid IL or missing references) //IL_039e: Unknown result type (might be due to invalid IL or missing references) EnsureRoot(); float value = _config.CurveWidth.Value; int num = (_config.ShowCurve.Value ? rowLines.Count : 0); Color val = (warning ? BlockedRing : _config.CurveColor.Value); for (int i = 0; i < num; i++) { LineRenderer obj = Get(_lines, i, "row", loop: false); List<Vector3> list = rowLines[i]; obj.positionCount = list.Count; obj.SetPositions(list.ToArray()); float startWidth = (obj.endWidth = value); obj.startWidth = startWidth; Color val2 = (Color)((i == 0) ? val : new Color(val.r, val.g, val.b, val.a * 0.55f)); Color startColor = (obj.endColor = val2); obj.startColor = startColor; ((Renderer)obj).enabled = true; } Disable(_lines, num); Color value2 = _config.ControlPointColor.Value; for (int j = 0; j < controlPoints.Count; j++) { LineRenderer obj2 = Get(_markers, j, "cp", loop: false); Vector3 val4 = controlPoints[j]; bool flag = j == snapIndex; float num3 = (flag ? 2.2f : ((j == 0) ? 1.6f : 1.2f)); obj2.positionCount = 2; obj2.SetPosition(0, val4 + Vector3.up * 0.05f); obj2.SetPosition(1, val4 + Vector3.up * num3); obj2.startWidth = value * (flag ? 4f : 2.5f); obj2.endWidth = value * 0.5f; Color startColor = (obj2.endColor = (flag ? SnapColor : value2)); obj2.startColor = startColor; ((Renderer)obj2).enabled = true; } Disable(_markers, controlPoints.Count); int num4 = 0; if (_config.ShowGrowRadius.Value && slots != null && growRadius > 0.05f) { foreach (PlantSlot slot in slots) { if (!slot.IsPlanted) { LineRenderer val6 = Get(_rings, num4++, "ring", loop: true); Vector3 normal = slot.Normal; Vector3 val7; if (!(((Vector3)(ref normal)).sqrMagnitude > 0.01f)) { val7 = Vector3.up; } else { normal = slot.Normal; val7 = ((Vector3)(ref normal)).normalized; } Vector3 val8 = val7; Vector3 val9 = Vector3.Cross(val8, Vector3.forward); if (((Vector3)(ref val9)).sqrMagnitude < 0.01f) { val9 = Vector3.Cross(val8, Vector3.right); } ((Vector3)(ref val9)).Normalize(); Vector3 val10 = Vector3.Cross(val8, val9); Vector3 val11 = slot.Position + val8 * 0.1f; for (int k = 0; k < 16; k++) { float num5 = (float)k * (float)Math.PI * 2f / 16f; _ringBuffer[k] = val11 + (val9 * Mathf.Cos(num5) + val10 * Mathf.Sin(num5)) * growRadius; } val6.positionCount = 16; val6.SetPositions(_ringBuffer); float startWidth = (val6.endWidth = value * 0.6f); val6.startWidth = startWidth; Color startColor = (val6.endColor = (slot.IsValid ? ValidRing : (slot.IsPending ? PendingRing : BlockedRing))); val6.startColor = startColor; ((Renderer)val6).enabled = true; } } } Disable(_rings, num4); } public void HideAll() { Disable(_lines, 0); Disable(_markers, 0); Disable(_rings, 0); } public void Destroy() { if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); } _root = null; _lines.Clear(); _markers.Clear(); _rings.Clear(); } private static void Disable(List<LineRenderer> pool, int from) { for (int i = from; i < pool.Count; i++) { if ((Object)(object)pool[i] != (Object)null && ((Renderer)pool[i]).enabled) { ((Renderer)pool[i]).enabled = false; } } } private void EnsureRoot() { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown if (!((Object)(object)_root != (Object)null)) { _root = new GameObject("AdvancedPlanting_Overlay"); Object.DontDestroyOnLoad((Object)(object)_root); _lines.Clear(); _markers.Clear(); _rings.Clear(); } } private Material GetMaterial() { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown if ((Object)(object)_material != (Object)null) { return _material; } string[] shaderCandidates = ShaderCandidates; foreach (string text in shaderCandidates) { Shader val = Shader.Find(text); if (!((Object)(object)val == (Object)null)) { _material = new Material(val); Plugin.Log.LogDebug((object)("Overlay shader: " + text)); return _material; } } if (!_warnedShader) { _warnedShader = true; Plugin.Log.LogWarning((object)"No overlay shader found; lines may render incorrectly."); } return null; } private LineRenderer Get(List<LineRenderer> pool, int index, string prefix, bool loop) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) while (pool.Count <= index) { GameObject val = new GameObject(prefix + "_" + pool.Count); val.transform.SetParent(_root.transform, false); LineRenderer val2 = val.AddComponent<LineRenderer>(); val2.useWorldSpace = true; val2.loop = loop; ((Renderer)val2).shadowCastingMode = (ShadowCastingMode)0; ((Renderer)val2).receiveShadows = false; val2.alignment = (LineAlignment)0; val2.numCapVertices = ((!loop) ? 2 : 0); val2.numCornerVertices = 2; Material material = GetMaterial(); if ((Object)(object)material != (Object)null) { ((Renderer)val2).material = material; } pool.Add(val2); } return pool[index]; } } public sealed class FigureState { public readonly List<Vector3> ControlPoints = new List<Vector3>(); public readonly List<Vector3> Planted = new List<Vector3>(); public PathMode Mode = (PathMode)3; public RowSide Side; public int Rows = 1; public float Jitter; public float Thinning; public uint Seed = 1u; public bool Fill; public bool Enabled = true; public bool AutoContinue; private readonly Dictionary<string, float> _spacingByPrefab = new Dictionary<string, float>(); private readonly List<Vector3> _lastShape = new List<Vector3>(); private PathMode _lastMode; public bool IsClosedPath { get { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Invalid comparison between Unknown and I4 if ((int)Mode != 2) { return (int)Mode == 4; } return true; } } public bool Finished { get; private set; } public bool Dirty { get; private set; } = true; public bool IsEmpty => ControlPoints.Count == 0; public bool HasEnoughPoints => ControlPoints.Count >= PathModeExtensions.MinControlPoints(Mode); public bool IsFixedSize => PathModeExtensions.MaxControlPoints(Mode) > 0; public bool CanFinish { get { if (!Finished && !IsFixedSize) { return HasEnoughPoints; } return false; } } public bool CanRepeat { get { if (!Finished) { if (IsEmpty) { return _lastShape.Count > 0; } return false; } return true; } } public void SetFill(bool fill) { if (fill != Fill) { Fill = fill; Dirty = true; } } public void MarkDirty() { Dirty = true; } public void ClearDirty() { Dirty = false; } public void AddPoint(Vector3 p) { //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) if (!Finished) { ControlPoints.Add(p); int num = PathModeExtensions.MaxControlPoints(Mode); if (num > 0 && ControlPoints.Count >= num) { Finished = true; } Dirty = true; } } public void Finish() { if (CanFinish) { Finished = true; Dirty = true; } } public void RemoveLastPoint() { if (ControlPoints.Count != 0) { ControlPoints.RemoveAt(ControlPoints.Count - 1); Finished = false; Dirty = true; } } public void Clear() { if (Finished) { RememberShape(); } ControlPoints.Clear(); Planted.Clear(); Finished = false; Dirty = true; } public void SetMode(PathMode mode) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000b: 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_0011: Unknown result type (might be due to invalid IL or missing references) if (mode != Mode) { Mode = mode; int num = PathModeExtensions.MaxControlPoints(mode); if (num > 0 && ControlPoints.Count > num) { ControlPoints.RemoveRange(num, ControlPoints.Count - num); } Finished = num > 0 && ControlPoints.Count >= num; Dirty = true; } } public void SetSide(RowSide side) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000b: 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) if (side != Side) { Side = side; Dirty = true; } } public void CycleSide() { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) SetSide((RowSide)((Side + 1) % 3)); } public void RerollSeed() { Seed = Seed * 1664525 + 1013904223; if (Seed == 0) { Seed = 1u; } Dirty = true; } public void SetRows(int rows) { rows = Mathf.Clamp(rows, 1, 50); if (rows != Rows) { Rows = rows; Dirty = true; } } public void SetJitter(float value) { value = Mathf.Clamp01(Mathf.Round(value * 100f) / 100f); if (!Mathf.Approximately(value, Jitter)) { Jitter = value; Dirty = true; } } public void SetThinning(float value) { value = Mathf.Clamp(Mathf.Round(value * 100f) / 100f, 0f, 0.9f); if (!Mathf.Approximately(value, Thinning)) { Thinning = value; Dirty = true; } } public float GetSpacing(string prefabName, float fallback) { if (string.IsNullOrEmpty(prefabName)) { return fallback; } if (_spacingByPrefab.TryGetValue(prefabName, out var value)) { return value; } _spacingByPrefab[prefabName] = fallback; return fallback; } public void SetSpacing(string prefabName, float value, float minimum) { if (!string.IsNullOrEmpty(prefabName)) { value = Mathf.Max(minimum, Mathf.Round(value * 20f) / 20f); if (!_spacingByPrefab.TryGetValue(prefabName, out var value2) || !Mathf.Approximately(value2, value)) { _spacingByPrefab[prefabName] = value; Dirty = true; } } } private void RememberShape() { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) _lastShape.Clear(); _lastShape.AddRange(ControlPoints); _lastMode = Mode; } public bool RepeatAt(Vector3 anchor) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) if (!CanRepeat) { return false; } if (Finished) { RememberShape(); } Vector3 val = AnchorOf(_lastShape, _lastMode); Vector3 val2 = anchor - val; ControlPoints.Clear(); foreach (Vector3 item in _lastShape) { ControlPoints.Add(item + val2); } Planted.Clear(); Mode = _lastMode; Finished = true; Dirty = true; return true; } private static Vector3 AnchorOf(List<Vector3> points, PathMode mode) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: 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_005c: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) if ((int)mode == 2) { return points[0]; } Vector3 val = points[0]; Vector3 val2 = points[0]; foreach (Vector3 point in points) { val = Vector3.Min(val, point); val2 = Vector3.Max(val2, point); } return new Vector3((val.x + val2.x) * 0.5f, points[0].y, (val.z + val2.z) * 0.5f); } public void MarkPlanted(Vector3 p) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) Planted.Add(p); } public bool IsPlanted(Vector3 p, float tolerance) { //IL_000f: 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_0027: 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) float num = tolerance * tolerance; for (int i = 0; i < Planted.Count; i++) { float num2 = Planted[i].x - p.x; float num3 = Planted[i].z - p.z; if (num2 * num2 + num3 * num3 <= num) { return true; } } return false; } } internal static class GameText { public static string Localize(string text) { if (string.IsNullOrEmpty(text) || text.IndexOf('$') < 0) { return text; } Localization instance = Localization.instance; if (instance == null) { return text; } return instance.Localize(text); } } public sealed class GhostPool { private static readonly int ColorId = Shader.PropertyToID("_Color"); private static readonly int EmissionId = Shader.PropertyToID("_EmissionColor"); private readonly List<GameObject> _ghosts = new List<GameObject>(); private readonly List<bool> _invalidState = new List<bool>(); private string _sourceName; private GameObject _activeRoot; private GameObject _staging; private const int MaxClonesPerCall = 24; public bool Apply(GameObject source, List<PlantSlot> slots) { //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)source == (Object)null) { HideAll(); return true; } EnsureRoots(); if (_sourceName != ((Object)source).name) { DestroyAll(); EnsureRoots(); _sourceName = ((Object)source).name; } int num = 24; while (_ghosts.Count < slots.Count && num-- > 0) { GameObject val = Clone(source); if ((Object)(object)val == (Object)null) { break; } _ghosts.Add(val); _invalidState.Add(item: false); } for (int i = 0; i < _ghosts.Count; i++) { GameObject val2 = _ghosts[i]; if ((Object)(object)val2 == (Object)null) { continue; } if (i >= slots.Count) { if (val2.activeSelf) { val2.SetActive(false); } continue; } PlantSlot plantSlot = slots[i]; val2.transform.position = plantSlot.Position; val2.transform.rotation = plantSlot.Rotation; if (!val2.activeSelf) { val2.SetActive(true); } SetInvalid(i, plantSlot.IsBlocked); } return _ghosts.Count >= slots.Count; } public void HideAll() { foreach (GameObject ghost in _ghosts) { if ((Object)(object)ghost != (Object)null && ghost.activeSelf) { ghost.SetActive(false); } } } public void DestroyAll() { foreach (GameObject ghost in _ghosts) { if ((Object)(object)ghost != (Object)null) { Object.Destroy((Object)(object)ghost); } } _ghosts.Clear(); _invalidState.Clear(); _sourceName = null; } public void DestroyRoots() { DestroyAll(); if ((Object)(object)_activeRoot != (Object)null) { Object.Destroy((Object)(object)_activeRoot); } if ((Object)(object)_staging != (Object)null) { Object.Destroy((Object)(object)_staging); } _activeRoot = null; _staging = null; } private void EnsureRoots() { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Expected O, but got Unknown if ((Object)(object)_activeRoot == (Object)null) { _activeRoot = new GameObject("AdvancedPlanting_Ghosts"); Object.DontDestroyOnLoad((Object)(object)_activeRoot); } if ((Object)(object)_staging == (Object)null) { _staging = new GameObject("AdvancedPlanting_GhostStaging"); _staging.SetActive(false); Object.DontDestroyOnLoad((Object)(object)_staging); } } private void SetInvalid(int index, bool invalid) { //IL_0044: 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_0060: Unknown result type (might be due to invalid IL or missing references) if (_invalidState[index] == invalid) { return; } _invalidState[index] = invalid; MaterialMan instance = MaterialMan.instance; if (!((Object)(object)instance == (Object)null)) { GameObject val = _ghosts[index]; if (invalid) { instance.SetValue<Color>(val, ColorId, Color.red, false); instance.SetValue<Color>(val, EmissionId, Color.red * 0.7f, false); } else { instance.ResetValue(val, ColorId); instance.ResetValue(val, EmissionId); } } } private GameObject Clone(GameObject source) { GameObject val = Object.Instantiate<GameObject>(source, _staging.transform, false); if ((Object)(object)val == (Object)null) { return null; } StripScripts(val); Collider[] componentsInChildren = val.GetComponentsInChildren<Collider>(true); for (int i = 0; i < componentsInChildren.Length; i++) { componentsInChildren[i].enabled = false; } ((Object)val).name = "AdvancedPlantingGhost_" + ((Object)source).name; val.SetActive(false); val.transform.SetParent(_activeRoot.transform, false); return val; } private static void StripScripts(GameObject go) { for (int i = 0; i < 4; i++) { MonoBehaviour[] componentsInChildren = go.GetComponentsInChildren<MonoBehaviour>(true); if (componentsInChildren.Length == 0) { break; } for (int num = componentsInChildren.Length - 1; num >= 0; num--) { if ((Object)(object)componentsInChildren[num] != (Object)null) { Object.DestroyImmediate((Object)(object)componentsInChildren[num]); } } } } } internal static class Harvest { [HarmonyPatch(typeof(Pickable), "Interact")] private static class Pickable_Interact_Patch { [HarmonyPrefix] private static void Prefix(Pickable __instance, out bool __state) { __state = !_running && __instance.CanBePicked(); } [HarmonyPostfix] private static void Postfix(Pickable __instance, Humanoid character, bool repeat, bool __state) { if (!__state || repeat || _running) { return; } Player val = (Player)(object)((character is Player) ? character : null); if (val == null || (Object)(object)val != (Object)(object)Player.m_localPlayer || !Armed) { return; } _running = true; try { PickAround(__instance, val); } finally { _running = false; } } } [HarmonyPatch(typeof(Pickable), "GetHoverText")] private static class Pickable_GetHoverText_Patch { [HarmonyPostfix] private static void Postfix(ref string __result) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_003f: 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) ModConfig config = Config; if (!string.IsNullOrEmpty(__result) && config != null && config.HarvestOn && config.HarvestHint.Value) { string text = GameText.Localize("$KEY_Use"); KeyCode value = config.HarvestModifier.Value; string text2 = (((int)value == 0) ? text : (KeyName(value) + " + " + text)); __result = __result + "\n[<color=yellow><b>" + text2 + "</b></color>] " + Loc.T("harvest.hover", Mathf.RoundToInt(config.HarvestRadiusLimit)); } } } private static readonly Collider[] Hits = (Collider[])(object)new Collider[4096]; private static readonly HashSet<Pickable> Seen = new HashSet<Pickable>(); private static bool _running; private static ModConfig Config => Plugin.Instance?.Settings; private static bool Armed { get { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) ModConfig config = Config; if (config == null || !config.HarvestOn) { return false; } KeyCode value = config.HarvestModifier.Value; if ((int)value != 0) { return ZInput.GetKey(value, false); } return true; } } private static void PickAround(Pickable target, Player player) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //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_00c4: Unknown result type (might be due to invalid IL or missing references) ModConfig config = Config; float harvestRadiusLimit = config.HarvestRadiusLimit; Vector3 position = ((Component)target).transform.position; int num = Physics.OverlapSphereNonAlloc(position, harvestRadiusLimit, Hits, player.m_interactMask, (QueryTriggerInteraction)2); Seen.Clear(); Seen.Add(target); int num2 = 0; for (int i = 0; i < num; i++) { Pickable val = (((Object)(object)Hits[i] != (Object)null) ? ((Component)Hits[i]).GetComponentInParent<Pickable>() : null); if (!((Object)(object)val == (Object)null) && Seen.Add(val) && val.CanBePicked() && (!config.HarvestSameType.Value || !((Object)(object)val.m_itemPrefab != (Object)(object)target.m_itemPrefab))) { Vector3 val2 = ((Component)val).transform.position - position; if (!(((Vector3)(ref val2)).sqrMagnitude > harvestRadiusLimit * harvestRadiusLimit)) { val.Interact((Humanoid)(object)player, false, false); num2++; } } } Seen.Clear(); if (num2 > 0) { Plugin.Log.LogDebug((object)$"Area harvest: {num2 + 1} picked within {harvestRadiusLimit} m."); } } private unsafe static string KeyName(KeyCode key) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected I4, but got Unknown switch (key - 303) { case 0: case 1: return "Shift"; case 2: case 3: return "Ctrl"; case 4: case 5: return "Alt"; default: return ((object)(*(KeyCode*)(&key))/*cast due to .constrained prefix*/).ToString(); } } } public struct PlantSlot { public Vector3 Position; public Vector3 Normal; public Quaternion Rotation; public PointStatus Status; public int Row; public bool IsValid => Status == PointStatus.Valid; public bool IsPlanted => Status == PointStatus.Planted; public bool IsPending => Status == PointStatus.Pending; public bool IsBlocked { get { if (!IsValid && !IsPlanted) { return !IsPending; } return false; } } } public sealed class LayoutService { private const float PlantedTolerance = 0.2f; private const float RowLineStep = 1f; private readonly ModConfig _config; private readonly int[] _statusCounts = new int[32]; private int _validateCursor; private bool _passComplete = true; public readonly List<PlantSlot> Slots = new List<PlantSlot>(); public readonly List<List<Vector3>> RowLines = new List<List<Vector3>>(); public readonly List<Vector3> PreviewPoints = new List<Vector3>(); public bool Truncated { get; private set; } public bool TooLarge { get; private set; } public float Extent { get; private set; } public int ValidCount { get; private set; } public int PlantedCount { get; private set; } public int PendingCount { get; private set; } public int BlockedCount => Slots.Count - ValidCount - PlantedCount - PendingCount; public PointStatus TopBlockReason { get; private set; } public int Version { get; private set; } public bool FullyValidated => PendingCount == 0; public bool PassComplete => _passComplete; public string BuiltForPrefab { get; private set; } public LayoutService(ModConfig config) { _config = config; } public float DefaultSpacing(Plant plant) { float num = (((Object)(object)plant != (Object)null) ? plant.m_growRadius : 1f); if (num <= 0.05f) { num = 0.5f; } return Mathf.Round(num * _config.DefaultSpacingFactor.Value * 100f) / 100f; } public float MinDistance(Plant plant) { float num = (((Object)(object)plant != (Object)null) ? plant.m_growRadius : 1f); if (num <= 0.05f) { num = 0.5f; } return num + _config.MinDistanceMargin.Value; } public void Clear() { Slots.Clear(); RowLines.Clear(); PreviewPoints.Clear(); Truncated = false; TooLarge = false; Extent = 0f; int num = (PendingCount = 0); int validCount = (PlantedCount = num); ValidCount = validCount; TopBlockReason = PointStatus.Valid; BuiltForPrefab = null; _validateCursor = 0; _passComplete = true; Version++; } public void Rebuild(FigureState state, Vector3? cursor, Plant plant, string prefabName) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0141: 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_0152: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_019e: Unknown result type (might be due to invalid IL or missing references) //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Expected O, but got Unknown //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: 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_01dd: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Unknown result type (might be due to invalid IL or missing references) //IL_01f8: Unknown result type (might be due to invalid IL or missing references) //IL_02ee: Unknown result type (might be due to invalid IL or missing references) //IL_030e: Unknown result type (might be due to invalid IL or missing references) //IL_0313: Unknown result type (might be due to invalid IL or missing references) //IL_031f: Unknown result type (might be due to invalid IL or missing references) //IL_032d: Unknown result type (might be due to invalid IL or missing references) //IL_0332: Unknown result type (might be due to invalid IL or missing references) //IL_0344: Unknown result type (might be due to invalid IL or missing references) //IL_0346: Unknown result type (might be due to invalid IL or missing references) //IL_0350: Unknown result type (might be due to invalid IL or missing references) //IL_0352: Unknown result type (might be due to invalid IL or missing references) //IL_0269: Unknown result type (might be due to invalid IL or missing references) //IL_026e: Unknown result type (might be due to invalid IL or missing references) //IL_0272: Unknown result type (might be due to invalid IL or missing references) //IL_0274: Unknown result type (might be due to invalid IL or missing references) //IL_027e: Unknown result type (might be due to invalid IL or missing references) //IL_0283: Unknown result type (might be due to invalid IL or missing references) //IL_039a: Unknown result type (might be due to invalid IL or missing references) //IL_039c: Unknown result type (might be due to invalid IL or missing references) //IL_03a8: Unknown result type (might be due to invalid IL or missing references) //IL_03aa: Unknown result type (might be due to invalid IL or missing references) //IL_03b4: Unknown result type (might be due to invalid IL or missing references) //IL_03b9: Unknown result type (might be due to invalid IL or missing references) //IL_0369: Unknown result type (might be due to invalid IL or missing references) //IL_036b: Unknown result type (might be due to invalid IL or missing references) //IL_0377: Unknown result type (might be due to invalid IL or missing references) //IL_0379: Unknown result type (might be due to invalid IL or missing references) //IL_0383: Unknown result type (might be due to invalid IL or missing references) //IL_0388: Unknown result type (might be due to invalid IL or missing references) //IL_038f: Unknown result type (might be due to invalid IL or missing references) //IL_0391: Unknown result type (might be due to invalid IL or missing references) //IL_03c9: Unknown result type (might be due to invalid IL or missing references) //IL_03e8: Unknown result type (might be due to invalid IL or missing references) //IL_03f3: Unknown result type (might be due to invalid IL or missing references) //IL_03f8: Unknown result type (might be due to invalid IL or missing references) //IL_02bd: Unknown result type (might be due to invalid IL or missing references) Clear(); BuiltForPrefab = prefabName; PreviewPoints.AddRange(state.ControlPoints); int num = PathModeExtensions.MaxControlPoints(state.Mode); if (!state.Finished && cursor.HasValue && (num == 0 || PreviewPoints.Count < num)) { PreviewPoints.Add(cursor.Value); } if (PreviewPoints.Count < PathModeExtensions.MinControlPoints(state.Mode)) { if (PreviewPoints.Count < 2) { return; } List<Vector3> list = new List<Vector3>(); foreach (Vector3 previewPoint in PreviewPoints) { list.Add(previewPoint + Vector3.up * 0.15f); } RowLines.Add(list); return; } float spacing = state.GetSpacing(prefabName, DefaultSpacing(plant)); LayoutRequest val = new LayoutRequest { Mode = state.Mode, Spacing = spacing, RowSpacing = spacing, Rows = state.Rows, Fill = (state.Fill && state.IsClosedPath), Side = state.Side, Jitter = state.Jitter, Thinning = state.Thinning, Seed = state.Seed, MinDistance = MinDistance(plant), WarpScale = _config.WarpScale.Value, MaxPoints = _config.PlantLimit, Resolution = Mathf.Clamp(spacing / 4f, 0.1f, 0.5f), MaxExtent = _config.FigureSizeLimit }; foreach (Vector3 previewPoint2 in PreviewPoints) { val.ControlPoints.Add(new Vec2(previewPoint2.x, previewPoint2.z)); } LayoutResult val2 = PlantingLayout.Generate(val); Truncated = val2.Truncated; TooLarge = val2.TooLarge; Extent = val2.Extent; if (TooLarge) { List<Vector3> list2 = new List<Vector3>(); foreach (Vector3 previewPoint3 in PreviewPoints) { list2.Add(previewPoint3 + Vector3.up * 0.15f); } if (state.IsClosedPath && list2.Count > 2) { list2.Add(list2[0]); } RowLines.Add(list2); Version++; return; } float y = PreviewPoints[0].y; foreach (LayoutPoint point in val2.Points) { PlantSlot item = new PlantSlot { Row = point.Row, Normal = Vector3.up, Status = PointStatus.Pending }; if (PlacementValidator.TryGetGround(point.Position.X, point.Position.Z, out var height, out var normal)) { item.Position = new Vector3(point.Position.X, height, point.Position.Z); item.Normal = normal; } else { item.Position = new Vector3(point.Position.X, y, point.Position.Z); item.Status = PointStatus.NoGround; } if (state.IsPlanted(item.Position, 0.2f)) { item.Status = PointStatus.Planted; } item.Rotation = RotationFor(item.Position, state.Seed); Slots.Add(item); } BuildRowLines(val2, y); _validateCursor = 0; _passComplete = false; CountStatuses(); } public bool ValidateStep(FigureState state, Piece piece, Plant plant, Player player, int budget) { //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) if (_passComplete || Slots.Count == 0) { _passComplete = true; return false; } bool flag = false; int num = Mathf.Min(Slots.Count, _validateCursor + budget); while (_validateCursor < num) { PlantSlot value = Slots[_validateCursor]; if (value.Status != PointStatus.NoGround && value.Status != PointStatus.Planted) { PointStatus pointStatus = (state.IsPlanted(value.Position, 0.2f) ? PointStatus.Planted : PlacementValidator.Check(value.Position, piece, plant, player)); if (pointStatus != value.Status) { value.Status = pointStatus; Slots[_validateCursor] = value; flag = true; } } _validateCursor++; } if (_validateCursor >= Slots.Count) { _passComplete = true; } if (flag || _passComplete) { CountStatuses(); } return _passComplete; } public void BeginRevalidation() { if (Slots.Count != 0) { _validateCursor = 0; _passComplete = false; } } public void ValidateAll(FigureState state, Piece piece, Plant plant, Player player) { if (_passComplete && PendingCount == 0) { BeginRevalidation(); } ValidateStep(state, piece, plant, player, int.MaxValue); } public void RefreshPlanted(FigureState state) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) bool flag = false; for (int i = 0; i < Slots.Count; i++) { PlantSlot value = Slots[i]; if (!value.IsPlanted && state.IsPlanted(value.Position, 0.2f)) { value.Status = PointStatus.Planted; Slots[i] = value; flag = true; } } if (flag) { CountStatuses(); } } private void CountStatuses() { Array.Clear(_statusCounts, 0, _statusCounts.Length); int num = (PendingCount = 0); int validCount = (PlantedCount = num); ValidCount = validCount; foreach (PlantSlot slot in Slots) { if (slot.IsValid) { ValidCount++; } else if (slot.IsPlanted) { PlantedCount++; } else if (slot.IsPending) { PendingCount++; } else { _statusCounts[(int)slot.Status]++; } } int num4 = 0; TopBlockReason = PointStatus.Valid; for (int i = 1; i < _statusCounts.Length; i++) { if (_statusCounts[i] > num4) { num4 = _statusCounts[i]; TopBlockReason = (PointStatus)i; } } Version++; } private Quaternion RotationFor(Vector3 pos, uint seed) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0065: 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 (!_config.RandomRotation.Value) { return Quaternion.identity; } float num = Noise.Float01((Mathf.RoundToInt(pos.x * 20f) * 73856093) ^ (Mathf.RoundToInt(pos.z * 20f) * 19349663), seed + 625341585) * 360f; return Quaternion.Euler(0f, num, 0f); } private void BuildRowLines(LayoutResult layout, float fallbackY) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) foreach (Polyline rowCurf in layout.RowCurves) { if (!rowCurf.IsDegenerate) { int num = Mathf.Max(2, Mathf.CeilToInt(rowCurf.Length / 1f)); List<Vector3> list = new List<Vector3>(num + 1); for (int i = 0; i <= num; i++) { Vec2 val = rowCurf.PointAt(rowCurf.Length * (float)i / (float)num); float height; float num2 = (PlacementValidator.TryGetGroundHeight(val.X, val.Z, out height) ? height : fallbackY); list.Add(new Vector3(val.X, num2 + 0.15f, val.Z)); } RowLines.Add(list); } } } } public static class Loc { private const string Fallback = "English"; private static readonly Dictionary<string, Dictionary<string, string>> Languages = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase); private static string _language = "English"; private static Dictionary<string, string> _current; private static Dictionary<string, string> _fallback; private static float _nextLanguageCheck; private static bool _loaded; public static string Language { get { Refresh(); return _language; } } public static string T(string key, params object[] args) { Refresh(); string value = null; if (_current != null) { _current.TryGetValue(key, out value); } if (value == null && _fallback != null) { _fallback.TryGetValue(key, out value); } if (value == null) { value = key; } if (args == null || args.Length == 0) { return value; } try { return string.Format(value, args); } catch (FormatException) { return value; } } public static void Load() { Languages.Clear(); Assembly assembly = typeof(Loc).Assembly; string[] manifestResourceNames = assembly.GetManifestResourceNames(); foreach (string text in manifestResourceNames) { if (!text.StartsWith("AdvancedPlanting.Translations.") || !text.EndsWith(".txt")) { continue; } string language = text.Substring("AdvancedPlanting.Translations.".Length, text.Length - "AdvancedPlanting.Translations.".Length - 4); using Stream stream = assembly.GetManifestResourceStream(text); using StreamReader streamReader = new StreamReader(stream, Encoding.UTF8); Merge(language, streamReader.ReadToEnd(), "embedded"); } try { string path = Path.Combine(Path.GetDirectoryName(assembly.Location) ?? ".", "Translations"); if (Directory.Exists(path)) { manifestResourceNames = Directory.GetFiles(path, "*.txt"); foreach (string text2 in manifestResourceNames) { Merge(Path.GetFileNameWithoutExtension(text2), File.ReadAllText(text2, Encoding.UTF8), text2); } } } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("Could not read translation files: " + ex.Message)); } } _loaded = true; _fallback = (Languages.TryGetValue("English", out var value) ? value : null); _nextLanguageCheck = 0f; ApplyLanguage(DetectLanguage()); } private static void Merge(string language, string content, string source) { if (!Languages.TryGetValue(language, out var value)) { value = new Dictionary<string, string>(); Languages[language] = value; } int num = 0; string[] array = content.Split(new char[1] { '\n' }); foreach (string obj in array) { num++; string text = obj.TrimEnd(new char[1] { '\r' }); string text2 = text.TrimStart(Array.Empty<char>()); if (text2.Length == 0 || text2.StartsWith("#")) { continue; } int num2 = text.IndexOf('='); if (num2 <= 0) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)$"{source}:{num}: expected 'key = value'"); } } else { string key = text.Substring(0, num2).Trim(); string value2 = text.Substring(num2 + 1).Trim().Replace("\\n", "\n"); value[key] = value2; } } } private static void Refresh() { if (!_loaded) { Load(); } if (!(Time.unscaledTime < _nextLanguageCheck)) { _nextLanguageCheck = Time.unscaledTime + 2f; string text = DetectLanguage(); if (!string.Equals(text, _language, StringComparison.OrdinalIgnoreCase)) { ApplyLanguage(text); } } } private static void ApplyLanguage(string lang) { _language = (string.IsNullOrEmpty(lang) ? "English" : lang); _current = (Languages.TryGetValue(_language, out var value) ? value : _fallback); } private static string DetectLanguage() { try { Localization instance = Localization.instance; string text = ((instance != null) ? instance.GetSelectedLanguage() : null); if (!string.IsNullOrEmpty(text)) { return text; } } catch (Exception) { } return PlayerPrefs.GetString("language", "English"); } } public sealed class ModConfig { public ConfigEntry<KeyCode> WheelKey; public ConfigEntry<KeyCode> FinishKey; public ConfigEntry<KeyCode> FillKey; public ConfigEntry<KeyCode> ClearKey; public ConfigEntry<KeyCode> RepeatKey; public ConfigEntry<bool> RowsOnMouseWheel; public ConfigEntry<bool> EnabledByDefault; public ConfigEntry<float> DefaultSpacingFactor; public ConfigEntry<float> MinDistanceMargin; public ConfigEntry<int> MaxPlants; public ConfigEntry<float> StaminaCostFactor; public ConfigEntry<float> MaxFigureSize; public ConfigEntry<bool> RandomRotation; public ConfigEntry<float> WarpScale; public ConfigEntry<float> RevalidateInterval; public ConfigEntry<float> SnapRadius; public ConfigEntry<float> AutoContinueInterval; public ConfigEntry<bool> HarvestEnabled; public ConfigEntry<KeyCode> HarvestModifier; public ConfigEntry<float> HarvestRadius; public ConfigEntry<bool> HarvestSameType; public ConfigEntry<bool> HarvestHint; public ConfigEntry<bool> EnforceOnClients; public readonly List<ConfigEntryBase> Synced = new List<ConfigEntryBase>(); public ConfigEntry<bool> ShowHud; public ConfigEntry<int> HudX; public ConfigEntry<int> HudY; public ConfigEntry<bool> ShowCurve; public ConfigEntry<bool> ShowGrowRadius; public ConfigEntry<float> CurveWidth; public ConfigEntry<Color> CurveColor; public ConfigEntry<Color> ControlPointColor; public ConfigFile File { get; } public float StaminaFactor => ServerConfig.Get<float>(StaminaCostFactor); public int PlantLimit => ServerConfig.Get<int>(MaxPlants); public float FigureSizeLimit => ServerConfig.Get<float>(MaxFigureSize); public bool HarvestOn => ServerConfig.Get<bool>(HarvestEnabled); public float HarvestRadiusLimit => ServerConfig.Get<float>(HarvestRadius); public ModConfig(ConfigFile file) { //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Expected O, but got Unknown //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Expected O, but got Unknown //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Expected O, but got Unknown //IL_01b2: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Expected O, but got Unknown //IL_01eb: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: Expected O, but got Unknown //IL_0240: Unknown result type (might be due to invalid IL or missing references) //IL_024a: Expected O, but got Unknown //IL_0299: Unknown result type (might be due to invalid IL or missing references) //IL_02a3: Expected O, but got Unknown //IL_02d2: Unknown result type (might be due to invalid IL or missing references) //IL_02dc: Expected O, but got Unknown //IL_0347: Unknown result type (might be due to invalid IL or missing references) //IL_0351: Expected O, but got Unknown //IL_04d0: Unknown result type (might be due to invalid IL or missing references) //IL_0504: Unknown result type (might be due to invalid IL or missing references) File = file; WheelKey = file.Bind<KeyCode>("0. Keys", "PlantingWheel", (KeyCode)98, "Opens the planting wheel while a seed is selected in build mode. Press again or hold and release to choose. B is unused by the game."); FinishKey = file.Bind<KeyCode>("0. Keys", "FinishFigure", (KeyCode)122, "Finishes a curve or closes a shape (same as clicking its last or first point). None to disable."); FillKey = file.Bind<KeyCode>("0. Keys", "ToggleFill", (KeyCode)103, "Turns filling of circles and shapes on or off. The radial-menu key does nothing in build mode, so G is free there. None to disable."); ClearKey = file.Bind<KeyCode>("0. Keys", "ClearFigure", (KeyCode)8, "Removes the whole figure. None to disable."); RepeatKey = file.Bind<KeyCode>("0. Keys", "RepeatFigure", (KeyCode)121, "Moves the finished figure under the cursor, or places the last figure again once it is planted or cleared. None to disable."); RowsOnMouseWheel = file.Bind<bool>("0. Keys", "RowsOnMouseWheel", true, "In figure mode the mouse wheel changes the number of rows instead of rotating (plants get a random rotation anyway)."); EnabledByDefault = file.Bind<bool>("1. Behaviour", "EnabledByDefault", true, "Start in figure mode. Switch to ordinary planting from the planting wheel."); DefaultSpacingFactor = file.Bind<float>("1. Behaviour", "DefaultSpacingFactor", 2f, new ConfigDescription("Initial spacing = plant grow radius × this factor.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 6f), Array.Empty<object>())); MinDistanceMargin = file.Bind<float>("1. Behaviour", "MinDistanceMargin", 0.35f, new ConfigDescription("Minimum distance between plants = grow radius + this margin. Jitter never goes below it.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), Array.Empty<object>())); MaxPlants = file.Bind<int>("1. Behaviour", "MaxPlants", 500, new ConfigDescription("Hard cap on plants per figure, protects frame rate and the server.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(10, 2000), Array.Empty<object>())); StaminaCostFactor = file.Bind<float>("1. Behaviour", "StaminaCostFactor", 0.5f, new ConfigDescription("Stamina per plant in figure mode = vanilla cost × this factor. 1 is vanilla, 0.5 is half, 0 is free. Single planting is not affected.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), Array.Empty<object>())); MaxFigureSize = file.Bind<float>("1. Behaviour", "MaxFigureSize", 80f, new ConfigDescription("Largest figure in meters (longer side; diameter for a circle). Bigger figures are not previewed or planted, so a stray click far away cannot freeze the game.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 500f), Array.Empty<object>())); RandomRotation = file.Bind<bool>("1. Behaviour", "RandomRotation", true, "Give every plant a random yaw instead of the cursor rotation."); WarpScale = file.Bind<float>("1. Behaviour", "WarpScale", 6f, new ConfigDescription("Size in meters of the slow waves that bend whole rows when jitter is on.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 30f), Array.Empty<object>())); RevalidateInterval = file.Bind<float>("1. Behaviour", "RevalidateInterval", 0.5f, "Seconds between re-checks of ground validity for the preview."); SnapRadius = file.Bind<float>("1. Behaviour", "SnapRadius", 0.8f, new ConfigDescription("Click this close to the last point to finish a curve, or to the first point to close a shape.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.2f, 3f), Array.Empty<object>())); AutoContinueInterval = file.Bind<float>("1. Behaviour", "AutoContinueInterval", 0.6f, new ConfigDescription("Seconds between plants when auto-continue is on.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.2f, 5f), Array.Empty<object>())); HarvestEnabled = file.Bind<bool>("3. Harvest", "Enabled", true, "Hold the modifier and pick a ripe crop to also pick every crop of the same kind around it."); HarvestModifier = file.Bind<KeyCode>("3. Harvest", "Modifier", (KeyCode)304, "Key to hold while picking. None picks the whole area with every pick."); HarvestRadius = file.Bind<float>("3. Harvest", "Radius", 5f, new ConfigDescription("Radius in meters around the picked crop.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 15f), Array.Empty<object>())); HarvestSameType = file.Bind<bool>("3. Harvest", "SameTypeOnly", true, "Only crops of the kind you picked. Off also picks berries, mushrooms, flint, branches and anything else pickable in range."); HarvestHint = file.Bind<bool>("3. Harvest", "ShowHint", true, "Show the area-harvest key under the game's pick-up hint."); EnforceOnClients = file.Bind<bool>("4. Server", "EnforceOnClients", true, "Only matters on a server or the host. Players who have this mod use the server's StaminaCostFactor, MaxPlants, MaxFigureSize, harvest Enabled and harvest Radius instead of their own."); Synced.Add((ConfigEntryBase)(object)StaminaCostFactor); Synced.Add((ConfigEntryBase)(object)MaxPlants); Synced.Add((ConfigEntryBase)(object)MaxFigureSize); Synced.Add((ConfigEntryBase)(object)HarvestEnabled); Synced.Add((ConfigEntryBase)(object)HarvestRadius); ShowHud = file.Bind<bool>("2. Display", "ShowHud", true, "Show the status panel while a seed is selected in figure mode."); HudX = file.Bind<int>("2. Display", "HudX", 20, "Panel X position in pixels from the left."); HudY = file.Bind<int>("2. Display", "HudY", 200, "Panel Y position in pixels from the top."); ShowCurve = file.Bind<bool>("2. Display", "ShowCurve", true, "Draw the guide path and rows as lines."); ShowGrowRadius = file.Bind<bool>("2. Display", "ShowGrowRadius", true, "Draw each plant's grow radius as a ring on the ground."); CurveWidth = file.Bind<float>("2. Display", "CurveWidth", 0.08f, "Line width in meters."); CurveColor = file.Bind<Color>("2. Display", "CurveColor", new Color(0.3f, 0.9f, 0.4f, 0.8f), "Row line colour."); ControlPointColor = file.Bind<Color>("2. Display", "ControlPointColor", new Color(1f, 0.85f, 0.2f, 0.9f), "Control point marker colour."); } } public enum PointStatus { Valid, NoGround, NoBuildZone, PrivateZone, NeedCultivated, NeedDirt, WrongBiome, TooHot, TooCold, DeepSnow, InWater, NoSpace, Planted, Pending } public static class PlacementValidator { private static int s_spaceMask; private static int SpaceMask { get { if (s_spaceMask == 0) { s_spaceMask = LayerMask.GetMask(new string[5] { "Default", "static_solid", "Default_small", "piece", "piece_nonsolid" }); } return s_spaceMask; } } public static bool TryGetGroundHeight(float x, float z, out float height) { Vector3 normal; return TryGetGround(x, z, out height, out normal); } public static bool TryGetGround(float x, float z, out float height, out Vector3 normal) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) height = 0f; normal = Vector3.up; ZoneSystem instance = ZoneSystem.instance; if ((Object)(object)instance == (Object)null) { return false; } RaycastHit val = default(RaycastHit); if (!Physics.Raycast(new Vector3(x, 6000f, z), Vector3.down, ref val, 10000f, instance.m_terrainRayMask)) { return false; } height = ((RaycastHit)(ref val)).point.y; normal = ((RaycastHit)(ref val)).normal; return true; } public static PointStatus Check(Vector3 pos, Piece piece, Plant plant, Player player) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000a: 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_005c: 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_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_0096: 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_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Invalid comparison between Unknown and I4 //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Invalid comparison between Unknown and I4 //IL_0188: 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_0127: Invalid comparison between Unknown and I4 //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Invalid comparison between Unknown and I4 //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Invalid comparison between Unknown and I4 if (Location.IsInsideNoBuildLocation(pos)) { return PointStatus.NoBuildZone; } if (!PrivateArea.CheckAccess(pos, 0f, false, false)) { return PointStatus.PrivateZone; } Heightmap val = Heightmap.FindHeightmap(pos); if ((Object)(object)val == (Object)null && (piece.m_groundOnly || piece.m_cultivatedGroundOnly || piece.m_vegetationGroundOnly)) { return PointStatus.NoGround; } ZoneSystem instance = ZoneSystem.instance; if (piece.m_noInWater && (Object)(object)instance != (Object)null && pos.y < instance.m_waterLevel) { return PointStatus.InWater; } if ((piece.m_cultivatedGroundOnly || ((Object)(object)plant != (Object)null && plant.m_needCultivatedGround)) && ((Object)(object)val == (Object)null || !val.IsCultivated(pos))) { return PointStatus.NeedCultivated; } Biome val2 = (((Object)(object)val != (Object)null) ? val.GetBiome(pos, 0.02f, false) : Heightmap.FindBiome(pos)); if (piece.m_vegetationGroundOnly && (Object)(object)val != (Object)null) { float vegetationMask = val.GetVegetationMask(pos); if (((int)val2 == 32) ? (vegetationMask > 0.1f) : (vegetationMask < 0.25f)) { return PointStatus.NeedDirt; } } if ((int)piece.m_onlyInBiome != 0 && (val2 & piece.m_onlyInBiome) == 0) { return PointStatus.WrongBiome; } if ((Object)(object)plant != (Object)null) { if ((val2 & plant.m_biome) == 0) { return PointStatus.WrongBiome; } if (!plant.m_tolerateHeat && (int)val2 == 32 && !ShieldGenerator.IsInsideShield(pos)) { return PointStatus.TooHot; } if (!plant.m_tolerateCold && ((int)val2 == 64 || (int)val2 == 4) && !ShieldGenerator.IsInsideShield(pos)) { return PointStatus.TooCold; } } if (!piece.m_allowedInDeepSnow && (int)val2 == 64 && (Object)(object)val != (Object)null && (Object)(object)player != (Object)null && val.GetCultivationMask(pos) > player.m_deepSnowBuildHeight) { return PointStatus.DeepSnow; } if ((Object)(object)plant != (Object)null && !HaveGrowSpace(pos, plant)) { return PointStatus.NoSpace; } return PointStatus.Valid; } public static bool HaveGrowSpace(Vector3 pos, Plant plant) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) float growRadius = plant.m_growRadius; if (growRadius > 0f) { Collider[] array = Physics.OverlapSphere(pos, growRadius, SpaceMask); for (int i = 0; i < array.Length; i++) { Plant component = ((Component)array[i]).GetComponent<Plant>(); if ((Object)(object)component == (Object)null || (int)component.GetStatus() == 0) { return false; } } } if (plant.m_growRadiusVines > 0f) { Collider[] array2 = Physics.OverlapSphere(pos, plant.m_growRadiusVines, SpaceMask); for (int j = 0; j < array2.Length; j++) { if ((Object)(object)((Component)array2[j]).GetComponentInParent<Vine>() != (Object)null) { return false; } } } return true; } public static string Describe(PointStatus status) { return status switch { PointStatus.Valid => Loc.T("reason.ok"), PointStatus.NoGround => Loc.T("reason.no_ground"), PointStatus.NoBuildZone => Loc.T("reason.no_build"), PointStatus.PrivateZone => Loc.T("reason.ward"), PointStatus.NeedCultivated => Loc.T("reason.not_cultivated"), PointStatus.NeedDirt => Loc.T("reason.needs_dirt"), PointStatus.WrongBiome => Loc.T("reason.wrong_biome"), PointStatus.TooHot => Loc.T("reason.too_hot"), PointStatus.TooCold => Loc.T("reason.too_cold"), PointStatus.DeepSnow => Loc.T("reason.deep_snow"), PointStatus.InWater => Loc.T("reason.in_water"), PointStatus.NoSpace => Loc.T("reason.no_space"), PointStatus.Planted => Loc.T("reason.planted"), PointStatus.Pending => Loc.T("reason.pending"), _ => status.ToString(), }; } } public static class Planter { public struct Outcome { public int Planted; public int Blocked; public bool OutOfSeeds; public bool OutOfStamina; public bool ToolBroken; } private static readonly EffectList EmptyEffects = new EffectList(); public static Outcome Plant(Player player, Piece piece, List<PlantSlot> slots, int limit, Action<Vector3> onPlanted) { //IL_003c: 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_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_01cb: Unknown result type (might be due to invalid IL or missing references) //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_0203: Unknown result type (might be due to invalid IL or missing references) Outcome result = default(Outcome); ItemData rightItem = ((Humanoid)player).GetRightItem(); if (rightItem == null) { return result; } bool flag = player.m_noPlacementCost || ((Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.GetGlobalKey(piece.FreeBuildKey())); bool flag2 = (((Humanoid)player).m_inventory.ItemCheated(piece.m_resources, -1, true) || player.NoCostCheat()) && !PlayerProfile.s_bypassCheatChecks; float buildStamina = player.GetBuildStamina(); List<int> list = new List<int>(slots.Count); for (int i = 0; i < slots.Count; i++) { if (slots[i].IsValid) { list.Add(i); } else if (slots[i].IsBlocked) { result.Blocked++; } } Vector3 origin = ((Component)player).transform.position; list.Sort((int a, int b) => SqrFlat(slots[a].Position, origin).CompareTo(SqrFlat(slots[b].Position, origin))); EffectList placeEffect = piece.m_placeEffect; try { foreach (int item in list) { if (result.Planted < limit) { PlantSlot plantSlot = slots[item]; if (!flag && !player.HaveRequirements(piece, (RequirementMode)0)) { result.OutOfSeeds = true; break; } if (!((Character)player).HaveStamina(buildStamina)) { result.OutOfStamina = true; break; } if (rightItem.m_shared.m_useDurability && rightItem.m_durability <= 0f) { result.ToolBroken = true; break; } PayForPlant(player, piece, rightItem, flag, buildStamina); piece.m_placeEffect = ((result.Planted == 0) ? placeEffect : EmptyEffects); player.PlacePiece(piece, plantSlot.Position, plantSlot.Rotation, result.Planted == 0, flag2); CountStats(piece, flag2); result.Planted++; onPlanted?.Invoke(plantSlot.Position); continue; } break; } if (result.Planted > 0) { player.m_lastToolUseTime = Time.time; if ((Object)(object)Hud.instance != (Object)null && (Object)(object)Hud.instance.m_buildUi != (Object)null) { Hud.instance.m_buildUi.AddRecentPiece(piece); } } } catch (Exception ex) { Plugin.Log.LogError((object)("Planting failed: " + ex)); } finally { piece.m_placeEffect = placeEffect; } return result; } private static float SqrFlat(Vector3 a, Vector3 b) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) float num = a.x - b.x; float num2 = a.z - b.z; return num * num + num2 * num2; } public static bool CanAffordOne(Player player, Piece piece) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) ItemData rightItem = ((Humanoid)player).GetRightItem(); if (rightItem == null) { return false; } if (!player.m_noPlacementCost && (!((Object)(object)ZoneSystem.instance != (Object)null) || !ZoneSystem.instance.GetGlobalKey(piece.FreeBuildKey())) && !player.HaveRequirements(piece, (RequirementMode)0)) { return false; } if (rightItem.m_shared.m_useDurability && rightItem.m_durability <= 0f) { return false; } return ((Character)player).HaveStamina(player.GetBuildStamina()); } private static void PayForPlant(Player player, Piece piece, ItemData tool, bool free, float stamina) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Invalid comparison between Unknown and I4 //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Invalid comparison between Unknown and I4 if (!free) { player.ConsumeResources(piece.m_resources, 0, -1, 1); } ((Character)player).UseStamina(stamina); PieceTable buildPieces = player.m_buildPieces; if ((Object)(object)buildPieces != (Object)null && (int)buildPieces.m_skill != 0) { if ((int)buildPieces.m_skill == 107) { Game.instance.IncrementPlayerStat((PlayerStatType)168, 1f, false); } if (player.m_buildRemoveDebt > 0) { player.m_buildRemoveDebt--; } else { ((Character)player).RaiseSkill(buildPieces.m_skill, 1f); if ((int)buildPieces.m_skill == 107) { Game.instance.IncrementPlayerStat((PlayerStatType)169, 1f, false); } } } if (tool.m_shared.m_useDurability) { tool.m_durability -= player.GetPlaceDurability(tool) * Game.m_durabilityRate; } } private static void CountStats(Piece piece, bool cheated) { Game instance = Game.instance; if (!((Object)(object)instance == (Object)null)) { instance.IncrementPlayerStat((PlayerStatType)2, 1f, cheated); PlayerProfile playerProfile = instance.GetPlayerProfile(); if (playerProfile != null) { playerProfile.IncrementStatBuildPiecePlaced(piece.m_name, 1f, cheated); } } } } [BepInPlugin("kepqa.AdvancedPlanting", "AdvancedPlanting", "0.4.7")] [BepInIncompatibility("advize.PlantEasily")] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "kepqa.AdvancedPlanting"; public const string PluginName = "AdvancedPlanting"; public const string PluginVersion = "0.4.7"; public const string PluginAuthor = "kepqa"; private GhostPool _ghosts; private CurveRenderer _overlay; private StatusPanel _panel; private BuildKeyHints _keyHints; private Harmony _harmony; private float _scrollAccumulator; private float _nextRevalidate; private float _nextRebuild; private Vector3? _builtCursor; private int _appliedVersion = -1; private int _appliedSnap = -2; private bool _ghostsComplete = true; private const float RebuildInterval = 0.08f; private const int ValidationsPerFrame = 40; private bool _previewVisible; private float _nextAutoPlant; private readonly List<PlantSlot> _ghostSlots = new List<PlantSlot>(); private Player _player; private Piece _piece; private Plant _plant; private string _prefabName; private int _validatedFrame = -1; public static Plugin Instance { get; private set; } public static ManualLogSource Log { get; private set; } public ModConfig Settings { get; private set; } public FigureState State { get; private set; } public LayoutService Layout { get; private set; } public SettingsWindow Window { get; private set; } internal PlantingWheel Wheel { get; private set; } private static bool IsHeadless => (int)SystemInfo.graphicsDeviceType == 4; public bool HasPlantContext { get { if ((Object)(object)_piece != (Object)null) { return (Object)(object)_player != (Object)null; } return false; } } private bool FigureModeActive { get { if (State.Enabled) { return HasPlantContext; } return false; } } public bool OpeningWheel { get; private set; } public int WheelOpenedFrame { get; private set; } = -10; public float WheelKeyDownTime { get; private set; } public bool WindowBlocksInput { get { if (Window != null) { return Window.IsBlockingInput; } return false; } } public string PlantDisplayName { get { if (!((Object)(object)_piece != (Object)null)) { return ""; } return GameText.Localize(_piece.m_name); } } public float MinSpacing => Layout.MinDistance(_plant); public float CurrentSpacing => State.GetSpacing(_prefabName, Layout.DefaultSpacing(_plant)); public float JitterRoom => Mathf.Max(0f, (CurrentSpacing - MinSpacing) * 0.5f); public string WheelKeyName => ((object)Settings.WheelKey.Value/*cast due to .constrained prefix*/).ToString(); public bool ShowsFigureHints { get { if (FigureModeActive) { return !Window.IsOpen; } return false; } } public bool KeyHintsShowing { get; private set; } private void Awake() { //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; Settings = new ModConfig(((BaseUnityPlugin)this).Config); ServerConfig.Init(Settings); Loc.Load(); if (!IsHeadless) { Icons.Prewarm(); UiKit.Prewarm(); } State = new FigureState { Enabled = Settings.EnabledByDefault.Value }; Layout = new LayoutService(Settings); Window = new SettingsWindow(this); Wheel = new PlantingWheel(this); _ghosts = new GhostPool(); _overlay = new CurveRenderer(Settings); _panel = new StatusPanel(Settings); _keyHints = new BuildKeyHints(); _harmony = new Harmony("kepqa.AdvancedPlanting"); _harmony.PatchAll(typeof(Plugin).Assembly); Log.LogInfo((object)"AdvancedPlanting 0.4.7 by kepqa loaded."); } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _ghosts?.DestroyRoots(); _overlay?.Destroy(); Window?.Destroy(); _panel?.Destroy(); Instance = null; } private bool RefreshContext() { _player = Player.m_localPlayer; _piece = null; _plant = null; _prefabName = null; if ((Object)(object)_player == (Object)null || !((Character)_player).InPlaceMode()) { return false; } Piece selectedPiece = _player.GetSelectedPiece(); if ((Object)(object)selectedPiece == (Object)null) { return false; } Plant component = ((Component)selectedPiece).GetComponent<Plant>(); if ((Object)(object)component == (Object)null) { return false; } _piece = selectedPiece; _plant = component; _prefabName = ((Object)((Component)selectedPiece).gameObject).name; return true; } public float ScaleBuildStamina(Player player, float stamina) { if ((Object)(object)player != (Object)(object)_player || !FigureModeActive) { return stamina; } return stamina * Settings.StaminaFactor; } public void SetSpacing(float value) { State.SetSpacing(_prefabName, value, MinSpacing); } internal void KeyHintsUpdated(KeyHints hints) { _keyHints.Update(hints, this); KeyHintsShowing = _keyHints.Available && hints.m_keyHintsEnabled && ShowsFigureHints; } public void SetFigureMode(bool on) { if (State.Enabled != on) { State.Enabled = on; State.MarkDirty(); if (!on) { Window.Close(); HidePreview(); } Player player = _player; if (player != null) { ((Character)player).Message((MessageType)2, Loc.T(on ? "msg.figure_mode" : "msg.single_mode"), 0, (Sprite)null, false); } } } private void Update() { ServerConfig.Update(); if (!RefreshContext()) { Window.Close(); HidePreview(); return; } Icons.UploadStep(); Window.Update(); HandleWheelKey(); HandleQuickKeys(); if (!State.Enabled) { HidePreview(); } else { UpdateAutoContinue(); } } private void HandleQuickKeys() { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_01c3: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) if (!State.Enabled || Hud.InRadial() || Window.IsBlockingInput || Hud.IsPieceSelectionVisible() || !((Character)_player).TakeInput()) { return; } if (Pressed(Settings.FinishKey.Value) && State.CanFinish) { State.Finish(); ((Character)_player).Message((MessageType)2, Loc.T("msg.ready", StatusPanel.ModeName(State.Mode)), 0, (Sprite)null, false); } if (Pressed(Settings.FillKey.Value) && State.IsClosedPath) { State.SetFill(!State.Fill); ((Character)_player).Message((MessageType)2, Loc.T(State.Fill ? "wheel.fill.on" : "wheel.fill.off"), 0, (Sprite)null, false); } if (Pressed(Settings.RepeatKey.Value) && State.CanRepeat) { GameObject placementGhost = _player.m_placementGhost; if ((Object)(object)placementGhost == (Object)null || !placementGhost.activeSelf) { ((Character)_player).Message((MessageType)2, Loc.T("msg.aim"), 0, (Sprite)null, false); } else if (State.RepeatAt(placementGhost.transform.position)) { ((Character)_player).Message((MessageType)2, Loc.T("msg.ready", StatusPanel.ModeName(State.Mode)), 0, (Sprite)null, false); } } if (Pressed(Settings.ClearKey.Value) && !State.IsEmpty) { State.Clear(); ((Character)_player).Message((MessageType)2, Loc.T("msg.cleared"), 0, (Sprite)null, false); } } private static bool Pressed(KeyCode key) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) if ((int)key != 0) { return ZInput.GetKeyDown(key, true); } return false; } public bool HandleMouseWheel(Player player) { if (!Settings.RowsOnMouseWheel.Value || (Object)(object)player != (Object)(object)_player || !FigureModeActive) { return false; } if (Window.IsBlockingInput || Hud.InRadial() || Hud.IsPieceSelectionVisible()) { return false; } float mouseScrollWheel = ZInput.GetMouseScrollWheel(); if (Mathf.Abs(mouseScrollWheel) < 0.001f) { return false; } if (State.IsClosedPath && State.Fill) { return true; } _scrollAccumulator += mouseScrollWheel; float num = Mathf.Max(0.01f, player.m_scrollAmountThreshold); if (Mathf.Abs(_scrollAccumulator) < num) { return true; } int num2 = ((_scrollAccumulator > 0f) ? 1 : (-1)); _scrollAccumulator = 0f; int rows = State.Rows; State.SetRows(State.Rows + num2); if (State.Rows != rows) { ((Character)_player).Message((MessageType)1, Loc.T("msg.rows", State.Rows), 0, (Sprite)null, false); } return true; } private void HandleWheelKey() { //IL_000b: 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_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) KeyCode value = Settings.WheelKey.Value; if ((int)value != 0 && ZInput.GetKeyDown(value, true)) { WheelKeyDownTime = Time.unscaledTime; if (!Hud.InRadial() && !Window.IsBlockingInput && !Hud.IsPieceSelectionVisible() && ((Character)_player).TakeInput()) { OpenWheel(); } } } private void OpenWheel() { Hud instance = Hud.instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance.m_radialMenu == (Object)null || (Object)(object)instance.m_config == (Object)null) { return; } OpeningWheel = true; try { WheelOpenedFrame = Time.frameCount; instance.m_radialMenu.CanOpen = true; instance.m_radialMenu.Open((IRadialConfig)(object)instance.m_config, (IRadialConfig)null); } catch (Exception ex) { Log.LogError((object)("Could not open the planting wheel: " + ex)); } finally { OpeningWheel = false; } } public bool HandlePlacementClick(Player player) { //IL_011c: 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_0123: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_01ab: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_01d9: Unknown result type (might be due to invalid IL or missing references) //IL_0201: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player != (Object)(object)_player || !FigureModeActive) { return false; } if (Window.IsBlockingInput || Hud.InRadial() || Hud.IsPieceSelectionVisible()) { return false; } if (!ZInput.GetButtonDown("Attack") && !ZInput.GetButtonDown("JoyPlace")) { return false; } if (State.Finished) { EnsureValidated(); if (Layout.ValidCount > 0) { ItemData rightItem = ((Humanoid)player).GetRightItem(); if (rightItem != null && !((Character)player).HaveStamina(rightItem.m_shared.m_attack.m_attackStamina)) { ((Character)player).Message((MessageType)2, Loc.T("msg.no_stamina") + (State.AutoContinue ? "" : Loc.T("msg.no_stamina.auto_hint")), 0, (Sprite)null, false); } return false; } State.Clear(); } GameObject placementGhost = player.m_placementGhost; if ((Object)(object)placementGhost == (Object)null || !placementGhost.activeSelf) { ((Character)player).Message((MessageType)2, Loc.T("msg.aim"), 0, (Sprite)null, false); return true; } Vector3 position = placementGhost.transform.position; if (SnapTarget(position) >= 0) { State.Finish(); ((Character)player).Message((MessageType)2, Loc.T("msg.ready", StatusPanel.ModeName(State.Mode)), 0, (Sprite)null, false); return true; } if (State.ControlPoints.Count > 0 && FlatDistance(State.ControlPoints[State.ControlPoints.Count - 1], position) < 0.2f) { return true; } float num = ExtentWith(position); if (num > Settings.FigureSizeLimit) { ((Character)player).Message((MessageType)2, TooLargeText(num), 0, (Sprite)null, false); return true; } State.AddPoint(position); if (State.Finished) { ((Character)player).Message((MessageType)2, Loc.T("msg.ready", StatusPanel.ModeName(State.Mode)), 0, (Sprite)null, false); } return true; } private int SnapTarget(Vector3 cursor) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Invalid comparison between Unknown and I4 //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) if (!State.CanFinish) { return -1; } float value = Settings.SnapRadius.Value; int num = State.ControlPoints.Count - 1; if (FlatDistance(State.ControlPoints[num], cursor) <= value) { return num; } if ((int)State.Mode == 4 && FlatDistance(State.ControlPoints[0], cursor) <= value) { return 0; } return -1; } private float ExtentWith(Vector3 extra) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Invalid comparison between Unknown and I4 //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) if ((int)State.Mode == 2 && State.ControlPoints.Count == 1) { return 2f * FlatDistance(State.ControlPoints[0], extra); } float num = extra.x; float num2 = extra.x; float num3 = extra.z; float num4 = extra.z; foreach (Vector3 controlPoint in State.ControlPoints) { num = Mathf.Min(num, controlPoint.x); num2 = Mathf.Max(num2, controlPoint.x); num3 = Mathf.Min(num3, controlPoint.z); num4 = Mathf.Max(num4, controlPoint.z); } return Mathf.Max(num2 - num, num4 - num3); } public string TooLargeText(float extent) { return Loc.T("msg.too_large", Mathf.RoundToInt(extent), Mathf.RoundToInt(Settings.FigureSizeLimit)); } private static float FlatDistance(Vector3 a, Vector3 b) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) float num = a.x - b.x; float num2 = a.z - b.z; return Mathf.Sqrt(num * num + num2 * num2); } private Vector3? CursorPoint() { //IL_003e: Unknown result type (might be due to invalid IL or missing references) GameObject val = (((Object)(object)_player != (Object)null) ? _player.m_placementGhost : null); if ((Object)(object)val == (Object)null || !val.activeSelf) { return null; } return val.transform.position; } private void RebuildNow() { Vector3? val = (State.Finished ? ((Vector3?)null) : CursorPoint()); try { Layout.Rebuild(State, val, _plant, _prefabName); } catch (Exception ex) { Log.LogError((object)("Layout rebuild failed: " + ex)); Layout.Clear(); } _builtCursor = val; State.ClearDirty(); _nextRebuild = Time.unscaledTime + 0.08f; _nextRevalidate = Time.unscaledTime + Settings.RevalidateInterval.Value; } private void EnsureValidated() { if (_validatedFrame != Time.frameCount || State.Dirty) { if (State.Dirty || Layout.BuiltForPrefab != _prefabName) { RebuildNow(); } Layout.ValidateAll(State, _piece, _plant, _player); _validatedFrame = Time.frameCount; } } public void OnPlacementGhostUpdated(Player player) { //IL_0080: 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_0125: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player != (Object)(object)Player.m_localPlayer) { return; } if (!FigureModeActive) { HidePreview(); return; } float unscaledTime = Time.unscaledTime; bool flag = State.Dirty || Layout.BuiltForPrefab != _prefabName; Vector3? val = CursorPoint(); if (!flag && !State.Finished) { flag = val.HasValue != _builtCursor.HasValue || (val.HasValue && FlatDistance(val.Value, _builtCursor.Value) > 0.1f); } if (flag && unscaledTime >= _nextRebuild) { RebuildNow(); } if (!Layout.PassComplete) { Layout.ValidateStep(State, _piece, _plant, _player, 40); } else if (unscaledTime >= _nextRevalidate) { Layout.BeginRevalidation(); _nextRevalidate = unscaledTime + Settings.RevalidateInterval.Value; } int num = (val.HasValue ? SnapTarget(val.Value) : (-1)); if (Layout.Version == _appliedVersion && num == _appliedSnap && _ghostsComplete && _previewVisible) { return; } _ghostSlots.Clear(); foreach (PlantSlot slot in Layout.Slots) { if (slot.IsValid || slot.IsPending) { _ghostSlots.Add(slot); } } _ghostsComplete = _ghosts.Apply(player.m_placementGhost, _ghostSlots); _overlay.Apply(Layout.RowLines, State.ControlPoints, num, Layout.Slots, ((Object)(object)_plant != (Object)null) ? _plant.m_growRadius : 0f, Layout.TooLarge); _appliedVersion = Layout.Version; _appliedSnap = num; _previewVisible = true; } private void HidePreview() { if (_previewVisible) { _ghosts.HideAll(); _overlay.HideAll(); _previewVisible = false; _appliedVersion = -1; } } public bool TryPlantFigure(Player player, Piece piece) { if (!FigureModeActive || (Object)(object)player != (Object)(object)_player || (Object)(object)piece == (Object)null || !State.Finished) { return false; } if ((Object)(object)((Component)piece).GetComponent<Plant>() == (Object)null) { return false; } EnsureValidated(); if (Layout.ValidCount == 0) { ((Character)player).Message((MessageType)2, Loc.T("msg.nothing", PlacementValidator.Describe(Layout.TopBlockReason)), 0, (Sprite)null, false); return true; } Planter.Outcome outcome = Planter.Plant(player, piece, Layout.Slots, int.MaxValue, State.MarkPlanted); Layout.RefreshPlanted(State); string text = Loc.T("msg.planted", outcome.Planted); if (Layout.ValidCount > 0) { text += Loc.T("msg.left", Layout.ValidCount); if (outcome.OutOfStamina) { text = text + " " + Loc.T(State.AutoContinue ? "msg.left.auto" : "msg.left.stamina"); } else if (outcome.OutOfSeeds) { text = text + " " + Loc.T("msg.left.seeds"); } else if (outcome.ToolBroken) { text = text + " " + Loc.T("msg.left.tool"); } } if (outcome.Blocked > 0) { text += Loc.T("msg.blocked", outcome.Blocked, PlacementValidator.Describe(Layout.TopBlockReason)); } ((Character)player).Message((MessageType)1, text, 0, (Sprite)null, false); CompleteIfDone(); return true; } private void UpdateAutoContinue() { if (State.AutoContinue && State.Finished && !Window.IsBlockingInput && !(Time.time < _nextAutoPlant) && Layout.ValidCount != 0 && !Hud.InRadial() && ((Character)_player).TakeInput()) { _nextAutoPlant = Time.time + Settings.AutoContinueInterval.Value; if (!State.Dirty && Layout.FullyValidated && Layout.ValidCount != 0 && Planter.CanAffordOne(_player, _piece) && Planter.Plant(_player, _piece, Layout.Slots, 1, State.MarkPlanted).Planted != 0) { Layout.RefreshPlanted(State); CompleteIfDone(); } } } private void CompleteIfDone() { if (Layout.ValidCount <= 0 && Layout.BlockedCount <= 0 && Layout.PlantedCount != 0) { Player player = _player; if (player != null) { ((Character)player).Message((MessageType)2, Loc.T("msg.complete", Layout.PlantedCount), 0, (Sprite)null, false); } State.Clear(); } } public string NextActionHint() { //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Expected I4, but got Unknown if (State.Finished) { if (Layout.ValidCount <= 0) { return Loc.T("hint.new_figure"); } return Loc.T("hint.plant", Layout.ValidCount); } int count = State.ControlPoints.Count; PathMode mode = State.Mode; switch ((int)mode) { case 0: return Loc.T
AdvancedPlanting.Core.dll
Decompiled a day agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using Microsoft.CodeAnalysis; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")] [assembly: AssemblyCompany("AdvancedPlanting.Core")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+634f9821e72cc99f55d00900f66b35f1c375ee61")] [assembly: AssemblyProduct("AdvancedPlanting.Core")] [assembly: AssemblyTitle("AdvancedPlanting.Core")] [assembly: AssemblyVersion("1.0.0.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] internal sealed class IsReadOnlyAttribute : Attribute { } } namespace AdvancedPlanting.Core { public static class CurveBuilder { public const float DefaultResolution = 0.1f; public static Polyline Build(PathMode mode, IReadOnlyList<Vec2> controlPoints, float resolution = 0.1f) { if (controlPoints == null) { throw new ArgumentNullException("controlPoints"); } if (resolution <= 0f) { resolution = 0.1f; } List<Vec2> list = TrimToMode(mode, controlPoints); if (list.Count < mode.MinControlPoints()) { return new Polyline(new Vec2[0], closed: false); } return mode switch { PathMode.Segment => new Polyline(new Vec2[2] { list[0], list[list.Count - 1] }, closed: false).Densify(resolution), PathMode.Arc => BuildArc(list[0], list[1], list[2], fullCircle: false, resolution), PathMode.Circle => BuildCircle(list[0], list[1], resolution), PathMode.Spline => BuildCatmullRom(list, closed: false, resolution), PathMode.Contour => BuildCatmullRom(list, closed: true, resolution), _ => throw new ArgumentOutOfRangeException("mode"), }; } private static List<Vec2> TrimToMode(PathMode mode, IReadOnlyList<Vec2> controlPoints) { int num = mode.MaxControlPoints(); List<Vec2> list = new List<Vec2>(controlPoints.Count); for (int i = 0; i < controlPoints.Count; i++) { if (num > 0 && list.Count >= num) { break; } list.Add(controlPoints[i]); } return list; } public static bool TryCircumcircle(Vec2 a, Vec2 b, Vec2 c, out Vec2 center, out float radius) { float num = 2f * (a.X * (b.Z - c.Z) + b.X * (c.Z - a.Z) + c.X * (a.Z - b.Z)); if (Math.Abs(num) < 1E-06f) { center = Vec2.Zero; radius = 0f; return false; } float sqrLength = a.SqrLength; float sqrLength2 = b.SqrLength; float sqrLength3 = c.SqrLength; float x = (sqrLength * (b.Z - c.Z) + sqrLength2 * (c.Z - a.Z) + sqrLength3 * (a.Z - b.Z)) / num; float z = (sqrLength * (c.X - b.X) + sqrLength2 * (a.X - c.X) + sqrLength3 * (b.X - a.X)) / num; center = new Vec2(x, z); radius = Vec2.Distance(center, a); if (radius > 0.0001f) { return radius < 100000f; } return false; } private static Polyline BuildArc(Vec2 start, Vec2 mid, Vec2 end, bool fullCircle, float resolution) { if (!TryCircumcircle(start, mid, end, out var center, out var radius)) { return new Polyline(new Vec2[3] { start, mid, end }, closed: false).Densify(resolution); } float num = (float)Math.Atan2(start.Z - center.Z, start.X - center.X); float num2 = (float)Math.Atan2(mid.Z - center.Z, mid.X - center.X); float num3 = Wrap((float)Math.Atan2(end.Z - center.Z, end.X - center.X) - num); bool flag = Wrap(num2 - num) <= num3; float num4 = (flag ? num3 : (0f - (MathF.PI * 2f - num3))); if (fullCircle) { num4 = (flag ? (MathF.PI * 2f) : (MathF.PI * -2f)); } float num5 = Math.Abs(num4) * radius; int num6 = Math.Max(8, (int)Math.Ceiling(num5 / resolution)); List<Vec2> list = new List<Vec2>(num6 + 1); int num7 = (fullCircle ? num6 : (num6 + 1)); for (int i = 0; i < num7; i++) { float num8 = num + num4 * (float)i / (float)num6; list.Add(new Vec2(center.X + radius * (float)Math.Cos(num8), center.Z + radius * (float)Math.Sin(num8))); } return new Polyline(list, fullCircle); } public static Polyline BuildCircle(Vec2 center, Vec2 edge, float resolution) { float num = Vec2.Distance(center, edge); if (num < 0.001f) { return new Polyline(new Vec2[1] { center }, closed: false); } float num2 = (float)Math.Atan2(edge.Z - center.Z, edge.X - center.X); int num3 = Math.Max(12, (int)Math.Ceiling(MathF.PI * 2f * num / resolution)); List<Vec2> list = new List<Vec2>(num3); for (int i = 0; i < num3; i++) { float num4 = num2 + MathF.PI * 2f * (float)i / (float)num3; list.Add(new Vec2(center.X + num * (float)Math.Cos(num4), center.Z + num * (float)Math.Sin(num4))); } return new Polyline(list, closed: true); } private static float Wrap(float angle) { angle %= MathF.PI * 2f; if (angle < 0f) { angle += MathF.PI * 2f; } return angle; } public static Polyline BuildCatmullRom(IReadOnlyList<Vec2> control, bool closed, float resolution) { List<Vec2> list = new List<Vec2>(control.Count); foreach (Vec2 item in control) { if (list.Count == 0 || Vec2.SqrDistance(list[list.Count - 1], item) > 1E-08f) { list.Add(item); } } if (closed && list.Count > 2 && Vec2.SqrDistance(list[0], list[list.Count - 1]) <= 1E-08f) { list.RemoveAt(list.Count - 1); } if (list.Count < 2) { return new Polyline(list, closed: false); } if (list.Count == 2 && !closed) { return new Polyline(list, closed: false).Densify(resolution); } if (closed && list.Count < 3) { return new Polyline(list, closed: false).Densify(resolution); } List<Vec2> list2 = new List<Vec2>(); int num = (closed ? list.Count : (list.Count - 1)); for (int i = 0; i < num; i++) { Vec2 control2 = GetControl(list, i - 1, closed); Vec2 control3 = GetControl(list, i, closed); Vec2 control4 = GetControl(list, i + 1, closed); Vec2 control5 = GetControl(list, i + 2, closed); float num2 = Vec2.Distance(control3, control4); int num3 = Math.Max(4, (int)Math.Ceiling(num2 / resolution)); for (int j = 0; j < num3; j++) { float t = (float)j / (float)num3; list2.Add(EvaluateCentripetal(control2, control3, control4, control5, t)); } } if (!closed) { list2.Add(list[list.Count - 1]); } return new Polyline(list2, closed); } private static Vec2 GetControl(List<Vec2> pts, int index, bool closed) { int count = pts.Count; if (closed) { index %= count; if (index < 0) { index += count; } return pts[index]; } if (index < 0) { return pts[0] + (pts[0] - pts[1]); } if (index >= count) { return pts[count - 1] + (pts[count - 1] - pts[count - 2]); } return pts[index]; } private static Vec2 EvaluateCentripetal(Vec2 p0, Vec2 p1, Vec2 p2, Vec2 p3, float t) { float num = 0f; float num2 = num + Knot(p0, p1, 0.5f); float num3 = num2 + Knot(p1, p2, 0.5f); float tb = num3 + Knot(p2, p3, 0.5f); if (num3 - num2 < 1E-06f) { return Vec2.Lerp(p1, p2, t); } float t2 = num2 + (num3 - num2) * t; Vec2 a = Blend(p0, p1, num, num2, t2); Vec2 vec = Blend(p1, p2, num2, num3, t2); Vec2 b = Blend(p2, p3, num3, tb, t2); Vec2 a2 = Blend(a, vec, num, num3, t2); Vec2 b2 = Blend(vec, b, num2, tb, t2); return Blend(a2, b2, num2, num3, t2); } private static float Knot(Vec2 a, Vec2 b, float alpha) { return (float)Math.Pow(Math.Max(Vec2.Distance(a, b), 1E-05f), alpha); } private static Vec2 Blend(Vec2 a, Vec2 b, float ta, float tb, float t) { float num = tb - ta; if (num < 1E-06f) { return a; } return a * ((tb - t) / num) + b * ((t - ta) / num); } } public static class Noise { public static uint Hash(int position, uint seed) { int num = position * -1255572915 + (int)seed; int num2 = (num ^ (num >>> 8)) + 1759714724; int num3 = (num2 ^ (num2 << 8)) * 458671337; return (uint)(num3 ^ (num3 >>> 8)); } public static uint Hash(int x, int y, uint seed) { return Hash(x + 198491317 * y, seed); } public static float Float01(int position, uint seed) { return (float)Hash(position, seed) / 4.2949673E+09f; } public static float Float01(int x, int y, uint seed) { return (float)Hash(x, y, seed) / 4.2949673E+09f; } public static float FloatSigned(int position, uint seed) { return Float01(position, seed) * 2f - 1f; } public static Vec2 InUnitDisk(int position, uint seed) { float num = (float)Math.Sqrt(Float01(position, seed)); float num2 = Float01(position, seed + 2654435769u) * 2f * MathF.PI; return new Vec2(num * (float)Math.Cos(num2), num * (float)Math.Sin(num2)); } public static float Smooth2D(float x, float y, uint seed) { int num = FloorToInt(x); int num2 = FloorToInt(y); float t = x - (float)num; float t2 = y - (float)num2; float num3 = Fade(t); float num4 = Fade(t2); float num5 = Float01(num, num2, seed); float num6 = Float01(num + 1, num2, seed); float num7 = Float01(num, num2 + 1, seed); float num8 = Float01(num + 1, num2 + 1, seed); float num9 = num5 + (num6 - num5) * num3; float num10 = num7 + (num8 - num7) * num3; return (num9 + (num10 - num9) * num4) * 2f - 1f; } public static Vec2 SmoothVector2D(float x, float y, uint seed) { return new Vec2(Smooth2D(x, y, seed), Smooth2D(x, y, seed + 2135587861)); } private static float Fade(float t) { return t * t * t * (t * (t * 6f - 15f) + 10f); } private static int FloorToInt(float v) { int num = (int)v; if (!(v < (float)num)) { return num; } return num - 1; } } public static class Offset { public static List<Polyline> OffsetPolyline(Polyline source, float distance, float resolution = 0.1f) { List<Polyline> list = new List<Polyline>(); if (source == null || source.IsDegenerate) { return list; } if (Math.Abs(distance) < 1E-05f) { list.Add(source); return list; } Polyline polyline = source.Densify(resolution); float num = Math.Abs(distance) * 0.98f - resolution; if (num < 0f) { num = 0f; } float length = polyline.Length; int num2 = Math.Max(2, (int)Math.Ceiling(length / resolution)); if (polyline.IsClosed) { num2 = Math.Max(3, num2); } float num3 = Math.Min(2f, Math.Max(resolution, Math.Abs(distance) * 0.15f)); SegmentGrid segmentGrid = new SegmentGrid((num3 > resolution * 1.5f) ? polyline.Resample(num3) : polyline, Math.Max(resolution * 2f, num * 0.5f)); List<Vec2> list2 = new List<Vec2>(); List<List<Vec2>> list3 = new List<List<Vec2>>(); int num4 = (polyline.IsClosed ? num2 : (num2 + 1)); for (int i = 0; i < num4; i++) { float distance2 = (polyline.IsClosed ? (length * (float)i / (float)num2) : Math.Min(length, length * (float)i / (float)num2)); Vec2 vec = polyline.PointAt(distance2) + polyline.NormalAt(distance2) * distance; if (!segmentGrid.AnyCloserThan(vec, num)) { list2.Add(vec); } else if (list2.Count > 0) { list3.Add(list2); list2 = new List<Vec2>(); } } if (list2.Count > 0) { list3.Add(list2); } if (list3.Count == 0) { return list; } if (polyline.IsClosed) { if (list3.Count == 1 && list3[0].Count == num4) { list.Add(new Polyline(list3[0], closed: true)); return list; } if (list3.Count > 1) { List<Vec2> list4 = list3[list3.Count - 1]; list4.AddRange(list3[0]); list3[0] = list4; list3.RemoveAt(list3.Count - 1); } } foreach (List<Vec2> item in list3) { Polyline polyline2 = new Polyline(item, closed: false); if (!polyline2.IsDegenerate) { list.Add(polyline2); } } return list; } public static float InwardSign(Polyline closed) { if (closed == null || !closed.IsClosed) { return 1f; } if (!(closed.SignedArea() >= 0f)) { return -1f; } return 1f; } } public enum PathMode { Segment, Arc, Circle, Spline, Contour } public static class PathModeExtensions { public static int MinControlPoints(this PathMode mode) { return mode switch { PathMode.Segment => 2, PathMode.Arc => 3, PathMode.Circle => 2, PathMode.Spline => 2, PathMode.Contour => 3, _ => 2, }; } public static int MaxControlPoints(this PathMode mode) { return mode switch { PathMode.Segment => 2, PathMode.Arc => 3, PathMode.Circle => 2, _ => 0, }; } public static bool IsClosed(this PathMode mode) { if (mode != PathMode.Circle) { return mode == PathMode.Contour; } return true; } public static PathMode Next(this PathMode mode) { return (PathMode)((int)(mode + 1) % 5); } } public enum RowSide { Both, Left, Right } public sealed class LayoutRequest { public PathMode Mode = PathMode.Spline; public List<Vec2> ControlPoints = new List<Vec2>(); public float Spacing = 2f; public float RowSpacing; public int Rows = 1; public bool Fill; public RowSide Side; public float Jitter; public float Thinning; public uint Seed = 1u; public float MinDistance = 1f; public float WarpScale = 6f; public int MaxPoints = 400; public float MaxExtent; public float Resolution = 0.1f; public float EffectiveRowSpacing { get { if (!(RowSpacing > 0f)) { return Spacing; } return RowSpacing; } } } public readonly struct LayoutPoint { public readonly Vec2 Position; public readonly int Row; public readonly int IndexInRow; public LayoutPoint(Vec2 position, int row, int indexInRow) { Position = position; Row = row; IndexInRow = indexInRow; } } public sealed class LayoutResult { public List<LayoutPoint> Points { get; } = new List<LayoutPoint>(); public List<Polyline> RowCurves { get; } = new List<Polyline>(); public bool Truncated { get; set; } public int Rejected { get; set; } public bool TooLarge { get; set; } public float Extent { get; set; } public bool IsEmpty => Points.Count == 0; } public static class PlantingLayout { private struct RowCurve { public Polyline Curve; public int Index; } private sealed class SpatialHash { private readonly float _cell; private readonly Dictionary<long, List<Vec2>> _buckets = new Dictionary<long, List<Vec2>>(); public SpatialHash(float cellSize) { _cell = cellSize; } private long Key(int cx, int cz) { return ((long)cx << 32) ^ (uint)cz; } public void Add(Vec2 p) { long key = Key(Cell(p.X), Cell(p.Z)); if (!_buckets.TryGetValue(key, out var value)) { value = new List<Vec2>(); _buckets[key] = value; } value.Add(p); } public bool IsFree(Vec2 p, float minDistance) { if (minDistance <= 0f) { return true; } float num = minDistance * minDistance; int num2 = (int)Math.Ceiling(minDistance / _cell); int num3 = Cell(p.X); int num4 = Cell(p.Z); for (int i = -num2; i <= num2; i++) { for (int j = -num2; j <= num2; j++) { if (!_buckets.TryGetValue(Key(num3 + i, num4 + j), out var value)) { continue; } for (int k = 0; k < value.Count; k++) { if (Vec2.SqrDistance(value[k], p) < num) { return false; } } } } return true; } private int Cell(float v) { int num = (int)(v / _cell); if (!(v < 0f) || (float)num * _cell == v) { return num; } return num - 1; } } private const int MaxContourRows = 200; private const int JitterRetries = 5; public static LayoutResult Generate(LayoutRequest request) { if (request == null) { throw new ArgumentNullException("request"); } LayoutResult layoutResult = new LayoutResult(); if (request.ControlPoints == null || request.ControlPoints.Count < request.Mode.MinControlPoints()) { return layoutResult; } if (request.Spacing <= 0.05f) { return layoutResult; } if (request.MaxExtent > 0f) { float num = ExtentOf(request.ControlPoints); if (request.Mode == PathMode.Circle && request.ControlPoints.Count >= 2) { num = 2f * Vec2.Distance(request.ControlPoints[0], request.ControlPoints[1]); } if (num > request.MaxExtent) { layoutResult.TooLarge = true; layoutResult.Extent = num; return layoutResult; } } Polyline polyline = CurveBuilder.Build(request.Mode, request.ControlPoints, request.Resolution); layoutResult.Extent = ExtentOf(polyline.Points); if (request.MaxExtent > 0f && layoutResult.Extent > request.MaxExtent) { layoutResult.TooLarge = true; layoutResult.RowCurves.Add(polyline); return layoutResult; } if (polyline.IsDegenerate) { if (polyline.Count == 1) { layoutResult.RowCurves.Add(polyline); layoutResult.Points.Add(new LayoutPoint(polyline.Points[0], 0, 0)); } return layoutResult; } float maxJitter = MaxJitterOffset(request); SpatialHash spatialHash = new SpatialHash(Math.Max(0.5f, request.MinDistance)); foreach (RowCurve item in BuildRows(request, polyline)) { layoutResult.RowCurves.Add(item.Curve); List<float> list = item.Curve.EvenDistances(request.Spacing); for (int i = 0; i < list.Count; i++) { if (layoutResult.Points.Count >= request.MaxPoints) { layoutResult.Truncated = true; return layoutResult; } Vec2 vec = item.Curve.PointAt(list[i]); int num2 = QuantizedId(vec); if (!(request.Thinning > 0f) || !(Noise.Float01(num2, request.Seed + 20903) < request.Thinning)) { if (!TryPlace(request, vec, num2, maxJitter, spatialHash, out var final)) { layoutResult.Rejected++; continue; } spatialHash.Add(final); layoutResult.Points.Add(new LayoutPoint(final, item.Index, i)); } } } return layoutResult; } private static float ExtentOf(IReadOnlyList<Vec2> pts) { if (pts == null || pts.Count == 0) { return 0f; } float num = float.MaxValue; float num2 = float.MinValue; float num3 = float.MaxValue; float num4 = float.MinValue; foreach (Vec2 pt in pts) { if (pt.X < num) { num = pt.X; } if (pt.X > num2) { num2 = pt.X; } if (pt.Z < num3) { num3 = pt.Z; } if (pt.Z > num4) { num4 = pt.Z; } } return Math.Max(num2 - num, num4 - num3); } public static float MaxJitterOffset(LayoutRequest request) { float num = (Math.Min(request.Spacing, request.EffectiveRowSpacing) - request.MinDistance) * 0.5f; if (!(num > 0f)) { return 0f; } return num; } private static bool TryPlace(LayoutRequest request, Vec2 gridPoint, int cell, float maxJitter, SpatialHash accepted, out Vec2 final) { if (request.Jitter <= 0f || maxJitter <= 0f) { final = gridPoint; return accepted.IsFree(final, request.MinDistance); } float num = request.Jitter * maxJitter; float num2 = ((request.WarpScale > 0.1f) ? request.WarpScale : 6f); Vec2 vec = Noise.SmoothVector2D(gridPoint.X / num2, gridPoint.Z / num2, request.Seed) * 0.6f; for (int i = 0; i < 5; i++) { uint seed = request.Seed + (uint)(i * -1640531527); Vec2 vec2 = Noise.InUnitDisk(cell, seed) * 0.4f; Vec2 vec3 = gridPoint + (vec + vec2) * num; if (accepted.IsFree(vec3, request.MinDistance)) { final = vec3; return true; } } final = gridPoint; return accepted.IsFree(final, request.MinDistance); } private static int QuantizedId(Vec2 p) { int num = (int)Math.Round(p.X * 20f); int num2 = (int)Math.Round(p.Z * 20f); return (num * 73856093) ^ (num2 * 19349663); } private static IEnumerable<RowCurve> BuildRows(LayoutRequest request, Polyline guide) { float rowSpacing = request.EffectiveRowSpacing; if (guide.IsClosed && request.Fill) { float inward = Offset.InwardSign(guide); yield return new RowCurve { Curve = guide, Index = 0 }; for (int k = 1; k < 200; k++) { List<Polyline> list = Offset.OffsetPolyline(guide, inward * rowSpacing * (float)k, request.Resolution); bool any = false; foreach (Polyline item in list) { if (!item.IsDegenerate) { any = true; yield return new RowCurve { Curve = item, Index = k }; } } if (!any) { break; } } yield break; } float sideSign = (guide.IsClosed ? Offset.InwardSign(guide) : 1f); int count = Math.Max(1, request.Rows); for (int k = 0; k < count; k++) { int k2 = request.Side switch { RowSide.Left => k, RowSide.Right => -k, _ => k - (count - 1) / 2, }; if (k2 == 0) { yield return new RowCurve { Curve = guide, Index = 0 }; continue; } foreach (Polyline item2 in Offset.OffsetPolyline(guide, sideSign * rowSpacing * (float)k2, request.Resolution)) { if (!item2.IsDegenerate) { yield return new RowCurve { Curve = item2, Index = k2 }; } } } } } public sealed class Polyline { private readonly List<Vec2> _points; private readonly float[] _cumulative; public bool IsClosed { get; } public IReadOnlyList<Vec2> Points => _points; public int Count => _points.Count; public float Length { get; } public bool IsDegenerate { get { if (_points.Count >= 2) { return Length < 1E-05f; } return true; } } private int EdgeCount => _cumulative.Length - 1; public Polyline(IEnumerable<Vec2> points, bool closed) { _points = RemoveDuplicates(points); IsClosed = closed && _points.Count >= 3; int num = (IsClosed ? _points.Count : Math.Max(0, _points.Count - 1)); _cumulative = new float[num + 1]; float num2 = 0f; for (int i = 0; i < num; i++) { Vec2 a = _points[i]; Vec2 b = _points[(i + 1) % _points.Count]; num2 += Vec2.Distance(a, b); _cumulative[i + 1] = num2; } Length = num2; } private static List<Vec2> RemoveDuplicates(IEnumerable<Vec2> points) { List<Vec2> list = new List<Vec2>(); foreach (Vec2 point in points) { if (list.Count == 0 || Vec2.SqrDistance(list[list.Count - 1], point) > 1E-08f) { list.Add(point); } } if (list.Count > 2 && Vec2.SqrDistance(list[0], list[list.Count - 1]) <= 1E-08f) { list.RemoveAt(list.Count - 1); } return list; } public Vec2 PointAt(float distance) { if (_points.Count == 0) { return Vec2.Zero; } if (_points.Count == 1 || Length <= 0f) { return _points[0]; } distance = NormalizeDistance(distance); int num = FindEdge(distance); float num2 = _cumulative[num]; float num3 = _cumulative[num + 1] - num2; float t = ((num3 <= 0f) ? 0f : ((distance - num2) / num3)); Vec2 a = _points[num]; Vec2 b = _points[(num + 1) % _points.Count]; return Vec2.Lerp(a, b, t); } public Vec2 TangentAt(float distance) { if (_points.Count < 2 || Length <= 0f) { return new Vec2(1f, 0f); } distance = NormalizeDistance(distance); int num = FindEdge(distance); Vec2 vec = EdgeDirection(num); float num2 = _cumulative[num]; float num3 = _cumulative[num + 1]; if (distance - num2 < 0.001f && (IsClosed || num > 0)) { int edge = (num - 1 + EdgeCount) % EdgeCount; vec = (vec + EdgeDirection(edge)).Normalized; } else if (num3 - distance < 0.001f && (IsClosed || num < EdgeCount - 1)) { int edge2 = (num + 1) % EdgeCount; vec = (vec + EdgeDirection(edge2)).Normalized; } if (!(vec.SqrLength < 1E-08f)) { return vec; } return EdgeDirection(num); } public Vec2 NormalAt(float distance) { return TangentAt(distance).PerpLeft; } private Vec2 EdgeDirection(int edge) { Vec2 vec = _points[edge]; return (_points[(edge + 1) % _points.Count] - vec).Normalized; } private float NormalizeDistance(float distance) { if (IsClosed) { distance %= Length; if (distance < 0f) { distance += Length; } return distance; } if (distance < 0f) { return 0f; } if (distance > Length) { return Length; } return distance; } private int FindEdge(float distance) { int num = 0; int num2 = _cumulative.Length - 2; while (num < num2) { int num3 = (num + num2 + 1) / 2; if (_cumulative[num3] <= distance) { num = num3; } else { num2 = num3 - 1; } } return num; } public float SignedArea() { if (_points.Count < 3) { return 0f; } float num = 0f; for (int i = 0; i < _points.Count; i++) { Vec2 a = _points[i]; Vec2 b = _points[(i + 1) % _points.Count]; num += Vec2.Cross(a, b); } return num * 0.5f; } public float DistanceTo(Vec2 p) { if (_points.Count == 0) { return float.PositiveInfinity; } if (_points.Count == 1) { return Vec2.Distance(p, _points[0]); } float num = float.PositiveInfinity; for (int i = 0; i < EdgeCount; i++) { Vec2 a = _points[i]; Vec2 b = _points[(i + 1) % _points.Count]; float num2 = DistancePointSegment(p, a, b); if (num2 < num) { num = num2; } } return num; } public static float DistancePointSegment(Vec2 p, Vec2 a, Vec2 b) { Vec2 vec = b - a; float sqrLength = vec.SqrLength; if (sqrLength <= 1E-12f) { return Vec2.Distance(p, a); } float num = Vec2.Dot(p - a, vec) / sqrLength; if (num < 0f) { num = 0f; } else if (num > 1f) { num = 1f; } return Vec2.Distance(p, a + vec * num); } public List<float> EvenDistances(float spacing) { List<float> list = new List<float>(); if (IsDegenerate || spacing <= 0f) { if (_points.Count > 0) { list.Add(0f); } return list; } if (IsClosed) { int num = Math.Max(1, (int)Math.Floor(Length / spacing + 0.0001f)); float num2 = Length / (float)num; for (int i = 0; i < num; i++) { list.Add((float)i * num2); } } else { int num3 = Math.Max(1, (int)Math.Floor(Length / spacing + 0.0001f)); float num4 = Length / (float)num3; for (int j = 0; j <= num3; j++) { list.Add(Math.Min(Length, (float)j * num4)); } } return list; } public Polyline Resample(float step) { if (IsDegenerate || step <= 0f) { return this; } List<Vec2> list = new List<Vec2>(); foreach (float item in EvenDistances(step)) { list.Add(PointAt(item)); } return new Polyline(list, IsClosed); } public Polyline Densify(float maxEdge) { if (IsDegenerate || maxEdge <= 0f) { return this; } List<Vec2> list = new List<Vec2>(); for (int i = 0; i < EdgeCount; i++) { Vec2 a = _points[i]; Vec2 b = _points[(i + 1) % _points.Count]; float num = Vec2.Distance(a, b); int num2 = Math.Max(1, (int)Math.Ceiling(num / maxEdge)); for (int j = 0; j < num2; j++) { list.Add(Vec2.Lerp(a, b, (float)j / (float)num2)); } } if (!IsClosed) { list.Add(_points[_points.Count - 1]); } return new Polyline(list, IsClosed); } } public sealed class SegmentGrid { private readonly IReadOnlyList<Vec2> _pts; private readonly int _edgeCount; private readonly float _cell; private readonly Dictionary<long, List<int>> _cells = new Dictionary<long, List<int>>(); public SegmentGrid(Polyline polyline, float cellSize) { _pts = polyline.Points; _cell = Math.Max(0.05f, cellSize); int count = _pts.Count; _edgeCount = (polyline.IsClosed ? count : Math.Max(0, count - 1)); for (int i = 0; i < _edgeCount; i++) { Vec2 vec = _pts[i]; Vec2 vec2 = _pts[(i + 1) % count]; int num = Cell(Math.Min(vec.X, vec2.X)); int num2 = Cell(Math.Max(vec.X, vec2.X)); int num3 = Cell(Math.Min(vec.Z, vec2.Z)); int num4 = Cell(Math.Max(vec.Z, vec2.Z)); for (int j = num; j <= num2; j++) { for (int k = num3; k <= num4; k++) { Bucket(j, k).Add(i); } } } } public bool AnyCloserThan(Vec2 p, float radius) { if (radius <= 0f) { return false; } int num = (int)Math.Ceiling(radius / _cell); int num2 = Cell(p.X); int num3 = Cell(p.Z); int count = _pts.Count; for (int i = num2 - num; i <= num2 + num; i++) { for (int j = num3 - num; j <= num3 + num; j++) { if (!_cells.TryGetValue(Key(i, j), out var value)) { continue; } foreach (int item in value) { if (Polyline.DistancePointSegment(p, _pts[item], _pts[(item + 1) % count]) < radius) { return true; } } } } return false; } private List<int> Bucket(int x, int z) { long key = Key(x, z); if (!_cells.TryGetValue(key, out var value)) { value = new List<int>(4); _cells[key] = value; } return value; } private int Cell(float v) { return (int)Math.Floor(v / _cell); } private static long Key(int x, int z) { return ((long)x << 32) ^ (uint)z; } } public readonly struct Vec2 : IEquatable<Vec2> { public readonly float X; public readonly float Z; public static readonly Vec2 Zero = new Vec2(0f, 0f); public float Length => (float)Math.Sqrt(X * X + Z * Z); public float SqrLength => X * X + Z * Z; public Vec2 Normalized { get { float length = Length; if (!(length < 1E-06f)) { return new Vec2(X / length, Z / length); } return Zero; } } public Vec2 PerpLeft => new Vec2(0f - Z, X); public Vec2(float x, float z) { X = x; Z = z; } public static Vec2 operator +(Vec2 a, Vec2 b) { return new Vec2(a.X + b.X, a.Z + b.Z); } public static Vec2 operator -(Vec2 a, Vec2 b) { return new Vec2(a.X - b.X, a.Z - b.Z); } public static Vec2 operator *(Vec2 a, float s) { return new Vec2(a.X * s, a.Z * s); } public static Vec2 operator *(float s, Vec2 a) { return new Vec2(a.X * s, a.Z * s); } public static Vec2 operator /(Vec2 a, float s) { return new Vec2(a.X / s, a.Z / s); } public static Vec2 operator -(Vec2 a) { return new Vec2(0f - a.X, 0f - a.Z); } public static float Dot(Vec2 a, Vec2 b) { return a.X * b.X + a.Z * b.Z; } public static float Cross(Vec2 a, Vec2 b) { return a.X * b.Z - a.Z * b.X; } public static float Distance(Vec2 a, Vec2 b) { return (a - b).Length; } public static float SqrDistance(Vec2 a, Vec2 b) { return (a - b).SqrLength; } public static Vec2 Lerp(Vec2 a, Vec2 b, float t) { return a + (b - a) * t; } public bool Equals(Vec2 other) { if (X == other.X) { return Z == other.Z; } return false; } public override bool Equals(object obj) { if (obj is Vec2 other) { return Equals(other); } return false; } public override int GetHashCode() { float x = X; int num = x.GetHashCode() * 397; x = Z; return num ^ x.GetHashCode(); } public override string ToString() { return $"({X:0.###}, {Z:0.###})"; } } }