From 30a4c17d792fef64d5ec2eaf04b77f6bfa7daaaf Mon Sep 17 00:00:00 2001 From: CPTProgrammer <46586216+CPTProgrammer@users.noreply.github.com> Date: Tue, 10 Dec 2024 01:49:42 +0800 Subject: [PATCH] First commit --- .gitignore | 5 ++ README.md | 44 ++++++++++++ extract_recipes.js | 101 +++++++++++++++++++++++++++ extract_recipes.txt | 15 ++++ generate.js | 163 ++++++++++++++++++++++++++++++++++++++++++++ generate.js.txt | 12 ++++ package-lock.json | 27 ++++++++ package.json | 5 ++ settings.json | 53 ++++++++++++++ 9 files changed, 425 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 extract_recipes.js create mode 100644 extract_recipes.txt create mode 100644 generate.js create mode 100644 generate.js.txt create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 settings.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3a3c790 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +data +mods +node_modules +recipes +recipes.zip \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..af2f43a --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +# Minecraft Recipe Tools + +使用前请先运行,以安装必要库 + +```bash +node install +``` + + + +### 文件夹说明 + +- `./data` 导出的合成配方 +- `./mods` 放置待导出合成配方的模组 +- `./recipes` 生成的数据包 + +### 文件作用 + +- `extract_recipes.js`:把 `./mods` 下所有模组的合成配方导出至 `./data` 文件夹下 +- `generate.js`:根据 `settings.json` 配置,找出 `./data` 下需要修改的配方,并于 `./recipes` 生成数据包 + +### 配置文件 + +- `enableSearch: boolean` 启用搜索 +- `enableGenerate: boolean` 启用生成数据包 +- `same: Array` 物品映射列表 + + ```typescript + type ItemMapping = { + tag: string; // Minecraft 标签 + replace: boolean; // 是否覆盖原标签(同 Minecraft Tag JSON 文件) + list: Array; // 应被视为相同物品的物品 ID 列表 + } + +- `search: SearchArgument` 搜索参数 + + ```typescript + type SearchArgument = { + tags: Array; // 要搜索的标签(不搜索合成结果) + items: Array; // 要搜索的物品ID(不搜索合成结果) + } + ``` + + \ No newline at end of file diff --git a/extract_recipes.js b/extract_recipes.js new file mode 100644 index 0000000..0257c3e --- /dev/null +++ b/extract_recipes.js @@ -0,0 +1,101 @@ +const fs = require("fs"); +const path = require("path"); +const AdmZip = require("adm-zip"); // 用于解压 .jar 文件 + +// 创建文件夹(如果不存在) +function createDirIfNotExist(dirPath) { + if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath, { recursive: true }); + } +} + +/*// 写入 pack.mcmeta 文件 +function writePackMcmeta() { + const packMcmetaPath = "./recipes/pack.mcmeta"; + const packContent = { + pack: { + description: "通用了部分整合包模组的物品", + pack_format: 9, + }, + }; + + if (!fs.existsSync(packMcmetaPath)) { + fs.writeFileSync( + packMcmetaPath, + JSON.stringify(packContent, null, 2), + "utf8" + ); + console.log("创建并写入 pack.mcmeta 文件"); + } +}*/ + +// 获取 .jar 文件列表 +function getJarFiles(dirPath) { + const files = fs.readdirSync(dirPath); + return files.filter((file) => file.endsWith(".jar")); +} + +// 解压 .jar 文件中的特定目录 +function extractRecipesFromJar(jarPath, dir) { + const zip = new AdmZip(jarPath); + const recipesPath = `data/${dir}/recipes/`; + const entries = zip.getEntries(); + + // 查找并解压 recipes 文件夹中的内容 + entries.forEach((entry) => { + if (/^data\/[^\/]+\/recipes\/.*$/.test(entry.entryName)) { + const targetFile = path.join(__dirname, entry.entryName); + const targetPath = path.dirname(targetFile) + if (entry.isDirectory){ + createDirIfNotExist(targetFile); + } else { + createDirIfNotExist(targetPath); + fs.writeFileSync(targetFile, entry.getData()); + } + // console.log(`解压文件到 ${targetFile}`); + } + }); +} + +// 处理 mods 目录下的所有 .jar 文件 +function processMods() { + const modsDir = "./mods"; + // const recipesDir = "./recipes"; + if (fs.existsSync("./data")) { + fs.rmSync("./data", { recursive: true, force: true }); + } + // createDirIfNotExist(recipesDir); // 确保 ./recipes 文件夹存在 + // writePackMcmeta(); // 确保 ./recipes/pack.mcmeta 文件存在 + + const jarFiles = getJarFiles(modsDir); + + jarFiles.forEach((jarFile) => { + const jarPath = path.join(modsDir, jarFile); + console.log(`正在读取 ${jarPath}`); + + const zip = new AdmZip(jarPath); + const dataEntry = zip.getEntry("data/"); + // console.log(zip.getEntries().map((v) => v.entryName).filter((v) => /data\/[a-zA-Z]+\/recipes\/.*/.test(v))); + if (!dataEntry) { + console.log(`未找到 data 目录: ${jarPath}`); + return; + } + + // 获取 /data 文件夹内的所有子文件夹目录 + const dirList = []; + const entries = zip.getEntries(); + entries.forEach((entry) => { + const match = entry.entryName.match(/^data\/([^\/]+)/); + if (match && !dirList.includes(match[1])) { + dirList.push(match[1]); + } + }); + + // 遍历并解压每个目录下的 recipes 文件夹 + dirList.forEach((dir) => { + extractRecipesFromJar(jarPath, dir); + }); + }); +} + +processMods(); diff --git a/extract_recipes.txt b/extract_recipes.txt new file mode 100644 index 0000000..625a1f5 --- /dev/null +++ b/extract_recipes.txt @@ -0,0 +1,15 @@ +按照此伪代码编写一个Node.js脚本,要求: + 1. 尽量不使用第三方库,adm-zip 除外(用来解析jar包) + 2. 使用双引号代替单引号 + 3. 使用引号 + +伪代码: +如果 ./recipes 不存在,则创建 ./recipes 文件夹 +如果 ./recipes/pack.mcmeta 不存在,则创建并写入格式化的 { "pack": { "description": "通用了部分整合包模组的物品", "pack_format": 9 } } + +遍历读取 ./mods 下的所有 *.jar { + const dirList = 读取 *.jar 内部的 /data 文件夹里的子文件夹目录; + 遍历 dirList { + 解压 *.jar 内部的 `/data/${dir}/recipes` 至 `./data/${dir}/recipes` + } +} \ No newline at end of file diff --git a/generate.js b/generate.js new file mode 100644 index 0000000..f1c86ed --- /dev/null +++ b/generate.js @@ -0,0 +1,163 @@ +const fs = require("fs"); +const path = require("path"); +const settings = JSON.parse(fs.readFileSync("settings.json")); + +const recipesDir = "./recipes"; + +// 创建文件夹(如果不存在) +function createDirIfNotExist(dirPath) { + if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath, { recursive: true }); + } +} + +// 写入 pack.mcmeta 文件 +function writePackMcmeta() { + const packMcmetaPath = "./recipes/pack.mcmeta"; + const packContent = { + pack: { + description: "通用了部分整合包模组的物品", + pack_format: 9, + }, + }; + + if (!fs.existsSync(packMcmetaPath)) { + fs.writeFileSync( + packMcmetaPath, + JSON.stringify(packContent, null, 2), + "utf8" + ); + console.log("创建并写入 pack.mcmeta 文件"); + } +} + +// 递归遍历文件夹并查找所有的 .json 文件 +function traverseDir(dirPath) { + const files = fs.readdirSync(dirPath); + + files.forEach((file) => { + const filePath = path.join(dirPath, file); + const stat = fs.statSync(filePath); + + if (stat.isDirectory()) { + // 如果是文件夹,则递归调用 + traverseDir(filePath); + } else if (file.endsWith(".json")) { + // 如果是 .json 文件,进行检查 + checkJsonFile(filePath); + } + }); +} + +// 遍历 JSON 内部的 key,替换内容 +function traverseJson(obj, path) { + const result = { + replace: false, + obj: Array.isArray(obj) ? [] : {} + }; + path = path !== undefined ? path : ""; + for (let key in obj) { + if (key === "result" || key === "results" || key === "output"){ + result.obj[key] = obj[key]; + continue; + } + if (typeof obj[key] === "object") { + const returnedResult = traverseJson(obj[key], path + "." + key); + result.replace = result.replace || returnedResult.replace; + result.obj[key] = returnedResult.obj; + continue; + } + + if (key === "item"){ + // Search item + if (settings.enableSearch && settings.search && settings.search.items && settings.search.items.includes(obj[key])){ + console.log(`[Item Found] ${path}.${key} 包含:${obj[key]}`); + } + + // Replace + if (settings.same){ + const index = settings.same.findIndex((v) => v.list.includes(obj[key])); + if (index >= 0){ + console.log(`[Replace] ${path}.${key} 包含:${obj[key]}`); + result.replace = true; + result.obj["tag"] = settings.same[index].tag; + continue; + } + } + } else if (key === "tag"){ + // Search tag + if (settings.enableSearch && settings.search && settings.search.tags && settings.search.tags.includes(obj[key])){ + console.log(`[Key Found] ${path}.${key} 包含:${obj[key]}`); + } + } + result.obj[key] = obj[key]; + } + return result; +} + +// 检查每个 .json 文件是否包含 "ingredients" 属性 +function checkJsonFile(filePath) { + try { + const recipeJson = JSON.parse(fs.readFileSync(filePath, "utf8")); + + // if (!recipeJson["ingredients"] && !recipeJson["ingredient"] && !recipeJson["key"]) { + // console.log(`未找到 "ingredients" 属性的文件: ${filePath}`); + // } + if ( + recipeJson["result"] || + recipeJson["results"] || + recipeJson["output"] + ) { + // console.log(`文件: ${filePath}`); + const result = traverseJson(recipeJson, `${filePath}$`); + if (settings.enableGenerate && result.replace){ + const writeFilePath = path.join(recipesDir, filePath); + console.log(`[Replace] 正在写入文件:${writeFilePath}`); + createDirIfNotExist(path.dirname(writeFilePath)); + fs.writeFileSync(writeFilePath, JSON.stringify(result.obj, null, 2)); + } + } else { + // console.log(`未找到 输出 属性的文件: ${filePath}`); + } + } catch (err) { + console.error(`无法读取或解析文件: ${filePath}`); + console.error(err); + } +} + +function generateTags(){ + const recipesDataDir = path.join(recipesDir, "data"); + createDirIfNotExist(recipesDir); // 确保 ./recipes 文件夹存在 + writePackMcmeta(); // 确保 ./recipes/pack.mcmeta 文件存在 + if (fs.existsSync(recipesDataDir)) { + console.log(`正在清理 "${recipesDataDir}"`); + fs.rmSync(recipesDataDir, { recursive: true, force: true }); + } + + if (settings.same){ + settings.same.forEach((ele) => { + const splitedTag = ele.tag.split(":"); + if (splitedTag.length !== 2){ + console.error(`"${ele.tag}"格式不正确`); + } + const namespace = splitedTag[0]; + const tagName = splitedTag[1]; + const filePath = path.join(recipesDataDir, namespace, "tags/items/", tagName + ".json"); + + console.log(`正在写入文件:${filePath}`); + + createDirIfNotExist(path.dirname(filePath)); + fs.writeFileSync(filePath, JSON.stringify({ + replace: ele.replace, + values: ele.list + }, null, 2)); + }); + } +} + +// 从 ./data 目录开始遍历 +const dataDir = "./data"; +if (settings.enableGenerate){ + generateTags(); +} +traverseDir(dataDir); diff --git a/generate.js.txt b/generate.js.txt new file mode 100644 index 0000000..87d6cf7 --- /dev/null +++ b/generate.js.txt @@ -0,0 +1,12 @@ +按照此伪代码编写一个Node.js脚本,要求: + 1. 尽量不使用第三方库,adm-zip 除外(用来解析jar包) + 2. 使用双引号代替单引号 + 3. 使用引号 + +伪代码: +递归遍历 ./data 及其子文件夹下的所有 *.json { + const recipeJson = 读取 *.json + if (!recipeJson["ingredients"]){ + console.log(*.json文件路径) + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..13f8aba --- /dev/null +++ b/package-lock.json @@ -0,0 +1,27 @@ +{ + "name": "Minecraft Mod Translator", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "dependencies": { + "adm-zip": "^0.5.16" + } + }, + "node_modules/adm-zip": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz", + "integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==", + "engines": { + "node": ">=12.0" + } + } + }, + "dependencies": { + "adm-zip": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz", + "integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..54e13d4 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "adm-zip": "^0.5.16" + } +} diff --git a/settings.json b/settings.json new file mode 100644 index 0000000..424c818 --- /dev/null +++ b/settings.json @@ -0,0 +1,53 @@ +{ + "enableSearch": false, + "enableGenerate": true, + "same": [ + { + "tag": "c:crops/coffee_beans", + "replace": false, + "list": [ + "croptopia:coffee_beans", + "farmersrespite:coffee_beans", + "coffee_delight:coffee_beans" + ] + }, + { + "tag": "c:coffee_beans", + "replace": false, + "list": [ + "croptopia:coffee_beans", + "farmersrespite:coffee_beans", + "coffee_delight:coffee_beans" + ] + }, + { + "tag": "c:lettuce", + "replace": false, + "list": [ + "culinaire:lettuce", + "croptopia:lettuce" + ] + }, + { + "tag": "c:vegetables/cabbage", + "replace": false, + "list": [ + "farmersdelight:cabbage", + "croptopia:cabbage" + ] + } + ], + "search": { + "tags": [ + "c:crops/coffee_beans", + "c:coffee_beans", + "c:coffees" + ], + "items": [ + "croptopia:coffee_beans", + "farmersrespite:coffee_beans", + "farmersdelight:cabbage", + "farmersdelight:cabbage_leaf" + ] + } +} \ No newline at end of file