using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Runic.Foundation.Core; using RunicAgriculture.Core; using RunicAgriculture.Integration; using TMPro; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("Runic Agriculture")] [assembly: AssemblyDescription("Bounded, validated planting patterns and modest crop harvesting for Valheim.")] [assembly: AssemblyCompany("Chazman")] [assembly: AssemblyProduct("Runic Agriculture")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: InternalsVisibleTo("RunicAgriculture.Tests")] [assembly: InternalsVisibleTo("RunicAgriculture.DedicatedHarness")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace Runic.Foundation.Core { public static class RunicIdentifier { public static bool IsValid(string value) { if (string.IsNullOrEmpty(value) || value.Length > 128) { return false; } bool flag = false; bool flag2 = false; foreach (char c in value) { if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')) { flag = true; flag2 = false; continue; } switch (c) { case '-': if (!flag || flag2) { return false; } flag2 = true; break; case '.': if (!flag || flag2) { return false; } flag = false; flag2 = false; break; default: return false; } } if (flag) { return !flag2; } return false; } public static string Require(string value, string parameterName) { if (!IsValid(value)) { throw new ArgumentException("Runic identifiers must be lowercase dot-separated tokens containing only ASCII letters, digits, and internal hyphens.", parameterName); } return value; } } public sealed class InputChord : IEquatable { private readonly IReadOnlyList _modifiers; public string DeviceId { get; } public string PrimaryControl { get; } public IReadOnlyList Modifiers => _modifiers; public string CanonicalId { get; } public InputChord(string deviceId, string primaryControl, IEnumerable modifiers = null) { DeviceId = RunicIdentifier.Require(deviceId, "deviceId"); PrimaryControl = RequireControl(primaryControl, "primaryControl"); string text = CanonicalizeControl(PrimaryControl); SortedDictionary sortedDictionary = new SortedDictionary(StringComparer.Ordinal); if (modifiers != null) { foreach (string modifier in modifiers) { string text2 = RequireControl(modifier, "modifiers"); string text3 = CanonicalizeControl(text2); if (string.Equals(text3, text, StringComparison.Ordinal)) { throw new ArgumentException("The primary control cannot also be a modifier.", "modifiers"); } if (sortedDictionary.ContainsKey(text3)) { throw new ArgumentException("Duplicate input modifier: " + text2, "modifiers"); } sortedDictionary.Add(text3, text2); } } string[] array = new string[sortedDictionary.Count]; int num = 0; foreach (string value in sortedDictionary.Values) { array[num++] = value; } _modifiers = Array.AsReadOnly(array); StringBuilder stringBuilder = new StringBuilder(DeviceId).Append(':'); foreach (string key in sortedDictionary.Keys) { stringBuilder.Append(key).Append('+'); } stringBuilder.Append(text); CanonicalId = stringBuilder.ToString(); } public bool Equals(InputChord other) { if (other != null) { return string.Equals(CanonicalId, other.CanonicalId, StringComparison.Ordinal); } return false; } public override bool Equals(object obj) { return Equals(obj as InputChord); } public override int GetHashCode() { return StringComparer.Ordinal.GetHashCode(CanonicalId); } public override string ToString() { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < _modifiers.Count; i++) { if (i > 0) { stringBuilder.Append(" + "); } stringBuilder.Append(_modifiers[i]); } if (_modifiers.Count > 0) { stringBuilder.Append(" + "); } return stringBuilder.Append(PrimaryControl).Append(" [").Append(DeviceId) .Append(']') .ToString(); } private static string RequireControl(string value, string parameterName) { if (string.IsNullOrWhiteSpace(value)) { throw new ArgumentException("An input control name is required.", parameterName); } string text = value.Trim(); if (text.Length > 64) { throw new ArgumentException("Input control names cannot exceed 64 characters.", parameterName); } string text2 = text; foreach (char c in text2) { if (char.IsControl(c) || c == ':' || c == '+' || c == '|') { throw new ArgumentException("The input control name contains a reserved character.", parameterName); } } if (CanonicalizeControl(text).Length == 0) { throw new ArgumentException("The input control name has no canonical characters.", parameterName); } return text; } private static string CanonicalizeControl(string value) { StringBuilder stringBuilder = new StringBuilder(value.Length); foreach (char c in value) { if (!char.IsWhiteSpace(c) && c != '_' && c != '-') { stringBuilder.Append(char.ToLowerInvariant(c)); } } return stringBuilder.ToString(); } } public sealed class KeybindingDescriptor { public string ModuleId { get; } public string BindingId { get; } public string QualifiedId => ModuleId + "/" + BindingId; public string DisplayName { get; } public InputChord Chord { get; } public string Context { get; } public KeybindingDescriptor(string moduleId, string bindingId, string displayName, InputChord chord, string context = "gameplay") { ModuleId = RunicIdentifier.Require(moduleId, "moduleId"); BindingId = RunicIdentifier.Require(bindingId, "bindingId"); Context = RunicIdentifier.Require(context, "context"); if (string.IsNullOrWhiteSpace(displayName)) { throw new ArgumentException("A keybinding display name is required.", "displayName"); } Chord = chord ?? throw new ArgumentNullException("chord"); DisplayName = displayName.Trim(); } } public sealed class KeybindingConflict { public InputChord Chord { get; } public IReadOnlyList Bindings { get; } internal KeybindingConflict(InputChord chord, IReadOnlyList bindings) { Chord = chord; Bindings = bindings; } } public enum KeybindingChangeKind { Registered, Unregistered } public sealed class KeybindingsChangedEventArgs : EventArgs { public KeybindingChangeKind Kind { get; } public KeybindingDescriptor Binding { get; } public KeybindingConflict Conflict { get; } internal KeybindingsChangedEventArgs(KeybindingChangeKind kind, KeybindingDescriptor binding, KeybindingConflict conflict) { Kind = kind; Binding = binding; Conflict = conflict; } } public sealed class KeybindingConflictRegistry { private sealed class BindingEntry { internal KeybindingDescriptor Binding { get; } internal long Token { get; } internal BindingEntry(KeybindingDescriptor binding, long token) { Binding = binding; Token = token; } } private readonly object _sync = new object(); private readonly Dictionary _byQualifiedId = new Dictionary(StringComparer.Ordinal); private readonly Dictionary> _byChord = new Dictionary>(StringComparer.Ordinal); private long _nextToken; public event EventHandler Changed; public KeybindingRegistration Register(KeybindingDescriptor binding) { if (binding == null) { throw new ArgumentNullException("binding"); } long token; KeybindingConflict conflict; lock (_sync) { if (_byQualifiedId.ContainsKey(binding.QualifiedId)) { throw new InvalidOperationException("A keybinding is already registered as '" + binding.QualifiedId + "'."); } if (_nextToken == long.MaxValue) { throw new InvalidOperationException("The keybinding registration token space is exhausted."); } token = ++_nextToken; BindingEntry bindingEntry = new BindingEntry(binding, token); _byQualifiedId.Add(binding.QualifiedId, bindingEntry); if (!_byChord.TryGetValue(binding.Chord.CanonicalId, out var value)) { value = new List(); _byChord.Add(binding.Chord.CanonicalId, value); } value.Add(bindingEntry); conflict = CreateConflictLocked(binding.Chord.CanonicalId); } RaiseChanged(new KeybindingsChangedEventArgs(KeybindingChangeKind.Registered, binding, conflict)); return new KeybindingRegistration(this, binding, token); } public bool Unregister(string moduleId, string bindingId) { RunicIdentifier.Require(moduleId, "moduleId"); RunicIdentifier.Require(bindingId, "bindingId"); return Unregister(moduleId + "/" + bindingId, (long?)null); } public bool TryGetBinding(string moduleId, string bindingId, out KeybindingDescriptor binding) { binding = null; if (!RunicIdentifier.IsValid(moduleId) || !RunicIdentifier.IsValid(bindingId)) { return false; } lock (_sync) { if (!_byQualifiedId.TryGetValue(moduleId + "/" + bindingId, out var value)) { return false; } binding = value.Binding; return true; } } public IReadOnlyList GetBindings() { lock (_sync) { List list = new List(_byQualifiedId.Count); foreach (BindingEntry value in _byQualifiedId.Values) { list.Add(value.Binding); } list.Sort((KeybindingDescriptor left, KeybindingDescriptor right) => StringComparer.Ordinal.Compare(left.QualifiedId, right.QualifiedId)); return list.AsReadOnly(); } } public IReadOnlyList GetConflicts() { lock (_sync) { List list = new List(); foreach (string key in _byChord.Keys) { KeybindingConflict keybindingConflict = CreateConflictLocked(key); if (keybindingConflict != null) { list.Add(keybindingConflict); } } list.Sort((KeybindingConflict left, KeybindingConflict right) => StringComparer.Ordinal.Compare(left.Chord.CanonicalId, right.Chord.CanonicalId)); return list.AsReadOnly(); } } public IReadOnlyList GetConflictsFor(string moduleId) { RunicIdentifier.Require(moduleId, "moduleId"); IReadOnlyList conflicts = GetConflicts(); List list = new List(); foreach (KeybindingConflict item in conflicts) { foreach (KeybindingDescriptor binding in item.Bindings) { if (string.Equals(binding.ModuleId, moduleId, StringComparison.Ordinal)) { list.Add(item); break; } } } return list.AsReadOnly(); } internal bool IsActive(string qualifiedId, long token) { lock (_sync) { BindingEntry value; return _byQualifiedId.TryGetValue(qualifiedId, out value) && value.Token == token; } } internal bool Unregister(string qualifiedId, long? requiredToken) { KeybindingDescriptor binding; KeybindingConflict conflict; lock (_sync) { if (!_byQualifiedId.TryGetValue(qualifiedId, out var entry) || (requiredToken.HasValue && entry.Token != requiredToken.Value)) { return false; } binding = entry.Binding; _byQualifiedId.Remove(qualifiedId); List list = _byChord[binding.Chord.CanonicalId]; list.RemoveAll((BindingEntry candidate) => candidate.Token == entry.Token); if (list.Count == 0) { _byChord.Remove(binding.Chord.CanonicalId); } conflict = CreateConflictLocked(binding.Chord.CanonicalId); } RaiseChanged(new KeybindingsChangedEventArgs(KeybindingChangeKind.Unregistered, binding, conflict)); return true; } private KeybindingConflict CreateConflictLocked(string chordId) { if (!_byChord.TryGetValue(chordId, out var value) || value.Count < 2) { return null; } List list = new List(value.Count); foreach (BindingEntry item in value) { list.Add(item.Binding); } list.Sort((KeybindingDescriptor left, KeybindingDescriptor right) => StringComparer.Ordinal.Compare(left.QualifiedId, right.QualifiedId)); return new KeybindingConflict(list[0].Chord, list.AsReadOnly()); } private void RaiseChanged(KeybindingsChangedEventArgs arguments) { EventHandler eventHandler = this.Changed; if (eventHandler == null) { return; } Delegate[] invocationList = eventHandler.GetInvocationList(); for (int i = 0; i < invocationList.Length; i++) { EventHandler eventHandler2 = (EventHandler)invocationList[i]; try { eventHandler2(this, arguments); } catch (Exception) { } } } } public sealed class KeybindingRegistration : IDisposable { private readonly KeybindingConflictRegistry _registry; private readonly long _token; public KeybindingDescriptor Binding { get; } public bool IsActive => _registry.IsActive(Binding.QualifiedId, _token); internal KeybindingRegistration(KeybindingConflictRegistry registry, KeybindingDescriptor binding, long token) { _registry = registry; Binding = binding; _token = token; } public void Dispose() { _registry.Unregister(Binding.QualifiedId, _token); } } } namespace RunicAgriculture { internal static class AgricultureConfig { internal const int HardMaximumPreview = 1600; internal const int HardMaximumHarvest = 25; internal static ConfigEntry Enabled { get; private set; } internal static ConfigEntry Pattern { get; private set; } internal static ConfigEntry Alignment { get; private set; } internal static ConfigEntry Rows { get; private set; } internal static ConfigEntry Columns { get; private set; } internal static ConfigEntry Spacing { get; private set; } internal static ConfigEntry LegacyCircleRadius { get; private set; } internal static ConfigEntry MirrorShape { get; private set; } internal static ConfigEntry TrapezoidLeftPinch { get; private set; } internal static ConfigEntry TrapezoidRightPinch { get; private set; } internal static ConfigEntry NearbySeedChestRange { get; private set; } internal static ConfigEntry InvalidPolicy { get; private set; } internal static ConfigEntry ResourcePolicy { get; private set; } internal static ConfigEntry HarvestRadius { get; private set; } internal static ConfigEntry MaximumHarvest { get; private set; } internal static ConfigEntry OfferReplantPreview { get; private set; } internal static ConfigEntry ShowHoverStatus { get; private set; } internal static ConfigEntry ShowContextualControls { get; private set; } internal static ConfigEntry ControlBarScale { get; private set; } internal static ConfigEntry VerboseLogging { get; private set; } internal static ConfigEntry ConfirmPattern { get; private set; } internal static ConfigEntry CyclePattern { get; private set; } internal static ConfigEntry AreaHarvest { get; private set; } internal static ConfigEntry ConfirmReplant { get; private set; } internal static ConfigEntry IncreaseRows { get; private set; } internal static ConfigEntry DecreaseRows { get; private set; } internal static ConfigEntry IncreaseColumns { get; private set; } internal static ConfigEntry DecreaseColumns { get; private set; } internal static ConfigEntry ToggleShapeSide { get; private set; } internal static ConfigEntry DecreaseLeftPinch { get; private set; } internal static ConfigEntry IncreaseLeftPinch { get; private set; } internal static ConfigEntry DecreaseRightPinch { get; private set; } internal static ConfigEntry IncreaseRightPinch { get; private set; } internal static ConfigEntry ControllerEnabled { get; private set; } internal static ConfigEntry ControllerModifier { get; private set; } internal static ConfigEntry ControllerConfirm { get; private set; } internal static ConfigEntry ControllerCycle { get; private set; } internal static ConfigEntry ControllerAreaHarvest { get; private set; } internal static ConfigEntry ControllerPreviousEditorField { get; private set; } internal static ConfigEntry ControllerNextEditorField { get; private set; } internal static ConfigEntry ControllerDecreaseEditorValue { get; private set; } internal static ConfigEntry ControllerIncreaseEditorValue { get; private set; } internal static ConfigEntry LegacyCircleRadiusMigrationApplied { get; private set; } internal static ConfigEntry GridAndCompactHudMigrationApplied { get; private set; } internal static void Bind(ConfigFile config) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Expected O, but got Unknown //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Expected O, but got Unknown //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Expected O, but got Unknown //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Expected O, but got Unknown //IL_0222: Unknown result type (might be due to invalid IL or missing references) //IL_022c: Expected O, but got Unknown //IL_025a: Unknown result type (might be due to invalid IL or missing references) //IL_0264: Expected O, but got Unknown //IL_0292: Unknown result type (might be due to invalid IL or missing references) //IL_029c: Expected O, but got Unknown //IL_0300: Unknown result type (might be due to invalid IL or missing references) //IL_030a: Expected O, but got Unknown //IL_032e: Unknown result type (might be due to invalid IL or missing references) //IL_0338: Expected O, but got Unknown //IL_03b7: Unknown result type (might be due to invalid IL or missing references) //IL_03c1: Expected O, but got Unknown //IL_0417: Unknown result type (might be due to invalid IL or missing references) //IL_0430: Unknown result type (might be due to invalid IL or missing references) //IL_0435: Unknown result type (might be due to invalid IL or missing references) //IL_0438: Unknown result type (might be due to invalid IL or missing references) //IL_0442: Invalid comparison between Unknown and I4 //IL_046c: Unknown result type (might be due to invalid IL or missing references) //IL_0449: Unknown result type (might be due to invalid IL or missing references) //IL_044e: Unknown result type (might be due to invalid IL or missing references) //IL_0491: Unknown result type (might be due to invalid IL or missing references) //IL_04c0: Unknown result type (might be due to invalid IL or missing references) //IL_04ef: Unknown result type (might be due to invalid IL or missing references) //IL_0529: Unknown result type (might be due to invalid IL or missing references) //IL_0563: Unknown result type (might be due to invalid IL or missing references) //IL_059d: Unknown result type (might be due to invalid IL or missing references) //IL_05d7: Unknown result type (might be due to invalid IL or missing references) //IL_060e: Unknown result type (might be due to invalid IL or missing references) //IL_0645: Unknown result type (might be due to invalid IL or missing references) //IL_067c: Unknown result type (might be due to invalid IL or missing references) //IL_06b3: Unknown result type (might be due to invalid IL or missing references) //IL_06ea: Unknown result type (might be due to invalid IL or missing references) Enabled = config.Bind("General", "Enabled", true, "Enable Runic planting previews, explicit batch actions, and read-only status text."); Pattern = config.Bind("Planting Pattern", "Pattern", PlantingGridPolicy.DefaultPattern, "Preview shape: Row, Grid, Circle, Star, RightTriangle, HalfCircle, or Trapezoid. The cycle shortcut changes it live."); Alignment = config.Bind("Planting Pattern", "Alignment", AgricultureAlignment.PlayerHeading, "Align to player heading, world axes, or the nearest two matching crops."); Rows = config.Bind("Planting Pattern", "Rows", 5, new ConfigDescription("Forward footprint in planting rows for every shape except Row. Change it live with the row controls.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 256), Array.Empty())); Columns = config.Bind("Planting Pattern", "ColumnsOrPoints", 5, new ConfigDescription("Side-to-side footprint in planting columns. Change it live with the column controls.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 256), Array.Empty())); Spacing = config.Bind("Planting Pattern", "SpacingMeters", 1.5f, new ConfigDescription("Center-to-center spacing for every generated shape.", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 6f), Array.Empty())); LegacyCircleRadius = config.Bind("Planting Pattern", "CircleRadiusMeters", 3f, new ConfigDescription("Legacy migration-only circle radius. Live Circle size now uses Rows and Columns; this value is never reapplied after the migration marker is set.", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 12f), Array.Empty())); LegacyCircleRadiusMigrationApplied = config.Bind("Migrations", "LegacyCircleRadiusMappedToRowsAndColumns", false, "Internal one-time migration marker. Custom row/column dimensions take precedence over the legacy radius."); if (!LegacyCircleRadiusMigrationApplied.Value) { bool saveOnConfigSet = config.SaveOnConfigSet; try { config.SaveOnConfigSet = false; if (ShouldMapLegacyCircleRadius(migrationApplied: false, Rows.Value, Columns.Value)) { int num = LegacyCircleDimension(LegacyCircleRadius.Value, Spacing.Value); if (Rows.Value != num) { Rows.Value = num; } if (Columns.Value != num) { Columns.Value = num; } } LegacyCircleRadiusMigrationApplied.Value = true; config.Save(); } finally { config.SaveOnConfigSet = saveOnConfigSet; } } MirrorShape = config.Bind("Planting Pattern", "MirrorShape", false, "Switch the side used by RightTriangle and HalfCircle, or mirror an asymmetric Trapezoid."); TrapezoidLeftPinch = config.Bind("Planting Pattern", "TrapezoidLeftPinch", 0.5f, new ConfigDescription("How far the unmirrored trapezoid's left front edge tapers inward (0 = straight, 1 = center).", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); TrapezoidRightPinch = config.Bind("Planting Pattern", "TrapezoidRightPinch", 0.5f, new ConfigDescription("How far the unmirrored trapezoid's right front edge tapers inward (0 = straight, 1 = center).", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); NearbySeedChestRange = config.Bind("Planting Resources", "NearbyChestRangeMeters", 30f, new ConfigDescription("Player-centered range for eligible nearby chests that may supply planting resources. Personal inventory is always consumed first. Static locally-owned chests only; access and wards are rechecked before mutation.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 30f), Array.Empty())); InvalidPolicy = config.Bind("Confirmation", "InvalidPositionPolicy", InvalidPositionPolicy.SkipInvalid, "Skip amber/gray terrain or spacing failures, or block the entire confirmation before the first plant."); ResourcePolicy = config.Bind("Confirmation", "ResourceShortfallPolicy", ResourceShortfallPolicy.TruncatePredictably, "Stop cleanly at the combined personal-and-nearby-chest per-cell resource budget, or block first. A confirmed left-click batch uses one normal stamina/tool-durability action."); HarvestRadius = config.Bind("Harvest", "RadiusMeters", 4f, new ConfigDescription("Area-harvest radius for the exact same registered Pickable prefab.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 8f), Array.Empty())); MaximumHarvest = config.Bind("Harvest", "MaximumPlants", 25, new ConfigDescription("Maximum Pickable requests in one area-harvest batch. Hard-capped at 25.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 25), Array.Empty())); OfferReplantPreview = config.Bind("Harvest", "OfferConfirmedReplant", true, "After bounded area harvest, remember successful positions and offer replant ghosts only for matching crops in authorized planting areas; the cultivator does not need to be equipped and planting is never automatic."); ShowHoverStatus = config.Bind("Status", "ShowBeeAndCropStatus", true, "Append concise read-only honey, bee happiness, crop maturity, and growth-failure status."); ShowContextualControls = config.Bind("Status", "ShowContextualControls", true, "Replace the vanilla bottom build hints with the Agriculture control bar while a crop preview is active."); ControlBarScale = config.Bind("Status", "ControlBarScale", 0.9f, new ConfigDescription("Scale Valheim's compact bottom Agriculture build-hint panel.", (AcceptableValueBase)(object)new AcceptableValueRange(0.75f, 1.75f), Array.Empty())); GridAndCompactHudMigrationApplied = config.Bind("Migrations", "GridAndCompactBottomHudApplied", false, "Internal one-time migration marker. Resets the prior shape to the basic Grid and selects the compact native HUD scale once."); ApplyGridAndCompactHudMigration(config); VerboseLogging = config.Bind("Diagnostics", "VerboseLogging", false, "Log input routing, preview state changes, denials, and action results for troubleshooting."); ConfirmPattern = config.Bind("Controls", "ConfirmPattern", new KeyboardShortcut((KeyCode)323, Array.Empty()), "Informational binding for ordinary left-click planting; the live Valheim Attack action confirms the displayed pattern."); KeyboardShortcut value = ConfirmPattern.Value; if ((int)((KeyboardShortcut)(ref value)).MainKey == 323) { value = ConfirmPattern.Value; if (!((KeyboardShortcut)(ref value)).Modifiers.Any()) { goto IL_0476; } } ConfirmPattern.Value = new KeyboardShortcut((KeyCode)323, Array.Empty()); goto IL_0476; IL_0476: CyclePattern = config.Bind("Controls", "CyclePattern", new KeyboardShortcut((KeyCode)111, (KeyCode[])(object)new KeyCode[1] { (KeyCode)308 }), "Cycle Row, Grid, Circle, Star, RightTriangle, HalfCircle, and Trapezoid while holding a plant with the cultivator."); AreaHarvest = config.Bind("Controls", "AreaHarvestModifierInteract", new KeyboardShortcut((KeyCode)101, (KeyCode[])(object)new KeyCode[1] { (KeyCode)308 }), "Modifier-interact an available Pickable to harvest nearby objects of that exact registered prefab."); ConfirmReplant = config.Bind("Controls", "ConfirmReplant", new KeyboardShortcut((KeyCode)116, (KeyCode[])(object)new KeyCode[1] { (KeyCode)308 }), "Explicitly confirm a pending replant preview while the matching crop is selected."); IncreaseRows = config.Bind("Pattern Editing Controls", "IncreaseRows", new KeyboardShortcut((KeyCode)273, (KeyCode[])(object)new KeyCode[2] { (KeyCode)308, (KeyCode)304 }), "Add one forward row to the live planting preview."); DecreaseRows = config.Bind("Pattern Editing Controls", "DecreaseRows", new KeyboardShortcut((KeyCode)274, (KeyCode[])(object)new KeyCode[2] { (KeyCode)308, (KeyCode)304 }), "Remove one forward row from the live planting preview."); IncreaseColumns = config.Bind("Pattern Editing Controls", "IncreaseColumns", new KeyboardShortcut((KeyCode)275, (KeyCode[])(object)new KeyCode[2] { (KeyCode)308, (KeyCode)304 }), "Add one side-to-side column to the live planting preview."); DecreaseColumns = config.Bind("Pattern Editing Controls", "DecreaseColumns", new KeyboardShortcut((KeyCode)276, (KeyCode[])(object)new KeyCode[2] { (KeyCode)308, (KeyCode)304 }), "Remove one side-to-side column from the live planting preview."); ToggleShapeSide = config.Bind("Pattern Editing Controls", "ToggleShapeSide", new KeyboardShortcut((KeyCode)108, (KeyCode[])(object)new KeyCode[2] { (KeyCode)308, (KeyCode)304 }), "Switch RightTriangle/HalfCircle sides or mirror an asymmetric Trapezoid."); DecreaseLeftPinch = config.Bind("Pattern Editing Controls", "DecreaseLeftTrapezoidPinch", new KeyboardShortcut((KeyCode)91, (KeyCode[])(object)new KeyCode[2] { (KeyCode)308, (KeyCode)304 }), "Widen the unmirrored trapezoid's left front edge by one step."); IncreaseLeftPinch = config.Bind("Pattern Editing Controls", "IncreaseLeftTrapezoidPinch", new KeyboardShortcut((KeyCode)93, (KeyCode[])(object)new KeyCode[2] { (KeyCode)308, (KeyCode)304 }), "Pinch the unmirrored trapezoid's left front edge inward by one step."); DecreaseRightPinch = config.Bind("Pattern Editing Controls", "DecreaseRightTrapezoidPinch", new KeyboardShortcut((KeyCode)59, (KeyCode[])(object)new KeyCode[2] { (KeyCode)308, (KeyCode)304 }), "Widen the unmirrored trapezoid's right front edge by one step."); IncreaseRightPinch = config.Bind("Pattern Editing Controls", "IncreaseRightTrapezoidPinch", new KeyboardShortcut((KeyCode)39, (KeyCode[])(object)new KeyCode[2] { (KeyCode)308, (KeyCode)304 }), "Pinch the unmirrored trapezoid's right front edge inward by one step."); ControllerEnabled = config.Bind("Controller Controls", "Enabled", true, "Enable contextual controller chords through Valheim's ZInput action mappings."); ControllerModifier = config.Bind("Controller Controls", "ModifierAction", ValheimControllerAction.JoyAltKeys, "Valheim controller action held as the Runic modifier."); ControllerConfirm = config.Bind("Controller Controls", "ConfirmAction", ValheimControllerAction.JoyPlace, "Valheim controller action pressed with the modifier to confirm a visible pattern or matching replant preview."); ControllerCycle = config.Bind("Controller Controls", "CyclePatternAction", ValheimControllerAction.JoyPrevSnap, "Valheim controller action pressed with the modifier to cycle the planting pattern."); ControllerAreaHarvest = config.Bind("Controller Controls", "AreaHarvestAction", ValheimControllerAction.JoyUse, "Valheim controller action pressed with the modifier while interacting with an available Pickable."); ControllerPreviousEditorField = config.Bind("Controller Pattern Editor", "PreviousEditorFieldAction", ValheimControllerAction.JoyDPadUp, "Unmodified Valheim controller action that selects the previous visible pattern setting only during an active crop preview."); ControllerNextEditorField = config.Bind("Controller Pattern Editor", "NextEditorFieldAction", ValheimControllerAction.JoyDPadDown, "Unmodified Valheim controller action that selects the next visible pattern setting only during an active crop preview."); ControllerDecreaseEditorValue = config.Bind("Controller Pattern Editor", "DecreaseEditorValueAction", ValheimControllerAction.JoyDPadLeft, "Unmodified Valheim controller action that decreases the selected pattern setting only during an active crop preview."); ControllerIncreaseEditorValue = config.Bind("Controller Pattern Editor", "IncreaseEditorValueAction", ValheimControllerAction.JoyDPadRight, "Unmodified Valheim controller action that increases the selected pattern setting only during an active crop preview."); } internal static AgricultureControllerBindings CurrentControllerBindings() { return new AgricultureControllerBindings(ControllerModifier.Value, ControllerConfirm.Value, ControllerCycle.Value, ControllerAreaHarvest.Value, ControllerPreviousEditorField.Value, ControllerNextEditorField.Value, ControllerDecreaseEditorValue.Value, ControllerIncreaseEditorValue.Value); } internal static int LegacyCircleDimension(float radius, float spacing) { if (float.IsNaN(radius) || float.IsInfinity(radius) || radius <= 0f) { throw new ArgumentOutOfRangeException("radius"); } if (float.IsNaN(spacing) || float.IsInfinity(spacing) || spacing <= 0f) { throw new ArgumentOutOfRangeException("spacing"); } int num = (int)Math.Round(radius / spacing, MidpointRounding.AwayFromZero); return Math.Max(1, Math.Min(49, num * 2 + 1)); } internal static bool ShouldMapLegacyCircleRadius(bool migrationApplied, int rows, int columns) { if (!migrationApplied && rows == 5) { return columns == 5; } return false; } private static void ApplyGridAndCompactHudMigration(ConfigFile config) { if (GridAndCompactHudMigrationApplied.Value) { return; } bool saveOnConfigSet = config.SaveOnConfigSet; try { config.SaveOnConfigSet = false; Pattern.Value = PlantingGridPolicy.MigrateToDefaultGrid(migrationAlreadyApplied: false, Pattern.Value); if (Math.Abs(ControlBarScale.Value - 1f) < 0.0001f) { ControlBarScale.Value = 0.9f; } GridAndCompactHudMigrationApplied.Value = true; config.Save(); } finally { config.SaveOnConfigSet = saveOnConfigSet; } } } [BepInPlugin("chazman.RunicAgriculture", "Runic Agriculture", "1.0.0")] public sealed class Plugin : BaseUnityPlugin { public const string Guid = "chazman.RunicAgriculture"; public const string Name = "Runic Agriculture"; public const string Version = "1.0.0"; public const string ModuleId = "runic.agriculture"; public const string ProtocolVersion = "1.0"; private readonly List _keybindingRegistrations = new List(); private readonly KeybindingConflictRegistry _keybindings = new KeybindingConflictRegistry(); private Harmony _harmony; private AgricultureRuntime _runtime; internal static Plugin Instance { get; private set; } internal AgricultureRuntime Runtime => _runtime; internal ManualLogSource Log => ((BaseUnityPlugin)this).Logger; private void Awake() { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown Instance = this; AgricultureConfig.Bind(((BaseUnityPlugin)this).Config); ((BaseUnityPlugin)this).Config.SettingChanged += OnSettingChanged; try { AgriculturePatternService patternService = new AgriculturePatternService(); _runtime = new AgricultureRuntime(patternService, ((BaseUnityPlugin)this).Logger); _harmony = new Harmony("chazman.RunicAgriculture"); _harmony.PatchAll(typeof(Plugin).Assembly); RegisterKeybindings(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Runic Agriculture v1.0.0 ready: bounded pattern preview, native owner-local planting, exact Pickable area harvest, replant offers, and live controls."); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Runic Agriculture configuration: " + _runtime.ConfigurationSummary())); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Runic Agriculture controls: " + _runtime.ControlSummary())); } catch (Exception ex) { ShutdownRuntime(); ((BaseUnityPlugin)this).Logger.LogError((object)("Runic Agriculture startup failed; vanilla agriculture remains unchanged. " + ex.GetType().Name + ": " + ex.Message)); } } private void OnDestroy() { ((BaseUnityPlugin)this).Config.SettingChanged -= OnSettingChanged; ShutdownRuntime(); Instance = null; } private void OnSettingChanged(object sender, SettingChangedEventArgs arguments) { try { object obj; if (arguments == null) { obj = null; } else { ConfigEntryBase changedSetting = arguments.ChangedSetting; obj = ((changedSetting != null) ? changedSetting.Definition : null); } ConfigDefinition val = (ConfigDefinition)obj; if (val == (ConfigDefinition)null || val.Section == "Controls" || val.Section == "Pattern Editing Controls" || val.Section == "Controller Controls" || val.Section == "Controller Pattern Editor") { RegisterKeybindings(); } string changedSetting2 = ((val == (ConfigDefinition)null) ? "unknown setting" : (val.Section + "/" + val.Key)); _runtime?.OnConfigurationChanged(changedSetting2); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Agriculture configuration refresh failed: " + ex.Message)); } } private void RegisterKeybindings() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) DisposeKeybindings(); RegisterKeybinding("confirm-pattern", "Confirm planting pattern", AgricultureConfig.ConfirmPattern.Value); RegisterKeybinding("cycle-pattern", "Cycle planting pattern", AgricultureConfig.CyclePattern.Value); RegisterKeybinding("area-harvest", "Area harvest", AgricultureConfig.AreaHarvest.Value); RegisterKeybinding("confirm-replant", "Confirm replant offer", AgricultureConfig.ConfirmReplant.Value); RegisterKeybinding("increase-rows", "Increase planting rows", AgricultureConfig.IncreaseRows.Value); RegisterKeybinding("decrease-rows", "Decrease planting rows", AgricultureConfig.DecreaseRows.Value); RegisterKeybinding("increase-columns", "Increase planting columns", AgricultureConfig.IncreaseColumns.Value); RegisterKeybinding("decrease-columns", "Decrease planting columns", AgricultureConfig.DecreaseColumns.Value); RegisterKeybinding("toggle-shape-side", "Switch planting shape side", AgricultureConfig.ToggleShapeSide.Value); RegisterKeybinding("decrease-left-pinch", "Widen trapezoid left edge", AgricultureConfig.DecreaseLeftPinch.Value); RegisterKeybinding("increase-left-pinch", "Pinch trapezoid left edge", AgricultureConfig.IncreaseLeftPinch.Value); RegisterKeybinding("decrease-right-pinch", "Widen trapezoid right edge", AgricultureConfig.DecreaseRightPinch.Value); RegisterKeybinding("increase-right-pinch", "Pinch trapezoid right edge", AgricultureConfig.IncreaseRightPinch.Value); ConfigEntry controllerEnabled = AgricultureConfig.ControllerEnabled; if (controllerEnabled != null && controllerEnabled.Value) { AgricultureControllerBindings agricultureControllerBindings = AgricultureConfig.CurrentControllerBindings(); if (!agricultureControllerBindings.TryValidate(out var problem)) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Agriculture controller bindings were not registered: " + problem + ".")); return; } RegisterControllerBinding("controller-confirm", "Confirm planting/replant preview (controller)", agricultureControllerBindings.Confirm, agricultureControllerBindings.Modifier); RegisterControllerBinding("controller-cycle", "Cycle planting pattern (controller)", agricultureControllerBindings.Cycle, agricultureControllerBindings.Modifier); RegisterControllerBinding("controller-area-harvest", "Area harvest (controller)", agricultureControllerBindings.AreaHarvest, agricultureControllerBindings.Modifier); RegisterControllerControl("controller-editor-previous", "Previous planting setting (controller)", agricultureControllerBindings.PreviousEditorField); RegisterControllerControl("controller-editor-next", "Next planting setting (controller)", agricultureControllerBindings.NextEditorField); RegisterControllerControl("controller-editor-decrease", "Decrease planting setting (controller)", agricultureControllerBindings.DecreaseEditorValue); RegisterControllerControl("controller-editor-increase", "Increase planting setting (controller)", agricultureControllerBindings.IncreaseEditorValue); } } private void RegisterKeybinding(string id, string name, KeyboardShortcut shortcut) { //IL_0002: 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_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) if ((int)((KeyboardShortcut)(ref shortcut)).MainKey == 0) { return; } List list = new List(); IEnumerable modifiers = ((KeyboardShortcut)(ref shortcut)).Modifiers; if (modifiers != null) { foreach (KeyCode item in modifiers) { list.Add(((object)item/*cast due to .constrained prefix*/).ToString()); } } _keybindingRegistrations.Add(_keybindings.Register(new KeybindingDescriptor("runic.agriculture", id, name, new InputChord("keyboard", ((object)((KeyboardShortcut)(ref shortcut)).MainKey/*cast due to .constrained prefix*/).ToString(), list), "agriculture"))); } private void RegisterControllerBinding(string id, string name, ValheimControllerAction primary, ValheimControllerAction modifier) { _keybindingRegistrations.Add(_keybindings.Register(new KeybindingDescriptor("runic.agriculture", id, name, new InputChord("controller", primary.ToString(), new string[1] { modifier.ToString() }), "agriculture"))); } private void RegisterControllerControl(string id, string name, ValheimControllerAction primary) { _keybindingRegistrations.Add(_keybindings.Register(new KeybindingDescriptor("runic.agriculture", id, name, new InputChord("controller", primary.ToString(), Array.Empty()), "agriculture"))); } private void ShutdownRuntime() { DisposeKeybindings(); try { _runtime?.Dispose(); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Agriculture runtime cleanup failed: " + ex.Message)); } _runtime = null; try { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } catch (Exception ex2) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Agriculture Harmony cleanup failed: " + ex2.Message)); } _harmony = null; } private void DisposeKeybindings() { for (int num = _keybindingRegistrations.Count - 1; num >= 0; num--) { try { _keybindingRegistrations[num].Dispose(); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Keybinding cleanup failed: " + ex.Message)); } } _keybindingRegistrations.Clear(); } } } namespace RunicAgriculture.Integration { internal readonly struct AgricultureHintRow { internal string Key { get; } internal string Action { get; } internal AgricultureHintRow(string key, string action) { Key = key ?? string.Empty; Action = action ?? string.Empty; } } internal sealed class AgricultureControlBarContent { internal IReadOnlyList Rows { get; } internal AgricultureControlBarContent(params AgricultureHintRow[] rows) { Rows = rows ?? Array.Empty(); } } internal sealed class AgricultureControlBar : IDisposable { private readonly Dictionary _originalText = new Dictionary(); private GameObject _root; private Vector3 _originalScale; private bool _hasOriginalScale; internal bool Apply(KeyHints hints, AgricultureControlBarContent content, float requestedScale) { //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) GameObject val = (((Object)(object)hints != (Object)null) ? hints.m_buildHints : null); if ((Object)(object)val == (Object)null || content == null || content.Rows.Count == 0) { Restore(); return false; } if (_root != val) { Restore(); _root = val; _originalScale = val.transform.localScale; _hasOriginalScale = true; TMP_Text[] componentsInChildren = val.GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { if ((Object)(object)componentsInChildren[i] != (Object)null && !_originalText.ContainsKey(componentsInChildren[i])) { _originalText.Add(componentsInChildren[i], componentsInChildren[i].text ?? string.Empty); } } } float num = Mathf.Clamp(requestedScale, 0.75f, 1.75f); val.transform.localScale = Vector3.Scale(_originalScale, new Vector3(num, num, num)); TMP_Text[] array = (TMP_Text[])(object)new TMP_Text[5] { (TMP_Text)hints.m_buildMenuKey, (TMP_Text)hints.m_buildRotateKey, (TMP_Text)hints.m_buildAlternativePlacingKey, (TMP_Text)hints.m_dodgeKey, (TMP_Text)hints.m_cycleSnapKey }; HashSet knownKeys = new HashSet(array.Where((TMP_Text value) => (Object)(object)value != (Object)null)); int num2 = 0; int num3 = Math.Min(array.Length, content.Rows.Count); for (int num4 = 0; num4 < num3; num4++) { TMP_Text val2 = array[num4]; if (!((Object)(object)val2 == (Object)null)) { val2.text = content.Rows[num4].Key; TMP_Text val3 = FindActionLabel(val2, val.transform, knownKeys); if ((Object)(object)val3 != (Object)null) { val3.text = content.Rows[num4].Action; } num2++; } } return num2 > 0; } internal void Restore() { //IL_0073: Unknown result type (might be due to invalid IL or missing references) foreach (KeyValuePair item in _originalText) { if ((Object)(object)item.Key != (Object)null) { item.Key.text = item.Value; } } if ((Object)(object)_root != (Object)null && _hasOriginalScale) { _root.transform.localScale = _originalScale; } _originalText.Clear(); _root = null; _hasOriginalScale = false; } public void Dispose() { Restore(); } private static TMP_Text FindActionLabel(TMP_Text key, Transform root, ISet knownKeys) { Transform parent = key.transform.parent; while ((Object)(object)parent != (Object)null) { TMP_Text[] componentsInChildren = ((Component)parent).GetComponentsInChildren(true); int num = 0; for (int i = 0; i < componentsInChildren.Length; i++) { if (knownKeys.Contains(componentsInChildren[i])) { num++; } } if (num == 1 && componentsInChildren.Length >= 2) { TMP_Text val = null; foreach (TMP_Text val2 in componentsInChildren) { if (!((Object)(object)val2 == (Object)null) && val2 != key && !knownKeys.Contains(val2) && ((Object)(object)val == (Object)null || (val2.text?.Length ?? 0) > (val.text?.Length ?? 0))) { val = val2; } } if ((Object)(object)val != (Object)null) { return val; } } if (parent == root) { break; } parent = parent.parent; } return null; } } internal static class AgricultureInputConsumption { private static int _sampleFrame = -1; private static int _wheelFrame = -1; private static int _buildMenuFrame = -1; private static int _placeFrame = -1; internal static void BeginSample(int frame) { if (_sampleFrame != frame) { _sampleFrame = frame; _wheelFrame = -1; _buildMenuFrame = -1; _placeFrame = -1; } } internal static void ConsumeWheel(int frame) { _wheelFrame = frame; } internal static void ConsumeBuildMenu(int frame) { _buildMenuFrame = frame; } internal static void ConsumePlace(int frame) { _placeFrame = frame; } internal static bool ShouldSuppressWheel(int frame) { return _wheelFrame == frame; } internal static bool ShouldSuppressBuildMenu(string action, int frame) { if (_buildMenuFrame == frame) { return string.Equals(action, "BuildMenu", StringComparison.Ordinal); } return false; } internal static bool ShouldSuppressPlace(string action, int frame) { if (_placeFrame == frame) { return string.Equals(action, "Attack", StringComparison.Ordinal); } return false; } internal static void Reset() { _sampleFrame = -1; _wheelFrame = -1; _buildMenuFrame = -1; _placeFrame = -1; } } [HarmonyPatch(typeof(ZInput), "GetMouseScrollWheel")] internal static class AgricultureConsumedMouseWheelPatch { [HarmonyPriority(800)] [HarmonyBefore(new string[] { "chazman.RunicPrecisionBuildTool" })] private static bool Prefix(ref float __result) { if (!AgricultureInputConsumption.ShouldSuppressWheel(Time.frameCount)) { return true; } __result = 0f; return false; } } [HarmonyPatch(typeof(ZInput), "GetButtonDown", new Type[] { typeof(string) })] internal static class AgricultureConsumedBuildMenuPatch { [HarmonyPriority(800)] [HarmonyBefore(new string[] { "chazman.RunicPrecisionBuildTool" })] private static bool Prefix(string name, ref bool __result) { if (!AgricultureInputConsumption.ShouldSuppressBuildMenu(name, Time.frameCount) && !AgricultureInputConsumption.ShouldSuppressPlace(name, Time.frameCount)) { return true; } __result = false; return false; } } [HarmonyPatch(typeof(KeyHints), "UpdateHints")] internal static class AgricultureBuildHintsReplacementPatch { [HarmonyPostfix] [HarmonyPriority(0)] [HarmonyAfter(new string[] { "chazman.RunicPrecisionBuildTool" })] private static void Postfix(KeyHints __instance) { try { Plugin.Instance?.Runtime?.ApplyBuildHintsReplacement(__instance); } catch (Exception ex) { Plugin instance = Plugin.Instance; if (instance != null) { instance.Log.LogError((object)("Agriculture build-hint replacement failed closed: " + ex)); } Plugin.Instance?.Runtime?.DisableForSession("agriculture.placement-failed"); } } } internal sealed class AgricultureRuntime : IDisposable { private sealed class MutationScope : IDisposable { private AgricultureRuntime _owner; internal MutationScope(AgricultureRuntime owner) { _owner = owner; } public void Dispose() { AgricultureRuntime agricultureRuntime = Interlocked.Exchange(ref _owner, null); if (agricultureRuntime != null) { Interlocked.Exchange(ref agricultureRuntime._mutationActive, 0); } } } private sealed class PreviewSnapshot { internal string CropId { get; } internal Piece Piece { get; } internal Quaternion Rotation { get; } internal IReadOnlyList Positions { get; } internal bool IsReplant { get; } internal PreviewSnapshot(string cropId, Piece piece, Quaternion rotation, IReadOnlyList positions, bool isReplant) { //IL_0015: 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) CropId = cropId; Piece = piece; Rotation = rotation; Positions = positions; IsReplant = isReplant; } } private sealed class HarvestPickableCandidate { internal Pickable Pickable { get; } internal string PrefabName { get; } internal int PrefabHash { get; } internal ZDOID ZdoId { get; } internal Vector3 Position { get; } internal HarvestPickableCandidate(Pickable pickable, string prefabName, int prefabHash, ZDOID zdoId, Vector3 position) { //IL_001c: 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_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) Pickable = pickable; PrefabName = prefabName; PrefabHash = prefabHash; ZdoId = zdoId; Position = position; } } private const int UnlimitedActions = 1000000; private readonly IAgriculturePatternService _patternService; private readonly ManualLogSource _log; private readonly ValheimPlacementValidator _validator = new ValheimPlacementValidator(); private readonly PreviewPool _previewPool = new PreviewPool(1600); private readonly ReplantConfirmation _replant = new ReplantConfirmation(25); private readonly Collider[] _alignmentHits = (Collider[])(object)new Collider[96]; private readonly Collider[] _harvestHits = (Collider[])(object)new Collider[160]; private readonly Dictionary _matureToPlant = new Dictionary(StringComparer.Ordinal); private readonly AgricultureControlBar _controlBar = new AgricultureControlBar(); private PreviewSnapshot _lastPreview; private bool _disabledForSession; private float _nextPreviewUpdate; private bool _controllerActionsVerified; private string _controllerPathSignature; private string _lastControllerProblem; private int _controllerHarvestRequestFrame = -1; private ControllerEditorField _controllerEditorField; private int _previewValidCount; private int _previewGroundValidCount; private int _previewTotalCount; private string _previewIssueSummary = string.Empty; private string _previewLimitSummary = string.Empty; private string _previewBatchActionSummary = string.Empty; private float _patternYawDegrees; private int _mutationActive; private Piece _seedBudgetPiece; private Vector3 _seedBudgetPlayerPosition; private int _seedBudgetValue; private float _seedBudgetExpiresAt; internal bool IsOperational { get { if (!_disabledForSession) { return AgricultureConfig.Enabled?.Value ?? false; } return false; } } internal AgricultureRuntime(IAgriculturePatternService patternService, ManualLogSource log) { _patternService = patternService ?? throw new ArgumentNullException("patternService"); _log = log ?? throw new ArgumentNullException("log"); ValheimAccess.Verify(); } internal void DisableForSession(string reasonCode) { if (!_disabledForSession) { _disabledForSession = true; _lastPreview = null; _previewPool.Hide(); RestoreBuildHintsIfOwned(); AgricultureInputConsumption.Reset(); Message(Player.m_localPlayer, "Runic Agriculture disabled for this session after an error. Check LogOutput.log."); Publish(reasonCode, "Agriculture runtime disabled after an unexpected failure.", "Restart Valheim, then check BepInEx/LogOutput.log before using batch actions."); } } internal string ConfigurationSummary() { return AgricultureFeedbackText.Configuration(AgricultureConfig.Enabled.Value, AgricultureConfig.Pattern.Value, AgricultureConfig.Rows.Value, AgricultureConfig.Columns.Value, AgricultureConfig.Spacing.Value, 1600, AgricultureConfig.HarvestRadius.Value, AgricultureConfig.MaximumHarvest.Value); } internal string ControlSummary() { //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: Unknown result type (might be due to invalid IL or missing references) //IL_01ec: Unknown result type (might be due to invalid IL or missing references) //IL_0208: Unknown result type (might be due to invalid IL or missing references) //IL_0224: Unknown result type (might be due to invalid IL or missing references) AgricultureControllerBindings agricultureControllerBindings = AgricultureConfig.CurrentControllerBindings(); string problem; string text = ((!AgricultureConfig.ControllerEnabled.Value) ? "controller controls disabled" : (agricultureControllerBindings.TryValidate(out problem) ? ("controller confirm " + agricultureControllerBindings.ConfirmChord + ", cycle " + agricultureControllerBindings.CycleChord + ", harvest " + agricultureControllerBindings.AreaHarvestChord + ", choose setting " + ControllerActionDisplay.Friendly(agricultureControllerBindings.PreviousEditorField) + "/" + ControllerActionDisplay.Friendly(agricultureControllerBindings.NextEditorField) + " unmodified in crop preview, adjust " + ControllerActionDisplay.Friendly(agricultureControllerBindings.DecreaseEditorValue) + "/" + ControllerActionDisplay.Friendly(agricultureControllerBindings.IncreaseEditorValue) + " unmodified in crop preview") : ("controller disabled by invalid bindings (" + problem + ")"))); return "keyboard plant Left Click, cycle " + ShortcutLabel(AgricultureConfig.CyclePattern.Value) + ", harvest " + ShortcutLabel(AgricultureConfig.AreaHarvest.Value) + ", replant " + ShortcutLabel(AgricultureConfig.ConfirmReplant.Value) + ", rows " + ShortcutLabel(AgricultureConfig.DecreaseRows.Value) + "/" + ShortcutLabel(AgricultureConfig.IncreaseRows.Value) + ", columns " + ShortcutLabel(AgricultureConfig.DecreaseColumns.Value) + "/" + ShortcutLabel(AgricultureConfig.IncreaseColumns.Value) + ", side " + ShortcutLabel(AgricultureConfig.ToggleShapeSide.Value) + ", trapezoid left " + ShortcutLabel(AgricultureConfig.DecreaseLeftPinch.Value) + "/" + ShortcutLabel(AgricultureConfig.IncreaseLeftPinch.Value) + ", right " + ShortcutLabel(AgricultureConfig.DecreaseRightPinch.Value) + "/" + ShortcutLabel(AgricultureConfig.IncreaseRightPinch.Value) + ", live wheel rotate Wheel, rows Alt+Wheel, columns Shift+Wheel, spacing Alt+Shift+Wheel, direct patterns Numpad 1-7, cycle Alt+BuildMenu; " + text + "."; } internal void OnConfigurationChanged(string changedSetting) { _lastPreview = null; _nextPreviewUpdate = 0f; _seedBudgetExpiresAt = 0f; _controllerEditorField = ControllerPatternEditor.Normalize(AgricultureConfig.Pattern.Value, _controllerEditorField); if (changedSetting != null && (changedSetting.StartsWith("Controller Controls/", StringComparison.Ordinal) || changedSetting.StartsWith("Controller Pattern Editor/", StringComparison.Ordinal))) { _controllerActionsVerified = false; _controllerPathSignature = null; _lastControllerProblem = null; } if (!IsOperational) { _previewPool.Hide(); } string text = ConfigurationSummary(); _log.LogInfo((object)("Runic Agriculture configuration changed (" + changedSetting + "): " + text)); _log.LogInfo((object)("Runic Agriculture controls after configuration change: " + ControlSummary())); Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null) { Message(localPlayer, "Runic Agriculture updated: " + text + "."); } AgricultureControllerBindings agricultureControllerBindings = AgricultureConfig.CurrentControllerBindings(); if (AgricultureConfig.ControllerEnabled.Value && !agricultureControllerBindings.TryValidate(out var problem)) { ReportControllerProblem(localPlayer, problem); } } internal void ApplyBuildHintsReplacement(KeyHints hints) { GameObject val = (((Object)(object)hints != (Object)null) ? hints.m_buildHints : null); Player localPlayer = Player.m_localPlayer; if ((Object)(object)val == (Object)null || !IsPlantingControlContext(localPlayer)) { _controlBar.Restore(); return; } if (!val.activeInHierarchy) { _controlBar.Restore(); return; } bool controller = false; try { controller = ZInput.IsGamepadActive(); } catch (Exception) { } _controlBar.Apply(hints, BuildControlBarContent(localPlayer, controller), AgricultureConfig.ControlBarScale.Value); } private AgricultureControlBarContent BuildControlBarContent(Player player, bool controller) { //IL_01bd: Unknown result type (might be due to invalid IL or missing references) PlantPattern value = AgricultureConfig.Pattern.Value; Piece val = ((player != null) ? player.GetSelectedPiece() : null); string text = (((Object)(object)val != (Object)null) ? val.m_name : string.Empty); try { if (!string.IsNullOrEmpty(text) && Localization.instance != null) { text = Localization.instance.Localize(text); } } catch (Exception) { } if (string.IsNullOrWhiteSpace(text)) { text = "Selected crop"; } string text2 = ((value == PlantPattern.Row) ? 1 : AgricultureConfig.Rows.Value) + " rows × " + AgricultureConfig.Columns.Value + " columns"; string text3 = ((_lastPreview == null) ? "preview loading" : (_previewValidCount + "/" + _previewTotalCount + " ready" + ((_previewGroundValidCount > _previewValidCount) ? ("; red: " + (_previewGroundValidCount - _previewValidCount) + " missing planting resources") : string.Empty) + (string.IsNullOrEmpty(_previewIssueSummary) ? string.Empty : ("; amber: " + _previewIssueSummary)) + (string.IsNullOrEmpty(_previewLimitSummary) ? string.Empty : ("; " + _previewLimitSummary)) + (string.IsNullOrEmpty(_previewBatchActionSummary) ? string.Empty : ("; " + _previewBatchActionSummary)))); bool flag = IsReplantPreviewForSelection(player); if (!controller) { return new AgricultureControlBarContent(new AgricultureHintRow(flag ? ShortcutLabel(AgricultureConfig.ConfirmReplant.Value) : "Mouse-1", (flag ? "Replant " : "Plant ") + text + " • " + value.ToString() + " • " + text3), new AgricultureHintRow("Alt + Wheel ↓ / ↑", "Rows − / + " + ((value == PlantPattern.Row) ? "1 (Row pattern)" : AgricultureConfig.Rows.Value.ToString(CultureInfo.InvariantCulture))), new AgricultureHintRow("Shift + Wheel ↓ / ↑", "Columns − / + " + AgricultureConfig.Columns.Value), new AgricultureHintRow("Wheel", "Rotate " + _patternYawDegrees.ToString("0.#", CultureInfo.InvariantCulture) + "° • spacing " + AgricultureConfig.Spacing.Value.ToString("0.0", CultureInfo.InvariantCulture) + " m"), new AgricultureHintRow("Alt + Mouse-2 / Num 2", "Pattern " + value.ToString() + " • " + text2 + " • change / select basic Grid")); } AgricultureControllerBindings agricultureControllerBindings = AgricultureConfig.CurrentControllerBindings(); string problem = string.Empty; if (!AgricultureConfig.ControllerEnabled.Value || !EnsureControllerActions(player) || !agricultureControllerBindings.TryValidate(out problem)) { string action = (AgricultureConfig.ControllerEnabled.Value ? ("Controller Agriculture bindings unavailable" + (string.IsNullOrWhiteSpace(problem) ? "." : (": " + problem + "."))) : "Controller Agriculture controls are disabled."); return new AgricultureControlBarContent(new AgricultureHintRow("Mouse-1", "Plant " + text + " — " + text3), new AgricultureHintRow("Alt + Wheel ↓ / ↑", "Rows − / + " + AgricultureConfig.Rows.Value), new AgricultureHintRow("Shift + Wheel ↓ / ↑", "Columns − / + " + AgricultureConfig.Columns.Value), new AgricultureHintRow("Wheel", "Rotate • spacing " + AgricultureConfig.Spacing.Value.ToString("0.0", CultureInfo.InvariantCulture) + " m"), new AgricultureHintRow("Alt + Shift + Wheel", action)); } if (flag) { return new AgricultureControlBarContent(new AgricultureHintRow(ValheimAccess.ControllerChordLabel(agricultureControllerBindings, agricultureControllerBindings.Confirm), "Replant " + text + " — " + text3)); } _controllerEditorField = ControllerPatternEditor.Normalize(value, _controllerEditorField); string text4 = ControllerEditorFieldLabel(value, _controllerEditorField); string key = ValheimAccess.ControllerControlLabel(agricultureControllerBindings.PreviousEditorField) + " / " + ValheimAccess.ControllerControlLabel(agricultureControllerBindings.NextEditorField); string key2 = ValheimAccess.ControllerControlLabel(agricultureControllerBindings.DecreaseEditorValue) + " / " + ValheimAccess.ControllerControlLabel(agricultureControllerBindings.IncreaseEditorValue); return new AgricultureControlBarContent(new AgricultureHintRow(ValheimAccess.ControllerChordLabel(agricultureControllerBindings, agricultureControllerBindings.Confirm), "Plant " + text + " • " + value.ToString() + " • " + text3), new AgricultureHintRow(ValheimAccess.ControllerChordLabel(agricultureControllerBindings, agricultureControllerBindings.Cycle), "Pattern " + value.ToString() + " " + text2), new AgricultureHintRow(key, "Choose " + text4), new AgricultureHintRow(key2, "Adjust " + text4), new AgricultureHintRow("", "Spacing " + AgricultureConfig.Spacing.Value.ToString("0.0", CultureInfo.InvariantCulture) + " m")); } internal void TickInput(Player player) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer || !((Character)player).IsOwner()) { return; } AgricultureInputConsumption.BeginSample(Time.frameCount); AgricultureControllerCollisionGuard.Poll(); if (!ValheimAccess.PlayerTakesInput(player)) { return; } bool keyboardCycle = ValheimAccess.ShortcutDown(AgricultureConfig.CyclePattern.Value); bool keyboardConfirmPattern = false; bool keyboardConfirmReplant = ValheimAccess.ShortcutDown(AgricultureConfig.ConfirmReplant.Value); PatternEditAction patternEditAction = ReadPatternEditAction(); int num = 0; bool flag = IsPlantPiece(player.GetSelectedPiece()); int num2; int num3; if (flag) { num2 = (IsPlantingControlContext(player) ? 1 : 0); if (num2 != 0) { num3 = ((!IsReplantPreviewForSelection(player)) ? 1 : 0); goto IL_008d; } } else { num2 = 0; } num3 = 0; goto IL_008d; IL_008d: bool flag2 = (byte)num3 != 0; PlantPattern? plantPattern = null; if (num2 != 0) { bool flag3 = ValheimAccess.KeyHeld((KeyCode)308, (KeyCode)307); bool flag4 = ValheimAccess.KeyHeld((KeyCode)304, (KeyCode)303); bool flag5 = ValheimAccess.KeyHeld((KeyCode)306, (KeyCode)305); if (flag2) { float num4 = ValheimAccess.MouseWheel(); AgricultureWheelTarget agricultureWheelTarget = AgricultureWheelRouter.Resolve(flag3, flag4, flag5, num4); if (agricultureWheelTarget != AgricultureWheelTarget.None) { AgricultureInputConsumption.ConsumeWheel(Time.frameCount); if (agricultureWheelTarget == AgricultureWheelTarget.Rotation) { num = ((num4 > 0f) ? 1 : (-1)); } else { patternEditAction = AgricultureWheelRouter.ToEditAction(agricultureWheelTarget, num4 > 0f); } } if (flag3 && !flag4 && !flag5 && ValheimAccess.ButtonDown("BuildMenu")) { AgricultureInputConsumption.ConsumeBuildMenu(Time.frameCount); keyboardCycle = true; } if (!flag3 && !flag4 && !flag5 && TryReadDirectPattern(out var pattern)) { plantPattern = pattern; } } if (!flag3 && !flag4 && !flag5 && ValheimAccess.ButtonDown("Attack")) { AgricultureInputConsumption.ConsumePlace(Time.frameCount); if (IsReplantPreviewForSelection(player)) { keyboardConfirmReplant = true; } else { keyboardConfirmPattern = true; } } } bool num5 = num2 != 0 && EnsureControllerActions(player); AgricultureControllerBindings agricultureControllerBindings = AgricultureConfig.CurrentControllerBindings(); bool flag6 = num5 && ValheimAccess.ControllerButtonHeldRaw(agricultureControllerBindings.Modifier); bool flag7 = flag6 && ValheimAccess.ControllerButtonDownRaw(agricultureControllerBindings.Cycle); bool flag8 = flag6 && ValheimAccess.ControllerButtonDownRaw(agricultureControllerBindings.Confirm); bool flag9 = num5 && !flag6 && flag2 && ValheimAccess.ControllerButtonDownRaw(agricultureControllerBindings.PreviousEditorField); bool flag10 = num5 && !flag6 && flag2 && ValheimAccess.ControllerButtonDownRaw(agricultureControllerBindings.NextEditorField); bool flag11 = num5 && !flag6 && flag2 && ValheimAccess.ControllerButtonDownRaw(agricultureControllerBindings.DecreaseEditorValue); bool flag12 = num5 && !flag6 && flag2 && ValheimAccess.ControllerButtonDownRaw(agricultureControllerBindings.IncreaseEditorValue); RoutedAgricultureAction routedAgricultureAction = AgricultureActionRouter.Resolve(new AgricultureInputFrame(keyboardCycle, keyboardConfirmPattern, keyboardConfirmReplant, flag7, flag8, _lastPreview != null && _lastPreview.IsReplant)); if (patternEditAction == PatternEditAction.None && num == 0 && routedAgricultureAction == RoutedAgricultureAction.None && !plantPattern.HasValue && !flag9 && !flag10 && !flag11 && !flag12) { return; } Trace("input routed to " + ((num != 0) ? "RotatePattern" : ((patternEditAction != PatternEditAction.None) ? patternEditAction.ToString() : routedAgricultureAction.ToString())) + ((flag7 || flag8 || flag9 || flag10 || flag11 || flag12) ? " from controller" : " from keyboard") + "."); if (!IsOperational) { Message(player, _disabledForSession ? "Runic Agriculture is disabled for this session after an error; check LogOutput.log." : "Runic Agriculture is disabled in Configuration Manager."); } else if (!flag) { Message(player, "Runic agriculture: select a crop with the cultivator first."); } else { if ((flag7 && !TryCaptureControllerGesture(player, agricultureControllerBindings.Cycle)) || (flag8 && !TryCaptureControllerGesture(player, agricultureControllerBindings.Confirm)) || (flag9 && !TryCaptureControllerEditorControl(player, agricultureControllerBindings.PreviousEditorField)) || (flag10 && !TryCaptureControllerEditorControl(player, agricultureControllerBindings.NextEditorField)) || (flag11 && !TryCaptureControllerEditorControl(player, agricultureControllerBindings.DecreaseEditorValue)) || (flag12 && !TryCaptureControllerEditorControl(player, agricultureControllerBindings.IncreaseEditorValue))) { return; } if (flag9 || flag10) { _controllerEditorField = ControllerPatternEditor.Move(AgricultureConfig.Pattern.Value, _controllerEditorField, flag10 ? 1 : (-1)); return; } if (flag11 || flag12) { patternEditAction = ControllerPatternEditor.ToEditAction(ControllerPatternEditor.Normalize(AgricultureConfig.Pattern.Value, _controllerEditorField), flag12); } if (num != 0) { RotatePattern(player, num); return; } if (patternEditAction != PatternEditAction.None) { Piece selectedPiece = player.GetSelectedPiece(); string b = PrefabIdentity.Of((selectedPiece != null) ? ((Component)selectedPiece).gameObject : null); if ((_lastPreview != null && _lastPreview.IsReplant) || (_replant.IsPending && string.Equals(_replant.CropId, b, StringComparison.Ordinal))) { Message(player, "Runic replant uses its saved harvest positions; shape editing resumes on the next normal planting preview."); } else { ApplyPatternEdit(player, patternEditAction); } return; } if (plantPattern.HasValue) { SetPattern(player, plantPattern.Value, "numpad"); return; } switch (routedAgricultureAction) { case RoutedAgricultureAction.CyclePattern: CyclePattern(player); break; case RoutedAgricultureAction.ConfirmReplant: ConfirmReplant(player); break; default: ConfirmPattern(player); break; } } } internal bool IsAreaHarvestRequested(Player player, GameObject targetObject) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) Pickable target = FindPickable(targetObject); if (!CanOfferAreaHarvest(player, target)) { return false; } if (ValheimAccess.ShortcutDown(AgricultureConfig.AreaHarvest.Value)) { return true; } if (!AgricultureConfig.ControllerEnabled.Value || !EnsureControllerActions(player)) { return false; } AgricultureControllerBindings agricultureControllerBindings = AgricultureConfig.CurrentControllerBindings(); int num; if (ValheimAccess.ControllerButtonHeldRaw(agricultureControllerBindings.Modifier)) { num = (ValheimAccess.ControllerButtonDownRaw(agricultureControllerBindings.AreaHarvest) ? 1 : 0); if (num != 0) { _controllerHarvestRequestFrame = Time.frameCount; } } else { num = 0; } return (byte)num != 0; } internal string HarvestControlHint() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) string text = ShortcutLabel(AgricultureConfig.AreaHarvest.Value); if (!AgricultureConfig.ControllerEnabled.Value) { return text; } AgricultureControllerBindings agricultureControllerBindings = AgricultureConfig.CurrentControllerBindings(); if (!EnsureControllerActions(Player.m_localPlayer) || !agricultureControllerBindings.TryValidate(out var _)) { return text; } return text + " or " + agricultureControllerBindings.AreaHarvestChord; } internal void UpdatePreview(Player player) { //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) if (!IsOperational || (Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer || !((Character)player).IsOwner()) { _lastPreview = null; ClearControlBarPreview(); _previewPool.Hide(); return; } Piece selectedPiece = player.GetSelectedPiece(); GameObject placementGhost = ValheimAccess.GetPlacementGhost(player); if (!IsPlantPiece(selectedPiece) || (Object)(object)placementGhost == (Object)null || !placementGhost.activeInHierarchy) { _lastPreview = null; ClearControlBarPreview(); _previewPool.Hide(); } else { if (Time.unscaledTime < _nextPreviewUpdate) { return; } float num = MinimumSpacingFor(selectedPiece); if (AgricultureConfig.Spacing.Value < num) { AgricultureConfig.Spacing.Value = num; } string text = PrefabIdentity.Of(((Component)selectedPiece).gameObject); Quaternion rotation = ResolveAlignment(player, selectedPiece, placementGhost.transform.position); List list = BuildRequestedPositions(player, selectedPiece, placementGhost.transform.position, rotation, text); if (list.Count == 0) { _nextPreviewUpdate = Time.unscaledTime + 0.1f; _lastPreview = null; ClearControlBarPreview(); _previewPool.Hide(); return; } bool isReplant = _replant.IsPending && string.Equals(_replant.CropId, text, StringComparison.Ordinal); List list2 = BuildValidatedPreview(player, selectedPiece, list, rotation); _lastPreview = new PreviewSnapshot(text, selectedPiece, rotation, list, isReplant); _previewTotalCount = list2.Count; _previewValidCount = 0; _previewGroundValidCount = 0; Dictionary dictionary = new Dictionary(StringComparer.Ordinal); for (int i = 0; i < list2.Count; i++) { RuntimePreviewPosition runtimePreviewPosition = list2[i]; if (runtimePreviewPosition.IsGroundValid) { _previewGroundValidCount++; if (runtimePreviewPosition.IsValid) { _previewValidCount++; } } else { string key = runtimePreviewPosition.ReasonCode ?? "agriculture.placement-failed"; dictionary.TryGetValue(key, out var value); dictionary[key] = value + 1; } } _previewIssueSummary = PreviewIssueSummary(dictionary); _previewBatchActionSummary = BatchActionIssue(player, selectedPiece); _previewPool.Show(placementGhost, list2); _nextPreviewUpdate = Time.unscaledTime + PreviewRefreshInterval(list2.Count); } } internal bool TryAreaHarvest(Player player, GameObject targetObject) { //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_033b: Unknown result type (might be due to invalid IL or missing references) //IL_0398: 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_0280: 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_028a: 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_02d9: Unknown result type (might be due to invalid IL or missing references) if (!IsOperational || (Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer || !((Character)player).IsOwner()) { return false; } bool flag = _controllerHarvestRequestFrame == Time.frameCount; _controllerHarvestRequestFrame = -1; Pickable target = FindPickable(targetObject); if (!CanOfferAreaHarvest(player, target)) { Trace("area harvest preserved the original interaction because the targeted Pickable was unavailable or access was denied."); return false; } if (!TryCaptureHarvestCandidate(target, out var aimed)) { if ((Object)(object)target != (Object)null) { Message(player, "Runic harvest: this Pickable is not currently available."); } Trace("area harvest preserved the original interaction because the targeted Pickable did not satisfy the live network/availability contract."); return false; } if (flag && !TryCaptureControllerGesture(player, AgricultureConfig.CurrentControllerBindings().AreaHarvest)) { return false; } string prefabName = aimed.PrefabName; string text = FindPlantForMature(prefabName); float num = Mathf.Clamp(AgricultureConfig.HarvestRadius.Value, 1f, 8f); int num2 = Mathf.Clamp(AgricultureConfig.MaximumHarvest.Value, 1, 25); int num3 = Physics.OverlapSphereNonAlloc(((Component)target).transform.position, num, _harvestHits, -1, (QueryTriggerInteraction)2); HashSet hashSet = new HashSet(); List list = new List(Math.Min(num3 + 1, _harvestHits.Length + 1)); hashSet.Add(target); list.Add(aimed); for (int i = 0; i < Math.Min(num3, _harvestHits.Length); i++) { Collider val = _harvestHits[i]; _harvestHits[i] = null; Pickable val2 = FindPickable(((Object)(object)val != (Object)null) ? ((Component)val).gameObject : null); if (!((Object)(object)val2 == (Object)null) && hashSet.Add(val2) && TryCaptureHarvestCandidate(val2, out var candidate) && HarvestPickableBatchPolicy.IsExactPrefab(aimed.PrefabName, aimed.PrefabHash, candidate.PrefabName, candidate.PrefabHash)) { list.Add(candidate); } } list.Sort(delegate(HarvestPickableCandidate left, HarvestPickableCandidate right) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_001a: 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_0024: 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_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006c: 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_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) bool leftIsAimed = left.Pickable == target; Vector3 val4 = left.Position - aimed.Position; float sqrMagnitude = ((Vector3)(ref val4)).sqrMagnitude; ZDOID zdoId = left.ZdoId; long userID = ((ZDOID)(ref zdoId)).UserID; zdoId = left.ZdoId; uint iD = ((ZDOID)(ref zdoId)).ID; bool rightIsAimed = right.Pickable == target; val4 = right.Position - aimed.Position; float sqrMagnitude2 = ((Vector3)(ref val4)).sqrMagnitude; zdoId = right.ZdoId; long userID2 = ((ZDOID)(ref zdoId)).UserID; zdoId = right.ZdoId; return HarvestPickableBatchPolicy.Compare(leftIsAimed, sqrMagnitude, userID, iD, rightIsAimed, sqrMagnitude2, userID2, ((ZDOID)(ref zdoId)).ID); }); List list2 = new List(num2); int num4 = 0; bool flag2 = false; for (int num5 = 0; num5 < list.Count; num5++) { if (num4 >= num2) { break; } if (!TryCaptureHarvestCandidate(list[num5].Pickable, out var candidate2) || !HarvestPickableBatchPolicy.IsExactPrefab(aimed.PrefabName, aimed.PrefabHash, candidate2.PrefabName, candidate2.PrefabHash)) { continue; } Vector3 val3 = candidate2.Position - aimed.Position; if (!(((Vector3)(ref val3)).sqrMagnitude > num * num)) { if (!PrivateArea.CheckAccess(candidate2.Position, 0f, false, false)) { flag2 |= candidate2.Pickable == target; continue; } candidate2.Pickable.Interact((Humanoid)(object)player, false, false); list2.Add(candidate2.Position); num4++; } } if (num4 == 0) { Message(player, "Runic harvest: no currently available, permitted Pickables."); Trace("area harvest found candidates but sent no permitted pick requests; " + (flag2 ? "the aimed Pickable was ward-denied." : "the original interaction remains available.")); return flag2; } bool authorized = PrivateArea.CheckAccess(aimed.Position, 0f, false, false); if (HarvestPickableBatchPolicy.OffersReplant(AgricultureConfig.OfferReplantPreview.Value, authorized, text)) { _replant.Offer(text, list2); Message(player, "Runic harvest: " + num4 + " request(s) sent. Select the matching seed, then confirm with " + ShortcutLabel(AgricultureConfig.ConfirmReplant.Value) + (AgricultureConfig.ControllerEnabled.Value ? (" or " + AgricultureConfig.CurrentControllerBindings().ConfirmChord) : string.Empty) + "."); } else { _replant.Clear(); Message(player, "Runic harvest: " + num4 + " request(s) sent."); } Trace("area harvest sent " + num4 + " owner-validated pick request(s) for '" + prefabName + "'."); return true; } internal bool CanOfferAreaHarvest(Player player, Pickable target) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) if (!IsOperational || (Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer || !((Character)player).IsOwner() || (Object)(object)target == (Object)null || !TryCaptureHarvestCandidate(target, out var candidate)) { return false; } return HarvestPickableBatchPolicy.AllowsAreaHarvest(PrivateArea.CheckAccess(candidate.Position, 0f, false, false)); } public void Dispose() { Interlocked.Exchange(ref _mutationActive, 0); _lastPreview = null; ClearControlBarPreview(); _replant.Clear(); _controllerHarvestRequestFrame = -1; RestoreBuildHintsIfOwned(); AgricultureInputConsumption.Reset(); AgricultureControllerCollisionGuard.Reset(); _controlBar.Dispose(); _previewPool.Dispose(); NearbySeedContainerIndex.Clear(); } private void CyclePattern(Player player) { SetPattern(player, PatternEditor.Next(AgricultureConfig.Pattern.Value), "cycle"); } private void SetPattern(Player player, PlantPattern pattern, string source) { if (Enum.IsDefined(typeof(PlantPattern), pattern)) { AgricultureConfig.Pattern.Value = pattern; _controllerEditorField = ControllerPatternEditor.Normalize(pattern, _controllerEditorField); Trace("planting pattern selected as " + pattern.ToString() + " from " + source + "."); } } private static PatternEditAction ReadPatternEditAction() { //IL_0005: 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_002b: 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_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) if (ValheimAccess.ShortcutDown(AgricultureConfig.IncreaseRows.Value)) { return PatternEditAction.IncreaseRows; } if (ValheimAccess.ShortcutDown(AgricultureConfig.DecreaseRows.Value)) { return PatternEditAction.DecreaseRows; } if (ValheimAccess.ShortcutDown(AgricultureConfig.IncreaseColumns.Value)) { return PatternEditAction.IncreaseColumns; } if (ValheimAccess.ShortcutDown(AgricultureConfig.DecreaseColumns.Value)) { return PatternEditAction.DecreaseColumns; } if (ValheimAccess.ShortcutDown(AgricultureConfig.ToggleShapeSide.Value)) { return PatternEditAction.ToggleSide; } if (ValheimAccess.ShortcutDown(AgricultureConfig.DecreaseLeftPinch.Value)) { return PatternEditAction.DecreaseLeftPinch; } if (ValheimAccess.ShortcutDown(AgricultureConfig.IncreaseLeftPinch.Value)) { return PatternEditAction.IncreaseLeftPinch; } if (ValheimAccess.ShortcutDown(AgricultureConfig.DecreaseRightPinch.Value)) { return PatternEditAction.DecreaseRightPinch; } if (ValheimAccess.ShortcutDown(AgricultureConfig.IncreaseRightPinch.Value)) { return PatternEditAction.IncreaseRightPinch; } return PatternEditAction.None; } private void RotatePattern(Player player, int direction) { float num = ValheimAccess.PlaceRotationDegrees(player); if (float.IsNaN(num) || float.IsInfinity(num) || num <= 0f) { num = 22.5f; } _patternYawDegrees = Mathf.Repeat(_patternYawDegrees + ((direction > 0) ? num : (0f - num)), 360f); _lastPreview = null; _nextPreviewUpdate = 0f; Message(player, "Runic shape: rotated to " + _patternYawDegrees.ToString("0.#", CultureInfo.InvariantCulture) + "°."); } private void ApplyPatternEdit(Player player, PatternEditAction action) { PlantPattern value = AgricultureConfig.Pattern.Value; PatternEditState patternEditState = new PatternEditState(AgricultureConfig.Rows.Value, AgricultureConfig.Columns.Value, AgricultureConfig.MirrorShape.Value, AgricultureConfig.TrapezoidLeftPinch.Value, AgricultureConfig.TrapezoidRightPinch.Value, AgricultureConfig.Spacing.Value); PatternEditState patternEditState2 = PatternEditor.Apply(value, patternEditState, action); float num = MinimumSpacingFor(player.GetSelectedPiece()); if (patternEditState2.Spacing < (double)num) { patternEditState2 = new PatternEditState(patternEditState2.Rows, patternEditState2.Columns, patternEditState2.Mirrored, patternEditState2.LeftPinch, patternEditState2.RightPinch, num); } if (patternEditState2.Equals(patternEditState)) { string text = ((value == PlantPattern.Row && (action == PatternEditAction.IncreaseRows || action == PatternEditAction.DecreaseRows)) ? "Row has one forward row; use the column controls to change its length." : ((action == PatternEditAction.ToggleSide && !PatternEditor.SupportsMirror(value)) ? (value.ToString() + " is symmetric; side switching applies to RightTriangle, HalfCircle, and Trapezoid.") : (((action == PatternEditAction.DecreaseLeftPinch || action == PatternEditAction.IncreaseLeftPinch || action == PatternEditAction.DecreaseRightPinch || action == PatternEditAction.IncreaseRightPinch) && value != PlantPattern.Trapezoid) ? "Independent taper controls apply only to Trapezoid." : ((action == PatternEditAction.DecreaseSpacing && patternEditState.Spacing <= (double)num) ? ("That crop requires at least " + num.ToString("0.0", CultureInfo.InvariantCulture) + "m spacing.") : "That pattern setting is already at its safe limit.")))); Message(player, "Runic shape: " + text); return; } if (patternEditState2.Rows != patternEditState.Rows) { AgricultureConfig.Rows.Value = patternEditState2.Rows; } else if (patternEditState2.Columns != patternEditState.Columns) { AgricultureConfig.Columns.Value = patternEditState2.Columns; } else if (patternEditState2.Mirrored != patternEditState.Mirrored) { AgricultureConfig.MirrorShape.Value = patternEditState2.Mirrored; } else if (!patternEditState2.LeftPinch.Equals(patternEditState.LeftPinch)) { AgricultureConfig.TrapezoidLeftPinch.Value = (float)patternEditState2.LeftPinch; } else if (!patternEditState2.RightPinch.Equals(patternEditState.RightPinch)) { AgricultureConfig.TrapezoidRightPinch.Value = (float)patternEditState2.RightPinch; } else if (!patternEditState2.Spacing.Equals(patternEditState.Spacing)) { AgricultureConfig.Spacing.Value = (float)patternEditState2.Spacing; } string text2 = value.ToString() + " " + ((value == PlantPattern.Row) ? (patternEditState2.Columns + " columns") : (patternEditState2.Rows + "x" + patternEditState2.Columns)); if (PatternEditor.SupportsMirror(value)) { text2 = text2 + ", " + PatternEditor.OrientationLabel(value, patternEditState2.Mirrored).ToLowerInvariant(); } if (value == PlantPattern.Trapezoid) { text2 = text2 + ", taper L " + Mathf.RoundToInt((float)patternEditState2.LeftPinch * 100f) + "% / R " + Mathf.RoundToInt((float)patternEditState2.RightPinch * 100f) + "%"; } text2 = text2 + ", spacing " + patternEditState2.Spacing.ToString("0.0", CultureInfo.InvariantCulture) + "m"; Message(player, "Runic shape: " + text2 + "."); Trace("live pattern edit " + action.ToString() + " applied to " + value.ToString() + "."); } private void ConfirmPattern(Player player) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) PreviewSnapshot lastPreview = _lastPreview; if (lastPreview == null || lastPreview.IsReplant) { Message(player, (lastPreview != null) ? "Runic planting: this is a replant preview; use the replant confirmation control." : "Runic planting: no preview is ready. Select a crop and aim at plantable ground."); Trace("pattern confirmation produced no mutation because no ordinary preview was ready."); } else { ExecuteBatch(player, lastPreview.Piece, lastPreview.CropId, lastPreview.Positions, lastPreview.Rotation); } } private void ConfirmReplant(Player player) { //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) Piece selectedPiece = player.GetSelectedPiece(); if (!IsPlantPiece(selectedPiece)) { Message(player, "Runic replant: select the matching crop with the cultivator first."); Trace("replant confirmation ignored because no crop is selected."); return; } string text = PrefabIdentity.Of(((Component)selectedPiece).gameObject); if (!_replant.TryConfirm(text, out var positions, out var reasonCode)) { Message(player, FriendlyReason(reasonCode)); Trace("replant confirmation denied: " + reasonCode + "."); return; } Quaternion rotation = Quaternion.Euler(0f, ((Component)player).transform.eulerAngles.y, 0f); if (ExecuteBatch(player, selectedPiece, text, positions, rotation) == 0 && positions.Count > 0) { _replant.Offer(text, positions); } } private int ExecuteBatch(Player player, Piece piece, string expectedCropId, IReadOnlyList requested, Quaternion rotation) { //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: 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_01fd: Unknown result type (might be due to invalid IL or missing references) //IL_0202: 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_02bf: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer || !((Character)player).IsOwner()) { Deny(player, "agriculture.not-authoritative"); return 0; } if (IsPlantPiece(piece)) { Piece selectedPiece = player.GetSelectedPiece(); if (string.Equals(PrefabIdentity.Of((selectedPiece != null) ? ((Component)selectedPiece).gameObject : null), expectedCropId, StringComparison.Ordinal)) { List list = new List(requested.Count); List list2 = new List(requested.Count); float requiredSpacing = _validator.RequiredSpacing(piece); for (int i = 0; i < requested.Count; i++) { RuntimePlacementValidation runtimePlacementValidation = _validator.Validate(player, piece, requested[i], rotation); PlacementValidationResult item = ApplyPlannedSpacing(runtimePlacementValidation.Result, runtimePlacementValidation.Position, list2, list, requiredSpacing); list2.Add(runtimePlacementValidation.Position); list.Add(item); } BatchPlan batchPlan = BatchPlanner.Plan(list, BuildBudget(player, piece), AgricultureConfig.InvalidPolicy.Value, AgricultureConfig.ResourcePolicy.Value); if (batchPlan.Blocked) { Deny(player, batchPlan.ReasonCode); return 0; } ItemData tool = null; if (batchPlan.SuccessfulCount > 0 && !CanStartBatchAction(player, piece, out tool, out var reason)) { Deny(player, reason); return 0; } if (!TryEnterMutation("runic.agriculture/plant-batch", out var lease)) { Message(player, "Runic planting paused: another Agriculture batch is in progress."); return 0; } using (lease) { int num = 0; int num2 = 0; string text = null; string text2 = null; for (int j = 0; j < batchPlan.Decisions.Count; j++) { BatchDecision batchDecision = batchPlan.Decisions[j]; if (!batchDecision.ShouldPlace) { num2++; if (string.IsNullOrEmpty(text2)) { text2 = batchDecision.ReasonCode; } if (batchDecision.ReasonCode == "agriculture.no-seeds" || batchDecision.ReasonCode == "agriculture.no-durability" || batchDecision.ReasonCode == "agriculture.no-stamina") { text = batchDecision.ReasonCode; break; } continue; } RuntimePlacementValidation runtimePlacementValidation2 = _validator.Validate(player, piece, list2[j], rotation); if (!runtimePlacementValidation2.Result.IsValid) { num2++; if (string.IsNullOrEmpty(text2)) { text2 = runtimePlacementValidation2.Result.ReasonCode; } text = runtimePlacementValidation2.Result.ReasonCode; if (AgricultureConfig.InvalidPolicy.Value == InvalidPositionPolicy.BlockConfirmation) { break; } continue; } if (!CanCommitOne(player, piece, expectedCropId, out var reason2)) { text = reason2; break; } try { bool consumeResources = PlantingGridPolicy.ConsumesSeedResources(IsFreeBuild(piece), player.NoCostCheat()); if (!NearbySeedResourceService.TryDebitOne(player, piece, AgricultureConfig.NearbySeedChestRange.Value, consumeResources, out var debit)) { text = "agriculture.no-seeds"; break; } using (debit) { player.PlacePiece(piece, runtimePlacementValidation2.Position, rotation, false); debit.Complete(); } _seedBudgetExpiresAt = 0f; num++; } catch (Exception ex) { text = "agriculture.placement-failed"; _log.LogError((object)("One planting commit failed after revalidation: " + ex)); break; } } if (num > 0) { try { ChargeBatchActionCosts(player, tool); } catch (Exception ex2) { text = "agriculture.placement-failed"; _log.LogError((object)("The planting batch completed but its one stamina/tool action could not be charged cleanly: " + ex2)); } } string text3 = "Runic planting: " + num + " planted"; if (num2 > 0) { text3 = text3 + ", " + num2 + " skipped"; } string text4 = ((!string.IsNullOrEmpty(text)) ? text : text2); if (!string.IsNullOrEmpty(text4) && text4 != "agriculture.valid") { text3 = text3 + ". " + FriendlyReason(text4); } Message(player, text3 + "."); Trace("batch result: planted=" + num + ", skipped=" + num2 + ", reason=" + (text4 ?? "agriculture.valid") + "."); return num; } } } Deny(player, "agriculture.crop-changed"); return 0; } private List BuildRequestedPositions(Player player, Piece piece, Vector3 origin, Quaternion rotation, string selectedCropId) { //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0133: 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_0137: 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_014a: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_015f: 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_0169: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_0182: Unknown result type (might be due to invalid IL or missing references) if (_replant.IsPending && string.Equals(_replant.CropId, selectedCropId, StringComparison.Ordinal)) { _previewLimitSummary = "confirmed replant positions"; return new List(_replant.Positions); } int num = 1600; float num2 = Math.Max(AgricultureConfig.Spacing.Value, MinimumSpacingFor(piece)); PatternRequest request = new PatternRequest(AgricultureConfig.Pattern.Value, AgricultureConfig.Rows.Value, AgricultureConfig.Columns.Value, num2, num, AgricultureConfig.MirrorShape.Value, AgricultureConfig.TrapezoidLeftPinch.Value, AgricultureConfig.TrapezoidRightPinch.Value); IReadOnlyList readOnlyList2; IReadOnlyList readOnlyList = (readOnlyList2 = _patternService.Generate(request)); int num3 = ((AgricultureConfig.Pattern.Value == PlantPattern.Row) ? AgricultureConfig.Columns.Value : checked(AgricultureConfig.Rows.Value * AgricultureConfig.Columns.Value)); if (readOnlyList.Count == num && num3 > num) { _previewLimitSummary = "performance cap " + num + " cells (Configuration Manager)"; } else { _previewLimitSummary = string.Empty; } List list = new List(readOnlyList2.Count); Vector3 val = rotation * Vector3.right; Vector3 val2 = rotation * Vector3.forward; for (int i = 0; i < readOnlyList2.Count; i++) { list.Add(origin + val * (float)readOnlyList2[i].Right + val2 * (float)readOnlyList2[i].Forward); } return list; } private List BuildValidatedPreview(Player player, Piece piece, IReadOnlyList requested, Quaternion rotation) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_005e: 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) List list = new List(requested.Count); List list2 = new List(requested.Count); float requiredSpacing = _validator.RequiredSpacing(piece); for (int i = 0; i < requested.Count; i++) { RuntimePlacementValidation runtimePlacementValidation = _validator.Validate(player, piece, requested[i], rotation); PlacementValidationResult item = ApplyPlannedSpacing(runtimePlacementValidation.Result, runtimePlacementValidation.Position, list, list2, requiredSpacing); list.Add(runtimePlacementValidation.Position); list2.Add(item); } int num = SeedActionBudget(player, piece, forceFresh: false); bool unlimitedSeeds = num >= 1000000; int num2 = 0; for (int j = 0; j < list2.Count; j++) { if (list2[j].IsValid) { num2++; } } int maximumSelectedCells = PlantingGridPolicy.SelectableSeedCount(Math.Max(0, num), num2, unlimitedSeeds); BatchPlan batchPlan = BatchPlanner.PlanPreview(list2, maximumSelectedCells); List list3 = new List(requested.Count); for (int k = 0; k < batchPlan.Decisions.Count; k++) { BatchDecision batchDecision = batchPlan.Decisions[k]; list3.Add(new RuntimePreviewPosition(list[k], rotation, batchDecision.ShouldPlace, batchDecision.ReasonCode)); } return list3; } private static PlacementValidationResult ApplyPlannedSpacing(PlacementValidationResult current, Vector3 position, IReadOnlyList previousPositions, IReadOnlyList previousValidations, float requiredSpacing) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) if (!current.IsValid) { return current; } float num = Math.Max(0.01f, requiredSpacing * 0.02f); float num2 = Math.Max(0f, requiredSpacing - num); float num3 = num2 * num2; for (int i = 0; i < previousPositions.Count; i++) { if (previousValidations[i].IsValid) { Vector3 val = previousPositions[i] - position; if (((Vector3)(ref val)).sqrMagnitude < num3) { return new PlacementValidationResult(isValid: false, "agriculture.spacing-blocked"); } } } return current; } private PlacementBudget BuildBudget(Player player, Piece piece) { return new PlacementBudget(SeedActionBudget(player, piece, forceFresh: true), 1000000, 1000000); } private int SeedActionBudget(Player player, Piece piece, bool forceFresh) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_008f: 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_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || (Object)(object)piece == (Object)null) { return 0; } if (!PlantingGridPolicy.ConsumesSeedResources(IsFreeBuild(piece), player.NoCostCheat())) { return 1000000; } Vector3 position = ((Component)player).transform.position; if (!forceFresh && _seedBudgetPiece == piece && Time.unscaledTime < _seedBudgetExpiresAt) { Vector3 val = position - _seedBudgetPlayerPosition; if (((Vector3)(ref val)).sqrMagnitude < 0.25f) { return _seedBudgetValue; } } int val2 = NearbySeedResourceService.AvailablePlantings(player, piece, AgricultureConfig.NearbySeedChestRange.Value); _seedBudgetPiece = piece; _seedBudgetPlayerPosition = position; _seedBudgetValue = Math.Min(1000000, Math.Max(0, val2)); _seedBudgetExpiresAt = Time.unscaledTime + 0.25f; return _seedBudgetValue; } private float MinimumSpacingFor(Piece piece) { float num = _validator.RequiredSpacing(piece); float num2 = Math.Max(0.05f, num * 0.05f); return Mathf.Clamp(Mathf.Ceil(Math.Max(0.5f, num + num2) * 10f) / 10f, 0.5f, 6f); } private bool CanCommitOne(Player player, Piece piece, string expectedCropId, out string reason) { if ((Object)(object)player != (Object)(object)Player.m_localPlayer || !((Character)player).IsOwner()) { reason = "agriculture.not-authoritative"; return false; } Piece selectedPiece = player.GetSelectedPiece(); if (!IsPlantPiece(selectedPiece) || !string.Equals(PrefabIdentity.Of(((Component)selectedPiece).gameObject), expectedCropId, StringComparison.Ordinal)) { reason = "agriculture.crop-changed"; return false; } reason = "agriculture.valid"; return true; } private static bool CanStartBatchAction(Player player, Piece piece, out ItemData tool, out string reason) { tool = ((player != null) ? ((Humanoid)player).RightItem : null); if ((Object)(object)player == (Object)null || (Object)(object)piece == (Object)null || tool == null) { reason = "agriculture.no-durability"; return false; } if (tool.m_shared.m_useDurability && tool.m_durability < ValheimAccess.GetPlaceDurability(player, tool)) { reason = "agriculture.no-durability"; return false; } float buildStamina = ValheimAccess.GetBuildStamina(player); if (!((Character)player).HaveStamina(buildStamina)) { reason = "agriculture.no-stamina"; return false; } reason = "agriculture.valid"; return true; } private static void ChargeBatchActionCosts(Player player, ItemData tool) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0091: 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) float buildStamina = ValheimAccess.GetBuildStamina(player); if (buildStamina > 0f) { ((Character)player).UseStamina(buildStamina); } PieceTable val = tool?.m_shared.m_buildPieces; if ((Object)(object)val != (Object)null && (int)val.m_skill != 0) { ((Character)player).RaiseSkill(val.m_skill, 1f); } if (tool != null && tool.m_shared.m_useDurability) { tool.m_durability = Mathf.Max(0f, tool.m_durability - ValheimAccess.GetPlaceDurability(player, tool)); } if (tool != null) { EffectList buildEffect = tool.m_shared.m_buildEffect; if (buildEffect != null) { buildEffect.Create(((Component)player).transform.position, Quaternion.identity, (Transform)null, 1f, -1); } } } private static bool IsFreeBuild(Piece piece) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)piece != (Object)null && (Object)(object)ZoneSystem.instance != (Object)null) { return ZoneSystem.instance.GetGlobalKey(piece.FreeBuildKey()); } return false; } private Quaternion ResolveAlignment(Player player, Piece piece, Vector3 origin) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0043: 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_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) Quaternion rotation; return (Quaternion)(AgricultureConfig.Alignment.Value switch { AgricultureAlignment.WorldAxes => Quaternion.identity, AgricultureAlignment.ExistingCropRow => TryFindExistingRow(piece, origin, out rotation) ? rotation : Quaternion.Euler(0f, ((Component)player).transform.eulerAngles.y, 0f), _ => Quaternion.Euler(0f, ((Component)player).transform.eulerAngles.y, 0f), }) * Quaternion.Euler(0f, _patternYawDegrees, 0f); } private bool TryFindExistingRow(Piece selectedPiece, Vector3 origin, out Quaternion rotation) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_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_0025: Unknown result type (might be due to invalid IL or missing references) //IL_010a: 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_011c: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_0147: 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_00c6: Unknown result type (might be due to invalid IL or missing references) rotation = Quaternion.identity; string b = PrefabIdentity.Of(((Component)selectedPiece).gameObject); int val = Physics.OverlapSphereNonAlloc(origin, 8f, _alignmentHits, LayerMask.GetMask(new string[2] { "piece", "piece_nonsolid" }), (QueryTriggerInteraction)2); HashSet hashSet = new HashSet(); List list = new List(); for (int i = 0; i < Math.Min(val, _alignmentHits.Length); i++) { Collider val2 = _alignmentHits[i]; _alignmentHits[i] = null; Plant val3 = (((Object)(object)val2 != (Object)null) ? ((Component)val2).GetComponentInParent() : null); if (!((Object)(object)val3 == (Object)null) && hashSet.Add(val3) && string.Equals(PrefabIdentity.Of(((Component)val3).gameObject), b, StringComparison.Ordinal)) { list.Add(((Component)val3).transform.position); } } list.Sort(delegate(Vector3 left, Vector3 right) { //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_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001a: 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_0024: 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_0056: 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) Vector3 val5 = left - origin; float sqrMagnitude = ((Vector3)(ref val5)).sqrMagnitude; val5 = right - origin; int num = sqrMagnitude.CompareTo(((Vector3)(ref val5)).sqrMagnitude); if (num != 0) { return num; } int num2 = left.x.CompareTo(right.x); if (num2 != 0) { return num2; } int num3 = left.z.CompareTo(right.z); return (num3 == 0) ? left.y.CompareTo(right.y) : num3; }); if (list.Count < 2) { return false; } Vector3 val4 = list[1] - list[0]; val4.y = 0f; if (((Vector3)(ref val4)).sqrMagnitude < 0.01f) { return false; } rotation = Quaternion.LookRotation(((Vector3)(ref val4)).normalized, Vector3.up); return true; } private bool TryCaptureControllerGesture(Player player, ValheimControllerAction primary) { if (AgricultureControllerCollisionGuard.TryCapture(AgricultureConfig.CurrentControllerBindings(), primary, out var problem)) { return true; } string text = "Runic Agriculture controller action blocked: " + problem + "."; _log.LogWarning((object)(text + " No agriculture mutation was attempted.")); Message(player, text + " Release the controls and try again, or use the keyboard shortcut."); Trace("controller capture failed closed: " + problem + "."); return false; } private bool TryCaptureControllerEditorControl(Player player, ValheimControllerAction primary) { if (AgricultureControllerCollisionGuard.TryCaptureEditorControl(AgricultureConfig.CurrentControllerBindings(), primary, out var problem)) { return true; } string text = "Runic Agriculture controller editor input blocked: " + problem + "."; _log.LogWarning((object)(text + " No pattern setting was changed.")); Message(player, text + " Release the D-pad/editor control and try again, or use the keyboard shortcut."); Trace("unmodified controller editor capture failed closed: " + problem + "."); return false; } private bool EnsureControllerActions(Player player) { if (!AgricultureConfig.ControllerEnabled.Value) { return false; } AgricultureControllerBindings agricultureControllerBindings = AgricultureConfig.CurrentControllerBindings(); if (!agricultureControllerBindings.TryValidate(out var problem)) { _controllerActionsVerified = false; ReportControllerProblem(player, problem); return false; } if (!ValheimAccess.TryVerifyControllerActions(agricultureControllerBindings, out problem, out var pathSignature)) { _controllerActionsVerified = false; if (!string.Equals(problem, "Valheim ZInput is not initialized", StringComparison.Ordinal)) { ReportControllerProblem(player, problem); } return false; } bool num = !_controllerActionsVerified || !string.Equals(_controllerPathSignature, pathSignature, StringComparison.Ordinal); _controllerActionsVerified = true; _controllerPathSignature = pathSignature; _lastControllerProblem = null; if (num) { _log.LogInfo((object)("Runic Agriculture validated distinct Valheim 0.221.12 controller paths: " + pathSignature + ".")); } return true; } private bool IsPlantingControlContext(Player player) { if (IsOperational) { ConfigEntry showContextualControls = AgricultureConfig.ShowContextualControls; if (showContextualControls != null && showContextualControls.Value && !((Object)(object)player == (Object)null) && !((Object)(object)player != (Object)(object)Player.m_localPlayer) && ((Character)player).IsOwner() && ((Character)player).InPlaceMode() && ValheimAccess.PlayerTakesInput(player)) { if ((Object)(object)Hud.instance == (Object)null || Hud.instance.m_userHidden || Hud.IsPieceSelectionVisible() || Game.IsPaused() || InventoryGui.IsVisible() || Minimap.IsOpen() || Menu.IsVisible() || Console.IsVisible() || TextInput.IsVisible() || ZInput.VirtualKeyboardOpen) { return false; } Piece selectedPiece = player.GetSelectedPiece(); GameObject placementGhost = ValheimAccess.GetPlacementGhost(player); if (IsPlantPiece(selectedPiece) && (Object)(object)placementGhost != (Object)null) { return placementGhost.activeInHierarchy; } return false; } } return false; } private bool IsReplantPreviewForSelection(Player player) { if (_lastPreview != null && _lastPreview.IsReplant) { return true; } if (!_replant.IsPending || (Object)(object)player == (Object)null) { return false; } string cropId = _replant.CropId; Piece selectedPiece = player.GetSelectedPiece(); return string.Equals(cropId, PrefabIdentity.Of((selectedPiece != null) ? ((Component)selectedPiece).gameObject : null), StringComparison.Ordinal); } private static bool TryReadDirectPattern(out PlantPattern pattern) { KeyCode[] array = new KeyCode[7]; RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); KeyCode[] array2 = (KeyCode[])(object)array; for (int i = 0; i < array2.Length; i++) { if (ValheimAccess.KeyDown(array2[i])) { return AgriculturePatternHotkeys.TryResolveNumpadSlot(i + 1, out pattern); } } pattern = PlantPattern.Row; return false; } private static string ControllerEditorFieldLabel(PlantPattern pattern, ControllerEditorField field) { return field switch { ControllerEditorField.Rows => "Rows " + AgricultureConfig.Rows.Value, ControllerEditorField.Columns => "Columns " + AgricultureConfig.Columns.Value, ControllerEditorField.Spacing => "Spacing " + AgricultureConfig.Spacing.Value.ToString("0.0", CultureInfo.InvariantCulture) + " m", ControllerEditorField.Side => "Side " + PatternEditor.OrientationLabel(pattern, AgricultureConfig.MirrorShape.Value), ControllerEditorField.LeftTaper => "Left taper " + Mathf.RoundToInt(AgricultureConfig.TrapezoidLeftPinch.Value * 100f) + "%", ControllerEditorField.RightTaper => "Right taper " + Mathf.RoundToInt(AgricultureConfig.TrapezoidRightPinch.Value * 100f) + "%", _ => field.ToString(), }; } private void ClearControlBarPreview() { _previewValidCount = 0; _previewGroundValidCount = 0; _previewTotalCount = 0; _previewIssueSummary = string.Empty; _previewLimitSummary = string.Empty; _previewBatchActionSummary = string.Empty; } private static string PreviewIssueSummary(IReadOnlyDictionary invalidReasons) { if (invalidReasons == null || invalidReasons.Count == 0) { return string.Empty; } List> list = new List>(); foreach (KeyValuePair invalidReason in invalidReasons) { int num = Math.Max(0, invalidReason.Value); if (num > 0) { list.Add(new KeyValuePair(invalidReason.Key, num)); } } list.Sort(delegate(KeyValuePair left, KeyValuePair right) { int num6 = right.Value.CompareTo(left.Value); return (num6 == 0) ? string.CompareOrdinal(left.Key, right.Key) : num6; }); if (list.Count == 0) { return string.Empty; } List list2 = new List(Math.Min(list.Count, 4)); int num2 = Math.Min(3, list.Count); for (int num3 = 0; num3 < num2; num3++) { list2.Add(list[num3].Value + " " + PreviewIssueLabel(list[num3].Key)); } if (list.Count > num2) { int num4 = 0; for (int num5 = num2; num5 < list.Count; num5++) { num4 += list[num5].Value; } list2.Add(num4 + " other blocked"); } return string.Join(", ", list2); } private static float PreviewRefreshInterval(int previewCount) { if (previewCount > 800) { return 0.35f; } if (previewCount > 400) { return 0.25f; } if (previewCount > 128) { return 0.15f; } return 0.1f; } private string BatchActionIssue(Player player, Piece piece) { if ((Object)(object)player == (Object)null || (Object)(object)piece == (Object)null || SeedActionBudget(player, piece, forceFresh: false) <= 0) { return string.Empty; } if (CanStartBatchAction(player, piece, out var _, out var reason)) { return string.Empty; } if (!(reason == "agriculture.no-stamina")) { if (reason == "agriculture.no-durability") { return "cultivator needs durability for the batch"; } return "batch action unavailable"; } return "need stamina for the one batch action"; } private static string PreviewIssueLabel(string reasonCode) { return reasonCode switch { "agriculture.terrain-unavailable" => "no terrain", "agriculture.crop-changed" => "crop changed", "agriculture.spacing-blocked" => "too close", "agriculture.slope-invalid" => "too steep", "agriculture.not-cultivated" => "not cultivated", "agriculture.biome-invalid" => "wrong biome", "agriculture.out-of-range" => "out of range", "agriculture.water-blocked" => "in water", "agriculture.ward-denied" => "ward denied", "agriculture.no-build-zone" => "in a no-build area", "agriculture.player-blocked" => "blocked by a player", "agriculture.no-seeds" => "without seeds", "agriculture.no-durability" => "without durability", "agriculture.no-stamina" => "without stamina", "agriculture.placement-failed" => "placement failed", _ => "invalid", }; } private void RestoreBuildHintsIfOwned() { _controlBar.Restore(); } private void ReportControllerProblem(Player player, string problem) { if (!string.Equals(_lastControllerProblem, problem, StringComparison.Ordinal)) { _lastControllerProblem = problem; string text = "Runic Agriculture controller controls disabled: " + problem + "."; _log.LogError((object)(text + " Keyboard controls remain available.")); Message(player, text + " Use keyboard controls or correct Configuration Manager."); } } private string FindPlantForMature(string matureCropId) { if (_matureToPlant.TryGetValue(matureCropId, out var value)) { return value; } if ((Object)(object)ZNetScene.instance == (Object)null) { return string.Empty; } List prefabNames = ZNetScene.instance.GetPrefabNames(); prefabNames.Sort(StringComparer.Ordinal); for (int i = 0; i < prefabNames.Count; i++) { GameObject prefab = ZNetScene.instance.GetPrefab(prefabNames[i]); Plant val = (((Object)(object)prefab != (Object)null) ? prefab.GetComponent() : null); Piece val2 = (((Object)(object)prefab != (Object)null) ? prefab.GetComponent() : null); if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null || val.m_grownPrefabs == null) { continue; } for (int j = 0; j < val.m_grownPrefabs.Length; j++) { string text = PrefabIdentity.Of(val.m_grownPrefabs[j]); if (!string.IsNullOrEmpty(text) && !_matureToPlant.ContainsKey(text)) { _matureToPlant.Add(text, PrefabIdentity.Of(prefab)); } } } if (_matureToPlant.TryGetValue(matureCropId, out value)) { return value; } _matureToPlant[matureCropId] = string.Empty; return string.Empty; } private static bool TryCaptureHarvestCandidate(Pickable pickable, out HarvestPickableCandidate candidate) { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0070: 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_008c: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) candidate = null; if (!IsPickableReady(pickable, out var zdo)) { return false; } int prefab = zdo.GetPrefab(); GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab(prefab) : null); string text = (Object.op_Implicit((Object)(object)(Object.op_Implicit((Object)(object)val) ? val.GetComponent() : null)) ? PrefabIdentity.Of(val) : string.Empty); Vector3 position = ((Component)pickable).transform.position; if (string.IsNullOrEmpty(text) || prefab == 0 || float.IsNaN(position.x) || float.IsInfinity(position.x) || float.IsNaN(position.y) || float.IsInfinity(position.y) || float.IsNaN(position.z) || float.IsInfinity(position.z)) { return false; } candidate = new HarvestPickableCandidate(pickable, text, prefab, zdo.m_uid, position); return true; } internal static bool IsPickableReady(Pickable pickable, out ZDO zdo) { zdo = null; if (!Object.op_Implicit((Object)(object)pickable) || !HarvestPickableBatchPolicy.Supports(HarvestComponentContract.Pickable)) { return false; } ZNetView component = ((Component)pickable).GetComponent(); zdo = ((Object.op_Implicit((Object)(object)component) && component.IsValid()) ? component.GetZDO() : null); bool flag = zdo != null && zdo.IsValid() && !((ZDOID)(ref zdo.m_uid)).IsNone() && zdo.GetPrefab() != 0 && component.GetZDO() == zdo; bool currentlyInTar = false; if (pickable.m_tarPreventsPicking) { Floating component2 = ((Component)pickable).GetComponent(); currentlyInTar = Object.op_Implicit((Object)(object)component2) && component2.IsInTar(); } bool alreadyPicked = pickable.GetPicked() || (flag && zdo.GetBool(ZDOVars.s_picked, false)); if (!HarvestPickableBatchPolicy.IsReady(Object.op_Implicit((Object)(object)component) && component.IsValid(), flag, pickable.CanBePicked(), alreadyPicked, pickable.m_tarPreventsPicking, currentlyInTar)) { zdo = null; return false; } return true; } private static Pickable FindPickable(GameObject gameObject) { if ((Object)(object)gameObject == (Object)null) { return null; } Pickable component = gameObject.GetComponent(); if (!((Object)(object)component != (Object)null)) { return gameObject.GetComponentInParent(); } return component; } private static bool IsPlantPiece(Piece piece) { if ((Object)(object)piece != (Object)null) { return (Object)(object)((Component)piece).GetComponent() != (Object)null; } return false; } private bool TryEnterMutation(string boundary, out IDisposable lease) { lease = null; if (_disabledForSession || Interlocked.CompareExchange(ref _mutationActive, 1, 0) != 0) { return false; } lease = new MutationScope(this); return true; } private static string ShortcutLabel(KeyboardShortcut shortcut) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) List list = new List(); IEnumerable modifiers = ((KeyboardShortcut)(ref shortcut)).Modifiers; if (modifiers != null) { foreach (KeyCode item in modifiers) { list.Add(FriendlyKey(item)); } } list.Add(FriendlyKey(((KeyboardShortcut)(ref shortcut)).MainKey)); return string.Join(" + ", list); } private unsafe static string FriendlyKey(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 return (key - 303) switch { 5 => "Left Alt", 4 => "Right Alt", 3 => "Left Ctrl", 2 => "Right Ctrl", 1 => "Left Shift", 0 => "Right Shift", _ => ((object)(*(KeyCode*)(&key))/*cast due to .constrained prefix*/).ToString(), }; } private static void Message(Player player, string text) { if ((Object)(object)player != (Object)null) { ((Character)player).Message((MessageType)2, text, 0, (Sprite)null); } } private static void StatusMessage(Player player, string text) { if ((Object)(object)player != (Object)null) { ((Character)player).Message((MessageType)1, text, 0, (Sprite)null); } } private void Trace(string text) { if (AgricultureConfig.VerboseLogging.Value) { _log.LogInfo((object)("[Agriculture verbose] " + text)); } } private void Deny(Player player, string reasonCode) { string text = FriendlyReason(reasonCode); Message(player, text); Trace("action denied: " + reasonCode + "."); Publish(reasonCode, text, "Adjust amber blocked positions or supply the missing planting resources, then confirm again."); } private static string FriendlyReason(string reasonCode) { return reasonCode switch { "agriculture.valid" => "Runic agriculture completed.", "agriculture.not-authoritative" => "Runic agriculture: the local player is not the authoritative owner.", "agriculture.crop-changed" => "Runic agriculture: the selected crop changed; review the new preview.", "agriculture.invalid-batch-blocked" => "Runic agriculture: confirmation blocked because at least one preview is invalid.", "agriculture.cost-batch-blocked" => "Runic agriculture: confirmation blocked because full costs are unavailable.", "agriculture.no-seeds" => "Runic agriculture stopped when personal inventory and eligible nearby chests ran out of planting resources.", "agriculture.no-durability" => "Runic agriculture stopped before cultivator durability was exhausted.", "agriculture.no-stamina" => "Runic agriculture stopped at the available stamina budget.", "agriculture.replant-not-offered" => "Runic replant: no harvested positions are awaiting confirmation.", "agriculture.replant-crop-mismatch" => "Runic replant: select the crop matching the pending harvest.", "agriculture.ward-denied" => "Runic agriculture: a ward denies this position.", "agriculture.spacing-blocked" => "Runic agriculture: crop spacing changed; remaining positions were not planted.", "agriculture.water-blocked" => "Runic agriculture: this crop cannot be planted in water.", "agriculture.terrain-unavailable" => "Runic agriculture: terrain could not be sampled at this position.", "agriculture.slope-invalid" => "Runic agriculture: this position is too steep.", "agriculture.biome-invalid" => "Runic agriculture: this crop cannot grow in the current biome.", "agriculture.not-cultivated" => "Runic agriculture: this position requires cultivated ground.", "agriculture.out-of-range" => "Runic agriculture: this position is outside placement range.", "agriculture.no-build-zone" => "Runic agriculture: building is not allowed at this position.", "agriculture.player-blocked" => "Runic agriculture: a player is blocking this position.", "agriculture.placement-failed" => "Runic agriculture: Valheim rejected the placement; no further positions were attempted.", _ => "Runic agriculture stopped: " + reasonCode + ".", }; } private static void Publish(string code, string reason, string remedy) { try { Plugin instance = Plugin.Instance; if (instance != null) { instance.Log.LogWarning((object)("Agriculture " + code + ": " + reason + " Remedy: " + remedy)); } } catch (Exception) { } } } internal static class AgricultureControllerCollisionGuard { private static readonly AgricultureControllerSuppressionSession Session = new AgricultureControllerSuppressionSession(); private static readonly AgricultureUnmodifiedControllerSuppression EditorSession = new AgricultureUnmodifiedControllerSuppression(); private static readonly Dictionary PrimaryDefinitions = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary EditorDefinitions = new Dictionary(StringComparer.OrdinalIgnoreCase); private static ButtonDef _modifier; internal static bool TryCapture(AgricultureControllerBindings bindings, ValheimControllerAction primaryAction, out string problem) { UpdateReleaseState(); if (!bindings.ContainsChordPrimary(primaryAction)) { problem = "the requested primary is not one of the validated agriculture actions"; return false; } if (!ValheimAccess.TryVerifyControllerActions(bindings, out problem, out var _)) { return false; } ButtonDef val; ButtonDef val2; string modifierPath; string text; try { val = ValheimAccess.ControllerButtonDefinition(bindings.Modifier); val2 = ValheimAccess.ControllerButtonDefinition(primaryAction); modifierPath = ValheimAccess.ControllerEffectivePath(val); text = ValheimAccess.ControllerEffectivePath(val2); } catch (Exception ex) { problem = "the accepted controller chord could not be resolved (" + ex.GetType().Name + ")"; return false; } if (val == null || val2 == null || !val.Held || !val2.Pressed) { problem = "the accepted controller chord is no longer pressed"; return false; } if (_modifier != null && _modifier != val) { problem = "the controller modifier changed while an accepted gesture is being released"; return false; } if (!Session.TryCapture(modifierPath, text, out problem)) { return false; } _modifier = val; PrimaryDefinitions[text] = val2; problem = string.Empty; return true; } internal static bool TryCaptureEditorControl(AgricultureControllerBindings bindings, ValheimControllerAction primaryAction, out string problem) { UpdateReleaseState(); if (!bindings.ContainsEditorPrimary(primaryAction)) { problem = "the requested primary is not one of the crop-preview editor controls"; return false; } if (!ValheimAccess.TryVerifyControllerActions(bindings, out problem, out var _)) { return false; } ButtonDef val; ButtonDef val2; string text; try { val = ValheimAccess.ControllerButtonDefinition(bindings.Modifier); val2 = ValheimAccess.ControllerButtonDefinition(primaryAction); text = ValheimAccess.ControllerEffectivePath(val2); } catch (Exception ex) { problem = "the accepted editor control could not be resolved (" + ex.GetType().Name + ")"; return false; } if (val == null || val2 == null || val.Held || !val2.Pressed) { problem = "the editor control is no longer pressed without the controller modifier"; return false; } if (!EditorSession.TryCapture(text, out problem)) { return false; } EditorDefinitions[text] = val2; problem = string.Empty; return true; } internal static bool ShouldSuppress(string buttonName) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Invalid comparison between Unknown and I4 UpdateReleaseState(); if ((!Session.IsActive && !EditorSession.IsActive) || ZInput.instance == null || string.IsNullOrEmpty(buttonName)) { return false; } ButtonDef buttonDef; try { buttonDef = ZInput.instance.GetButtonDef(buttonName); } catch (Exception) { return false; } if (buttonDef == null || (int)buttonDef.Source != 180) { return false; } string text; try { text = buttonDef.GetActionPath(true)?.Trim(); } catch (Exception) { return false; } if (buttonDef != _modifier && !IsCapturedPrimary(buttonDef) && !IsCapturedEditorControl(buttonDef) && !Session.ShouldSuppress(text)) { return EditorSession.ShouldSuppress(text); } return true; } internal static void Poll() { UpdateReleaseState(); } internal static void Reset() { Session.Reset(); EditorSession.Reset(); PrimaryDefinitions.Clear(); EditorDefinitions.Clear(); _modifier = null; } private static void UpdateReleaseState() { if (!Session.IsActive && !EditorSession.IsActive) { return; } if ((Session.IsActive && !DefinitionsRemainCurrent()) || (EditorSession.IsActive && !EditorDefinitionsRemainCurrent())) { Reset(); return; } if (Session.IsActive) { Session.Advance(Time.frameCount, _modifier.Held, (string path) => PrimaryDefinitions.TryGetValue(path, out var value) && value != null && value.Held); RemoveReleasedDefinitions(); if (!Session.IsActive) { PrimaryDefinitions.Clear(); _modifier = null; } } if (EditorSession.IsActive) { EditorSession.Advance(Time.frameCount, (string path) => EditorDefinitions.TryGetValue(path, out var value) && value != null && value.Held); RemoveReleasedEditorDefinitions(); } } private static bool DefinitionsRemainCurrent() { if (ZInput.instance == null || _modifier == null) { return false; } try { ButtonDef buttonDef = ZInput.instance.GetButtonDef(_modifier.Name); if (buttonDef != _modifier || !string.Equals(buttonDef.GetActionPath(true)?.Trim(), Session.ModifierPath, StringComparison.OrdinalIgnoreCase)) { return false; } foreach (KeyValuePair primaryDefinition in PrimaryDefinitions) { ButtonDef buttonDef2 = ZInput.instance.GetButtonDef(primaryDefinition.Value.Name); if (buttonDef2 != primaryDefinition.Value || !string.Equals(buttonDef2.GetActionPath(true)?.Trim(), primaryDefinition.Key, StringComparison.OrdinalIgnoreCase)) { return false; } } return true; } catch (Exception) { return false; } } private static bool IsCapturedPrimary(ButtonDef requested) { foreach (ButtonDef value in PrimaryDefinitions.Values) { if (requested == value) { return true; } } return false; } private static bool IsCapturedEditorControl(ButtonDef requested) { foreach (ButtonDef value in EditorDefinitions.Values) { if (requested == value) { return true; } } return false; } private static bool EditorDefinitionsRemainCurrent() { if (ZInput.instance == null) { return false; } try { foreach (KeyValuePair editorDefinition in EditorDefinitions) { ButtonDef buttonDef = ZInput.instance.GetButtonDef(editorDefinition.Value.Name); if (buttonDef != editorDefinition.Value || !string.Equals(buttonDef.GetActionPath(true)?.Trim(), editorDefinition.Key, StringComparison.OrdinalIgnoreCase)) { return false; } } return true; } catch (Exception) { return false; } } private static void RemoveReleasedDefinitions() { if (PrimaryDefinitions.Count == 0) { return; } List list = new List(); foreach (string key in PrimaryDefinitions.Keys) { if (!Session.ContainsPrimary(key)) { list.Add(key); } } for (int i = 0; i < list.Count; i++) { PrimaryDefinitions.Remove(list[i]); } } private static void RemoveReleasedEditorDefinitions() { if (EditorDefinitions.Count == 0) { return; } List list = new List(); foreach (string key in EditorDefinitions.Keys) { if (!EditorSession.Contains(key)) { list.Add(key); } } for (int i = 0; i < list.Count; i++) { EditorDefinitions.Remove(list[i]); } } } [HarmonyPatch(typeof(ZInput), "GetButton", new Type[] { typeof(string) })] internal static class AgricultureControllerGetButtonPatch { [HarmonyPriority(400)] [HarmonyAfter(new string[] { "chazman.RunicStorage" })] [HarmonyBefore(new string[] { "chazman.RunicInventory" })] private static bool Prefix(string name, ref bool __result) { if (!AgricultureControllerCollisionGuard.ShouldSuppress(name)) { return true; } __result = false; return false; } } [HarmonyPatch(typeof(ZInput), "GetButtonDown", new Type[] { typeof(string) })] internal static class AgricultureControllerGetButtonDownPatch { [HarmonyPriority(400)] [HarmonyAfter(new string[] { "chazman.RunicStorage" })] [HarmonyBefore(new string[] { "chazman.RunicInventory" })] private static bool Prefix(string name, ref bool __result) { if (!AgricultureControllerCollisionGuard.ShouldSuppress(name)) { return true; } __result = false; return false; } } [HarmonyPatch(typeof(ZInput), "GetButtonUp", new Type[] { typeof(string) })] internal static class AgricultureControllerGetButtonUpPatch { [HarmonyPriority(400)] [HarmonyAfter(new string[] { "chazman.RunicStorage" })] [HarmonyBefore(new string[] { "chazman.RunicInventory" })] private static bool Prefix(string name, ref bool __result) { if (!AgricultureControllerCollisionGuard.ShouldSuppress(name)) { return true; } __result = false; return false; } } [HarmonyPatch(typeof(ZInput), "GetButtonPressedTimer", new Type[] { typeof(string) })] internal static class AgricultureControllerPressedTimerPatch { [HarmonyPriority(400)] [HarmonyAfter(new string[] { "chazman.RunicStorage" })] [HarmonyBefore(new string[] { "chazman.RunicInventory" })] private static bool Prefix(string name, ref float __result) { if (!AgricultureControllerCollisionGuard.ShouldSuppress(name)) { return true; } __result = 0f; return false; } } [HarmonyPatch(typeof(ZInput), "GetButtonLastPressedTimer", new Type[] { typeof(string) })] internal static class AgricultureControllerLastPressedTimerPatch { [HarmonyPriority(400)] [HarmonyAfter(new string[] { "chazman.RunicStorage" })] [HarmonyBefore(new string[] { "chazman.RunicInventory" })] private static bool Prefix(string name, ref float __result) { if (!AgricultureControllerCollisionGuard.ShouldSuppress(name)) { return true; } __result = 0f; return false; } } [HarmonyPatch(typeof(Player), "Update")] internal static class PlayerUpdateAgricultureInputPatch { private static void Prefix(Player __instance) { try { Plugin.Instance?.Runtime?.TickInput(__instance); } catch (Exception ex) { Plugin instance = Plugin.Instance; if (instance != null) { instance.Log.LogError((object)("Agriculture input routing failed closed: " + ex)); } Plugin.Instance?.Runtime?.DisableForSession("agriculture.placement-failed"); } } } [HarmonyPatch(typeof(Player), "UpdatePlacementGhost", new Type[] { typeof(bool) })] internal static class PlayerUpdatePlacementGhostPatch { private static void Postfix(Player __instance) { try { Plugin.Instance?.Runtime?.UpdatePreview(__instance); } catch (Exception ex) { Plugin instance = Plugin.Instance; if (instance != null) { instance.Log.LogError((object)("Agriculture preview failed closed: " + ex)); } Plugin.Instance?.Runtime?.DisableForSession("agriculture.placement-failed"); } } } [HarmonyPatch(typeof(Player), "Interact", new Type[] { typeof(GameObject), typeof(bool), typeof(bool) })] internal static class PlayerInteractPatch { [HarmonyPriority(0)] [HarmonyAfter(new string[] { "chazman.RunicProduction" })] private static bool Prefix(Player __instance, GameObject go) { try { Plugin instance = Plugin.Instance; if (instance?.Runtime == null || !instance.Runtime.IsOperational) { return true; } if (!instance.Runtime.IsAreaHarvestRequested(__instance, go)) { return true; } return !instance.Runtime.TryAreaHarvest(__instance, go); } catch (Exception ex) { Plugin instance2 = Plugin.Instance; if (instance2 != null) { instance2.Log.LogWarning((object)("Area harvest failed; preserving the original single interaction: " + ex.Message)); } return true; } } } [HarmonyPatch(typeof(Plant), "GetHoverText")] internal static class PlantHoverTextPatch { private static void Postfix(Plant __instance, ref string __result) { //IL_0087: Unknown result type (might be due to invalid IL or missing references) ConfigEntry enabled = AgricultureConfig.Enabled; if (enabled == null || !enabled.Value) { return; } ConfigEntry showHoverStatus = AgricultureConfig.ShowHoverStatus; if (showHoverStatus == null || !showHoverStatus.Value) { return; } try { double num = Math.Max(0.001, ValheimAccess.PlantGrowTime(__instance)); int num2 = Mathf.Clamp(Mathf.FloorToInt((float)(ValheimAccess.PlantAge(__instance) / num * 100.0)), 0, 100); __result = __result + "\n[Runic] Growth " + num2 + "% - " + FriendlyPlantStatus(__instance.GetStatus()); } catch (Exception) { } } private unsafe static string FriendlyPlantStatus(Status status) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected I4, but got Unknown return (int)status switch { 0 => "healthy", 1 => "needs sunlight / no roof", 2 => "needs more spacing", 3 => "wrong biome", 4 => "ground is not cultivated", 5 => "missing attachment", 6 => "too hot", 7 => "too cold", _ => ((object)(*(Status*)(&status))/*cast due to .constrained prefix*/).ToString(), }; } } [HarmonyPatch(typeof(Beehive), "GetHoverText")] internal static class BeehiveHoverTextPatch { private static void Postfix(Beehive __instance, ref string __result) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) ConfigEntry enabled = AgricultureConfig.Enabled; if (enabled == null || !enabled.Value) { return; } ConfigEntry showHoverStatus = AgricultureConfig.ShowHoverStatus; if (showHoverStatus == null || !showHoverStatus.Value) { return; } try { if (PrivateArea.CheckAccess(((Component)__instance).transform.position, 0f, false, false)) { string text = ((!ValheimAccess.BeeBiomeValid(__instance)) ? "wrong biome" : ((!ValheimAccess.BeeHasSpace(__instance)) ? "blocked" : "happy")); __result = __result + "\n[Runic] Honey " + ValheimAccess.BeeHoney(__instance) + "/" + __instance.m_maxHoney + " - bees " + text; } } catch (Exception) { } } } [HarmonyPatch(typeof(Pickable), "GetHoverText")] internal static class PickableHarvestHoverTextPatch { private static void Postfix(Pickable __instance, ref string __result) { ConfigEntry enabled = AgricultureConfig.Enabled; if (enabled == null || !enabled.Value) { return; } try { AgricultureRuntime agricultureRuntime = Plugin.Instance?.Runtime; if (agricultureRuntime == null || !agricultureRuntime.CanOfferAreaHarvest(Player.m_localPlayer, __instance)) { return; } ZDO zdo; bool flag = AgricultureRuntime.IsPickableReady(__instance, out zdo); ConfigEntry showHoverStatus = AgricultureConfig.ShowHoverStatus; if (showHoverStatus != null && showHoverStatus.Value) { __result += (flag ? "\n[Runic] Ready for area harvest" : "\n[Runic] Currently unavailable"); } if (!flag) { return; } ConfigEntry showContextualControls = AgricultureConfig.ShowContextualControls; if (showContextualControls != null && showContextualControls.Value) { string text = Plugin.Instance?.Runtime?.HarvestControlHint(); if (!string.IsNullOrEmpty(text)) { __result = __result + "\n[Runic] Area harvest: " + text; } } } catch (Exception) { } } } internal static class NearbySeedContainerIndex { private readonly struct CellKey : IEquatable { internal int X { get; } internal int Z { get; } internal CellKey(int x, int z) { X = x; Z = z; } internal static CellKey From(Vector3 position) { //IL_0000: 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) return new CellKey(Mathf.FloorToInt(position.x / 10f), Mathf.FloorToInt(position.z / 10f)); } public bool Equals(CellKey other) { if (X == other.X) { return Z == other.Z; } return false; } public override bool Equals(object obj) { if (obj is CellKey other) { return Equals(other); } return false; } public override int GetHashCode() { return (X * 397) ^ Z; } } private readonly struct IndexedContainer { internal int InstanceId { get; } internal Container Container { get; } internal IndexedContainer(int instanceId, Container container) { InstanceId = instanceId; Container = container; } } internal const float HardMaximumRangeMeters = 30f; internal const int HardMaximumCandidates = 64; private const float CellSizeMeters = 10f; private static readonly object Gate = new object(); private static readonly Dictionary> Cells = new Dictionary>(); private static readonly Dictionary Membership = new Dictionary(); internal static void Register(Container container) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0026: 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) if ((Object)(object)container == (Object)null) { return; } int instanceID; Vector3 position; try { instanceID = ((Object)container).GetInstanceID(); position = ((Component)container).transform.position; } catch { return; } if (!IsFinite(position)) { return; } lock (Gate) { CellKey cellKey = CellKey.From(position); if (!Membership.TryGetValue(instanceID, out var value) || !value.Equals(cellKey) || !Cells.TryGetValue(value, out var value2) || !ContainsExact(value2, instanceID, container)) { UnregisterLocked(container, instanceID); if (!Cells.TryGetValue(cellKey, out var value3)) { value3 = new List(); Cells.Add(cellKey, value3); } value3.Add(new IndexedContainer(instanceID, container)); Membership[instanceID] = cellKey; } } } internal static void Unregister(Container container) { if (container == null) { return; } int instanceID; try { instanceID = ((Object)container).GetInstanceID(); } catch { return; } lock (Gate) { UnregisterLocked(container, instanceID); } } internal static IReadOnlyList Query(Vector3 origin, float rangeMeters) { //IL_0000: 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_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) if (!IsFinite(origin) || float.IsNaN(rangeMeters) || float.IsInfinity(rangeMeters)) { return Array.Empty(); } float num = Mathf.Clamp(rangeMeters, 1f, 30f); float rangeSquared = num * num; CellKey cellKey = CellKey.From(origin - new Vector3(num, 0f, num)); CellKey cellKey2 = CellKey.From(origin + new Vector3(num, 0f, num)); Dictionary dictionary = new Dictionary(); HashSet hashSet = new HashSet(); lock (Gate) { for (int i = cellKey.X; i <= cellKey2.X; i++) { for (int j = cellKey.Z; j <= cellKey2.Z; j++) { CellKey key = new CellKey(i, j); if (!Cells.TryGetValue(key, out var value)) { continue; } for (int num2 = value.Count - 1; num2 >= 0; num2--) { IndexedContainer indexedContainer = value[num2]; NearbySeedContainerCandidate candidate; if ((Object)(object)indexedContainer.Container == (Object)null) { value.RemoveAt(num2); Membership.Remove(indexedContainer.InstanceId); } else if (TryCandidate(indexedContainer.Container, origin, rangeSquared, out candidate) && !hashSet.Contains(candidate.EndpointId)) { if (dictionary.ContainsKey(candidate.EndpointId)) { dictionary.Remove(candidate.EndpointId); hashSet.Add(candidate.EndpointId); } else { dictionary.Add(candidate.EndpointId, candidate); } } } if (value.Count == 0) { Cells.Remove(key); } } } } List list = new List(dictionary.Values); list.Sort(CompareCandidate); if (list.Count > 64) { list.RemoveRange(64, list.Count - 64); } return list.AsReadOnly(); } internal static bool IsStaticChest(Container container) { if ((Object)(object)container == (Object)null || (Object)(object)container.m_wagon != (Object)null) { return false; } try { Rigidbody componentInParent = ((Component)container).GetComponentInParent(); return (Object)(object)componentInParent == (Object)null || componentInParent.isKinematic; } catch { return false; } } internal static void Clear() { lock (Gate) { Cells.Clear(); Membership.Clear(); } } private static bool TryCandidate(Container container, Vector3 origin, float rangeSquared, out NearbySeedContainerCandidate candidate) { //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) candidate = default(NearbySeedContainerCandidate); if ((Object)(object)container == (Object)null || !((Behaviour)container).isActiveAndEnabled || !IsStaticChest(container)) { return false; } ZNetView val = NearbySeedResourceService.ContainerNetworkView(container); ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null); if (val2 == null || !val2.IsValid() || ((ZDOID)(ref val2.m_uid)).IsNone()) { return false; } Vector3 position = ((Component)container).transform.position; Vector3 val3 = position - origin; float sqrMagnitude = ((Vector3)(ref val3)).sqrMagnitude; if (!IsFinite(position) || float.IsNaN(sqrMagnitude) || float.IsInfinity(sqrMagnitude) || sqrMagnitude > rangeSquared) { return false; } candidate = new NearbySeedContainerCandidate(container, val2.m_uid, sqrMagnitude); return true; } private static int CompareCandidate(NearbySeedContainerCandidate left, NearbySeedContainerCandidate right) { //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) //IL_0031: 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_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) int num = left.DistanceSquared.CompareTo(right.DistanceSquared); if (num != 0) { return num; } ZDOID endpointId = left.EndpointId; long userID = ((ZDOID)(ref endpointId)).UserID; endpointId = right.EndpointId; int num2 = userID.CompareTo(((ZDOID)(ref endpointId)).UserID); if (num2 == 0) { endpointId = left.EndpointId; uint iD = ((ZDOID)(ref endpointId)).ID; endpointId = right.EndpointId; return iD.CompareTo(((ZDOID)(ref endpointId)).ID); } return num2; } private static void UnregisterLocked(Container container, int instanceId) { if (!Membership.TryGetValue(instanceId, out var value)) { return; } if (Cells.TryGetValue(value, out var value2)) { value2.RemoveAll((IndexedContainer indexedContainer) => indexedContainer.InstanceId == instanceId || indexedContainer.Container == container); if (value2.Count == 0) { Cells.Remove(value); } } Membership.Remove(instanceId); } private static bool ContainsExact(IReadOnlyList entries, int instanceId, Container container) { for (int i = 0; i < entries.Count; i++) { if (entries[i].InstanceId == instanceId && entries[i].Container == container) { return true; } } return false; } private static bool IsFinite(Vector3 value) { //IL_0000: 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_001a: 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_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) if (!float.IsNaN(value.x) && !float.IsInfinity(value.x) && !float.IsNaN(value.y) && !float.IsInfinity(value.y) && !float.IsNaN(value.z)) { return !float.IsInfinity(value.z); } return false; } } internal readonly struct NearbySeedContainerCandidate { internal Container Container { get; } internal ZDOID EndpointId { get; } internal float DistanceSquared { get; } internal NearbySeedContainerCandidate(Container container, ZDOID endpointId, float distanceSquared) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) Container = container; EndpointId = endpointId; DistanceSquared = distanceSquared; } } internal static class NearbySeedResourceService { internal readonly struct ResourceSource { internal Inventory Inventory { get; } internal Container Container { get; } internal ZDOID EndpointId { get; } internal float DistanceSquared { get; } internal ResourceSource(Inventory inventory, Container container, ZDOID endpointId, float distanceSquared) { //IL_000f: 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) Inventory = inventory; Container = container; EndpointId = endpointId; DistanceSquared = distanceSquared; } } private readonly struct PlantResourceRemoval { internal ResourceSource Source { get; } internal ItemData Item { get; } internal int Amount { get; } internal PlantResourceRemoval(ResourceSource source, ItemData item, int amount) { Source = source; Item = item; Amount = amount; } } internal readonly struct ResourceSnapshot { internal ResourceSource Source { get; } internal byte[] Payload { get; } internal ResourceSnapshot(ResourceSource source, byte[] payload) { Source = source; Payload = payload; } } internal sealed class PlantResourceDebit : IDisposable { internal static readonly PlantResourceDebit Empty = new PlantResourceDebit(null, 0f, Array.Empty(), completed: true); private readonly Player _player; private readonly float _rangeMeters; private readonly IReadOnlyList _snapshots; private bool _completed; internal PlantResourceDebit(Player player, float rangeMeters, IReadOnlyList snapshots, bool completed = false) { _player = player; _rangeMeters = rangeMeters; _snapshots = snapshots; _completed = completed; } internal void Complete() { _completed = true; } public void Dispose() { if (!_completed) { Restore(_player, _rangeMeters, _snapshots); _completed = true; } } } private static readonly MethodInfo CheckAccessMethod = AccessTools.Method(typeof(Container), "CheckAccess", new Type[1] { typeof(long) }, (Type[])null); private static readonly MethodInfo LoadMethod = AccessTools.Method(typeof(Container), "Load", Type.EmptyTypes, (Type[])null); private static readonly FieldInfo LoadingField = AccessTools.Field(typeof(Container), "m_loading"); private static readonly FieldInfo ContainerViewField = AccessTools.Field(typeof(Container), "m_nview"); internal static ZNetView ContainerNetworkView(Container container) { if ((Object)(object)container == (Object)null) { return null; } if ((Object)(object)container.m_rootObjectOverride != (Object)null) { return container.m_rootObjectOverride; } try { object? obj = ContainerViewField?.GetValue(container); return (ZNetView)((obj is ZNetView) ? obj : null); } catch { return null; } } internal static int AvailablePlantings(Player player, Piece piece, float rangeMeters) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) if (!CanMutatePlayer(player) || (Object)(object)piece == (Object)null) { return 0; } IReadOnlyList readOnlyList = Requirements(piece); if (readOnlyList.Count == 0) { return int.MaxValue; } Dictionary dictionary = new Dictionary(StringComparer.Ordinal); AddInventoryCounts(((Humanoid)player).GetInventory(), readOnlyList, dictionary); IReadOnlyList readOnlyList2 = NearbySeedContainerIndex.Query(((Component)player).transform.position, rangeMeters); for (int i = 0; i < readOnlyList2.Count; i++) { if (TryGetExactWritableInventory(readOnlyList2[i], player, ((Component)player).transform.position, rangeMeters, out var inventory)) { AddInventoryCounts(inventory, readOnlyList, dictionary); } } return SeedResourceMath.MaximumPlantings(readOnlyList, dictionary); } internal static bool TryDebitOne(Player player, Piece piece, float rangeMeters, bool consumeResources, out PlantResourceDebit debit) { //IL_01ff: Unknown result type (might be due to invalid IL or missing references) debit = null; if (!CanMutatePlayer(player) || (Object)(object)piece == (Object)null) { return false; } if (!consumeResources) { debit = PlantResourceDebit.Empty; return true; } IReadOnlyList readOnlyList = Requirements(piece); if (readOnlyList.Count == 0) { debit = PlantResourceDebit.Empty; return true; } List list = ResolveSources(player, rangeMeters); List list2 = new List(); try { for (int num = list.Count - 1; num >= 0; num--) { ResourceSource source = list[num]; if (!SourceIsCurrent(source, player, rangeMeters)) { if ((Object)(object)source.Container == (Object)null) { return false; } list.RemoveAt(num); } } List list3 = PlanRemovals(readOnlyList, list); if (list3 == null) { return false; } for (int i = 0; i < list.Count; i++) { ResourceSource source2 = list[i]; if (IsTouched(source2.Inventory, list3)) { list2.Add(new ResourceSnapshot(source2, Save(source2.Inventory).GetArray())); } } for (int j = 0; j < list3.Count; j++) { PlantResourceRemoval plantResourceRemoval = list3[j]; if (!SourceAuthorityStillCurrent(plantResourceRemoval.Source, player, rangeMeters) || plantResourceRemoval.Item == null || plantResourceRemoval.Item.m_stack < plantResourceRemoval.Amount || !plantResourceRemoval.Source.Inventory.RemoveItem(plantResourceRemoval.Item, plantResourceRemoval.Amount)) { throw new InvalidOperationException("A planting resource changed before debit."); } } for (int k = 0; k < list2.Count; k++) { if (!SourceIsCurrent(list2[k].Source, player, rangeMeters) || ((Object)(object)list2[k].Source.Container != (Object)null && !InventoryMatchesZdo(list2[k].Source.Container, list2[k].Source.Inventory, list2[k].Source.EndpointId))) { throw new InvalidOperationException("A nearby planting chest did not publish its exact debit."); } } debit = new PlantResourceDebit(player, rangeMeters, list2); return true; } catch (Exception ex) { try { Restore(player, rangeMeters, list2); } catch (Exception ex2) { throw new AggregateException("A planting debit failed and one or more rollbacks also failed.", ex, ex2); } throw; } } internal static IReadOnlyList Requirements(Piece piece) { List list = new List(); Requirement[] array = piece?.m_resources ?? Array.Empty(); foreach (Requirement val in array) { if (!((Object)(object)val?.m_resItem == (Object)null)) { string text = val.m_resItem.m_itemData?.m_shared?.m_name; if (!string.IsNullOrWhiteSpace(text)) { list.Add(new PlantResourceRequirement(text, Math.Max(1, val.GetAmount(0)))); } } } return list.AsReadOnly(); } private static List ResolveSources(Player player, float rangeMeters) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: 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_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) List list = new List { new ResourceSource(((Humanoid)player).GetInventory(), null, default(ZDOID), 0f) }; IReadOnlyList readOnlyList = NearbySeedContainerIndex.Query(((Component)player).transform.position, rangeMeters); for (int i = 0; i < readOnlyList.Count; i++) { NearbySeedContainerCandidate candidate = readOnlyList[i]; if (TryGetExactWritableInventory(candidate, player, ((Component)player).transform.position, rangeMeters, out var inventory)) { list.Add(new ResourceSource(inventory, candidate.Container, candidate.EndpointId, candidate.DistanceSquared)); } } return list; } private static List PlanRemovals(IReadOnlyList requirements, IReadOnlyList sources) { SortedDictionary sortedDictionary = new SortedDictionary(StringComparer.Ordinal); for (int i = 0; i < requirements.Count; i++) { PlantResourceRequirement plantResourceRequirement = requirements[i]; sortedDictionary.TryGetValue(plantResourceRequirement.ResourceId, out var value); sortedDictionary[plantResourceRequirement.ResourceId] = AddSaturated(value, plantResourceRequirement.Amount); } List list = new List(); foreach (KeyValuePair item in sortedDictionary) { int num = item.Value; for (int j = 0; j < sources.Count; j++) { if (num <= 0) { break; } ResourceSource source = sources[j]; List list2 = MatchingItems(source.Inventory, item.Key); for (int k = 0; k < list2.Count; k++) { if (num <= 0) { break; } ItemData val = list2[k]; int num2 = Math.Min(num, Math.Max(0, val.m_stack)); if (num2 > 0) { list.Add(new PlantResourceRemoval(source, val, num2)); num -= num2; } } } if (num > 0) { return null; } } return list; } private static void AddInventoryCounts(Inventory inventory, IReadOnlyList requirements, IDictionary totals) { if (inventory == null) { return; } HashSet hashSet = new HashSet(StringComparer.Ordinal); for (int i = 0; i < requirements.Count; i++) { hashSet.Add(requirements[i].ResourceId); } foreach (ItemData allItem in inventory.GetAllItems()) { string text = allItem?.m_shared?.m_name; if (!string.IsNullOrEmpty(text) && hashSet.Contains(text) && allItem.m_stack > 0) { totals.TryGetValue(text, out var value); totals[text] = AddSaturated(value, allItem.m_stack); } } } private static List MatchingItems(Inventory inventory, string resourceId) { List list = new List(); foreach (ItemData allItem in inventory.GetAllItems()) { if (allItem != null && allItem.m_stack > 0 && string.Equals(allItem.m_shared?.m_name, resourceId, StringComparison.Ordinal)) { list.Add(allItem); } } list.Sort(delegate(ItemData left, ItemData right) { int num = left.m_gridPos.y.CompareTo(right.m_gridPos.y); return (num == 0) ? left.m_gridPos.x.CompareTo(right.m_gridPos.x) : num; }); return list; } private static bool TryGetExactWritableInventory(NearbySeedContainerCandidate candidate, Player player, Vector3 origin, float rangeMeters, out Inventory inventory) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: 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_0138: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) inventory = null; Container container = candidate.Container; float num = Mathf.Clamp(rangeMeters, 1f, 30f); if (CanMutatePlayer(player) && !((Object)(object)ZNet.instance == (Object)null) && !((Object)(object)container == (Object)null) && ((Behaviour)container).isActiveAndEnabled && NearbySeedContainerIndex.IsStaticChest(container)) { Vector3 val = ((Component)container).transform.position - origin; if (!(((Vector3)(ref val)).sqrMagnitude > num * num) && !container.IsInUse() && container.IsOwner() && !(CheckAccessMethod == null) && !(LoadMethod == null) && !(LoadingField == null)) { if (!PrivateArea.CheckAccess(((Component)container).transform.position, 0f, false, false)) { return false; } try { if (!(bool)CheckAccessMethod.Invoke(container, new object[1] { player.GetPlayerID() }) || (bool)LoadingField.GetValue(container)) { return false; } ZNetView val2 = ContainerNetworkView(container); ZDO val3 = (((Object)(object)val2 != (Object)null && val2.IsValid() && val2.IsOwner()) ? val2.GetZDO() : null); if (val3 == null || !val3.IsValid() || val3.m_uid != candidate.EndpointId || val3.GetOwner() != ZNet.GetUID()) { return false; } LoadMethod.Invoke(container, Array.Empty()); inventory = container.GetInventory(); if (inventory == null || !InventoryMatchesZdo(container, inventory, candidate.EndpointId)) { inventory = null; return false; } return true; } catch { inventory = null; return false; } } } return false; } private static bool SourceIsCurrent(ResourceSource source, Player player, float rangeMeters) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)source.Container == (Object)null) { if (CanMutatePlayer(player)) { return source.Inventory == ((Humanoid)player).GetInventory(); } return false; } if (TryGetExactWritableInventory(new NearbySeedContainerCandidate(source.Container, source.EndpointId, source.DistanceSquared), player, ((Component)player).transform.position, rangeMeters, out var inventory)) { return inventory == source.Inventory; } return false; } private static bool SourceAuthorityStillCurrent(ResourceSource source, Player player, float rangeMeters) { //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_0169: 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) if ((Object)(object)source.Container == (Object)null) { if (CanMutatePlayer(player)) { return source.Inventory == ((Humanoid)player).GetInventory(); } return false; } Container container = source.Container; float num = Mathf.Clamp(rangeMeters, 1f, 30f); if (CanMutatePlayer(player) && !((Object)(object)ZNet.instance == (Object)null) && !((Object)(object)container == (Object)null) && ((Behaviour)container).isActiveAndEnabled && NearbySeedContainerIndex.IsStaticChest(container)) { Vector3 val = ((Component)container).transform.position - ((Component)player).transform.position; if (!(((Vector3)(ref val)).sqrMagnitude > num * num) && !container.IsInUse() && container.IsOwner() && !(LoadingField == null) && !(CheckAccessMethod == null) && container.GetInventory() == source.Inventory) { if (!PrivateArea.CheckAccess(((Component)container).transform.position, 0f, false, false)) { return false; } try { if ((bool)LoadingField.GetValue(container) || !(bool)CheckAccessMethod.Invoke(container, new object[1] { player.GetPlayerID() })) { return false; } ZNetView val2 = ContainerNetworkView(container); ZDO val3 = (((Object)(object)val2 != (Object)null && val2.IsValid() && val2.IsOwner()) ? val2.GetZDO() : null); return val3 != null && val3.IsValid() && val3.m_uid == source.EndpointId && val3.GetOwner() == ZNet.GetUID(); } catch { return false; } } } return false; } private static bool InventoryMatchesZdo(Container container, Inventory inventory, ZDOID endpointId) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) ZNetView val = ContainerNetworkView(container); ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid() && val.IsOwner()) ? val.GetZDO() : null); if (val2 == null || val2.m_uid != endpointId || val2.GetOwner() != ZNet.GetUID()) { return false; } string text = val2.GetString(ZDOVars.s_items, string.Empty); string @base = Save(inventory).GetBase64(); if (!string.IsNullOrEmpty(text)) { return string.Equals(@base, text, StringComparison.Ordinal); } return inventory.GetAllItems().Count == 0; } private static bool CanMutatePlayer(Player player) { if ((Object)(object)player != (Object)null && player == Player.m_localPlayer) { return ((Character)player).IsOwner(); } return false; } private static bool IsTouched(Inventory inventory, IReadOnlyList removals) { for (int i = 0; i < removals.Count; i++) { if (inventory == removals[i].Source.Inventory) { return true; } } return false; } private static ZPackage Save(Inventory inventory) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); inventory.Save(val); return val; } private static void Restore(Player player, float rangeMeters, IReadOnlyList snapshots) { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Expected O, but got Unknown //IL_00f3: Unknown result type (might be due to invalid IL or missing references) List list = new List(); for (int num = snapshots.Count - 1; num >= 0; num--) { ResourceSnapshot resourceSnapshot = snapshots[num]; try { if (!SourceIsCurrent(resourceSnapshot.Source, player, rangeMeters)) { throw new InvalidOperationException("A planting resource endpoint lost authority before rollback."); } string text = Convert.ToBase64String(resourceSnapshot.Payload); ValidateSnapshotRoundTrip(resourceSnapshot.Source.Inventory, resourceSnapshot.Payload, text); resourceSnapshot.Source.Inventory.Load(new ZPackage(resourceSnapshot.Payload)); if (!string.Equals(Save(resourceSnapshot.Source.Inventory).GetBase64(), text, StringComparison.Ordinal)) { throw new InvalidOperationException("A planting resource snapshot did not restore exactly."); } if ((Object)(object)resourceSnapshot.Source.Container != (Object)null && !InventoryMatchesZdo(resourceSnapshot.Source.Container, resourceSnapshot.Source.Inventory, resourceSnapshot.Source.EndpointId)) { throw new InvalidOperationException("A restored planting chest did not publish exactly."); } } catch (Exception item) { list.Add(item); } } if (list.Count > 0) { throw new AggregateException("One or more planting-resource rollbacks failed.", list); } } private static void ValidateSnapshotRoundTrip(Inventory shape, byte[] payload, string expected) { //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_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown //IL_0042: Expected O, but got Unknown if (shape == null || payload == null || string.IsNullOrEmpty(expected)) { throw new InvalidOperationException("A planting resource snapshot is unavailable."); } Inventory val = new Inventory(shape.GetName(), (Sprite)null, shape.GetWidth(), shape.GetHeight()); val.Load(new ZPackage(payload)); if (!string.Equals(Save(val).GetBase64(), expected, StringComparison.Ordinal)) { throw new InvalidOperationException("A planting resource snapshot cannot round-trip exactly."); } } private static int AddSaturated(int left, int right) { if (left <= int.MaxValue - right) { return left + right; } return int.MaxValue; } } [HarmonyPatch(typeof(Container), "Awake")] internal static class AgricultureSeedContainerAwakePatch { private static void Postfix(Container __instance) { NearbySeedContainerIndex.Register(__instance); } } [HarmonyPatch(typeof(Container), "OnDestroyed")] internal static class AgricultureSeedContainerDestroyedPatch { private static void Postfix(Container __instance, bool __runOriginal) { if (__runOriginal) { NearbySeedContainerIndex.Unregister(__instance); } } } [HarmonyPatch(typeof(Container), "CheckForChanges")] internal static class AgricultureSeedContainerRefreshPatch { private static void Postfix(Container __instance) { NearbySeedContainerIndex.Register(__instance); } } internal readonly struct RuntimePreviewPosition { internal Vector3 Position { get; } internal Quaternion Rotation { get; } internal bool IsValid { get; } internal bool IsResourceShortage => string.Equals(ReasonCode, "agriculture.no-seeds", StringComparison.Ordinal); internal bool IsGroundValid { get { if (!IsValid) { return IsResourceShortage; } return true; } } internal string ReasonCode { get; } internal RuntimePreviewPosition(Vector3 position, Quaternion rotation, bool isValid, string reasonCode) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) Position = position; Rotation = rotation; IsValid = isValid; ReasonCode = reasonCode; } } internal sealed class PreviewPool : IDisposable { private readonly struct SourceRendererState { internal GameObject Source { get; } internal Renderer Renderer { get; } internal bool WasEnabled { get; } internal SourceRendererState(GameObject source, Renderer renderer, bool wasEnabled) { Source = source; Renderer = renderer; WasEnabled = wasEnabled; } } private readonly int _hardMaximum; private readonly List _ghosts = new List(); private readonly List _sourceRenderers = new List(); private static readonly int ColorProperty = Shader.PropertyToID("_Color"); private static readonly int EmissionColorProperty = Shader.PropertyToID("_EmissionColor"); private static MaterialPropertyBlock _blockedHighlight; private static MaterialPropertyBlock _shortageHighlight; private string _sourceIdentity; internal int AllocatedCount => _ghosts.Count; internal PreviewPool(int hardMaximum) { if (hardMaximum < 1) { throw new ArgumentOutOfRangeException("hardMaximum"); } _hardMaximum = hardMaximum; } internal void Show(GameObject sourceGhost, IReadOnlyList positions) { //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)sourceGhost == (Object)null || positions == null) { Hide(); return; } string text = PrefabIdentity.Of(sourceGhost); if (!string.Equals(_sourceIdentity, text, StringComparison.Ordinal)) { DestroyGhosts(); _sourceIdentity = text; } PreviewPoolMaintenance.PruneUnavailable(_ghosts, (GameObject ghost) => (Object)(object)ghost == (Object)null); int num = Math.Min(_hardMaximum, positions.Count); if (num > _ghosts.Count) { RestoreSourceRenderers(); Grow(sourceGhost, num); } HideSourceRenderers(sourceGhost); for (int num2 = 0; num2 < _ghosts.Count; num2++) { GameObject val = _ghosts[num2]; if (num2 >= num) { val.SetActive(false); continue; } RuntimePreviewPosition preview = positions[num2]; val.transform.SetPositionAndRotation(preview.Position, preview.Rotation); val.SetActive(true); SetPreviewHighlight(val, preview); } } internal void Hide() { for (int i = 0; i < _ghosts.Count; i++) { if ((Object)(object)_ghosts[i] != (Object)null) { _ghosts[i].SetActive(false); } } RestoreSourceRenderers(); } public void Dispose() { DestroyGhosts(); } private void Grow(GameObject sourceGhost, int required) { while (_ghosts.Count < required && _ghosts.Count < _hardMaximum) { GameObject item = CreateRendererOnlyProxy(sourceGhost); _ghosts.Add(item); } } private static GameObject CreateRendererOnlyProxy(GameObject source) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected O, but got Unknown //IL_002a: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("RunicAgriculturePreview"); val.SetActive(false); val.layer = source.layer; val.transform.localScale = source.transform.lossyScale; Dictionary transformMap = new Dictionary { [source.transform] = val.transform }; CopyTransforms(source.transform, val.transform, transformMap); Renderer[] componentsInChildren = source.GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { CopyRenderer(componentsInChildren[i], transformMap); } return val; } private static void CopyTransforms(Transform source, Transform destination, IDictionary transformMap) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected O, but got Unknown //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < source.childCount; i++) { Transform child = source.GetChild(i); GameObject val = new GameObject(((Object)((Component)child).gameObject).name + ".Preview"); val.layer = ((Component)child).gameObject.layer; val.transform.SetParent(destination, false); val.transform.localPosition = child.localPosition; val.transform.localRotation = child.localRotation; val.transform.localScale = child.localScale; val.SetActive(((Component)child).gameObject.activeSelf); transformMap[child] = val.transform; CopyTransforms(child, val.transform, transformMap); } } private static void CopyRenderer(Renderer source, IReadOnlyDictionary transformMap) { //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)source == (Object)null || !transformMap.TryGetValue(((Component)source).transform, out var value)) { return; } Renderer val = null; if (source is MeshRenderer) { MeshFilter component = ((Component)source).GetComponent(); if ((Object)(object)component == (Object)null || (Object)(object)component.sharedMesh == (Object)null) { return; } ((Component)value).gameObject.AddComponent().sharedMesh = component.sharedMesh; val = (Renderer)(object)((Component)value).gameObject.AddComponent(); } else { SkinnedMeshRenderer val2 = (SkinnedMeshRenderer)(object)((source is SkinnedMeshRenderer) ? source : null); if (val2 != null) { SkinnedMeshRenderer val3 = ((Component)value).gameObject.AddComponent(); val3.sharedMesh = val2.sharedMesh; ((Renderer)val3).localBounds = ((Renderer)val2).localBounds; val3.updateWhenOffscreen = val2.updateWhenOffscreen; val3.quality = val2.quality; if ((Object)(object)val2.rootBone != (Object)null && transformMap.TryGetValue(val2.rootBone, out var value2)) { val3.rootBone = value2; } Transform[] bones = val2.bones; Transform[] array = (Transform[])(object)new Transform[bones.Length]; for (int i = 0; i < bones.Length; i++) { if ((Object)(object)bones[i] != (Object)null && transformMap.TryGetValue(bones[i], out var value3)) { array[i] = value3; } } val3.bones = array; val = (Renderer)(object)val3; } } if (!((Object)(object)val == (Object)null)) { val.sharedMaterials = source.sharedMaterials; val.enabled = source.enabled; val.shadowCastingMode = source.shadowCastingMode; val.receiveShadows = source.receiveShadows; val.lightProbeUsage = source.lightProbeUsage; val.reflectionProbeUsage = source.reflectionProbeUsage; val.sortingLayerID = source.sortingLayerID; val.sortingOrder = source.sortingOrder; } } private static void SetPreviewHighlight(GameObject ghost, RuntimePreviewPosition preview) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Expected O, but got Unknown //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) if (!preview.IsGroundValid && _blockedHighlight == null) { Color val = default(Color); ((Color)(ref val))..ctor(0.62f, 0.43f, 0.2f, 0.72f); _blockedHighlight = new MaterialPropertyBlock(); _blockedHighlight.SetColor(ColorProperty, val); _blockedHighlight.SetColor(EmissionColorProperty, val * 0.4f); } if (preview.IsResourceShortage && _shortageHighlight == null) { Color val2 = default(Color); ((Color)(ref val2))..ctor(1f, 0.08f, 0.06f, 0.82f); _shortageHighlight = new MaterialPropertyBlock(); _shortageHighlight.SetColor(ColorProperty, val2); _shortageHighlight.SetColor(EmissionColorProperty, val2 * 0.65f); } MaterialPropertyBlock propertyBlock = (preview.IsValid ? null : (preview.IsResourceShortage ? _shortageHighlight : _blockedHighlight)); Renderer[] componentsInChildren = ghost.GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { componentsInChildren[i].SetPropertyBlock(propertyBlock); } } private void DestroyGhosts() { RestoreSourceRenderers(); for (int i = 0; i < _ghosts.Count; i++) { if ((Object)(object)_ghosts[i] != (Object)null) { Object.Destroy((Object)(object)_ghosts[i]); } } _ghosts.Clear(); _sourceIdentity = null; } private void HideSourceRenderers(GameObject sourceGhost) { if (_sourceRenderers.Count > 0 && _sourceRenderers[0].Source == sourceGhost) { for (int i = 0; i < _sourceRenderers.Count; i++) { if ((Object)(object)_sourceRenderers[i].Renderer != (Object)null) { _sourceRenderers[i].Renderer.enabled = false; } } return; } RestoreSourceRenderers(); Renderer[] componentsInChildren = sourceGhost.GetComponentsInChildren(true); foreach (Renderer val in componentsInChildren) { _sourceRenderers.Add(new SourceRendererState(sourceGhost, val, val.enabled)); val.enabled = false; } } private void RestoreSourceRenderers() { for (int i = 0; i < _sourceRenderers.Count; i++) { SourceRendererState sourceRendererState = _sourceRenderers[i]; if ((Object)(object)sourceRendererState.Renderer != (Object)null) { sourceRendererState.Renderer.enabled = sourceRendererState.WasEnabled; } } _sourceRenderers.Clear(); } } internal static class ValheimAccess { private delegate bool PlayerInputDelegate(Player player); private static readonly FieldInfo PlacementGhostField = AccessTools.Field(typeof(Player), "m_placementGhost"); private static readonly FieldInfo PlaceRotationDegreesField = AccessTools.Field(typeof(Player), "m_placeRotationDegrees"); private static readonly MethodInfo GetBuildStaminaMethod = AccessTools.Method(typeof(Player), "GetBuildStamina", Type.EmptyTypes, (Type[])null); private static readonly MethodInfo GetPlaceDurabilityMethod = AccessTools.Method(typeof(Player), "GetPlaceDurability", new Type[1] { typeof(ItemData) }, (Type[])null); private static readonly MethodInfo InventoryChangedMethod = AccessTools.Method(typeof(Inventory), "Changed", Type.EmptyTypes, (Type[])null); private static readonly PlayerInputDelegate TakeInput = ResolveTakeInput(); private static readonly MethodInfo PlantGrowTimeMethod = AccessTools.Method(typeof(Plant), "GetGrowTime", Type.EmptyTypes, (Type[])null); private static readonly MethodInfo PlantAgeMethod = AccessTools.Method(typeof(Plant), "TimeSincePlanted", Type.EmptyTypes, (Type[])null); private static readonly MethodInfo BeeHoneyMethod = AccessTools.Method(typeof(Beehive), "GetHoneyLevel", Type.EmptyTypes, (Type[])null); private static readonly MethodInfo BeeBiomeMethod = AccessTools.Method(typeof(Beehive), "CheckBiome", Type.EmptyTypes, (Type[])null); private static readonly MethodInfo BeeSpaceMethod = AccessTools.Method(typeof(Beehive), "HaveFreeSpace", Type.EmptyTypes, (Type[])null); private static readonly MethodInfo GetControllerButtonMethod = AccessTools.Method(typeof(ZInput), "GetButton", new Type[1] { typeof(string) }, (Type[])null); private static readonly MethodInfo GetControllerButtonDownMethod = AccessTools.Method(typeof(ZInput), "GetButtonDown", new Type[1] { typeof(string) }, (Type[])null); private static readonly MethodInfo GetControllerButtonDefinitionMethod = AccessTools.Method(typeof(ZInput), "GetButtonDef", new Type[1] { typeof(string) }, (Type[])null); private static readonly MethodInfo GetControllerActionPathMethod = AccessTools.Method(typeof(ButtonDef), "GetActionPath", new Type[1] { typeof(bool) }, (Type[])null); private static readonly MethodInfo GetKeyboardKeyMethod = AccessTools.Method(typeof(ZInput), "GetKey", new Type[2] { typeof(KeyCode), typeof(bool) }, (Type[])null); private static readonly MethodInfo GetKeyboardKeyDownMethod = AccessTools.Method(typeof(ZInput), "GetKeyDown", new Type[2] { typeof(KeyCode), typeof(bool) }, (Type[])null); private static readonly MethodInfo GetBoundKeyStringMethod = AccessTools.Method(typeof(ZInput), "GetBoundKeyString", new Type[2] { typeof(string), typeof(bool) }, (Type[])null); private static readonly MethodInfo GetMouseScrollWheelMethod = AccessTools.Method(typeof(ZInput), "GetMouseScrollWheel", Type.EmptyTypes, (Type[])null); internal static void Verify() { if (PlacementGhostField == null || PlaceRotationDegreesField == null || PlaceRotationDegreesField.FieldType != typeof(float) || GetBuildStaminaMethod == null || GetPlaceDurabilityMethod == null || InventoryChangedMethod == null || TakeInput == null || PlantGrowTimeMethod == null || PlantAgeMethod == null || BeeHoneyMethod == null || BeeBiomeMethod == null || BeeSpaceMethod == null || GetControllerButtonMethod == null || GetControllerButtonDownMethod == null || GetControllerButtonDefinitionMethod == null || GetControllerActionPathMethod == null || GetKeyboardKeyMethod == null || GetKeyboardKeyDownMethod == null || GetBoundKeyStringMethod == null || GetMouseScrollWheelMethod == null) { throw new MissingMemberException("Valheim placement or ZInput signatures do not match the audited 0.221.12 contract."); } } internal static GameObject GetPlacementGhost(Player player) { object? obj = PlacementGhostField?.GetValue(player); return (GameObject)((obj is GameObject) ? obj : null); } internal static float PlaceRotationDegrees(Player player) { if (!((Object)(object)player == (Object)null)) { return Convert.ToSingle(PlaceRotationDegreesField.GetValue(player)); } return 22.5f; } internal static float GetBuildStamina(Player player) { return Convert.ToSingle(GetBuildStaminaMethod.Invoke(player, Array.Empty())); } internal static float GetPlaceDurability(Player player, ItemData tool) { return Convert.ToSingle(GetPlaceDurabilityMethod.Invoke(player, new object[1] { tool })); } internal static void NotifyInventoryChanged(Inventory inventory) { if (inventory == null) { throw new ArgumentNullException("inventory"); } InventoryChangedMethod.Invoke(inventory, Array.Empty()); } internal static bool PlayerTakesInput(Player player) { if ((Object)(object)player != (Object)null) { return TakeInput(player); } return false; } private static PlayerInputDelegate ResolveTakeInput() { try { MethodInfo methodInfo = AccessTools.Method(typeof(Player), "TakeInput", Type.EmptyTypes, (Type[])null); return (methodInfo == null) ? null : AccessTools.MethodDelegate(methodInfo, (object)null, true); } catch (Exception) { return null; } } internal static double PlantGrowTime(Plant plant) { return Convert.ToDouble(PlantGrowTimeMethod.Invoke(plant, Array.Empty())); } internal static double PlantAge(Plant plant) { return Convert.ToDouble(PlantAgeMethod.Invoke(plant, Array.Empty())); } internal static int BeeHoney(Beehive hive) { return Convert.ToInt32(BeeHoneyMethod.Invoke(hive, Array.Empty())); } internal static bool BeeBiomeValid(Beehive hive) { return Convert.ToBoolean(BeeBiomeMethod.Invoke(hive, Array.Empty())); } internal static bool BeeHasSpace(Beehive hive) { return Convert.ToBoolean(BeeSpaceMethod.Invoke(hive, Array.Empty())); } internal static bool ControllerButtonHeldRaw(ValheimControllerAction action) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Invalid comparison between Unknown and I4 try { ButtonDef val = ControllerButtonDefinition(action); return val != null && (int)val.Source == 180 && val.Held; } catch (Exception) { return false; } } internal static bool ShortcutDown(KeyboardShortcut shortcut) { //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_0031: 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_003a: Unknown result type (might be due to invalid IL or missing references) if ((int)((KeyboardShortcut)(ref shortcut)).MainKey == 0 || !ZInput.GetKeyDown(((KeyboardShortcut)(ref shortcut)).MainKey, false)) { return false; } IEnumerable modifiers = ((KeyboardShortcut)(ref shortcut)).Modifiers; if (modifiers == null) { return true; } foreach (KeyCode item in modifiers) { if ((int)item == 0 || !ZInput.GetKey(item, false)) { return false; } } return true; } internal static bool KeyHeld(KeyCode left, KeyCode right) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) if (!ZInput.GetKey(left, false)) { return ZInput.GetKey(right, false); } return true; } internal static bool KeyDown(KeyCode key) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return ZInput.GetKeyDown(key, false); } internal static float MouseWheel() { return ZInput.GetMouseScrollWheel(); } internal static bool ButtonDown(string action) { if (!string.IsNullOrWhiteSpace(action)) { return ZInput.GetButtonDown(action); } return false; } internal static string BoundKeyLabel(string action, bool gamepad, string fallback) { try { ZInput instance = ZInput.instance; string text = ((instance == null) ? null : instance.GetBoundKeyString(action, gamepad)?.Trim()); return string.IsNullOrEmpty(text) ? fallback : text; } catch (Exception) { return fallback; } } internal static string ControllerChordLabel(AgricultureControllerBindings bindings, ValheimControllerAction primary) { if (bindings == null) { throw new ArgumentNullException("bindings"); } return ControllerControlLabel(bindings.Modifier) + " + " + ControllerControlLabel(primary); } internal static string ControllerControlLabel(ValheimControllerAction action) { string text = ControllerActionDisplay.Friendly(action); try { return ControllerPathLabel(ControllerEffectivePath(ControllerButtonDefinition(action)), text); } catch (Exception) { return text; } } internal static string ControllerPathLabel(string path, string fallback) { if (string.IsNullOrWhiteSpace(path)) { return fallback; } string text = path.Trim().Replace('\\', '/').ToLowerInvariant(); if (text.EndsWith("/buttonsouth", StringComparison.Ordinal)) { return "A / Cross"; } if (text.EndsWith("/buttoneast", StringComparison.Ordinal)) { return "B / Circle"; } if (text.EndsWith("/buttonwest", StringComparison.Ordinal)) { return "X / Square"; } if (text.EndsWith("/buttonnorth", StringComparison.Ordinal)) { return "Y / Triangle"; } if (text.EndsWith("/leftshoulder", StringComparison.Ordinal)) { return "LB / L1"; } if (text.EndsWith("/rightshoulder", StringComparison.Ordinal)) { return "RB / R1"; } if (text.EndsWith("/lefttrigger", StringComparison.Ordinal)) { return "LT / L2"; } if (text.EndsWith("/righttrigger", StringComparison.Ordinal)) { return "RT / R2"; } if (text.EndsWith("/leftstickpress", StringComparison.Ordinal)) { return "LS / L3"; } if (text.EndsWith("/rightstickpress", StringComparison.Ordinal)) { return "RS / R3"; } if (text.EndsWith("/dpad/up", StringComparison.Ordinal)) { return "D-pad Up"; } if (text.EndsWith("/dpad/down", StringComparison.Ordinal)) { return "D-pad Down"; } if (text.EndsWith("/dpad/left", StringComparison.Ordinal)) { return "D-pad Left"; } if (text.EndsWith("/dpad/right", StringComparison.Ordinal)) { return "D-pad Right"; } if (text.EndsWith("/start", StringComparison.Ordinal)) { return "Menu / Options"; } if (text.EndsWith("/select", StringComparison.Ordinal)) { return "View / Share"; } return fallback; } internal static bool ControllerButtonDownRaw(ValheimControllerAction action) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Invalid comparison between Unknown and I4 try { ButtonDef val = ControllerButtonDefinition(action); return val != null && (int)val.Source == 180 && val.Pressed; } catch (Exception) { return false; } } internal static ButtonDef ControllerButtonDefinition(ValheimControllerAction action) { ZInput instance = ZInput.instance; if (instance == null) { return null; } return instance.GetButtonDef(action.ToString()); } internal static string ControllerEffectivePath(ButtonDef definition) { if (definition == null) { return null; } return definition.GetActionPath(true)?.Trim(); } internal static bool TryVerifyControllerActions(AgricultureControllerBindings bindings, out string problem, out string pathSignature) { //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Invalid comparison between Unknown and I4 pathSignature = string.Empty; if (ZInput.instance == null) { problem = "Valheim ZInput is not initialized"; return false; } if (!bindings.TryValidate(out problem)) { return false; } ValheimControllerAction[] array = new ValheimControllerAction[8] { bindings.Modifier, bindings.Confirm, bindings.Cycle, bindings.AreaHarvest, bindings.PreviousEditorField, bindings.NextEditorField, bindings.DecreaseEditorValue, bindings.IncreaseEditorValue }; ButtonDef[] array2 = (ButtonDef[])(object)new ButtonDef[array.Length]; string[] array3 = new string[array.Length]; for (int i = 0; i < array.Length; i++) { string text = array[i].ToString(); try { array2[i] = ZInput.instance.GetButtonDef(text); } catch (Exception ex) { problem = "Valheim input action '" + text + "' could not be queried (" + ex.GetType().Name + ")"; return false; } if (array2[i] == null) { problem = "Valheim input action '" + text + "' is unavailable"; return false; } if ((int)array2[i].Source != 180) { problem = "Valheim input action '" + text + "' is not a gamepad action"; return false; } try { array3[i] = array2[i].GetActionPath(true)?.Trim(); } catch (Exception ex2) { problem = "Valheim input action '" + text + "' has no queryable effective path (" + ex2.GetType().Name + ")"; return false; } } if (!bindings.TryValidateEffectivePaths(array3[0], array3[1], array3[2], array3[3], array3[4], array3[5], array3[6], array3[7], out problem)) { return false; } StringBuilder stringBuilder = new StringBuilder(); for (int j = 0; j < array.Length; j++) { if (j > 0) { stringBuilder.Append('|'); } stringBuilder.Append(array[j]).Append('=').Append(array3[j]); } pathSignature = stringBuilder.ToString(); return true; } } internal static class PrefabIdentity { internal static string Of(GameObject gameObject) { if ((Object)(object)gameObject == (Object)null) { return string.Empty; } string text = ((Object)gameObject).name ?? string.Empty; if (text.EndsWith("(Clone)", StringComparison.Ordinal)) { text = text.Substring(0, text.Length - "(Clone)".Length); } return text.Trim(); } } internal readonly struct RuntimePlacementValidation { internal Vector3 Position { get; } internal Vector3 GroundNormal { get; } internal Biome Biome { get; } internal PlacementValidationResult Result { get; } internal RuntimePlacementValidation(Vector3 position, Vector3 groundNormal, Biome biome, PlacementValidationResult result) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000f: 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) Position = position; GroundNormal = groundNormal; Biome = biome; Result = result; } } internal sealed class ValheimPlacementValidator { private readonly Collider[] _spacingHits = (Collider[])(object)new Collider[96]; private readonly List _nearbyCharacters = new List(16); internal RuntimePlacementValidation Validate(Player player, Piece piece, Vector3 requested, Quaternion rotation) { //IL_0060: 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_015a: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: 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_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_009a: 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_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Invalid comparison between Unknown and I4 //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || (Object)(object)piece == (Object)null || (Object)(object)ZoneSystem.instance == (Object)null) { return Invalid(requested, "agriculture.terrain-unavailable"); } Plant component = ((Component)piece).GetComponent(); if ((Object)(object)component == (Object)null) { return Invalid(requested, "agriculture.crop-changed"); } Vector3 val = requested; Vector3 val2 = default(Vector3); Biome val3 = default(Biome); Heightmap val5 = default(Heightmap); try { BiomeArea val4 = default(BiomeArea); ZoneSystem.instance.GetGroundData(ref val, ref val2, ref val3, ref val4, ref val5); } catch (Exception) { return Invalid(requested, "agriculture.terrain-unavailable"); } bool flag = (Object)(object)val5 != (Object)null; bool slopeValid = !piece.m_notOnTiltingSurface || val2.y >= 0.8f; Biome val6 = (((int)piece.m_onlyInBiome != 0) ? piece.m_onlyInBiome : component.m_biome); bool biomeValid = (int)val6 == 0 || (val3 & val6) > 0; bool cultivated = (!piece.m_cultivatedGroundOnly && !component.m_needCultivatedGround) || (flag && val5.IsCultivated(val)); float liquidLevel = Floating.GetLiquidLevel(val, 1f, (LiquidType)10); bool waterClear = !piece.m_noInWater || liquidLevel <= val.y + 0.02f; bool spacingClear = IsSpacingClear(component, val); bool inRange = PlacementRange.IsWithin(Vector3.Distance(((Character)player).GetEyePoint(), val), player.m_maxPlaceDistance, piece.m_extraPlacementDistance); bool authorized; try { authorized = PrivateArea.CheckAccess(val, 0f, false, false); } catch (Exception) { authorized = false; } bool worldBuildAllowed = !Location.IsInsideNoBuildLocation(val) && (piece.m_allowedInDungeons || !Character.InInterior(val)); bool playersClear = AreCharactersClear(player, val, rotation); return new RuntimePlacementValidation(result: PlacementValidation.Evaluate(new PlacementValidationInputs(flag, slopeValid, biomeValid, cultivated, spacingClear, inRange, authorized, worldBuildAllowed, playersClear, waterClear)), position: val, groundNormal: val2, biome: val3); } internal float RequiredSpacing(Piece piece) { Plant val = (((Object)(object)piece != (Object)null) ? ((Component)piece).GetComponent() : null); if (!((Object)(object)val != (Object)null)) { return 0.1f; } return Math.Max(0.1f, val.m_growRadius); } private bool IsSpacingClear(Plant candidate, Vector3 position) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) float num = Math.Max(0.1f, candidate.m_growRadius); int mask = LayerMask.GetMask(new string[5] { "Default", "static_solid", "Default_small", "piece", "piece_nonsolid" }); int num2 = Physics.OverlapSphereNonAlloc(position, num, _spacingHits, mask, (QueryTriggerInteraction)2); if (num2 >= _spacingHits.Length) { return false; } for (int i = 0; i < num2; i++) { Collider val = _spacingHits[i]; _spacingHits[i] = null; if (!((Object)(object)val == (Object)null) && val.enabled) { Plant val2 = ((Component)val).GetComponent(); if ((Object)(object)val2 == (Object)null) { val2 = ((Component)val).GetComponentInParent(); } if (!((Object)(object)val2 != (Object)null) || !((Component)val2).gameObject.activeInHierarchy || (int)val2.GetStatus() == 0) { return false; } } } if (candidate.m_growRadiusVines > 0f) { num2 = Physics.OverlapSphereNonAlloc(position, candidate.m_growRadiusVines, _spacingHits, mask, (QueryTriggerInteraction)2); if (num2 >= _spacingHits.Length) { return false; } for (int j = 0; j < num2; j++) { Collider val3 = _spacingHits[j]; _spacingHits[j] = null; if ((Object)(object)val3 != (Object)null && (Object)(object)((Component)val3).GetComponentInParent() != (Object)null) { return false; } } } return true; } private bool AreCharactersClear(Player player, Vector3 position, Quaternion rotation) { //IL_001d: 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_0041: 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_0052: 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_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: 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_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0131: 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_0148: Unknown result type (might be due to invalid IL or missing references) GameObject placementGhost = ValheimAccess.GetPlacementGhost(player); if ((Object)(object)placementGhost == (Object)null) { return false; } _nearbyCharacters.Clear(); Character.GetCharactersInRange(position, 30f, _nearbyCharacters); Collider[] componentsInChildren = placementGhost.GetComponentsInChildren(true); Quaternion val = Quaternion.Inverse(placementGhost.transform.rotation); Vector3 lossyScale = placementGhost.transform.lossyScale; Vector3 val8 = default(Vector3); float num = default(float); foreach (Collider val2 in componentsInChildren) { if ((Object)(object)val2 == (Object)null || !val2.enabled || val2.isTrigger || (Object)(object)((Component)val2).gameObject == (Object)(object)placementGhost) { continue; } MeshCollider val3 = (MeshCollider)(object)((val2 is MeshCollider) ? val2 : null); if (val3 != null && !val3.convex) { continue; } Vector3 val4 = placementGhost.transform.InverseTransformPoint(((Component)val2).transform.position); Vector3 val5 = position + rotation * Vector3.Scale(val4, lossyScale); Quaternion val6 = rotation * val * ((Component)val2).transform.rotation; for (int j = 0; j < _nearbyCharacters.Count; j++) { Character obj = _nearbyCharacters[j]; CapsuleCollider val7 = ((obj != null) ? obj.GetCollider() : null); if (!((Object)(object)val7 == (Object)null) && ((Collider)val7).enabled && Physics.ComputePenetration(val2, val5, val6, (Collider)(object)val7, ((Component)val7).transform.position, ((Component)val7).transform.rotation, ref val8, ref num)) { return false; } } } return true; } private static RuntimePlacementValidation Invalid(Vector3 position, string reason) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) return new RuntimePlacementValidation(position, Vector3.up, (Biome)0, new PlacementValidationResult(isValid: false, reason)); } } } namespace RunicAgriculture.Core { public enum ValheimControllerAction { JoyAltKeys, JoyPlace, JoyRotate, JoyUse, JoyRemove, JoyButtonA, JoyButtonB, JoyButtonX, JoyButtonY, JoyDPadUp, JoyDPadDown, JoyDPadLeft, JoyDPadRight, JoyLBumper, JoyRBumper, JoyLTrigger, JoyRTrigger, JoyLStick, JoyRStick, JoyPrevSnap } public sealed class AgricultureControllerBindings { public ValheimControllerAction Modifier { get; } public ValheimControllerAction Confirm { get; } public ValheimControllerAction Cycle { get; } public ValheimControllerAction AreaHarvest { get; } public ValheimControllerAction PreviousEditorField { get; } public ValheimControllerAction NextEditorField { get; } public ValheimControllerAction DecreaseEditorValue { get; } public ValheimControllerAction IncreaseEditorValue { get; } public string ConfirmChord => FormatChord(Confirm); public string CycleChord => FormatChord(Cycle); public string AreaHarvestChord => FormatChord(AreaHarvest); public AgricultureControllerBindings(ValheimControllerAction modifier, ValheimControllerAction confirm, ValheimControllerAction cycle, ValheimControllerAction areaHarvest) : this(modifier, confirm, cycle, areaHarvest, ValheimControllerAction.JoyDPadUp, ValheimControllerAction.JoyDPadDown, ValheimControllerAction.JoyDPadLeft, ValheimControllerAction.JoyDPadRight) { } public AgricultureControllerBindings(ValheimControllerAction modifier, ValheimControllerAction confirm, ValheimControllerAction cycle, ValheimControllerAction areaHarvest, ValheimControllerAction previousEditorField, ValheimControllerAction nextEditorField, ValheimControllerAction decreaseEditorValue, ValheimControllerAction increaseEditorValue) { if (!Enum.IsDefined(typeof(ValheimControllerAction), modifier)) { throw new ArgumentOutOfRangeException("modifier"); } if (!Enum.IsDefined(typeof(ValheimControllerAction), confirm)) { throw new ArgumentOutOfRangeException("confirm"); } if (!Enum.IsDefined(typeof(ValheimControllerAction), cycle)) { throw new ArgumentOutOfRangeException("cycle"); } if (!Enum.IsDefined(typeof(ValheimControllerAction), areaHarvest)) { throw new ArgumentOutOfRangeException("areaHarvest"); } if (!Enum.IsDefined(typeof(ValheimControllerAction), previousEditorField)) { throw new ArgumentOutOfRangeException("previousEditorField"); } if (!Enum.IsDefined(typeof(ValheimControllerAction), nextEditorField)) { throw new ArgumentOutOfRangeException("nextEditorField"); } if (!Enum.IsDefined(typeof(ValheimControllerAction), decreaseEditorValue)) { throw new ArgumentOutOfRangeException("decreaseEditorValue"); } if (!Enum.IsDefined(typeof(ValheimControllerAction), increaseEditorValue)) { throw new ArgumentOutOfRangeException("increaseEditorValue"); } Modifier = modifier; Confirm = confirm; Cycle = cycle; AreaHarvest = areaHarvest; PreviousEditorField = previousEditorField; NextEditorField = nextEditorField; DecreaseEditorValue = decreaseEditorValue; IncreaseEditorValue = increaseEditorValue; } public bool TryValidate(out string problem) { ValheimControllerAction[] array = AllActions(); for (int i = 1; i < array.Length; i++) { if (array[i] == Modifier) { problem = "the controller modifier must differ from every action"; return false; } for (int j = i + 1; j < array.Length; j++) { if (array[i] == array[j]) { problem = "every controller agriculture action must use a different control"; return false; } } } problem = string.Empty; return true; } public bool TryValidateEffectivePaths(string modifierPath, string confirmPath, string cyclePath, string areaHarvestPath, out string problem) { return TryValidatePathSet(new string[4] { "modifier", "confirm", "cycle", "area harvest" }, new string[4] { modifierPath, confirmPath, cyclePath, areaHarvestPath }, out problem); } public bool TryValidateEffectivePaths(string modifierPath, string confirmPath, string cyclePath, string areaHarvestPath, string previousEditorFieldPath, string nextEditorFieldPath, string decreaseEditorValuePath, string increaseEditorValuePath, out string problem) { return TryValidatePathSet(new string[8] { "modifier", "confirm", "cycle", "area harvest", "previous editor field", "next editor field", "decrease editor value", "increase editor value" }, new string[8] { modifierPath, confirmPath, cyclePath, areaHarvestPath, previousEditorFieldPath, nextEditorFieldPath, decreaseEditorValuePath, increaseEditorValuePath }, out problem); } public bool ContainsChordPrimary(ValheimControllerAction action) { if (action != Confirm && action != Cycle) { return action == AreaHarvest; } return true; } public bool ContainsEditorPrimary(ValheimControllerAction action) { if (action != PreviousEditorField && action != NextEditorField && action != DecreaseEditorValue) { return action == IncreaseEditorValue; } return true; } private bool TryValidatePathSet(string[] labels, string[] paths, out string problem) { if (!TryValidate(out problem)) { return false; } Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); for (int i = 0; i < paths.Length; i++) { string text = paths[i]?.Trim(); if (string.IsNullOrEmpty(text)) { problem = labels[i] + " has no effective controller action path"; return false; } if (dictionary.TryGetValue(text, out var value)) { problem = labels[i] + " resolves to the same physical control as " + value; return false; } dictionary.Add(text, labels[i]); } problem = string.Empty; return true; } private ValheimControllerAction[] AllActions() { return new ValheimControllerAction[8] { Modifier, Confirm, Cycle, AreaHarvest, PreviousEditorField, NextEditorField, DecreaseEditorValue, IncreaseEditorValue }; } private string FormatChord(ValheimControllerAction primary) { return ControllerActionDisplay.Friendly(Modifier) + " + " + ControllerActionDisplay.Friendly(primary); } } public static class ControllerActionDisplay { public static string Friendly(ValheimControllerAction action) { return action switch { ValheimControllerAction.JoyAltKeys => "Controller Alt", ValheimControllerAction.JoyPlace => "Place", ValheimControllerAction.JoyRotate => "Rotate", ValheimControllerAction.JoyUse => "Use", ValheimControllerAction.JoyRemove => "Remove", ValheimControllerAction.JoyButtonA => "A / Cross", ValheimControllerAction.JoyButtonB => "B / Circle", ValheimControllerAction.JoyButtonX => "X / Square", ValheimControllerAction.JoyButtonY => "Y / Triangle", ValheimControllerAction.JoyDPadUp => "D-pad Up", ValheimControllerAction.JoyDPadDown => "D-pad Down", ValheimControllerAction.JoyDPadLeft => "D-pad Left", ValheimControllerAction.JoyDPadRight => "D-pad Right", ValheimControllerAction.JoyLBumper => "Left Bumper", ValheimControllerAction.JoyRBumper => "Right Bumper", ValheimControllerAction.JoyLTrigger => "Left Trigger", ValheimControllerAction.JoyRTrigger => "Right Trigger", ValheimControllerAction.JoyLStick => "Left Stick Click", ValheimControllerAction.JoyRStick => "Right Stick Click", ValheimControllerAction.JoyPrevSnap => "Left Stick Click", _ => throw new ArgumentOutOfRangeException("action"), }; } } public static class AgricultureControllerPathRouting { public static bool ShouldSuppress(string requestedPath, string acceptedModifierPath, string acceptedPrimaryPath) { if (string.IsNullOrWhiteSpace(requestedPath)) { return false; } string a = requestedPath.Trim(); if (string.IsNullOrWhiteSpace(acceptedModifierPath) || !string.Equals(a, acceptedModifierPath.Trim(), StringComparison.OrdinalIgnoreCase)) { if (!string.IsNullOrWhiteSpace(acceptedPrimaryPath)) { return string.Equals(a, acceptedPrimaryPath.Trim(), StringComparison.OrdinalIgnoreCase); } return false; } return true; } } internal sealed class AgricultureControllerSuppressionSession { private sealed class PrimaryLatch { internal string Path { get; } internal int ReleasedFrame { get; set; } = -1; internal PrimaryLatch(string path) { Path = path; } } private const int MaximumPrimaryPaths = 7; private readonly List _primaries = new List(); private string _modifierPath; private int _modifierReleasedFrame = -1; internal bool IsActive => !string.IsNullOrEmpty(_modifierPath); internal string ModifierPath => _modifierPath; internal int PrimaryCount => _primaries.Count; internal bool TryCapture(string modifierPath, string primaryPath, out string problem) { string text = Normalize(modifierPath); string text2 = Normalize(primaryPath); if (text.Length == 0 || text2.Length == 0 || Same(text, text2)) { problem = "the accepted controller chord no longer has two distinct effective paths"; return false; } if (IsActive && !Same(_modifierPath, text)) { problem = "the controller modifier changed while an accepted gesture is being released"; return false; } for (int i = 0; i < _primaries.Count; i++) { if (Same(_primaries[i].Path, text2)) { _primaries[i].ReleasedFrame = -1; problem = string.Empty; return true; } } if (_primaries.Count >= 7) { problem = "too many controller primaries are still being released"; return false; } if (!IsActive) { _modifierPath = text; } _modifierReleasedFrame = -1; _primaries.Add(new PrimaryLatch(text2)); problem = string.Empty; return true; } internal void Advance(int frame, bool modifierHeld, Func primaryHeld) { if (!IsActive) { return; } if (primaryHeld == null) { throw new ArgumentNullException("primaryHeld"); } for (int num = _primaries.Count - 1; num >= 0; num--) { PrimaryLatch primaryLatch = _primaries[num]; if (primaryHeld(primaryLatch.Path)) { primaryLatch.ReleasedFrame = -1; } else if (primaryLatch.ReleasedFrame < 0) { primaryLatch.ReleasedFrame = frame; } else if (primaryLatch.ReleasedFrame != frame) { _primaries.RemoveAt(num); } } if (modifierHeld) { _modifierReleasedFrame = -1; } else if (_modifierReleasedFrame < 0) { _modifierReleasedFrame = frame; } else if (_modifierReleasedFrame != frame && _primaries.Count == 0) { Reset(); } } internal bool ContainsPrimary(string path) { string right = Normalize(path); for (int i = 0; i < _primaries.Count; i++) { if (Same(_primaries[i].Path, right)) { return true; } } return false; } internal bool ShouldSuppress(string requestedPath) { string text = Normalize(requestedPath); if (text.Length == 0 || !IsActive) { return false; } if (Same(text, _modifierPath)) { return true; } return ContainsPrimary(text); } internal void Reset() { _primaries.Clear(); _modifierPath = null; _modifierReleasedFrame = -1; } private static string Normalize(string path) { return path?.Trim() ?? string.Empty; } private static bool Same(string left, string right) { return string.Equals(left, right, StringComparison.OrdinalIgnoreCase); } } internal sealed class AgricultureUnmodifiedControllerSuppression { private sealed class PrimaryLatch { internal string Path { get; } internal int ReleasedFrame { get; set; } = -1; internal PrimaryLatch(string path) { Path = path; } } private const int MaximumPaths = 4; private readonly List _paths = new List(); internal bool IsActive => _paths.Count != 0; internal int Count => _paths.Count; internal bool TryCapture(string path, out string problem) { string text = Normalize(path); if (text.Length == 0) { problem = "the accepted unmodified editor control has no effective path"; return false; } for (int i = 0; i < _paths.Count; i++) { if (Same(_paths[i].Path, text)) { _paths[i].ReleasedFrame = -1; problem = string.Empty; return true; } } if (_paths.Count >= 4) { problem = "too many unmodified editor controls are still being released"; return false; } _paths.Add(new PrimaryLatch(text)); problem = string.Empty; return true; } internal void Advance(int frame, Func held) { if (held == null) { throw new ArgumentNullException("held"); } for (int num = _paths.Count - 1; num >= 0; num--) { PrimaryLatch primaryLatch = _paths[num]; if (held(primaryLatch.Path)) { primaryLatch.ReleasedFrame = -1; } else if (primaryLatch.ReleasedFrame < 0) { primaryLatch.ReleasedFrame = frame; } else if (primaryLatch.ReleasedFrame != frame) { _paths.RemoveAt(num); } } } internal bool ShouldSuppress(string path) { string right = Normalize(path); for (int i = 0; i < _paths.Count; i++) { if (Same(_paths[i].Path, right)) { return true; } } return false; } internal bool Contains(string path) { return ShouldSuppress(path); } internal void Reset() { _paths.Clear(); } private static string Normalize(string path) { return path?.Trim() ?? string.Empty; } private static bool Same(string left, string right) { return string.Equals(left, right, StringComparison.OrdinalIgnoreCase); } } public enum RoutedAgricultureAction { None, CyclePattern, ConfirmPattern, ConfirmReplant } public readonly struct AgricultureInputFrame { public bool KeyboardCycle { get; } public bool KeyboardConfirmPattern { get; } public bool KeyboardConfirmReplant { get; } public bool ControllerCycle { get; } public bool ControllerConfirm { get; } public bool ReplantPreviewReady { get; } public AgricultureInputFrame(bool keyboardCycle, bool keyboardConfirmPattern, bool keyboardConfirmReplant, bool controllerCycle, bool controllerConfirm, bool replantPreviewReady) { KeyboardCycle = keyboardCycle; KeyboardConfirmPattern = keyboardConfirmPattern; KeyboardConfirmReplant = keyboardConfirmReplant; ControllerCycle = controllerCycle; ControllerConfirm = controllerConfirm; ReplantPreviewReady = replantPreviewReady; } } public static class AgricultureActionRouter { public static RoutedAgricultureAction Resolve(AgricultureInputFrame input) { if (input.KeyboardCycle || input.ControllerCycle) { return RoutedAgricultureAction.CyclePattern; } if (input.KeyboardConfirmReplant) { return RoutedAgricultureAction.ConfirmReplant; } if (input.KeyboardConfirmPattern) { return RoutedAgricultureAction.ConfirmPattern; } if (!input.ControllerConfirm) { return RoutedAgricultureAction.None; } if (!input.ReplantPreviewReady) { return RoutedAgricultureAction.ConfirmPattern; } return RoutedAgricultureAction.ConfirmReplant; } } public static class AgricultureFeedbackText { public static string Preview(PlantPattern pattern, int valid, int total, bool replant, string confirmControl, string cycleControl) { if (valid < 0 || total < 0 || valid > total) { throw new ArgumentOutOfRangeException("valid"); } string text = (replant ? "replant" : pattern.ToString().ToLowerInvariant()); string text2 = "Runic " + text + ": " + valid + "/" + total + " positions ready | Confirm " + Require(confirmControl, "confirmControl"); if (!replant) { return text2 + " | Cycle " + Require(cycleControl, "cycleControl"); } return text2; } public static string Configuration(bool enabled, PlantPattern pattern, int rows, int columns, double spacing, int maximumPreview, double harvestRadius, int maximumHarvest) { if (rows < 1 || columns < 1 || maximumPreview < 1 || maximumHarvest < 1) { throw new ArgumentOutOfRangeException("rows"); } return (enabled ? "enabled" : "disabled") + "; pattern " + pattern.ToString() + " " + rows + "x" + columns + " at " + spacing.ToString("0.##", CultureInfo.InvariantCulture) + "m; preview cap " + maximumPreview + "; harvest " + harvestRadius.ToString("0.##", CultureInfo.InvariantCulture) + "m / " + maximumHarvest + " plants"; } public static string PatternEditing(PlantPattern pattern, int rows, int columns, bool mirrored, double leftPinch, double rightPinch, string rowControls, string columnControls, string sideControl, string leftPinchControls, string rightPinchControls) { if (rows < 1 || columns < 1 || leftPinch < 0.0 || leftPinch > 1.0 || rightPinch < 0.0 || rightPinch > 1.0) { throw new ArgumentOutOfRangeException("rows"); } string text = ((pattern == PlantPattern.Row) ? (columns + " columns") : (rows + "x" + columns)); string text2 = "Shape " + text + " | " + ((pattern == PlantPattern.Row) ? ("Columns " + Require(columnControls, "columnControls")) : ("Rows " + Require(rowControls, "rowControls") + ", columns " + Require(columnControls, "columnControls"))); if (PatternEditor.SupportsMirror(pattern)) { text2 = text2 + " | " + PatternEditor.OrientationLabel(pattern, mirrored) + " " + Require(sideControl, "sideControl"); } if (pattern == PlantPattern.Trapezoid) { text2 = text2 + " | Taper L " + Percent(leftPinch) + " " + Require(leftPinchControls, "leftPinchControls") + ", R " + Percent(rightPinch) + " " + Require(rightPinchControls, "rightPinchControls"); } return text2; } private static string Percent(double value) { return Math.Round(value * 100.0).ToString("0", CultureInfo.InvariantCulture) + "%"; } private static string Require(string value, string name) { if (string.IsNullOrWhiteSpace(value)) { throw new ArgumentException("Control text is required.", name); } return value.Trim(); } } public enum AgricultureWheelTarget { None, Rows, Columns, Spacing, Rotation } public static class AgricultureWheelRouter { public static AgricultureWheelTarget Resolve(bool altHeld, bool shiftHeld, bool controlHeld, double wheelDelta) { if (double.IsNaN(wheelDelta) || double.IsInfinity(wheelDelta)) { throw new ArgumentOutOfRangeException("wheelDelta"); } if (wheelDelta == 0.0 || controlHeld) { return AgricultureWheelTarget.None; } if (altHeld && shiftHeld) { return AgricultureWheelTarget.Spacing; } if (altHeld) { return AgricultureWheelTarget.Rows; } if (shiftHeld) { return AgricultureWheelTarget.Columns; } return AgricultureWheelTarget.Rotation; } public static PatternEditAction ToEditAction(AgricultureWheelTarget target, bool increase) { switch (target) { case AgricultureWheelTarget.Rows: if (!increase) { return PatternEditAction.DecreaseRows; } return PatternEditAction.IncreaseRows; case AgricultureWheelTarget.Columns: if (!increase) { return PatternEditAction.DecreaseColumns; } return PatternEditAction.IncreaseColumns; case AgricultureWheelTarget.Spacing: if (!increase) { return PatternEditAction.DecreaseSpacing; } return PatternEditAction.IncreaseSpacing; case AgricultureWheelTarget.None: case AgricultureWheelTarget.Rotation: return PatternEditAction.None; default: throw new ArgumentOutOfRangeException("target"); } } } public static class AgriculturePatternHotkeys { public static bool TryResolveNumpadSlot(int slot, out PlantPattern pattern) { if (slot >= 1 && slot <= 7) { pattern = (PlantPattern)(slot - 1); return Enum.IsDefined(typeof(PlantPattern), pattern); } pattern = PlantPattern.Row; return false; } } public enum ControllerEditorField { Rows, Columns, Spacing, Side, LeftTaper, RightTaper } public static class ControllerPatternEditor { private static readonly ControllerEditorField[] RowFields = new ControllerEditorField[2] { ControllerEditorField.Columns, ControllerEditorField.Spacing }; private static readonly ControllerEditorField[] SymmetricFields = new ControllerEditorField[3] { ControllerEditorField.Rows, ControllerEditorField.Columns, ControllerEditorField.Spacing }; private static readonly ControllerEditorField[] MirroredFields = new ControllerEditorField[4] { ControllerEditorField.Rows, ControllerEditorField.Columns, ControllerEditorField.Spacing, ControllerEditorField.Side }; private static readonly ControllerEditorField[] TrapezoidFields = new ControllerEditorField[6] { ControllerEditorField.Rows, ControllerEditorField.Columns, ControllerEditorField.Spacing, ControllerEditorField.Side, ControllerEditorField.LeftTaper, ControllerEditorField.RightTaper }; public static IReadOnlyList FieldsFor(PlantPattern pattern) { if (!Enum.IsDefined(typeof(PlantPattern), pattern)) { throw new ArgumentOutOfRangeException("pattern"); } switch (pattern) { case PlantPattern.Row: return RowFields; case PlantPattern.Trapezoid: return TrapezoidFields; default: if (!PatternEditor.SupportsMirror(pattern)) { return SymmetricFields; } return MirroredFields; } } public static ControllerEditorField Normalize(PlantPattern pattern, ControllerEditorField current) { IReadOnlyList readOnlyList = FieldsFor(pattern); for (int i = 0; i < readOnlyList.Count; i++) { if (readOnlyList[i] == current) { return current; } } return readOnlyList[0]; } public static ControllerEditorField Move(PlantPattern pattern, ControllerEditorField current, int direction) { if (direction == 0) { return Normalize(pattern, current); } IReadOnlyList readOnlyList = FieldsFor(pattern); ControllerEditorField controllerEditorField = Normalize(pattern, current); int i; for (i = 0; i < readOnlyList.Count && readOnlyList[i] != controllerEditorField; i++) { } int index = (i + ((direction > 0) ? 1 : (-1)) + readOnlyList.Count) % readOnlyList.Count; return readOnlyList[index]; } public static PatternEditAction ToEditAction(ControllerEditorField field, bool increase) { switch (field) { case ControllerEditorField.Rows: if (!increase) { return PatternEditAction.DecreaseRows; } return PatternEditAction.IncreaseRows; case ControllerEditorField.Columns: if (!increase) { return PatternEditAction.DecreaseColumns; } return PatternEditAction.IncreaseColumns; case ControllerEditorField.Spacing: if (!increase) { return PatternEditAction.DecreaseSpacing; } return PatternEditAction.IncreaseSpacing; case ControllerEditorField.Side: return PatternEditAction.ToggleSide; case ControllerEditorField.LeftTaper: if (!increase) { return PatternEditAction.DecreaseLeftPinch; } return PatternEditAction.IncreaseLeftPinch; case ControllerEditorField.RightTaper: if (!increase) { return PatternEditAction.DecreaseRightPinch; } return PatternEditAction.IncreaseRightPinch; default: throw new ArgumentOutOfRangeException("field"); } } } public enum InvalidPositionPolicy { SkipInvalid, BlockConfirmation } public enum ResourceShortfallPolicy { TruncatePredictably, BlockConfirmation } public readonly struct PlacementBudget { public int SeedActions { get; } public int DurabilityActions { get; } public int StaminaActions { get; } public int MaximumSuccessfulActions => Math.Min(SeedActions, Math.Min(DurabilityActions, StaminaActions)); public PlacementBudget(int seedActions, int durabilityActions, int staminaActions) { if (seedActions < 0) { throw new ArgumentOutOfRangeException("seedActions"); } if (durabilityActions < 0) { throw new ArgumentOutOfRangeException("durabilityActions"); } if (staminaActions < 0) { throw new ArgumentOutOfRangeException("staminaActions"); } SeedActions = seedActions; DurabilityActions = durabilityActions; StaminaActions = staminaActions; } } public readonly struct BatchDecision { public int Index { get; } public bool ShouldPlace { get; } public string ReasonCode { get; } public BatchDecision(int index, bool shouldPlace, string reasonCode) { Index = index; ShouldPlace = shouldPlace; ReasonCode = reasonCode ?? throw new ArgumentNullException("reasonCode"); } } public sealed class BatchPlan { public IReadOnlyList Decisions { get; } public bool Blocked { get; } public string ReasonCode { get; } public int SuccessfulCount { get { int num = 0; for (int i = 0; i < Decisions.Count; i++) { if (Decisions[i].ShouldPlace) { num++; } } return num; } } internal BatchPlan(IReadOnlyList decisions, bool blocked, string reasonCode) { Decisions = decisions; Blocked = blocked; ReasonCode = reasonCode; } } public static class BatchPlanner { public static BatchPlan PlanPreview(IReadOnlyList validations) { return PlanPreview(validations, int.MaxValue); } public static BatchPlan PlanPreview(IReadOnlyList validations, int maximumSelectedCells) { if (validations == null) { throw new ArgumentNullException("validations"); } if (maximumSelectedCells < 0) { throw new ArgumentOutOfRangeException("maximumSelectedCells"); } List list = new List(validations.Count); int num = 0; for (int i = 0; i < validations.Count; i++) { PlacementValidationResult placementValidationResult = validations[i]; bool flag = placementValidationResult.IsValid && num < maximumSelectedCells; if (flag) { num++; } list.Add(new BatchDecision(i, flag, (placementValidationResult.IsValid && !flag) ? "agriculture.no-seeds" : placementValidationResult.ReasonCode)); } return new BatchPlan(list.AsReadOnly(), blocked: false, "agriculture.valid"); } public static BatchPlan Plan(IReadOnlyList validations, PlacementBudget budget, InvalidPositionPolicy invalidPolicy, ResourceShortfallPolicy shortfallPolicy) { if (validations == null) { throw new ArgumentNullException("validations"); } if (!Enum.IsDefined(typeof(InvalidPositionPolicy), invalidPolicy)) { throw new ArgumentOutOfRangeException("invalidPolicy"); } if (!Enum.IsDefined(typeof(ResourceShortfallPolicy), shortfallPolicy)) { throw new ArgumentOutOfRangeException("shortfallPolicy"); } int num = 0; bool flag = false; for (int i = 0; i < validations.Count; i++) { if (validations[i].IsValid) { num++; } else { flag = true; } } if (flag && invalidPolicy == InvalidPositionPolicy.BlockConfirmation) { return BlockAll(validations.Count, "agriculture.invalid-batch-blocked"); } if (num > budget.MaximumSuccessfulActions && shortfallPolicy == ResourceShortfallPolicy.BlockConfirmation) { return BlockAll(validations.Count, "agriculture.cost-batch-blocked"); } List list = new List(validations.Count); int num2 = 0; for (int j = 0; j < validations.Count; j++) { PlacementValidationResult placementValidationResult = validations[j]; if (!placementValidationResult.IsValid) { list.Add(new BatchDecision(j, shouldPlace: false, placementValidationResult.ReasonCode)); } else if (num2 < budget.MaximumSuccessfulActions) { list.Add(new BatchDecision(j, shouldPlace: true, "agriculture.valid")); num2++; } else { list.Add(new BatchDecision(j, shouldPlace: false, ExhaustedReason(budget, num2))); } } return new BatchPlan(list.AsReadOnly(), blocked: false, "agriculture.valid"); } private static BatchPlan BlockAll(int count, string reason) { List list = new List(count); for (int i = 0; i < count; i++) { list.Add(new BatchDecision(i, shouldPlace: false, reason)); } return new BatchPlan(list.AsReadOnly(), blocked: true, reason); } private static string ExhaustedReason(PlacementBudget budget, int successes) { if (successes >= budget.SeedActions) { return "agriculture.no-seeds"; } if (successes >= budget.DurabilityActions) { return "agriculture.no-durability"; } return "agriculture.no-stamina"; } } public readonly struct PlanarPoint : IEquatable { public double Right { get; } public double Forward { get; } public PlanarPoint(double right, double forward) { if (double.IsNaN(right) || double.IsInfinity(right)) { throw new ArgumentOutOfRangeException("right"); } if (double.IsNaN(forward) || double.IsInfinity(forward)) { throw new ArgumentOutOfRangeException("forward"); } Right = right; Forward = forward; } public bool Equals(PlanarPoint other) { if (Right.Equals(other.Right)) { return Forward.Equals(other.Forward); } return false; } public override bool Equals(object obj) { if (obj is PlanarPoint other) { return Equals(other); } return false; } public override int GetHashCode() { return (Right.GetHashCode() * 397) ^ Forward.GetHashCode(); } public override string ToString() { return $"({Right:0.###}, {Forward:0.###})"; } } public enum PlantPattern { Row, Grid, Circle, Star, RightTriangle, HalfCircle, Trapezoid } public enum AgricultureAlignment { PlayerHeading, WorldAxes, ExistingCropRow } public sealed class PatternRequest { public const int AbsoluteMaximumPoints = 1600; public const int AbsoluteMaximumDimension = 256; public PlantPattern Pattern { get; } public int Rows { get; } public int Columns { get; } public double Spacing { get; } public int MaximumPoints { get; } public bool Mirrored { get; } public double LeftPinch { get; } public double RightPinch { get; } public PatternRequest(PlantPattern pattern, int rows, int columns, double spacing, int maximumPoints, bool mirrored = false, double leftPinch = 0.5, double rightPinch = 0.5) { if (!Enum.IsDefined(typeof(PlantPattern), pattern)) { throw new ArgumentOutOfRangeException("pattern"); } if (rows < 1 || rows > 256) { throw new ArgumentOutOfRangeException("rows"); } if (columns < 1 || columns > 256) { throw new ArgumentOutOfRangeException("columns"); } if (spacing <= 0.0 || double.IsNaN(spacing) || double.IsInfinity(spacing)) { throw new ArgumentOutOfRangeException("spacing"); } if (maximumPoints < 1 || maximumPoints > 1600) { throw new ArgumentOutOfRangeException("maximumPoints"); } if (!IsUnitInterval(leftPinch)) { throw new ArgumentOutOfRangeException("leftPinch"); } if (!IsUnitInterval(rightPinch)) { throw new ArgumentOutOfRangeException("rightPinch"); } Pattern = pattern; Rows = rows; Columns = columns; Spacing = spacing; MaximumPoints = maximumPoints; Mirrored = mirrored; LeftPinch = leftPinch; RightPinch = rightPinch; } private static bool IsUnitInterval(double value) { if (!double.IsNaN(value) && !double.IsInfinity(value) && value >= 0.0) { return value <= 1.0; } return false; } } public static class PlacementRange { public static bool IsWithin(double distance, double maximumDistance, double extraDistance) { if (double.IsNaN(distance) || double.IsInfinity(distance) || distance < 0.0) { throw new ArgumentOutOfRangeException("distance"); } if (double.IsNaN(maximumDistance) || double.IsInfinity(maximumDistance)) { throw new ArgumentOutOfRangeException("maximumDistance"); } if (double.IsNaN(extraDistance) || double.IsInfinity(extraDistance)) { throw new ArgumentOutOfRangeException("extraDistance"); } return distance < Math.Max(0.0, maximumDistance + extraDistance); } } internal enum HarvestComponentContract : byte { None, Pickable, PickableItem } internal static class HarvestPickableBatchPolicy { internal static bool Supports(HarvestComponentContract contract) { return contract == HarvestComponentContract.Pickable; } internal static bool IsReady(bool networkViewValid, bool zdoIdentityValid, bool canBePicked, bool alreadyPicked, bool tarPreventsPicking, bool currentlyInTar) { if (networkViewValid && zdoIdentityValid && canBePicked && !alreadyPicked) { if (tarPreventsPicking) { return !currentlyInTar; } return true; } return false; } internal static bool IsExactPrefab(string aimedPrefabName, int aimedPrefabHash, string candidatePrefabName, int candidatePrefabHash) { if (!string.IsNullOrEmpty(aimedPrefabName) && aimedPrefabHash != 0 && string.Equals(aimedPrefabName, candidatePrefabName, StringComparison.Ordinal)) { return aimedPrefabHash == candidatePrefabHash; } return false; } internal static bool OffersReplant(bool enabled, bool authorized, string plantPrefabName) { if (enabled && authorized) { return !string.IsNullOrEmpty(plantPrefabName); } return false; } internal static bool AllowsAreaHarvest(bool authorized) { return authorized; } internal static int Compare(bool leftIsAimed, float leftDistanceSquared, long leftUserId, uint leftObjectId, bool rightIsAimed, float rightDistanceSquared, long rightUserId, uint rightObjectId) { if (leftIsAimed != rightIsAimed) { if (!leftIsAimed) { return 1; } return -1; } int num = leftDistanceSquared.CompareTo(rightDistanceSquared); if (num != 0) { return num; } int num2 = leftUserId.CompareTo(rightUserId); if (num2 == 0) { return leftObjectId.CompareTo(rightObjectId); } return num2; } } public enum PatternEditAction { None, IncreaseRows, DecreaseRows, IncreaseColumns, DecreaseColumns, ToggleSide, DecreaseLeftPinch, IncreaseLeftPinch, DecreaseRightPinch, IncreaseRightPinch, IncreaseSpacing, DecreaseSpacing } public readonly struct PatternEditState : IEquatable { public int Rows { get; } public int Columns { get; } public bool Mirrored { get; } public double LeftPinch { get; } public double RightPinch { get; } public double Spacing { get; } public PatternEditState(int rows, int columns, bool mirrored, double leftPinch, double rightPinch) : this(rows, columns, mirrored, leftPinch, rightPinch, 1.5) { } public PatternEditState(int rows, int columns, bool mirrored, double leftPinch, double rightPinch, double spacing) { if (rows < 1 || rows > 256) { throw new ArgumentOutOfRangeException("rows"); } if (columns < 1 || columns > 256) { throw new ArgumentOutOfRangeException("columns"); } if (!Unit(leftPinch)) { throw new ArgumentOutOfRangeException("leftPinch"); } if (!Unit(rightPinch)) { throw new ArgumentOutOfRangeException("rightPinch"); } if (double.IsNaN(spacing) || double.IsInfinity(spacing) || spacing < 0.5 || spacing > 6.0) { throw new ArgumentOutOfRangeException("spacing"); } Rows = rows; Columns = columns; Mirrored = mirrored; LeftPinch = leftPinch; RightPinch = rightPinch; Spacing = spacing; } public bool Equals(PatternEditState other) { if (Rows == other.Rows && Columns == other.Columns && Mirrored == other.Mirrored && LeftPinch.Equals(other.LeftPinch) && RightPinch.Equals(other.RightPinch)) { return Spacing.Equals(other.Spacing); } return false; } public override bool Equals(object obj) { if (obj is PatternEditState other) { return Equals(other); } return false; } public override int GetHashCode() { return (((((((((Rows * 397) ^ Columns) * 397) ^ Mirrored.GetHashCode()) * 397) ^ LeftPinch.GetHashCode()) * 397) ^ RightPinch.GetHashCode()) * 397) ^ Spacing.GetHashCode(); } private static bool Unit(double value) { if (!double.IsNaN(value) && !double.IsInfinity(value) && value >= 0.0) { return value <= 1.0; } return false; } } public static class PatternEditor { public const double PinchStep = 0.1; public const double SpacingStep = 0.1; public const double MinimumSpacing = 0.5; public const double MaximumSpacing = 6.0; public static PatternEditState Apply(PlantPattern pattern, PatternEditState state, PatternEditAction action) { if (!Enum.IsDefined(typeof(PlantPattern), pattern)) { throw new ArgumentOutOfRangeException("pattern"); } if (!Enum.IsDefined(typeof(PatternEditAction), action)) { throw new ArgumentOutOfRangeException("action"); } int num = state.Rows; int num2 = state.Columns; bool flag = state.Mirrored; double num3 = state.LeftPinch; double num4 = state.RightPinch; double num5 = state.Spacing; switch (action) { case PatternEditAction.IncreaseRows: if (pattern != PlantPattern.Row) { num = Math.Min(num + 1, 256); } break; case PatternEditAction.DecreaseRows: if (pattern != PlantPattern.Row) { num = Math.Max(num - 1, 1); } break; case PatternEditAction.IncreaseColumns: num2 = Math.Min(num2 + 1, 256); break; case PatternEditAction.DecreaseColumns: num2 = Math.Max(num2 - 1, 1); break; case PatternEditAction.ToggleSide: if (SupportsMirror(pattern)) { flag = !flag; } break; case PatternEditAction.DecreaseLeftPinch: if (pattern == PlantPattern.Trapezoid) { num3 = ClampUnit(num3 - 0.1); } break; case PatternEditAction.IncreaseLeftPinch: if (pattern == PlantPattern.Trapezoid) { num3 = ClampUnit(num3 + 0.1); } break; case PatternEditAction.DecreaseRightPinch: if (pattern == PlantPattern.Trapezoid) { num4 = ClampUnit(num4 - 0.1); } break; case PatternEditAction.IncreaseRightPinch: if (pattern == PlantPattern.Trapezoid) { num4 = ClampUnit(num4 + 0.1); } break; case PatternEditAction.IncreaseSpacing: num5 = ClampSpacing(num5 + 0.1); break; case PatternEditAction.DecreaseSpacing: num5 = ClampSpacing(num5 - 0.1); break; } return new PatternEditState(num, num2, flag, num3, num4, num5); } public static PlantPattern Next(PlantPattern pattern) { if (!Enum.IsDefined(typeof(PlantPattern), pattern)) { throw new ArgumentOutOfRangeException("pattern"); } if (pattern != PlantPattern.Trapezoid) { return pattern + 1; } return PlantPattern.Row; } public static bool SupportsMirror(PlantPattern pattern) { if (pattern != PlantPattern.RightTriangle && pattern != PlantPattern.HalfCircle) { return pattern == PlantPattern.Trapezoid; } return true; } public static string OrientationLabel(PlantPattern pattern, bool mirrored) { switch (pattern) { case PlantPattern.RightTriangle: return "Corner " + (mirrored ? "right" : "left"); case PlantPattern.HalfCircle: return "Side " + (mirrored ? "left" : "right"); case PlantPattern.Trapezoid: if (!mirrored) { return "Normal"; } return "Mirrored"; default: return string.Empty; } } private static double ClampUnit(double value) { return Math.Max(0.0, Math.Min(1.0, Math.Round(value, 3))); } private static double ClampSpacing(double value) { return Math.Max(0.5, Math.Min(6.0, Math.Round(value, 3))); } } public interface IAgriculturePatternService { IReadOnlyList Generate(PatternRequest request); } public sealed class AgriculturePatternService : IAgriculturePatternService { private delegate bool ShapePredicate(double normalizedRight, double normalizedForward); public IReadOnlyList Generate(PatternRequest request) { if (request == null) { throw new ArgumentNullException("request"); } List list = new List(request.MaximumPoints); switch (request.Pattern) { case PlantPattern.Row: AddRow(list, request.Columns, request.Spacing, request.MaximumPoints); break; case PlantPattern.Grid: AddGrid(list, request.Rows, request.Columns, request.Spacing, request.MaximumPoints); break; case PlantPattern.Circle: AddMaskedShape(list, request, IncludesCircle); break; case PlantPattern.Star: AddMaskedShape(list, request, IncludesStar); break; case PlantPattern.RightTriangle: AddMaskedShape(list, request, IncludesRightTriangle); break; case PlantPattern.HalfCircle: AddMaskedShape(list, request, IncludesHalfCircle); break; case PlantPattern.Trapezoid: AddMaskedShape(list, request, (double right, double forward) => IncludesTrapezoid(right, forward, request.LeftPinch, request.RightPinch)); break; default: throw new ArgumentOutOfRangeException("request"); } return list.AsReadOnly(); } private static void AddRow(ICollection points, int count, double spacing, int maximum) { List list = new List(count); double num = (double)(count - 1) * spacing * 0.5; for (int i = 0; i < count; i++) { list.Add(new PlanarPoint((double)i * spacing - num, 0.0)); } AddOrdered(points, PlantPattern.Row, list, maximum); } private static void AddGrid(ICollection points, int rows, int columns, double spacing, int maximum) { List list = new List(rows * columns); double num = (double)(columns - 1) * spacing * 0.5; double num2 = (double)(rows - 1) * spacing * 0.5; for (int i = 0; i < rows; i++) { for (int j = 0; j < columns; j++) { list.Add(new PlanarPoint((double)j * spacing - num, (double)i * spacing - num2)); } } AddOrdered(points, PlantPattern.Grid, list, maximum); } private static void AddOrdered(ICollection points, PlantPattern pattern, IReadOnlyList candidates, int maximum) { IReadOnlyList readOnlyList = PlantingFillOrder.Order(pattern, candidates); int num = Math.Min(maximum, readOnlyList.Count); for (int i = 0; i < num; i++) { points.Add(readOnlyList[i]); } } private static void AddMaskedShape(ICollection points, PatternRequest request, ShapePredicate includes) { List list = new List(request.Rows * request.Columns); for (int i = 0; i < request.Rows; i++) { double normalizedForward = NormalizeCell(i, request.Rows); double forward = CenteredOffset(i, request.Rows, request.Spacing); for (int j = 0; j < request.Columns; j++) { double normalizedRight = NormalizeCell(j, request.Columns); if (includes(normalizedRight, normalizedForward)) { list.Add(new PlanarPoint(CenteredOffset(j, request.Columns, request.Spacing), forward)); } } } if (list.Count == 0) { list.Add(new PlanarPoint(0.0, 0.0)); } IReadOnlyList readOnlyList = PlantingFillOrder.Order(request.Pattern, list); int num = Math.Min(request.MaximumPoints, readOnlyList.Count); for (int k = 0; k < num; k++) { points.Add(new PlanarPoint(request.Mirrored ? (0.0 - readOnlyList[k].Right) : readOnlyList[k].Right, readOnlyList[k].Forward)); } } private static double CenteredOffset(int index, int count, double spacing) { return ((double)index - (double)(count - 1) * 0.5) * spacing; } private static double NormalizeCell(int index, int count) { if (count > 1) { return (2.0 * (double)index + 1.0 - (double)count) / (double)count; } return 0.0; } private static bool IncludesCircle(double right, double forward) { return right * right + forward * forward <= 1.000000000001; } private static bool IncludesHalfCircle(double right, double forward) { if (right >= -1E-12) { return IncludesCircle(right, forward); } return false; } private static bool IncludesRightTriangle(double right, double forward) { return right + forward <= 1E-12; } private static bool IncludesTrapezoid(double right, double forward, double leftPinch, double rightPinch) { double num = (forward + 1.0) * 0.5; double num2 = -1.0 + leftPinch * num; double num3 = 1.0 - rightPinch * num; if (right >= num2 - 1E-12) { return right <= num3 + 1E-12; } return false; } private static bool IncludesStar(double right, double forward) { bool flag = false; int index = 9; for (int i = 0; i < 10; i++) { StarVertex(i, out var right2, out var forward2); StarVertex(index, out var right3, out var forward3); if (forward2 > forward != forward3 > forward && right < (right3 - right2) * (forward - forward2) / (forward3 - forward2) + right2) { flag = !flag; } index = i; } return flag; } private static void StarVertex(int index, out double right, out double forward) { double num = (((index & 1) == 0) ? 1.0 : 0.42); double num2 = Math.PI / 2.0 + (double)index * Math.PI / 5.0; right = Math.Cos(num2) * num; forward = Math.Sin(num2) * num; } } public static class AgricultureReasonCodes { public const string Valid = "agriculture.valid"; public const string Disabled = "agriculture.disabled"; public const string NotAuthoritative = "agriculture.not-authoritative"; public const string CropChanged = "agriculture.crop-changed"; public const string TerrainUnavailable = "agriculture.terrain-unavailable"; public const string WaterBlocked = "agriculture.water-blocked"; public const string SlopeInvalid = "agriculture.slope-invalid"; public const string BiomeInvalid = "agriculture.biome-invalid"; public const string NotCultivated = "agriculture.not-cultivated"; public const string SpacingBlocked = "agriculture.spacing-blocked"; public const string OutOfRange = "agriculture.out-of-range"; public const string WardDenied = "agriculture.ward-denied"; public const string NoBuildZone = "agriculture.no-build-zone"; public const string PlayerBlocked = "agriculture.player-blocked"; public const string NoSeeds = "agriculture.no-seeds"; public const string NoDurability = "agriculture.no-durability"; public const string NoStamina = "agriculture.no-stamina"; public const string InvalidBatchBlocked = "agriculture.invalid-batch-blocked"; public const string CostBatchBlocked = "agriculture.cost-batch-blocked"; public const string ReplantNotOffered = "agriculture.replant-not-offered"; public const string ReplantCropMismatch = "agriculture.replant-crop-mismatch"; public const string PlacementFailed = "agriculture.placement-failed"; } public readonly struct PlacementValidationInputs { public bool TerrainAvailable { get; } public bool SlopeValid { get; } public bool BiomeValid { get; } public bool Cultivated { get; } public bool SpacingClear { get; } public bool InRange { get; } public bool Authorized { get; } public bool WorldBuildAllowed { get; } public bool PlayersClear { get; } public bool WaterClear { get; } public PlacementValidationInputs(bool terrainAvailable, bool slopeValid, bool biomeValid, bool cultivated, bool spacingClear, bool inRange, bool authorized, bool worldBuildAllowed, bool playersClear, bool waterClear = true) { TerrainAvailable = terrainAvailable; SlopeValid = slopeValid; BiomeValid = biomeValid; Cultivated = cultivated; SpacingClear = spacingClear; InRange = inRange; Authorized = authorized; WorldBuildAllowed = worldBuildAllowed; PlayersClear = playersClear; WaterClear = waterClear; } } public readonly struct PlacementValidationResult { public bool IsValid { get; } public string ReasonCode { get; } public PlacementValidationResult(bool isValid, string reasonCode) { if (string.IsNullOrWhiteSpace(reasonCode)) { throw new ArgumentException("A stable reason code is required.", "reasonCode"); } IsValid = isValid; ReasonCode = reasonCode; } } public static class PlacementValidation { public static PlacementValidationResult Evaluate(PlacementValidationInputs inputs) { if (!inputs.TerrainAvailable) { return Deny("agriculture.terrain-unavailable"); } if (!inputs.WaterClear) { return Deny("agriculture.water-blocked"); } if (!inputs.SlopeValid) { return Deny("agriculture.slope-invalid"); } if (!inputs.BiomeValid) { return Deny("agriculture.biome-invalid"); } if (!inputs.Cultivated) { return Deny("agriculture.not-cultivated"); } if (!inputs.SpacingClear) { return Deny("agriculture.spacing-blocked"); } if (!inputs.InRange) { return Deny("agriculture.out-of-range"); } if (!inputs.Authorized) { return Deny("agriculture.ward-denied"); } if (!inputs.WorldBuildAllowed) { return Deny("agriculture.no-build-zone"); } if (!inputs.PlayersClear) { return Deny("agriculture.player-blocked"); } return new PlacementValidationResult(isValid: true, "agriculture.valid"); } private static PlacementValidationResult Deny(string reason) { return new PlacementValidationResult(isValid: false, reason); } } public static class PlantingFillOrder { public static IReadOnlyList Order(PlantPattern pattern, IReadOnlyList candidates) { if (candidates == null) { throw new ArgumentNullException("candidates"); } if (!Enum.IsDefined(typeof(PlantPattern), pattern)) { throw new ArgumentOutOfRangeException("pattern"); } List list = new List(candidates.Count); for (int i = 0; i < candidates.Count; i++) { list.Add(candidates[i]); } list.Sort((pattern == PlantPattern.Grid || pattern == PlantPattern.Row) ? new Comparison(CompareRightToLeft) : new Comparison(CompareCenterOut)); return list.AsReadOnly(); } private static int CompareRightToLeft(PlanarPoint left, PlanarPoint right) { int num = right.Right.CompareTo(left.Right); if (num == 0) { return right.Forward.CompareTo(left.Forward); } return num; } private static int CompareCenterOut(PlanarPoint left, PlanarPoint right) { int num = SquaredDistance(left).CompareTo(SquaredDistance(right)); if (num != 0) { return num; } int num2 = right.Forward.CompareTo(left.Forward); if (num2 == 0) { return right.Right.CompareTo(left.Right); } return num2; } private static double SquaredDistance(PlanarPoint point) { return point.Right * point.Right + point.Forward * point.Forward; } } public static class PlantingGridPolicy { public const int LegacyPreviewLimit = 256; public const int DefaultPreviewLimit = 1600; public static PlantPattern DefaultPattern => PlantPattern.Grid; public static PlantPattern MigrateToDefaultGrid(bool migrationAlreadyApplied, PlantPattern configuredPattern) { if (!migrationAlreadyApplied) { return PlantPattern.Grid; } return configuredPattern; } public static int ConfiguredPreviewLimit(int configured) { if (configured < 1) { throw new ArgumentOutOfRangeException("configured"); } return Math.Min(configured, 1600); } public static int SelectableSeedCount(int availableSeedActions, int groundValidCells, bool unlimitedSeeds) { if (groundValidCells < 0) { throw new ArgumentOutOfRangeException("groundValidCells"); } if (unlimitedSeeds) { return groundValidCells; } if (availableSeedActions < 0) { throw new ArgumentOutOfRangeException("availableSeedActions"); } return Math.Min(availableSeedActions, groundValidCells); } public static bool ConsumesSeedResources(bool worldFreeBuild, bool noCostCheat) { if (!worldFreeBuild) { return !noCostCheat; } return false; } } internal static class PreviewPoolMaintenance { internal static int PruneUnavailable(IList entries, Func isUnavailable) { if (entries == null) { throw new ArgumentNullException("entries"); } if (isUnavailable == null) { throw new ArgumentNullException("isUnavailable"); } int num = 0; for (int num2 = entries.Count - 1; num2 >= 0; num2--) { if (isUnavailable(entries[num2])) { entries.RemoveAt(num2); num++; } } return num; } } public sealed class ReplantConfirmation { private readonly int _maximumPositions; private string _cropId; private IReadOnlyList _positions = Array.Empty(); public bool IsPending => _cropId != null; public string CropId => _cropId; public IReadOnlyList Positions => _positions; public ReplantConfirmation(int maximumPositions) { if (maximumPositions < 1) { throw new ArgumentOutOfRangeException("maximumPositions"); } _maximumPositions = maximumPositions; } public void Offer(string cropId, IEnumerable positions) { if (string.IsNullOrWhiteSpace(cropId)) { throw new ArgumentException("A crop identifier is required.", "cropId"); } if (positions == null) { throw new ArgumentNullException("positions"); } List list = new List(_maximumPositions); foreach (TPosition position in positions) { if (list.Count >= _maximumPositions) { break; } list.Add(position); } if (list.Count == 0) { Clear(); return; } _cropId = cropId.Trim(); _positions = list.AsReadOnly(); } public bool TryConfirm(string cropId, out IReadOnlyList positions, out string reasonCode) { positions = Array.Empty(); if (!IsPending) { reasonCode = "agriculture.replant-not-offered"; return false; } if (!string.Equals(_cropId, cropId, StringComparison.Ordinal)) { reasonCode = "agriculture.replant-crop-mismatch"; return false; } positions = _positions; reasonCode = "agriculture.valid"; Clear(); return true; } public void Clear() { _cropId = null; _positions = Array.Empty(); } } public readonly struct PlantResourceRequirement { public string ResourceId { get; } public int Amount { get; } public PlantResourceRequirement(string resourceId, int amount) { if (string.IsNullOrWhiteSpace(resourceId)) { throw new ArgumentException("A resource id is required.", "resourceId"); } if (amount <= 0) { throw new ArgumentOutOfRangeException("amount"); } ResourceId = resourceId; Amount = amount; } } public static class SeedResourceMath { public static int MaximumPlantings(IEnumerable requirements, IReadOnlyDictionary available) { if (requirements == null) { throw new ArgumentNullException("requirements"); } if (available == null) { throw new ArgumentNullException("available"); } Dictionary dictionary = new Dictionary(StringComparer.Ordinal); foreach (PlantResourceRequirement requirement in requirements) { dictionary.TryGetValue(requirement.ResourceId, out var value); dictionary[requirement.ResourceId] = AddSaturated(value, requirement.Amount); } if (dictionary.Count == 0) { return int.MaxValue; } int num = int.MaxValue; foreach (KeyValuePair item in dictionary) { available.TryGetValue(item.Key, out var value2); num = Math.Min(num, Math.Max(0, value2) / item.Value); } return num; } private static int AddSaturated(int left, int right) { if (left <= int.MaxValue - right) { return left + right; } return int.MaxValue; } } }