physics.lua 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. local modname = minetest.get_current_modname()
  2. local minetest, nodeupdate, vector = minetest, nodeupdate, vector
  3. local random, pairs = math.random, pairs
  4. local cools_to, melt_densities, random_melt_product = ...
  5. local function melt(pos, node)
  6. local node_name = node and node.name or minetest.get_node(pos).name
  7. minetest.set_node(pos, {name=random_melt_product(node_name)})
  8. return true
  9. end
  10. local function cool_down(pos, node)
  11. local node_name = node and node.name or minetest.get_node(pos).name
  12. local cold_name = cools_to[node_name]
  13. if not cold_name then
  14. return false
  15. end
  16. minetest.set_node(pos, {name = cold_name})
  17. minetest.spawn_falling_node(pos)-- so cooled ores fall to the bottom of a pool of water
  18. return true
  19. end
  20. local function swap_nodes(pos1, node1, pos2, node2)
  21. -- swap_node is faster than set_node, avoiding node destructors/constructors
  22. -- also metadata is not reset, which two molten_ores don't have anyway
  23. minetest.swap_node(pos1, node2 or minetest.get_node(pos2))
  24. minetest.swap_node(pos2, node1 or minetest.get_node(pos1))
  25. end
  26. -- fluid dynamics
  27. minetest.register_abm({
  28. nodenames = {"group:molten_ore"},
  29. interval = 1,
  30. chance = 1,
  31. action = function(pos, node)
  32. if minetest.get_item_group(node.name, "molten_ore") < 3 then
  33. return
  34. end
  35. local flow_name = node.name.."_flowing"
  36. -- look below
  37. local flow_nodes = minetest.find_nodes_in_area(
  38. {x=pos.x , y=pos.y - 1, z=pos.z},
  39. {x=pos.x , y=pos.y - 1, z=pos.z},
  40. "group:molten_ore_flowing"
  41. )
  42. for _,fp in pairs(flow_nodes) do
  43. swap_nodes(pos, node, fp)
  44. return
  45. end
  46. -- look one node out
  47. flow_nodes = minetest.find_nodes_in_area(
  48. {x=pos.x - 1, y=pos.y - 1, z=pos.z - 1},
  49. {x=pos.x + 1, y=pos.y - 1, z=pos.z + 1},
  50. "group:molten_ore_flowing"
  51. )
  52. for _,fp in pairs(flow_nodes) do
  53. -- check above to make sure it can get here
  54. local na = minetest.get_node({x=fp.x, y=fp.y+1, z=fp.z})
  55. local g = minetest.get_item_group(na.name, "molten_ore")
  56. if g > 0 then
  57. swap_nodes(pos, node, fp)
  58. return
  59. end
  60. end
  61. -- look two nodes out
  62. flow_nodes = minetest.find_nodes_in_area(
  63. {x=pos.x - 2, y=pos.y - 1, z=pos.z - 2},
  64. {x=pos.x + 2, y=pos.y - 1, z=pos.z + 2},
  65. "group:molten_ore_flowing"
  66. )
  67. for _,fp in pairs(flow_nodes) do
  68. -- check above
  69. local na = minetest.get_node({x=fp.x, y=fp.y+1, z=fp.z})
  70. local ga = minetest.get_item_group(na.name, "molten_ore")
  71. if ga > 0 then
  72. -- check between above and node
  73. local nb = minetest.get_node({x=(fp.x + pos.x) / 2, y=pos.y, z=(fp.z + pos.z) / 2})
  74. local gb = minetest.get_item_group(nb.name, "molten_ore")
  75. if gb > 0 then
  76. swap_nodes(pos, node, fp)
  77. return
  78. end
  79. end
  80. end
  81. end,
  82. })
  83. -- dense metals sink to the bottom
  84. minetest.register_abm({
  85. nodenames = {"group:molten_ore_source"},
  86. neightbors = {"group:molten_ore_source"},
  87. interval = 4,
  88. chance = 2,
  89. action = function(pos, node)
  90. -- look one node out
  91. local light_nodes = minetest.find_nodes_in_area(
  92. {x=pos.x - 1, y=pos.y - 1, z=pos.z - 1},
  93. {x=pos.x + 1, y=pos.y - 1, z=pos.z + 1},
  94. "group:molten_ore_source"
  95. )
  96. for _,fp in pairs(light_nodes) do
  97. local n = minetest.get_node(fp)
  98. local sd = melt_densities[node.name]
  99. local dd = melt_densities[n.name]
  100. if dd and sd and dd < sd then
  101. swap_nodes(pos, node, fp, n)
  102. return
  103. end
  104. end
  105. end,
  106. })
  107. local function is_heated(pos, node)
  108. -- don't cool near active electrodes
  109. if minetest.find_node_near(pos, 4, {modname..":electrode_on"}) then
  110. return true
  111. end
  112. -- BUG: should only heat above it
  113. if minetest.find_node_near(pos, 4, {modname..":burner_on"}) then
  114. return true
  115. end
  116. -- BUG: should only heat above it
  117. if minetest.find_node_near(pos, 5, {modname..":oil_burner_on"}) then
  118. return true
  119. end
  120. -- don't cool near heater bricks
  121. if minetest.find_node_near(pos, 2, {modname..":furnace_heater"}) then
  122. return true
  123. end
  124. return false
  125. end
  126. -- air cooling
  127. minetest.register_abm({
  128. nodenames = {"group:molten_ore"},
  129. interval = 10,
  130. chance = 15,
  131. action = function(pos, node)
  132. if is_heated(pos, node) then
  133. return
  134. end
  135. -- let ore fall before cooling
  136. local below = minetest.get_node_or_nil({x=pos.x, y=pos.y-1, z=pos.z})
  137. if below then
  138. if 0 ~= minetest.get_item_group(below.name, "molten_ore_flowing") then
  139. return
  140. end
  141. -- melt cools 3 times more slowly over refractory materials
  142. -- helps prevent clogs in structures
  143. if 0 ~= minetest.get_item_group(below.name, "refractory") then
  144. if random(3) >= 2 then
  145. return
  146. end
  147. end
  148. end
  149. if cool_down(pos, node) then
  150. minetest.sound_play("default_cool_lava",
  151. {pos = pos, max_hear_distance = 16, gain = 0.25})
  152. end
  153. end,
  154. })
  155. local function spawnSteam(pos)
  156. pos.y = pos.y+1
  157. minetest.add_particlespawner({
  158. amount = 20,
  159. time = 3,
  160. minpos = vector.subtract(pos, 2 / 2),
  161. maxpos = vector.add(pos, 2 / 2),
  162. minvel = {x=-0.1, y=0, z=-0.1},
  163. maxvel = {x=0.1, y=0.5, z=0.1},
  164. minacc = {x=-0.1, y=0.1, z=-0.1},
  165. maxacc = {x=0.1, y=0.3, z=0.1},
  166. minexptime = 1,
  167. maxexptime = 3,
  168. minsize = 10,
  169. maxsize = 20,
  170. texture = modname.."_steam.png^[colorize:white:120",
  171. })
  172. end
  173. -- water cooling
  174. minetest.register_abm({
  175. nodenames = {"group:molten_ore"},
  176. neighbors = {
  177. "default:water_source",
  178. "default:water_flowing",
  179. "default:river_water_source",
  180. "default:river_water_flowing"
  181. },
  182. interval = 2,
  183. chance = 2,
  184. action = function(pos, node)
  185. if cool_down(pos, node) then
  186. spawnSteam(pos)
  187. minetest.sound_play("default_cool_lava",
  188. {pos = pos, max_hear_distance = 16, gain = 0.25})
  189. end
  190. end,
  191. })
  192. local function try_conduct(pos, node)
  193. local node = minetest.get_node_or_nil(pos)
  194. if node and 0 == minetest.get_item_group(node.name, "refractory") and
  195. 0 == minetest.get_item_group(node.name, "molten_ore") then
  196. melt(pos, node)
  197. -- avoid conduction loops around heaters
  198. if not is_heated(pos, node) then
  199. -- but conserve energy if possible, which also protects from
  200. -- melting entire mountains from a single molten ore
  201. cool_down(pos, node)
  202. end
  203. return true
  204. end
  205. return false
  206. end
  207. local heat_conduct_dirs = {
  208. {1, 0}, {-1, 0}, {0, 1}, {0, -1},
  209. }
  210. -- molten ore conducts heat, either remelting other things or destroying them
  211. minetest.register_abm({
  212. nodenames = {"group:molten_ore_source"},
  213. interval = 5,
  214. chance = 40,
  215. action = function(pos, node)
  216. -- prefer the node below
  217. if try_conduct({x=pos.x, y=pos.y - 1, z=pos.z }) then
  218. return
  219. end
  220. -- then start with a random direction and rotate
  221. local start = random(4)
  222. for i=0, 3 do
  223. local dir = heat_conduct_dirs[(start + i) % 4 + 1]
  224. if try_conduct({x=pos.x + dir[1], y=pos.y, z=pos.z + dir[2]}) then
  225. return
  226. end
  227. end
  228. -- above is not destroyed
  229. end,
  230. })