Decompiled source of Full Belly v2.2.1

Full Belly.dll

Decompiled 3 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Photon.Pun;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("Full Belly")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Full Belly")]
[assembly: AssemblyCopyright("Copyright ©  2025")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("940386f2-efcb-440c-a8e5-aa2361ab032b")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace FullBellyMod;

[BepInPlugin("tony4twentys.full_belly", "Full Belly", "2.1.1")]
[BepInProcess("PEAK.exe")]
public class FullBellyPlugin : BaseUnityPlugin
{
	private static ConfigEntry<float> cfgFullnessDuration;

	private static ConfigEntry<float> cfgAccumulationWindow;

	private static ConfigEntry<float> cfgFullnessThreshold;

	private static ConfigEntry<string> cfgIgnoredItems;

	private static ConfigEntry<bool> cfgShowFullnessTimer;

	private void Awake()
	{
		//IL_0105: Unknown result type (might be due to invalid IL or missing references)
		//IL_010b: Expected O, but got Unknown
		cfgFullnessDuration = ((BaseUnityPlugin)this).Config.Bind<float>("Full Belly", "FullnessDuration", 300f, "Duration in seconds that hunger is paused after reaching fullness.");
		cfgAccumulationWindow = ((BaseUnityPlugin)this).Config.Bind<float>("Full Belly", "AccumulationWindow", 120f, "Time window in seconds to accumulate small hunger restorations.");
		cfgFullnessThreshold = ((BaseUnityPlugin)this).Config.Bind<float>("Full Belly", "FullnessThreshold", 0.25f, "Total hunger restoration percentage needed to trigger fullness.");
		cfgIgnoredItems = ((BaseUnityPlugin)this).Config.Bind<string>("Full Belly", "IgnoredItems", "", "Comma-separated list of item names to ignore when calculating fullness.Apple Berry Green,Apple Berry Red,Apple Berry Yellow,Airplane Food,Berrynana Blue,Berrynana Brown,Berrynana Pink,Berrynana Yellow,Clusterberry Black,Clusterberry Red,Clusterberry Yellow,Cure-All,Egg,Granola Bar,Item_Coconut_half,Item_Honeycomb,Kingberry Green,Kingberry Purple,Kingberry Yellow,Lollipop,Marshmallow,MedicinalRoot,Mushroom Chubby,Mushroom Cluster Poison,Mushroom Cluster,Mushroom Glow,Mushroom Lace Poison,Mushroom Lace,Mushroom Normie Poison,Mushroom Normie,Napberry,PandorasBox,Pepper Berry,ScoutCookies,TrailMix,Winterberry Orange,Winterberry Yellow,Glizzy.");
		cfgShowFullnessTimer = ((BaseUnityPlugin)this).Config.Bind<bool>("Full Belly", "ShowFullnessTimer", true, "Display countdown timer for fullness expiration.");
		((BaseUnityPlugin)this).Logger.LogInfo((object)"Full Belly mod started.");
		FullBellyManager.Initialize(((BaseUnityPlugin)this).Logger, cfgFullnessDuration.Value, cfgAccumulationWindow.Value, cfgFullnessThreshold.Value, cfgIgnoredItems.Value, cfgShowFullnessTimer.Value);
		Harmony val = new Harmony("tony4twentys.full_belly");
		val.PatchAll();
	}
}
public class FullBellyManager : MonoBehaviour
{
	[CompilerGenerated]
	private sealed class <ApplyFullness>d__22 : IEnumerator<object>, IDisposable, IEnumerator
	{
		private int <>1__state;

		private object <>2__current;

		public CharacterAfflictions afflictions;

		public FullBellyManager <>4__this;

		object IEnumerator<object>.Current
		{
			[DebuggerHidden]
			get
			{
				return <>2__current;
			}
		}

		object IEnumerator.Current
		{
			[DebuggerHidden]
			get
			{
				return <>2__current;
			}
		}

		[DebuggerHidden]
		public <ApplyFullness>d__22(int <>1__state)
		{
			this.<>1__state = <>1__state;
		}

		[DebuggerHidden]
		void IDisposable.Dispose()
		{
			<>1__state = -2;
		}

		private bool MoveNext()
		{
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Expected O, but got Unknown
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			switch (<>1__state)
			{
			default:
				return false;
			case 0:
				<>1__state = -1;
				<>4__this.isFull = true;
				afflictions.hungerPerSecond = 0f;
				<>4__this.logger.LogInfo((object)$"Full Belly active for {<>4__this.fullnessDuration} seconds.");
				<>2__current = (object)new WaitForSeconds(<>4__this.fullnessDuration);
				<>1__state = 1;
				return true;
			case 1:
				<>1__state = -1;
				afflictions.hungerPerSecond = <>4__this.cachedHungerRate;
				<>4__this.isFull = false;
				<>4__this.logger.LogInfo((object)"Full Belly expired. Hunger gain restored.");
				<>4__this.ShowPopup("Getting hungry again", Color.yellow);
				return false;
			}
		}

		bool IEnumerator.MoveNext()
		{
			//ILSpy generated this explicit interface implementation from .override directive in MoveNext
			return this.MoveNext();
		}

		[DebuggerHidden]
		void IEnumerator.Reset()
		{
			throw new NotSupportedException();
		}
	}

	[CompilerGenerated]
	private sealed class <FullnessCountdown>d__19 : IEnumerator<object>, IDisposable, IEnumerator
	{
		private int <>1__state;

		private object <>2__current;

		public float duration;

		public FullBellyManager <>4__this;

		private float <remaining>5__1;

		private int <minutes>5__2;

		private int <seconds>5__3;

		object IEnumerator<object>.Current
		{
			[DebuggerHidden]
			get
			{
				return <>2__current;
			}
		}

		object IEnumerator.Current
		{
			[DebuggerHidden]
			get
			{
				return <>2__current;
			}
		}

		[DebuggerHidden]
		public <FullnessCountdown>d__19(int <>1__state)
		{
			this.<>1__state = <>1__state;
		}

		[DebuggerHidden]
		void IDisposable.Dispose()
		{
			<>1__state = -2;
		}

		private bool MoveNext()
		{
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Expected O, but got Unknown
			switch (<>1__state)
			{
			default:
				return false;
			case 0:
				<>1__state = -1;
				<remaining>5__1 = duration;
				<>4__this.CreateOrUpdatePersistentTimer();
				break;
			case 1:
				<>1__state = -1;
				<remaining>5__1 -= 1f;
				break;
			}
			if (<remaining>5__1 > 0f)
			{
				<minutes>5__2 = Mathf.FloorToInt(<remaining>5__1 / 60f);
				<seconds>5__3 = Mathf.FloorToInt(<remaining>5__1 % 60f);
				((TMP_Text)<>4__this.timerText).text = $"Belly Full: {<minutes>5__2:0}:{<seconds>5__3:00}";
				((Graphic)<>4__this.timerText).color = Color.green;
				<>2__current = (object)new WaitForSeconds(1f);
				<>1__state = 1;
				return true;
			}
			TextMeshProUGUI timerText = <>4__this.timerText;
			Object.Destroy((Object)(object)((timerText != null) ? ((Component)timerText).gameObject : null));
			<>4__this.timerText = null;
			return false;
		}

		bool IEnumerator.MoveNext()
		{
			//ILSpy generated this explicit interface implementation from .override directive in MoveNext
			return this.MoveNext();
		}

		[DebuggerHidden]
		void IEnumerator.Reset()
		{
			throw new NotSupportedException();
		}
	}

	private static GameObject managerObject;

	private static FullBellyManager instance;

	private ManualLogSource logger;

	private float hungerRestoreAccumulator;

	private float timeSinceLastRestore;

	private float accumulationWindow;

	private float fullnessThreshold;

	private float fullnessDuration;

	private Coroutine fullnessCoroutine;

	private Coroutine timerCoroutine;

	private bool isFull;

	private bool hasLoggedReset;

	private float cachedHungerRate = -1f;

	private bool showFullnessTimer;

	private readonly Queue<(string itemName, float value)> restoreIntents = new Queue<(string, float)>();

	private HashSet<string> ignoredItems = new HashSet<string>();

	private TextMeshProUGUI timerText;

	public static void Initialize(ManualLogSource log, float duration, float window, float threshold, string ignoredItemsCSV, bool showTimer)
	{
		//IL_0020: Unknown result type (might be due to invalid IL or missing references)
		//IL_002a: Expected O, but got Unknown
		if ((Object)(object)managerObject != (Object)null)
		{
			Object.Destroy((Object)(object)managerObject);
		}
		managerObject = new GameObject("FullBellyManager");
		Object.DontDestroyOnLoad((Object)(object)managerObject);
		instance = managerObject.AddComponent<FullBellyManager>();
		instance.logger = log;
		instance.fullnessDuration = duration;
		instance.accumulationWindow = window;
		instance.fullnessThreshold = threshold;
		instance.showFullnessTimer = showTimer;
		instance.ignoredItems = new HashSet<string>(from s in ignoredItemsCSV.Split(new char[1] { ',' })
			select s.Trim() into s
			where !string.IsNullOrEmpty(s)
			select s, StringComparer.OrdinalIgnoreCase);
		SceneManager.activeSceneChanged += instance.OnSceneChanged;
	}

	private void OnSceneChanged(Scene oldScene, Scene newScene)
	{
		logger.LogInfo((object)"Scene changed. Resetting Full Belly state.");
		if (fullnessCoroutine != null)
		{
			((MonoBehaviour)this).StopCoroutine(fullnessCoroutine);
			fullnessCoroutine = null;
		}
		if (timerCoroutine != null)
		{
			((MonoBehaviour)this).StopCoroutine(timerCoroutine);
			timerCoroutine = null;
		}
		if ((Object)(object)timerText != (Object)null)
		{
			Object.Destroy((Object)(object)((Component)timerText).gameObject);
			timerText = null;
		}
		if (isFull && fullnessCoroutine != null && (Object)(object)Character.localCharacter?.refs?.afflictions != (Object)null)
		{
			((MonoBehaviour)this).StopCoroutine(fullnessCoroutine);
			if (timerCoroutine != null)
			{
				((MonoBehaviour)this).StopCoroutine(timerCoroutine);
			}
			Character.localCharacter.refs.afflictions.hungerPerSecond = 0.0005f;
			logger.LogInfo((object)"Full Belly effect cancelled due to scene change.");
			if ((Object)(object)timerText != (Object)null)
			{
				Object.Destroy((Object)(object)((Component)timerText).gameObject);
				timerText = null;
			}
		}
		isFull = false;
		hungerRestoreAccumulator = 0f;
		timeSinceLastRestore = 0f;
		hasLoggedReset = false;
		restoreIntents.Clear();
		try
		{
			float num = 0.0005f * Ascents.hungerRateMultiplier;
			if (num > 0f)
			{
				instance.cachedHungerRate = num;
				logger.LogInfo((object)$"[Scene Init] Cached base hungerPerSecond: {num}");
			}
			else
			{
				logger.LogWarning((object)"[Scene Init] Hunger rate multiplier returned zero or negative. Skipping cache.");
			}
		}
		catch (Exception ex)
		{
			logger.LogWarning((object)("[Scene Init] Could not determine hunger rate: " + ex.Message));
		}
	}

	[IteratorStateMachine(typeof(<FullnessCountdown>d__19))]
	private IEnumerator FullnessCountdown(float duration)
	{
		//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
		return new <FullnessCountdown>d__19(0)
		{
			<>4__this = this,
			duration = duration
		};
	}

	private void CreateOrUpdatePersistentTimer()
	{
		//IL_0029: Unknown result type (might be due to invalid IL or missing references)
		//IL_002f: Expected O, but got Unknown
		//IL_0079: Unknown result type (might be due to invalid IL or missing references)
		//IL_0080: Expected O, but got Unknown
		//IL_0102: Unknown result type (might be due to invalid IL or missing references)
		//IL_0119: Unknown result type (might be due to invalid IL or missing references)
		//IL_0130: Unknown result type (might be due to invalid IL or missing references)
		//IL_0147: Unknown result type (might be due to invalid IL or missing references)
		//IL_015e: Unknown result type (might be due to invalid IL or missing references)
		GameObject obj = GameObject.Find("FullBellyNotificationCanvas");
		Canvas val = ((obj != null) ? obj.GetComponent<Canvas>() : null);
		if ((Object)(object)val == (Object)null)
		{
			GameObject val2 = new GameObject("FullBellyNotificationCanvas");
			val = val2.AddComponent<Canvas>();
			val.renderMode = (RenderMode)0;
			val.sortingOrder = 999;
			val2.AddComponent<CanvasScaler>();
			val2.AddComponent<GraphicRaycaster>();
			Object.DontDestroyOnLoad((Object)(object)val2);
		}
		if ((Object)(object)timerText == (Object)null)
		{
			GameObject val3 = new GameObject("FullBellyTimerText");
			val3.transform.SetParent(((Component)val).transform);
			timerText = val3.AddComponent<TextMeshProUGUI>();
			((TMP_Text)timerText).fontSize = 30f;
			((TMP_Text)timerText).alignment = (TextAlignmentOptions)514;
			((Graphic)timerText).raycastTarget = false;
			((TMP_Text)timerText).enableWordWrapping = false;
			((TMP_Text)timerText).fontStyle = (FontStyles)1;
			RectTransform component = ((Component)timerText).GetComponent<RectTransform>();
			component.sizeDelta = new Vector2(400f, 100f);
			component.anchoredPosition = new Vector2(-500f, -400f);
			component.anchorMin = new Vector2(0.5f, 0.5f);
			component.anchorMax = new Vector2(0.5f, 0.5f);
			component.pivot = new Vector2(0.5f, 0.5f);
		}
	}

	private void LateUpdate()
	{
		timeSinceLastRestore += Time.deltaTime;
		Character localCharacter = Character.localCharacter;
		if ((Object)(object)localCharacter == (Object)null || localCharacter.refs == null || (Object)(object)localCharacter.refs.afflictions == (Object)null)
		{
			return;
		}
		if (!isFull && localCharacter.refs.afflictions.hungerPerSecond <= 0f && cachedHungerRate > 0f)
		{
			localCharacter.refs.afflictions.hungerPerSecond = cachedHungerRate;
			logger.LogWarning((object)$"[Fallback Reset] hungerPerSecond was 0 while not full. Restored to cached value: {cachedHungerRate}");
		}
		while (restoreIntents.Count > 0)
		{
			var (arg, num) = restoreIntents.Dequeue();
			logger.LogInfo((object)$"Applying hunger from: {arg}, value: {num * 100f:0.00}%");
			if (num >= fullnessThreshold || hungerRestoreAccumulator + num >= fullnessThreshold)
			{
				logger.LogInfo((object)"Fullness triggered.");
				if (fullnessCoroutine != null)
				{
					((MonoBehaviour)this).StopCoroutine(fullnessCoroutine);
				}
				if (timerCoroutine != null)
				{
					((MonoBehaviour)this).StopCoroutine(timerCoroutine);
				}
				fullnessCoroutine = ((MonoBehaviour)this).StartCoroutine(ApplyFullness(localCharacter.refs.afflictions));
				if (showFullnessTimer)
				{
					timerCoroutine = ((MonoBehaviour)this).StartCoroutine(FullnessCountdown(fullnessDuration));
				}
				hungerRestoreAccumulator = 0f;
				timeSinceLastRestore = 0f;
				hasLoggedReset = false;
			}
			else
			{
				hungerRestoreAccumulator += num;
				timeSinceLastRestore = 0f;
				hasLoggedReset = false;
				logger.LogInfo((object)$"Accumulated: {num * 100f:0.00}%, Total: {hungerRestoreAccumulator * 100f:0.00}%");
			}
		}
		if (timeSinceLastRestore >= accumulationWindow && !hasLoggedReset)
		{
			logger.LogInfo((object)"Accumulation window expired. Resetting.");
			hungerRestoreAccumulator = 0f;
			hasLoggedReset = true;
		}
	}

	[IteratorStateMachine(typeof(<ApplyFullness>d__22))]
	private IEnumerator ApplyFullness(CharacterAfflictions afflictions)
	{
		//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
		return new <ApplyFullness>d__22(0)
		{
			<>4__this = this,
			afflictions = afflictions
		};
	}

	private void ShowPopup(string message, Color color)
	{
		//IL_002c: Unknown result type (might be due to invalid IL or missing references)
		CreateOrUpdatePersistentTimer();
		if ((Object)(object)timerText != (Object)null)
		{
			((TMP_Text)timerText).text = message;
			((Graphic)timerText).color = color;
		}
	}

	public static void HandleHungerRestore(string itemName, float hungerRestored)
	{
		if (!((Object)(object)instance == (Object)null) && !(hungerRestored <= 0f))
		{
			string text = itemName.Replace("(Clone)", "").Trim();
			if (text.Contains(":"))
			{
				text = text.Split(new char[1] { ':' }).Last();
			}
			if (instance.ignoredItems.Contains(text))
			{
				instance.logger.LogInfo((object)("[FOOD IGNORED] " + text + " (ignored by config)"));
				return;
			}
			instance.logger.LogInfo((object)$"[FOOD LOG] Used: {text}, hunger restore: {hungerRestored * 100f:0.00}%");
			instance.restoreIntents.Enqueue((text, hungerRestored));
		}
	}
}
[HarmonyPatch(typeof(Item))]
[HarmonyPatch(/*Could not decode attribute arguments.*/)]
public class Patch_FinishCastPrimary
{
	public static MethodBase TargetMethod()
	{
		return typeof(Item).GetMethod("FinishCastPrimary", BindingFlags.Instance | BindingFlags.NonPublic);
	}

	public static void Postfix(Item __instance)
	{
		if ((Object)(object)__instance == (Object)null)
		{
			return;
		}
		Character localCharacter = Character.localCharacter;
		if ((Object)(object)__instance.holderCharacter != (Object)(object)localCharacter)
		{
			return;
		}
		ItemAction[] components = ((Component)__instance).GetComponents<ItemAction>();
		ItemAction[] array = components;
		foreach (ItemAction val in array)
		{
			if (((object)val).GetType().Name == "Action_RestoreHunger")
			{
				FieldInfo field = ((object)val).GetType().GetField("restorationAmount", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (!(field == null))
				{
					float num = (float)field.GetValue(val);
					if (num > 0f)
					{
						FullBellyManager.HandleHungerRestore(((Object)__instance).name, num);
					}
				}
			}
			else if (((object)val).GetType().Name == "Action_ModifyStatus")
			{
				FieldInfo field2 = ((object)val).GetType().GetField("statusType", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				FieldInfo field3 = ((object)val).GetType().GetField("changeAmount", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (field2 == null || field3 == null)
				{
					continue;
				}
				object value = field2.GetValue(val);
				if (value != null && value.ToString().Contains("Hunger"))
				{
					float num2 = (float)field3.GetValue(val);
					if (num2 < 0f)
					{
						FullBellyManager.HandleHungerRestore(((Object)__instance).name, 0f - num2);
					}
				}
			}
			else
			{
				if (!(((object)val).GetType().Name == "Action_ApplyAffliction"))
				{
					continue;
				}
				FieldInfo field4 = ((object)val).GetType().GetField("affliction", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (!(field4 == null))
				{
					object value2 = field4.GetValue(val);
					if (value2 != null && value2.ToString().Contains("Chaos"))
					{
						float currentStatus = Character.localCharacter.refs.afflictions.GetCurrentStatus((STATUSTYPE)1);
						FullBellyManager.HandleHungerRestore("Pandora's Box", currentStatus);
					}
				}
			}
		}
	}
}
[HarmonyPatch(typeof(Character), "GetFedItemRPC")]
public class Patch_GetFedItemRPC
{
	public static void Postfix(Character __instance, int itemPhotonID)
	{
		bool? obj;
		if (__instance == null)
		{
			obj = null;
		}
		else
		{
			PhotonView photonView = ((MonoBehaviourPun)__instance).photonView;
			obj = ((photonView != null) ? new bool?(!photonView.IsMine) : null);
		}
		bool? flag = obj;
		if (flag.GetValueOrDefault(true))
		{
			return;
		}
		PhotonView val = PhotonView.Find(itemPhotonID);
		if ((Object)(object)val == (Object)null)
		{
			return;
		}
		Item component = ((Component)val).GetComponent<Item>();
		if ((Object)(object)component == (Object)null)
		{
			return;
		}
		ItemAction[] components = ((Component)component).GetComponents<ItemAction>();
		ItemAction[] array = components;
		foreach (ItemAction val2 in array)
		{
			if (((object)val2).GetType().Name == "Action_RestoreHunger")
			{
				FieldInfo field = ((object)val2).GetType().GetField("restorationAmount", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (!(field == null))
				{
					float num = (float)field.GetValue(val2);
					if (num > 0f)
					{
						FullBellyManager.HandleHungerRestore(((Object)component).name + " (Fed)", num);
					}
				}
			}
			else
			{
				if (!(((object)val2).GetType().Name == "Action_ModifyStatus"))
				{
					continue;
				}
				FieldInfo field2 = ((object)val2).GetType().GetField("statusType", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				FieldInfo field3 = ((object)val2).GetType().GetField("changeAmount", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (field2 == null || field3 == null)
				{
					continue;
				}
				object value = field2.GetValue(val2);
				if (value != null && value.ToString().Contains("Hunger"))
				{
					float num2 = (float)field3.GetValue(val2);
					if (num2 < 0f)
					{
						FullBellyManager.HandleHungerRestore(((Object)component).name + " (Fed)", 0f - num2);
					}
				}
			}
		}
	}
}