liquidinteraction.lua 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  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. -- Override what's in the server's builtin.
  125. function core.item_place_node(itemstack, placer, pointed_thing, param2)
  126. local def = itemstack:get_definition()
  127. if def.type ~= "node" or pointed_thing.type ~= "node" then
  128. return itemstack, false, nil
  129. end
  130. local under = pointed_thing.under
  131. local oldnode_under = core.get_node_or_nil(under)
  132. local above = pointed_thing.above
  133. local oldnode_above = core.get_node_or_nil(above)
  134. local playername = user_name(placer)
  135. local log = make_log(playername)
  136. if not oldnode_under or not oldnode_above then
  137. log("info", playername .. " tried to place"
  138. .. " node in unloaded position " .. core.pos_to_string(above))
  139. return itemstack, false, nil
  140. end
  141. local oldnode_uname = oldnode_under.name
  142. local oldnode_aname = oldnode_above.name
  143. -- Get node definitions, or fallback to default.
  144. local olddef_under = core.reg_ns_nodes[oldnode_uname] or
  145. core.registered_nodes[oldnode_uname]
  146. olddef_under = olddef_under or core.nodedef_default
  147. local olddef_above = core.reg_ns_nodes[oldnode_aname] or
  148. core.registered_nodes[oldnode_aname]
  149. olddef_above = olddef_above or core.nodedef_default
  150. if not olddef_above.buildable_to and not olddef_under.buildable_to then
  151. log("info", playername .. " tried to place"
  152. .. " node in invalid position " .. core.pos_to_string(above)
  153. .. ", replacing " .. oldnode_aname)
  154. return itemstack, false, nil
  155. end
  156. -- Don't permit building against some nodes.
  157. if olddef_under.not_buildable_against then
  158. return itemstack, false, nil
  159. end
  160. -- Place above pointed node
  161. local will_place_above = true
  162. local place_to = {x = above.x, y = above.y, z = above.z}
  163. -- If node under is buildable_to, place into it instead (eg. snow)
  164. if olddef_under.buildable_to then
  165. log("info", "node under is buildable to")
  166. place_to = {x = under.x, y = under.y, z = under.z}
  167. will_place_above = false
  168. end
  169. -- Feature addition by MustTest.
  170. if not def.can_place_in_liquid then
  171. if will_place_above and olddef_above.liquidtype ~= "none" then
  172. return itemstack, false, nil
  173. elseif not will_place_above and olddef_under.liquidtype ~= "none" then
  174. return itemstack, false, nil
  175. end
  176. end
  177. if is_protected(place_to, playername) then
  178. log("action", playername
  179. .. " tried to place " .. def.name
  180. .. " at protected position "
  181. .. core.pos_to_string(place_to))
  182. core.record_protection_violation(place_to, playername)
  183. return itemstack, false, nil
  184. end
  185. log("action", playername .. " places node "
  186. .. def.name .. " at " .. core.pos_to_string(place_to))
  187. local oldnode = core.get_node(place_to)
  188. local newnode = {name = def.name, param1 = 0, param2 = param2 or 0}
  189. -- Calculate direction for wall mounted stuff like torches and signs
  190. if def.place_param2 ~= nil then
  191. newnode.param2 = def.place_param2
  192. elseif (def.paramtype2 == "wallmounted" or
  193. def.paramtype2 == "colorwallmounted") and not param2 then
  194. local dir = {
  195. x = under.x - above.x,
  196. y = under.y - above.y,
  197. z = under.z - above.z
  198. }
  199. newnode.param2 = core.dir_to_wallmounted(dir)
  200. -- Calculate the direction for furnaces and chests and stuff
  201. elseif (def.paramtype2 == "facedir" or
  202. def.paramtype2 == "colorfacedir") and not param2 then
  203. local placer_pos = placer and placer:get_pos()
  204. if placer_pos then
  205. local dir = {
  206. x = above.x - placer_pos.x,
  207. y = above.y - placer_pos.y,
  208. z = above.z - placer_pos.z
  209. }
  210. newnode.param2 = core.dir_to_facedir(dir)
  211. log("action", "facedir: " .. newnode.param2)
  212. end
  213. end
  214. local metatable = itemstack:get_meta():to_table().fields
  215. -- Transfer color information
  216. if metatable.palette_index and not def.place_param2 then
  217. local color_divisor = nil
  218. if def.paramtype2 == "color" then
  219. color_divisor = 1
  220. elseif def.paramtype2 == "colorwallmounted" then
  221. color_divisor = 8
  222. elseif def.paramtype2 == "colorfacedir" then
  223. color_divisor = 32
  224. end
  225. if color_divisor then
  226. local color = math_floor(metatable.palette_index / color_divisor)
  227. local other = newnode.param2 % color_divisor
  228. newnode.param2 = color * color_divisor + other
  229. end
  230. end
  231. -- Check if the node is attached and if it can be placed there
  232. local attached_rating = core.get_item_group(def.name, "attached_node")
  233. if attached_rating ~= 0 and
  234. not utility.check_attached_node(place_to, newnode, attached_rating) then
  235. log("action", "attached node " .. def.name ..
  236. " can not be placed at " .. core.pos_to_string(place_to))
  237. return itemstack, false, nil
  238. end
  239. -- Add node and update
  240. -- This executes additional hooks (droplift, dirtspread).
  241. core.add_node(place_to, newnode)
  242. local take_item = true
  243. -- Run callback
  244. if def.after_place_node then
  245. -- Deepcopy place_to and pointed_thing because callback can modify it
  246. local place_to_copy = {x=place_to.x, y=place_to.y, z=place_to.z}
  247. local pointed_thing_copy = copy_pointed_thing(pointed_thing)
  248. if def.after_place_node(place_to_copy, placer, itemstack,
  249. pointed_thing_copy) then
  250. take_item = false
  251. end
  252. end
  253. -- Run script hook
  254. for _, callback in ipairs(core.registered_on_placenodes) do
  255. -- Deepcopy pos, node and pointed_thing because callback can modify them
  256. local place_to_copy = {x=place_to.x, y=place_to.y, z=place_to.z}
  257. local newnode_copy = {name=newnode.name, param1=newnode.param1, param2=newnode.param2}
  258. local oldnode_copy = {name=oldnode.name, param1=oldnode.param1, param2=oldnode.param2}
  259. local pointed_thing_copy = copy_pointed_thing(pointed_thing)
  260. if callback(place_to_copy, newnode_copy, placer, oldnode_copy, itemstack, pointed_thing_copy) then
  261. take_item = false
  262. end
  263. end
  264. if take_item then
  265. itemstack:take_item()
  266. end
  267. -- Return position as 3rd result. By MustTest.
  268. return itemstack, true, place_to
  269. end