using Comfort.Common; using EFT.HandBook; using EFT.InventoryLogic; using EFT.UI.DragAndDrop; using System; using System.Collections.Generic; using System.IO; using System.Reflection; using UnityEngine; using UnityEngine.UI; namespace IcyClawz.MunitionsExpert { internal enum EAmmoExtraAttributeId { Damage, PenetrationPower, ArmorDamage, FragmentationChance, RicochetChance } internal static class ImageExtensions { public static Sprite ToSprite(this System.Drawing.Image instance) { byte[] data; using (MemoryStream ms = new MemoryStream()) { instance.Save(ms, System.Drawing.Imaging.ImageFormat.Png); data = ms.ToArray(); } Texture2D texture = new Texture2D(instance.Width, instance.Height); ImageConversion.LoadImage(texture, data); return Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.zero); } } internal static class IconCache { private static readonly Dictionary Cache = new Dictionary(5); public static void Initialize() { Cache.Add(EAmmoExtraAttributeId.Damage, Properties.Resources.Damage.ToSprite()); Cache.Add(EAmmoExtraAttributeId.PenetrationPower, Properties.Resources.PenetrationPower.ToSprite()); Cache.Add(EAmmoExtraAttributeId.ArmorDamage, Properties.Resources.ArmorDamage.ToSprite()); Cache.Add(EAmmoExtraAttributeId.FragmentationChance, Properties.Resources.FragmentationChance.ToSprite()); Cache.Add(EAmmoExtraAttributeId.RicochetChance, Properties.Resources.RicochetChance.ToSprite()); } public static Sprite Get(Enum id) => id is EAmmoExtraAttributeId extraId ? Cache[extraId] : null; } internal static class AmmoTemplateExtensions { private static readonly FieldInfo CachedQualitiesField = typeof(AmmoTemplate).GetField("_cachedQualities", BindingFlags.NonPublic | BindingFlags.Instance); public static List GetCachedQualities(this AmmoTemplate instance) => CachedQualitiesField.GetValue(instance) as List; public static void AddExtraAttributes(this AmmoTemplate instance) { instance.SafelyAddQualityToList(new ItemAttributeClass(EAmmoExtraAttributeId.Damage) { Name = EAmmoExtraAttributeId.Damage.ToString(), DisplayNameFunc = () => "Damage", Base = () => instance.Damage * instance.ProjectileCount, StringValue = () => { int totalDamage = instance.Damage * instance.ProjectileCount; if (instance.ProjectileCount > 1) { return $"{instance.ProjectileCount} × {instance.Damage} = {totalDamage}"; } else { return totalDamage.ToString(); } }, DisplayType = () => EItemAttributeDisplayType.Compact }); instance.SafelyAddQualityToList(new ItemAttributeClass(EAmmoExtraAttributeId.PenetrationPower) { Name = EAmmoExtraAttributeId.PenetrationPower.ToString(), DisplayNameFunc = () => "Penetration power", Base = () => instance.PenetrationPower, StringValue = () => { int armorClass = instance.GetPenetrationArmorClass(); string armorClassStr = armorClass > 0 ? $"Class {armorClass}" : "Unarmored"; return $"{armorClassStr} ({instance.PenetrationPower})"; }, DisplayType = () => EItemAttributeDisplayType.Compact }); instance.SafelyAddQualityToList(new ItemAttributeClass(EAmmoExtraAttributeId.ArmorDamage) { Name = EAmmoExtraAttributeId.ArmorDamage.ToString(), DisplayNameFunc = () => "Armor damage", Base = () => instance.ArmorDamage, StringValue = () => $"{instance.ArmorDamage}%", DisplayType = () => EItemAttributeDisplayType.Compact }); instance.SafelyAddQualityToList(new ItemAttributeClass(EAmmoExtraAttributeId.FragmentationChance) { Name = EAmmoExtraAttributeId.FragmentationChance.ToString(), DisplayNameFunc = () => "Fragmentation chance", Base = () => instance.FragmentationChance, StringValue = () => $"{Math.Round(instance.FragmentationChance * 100, 1)}%", DisplayType = () => EItemAttributeDisplayType.Compact }); instance.SafelyAddQualityToList(new ItemAttributeClass(EAmmoExtraAttributeId.RicochetChance) { Name = EAmmoExtraAttributeId.RicochetChance.ToString(), DisplayNameFunc = () => "Ricochet chance", Base = () => instance.RicochetChance, StringValue = () => $"{Math.Round(instance.RicochetChance * 100, 1)}%", DisplayType = () => EItemAttributeDisplayType.Compact }); } public static int GetPenetrationArmorClass(this AmmoTemplate instance) { var armorClasses = Singleton.Instance.Armor.ArmorClass; for (int i = armorClasses.Length - 1; i >= 0; i--) { if (armorClasses[i].Resistance <= instance.PenetrationPower) { return i; } } return 0; } } internal static class ColorUtils { private const byte ALPHA = 38; private static readonly Color[] ArmorClassColors = new Color[] { new Color32(120, 40, 135, ALPHA), // Unarmored => Violet new Color32(0, 60, 170, ALPHA), // Class 1 => Blue new Color32(0, 150, 150, ALPHA), // Class 2 => Cyan new Color32(70, 140, 0, ALPHA), // Class 3 => Green new Color32(170, 170, 0, ALPHA), // Class 4 => Yellow new Color32(140, 70, 0, ALPHA), // Class 5 => Orange new Color32(170, 20, 0, ALPHA) // Class 6+ => Red }; public static Color GetArmorClassColor(int armorClass) => ArmorClassColors[Mathf.Clamp(armorClass, 0, ArmorClassColors.Length - 1)]; } internal static class ItemViewExtensions { private static readonly FieldInfo BackgroundColorField = typeof(ItemView).GetField("BackgroundColor", BindingFlags.NonPublic | BindingFlags.Instance); private static void SetBackgroundColor(this ItemView instance, Color color) => BackgroundColorField.SetValue(instance, color); public static void OverrideColor(this ItemView instance, Item item) { if (item is BulletClass bullet && bullet.PenetrationPower > 0) { int armorClass = bullet.AmmoTemplate.GetPenetrationArmorClass(); instance.SetBackgroundColor(ColorUtils.GetArmorClassColor(armorClass)); } } } internal static class EntityIconExtensions { private static readonly FieldInfo ColorPanelField = typeof(EntityIcon).GetField("_colorPanel", BindingFlags.NonPublic | BindingFlags.Instance); private static Image GetColorPanel(this EntityIcon instance) => ColorPanelField.GetValue(instance) as Image; public static void OverrideColor(this EntityIcon instance, Item item) { if (item is BulletClass bullet && bullet.PenetrationPower > 0) { int armorClass = bullet.AmmoTemplate.GetPenetrationArmorClass(); instance.GetColorPanel().color = ColorUtils.GetArmorClassColor(armorClass); } } } }