]> git.lizzy.rs Git - worldedit.git/blob - worldedit/serialization.lua
Change compatibility notices.
[worldedit.git] / worldedit / serialization.lua
1 worldedit = worldedit or {}\r
2 local minetest = minetest --local copy of global\r
3 \r
4 --modifies positions `pos1` and `pos2` so that each component of `pos1` is less than or equal to its corresponding conent of `pos2`, returning two new positions\r
5 worldedit.sort_pos = function(pos1, pos2)\r
6         pos1 = {x=pos1.x, y=pos1.y, z=pos1.z}\r
7         pos2 = {x=pos2.x, y=pos2.y, z=pos2.z}\r
8         if pos1.x > pos2.x then\r
9                 pos2.x, pos1.x = pos1.x, pos2.x\r
10         end\r
11         if pos1.y > pos2.y then\r
12                 pos2.y, pos1.y = pos1.y, pos2.y\r
13         end\r
14         if pos1.z > pos2.z then\r
15                 pos2.z, pos1.z = pos1.z, pos2.z\r
16         end\r
17         return pos1, pos2\r
18 end\r
19 \r
20 --determines the version of serialized data `value`, returning the version as a positive integer or 0 for unknown versions\r
21 worldedit.valueversion = function(value)\r
22         if value:find("([+-]?%d+)%s+([+-]?%d+)%s+([+-]?%d+)") and not value:find("%{") then --previous list format\r
23                 return 3\r
24         elseif value:find("^[^\"']+%{%d+%}") then\r
25                 if value:find("%[\"meta\"%]") then --previous meta flat table format\r
26                         return 2\r
27                 end\r
28                 return 1 --original flat table format\r
29         elseif value:find("%{") then --current nested table format\r
30                 return 4\r
31         end\r
32         return 0 --unknown format\r
33 end\r
34 \r
35 --converts the region defined by positions `pos1` and `pos2` into a single string, returning the serialized data and the number of nodes serialized\r
36 worldedit.serialize = function(pos1, pos2)\r
37         --make area stay loaded\r
38         local manip = minetest.get_voxel_manip()\r
39         manip:read_from_map(pos1, pos2)\r
40 \r
41         local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
42         local pos = {x=pos1.x, y=0, z=0}\r
43         local count = 0\r
44         local result = {}\r
45         local get_node, get_meta = minetest.get_node, minetest.get_meta\r
46         while pos.x <= pos2.x do\r
47                 pos.y = pos1.y\r
48                 while pos.y <= pos2.y do\r
49                         pos.z = pos1.z\r
50                         while pos.z <= pos2.z do\r
51                                 local node = get_node(pos)\r
52                                 if node.name ~= "air" and node.name ~= "ignore" then\r
53                                         count = count + 1\r
54                                         local meta = get_meta(pos):to_table()\r
55 \r
56                                         --convert metadata itemstacks to itemstrings\r
57                                         for name, inventory in pairs(meta.inventory) do\r
58                                                 for index, stack in ipairs(inventory) do\r
59                                                         inventory[index] = stack.to_string and stack:to_string() or stack\r
60                                                 end\r
61                                         end\r
62 \r
63                                         result[count] = {\r
64                                                 x = pos.x - pos1.x,\r
65                                                 y = pos.y - pos1.y,\r
66                                                 z = pos.z - pos1.z,\r
67                                                 name = node.name,\r
68                                                 param1 = node.param1,\r
69                                                 param2 = node.param2,\r
70                                                 meta = meta,\r
71                                         }\r
72                                 end\r
73                                 pos.z = pos.z + 1\r
74                         end\r
75                         pos.y = pos.y + 1\r
76                 end\r
77                 pos.x = pos.x + 1\r
78         end\r
79         result = minetest.serialize(result) --convert entries to a string\r
80         return result, count\r
81 end\r
82 \r
83 --determines the volume the nodes represented by string `value` would occupy if deserialized at `originpos`, returning the two corner positions and the number of nodes\r
84 --contains code based on [table.save/table.load](http://lua-users.org/wiki/SaveTableToFile) by ChillCode, available under the MIT license (GPL compatible)\r
85 worldedit.allocate = function(originpos, value)\r
86         local huge = math.huge\r
87         local pos1x, pos1y, pos1z = huge, huge, huge\r
88         local pos2x, pos2y, pos2z = -huge, -huge, -huge\r
89         local originx, originy, originz = originpos.x, originpos.y, originpos.z\r
90         local count = 0\r
91         local version = worldedit.valueversion(value)\r
92         if version == 1 or version == 2 then --flat table format\r
93                 --obtain the node table\r
94                 local get_tables = loadstring(value)\r
95                 if get_tables then --error loading value\r
96                         return originpos, originpos, count\r
97                 end\r
98                 local tables = get_tables()\r
99 \r
100                 --transform the node table into an array of nodes\r
101                 for i = 1, #tables do\r
102                         for j, v in pairs(tables[i]) do\r
103                                 if type(v) == "table" then\r
104                                         tables[i][j] = tables[v[1]]\r
105                                 end\r
106                         end\r
107                 end\r
108                 local nodes = tables[1]\r
109 \r
110                 --check the node array\r
111                 count = #nodes\r
112                 if version == 1 then --original flat table format\r
113                         for index = 1, count do\r
114                                 local entry = nodes[index]\r
115                                 local pos = entry[1]\r
116                                 local x, y, z = originx - pos.x, originy - pos.y, originz - pos.z\r
117                                 if x < pos1x then pos1x = x end\r
118                                 if y < pos1y then pos1y = y end\r
119                                 if z < pos1z then pos1z = z end\r
120                                 if x > pos2x then pos2x = x end\r
121                                 if y > pos2y then pos2y = y end\r
122                                 if z > pos2z then pos2z = z end\r
123                         end\r
124                 else --previous meta flat table format\r
125                         for index = 1, count do\r
126                                 local entry = nodes[index]\r
127                                 local x, y, z = originx - entry.x, originy - entry.y, originz - entry.z\r
128                                 if x < pos1x then pos1x = x end\r
129                                 if y < pos1y then pos1y = y end\r
130                                 if z < pos1z then pos1z = z end\r
131                                 if x > pos2x then pos2x = x end\r
132                                 if y > pos2y then pos2y = y end\r
133                                 if z > pos2z then pos2z = z end\r
134                         end\r
135                 end\r
136         elseif version == 3 then --previous list format\r
137                 for x, y, z, name, param1, param2 in value:gmatch("([+-]?%d+)%s+([+-]?%d+)%s+([+-]?%d+)%s+([^%s]+)%s+(%d+)%s+(%d+)[^\r\n]*[\r\n]*") do --match node entries\r
138                         x, y, z = originx + tonumber(x), originy + tonumber(y), originz + tonumber(z)\r
139                         if x < pos1x then pos1x = x end\r
140                         if y < pos1y then pos1y = y end\r
141                         if z < pos1z then pos1z = z end\r
142                         if x > pos2x then pos2x = x end\r
143                         if y > pos2y then pos2y = y end\r
144                         if z > pos2z then pos2z = z end\r
145                         count = count + 1\r
146                 end\r
147         elseif version == 4 then --current nested table format\r
148                 --wip: this is a filthy hack that works surprisingly well\r
149                 value = value:gsub("return%s*{", "", 1):gsub("}%s*$", "", 1)\r
150                 local escaped = value:gsub("\\\\", "@@"):gsub("\\\"", "@@"):gsub("(\"[^\"]*\")", function(s) return string.rep("@", #s) end)\r
151                 local startpos, startpos1, endpos = 1, 1\r
152                 local nodes = {}\r
153                 while true do\r
154                         startpos, endpos = escaped:find("},%s*{", startpos)\r
155                         if not startpos then\r
156                                 break\r
157                         end\r
158                         local current = value:sub(startpos1, startpos)\r
159                         table.insert(nodes, minetest.deserialize("return " .. current))\r
160                         startpos, startpos1 = endpos, endpos\r
161                 end\r
162                 table.insert(nodes, minetest.deserialize("return " .. value:sub(startpos1)))\r
163 \r
164                 --local nodes = minetest.deserialize(value) --wip: this is broken for larger tables in the current version of LuaJIT\r
165 \r
166                 count = #nodes\r
167                 for index = 1, count do\r
168                         local entry = nodes[index]\r
169                         x, y, z = originx + entry.x, originy + entry.y, originz + entry.z\r
170                         if x < pos1x then pos1x = x end\r
171                         if y < pos1y then pos1y = y end\r
172                         if z < pos1z then pos1z = z end\r
173                         if x > pos2x then pos2x = x end\r
174                         if y > pos2y then pos2y = y end\r
175                         if z > pos2z then pos2z = z end\r
176                 end\r
177         end\r
178         local pos1 = {x=pos1x, y=pos1y, z=pos1z}\r
179         local pos2 = {x=pos2x, y=pos2y, z=pos2z}\r
180         return pos1, pos2, count\r
181 end\r
182 \r
183 --loads the nodes represented by string `value` at position `originpos`, returning the number of nodes deserialized\r
184 --contains code based on [table.save/table.load](http://lua-users.org/wiki/SaveTableToFile) by ChillCode, available under the MIT license (GPL compatible)\r
185 worldedit.deserialize = function(originpos, value)\r
186         --make area stay loaded --wip: not very performant\r
187         local pos1, pos2 = worldedit.allocate(originpos, value)\r
188         local manip = minetest.get_voxel_manip()\r
189         manip:read_from_map(pos1, pos2)\r
190 \r
191         local originx, originy, originz = originpos.x, originpos.y, originpos.z\r
192         local count = 0\r
193         local add_node, get_meta = minetest.add_node, minetest.get_meta\r
194         local version = worldedit.valueversion(value)\r
195         if version == 1 or version == 2 then --original flat table format\r
196                 --obtain the node table\r
197                 local get_tables = loadstring(value)\r
198                 if not get_tables then --error loading value\r
199                         return count\r
200                 end\r
201                 local tables = get_tables()\r
202 \r
203                 --transform the node table into an array of nodes\r
204                 for i = 1, #tables do\r
205                         for j, v in pairs(tables[i]) do\r
206                                 if type(v) == "table" then\r
207                                         tables[i][j] = tables[v[1]]\r
208                                 end\r
209                         end\r
210                 end\r
211                 local nodes = tables[1]\r
212 \r
213                 --load the node array\r
214                 count = #nodes\r
215                 if version == 1 then --original flat table format\r
216                         for index = 1, count do\r
217                                 local entry = nodes[index]\r
218                                 local pos = entry[1]\r
219                                 pos.x, pos.y, pos.z = originx - pos.x, originy - pos.y, originz - pos.z\r
220                                 add_node(pos, entry[2])\r
221                         end\r
222                 else --previous meta flat table format\r
223                         for index = 1, #nodes do\r
224                                 local entry = nodes[index]\r
225                                 entry.x, entry.y, entry.z = originx + entry.x, originy + entry.y, originz + entry.z\r
226                                 add_node(entry, entry) --entry acts both as position and as node\r
227                                 get_meta(entry):from_table(entry.meta)\r
228                         end\r
229                 end\r
230         elseif version == 3 then --previous list format\r
231                 local pos = {x=0, y=0, z=0}\r
232                 local node = {name="", param1=0, param2=0}\r
233                 for x, y, z, name, param1, param2 in value:gmatch("([+-]?%d+)%s+([+-]?%d+)%s+([+-]?%d+)%s+([^%s]+)%s+(%d+)%s+(%d+)[^\r\n]*[\r\n]*") do --match node entries\r
234                         pos.x, pos.y, pos.z = originx + tonumber(x), originy + tonumber(y), originz + tonumber(z)\r
235                         node.name, node.param1, node.param2 = name, param1, param2\r
236                         add_node(pos, node)\r
237                         count = count + 1\r
238                 end\r
239         elseif version == 4 then --current nested table format\r
240                 --wip: this is a filthy hack that works surprisingly well\r
241                 value = value:gsub("return%s*{", "", 1):gsub("}%s*$", "", 1)\r
242                 local escaped = value:gsub("\\\\", "@@"):gsub("\\\"", "@@"):gsub("(\"[^\"]*\")", function(s) return string.rep("@", #s) end)\r
243                 local startpos, startpos1, endpos = 1, 1\r
244                 local nodes = {}\r
245                 while true do\r
246                         startpos, endpos = escaped:find("},%s*{", startpos)\r
247                         if not startpos then\r
248                                 break\r
249                         end\r
250                         local current = value:sub(startpos1, startpos)\r
251                         table.insert(nodes, minetest.deserialize("return " .. current))\r
252                         startpos, startpos1 = endpos, endpos\r
253                 end\r
254                 table.insert(nodes, minetest.deserialize("return " .. value:sub(startpos1)))\r
255 \r
256                 --local nodes = minetest.deserialize(value) --wip: this is broken for larger tables in the current version of LuaJIT\r
257 \r
258                 --load the nodes\r
259                 count = #nodes\r
260                 for index = 1, count do\r
261                         local entry = nodes[index]\r
262                         entry.x, entry.y, entry.z = originx + entry.x, originy + entry.y, originz + entry.z\r
263                         add_node(entry, entry) --entry acts both as position and as node\r
264                 end\r
265 \r
266                 --load the metadata\r
267                 for index = 1, count do\r
268                         local entry = nodes[index]\r
269                         get_meta(entry):from_table(entry.meta)\r
270                 end\r
271         end\r
272         return count\r
273 end\r