Add Resources update tool

This commit is contained in:
2023-08-22 22:17:47 +08:00
parent 82668c4c6a
commit 2645e16bee
26 changed files with 874 additions and 35 deletions

View File

@ -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;
}
}

View File

@ -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; }
}
}

View File

@ -0,0 +1,7 @@
namespace GrasscutterTools.Game.Data.Excels
{
[ResourceType("DungeonExcelConfigData.json")]
internal class DungeonData : GameResource
{
}
}

View File

@ -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
{
}
}

View File

@ -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; }
}
}

View File

@ -0,0 +1,7 @@
namespace GrasscutterTools.Game.Data.Excels
{
[ResourceType("HomeWorldFurnitureExcelConfigData.json")]
internal class HomeWorldFurnitureData : GameResource
{
}
}

View File

@ -0,0 +1,9 @@
using Newtonsoft.Json;
namespace GrasscutterTools.Game.Data.Excels
{
[ResourceType("MainQuestExcelConfigData.json")]
internal class MainQuestData : GameResource
{
}
}

View File

@ -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<ItemUseData> ItemUse { get; set; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -0,0 +1,7 @@
namespace GrasscutterTools.Game.Data.Excels
{
[ResourceType("ReliquaryExcelConfigData.json")]
internal class ReliquaryData : GameResource
{
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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<int, AchievementData> AchievementData { get; set; }
public Dictionary<int, AvatarData> AvatarData { get; set; }
public Dictionary<int, HomeWorldBgmData> HomeWorldBgmData { get; set; }
public Dictionary<int, DungeonData> DungeonData { get; set; }
public Dictionary<int, HomeWorldFurnitureData> HomeWorldFurnitureData { get; set; }
public Dictionary<int, MainQuestData> MainQuestData { get; set; }
public Dictionary<int, QuestData> QuestData { get; set; }
public Dictionary<int, MaterialData> MaterialData { get; set; }
public Dictionary<int, MonsterData> MonsterData { get; set; }
public Dictionary<int, ReliquaryData> ReliquaryData { get; set; }
public Dictionary<int, SceneData> SceneData { get; set; }
public Dictionary<int, WeaponData> 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<string, string> Languages = new Dictionary<string, string>
{
["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;
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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<ItemType, string> TextMapCHS = new Dictionary<ItemType, string>
{
[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<ItemType, string> TextMapEN = new Dictionary<ItemType, string>
{
[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];
}
}
}

View File

@ -0,0 +1,175 @@

// ReSharper disable InconsistentNaming
// ReSharper disable IdentifierTypo
using System;
using System.Collections.Generic;
namespace GrasscutterTools.Game.Inventory
{
/// <summary>
/// 材料类型
/// </summary>
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<MaterialType, string> TextMapCHS = new Dictionary<MaterialType, string>
{
[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<MaterialType, string> TextMapEN = new Dictionary<MaterialType, string>
{
[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];
}
}
}

View File

@ -0,0 +1,10 @@
using Newtonsoft.Json;
namespace GrasscutterTools.Game.Props
{
internal class ItemUseData
{
[JsonProperty("useParam")]
public string[] UseParam { get; set; }
}
}

View File

@ -0,0 +1,51 @@

// ReSharper disable InconsistentNaming
using System.Collections.Generic;
namespace GrasscutterTools.Game.Props
{
/// <summary>
/// 怪物种类
/// </summary>
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<MonsterType, string> TextMapCHS = new Dictionary<MonsterType, string>
{
[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<MonsterType, string> TextMapEN = new Dictionary<MonsterType, string>
{
[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];
}
}
}

View File

@ -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,
}
}

View File

@ -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;
}
}
}

View File

@ -139,6 +139,21 @@
<Compile Include="Game\Activity\NewActivityItem.cs" />
<Compile Include="Game\CommandVersion.cs" />
<Compile Include="Game\CutScene\CutSceneItem.cs" />
<Compile Include="Game\Data\Excels\AvatarData.cs" />
<Compile Include="Game\Data\Excels\AchievementData.cs" />
<Compile Include="Game\Data\Excels\DungeonData.cs" />
<Compile Include="Game\Data\Excels\GadgetData.cs" />
<Compile Include="Game\Data\Excels\HomeWorldBgmData.cs" />
<Compile Include="Game\Data\Excels\HomeWorldFurnitureData.cs" />
<Compile Include="Game\Data\Excels\MaterialData.cs" />
<Compile Include="Game\Data\Excels\MainQuestData.cs" />
<Compile Include="Game\Data\Excels\QuestData.cs" />
<Compile Include="Game\Data\Excels\ReliquaryData.cs" />
<Compile Include="Game\Data\Excels\MonsterData.cs" />
<Compile Include="Game\Data\Excels\SceneData.cs" />
<Compile Include="Game\Data\GameResource.cs" />
<Compile Include="Game\Data\GameResources.cs" />
<Compile Include="Game\Data\ResourceTypeAttribute.cs" />
<Compile Include="Game\Drop\DropData.cs" />
<Compile Include="Game\Drop\DropInfo.cs" />
<Compile Include="Game\Dungeon\DungeonItem.cs" />
@ -148,12 +163,18 @@
<Compile Include="Game\Gacha\BannerType.cs" />
<Compile Include="Game\Gacha\GachaBanner.cs" />
<Compile Include="Game\GameData.cs" />
<Compile Include="Game\Inventory\ItemType.cs" />
<Compile Include="Game\Inventory\MaterialType.cs" />
<Compile Include="Game\Data\Excels\WeaponData.cs" />
<Compile Include="Game\ItemMap.cs" />
<Compile Include="Game\ItemMapGroup.cs" />
<Compile Include="Game\Mail\Mail.cs" />
<Compile Include="Game\Mail\MailItem.cs" />
<Compile Include="Game\Player\PlayerData.cs" />
<Compile Include="Game\Props\ItemUseData.cs" />
<Compile Include="Game\Props\MonsterType.cs" />
<Compile Include="Game\Props\PlayerProperty.cs" />
<Compile Include="Game\Props\SceneType.cs" />
<Compile Include="Game\SetStatsCommand.cs" />
<Compile Include="Game\Shop\ItemParamData.cs" />
<Compile Include="Game\Shop\ShopGoodsData.cs" />
@ -295,6 +316,7 @@
<Compile Include="Utils\HttpHelper.cs" />
<Compile Include="Utils\Logger.cs" />
<Compile Include="Utils\GithubHelper.cs" />
<Compile Include="Utils\SparseSet.cs" />
<Compile Include="Utils\ToggleParser.cs" />
<Compile Include="Utils\UIUtil.cs" />
<EmbeddedResource Include="Forms\FormActivityEditor.en-US.resx">

View File

@ -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;
}
}

View File

@ -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<List<DungeonItem>>(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<DungeonItem> 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);
}
}
}

View File

@ -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<Range> rangeEntries;
private readonly HashSet<int> denseEntries;
public SparseSet(string csv)
{
rangeEntries = new List<Range>();
denseEntries = new HashSet<int>();
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);
}
}
}