Version 0.41.3
[MineClone/MineClone2/MineClone2-Fixes.git] / mods / ENTITIES / mcl_falling_nodes / init.lua
blob92cca7031d874495fb5eba204717d7437450cff0
1 local dmes = minetest.get_modpath("mcl_death_messages") ~= nil
2 local hung = minetest.get_modpath("mcl_hunger") ~= nil
4 local get_falling_depth = function(self)
5 if not self._startpos then
6 -- Fallback
7 self._startpos = self.object:get_pos()
8 end
9 return self._startpos.y - vector.round(self.object:get_pos()).y
10 end
12 local deal_falling_damage = function(self, dtime)
13 if minetest.get_item_group(self.node.name, "falling_node_damage") == 0 then
14 return
15 end
16 -- Cause damage to any player it hits.
17 -- Algorithm based on MC anvils.
18 -- TODO: Support smashing other objects, too.
19 local pos = self.object:get_pos()
20 if not self._startpos then
21 -- Fallback
22 self._startpos = pos
23 end
24 local objs = minetest.get_objects_inside_radius(pos, 1)
25 for _,v in ipairs(objs) do
26 local hp = v:get_hp()
27 if v:is_player() and hp ~= 0 then
28 if not self._hit_players then
29 self._hit_players = {}
30 end
31 local name = v:get_player_name()
32 local hit = false
33 for _,v in ipairs(self._hit_players) do
34 if name == v then
35 hit = true
36 end
37 end
38 if not hit then
39 table.insert(self._hit_players, name)
40 local way = self._startpos.y - pos.y
41 local damage = (way - 1) * 2
42 damage = math.min(40, math.max(0, damage))
43 if damage >= 1 then
44 hp = hp - damage
45 if hp < 0 then
46 hp = 0
47 end
48 if v:is_player() then
49 -- TODO: Reduce damage if wearing a helmet
50 local msg
51 if minetest.get_item_group(self.node.name, "anvil") ~= 0 then
52 msg = "%s was smashed by a falling anvil."
53 else
54 msg = "%s was smashed by a falling block."
55 end
56 if dmes then
57 mcl_death_messages.player_damage(v, string.format(msg, v:get_player_name()))
58 end
59 if hung then
60 mcl_hunger.exhaust(v:get_player_name(), mcl_hunger.EXHAUST_DAMAGE)
61 end
62 end
63 v:set_hp(hp)
64 end
65 end
66 end
67 end
68 end
70 minetest.register_entity(":__builtin:falling_node", {
71 initial_properties = {
72 visual = "wielditem",
73 visual_size = {x = 0.667, y = 0.667},
74 textures = {},
75 physical = true,
76 is_visible = false,
77 collide_with_objects = false,
78 collisionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
81 node = {},
82 meta = {},
84 set_node = function(self, node, meta)
85 self.node = node
86 self.meta = meta or {}
87 self.object:set_properties({
88 is_visible = true,
89 textures = {node.name},
91 local def = core.registered_nodes[node.name]
92 -- Set correct entity yaw
93 if def and node.param2 ~= 0 then
94 if (def.paramtype2 == "facedir" or def.paramtype2 == "colorfacedir") then
95 self.object:set_yaw(core.dir_to_yaw(core.facedir_to_dir(node.param2)))
96 elseif (def.paramtype2 == "wallmounted" or def.paramtype2 == "colorwallmounted") then
97 self.object:set_yaw(core.dir_to_yaw(core.wallmounted_to_dir(node.param2)))
98 end
99 end
100 end,
102 get_staticdata = function(self)
103 local meta = self.meta
104 -- Workaround: Save inventory seperately from metadata.
105 -- Because Minetest crashes when a node with inventory gets deactivated
106 -- (GitHub issue #7020).
107 -- FIXME: Remove the _inv workaround when it is no longer needed
108 local inv
109 if meta then
110 inv = meta.inv
111 meta.inventory = nil
113 local ds = {
114 node = self.node,
115 meta = self.meta,
116 _inv = inv,
117 _startpos = self._startpos,
118 _hit_players = self._hit_players,
120 return minetest.serialize(ds)
121 end,
123 on_activate = function(self, staticdata)
124 self.object:set_armor_groups({immortal = 1})
126 local ds = minetest.deserialize(staticdata)
127 if ds then
128 self._startpos = ds._startpos
129 self._hit_players = ds._hit_players
130 if ds.node then
131 local meta = ds.meta
132 meta.inventory = ds._inv
133 self:set_node(ds.node, meta)
134 else
135 self:set_node(ds)
137 elseif staticdata ~= "" then
138 self:set_node({name = staticdata})
140 if not self._startpos then
141 self._startpos = self.object:get_pos()
143 self._startpos = vector.round(self._startpos)
144 end,
146 on_step = function(self, dtime)
147 -- Set gravity
148 local acceleration = self.object:getacceleration()
149 if not vector.equals(acceleration, {x = 0, y = -10, z = 0}) then
150 self.object:setacceleration({x = 0, y = -10, z = 0})
152 -- Turn to actual node when colliding with ground, or continue to move
153 local pos = self.object:get_pos()
155 -- Portal check
156 local np = {x = pos.x, y = pos.y + 0.3, z = pos.z}
157 local n2 = minetest.get_node(np)
158 if n2.name == "mcl_portals:portal_end" then
159 -- TODO: Teleport falling node.
160 self.object:remove()
161 return
164 -- Position of bottom center point
165 local bcp = {x = pos.x, y = pos.y - 0.7, z = pos.z}
166 -- Avoid bugs caused by an unloaded node below
167 local bcn = minetest.get_node_or_nil(bcp)
168 local bcd = bcn and minetest.registered_nodes[bcn.name]
170 -- TODO: At this point, we did 2 get_nodes in 1 tick.
171 -- Figure out how to improve that (if it is a problem).
173 if bcn and (not bcd or bcd.walkable or
174 (minetest.get_item_group(self.node.name, "float") ~= 0 and
175 bcd.liquidtype ~= "none")) then
176 if bcd and bcd.leveled and
177 bcn.name == self.node.name then
178 local addlevel = self.node.level
179 if not addlevel or addlevel <= 0 then
180 addlevel = bcd.leveled
182 if minetest.add_node_level(bcp, addlevel) == 0 then
183 if minetest.registered_nodes[self.node.name]._mcl_after_falling then
184 minetest.registered_nodes[self.node.name]._mcl_after_falling(bcp, get_falling_depth(self))
186 deal_falling_damage(self, dtime)
187 self.object:remove()
188 return
190 elseif bcd and bcd.buildable_to and
191 (minetest.get_item_group(self.node.name, "float") == 0 or
192 bcd.liquidtype == "none") then
193 minetest.remove_node(bcp)
194 return
196 local nd = minetest.registered_nodes[n2.name]
197 if n2.name == "mcl_portals:portal_end" then
198 -- TODO: Teleport falling node.
200 elseif (nd and nd.buildable_to == true) or minetest.get_item_group(self.node.name, "crush_after_fall") ~= 0 then
201 -- Replace destination node if it's buildable to
202 minetest.remove_node(np)
203 -- Run script hook
204 for _, callback in pairs(minetest.registered_on_dignodes) do
205 callback(np, n2)
207 if minetest.registered_nodes[self.node.name] then
208 minetest.add_node(np, self.node)
209 if minetest.registered_nodes[self.node.name]._mcl_after_falling then
210 minetest.registered_nodes[self.node.name]._mcl_after_falling(np, get_falling_depth(self))
212 if self.meta then
213 local meta = minetest.get_meta(np)
214 meta:from_table(self.meta)
217 else
218 -- Drop the *falling node* as an item if the destination node is NOT buildable to
219 local drops = minetest.get_node_drops(self.node.name, "")
220 for _, dropped_item in pairs(drops) do
221 minetest.add_item(np, dropped_item)
224 deal_falling_damage(self, dtime)
225 self.object:remove()
226 minetest.check_for_falling(np)
227 return
229 local vel = self.object:getvelocity()
230 -- Fix position if entity does not move
231 if vector.equals(vel, {x = 0, y = 0, z = 0}) then
232 local npos = vector.round(self.object:get_pos())
233 local npos2 = table.copy(npos)
234 npos2.y = npos2.y - 2
235 local lownode = minetest.get_node(npos2)
236 -- Special check required for fences and walls, because of their overhigh collision box.
237 if minetest.get_item_group(lownode.name, "fence") == 1 or minetest.get_item_group(lownode.name, "wall") == 1 then
238 -- Instantly stop the node if it is above a fence/wall. This is needed
239 -- because the falling node collides early with a fence/wall node.
240 -- Hacky, because the falling node will teleport a short distance, instead
241 -- of smoothly fall on the fence post.
242 local npos3 = table.copy(npos)
243 npos3.y = npos3.y - 1
244 minetest.add_node(npos3, self.node)
245 if minetest.registered_nodes[self.node.name]._mcl_after_falling then
246 minetest.registered_nodes[self.node.name]._mcl_after_falling(npos3, get_falling_depth(self))
248 deal_falling_damage(self, dtime)
249 self.object:remove()
250 minetest.check_for_falling(npos3)
251 return
252 else
253 -- Normal position fix (expected case)
254 self.object:set_pos(npos)
258 deal_falling_damage(self, dtime)