/** * Grasscutter Tools * Copyright (C) 2023 jie65535 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * **/ using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Text; using System.Threading; using GrasscutterTools.Game.CutScene; using GrasscutterTools.Game.Data.Excels; using GrasscutterTools.Game.Inventory; using GrasscutterTools.Game.Props; using GrasscutterTools.Utils; using Newtonsoft.Json; namespace GrasscutterTools.Game.Data { internal class GameResources { public Dictionary AchievementData { get; set; } public Dictionary AvatarData { get; set; } public Dictionary HomeWorldBgmData { get; set; } public Dictionary DungeonData { get; set; } public Dictionary GadgetData { get; set; } public List GatherData { get; set; } public Dictionary HomeWorldFurnitureData { get; set; } public Dictionary MainQuestData { get; set; } public Dictionary QuestData { get; set; } public Dictionary MaterialData { get; set; } public Dictionary MonsterData { get; set; } public Dictionary ReliquaryData { get; set; } public Dictionary SceneData { get; set; } public List SceneTagData { get; set; } public Dictionary WeaponData { get; set; } public TextMapData TextMapData { get; set; } public List WeatherData { get; set; } public List CutSceneData { get; set; } public GameResources(string resourcesDirPath, TextMapData textMapData) { TextMapData = textMapData; var properties = typeof(GameResources).GetProperties(); foreach (var property in properties) { var type = property.PropertyType; if (!type.IsGenericType) continue; var genericArguments = type.GetGenericArguments(); ResourceTypeAttribute attribute = null; foreach (var it in genericArguments) { var attributes = it.GetCustomAttributes(typeof(ResourceTypeAttribute), true); if (attributes.Length == 0) continue; attribute = (ResourceTypeAttribute)attributes[0]; type = it; } if (attribute == null) continue; var dataFile = Path.Combine(resourcesDirPath, "ExcelBinOutput", attribute.Name); var data = LoadDataFile(type, dataFile); property.SetValue(this, data, null); } var illegalWeaponIds = new SparseSet( "10000-10008, 11411, 11506-11508, 12505, 12506, 12508, 12509," + "13503, 13506, 14411, 14503, 14508, 15504-15506, 20001"); foreach (var id in WeaponData.Keys.Where(id => illegalWeaponIds.Contains(id)).ToList()) WeaponData.Remove(id); var illegalRelicIds = new SparseSet( "20002, 20004, 23300-24825" ); //var illegalRelicIds = new SparseSet( // "20001, 23300-23340, 23383-23385, 78310-78554, 99310-99554" //); foreach (var id in ReliquaryData.Keys.Where(id => illegalRelicIds.Contains(id)).ToList()) ReliquaryData.Remove(id); var illegalItemIds = new SparseSet( "3004-3008, 3018-3022" ); //var illegalItemIds = new SparseSet( // "3004-3008, 3018-3022, 100086, 100087, 100100-101000, 101106-101110, 101306, 101500-104000," + // "105001, 105004, 106000-107000, 107011, 108000, 109000-110000," + // "115000-130000, 200200-200899, 220050, 220054" //); foreach (var id in MaterialData.Keys.Where(id => illegalItemIds.Contains(id)).ToList()) MaterialData.Remove(id); foreach (var id in AvatarData.Keys.Where(id => id < 10000002 || id >= 11000000).ToList()) AvatarData.Remove(id); } private static object LoadDataFile(Type type, string path) { IList list = null; try { list = (IList)JsonConvert.DeserializeObject(File.ReadAllText(path), typeof(List<>).MakeGenericType(type)); } catch (Exception ex) { Console.WriteLine($"Failed to load \"{path}\", Exception={ex}"); } if (type.IsSubclassOf(typeof(GameResource))) { var dicType = typeof(Dictionary<,>).MakeGenericType(typeof(int), type); var dic = (IDictionary)Activator.CreateInstance(dicType); if (list != null) { foreach (GameResource gameResource in list) { if (gameResource.Id == 0) continue; dic.Add(gameResource.Id, gameResource); } } return dic; } else { if (list == null) list = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(type)); return list; } } private Dictionary Languages = new Dictionary { ["zh-cn"] = "TextMapCHS", ["zh-tw"] = "TextMapCHT", ["en-us"] = "TextMapEN", ["ru-ru"] = "TextMapRU", }; public void ConvertResources(string projectResourcesDir) { var currentCultureInfo = Thread.CurrentThread.CurrentUICulture; try { var sb = new StringBuilder(MaterialData.Count * 24); foreach (var language in Languages) { var dir = Path.Combine(projectResourcesDir, language.Key); TextMapData.LoadTextMap(TextMapData.TextMapFilePaths[Array.IndexOf(TextMapData.TextMapFiles, language.Value)]); Thread.CurrentThread.CurrentUICulture = new CultureInfo(language.Key); GameData.LoadResources(); #region Achievement // Achievement File.WriteAllLines( Path.Combine(dir, "Achievement.txt"), AchievementData.Values.Where(it => it.IsUsed) .Select(it => $"{it.Id}:{TextMapData.GetText(it.TitleTextMapHash.ToString())} - {TextMapData.GetText(it.DescTextMapHash.ToString())}"), Encoding.UTF8); #endregion Achievement #region Artifact // Artifact File.WriteAllLines( Path.Combine(dir, "Artifact.txt"), ReliquaryData.Values.OrderBy(it => it.Id).Select(it => $"{it.Id}:{TextMapData.GetText(it.NameTextMapHash.ToString())}"), Encoding.UTF8); #endregion Artifact #region Avatar // Avatar File.WriteAllLines( Path.Combine(dir, "Avatar.txt"), MaterialData.Values .Where(it => it.MaterialType == MaterialType.MATERIAL_AVATAR) .Select(it => $"{it.Id}:{TextMapData.GetText(it.NameTextMapHash.ToString())}"), Encoding.UTF8); #endregion Avatar #region Dungeon // Dungeon sb.Clear(); foreach (var it in DungeonData.Values) { if (!TextMapData.TryGetText(it.NameTextMapHash.ToString(), out var name)) { var temp = GameData.Dungeons[it.Id]; if (temp != ItemMap.EmptyName) name = temp; } sb.AppendFormat("{0}:{1}", it.Id, name).AppendLine(); } File.WriteAllText( Path.Combine(dir, "Dungeon.txt"), sb.ToString(), Encoding.UTF8); //File.WriteAllLines( // Path.Combine(dir, "Dungeon.txt"), // DungeonData.Values.Select(it => $"{it.Id}:{TextMapData.GetText(it.NameTextMapHash.ToString())}"), // Encoding.UTF8); #endregion Dungeon #region Gadget // Gadget sb.Clear(); var gatherMap = new Dictionary(GatherData.Count); foreach (var it in GatherData.Where(it => !gatherMap.ContainsKey(it.GadgetId))) gatherMap.Add(it.GadgetId, it.ItemId); var furnitureMap = new Dictionary(HomeWorldFurnitureData.Count); foreach (var it in HomeWorldFurnitureData.Values.Where(it => it.FurnitureGadgetId != null)) foreach (var gadgetId in it.FurnitureGadgetId.Where(id => id > 0 && !furnitureMap.ContainsKey(id))) furnitureMap.Add(gadgetId, it.NameTextMapHash); var oldFirst = language.Key.StartsWith("zh"); foreach (var gadgetTypes in GadgetData.Values.OrderBy(it => it.Id).GroupBy(it => it.Type)) { sb.Append("// ").AppendLine(GadgetType.ToTranslatedString(gadgetTypes.Key, language.Key)); foreach (var it in gadgetTypes) { var name = oldFirst ? GameData.Gadgets[it.Id] : ItemMap.EmptyName; if (name == ItemMap.EmptyName) { if (!TextMapData.TryGetText(it.NameTextMapHash.ToString(), out name) && !TextMapData.TryGetText(it.InteractNameTextMapHash.ToString(), out name)) { if (gatherMap.TryGetValue(it.Id, out var itemId) && MaterialData.TryGetValue(itemId, out var item)) { name = TextMapData.GetText(item.NameTextMapHash.ToString()); } else if (furnitureMap.TryGetValue(it.Id, out var hash)) { name = TextMapData.GetText(hash.ToString()); } else if (!string.IsNullOrEmpty(it.JsonName)) { name = it.JsonName; } else if (!oldFirst) { var temp = GameData.Gadgets[it.Id]; if (temp != ItemMap.EmptyName) name = temp; } } } sb.AppendFormat("{0}:{1}", it.Id, name).AppendLine(); } sb.AppendLine(); } // 旧的数据 //var old = GameData.Gadgets.AllIds.Where(it => !GadgetData.ContainsKey(it)); //if (old.Any()) //{ // sb.AppendLine("// Old lines"); // foreach (var it in old) // sb.AppendFormat("{0}:{1}", it, GameData.Gadgets[it]).AppendLine(); //} File.WriteAllText( Path.Combine(dir, "Gadget.txt"), sb.ToString(), Encoding.UTF8); #endregion Gadget #region Item // Item sb.Clear(); foreach (var itemTypes in MaterialData.Values.GroupBy(it => it.ItemType)) { sb.Append("// ").AppendLine(itemTypes.Key.ToTranslatedString(language.Key)); if (itemTypes.Key == ItemType.ITEM_MATERIAL) { foreach (var m in itemTypes .GroupBy(it => it.MaterialType) .Where(it => it.Key != MaterialType.MATERIAL_NONE) .OrderBy(it => it.Average(m => m.Id))) { sb.Append("// ").AppendLine(m.Key.ToTranslatedString(language.Key)); if (m.Key == MaterialType.MATERIAL_BGM) { foreach (var materialData in m) { var param = int.Parse(materialData.ItemUse[0].UseParam[0]); var name = HomeWorldBgmData.ContainsKey(param) ? TextMapData.GetText( HomeWorldBgmData[param].BgmNameTextMapHash.ToString()) : string.Empty; sb.AppendFormat("{0}:{1} - {2}", materialData.Id, TextMapData.GetText(materialData.NameTextMapHash.ToString()), name); sb.AppendLine(); } } else { foreach (var materialData in m) sb.AppendFormat("{0}:{1}", materialData.Id, TextMapData.GetText(materialData.NameTextMapHash.ToString())).AppendLine(); } sb.AppendLine(); } } else { foreach (var materialData in itemTypes) sb.AppendFormat("{0}:{1}", materialData.Id, TextMapData.GetText(materialData.NameTextMapHash.ToString())).AppendLine(); sb.AppendLine(); } } sb.Append("// ").AppendLine(ItemType.ITEM_FURNITURE.ToTranslatedString(language.Key)); foreach (var value in HomeWorldFurnitureData.Values) sb.AppendFormat("{0}:{1}", value.Id, TextMapData.GetText(value.NameTextMapHash.ToString())).AppendLine(); File.WriteAllText(Path.Combine(dir, "Item.txt"), sb.ToString(), Encoding.UTF8); #endregion Item #region Monsters // Monsters sb.Clear(); foreach (var monsterType in MonsterData.Values.OrderBy(it => it.Id) .GroupBy(it => it.Type) .OrderBy(it => it.Key)) { sb.Append("// ").AppendLine(monsterType.Key.ToTranslatedString(language.Key)); foreach (var monsterData in monsterType) { if (TextMapData.TryGetText(monsterData.NameTextMapHash.ToString(), out var text)) { sb.AppendFormat("{0}:{1}", monsterData.Id, text); } else { var name = GameData.Monsters[monsterData.Id]; if (name == ItemMap.EmptyName) sb.AppendFormat("{0}:{1} - {2}", monsterData.Id, monsterData.MonsterName, text); else sb.AppendFormat("{0}:{1}", monsterData.Id, name); } sb.AppendLine(); } sb.AppendLine(); } File.WriteAllText( Path.Combine(dir, "Monsters.txt"), sb.ToString(), Encoding.UTF8); #endregion Monsters #region Quest // Quest sb.Clear(); foreach (var it in QuestData.Values.OrderBy(it => it.Id)) { var name = GameData.Quests[it.Id]; if (name == ItemMap.EmptyName) { sb.AppendFormat("{0}:{1} - {2}", it.Id, TextMapData.GetText(MainQuestData[it.MainId].TitleTextMapHash.ToString()), TextMapData.GetText(it.DescTextMapHash.ToString())); } else { sb.AppendFormat("{0}:{1}", it.Id, name); } sb.AppendLine(); } File.WriteAllText( Path.Combine(dir, "Quest.txt"), sb.ToString(), Encoding.UTF8); #endregion Quest #region Scene // Scene sb.Clear(); foreach (var it in DungeonData.Values) { var scene = SceneData[it.SceneId]; scene.NameTextMapHash = it.NameTextMapHash; } foreach (var it in SceneData.Values.OrderBy(it => it.Id)) { if (it.NameTextMapHash == 0 || !TextMapData.TryGetText(it.NameTextMapHash.ToString(), out var name)) { name = GameData.Scenes[it.Id]; if (name == ItemMap.EmptyName) name = it.ScriptData; } sb.AppendLine($"{it.Id}:{name}"); } File.WriteAllText( Path.Combine(dir, "Scene.txt"), sb.ToString(), Encoding.UTF8); #endregion Scene #region CutScene // CutScene File.WriteAllLines( Path.Combine(dir, "Cutscene.txt"), CutSceneData.Select(it => { var text = GameData.CutScenes[it.Id]; return text == ItemMap.EmptyName ? $"{it.Id}:{it.Path.Substring(it.Path.IndexOf('/') + 1)}" : $"{it.Id}:{text}"; }), Encoding.UTF8); #endregion CutScene #region Weapon // Weapon File.WriteAllLines( Path.Combine(dir, "Weapon.txt"), WeaponData.Values.Select(it => $"{it.Id}:{TextMapData.GetText(it.NameTextMapHash.ToString())}"), Encoding.UTF8); #endregion Weapon } #region SceneTag // SceneTag sb.Clear(); foreach (var scene in SceneTagData .GroupBy(it => it.SceneId)) { sb.Append("// ").AppendLine(scene.Key.ToString()); foreach (var sceneTag in scene) { sb.Append($"{sceneTag.Id}:{sceneTag.SceneTagName}"); if (sceneTag.IsDefaultValid) sb.Append(" (Default)"); sb.AppendLine(); } } File.WriteAllText( Path.Combine(projectResourcesDir, "SceneTag.txt"), sb.ToString(), Encoding.UTF8); #endregion #region Weather // Weather sb.Clear(); foreach (var scene in WeatherData .GroupBy(it => it.SceneId) .OrderBy(it => it.Key)) { sb.Append("// ").AppendLine(scene.Key.ToString()); foreach (var weather in scene) { var profileName = weather.ProfileName.Substring(weather.ProfileName.LastIndexOf('/') + 1) .Replace("ESP_", ""); sb.AppendLine($"{weather.AreaId}:{profileName}"); } } File.WriteAllText( Path.Combine(projectResourcesDir, "Weather.txt"), sb.ToString(), Encoding.UTF8); #endregion File.WriteAllLines( Path.Combine(projectResourcesDir, "AvatarColor.txt"), AvatarData.Values.Select(it => $"{it.Id % 1000 + 1000}:{(int)it.QualityType}"), Encoding.UTF8); File.WriteAllLines( Path.Combine(projectResourcesDir, "WeaponColor.txt"), WeaponData.Values.Select(it => $"{it.Id}:{(it.RankLevel >= 5 ? "yellow" : it.RankLevel >= 4 ? "purple" : "blue")}"), Encoding.UTF8); } finally { Thread.CurrentThread.CurrentUICulture = currentCultureInfo; } } } }