1
2
3
4
5
6
7
..
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
..
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
...
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
|
local ofs = {
neighbors = {
{x = 1, y = 0, z = 0};
{x = -1, y = 0, z = 0};
{x = 0, y = 1, z = 0};
{x = 0, y = -1, z = 0};
{x = 0, y = 0, z = 1};
................................................................................
for _, item in pairs(inv) do
if not item:is_empty() then
minetest.add_item(offset(pos,0.4), item)
end
end
::skip::end
end;
return {
offsets = ofs;
purge_container = function(...) return purge_container(nil, ...) end;
purge_only = function(lst)
return function(...)
return purge_container(lst, ...)
end
end;
is_air = function(pos)
local n = sorcery.lib.node.force(pos)
if n.name == 'air' then return true end
local d = minetest.registered_nodes[n.name]
if not d then return false end
return (d.walkable == false) and (d.drawtype == 'airlike' or d.buildable_to == true)
end;
is_clear = function(pos)
if not sorcery.lib.node.is_air(pos) then return false end
local ents = minetest.get_objects_inside_radius(pos,0.5)
if #ents > 0 then return false end
return true
end;
get_arrival_point = function(pos)
local try = function(p)
local air = sorcery.lib.node.is_clear
if air(p) then
if air(vector.offset(p,0,1,0)) then return p end
if air(vector.offset(p,0,-1,0)) then return vector.offset(p,0,-1,0) end
................................................................................
do local t = try(pos) if t then return t end end
for _,o in pairs(ofs.neighbors) do
local p = vector.add(pos, o)
do local t = try(p) if t then return t end end
end
end;
amass = function(startpoint,names,directions)
if not directions then directions = ofs.neighbors end
local nodes, positions, checked = {},{},{}
local checkedp = function(pos)
for _,v in pairs(checked) do
if vector.equals(pos,v) then return true end
end
return false
end
local i,stack = 1,{startpoint} repeat
local pos = stack[i]
local n = sorcery.lib.node.force(pos).name
if sorcery.lib.tbl.has(names, n, function(check,against)
return sorcery.lib.item.groupmatch(against,check)
end) then -- match found
-- record the find
nodes[pos] = n
if positions[n] then positions[n][#positions[n]] = pos
else positions[n] = {pos} end
-- check selected neighbors to see if any need scanning
for _,d in pairs(directions) do
local sum = vector.add(pos, d)
if not checkedp(sum) then
stack[#stack + 1] = sum
end
end
end
checked[#checked+1] = pos
i = i + 1
until i > #stack
return nodes, positions
end;
forneighbor = function(pos, n, fn)
for _,p in pairs(n) do
local sum = vector.add(pos, p)
local n = minetest.get_node(sum)
if n.name == 'ignore' then
minetest.load_area(sum)
n = minetest.get_node(sum)
end
fn(sum, n)
end
end;
force = function(pos,preload_for)
local n = minetest.get_node_or_nil(pos)
if preload_for then sorcery.lib.node.preload(pos,preload_for) end
if n then return n end
minetest.load_area(pos)
return minetest.get_node(pos)
end;
-- when items have already been removed; notify cannot be relied on
-- to reach the entire network; this function accounts for the gap
notifyneighbors = function(pos)
sorcery.lib.node.forneighbor(pos, sorcery.ley.txofs, function(sum,node)
if minetest.get_item_group(node.name,'sorcery_ley_device') ~= 0 then
sorcery.ley.notify(sum)
................................................................................
preload = function(pos, user)
minetest.load_area(pos)
user:send_mapblock(sorcery.lib.node.blockpos(pos))
end;
discharger = function(pos)
local below = sorcery.lib.node.force(vector.subtract(pos,{x=0,y=1,z=0}))
if below.name == 'hopper:hopper'
or below.name == 'hopper:hopper_side' then
local hopper = minetest.get_meta(below):get_inventory()
return function(i)
if hopper:room_for_item('main',i) then
return hopper:add_item('main',i), true
end
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
<
<
<
<
<
<
<
|
|
1
2
3
4
5
6
7
8
..
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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
...
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
...
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
|
local log = sorcery.logger('lib.node')
local ofs = {
neighbors = {
{x = 1, y = 0, z = 0};
{x = -1, y = 0, z = 0};
{x = 0, y = 1, z = 0};
{x = 0, y = -1, z = 0};
{x = 0, y = 0, z = 1};
................................................................................
for _, item in pairs(inv) do
if not item:is_empty() then
minetest.add_item(offset(pos,0.4), item)
end
end
::skip::end
end;
local force = function(pos,preload_for)
local n = minetest.get_node_or_nil(pos)
if preload_for then sorcery.lib.node.preload(pos,preload_for) end
if n then return n end
minetest.load_area(pos)
return minetest.get_node(pos)
end;
local amass = function(startpoint,names,directions)
if not directions then directions = ofs.neighbors end
local check = function(n)
return sorcery.lib.tbl.has(names, n.name, function(check,against)
return sorcery.lib.item.groupmatch(against,check)
end)-- match found
end
if type(names) == 'function' then check = names end
local nodes, positions, checked = {},{},{}
local checkedp = function(pos)
for _,v in pairs(checked) do
if vector.equals(pos,v) then return true end
end
return false
end
local i,stack = 1,{startpoint} repeat
local pos = stack[i]
local n = force(pos)
if check(n, pos, nodes, positions) then
-- record the find
nodes[pos] = n.name
if positions[n.name]
then positions[n.name][#positions[n.name]+1] = pos
else positions[n.name] = {pos}
end
-- check selected neighbors to see if any need scanning
for _,d in pairs(directions) do
local sum = vector.add(pos, d)
if not checkedp(sum) then
stack[#stack + 1] = sum
checked[#checked+1] = sum
end
end
end
checked[#checked+1] = pos
i = i + 1
until i > #stack
return nodes, positions
end;
return {
offsets = ofs;
purge_container = function(...) return purge_container(nil, ...) end;
purge_only = function(lst)
return function(...)
return purge_container(lst, ...)
end
end;
is_air = function(pos)
local n = force(pos)
if n.name == 'air' then return true end
local d = minetest.registered_nodes[n.name]
if not d then return false end
return (d.walkable == false) and (d.drawtype == 'airlike' or d.buildable_to == true)
end;
is_clear = function(pos)
if not sorcery.lib.node.is_air(pos) then return false end
local ents = minetest.get_objects_inside_radius(pos,0.5)
if #ents > 0 then return false end
return true
end;
tree_is_live = function(pos, checklight) -- VERY EXPENSIVE FUNCTION
-- this is going to require some explanation.
--
-- for various purposes, we want to be able to tell the difference between
-- a tree that has grown naturally from the grown vs. a couple of trunk nodes
-- that the player has jammed together, even if she's built her own counterfeit
-- tree. unfortunately, mtg provides no easy way to do this. the only
-- difference between a cluster of trunk blocks and a real tree is that the
-- real tree will have a specific kind of leaves attached with their param2
-- set to 1 so that they can be distinguished for the purpose of leaf cleaning.
-- so to check a tree's state, we need to amass its whole potential body, and if
-- there are legitimate leaves connected, then we identify it as a legit tree.
--
-- couple of caveats. firstly, we need to prevent the user from faking a tree
-- simply by using a chain of leaf nodes to connect a fraudulent tree to a
-- Genuine Arboreal® Product™. this means we can't just use a naive amass()
-- call, and instead need to define our own checking function. this allows us
-- to eliminate nonliving leaf nodes in the mass-collection process, so that
-- they can't be used to "branch" (hurr hurr geddit) off to a real tree.
--
-- secondly, we want this to work with trees that aren't necessarily known to
-- the sorcery mod, so we can't rely on the tree register. we'll use it when we
-- can, but otherwise we'll have to guesstimate the correct leaf node. we do
-- this with a stateful closure, by saving the name of the first living leaf
-- node we see, and then referring back to it from that point on. this is ugly
-- but covers all but pathological edge cases.
--
-- finally, the trunk itself is basically inert. the user can mine and then
-- replace as many trunk blocks as he likes without "killing" the tree by
-- this function's estimation. there is a way around this but it would require
-- hooking the on_dignode global callback to invalidate leaf bodies, and this
-- is way too trivial and niche a use for such a performance-critical function,
-- especially since it would involve calling amass on trees for *every node you
-- dig*. imagine O(chopping down an emergent jungle tree) with something like
-- *that* hooked! not on my watch, pal.
--
-- however, there is one problem we have to deal with, and unfortunately there
-- is no good solution. the user can still attach new trunk blocks to living
-- leaves and get extra tree to work with, e.g. for sap generation. this is
-- l33t hax and we don't want it, but preventing it is nontrivial. the best i
-- can come up with for now is hooking the after_place_node functions of
-- known trees to set a metadata key excluding them from amassing if they're
-- positioned near a relevant leaf node. this is ugly and not very efficient,
-- and if you have a better idea i'd love to hear it. apart from no back-compat
-- for existing maps, it also fails to address
-- two edge cases:
-- - a sapling grows such that its leaf nodes connect to a fake trunk
-- - a non-growth trunk node is inserted by another mod that fails to use
-- the place function and attribute the call to a user
--
-- various problems could be avoided by unconditionally inserted the meta key,
-- or inserting it also when it comes into contact with another trunk node,
-- but pepole use these things to build with and that is just way way too many
-- meta keys for me to consider it an option.
--
-- verdict: not very good, but decent enough for most cases. mtg should have
-- done better than this, but now we're all stuck with their bullshit
local treetype = force(pos).name
if minetest.get_item_group(treetype, 'tree') == 0 then -- sir this is not a tree
return nil -- 無
end
local treedef = sorcery.lib.tbl.select(sorcery.data.trees, 'node', treetype)
local leaftype = treedef and treedef.leaves or nil
local uppermost, lowermost
local treemap, treenodes = amass(pos,function(node, where)
if node.name == treetype then
-- abuse predicate function to also track y minimum, so we can
-- avoid iterating over it all later again -- this function is
-- expensive enough already
if (not lowermost) or where.y < lowermost then
lowermost = where.y
end
if (not uppermost) or where.y > uppermost then
uppermost = where.y
end
local m=minetest.get_meta(where)
if m:get_int('sorcery:trunk_node_role') ~= 1 then
return true
else
log.warn('found a log node!')
return false
end
end
if leaftype == nil then
if minetest.get_item_group(node.name, 'leaves') ~= 0 and node.param2 == 0 then
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')
leaftype = node.name
return true
end
elseif leaftype == node.name and node.param2 == 0 then
return true
end
return false
end,ofs.adjoining)
if leaftype == nil then return false end
local trunkmap, trunknodes = amass(pos, {treetype}, ofs.adjoining)
if treenodes[leaftype] == nil then return false end
local cache = {}
local uppermost_check_leaves = true
local topnode
for _, p in pairs(treenodes[treetype]) do
-- if not sorcery.lib.tbl.select(trunknodes[treetype], function(v)
-- return vector.equals(p, v)
-- end, cache) then
-- log.act('tree node', p, 'not accounted for in trunk!')
-- return false
-- end
if p.y == uppermost and uppermost_check_leaves then
topnode = p
uppermost_check_leaves = false
end
if p.y == lowermost then
-- this is the bottom of the tree, bail if it's in not full contact
-- with soil or other eligible nodes as determined by the tree def's
-- 'rooted' predicate
local beneath = vector.offset(p, 0,-1,0);
if treedef.rooted then
if not treedef.rooted {
trunk = p;
groundpos = beneath;
ground = force(beneath);
treemap = treemap;
treenodes = treenodes;
} then return false end
else
if minetest.get_item_group(force(beneath).name, 'soil') == 0 then
return false
end
end
end
end
if uppermost_check_leaves then
for _,p in pairs(treenodes[leaftype]) do
if p.y == uppermost then
topnode = p
break
end
end
end
--
--make sure the tree gets enough light
if checklight and minetest.get_natural_light(vector.offset(topnode,0,1,0), 0.5) < 13 then return false end
-- other possible checks: make sure all ground-touching nodes are directly
-- adjacent
return true, {map = treemap, nodes = treenodes, trunk = treetype, leaves = leaftype, topnode = topnode}
end;
get_arrival_point = function(pos)
local try = function(p)
local air = sorcery.lib.node.is_clear
if air(p) then
if air(vector.offset(p,0,1,0)) then return p end
if air(vector.offset(p,0,-1,0)) then return vector.offset(p,0,-1,0) end
................................................................................
do local t = try(pos) if t then return t end end
for _,o in pairs(ofs.neighbors) do
local p = vector.add(pos, o)
do local t = try(p) if t then return t end end
end
end;
forneighbor = function(pos, n, fn)
for _,p in pairs(n) do
local sum = vector.add(pos, p)
local n = minetest.get_node(sum)
if n.name == 'ignore' then
minetest.load_area(sum)
n = minetest.get_node(sum)
end
fn(sum, n)
end
end;
force = force;
-- when items have already been removed; notify cannot be relied on
-- to reach the entire network; this function accounts for the gap
notifyneighbors = function(pos)
sorcery.lib.node.forneighbor(pos, sorcery.ley.txofs, function(sum,node)
if minetest.get_item_group(node.name,'sorcery_ley_device') ~= 0 then
sorcery.ley.notify(sum)
................................................................................
preload = function(pos, user)
minetest.load_area(pos)
user:send_mapblock(sorcery.lib.node.blockpos(pos))
end;
discharger = function(pos)
local below = force(vector.subtract(pos,{x=0,y=1,z=0}))
if below.name == 'hopper:hopper'
or below.name == 'hopper:hopper_side' then
local hopper = minetest.get_meta(below):get_inventory()
return function(i)
if hopper:room_for_item('main',i) then
return hopper:add_item('main',i), true
end
|