Decompiled source of SlowHunger v1.1.0

plugins/SlowHunger.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.0", FrameworkDisplayName = "")]
[assembly: AssemblyCompany("SlowHunger")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: AssemblyInformationalVersion("1.1.0")]
[assembly: AssemblyProduct("SlowHunger")]
[assembly: AssemblyTitle("SlowHunger")]
[assembly: AssemblyVersion("1.1.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 SlowHunger
{
	[BepInPlugin("com.sillas.muck.slowhunger", "SlowHunger", "1.1.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		private sealed class Preset
		{
			public readonly float Multiplier;

			public readonly string Label;

			public Preset(float multiplier, string label)
			{
				Multiplier = multiplier;
				Label = label;
			}
		}

		public const string PluginGuid = "com.sillas.muck.slowhunger";

		public const string PluginName = "SlowHunger";

		public const string PluginVersion = "1.1.0";

		private const string SettingsApiGuid = "Terrain.MuckSettings";

		private const string BetterSettingsApiGuid = "muck.mrboxxy.bettersettingsapi";

		private const string MuckSaveGameGuid = "MuckSaveGame.MichMcb";

		private const string UiSettingName = "Hunger duration";

		private static readonly Preset[] StandardPresets = new Preset[12]
		{
			new Preset(1f, "Vanilla — 1x"),
			new Preset(0.8f, "1.25x — 20% less drain"),
			new Preset(2f / 3f, "1.5x — 33% less drain"),
			new Preset(0.6f, "1.67x — 40% less drain"),
			new Preset(0.5f, "2x — 50% less drain"),
			new Preset(0.4f, "2.5x — 60% less drain"),
			new Preset(1f / 3f, "3x — 67% less drain"),
			new Preset(0.25f, "4x — 75% less drain"),
			new Preset(0.2f, "5x — 80% less drain"),
			new Preset(0.1f, "10x — 90% less drain"),
			new Preset(0.05f, "20x — 95% less drain"),
			new Preset(0f, "Infinite — no hunger drain")
		};

		private static Plugin instance;

		private ConfigEntry<float> drainMultiplier;

		private Harmony harmony;

		private Type playerStatusType;

		private FieldInfo instanceField;

		private PropertyInfo instanceProperty;

		private FieldInfo hungerDrainRateField;

		private object currentStatus;

		private float originalDrainRate;

		private float lastAppliedMultiplier = -999f;

		private bool reflectionReady;

		private bool reflectionErrorLogged;

		private void Awake()
		{
			instance = this;
			((BaseUnityPlugin)this).Config.SaveOnConfigSet = true;
			drainMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Hunger", "DrainMultiplier", 0.6f, "Multiplicador da velocidade natural de perda de fome. 1.00 = vanilla; 0.25 = 75% menos drain e duracao 4x; 0.00 = sem perda natural. Faixa aplicada: 0.00 a 2.00.");
			((BaseUnityPlugin)this).Logger.LogInfo((object)"SlowHunger 1.1.0 carregado. Apenas o hunger drain sera alterado.");
			TryResolvePlayerStatus();
			TryInstallSettingsUi();
		}

		private void TryInstallSettingsUi()
		{
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Expected O, but got Unknown
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Expected O, but got Unknown
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Expected O, but got Unknown
			Type type = AccessTools.TypeByName("MuckSettings.Settings");
			if (type == null)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"SlowHunger UI indisponivel: SettingsApi nao instalado. O arquivo .cfg continua funcionando normalmente.");
				return;
			}
			MethodInfo methodInfo = AccessTools.Method(type, "Gameplay", (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"SlowHunger UI indisponivel: MuckSettings.Settings.Gameplay nao foi localizado.");
				return;
			}
			harmony = new Harmony("com.sillas.muck.slowhunger");
			try
			{
				harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(Plugin), "GameplaySettingsPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				Type type2 = AccessTools.TypeByName("Settings");
				MethodInfo methodInfo2 = ((type2 == null) ? null : AccessTools.Method(type2, "Start", (Type[])null, (Type[])null));
				if (methodInfo2 != null)
				{
					HarmonyMethod val = new HarmonyMethod(typeof(Plugin), "SettingsStartPostfix", (Type[])null);
					val.priority = 0;
					harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("SlowHunger UI indisponivel: falha ao aplicar o patch do SettingsApi. O .cfg continua ativo. " + ex));
				harmony.UnpatchSelf();
				harmony = null;
				return;
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)"SlowHunger UI integrada a aba Gameplay do SettingsApi.");
		}

		private static void GameplaySettingsPostfix(object page)
		{
			if ((Object)(object)instance == (Object)null || page == null)
			{
				return;
			}
			try
			{
				instance.AddHungerSlider(page);
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)instance).Logger.LogError((object)("SlowHunger: falha ao criar o slider de fome. O .cfg continua ativo. " + ex));
			}
		}

		private static void SettingsStartPostfix(object __instance)
		{
			if ((Object)(object)instance == (Object)null || __instance == null)
			{
				return;
			}
			try
			{
				instance.MoveAutoSaveNextToHunger(__instance);
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)instance).Logger.LogWarning((object)("SlowHunger UI: nao foi possivel reorganizar Auto Save. " + ex));
			}
		}

		private void AddHungerSlider(object page)
		{
			Type type = AccessTools.TypeByName("MuckSettings.SliderSetting");
			if (type == null)
			{
				throw new InvalidOperationException("MuckSettings.SliderSetting nao encontrado.");
			}
			MethodInfo methodInfo = FindAddSliderMethod(page.GetType());
			if (methodInfo == null)
			{
				throw new MissingMethodException(page.GetType().FullName, "AddSliderSetting(string, int, int, int, Action<int>)");
			}
			List<Component> list = FindSliderComponents(page, type);
			for (int i = 0; i < list.Count; i++)
			{
				if (ReadStringField(list[i], "settingName") == "Hunger duration")
				{
					return;
				}
			}
			List<Preset> presets = BuildPresets(ClampMultiplier(drainMultiplier.Value));
			int num = FindCurrentPresetIndex(presets, ClampMultiplier(drainMultiplier.Value));
			object createdControl = null;
			Action<int> action = delegate(int index)
			{
				if (index >= 0 && index < presets.Count)
				{
					drainMultiplier.Value = presets[index].Multiplier;
					if (createdControl != null)
					{
						SetSliderText(createdControl, presets[index].Label);
					}
					((BaseUnityPlugin)this).Logger.LogInfo((object)("SlowHunger UI: selecionado " + presets[index].Label + "."));
				}
			};
			HashSet<int> hashSet = new HashSet<int>();
			for (int num2 = 0; num2 < list.Count; num2++)
			{
				hashSet.Add(((Object)list[num2]).GetInstanceID());
			}
			methodInfo.Invoke(page, new object[5]
			{
				"Hunger duration",
				num,
				0,
				presets.Count - 1,
				action
			});
			List<Component> list2 = FindSliderComponents(page, type);
			for (int num3 = 0; num3 < list2.Count; num3++)
			{
				Component val = list2[num3];
				if (!hashSet.Contains(((Object)val).GetInstanceID()) && ReadStringField(val, "settingName") == "Hunger duration")
				{
					createdControl = val;
					break;
				}
			}
			if (createdControl == null)
			{
				for (int num4 = list2.Count - 1; num4 >= 0; num4--)
				{
					if (ReadStringField(list2[num4], "settingName") == "Hunger duration")
					{
						createdControl = list2[num4];
						break;
					}
				}
			}
			if (createdControl == null)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"SlowHunger: slider criado, mas o componente visual nao foi localizado; o valor numerico ainda funcionara.");
				return;
			}
			SetBooleanProperty(ReadField(createdControl, "slider"), "wholeNumbers", value: true);
			SetSliderText(createdControl, presets[num].Label);
			((BaseUnityPlugin)this).Logger.LogInfo((object)("SlowHunger UI: slider criado com " + presets[num].Label + "."));
		}

		private void MoveAutoSaveNextToHunger(object gameSettings)
		{
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_011b: Unknown result type (might be due to invalid IL or missing references)
			object obj = ReadField(gameSettings, "tutorial");
			Component val = (Component)((obj is Component) ? obj : null);
			if ((Object)(object)val == (Object)null || (Object)(object)val.transform.parent == (Object)null)
			{
				return;
			}
			Transform parent = val.transform.parent;
			RectTransform val2 = (RectTransform)(object)((parent is RectTransform) ? parent : null);
			RectTransform val3 = (RectTransform)(((Object)(object)val2 == (Object)null) ? null : /*isinst with value type is only supported in some contexts*/);
			if ((Object)(object)val3 == (Object)null)
			{
				return;
			}
			RectTransform val4 = FindDirectChildWithText(val2, "Auto Save");
			RectTransform val5 = FindHungerRoot(val3);
			if ((Object)(object)val4 == (Object)null || (Object)(object)val5 == (Object)null)
			{
				return;
			}
			((Transform)val4).SetParent((Transform)(object)val3, false);
			((Transform)val4).localScale = Vector3.one;
			((Transform)val4).SetSiblingIndex(((Transform)val5).GetSiblingIndex());
			VerticalLayoutGroup component = ((Component)val3).GetComponent<VerticalLayoutGroup>();
			if (!((Object)(object)component != (Object)null))
			{
				return;
			}
			float num = ((LayoutGroup)component).padding.top + ((LayoutGroup)component).padding.bottom;
			int num2 = 0;
			for (int i = 0; i < ((Transform)val3).childCount; i++)
			{
				Transform child = ((Transform)val3).GetChild(i);
				RectTransform val6 = (RectTransform)(object)((child is RectTransform) ? child : null);
				if (!((Object)(object)val6 == (Object)null) && ((Component)val6).gameObject.activeSelf && !IsIgnoredByLayout(val6))
				{
					float num3 = num;
					Rect rect = val6.rect;
					num = num3 + Math.Abs(((Rect)(ref rect)).height);
					num2++;
				}
			}
			if (num2 > 1)
			{
				num += ((HorizontalOrVerticalLayoutGroup)component).spacing * (float)(num2 - 1);
			}
			val3.SetSizeWithCurrentAnchors((Axis)1, num);
			LayoutRebuilder.ForceRebuildLayoutImmediate(val3);
			((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format(CultureInfo.InvariantCulture, "SlowHunger UI: Auto Save movido antes de Hunger duration; altura da lista = {0:0.##}.", new object[1] { num }));
		}

		private static RectTransform FindDirectChildWithText(RectTransform parent, string expectedText)
		{
			for (int i = 0; i < ((Transform)parent).childCount; i++)
			{
				Transform child = ((Transform)parent).GetChild(i);
				RectTransform val = (RectTransform)(object)((child is RectTransform) ? child : null);
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				Component[] componentsInChildren = ((Component)val).GetComponentsInChildren<Component>(true);
				foreach (Component val2 in componentsInChildren)
				{
					PropertyInfo propertyInfo = AccessTools.Property(((object)val2).GetType(), "text");
					if (!(propertyInfo == null) && !(propertyInfo.PropertyType != typeof(string)) && propertyInfo.CanRead && propertyInfo.GetValue(val2, null) as string == expectedText)
					{
						return val;
					}
				}
			}
			return null;
		}

		private static RectTransform FindHungerRoot(RectTransform content)
		{
			Type type = AccessTools.TypeByName("MuckSettings.SliderSetting");
			if (type == null)
			{
				return null;
			}
			Component[] componentsInChildren = ((Component)content).GetComponentsInChildren(type, true);
			foreach (Component val in componentsInChildren)
			{
				if (!(ReadStringField(val, "settingName") != "Hunger duration"))
				{
					Transform val2 = val.transform;
					while ((Object)(object)val2.parent != (Object)null && (Object)(object)val2.parent != (Object)(object)content)
					{
						val2 = val2.parent;
					}
					if (!((Object)(object)val2.parent == (Object)(object)content))
					{
						return null;
					}
					return (RectTransform)(object)((val2 is RectTransform) ? val2 : null);
				}
			}
			return null;
		}

		private static bool IsIgnoredByLayout(RectTransform child)
		{
			Component[] components = ((Component)child).GetComponents<Component>();
			foreach (Component obj in components)
			{
				ILayoutIgnorer val = (ILayoutIgnorer)(object)((obj is ILayoutIgnorer) ? obj : null);
				if (val != null && val.ignoreLayout)
				{
					return true;
				}
			}
			return false;
		}

		private static MethodInfo FindAddSliderMethod(Type pageType)
		{
			MethodInfo[] methods = pageType.GetMethods(BindingFlags.Instance | BindingFlags.Public);
			foreach (MethodInfo methodInfo in methods)
			{
				if (!(methodInfo.Name != "AddSliderSetting"))
				{
					ParameterInfo[] parameters = methodInfo.GetParameters();
					if (parameters.Length == 5 && parameters[0].ParameterType == typeof(string) && parameters[1].ParameterType == typeof(int) && parameters[2].ParameterType == typeof(int) && parameters[3].ParameterType == typeof(int) && parameters[4].ParameterType == typeof(Action<int>))
					{
						return methodInfo;
					}
				}
			}
			return null;
		}

		private static List<Component> FindSliderComponents(object page, Type sliderSettingType)
		{
			List<Component> list = new List<Component>();
			FieldInfo fieldInfo = AccessTools.Field(page.GetType(), "content");
			Component val = (Component)((fieldInfo == null) ? null : /*isinst with value type is only supported in some contexts*/);
			if ((Object)(object)val != (Object)null)
			{
				Component[] componentsInChildren = val.GetComponentsInChildren(sliderSettingType, true);
				for (int i = 0; i < componentsInChildren.Length; i++)
				{
					list.Add(componentsInChildren[i]);
				}
				return list;
			}
			Object[] array = Resources.FindObjectsOfTypeAll(sliderSettingType);
			foreach (Object obj in array)
			{
				Component val2 = (Component)(object)((obj is Component) ? obj : null);
				if ((Object)(object)val2 != (Object)null)
				{
					list.Add(val2);
				}
			}
			return list;
		}

		private static List<Preset> BuildPresets(float currentMultiplier)
		{
			List<Preset> list = new List<Preset>(StandardPresets);
			for (int i = 0; i < list.Count; i++)
			{
				if (NearlyEqual(list[i].Multiplier, currentMultiplier))
				{
					return list;
				}
			}
			Preset item = new Preset(currentMultiplier, BuildCustomLabel(currentMultiplier));
			float num = DurationFor(currentMultiplier);
			int index = list.Count - 1;
			for (int j = 0; j < list.Count - 1; j++)
			{
				if (DurationFor(list[j].Multiplier) > num)
				{
					index = j;
					break;
				}
			}
			list.Insert(index, item);
			return list;
		}

		private static int FindCurrentPresetIndex(List<Preset> presets, float multiplier)
		{
			for (int i = 0; i < presets.Count; i++)
			{
				if (NearlyEqual(presets[i].Multiplier, multiplier))
				{
					return i;
				}
			}
			return 0;
		}

		private static string BuildCustomLabel(float multiplier)
		{
			if (multiplier <= 0f)
			{
				return "Infinite — no hunger drain";
			}
			float num = 1f / multiplier;
			if (multiplier <= 1f)
			{
				int num2 = (int)Math.Round((1f - multiplier) * 100f);
				return string.Format(CultureInfo.InvariantCulture, "Custom — {0}x / {1}% less drain", new object[2]
				{
					num.ToString("0.##", CultureInfo.InvariantCulture),
					num2
				});
			}
			int num3 = (int)Math.Round((multiplier - 1f) * 100f);
			return string.Format(CultureInfo.InvariantCulture, "Custom — {0}x / {1}% more drain", new object[2]
			{
				num.ToString("0.##", CultureInfo.InvariantCulture),
				num3
			});
		}

		private static float DurationFor(float multiplier)
		{
			if (!(multiplier <= 0f))
			{
				return 1f / multiplier;
			}
			return float.PositiveInfinity;
		}

		private static bool NearlyEqual(float first, float second)
		{
			return Math.Abs(first - second) < 0.0005f;
		}

		private static object ReadField(object target, string fieldName)
		{
			if (target == null)
			{
				return null;
			}
			FieldInfo fieldInfo = AccessTools.Field(target.GetType(), fieldName);
			if (!(fieldInfo == null))
			{
				return fieldInfo.GetValue(target);
			}
			return null;
		}

		private static string ReadStringField(object target, string fieldName)
		{
			return ReadField(target, fieldName)?.ToString();
		}

		private static void SetSliderText(object sliderControl, string text)
		{
			object obj = ReadField(sliderControl, "value");
			if (obj != null)
			{
				SetProperty(obj, "text", text);
				SetProperty(obj, "enableAutoSizing", true);
				SetProperty(obj, "fontSizeMin", 10f);
				SetProperty(obj, "fontSizeMax", 22f);
			}
		}

		private static void SetBooleanProperty(object target, string propertyName, bool value)
		{
			SetProperty(target, propertyName, value);
		}

		private static void SetProperty(object target, string propertyName, object value)
		{
			if (target != null)
			{
				PropertyInfo propertyInfo = AccessTools.Property(target.GetType(), propertyName);
				if (propertyInfo != null && propertyInfo.CanWrite)
				{
					propertyInfo.SetValue(target, value, null);
				}
			}
		}

		private void TryResolvePlayerStatus()
		{
			if (reflectionReady)
			{
				return;
			}
			playerStatusType = Type.GetType("PlayerStatus, Assembly-CSharp", throwOnError: false);
			if (playerStatusType == null)
			{
				Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
				for (int i = 0; i < assemblies.Length; i++)
				{
					if (assemblies[i].GetName().Name == "Assembly-CSharp")
					{
						playerStatusType = assemblies[i].GetType("PlayerStatus", throwOnError: false);
						if (playerStatusType != null)
						{
							break;
						}
					}
				}
			}
			if (playerStatusType == null)
			{
				return;
			}
			BindingFlags bindingAttr = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
			BindingFlags bindingAttr2 = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
			instanceField = playerStatusType.GetField("Instance", bindingAttr);
			instanceProperty = playerStatusType.GetProperty("Instance", bindingAttr);
			hungerDrainRateField = playerStatusType.GetField("hungerDrainRate", bindingAttr2);
			if ((instanceField == null && instanceProperty == null) || hungerDrainRateField == null)
			{
				if (!reflectionErrorLogged)
				{
					((BaseUnityPlugin)this).Logger.LogError((object)"SlowHunger: nao encontrei PlayerStatus.Instance ou hungerDrainRate. Nenhuma alteracao foi aplicada.");
					reflectionErrorLogged = true;
				}
			}
			else
			{
				reflectionReady = true;
				((BaseUnityPlugin)this).Logger.LogInfo((object)"SlowHunger: PlayerStatus.hungerDrainRate encontrado.");
			}
		}

		private object GetPlayerStatusInstance()
		{
			if (instanceField != null)
			{
				return instanceField.GetValue(null);
			}
			if (instanceProperty != null)
			{
				return instanceProperty.GetValue(null, null);
			}
			return null;
		}

		private void Update()
		{
			if (!reflectionReady)
			{
				TryResolvePlayerStatus();
				return;
			}
			object playerStatusInstance = GetPlayerStatusInstance();
			if (playerStatusInstance == null)
			{
				currentStatus = null;
				lastAppliedMultiplier = -999f;
				return;
			}
			if (currentStatus != playerStatusInstance)
			{
				currentStatus = playerStatusInstance;
				object value = hungerDrainRateField.GetValue(playerStatusInstance);
				originalDrainRate = Convert.ToSingle(value);
				lastAppliedMultiplier = -999f;
				((BaseUnityPlugin)this).Logger.LogInfo((object)("SlowHunger: hungerDrainRate base detectado = " + originalDrainRate.ToString("0.###", CultureInfo.InvariantCulture)));
			}
			float num = ClampMultiplier(drainMultiplier.Value);
			if (!(Math.Abs(num - lastAppliedMultiplier) < 0.0001f))
			{
				float num2 = originalDrainRate * num;
				hungerDrainRateField.SetValue(playerStatusInstance, num2);
				lastAppliedMultiplier = num;
				((BaseUnityPlugin)this).Logger.LogInfo((object)("SlowHunger: hunger drain ajustado " + originalDrainRate.ToString("0.###", CultureInfo.InvariantCulture) + " -> " + num2.ToString("0.###", CultureInfo.InvariantCulture) + " (" + num.ToString("0.##", CultureInfo.InvariantCulture) + "x)"));
			}
		}

		private static float ClampMultiplier(float multiplier)
		{
			if (multiplier < 0f)
			{
				return 0f;
			}
			if (multiplier > 2f)
			{
				return 2f;
			}
			return multiplier;
		}

		private void OnDestroy()
		{
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			if (reflectionReady && currentStatus != null && hungerDrainRateField != null)
			{
				try
				{
					hungerDrainRateField.SetValue(currentStatus, originalDrainRate);
				}
				catch
				{
				}
			}
			if (instance == this)
			{
				instance = null;
			}
		}
	}
}