liquidinteraction.lua 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. -- File is individually reloadable.
  2. -- List of all nodes which have special liquid interaction override code.
  3. falldamage.liquid_interact_nodes = falldamage.liquid_interact_nodes or {}
  4. local string_gsub = string.gsub
  5. local string_find = string.find
  6. -- Alter the way a node interacts with liquids base on its data.
  7. -- Specifically, this determines whether a node can be placed in liquid.
  8. -- A node should not be placeable if it could create airgaps for breathing.
  9. -- This can be considered an extension of the `buildable_to' concept, where
  10. -- all liquids become `buildable_to = false' when placing this node type.
  11. function falldamage.apply_liquid_interaction_mod(name, def)
  12. name = string_gsub(name, "^:", "") -- Eases later parsing.
  13. def.can_place_in_liquid = true -- Defaults to true.
  14. local nodes = falldamage.liquid_interact_nodes
  15. local need_mod = false
  16. -- First, check parameters which may set `need_mod' to true.
  17. -- Then, check paramters which override it to be set false instead.
  18. -- If neither check triggers, the default is false.
  19. -- Only if walkability is specifically falsified.
  20. if def.walkable and def.walkable == false then
  21. need_mod = true
  22. end
  23. if def.climbable and def.climbable == true then
  24. need_mod = true
  25. end
  26. if def.drawtype then
  27. if def.drawtype == "nodebox" or
  28. def.drawtype == "mesh" or
  29. def.drawtype == "plantlike" then
  30. need_mod = true
  31. end
  32. end
  33. if def.groups then
  34. if def.groups.attached_node and def.groups.attached_node > 0 then
  35. need_mod = true
  36. end
  37. if def.groups.hanging_node and def.groups.hanging_node > 0 then
  38. need_mod = true
  39. end
  40. end
  41. -- Whitelisted drawtypes. Nodes with these drawtypes need no modification.
  42. -- They can never create airgaps under any circumstance.
  43. if def.drawtype then
  44. local dt = def.drawtype
  45. if dt == "normal" or
  46. dt == "liquid" or
  47. dt == "flowingliquid" or
  48. dt == "glasslike" or
  49. dt == "glasslike_framed" or
  50. dt == "glasslike_framed_optional" or
  51. dt == "allfaces" or
  52. dt == "allfaces_optional" or
  53. dt == "plantlike_rooted" then
  54. need_mod = false
  55. end
  56. end
  57. -- Obviously, immovable nodes can be placed in liquid.
  58. -- Protectors can be these.
  59. if def.groups and def.groups.immovable and def.groups.immovable > 0 then
  60. need_mod = false
  61. end
  62. -- Tree trunk nodes are exempt (they are nodeboxes).
  63. if def.groups and def.groups.tree and def.groups.tree > 0 then
  64. need_mod = false
  65. end
  66. -- If node is already marked as floodable, it has custom handling elsewhere.
  67. -- So we do not need to prevent placement in liquids, here.
  68. if def.floodable or def.on_flood then
  69. need_mod = false
  70. end
  71. -- Other special node exemptions.
  72. if string_find(name, "^bat2:") or
  73. string_find(name, "^stat2:") or
  74. string_find(name, "^tide:") or -- Tidal turbine must be in water!
  75. string_find(name, "^scaffolding:") or
  76. string_find(name, "^throwing:") or
  77. string_find(name, "^maptools:") or
  78. string_find(name, "^doors:") or
  79. string_find(name, "^protector:") or
  80. string_find(name, "^vines:") or
  81. string_find(name, "^morechests:") or
  82. string_find(name, "^chests:") or
  83. string_find(name, "^barter_table:") or
  84. string_find(name, "^engraver:") or
  85. string_find(name, "^solar:") or
  86. string_find(name, "^windy:") then
  87. need_mod = false
  88. end
  89. -- Is a nodebox.
  90. if name == "default:cactus" or
  91. name == "farming:soil" or
  92. name == "farming:soil_wet" or
  93. name == "farming:desert_sand_soil" or
  94. name == "farming:desert_sand_soil_wet" or
  95. name == "techcrafts:machine_casing" then
  96. need_mod = false
  97. end
  98. if need_mod then
  99. nodes[#nodes+1] = name
  100. def.can_place_in_liquid = false
  101. end
  102. end
  103. local function copy_pointed_thing(pointed_thing)
  104. return {
  105. type = pointed_thing.type,
  106. above = vector.new(pointed_thing.above),
  107. under = vector.new(pointed_thing.under),
  108. ref = pointed_thing.ref,
  109. }
  110. end
  111. local function user_name(user)
  112. return user and user:get_player_name() or ""
  113. end
  114. local function is_protected(pos, name)
  115. return core.is_protected(pos, name) and
  116. not minetest.check_player_privs(name, "protection_bypass")
  117. end
  118. -- Returns a logging function. For empty names, does not log.
  119. local function make_log(name)
  120. return name ~= "" and core.log or function() end
  121. end
  122. local function check_attached_node(p, n)
  123. local def = core.registered_nodes[n.name]
  124. local d = {x = 0, y = 0, z = 0}
  125. if def.paramtype2 == "wallmounted" or
  126. def.paramtype2 == "colorwallmounted" then
  127. -- The fallback vector here is in case 'wallmounted to dir' is nil due
  128. -- to voxelmanip placing a wallmounted node without resetting a
  129. -- pre-existing param2 value that is out-of-range for wallmounted.
  130. -- The fallback vector corresponds to param2 = 0.
  131. d = core.wallmounted_to_dir(n.param2) or {x = 0, y = 1, z = 0}
  132. else
  133. d.y = -1
  134. end
  135. local p2 = vector.add(p, d)
  136. local nn = core.get_node(p2).name
  137. local def2 = core.registered_nodes[nn]
  138. if def2 and not def2.walkable then
  139. return false
  140. end
  141. return true
  142. end
  143. -- Override what's in the server's builtin.
  144. function core.item_place_node(itemstack, placer, pointed_thing, param2)
  145. local def = itemstack:get_definition()
  146. if def.type ~= "node" or pointed_thing.type ~= "node" then
  147. return itemstack, false, nil
  148. end
  149. local under = pointed_thing.under
  150. local oldnode_under = core.get_node_or_nil(under)
  151. local above = pointed_thing.above
  152. local oldnode_above = core.get_node_or_nil(above)
  153. local playername = user_name(placer)
  154. local log = make_log(playername)
  155. if not oldnode_under or not oldnode_above then
  156. log("info", playername .. " tried to place"
  157. .. " node in unloaded position " .. core.pos_to_string(above))
  158. return itemstack, false, nil
  159. end
  160. local oldnode_uname = oldnode_under.name
  161. local oldnode_aname = oldnode_above.name
  162. -- Get node definitions, or fallback to default.
  163. local olddef_under = core.reg_ns_nodes[oldnode_uname] or
  164. core.registered_nodes[oldnode_uname]
  165. olddef_under = olddef_under or core.nodedef_default
  166. local olddef_above = core.reg_ns_nodes[oldnode_aname] or
  167. core.registered_nodes[oldnode_aname]
  168. olddef_above = olddef_above or core.nodedef_default
  169. if not olddef_above.buildable_to and not olddef_under.buildable_to then
  170. log("info", playername .. " tried to place"
  171. .. " node in invalid position " .. core.pos_to_string(above)
  172. .. ", replacing " .. oldnode_aname)
  173. return itemstack, false, nil
  174. end
  175. -- Don't permit building against some nodes.
  176. if olddef_under.not_buildable_against then
  177. return itemstack, false, nil
  178. end
  179. -- Place above pointed node
  180. local will_place_above = true
  181. local place_to = {x = above.x, y = above.y, z = above.z}
  182. -- If node under is buildable_to, place into it instead (eg. snow)
  183. if olddef_under.buildable_to then
  184. log("info", "node under is buildable to")
  185. place_to = {x = under.x, y = under.y, z = under.z}
  186. will_place_above = false
  187. end
  188. -- Feature addition by MustTest.
  189. if not def.can_place_in_liquid then
  190. if will_place_above and olddef_above.liquidtype ~= "none" then
  191. return itemstack, false, nil
  192. elseif not will_place_above and olddef_under.liquidtype ~= "none" then
  193. return itemstack, false, nil
  194. end
  195. end
  196. if is_protected(place_to, playername) then
  197. log("action", playername
  198. .. " tried to place " .. def.name
  199. .. " at protected position "
  200. .. core.pos_to_string(place_to))
  201. core.record_protection_violation(place_to, playername)
  202. return itemstack, false, nil
  203. end
  204. log("action", playername .. " places node "
  205. .. def.name .. " at " .. core.pos_to_string(place_to))
  206. local oldnode = core.get_node(place_to)
  207. local newnode = {name = def.name, param1 = 0, param2 = param2 or 0}
  208. -- Calculate direction for wall mounted stuff like torches and signs
  209. if def.place_param2 ~= nil then
  210. newnode.param2 = def.place_param2
  211. elseif (def.paramtype2 == "wallmounted" or
  212. def.paramtype2 == "colorwallmounted") and not param2 then
  213. local dir = {
  214. x = under.x - above.x,
  215. y = under.y - above.y,
  216. z = under.z - above.z
  217. }
  218. newnode.param2 = core.dir_to_wallmounted(dir)
  219. -- Calculate the direction for furnaces and chests and stuff
  220. elseif (def.paramtype2 == "facedir" or
  221. def.paramtype2 == "colorfacedir") and not param2 then
  222. local placer_pos = placer and placer:getpos()
  223. if placer_pos then
  224. local dir = {
  225. x = above.x - placer_pos.x,
  226. y = above.y - placer_pos.y,
  227. z = above.z - placer_pos.z
  228. }
  229. newnode.param2 = core.dir_to_facedir(dir)
  230. log("action", "facedir: " .. newnode.param2)
  231. end
  232. end
  233. local metatable = itemstack:get_meta():to_table().fields
  234. -- Transfer color information
  235. if metatable.palette_index and not def.place_param2 then
  236. local color_divisor = nil
  237. if def.paramtype2 == "color" then
  238. color_divisor = 1
  239. elseif def.paramtype2 == "colorwallmounted" then
  240. color_divisor = 8
  241. elseif def.paramtype2 == "colorfacedir" then
  242. color_divisor = 32
  243. end
  244. if color_divisor then
  245. local color = math.floor(metatable.palette_index / color_divisor)
  246. local other = newnode.param2 % color_divisor
  247. newnode.param2 = color * color_divisor + other
  248. end
  249. end
  250. -- Check if the node is attached and if it can be placed there
  251. if core.get_item_group(def.name, "attached_node") ~= 0 and
  252. not check_attached_node(place_to, newnode) then
  253. log("action", "attached node " .. def.name ..
  254. " can not be placed at " .. core.pos_to_string(place_to))
  255. return itemstack, false, nil
  256. end
  257. -- Add node and update
  258. -- This executes additional hooks (droplift, dirtspread).
  259. core.add_node(place_to, newnode)
  260. local take_item = true
  261. -- Run callback
  262. if def.after_place_node then
  263. -- Deepcopy place_to and pointed_thing because callback can modify it
  264. local place_to_copy = {x=place_to.x, y=place_to.y, z=place_to.z}
  265. local pointed_thing_copy = copy_pointed_thing(pointed_thing)
  266. if def.after_place_node(place_to_copy, placer, itemstack,
  267. pointed_thing_copy) then
  268. take_item = false
  269. end
  270. end
  271. -- Run script hook
  272. for _, callback in ipairs(core.registered_on_placenodes) do
  273. -- Deepcopy pos, node and pointed_thing because callback can modify them
  274. local place_to_copy = {x=place_to.x, y=place_to.y, z=place_to.z}
  275. local newnode_copy = {name=newnode.name, param1=newnode.param1, param2=newnode.param2}
  276. local oldnode_copy = {name=oldnode.name, param1=oldnode.param1, param2=oldnode.param2}
  277. local pointed_thing_copy = copy_pointed_thing(pointed_thing)
  278. if callback(place_to_copy, newnode_copy, placer, oldnode_copy, itemstack, pointed_thing_copy) then
  279. take_item = false
  280. end
  281. end
  282. if take_item then
  283. itemstack:take_item()
  284. end
  285. -- Return position as 3rd result. By MustTest.
  286. return itemstack, true, place_to
  287. end