diff --git a/Source/GrasscutterTools/Game/Data/Excels/AchievementData.cs b/Source/GrasscutterTools/Game/Data/Excels/AchievementData.cs new file mode 100644 index 0000000..c4e7e47 --- /dev/null +++ b/Source/GrasscutterTools/Game/Data/Excels/AchievementData.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; + +namespace GrasscutterTools.Game.Data.Excels +{ + [ResourceType("AchievementExcelConfigData.json")] + internal class AchievementData : GameResource + { + [JsonProperty("isDisuse")] + public bool IsDisuse { get; set; } + + public bool IsUsed => !IsDisuse; + } +} diff --git a/Source/GrasscutterTools/Game/Data/Excels/AvatarData.cs b/Source/GrasscutterTools/Game/Data/Excels/AvatarData.cs new file mode 100644 index 0000000..040cd89 --- /dev/null +++ b/Source/GrasscutterTools/Game/Data/Excels/AvatarData.cs @@ -0,0 +1,11 @@ +using Newtonsoft.Json; + +namespace GrasscutterTools.Game.Data.Excels +{ + [ResourceType("AvatarExcelConfigData.json")] + internal class AvatarData : GameResource + { + [JsonProperty("qualityType")] + public string QualityType { get; set; } + } +} diff --git a/Source/GrasscutterTools/Game/Data/Excels/DungeonData.cs b/Source/GrasscutterTools/Game/Data/Excels/DungeonData.cs new file mode 100644 index 0000000..17b246b --- /dev/null +++ b/Source/GrasscutterTools/Game/Data/Excels/DungeonData.cs @@ -0,0 +1,7 @@ +namespace GrasscutterTools.Game.Data.Excels +{ + [ResourceType("DungeonExcelConfigData.json")] + internal class DungeonData : GameResource + { + } +} diff --git a/Source/GrasscutterTools/Game/Data/Excels/GadgetData.cs b/Source/GrasscutterTools/Game/Data/Excels/GadgetData.cs new file mode 100644 index 0000000..d13eab3 --- /dev/null +++ b/Source/GrasscutterTools/Game/Data/Excels/GadgetData.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GrasscutterTools.Game.Data.Excels +{ + [ResourceType("GadgetExcelConfigData.json")] + internal class GadgetData + { + } +} diff --git a/Source/GrasscutterTools/Game/Data/Excels/HomeWorldBgmData.cs b/Source/GrasscutterTools/Game/Data/Excels/HomeWorldBgmData.cs new file mode 100644 index 0000000..ac407bf --- /dev/null +++ b/Source/GrasscutterTools/Game/Data/Excels/HomeWorldBgmData.cs @@ -0,0 +1,14 @@ +using Newtonsoft.Json; + +namespace GrasscutterTools.Game.Data.Excels +{ + [ResourceType("HomeWorldBgmExcelConfigData.json")] + internal class HomeWorldBgmData : GameResource + { + [JsonProperty("homeBgmId")] + public override int Id { get; set; } + + [JsonProperty("bgmNameTextMapHash")] + public long BgmNameTextMapHash { get; set; } + } +} diff --git a/Source/GrasscutterTools/Game/Data/Excels/HomeWorldFurnitureData.cs b/Source/GrasscutterTools/Game/Data/Excels/HomeWorldFurnitureData.cs new file mode 100644 index 0000000..98ea3e4 --- /dev/null +++ b/Source/GrasscutterTools/Game/Data/Excels/HomeWorldFurnitureData.cs @@ -0,0 +1,7 @@ +namespace GrasscutterTools.Game.Data.Excels +{ + [ResourceType("HomeWorldFurnitureExcelConfigData.json")] + internal class HomeWorldFurnitureData : GameResource + { + } +} diff --git a/Source/GrasscutterTools/Game/Data/Excels/MainQuestData.cs b/Source/GrasscutterTools/Game/Data/Excels/MainQuestData.cs new file mode 100644 index 0000000..b1f69e1 --- /dev/null +++ b/Source/GrasscutterTools/Game/Data/Excels/MainQuestData.cs @@ -0,0 +1,9 @@ +using Newtonsoft.Json; + +namespace GrasscutterTools.Game.Data.Excels +{ + [ResourceType("MainQuestExcelConfigData.json")] + internal class MainQuestData : GameResource + { + } +} diff --git a/Source/GrasscutterTools/Game/Data/Excels/MaterialData.cs b/Source/GrasscutterTools/Game/Data/Excels/MaterialData.cs new file mode 100644 index 0000000..40744e2 --- /dev/null +++ b/Source/GrasscutterTools/Game/Data/Excels/MaterialData.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using GrasscutterTools.Game.Inventory; +using GrasscutterTools.Game.Props; + +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace GrasscutterTools.Game.Data.Excels +{ + [ResourceType("MaterialExcelConfigData.json")] + internal class MaterialData : GameResource + { + [JsonProperty("itemType"), JsonConverter(typeof(StringEnumConverter))] + public ItemType ItemType { get; set; } + + [JsonProperty("materialType"), JsonConverter(typeof(StringEnumConverter))] + public MaterialType MaterialType { get; set; } + + [JsonProperty("itemUse")] + public List ItemUse { get; set; } + } +} diff --git a/Source/GrasscutterTools/Game/Data/Excels/MonsterData.cs b/Source/GrasscutterTools/Game/Data/Excels/MonsterData.cs new file mode 100644 index 0000000..834a168 --- /dev/null +++ b/Source/GrasscutterTools/Game/Data/Excels/MonsterData.cs @@ -0,0 +1,15 @@ +using GrasscutterTools.Game.Props; +using Newtonsoft.Json; + +namespace GrasscutterTools.Game.Data.Excels +{ + [ResourceType("MonsterExcelConfigData.json")] + internal class MonsterData : GameResource + { + [JsonProperty("monsterName")] + public string MonsterName { get; set; } + + [JsonProperty("type")] + public MonsterType Type { get; set; } + } +} diff --git a/Source/GrasscutterTools/Game/Data/Excels/QuestData.cs b/Source/GrasscutterTools/Game/Data/Excels/QuestData.cs new file mode 100644 index 0000000..12b78ff --- /dev/null +++ b/Source/GrasscutterTools/Game/Data/Excels/QuestData.cs @@ -0,0 +1,17 @@ +using Newtonsoft.Json; + +namespace GrasscutterTools.Game.Data.Excels +{ + [ResourceType("QuestExcelConfigData.json")] + internal class QuestData : GameResource + { + [JsonProperty("subId")] + public override int Id { get; set; } + + [JsonProperty("mainId")] + public int MainId { get; set; } + + [JsonProperty("order")] + public int Order { get; set; } + } +} diff --git a/Source/GrasscutterTools/Game/Data/Excels/ReliquaryData.cs b/Source/GrasscutterTools/Game/Data/Excels/ReliquaryData.cs new file mode 100644 index 0000000..c64e068 --- /dev/null +++ b/Source/GrasscutterTools/Game/Data/Excels/ReliquaryData.cs @@ -0,0 +1,7 @@ +namespace GrasscutterTools.Game.Data.Excels +{ + [ResourceType("ReliquaryExcelConfigData.json")] + internal class ReliquaryData : GameResource + { + } +} diff --git a/Source/GrasscutterTools/Game/Data/Excels/SceneData.cs b/Source/GrasscutterTools/Game/Data/Excels/SceneData.cs new file mode 100644 index 0000000..fd47b5a --- /dev/null +++ b/Source/GrasscutterTools/Game/Data/Excels/SceneData.cs @@ -0,0 +1,15 @@ +using GrasscutterTools.Game.Props; +using Newtonsoft.Json; + +namespace GrasscutterTools.Game.Data.Excels +{ + [ResourceType("SceneExcelConfigData.json")] + internal class SceneData : GameResource + { + [JsonProperty("type")] + public SceneType SceneType { get; set; } + + [JsonProperty("scriptData")] + public string ScriptData { get; set; } + } +} diff --git a/Source/GrasscutterTools/Game/Data/Excels/WeaponData.cs b/Source/GrasscutterTools/Game/Data/Excels/WeaponData.cs new file mode 100644 index 0000000..2609468 --- /dev/null +++ b/Source/GrasscutterTools/Game/Data/Excels/WeaponData.cs @@ -0,0 +1,11 @@ +using Newtonsoft.Json; + +namespace GrasscutterTools.Game.Data.Excels +{ + [ResourceType("WeaponExcelConfigData.json")] + internal class WeaponData : GameResource + { + [JsonProperty("rankLevel")] + public int RankLevel { get; set; } + } +} diff --git a/Source/GrasscutterTools/Game/Data/GameResource.cs b/Source/GrasscutterTools/Game/Data/GameResource.cs new file mode 100644 index 0000000..80ef28d --- /dev/null +++ b/Source/GrasscutterTools/Game/Data/GameResource.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace GrasscutterTools.Game.Data +{ + internal abstract class GameResource + { + [JsonProperty("id")] + public virtual int Id { get; set; } + + [JsonProperty("nameTextMapHash")] + public long NameTextMapHash { get; set; } + + [JsonProperty("titleTextMapHash")] + public string TitleTextMapHash { get; set; } + + [JsonProperty("descTextMapHash")] + public long DescTextMapHash { get; set; } + } +} diff --git a/Source/GrasscutterTools/Game/Data/GameResources.cs b/Source/GrasscutterTools/Game/Data/GameResources.cs new file mode 100644 index 0000000..fea0e40 --- /dev/null +++ b/Source/GrasscutterTools/Game/Data/GameResources.cs @@ -0,0 +1,249 @@ +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.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 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 Dictionary WeaponData { get; set; } + + public TextMapData TextMapData { 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 gameResourceType = type.GetGenericArguments()[1]; + var attributes = (ResourceTypeAttribute[])gameResourceType.GetCustomAttributes(typeof(ResourceTypeAttribute), true); + if (attributes.Length < 1) continue; + var dataFile = Path.Combine(resourcesDirPath, "ExcelBinOutput", attributes[0].Name); + var data = LoadDataFile(gameResourceType, dataFile); + property.SetValue(this, data, null); + } + + + var illegalWeaponIds = new SparseSet( + "10000-10008, 11411, 11506-11508, 12505, 12506, 12508, 12509," + + "13503, 13506, 14411, 14503, 14505, 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) + { + var list = (IList)JsonConvert.DeserializeObject(File.ReadAllText(path), typeof(List<>).MakeGenericType(type)); + if (list == null) return null; + + var dicType = typeof(Dictionary<,>).MakeGenericType(typeof(int), type); + var dic = (IDictionary)Activator.CreateInstance(dicType); + foreach (GameResource gameResource in list) + dic.Add(gameResource.Id, gameResource); + return dic; + } + + 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(); + + 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); + + File.WriteAllLines( + Path.Combine(dir, "Artifact.txt"), + ReliquaryData.Values.OrderBy(it => it.Id).Select(it => $"{it.Id}:{TextMapData.GetText(it.NameTextMapHash.ToString())}"), + Encoding.UTF8); + + 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); + + File.WriteAllLines( + Path.Combine(dir, "Dungeon.txt"), + DungeonData.Values.Select(it => $"{it.Id}:{TextMapData.GetText(it.NameTextMapHash.ToString())}"), + Encoding.UTF8); + + 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) + sb.AppendFormat("{0}:{1} - {2}", + materialData.Id, + TextMapData.GetText(materialData.NameTextMapHash.ToString()), + TextMapData.GetText(HomeWorldBgmData[int.Parse(materialData.ItemUse[0].UseParam[0])].BgmNameTextMapHash.ToString()) + ).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); + + + 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); + + File.WriteAllLines( + Path.Combine(dir, "Quest.txt"), + QuestData.Values.OrderBy(it => it.Id).Select(it => $"{it.Id}:{TextMapData.GetText(MainQuestData[it.MainId].TitleTextMapHash)} - {TextMapData.GetText(it.DescTextMapHash.ToString())}"), + Encoding.UTF8); + + //File.WriteAllLines( + // Path.Combine(dir, "Scene.txt"), + // SceneData.Values.Select(it => $"{it.Id}:{it.ScriptData}"), + // Encoding.UTF8); + + File.WriteAllLines( + Path.Combine(dir, "Weapon.txt"), + WeaponData.Values.Select(it => $"{it.Id}:{TextMapData.GetText(it.NameTextMapHash.ToString())}"), + Encoding.UTF8); + } + } + finally + { + Thread.CurrentThread.CurrentUICulture = currentCultureInfo; + } + } + } +} diff --git a/Source/GrasscutterTools/Game/Data/ResourceTypeAttribute.cs b/Source/GrasscutterTools/Game/Data/ResourceTypeAttribute.cs new file mode 100644 index 0000000..569fdb0 --- /dev/null +++ b/Source/GrasscutterTools/Game/Data/ResourceTypeAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace GrasscutterTools.Game.Data +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false)] + public class ResourceTypeAttribute : Attribute + { + public string Name { get; set; } + + public ResourceTypeAttribute(string name) + { + Name = name; + } + } +} diff --git a/Source/GrasscutterTools/Game/Inventory/ItemType.cs b/Source/GrasscutterTools/Game/Inventory/ItemType.cs new file mode 100644 index 0000000..ced247b --- /dev/null +++ b/Source/GrasscutterTools/Game/Inventory/ItemType.cs @@ -0,0 +1,48 @@ + +// ReSharper disable InconsistentNaming + +using System.Collections.Generic; + +namespace GrasscutterTools.Game.Inventory +{ + internal enum ItemType + { + ITEM_NONE = 0, + ITEM_VIRTUAL = 1, + ITEM_MATERIAL = 2, + ITEM_RELIQUARY = 3, + ITEM_WEAPON = 4, + ITEM_DISPLAY = 5, + ITEM_FURNITURE = 6, + } + + + internal static class ItemTypeExtension + { + private static readonly Dictionary TextMapCHS = new Dictionary + { + [ItemType.ITEM_NONE] = "未分类", + [ItemType.ITEM_VIRTUAL] = "虚拟道具", + [ItemType.ITEM_MATERIAL] = "材料", + [ItemType.ITEM_RELIQUARY] = "圣遗物", + [ItemType.ITEM_WEAPON] = "物品", + [ItemType.ITEM_DISPLAY] = "任务", + [ItemType.ITEM_FURNITURE] = "尘歌壶摆设", + }; + private static readonly Dictionary TextMapEN = new Dictionary + { + [ItemType.ITEM_NONE] = "None", + [ItemType.ITEM_VIRTUAL] = "Virtual", + [ItemType.ITEM_MATERIAL] = "Material", + [ItemType.ITEM_RELIQUARY] = "Reliquary", + [ItemType.ITEM_WEAPON] = "Weapon", + [ItemType.ITEM_DISPLAY] = "Display", + [ItemType.ITEM_FURNITURE] = "Furniture", + }; + + public static string ToTranslatedString(this ItemType materialType, string language) + { + return language.StartsWith("zh") ? TextMapCHS[materialType] : TextMapEN[materialType]; + } + } +} diff --git a/Source/GrasscutterTools/Game/Inventory/MaterialType.cs b/Source/GrasscutterTools/Game/Inventory/MaterialType.cs new file mode 100644 index 0000000..89b7c15 --- /dev/null +++ b/Source/GrasscutterTools/Game/Inventory/MaterialType.cs @@ -0,0 +1,175 @@ + +// ReSharper disable InconsistentNaming +// ReSharper disable IdentifierTypo + +using System; +using System.Collections.Generic; + +namespace GrasscutterTools.Game.Inventory +{ + /// + /// 材料类型 + /// + internal enum MaterialType + { + MATERIAL_NONE = 0, + MATERIAL_FOOD = 1, + MATERIAL_QUEST = 2, + MATERIAL_EXCHANGE = 4, + MATERIAL_CONSUME = 5, + MATERIAL_EXP_FRUIT = 6, + MATERIAL_AVATAR = 7, + MATERIAL_ADSORBATE = 8, + MATERIAL_CRICKET = 9, + MATERIAL_ELEM_CRYSTAL = 10, + MATERIAL_WEAPON_EXP_STONE = 11, + MATERIAL_CHEST = 12, + MATERIAL_RELIQUARY_MATERIAL = 13, + MATERIAL_AVATAR_MATERIAL = 14, + MATERIAL_NOTICE_ADD_HP = 15, + MATERIAL_SEA_LAMP = 16, + MATERIAL_SELECTABLE_CHEST = 17, + MATERIAL_FLYCLOAK = 18, + MATERIAL_NAMECARD = 19, + MATERIAL_TALENT = 20, + MATERIAL_WIDGET = 21, + MATERIAL_CHEST_BATCH_USE = 22, + MATERIAL_FAKE_ABSORBATE = 23, + MATERIAL_CONSUME_BATCH_USE = 24, + MATERIAL_WOOD = 25, + MATERIAL_FURNITURE_FORMULA = 27, + MATERIAL_CHANNELLER_SLAB_BUFF = 28, + MATERIAL_FURNITURE_SUITE_FORMULA = 29, + MATERIAL_COSTUME = 30, + MATERIAL_HOME_SEED = 31, + MATERIAL_FISH_BAIT = 32, + MATERIAL_FISH_ROD = 33, + MATERIAL_SUMO_BUFF = 34, // sumo 活动道具,never appear + MATERIAL_FIREWORKS = 35, + MATERIAL_BGM = 36, + MATERIAL_SPICE_FOOD = 37, + MATERIAL_ACTIVITY_ROBOT = 38, + MATERIAL_ACTIVITY_GEAR = 39, + MATERIAL_ACTIVITY_JIGSAW = 40, + MATERIAL_ARANARA = 41, + MATERIAL_GCG_CARD = 42, + MATERIAL_GCG_CARD_FACE = 43, // 影幻卡面 + MATERIAL_GCG_CARD_BACK = 44, + MATERIAL_GCG_FIELD = 45, + MATERIAL_DESHRET_MANUAL = 46, + MATERIAL_RENAME_ITEM = 47, + MATERIAL_GCG_EXCHANGE_ITEM = 48, + MATERIAL_QUEST_EVENT_BOOK = 49, + } + + internal static class MaterialTypeExtension + { + private static readonly Dictionary TextMapCHS = new Dictionary + { + [MaterialType.MATERIAL_NONE] = "空", + [MaterialType.MATERIAL_FOOD] = "食物", + [MaterialType.MATERIAL_QUEST] = "任务", + [MaterialType.MATERIAL_EXCHANGE] = "收集物", + [MaterialType.MATERIAL_CONSUME] = "消耗品", + [MaterialType.MATERIAL_EXP_FRUIT] = "经验书", + [MaterialType.MATERIAL_AVATAR] = "角色", + [MaterialType.MATERIAL_ADSORBATE] = "能量球", + [MaterialType.MATERIAL_CRICKET] = "蛐蛐", + [MaterialType.MATERIAL_ELEM_CRYSTAL] = "神瞳", + [MaterialType.MATERIAL_WEAPON_EXP_STONE] = "武器锻造矿", + [MaterialType.MATERIAL_CHEST] = "宝箱", + [MaterialType.MATERIAL_RELIQUARY_MATERIAL] = "圣遗物经验瓶", + [MaterialType.MATERIAL_AVATAR_MATERIAL] = "角色天赋材料", + [MaterialType.MATERIAL_NOTICE_ADD_HP] = "回血食物", + [MaterialType.MATERIAL_SEA_LAMP] = "海灯节", + [MaterialType.MATERIAL_SELECTABLE_CHEST] = "自选礼包", + [MaterialType.MATERIAL_FLYCLOAK] = "风之翼", + [MaterialType.MATERIAL_NAMECARD] = "名片卡", + [MaterialType.MATERIAL_TALENT] = "天赋材料", + [MaterialType.MATERIAL_WIDGET] = "装饰物", + [MaterialType.MATERIAL_CHEST_BATCH_USE] = "礼包", + [MaterialType.MATERIAL_FAKE_ABSORBATE] = "MATERIAL_FAKE_ABSORBATE", + [MaterialType.MATERIAL_CONSUME_BATCH_USE] = "树脂", + [MaterialType.MATERIAL_WOOD] = "树木", + [MaterialType.MATERIAL_FURNITURE_FORMULA] = "尘歌壶室内摆设", + [MaterialType.MATERIAL_CHANNELLER_SLAB_BUFF] = "增益Buff", + [MaterialType.MATERIAL_FURNITURE_SUITE_FORMULA] = "尘歌壶摆设套装", + [MaterialType.MATERIAL_COSTUME] = "皮肤", + [MaterialType.MATERIAL_HOME_SEED] = "种子", + [MaterialType.MATERIAL_FISH_BAIT] = "鱼饵", + [MaterialType.MATERIAL_FISH_ROD] = "鱼竿", + [MaterialType.MATERIAL_SUMO_BUFF] = "MATERIAL_SUMO_BUFF", + [MaterialType.MATERIAL_FIREWORKS] = "烟花", + [MaterialType.MATERIAL_BGM] = "旋曜玉帛", + [MaterialType.MATERIAL_SPICE_FOOD] = "香气四溢的食物", + [MaterialType.MATERIAL_ACTIVITY_ROBOT] = "活动-兑换券", + [MaterialType.MATERIAL_ACTIVITY_GEAR] = "活动-齿轮", + [MaterialType.MATERIAL_ACTIVITY_JIGSAW] = "活动-部件", + [MaterialType.MATERIAL_ARANARA] = "兰纳罗", + [MaterialType.MATERIAL_GCG_CARD] = "七圣召唤-卡片", + [MaterialType.MATERIAL_GCG_CARD_FACE] = "七圣召唤-卡片-正面", + [MaterialType.MATERIAL_GCG_CARD_BACK] = "七圣召唤-卡片-背面", + [MaterialType.MATERIAL_GCG_FIELD] = "七圣召唤-卡片-场地", + [MaterialType.MATERIAL_DESHRET_MANUAL] = "沙漠书", + [MaterialType.MATERIAL_RENAME_ITEM] = "改名卡", + [MaterialType.MATERIAL_GCG_EXCHANGE_ITEM] = "七圣召唤-特殊卡", + [MaterialType.MATERIAL_QUEST_EVENT_BOOK] = "案件记录册", + }; + private static readonly Dictionary TextMapEN = new Dictionary + { + [MaterialType.MATERIAL_NONE] = "None", + [MaterialType.MATERIAL_FOOD] = "Food", + [MaterialType.MATERIAL_QUEST] = "Quest", + [MaterialType.MATERIAL_EXCHANGE] = "Exchange", + [MaterialType.MATERIAL_CONSUME] = "Consume", + [MaterialType.MATERIAL_EXP_FRUIT] = "Exp_fruit", + [MaterialType.MATERIAL_AVATAR] = "Avatar", + [MaterialType.MATERIAL_ADSORBATE] = "Adsorbate", + [MaterialType.MATERIAL_CRICKET] = "Cricket", + [MaterialType.MATERIAL_ELEM_CRYSTAL] = "Elem_crystal", + [MaterialType.MATERIAL_WEAPON_EXP_STONE] = "Weapon_exp_stone", + [MaterialType.MATERIAL_CHEST] = "Chest", + [MaterialType.MATERIAL_RELIQUARY_MATERIAL] = "Reliquary_material", + [MaterialType.MATERIAL_AVATAR_MATERIAL] = "Avatar_material", + [MaterialType.MATERIAL_NOTICE_ADD_HP] = "Notice_add_hp", + [MaterialType.MATERIAL_SEA_LAMP] = "Sea_lamp", + [MaterialType.MATERIAL_SELECTABLE_CHEST] = "Selectable_chest", + [MaterialType.MATERIAL_FLYCLOAK] = "Flycloak", + [MaterialType.MATERIAL_NAMECARD] = "Namecard", + [MaterialType.MATERIAL_TALENT] = "Talent", + [MaterialType.MATERIAL_WIDGET] = "Widget", + [MaterialType.MATERIAL_CHEST_BATCH_USE] = "Chest_batch_use", + [MaterialType.MATERIAL_FAKE_ABSORBATE] = "Fake_absorbate", + [MaterialType.MATERIAL_CONSUME_BATCH_USE] = "Consume_batch_use", + [MaterialType.MATERIAL_WOOD] = "Wood", + [MaterialType.MATERIAL_FURNITURE_FORMULA] = "Furniture_formula", + [MaterialType.MATERIAL_CHANNELLER_SLAB_BUFF] = "Channeller_slab_buff", + [MaterialType.MATERIAL_FURNITURE_SUITE_FORMULA] = "Furniture_suite_formula", + [MaterialType.MATERIAL_COSTUME] = "Costume", + [MaterialType.MATERIAL_HOME_SEED] = "Home_seed", + [MaterialType.MATERIAL_FISH_BAIT] = "Fish_bait", + [MaterialType.MATERIAL_FISH_ROD] = "Fish_rod", + [MaterialType.MATERIAL_SUMO_BUFF] = "Sumo_buff", + [MaterialType.MATERIAL_FIREWORKS] = "Fireworks", + [MaterialType.MATERIAL_BGM] = "Bgm", + [MaterialType.MATERIAL_SPICE_FOOD] = "Spice_food", + [MaterialType.MATERIAL_ACTIVITY_ROBOT] = "Activity_robot", + [MaterialType.MATERIAL_ACTIVITY_GEAR] = "Activity_gear", + [MaterialType.MATERIAL_ACTIVITY_JIGSAW] = "Activity_jigsaw", + [MaterialType.MATERIAL_ARANARA] = "Aranara", + [MaterialType.MATERIAL_GCG_CARD] = "Gcg_card", + [MaterialType.MATERIAL_GCG_CARD_FACE] = "Gcg_card_face", + [MaterialType.MATERIAL_GCG_CARD_BACK] = "Gcg_card_back", + [MaterialType.MATERIAL_GCG_FIELD] = "Gcg_field", + [MaterialType.MATERIAL_DESHRET_MANUAL] = "Deshret_manual", + [MaterialType.MATERIAL_RENAME_ITEM] = "Rename_item", + [MaterialType.MATERIAL_GCG_EXCHANGE_ITEM] = "Gcg_exchange_item", + [MaterialType.MATERIAL_QUEST_EVENT_BOOK] = "Quest_event_book", + }; + + public static string ToTranslatedString(this MaterialType materialType, string language) + { + return language.StartsWith("zh") ? TextMapCHS[materialType] : TextMapEN[materialType]; + } + } +} diff --git a/Source/GrasscutterTools/Game/Props/ItemUseData.cs b/Source/GrasscutterTools/Game/Props/ItemUseData.cs new file mode 100644 index 0000000..b8679e6 --- /dev/null +++ b/Source/GrasscutterTools/Game/Props/ItemUseData.cs @@ -0,0 +1,10 @@ +using Newtonsoft.Json; + +namespace GrasscutterTools.Game.Props +{ + internal class ItemUseData + { + [JsonProperty("useParam")] + public string[] UseParam { get; set; } + } +} diff --git a/Source/GrasscutterTools/Game/Props/MonsterType.cs b/Source/GrasscutterTools/Game/Props/MonsterType.cs new file mode 100644 index 0000000..bb94ceb --- /dev/null +++ b/Source/GrasscutterTools/Game/Props/MonsterType.cs @@ -0,0 +1,51 @@ + +// ReSharper disable InconsistentNaming + +using System.Collections.Generic; + +namespace GrasscutterTools.Game.Props +{ + /// + /// 怪物种类 + /// + internal enum MonsterType + { + MONSTER_NONE = 0, + MONSTER_ORDINARY = 1, + MONSTER_BOSS = 2, + MONSTER_ENV_ANIMAL = 3, + MONSTER_LITTLE_MONSTER = 4, + MONSTER_FISH = 5, + MONSTER_PARTNER = 6, + } + + + internal static class ItemTypeExtension + { + private static readonly Dictionary TextMapCHS = new Dictionary + { + [MonsterType.MONSTER_NONE] = "未分类", + [MonsterType.MONSTER_ORDINARY] = "普通怪物", + [MonsterType.MONSTER_BOSS] = "BOSS", + [MonsterType.MONSTER_ENV_ANIMAL] = "动物", + [MonsterType.MONSTER_LITTLE_MONSTER] = "小怪", + [MonsterType.MONSTER_FISH] = "鱼", + [MonsterType.MONSTER_PARTNER] = "友军", + }; + private static readonly Dictionary TextMapEN = new Dictionary + { + [MonsterType.MONSTER_NONE] = "None", + [MonsterType.MONSTER_ORDINARY] = "Ordinary", + [MonsterType.MONSTER_BOSS] = "Boss", + [MonsterType.MONSTER_ENV_ANIMAL] = "Env_animal", + [MonsterType.MONSTER_LITTLE_MONSTER] = "Little_monster", + [MonsterType.MONSTER_FISH] = "Fish", + [MonsterType.MONSTER_PARTNER] = "Partner", + }; + + public static string ToTranslatedString(this MonsterType materialType, string language) + { + return language.StartsWith("zh") ? TextMapCHS[materialType] : TextMapEN[materialType]; + } + } +} diff --git a/Source/GrasscutterTools/Game/Props/SceneType.cs b/Source/GrasscutterTools/Game/Props/SceneType.cs new file mode 100644 index 0000000..9b5b9d8 --- /dev/null +++ b/Source/GrasscutterTools/Game/Props/SceneType.cs @@ -0,0 +1,16 @@ + +// ReSharper disable InconsistentNaming + +namespace GrasscutterTools.Game.Props +{ + public enum SceneType + { + SCENE_NONE = 0, + SCENE_WORLD = 1, + SCENE_DUNGEON = 2, + SCENE_ROOM = 3, + SCENE_HOME_WORLD = 4, + SCENE_HOME_ROOM = 5, + SCENE_ACTIVITY = 6, + } +} diff --git a/Source/GrasscutterTools/Game/TextMapData.cs b/Source/GrasscutterTools/Game/TextMapData.cs index 6314500..735bbef 100644 --- a/Source/GrasscutterTools/Game/TextMapData.cs +++ b/Source/GrasscutterTools/Game/TextMapData.cs @@ -44,6 +44,8 @@ namespace GrasscutterTools.Game private void LoadManualTextMap(string manualTextMapPath) { + if (!File.Exists(manualTextMapPath)) return; + using (var fs = File.OpenRead(manualTextMapPath)) using (var sr = new StreamReader(fs)) using (var reader = new JsonTextReader(sr)) @@ -86,11 +88,30 @@ namespace GrasscutterTools.Game } } + public bool Contains(string textMapPath) => TextMap.ContainsKey(textMapPath) || DefaultTextMap.ContainsKey(textMapPath); + public string GetText(string textMapHash) { return TextMap.TryGetValue(textMapHash, out var text) ? text - : DefaultTextMap.TryGetValue(textMapHash, out text) ? text - : "???"; + : DefaultTextMap.TryGetValue(textMapHash, out text) ? "[CHS] - " + text + : "[N/A] " + textMapHash; + } + + public bool TryGetText(string textMapHash, out string text) + { + if (TextMap.TryGetValue(textMapHash, out text)) + { + return true; + } + + if (DefaultTextMap.TryGetValue(textMapHash, out text)) + { + text = "[CHS] - " + text; + return true; + } + + text = "[N/A] " + textMapHash; + return false; } } } \ No newline at end of file diff --git a/Source/GrasscutterTools/GrasscutterTools.csproj b/Source/GrasscutterTools/GrasscutterTools.csproj index 344b1f4..d558504 100644 --- a/Source/GrasscutterTools/GrasscutterTools.csproj +++ b/Source/GrasscutterTools/GrasscutterTools.csproj @@ -139,6 +139,21 @@ + + + + + + + + + + + + + + + @@ -148,12 +163,18 @@ + + + + + + @@ -295,6 +316,7 @@ + diff --git a/Source/GrasscutterTools/Pages/PageTools.Designer.cs b/Source/GrasscutterTools/Pages/PageTools.Designer.cs index 34b8c94..445c806 100644 --- a/Source/GrasscutterTools/Pages/PageTools.Designer.cs +++ b/Source/GrasscutterTools/Pages/PageTools.Designer.cs @@ -34,7 +34,7 @@ this.TxtProjectResRoot = new System.Windows.Forms.TextBox(); this.LblGcResRoot = new System.Windows.Forms.Label(); this.TxtGcResRoot = new System.Windows.Forms.TextBox(); - this.BtnUpdateDungeon = new System.Windows.Forms.Button(); + this.BtnUpdateAllResources = new System.Windows.Forms.Button(); this.BtnUpdateActivity = new System.Windows.Forms.Button(); this.SuspendLayout(); // @@ -90,15 +90,15 @@ this.TxtGcResRoot.Size = new System.Drawing.Size(413, 23); this.TxtGcResRoot.TabIndex = 4; // - // BtnUpdateDungeon + // BtnUpdateAllResources // - this.BtnUpdateDungeon.Location = new System.Drawing.Point(41, 100); - this.BtnUpdateDungeon.Name = "BtnUpdateDungeon"; - this.BtnUpdateDungeon.Size = new System.Drawing.Size(150, 30); - this.BtnUpdateDungeon.TabIndex = 0; - this.BtnUpdateDungeon.Text = "Update Dungeon"; - this.BtnUpdateDungeon.UseVisualStyleBackColor = true; - this.BtnUpdateDungeon.Click += new System.EventHandler(this.BtnUpdateDungeon_Click); + this.BtnUpdateAllResources.Location = new System.Drawing.Point(41, 100); + this.BtnUpdateAllResources.Name = "BtnUpdateAllResources"; + this.BtnUpdateAllResources.Size = new System.Drawing.Size(150, 30); + this.BtnUpdateAllResources.TabIndex = 0; + this.BtnUpdateAllResources.Text = "Update All Resources"; + this.BtnUpdateAllResources.UseVisualStyleBackColor = true; + this.BtnUpdateAllResources.Click += new System.EventHandler(this.BtnUpdateAllResources_Click); // // BtnUpdateActivity // @@ -119,7 +119,7 @@ this.Controls.Add(this.TxtProjectResRoot); this.Controls.Add(this.LblProjectResRoot); this.Controls.Add(this.BtnUpdateActivity); - this.Controls.Add(this.BtnUpdateDungeon); + this.Controls.Add(this.BtnUpdateAllResources); this.Controls.Add(this.BtnConvertCutScene); this.Controls.Add(this.BtnUpdateResources); this.Name = "PageTools"; @@ -136,7 +136,7 @@ private System.Windows.Forms.TextBox TxtProjectResRoot; private System.Windows.Forms.Label LblGcResRoot; private System.Windows.Forms.TextBox TxtGcResRoot; - private System.Windows.Forms.Button BtnUpdateDungeon; + private System.Windows.Forms.Button BtnUpdateAllResources; private System.Windows.Forms.Button BtnUpdateActivity; } } diff --git a/Source/GrasscutterTools/Pages/PageTools.cs b/Source/GrasscutterTools/Pages/PageTools.cs index 6b809c2..e5ad773 100644 --- a/Source/GrasscutterTools/Pages/PageTools.cs +++ b/Source/GrasscutterTools/Pages/PageTools.cs @@ -8,6 +8,7 @@ using System.Windows.Forms; using GrasscutterTools.Game; using GrasscutterTools.Game.Activity; using GrasscutterTools.Game.CutScene; +using GrasscutterTools.Game.Data; using GrasscutterTools.Game.Dungeon; using GrasscutterTools.Properties; @@ -121,26 +122,25 @@ namespace GrasscutterTools.Pages } } - private TextMapData TextMapData; - private void BtnUpdateDungeon_Click(object sender, EventArgs e) + + + + private TextMapData TextMapData; + private GameResources GameResources; + + private void BtnUpdateAllResources_Click(object sender, EventArgs e) { try { if (!CheckInputPaths()) return; - - var json = File.ReadAllText( - Path.Combine(TxtGcResRoot.Text, "ExcelBinOutput", "DungeonExcelConfigData.json"), - Encoding.UTF8); - var dungeons = JsonConvert.DeserializeObject>(json); - + if (TextMapData == null) TextMapData = new TextMapData(TxtGcResRoot.Text); + if (GameResources == null) + GameResources = new GameResources(TxtGcResRoot.Text, TextMapData); - UpdateDungeonsForLanguage(dungeons, "TextMapCHS", "zh-cn"); - UpdateDungeonsForLanguage(dungeons, "TextMapCHT", "zh-tw"); - UpdateDungeonsForLanguage(dungeons, "TextMapEN", "en-us"); - UpdateDungeonsForLanguage(dungeons, "TextMapRU", "ru-ru"); + GameResources.ConvertResources(TxtProjectResRoot.Text); MessageBox.Show("OK", Resources.Tips, MessageBoxButtons.OK); } catch (Exception ex) @@ -149,17 +149,12 @@ namespace GrasscutterTools.Pages } } - private void UpdateDungeonsForLanguage(IEnumerable dungeons, string textMap, string language) - { - var i = Array.IndexOf(TextMapData.TextMapFiles, textMap); - TextMapData.LoadTextMap(TextMapData.TextMapFilePaths[i]); - var dungeonFilePath = Path.Combine(TxtProjectResRoot.Text, language, "Dungeon.txt"); - File.WriteAllLines( - dungeonFilePath, - dungeons.Select(it => $"{it.Id}:{TextMapData.GetText(it.NameTextMapHash)}"), - Encoding.UTF8); - } + + + + + private void BtnUpdateActivity_Click(object sender, EventArgs e) { @@ -213,5 +208,6 @@ namespace GrasscutterTools.Pages // activityItems.Select(it => $"{it.ActivityId}:{TextMapData.GetText(it.NameTextMapHash)}"), // Encoding.UTF8); } + } } \ No newline at end of file diff --git a/Source/GrasscutterTools/Utils/SparseSet.cs b/Source/GrasscutterTools/Utils/SparseSet.cs new file mode 100644 index 0000000..c9775ae --- /dev/null +++ b/Source/GrasscutterTools/Utils/SparseSet.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GrasscutterTools.Utils +{ + public class SparseSet + { + private struct Range + { + public int Min; + public int Max; + + public Range(int min, int max) + { + Min = min; + Max = max; + } + public bool Check(int value) => + Min <= value && value <= Max; + } + + private readonly List rangeEntries; + private readonly HashSet denseEntries; + public SparseSet(string csv) + { + rangeEntries = new List(); + denseEntries = new HashSet(); + foreach (var token in csv.Replace("\n", "").Replace(" ", "").Split(',')) + { + var tokens = token.Split('-'); + switch (tokens.Length) + { + case 1: + denseEntries.Add(int.Parse(tokens[0])); + break; + case 2: + rangeEntries.Add(new Range(int.Parse(tokens[0]), int.Parse(tokens[1]))); + break; + default: + throw new ArgumentException($"Invalid token passed to SparseSet initializer - {token} (split length {tokens.Length})"); + } + } + } + + public bool Contains(int i) + { + foreach (var range in rangeEntries) + if (range.Check(i)) + return true; + return denseEntries.Contains(i); + } + } +}