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 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("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)"); } List list = FindSliderComponents(page, type); for (int i = 0; i < list.Count; i++) { if (ReadStringField(list[i], "settingName") == "Hunger duration") { return; } } List presets = BuildPresets(ClampMultiplier(drainMultiplier.Value)); int num = FindCurrentPresetIndex(presets, ClampMultiplier(drainMultiplier.Value)); object createdControl = null; Action 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 hashSet = new HashSet(); 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 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(); 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(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(); 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)) { return methodInfo; } } } return null; } private static List FindSliderComponents(object page, Type sliderSettingType) { List list = new List(); 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 BuildPresets(float currentMultiplier) { List list = new List(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 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; } } } }