init.lua 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. if not minetest.global_exists("flameportal") then flameportal = {} end
  2. flameportal.modpath = minetest.get_modpath("flameportal")
  3. -- Localize for performance.
  4. local vector_round = vector.round
  5. local math_random = math.random
  6. -- Check if the position `pos` is the center of a portal ring.
  7. flameportal.check_is_gateway = function(pos)
  8. local p = vector_round(pos)
  9. local positions = {
  10. -- North side.
  11. {x=p.x-1, y=p.y, z=p.z+2},
  12. {x=p.x, y=p.y, z=p.z+2},
  13. {x=p.x+1, y=p.y, z=p.z+2},
  14. -- South side.
  15. {x=p.x-1, y=p.y, z=p.z-2},
  16. {x=p.x, y=p.y, z=p.z-2},
  17. {x=p.x+1, y=p.y, z=p.z-2},
  18. -- West side.
  19. {x=p.x-2, y=p.y, z=p.z+1},
  20. {x=p.x-2, y=p.y, z=p.z },
  21. {x=p.x-2, y=p.y, z=p.z-1},
  22. -- East side.
  23. {x=p.x+2, y=p.y, z=p.z+1},
  24. {x=p.x+2, y=p.y, z=p.z },
  25. {x=p.x+2, y=p.y, z=p.z-1},
  26. }
  27. for k, v in ipairs(positions) do
  28. local n1 = minetest.get_node(v).name
  29. if n1 ~= "default:obsidian" then return false end
  30. end
  31. local pos_air = {
  32. {x=p.x, y=p.y, z=p.z },
  33. {x=p.x, y=p.y, z=p.z+1},
  34. {x=p.x, y=p.y, z=p.z-1},
  35. {x=p.x+1, y=p.y, z=p.z },
  36. {x=p.x-1, y=p.y, z=p.z },
  37. {x=p.x+1, y=p.y, z=p.z+1},
  38. {x=p.x+1, y=p.y, z=p.z-1},
  39. {x=p.x-1, y=p.y, z=p.z+1},
  40. {x=p.x-1, y=p.y, z=p.z-1},
  41. }
  42. for k, v in ipairs(pos_air) do
  43. local n1 = minetest.get_node(v).name
  44. if n1 ~= "air" then return false end
  45. end
  46. return true
  47. end
  48. -- Attempt to find a gateway using a brute-force method, starting at position `pos`.
  49. -- Note: the gateway can only be found if `pos` is the position of one of the obsidian blocks at the top.
  50. flameportal.find_gateway = function(pos)
  51. local p = {x=pos.x, y=pos.y, z=pos.z}
  52. local positions = {
  53. -- North side.
  54. {x=p.x-2, y=p.y, z=p.z+2},
  55. {x=p.x-1, y=p.y, z=p.z+2},
  56. {x=p.x, y=p.y, z=p.z+2},
  57. {x=p.x+1, y=p.y, z=p.z+2},
  58. {x=p.x+2, y=p.y, z=p.z+2},
  59. -- South side.
  60. {x=p.x-2, y=p.y, z=p.z-2},
  61. {x=p.x-1, y=p.y, z=p.z-2},
  62. {x=p.x, y=p.y, z=p.z-2},
  63. {x=p.x+1, y=p.y, z=p.z-2},
  64. {x=p.x+2, y=p.y, z=p.z-2},
  65. -- West side.
  66. {x=p.x-2, y=p.y, z=p.z+2},
  67. {x=p.x-2, y=p.y, z=p.z+1},
  68. {x=p.x-2, y=p.y, z=p.z },
  69. {x=p.x-2, y=p.y, z=p.z-1},
  70. {x=p.x-2, y=p.y, z=p.z-2},
  71. -- East side.
  72. {x=p.x+2, y=p.y, z=p.z+2},
  73. {x=p.x+2, y=p.y, z=p.z+1},
  74. {x=p.x+2, y=p.y, z=p.z },
  75. {x=p.x+2, y=p.y, z=p.z-1},
  76. {x=p.x+2, y=p.y, z=p.z-2},
  77. }
  78. for k, v in ipairs(positions) do
  79. if flameportal.check_is_gateway(v) then return true, v end
  80. end
  81. return false, nil
  82. end
  83. -- Attempt to activate a gateway at the given position.
  84. flameportal.activate_gateway = function(pos)
  85. if flameportal.check_is_gateway(pos) == false then return end
  86. local p = vector_round(pos)
  87. minetest.log("action", "Nether portal activated at (" .. minetest.pos_to_string(p) .. ")")
  88. local flames = {
  89. -- North side.
  90. {x=p.x-1, y=p.y+1, z=p.z+2},
  91. {x=p.x, y=p.y+1, z=p.z+2},
  92. {x=p.x+1, y=p.y+1, z=p.z+2},
  93. -- South side.
  94. {x=p.x-1, y=p.y+1, z=p.z-2},
  95. {x=p.x, y=p.y+1, z=p.z-2},
  96. {x=p.x+1, y=p.y+1, z=p.z-2},
  97. -- West side.
  98. {x=p.x-2, y=p.y+1, z=p.z+1},
  99. {x=p.x-2, y=p.y+1, z=p.z },
  100. {x=p.x-2, y=p.y+1, z=p.z-1},
  101. -- East side.
  102. {x=p.x+2, y=p.y+1, z=p.z+1},
  103. {x=p.x+2, y=p.y+1, z=p.z },
  104. {x=p.x+2, y=p.y+1, z=p.z-1},
  105. }
  106. for k, v in ipairs(flames) do
  107. if math_random(1, 3) == 1 then
  108. if minetest.get_node(v).name == "air" then
  109. minetest.add_node(v, {name="fire:nether_flame"})
  110. end
  111. end
  112. end
  113. local void = {
  114. {x=p.x, y=p.y, z=p.z },
  115. {x=p.x, y=p.y, z=p.z+1},
  116. {x=p.x, y=p.y, z=p.z-1},
  117. {x=p.x+1, y=p.y, z=p.z },
  118. {x=p.x-1, y=p.y, z=p.z },
  119. {x=p.x-1, y=p.y, z=p.z-1},
  120. {x=p.x+1, y=p.y, z=p.z-1},
  121. {x=p.x-1, y=p.y, z=p.z+1},
  122. {x=p.x+1, y=p.y, z=p.z+1},
  123. }
  124. for k, v in ipairs(void) do
  125. minetest.add_node(v, {name="voidstone:void"})
  126. end
  127. -- Success.
  128. return true
  129. end
  130. flameportal.make_platform = function(param)
  131. for x = param.minp.x, param.maxp.x, 1 do
  132. for y = param.minp.y, param.maxp.y, 1 do
  133. for z = param.minp.z, param.maxp.z, 1 do
  134. local pos = {x=x, y=y, z=z}
  135. local node = minetest.get_node(pos)
  136. --minetest.chat_send_all(minetest.pos_to_string(pos) .. "=" .. node.name)
  137. if node.name == "air" or node.name == "rackstone:redrack" then
  138. if not minetest.test_protection(pos, "") then
  139. minetest.add_node(pos, {name="rackstone:redrack"})
  140. if vector.equals(pos, param.top) then
  141. minetest.add_node(pos, {name="flameportal:redrack"})
  142. end
  143. end
  144. end
  145. end
  146. end
  147. end
  148. end
  149. flameportal.make_flame_pillar = function(param)
  150. for x = param.minp.x, param.maxp.x, 1 do
  151. for y = param.minp.y, param.maxp.y, 1 do
  152. for z = param.minp.z, param.maxp.z, 1 do
  153. if math_random(1, 7) == 1 then
  154. -- Leave a vertical tunnel clear for the player to drop down.
  155. if not (x == param.avoid_x and z == param.avoid_z) then
  156. local pos = {x=x, y=y, z=z}
  157. local node = minetest.get_node(pos)
  158. if node.name == "air" then
  159. minetest.set_node(pos, {name="fire:basic_flame"})
  160. end
  161. end
  162. end
  163. end
  164. end
  165. end
  166. end
  167. flameportal.teleport_player = function(name, voidpos)
  168. local player = minetest.get_player_by_name(name)
  169. if player and player:is_player() then
  170. -- Don't teleport dead players.
  171. if player:get_hp() > 0 then
  172. if minetest.get_node(voidpos).name ~= "voidstone:void" then return end
  173. if sheriff.is_cheater(name) then
  174. if sheriff.punish_probability(name) then
  175. sheriff.punish_player(name)
  176. return
  177. end
  178. end
  179. local pp = player:get_pos()
  180. if pp.y > -25000 then
  181. -- Player is not in nether. Teleport them to the nether.
  182. flameportal.teleport_player_to_nether(player, voidpos)
  183. else
  184. local pname = player:get_player_name()
  185. local storage = flameportal.modstorage
  186. local return_pos = minetest.string_to_pos(storage:get_string(pname))
  187. if return_pos then
  188. minetest.log("action", "Player " .. pname .. " returns from the nether at (" .. minetest.pos_to_string(voidpos) .. ")")
  189. preload_tp.execute({
  190. player_name = pname,
  191. target_position = return_pos,
  192. emerge_radius = 32,
  193. particle_effects = true,
  194. post_teleport_callback = function()
  195. -- Damage player on return journey only sometimes.
  196. if math_random(1, 30) == 1 then
  197. minetest.after(0.5, function()
  198. local pref = minetest.get_player_by_name(pname)
  199. if pref and pref:is_player() then
  200. utility.damage_player(pref, "poison", (math_random(2, 15)*500), "portal")
  201. end
  202. end)
  203. end
  204. portal_sickness.on_use_portal(pname)
  205. end,
  206. force_teleport = false,
  207. teleport_sound = "nether_portal_usual",
  208. send_blocks = true,
  209. })
  210. end
  211. end
  212. end
  213. end
  214. end
  215. -- 18200,-30794,-22386
  216. -- Teleport a player into or out of the nether.
  217. flameportal.teleport_player_to_nether = function(player, voidpos)
  218. local pname = player:get_player_name()
  219. local meta = minetest.get_meta(voidpos)
  220. local spos = meta:get_string("target") or ""
  221. local target
  222. meta:mark_as_private("target")
  223. if spos == "" then
  224. -- If metadata target hasn't been initialized yet.
  225. local pos = vector_round(voidpos)
  226. --local pr = PcgRandom(pos.x+pos.y+pos.z)
  227. target = {x=0, y=-30790, z=0}
  228. --target.x = pr:next(-30000, 30000)
  229. --target.z = pr:next(-30000, 30000)
  230. -- Chose a wholy random location.
  231. target.x = math_random(-30000, 30000)
  232. target.z = math_random(-30000, 30000)
  233. meta:set_string("target", minetest.pos_to_string(target))
  234. meta:mark_as_private({"target"})
  235. else
  236. -- Target location already set, use it.
  237. target = minetest.string_to_pos(spos)
  238. if target == nil then return end
  239. end
  240. -- Create platform beneath player.
  241. local minp = {x=target.x-1, y=target.y-12, z=target.z-1}
  242. local maxp = {x=target.x+1, y=target.y-6, z=target.z+1}
  243. local tb = {minp=minp, maxp=maxp, top={x=target.x, y=target.y-6, z=target.z}}
  244. -- Create flame pillar.
  245. local minp2 = {x=target.x-1, y=target.y-3, z=target.z-1}
  246. local maxp2 = {x=target.x+1, y=target.y+10, z=target.z+1}
  247. local tb2 = {
  248. minp = minp2,
  249. maxp = maxp2,
  250. avoid_x = target.x,
  251. avoid_z = target.z,
  252. }
  253. target.y = target.y+10
  254. preload_tp.execute({
  255. player_name = pname,
  256. target_position = target,
  257. emerge_radius = 64,
  258. particle_effects = true,
  259. -- Test code.
  260. --spinup_time = 3,
  261. -- Map modifications have to be done here, instead of in 'pre_teleport_callback'
  262. -- because otherwise if we wait for the long spinup time, the map will become
  263. -- unloaded again and we won't spawn the platform or the flame pillar!
  264. on_map_loaded = function()
  265. --minetest.chat_send_all('pretp!')
  266. flameportal.make_platform(tb)
  267. flameportal.make_flame_pillar(tb2)
  268. end,
  269. post_teleport_callback = function()
  270. local storage = flameportal.modstorage
  271. local return_pos = {x=voidpos.x, y=voidpos.y+1, z=voidpos.z}
  272. storage:set_string(pname, minetest.pos_to_string(return_pos))
  273. minetest.log("action", "Player " .. pname .. " teleports into the nether @ (" .. minetest.pos_to_string(target) .. ")")
  274. end,
  275. teleport_sound = "nether_portal_usual",
  276. send_blocks = true,
  277. })
  278. end
  279. -- API function.
  280. function flameportal.clear_return_location(pname)
  281. flameportal.modstorage:set_string(pname, nil)
  282. end
  283. -- Flint & steel should call this when used on obsidian.
  284. -- This function should check if the player is standing on void, and teleport them.
  285. flameportal.try_teleport_on_flint_use =
  286. function(pref)
  287. if not pref or not pref:is_player() then return end
  288. local p1 = utility.node_under_pos(pref:get_pos())
  289. if minetest.get_node(p1).name == "voidstone:void" then
  290. flameportal.teleport_player(pref:get_player_name(), p1)
  291. end
  292. end
  293. -- This function should be called whenever a nodetype that takes part in portal construction is removed.
  294. flameportal.after_portal_destruct = function(pos, oldnode)
  295. if oldnode.name == "fire:nether_flame" then
  296. local p = minetest.find_node_near(pos, 2, "voidstone:void")
  297. if p then
  298. minetest.add_node(p, {name="fire:basic_flame"})
  299. end
  300. elseif oldnode.name == "voidstone:void" then
  301. local void = {
  302. {x=pos.x+1, y=pos.y, z=pos.z },
  303. {x=pos.x-1, y=pos.y, z=pos.z },
  304. {x=pos.x, y=pos.y, z=pos.z+1},
  305. {x=pos.x, y=pos.y, z=pos.z-1},
  306. {x=pos.x, y=pos.y+1, z=pos.z },
  307. {x=pos.x, y=pos.y-1, z=pos.z },
  308. }
  309. for k, v in pairs(void) do
  310. local n = minetest.get_node(v).name
  311. if n == "voidstone:void" then
  312. minetest.add_node(v, {name="fire:basic_flame"})
  313. end
  314. end
  315. elseif oldnode.name == "default:obsidian" then
  316. local fp = {x=pos.x, y=pos.y+1, z=pos.z}
  317. if minetest.get_node(fp).name == "fire:nether_flame" then
  318. minetest.remove_node(fp)
  319. end
  320. local void = {
  321. {x=pos.x+1, y=pos.y, z=pos.z },
  322. {x=pos.x-1, y=pos.y, z=pos.z },
  323. {x=pos.x, y=pos.y, z=pos.z+1},
  324. {x=pos.x, y=pos.y, z=pos.z-1},
  325. }
  326. for k, v in pairs(void) do
  327. local n = minetest.get_node(v).name
  328. if n == "voidstone:void" then
  329. minetest.add_node(v, {name="fire:basic_flame"})
  330. end
  331. end
  332. end
  333. end
  334. if not flameportal.run_once then
  335. flameportal.modstorage = minetest.get_mod_storage()
  336. -- Special node which the player can land on safely when dropped into the nether.
  337. minetest.register_node("flameportal:redrack", {
  338. description = "Netherack",
  339. tiles = {"rackstone_redrack.png"},
  340. groups = utility.dig_groups("netherack", {fall_damage_add_percent=-100}),
  341. sounds = rackstone.rackstone_sounds(),
  342. drop = "rackstone:redrack",
  343. })
  344. minetest.register_alias("flameportal:no_fall_damage", "flameportal:redrack")
  345. minetest.override_item("default:obsidian", {
  346. after_destruct = function(pos, oldnode)
  347. minetest.after(0, ambiance.recheck_nearby_sound_beacons, {x=pos.x, y=pos.y, z=pos.z}, 16)
  348. return flameportal.after_portal_destruct(pos, oldnode)
  349. end,
  350. on_destruct = function(pos, oldnode)
  351. obsidian_gateway.on_damage_gate(pos)
  352. end,
  353. -- Note: default obsidian is blast resistent.
  354. -- 'on_blast' is defined in the default mod.
  355. })
  356. minetest.override_item("voidstone:void", {
  357. after_destruct = function(...) return flameportal.after_portal_destruct(...) end,
  358. on_blast = function(pos) minetest.remove_node(pos) end,
  359. })
  360. minetest.override_item("fire:nether_flame", {
  361. after_destruct = function(pos, oldnode)
  362. flameportal.after_portal_destruct(pos, oldnode)
  363. fireambiance.on_flame_addremove(pos)
  364. particles.del_flame_spawner(pos)
  365. end,
  366. on_blast = function(pos) minetest.remove_node(pos) end,
  367. })
  368. -- Reloadable.
  369. local file = flameportal.modpath .. "/init.lua"
  370. local name = "flameportal:core"
  371. reload.register_file(name, file, false)
  372. flameportal.run_once = true
  373. end