using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using CloneExtensions; using Npgsql; using PhoenixLib.Logging; using WingsAPI.Data.GameData; using WingsEmu.DTOs.Quests; using WingsEmu.Packets.Enums; namespace Plugin.ResourceLoader.Loaders { public class QuestResourceFileLoader : IResourceLoader { private readonly ResourceLoadingConfiguration _configuration; private readonly List _quests = new(); public QuestResourceFileLoader(ResourceLoadingConfiguration configuration) => _configuration = configuration; public async Task> LoadAsync() { if (_quests.Any()) { return _quests; } string fileQuestPath = Path.Combine(_configuration.GameDataPath, "quest.dat"); string fileRewardsPath = Path.Combine(_configuration.GameDataPath, "qstprize.dat"); if (!File.Exists(fileQuestPath) || !File.Exists(fileRewardsPath)) { bool dbFirst = string.Equals(Environment.GetEnvironmentVariable("DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase) || string.Equals(Environment.GetEnvironmentVariable("RESOURCE_DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase); if (dbFirst) { TryHydrateDatFileFromDatabase("quest.dat", fileQuestPath); TryHydrateDatFileFromDatabase("qstprize.dat", fileRewardsPath); } } if (!File.Exists(fileQuestPath)) { throw new FileNotFoundException($"{fileQuestPath} should be present"); } if (!File.Exists(fileRewardsPath)) { throw new FileNotFoundException($"{fileRewardsPath} should be present"); } var dictionaryRewards = new Dictionary(); string line; FillRewards(fileRewardsPath, dictionaryRewards); // Current var quest = new QuestDto(); byte objectiveIndex = 0; using var questStream = new StreamReader(fileQuestPath, Encoding.GetEncoding(1252)); while ((line = await questStream.ReadLineAsync()) != null) { string[] currentLine = line.Split('\t'); if (currentLine.Length > 1 || currentLine[0] == "END") { switch (currentLine[0]) { case "VNUM": quest = new QuestDto { Id = int.Parse(currentLine[1]), QuestType = (QuestType)int.Parse(currentLine[2]), AutoFinish = Convert.ToInt32(currentLine[3]) == 0, Unknown1 = Convert.ToInt32(currentLine[4]), RequiredQuestId = Convert.ToInt32(currentLine[5]), IsBlue = Convert.ToInt32(currentLine[6]) == 1 }; objectiveIndex = 0; break; case "LINK": quest.NextQuestId = int.Parse(currentLine[1]); break; case "LEVEL": quest.MinLevel = byte.Parse(currentLine[1]); quest.MaxLevel = byte.Parse(currentLine[2]); break; case "TALK": quest.DialogStarting = int.Parse(currentLine[1]); quest.DialogFinish = int.Parse(currentLine[2]); quest.TalkerVnum = int.Parse(currentLine[3]); quest.DialogDuring = int.Parse(currentLine[4]); break; case "TARGET": quest.TargetMapX = short.Parse(currentLine[1]); quest.TargetMapY = short.Parse(currentLine[2]); quest.TargetMapId = short.Parse(currentLine[3]); break; case "TITLE": quest.Name = currentLine[1]; break; case "DESC": quest.Description = currentLine[1]; break; case "DATA": objectiveIndex++; int data0 = int.Parse(currentLine[1]); int data1 = int.Parse(currentLine[2]); int data2 = int.Parse(currentLine[3]); int data3 = int.Parse(currentLine[4]); quest.Objectives.Add(new QuestObjectiveDto { Data0 = data0, Data1 = data1, Data2 = data2, Data3 = data3, ObjectiveIndex = objectiveIndex, QuestId = quest.Id }); break; case "PRIZE": for (int a = 1; a < 5; a++) { if (!dictionaryRewards.ContainsKey(long.Parse(currentLine[a]))) { continue; } QuestPrizeDto currentReward = dictionaryRewards[long.Parse(currentLine[a])].GetClone(); currentReward.QuestId = quest.Id; quest.Prizes.Add(currentReward); } break; case "END": _quests.Add(quest); break; } } } questStream.Close(); Log.Info($"[RESOURCE_LOADER] {_quests.Count.ToString()} Quests loaded"); return _quests; } 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); } } private void FillRewards(string fileRewardsPath, Dictionary dictionaryRewards) { using var questRewardStream = new StreamReader(fileRewardsPath, Encoding.GetEncoding(1252)); string line; var reward = new QuestPrizeDto(); int currentRewardId = 0; while ((line = questRewardStream.ReadLine()) != null) { string[] currentLine = line.Split('\t'); if (currentLine.Length <= 1 && currentLine[0] != "END") { continue; } switch (currentLine[0]) { case "VNUM": reward = new QuestPrizeDto { RewardType = byte.Parse(currentLine[2]) }; currentRewardId = int.Parse(currentLine[1]); break; case "DATA": reward.Data0 = int.Parse(currentLine[1]); reward.Data1 = int.Parse(currentLine[2]); reward.Data2 = int.Parse(currentLine[3]); reward.Data3 = int.Parse(currentLine[4]); reward.Data4 = int.Parse(currentLine[5]); break; case "END": dictionaryRewards[currentRewardId] = reward; break; } } questRewardStream.Close(); } } }