using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using PhoenixLib.Logging; using WingsAPI.Data.GameData; using WingsAPI.Packets.Enums.Shells; using WingsEmu.DTOs.BCards; using WingsEmu.DTOs.Items; using WingsEmu.Game._enum; using WingsEmu.Packets.Enums; using WingsEmu.Packets.Enums.Battle; namespace Plugin.ResourceLoader.Loaders { public class ItemResourceFileLoader : IResourceLoader { private const string FILE_NAME = "Item.dat"; private readonly ResourceLoadingConfiguration _configuration; private readonly List _itemDtos = new(); public ItemResourceFileLoader(ResourceLoadingConfiguration configuration) => _configuration = configuration; public async Task> LoadAsync() { if (_itemDtos.Any()) { return _itemDtos; } string filePath = Path.Combine(_configuration.GameDataPath, FILE_NAME); if (!File.Exists(filePath)) { throw new FileNotFoundException($"{filePath} should be present"); } using var npcIdStream = new StreamReader(filePath, Encoding.GetEncoding(1252)); var item = new ItemDTO(); bool itemAreaBegin = false; int itemCounter = 0; string line; while ((line = await npcIdStream.ReadLineAsync()) != null) { try { string[] currentLine = line.Split('\t'); switch (currentLine.Length) { case > 3 when currentLine[1] == "VNUM": itemAreaBegin = true; item.Id = int.Parse(currentLine[2]); item.Price = long.Parse(currentLine[3]); break; case > 1 when currentLine[1] == "END": { if (!itemAreaBegin) { continue; } _itemDtos.Add(item); itemCounter++; item = new ItemDTO(); itemAreaBegin = false; break; } case > 2 when currentLine[1] == "NAME": item.Name = currentLine[2]; break; case > 7 when currentLine[1] == "INDEX": FillMorphAndIndexValues(currentLine, item); break; case > 3 when currentLine[1] == "TYPE": // currentLine[2] 0-range 2-range 3-magic item.AttackType = (AttackType)Convert.ToByte(currentLine[2]); item.Class = item.EquipmentSlot == EquipmentType.Fairy ? (byte)15 : Convert.ToByte(currentLine[3]); break; case > 3 when currentLine[1] == "FLAG": FillFlags(item, currentLine); break; case > 1 when currentLine[1] == "DATA": FillData(item, currentLine); break; case > 1 when currentLine[1] == "BUFF": FillBuff(currentLine, item); break; } } catch (Exception e) { Log.Error("Error while loading Item.dat", e); } } Log.Info($"[RESOURCE_LOADER] {itemCounter.ToString()} Items loaded"); return _itemDtos; } private static void FillBuff(IReadOnlyList currentLine, ItemDTO item) { for (int i = 0; i < 5; i++) { byte type = (byte)int.Parse(currentLine[2 + 5 * i]); if (type is 0 or 255) // 255 = -1 { continue; } int first = int.Parse(currentLine[3 + 5 * i]); int second = int.Parse(currentLine[4 + 5 * i]); int firstModulo = first % 4; firstModulo = firstModulo switch { -1 => 1, -2 => 2, -3 => 1, _ => firstModulo }; int secondModulo = second % 4; secondModulo = secondModulo switch { -1 => 1, -2 => 2, -3 => 1, _ => secondModulo }; var itemCard = new BCardDTO { ItemVNum = item.Id, Type = type, SubType = (byte)((int.Parse(currentLine[5 + i * 5]) + 1) * 10 + 1 + (first < 0 ? 1 : 0)), FirstDataScalingType = (BCardScalingType)firstModulo, SecondDataScalingType = (BCardScalingType)secondModulo, FirstData = (int)Math.Abs(Math.Floor(first / 4.0)), SecondData = (int)Math.Abs(Math.Floor(second / 4.0)), CastType = byte.Parse(currentLine[6 + 5 * i]) }; item.BCards.Add(itemCard); } } private static void FillFlags(ItemDTO item, IReadOnlyList currentLine) { // useless [2] // useless [3] // useless [4] item.IsSoldable = currentLine[5] == "0"; item.IsDroppable = currentLine[6] == "0"; item.IsTradable = currentLine[7] == "0"; item.IsMinilandActionable = currentLine[8] == "1"; item.IsWarehouse = currentLine[9] == "1"; item.ShowWarningOnUse = currentLine[10] == "1"; item.IsTimeSpaceRewardBox = currentLine[11] == "1"; item.ShowDescriptionOnHover = currentLine[12] == "1"; item.Flag3 = currentLine[13] == "1"; item.FollowMouseOnUse = currentLine[14] == "1"; item.ShowSomethingOnHover = currentLine[15] == "1"; item.IsColorable = currentLine[16] == "1"; item.Sex = currentLine[18] == "1" ? (byte)1 : currentLine[17] == "1" ? (byte)2 : (byte)0; //not used item.Flag6 = currentLine[19] == "1"; item.PlaySoundOnPickup = currentLine[20] == "1"; item.UseReputationAsPrice = currentLine[21] == "1"; if (item.UseReputationAsPrice) { item.ReputPrice = item.Price; } item.IsHeroic = currentLine[22] == "1"; item.Flag7 = currentLine[23] == "1"; item.IsLimited = currentLine[24] == "1"; } private static void FillData(ItemDTO item, string[] currentLine) { item.Data = new int[20]; for (int i = 0; i < 20; i++) { item.Data[i] = Convert.ToInt32(currentLine[2 + i]); } switch (item.ItemType) { case ItemType.Weapon: item.LevelMinimum = Convert.ToByte(currentLine[2]); item.DamageMinimum = Convert.ToInt16(currentLine[3]); item.DamageMaximum = Convert.ToInt16(currentLine[4]); item.HitRate = Convert.ToInt16(currentLine[5]); item.CriticalLuckRate = Convert.ToSByte(currentLine[6]); item.CriticalRate = Convert.ToInt16(currentLine[7]); item.BasicUpgrade = Convert.ToByte(currentLine[10]); item.MaximumAmmo = 100; break; case ItemType.Armor: item.LevelMinimum = Convert.ToByte(currentLine[2]); item.CloseDefence = Convert.ToInt16(currentLine[3]); item.DistanceDefence = Convert.ToInt16(currentLine[4]); item.MagicDefence = Convert.ToInt16(currentLine[5]); item.DefenceDodge = Convert.ToInt16(currentLine[6]); item.DistanceDefenceDodge = Convert.ToInt16(currentLine[6]); item.BasicUpgrade = Convert.ToByte(currentLine[10]); break; case ItemType.Box: item.Effect = Convert.ToInt16(currentLine[21]); item.EffectValue = Convert.ToInt32(currentLine[3]); item.LevelMinimum = Convert.ToByte(currentLine[4]); if (item.ItemSubType == 7) // Magic Speed Booster { long time = Convert.ToInt32(currentLine[4]); item.ItemValidTime = time == 0 ? -1 : Convert.ToInt32(currentLine[4]) * 3600; } break; case ItemType.Fashion: item.LevelMinimum = Convert.ToByte(currentLine[2]); item.CloseDefence = Convert.ToInt16(currentLine[3]); item.DistanceDefence = Convert.ToInt16(currentLine[4]); item.MagicDefence = Convert.ToInt16(currentLine[5]); item.DefenceDodge = Convert.ToInt16(currentLine[6]); item.DistanceDefenceDodge = Convert.ToInt16(currentLine[6]); if (item.EquipmentSlot == EquipmentType.CostumeHat || item.EquipmentSlot == EquipmentType.CostumeSuit || item.EquipmentSlot == EquipmentType.WeaponSkin) { long time = Convert.ToInt32(currentLine[13]); item.ItemValidTime = time == 0 ? -1 : Convert.ToInt32(currentLine[13]) * 3600; } break; case ItemType.Food: item.Hp = Convert.ToInt16(currentLine[2]); item.Mp = Convert.ToInt16(currentLine[4]); break; case ItemType.Jewelry: switch (item.EquipmentSlot) { case EquipmentType.Amulet: item.LevelMinimum = Convert.ToByte(currentLine[2]); item.ItemLeftType = Convert.ToInt16(currentLine[4]); if (item.ItemLeftType == 100) { item.LeftUsages = Convert.ToInt32(currentLine[3]); } else if (item.ItemLeftType >= 1000) { item.ItemValidTime = Convert.ToInt64(currentLine[13]) * 3600; } else { item.ItemValidTime = Convert.ToInt64(currentLine[3]) == 0 ? -1 : Convert.ToInt64(currentLine[3]) / 10; } break; case EquipmentType.Fairy: item.Element = Convert.ToByte(currentLine[2]); item.ElementRate = Convert.ToInt16(currentLine[3]); if (item.Id <= 256) { item.MaxElementRate = 50; } else if (item.ElementRate == 0) { if (item.Id >= 800 && item.Id <= 804) { item.MaxElementRate = 50; } else { item.MaxElementRate = 70; } } else if (item.ElementRate == 30) { item.MaxElementRate = 30; } else if (item.ElementRate == 35) { item.MaxElementRate = 35; } else if (item.ElementRate == 40) { item.MaxElementRate = 70; } else if (item.ElementRate == 50) { item.MaxElementRate = 80; } break; default: item.LevelMinimum = Convert.ToByte(currentLine[2]); item.MaxCellonLvl = Convert.ToByte(currentLine[3]); item.MaxCellon = Convert.ToByte(currentLine[4]); break; } break; case ItemType.Event: item.Effect = Convert.ToInt16(currentLine[2]); item.EffectValue = Convert.ToInt16(currentLine[3]); break; case ItemType.Special: switch (item.Id) { case 5853: item.Effect = 1717; item.EffectValue = 1; break; case 5854: item.Effect = 1717; item.EffectValue = 2; break; case 5855: item.Effect = 1717; item.EffectValue = 3; break; case 1272: case 1858: case 9047: item.Effect = 1005; item.EffectValue = 10; break; case 1273: case 9024: item.Effect = 1005; item.EffectValue = 30; break; case 1274: case 9025: item.Effect = 1005; item.EffectValue = 60; break; case 1279: case 9029: item.Effect = 1007; item.EffectValue = 30; break; case 1280: case 9030: item.Effect = 1007; item.EffectValue = 60; break; case 1923: case 9056: item.Effect = 1007; item.EffectValue = 10; break; case 1275: case 1886: case 9026: item.Effect = 1008; item.EffectValue = 10; break; case 1276: case 9027: item.Effect = 1008; item.EffectValue = 30; break; case 1277: case 9028: item.Effect = 1008; item.EffectValue = 60; break; case 5060: case 9066: item.Effect = 1003; item.EffectValue = 30; break; case 5061: case 9067: item.Effect = 1004; item.EffectValue = 7; break; case 5062: case 9068: item.Effect = 1004; item.EffectValue = 1; break; case 5115: item.Effect = 652; break; case 1981: item.Effect = 34; // imagined number as for I = √(-1), complex z = a + bi break; case 1982: item.Effect = 6969; // imagined number as for I = √(-1), complex z = a + bi break; case 9071: case 5119: // Speed booster item.Effect = 998; break; case 180: // attack amulet item.Effect = 932; break; case 181: // defense amulet item.Effect = 933; break; default: if (item.Id > 5891 && item.Id < 5900 || item.Id > 9100 && item.Id < 9109) { item.Effect = 69; // imagined number as for I = √(-1), complex z = a + bi } else { item.Effect = Convert.ToInt16(currentLine[2]); } break; } switch (item.Effect) { case 305: item.EffectValue = Convert.ToInt32(currentLine[5]); item.Morph = Convert.ToInt16(currentLine[4]); break; default: item.EffectValue = item.EffectValue == 0 ? Convert.ToInt32(currentLine[4]) : item.EffectValue; break; } item.WaitDelay = 5000; break; case ItemType.Magical: item.Effect = Convert.ToInt16(currentLine[2]); if (item.Effect == 99) { item.LevelMinimum = Convert.ToByte(currentLine[4]); item.EffectValue = Convert.ToByte(currentLine[5]); } else { item.EffectValue = Convert.ToInt32(currentLine[4]); } break; case ItemType.Specialist: item.IsPartnerSpecialist = item.ItemSubType == 4; item.Speed = Convert.ToByte(currentLine[5]); if (item.IsPartnerSpecialist) { item.Element = Convert.ToByte(currentLine[3]); item.ElementRate = Convert.ToInt16(currentLine[4]); item.PartnerClass = Convert.ToByte(currentLine[19]); item.LevelMinimum = Convert.ToByte(currentLine[20]); } else { item.LevelJobMinimum = Convert.ToByte(currentLine[20]); item.ReputationMinimum = Convert.ToByte(currentLine[21]); } item.SpPointsUsage = Convert.ToByte(currentLine[13]); item.SpMorphId = item.IsPartnerSpecialist ? (byte)(1 + Convert.ToByte(currentLine[14])) : Convert.ToByte(currentLine[14]); item.FireResistance = Convert.ToByte(currentLine[15]); item.WaterResistance = Convert.ToByte(currentLine[16]); item.LightResistance = Convert.ToByte(currentLine[17]); item.DarkResistance = Convert.ToByte(currentLine[18]); var elementdic = new Dictionary { { 0, 0 } }; if (item.FireResistance != 0) { elementdic.Add(1, item.FireResistance); } if (item.WaterResistance != 0) { elementdic.Add(2, item.WaterResistance); } if (item.LightResistance != 0) { elementdic.Add(3, item.LightResistance); } if (item.DarkResistance != 0) { elementdic.Add(4, item.DarkResistance); } if (!item.IsPartnerSpecialist) { item.Element = (byte)elementdic.OrderByDescending(s => s.Value).First().Key; } // needs to be hardcoded switch (item.Id) { case 901: item.Element = 1; break; case 903: item.Element = 2; break; case 906: item.Element = 3; break; case 909: item.Element = 3; break; } break; case ItemType.Shell: byte shellType = Convert.ToByte(currentLine[5]); item.ShellMinimumLevel = Convert.ToInt16(currentLine[3]); item.ShellMaximumLevel = Convert.ToInt16(currentLine[4]); item.ShellType = (ShellType)(item.ItemSubType == 1 ? shellType + 50 : shellType); break; case ItemType.Main: item.Effect = Convert.ToInt16(currentLine[2]); item.EffectValue = Convert.ToInt32(currentLine[4]); break; case ItemType.Upgrade: item.Effect = Convert.ToInt16(currentLine[2]); switch (item.Id) { // UpgradeItems (needed to be hardcoded) case (int)ItemVnums.EQ_NORMAL_SCROLL: item.EffectValue = 26; break; case (int)ItemVnums.LOWER_SP_SCROLL: item.EffectValue = 27; break; case (int)ItemVnums.HIGHER_SP_SCROLL: item.EffectValue = 28; break; case (int)ItemVnums.SCROLL_CHICKEN: item.EffectValue = 47; break; case (int)ItemVnums.SCROLL_PYJAMA: item.EffectValue = 50; break; case (int)ItemVnums.EQ_GOLD_SCROLL: item.EffectValue = 61; break; case (int)ItemVnums.SCROLL_PIRATE: item.EffectValue = 60; break; default: item.EffectValue = Convert.ToInt32(currentLine[4]); break; } break; case ItemType.Production: item.Effect = Convert.ToInt16(currentLine[2]); item.EffectValue = Convert.ToInt32(currentLine[4]); break; case ItemType.Map: item.Effect = Convert.ToInt16(currentLine[2]); item.EffectValue = Convert.ToInt32(currentLine[4]); break; case ItemType.Potion: item.Hp = Convert.ToInt16(currentLine[2]); item.Mp = Convert.ToInt16(currentLine[4]); break; case ItemType.Snack: item.Hp = Convert.ToInt16(currentLine[2]); item.Mp = Convert.ToInt16(currentLine[4]); break; case ItemType.PetPartnerItem: item.Effect = Convert.ToInt16(currentLine[2]); item.EffectValue = Convert.ToInt32(currentLine[4]); break; case ItemType.Material: case ItemType.Sell: case ItemType.Quest2: case ItemType.Quest1: case ItemType.Ammo: // nothing to parse break; } if (item.Type == InventoryType.Miniland) { item.MinilandObjectPoint = int.Parse(currentLine[2]); item.EffectValue = short.Parse(currentLine[8]); item.Width = Convert.ToByte(currentLine[9]) == 0 ? (byte)1 : Convert.ToByte(currentLine[9]); item.Height = Convert.ToByte(currentLine[10]) == 0 ? (byte)1 : Convert.ToByte(currentLine[10]); } if (item.EquipmentSlot != EquipmentType.Boots && item.EquipmentSlot != EquipmentType.Gloves || item.Type != 0) { return; } item.FireResistance = Convert.ToByte(currentLine[7]); item.WaterResistance = Convert.ToByte(currentLine[8]); item.LightResistance = Convert.ToByte(currentLine[9]); item.DarkResistance = Convert.ToByte(currentLine[11]); } private static void FillMorphAndIndexValues(string[] currentLine, ItemDTO item) { switch (Convert.ToByte(currentLine[2])) { case 4: item.Type = InventoryType.Equipment; break; case 8: item.Type = InventoryType.Equipment; break; case 9: item.Type = InventoryType.Main; break; case 10: item.Type = InventoryType.Etc; break; default: item.Type = (InventoryType)Enum.Parse(typeof(InventoryType), currentLine[2]); break; } item.ItemType = currentLine[3] != "-1" ? (ItemType)Enum.Parse(typeof(ItemType), $"{(short)item.Type}{currentLine[3]}") : ItemType.Weapon; item.ItemSubType = Convert.ToByte(currentLine[4]); item.EquipmentSlot = (EquipmentType)Enum.Parse(typeof(EquipmentType), currentLine[5] != "-1" ? currentLine[5] : "0"); item.IconId = Convert.ToInt32(currentLine[6]); switch (item.Id) { case 4101: case 4102: case 4103: case 4104: case 4105: item.EquipmentSlot = 0; break; default: if (item.EquipmentSlot.Equals(EquipmentType.Amulet)) { switch (item.Id) { case 4503: item.EffectValue = 4544; break; case 4504: item.EffectValue = 4294; break; default: item.EffectValue = Convert.ToInt16(currentLine[7]); break; } } else { item.Morph = Convert.ToInt16(currentLine[7]); } break; } } } }