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.Quests; namespace Plugin.ResourceLoader.Loaders { public class NpcQuestResourceFileLoader : IResourceLoader { private readonly ResourceLoadingConfiguration _configuration; public NpcQuestResourceFileLoader(ResourceLoadingConfiguration configuration) => _configuration = configuration; public async Task> LoadAsync() { string filePath = Path.Combine(_configuration.GameDataPath, "qstnpc.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("qstnpc.dat", filePath); } } if (!File.Exists(filePath)) { throw new FileNotFoundException($"{filePath} should be present"); } var npcQuests = new List(); using var idStream = new StreamReader(filePath, Encoding.GetEncoding(1252)); string line; int counter = 0; int idCounter = 1; while ((line = idStream.ReadLine()) != null) { string[] currentLine = line.Split(' '); if (currentLine.Length < 5 || line.StartsWith('#')) { continue; } counter++; var dto = new QuestNpcDto { Id = idCounter++, NpcVnum = short.Parse(currentLine[0]), Level = short.Parse(currentLine[4]) }; bool isMainQuest = short.Parse(currentLine[1]) == 0; if (isMainQuest) { dto.IsMainQuest = true; dto.StartingScript = short.Parse(currentLine[2]); dto.RequiredCompletedScript = short.Parse(currentLine[3]); dto.MapId = short.Parse(currentLine[5]); } else { dto.QuestId = int.Parse(currentLine[2]); dto.IsMainQuest = false; } npcQuests.Add(dto); } Log.Info($"[RESOURCE_LOADER] {npcQuests.Count.ToString()} NPC quests loaded"); return npcQuests; } 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) ?? _configuration.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); } } } }