using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using BepInEx; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyVersion("0.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace LevelingSystemSharingXP { [BepInPlugin("deloolv.valheim.levelingsystemsharingxp.patch", "LevelingSystem_SharingXP_Patch", "1.0.6")] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class CozyheimNearbyXpPlugin : BaseUnityPlugin { [HarmonyPatch(typeof(Character), "OnDeath")] private static class NearbyMonsterXpPatch { private static void Prefix(Character __instance) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null || (Object)(object)Player.m_localPlayer == (Object)null || !CanTargetAwardXp(__instance)) { return; } Player localPlayer = Player.m_localPlayer; if (!((Character)localPlayer).IsDead() && !(Vector3.Distance(((Component)localPlayer).transform.position, ((Component)__instance).transform.position) > 50f)) { string monsterName = NormalizeName(((Object)__instance).name); int monsterXp = GetMonsterXp(monsterName); if (monsterXp > 0) { AwardMonsterXp(__instance, monsterXp); } } } } private const float NearbyPlayerXpRadius = 50f; private static readonly Harmony Harmony = new Harmony("deloolv.valheim.levelingsystemsharingxp.patch"); private static readonly Dictionary TextTranslations = new Dictionary { ["Level"] = "Уровень", ["Levels"] = "Уровни", ["Skill"] = "Навык", ["Skills"] = "Навыки", ["Remaining Points"] = "Свободные очки", ["Remaining points:"] = "Свободные очки:", ["Reset Skill Points"] = "Сбросить очки навыков", ["Close Menu"] = "Закрыть", ["Core"] = "Основные", ["Defensive"] = "Защита", ["Offensive"] = "Атака", ["Utility"] = "Полезные", ["Total Bonus"] = "Общий бонус", ["Reset"] = "Сброс", ["Max Level"] = "Максимальный уровень", ["Health"] = "Здоровье", ["Health Regen"] = "Регенерация здоровья", ["Stamina"] = "Выносливость", ["Stamina Regen"] = "Регенерация выносливости", ["Eitr"] = "Эйтр", ["Eitr Regen"] = "Регенерация эйтра", ["Carry Weight"] = "Грузоподъёмность", ["Movement Speed"] = "Скорость движения", ["Woodcutting"] = "Рубка леса", ["Mining"] = "Добыча руды", ["Physical Damage"] = "Физический урон", ["Elemental Damage"] = "Стихийный урон", ["Physical Resistance"] = "Физическое сопротивление", ["Elemental Resistance"] = "Стихийное сопротивление", ["Critical Hit Chance"] = "Шанс критического удара", ["Critical Hit Damage"] = "Критический урон", ["Blunt Resistance"] = "Сопротивление дробящему урону", ["Fire Resistance"] = "Сопротивление огню", ["Frost Resistance"] = "Сопротивление холоду", ["Lightning Resistance"] = "Сопротивление молнии", ["Pierce Resistance"] = "Сопротивление колющему урону", ["Poison Resistance"] = "Сопротивление яду", ["Slash Resistance"] = "Сопротивление рубящему урону", ["Spirit Resistance"] = "Сопротивление духу" }; private static Type _xpManagerType; private static Type _xpTableType; private static Type _uiManagerType; private static Type _mainType; private static Type _xpTypeEnum; private static MethodInfo _getMonsterXp; private static MethodInfo _addExperience; private static MethodInfo _spawnFloatingXpText; private static FieldInfo _uiManagerInstance; private float _nextUiTranslationAt; private void Awake() { ((MonoBehaviour)this).StartCoroutine(ApplyPatchesWhenReady()); } private void OnDestroy() { Harmony.UnpatchSelf(); } private void Update() { if (!(Time.unscaledTime < _nextUiTranslationAt)) { _nextUiTranslationAt = Time.unscaledTime + 0.5f; TranslateAllUiTexts(); } } private static IEnumerator ApplyPatchesWhenReady() { while (!ResolveLevelingSystemTypes()) { yield return null; } Harmony.PatchAll(typeof(CozyheimNearbyXpPlugin).Assembly); PatchOriginalMonsterReward(); PatchRussianLocalization(); TranslateAllUiTexts(); } private static bool ResolveLevelingSystemTypes() { _xpManagerType = AccessTools.TypeByName("Cozyheim.LevelingSystem.XPManager"); _xpTableType = AccessTools.TypeByName("Cozyheim.LevelingSystem.XPTable"); _uiManagerType = AccessTools.TypeByName("Cozyheim.LevelingSystem.UIManager"); _mainType = AccessTools.TypeByName("Cozyheim.LevelingSystem.Main"); _xpTypeEnum = AccessTools.TypeByName("XPType"); if (_xpManagerType == null || _xpTableType == null || _uiManagerType == null || _mainType == null || _xpTypeEnum == null) { return false; } _getMonsterXp = AccessTools.Method(_xpTableType, "GetMonsterXP", (Type[])null, (Type[])null); _addExperience = AccessTools.Method(_uiManagerType, "AddExperience", (Type[])null, (Type[])null); _spawnFloatingXpText = AccessTools.Method(_uiManagerType, "SpawnFloatingXPText", (Type[])null, (Type[])null); _uiManagerInstance = AccessTools.Field(_uiManagerType, "Instance"); return _getMonsterXp != null && _addExperience != null && _spawnFloatingXpText != null && _uiManagerInstance != null; } private static void PatchOriginalMonsterReward() { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(_xpManagerType, "RPC_RewardXPMonsters", (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(CozyheimNearbyXpPlugin), "SkipOriginalMonsterReward", (Type[])null, (Type[])null); Harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } private static void PatchRussianLocalization() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown HarmonyMethod val = new HarmonyMethod(AccessTools.Method(typeof(CozyheimNearbyXpPlugin), "TranslateStringConstants", (Type[])null, (Type[])null)); Harmony.Patch((MethodBase)_addExperience, (HarmonyMethod)null, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null); PatchDeclaredMethodsWithTranspiler(AccessTools.TypeByName("Cozyheim.LevelingSystem.XPText"), val); PatchDeclaredMethodsWithTranspiler(AccessTools.TypeByName("Cozyheim.LevelingSystem.ConsoleLog"), val); } private static void PatchDeclaredMethodsWithTranspiler(Type type, HarmonyMethod transpiler) { if (type == null) { return; } foreach (MethodInfo declaredMethod in AccessTools.GetDeclaredMethods(type)) { if (!(declaredMethod == null) && !declaredMethod.IsAbstract && !declaredMethod.ContainsGenericParameters && declaredMethod.GetMethodBody() != null) { try { Harmony.Patch((MethodBase)declaredMethod, (HarmonyMethod)null, (HarmonyMethod)null, transpiler, (HarmonyMethod)null, (HarmonyMethod)null); } catch { } } } } private static IEnumerable TranslateStringConstants(IEnumerable instructions) { string text = default(string); foreach (CodeInstruction instruction in instructions) { int num; if (instruction.opcode == OpCodes.Ldstr) { object operand = instruction.operand; text = operand as string; num = ((text != null) ? 1 : 0); } else { num = 0; } if (num != 0) { instruction.operand = TranslateText(text); } yield return instruction; text = null; } } private static bool SkipOriginalMonsterReward(ref IEnumerator __result) { __result = EmptyEnumerator(); return false; } private static IEnumerator EmptyEnumerator() { yield break; } private static bool CanTargetAwardXp(Character target) { return !target.IsPlayer() && GetMonsterXp(NormalizeName(((Object)target).name)) > 0; } private static string NormalizeName(string name) { return name.Replace("(Clone)", "").Trim(); } private static int GetMonsterXp(string monsterName) { return (int)_getMonsterXp.Invoke(null, new object[1] { monsterName }); } private static void AwardMonsterXp(Character target, int baseXp) { object value = _uiManagerInstance.GetValue(null); if (value != null) { float num = Mathf.Min(1f - GetConfigFloat("baseXpSpreadMin") / 100f, 1f); float num2 = Mathf.Max(1f + GetConfigFloat("baseXpSpreadMax") / 100f, 1f); float num3 = Mathf.Max(0f, GetConfigFloat("allXPMultiplier") / 100f); float num4 = Mathf.Max(0f, GetConfigFloat("monsterLvlXPMultiplier") / 100f); float num5 = Mathf.Max(0f, GetConfigFloat("restedXPMultiplier") / 100f); int num6 = Mathf.RoundToInt((float)baseXp * Random.Range(num, num2) * num3); int xp = Mathf.RoundToInt((float)(target.GetLevel() - 1) * num4 * (float)num6); int xp2 = (IsLocalPlayerRested() ? Mathf.RoundToInt((float)num6 * num5) : 0); int totalXp = 0; AddExperience(value, num6, "Regular", ref totalXp); AddExperience(value, xp, "MonsterLevel", ref totalXp); AddExperience(value, xp2, "Rested", ref totalXp); if (totalXp > 0 && GetConfigBool("displayMonsterXPText")) { _spawnFloatingXpText.Invoke(value, new object[1] { totalXp }); } } } private static void AddExperience(object uiManager, int xp, string xpTypeName, ref int totalXp) { if (xp > 0) { object obj = Enum.Parse(_xpTypeEnum, xpTypeName); _addExperience.Invoke(uiManager, new object[2] { xp, obj }); totalXp += xp; } } private static bool IsLocalPlayerRested() { int num = (int)AccessTools.Field(_uiManagerType, "s_statusEffectRested").GetValue(null); return (Object)(object)((Character)Player.m_localPlayer).GetSEMan().GetStatusEffect(num) != (Object)null; } private static float GetConfigFloat(string fieldName) { object value = AccessTools.Field(_mainType, fieldName).GetValue(null); return Convert.ToSingle(value.GetType().GetProperty("Value").GetValue(value, null)); } private static bool GetConfigBool(string fieldName) { object value = AccessTools.Field(_mainType, fieldName).GetValue(null); return Convert.ToBoolean(value.GetType().GetProperty("Value").GetValue(value, null)); } private static void TranslateAllUiTexts() { Text[] array = Resources.FindObjectsOfTypeAll(); foreach (Text val in array) { if (!((Object)(object)val == (Object)null) && !string.IsNullOrEmpty(val.text)) { string text = TranslateText(val.text); if (text != val.text) { val.text = text; } } } } private static string TranslateText(string text) { if (string.IsNullOrEmpty(text)) { return text; } if (TextTranslations.TryGetValue(text.Trim(), out var value)) { return PreserveEdgeWhitespace(text, value); } return text.Replace("You gained +", "Получено +").Replace("-> Monster level bonus: +", "-> Бонус уровня монстра: +").Replace("-> Rested bonus: +", "-> Бонус отдыха: +") .Replace("Only admins are allowed to use this command", "Только администраторы могут использовать эту команду") .Replace("The command doesn't exist:", "Такой команды не существует:") .Replace("* Incorrect Arguments *", "* Неверные аргументы *") .Replace("Admin has set your level to {0}", "Администратор установил ваш уровень на {0}") .Replace("invalid level number", "Указан неверный уровень") .Replace("Invalid level number", "Указан неверный уровень") .Replace("Level ", "Уровень ") .Replace(" / Level", " / уровень") .Replace("/ Level", "/ уровень") .Replace(" to level", " до уровня") .Replace("% damage", "% урона") .Replace("+{xp} XP", "+{xp} опыта") .Replace(" xp", " опыта") .Replace("XP", "опыт"); } private static string PreserveEdgeWhitespace(string original, string translated) { int length = original.Length - original.TrimStart(Array.Empty()).Length; int num = original.Length - original.TrimEnd(Array.Empty()).Length; return original.Substring(0, length) + translated + original.Substring(original.Length - num, num); } } }