portals.lua 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. local c_lava_source = minetest.get_content_id("default:lava_source")
  2. local c_lava_flowing = minetest.get_content_id("default:lava_flowing")
  3. local portal_disconnected_animation_tile = {
  4. name = "underworld_portal_disconnected_side.png",
  5. backface_culling = false,
  6. animation = {
  7. type = "vertical_frames",
  8. aspect_w = 16,
  9. aspect_h = 16,
  10. length = 0.5,
  11. }
  12. }
  13. local portal_vm_data = {}
  14. minetest.register_node("underworld:portal_disconnected", {
  15. description = "Underworld Portal (Disconnected)",
  16. drawtype = "nodebox",
  17. node_box = {
  18. type = "fixed",
  19. fixed = {
  20. {-0.499, -0.499, -0.499, 0.499, 0.499, 0.499}, -- Not 0.5, 0.5, ... due to visual glitches
  21. },
  22. },
  23. tiles = {
  24. "underworld_transparent.png",
  25. {name = "underworld_portal_disconnected_bottom.png", backface_culling = false},
  26. portal_disconnected_animation_tile,
  27. portal_disconnected_animation_tile,
  28. portal_disconnected_animation_tile,
  29. portal_disconnected_animation_tile,
  30. },
  31. paramtype = "light",
  32. sunlight_propagates = true,
  33. walkable = false,
  34. diggable = false,
  35. pointable = false,
  36. buildable_to = false,
  37. is_ground_content = false,
  38. drop = "",
  39. light_source = math.floor(minetest.LIGHT_MAX / 2),
  40. groups = {attached_node = 1, not_in_creative_inventory = 1},
  41. on_construct = function(pos)
  42. minetest.get_node_timer(pos):start(0)
  43. end,
  44. on_timer = function(pos)
  45. local meta = minetest.get_meta(pos)
  46. local target_pos = minetest.string_to_pos(meta:get_string("target"))
  47. if not target_pos then
  48. local min_search_y, max_search_y
  49. if pos.y > underworld.depth then
  50. min_search_y = -31000
  51. max_search_y = underworld.depth - underworld.barrier_size
  52. else
  53. min_search_y = -50
  54. max_search_y = 200
  55. end
  56. local search_pos = {x = pos.x + math.random(-40, 40), y = math.random(min_search_y, max_search_y), z = pos.z + math.random(-40, 40)}
  57. local minp = vector.subtract(search_pos, 40)
  58. local maxp = vector.add(search_pos, 40)
  59. minetest.emerge_area(
  60. minp,
  61. maxp,
  62. function(_, _, calls_remaining)
  63. if calls_remaining == 0 then
  64. local target_dist
  65. local vm = minetest.get_voxel_manip()
  66. local emin, emax = vm:read_from_map(minp, maxp)
  67. local area = VoxelArea:new({MinEdge = emin, MaxEdge = emax})
  68. vm:get_data(portal_vm_data)
  69. for z = math.max(minp.z, emin.z), math.min(maxp.z, emax.z) do
  70. for y = math.max(min_search_y, minp.y, emin.y), math.min(max_search_y, maxp.y, emax.y) do
  71. for x = math.max(minp.x, emin.x), math.min(maxp.x, emax.x) do
  72. local cid = portal_vm_data[area:index(x, y, z)]
  73. if cid == minetest.CONTENT_AIR then
  74. -- Check if node belown is walkable and node above is air
  75. local under_cid = portal_vm_data[area:index(x, y - 1, z)]
  76. local under_node_name = minetest.get_name_from_content_id(under_cid)
  77. local above_cid = portal_vm_data[area:index(x, y + 1, z)]
  78. if minetest.registered_nodes[under_node_name].walkable and above_cid == minetest.CONTENT_AIR then
  79. -- Check for surrounding lava
  80. local lava_around = false
  81. for check_z = -5, 5 do
  82. for check_y = -5, 5 do
  83. for check_x = -5, 5 do
  84. local check_cid = portal_vm_data[area:index(x + check_x, y + check_y, z + check_z)]
  85. if check_cid == c_lava_source or check_cid == c_lava_flowing then
  86. lava_around = true
  87. end
  88. end
  89. end
  90. end
  91. if not lava_around then
  92. -- Found suitable spot
  93. local found_pos = {x = x, y = y, z = z}
  94. local found_dist = vector.distance(pos, search_pos)
  95. if not target_pos or found_dist < target_dist then
  96. target_pos = found_pos
  97. target_dist = found_dist
  98. end
  99. end
  100. end
  101. end
  102. end
  103. end
  104. end
  105. if target_pos then
  106. if minetest.get_node(pos).name == "underworld:portal_disconnected" then
  107. minetest.set_node(pos, {name = "underworld:portal"})
  108. meta:set_string("target", minetest.pos_to_string(target_pos))
  109. end
  110. else
  111. minetest.get_node_timer(pos):start(1) -- Retry search in one second
  112. end
  113. end
  114. end
  115. )
  116. end
  117. end,
  118. })
  119. local portal_animation_tile = {
  120. name = "underworld_portal_side.png",
  121. backface_culling = false,
  122. animation = {
  123. type = "vertical_frames",
  124. aspect_w = 16,
  125. aspect_h = 16,
  126. length = 0.5,
  127. }
  128. }
  129. minetest.register_node("underworld:portal", {
  130. description = "Underworld Portal",
  131. drawtype = "nodebox",
  132. node_box = {
  133. type = "fixed",
  134. fixed = {
  135. {-0.499, -0.499, -0.499, 0.499, 0.499, 0.499}, -- Not 0.5, 0.5, ... due to visual glitches
  136. },
  137. },
  138. tiles = {
  139. "underworld_transparent.png",
  140. {name = "underworld_portal_bottom.png", backface_culling = false},
  141. portal_animation_tile,
  142. portal_animation_tile,
  143. portal_animation_tile,
  144. portal_animation_tile,
  145. },
  146. paramtype = "light",
  147. sunlight_propagates = true,
  148. walkable = false,
  149. diggable = false,
  150. pointable = false,
  151. buildable_to = false,
  152. is_ground_content = false,
  153. drop = "",
  154. light_source = math.floor(minetest.LIGHT_MAX / 2),
  155. groups = {attached_node = 1, not_in_creative_inventory = 1},
  156. })
  157. local function teleport_ps_def(pos)
  158. return {
  159. texture = "underworld_portal_particle.png",
  160. amount = 500,
  161. time = 0.5,
  162. minpos = vector.subtract(pos, 0.5),
  163. maxpos = vector.add(pos, 0.5),
  164. minvel = {x = -2, y = -2, z = -2},
  165. maxvel = {x = 2, y = 2, z = 2},
  166. minacc = {x = 0, y = 0, z = 0},
  167. maxacc = {x = 0, y = 0, z = 0},
  168. minexptime = 1,
  169. maxexptime = 1,
  170. minsize = 0.5,
  171. maxsize = 2,
  172. glow = minetest.LIGHT_MAX,
  173. }
  174. end
  175. minetest.register_abm({
  176. label = "Manage portals",
  177. nodenames = {"underworld:portal", "underworld:portal_disconnected"},
  178. interval = 1,
  179. chance = 1,
  180. action = function(pos, node)
  181. local texture
  182. if node.name == "underworld:portal" then
  183. texture = "underworld_portal_particle.png"
  184. else
  185. texture = "underworld_portal_disconnected_particle.png"
  186. end
  187. minetest.add_particlespawner({
  188. texture = texture,
  189. amount = 50,
  190. time = 2,
  191. minpos = vector.subtract(pos, 0.5),
  192. maxpos = vector.add(pos, 0.5),
  193. minvel = {x = 0, y = 0.25, z = 0},
  194. maxvel = {x = 0, y = 0.75, z = 0},
  195. minacc = {x = 0, y = 0.25, z = 0},
  196. maxacc = {x = 0, y = 0.75, z = 0},
  197. minexptime = 0.5,
  198. maxexptime = 1,
  199. minsize = 0.5,
  200. maxsize = 2,
  201. minexptime = 0.5,
  202. maxexptime = 1,
  203. minsize = 0.5,
  204. maxsize = 2,
  205. glow = math.floor(minetest.LIGHT_MAX / 2),
  206. })
  207. if node.name == "underworld:portal" and math.random(1, 4) == 1 then
  208. local target_pos = minetest.string_to_pos(minetest.get_meta(pos):get_string("target"))
  209. if target_pos then
  210. for _, obj in ipairs(minetest.get_objects_inside_radius(pos, 1)) do
  211. if obj:is_player() then
  212. -- Force emerge
  213. minetest.get_voxel_manip():read_from_map(vector.subtract(target_pos, 5), vector.add(target_pos, 5))
  214. minetest.add_particlespawner(teleport_ps_def(vector.add(obj:get_pos(), {x = 0, y = 1, z = 0})))
  215. obj:set_pos(target_pos)
  216. minetest.add_particlespawner(teleport_ps_def(vector.add(obj:get_pos(), {x = 0, y = 1, z = 0})))
  217. end
  218. end
  219. end
  220. end
  221. end,
  222. })
  223. minetest.register_node("underworld:portal_base", {
  224. description = "Underworld Portal Base",
  225. tiles = {
  226. "default_coal_block.png",
  227. "default_obsidian.png",
  228. "default_obsidian.png^(underworld_portal_base_side.png)",
  229. "default_obsidian.png^(underworld_portal_base_side.png)",
  230. "default_obsidian.png^(underworld_portal_base_side.png^[transformFY)",
  231. "default_obsidian.png^(underworld_portal_base_side.png^[transformFY)",
  232. },
  233. is_ground_content = false,
  234. groups = {cracky = 3, level = 2},
  235. sounds = default.node_sound_stone_defaults(),
  236. on_construct = function(pos)
  237. local above = vector.add(pos, {x = 0, y = 1, z = 0})
  238. if minetest.get_node(above).name == "air" then
  239. minetest.set_node(above, {name = "underworld:portal_disconnected"})
  240. end
  241. end,
  242. on_destruct = function(pos)
  243. local above = vector.add(pos, {x = 0, y = 1, z = 0})
  244. local above_node_name = minetest.get_node(above).name
  245. if above_node_name == "underworld:portal_disconnected" or above_node_name == "underworld:portal" then
  246. minetest.remove_node(above)
  247. end
  248. end,
  249. on_rotate = false,
  250. })
  251. minetest.register_craft({
  252. output = "underworld:portal_base",
  253. recipe = {
  254. {"default:coalblock", "default:coalblock", "default:coalblock"},
  255. {"default:mese", "default:mese", "default:mese"},
  256. {"default:obsidian", "default:obsidian", "default:obsidian"},
  257. },
  258. })