liquidinteraction.lua 10 KB

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