Decompiled source of SimPassiveLevelingOverhaul v1.1.0

SimPassiveLevelingOverhaul/SimPassiveLevelingOverhaul.dll

Decompiled 13 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("SimPassiveLevelingFix")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("SimPassiveLevelingFix")]
[assembly: AssemblyCopyright("Copyright ©  2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("7e45fa90-e15b-4e26-9cd2-f5027db62a16")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("1.1.0.0")]
namespace Erenshor.SimXPOverTime;

[BepInPlugin("erenshor.simxpovertime", "Sim XP Over Time", "1.1.0")]
public class SimXPOverTimePlugin : BaseUnityPlugin
{
	public static Dictionary<string, SimPlayerSaveData> _cache = new Dictionary<string, SimPlayerSaveData>();

	public static bool _cacheBuilt;

	public static Dictionary<string, (int level, int curXp)> _preLoadSnapshot = new Dictionary<string, (int, int)>();

	public static Queue<SimPlayerSaveData> _saveQueue = new Queue<SimPlayerSaveData>();

	private ConfigEntry<float> _cfgTickInterval;

	private ConfigEntry<float> _cfgVarianceMin;

	private ConfigEntry<float> _cfgVarianceMax;

	private ConfigEntry<bool> _cfgUseFixedSeed;

	private ConfigEntry<int> _cfgFixedSeedValue;

	private ConfigEntry<bool> _cfgSeedIncrementOnZone;

	private ConfigEntry<float> _cfgRivalSpeedMult;

	private ConfigEntry<float> _cfgSlotCatchupPerLevel;

	private ConfigEntry<float> _cfgSlotCatchupMax;

	private ConfigEntry<int> _cfgSlotLevelCapOffset;

	private ConfigEntry<int> _cfgMaxLevel;

	private ConfigEntry<bool> _cfgDebugLogging;

	public static float _tickIntervalSecs;

	public static float _varianceMin;

	public static float _varianceMax;

	public static bool _useFixedSeed;

	public static int _fixedSeedValue;

	public static bool _seedIncrementOnZone;

	public static float _rivalSpeedMultiplier;

	public static float _slotCatchupPerLevel;

	public static float _slotCatchupMax;

	public static int _slotLevelCapOffset;

	public static int _maxLevel;

	public static bool _debugLog;

	public static float _timer;

	public static bool _tickNow;

	public static int _zoneSeed;

	public const int FNV_BASIS = -2128831035;

	public const int FNV_PRIME = 16777619;

	public static void DrainOneSave()
	{
		if (_saveQueue.Count > 0)
		{
			SimPlayerSaveData val = _saveQueue.Dequeue();
			try
			{
				SimPlayerDataManager.SaveSimData(val);
			}
			catch
			{
			}
		}
	}

	public static void CheckCache(SimPlayerMngr mgr)
	{
		if (mgr.LoadedSimplayers && !_cacheBuilt)
		{
			RebuildCache(mgr);
		}
		if (!mgr.LoadedSimplayers)
		{
			_cacheBuilt = false;
		}
	}

	public static void RebuildCache(SimPlayerMngr mgr)
	{
		if (_debugLog)
		{
			Debug.Log((object)("[SimXPOverTime] RebuildCache: starting I/O for " + mgr.Sims.Count + " sims..."));
		}
		float realtimeSinceStartup = Time.realtimeSinceStartup;
		_cache.Clear();
		_saveQueue.Clear();
		int num = 0;
		int num2 = 0;
		int num3 = 0;
		foreach (SimPlayerTracking sim in mgr.Sims)
		{
			if (sim == null || sim.Level >= _maxLevel)
			{
				continue;
			}
			string text = Path.Combine(Application.persistentDataPath, "ESSaveData", "Sims" + sim.SimName);
			if (!File.Exists(text))
			{
				continue;
			}
			try
			{
				SimPlayerSaveData val = SimPlayerDataManager.LoadFromFile(text);
				if (val == null)
				{
					continue;
				}
				if (_preLoadSnapshot.TryGetValue(sim.SimName, out (int, int) value) && val.MyLevel > value.Item1)
				{
					if (_debugLog)
					{
						Debug.Log((object)("[SimXPOverTime] Reverting catchup: " + sim.SimName + " L" + value.Item1 + "→" + val.MyLevel + " (caught up) → L" + value.Item1));
					}
					val.MyLevel = value.Item1;
					val.XpForLevelUp = value.Item2;
					sim.CurXp = value.Item2;
					(sim.Level, _) = value;
					SimPlayerDataManager.SaveSimData(val);
					num3++;
				}
				_cache[sim.SimName] = val;
				num++;
				if (val.XpForLevelUp > 0)
				{
					sim.CurXp = val.XpForLevelUp;
					num2++;
				}
			}
			catch
			{
			}
		}
		_cacheBuilt = true;
		float num4 = (Time.realtimeSinceStartup - realtimeSinceStartup) * 1000f;
		if (_debugLog)
		{
			Debug.Log((object)("[SimXPOverTime] RebuildCache: done in " + num4.ToString("F1") + "ms. " + num + " sims cached, " + num2 + " XP restored, " + num3 + " catchup reverted."));
		}
	}

	private void Awake()
	{
		_cfgTickInterval = ((BaseUnityPlugin)this).Config.Bind<float>("General", "TickIntervalSeconds", 120f, "How often (in seconds) passive XP is granted to sims. Lower = more frequent but smaller XP chunks. Does NOT change overall leveling speed.");
		_cfgMaxLevel = ((BaseUnityPlugin)this).Config.Bind<int>("General", "MaxLevel", 35, "Level cap. Sims at this level stop receiving passive XP. Keep at 35, or below if desired. Higher values will result in Sims receiving too many ascension points (base game handles this.)");
		_cfgDebugLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "DebugLogging", false, "If true, logs every XP tick for every sim (spammy!). Use only for troubleshooting.");
		_cfgVarianceMin = ((BaseUnityPlugin)this).Config.Bind<float>("Variance", "VarianceMin", 0.5f, "Slowest multiplier for per-sim XP variance. 0.5 = half speed.");
		_cfgVarianceMax = ((BaseUnityPlugin)this).Config.Bind<float>("Variance", "VarianceMax", 1.5f, "Fastest multiplier for per-sim XP variance. 1.5 = 50% faster.");
		_cfgUseFixedSeed = ((BaseUnityPlugin)this).Config.Bind<bool>("Variance", "UseFixedSeed", false, "If true, uses a fixed seed for per-sim variance. If setting to false, its recommended to set SeedIncrementOnZoneChange to false as well so sims level at their same pace every session.");
		_cfgFixedSeedValue = ((BaseUnityPlugin)this).Config.Bind<int>("Variance", "FixedSeedValue", 12345, "The seed value used when UseFixedSeed is true.");
		_cfgSeedIncrementOnZone = ((BaseUnityPlugin)this).Config.Bind<bool>("Variance", "SeedIncrementOnZoneChange", true, "If true, re-rolls per-sim variance when you change zones. Set to false for stable variance throughout a session.");
		_cfgRivalSpeedMult = ((BaseUnityPlugin)this).Config.Bind<float>("Rivals", "RivalSpeedMultiplier", 1.25f, "How much faster rivals level compared to generic sims. 1.0 = same speed, 1.5 = 50% faster.");
		_cfgSlotCatchupPerLevel = ((BaseUnityPlugin)this).Config.Bind<float>("SlotTied", "SlotCatchupPerLevel", 0.1f, "Catchup boost per level behind the player (for current-slot sims). 0.10 = 10% boost per level. Set to 0 to disable.");
		_cfgSlotCatchupMax = ((BaseUnityPlugin)this).Config.Bind<float>("SlotTied", "SlotCatchupMax", 3f, "Maximum total catchup multiplier for slot-tied sims.");
		_cfgSlotLevelCapOffset = ((BaseUnityPlugin)this).Config.Bind<int>("SlotTied", "SlotLevelCapOffset", 2, "How many levels above the player a slot-tied sim can passively reach. Set to 35 to effectively disable this cap.");
		_tickIntervalSecs = _cfgTickInterval.Value;
		_varianceMin = _cfgVarianceMin.Value;
		_varianceMax = _cfgVarianceMax.Value;
		_useFixedSeed = _cfgUseFixedSeed.Value;
		_fixedSeedValue = _cfgFixedSeedValue.Value;
		_seedIncrementOnZone = _cfgSeedIncrementOnZone.Value;
		_rivalSpeedMultiplier = _cfgRivalSpeedMult.Value;
		_slotCatchupPerLevel = _cfgSlotCatchupPerLevel.Value;
		_slotCatchupMax = _cfgSlotCatchupMax.Value;
		_slotLevelCapOffset = _cfgSlotLevelCapOffset.Value;
		_maxLevel = _cfgMaxLevel.Value;
		_debugLog = _cfgDebugLogging.Value;
		_zoneSeed = (_useFixedSeed ? _fixedSeedValue : Random.Range(1, int.MaxValue));
		Harmony.CreateAndPatchAll(typeof(SimXPOverTimePlugin), (string)null);
		((BaseUnityPlugin)this).Logger.LogInfo((object)("SimXPOverTime v1.1 initialized. Tick: " + _tickIntervalSecs + "s | Variance: " + _varianceMin + "x–" + _varianceMax + "x | Seed: " + (_useFixedSeed ? ("fixed=" + _fixedSeedValue) : "random") + " | Zone re-roll: " + (_seedIncrementOnZone ? "ON" : "OFF") + " | Rivals: " + _rivalSpeedMultiplier + "x | Slot catchup: " + _slotCatchupPerLevel * 100f + "%/lvl, max " + _slotCatchupMax + "x | Slot cap: player+" + _slotLevelCapOffset + " | Debug: " + (_debugLog ? "ON" : "OFF")));
	}

	[HarmonyPatch(typeof(SimPlayerMngr), "LoadSimPlayersIntoGame")]
	[HarmonyPrefix]
	public static void LoadSimPlayersIntoGame_Prefix()
	{
		_preLoadSnapshot.Clear();
		string path = Path.Combine(Application.persistentDataPath, "ESSaveData");
		if (!Directory.Exists(path))
		{
			return;
		}
		string[] files = Directory.GetFiles(path, "Sims*");
		string[] array = files;
		foreach (string text in array)
		{
			try
			{
				SimPlayerSaveData val = SimPlayerDataManager.LoadFromFile(text);
				if (val != null && !string.IsNullOrEmpty(val.NPCName))
				{
					_preLoadSnapshot[val.NPCName] = (val.MyLevel, val.XpForLevelUp);
				}
			}
			catch
			{
			}
		}
		if (_debugLog)
		{
			Debug.Log((object)("[SimXPOverTime] Snapshot: " + _preLoadSnapshot.Count + " sims recorded before load."));
		}
	}

	[HarmonyPatch(typeof(SimPlayer), "SaveSim")]
	[HarmonyPrefix]
	public static void SaveSim_Prefix(SimPlayer __instance)
	{
		if (!((Object)(object)__instance == (Object)null))
		{
			SimPlayerTracking mySimTracking = __instance.MySimTracking;
			if (mySimTracking != null && _cache.TryGetValue(mySimTracking.SimName, out var value))
			{
				value.XpForLevelUp = mySimTracking.CurXp;
				value.MyLevel = mySimTracking.Level;
				_saveQueue.Enqueue(value);
			}
		}
	}

	[HarmonyPatch(typeof(SimPlayerMngr), "CollectActiveSimData")]
	[HarmonyPrefix]
	public static void CollectActiveSimData_Prefix(SimPlayerMngr __instance)
	{
		if (!_cacheBuilt)
		{
			return;
		}
		if (_seedIncrementOnZone)
		{
			_zoneSeed++;
		}
		int num = 0;
		foreach (SimPlayerTracking sim in __instance.Sims)
		{
			if (sim != null && sim.Level < _maxLevel && _cache.TryGetValue(sim.SimName, out var value))
			{
				value.XpForLevelUp = sim.CurXp;
				value.MyLevel = sim.Level;
				_saveQueue.Enqueue(value);
				num++;
			}
		}
		if (_debugLog)
		{
			Debug.Log((object)("[SimXPOverTime] Zone change: synced " + num + " sims, seed=" + _zoneSeed + ", queued=" + _saveQueue.Count));
		}
	}

	[HarmonyPatch(typeof(SimPlayerDataManager), "SaveAllSimData")]
	[HarmonyPrefix]
	public static void SaveAllSimData_Prefix()
	{
		if (!_cacheBuilt)
		{
			return;
		}
		SimPlayerMngr simMngr = GameData.SimMngr;
		if ((Object)(object)simMngr == (Object)null)
		{
			return;
		}
		foreach (SimPlayerTracking sim in simMngr.Sims)
		{
			if (sim != null && sim.Level < _maxLevel && _cache.TryGetValue(sim.SimName, out var value))
			{
				value.XpForLevelUp = sim.CurXp;
				value.MyLevel = sim.Level;
			}
		}
		if (_debugLog)
		{
			Debug.Log((object)"[SimXPOverTime] SaveAllSimData: synced all sim XP to cache.");
		}
	}

	[HarmonyPatch(typeof(SimPlayerMngr), "SimPlayerCatchupCode")]
	[HarmonyPrefix]
	public static bool CatchupCode_Prefix()
	{
		return false;
	}

	[HarmonyPatch(typeof(SimPlayerMngr), "Update")]
	[HarmonyTranspiler]
	public static IEnumerable<CodeInstruction> Update_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator gen)
	{
		//IL_010c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0116: Expected O, but got Unknown
		//IL_0132: Unknown result type (might be due to invalid IL or missing references)
		//IL_013c: Expected O, but got Unknown
		//IL_015a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0164: Expected O, but got Unknown
		//IL_016d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0177: Expected O, but got Unknown
		//IL_0195: Unknown result type (might be due to invalid IL or missing references)
		//IL_019f: Expected O, but got Unknown
		//IL_01bb: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c5: Expected O, but got Unknown
		//IL_01e6: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f0: Expected O, but got Unknown
		//IL_01f9: Unknown result type (might be due to invalid IL or missing references)
		//IL_0203: Expected O, but got Unknown
		//IL_021f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0229: Expected O, but got Unknown
		//IL_0245: Unknown result type (might be due to invalid IL or missing references)
		//IL_024f: Expected O, but got Unknown
		//IL_026b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0275: Expected O, but got Unknown
		//IL_0284: Unknown result type (might be due to invalid IL or missing references)
		//IL_028e: Expected O, but got Unknown
		//IL_02aa: Unknown result type (might be due to invalid IL or missing references)
		//IL_02b4: Expected O, but got Unknown
		//IL_02d0: Unknown result type (might be due to invalid IL or missing references)
		//IL_02da: Expected O, but got Unknown
		//IL_02e3: Unknown result type (might be due to invalid IL or missing references)
		//IL_02ed: Expected O, but got Unknown
		//IL_0309: Unknown result type (might be due to invalid IL or missing references)
		//IL_0313: Expected O, but got Unknown
		//IL_031c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0326: Expected O, but got Unknown
		//IL_0342: Unknown result type (might be due to invalid IL or missing references)
		//IL_034c: Expected O, but got Unknown
		//IL_040c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0416: Expected O, but got Unknown
		List<CodeInstruction> list = instructions.ToList();
		bool flag = false;
		bool flag2 = false;
		for (int i = 0; i < list.Count; i++)
		{
			if (!flag && i + 2 < list.Count && list[i].opcode == OpCodes.Ldarg_0 && list[i + 1].opcode == OpCodes.Ldfld && list[i + 1].operand is FieldInfo { Name: "Sims" } fieldInfo && fieldInfo.DeclaringType == typeof(SimPlayerMngr) && list[i + 2].opcode == OpCodes.Callvirt && list[i + 2].operand is MethodInfo { Name: "GetEnumerator" })
			{
				Label label = gen.DefineLabel();
				List<CodeInstruction> list2 = new List<CodeInstruction>();
				list2.Add(new CodeInstruction(OpCodes.Ldc_I4_0, (object)null));
				list2.Add(new CodeInstruction(OpCodes.Stsfld, (object)AccessTools.Field(typeof(SimXPOverTimePlugin), "_tickNow")));
				list2.Add(new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(SimXPOverTimePlugin), "DrainOneSave", (Type[])null, (Type[])null)));
				list2.Add(new CodeInstruction(OpCodes.Dup, (object)null));
				list2.Add(new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(SimXPOverTimePlugin), "CheckCache", (Type[])null, (Type[])null)));
				list2.Add(new CodeInstruction(OpCodes.Ldsfld, (object)AccessTools.Field(typeof(SimXPOverTimePlugin), "_timer")));
				list2.Add(new CodeInstruction(OpCodes.Call, (object)AccessTools.Property(typeof(Time), "deltaTime").GetGetMethod()));
				list2.Add(new CodeInstruction(OpCodes.Add, (object)null));
				list2.Add(new CodeInstruction(OpCodes.Stsfld, (object)AccessTools.Field(typeof(SimXPOverTimePlugin), "_timer")));
				list2.Add(new CodeInstruction(OpCodes.Ldsfld, (object)AccessTools.Field(typeof(SimXPOverTimePlugin), "_timer")));
				list2.Add(new CodeInstruction(OpCodes.Ldsfld, (object)AccessTools.Field(typeof(SimXPOverTimePlugin), "_tickIntervalSecs")));
				list2.Add(new CodeInstruction(OpCodes.Blt, (object)label));
				list2.Add(new CodeInstruction(OpCodes.Ldsfld, (object)AccessTools.Field(typeof(SimXPOverTimePlugin), "_timer")));
				list2.Add(new CodeInstruction(OpCodes.Ldsfld, (object)AccessTools.Field(typeof(SimXPOverTimePlugin), "_tickIntervalSecs")));
				list2.Add(new CodeInstruction(OpCodes.Sub, (object)null));
				list2.Add(new CodeInstruction(OpCodes.Stsfld, (object)AccessTools.Field(typeof(SimXPOverTimePlugin), "_timer")));
				list2.Add(new CodeInstruction(OpCodes.Ldc_I4_1, (object)null));
				list2.Add(new CodeInstruction(OpCodes.Stsfld, (object)AccessTools.Field(typeof(SimXPOverTimePlugin), "_tickNow")));
				list.InsertRange(i + 1, list2);
				list[i + 1 + list2.Count].labels.Add(label);
				flag = true;
				i += list2.Count + 1;
			}
			else if (!flag2 && list[i].opcode == OpCodes.Stfld && list[i].operand is FieldInfo { Name: "TimeInZone" } fieldInfo2 && fieldInfo2.DeclaringType == typeof(SimPlayerTracking))
			{
				list[i] = new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(SimXPOverTimePlugin), "SetTimeInZoneAndProcessXp", (Type[])null, (Type[])null));
				flag2 = true;
				if (_debugLog)
				{
					Debug.Log((object)"[SimXPOverTime] Transpiler: replaced stfld TimeInZone.");
				}
			}
		}
		if (!flag)
		{
			Debug.LogError((object)"[SimXPOverTime] FAILED: foreach entry not found!");
		}
		if (!flag2)
		{
			Debug.LogError((object)"[SimXPOverTime] FAILED: stfld TimeInZone not found!");
		}
		return list;
	}

	public static int XpForLevel(int level)
	{
		int num = level * 10 * (level + level);
		if (level > 9 && level < 20)
		{
			num = Mathf.RoundToInt((float)num * 1.3f);
		}
		if (level >= 20 && level < 30)
		{
			num = Mathf.RoundToInt((float)num * 1.6f);
		}
		if (level >= 30)
		{
			num *= 2;
		}
		return num;
	}

	private static float GetHoursForLevel(int level)
	{
		return level switch
		{
			1 => 0.2f, 
			2 => 0.3f, 
			3 => 0.3f, 
			4 => 0.3f, 
			5 => 0.3f, 
			6 => 0.5f, 
			7 => 0.7f, 
			8 => 0.9f, 
			9 => 1.1f, 
			10 => 1.3f, 
			11 => 1f, 
			12 => 1.2f, 
			13 => 1.3f, 
			14 => 1.5f, 
			15 => 1.6f, 
			16 => 1.8f, 
			17 => 2f, 
			18 => 2.2f, 
			19 => 2.5f, 
			20 => 3.5f, 
			21 => 3.8f, 
			22 => 4.2f, 
			23 => 4.5f, 
			24 => 4.8f, 
			25 => 5f, 
			26 => 5.2f, 
			27 => 5.5f, 
			28 => 5.8f, 
			29 => 6f, 
			30 => 6.5f, 
			31 => 6.2f, 
			32 => 6.5f, 
			33 => 6.8f, 
			34 => 7f, 
			_ => 7.5f, 
		};
	}

	private static float GetSimVariance(string simName)
	{
		int num = -2128831035;
		foreach (char c in simName)
		{
			num = (num ^ c) * 16777619;
		}
		num ^= _zoneSeed;
		float num2 = (float)(num & 0x7FFFFFFF) / 2.1474836E+09f;
		return _varianceMin + num2 * (_varianceMax - _varianceMin);
	}

	public static void SetTimeInZoneAndProcessXp(SimPlayerTracking sim, float newValue)
	{
		sim.TimeInZone = newValue;
		if (!_tickNow || sim.Level >= _maxLevel || GameData.InCharSelect)
		{
			return;
		}
		for (int i = 0; i < GameData.GroupMembers.Length; i++)
		{
			if (GameData.GroupMembers[i] != null && GameData.GroupMembers[i].SimName == sim.SimName)
			{
				return;
			}
		}
		bool flag = sim.TiedToSlot >= 0 && sim.TiedToSlot <= 10;
		bool flag2 = flag && sim.TiedToSlot == GameData.CurrentCharacterSlot.index;
		bool flag3 = sim.TiedToSlot == 99;
		if ((flag && !flag2) || (flag2 && (Object)(object)GameData.PlayerStats != (Object)null && sim.Level >= GameData.PlayerStats.Level + _slotLevelCapOffset))
		{
			return;
		}
		int num = XpForLevel(sim.Level);
		float hoursForLevel = GetHoursForLevel(sim.Level);
		float num2 = 3600f / _tickIntervalSecs;
		int num3 = Mathf.RoundToInt(hoursForLevel * num2);
		if (num3 < 1)
		{
			num3 = 1;
		}
		int num4 = num / num3;
		if (num4 < 1)
		{
			num4 = 1;
		}
		float simVariance = GetSimVariance(sim.SimName);
		float num5 = 1f;
		float num6 = 1f;
		if (flag3)
		{
			num5 = _rivalSpeedMultiplier;
		}
		if (flag2 && (Object)(object)GameData.PlayerStats != (Object)null && sim.Level < GameData.PlayerStats.Level)
		{
			int num7 = GameData.PlayerStats.Level - sim.Level;
			num6 = 1f + Mathf.Min((float)num7 * _slotCatchupPerLevel, _slotCatchupMax - 1f);
		}
		float num8 = simVariance * num5 * num6;
		int num9 = Mathf.RoundToInt((float)num4 * num8);
		if (num9 < 1)
		{
			num9 = 1;
		}
		int curXp = sim.CurXp;
		int level = sim.Level;
		sim.CurXp += num9;
		while (sim.CurXp >= num && sim.Level < _maxLevel)
		{
			sim.CurXp -= num;
			sim.Level++;
			num = XpForLevel(sim.Level);
		}
		if ((Object)(object)sim.MyAvatar != (Object)null && (Object)(object)sim.MyStats != (Object)null)
		{
			sim.MyStats.CurrentExperience = sim.CurXp;
			if (sim.Level != level)
			{
				sim.MyStats.Level = sim.Level;
				sim.MyStats.SetXpForLevelUp();
				sim.MyStats.CalcStats();
			}
		}
		if (_debugLog)
		{
			string text = (flag3 ? "rival" : (flag2 ? "slot" : "generic"));
			string text2 = "";
			if (num5 != 1f)
			{
				text2 = text2 + " rival=" + num5.ToString("F2");
			}
			if (num6 != 1f)
			{
				text2 = text2 + " catchup=" + num6.ToString("F2");
			}
			Debug.Log((object)("[SimXPOverTime] TICK | " + sim.SimName + " L" + sim.Level + " | +" + num9 + " XP | " + sim.CurXp + "/" + num + " (" + sim.CurXp * 100 / num + "%) | var=" + simVariance.ToString("F2") + " | " + text + text2));
			if (sim.Level != level)
			{
				Debug.Log((object)("[SimXPOverTime] LEVELUP | " + sim.SimName + " L" + level + "→" + sim.Level + " | " + text + text2));
			}
		}
		if (_cache.TryGetValue(sim.SimName, out var value))
		{
			value.XpForLevelUp = sim.CurXp;
			value.MyLevel = sim.Level;
		}
		if (sim.Level != level && value != null)
		{
			_saveQueue.Enqueue(value);
		}
	}
}