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 PhoenixLib.MultiLanguage; using WingsAPI.Data.GameData; using WingsEmu.DTOs.Buffs; using WingsEmu.DTOs.Items; using WingsEmu.DTOs.NpcMonster; using WingsEmu.DTOs.Quests; using WingsEmu.DTOs.Skills; using WingsEmu.Game._i18n; namespace Plugin.ResourceLoader.Loaders { public class GameDataLanguageFileLoader : IResourceLoader { private static readonly List<(string, GameDataType)> _fileNames = new() { new ValueTuple("_code_{0}_Card.txt", GameDataType.Card), new ValueTuple("_code_{0}_monster.txt", GameDataType.NpcMonster), new ValueTuple("_code_{0}_Item.txt", GameDataType.Item), new ValueTuple("_code_{0}_quest.txt", GameDataType.QuestName), new ValueTuple("_code_{0}_Skill.txt", GameDataType.Skill) }; private readonly IResourceLoader _cardLoader; private readonly ResourceLoadingConfiguration _config; private readonly IResourceLoader _itemLoader; private readonly IResourceLoader _npcLoader; private readonly IResourceLoader _questLoader; private readonly IResourceLoader _skillLoader; public GameDataLanguageFileLoader(ResourceLoadingConfiguration config, IResourceLoader itemLoader, IResourceLoader cardLoader, IResourceLoader skillLoader, IResourceLoader questLoader, IResourceLoader npcLoader) { _config = config; _itemLoader = itemLoader; _cardLoader = cardLoader; _skillLoader = skillLoader; _questLoader = questLoader; _npcLoader = npcLoader; } public async Task> LoadAsync() { var translations = new List(); foreach ((string fileName, GameDataType dataType) in _fileNames) { translations.AddRange(await LoadAsync(fileName, dataType)); } return translations; } private static string ToNostaleRegionKey(RegionLanguageType type) { switch (type) { case RegionLanguageType.FR: return "fr"; case RegionLanguageType.EN: return "uk"; case RegionLanguageType.DE: return "de"; case RegionLanguageType.PL: return "pl"; case RegionLanguageType.IT: return "it"; case RegionLanguageType.ES: return "es"; case RegionLanguageType.CZ: return "cz"; case RegionLanguageType.TR: return "tr"; default: return "uk"; } } public static Encoding GetEncoding(RegionLanguageType key) { switch (key) { case RegionLanguageType.EN: case RegionLanguageType.FR: case RegionLanguageType.ES: return Encoding.GetEncoding(1252); case RegionLanguageType.DE: case RegionLanguageType.PL: case RegionLanguageType.IT: case RegionLanguageType.CZ: return Encoding.GetEncoding(1250); case RegionLanguageType.TR: return Encoding.GetEncoding(1254); default: throw new ArgumentOutOfRangeException(nameof(key), key, null); } } private async Task> LoadAsync(string fileToParse, GameDataType dataType) { var translations = new List(); HashSet _hashSet = dataType switch { GameDataType.Item => (await _itemLoader.LoadAsync()).Select(s => s.Name).ToHashSet(), GameDataType.Card => (await _cardLoader.LoadAsync()).Select(s => s.Name).ToHashSet(), GameDataType.NpcMonster => (await _npcLoader.LoadAsync()).Select(s => s.Name).ToHashSet(), GameDataType.Skill => (await _skillLoader.LoadAsync()).Select(s => s.Name).ToHashSet(), GameDataType.QuestName => (await _questLoader.LoadAsync()).Select(s => s.Name).ToHashSet(), _ => new HashSet() }; foreach (RegionLanguageType lang in Enum.GetValues()) { if (lang == RegionLanguageType.RU) { continue; } string fileName = string.Format(fileToParse, ToNostaleRegionKey(lang)); string fileLang = $"{_config.GameLanguagePath}/{fileName}"; bool dbFirst = string.Equals(Environment.GetEnvironmentVariable("DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase) || string.Equals(Environment.GetEnvironmentVariable("RESOURCE_DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase); IEnumerable lines = null; if (File.Exists(fileLang)) { lines = await File.ReadAllLinesAsync(fileLang, GetEncoding(lang)); } else if (dbFirst) { lines = TryLoadLangLinesFromDatabase(fileName, GetEncoding(lang)); } if (lines == null) { Log.Warn($"[DB_FIRST] Missing language source '{fileLang}', skipping."); continue; } foreach (string line in lines) { string[] lineSave = line.Split('\t'); if (lineSave.Length <= 1) { continue; } if (!_hashSet.Contains(lineSave[0])) { continue; } translations.Add(new GameDataTranslationDto { DataType = dataType, Language = lang, Key = lineSave[0], Value = lineSave[1] }); } } Log.Info($"[RESOURCE_LOADER] Loaded {translations.Count} Game Data translations of {dataType.ToString()}"); return translations; } private IEnumerable TryLoadLangLinesFromDatabase(string langFileName, Encoding encoding) { 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='lang' AND (relative_path=@path OR lower(relative_path)=lower(@path) OR lower(relative_path) LIKE lower('%/' || @name)) LIMIT 1;", conn); cmd.Parameters.AddWithValue("path", $"lang/{langFileName}"); cmd.Parameters.AddWithValue("name", langFileName); object result = cmd.ExecuteScalar(); if (result is byte[] bytes && bytes.Length > 0) { string text = encoding.GetString(bytes); Log.Info($"[DB_FIRST] Loaded lang file {langFileName} from database"); return text.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None); } } catch (Exception ex) { Log.Error($"[DB_FIRST] Could not load lang file {langFileName} from database", ex); } return null; } } }