Decompiled source of ExpandedWorldModifiers v0.1.15

plugins\ExpandedWorldModifiers.dll

Decompiled 6 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("ExpandedWorldModifiers")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.15.0")]
[assembly: AssemblyInformationalVersion("0.1.15")]
[assembly: AssemblyProduct("ExpandedWorldModifiers")]
[assembly: AssemblyTitle("ExpandedWorldModifiers")]
[assembly: AssemblyVersion("0.1.15.0")]
namespace DCCreative.ExpandedWorldModifiers;

[BepInPlugin("dccreative.expandedworldmodifiers", "Expanded World Modifiers", "0.1.15-alpha")]
public sealed class ExpandedWorldModifiersPlugin : BaseUnityPlugin
{
	public const string Guid = "dccreative.expandedworldmodifiers";

	public const string Name = "Expanded World Modifiers";

	public const string Version = "0.1.15-alpha";

	internal static ManualLogSource Log;

	internal static ConfigEntry<string> ProtectedItemPrefabs;

	internal static ConfigEntry<string> CultivatedPickablePrefabs;

	internal static ConfigEntry<bool> VerboseLogging;

	private Harmony _harmony;

	private void Awake()
	{
		//IL_0084: Unknown result type (might be due to invalid IL or missing references)
		//IL_008e: Expected O, but got Unknown
		Log = ((BaseUnityPlugin)this).Logger;
		ProtectedItemPrefabs = ((BaseUnityPlugin)this).Config.Bind<string>("Drop Classification", "ProtectedItemPrefabs", "HardAntler,Wishbone,DragonTear,YagluthDrop,QueenDrop,FaderDrop", "Comma-separated prefab names that always stay at 1x. Quest items are protected automatically.");
		CultivatedPickablePrefabs = ((BaseUnityPlugin)this).Config.Bind<string>("Drop Classification", "CultivatedPickablePrefabs", "Pickable_Carrot,Pickable_Turnip,Pickable_Onion,Pickable_Barley,Pickable_Flax,Pickable_SeedCarrot,Pickable_SeedTurnip,Pickable_SeedOnion,Pickable_Mushroom_JotunPuffs,Pickable_Mushroom_Magecap,Pickable_Poteitr", "Pickable prefab names considered player-grown when their pickable has no world respawn timer.");
		VerboseLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "VerboseLogging", false, "Extra classification and synchronization logging.");
		RuntimeRates.SetLocal(WorldProfileStore.LoadForCurrentWorld(createIfMissing: true));
		_harmony = new Harmony("dccreative.expandedworldmodifiers");
		int num = 0;
		int num2 = 0;
		Type[] array = (from t in Assembly.GetExecutingAssembly().GetTypes()
			where t.GetCustomAttributes(typeof(HarmonyPatch), inherit: false).Length != 0
			select t).ToArray();
		foreach (Type type in array)
		{
			try
			{
				_harmony.CreateClassProcessor(type).Patch();
				num++;
			}
			catch (Exception arg)
			{
				num2++;
				((BaseUnityPlugin)this).Logger.LogError((object)$"Patch group {type.FullName} failed: {arg}");
			}
		}
		((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("{0} {1} loaded. Patch groups: {2} OK, {3} failed.", "Expanded World Modifiers", "0.1.15-alpha", num, num2));
	}

	private void OnDestroy()
	{
		Harmony harmony = _harmony;
		if (harmony != null)
		{
			harmony.UnpatchSelf();
		}
	}
}
internal static class JsonUtil
{
	internal static string Serialize<T>(T value)
	{
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		//IL_000f: Unknown result type (might be due to invalid IL or missing references)
		//IL_001b: Expected O, but got Unknown
		//IL_0016: Unknown result type (might be due to invalid IL or missing references)
		//IL_001c: Expected O, but got Unknown
		DataContractJsonSerializer val = new DataContractJsonSerializer(typeof(T), new DataContractJsonSerializerSettings
		{
			UseSimpleDictionaryFormat = true
		});
		using MemoryStream memoryStream = new MemoryStream();
		((XmlObjectSerializer)val).WriteObject((Stream)memoryStream, (object)value);
		return Encoding.UTF8.GetString(memoryStream.ToArray());
	}

	internal static T Deserialize<T>(string json)
	{
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		//IL_000f: Unknown result type (might be due to invalid IL or missing references)
		//IL_001b: Expected O, but got Unknown
		//IL_0016: Unknown result type (might be due to invalid IL or missing references)
		//IL_001c: Expected O, but got Unknown
		DataContractJsonSerializer val = new DataContractJsonSerializer(typeof(T), new DataContractJsonSerializerSettings
		{
			UseSimpleDictionaryFormat = true
		});
		using MemoryStream memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(json));
		return (T)((XmlObjectSerializer)val).ReadObject((Stream)memoryStream);
	}
}
internal static class NetworkAuthority
{
	[HarmonyPatch(typeof(ZNet), "OnNewConnection")]
	private static class OnNewConnectionPatch
	{
		private static void Postfix(ZNet __instance, ZNetPeer peer)
		{
			try
			{
				object? obj = PeerRpcField?.GetValue(peer);
				ZRpc val = (ZRpc)((obj is ZRpc) ? obj : null);
				if (val != null)
				{
					val.Register<string>("DCC_EWM_Sync_010", (Action<ZRpc, string>)Receive);
					if (__instance.IsServer())
					{
						RateSettings value = WorldProfileStore.LoadForCurrentWorld(createIfMissing: true);
						val.Invoke("DCC_EWM_Sync_010", new object[1] { JsonUtil.Serialize(value) });
					}
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = ExpandedWorldModifiersPlugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("Rate sync registration failed: " + ex.Message));
				}
			}
		}
	}

	[HarmonyPatch(typeof(ZoneSystem), "Start")]
	private static class ZoneSystemStartPatch
	{
		private static void Postfix()
		{
			try
			{
				int num = VanillaResourceIsolation.NeutralizeRuntime();
				if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
				{
					RateSettings rateSettings = WorldProfileStore.LoadForCurrentWorld(createIfMissing: true);
					RuntimeRates.SetLocal(rateSettings);
					ManualLogSource log = ExpandedWorldModifiersPlugin.Log;
					if (log != null)
					{
						log.LogInfo((object)$"Runtime split rates: resource={rateSettings.ResourceDrops:0.0#} mob={rateSettings.MobDrops:0.0#} harvest={rateSettings.PlayerHarvest:0.0#}; vanilla Resource Rate keys removed={num}.");
					}
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log2 = ExpandedWorldModifiersPlugin.Log;
				if (log2 != null)
				{
					log2.LogWarning((object)("Could not initialize runtime split rates: " + ex.Message));
				}
			}
		}
	}

	private const string RpcName = "DCC_EWM_Sync_010";

	private static readonly FieldInfo PeerRpcField = AccessTools.Field(typeof(ZNetPeer), "m_rpc");

	private static void Receive(ZRpc rpc, string json)
	{
		try
		{
			if (!((Object)(object)ZNet.instance != (Object)null) || !ZNet.instance.IsServer())
			{
				RateSettings rateSettings = JsonUtil.Deserialize<RateSettings>(json);
				rateSettings.Normalize();
				RuntimeRates.SetLocal(rateSettings);
				if (ExpandedWorldModifiersPlugin.VerboseLogging.Value)
				{
					ExpandedWorldModifiersPlugin.Log.LogInfo((object)$"Server rates: resource={rateSettings.ResourceDrops} mob={rateSettings.MobDrops} harvest={rateSettings.PlayerHarvest}");
				}
			}
		}
		catch (Exception ex)
		{
			ManualLogSource log = ExpandedWorldModifiersPlugin.Log;
			if (log != null)
			{
				log.LogWarning((object)("Could not apply server rate settings: " + ex.Message));
			}
		}
	}
}
internal enum YieldCategory
{
	ResourceDrops,
	MobDrops,
	PlayerHarvest
}
[DataContract]
public sealed class RateSettings
{
	[DataMember(Order = 1)]
	public float ResourceDrops = 1f;

	[DataMember(Order = 2)]
	public float MobDrops = 1f;

	[DataMember(Order = 3)]
	public float PlayerHarvest = 1f;

	public RateSettings Clone()
	{
		return new RateSettings
		{
			ResourceDrops = ResourceDrops,
			MobDrops = MobDrops,
			PlayerHarvest = PlayerHarvest
		};
	}

	public void Normalize()
	{
		ResourceDrops = RateMath.NearestStep(ResourceDrops);
		MobDrops = RateMath.NearestStep(MobDrops);
		PlayerHarvest = RateMath.NearestStep(PlayerHarvest);
	}

	internal float Get(YieldCategory c)
	{
		return c switch
		{
			YieldCategory.MobDrops => MobDrops, 
			YieldCategory.ResourceDrops => ResourceDrops, 
			_ => PlayerHarvest, 
		};
	}
}
internal static class RateMath
{
	internal static readonly float[] Steps = new float[5] { 0.5f, 1f, 1.5f, 2f, 3f };

	internal static float NearestStep(float value)
	{
		float num = Steps[0];
		float num2 = Math.Abs(value - num);
		float[] steps = Steps;
		foreach (float num3 in steps)
		{
			float num4 = Math.Abs(value - num3);
			if (num4 < num2)
			{
				num = num3;
				num2 = num4;
			}
		}
		return num;
	}

	internal static int StepIndex(float value)
	{
		value = NearestStep(value);
		for (int i = 0; i < Steps.Length; i++)
		{
			if (Math.Abs(Steps[i] - value) < 0.001f)
			{
				return i;
			}
		}
		return 1;
	}

	internal static int ScaleCount(int amount, float rate)
	{
		if (amount <= 0)
		{
			return amount;
		}
		float num = (float)amount * Math.Max(0f, rate);
		int num2 = Mathf.FloorToInt(num);
		float num3 = num - (float)num2;
		if (num3 > 0f && Random.value < num3)
		{
			num2++;
		}
		return num2;
	}
}
internal static class RuntimeRates
{
	private static RateSettings _settings = new RateSettings();

	internal static RateSettings Current => _settings;

	internal static void SetLocal(RateSettings settings)
	{
		_settings = settings ?? new RateSettings();
		_settings.Normalize();
	}

	internal static float Get(YieldCategory category)
	{
		return _settings.Get(category);
	}
}
internal static class VanillaResourceIsolation
{
	private static bool _reportedCachedCorrection;

	internal static bool IsResourceRateKey(string key)
	{
		if (string.IsNullOrWhiteSpace(key))
		{
			return false;
		}
		key = key.Trim();
		if (!key.Equals("resourcerate", StringComparison.OrdinalIgnoreCase))
		{
			return key.StartsWith("resourcerate ", StringComparison.OrdinalIgnoreCase);
		}
		return true;
	}

	internal static int NeutralizeWorld(object world, bool saveMetadata)
	{
		if (world == null)
		{
			return 0;
		}
		int num = 0;
		try
		{
			num += RemoveFromStringCollection(AccessTools.Field(world.GetType(), "m_startingGlobalKeys")?.GetValue(world));
			if (saveMetadata && num > 0)
			{
				world.GetType().GetMethod("SaveWorldMetaData", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null)?.Invoke(world, null);
			}
		}
		catch (Exception ex)
		{
			ManualLogSource log = ExpandedWorldModifiersPlugin.Log;
			if (log != null)
			{
				log.LogWarning((object)("Could not neutralize saved vanilla Resource Rate: " + ex.Message));
			}
		}
		return num;
	}

	internal static int NeutralizeRuntime()
	{
		ZoneSystem instance = ZoneSystem.instance;
		int num = 0;
		try
		{
			if ((Object)(object)instance != (Object)null)
			{
				object collection = AccessTools.Field(typeof(ZoneSystem), "m_globalKeys")?.GetValue(instance);
				num += RemoveFromStringCollection(collection);
				object dictionary = AccessTools.Field(typeof(ZoneSystem), "m_globalKeysValues")?.GetValue(instance);
				num += RemoveFromDictionary(dictionary);
			}
			NeutralizeCachedResourceRate(out var _);
		}
		catch (Exception ex)
		{
			ManualLogSource log = ExpandedWorldModifiersPlugin.Log;
			if (log != null)
			{
				log.LogWarning((object)("Could not neutralize live vanilla Resource Rate: " + ex.Message));
			}
		}
		return num;
	}

	internal static bool NeutralizeCachedResourceRate(out float previous)
	{
		previous = 1f;
		try
		{
			FieldInfo fieldInfo = AccessTools.Field(typeof(Game), "m_resourceRate");
			if (fieldInfo == null || !fieldInfo.IsStatic)
			{
				return false;
			}
			object value = fieldInfo.GetValue(null);
			if (value == null)
			{
				return false;
			}
			previous = Convert.ToSingle(value);
			if (Math.Abs(previous - 1f) < 0.001f)
			{
				return false;
			}
			fieldInfo.SetValue(null, 1f);
			if (!_reportedCachedCorrection)
			{
				_reportedCachedCorrection = true;
				ManualLogSource log = ExpandedWorldModifiersPlugin.Log;
				if (log != null)
				{
					log.LogInfo((object)$"Neutralized cached vanilla Resource Rate {previous:0.###}x -> 1x.");
				}
			}
			return true;
		}
		catch
		{
			return false;
		}
	}

	private static int RemoveFromStringCollection(object collection)
	{
		if (collection == null)
		{
			return 0;
		}
		List<string> list = new List<string>();
		if (collection is IEnumerable enumerable)
		{
			foreach (object item in enumerable)
			{
				if (item is string text && IsResourceRateKey(text))
				{
					list.Add(text);
				}
			}
		}
		if (list.Count == 0)
		{
			return 0;
		}
		int num = 0;
		if (collection is IList list2)
		{
			for (int num2 = list2.Count - 1; num2 >= 0; num2--)
			{
				if (list2[num2] is string key && IsResourceRateKey(key))
				{
					list2.RemoveAt(num2);
					num++;
				}
			}
			return num;
		}
		MethodInfo method = collection.GetType().GetMethod("Remove", new Type[1] { typeof(string) });
		if (method == null)
		{
			return 0;
		}
		bool flag = default(bool);
		foreach (string item2 in list)
		{
			try
			{
				object obj = method.Invoke(collection, new object[1] { item2 });
				int num3;
				if (obj is bool)
				{
					flag = (bool)obj;
					num3 = ((1 == 0) ? 1 : 0);
				}
				else
				{
					num3 = 1;
				}
				if (((uint)num3 | (flag ? 1u : 0u)) != 0)
				{
					num++;
				}
			}
			catch
			{
			}
		}
		return num;
	}

	private static int RemoveFromDictionary(object dictionary)
	{
		if (dictionary == null)
		{
			return 0;
		}
		List<object> list = new List<object>();
		if (dictionary is IDictionary dictionary2)
		{
			foreach (object key3 in dictionary2.Keys)
			{
				if (key3 is string key && IsResourceRateKey(key))
				{
					list.Add(key3);
				}
			}
			foreach (object item in list)
			{
				dictionary2.Remove(item);
			}
			return list.Count;
		}
		if (!(dictionary.GetType().GetProperty("Keys")?.GetValue(dictionary, null) is IEnumerable enumerable))
		{
			return 0;
		}
		foreach (object item2 in enumerable)
		{
			if (item2 is string key2 && IsResourceRateKey(key2))
			{
				list.Add(item2);
			}
		}
		MethodInfo method = dictionary.GetType().GetMethod("Remove", new Type[1] { typeof(string) });
		int num = 0;
		bool flag = default(bool);
		foreach (object item3 in list)
		{
			try
			{
				object obj = method?.Invoke(dictionary, new object[1] { item3 });
				int num2;
				if (obj is bool)
				{
					flag = (bool)obj;
					num2 = ((1 == 0) ? 1 : 0);
				}
				else
				{
					num2 = 1;
				}
				if (((uint)num2 | (flag ? 1u : 0u)) != 0)
				{
					num++;
				}
			}
			catch
			{
			}
		}
		return num;
	}
}
internal static class WorldModifierMenu
{
	[HarmonyPatch(typeof(FejdStartup), "OnServerOptions")]
	private static class OpenPatch
	{
		private static void Postfix(FejdStartup __instance)
		{
			try
			{
				BuildOrRefresh(__instance);
			}
			catch (Exception ex)
			{
				ExpandedWorldModifiersPlugin.Log.LogWarning((object)("World Modifiers UI injection failed: " + ex));
			}
		}
	}

	[HarmonyPatch(typeof(FejdStartup), "OnServerOptionsDone")]
	private static class DonePatch
	{
		private static void Prefix(FejdStartup __instance)
		{
			try
			{
				object world = AccessTools.Field(typeof(FejdStartup), "m_world")?.GetValue(__instance) ?? ActiveWorld;
				NeutralizeVanillaResourceRate();
				RateSettings rateSettings = ReadRows();
				WorldProfileStore.SaveForWorld(world, rateSettings);
				ManualLogSource log = ExpandedWorldModifiersPlugin.Log;
				if (log != null)
				{
					log.LogInfo((object)$"Saved split rates for {WorldProfileStore.GetWorldName(world)}: resource={rateSettings.ResourceDrops:0.0#} mob={rateSettings.MobDrops:0.0#} harvest={rateSettings.PlayerHarvest:0.0#}");
				}
			}
			catch (Exception ex)
			{
				ExpandedWorldModifiersPlugin.Log.LogWarning((object)("Could not save split rates: " + ex.Message));
			}
		}

		private static void Postfix(FejdStartup __instance)
		{
			try
			{
				object world = AccessTools.Field(typeof(FejdStartup), "m_world")?.GetValue(__instance) ?? ActiveWorld;
				int num = VanillaResourceIsolation.NeutralizeWorld(world, saveMetadata: true);
				if (num > 0)
				{
					ManualLogSource log = ExpandedWorldModifiersPlugin.Log;
					if (log != null)
					{
						log.LogInfo((object)$"Removed {num} saved vanilla Resource Rate key(s) from {WorldProfileStore.GetWorldName(world)}.");
					}
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log2 = ExpandedWorldModifiersPlugin.Log;
				if (log2 != null)
				{
					log2.LogWarning((object)("Could not finalize vanilla Resource Rate isolation: " + ex.Message));
				}
			}
		}
	}

	[HarmonyPatch(typeof(ServerOptionsGUI), "OnPresetButton")]
	private static class PresetPatch
	{
		private static void Postfix(KeyButton button)
		{
			try
			{
				string text = ((Object)((Component)button).gameObject).name ?? "";
				float num = 1f;
				if (text.IndexOf("Casual", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Easy", StringComparison.OrdinalIgnoreCase) >= 0)
				{
					num = 1.5f;
				}
				else if (text.IndexOf("Hardcore", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Hard", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Immersive", StringComparison.OrdinalIgnoreCase) >= 0)
				{
					num = 0.5f;
				}
				else if (text.IndexOf("Hammer", StringComparison.OrdinalIgnoreCase) >= 0)
				{
					num = 3f;
				}
				SetRows(new RateSettings
				{
					ResourceDrops = num,
					MobDrops = num,
					PlayerHarvest = num
				});
			}
			catch
			{
			}
		}
	}

	private static readonly string[] RowNames = new string[3] { "EWM_ResourceDrops", "EWM_MobDrops", "EWM_PlayerHarvest" };

	private static readonly string[] Labels = new string[3] { "Resource Drops", "Mob Drops", "Player Harvest" };

	private static readonly string[] Tooltips = new string[3] { "Changes resources obtained directly from the world: trees, rocks, ore deposits and world pickables. Creature loot is not affected.", "Changes loot dropped by wild or hostile creatures. The same item gathered directly from the world is not affected.", "Changes player-managed production: planted crops and seeds, tamed animal drops and offspring, eggs, honey and sap." };

	private static readonly string[] StepNames = new string[5] { "0.5x", "1x", "1.5x", "2x", "3x" };

	private static readonly List<GameObject> Rows = new List<GameObject>();

	private static object ActiveWorld;

	private static GameObject VanillaResourceRow;

	private const string ScrollViewportName = "EWM_ModifierViewport";

	private const string ScrollContentName = "EWM_ScrollContent";

	private static void BuildOrRefresh(FejdStartup startup)
	{
		//IL_0157: Unknown result type (might be due to invalid IL or missing references)
		//IL_0174: Unknown result type (might be due to invalid IL or missing references)
		//IL_017b: Expected O, but got Unknown
		//IL_019a: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b1: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c3: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c8: Unknown result type (might be due to invalid IL or missing references)
		//IL_0232: Unknown result type (might be due to invalid IL or missing references)
		//IL_0245: Unknown result type (might be due to invalid IL or missing references)
		ActiveWorld = AccessTools.Field(typeof(FejdStartup), "m_world")?.GetValue(startup);
		VanillaResourceIsolation.NeutralizeWorld(ActiveWorld, saveMetadata: false);
		RateSettings rows = WorldProfileStore.LoadForWorld(ActiveWorld);
		GameObject val = GameObject.Find("StartGui_ServerOptions") ?? GameObject.Find("GuiRoot/GUI/StartGui/StartGame/StartGui_ServerOptions");
		if (!Object.op_Implicit((Object)(object)val))
		{
			return;
		}
		Transform val2 = val.transform.Find("panel/Modifiers");
		if (!Object.op_Implicit((Object)(object)val2))
		{
			return;
		}
		Transform modifierContent = GetModifierContent(val2);
		Rows.Clear();
		for (int i = 0; i < RowNames.Length; i++)
		{
			Transform val3 = modifierContent.Find(RowNames[i]);
			if (Object.op_Implicit((Object)(object)val3))
			{
				Rows.Add(((Component)val3).gameObject);
			}
		}
		if (Rows.Count != 3)
		{
			Rows.Clear();
			Transform val4 = FindResourceRow(modifierContent);
			Transform val5 = modifierContent.Find("Portals") ?? val4;
			if (!Object.op_Implicit((Object)(object)val4) || !Object.op_Implicit((Object)(object)val5))
			{
				return;
			}
			VanillaResourceRow = ((Component)val4).gameObject;
			NeutralizeVanillaResourceRate();
			float num = Separation(modifierContent.Find("Combat"), modifierContent.Find("Death"));
			if (num <= 1f)
			{
				num = 58f;
			}
			float y = ((Component)val4).GetComponent<RectTransform>().anchoredPosition.y;
			foreach (Transform item in modifierContent)
			{
				Transform val6 = item;
				if (!((Object)(object)val6 == (Object)(object)val4))
				{
					RectTransform component = ((Component)val6).GetComponent<RectTransform>();
					if (Object.op_Implicit((Object)(object)component) && component.anchoredPosition.y < y - 1f)
					{
						component.anchoredPosition -= new Vector2(0f, num * 2f);
					}
				}
			}
			((Component)val4).gameObject.SetActive(false);
			for (int j = 0; j < 3; j++)
			{
				GameObject val7 = Object.Instantiate<GameObject>(((Component)val5).gameObject, modifierContent);
				((Object)val7).name = RowNames[j];
				val7.SetActive(true);
				RectTransform component2 = val7.GetComponent<RectTransform>();
				component2.anchoredPosition = new Vector2(component2.anchoredPosition.x, y - num * (float)j);
				ConfigureRow(val7, Labels[j], Tooltips[j]);
				Rows.Add(val7);
			}
			modifierContent = MakeScrollable(val2, num * 2f);
		}
		else
		{
			if (!Object.op_Implicit((Object)(object)VanillaResourceRow))
			{
				Transform val8 = FindResourceRowIncludingInactive(modifierContent);
				if (Object.op_Implicit((Object)(object)val8))
				{
					VanillaResourceRow = ((Component)val8).gameObject;
				}
			}
			NeutralizeVanillaResourceRate();
			EnsureScrollAtTop(val2);
		}
		SetRows(rows);
	}

	private static void NeutralizeVanillaResourceRate()
	{
		try
		{
			if (Object.op_Implicit((Object)(object)VanillaResourceRow))
			{
				Slider componentInChildren = VanillaResourceRow.GetComponentInChildren<Slider>(true);
				if (Object.op_Implicit((Object)(object)componentInChildren))
				{
					int num = FindVanillaNeutralIndex(componentInChildren);
					componentInChildren.value = num;
				}
			}
		}
		catch (Exception ex)
		{
			ManualLogSource log = ExpandedWorldModifiersPlugin.Log;
			if (log != null)
			{
				log.LogWarning((object)("Could not neutralize vanilla Resource Rate: " + ex.Message));
			}
		}
	}

	private static int FindVanillaNeutralIndex(Slider slider)
	{
		try
		{
			Type type = AccessTools.TypeByName("KeySlider");
			if (type == null)
			{
				return 2;
			}
			Component component = ((Component)slider).GetComponent(type);
			if ((Object)(object)component == (Object)null)
			{
				return 2;
			}
			if (!(AccessTools.Field(type, "m_settings")?.GetValue(component) is IList list))
			{
				return 2;
			}
			for (int i = 0; i < list.Count; i++)
			{
				object obj = list[i];
				if (obj == null)
				{
					continue;
				}
				string text = ((AccessTools.Field(obj.GetType(), "m_name")?.GetValue(obj) as string) ?? string.Empty).Replace("×", "x").Replace(" ", string.Empty).ToLowerInvariant();
				switch (text)
				{
				default:
					if (text.IndexOf("normal", StringComparison.OrdinalIgnoreCase) < 0)
					{
						continue;
					}
					break;
				case "1x":
				case "x1":
				case "1.0x":
				case "x1.0":
					break;
				}
				return i;
			}
		}
		catch
		{
		}
		return 2;
	}

	private static Transform FindResourceRowIncludingInactive(Transform root)
	{
		if (!Object.op_Implicit((Object)(object)root))
		{
			return null;
		}
		Transform[] componentsInChildren = ((Component)root).GetComponentsInChildren<Transform>(true);
		foreach (Transform val in componentsInChildren)
		{
			if (((Object)val).name == "Resources" || ((Object)val).name == "Resource" || ((Object)val).name == "ResourceRate")
			{
				return val;
			}
			try
			{
				Transform obj = val.Find("label");
				TextMeshProUGUI obj2 = ((obj != null) ? ((Component)obj).GetComponent<TextMeshProUGUI>() : null);
				string text = ((obj2 != null) ? ((TMP_Text)obj2).text : null) ?? "";
				if (text.IndexOf("resource", StringComparison.OrdinalIgnoreCase) >= 0 && text.IndexOf("drop", StringComparison.OrdinalIgnoreCase) < 0)
				{
					return val;
				}
			}
			catch
			{
			}
		}
		return null;
	}

	private static Transform FindResourceRow(Transform modifiers)
	{
		//IL_0054: Unknown result type (might be due to invalid IL or missing references)
		//IL_005b: Expected O, but got Unknown
		string[] array = new string[3] { "Resources", "Resource", "ResourceRate" };
		foreach (string text in array)
		{
			Transform val = modifiers.Find(text);
			if (Object.op_Implicit((Object)(object)val))
			{
				return val;
			}
		}
		foreach (Transform modifier in modifiers)
		{
			Transform val2 = modifier;
			Transform obj = val2.Find("label");
			TextMeshProUGUI obj2 = ((obj != null) ? ((Component)obj).GetComponent<TextMeshProUGUI>() : null);
			if ((((obj2 != null) ? ((TMP_Text)obj2).text : null) ?? "").IndexOf("resource", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return val2;
			}
		}
		return null;
	}

	private static float Separation(Transform a, Transform b)
	{
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		//IL_002c: Unknown result type (might be due to invalid IL or missing references)
		if (!Object.op_Implicit((Object)(object)a) || !Object.op_Implicit((Object)(object)b))
		{
			return 0f;
		}
		return Math.Abs(((Component)a).GetComponent<RectTransform>().anchoredPosition.y - ((Component)b).GetComponent<RectTransform>().anchoredPosition.y);
	}

	private static void ConfigureRow(GameObject row, string label, string tooltip)
	{
		Transform obj = row.transform.Find("label");
		TextMeshProUGUI val = ((obj != null) ? ((Component)obj).GetComponent<TextMeshProUGUI>() : null);
		if (Object.op_Implicit((Object)(object)val))
		{
			((TMP_Text)val).text = label;
		}
		Transform val2 = row.transform.Find("slider");
		Slider val3 = ((val2 != null) ? ((Component)val2).GetComponent<Slider>() : null);
		if (Object.op_Implicit((Object)(object)val3))
		{
			val3.minValue = 0f;
			val3.maxValue = 4f;
			val3.wholeNumbers = true;
		}
		if (!Object.op_Implicit((Object)(object)val2))
		{
			return;
		}
		Type type = AccessTools.TypeByName("KeySlider");
		Component val4 = ((type == null) ? null : ((Component)val2).GetComponent(type));
		if ((Object)(object)val4 == (Object)null)
		{
			return;
		}
		AccessTools.Field(type, "m_toolTip")?.SetValue(val4, tooltip);
		FieldInfo fieldInfo = AccessTools.Field(type, "m_settings");
		if (!(fieldInfo == null))
		{
			IList list = Activator.CreateInstance(fieldInfo.FieldType) as IList;
			Type type2 = fieldInfo.FieldType.GetGenericArguments()[0];
			for (int i = 0; i < StepNames.Length; i++)
			{
				object obj2 = Activator.CreateInstance(type2);
				AccessTools.Field(type2, "m_name")?.SetValue(obj2, StepNames[i]);
				AccessTools.Field(type2, "m_toolTip")?.SetValue(obj2, "Current multiplier: " + StepNames[i] + ".");
				list.Add(obj2);
			}
			fieldInfo.SetValue(val4, list);
		}
	}

	private static RateSettings ReadRows()
	{
		return new RateSettings
		{
			ResourceDrops = Get(0),
			MobDrops = Get(1),
			PlayerHarvest = Get(2)
		};
		static float Get(int i)
		{
			if (Rows.Count <= i || !Object.op_Implicit((Object)(object)Rows[i]))
			{
				return 1f;
			}
			if (TryReadDisplayedStep(Rows[i], out var value))
			{
				return value;
			}
			Slider componentInChildren = Rows[i].GetComponentInChildren<Slider>(true);
			int num = ((!Object.op_Implicit((Object)(object)componentInChildren)) ? 1 : Mathf.RoundToInt(componentInChildren.value));
			num = Mathf.Clamp(num, 0, RateMath.Steps.Length - 1);
			return RateMath.Steps[num];
		}
	}

	private static bool TryReadDisplayedStep(GameObject row, out float value)
	{
		value = 1f;
		try
		{
			TextMeshProUGUI[] componentsInChildren = row.GetComponentsInChildren<TextMeshProUGUI>(true);
			for (int i = 0; i < componentsInChildren.Length; i++)
			{
				string text = (((TMP_Text)componentsInChildren[i]).text ?? string.Empty).Trim();
				if (text.Length == 0)
				{
					continue;
				}
				string text2 = text.Replace("×", "x").Replace(" ", string.Empty).ToLowerInvariant();
				for (int j = 0; j < StepNames.Length; j++)
				{
					string text3 = RateMath.Steps[j].ToString("0.#", CultureInfo.InvariantCulture);
					if (text2 == StepNames[j].ToLowerInvariant() || text2 == "x" + text3 || text2 == text3 + "x")
					{
						value = RateMath.Steps[j];
						return true;
					}
				}
			}
		}
		catch
		{
		}
		return false;
	}

	private static void SetRows(RateSettings s)
	{
		if (s == null)
		{
			s = new RateSettings();
		}
		s.Normalize();
		float[] array = new float[3] { s.ResourceDrops, s.MobDrops, s.PlayerHarvest };
		for (int i = 0; i < Rows.Count && i < 3; i++)
		{
			Slider componentInChildren = Rows[i].GetComponentInChildren<Slider>();
			if (Object.op_Implicit((Object)(object)componentInChildren))
			{
				componentInChildren.value = RateMath.StepIndex(array[i]);
			}
		}
	}

	private static Transform GetModifierContent(Transform modifiers)
	{
		Transform parent = modifiers.parent;
		Transform obj = ((parent != null) ? parent.Find("EWM_ModifierViewport") : null);
		Transform val = ((obj != null) ? obj.Find("EWM_ScrollContent") : null);
		if (!Object.op_Implicit((Object)(object)val))
		{
			return modifiers;
		}
		return val;
	}

	private static Transform MakeScrollable(Transform modifiers, float extraHeight)
	{
		//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00db: Unknown result type (might be due to invalid IL or missing references)
		//IL_0138: Unknown result type (might be due to invalid IL or missing references)
		//IL_013f: Expected O, but got Unknown
		//IL_016f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0185: Unknown result type (might be due to invalid IL or missing references)
		//IL_019b: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ab: Unknown result type (might be due to invalid IL or missing references)
		//IL_01d1: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f7: Unknown result type (might be due to invalid IL or missing references)
		//IL_021f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0224: Unknown result type (might be due to invalid IL or missing references)
		//IL_0252: Unknown result type (might be due to invalid IL or missing references)
		//IL_0268: Unknown result type (might be due to invalid IL or missing references)
		//IL_027e: Unknown result type (might be due to invalid IL or missing references)
		//IL_028a: Unknown result type (might be due to invalid IL or missing references)
		//IL_02af: Unknown result type (might be due to invalid IL or missing references)
		//IL_02d1: Unknown result type (might be due to invalid IL or missing references)
		//IL_02d8: Expected O, but got Unknown
		try
		{
			Transform parent = modifiers.parent;
			RectTransform val = (RectTransform)(object)((parent is RectTransform) ? parent : null);
			if (!Object.op_Implicit((Object)(object)val))
			{
				return modifiers;
			}
			Transform val2 = ((Transform)val).Find("EWM_ModifierViewport");
			if (Object.op_Implicit((Object)(object)val2))
			{
				EnsureScrollAtTop(modifiers);
				Transform val3 = val2.Find("EWM_ScrollContent");
				return Object.op_Implicit((Object)(object)val3) ? val3 : modifiers;
			}
			Transform obj = modifiers.Find("Combat");
			RectTransform val4 = ((obj != null) ? ((Component)obj).GetComponent<RectTransform>() : null);
			if (!Object.op_Implicit((Object)(object)val4))
			{
				return modifiers;
			}
			float num = GetTopInPanel(val4, val) + 6f;
			float num2 = FindBottomButtonTop(val) + 8f;
			if (num <= num2 + 80f)
			{
				ExpandedWorldModifiersPlugin.Log.LogWarning((object)"Modifier scroll viewport was too small; leaving vanilla layout unmasked.");
				return modifiers;
			}
			Rect rect = val.rect;
			float num3 = ((Rect)(ref rect)).xMin + 6f;
			rect = val.rect;
			float num4 = ((Rect)(ref rect)).xMax - 6f;
			float num5 = num4 - num3;
			float num6 = num - num2;
			GameObject val5 = new GameObject("EWM_ModifierViewport", new Type[4]
			{
				typeof(RectTransform),
				typeof(Image),
				typeof(RectMask2D),
				typeof(ScrollRect)
			});
			val5.layer = ((Component)val).gameObject.layer;
			RectTransform component = val5.GetComponent<RectTransform>();
			((Transform)component).SetParent((Transform)(object)val, false);
			component.anchorMin = new Vector2(0.5f, 0.5f);
			component.anchorMax = new Vector2(0.5f, 0.5f);
			component.pivot = new Vector2(0.5f, 0.5f);
			component.sizeDelta = new Vector2(num5, num6);
			((Transform)component).localPosition = new Vector3((num3 + num4) * 0.5f, (num2 + num) * 0.5f, 0f);
			Image component2 = val5.GetComponent<Image>();
			((Graphic)component2).color = new Color(0f, 0f, 0f, 0f);
			((Graphic)component2).raycastTarget = true;
			RectTransform component3 = new GameObject("EWM_ScrollContent", new Type[1] { typeof(RectTransform) })
			{
				layer = ((Component)val).gameObject.layer
			}.GetComponent<RectTransform>();
			((Transform)component3).SetParent((Transform)(object)component, false);
			component3.anchorMin = new Vector2(0f, 1f);
			component3.anchorMax = new Vector2(1f, 1f);
			component3.pivot = new Vector2(0.5f, 1f);
			component3.anchoredPosition = Vector2.zero;
			component3.sizeDelta = new Vector2(0f, num6 + Mathf.Max(extraHeight, 1f) + 8f);
			List<Transform> list = new List<Transform>();
			foreach (Transform modifier in modifiers)
			{
				Transform val6 = modifier;
				if (!IsFixedHeading(val6))
				{
					list.Add(val6);
				}
			}
			foreach (Transform item in list)
			{
				item.SetParent((Transform)(object)component3, true);
			}
			ScrollRect component4 = val5.GetComponent<ScrollRect>();
			component4.horizontal = false;
			component4.vertical = true;
			component4.movementType = (MovementType)2;
			component4.inertia = true;
			component4.decelerationRate = 0.135f;
			component4.scrollSensitivity = 150f;
			component4.viewport = component;
			component4.content = component3;
			component4.verticalNormalizedPosition = 1f;
			return (Transform)(object)component3;
		}
		catch (Exception ex)
		{
			ExpandedWorldModifiersPlugin.Log.LogWarning((object)("Could not enable World Modifiers scrolling: " + ex));
			return modifiers;
		}
	}

	private static bool IsFixedHeading(Transform child)
	{
		try
		{
			TextMeshProUGUI[] componentsInChildren = ((Component)child).GetComponentsInChildren<TextMeshProUGUI>(true);
			for (int i = 0; i < componentsInChildren.Length; i++)
			{
				if ((((TMP_Text)componentsInChildren[i]).text ?? "").Trim().Equals("Customize", StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
			}
		}
		catch
		{
		}
		return false;
	}

	private static float GetTopInPanel(RectTransform target, RectTransform panel)
	{
		//IL_001b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0020: Unknown result type (might be due to invalid IL or missing references)
		Vector3[] array = (Vector3[])(object)new Vector3[4];
		target.GetWorldCorners(array);
		float num = float.MinValue;
		for (int i = 0; i < array.Length; i++)
		{
			float y = ((Transform)panel).InverseTransformPoint(array[i]).y;
			if (y > num)
			{
				num = y;
			}
		}
		return num;
	}

	private static float FindBottomButtonTop(RectTransform panel)
	{
		//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
		List<RectTransform> list = new List<RectTransform>();
		Button[] componentsInChildren = ((Component)panel).GetComponentsInChildren<Button>(true);
		foreach (Button val in componentsInChildren)
		{
			RectTransform component = ((Component)val).GetComponent<RectTransform>();
			if (Object.op_Implicit((Object)(object)component) && ((Component)val).gameObject.activeInHierarchy)
			{
				string text = "";
				TextMeshProUGUI componentInChildren = ((Component)val).GetComponentInChildren<TextMeshProUGUI>(true);
				if (Object.op_Implicit((Object)(object)componentInChildren))
				{
					text = (((TMP_Text)componentInChildren).text ?? "").Trim();
				}
				if (text.Equals("Cancel", StringComparison.OrdinalIgnoreCase) || text.Equals("Done", StringComparison.OrdinalIgnoreCase))
				{
					list.Add(component);
				}
			}
		}
		List<RectTransform> list2 = ((list.Count > 0) ? list : GetLowestButtons(panel, 2));
		if (list2.Count == 0)
		{
			Rect rect = panel.rect;
			return ((Rect)(ref rect)).yMin + 58f;
		}
		float num = float.MinValue;
		foreach (RectTransform item in list2)
		{
			float topInPanel = GetTopInPanel(item, panel);
			if (topInPanel > num)
			{
				num = topInPanel;
			}
		}
		return num;
	}

	private static List<RectTransform> GetLowestButtons(RectTransform panel, int count)
	{
		List<RectTransform> list = new List<RectTransform>();
		Button[] componentsInChildren = ((Component)panel).GetComponentsInChildren<Button>(true);
		foreach (Button val in componentsInChildren)
		{
			RectTransform component = ((Component)val).GetComponent<RectTransform>();
			if (Object.op_Implicit((Object)(object)component) && ((Component)val).gameObject.activeInHierarchy)
			{
				list.Add(component);
			}
		}
		list.Sort((RectTransform a, RectTransform b) => GetCenterYInPanel(a, panel).CompareTo(GetCenterYInPanel(b, panel)));
		if (list.Count > count)
		{
			list.RemoveRange(count, list.Count - count);
		}
		return list;
	}

	private static float GetCenterYInPanel(RectTransform target, RectTransform panel)
	{
		//IL_0010: Unknown result type (might be due to invalid IL or missing references)
		//IL_0017: Unknown result type (might be due to invalid IL or missing references)
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0026: Unknown result type (might be due to invalid IL or missing references)
		//IL_002b: Unknown result type (might be due to invalid IL or missing references)
		//IL_002d: Unknown result type (might be due to invalid IL or missing references)
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		Vector3[] array = (Vector3[])(object)new Vector3[4];
		target.GetWorldCorners(array);
		Vector3 val = (array[0] + array[2]) * 0.5f;
		return ((Transform)panel).InverseTransformPoint(val).y;
	}

	private static void EnsureScrollAtTop(Transform modifiers)
	{
		Transform parent = modifiers.parent;
		Transform obj = ((parent != null) ? parent.Find("EWM_ModifierViewport") : null);
		ScrollRect val = ((obj != null) ? ((Component)obj).GetComponent<ScrollRect>() : null);
		if (Object.op_Implicit((Object)(object)val))
		{
			val.verticalNormalizedPosition = 1f;
		}
	}
}
internal static class WorldProfileStore
{
	private static readonly string DirectoryPath = Path.Combine(Paths.ConfigPath, "ExpandedWorldModifiers", "Worlds");

	internal static string CurrentWorldName = "Default";

	internal static RateSettings LoadForCurrentWorld(bool createIfMissing)
	{
		string path = PathFor(CurrentWorldName = GetCurrentWorldName());
		try
		{
			Directory.CreateDirectory(DirectoryPath);
			if (File.Exists(path))
			{
				RateSettings rateSettings = JsonUtil.Deserialize<RateSettings>(File.ReadAllText(path));
				rateSettings.Normalize();
				RuntimeRates.SetLocal(rateSettings);
				return rateSettings;
			}
			RateSettings rateSettings2 = new RateSettings();
			if (createIfMissing)
			{
				File.WriteAllText(path, JsonUtil.Serialize(rateSettings2));
			}
			RuntimeRates.SetLocal(rateSettings2);
			return rateSettings2;
		}
		catch (Exception ex)
		{
			ManualLogSource log = ExpandedWorldModifiersPlugin.Log;
			if (log != null)
			{
				log.LogWarning((object)("Could not load world rate profile: " + ex.Message));
			}
			return new RateSettings();
		}
	}

	internal static RateSettings LoadForWorld(object world)
	{
		CurrentWorldName = GetWorldName(world);
		string path = PathFor(CurrentWorldName);
		if (!File.Exists(path))
		{
			return new RateSettings();
		}
		try
		{
			RateSettings rateSettings = JsonUtil.Deserialize<RateSettings>(File.ReadAllText(path));
			rateSettings.Normalize();
			return rateSettings;
		}
		catch
		{
			return new RateSettings();
		}
	}

	internal static void SaveForWorld(object world, RateSettings settings)
	{
		settings.Normalize();
		CurrentWorldName = GetWorldName(world);
		Directory.CreateDirectory(DirectoryPath);
		File.WriteAllText(PathFor(CurrentWorldName), JsonUtil.Serialize(settings));
		RuntimeRates.SetLocal(settings.Clone());
	}

	private static string PathFor(string worldName)
	{
		char[] invalidFileNameChars = Path.GetInvalidFileNameChars();
		foreach (char oldChar in invalidFileNameChars)
		{
			worldName = worldName.Replace(oldChar, '_');
		}
		return Path.Combine(DirectoryPath, worldName + ".json");
	}

	internal static string GetCurrentWorldName()
	{
		try
		{
			FieldInfo fieldInfo = AccessTools.Field(typeof(ZNet), "m_world");
			return GetWorldName(((Object)(object)ZNet.instance == (Object)null) ? null : fieldInfo?.GetValue(ZNet.instance));
		}
		catch
		{
			return "Default";
		}
	}

	internal static string GetWorldName(object world)
	{
		if (world == null)
		{
			return "Default";
		}
		try
		{
			string text = AccessTools.Field(world.GetType(), "m_name")?.GetValue(world) as string;
			return string.IsNullOrWhiteSpace(text) ? "Default" : text;
		}
		catch
		{
			return "Default";
		}
	}
}
internal static class PrefabNames
{
	internal static string Get(GameObject obj)
	{
		if (!Object.op_Implicit((Object)(object)obj))
		{
			return string.Empty;
		}
		string text = ((Object)obj).name ?? string.Empty;
		if (text.EndsWith("(Clone)", StringComparison.OrdinalIgnoreCase))
		{
			text = text.Substring(0, text.Length - "(Clone)".Length);
		}
		return text.Trim();
	}
}
internal static class CharacterDropCompat
{
	internal static Character GetCharacter(CharacterDrop drop)
	{
		if ((Object)(object)drop == (Object)null)
		{
			return null;
		}
		try
		{
			object? obj = AccessTools.Field(typeof(CharacterDrop), "m_character")?.GetValue(drop);
			Character val = (Character)((obj is Character) ? obj : null);
			if ((Object)(object)val != (Object)null)
			{
				return val;
			}
		}
		catch
		{
		}
		try
		{
			Character component = ((Component)drop).GetComponent<Character>();
			if ((Object)(object)component != (Object)null)
			{
				return component;
			}
		}
		catch
		{
		}
		try
		{
			Character componentInParent = ((Component)drop).GetComponentInParent<Character>();
			if ((Object)(object)componentInParent != (Object)null)
			{
				return componentInParent;
			}
		}
		catch
		{
		}
		return null;
	}

	internal static bool IsTamed(CharacterDrop drop)
	{
		Character character = GetCharacter(drop);
		if ((Object)(object)character != (Object)null)
		{
			try
			{
				MethodInfo methodInfo = AccessTools.Method(typeof(Character), "IsTamed", Type.EmptyTypes, (Type[])null);
				if (methodInfo != null && Convert.ToBoolean(methodInfo.Invoke(character, null)))
				{
					return true;
				}
			}
			catch
			{
			}
			try
			{
				FieldInfo fieldInfo = AccessTools.Field(typeof(Character), "m_tamed");
				if (fieldInfo != null && Convert.ToBoolean(fieldInfo.GetValue(character)))
				{
					return true;
				}
			}
			catch
			{
			}
		}
		try
		{
			Type type = AccessTools.TypeByName("Tameable");
			if (type != null)
			{
				Component val = ((Component)drop).GetComponent(type) ?? ((Component)drop).GetComponentInParent(type);
				if ((Object)(object)val != (Object)null)
				{
					MethodInfo methodInfo2 = AccessTools.Method(type, "IsTamed", Type.EmptyTypes, (Type[])null);
					if (methodInfo2 != null && Convert.ToBoolean(methodInfo2.Invoke(val, null)))
					{
						return true;
					}
				}
			}
		}
		catch
		{
		}
		return false;
	}
}
internal static class DropProtection
{
	internal static bool IsProtected(GameObject prefab)
	{
		if (!Object.op_Implicit((Object)(object)prefab))
		{
			return false;
		}
		string name = PrefabNames.Get(prefab);
		if ((from x in (ExpandedWorldModifiersPlugin.ProtectedItemPrefabs?.Value ?? "").Split(new char[1] { ',' })
			select x.Trim() into x
			where x.Length > 0
			select x).Any((string x) => string.Equals(x, name, StringComparison.OrdinalIgnoreCase)))
		{
			return true;
		}
		try
		{
			ItemDrop component = prefab.GetComponent<ItemDrop>();
			return (Object)(object)component != (Object)null && component.m_itemData != null && component.m_itemData.m_shared != null && component.m_itemData.m_shared.m_questItem;
		}
		catch
		{
			return false;
		}
	}
}
[HarmonyPatch(typeof(CharacterDrop), "GenerateDropList")]
internal static class CharacterDropGeneratePatch
{
	private static void Prefix()
	{
		VanillaResourceIsolation.NeutralizeCachedResourceRate(out var _);
	}

	private static void Postfix(CharacterDrop __instance, ref List<KeyValuePair<GameObject, int>> __result)
	{
		if (__result == null || (Object)(object)__instance == (Object)null)
		{
			return;
		}
		float num = RuntimeRates.Get((!CharacterDropCompat.IsTamed(__instance)) ? YieldCategory.MobDrops : YieldCategory.PlayerHarvest);
		if (Math.Abs(num - 1f) < 0.001f)
		{
			return;
		}
		List<KeyValuePair<GameObject, int>> list = new List<KeyValuePair<GameObject, int>>(__result.Count);
		foreach (KeyValuePair<GameObject, int> item in __result)
		{
			int num2 = (DropProtection.IsProtected(item.Key) ? item.Value : RateMath.ScaleCount(item.Value, num));
			if (num2 > 0)
			{
				list.Add(new KeyValuePair<GameObject, int>(item.Key, num2));
			}
		}
		__result = list;
	}
}
internal static class YieldContext
{
	private sealed class Frame
	{
		internal YieldCategory Category;

		internal bool SuppressGameScaleDrops;

		internal bool DropTableScaled;

		internal bool DirectCountScaled;
	}

	[ThreadStatic]
	private static Stack<Frame> _stack;

	internal static bool Active
	{
		get
		{
			if (_stack != null)
			{
				return _stack.Count > 0;
			}
			return false;
		}
	}

	internal static YieldCategory Current
	{
		get
		{
			if (!Active)
			{
				return YieldCategory.ResourceDrops;
			}
			return _stack.Peek().Category;
		}
	}

	internal static bool SuppressGameScaleDrops
	{
		get
		{
			if (Active)
			{
				return _stack.Peek().SuppressGameScaleDrops;
			}
			return false;
		}
	}

	internal static bool DropTableScaled
	{
		get
		{
			if (Active)
			{
				return _stack.Peek().DropTableScaled;
			}
			return false;
		}
		set
		{
			if (Active)
			{
				_stack.Peek().DropTableScaled = value;
			}
		}
	}

	internal static bool DirectCountScaled
	{
		get
		{
			if (Active)
			{
				return _stack.Peek().DirectCountScaled;
			}
			return false;
		}
		set
		{
			if (Active)
			{
				_stack.Peek().DirectCountScaled = value;
			}
		}
	}

	internal static void Push(YieldCategory c, bool suppressGameScaleDrops = false)
	{
		(_stack ?? (_stack = new Stack<Frame>())).Push(new Frame
		{
			Category = c,
			SuppressGameScaleDrops = suppressGameScaleDrops
		});
	}

	internal static void Pop()
	{
		if (Active)
		{
			_stack.Pop();
		}
	}
}
[HarmonyPatch]
internal static class ResourceContextPatch
{
	private static IEnumerable<MethodBase> TargetMethods()
	{
		(string, string)[] array = new(string, string)[5]
		{
			("DropOnDestroyed", "OnDestroyed"),
			("MineRock", "RPC_Hit"),
			("MineRock5", "DamageArea"),
			("TreeBase", "RPC_Damage"),
			("TreeLog", "RPC_Damage")
		};
		for (int i = 0; i < array.Length; i++)
		{
			(string, string) tuple = array[i];
			Type type = AccessTools.TypeByName(tuple.Item1);
			MethodInfo methodInfo = ((type == null) ? null : AccessTools.Method(type, tuple.Item2, (Type[])null, (Type[])null));
			if (methodInfo != null)
			{
				yield return methodInfo;
			}
		}
	}

	private static void Prefix()
	{
		VanillaResourceIsolation.NeutralizeCachedResourceRate(out var _);
		YieldContext.Push(YieldCategory.ResourceDrops);
	}

	private static Exception Finalizer(Exception __exception)
	{
		YieldContext.Pop();
		return __exception;
	}
}
[HarmonyPatch]
internal static class PlayerProductionContextPatch
{
	private static IEnumerable<MethodBase> TargetMethods()
	{
		(string, string)[] array = new(string, string)[2]
		{
			("Beehive", "RPC_Extract"),
			("SapCollector", "RPC_Extract")
		};
		for (int i = 0; i < array.Length; i++)
		{
			(string, string) tuple = array[i];
			Type type = AccessTools.TypeByName(tuple.Item1);
			MethodInfo methodInfo = ((type == null) ? null : AccessTools.Method(type, tuple.Item2, (Type[])null, (Type[])null));
			if (methodInfo != null)
			{
				yield return methodInfo;
			}
		}
	}

	private static void Prefix()
	{
		VanillaResourceIsolation.NeutralizeCachedResourceRate(out var _);
		YieldContext.Push(YieldCategory.PlayerHarvest);
	}

	private static Exception Finalizer(Exception __exception)
	{
		YieldContext.Pop();
		return __exception;
	}
}
[HarmonyPatch]
internal static class DropTablePatch
{
	private static IEnumerable<MethodBase> TargetMethods()
	{
		return from m in typeof(DropTable).GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
			where m.Name == "GetDropList" && m.ReturnType == typeof(List<GameObject>)
			select m;
	}

	private static void Prefix()
	{
		VanillaResourceIsolation.NeutralizeCachedResourceRate(out var _);
	}

	private static void Postfix(ref List<GameObject> __result)
	{
		if (!YieldContext.Active || __result == null)
		{
			return;
		}
		float num = RuntimeRates.Get(YieldContext.Current);
		if (Math.Abs(num - 1f) < 0.001f)
		{
			return;
		}
		List<GameObject> list = __result.ToList();
		List<GameObject> list2 = new List<GameObject>();
		foreach (GameObject item in list)
		{
			int num2 = (DropProtection.IsProtected(item) ? 1 : RateMath.ScaleCount(1, num));
			for (int i = 0; i < num2; i++)
			{
				list2.Add(item);
			}
		}
		__result = list2;
		YieldContext.DropTableScaled = true;
	}
}
[HarmonyPatch]
internal static class GameScaleDropsPatch
{
	private static MethodBase TargetMethod()
	{
		return AccessTools.Method(typeof(Game), "ScaleDrops", new Type[2]
		{
			typeof(ItemData),
			typeof(int)
		}, (Type[])null);
	}

	private static void Prefix()
	{
		VanillaResourceIsolation.NeutralizeCachedResourceRate(out var _);
	}

	private static void Postfix(ref int __result)
	{
		if (YieldContext.Active && YieldContext.Current == YieldCategory.PlayerHarvest && !YieldContext.SuppressGameScaleDrops && !YieldContext.DropTableScaled)
		{
			float num = RuntimeRates.Get(YieldCategory.PlayerHarvest);
			if (!(Math.Abs(num - 1f) < 0.001f))
			{
				__result = RateMath.ScaleCount(__result, num);
				YieldContext.DirectCountScaled = true;
			}
		}
	}
}
[HarmonyPatch(typeof(ItemDrop), "DropItem", new Type[]
{
	typeof(ItemData),
	typeof(int),
	typeof(Vector3),
	typeof(Quaternion)
})]
internal static class ItemDropPatch
{
	private static void Prefix(ItemData item, ref int amount)
	{
		if (!YieldContext.Active || YieldContext.DropTableScaled || YieldContext.DirectCountScaled || item == null)
		{
			return;
		}
		try
		{
			if (item.m_shared == null || !item.m_shared.m_questItem)
			{
				amount = RateMath.ScaleCount(amount, RuntimeRates.Get(YieldContext.Current));
			}
		}
		catch
		{
		}
	}
}
internal static class CultivatedPickables
{
	private static readonly HashSet<string> Known = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
	{
		"Pickable_Carrot", "Pickable_Turnip", "Pickable_Onion", "Pickable_SeedCarrot", "Pickable_SeedTurnip", "Pickable_SeedOnion", "Pickable_Barley", "Pickable_Flax", "Pickable_Mushroom_JotunPuffs", "Pickable_Mushroom_Magecap",
		"Pickable_Poteitr"
	};

	private static readonly HashSet<string> GrownPrefabs = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

	private static ZNetScene _cachedScene;

	private static void RebuildIfNeeded()
	{
		ZNetScene instance = ZNetScene.instance;
		if (!Object.op_Implicit((Object)(object)instance) || instance == _cachedScene)
		{
			return;
		}
		_cachedScene = instance;
		GrownPrefabs.Clear();
		try
		{
			if (instance.m_prefabs == null)
			{
				return;
			}
			foreach (GameObject prefab in instance.m_prefabs)
			{
				if (!Object.op_Implicit((Object)(object)prefab))
				{
					continue;
				}
				Plant component = prefab.GetComponent<Plant>();
				if (!Object.op_Implicit((Object)(object)component) || !(AccessTools.Field(typeof(Plant), "m_grownPrefabs")?.GetValue(component) is IEnumerable enumerable))
				{
					continue;
				}
				foreach (object item in enumerable)
				{
					GameObject val = (GameObject)((item is GameObject) ? item : null);
					if (Object.op_Implicit((Object)(object)val) && Object.op_Implicit((Object)(object)val.GetComponent<Pickable>()))
					{
						string text = PrefabNames.Get(val);
						if (!string.IsNullOrEmpty(text))
						{
							GrownPrefabs.Add(text);
						}
					}
				}
			}
			ConfigEntry<bool> verboseLogging = ExpandedWorldModifiersPlugin.VerboseLogging;
			if (verboseLogging != null && verboseLogging.Value)
			{
				ManualLogSource log = ExpandedWorldModifiersPlugin.Log;
				if (log != null)
				{
					log.LogInfo((object)$"Detected {GrownPrefabs.Count} cultivated harvest prefab(s) from Plant.m_grownPrefabs.");
				}
			}
		}
		catch (Exception ex)
		{
			ManualLogSource log2 = ExpandedWorldModifiersPlugin.Log;
			if (log2 != null)
			{
				log2.LogWarning((object)("Could not build cultivated crop registry: " + ex.Message));
			}
		}
	}

	internal static bool IsCultivated(Pickable p)
	{
		if ((Object)(object)p == (Object)null)
		{
			return false;
		}
		string name = PrefabNames.Get(((Component)p).gameObject);
		bool num = (from x in (ExpandedWorldModifiersPlugin.CultivatedPickablePrefabs?.Value ?? "").Split(new char[1] { ',' })
			select x.Trim()).Any((string x) => string.Equals(x, name, StringComparison.OrdinalIgnoreCase));
		RebuildIfNeeded();
		if (!num && !Known.Contains(name) && !GrownPrefabs.Contains(name))
		{
			return false;
		}
		try
		{
			FieldInfo fieldInfo = AccessTools.Field(typeof(Pickable), "m_respawnTimeMinutes");
			return fieldInfo == null || Convert.ToSingle(fieldInfo.GetValue(p)) <= 0.01f;
		}
		catch
		{
			return true;
		}
	}
}
[HarmonyPatch(typeof(Pickable), "RPC_Pick")]
internal static class PickablePatch
{
	private sealed class State
	{
		internal FieldInfo AmountField;

		internal int OriginalAmount;

		internal bool Pushed;
	}

	private static void Prefix(Pickable __instance, ref State __state)
	{
		if ((Object)(object)__instance == (Object)null)
		{
			return;
		}
		try
		{
			VanillaResourceIsolation.NeutralizeCachedResourceRate(out var _);
			YieldCategory yieldCategory = (CultivatedPickables.IsCultivated(__instance) ? YieldCategory.PlayerHarvest : YieldCategory.ResourceDrops);
			YieldContext.Push(yieldCategory, suppressGameScaleDrops: true);
			FieldInfo fieldInfo = AccessTools.Field(typeof(Pickable), "m_amount");
			if (fieldInfo == null || fieldInfo.FieldType != typeof(int))
			{
				__state = new State
				{
					Pushed = true
				};
				return;
			}
			int num = (int)fieldInfo.GetValue(__instance);
			int num2 = RateMath.ScaleCount(num, RuntimeRates.Get(yieldCategory));
			__state = new State
			{
				AmountField = fieldInfo,
				OriginalAmount = num,
				Pushed = true
			};
			fieldInfo.SetValue(__instance, num2);
			ConfigEntry<bool> verboseLogging = ExpandedWorldModifiersPlugin.VerboseLogging;
			if (verboseLogging != null && verboseLogging.Value)
			{
				ManualLogSource log = ExpandedWorldModifiersPlugin.Log;
				if (log != null)
				{
					log.LogInfo((object)$"Pickable {PrefabNames.Get(((Component)__instance).gameObject)} => {yieldCategory}: {num} -> {num2}");
				}
			}
		}
		catch (Exception ex)
		{
			ManualLogSource log2 = ExpandedWorldModifiersPlugin.Log;
			if (log2 != null)
			{
				log2.LogWarning((object)("Could not scale pickable: " + ex.Message));
			}
			if (__state == null)
			{
				__state = new State();
			}
		}
	}

	private static Exception Finalizer(Pickable __instance, State __state, Exception __exception)
	{
		try
		{
			if (__state != null && __state.AmountField != null && (Object)(object)__instance != (Object)null)
			{
				__state.AmountField.SetValue(__instance, __state.OriginalAmount);
			}
		}
		catch
		{
		}
		finally
		{
			if (__state != null && __state.Pushed)
			{
				YieldContext.Pop();
			}
		}
		return __exception;
	}
}
internal static class ProcreationCompat
{
	internal static bool IsTamed(Procreation procreation)
	{
		if ((Object)(object)procreation == (Object)null)
		{
			return false;
		}
		try
		{
			Character val = ((Component)procreation).GetComponent<Character>() ?? ((Component)procreation).GetComponentInParent<Character>();
			if ((Object)(object)val != (Object)null)
			{
				MethodInfo methodInfo = AccessTools.Method(typeof(Character), "IsTamed", Type.EmptyTypes, (Type[])null);
				if (methodInfo != null && Convert.ToBoolean(methodInfo.Invoke(val, null)))
				{
					return true;
				}
				FieldInfo fieldInfo = AccessTools.Field(typeof(Character), "m_tamed");
				if (fieldInfo != null && Convert.ToBoolean(fieldInfo.GetValue(val)))
				{
					return true;
				}
			}
		}
		catch
		{
		}
		try
		{
			Type type = AccessTools.TypeByName("Tameable");
			if (type == null)
			{
				return false;
			}
			Component val2 = ((Component)procreation).GetComponent(type) ?? ((Component)procreation).GetComponentInParent(type);
			if ((Object)(object)val2 == (Object)null)
			{
				return false;
			}
			MethodInfo methodInfo2 = AccessTools.Method(type, "IsTamed", Type.EmptyTypes, (Type[])null);
			return methodInfo2 != null && Convert.ToBoolean(methodInfo2.Invoke(val2, null));
		}
		catch
		{
			return false;
		}
	}
}
[HarmonyPatch(typeof(Procreation), "Procreate")]
internal static class ProcreationPatch
{
	private sealed class State
	{
		internal bool WasPregnant;

		internal bool Tamed;
	}

	[ThreadStatic]
	private static bool _replayingBonusBirth;

	private static bool IsPregnant(Procreation procreation)
	{
		if ((Object)(object)procreation == (Object)null)
		{
			return false;
		}
		try
		{
			MethodInfo methodInfo = AccessTools.Method(typeof(Procreation), "IsPregnant", Type.EmptyTypes, (Type[])null);
			return methodInfo != null && Convert.ToBoolean(methodInfo.Invoke(procreation, null));
		}
		catch
		{
			return false;
		}
	}

	private static bool ReplayVanillaBirth(Procreation procreation)
	{
		if ((Object)(object)procreation == (Object)null)
		{
			return false;
		}
		MethodInfo methodInfo = AccessTools.Method(typeof(Procreation), "MakePregnant", Type.EmptyTypes, (Type[])null);
		MethodInfo methodInfo2 = AccessTools.Method(typeof(Procreation), "Procreate", Type.EmptyTypes, (Type[])null);
		FieldInfo fieldInfo = AccessTools.Field(typeof(Procreation), "m_pregnancyDuration");
		if (methodInfo == null || methodInfo2 == null || fieldInfo == null)
		{
			return false;
		}
		object obj = null;
		try
		{
			obj = fieldInfo.GetValue(procreation);
			fieldInfo.SetValue(procreation, Convert.ChangeType(-1, fieldInfo.FieldType));
			_replayingBonusBirth = true;
			methodInfo.Invoke(procreation, null);
			methodInfo2.Invoke(procreation, null);
			return !IsPregnant(procreation);
		}
		catch (Exception ex)
		{
			ManualLogSource log = ExpandedWorldModifiersPlugin.Log;
			if (log != null)
			{
				log.LogWarning((object)("Could not replay vanilla bonus birth: " + ex.Message));
			}
			return false;
		}
		finally
		{
			_replayingBonusBirth = false;
			try
			{
				if (obj != null)
				{
					fieldInfo.SetValue(procreation, obj);
				}
			}
			catch
			{
			}
		}
	}

	private static bool Prefix(Procreation __instance, ref State __state)
	{
		if (_replayingBonusBirth)
		{
			__state = null;
			return true;
		}
		bool flag = ProcreationCompat.IsTamed(__instance);
		__state = new State
		{
			Tamed = flag,
			WasPregnant = (flag && IsPregnant(__instance))
		};
		if (!flag)
		{
			return true;
		}
		float num = RuntimeRates.Get(YieldCategory.PlayerHarvest);
		if (num >= 1f)
		{
			return true;
		}
		return Random.value < num;
	}

	private static void Postfix(Procreation __instance, State __state)
	{
		if (_replayingBonusBirth || __state == null || !__state.Tamed || !__state.WasPregnant || IsPregnant(__instance))
		{
			return;
		}
		float num = RuntimeRates.Get(YieldCategory.PlayerHarvest);
		if (num <= 1f)
		{
			return;
		}
		int num2 = RateMath.ScaleCount(1, num) - 1;
		if (num2 <= 0)
		{
			return;
		}
		int num3 = 0;
		for (int i = 0; i < num2; i++)
		{
			if (!ReplayVanillaBirth(__instance))
			{
				break;
			}
			num3++;
		}
		ConfigEntry<bool> verboseLogging = ExpandedWorldModifiersPlugin.VerboseLogging;
		if (verboseLogging != null && verboseLogging.Value && num3 > 0)
		{
			ManualLogSource log = ExpandedWorldModifiersPlugin.Log;
			if (log != null)
			{
				log.LogInfo((object)$"Player Harvest bonus birth: spawned {num3} additional offspring at {num:0.##}x.");
			}
		}
	}
}