using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Unity.Netcode;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("OneClickHarvest")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("OneClickHarvest")]
[assembly: AssemblyCopyright("Copyright © 2025")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("0e101077-aa15-4bd9-aaee-1a3e7371c9f0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.0.0.0")]
[BepInPlugin("OneClickInteractions", "One Click Interactions", "1.0.3")]
public class OneClickInteractions : BaseUnityPlugin
{
public const string PluginGuid = "OneClickInteractions";
public const string PluginName = "One Click Interactions";
public const string PluginVersion = "1.0.3";
internal static bool Enabled { get; private set; } = true;
internal static ConfigEntry<KeyboardShortcut> ToggleKey { get; private set; }
private void Awake()
{
//IL_001a: Unknown result type (might be due to invalid IL or missing references)
//IL_0039: Unknown result type (might be due to invalid IL or missing references)
ToggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("General", "ToggleKey", new KeyboardShortcut((KeyCode)291, Array.Empty<KeyCode>()), "Toggle OneClickInteractions on/off");
Enabled = true;
new Harmony("OneClickInteractions").PatchAll();
}
private void Update()
{
//IL_000c: Unknown result type (might be due to invalid IL or missing references)
//IL_0011: Unknown result type (might be due to invalid IL or missing references)
if (ToggleKey != null)
{
KeyboardShortcut value = ToggleKey.Value;
if (((KeyboardShortcut)(ref value)).IsDown())
{
Toggle();
}
}
}
private static void Toggle()
{
Enabled = !Enabled;
if (!Enabled)
{
try
{
OneClickState.RestoreAllConvertedToHold();
}
catch
{
}
}
else
{
try
{
OneClickState.ReapplyCachedConversions();
}
catch
{
}
}
try
{
Game.Instance?.onGameEventText?.Invoke("OneClickInteractions : " + (Enabled ? "ON" : "OFF"));
}
catch
{
}
}
}
internal static class OneClickState
{
private struct CachedConv
{
public WeakReference<Interactive> Ref;
public int InstanceId;
public string Descr;
}
public const int PropertyMarkerId = -1000000;
private static readonly Dictionary<int, HashSet<int>> _converted = new Dictionary<int, HashSet<int>>();
private static readonly Dictionary<int, WeakReference<Interactive>> _refs = new Dictionary<int, WeakReference<Interactive>>();
private static readonly Dictionary<int, Dictionary<int, string>> _descs = new Dictionary<int, Dictionary<int, string>>();
internal static bool SuppressConversion;
private static readonly List<CachedConv> _reapplyCache = new List<CachedConv>(256);
private static int KeyOf(Interactive it)
{
if (!((Object)(object)it != (Object)null))
{
return 0;
}
return ((Object)it).GetInstanceID();
}
public static bool IsConverted(Interactive it)
{
if ((Object)(object)it == (Object)null)
{
return false;
}
if (_converted.TryGetValue(KeyOf(it), out var value) && value != null)
{
return value.Count > 0;
}
return false;
}
public static void MarkConverted(Interactive it, int instanceId, string descr)
{
if ((Object)(object)it == (Object)null)
{
return;
}
int key = KeyOf(it);
if (!_converted.TryGetValue(key, out var value) || value == null)
{
value = new HashSet<int>();
_converted[key] = value;
}
value.Add(instanceId);
_refs[key] = new WeakReference<Interactive>(it);
if (!string.IsNullOrEmpty(descr))
{
if (!_descs.TryGetValue(key, out var value2) || value2 == null)
{
value2 = new Dictionary<int, string>();
_descs[key] = value2;
}
value2[instanceId] = descr;
}
}
public static void MarkConverted(Interactive it, int instanceId)
{
string descr = null;
if ((Object)(object)it != (Object)null)
{
descr = ((!string.IsNullOrEmpty(it.PressDescr)) ? it.PressDescr : it.HoldDescr);
}
MarkConverted(it, instanceId, descr);
}
public static bool UnmarkConverted(Interactive it, int instanceId)
{
if ((Object)(object)it == (Object)null)
{
return false;
}
int key = KeyOf(it);
if (!_converted.TryGetValue(key, out var value) || value == null)
{
return false;
}
if (!value.Remove(instanceId))
{
return false;
}
if (_descs.TryGetValue(key, out var value2) && value2 != null)
{
value2.Remove(instanceId);
if (value2.Count == 0)
{
_descs.Remove(key);
}
}
if (value.Count == 0)
{
_converted.Remove(key);
_refs.Remove(key);
_descs.Remove(key);
}
return true;
}
public static void RestoreAllConvertedToHold()
{
_reapplyCache.Clear();
List<int> list = new List<int>(_converted.Keys);
for (int i = 0; i < list.Count; i++)
{
int key = list[i];
if (!_converted.TryGetValue(key, out var value) || value == null || value.Count == 0 || !_refs.TryGetValue(key, out var value2) || value2 == null || !value2.TryGetTarget(out var target) || (Object)(object)target == (Object)null)
{
continue;
}
_descs.TryGetValue(key, out var value3);
foreach (int item in value)
{
string value4 = null;
value3?.TryGetValue(item, out value4);
if (string.IsNullOrEmpty(value4))
{
value4 = ((!string.IsNullOrEmpty(target.PressDescr)) ? target.PressDescr : target.HoldDescr);
}
_reapplyCache.Add(new CachedConv
{
Ref = value2,
InstanceId = item,
Descr = value4
});
try
{
if (item == -1000000)
{
if (!string.IsNullOrEmpty(value4))
{
target.HoldDescr = value4;
}
target.IsHoldAvailable = true;
target.IsPressAvailable = false;
}
else
{
target.SetInteractParams((Event)2, false, value4, item);
target.SetInteractParams((Event)4, true, value4, item);
}
}
catch
{
}
}
}
_converted.Clear();
_refs.Clear();
_descs.Clear();
}
public static void ReapplyCachedConversions()
{
if (_reapplyCache.Count == 0)
{
return;
}
for (int i = 0; i < _reapplyCache.Count; i++)
{
CachedConv cachedConv = _reapplyCache[i];
if (cachedConv.Ref == null || !cachedConv.Ref.TryGetTarget(out var target) || (Object)(object)target == (Object)null)
{
continue;
}
try
{
if (cachedConv.InstanceId == -1000000)
{
if (!string.IsNullOrEmpty(cachedConv.Descr))
{
target.PressDescr = cachedConv.Descr;
}
target.IsPressAvailable = true;
target.IsHoldAvailable = false;
MarkConverted(target, -1000000, cachedConv.Descr);
continue;
}
bool suppressConversion = SuppressConversion;
SuppressConversion = true;
try
{
target.SetInteractParams((Event)4, false, cachedConv.Descr, cachedConv.InstanceId);
target.SetInteractParams((Event)2, true, cachedConv.Descr, cachedConv.InstanceId);
if (!string.IsNullOrEmpty(cachedConv.Descr))
{
target.PressDescr = cachedConv.Descr;
}
MarkConverted(target, cachedConv.InstanceId, cachedConv.Descr);
}
finally
{
SuppressConversion = suppressConversion;
}
}
catch
{
}
}
}
}
[HarmonyPatch]
internal static class OneClickInteractionsPatches
{
private static bool _guard_SetHoldAvailable;
private static FieldRef<Vulnerable, NetworkVariable<ushort>> _vulnerableHpRef;
private static FieldRef<CreatureBase, NetworkVariable<bool>> _creatureLootAvailableRef;
private static readonly Collider[] _nearbyBuf = (Collider[])(object)new Collider[32];
private static bool TryGetHp(Vulnerable v, out ushort hp)
{
hp = 0;
if ((Object)(object)v == (Object)null)
{
return false;
}
try
{
if (_vulnerableHpRef == null)
{
_vulnerableHpRef = AccessTools.FieldRefAccess<Vulnerable, NetworkVariable<ushort>>("hp");
}
NetworkVariable<ushort> val = _vulnerableHpRef.Invoke(v);
hp = val.Value;
return true;
}
catch
{
return false;
}
}
private static bool TryGetCreatureLootAvailable(CreatureBase creature, out bool available)
{
available = false;
if ((Object)(object)creature == (Object)null)
{
return false;
}
try
{
if (_creatureLootAvailableRef == null)
{
_creatureLootAvailableRef = AccessTools.FieldRefAccess<CreatureBase, NetworkVariable<bool>>("isLootAvailable");
}
NetworkVariable<bool> val = _creatureLootAvailableRef.Invoke(creature);
available = val.Value;
return true;
}
catch
{
return false;
}
}
private static T FindInContext<T>(Component c) where T : Component
{
if ((Object)(object)c == (Object)null)
{
return default(T);
}
T componentInParent = c.GetComponentInParent<T>();
if ((Object)(object)componentInParent != (Object)null)
{
return componentInParent;
}
Transform val = (((Object)(object)c.transform != (Object)null) ? c.transform.root : null);
if ((Object)(object)val == (Object)null)
{
return default(T);
}
return ((Component)val).GetComponentInChildren<T>(true);
}
private static bool IsCorpseContext(Component c)
{
if ((Object)(object)c == (Object)null)
{
return false;
}
Vulnerable val = FindInContext<Vulnerable>(c);
if ((Object)(object)val != (Object)null && TryGetHp(val, out var hp))
{
return hp == 0;
}
return false;
}
private static bool TryGetLootAvailable(Component c, out bool lootAvailable)
{
lootAvailable = false;
if ((Object)(object)c == (Object)null)
{
return false;
}
CreatureBase val = FindInContext<CreatureBase>(c);
if ((Object)(object)val != (Object)null)
{
return TryGetCreatureLootAvailable(val, out lootAvailable);
}
EnemyMeleeAir val2 = FindInContext<EnemyMeleeAir>(c);
if ((Object)(object)val2 != (Object)null)
{
try
{
lootAvailable = val2.isLootAvailable.Value;
return true;
}
catch
{
return false;
}
}
return false;
}
private static bool IsNearDeadCreatureOrEnemy(Vector3 pos, float radius)
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
int num;
try
{
num = Physics.OverlapSphereNonAlloc(pos, radius, _nearbyBuf, -1, (QueryTriggerInteraction)2);
}
catch
{
return false;
}
for (int i = 0; i < num; i++)
{
Collider val = _nearbyBuf[i];
if (!((Object)(object)val == (Object)null))
{
Vulnerable componentInParent = ((Component)val).GetComponentInParent<Vulnerable>();
if ((Object)(object)componentInParent != (Object)null && TryGetHp(componentInParent, out var hp) && hp == 0)
{
return true;
}
}
}
return false;
}
private static bool IsServingTableLike(Interactive it)
{
if ((Object)(object)it == (Object)null || (Object)(object)((Component)it).transform == (Object)null)
{
return false;
}
Transform val = ((Component)it).transform;
while ((Object)(object)val != (Object)null)
{
string name = ((Object)val).name;
if (!string.IsNullOrEmpty(name) && name.IndexOf("ServingTable", StringComparison.OrdinalIgnoreCase) >= 0)
{
return true;
}
val = val.parent;
}
Transform root = ((Component)it).transform.root;
if ((Object)(object)root != (Object)null && !string.IsNullOrEmpty(((Object)root).name) && ((Object)root).name.IndexOf("ServingTable", StringComparison.OrdinalIgnoreCase) >= 0)
{
return true;
}
return false;
}
private static bool IsInteractiveLinkedToItemDispenser(Interactive it)
{
if ((Object)(object)it == (Object)null)
{
return false;
}
Transform val = (((Object)(object)((Component)it).transform != (Object)null) ? ((Component)it).transform.root : null);
if ((Object)(object)val == (Object)null)
{
return false;
}
ItemDispenser[] componentsInChildren = ((Component)val).GetComponentsInChildren<ItemDispenser>(true);
foreach (ItemDispenser val2 in componentsInChildren)
{
if (!((Object)(object)val2 == (Object)null) && (Object)(object)val2.interactive == (Object)(object)it)
{
return true;
}
}
return false;
}
private static bool IsExcluded(Interactive it)
{
//IL_0060: Unknown result type (might be due to invalid IL or missing references)
//IL_0067: Invalid comparison between Unknown and I4
//IL_006a: Unknown result type (might be due to invalid IL or missing references)
//IL_0071: Invalid comparison between Unknown and I4
//IL_0074: Unknown result type (might be due to invalid IL or missing references)
//IL_007b: Invalid comparison between Unknown and I4
//IL_0080: Unknown result type (might be due to invalid IL or missing references)
//IL_0087: Invalid comparison between Unknown and I4
//IL_008c: Unknown result type (might be due to invalid IL or missing references)
//IL_0093: Invalid comparison between Unknown and I4
//IL_0098: Unknown result type (might be due to invalid IL or missing references)
//IL_009f: Invalid comparison between Unknown and I4
if ((Object)(object)it == (Object)null)
{
return true;
}
if ((Object)(object)((Component)it).GetComponentInParent<BannerSelector>() != (Object)null)
{
return true;
}
if ((Object)(object)((Component)it).GetComponentInParent<ItemDispenser>() != (Object)null)
{
return true;
}
if (IsInteractiveLinkedToItemDispenser(it))
{
return true;
}
if ((Object)(object)FindInContext<Hive>((Component)(object)it) != (Object)null)
{
return true;
}
if (IsServingTableLike(it))
{
return true;
}
InteractiveObject componentInParent = ((Component)it).GetComponentInParent<InteractiveObject>();
if ((Object)(object)componentInParent != (Object)null)
{
if ((int)componentInParent.type == 20 || (int)componentInParent.type == 21 || (int)componentInParent.type == 22)
{
return true;
}
if ((int)componentInParent.type == 13)
{
return true;
}
if ((int)componentInParent.type == 37)
{
return true;
}
if ((int)componentInParent.type == 50)
{
return true;
}
}
return false;
}
private static bool IsSupplySourceUnsafeForConversion(Interactive it)
{
//IL_001f: Unknown result type (might be due to invalid IL or missing references)
//IL_0025: Invalid comparison between Unknown and I4
//IL_0028: Unknown result type (might be due to invalid IL or missing references)
//IL_002e: Invalid comparison between Unknown and I4
//IL_0033: Unknown result type (might be due to invalid IL or missing references)
//IL_0039: Invalid comparison between Unknown and I4
//IL_003c: Unknown result type (might be due to invalid IL or missing references)
//IL_0042: Invalid comparison between Unknown and I4
//IL_0047: Unknown result type (might be due to invalid IL or missing references)
//IL_004f: Unknown result type (might be due to invalid IL or missing references)
//IL_0057: Unknown result type (might be due to invalid IL or missing references)
//IL_005d: Unknown result type (might be due to invalid IL or missing references)
SupplySource val = (((Object)(object)it != (Object)null) ? ((Component)it).GetComponentInParent<SupplySource>() : null);
if ((Object)(object)val == (Object)null)
{
return false;
}
if ((int)val.putEvent == 2 || (int)val.putEvent == 1)
{
return true;
}
if ((int)val.putAllEvent == 2 || (int)val.putAllEvent == 1)
{
return true;
}
if ((int)val.putEvent != 0 && (int)val.putAllEvent != 0 && val.putEvent != val.putAllEvent)
{
return true;
}
if (IsServingTableLike(it))
{
return true;
}
if ((Object)(object)FindInContext<ItemDispenser>((Component)(object)it) != (Object)null)
{
return true;
}
if ((Object)(object)FindInContext<Hive>((Component)(object)it) != (Object)null)
{
return true;
}
return false;
}
private static bool IsAllowedTargetForConversion(Interactive it)
{
//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
//IL_00ba: Invalid comparison between Unknown and I4
//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
//IL_00c6: Invalid comparison between Unknown and I4
//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
//IL_00d2: Invalid comparison between Unknown and I4
if ((Object)(object)it == (Object)null)
{
return false;
}
if ((Object)(object)FindInContext<GardenBed>((Component)(object)it) != (Object)null)
{
return true;
}
if ((Object)(object)FindInContext<FruitHarvest>((Component)(object)it) != (Object)null)
{
return true;
}
if ((Object)(object)FindInContext<WashingMachine>((Component)(object)it) != (Object)null)
{
return true;
}
if ((Object)(object)FindInContext<ChickenCoop>((Component)(object)it) != (Object)null)
{
return true;
}
if ((Object)(object)FindInContext<FishTrap>((Component)(object)it) != (Object)null)
{
return true;
}
if ((Object)(object)FindInContext<CrabTrap>((Component)(object)it) != (Object)null)
{
return true;
}
if ((Object)(object)FindInContext<XmasPresent>((Component)(object)it) != (Object)null)
{
return true;
}
if ((Object)(object)FindInContext<BedroomBed>((Component)(object)it) != (Object)null)
{
return true;
}
if ((Object)(object)FindInContext<SupplySource>((Component)(object)it) != (Object)null && !IsSupplySourceUnsafeForConversion(it))
{
return true;
}
InteractiveObject componentInParent = ((Component)it).GetComponentInParent<InteractiveObject>();
if ((Object)(object)componentInParent != (Object)null)
{
if ((int)componentInParent.type == 6)
{
return true;
}
if ((int)componentInParent.type == 14)
{
return true;
}
if ((int)componentInParent.type == 36)
{
return true;
}
}
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(Interactive), "SetInteractParams")]
private static void Interactive_SetInteractParams_Prefix(Interactive __instance, ref Event interactEvent, bool isEnabled, ref string description, int instanceId)
{
if (!OneClickInteractions.Enabled || OneClickState.SuppressConversion || (Object)(object)__instance == (Object)null || (int)interactEvent != 4 || IsExcluded(__instance))
{
return;
}
bool flag = IsCorpseContext((Component)(object)__instance);
if ((!flag && !IsAllowedTargetForConversion(__instance)) || (!flag && (Object)(object)FindInContext<SupplySource>((Component)(object)__instance) != (Object)null && IsSupplySourceUnsafeForConversion(__instance)))
{
return;
}
if (isEnabled)
{
if (string.IsNullOrEmpty(description) && !string.IsNullOrEmpty(__instance.HoldDescr))
{
description = __instance.HoldDescr;
}
string descr = description;
OneClickState.MarkConverted(__instance, instanceId, descr);
interactEvent = (Event)2;
}
else if (OneClickState.UnmarkConverted(__instance, instanceId))
{
interactEvent = (Event)2;
}
}
[HarmonyPrefix]
[HarmonyPatch(typeof(Interactive), "Interact")]
private static bool Interactive_Interact_Prefix(Interactive __instance, ref Event e, ushort itemDataId, uint itemId)
{
if (!OneClickInteractions.Enabled)
{
return true;
}
if ((Object)(object)__instance == (Object)null)
{
return true;
}
if (((int)e == 1 || (int)e == 2 || (int)e == 4) && IsCorpseContext((Component)(object)__instance))
{
if ((int)e == 1)
{
return false;
}
if ((int)e == 2)
{
if (TryGetLootAvailable((Component)(object)__instance, out var lootAvailable) && lootAvailable)
{
e = (Event)4;
return true;
}
return false;
}
if ((int)e == 4)
{
if (TryGetLootAvailable((Component)(object)__instance, out var lootAvailable2) && lootAvailable2)
{
return true;
}
return false;
}
}
if ((int)e == 2 && OneClickState.IsConverted(__instance))
{
if (!IsAllowedTargetForConversion(__instance))
{
return true;
}
e = (Event)4;
return true;
}
return true;
}
[HarmonyPostfix]
[HarmonyPatch(typeof(Interactive), "set_IsHoldAvailable")]
private static void Interactive_set_IsHoldAvailable_Postfix(Interactive __instance, bool value)
{
if (!OneClickInteractions.Enabled || _guard_SetHoldAvailable || (Object)(object)__instance == (Object)null || IsExcluded(__instance) || IsCorpseContext((Component)(object)__instance) || !IsAllowedTargetForConversion(__instance))
{
return;
}
if (value && !__instance.IsPressAvailable)
{
_guard_SetHoldAvailable = true;
try
{
OneClickState.MarkConverted(__instance, -1000000, __instance.HoldDescr);
if (!string.IsNullOrEmpty(__instance.HoldDescr))
{
__instance.PressDescr = __instance.HoldDescr;
}
__instance.IsPressAvailable = true;
__instance.IsHoldAvailable = false;
return;
}
finally
{
_guard_SetHoldAvailable = false;
}
}
if (value || !OneClickState.UnmarkConverted(__instance, -1000000))
{
return;
}
_guard_SetHoldAvailable = true;
try
{
__instance.IsPressAvailable = false;
}
finally
{
_guard_SetHoldAvailable = false;
}
}
[HarmonyPostfix]
[HarmonyPatch(typeof(XmasPresent), "OnActiveChanged")]
private static void XmasPresent_OnActiveChanged_Postfix(XmasPresent __instance, bool previousvalue, bool newvalue)
{
if (!OneClickInteractions.Enabled || (Object)(object)__instance == (Object)null)
{
return;
}
Interactive componentInChildren = ((Component)__instance).GetComponentInChildren<Interactive>(true);
if ((Object)(object)componentInChildren == (Object)null || IsExcluded(componentInChildren) || !IsAllowedTargetForConversion(componentInChildren))
{
return;
}
_guard_SetHoldAvailable = true;
try
{
if (newvalue)
{
OneClickState.MarkConverted(componentInChildren, -1000000, componentInChildren.HoldDescr);
if (!string.IsNullOrEmpty(componentInChildren.HoldDescr))
{
componentInChildren.PressDescr = componentInChildren.HoldDescr;
}
componentInChildren.IsPressAvailable = true;
componentInChildren.IsHoldAvailable = false;
}
else
{
OneClickState.UnmarkConverted(componentInChildren, -1000000);
componentInChildren.IsPressAvailable = false;
componentInChildren.IsHoldAvailable = false;
}
}
finally
{
_guard_SetHoldAvailable = false;
}
}
[HarmonyPrefix]
[HarmonyPatch(typeof(SpawnableCollectible), "Interact")]
private static bool SpawnableCollectible_Interact_Prefix(SpawnableCollectible __instance, Event e, ushort itemDataId, uint itemId)
{
//IL_0014: Unknown result type (might be due to invalid IL or missing references)
//IL_0016: Invalid comparison between Unknown and I4
//IL_0018: Unknown result type (might be due to invalid IL or missing references)
//IL_001a: Invalid comparison between Unknown and I4
//IL_0032: Unknown result type (might be due to invalid IL or missing references)
if (!OneClickInteractions.Enabled)
{
return true;
}
if ((Object)(object)__instance == (Object)null)
{
return true;
}
if ((int)e != 2 && (int)e != 1)
{
return true;
}
if ((Object)(object)((Component)__instance).transform != (Object)null && IsNearDeadCreatureOrEnemy(((Component)__instance).transform.position, 2.5f))
{
return false;
}
return true;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(SpawnableCollectible), "TakeCollectibleServerRpc")]
private static bool SpawnableCollectible_TakeCollectibleServerRpc_Prefix(SpawnableCollectible __instance)
{
//IL_0028: Unknown result type (might be due to invalid IL or missing references)
if (!OneClickInteractions.Enabled)
{
return true;
}
if ((Object)(object)__instance == (Object)null)
{
return true;
}
if ((Object)(object)((Component)__instance).transform != (Object)null && IsNearDeadCreatureOrEnemy(((Component)__instance).transform.position, 2.5f))
{
return false;
}
return true;
}
}