server-master/srcs/_plugins/Plugin.ResourceLoader/Loaders/ItemResourceFileLoader.cs

772 lines
No EOL
30 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Npgsql;
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<ItemDTO>
{
private const string FILE_NAME = "Item.dat";
private readonly ResourceLoadingConfiguration _configuration;
private readonly List<ItemDTO> _itemDtos = new();
public ItemResourceFileLoader(ResourceLoadingConfiguration configuration) => _configuration = configuration;
public async Task<IReadOnlyList<ItemDTO>> LoadAsync()
{
if (_itemDtos.Any())
{
return _itemDtos;
}
string filePath = Path.Combine(_configuration.GameDataPath, FILE_NAME);
if (!File.Exists(filePath))
{
bool dbFirst = string.Equals(Environment.GetEnvironmentVariable("DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase)
|| string.Equals(Environment.GetEnvironmentVariable("RESOURCE_DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase);
if (dbFirst)
{
TryHydrateItemDatFromDatabase(filePath);
}
}
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<string> 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<string> 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<int, int> { { 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 void TryHydrateItemDatFromDatabase(string filePath)
{
try
{
string host = Environment.GetEnvironmentVariable("DATABASE_IP")
?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_IP")
?? "127.0.0.1";
string port = Environment.GetEnvironmentVariable("DATABASE_PORT")
?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_PORT")
?? "5432";
string db = Environment.GetEnvironmentVariable("DATABASE_NAME")
?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_NAME")
?? "game";
string user = Environment.GetEnvironmentVariable("DATABASE_USER")
?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_USER")
?? "postgres";
string pass = Environment.GetEnvironmentVariable("DATABASE_PASSWORD")
?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_PASSWORD")
?? "postgres";
using var conn = new NpgsqlConnection($"Host={host};Port={port};Database={db};Username={user};Password={pass}");
conn.Open();
using var cmd = new NpgsqlCommand("SELECT content FROM resource_files WHERE category='dat' AND (relative_path='dat/Item.dat' OR relative_path='Item.dat') LIMIT 1;", conn);
object result = cmd.ExecuteScalar();
if (result is byte[] bytes && bytes.Length > 0)
{
Directory.CreateDirectory(Path.GetDirectoryName(filePath) ?? _configuration.GameDataPath);
File.WriteAllBytes(filePath, bytes);
Log.Info("[DB_FIRST] Hydrated Item.dat from resource_files");
}
}
catch (Exception ex)
{
Log.Error("[DB_FIRST] Could not hydrate Item.dat from database", ex);
}
}
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;
}
}
}
}