Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of RunicAgriculture v1.0.0
RunicAgriculture.dll
Decompiled 14 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.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<InputChord> { private readonly IReadOnlyList<string> _modifiers; public string DeviceId { get; } public string PrimaryControl { get; } public IReadOnlyList<string> Modifiers => _modifiers; public string CanonicalId { get; } public InputChord(string deviceId, string primaryControl, IEnumerable<string> modifiers = null) { DeviceId = RunicIdentifier.Require(deviceId, "deviceId"); PrimaryControl = RequireControl(primaryControl, "primaryControl"); string text = CanonicalizeControl(PrimaryControl); SortedDictionary<string, string> sortedDictionary = new SortedDictionary<string, string>(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<KeybindingDescriptor> Bindings { get; } internal KeybindingConflict(InputChord chord, IReadOnlyList<KeybindingDescriptor> 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<string, BindingEntry> _byQualifiedId = new Dictionary<string, BindingEntry>(StringComparer.Ordinal); private readonly Dictionary<string, List<BindingEntry>> _byChord = new Dictionary<string, List<BindingEntry>>(StringComparer.Ordinal); private long _nextToken; public event EventHandler<KeybindingsChangedEventArgs> 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<BindingEntry>(); _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<KeybindingDescriptor> GetBindings() { lock (_sync) { List<KeybindingDescriptor> list = new List<KeybindingDescriptor>(_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<KeybindingConflict> GetConflicts() { lock (_sync) { List<KeybindingConflict> list = new List<KeybindingConflict>(); 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<KeybindingConflict> GetConflictsFor(string moduleId) { RunicIdentifier.Require(moduleId, "moduleId"); IReadOnlyList<KeybindingConflict> conflicts = GetConflicts(); List<KeybindingConflict> list = new List<KeybindingConflict>(); 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<BindingEntry> 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<KeybindingDescriptor> list = new List<KeybindingDescriptor>(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<KeybindingsChangedEventArgs> eventHandler = this.Changed; if (eventHandler == null) { return; } Delegate[] invocationList = eventHandler.GetInvocationList(); for (int i = 0; i < invocationList.Length; i++) { EventHandler<KeybindingsChangedEventArgs> eventHandler2 = (EventHandler<KeybindingsChangedEventArgs>)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<bool> Enabled { get; private set; } internal static ConfigEntry<PlantPattern> Pattern { get; private set; } internal static ConfigEntry<AgricultureAlignment> Alignment { get; private set; } internal static ConfigEntry<int> Rows { get; private set; } internal static ConfigEntry<int> Columns { get; private set; } internal static ConfigEntry<float> Spacing { get; private set; } internal static ConfigEntry<float> LegacyCircleRadius { get; private set; } internal static ConfigEntry<bool> MirrorShape { get; private set; } internal static ConfigEntry<float> TrapezoidLeftPinch { get; private set; } internal static ConfigEntry<float> TrapezoidRightPinch { get; private set; } internal static ConfigEntry<float> NearbySeedChestRange { get; private set; } internal static ConfigEntry<InvalidPositionPolicy> InvalidPolicy { get; private set; } internal static ConfigEntry<ResourceShortfallPolicy> ResourcePolicy { get; private set; } internal static ConfigEntry<float> HarvestRadius { get; private set; } internal static ConfigEntry<int> MaximumHarvest { get; private set; } internal static ConfigEntry<bool> OfferReplantPreview { get; private set; } internal static ConfigEntry<bool> ShowHoverStatus { get; private set; } internal static ConfigEntry<bool> ShowContextualControls { get; private set; } internal static ConfigEntry<float> ControlBarScale { get; private set; } internal static ConfigEntry<bool> VerboseLogging { get; private set; } internal static ConfigEntry<KeyboardShortcut> ConfirmPattern { get; private set; } internal static ConfigEntry<KeyboardShortcut> CyclePattern { get; private set; } internal static ConfigEntry<KeyboardShortcut> AreaHarvest { get; private set; } internal static ConfigEntry<KeyboardShortcut> ConfirmReplant { get; private set; } internal static ConfigEntry<KeyboardShortcut> IncreaseRows { get; private set; } internal static ConfigEntry<KeyboardShortcut> DecreaseRows { get; private set; } internal static ConfigEntry<KeyboardShortcut> IncreaseColumns { get; private set; } internal static ConfigEntry<KeyboardShortcut> DecreaseColumns { get; private set; } internal static ConfigEntry<KeyboardShortcut> ToggleShapeSide { get; private set; } internal static ConfigEntry<KeyboardShortcut> DecreaseLeftPinch { get; private set; } internal static ConfigEntry<KeyboardShortcut> IncreaseLeftPinch { get; private set; } internal static ConfigEntry<KeyboardShortcut> DecreaseRightPinch { get; private set; } internal static ConfigEntry<KeyboardShortcut> IncreaseRightPinch { get; private set; } internal static ConfigEntry<bool> ControllerEnabled { get; private set; } internal static ConfigEntry<ValheimControllerAction> ControllerModifier { get; private set; } internal static ConfigEntry<ValheimControllerAction> ControllerConfirm { get; private set; } internal static ConfigEntry<ValheimControllerAction> ControllerCycle { get; private set; } internal static ConfigEntry<ValheimControllerAction> ControllerAreaHarvest { get; private set; } internal static ConfigEntry<ValheimControllerAction> ControllerPreviousEditorField { get; private set; } internal static ConfigEntry<ValheimControllerAction> ControllerNextEditorField { get; private set; } internal static ConfigEntry<ValheimControllerAction> ControllerDecreaseEditorValue { get; private set; } internal static ConfigEntry<ValheimControllerAction> ControllerIncreaseEditorValue { get; private set; } internal static ConfigEntry<bool> LegacyCircleRadiusMigrationApplied { get; private set; } internal static ConfigEntry<bool> 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<bool>("General", "Enabled", true, "Enable Runic planting previews, explicit batch actions, and read-only status text."); Pattern = config.Bind<PlantPattern>("Planting Pattern", "Pattern", PlantingGridPolicy.DefaultPattern, "Preview shape: Row, Grid, Circle, Star, RightTriangle, HalfCircle, or Trapezoid. The cycle shortcut changes it live."); Alignment = config.Bind<AgricultureAlignment>("Planting Pattern", "Alignment", AgricultureAlignment.PlayerHeading, "Align to player heading, world axes, or the nearest two matching crops."); Rows = config.Bind<int>("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<int>(1, 256), Array.Empty<object>())); Columns = config.Bind<int>("Planting Pattern", "ColumnsOrPoints", 5, new ConfigDescription("Side-to-side footprint in planting columns. Change it live with the column controls.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 256), Array.Empty<object>())); Spacing = config.Bind<float>("Planting Pattern", "SpacingMeters", 1.5f, new ConfigDescription("Center-to-center spacing for every generated shape.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 6f), Array.Empty<object>())); LegacyCircleRadius = config.Bind<float>("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<float>(0.5f, 12f), Array.Empty<object>())); LegacyCircleRadiusMigrationApplied = config.Bind<bool>("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<bool>("Planting Pattern", "MirrorShape", false, "Switch the side used by RightTriangle and HalfCircle, or mirror an asymmetric Trapezoid."); TrapezoidLeftPinch = config.Bind<float>("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<float>(0f, 1f), Array.Empty<object>())); TrapezoidRightPinch = config.Bind<float>("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<float>(0f, 1f), Array.Empty<object>())); NearbySeedChestRange = config.Bind<float>("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<float>(1f, 30f), Array.Empty<object>())); InvalidPolicy = config.Bind<InvalidPositionPolicy>("Confirmation", "InvalidPositionPolicy", InvalidPositionPolicy.SkipInvalid, "Skip amber/gray terrain or spacing failures, or block the entire confirmation before the first plant."); ResourcePolicy = config.Bind<ResourceShortfallPolicy>("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<float>("Harvest", "RadiusMeters", 4f, new ConfigDescription("Area-harvest radius for the exact same registered Pickable prefab.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 8f), Array.Empty<object>())); MaximumHarvest = config.Bind<int>("Harvest", "MaximumPlants", 25, new ConfigDescription("Maximum Pickable requests in one area-harvest batch. Hard-capped at 25.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 25), Array.Empty<object>())); OfferReplantPreview = config.Bind<bool>("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<bool>("Status", "ShowBeeAndCropStatus", true, "Append concise read-only honey, bee happiness, crop maturity, and growth-failure status."); ShowContextualControls = config.Bind<bool>("Status", "ShowContextualControls", true, "Replace the vanilla bottom build hints with the Agriculture control bar while a crop preview is active."); ControlBarScale = config.Bind<float>("Status", "ControlBarScale", 0.9f, new ConfigDescription("Scale Valheim's compact bottom Agriculture build-hint panel.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.75f, 1.75f), Array.Empty<object>())); GridAndCompactHudMigrationApplied = config.Bind<bool>("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<bool>("Diagnostics", "VerboseLogging", false, "Log input routing, preview state changes, denials, and action results for troubleshooting."); ConfirmPattern = config.Bind<KeyboardShortcut>("Controls", "ConfirmPattern", new KeyboardShortcut((KeyCode)323, Array.Empty<KeyCode>()), "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<KeyCode>()); goto IL_0476; IL_0476: CyclePattern = config.Bind<KeyboardShortcut>("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<KeyboardShortcut>("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<KeyboardShortcut>("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<KeyboardShortcut>("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<KeyboardShortcut>("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<KeyboardShortcut>("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<KeyboardShortcut>("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<KeyboardShortcut>("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<KeyboardShortcut>("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<KeyboardShortcut>("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<KeyboardShortcut>("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<KeyboardShortcut>("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<bool>("Controller Controls", "Enabled", true, "Enable contextual controller chords through Valheim's ZInput action mappings."); ControllerModifier = config.Bind<ValheimControllerAction>("Controller Controls", "ModifierAction", ValheimControllerAction.JoyAltKeys, "Valheim controller action held as the Runic modifier."); ControllerConfirm = config.Bind<ValheimControllerAction>("Controller Controls", "ConfirmAction", ValheimControllerAction.JoyPlace, "Valheim controller action pressed with the modifier to confirm a visible pattern or matching replant preview."); ControllerCycle = config.Bind<ValheimControllerAction>("Controller Controls", "CyclePatternAction", ValheimControllerAction.JoyPrevSnap, "Valheim controller action pressed with the modifier to cycle the planting pattern."); ControllerAreaHarvest = config.Bind<ValheimControllerAction>("Controller Controls", "AreaHarvestAction", ValheimControllerAction.JoyUse, "Valheim controller action pressed with the modifier while interacting with an available Pickable."); ControllerPreviousEditorField = config.Bind<ValheimControllerAction>("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<ValheimControllerAction>("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<ValheimControllerAction>("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<ValheimControllerAction>("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<KeybindingRegistration> _keybindingRegistrations = new List<KeybindingRegistration>(); 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<bool> 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<string> list = new List<string>(); IEnumerable<KeyCode> 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<string>()), "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<AgricultureHintRow> Rows { get; } internal AgricultureControlBarContent(params AgricultureHintRow[] rows) { Rows = rows ?? Array.Empty<AgricultureHintRow>(); } } internal sealed class AgricultureControlBar : IDisposable { private readonly Dictionary<TMP_Text, string> _originalText = new Dictionary<TMP_Text, string>(); 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<TMP_Text>(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<TMP_Text> knownKeys = new HashSet<TMP_Text>(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<TMP_Text, string> 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<TMP_Text> knownKeys) { Transform parent = key.transform.parent; while ((Object)(object)parent != (Object)null) { TMP_Text[] componentsInChildren = ((Component)parent).GetComponentsInChildren<TMP_Text>(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<Vector3> Positions { get; } internal bool IsReplant { get; } internal PreviewSnapshot(string cropId, Piece piece, Quaternion rotation, IReadOnlyList<Vector3> 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<Vector3> _replant = new ReplantConfirmation<Vector3>(25); private readonly Collider[] _alignmentHits = (Collider[])(object)new Collider[96]; private readonly Collider[] _harvestHits = (Collider[])(object)new Collider[160]; private readonly Dictionary<string, string> _matureToPlant = new Dictionary<string, string>(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<Vector3> 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<RuntimePreviewPosition> list2 = BuildValidatedPreview(player, selectedPiece, list, rotation); _lastPreview = new PreviewSnapshot(text, selectedPiece, rotation, list, isReplant); _previewTotalCount = list2.Count; _previewValidCount = 0; _previewGroundValidCount = 0; Dictionary<string, int> dictionary = new Dictionary<string, int>(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<Pickable> hashSet = new HashSet<Pickable>(); List<HarvestPickableCandidate> list = new List<HarvestPickableCandidate>(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<Vector3> list2 = new List<Vector3>(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<Vector3> 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<PlacementValidationResult> list = new List<PlacementValidationResult>(requested.Count); List<Vector3> list2 = new List<Vector3>(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.I