1
0

BlockTypePalette: Load from TSV or original reports' JSON.

This commit is contained in:
Mattes D 2019-12-20 22:10:24 +01:00
parent 7453a9fbe1
commit 2d6f6a574d
14 changed files with 21106 additions and 403 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,554 @@
UpgradeBlockTypePalette
FileVersion 1
CommonPrefix minecraft:
0 0 air
1 0 stone
1 1 granite
1 2 polished_granite
1 3 diorite
1 4 polished_diorite
1 5 andesite
1 6 polished_andesite
2 0 grass_block
3 0 dirt
3 1 coarse_dirt
3 2 podzol
4 0 cobblestone
5 0 oak_planks
5 1 spruce_planks
5 2 birch_planks
5 3 jungle_planks
5 4 acacia_planks
5 5 dark_oak_planks
6 0 oak_sapling
6 1 spruce_sapling
6 2 birch_sapling
6 3 jungle_sapling
6 4 acacia_sapling
6 5 dark_oak_sapling
7 0 bedrock
8 0 flowing_water
9 0 water
10 0 flowing_lava
11 0 lava
12 0 sand
12 1 red_sand
13 0 gravel
14 0 gold_ore
15 0 iron_ore
16 0 coal_ore
17 0 oak_log
17 1 spruce_log
17 2 birch_log
17 3 jungle_log
18 0 oak_leaves
18 1 spruce_leaves
18 2 birch_leaves
18 3 jungle_leaves
19 0 sponge
19 1 wet_sponge
20 0 glass
21 0 lapis_ore
22 0 lapis_block
23 0 dispenser
24 0 sandstone
24 1 chiseled_sandstone
24 2 smooth_sandstone
25 0 note_block
26 0 bed
27 0 powered_rail
28 0 detector_rail
29 0 sticky_piston
30 0 cobweb
31 0 dead_bush
31 1 grass
31 2 fern
32 0 dead_bush
33 0 piston
34 0 piston_head
35 0 white_wool
35 1 orange_wool
35 2 magenta_wool
35 3 light_blue_wool
35 4 yellow_wool
35 5 lime_wool
35 6 pink_wool
35 7 gray_wool
35 8 light_gray_wool
35 9 cyan_wool
35 10 purple_wool
35 11 blue_wool
35 12 brown_wool
35 13 green_wool
35 14 red_wool
35 15 black_wool
36 0 moving_piston
37 0 dandelion
38 0 poppy
38 1 blue_orchid
38 2 allium
38 3 azure_bluet
38 4 red_tulip
38 5 orange_tulip
38 6 white_tulip
38 7 pink_tulip
38 8 oxeye_daisy
39 0 brown_mushroom
40 0 red_mushroom
41 0 gold_block
42 0 iron_block
43 0 stone_slab
43 1 sandstone_slab
43 2 oak_slab
43 3 cobblestone_slab
43 4 brick_slab
43 5 stone_brick_slab
43 6 nether_brick_slab
43 7 smooth_quartz
43 8 stone_slab
43 9 sandstone_slab
44 0 stone_slab
44 1 sandstone_slab
44 2 oak_slab
44 3 cobblestone_slab
44 4 brick_slab
44 5 stone_brick_slab
44 6 nether_brick_slab
44 7 quartz_slab
44 8 stone_slab type top
44 9 sandstone_slab type top
44 10 oak_slab
44 11 cobblestone_slab type top
44 12 brick_slab type top
44 13 stone_brick_slab type top
44 14 nether_brick_slab type top
44 15 quartz_slab type top
45 0 bricks
46 0 tnt
47 0 bookshelf
48 0 mossy_cobblestone
49 0 obsidian
50 0 torch
51 0 fire
52 0 spawner
53 0 oak_stairs
53 4 oak_stairs half top
54 0 chest
55 0 redstone_wire
56 0 diamond_ore
57 0 diamond_block
58 0 crafting_table
59 0 wheat
59 0 wheat age 0
59 1 wheat age 1
59 2 wheat age 2
59 3 wheat age 3
59 4 wheat age 4
59 5 wheat age 5
59 6 wheat age 6
60 0 farmland
60 0 farmland moisture 0
61 0 furnace
62 0 furnace
63 0 sign
64 0 oak_door
65 0 ladder
66 0 rail
67 0 cobblestone_stairs
67 4 cobblestone_stairs half top
68 0 wall_sign
69 0 lever
70 0 stone_pressure_plate
71 0 iron_door
72 0 oak_pressure_plate
73 0 redstone_ore
74 0 redstone_ore
75 0 redstone_torch
76 0 redstone_torch powered true
77 0 stone_button
78 0 snow
79 0 ice
80 0 snow_block
81 0 cactus
82 0 clay
83 0 sugar_cane
84 0 jukebox
85 0 oak_fence
86 0 pumpkin
87 0 netherrack
88 0 soul_sand
89 0 glowstone
90 0 nether_portal
91 0 jack_o_lantern
92 0 cake
93 0 repeater
94 0 repeater powered true
95 0 white_stained_glass
95 1 orange_stained_glass
95 2 magenta_stained_glass
95 3 light_blue_stained_glass
95 4 yellow_stained_glass
95 5 lime_stained_glass
95 6 pink_stained_glass
95 7 gray_stained_glass
95 8 light_gray_stained_glass
95 9 cyan_stained_glass
95 10 purple_stained_glass
95 11 blue_stained_glass
95 12 brown_stained_glass
95 13 green_stained_glass
95 14 red_stained_glass
95 15 black_stained_glass
96 0 oak_trapdoor
97 0 infested_stone
97 1 infested_cobblestone
97 2 infested_stone_bricks
97 3 infested_mossy_stone_bricks
97 4 infested_cracked_stone_bricks
97 5 infested_chiseled_stone_bricks
98 0 stone_bricks
98 1 mossy_stone_bricks
98 2 cracked_stone_bricks
98 3 chiseled_stone_bricks
99 0 brown_mushroom_block up false
99 0 brown_mushroom_block
99 1 brown_mushroom_block
99 2 brown_mushroom_block
99 3 brown_mushroom_block
99 4 brown_mushroom_block
99 5 brown_mushroom_block
99 6 brown_mushroom_block
99 7 brown_mushroom_block
99 8 brown_mushroom_block
99 9 brown_mushroom_block
99 10 mushroom_stem up false
99 14 brown_mushroom_block
99 15 mushroom_stem
100 0 red_mushroom_block
100 0 red_mushroom_block up false
100 1 red_mushroom_block
100 2 red_mushroom_block
100 3 red_mushroom_block
100 4 red_mushroom_block
100 5 red_mushroom_block
100 6 red_mushroom_block
100 7 red_mushroom_block
100 8 red_mushroom_block
100 9 red_mushroom_block
100 10 mushroom_stem up false
100 14 red_mushroom_block
100 15 mushroom_stem
101 0 iron_bars
102 0 glass_pane
103 0 melon
104 0 pumpkin_stem
105 0 melon_stem
106 0 vine
107 0 oak_fence_gate
108 0 brick_stairs
108 4 brick_stairs half top
109 0 stone_brick_stairs
109 4 stone_brick_stairs half top
110 0 mycelium
111 0 lily_pad
112 0 nether_bricks
113 0 nether_brick_fence
114 0 nether_brick_stairs
114 4 netherbrick_stairs half top
115 0 nether_wart
115 0 nether_wart age 0
115 3 nether_wart age 3
116 0 enchanting_table
117 0 brewing_stand
118 0 cauldron
119 0 end_portal
120 0 end_portal_frame
120 4 end_portal_frame eye true
121 0 end_stone
122 0 dragon_egg
123 0 redstone_lamp
124 0 redstone_lamp lit on
125 0 oak_slab
125 1 spruce_slab
125 2 birch_slab
125 3 jungle_slab
125 4 acacia_slab
125 5 dark_oak_slab
126 0 oak_slab
126 1 spruce_slab
126 2 birch_slab
126 3 jungle_slab
126 4 acacia_slab
126 5 dark_oak_slab
126 8 oak_slab type top
126 9 spruce_slab type top
126 10 birch_slab type top
126 11 jungle_slab type top
126 12 acacia_slab type top
126 13 dark_oak_slab type top
127 0 cocoa
127 8 cocoa
128 0 sandstone_stairs
128 4 sandstone_stairs half top
129 0 emerald_ore
130 0 ender_chest
131 0 tripwire_hook
132 0 tripwire
133 0 emerald_block
134 0 spruce_stairs
134 4 spruce_stairs half top
135 0 birch_stairs
135 4 birch_stairs half top
136 0 jungle_stairs
136 4 jungle_stairs half top
137 0 command_block
138 0 beacon
139 0 cobblestone_wall
139 1 mossy_cobblestone_wall
140 0 flower_pot
140 1 potted_poppy
140 2 potted_dandelion
140 3 potted_oak_sapling
140 4 potted_spruce_sapling
140 5 potted_birch_sapling
140 6 potted_jungle_sapling
140 7 potted_red_mushroom
140 8 potted_brown_mushroom
140 9 potted_cactus
140 10 potted_dead_bush
140 11 potted_fern
140 12 potted_acacia_sapling
140 13 potted_dark_oak_sapling
141 0 carrots
141 0 carrots age 0
141 1 carrots age 1
141 2 carrots age 2
142 0 potatoes age 0
142 0 potatoes
142 1 potatoes age 1
142 2 potatoes age 2
142 3 potatoes age 3
142 4 potatoes age 4
142 5 potatoes age 5
142 6 potatoes age 6
143 0 oak_button
144 0 mob_head
145 0 anvil
145 4 chipped_anvil
145 8 damaged_anvil
146 0 trapped_chest
147 0 light_weighted_pressure_plate
148 0 heavy_weighted_pressure_plate
149 0 comparator
150 0 comparator powered true
151 0 daylight_detector
152 0 redstone_block
153 0 nether_quartz_ore
154 0 hopper
155 0 quartz_block
155 1 chiseled_quartz_block
155 2 quartz_pillar
155 3 quartz_pillar
155 4 quartz_pillar
156 0 quartz_stairs
156 4 quartz_stairs half top
157 0 activator_rail
158 0 dropper
159 0 white_terracotta
159 1 orange_terracotta
159 2 magenta_terracotta
159 3 light_blue_terracotta
159 4 yellow_terracotta
159 5 lime_terracotta
159 6 pink_terracotta
159 7 gray_terracotta
159 8 light_gray_terracotta
159 9 cyan_terracotta
159 10 purple_terracotta
159 11 blue_terracotta
159 12 brown_terracotta
159 13 green_terracotta
159 14 red_terracotta
159 15 black_terracotta
160 0 white_stained_glass_pane
160 1 orange_stained_glass_pane
160 2 magenta_stained_glass_pane
160 3 light_blue_stained_glass_pane
160 4 yellow_stained_glass_pane
160 5 lime_stained_glass_pane
160 6 pink_stained_glass_pane
160 7 gray_stained_glass_pane
160 8 light_gray_stained_glass_pane
160 9 cyan_stained_glass_pane
160 10 purple_stained_glass_pane
160 11 blue_stained_glass_pane
160 12 brown_stained_glass_pane
160 13 green_stained_glass_pane
160 14 red_stained_glass_pane
160 15 black_stained_glass_pane
161 0 acacia_leaves
161 1 dark_oak_leaves
162 0 acacia_log
162 1 dark_oak_log
163 0 acacia_stairs
163 4 acacia_stairs half top
164 0 dark_oak_stairs
164 4 dark_oak_stairs half top
165 0 slime_block
166 0 barrier
167 0 iron_trapdoor
168 0 prismarine
168 1 prismarine_bricks
168 2 dark_prismarine
169 0 sea_lantern
170 0 hay_block
171 0 white_carpet
171 1 orange_carpet
171 2 magenta_carpet
171 3 light_blue_carpet
171 4 yellow_carpet
171 5 lime_carpet
171 6 pink_carpet
171 7 gray_carpet
171 8 light_gray_carpet
171 9 cyan_carpet
171 10 purple_carpet
171 11 blue_carpet
171 12 brown_carpet
171 13 green_carpet
171 14 red_carpet
171 15 black_carpet
172 0 terracotta
173 0 coal_block
174 0 packed_ice
175 0 sunflower
175 1 lilac
175 2 tall_grass
175 3 large_fern
175 4 rose_bush
175 5 peony
175 8 large_flower_(top_part)
175 10 large_flower_(top_part)
176 0 white_banner
177 0 white_wall_banner
178 0 daylight_detector
179 0 red_sandstone
179 1 chiseled_red_sandstone
179 2 smooth_red_sandstone
180 0 red_sandstone_stairs
180 4 red_sandstone_stairs half top
181 0 red_sandstone_slab
181 8 red_sandstone_slab
182 0 red_sandstone_slab
182 8 red_sandstone_slab type top
183 0 spruce_fence_gate
184 0 birch_fence_gate
185 0 jungle_fence_gate
186 0 dark_oak_fence_gate
187 0 acacia_fence_gate
188 0 spruce_fence
189 0 birch_fence
190 0 jungle_fence
191 0 dark_oak_fence
192 0 acacia_fence
193 0 spruce_door
194 0 birch_door
195 0 jungle_door
196 0 acacia_door
197 0 dark_oak_door
198 0 end_rod
199 0 chorus_plant
200 0 chorus_flower
200 5 chorus_flower age 5
201 0 purpur_block
202 0 purpur_pillar
203 0 purpur_stairs
203 4 purpur_stairs half top
204 0 purpur_slab
205 0 purpur_slab
205 8 purpur_slab type top
206 0 end_stone_bricks
207 0 beetroots age 0
207 0 beetroots
207 1 beetroots age 1
207 2 beetroots age 2
208 0 grass_path
209 0 end_gateway
210 0 repeating_command_block
211 0 chain_command_block
212 0 frosted_ice
213 0 magma_block
214 0 nether_wart_block
215 0 red_nether_bricks
216 0 bone_block
217 0 structure_void
218 0 observer
219 0 white_shulker_box
220 0 orange_shulker_box
221 0 magenta_shulker_box
222 0 light_blue_shulker_box
223 0 yellow_shulker_box
224 0 lime_shulker_box
225 0 pink_shulker_box
226 0 gray_shulker_box
227 0 light_gray_shulker_box
228 0 cyan_shulker_box
229 0 purple_shulker_box
230 0 blue_shulker_box
231 0 brown_shulker_box
232 0 green_shulker_box
233 0 red_shulker_box
234 0 black_shulker_box
235 0 white_glazed_terracotta
236 0 orange_glazed_terracotta
237 0 magenta_glazed_terracotta
238 0 light_blue_glazed_terracotta
239 0 yellow_glazed_terracotta
240 0 lime_glazed_terracotta
241 0 pink_glazed_terracotta
242 0 gray_glazed_terracotta
243 0 light_gray_glazed_terracotta
244 0 cyan_glazed_terracotta
245 0 purple_glazed_terracotta
246 0 blue_glazed_terracotta
247 0 brown_glazed_terracotta
248 0 green_glazed_terracotta
249 0 red_glazed_terracotta
250 0 black_glazed_terracotta
251 0 white_concrete
251 1 orange_concrete
251 2 magenta_concrete
251 3 light_blue_concrete
251 4 yellow_concrete
251 5 lime_concrete
251 6 pink_concrete
251 7 gray_concrete
251 8 light_gray_concrete
251 9 cyan_concrete
251 10 purple_concrete
251 11 blue_concrete
251 12 brown_concrete
251 13 green_concrete
251 14 red_concrete
251 15 black_concrete
252 0 white_concrete_powder
252 1 orange_concrete_powder
252 2 magenta_concrete_powder
252 3 light_blue_concrete_powder
252 4 yellow_concrete_powder
252 5 lime_concrete_powder
252 6 pink_concrete_powder
252 7 gray_concrete_powder
252 8 light_gray_concrete_powder
252 9 cyan_concrete_powder
252 10 purple_concrete_powder
252 11 blue_concrete_powder
252 12 brown_concrete_powder
252 13 green_concrete_powder
252 14 red_concrete_powder
252 15 black_concrete_powder
255 0 structure_block
255 1 structure_block
255 2 structure_block
255 3 structure_block

View File

@ -0,0 +1,5 @@
# Ignore the scripts' outputs:
*.json
*.btp.txt
UpgradeBlockTypePalette.txt

View File

@ -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/<version>/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`.");
--- 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
--- 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
--- 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
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.");
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
if inpath ~= "-" then
local handle, err = io.open(inpath, "r");
io.input(handle or usage(err));
end
-- Parse the registry:
local registry = parseRegistry(io.input():read("*a"))
local commonPrefix = findCommonPrefix(registry)
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;
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,
};
-- 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
-- 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");

View File

@ -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"
}
}
]
}
```

View File

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

View File

@ -123,7 +123,18 @@ std::map<UInt32, UInt32> BlockTypePalette::createTransformMapWithFallback(const
void BlockTypePalette::loadFromString(const AString & aString)
{
// TODO: Detect format (Json vs Lua)
static const AString hdrTsvRegular = "BlockTypePalette";
static const AString hdrTsvUpgrade = "UpgradeBlockTypePalette";
// Detect format by checking the header line (none -> JSON):
if (aString.substr(0, hdrTsvRegular.length()) == hdrTsvRegular)
{
return loadFromTsv(aString, false);
}
else if (aString.substr(0, hdrTsvUpgrade.length()) == hdrTsvUpgrade)
{
return loadFromTsv(aString, true);
}
return loadFromJsonString(aString);
}
@ -143,55 +154,144 @@ void BlockTypePalette::loadFromJsonString(const AString & aJsonPalette)
throw LoadFailedException(errs);
}
// Check the JSON's metadata + version:
if (!root.isObject() ||
!root.isMember("Metadata") ||
!root["Metadata"].isMember("ProtocolBlockTypePaletteVersion") ||
!root.isMember("Palette") ||
!root["Palette"].isArray())
// Sanity-check the JSON's structure:
if (!root.isObject())
{
throw LoadFailedException("Incorrect palette format, wrong or missing metadata.");
}
auto version = root["Metadata"]["ProtocolBlockTypePaletteVersion"].asUInt();
if (version != 1)
{
throw(Printf("Palette format version %d not supported.", version));
throw LoadFailedException("Incorrect palette format, expected an object at root.");
}
// Load the palette:
auto len = root["Palette"].size();
for (decltype(len) i = 0; i < len; ++i)
for (auto itr = root.begin(), end = root.end(); itr != end; ++itr)
{
const auto & record = root["Palette"][i];
if (!record.isObject())
const auto & blockTypeName = itr.name();
const auto & states = (*itr)["states"];
if (states == Json::Value())
{
throw LoadFailedException(Printf("Palette record #%u is not a JSON object.", i));
throw LoadFailedException(Printf("Missing \"states\" for block type \"%s\"", blockTypeName));
}
auto blockTypeName = record["name"].asString();
auto id = static_cast<UInt32>(std::stoul(record["id"].asString()));
std::map<AString, AString> state;
if (record.isMember("props"))
for (const auto & state: states)
{
const auto & props = record["props"];
if (!props.isObject())
auto id = static_cast<UInt32>(std::stoul(state["id"].asString()));
std::map<AString, AString> props;
if (state.isMember("properties"))
{
throw LoadFailedException(Printf("Palette record #%u: \"props\" value is not a JSON object.", i));
}
for (const auto & key: props.getMemberNames())
{
state[key] = props[key].asString();
const auto & properties = state["properties"];
if (!properties.isObject())
{
throw LoadFailedException(Printf("Member \"properties\" is not a JSON object (block type \"%s\", id %u).", blockTypeName, id));
}
for (const auto & key: properties.getMemberNames())
{
props[key] = properties[key].asString();
}
}
addMapping(id, blockTypeName, props);
}
BlockState blockState(state);
// Insert / update in the maps:
mNumberToBlock[id] = {blockTypeName, blockState};
mBlockToNumber[blockTypeName][blockState] = id;
if (id > mMaxIndex)
{
mMaxIndex = id;
}
} // for i - Palette[]
}
}
void BlockTypePalette::loadFromTsv(const AString & aTsvPalette, bool aIsUpgrade)
{
auto lines = StringSplitAndTrim(aTsvPalette, "\n");
// Parse the header:
int fileVersion = 0;
AString commonPrefix;
auto numLines = lines.size();
for (size_t idx = 1; idx < numLines; ++idx)
{
const auto & line = lines[idx];
if (line.empty())
{
// End of headers, erase them from lines[] and go parse the data
lines.erase(lines.begin(), lines.begin() + static_cast<AStringVector::difference_type>(idx) + 1);
break;
}
auto s = StringSplit(line, "\t");
if (s.size() != 2)
{
throw LoadFailedException(Printf("Invalid header format on line %u", idx + 1));
}
if (s[0] == "FileVersion")
{
try
{
fileVersion = std::stoi(s[1]);
}
catch (const std::exception & exc)
{
throw LoadFailedException(Printf("Invalid file version: \"%d\" (%s)", s[1], exc.what()));
}
}
else if (s[0] == "CommonPrefix")
{
commonPrefix = s[1];
}
}
if (fileVersion != 1)
{
throw LoadFailedException(Printf("Unknown file version (%d), only version 1 is supported", fileVersion));
}
// Parse the data:
size_t minSplit = aIsUpgrade ? 3 : 2;
for (const auto & line: lines)
{
auto s = StringSplit(line, "\t");
auto numSplit = s.size();
if (numSplit < minSplit)
{
throw LoadFailedException(Printf("Not enough values on data line: \"%s\"", line));
}
UInt32 id;
try
{
id = static_cast<UInt32>(std::stoi(s[0]));
}
catch (const std::exception & exc)
{
throw LoadFailedException(Printf("Invalid block ID: \"%s\" (%s)", s[0], exc.what()));
}
size_t idx = 1;
if (aIsUpgrade)
{
id = id * 16;
try
{
id = id + static_cast<UInt32>(Clamp(std::stoi(s[1]), 0, 15));
}
catch (const std::exception & exc)
{
throw LoadFailedException(Printf("Invalid block meta: \"%s\" (%s)", s[1], exc.what()));
}
idx = 2;
}
const auto & blockTypeName = s[idx];
idx += 1;
std::map<AString, AString> state;
while (idx + 1 < numSplit)
{
state[s[idx]] = s[idx + 1];
idx += 2;
}
addMapping(id, commonPrefix + blockTypeName, state);
}
}
void BlockTypePalette::addMapping(UInt32 aID, const AString & aBlockTypeName, const BlockState & aBlockState)
{
mNumberToBlock[aID] = {aBlockTypeName, aBlockState};
mBlockToNumber[aBlockTypeName][aBlockState] = aID;
if (aID > mMaxIndex)
{
mMaxIndex = aID;
}
}

View File

@ -15,7 +15,25 @@ The object itself provides no thread safety, users of this class need to handle
Note that the palette itself doesn't support erasing;
to erase, create a new instance and re-add only the wanted items.
Internally, the object uses two synced maps, one for each translation direction. */
Internally, the object uses two synced maps, one for each translation direction.
The palette can be loaded from a string (file). The loader supports either the blocks.json file exported by
the vanilla server itself (https://wiki.vg/Data_Generators), or a processed text file generated by
our tool $/Tools/BlockTypePaletteGenerator/, or a hand-written text file describing the upgrade from
1.12 BlockType + BlockMeta to 1.13 string representations.
The text file is a TSV (tab-separated values), which basically means the data is generally structured as
<value1><tab><value2><tab><value3><tab>...<valueN><eol>, where eol is the platform's CR / CRLF / LF lineend.
The file starts with a single value on the first line, "BlockTypePalette" or "UpgradeBlockTypePalette", which
is used to detect the file format. The following lines are "headers", simple <key><tab><value><eol> entries
that contain the metadata about the file. "FileVersion" is a compulsory key, "CommonPrefix" is supported, others
are ignored.
The headers are followed by an empty line (that signalizes the end of headers) and then the actual data.
For regular BlockTypePalette TSV file of version 1, the data is in the format:
<index><tab><blockTypeName><tab><state1Name><tab><state1Value><tab><state2Name> ... <eol>
For the UpgradeBlockTypePalette TSV file of version 1, the data is in the format:
<blockType><tab><blockMeta><tab><blockTypeName><tab><state1Name><tab><state1Value><tab><state2Name> ... <eol>
If a CommonPrefix header is present, its value is pre-pended to each blockTypeName loaded (thus allowing
the file to be overall smaller). */
class BlockTypePalette
{
public:
@ -78,10 +96,11 @@ public:
std::map<UInt32, UInt32> createTransformMapWithFallback(const BlockTypePalette & aFrom, UInt32 aFallbackIndex) const;
/** Loads the palette from the string representation.
Throws a LoadFailedException if the loading fails hard (bad string format).
Throws a LoadFailedException if the loading fails hard (bad string format);
but still a part of the data may already be loaded at that point.
If the string specifies duplicate entries (either to already existing entries, or to itself),
the duplicates replace the current values silently (this allows us to chain multiple files as "overrides".
Currently handles only JSON representation, expected to handle also Lua representation in the future. */
Auto-detects the string format (json / tsv, normal / upgrade palette) and calls the appropriate load function. */
void loadFromString(const AString & aString);
@ -100,9 +119,21 @@ protected:
UInt32 mMaxIndex;
/** Loads the palette from the JSON representation.
Throws a LoadFailedException if the loading fails hard (bad string format).
If the string specifies duplicate entries (either to already existing entries, or to itself),
the duplicates replace the current values silently (this allows us to chain multiple files as "overrides". */
/** Loads the palette from the JSON representation, https://wiki.vg/Data_Generators
Throws a LoadFailedException if the loading fails hard (bad string format);
but still a part of the data may already be loaded at that point.
See also: loadFromString(). */
void loadFromJsonString(const AString & aJsonPalette);
/** Loads the palette from the regular or upgrade TSV representation.
aIsUpgrade specifies whether the format is an upgrade TSV (true) or a regular one (false)
Throws a LoadFailedException if the loading fails hard (bad string format);
but still a part of the data may already be loaded at that point.
See also: loadFromString(). */
void loadFromTsv(const AString & aTsvPalette, bool aIsUpgrade);
/** Adds a mapping between the numeric and stringular representation into both maps,
updates the mMaxIndex, if appropriate.
Silently overwrites any previous mapping for the ID, if present, but keeps the old string->id mapping. */
void addMapping(UInt32 aID, const AString & aBlockTypeName, const BlockState & aBlockState);
};

View File

@ -24,10 +24,8 @@ public:
~cStopwatch()
{
#ifdef _DEBUG
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - m_StartTime).count();
LOGD("Stopwatch: %s took %.03f sec", m_Name.c_str(), static_cast<double>(duration) / 1000);
#endif // _DEBUG
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - m_StartTime).count();
LOG("Stopwatch: %s took %.03f sec", m_Name, static_cast<double>(duration) / 1000);
}
protected:

View File

@ -1,6 +1,7 @@
#include "Globals.h"
#include "../TestHelpers.h"
#include "BlockTypePalette.h"
#include "Stopwatch.h"
@ -123,31 +124,7 @@ static void testTransformWithFallback()
/** Tests that loading a simple JSON palette succeeds. */
static void testLoadSimpleSuccess(void)
{
LOG("Testing loading a simple JSON palette");
BlockTypePalette palette;
auto example = "{\"Metadata\":{\"ProtocolBlockTypePaletteVersion\":1}, \"Palette\":[{\
\"props\": {\
\"foo\": \"bar\"\
}, \
\"name\": \"b\", \
\"id\": \"0\"\
}]}";
palette.loadFromString(example);
TEST_EQUAL(palette.maybeIndex("b", BlockState({{"foo", "bar"}})), (std::make_pair<UInt32, bool>(0, true)));
TEST_EQUAL(palette.maybeIndex("b", BlockState({{"foo", "baz"}})), (std::make_pair<UInt32, bool>(0, false)));
TEST_EQUAL(palette.maybeIndex("a", BlockState({{"foo", "bar"}})), (std::make_pair<UInt32, bool>(0, false)));
}
/** Tests that loading fails for nonsense input */
static void testLoadErrors(void)
{
LOG("Testing palette load error reporting.");
@ -164,69 +141,154 @@ static void testLoadErrors(void)
static void testLoadComplex1(void)
/** Tests that loading a simple JSON palette succeeds. */
static void testLoadJsonSimple(void)
{
LOG("Testing loading a complex palette (1)");
LOG("Testing loading a simple JSON palette");
BlockTypePalette palette;
auto str = "{\"Metadata\":{\"ProtocolBlockTypePaletteVersion\":1}, \"Palette\":[{\
\"props\": {\
\"foo\": \"bar\", \
\"moo\": \"baz\"\
auto example = " \
{ \
\"minecraft:air\": { \
\"states\": [ \
{ \
\"id\": 0, \
\"default\": true \
} \
] \
} \
}";
palette.loadFromString(example);
TEST_EQUAL(palette.maybeIndex("minecraft:air", BlockState()), (std::make_pair<UInt32, bool>(0, true)));
TEST_EQUAL(palette.maybeIndex("minecraft:air", BlockState({{"foo", "baz"}})).second, false);
TEST_EQUAL(palette.maybeIndex("minecraft:a", BlockState()).second, false);
}
/** Tests loading a complex block with multiple states and duplicates. */
static void testLoadJsonComplex(void)
{
LOG("Testing loading a complex JSON palette");
BlockTypePalette palette;
auto str = " \
{ \
\"minecraft:oak_sapling\": { \
\"properties\": { \
\"stage\": [ \
\"0\", \
\"1\" \
] \
}, \
\"id\": \"0\", \
\"name\": \"b\"\
}, {\
\"props\": {\
\"foo\": \"baz\", \
\"moo\": \"bar\"\
}, \
\"id\": \"1\", \
\"name\": \"b\"\
}, {\
\"props\": {\
\"foo\": \"baz\", \
\"moo\": \"bar\"\
}, \
\"id\": \"1001\", \
\"name\": \"b\"\
}]}";
\"states\": [ \
{ \
\"properties\": { \
\"stage\": \"0\" \
}, \
\"id\" : 21, \
\"default\" : true \
}, \
{ \
\"properties\": { \
\"stage\": \"1\" \
}, \
\"id\" : 22 \
}, \
{ \
\"properties\": { \
\"stage\": \"1\" \
}, \
\"id\" : 23 \
}\
] \
} \
}";
// Note: The palette has a duplicate entry with differrent IDs, the latter ID wins
palette.loadFromString(str);
TEST_EQUAL(palette.maybeIndex("b", {{"foo", "bar"}}).second, false);
TEST_EQUAL(palette.maybeIndex("b", {{"foo", "bar"}, {"moo", "baz"}}), (std::make_pair<UInt32, bool>(0, true)));
TEST_EQUAL(palette.maybeIndex("b", {{"foo", "baz"}, {"moo", "bar"}}), (std::make_pair<UInt32, bool>(1001, true)));
TEST_EQUAL(palette.maybeIndex("c", {{"foo", "baz"}, {"moo", "bar"}}).second, false);
TEST_EQUAL(palette.maybeIndex("minecraft:oak_sapling", {{"stage", "10"}}).second, false);
TEST_EQUAL(palette.maybeIndex("minecraft:oak_sapling", {{"stage", "0"}}), (std::make_pair<UInt32, bool>(21, true)));
TEST_EQUAL(palette.maybeIndex("minecraft:oak_sapling", {{"stage", "1"}}), (std::make_pair<UInt32, bool>(23, true)));
TEST_EQUAL(palette.maybeIndex("minecraft:oak_sapling", {{"foo", "baz"}}).second, false);
TEST_EQUAL(palette.maybeIndex("minecraft:oak_sap", {{"stage", "0"}}).second, false);
}
static void testLoadComplex2(void)
/** Tests loading a palette from simple regular TSV text data. */
static void testLoadTsvRegular(void)
{
LOG("Testing loading a complex palette (2)");
LOG("Testing loading a simple regular TSV palette");
BlockTypePalette palette;
auto str = "{\"Metadata\":{\"ProtocolBlockTypePaletteVersion\":1}, \"Palette\":[{\
\"id\": \"0\", \
\"name\": \"a\"\
}, {\
\"id\": \"1\", \
\"name\": \"b\"\
}]}";
auto str = "\
BlockTypePalette\r\n\
FileVersion\t1\n\
CommonPrefix\tminecraft:\r\n\
\n\
0\tair\r\n\
1\tstone\n\
2\tgrass\tsnow_covered\t0\n\
3\tgrass\tsnow_covered\t1\n\
";
palette.loadFromString(str);
TEST_EQUAL(palette.maybeIndex("a", BlockState()), (std::make_pair<UInt32, bool>(0, true)));
TEST_EQUAL(palette.maybeIndex("b", BlockState()), (std::make_pair<UInt32, bool>(1, true)));
TEST_EQUAL(palette.maybeIndex("minecraft:air", BlockState()), (std::make_pair<UInt32, bool>(0, true)));
TEST_EQUAL(palette.maybeIndex("minecraft:stone", BlockState()), (std::make_pair<UInt32, bool>(1, true)));
TEST_EQUAL(palette.maybeIndex("minecraft:grass", BlockState({{"snow_covered", "0"}})), (std::make_pair<UInt32, bool>(2, true)));
TEST_EQUAL(palette.maybeIndex("minecraft:grass", BlockState({{"snow_covered", "1"}})), (std::make_pair<UInt32, bool>(3, true)));
TEST_EQUAL(palette.maybeIndex("minecraft:air", BlockState({{"snow_covered", "0"}})).second, false);