Decompiled source of EndlessDays v1.2.1

EndlessDays.dll

Decompiled 14 hours ago
using System;
using System.Collections;
using System.Reflection;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;

[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace EndlessDays;

[BepInPlugin("aj.endlessdays", "EndlessDays", "1.2.0")]
public class Plugin : BaseUnityPlugin
{
	public const string Guid = "aj.endlessdays";

	public const string Name = "EndlessDays";

	public const string Version = "1.2.0";

	internal static ManualLogSource Log;

	private static ConfigEntry<bool> cfgEnabled;

	private const float NewDayAtHour = 22f;

	private static ConfigEntry<bool> cfgResetMonsters;

	private static ConfigEntry<bool> cfgShowMessage;

	private const BindingFlags Any = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

	private static Type tTimeOfDay;

	private static PropertyInfo pTimeOfDayInstance;

	private static FieldInfo fCurrentDayTime;

	private static FieldInfo fTotalTime;

	private static FieldInfo fNormalizedTime;

	private static FieldInfo fHour;

	private static FieldInfo fNumberOfHours;

	private static FieldInfo fGlobalTime;

	private static MethodInfo mCalculatePlanetTime;

	private static PropertyInfo pStartOfRoundInstance;

	private static FieldInfo fShipHasLanded;

	private static FieldInfo fShipIsLeaving;

	private static FieldInfo fInShipPhase;

	private static FieldInfo fCurrentLevel;

	private static FieldInfo fPlanetHasTime;

	private static FieldInfo fLivingPlayers;

	private static PropertyInfo pRoundManagerInstance;

	private static PropertyInfo pRoundManagerIsServer;

	private static FieldInfo fSpawnedEnemies;

	private static FieldInfo fIsEnemyDead;

	private static FieldInfo fRmCurrentHour;

	private static FieldInfo fCannotSpawnMoreInside;

	private static FieldInfo[] fEnemyPowerFields;

	private static MethodInfo mDespawnEnemyOnServer;

	private static MethodInfo mResetEnemyTypesSpawnedCounts;

	private static PropertyInfo pHudInstance;

	private static MethodInfo mDisplayTip;

	private static bool wasOnMoon;

	private static float morningTime = -1f;

	private static int loopCount;

	private static bool loggedUpdateError;

	private static DateTime lastLoopUtc = DateTime.MinValue;

	private static bool verifyPending;

	private static int failedLoops;

	private static bool pausedThisMoon;

	private const double LoopCooldownSeconds = 5.0;

	private static DateTime cycleStartUtc = DateTime.MinValue;

	private void Awake()
	{
		//IL_008a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0090: Expected O, but got Unknown
		//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b9: Expected O, but got Unknown
		//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
		//IL_0108: Expected O, but got Unknown
		Log = ((BaseUnityPlugin)this).Logger;
		cfgEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Turn the endless day loop on or off.");
		cfgResetMonsters = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ResetMonstersEachMorning", true, "Despawn all living monsters and reset spawn counters when a new morning starts (host only).");
		cfgShowMessage = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ShowNewDayMessage", true, "Show a popup when the day loops.");
		if (!ResolveGameMembers())
		{
			Log.LogError((object)"Could not find required game code. The game may have updated; EndlessDays is disabled.");
			return;
		}
		Harmony val = new Harmony("aj.endlessdays");
		MethodInfo methodInfo = AccessTools.Method(tTimeOfDay, "Update", (Type[])null, (Type[])null);
		HarmonyMethod val2 = new HarmonyMethod(typeof(Plugin), "TimeOfDayUpdatePostfix", (Type[])null);
		val.Patch((MethodBase)methodInfo, (HarmonyMethod)null, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		MethodInfo methodInfo2 = AccessTools.Method(pStartOfRoundInstance.DeclaringType, "ShipLeaveAutomatically", (Type[])null, (Type[])null);
		if (methodInfo2 != null)
		{
			val.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(Plugin), "ShipLeaveAutomaticallyPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}
		else
		{
			Log.LogWarning((object)"ShipLeaveAutomatically not found; midnight safety net disabled (day loop still works).");
		}
		Log.LogInfo((object)"EndlessDays 1.2.0 loaded. The ship will wait for you.");
	}

	private static bool ResolveGameMembers()
	{
		tTimeOfDay = AccessTools.TypeByName("TimeOfDay");
		Type type = AccessTools.TypeByName("StartOfRound");
		Type type2 = AccessTools.TypeByName("RoundManager");
		Type type3 = AccessTools.TypeByName("HUDManager");
		Type type4 = AccessTools.TypeByName("SelectableLevel");
		Type type5 = AccessTools.TypeByName("EnemyAI");
		if (tTimeOfDay == null || type == null)
		{
			return false;
		}
		pTimeOfDayInstance = tTimeOfDay.GetProperty("Instance", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
		fCurrentDayTime = tTimeOfDay.GetField("currentDayTime", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
		fTotalTime = tTimeOfDay.GetField("totalTime", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
		fNormalizedTime = tTimeOfDay.GetField("normalizedTimeOfDay", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
		fHour = tTimeOfDay.GetField("hour", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
		fNumberOfHours = tTimeOfDay.GetField("numberOfHours", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
		fGlobalTime = tTimeOfDay.GetField("globalTime", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
		if (fGlobalTime != null && fGlobalTime.FieldType != typeof(float))
		{
			fGlobalTime = null;
		}
		if (type4 != null)
		{
			mCalculatePlanetTime = tTimeOfDay.GetMethod("CalculatePlanetTime", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { type4 }, null);
		}
		pStartOfRoundInstance = type.GetProperty("Instance", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
		fShipHasLanded = type.GetField("shipHasLanded", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
		fShipIsLeaving = type.GetField("shipIsLeaving", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
		fInShipPhase = type.GetField("inShipPhase", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
		fCurrentLevel = type.GetField("currentLevel", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
		fLivingPlayers = type.GetField("livingPlayers", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
		if (fLivingPlayers != null && fLivingPlayers.FieldType != typeof(int))
		{
			fLivingPlayers = null;
		}
		if (type4 != null)
		{
			fPlanetHasTime = type4.GetField("planetHasTime", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
		}
		if (type2 != null)
		{
			pRoundManagerInstance = type2.GetProperty("Instance", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			pRoundManagerIsServer = type2.GetProperty("IsServer", BindingFlags.Instance | BindingFlags.Public);
			fSpawnedEnemies = type2.GetField("SpawnedEnemies", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			fRmCurrentHour = type2.GetField("currentHour", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			fCannotSpawnMoreInside = type2.GetField("cannotSpawnMoreInsideEnemies", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			fEnemyPowerFields = new FieldInfo[3]
			{
				type2.GetField("currentEnemyPower", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic),
				type2.GetField("currentOutsideEnemyPower", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic),
				type2.GetField("currentDaytimeEnemyPower", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
			};
			mResetEnemyTypesSpawnedCounts = type2.GetMethod("ResetEnemyTypesSpawnedCounts", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
			MethodInfo[] methods = type2.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (MethodInfo methodInfo in methods)
			{
				if (methodInfo.Name == "DespawnEnemyOnServer" && methodInfo.GetParameters().Length == 1)
				{
					mDespawnEnemyOnServer = methodInfo;
					break;
				}
			}
		}
		if (type5 != null)
		{
			fIsEnemyDead = type5.GetField("isEnemyDead", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
		}
		if (type3 != null)
		{
			pHudInstance = type3.GetProperty("Instance", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			MethodInfo[] methods2 = type3.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (MethodInfo methodInfo2 in methods2)
			{
				if (methodInfo2.Name == "DisplayTip")
				{
					mDisplayTip = methodInfo2;
					break;
				}
			}
		}
		bool flag = pTimeOfDayInstance != null && fCurrentDayTime != null && fTotalTime != null && pStartOfRoundInstance != null && fShipHasLanded != null && fShipIsLeaving != null && fCurrentLevel != null;
		if (flag)
		{
			if (fGlobalTime == null || mCalculatePlanetTime == null)
			{
				Log.LogWarning((object)"Global clock not found; day loop may not stick.");
			}
			if (mDespawnEnemyOnServer == null || fSpawnedEnemies == null)
			{
				Log.LogWarning((object)"Enemy despawn code not found; monsters will not reset (day loop still works).");
			}
			if (mDisplayTip == null)
			{
				Log.LogWarning((object)"HUD popup not found; no new-day message.");
			}
		}
		return flag;
	}

	private static float LoopPoint(object tod)
	{
		int num = 18;
		if (fNumberOfHours != null)
		{
			int num2 = (int)fNumberOfHours.GetValue(tod);
			if (num2 > 0 && num2 <= 24)
			{
				num = num2;
			}
		}
		float num3 = 24f - (float)num;
		float num4 = (22f - num3) / (float)num;
		if (num4 < 0.3f)
		{
			num4 = 0.3f;
		}
		if (num4 > 0.98f)
		{
			num4 = 0.98f;
		}
		return num4;
	}

	private static float MeasureSlope(object tod, object level)
	{
		if (fGlobalTime == null || mCalculatePlanetTime == null || level == null)
		{
			return -1f;
		}
		float num = (float)fGlobalTime.GetValue(tod);
		float num2 = (float)mCalculatePlanetTime.Invoke(tod, new object[1] { level });
		fGlobalTime.SetValue(tod, num + 10f);
		float num3 = (float)mCalculatePlanetTime.Invoke(tod, new object[1] { level });
		fGlobalTime.SetValue(tod, num);
		float num4 = (num3 - num2) / 10f;
		return (!(num4 > 0.0001f)) ? (-1f) : num4;
	}

	private static bool OnTimedMoon(object sor)
	{
		if (!(bool)fShipHasLanded.GetValue(sor))
		{
			return false;
		}
		if ((bool)fShipIsLeaving.GetValue(sor))
		{
			return false;
		}
		if (fInShipPhase != null && (bool)fInShipPhase.GetValue(sor))
		{
			return false;
		}
		object value = fCurrentLevel.GetValue(sor);
		if (value == null)
		{
			return false;
		}
		if (fPlanetHasTime != null && !(bool)fPlanetHasTime.GetValue(value))
		{
			return false;
		}
		return true;
	}

	private static void TimeOfDayUpdatePostfix(object __instance)
	{
		if (!cfgEnabled.Value || __instance == null)
		{
			return;
		}
		try
		{
			object value = pStartOfRoundInstance.GetValue(null, null);
			if (value == null)
			{
				return;
			}
			if (!OnTimedMoon(value))
			{
				if (!(bool)fShipHasLanded.GetValue(value))
				{
					wasOnMoon = false;
					loopCount = 0;
					morningTime = -1f;
					verifyPending = false;
					failedLoops = 0;
					pausedThisMoon = false;
				}
				return;
			}
			float num = (float)fCurrentDayTime.GetValue(__instance);
			float num2 = (float)fTotalTime.GetValue(__instance);
			if (num2 <= 0f)
			{
				return;
			}
			if (!wasOnMoon)
			{
				wasOnMoon = true;
				morningTime = num;
				loopCount = 0;
				cycleStartUtc = DateTime.UtcNow;
			}
			double totalSeconds = (DateTime.UtcNow - lastLoopUtc).TotalSeconds;
			bool flag = num / num2 >= LoopPoint(__instance);
			if (verifyPending)
			{
				verifyPending = false;
				if (flag)
				{
					failedLoops++;
					if (failedLoops >= 2)
					{
						pausedThisMoon = true;
						Log.LogError((object)"Clock rewind did not stick; EndlessDays paused until you leave this moon.");
					}
				}
				else
				{
					failedLoops = 0;
				}
			}
			if (!pausedThisMoon && flag && totalSeconds > 5.0)
			{
				LoopDay(__instance, num2);
			}
		}
		catch (Exception ex)
		{
			if (!loggedUpdateError)
			{
				loggedUpdateError = true;
				Log.LogError((object)("Day loop error: " + ex));
			}
		}
	}

	private static bool ShipLeaveAutomaticallyPrefix(bool leavingOnMidnight)
	{
		if (!cfgEnabled.Value || !leavingOnMidnight)
		{
			return true;
		}
		try
		{
			object value = pStartOfRoundInstance.GetValue(null, null);
			object value2 = pTimeOfDayInstance.GetValue(null, null);
			if (value == null || value2 == null || !OnTimedMoon(value))
			{
				return true;
			}
			if (fLivingPlayers != null && (int)fLivingPlayers.GetValue(value) <= 0)
			{
				return true;
			}
			Log.LogInfo((object)"Blocked midnight auto-leave; looping the day instead.");
			LoopDay(value2, (float)fTotalTime.GetValue(value2));
			return false;
		}
		catch (Exception ex)
		{
			Log.LogError((object)("Midnight safety net error, letting the ship leave: " + ex));
			return true;
		}
	}

	private static void LoopDay(object tod, float total)
	{
		float num = morningTime;
		if (num < 0f || num >= total * LoopPoint(tod))
		{
			num = total * 0.1f;
		}
		bool flag = false;
		try
		{
			object value = pStartOfRoundInstance.GetValue(null, null);
			object obj = ((value == null) ? null : fCurrentLevel.GetValue(value));
			float num2 = MeasureSlope(tod, obj);
			if (num2 > 0f)
			{
				float num3 = (float)fGlobalTime.GetValue(tod);
				float num4 = (float)mCalculatePlanetTime.Invoke(tod, new object[1] { obj });
				fGlobalTime.SetValue(tod, num3 - (num4 - num) / num2);
				flag = true;
			}
		}
		catch (Exception ex)
		{
			Log.LogWarning((object)("Global clock rewind failed: " + ex.Message));
		}
		if (!flag)
		{
			Log.LogWarning((object)"Could not rewind the global clock this loop.");
		}
		lastLoopUtc = DateTime.UtcNow;
		verifyPending = true;
		fCurrentDayTime.SetValue(tod, num);
		if (fNormalizedTime != null)
		{
			fNormalizedTime.SetValue(tod, num / total);
		}
		if (fHour != null && fNumberOfHours != null)
		{
			int num5 = (int)fNumberOfHours.GetValue(tod);
			if (num5 > 0)
			{
				fHour.SetValue(tod, (int)(num / (total / (float)num5)));
			}
		}
		loopCount++;
		double totalMinutes = (DateTime.UtcNow - cycleStartUtc).TotalMinutes;
		Log.LogInfo((object)("New morning (loop " + loopCount + "). Last cycle took " + totalMinutes.ToString("0.0") + " real min."));
		cycleStartUtc = DateTime.UtcNow;
		if (cfgResetMonsters.Value)
		{
			ResetMonsters();
		}
		if (cfgShowMessage.Value)
		{
			ShowNewDayMessage();
		}
	}

	private static void ResetMonsters()
	{
		try
		{
			if (pRoundManagerInstance == null)
			{
				return;
			}
			object value = pRoundManagerInstance.GetValue(null, null);
			if (value == null || !(pRoundManagerIsServer != null) || !(bool)pRoundManagerIsServer.GetValue(value, null))
			{
				return;
			}
			int num = 0;
			if (mDespawnEnemyOnServer != null && fSpawnedEnemies != null && fSpawnedEnemies.GetValue(value) is IList list)
			{
				object[] array = new object[list.Count];
				list.CopyTo(array, 0);
				object[] array2 = array;
				foreach (object obj in array2)
				{
					try
					{
						if (obj != null && (!(fIsEnemyDead != null) || !(bool)fIsEnemyDead.GetValue(obj)))
						{
							PropertyInfo property = obj.GetType().GetProperty("NetworkObject", BindingFlags.Instance | BindingFlags.Public);
							object obj2 = ((!(property != null)) ? null : property.GetValue(obj, null));
							if (obj2 != null)
							{
								mDespawnEnemyOnServer.Invoke(value, new object[1] { obj2 });
								num++;
							}
						}
					}
					catch (Exception ex)
					{
						Log.LogDebug((object)("Skip enemy despawn: " + ex.Message));
					}
				}
			}
			FieldInfo[] array3 = fEnemyPowerFields;
			foreach (FieldInfo fieldInfo in array3)
			{
				if (fieldInfo != null && fieldInfo.FieldType == typeof(float))
				{
					fieldInfo.SetValue(value, 0f);
				}
			}
			if (fRmCurrentHour != null && fRmCurrentHour.FieldType == typeof(int))
			{
				fRmCurrentHour.SetValue(value, 0);
			}
			if (fCannotSpawnMoreInside != null && fCannotSpawnMoreInside.FieldType == typeof(bool))
			{
				fCannotSpawnMoreInside.SetValue(value, false);
			}
			if (mResetEnemyTypesSpawnedCounts != null)
			{
				mResetEnemyTypesSpawnedCounts.Invoke(value, null);
			}
			Log.LogInfo((object)("Monsters reset: " + num + " despawned."));
		}
		catch (Exception ex2)
		{
			Log.LogError((object)("Monster reset failed: " + ex2));
		}
	}

	private static void ShowNewDayMessage()
	{
		try
		{
			if (pHudInstance == null || mDisplayTip == null)
			{
				return;
			}
			object value = pHudInstance.GetValue(null, null);
			if (value != null)
			{
				ParameterInfo[] parameters = mDisplayTip.GetParameters();
				object[] array = new object[parameters.Length];
				for (int i = 0; i < parameters.Length; i++)
				{
					array[i] = ((!parameters[i].HasDefaultValue) ? ((!parameters[i].ParameterType.IsValueType) ? null : Activator.CreateInstance(parameters[i].ParameterType)) : parameters[i].DefaultValue);
				}
				if (parameters.Length > 0)
				{
					array[0] = "A NEW DAY";
				}
				if (parameters.Length > 1)
				{
					array[1] = "Day " + (loopCount + 1) + " on this moon. The ship waits until you pull the lever.";
				}
				mDisplayTip.Invoke(value, array);
			}
		}
		catch (Exception ex)
		{
			Log.LogDebug((object)("New day message failed: " + ex.Message));
		}
	}
}