summaryrefslogtreecommitdiffstats
path: root/Tools/BlockTypePaletteGenerator/UpgradeGenerator.lua
blob: 47fa62109c4b43e5e2c7a938389157aec15fdd89 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
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