From 2d6f6a574d8dd6a94dc13cb51628626d99c1853a Mon Sep 17 00:00:00 2001 From: Mattes D Date: Fri, 20 Dec 2019 22:10:24 +0100 Subject: BlockTypePalette: Load from TSV or original reports' JSON. --- Tools/BlockTypePaletteGenerator/.gitignore | 5 + Tools/BlockTypePaletteGenerator/Generator.lua | 180 ++++++++++++----- Tools/BlockTypePaletteGenerator/Readme.md | 63 ------ .../BlockTypePaletteGenerator/UpgradeGenerator.lua | 223 +++++++++++++++++++++ 4 files changed, 355 insertions(+), 116 deletions(-) create mode 100644 Tools/BlockTypePaletteGenerator/.gitignore delete mode 100644 Tools/BlockTypePaletteGenerator/Readme.md create mode 100644 Tools/BlockTypePaletteGenerator/UpgradeGenerator.lua (limited to 'Tools') diff --git a/Tools/BlockTypePaletteGenerator/.gitignore b/Tools/BlockTypePaletteGenerator/.gitignore new file mode 100644 index 000000000..234b0c873 --- /dev/null +++ b/Tools/BlockTypePaletteGenerator/.gitignore @@ -0,0 +1,5 @@ +# Ignore the scripts' outputs: +*.json +*.btp.txt +UpgradeBlockTypePalette.txt + diff --git a/Tools/BlockTypePaletteGenerator/Generator.lua b/Tools/BlockTypePaletteGenerator/Generator.lua index f33f2b789..b052ffa92 100644 --- a/Tools/BlockTypePaletteGenerator/Generator.lua +++ b/Tools/BlockTypePaletteGenerator/Generator.lua @@ -1,14 +1,33 @@ --- lib/lunajson/src/ is not in default Lua package paths +-- Generator.lua + +--[[ +Crafts an intermediate block palette format to be read by Cuberite. +It processes the blocks.json report file (https://wiki.vg/Data_Generators) +into a file that can be loaded into a BlockTypePalette (and is to be stored +as Server/Protocol//base.btp.txt). + +The output format is the regular TSV BlockTypePalette, described in the +$/src/BlockTypePalette.h file. +--]] + + + + +-- Allow Lua to load libraries in our subfolder: package.path = 'lib/lunajson/src/?.lua;' .. package.path; + + + + --- Prints usage instructions to stdout. --- If the optional `message` is passed, output is prepended by message _and_ +-- If the optional `aMessage` is passed, output is prepended by message _and_ -- redirected to stderr. -function usage(message) - if message then +local function usage(aMessage) + if aMessage then io.output(io.stderr); - io.write(message, "\n\n"); + io.write(aMessage, "\n\n"); end io.write( "Usage: lua Generator.lua INPUTFILE OUTPUTFILE\n".. @@ -22,70 +41,125 @@ function usage(message) end --- Test whether the script is run in a path where it can load it's libraries -if not pcall(function() require("lunajson.decoder") end) then - usage("Could not load required libraries, please run `Generator.lua` ".. - "within its directory and make sure to run `git submodule update`."); -end --- Check/Prepare CLI arguments -local inpath, outpath = ...; -io.input(io.stdin); -io.output(io.stdout); -if select("#", ...) ~= 2 then - usage("Incorrect number of arguments."); +--- Parses the JSON registry into a Lua table +--[[ The returned array-table has the following format: +{ + { id = 1, blockTypeName = "minecraft:stone", properties = {key = value, ...} }, + ... +} +--]] +local function parseRegistry(aBlockRegistryJsonStr) + assert(type(aBlockRegistryJsonStr) == "string") + + local lj = require("lunajson") + local input = lj.decode(aBlockRegistryJsonStr) + local registry = {} + local idx = 1 + for blockTypeName, blockData in pairs(input) do + for _, state in pairs(blockData.states) do + registry[idx] = { + id = state.id, + blockTypeName = blockTypeName, + properties = state.properties, + } + idx = idx + 1 + end + end + return registry end -if inpath ~= "-" then - local handle, err = io.open(inpath, "r"); - io.input(handle or usage(err)); -end -if outpath ~= "-" then - local handle, err = io.open(outpath, "w"); - io.output(handle or usage(err)); -end --- Main program starts here -local decode = (require("lunajson.decoder"))(); -local encode = (require("lunajson.encoder"))(); -local input = decode(io.input():read("*a")); -local registry = {}; -local max_id = -1; +--- Serializes the properties from the JSON / array table format into a single output string +-- Concatenates all properties with \t as the delimiting character +local function serializeProperties(aProperties) + local res = {} + local idx = 1 + for k, v in pairs(aProperties or {}) do + res[idx] = k + res[idx + 1] = v + idx = idx + 2 + end + return table.concat(res, "\t") +end + -for blockname, blockdata in pairs(input) do - for i = 1, #(blockdata.states or {}) do - local state = blockdata.states[i]; - assert(registry[state.id + 1] == nil, "Ensure no duplicate IDs"); - -- needed in the end to verify we got no holes in the array: - max_id = math.max(max_id, state.id); - registry[state.id + 1] = { - id = assert(state.id, "id is required."), - name = assert(blockname, "Block type name is required."), - -- default = state.default or nil, -- may need this later - props = state.properties, - }; +--- Returns the prefix that is common for all block type names in the registry +-- aRegistry is the parsed registry, as returned from parseRegistry() +local function findCommonPrefix(aRegistryTable) + local prefix = aRegistryTable[1].blockTypeName + local len = string.len(prefix) + local sub = string.sub + for _, block in ipairs(aRegistryTable) do + while (sub(block.blockTypeName, 1, len) ~= prefix) do + len = len - 1 + if (len == 0) then + return "" + end + prefix = sub(prefix, 1, len) + end end + return prefix end --- The following assertion is not necessary by the current spec, but is required --- by how lunajson distinguishes objects from arrays. Also if this fails, it is --- _very_ likely that the input file is faulty. -assert(#registry == max_id + 1, "Ensure that registry has contiguous keys"); -local out = { - Metadata = { - ProtocolBlockTypePaletteVersion = 1 - }, - Palette = registry -}; -io.write(encode(out), "\n"); + +-- Test whether the script is run in a path where it can load it's libraries +if not(pcall(function() require("lunajson") end)) then + usage( + "Could not load required libraries, please run `Generator.lua` " .. + "within its directory and make sure to run `git submodule update`." + ) +end + +-- Check/Prepare CLI arguments +local inpath, outpath = ...; +inpath = inpath or "blocks.json" +outpath = outpath or "base.btp.txt" +if (inpath ~= "-") then + local handle, err = io.open(inpath, "r") + io.input(handle or usage(err)) +end +if (outpath ~= "-") then + local handle, err = io.open(outpath, "w") + io.output(handle or usage(err)) +end + +-- Parse the registry: +local registry = parseRegistry(io.input():read("*a")) +local commonPrefix = findCommonPrefix(registry) + +-- Sort the entries: +table.sort(registry, + function (entry1, entry2) + return (entry1.id < entry2.id) + end +) + +-- Write out the output format: +io.write("BlockTypePalette\n") +io.write("FileVersion\t1\n") +io.write("CommonPrefix\t", commonPrefix, "\n") +io.write("\n") +local prefixLen = string.len(commonPrefix) + 1 +for _, entry in ipairs(registry) do + local props = serializeProperties(entry.properties) + if (props ~= "") then + props = "\t" .. props + end + io.write( + entry.id, "\t", + string.sub(entry.blockTypeName, prefixLen), + props, "\n" + ) +end diff --git a/Tools/BlockTypePaletteGenerator/Readme.md b/Tools/BlockTypePaletteGenerator/Readme.md deleted file mode 100644 index dc479d16f..000000000 --- a/Tools/BlockTypePaletteGenerator/Readme.md +++ /dev/null @@ -1,63 +0,0 @@ -This generator crafts an intermediate index format to be read by cuberite - -# Running - -Run `lua ./Generator.lua`, pass `blocks.json` as first argument to the script -and the desired output location as 2nd argument. - -Make sure to run the Generator from within its directory (`cd` into the path -where `Generator.lua` is.) - -## Examples - -```bash -SERVER=/path/to/server.jar -java -cp "$SERVER" net.minecraft.data.Main --reports && -lua Generator.lua \ - generated/reports/blocks.json \ - ../../Server/Protocol/1.13/ProtocolBlockTypePalette.json -``` - -```bash -SERVER=/path/to/server.jar -java -cp "$SERVER" net.minecraft.data.Main --reports && -lua Generator.lua - -\ - < generated/reports/blocks.json \ - > ../Server/Protocol/1.13/ProtocolBlockTypePalette.json -``` - -## Output format - -The Format is a `JSON` document containing an object with at least two keys at -the top level: `Metadata` and `Palette`. - -`Metadata` contains document metadata, namely a key `"ProtocolBlockType": 1`. - -`Palette` contains an array of objects. Each of these objects has at least the -keys `id`, `name` and an optional `props` key that contains the individual -properties of the current state. These properties are a KV dict of pure strings. - -The order of the array or object elements is not significant. `id` is unique. - -```json -{ - "Metadata": { - "ProtocolBlockType": 1 - }, - "Palette": [{ - "id": 0, - "name": "minecraft:air" - }, { - "id": 1, - "name": "minecraft:stone" - }, { - "id": 221, - "name": "minecraft:dark_oak_leaves", - "props": { - "persistent": "false", - "distance": "4" - } - } - ] -} -``` \ No newline at end of file diff --git a/Tools/BlockTypePaletteGenerator/UpgradeGenerator.lua b/Tools/BlockTypePaletteGenerator/UpgradeGenerator.lua new file mode 100644 index 000000000..47fa62109 --- /dev/null +++ b/Tools/BlockTypePaletteGenerator/UpgradeGenerator.lua @@ -0,0 +1,223 @@ +-- UpgradeGenerator.lua + +--[[ Creates the UpgradeBlockTypePalette out of JSON data of the Minutor project +(https://github.com/mrkite/minutor/blob/master/definitions/vanilla_ids.json + +Parses the JSON into memory, then walks each block's "id" member and possibly +the "variants" sub-member to read the block types. The name is either present as "flatname", +or is synthesized from the internal Minutor "name" by lowercasing and replacing spaces +with underscores. + +Expects two parameters, the input file and output file; either can be replaced by +a "-" to use stdin / stdout instead. If not given, the input file defaults to +"vanilla_ids.json" and the output file defaults to "UpgradeBlockTypePalette.txt" + +The output format is the upgrade TSV BlockTypePalette, described in the +$/src/BlockTypePalette.h file. +--]] + + + + + + +-- Allow Lua to load libraries in our subfolder: +package.path = 'lib/lunajson/src/?.lua;' .. package.path; + + + + + + +--- Splits the full flat name into flat name and properties +-- "minecraft:carrots:age:0" -> "minecraft:carrots", {age = 0} +local function splitFlatName(aFullFlatName) + local props = {} + local numParts = 0 + local flatName = "" + local propKey = "" + aFullFlatName:gsub("([^:]+)", + function (aPart) + if (numParts == 0) then + flatName = aPart + elseif (numParts == 1) then + flatName = flatName .. ":" .. aPart + elseif (numParts % 2 == 0) then + propKey = aPart + else + props[propKey] = aPart + end + numParts = numParts + 1 + end + ) + return flatName, props +end + + + + + +--- Returns the minecraft block name, created from the flat name if present, or synthesized +-- from the Minutor name +-- If the flat name contains encoded block properties, it returns those properties as a dict-table +-- in the second return value +local function processBlockName(aFlatName, aMinutorName) + if (aFlatName) then + assert(type(aFlatName) == "string") + return splitFlatName(aFlatName) + end + if not(type(aMinutorName) == "string") then + return nil + end + return "minecraft:" .. (aMinutorName:lower():gsub(" ", "_")), {} +end + + + + + + +--- Serializes the properties from the JSON / array table format into a single output string +-- Concatenates all properties with \t as the delimiting character +local function serializeProperties(aProperties) + local res = {} + local idx = 1 + for k, v in pairs(aProperties or {}) do + res[idx] = k + res[idx + 1] = v + idx = idx + 2 + end + return table.concat(res, "\t") +end + + + + + +--- Parses the vanilla_ids.json into a common registry format +-- The returned registry is an array-table of +-- {blockType = 1, blockMeta = 2, blockTypeName = "name", properties = {key = value, ...}} +local function parseRegistry(aJsonString) + assert(type(aJsonString) == "string") + + -- Parse the JSON: + local lj = require("lunajson") + local input = lj.decode(aJsonString) + if (not(input) or (input["type"] ~= "block") or not(input["data"])) then + error("The input file doesn't contain vanilla IDs.") + end + + -- Create the registry: + local registry = {} + local idx = 1 + for _, entry in pairs(input["data"]) do + local id = entry["id"] + local parentBlockTypeName, props = processBlockName(entry["flatname"], entry["name"]) + registry[idx] = + { + blockType = id, + blockMeta = 0, + blockTypeName = parentBlockTypeName, + properties = props, + } + idx = idx + 1 + for _, variant in pairs(entry["variants"] or {}) do + local blockTypeName, props = processBlockName(variant["flatname"], variant["name"]) + if not(blockTypeName) then + -- Some blocks don't have all their variants named ("brown mushroom block"), use the parent name in such a case + blockTypeName = parentBlockTypeName + end + registry[idx] = + { + blockType = id, + blockMeta = variant["data"], + blockTypeName = blockTypeName, + properties = props, + } + idx = idx + 1 + end + end + return registry +end + + + + + +--- Returns the prefix that is common for all block type names in the registry +-- aRegistry is the parsed registry, as returned from parseRegistry() +local function findCommonPrefix(aRegistryTable) + local prefix = aRegistryTable[1].blockTypeName + local len = string.len(prefix) + local sub = string.sub + for _, block in ipairs(aRegistryTable) do + while (sub(block.blockTypeName, 1, len) ~= prefix) do + len = len - 1 + if (len == 0) then + return "" + end + prefix = sub(prefix, 1, len) + end + end + return prefix +end + + + + + +-- Test whether the script is run in a path where it can load it's libraries +if not(pcall(function() require("lunajson") end)) then + error( + "Could not load required libraries, please run `UpgradeGenerator.lua` " .. + "within its directory and make sure to run `git submodule update`." + ) +end + +-- Check/Prepare CLI arguments +local inpath, outpath = ...; +inpath = inpath or "vanilla_ids.json" +outpath = outpath or "UpgradeBlockTypePalette.txt" +if (inpath ~= "-") then + local handle, err = io.open(inpath, "r") + io.input(handle or usage(err)) +end +if (outpath ~= "-") then + local handle, err = io.open(outpath, "w") + io.output(handle or usage(err)) +end + +-- Parse the registry: +local registry = parseRegistry(io.input():read("*a")) +local commonPrefix = findCommonPrefix(registry) + +-- Sort the entries: +table.sort(registry, + function (entry1, entry2) + if (entry1.blockType < entry2.blockType) then + return true + elseif (entry1.blockType > entry2.blockType) then + return false + else + return (entry1.blockMeta < entry2.blockMeta) + end + end +) + +-- Write out the output format: +io.write("UpgradeBlockTypePalette\n") +io.write("FileVersion\t1\n") +io.write("CommonPrefix\t", commonPrefix, "\n") +io.write("\n") +local prefixLen = string.len(commonPrefix) + 1 +for _, entry in ipairs(registry) do + local props = serializeProperties(entry.properties) + if (props ~= "") then + props = "\t" .. props + end + io.write( + entry.blockType, "\t", entry.blockMeta, "\t", + string.sub(entry.blockTypeName, prefixLen), + props, "\n" + ) +end -- cgit v1.2.3