Replace getpos() with get_pos()
[MineClone/MineClone2.git] / mods / ITEMS / mcl_portals / portal_nether.lua
blobd08bddef8d2cd1f9029dddf55c98087596e19bb0
1 -- Parameters
3 local TCAVE = 0.6
4 local nobj_cave = nil
6 -- Portal frame sizes
7 local FRAME_SIZE_X_MIN = 4
8 local FRAME_SIZE_Y_MIN = 5
9 local FRAME_SIZE_X_MAX = 23
10 local FRAME_SIZE_Y_MAX = 23
12 local TELEPORT_DELAY = 3 -- seconds before teleporting in Nether portal
13 local TELEPORT_COOLOFF = 4 -- after object was teleported, for this many seconds it won't teleported again
15 local mg_name = minetest.get_mapgen_setting("mg_name")
17 -- 3D noise
18 local np_cave = {
19 offset = 0,
20 scale = 1,
21 spread = {x = 384, y = 128, z = 384},
22 seed = 59033,
23 octaves = 5,
24 persist = 0.7
27 -- Table of objects (including players) which recently teleported by a
28 -- Nether portal. Those objects have a brief cooloff period before they
29 -- can teleport again. This prevents annoying back-and-forth teleportation.
30 local portal_cooloff = {}
32 -- Destroy portal if pos (portal frame or portal node) got destroyed
33 local destroy_portal = function(pos)
34 -- Deactivate Nether portal
35 local meta = minetest.get_meta(pos)
36 local p1 = minetest.string_to_pos(meta:get_string("portal_frame1"))
37 local p2 = minetest.string_to_pos(meta:get_string("portal_frame2"))
38 if not p1 or not p2 then
39 return
40 end
42 local counter = 1
44 local mp1
45 for x = p1.x, p2.x do
46 for y = p1.y, p2.y do
47 for z = p1.z, p2.z do
48 local p = vector.new(x, y, z)
49 local m = minetest.get_meta(p)
50 if counter == 2 then
51 --[[ Only proceed if the second node still has metadata.
52 (first node is a corner and not needed for the portal)
53 If it doesn't have metadata, another node propably triggred the delection
54 routine earlier, so we bail out earlier to avoid an infinite cascade
55 of on_destroy events. ]]
56 mp1 = minetest.string_to_pos(m:get_string("portal_frame1"))
57 if not mp1 then
58 return
59 end
60 end
61 local nn = minetest.get_node(p).name
62 if nn == "mcl_core:obsidian" or nn == "mcl_portals:portal" then
63 -- Remove portal nodes, but not myself
64 if nn == "mcl_portals:portal" and not vector.equals(p, pos) then
65 minetest.remove_node(p)
66 end
67 -- Clear metadata of portal nodes and the frame
68 m:set_string("portal_frame1", "")
69 m:set_string("portal_frame2", "")
70 m:set_string("portal_target", "")
71 end
72 counter = counter + 1
73 end
74 end
75 end
76 end
78 minetest.register_node("mcl_portals:portal", {
79 description = "Nether Portal",
80 _doc_items_longdesc = "A Nether portal teleports creatures and objects to the hot and dangerous Nether dimension (and back!). Enter at your own risk!",
81 _doc_items_usagehelp = "Stand in the portal for a moment to activate the teleportation. Entering a Nether portal for the first time will also create a new portal in the other dimension. If a Nether portal has been built in the Nether, it will lead to the Overworld. A Nether portal is destroyed if the any of the obsidian which surrounds it is destroyed, or if it was caught in an explosion.",
83 tiles = {
84 "blank.png",
85 "blank.png",
86 "blank.png",
87 "blank.png",
89 name = "mcl_portals_portal.png",
90 animation = {
91 type = "vertical_frames",
92 aspect_w = 16,
93 aspect_h = 16,
94 length = 0.5,
98 name = "mcl_portals_portal.png",
99 animation = {
100 type = "vertical_frames",
101 aspect_w = 16,
102 aspect_h = 16,
103 length = 0.5,
107 drawtype = "nodebox",
108 paramtype = "light",
109 paramtype2 = "facedir",
110 sunlight_propagates = true,
111 use_texture_alpha = true,
112 walkable = false,
113 diggable = false,
114 pointable = false,
115 buildable_to = false,
116 is_ground_content = false,
117 drop = "",
118 light_source = 11,
119 post_effect_color = {a = 180, r = 51, g = 7, b = 89},
120 alpha = 192,
121 node_box = {
122 type = "fixed",
123 fixed = {
124 {-0.5, -0.5, -0.1, 0.5, 0.5, 0.1},
127 groups = {not_in_creative_inventory = 1},
128 on_destruct = destroy_portal,
130 _mcl_hardness = -1,
131 _mcl_blast_resistance = 0,
134 -- Functions
135 --Build arrival portal
136 local function build_portal(pos, target)
137 local p = {x = pos.x - 1, y = pos.y - 1, z = pos.z}
138 local p1 = {x = pos.x - 1, y = pos.y - 1, z = pos.z}
139 local p2 = {x = p1.x + 3, y = p1.y + 4, z = p1.z}
141 for i = 1, FRAME_SIZE_Y_MIN - 1 do
142 minetest.set_node(p, {name = "mcl_core:obsidian"})
143 p.y = p.y + 1
145 for i = 1, FRAME_SIZE_X_MIN - 1 do
146 minetest.set_node(p, {name = "mcl_core:obsidian"})
147 p.x = p.x + 1
149 for i = 1, FRAME_SIZE_Y_MIN - 1 do
150 minetest.set_node(p, {name = "mcl_core:obsidian"})
151 p.y = p.y - 1
153 for i = 1, FRAME_SIZE_X_MIN - 1 do
154 minetest.set_node(p, {name = "mcl_core:obsidian"})
155 p.x = p.x - 1
158 for x = p1.x, p2.x do
159 for y = p1.y, p2.y do
160 p = {x = x, y = y, z = p1.z}
161 if not ((x == p1.x or x == p2.x) and (y == p1.y or y == p2.y)) then
162 if not (x == p1.x or x == p2.x or y == p1.y or y == p2.y) then
163 minetest.set_node(p, {name = "mcl_portals:portal", param2 = 0})
165 local meta = minetest.get_meta(p)
166 meta:set_string("portal_frame1", minetest.pos_to_string(p1))
167 meta:set_string("portal_frame2", minetest.pos_to_string(p2))
168 meta:set_string("portal_target", minetest.pos_to_string(target))
171 if y ~= p1.y then
172 for z = -2, 2 do
173 if z ~= 0 then
174 p.z = p.z + z
175 if minetest.registered_nodes[
176 minetest.get_node(p).name].is_ground_content then
177 minetest.remove_node(p)
179 p.z = p.z - z
185 minetest.log("action", "[mcl_portal] Destination Nether portal generated at "..minetest.pos_to_string(p2).."!")
188 local function find_nether_target_y(target_x, target_z)
189 if mg_name == "flat" then
190 return mcl_vars.mg_bedrock_nether_bottom_max + 5
192 local start_y = math.random(mcl_vars.mg_lava_nether_max + 1, mcl_vars.mg_bedrock_nether_top_min - 5) -- Search start
193 if not nobj_cave then
194 nobj_cave = minetest.get_perlin(np_cave)
196 local air = 4
198 for y = start_y, math.max(mcl_vars.mg_lava_nether_max + 1), -1 do
199 local nval_cave = nobj_cave:get3d({x = target_x, y = y, z = target_z})
201 if nval_cave > TCAVE then -- Cavern
202 air = air + 1
203 else -- Not cavern, check if 4 nodes of space above
204 if air >= 4 then
205 return y + 2
206 else -- Not enough space, reset air to zero
207 air = 0
212 return start_y -- Fallback
215 local function move_check(p1, max, dir)
216 local p = {x = p1.x, y = p1.y, z = p1.z}
217 local d = math.sign(max - p1[dir])
218 local min = p[dir]
220 for k = min, max, d do
221 p[dir] = k
222 local node = minetest.get_node(p)
223 -- Check for obsidian (except at corners)
224 if k ~= min and k ~= max and node.name ~= "mcl_core:obsidian" then
225 return false
227 -- Abort if any of the portal frame blocks already has metadata.
228 -- This mod does not yet portals which neighbor each other directly.
229 -- TODO: Reorganize the way how portal frame coordinates are stored.
230 if node.name == "mcl_core:obsidian" then
231 local meta = minetest.get_meta(p)
232 local pframe1 = meta:get_string("portal_frame1")
233 if minetest.string_to_pos(pframe1) ~= nil then
234 return false
239 return true
242 local function check_portal(p1, p2)
243 if p1.x ~= p2.x then
244 if not move_check(p1, p2.x, "x") then
245 return false
247 if not move_check(p2, p1.x, "x") then
248 return false
250 elseif p1.z ~= p2.z then
251 if not move_check(p1, p2.z, "z") then
252 return false
254 if not move_check(p2, p1.z, "z") then
255 return false
257 else
258 return false
261 if not move_check(p1, p2.y, "y") then
262 return false
264 if not move_check(p2, p1.y, "y") then
265 return false
268 return true
271 local function is_portal(pos)
272 local xsize, ysize = FRAME_SIZE_X_MIN-1, FRAME_SIZE_Y_MIN-1
273 for d = -xsize, xsize do
274 for y = -ysize, ysize do
275 local px = {x = pos.x + d, y = pos.y + y, z = pos.z}
276 local pz = {x = pos.x, y = pos.y + y, z = pos.z + d}
278 if check_portal(px, {x = px.x + xsize, y = px.y + ysize, z = px.z}) then
279 return px, {x = px.x + xsize, y = px.y + ysize, z = px.z}
281 if check_portal(pz, {x = pz.x, y = pz.y + ysize, z = pz.z + xsize}) then
282 return pz, {x = pz.x, y = pz.y + ysize, z = pz.z + xsize}
288 -- Attempts to light a Nether portal at pos and
289 -- select target position.
290 -- Pos can be any of the obsidian frame blocks or the inner part.
291 -- The frame MUST be filled only with air or any fire, which will be replaced with Nether portal blocks.
292 -- If no Nether portal can be lit, nothing happens.
293 -- Returns true on success and false on failure.
294 function mcl_portals.light_nether_portal(pos)
295 -- Only allow to make portals in Overworld and Nether
296 local dim = mcl_worlds.pos_to_dimension(pos)
297 if dim ~= "overworld" and dim ~= "nether" then
298 return false
300 -- Create Nether portal nodes
301 local p1, p2 = is_portal(pos)
302 if not p1 or not p2 then
303 return false
306 for d = 1, 2 do
307 for y = p1.y + 1, p2.y - 1 do
308 local p
309 if p1.z == p2.z then
310 p = {x = p1.x + d, y = y, z = p1.z}
311 else
312 p = {x = p1.x, y = y, z = p1.z + d}
314 local nn = minetest.get_node(p).name
315 if nn ~= "air" and minetest.get_item_group(nn, "fire") ~= 1 then
316 return false
321 local param2
322 if p1.z == p2.z then
323 param2 = 0
324 else
325 param2 = 1
328 -- Find target
330 local target = {x = p1.x, y = p1.y, z = p1.z}
331 target.x = target.x + 1
332 if target.y < mcl_vars.mg_nether_max and target.y > mcl_vars.mg_nether_min then
333 if mg_name == "flat" then
334 target.y = mcl_vars.mg_bedrock_overworld_max + 5
335 else
336 target.y = math.random(mcl_vars.mg_overworld_min + 40, mcl_vars.mg_overworld_min + 96)
338 else
339 target.y = find_nether_target_y(target.x, target.z)
342 local dmin, dmax, ymin, ymax = 0, FRAME_SIZE_X_MIN - 1, p1.y, p2.y
343 for d = dmin, dmax do
344 for y = ymin, ymax do
345 if not ((d == dmin or d == dmax) and (y == ymin or y == ymax)) then
346 local p
347 if param2 == 0 then
348 p = {x = p1.x + d, y = y, z = p1.z}
349 else
350 p = {x = p1.x, y = y, z = p1.z + d}
352 if d ~= dmin and d ~= dmax and y ~= ymin and y ~= ymax then
353 minetest.set_node(p, {name = "mcl_portals:portal", param2 = param2})
355 local meta = minetest.get_meta(p)
357 -- Portal frame corners
358 meta:set_string("portal_frame1", minetest.pos_to_string(p1))
359 meta:set_string("portal_frame2", minetest.pos_to_string(p2))
361 -- Portal target coordinates
362 meta:set_string("portal_target", minetest.pos_to_string(target))
367 return true
371 minetest.register_abm({
372 label = "Nether portal teleportation and particles",
373 nodenames = {"mcl_portals:portal"},
374 interval = 1,
375 chance = 2,
376 action = function(pos, node)
377 minetest.add_particlespawner({
378 amount = 32,
379 time = 4,
380 minpos = {x = pos.x - 0.25, y = pos.y - 0.25, z = pos.z - 0.25},
381 maxpos = {x = pos.x + 0.25, y = pos.y + 0.25, z = pos.z + 0.25},
382 minvel = {x = -0.8, y = -0.8, z = -0.8},
383 maxvel = {x = 0.8, y = 0.8, z = 0.8},
384 minacc = {x = 0, y = 0, z = 0},
385 maxacc = {x = 0, y = 0, z = 0},
386 minexptime = 0.5,
387 maxexptime = 1,
388 minsize = 1,
389 maxsize = 2,
390 collisiondetection = false,
391 texture = "mcl_particles_teleport.png",
393 for _,obj in ipairs(minetest.get_objects_inside_radius(pos,1)) do --maikerumine added for objects to travel
394 local lua_entity = obj:get_luaentity() --maikerumine added for objects to travel
395 if obj:is_player() or lua_entity then
396 -- Prevent quick back-and-forth teleportation
397 if portal_cooloff[obj] then
398 return
400 local meta = minetest.get_meta(pos)
401 local target = minetest.string_to_pos(meta:get_string("portal_target"))
402 if target then
403 -- force emerge of target area
404 minetest.get_voxel_manip():read_from_map(target, target)
405 if not minetest.get_node_or_nil(target) then
406 minetest.emerge_area(vector.subtract(target, 4), vector.add(target, 4))
409 -- teleport function
410 local teleport = function(obj, pos, target)
411 if (not obj:get_luaentity()) and (not obj:is_player()) then
412 return
414 -- Prevent quick back-and-forth teleportation
415 if portal_cooloff[obj] then
416 return
418 local objpos = obj:get_pos()
419 if objpos == nil then
420 return
422 -- If player stands, player is at ca. something+0.5
423 -- which might cause precision problems, so we used ceil.
424 objpos.y = math.ceil(objpos.y)
426 if minetest.get_node(objpos).name ~= "mcl_portals:portal" then
427 return
430 -- Teleport
431 obj:set_pos(target)
432 if obj:is_player() then
433 mcl_worlds.dimension_change(obj, mcl_worlds.pos_to_dimension(target))
434 minetest.sound_play("mcl_portals_teleport", {pos=target, gain=0.5, max_hear_distance = 16})
437 -- Enable teleportation cooloff for some seconds, to prevent back-and-forth teleportation
438 portal_cooloff[obj] = true
439 minetest.after(TELEPORT_COOLOFF, function(o)
440 portal_cooloff[o] = false
441 end, obj)
442 if obj:is_player() then
443 local name = obj:get_player_name()
444 minetest.log("action", "[mcl_portal] "..name.." teleported to Nether portal at "..minetest.pos_to_string(target)..".")
448 local n = minetest.get_node_or_nil(target)
449 if n and n.name ~= "mcl_portals:portal" then
450 -- Emerge target area, wait for emerging to be finished, build destination portal
451 -- (if there isn't already one, teleport object after a short delay.
452 local emerge_callback = function(blockpos, action, calls_remaining, param)
453 minetest.log("verbose", "[mcl_portal] emerge_callack called! action="..action)
454 if calls_remaining <= 0 and action ~= minetest.EMERGE_CANCELLED and action ~= minetest.EMERGE_ERRORED then
455 minetest.log("verbose", "[mcl_portal] Area for destination Nether portal emerged!")
456 build_portal(param.target, param.pos, false)
457 minetest.after(TELEPORT_DELAY, teleport, obj, pos, target)
460 minetest.log("verbose", "[mcl_portal] Emerging area for destination Nether portal ...")
461 minetest.emerge_area(vector.subtract(target, 7), vector.add(target, 7), emerge_callback, { pos = pos, target = target })
462 else
463 minetest.after(TELEPORT_DELAY, teleport, obj, pos, target)
469 end,
473 --[[ ITEM OVERRIDES ]]
475 local longdesc = minetest.registered_nodes["mcl_core:obsidian"]._doc_items_longdesc
476 longdesc = longdesc .. "\n" .. "Obsidian is also used as the frame of Nether portals."
477 local usagehelp = "To open a Nether portal, place an upright frame of obsidian with a width of 4 blocks and a height of 5 blocks, leaving only air in the center. After placing this frame, light a fire in the obsidian frame. Nether portals only work in the Overworld and the Nether."
479 minetest.override_item("mcl_core:obsidian", {
480 _doc_items_longdesc = longdesc,
481 _doc_items_usagehelp = usagehelp,
482 on_destruct = destroy_portal,
483 _on_ignite = function(user, pointed_thing)
484 local pos = pointed_thing.under
485 local portal_placed = mcl_portals.light_nether_portal(pos)
486 if portal_placed then
487 minetest.log("action", "[mcl_portal] Nether portal activated at "..minetest.pos_to_string(pos)..".")
489 if portal_placed and minetest.get_modpath("doc") then
490 doc.mark_entry_as_revealed(user:get_player_name(), "nodes", "mcl_portals:portal")
492 -- Achievement for finishing a Nether portal TO the Nether
493 local dim = mcl_worlds.pos_to_dimension(pos)
494 if minetest.get_modpath("awards") and dim ~= "nether" and user:is_player() then
495 awards.unlock(user:get_player_name(), "mcl:buildNetherPortal")
497 return true
498 else
499 return false
501 end,