Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of LevelingSystem SharingXP v2.1.1
DW.CharacterProgression.dll
Decompiled 2 weeks agousing 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<Character>(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<PlayerLevelProgression>(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<Modifier> list = new List<Modifier>(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<Modifier> 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<int, int> ExperienceChanged; public event Action<int, int> LevelChanged; private void Awake() { _addExperienceRpcId = RpcId.Generate("AddExperience"); _setLevelRpcId = RpcId.Generate("SetLevel"); _player = ((Component)this).GetComponent<Player>(); ZNetView nview = ((Character)_player).m_nview; if (((nview != null) ? nview.GetZDO() : null) != null) { ((Character)_player).m_nview.Register<int>(_addExperienceRpcId, (Action<long, int>)RPC_AddExperience); ((Character)_player).m_nview.Register<int>(_setLevelRpcId, (Action<long, int>)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<PlayerSkillProgression>(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<string, int> _entries = new Dictionary<string, int>(); private readonly Dictionary<string, string> _groups = new Dictionary<string, string>(); 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<string, int> dictionary = JsonConvert.DeserializeObject<Dictionary<string, int>>(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<string, int>(_entries, dictionary); } } else { if (!AllowGroups) { continue; } string pattern2 = "^CharacterProgressionMod.Resources.default_configs\\." + NameId + "\\.groups\\.(?:[\\w-]+).json"; if (!Regex.IsMatch(text, pattern2)) { continue; } Dictionary<string, string[]> dictionary2 = JsonConvert.DeserializeObject<Dictionary<string, string[]>>(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<KeyValuePair<string, string[]>>((IEnumerable<KeyValuePair<string, string[]>>)dictionary2, (Action<KeyValuePair<string, string[]>>)delegate(KeyValuePair<string, string[]> pair) { CollectionExtensions.Do<string>((IEnumerable<string>)pair.Value, (Action<string>)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<string, int> dictionary = JsonConvert.DeserializeObject<Dictionary<string, int>>(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<KeyValuePair<string, int>>((IEnumerable<KeyValuePair<string, int>>)dictionary, (Action<KeyValuePair<string, int>>)delegate(KeyValuePair<string, int> 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<string, string[]> dictionary2 = JsonConvert.DeserializeObject<Dictionary<string, string[]>>(File.ReadAllText(text2)); if (dictionary2.Count == 0) { Logger.LogWarning((object)("Skipped loading custom " + NameId + " group file at '" + text2 + "' - no entries found.")); continue; } CollectionExtensions.Do<KeyValuePair<string, string[]>>((IEnumerable<KeyValuePair<string, string[]>>)dictionary2, (Action<KeyValuePair<string, string[]>>)delegate(KeyValuePair<string, string[]> pair) { CollectionExtensions.Do<string>((IEnumerable<string>)pair.Value, (Action<string>)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<Text>(); CanvasGroup group = ((Component)this).GetComponentInChildren<CanvasGroup>() ?? ((Component)this).gameObject.AddComponent<CanvasGroup>(); 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<Player>(); 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<GameObject>(val); break; } yield return null; } } } internal sealed class ProgressionUiController : MonoBehaviour { private readonly List<Button> _categoryButtons = new List<Button>(); private readonly List<RectTransform> _categoryContainers = new List<RectTransform>(); private readonly List<SkillCardView> _cards = new List<SkillCardView>(); private Player _player; private PlayerLevelProgression _progression; private PlayerSkillProgression _skills; private CanvasGroup _xpBarGroup; private CanvasGroup _skillsGroup; private CanvasGroup _levelUpGroup; private RectTransform _xpBarRect; private RectTransform _xpBarContainer; private RectTransform _content; private Text _levelText; private Text _levelShadow; private Text _xpText; private Text _xpShadow; private Text _levelUpText; private Text _levelUpShadow; private Text _remainingPoints; private Image _xpFill; private ScrollRect _scrollRect; private Scrollbar _scrollbar; private Button _closeButton; private Button _resetButton; private GameObject _skillPrefab; private bool _initialized; private bool _menuVisible; private int _currentCategory; private float _targetFill; private float _fillVelocity; public static ProgressionUiController Instance { get; private set; } private void Awake() { if ((Object)(object)Instance != (Object)null && (Object)(object)Instance != (Object)(object)this) { Object.Destroy((Object)(object)((Component)this).gameObject); return; } Instance = this; _levelText = Find<Text>("XP Bar/LevelText"); _levelShadow = Find<Text>("XP Bar/LevelText/Shadow"); _xpText = Find<Text>("XP Bar/XP Bar/XPText"); _xpShadow = Find<Text>("XP Bar/XP Bar/XPText/Shadow"); _xpFill = Find<Image>("XP Bar/XP Bar/XPFill"); _xpBarGroup = Find<CanvasGroup>("XP Bar"); _xpBarContainer = ((Component)((Component)this).transform.Find("XP Bar")).GetComponent<RectTransform>(); _xpBarRect = ((Component)((Component)this).transform.Find("XP Bar/XP Bar")).GetComponent<RectTransform>(); _levelUpText = Find<Text>("LevelUp Pop-Up/LevelUpText"); _levelUpShadow = Find<Text>("LevelUp Pop-Up/LevelUpText/Shadow"); _levelUpGroup = Find<CanvasGroup>("LevelUp Pop-Up"); _skillsGroup = Find<CanvasGroup>("Skills UI"); _remainingPoints = Find<Text>("Skills UI/Remaining Points"); _closeButton = Find<Button>("Skills UI/Close Menu"); _resetButton = Find<Button>("Skills UI/Reset Skills Button"); _content = ((Component)((Component)this).transform.Find("Skills UI/Scroll View/Viewport/Content")).GetComponent<RectTransform>(); _scrollRect = Find<ScrollRect>("Skills UI"); _scrollbar = Find<Scrollbar>("Skills UI/Scrollbar"); Transform val = ((Component)this).transform.Find("Skills UI/Scroll View/Category Buttons"); for (int i = 0; i < val.childCount; i++) { _categoryButtons.Add(((Component)val.GetChild(i)).GetComponent<Button>()); } for (int j = 0; j < ((Transform)_content).childCount; j++) { _categoryContainers.Add(((Component)((Transform)_content).GetChild(j)).GetComponent<RectTransform>()); } } private IEnumerator Start() { _levelUpGroup.alpha = 0f; ToggleMenu(visible: false); while ((Object)(object)Player.m_localPlayer == (Object)null || !((Component)Player.m_localPlayer).TryGetComponent<PlayerLevelProgression>(ref _progression) || !((Component)Player.m_localPlayer).TryGetComponent<PlayerSkillProgression>(ref _skills)) { yield return null; } _player = Player.m_localPlayer; _skillPrefab = PluginRuntime.Resources?.SkillUiPrefab; if ((Object)(object)_skillPrefab == (Object)null) { Logger.LogError((object)"SkillUI prefab was not registered."); yield break; } _progression.ExperienceChanged += OnExperienceChanged; _progression.LevelChanged += OnLevelChanged; _skills.RankChanged += OnRankChanged; _skills.SkillsReset += OnSkillsReset; _skills.CombatEffectRequested += SkillVisualEffects.Play; ConfigureLayout(); ConfigureButtons(); OpenCategory(0); RefreshAll(); _initialized = true; } private void Update() { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) if (!_initialized) { return; } if (_menuVisible && Input.GetKeyDown((KeyCode)27)) { ToggleMenu(visible: false); } else if (!IsTyping()) { KeyboardShortcut value = PluginRuntime.Config.OpenSkillsMenu.Value; if (((KeyboardShortcut)(ref value)).IsDown()) { ToggleMenu(!_menuVisible); } } _xpFill.fillAmount = Mathf.SmoothDamp(_xpFill.fillAmount, _targetFill, ref _fillVelocity, 0.18f, 8f); } private void OnDestroy() { if ((Object)(object)Instance == (Object)(object)this) { Instance = null; } if (_menuVisible) { GUIManager.BlockInput(false); } if ((Object)(object)_progression != (Object)null) { _progression.ExperienceChanged -= OnExperienceChanged; _progression.LevelChanged -= OnLevelChanged; } if ((Object)(object)_skills != (Object)null) { _skills.RankChanged -= OnRankChanged; _skills.SkillsReset -= OnSkillsReset; _skills.CombatEffectRequested -= SkillVisualEffects.Play; } } private void ConfigureLayout() { //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) PluginConfig config = PluginRuntime.Config; _xpBarGroup.alpha = ((config.ShowXp.Value || config.ShowLevel.Value) ? 1f : 0f); ((Component)_levelText).gameObject.SetActive(config.ShowLevel.Value); ((Component)_xpFill).gameObject.SetActive(config.ShowXp.Value); ((Component)_xpText).gameObject.SetActive(config.ShowXp.Value); _xpBarContainer.anchoredPosition = config.XpBarPosition.Value; Vector2 sizeDelta = _xpBarRect.sizeDelta; sizeDelta.x *= config.XpBarScale.Value / 100f; _xpBarRect.sizeDelta = sizeDelta; _scrollRect.verticalScrollbar = (config.ShowScrollbar.Value ? _scrollbar : null); ((Component)_scrollbar).gameObject.SetActive(config.ShowScrollbar.Value); } private void ConfigureButtons() { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Expected O, but got Unknown //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Expected O, but got Unknown for (int i = 0; i < _categoryButtons.Count; i++) { int categoryIndex = i; ((UnityEventBase)_categoryButtons[i].onClick).RemoveAllListeners(); ((UnityEvent)_categoryButtons[i].onClick).AddListener((UnityAction)delegate { OpenCategory(categoryIndex); }); } ((UnityEventBase)_closeButton.onClick).RemoveAllListeners(); ((UnityEvent)_closeButton.onClick).AddListener((UnityAction)delegate { ToggleMenu(visible: false); }); ((UnityEventBase)_resetButton.onClick).RemoveAllListeners(); ((UnityEvent)_resetButton.onClick).AddListener((UnityAction)delegate { _skills.ResetAll(); }); Text componentInChildren = ((Component)_resetButton).GetComponentInChildren<Text>(); if ((Object)(object)componentInChildren != (Object)null) { componentInChildren.text = Localize("$ls_reset_skills"); } } public void ToggleMenu(bool visible) { //IL_006a: Unknown result type (might be due to invalid IL or missing references) bool num = _menuVisible != visible; _menuVisible = visible; _skillsGroup.alpha = (visible ? 1f : 0f); _skillsGroup.interactable = visible; _skillsGroup.blocksRaycasts = visible; ((Component)_skillsGroup).gameObject.SetActive(visible); if ((Object)(object)_content != (Object)null) { _content.anchoredPosition = Vector2.zero; } if (num) { GUIManager.BlockInput(visible); } if (visible && _initialized) { RefreshAll(); } } private void OpenCategory(int categoryIndex) { //IL_007f: 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) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) if (categoryIndex < 0 || categoryIndex >= _categoryContainers.Count) { return; } _currentCategory = categoryIndex; _cards.Clear(); for (int i = 0; i < _categoryContainers.Count; i++) { ((Component)_categoryContainers[i]).gameObject.SetActive(i == categoryIndex); } RectTransform val = _categoryContainers[categoryIndex]; foreach (Transform item in (Transform)val) { Object.Destroy((Object)(object)((Component)item).gameObject); } SkillCategory category = (SkillCategory)categoryIndex; SkillDefinition[] array = SkillCatalog.Definitions.Where((SkillDefinition skillDefinition) => skillDefinition.Enabled && skillDefinition.Category == category).ToArray(); Color color = ((Graphic)((Component)_categoryButtons[categoryIndex]).GetComponent<Image>()).color; SkillDefinition[] array2 = array; foreach (SkillDefinition definition in array2) { GameObject obj = Object.Instantiate<GameObject>(_skillPrefab, (Transform)(object)val); Image component = obj.GetComponent<Image>(); if ((Object)(object)component != (Object)null) { ((Graphic)component).color = color; } SkillCardView skillCardView = obj.AddComponent<SkillCardView>(); skillCardView.Initialize(definition, _skills); _cards.Add(skillCardView); } float num2 = Mathf.Max(215f, Mathf.Ceil((float)array.Length / 3f) * 215f); _content.sizeDelta = new Vector2(_content.sizeDelta.x, num2); _content.anchoredPosition = Vector2.zero; UpdateCategoryLabels(); } private void OnExperienceChanged(int awarded, int totalExperience) { RefreshExperience(); if (awarded > 0) { SpawnExperienceText(awarded); } } private void OnLevelChanged(int oldLevel, int newLevel) { RefreshAll(); if (newLevel > oldLevel) { ((MonoBehaviour)this).StartCoroutine(ShowLevelUp(newLevel)); SkillVisualEffects.PlayLevelUp(_player); } } private void OnRankChanged(SkillId id, int oldRank, int newRank) { RefreshSkills(); } private void OnSkillsReset() { RefreshSkills(); } private void RefreshAll() { RefreshExperience(); RefreshSkills(); } private void RefreshExperience() { LevelEvaluationResult currentLevelEvaluation = _progression.CurrentLevelEvaluation; string value = Localize("$ls_level") + " " + _progression.GetLevel(); SetText(_levelText, _levelShadow, value); if (currentLevelEvaluation.IsMaxLevel) { SetText(_xpText, _xpShadow, Localize("$ls_max_level")); _targetFill = 1f; } else { string value2 = Mathf.Max(0, _progression.GetTotalExperience() - currentLevelEvaluation.TotalExperience).ToString("N0", CultureInfo.CurrentCulture) + " / " + currentLevelEvaluation.MaxExperience.ToString("N0", CultureInfo.CurrentCulture); SetText(_xpText, _xpShadow, value2); _targetFill = _progression.GetProgress01(); } } private void RefreshSkills() { if ((Object)(object)_skills == (Object)null) { return; } _remainingPoints.text = string.Format(Localize("$ls_remaining_points"), _skills.GetAvailablePoints()); UpdateCategoryLabels(); foreach (SkillCardView card in _cards) { card?.Refresh(); } } private void UpdateCategoryLabels() { if ((Object)(object)_skills == (Object)null) { return; } for (int i = 0; i < _categoryButtons.Count; i++) { SkillCategory category = (SkillCategory)i; int num = SkillCatalog.Definitions.Where((SkillDefinition definition) => definition.Category == category).Sum((SkillDefinition definition) => _skills.GetRank(definition.Id)); Text componentInChildren = ((Component)_categoryButtons[i]).GetComponentInChildren<Text>(); if ((Object)(object)componentInChildren != (Object)null) { componentInChildren.text = Localize("$ls_category_" + category.ToString().ToLowerInvariant()) + " (" + num + ")"; } } } private IEnumerator ShowLevelUp(int level) { SetText(_levelUpText, _levelUpShadow, Localize("$ls_level") + " " + level); yield return Fade(_levelUpGroup, 0f, 1f, 0.5f); yield return (object)new WaitForSeconds(2.2f); yield return Fade(_levelUpGroup, 1f, 0f, 1.2f); } private static IEnumerator Fade(CanvasGroup group, float from, float to, float duration) { for (float time = 0f; time < duration; time += Time.deltaTime) { group.alpha = Mathf.Lerp(from, to, time / duration); yield return null; } group.alpha = to; } private void SpawnExperienceText(int awarded) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) GameObject val = PluginRuntime.Resources?.ExperienceTextPrefab; if (!((Object)(object)val == (Object)null) && !((Object)(object)_player == (Object)null)) { Vector3 val2 = Random.insideUnitSphere * 0.22f; val2.y = Mathf.Abs(val2.y); Object.Instantiate<GameObject>(val, ((Character)_player).GetTopPoint() + val2, Quaternion.identity).AddComponent<FloatingTextEffect>().Configure("+" + awarded + " XP", new Color(0.45f, 0.9f, 1f, 1f)); } } private T Find<T>(string path) where T : Component { return ((Component)((Component)this).transform.Find(path)).GetComponent<T>(); } private static void SetText(Text text, Text shadow, string value) { text.text = value; shadow.text = value; } private static string Localize(string token) { if (Localization.instance != null) { return Localization.instance.Localize(token); } return token.TrimStart(new char[1] { '$' }); } private static bool IsTyping() { if (!Console.IsVisible() && !TextInput.IsVisible()) { if ((Object)(object)Chat.instance != (Object)null) { return Chat.instance.HasFocus(); } return false; } return true; } } internal sealed class SkillCardView : MonoBehaviour { private SkillDefinition _definition; private PlayerSkillProgression _progression; private Text _nameText; private Text _nameShadow; private Text _bonusText; private Text _bonusShadow; private Text _description; private Text _levelText; private Text _levelShadow; private Text _maxLevelText; private Image _icon; private Image _levelFill; private Button _addButton; private Button _removeButton; private Button _resetButton; private CanvasGroup _addGroup; private CanvasGroup _removeGroup; private CanvasGroup _resetGroup; private GameObject _resetText; public void Initialize(SkillDefinition definition, PlayerSkillProgression progression) { //IL_0182: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Expected O, but got Unknown //IL_019e: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Expected O, but got Unknown //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Expected O, but got Unknown _definition = definition; _progression = progression; _nameText = Find<Text>("Skill Name"); _nameShadow = Find<Text>("Skill Name/Skill Name Shadow"); _bonusText = Find<Text>("Bonus Text"); _bonusShadow = Find<Text>("Bonus Text/Bonus Text Shadow"); _description = Find<Text>("Description"); _levelText = Find<Text>("Skill Level"); _levelShadow = Find<Text>("Skill Level/Skill Level Shadow"); _maxLevelText = Find<Text>("Skill Level Ring/Skill Level Max"); _icon = Find<Image>("Skill Icon"); _levelFill = Find<Image>("Skill Level Ring/Skill Level Fill Slider"); _addButton = Find<Button>("Add Skill Point (Mask)/Add Skill Point"); _removeButton = Find<Button>("Remove Skill Point (Mask)/Remove Skill Point"); _resetButton = Find<Button>("Reset Skill Point (Mask)/Reset Skill Point"); _addGroup = ((Component)_addButton).GetComponent<CanvasGroup>(); _removeGroup = ((Component)_removeButton).GetComponent<CanvasGroup>(); _resetGroup = ((Component)_resetButton).GetComponent<CanvasGroup>(); Transform obj = ((Component)this).transform.Find("Reset Skill Point (Mask)/Reset Text"); _resetText = ((obj != null) ? ((Component)obj).gameObject : null); ((UnityEventBase)_addButton.onClick).RemoveAllListeners(); ((UnityEventBase)_removeButton.onClick).RemoveAllListeners(); ((UnityEventBase)_resetButton.onClick).RemoveAllListeners(); ((UnityEvent)_addButton.onClick).AddListener((UnityAction)delegate { _progression.TryAddRank(_definition.Id); }); ((UnityEvent)_removeButton.onClick).AddListener((UnityAction)delegate { _progression.TryRemoveRank(_definition.Id); }); ((UnityEvent)_resetButton.onClick).AddListener((UnityAction)delegate { _progression.ResetRank(_definition.Id); }); _icon.sprite = SkillIconProvider.Load(_definition.IconName); _progression.RankChanged += OnRankChanged; _progression.SkillsReset += Refresh; Refresh(); } private void OnDestroy() { if (!((Object)(object)_progression == (Object)null)) { _progression.RankChanged -= OnRankChanged; _progression.SkillsReset -= Refresh; } } private void OnRankChanged(SkillId id, int oldRank, int newRank) { Refresh(); } public void Refresh() { if (!((Object)(object)_progression == (Object)null) && _definition != null) { int rank = _progression.GetRank(_definition.Id); string value = Localize(_definition.NameToken); string text = _progression.GetBonus(_definition.Id).ToString("0.##", CultureInfo.InvariantCulture); string value2 = "+" + text + _definition.Unit; SetText(_nameText, _nameShadow, value); SetText(_bonusText, _bonusShadow, value2); SetText(_levelText, _levelShadow, rank.ToString(CultureInfo.InvariantCulture)); _maxLevelText.text = _definition.MaxRank.ToString(CultureInfo.InvariantCulture); _description.text = Localize(_definition.DescriptionToken) + "\n" + string.Format(Localize("$ls_per_rank"), _definition.BonusPerRank.ToString("0.##", CultureInfo.InvariantCulture), _definition.Unit); _levelFill.fillAmount = 0.1245f + (float)rank / (float)_definition.MaxRank * 0.75f; Toggle(_addGroup, rank < _definition.MaxRank && _progression.GetAvailablePoints() > 0); Toggle(_removeGroup, rank > 0); Toggle(_resetGroup, rank > 0); if ((Object)(object)_resetText != (Object)null) { _resetText.SetActive(rank > 0); } } } private T Find<T>(string path) where T : Component { return ((Component)((Component)this).transform.Find(path)).GetComponent<T>(); } private static void SetText(Text text, Text shadow, string value) { text.text = value; shadow.text = value; } private static void Toggle(CanvasGroup group, bool visible) { if (!((Object)(object)group == (Object)null)) { group.alpha = (visible ? 1f : 0.2f); group.interactable = visible; group.blocksRaycasts = visible; } } private static string Localize(string token) { if (Localization.instance != null) { return Localization.instance.Localize(token); } return token.TrimStart(new char[1] { '$' }); } } internal static class SkillIconProvider { private const string BundleIconPath = "Assets/_Leveling System/Sprites/Skill Icons/"; private static readonly Dictionary<string, Sprite> Cache = new Dictionary<string, Sprite>(); public static Sprite Load(string iconName) { //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Expected O, but got Unknown //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Unknown result type (might be due to invalid IL or missing references) if (Cache.TryGetValue(iconName, out var value)) { return value; } ModResources resources = PluginRuntime.Resources; Sprite val = ((resources != null) ? resources.AssetBundle.LoadAsset<Sprite>("Assets/_Leveling System/Sprites/Skill Icons/" + iconName + ".png") : null); if ((Object)(object)val != (Object)null) { Cache[iconName] = val; return val; } Assembly executingAssembly = Assembly.GetExecutingAssembly(); string text = executingAssembly.GetManifestResourceNames().FirstOrDefault((string name) => name.EndsWith("Resources.skill_icons." + iconName + ".png", StringComparison.OrdinalIgnoreCase)); if (text == null) { Logger.LogWarning((object)("Skill icon '" + iconName + "' was not found.")); return null; } using Stream stream = executingAssembly.GetManifestResourceStream(text); using MemoryStream memoryStream = new MemoryStream(); stream.CopyTo(memoryStream); Texture2D val2 = new Texture2D(2, 2, (TextureFormat)5, false) { name = iconName, filterMode = (FilterMode)1, wrapMode = (TextureWrapMode)1 }; MethodInfo methodInfo = Type.GetType("UnityEngine.ImageConversion, UnityEngine.ImageConversionModule")?.GetMethod("LoadImage", BindingFlags.Static | BindingFlags.Public, null, new Type[3] { typeof(Texture2D), typeof(byte[]), typeof(bool) }, null); if (!(methodInfo != null) || !(bool)methodInfo.Invoke(null, new object[3] { val2, memoryStream.ToArray(), false })) { Object.Destroy((Object)(object)val2); return null; } Sprite val3 = Sprite.Create(val2, new Rect(0f, 0f, (float)((Texture)val2).width, (float)((Texture)val2).height), new Vector2(0.5f, 0.5f), 100f); Cache[iconName] = val3; return val3; } } internal static class SkillVisualEffects { public static void Play(Vector3 position, CombatEffectType effectType) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) GameObject val = PluginRuntime.Resources?.CriticalHitEffectPrefab; if ((Object)(object)val == (Object)null) { return; } GameObject val2 = Object.Instantiate<GameObject>(val, position, Quaternion.identity); switch (effectType) { case CombatEffectType.SummonHit: Tint(val2, new Color(0.2f, 0.75f, 1f, 1f)); break; case CombatEffectType.WeakHitBlocked: Tint(val2, new Color(0.1f, 0.95f, 1f, 1f)); if ((Object)(object)GameCamera.instance != (Object)null && (Object)(object)Player.m_localPlayer != (Object)null) { GameCamera.instance.AddShake(((Component)Player.m_localPlayer).transform.position, 4f, 0.12f, false); } break; default: if ((Object)(object)GameCamera.instance != (Object)null && (Object)(object)Player.m_localPlayer != (Object)null) { GameCamera.instance.AddShake(((Component)Player.m_localPlayer).transform.position, 10f, 0.25f, false); } break; } Object.Destroy((Object)(object)val2, 4f); } public static void PlayLevelUp(Player player) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { return; } PluginConfig config = PluginRuntime.Config; if (config == null || config.LevelUpVFX.Value) { GameObject val = PluginRuntime.Resources?.LevelUpEffectPrefab; if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)Object.Instantiate<GameObject>(val, ((Character)player).GetCenterPoint(), Quaternion.identity, ((Component)player).transform), 6f); } } } private static void Tint(GameObject effect, Color color) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) ParticleSystem[] componentsInChildren = effect.GetComponentsInChildren<ParticleSystem>(true); for (int i = 0; i < componentsInChildren.Length; i++) { MainModule main = componentsInChildren[i].main; ((MainModule)(ref main)).startColor = MinMaxGradient.op_Implicit(color); } } } } namespace CharacterProgressionMod.Skills { public enum CombatEffectType { CriticalHit, SummonHit, WeakHitBlocked } public sealed class PlayerSkillProgression : MonoBehaviour { private const string SaveKeyPrefix = "Cozyheim!Skill!"; private readonly Dictionary<SkillId, int> _ranks = new Dictionary<SkillId, int>(); private Player _player; private PluginConfig _config; private bool _loaded; private string _combatEffectRpcId; public Player Player => _player; public event Action<SkillId, int, int> RankChanged; public event Action SkillsReset; public event Action<Vector3, CombatEffectType> CombatEffectRequested; private void Awake() { _player = ((Component)this).GetComponent<Player>(); _combatEffectRpcId = RpcId.Generate("CombatSkillEffect"); ZNetView nview = ((Character)_player).m_nview; if (((nview != null) ? nview.GetZDO() : null) != null) { ((Character)_player).m_nview.Register<Vector3, int>(_combatEffectRpcId, (Action<long, Vector3, int>)RPC_CombatEffect); } } private void Start() { Load(); } public void Configure(PluginConfig config) { _config = config; } public int GetRank(SkillId id) { 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(GetNetworkRankKey(id), 0)) : ((int?)null)); } int? num = obj; return Mathf.Clamp(num.GetValueOrDefault(), 0, SkillCatalog.Get(id).MaxRank); } EnsureLoaded(); if (!_ranks.TryGetValue(id, out var value)) { return 0; } return value; } public float GetBonus(SkillId id) { SkillDefinition skillDefinition = SkillCatalog.Get(id); if (!skillDefinition.Enabled) { return 0f; } return (float)GetRank(id) * skillDefinition.BonusPerRank; } public float GetMultiplier(SkillId id) { return 1f + GetBonus(id) / 100f; } public int GetSpentPoints() { EnsureLoaded(); return SkillCatalog.Definitions.Where((SkillDefinition definition) => definition.Enabled).Sum((SkillDefinition definition) => GetRank(definition.Id)); } public int GetAvailablePoints() { PlayerLevelProgression component = ((Component)this).GetComponent<PlayerLevelProgression>(); int num = Mathf.FloorToInt((float)(((Object)(object)component == (Object)null) ? 1 : Mathf.Max(1, component.GetLevel())) * (_config?.SkillPointsPerLevel.Value ?? 1f)); return Mathf.Max(0, num - GetSpentPoints()); } public bool TryAddRank(SkillId id) { EnsureLoaded(); if (!((Character)_player).IsOwner() || !SkillCatalog.Get(id).Enabled || GetAvailablePoints() <= 0) { return false; } int rank = GetRank(id); if (rank >= SkillCatalog.Get(id).MaxRank) { return false; } SetRank(id, rank + 1); return true; } public bool TryRemoveRank(SkillId id) { EnsureLoaded(); if (((Character)_player).IsOwner()) { PluginConfig config = _config; if (config == null || config.AllowSkillRefunds.Value) { int rank = GetRank(id); if (rank <= 0) { return false; } SetRank(id, rank - 1); return true; } } return false; } public void ResetRank(SkillId id) { EnsureLoaded(); if (((Character)_player).IsOwner()) { PluginConfig config = _config; if (config == null || config.AllowSkillRefunds.Value) { SetRank(id, 0); } } } public void RequestCombatEffect(Vector3 position, CombatEffectType effectType) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_player == (Object)null) { return; } ZNetView nview = ((Character)_player).m_nview; if (((nview != null) ? nview.GetZDO() : null) != null) { if (((Character)_player).IsOwner()) { RPC_CombatEffect(0L, position, (int)effectType); return; } ((Character)_player).m_nview.InvokeRPC(_combatEffectRpcId, new object[2] { position, (int)effectType }); } } public void ResetAll() { EnsureLoaded(); if (!((Character)_player).IsOwner()) { return; } PluginConfig config = _config; if (config != null && !config.AllowSkillRefunds.Value) { return; } foreach (SkillDefinition definition in SkillCatalog.Definitions) { if (GetRank(definition.Id) > 0) { SetRank(definition.Id, 0, save: false); } } Save(); this.SkillsReset?.Invoke(); } private void SetRank(SkillId id, int rank, bool save = true) { SkillDefinition skillDefinition = SkillCatalog.Get(id); int rank2 = GetRank(id); rank = Mathf.Clamp(rank, 0, skillDefinition.MaxRank); if (rank2 != rank) { _ranks[id] = rank; if (save) { Save(); } this.RankChanged?.Invoke(id, rank2, rank); if (id == SkillId.SummonHealth) { SummonBonusService.RefreshOwnedSummons(_player); } } } private void RPC_CombatEffect(long sender, Vector3 position, int effectType) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) if (((Character)_player).IsOwner() && Enum.IsDefined(typeof(CombatEffectType), effectType)) { this.CombatEffectRequested?.Invoke(position, (CombatEffectType)effectType); } } private void EnsureLoaded() { if (!_loaded) { Load(); } } private void Load() { if (_loaded || (Object)(object)_player == (Object)null || _config == null) { return; } foreach (SkillDefinition definition in SkillCatalog.Definitions) { int result = 0; if (_player.m_customData.TryGetValue("Cozyheim!Skill!" + definition.Id, out var value)) { int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out result); } _ranks[definition.Id] = Mathf.Clamp(result, 0, definition.MaxRank); } _loaded = true; PublishRanks(); } private void Save() { if (!((Character)_player).IsOwner()) { return; } foreach (KeyValuePair<SkillId, int> rank in _ranks) { _player.m_customData["Cozyheim!Skill!" + rank.Key] = rank.Value.ToString(CultureInfo.InvariantCulture); ZNetView nview = ((Character)_player).m_nview; if (nview != null) { ZDO zDO = nview.GetZDO(); if (zDO != null) { zDO.Set(GetNetworkRankKey(rank.Key), rank.Value, false); } } } } private void PublishRanks() { if (!((Character)_player).IsOwner()) { return; } foreach (KeyValuePair<SkillId, int> rank in _ranks) { ZNetView nview = ((Character)_player).m_nview; if (nview != null) { ZDO zDO = nview.GetZDO(); if (zDO != null) { zDO.Set(GetNetworkRankKey(rank.Key), rank.Value, false); } } } } private static int GetNetworkRankKey(SkillId id) { return StringExtensionMethods.GetStableHashCode("LevelingSystem.Skill." + id); } } public static class SkillCatalog { private static IReadOnlyList<SkillDefinition> _definitions = Array.Empty<SkillDefinition>(); private static IReadOnlyDictionary<SkillId, SkillDefinition> _byId = new Dictionary<SkillId, SkillDefinition>(); public static IReadOnlyList<SkillDefinition> Definitions => _definitions; public static void Initialize(PluginConfig config) { _definitions = new SkillDefinition[10] { Create(config, SkillId.SummonDamage, SkillCategory.Offensive, "SummonDamage", "%"), Create(config, SkillId.CriticalChance, SkillCategory.Offensive, "CriticalHitChance", "%"), Create(config, SkillId.CriticalDamage, SkillCategory.Offensive, "CriticalHitDamage", "%"), Create(config, SkillId.SummonHealth, SkillCategory.Defensive, "SummonHealth", "%"), Create(config, SkillId.Vitality, SkillCategory.Defensive, "HP", string.Empty), Create(config, SkillId.WeakHitImmunity, SkillCategory.Defensive, "WeakHitImmunity", string.Empty), Create(config, SkillId.Endurance, SkillCategory.Core, "Stamina", string.Empty), Create(config, SkillId.ArcaneCapacity, SkillCategory.Core, "Eitr", string.Empty), Create(config, SkillId.ExperienceGain, SkillCategory.Utility, "ExperienceGain", "%"), Create(config, SkillId.CarryWeight, SkillCategory.Utility, "CarryWeight", string.Empty) }; _byId = _definitions.ToDictionary((SkillDefinition definition) => definition.Id); } public static SkillDefinition Get(SkillId id) { return _byId[id]; } private static SkillDefinition Create(PluginConfig config, SkillId id, SkillCategory category, string iconName, string unit) { string text = ToSnakeCase(id.ToString()); return new SkillDefinition(id, category, "$ls_skill_" + text, "$ls_skill_" + text + "_description", iconName, unit, config.SkillTunings[id]); } private static string ToSnakeCase(string value) { List<char> list = new List<char>(value.Length + 4); for (int i = 0; i < value.Length; i++) { char c = value[i]; if (i > 0 && char.IsUpper(c)) { list.Add('_'); } list.Add(char.ToLowerInvariant(c)); } return new string(list.ToArray()); } } public enum SkillCategory { Offensive, Defensive, Core, Utility } public sealed class SkillDefinition { public SkillId Id { get; } public SkillCategory Category { get; } public string NameToken { get; } public string DescriptionToken { get; } public string IconName { get; } public string Unit { get; } public SkillTuning Tuning { get; } public bool Enabled => Tuning.Enabled.Value; public int MaxRank => Tuning.MaxRank.Value; public float BonusPerRank => Tuning.BonusPerRank.Value; public SkillDefinition(SkillId id, SkillCategory category, string nameToken, string descriptionToken, string iconName, string unit, SkillTuning tuning) { Id = id; Category = category; NameToken = nameToken; DescriptionToken = descriptionToken; IconName = iconName; Unit = unit; Tuning = tuning; } } public enum SkillId { SummonDamage, SummonHealth, ExperienceGain, Vitality, WeakHitImmunity, Endurance, ArcaneCapacity, CarryWeight, CriticalChance, CriticalDamage } public sealed class SkillTuning { public ConfigEntry<bool> Enabled { get; } public ConfigEntry<int> MaxRank { get; } public ConfigEntry<float> BonusPerRank { get; } public SkillTuning(ConfigEntry<bool> enabled, ConfigEntry<int> maxRank, ConfigEntry<float> bonusPerRank) { Enabled = enabled; MaxRank = maxRank; BonusPerRank = bonusPerRank; } } internal static class SummonBonusService { private static readonly int BaseHealthKey = StringExtensionMethods.GetStableHashCode("LevelingSystem.SummonBaseHealth"); private static readonly int HealthMultiplierKey = StringExtensionMethods.GetStableHashCode("LevelingSystem.SummonHealthMultiplier"); public static void ApplyHealthBonus(Character summon, Player owner) { PlayerSkillProgression playerSkillProgression = default(PlayerSkillProgression); if ((Object)(object)summon == (Object)null || (Object)(object)owner == (Object)null || summon.IsPlayer() || !summon.IsOwner() || !((Component)owner).TryGetComponent<PlayerSkillProgression>(ref playerSkillProgression)) { return; } ZNetView nview = summon.m_nview; ZDO val = ((nview != null) ? nview.GetZDO() : null); if (val != null) { float num = Mathf.Max(1f, val.GetFloat(HealthMultiplierKey, 1f)); float num2 = Mathf.Max(1f, summon.GetMaxHealth()); float num3 = val.GetFloat(BaseHealthKey, 0f); if (num3 <= 0f) { num3 = num2 / num; val.Set(BaseHealthKey, num3); } else if (!Mathf.Approximately(num2, num3 * num)) { num3 = num2 / num; val.Set(BaseHealthKey, num3); } float multiplier = playerSkillProgression.GetMultiplier(SkillId.SummonHealth); float num4 = Mathf.Max(1f, num3 * multiplier); if (!Mathf.Approximately(num2, num4)) { float num5 = Mathf.Clamp01(summon.GetHealth() / num2); summon.SetMaxHealth(num4); summon.SetHealth(Mathf.Max(1f, num4 * num5)); val.Set(HealthMultiplierKey, multiplier); } } } public static void RefreshOwnedSummons(Player owner) { if ((Object)(object)owner == (Object)null || !((Character)owner).IsOwner()) { return; } foreach (Character allCharacter in Character.GetAllCharacters()) { if ((Object)(object)allCharacter != (Object)null && (Object)(object)SummonOwnerResolver.Resolve(allCharacter) == (Object)(object)owner) { ApplyHealthBonus(allCharacter, owner); } } } } public static class SummonOwnerResolver { private static readonly int OwnerPlayerIdKey = StringExtensionMethods.GetStableHashCode("LevelingSystem.OwnerPlayerId"); private static readonly List<Func<Character, Player>> ExternalResolvers = new List<Func<Character, Player>>(); public static void RegisterResolver(Func<Character, Player> resolver) { if (resolver != null && !ExternalResolvers.Contains(resolver)) { ExternalResolvers.Add(resolver); } } public static void UnregisterResolver(Func<Character, Player> resolver) { ExternalResolvers.Remove(resolver); } public static void RememberOwner(Character summon, Player owner) { if (!((Object)(object)summon == (Object)null) && !((Object)(object)owner == (Object)null) && !summon.IsPlayer()) { ZNetView nview = summon.m_nview; ZDO val = ((nview != null) ? nview.GetZDO() : null); if (val != null && summon.IsOwner()) { val.Set(OwnerPlayerIdKey, owner.GetPlayerID()); } SummonBonusService.ApplyHealthBonus(summon, owner); } } public static Player Resolve(Character summon) { if ((Object)(object)summon == (Object)null || summon.IsPlayer()) { return null; } Func<Character, Player>[] array = ExternalResolvers.ToArray(); foreach (Func<Character, Player> func in array) { try { Player val = func(summon); if ((Object)(object)val != (Object)null) { RememberOwner(summon, val); return val; } } catch (Exception ex) { Logger.LogWarning((object)("A summon owner resolver failed: " + ex.Message)); } } ZNetView nview = summon.m_nview; ZDO val2 = ((nview != null) ? nview.GetZDO() : null); long num = ((val2 != null) ? val2.GetLong(OwnerPlayerIdKey, 0L) : 0); if (num != 0L) { Player val3 = FindPlayer(num); if ((Object)(object)val3 != (Object)null) { return val3; } } BaseAI baseAI = summon.GetBaseAI(); MonsterAI val4 = (MonsterAI)(object)((baseAI is MonsterAI) ? baseAI : null); if (val4 != null) { GameObject followTarget = val4.GetFollowTarget(); Player val5 = (((Object)(object)followTarget == (Object)null) ? null : followTarget.GetComponent<Player>()); if ((Object)(object)val5 != (Object)null) { RememberOwner(summon, val5); return val5; } } if (val2 != null) { string text = val2.GetString(ZDOVars.s_follow, string.Empty); if (!string.IsNullOrWhiteSpace(text)) { foreach (Player allPlayer in Player.GetAllPlayers()) { if ((Object)(object)allPlayer != (Object)null && allPlayer.GetPlayerName() == text) { RememberOwner(summon, allPlayer); return allPlayer; } } } } return null; } private static Player FindPlayer(long playerId) { foreach (Player allPlayer in Player.GetAllPlayers()) { if ((Object)(object)allPlayer != (Object)null && allPlayer.GetPlayerID() == playerId) { return allPlayer; } } return null; } } } namespace CharacterProgressionMod.Patches { [HarmonyPatch] internal static class Patcher { [HarmonyPatch(typeof(Character))] internal static class CharacterInitPatch { [HarmonyPostfix] [HarmonyPatch("Awake")] private static void CharacterAwake_Postfix(Character __instance) { if (!__instance.IsPlayer() && __instance.IsOwner()) { ((Component)__instance).gameObject.AddComponent<RewardExpOnDeath>(); } } } [HarmonyPatch(typeof(Player))] internal static class PlayerInitPatch { [HarmonyPostfix] [HarmonyPatch("Awake")] private static void PlayerAwake_Postfix(Player __instance) { PlayerLevelProgression obj = ((Component)__instance).GetComponent<PlayerLevelProgression>() ?? ((Component)__instance).gameObject.AddComponent<PlayerLevelProgression>(); obj.LevelExperienceTable = _resources.LevelExperienceTable; obj.Config = _config; (((Component)__instance).GetComponent<PlayerSkillProgression>() ?? ((Component)__instance).gameObject.AddComponent<PlayerSkillProgression>()).Configure(_config); } [HarmonyPostfix] [HarmonyPatch("Start")] private static void PlayerStart_Postfix(Player __instance) { if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && !((Object)(object)ProgressionUiController.Instance != (Object)null)) { ((Component)__instance).gameObject.AddComponent<LocalPlayerUiSpawner>(); } } } private static readonly Harmony Harmony = new Harmony("node.deepwolf.LevelingSystem"); private static PluginConfig _config; private static ModResources _resources; public static void PatchAll(PluginConfig config, ModResources resources) { Harmony.PatchAll(Assembly.GetExecutingAssembly()); _config = config; _resources = resources; RewardExpOnDeath.CreatureExperienceTable = resources.CreatureExperienceTable; RewardExpOnDeath.NearbyPlayerXpRadius = config.NearbyXpRadius.Value; ForagingExpSourcePatch.PickableExperienceTable = resources.PickableExperienceTable; } public static void Unpatch() { Harmony.UnpatchSelf(); } } [HarmonyPatch] internal static class CreatureKillExpSourcePatch { } [HarmonyPatch] internal static class ForagingExpSourcePatch { public static XpTable PickableExperienceTable { get; set; } [HarmonyPatch(typeof(Pickable), "Interact")] [HarmonyPostfix] private static void Interact_Postfix(Pickable __instance, Humanoid character, bool __result) { if (!__result) { return; } Player val = (Player)(object)((character is Player) ? character : null); if (val != null && __instance.m_picked) { int num = PickableExperienceTable?.GetXp(((Object)__instance).name) ?? 0; PlayerLevelProgression playerLevelProgression = default(PlayerLevelProgression); if (num > 0 && ((Component)val).TryGetComponent<PlayerLevelProgression>(ref playerLevelProgression)) { playerLevelProgression.AddExperience(num); } } } } [HarmonyPatch] internal static class SkillBonusPatches { [HarmonyPatch(typeof(Character), "GetMaxHealth")] [HarmonyPostfix] private static void GetMaxHealth_Postfix(Character __instance, ref float __result) { Player val = (Player)(object)((__instance is Player) ? __instance : null); PlayerSkillProgression playerSkillProgression = default(PlayerSkillProgression); if (val != null && ((Component)val).TryGetComponent<PlayerSkillProgression>(ref playerSkillProgression)) { __result += playerSkillProgression.GetBonus(SkillId.Vitality); } } [HarmonyPatch(typeof(Player), "GetMaxStamina")] [HarmonyPostfix] private static void GetMaxStamina_Postfix(Player __instance, ref float __result) { PlayerSkillProgression playerSkillProgression = default(PlayerSkillProgression); if (((Component)__instance).TryGetComponent<PlayerSkillProgression>(ref playerSkillProgression)) { __result += playerSkillProgression.GetBonus(SkillId.Endurance); } } [HarmonyPatch(typeof(Player), "GetMaxEitr")] [HarmonyPostfix] private static void GetMaxEitr_Postfix(Player __instance, ref float __result) { PlayerSkillProgression playerSkillProgression = default(PlayerSkillProgression); if (((Component)__instance).TryGetComponent<PlayerSkillProgression>(ref playerSkillProgression)) { __result += playerSkillProgression.GetBonus(SkillId.ArcaneCapacity); } } [HarmonyPatch(typeof(Player), "GetMaxCarryWeight")] [HarmonyPostfix] private static void GetMaxCarryWeight_Postfix(Player __instance, ref float __result) { PlayerSkillProgression playerSkillProgression = default(PlayerSkillProgression); if (((Component)__instance).TryGetComponent<PlayerSkillProgression>(ref playerSkillProgression)) { __result += playerSkillProgression.GetBonus(SkillId.CarryWeight); } } [HarmonyPatch(typeof(Character), "ApplyDamage")] [HarmonyPrefix] [HarmonyPriority(600)] private static void ApplyDamage_Prefix(Character __instance, HitData hit) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) if (hit == null || (Object)(object)__instance == (Object)null) { return; } Player val = (Player)(object)((__instance is Player) ? __instance : null); if (val != null) { ApplyWeakHitImmunity(val, hit); } else { if ((int)__instance.GetFaction() == 0) { return; } Character attacker = hit.GetAttacker(); if ((Object)(object)attacker == (Object)null || (Object)(object)attacker == (Object)(object)__instance) { return; } Player val2 = (Player)(object)((attacker is Player) ? attacker : null); if (val2 != null) { ApplyCriticalHit(val2, hit); return; } Player val3 = SummonOwnerResolver.Resolve(attacker); PlayerSkillProgression playerSkillProgression = default(PlayerSkillProgression); if ((Object)(object)val3 == (Object)null || !((Component)val3).TryGetComponent<PlayerSkillProgression>(ref playerSkillProgression)) { return; } SummonBonusService.ApplyHealthBonus(attacker, val3); float multiplier = playerSkillProgression.GetMultiplier(SkillId.SummonDamage); if (!(multiplier <= 1f)) { ((DamageTypes)(ref hit.m_damage)).Modify(multiplier); PluginConfig config = PluginRuntime.Config; if (config == null || config.SummonHitVfx.Value) { playerSkillProgression.RequestCombatEffect(__instance.GetCenterPoint(), CombatEffectType.SummonHit); } } } } [HarmonyPatch(typeof(Character), "DamageArmorDurability")] [HarmonyPrefix] [HarmonyPriority(600)] private static bool DamageArmorDurability_Prefix(Character __instance, HitData hit) { Player val = (Player)(object)((__instance is Player) ? __instance : null); PlayerSkillProgression skills; if (val != null) { return !ShouldBlockWeakHit(val, hit, out skills); } return true; } private static void ApplyWeakHitImmunity(Player victim, HitData hit) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) if (ShouldBlockWeakHit(victim, hit, out var skills)) { ((DamageTypes)(ref hit.m_damage)).Modify(0f); PluginConfig config = PluginRuntime.Config; if (config == null || config.WeakHitImmunityVfx.Value) { skills.RequestCombatEffect(((Character)victim).GetCenterPoint(), CombatEffectType.WeakHitBlocked); } } } private static bool ShouldBlockWeakHit(Player victim, HitData hit, out PlayerSkillProgression skills) { skills = null; if ((Object)(object)victim == (Object)null || hit == null || !((Component)victim).TryGetComponent<PlayerSkillProgression>(ref skills)) { return false; } float bonus = skills.GetBonus(SkillId.WeakHitImmunity); float totalDamage = hit.GetTotalDamage(); if (bonus <= 0f || totalDamage <= 0.1f || totalDamage > bonus) { return false; } Character attacker = hit.GetAttacker(); if ((Object)(object)attacker == (Object)null || (Object)(object)attacker == (Object)(object)victim || attacker.IsPlayer() || !BaseAI.IsEnemy(attacker, (Character)(object)victim)) { return false; } if (attacker.IsBoss()) { return PluginRuntime.Config?.WeakHitImmunityAffectsBosses.Value ?? false; } return true; } private static void ApplyCriticalHit(Player player, HitData hit) { //IL_0065: Unknown result type (might be due to invalid IL or missing references) PlayerSkillProgression playerSkillProgression = default(PlayerSkillProgression); if (!((Component)player).TryGetComponent<PlayerSkillProgression>(ref playerSkillProgression)) { return; } float num = playerSkillProgression.GetBonus(SkillId.CriticalChance) / 100f; if (!(num <= 0f) && !(Random.value >= num)) { float num2 = 1.5f + playerSkillProgression.GetBonus(SkillId.CriticalDamage) / 100f; ((DamageTypes)(ref hit.m_damage)).Modify(num2); PluginConfig config = PluginRuntime.Config; if (config == null || config.CriticalHitVfx.Value) { playerSkillProgression.RequestCombatEffect(hit.m_point, CombatEffectType.CriticalHit); } } } } [HarmonyPatch(typeof(Tameable), "Command")] internal static class SummonOwnerPatch { [HarmonyPrefix] private static void Command_Prefix(Tameable __instance, Player __0) { SummonOwnerResolver.RememberOwner(((Object)(object)__instance == (Object)null) ? null : ((Component)__instance).GetComponent<Character>(), __0); } } [HarmonyPatch(typeof(Tameable), "RPC_Command")] internal static class SummonOwnerRpcPatch { [HarmonyPostfix] private static void Command_Postfix(Tameable __instance, ZDOID __1) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)__instance == (Object)null) && !((Object)(object)ZNetScene.instance == (Object)null)) { GameObject val = ZNetScene.instance.FindInstance(__1); Player owner = (((Object)(object)val == (Object)null) ? null : val.GetComponent<Player>()); SummonOwnerResolver.RememberOwner(((Component)__instance).GetComponent<Character>(), owner); } } } } namespace CharacterProgressionMod.Loaders { public static class EmbeddedResourceLoader { public static string Load(string path) { return AssetUtils.LoadTextFromResources(path); } } public class FileResourceLoader { public static string Load(string path) { return string.Empty; } } } namespace CharacterProgressionMod.Core { internal static class LocalizationSetup { public static void Register() { CustomLocalization localization = LocalizationManager.Instance.GetLocalization(); string text = "English"; localization.AddTranslation(ref text, English()); text = "Russian"; localization.AddTranslation(ref text, Russian()); } private static Dictionary<string, string> English() { return new Dictionary<string, string> { ["ls_level"] = "Level", ["ls_max_level"] = "MAX LEVEL", ["ls_remaining_points"] = "Skill points: {0}", ["ls_reset_skills"] = "Reset skills", ["ls_category_offensive"] = "Offensive", ["ls_category_defensive"] = "Defensive", ["ls_category_core"] = "Core", ["ls_category_utility"] = "Utility", ["ls_per_rank"] = "+{0}{1} per rank", ["ls_skill_summon_damage"] = "Spirit Command", ["ls_skill_summon_damage_description"] = "Increases damage dealt by creatures summoned or commanded by you, including compatible modded creatures.", ["ls_skill_summon_health"] = "Spirit Ward", ["ls_skill_summon_health_description"] = "Increases maximum health of creatures summoned or commanded by you.", ["ls_skill_experience_gain"] = "Wisdom", ["ls_skill_experience_gain_description"] = "Increases all experience earned by this progression system.", ["ls_skill_vitality"] = "Vitality", ["ls_skill_vitality_description"] = "Increases your maximum health.", ["ls_skill_weak_hit_immunity"] = "Unyielding Ward", ["ls_skill_weak_hit_immunity_description"] = "Completely negates hostile monster attacks whose direct damage, after armor and resistances, does not exceed the listed threshold. Players, environmental damage, damage over time and bosses are unaffected by default.", ["ls_skill_endurance"] = "Endurance", ["ls_skill_endurance_description"] = "Increases your maximum stamina.", ["ls_skill_arcane_capacity"] = "Arcane Capacity", ["ls_skill_arcane_capacity_description"] = "Increases your maximum Eitr.", ["ls_skill_carry_weight"] = "Pack Bearer", ["ls_skill_carry_weight_description"] = "Increases your maximum carry weight.", ["ls_skill_critical_chance"] = "Keen Eye", ["ls_skill_critical_chance_description"] = "Grants a chance for direct attacks to become critical hits.", ["ls_skill_critical_damage"] = "Brutal Precision", ["ls_skill_critical_damage_description"] = "Increases critical hit damage beyond its base 50% bonus." }; } private static Dictionary<string, string> Russian() { return new Dictionary<string, string> { ["ls_level"] = "Уровень", ["ls_max_level"] = "МАКС. УРОВЕНЬ", ["ls_remaining_points"] = "Очки навыков: {0}", ["ls_reset_skills"] = "Сбросить навыки", ["ls_category_offensive"] = "Атака", ["ls_category_defensive"] = "Защита", ["ls_category_core"] = "Основа", ["ls_category_utility"] = "Полезное", ["ls_per_rank"] = "+{0}{1} за ранг", ["ls_skill_summon_damage"] = "Воля духов", ["ls_skill_summon_damage_description"] = "Увеличивает урон призванных или следующих за вами существ, включая совместимых существ из других модов.", ["ls_skill_summon_health"] = "Оберег духов", ["ls_skill_summon_health_description"] = "Увеличивает максимальное здоровье призванных или следующих за вами существ.", ["ls_skill_experience_gain"] = "Мудрость", ["ls_skill_experience_gain_description"] = "Увеличивает весь опыт, получаемый в этой системе развития.", ["ls_skill_vitality"] = "Живучесть", ["ls_skill_vitality_description"] = "Увеличивает максимальное здоровье персонажа.", ["ls_skill_weak_hit_immunity"] = "Несокрушимый оберег", ["ls_skill_weak_hit_immunity_description"] = "Полностью отменяет прямой урон враждебных монстров, если после брони и сопротивлений он не превышает указанный порог. По умолчанию не действует на игроков, окружение, периодический урон и боссов.", ["ls_skill_endurance"] = "Выносливость", ["ls_skill_endurance_description"] = "Увеличивает максимальный запас выносливости.", ["ls_skill_arcane_capacity"] = "Магический запас", ["ls_skill_arcane_capacity_description"] = "Увеличивает максимальный запас эйтра.", ["ls_skill_carry_weight"] = "Носильщик", ["ls_skill_carry_weight_description"] = "Увеличивает максимальный переносимый вес.", ["ls_skill_critical_chance"] = "Меткий глаз", ["ls_skill_critical_chance_description"] = "Даёт прямым атакам шанс стать критическими.", ["ls_skill_critical_damage"] = "Смертельная точность", ["ls_skill_critical_damage_description"] = "Увеличивает урон критического удара сверх базового бонуса в 50%." }; } } public class ModResources { private const string AssetsPath = "Assets/_Leveling System/"; private readonly PluginConfig _config; public AssetBundle AssetBundle { get; } public LevelExperienceTable LevelExperienceTable { get; private set; } public XpTable CreatureExperienceTable { get; } public XpTable PickableExperienceTable { get; } public GameObject LevelingSystemUiPrefab { get; private set; } public GameObject SkillUiPrefab { get; private set; } public GameObject LevelUpEffectPrefab { get; private set; } public GameObject CriticalHitEffectPrefab { get; private set; } public GameObject ExperienceTextPrefab { get; private set; } public GameObject CriticalDamageTextPrefab { get; private set; } public ModResources(PluginConfig config) { _config = config; AssetBundle = AssetUtils.LoadAssetBundleFromResources("leveling_system"); PrefabManager.OnVanillaPrefabsAvailable += LoadAssets; int value = _config.MaxLevel.Value; int value2 = _config.InitialMaxExperience.Value; string value3 = _config.MaxExperienceModifierFormula.Value; LevelExperienceTable = new LevelExperienceTable(new LevelTableGenerationSettings(value, value2, value3)); CreatureExperienceTable = new XpTable(Assembly.GetExecutingAssembly(), PluginConfig.CustomCreaturesDirectory, allowGroups: false); PickableExperienceTable = new XpTable(Assembly.GetExecutingAssembly(), PluginConfig.CustomPickablesDirectory, allowGroups: false); } public void SetLevelExperienceTable(LevelExperienceTable experienceTable) { LevelExperienceTable = experienceTable; } private void LoadAssets() { //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Expected O, but got Unknown //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Expected O, but got Unknown LevelingSystemUiPrefab = AssetBundle.LoadAsset<GameObject>("Assets/_Leveling System/Prefabs/LevelingSystemUI.prefab"); LevelingSystemUiPrefab.AddComponent<ProgressionUiController>(); LevelUpEffectPrefab = AssetBundle.LoadAsset<GameObject>("Assets/_Leveling System/Prefabs/LevelUpEffectNew.prefab"); CriticalHitEffectPrefab = AssetBundle.LoadAsset<GameObject>("Assets/_Leveling System/Prefabs/CriticalHitEffect.prefab"); ExperienceTextPrefab = AssetBundle.LoadAsset<GameObject>("Assets/_Leveling System/Prefabs/XPText.prefab"); CriticalDamageTextPrefab = AssetBundle.LoadAsset<GameObject>("Assets/_Leveling System/Prefabs/CritDamageText.prefab"); SkillUiPrefab = AssetBundle.LoadAsset<GameObject>("Assets/_Leveling System/Prefabs/SkillUI.prefab"); GameObject val = AssetBundle.LoadAsset<GameObject>("Assets/_Leveling System/Prefabs/LevelingDummy.prefab"); PieceManager.Instance.AddPiece(new CustomPiece(val, PieceTables.Hammer, false)); GameObject val2 = AssetBundle.LoadAsset<GameObject>("Assets/_Leveling System/Prefabs/LevelingDummyStrawman.prefab"); PieceManager.Instance.AddPiece(new CustomPiece(val2, PieceTables.Hammer, false)); PrefabManager.OnVanillaPrefabsAvailable -= LoadAssets; } } public sealed class PluginConfig { private class ConfigEntryBuilder { private class EntrySettings { public string Section { get; set; } public string Key { get; set; } public ConfigDescription Description { get; set; } } private readonly ConfigFile _configFile; private readonly EntrySettings _entrySettings = new EntrySettings(); private string _description = string.Empty; private AcceptableValueBase _acceptableValues; private ConfigurationManagerAttributes _attributes = new ConfigurationManagerAttributes(); public ConfigEntryBuilder(ConfigFile configFile) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown _configFile = configFile; } public ConfigEntryBuilder SetSection(string section) { _entrySettings.Section = section; return this; } public ConfigEntryBuilder SetKey(string key) { _entrySettings.Key = key; return this; } public ConfigEntryBuilder SetDescription(string description) { _description = description; return this; } public ConfigEntryBuilder SetAcceptableValues(AcceptableValueBase acceptableValues) { _acceptableValues = acceptableValues; return this; } public ConfigEntryBuilder RequireAdmin() { _attributes.IsAdminOnly = true; return this; } public ConfigEntry<TValue> Build<TValue>(TValue defaultValue = default(TValue)) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown _entrySettings.Description = new ConfigDescription(_description, _acceptableValues, new object[1] { _attributes }); ConfigEntry<TValue> result = _configFile.Bind<TValue>(_entrySettings.Section, _entrySettings.Key, defaultValue, _entrySettings.Description); Reset(); return result; } private void Reset() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown _description = string.Empty; _acceptableValues = null; _attributes = new ConfigurationManagerAttributes(); } } private const string CustomFolder = "custom"; private const string CategoriesFolderName = "categories"; private readonly ConfigFile _configFile; public static string CustomMiningDirectory => Path.Combine("custom", "mining"); public static string CustomMiningCategoriesDirectory => Path.Combine("custom", "mining", "categories"); public static string CustomWoodcuttingDirectory => Path.Combine("custom", "woodcutting"); public static string CustomWoodcuttingCategoriesDirectory => Path.Combine("custom", "woodcutting", "categories"); public static string CustomCreaturesDirectory => Path.Combine("custom", "creatures"); public static string CustomPickablesDirectory => Path.Combine("custom", "pickables"); public static string CustomPickablesCategoriesDirectory => Path.Combine("custom", "pickables", "categories"); public static string CustomPlayerDirectory => Path.Combine("custom", "player"); public ConfigEntry<bool> LevelUpVFX { get; } public ConfigEntry<bool> SummonHitVfx { get; } public ConfigEntry<bool> CriticalHitVfx { get; } public ConfigEntry<bool> WeakHitImmunityVfx { get; } public ConfigEntry<bool> ShowScrollbar { get; } public ConfigEntry<float> SkillPointsPerLevel { get; } public ConfigEntry<bool> AllowSkillRefunds { get; } public ConfigEntry<bool> WeakHitImmunityAffectsBosses { get; } public IReadOnlyDictionary<SkillId, SkillTuning> SkillTunings { get; } public ConfigEntry<int> MaxLevel { get; } public ConfigEntry<int> InitialMaxExperience { get; } public ConfigEntry<string> MaxExperienceModifierFormula { get; } public ConfigEntry<float> RestedXpMultiplier { get; } public ConfigEntry<float> NearbyXpRadius { get; } public ConfigEntry<KeyboardShortcut> OpenSkillsMenu { get; } public ConfigEntry<bool> ShowLevel { get; } public ConfigEntry<bool> ShowXp { get; } public ConfigEntry<float> XpBarScale { get; } public ConfigEntry<Vector2> XpBarPosition { get; } public PluginConfig(ConfigFile configFile) { //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_0264: Unknown result type (might be due to invalid IL or missing references) _configFile = configFile; _configFile.SaveOnConfigSet = true; string weakHitMaxRankKey = $"{SkillId.WeakHitImmunity}.MaxRank"; bool flag = File.Exists(_configFile.ConfigFilePath) && File.ReadLines(_configFile.ConfigFilePath).Any((string line) => line.TrimStart(Array.Empty<char>()).StartsWith(weakHitMaxRankKey + " =", StringComparison.Ordinal)); ConfigEntryBuilder configEntryBuilder = new ConfigEntryBuilder(_configFile); ShowLevel = configEntryBuilder.SetSection("HUD").SetKey("ShowLevel").Build(defaultValue: true); ShowXp = configEntryBuilder.SetKey("ShowXp").Build(defaultValue: true); XpBarScale = configEntryBuilder.SetKey("XpBarScale").SetAcceptableValues((AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 100f)).Build(100f); XpBarPosition = configEntryBuilder.SetKey("XpBarPosition").Build<Vector2>(Vector2.zero); ShowScrollbar = configEntryBuilder.SetSection("Menus").SetKey("ShowScrollbar").SetDescription("Unchecking this only disables the graphics, you will still be able to scroll.") .Build(defaultValue: true); MaxLevel = configEntryBuilder.SetSection("Progression").SetKey("MaxLevel").RequireAdmin() .SetAcceptableValues((AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 999)) .Build(50); InitialMaxExperience = configEntryBuilder.SetKey("InitialMaxExperience").RequireAdmin().Build(75); MaxExperienceModifierFormula = configEntryBuilder.SetKey("MaxExperienceModifierFormula").RequireAdmin().Build("0=[10,15%]"); SkillPointsPerLevel = configEntryBuilder.SetKey("SkillPointsPerLevel").RequireAdmin().Build(1f); RestedXpMultiplier = configEntryBuilder.SetKey("RestedXpMultiplier").RequireAdmin().SetAcceptableValues((AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 5f)) .Build(1.2f); NearbyXpRadius = configEntryBuilder.SetKey("NearbyXpRadius").RequireAdmin().SetAcceptableValues((AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 200f)) .Build(50f); AllowSkillRefunds = configEntryBuilder.SetSection("Skills").SetKey("AllowSkillRefunds").RequireAdmin() .Build(defaultValue: true); OpenSkillsMenu = configEntryBuilder.SetSection("Input").SetKey("OpenSkillsMenu").Build<KeyboardShortcut>(new KeyboardShortcut((KeyCode)105, Array.Empty<KeyCode>())); LevelUpVFX = configEntryBuilder.SetSection("VFX").SetKey("LevelUpVFX").Build(defaultValue: true); SummonHitVfx = configEntryBuilder.SetKey("SummonHitVfx").Build(defaultValue: true); CriticalHitVfx = configEntryBuilder.SetKey("CriticalHitVfx").Build(defaultValue: true); WeakHitImmunityVfx = configEntryBuilder.SetKey("WeakHitImmunityVfx").Build(defaultValue: true); WeakHitImmunityAffectsBosses = configEntryBuilder.SetSection("Skills").SetKey("WeakHitImmunityAffectsBosses").SetDescription("Allow Weak Hit Immunity to negate direct attacks from bosses.") .RequireAdmin() .Build(defaultValue: false); SkillTuning skillTuning = BindSkill(configEntryBuilder, SkillId.WeakHitImmunity, 20, 5f); SkillTunings = new Dictionary<SkillId, SkillTuning> { [SkillId.SummonDamage] = BindSkill(configEntryBuilder, SkillId.SummonDamage, 10, 4f), [SkillId.SummonHealth] = BindSkill(configEntryBuilder, SkillId.SummonHealth, 10, 5f), [SkillId.ExperienceGain] = BindSkill(configEntryBuilder, SkillId.ExperienceGain, 10, 2f), [SkillId.Vitality] = BindSkill(configEntryBuilder, SkillId.Vitality, 20, 5f), [SkillId.WeakHitImmunity] = skillTuning, [SkillId.Endurance] = BindSkill(configEntryBuilder, SkillId.Endurance, 20, 5f), [SkillId.ArcaneCapacity] = BindSkill(configEntryBuilder, SkillId.ArcaneCapacity, 20, 5f), [SkillId.CarryWeight] = BindSkill(configEntryBuilder, SkillId.CarryWeight, 20, 7.5f), [SkillId.CriticalChance] = BindSkill(configEntryBuilder, SkillId.CriticalChance, 10, 0.5f), [SkillId.CriticalDamage] = BindSkill(configEntryBuilder, SkillId.CriticalDamage, 10, 5f) }; ConfigEntry<int> val = configEntryBuilder.SetSection("Internal").SetKey("ConfigRevision").Build(0); if (val.Value < 2) { if (flag && skillTuning.MaxRank.Value == 10) { skillTuning.MaxRank.Value = 20; } val.Value = 2; } } private static SkillTuning BindSkill(ConfigEntryBuilder builder, SkillId id, int defaultMaxRank, float defaultBonusPerRank) { ConfigEntry<bool> enabled = builder.SetSection("Skills").SetKey($"{id}.Enabled").RequireAdmin() .Build(defaultValue: true); ConfigEntry<int> maxRank = builder.SetKey($"{id}.MaxRank").RequireAdmin().SetAcceptableValues((AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100)) .Build(defaultMaxRank); ConfigEntry<float> bonusPerRank = builder.SetKey($"{id}.BonusPerRank").RequireAdmin().SetAcceptableValues((AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1000f)) .Build(defaultBonusPerRank); return new SkillTuning(enabled, maxRank, bonusPerRank); } } internal static class PluginDependencyFinder { public static class Guids { public const string SmoothbrainsJewelcrafting = "org.bepinex.plugins.jewelcrafting"; } public static bool CanFind(string pluginGuid) { return Chainloader.PluginInfos.Values.Any((PluginInfo x) => x.Metadata.GUID == pluginGuid); } } public static class PluginInfo { public const string ModName = "LevelingSystem"; public const string Version = "2.1.1"; public const string Guid = "node.deepwolf.LevelingSystem"; } [BepInPlugin("node.deepwolf.LevelingSystem", "LevelingSystem", "2.1.1")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] internal sealed class PluginInitializer : BaseUnityPlugin { private void Awake() { InitCommands(); LocalizationSetup.Register(); PluginConfig config = new PluginConfig(((BaseUnityPlugin)this).Config); SkillCatalog.Initialize(config); ModResources resources = new ModResources(config); PluginRuntime.Initialize(config, resources); Patcher.PatchAll(config, resources); static void InitCommands() { CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new SetLevelCommand()); CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new LevelUpCommand()); } } private void OnDestroy() { Patcher.Unpatch(); } } internal static class PluginRuntime { public static PluginConfig Config { get; private set; } public static ModResources Resources { get; private set; } public static void Initialize(PluginConfig config, ModResources resources) { Config = config; Resources = resources; } } public static class RpcId { private const string Prefix = "node.deepwolf.LevelingSystem!"; public static string Generate(string rpcName) { return "node.deepwolf.LevelingSystem!" + rpcName; } } } namespace CharacterProgressionMod.Commands { public class LevelUpCommand : ConsoleCommand { public override string Name => "level_up"; public override string Help => "Adds progression levels to a connected player. Usage: level_up <playerName|me> <amount>"; public override bool IsCheat => true; public override bool IsNetwork => true; public override bool OnlyServer => true; public override void Run(string[] args) { if (args.Length < 2 || !int.TryParse(args[1], out var result) || result < 1) { SetLevelCommand.Print(((ConsoleCommand)this).Help); return; } if ((Object)(object)ZNet.instance != (Object)null && !ZNet.instance.IsServer() && !ZNet.instance.LocalPlayerIsAdminOrHost()) { SetLevelCommand.Print("Only the server or host can change progression levels."); return; } Player val = SetLevelCommand.FindPlayer(args[0]); PlayerLevelProgression playerLevelProgression = default(PlayerLevelProgression); if ((Object)(object)val == (Object)null || !((Component)val).TryGetComponent<PlayerLevelProgression>(ref playerLevelProgression)) { SetLevelCommand.Print("Player '" + args[0] + "' was not found on this peer."); return; } int num = playerLevelProgression.GetLevel() + result; playerLevelProgression.RequestSetLevel(num); SetLevelCommand.Print($"Requested level {num} for {val.GetPlayerName()}."); } } public class SetLevelCommand : ConsoleCommand { public override string Name => "set_level"; public override string Help => "Sets a connected player's progression level. Usage: set_level <playerName|me> <level>"; public override bool IsCheat => true; public override bool IsNetwork => true; public override bool OnlyServer => true; public override void Run(string[] args) { if (args.Length < 2 || !int.TryParse(args[1], out var result) || result < 1) { Print(((ConsoleCommand)this).Help); return; } if ((Object)(object)ZNet.instance != (Object)null && !ZNet.instance.IsServer() && !ZNet.instance.LocalPlayerIsAdminOrHost()) { Print("Only the server or host can change progression levels."); return; } Player val = FindPlayer(args[0]); PlayerLevelProgression playerLevelProgression = default(PlayerLevelProgression); if ((Object)(object)val == (Object)null || !((Component)val).TryGetComponent<PlayerLevelProgression>(ref playerLevelProgression)) { Print("Player '" + args[0] + "' was not found on this peer."); return; } playerLevelProgression.RequestSetLevel(result); Print($"Requested level {result} for {val.GetPlayerName()}."); } internal static Player FindPlayer(string name) { if (name.Equals("me", StringComparison.OrdinalIgnoreCase)) { return Player.m_localPlayer; } return ((IEnumerable<Player>)Player.GetAllPlayers()).FirstOrDefault((Func<Player, bool>)((Player player) => (Object)(object)player != (Object)null && player.GetPlayerName().Equals(name, StringComparison.OrdinalIgnoreCase))); } internal static void Print(string message) { Console instance = Console.instance; if (instance != null) { instance.Print(message); } } } }