using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Threading.Tasks; using Npgsql; using PhoenixLib.Logging; using WingsAPI.Data.GameData; using WingsEmu.DTOs.BCards; using WingsEmu.DTOs.Buffs; using WingsEmu.Game._enum; using WingsEmu.Packets.Enums; namespace Plugin.ResourceLoader.Loaders { public class CardResourceFileLoader : IResourceLoader { private readonly ResourceLoadingConfiguration _config; public CardResourceFileLoader(ResourceLoadingConfiguration config) => _config = config; public async Task> LoadAsync() { string filePath = Path.Combine(_config.GameDataPath, "Card.dat"); 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) { TryHydrateDatFileFromDatabase("Card.dat", filePath); } } if (!File.Exists(filePath)) { throw new FileNotFoundException($"{filePath} should be present"); } var card = new CardDTO(); var cards = new List(); int counter = 0; bool itemAreaBegin = false; using var npcIdStream = new StreamReader(filePath, Encoding.GetEncoding(1252)); string line; while ((line = await npcIdStream.ReadLineAsync()) != null) { string[] currentLine = line.Split('\t'); switch (currentLine.Length) { case > 2 when currentLine[1] == "VNUM": card = new CardDTO { Id = Convert.ToInt16(currentLine[2]) }; itemAreaBegin = true; break; case > 2 when currentLine[1] == "NAME": card.Name = currentLine[2]; break; case > 3 when currentLine[1] == "GROUP": { if (!itemAreaBegin) { continue; } card.GroupId = Convert.ToInt32(currentLine[2]); card.Level = Convert.ToByte(currentLine[3]); break; } case > 3 when currentLine[1] == "STYLE": card.BuffCategory = (BuffCategory)byte.Parse(currentLine[2]); card.BuffType = Convert.ToByte(currentLine[3]); card.ElementType = Convert.ToByte(currentLine[4]); card.IsConstEffect = currentLine[5] == "1"; card.BuffPartnerLevel = Convert.ToByte(currentLine[6]); break; case > 3 when currentLine[1] == "EFFECT": card.EffectId = Convert.ToInt32(currentLine[2]); break; case > 3 when currentLine[1] == "TIME": card.Duration = Convert.ToInt32(currentLine[2]); card.SecondBCardsDelay = Convert.ToInt32(currentLine[3]); break; default: { BCardDTO bCard; switch (currentLine.Length) { case > 3 when currentLine[1] == "1ST": { for (int i = 0; i < 3; i++) { if (currentLine[2 + i * 6] == "-1" || currentLine[2 + i * 6] == "0") { continue; } int first = int.Parse(currentLine[6 + i * 6]); int second = int.Parse(currentLine[7 + i * 6]); 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 }; byte tickPeriod = byte.Parse(currentLine[5 + i * 6]); bCard = new BCardDTO { CardId = card.Id, Type = byte.Parse(currentLine[2 + i * 6]), SubType = (byte)((Convert.ToByte(currentLine[3 + i * 6]) + 1) * 10 + 1 + (first < 0 ? 1 : 0)), FirstData = (int)Math.Abs(Math.Floor(first / 4.0)), SecondData = (int)Math.Abs(Math.Floor(second / 4.0)), ProcChance = int.Parse(currentLine[4 + i * 6]), TickPeriod = tickPeriod == 0 ? null : (byte?)(tickPeriod * 2), FirstDataScalingType = (BCardScalingType)firstModulo, SecondDataScalingType = (BCardScalingType)secondModulo, IsSecondBCardExecution = false }; card.Bcards.Add(bCard); } break; } case > 3 when currentLine[1] == "2ST": { for (int i = 0; i < 2; i++) { if (currentLine[2 + i * 6] == "-1" || currentLine[2 + i * 6] == "0") { continue; } int first = int.Parse(currentLine[6 + i * 6]); int second = int.Parse(currentLine[7 + i * 6]); 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 }; byte tickPeriod = byte.Parse(currentLine[5 + i * 6]); bCard = new BCardDTO { CardId = card.Id, Type = byte.Parse(currentLine[2 + i * 6]), SubType = (byte)((Convert.ToByte(currentLine[3 + i * 6]) + 1) * 10 + 1 + (first < 0 ? 1 : 0)), FirstData = (int)Math.Abs(Math.Floor(first / 4.0)), SecondData = (int)Math.Abs(Math.Floor(second / 4.0)), ProcChance = int.Parse(currentLine[4 + i * 6]), TickPeriod = tickPeriod == 0 ? null : (byte?)(tickPeriod * 2), FirstDataScalingType = (BCardScalingType)firstModulo, SecondDataScalingType = (BCardScalingType)secondModulo, IsSecondBCardExecution = true }; card.Bcards.Add(bCard); } break; } case > 3 when currentLine[1] == "LAST": card.TimeoutBuff = short.Parse(currentLine[2]); card.TimeoutBuffChance = byte.Parse(currentLine[3]); itemAreaBegin = false; cards.Add(card); counter++; break; } break; } } } Log.Info($"[RESOURCE_LOADER] {cards.Count.ToString()} act desc loaded"); return cards; } private void TryHydrateDatFileFromDatabase(string datFileName, string targetPath) { 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=@path LIMIT 1;", conn); cmd.Parameters.AddWithValue("path", $"dat/{datFileName}"); object result = cmd.ExecuteScalar(); if (result is byte[] bytes && bytes.Length > 0) { Directory.CreateDirectory(Path.GetDirectoryName(targetPath) ?? _config.GameDataPath); File.WriteAllBytes(targetPath, bytes); Log.Info($"[DB_FIRST] Hydrated {datFileName} from resource_files"); } } catch (Exception ex) { Log.Error($"[DB_FIRST] Could not hydrate {datFileName} from database", ex); } } } }