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 Odins Weapon Memories v0.1.1
plugins/OdinsWeaponMemories.dll
Decompiled a day agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("OdinsWeaponMemories")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.1.0")] [assembly: AssemblyInformationalVersion("0.1.1+9e3cfe7ec14bbc94408ce317c554af6a14d4fa95")] [assembly: AssemblyProduct("OdinsWeaponMemories")] [assembly: AssemblyTitle("OdinsWeaponMemories")] [assembly: AssemblyVersion("0.1.1.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 OdinsWeaponMemories { [HarmonyPatch(typeof(Terminal), "InitTerminal")] internal static class Commands { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleEvent <>9__0_0; public static Func<KeyValuePair<string, string>, bool> <>9__1_0; public static Func<string, bool> <>9__1_1; internal void <Postfix>b__0_0(ConsoleEventArgs args) { string[] array = Report(); foreach (string text in array) { Terminal context = args.Context; if (context != null) { context.AddString(text); } Plugin.Log.LogInfo((object)text); } } internal bool <Report>b__1_0(KeyValuePair<string, string> kv) { return WeaponMemory.IsOurKey(kv.Key); } internal bool <Report>b__1_1(string o) { return o != "hevyh2o.valheim.odinsweaponmemories"; } } private static void Postfix() { //IL_0033: 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_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown object obj = <>c.<>9__0_0; if (obj == null) { ConsoleEvent val = delegate(ConsoleEventArgs args) { string[] array = Report(); foreach (string text in array) { Terminal context = args.Context; if (context != null) { context.AddString(text); } Plugin.Log.LogInfo((object)text); } }; <>c.<>9__0_0 = val; obj = (object)val; } new ConsoleCommand("weaponmemory", "Odin's Weapon Memories: show what the mod sees on the weapon in your hands.", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } private static string[] Report() { List<string> list = new List<string>(); list.Add(string.Format("{0} {1}, enabled: {2}, style: {3}", "Odin's Weapon Memories", "0.1.1", Plugin.Enabled.Value, Plugin.NameStyle.Value)); List<string> list2 = list; Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return list2.Concat(new string[1] { "No player loaded." }).ToArray(); } ItemData val = ((Humanoid)localPlayer).GetCurrentWeapon(); if (val == null || !((Humanoid)localPlayer).GetInventory().ContainsItem(val)) { val = ((Humanoid)localPlayer).LeftItem; } if (val == null || !((Humanoid)localPlayer).GetInventory().ContainsItem(val)) { return list2.Concat(new string[1] { "Hold a weapon or shield first." }).ToArray(); } WeaponKind weaponKind = WeaponKinds.Of(val); list2.Add($"Item: {Localization.instance.Localize(val.m_shared.m_name)} (quality {val.m_quality}), kind {weaponKind}, " + "switched " + (WeaponKinds.IsEnabled(weaponKind) ? "on" : "OFF") + " in [Weapons]"); list2.Add("Vanilla crafter: " + (string.IsNullOrEmpty(val.m_crafterName) ? "(none)" : val.m_crafterName)); List<KeyValuePair<string, string>> list3 = val.m_customData?.Where((KeyValuePair<string, string> kv) => WeaponMemory.IsOurKey(kv.Key)).ToList(); if (list3 == null || list3.Count == 0) { list2.Add("No memories stored on this item."); } else { foreach (KeyValuePair<string, string> item in list3) { list2.Add(" " + item.Key + " = " + ((item.Value.Length > 200) ? (item.Value.Substring(0, 200) + "...") : item.Value)); } WeaponMemory weaponMemory = WeaponMemory.Read(val); list2.Add($"Maker: {weaponMemory.MakerName}, namer: {weaponMemory.NamerName}, kills: {weaponMemory.All.Kills} (namer's {weaponMemory.Namer.Kills})"); } list2.Add("Shown as: " + (Names.Display(val) ?? "(its plain name)")); MethodBase[] array = new MethodBase[3] { AccessTools.Method(typeof(ItemData), "GetTooltip", new Type[6] { typeof(ItemData), typeof(int), typeof(bool), typeof(float), typeof(int), typeof(bool) }, (Type[])null), AccessTools.Method(typeof(InventoryGrid), "CreateItemTooltip", (Type[])null, (Type[])null), AccessTools.Method(typeof(ItemDrop), "GetHoverText", (Type[])null, (Type[])null) }; foreach (MethodBase methodBase in array) { Patches patchInfo = Harmony.GetPatchInfo(methodBase); List<string> list4 = ((patchInfo != null) ? patchInfo.Owners.Where((string o) => o != "hevyh2o.valheim.odinsweaponmemories").ToList() : null); if (list4 != null && list4.Count > 0) { list2.Add("Also patched by other mods: " + methodBase.DeclaringType.Name + "." + methodBase.Name + " <- " + string.Join(", ", list4)); } } return list2.ToArray(); } } [HarmonyPatch(typeof(InventoryGui), "DoCrafting")] internal static class CraftingPatch { internal struct State { public ItemData OldItem; public Dictionary<string, string> Memories; public long MakerId; public string MakerName; public HashSet<ItemData> ItemsBefore; } private static void Prefix(Player player, ItemData ___m_craftUpgradeItem, out State __state) { __state = default(State); if ((Object)(object)player == (Object)null) { return; } __state.ItemsBefore = new HashSet<ItemData>(((Humanoid)player).GetInventory().GetAllItems()); if (___m_craftUpgradeItem != null) { __state.OldItem = ___m_craftUpgradeItem; __state.Memories = ___m_craftUpgradeItem.m_customData?.Where((KeyValuePair<string, string> kv) => WeaponMemory.IsOurKey(kv.Key)).ToDictionary((KeyValuePair<string, string> kv) => kv.Key, (KeyValuePair<string, string> kv) => kv.Value) ?? new Dictionary<string, string>(); WeaponMemory weaponMemory = WeaponMemory.Read(___m_craftUpgradeItem); bool flag = weaponMemory != null && !string.IsNullOrEmpty(weaponMemory.MakerName); __state.MakerId = (flag ? weaponMemory.MakerId : ___m_craftUpgradeItem.m_crafterID); __state.MakerName = (flag ? weaponMemory.MakerName : ___m_craftUpgradeItem.m_crafterName); } } private static void Postfix(Player player, State __state) { if ((Object)(object)player == (Object)null || __state.ItemsBefore == null) { return; } Inventory inventory = ((Humanoid)player).GetInventory(); ItemData oldItem = __state.OldItem; if (oldItem != null) { if (inventory.ContainsItem(oldItem)) { return; } ItemData itemAt = inventory.GetItemAt(oldItem.m_gridPos.x, oldItem.m_gridPos.y); if (itemAt == null || itemAt == oldItem || itemAt.m_shared.m_name != oldItem.m_shared.m_name || WeaponMemory.Has(itemAt) || (__state.Memories.Count == 0 && !WeaponMemory.CanRemember(itemAt))) { return; } if (itemAt.m_customData == null) { itemAt.m_customData = new Dictionary<string, string>(); } foreach (KeyValuePair<string, string> memory in __state.Memories) { itemAt.m_customData[memory.Key] = memory.Value; } WeaponMemory.StampMaker(itemAt, __state.MakerId, __state.MakerName); if (Plugin.LogKills.Value) { Plugin.Log.LogInfo((object)$"Carried memories of {oldItem.m_shared.m_name} across the upgrade to quality {itemAt.m_quality}; engraved by {__state.MakerName}."); } return; } foreach (ItemData allItem in inventory.GetAllItems()) { if (!__state.ItemsBefore.Contains(allItem) && WeaponMemory.CanRemember(allItem) && !string.IsNullOrEmpty(allItem.m_crafterName)) { WeaponMemory.StampMaker(allItem, allItem.m_crafterID, allItem.m_crafterName); if (Plugin.LogKills.Value) { Plugin.Log.LogInfo((object)("Engraved " + allItem.m_crafterName + " on a new " + allItem.m_shared.m_name + ".")); } } } } } [HarmonyPatch(typeof(ItemData), "GetTooltip", new Type[] { typeof(ItemData), typeof(int), typeof(bool), typeof(float), typeof(int), typeof(bool) })] internal static class TooltipPatch { private static void Postfix(ItemData item, bool crafting, ref string __result) { if (!crafting && item != null) { string text = Epithets.TooltipSection(item); if (text != null) { __result += text; } } } } [HarmonyPatch(typeof(InventoryGrid), "CreateItemTooltip")] internal static class InventoryTitlePatch { private static void Postfix(ItemData item, UITooltip tooltip, RectTransform ___m_tooltipAnchor) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) string text = Names.Display(item); if (text != null && (Object)(object)tooltip != (Object)null) { tooltip.Set(text, tooltip.m_text, ___m_tooltipAnchor, default(Vector2)); } } } [HarmonyPatch(typeof(ItemDrop), "GetHoverText")] internal static class GroundHoverTextPatch { private static void Postfix(ItemDrop __instance, ref string __result) { ItemData itemData = __instance.m_itemData; string text = Names.Display(itemData); if (text == null || string.IsNullOrEmpty(__result)) { return; } string text2 = Localization.instance.Localize(itemData.m_shared.m_name); if (!__result.StartsWith(text2)) { return; } int num = text2.Length; string text3 = ""; if (num < __result.Length && __result[num] == '[') { int num2 = __result.IndexOf(']', num); if (num2 > 0) { text3 = " " + __result.Substring(num, num2 + 1 - num); num = num2 + 1; } } __result = text + text3 + __result.Substring(num).TrimStart(new char[1] { ' ' }); } } [HarmonyPatch(typeof(ItemDrop), "GetHoverName")] internal static class GroundHoverNamePatch { private static void Postfix(ItemDrop __instance, ref string __result) { __result = Names.Display(__instance.m_itemData) ?? __result; } } internal enum NameStyle { Legend, Epithet } internal enum LaterBearers { KeepNamersName, EvolveWithNewBearer } internal static class Names { internal static string Display(ItemData item) { if (!Plugin.Enabled.Value || !WeaponMemory.CanRemember(item)) { return null; } WeaponMemory weaponMemory = WeaponMemory.Read(item); if (weaponMemory != null) { return Announced(item, weaponMemory); } return null; } internal static string Announced(ItemData item, WeaponMemory memory) { if (Plugin.NameStyle.Value == NameStyle.Legend) { return Legend.For(item, memory); } string text = Epithets.Headline(memory.All); if (text != null) { return Localization.instance.Localize(item.m_shared.m_name) + ", " + text; } return null; } } internal static class Epithets { private struct Tier { public int Kills; public string Title; } internal static Func<string, string> Localize = (string text) => (Localization.instance == null) ? text : Localization.instance.Localize(text); private static List<Tier> Tiers() { List<Tier> list = new List<Tier>(3); Add(list, Plugin.Tier1Kills.Value, Plugin.Tier1Title.Value); Add(list, Plugin.Tier2Kills.Value, Plugin.Tier2Title.Value); Add(list, Plugin.Tier3Kills.Value, Plugin.Tier3Title.Value); list.Sort((Tier a, Tier b) => a.Kills.CompareTo(b.Kills)); return list; } private static void Add(List<Tier> tiers, int kills, string title) { if (kills > 0 && !string.IsNullOrWhiteSpace(title)) { tiers.Add(new Tier { Kills = kills, Title = title }); } } internal static int TierFor(int kills) { return Tiers().Count((Tier t) => kills >= t.Kills); } internal static string Title(int tier, string creature) { List<Tier> list = Tiers(); if (tier <= 0 || tier > list.Count) { return null; } string text = CreatureName(creature); try { return string.Format(list[tier - 1].Title, text); } catch (FormatException) { return list[tier - 1].Title.Replace("{0}", text); } } internal static string CreatureName(string creature) { return Localize(creature); } internal static string Headline(Tally t) { string creature = null; int num = 0; foreach (KeyValuePair<string, int> item in t.Ranked("kill:")) { int num2 = TierFor(item.Value); if (num2 > num) { num = num2; creature = item.Key; } } if (num <= 0) { return null; } return Title(num, creature); } internal static string TooltipSection(ItemData item) { if (!Plugin.Enabled.Value || !WeaponMemory.CanRemember(item)) { return null; } WeaponMemory weaponMemory = WeaponMemory.Read(item); if (weaponMemory == null) { return null; } Tally all = weaponMemory.All; bool flag = WeaponKinds.Of(item) == WeaponKind.Shields; bool flag2 = Plugin.NameStyle.Value == NameStyle.Legend; StringBuilder stringBuilder = new StringBuilder(); if (all.IsEmpty) { string text = (Plugin.ShowEngraving.Value ? Legend.Engraving(item, weaponMemory) : null); if (text != null) { return "\n\n<i>" + text + "</i>"; } return null; } stringBuilder.Append("\n\n<color=#D9A441>Weapon memories</color>"); if (flag2 && Names.Display(item) != null) { stringBuilder.Append("\nForged as: <color=orange>").Append(Localization.instance.Localize(item.m_shared.m_name)).Append("</color>"); } if (flag) { stringBuilder.Append("\nBlows turned aside: <color=orange>").Append(all["block"]).Append("</color>"); AppendFoes(stringBuilder, item, all.Ranked("blocked:"), legend: true); AppendDeeds(stringBuilder, all); AppendBearers(stringBuilder, item, weaponMemory, flag2); return stringBuilder.ToString(); } stringBuilder.Append("\nKills: <color=orange>").Append(all.Kills).Append("</color>"); int num = all["attack"]; if (num >= 10) { int num2 = Math.Min(all["attackhit"], num); stringBuilder.Append(" Struck true: <color=orange>").Append(num2 * 100 / num).Append("%</color>"); } AppendFoes(stringBuilder, item, all.Ranked("kill:"), flag2); AppendDeeds(stringBuilder, all); if (!flag2) { string text2 = NextGoal(all); if (text2 != null) { stringBuilder.Append("\nNext: ").Append(text2); } } AppendBearers(stringBuilder, item, weaponMemory, flag2); return stringBuilder.ToString(); } private static void AppendFoes(StringBuilder sb, ItemData item, List<KeyValuePair<string, int>> ranked, bool legend) { int num = Math.Min(ranked.Count, Plugin.TopFoes.Value); for (int i = 0; i < num; i++) { KeyValuePair<string, int> keyValuePair = ranked[i]; sb.Append("\n ").Append(CreatureName(keyValuePair.Key)).Append(": <color=orange>") .Append(keyValuePair.Value) .Append("</color>"); int num2 = TierFor(keyValuePair.Value); if (num2 > 0 && !legend) { sb.Append(" <color=#D9A441>").Append(Title(num2, keyValuePair.Key)).Append("</color>"); } float num3 = DamageMultiplier(item, keyValuePair.Key) - 1f; if (num3 > 0f) { sb.Append(" <color=#8FBF6A>+").Append(Math.Round(num3 * 100f, 1)).Append("% dmg</color>"); } } if (ranked.Count > num) { sb.Append("\n and ").Append(ranked.Count - num).Append((ranked.Count - num == 1) ? " other kind" : " other kinds"); } } private static void AppendDeeds(StringBuilder sb, Tally t) { string text = Feats(t); if (text != null) { sb.Append("\nDeeds: ").Append(text); } } private static string Feats(Tally t) { List<string> list = new List<string>(); foreach (KeyValuePair<string, int> item in t.Ranked("boss:")) { list.Add("felled " + CreatureName(item.Key)); } foreach (KeyValuePair<string, int> item2 in t.Ranked("wardboss:")) { list.Add("stood against " + CreatureName(item2.Key)); } Count(list, t["sneak"], "unseen kill", "unseen kills"); Count(list, t["oneshot"], "single-blow kill", "single-blow kills"); Count(list, t["clutch"], "kill at death's door", "kills at death's door"); Count(list, t["starred"], "starred foe", "starred foes"); if (t["longshot"] >= 20) { list.Add("longest shot " + t["longshot"] + " m"); } Count(list, t["parry"], "perfect parry", "perfect parries"); Count(list, t["block"], "block", "blocks"); Count(list, t["chop"], "axe-stroke on wood", "axe-strokes on wood"); Count(list, t["mine"], "strike on stone", "strikes on stone"); Count(list, t["death"], "bearer fallen", "bearers fallen"); if (list.Count != 0) { return string.Join(", ", list); } return null; } private static void Count(List<string> parts, int n, string one, string many) { if (n > 0) { parts.Add(n + " " + ((n == 1) ? one : many)); } } private static void AppendBearers(StringBuilder sb, ItemData item, WeaponMemory memory, bool legend) { if (Plugin.ShowEngraving.Value) { string text = Legend.Engraving(item, memory); if (text != null) { sb.Append("\n<i>").Append(text).Append("</i>"); } } if (!legend || string.IsNullOrEmpty(memory.NamerName)) { return; } string text2 = Legend.NameFrom(WeaponProfile.Of(item), memory.MakerName, memory.Namer); if (memory.NamerName != memory.MakerName && text2 != null) { sb.Append("\nNamed in the hands of <color=orange>").Append(memory.NamerName).Append("</color>"); } if (Plugin.LaterBearers.Value == LaterBearers.KeepNamersName) { int num = memory.All.Kills - memory.Namer.Kills; if (num > 0 && text2 != null) { sb.Append("\nKills since it left ").Append(memory.NamerName).Append("'s hands: <color=orange>") .Append(num) .Append("</color>"); } } else if (text2 != null && text2 != Legend.For(item, memory)) { sb.Append("\nOnce called <color=orange>").Append(text2).Append("</color>"); } } private static string NextGoal(Tally t) { List<Tier> list = Tiers(); string text = null; int tier = 0; int num = int.MaxValue; foreach (KeyValuePair<string, int> item in t.Ranked("kill:")) { int num2 = TierFor(item.Value); if (num2 < list.Count) { int num3 = list[num2].Kills - item.Value; if (num3 < num) { num = num3; text = item.Key; tier = num2 + 1; } } } if (text == null) { return null; } return Title(tier, text) + " in " + num + ((num == 1) ? " kill" : " kills"); } internal static float DamageMultiplier(ItemData item, string creature) { if (!Plugin.Enabled.Value || !Plugin.DamageBonus.Value) { return 1f; } WeaponMemory weaponMemory = WeaponMemory.Read(item); if (weaponMemory == null) { return 1f; } int num = TierFor(weaponMemory.All.KillsOf(creature)); return 1f + (float)num * Plugin.DamageBonusPerTier.Value / 100f; } } internal static class KillTracker { private sealed class Pending { public Character Target; public ZNetView View; public ItemData Weapon; public string Creature; public string Biome; public bool Boss; public bool Starred; public bool Unseen; public bool FromFullHealth; public int Distance; public float Time; public bool LooksLethal; public bool OwnedHere; } internal enum SourceKind { Melee, Projectile, Splash } internal sealed class Source { public ItemData Item; public SourceKind Kind; public bool StruckSomething; } private const float RemoteKillWindow = 4f; private const float ForgetAfter = 120f; private const float ClutchHealth = 0.2f; private static readonly Dictionary<ZDOID, Pending> PendingHits = new Dictionary<ZDOID, Pending>(); private static Source _source; internal static Source Enter(Character owner, ItemData item, SourceKind kind) { Source source = _source; _source = new Source { Item = (((Object)(object)owner != (Object)null && (Object)(object)owner == (Object)(object)Player.m_localPlayer) ? item : null), Kind = kind }; return source; } internal static void Exit(Source previous) { Source source = _source; _source = previous; if (source?.Item == null) { return; } if (previous != null && previous.Item == source.Item) { previous.StruckSomething |= source.StruckSomething; } if (source.Kind == SourceKind.Melee) { bool struck = source.StruckSomething; WeaponMemory.Record(source.Item, delegate(Tally t) { t.Add("attack"); if (struck) { t.Add("attackhit"); } }); } else if (source.Kind == SourceKind.Projectile && source.StruckSomething) { WeaponMemory.Record(source.Item, delegate(Tally t) { t.Add("attackhit"); }); } } private static ItemData WeaponBehind(HitData hit) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if (!Plugin.Enabled.Value || hit == null || (Object)(object)localPlayer == (Object)null || hit.m_attacker != ((Character)localPlayer).GetZDOID()) { return null; } ItemData val = ((_source != null) ? _source.Item : CurrentWeaponFor(localPlayer, hit)); if (!WeaponMemory.CanRemember(val) || !((Humanoid)localPlayer).GetInventory().ContainsItem(val)) { return null; } if (_source != null) { _source.StruckSomething = true; } return val; } private static ItemData CurrentWeaponFor(Player local, HitData hit) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) ItemData currentWeapon = ((Humanoid)local).GetCurrentWeapon(); if (currentWeapon == null || currentWeapon.m_shared.m_skillType != hit.m_skill) { return null; } return currentWeapon; } internal static void OnHit(Character target, HitData hit) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)target == (Object)null || target.IsPlayer()) { return; } ItemData val = WeaponBehind(hit); string text = Tally.CleanKey(target.m_name); ZDOID zDOID = target.GetZDOID(); if (val != null && text != null && !((ZDOID)(ref zDOID)).IsNone()) { float num = Epithets.DamageMultiplier(val, text); if (num > 1f) { hit.ApplyModifier(num); } Player localPlayer = Player.m_localPlayer; BaseAI baseAI = target.GetBaseAI(); PendingHits[zDOID] = new Pending { Target = target, View = ((Component)target).GetComponent<ZNetView>(), Weapon = val, Creature = text, Biome = ((object)Heightmap.FindBiome(((Component)target).transform.position)/*cast due to .constrained prefix*/).ToString(), Boss = target.IsBoss(), Starred = (target.GetLevel() > 1), Unseen = ((Object)(object)baseAI != (Object)null && !baseAI.IsAlerted()), FromFullHealth = (target.GetHealth() >= target.GetMaxHealth() - 0.5f), Distance = (hit.m_ranged ? Mathf.RoundToInt(Vector3.Distance(((Component)localPlayer).transform.position, ((Component)target).transform.position)) : 0), Time = Time.time, LooksLethal = LooksLethal(target, hit), OwnedHere = target.IsOwner() }; WeaponMemory.Record(val, delegate(Tally t) { t.Add("hit"); }); } } internal static void OnWorkHit(HitData hit, string stat) { ItemData val = WeaponBehind(hit); if (val != null) { WeaponMemory.Record(val, delegate(Tally t) { t.Add(stat); }); } } internal static void OnShot(Humanoid shooter, ItemData weapon) { if (Plugin.Enabled.Value && (Object)(object)shooter != (Object)null && (Object)(object)shooter == (Object)(object)Player.m_localPlayer) { WeaponMemory.Record(weapon, delegate(Tally t) { t.Add("attack"); }); } } private static bool LooksLethal(Character target, HitData hit) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0063: 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_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) float health = target.GetHealth(); if (health <= 0f) { return true; } HitData val = hit.Clone(); DamageModifier val2 = default(DamageModifier); val.ApplyResistance(target.GetDamageModifiers((WeakSpot)null), ref val2); if ((Object)(object)Game.instance != (Object)null) { val.ApplyModifier(Game.instance.GetDifficultyDamageScaleEnemy(((Component)target).transform.position) * Game.m_playerDamageRate); } DamageTypes damage = val.m_damage; float num = ((DamageTypes)(ref damage)).GetTotalDamage() - damage.m_fire - damage.m_poison - damage.m_spirit; BaseAI baseAI = target.GetBaseAI(); if ((Object)(object)baseAI != (Object)null && !baseAI.IsAlerted() && hit.m_backstabBonus > 1f) { num *= hit.m_backstabBonus; } if (target.IsStaggering()) { num *= 2f; } return num >= health * 0.9f; } internal static void OnDeath(Character dead, HitData lastHit, ZNetView view) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)dead == (Object)null || dead.IsPlayer() || (Object)(object)view == (Object)null || !view.IsValid() || !view.IsOwner()) { return; } ZDOID zDOID = dead.GetZDOID(); if (!PendingHits.TryGetValue(zDOID, out var value)) { return; } PendingHits.Remove(zDOID); Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || lastHit == null || lastHit.m_attacker != ((Character)localPlayer).GetZDOID()) { if (Plugin.LogKills.Value) { Plugin.Log.LogInfo((object)(dead.m_name + " died to someone else's blow; not counted.")); } } else { Credit(value, "killing blow"); } } internal static void Tick() { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) if (PendingHits.Count == 0) { return; } if ((Object)(object)Player.m_localPlayer == (Object)null) { PendingHits.Clear(); return; } float time = Time.time; foreach (KeyValuePair<ZDOID, Pending> item in PendingHits.ToList()) { Pending value = item.Value; if ((Object)(object)value.Target == (Object)null) { PendingHits.Remove(item.Key); if (!value.OwnedHere && value.LooksLethal && time - value.Time <= 4f) { Credit(value, "lethal hit, creature owned by another client"); } continue; } if ((Object)(object)value.View != (Object)null && value.View.IsValid()) { value.OwnedHere = value.View.IsOwner(); } if (time - value.Time > 120f) { PendingHits.Remove(item.Key); } } } private static void Credit(Pending p, string how) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } bool clutch = ((Character)localPlayer).GetHealth() <= ((Character)localPlayer).GetMaxHealth() * 0.2f; bool night = EnvMan.IsNight(); bool oneShot = p.FromFullHealth && p.LooksLethal; WeaponMemory.Record(p.Weapon, delegate(Tally t) { t.Add("kill:" + p.Creature); t.Add("biome:" + p.Biome); if (p.Boss) { t.Add("boss:" + p.Creature); } if (p.Starred) { t.Add("starred"); } if (p.Unseen) { t.Add("sneak"); } if (oneShot) { t.Add("oneshot"); } if (clutch) { t.Add("clutch"); } if (night) { t.Add("night"); } if (p.Distance > 0) { t.Max("longshot", p.Distance); } }); if (Plugin.LogKills.Value) { Plugin.Log.LogInfo((object)(p.Weapon.m_shared.m_name + " remembers " + p.Creature + " in " + p.Biome + " (" + how + ")" + (p.Boss ? ", boss" : "") + (p.Unseen ? ", unseen" : "") + (oneShot ? ", one blow" : "") + (clutch ? ", at death's door" : "") + (night ? ", night" : "") + ((p.Distance > 0) ? $", {p.Distance} m" : "") + ".")); } } internal static void OnBearerDeath(Player player) { if (Plugin.Enabled.Value && !((Object)(object)player == (Object)null) && !((Object)(object)player != (Object)(object)Player.m_localPlayer)) { WeaponMemory.Record(((Humanoid)player).GetCurrentWeapon(), delegate(Tally t) { t.Add("death"); }); } } internal static void OnBlocked(Humanoid blocker, ItemData item, bool perfect, Character attacker) { //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.Enabled.Value || (Object)(object)blocker == (Object)null || (Object)(object)blocker != (Object)(object)Player.m_localPlayer) { return; } string foe = (((Object)(object)attacker != (Object)null && !attacker.IsPlayer()) ? Tally.CleanKey(attacker.m_name) : null); bool boss = foe != null && attacker.IsBoss(); string biome = ((object)Heightmap.FindBiome(((Component)blocker).transform.position)/*cast due to .constrained prefix*/).ToString(); WeaponMemory.Record(item, delegate(Tally t) { t.Add("block"); t.Add("wardbiome:" + biome); if (perfect) { t.Add("parry"); } if (foe != null) { t.Add("blocked:" + foe); } if (boss) { t.Add("wardboss:" + foe); } }); } } [HarmonyPatch(typeof(Character), "Damage")] internal static class CharacterDamagePatch { private static void Prefix(Character __instance, HitData hit) { KillTracker.OnHit(__instance, hit); } } [HarmonyPatch(typeof(Character), "OnDeath")] internal static class CharacterOnDeathPatch { private static void Prefix(Character __instance, HitData ___m_lastHit, ZNetView ___m_nview) { KillTracker.OnDeath(__instance, ___m_lastHit, ___m_nview); } } [HarmonyPatch(typeof(Player), "OnDeath")] internal static class PlayerOnDeathPatch { private static void Prefix(Player __instance, ZNetView ___m_nview) { if ((Object)(object)___m_nview != (Object)null && ___m_nview.IsValid() && ___m_nview.IsOwner()) { KillTracker.OnBearerDeath(__instance); } } } [HarmonyPatch(typeof(Humanoid), "BlockAttack")] internal static class BlockPatch { private static readonly FieldRef<Humanoid, ItemData> LeftItem = AccessTools.FieldRefAccess<Humanoid, ItemData>("m_leftItem"); private static ItemData Blocker(Humanoid h) { return LeftItem.Invoke(h) ?? h.GetCurrentWeapon(); } private static void Prefix(Humanoid __instance, float ___m_blockTimer, out bool __state) { ItemData val = Blocker(__instance); __state = val != null && val.m_shared.m_timedBlockBonus > 1f && ___m_blockTimer != -1f && ___m_blockTimer < 0.25f; } private static void Postfix(Humanoid __instance, Character attacker, bool __result, bool __state) { if (__result) { KillTracker.OnBlocked(__instance, Blocker(__instance), __state, attacker); } } } [HarmonyPatch(typeof(TreeBase), "Damage")] internal static class TreeHitPatch { private static void Prefix(HitData hit) { KillTracker.OnWorkHit(hit, "chop"); } } [HarmonyPatch(typeof(TreeLog), "Damage")] internal static class LogHitPatch { private static void Prefix(HitData hit) { KillTracker.OnWorkHit(hit, "chop"); } } [HarmonyPatch(typeof(MineRock), "Damage")] internal static class RockHitPatch { private static void Prefix(HitData hit) { KillTracker.OnWorkHit(hit, "mine"); } } [HarmonyPatch(typeof(MineRock5), "Damage")] internal static class BigRockHitPatch { private static void Prefix(HitData hit) { KillTracker.OnWorkHit(hit, "mine"); } } [HarmonyPatch(typeof(Attack), "DoMeleeAttack")] internal static class MeleeAttackPatch { private static void Prefix(Humanoid ___m_character, ItemData ___m_weapon, out KillTracker.Source __state) { __state = KillTracker.Enter((Character)(object)___m_character, ___m_weapon, KillTracker.SourceKind.Melee); } private static void Finalizer(KillTracker.Source __state) { KillTracker.Exit(__state); } } [HarmonyPatch(typeof(Attack), "DoAreaAttack")] internal static class AreaAttackPatch { private static void Prefix(Humanoid ___m_character, ItemData ___m_weapon, out KillTracker.Source __state) { __state = KillTracker.Enter((Character)(object)___m_character, ___m_weapon, KillTracker.SourceKind.Melee); } private static void Finalizer(KillTracker.Source __state) { KillTracker.Exit(__state); } } [HarmonyPatch(typeof(Attack), "FireProjectileBurst")] internal static class FireProjectilePatch { private static void Prefix(Humanoid ___m_character, ItemData ___m_weapon) { KillTracker.OnShot(___m_character, ___m_weapon); } } [HarmonyPatch(typeof(Projectile), "OnHit")] internal static class ProjectileHitPatch { private static void Prefix(Character ___m_owner, ItemData ___m_weapon, out KillTracker.Source __state) { __state = KillTracker.Enter(___m_owner, ___m_weapon, KillTracker.SourceKind.Projectile); } private static void Finalizer(KillTracker.Source __state) { KillTracker.Exit(__state); } } [HarmonyPatch(typeof(Projectile), "DoAOE")] internal static class ProjectileAoePatch { private static void Prefix(Character ___m_owner, ItemData ___m_weapon, out KillTracker.Source __state) { __state = KillTracker.Enter(___m_owner, ___m_weapon, KillTracker.SourceKind.Splash); } private static void Finalizer(KillTracker.Source __state) { KillTracker.Exit(__state); } } [HarmonyPatch(typeof(Aoe), "OnHit", new Type[] { typeof(Collider), typeof(Vector3) })] internal static class AoeHitPatch { private static void Prefix(Character ___m_owner, ItemData ___m_itemData, out KillTracker.Source __state) { __state = KillTracker.Enter(___m_owner, ___m_itemData, KillTracker.SourceKind.Splash); } private static void Finalizer(KillTracker.Source __state) { KillTracker.Exit(__state); } } internal static class Legend { private static readonly (int Kills, string Word)[] Renown = new(int, string)[5] { (500, "Legendary"), (200, "Fabled"), (75, "Storied"), (25, "Seasoned"), (5, "Blooded") }; private static readonly (int Blocks, string Word)[] ShieldRenown = new(int, string)[5] { (1000, "Legendary"), (400, "Fabled"), (150, "Storied"), (50, "Battered"), (10, "Dented") }; private static readonly string[] Prey = new string[7] { "$enemy_deer", "$enemy_boar", "$enemy_boarpiggy", "$enemy_neck", "$enemy_hare", "$enemy_chicken", "$enemy_hen" }; private static readonly string[] Engravings = new string[4] { "A faint engraving reads \"{0}\"", "A strange inscription: \"{0}\"", "Scratched runes spell out \"{0}\"", "Worn letters near the grip read \"{0}\"" }; internal static string For(ItemData item, WeaponMemory memory) { Tally t = ((Plugin.LaterBearers.Value == LaterBearers.KeepNamersName) ? memory.Namer : memory.All); return NameFrom(WeaponProfile.Of(item), memory.MakerName, t); } internal static string NameFrom(WeaponProfile w, string maker, Tally t) { bool flag = w.Kind == WeaponKind.Shields; string text = (flag ? ShieldPrefix(t) : Prefix(w, t)); string text2 = (flag ? ShieldSuffix(t) : Suffix(t)); if (text == null && text2 == null) { return null; } string text3 = (flag ? LandWord(t.Ranked("wardbiome:"), 25, t["block"]) : Element(w, t)); return string.Join(" ", new string[4] { text, text3, BaseWord(w, Seed(w, maker)), text2 }.Where((string s) => !string.IsNullOrEmpty(s))); } internal static bool HasEarnedName(ItemData item, Tally t) { if (!t.IsEmpty) { return NameFrom(WeaponProfile.Of(item), "", t) != null; } return false; } private static string Prefix(WeaponProfile w, Tally t) { int kills = t.Kills; if (kills == 0) { if (t["hit"] + t["chop"] + t["mine"] + t["block"] < 10) { return null; } return "Unblooded"; } string item = Renown.FirstOrDefault(((int Kills, string Word) r) => kills >= r.Kills).Word; if (kills >= 200) { return item; } return Trait(w, t, kills) ?? item; } private static string Trait(WeaponProfile w, Tally t, int kills) { int num = t["attack"]; int num2 = t["attackhit"]; bool flag = w.Kind == WeaponKind.ElementalMagic || w.Kind == WeaponKind.BloodMagic; if (t["death"] >= 3 && t["death"] * 5 >= kills) { return "Ill-fated"; } if (t["clutch"] >= 5) { return "Defiant"; } if (t["parry"] >= 25) { return "Unyielding"; } if (kills >= 20 && t["sneak"] * 4 >= kills) { return "Silent"; } if (kills >= 20 && t["oneshot"] * 4 >= kills) { return "Merciless"; } if (!flag && num >= 50 && num2 * 100 >= num * 85) { return "Unerring"; } if (!flag && num >= 50 && num2 * 100 < num * 35) { return "Wayward"; } if (kills >= 20 && t["night"] * 10 >= kills * 6) { return "Moonlit"; } if (kills >= 20 && t["starred"] * 2 >= kills) { return "Proven"; } if (t["block"] >= 50) { return "Steadfast"; } return null; } private static string ShieldPrefix(Tally t) { int blocks = t["block"]; string item = ShieldRenown.FirstOrDefault(((int Blocks, string Word) r) => blocks >= r.Blocks).Word; if (blocks >= 400) { return item; } if (t["death"] >= 3 && t["death"] * 20 >= blocks) { return "Ill-fated"; } if (t["parry"] >= 25 && t["parry"] * 3 >= blocks) { return "Unyielding"; } return item; } private static string Element(WeaponProfile w, Tally t) { (float, string) tuple = new(float, string)[5] { (w.Fire, "Ember"), (w.Frost, "Frost"), (w.Lightning, "Storm"), (w.Poison, "Venom"), (w.Spirit, "Hallowed") }.OrderByDescending(((float Amount, string Word) e) => e.Amount).First(); if (tuple.Item1 > 0f) { return tuple.Item2; } return LandWord(t.Ranked("biome:"), 10, t.Kills); } private static string LandWord(List<KeyValuePair<string, int>> biomes, int atLeast, int total) { if (biomes.Count == 0 || biomes[0].Value < atLeast || biomes[0].Value * 2 < total) { return null; } return biomes[0].Key switch { "Meadows" => "Verdant", "BlackForest" => "Shadow", "Swamp" => "Mire", "Mountain" => "Rime", "Plains" => "Sunscorched", "Mistlands" => "Mist", "AshLands" => "Ashen", "DeepNorth" => "Glacial", "Ocean" => "Tidal", _ => null, }; } private static string BaseWord(WeaponProfile w, uint seed) { string[] array; switch (w.Kind) { case WeaponKind.Swords: array = ((!w.TwoHanded) ? new string[3] { "Sword", "Blade", "Brand" } : new string[2] { "Greatsword", "Greatblade" }); break; case WeaponKind.Knives: array = new string[3] { "Knife", "Seax", "Dagger" }; break; case WeaponKind.Clubs: array = ((!w.TwoHanded) ? new string[3] { "Club", "Mace", "Cudgel" } : new string[2] { "Maul", "Sledge" }); break; case WeaponKind.Axes: array = ((!w.TwoHanded) ? new string[3] { "Axe", "Hewer", "Bearded Axe" } : new string[2] { "Greataxe", "Battleaxe" }); break; case WeaponKind.Spears: array = new string[2] { "Spear", "Javelin" }; break; case WeaponKind.Polearms: array = new string[3] { "Atgeir", "Halberd", "Glaive" }; break; case WeaponKind.Bows: array = new string[3] { "Bow", "Longbow", "Warbow" }; break; case WeaponKind.Crossbows: array = new string[2] { "Crossbow", "Arbalest" }; break; case WeaponKind.ElementalMagic: array = new string[2] { "Staff", "Rod" }; break; case WeaponKind.BloodMagic: array = new string[2] { "Staff", "Sceptre" }; break; case WeaponKind.Pickaxes: array = new string[2] { "Pick", "Pickaxe" }; break; case WeaponKind.Unarmed: array = new string[2] { "Knuckles", "Claws" }; break; case WeaponKind.Torches: array = new string[2] { "Torch", "Brand" }; break; case WeaponKind.Shields: array = ShieldWords(w.Prefab); break; default: return w.PlainName; } return array[seed % (uint)array.Length]; } private static string[] ShieldWords(string prefab) { string text = (prefab ?? "").ToLowerInvariant(); if (!text.Contains("tower")) { if (!text.Contains("buckler")) { return new string[3] { "Shield", "Targe", "Linden-board" }; } return new string[2] { "Buckler", "Targe" }; } return new string[2] { "Tower Shield", "Wall" }; } private static string Suffix(Tally t) { List<KeyValuePair<string, int>> list = t.Ranked("boss:"); if (list.Count > 0) { return "of " + Epithets.CreatureName(list[0].Key) + "'s Fall"; } int num = t["hit"]; if (t["chop"] >= 100 && t["chop"] >= num * 3) { if (t["chop"] < 1000) { return "of the Woodpile"; } return "of the Felled Forest"; } if (t["mine"] >= 100 && t["mine"] >= num * 3) { if (t["mine"] < 1000) { return "of the Quarry"; } return "of the Deep Delve"; } int kills = t.Kills; int num2 = Prey.Sum((string p) => t.KillsOf(p)); int num3 = Epithets.TierFor(num2); if (num3 > 0 && num2 * 10 >= kills * 6) { if (num3 < 3) { if (num3 != 2) { return "of the Hunt"; } return "of the Long Hunt"; } return "of the Wild Hunt"; } int bestTier; string text = BestByTier(t, "kill:", out bestTier); if (text != null) { string text2 = Epithets.CreatureName(text); if (bestTier < 3) { if (bestTier != 2) { return "of " + text2 + "-Felling"; } return "of the " + text2 + "'s Bane"; } return "of the " + text2 + "-Scourge"; } if (t["longshot"] >= 60) { return "of the Far Mark"; } if (t["parry"] >= 50) { return "of the Turned Blade"; } if (t["death"] >= 10) { return "of Many Graves"; } return null; } private static string ShieldSuffix(Tally t) { List<KeyValuePair<string, int>> list = t.Ranked("wardboss:"); if (list.Count > 0) { return "of the " + Epithets.CreatureName(list[0].Key) + "-Ward"; } int bestTier; string text = BestByTier(t, "blocked:", out bestTier); if (text != null) { string text2 = Epithets.CreatureName(text); if (bestTier < 3) { if (bestTier != 2) { return "of the " + text2 + "-Ward"; } return "of the " + text2 + "-Wall"; } return "of the " + text2 + "'s Despair"; } if (t["parry"] >= 50) { return "of the Turned Blade"; } if (t["block"] >= 500) { return "of the Unbroken Wall"; } if (t["death"] >= 10) { return "of Many Graves"; } return null; } private static string BestByTier(Tally t, string prefix, out int bestTier) { string result = null; bestTier = 0; foreach (KeyValuePair<string, int> item in t.Ranked(prefix)) { int num = Epithets.TierFor(item.Value); if (num > bestTier) { bestTier = num; result = item.Key; } } return result; } internal static string Engraving(ItemData item, WeaponMemory memory) { if (!string.IsNullOrEmpty(memory.MakerName)) { return EngravingFor(WeaponProfile.Of(item), memory.MakerName); } return null; } internal static string EngravingFor(WeaponProfile w, string maker) { return string.Format(Engravings[Seed(w, maker) / 7 % (uint)Engravings.Length], maker); } private static uint Seed(WeaponProfile w, string maker) { string text = maker + "/" + w.Prefab; uint num = 2166136261u; string text2 = text; foreach (char c in text2) { num ^= c; num *= 16777619; } return num; } } [BepInPlugin("hevyh2o.valheim.odinsweaponmemories", "Odin's Weapon Memories", "0.1.1")] public class Plugin : BaseUnityPlugin { public const string Guid = "hevyh2o.valheim.odinsweaponmemories"; public const string Name = "Odin's Weapon Memories"; public const string Version = "0.1.1"; internal static ManualLogSource Log; internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<bool> AnnounceEpithets; internal static ConfigEntry<int> TopFoes; internal static ConfigEntry<NameStyle> NameStyle; internal static ConfigEntry<LaterBearers> LaterBearers; internal static ConfigEntry<bool> ShowEngraving; internal static ConfigEntry<int> Tier1Kills; internal static ConfigEntry<string> Tier1Title; internal static ConfigEntry<int> Tier2Kills; internal static ConfigEntry<string> Tier2Title; internal static ConfigEntry<int> Tier3Kills; internal static ConfigEntry<string> Tier3Title; internal static ConfigEntry<bool> DamageBonus; internal static ConfigEntry<float> DamageBonusPerTier; internal static ConfigEntry<bool> LogKills; internal static readonly Dictionary<WeaponKind, ConfigEntry<bool>> KindEnabled = new Dictionary<WeaponKind, ConfigEntry<bool>>(); private Harmony _harmony; private void Awake() { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; BindConfig(((BaseUnityPlugin)this).Config); _harmony = new Harmony("hevyh2o.valheim.odinsweaponmemories"); _harmony.PatchAll(typeof(Plugin).Assembly); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Odin's Weapon Memories 0.1.1 loaded. Odin is listening to the blades."); } internal static void BindConfig(ConfigFile config) { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005d: 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 //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Expected O, but got Unknown //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_0182: Expected O, but got Unknown //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Expected O, but got Unknown Enabled = config.Bind<bool>("General", "Enabled", true, "Turn the whole mod on or off. When off, weapons stop counting and show no epithets, but the memories already on them are kept."); AnnounceEpithets = config.Bind<bool>("General", "AnnounceEpithets", true, "Show a message in the middle of the screen when a weapon earns a new name or epithet."); TopFoes = config.Bind<int>("General", "TopFoes", 3, new ConfigDescription("How many kinds of creature the tooltip lists, most kills first.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 8), Array.Empty<object>())); NameStyle = config.Bind<NameStyle>("Naming", "NameStyle", OdinsWeaponMemories.NameStyle.Legend, "Legend: the weapon earns a whole name from how it was used, e.g. \"Storied Frost Greataxe of the Troll's Bane\". Epithet: the item keeps its name with one epithet added, e.g. \"Iron Sword, Troll-Feller\"."); LaterBearers = config.Bind<LaterBearers>("Naming", "LaterBearers", OdinsWeaponMemories.LaterBearers.KeepNamersName, "What happens to a legend name when the weapon changes hands. KeepNamersName: it stays as its first bearer left it; new bearers' deeds are still counted and shown. EvolveWithNewBearer: everyone's deeds keep shaping the name, and the tooltip remembers what its first bearer called it. The maker's engraving stays either way."); ShowEngraving = config.Bind<bool>("Naming", "ShowEngraving", true, "Show the maker's name as an engraving in the tooltip. The maker is the crafter, or the first bearer for found weapons."); Tier1Kills = config.Bind<int>("Epithets", "Tier1Kills", 10, new ConfigDescription("Kills of one kind of creature for the first tier: \"X-Felling\" in legend names, Tier1Title in epithet style. 0 turns this tier off.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100000), Array.Empty<object>())); Tier1Title = config.Bind<string>("Epithets", "Tier1Title", "{0}-Feller", "First epithet, used when NameStyle is Epithet. {0} is the creature's name, e.g. \"{0}-Feller\" gives \"Troll-Feller\"."); Tier2Kills = config.Bind<int>("Epithets", "Tier2Kills", 50, new ConfigDescription("Kills of one kind of creature needed for the second epithet. 0 turns this tier off.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100000), Array.Empty<object>())); Tier2Title = config.Bind<string>("Epithets", "Tier2Title", "Bane of the {0}", "Second epithet, used when NameStyle is Epithet. {0} is the creature's name."); Tier3Kills = config.Bind<int>("Epithets", "Tier3Kills", 150, new ConfigDescription("Kills of one kind of creature needed for the third epithet. 0 turns this tier off.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100000), Array.Empty<object>())); Tier3Title = config.Bind<string>("Epithets", "Tier3Title", "Scourge of the {0}", "Third epithet, used when NameStyle is Epithet. {0} is the creature's name."); DamageBonus = config.Bind<bool>("Bonus", "DamageBonus", false, "Off by default, so the mod stays purely cosmetic. When on, a weapon hits a little harder against kinds of creature it has earned an epithet for."); DamageBonusPerTier = config.Bind<float>("Bonus", "DamageBonusPerTier", 2f, new ConfigDescription("Extra damage in percent per epithet tier earned against that kind of creature (so 2 gives +2%, +4%, +6%). Only used when DamageBonus is on.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 5f), Array.Empty<object>())); LogKills = config.Bind<bool>("Debug", "LogKills", false, "Write a line to the BepInEx log for every kill a weapon remembers. Handy for testing."); BindKind(config, WeaponKind.Swords, on: true, "Swords"); BindKind(config, WeaponKind.Axes, on: true, "Axes and battleaxes, including woodcutting axes"); BindKind(config, WeaponKind.Clubs, on: true, "Clubs, maces and sledges"); BindKind(config, WeaponKind.Spears, on: true, "Spears (stabbing; thrown spears leave your hands and aren't counted)"); BindKind(config, WeaponKind.Polearms, on: true, "Atgeirs and other polearms"); BindKind(config, WeaponKind.Knives, on: true, "Knives and daggers"); BindKind(config, WeaponKind.Shields, on: true, "Shields, which remember the blows they turned aside rather than kills"); BindKind(config, WeaponKind.Bows, on: true, "Bows"); BindKind(config, WeaponKind.Crossbows, on: true, "Crossbows"); BindKind(config, WeaponKind.ElementalMagic, on: true, "Elemental magic staffs"); BindKind(config, WeaponKind.BloodMagic, on: true, "Blood magic staffs"); BindKind(config, WeaponKind.Pickaxes, on: false, "Pickaxes"); BindKind(config, WeaponKind.Unarmed, on: false, "Fist weapons (knuckles, claws)"); BindKind(config, WeaponKind.Torches, on: false, "Torches"); BindKind(config, WeaponKind.Other, on: false, "Any other weapon, e.g. from other mods, that trains a skill not listed here"); } private static void BindKind(ConfigFile config, WeaponKind kind, bool on, string what) { KindEnabled[kind] = config.Bind<bool>("Weapons", kind.ToString(), on, what + ": remember their deeds and earn names. When off they stop counting and show their plain name; memories already on them are kept."); } private void Update() { KillTracker.Tick(); } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } internal enum WeaponKind { None, Swords, Axes, Clubs, Spears, Polearms, Knives, Shields, Bows, Crossbows, ElementalMagic, BloodMagic, Pickaxes, Unarmed, Torches, Other } internal struct WeaponProfile { public WeaponKind Kind; public bool TwoHanded; public float Fire; public float Frost; public float Lightning; public float Poison; public float Spirit; public string Prefab; public string PlainName; internal static WeaponProfile Of(ItemData item) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Invalid comparison between Unknown and I4 //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) DamageTypes damage = item.GetDamage(); return new WeaponProfile { Kind = WeaponKinds.Of(item), TwoHanded = ((int)item.m_shared.m_itemType == 14), Fire = damage.m_fire, Frost = damage.m_frost, Lightning = damage.m_lightning, Poison = damage.m_poison, Spirit = damage.m_spirit, Prefab = (((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : item.m_shared.m_name), PlainName = Localization.instance.Localize(item.m_shared.m_name) }; } } internal static class WeaponKinds { internal static WeaponKind Of(ItemData item) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Invalid comparison between Unknown and I4 //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Invalid comparison between Unknown and I4 //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Invalid comparison between Unknown and I4 //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Invalid comparison between Unknown and I4 //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Invalid comparison between Unknown and I4 //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Expected I4, but got Unknown //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Invalid comparison between Unknown and I4 if (item?.m_shared == null) { return WeaponKind.None; } ItemType itemType = item.m_shared.m_itemType; if ((int)itemType <= 5) { if (itemType - 3 > 1) { if ((int)itemType != 5) { goto IL_0042; } return WeaponKind.Shields; } } else if ((int)itemType != 14) { if ((int)itemType == 15) { return WeaponKind.Torches; } if ((int)itemType != 22) { goto IL_0042; } } if (item.m_shared.m_maxStackSize > 1) { return WeaponKind.None; } SkillType skillType = item.m_shared.m_skillType; return (skillType - 1) switch { 0 => WeaponKind.Swords, 6 => WeaponKind.Axes, 2 => WeaponKind.Clubs, 4 => WeaponKind.Spears, 3 => WeaponKind.Polearms, 1 => WeaponKind.Knives, 7 => WeaponKind.Bows, 13 => WeaponKind.Crossbows, 8 => WeaponKind.ElementalMagic, 9 => WeaponKind.BloodMagic, 11 => WeaponKind.Pickaxes, 10 => WeaponKind.Unarmed, _ => WeaponKind.Other, }; IL_0042: return WeaponKind.None; } internal static bool IsEnabled(WeaponKind kind) { if (kind != WeaponKind.None && Plugin.KindEnabled.TryGetValue(kind, out var value)) { return value.Value; } return false; } } internal static class Stat { internal const string Kill = "kill:"; internal const string Biome = "biome:"; internal const string Boss = "boss:"; internal const string Hit = "hit"; internal const string Attack = "attack"; internal const string AttackHit = "attackhit"; internal const string Chop = "chop"; internal const string Mine = "mine"; internal const string Death = "death"; internal const string Sneak = "sneak"; internal const string OneShot = "oneshot"; internal const string Clutch = "clutch"; internal const string Night = "night"; internal const string Starred = "starred"; internal const string LongShot = "longshot"; internal const string Block = "block"; internal const string Parry = "parry"; internal const string Blocked = "blocked:"; internal const string WardBiome = "wardbiome:"; internal const string WardBoss = "wardboss:"; } internal sealed class Tally { private const string FormatVersion = "v2"; private readonly Dictionary<string, int> _counts = new Dictionary<string, int>(StringComparer.Ordinal); internal int this[string key] { get { if (!_counts.TryGetValue(key, out var value)) { return 0; } return value; } } internal bool IsEmpty => _counts.Count == 0; internal int Kills => Sum("kill:"); internal void Add(string key, int n = 1) { _counts[key] = this[key] + n; } internal void Max(string key, int value) { if (value > this[key]) { _counts[key] = value; } } internal List<KeyValuePair<string, int>> Ranked(string prefix) { return (from kv in _counts where kv.Key.StartsWith(prefix, StringComparison.Ordinal) select new KeyValuePair<string, int>(kv.Key.Substring(prefix.Length), kv.Value) into kv orderby kv.Value descending select kv).ThenBy<KeyValuePair<string, int>, string>((KeyValuePair<string, int> kv) => kv.Key, StringComparer.Ordinal).ToList(); } internal int Sum(string prefix) { return _counts.Where((KeyValuePair<string, int> kv) => kv.Key.StartsWith(prefix, StringComparison.Ordinal)).Sum((KeyValuePair<string, int> kv) => kv.Value); } internal int KillsOf(string creature) { return this["kill:" + creature]; } internal string Serialize() { StringBuilder stringBuilder = new StringBuilder("v2"); foreach (KeyValuePair<string, int> item in _counts.OrderBy<KeyValuePair<string, int>, string>((KeyValuePair<string, int> kv) => kv.Key, StringComparer.Ordinal)) { stringBuilder.Append('|').Append(item.Key).Append('=') .Append(item.Value); } return stringBuilder.ToString(); } internal static Tally Parse(string raw) { Tally tally = new Tally(); if (string.IsNullOrEmpty(raw)) { return tally; } string[] array = raw.Split(new char[1] { '|' }); string text = ((array[0] == "v1") ? "kill:" : ((array[0] == "v2") ? "" : null)); if (text == null) { return tally; } for (int i = 1; i < array.Length; i++) { int num = array[i].LastIndexOf('='); if (num > 0 && int.TryParse(array[i].Substring(num + 1), out var result) && result > 0) { tally._counts[text + array[i].Substring(0, num)] = result; } } return tally; } internal static string CleanKey(string name) { if (!string.IsNullOrEmpty(name)) { return name.Replace("|", "").Replace("=", "").Trim(); } return null; } } internal sealed class WeaponMemory { internal const string DataKey = "hevyh2o.weaponmemories"; private const string NamerKey = "hevyh2o.weaponmemories.namer"; private const string WhoKey = "hevyh2o.weaponmemories.who"; internal Tally All = new Tally(); internal Tally Namer = new Tally(); internal long MakerId; internal string MakerName = ""; internal long NamerId; internal string NamerName = ""; internal static bool CanRemember(ItemData item) { return WeaponKinds.IsEnabled(WeaponKinds.Of(item)); } internal static bool Has(ItemData item) { if (item?.m_customData != null) { return item.m_customData.ContainsKey("hevyh2o.weaponmemories"); } return false; } internal static bool IsOurKey(string key) { if (!(key == "hevyh2o.weaponmemories")) { return key.StartsWith("hevyh2o.weaponmemories.", StringComparison.Ordinal); } return true; } internal static WeaponMemory Read(ItemData item) { if (!Has(item)) { return null; } Dictionary<string, string> customData = item.m_customData; WeaponMemory weaponMemory = new WeaponMemory { All = Tally.Parse(customData["hevyh2o.weaponmemories"]) }; if (customData.TryGetValue("hevyh2o.weaponmemories.namer", out var value)) { weaponMemory.Namer = Tally.Parse(value); } if (customData.TryGetValue("hevyh2o.weaponmemories.who", out var value2)) { string[] array = value2.Split(new char[1] { '|' }); if (array.Length == 4) { long.TryParse(array[0], out weaponMemory.MakerId); weaponMemory.MakerName = array[1]; long.TryParse(array[2], out weaponMemory.NamerId); weaponMemory.NamerName = array[3]; } } else { weaponMemory.Namer = Tally.Parse(customData["hevyh2o.weaponmemories"]); } return weaponMemory; } internal void Write(ItemData item) { if (item.m_customData == null) { item.m_customData = new Dictionary<string, string>(); } item.m_customData["hevyh2o.weaponmemories"] = All.Serialize(); item.m_customData["hevyh2o.weaponmemories.namer"] = Namer.Serialize(); item.m_customData["hevyh2o.weaponmemories.who"] = string.Join("|", MakerId.ToString(), Clean(MakerName), NamerId.ToString(), Clean(NamerName)); } private static string Clean(string name) { return (name ?? "").Replace("|", ""); } internal static void StampMaker(ItemData item, long makerId, string makerName) { if (!string.IsNullOrEmpty(makerName)) { WeaponMemory weaponMemory = Read(item) ?? new WeaponMemory(); if (string.IsNullOrEmpty(weaponMemory.MakerName)) { weaponMemory.MakerId = makerId; weaponMemory.MakerName = makerName; weaponMemory.Write(item); } } } internal static void Record(ItemData weapon, Action<Tally> change) { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && CanRemember(weapon) && ((Humanoid)localPlayer).GetInventory().ContainsItem(weapon)) { WeaponMemory weaponMemory = Read(weapon) ?? new WeaponMemory(); string text = Names.Announced(weapon, weaponMemory); long playerID = localPlayer.GetPlayerID(); string playerName = localPlayer.GetPlayerName(); if (string.IsNullOrEmpty(weaponMemory.MakerName)) { bool flag = !string.IsNullOrEmpty(weapon.m_crafterName); weaponMemory.MakerId = (flag ? weapon.m_crafterID : playerID); weaponMemory.MakerName = (flag ? weapon.m_crafterName : playerName); } if (weaponMemory.NamerId == 0L) { weaponMemory.NamerId = playerID; weaponMemory.NamerName = playerName; } else if (weaponMemory.NamerId != playerID && !Legend.HasEarnedName(weapon, weaponMemory.Namer)) { weaponMemory.NamerId = playerID; weaponMemory.NamerName = playerName; weaponMemory.Namer = new Tally(); } change(weaponMemory.All); if (weaponMemory.NamerId == playerID) { change(weaponMemory.Namer); } weaponMemory.Write(weapon); string text2 = Names.Announced(weapon, weaponMemory); if (text2 != null && text2 != text && Plugin.AnnounceEpithets.Value) { ((Character)localPlayer).Message((MessageType)2, "Odin names your weapon\n" + text2, 0, (Sprite)null, false); } } } } internal static class BuildInfo { public const string Version = "0.1.1"; } }