using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using CharacterProgressionMod.Commands; using CharacterProgressionMod.Core; using CharacterProgressionMod.Patches; using CharacterProgressionMod.Skills; using CharacterProgressionMod.UI; using HarmonyLib; using Jotunn; using Jotunn.Configs; using Jotunn.Entities; using Jotunn.Managers; using Jotunn.Utils; using MonoMod.Utils; using Newtonsoft.Json; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("LevelingSystem")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("LevelingSystem")] [assembly: AssemblyCopyright("Copyright © 2023")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("4d2a4a69-cbcf-4527-b504-7c1d1d3b3696")] [assembly: AssemblyFileVersion("2.1.1")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("2.1.1.0")] [module: UnverifiableCode] namespace CharacterProgressionMod { public sealed class RewardExpOnDeath : MonoBehaviour { private Character _character; private XpTable _creatureExperienceTable; public static XpTable CreatureExperienceTable { get; set; } public static float NearbyPlayerXpRadius { get; set; } = 50f; private void Start() { if (((Component)this).TryGetComponent(ref _character)) { _creatureExperienceTable = CreatureExperienceTable; Character character = _character; character.m_onDeath = (Action)Delegate.Combine(character.m_onDeath, new Action(Character_OnDeath)); } } private void Character_OnDeath() { //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) Logger.LogDebug((object)$"Are we the owner of {((Object)_character).name}?: {_character.IsOwner()}"); Character character = _character; character.m_onDeath = (Action)Delegate.Remove(character.m_onDeath, new Action(Character_OnDeath)); if (!_character.IsOwner()) { return; } string key = ((Object)_character).name.Replace("(Clone)", "").Trim(); int num = (_creatureExperienceTable ?? CreatureExperienceTable)?.GetXp(key) ?? 0; if (num <= 0) { return; } Vector3 position = ((Component)_character).transform.position; PlayerLevelProgression playerLevelProgression = default(PlayerLevelProgression); foreach (Player allPlayer in Player.GetAllPlayers()) { if (!((Object)(object)allPlayer == (Object)null) && !((Character)allPlayer).IsDead() && !(Vector3.Distance(((Component)allPlayer).transform.position, position) > NearbyPlayerXpRadius) && ((Component)allPlayer).TryGetComponent(ref playerLevelProgression)) { playerLevelProgression.AddExperience(num); } } } } public class BiomeExperienceConfig { public class ExperienceCategory { public string Name { get; } public int BaseExp { get; set; } public float TierMultiplier { get; set; } public ExperienceCategory(string name, int baseExp, float tierMultiplier = 1f) { Name = name; BaseExp = baseExp; TierMultiplier = tierMultiplier; } } public ExperienceCategory Foraging { get; } = new ExperienceCategory("Foraging", 5); public ExperienceCategory Woodcutting { get; } = new ExperienceCategory("Woodcutting", 10, 1.2f); public ExperienceCategory Mining { get; } = new ExperienceCategory("Mining", 5, 1.2f); public ExperienceCategory CreatureKilling { get; } = new ExperienceCategory("CreatureKilling", 2, 2f); } public readonly struct LevelEvaluationResult { public int Level { get; } public int MaxExperience { get; } public int TotalExperience { get; } public int NextLevelTotalExperience { get; } public bool IsMaxLevel { get; } public LevelEvaluationResult(int level, int maxExperience, int totalExperience, int nextLevelTotalExperience, bool isMaxLevel) { Level = level; MaxExperience = maxExperience; TotalExperience = totalExperience; NextLevelTotalExperience = nextLevelTotalExperience; IsMaxLevel = isMaxLevel; } public float EvaluateProgressPercentage(int currentTotalExperience) { if (IsMaxLevel || MaxExperience <= 0) { return 100f; } return Mathf.Clamp((float)(currentTotalExperience - TotalExperience) / (float)MaxExperience * 100f, 0f, 100f); } public override string ToString() { return $"{Level} | {MaxExperience} | {TotalExperience} | {NextLevelTotalExperience} | {IsMaxLevel}"; } } public class LevelTableGenerationSettings { public int MaxLevel { get; } public int InitialMaxExperience { get; } public MaxExperienceModifierFormula MaxExperienceModifierFormula { get; } public LevelTableGenerationSettings(int maxLevel, int initialMaxExperience, string maxExperienceModifierFormula) { MaxLevel = maxLevel; InitialMaxExperience = initialMaxExperience; MaxExperienceModifierFormula = new MaxExperienceModifierFormula(maxExperienceModifierFormula); } } public class MaxExperienceModifierFormula { private struct Modifier { public int Level { get; } public int Value { get; } public bool IsValuePercentage { get; } public Modifier(int level, int value, bool isValuePercentage) { Level = level; Value = value; IsValuePercentage = isValuePercentage; } } private readonly Modifier[] _modifiers; public MaxExperienceModifierFormula(string formula) { List list = new List(5); string[] array = new string(formula.Where((char c) => !char.IsWhiteSpace(c)).ToArray()).Split(new char[1] { ';' }); for (int num = 0; num < array.Length; num++) { string[] array2 = array[num].Split(new char[1] { '=' }); if (array2.Length != 2) { continue; } string text = array2[0]; if (!IsInteger(text)) { continue; } int level = int.Parse(text, NumberStyles.Integer); string text2 = array2[1]; bool isValuePercentage = false; int value; if (IsPercentage(text2)) { text2 = text2.TrimEnd(new char[1] { '%' }); value = int.Parse(text2, NumberStyles.Integer); isValuePercentage = true; } else { if (!IsInteger(text2)) { if (!IsArray(text2)) { continue; } text2 = text2.Trim('[', ']'); string[] array3 = text2.Split(new char[1] { ',' }); foreach (string text3 in array3) { if (IsPercentage(text3) && int.TryParse(text3.TrimEnd(new char[1] { '%' }), out var result)) { list.Add(new Modifier(level, result, isValuePercentage: true)); } else if (IsInteger(text3)) { value = int.Parse(text3, NumberStyles.Integer); list.Add(new Modifier(level, value, isValuePercentage: false)); } } continue; } value = int.Parse(text2, NumberStyles.Integer); } list.Add(new Modifier(level, value, isValuePercentage)); static bool IsArray(string text4) { return text4.StartsWith("["); } static bool IsInteger(string source) { return source.All(char.IsDigit); } static bool IsPercentage(string text4) { return text4.EndsWith("%"); } } _modifiers = list.ToArray(); } public int Evaluate(int level, int oldMaxExperience) { Modifier lastValidKey = _modifiers.LastOrDefault((Modifier key) => level >= key.Level); IEnumerable enumerable = _modifiers.Where((Modifier key) => key.Level == lastValidKey.Level); int num = oldMaxExperience; foreach (Modifier item in enumerable) { num = ((!item.IsValuePercentage) ? (num + item.Value) : (num + Mathf.CeilToInt((float)oldMaxExperience * ((float)item.Value / 100f)))); } return num; } } public class LevelExperienceTable { private readonly int[] _entries; public int MaxLevel { get; } public LevelExperienceTable(int[] entries) { if (entries.Length == 0) { Logger.LogWarning((object)"No entries was given."); return; } _entries = entries; MaxLevel = _entries.Length + 1; Logger.LogDebug((object)$"Player level table has been created! Max level is {MaxLevel}."); } public LevelExperienceTable(LevelTableGenerationSettings generationSettings) { int num = generationSettings.MaxLevel - 1; _entries = new int[num]; int num2 = generationSettings.InitialMaxExperience; for (int i = 0; i < _entries.Length; i++) { _entries[i] = num2; int level = i + 2; num2 = generationSettings.MaxExperienceModifierFormula.Evaluate(level, num2); } MaxLevel = _entries.Length + 1; Logger.LogDebug((object)$"Player level table has been created! Max level is {MaxLevel}."); } public int GetMaxExperience(int level) { int num = level - 1; if (num < 0 || num >= _entries.Length) { Logger.LogError((object)$"Level {level} is out of range. Max level is {_entries.Length}."); return 1; } return _entries[num]; } public int GetTotalExperience(int level) { if (MaxLevel <= 1) { return 0; } int num = 0; for (int i = 0; i < _entries.Length && i + 1 < level; i++) { num += _entries[i]; } return num; } public LevelEvaluationResult EvaluateLevel(int totalExperience) { totalExperience = Math.Max(0, totalExperience); int num = 0; for (int i = 0; i < _entries.Length; i++) { int num2 = _entries[i]; num += num2; if (totalExperience < num) { int nextLevelTotalExperience = num; int totalExperience2 = num - num2; return new LevelEvaluationResult(i + 1, num2, totalExperience2, nextLevelTotalExperience, isMaxLevel: false); } } return new LevelEvaluationResult(MaxLevel, 0, num, num, isMaxLevel: true); } } public sealed class PlayerLevelProgression : MonoBehaviour { private const string TotalExpSaveKey = "Cozyheim!TotalExperience"; private const string LevelSaveKey = "Cozyheim!Level"; private static readonly int NetworkLevelKey = StringExtensionMethods.GetStableHashCode("LevelingSystem.Level"); private Player _player; private string _addExperienceRpcId; private string _setLevelRpcId; private LevelExperienceTable _levelExperienceTable; private LevelEvaluationResult _currentLevelEvaluation; public PluginConfig Config { get; set; } public LevelExperienceTable LevelExperienceTable { get { return _levelExperienceTable; } set { if (_levelExperienceTable != value) { _levelExperienceTable = value; if ((Object)(object)_player != (Object)null) { UpdateLevel(); } } } } public LevelEvaluationResult CurrentLevelEvaluation => _currentLevelEvaluation; public event Action ExperienceChanged; public event Action LevelChanged; private void Awake() { _addExperienceRpcId = RpcId.Generate("AddExperience"); _setLevelRpcId = RpcId.Generate("SetLevel"); _player = ((Component)this).GetComponent(); ZNetView nview = ((Character)_player).m_nview; if (((nview != null) ? nview.GetZDO() : null) != null) { ((Character)_player).m_nview.Register(_addExperienceRpcId, (Action)RPC_AddExperience); ((Character)_player).m_nview.Register(_setLevelRpcId, (Action)RPC_SetLevel); } } private void Start() { UpdateLevel(); } public void AddExperience(int expReward) { if (expReward <= 0 || (Object)(object)_player == (Object)null) { return; } ZNetView nview = ((Character)_player).m_nview; if (((nview != null) ? nview.GetZDO() : null) != null) { if (((Character)_player).IsOwner()) { RPC_AddExperience(0L, expReward); return; } ((Character)_player).m_nview.InvokeRPC(_addExperienceRpcId, new object[1] { expReward }); } } public void SetLevel(int level) { if (((Character)_player).IsOwner() && _levelExperienceTable != null) { level = Mathf.Clamp(level, 1, _levelExperienceTable.MaxLevel); SetTotalExperience(_levelExperienceTable.GetTotalExperience(level), 0); } } public void RequestSetLevel(int level) { if ((Object)(object)_player == (Object)null) { return; } ZNetView nview = ((Character)_player).m_nview; if (((nview != null) ? nview.GetZDO() : null) != null) { if (((Character)_player).IsOwner()) { SetLevel(level); return; } ((Character)_player).m_nview.InvokeRPC(_setLevelRpcId, new object[1] { level }); } } private void RPC_SetLevel(long sender, int level) { if (!((Character)_player).IsOwner()) { return; } if (sender != 0L && (Object)(object)ZNet.instance != (Object)null) { ZNetPeer serverPeer = ZNet.instance.GetServerPeer(); if (serverPeer == null || serverPeer.m_uid != sender) { Logger.LogWarning((object)$"Rejected a progression level change from peer {sender}."); return; } } SetLevel(level); } private void RPC_AddExperience(long sender, int expReward) { if (((Character)_player).IsOwner() && expReward > 0) { float num = 1f; if (((Character)_player).GetSEMan().HaveStatusEffect(SEMan.s_statusEffectRested)) { num *= Config?.RestedXpMultiplier.Value ?? 1.2f; } PlayerSkillProgression playerSkillProgression = default(PlayerSkillProgression); if (((Component)_player).TryGetComponent(ref playerSkillProgression)) { num *= playerSkillProgression.GetMultiplier(SkillId.ExperienceGain); } int num2 = Mathf.Max(1, Mathf.RoundToInt((float)expReward * num)); int totalExperience = GetTotalExperience() + num2; Logger.LogDebug((object)$"Added {num2:N0} experience (x{num:F2})"); SetTotalExperience(totalExperience, num2); } } private void SetTotalExperience(int totalExperience, int awardedExperience) { totalExperience = Mathf.Max(0, totalExperience); _player.m_customData["Cozyheim!TotalExperience"] = totalExperience.ToString(CultureInfo.InvariantCulture); UpdateLevel(); float num = _currentLevelEvaluation.EvaluateProgressPercentage(totalExperience); Logger.LogDebug((object)($"Level progress: {totalExperience - _currentLevelEvaluation.TotalExperience:N0} / " + $"{_currentLevelEvaluation.MaxExperience:N0} ({num:F0}%)")); this.ExperienceChanged?.Invoke(awardedExperience, totalExperience); } public int GetTotalExperience() { if (!_player.m_customData.TryGetValue("Cozyheim!TotalExperience", out var value) || !int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return 0; } return Mathf.Max(0, result); } public int GetLevel() { if ((Object)(object)_player != (Object)null && !((Character)_player).IsOwner()) { ZNetView nview = ((Character)_player).m_nview; int? obj; if (nview == null) { obj = null; } else { ZDO zDO = nview.GetZDO(); obj = ((zDO != null) ? new int?(zDO.GetInt(NetworkLevelKey, 1)) : ((int?)null)); } return Mathf.Max(1, obj ?? 1); } if (!_player.m_customData.TryGetValue("Cozyheim!Level", out var value) || !int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return 1; } return Mathf.Max(1, result); } public float GetProgress01() { return _currentLevelEvaluation.EvaluateProgressPercentage(GetTotalExperience()) / 100f; } private void UpdateLevel() { if (_levelExperienceTable == null || (Object)(object)_player == (Object)null) { return; } int level = GetLevel(); _currentLevelEvaluation = _levelExperienceTable.EvaluateLevel(GetTotalExperience()); int level2 = _currentLevelEvaluation.Level; _player.m_customData["Cozyheim!Level"] = level2.ToString(CultureInfo.InvariantCulture); if (((Character)_player).IsOwner()) { ZNetView nview = ((Character)_player).m_nview; if (nview != null) { ZDO zDO = nview.GetZDO(); if (zDO != null) { zDO.Set(NetworkLevelKey, level2, false); } } } if (level != level2) { this.LevelChanged?.Invoke(level, level2); } Logger.LogDebug((object)_currentLevelEvaluation.ToString()); } } public sealed class XpTable { private const string EmbeddedConfigPath = "CharacterProgressionMod.Resources.default_configs"; private readonly Dictionary _entries = new Dictionary(); private readonly Dictionary _groups = new Dictionary(); public string NameId { get; } public bool AllowGroups { get; } public string CustomConfigFolderPath { get; } public string CustomGroupsFolderPath { get; } public XpTable(Assembly resourceAssembly, string customFolderPath, bool allowGroups) { if (string.IsNullOrEmpty(customFolderPath)) { Logger.LogError((object)"A critical error occurred during the initialization of the leveling system. Please report this to the mod author."); throw new ArgumentException("The customFolderPath parameter cannot be null or empty."); } NameId = Path.GetFileName(customFolderPath).ToLowerInvariant(); AllowGroups = allowGroups; CustomConfigFolderPath = customFolderPath; CustomGroupsFolderPath = Path.Combine(customFolderPath, "groups"); VerifyAndSetupConfigDirectory(); if ((object)resourceAssembly == null) { Logger.LogError((object)"A critical error occurred during the initialization of the leveling system. Please report this to the mod author."); throw new ArgumentNullException("The resourceAssembly parameter cannot be null."); } LoadEmbeddedResources(resourceAssembly); LoadCustomResources(); Logger.LogDebug((object)$"Loaded {NameId} xp table with {_entries.Count} entries."); if (AllowGroups) { Logger.LogDebug((object)$"Loaded {NameId} groups with {_groups.Count} entries."); } } private void VerifyAndSetupConfigDirectory() { bool num = Directory.Exists(CustomConfigFolderPath); bool flag = Directory.Exists(CustomGroupsFolderPath); if (!(num && flag)) { Logger.LogDebug((object)("Creating directories for custom " + NameId + " configs.")); Directory.CreateDirectory(CustomConfigFolderPath); Directory.CreateDirectory(CustomGroupsFolderPath); } } private void LoadEmbeddedResources(Assembly resourceAssembly) { string[] manifestResourceNames = resourceAssembly.GetManifestResourceNames(); foreach (string text in manifestResourceNames) { string pattern = "^CharacterProgressionMod.Resources.default_configs\\." + NameId + "\\.xp_tables\\.(?:[\\w-]+).json"; if (Regex.IsMatch(text, pattern)) { Dictionary dictionary = JsonConvert.DeserializeObject>(AssetUtils.LoadTextFromResources(text, resourceAssembly)); if (dictionary.Count == 0) { Logger.LogError((object)("Skipped loading embedded " + NameId + " xp table file at '" + text + "' - no entries found. Please report this to the mod author.")); } else { Extensions.AddRange(_entries, dictionary); } } else { if (!AllowGroups) { continue; } string pattern2 = "^CharacterProgressionMod.Resources.default_configs\\." + NameId + "\\.groups\\.(?:[\\w-]+).json"; if (!Regex.IsMatch(text, pattern2)) { continue; } Dictionary dictionary2 = JsonConvert.DeserializeObject>(AssetUtils.LoadTextFromResources(text, resourceAssembly)); if (dictionary2.Count == 0) { Logger.LogError((object)("Skipped loading embedded " + NameId + " group file at '" + text + "' - no entries found. Please report this to the mod author.")); continue; } CollectionExtensions.Do>((IEnumerable>)dictionary2, (Action>)delegate(KeyValuePair pair) { CollectionExtensions.Do((IEnumerable)pair.Value, (Action)delegate(string groupEntry) { _groups[groupEntry] = pair.Key; }); }); } } } private void LoadCustomResources() { if (!Directory.Exists(CustomConfigFolderPath)) { VerifyAndSetupConfigDirectory(); return; } string[] files = Directory.GetFiles(CustomConfigFolderPath, "*.json", SearchOption.TopDirectoryOnly); if (files.Length == 0) { Logger.LogDebug((object)("Skipping loading custom " + NameId + " configs - no files found in the custom config folder.")); return; } string[] array = files; foreach (string text in array) { Dictionary dictionary = JsonConvert.DeserializeObject>(File.ReadAllText(text)); if (dictionary.Count == 0) { Logger.LogWarning((object)("Skipped loading custom " + NameId + " xp table file at '" + text + "' - no entries found.")); } else { CollectionExtensions.Do>((IEnumerable>)dictionary, (Action>)delegate(KeyValuePair pair) { _entries[pair.Key] = pair.Value; }); } } if (!AllowGroups || !Directory.Exists(CustomGroupsFolderPath)) { return; } files = Directory.GetFiles(CustomGroupsFolderPath, "*.json", SearchOption.TopDirectoryOnly); array = files; foreach (string text2 in array) { Dictionary dictionary2 = JsonConvert.DeserializeObject>(File.ReadAllText(text2)); if (dictionary2.Count == 0) { Logger.LogWarning((object)("Skipped loading custom " + NameId + " group file at '" + text2 + "' - no entries found.")); continue; } CollectionExtensions.Do>((IEnumerable>)dictionary2, (Action>)delegate(KeyValuePair pair) { CollectionExtensions.Do((IEnumerable)pair.Value, (Action)delegate(string groupEntry) { _groups[groupEntry] = pair.Key; }); }); } } public void ReloadResources() { _entries.Clear(); Assembly callingAssembly = ReflectionHelper.GetCallingAssembly(); LoadEmbeddedResources(callingAssembly); LoadCustomResources(); } public int GetXp(string key) { key = key.Replace("(Clone)", ""); if (_entries.TryGetValue(key, out var value)) { Logger.LogDebug((object)$"Found xp for '{key}': {value} xp"); return value; } if (!AllowGroups || _groups.Count == 0) { Logger.LogDebug((object)("Skipping group check - groups are not allowed for the " + NameId + " xp table.")); return 0; } if (!_groups.TryGetValue(key, out var value2)) { Logger.LogDebug((object)("Failed to find a group for '" + key + "'.")); return 0; } Logger.LogDebug((object)("Found group '" + value2 + "' for '" + key + "'.")); if (!_entries.TryGetValue(value2, out value)) { return 0; } return value; } } } namespace CharacterProgressionMod.UI { internal sealed class FloatingTextEffect : MonoBehaviour { private string _value; private Color _color = Color.white; public void Configure(string value, Color color) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) _value = value; _color = color; } private IEnumerator Start() { Text componentInChildren = ((Component)this).GetComponentInChildren(); CanvasGroup group = ((Component)this).GetComponentInChildren() ?? ((Component)this).gameObject.AddComponent(); if ((Object)(object)componentInChildren != (Object)null) { componentInChildren.text = _value; ((Graphic)componentInChildren).color = _color; } Vector3 origin = ((Component)this).transform.position; Vector3 initialScale = ((Component)this).transform.localScale; for (float time = 0f; time < 2.2f; time += Time.deltaTime) { float num = time / 2.2f; ((Component)this).transform.position = origin + Vector3.up * (0.85f * num); group.alpha = Mathf.Sin(num * (float)Math.PI); ((Component)this).transform.localScale = initialScale * Mathf.Lerp(0.75f, 1.1f, Mathf.Sin(num * (float)Math.PI)); yield return null; } Object.Destroy((Object)(object)((Component)this).gameObject); } private void LateUpdate() { //IL_001d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Camera.main != (Object)null) { ((Component)this).transform.rotation = ((Component)Camera.main).transform.rotation; } } } internal sealed class LocalPlayerUiSpawner : MonoBehaviour { private IEnumerator Start() { Player player = ((Component)this).GetComponent(); while ((Object)(object)player != (Object)null && (Object)(object)Player.m_localPlayer != (Object)(object)player) { yield return null; } while ((Object)(object)player != (Object)null && (Object)(object)ProgressionUiController.Instance == (Object)null) { GameObject val = PluginRuntime.Resources?.LevelingSystemUiPrefab; if ((Object)(object)val != (Object)null) { Object.Instantiate(val); break; } yield return null; } } } internal sealed class ProgressionUiController : MonoBehaviour { private readonly List