]> git.lizzy.rs Git - dragonfireclient.git/blob - builtin/game/falling.lua
Randomwalk caves: Extend beyond mapchunk vertically also (#9094)
[dragonfireclient.git] / builtin / game / falling.lua
1 -- Minetest: builtin/item.lua
2
3 local builtin_shared = ...
4
5 --
6 -- Falling stuff
7 --
8
9 core.register_entity(":__builtin:falling_node", {
10         initial_properties = {
11                 visual = "wielditem",
12                 visual_size = {x = 0.667, y = 0.667},
13                 textures = {},
14                 physical = true,
15                 is_visible = false,
16                 collide_with_objects = false,
17                 collisionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
18         },
19
20         node = {},
21         meta = {},
22
23         set_node = function(self, node, meta)
24                 self.node = node
25                 meta = meta or {}
26                 if type(meta.to_table) == "function" then
27                         meta = meta:to_table()
28                 end
29                 for _, list in pairs(meta.inventory or {}) do
30                         for i, stack in pairs(list) do
31                                 if type(stack) == "userdata" then
32                                         list[i] = stack:to_string()
33                                 end
34                         end
35                 end
36                 self.meta = meta
37                 self.object:set_properties({
38                         is_visible = true,
39                         textures = {node.name},
40                 })
41         end,
42
43         get_staticdata = function(self)
44                 local ds = {
45                         node = self.node,
46                         meta = self.meta,
47                 }
48                 return core.serialize(ds)
49         end,
50
51         on_activate = function(self, staticdata)
52                 self.object:set_armor_groups({immortal = 1})
53
54                 local ds = core.deserialize(staticdata)
55                 if ds and ds.node then
56                         self:set_node(ds.node, ds.meta)
57                 elseif ds then
58                         self:set_node(ds)
59                 elseif staticdata ~= "" then
60                         self:set_node({name = staticdata})
61                 end
62         end,
63
64         on_step = function(self, dtime)
65                 -- Set gravity
66                 local acceleration = self.object:get_acceleration()
67                 if not vector.equals(acceleration, {x = 0, y = -10, z = 0}) then
68                         self.object:set_acceleration({x = 0, y = -10, z = 0})
69                 end
70                 -- Turn to actual node when colliding with ground, or continue to move
71                 local pos = self.object:get_pos()
72                 -- Position of bottom center point
73                 local bcp = {x = pos.x, y = pos.y - 0.7, z = pos.z}
74                 -- 'bcn' is nil for unloaded nodes
75                 local bcn = core.get_node_or_nil(bcp)
76                 -- Delete on contact with ignore at world edges
77                 if bcn and bcn.name == "ignore" then
78                         self.object:remove()
79                         return
80                 end
81                 local bcd = bcn and core.registered_nodes[bcn.name]
82                 if bcn and
83                                 (not bcd or bcd.walkable or
84                                 (core.get_item_group(self.node.name, "float") ~= 0 and
85                                 bcd.liquidtype ~= "none")) then
86                         if bcd and bcd.leveled and
87                                         bcn.name == self.node.name then
88                                 local addlevel = self.node.level
89                                 if not addlevel or addlevel <= 0 then
90                                         addlevel = bcd.leveled
91                                 end
92                                 if core.add_node_level(bcp, addlevel) == 0 then
93                                         self.object:remove()
94                                         return
95                                 end
96                         elseif bcd and bcd.buildable_to and
97                                         (core.get_item_group(self.node.name, "float") == 0 or
98                                         bcd.liquidtype == "none") then
99                                 core.remove_node(bcp)
100                                 return
101                         end
102                         local np = {x = bcp.x, y = bcp.y + 1, z = bcp.z}
103                         -- Check what's here
104                         local n2 = core.get_node(np)
105                         local nd = core.registered_nodes[n2.name]
106                         -- If it's not air or liquid, remove node and replace it with
107                         -- it's drops
108                         if n2.name ~= "air" and (not nd or nd.liquidtype == "none") then
109                                 core.remove_node(np)
110                                 if nd and nd.buildable_to == false then
111                                         -- Add dropped items
112                                         local drops = core.get_node_drops(n2, "")
113                                         for _, dropped_item in pairs(drops) do
114                                                 core.add_item(np, dropped_item)
115                                         end
116                                 end
117                                 -- Run script hook
118                                 for _, callback in pairs(core.registered_on_dignodes) do
119                                         callback(np, n2)
120                                 end
121                         end
122                         -- Create node and remove entity
123                         local def = core.registered_nodes[self.node.name]
124                         if def then
125                                 core.add_node(np, self.node)
126                                 if self.meta then
127                                         local meta = core.get_meta(np)
128                                         meta:from_table(self.meta)
129                                 end
130                                 if def.sounds and def.sounds.place then
131                                         core.sound_play(def.sounds.place, {pos = np})
132                                 end
133                         end
134                         self.object:remove()
135                         core.check_for_falling(np)
136                         return
137                 end
138                 local vel = self.object:get_velocity()
139                 if vector.equals(vel, {x = 0, y = 0, z = 0}) then
140                         local npos = self.object:get_pos()
141                         self.object:set_pos(vector.round(npos))
142                 end
143         end
144 })
145
146 local function convert_to_falling_node(pos, node)
147         local obj = core.add_entity(pos, "__builtin:falling_node")
148         if not obj then
149                 return false
150         end
151         node.level = core.get_node_level(pos)
152         local meta = core.get_meta(pos)
153         local metatable = meta and meta:to_table() or {}
154
155         local def = core.registered_nodes[node.name]
156         if def and def.sounds and def.sounds.fall then
157                 core.sound_play(def.sounds.fall, {pos = pos})
158         end
159
160         obj:get_luaentity():set_node(node, metatable)
161         core.remove_node(pos)
162         return true
163 end
164
165 function core.spawn_falling_node(pos)
166         local node = core.get_node(pos)
167         if node.name == "air" or node.name == "ignore" then
168                 return false
169         end
170         return convert_to_falling_node(pos, node)
171 end
172
173 local function drop_attached_node(p)
174         local n = core.get_node(p)
175         local drops = core.get_node_drops(n, "")
176         local def = core.registered_items[n.name]
177         if def and def.preserve_metadata then
178                 local oldmeta = core.get_meta(p):to_table().fields
179                 -- Copy pos and node because the callback can modify them.
180                 local pos_copy = {x=p.x, y=p.y, z=p.z}
181                 local node_copy = {name=n.name, param1=n.param1, param2=n.param2}
182                 local drop_stacks = {}
183                 for k, v in pairs(drops) do
184                         drop_stacks[k] = ItemStack(v)
185                 end
186                 drops = drop_stacks
187                 def.preserve_metadata(pos_copy, node_copy, oldmeta, drops)
188         end
189         if def and def.sounds and def.sounds.fall then
190                 core.sound_play(def.sounds.fall, {pos = p})
191         end
192         core.remove_node(p)
193         for _, item in pairs(drops) do
194                 local pos = {
195                         x = p.x + math.random()/2 - 0.25,
196                         y = p.y + math.random()/2 - 0.25,
197                         z = p.z + math.random()/2 - 0.25,
198                 }
199                 core.add_item(pos, item)
200         end
201 end
202
203 function builtin_shared.check_attached_node(p, n)
204         local def = core.registered_nodes[n.name]
205         local d = {x = 0, y = 0, z = 0}
206         if def.paramtype2 == "wallmounted" or
207                         def.paramtype2 == "colorwallmounted" then
208                 -- The fallback vector here is in case 'wallmounted to dir' is nil due
209                 -- to voxelmanip placing a wallmounted node without resetting a
210                 -- pre-existing param2 value that is out-of-range for wallmounted.
211                 -- The fallback vector corresponds to param2 = 0.
212                 d = core.wallmounted_to_dir(n.param2) or {x = 0, y = 1, z = 0}
213         else
214                 d.y = -1
215         end
216         local p2 = vector.add(p, d)
217         local nn = core.get_node(p2).name
218         local def2 = core.registered_nodes[nn]
219         if def2 and not def2.walkable then
220                 return false
221         end
222         return true
223 end
224
225 --
226 -- Some common functions
227 --
228
229 function core.check_single_for_falling(p)
230         local n = core.get_node(p)
231         if core.get_item_group(n.name, "falling_node") ~= 0 then
232                 local p_bottom = {x = p.x, y = p.y - 1, z = p.z}
233                 -- Only spawn falling node if node below is loaded
234                 local n_bottom = core.get_node_or_nil(p_bottom)
235                 local d_bottom = n_bottom and core.registered_nodes[n_bottom.name]
236                 if d_bottom and
237
238                                 (core.get_item_group(n.name, "float") == 0 or
239                                 d_bottom.liquidtype == "none") and
240
241                                 (n.name ~= n_bottom.name or (d_bottom.leveled and
242                                 core.get_node_level(p_bottom) <
243                                 core.get_node_max_level(p_bottom))) and
244
245                                 (not d_bottom.walkable or d_bottom.buildable_to) then
246                         convert_to_falling_node(p, n)
247                         return true
248                 end
249         end
250
251         if core.get_item_group(n.name, "attached_node") ~= 0 then
252                 if not builtin_shared.check_attached_node(p, n) then
253                         drop_attached_node(p)
254                         return true
255                 end
256         end
257
258         return false
259 end
260
261 -- This table is specifically ordered.
262 -- We don't walk diagonals, only our direct neighbors, and self.
263 -- Down first as likely case, but always before self. The same with sides.
264 -- Up must come last, so that things above self will also fall all at once.
265 local check_for_falling_neighbors = {
266         {x = -1, y = -1, z = 0},
267         {x = 1, y = -1, z = 0},
268         {x = 0, y = -1, z = -1},
269         {x = 0, y = -1, z = 1},
270         {x = 0, y = -1, z = 0},
271         {x = -1, y = 0, z = 0},
272         {x = 1, y = 0, z = 0},
273         {x = 0, y = 0, z = 1},
274         {x = 0, y = 0, z = -1},
275         {x = 0, y = 0, z = 0},
276         {x = 0, y = 1, z = 0},
277 }
278
279 function core.check_for_falling(p)
280         -- Round p to prevent falling entities to get stuck.
281         p = vector.round(p)
282
283         -- We make a stack, and manually maintain size for performance.
284         -- Stored in the stack, we will maintain tables with pos, and
285         -- last neighbor visited. This way, when we get back to each
286         -- node, we know which directions we have already walked, and
287         -- which direction is the next to walk.
288         local s = {}
289         local n = 0
290         -- The neighbor order we will visit from our table.
291         local v = 1
292
293         while true do
294                 -- Push current pos onto the stack.
295                 n = n + 1
296                 s[n] = {p = p, v = v}
297                 -- Select next node from neighbor list.
298                 p = vector.add(p, check_for_falling_neighbors[v])
299                 -- Now we check out the node. If it is in need of an update,
300                 -- it will let us know in the return value (true = updated).
301                 if not core.check_single_for_falling(p) then
302                         -- If we don't need to "recurse" (walk) to it then pop
303                         -- our previous pos off the stack and continue from there,
304                         -- with the v value we were at when we last were at that
305                         -- node
306                         repeat
307                                 local pop = s[n]
308                                 p = pop.p
309                                 v = pop.v
310                                 s[n] = nil
311                                 n = n - 1
312                                 -- If there's nothing left on the stack, and no
313                                 -- more sides to walk to, we're done and can exit
314                                 if n == 0 and v == 11 then
315                                         return
316                                 end
317                         until v < 11
318                         -- The next round walk the next neighbor in list.
319                         v = v + 1
320                 else
321                         -- If we did need to walk the neighbor, then
322                         -- start walking it from the walk order start (1),
323                         -- and not the order we just pushed up the stack.
324                         v = 1
325                 end
326         end
327 end
328
329 --
330 -- Global callbacks
331 --
332
333 local function on_placenode(p, node)
334         core.check_for_falling(p)
335 end
336 core.register_on_placenode(on_placenode)
337
338 local function on_dignode(p, node)
339         core.check_for_falling(p)
340 end
341 core.register_on_dignode(on_dignode)
342
343 local function on_punchnode(p, node)
344         core.check_for_falling(p)
345 end
346 core.register_on_punchnode(on_punchnode)