From 81e800282e106d6f52b24b2d27855da6ed5f8941 Mon Sep 17 00:00:00 2001 From: wifi_left Date: Wed, 3 Apr 2024 21:18:31 +0800 Subject: [PATCH] Fix NBTParser and command transformer bugs --- NBTParser.js | 162 +++++++++++++++++++++++++++ NBTool.js | 211 +++-------------------------------- debug/test_result.mcfunction | 7 +- index.js | 27 +++-- mccommand.js | 6 +- test2.mcfunction | 4 +- 6 files changed, 202 insertions(+), 215 deletions(-) create mode 100644 NBTParser.js diff --git a/NBTParser.js b/NBTParser.js new file mode 100644 index 0000000..9d8c3b6 --- /dev/null +++ b/NBTParser.js @@ -0,0 +1,162 @@ +// Author: wifi_left +// 灵感来源自:https://zhuanlan.zhihu.com/p/107344979 +function NBTParser(str) { + let i = 0; + function parseValue() { + skipWhitespace(); + if (str[i] == '[') { + // Array + return parseArray(); + } else if (str[i] == '{') { + // Array + return parseObject(); + } else { + return parseString(); + } + } + function skipWhitespace() { + while (str[i] == ' ' || str[i] == '\n' || str[i] == '\r') i++; + } + function eatComma() { + if (str[i] !== ',') { + throw new SyntaxError('Expected "," but only found "' + str[i] + '" at ' + i); + } + i++; + } + function eatColon() { + if (str[i] !== ':') { + throw new SyntaxError('Expected ":" but only found "' + str[i] + '" at ' + i); + } + i++; + } + function parseString() { + let escapeLock = false; // '\' + let topElement = -1; // -1 for none, 0 for "", 1 for '' + let StringBuffer = ""; + while (true) { + if (topElement == -1) { + if (str[i] === '}' || str[i] === ']' || str[i] === ',' || str[i] === ':') { + break; + } + if (i >= str.length) { + throw new SyntaxError('Unexpected ends of string.'); + } + if (str[i] == '"') { + if (StringBuffer !== "") { + throw new SyntaxError("Unexpected '\"' at " + i); + } + topElement = 0; + } else if (str[i] == '\'') { + if (StringBuffer !== "") { + throw new SyntaxError("Unexpected \"'\" at " + i); + } + topElement = 1; + } + StringBuffer = StringBuffer + str[i]; + + } else if (topElement == 0) { + if (str[i] == '"' && !escapeLock) { + StringBuffer = StringBuffer + str[i]; + i++; + return StringBuffer; + } + if (escapeLock) { + escapeLock = false; + } else if (str[i] == '\\') { + escapeLock = true; + } + StringBuffer = StringBuffer + str[i]; + + } else if (topElement == 1) { + if (str[i] == '\'' && !escapeLock) { + StringBuffer = StringBuffer + str[i]; + i++; + return StringBuffer; + } + if (escapeLock) { + escapeLock = false; + } else if (str[i] == '\\') { + escapeLock = true; + } + StringBuffer = StringBuffer + str[i]; + + } + i++; + } + return StringBuffer; + } + function parseObject() { + if (str[i] === '{') { + i++; + skipWhitespace(); + + // if it is not '}', + // we take the path of string -> whitespace -> ':' -> value -> ... + let initial = true; + // if it is not '}', + let map = {} + // we take the path of string -> whitespace -> ':' -> value -> ... + while (str[i] !== '}') { + if (i >= str.length) { + throw new SyntaxError("Expected '}' at " + i + " (End)"); + } + if (!initial) { + eatComma(); + skipWhitespace(); + } + const key = parseString(); + if (key === "" && str[i] === '}') break; + skipWhitespace(); + eatColon(); + const value = parseValue(); + if (value === "") { + throw new SyntaxError("Unexpected: value of key '" + key + "' is null."); + } + initial = false; + map[key] = value; + } + // move to the next character of '}' + i++; + return map; + } + } + function parseArray() { + if (str[i] === '[') { + i++; + skipWhitespace(); + + // if it is not '}', + // we take the path of string -> whitespace -> ':' -> value -> ... + let initial = true; + // if it is not '}', + let map = [] + // we take the path of string -> whitespace -> ':' -> value -> ... + while (str[i] !== ']') { + if (i >= str.length) { + throw new SyntaxError("Expected ']' at " + i + " (End)"); + } + if (!initial) { + eatComma(); + skipWhitespace(); + } + const value = parseValue(); + skipWhitespace(); + + initial = false; + if (value === "") continue; + map.push(value); + } + // move to the next character of '}' + i++; + return map; + } + } + if (str[i] === '{') { + return parseObject(); + } else if (str[i] === '[') { + return parseArray(); + } else { + return JSON.parse(str); + } +} +module.exports = { NBTParser } diff --git a/NBTool.js b/NBTool.js index 2b38e4a..3901746 100644 --- a/NBTool.js +++ b/NBTool.js @@ -1,3 +1,4 @@ +const { NBTParser } = require("./NBTParser.js") function getNbtContent(nbttext) { if (nbttext === undefined) return undefined; if (typeof (nbttext) === 'object') { @@ -16,8 +17,10 @@ function getNbtContent(nbttext) { return nbttext; } if (typeof (nbttext) !== 'string') return nbttext; - if (nbttext.startsWith("S;")) { - return nbttext.substring(2); + if (nbttext.startsWith("\"")) { + return JSON.parse(nbttext); + } else if (nbttext.startsWith("'")) { + return nbttext.substring(1, nbttext.length - 1); } switch (nbttext[nbttext.length - 1]) { case 's': @@ -58,8 +61,10 @@ function getNbtType(nbttext) { } if (typeof (nbttext) !== 'string') return nbttext; - if (nbttext.startsWith("S;")) { - return 'string' + if (nbttext.startsWith("\"")) { + return 'string'; + } else if (nbttext.startsWith("'")) { + return 'string'; } switch (nbttext[nbttext.length - 1]) { case 'b': @@ -84,6 +89,9 @@ function getNbtType(nbttext) { return (intN == floatN ? 'int' : 'double'); } } +function warpComponentValue(key) { + return key; +} function warpKey(key) { if (typeof key !== 'string') @@ -135,199 +143,8 @@ const NBTools = { } }, ParseNBT: function (NbtString) { - let JSON_Decode = false; - if (arguments.length > 1) { - JSON_Decode = arguments[1]; - } - if (typeof NbtString !== "string") { - throw new SyntaxError("Argument is not a String"); - } - function setValue(value) { - IndexList[IndexList.length - 1][Keys[Keys.length - 1]] = value; - return IndexList[IndexList.length - 1][Keys[Keys.length - 1]]; - } - function parseJson(str) { - if (!JSON_Decode) return str - if (typeof str === 'string') { - try { - var str = JSON.parse(str); - } catch (err) { - return str - } - } - if (typeof str === 'object') { - for (let item of Object.keys(str)) { - str[item] = parseJson(str[item]); - } - } - return str; - } - function NaviteBufferEscape(value) { - if (value === "true") { - return true; - } else if (value === "false") { - return false; - } else { - return value.toString(); - } - } - var Stack = []; // 类型堆栈 - var NbtObject = new this.Class.NbtObject(); - var StringBuffer = ""; //字符串缓冲区 - var NativeBuffer = ""; // 自然关键字缓冲区 - var ItemStep = 0; // 冒号位置 - var escape = false; //是否处于转义状态 - var Keys = []; // 索引列表 (名称) - var IndexList = [NbtObject]; // 索引列表(引用) - for (var i = 0, item; item = NbtString[i]; i++) { //循环遍历每个字符 - if (Stack[Stack.length - 1] === "String" || Stack[Stack.length - 1] === "StringD") { // 判断目前是否处于字符串堆栈 - //debugger; - let quoteType = Stack[Stack.length - 1]; - var escapeLock = false; // 转义锁 - if (!escape && (item === '"' && quoteType !== 'StringD') || item === '\'') { - Stack.pop(); - if (StringBuffer.length === 0) { - setValue(""); - continue; - } - StringBuffer = (Number(StringBuffer).toString() === StringBuffer) ? StringBuffer : parseJson(StringBuffer); // 尝试把字符串当做JSON解析 - } - if (escape || quoteType == 'StringD' && item === '"' || item !== '"' && item !== '\'' && item !== "\\") { - - StringBuffer += item; //添加字符到字符串缓冲区 - } - if (escape) { - escape = false; - escapeLock = true; - } - if (!escapeLock && item === "\\") { - escape = true; - } - continue; - } else { - switch (item) { - case "{": - if (ItemStep === 1) { - var rValue = setValue({}); // 设置目前元素的值 - IndexList.push(rValue); - ItemStep = 0; - } - Stack.push("Object"); // 将类型Object加入堆栈 - break; - - case "}": - if (ItemStep === 1) { - if (NativeBuffer !== "") { - var rValue = setValue(NaviteBufferEscape.call(this, NativeBuffer)); // 设置目前元素的值 - NativeBuffer = ""; - Keys.pop(); - } else if (StringBuffer !== "") { - var rValue = setValue(StringBuffer); // 设置目前元素的值 - StringBuffer = ""; - Keys.pop(); - } - } - IndexList.pop(); - if (Stack.length !== 1) { - if (Stack[Stack.length - 2] !== "Array") { - Keys.pop(); - } - } - if (Stack.pop() !== "Object") { - throw new SyntaxError("Unexpect '}' in " + i); - } - break; - - case "[": - if (ItemStep === 1) { - var rValue = setValue([]); // 设置目前元素的值 - IndexList.push(rValue); - } - Keys.push(0); - Stack.push("Array"); // 将类型Array加入堆栈 - ItemStep = 1; - break; - - case "]": - if (ItemStep === 1) { - if (NativeBuffer !== "") { - var rValue = setValue(NaviteBufferEscape.call(this, NativeBuffer)); // 设置目前元素的值 - NativeBuffer = ""; - } else if (StringBuffer !== "") { - var rValue = setValue(StringBuffer); // 设置目前元素的值 - StringBuffer = ""; - } - } - IndexList.pop(); - Keys.pop(); - if (Stack.pop() !== "Array") { - throw new SyntaxError("Unexpect ']' in " + i); - } - break; - - case '"': - if (StringBuffer !== "") { - throw new SyntaxError("Unexpect '\"' in " + i); - } - StringBuffer = 'S;'; - Stack.push("String"); // 将类型String加入堆栈 - break; - case '\'': - if (StringBuffer !== "") { - throw new SyntaxError("Unexpect \"'\" in " + i); - } - StringBuffer = 'S;'; - Stack.push("StringD"); // 将类型StringD加入堆栈 - break; - case ":": - if (StringBuffer === "" && NativeBuffer === "") { - throw new SyntaxError(); - } - if (StringBuffer.startsWith("S;")) StringBuffer = StringBuffer.substring(2); - Keys.push(StringBuffer !== "" ? StringBuffer : NativeBuffer); // 添加 变量名到堆栈 - if (StringBuffer === "") { - NativeBuffer = ""; - } else { - StringBuffer = ""; - } - ItemStep = 1; - break; - - case ",": - if (StringBuffer !== "") { - var rValue = setValue(StringBuffer); // 设置目前元素的值 - StringBuffer = ""; - if (Stack[Stack.length - 1] !== "Array") { - Keys.pop(); - } - } else if (NativeBuffer !== "") { - var rValue = setValue(NaviteBufferEscape.call(this, NativeBuffer)); // 设置目前元素的值 - NativeBuffer = ""; - if (Stack[Stack.length - 1] !== "Array") { - Keys.pop(); - } - } else { - if (Stack[Stack.length - 1] !== "Array") { - Keys.pop(); - } - } - if (Stack[Stack.length - 1] !== "Array") { - ItemStep = 0; - } else { - ItemStep = 1; - Keys[Keys.length - 1]++; - } - break; - - default: - //debugger; - NativeBuffer += item; - break; - } - } - } - return NbtObject; + return NBTParser(NbtString) } }; -module.exports = { NBTools, getNbtContent, getNbtType, warpKey } \ No newline at end of file +module.exports = { NBTools, getNbtContent, getNbtType, warpKey, warpComponentValue } \ No newline at end of file diff --git a/debug/test_result.mcfunction b/debug/test_result.mcfunction index 11758ce..5a8d607 100644 --- a/debug/test_result.mcfunction +++ b/debug/test_result.mcfunction @@ -2,9 +2,4 @@ ## Datapack Upgrader v1.0.0 by wifi_left ## If you encounter a problem, make an issue on https://github.com/wifi-left/Datapack-Upgrader ## -# execute store result block 1 1 1 Items[0].tag.display.Name byte 1 if data entity @s {"Inventory":[{id:"minecraft:emerald",Count:1b,tag:{Enchantments:[{id:"minecraft:sharpness",lvl:1}]}}]} -## WARNING: The transformation may cause problem. You might need to modify it by yourself. -data modify block ~ ~ ~ Items[0].components."minecraft:enchantments".levels set from block ~ ~ ~ Items[1].components."minecraft:enchantments".levels -## WARNING: Unsupport transformation -data merge block ~ ~ ~ {Items:[{tag:{display:{Name:'"1"'}}}]} - +give @p[distance=0..5] chest[can_place_on={blocks:["minecraft:ice","minecraft:mud"]},can_break={blocks:["minecraft:bell","minecraft:tnt","minecraft:dirt"]},custom_name="{\"text\":\"大礼包\",\"color\":\"green\",\"bold\":true,\"underlined\":true}",custom_model_data=123456,custom_data={a:1,b:2,c:3},enchantments={levels:{"minecraft:flame":15s,"minecraft:power":20s}},attribute_modifiers={modifiers:[{type:"generic.max_health",slot:"mainhand",uuid:[1252358603,-1539225956,-1477859009,836212788],name:"generic.max_health",amount:10,operation:"add_value"},{type:"generic.attack_damage",slot:"offhand",uuid:[-1185730063,1295926585,-1519262741,-400858813],name:"generic.attack_damage",amount:20,operation:"add_value"}]},container=[{slot:0b,item:{id:"minecraft:crossbow",count:1,components:{"minecraft:repair_cost":20,"minecraft:unbreakable":{},"minecraft:damage":100,"minecraft:enchantments":{levels:{"minecraft:multishot":111s,"minecraft:piercing":222s,"minecraft:quick_charge":255s}},"minecraft:charged_projectiles":[{id:"minecraft:arrow",count:1},{id:"minecraft:tipped_arrow",count:1,components:{"minecraft:potion_contents":{potion:"minecraft:water"}}},{id:"minecraft:arrow",count:1}]}}},{slot:1b,item:{id:"minecraft:firework_rocket",count:1,components:{"minecraft:fireworks":{explosions:[{shape:small_ball,color:[],fade_colors:[],has_trail:false,has_twinkle:false},{shape:large_ball,color:[I;2883371],fade_colors:[I;16772999],has_trail:1b,has_twinkle:1b}],flight_duration:4b}}}},{slot:2b,item:{id:"minecraft:bundle",count:1,components:{"minecraft:bundle_contents":[{id:"minecraft:tnt",count:1},{id:"minecraft:player_head",count:1,components:{"minecraft:profile":"xiao_qi_zi"}}]}}}]] diff --git a/index.js b/index.js index 808a663..5f0ef0e 100644 --- a/index.js +++ b/index.js @@ -12,6 +12,7 @@ console.warn("### ") const { NBTools, getNbtContent, getNbtType, warpKey } = require("./NBTool.js"); var OutputFile = null; var debugMode = false; +var jsonMode = false; function writeLine(...lines) { for (let i = 0; i < lines.length; i++) { if (OutputFile != null) { @@ -32,7 +33,10 @@ let i = 0; while (i < argvs.length) { let arg = argvs[i]; if (arg == '-debug') { - debugMode = true; + debugMode = !debugMode; + } + if (arg == '-json') { + jsonMode = !jsonMode; } if (arg == '-c') { i++; @@ -701,13 +705,14 @@ function transformSelector(selectorText) { function transformEntityItemTag(itemTag) { let id = itemTag.id; + let rawid = getNbtContent(id); let count = getNbtContent(itemTag.Count); let tag = itemTag.tag; let slot = itemTag.Slot; let components = null; let result = { id: id, count: count }; if (tag != undefined) { - components = transformItemTags(tag); + components = transformItemTags(tag, rawid); result['components'] = {}; for (var key in components) { result['components']["minecraft:" + (key)] = components[key]; @@ -721,6 +726,8 @@ function transformEntityItemTag(itemTag) { } function transformItemItemsTag(itemTag) { let id = itemTag.id; + let rawid = getNbtContent(id); + let count = getNbtContent(itemTag.Count); let tag = itemTag.tag; let slot = itemTag.Slot; @@ -728,7 +735,7 @@ function transformItemItemsTag(itemTag) { let result = { slot: 0, item: {} }; let result1 = { id: id, count: count }; if (tag != undefined) { - components = transformItemTags(tag); + components = transformItemTags(tag, rawid); result1['components'] = {}; for (var key in components) { result1['components'][(`minecraft:${key}`)] = components[key]; @@ -994,10 +1001,9 @@ function transformItemTags(tag, itemId = undefined) { } } if (hiddenflags & (1 << 7)) { - if (components['trim'] == undefined) { - components['trim'] = {}; + if (components['trim'] != undefined) { + components['trim']['show_in_tooltip'] = false; } - components['trim']['show_in_tooltip'] = false; } break; @@ -1053,7 +1059,7 @@ function transformItemTags(tag, itemId = undefined) { components['map_color'] = (mapcolor); } break; - case 'CanDestory': + case 'CanDestroy': components['can_break'] = { blocks: [] }; for (var i in tag[key]) { components['can_break']['blocks'].push(tag[key][i]); @@ -1103,11 +1109,15 @@ function transformItemTags(tag, itemId = undefined) { if (components['charged_projectiles'] == undefined) components['charged_projectiles'] = []; break; case 'ChargedProjectiles': - components['charged_projectiles'] = (tag[key]); + components['charged_projectiles'] = transformBlockItemTag(tag[key]); break; case 'Items': if (deleteNameSpace(itemId) == 'bundle') components['bundle_contents'] = transformBlockItemTag(tag[key]); + else { + if (components['custom_data'] == undefined) components['custom_data'] = {}; + components['custom_data'][key] = transformBlockItemTag(tag[key]); + } break; case 'Decorations': components['map_decorations'] = {}; @@ -1256,6 +1266,7 @@ function transformItemTags(tag, itemId = undefined) { case 'SkullOwner': let t = tag[key]; components['profile'] = transformProfile(t); + break; case 'BlockEntityTag': let note_block_sound = tag[key]['note_block_sound']; let base_color = transformId(FLAGSCOLOR_TRANSFORMATION, tag[key]['Base']); diff --git a/mccommand.js b/mccommand.js index f7f030d..9be421d 100644 --- a/mccommand.js +++ b/mccommand.js @@ -1,4 +1,5 @@ const NBTools = require("./NBTool.js").NBTools; +const { warpComponentValue } = require("./NBTool.js"); function parseCommand(cmd) { if (cmd.startsWith("#")) return [cmd]; if (typeof (cmd) != "string") throw SyntaxError("Not a String object!"); @@ -254,7 +255,10 @@ function toSelectorText(selectorObj, splitChar = '=') { let components = ""; if (selectorObj.components != null) { for (let key in selectorObj.components) { - components += (components == "" ? "" : ",") + `${key}${splitChar}${NBTools.ToString(selectorObj.components[key])}`; + let b = selectorObj.components[key]; + if (typeof b === 'object') + components += (components == "" ? "" : ",") + `${key}${splitChar}${NBTools.ToString(selectorObj.components[key])}`; + else components += (components == "" ? "" : ",") + `${key}${splitChar}${warpComponentValue(selectorObj.components[key])}`; } components = `[${components}]` } diff --git a/test2.mcfunction b/test2.mcfunction index 4529907..bc9889b 100644 --- a/test2.mcfunction +++ b/test2.mcfunction @@ -1,3 +1 @@ -# execute store result block 1 1 1 Items[0].tag.display.Name byte 1 if data entity @s {"Inventory":[{id:"minecraft:emerald",Count:1b,tag:{Enchantments:[{id:"minecraft:sharpness",lvl:1}]}}]} -data modify block ~ ~ ~ Items[0].tag.Enchantments set from block ~ ~ ~ Items[1].tag.Enchantments -data merge block ~ ~ ~ {Items:[{tag:{display:{Name:'"1"'}}}]} +give @p[distance=0..5] chest{CanPlaceOn:["minecraft:ice","minecraft:mud"],CanDestroy:["minecraft:bell","minecraft:tnt","minecraft:dirt"],display:{Name:'{"text":"大礼包","color":"green","bold":true,"underlined":true}'},HideFlags:128,CustomModelData:123456,a:1,b:2,c:3,Enchantments:[{id:"minecraft:flame",lvl:15s},{id:"minecraft:power",lvl:20s}],AttributeModifiers:[{AttributeName:"generic.max_health",Name:"generic.max_health",Amount:10,Operation:0,UUID:[I;1252358603,-1539225956,-1477859009,836212788],Slot:"mainhand"},{AttributeName:"generic.attack_damage",Name:"generic.attack_damage",Amount:20,Operation:0,UUID:[I;-1185730063,1295926585,-1519262741,-400858813],Slot:"offhand"}],BlockEntityTag:{Items:[{Slot:0b,id:"minecraft:crossbow",Count:1b,tag:{RepairCost:20,Unbreakable:1b,Damage:100,Enchantments:[{id:"minecraft:multishot",lvl:111s},{id:"minecraft:piercing",lvl:222s},{id:"minecraft:quick_charge",lvl:255s}],ChargedProjectiles:[{id:"minecraft:arrow",Count:1b},{id:"minecraft:tipped_arrow",Count:1b,tag:{Potion:"minecraft:water"}},{id:"minecraft:arrow",Count:1b}],Charged:1b}},{Slot:1b,id:"minecraft:firework_rocket",Count:1b,tag:{Fireworks:{Flight:4b,Explosions:[{Type:0},{Type:1,Flicker:1b,Trail:1b,Colors:[I;2883371],FadeColors:[I;16772999]}]}}},{Slot:2b,id:"minecraft:bundle",Count:1b,tag:{Items:[{id:"minecraft:tnt",Count:1b},{id:"minecraft:player_head",Count:1b,tag:{SkullOwner:"xiao_qi_zi"}}]}},]}} \ No newline at end of file