Differences From
Artifact [b8ef82adf6]:
- File
lib/node.lua
— part of check-in
[15f176a7fe]
at
2020-10-31 19:49:49
on branch trunk
— add background noise for condensers (temporary hack, need to write a proper environment sound framework as the fucking env_sounds module is completely impossible to extend), fix a couple of really stupid bugs, make higher-quality phials increase the chance of getting good runes so it's not a complete waste to burn iridium or levitanium powder on making them, add targeted disjunction and some other amulet spells
(user:
lexi,
size: 5167)
[annotate]
[blame]
[check-ins using]
1 +local log = sorcery.logger('lib.node')
1 2 local ofs = {
2 3 neighbors = {
3 4 {x = 1, y = 0, z = 0};
4 5 {x = -1, y = 0, z = 0};
5 6 {x = 0, y = 1, z = 0};
6 7 {x = 0, y = -1, z = 0};
7 8 {x = 0, y = 0, z = 1};
................................................................................
55 56 for _, item in pairs(inv) do
56 57 if not item:is_empty() then
57 58 minetest.add_item(offset(pos,0.4), item)
58 59 end
59 60 end
60 61 ::skip::end
61 62 end;
63 +
64 +local force = function(pos,preload_for)
65 + local n = minetest.get_node_or_nil(pos)
66 + if preload_for then sorcery.lib.node.preload(pos,preload_for) end
67 + if n then return n end
68 +
69 + minetest.load_area(pos)
70 + return minetest.get_node(pos)
71 +end;
72 +
73 +local amass = function(startpoint,names,directions)
74 + if not directions then directions = ofs.neighbors end
75 + local check = function(n)
76 + return sorcery.lib.tbl.has(names, n.name, function(check,against)
77 + return sorcery.lib.item.groupmatch(against,check)
78 + end)-- match found
79 + end
80 + if type(names) == 'function' then check = names end
81 + local nodes, positions, checked = {},{},{}
82 + local checkedp = function(pos)
83 + for _,v in pairs(checked) do
84 + if vector.equals(pos,v) then return true end
85 + end
86 + return false
87 + end
88 + local i,stack = 1,{startpoint} repeat
89 + local pos = stack[i]
90 + local n = force(pos)
91 + if check(n, pos, nodes, positions) then
92 + -- record the find
93 + nodes[pos] = n.name
94 + if positions[n.name]
95 + then positions[n.name][#positions[n.name]+1] = pos
96 + else positions[n.name] = {pos}
97 + end
98 +
99 + -- check selected neighbors to see if any need scanning
100 + for _,d in pairs(directions) do
101 + local sum = vector.add(pos, d)
102 + if not checkedp(sum) then
103 + stack[#stack + 1] = sum
104 + checked[#checked+1] = sum
105 + end
106 + end
107 + end
108 + checked[#checked+1] = pos
109 + i = i + 1
110 + until i > #stack
111 + return nodes, positions
112 +end;
113 +
62 114 return {
63 115 offsets = ofs;
64 116 purge_container = function(...) return purge_container(nil, ...) end;
65 117 purge_only = function(lst)
66 118 return function(...)
67 119 return purge_container(lst, ...)
68 120 end
69 121 end;
70 122
71 123 is_air = function(pos)
72 - local n = sorcery.lib.node.force(pos)
124 + local n = force(pos)
73 125 if n.name == 'air' then return true end
74 126 local d = minetest.registered_nodes[n.name]
75 127 if not d then return false end
76 128 return (d.walkable == false) and (d.drawtype == 'airlike' or d.buildable_to == true)
77 129 end;
78 130
79 131 is_clear = function(pos)
80 132 if not sorcery.lib.node.is_air(pos) then return false end
81 133 local ents = minetest.get_objects_inside_radius(pos,0.5)
82 134 if #ents > 0 then return false end
83 135 return true
84 136 end;
137 +
138 + tree_is_live = function(pos, checklight) -- VERY EXPENSIVE FUNCTION
139 + -- this is going to require some explanation.
140 + --
141 + -- for various purposes, we want to be able to tell the difference between
142 + -- a tree that has grown naturally from the grown vs. a couple of trunk nodes
143 + -- that the player has jammed together, even if she's built her own counterfeit
144 + -- tree. unfortunately, mtg provides no easy way to do this. the only
145 + -- difference between a cluster of trunk blocks and a real tree is that the
146 + -- real tree will have a specific kind of leaves attached with their param2
147 + -- set to 1 so that they can be distinguished for the purpose of leaf cleaning.
148 + -- so to check a tree's state, we need to amass its whole potential body, and if
149 + -- there are legitimate leaves connected, then we identify it as a legit tree.
150 + --
151 + -- couple of caveats. firstly, we need to prevent the user from faking a tree
152 + -- simply by using a chain of leaf nodes to connect a fraudulent tree to a
153 + -- Genuine Arboreal® Product™. this means we can't just use a naive amass()
154 + -- call, and instead need to define our own checking function. this allows us
155 + -- to eliminate nonliving leaf nodes in the mass-collection process, so that
156 + -- they can't be used to "branch" (hurr hurr geddit) off to a real tree.
157 + --
158 + -- secondly, we want this to work with trees that aren't necessarily known to
159 + -- the sorcery mod, so we can't rely on the tree register. we'll use it when we
160 + -- can, but otherwise we'll have to guesstimate the correct leaf node. we do
161 + -- this with a stateful closure, by saving the name of the first living leaf
162 + -- node we see, and then referring back to it from that point on. this is ugly
163 + -- but covers all but pathological edge cases.
164 + --
165 + -- finally, the trunk itself is basically inert. the user can mine and then
166 + -- replace as many trunk blocks as he likes without "killing" the tree by
167 + -- this function's estimation. there is a way around this but it would require
168 + -- hooking the on_dignode global callback to invalidate leaf bodies, and this
169 + -- is way too trivial and niche a use for such a performance-critical function,
170 + -- especially since it would involve calling amass on trees for *every node you
171 + -- dig*. imagine O(chopping down an emergent jungle tree) with something like
172 + -- *that* hooked! not on my watch, pal.
173 + --
174 + -- however, there is one problem we have to deal with, and unfortunately there
175 + -- is no good solution. the user can still attach new trunk blocks to living
176 + -- leaves and get extra tree to work with, e.g. for sap generation. this is
177 + -- l33t hax and we don't want it, but preventing it is nontrivial. the best i
178 + -- can come up with for now is hooking the after_place_node functions of
179 + -- known trees to set a metadata key excluding them from amassing if they're
180 + -- positioned near a relevant leaf node. this is ugly and not very efficient,
181 + -- and if you have a better idea i'd love to hear it. apart from no back-compat
182 + -- for existing maps, it also fails to address
183 + -- two edge cases:
184 + -- - a sapling grows such that its leaf nodes connect to a fake trunk
185 + -- - a non-growth trunk node is inserted by another mod that fails to use
186 + -- the place function and attribute the call to a user
187 + --
188 + -- various problems could be avoided by unconditionally inserted the meta key,
189 + -- or inserting it also when it comes into contact with another trunk node,
190 + -- but pepole use these things to build with and that is just way way too many
191 + -- meta keys for me to consider it an option.
192 + --
193 + -- verdict: not very good, but decent enough for most cases. mtg should have
194 + -- done better than this, but now we're all stuck with their bullshit
195 +
196 + local treetype = force(pos).name
197 + if minetest.get_item_group(treetype, 'tree') == 0 then -- sir this is not a tree
198 + return nil -- 無
199 + end
200 + local treedef = sorcery.lib.tbl.select(sorcery.data.trees, 'node', treetype)
201 + local leaftype = treedef and treedef.leaves or nil
202 + local uppermost, lowermost
203 +
204 + local treemap, treenodes = amass(pos,function(node, where)
205 + if node.name == treetype then
206 + -- abuse predicate function to also track y minimum, so we can
207 + -- avoid iterating over it all later again -- this function is
208 + -- expensive enough already
209 + if (not lowermost) or where.y < lowermost then
210 + lowermost = where.y
211 + end
212 + if (not uppermost) or where.y > uppermost then
213 + uppermost = where.y
214 + end
215 + local m=minetest.get_meta(where)
216 + if m:get_int('sorcery:trunk_node_role') ~= 1 then
217 + return true
218 + else
219 + log.warn('found a log node!')
220 + return false
221 + end
222 + end
223 + if leaftype == nil then
224 + if minetest.get_item_group(node.name, 'leaves') ~= 0 and node.param2 == 0 then
225 + log.warn('guessing leaf node for tree',treetype,'is',node.name,'; please report this bug to the mod responsible for this tree and ask for appropriate Sorcery interop code to be added')
226 + leaftype = node.name
227 + return true
228 + end
229 + elseif leaftype == node.name and node.param2 == 0 then
230 + return true
231 + end
232 + return false
233 + end,ofs.adjoining)
234 +
235 + if leaftype == nil then return false end
236 +
237 + local trunkmap, trunknodes = amass(pos, {treetype}, ofs.adjoining)
238 + if treenodes[leaftype] == nil then return false end
239 +
240 + local cache = {}
241 + local uppermost_check_leaves = true
242 + local topnode
243 + for _, p in pairs(treenodes[treetype]) do
244 + -- if not sorcery.lib.tbl.select(trunknodes[treetype], function(v)
245 + -- return vector.equals(p, v)
246 + -- end, cache) then
247 + -- log.act('tree node', p, 'not accounted for in trunk!')
248 + -- return false
249 + -- end
250 + if p.y == uppermost and uppermost_check_leaves then
251 + topnode = p
252 + uppermost_check_leaves = false
253 + end
254 + if p.y == lowermost then
255 + -- this is the bottom of the tree, bail if it's in not full contact
256 + -- with soil or other eligible nodes as determined by the tree def's
257 + -- 'rooted' predicate
258 + local beneath = vector.offset(p, 0,-1,0);
259 + if treedef.rooted then
260 + if not treedef.rooted {
261 + trunk = p;
262 + groundpos = beneath;
263 + ground = force(beneath);
264 + treemap = treemap;
265 + treenodes = treenodes;
266 + } then return false end
267 + else
268 + if minetest.get_item_group(force(beneath).name, 'soil') == 0 then
269 + return false
270 + end
271 + end
272 + end
273 + end
274 +
275 + if uppermost_check_leaves then
276 + for _,p in pairs(treenodes[leaftype]) do
277 + if p.y == uppermost then
278 + topnode = p
279 + break
280 + end
281 + end
282 + end
283 + --
284 + --make sure the tree gets enough light
285 + if checklight and minetest.get_natural_light(vector.offset(topnode,0,1,0), 0.5) < 13 then return false end
286 +
287 + -- other possible checks: make sure all ground-touching nodes are directly
288 + -- adjacent
289 +
290 + return true, {map = treemap, nodes = treenodes, trunk = treetype, leaves = leaftype, topnode = topnode}
291 + end;
85 292
86 293 get_arrival_point = function(pos)
87 294 local try = function(p)
88 295 local air = sorcery.lib.node.is_clear
89 296 if air(p) then
90 297 if air(vector.offset(p,0,1,0)) then return p end
91 298 if air(vector.offset(p,0,-1,0)) then return vector.offset(p,0,-1,0) end
................................................................................
96 303 do local t = try(pos) if t then return t end end
97 304 for _,o in pairs(ofs.neighbors) do
98 305 local p = vector.add(pos, o)
99 306 do local t = try(p) if t then return t end end
100 307 end
101 308 end;
102 309
103 - amass = function(startpoint,names,directions)
104 - if not directions then directions = ofs.neighbors end
105 - local nodes, positions, checked = {},{},{}
106 - local checkedp = function(pos)
107 - for _,v in pairs(checked) do
108 - if vector.equals(pos,v) then return true end
109 - end
110 - return false
111 - end
112 - local i,stack = 1,{startpoint} repeat
113 - local pos = stack[i]
114 - local n = sorcery.lib.node.force(pos).name
115 - if sorcery.lib.tbl.has(names, n, function(check,against)
116 - return sorcery.lib.item.groupmatch(against,check)
117 - end) then -- match found
118 - -- record the find
119 - nodes[pos] = n
120 - if positions[n] then positions[n][#positions[n]] = pos
121 - else positions[n] = {pos} end
122 -
123 - -- check selected neighbors to see if any need scanning
124 - for _,d in pairs(directions) do
125 - local sum = vector.add(pos, d)
126 - if not checkedp(sum) then
127 - stack[#stack + 1] = sum
128 - end
129 - end
130 - end
131 - checked[#checked+1] = pos
132 - i = i + 1
133 - until i > #stack
134 - return nodes, positions
135 - end;
136 310
137 311 forneighbor = function(pos, n, fn)
138 312 for _,p in pairs(n) do
139 313 local sum = vector.add(pos, p)
140 314 local n = minetest.get_node(sum)
141 315 if n.name == 'ignore' then
142 316 minetest.load_area(sum)
143 317 n = minetest.get_node(sum)
144 318 end
145 319 fn(sum, n)
146 320 end
147 321 end;
148 322
149 - force = function(pos,preload_for)
150 - local n = minetest.get_node_or_nil(pos)
151 - if preload_for then sorcery.lib.node.preload(pos,preload_for) end
152 - if n then return n end
153 -
154 - minetest.load_area(pos)
155 - return minetest.get_node(pos)
156 - end;
323 + force = force;
157 324
158 325 -- when items have already been removed; notify cannot be relied on
159 326 -- to reach the entire network; this function accounts for the gap
160 327 notifyneighbors = function(pos)
161 328 sorcery.lib.node.forneighbor(pos, sorcery.ley.txofs, function(sum,node)
162 329 if minetest.get_item_group(node.name,'sorcery_ley_device') ~= 0 then
163 330 sorcery.ley.notify(sum)
................................................................................
175 342
176 343 preload = function(pos, user)
177 344 minetest.load_area(pos)
178 345 user:send_mapblock(sorcery.lib.node.blockpos(pos))
179 346 end;
180 347
181 348 discharger = function(pos)
182 - local below = sorcery.lib.node.force(vector.subtract(pos,{x=0,y=1,z=0}))
349 + local below = force(vector.subtract(pos,{x=0,y=1,z=0}))
183 350 if below.name == 'hopper:hopper'
184 351 or below.name == 'hopper:hopper_side' then
185 352 local hopper = minetest.get_meta(below):get_inventory()
186 353 return function(i)
187 354 if hopper:room_for_item('main',i) then
188 355 return hopper:add_item('main',i), true
189 356 end