pax_global_header00006660000000000000000000000064146134774320014525gustar00rootroot0000000000000052 comment=a6d1f55f0a4983a3616e7ecf5a6b972dd23fae40 nether-3.6/000077500000000000000000000000001461347743200126625ustar00rootroot00000000000000nether-3.6/.luacheckrc000066400000000000000000000010011461347743200147570ustar00rootroot00000000000000unused_args = false allow_defined_top = true globals = { "bucket", "nether" } read_globals = { "climate_api", "core", "default", "DIR_DELIM", "dump", "dungeon_loot", "intllib", "ItemStack", "loot", "doc", "math.hypot", "mesecon", "minetest", "nodeupdate", "PcgRandom", "PseudoRandom", "stairs", "stairsplus", "string.split", table = { fields = { "copy", "getn" } }, "technic", "toolranks", "vector", "VoxelArea", "VoxelManip", "walls", xpanes = { fields = { "register_pane" } }, } nether-3.6/README.md000066400000000000000000000113001461347743200141340ustar00rootroot00000000000000# Nether Mod for Minetest, with Portals API. Allows Nether portals to be constructed, opening a gateway between the surface realm and one of lava and netherrack, with rumors of a passageway through the netherrack to a great magma ocean. To view the options provided by this mod, see settingtypes.txt or go to "Settings"->"All Settings"->"Mods"->"nether" in the game. A Nether portal is built as a rectangular vertical frame of obsidian, 4 blocks wide and 5 blocks high. Once constructed, a Mese crystal fragment can be right-click/used on the frame to activate it. ## Modders and game designers See portal_api.txt for how to create custom portals to your own realms. Nether Portals can allow surface fast-travel. This mod provides Nether basalts (natural, hewn, and chiseled) as nodes which require a player to journey to the magma ocean to obtain, so these can be used for gating progression through a game. For example, a portal to another realm might need to be constructed from basalt, thus requiring a journey through the nether first, or basalt might be a crafting ingredient required to reach a particular branch of the tech-tree. Netherbrick tools are provided (pick, shovel, axe, & sword), see tools.lua The Nether pickaxe has a 10x bonus against wear when mining netherrack. ## License of source code: Copyright (C) 2013 PilzAdam Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ## License and attribution of media (textures and sounds) ### [Public Domain Dedication (CC0 1.0)](https://creativecommons.org/publicdomain/zero/1.0/) * `nether_portal_teleport.ogg` is a timing adjusted version of "teleport" by [outroelison](https://freesound.org/people/outroelison), used under CC0 1.0 * `nether_rack_destroy.ogg` is from "Rock destroy" by [Bertsz](https://freesound.org/people/Bertsz/), used under CC0 1.0 ### [Attribution 3.0 Unported (CC BY 3.0)](https://creativecommons.org/licenses/by/3.0/) * `nether_lightstaff.ogg` is "Fire Burst" by [SilverIllusionist](https://freesound.org/people/SilverIllusionist/), 2019 * `nether_portal_ambient.ogg` & `nether_portal_ambient.0.ogg` are extractions from "Deep Cinematic Rumble Stereo" by [Patrick Lieberkind](http://www.lieberkindvisuals.dk), used under CC BY 3.0 * `nether_portal_extinguish.ogg` is an extraction from "Tight Laser Weapon Hit Scifi" by [damjancd](https://freesound.org/people/damjancd), used under CC BY 3.0 * `nether_portal_ignite.ogg` is a derivative of "Flame Ignition" by [hykenfreak](https://freesound.org/people/hykenfreak), used under CC BY 3.0. "Nether Portal ignite" is licensed under CC BY 3.0 by Treer. ### [Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)](https://creativecommons.org/licenses/by-sa/4.0/) * `nether_basalt`* (files starting with "nether_basalt"): Treer, 2020 * `nether_book_`* (files starting with "nether_book"): Treer, 2019-2020 * `nether_brick_deep.png`: Treer, 2021 * `nether_fumarole.ogg`: Treer, 2020 * `nether_geode.png`: Treer, 2021 * `nether_geode_glass.png`: Treer, 2021 * `nether_lava_bubble`* (files starting with "nether_lava_bubble"): Treer, 2020 * `nether_lava_crust_animated.png`: Treer, 2019-2020 * `nether_lightstaff.png`: Treer, 2021 * `nether_particle_anim`* (files starting with "nether_particle_anim"): Treer, 2019 * `nether_portal_ignition_failure.ogg`: Treer, 2019 * `nether_smoke_puff.png`: Treer, 2020 ### [Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)](http://creativecommons.org/licenses/by-sa/3.0/) * `nether_glowstone`* (files starting with "nether_glowstone"): BlockMen * `nether_nether_ingot.png` & `nether_nether_lump.png`: color adjusted versions from "[default](https://github.com/minetest/minetest_game/tree/master/mods/default)" mod, originally by Gambit * `nether_portal.png`: [Extex101](https://github.com/Extex101), 2020 * `nether_rack`* (files starting with "nether_rack"): Zeg9 * `nether_tool_`* (files starting with "nether_tool_"): color adjusted versions from "[default](https://github.com/minetest/minetest_game/tree/master/mods/default)" mod, originals by BlockMen All other media: Copyright © 2013 PilzAdam, licensed under CC BY-SA 3.0 by PilzAdam. nether-3.6/crafts.lua000066400000000000000000000037261461347743200146570ustar00rootroot00000000000000--[[ Copyright (C) 2013 PilzAdam Copyright (C) 2020 lortas Copyright (C) 2020 Treer Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ]]-- minetest.register_craft({ output = "nether:brick 4", recipe = { {"nether:rack", "nether:rack"}, {"nether:rack", "nether:rack"}, } }) minetest.register_craft({ output = "nether:fence_nether_brick 6", recipe = { {"nether:brick", "nether:brick", "nether:brick"}, {"nether:brick", "nether:brick", "nether:brick"}, }, }) minetest.register_craft({ output = "nether:brick_compressed", recipe = { {"nether:brick","nether:brick","nether:brick"}, {"nether:brick","nether:brick","nether:brick"}, {"nether:brick","nether:brick","nether:brick"}, } }) minetest.register_craft({ output = "nether:brick_deep 4", recipe = { {"nether:rack_deep", "nether:rack_deep"}, {"nether:rack_deep", "nether:rack_deep"} } }) minetest.register_craft({ output = "nether:basalt_hewn", type = "shapeless", recipe = { "nether:basalt", "nether:basalt", }, }) minetest.register_craft({ output = "nether:basalt_chiselled 4", recipe = { {"nether:basalt_hewn", "nether:basalt_hewn"}, {"nether:basalt_hewn", "nether:basalt_hewn"} } }) -- See tools.lua for tools related crafting nether-3.6/depends.txt000066400000000000000000000001371461347743200150460ustar00rootroot00000000000000default stairs doc_basics? dungeon_loot? fire? loot? mesecons? moreblocks? climate_api? xpanes?nether-3.6/description.txt000066400000000000000000000001361461347743200157460ustar00rootroot00000000000000Adds a deep underground realm with different mapgen that you can reach with obsidian portals. nether-3.6/init.lua000066400000000000000000000350501461347743200143330ustar00rootroot00000000000000--[[ Nether mod for minetest Copyright (C) 2013 PilzAdam Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ]]-- -- Set DEBUG_FLAGS to determine the behavior of nether.debug(): -- 0 = off -- 1 = print(...) -- 2 = minetest.chat_send_all(...) -- 4 = minetest.log("info", ...) local DEBUG_FLAGS = 0 local S if minetest.get_translator ~= nil then S = minetest.get_translator("nether") else -- mock the translator function for MT 0.4 S = function(str, ...) local args={...} return str:gsub( "@%d+", function(match) return args[tonumber(match:sub(2))] end ) end end -- Global Nether namespace nether = {} nether.mapgen = {} -- Shared Nether mapgen namespace, for mapgen files to expose functions and constants nether.modname = minetest.get_current_modname() nether.path = minetest.get_modpath(nether.modname) nether.get_translator = S -- nether.useBiomes allows other mods to know whether they can register ores etc. in the Nether. -- See mapgen.lua for an explanation of why minetest.read_schematic is being checked nether.useBiomes = minetest.get_mapgen_setting("mg_name") ~= "v6" and minetest.read_schematic ~= nil nether.fogColor = { -- only used if climate_api is installed netherCaverns = "#1D0504", -- Distance-fog colour for classic nether mantle = "#070916", -- Distance-fog colour for the Mantle region geodes = "#300530" -- Distance-fog colour for secondary region } -- Settings nether.DEPTH_CEILING = -5000 -- The y location of the Nether's celing nether.DEPTH_FLOOR = -11000 -- The y location of the Nether's floor nether.FASTTRAVEL_FACTOR = 8 -- 10 could be better value for Minetest, since there's no sprint, but ex-Minecraft players will be mathing for 8 nether.PORTAL_BOOK_LOOT_WEIGHTING = 0.9 -- Likelyhood of finding the Book of Portals (guide) in dungeon chests. Set to 0 to disable. nether.NETHER_REALM_ENABLED = true -- Setting to false disables the Nether and Nether portal -- Override default settings with values from the .conf file, if any are present. nether.FASTTRAVEL_FACTOR = tonumber(minetest.settings:get("nether_fasttravel_factor") or nether.FASTTRAVEL_FACTOR) nether.PORTAL_BOOK_LOOT_WEIGHTING = tonumber(minetest.settings:get("nether_portalBook_loot_weighting") or nether.PORTAL_BOOK_LOOT_WEIGHTING) nether.NETHER_REALM_ENABLED = minetest.settings:get_bool("nether_realm_enabled", nether.NETHER_REALM_ENABLED) nether.DEPTH_CEILING = tonumber(minetest.settings:get("nether_depth_ymax") or nether.DEPTH_CEILING) nether.DEPTH_FLOOR = tonumber(minetest.settings:get("nether_depth_ymin") or nether.DEPTH_FLOOR) if nether.DEPTH_FLOOR + 1000 > nether.DEPTH_CEILING then error("The lower limit of the Nether must be set at least 1000 lower than the upper limit, and more than 3000 is recommended. Set settingtypes.txt, or 'All Settings' -> 'Mods' -> 'nether' -> 'Nether depth'", 0) end nether.DEPTH = nether.DEPTH_CEILING -- Deprecated, use nether.DEPTH_CEILING instead. -- DEPTH_FLOOR_LAYERS gives the bottom Y of all locations that wish to be -- considered part of the Nether. -- DEPTH_FLOOR_LAYERS Allows mods to insert extra layers below the -- Nether, by knowing where their layer ceiling should start, and letting -- the layers be included in effects which only happen in the Nether. -- If a mod wishes to add a layer below the Nether it should read -- nether.DEPTH_FLOOR_LAYERS to find the bottom Y of the Nether and any -- other layers already under the Nether. The mod should leave a small gap -- between DEPTH_FLOOR_LAYERS and its ceiling (e.g. use DEPTH_FLOOR_LAYERS - 6 -- for its ceiling Y, so there is room to shift edge-case biomes), then set -- nether.DEPTH_FLOOR_LAYERS to reflect the mod's floor Y value, and call -- shift_existing_biomes() with DEPTH_FLOOR_LAYERS as the floor_y argument. nether.DEPTH_FLOOR_LAYERS = nether.DEPTH_FLOOR -- A debug-print function that understands vectors etc. and does not -- evaluate when debugging is turned off. -- Works like string.format(), treating the message as a format string. -- nils, tables, and vectors passed as arguments to nether.debug() are -- converted to strings and can be included inside the message with %s function nether.debug(message, ...) local args = {...} local argCount = select("#", ...) for i = 1, argCount do local arg = args[i] if arg == nil then -- convert nils to strings args[i] = '' elseif type(arg) == "table" then local tableCount = 0 for _,_ in pairs(arg) do tableCount = tableCount + 1 end if tableCount == 3 and arg.x ~= nil and arg.y ~= nil and arg.z ~= nil then -- convert vectors to strings args[i] = minetest.pos_to_string(arg) else -- convert tables to strings -- (calling function can use dump() if a multi-line listing is desired) args[i] = string.gsub(dump(arg, ""), "\n", " ") end end end local composed_message = "nether: " .. string.format(message, unpack(args)) if math.floor(DEBUG_FLAGS / 1) % 2 == 1 then print(composed_message) end if math.floor(DEBUG_FLAGS / 2) % 2 == 1 then minetest.chat_send_all(composed_message) end if math.floor(DEBUG_FLAGS / 4) % 2 == 1 then minetest.log("info", composed_message) end end if DEBUG_FLAGS == 0 then -- do as little evaluation as possible nether.debug = function() end end -- Load files dofile(nether.path .. "/portal_api.lua") dofile(nether.path .. "/nodes.lua") dofile(nether.path .. "/tools.lua") dofile(nether.path .. "/crafts.lua") if nether.NETHER_REALM_ENABLED then if nether.useBiomes then dofile(nether.path .. "/mapgen.lua") else dofile(nether.path .. "/mapgen_nobiomes.lua") end end dofile(nether.path .. "/portal_examples.lua") if minetest.get_modpath("technic") then dofile(nether.path .. "/nether-compressor-recipe.lua") end -- Portals are ignited by right-clicking with a mese crystal fragment nether.register_portal_ignition_item( "default:mese_crystal_fragment", {name = "nether_portal_ignition_failure", gain = 0.3} ) if nether.NETHER_REALM_ENABLED then -- Use the Portal API to add a portal type which goes to the Nether -- See portal_api.txt for documentation nether.register_portal("nether_portal", { shape = nether.PortalShape_Traditional, frame_node_name = "default:obsidian", wormhole_node_color = 0, -- 0 is magenta title = S("Nether Portal"), book_of_portals_pagetext = S([[Construction requires 14 blocks of obsidian, which we found deep underground where water had solidified molten rock. The finished frame is four blocks wide, five blocks high, and stands vertically, like a doorway. This opens to a truly hellish place, though for small mercies the air there is still breathable. There is an intriguing dimensional mismatch happening between this realm and ours, as after opening the second portal into it we observed that 10 strides taken in the Nether appear to be an equivalent of @1 in the natural world. The expedition parties have found no diamonds or gold, and after an experienced search party failed to return from the trail of a missing expedition party, I must conclude this is a dangerous place.]], 10 * nether.FASTTRAVEL_FACTOR), is_within_realm = function(pos) -- return true if pos is inside the Nether return pos.y < nether.DEPTH_CEILING and pos.y > nether.DEPTH_FLOOR end, find_realm_anchorPos = function(surface_anchorPos, player_name) -- divide x and z by a factor of 8 to implement Nether fast-travel local destination_pos = vector.divide(surface_anchorPos, nether.FASTTRAVEL_FACTOR) destination_pos.x = math.floor(0.5 + destination_pos.x) -- round to int destination_pos.z = math.floor(0.5 + destination_pos.z) -- round to int destination_pos.y = nether.DEPTH_CEILING - 1 -- temp value so find_nearest_working_portal() returns nether portals -- a y_factor of 0 makes the search ignore the altitude of the portals (as long as they are in the Nether) local existing_portal_location, existing_portal_orientation = nether.find_nearest_working_portal("nether_portal", destination_pos, 8, 0) if existing_portal_location ~= nil then return existing_portal_location, existing_portal_orientation else local start_y = nether.DEPTH_CEILING - math.random(500, 1500) -- Search starting altitude destination_pos.y = nether.find_nether_ground_y(destination_pos.x, destination_pos.z, start_y, player_name) return destination_pos end end, find_surface_anchorPos = function(realm_anchorPos, player_name) -- A portal definition doesn't normally need to provide a find_surface_anchorPos() function, -- since find_surface_target_y() will be used by default, but Nether portals also scale position -- to create fast-travel. -- Defining a custom function also means we can look for existing nearby portals. -- Multiply x and z by a factor of 8 to implement Nether fast-travel local destination_pos = vector.multiply(realm_anchorPos, nether.FASTTRAVEL_FACTOR) destination_pos.x = math.min(30900, math.max(-30900, destination_pos.x)) -- clip to world boundary destination_pos.z = math.min(30900, math.max(-30900, destination_pos.z)) -- clip to world boundary destination_pos.y = nether.DEPTH_CEILING + 1 -- temp value so find_nearest_working_portal() doesn't return nether portals -- a y_factor of 0 makes the search ignore the altitude of the portals (as long as they are outside the Nether) local existing_portal_location, existing_portal_orientation = nether.find_nearest_working_portal("nether_portal", destination_pos, 8 * nether.FASTTRAVEL_FACTOR, 0) if existing_portal_location ~= nil then return existing_portal_location, existing_portal_orientation else destination_pos.y = nether.find_surface_target_y(destination_pos.x, destination_pos.z, "nether_portal", player_name) return destination_pos end end, on_ignite = function(portalDef, anchorPos, orientation) -- make some sparks fly local p1, p2 = portalDef.shape:get_p1_and_p2_from_anchorPos(anchorPos, orientation) local pos = vector.divide(vector.add(p1, p2), 2) local textureName = portalDef.particle_texture if type(textureName) == "table" then textureName = textureName.name end minetest.add_particlespawner({ amount = 110, time = 0.1, minpos = {x = pos.x - 0.5, y = pos.y - 1.2, z = pos.z - 0.5}, maxpos = {x = pos.x + 0.5, y = pos.y + 1.2, z = pos.z + 0.5}, minvel = {x = -5, y = -1, z = -5}, maxvel = {x = 5, y = 1, z = 5}, minacc = {x = 0, y = 0, z = 0}, maxacc = {x = 0, y = 0, z = 0}, minexptime = 0.1, maxexptime = 0.5, minsize = 0.2 * portalDef.particle_texture_scale, maxsize = 0.8 * portalDef.particle_texture_scale, collisiondetection = false, texture = textureName .. "^[colorize:#F4F:alpha", animation = portalDef.particle_texture_animation, glow = 8 }) end }) -- Set appropriate nether distance-fog if climate_api is available -- -- Delegating to a mod like climate_api means nether won't unexpectedly stomp on the sky of -- any other mod. -- Skylayer is another mod which can perform this role, and skylayer support could be added -- here as well. However skylayer doesn't provide a position-based method of specifying sky -- colours out-of-the-box, so the nether mod will have to monitor when players enter and -- leave the nether. if minetest.get_modpath("climate_api") and minetest.global_exists("climate_api") and climate_api.register_weather ~= nil then climate_api.register_influence( "nether_biome", function(pos) local result = "surface" if pos.y <= nether.DEPTH_CEILING and pos.y >= nether.DEPTH_FLOOR then result = "nether" -- since mapgen_nobiomes.lua has no regions it doesn't implement get_region(), -- so only use get_region() if it exists if nether.mapgen.get_region ~= nil then -- the biomes-based mapgen supports 2 extra regions local regions = nether.mapgen.RegionEnum local region = nether.mapgen.get_region(pos) if region == regions.CENTER or region == regions.CENTERSHELL then result = "mantle" elseif region == regions.NEGATIVE or region == regions.NEGATIVESHELL then result = "geode" end end end return result end ) -- using sky type "plain" unfortunately means we don't get smooth fading transitions when -- the color of the sky changes, but it seems to be the only way to obtain a sky colour -- which doesn't brighten during the daytime. local undergroundSky = { sky_data = { base_color = nil, type = "plain", textures = nil, clouds = false, }, sun_data = { visible = false, sunrise_visible = false }, moon_data = { visible = false }, star_data = { visible = false } } local netherSky, mantleSky, geodeSky = table.copy(undergroundSky), table.copy(undergroundSky), table.copy(undergroundSky) netherSky.sky_data.base_color = nether.fogColor.netherCaverns mantleSky.sky_data.base_color = nether.fogColor.mantle geodeSky.sky_data.base_color = nether.fogColor.geodes climate_api.register_weather( "nether:caverns", { nether_biome = "nether" }, { ["climate_api:skybox"] = netherSky } ) climate_api.register_weather( "nether:mantle", { nether_biome = "mantle" }, { ["climate_api:skybox"] = mantleSky } ) climate_api.register_weather( "nether:geode", { nether_biome = "geode" }, { ["climate_api:skybox"] = geodeSky } ) end end -- end of "if nether.NETHER_REALM_ENABLED..." -- Play bubbling lava sounds if player killed by lava minetest.register_on_dieplayer( function(player, reason) if reason.node ~= nil and minetest.get_node_group(reason.node, "lava") > 0 or reason.node == "nether:lava_crust" then minetest.sound_play( "nether_lava_bubble", -- this sample was encoded at 3x speed to reduce .ogg file size -- at the expense of higher frequencies, so pitch it down ~3x {to_player = player:get_player_name(), pitch = 0.3, gain = 0.8} ) end end ) nether-3.6/locale/000077500000000000000000000000001461347743200141215ustar00rootroot00000000000000nether-3.6/locale/nether.fr.tr000066400000000000000000000324301461347743200163650ustar00rootroot00000000000000# textdomain: nether # Translation FR by Louis Royer and JoeEnderman ### init.lua ### Construction requires 14 blocks of obsidian, which we found deep underground where water had solidified molten rock. The finished frame is four blocks wide, five blocks high, and stands vertically, like a doorway.@n@nThis opens to a truly hellish place, though for small mercies the air there is still breathable. There is an intriguing dimensional mismatch happening between this realm and ours, as after opening the second portal into it we observed that 10 strides taken in the Nether appear to be an equivalent of @1 in the natural world.@n@nThe expedition parties have found no diamonds or gold, and after an experienced search party failed to return from the trail of a missing expedition party, I must conclude this is a dangerous place.=Cette construction nécessite 14 blocs d’obsidienne, qui peut être trouvée profondément sous terre, là où l’eau a solidifié de la roche fondue. Une fois terminé, le cadre fait quatre blocs de large, cinq blocs de haut, et se tient verticalement comme une porte.@n@nElle ouvre sur un lieu vraiment infernal, mais on peut s’estimer heureux que l’air y soit quand même respirable. Il y a un décalage dimensionnel intrigant entre ce monde et le nôtre, car après avoir ouvert un deuxième portail, nous avons observé que 10 pas effectués dans le Nether semblent être l’équivalent de @1 dans notre monde.@n@nLes membres de l’expédition n’ontr trouvé ni diamants ni or, et après qu’un groupe de recheche expérimenté n’ait pas réussi à retrouver la piste d’un membre de l’expédition disparu, je n’ai d’autre choix que de conclure que cet endroit est trop dangereux pour nous. ### init.lua ### ### nodes.lua ### Nether Portal=Portail du Nether ### mapgen_mantle.lua ### , @1m above lava-sea level=, @1m au-dessus du niveau de la mer de lave , @1m below lava-sea level=, @1m en-dessous du niveau de la mer de lave , approaching y boundary of Nether=, approchant de la limite y du Nether @1@2@3@4= Center/Mantle, but outside the caverns=Centre/Manteau, mais à l'extérieur des cavernes Center/Mantle, inside cavern=Centre/Manteau, à l'intérieur d'une caverne Describes which region of the nether the player is in=Indique dans quelle région du Nether se trouve le joueur Negative nether=Nether négatif Positive nether=Nether positif Shell between negative nether and center region=Coquille entre le Nether négatif et la région centrale Shell between positive nether and center region=Coquille entre le Nether positif et la région centrale The Overworld=L'Overworld Unknown player position=Position du joueur inconnue [Perlin @1] = ### nodes.lua ### A Deep Netherrack Wall=Un mur profond en Netherrack A Netherrack Wall=Un mur en Netherrack A finely finished block of solid Nether Basalt.=Un bloc fini de basalte du Nether solide. A rough cut solid block of Nether Basalt.=Un bloc solide de basalte du Nether taillé à la hache. A thin crust of cooled lava with liquid lava beneath=Une croûte fine de lave refroidie avec de la lave liquide en dessous A vent in the earth emitting steam and gas=Une fissure dans la terre émettant de la vapeur et du gaz Can be repurposed to provide puffs of smoke in a chimney=Peut être réutilisé pour produire des bouffées de fumée dans une cheminée Chiselled Basalt=Basalte sculpté Columns of dark basalt found only in magma oceans deep within the Nether.=Colonnes de basalte noir que l'on trouve uniquement dans les océans de magma profonds du Nether. Compressed Netherbrick=Briques du Nether compressées Cracked Nether Brick=Briques du Nether craquelées Deep Glowstone=Pierre lumineuse profonde Deep Nether Brick=Briques du Nether profondes Deep Nether Slab=Dalle du Nether profonde Deep Nether Stair=Escalier du Nether profond Deep Netherrack=Netherrack profond Deep Netherrack Slab=Dalle de Netherrack profonde Deep Netherrack Stair=Escalier de Netherrack profond Fumarolic Chimney=Cheminée fumarolique Fumarolic Chimney Corner=Coin de cheminée fumarolique Fumarolic Chimney Slab=Dalle de cheminée fumarolique Glowstone=Pierre lumineuse Hewn Basalt=Basalte taillé Inner Deep Nether Stair=Escalier intérieur du Nether profond Inner Nether Stair=Escalier intérieur du Nether Lava Crust=Croûte de lave Lava crust is strong enough to walk on, but still hot enough to inflict burns.=La croûte de lave est assez solide pour marcher dessus, mais encore assez chaude pour causer des brûlures Nether Basalt=Basalte du Nether Nether Beryl=Béryl du Nether Nether Berylite=Bérylite du Nether Nether Brick=Brique du Nether Nether Brick Fence=Barrière en briques du Nether Nether Brick Fence Rail=Rail de barrière en briques du Nether Nether Crystal Pane=Panneau de cristal du Nether Nether Slab=Dalle du Nether Nether Stair=Escalier du Nether Nether geode crystal, found lining the interior walls of Nether geodes=Cristal de géode du Nether, trouvé le long des parois intérieures des géodes du Nether Nether geode crystal. A crystalline structure with faint glow found inside large Nether geodes=Cristal de géode du Nether. Une structure cristalline avec une faible lueur trouvée à l'intérieur des grandes géodes du Nether. Netherrack=Roche du Nether Netherrack from deep in the mantle=Roche du Nether provenant des profondeurs du manteau Netherrack Slab=Dalle du Nether Netherrack Stair=Escalier du Nether Nethersand=Sable du Nether Outer Deep Nether Stair=Escalier extérieur profond du Nether Outer Nether Stair=Escalier extérieur du Nether Portal=Portail ### portal_api.lua ### @n@nThe key to opening such a doorway is to strike the frame with a @1, at which point the very air inside begins to crackle and glow.=@n@nLa méthode pour ouvrir une telle porte est de frapper son cadre avec un @1, jusqu’à ce que tout l’air à l’intérieur commence à crépiter et briller. A definitive guide to Rifts and Portals=Un guide détaillé des failles et des portails A guidebook for how to build portals to other realms. It can sometimes be found in dungeon chests, however a copy of this book is not needed as its contents are included in this Encyclopedia.=Un guide sur comment construire des portails vers d’autres mondes. Il peut parfois être trouvé dans des coffres de dongeons, cependant la copie de ce livre n’est pas nécessaire puisque son contenu est inclus dans l’encyclopédie. Book of Portals=Livre des portails Building Portals=Construire des portails In all my travels, and time spent in the Great Libraries, I have encountered no shortage of legends surrounding preternatural doorways said to open into other worlds, yet only @1 can I confirm as being more than merely stories.=Après tous mes voyages, et le temps passé dans les Grandes Bibliothèques, je ne manque pas de légendes sur les portes surnaturelles qui, dit-on s’ouvrent vers d’autres mondes, mais seul @1 peut confirmer que ce sont plus que de simples histoires. In all my travels, and time spent in the Great Libraries, I have encountered no shortage of legends surrounding preternatural doorways said to open into other worlds, yet only one can I confirm as being more than merely a story.=Après tous mes voyages, et le temps passé dans les Grandes Bibliothèques, je ne manque pas de légendes sur les portes surnaturelles qui, dit-on s’ouvrent vers d’autres mondes, mais seul une personne peut confirmer que ce sont plus que de simples histoires. Mysterious forces prevented you from opening that portal. Please try another location=Des forces mystérieuses vous empêchent d'ouvrir ce portail. Veuillez essayer un autre emplacement. Portal wormhole=Vortex du portail Portals to other realms can be opened by building a frame in the right shape with the right blocks, then using an item to activate it. A local copy of the guidebook to portals is published below.@n---@n@n=Les portails vers d’autres mondes peuvent être ouvert en construisant un cadre de la bonne forme avec les bons blocs, puis en utilisant un objet pour l’activer. Une copie du guide des portails est ci-dessous.@n---@n@n Refer: "Help" > "Basics" > "Building Portals"=Voir : Aide > Bases > Construire des portails Untitled portal=Portail sans nom We know almost nothing about this portal=Nous ne savons presque rien sur les portails ### portal_examples.lua ### Floatlands Portal=Portail du monde flottant Requiring 16 blocks of tin and constructed in a circular fashion, a finished frame is seven blocks wide, seven blocks high, and stands vertically like a doorway.@n@nThese travel a distance along the ground, and even when constructed deep underground will link back up to the surface. They appear to favor a strange direction, with the exit portal linking back only for as long as the portal stays open — attempting to reopen a portal from the exit doorway leads to a new destination along this favored direction. It has stymied our ability to study the behavior of these portals because without constructing dual portals and keeping both open it's hard to step through more than one and still be able to return home.@n@nDue to such difficulties, we never learned what determines the direction and distance where the matching twin portal will appear, and I have lost my friend and protégé. In cavalier youth and with little more than a rucksack, Coudreau has decided to follow the chain as far as it goes, and has not been seen since. Coudreau believes it works in epicycles, but I am not convinced. Still, I cling to the hope that one day the portal will open and Coudreau will step out from whichever place leads to this one, perhaps with an epic tale to tell.=Nécessite 16 blocs d’étain placés de manière circulaire, le cadre final fait sept blocs de large, sept blocs de haut, et se tient verticalement comme une porte.@n@nIls permettent de voyager une distance sous le sol et se relieent à la surface même s’ils sont construits profondément sous terre. Ils ont l’air de préférer une direction étrange, avec le portail de sortie ne se reliant au portail d’entrée que tant qu’ils restent tous deux ouverts – tenter de réouvrir le portail de sortie mènera à une nouvelle destination dans cette direction privilégiée.Cela a entravé notre capacité à étudier le comportement de ces portails, car sans la construction de doubles portails et en gardant les deux ouverts, il est difficile d'en traverser plus d'un et de pouvoir rentrer chez soi.@n@nEn raison de ces difficultés, nous n'avons jamais appris ce qui détermine la direction et la distance à laquelle le portail jumeau correspondant apparaîtra, et j’ai perdu mon ami et mon protégé. Dans sa jeunesse cavalière et avec à peine plus qu'un sac à dos, Coudreau a décidé de suivre la chaîne jusqu'au bout, et n'a pas été vu depuis. Coudreau croit qu'elle fonctionne sur les épicycles, mais je n'en suis pas convaincu. Je m'accroche néanmoins à l'espoir qu'un jour le portail s'ouvrira et que Coudreau sortira de l'endroit qui mène à celui-ci, peut-être avec un récit épique à raconter. Requiring 21 blocks of ice, and constructed in the shape of a 3 × 3 platform with walls, or like a bowl. A finished platform is 2 blocks high, and 5 blocks wide at the widest in both directions.@n@nThis portal is different to the others, rather than acting akin to a doorway it appears to the eye more like a small pool of water which can be stepped into. Upon setting foot in the portal we found ourselves at a tremendous altitude.@1=Nécessite 21 blocs de glace placés pour former une plateforme de 3 × 3 avec des murs, ou comme un bol. La plateforme finale fait 2 blocs de haut, et 5 blocs de large à sa largeur maximale dans les deux directions.@n@nCe portail est différent des autres, au lieu de ressembler à une porte, il ressemble plus à un petit bassin d’eau dans lequel on peut entrer. En mettant les pieds dans le portail, nous nous sommes retrouvés à une altitude énorme.@1 Surface Portal=Portail de surface There is a floating land of hills and forests up there, over the edges of which is a perilous drop all the way back down to sea level. We have not found how far these pristine lands extend. I have half a mind to retire there one day.=Il y a là un monde flottant remplis de collines et de forêts, sur les bords duquel se trouve une chute périlleuse jusqu'au niveau de la mer. Nous n'avons pas encore trouvé jusqu'où s'étendent ces terres vierges. J'ai à moitié envie de m'y retirer un jour. ### tools.lua ### Nether Axe=Hache du Nether Nether Ingot=Lingot du Nether Nether Lump=Morceau du Nether Nether Pickaxe@nWell suited for mining netherrack=Pioche du Nether@nBien adaptée pour miner la roche du Nether Nether Shovel=Pelle du Nether Nether Sword=Épée du Nether Nether staff of Eternal Light@nCreates glowstone from netherrack=Bâton du Nether de lumière éternelle@nCrée des blocs de pierre lumineuse à partir de roche du Nether Nether staff of Light@nTemporarily transforms the netherrack into glowstone=Bâton du Nether de lumière@nTransforme temporairement la roche du Nether en blocs de pierre lumineuse Uniquely suited for mining netherrack, with minimal wear when doing so. Blunts quickly on other materials.=Convient parfaitement pour miner la roche du Nether avec une usure minimale. S'émousse rapidement sur les autres matériaux. nether-3.6/locale/template.txt000066400000000000000000000144351461347743200165040ustar00rootroot00000000000000# textdomain: nether ### init.lua ### Construction requires 14 blocks of obsidian, which we found deep underground where water had solidified molten rock. The finished frame is four blocks wide, five blocks high, and stands vertically, like a doorway.@n@nThis opens to a truly hellish place, though for small mercies the air there is still breathable. There is an intriguing dimensional mismatch happening between this realm and ours, as after opening the second portal into it we observed that 10 strides taken in the Nether appear to be an equivalent of @1 in the natural world.@n@nThe expedition parties have found no diamonds or gold, and after an experienced search party failed to return from the trail of a missing expedition party, I must conclude this is a dangerous place.= ### init.lua ### ### nodes.lua ### Nether Portal= ### mapgen_mantle.lua ### , @1m above lava-sea level= , @1m below lava-sea level= , approaching y boundary of Nether= @1@2@3@4= Center/Mantle, but outside the caverns= Center/Mantle, inside cavern= Describes which region of the nether the player is in= Negative nether= Positive nether= Shell between negative nether and center region= Shell between positive nether and center region= The Overworld= Unknown player position= [Perlin @1] = ### nodes.lua ### A Deep Netherrack Wall= A Netherrack Wall= A finely finished block of solid Nether Basalt.= A rough cut solid block of Nether Basalt.= A thin crust of cooled lava with liquid lava beneath= A vent in the earth emitting steam and gas= Can be repurposed to provide puffs of smoke in a chimney= Chiselled Basalt= Columns of dark basalt found only in magma oceans deep within the Nether.= Compressed Netherbrick= Cracked Nether Brick= Deep Glowstone= Deep Nether Brick= Deep Nether Slab= Deep Nether Stair= Deep Netherrack= Deep Netherrack Slab= Deep Netherrack Stair= Fumarolic Chimney= Fumarolic Chimney Corner= Fumarolic Chimney Slab= Glowstone= Hewn Basalt= Inner Deep Nether Stair= Inner Nether Stair= Lava Crust= Lava crust is strong enough to walk on, but still hot enough to inflict burns.= Nether Basalt= Nether Beryl= Nether Berylite= Nether Brick= Nether Brick Fence= Nether Brick Fence Rail= Nether Crystal Pane= Nether Slab= Nether Stair= Nether geode crystal, found lining the interior walls of Nether geodes= Nether geode crystal. A crystalline structure with faint glow found inside large Nether geodes= Netherrack= Netherrack from deep in the mantle= Netherrack Slab= Netherrack Stair= Nethersand= Outer Deep Nether Stair= Outer Nether Stair= Portal= ### portal_api.lua ### @n@nThe key to opening such a doorway is to strike the frame with a @1, at which point the very air inside begins to crackle and glow.= A definitive guide to Rifts and Portals= A guidebook for how to build portals to other realms. It can sometimes be found in dungeon chests, however a copy of this book is not needed as its contents are included in this Encyclopedia.= Book of Portals= Building Portals= In all my travels, and time spent in the Great Libraries, I have encountered no shortage of legends surrounding preternatural doorways said to open into other worlds, yet only @1 can I confirm as being more than merely stories.= In all my travels, and time spent in the Great Libraries, I have encountered no shortage of legends surrounding preternatural doorways said to open into other worlds, yet only one can I confirm as being more than merely a story.= Mysterious forces prevented you from opening that portal. Please try another location= Portal wormhole= Portals to other realms can be opened by building a frame in the right shape with the right blocks, then using an item to activate it. A local copy of the guidebook to portals is published below.@n---@n@n= Refer: "Help" > "Basics" > "Building Portals"= Untitled portal= We know almost nothing about this portal= ### portal_examples.lua ### Floatlands Portal= Requiring 16 blocks of tin and constructed in a circular fashion, a finished frame is seven blocks wide, seven blocks high, and stands vertically like a doorway.@n@nThese travel a distance along the ground, and even when constructed deep underground will link back up to the surface. They appear to favor a strange direction, with the exit portal linking back only for as long as the portal stays open — attempting to reopen a portal from the exit doorway leads to a new destination along this favored direction. It has stymied our ability to study the behavior of these portals because without constructing dual portals and keeping both open it's hard to step through more than one and still be able to return home.@n@nDue to such difficulties, we never learned what determines the direction and distance where the matching twin portal will appear, and I have lost my friend and protégé. In cavalier youth and with little more than a rucksack, Coudreau has decided to follow the chain as far as it goes, and has not been seen since. Coudreau believes it works in epicycles, but I am not convinced. Still, I cling to the hope that one day the portal will open and Coudreau will step out from whichever place leads to this one, perhaps with an epic tale to tell.= Requiring 21 blocks of ice, and constructed in the shape of a 3 × 3 platform with walls, or like a bowl. A finished platform is 2 blocks high, and 5 blocks wide at the widest in both directions.@n@nThis portal is different to the others, rather than acting akin to a doorway it appears to the eye more like a small pool of water which can be stepped into. Upon setting foot in the portal we found ourselves at a tremendous altitude.@1= Surface Portal= There is a floating land of hills and forests up there, over the edges of which is a perilous drop all the way back down to sea level. We have not found how far these pristine lands extend. I have half a mind to retire there one day.= ### tools.lua ### Nether Axe= Nether Ingot= Nether Lump= Nether Pickaxe@nWell suited for mining netherrack= Nether Shovel= Nether Sword= Nether staff of Eternal Light@nCreates glowstone from netherrack= Nether staff of Light@nTemporarily transforms the netherrack into glowstone= Uniquely suited for mining netherrack, with minimal wear when doing so. Blunts quickly on other materials.= nether-3.6/mapgen.lua000066400000000000000000000522121461347743200146360ustar00rootroot00000000000000--[[ Nether mod for minetest "mapgen.lua" is the modern biomes-based Nether mapgen, which requires Minetest v5.1 or greater "mapgen_nobiomes.lua" is the legacy version of the mapgen, only used in older versions of Minetest or in v6 worlds. Copyright (C) 2013 PilzAdam Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ]]-- -- Parameters local NETHER_CEILING = nether.DEPTH_CEILING local NETHER_FLOOR = nether.DEPTH_FLOOR local TCAVE = 0.6 local BLEND = 128 -- parameters for central region local REGION_BUFFER_THICKNESS = 0.2 local CENTER_REGION_LIMIT = TCAVE - REGION_BUFFER_THICKNESS -- Netherrack gives way to Deep-Netherrack here local CENTER_CAVERN_LIMIT = CENTER_REGION_LIMIT - 0.1 -- Deep-Netherrack gives way to air here local SURFACE_CRUST_LIMIT = CENTER_CAVERN_LIMIT * 0.25 -- Crusted-lava at the surface of the lava ocean gives way to liquid lava here local CRUST_LIMIT = CENTER_CAVERN_LIMIT * 0.85 -- Crusted-lava under the surface of the lava ocean gives way to liquid lava here local BASALT_COLUMN_UPPER_LIMIT = CENTER_CAVERN_LIMIT * 0.9 -- Basalt columns may appear between these upper and lower limits local BASALT_COLUMN_LOWER_LIMIT = CENTER_CAVERN_LIMIT * 0.25 -- This value is close to SURFACE_CRUST_LIMIT so basalt columns give way to "flowing" lava rivers -- Shared Nether mapgen namespace -- For mapgen files to share functions and constants local mapgen = nether.mapgen mapgen.TCAVE = TCAVE -- const needed in mapgen_mantle.lua mapgen.BLEND = BLEND -- const needed in mapgen_mantle.lua mapgen.CENTER_REGION_LIMIT = CENTER_REGION_LIMIT -- const needed in mapgen_mantle.lua mapgen.CENTER_CAVERN_LIMIT = CENTER_CAVERN_LIMIT -- const needed in mapgen_mantle.lua mapgen.BASALT_COLUMN_UPPER_LIMIT = BASALT_COLUMN_UPPER_LIMIT -- const needed in mapgen_mantle.lua mapgen.BASALT_COLUMN_LOWER_LIMIT = BASALT_COLUMN_LOWER_LIMIT -- const needed in mapgen_mantle.lua mapgen.ore_ceiling = NETHER_CEILING - BLEND -- leave a solid 128 node cap of netherrack before introducing ores mapgen.ore_floor = NETHER_FLOOR + BLEND local debugf = nether.debug if minetest.read_schematic == nil then -- Using biomes to create the Nether requires the ability for biomes to set "node_cave_liquid = air". -- This feature was introduced by paramat in b1b40fef1 on 2019-05-19, but we can't test for -- it directly. However b2065756c was merged a few months later (in 2019-08-14) and it is easy -- to directly test for - it adds minetest.read_schematic() - so we use this as a proxy-test -- for whether the Minetest engine is recent enough to have implemented node_cave_liquid=air error("This " .. nether.modname .. " mapgen requires Minetest v5.1 or greater, use mapgen_nobiomes.lua instead.", 0) end -- Load specialty helper functions dofile(nether.path .. "/mapgen_dungeons.lua") dofile(nether.path .. "/mapgen_mantle.lua") dofile(nether.path .. "/mapgen_geodes.lua") -- Misc math functions -- avoid needing table lookups each time a common math function is invoked local math_max, math_min, math_abs, math_floor = math.max, math.min, math.abs, math.floor -- Inject nether_caverns biome -- Move any existing biomes out of the y-range specified by 'floor_y' and 'ceiling_y' mapgen.shift_existing_biomes = function(floor_y, ceiling_y) -- https://forum.minetest.net/viewtopic.php?p=257522#p257522 -- Q: Is there a way to override an already-registered biome so I can get it out of the -- way of my own underground biomes without disturbing the other biomes registered by -- default? -- A: No, all you can do is use a mod to clear all biomes then re-register the complete -- set but with your changes. It has been described as hacky but this is actually the -- official way to alter biomes, most mods and subgames would want to completely change -- all biomes anyway. -- To avoid the engine side of mapgen becoming overcomplex the approach is to require mods -- to do slightly more complex stuff in Lua. -- take a copy of all biomes, decorations, and ores. Regregistering a biome changes its ID, so -- any decorations or ores using the 'biomes' field must afterwards be cleared and re-registered. -- https://github.com/minetest/minetest/issues/9288 local registered_biomes_copy = {} local registered_decorations_copy = {} local registered_ores_copy = {} for old_biome_key, old_biome_def in pairs(minetest.registered_biomes) do registered_biomes_copy[old_biome_key] = old_biome_def end for old_decoration_key, old_decoration_def in pairs(minetest.registered_decorations) do registered_decorations_copy[old_decoration_key] = old_decoration_def end for old_ore_key, old_ore_def in pairs(minetest.registered_ores) do registered_ores_copy[old_ore_key] = old_ore_def end -- clear biomes, decorations, and ores minetest.clear_registered_decorations() minetest.clear_registered_ores() minetest.clear_registered_biomes() -- Restore biomes, adjusted to not overlap the Nether for biome_key, new_biome_def in pairs(registered_biomes_copy) do -- follow similar min_pos/max_pos processing logic as read_biome_def() in l_mapgen.cpp local biome_y_max, biome_y_min = 31000, -31000 if type(new_biome_def.min_pos) == 'table' and type(new_biome_def.min_pos.y) == 'number' then biome_y_min = new_biome_def.min_pos.y end if type(new_biome_def.max_pos) == 'table' and type(new_biome_def.max_pos.y) == 'number' then biome_y_max = new_biome_def.max_pos.y end if type(new_biome_def.y_min) == 'number' then biome_y_min = new_biome_def.y_min end if type(new_biome_def.y_max) == 'number' then biome_y_max = new_biome_def.y_max end if biome_y_max > floor_y and biome_y_min < ceiling_y then -- This biome occupies some or all of the depth of the Nether, shift/crop it. local new_y_min, new_y_max local spaceOccupiedAbove = biome_y_max - ceiling_y local spaceOccupiedBelow = floor_y - biome_y_min if spaceOccupiedAbove >= spaceOccupiedBelow or biome_y_min <= -30000 then -- place the biome above the Nether -- We also shift biomes which extend to the bottom of the map above the Nether, since they -- likely only extend that deep as a catch-all, and probably have a role nearer the surface. new_y_min = ceiling_y + 1 new_y_max = math_max(biome_y_max, ceiling_y + 2) else -- shift the biome to below the Nether new_y_max = floor_y - 1 new_y_min = math_min(biome_y_min, floor_y - 2) end debugf("Moving biome \"%s\" from %s..%s to %s..%s", new_biome_def.name, new_biome_def.y_min, new_biome_def.y_max, new_y_min, new_y_max) if type(new_biome_def.min_pos) == 'table' and type(new_biome_def.min_pos.y) == 'number' then new_biome_def.min_pos.y = new_y_min end if type(new_biome_def.max_pos) == 'table' and type(new_biome_def.max_pos.y) == 'number' then new_biome_def.max_pos.y = new_y_max end new_biome_def.y_min = new_y_min -- Ensure the new heights are saved, even if original biome never specified one new_biome_def.y_max = new_y_max end minetest.register_biome(new_biome_def) end -- Restore biome decorations for decoration_key, new_decoration_def in pairs(registered_decorations_copy) do minetest.register_decoration(new_decoration_def) end -- Restore biome ores for ore_key, new_ore_def in pairs(registered_ores_copy) do minetest.register_ore(new_ore_def) end end -- Shift any overlapping biomes out of the way before we create the Nether biomes mapgen.shift_existing_biomes(NETHER_FLOOR, NETHER_CEILING) -- nether:native_mapgen is used to prevent ores and decorations being generated according -- to landforms created by the native mapgen. -- Ores and decorations can be registered against "nether:rack" instead, and the lua -- on_generate() callback will carve the Nether with nether:rack before invoking -- generate_decorations and generate_ores. -- It is disguised as stone to hide any bug where it leaks out of the nether, such as -- https://github.com/minetest/minetest/issues/13440 or if on_generated() somehow was aborted. local stone_copy_def = table.copy(minetest.registered_nodes["default:stone"] or {}) stone_copy_def.drop = stone_copy_def.drop or "default:stone" -- probably already defined as cobblestone minetest.register_node("nether:native_mapgen", stone_copy_def) minetest.register_biome({ name = "nether_caverns", node_stone = "nether:native_mapgen", -- nether:native_mapgen is used here to prevent the native mapgen from placing ores and decorations. node_filler = "nether:native_mapgen", -- The lua on_generate will transform nether:native_mapgen into nether:rack then decorate and add ores. node_dungeon = "nether:brick", node_dungeon_alt = "nether:brick_cracked", node_dungeon_stair = "stairs:stair_nether_brick", -- Setting node_cave_liquid to "air" avoids the need to filter lava and water out of the mapchunk and -- surrounding shell (overdraw nodes beyond the mapchunk). -- This feature was introduced by paramat in b1b40fef1 on 2019-05-19, and this mapgen.lua file should only -- be run if the Minetest version includes it. The earliest tag made after 2019-05-19 is 5.1.0 on 2019-10-13, -- however we shouldn't test version numbers. minetest.read_schematic() was added by b2065756c and merged in -- 2019-08-14 and is easy to test for, we don't use it but it should make a good proxy-test for whether the -- Minetest version is recent enough to have implemented node_cave_liquid=air node_cave_liquid = "air", y_max = NETHER_CEILING, y_min = NETHER_FLOOR, vertical_blend = 0, heat_point = 50, humidity_point = 50, }) -- Ores and decorations dofile(nether.path .. "/mapgen_decorations.lua") minetest.register_ore({ ore_type = "scatter", ore = "nether:glowstone", wherein = "nether:rack", clust_scarcity = 11 * 11 * 11, clust_num_ores = 3, clust_size = 2, y_max = mapgen.ore_ceiling, y_min = mapgen.ore_floor }) minetest.register_ore({ ore_type = "scatter", ore = "nether:lava_crust", -- crusted lava replaces scattered glowstone in the mantle wherein = "nether:rack_deep", clust_scarcity = 16 * 16 * 16, clust_num_ores = 4, clust_size = 2, y_max = mapgen.ore_ceiling, y_min = mapgen.ore_floor }) minetest.register_ore({ ore_type = "scatter", ore = "default:lava_source", wherein = {"nether:rack", "nether:rack_deep"}, clust_scarcity = 36 * 36 * 36, clust_num_ores = 4, clust_size = 2, y_max = mapgen.ore_ceiling, y_min = mapgen.ore_floor }) minetest.register_ore({ ore_type = "blob", ore = "nether:sand", wherein = "nether:rack", clust_scarcity = 14 * 14 * 14, clust_size = 8, y_max = mapgen.ore_ceiling, y_min = mapgen.ore_floor }) -- Mapgen -- 3D noise mapgen.np_cave = { offset = 0, scale = 1, spread = {x = 384, y = 128, z = 384}, -- squashed 3:1 seed = 59033, octaves = 5, persist = 0.7, lacunarity = 2.0, --flags = "" } local cavePointPerlin = nil mapgen.get_cave_point_perlin = function() cavePointPerlin = cavePointPerlin or minetest.get_perlin(mapgen.np_cave) return cavePointPerlin end mapgen.get_cave_perlin_at = function(pos) cavePointPerlin = cavePointPerlin or minetest.get_perlin(mapgen.np_cave) return cavePointPerlin:get_3d(pos) end -- Buffers and objects we shouldn't recreate every on_generate local nobj_cave = nil local nbuf_cave = {} local dbuf = {} -- Content ids local c_air = minetest.get_content_id("air") local c_netherrack = minetest.get_content_id("nether:rack") local c_netherrack_deep = minetest.get_content_id("nether:rack_deep") local c_crystaldark = minetest.get_content_id("nether:geode") local c_lavasea_source = minetest.get_content_id("nether:lava_source") -- same as lava but with staggered animation to look better as an ocean local c_lava_crust = minetest.get_content_id("nether:lava_crust") local c_native_mapgen = minetest.get_content_id("nether:native_mapgen") local yblmin = NETHER_FLOOR + BLEND * 2 local yblmax = NETHER_CEILING - BLEND * 2 -- At both the top and bottom of the Nether, as set by NETHER_CEILING and NETHER_FLOOR, -- there is a 128 deep cap of solid netherrack, followed by a 128-deep blending zone -- where Nether caverns may start to appear. -- The solid zones and blending zones are achieved by adjusting the np_cave noise to be -- outside the range where caverns form, this function returns that adjustment. -- -- Returns two values: the noise limit adjustment for nether caverns, and the -- noise limit adjustment for the central region / mantle caverns mapgen.get_mapgenblend_adjustments = function(y) -- floorAndCeilingBlend will normally be 0, but shifts toward 1 in the -- blending zone, and goes higher than 1 in the solid zone between the -- blending zone and the end of the nether. local floorAndCeilingBlend = 0 if y > yblmax then floorAndCeilingBlend = ((y - yblmax) / BLEND) ^ 2 end if y < yblmin then floorAndCeilingBlend = ((yblmin - y) / BLEND) ^ 2 end -- the nether caverns exist when np_cave noise is greater than TCAVE, so -- to fade out the nether caverns, adjust TCAVE upward. local tcave_adj = floorAndCeilingBlend -- the central regions exists when np_cave noise is below CENTER_REGION_LIMIT, -- so to fade out the mantle caverns adjust CENTER_REGION_LIMIT downward. local centerRegionLimit_adj = -(CENTER_REGION_LIMIT * floorAndCeilingBlend) return tcave_adj, centerRegionLimit_adj end -- On-generated function local tunnelCandidate_count = 0 local tunnel_count = 0 local total_chunk_count = 0 local function on_generated(minp, maxp, seed) if minp.y > NETHER_CEILING or maxp.y < NETHER_FLOOR then return end local vm, emerge_min, emerge_max = minetest.get_mapgen_object("voxelmanip") local area = VoxelArea:new{MinEdge=emerge_min, MaxEdge=emerge_max} local data = vm:get_data(dbuf) local x0, y0, z0 = minp.x, math_max(minp.y, NETHER_FLOOR), minp.z local x1, y1, z1 = maxp.x, math_min(maxp.y, NETHER_CEILING), maxp.z local yCaveStride = x1 - x0 + 1 local zCaveStride = yCaveStride * yCaveStride local chulens = {x = yCaveStride, y = yCaveStride, z = yCaveStride} nobj_cave = nobj_cave or minetest.get_perlin_map(mapgen.np_cave, chulens) local nvals_cave = nobj_cave:get_3d_map_flat(minp, nbuf_cave) local dungeonRooms = mapgen.build_dungeon_room_list(data, area) -- function from mapgen_dungeons.lua local abs_cave_noise, abs_cave_noise_adjusted local contains_nether = false local contains_mantle = false local contains_ocean = false for y = y0, y1 do -- Y loop first to minimise tcave & lava-sea calculations local sea_level, cavern_limit_distance = mapgen.find_nearest_lava_sealevel(y) -- function from mapgen_mantle.lua local above_lavasea = y > sea_level local below_lavasea = y < sea_level local tcave_adj, centerRegionLimit_adj = mapgen.get_mapgenblend_adjustments(y) local tcave = TCAVE + tcave_adj local tmantle = CENTER_REGION_LIMIT + centerRegionLimit_adj -- cavern_noise_adj already contains central_region_limit_adj, so tmantle is only for comparisons when cavern_noise_adj hasn't been added to the noise value -- cavern_noise_adj gets added to noise value instead of added to the limit np_noise -- is compared against, so subtract centerRegionLimit_adj instead of adding local cavern_noise_adj = CENTER_REGION_LIMIT * (cavern_limit_distance * cavern_limit_distance * cavern_limit_distance) - centerRegionLimit_adj for z = z0, z1 do local vi = area:index(x0, y, z) -- Initial voxelmanip index local ni = (z - z0) * zCaveStride + (y - y0) * yCaveStride + 1 local noise2di = 1 + (z - z0) * yCaveStride for x = x0, x1 do local cave_noise = nvals_cave[ni] if cave_noise > tcave then -- Prime region -- This was the only region in initial versions of the Nether mod. -- It is the only region which portals from the surface will open into, -- getting to any other regions in the Nether will require Shanks' Pony. data[vi] = c_air contains_nether = true elseif -cave_noise > tcave then -- Secondary/spare region -- This secondary region is unused until someone decides to do something cool or novel with it. -- Reaching here would require the player to first find and journey through the central region, -- as it's always separated from the Prime region by the central region. data[vi] = mapgen.getGeodeInteriorNodeId(x, y, z)-- function from mapgen_geodes.lua -- Only set contains_nether to true here if you want tunnels created between the secondary region -- and the central region. contains_nether = true else -- netherrack walls and/or center region/mantle abs_cave_noise = math_abs(cave_noise) -- abs_cave_noise_adjusted makes the center region smaller as distance from the lava ocean -- increases, we do this by pretending the abs_cave_noise value is higher. abs_cave_noise_adjusted = abs_cave_noise + cavern_noise_adj if abs_cave_noise_adjusted >= CENTER_CAVERN_LIMIT then local id = data[vi] -- Check existing node to avoid removing dungeons if id == c_air or id == c_native_mapgen then if abs_cave_noise < tmantle then data[vi] = c_netherrack_deep else -- the shell seperating the mantle from the rest of the nether... if cave_noise > 0 then data[vi] = c_netherrack -- excavate_dungeons() will mostly reverse this inside dungeons else data[vi] = c_crystaldark end end end elseif above_lavasea then data[vi] = c_air contains_mantle = true elseif abs_cave_noise_adjusted < SURFACE_CRUST_LIMIT or (below_lavasea and abs_cave_noise_adjusted < CRUST_LIMIT) then data[vi] = c_lavasea_source contains_ocean = true else data[vi] = c_lava_crust contains_ocean = true end end ni = ni + 1 vi = vi + 1 noise2di = noise2di + 1 end end end if contains_mantle or contains_ocean then mapgen.add_basalt_columns(data, area, minp, maxp) -- function from mapgen_mantle.lua end if contains_nether and contains_mantle then tunnelCandidate_count = tunnelCandidate_count + 1 local success = mapgen.excavate_tunnel_to_center_of_the_nether(data, area, nvals_cave, minp, maxp) -- function from mapgen_mantle.lua if success then tunnel_count = tunnel_count + 1 end end total_chunk_count = total_chunk_count + 1 if total_chunk_count % 50 == 0 then debugf( "%s of %s chunks contain both nether and lava-sea (%s%%), %s chunks generated a pathway (%s%%)", tunnelCandidate_count, total_chunk_count, math_floor(tunnelCandidate_count * 100 / total_chunk_count), tunnel_count, math_floor(tunnel_count * 100 / total_chunk_count) ) end -- any air from the native mapgen has been replaced by netherrack, but we -- don't want netherrack inside dungeons, so fill known dungeon rooms with air. mapgen.excavate_dungeons(data, area, dungeonRooms) -- function from mapgen_dungeons.lua mapgen.decorate_dungeons(data, area, dungeonRooms) -- function from mapgen_dungeons.lua vm:set_data(data) minetest.generate_ores(vm) minetest.generate_decorations(vm) vm:set_lighting({day = 0, night = 0}, minp, maxp) vm:calc_lighting() vm:update_liquids() vm:write_to_map() end -- use knowledge of the nether mapgen algorithm to return a suitable ground level for placing a portal. -- player_name is optional, allowing a player to spawn a remote portal in their own protected areas. function nether.find_nether_ground_y(target_x, target_z, start_y, player_name) local nobj_cave_point = mapgen.get_cave_point_perlin() local air = 0 -- Consecutive air nodes found local minp_schem, maxp_schem = nether.get_schematic_volume({x = target_x, y = 0, z = target_z}, nil, "nether_portal") local minp = {x = minp_schem.x, y = 0, z = minp_schem.z} local maxp = {x = maxp_schem.x, y = 0, z = maxp_schem.z} for y = start_y, math_max(NETHER_FLOOR + BLEND, start_y - 4096), -1 do local nval_cave = nobj_cave_point:get_3d({x = target_x, y = y, z = target_z}) if nval_cave > TCAVE then -- Cavern air = air + 1 else -- Not cavern, check if 4 nodes of space above if air >= 4 then local portal_y = y + 1 -- Check volume for non-natural nodes minp.y = minp_schem.y + portal_y maxp.y = maxp_schem.y + portal_y if nether.volume_is_natural_and_unprotected(minp, maxp, player_name) then return portal_y else -- Restart search a little lower nether.find_nether_ground_y(target_x, target_z, y - 16, player_name) end else -- Not enough space, reset air to zero air = 0 end end end return math_max(start_y, NETHER_FLOOR + BLEND) -- Fallback end minetest.register_on_generated(on_generated) nether-3.6/mapgen_decorations.lua000066400000000000000000000457041461347743200172400ustar00rootroot00000000000000--[[ Register decorations for Nether mapgen Copyright (C) 2020 Treer Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ]]-- -- Lava is unreliable in the old Nether mapgen because it removes lava -- from the overdraw areas, so any decorations involving lava will often -- have the lava missing depending on whether nearby chunks were already -- emerged or not before the decoration was placed. local allow_lava_decorations = nether.useBiomes -- Keep compatibility with mapgen_nobiomes.lua, so hardcoding 128 -- instead of using nether.mapgen.BLEND local decoration_ceiling = nether.DEPTH_CEILING - 128 local decoration_floor = nether.DEPTH_FLOOR + 128 local _ = {name = "air", prob = 0} local A = {name = "air", prob = 255, force_place = true} local G = {name = "nether:glowstone", prob = 255, force_place = true} local N = {name = "nether:rack", prob = 255} local D = {name = "nether:rack_deep", prob = 255} local S = {name = "nether:sand", prob = 255, force_place = true} local L = {name = "default:lava_source", prob = 255, force_place = true} local F = {name = "nether:fumarole", prob = 255, force_place = true} local FS = {name = "nether:fumarole_slab", prob = 255, force_place = true} local F1 = {name = "nether:fumarole_corner", prob = 255, force_place = true, param2 = 0} local F2 = {name = "nether:fumarole_corner", prob = 255, force_place = true, param2 = 1} local F3 = {name = "nether:fumarole_corner", prob = 255, force_place = true, param2 = 2} local F4 = {name = "nether:fumarole_corner", prob = 255, force_place = true, param2 = 3} local S1 = {name = "stairs:stair_netherrack", prob = 255, force_place = true, param2 = 5} local S2 = {name = "stairs:stair_netherrack", prob = 255, force_place = true, param2 = 7} local S3 = {name = "stairs:stair_netherrack", prob = 255, force_place = true, param2 = 12} local S4 = {name = "stairs:stair_netherrack", prob = 255, force_place = true, param2 = 16} -- ================= -- Stalactites -- ================= local schematic_GlowstoneStalactite = { size = {x = 5, y = 10, z = 5}, data = { -- note that data is upside down _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, N, G, N, _, _, N, N, N, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, G, _, _, _, _, G, _, _, _, G, G, G, _, N, G, G, G, N, N, N, G, N, N, _, _, N, _, _, -- ypos 0, prob 25% (64/256) _, _, G, _, _, -- ypos 1, prob 37% (96/256) _, _, G, _, _, -- ypos 2, prob 100% _, _, G, _, _, -- ypos 3, prob 100% _, _, G, G, _, -- ypos 4, prob 50% (128/256) to make half of stalactites asymmetric _, G, G, G, _, -- ypos 5, prob 75% (192/256) _, G, G, G, _, -- ypos 6, prob 75% (192/256) _, G, G, G, _, -- ypos 7, prob 100% G, G, G, G, G, -- ypos 8, prob 100% N, G, G, G, N, -- ypos 9, prob 75% (192/256) _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, G, _, _, _, _, G, _, _, _, _, G, _, _, _, G, G, G, _, N, G, G, G, N, N, N, G, N, N, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, N, G, N, _, _, N, N, N, _ }, -- Y-slice probabilities do not function correctly for ceiling schematic -- decorations because they are inverted, so ypos numbers have been inverted -- to match, and a larger offset in place_offset_y should be used (e.g. -3). yslice_prob = { {ypos = 9, prob = 192}, {ypos = 6, prob = 192}, {ypos = 5, prob = 192}, {ypos = 4, prob = 128}, {ypos = 1, prob = 96}, {ypos = 0, prob = 64} } } minetest.register_decoration({ name = "Glowstone stalactite", deco_type = "schematic", place_on = "nether:rack", sidelen = 80, fill_ratio = 0.0003, biomes = {"nether_caverns"}, y_max = decoration_ceiling, y_min = decoration_floor, schematic = schematic_GlowstoneStalactite, flags = "place_center_x,place_center_z,force_placement,all_ceilings", place_offset_y=-3 }) minetest.register_decoration({ name = "Netherrack stalactite", deco_type = "schematic", place_on = "nether:rack", sidelen = 80, fill_ratio = 0.0008, biomes = {"nether_caverns"}, y_max = decoration_ceiling, y_min = decoration_floor, schematic = schematic_GlowstoneStalactite, replacements = {["nether:glowstone"] = "nether:rack"}, flags = "place_center_x,place_center_z,all_ceilings", place_offset_y=-3 }) local schematic_GreaterStalactite = { size = {x = 3, y = 23, z = 3}, data = { -- note that data is upside down _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, D, _, _, D, _, _, D, _, _, D, _, D, D, D, D, D, D, D, D, D, _, D, _, _, _, _, _, _, _, _, D, _, -- ypos 0, prob 85% (218/255) _, D, _, -- ypos 1, prob 85% (218/255) _, D, _, -- ypos 2, prob 85% (218/255) _, D, _, -- ypos 3, prob 85% (218/255) _, D, _, -- ypos 4, prob 85% (218/255) _, D, _, -- ypos 5, prob 85% (218/255) _, D, _, -- ypos 6, prob 85% (218/255) _, D, _, -- ypos 7, prob 85% (218/255) _, D, _, -- ypos 8, prob 85% (218/255) _, D, D, -- ypos 9, prob 50% (128/256) to make half of stalactites asymmetric _, D, D, -- ypos 10, prob 50% (128/256) to make half of stalactites asymmetric _, D, D, -- ypos 11, prob 50% (128/256) to make half of stalactites asymmetric _, D, D, -- ypos 12, prob 50% (128/256) to make half of stalactites asymmetric D, D, D, -- ypos 13, prob 75% (192/256) D, D, D, -- ypos 14, prob 75% (192/256) D, D, D, -- ypos 15, prob 100% D, D, D, -- ypos 16, prob 100% D, D, D, -- ypos 17, prob 100% D, D, D, -- ypos 18, prob 100% D, D, D, -- ypos 19, prob 75% (192/256) D, D, D, -- ypos 20, prob 85% (218/255) _, D, D, -- ypos 21, prob 50% (128/256) to make half of stalactites asymmetric _, D, _, -- ypos 22, prob 100% _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, D, _, _, D, _, _, D, _, _, D, _, _, D, _, D, D, D, D, D, D, D, D, D, _, D, _, _, D, _, _, _, _, }, -- Y-slice probabilities do not function correctly for ceiling schematic -- decorations because they are inverted, so ypos numbers have been inverted -- to match, and a larger offset in place_offset_y should be used (e.g. -3). yslice_prob = { {ypos = 21, prob = 128}, {ypos = 20, prob = 218}, {ypos = 19, prob = 192}, {ypos = 14, prob = 192}, {ypos = 13, prob = 192}, {ypos = 12, prob = 128}, {ypos = 11, prob = 128}, {ypos = 10, prob = 128}, {ypos = 9, prob = 128}, {ypos = 8, prob = 218}, {ypos = 7, prob = 218}, {ypos = 6, prob = 218}, {ypos = 5, prob = 218}, {ypos = 4, prob = 218}, {ypos = 3, prob = 218}, {ypos = 2, prob = 218}, {ypos = 1, prob = 218}, {ypos = 0, prob = 218} } } -- A stalagmite is an upsidedown stalactite, so -- use the GreaterStalactite to create a ToweringStalagmite schematic local schematic_ToweringStalagmite = { size = schematic_GreaterStalactite.size, data = {}, yslice_prob = {} } local array_length = #schematic_GreaterStalactite.data + 1 for i, node in ipairs(schematic_GreaterStalactite.data) do schematic_ToweringStalagmite.data[array_length - i] = node end y_size = schematic_GreaterStalactite.size.y for i, node in ipairs(schematic_GreaterStalactite.yslice_prob) do schematic_ToweringStalagmite.yslice_prob[i] = { -- we can safely lower the prob. to gain more variance because floor based schematics -- don't have the bug where missing lines moves them away from the surface prob = schematic_GreaterStalactite.yslice_prob[i].prob - 20, ypos = y_size - 1 - schematic_GreaterStalactite.yslice_prob[i].ypos } end minetest.register_decoration({ name = "Deep-glowstone stalactite", deco_type = "schematic", place_on = "nether:rack_deep", sidelen = 80, fill_ratio = 0.0003, biomes = {"nether_caverns"}, y_max = decoration_ceiling, y_min = decoration_floor, schematic = schematic_GlowstoneStalactite, replacements = {["nether:rack"] = "nether:rack_deep", ["nether:glowstone"] = "nether:glowstone_deep"}, flags = "place_center_x,place_center_z,force_placement,all_ceilings", place_offset_y=-3 }) minetest.register_decoration({ name = "Deep-glowstone stalactite outgrowth", deco_type = "schematic", place_on = "nether:glowstone_deep", sidelen = 40, fill_ratio = 0.15, biomes = {"nether_caverns"}, y_max = decoration_ceiling, y_min = decoration_floor, schematic = { size = {x = 1, y = 4, z = 1}, data = { G, G, G, G } }, replacements = {["nether:glowstone"] = "nether:glowstone_deep"}, flags = "place_center_x,place_center_z,all_ceilings", }) minetest.register_decoration({ name = "Deep-netherrack stalactite", deco_type = "schematic", place_on = "nether:rack_deep", sidelen = 80, fill_ratio = 0.0003, biomes = {"nether_caverns"}, y_max = decoration_ceiling, y_min = decoration_floor, schematic = schematic_GlowstoneStalactite, replacements = {["nether:rack"] = "nether:rack_deep", ["nether:glowstone"] = "nether:rack_deep"}, flags = "place_center_x,place_center_z,force_placement,all_ceilings", place_offset_y=-3 }) minetest.register_decoration({ name = "Deep-netherrack towering stalagmite", deco_type = "schematic", place_on = "nether:rack_deep", sidelen = 80, fill_ratio = 0.001, biomes = {"nether_caverns"}, y_max = decoration_ceiling, y_min = decoration_floor, schematic = schematic_ToweringStalagmite, replacements = {["nether:basalt"] = "nether:rack_deep"}, flags = "place_center_x,place_center_z,force_placement,all_floors", place_offset_y=-2 }) -- ======================================= -- Concealed crevice / Lava sinkhole -- ======================================= -- if player places a torch/block on this sand or digs it while standing on it, it sinks into lava if allow_lava_decorations then minetest.register_decoration({ name = "Weak trap", deco_type = "schematic", place_on = "nether:rack", sidelen = 80, fill_ratio = 0.002, biomes = {"nether_caverns"}, y_max = decoration_ceiling, y_min = decoration_floor, schematic = { size = {x = 4, y = 7, z = 4}, data = { -- note that data is upside down _, _, _, _, _, _, _, _, _, N, _, _, _, N, N, _, _, N, N, _, _, N, N, _, _, _, _, _, _, N, _, _, -- make it look like a stalactite if it protrudes out the bottom of a landform _, N, _, _, N, L, N, _, N, L, L, N, N, L, L, N, N, A, A, N, _, S, S, _, _, _, _, _, _, _, _, _, _, N, N, _, N, L, L, N, N, L, L, N, N, A, A, N, _, S, S, _, _, _, _, _, _, _, _, _, _, _, _, _, _, N, N, _, _, N, N, _, _, N, N, _, _, _, _, _, } }, replacements = {["nether:glowstone"] = "nether:rack"}, flags = "place_center_x,place_center_z,force_placement, all_floors", place_offset_y=-6, rotation = "random" }) end -- ========================== -- Fumaroles (Chimneys) -- ========================== local replacements_slab = {} local replacements_full = {["nether:fumarole_slab"] = "nether:fumarole"} if allow_lava_decorations then -- Minetest engine limitations mean any mesh or nodebox node (like nether:fumarole) -- will light up if it has lava below it, so replace the air node over the lava with -- a node that prevents light propagation. -- (Unfortunately this also means if a player digs down to get the lava block it'll -- look like the lighting wasn't set in the block above the lava) replacements_slab["air"] = "nether:airlike_darkness" replacements_full["air"] = "nether:airlike_darkness" else -- Lava is frequently removed by the old mapgen, so put sand at the bottom -- of fumaroles. replacements_slab["default:lava_source"] = "nether:sand" replacements_full["default:lava_source"] = "nether:sand" end local schematic_fumarole = { size = {x = 3, y = 5, z = 3}, data = { -- note that data is upside down _, _, _, _, N, _, _, N, _, _, _, _, _, _, _, _, N, _, N, L, N, N, A, N, _, F, _, _,FS, _, _, _, _, _, N, _, _, N, _, _, _, _, _, _, _, }, } -- Common fumarole decoration that's flush with the floor and spawns everywhere minetest.register_decoration({ name = "Sunken nether fumarole", deco_type = "schematic", place_on = {"nether:rack"}, sidelen = 80, fill_ratio = 0.005, biomes = {"nether_caverns"}, y_max = decoration_ceiling, y_min = decoration_floor, schematic = schematic_fumarole, replacements = replacements_full, flags = "place_center_x,place_center_z,all_floors", place_offset_y=-4 }) -- Rarer formations of raised fumaroles in clumps local fumarole_clump_noise_offset = -0.58 local fumarole_clump_noise = { offset = fumarole_clump_noise_offset, scale = 0.5, spread = {x = 40, y = 40, z = 15}, octaves = 4, persist = 0.65, lacunarity = 2.0, } fumarole_clump_noise.offset = fumarole_clump_noise_offset - 0.035 minetest.register_decoration({ name = "Raised Nether fumarole", deco_type = "schematic", place_on = {"nether:rack"}, sidelen = 8, noise_params = fumarole_clump_noise, biomes = {"nether_caverns"}, y_max = decoration_ceiling, y_min = decoration_floor, schematic = schematic_fumarole, replacements = replacements_full, flags = "place_center_x,place_center_z,all_floors", place_offset_y=-3 }) fumarole_clump_noise.offset = fumarole_clump_noise_offset minetest.register_decoration({ name = "Half-raised Nether fumarole", deco_type = "schematic", place_on = {"nether:rack"}, sidelen = 8, noise_params = fumarole_clump_noise, biomes = {"nether_caverns"}, y_max = decoration_ceiling, y_min = decoration_floor, schematic = schematic_fumarole, replacements = replacements_slab, flags = "place_center_x,place_center_z,all_floors", place_offset_y=-3 }) fumarole_clump_noise.offset = fumarole_clump_noise_offset - 0.035 minetest.register_decoration({ name = "Nether fumarole mound", deco_type = "schematic", place_on = {"nether:rack"}, sidelen = 8, noise_params = fumarole_clump_noise, biomes = {"nether_caverns"}, y_max = decoration_ceiling, y_min = decoration_floor, schematic = { size = {x = 4, y = 4, z = 4}, data = { -- note that data is upside down _, _, _, _, _, N, N, _, _, _, _, _, _, _, _, _, _, S, S, _, N, A, A, N, _, S2, S1, _, _, F2, F1, _, _, S, S, _, N, A, A, N, _, S3, S4, _, _, F3, F4, _, _, _, _, _, _, N, N, _, _, _, _, _, _, _, _, _ }, yslice_prob = {{ypos = 3, prob = 192}} -- occasionally leave the fumarole cap off }, flags = "place_center_x,place_center_z,all_floors", place_offset_y = -2 }) fumarole_clump_noise.offset = fumarole_clump_noise_offset - 0.01 minetest.register_decoration({ name = "Double Nether fumarole", deco_type = "schematic", place_on = {"nether:rack"}, sidelen = 8, noise_params = fumarole_clump_noise, biomes = {"nether_caverns"}, y_max = decoration_ceiling, y_min = decoration_floor, schematic = { size = {x = 4, y = 5, z = 4}, data = { -- note that data is upside down _, _, _, _, _, N, N, _, _, _, _, _, _, _, _, _, _, _, _, _, _, S, S, _, N, A, A, N, _, S2, S1, _, _, F2, F, _, _, _, FS, _, _, S, S, _, F, A, A, N, -- the F may add slight variance in landforms where it gets exposed _, S3, S4, _, _, F3, F4, _, _, _, _, _, _, _, _, _, _, N, N, _, _, _, _, _, _, _, _, _, _, _, _, _ } }, flags = "place_center_x,place_center_z,all_floors", place_offset_y = -2, rotation = "random" })nether-3.6/mapgen_dungeons.lua000066400000000000000000000311441461347743200165410ustar00rootroot00000000000000--[[ Nether mod for minetest All the Dungeon related functions used by the biomes-based mapgen are here. Copyright (C) 2021 Treer Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ]]-- -- We don't need to be gen-notified of temples because only dungeons will be generated -- if a biome defines the dungeon nodes minetest.set_gen_notify({dungeon = true}) -- Content ids local c_air = minetest.get_content_id("air") local c_netherrack = minetest.get_content_id("nether:rack") local c_netherrack_deep = minetest.get_content_id("nether:rack_deep") local c_crystaldark = minetest.get_content_id("nether:geode") local c_dungeonbrick = minetest.get_content_id("nether:brick") local c_dungeonbrick_alt = minetest.get_content_id("nether:brick_cracked") local c_netherbrick_slab = minetest.get_content_id("stairs:slab_nether_brick") local c_netherfence = minetest.get_content_id("nether:fence_nether_brick") local c_glowstone = minetest.get_content_id("nether:glowstone") local c_glowstone_deep = minetest.get_content_id("nether:glowstone_deep") local c_lava_source = minetest.get_content_id("default:lava_source") -- Misc math functions -- avoid needing table lookups each time a common math function is invoked local math_max, math_min = math.max, math.min -- Dungeon excavation functions function is_dungeon_brick(node_id) return node_id == c_dungeonbrick or node_id == c_dungeonbrick_alt end nether.mapgen.build_dungeon_room_list = function(data, area) local result = {} -- Unfortunately gennotify only returns dungeon rooms, not corridors. -- We don't need to check for temples because only dungeons are generated in biomes -- that define their own dungeon nodes. local gennotify = minetest.get_mapgen_object("gennotify") local roomLocations = gennotify["dungeon"] or {} -- Excavation should still know to stop if a cave or corridor has removed the dungeon wall. -- See MapgenBasic::generateDungeons in mapgen.cpp for max room sizes. local maxRoomSize = 18 local maxRoomRadius = math.ceil(maxRoomSize / 2) local xStride, yStride, zStride = 1, area.ystride, area.zstride local minEdge, maxEdge = area.MinEdge, area.MaxEdge for _, roomPos in ipairs(roomLocations) do if area:containsp(roomPos) then -- this safety check does not appear to be necessary, but lets make it explicit local room_vi = area:indexp(roomPos) --data[room_vi] = minetest.get_content_id("default:torch") -- debug local startPos = vector.new(roomPos) if roomPos.y + 1 <= maxEdge.y and data[room_vi + yStride] == c_air then -- The roomPos coords given by gennotify are at floor level, but whenever possible we -- want to be performing searches a node higher than floor level to avoids dungeon chests. startPos.y = startPos.y + 1 room_vi = area:indexp(startPos) end local bound_min_x = math_max(minEdge.x, roomPos.x - maxRoomRadius) local bound_min_y = math_max(minEdge.y, roomPos.y - 1) -- room coords given by gennotify are on the floor local bound_min_z = math_max(minEdge.z, roomPos.z - maxRoomRadius) local bound_max_x = math_min(maxEdge.x, roomPos.x + maxRoomRadius) local bound_max_y = math_min(maxEdge.y, roomPos.y + maxRoomSize) -- room coords given by gennotify are on the floor local bound_max_z = math_min(maxEdge.z, roomPos.z + maxRoomRadius) local room_min = vector.new(startPos) local room_max = vector.new(startPos) local vi = room_vi while room_max.y < bound_max_y and data[vi + yStride] == c_air do room_max.y = room_max.y + 1 vi = vi + yStride end vi = room_vi while room_min.y > bound_min_y and data[vi - yStride] == c_air do room_min.y = room_min.y - 1 vi = vi - yStride end vi = room_vi while room_max.z < bound_max_z and data[vi + zStride] == c_air do room_max.z = room_max.z + 1 vi = vi + zStride end vi = room_vi while room_min.z > bound_min_z and data[vi - zStride] == c_air do room_min.z = room_min.z - 1 vi = vi - zStride end vi = room_vi while room_max.x < bound_max_x and data[vi + xStride] == c_air do room_max.x = room_max.x + 1 vi = vi + xStride end vi = room_vi while room_min.x > bound_min_x and data[vi - xStride] == c_air do room_min.x = room_min.x - 1 vi = vi - xStride end local roomInfo = vector.new(roomPos) roomInfo.minp = room_min roomInfo.maxp = room_max result[#result + 1] = roomInfo end end return result; end -- Only partially excavates dungeons, the rest is left as an exercise for the player ;) -- (Corridors and the parts of rooms which extend beyond the emerge boundary will remain filled) nether.mapgen.excavate_dungeons = function(data, area, rooms) local vi, node_id -- any air from the native mapgen has been replaced by netherrack, but -- we don't want this inside dungeons, so fill dungeon rooms with air for _, roomInfo in ipairs(rooms) do local room_min = roomInfo.minp local room_max = roomInfo.maxp for z = room_min.z, room_max.z do for y = room_min.y, room_max.y do vi = area:index(room_min.x, y, z) for x = room_min.x, room_max.x do node_id = data[vi] if node_id == c_netherrack or node_id == c_netherrack_deep or node_id == c_crystaldark then data[vi] = c_air end vi = vi + 1 end end end end -- clear netherrack from dungeon stairways if #rooms > 0 then local stairPositions = minetest.find_nodes_in_area(area.MinEdge, area.MaxEdge, minetest.registered_biomes["nether_caverns"].node_dungeon_stair) for _, stairPos in ipairs(stairPositions) do vi = area:indexp(stairPos) for i = 1, 4 do if stairPos.y + i > area.MaxEdge.y then break end vi = vi + area.ystride node_id = data[vi] -- searching forward of the stairs could also be done if node_id == c_netherrack or node_id == c_netherrack_deep or node_id == c_crystaldark then data[vi] = c_air end end end end end -- Since we already know where all the rooms and their walls are, and have all the nodes stored -- in a voxelmanip already, we may as well add a little Nether flair to the dungeons found here. nether.mapgen.decorate_dungeons = function(data, area, rooms) local xStride, yStride, zStride = 1, area.ystride, area.zstride local minEdge, maxEdge = area.MinEdge, area.MaxEdge for _, roomInfo in ipairs(rooms) do local room_min, room_max = roomInfo.minp, roomInfo.maxp local room_size = vector.distance(room_min, room_max) if room_size > 10 then local room_seed = roomInfo.x + 3 * roomInfo.z + 13 * roomInfo.y local window_y = roomInfo.y + math_min(2, room_max.y - roomInfo.y - 1) local roomWidth = room_max.x - room_min.x + 1 local roomLength = room_max.z - room_min.z + 1 if room_seed % 3 == 0 and room_max.y < maxEdge.y then -- Glowstone chandelier (feel free to replace with a fancy schematic) local vi = area:index(roomInfo.x, room_max.y + 1, roomInfo.z) if is_dungeon_brick(data[vi]) then data[vi] = c_glowstone end elseif room_seed % 4 == 0 and room_min.y > minEdge.y and room_min.x > minEdge.x and room_max.x < maxEdge.x and room_min.z > minEdge.z and room_max.z < maxEdge.z then -- lava well (feel free to replace with a fancy schematic) local vi = area:index(roomInfo.x, room_min.y, roomInfo.z) if is_dungeon_brick(data[vi - yStride]) then data[vi - yStride] = c_lava_source if data[vi - zStride] == c_air then data[vi - zStride] = c_netherbrick_slab end if data[vi + zStride] == c_air then data[vi + zStride] = c_netherbrick_slab end if data[vi - xStride] == c_air then data[vi - xStride] = c_netherbrick_slab end if data[vi + xStride] == c_air then data[vi + xStride] = c_netherbrick_slab end end end -- Barred windows if room_seed % 7 < 5 and roomWidth >= 5 and roomLength >= 5 and window_y >= minEdge.y and window_y + 1 <= maxEdge.y and room_min.x > minEdge.x and room_max.x < maxEdge.x and room_min.z > minEdge.z and room_max.z < maxEdge.z then --data[area:indexp(roomInfo)] = minetest.get_content_id("default:mese_post_light") -- debug -- Glass panes can't go in the windows because we aren't setting param data. -- Until a Nether glass is added, every window will be made of netherbrick fence rather -- than material depending on room_seed. local window_node = c_netherfence --if c_netherglass ~= nil and room_seed % 20 >= 12 then window_node = c_netherglass end local function placeWindow(vi, viOutsideOffset, windowNo) if is_dungeon_brick(data[vi]) and is_dungeon_brick(data[vi + yStride]) then data[vi] = window_node if room_seed % 19 == windowNo then -- place a glowstone light behind the window local node_id = data[vi + viOutsideOffset] if node_id == c_netherrack then data[vi + viOutsideOffset] = c_glowstone elseif node_id == c_netherrack_deep then data[vi + viOutsideOffset] = c_glowstone_deep end end end end local vi_min = area:index(room_min.x - 1, window_y, roomInfo.z) local vi_max = area:index(room_max.x + 1, window_y, roomInfo.z) local locations = {-zStride, zStride, -zStride + yStride, zStride + yStride} for i, offset in ipairs(locations) do placeWindow(vi_min + offset, -1, i) placeWindow(vi_max + offset, 1, i + #locations) end vi_min = area:index(roomInfo.x, window_y, room_min.z - 1) vi_max = area:index(roomInfo.x, window_y, room_max.z + 1) locations = {-xStride, xStride, -xStride + yStride, xStride + yStride} for i, offset in ipairs(locations) do placeWindow(vi_min + offset, -zStride, i + #locations * 2) placeWindow(vi_max + offset, zStride, i + #locations * 3) end end -- pillars or mezzanine floor if room_seed % 43 > 10 and roomWidth >= 6 and roomLength >= 6 then local pillar_vi = {} local pillarHeight = 0 local wallDist = 1 + math.floor((roomWidth + roomLength) / 14) local roomHeight = room_max.y - room_min.y if roomHeight >= 7 then -- mezzanine floor local mezzMax = { x = room_min.x + math.floor(roomWidth / 7 * 4), y = room_min.y + math.floor(roomHeight / 5 * 3), z = room_max.z } pillarHeight = mezzMax.y - room_min.y - 1 pillar_vi = { area:index(mezzMax.x, room_min.y, room_min.z + wallDist), area:index(mezzMax.x, room_min.y, room_max.z - wallDist), } if is_dungeon_brick(data[pillar_vi[1] - yStride]) and is_dungeon_brick(data[pillar_vi[2] - yStride]) then -- The floor of the dungeon looks like it exists (i.e. not erased by nether -- cavern), so add the mezzanine floor for z = 0, roomLength - 1 do local vi = area:index(room_min.x, mezzMax.y, room_min.z + z) for x = room_min.x, mezzMax.x do if data[vi] == c_air then data[vi] = c_dungeonbrick end vi = vi + 1 end end end elseif roomHeight >= 4 then -- 4 pillars pillarHeight = roomHeight pillar_vi = { area:index(room_min.x + wallDist, room_min.y, room_min.z + wallDist), area:index(room_min.x + wallDist, room_min.y, room_max.z - wallDist), area:index(room_max.x - wallDist, room_min.y, room_min.z + wallDist), area:index(room_max.x - wallDist, room_min.y, room_max.z - wallDist) } end for i = #pillar_vi, 1, -1 do if not is_dungeon_brick(data[pillar_vi[i] - yStride]) then -- there's no dungeon floor under this pillar so skip it, it's probably been cut away by nether cavern. table.remove(pillar_vi, i) end end for y = 0, pillarHeight do for _, vi in ipairs(pillar_vi) do if data[vi + y * yStride] == c_air then data[vi + y * yStride] = c_dungeonbrick end end end end -- Weeds on the floor once Nether weeds are added end end end nether-3.6/mapgen_geodes.lua000066400000000000000000000172161461347743200161710ustar00rootroot00000000000000--[[ Nether mod for minetest This file contains helper functions for generating geode interiors, a proof-of-concept to demonstrate how the secondary/spare region in the nether might be put to use by someone. Copyright (C) 2021 Treer Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ]]-- local debugf = nether.debug local mapgen = nether.mapgen -- Content ids local c_air = minetest.get_content_id("air") local c_crystal = minetest.get_content_id("nether:geodelite") -- geodelite has a faint glow local c_netherrack = minetest.get_content_id("nether:rack") local c_glowstone = minetest.get_content_id("nether:glowstone") -- Math funcs local math_max, math_min, math_abs, math_floor, math_pi = math.max, math.min, math.abs, math.floor, math.pi -- avoid needing table lookups each time a common math function is invoked -- Create a tiling space of close-packed spheres, using Hexagonal close packing -- of spheres with radius 0.5. -- With a layer of spheres on a flat surface, if the pack-z distance is 1 due to 0.5 -- radius then the pack-x distance will be the height of an equilateral triangle: sqrt(3) / 2, -- and the pack-y distance between each layer will be sqrt(6) / 3, -- The tessellating space will be a rectangular box of 2*pack-x by 1*pack-z by 3*pack-y local xPack = math.sqrt(3)/2 -- 0.866, height of an equalateral triangle local xPack2 = xPack * 2 -- 1.732 local yPack = math.sqrt(6) / 3 -- 0.816, y height of each layer local yPack2 = yPack * 2 local yPack3 = yPack * 3 local layer2offsetx = xPack / 3 -- 0.289, height to center of equalateral triangle local layer3offsetx = xPack2 / 3 -- 0.577 local structureSize = 50 -- magic numbers may need retuning if this changes too much local layer1 = { {0, 0, 0}, {0, 0, 1}, {xPack, 0, -0.5}, {xPack, 0, 0.5}, {xPack, 0, 1.5}, {xPack2, 0, 0}, {xPack2, 0, 1}, } local layer2 = { {layer2offsetx - xPack, yPack, 0}, {layer2offsetx - xPack, yPack, 1}, {layer2offsetx, yPack, -0.5}, {layer2offsetx, yPack, 0.5}, {layer2offsetx, yPack, 1.5}, {layer2offsetx + xPack, yPack, 0}, {layer2offsetx + xPack, yPack, 1}, {layer2offsetx + xPack2, yPack, -0.5}, {layer2offsetx + xPack2, yPack, 0.5}, {layer2offsetx + xPack2, yPack, 1.5}, } local layer3 = { {layer3offsetx - xPack, yPack2, -0.5}, {layer3offsetx - xPack, yPack2, 0.5}, {layer3offsetx - xPack, yPack2, 1.5}, {layer3offsetx, yPack2, 0}, {layer3offsetx, yPack2, 1}, {layer3offsetx + xPack, yPack2, -0.5}, {layer3offsetx + xPack, yPack2, 0.5}, {layer3offsetx + xPack, yPack2, 1.5}, {layer3offsetx + xPack2, yPack2, 0}, {layer3offsetx + xPack2, yPack2, 1}, } local layer4 = { {0, yPack3, 0}, {0, yPack3, 1}, {xPack, yPack3, -0.5}, {xPack, yPack3, 0.5}, {xPack, yPack3, 1.5}, {xPack2, yPack3, 0}, {xPack2, yPack3, 1}, } local layers = { {y = layer1[1][2], points = layer1}, -- layer1[1][2] is the y value of the first point in layer1, and all spheres in a layer have the same y {y = layer2[1][2], points = layer2}, {y = layer3[1][2], points = layer3}, {y = layer4[1][2], points = layer4}, } -- Geode mapgen functions (AKA proof of secondary/spare region concept) -- fast for small lists function insertionSort(array) local i for i = 2, #array do local key = array[i] local j = i - 1 while j > 0 and array[j] > key do array[j + 1] = array[j] j = j - 1 end array[j + 1] = key end return array end local distSquaredList = {} local adj_x = 0 local adj_y = 0 local adj_z = 0 local lasty, lastz local warpx, warpz -- It's quite a lot to calculate for each air node, but its not terribly slow and -- it'll be pretty darn rare for chunks in the secondary region to ever get emerged. mapgen.getGeodeInteriorNodeId = function(x, y, z) if z ~= lastz then lastz = z -- Calculate structure warping -- To avoid calculating this for each node there's no warping as you look along the x axis :( adj_y = math.sin(math_pi / 222 * y) * 30 if y ~= lasty then lasty = y warpx = math.sin(math_pi / 100 * y) * 10 warpz = math.sin(math_pi / 43 * y) * 15 end local twistRadians = math_pi / 73 * y local sinTwist, cosTwist = math.sin(twistRadians), math.cos(twistRadians) adj_x = cosTwist * warpx - sinTwist * warpz adj_z = sinTwist * warpx + cosTwist * warpz end -- convert x, y, z into a position in the tessellating space local cell_x = (((x + adj_x) / xPack2 + 0.5) % structureSize) / structureSize * xPack2 local cell_y = (((y + adj_y) / yPack3 + 0.5) % structureSize) / structureSize * yPack3 local cell_z = (((z + adj_z) + 0.5) % structureSize) / structureSize -- zPack = 1, so can be omitted local iOut = 1 local i, j local canSkip = false for i = 1, #layers do local layer = layers[i] local dy = cell_y - layer.y if dy > -0.71 and dy < 0.71 then -- optimization - don't include points to far away to make a difference. (0.71 comes from sin(45°)) local points = layer.points for j = 1, #points do local point = points[j] local dx = cell_x - point[1] local dz = cell_z - point[3] local distSquared = dx*dx + dy*dy + dz*dz if distSquared < 0.25 then -- optimization - point is inside a sphere, so cannot be a wall edge. (0.25 comes from radius of 0.5 squared) return c_air end distSquaredList[iOut] = distSquared iOut = iOut + 1 end end end -- clear the rest of the array instead of creating a new one to hopefully reduce luajit mem leaks. while distSquaredList[iOut] ~= nil do rawset(distSquaredList, iOut, nil) iOut = iOut + 1 end insertionSort(distSquaredList) local d3_1 = distSquaredList[3] - distSquaredList[1] local d3_2 = distSquaredList[3] - distSquaredList[2] --local d4_1 = distSquaredList[4] - distSquaredList[1] --local d4_3 = distSquaredList[4] - distSquaredList[3] -- Some shape formulas (tuned for a structureSize of 50) -- (d3_1 < 0.05) gives connective lines -- (d3_1 < 0.05 or d3_2 < .02) give fancy elven bridges - prob doesn't need the d3_1 part -- ((d3_1 < 0.05 or d3_2 < .02) and distSquaredList[1] > .3) tapers the fancy connections in the middle -- (d4_3 < 0.03 and d3_2 < 0.03) produces caltrops at intersections -- (d4_1 < 0.1) produces spherish balls at intersections -- The idea is voronoi based - edges in a voronoi diagram are where each nearby point is at equal distance. -- In this case we use squared distances to avoid calculating square roots. if (d3_1 < 0.05 or d3_2 < .02) and distSquaredList[1] > .3 then return c_crystal elseif (distSquaredList[4] - distSquaredList[1]) < 0.08 then return c_glowstone else return c_air end endnether-3.6/mapgen_mantle.lua000066400000000000000000000465141461347743200162060ustar00rootroot00000000000000--[[ Nether mod for minetest This file contains helper functions for generating the Mantle (AKA center region), which are moved into a separate file to keep the size of mapgen.lua manageable. Copyright (C) 2021 Treer Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ]]-- local debugf = nether.debug local mapgen = nether.mapgen local S = nether.get_translator local BASALT_COLUMN_UPPER_LIMIT = mapgen.BASALT_COLUMN_UPPER_LIMIT local BASALT_COLUMN_LOWER_LIMIT = mapgen.BASALT_COLUMN_LOWER_LIMIT -- 2D noise for basalt formations local np_basalt = { offset =-0.85, scale = 1, spread = {x = 46, y = 46, z = 46}, seed = 1000, octaves = 5, persistence = 0.5, lacunarity = 2.6, flags = "eased" } -- Buffers and objects we shouldn't recreate every on_generate local nobj_basalt = nil local nbuf_basalt = {} -- Content ids local c_air = minetest.get_content_id("air") local c_netherrack_deep = minetest.get_content_id("nether:rack_deep") local c_glowstone = minetest.get_content_id("nether:glowstone") local c_lavasea_source = minetest.get_content_id("nether:lava_source") -- same as lava but with staggered animation to look better as an ocean local c_lava_crust = minetest.get_content_id("nether:lava_crust") local c_basalt = minetest.get_content_id("nether:basalt") -- Math funcs local math_max, math_min, math_abs, math_floor = math.max, math.min, math.abs, math.floor -- avoid needing table lookups each time a common math function is invoked function random_unit_vector() return vector.normalize({ x = math.random() - 0.5, y = math.random() - 0.5, z = math.random() - 0.5 }) end -- returns the smallest component in the vector function vector_min(v) return math_min(v.x, math_min(v.y, v.z)) end -- Mantle mapgen functions (AKA Center region) -- Returns (absolute height, fractional distance from ceiling or sea floor) -- the fractional distance from ceiling or sea floor is a value between 0 and 1 (inclusive) -- Note it may find the most relevent sea-level - not necesssarily the one you are closest -- to, since the space above the sea reaches much higher than the depth below the sea. mapgen.find_nearest_lava_sealevel = function(y) -- todo: put oceans near the bottom of chunks to improve ability to generate tunnels to the center -- todo: constrain y to be not near the bounds of the nether -- todo: add some random adj at each level, seeded only by the level height local sealevel = math.floor((y + 100) / 200) * 200 --local sealevel = math.floor((y + 80) / 160) * 160 --local sealevel = math.floor((y + 120) / 240) * 240 local cavern_limits_fraction local height_above_sea = y - sealevel if height_above_sea >= 0 then cavern_limits_fraction = math_min(1, height_above_sea / 95) else -- approaches 1 much faster as the lava sea is shallower than the cavern above it cavern_limits_fraction = math_min(1, -height_above_sea / 40) end return sealevel, cavern_limits_fraction end mapgen.add_basalt_columns = function(data, area, minp, maxp) -- Basalt columns are structures found in lava oceans, and the only way to obtain -- nether basalt. -- Their x, z position is determined by a 2d noise map and a 2d slice of the cave -- noise (taken at lava-sealevel). local x0, y0, z0 = minp.x, math_max(minp.y, nether.DEPTH_FLOOR), minp.z local x1, y1, z1 = maxp.x, math_min(maxp.y, nether.DEPTH_CEILING), maxp.z local yStride = area.ystride local yCaveStride = x1 - x0 + 1 local cavePerlin = mapgen.get_cave_point_perlin() nobj_basalt = nobj_basalt or minetest.get_perlin_map(np_basalt, {x = yCaveStride, y = yCaveStride}) local nvals_basalt = nobj_basalt:get_2d_map_flat({x=minp.x, y=minp.z}, {x=yCaveStride, y=yCaveStride}, nbuf_basalt) local nearest_sea_level, _ = mapgen.find_nearest_lava_sealevel(math_floor((y0 + y1) / 2)) local leeway = mapgen.CENTER_CAVERN_LIMIT * 0.18 for z = z0, z1 do local noise2di = 1 + (z - z0) * yCaveStride for x = x0, x1 do local basaltNoise = nvals_basalt[noise2di] if basaltNoise > 0 then -- a basalt column is here local abs_sealevel_cave_noise = math_abs(cavePerlin:get_3d({x = x, y = nearest_sea_level, z = z})) -- Add Some quick deterministic noise to the column heights -- This is probably not good noise, but it doesn't have to be. local fastNoise = 17 fastNoise = 37 * fastNoise + y0 fastNoise = 37 * fastNoise + z fastNoise = 37 * fastNoise + x fastNoise = 37 * fastNoise + math_floor(basaltNoise * 32) local columnHeight = basaltNoise * 18 + ((fastNoise % 3) - 1) -- columns should drop below sealevel where lava rivers are flowing -- i.e. anywhere abs_sealevel_cave_noise < BASALT_COLUMN_LOWER_LIMIT -- And we'll also have it drop off near the edges of the lava ocean so that -- basalt columns can only be found by the player reaching a lava ocean. local lowerClip = (math_min(math_max(abs_sealevel_cave_noise, BASALT_COLUMN_LOWER_LIMIT - leeway), BASALT_COLUMN_LOWER_LIMIT + leeway) - BASALT_COLUMN_LOWER_LIMIT) / leeway local upperClip = (math_min(math_max(abs_sealevel_cave_noise, BASALT_COLUMN_UPPER_LIMIT - leeway), BASALT_COLUMN_UPPER_LIMIT + leeway) - BASALT_COLUMN_UPPER_LIMIT) / leeway local columnHeightAdj = lowerClip * -upperClip -- all are values between 1 and -1 columnHeight = columnHeight + math_floor(columnHeightAdj * 12 - 12) local vi = area:index(x, y0, z) -- Initial voxelmanip index for y = y0, y1 do -- Y loop first to minimise tcave & lava-sea calculations if y < nearest_sea_level + columnHeight then local id = data[vi] -- Existing node if id == c_lava_crust or id == c_lavasea_source or (id == c_air and y > nearest_sea_level) then -- Avoid letting columns extend beyond the central region. -- (checking node ids saves having to calculate abs_cave_noise_adjusted here -- to test it against CENTER_CAVERN_LIMIT) data[vi] = c_basalt end end vi = vi + yStride end end noise2di = noise2di + 1 end end end -- returns an array of points from pos1 and pos2 which deviate from a straight line -- but which don't venture too close to a chunk boundary function generate_waypoints(pos1, pos2, minp, maxp) local segSize = 10 local maxDeviation = 7 local minDistanceFromChunkWall = 5 local pathVec = vector.subtract(pos2, pos1) local pathVecNorm = vector.normalize(pathVec) local pathLength = vector.distance(pos1, pos2) local minBound = vector.add(minp, minDistanceFromChunkWall) local maxBound = vector.subtract(maxp, minDistanceFromChunkWall) local result = {} result[1] = pos1 local segmentCount = math_floor(pathLength / segSize) for i = 1, segmentCount do local waypoint = vector.add(pos1, vector.multiply(pathVec, i / (segmentCount + 1))) -- shift waypoint a few blocks in a random direction orthogonally to the pathVec, to make the path crooked. local crossProduct repeat crossProduct = vector.normalize(vector.cross(pathVecNorm, random_unit_vector())) until vector.length(crossProduct) > 0 local deviation = vector.multiply(crossProduct, math.random(1, maxDeviation)) waypoint = vector.add(waypoint, deviation) waypoint = { x = math_min(maxBound.x, math_max(minBound.x, waypoint.x)), y = math_min(maxBound.y, math_max(minBound.y, waypoint.y)), z = math_min(maxBound.z, math_max(minBound.z, waypoint.z)) } result[#result + 1] = waypoint end result[#result + 1] = pos2 return result end function excavate_pathway(data, area, nether_pos, center_pos, minp, maxp) local ystride = area.ystride local zstride = area.zstride math.randomseed(nether_pos.x + 10 * nether_pos.y + 100 * nether_pos.z) -- so each tunnel generates deterministically (this doesn't have to be a quality seed) local dist = math_floor(vector.distance(nether_pos, center_pos)) local waypoints = generate_waypoints(nether_pos, center_pos, minp, maxp) -- First pass: record path details local linedata = {} local last_pos = {} local line_index = 1 local first_filled_index, boundary_index, last_filled_index for i = 0, dist do -- Bresenham's line would be good here, but too much lua code local waypointProgress = (#waypoints - 1) * i / dist local segmentIndex = math_min(math_floor(waypointProgress) + 1, #waypoints - 1) -- from the integer portion of waypointProgress local segmentInterp = waypointProgress - (segmentIndex - 1) -- the remaining fractional portion local segmentStart = waypoints[segmentIndex] local segmentVector = vector.subtract(waypoints[segmentIndex + 1], segmentStart) local pos = vector.round(vector.add(segmentStart, vector.multiply(segmentVector, segmentInterp))) if not vector.equals(pos, last_pos) then local vi = area:indexp(pos) local node_id = data[vi] linedata[line_index] = { pos = pos, vi = vi, node_id = node_id } if boundary_index == nil and node_id == c_netherrack_deep then boundary_index = line_index end if node_id == c_air then if boundary_index ~= nil and last_filled_index == nil then last_filled_index = line_index end else if first_filled_index == nil then first_filled_index = line_index end end line_index = line_index + 1 last_pos = pos end end first_filled_index = first_filled_index or 1 last_filled_index = last_filled_index or #linedata boundary_index = boundary_index or last_filled_index -- limit tunnel radius to roughly the closest that startPos or stopPos comes to minp-maxp, so we -- don't end up exceeding minp-maxp and having excavation filled in when the next chunk is generated. local startPos, stopPos = linedata[first_filled_index].pos, linedata[last_filled_index].pos local radiusLimit = vector_min(vector.subtract(startPos, minp)) radiusLimit = math_min(radiusLimit, vector_min(vector.subtract(stopPos, minp))) radiusLimit = math_min(radiusLimit, vector_min(vector.subtract(maxp, startPos))) radiusLimit = math_min(radiusLimit, vector_min(vector.subtract(maxp, stopPos))) if radiusLimit < 4 then -- This is a logic check, ignore it. It could be commented out -- 4 is (79 - 75), and values less than 4 shouldn't be possible if sampling-skip was 10 -- i.e. if sampling-skip was 10 then {5, 15, 25, 35, 45, 55, 65, 75} should be sampled from possible positions 0 to 79 debugf("Error: radiusLimit %s is smaller then half the sampling distance. min %s, max %s, start %s, stop %s", radiusLimit, minp, maxp, startPos, stopPos) end radiusLimit = radiusLimit + 1 -- chunk walls wont be visibly flat if the radius only exceeds it a little ;) -- Second pass: excavate local start_index, stop_index = math_max(1, first_filled_index - 2), math_min(#linedata, last_filled_index + 3) for i = start_index, stop_index, 3 do -- Adjust radius so that tunnels start wide but thin out in the middle local distFromEnds = 1 - math_abs(((start_index + stop_index) / 2) - i) / ((stop_index - start_index) / 2) -- from 0 to 1, with 0 at ends and 1 in the middle -- Have it more flaired at the ends, rather than linear. -- i.e. sizeAdj approaches 1 quickly as distFromEnds increases local distFromMiddle = 1 - distFromEnds local sizeAdj = 1 - (distFromMiddle * distFromMiddle * distFromMiddle) local radius = math_min(radiusLimit, math.random(50 - (25 * sizeAdj), 80 - (45 * sizeAdj)) / 10) local radiusSquared = radius * radius local radiusCeil = math_floor(radius + 0.5) linedata[i].radius = radius -- Needed in third pass linedata[i].distFromEnds = distFromEnds -- Needed in third pass local vi = linedata[i].vi for z = -radiusCeil, radiusCeil do local vi_z = vi + z * zstride for y = -radiusCeil, radiusCeil do local vi_zy = vi_z + y * ystride local xSquaredLimit = radiusSquared - (z * z + y * y) for x = -radiusCeil, radiusCeil do if x * x < xSquaredLimit then data[vi_zy + x] = c_air end end end end end -- Third pass: decorate -- Add glowstones to make tunnels to the mantle easier to find -- https://i.imgur.com/sRA28x7.jpg for i = start_index, stop_index, 3 do if linedata[i].distFromEnds < 0.3 then local glowcount = 0 local radius = linedata[i].radius for _ = 1, 20 do local testPos = vector.round(vector.add(linedata[i].pos, vector.multiply(random_unit_vector(), radius + 0.5))) local vi = area:indexp(testPos) if data[vi] ~= c_air then data[vi] = c_glowstone glowcount = glowcount + 1 --else -- data[vi] = c_debug end if glowcount >= 2 then break end end end end end -- excavates a tunnel connecting the Primary or Secondary region with the mantle / central region -- if a suitable path is found. -- Returns true if successful mapgen.excavate_tunnel_to_center_of_the_nether = function(data, area, nvals_cave, minp, maxp) local result = false local extent = vector.subtract(maxp, minp) local skip = 10 -- sampling rate of 1 in 10 local highest = -1000 local lowest = 1000 local lowest_vi local highest_vi local yCaveStride = maxp.x - minp.x + 1 local zCaveStride = yCaveStride * yCaveStride local vi_offset = area:indexp(vector.add(minp, math_floor(skip / 2))) -- start half the sampling distance away from minp local vi, ni for y = 0, extent.y - 1, skip do local sealevel = mapgen.find_nearest_lava_sealevel(minp.y + y) if minp.y + y > sealevel then -- only create tunnels above sea level for z = 0, extent.z - 1, skip do vi = vi_offset + y * area.ystride + z * area.zstride ni = z * zCaveStride + y * yCaveStride + 1 for x = 0, extent.x - 1, skip do local noise = math_abs(nvals_cave[ni]) if noise < lowest then lowest = noise lowest_vi = vi end if noise > highest then highest = noise highest_vi = vi end ni = ni + skip vi = vi + skip end end end end if lowest < mapgen.CENTER_CAVERN_LIMIT and highest > mapgen.TCAVE + 0.03 then local mantle_y = area:position(lowest_vi).y local _, cavern_limit_distance = mapgen.find_nearest_lava_sealevel(mantle_y) local _, centerRegionLimit_adj = mapgen.get_mapgenblend_adjustments(mantle_y) -- cavern_noise_adj gets added to noise value instead of added to the limit np_noise -- is compared against, so subtract centerRegionLimit_adj instead of adding local cavern_noise_adj = mapgen.CENTER_REGION_LIMIT * (cavern_limit_distance * cavern_limit_distance * cavern_limit_distance) - centerRegionLimit_adj if lowest + cavern_noise_adj < mapgen.CENTER_CAVERN_LIMIT then excavate_pathway(data, area, area:position(highest_vi), area:position(lowest_vi), minp, maxp) result = true end end return result end -- an enumerated list of the different regions in the nether mapgen.RegionEnum = { OVERWORLD = {name = "overworld", desc = S("The Overworld") }, -- Outside the Nether / none of the regions in the Nether POSITIVE = {name = "positive", desc = S("Positive nether") }, -- The classic nether caverns are here - where cavePerlin > 0.6 POSITIVESHELL = {name = "positive shell", desc = S("Shell between positive nether and center region") }, -- the nether side of the wall/buffer area separating classic nether from the mantle CENTER = {name = "center", desc = S("Center/Mantle, inside cavern") }, CENTERSHELL = {name = "center shell", desc = S("Center/Mantle, but outside the caverns") }, -- the mantle side of the wall/buffer area separating the positive and negative regions from the center region NEGATIVE = {name = "negative", desc = S("Negative nether") }, -- Secondary/spare region - where cavePerlin < -0.6 NEGATIVESHELL = {name = "negative shell", desc = S("Shell between negative nether and center region") } -- the spare region side of the wall/buffer area separating the negative region from the mantle } -- Returns (region, noise) where region is a value from mapgen.RegionEnum -- and noise is the unadjusted cave perlin value mapgen.get_region = function(pos) if pos.y > nether.DEPTH_CEILING or pos.y < nether.DEPTH_FLOOR then return mapgen.RegionEnum.OVERWORLD, nil end local caveNoise = mapgen.get_cave_perlin_at(pos) local sealevel, cavern_limit_distance = mapgen.find_nearest_lava_sealevel(pos.y) local tcave_adj, centerRegionLimit_adj = mapgen.get_mapgenblend_adjustments(pos.y) local tcave = mapgen.TCAVE + tcave_adj local tmantle = mapgen.CENTER_REGION_LIMIT + centerRegionLimit_adj -- cavern_noise_adj gets added to noise value instead of added to the limit np_noise -- is compared against, so subtract centerRegionLimit_adj instead of adding local cavern_noise_adj = mapgen.CENTER_REGION_LIMIT * (cavern_limit_distance * cavern_limit_distance * cavern_limit_distance) - centerRegionLimit_adj local region if caveNoise > tcave then region = mapgen.RegionEnum.POSITIVE elseif -caveNoise > tcave then region = mapgen.RegionEnum.NEGATIVE elseif math_abs(caveNoise) < tmantle then if math_abs(caveNoise) + cavern_noise_adj < mapgen.CENTER_CAVERN_LIMIT then region = mapgen.RegionEnum.CENTER else region = mapgen.RegionEnum.CENTERSHELL end elseif caveNoise > 0 then region = mapgen.RegionEnum.POSITIVESHELL else region = mapgen.RegionEnum.NEGATIVESHELL end return region, caveNoise end minetest.register_chatcommand("nether_whereami", { description = S("Describes which region of the nether the player is in"), privs = {debug = true}, func = function(name, param) local player = minetest.get_player_by_name(name) if player == nil then return false, S("Unknown player position") end local playerPos = vector.round(player:get_pos()) local region, caveNoise = mapgen.get_region(playerPos) local seaLevel, cavernLimitDistance = mapgen.find_nearest_lava_sealevel(playerPos.y) local tcave_adj, centerRegionLimit_adj = mapgen.get_mapgenblend_adjustments(playerPos.y) local seaDesc = "" local boundaryDesc = "" local perlinDesc = "" if region ~= mapgen.RegionEnum.OVERWORLD then local seaPos = playerPos.y - seaLevel if seaPos > 0 then seaDesc = S(", @1m above lava-sea level", seaPos) else seaDesc = S(", @1m below lava-sea level", seaPos) end if tcave_adj > 0 then boundaryDesc = S(", approaching y boundary of Nether") end perlinDesc = S("[Perlin @1] ", (math_floor(caveNoise * 1000) / 1000)) end return true, S("@1@2@3@4", perlinDesc, region.desc, seaDesc, boundaryDesc) end } ) nether-3.6/mapgen_nobiomes.lua000066400000000000000000000167661461347743200165470ustar00rootroot00000000000000--[[ Nether mod for minetest "mapgen_nobiomes.lua" is the legacy version of the mapgen, only used in older versions of Minetest or in v6 worlds. "mapgen.lua" is the modern biomes-based Nether mapgen, which requires Minetest v5.1 or greater Copyright (C) 2013 PilzAdam Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ]]-- -- Parameters local NETHER_CEILING = nether.DEPTH_CEILING local NETHER_FLOOR = nether.DEPTH_FLOOR local TCAVE = 0.6 local BLEND = 128 -- 3D noise local np_cave = { offset = 0, scale = 1, spread = {x = 384, y = 128, z = 384}, -- squashed 3:1 seed = 59033, octaves = 5, persist = 0.7, lacunarity = 2.0, --flags = "" } -- Stuff local yblmin = NETHER_FLOOR + BLEND * 2 local yblmax = NETHER_CEILING - BLEND * 2 -- Mapgen dofile(nether.path .. "/mapgen_decorations.lua") -- Initialize noise object, localise noise and data buffers local nobj_cave = nil local nbuf_cave = {} local dbuf = {} -- Content ids local c_air = minetest.get_content_id("air") --local c_stone_with_coal = minetest.get_content_id("default:stone_with_coal") --local c_stone_with_iron = minetest.get_content_id("default:stone_with_iron") local c_stone_with_mese = minetest.get_content_id("default:stone_with_mese") local c_stone_with_diamond = minetest.get_content_id("default:stone_with_diamond") local c_stone_with_gold = minetest.get_content_id("default:stone_with_gold") --local c_stone_with_copper = minetest.get_content_id("default:stone_with_copper") local c_mese = minetest.get_content_id("default:mese") local c_gravel = minetest.get_content_id("default:gravel") local c_dirt = minetest.get_content_id("default:dirt") local c_sand = minetest.get_content_id("default:sand") local c_cobble = minetest.get_content_id("default:cobble") local c_mossycobble = minetest.get_content_id("default:mossycobble") local c_stair_cobble = minetest.get_content_id("stairs:stair_cobble") local c_lava_source = minetest.get_content_id("default:lava_source") local c_lava_flowing = minetest.get_content_id("default:lava_flowing") local c_water_source = minetest.get_content_id("default:water_source") local c_water_flowing = minetest.get_content_id("default:water_flowing") local c_glowstone = minetest.get_content_id("nether:glowstone") local c_nethersand = minetest.get_content_id("nether:sand") local c_netherbrick = minetest.get_content_id("nether:brick") local c_netherrack = minetest.get_content_id("nether:rack") -- On-generated function minetest.register_on_generated(function(minp, maxp, seed) if minp.y > NETHER_CEILING or maxp.y < NETHER_FLOOR then return end local x1 = maxp.x local y1 = math.min(maxp.y, NETHER_CEILING) local z1 = maxp.z local x0 = minp.x local y0 = math.max(minp.y, NETHER_FLOOR) local z0 = minp.z local vm, emin, emax = minetest.get_mapgen_object("voxelmanip") local area = VoxelArea:new{MinEdge = emin, MaxEdge = emax} local data = vm:get_data(dbuf) local x11 = emax.x -- Limits of mapchunk plus mapblock shell local y11 = emax.y local z11 = emax.z local x00 = emin.x local y00 = emin.y local z00 = emin.z local ystride = x1 - x0 + 1 local zstride = ystride * ystride local chulens = {x = ystride, y = ystride, z = ystride} local minposxyz = {x = x0, y = y0, z = z0} nobj_cave = nobj_cave or minetest.get_perlin_map(np_cave, chulens) local nvals_cave = nobj_cave:get_3d_map_flat(minposxyz, nbuf_cave) for y = y00, y11 do -- Y loop first to minimise tcave calculations local tcave local in_chunk_y = false if y >= y0 and y <= y1 then tcave = TCAVE if y > yblmax then tcave = TCAVE + ((y - yblmax) / BLEND) ^ 2 end if y < yblmin then tcave = TCAVE + ((yblmin - y) / BLEND) ^ 2 end in_chunk_y = true end for z = z00, z11 do local vi = area:index(x00, y, z) -- Initial voxelmanip index local ni local in_chunk_yz = in_chunk_y and z >= z0 and z <= z1 for x = x00, x11 do if in_chunk_yz and x == x0 then -- Initial noisemap index ni = (z - z0) * zstride + (y - y0) * ystride + 1 end local in_chunk_yzx = in_chunk_yz and x >= x0 and x <= x1 -- In mapchunk local id = data[vi] -- Existing node -- Cave air, cave liquids and dungeons are overgenerated, -- convert these throughout mapchunk plus shell if id == c_air or -- Air and liquids to air id == c_lava_source or id == c_lava_flowing or id == c_water_source or id == c_water_flowing then data[vi] = c_air -- Dungeons are preserved so we don't need -- to check for cavern in the shell elseif id == c_cobble or -- Dungeons (preserved) to netherbrick id == c_mossycobble or id == c_stair_cobble then data[vi] = c_netherbrick end if in_chunk_yzx then -- In mapchunk if nvals_cave[ni] > tcave then -- Only excavate cavern in mapchunk data[vi] = c_air elseif id == c_mese then -- Mese block to lava data[vi] = c_lava_source elseif id == c_stone_with_gold or -- Precious ores to glowstone id == c_stone_with_mese or id == c_stone_with_diamond then data[vi] = c_glowstone elseif id == c_gravel or -- Blob ore to nethersand id == c_dirt or id == c_sand then data[vi] = c_nethersand else -- All else to netherstone data[vi] = c_netherrack end ni = ni + 1 -- Only increment noise index in mapchunk end vi = vi + 1 end end end vm:set_data(data) minetest.generate_decorations(vm) vm:set_lighting({day = 0, night = 0}, minp, maxp) vm:calc_lighting() vm:update_liquids() vm:write_to_map() end) -- use knowledge of the nether mapgen algorithm to return a suitable ground level for placing a portal. -- player_name is optional, allowing a player to spawn a remote portal in their own protected areas. function nether.find_nether_ground_y(target_x, target_z, start_y, player_name) local nobj_cave_point = minetest.get_perlin(np_cave) local air = 0 -- Consecutive air nodes found local minp_schem, maxp_schem = nether.get_schematic_volume({x = target_x, y = 0, z = target_z}, nil, "nether_portal") local minp = {x = minp_schem.x, y = 0, z = minp_schem.z} local maxp = {x = maxp_schem.x, y = 0, z = maxp_schem.z} for y = start_y, math.max(NETHER_FLOOR + BLEND, start_y - 4096), -1 do local nval_cave = nobj_cave_point:get_3d({x = target_x, y = y, z = target_z}) if nval_cave > TCAVE then -- Cavern air = air + 1 else -- Not cavern, check if 4 nodes of space above if air >= 4 then local portal_y = y + 1 -- Check volume for non-natural nodes minp.y = minp_schem.y + portal_y maxp.y = maxp_schem.y + portal_y if nether.volume_is_natural_and_unprotected(minp, maxp, player_name) then return portal_y else -- Restart search a little lower nether.find_nether_ground_y(target_x, target_z, y - 16, player_name) end else -- Not enough space, reset air to zero air = 0 end end end return math.max(start_y, NETHER_FLOOR + BLEND) -- Fallback end nether-3.6/mod.conf000066400000000000000000000004341461347743200143110ustar00rootroot00000000000000name = nether description = Adds a deep underground realm with different mapgen that you can reach with obsidian portals. depends = stairs, default optional_depends = toolranks, technic, moreblocks, mesecons, loot, dungeon_loot, doc_basics, fire, climate_api, ethereal, xpanes, walls nether-3.6/nether-compressor-recipe.lua000066400000000000000000000023031461347743200203070ustar00rootroot00000000000000local S = minetest.get_translator("nether") technic.register_recipe_type("compressing", { description = S("Compressing") }) function register_compressor_recipe(data) data.time = data.time or 4 technic.register_recipe("compressing", data) end local recipes = { {"nether:rack", "nether:brick",}, {"nether:rack_deep", "nether:brick_deep"}, {"nether:brick 9", "nether:brick_compressed", 12}, {"nether:brick_compressed 9", "nether:nether_lump", 12} } -- clear craft recipe -- But allow brick blocks to be crafted like the other bricks from Minetest Game minetest.clear_craft({ recipe = { {"nether:brick","nether:brick","nether:brick"}, {"nether:brick","nether:brick","nether:brick"}, {"nether:brick","nether:brick","nether:brick"}, } }) minetest.clear_craft({ recipe = { {"nether:brick_compressed","nether:brick_compressed","nether:brick_compressed"}, {"nether:brick_compressed","nether:brick_compressed","nether:brick_compressed"}, {"nether:brick_compressed","nether:brick_compressed","nether:brick_compressed"}, } }) for _, data in pairs(recipes) do register_compressor_recipe({input = {data[1]}, output = data[2], time = data[3]}) end nether-3.6/nether_api.txt000066400000000000000000000072401461347743200155440ustar00rootroot00000000000000Modding/interop guide to Nether =============================== For portals API see portal_api.txt The Nether mod exposes some of its functions and data via the lua global `nether` and `nether.mapgen` * `nether.DEPTH_CEILING`: [read-only] Y value of the top of the Nether. * `nether.DEPTH_FLOOR`: [read-only] Y value of the bottom of the Nether. * `nether.DEPTH_FLOOR_LAYERS`: [writable] Gives the bottom Y of all locations that wish to be considered part of the Nether. DEPTH_FLOOR_LAYERS Allows mods to insert extra layers below the Nether, by knowing where their layer ceiling should start, and letting the layers be included in effects which only happen in the Nether. If a mod wishes to add a layer below the Nether it should read `nether.DEPTH_FLOOR_LAYERS` to find the bottom Y of the Nether and any other layers already under the Nether. The mod should leave a small gap between DEPTH_FLOOR_LAYERS and its ceiling (e.g. use DEPTH_FLOOR_LAYERS - 6 for its ceiling Y, so there is room to shift edge-case biomes), then set `nether.DEPTH_FLOOR_LAYERS` to reflect the mod's floor Y value, and call `shift_existing_biomes()` with DEPTH_FLOOR_LAYERS as the `floor_y` argument. * `nether.NETHER_REALM_ENABLED`: [read-only] Gets the value of the "Enable Nether realm & portal" setting the nether mod exposes in Minetest's "All Settings" -> "Mods" -> "nether" options. When false, the entire nether mapgen is disabled (not run), and the portal to it is not registered. Reasons someone might disable the Nether realm include if a nether-layer mod was to be used as the Nether instead, or if the portal mechanic was desired in a game without the Nether, etc. * `nether.useBiomes`: [read-only] When this is false, the Nether interop functions below are not available (nil). Indicates that the biomes-enabled mapgen is in use. The Nether mod falls back to older mapgen code for v6 maps and old versions of Minetest, the older mapgen code doesn't use biomes and doesn't provide API/interop functions. Mapgen functions available when nether.useBiomes is true -------------------------------------------------------- The following functions are nil if `nether.useBiomes` is false, and also nil if `nether.NETHER_REALM_ENABLED` is false. * `nether.mapgen.shift_existing_biomes(floor_y, ceiling_y)` Move any existing biomes out of the y-range specified by `floor_y` and `ceiling_y`. * `nether.mapgen.get_region(pos)`: Returns two values, (region, noise) where `region` is a value from `nether.mapgen.RegionEnum` and `noise` is the unadjusted cave perlin value. * `nether.mapgen.RegionEnum` values are tables which contain an invariant `name` and a localized `desc`. Current region names include overworld, positive, positive shell, center, center shell, negative, and negative shell. "positive" corresponds to conventional Nether caverns, and "center" corresponds to the Mantle region. * `nether.mapgen.get_cave_point_perlin()`: Returns the PerlinNoise object for the Nether's cavern noise. * `nether.mapgen.get_cave_perlin_at(pos)`: Returns the Nether cavern noise value at a given 3D position. Other mapgen functions ------------------------------------------- If the Nether realm is enabled, then this function will be available regardless of whether `nether.useBiomes` is true: * `nether.find_nether_ground_y(target_x, target_z, start_y, player_name)` Uses knowledge of the nether mapgen algorithm to return a suitable ground level for placing a portal. * `player_name` is optional, allowing a player to spawn a remote portal in their own protected areas.nether-3.6/nodes.lua000066400000000000000000001062301461347743200144770ustar00rootroot00000000000000--[[ Nether mod for minetest Copyright (C) 2013 PilzAdam Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ]]-- local S = nether.get_translator -- Portal/wormhole nodes nether.register_wormhole_node("nether:portal", { description = S("Nether Portal"), post_effect_color = { -- post_effect_color can't be changed dynamically in Minetest like the portal colour is. -- If you need a different post_effect_color then use register_wormhole_node to create -- another wormhole node and set it as the wormhole_node_name in your portaldef. -- Hopefully this colour is close enough to magenta to work with the traditional magenta -- portals, close enough to red to work for a red portal, and also close enough to red to -- work with blue & cyan portals - since blue portals are sometimes portrayed as being red -- from the opposite side / from the inside. a = 160, r = 128, g = 0, b = 80 } }) local portal_animation2 = { name = "nether_portal_alt.png", animation = { type = "vertical_frames", aspect_w = 16, aspect_h = 16, length = 0.5, }, } nether.register_wormhole_node("nether:portal_alt", { description = S("Portal"), tiles = { "nether_transparent.png", "nether_transparent.png", "nether_transparent.png", "nether_transparent.png", portal_animation2, portal_animation2 }, post_effect_color = { -- hopefully blue enough to work with blue portals, and green enough to -- work with cyan portals. a = 120, r = 0, g = 128, b = 188 } }) --== Transmogrification functions ==-- -- Functions enabling selected nodes to be temporarily transformed into other nodes. -- (so the light staff can temporarily turn netherrack into glowstone) -- Swaps the node at `nodePos` with `newNode`, unless `newNode` is nil in which -- case the node is swapped back to its original type. -- `monoSimpleSoundSpec` is optional. -- returns true if a node was transmogrified nether.magicallyTransmogrify_node = function(nodePos, playerName, newNode, monoSimpleSoundSpec, isPermanent) local meta = minetest.get_meta(nodePos) local playerEyePos = nodePos -- fallback value in case the player no longer exists local player = minetest.get_player_by_name(playerName) if player ~= nil then local playerPos = player:get_pos() playerEyePos = vector.add(playerPos, {x = 0, y = 1.5, z = 0}) -- not always the cameraPos, e.g. 3rd person mode. end local oldNode = minetest.get_node(nodePos) if oldNode.name == "air" then -- the node has been mined or otherwise destroyed, abort the operation return false end local oldNodeDef = minetest.registered_nodes[oldNode.name] or minetest.registered_nodes["air"] local specialFXSize = 1 -- a specialFXSize of 1 is for full SFX, 0.5 is half-sized local returningToNormal = newNode == nil if returningToNormal then -- This is the transmogrified node returning back to normal - a more subdued animation specialFXSize = 0.5 -- read what the node used to be from the metadata newNode = { name = meta:get_string("transmogrified_name"), param1 = meta:get_string("transmogrified_param1"), param2 = meta:get_string("transmogrified_param2") } if newNode.name == "" then minetest.log("warning", "nether.magicallyTransmogrify_node() invoked to restore node which wasn't transmogrified") return false end end local soundSpec = monoSimpleSoundSpec if soundSpec == nil and oldNodeDef.sounds ~= nil then soundSpec = oldNodeDef.sounds.dug or oldNodeDef.sounds.dig if soundSpec == "__group" then soundSpec = "default_dig_cracky" end end if soundSpec ~= nil then minetest.sound_play(soundSpec, {pos = nodePos, max_hear_distance = 50}) end -- Start the particlespawner nearer the player's side of the node to create -- more initial occlusion for an illusion of the old node breaking apart / falling away. local dirToPlayer = vector.normalize(vector.subtract(playerEyePos, nodePos)) local impactPos = vector.add(nodePos, vector.multiply(dirToPlayer, 0.5)) local velocity = 1 + specialFXSize minetest.add_particlespawner({ amount = 50 * specialFXSize, time = 0.1, minpos = vector.add(impactPos, -0.3), maxpos = vector.add(impactPos, 0.3), minvel = {x = -velocity, y = -velocity, z = -velocity}, maxvel = {x = velocity, y = 3 * velocity, z = velocity}, -- biased upward to counter gravity in the initial stages minacc = {x=0, y=-10, z=0}, maxacc = {x=0, y=-10, z=0}, minexptime = 1.5 * specialFXSize, maxexptime = 3 * specialFXSize, minsize = 0.5, maxsize = 5, node = {name = oldNodeDef.name}, glow = oldNodeDef.light_source }) if returningToNormal or isPermanent then -- clear the metadata that indicates the node is transformed meta:set_string("transmogrified_name", "") meta:set_int("transmogrified_param1", 0) meta:set_int("transmogrified_param2", 0) else -- save the original node so it can be restored meta:set_string("transmogrified_name", oldNode.name) meta:set_int("transmogrified_param1", oldNode.param1) meta:set_int("transmogrified_param2", oldNode.param2) end minetest.swap_node(nodePos, newNode) return true end local function transmogrified_can_dig (pos, player) if minetest.get_meta(pos):get_string("transmogrified_name") ~= "" then -- This node was temporarily transformed into its current form -- revert it back, rather than allow the player to mine transmogrified nodes. local playerName = "" if player ~= nil then playerName = player:get_player_name() end nether.magicallyTransmogrify_node(pos, playerName) return false end return true end -- Nether nodes minetest.register_node("nether:rack", { description = S("Netherrack"), tiles = {"nether_rack.png"}, is_ground_content = true, -- setting workable_with_nether_tools reduces the wear on nether:pick_nether when mining this node groups = {cracky = 3, level = 2, workable_with_nether_tools = 3}, sounds = default.node_sound_stone_defaults(), }) -- Geode crystals can only be introduced by the biomes-based mapgen, since it requires the -- MT 5.0 world-align texture features. minetest.register_node("nether:geode", { description = S("Nether Beryl"), _doc_items_longdesc = S("Nether geode crystal, found lining the interior walls of Nether geodes"), tiles = {{ name = "nether_geode.png", align_style = "world", scale = 4 }}, is_ground_content = true, groups = {cracky = 3, oddly_breakable_by_hand = 3, nether_crystal = 1}, sounds = default.node_sound_glass_defaults(), }) -- Nether Berylite is a Beryl that can seen in the dark, used to light up the internal structure -- of the geode, so to avoid player confusion we'll just have it drop plain Beryl, and have only -- plain Beryl in the creative inventory. minetest.register_node("nether:geodelite", { description = S("Nether Berylite"), _doc_items_longdesc = S("Nether geode crystal. A crystalline structure with faint glow found inside large Nether geodes"), tiles = {{ name = "nether_geode.png", align_style = "world", scale = 4 }}, light_source = 2, drop = "nether:geode", is_ground_content = true, groups = {cracky = 3, oddly_breakable_by_hand = 3, nether_crystal = 1, not_in_creative_inventory = 1}, sounds = default.node_sound_glass_defaults(), }) if minetest.get_modpath("xpanes") and minetest.global_exists("xpanes") and xpanes.register_pane ~= nil then xpanes.register_pane("nether_crystal_pane", { description = S("Nether Crystal Pane"), textures = { { name = "nether_geode_glass.png", align_style = "world", scale = 2 }, "", "xpanes_edge_obsidian.png" }, inventory_image = "([combine:32x32:-8,-8=nether_geode_glass.png:24,-8=nether_geode_glass.png:-8,24=nether_geode_glass.png:24,24=nether_geode_glass.png)^[resize:16x16^[multiply:#922^default_obsidian_glass.png", wield_image = "([combine:32x32:-8,-8=nether_geode_glass.png:24,-8=nether_geode_glass.png:-8,24=nether_geode_glass.png:24,24=nether_geode_glass.png)^[resize:16x16^[multiply:#922^default_obsidian_glass.png", use_texture_alpha = true, sounds = default.node_sound_glass_defaults(), groups = {snappy=2, cracky=3, oddly_breakable_by_hand=3}, recipe = { {"group:nether_crystal", "group:nether_crystal", "group:nether_crystal"}, {"group:nether_crystal", "group:nether_crystal", "group:nether_crystal"} } }) end -- Deep Netherrack, found in the mantle / central magma layers minetest.register_node("nether:rack_deep", { description = S("Deep Netherrack"), _doc_items_longdesc = S("Netherrack from deep in the mantle"), tiles = {"nether_rack_deep.png"}, is_ground_content = true, -- setting workable_with_nether_tools reduces the wear on nether:pick_nether when mining this node groups = {cracky = 3, level = 2, workable_with_nether_tools = 3}, sounds = default.node_sound_stone_defaults(), }) minetest.register_node("nether:sand", { description = S("Nethersand"), tiles = {"nether_sand.png"}, is_ground_content = true, groups = {crumbly = 3, level = 2, falling_node = 1}, sounds = default.node_sound_gravel_defaults({ footstep = {name = "default_gravel_footstep", gain = 0.45}, }), }) minetest.register_node("nether:glowstone", { description = S("Glowstone"), tiles = {"nether_glowstone.png"}, is_ground_content = true, light_source = 14, paramtype = "light", groups = {cracky = 3, oddly_breakable_by_hand = 3}, sounds = default.node_sound_glass_defaults(), can_dig = transmogrified_can_dig, -- to ensure glowstone temporarily created by the lightstaff can't be kept }) -- Deep glowstone, found in the mantle / central magma layers minetest.register_node("nether:glowstone_deep", { description = S("Deep Glowstone"), tiles = {"nether_glowstone_deep.png"}, is_ground_content = true, light_source = 14, paramtype = "light", groups = {cracky = 3, oddly_breakable_by_hand = 3}, sounds = default.node_sound_glass_defaults(), can_dig = transmogrified_can_dig, -- to ensure glowstone temporarily created by the lightstaff can't be kept }) minetest.register_node("nether:brick", { description = S("Nether Brick"), tiles = {"nether_brick.png"}, is_ground_content = false, groups = {cracky = 2, level = 2}, sounds = default.node_sound_stone_defaults(), }) minetest.register_node("nether:brick_compressed", { description = S("Compressed Netherbrick"), tiles = {"nether_brick_compressed.png"}, groups = {cracky = 3, level = 2}, is_ground_content = false, sounds = default.node_sound_stone_defaults(), }) -- A decorative node which can only be obtained from dungeons or structures minetest.register_node("nether:brick_cracked", { description = S("Cracked Nether Brick"), tiles = {"nether_brick_cracked.png"}, is_ground_content = false, groups = {cracky = 2, level = 2}, sounds = default.node_sound_stone_defaults(), }) minetest.register_node("nether:brick_deep", { description = S("Deep Nether Brick"), tiles = {{ name = "nether_brick_deep.png", align_style = "world", scale = 2 }}, is_ground_content = false, groups = {cracky = 2, level = 2}, sounds = default.node_sound_stone_defaults() }) -- Register fence and rails local fence_texture = "default_fence_overlay.png^nether_brick.png^default_fence_overlay.png^[makealpha:255,126,126" local rail_texture = "default_fence_rail_overlay.png^nether_brick.png^default_fence_rail_overlay.png^[makealpha:255,126,126" default.register_fence("nether:fence_nether_brick", { description = S("Nether Brick Fence"), texture = "nether_brick.png", inventory_image = fence_texture, wield_image = fence_texture, material = "nether:brick", groups = {cracky = 2, level = 2}, sounds = default.node_sound_stone_defaults() }) default.register_fence_rail("nether:fence_rail_nether_brick", { description = S("Nether Brick Fence Rail"), texture = "nether_brick.png", inventory_image = rail_texture, wield_image = rail_texture, material = "nether:brick", groups = {cracky = 2, level = 2}, sounds = default.node_sound_stone_defaults() }) -- Register stair and slab -- Nether bricks can be made into stairs, slabs, inner stairs, and outer stairs stairs.register_stair_and_slab( -- this function also registers inner and outer stairs "nether_brick", -- subname "nether:brick", -- recipeitem {cracky = 2, level = 2}, -- groups {"nether_brick.png"}, -- images S("Nether Stair"), -- desc_stair S("Nether Slab"), -- desc_slab minetest.registered_nodes["nether:brick"].sounds, -- sounds false, -- worldaligntex S("Inner Nether Stair"), -- desc_stair_inner S("Outer Nether Stair") -- desc_stair_outer ) stairs.register_stair_and_slab( -- this function also registers inner and outer stairs "nether_brick_deep", -- subname "nether:brick_deep", -- recipeitem {cracky = 2, level = 2}, -- groups {"nether_brick_deep.png"}, -- images S("Deep Nether Stair"), -- desc_stair S("Deep Nether Slab"), -- desc_slab minetest.registered_nodes["nether:brick_deep"].sounds, -- sounds false, -- worldaligntex S("Inner Deep Nether Stair"), -- desc_stair_inner S("Outer Deep Nether Stair") -- desc_stair_outer ) -- Netherrack can be shaped into stairs, slabs and walls stairs.register_stair( "netherrack", "nether:rack", {cracky = 2, level = 2}, {"nether_rack.png"}, S("Netherrack Stair"), minetest.registered_nodes["nether:rack"].sounds ) stairs.register_slab( -- register a slab without adding inner and outer stairs "netherrack", "nether:rack", {cracky = 2, level = 2}, {"nether_rack.png"}, S("Netherrack Slab"), minetest.registered_nodes["nether:rack"].sounds ) stairs.register_stair( "netherrack_deep", "nether:rack_deep", {cracky = 2, level = 2}, {"nether_rack_deep.png"}, S("Deep Netherrack Stair"), minetest.registered_nodes["nether:rack_deep"].sounds ) stairs.register_slab( -- register a slab without adding inner and outer stairs "netherrack_deep", "nether:rack_deep", {cracky = 2, level = 2}, {"nether_rack_deep.png"}, S("Deep Netherrack Slab"), minetest.registered_nodes["nether:rack_deep"].sounds ) -- Connecting walls if minetest.get_modpath("walls") and minetest.global_exists("walls") and walls.register ~= nil then walls.register("nether:rack_wall", S("A Netherrack Wall"), "nether_rack.png", "nether:rack", minetest.registered_nodes["nether:rack"].sounds) walls.register("nether:rack_deep_wall", S("A Deep Netherrack Wall"), "nether_rack_deep.png", "nether:rack_deep", minetest.registered_nodes["nether:rack_deep"].sounds) end -- StairsPlus if minetest.get_modpath("moreblocks") then -- Registers about 49 different shapes of nether brick, replacing the stairs & slabs registered above. -- (This could also be done for deep nether brick, but I've left that out to avoid a precedent of 49 new -- nodes every time the nether gets a new material. Nether structures won't be able to use them because -- they can't depend on moreblocks) stairsplus:register_all( "nether", "brick", "nether:brick", { description = S("Nether Brick"), groups = {cracky = 2, level = 2}, tiles = {"nether_brick.png"}, sounds = minetest.registered_nodes["nether:brick"].sounds, }) end -- Mantle nodes -- Nether basalt is intended as a valuable material and possible portalstone - an alternative to -- obsidian that's available for other mods to use. -- It cannot be found in the regions of the nether where Nether portals link to, so requires a journey to obtain. minetest.register_node("nether:basalt", { description = S("Nether Basalt"), _doc_items_longdesc = S("Columns of dark basalt found only in magma oceans deep within the Nether."), tiles = { "nether_basalt.png", "nether_basalt.png", "nether_basalt_side.png", "nether_basalt_side.png", "nether_basalt_side.png", "nether_basalt_side.png" }, is_ground_content = true, groups = {cracky = 1, level = 3}, -- set proper digging times and uses, and maybe explosion immune if api handles that on_blast = function() --[[blast proof]] end, sounds = default.node_sound_stone_defaults(), }) -- Potentially a portalstone, but will also be a stepping stone between basalt -- and chiseled basalt. -- It can only be introduced by the biomes-based mapgen, since it requires the -- MT 5.0 world-align texture features. minetest.register_node("nether:basalt_hewn", { description = S("Hewn Basalt"), _doc_items_longdesc = S("A rough cut solid block of Nether Basalt."), tiles = {{ name = "nether_basalt_hewn.png", align_style = "world", scale = 2 }}, inventory_image = minetest.inventorycube( "nether_basalt_hewn.png^[sheet:2x2:0,0", "nether_basalt_hewn.png^[sheet:2x2:0,1", "nether_basalt_hewn.png^[sheet:2x2:1,1" ), is_ground_content = false, groups = {cracky = 1, level = 2}, on_blast = function() --[[blast proof]] end, sounds = default.node_sound_stone_defaults(), }) -- Chiselled basalt is intended as a portalstone - an alternative to obsidian that's -- available for other mods to use. It is crafted from Hewn Basalt. -- It should only be introduced by the biomes-based mapgen, since in future it may -- require the MT 5.0 world-align texture features. minetest.register_node("nether:basalt_chiselled", { description = S("Chiselled Basalt"), _doc_items_longdesc = S("A finely finished block of solid Nether Basalt."), tiles = { "nether_basalt_chiselled_top.png", "nether_basalt_chiselled_top.png" .. "^[transformFY", "nether_basalt_chiselled_side.png", "nether_basalt_chiselled_side.png", "nether_basalt_chiselled_side.png", "nether_basalt_chiselled_side.png" }, inventory_image = minetest.inventorycube( "nether_basalt_chiselled_top.png", "nether_basalt_chiselled_side.png", "nether_basalt_chiselled_side.png" ), paramtype2 = "facedir", is_ground_content = false, groups = {cracky = 1, level = 2}, on_blast = function() --[[blast proof]] end, sounds = default.node_sound_stone_defaults(), }) -- Lava-sea source -- This is a lava source using a different animated texture so that each node -- is out of phase in its animation from its neighbor. This prevents the magma -- ocean from visually clumping together into a patchwork of 16x16 squares. -- It can only be used by the biomes-based mapgen, since it requires the MT 5.0 -- world-align texture features. local lavasea_source = {} local lava_source = minetest.registered_nodes["default:lava_source"] for key, value in pairs(lava_source) do lavasea_source[key] = value end lavasea_source.name = nil lavasea_source.tiles = { { name = "nether_lava_source_animated.png", backface_culling = false, align_style = "world", scale = 2, animation = { type = "vertical_frames", aspect_w = 32, aspect_h = 32, length = 3.0, }, }, { name = "nether_lava_source_animated.png", backface_culling = true, align_style = "world", scale = 2, animation = { type = "vertical_frames", aspect_w = 32, aspect_h = 32, length = 3.0, }, }, } lavasea_source.groups = { not_in_creative_inventory = 1 } -- Avoid having two lava source blocks in the inv. for key, value in pairs(lava_source.groups) do lavasea_source.groups[key] = value end lavasea_source.liquid_alternative_source = "nether:lava_source" lavasea_source.inventory_image = minetest.inventorycube( "nether_lava_source_animated.png^[sheet:2x16:0,0", "nether_lava_source_animated.png^[sheet:2x16:0,1", "nether_lava_source_animated.png^[sheet:2x16:1,1" ) minetest.register_node("nether:lava_source", lavasea_source) -- a place to store the original ABM function so nether.cool_lava() can call it local original_cool_lava_action nether.cool_lava = function(pos, node) local pos_above = {x = pos.x, y = pos.y + 1, z = pos.z} local node_above = minetest.get_node(pos_above) -- Evaporate water sitting above lava, if it's in the Nether. -- (we don't want Nether mod to affect overworld lava mechanics) if minetest.get_item_group(node_above.name, "water") > 0 and pos.y < nether.DEPTH_CEILING and pos.y > nether.DEPTH_FLOOR_LAYERS then -- cools_lava might be a better group to check for, but perhaps there's -- something in that group that isn't a liquid and shouldn't be evaporated? minetest.swap_node(pos_above, {name="air"}) end -- add steam to cooling lava minetest.add_particlespawner({ amount = 20, time = 0.15, minpos = {x=pos.x - 0.4, y=pos.y - 0, z=pos.z - 0.4}, maxpos = {x=pos.x + 0.4, y=pos.y + 0.5, z=pos.z + 0.4}, minvel = {x = -0.5, y = 0.5, z = -0.5}, maxvel = {x = 0.5, y = 1.5, z = 0.5}, minacc = {x = 0, y = 0.1, z = 0}, maxacc = {x = 0, y = 0.2, z = 0}, minexptime = 0.5, maxexptime = 1.3, minsize = 1.5, maxsize = 3.5, texture = "nether_particle_anim4.png", animation = { type = "vertical_frames", aspect_w = 7, aspect_h = 7, length = 1.4, } }) if node.name == "nether:lava_source" or node.name == "nether:lava_crust" then -- use swap_node to avoid triggering the lava_crust's after_destruct minetest.swap_node(pos, {name = "nether:basalt"}) minetest.sound_play("default_cool_lava", {pos = pos, max_hear_distance = 16, gain = 0.25}, true) else -- chain the original ABM action to handle conventional lava original_cool_lava_action(pos, node) end end minetest.register_on_mods_loaded(function() -- register a bucket of Lava-sea source - but make it just the same bucket as default lava. -- (by doing this in register_on_mods_loaded we don't need to declare a soft dependency) if minetest.get_modpath("bucket") and minetest.global_exists("bucket") and type(bucket.liquids) == "table" then local lava_bucket = bucket.liquids["default:lava_source"] if lava_bucket ~= nil then local lavasea_bucket = {} for key, value in pairs(lava_bucket) do lavasea_bucket[key] = value end lavasea_bucket.source = "nether:lava_source" bucket.liquids[lavasea_bucket.source] = lavasea_bucket end end -- include "nether:lava_source" in any "default:lava_source" ABMs local function include_nether_lava(set_of_nodes) if (type(set_of_nodes) == "table") then for _, nodename in pairs(set_of_nodes) do if nodename == "default:lava_source" then -- I'm amazed this works, but it does table.insert(set_of_nodes, "nether:lava_source") break; end end end end for _, abm in pairs(minetest.registered_abms) do include_nether_lava(abm.nodenames) include_nether_lava(abm.neighbors) if abm.label == "Lava cooling" and abm.action ~= nil then -- lets have lava_crust cool as well original_cool_lava_action = abm.action abm.action = nether.cool_lava table.insert(abm.nodenames, "nether:lava_crust") end end for _, lbm in pairs(minetest.registered_lbms) do include_nether_lava(lbm.nodenames) end --minetest.log("minetest.registered_abms" .. dump(minetest.registered_abms)) --minetest.log("minetest.registered_lbms" .. dump(minetest.registered_lbms)) end) -- creates a lava splash, and leaves lava_source in place of the lava_crust local function smash_lava_crust(pos, playsound) local lava_particlespawn_def = { amount = 6, time = 0.1, minpos = {x=pos.x - 0.5, y=pos.y + 0.3, z=pos.z - 0.5}, maxpos = {x=pos.x + 0.5, y=pos.y + 0.5, z=pos.z + 0.5}, minvel = {x = -1.5, y = 1.5, z = -1.5}, maxvel = {x = 1.5, y = 5, z = 1.5}, minacc = {x = 0, y = -10, z = 0}, maxacc = {x = 0, y = -10, z = 0}, minexptime = 1, maxexptime = 1, minsize = .2, maxsize = .8, texture = "^[colorize:#A00:255", glow = 8 } minetest.add_particlespawner(lava_particlespawn_def) lava_particlespawn_def.texture = "^[colorize:#FB0:255" lava_particlespawn_def.maxvel.y = 3 lava_particlespawn_def.glow = 12 minetest.add_particlespawner(lava_particlespawn_def) minetest.set_node(pos, {name = "default:lava_source"}) if math.random(1, 3) == 1 and minetest.registered_nodes["fire:basic_flame"] ~= nil then -- occasionally brief flames will be seen when breaking lava crust local posAbove = {x = pos.x, y = pos.y + 1, z = pos.z} if minetest.get_node(posAbove).name == "air" then minetest.set_node(posAbove, {name = "fire:basic_flame"}) minetest.get_node_timer(posAbove):set(math.random(7, 15) / 10, 0) --[[ commented out because the flame sound plays for too long if minetest.global_exists("fire") and fire.update_player_sound ~= nil then -- The fire mod only updates its sound every 3 seconds, these flames will be -- out by then, so start the sound immediately local players = minetest.get_connected_players() for n = 1, #players do fire.update_player_sound(players[n]) end end]] end end if playsound then minetest.sound_play( "nether_lava_bubble", -- this sample was encoded at 3x speed to reduce .ogg file size -- at the expense of higher frequencies, so pitch it down ~3x {pos = pos, pitch = 0.3, max_hear_distance = 8, gain = 0.4} ) end end -- lava_crust nodes can only be used in the biomes-based mapgen, since they require -- the MT 5.0 world-align texture features. minetest.register_node("nether:lava_crust", { description = S("Lava Crust"), _doc_items_longdesc = S("A thin crust of cooled lava with liquid lava beneath"), _doc_items_usagehelp = S("Lava crust is strong enough to walk on, but still hot enough to inflict burns."), tiles = { { name="nether_lava_crust_animated.png", backface_culling=true, tileable_vertical=true, tileable_horizontal=true, align_style="world", scale=2, animation = { type = "vertical_frames", aspect_w = 32, aspect_h = 32, length = 1.8, }, } }, inventory_image = minetest.inventorycube( "nether_lava_crust_animated.png^[sheet:2x48:0,0", "nether_lava_crust_animated.png^[sheet:2x48:0,1", "nether_lava_crust_animated.png^[sheet:2x48:1,1" ), collision_box = { type = "fixed", fixed = { -- Damage is calculated "starting 0.1 above feet -- and progressing upwards in 1 node intervals", so -- lower this node's collision box by more than 0.1 -- to ensure damage will be taken when standing on -- the node. {-0.5, -0.5, -0.5, 0.5, 0.39, 0.5} }, }, selection_box = { type = "fixed", fixed = { -- Keep the selection box matching the visual node, -- rather than the collision_box. {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5} }, }, after_destruct = function(pos, oldnode) smash_lava_crust(pos, true) end, after_dig_node = function(pos, oldnode, oldmetadata, digger) end, on_blast = function(pos, intensity) smash_lava_crust(pos, false) end, paramtype = "light", light_source = default.LIGHT_MAX - 3, buildable_to = false, walkable = true, is_ground_content = true, drop = { items = {{ -- Allow SilkTouch-esque "pickaxes of preservation" to mine the lava crust intact, if PR #10141 gets merged. tools = {"this line will block early MT versions which don't respect the tool_groups restrictions"}, tool_groups = {{"pickaxe", "preservation"}}, items = {"nether:lava_crust"} }} }, --liquid_viscosity = 7, damage_per_second = 2, groups = {oddly_breakable_by_hand = 3, cracky = 3, explody = 1, igniter = 1}, sounds = default.node_sound_gravel_defaults(), }) -- Fumaroles (Chimney's) local function fumarole_startTimer(pos, timeout_factor) if timeout_factor == nil then timeout_factor = 1 end local next_timeout = (math.random(50, 900) / 10) * timeout_factor minetest.get_meta(pos):set_float("expected_timeout", next_timeout) minetest.get_node_timer(pos):start(next_timeout) end -- Create an LBM to start fumarole node timers minetest.register_lbm({ label = "Start fumarole smoke", name = "nether:start_fumarole", nodenames = {"nether:fumarole"}, run_at_every_load = true, action = function(pos, node) local node_above = minetest.get_node({x = pos.x, y = pos.y + 1, z = pos.z}) if node_above.name == "air" then --and node.param2 % 4 == 0 then fumarole_startTimer(pos) end end }) local function set_fire(pos, extinguish) local posBelow = {x = pos.x, y = pos.y - 1, z = pos.z} if extinguish then if minetest.get_node(pos).name == "fire:permanent_flame" then minetest.set_node(pos, {name="air"}) end if minetest.get_node(posBelow).name == "fire:permanent_flame" then minetest.set_node(posBelow, {name="air"}) end elseif minetest.get_node(posBelow).name == "air" then minetest.set_node(posBelow, {name="fire:permanent_flame"}) elseif minetest.get_node(pos).name == "air" then minetest.set_node(pos, {name="fire:permanent_flame"}) end end local function fumarole_onTimer(pos, elapsed) local expected_timeout = minetest.get_meta(pos):get_float("expected_timeout") if elapsed > expected_timeout + 10 then -- The timer didn't fire when it was supposed to, so the chunk was probably inactive and has -- just been approached again, meaning *every* fumarole's on_timer is about to go off. -- Skip this event and restart the clock for a future random interval. fumarole_startTimer(pos, 1) return false end -- Fumaroles in the Nether can catch fire. -- (if taken to the surface and used as cottage chimneys, they don't catch fire) local inNether = pos.y <= nether.DEPTH and pos.y >= nether.DEPTH_FLOOR_LAYERS local canCatchFire = inNether and minetest.registered_nodes["fire:permanent_flame"] ~= nil local smoke_offset = 0 local timeout_factor = 1 local smoke_time_adj = 1 local posAbove = {x = pos.x, y = pos.y + 1, z = pos.z} local extinguish = inNether and minetest.get_node(posAbove).name ~= "air" if extinguish or (canCatchFire and math.floor(elapsed) % 7 == 0) then if not extinguish then -- fumarole gasses are igniting smoke_offset = 1 timeout_factor = 0.22 -- reduce burning time end set_fire(posAbove, extinguish) set_fire({x = pos.x + 1, y = pos.y + 1, z = pos.z}, extinguish) set_fire({x = pos.x - 1, y = pos.y + 1, z = pos.z}, extinguish) set_fire({x = pos.x, y = pos.y + 1, z = pos.z + 1}, extinguish) set_fire({x = pos.x, y = pos.y + 1, z = pos.z - 1}, extinguish) elseif inNether then if math.floor(elapsed) % 3 == 1 then -- throw up some embers / lava splash local embers_particlespawn_def = { amount = 6, time = 0.1, minpos = {x=pos.x - 0.1, y=pos.y + 0.0, z=pos.z - 0.1}, maxpos = {x=pos.x + 0.1, y=pos.y + 0.2, z=pos.z + 0.1}, minvel = {x = -.5, y = 4.5, z = -.5}, maxvel = {x = .5, y = 7, z = .5}, minacc = {x = 0, y = -10, z = 0}, maxacc = {x = 0, y = -10, z = 0}, minexptime = 1.4, maxexptime = 1.4, minsize = .2, maxsize = .8, texture = "^[colorize:#A00:255", glow = 8 } minetest.add_particlespawner(embers_particlespawn_def) embers_particlespawn_def.texture = "^[colorize:#A50:255" embers_particlespawn_def.maxvel.y = 3 embers_particlespawn_def.glow = 12 minetest.add_particlespawner(embers_particlespawn_def) else -- gas noises minetest.sound_play("nether_fumarole", { pos = pos, max_hear_distance = 60, gain = 0.24, pitch = math.random(35, 95) / 100 }) end else -- we're not in the Nether, so can afford to be a bit more smokey timeout_factor = 0.4 smoke_time_adj = 1.3 end -- let out some smoke minetest.add_particlespawner({ amount = 12 * smoke_time_adj, time = math.random(40, 60) / 10 * smoke_time_adj, minpos = {x=pos.x - 0.2, y=pos.y + smoke_offset, z=pos.z - 0.2}, maxpos = {x=pos.x + 0.2, y=pos.y + smoke_offset, z=pos.z + 0.2}, minvel = {x=0, y=0.7, z=-0}, maxvel = {x=0, y=0.8, z=-0}, minacc = {x=0.0,y=0.0,z=-0}, maxacc = {x=0.0,y=0.1,z=-0}, minexptime = 5, maxexptime = 5.5, minsize = 1.5, maxsize = 7, texture = "nether_smoke_puff.png", }) fumarole_startTimer(pos, timeout_factor) return false end minetest.register_node("nether:fumarole", { description=S("Fumarolic Chimney"), _doc_items_longdesc = S("A vent in the earth emitting steam and gas"), _doc_items_usagehelp = S("Can be repurposed to provide puffs of smoke in a chimney"), tiles = {"nether_rack.png"}, on_timer = fumarole_onTimer, after_place_node = function(pos, placer, itemstack, pointed_thing) fumarole_onTimer(pos, 1) return false end, is_ground_content = true, groups = {cracky = 3, level = 2, fumarole=1}, paramtype = "light", drawtype = "nodebox", node_box = { type = "fixed", fixed = { {-0.5000, -0.5000, -0.5000, -0.2500, 0.5000, 0.5000}, {-0.5000, -0.5000, -0.5000, 0.5000, 0.5000, -0.2500}, {-0.5000, -0.5000, 0.2500, 0.5000, 0.5000, 0.5000}, {0.2500, -0.5000, -0.5000, 0.5000, 0.5000, 0.5000} } }, selection_box = {type = 'fixed', fixed = {-.5, -.5, -.5, .5, .5, .5}} }) minetest.register_node("nether:fumarole_slab", { description=S("Fumarolic Chimney Slab"), _doc_items_longdesc = S("A vent in the earth emitting steam and gas"), _doc_items_usagehelp = S("Can be repurposed to provide puffs of smoke in a chimney"), tiles = {"nether_rack.png"}, is_ground_content = true, on_timer = fumarole_onTimer, after_place_node = function(pos, placer, itemstack, pointed_thing) fumarole_onTimer(pos, 1) return false end, groups = {cracky = 3, level = 2, fumarole=1}, paramtype = "light", drawtype = "nodebox", node_box = { type = "fixed", fixed = { {-0.5000, -0.5000, -0.5000, -0.2500, 0.000, 0.5000}, {-0.5000, -0.5000, -0.5000, 0.5000, 0.000, -0.2500}, {-0.5000, -0.5000, 0.2500, 0.5000, 0.000, 0.5000}, {0.2500, -0.5000, -0.5000, 0.5000, 0.000, 0.5000} } }, selection_box = {type = 'fixed', fixed = {-.5, -.5, -.5, .5, 0, .5}}, collision_box = {type = 'fixed', fixed = {-.5, -.5, -.5, .5, 0, .5}} }) minetest.register_node("nether:fumarole_corner", { description=S("Fumarolic Chimney Corner"), tiles = {"nether_rack.png"}, is_ground_content = true, groups = {cracky = 3, level = 2, fumarole=1}, paramtype = "light", paramtype2 = "facedir", drawtype = "nodebox", node_box = { type = "fixed", fixed = { {-0.2500, -0.5000, 0.5000, 0.000, 0.5000, 0.000}, {-0.5000, -0.5000, 0.2500, 0.000, 0.5000, 0.000}, {-0.5000, -0.5000, 0.2500, 0.000, 0.000, -0.5000}, {0.000, -0.5000, -0.5000, 0.5000, 0.000, 0.5000} } }, selection_box = { type = 'fixed', fixed = { {-.5, -.5, -.5, .5, 0, .5}, {0, 0, .5, -.5, .5, 0}, } } }) -- nether:airlike_darkness is an air node through which light does not propagate. -- Use of it should be avoided when possible as it has the appearance of a lighting bug. -- Fumarole decorations use it to stop the propagation of light from the lava below, -- since engine limitations mean any mesh or nodebox node will light up if it has lava -- below it. local airlike_darkness = {} for k,v in pairs(minetest.registered_nodes["air"]) do airlike_darkness[k] = v end airlike_darkness.paramtype = "none" minetest.register_node("nether:airlike_darkness", airlike_darkness) nether-3.6/portal_api.lua000066400000000000000000003130271461347743200155250ustar00rootroot00000000000000--[[ Portal API for Minetest See portal_api.txt for documentation -- Copyright (C) 2020 Treer Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ]]-- -- setting DEBUG_IGNORE_MODSTORAGE true prevents portals from knowing where other -- portals are, forcing find_realm_anchorpos() etc. to be executed every time. local DEBUG_IGNORE_MODSTORAGE = false nether.registered_portals = {} nether.registered_portals_count = 0 -- Exposes a list of node names that are used as frame nodes by registered portals nether.is_frame_node = {} -- gives the colour values in nether_portals_palette.png that are used by the wormhole colorfacedir -- hardware colouring. nether.portals_palette = { [0] = {r = 128, g = 0, b = 128, asString = "#800080"}, -- traditional/magenta [1] = {r = 0, g = 0, b = 0, asString = "#000000"}, -- black [2] = {r = 19, g = 19, b = 255, asString = "#1313FF"}, -- blue [3] = {r = 55, g = 168, b = 0, asString = "#37A800"}, -- green [4] = {r = 141, g = 237, b = 255, asString = "#8DEDFF"}, -- cyan [5] = {r = 221, g = 0, b = 0, asString = "#DD0000"}, -- red [6] = {r = 255, g = 240, b = 0, asString = "#FFF000"}, -- yellow [7] = {r = 255, g = 255, b = 255, asString = "#FFFFFF"} -- white } if minetest.get_mod_storage == nil then error(nether.modname .. " does not support Minetest versions earlier than 0.4.16", 0) end local S = nether.get_translator nether.portal_destination_not_found_message = S("Mysterious forces prevented you from opening that portal. Please try another location") --[[ Positions ========= p1 & p2 p1 and p2 is the system used by earlier versions of the nether mod, which the portal_api is forwards and backwards compatible with. p1 is the bottom/west/south corner of the portal, and p2 is the opposite corner, together they define the bounding volume for the portal. The value of p1 and p2 is kept in the metadata of every node in the portal WormholePos The location of the node that a portal's target is set to, and a player is teleported to. It can also be used to test whether a portal is active. AnchorPos Introduced by the portal_api. Coordinates for portals are normally given in terms of the AnchorPos. The AnchorPos does not change with portal orientation - portals rotate around the AnchorPos. Ideally an AnchorPos would be near the bottom center of a portal shape, but this is not the case with PortalShape_Traditional to keep comptaibility with earlier versions of the nether mod. Usually an orientation is required with an AnchorPos. Orientation is yaw, either 0 or 90, 0 meaning a portal that faces north/south - i.e. obsidian running east/west. TimerPos The portal_api replaces ABMs with a single node timer per portal, and the TimerPos is the node in which that timer is located. Extra metadata is also kept in the TimerPos node. Portal shapes ============= For the PortalShape_Traditional implementation, p1, p2, anchorPos, wormholdPos and TimerPos are defined as follows: . +--------+--------+--------+--------+ | | Frame | | | | | | p2 | +--------+--------+--------+--------+ | | | | | | | | +--------+ + +--------+ | | Wormhole | | | | | | +--------+ + +--------+ | |Wormhole | | | | Pos | | +--------+--------+--------+--------+ AnchorPos|TimerPos| | | | p1 | | | | +--------+--------+--------+--------+ +X/East or +Z/North -----> A better location for AnchorPos would be directly under WormholePos, as it's more centered and you don't need to know the portal's orientation to find AnchorPos from the WormholePos or vice-versa, however AnchorPos is in the bottom/south/west-corner to keep compatibility with earlier versions of nether mod (which only records portal corners p1 & p2 in the node metadata). ]] local facedir_up, facedir_north, facedir_south, facedir_east, facedir_west, facedir_down = 0, 4, 8, 12, 16, 20 local __ = {name = "air", prob = 0} local AA = {name = "air", prob = 255, force_place = true} local ON = {name = "default:obsidian", facedir = facedir_north + 0, prob = 255, force_place = true} local ON2 = {name = "default:obsidian", facedir = facedir_north + 1, prob = 255, force_place = true} local ON3 = {name = "default:obsidian", facedir = facedir_north + 2, prob = 255, force_place = true} local ON4 = {name = "default:obsidian", facedir = facedir_north + 3, prob = 255, force_place = true} local OS = {name = "default:obsidian", facedir = facedir_south, prob = 255, force_place = true} local OE = {name = "default:obsidian", facedir = facedir_east, prob = 255, force_place = true} local OW = {name = "default:obsidian", facedir = facedir_west, prob = 255, force_place = true} local OU = {name = "default:obsidian", facedir = facedir_up + 0, prob = 255, force_place = true} local OU2 = {name = "default:obsidian", facedir = facedir_up + 1, prob = 255, force_place = true} local OU3 = {name = "default:obsidian", facedir = facedir_up + 2, prob = 255, force_place = true} local OU4 = {name = "default:obsidian", facedir = facedir_up + 3, prob = 255, force_place = true} local OD = {name = "default:obsidian", facedir = facedir_down, prob = 255, force_place = true} -- facedirNodeList is a list of node references which should have their facedir value copied into -- param2 before placing a schematic. The facedir values will only be copied when the portal's frame -- node has a paramtype2 of "facedir" or "colorfacedir". -- Having schematics provide this list avoids needing to check every node in the schematic volume. local facedirNodeList = {ON, ON2, ON3, ON4, OS, OE, OW, OU, OU2, OU3, OU4, OD} -- This object defines a portal's shape, segregating the shape logic code from portal behaviour code. -- You can create a new "PortalShape" definition object which implements the same -- functions if you wish to register a custom shaped portal in register_portal(). Examples of other -- shapes follow after PortalShape_Traditional. -- Since it's symmetric, this PortalShape definition has only implemented orientations of 0 and 90 nether.PortalShape_Traditional = { name = "Traditional", size = vector.new(4, 5, 1), -- size of the portal, and not necessarily the size of the schematic, -- which may clear area around the portal. is_horizontal = false, -- whether the wormhole is a vertical or horizontal surface diagram_image = { image = "nether_book_diagram_traditional.png", -- The diagram to be shown in the Book of Portals width = 142, height = 305 }, -- returns the coords for minetest.place_schematic() that will place the schematic on the anchorPos get_schematicPos_from_anchorPos = function(anchorPos, orientation) assert(orientation, "no orientation passed") if orientation == 0 then return {x = anchorPos.x, y = anchorPos.y, z = anchorPos.z - 2} else return {x = anchorPos.x - 2, y = anchorPos.y, z = anchorPos.z } end end, get_wormholePos_from_anchorPos = function(anchorPos, orientation) assert(orientation, "no orientation passed") if orientation == 0 then return {x = anchorPos.x + 1, y = anchorPos.y + 1, z = anchorPos.z } else return {x = anchorPos.x, y = anchorPos.y + 1, z = anchorPos.z + 1} end end, get_anchorPos_from_wormholePos = function(wormholePos, orientation) assert(orientation, "no orientation passed") if orientation == 0 then return {x = wormholePos.x - 1, y = wormholePos.y - 1, z = wormholePos.z } else return {x = wormholePos.x, y = wormholePos.y - 1, z = wormholePos.z - 1} end end, -- p1 and p2 are used to keep maps compatible with earlier versions of this mod. -- p1 is the bottom/west/south corner of the portal, and p2 is the opposite corner, together -- they define the bounding volume for the portal. get_p1_and_p2_from_anchorPos = function(self, anchorPos, orientation) assert(orientation, "no orientation passed") assert(self ~= nil and self.name == nether.PortalShape_Traditional.name, "Must pass self as first argument, or use shape:func() instead of shape.func()") local p1 = anchorPos -- PortalShape_Traditional puts the anchorPos at p1 for backwards&forwards compatibility local p2 if orientation == 0 then p2 = {x = p1.x + self.size.x - 1, y = p1.y + self.size.y - 1, z = p1.z } else p2 = {x = p1.x, y = p1.y + self.size.y - 1, z = p1.z + self.size.x - 1} end return p1, p2 end, get_anchorPos_and_orientation_from_p1_and_p2 = function(p1, p2) if p1.z == p2.z then return p1, 0 elseif p1.x == p2.x then return p1, 90 else -- this KISS implementation will break you've made a 3D PortalShape definition minetest.log("error", "get_anchorPos_and_orientation_from_p1_and_p2 failed on p1=" .. minetest.pos_to_string(p1) .. " p2=" .. minetest.pos_to_string(p2)) end end, -- returns true if function was applied to all frame nodes apply_func_to_frame_nodes = function(anchorPos, orientation, func) -- a 4x5 portal is small enough that hardcoded positions is simpler that procedural code local shortCircuited if orientation == 0 then -- use short-circuiting of boolean evaluation to allow func() to cause an abort by returning true shortCircuited = func({x = anchorPos.x + 0, y = anchorPos.y, z = anchorPos.z}) or func({x = anchorPos.x + 1, y = anchorPos.y, z = anchorPos.z}) or func({x = anchorPos.x + 2, y = anchorPos.y, z = anchorPos.z}) or func({x = anchorPos.x + 3, y = anchorPos.y, z = anchorPos.z}) or func({x = anchorPos.x + 0, y = anchorPos.y + 4, z = anchorPos.z}) or func({x = anchorPos.x + 1, y = anchorPos.y + 4, z = anchorPos.z}) or func({x = anchorPos.x + 2, y = anchorPos.y + 4, z = anchorPos.z}) or func({x = anchorPos.x + 3, y = anchorPos.y + 4, z = anchorPos.z}) or func({x = anchorPos.x, y = anchorPos.y + 1, z = anchorPos.z}) or func({x = anchorPos.x, y = anchorPos.y + 2, z = anchorPos.z}) or func({x = anchorPos.x, y = anchorPos.y + 3, z = anchorPos.z}) or func({x = anchorPos.x + 3, y = anchorPos.y + 1, z = anchorPos.z}) or func({x = anchorPos.x + 3, y = anchorPos.y + 2, z = anchorPos.z}) or func({x = anchorPos.x + 3, y = anchorPos.y + 3, z = anchorPos.z}) else shortCircuited = func({x = anchorPos.x, y = anchorPos.y, z = anchorPos.z + 0}) or func({x = anchorPos.x, y = anchorPos.y, z = anchorPos.z + 1}) or func({x = anchorPos.x, y = anchorPos.y, z = anchorPos.z + 2}) or func({x = anchorPos.x, y = anchorPos.y, z = anchorPos.z + 3}) or func({x = anchorPos.x, y = anchorPos.y + 4, z = anchorPos.z + 0}) or func({x = anchorPos.x, y = anchorPos.y + 4, z = anchorPos.z + 1}) or func({x = anchorPos.x, y = anchorPos.y + 4, z = anchorPos.z + 2}) or func({x = anchorPos.x, y = anchorPos.y + 4, z = anchorPos.z + 3}) or func({x = anchorPos.x, y = anchorPos.y + 1, z = anchorPos.z }) or func({x = anchorPos.x, y = anchorPos.y + 2, z = anchorPos.z }) or func({x = anchorPos.x, y = anchorPos.y + 3, z = anchorPos.z }) or func({x = anchorPos.x, y = anchorPos.y + 1, z = anchorPos.z + 3}) or func({x = anchorPos.x, y = anchorPos.y + 2, z = anchorPos.z + 3}) or func({x = anchorPos.x, y = anchorPos.y + 3, z = anchorPos.z + 3}) end return not shortCircuited end, -- returns true if function was applied to all wormhole nodes apply_func_to_wormhole_nodes = function(anchorPos, orientation, func) local shortCircuited if orientation == 0 then local wormholePos = {x = anchorPos.x + 1, y = anchorPos.y + 1, z = anchorPos.z} -- use short-circuiting of boolean evaluation to allow func() to cause an abort by returning true shortCircuited = func({x = wormholePos.x + 0, y = wormholePos.y + 0, z = wormholePos.z}) or func({x = wormholePos.x + 1, y = wormholePos.y + 0, z = wormholePos.z}) or func({x = wormholePos.x + 0, y = wormholePos.y + 1, z = wormholePos.z}) or func({x = wormholePos.x + 1, y = wormholePos.y + 1, z = wormholePos.z}) or func({x = wormholePos.x + 0, y = wormholePos.y + 2, z = wormholePos.z}) or func({x = wormholePos.x + 1, y = wormholePos.y + 2, z = wormholePos.z}) else local wormholePos = {x = anchorPos.x, y = anchorPos.y + 1, z = anchorPos.z + 1} shortCircuited = func({x = wormholePos.x, y = wormholePos.y + 0, z = wormholePos.z + 0}) or func({x = wormholePos.x, y = wormholePos.y + 0, z = wormholePos.z + 1}) or func({x = wormholePos.x, y = wormholePos.y + 1, z = wormholePos.z + 0}) or func({x = wormholePos.x, y = wormholePos.y + 1, z = wormholePos.z + 1}) or func({x = wormholePos.x, y = wormholePos.y + 2, z = wormholePos.z + 0}) or func({x = wormholePos.x, y = wormholePos.y + 2, z = wormholePos.z + 1}) end return not shortCircuited end, -- Check for whether the portal is blocked in, and if so then provide a safe way -- on one side for the player to step out of the portal. Suggest including a roof -- incase the portal was blocked with lava flowing from above. -- If portal can appear in mid-air then can also check for that and add a platform. disable_portal_trap = function(anchorPos, orientation) assert(orientation, "no orientation passed") -- Not implemented yet. It may not need to be implemented because if you -- wait in a portal long enough you teleport again. So a trap portal would have to link -- to one of two blocked-in portals which link to each other - which is possible, but -- quite extreme. end, schematic = { size = {x = 4, y = 5, z = 5}, data = { -- note that data is upside down __,__,__,__, AA,AA,AA,AA, AA,AA,AA,AA, AA,AA,AA,AA, AA,AA,AA,AA, __,__,__,__, AA,AA,AA,AA, AA,AA,AA,AA, AA,AA,AA,AA, AA,AA,AA,AA, ON,OW,OE,ON2, OU,AA,AA,OU, OU,AA,AA,OU, OU,AA,AA,OU, ON4,OE,OW,ON3, __,__,__,__, AA,AA,AA,AA, AA,AA,AA,AA, AA,AA,AA,AA, AA,AA,AA,AA, __,__,__,__, AA,AA,AA,AA, AA,AA,AA,AA, AA,AA,AA,AA, AA,AA,AA,AA, }, facedirNodes = facedirNodeList } } -- End of PortalShape_Traditional class -- Example alternative PortalShape nether.PortalShape_Circular = { name = "Circular", size = vector.new(7, 7, 1), -- size of the portal, and not necessarily the size of the schematic, -- which may clear area around the portal. is_horizontal = false, -- whether the wormhole is a vertical or horizontal surface diagram_image = { image = "nether_book_diagram_circular.png", -- The diagram to be shown in the Book of Portals width = 149, height = 243 }, -- returns the coords for minetest.place_schematic() that will place the schematic on the anchorPos get_schematicPos_from_anchorPos = function(anchorPos, orientation) assert(orientation, "no orientation passed") if orientation == 0 then return {x = anchorPos.x - 3, y = anchorPos.y, z = anchorPos.z - 3} else return {x = anchorPos.x - 3, y = anchorPos.y, z = anchorPos.z - 3 } end end, get_wormholePos_from_anchorPos = function(anchorPos, orientation) -- wormholePos is the node above anchorPos return {x = anchorPos.x, y = anchorPos.y + 1, z = anchorPos.z} end, get_anchorPos_from_wormholePos = function(wormholePos, orientation) -- wormholePos is the node above anchorPos return {x = wormholePos.x, y = wormholePos.y - 1, z = wormholePos.z} end, -- p1 and p2 are used to keep maps compatible with earlier versions of this mod. -- p1 is the bottom/west/south corner of the portal, and p2 is the opposite corner, together -- they define the bounding volume for the portal. get_p1_and_p2_from_anchorPos = function(self, anchorPos, orientation) assert(orientation, "no orientation passed") assert(self ~= nil and self.name == nether.PortalShape_Circular.name, "Must pass self as first argument, or use shape:func() instead of shape.func()") local p1 local p2 if orientation == 0 then p1 = {x = anchorPos.x - 3, y = anchorPos.y, z = anchorPos.z } p2 = {x = p1.x + self.size.x - 1, y = p1.y + self.size.y - 1, z = p1.z } else p1 = {x = anchorPos.x, y = anchorPos.y, z = anchorPos.z - 3 } p2 = {x = p1.x, y = p1.y + self.size.y - 1, z = p1.z + self.size.x - 1} end return p1, p2 end, get_anchorPos_and_orientation_from_p1_and_p2 = function(p1, p2) if p1.z == p2.z then return {x= p1.x + 3, y = p1.y, z = p1.z }, 0 elseif p1.x == p2.x then return {x= p1.x, y = p1.y, z = p1.z + 3}, 90 end end, apply_func_to_frame_nodes = function(anchorPos, orientation, func) local shortCircuited if orientation == 0 then -- use short-circuiting of boolean evaluation to allow func() to cause an abort by returning true shortCircuited = func({x = anchorPos.x + 0, y = anchorPos.y + 0, z = anchorPos.z}) or func({x = anchorPos.x + 1, y = anchorPos.y + 0, z = anchorPos.z}) or func({x = anchorPos.x - 1, y = anchorPos.y + 0, z = anchorPos.z}) or func({x = anchorPos.x + 2, y = anchorPos.y + 1, z = anchorPos.z}) or func({x = anchorPos.x - 2, y = anchorPos.y + 1, z = anchorPos.z}) or func({x = anchorPos.x + 3, y = anchorPos.y + 2, z = anchorPos.z}) or func({x = anchorPos.x - 3, y = anchorPos.y + 2, z = anchorPos.z}) or func({x = anchorPos.x + 3, y = anchorPos.y + 3, z = anchorPos.z}) or func({x = anchorPos.x - 3, y = anchorPos.y + 3, z = anchorPos.z}) or func({x = anchorPos.x + 3, y = anchorPos.y + 4, z = anchorPos.z}) or func({x = anchorPos.x - 3, y = anchorPos.y + 4, z = anchorPos.z}) or func({x = anchorPos.x + 2, y = anchorPos.y + 5, z = anchorPos.z}) or func({x = anchorPos.x - 2, y = anchorPos.y + 5, z = anchorPos.z}) or func({x = anchorPos.x + 1, y = anchorPos.y + 6, z = anchorPos.z}) or func({x = anchorPos.x - 1, y = anchorPos.y + 6, z = anchorPos.z}) or func({x = anchorPos.x + 0, y = anchorPos.y + 6, z = anchorPos.z}) else shortCircuited = func({x = anchorPos.x, y = anchorPos.y + 0, z = anchorPos.z + 0}) or func({x = anchorPos.x, y = anchorPos.y + 0, z = anchorPos.z + 1}) or func({x = anchorPos.x, y = anchorPos.y + 0, z = anchorPos.z - 1}) or func({x = anchorPos.x, y = anchorPos.y + 1, z = anchorPos.z + 2}) or func({x = anchorPos.x, y = anchorPos.y + 1, z = anchorPos.z - 2}) or func({x = anchorPos.x, y = anchorPos.y + 2, z = anchorPos.z + 3}) or func({x = anchorPos.x, y = anchorPos.y + 2, z = anchorPos.z - 3}) or func({x = anchorPos.x, y = anchorPos.y + 3, z = anchorPos.z + 3}) or func({x = anchorPos.x, y = anchorPos.y + 3, z = anchorPos.z - 3}) or func({x = anchorPos.x, y = anchorPos.y + 4, z = anchorPos.z + 3}) or func({x = anchorPos.x, y = anchorPos.y + 4, z = anchorPos.z - 3}) or func({x = anchorPos.x, y = anchorPos.y + 5, z = anchorPos.z + 2}) or func({x = anchorPos.x, y = anchorPos.y + 5, z = anchorPos.z - 2}) or func({x = anchorPos.x, y = anchorPos.y + 6, z = anchorPos.z + 1}) or func({x = anchorPos.x, y = anchorPos.y + 6, z = anchorPos.z - 1}) or func({x = anchorPos.x, y = anchorPos.y + 6, z = anchorPos.z + 0}) end return not shortCircuited end, -- returns true if function was applied to all wormhole nodes apply_func_to_wormhole_nodes = function(anchorPos, orientation, func) local xRange = 2 local zRange = 0 if orientation ~= 0 then xRange = 0 zRange = 2 end local xEdge, yEdge, zEdge local pos = {} for x = -xRange, xRange do pos.x = anchorPos.x + x xEdge = x == -xRange or x == xRange for z = -zRange, zRange do zEdge = z == -zRange or z == zRange pos.z = anchorPos.z + z for y = 1, 5 do yEdge = y == 1 or y == 5 if not (yEdge and xEdge and zEdge) then pos.y = anchorPos.y + y if func(pos) then -- func() caused an abort by returning true return false end end end end end return true end, -- Check for whether the portal is blocked in, and if so then provide a safe way -- on one side for the player to step out of the portal. Suggest including a roof -- incase the portal was blocked with lava flowing from above. -- If portal can appear in mid-air then can also check for that and add a platform. disable_portal_trap = function(anchorPos, orientation) assert(orientation, "no orientation passed") -- Not implemented. end, schematic = { size = {x = 7, y = 7, z = 7}, data = { -- note that data is upside down __,__,__,__,__,__,__, __,__,__,__,__,__,__, __,__,AA,AA,AA,__,__, __,__,AA,AA,AA,__,__, __,__,AA,AA,AA,__,__, __,__,__,__,__,__,__, __,__,__,__,__,__,__, __,__,__,__,__,__,__, __,AA,AA,AA,AA,AA,__, __,AA,AA,AA,AA,AA,__, __,AA,AA,AA,AA,AA,__, __,AA,AA,AA,AA,AA,__, __,AA,AA,AA,AA,AA,__, __,__,__,__,__,__,__, __,__,__,__,__,__,__, __,AA,AA,AA,AA,AA,__, AA,AA,AA,AA,AA,AA,AA, AA,AA,AA,AA,AA,AA,AA, AA,AA,AA,AA,AA,AA,AA, __,AA,AA,AA,AA,AA,__, __,__,AA,AA,AA,__,__, __,__,OW,OW,OW,__,__, __,ON,AA,AA,AA,ON2,__, OU,AA,AA,AA,AA,AA,OD, OU,AA,AA,AA,AA,AA,OD, OU,AA,AA,AA,AA,AA,OD, __,ON4,AA,AA,AA,ON3,__, __,__,OE,OE,OE,__,__, __,__,__,__,__,__,__, __,AA,AA,AA,AA,AA,__, AA,AA,AA,AA,AA,AA,AA, AA,AA,AA,AA,AA,AA,AA, AA,AA,AA,AA,AA,AA,AA, __,AA,AA,AA,AA,AA,__, __,__,AA,AA,AA,__,__, __,__,__,__,__,__,__, __,AA,AA,AA,AA,AA,__, __,AA,AA,AA,AA,AA,__, __,AA,AA,AA,AA,AA,__, __,AA,AA,AA,AA,AA,__, __,AA,AA,AA,AA,AA,__, __,__,__,__,__,__,__, __,__,__,__,__,__,__, __,__,__,__,__,__,__, __,__,AA,AA,AA,__,__, __,__,AA,AA,AA,__,__, __,__,AA,AA,AA,__,__, __,__,__,__,__,__,__, __,__,__,__,__,__,__, }, facedirNodes = facedirNodeList } } -- End of PortalShape_Circular class -- Example alternative PortalShape -- This platform shape is symmetrical around the y-axis, so the orientation value never matters. nether.PortalShape_Platform = { name = "Platform", size = vector.new(5, 2, 5), -- size of the portal, and not necessarily the size of the schematic, -- which may clear area around the portal. is_horizontal = true, -- whether the wormhole is a vertical or horizontal surface diagram_image = { image = "nether_book_diagram_platform.png", -- The diagram to be shown in the Book of Portals width = 200, height = 130 }, -- returns the coords for minetest.place_schematic() that will place the schematic on the anchorPos get_schematicPos_from_anchorPos = function(anchorPos, orientation) return {x = anchorPos.x - 2, y = anchorPos.y, z = anchorPos.z - 2} end, get_wormholePos_from_anchorPos = function(anchorPos, orientation) -- wormholePos is the node above anchorPos return {x = anchorPos.x, y = anchorPos.y + 1, z = anchorPos.z} end, get_anchorPos_from_wormholePos = function(wormholePos, orientation) -- wormholePos is the node above anchorPos return {x = wormholePos.x, y = wormholePos.y - 1, z = wormholePos.z} end, -- p1 and p2 are used to keep maps compatible with earlier versions of this mod. -- p1 is the bottom/west/south corner of the portal, and p2 is the opposite corner, together -- they define the bounding volume for the portal. get_p1_and_p2_from_anchorPos = function(self, anchorPos, orientation) assert(self ~= nil and self.name == nether.PortalShape_Platform.name, "Must pass self as first argument, or use shape:func() instead of shape.func()") local p1 = {x = anchorPos.x - 2, y = anchorPos.y, z = anchorPos.z - 2} local p2 = {x = anchorPos.x + 2, y = anchorPos.y + 1, z = anchorPos.z + 2} return p1, p2 end, get_anchorPos_and_orientation_from_p1_and_p2 = function(p1, p2) return {x= p1.x + 2, y = p1.y, z = p1.z + 2}, 0 end, apply_func_to_frame_nodes = function(anchorPos, orientation, func) local shortCircuited local yPlus1 = anchorPos.y + 1 -- use short-circuiting of boolean evaluation to allow func() to cause an abort by returning true shortCircuited = func({x = anchorPos.x - 2, y = yPlus1, z = anchorPos.z - 1}) or func({x = anchorPos.x + 2, y = yPlus1, z = anchorPos.z - 1}) or func({x = anchorPos.x - 2, y = yPlus1, z = anchorPos.z }) or func({x = anchorPos.x + 2, y = yPlus1, z = anchorPos.z }) or func({x = anchorPos.x - 2, y = yPlus1, z = anchorPos.z + 1}) or func({x = anchorPos.x + 2, y = yPlus1, z = anchorPos.z + 1}) or func({x = anchorPos.x - 1, y = yPlus1, z = anchorPos.z - 2}) or func({x = anchorPos.x - 1, y = yPlus1, z = anchorPos.z + 2}) or func({x = anchorPos.x , y = yPlus1, z = anchorPos.z - 2}) or func({x = anchorPos.x , y = yPlus1, z = anchorPos.z + 2}) or func({x = anchorPos.x + 1, y = yPlus1, z = anchorPos.z - 2}) or func({x = anchorPos.x + 1, y = yPlus1, z = anchorPos.z + 2}) or func({x = anchorPos.x - 1, y = anchorPos.y, z = anchorPos.z - 1}) or func({x = anchorPos.x - 1, y = anchorPos.y, z = anchorPos.z }) or func({x = anchorPos.x - 1, y = anchorPos.y, z = anchorPos.z + 1}) or func({x = anchorPos.x , y = anchorPos.y, z = anchorPos.z - 1}) or func({x = anchorPos.x , y = anchorPos.y, z = anchorPos.z }) or func({x = anchorPos.x , y = anchorPos.y, z = anchorPos.z + 1}) or func({x = anchorPos.x + 1, y = anchorPos.y, z = anchorPos.z - 1}) or func({x = anchorPos.x + 1, y = anchorPos.y, z = anchorPos.z }) or func({x = anchorPos.x + 1, y = anchorPos.y, z = anchorPos.z + 1}) return not shortCircuited end, -- returns true if function was applied to all wormhole nodes apply_func_to_wormhole_nodes = function(anchorPos, orientation, func) local shortCircuited local yPlus1 = anchorPos.y + 1 -- use short-circuiting of boolean evaluation to allow func() to cause an abort by returning true shortCircuited = func({x = anchorPos.x - 1, y = yPlus1, z = anchorPos.z - 1}) or func({x = anchorPos.x - 1, y = yPlus1, z = anchorPos.z }) or func({x = anchorPos.x - 1, y = yPlus1, z = anchorPos.z + 1}) or func({x = anchorPos.x , y = yPlus1, z = anchorPos.z - 1}) or func({x = anchorPos.x , y = yPlus1, z = anchorPos.z }) or func({x = anchorPos.x , y = yPlus1, z = anchorPos.z + 1}) or func({x = anchorPos.x + 1, y = yPlus1, z = anchorPos.z - 1}) or func({x = anchorPos.x + 1, y = yPlus1, z = anchorPos.z }) or func({x = anchorPos.x + 1, y = yPlus1, z = anchorPos.z + 1}) return not shortCircuited end, -- Check for suffocation disable_portal_trap = function(anchorPos, orientation) -- Not implemented. end, schematic = { size = {x = 5, y = 5, z = 5}, data = { -- note that data is upside down __,__,__,__,__, OU4,OW,OW,OW,OU3, __,AA,AA,AA,__, __,AA,AA,AA,__, __,__,__,__,__, __,OU4,OW,OU3,__, ON,AA,AA,AA,OS, AA,AA,AA,AA,AA, AA,AA,AA,AA,AA, __,AA,AA,AA,__, __,ON,OD,OS,__, ON,AA,AA,AA,OS, AA,AA,AA,AA,AA, AA,AA,AA,AA,AA, __,AA,AA,AA,__, __,OU,OE,OU2,__, ON,AA,AA,AA,OS, AA,AA,AA,AA,AA, AA,AA,AA,AA,AA, __,AA,AA,AA,__, __,__,__,__,__, OU,OE,OE,OE,OU2, __,AA,AA,AA,__, __,AA,AA,AA,__, __,__,__,__,__, }, facedirNodes = facedirNodeList } } -- End of PortalShape_Platform class --====================================================-- --======== End of PortalShape implementations ========-- --====================================================-- -- Portal implementation functions -- -- =============================== -- local debugf = nether.debug local ignition_item_name local mod_storage = minetest.get_mod_storage() local meseconsAvailable = minetest.get_modpath("mesecon") ~= nil and minetest.global_exists("mesecon") local book_added_as_treasure = false local function get_timerPos_from_p1_and_p2(p1, p2) -- Pick a frame node for the portal's timer. -- -- The timer event will need to know the portal definition, which can be determined by -- what the portal frame is made from, so the timer node should be on the frame. -- The timer event will also need to know its portal orientation, but unless someone -- makes a cubic portal shape, orientation can be determined from p1 and p2 in the node's -- metadata (frame nodes don't have orientation set in param2 like wormhole nodes do). -- -- We shouldn't pick p1 or p2 as it's possible for two orthogonal portals to share -- the same p1, etc. - or at least it was - there's code to try to stop that now. -- -- I'll pick the bottom center node of the portal, since that works for rectangular portals -- and if someone want to make a circular portal then that positon will still likely be part -- of the frame. return { x = math.floor((p1.x + p2.x) / 2), y = p1.y, z = math.floor((p1.z + p2.z) / 2), } end -- orientation is the yaw rotation degrees passed to place_schematic: 0, 90, 180, or 270 -- color is a value from 0 to 7 corresponding to the color of pixels in nether_portals_palette.png -- portal_is_horizontal is a bool indicating whether the portal lies flat or stands vertically local function get_colorfacedir_from_color_and_orientation(color, orientation, portal_is_horizontal) assert(orientation, "no orientation passed") local axis_direction, rotation local dir = math.floor((orientation % 360) / 90 + 0.5) -- if the portal is vertical then node axis direction will be +Y (up) and portal orientation -- will set the node's rotation. -- if the portal is horizontal then the node axis direction reflects the yaw orientation and -- the node's rotation will be whatever's needed to keep the texture horizontal (either 0 or 1) if portal_is_horizontal then if dir == 0 then axis_direction = 1 end -- North if dir == 1 then axis_direction = 3 end -- East if dir == 2 then axis_direction = 2 end -- South if dir == 3 then axis_direction = 4 end -- West rotation = math.floor(axis_direction / 2); -- a rotation is only needed if axis_direction is east or west else axis_direction = 0 -- 0 is up, or +Y rotation = dir end -- wormhole nodes have a paramtype2 of colorfacedir, which means the -- high 3 bits are palette, followed by 3 direction bits and 2 rotation bits. -- We set the palette bits and rotation return rotation + axis_direction * 4 + color * 32 end local function get_orientation_from_colorfacedir(param2) local axis_direction = 0 -- Strip off the top 6 bits to leave the 2 rotation bits, unfortunately MT lua has no bitwise '&' -- (high 3 bits are palette, followed by 3 direction bits then 2 rotation bits) if param2 >= 128 then param2 = param2 - 128 end if param2 >= 64 then param2 = param2 - 64 end if param2 >= 32 then param2 = param2 - 32 end if param2 >= 16 then param2 = param2 - 16; axis_direction = axis_direction + 4 end if param2 >= 8 then param2 = param2 - 8; axis_direction = axis_direction + 2 end if param2 >= 4 then param2 = param2 - 4; axis_direction = axis_direction + 1 end -- if the portal is vertical then node axis direction will be +Y (up) and portal orientation -- will set the node's rotation. -- if the portal is horizontal then the node axis direction reflects the yaw orientation and -- the node's rotation will be whatever's needed to keep the texture horizontal (either 0 or 1) if axis_direction == 0 or axis_direction == 5 then -- portal is vertical return param2 * 90 else if axis_direction == 1 then return 0 end if axis_direction == 3 then return 90 end if axis_direction == 2 then return 180 end if axis_direction == 4 then return 270 end end end -- We want wormhole nodes to only emit mesecon energy orthogonally to the -- wormhole surface so that the wormhole will not send power to the frame, -- this allows the portal frame to listen for mesecon energy from external switches/wires etc. function get_mesecon_emission_rules_from_colorfacedir(param2) local axis_direction = 0 -- Strip off the top 6 bits to leave the 2 rotation bits, unfortunately MT lua has no bitwise '&' -- (high 3 bits are palette, followed by 3 direction bits then 2 rotation bits) if param2 >= 128 then param2 = param2 - 128 end if param2 >= 64 then param2 = param2 - 64 end if param2 >= 32 then param2 = param2 - 32 end if param2 >= 16 then param2 = param2 - 16; axis_direction = axis_direction + 4 end if param2 >= 8 then param2 = param2 - 8; axis_direction = axis_direction + 2 end if param2 >= 4 then param2 = param2 - 4; axis_direction = axis_direction + 1 end -- if the portal is vertical then node axis_direction will be +Y (up) and node rotation -- will reflect the portal's yaw orientation. -- If the portal is horizontal then the node axis direction reflects the yaw orientation and -- the node's rotation will be whatever's needed to keep the texture horizontal (either 0 or 1) local rules if axis_direction == 0 or axis_direction == 5 then -- portal is vertical rules = {{x = 0, y = 0, z = 1}, {x = 0, y = 0, z = -1}} if param2 % 2 ~= 0 then rules = mesecon.rotate_rules_right(rules) end else -- portal is horizontal, only emit up rules = {{x = 0, y = 1, z = 0}} end return rules end nether.get_mesecon_emission_rules_from_colorfacedir = get_mesecon_emission_rules_from_colorfacedir -- make the function available to nodes.lua -- Combining frame_node_name, p1, and p2 will always be enough to uniquely identify a portal_definition -- WITHOUT needing to inspect the world. register_portal() will enforce this. -- This function does not require the portal to be in a loaded chunk. -- Returns nil if no portal_definition matches the arguments local function get_portal_definition(frame_node_name, p1, p2) local size = vector.add(vector.subtract(p2, p1), 1) local rotated_size = {x = size.z, y = size.y, z = size.x} for _, portal_def in pairs(nether.registered_portals) do if portal_def.frame_node_name == frame_node_name then if vector.equals(size, portal_def.shape.size) or vector.equals(rotated_size, portal_def.shape.size) then return portal_def end end end return nil end -- Returns a list of all portal_definitions with a frame made of frame_node_name. -- Ideally no two portal types will be built from the same frame material so this call might be enough -- to uniquely identify a portal_definition without needing to inspect the world, HOWEVER we shouldn't -- cramp anyone's style and prohibit non-nether use of obsidian to make portals, so it returns a list. -- If the list contains more than one item then routines like ignite_portal() will have to search twice -- for a portal and take twice the CPU. local function list_portal_definitions_for_frame_node(frame_node_name) local result = {} for _, portal_def in pairs(nether.registered_portals) do if portal_def.frame_node_name == frame_node_name then table.insert(result, portal_def) end end return result end -- Add portal information to mod storage, so new portals may find existing portals near the target location. -- Do this whenever a portal is created or changes its ignition state local function store_portal_location_info(portal_name, anchorPos, orientation, ignited) if not DEBUG_IGNORE_MODSTORAGE then local key = minetest.pos_to_string(anchorPos) .. " is " .. portal_name debugf("Adding/updating portal in mod_storage: " .. key) mod_storage:set_string( key, minetest.serialize({orientation = orientation, active = ignited}) ) end end -- Remove portal information from mod storage. -- Do this if a portal frame is destroyed such that it cannot be ignited anymore. local function remove_portal_location_info(portal_name, anchorPos) if not DEBUG_IGNORE_MODSTORAGE then local key = minetest.pos_to_string(anchorPos) .. " is " .. portal_name debugf("Removing portal from mod_storage: " .. key) mod_storage:set_string(key, "") end end -- Returns a table of the nearest portals to anchorPos indexed by distance, based on mod_storage -- data. -- Only portals in the same realm as the anchorPos will be returned, even if y_factor is 0. -- WARNING: Portals are not checked, and inactive portals especially may have been damaged without -- being removed from the mod_storage data. Check these portals still exist before using them, and -- invoke remove_portal_location_info() on any found to no longer exist. -- -- A y_factor of 0 means y does not affect the distance_limit, a y_factor of 1 means y is included, -- and a y_factor of 2 would squash the search-sphere by a factor of 2 on the y-axis, etc. -- Pass a nil or negative distance_limit to indicate no distance limit local function list_closest_portals(portal_definition, anchorPos, distance_limit, y_factor) local result = {} if not DEBUG_IGNORE_MODSTORAGE then local isRealm = portal_definition.is_within_realm(anchorPos) if distance_limit == nil then distance_limit = -1 end if y_factor == nil then y_factor = 1 end for key, value in pairs(mod_storage:to_table().fields) do local closingBrace = key:find(")", 6, true) if closingBrace ~= nil then local found_anchorPos = minetest.string_to_pos(key:sub(0, closingBrace)) if found_anchorPos ~= nil and portal_definition.is_within_realm(found_anchorPos) == isRealm then local found_name = key:sub(closingBrace + 5) if found_name == portal_definition.name then local x = anchorPos.x - found_anchorPos.x local y = anchorPos.y - found_anchorPos.y local z = anchorPos.z - found_anchorPos.z local distance = math.hypot(y * y_factor, math.hypot(x, z)) if distance <= distance_limit or distance_limit < 0 then local info = minetest.deserialize(value) or {} debugf("found %s listed at distance %.2f (within %.2f) from dest %s, found: %s orientation %s", found_name, distance, distance_limit, anchorPos, found_anchorPos, info.orientation) info.anchorPos = found_anchorPos info.distance = distance result[distance] = info end end end end end end return result end -- the timerNode is used to keep the metadata as that node already needs to be known any time a portal is stopped or run -- see also ambient_sound_stop() function ambient_sound_play(portal_definition, soundPos, timerNodeMeta) if portal_definition.sounds.ambient ~= nil then local soundLength = portal_definition.sounds.ambient.length if soundLength == nil then soundLength = 3 end local lastPlayed = timerNodeMeta:get_int("ambient_sound_last_played") -- Using "os.time() % soundLength == 0" is lightweight but means delayed starts, so trying a stored lastPlayed if os.time() >= lastPlayed + soundLength then local soundHandle = minetest.sound_play(portal_definition.sounds.ambient, {pos = soundPos, max_hear_distance = 8}) if timerNodeMeta ~= nil then timerNodeMeta:set_int("ambient_sound_handle", soundHandle) timerNodeMeta:set_int("ambient_sound_last_played", os.time()) end end end end -- the timerNode is used to keep the metadata as that node already needs to be known any time a portal is stopped or run -- see also ambient_sound_play() function ambient_sound_stop(timerNodeMeta) if timerNodeMeta ~= nil then local soundHandle = timerNodeMeta:get_int("ambient_sound_handle") minetest.sound_fade(soundHandle, -3, 0) -- clear the metadata timerNodeMeta:set_string("ambient_sound_handle", "") timerNodeMeta:set_string("ambient_sound_last_played", "") end end -- WARNING - this is invoked by on_destruct, so you can't assume there's an accesible node at pos -- Returns true if a portal was found to extinguish function extinguish_portal(pos, node_name, frame_was_destroyed) -- mesecons seems to invoke action_off() 6 times every time you place a block? debugf("extinguish_portal %s %s", pos, node_name) local meta = minetest.get_meta(pos) local p1 = minetest.string_to_pos(meta:get_string("p1")) local p2 = minetest.string_to_pos(meta:get_string("p2")) local target = minetest.string_to_pos(meta:get_string("target")) if p1 == nil or p2 == nil then debugf(" no active portal found to extinguish") return false end local portal_definition = get_portal_definition(node_name, p1, p2) if portal_definition == nil then minetest.log("error", "extinguish_portal() invoked on " .. node_name .. " but no registered portal is constructed from " .. node_name) return false -- no portal frames are made from this type of node end if portal_definition.sounds.extinguish ~= nil then minetest.sound_play(portal_definition.sounds.extinguish, {pos = p1}) end -- stop timer and ambient sound local timerPos = get_timerPos_from_p1_and_p2(p1, p2) minetest.get_node_timer(timerPos):stop() ambient_sound_stop(minetest.get_meta(timerPos)) -- update the ignition state in the portal location info local anchorPos, orientation = portal_definition.shape.get_anchorPos_and_orientation_from_p1_and_p2(p1, p2) if frame_was_destroyed then remove_portal_location_info(portal_definition.name, anchorPos) else store_portal_location_info(portal_definition.name, anchorPos, orientation, false) end local frame_node_name = portal_definition.frame_node_name local wormhole_node_name = portal_definition.wormhole_node_name for x = p1.x, p2.x do for y = p1.y, p2.y do for z = p1.z, p2.z do local clearPos = {x = x, y = y, z = z} local nn = minetest.get_node(clearPos).name if nn == frame_node_name or nn == wormhole_node_name then if nn == wormhole_node_name then minetest.remove_node(clearPos) if meseconsAvailable then mesecon.receptor_off(clearPos) end end local m = minetest.get_meta(clearPos) m:set_string("p1", "") m:set_string("p2", "") m:set_string("target", "") m:set_string("portal_type", "") end end end end if target ~= nil then debugf(" attempting to also extinguish target with wormholePos %s", target) extinguish_portal(target, node_name) end if portal_definition.on_extinguish ~= nil then portal_definition.on_extinguish(portal_definition, anchorPos, orientation) end return true end -- Note: will extinguish any portal using the same nodes that are being set local function set_portal_metadata(portal_definition, anchorPos, orientation, destination_wormholePos, ignite) ignite = ignite or false; debugf("set_portal_metadata(ignite=%s) at %s orient %s, setting to target %s", ignite, anchorPos, orientation, destination_wormholePos) -- Portal position is stored in metadata as p1 and p2 to keep maps compatible with earlier versions of this mod. -- p1 is the bottom/west/south corner of the portal, and p2 is the opposite corner, together -- they define the bounding volume for the portal. local p1, p2 = portal_definition.shape:get_p1_and_p2_from_anchorPos(anchorPos, orientation) local p1_string, p2_string = minetest.pos_to_string(p1), minetest.pos_to_string(p2) local param2 = get_colorfacedir_from_color_and_orientation(portal_definition.wormhole_node_color, orientation, portal_definition.shape.is_horizontal) local mesecon_rules if ignite and meseconsAvailable then mesecon_rules = get_mesecon_emission_rules_from_colorfacedir(param2) end local update_aborted-- using closures to allow the updateFunc to return extra information - by setting this variable local updateFunc = function(pos) local meta = minetest.get_meta(pos) if ignite then local node_name = minetest.get_node(pos).name if node_name == "air" then minetest.set_node(pos, {name = portal_definition.wormhole_node_name, param2 = param2}) if meseconsAvailable then mesecon.receptor_on(pos, mesecon_rules) end end local existing_p1 = meta:get_string("p1") if existing_p1 ~= "" then local existing_p2 = meta:get_string("p2") if existing_p1 ~= p1_string or existing_p2 ~= p2_string then debugf("set_portal_metadata() found existing metadata from another portal: existing_p1 %s, existing_p2 %s, p1 %s, p2 %s, will extinguish existing portal...", existing_p1, existing_p2, p1_string, p2_string) -- this node is already part of another portal, so extinguish that, because nodes only -- contain a link in the metadata to one portal, and being part of two allows a slew of bugs extinguish_portal(pos, node_name, false) -- clear the metadata to avoid causing a loop if extinguish_portal() fails on this node (e.g. it only works on frame nodes) meta:set_string("p1", nil) meta:set_string("p2", nil) meta:set_string("target", nil) meta:set_string("portal_type", nil) update_aborted = true return true -- short-circuit the update end end end meta:set_string("p1", minetest.pos_to_string(p1)) meta:set_string("p2", minetest.pos_to_string(p2)) meta:set_string("target", minetest.pos_to_string(destination_wormholePos)) if portal_definition.name ~= "nether_portal" then -- Legacy portals won't have this extra metadata, so don't rely on it. -- It's not strictly necessary for PortalShape_Traditional as we know that p1 is part of -- the frame and we can look up the portal type from p1, p2, and frame node name. -- Being able to read this from the metadata means other portal shapes needn't have their -- frame at the timerPos, it may handle unloaded nodes better, and it saves an extra call -- to minetest.getnode(). meta:set_string("portal_type", portal_definition.name) end end repeat update_aborted = false portal_definition.shape.apply_func_to_frame_nodes(anchorPos, orientation, updateFunc) portal_definition.shape.apply_func_to_wormhole_nodes(anchorPos, orientation, updateFunc) until not update_aborted local timerPos = get_timerPos_from_p1_and_p2(p1, p2) minetest.get_node_timer(timerPos):start(1) store_portal_location_info(portal_definition.name, anchorPos, orientation, true) end local function set_portal_metadata_and_ignite(portal_definition, anchorPos, orientation, destination_wormholePos) set_portal_metadata(portal_definition, anchorPos, orientation, destination_wormholePos, true) end -- this function returns two bools: portal found, portal is lit local function is_portal_at_anchorPos(portal_definition, anchorPos, orientation, force_chunk_load) local nodes_are_valid -- using closures to allow the check functions to return extra information - by setting this variable local portal_is_ignited -- using closures to allow the check functions to return extra information - by setting this variable local frame_node_name = portal_definition.frame_node_name local check_frame_Func = function(check_pos) local foundName = minetest.get_node(check_pos).name if foundName ~= frame_node_name then if force_chunk_load and foundName == "ignore" then -- area isn't loaded, force loading/emerge of check area minetest.get_voxel_manip():read_from_map(check_pos, check_pos) foundName = minetest.get_node(check_pos).name debugf("Forced loading of 'ignore' node at %s, got %s", check_pos, foundName) if foundName ~= frame_node_name then nodes_are_valid = false return true -- short-circuit the search end else nodes_are_valid = false return true -- short-circuit the search end end end local wormhole_node_name = portal_definition.wormhole_node_name local check_wormhole_Func = function(check_pos) local node_name = minetest.get_node(check_pos).name if node_name ~= wormhole_node_name then portal_is_ignited = false; if node_name ~= "air" then nodes_are_valid = false return true -- short-circuit the search end end end nodes_are_valid = true portal_is_ignited = true portal_definition.shape.apply_func_to_frame_nodes(anchorPos, orientation, check_frame_Func) -- check_frame_Func affects nodes_are_valid, portal_is_ignited if nodes_are_valid then -- a valid frame exists at anchorPos, check the wormhole is either ignited or unobstructed portal_definition.shape.apply_func_to_wormhole_nodes(anchorPos, orientation, check_wormhole_Func) -- check_wormhole_Func affects nodes_are_valid, portal_is_ignited end return nodes_are_valid, portal_is_ignited and nodes_are_valid -- returns two bools: portal was found, portal is lit end -- Checks pos, and if it's part of a portal or portal frame then three values are returned: anchorPos, orientation, is_ignited -- where orientation is 0 or 90 (0 meaning a portal that faces north/south - i.e. obsidian running east/west) local function is_within_portal_frame(portal_definition, pos) local width_minus_1 = portal_definition.shape.size.x - 1 local height_minus_1 = portal_definition.shape.size.y - 1 local depth_minus_1 = portal_definition.shape.size.z - 1 for d = -depth_minus_1, depth_minus_1 do for w = -width_minus_1, width_minus_1 do for y = -height_minus_1, height_minus_1 do local testAnchorPos_x = {x = pos.x + w, y = pos.y + y, z = pos.z + d} local portal_found, portal_lit = is_portal_at_anchorPos(portal_definition, testAnchorPos_x, 0, true) if portal_found then return testAnchorPos_x, 0, portal_lit else -- try orthogonal orientation local testForAnchorPos_z = {x = pos.x + d, y = pos.y + y, z = pos.z + w} portal_found, portal_lit = is_portal_at_anchorPos(portal_definition, testForAnchorPos_z, 90, true) if portal_found then return testForAnchorPos_z, 90, portal_lit end end end end end end -- sets param2 values in the schematic to match facedir values, or 0 if the portalframe-nodedef doesn't use facedir local function set_schematic_param2(schematic_table, frame_node_name, frame_node_color) local paramtype2 = minetest.registered_nodes[frame_node_name].paramtype2 local isFacedir = paramtype2 == "facedir" or paramtype2 == "colorfacedir" if schematic_table.facedirNodes ~= nil then for _, node in ipairs(schematic_table.facedirNodes) do if isFacedir and node.facedir ~= nil then -- frame_node_color can be nil local colorBits = (frame_node_color or math.floor((node.param2 or 0) / 32)) * 32 node.param2 = node.facedir + colorBits else node.param2 = 0 end end end end local function build_portal(portal_definition, anchorPos, orientation, destination_wormholePos) set_schematic_param2(portal_definition.shape.schematic, portal_definition.frame_node_name, portal_definition.frame_node_color) minetest.place_schematic( portal_definition.shape.get_schematicPos_from_anchorPos(anchorPos, orientation), portal_definition.shape.schematic, orientation, { -- node replacements ["default:obsidian"] = portal_definition.frame_node_name, }, true ) -- set the param2 on wormhole nodes to ensure they are the right color local wormholeNode = { name = portal_definition.wormhole_node_name, param2 = get_colorfacedir_from_color_and_orientation(portal_definition.wormhole_node_color, orientation, portal_definition.shape.is_horizontal) } portal_definition.shape.apply_func_to_wormhole_nodes( anchorPos, orientation, function(pos) minetest.swap_node(pos, wormholeNode) end ) debugf("Placed %s portal schematic at %s, orientation %s", portal_definition.name, portal_definition.shape.get_schematicPos_from_anchorPos(anchorPos, orientation), orientation) set_portal_metadata(portal_definition, anchorPos, orientation, destination_wormholePos) if portal_definition.on_created ~= nil then portal_definition.on_created(portal_definition, anchorPos, orientation) end end -- Sometimes after a portal is placed, concurrent mapgen routines overwrite it. -- Make portals immortal for ~20 seconds after creation local function remote_portal_checkup(elapsed, portal_definition, anchorPos, orientation, destination_wormholePos) debugf("portal checkup at %d seconds", elapsed) local wormholePos = portal_definition.shape.get_wormholePos_from_anchorPos(anchorPos, orientation) local wormhole_node = minetest.get_node_or_nil(wormholePos) local portalFound, portalLit = false, false if wormhole_node ~= nil and wormhole_node.name == portal_definition.wormhole_node_name then -- a wormhole node was there, but check the whole frame is intact portalFound, portalLit = is_portal_at_anchorPos(portal_definition, anchorPos, orientation, false) end if not portalFound or not portalLit then -- ruh roh local message = "Newly created portal at " .. minetest.pos_to_string(anchorPos) .. " was overwritten. Attempting to recreate. Issue spotted after " .. elapsed .. " seconds" minetest.log("warning", message) debugf("!!! " .. message) -- A pre-existing portal frame wouldn't have been immediately overwritten, so no need to check for one, just place the portal. build_portal(portal_definition, anchorPos, orientation, destination_wormholePos) end if elapsed < 10 then -- stop checking after ~20 seconds local delay = elapsed * 2 minetest.after(delay, remote_portal_checkup, elapsed + delay, portal_definition, anchorPos, orientation, destination_wormholePos) end end -- Used to find or build the remote twin after a portal is opened. -- If a portal is found that is already lit then it will be extinguished first and its destination_wormholePos updated, -- this is to enforce that portals only link together in mutual pairs. It would be better for gameplay if I didn't apply -- that restriction, but it would require maintaining an accurate list of every portal that links to a portal so they -- could be updated if the portal is destroyed. To keep the code simple I'm going to limit portals to only being the -- destination of one lit portal at a time. -- * suggested_wormholePos indicates where the portal should be built - note this not an anchorPos! -- * suggested_orientation is the suggested schematic rotation to use if no useable portal is found at suggested_wormholePos: -- 0, 90, 180, 270 (0 meaning a portal that faces north/south - i.e. obsidian running east/west) -- * destination_wormholePos is the wormholePos of the destination portal this one will be linked to. -- -- Returns the final (anchorPos, orientation), as they may differ from the anchorPos and orientation that was -- specified if an existing portal was already found there. local function locate_or_build_portal(portal_definition, suggested_wormholePos, suggested_orientation, destination_wormholePos) debugf("locate_or_build_portal() called at wormholePos%s with suggested orient %s, targeted to %s", suggested_wormholePos, suggested_orientation, destination_wormholePos) local result_anchorPos; local result_orientation; -- Searching for an existing portal at wormholePos seems better than at anchorPos, though isn't important local found_anchorPos, found_orientation, is_ignited = is_within_portal_frame(portal_definition, suggested_wormholePos) -- can be optimized - check for portal at exactly suggested_wormholePos first if found_anchorPos ~= nil then -- A portal is already here, we don't have to build one, though we may need to ignite it result_anchorPos = found_anchorPos result_orientation = found_orientation if is_ignited then -- We're about to link to this portal, so if it's already linked to a different portal then -- extinguish it, to update the state of the about-to-be-orphaned portal. local result_target_str = minetest.get_meta(result_anchorPos):get_string("target") local result_target = minetest.string_to_pos(result_target_str) if result_target ~= nil and vector.equals(result_target, destination_wormholePos) then -- It already links back to the portal the player is teleporting from, so don't -- extinguish it or the player's portal will also extinguish. debugf(" Build unnecessary: already a lit portal that links back here at %s, orientation %s", found_anchorPos, result_orientation) else debugf(" Build unnecessary: already a lit portal at %s, orientation %s, linking to %s. Extinguishing...", found_anchorPos, result_orientation, result_target_str) extinguish_portal(found_anchorPos, portal_definition.frame_node_name, false) end else debugf(" Build unnecessary: already an unlit portal at %s, orientation %s", found_anchorPos, result_orientation) end -- ignite the portal set_portal_metadata_and_ignite(portal_definition, result_anchorPos, result_orientation, destination_wormholePos) else result_orientation = suggested_orientation result_anchorPos = portal_definition.shape.get_anchorPos_from_wormholePos(suggested_wormholePos, result_orientation) build_portal(portal_definition, result_anchorPos, result_orientation, destination_wormholePos) -- make sure portal isn't overwritten by ongoing generation/emerge minetest.after(2, remote_portal_checkup, 2, portal_definition, result_anchorPos, result_orientation, destination_wormholePos) end return result_anchorPos, result_orientation end -- invoked when a player attempts to turn obsidian nodes into an open portal -- player_name is optional, allowing a player to spawn a remote portal in their own protected area -- ignition_node_name is optional local function ignite_portal(ignition_pos, player_name, ignition_node_name) if ignition_node_name == nil then ignition_node_name = minetest.get_node(ignition_pos).name end debugf("IGNITE the %s at %s", ignition_node_name, ignition_pos) -- find which sort of portals are made from the node that was clicked on local portal_definition_list = list_portal_definitions_for_frame_node(ignition_node_name) for _, portal_definition in ipairs(portal_definition_list) do local continue = false -- WRT the for loop, since lua has no continue keyword -- check it was a portal frame that the player is trying to ignite local anchorPos, orientation, is_ignited = is_within_portal_frame(portal_definition, ignition_pos) if anchorPos == nil then debugf("No %s portal frame found at ", portal_definition.name, ignition_pos) continue = true -- no portal is here, but perhaps there's more than one portal type we need to search for elseif is_ignited then -- Found a portal, check its metadata and timer is healthy. local repair = false local meta = minetest.get_meta(ignition_pos) if meta ~= nil then local p1, p2, target = meta:get_string("p1"), meta:get_string("p2"), meta:get_string("target") if p1 == "" or p2 == "" or target == "" then -- metadata is missing, the portal frame node must have been removed without calling -- on_destruct - perhaps by an ABM, then replaced - presumably by a player. -- allowing reigniting will repair the portal debugf("Broken portal detected, allowing reignition/repair") repair = true else debugf("This portal links to %s. p1=%s p2=%s", meta:get_string("target"), meta:get_string("p1"), meta:get_string("p2")) -- Check the portal's timer is running, and fix if it's not. -- A portal's timer can stop running if the game is played without that portal type being -- registered, e.g. enabling one of the example portals then later disabling it, then enabling it again. -- (if this is a frequent problem, then change the value of "run_at_every_load" in the lbm) local timer = minetest.get_node_timer(get_timerPos_from_p1_and_p2(minetest.string_to_pos(p1), minetest.string_to_pos(p2))) if timer ~= nil and timer:get_timeout() == 0 then debugf("Portal timer was not running: restarting the timer.") timer:start(1) end end end if not repair then return false end -- portal is already ignited (or timer has been fixed) end if continue == false then debugf("Found portal frame. Looked at %s, found at %s orientation %s", ignition_pos, anchorPos, orientation) local destination_anchorPos, destination_orientation if portal_definition.is_within_realm(ignition_pos) then destination_anchorPos, destination_orientation = portal_definition.find_surface_anchorPos(anchorPos, player_name or "") else destination_anchorPos, destination_orientation = portal_definition.find_realm_anchorPos(anchorPos, player_name or "") end if destination_orientation == nil then debugf("No destination_orientation given") destination_orientation = orientation end if destination_anchorPos == nil or destination_anchorPos.y == nil then -- destination_anchorPos.y was also checked for nil in case portal_definition.find_surface_anchorPos() -- had used nether.find_surface_target_y() and that had returned nil. debugf("No portal destination available here!") if (player_name or "") ~= "" then minetest.chat_send_player(player_name, nether.portal_destination_not_found_message) end return false else local destination_wormholePos = portal_definition.shape.get_wormholePos_from_anchorPos(destination_anchorPos, destination_orientation) debugf("Destination set to %s", destination_anchorPos) -- ignition/BURN_BABY_BURN set_portal_metadata_and_ignite(portal_definition, anchorPos, orientation, destination_wormholePos) if portal_definition.sounds.ignite ~= nil then local local_wormholePos = portal_definition.shape.get_wormholePos_from_anchorPos(anchorPos, orientation) minetest.sound_play(portal_definition.sounds.ignite, {pos = local_wormholePos, max_hear_distance = 20}) end if portal_definition.on_ignite ~= nil then portal_definition.on_ignite(portal_definition, anchorPos, orientation) end return true end end end end -- invoked when a player is standing in a portal local function ensure_remote_portal_then_teleport(playerName, portal_definition, local_anchorPos, local_orientation, destination_wormholePos) local player = minetest.get_player_by_name(playerName) if player == nil then return end -- player quit the game while teleporting local playerPos = player:get_pos() if playerPos == nil then return end -- player quit the game while teleporting -- check player is still standing in a portal playerPos.y = playerPos.y + 0.1 -- Fix some glitches at -8000 if minetest.get_node(playerPos).name ~= portal_definition.wormhole_node_name then return -- the player has moved out of the portal end -- debounce - check player is still standing in the *same* portal that called this function local meta = minetest.get_meta(playerPos) local local_p1, local_p2 = portal_definition.shape:get_p1_and_p2_from_anchorPos(local_anchorPos, local_orientation) local p1_at_playerPos = minetest.string_to_pos(meta:get_string("p1")) if p1_at_playerPos == nil or not vector.equals(local_p1, p1_at_playerPos) then debugf("the player already teleported from %s, and is now standing in a different portal - %s", local_anchorPos, meta:get_string("p1")) return -- the player already teleported, and is now standing in a different portal end local dest_wormhole_node = minetest.get_node_or_nil(destination_wormholePos) if dest_wormhole_node == nil then -- area not emerged yet, delay and retry debugf("ensure_remote_portal_then_teleport() could not find anything yet at %s", destination_wormholePos) minetest.after(1, ensure_remote_portal_then_teleport, playerName, portal_definition, local_anchorPos, local_orientation, destination_wormholePos) else local local_wormholePos = portal_definition.shape.get_wormholePos_from_anchorPos(local_anchorPos, local_orientation) if dest_wormhole_node.name == portal_definition.wormhole_node_name then -- portal exists local destination_orientation = get_orientation_from_colorfacedir(dest_wormhole_node.param2) local destination_anchorPos = portal_definition.shape.get_anchorPos_from_wormholePos(destination_wormholePos, destination_orientation) portal_definition.shape.disable_portal_trap(destination_anchorPos, destination_orientation) -- if the portal is already linked to a different portal then extinguish the other portal and -- update the target portal to point back at this one. local remoteMeta = minetest.get_meta(destination_wormholePos) local remoteTarget = minetest.string_to_pos(remoteMeta:get_string("target")) if remoteTarget == nil then debugf("Failed to test whether target portal links back to this one") elseif not vector.equals(remoteTarget, local_wormholePos) then debugf("Target portal is already linked, extinguishing then relighting to point back at this one") extinguish_portal(remoteTarget, portal_definition.frame_node_name, false) set_portal_metadata_and_ignite( portal_definition, destination_anchorPos, destination_orientation, local_wormholePos ) end debugf("Teleporting player from wormholePos%s to wormholePos%s", local_wormholePos, destination_wormholePos) -- play the teleport sound if portal_definition.sounds.teleport ~= nil then minetest.sound_play(portal_definition.sounds.teleport, {to_player = playerName}) end -- rotate the player if the destination portal is a different orientation local rotation_angle = math.rad(destination_orientation - local_orientation) local offset = vector.subtract(playerPos, local_wormholePos) -- preserve player's position in the portal local rotated_offset = {x = math.cos(rotation_angle) * offset.x - math.sin(rotation_angle) * offset.z, y = offset.y, z = math.sin(rotation_angle) * offset.x + math.cos(rotation_angle) * offset.z} local new_playerPos = vector.add(destination_wormholePos, rotated_offset) player:set_pos(new_playerPos) player:set_look_horizontal(player:get_look_horizontal() + rotation_angle) if portal_definition.on_player_teleported ~= nil then portal_definition.on_player_teleported(portal_definition, player, playerPos, new_playerPos) end else -- no wormhole node at destination - destination portal either needs to be built or ignited. -- Note: A very rare edge-case that is difficult to set up: -- If the destination portal is unlit and its frame shares a node with a lit portal that is linked to this -- portal (but has not been travelled through, thus not linking this portal back to it), then igniting -- the destination portal will extinguish the portal it's touching, which will extinguish this portal -- which will leave a confused player. -- I don't think this is worth preventing, but I document it incase someone describes entering a portal -- and then the portal turning off. debugf("ensure_remote_portal_then_teleport() saw %s at %s rather than a wormhole. Calling locate_or_build_portal()", dest_wormhole_node.name, destination_wormholePos) local new_dest_anchorPos, new_dest_orientation = locate_or_build_portal(portal_definition, destination_wormholePos, local_orientation, local_wormholePos) local new_dest_wormholePos = portal_definition.shape.get_wormholePos_from_anchorPos(new_dest_anchorPos, new_dest_orientation) if not vector.equals(destination_wormholePos, new_dest_wormholePos) then -- Update the local portal's target to match where the existing remote portal was found if minetest.get_meta(local_anchorPos):get_string("target") == "" then -- The local portal has been extinguished! -- Abort setting its metadata as that assumes it is active. -- This shouldn't happen and may indicate a bug, I trap it incase when the destination -- portal was found and extinguished, it somehow linked back to the local portal in a -- misaligned fashion that wasn't recognized as being the local portal and caused the -- local portal to also be extinguished. local message = "Local portal at " .. minetest.pos_to_string(local_anchorPos) .. " was extinguished while linking to existing portal at " .. minetest.pos_to_string(new_dest_anchorPos) minetest.log("error", message) debugf("!ERROR! - " .. message) else destination_wormholePos = new_dest_wormholePos debugf(" updating target to where remote portal was found - %s", destination_wormholePos) set_portal_metadata( portal_definition, local_anchorPos, local_orientation, destination_wormholePos ) end end minetest.after(0.1, ensure_remote_portal_then_teleport, playerName, portal_definition, local_anchorPos, local_orientation, destination_wormholePos) end end end -- run_wormhole() is invoked once per second per portal, handling teleportation and particle effects. -- See get_timerPos_from_p1_and_p2() for an explanation of the timerPos location function run_wormhole(timerPos, time_elapsed) local portal_definition -- will be used inside run_wormhole_node_func() local run_wormhole_node_func = function(pos) if math.random(2) == 1 then -- lets run only 3 particlespawners instead of 6 per portal minetest.add_particlespawner({ amount = 16, time = 2, minpos = {x = pos.x - 0.25, y = pos.y - 0.25, z = pos.z - 0.25}, maxpos = {x = pos.x + 0.25, y = pos.y + 0.25, z = pos.z + 0.25}, minvel = {x = -0.8, y = -0.8, z = -0.8}, maxvel = {x = 0.8, y = 0.8, z = 0.8}, minacc = {x = 0, y = 0, z = 0}, maxacc = {x = 0, y = 0, z = 0}, minexptime = 0.5, maxexptime = 1.7, minsize = 0.5 * portal_definition.particle_texture_scale, maxsize = 1.5 * portal_definition.particle_texture_scale, collisiondetection = false, texture = portal_definition.particle_texture_colored, animation = portal_definition.particle_texture_animation, glow = 5 }) end for _, obj in ipairs(minetest.get_objects_inside_radius(pos, 1)) do if obj:is_player() then local meta = minetest.get_meta(pos) local destination_wormholePos = minetest.string_to_pos(meta:get_string("target")) local local_p1 = minetest.string_to_pos(meta:get_string("p1")) local local_p2 = minetest.string_to_pos(meta:get_string("p2")) if destination_wormholePos ~= nil and local_p1 ~= nil and local_p2 ~= nil then -- force emerge of target area minetest.get_voxel_manip():read_from_map(destination_wormholePos, destination_wormholePos) -- force load if minetest.get_node_or_nil(destination_wormholePos) == nil then minetest.emerge_area(vector.subtract(destination_wormholePos, 4), vector.add(destination_wormholePos, 4)) end local local_anchorPos, local_orientation = portal_definition.shape.get_anchorPos_and_orientation_from_p1_and_p2(local_p1, local_p2) local playerName = obj:get_player_name() minetest.after( 3, -- hopefully target area is emerged in 3 seconds function() ensure_remote_portal_then_teleport( playerName, portal_definition, local_anchorPos, local_orientation, destination_wormholePos ) end ) end end end end local p1, p2, portal_name local meta = minetest.get_meta(timerPos) if meta ~= nil then p1 = minetest.string_to_pos(meta:get_string("p1")) p2 = minetest.string_to_pos(meta:get_string("p2")) portal_name = minetest.string_to_pos(meta:get_string("portal_type")) -- don't rely on this yet until you're sure everything works with old portals that don't have this set end if p1 ~= nil and p2 ~= nil then -- figure out the portal shape so we know where the wormhole nodes will be located local frame_node_name if portal_name ~= nil and nether.registered_portals[portal_name] ~= nil then portal_definition = nether.registered_portals[portal_name] else frame_node_name = minetest.get_node(timerPos).name -- timerPos should be a frame node if the shape is traditionalPortalShape portal_definition = get_portal_definition(frame_node_name, p1, p2) end if portal_definition == nil then minetest.log("error", "No portal with a \"" .. frame_node_name .. "\" frame is registered. run_wormhole" .. minetest.pos_to_string(timerPos) .. " was invoked but that location contains \"" .. frame_node_name .. "\"") else local anchorPos, orientation = portal_definition.shape.get_anchorPos_and_orientation_from_p1_and_p2(p1, p2) portal_definition.shape.apply_func_to_wormhole_nodes(anchorPos, orientation, run_wormhole_node_func) if portal_definition.on_run_wormhole ~= nil then portal_definition.on_run_wormhole(portal_definition, anchorPos, orientation) end local wormholePos = portal_definition.shape.get_wormholePos_from_anchorPos(anchorPos, orientation) ambient_sound_play(portal_definition, wormholePos, meta) end end end local function create_book(item_name, inventory_description, inventory_image, title, author, chapters) local display_book = function(itemstack, user, pointed_thing) local player_name = user:get_player_name() minetest.sound_play("nether_book_open", {to_player = player_name, gain = 0.25}) local formspec = "size[18,12.122]" .. "background[0,0;18,11;nether_book_background.png;true]".. "image_button_exit[17.3,0;0.8,0.8;nether_book_close.png;;]".. "label[3.1,0.5;" .. minetest.formspec_escape(title) .. "]" .. "label[3.6,0.9;" .. author .. "]" local image_x_adj = -0.4 local image_width = 1.6 local image_padding = 0.06 for i, chapter in ipairs(chapters) do local left_margin = 0.9 local top_margin = 1.7 local width = 7.9 local height = 12.0 local item_number = i local items_on_page = math.floor(#chapters / 2) if i > items_on_page then -- page 2 left_margin = 10.1 top_margin = 0.8 height = 12.9 item_number = i - items_on_page items_on_page = #chapters - items_on_page end local available_height = (height - top_margin) / items_on_page local y = top_margin + (item_number - 1) * available_height -- add chapter title local title_height = 0 if chapter.title ~= nil then title_height = 0.6 formspec = formspec .. "label[".. left_margin + 1.5 .. "," .. y .. "; ──══♦♦♦◊ " .. minetest.formspec_escape(chapter.title) .. " ◊♦♦♦══──]" end -- add chapter image local x_offset = 0 if chapter.image ~= nil then x_offset = image_width + image_x_adj + image_padding local image_height = image_width / chapter.image.width * chapter.image.height formspec = formspec .. "image[" .. left_margin + image_x_adj .. "," .. y + title_height .. ";" .. image_width .. "," .. image_height .. ";" .. chapter.image.image .. "]" end -- add chapter text formspec = formspec .. "textarea[" .. left_margin + x_offset .. "," .. y + title_height .. ";" .. width - x_offset .. "," .. available_height - title_height .. ";;" .. minetest.formspec_escape(chapter.text) .. ";]" end minetest.show_formspec(player_name, item_name, formspec) end minetest.register_craftitem(item_name, { description = inventory_description, inventory_image = inventory_image, groups = {book = 1}, on_use = display_book, _doc_items_hidden = true, _doc_items_longdesc = S("A guidebook for how to build portals to other realms. It can sometimes be found in dungeon chests, however a copy of this book is not needed as its contents are included in this Encyclopedia.") .. "\n" .. S("Refer: \"Help\" > \"Basics\" > \"Building Portals\""), }) end local function add_book_as_treasure() book_added_as_treasure = true if minetest.get_modpath("loot") then loot.register_loot({ weights = { generic = nether.PORTAL_BOOK_LOOT_WEIGHTING * 1000, books = 100 }, payload = { stack = "nether:book_of_portals" } }) end if minetest.get_modpath("dungeon_loot") then dungeon_loot.register({name = "nether:book_of_portals", chance = nether.PORTAL_BOOK_LOOT_WEIGHTING}) end -- todo: add to Treasurer mod TRMP https://github.com/poikilos/trmp_minetest_game end -- Returns true if the Help-modpack was installed and Portal instructions were added to it -- Help-modpack details can be found at https://forum.minetest.net/viewtopic.php?t=15912 local function add_book_to_help_modpack(chapters) local result = false if minetest.get_modpath("doc") ~= nil and minetest.global_exists("doc") then if minetest.get_modpath("doc_basics") ~= nil then local text = S("Portals to other realms can be opened by building a frame in the right shape with the right blocks, then using an item to activate it. A local copy of the guidebook to portals is published below.\n---\n\n") local images = {} for i, chapter in ipairs(chapters) do if chapter.image ~= nil then -- Portal chapters have images (from their portalDef.shape) text = text .. "\n\n\n" .. (i - 1) .. ") " .. chapter.title .. "\n\n" local aspect_3_to_2_width = chapter.image.width local aspect_3_to_2_height = aspect_3_to_2_width / 3 * 2 if chapter.image.height > aspect_3_to_2_height then aspect_3_to_2_height = chapter.image.height aspect_3_to_2_width = aspect_3_to_2_height / 2 * 3 end local image_conveted_to_3_2_ratio = "[combine:"..aspect_3_to_2_width.."x"..aspect_3_to_2_height..":0,0="..chapter.image.image images[#images + 1] = {image=image_conveted_to_3_2_ratio} end text = text .. chapter.text end result = doc.add_entry("basics", "portals_api", { name = S("Building Portals"), data = { text = text, images = images, aspect_ratio=.5 } }) end end return result end -- Updates nether:book_of_portals -- A book the player can read to lean how to build the different portals local function create_book_of_portals() local chapters = {} local intro_text -- tell the player how many portal types there are if nether.registered_portals_count == 1 then intro_text = S("In all my travels, and time spent in the Great Libraries, I have encountered no shortage of legends surrounding preternatural doorways said to open into other worlds, yet only one can I confirm as being more than merely a story.") else intro_text = S("In all my travels, and time spent in the Great Libraries, I have encountered no shortage of legends surrounding preternatural doorways said to open into other worlds, yet only @1 can I confirm as being more than merely stories.", nether.registered_portals_count) end -- tell the player how to ignite portals local ignition_item_description = "" if ignition_item_name ~= nil and minetest.registered_items[ignition_item_name] ~= nil then ignition_item_description = minetest.registered_items[ignition_item_name].description end intro_text = intro_text .. S("\n\nThe key to opening such a doorway is to strike the frame with a @1, at which point the very air inside begins to crackle and glow.", ignition_item_description) chapters[#chapters + 1] = {text = intro_text} -- Describe how to create each type of portal, or perhaps just give clues or flavor text, -- but ensure the Nether is always listed first on the first page so other definitions can -- refer to it (pairs() returns order based on a random hash). local portalDefs_in_order = {} if nether.registered_portals["nether_portal"] then portalDefs_in_order[#portalDefs_in_order + 1] = nether.registered_portals["nether_portal"] end for portalName, portalDef in pairs(nether.registered_portals) do if portalName ~= "nether_portal" then portalDefs_in_order[#portalDefs_in_order + 1] = portalDef end end for _, portalDef in ipairs(portalDefs_in_order) do chapters[#chapters + 1] = { text = portalDef.book_of_portals_pagetext, image = portalDef.shape.diagram_image, title = portalDef.title } end create_book( ":nether:book_of_portals", S("Book of Portals"), "nether_book_of_portals.png", S("A definitive guide to Rifts and Portals"), "Riccard F. Burton", -- perhaps a Richard F. Burton of an alternate universe chapters ) local using_helpModpack = add_book_to_help_modpack(chapters) if not using_helpModpack and not book_added_as_treasure and nether.PORTAL_BOOK_LOOT_WEIGHTING > 0 then -- Only place the Book of Portals in chests if there are non-Nether (i.e. novel) portals -- which players need a way to find out about. if nether.registered_portals_count > 1 or (nether.registered_portals_count == 1 and nether.registered_portals["nether_portal"] == nil) then add_book_as_treasure() end end end function register_frame_node(frame_node_name) -- copy the existing node definition local node_def = minetest.registered_nodes[frame_node_name] local extended_node_def = {} for key, value in pairs(node_def) do extended_node_def[key] = value end extended_node_def.replaced_by_portalapi = {} -- allows chaining or restoration of original functions, if necessary -- add portal portal functionality extended_node_def.replaced_by_portalapi.mesecons = extended_node_def.mesecons extended_node_def.mesecons = {effector = { action_on = function (pos, node) debugf("portal frame material: mesecons action ON") ignite_portal(pos, nil, node.name) end, action_off = function (pos, node) debugf("portal frame material: mesecons action OFF") extinguish_portal(pos, node.name, false) end }} extended_node_def.replaced_by_portalapi.on_destruct = extended_node_def.on_destruct extended_node_def.on_destruct = function(pos) debugf("portal frame material: destruct") extinguish_portal(pos, frame_node_name, true) end extended_node_def.replaced_by_portalapi.on_blast = extended_node_def.on_blast extended_node_def.on_blast = function(pos, intensity) debugf("portal frame material: blast") extinguish_portal(pos, frame_node_name, extended_node_def.replaced_by_portalapi.on_blast == nil) if extended_node_def.replaced_by_portalapi.on_blast ~= nil then extended_node_def.replaced_by_portalapi.on_blast(pos, intensity) else minetest.remove_node(pos) end end extended_node_def.replaced_by_portalapi.on_timer = extended_node_def.on_timer extended_node_def.on_timer = function(pos, elapsed) run_wormhole(pos, elapsed) return true end -- replace the node with the new extended definition minetest.register_node(":" .. frame_node_name, extended_node_def) end function unregister_frame_node(frame_node_name) -- copy the existing node definition local node = minetest.registered_nodes[frame_node_name] local restored_node_def = {} for key, value in pairs(node) do restored_node_def[key] = value end -- remove portal portal functionality restored_node_def.mesecons = nil restored_node_def.on_destruct = nil restored_node_def.on_timer = nil restored_node_def.replaced_by_portalapi = nil if node.replaced_by_portalapi ~= nil then for key, value in pairs(node.replaced_by_portalapi) do restored_node_def[key] = value end end -- replace the node with the restored definition minetest.register_node(":" .. frame_node_name, restored_node_def) end -- check for mistakes people might make in custom shape definitions function test_shapedef_is_valid(shape_defintion) assert(shape_defintion ~= nil, "shape definition cannot be nil") assert(shape_defintion.name ~= nil, "shape definition must have a name") local result = true local origin = vector.new() local p1, p2 = shape_defintion:get_p1_and_p2_from_anchorPos(origin, 0) assert(vector.equals(shape_defintion.size, vector.add(vector.subtract(p2, p1), 1)), "p1 and p2 of shape definition '" .. shape_defintion.name .. "' don't match shapeDef.size") assert(shape_defintion.diagram_image ~= nil and shape_defintion.diagram_image.image ~= nil, "Shape definition '" .. shape_defintion.name .. "' does not provide an image for Help/Book of Portals") assert(shape_defintion.diagram_image.width > 0 and shape_defintion.diagram_image.height > 0, "Shape definition '" .. shape_defintion.name .. "' does not provide the size of the image for Help/Book of Portals") -- todo return result end -- check for mistakes people might make in portal definitions function test_portaldef_is_valid(portal_definition) local result = test_shapedef_is_valid(portal_definition.shape) assert(portal_definition.wormhole_node_color >= 0 and portal_definition.wormhole_node_color < 8, "portaldef.wormhole_node_color must be between 0 and 7 (inclusive)") assert(portal_definition.is_within_realm ~= nil, "portaldef.is_within_realm() must be implemented") assert(portal_definition.find_realm_anchorPos ~= nil, "portaldef.find_realm_anchorPos() must be implemented") if portal_definition.frame_node_color ~= nil then assert(portal_definition.frame_node_color >= 0 and portal_definition.frame_node_color < 8, "portal_definition.frame_node_color must be between 0 and 7 (inclusive)") end -- todo return result end -- convert portals made with old ABM version of nether mod to use the timer instead minetest.register_lbm({ label = "Start portal timer", name = "nether:start_portal_timer", nodenames = {"nether:portal"}, run_at_every_load = false, action = function(pos, node) local p1, p2 local meta = minetest.get_meta(pos) if meta ~= nil then p1 = minetest.string_to_pos(meta:get_string("p1")) p2 = minetest.string_to_pos(meta:get_string("p2")) end if p1 ~= nil and p2 ~= nil then local timerPos = get_timerPos_from_p1_and_p2(p1, p2) local timer = minetest.get_node_timer(timerPos) if timer ~= nil then timer:start(1) debugf("LBM started portal timer %s", timerPos) else debugf("get_node_timer%s returned null", timerPos) end end end }) -- Portal API functions -- -- ==================== -- -- the fallback defaults for wormhole nodedefs local wormhole_nodedef_default = { description = S("Portal wormhole"), tiles = { "nether_transparent.png", "nether_transparent.png", "nether_transparent.png", "nether_transparent.png", { name = "nether_portal.png", animation = { type = "vertical_frames", aspect_w = 16, aspect_h = 16, length = 0.9, }, }, { name = "nether_portal.png", animation = { type = "vertical_frames", aspect_w = 16, aspect_h = 16, length = 0.9, }, }, }, drawtype = "nodebox", paramtype = "light", paramtype2 = "colorfacedir", palette = "nether_portals_palette.png", post_effect_color = { -- post_effect_color can't be changed dynamically in Minetest like the portal colour is. -- If you need a different post_effect_color then use register_wormhole_node() to create -- another wormhole node with the right post_effect_color and set it as the wormhole_node_name -- in your portaldef. -- Hopefully this colour is close enough to magenta to work with the traditional magenta -- portals, close enough to red to work for a red portal, and also close enough to red to -- work with blue & cyan portals - since blue portals are sometimes portrayed as being red -- from the opposite side / from the inside. a = 160, r = 128, g = 0, b = 80 }, sunlight_propagates = true, use_texture_alpha = minetest.features.use_texture_alpha_string_modes and "blend" or true, walkable = false, diggable = false, pointable = false, buildable_to = false, is_ground_content = false, drop = "", light_source = 5, node_box = { type = "fixed", fixed = { {-0.5, -0.5, -0.1, 0.5, 0.5, 0.1}, }, }, groups = {not_in_creative_inventory = 1}, mesecons = {receptor = { state = "on", rules = function(node) return nether.get_mesecon_emission_rules_from_colorfacedir(node.param2) end }} } -- Call only at load time function nether.register_wormhole_node(name, nodedef) assert(name ~= nil, "Unable to register wormhole node: Name is nil") assert(nodedef ~= nil, "Unable to register wormhole node ''" .. name .. "'': nodedef is nil") for key, value in pairs(wormhole_nodedef_default) do if nodedef[key] == nil then nodedef[key] = value end end minetest.register_node(name, nodedef) end -- The fallback defaults for registered portaldef tables local portaldef_default = { title = S("Untitled portal"), book_of_portals_pagetext = S("We know almost nothing about this portal"), shape = nether.PortalShape_Traditional, wormhole_node_name = "nether:portal", wormhole_node_color = 0, frame_node_name = "default:obsidian", particle_texture = "nether_particle.png", particle_texture_animation = nil, particle_texture_scale = 1, sounds = { ambient = {name = "nether_portal_ambient", gain = 0.6, length = 3}, ignite = {name = "nether_portal_ignite", gain = 0.7}, extinguish = {name = "nether_portal_extinguish", gain = 0.6}, teleport = {name = "nether_portal_teleport", gain = 0.3} } } function nether.register_portal(name, portaldef) assert(name ~= nil, "Unable to register portal: Name is nil") assert(portaldef ~= nil, "Unable to register portal ''" .. name .. "'': portaldef is nil") if nether.registered_portals[name] ~= nil then minetest.log("error", "Unable to register portal: '" .. name .. "' is already in use") return false; end portaldef.name = name portaldef.mod_name = minetest.get_current_modname() or "" -- use portaldef_default for any values missing from portaldef or portaldef.sounds if portaldef.sounds ~= nil then setmetatable(portaldef.sounds, {__index = portaldef_default.sounds}) end setmetatable(portaldef, {__index = portaldef_default}) if portaldef.particle_color == nil then -- default the particle colours to be the same as the wormhole colour assert(portaldef.wormhole_node_color >= 0 and portaldef.wormhole_node_color < 8, "portaldef.wormhole_node_color must be between 0 and 7 (inclusive)") portaldef.particle_color = nether.portals_palette[portaldef.wormhole_node_color].asString end if portaldef.particle_texture_colored == nil then -- Combine the particle texture with the particle color unless a particle_texture_colored was specified. if type(portaldef.particle_texture) == "table" and portaldef.particle_texture.animation ~= nil then portaldef.particle_texture_colored = portaldef.particle_texture.name .. "^[colorize:" .. portaldef.particle_color .. ":alpha" portaldef.particle_texture_animation = portaldef.particle_texture.animation portaldef.particle_texture_scale = portaldef.particle_texture.scale or 1 else portaldef.particle_texture_colored = portaldef.particle_texture .. "^[colorize:" .. portaldef.particle_color .. ":alpha" end end if portaldef.find_surface_anchorPos == nil then -- default to using find_surface_target_y() portaldef.find_surface_anchorPos = function(pos, player_name) local destination_pos = {x = pos.x, y = 0, z = pos.z} local existing_portal_location, existing_portal_orientation = nether.find_nearest_working_portal(name, destination_pos, 10, 0) -- a y_factor of 0 makes the search ignore the altitude of the portals (as long as they are outside the realm) if existing_portal_location ~= nil then return existing_portal_location, existing_portal_orientation else destination_pos.y = nether.find_surface_target_y(destination_pos.x, destination_pos.z, name, player_name) return destination_pos end end end if test_portaldef_is_valid(portaldef) then -- check whether the portal definition clashes with anyone else's portal local p1, p2 = portaldef.shape:get_p1_and_p2_from_anchorPos(vector.new(), 0) local existing_portaldef = get_portal_definition(portaldef.frame_node_name, p1, p2) if existing_portaldef ~= nil then minetest.log("error", portaldef.mod_name .." tried to register a portal '" .. portaldef.name .. "' made of " .. portaldef.frame_node_name .. ", but it is the same material and shape as the portal '" .. existing_portaldef.name .. "' already registered by " .. existing_portaldef.mod_name .. ". Edit the values one of those mods uses in its call to nether.register_portal() if you wish to resolve this clash.") else -- the new portaldef is good nether.registered_portals[portaldef.name] = portaldef -- Update registered_portals_count local portalCount = 0 for _ in pairs(nether.registered_portals) do portalCount = portalCount + 1 end nether.registered_portals_count = portalCount create_book_of_portals() if not nether.is_frame_node[portaldef.frame_node_name] then -- add portal functions to the nodedef being used for the portal frame register_frame_node(portaldef.frame_node_name) nether.is_frame_node[portaldef.frame_node_name] = true end return true end end return false end function nether.unregister_portal(name) assert(name ~= nil, "Cannot unregister portal: Name is nil") local portaldef = nether.registered_portals[name] local result = portaldef ~= nil if portaldef ~= nil then nether.registered_portals[name] = nil local portals_still_using_frame_node = list_portal_definitions_for_frame_node(portaldef.frame_node_name) if next(portals_still_using_frame_node) == nil then -- no portals are using this frame node any more unregister_frame_node(portaldef.frame_node_name) nether.is_frame_node[portaldef.frame_node_name] = nil end end return result end function nether.register_portal_ignition_item(item_name, ignition_failure_sound) local old_on_place = minetest.registered_items[item_name].on_place or minetest.item_place minetest.override_item(item_name, { on_place = function(stack, placer, pt, ...) if pt.under and nether.is_frame_node[minetest.get_node(pt.under).name] then local done = ignite_portal(pt.under, placer:get_player_name()) if done and not minetest.settings:get_bool("creative_mode") then stack:take_item() end if not done and ignition_failure_sound ~= nil then minetest.sound_play(ignition_failure_sound, {pos = pt.under, max_hear_distance = 10}) end return stack end return old_on_place(stack, placer, pt, ...) end, }) ignition_item_name = item_name end -- use this when determining where to spawn a portal, to avoid overwriting player builds -- It checks the area for any nodes that aren't ground or trees. -- player_name is optional, allowing a player to spawn a remote portal in their own protected areas. -- (Water also fails this test, unless it is unemerged) function nether.volume_is_natural_and_unprotected(minp, maxp, player_name) local c_air = minetest.get_content_id("air") local c_ignore = minetest.get_content_id("ignore") local vm = minetest.get_voxel_manip() local emin, emax = vm:read_from_map(minp, maxp) local area = VoxelArea:new({MinEdge = emin, MaxEdge = emax}) local data = vm:get_data() for z = minp.z, maxp.z do for y = minp.y, maxp.y do local vi = area:index(minp.x, y, z) for x = minp.x, maxp.x do local id = data[vi] -- Existing node if id == nil then debugf("nil block at index " .. vi) end if id ~= c_air and id ~= c_ignore and id ~= nil then -- checked for common natural or not emerged local name = minetest.get_name_from_content_id(id) local nodedef = minetest.registered_nodes[name] if nodedef and not nodedef.is_ground_content then -- trees are natural but not "ground content" local node_groups = nodedef.groups if node_groups == nil or (node_groups.tree == nil and node_groups.leaves == nil and node_groups.leafdecay == nil) then debugf("volume_is_natural_and_unprotected() found unnatural node %s", name) return false end end end vi = vi + 1 end end end if minetest.is_area_protected(minp, maxp, player_name or "") then debugf("Volume is protected against player '%s', %s-%s", player_name, minp, maxp) return false; end debugf("Volume is natural and unprotected for player '%s', %s-%s", player_name, minp, maxp) return true end -- Deprecated, use nether.volume_is_natural_and_unprotected() instead. function nether.volume_is_natural(minp, maxp) if nether.deprecation_warning_volume_is_natural == nil then local stack = debug.traceback("", 2); local calling_func = (string.split(stack, "\n", false, 2, false)[2] or ""):trim() minetest.log("warning", "Deprecated function \"nether.volume_is_natural()\" invoked, use \"nether.volume_is_natural_and_unprotected()\" instead. " .. calling_func) nether.deprecation_warning_volume_is_natural = true; end return nether.volume_is_natural_and_unprotected(minp, maxp) end -- Gets the volume that may be altered if a portal is placed at the anchor_pos -- orientation is optional, but specifying it will reduce the volume returned -- portal_name is optional, but specifying it will reduce the volume returned -- returns minp, maxp function nether.get_schematic_volume(anchor_pos, orientation, portal_name) if orientation == nil then -- Return a volume large enough for any orientation local minp0, maxp0 = nether.get_schematic_volume(anchor_pos, 0, portal_name) local minp1, maxp1 = nether.get_schematic_volume(anchor_pos, 1, portal_name) -- ToDo: If an asymmetric portal is used with an anchor not at the center of the -- schematic then we will also need to check orientations 3 and 4. -- (The currently existing portal-shapes are not affected) return {x = math.min(minp0.x, minp1.x), y = math.min(minp0.y, minp1.y), z = math.min(minp0.z, minp1.z)}, {x = math.max(maxp0.x, maxp1.x), y = math.max(maxp0.y, maxp1.y), z = math.max(maxp0.z, maxp1.z)} end -- Assume the largest possible portal shape unless we know it's a smaller one. local shape_defintion = nether.PortalShape_Circular if portal_name ~= nil and nether.registered_portals[portal_name] ~= nil then shape_defintion = nether.registered_portals[portal_name].shape end local size = shape_defintion.schematic.size local minp = shape_defintion.get_schematicPos_from_anchorPos(anchor_pos, orientation); local maxp if (orientation % 2) == 0 then maxp = {x = minp.x + size.x - 1, y = minp.y + size.y - 1, z = minp.z + size.z - 1} else maxp = {x = minp.x + size.z - 1, y = minp.y + size.y - 1, z = minp.z + size.x - 1} end return minp, maxp end -- Can be used when implementing custom find_surface_anchorPos() functions -- portal_name is optional, providing it allows existing portals on the surface to be reused, and -- a potentially smaller volume to be checked by volume_is_natural_and_unprotected(). -- player_name is optional, allowing a player to spawn a remote portal in their own protected areas. function nether.find_surface_target_y(target_x, target_z, portal_name, player_name) assert(target_x ~= nil and target_z ~= nil, "Arguments `target_x` and `target_z` cannot be nil when calling find_surface_target_y()") -- default to starting the search at -16 (probably underground) if we don't know the -- surface, like paramat's original code from before get_spawn_level() was available: -- https://github.com/minetest-mods/nether/issues/5#issuecomment-506983676 local start_y = -16 -- try to spawn on surface first if minetest.get_spawn_level ~= nil then -- older versions of Minetest don't have this local surface_level = minetest.get_spawn_level(target_x, target_z) if surface_level ~= nil then -- test this since get_spawn_level() can return nil over water or steep/high terrain -- get_spawn_level() tends to err on the side of caution and spawns the player a -- block higher than the ground level. The implementation is mapgen specific -- and -2 seems to be the right correction for v6, v5, carpathian, valleys, and flat, -- but v7 only needs -1. -- Perhaps this was not always the case, and -2 may be too much in older versions -- of minetest, but half-buried portals are perferable to floating ones, and they -- will clear a suitable hole around themselves. if minetest.get_mapgen_setting("mg_name") == "v7" then surface_level = surface_level - 1 else surface_level = surface_level - 2 end start_y = surface_level end end local minp_schem, maxp_schem = nether.get_schematic_volume({x = target_x, y = 0, z = target_z}, nil, portal_name) local minp = {x = minp_schem.x, y = 0, z = minp_schem.z} local maxp = {x = maxp_schem.x, y = 0, z = maxp_schem.z} -- Starting searchstep at -16 and making it larger by 2 after each step gives a 20-step search range down to -646: -- 0, -16, -34, -54, -76, -100, -126, -154, -184, -216, -250, -286, -324, -364, -406, -450, -496, -544, -594, -646 local searchstep = -16; local y = start_y while y > start_y - 650 do -- Check volume for non-natural nodes minp.y = minp_schem.y + y maxp.y = maxp_schem.y + y if nether.volume_is_natural_and_unprotected(minp, maxp, player_name) then return y elseif portal_name ~= nil and nether.registered_portals[portal_name] ~= nil then -- players have built here - don't grief. -- but reigniting existing portals in portal rooms is fine - desirable even. local anchorPos, orientation, is_ignited = is_within_portal_frame(nether.registered_portals[portal_name], {x = target_x, y = y, z = target_z}) if anchorPos ~= nil then debugf("volume_is_natural_and_unprotected check failed, but a portal frame is here %s, so this is still a good target y level", anchorPos) return y end end y = y + searchstep searchstep = searchstep - 2 end return nil -- Portal ignition failure. Possibly due to a large protected area. end -- Returns the anchorPos, orientation of the nearest portal, or nil. -- A y_factor of 0 means y does not affect the distance_limit, a y_factor of 1 means y is included, -- and a y_factor of 2 would squash the search-sphere by a factor of 2 on the y-axis, etc. -- Pass a negative distance_limit to indicate no distance limit function nether.find_nearest_working_portal(portal_name, anchorPos, distance_limit, y_factor) local portal_definition = nether.registered_portals[portal_name] assert(portal_definition ~= nil, "find_nearest_working_portal() called with portal_name '" .. portal_name .. "', but no portal is registered with that name.") assert(anchorPos ~= nil, "Argument `anchorPos` cannot be nil when calling find_nearest_working_portal()") local contenders = list_closest_portals(portal_definition, anchorPos, distance_limit, y_factor) -- sort by distance local dist_list = {} for dist, _ in pairs(contenders) do table.insert(dist_list, dist) end table.sort(dist_list) for _, dist in ipairs(dist_list) do local portal_info = contenders[dist] debugf("checking portal from mod_storage at %s orientation %s", portal_info.anchorPos, portal_info.orientation) -- the mod_storage list of portals is unreliable - e.g. it won't know if inactive portals have been -- destroyed, so check the portal is still there local portalFound, portalIsActive = is_portal_at_anchorPos(portal_definition, portal_info.anchorPos, portal_info.orientation, true) if portalFound then return portal_info.anchorPos, portal_info.orientation else debugf("Portal wasn't found, removing portal from mod_storage at %s orientation %s", portal_info.anchorPos, portal_info.orientation) -- The portal at that location must have been destroyed remove_portal_location_info(portal_name, portal_info.anchorPos) end end return nil end nether-3.6/portal_api.txt000066400000000000000000000301571461347743200155630ustar00rootroot00000000000000Portal API Reference ==================== The portal system used to get to the Nether can be used to create portals to other realms. Pick a node type to have your portals built from, a shape in which the portals must be built, and provide 3 functions for portals to find their destination with: * `find_realm_anchorPos(surface_anchorPos)` * `find_surface_anchorPos(realm_anchorPos)` * `is_within_realm(pos)` Optionally decorate by choosing portal colors, particles, media etc. See `init.lua` and `portal_examples.lua` for examples of 3 different portals. Portal code is more efficient when each type of portal uses a different type of node to build its frame out of - consider creating your own node for players to build portals from. However it is possible to register more than one kind of portal with the same frame material — such as obsidian — provided the size of the PortalShape is distinct from any other type of portal that is using the same node for its frame, and portal sizes remain small. The Nether provides three variants of Nether basalt to ensure there are alternatives to obsidian for other mods to use as portalstones. Realms ------ This API uses the concept of a realm for each type of portal. If a portal is outside its realm then it links to a portal inside the realm, if a portal is inside its realm then it links to the outside. You get to decide what constitutes your realm by implementing the function `is_within_realm(position)`. For example, the Nether realm is defined as existing at a certain depth and anything above that depth is considered outside the Realm. In contrast, the "Surface portal" - an example in portal_examples.lua, only uses one realm. Its `is_within_realm()` function always returns true so that any time a portal is opened it will use `find_surface_anchorPos()`. Note that the name "find_surface_anchorPos" is a Nether-centric misnomer, as different types of portals are free to use different definitions of a realm such that leaving the realm might not be synonymous with travelling to the surface. Helper functions ---------------- * `nether.volume_is_natural_and_unprotected(minp, maxp, player_name)`: returns a boolean. * use this when determining where to spawn a portal, to avoid overwriting player builds. It checks the area for any nodes that aren't ground or trees. Water will fail this test, unless it is unemerged. * player_name is optional, providing it allows the player's own protected areas to be treated as unprotected. * `nether.find_surface_target_y(target_x, target_z, portal_name, player_name)`: returns a suitable anchorPos * Can be used when implementing custom find_surface_anchorPos() functions * portal_name is optional, providing it allows existing portals on the surface to be reused. * player_name is optional, providing it prevents the exclusion of surface target areas which are protected by the player. * May return nil in extreme circumstances, such as the surface being protected down to a great depth. * `nether.find_nearest_working_portal(portal_name, anchorPos, distance_limit, y_factor)`: returns (anchorPos, orientation), or nil if no portal was found within the distance_limit. * A y_factor of 0 means y does not affect the distance_limit, a y_factor of 1 means y is included (the default if y_factor is nil), and a y_factor of 2 would squash the search-sphere by a factor of 2 on the y-axis, etc. * Only portals in the same realm as the anchorPos will be returned, even if y_factor is 0. * Pass a nil (or negative) distance_limit to indicate no distance limit API functions ------------- Call these functions only at load time: * `nether.register_portal(name, portal_definition)` * Returns true on success. Can return false if the portal definition clashes with a portal already registered by another mod, e.g. if the size and frame node is not unique. A false return value should be handled, you could: * Fall back to using a secondary material for portals to be built with. * Use error() to exit lua with a message explaining how two mods are clashing and how it can be resolved. * Continue without a portal (the reason will be logged for the user). * `nether.register_portal_ignition_item(name, ignition_failure_sound)` * ignition_failure_sound is optional, it plays any time an attempt to use the item occurs if a portal is not ignited. * `nether.register_wormhole_node(name, nodedef_overrides)` * Can be used to register wormhole nodes with a different post_effect_color from the "nether:portal" node. "Post effect color" is the tint the world takes on when you are standing inside a portal. `post_effect_color` is the only key/value that is needed in the nodedef_overrides table to achieve that, but the function allows any nodedef key/value to be specified/overridden. * After `register_wormhole_node()`, invoke `register_portal()` and include `wormhole_node_name` in the portal_definition, assigning it the name of the new wormhole node. * `nether.unregister_portal(name)` * Unregisters the portal from the engine, and deletes the entry with key `name` from `nether.registered_portals` and associated internal tables. * Returns true on success * You will probably never need to call this, it exists only for completeness. Portal definition ----------------- Used by `nether.register_portal`. { frame_node_name = "default:obsidian", -- Required. For best results, have your portal constructed of a -- material nobody else is using. frame_node_color = 0, -- Optional. -- A value from 0 to 7. Only used if the frame node's paramtype2 is -- "colorfacedir", in which case this color will be used when a remote -- portal is created. shape = nether.PortalShape_Traditional, -- Optional. -- Shapes available are: -- nether.PortalShape_Traditional (default) -- nether.PortalShape_Circular -- nether.PortalShape_Platform -- New shapes can be created, but are beyond the scope of this guide. wormhole_node_name = "nether:portal", -- Optional. Allows a custom wormhole node to be specified. -- Useful if you want the portals to have a different post_effect_color -- or texture. -- The Nether mod provides: -- "nether:portal" (default) -- "nether:portal_alt" wormhole_node_color = 0, -- Optional. Defaults to 0/magenta. -- A value from 0 to 7 corresponding to the color of pixels in -- nether_portals_palette.png: -- 0 traditional/magenta -- 1 black -- 2 blue -- 3 green -- 4 cyan -- 5 red -- 6 yellow -- 7 white particle_color = "#808", -- Optional. Will default to a colour matching the wormhole_node_color -- if not specified. particle_texture = "image.png", -- Optional. Hardware colouring (i.e. particle_color) is applied to -- this texture, use particle_texture_colored instead if you want to -- use the colors of the image. -- Animation and particle scale may also be specified, e.g: -- particle_texture = { -- name = "nether_particle_anim1.png", -- animation = { -- type = "vertical_frames", -- aspect_w = 7, -- aspect_h = 7, -- length = 1, -- }, -- scale = 1.5 -- }, -- See lua_api.txt for Tile Animation definition -- Some animated and non-animated textures are provided by this mod: -- nether_particle.png (original) -- nether_particle_anim1.png (stars) -- nether_particle_anim2.png (bubbles) -- nether_particle_anim3.png (sparks) -- nether_particle_anim4.png (particles) title = "Gateway to Moria", -- Optional. Provides a title for the portal. -- Used in the Book of Portals or Help modpack. book_of_portals_pagetext = "Everything I need the player to know", -- Optional. Provides the text for the portal in the Book of Portals -- and Help modpack. -- The Book of Portals is a book that can be found in chests, and -- provides players with instructions on how to build and use the -- portal, so be sure to mention the node type the frame must be built -- from. -- This can also provide flavortext or details about where the portal -- will take the player. sounds = { ambient = , -- if the ambient SimpleSoundSpec is a table it can also contain a -- "length" int, which is the number of seconds to wait before -- repeating the ambient sound. Default is 3. ignite = , extinguish = , teleport = , } -- sounds is optional within_realm = function(pos), -- Required. Return true if a portal at pos is in the realm, rather -- than the surface world. -- Ideally implementations are fast, as this function can be used to -- sift through a list of portals. find_realm_anchorPos = function(surface_anchorPos, player_name), -- Required. Return a position in the realm that a portal created at -- surface_anchorPos will link to. -- Return an anchorPos or (anchorPos, orientation) -- If orientation is not specified then the orientation of the surface -- portal will be used. -- If the location of an existing portal is returned then include the -- orientation, otherwise the existing portal could be overwritten by -- a new one with the orientation of the surface portal. -- Return nil, or a position with a nil y component, to prevent the -- portal from igniting. -- player_name may be "", e.g. if the portal was ignited by a mesecon, -- and is provided for use with volume_is_natural_and_unprotected() etc. find_surface_anchorPos = function(realm_anchorPos, player_name), -- Optional. If you don't implement this then a position near the -- surface will be picked. -- Return an anchorPos or (anchorPos, orientation) -- The name of this function is a Nether-centric misnomer. It should -- return a position outside the realm, and different types of portals -- are free to use different definitions of a realm such that leaving -- the realm might not be synonymous with travelling to the surface. -- If orientation is not specified then the orientation of the realm -- portal will be used. -- If the location of an existing portal is returned then include the -- orientation, otherwise the existing portal could be overwritten by -- a new one with the orientation of the realm portal. -- Return nil, or a position with a nil y component, to prevent the -- portal from igniting. -- player_name may be "", e.g. if the portal was ignited by a mesecon, -- and is provided for use with volume_is_natural_and_unprotected() etc. on_run_wormhole = function(portalDef, anchorPos, orientation), -- invoked once per second per portal on_extinguish = function(portalDef, anchorPos, orientation), -- invoked when a portal is extinguished, including when the portal -- it connected to was extinguished. on_player_teleported = function(portalDef, player, oldPos, newPos), -- invoked immediately after a player is teleported on_ignite = function(portalDef, anchorPos, orientation) -- invoked when a player or mesecon ignites a portal on_created = function(portalDef, anchorPos, orientation) -- invoked when a portal creates a remote twin, this is usually when -- a player travels through a portal for the first time. } nether-3.6/portal_examples.lua000066400000000000000000000334521461347743200165730ustar00rootroot00000000000000--[[ Nether mod portal examples for Minetest These portal API examples work independently of the Nether realm and Nether portal. To try these examples, enable them in: Minetest -> Settings -> All settings -> Mods -> nether Once enabled, details on how to build them can be found in dungeon chests in the book of portals. -- Copyright (C) 2020 Treer Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ]]-- local S = nether.get_translator local ENABLE_PORTAL_EXAMPLE_FLOATLANDS = false local ENABLE_PORTAL_EXAMPLE_SURFACETRAVEL = false -- Sets how far a Surface Portal will travel, measured in cells along the Moore curve, -- which are about 117 nodes square each. Larger numbers will generally mean further distance -- as-the-crow-flies, but this will not always be true due to the how the Moore curve -- frequently doubles back upon itself. -- This doubling-back prevents the surface portal from taking players easily accross the -- map - the curve is 262144 cells long! local SURFACE_TRAVEL_DISTANCE = 26 --=================================================-- -- Portal to the Floatlands, playable code example -- --==================================================-- local FLOATLANDS_ENABLED local FLOATLAND_LEVEL = 1280 if minetest.settings:get_bool("nether_enable_portal_example_floatlands", ENABLE_PORTAL_EXAMPLE_FLOATLANDS) or ENABLE_PORTAL_EXAMPLE_FLOATLANDS then local floatlands_flavortext = "" if minetest.get_mapgen_setting("mg_name") == "v7" then local mgv7_spflags = minetest.get_mapgen_setting("mgv7_spflags") FLOATLANDS_ENABLED = mgv7_spflags ~= nil and mgv7_spflags:find("floatlands") ~= nil and mgv7_spflags:find("nofloatlands") == nil FLOATLAND_LEVEL = minetest.get_mapgen_setting("mgv7_floatland_level") or 1280 if FLOATLANDS_ENABLED then floatlands_flavortext = "\n\n " .. S("There is a floating land of hills and forests up there, over the edges of which is a perilous drop all the way back down to sea level. We have not found how far these pristine lands extend. I have half a mind to retire there one day.") end end nether.register_portal("floatlands_portal", { shape = nether.PortalShape_Platform, frame_node_name = "default:ice", wormhole_node_color = 7, -- 7 is white particle_texture = { name = "nether_particle_anim1.png", animation = { type = "vertical_frames", aspect_w = 7, aspect_h = 7, length = 1, }, scale = 1.5 }, title = S("Floatlands Portal"), book_of_portals_pagetext = S([[Requiring 21 blocks of ice, and constructed in the shape of a 3 × 3 platform with walls, or like a bowl. A finished platform is 2 blocks high, and 5 blocks wide at the widest in both directions. This portal is different to the others, rather than acting akin to a doorway it appears to the eye more like a small pool of water which can be stepped into. Upon setting foot in the portal we found ourselves at a tremendous altitude.@1]], floatlands_flavortext), is_within_realm = function(pos) -- return true if pos is inside the Nether return pos.y > FLOATLAND_LEVEL - 200 end, find_realm_anchorPos = function(surface_anchorPos, player_name) -- TODO: Once paramat finishes adjusting the floatlands, implement a surface algorithm that finds land local destination_pos = {x = surface_anchorPos.x ,y = FLOATLAND_LEVEL + 2, z = surface_anchorPos.z} -- a y_factor of 0 makes the search ignore the altitude of the portals (as long as they are in the Floatlands) local existing_portal_location, existing_portal_orientation = nether.find_nearest_working_portal("floatlands_portal", destination_pos, 10, 0) if existing_portal_location ~= nil then return existing_portal_location, existing_portal_orientation else return destination_pos end end }) end --==============================================-- -- Surface-travel portal, playable code example -- --==============================================-- -- These Moore Curve functions required by surface_portal's find_surface_anchorPos() will -- be assigned later in this file. local get_moore_distance -- will be function get_moore_distance(cell_count, x, y): integer local get_moore_coords -- will be function get_moore_coords(cell_count, distance): pos2d if minetest.settings:get_bool("nether_enable_portal_example_surfacetravel", ENABLE_PORTAL_EXAMPLE_SURFACETRAVEL) or ENABLE_PORTAL_EXAMPLE_SURFACETRAVEL then nether.register_portal("surface_portal", { shape = nether.PortalShape_Circular, frame_node_name = "default:tinblock", wormhole_node_name = "nether:portal_alt", wormhole_node_color = 4, -- 4 is cyan title = S("Surface Portal"), book_of_portals_pagetext = S([[Requiring 16 blocks of tin and constructed in a circular fashion, a finished frame is seven blocks wide, seven blocks high, and stands vertically like a doorway. These travel a distance along the ground, and even when constructed deep underground will link back up to the surface. They appear to favor a strange direction, with the exit portal linking back only for as long as the portal stays open — attempting to reopen a portal from the exit doorway leads to a new destination along this favored direction. It has stymied our ability to study the behavior of these portals because without constructing dual portals and keeping both open it's hard to step through more than one and still be able to return home. Due to such difficulties, we never learned what determines the direction and distance where the matching twin portal will appear, and I have lost my friend and protégé. In cavalier youth and with little more than a rucksack, Coudreau has decided to follow the chain as far as it goes, and has not been seen since. Coudreau believes it works in epicycles, but I am not convinced. Still, I cling to the hope that one day the portal will open and Coudreau will step out from whichever place leads to this one, perhaps with an epic tale to tell.]]), is_within_realm = function(pos) -- Always return true, because these portals always just take you around the surface -- rather than taking you to a different realm return true end, find_realm_anchorPos = function(surface_anchorPos, player_name) -- This function isn't needed, since this type of portal always goes to the surface minetest.log("error" , "find_realm_anchorPos called for surface portal") return {x=0, y=0, z=0} end, find_surface_anchorPos = function(realm_anchorPos, player_name) -- A portal definition doesn't normally need to provide a find_surface_anchorPos() function, -- since find_surface_target_y() will be used by default, but these portals travel around the -- surface (following a Moore curve) so will be calculating a different x and z to realm_anchorPos. local cellCount = 512 local maxDistFromOrigin = 30000 -- the world edges are at X=30927, X=−30912, Z=30927 and Z=−30912 -- clip realm_anchorPos to maxDistFromOrigin, and move the origin so that all values are positive local x = math.min(maxDistFromOrigin, math.max(-maxDistFromOrigin, realm_anchorPos.x)) + maxDistFromOrigin local z = math.min(maxDistFromOrigin, math.max(-maxDistFromOrigin, realm_anchorPos.z)) + maxDistFromOrigin local divisor = math.ceil(maxDistFromOrigin * 2 / cellCount) local distance = get_moore_distance(cellCount, math.floor(x / divisor + 0.5), math.floor(z / divisor + 0.5)) local destination_distance = (distance + SURFACE_TRAVEL_DISTANCE) % (cellCount * cellCount) local moore_pos = get_moore_coords(cellCount, destination_distance) local target_x = moore_pos.x * divisor - maxDistFromOrigin local target_z = moore_pos.y * divisor - maxDistFromOrigin local search_radius = divisor / 2 - 5 -- any portal within this area will do -- a y_factor of 0 makes the search ignore the altitude of the portals local existing_portal_location, existing_portal_orientation = nether.find_nearest_working_portal("surface_portal", {x = target_x, y = 0, z = target_z}, search_radius, 0) if existing_portal_location ~= nil then -- use the existing portal that was found near target_x, target_z return existing_portal_location, existing_portal_orientation else -- find a good location for the new portal, or if that isn't possible then at -- least adjust the coords a little so portals don't line up in a grid local adj_x, adj_z = 0, 0 -- Deterministically look for a location in the cell where get_spawn_level() can give -- us a surface height, since nether.find_surface_target_y() works *much* better when -- it can use get_spawn_level() local prng = PcgRandom( -- seed the prng so that all portals for these Moore Curve coords will use the same random location moore_pos.x * 65732 + moore_pos.y * 729 + minetest.get_mapgen_setting("seed") * 3 ) local attemptLimit = 15 -- how many attempts we'll make at finding a good location for attempt = 1, attemptLimit do adj_x = math.floor(prng:rand_normal_dist(-search_radius, search_radius, 2) + 0.5) adj_z = math.floor(prng:rand_normal_dist(-search_radius, search_radius, 2) + 0.5) if minetest.get_spawn_level == nil or minetest.get_spawn_level(target_x + adj_x, target_z + adj_z) ~= nil then -- Found a location which will be at ground level - unless a player has built there. -- Or this is MT 0.4 which does not have get_spawn_level(), so there's no point looking -- at any further further random locations. break end end local destination_pos = {x = target_x + adj_x, y = 0, z = target_z + adj_z} destination_pos.y = nether.find_surface_target_y(destination_pos.x, destination_pos.z, "surface_portal", player_name) return destination_pos end end }) end --=========================================-- -- Hilbert curve and Moore curve functions -- --=========================================-- -- These are space-filling curves, used by the surface_portal example as a way to determine where -- to place portals. https://en.wikipedia.org/wiki/Moore_curve -- Flip a quadrant on a diagonal axis -- cell_count is the number of cells across the square is split into, and must be a power of 2 -- if flip_twice is true then pos does not change (even numbers of flips cancel out) -- if flip_direction is true then the position is flipped along the \ diagonal -- if flip_direction is false then the position is flipped along the / diagonal local function hilbert_flip(cell_count, pos, flip_direction, flip_twice) if not flip_twice then if flip_direction then pos.x = (cell_count - 1) - pos.x; pos.y = (cell_count - 1) - pos.y; end local temp_x = pos.x; pos.x = pos.y; pos.y = temp_x; end end local function test_bit(cell_count, value, flag) local bit_value = cell_count / 2 while bit_value > flag and bit_value >= 1 do if value >= bit_value then value = value - bit_value end bit_value = bit_value / 2 end return value >= bit_value end -- Converts (x,y) to distance -- starts at bottom left corner, i.e. (0, 0) -- ends at bottom right corner, i.e. (cell_count - 1, 0) local function get_hilbert_distance (cell_count, x, y) local distance = 0 local pos = {x=x, y=y} local rx, ry local s = cell_count / 2 while s > 0 do if test_bit(cell_count, pos.x, s) then rx = 1 else rx = 0 end if test_bit(cell_count, pos.y, s) then ry = 1 else ry = 0 end local rx_XOR_ry = rx if ry == 1 then rx_XOR_ry = 1 - rx_XOR_ry end -- XOR'd ry against rx distance = distance + s * s * (2 * rx + rx_XOR_ry) hilbert_flip(cell_count, pos, rx > 0, ry > 0); s = math.floor(s / 2) end return distance; end -- Converts distance to (x,y) local function get_hilbert_coords(cell_count, distance) local pos = {x=0, y=0} local rx, ry local s = 1 while s < cell_count do rx = math.floor(distance / 2) % 2 ry = distance % 2 if rx == 1 then ry = 1 - ry end -- XOR ry with rx hilbert_flip(s, pos, rx > 0, ry > 0); pos.x = pos.x + s * rx pos.y = pos.y + s * ry distance = math.floor(distance / 4) s = s * 2 end return pos end -- Converts (x,y) to distance -- A Moore curve is a variation of the Hilbert curve that has the start and -- end next to each other. -- Top middle point is the start/end location get_moore_distance = function(cell_count, x, y) local quadLength = cell_count / 2 local quadrant = 1 - math.floor(y / quadLength) if math.floor(x / quadLength) == 1 then quadrant = 3 - quadrant end local flipDirection = x < quadLength local pos = {x = x % quadLength, y = y % quadLength} hilbert_flip(quadLength, pos, flipDirection, false) return (quadrant * quadLength * quadLength) + get_hilbert_distance(quadLength, pos.x, pos.y) end -- Converts distance to (x,y) -- A Moore curve is a variation of the Hilbert curve that has the start and -- end next to each other. -- Top middle point is the start/end location get_moore_coords = function(cell_count, distance) local quadLength = cell_count / 2 local quadDistance = quadLength * quadLength local quadrant = math.floor(distance / quadDistance) local flipDirection = distance * 2 < cell_count * cell_count local pos = get_hilbert_coords(quadLength, distance % quadDistance) hilbert_flip(quadLength, pos, flipDirection, false) if quadrant >= 2 then pos.x = pos.x + quadLength end if quadrant % 3 == 0 then pos.y = pos.y + quadLength end return pos end nether-3.6/screenshot.png000066400000000000000000002546661461347743200155700ustar00rootroot00000000000000PNG  IHDR,ݽKbKGD pHYs  tIME#c|1 IDATx\ˮl˒%4猈>{g>Tj! zhѦED %P |(˽Z+bf6h}RD/flB?j2h"VU&Hª^Z.5z|xݻ/nE o[F_?}|[/e﷭/Ju7~^m?<~?˻۲>n}EL6J>gff^R `mf uZQa1)E5e"3 E B"3Bc<3u#EQdlMSdL*D(LK)K3E$b 1S"YU!\vxaBV3QQJjB@@-V  $@dzb3KU R@ig:{F"g>5 O9=A^HK$A!&13 1S"*3Q.}2f 2iMQA\,FT&RVժɠ@H@MEBD@F2]LrF2g"3FgPd+F_{̧FDzp־aC ^y[ת>g&D^oKtd0R &)LmǘU/k` ܩJ P 1 cIzZJ BLjU) jQ+bFHvPL$d ɘ*"СPH9ɔR3{3k0w1{rtw.hEb?r =JZԤ{d9w/T(Y )H'Q6/gk14I@1#^] X@ EJ1)RJ(9GMviZb|}4%=~|T=2rL~R[Ogޮ-S,B!Kߎ*׵ed[Kd.Ś#< H!L_hm&3VD9Gp˥`b>ۥ\j^EU|LzulK3'~Y@c4U?*J՜<[>]DUXeE5'ybVtϏo[/F]\.E)cԪ".*ó@fr3SV}8ukxB뵮{(h@@JVʾOAZI@ 2qF7aiZtCdxL|+(Ù8LJNaǷlglcŶ$JLFR{F59rS:я)hXDdzp>BNE6r瑥P੊*&E3mHUy.Ib-"8j9Okkș|yG1zxd?m|"c뵮VuQ?| 3j,U/׆LӾcYV2(Euxd1ӊ`O!D&'@TLtLD)kUʼn³T1#f8raRn{03٧%#Ag() i:b'VUeUQL&ɷ_2!b3gvg_ڶ 1{g%3PH#%xDiE1{xDH#Ee}ƾϯЩ'CPezo[lcpZBwOoDPMzSE.niJ=Zu;>^繴bZæsr-tkaOqmk]by](gHI1U0%%s )H DH!E*d[D$3S<Ŭjk%I&!l9RJSU.z%IBo~y>Ǿ"+cyy=v}fZP {&Era{9k-Xo9G޷ڇ4#c滧K֊(22bz3|yzj*A1> z32k%9r{bmK@jRoG DB֪6jfT@$WV%=;]HfJQYr{.,EM$h.3bLnWR@EX<4dB<Z1p<Y 䘜sF62L Q̊H.ժe=髑:b0tVLjҬ$G$Y!"9SAU3/:3݋r)Em r,HOǜ Z)`y8ZJ艤'fWZD :݋9gBLRd )%>1GjVcZњ9ȄELuWWnQJp˵ E2ٻ)}DaR{<_zi˯,fuwתX)QnOc]W$DE fr-EAh j7 ?3ULf"=Do!&mwĶ{1c᪒5PRz) lȌl\jI-stn/<]?g!`۲.%L#*Tf"EUej&@#H3 GpaZumsNODH)PSjx5UXZHbb*YΑ&I,?%GB9s8Q}J)b$& J>ܪ!{h=ǜv)yOa9G^2_]T9{( b֒]ǘIfhYʙ4ac(KٻLkIk2 HB@?yFR3BcDY8r0#fcm<_5)?)3|沚ZЊUTI!-U3EjUR B_+ hĤ(& ŖDeaU3sr΄JP$2j@"f&VVEd"KcfR,$`g8K>#"b&}AEx-yݷ1Skmպ_oeαq_dt~Ͼ}J2=VR/)O_V̭;L$ъa͂@)F5 *#ۄ #Vͪap(OUThQ)3)H* ZD &_Vǃ+<cqxI<P 92qD->TD:z(qLό̀h33?Pu4c3iT"K+H $=OׄUU?LLS/$3&1f:0&ng *f3!@F0UU.""tiRľGsIPȠc2|ço~>=朓ǾO*E|{ )I&UVT%TEHSIRdpΤɣ; fqt7-Frۼ6CMkpSG\.EDGxĻR"8~Dwm19#JbDU1YSQ&TQ̌ q'(FSd$mUE1ubUyZ/LTDe- !j˥bR$njcӺW mSTRZEc$o}4k+,3d&D jq TGj$z[Lή.e LjG߼<}z|<P-eXYrc+.'U}>'CbljڪVU5E;Scؽo?o^KX)˟>m/_+O7ӯ.O:"^}VGdc4U=OU~d`ڜR!9ӵ(tg)bf,(1ITZϗ{g.,TS4@UO$#(F¬t@_\4Hf1s AO3tߝ̺neʈ4B 4f΃Z&jUb;8V<6wO ~ϿG ŬԂLS% Wkmm[Tdik]L/Ls}g၀@Ĭ4BXꥭO3cxstRcOwJ[?>=~禢o}kRLzJE&uIܷeR0uETD>:jE@z$4Q$}~n~y`ά&bf|m-TSmEAqʢA BLZ5V6QΪ:kI fbEhΌQU iEYPSwUU5=3fE8Y+!^R3ݗurSUnKDN9Bxjd"}ń2ff p.>cFOR#E#Nj91)1T$0l|y;>|ط yidt~mL,ԪrK<rRtXx:H9ʼ]6B1 IRfpi\fdé$PP[QUDִ)Ir__x'ad$%bRR|V1 ?<>N@DNV ?>tlj-#ZiW+J#mVrFPT hY?rpOO#H&m.5}_|qW3P 2gP q~QZG*8L$IJ-g8sh˓Z ;h9VbUf8Dd9]j7X'+8fߏU""1jvqjŮ3+O/ޭ~#{RxZb} K)f2f@ML j<aJmzN!^^vϤ<֦}sd+:֜Sl`>H,žE6S9e)""Y@GTcrXUOwϗ,{B1==;۵LL^+9,hU_W)"hՒXPԥ(@rzjLW%d]UE@M8vJ1s id-b IDAT2.U% ?Cܰ tNDKD&Xy"ZSӗ^kﻠ!Z_rsG1B$_5*dfBlfNwiyĩ &jSrEZL z V41Kew_]=I1dM*S^e36h#`3Gɴ~ׁ'0Z)_(zPL 1w(h:'OIaEɺHƙdu@L9p LDDAZq=5PL;gDZ(Tv9{38gso#b}\JIh0fʀbS] rfe7QScyzz1,Gcp?ί&r瓓 -+ʒ̘Gz?%3Ͼ<31ePZ]T`m9U])}YQU03qY/ 2|33Dno_k{K-mϿ(_}2##W_5Dz1"9b&m T)E ;_jTp)Br/ef8͔}~DLԢCN??$Y-ȋMɭVdGE=|2݇gV̌\ex#R~^ABZ4gsQ*鶴 wiK!G6PbeY(LTꙙ|}4AR˺9t1Q4E$f'PSǖmrӚ^Kbߜ9*i*>6QLe)c[e/zmQs JX)-3|gp.ťy@$}dILtWU%"|;DΒvI6@4}FT>g'LgwZ[ng>zTo|DU}g˹6U-|okm嶔ryc?0kdn3ݬ⥵q]k*W_-f7OgO߿nA#^^-2gDzp~DbfڏHp3E}G>=ٶ#E{F[3+Jgx^uiLΞ")9eɨeywcj}pdI sy{3bfZd ^*kV vLRVH"HĈ|G|[R(Yª:[bJj,Vs ʙ7)޽ƈN`<GԥxȺ?})+tǜ9zi,'ؓ*vo/14yB?Q9>Ϙ,bdfTUj}{}-rO1@h)u8Ύo,Z۩r}zy`Hxd2s@CrQ96eF|igj.?-EWZ2Tf wۑ$IĎ_""/u鮞ê\.y!?/?/|XX`ArI2"brლGFVUwWLUna"r#_S~J$LuQrxy_3ՈZw{>u΅.]}7-j׶hN[y#YSI9!%1mPA/{4 ֪@ =>ͼwv_Z I|^Y |^i9yX=4,..m'3e"mCd4S8BIͶ j.LCߛO 9,%qbP3o̓{wK<-Q>7zuc[O4D.,IRƘN(0MG8@f0muyIQ@mFhѭ-} }@c%&zotC_yt2$z8@-U>\mw$yڿMe ]{]XR;w ]3VVqݚe>*iR*fCz}f3(&.8eai;<-sT_W;<?C /of>?}Sץ{~>eU0<|f"pz^SIH/<1Pa*qIJ/`Wc}e w DeN6Ró[W?]H;wǰq\WO#§k{:kkj[ov"hBCp~Vڬ7syÈ9ɺ"[ɻ9M;H$B9$&39XT$1a2u0 I)JB0O{*@kEL-n64v/e>Ab}vuWzMe?qр$e9]vz %MGbFķ } ụghӭcvnt`pN[N1dfbL{b{y| j2:S<+X\] ,E@z5m8X(4Lnߧ4BK`Qucag"sZ$e"ݽ#j%)ͽ]ZXLx'7'iwJa8r O/2ϗf~~TnjN"(R޺9ΒECpa0GwP&>ah}!g$jÁk00E[13DhDAYֻ[KLO-:񹖒B@7bm$S{DD.jDaO;aʎ#|ԇo!J ]S{8# ˼z kL"e9Ey96s9p*mŀE'u@hWg5*سL Lsev[=fozjz!j M[Op^9c5djU|is{n7<\ƃUf1x0K1Q*;bq(1#A6:a}#2kqD5x |81p{i_O>LyڻF>f5nQ ]UWP4$e:%Tv+rʹ^Fػiεk_p3"z~v$4F2'bSZi fEϾ-pěսM;&0YB2/w53aOyDSP=+ora]Z;'&2'ʁ1LRD"\r"a Qr"JΑdxڬVdˢץkTcާvu S@f9q& ~JRpw (Hf&hw"rCw24nRЮZ!Mdz]+xnjw9͜v*O7"Bմaa ŎaIdGpz6"%oΊw a}ِ$3"z2Yo3@y;ЖƔ j3 RQgX 4z vg8bAu[gڙ4(w,#42J̢ʔZemsJ׋#gNDDH" byb2@X>ZdC4u&.D4%!tN}x^-|7H݁ͻ55&gր &ʳ`$ e"$౛hj aMk<__- cb==wVC()̩uo͙c^^J 0 .Sr($9s9mZv"ܭ`m 7bJW3-"vsP̛i=\rZ9"FEᒊ9 m>y z^+d~yh}@ʓkeūE$g]z<I|pQXDeu}- ^@An]['o IYL k\)siu3"_IeSt,^J1}a݅[em5E)EYz+"1LMǻ]?>Ϥ?Oz{k˩Ws~!=x8zCzẊ"r<yڧBuH>!|H|烺Es0i([g&e6#L{<cK< H}Dgq"I,ߎ'QGߕ+F-շzOKH޷mY:$#x-x7i#ߺie<DD4CoLY8vBN!m60WPfqƔAdĄ Zs!Zh$ AN|KC T%eLIJɆX>8g<`Q&!z6"JjwR)TDq('R <9zk s\Ͻ^b;xB7o$BDb,T=(SXZ> yJ5Ԍ4ܯm8ADp*}l V R 4pDSy>VW l5F%t9}"D_OQ\k:.[2 ׾F@Ӿ~nbod_ D& p*Ly&<1hmyJʁ8@2jiYX 2ID*;D%`:ez}!46i/yҶf?/m4ih_=y|c^BxQn/#ijm9FE>xwWMIZdv*A B)1LSRͅ~`-H?z?u݀w yo6,$Ͼº #hͮaDz4^cKc2yz2Ļc k0M_V ?yRmYlfݴw cӡ3S)-jn e!nn3w;G4[ lxљ XRKxfu[=ޥrkkkvk4݆f`._d")31ɷhf?H@JDǙ>m>߭7}=#A YX0zvm)}~Bd_|&á&n&SݝE[$@b!hͳnVuӹ̵0?q Q`DiK&' @$Y=~3]PoQI4ve$&I)^;[i_ds0]u%]]'ۜ?_v|#mFDJS OMc%Q$JY[_zcn;|!~v|V)ϒSRNLDp8[n",zweg!HI)9>I{\Iё~5>qM("4ޘy )|mx@%u,\ā];M6O)s Blaڧ7m/0~}'5>DFj*q>c26v οM5][-D3EfDDe19~+µ^{| xaџ>2b> #BEZk.wGDۮOZĒQʳY` B")"(9QIؤi41̄-T7L]9PCy.|Y4w{s=_֧8MEǩW0?]Zɼ/c*pt|U^46H(|l"< &IH ji(O2*6L)fH"BD0t7C˔$,6Sfym,G, ?җ4],6L2 #,D̼%@5w<N p5!VZ.7`Xnș2Ea F^݃,^Io"MQ|xJ2?oδvw©Qxٵ||O,G׭${1)$q;12fNj&YpxpFD< 1SHDLɃ Sw̽{pn;waLjaDlڭVYUۢD eᜩaafPzOHd]dkV3Q`Ezwb ;BWgȇxl$M಍Œgwr.×̈́O$/hcz[.OdNH6䩯yff^?,O^`1ߴzFߕ, -Nz _},ZĽ];ۿ"z@ƹ{:OͽJrm>k_DҴIc a $-!Uw7w i/ @dB[S|qlIػx<5HIvI$Rz[{]’;U:0|09zir#Ik-#{js za2CZ7yڹEGDD Kzel׳o^'|\S[{@Hf$e>pkA}rm3nG 3(;"r>,0lED "rN>`qFXD 聉C=4ȇ(=Zk.5ڬ)4.yP)ORWՇ* c81 b­ab?NJ NJpubfFA#2h۶|sVn/Ȱ6xT,N"<0D8L}0戈EfJgoĽXOm=3 IbV0p~{"s㿞A[*i0rf+"-Z[pޭ1!8GD^`rK>'%c{C )Ho(ݽ^>+NNb l8Ť<xbyEN`~Ř[\t¼zHy>ÏϿ ;#GφJBc0 E趽2fD}݂I1K q3"LrKP[湬: I(h,NDWˉ(*eQ2(̐3@"#+jmSY8`y⨱!q"dٔ r{_5%i͘eJR4CPb%$07h @Y.O\_m2ԃnᦛgHr^iTԃ11p\Dzz~~m}q7E8K"+h6e!^͸ɭ߶8Aw:\:tL_ <Iv w *Ty:`ڀק:LK@)d2vi9,|O_6Qy/M}=w[Iዟ?wz:tRGİ~b?i]Kب&&ʢ_#(X&щX}/ugn.;?V$1|x ²3+&-wUca"l֚|l9Og"=2Xg$=fe漿]l=߾z3@5)a sLoV0 V76mpdlc$VB&!|yJdZDHk#Ix}Xnk#ΦuuߤSE4t;ЦA!zXHHDqf5)\))"DT#OAV ͺ"&6GJ R1sj>l*al3'_^ͷ1VqފѩW>秝8߿yudvf`|m?q.fRЯ#2X0V1"@[r)X޲NO;7׾D'DAxtwz]O&.2e|VIz H(NiWmw: R>~Fl+IX_X їRێʢ@~'pXE%@6gCf j<2QxmJޅ,mGJ%0NqL!pzˑjP1ɵMD@ 6Nz4шX br^%OmPRa5C <6fR\IaPu !!iBX$Y(ecZ|1=1:.cz=AHED?*qVh^ O҈Jca×MYO鉙YHE1'OGԣ)c@{PTww͵y{4_UGYI+9[>@+3J]?.?mܒXH|C?(¦\mc^2馀2<@eƷYHZ"afId`[sX6!U&b\?z*̉tspPy8Nфd_uT{IjID"B6U/L xwVBPȩ0Ĵ$N#@6,D>Dm^[ Bs*"xW@lxX/S/ m[?IXOc 4")dkȢگCyAmϪUI<·gOy6Ҏ/~ FO"nP>?7vǗfk6D.7 vm˝HsL*Nm|WG\w<݇~<64i"}̇g wd~Oiw =w/RolgϺ01'BLU֦[@DmAc=H($t7gRaR# 7wTB>!. RULԀLh[adm8C0It`dyfYIfNהI02KsD:k`V%d%1vB5V1=f7+C + IDAT 7e3O&D6oTM+"ex"N&o~CF7{s]oz-Y.N˜N2txQ6|ggOF#)mj&Om&QIOi{5%1)N!s0gxoJe c[4 =i,?jG͇mnOV õ輬c7s1F(;‘΅Ad!bJ)*@m0Au&^Wz?ƈmvYW9 S% BK$2qZD!uPmQ; v Q"FY 7 FDXfՂjZDd7MQ͙YcrX>D3bpb831()(,䫉TB-f!+x8ī7/^r%G "HC]~Xf6NODgEfBLȚ~>,FeuԙF2E :MNHI‘0)T5Bk N9VR- &1I",SSAHN(a},7CT'9r57֤094#I%6ԝ+ )&9MjrHk}q0pCX| D.m i31{deG?7I"G̘/݆W~ͽ>~bVu-,aSmLv$<Wz?[xT 7Efb)&G"k揀4?MҨsu푙mڋHNw]@LYD[?~ogc“;VkDfu'@aoG!*U& pSf1a^WLz=pF5d(˖W V>ƒɺJRc3 aJ8=<L䑢GZf m A$J5@Y1 %*LRҕ\QxJg&u;-mc_UVO=,m] PAu{Ä@ӏḠ!37>| 3LabT!S扤o~:~+u%mF3syŴeՔ%ʩUgqFX.^81SBky1pW/ᾞCHȈ$'^S<&,S&%"z)3D!9=2},M uq"[x{".w|sCsd"4%cT ft$IL}-][LT j!"FQ“,LQJ W"B#6"b(#)F` wT\.E_.o +,pn;)=}=xԑ<,.G/)hǬὟYU@L.|l?y<ֳE&ɴ/3˶]\E/wceq>dz}N>|D܏:O?2c0 ?X_/Uefrkf8A:5-Ou"6햇Dt|XӻsrPC#"'ॻh@bS]^(E'/yb-X"o%b1H?&[&Ë;zSu[2⑧𴿶~.Ӷ5i|>]v 'EO0W;mk2pZF$fZR-.QWJLQjR]De0!8tgp`+FL;"*|15R^3#pw3T[ֶkja lׁZ Q؍*%ϒƽ*eDk,KE$= Dƭqm@ՉTlp01Jf,F*WSO;ٗ$Z=EW:nZ^'Vı>XzߗQҫ1^N`UM> _jռW0 E^aIt.9>aT B襬aLXOY+$s[23zf~FT$¦tle"Y$-W|X:vu2 " r`N7l F\mZgz_M՛֔=$,Yv<>LB,1(#"¡SM(JY`Df VY( &40N᪙hX12#&b%ve$!I#4 efE%Ĝn2\!&ϓ2i\Da}SeNEajv陱ȼSFޝ(rL| J} jC@2seH0g$fiSjc2-|xɪe3"]܄& O$R5!#FXX>#a8ɠt _W'05S52u+|3Yϱ@WdҴ#Fn"L"\1&pY#b 7 ( S$U7hVU3Ju:eEQ?ahڈ/㒅@Z-"E;EιSӸ6/Oh0/otdm@tx} ˞bϾ b a}w6Έpyl>r;["Qo ZY vWn#-fkDg1Yai;dEYӇ-r*ev7IJ>h񥔄=k~_ߟ޼~~#̥) + !GzxPB;"-qIAFStPBzŭqI`d=zhaRh3{"&N""6L17b":FyXR\ZD-II#3`nڶ-{6Qm`,NYDG9#"w;!u9 a!VbID}$W\Y mlm>RLL}8@m0%{3u;i*nMڤd(ג!=Y826m^DP~5 2c 2I x4jnvgl 6cLD _ܖil߄ǒeGzsF$e~j &.NTͥqA*?اGZxq/[v<^i?w\DaV`|iY[q: o`wr<>c/~25cA)Yæ!92^YԸ)kMhR0WC5"Oҹ= (SP6TI$m@ FĒCқ!L3OYn^z`nQAD*FL`_y``33$IrGnH4If$7f0HO3♔A5fU^ "$##cY9n/Oa1봿)*㴻n]nm}?`4#+-p7wT_G9e$L*/ `}!P5~Ćw>Wy7$kW.&U{L$C@ӤxhY$"cY0Zc,SK"`$ت (IkSx.~֬LGFRQLk%ڴ=1B9@[/6[o~v$DI1v+ Ld̤ni$>N3KZaM. BuLYMl-#FU|fi73M*ml_߯6>I#FONklN} Y2sw| `=GkQ 5щe*'h.3zDOjF_q]nMmdȃ᱅I :?_1q/*ORHv˗mp9ɷem1OHϦDI&biȴbjDXg6TH#Dz|P2_/J|_ I gB{f8 !,ItVgDM)eW[ ۤkO&oJEڢaڴM9@6ͬDDhS"BF2rdy9E`"| vn[+){uۥ?׻>VSĨ=(}0f$:OOad:w/L"#m@VnH"K?GiΛ~+1=FTM8|'&W=n~:e[ގ~zSdAaJ,s4\Tv9'mڿUFXpX(x /c9?%~3x yDW%Zcp8,H BJb`Jw6MF 2r ׌ݙZ*`Y6,=""&#³tQ  v3OvaR]Q)-t2!AY"߲`"U$ĈMII Py*2mټ4<"AфpU$e`O zEIS_e,X7Q.4<( /"9mqSoل]%(qZb]cr8cSvW)&~JzR#*6;e(4~U x ݳ(4yul×|x?OBq܇JD6Nw߷cmS-Jyc|`iWep֒WmC]y#E+aF_}q%J⟙,68|tg*W%_)syNx"<0_ bl%tNN"6ItGc G `KJ>S$!xLa nh뻿 }2z:Y(@6~z2hMlOeW,mWl Yn|e t .RSx4TLE OxL3)MBԘIF9OH=/3D&c)Je'$,dsw&"(]*!HmBN?)U53Sb`%T,aFR2{[bh<[Dͼ"B^1gvϷ:pXD6ߔ NA40&쎯y=~K$gq9Ux໠cYuzZanC.`MWnw ږ[-=Vԍd\"JhcL"G=1S˄f͢[^_oGf{^֗zu飴\H<~nQ D4LѭzNć'YȦTU["}02ei#cR"`f1CF`W%((2%&-A Stm)hv}|xC?߹*>B# GUkƕ ޫbaY3rYfanDd)qQEb%;+nR| ereݪ21(e7GsN$+MBlh/bu@;EiS0vE`$xm,M)/JxCr`f=Ꭽ%N ,)fC1b#l|K4MsQ|~OY_Ϭ_tͣ'`(ߣ}tȐ_ıs~QȓCt$79c<2RYVuP'-8k2* hs9qj>ZBBˉXuB/IٖSqG)9q#/ЃF"*ƈW{&=D=6jXG_F"|ث;ғT:>YRx1SJ? ?a4PKW11)]uA`mQ= }?ҭ~}_O8"&ϟO1b ;=GÐ@6|],#ƒǛic]$I+PU3szt7A`8PHʬɟPH  +nz=\\ȨG7YԢ22MM[ADmZxs@pqXIX#6.׳Iת"6 ;MK;eA= n^Teג$BHJa$jm** eJ4K JAd[b,P+ Ѽ&4I]\KiPI _9yzyOD|zCĢI}kmӸ> ]?欼FN,/{gu:Sa[raQԴ2[7(BI9BOmG?9#rV<s2`+{)n=;Bb=M>^e:>[n!"=\C觕jqDDDk;h"L{-*_>G!t8Qjm>P~l~jS0Z>/ 10 avIyvuQ?*\"Q愭l$Kۃ@h_,q*E} Bz3m3T 6^Erx%mT"^#)ITڲ!R/קxN'-m3dXqʢ+J4c&YnOFlw!13f؃bM{9Ց4bΌ]a;/9hy?"7-2˧t3!~Dma63 7e7bR[ՉֲNHHj?h^S[b!E, L<6W2Q, >()$u7=]tOB:AhP ׫"t^eV3VE S cm}z4 ;(ITt7h"Ā# Ѵj}⌒#:_*CVH^s\3("3=[~_{ 1A$2ėqs@8%YW(UM*n焏p>̣'_?7aݽ6>l,%1'a>No~zyDc=Gb6^RD4}}TxaFTQ$偨s=n[{f΀Jp 5hxL=>7ռc}5Hق,sթ-rpw^>[WIDJ 9ꤕpiցPHXH`AΜbX) g 3KF@D}f! mjN2k6[{r'}OzPD `*ED9}"9!fvC"hXq,DZx>QE62эlm(Ez,n{%R RLR1" Oot,u)+4!,AD.E4ju:v&;j]A]/3&:i.{1C̴vhE"Jgϖ[$$QQ'0ﹺUI iӷ=匀ɿ &5h[-s)U|Tb؋oN.=|_hOuHK 9Nd#"D.pG(A=s! `Iuʠƌ[lNQ+2 Jƺ[L^Wb*#2fv*#D4N {Xn&𼨖*ʻ& ;."cDe2Nލ efL2CVa7p`RUH {7=0MТ,%D詢'@X=v-^d$/;w'Ĕ2տalzJOX2ŘI"Qʳ' Dd;WJ :}piWc_u}gz<~tNuQx\P {xsQIZ4w)89ԙ4TrPH2 2uGf}@]&!q}O_?_#_~y|fV`AshBB̢'6| fCLpɝM rbN>V䝴}9V&:4 m!Px- zߣ6{r5HD ܲ@X0&2ת\XTۓVDkNRa1L+Zx׽1I46Lsb-\'DTeX_D]*}swH!ղr|R~/Gw_f:~\ކѷ~ԅzg'% DJ"=L"(ҸR.lhE#[}{]䭧d_lPNHߤ^ Bq&Q'Q4߇ӷ/2/i<\92XOwa9|ᵙML`G~䵲* Ѝ><UT&%}<"hvC;@6AMᒨxP,b,ǚ G#TNha=jĈa03eSlU,s 8pmttCOS"f򀛗ZbPU*Lnjʝ@T{GQ)wpz ",E񍏾^Kx ܕ,% pT^:h$4ŀ}t6gUis*%v ʫLtdmw]Frk%%ޣ}]J?$bv63Cm*\"m1¥D7bM)%#*H߼nK<p]616 Ԩk 'ژXc3ٰՈ2iSm)˩_?]o /\9mа_>}[C={~Y|O4ޖMH< 2듌)Qגl $EvR苜F/-H" п1 g!n/$VW- ‡ۆ ?G(_p_^>~̕{m}ݢҊc*S4By{>ƒ̫S֮x Re F\/#M) OXLrG*)T>Ml.-h׶|L11(M$:֫ͧY!L˩iDԦ:$x{NAn7!A,_HXEo+s)<|u"&ZL5͵5D J>Fm*B06M8?},GNZ6=m :/ DD_7i$2" 31w6VOBP8,¸g-q!`";g/-vV6qʫ;!`0]4{?C`b"E6Eh.}{q~ASş~~o0Lݳ9):͸;DT&(E( 4ӬK 08mn{mT9K;Dv4*a##l bF*f.z[& _R6 2l).UFZ龱`5㙓 Au*;P]9Ō콝HmpNIXq$Ijcp3mDE2UYJˈiMug\>FKNoᛱ=ߓ)19:l5WЏJATv 9ӋԳ_DnJ섚MHe,Dd@ΆZI"q{x",pw?{NebݴEZ;NJ݀?}54ZҦ#E"Ji":m>*7"C!W&L ,zx"m;Ͽʸ Vt"/EQhM6% RmKw=<nv XxxX/< "d6)UX޸ #|rVH#ZU6 ]ֽonDbs JdW+UYe RV#̃DLka }[(_Wcڪ9[Ӿ "Blf^CTȶ2DE]5X?'B<m侥P>{[1On]BNɾz(i:8F& 33S=@t"٣2!"97|V.6ʰ (OH2,*Ӵܻm>nFxhib?_ƅ~؈ \N_jiol}J|xr) "z!iR֜Eit"(:KTP)P+)qfJZ͙A"G *ѻ3xI۬*fM$FMP_7<9KcUC041THe;Dذ^>+p",=uaϋݫ`+JD/pxq UM-HL q<ܒ}o ݓYSma?7 U-N6ncQU+K-Z˝~~u:FӇo~!·o$"Rffٶ~s831V8P<{Ldc }>.XPeT$ZݶCYqg7~N3`xg(7LF%xDj\?h,ɱu/Tϝy%lnZHafi9}A,c;Xs~83>BU1U">KzWi.cxX$L,TohfJbXjV`a I+-/=p3N}g 6bQGDۂARYX3ٝ!"*`i狊fҧB!3TQ"#lDm"Bk-Zlשݤ=-ܑ)QY\Y(ETrD8=xrP!Dy*2Jʛpy靶EJu *QkRm׏&2=v|aV-}y" PX&bm?H~[6vP961=QITKRPr! n[f\9ªNZHe)ΥD?>S~'X1>ΟeRgꋑa=%)v:_9m[W-26>Pӑo9H"4) өõ@E-8A.Eax"4Z<,d=yɄ{R"b0U-ͷXkm">PQ iTVb\p*)6"gJ 410'Q0^ *71>[&"}VC~{ѽLZnttxi6rFڴvxu}mIֲq $}egnH/ܖ E=IDj+2ik2ϔ:AlADfk$D0DgG<"A,\UZZPKm\hij]68bA-JPa?s߼p6Asbt0e'X&2;G-,]*=S1b= iaRdzzw-ʖ~)uO 10)"RP[̳ԌIn؃[-OT7vy2!)a& t?^X H̟b8@< w!a,XT>4p"@(&%f% H@5']l^uEk|A&[RÝRN'7-?,pDz8nE2 *oM!}f]xU3b3lXThEi8TdF-cZUt}z;3Ho[j;G0vaudV4Uaa ʐh03.}`Zp3OByt;i@9DnRÅhQY%Ju5Ea:gai~uQ__37m9}fq1nO1 j-uf)+=N~ɌEQ>ﮉœ1OX2JqgS'UΟ/`#6q3hjK؃Eҳ7 z+YEK|5ۏ~<0e_KyK;0/$ZkIr CDxH#nAl0dY&L$ @իv:ϝ gD0CLE DDN*} b-UeH)%= a!Icrsy>S0#xZ!L+8'Ӥ( ,4g\ [6E*BT'%V}3hc1ȿTb>~/6cs޴#ۧk);&]m!1d6]!@P*,~N׷=-Rr˟<(g\Gƽ @_^&;"TKp!W3maH>nIAr&w8[m4qG4,.ΗO߹imx|g>ñwVy޵ABYK--73,<6iA88;}Վoӗc_?}Ǭϓ?k;,>:‰,m8lA+S [YHWD EDs)3)>+y>7U =w:8 7ӳ@Vځ»𱥠4/ɟfK͉B//r&sz;Qq:vv7=&q0"j0d\@G;6@J"`r/E;ju9>T\ Ĕpey> v3-f0 KE]wcPesXZo޸ &*K !xZEP|p`n6U1uRUfm QrVѶEMİRњ+py8, ܱnU 'Q, le@E>l\}V\r;? {rz3Ž'<>.#Խ;rPRQf&ƓP0^? .z 9g?zz ϷGXvXvNL22}/߂h#zvg:/Ejvv\?Z:GΥLfvS,Pm_!|,w_Mo.F@f~T+IQf׎52C!'tZKJC}xsH Dugbcخc[e ZWK3yuﰅm20hH؀ -(UIa*`&ea at aO"}@\/麎 '8l41<<^T-Ua*Rdf.S&ZXA֩6A ?\"]0Qsx8uTa)_ܿ:No ߇{Zm˴1ۮg*6c1kZgmD{&Ke t[>eWjcQ옎99IMx6ˤ33M~Q:D3%;Hx1؞X-.`ɺNow~;'yit%lfZV6Vm| ]ߊ(~7_DTcDjUQ@**R6ܵL{A (o&$BRU[G^7[7/ C8h xi6:^T\ҡhp֡E"2wA*m1B5VBC,rPZIi 6G$BSKsd7LcxJ )j>&L4zD-LUTx _{Ƭ4(Sa׳1·~yuѶn뻈ϗ[`#^oE['I/X޺l/͡U Ԩq{Q'YkHC!ʐ瘴3ZT__`Nh8!H9ҘIDӢJy.c{ʦ72(Ek;u"RO~|=-wmD{+ZRXmlޯQm~Yjr=QV>>*1dB]:% RLEi &bJz a,`[* MKn޽؆SQaԚQ"U>J>LL3#ᥦś VDpdicDfDBPIYj )L*thJcf0DpGApͶ6 yPq}!ujyo_7$!I3+&q_2cO&.^P%BLD3ů<oRPc0 ȉ)UlIl0@AN6KiP>_nU뮇WLh_p,,Rt9珿%b-MKc&t^t*ѭ?nRn#+{0%h9ҘY\ѿ-3"cyD AUa qp*@ P-4ڈ@ui`clW Q*T2z@06SE+!JU=$S "\EE8NanZEcRV2'.N-YnZ3yAXi'a1Rnd&6x*a`m6KHMH2}+ZETئS}}<~ʤ6kjV||cs@ 4G|wΐ Hwo}`w*f4X>Tf QOT]JR@q=[_-"h3V_ڄ :oZW;6'pWjG..$ӑ- "DC͜b^}Wk2W<Yg.g+S^pZea˺&]\ÀH0 Dȝ̥6j9oFbk3]2<-0MR|^Q>wU>}= ¥pfe eCqJ:% 墁(vUa5|"6Peo&+\/_ %/Y~q{+uO0ۘo~W=,BbG ۣ׏vWO/0gI'mQwꃉ?;b'RbB][FvͱSuyVeRF_З[YJmw.Gr"lek<Wt<߿˟_"tC C=JJAW,hIӼ/WpI8jxku"z&6KdͲ2fL*?<4n%RJ߭MAA"YϣW=i0Pً> LKMXPW%r Ue)ca F̵aC 3-gb.p4)1pyh-H̒< a!$UGXÀ03".lSu{\2I0|w}:_hFԹFȧ>Q@jOOߍ,?-Im˧Qc$`:@K\QV^~y:*7xesfg.}_f=Y3%[ͳ<J1bư[S/KIJe?sE_vkXT2|ܴ/O}y~bImsѱL7.o"idž IDAT՛6[׊G a|5WIlas$4!ݡ#BwP3Q0TTci0V 2͜;ٹq94X8(<t(ѫ\'dݺJ7SPuTa=jRU] ̻Ga U wbyQX.pmerC)>4\[.VwB,@7G$1t3ro8?^%<릉~s]4y nW epOpp"wBdfٍu }E ȵR3ht6ޢKj_%c%^cM{.D%=No­gu_[1Xce]NN S?.[6W½TҗD.Ȣ<(E*{ ֫ aG}DTǾz3ך\S҄'WZsjEC(ZV[ ԦٿR|Q$>6g| N.iސB+ mj)"R*LLaaݵG A Q}ںi޵w;MR@JFn!%\CwXGx9!wfǸm}5m'2_\ۉt26tr{Si2tnX$ܗ_\ "2޳n!N7H֍RM0P['/+$G7^mnNʷ$1*> h3^~f!uagĔ-DeRWi:v_}vy~qD).|5Le>c_TÄi/3T $^{n 'azUUO}z !y؜I(5uTHy&  0qKBge_'F[g~2#aQwK;nWzc؍wmJ#cv+y[˽0q[lA T|(c=bAL$%ɤSx{Ny(vxvjVs`bf2X4Ev O"ix@G,>k|ESQV !,hƺ `6Ilӡ\Z# p.) t/jC w}eB*D7@EH| `FP=ԗ T)u y_i%:1vocYrta*FmIE/NLJRyhl6x@A{ ˇ X_d'W\/Z%H%"Qg؁ %`fL=wV@qە0iN_R7R!~3 a^5u:]>T~X q%w`XGbmd]-!@!rqP\<12qBLR$? I16/IY éEϴN f1$۶ jA$#MQ cnPxX kŋ4:oK%!!B{\ϫO33а\ pa "pL0狚':jcf&]/.+P̦66Ϥ&Hۿ?L'>V_a;EumޔӮ; kk!۴W"wdJCb-; bbw[ LɏT,O'~񲋟.c~(}J;vh2 Hix}<}OD*Ov"¯[o>Vt},>H$H0,Y~%%SgרE̽S֡$D=)`Z vP!TEor:/0Ոzl^,mC {2,G)x)LaRm';Rp7JVPnIRD!L~¡μ$)[@`>!"bjD QjL 6pU >FH.mLC5M073(.}%qT|zٸ|ݺ|ifDA̒JC|}[vw7WdC0LMlVx|DPçoĉC8`A9|cDk}}NH:ttiKH͂P˟.jc:lz͛tRrGvC*GaDFL䖶@7Rj0CSŎ*؃"#4/MX=ƒJ!EM[+ Ü" R8 ՆKغja\*GfćQ|w/ED,,88!$+5"XՂ)򍀺Zyg;EE#0RZ33G!mJm[l:L4ufnGpF14%e01[WouJ|n[ϯ-gwﹴMǾ|jWNo绷ey'kjBG)N>Sq8!g2|#z"R%_A_ᆧATfjsXLA"2OBc Ͼ(mI5|DKV:>A5 HuC 'ew*֫1V$(Sf*wks5O#5b/ւt3 &E(VY=>Fr $p>ZDHa."U(QEɇ۰[Ei&bȰ@d:&B‡_.<3GWZ=Rm`&ᮃ4[p\v)I (Mp&Bp,M*'2"f;']?7c}y7i_/3i66Trz:r2Ͷ7Ñ/=sRP(knF3&_툇0?Aw!+{J]]@@tz}cnб̥vrT;inej_֎q[ޯ_2m1fo:qS *<,|0ڨ1z7*0Ln8[LfȎmt8|$,iҎe h4I(7bxBTغYRw f*O hecdz5"MD pZ)"ưSnZ``Ԍ SJ+ǻFѭ6S>d# H;1LJYCifvs-Ri3,6ʇe=@=<|],8CzٮV N_$`wGĥΨl$|S!w۶<]Lv}tqcrp}5<-A"T[2qf2%J ZY E PF'">$pB#_B("Er0*#a;: $/_k¬;nK!Pb6]n+zDtcaWۮJ@bF:0f& !pZ+r!.B ]ǶsB@]2:)Wӱͳ,-6Kim,ͮ\dy*C(lyzxUSC|I_f;p}V*XW?ir10`4_<̠A ɓl(J Pgz ΍J&[e޽EgS)E`C(9C9jy6v"A}yy/b1 `]2=,_vRKƴIJ'gPi1>y*#tL"`F `EP0*L$>`<><Z!*9Ќ`a at6Q2Of ErD`]/ת3Ak\ wˈDPGY8w}8g۶"}mrZ Uϰ;c:V"v Hk \aRLuW“o,ĢץΊ q#.ɗblnGvw$wsͫ'Dac+Psqu>ѯeٌ!ADs`u}c;fF<̇ڱqm=BpxMN_ 'wl8@pzC^B{@'õOo @_h( E˟<Ϟ@9zZnҗty )qzcڭ_G_\5Bݛ7eS|ӷoN樵Y_@il)j\V/瑘sM ICS *๫H&5jKwsl|!=. I+΀QMs-2<71ԩpmSGHi)gH46½#Hv@-CJwu`IޛI&?^5! Ȃ$}\ΛN g0n 3acx)77IX^U{,Lw.2)|?3Cz~{iY8ZܖOr=X{_F !:F~sKxp'Y+ۿk͎AL:s¬#c5ܽ܃7K ̥_گ/v]2IJ)E msN=m^Ώ{OǏ߾jTѲ^w:ͥ HDx8S<2pd,j, 8Y_0ctWa֙@=R=L mUKTPaڝ+qAAhSS߽ lJ ]Pخa3̢uRi0 €Lc =j]]uYL ?a`muTԒt&ÿzTgNnh:!KNs0i%47+]gsmhJFNOKQ# %QA$-GT|G;ӑӷ[q GG?n:<} ~c!z$(eb)"Uvyegw;ܿߖǭo޽=]N\s'.@%B#(倍PBL͜w6Ufnѷ<6ziL#KJ|@璯6XaFcyhLDT>Rخ۶{^-kfXPByѝKy.1 L6MmX眀~]gf;/|Λr5JM岹C\'VcxīxguʌVɉ^}?ۈ@?^TӶY2e:lg`[KismɭDm;X?õ4VU$aE<HA2 s<"pJmDEIVfcL]]I_ȧconAWf2Edi똅J63 2F3:v=l?wsrHom\ pbr>k]a0"'ymLT@l]/H3Y\KXv0qxxxVLS)f>n@TY#O>%XZV&Éѽq:C+ܶu~˚.N6ߏlڙ94%-K,CNX.t,?z S ZѻEykdzQ d<<X]8('l_XI* 77d|9z$PO8_v=߯=|t[xom\ɲ Y[?p.7O6~v/"7u>~ .wW &9AÃD>X-,mC4Xh>Lp!xp& v|% `B缲8aI`GYV3!p2 q:e'wz"{`fn3Hb0<ޭێ\xtS4ƪ }[ 3hz!v GPcW CmZSsĘ|B~?$! SP;5 bo`N%Z?ON'f]GcѾDX. F\A/*՟ݮJ~IǖTR,A#K(vn`!8hgR"\`>c{"Z-Ϯnn qNi"{`!R5Cձc8u" \s gIC}]T5"W#I[%fhz#j!bD3O2yP#\Ֆk_nR ۪b :̒rw3^ J;LUg_B\;[@l- Ÿy3 p]A~O!|;Eb:E p)7Z? ?# 1,?s3%[4_iR-c>{ZDHihk; տcߌކ^33sbr˕(b݂9q"槀{&. E(uZQTr e*O$9:&}6Z0gd$}G7D*<œL@,5TP "H|l:]+/`F7Ji.A{$r6ǰaxfJ}_n>v#(,w\ sg< [Yz& 8R89aff̙LԆuCYl\޾p&p) SQLEVM/_nݻ]v^/LJ߀e}6 _saSh^8ā.l5rih:v ֹb>sMr$< ^H0?x5YJn˗wHR2p|m:6#fw,"R:G42+W7DTݗ2zGl@m% ?L&U&*Ю@a{X81JPo݄i]ۢ\ꛞNM A`mR4زCBRSG)( >o2SwԘIi =Pu ( G@7]W훊 s5+1"D9P܇0(ܨ<‡b3e˛c^="ajEc ߥqd,EL,DfHaު߼_~&nc;:9wO2vxvd˭|iNB{OيyMD0aڳ~ "{/RJîNF۟7# *nsuEIJ2z`T>'/O2鎲1YdQ\ꬺC݋?p;{rÿ~yDm1"ΌWC^k";DTP4i,1"|Jӓgq@K]EgxxZ"^~V޵o]A4 q7PhW޻u(TI_=`ɷakmT[˪+t䗰FR᪾mm؈I=rXk-,HBSv<Spth͆' [( @z@ h{ۻ7c׏i4E0|n=ӗA|{>|5bqU/wP6?DX> s~ W}x͡mts;^v^QI<Ÿޯ; :?Kܺnal};DY#/R|6/O~crѴv*u<S=N"aQD6HGDr^KH*X*LǴBN\.#A$=A-Eg t0qKFzU(UTP9wa aaP6`>\3SC!Uj!Ca#V΋]niR9,334ưMc~8NDdڰ`P8M-$(LyqPo~ɫ Es;Oo:=?n׏6pK<0/]}b#,:ƛ ?N+_=Pe" q,;7~54%즲מm}tv|o{1у[ilǣ9w=i @013q$.ۺB -HmXDpn"]ĤÁz GX9?wwّzr s$B@E=Ay8YY.~l j@6|]t4i NNm3t0}Ì\Ã0iRxgpaj}SbBe+] C;2Sdy:,UWs@BLZyT*eoBP-fKѥ$ J+$'H=v;!]i_ey}_p8Xc`CJ2vq2E艊89b+֫C_eĸ*LA@q b1FCD7zz"w3Lzy{Q3[3l{[ [FV\?M=ZPR>sMW1SX#NV1UOٷ/"/g%m*@|=SXB(K+MYR57\`ꕷ9 @\0c*DR42 s^Pi{2סná!47_]W5Fh!1H%B "S#shI #=5H^! &Dv̩u3fTD #81YC0X\"{Q+aXD<>6Zm —ӫOD1aگ@R=m""O(mt_^`esz:>y}zǹ=Wab `ah 툢f)C0U1zSp> t&!{T*gi@ Q` R J%xn+ܵ[({ q!t5S[v2.*{X 2F@`M00 p  2XTǻ绻r,ak3Ipz>u$Siw/c=Im2Kz{w}gNm}^B*wܨ5&yzl^`8πfX&vCp(XDQJ6w?f|:CڮKa)u:[lݒU%P4R,'}m+}}m/Av{*Sك ƃ4q%^9%kY9 hY*=m8m@t[gZ :r67Pvw\ϤxG}= D!dù=&tZaBL I*7 DS.ct"=<I"fTƺh?z^|v7ھk2ݍcؘR&3 @o<Gw *_"Z)=> p@IQ~Y uPxW0z*cf JB1b(0|R!Ba*bIaH$"jn.LH}y&pZ5pއ@@U( L=Wo%晟 U#L恖&;,ˈ &Hʢ4bP0UjAދ< #:k$eC=£V5"c2:kRpBDLs~y5^8rXcy~}K2\t'<|vjE:? @+q]\-( >Be_^?,<~*ǹ›)IbakC 16Z1.pyPgvO vA*<蛦˽b#b:W[#藄Τ힔cׇ.Dƒ!m:3G*̞zwa&:<0je0#X 0r m#LYDm2o'%iaBBc4*l#cq0~5D$J~_Ϊ9 M\m!2 8B@Ji7cFO]W-_͸0۾sc yL'~js ޭND`nR_30SMFwwa^ݍꄒ5x@xP4+E8<#e7>^H_0%p} .s=q~q%Ig~}d/X*@QUS鬡+ En H:huaa\87a g螾zƳ/*?~m8kMf6֓]RS+K-bM4ji{w3c=."`X@^XzJyp@@hv ȄDLfajl1J0 C\( XD0"7y`ݶ;(}t攜$\yZ̕>SL  \5AZ"FEW?_M#\EU s^;4?'ޖcu``s`ABH݃ǩT͓G7Ud D|v'œ<|ax90S5U0~V ߄]RMgD_wίuJg%+2Gѯom2ոTcbS ]^gR@,$/`EXcY "Cx\^lAWTœn_ o |] z E +׸øGn×e*1W>H:?KDA\ڮXzׇ؆7:V)"xD)S=$Y'T~!RVFdK%n.Qf'vpU0"0z8RE(SPIH#|tnɛUm_/R !#㆓6 uP0T#ذ(LU?Ma84ȄӮ+S#ưC}rRM\ 3N;R2j,4bIkjcG,f"72#•1 Wc}8]/6 ||LgFw~͛K; r2]dώ˔_;V8:@nvvL<,P_=d=C Ah7دa ]K[IM6Auu:.cy0]YytzpfR#Gmc,e#⺞_'6GyG<+<7IX[ˀv B)z/WNrNHkڸ4dF"=E=$co9c$eKU#R3wz[ŋ(Mw$ZO?P7 u F[(#`ۘ_D81pڐ3 T"E0Y\u p\wdW4"$o.IGLۣ9c8{u؆@Uno֟|?쬮EMF)]ryecEĜnQ냻ExޓDaY ;!q8xyo0=,ۆ-"EJT{7mq^\Svu<B\Ϡ=3^>[@ׇRw\&)qPek#O#0yq-mt{Ӯ%:׉(l,"4~TlB$~KO窋b#D0sBz"PUYsa{Á|ftRq >T/Ą^,)+H]1}(*[9 <43P p'Ocx;lu2|"l/ZD8o:Ad.JPp?/n+M!p`RL޳AgoiWVmБ9sLj3,'u >Ez5uƽ??v-L/dܽ}|z]lSֺx^ro z8F0ra8 =ġ]/3g0#SRH’kٛse90 rE K Q*=.q3 cV6іz@ppf,hXpErSn_Ir }iJ;&c!b 2P4zi͆K!n#f( C y5‰AG\ΫP SmH94 [$|wswjf&qj<<L" IDATfkjây=B03qޯ)[jW d$dbwEDut8c)vr y. H@e3%<."JE4ʼn`*W{kr*df5&(D?o|G|-c=MX\G_% RMR>S$7u#"h_h|$Ŧ7sC$(6 f$ި+XX6 Fm"=NiW1!LO ʎg_c,7?(u6ƽ˞z,[eoRx.sn;ru8TJ%?R wүb l xzPk,V RevƽHcS$ԇ1A Jd7txxv!`fR@-Gw): eXD"eQf2Ck$F%roC1!0JhMw2mPk!!Wk'fBa枬~ DPW<ܢMjI$HQ ;0 ñT!CX{B4FhW( ~WɷspZ.XkkD0;1i1Gဇ ;b9Gy n| wz> ! p%W;D>>zt@6nkfAgp9}ylcq͏VYzN놺-qRĺ^rsc':mGAr$pG0H  0a<1WD C@u%YqSR5 )c|'-b GDq@#a#z e-SR Zц9EDDCG "\!@!Sآ,P= pƹ4k AvuWG`bF f$uQSoj]01ӎKe<C-BXڮT BpNQ "lp]{R1qΚ<@3߱N}r~L4!ɇUC*quQ'hsN0 &!Q8 e)%,ΦPF# _j_}K?;{בD~-cE\4=)tyrŌG$X4N^bl>0ёor|z,BUu:ַr{x}Y_Rsi,U``E<58ß8mb??<S3vYL{NDn(?ᱫgRzF# uu(0%ācRH )PP*PJq6.`"Pna\,<bj|[m?_"R:zQU&?sqP>Lͣnͭ0##д/}UrmZS)TJ!)Ȅ`缸|D8RP9CsQ3ArE>E3k`saa.ySEKmccqw`k !H wweՉK//_RABd=hi#C׿f=y~yuhLD}‘sqQ'10-K> kdbH9}}72c/OMd)J\$5n(R"110G+"iɧM b#<rcTjEk"zUGf1JisǺ괯D[o~%K HbSykf~: hV,͑P’A yx1+vi/04Bcٺ]0G8VYfq T61ֆ" 1tӭa['"y7ndf6ϒ ޝw3ƺ3 X&t0,`w_?_@J%."l̇pY?͢:9/]{_# ܫ` -3c-(1BȢ5l7-Lspj0Cax|yzD(sF!m̨L "Rs M[44c\ iH0sF!*Qb!-D@N R5zW:i\cjlřq7q)\4@au:)374a )zݥBzrOX`{4:"K2W3kFQt9uSB#y|= oRhjD}YVP613 3CT*KڢC1P8,o{ԇs*9IZ]jLҌAd;nv̬0-M\(iKG3 )GmbuAiW/v^n}QFG\Skj65A"U#L%P*Fi )誡ɓ|~A SI9x$ l5D•oADpG`2. r@Je5U_/ݿ>~@2^ Ӆs}=_?bND_=0.|}]^O6=w¶3\ @n  7w۸Q7`xK*^TWu~H#HH%X4/n={g *z<"Lç?ljh*E|m@\ q!WϦ,@5ź8xraS i#Ŋu#D)۱]QPʪ1f;"{.3{F⺨F,[{oy`CmI)LNRL&ar07>9a]UL}h|!;Q,}9 (@,=j:nF Q D_`7$/BT(2*37-:L[x0@ԉoڴQC`D(,4C80Iu|ƽae<ʿ~~r~)IuI yKBm!I̧~ʧO;?xw@b$qn:IoiGx@C_NpЦ;7lct󋸩MGO> #22Cd Ȁĉօpw8ap°Ewg%#%&n0>&( }4'9×jf.µnw̓PJ9 PbDx'f@¾hweOEA΂O_LoZBLx PZu= "Eafc(3'Ӝ`D>{n6sp0X8E-c[/Җ.-1/nC<ݟ^ ׆3ݑlHϥꎵPkԯ 8/~?gǟ4NeEI~V<^g{;m7  Kyר{HLFmNԙ@p[6"Y$Zcv7.e ~y2֓ ƽp $Lfw$ēAA\o]3P DV뚹܁L)3'=7NbDŽq2"Y+S[Zdj2%,i׊`M'CHP+3a`dB,:,Թ"Q B$DLR5÷ bB\#}!qv́%D8JQi\P3kpizE2 b(QP 1Y`b0gx R9W1lJ(˵<yJʿ/??o}ӷZ6x1I_tLew~r)aą A\j-\=,K#P@ jvO=hC@"׷TJrzYk_{q7mCk۩"T auw#ҠV, W_V<nn(܆Bи\7󮾮~yܣj.Y"P[if Z"rHffiH<#^ wweQ&"X y& aM bj|2[@t|o?cfGZ;=6=<щȁ0M`e8QȦG rFF9:iĔ 1K_O2Iip) JF[{گoպ^LfL#"w]#p]] F)d {p|xܨwRH S\HǰC 0X/]Sɼq~  `̍sa&0p d!LEuB3KBİtq#ƥKR!2smȓeYlB n.fm\+*p9[Q'iOLE,0 R,Tf aD@H(M3#1CjZp. W@8ܕZ>2+olZ)T a?a>dda[:l2IA3__~?>y?}^DD4_h^>';a3!\;ۭӱ'> \v}q]Yj0MƉ/T _~j"5o:UUh!u/R|i{!mgc]~k6JpcTk2ѽT . V8A-`=PCR1M7|2|3׆f 1<ܝKy_#> k8if*S, Q&2m/[>&0Ab$@iHHT[} '+mr4a!lvLk W .fN۴+cD"/=kpLpCDw;HL'@S<Ӱuxs\,&"!61~,;T̅(RdE$֤T&b$:sH Ƶ4*LXP?~׿[~ޗ<,kpcOnmMe\١>yOD.t>W];:2 JHh gIMLO;A$ĥ_njo0iמBeXZi2Y{^µ#|-[?'"zP}e: 7Eț6pU(iwdc7s HR2"m2 #n "oB<υpA\P@1wauD_zlY4QmR*Iji*Jj2HYb:_Hf?̌5Q^ ·:D*ԛ 6"ۦbKP> uBfHe`oJH[݆[ ֩S" T!J afAi5ê(%i2ł=TlyXS5f\}]$5D?w᧋"cſ|K_JDuڗso Ľw~ɓ'v>[T~D"e:S(T׳ېF9#>(U׋MH$u'm^Z?obKXOuÿ|twbj@I>cl- qָ4!Yn-[9#  I6"J%]H{;݇'Z)yM sp33| .(J#$n'Dt ˆ#Qz169C6qS7A?6xMy6=Dc HÊVX6P8\5r.Db0"x:j01T$w8xF(ueqv_{,>2z9GP*lFu( !̅o~+<|^UJH!R_FDwGc9.(lsK]QqhWRVDD,`h0!QS"؎1]"&u7p냖Jd=U>ᇟxO~o?z'ohrcmצ=}}L sb`~},GzK̾rB|zmGDݯ"rnnU*eiyݾg]O#V6g&4HTmJ\#ǀ!a]=@jt>C@܇LH > Hm iqxlK%BZO{Dn{dDX nN=A,#rRL"f8t D<@AU@Fh& BDk#9'{#yJ!tPҝ6_`۬oW"PD켎X ީJ]@2 P+0A[z'.,k-)eN 2H$$ e"i`* B̘5:D0{n}_XXG IDATQ͈J9OϐD*?77ypWDCdҾ!:t`uoɱm/v}eNF&?("bz "궯7C;}ޱ_l߮¹=3i"m,/,cMLy9V" 7s7\qYw2j̨g Af(:|9Nvpb>gaw+kEݷRn`:;x,a]xUuD)x"XT\oG@)c`Õ.`8"k"$UYc[{Et1G"D;=vWWE'-&մcغ9fE%pZr.Jn8Un1բD膦Q*'"2ދ$0L0 ɾ[D632"z#.F'`DA?`JEطOAα/B3Lcm\ 덛;o:HiY5ӣB>%s_FYpBel6 !sij="D@B9߾=t, :ݼecU zU^c;.W/?~T6=x\kZ9"#I wsbkJ45BC hYR y}N5cFEOdפJgטjl!H" nLfD"@F9FaTuA©`BIhECj|fȌ:n,s݉-$\QB\=H@3jւJ+n!eC~ Av#n !##n2\MLS8EH!\*f'c]="F%u 7dsH"><|` gXJI" S)@m)Ua1V;pr''08w+fqhc&8:>x{R Fө`leӇ[ctShWd7v?zFrr\XMӷ)TڃWJ=b~l`iɧҖ񹱟&|Lݽg"T~8U{?=/R \c .{d6GRT`u *s1&p }{F-#L .ch,GA,$p$9y@鮝ێk!Ǹ(-4]UJÛBZJ"Ds0f &pfQSkR`z9U-̰IK?)px_rrtǾy-0{x8KvwV&p j!K,okZ!G $cf*T*Cz 1qAtߔ+    fnK}>£V %}Y,&fn\T RzO8ylwG_}W'7+/鏾K-5^r]@svRAʁXڷ߾k.Ma=HHiתu~vmLI܇N Yzh,Ԁ2A-LQJѭ.u)H0=̂8ڂ Yk%8AB#̲c & s1+YD WfBR1"O/D*xҎb3Mm8pƤuD(ྦྷP3µ) -z˛p~KT< 7n7_旿柼s{ZԖH!KfBLhB,W,3g[oIKlគ&{ۙvz$m)V;_,k}}qXcX `W(0>pCT+c!iLUܧڃuM!MH!affR"0mx> ~P .X(1LH @`P&i!tuUpw7Nc@tP1Qj _ ㌲yՄG2݃x6U/ xPm̂d:"&SCwFwAs4m_ JA)z\/N*P.82z x]ՂkBK p$( ;SCwZ L1\v𮉖 `r sH9BrO,r\^8W>irܺBG^c'7^?|q; c5YZbԫ/}oۦ{\ b0cqf9!*jO.z=Ks[=Q ;[ " Ea"ط$,Kq N"$e#oG `,I܂2oBdq0B) 1v!rb? ]- )n_O#ϓ(Ry9#Ұ-eÁjָ]?()L gޱz缜"FvG{U|(cy2T"]~'oA- fQ maQmi; f pg  'X0Rޝp GR5= BļL?CD5BbR]tx>هR0"P \T'C 3o\BY=f(<&gx2)%{oA!wȂ"SMSmn$|hhL EDti?!$@to +o{ozܥr,'"w,\#g,,D[O\Bm?=~3\뗥۷>Tɳ8y̝}cI(eK`bÙ<#MfD7'(mWQPqLyquM9.jdL7`RԥΈ,da@% Lg!6,,)rH樛B.b/ -`"=+(Lw&wY0rGLH ætM! 1ԗ鈻=}LT;y;GamL cp+{#. n$T)_$AS6 O刜DTsoS BY:64qM#GDW#ʳ,IB@ $0O:^{k_| sYs`MGeyOmd-ˁX8nIp]id?;+/]y򍈜0DYF:U9%̗ !V>q#D,F! ԘT 'W8ɿf$JYdH] B3wc4$ 37GDRP#V_)q Yk[#3rWD7]'[;Fjz>u8vX}t<y_w34ݮ݈1(z@bqRH"wi셀)@Kz Pī "ĘofWQ#A iKr WUYD3ԝUOԦ,ʯp'?|Tww)n38UYIfcwWr|!.;"/ׯLnGc/n|k}73{ &/|@@Fn[`7뛆ȩYH$\)zȲ\7AܓĔqBdf,"IXj$%!}_xpR7kwa0U7ӡ{ު9H%i08oG@6|XlwcTDRVITtj@(EWM<J.r.b:,pHk|B 9nوp=]CM+BD(LB1 :KkbL$ L =tN @XD03{׷w۷л,& :| 5jnAS ....f"_J $,B[ Pi,Ϧ% 7*S?K>nf󈫇о>t0`m9]B~OZ9\ mnĔ"bxp1:k#cdɌg0Fɑ\f4fYD=6Uc? A@O#7k悓.cNt,Rەrp-T0tnB~jcKñX`;8 `};0A`nmitfw@y>$HϹ0]Id 3D,lD[8U>r>kDԊc8.),1F "U-y`"{bd@a &I2!#IVsp1\f (ӽL,@cs @ ˔pȒ!4[*2p(˜ߝ!»Ͽo7 H\r Zu޾\]lJҾ,'^}#0xZO: vma!09 HBbjdnp)0̠/  {fTCZ Խ _D S.W<=X\0\:G;3㲰~ћFma0ٞ D44)Em!0"AIKö*JHixSW0!I}%!tRK;f1,g~+ChjϬ“w:"q8h7# _$NcE@"[^P,%'D`4MNp1[yD< !L # s=B6t3pl,rw ָUw`CU[׿oݨE/9#xCW?C&S%] _@PKBBD:ةl,li$7¤_< =.lR197/"Toz7y)ĜQbAyUPdb2?;l@xXD"Ճzri,"~f΅0ݲǩx tÁe@91wOlyO=sUWUmLǽ5N:yA6< sXHYlKA@P {P>ln Ӎ$Sstus"c᪪E[1 LDo^DzilIAϡ#*3Ab߃=d#fXTRL&B8&W=0! a nUe_-ce!7C`F$˖4:<I5]*:c Imq4+-DۖY*cq9"@D!Z(!x؇#B-[VdF̘q=pjqwE NDmt/W Ý04=O3$\$<$-'.׵.2j=˳1,3! KݸTv"s~m)#R#0 '/\#9biEDJ@݉4 ݼnw3,*BQPW]jWTws%9L \>Z@G%ψTam=uPn_ݷ?w:dDMs_ `NǫF @K %|1Ҝd>vc4;3+ɶ[\ C^kPo8}Kc@֘jF39aXҹ jk= \nnu@iu#~Dn:wp^}hپ+ctבB}n6FJIF",9;ӕڴe3$&=Q\=lR &"a;舽jEn }.UAa!` <`#r̗JaDURΰe)1r.2҈ncZ.C% #vwbLFO qtq0igB,Gr?~?`0_N"}9,BBˡ-T D-? `j6 %@)acm 骁u[x,UPBR2l\ %ŇƉAJBW5V 匵4BߔOT;=/j0bHnգM2H Ozwǻ@ !ma`Q*!06!0(`3ۻ}w$iGpi{p-}1CN.g,b11eb)}$؛FpY$E+DF"|>Lbʬ"IͲż1'̲CF ̌l_m?k`;dȜpWB$<($7@54gyw'!\3:.wk#Ps}/ON/|S"aD,Disԟm6HhfnjjwN ER3Cm,aX /ԅP /NRN­qF0# F(‡r`'.B4>!@D&,un6<{Tʅ*n$nv;?x ۦ~ėl\acHkw @D<*B($ΌWׂ{0>p~lplUAYo ྛ{*ImdZ)HPJY= "TD8y1p )A;aqzGFS8M_` "{ΟG58C ˱&&sQ1IHkd莀@T/=O+GRi;nfk_+3Qĩ>OLH =.c,%%wo7'>[c, ãLrTF'VApwMM*g|'"[8<::.xi\*f f1vM+!I8sh1,OtrdBcW3C*dNWt_ᑼu PuSEX 1L|c`4_NKy컪;R3)ݑ SFLØ/&3a[H$]0Ĺ0 4!0 x$ nnUJ%''#YאZT$"J "?u!aևM͉H-@JFi TԸf Ӱ}vԌ 8mz]HOsfd&@rlc8*bW,KC}$̔ &a Ggs9 @) nKPYDd")l1^ ~Ә  *$&\s"fPwtB8Q -Ph,B64grPvi}s@ʸ7\3TA,jȥ^g}JZ,w^]DÆ{@ՃPkfP 8DT!2 B;HH=R4cZ!г&oSgt[nn3pp.K?nPi!ӧ~.3!cMi{j&_5ú^Og[4M3ij9,Tn:cwX-<7HL f7mm'>F.f3PBEv-rc: H䁑8nyO^#bI:ϘWԠu10,M@,"Jh)2{+]ө:U#rμ {I}DE *cf}DseDrPdi{xi;ߕ}@şm"K 1fԬ܀>Wkf3=H=*;SAuqY!&bm7N*&lu;,Gb&<#E0XO#ΓQdU^xAk+JWZ뚚*"BDʹF$`"|,v="9s#` E" ʿPn.'~=yyˏ3!s孉dY@JDPsFShq b rw^6mt߼`4F/jKe W|mAF Jn|E{$ؙcyWo[dIENDB`nether-3.6/settingtypes.txt000066400000000000000000000025631461347743200161730ustar00rootroot00000000000000# Travelling a short distance in the Nether can correspond to a much further distance on the surface. # # A factor of 10 might be a better value for Minetest, since there's no sprint, but ex-Minecraft players will be mathing for 8. nether_fasttravel_factor (Nether fast-travel factor) int 8 # The likelyhood of finding a Book containing all the portal plans in a dungeon chest. # Set to 0 to disable, or 10 to have it extremely common. # # (This value will be treated as 0 when the Nether portal is the only type of portal available, or when the help modpack is installed) nether_portalBook_loot_weighting (Likelyhood of finding Book of Portals in dungeon chests) int 9 # Turn off to disable the Nether and Nether portal nether_realm_enabled (Enable Nether realm & portal) bool true # Enables the Floatlands portal api code example nether_enable_portal_example_floatlands (Enable example portal: Floatlands) bool false # Enables the Surface-travel portal api code example nether_enable_portal_example_surfacetravel (Enable example portal: Surface-travel) bool false [Nether depth] #The depth where the Nether begins / the Nether ceiling nether_depth_ymax (Upper limit of Nether) int -5000 -30000 32767 #The lower limit of the Nether must be at least 1000 lower than the upper limit, and more than 3000 lower is recommended. nether_depth_ymin (Lower limit of Nether) int -11000 -32768 30000nether-3.6/sounds/000077500000000000000000000000001461347743200141755ustar00rootroot00000000000000nether-3.6/sounds/nether_book_open.ogg000066400000000000000000000433071461347743200202220ustar00rootroot00000000000000OggS :wivorbisDpOggS :ݳvvorbis+Xiph.Org libVorbis I 20120203 (Omnipresent)'ARTIST=Treer - https://github.com/Treerqlicense=Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) - https://creativecommons.org/licenses/by-sa/4.0/!title=Nether Book Of Portals open DATE=2019vorbis"BCV@$s*FsBPBkBL2L[%s!B[(АU@AxA!%=X'=!9xiA!B!B!E9h'A08 8E9X'A B9!$5HP9,(05(0ԃ BI5gAxiA!$AHAFAX9A*9 4d((  @Qqɑɱ  YHHH$Y%Y%Y扪,˲,˲,2 HPQ Eq Yd8Xh爎4CSVs)c1 kνwZ JVk1PJ%KIsmJkIcα^Sk{N-\sjc)k܂,` 2PhJ 01 4 9眔)眓9!2 RZ뜃PJkZRk1P`R cYgjˎ%y(#Y'my)['i,g+˾o뺮,˲꺮+˲ ʲ,۶n꺲,˶mq뺮G0p$<N Y d dR RH)RL("ZcZZkRJ)RJ)RJ)RJ)RJ)RJ)RRR6hJ,PhJ 0) :ÔsI(%1眃RJsRJI2礔Rk1fRi-k Z1Ji-Zs=Zsi-{ARZk9 LktBz`'EcRb1CJ1s9b1sN1ƜsAb1 BsA!9眃BsB!: B!tB!B B!A!BB!B6pR4XhJr,13H9,6!UH)ŴfFU !4dN1dZs! f 6A "\H.\]BBX@ 88'ST "" 8D02468:<>@B@OggS@[ :WI ')҅ղZ6íubscUiA"k5>=8%4}i?DQio *[Jġ2V̠k=v="lB\j11h0Fp`Q_EP JKc%NP ~'Fe>ԻbAXe}!.67u!ώ]f,U՞n?XtY?iShp-@#:oWF;򨸫gruOL`3FvS-m̾{'Nrm;N1+Ó w?& %|d^ь=#H adI{!%bʵtu՗Wwӡ9' 7Zv ^h9 {b$KZcji -1oqNVPKETF ݫĹkZY}2v )nkLryہNP2CQ|޿ma$nrO4i ^e'b5wc:Gd 9H[cKdoKzqY ŠCw,t 7OT*`p${-ve\a#@r52XRAJVim s 9mVKT$Ime%4݉v7 %hJο٧kKbfnΘ/ϧ*?rOzL#2kk/'J,"ai¶Ob`2Vߑ'GG8raֶ&kW,gJԍM֚V<vMrMf|&p?l)U ƺ7^dȔ |ozlܑMD,JHL6|n;25A=NQޢ)pw*)76b'w#Z>ђBzfY(ZX w)h]eq8hQ@j E2QW@uHzm;s&bR:ߋU$ AW̩70&1k)-˳۞~^`j].0bH@z,Lܫw֖Ǒ䆚[la;ha@4Ld}И/yy##UȪ MSȮ?ţ;۽?ی#X&21YUɀТ^ %g.lաeF#Y˖dGa%/I[t1ӭ{39QjЯ-7|V'F*|g?Q#1Z.EQ8 Q#(>}xJYU9^Ex}QTntUgu<է EA"& 15(TG,_~ B@?tofrE%n=yUvuL"PҔ1mKKrT}UuWb)2H-)xym{jrZeK0T\N"(?b_\4Mh4[#1}lE$17.^ڌ Tjݛ Ue"k5W1VS?_kܨYkbdPdO;nw{Zzօ7Ҵiqz`pkM:0gFJ[OfQ(&1O&8`Uf%%PJ]Q]Dz&TNlPaCzLmZ.]P:s/fJbI-Fvzg1"KNP׬oh1b7IG q~+! 9џḘ2Q8bLql#{I/jEP\*͡ |VIDI[1%UknuX&Q7ZZ[BG~p/]<}1Qn'6 *Z"Au[7^Of%Q#"h0ag|沜CgbaJQфv:(H5(DnMUҟi(ɢZLpeV|Rli*lf=w`S~0Zڬo}B#aj^j% @ODZvw}PMaL\ωd^[ZZ0&d4j\V[c< 6]fĹ :/Oc*~%;߁e5~oM-qM)59 Jlv?%0 `Z&)*J>Qf$E.~EƔt4ojm'پn4x=>y}?]4e\iVQ;ܤtg TV̋FmL8JchC:Zu}'#+ 3T~Fl$͖I睍xR?k9|$/ µf:UL@El)]A28jWܳG+I%-;6ӉHHd<Xw~ַOSigRhm"w 1vK"_p=ؖ)ZvLWwlU9rE7oIj&YGI\ `&Io_!R|]9D֍Phց鲧&J2uy1ƽ襪R"žv3Q3}d\٥>N:RxUF}FvH^@Q{\$hgRGx~ۏ1r..Ȭh@R/݄y$bQV=k}2;l,\R%cxLm)SS >N wGo! `fU$~vmSDg@okZY ] wׁIJBմWQL35>Hm[7=A|emGoY!ѝ09f?|Vn̘P0-a4R$2j(1Ha% ؍*;;K'r;H^&5lq'O a XϾCt Ҫ*Y)͹m/κ~#YJn͸ri7e鑧U5JfQŐcG5]̫>z9Dh(C %&Aņ7FBᢏbg[ _z5 9rLFJ6JFwM[P>]I홪ZDmߟ9Ɵ~O[:w31u8ܭǰhq!B&*-U;PEwqYrʜkĥq[7M.iq]5A L7ΔIV]YF=\' BOŀOggS :%a&)+'!!! *)!!&*)˕|]W=UʳR0r>]ĤkſzUylLqÐ_ ''6/1{0qg*zcHDGӗWX!d5'/. zo>0حZAHܴbc: %?Ӹ;O|#{8q4i>5CtY̚,5"쨭>a1w3yJ4UV9Ry<3aPB}CH-32`"s<#V#[.#"2eg!o0Dd氥AdT@&*ɪ&}Vʧs_?o#ڮ^~V隖:T(^5 {P@;W|Wr=KCm3(K2W5%&תY[ٱpg7S1$m}(Nhly.a۾ģ# U\8rUe{dvلR,ެcaD7*DșytuB>]^ĭ7>DTxjPvev| L7/þj=݌?m"d\rd_"ao,RAT1q,p0M!(nZFUo\I0Z~bg{rTjYOy?Cfޟ@mq"3ZlSM $~T%VvN3] qL`eQt+) @XkqA5Ykic՜d"V}WӭI8=Cg4OC,w95itO,?^ҝIlYBV*Eb)ԭR^NG1RۮN& t 6z{T^b)ofj.C?Tp̽=oy.,KJJuq.16"' ՚A6$[ȱupnS],djdA(x<3z.k#UͩR[Z\Fљ>K=!Loz%iX?thP3JŒnad;gfݭܚ'"W Wg˙S AF=FQUg50"k:Ld_rRE*5Ǣ@ӵCgKf၊2`u }7'|Rt~{1$'ʎI%(>%iݠ폔9+挫|y{ b{%=rI7Z~B?4pu̎U[Ō3 [ >FהiJ샹95Ӵ% w:Y*D-'Hn-'E\ͷ"MG3AUD> VhSfD@J *gݹW.hg[mT,8#mx ~po1!}J`Q~W~kK QSUq~?ej&|j< -sUvL-tfiZALj'eZv; ( Bjw-Z*(ao[sw*ݏ?1 Ϫp4 U]Il><3ʅ_=~xJ'}9ԟƖ K7v(È3"lt%RRR`S;`Htpk\\kQګ> :C5zl)0ǗkmyGl3EzƞaY蜗'qLqK"F&lͅEF=ʗ:MOhU~Ԑ:ZW!$`jiiȓo-nG\(m(sW,ѕC@p>&@o}$s*YhVhomI/O[} 67vؓҞrq׭發{5c>^bMF5qek92B 7όQEn"ŋRUӗS5jd5yG11:L-+tOsE'~J6Gz d %?Yo&loÌ$1==gRKCMyd]9/0eZ>GIt;ohg-Ø>011K%[\DJ[%ol׀DWaCT0WoN f;bJgߛG{.]/y)eU3z]a_|ZWZտI/^li"y_Ƒ}ôv(/ʔA!{wŪgYj2ӥ*%nAm#Tv]*.`&\g]R`ﯬj+V_NK;@w[V+>N%IHmp@lJLԂ :T6Qׯ2kʈ\`Gj~JcI3} /YhjOELMe>>6M(9̯/dy֍W I{< Pb }5RK@ j{e_߈G o(#Ia̩q zX[a/7;DMD%\y1(J)W˳ JKr.5_ukT‘ގ6~,N"ok?gEyS(Z'_V(p^8Qv;V ?Q/.+r3F)|Jq25HW>KsB˫]5}w h:·^5)ڗsh[G#eUeR"q.;r,iJDK&Ǽ5J!ɧJMš4o-BZZEYT j|+'b]wnzxg*kuf)7kfŕF^i1! /_6ڒf]>^| i1&e.@%/_ bÊ /Q9OQ[sn},qT+_$Cj8R黛7hߌ(U.ؿ/cvSk){5 q_*Q?j[E*5qWg< {JE&6zJW >!f3g$Ig'YTc=V맒.6Ul<}q&ڠ]u=^c.qB!ѵҌ?ֻ'1d ,§9DQ2/YshU4'tukjB*:)k*% w df#1iUd u"i,6J22Wm""鶸쪻UFPdp: D<9_F䅲i_rKվJ @h{,XI{dty6Vʰ >n, *ynYJ(:M_aae}dPiAs |7I#/3W'mżQJqכ~o؊C1"3Ȇ@Ȭ Vm(WD3G(?Tq$zWPv@OggS :XO, ,)((.*&*uzP#?@t磋R}e۱#m#b l$dv^|.6^3vzY^44MLirR`'/VK_ "!n(بAs!`)5#DZ a\Y+[(v)L%ҷ ?r XPxEe60!džU6B@A 䌄ʪbaEMtT>L*V~_Bê~s~$~P/h\F8H{k<鵬!fJ4hQ|Q `Xd7&p22Ú>amS#JQ{I嫘t& (v ~YNQ$RϽ4-3KL)k&eY~xR/^W#q QbڞUaeTG,TfJQյv1 VJ= I%Oig//sϱ]TR~) \yc!V٪Hp,Ďʇ#mVmYFY (E<|ӯ.cmb^=դ>dF)V}$24ػcz,}uG[=zkAH$GU?{ یqY}_/,#W8/rOEUCt|s@"a_={}G#³9YR̘F!{9q_TODԍSF8$]k]ozS7ӒySڇEm{; u<λ}i`;m.'IBN:!ӅTkefXp">\BmH5Bs? ]Mzf֩s)Mw uXyvEb:W<{; 3ǽN'uOG8d{(Y)㞍QIa\dXߚTUx7 WSS:B>9Y]LR@? V͚]33Z-EQ1rkǼ!_z\=fe`KO5uߏ=CYJDo0l>~B cі"ƃkd!QޚNLg,QMor*!| \?IzSXX7^(Lj]F^Q+h?x)}t;W+ s; {Fwqes}'tvOy& jcD)7&3?ۮk汑էB9;(}#C HO1/$zxh -@i,41"Oڵ*jkFs8BXT>PtSl`gIJ+FȖxz.KRt<ҰB\rۊHST).MULyjJ|ImMnfx*C MH?VL4U1cn|YSuH^%8E@_0J{"a#l[`1+"$%4ssu->Ib)W@$G rvjV?Ov͑HdAFD2.Z@v*}PIcFK\2#3wf)i-v`C(&MRüӓN"bQz7-]@@9*rڲKL/aImk}ruϢyZ׳p~Bتbe/e9<՟mo-[9L"Si?+%l1G^rek_}-HCpV1fsgLjф(9cI`>E2,oZL$Y' G yr#]=b$੼a2rV<.d;dfq.tQs%ȍ`!BTp6L: "v޻kI]FM)Gw7˜n>Bt&(F%2nTQ p` %=fHva#39lݙptJ0))`#m-i:҅Gdv;euҌC̭;!ph&ZndF`ɵ,g,Զu\u#Sg*0ި']" ,xc>ilj2(/yr/uӿxn7m!V)]eKI_$qk#BBIM9V“dj]RrgJr%֜::R8GbmCulRE-&re}&4'F[~N)5:>'e&m;gױ<]h$Kwٓ &A֤9DC/gG]UGfn/pq^w #&@NGL/1v-As(<"c[9獮]cH2h ߿epڿ ێ&ue 4ͬRJb.{m)!&RQ{eJ<3Q);- DwPV m0(>Å'-rQOhFYC0K$BԌSJ0G:'e3@0|>QCU}YBI"1i_Mw+}Ս\[RnCZp }5j?6E,(ۜwϪi&8Y·>oՂ֚7ZBzSAx/\=ru!Z`[LV^'τc%8W`ֈc Z;M8ViW}gvZ*c*ܐgFlWɽ'h'#1I3;31ůld7 xAatǡo.mquߵ\(yJxfKͶ.] ( `! '>;GA09}M[k>_q3z~7fMBq57S\_jKo?{[if.>WL1@i󼡜}$; Em^',_nnether-3.6/sounds/nether_fumarole.ogg000066400000000000000000000507271461347743200200650ustar00rootroot00000000000000OggSoCvorbisDOggSom%S2vorbis+Xiph.Org libVorbis I 20120203 (Omnipresent)'ARTIST=Treer - https://github.com/Treertitle=Nether fumerole DATE=2020qlicense=Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) - https://creativecommons.org/licenses/by-sa/4.0/vorbisBCVcT)FRJs1FbJBHsS9לk SP)RRic)RKI%t:'c[I֘kA RL)ĔRBS)ŔRJB%t:SJ(AsctJ$dLBH)JSNBH5R)sRRjA B АU@ P2((#9cI pIɱ$K,KDQU}6UUu]u]u 4d@H d Y F(BCVb(9&|sf9h*tp"In*s9's8srf1h&sf)h&sAks9qFsAj6s9j.s"Im.s9s9sspN8sZnBs>{sB8s9s9sАUA6q HEiȤݣ$h r GR TI) 4d!RH!RH!R!b) *2,2,2밳:0C KMXckZiJ)RJ) YdAF!R!r)BCVU}SvteׅYn]8u}amXeY9~Xet]_XmVY_}xu]n̺ mc}Yf_w:㩪+ , Ƴ, *±,j°ڶ1ܾn,pk1Qu|_x tu]yf]utG8~@!+8$dY(Y(躢hiiiZgi),iiZfhk)˦jʲi캲mlۢiʲil,ۮ꺤Yyijgjʲiyjzh(j,[g試'j*˦ڲilmmim-jۮ,ۺ/iijg癦il+[(扦j,ʖ癪'ꉞk*˦jڪil-*ˮmʲnljjʲl˾ʪ)˦ڲi-۲˲iʲim.˲mlhm-*۲-,ۺʮo-p0l+ۺo2}DӔeS5mTUYvee}4m[UU[6MնeY}Yma4M6UUMմmYmaeveٷe[uוu_}e溲˲p p0 a1R9Q9 dA!9!92 RZ !Rk8ؠ)8@!+TX癢jڲcI'H牢im['牢ih뺺.뺲+릪ʮ,ª+˲m°ʲl۶oܺ‘.1 G @6pR4XhJ 0!B!RJ!0`B(4dE'C)RJ)RJ)RJ)RJ)RJ)RH)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJRJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)R pz0 HQJ)Ɯ1cI()b9RRi-9!RmsRZ13礤[9RRkVk5ZZ5לs͹k9לs1לs9s9s48ذ:IX`!+Ts9R9"s9!T9tBs9!9B!s: BBA!J(!B!:!B!B!RJ!B P`@ #R΄AA AQ3 BL9љbNj3S9tjA^2  (B1A U DDH.\]BBX@ 88'ST ""8>*,.02468:<@OggS@o!U"m}{wmn|~zljwzt% @oZ=j,ٕV܏l`%3Uv6t=s j^U@$@zU C&b?N$pc9y^?ٰN7'Nx&P H@@ g [ ;bȭ Z!2{O`$%1ڂ~|'m%LC<V.5,j,9i(6g~9R{*PێY Rd?`-ss79{b´1с^nnCP oFHQ)넨y$ Qc?PVGJZierjr?)r#JO;]0\ɋʥ^.FŊ 덑b?eF3g1VH  t@^ɝݻ;|2/쉰RY-H*&־D[,9zʋ6Q:!\ei2T\pVݤ8zO:3Q3uN>hb Ba#l]h.Tu.h蠘>} 8!e:k> $QnqΑ~LѼOq%M|h.+׏Z+p6B.S>r&׾zؗȳ64mtH S#3`x(X`nu@@)~ɝke'4bzЄYԊnm9~wFط-FE mr5?wZ{N׏cDJr ėNGV YN4u ] .&$,W޾l$LIUDĕktYʉ+QNZ{(nN꼩a*%wVFL(Ww-c`ۄ< 3މN֫nʓ7 [I Pg)gYC(𐇛v"F:h-~*p;_ J,Cs-@$. 3G0R2?7+rͤN҄Ȇ0#$:#g#~o !Bo&25 PPI7=5.`7fD\mj6ndrLL:.xҟ]Jy{pypFN z'v~{-%VNHIU(O!9!,l@3*>jd^VlUnlY`#AmԸM b9N6PL&wg-f֋gơBA z˄ xhnPQjl?'M/[Bzvag;a0ӟjզ%'En_^lݺ }MW ^0:(lܣs ~]U^m㙇!XWdH´ 6sڭ(Zz֙h ÅA$h3]]B^c>3f_FHQHc(Xr~Ns]1{<"W= TV^1-|kP"hɸ+*R*ZFo(WinM99wܻog`H$x6* Fز1dk{t툳GuEGJ͵3]x&暸@ 33U>om`mĬs`fdFCܘvpJO()_iZ%q$MŠrm?[c<pYV/6|9NbT 6֓sXF2kj-`L#u% qhWJōJ RP&0Nc 8ygtDXI @Ai u}~wqubwb#$^(Saklӷ'5jZ'!j#} +K En\2רͺnU}n!oQ2>n.qFջ ]kBC*Uj\(χ*gFdYY߮/ۆ }&ܢm@xUCop3RTlb&M'?(>vp75x@@!ɒ"@v&yë-& ͦ[D^P#jYyB&'1 V~n <{8Ӯ0uа霖(reU@IhL^HCu3e5ˉ6՛XTIyp=&Y_7Av58Tl Nmt:)AY->gX͞Ծ*Zc#Z}Ngϛf{SznbM$N`i7{Q~ªsT㾦=1g3Ζ5QZs"eYl+h`XpG?FBKfO <4aH%j>:Ne^EPZ}h=}zXss !n'ɠl$R2uS}[,#f4*'4|2v b"3-}XZkf"f\K͑h@'>:.ij <κ %J tPR#0i&$e)ONigld SR%u\Jj|__HͮG@ISYE(.FM: 0>jNerpxFvtJ M"Oؾ;<̤byF FsZ~>99&*}6{~I1]VhaXLC=e2Ѩ]wqh%W_⿸zU|At`>vn,SqVhG%U@[ Rg;cH N/[IdD=9Hd͠a`{jIxDh%5)\2 e)QBcPt>j6__NLxap Q:НrxL@+ 1qݹ9\k32O»t&1 '+JGw%&0]z&x'kD.<X. XSm'kȄ3*pn`ϷVJ}3mp&v9:NΫ"\3PEٸ2Ѻ`N!Woh@WZs"SИ" PPf]irHx8rV*Q Irz , BC|/W<\7&_^Sp_mڝ4@>zp6F3b#ҨBkmmц _LRuLjO}s)f+fǛ" PS,%VNLks'\lv{+&!>D4$ͨQYZF3O v}FZY>=3R?G>>4ASDŹP5oW0Lpg8,gD)\[ԌO*@̇h K`oy3}B@9U m?Jt{;i H6yʡt+W1.욞 z$)UvE_<ʻʉN^¹s"&6 ^.{\J.-xN3 @c V@E(][.9 pt缫i!P4VV5&+ &;FpD$ar^(`; th^{\k5fڌ\=Q0WE誒19Q, Z4tx/ɜjvmH+&x @xj)-ٕ9f "*TKk4MGIlSR3pz 917Ko4tbet؞&&xC >ZՔdx2ޮG6f@9aUj,dQIS+^suMTgQQ30#%Qsfc X:Ԋ t ^MgbOY>r@ˡaV g{|IP/6npJV})mچ +nwX)!Mϸ|;dx"O}s tFVfT8;q=i&o^/7NĊvj͒*7˻2oRRzSh~Z2b[ƃHyhaPoY"v Knk~ɭ;׎!elx3GWf^1#ẹ ZR⶗!{@u1srww7Ud#*=ȫo16@0!_nG]N*xr D_ GII@@~ɭ{\솬jq^ ˸%05ZozzVUi)%%{Aő9wGZv)'1oh)VU+pfH193Ցk\%}4~x.8a]^ƨ{2(^ɭ+=LC@:Fr) ׈H{#U/yuT'F$Ic5D&{r j~Mh/)x-ƍpe<%ߚ Z|r8KMRst@Pڄ~ɝm'un23ja9ZVQ~s8A0Bg71=Toso{^ٝ0{֍l'`9iŜWvdEEѼ=!oS҅L^ɭ𧹺A3TYpANk9}eY(2:'^&0qTgJ977crAO0[{+ӌN֫%q~P' ;A0 VMzPc: ^ɝ^t#7c6L.MCШ{;o[nvǹ#WSstmw lIIh#+sN2Ӻ_ h>mWyAɝW!aK9k = *4llϑeӤ@IdՓ소 [lE4}x9^~;毫 ZA9|H`]GƩ <9U:;J&nr|j֟EE1e Cʺ#'Qӿ W<1<FA"uupm1zǟlC3'z1lL]]cm8=ԟIޱ(.t>]ٳ&ۨM͈A~ faLX1FN1c; %R'-[gAj~-p=&x]o5OP̊jovG,*/U`*n['WLk(sSNhod> (//aNa*1lIѪD` +na͂u}|vY}m`2ZSS1SB@s}12_i2B]}_ʳ*3L5*z" p٦a]C\72gƌ?- СʏdODv>P*kL=M=ZRMmwLßS1wrGT*dYBJG8ſgRۡhf|ah< ѰEV7xȝгHx7dHV1cyb7HoT_($ aC\KM h5]CsIx /G^gVFf hHLze4)'2f$ȝZk C 4+t5n[%@ɦO,j89F8C5?OFsSYj7l;;\ &9'`+Ó !(ph$f ,:;TLd3lLFhg'eĦoc- ?pLϰIK![^Z] ?!N6=\gAZ%ggOHXd',[qj:#m3WG'W[Z5JY&VXGQuTt\ ,fY)v۾s5/QE'i?MbʦrKFї{!&-ҿ] ݢ~md*q3"J?4Z>4Kfڪ3UG%&x1mðgܹRے&rё9t79LNp!7aT)+(dOfآmuP&ITڷ3^%TMK-5a">A'mcQ(𾭤g tu\0z1Ѳ!pb.L}v+SmZg0D=C8S F[JBmMP=$ ]7!aVyޓ%ȝf E>s~Ҝ/*+.5눹0mp2 Usd%8NOy0ƍ11XL  ,\ [u1 3gȦutP#kixz޾iW I}eNg bgF{6.^ҺLv|KSt! q6(z¸SwsOFL1tٳːG\8( 3u8ZEZ T%` DgFl_ÿ& =֔01#VW]Sp5mS0m`K$",}; `*mfMAD$#j#DoqOߥq\>Z*V z}+t[}4uS1<\Wy.K+.u@6%ˏ؉'a<m jxXRddCl9Wk "W8kAYEq_mา1g})ʻX21٠̓:7 L*F߁PqrŸmL#'BD+i*`Xڑ$I4xs|[!8x_[yU]P?v#7։[ʵިz`'-]ivfX2SRE*`SvۮF"ba&c c ZPL>M )ZCNJ ޵znޟv֓\OML{.$dVȫcNR~ {+E W5[nu+Pz*/B@t`Ӵf <#)ҶZ'͇DDf7<X-5\; %̕;!KKeuz+pJ،-m$lus.cη֌s!ݦ/N-n^}Q^+4 ~+$ۘݺ{;0ҘծzG=޶GATT۬uW.QtUMȞ*M~ÐPy޼H@0{d /aV_ÎWqqH 4ȭ 96ݐ Èڢαu?%A:7YfLVLw}s\@,zvLr03##itHПBC~m#(bW4i 9>jsףzˢ 52>)nO,Q SJ=w#Xp{Pr׋U&J:t^l)6b^;6E MaV`eMCQtl|OuZ,I(qQko=zk|9`8a$%2Z $8MM>mt$1ϊcrQ{6aZ:ڼA}Ǐ܄>^SC_@34v"ݨF" G3S1ކ;ژbs5pB6j9=n>m╴+H6N^;956n,] S1i{:=:iR^h“+Xgg?gxoKpW-svtc/۞ևƜP>ٕM)/VX]$ˢ^Kz[T..J"^y{RL„N_5yo > JP N;+f\S宕uy]aj;lǭ(d*q40SHXU]4x3?~=hԨQ#"!+;*o_ aW7޺f`C%(UׇP)#p`\+/54ll<##l @ ]XU~7>9=^F D0O=kh~h-5yik9ğWcN0${rٹj̟-u_t4I&hȝa,*FujЅU5?%"&keʒEN%KPfO w gI5n9Z`_TxNA=*X0@ h]󲻠 LuB:W>Zg_+oa JoR⦉'ro?E::v[0 0by9<{2 =3u/0pkpO' tXmATbwscET V7WMZ[G.s?FN=\OSh[~Z/V{Z(^i1t(] Drg[ZPUNd}oUߖ P7e`h_8ًY"!]l@r׹L.u˘ AP&:XL OggSo7fbX~=vLd.898{0Ot9_y,2`8/'ߝ17mA]~-)HhyKEԘnNj&@o $H>ݖ܊8U5DM~_}?2sVh>l 1SxBej+MP,a`&Hd @^} 78^F$ !^wl=Dqc5C ) z̩aKNh@` ~(onether-3.6/sounds/nether_lava_bubble.0.ogg000066400000000000000000000166641461347743200206510ustar00rootroot00000000000000OggSBOvorbisD8OggS?@vorbis+Xiph.Org libVorbis I 20120203 (Omnipresent)FLicense=CC BY-SA 4.0 - https://creativecommons.org/licenses/by-sa/4.0/Attribution=This sound "Nether lava bubbles 2" is an extraction from "Mud Volcanos - Salton Sea" by RTB45 (https://freesound.org/people/RTB45), used under CC0 1.0 Public Domain DedicationTITLE=Nether lava bubbles 2eCOMMENT=encoded at 3x speed to reduce .ogg file size at expense of high frequencies, so pitch it down DATE=2020vorbis"BCV@$s*FsBPBkBL2L[%s!B[(АU@AxA!%=X'=!9xiA!B!B!E9h'A08 8E9X'A B9!$5HP9,(05(0ԃ BI5gAxiA!$AHAFAX9A*9 4d((  @Qqɑɱ  YHHH$Y%Y%Y扪,˲,˲,2 HPQ Eq Yd8Xh爎4CS# G,]6MuC74ӴmUut]_uhPTU]WeUW}[}UUYՖa} Uum]X~2tu[h뺱̾L(CLABH)R9)sRB)RR9&%sNJ(PJKB)RZl՚Z5Z(PJ[k5FAȜ9'Z(9*:)Z,)X9'%J!JL%C*b,)ZlŘs(ŒJl%X[L9s9'%sNJ(RRksR:)eJ*)XJJ1sNJ!BJ%SJRb+)XJjŘsK1PR%KJ1snAh-c(%cC)b,)cŘs(%ƒJ%X[sNZkmsЩZSLsYsZ(PJZ[9Rb+)XJŘskPJ%XKJ5k5ZŘkjs1Sk5kNZsc&BCVQ!J1A141朔1 R1R2 RRR RkRRj4%(4d% `pA'@pBdH4,1$&(@Et.:B H 7<'RpFGHH!DD4OggS "-,* #!" *(,-#"0*-&/*.++#" (.0/R EGdFQSkNԓF߹|ׂjybg{.pLOyb|+k$>(6ΐc,54MS^VC/s$K Yn\08̖q IW3gM :{VmՆs X-% "FD$K3@0s1\PjW~M>yDjcgWNzd[&Wu<7zU.Zdm*>Dv4 wjHg,wt8E,ՋL1n?ס'"\د5 `VGE 9wm[zaDVENYH# }+ISgT^W26 RLW'` 7z$Z;6rXR\sUޅ c)O9bP E!L;*M2j; X滶rw!G<ݗ dž& ƹSܥo[$e_ƾd㮥vswH*F31'/u7k|_Io^ˤ ~*LU /?WH|as.8PKebT"kbC7ru̿K0pf$ $nIlQ]bل{a#"tAY,s֪DqEH^XC34_zjuζFwm̅03 _5 %>ϫ֑{=t_@Ǹd}5xk&E#X )S>S%k[ QKìa(*_:@UT~$Kc.{(V 'm}ZHf 'Zu$Gq)`O-g?WF_ڲyN4l_awGdiҤ(O@J%#f9j{.!3y6E>I ~{ iFOp7kb}ȁQ>\SNv"@FM.PMeM$/LLXuTLE-1lNipXbiEc݀}؎#paK^^?>B/j=z|k"5·IO3`?.# ׀Ԗ][%%sp!533,GGRVs)c1 kνwZ JVk1PJ%KIsmJkIcα^Sk{N-\sjc)k܂,` 2PhJ 01 4 9眔)眓9!2 RZ뜃PJkZRk1P`R cYgjˎ%y(#Y'my)['i,g+˾o뺮,˲꺮+˲ ʲ,۶n꺲,˶mq뺮G0p$<N Y d dR RH)RL("ZcZZkRJ)RJ)RJ)RJ)RJ)RJ)RRR6hJ,PhJ 0) :ÔsI(%1眃RJsRJI2礔Rk1fRi-k Z1Ji-Zs=Zsi-{ARZk9 LktBz`'EcRb1CJ1s9b1sN1ƜsAb1 BsA!9眃BsB!: B!tB!B B!A!BB!B6pR4XhJr,13H9,6!UH)ŴfFU !4dN1dZs! f 6A "\H.\]BBX@ 88'ST "" 8D02468:<>@B@OggS1(A@H)(''*&* )))!,) ','&()+, ,(++*E ^ \~w [ǽcWז3?ce__S a'/YZ4=$Ka6s5 7O5yrR轵]cLOKU$D՜#7/G/&wosFE)4ۼӼZ]=GDy_c\O {NfUVEIuW39'}/s5{r)(>Щ~MMh;S)tnq.`QQps6RyJ;SERY[I\mv#YY7;4 M.;,t!F_7ۿ'=ȉ5$QS9%ZlU3ϵASNpE9FV(3is 84lK}S>r1}Ĵq=,Gfr  lj-0$_r/4-UufK{Ɛ)^2 {o տi뉌4Pe5䥻ЮI嚰mzW6/K֫%-\@1>#6R;$˴SYQR:n޼YcGjv!E `ѱRV`55mDN( "oR=j* ^bS[ <ё=eioW$ֿ= 4,m _ p==&HK'2.%'w6I ^ğ!3 ϫ->{/?u{zOncr M oL:x[FɎ8^Ļ}Ӭ"w$b/_%[$r4̻5}y^} -4kx$ MĚ"O﷿?L<]<3Zg4snZ$Y0k]n'[78Tz"xz3+8ߋ#MN,&B^nE@XQx/*Ld|7. ~ZRrh d=H~Y*d*8xp޼*js[Tǿ_scsYNqlڗo,qǑscf1vȡdPd~ V8UT.63>/Vyty"Wix/y̫x" O=y1;t;nP,n vawRQO3%d5Hj{rPv K,$9U<{sB8s9s9sАUA6q HEiȤݣ$h r GR TI) 4d!RH!RH!R!b) *2,2,2밳:0C KMXckZiJ)RJ) YdAF!R!r)BCV>a$6c !I۾ʛ׺;Gdn$t.Mm&vS^W.eZ y -WT[Im&-^rR$JH4&űd3'ɐt9{hS,I.+<Ͽ $wLZwFqä!ZSvj#.űFk2eb 9,,eN_~ku5cv"n0 Tlξuټ]\3 ZŠ.$Hrrfd49Y?M3:: S]c5r012Tmk]7Yq<3c'_n2&"-noJocߣӢ{Fdm2+_c˜~n\_XIk7G>{H^>ējγrفiL$eeLjٴrHI#8CٸzϩҶ>㦪 ?)H0y;kGa[njm5JMRn\$Roݎ݋2>qYm- > ?^^f`f+)&ffD3ǁXJ%BTLi_ff2-`7̈S2.L+Hg _Go8)#NR !ݙx'd^/25SBڸg#~_%a.ϷNJ)\N$.$>5%Ф@H횒[e=_ ;TA<vALJd 5Yln,XE Ο^OZ^F}9ºN'ɯ uwezmc7qC"e֔e#C:=Os$$ Y>0ZU>쫕$hG76"IeB 4}$Y?.a"iDfЉeԤۦ)fs?oRb?jСڄWklx8O0H-wSs"f%׻ az[SpvLJd#1>rbbv8b+[E0ϩ)q4==rV|?'|B_o%t񵛬2-S{_C шCPSrxZVA"2хբi~X7S^lޤmAkXi/gӓy ShyFY0R9L4[U ViB {2&mfn]ZŸjpI IZkyQ1დ}LHn m C2^#|~P͖8t?%B s`"vUg0bp '#d (sy惝H#ĹXp4r*UԓdCl"~Wf_enppby=.y= *XacXoC+`Þ]9Q%L'?QyY;Csue6!d{,^ޤ@ ϏDltk\"d%4ב\ڔp~i#OΧc'Ƣ 4ġ!'Qۄy)O .4ҷ=)')W̛ͫ{.ͧk*c.}JgW7yheT7ʉuPձEOqҾ ~%kS ԂPsadS t˟Lx:xۙhR4b0=kBLMgZnU*{5خRy|^e9~8i}b)FML޽mH,* ;{KMI Nqu?kDH|m|v ,c|6\;I㉈<,)sz '_#*"\WyFY%Q;LDzu"¾h(@s ;2K?]f\̻u0&ĸ["po]? (7TJ/&ilm醘ӛW6Z-sնugp _LWfBZJKfʹ /Y ޛΎa}mx37nkէe<gY;jwItM rbQlu+VIv8:oq* "S73qvgr9h7TV'9}ņ'q<{3zĔa]Ӌo3Bh..W.}# y30|J:~N,,Z. UW4q7~kH< s u/Paabt;ZflJXkTϔ|?5QLVsXnoQE,^S_,Y?<1ۇI/@ekqXz^! s'=v"&.df$aF(6EWũI6pqdmt+* H%3~j*fu+LW{>guQxC.V3lNuqRxxZ~3ě%>Vf.~0NLeҘFVvPP9:4Q[NΓLyH qΧ+E˪&Hp3Bd6ڦ49\,VCcP,C7<dj]洵֒6OJMSD~}ܶDjXZN8U-Mb r]뫚CMg7;Wβ\[j|* ~=vAHT8n>@KbFyaℌvO]uf)ǦNM,xܿ.}i#~_[ p%.S~_C#P{a˫P RuYEQ_krnYPzC<31W.B-P-%^P!J-{)W8|; BzrUӄm r_8Z/^7ou"r,5<9mˏ":EZ!8]~ojePWeqW砘C·b^uKLx)^D;PF,u`L@ u!{OggS1e5E|!mgöZoHRiT248ꄍQ':h璁igXs|jWk!&).뙟ٕٝ#R]bu>$r.B}85_ǘ'>NIkc9jXF˗Ttl4:݉Ӄ:k 0]>RaxksV$Q q)ɷC<!OcNkJev8]Fks/}bMQxzYF% gv{-s75L5P0ZS7mG)_!X-<5\x4$ӄ?I åqQw)>&sj>mdfӞ^Ϸv+3;THT,5azpcKKæO7c')akca1Q}?Y%cQpikŀ[%6=Jx̎ױὅb<8M^$! sFE'Nyh/;p7av\[G>=P84IqdBqmjQjz$N2"mE?Ww1zz)r;f;qٕⶺHy0%eQEb䵧#ak1Nrz:7Hl59"XҮtVWMH-$1jmJ~o=˞ro{yD.qJ]( If6#I9qi­W/=5mEv>0N^B$,JNY)2xјE?ߊ2|%^P:/_xM,T/'ko, 8+LeL[asn5yS~# tES)b._{5&ܗ Phݞr0%(FzOﲩ/vAcB54ݲՠK:6^(Xi.aU`*ϯ[5+ȨǮA:>[#r0cR\M^Mv(:%>ROEHϼl'Z.^]xrbd+c; /ZL%ظ0y^fb4*1N*I"t_" (rr#_&'{sB8s9s9sАUA6q HEiȤݣ$h r GR TI) 4d!RH!RH!R!b) *2,2,2밳:0C KMXckZiJ)RJ) YdAF!R!r)BCVU}SvteׅYn]8u}amXeY9~Xet]_XmVY_}xu]n̺ mc}Yf_w:㩪+ , Ƴ, *±,j°ڶ1ܾn,pk1Qu|_x tu]yf]utG8~@!+8$dY(Y(躢hiiiZgi),iiZfhk)˦jʲi캲mlۢiʲil,ۮ꺤Yyijgjʲiyjzh(j,[g試'j*˦ڲilmmim-jۮ,ۺ/iijg癦il+[(扦j,ʖ癪'ꉞk*˦jڪil-*ˮmʲnljjʲl˾ʪ)˦ڲi-۲˲iʲim.˲mlhm-*۲-,ۺʮo-p0l+ۺo2}DӔeS5mTUYvee}4m[UU[6MնeY}Yma4M6UUMմmYmaeveٷe[uוu_}e溲˲p p0 a1R9Q9 dA!9!92 RZ !Rk8ؠ)8@!+TX癢jڲcI'H牢im['牢ih뺺.뺲+릪ʮ,ª+˲m°ʲl۶oܺ‘.1 G @6pR4XhJ 0!B!RJ!0`B(4d%!B!B!B!Bs9s ؏pz011RcR1 D9tJs1T9!TZ˭rB)T[朔b99))VsΡb;)՚k͹j5\s.Śk5r5s9Ɯs9s8 6pR4XhJ @F)Ɯs:c9!H!ƜsBc9*sB!d9!BȜ:!A!B(sB!J!B!B!B!BB!PB(%X`'Ec %3accCrLSNtLdD'DZP 0 b @"3DBa,0(y!$&(..tqׁ P@N7<':x8(*,.02468:<> "   OggS@ F\(>Vhootrv{~{{yrntuogdf^fcei`Y^dX`YZcelln??ӄ:HR3pſzǛx -3ybӿ~}G%{t~L=  Jq>s3b_w$kLUK$0zJLxu1BwˀޙNo$Xoyޫ\sB-@(/h`Jq&sCiiuo eαOvRJ2T֥S1}##Y0 (M0o8k ѱ& h{-I01{1_[žtMH:qIqoU=t9^t5,s!L$gyF5_gT8'hF1B ^[=O8kqZh$̓0 <VMF [7+b*>0Fs].b1 wƴzi&[H J6.0ɩuv8S?9A;>{ҞxYu%*xyL$뵋̢a# ΨΧyXT]z&",O?M6bj)p=*ѳF]O@(D(Կi>i=p"IX#myAk{*|md4 NΜ;3Ŧt:3[ݦqiι2Ϩ;a7Hޫy`Gʖ[, +p0ebת瞍KyqoO3u R1o-WfI)D 1[b?Q0,E[4dOPɍOni` %> eY,%H7{);z18Y5&#['ph(d~MM&o<-va.N~q;k${whBSF29=qo_8}"7P[w g1!s0ߘQXly{S/a<ґ:&yf[BSSTQ5컗^hW)n vQ3Q]{i #YgֲLe,A|^In6pNf%ox/Lቇh9,r#S~,~V-bjM+lM)@'Gԍq`;^h>=]x22Y0Jz܎}0^w]us<>r=ٮ;SWbjxě%=E# F$KfI"{LP:QAMU[>q&2Ome_tl% ObS]V "jϹwkOQo7}sdlI>e M;q&H"ݧ$úH,0׵̙2RG8%tpZt2QܖkQ*S_OWLAKUO:"ӣr䇳o<b ~2}YiY e6N p+z &:s 0J@`Sj<>To;{Xy[{,YVk|873(R[fB?;oDy:v&jp%V]?)e6^uK%?Fol޴9U3ele|IGqSr$\/vxkͼ3 C&ѦGe[wtut`)3o9q|Z6m W}X(6$=s'҆1 &z-U8*Ao]4!=g3K GdP`р`^,?pWg('k+YT(0H36rOs:{5*:$ AڳB#EdGƍO^W!qCO$ ^,_:M8G'Y)VP lퟅuYߊ ̠tZfm˄߲6?*ň-Fu i4m‘,zL(x~mi[GnY <}k/NhΫg8gnwdE^'4ͣ3IG&\Si'c>q e _[ %Eмr9}Ew ;,ܭK:.C*߰Ihlެ}#NԝI9Pmc@n~R޼Np1ʩ#/)q/aIOiaW@ >]_ ("+A Hc]M  w08$|Ӛ\4mFAIe f2n: L_2S 89 JB@vَ*B{2J#.a+fu֑&bПۨRX:( cN)߸cc@h@?N 8(TqkeYXuٴO^P064F>|h*0a[2pU,:U( (]yg}߱O 8%^M+Viw&M=lޙ϶,=0]KM؃_]Q F}ɂ2Ij}&]Q ukv9On?dOggS@. FZ%souus~zqztsjqwystbi`edf^ebdg^lh^ _8-'D"ik], _8o8崋ݑQR~V 1ۧUcgSNzovfNM^{tlJ%Ӣ5+X*yMѷ(j鏸( v ߺ&bcz>=rYI(y-{-?.KDq=>ME7 @a*t˸Z/L}#lfsd&$*3R㶮?Hf& u3z 3NE^++EX`]ZIK?OVG5\Ď}$ 5?={JmF 2 !52+'[ եk ]P$ZLspRn]^׌::3M{&iڝ_KTױEvKF z+a,gbi̇3_6p!+/ i++E @Sw٪"7;9ūto3I0zrK5Eʸ9n$[Z=3< ݆,D4 .׭x&.01K|ɹ\ay¶~_8k07;nX̋T7c?qgif}0')9 U-/R9睎q{fܨPo̓SeTY/ѪĮg99ᩍ-eײ-#L%_4_*gƭ[}W0lkvuš|ˎpY6mhR)FVޏ$f.qF1\id:3 _O (HmY킟o ^_4z.d:I"!ȐӴeyrZg]=#}m0l-{ 0 \J2CZv>&k1\@葉Y9>`j^!sQ%hte @ei%ay{S΀Lpq ų I qUb]V'd<'>p],V;@)v΂ޯS% ,%< sa:2/a{8ӥy(T398Wo CrHPwIN։tI(Rȏ9Lmy)'FLFw Gt[o_eow\<_ֹ"a1vm~况i6R9''{U7J;,8Mk=>fXЅ}6ûlt{Ygk? 3N7Ξ4PvXsV+wGw.z=vSdcEZNRgX[F}Փ}uc;Vnjm|y \(%FB'L/$^g]17.θiDBio9 u=ߕm5a\©\#㪪|EakOL?7o6UTge1JZ zw|lNZP n7pu[YYDX{GZ}C" \PZ0[/eM3Ĥ *\>14"!O50qa;#vb谜eU#4?~j'QT X.5ϒTU/ HN?y7*#8^(Ms\7#9g BuoK ۻ(c%X'蟘`{ ^8an82 f̳$aXsOų0*wx~QBnm7'a0_}xBZy2-K!zNN7rΣ=N#w(q¦.H ;  ?fl~#9$vܥacvY_pʪ.\W]vOTnc]\ Go쩕g>H]Վ;v #? `g{*Mn]8jLα9KXgl1KR(|Taxr\ij#錳6č$/*W`MGL@Ͻ^!/w7X dk_ \+Ewr ]o-sE=0O,%):w}Uۼuye:q>BM֕?Vpo[% +7MfIӺy4 _fgcBѤrIFҧ!"}$'s$NjT(a5, >JN1π.Y.`XXP7ZÎ!f—8ՠfQ?P73Qh(` .x"k 0HtS+f@:tSmVO8Sd`Y*TrQmLCw}\^y=(%tOX66c+"逅~~-UX5:dcR(h`^4_6pyM>ae GOA`.y檮Ð\|JM\>0ȉtF|0Iv&Q8WFLz|4}c茤l$Bh%SS$Ra+[/M0zޣF**˂~g)4rQ"&tpH~cގ8]n{lK }tq̪\=aڝ $R[s]yReɢUbJNo, `N)^b jn& u7Һ=c?4y4<4Θa"=U{=kRrd=tƐ>JXq2:퓑JU(buX]fƝ/ǷvH|*W&zu I#U}=wh2uhzӤL`@<>jŌ v"{,a߱I)ij㝥=|N_ duK.zfP(P]TelCdhK=õ94V(+WM޴l;cfSߌg9⺇4龾8aU77̶W!z5T #r*^{n ;86N6ED&+ł Fof)ց ͔ 'OCOnhumH[ц0/-JK8/iԕU%gA߹Rug0Vs8o2 ?*҇RYqaK\vu>˶yEj L^J\^K9*\'tQE0ryQ<7mDgsuJ]2=qTfpD,} "t Jcu"Ჵ"lƮ$pWֈj6\!|l' } O-fyWݏzeNGd-c&_.ut,f=4 ~JNxH^BŃǴo9vc.4Ѯ)g0oUC3u:m]s\L%M>{9%LIJqN+Ji.Q(`#?܎oaS7w)-JqX#TT [8uߺ^(<1Iz?0c^vPb14Ō%Bm{;#=߭UY⌸zL&G6. z-8ĥxHG@IjL83b&Bn_ѱZ 5[ɅB٦O8*dOnT\5Bqȁ4+@X I\q+pQԳP(zrGVu]Oc7k9: ܀=&A`T(/Q$(x`IΝE\ؑk6U(MvÍigm`䔩ɫZgt# G,]6MuC74ӴmUut]_uhPTU]WeUW}[}UUYՖa} Uum]X~2tu[h뺱̾L(CLABH)R9)sRB)RR9&%sNJ(PJKB)RZl՚Z5Z(PJ[k5FAȜ9'Z(9*:)Z,)X9'%J!JL%C*b,)ZlŘs(ŒJl%X[L9s9'%sNJ(RRksR:)eJ*)XJJ1sNJ!BJ%SJRb+)XJjŘsK1PR%KJ1snAh-c(%cC)b,)cŘs(%ƒJ%X[sNZkmsЩZSLsYsZ(PJZ[9Rb+)XJŘskPJ%XKJ5k5ZŘkjs1Sk5kNZsc&BCVQ!J1A141朔1 R1R2 RRR RkRRj4%(4d% `pA'@pBdH4,1$&(@Et.:B H 7<'RpFGHH!DD4OggS@}ĸ -0/05=A;=KTNH@BYQ]_RZZVicq|usxn[Y]r\j~|ȅ}dy_tP)$Xȍvr)Z|14xk.pvA@KFo_$Χ:[]8p9 Z_ss\*~^L 7`Ǒ2e*ly/ɕhjޘL+7lvƈ#%,ɺ6ܶ+yRڗh'%N} 7`p3+r%j]Wc)ѶUlPX`~RZ6`s夃0?-ڹ,ˋh N^7VY |kpG{tRC߼)KS|Aj>nöb^8=Gy l`]uIO}!&r~=aL}y -E>}9&[̓ຎgmD<ֿɕf?(}SZm{%Ts+N.u-i )z> 4 ^ uz?t6;,@49zG2[icwVUWFXHW;䮲LJ:O9O+ӱwx``s j??mDqx4NQ5:݄ z_>ݟrWob4e>09hf `dO_;{gb;15[=:ja @Ѱ`1:>m`-?W\  a5>axW Xc_ӱ\[lv5Z.I)~z[y. lmOSu4B@`~xj!,-sq^t0rӝC-D 9l?z i SKI::gZgi[>{>eWo6i=KQA^6X{׺M?51MJCWչt*vƆ6%(@@>{>6{t稱㐶xg̩3k#E1s1ehK2}-:2ZK˸sl*l5Π~ޟr:o 4ѓ4 lD00wh_Yi;5,uq@"M@]fƯOڂ{7`|:P# Z?r}2/o[l.g}@h ViP<^է > ?po~l9ޠ8p>r4Fc`FEjނf9+%Z~7L!Hɶ|-F7o],<=bz&E`H 8mmh-fp#?ɍ6S{Ttw{5ѼtxZ[iF*??z;~9vGlL,!5Ԩe1G;ϗU"|8]CrvT}3Ծq)5P2goWfIIx}7A=Z_~^^mv7h1"".1VZ \jYy?ĞV ׹Myf\}E:©m׶% &)8F(" aZ>rV-K1OMzEFDDfM랦Ź{/5pH{ѼH>b/fx{6LeM$ X:d_@P J>e/޶hlWqC':!"",Ϙc UgꋯfܗWuV{2m''@'ϲ[fRk۶yѬVj8iJ?bOy6ptwaR'DDD`}c ו@B;\Ru+A}O<ڙE"`{;>fDaNg[H $2(]d0m(:>}pwhb!""=l I|ve癆VG>!;Oe㬜 gD+#^jB&" GTԅ(jт5KP@bLz\r.VƏ%z@D0@0<{{mKۈ+po)%h%3f<bWb4REt/oo~k `J:=r6c9GR8a+i[nhgS|+XBD9Gd=7zSl f1Xuأ4b)EaLXC&Xj_>kO` mo}hXmy!>r&|/ ^Y^YmIcE)m. @Z?tmWxVO[l2y |t$`p?3+XEN\o>"*S\9}ctzQlou>kYZj5O)| ,:Х^eJp茹DH]3̈]c0_k.R;LyUTxVkʪGθ8 3d"|3JgJ;k#Sb"*h%OggS@}Ǔ?bUOHF<==?<2}p/زRLD2X GYo.\* eL7!#`ڇ]4q*!i(K=zDk4"`UPש^꟮tΡI|}xYHz[y lQ9wEɺɈ`M 2v}~Eu/,G ?zR:ۧp6GΨW4 ^Fz)>2.؜.3_|]+:75Zf cmWnTVSo `DC__SӊXU@ezWl6q'"#XI#ag6Q<ۡLOi~l>]8pu:CYh1aVr9b ӢCESpmzcao&-Keqʲ/{גfWؗ ~2MarrKL`F|!1iغ4I>^V;SJMsa~>]lkUיb @;quwOQ~KUG lK` 6w*D8OMlByjˡ{"@r.L`z[R&lgn@8msszD"m9+^{޷d _(h >8/QWgJ  %?35$)K1@&ފJvx0/`eA @[6f}ny5o*d8&a`:9ƅ#l\[ 裤 I.o6 ƾv~>>I'/p9<@*m@ yZU0>.8Gv e!VC.H:M燐EHϨ.Gyv-ylL<!eClf06h~dBt(l\ܶ6Y[8Mn "њD&@^{Sʅ-n󸉄.f-PطT1Rh` ~s%l_Wv lGY9N^F@%~G+*@cFf3~&bL.N+6Xo'^x`}&:=:)z;e0*&^%M_SƊ-lqq8!V Jmﺒ=nQ7xެg:aWv>9}hI_߱Mu#ؿgm\ ENds)#[Rv i ۴hȾ>Ld5׉ic^59aʊl1ADy$g&T;'] `8u<}Zd8;^YZgNDxd>rd_hޗN.ls/1"2h{%fFV6SzIO12}&+k5Ĭ[}mhz8kU S2N.[2htAWI|Q#ྟZ +ZzsޗNOAQYY;v-X+K9C-no.bR}>Ҿ]p #3 0BȿqOс9zˡR.8B`'ƚ 'su\̭4WP!jq&{Juvw fZ䯴/ˬL6sSl^Y>:{pn\^6#X`X>ap4 4h'#;i>:p[m *jܷOC_̵L$4=>:0~#gpD M#klqcdI*upnvق5 @Ow.lJZ=&~twa:|  =nh6l% 8h_ۻ 7`h ʗs+Uq _K.8f\0@\A^1*Ԝc:ߥ.p/jȦpiwHzƐj &q'{.8B+yn9qϧ-, MDaԦޖ?O6`^}s__-ܩ`ZӅ uVF0\_ro%-%;pOggSW}g ,''%%%%##!RӅ 9,`#qna|=~gdq~JJHpB~g]p G XkrlG+&{ M0yz{\N$ro- y:6RB&vDD{Uޕ|* Y5PcL8`o.M m{Y`Y-z ^5pҶso2Q&w/w@N@@: w-o`1nether-3.6/sounds/nether_portal_extinguish.ogg000066400000000000000000000245321461347743200220160ustar00rootroot00000000000000OggSRlvorbisD`OggSR}жvorbis+Xiph.Org libVorbis I 20120203 (Omnipresent)8ARTIST=damjancd - https://freesound.org/people/damjancd/attribution=This sound "Nether Portal Extinguish" is an extraction from "Tight Laser Weapon Hit Scifi" by damjancd (https://freesound.org/people/damjancd), used under CC BY 3.0[license=Attribution 3.0 Unported (CC BY 3.0) - https://creativecommons.org/licenses/by/3.0/title=Nether Portal extinguish DATE=2016vorbisBCVcT)FRJs1FbJBHsS9לk SP)RRic)RKI%t:'c[I֘kA RL)ĔRBS)ŔRJB%t:SJ(AsctJ$dLBH)JSNBH5R)sRRjA B АU@ P2((#9cI pIɱ$K,KDQU}6UUu]u]u 4d@H d Y F(BCVb(9&|sf9h*tp"In*s9's8srf1h&sf)h&sAks9qFsAj6s9j.s"Im.s9s9sspN8sZnBs>{sB8s9s9sАUA6q HEiȤݣ$h r GR TI) 4d!RH!RH!R!b) *2,2,2밳:0C KMXckZiJ)RJ) YdAF!R!r)BCVU}SvteׅYn]8u}amXeY9~Xet]_XmVY_}xu]n̺ mc}Yf_w:㩪+ , Ƴ, *±,j°ڶ1ܾn,pk1Qu|_x tu]yf]utG8~@!+8$dY(Y(躢hiiiZgi),iiZfhk)˦jʲi캲mlۢiʲil,ۮ꺤Yyijgjʲiyjzh(j,[g試'j*˦ڲilmmim-jۮ,ۺ/iijg癦il+[(扦j,ʖ癪'ꉞk*˦jڪil-*ˮmʲnljjʲl˾ʪ)˦ڲi-۲˲iʲim.˲mlhm-*۲-,ۺʮo-p0l+ۺo2}DӔeS5mTUYvee}4m[UU[6MնeY}Yma4M6UUMմmYmaeveٷe[uוu_}e溲˲p p0 a1R9Q9 dA!9!92 RZ !Rk8ؠ)8@!+TX癢jڲcI'H牢im['牢ih뺺.뺲+릪ʮ,ª+˲m°ʲl۶oܺ‘.1 G @6pR4XhJ 0!B!RJ!0`B(4d%!B!B!B!Bs9s ؏pz011RcR1 D9tJs1T9!TZ˭rB)T[朔b99))VsΡb;)՚k͹j5\s.Śk5r5s9Ɯs9s8 6pR4XhJ @F)Ɯs:c9!H!ƜsBc9*sB!d9!BȜ:!A!B(sB!J!B!B!B!BB!PB(%X`'Ec %3accCrLSNtLdD'DZP 0 b @"3DBa,0(y!$&(..tqׁ P@N7<':x8(*,.02468:<> "   OggS@cR)$&+gPx :|{N{g>"g,,/R/4qNewR86}_JRƀvc֊~[蹧lj̘#99 , `Δ@ ʪyB"%bc-86gk7ڿcn%}Nm^>EyRݥΤX6 e_4k5j8/[muDϰi*bM)F 2prJh{0cv=댛F 0ϪC\^Wψq꠻5ZBQbq5Źn}nB"acVxYrSY*dm!"xduJ9wn~lCJUԷ3Hޔ5KÙh_P%Y0; v^ pjwm@,AX@ <'ʜ nvChC钿Z3Aϧ: '2{ 0iMUu!wMBꡉ|j7̉;a78UAq̤a*|×9>xUZ)O9h`I aKx5lk9m}/rD:Z ~^tWGu #V7gxmaeU`cM ֪$&:(btFNdQ4i]giv69<Ux*޼w2 Ѱss@oWG1b=㨸Z=f}uLV\h 8\7 De~$(@9m#zeD oWy 4.EagS-ŤGzj93>JYGԟSV j'!3n(N=xY7s_qc&p#.B@N^FɩmN4&}^72Bm`eQmW4s5FV,y;W8_ !`֊07^,~W T9<7͡:y/\% lzuĵS2F\=YdAds<Ľkm1́g8 VV`!e3=. ꣽhTCr}s( 3[{AEo'kMubImZ8c9|am^ yW ,0p%bvSIS_#;<0tnWـ *uF9ŝ;[{73 lCytg3vީ+\L~6>$eS$ @RZ<{8/}>ױc@s*&{+8m@f"ЃQG0W^ՙZ2&^M>NA^ 25}}UPH>zl0+R{L(芃?)?ʡqvFC$R݉+u]]X^^{j` ة-4-@+U @(`A1`NMn' m^ wȆ4,z,YBۿ*C=;YfYiIV>n8W:>a埽pEO+x|z,5offc:bR$pzP>a@WC Z60.e0}-!8gX̛.Q!Q?L%ȁDP;к34>9Ū\ww(Lre.YUZ y}&k:oZ V ]Rh蠳yB@oSYvoR7z~ ѷx!bD'<"HZugazdpD~ 69V[ѭ ^4Y.g[LN sxt8=$;=7pU`] yrF[1PXY;/*2jyQ\ޞ2d"2 {+enLYߝ1>MvsO6`mȌ8k'9"Iy"%#X!64A 0n]&6id~al! HJs1hn~^NnGPp]#W,F?aݑQނvn K3 S[f#}7mOBaZ>^2%믿cQ3c<{1$eh}Ȼa'I`ј:.B,/)|xQS-ۻ>0[̗IXhnu>$պćbV}0VTesH`?t8(z7 ly[+&lLB.A||Jj:'rWxfph]]RxF^ >.sZ9nmT&6_ɒZw[sF#&-i!9r/>Yr)`.U `ԏ#w7rxcM Kf]}Cߧge`ΐUĶU09~?-G~G|q/dŃU2mόRh%kt;(/sxOZw'Xö, &+{!:H7}_2-"n> "+H썒:$^I9 D҆ds_dx k^ Hv'Y!9\JkL-1hOh4Q?pO:^h~itn>_FdDgT >_YPFK[?6H6}/UJ~9(Z>%Vaj^!ӧZCmcYꜭ8Lߞέ+]C2p՞/Vb˴(BTs6ܶ|A Hm&K#&d!)`>/KL~qK8!%ҪMk'EWc#c/sTaODGXC,潄rXGolVj\ YMAӷjR-De'~4!<_3L1g0BRS~)eNy]=_s$2?j`޹&A!+K{ zHMw}㺇sӖ]k&65mcԟbL̪18xζ]U\sɛ#''\&dBL .dz]9a6Kyr~61b_5_Y\~6d;*{;ZWڌ4M`ݕ] Oz]PoB`r l\v\`݆"d=T2ʗ7>>>VܯQ.&\sӬdS:UeunuẄB aؕ8D1XZ5=Kv<0cbmb"0By͢m"!<}t;&wä}f4.:ccGkUR Z;E$sjg%s%|CB6"0+&' GǕwBB|V{1@mK՛YM˔#^xz{Fa!^Eg&.pvyy/HOjÎ:o&MlpmuB4FoM>mAre:-p]IG~PY="ýlztIbOggSUtR#R VZ=k9 x0d mNnV Fr!QJ1fB_1T}]' Qi=;8\ZU'AsѐjcX)+XTKy dy ,96*;p2 ]eg9[ˁ}a2L^.s&8ASzY=nFާ?MнlD+0'BYoYujV~ #C^{z5.GuU DH }ڗHϣIqGlR,d 98fIl:r#{@4O]N(/n4X%n'NX}e'BZǗ69Mc/d.ΛB=IsYr_5prΘ =F,cm򞮷®2VFQEy]{7sؚSU/qSqMX` G/D2{sB8s9s9sАUA6q HEiȤݣ$h r GR TI) 4d!RH!RH!R!b) *2,2,2밳:0C KMXckZiJ)RJ) YdAF!R!r)BCVU}SvteׅYn]8u}amXeY9~Xet]_XmVY_}xu]n̺ mc}Yf_w:㩪+ , Ƴ, *±,j°ڶ1ܾn,pk1Qu|_x tu]yf]utG8~@!+8$dY(Y(躢hiiiZgi),iiZfhk)˦jʲi캲mlۢiʲil,ۮ꺤Yyijgjʲiyjzh(j,[g試'j*˦ڲilmmim-jۮ,ۺ/iijg癦il+[(扦j,ʖ癪'ꉞk*˦jڪil-*ˮmʲnljjʲl˾ʪ)˦ڲi-۲˲iʲim.˲mlhm-*۲-,ۺʮo-p0l+ۺo2}DӔeS5mTUYvee}4m[UU[6MնeY}Yma4M6UUMմmYmaeveٷe[uוu_}e溲˲p p0 a1R9Q9 dA!9!92 RZ !Rk8ؠ)8@!+TX癢jڲcI'H牢im['牢ih뺺.뺲+릪ʮ,ª+˲m°ʲl۶oܺ‘.1 G @6pR4XhJ 0!B!RJ!0`B(4d%!B!B!B!Bs9s ؏pz011RcR1 D9tJs1T9!TZ˭rB)T[朔b99))VsΡb;)՚k͹j5\s.Śk5r5s9Ɯs9s8 6pR4XhJ @F)Ɯs:c9!H!ƜsBc9*sB!d9!BȜ:!A!B(sB!J!B!B!B!BB!PB(%X`'Ec %3accCrLSNtLdD'DZP 0 b @"3DBa,0(y!$&(..tqׁ P@N7<':x8(*,.02468:<> "   OggS_S$+*),/LHZ ܎>}qJ{>drkw_OxOq% =3ɉ1ʂ(4i>쩥3.!6{Ӧvvsg}gIR<|0mCLqwgSgogsnZkd;3ԅP9q;J0N +5 Y NML*Vcm'4 G[&|^#V| o{b;gcC]>uC#h-N<ֆ4ðNwc`#zvϛD)^ل-xu_lxrڝ Gnxѵ!6{uU=mgDŽܫ>Rq=(JT`%O<`[JD@ k*O69QTa]d'VuNO8{+|:B y5lB*Z8i{>]iK4O_*9dҥ{?eu1* Z g甹f(ۛa@)G8ۑLk?Y'&܏]enQFgTs)rUS1&v9oϞm+{lbѰ [L C۵SU+]ΦGqr+?rt-yJdh?v7>oa*yv ܜY@M:LÅ#_eJ|Ɛפm+;kڅȫ']˯tFO$\%6yĔ[DBcBJ<.6ۘ 삕) f^P3cÌaKO`G-&q-\;y}%BXD\٧)1jĜqGH)dG@7wvDUגrT]}Y)mF>sg= =>.|Y^wˆ0~ƌ}%-6 C{Y&r7KsiX:6 9Vc!(xzLcVsm"\5ƝgnDS*d&!Z:Dס^}-->t69NKؐ`+0<#B^j'm?D0+p bGv-0FaVN);d{_[nf$ )ѕS(VBR}By*Ho~&R`~\G,~t\wҧj*[m@L6m8"99&@lJq(h8Yv?c|}l5 &p0.fkHZ1%iyûsZq:;Si362%aO쌆+mif9p&TJ mw1, IҡNyfUL z}>=,8D"TwGm[k`^V`}%U7gۓz5UT}/B5>v{pvvN/ z]vl$樗htkW'e /OF{7$$*G (ތ[g!sOiYJHkpJ~kQ[ !I.dHFY9Q_xQ:(}>33Kdɋz[\sb`e"M1PmnxYjn^5w6Cps}V,#Nݵ{)wga*|ޘr0;?N}ey~)h)SMc 0!LF#G>rp^JO泑xG%kP^ # i̞ag6kij 3"MWb:̱x,HXW 'ӒS ;cS7_ʁ!Ukt*Ķ_4ݩVLQA\x-˳&4ekh}gM.;;MZ>>gz9{a/)OsrͶ7 \Y8_cðՎ'lHf. ^yY6c7KzɱjY!MZߎbtik yqYQ1դ [x>Jɠ1έQ|-"Kn+Eaȡw=ICNUc7hۛaMIk, Z^!(@m8XfY?Wก \ZiG+Uz2 'oRڷ-R4kT/&Adň>C]>wf'pLfغE*VJ9 9t9zv4o}=ZWG{(;ZZfh$_Fٱz/TF걮;*3'I< QX1y*I?\!|B,C|1j-Qel@twHwXVV!yKh_.W _k]֠RU0ȍ%0ݗAoeK *O8=uA(qԼ97تCn]UD]fz=>8&ٹ@s+d^9h^#i 4rV(wz8}woދ w,l `sdK; \C8돭_ N2fMs;q%*j/vbArrԝ\@Z 5ޜ? id>LX/+++ETa X/O1Txp< 1\ .aמ݃N5T6&ĵ @sE6lȋvˋ;?"FAеqj#H>i%4ب7,dhRa 9M۰=ATL_Cr^Gt~੖i sPm|v}CRjM #ëg wyK F^-> L٫D6"X^)FXB]s\=/w8{gGͅ+^mX^\eWE%מ7S)X5*ϟ![n&Z+݁dE$x@T '+U ޜބwsJ:y lHP.V VCF|s>ʦwՉɦ7K9uݡ{BhɆzo0* NWٶm.C`pKT ~?an_$z6baU(PgE[:wLǕ{OsUV`q2sEB.yݎjU&qXE m3G$!dSޤ;,>U29 l ӗ[l##b1~;y]ⳈwBN]Uf3n&;-yޡ=THWxdTT @|X~މAIg+%QB!7llj~YZPmio\ME`բkW~C??L8I0u&4U(V/ޔgg> њ#zdi Zsݮw/4~?{9d{Sj(ӚM~k׹C6NXDR-xj@ j$^uDP(P|WkGmfh ʿvDN]_ +k{F:79jnJM[/( l@ f >OehV B%"W$T,n^ʻ;Mgm/`q8zLxo׼eCI@Ti ^KA|&h U+@(vh^K]~~,mc4nl8-Z瀢_=ɈA.`^@y-2lRg}0'ޜh8v1`Pȡ1 &xrw zFӝYvz~@^ |x(lތ>ʁ&tPȡm|7 ;J$ YO$('?tH `|Gh  Z!B!=6];. @@!n~|G9X"t[! ;)g%D@FX>4s%بbyP@U?|Op]3/ȻB tQ" q( ޾ a`P̨{h P|lǿJK`u5 7\{~$h P:p< @6 @ /~l?ʁ&np.w+ Ny`3&* (xPPZ&z[ ~\2 zP(иuջ$3_( DbPU{}^L26z{S@)PȡWl{ `m>_QPz&lYp؀~<_l`VPȡ H6"0'?@6 P(eN$ aD\;ߟ6+ yBBmU]HTP;h^V `8,S_JIgl`u/QT  9tܩHJ6 x`D8$@e*x?eNcl`ug[ z_ɡQ͘pUeH= V ʜnenP(rM'! Jj0qP>ߥ 6Ցn _"?+Py@_w 4;$4 8?@q--\[!B! 'L" ɦdGs# PZZ^F8jY5oBCJqJ /0 *CUX~ս[!B!8  H.@Y`~dh`~ ~J>ˌN8Yrݭ@(r(AϽY6g{sB8s9s9sАUA6q HEiȤݣ$h r GR TI) 4d!RH!RH!R!b) *2,2,2밳:0C KMXckZiJ)RJ) YdAF!R!r)BCVU}SvteׅYn]8u}amXeY9~Xet]_XmVY_}xu]n̺ mc}Yf_w:㩪+ , Ƴ, *±,j°ڶ1ܾn,pk1Qu|_x tu]yf]utG8~@!+8$dY(Y(躢hiiiZgi),iiZfhk)˦jʲi캲mlۢiʲil,ۮ꺤Yyijgjʲiyjzh(j,[g試'j*˦ڲilmmim-jۮ,ۺ/iijg癦il+[(扦j,ʖ癪'ꉞk*˦jڪil-*ˮmʲnljjʲl˾ʪ)˦ڲi-۲˲iʲim.˲mlhm-*۲-,ۺʮo-p0l+ۺo2}DӔeS5mTUYvee}4m[UU[6MնeY}Yma4M6UUMմmYmaeveٷe[uוu_}e溲˲p p0 a1R9Q9 dA!9!92 RZ !Rk8ؠ)8@!+TX癢jڲcI'H牢im['牢ih뺺.뺲+릪ʮ,ª+˲m°ʲl۶oܺ‘.1 G @6pR4XhJ 0!B!RJ!0`B(4d%!B!B!B!Bs9s ؏pz011RcR1 D9tJs1T9!TZ˭rB)T[朔b99))VsΡb;)՚k͹j5\s.Śk5r5s9Ɯs9s8 6pR4XhJ @F)Ɯs:c9!H!ƜsBc9*sB!d9!BȜ:!A!B(sB!J!B!B!B!BB!PB(%X`'Ec %3accCrLSNtLdD'DZP 0 b @"3DBa,0(y!$&(..tqׁ P@N7<':x8(*,.02468:<> "   OggSZ۴x $+*),/LHZ 1nsogv?R%{>drkw_OxOq% =)<̳t[P"S9[+۩4Ό5uY+ :8EM!Ft͐9XYװ%3D#~8R*}wz}a6(~tIC>9`V+Tv,IoLsf*͒u&n@aX1#HC=حVHv(TG4%,EQe 8MH+b%{VUE\XH>;V\kF\sl޲gS4xc4OP(G..틱g9 omyZia87 :8ŎcK((R!܅٧WaٴA/W]f+V$A8#ǰ+ʲpB4D } h!mȵ>654|wʵye]l]onn1]Dwጎ̺@_ -Yu֫m0nether-3.6/sounds/nether_portal_teleport.ogg000066400000000000000000000337511461347743200214700ustar00rootroot00000000000000OggS'o{vorbisD8OggS'owhvorbis+Xiph.Org libVorbis I 20120203 (Omnipresent)=artist=outroelison - https://freesound.org/people/outroelisonattribution=This sound "Nether Portal teleport", is a timing adjusted version of "teleport" by outroelison (https://freesound.org/people/outroelison), used under CC0 1.0<license=CC0 1.0 Universal (CC0 1.0) Public Domain Dedicationtitle=Nether Portal teleport DATE=2012vorbis!BCVcT)FRJs1FbJBHsS9לk SP)RRic)RKI%t:'c[I֘kA RL)ĔRBS)ŔRJB%t:SJ(AsctJ$dLBH)JSNBH5R)sRRjA B АU@ P2((#9cI pIɱ$K,KDQU}6UUu]u]u 4d@H d Y F(BCVb(9&|sf9h*tp"In*s9's8srf1h&sf)h&sAks9qFsAj6s9j.s"Im.s9s9sspN8sZnBs>{sB8s9s9sАUA6q HEiȤݣ$h r GR TI) 4d!RH!RH!R!b) *2,2,2밳:0C KMXckZiJ)RJ) YdAF!R!r)BCVذ:IX`!+TPJkB9'*sNJJ9!b˝s B)S霔Z:*)[、Zk-{ )ZVSk-{9K1{b[KXfD # QJ)Ɯs9礔1B1ǜB!R2會B%RJƜB%RRsBPJ)sB!PJJsB!J)%9!B)B!B!R!B(TR !BRRJ)J!PRJ)RJ !JJ)J!BJ)J !JHRI!B8A'Ua BCVQBI-H)&H9'9"BRLJ -tIK):HS)@` ! !2C$V2hp@b" pAw!Abq$xnpNQ`h*,.02468:<>@ > "   OggS@J'oY#ʔ䑒y4X ФS$/Yѿb\:(;!_աDg\u]QR?flu6JAc/vuk9k׏b袳{i:?GϫH@J*"$~$ }KD=7HJKX/I$%Ini">Xߒ?CtdguJ_ŖBZyUyԈLX.N#_Ƹ,b0ᔎoe FY8ңYuދM懭lfSL tPFp Rn9N\sE7٬;kƴj$-V4!6l,~eOR2+%u a8WH F[bxy @kxyeE6?ε7R)6fIj$ *ιgb]{ችfc\3.zSI߳_=;gfX8`s \C!ZRHPz<)Ao]&k9uDvf0Zx^Zവtȑ?$wϗ)ɼޜfGƘ;[!ŤK%)Ȱ`RdNˍ @z#@iIw{'rN_]7 i m;&ߗ|Qqc07Q>lP_mj0OS@70C<7LPV.&lPa#,q~-̌1m> pz$xҐBu<{c㱼aSk|u$羣8\hᩛt,Cwޱ4ܜ p@ۤ >RL#9s{hhqs<@S+_/S-qm I2 ,+-]϶N'u^" 껾ޡ<@1a4\BQr0DaPI"lY9;[r3tZ pk;ChךM%{}0P-,CX./oWU| peGM{* ܿ?=^gNXT }-N@ח {hPL Turv#Pc$7 7%-UVBrs(3s@!%JƖ8~] ]2撥o-Y7ia{ XېFm̈Z]eOİ&t9/8>~G!Ic4d'ݨT}"$|>Ń*@F(`SPIw:?},-bP >qFF?D~ PN t1h4rB[aWBW>Y rK ۽`2,g)(noޞwcڋQYcͶ;("-A+uO @.<605x)ub]²GͰǟPQ;8ɒL?JwҶ#2 .~E!N%1$9) נ̕/HغΛDeGڮE pX? mO>#~&<&n#9$HL?y隽tX)/s~(ݬgKY 7~#r.-P`tJ "f+VPݻ@~RI@ @JJt0 o~f`/or#Ume Y";x=m04edbHVX9 a-^+NE[IUF:seN)Ȉ1tBJSʹ2P&KE]:mt(o38j tMGgP%$I&)Pݔ; 7-wd r䁄{+Jzee|3tG55>3m,A6(F# iQ$WGdB0DX'ټi̬H#Z`}>TdK5+&c(DdD&&gW|-IHקvzKpo"Wigtv€49yK Έ)@dAT DD|"(a9>G /2Ot8M{ߜxKeAeBH싎rgZQs@X|}p#_wLKʝ p kCJ^JϨBXʒE4,W2A>%%pC;۽nB||#[q*DrBKlvҒhly1vjl}8/Lo I9%^f}Xz`0@:  Pܤh\.&1LeX@q(aJΆ!"@ o`G~kkN>bK65{dE< >ws6bv2' AAv(5H)-Օ},{SxM}o*y%*ʲ}*ֲ(#4 )cے3+<{ZoȻqOWҔ,9O콯) }20>CJX,ҧR}tRf,D ^ޥ-w %+w:Zh ӱQM׍v#\ ` @ADo[8H )1?& D]T>F(ztȒ2ٚ:!ŴSP8ݽhjф]N=nmFKOޝ3ң`2 }V)/IGJJ^ΝCxI#F t pr"^H|GG~U^ $8?4| K]͝/(@TvZjre!̀"C*m$dq4#љS"h'.(VJ$1rPaSlC' "@+" k\\]1E?ISc]XG*_CU?%?} 0k},FB[ҧN'A]̈́R Z A )%MÌ$|`-I  %egޔԊuY-pq: `ekew }wj$POR1 wI#qe]0K@l_&`46yQ{}lh\"꺗ж H-9P@˕cl[[f4(Kj~tζjypbuu4 ?^vחnɰZ}uq8Z 4 |.y_@~?ZPL"|?W"E#9m?ח,)QQVA1o0)#?A!}j'XP-B S7 (2̧#OT v@{CEzCu(dahTT͎SHV4A; 8{(Rg">={r R[yg^[j w^ @0O$ Q;m'ς>1eĆ">+"<9hʄY5686-b \rG '3(}B>mԨ~nob( h^p 8R1"s,Ct0Ϥ|Ts$A΀8/yu.(nbTY[E}uj LxbF=*TF,>- `oaP^%m{*gve9%d|ҧeY6-U\Bp:ams){><C>!q| `+bc*!<6R@;K6kI0 30V6\}<j>(m[~]q˔ezBlCyK+R/XrAa Kې ѧ4< 8;ү?O=g֦S}E|З+>}I^ϓ$yl^ D_z),Ì@'IӦ/ZJkH! ei1>^~*h4M.UD8!GpQAy1@20ssy>:%vAc4%A6.k$Qp΍mI)=h,O?m~Bۘ [?x;gp#kZD;Io ` h ;2#Jpc(wvuHN_ ("JO#fy,> 2EpwY,Xb Eg5WGAӑWxJ ϗ'ŌV멟a7wx1F%LƦSc3'!y}iޒc{ǵwҎ=v3m}9=>FFz1rtwoz/'rod)BKJ )C5KoqPԒzOwP9m%Tyd(KѬ;jLGKJcV.JFnHV8/9=LiKnTUU"Mŋ &v *l/YiQ z@;q SJ?LA՘RΐV=69_ދ[}*bx:[R)},G`H My_COh%g }@dOӫM>"icrn:ebjOFۘY HTfyR"| _*[QMUS qf{'$YI՜j)~V )PAD{}v䗏 ^]5Ȇ;b[y}yU/A g9r}iDs]鶑 t8RlqAM[].6`D-:mUbj&(Y;TCNNj~߸UG[ցX3!~ eY~ :{y n/&ܔO;n^WזǶ6]=Ky(C^t!?~?pYkhz;܂SM[^<>-ܮ?c67MU,sw@䤿6\nܑw{\9qHuʤO\;N;M^ت޽9yœq {sB8s9s9sАUA6q HEiȤݣ$h r GR TI) 4d!RH!RH!R!b) *2,2,2밳:0C KMXckZiJ)RJ) YdAF!R!r)BCVU}SvteׅYn]8u}amXeY9~Xet]_XmVY_}xu]n̺ mc}Yf_w:㩪+ , Ƴ, *±,j°ڶ1ܾn,pk1Qu|_x tu]yf]utG8~@!+8$dY(Y(躢hiiiZgi),iiZfhk)˦jʲi캲mlۢiʲil,ۮ꺤Yyijgjʲiyjzh(j,[g試'j*˦ڲilmmim-jۮ,ۺ/iijg癦il+[(扦j,ʖ癪'ꉞk*˦jڪil-*ˮmʲnljjʲl˾ʪ)˦ڲi-۲˲iʲim.˲mlhm-*۲-,ۺʮo-p0l+ۺo2}DӔeS5mTUYvee}4m[UU[6MնeY}Yma4M6UUMմmYmaeveٷe[uוu_}e溲˲p p0 a1R9Q9 dA!9!92 RZ !Rk8ؠ)8@!+TX癢jڲcI'H牢im['牢ih뺺.뺲+릪ʮ,ª+˲m°ʲl۶oܺ‘.1 G @6pR4XhJ 0!B!RJ!0`B(4dE'C)RJ)RJ)RJ)RJ)RJ)RH)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJRJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)RJ)R pz0 HQJ)Ɯ1cI()b9RRi-9!RmsRZ13礤[9RRkVk5ZZ5לs͹k9לs1לs9s9s48ذ:IX`!+Ts9R9"s9!T9tBs9!9B!s: BBA!J(!B!:!B!B!RJ!B P`@ #R΄AA AQ3 BL9љbNj3S9tjA^2  (B1A U DDH.\]BBX@ 88'ST ""8>*,.02468:<@OggSqzf8X{rvqrjosqs unwworrysu 4 Ͼ୭BEӹT+O DzVZK5|gj%?m.nDS%s8*^j8Ь-.z1|ߗjs'$Zj |Gi ul&d__Khucݷzt_Fq'9/>BΝ #Wc9C^3E:CCn/`>a9)d 2`ެjt@ =5EMDQAtl\s9ԏd'\ a`ѧ\ ڬctMj$Iż=tz_u ߖ6 ]UTeTO q~ZiɮP mZ\W *&N@,,q{14$q%n] Wva^TEDS>n !KdMBYTyM4Qh"{D̴1EQzjk0,OTpfdMx $>-mˠ0ލν;`+Dž! tCcY$SK*c\ftm2ӸQ!~O7aN_6 0v+V NX>h{Z+!撮AC.;p5^e6 zd٥;5l nۊJVzYy \RLp~".z_P]( ^5̄ ε/tCPd  g}Z?:9ձYX,kuZ>K#ɭÜK{D%V(լ,) ƄF! kaP|꜅ό\:U1ߝkN%@~wO1?Z$4շXq9Բy{L+R[G7XU;; DCs]Jyc_=Z{}[G7yC߉~~/} "+KQ!8=$}7eU!\"67lMp|n4Ͳq頏#9]PJ&7S28<:MOK3]w˜]3jgּsxlH)ir˱TBc)B,.gBk-׷ӄnN %ףܬEKjner:V$I=2Ztڊݳy*Ny:d>H(#^-vW}f/ގRtIVtRrb ;)O_ӆ&3-2[&F}<|v@'`ӳg[o} }@bNoLy<!,_@9.Vcsq{ B!l[]N.f4$Uu E˅^L[o M]k}+< ]u:02Oi窼O W'P`p+eul?]}lW'rS;ӟ㧬=2kJ[(?MaѶ꿪iWw}JngMZ7 s窹:dPw`:9jڴӝjc4-,4m$+k@= 8D3qy_~_$#UG@ lmBcC fV > i,}pWe֣ mlٻ,^UmۭADn,_>` (4wd/ >;+ h֒QپQuk{ G $w}u5k"j,}xz&@=6@R$]@wD|}-D\/_.Dg d 5[MvA#>kl;/4$3mlI–#Ԍ}p|7@Ыi2e,<ٟR=!Huēǣ> 8S ӻ!/$?DSL[ cba "!WII%k*VN,Cy/%Go[״NܻVj }d;f >(=3QWb$@8_[>QS[/Wl^%m5fh=35'd>'I *0$Xh!@Iz 2CAg/J=߿^uWQv.冝-";wrcadFV̼~bɀRS3Cl n"m"V>I,>%dw蹾-\~{4Qc:c66l՚KI"E%fY=* \z{ust_[$?VI@c5e_lJPkFTԃ~W~/+]OK'3 -cKgklg '_X?? =[ ǣv<4}2h%Ej~>'*QuȾsk'JuX=Cf;ͥ:T y0S@͏h͙ 74?<'ƞ V3:sw7iJ$]Pd *|4KmYx0, ZEwۂ?5VoVk R ?~&5<KUg`OzAyM6C$8Y\ER| (iY]_/n go m VJZ/O)΍_Q@h o X*mkߡz_ }ug q(RQ\P;e븍ɓu8bI*cS9g ېX;FS:ycwޒa弽=ooooooooooo][y~c `jw)M\4R:KW 峮 xÏ2H s?8WTU|dwg.0TG1~XFχxid\Sjn+P| K8gElANͥzZtW勦o--˃Wh\0drks%`*+,>w\[@/x_f`CXmMBfMbj)ߴdF\ f&#A0JmS1k}+N܋M},/|Ĵw>Fnæ/!7\֦}^~[Zwaòq:N"R E oo=۷+8U{M MHɊafXX_ʛ׼گ}ٌ 0u)?mmYv]nn %_^P&Ms znFlǩjy{{וR.l7y-x Ӽ0TSiq|@uvרySd&b:)mn40}i1 9P۫6=ގg~9F{|jc!d6k<(^l`9vrׯBvݏG[EwݥKdb4`{݈?ŝ*g; :jaLU+ z+8LVD'\`f @4NJvJ  V7[ɑ݂Gi_w{O=`7J7I/w>pk$&.<foCF<^]62 ⧪mObxm {+u _g?F=WQd@YӪ{f>R8Kz12 ayn g1y%at޾771]i ݤI\544j/Z}m}}}}}}<7<1 AcC ^]MPe-$um pu>l& c;$TҞ;)B 7nP?1`Zdv:|'U~\ q钗SW EkTe/pg&5FA@ˤ}-ַmlzTI/&uDŽ+3Ą>u_!UPS5a;7YZjD?=ъuRriC8qxa0([g~{.t:xCވj'] 3u* #){s:Z"r~D3+\UDb9ʶԁˮ^970R)ӢWE)֜ΑPV]={]+ĹRiܖG۹B]h@0@,xCM1 7>ݤ6%W`TQ9B }&-`Bj OggSza75*~ܴNZ\g\.[C4iA:ߙWK@)9PW @h{ASSph`%R7W Mbn R;нxJ'@gJH߿:)> @<~,to)nether-3.6/textures/000077500000000000000000000000001461347743200145455ustar00rootroot00000000000000nether-3.6/textures/nether_basalt.png000066400000000000000000000012461461347743200200710ustar00rootroot00000000000000PNG  IHDRh6 pHYs  IDAT(r8 @dK8&/:H$ryؿ(DD㹬waR v4+: 0D9֚R5ìf2k=#\d0ͽ4MD9pwY/wB޴,,[mMO{o%dJw Qh=i^bZsߜNoV˫}}v HUN""rjyŽi"!>͋ˬ ""SJx i^j~ZswZ^f:f":/Zc k=}G"""ܚ4T=.̪p{|K՘YDƃ _5%"lWz+UY+Zwrۏ&rLz+S.ۃș"<.!0Q/Z60s EN ԚEfξ(cZwKlkx;be}̧e~{y}V"W4\.fx,?#e4{L|~oqm%tEXtArtistTreer https://github.com/TreerلtEXtCopyright2021 TreerOIENDB`nether-3.6/textures/nether_basalt_chiselled_side.png000066400000000000000000000011251461347743200231050ustar00rootroot00000000000000PNG  IHDRh6 pHYs.#.#x?vIDAT(-YIf:fG@< ]]2ƈ+0oڽo_?۶oFm{㟿u*"I*r]]u= b,˞U"%t{  e59n2ʀMcP z'%sK|N"s^rlyvVwy9$1HI ]J1 6@@6=OU0]kUmCAL ŤowW^EIAjf.TvUxܺ[1uCuoy]1YU#rQĎRE $ wdF.Rh ml0h=׋|D^.R$E2$X2 Ʋ)@>/V!-1bU $JLJ*ֵuY$hO'c;APIJ ȇr>I -Z 8{Bm{/}%DK'KZ=f(˯ۓI;DIȲGIIzܞT:2+J H#( I**_|Eؑށl Tv<;ZWԞr޶=LAj gV3u#RH*4A; Frƽc'$IHHQ"I mHdS,xL2I"0HljQ5Rk!"gTIk= `f'RNY{=fz>___'dL'U?qwFR y}(Id%Zk͸˳UUu 01Y;H{;S7$ -gWxW yH8 D$Fƶg:Y$18Jpj91FB4 @Ԁ"9Hx Iϓ$KC"%` H5ZO) 7HV%fh?~AyntЉ:vTdU^!Ru]}^f{lHUW+(Z|N>ȩ'x:BșqRS:z,N^~Zg{}{$X%Rx|߳K `RǶZ3zy>y8"̉xۇffukY'9N."z P'%$`_ V5<@AJUMʱgPgTJBUL$1L%tEXtArtistTreer https://github.com/TreerلtEXtCopyright2021 TreerOIENDB`nether-3.6/textures/nether_basalt_side.png000066400000000000000000000010061461347743200210670ustar00rootroot00000000000000PNG  IHDRh6 pHYs  gIDAT(=n@ 3^yCewbs9%׏o_{߿|ٲק^oJ"FR'ٙ"3(}/Cg6)@hCLF춴lK23ŃD4 IKiH%eDF2cDnO%U8 C* aH fQ2G([AkR-nX5bO.}r_qeMo`CucdO*mL.]I*R3D)+ O9eL"dO-n`F`M,fP)eR.紀dCթlΆkY8pZ53dK$?$<kT-s^=R9iQ*P7o[6^Dq];|iOaN)kW0ZG(ZI+n\7\I+]ؕP6<J/L24O5vdB& \Ar`?xkTbP+vhOXH(_N,gU4mM- ZD#N3H- aJ+bHnZ=cO)aM'w<"-Z<K1kT.dJ udE^K+F, C"D+]CfN%q]9tJUF'`M)9W@lY3o[8_K){q]`M+znXfR+^L*T;) 8dK"!ZH)bN*ynW{oZ$0_FB(lY5`N+'fP'cQ/uhP\I'{q[lV1gT,kZ5fQ+eR,`N,gU2fR0xkRp_?cP,ylXk\G|DM>'gNEmF;~}7/ H#"pbh&+Ϊ7^xǏϞ*98?8B~|H|y;PyѝnȰ?Ty u0&<՟f _: h, yQX(iZyenHg呋Θwg= az|sGÙ% Np6w|gI4=P2(24b&jkz3.7>G@߼jw|ja3 #`z˭}ͧ)~Νq usztJa9 L5H8`YgyHR\3nrBsGm`DSn;/\KL3`/9,3X'ôv n[!E\2rV7HOtFG7#`bE_Yq2yd$Tf$"*L{5iWB- W"MPPG64gc[&{6+x[X\٪i&$Cb4&h4\߻ ;Vc 6v~jJfi4uVtn۾~͛DA’܁ Џ`㉂ߓU(CXzB4t <ش1y߳l@{ܽ}6խi(:eːBΪF#m:=&㵤 xqcjM)11KWa溤KHA`rT1}gk|X!8M-ƒd5dz};y7{1`MiAeeXgF̌)mߎw~+Z@ p^@ҎfEדH3O8RX`0s8nbIOo[xHSY.0 [6C8Q:y @,yRDwN=]Ҩ袉V*zM,T fDO8Wc/m}r׫C`r")[U]; kbmƚ8:FÂ)bX t?@5Z2\OoG[,l2C]iPխQCB9xY:,E'4eJX4CiS7.Ú[m16B !T JPfRc!{CmLRu\vTYw!뀛.0_뢵'e5EPoj.AAUMQP.L>W`QÑ;kBR\8iPH"!NU{o{ plϒ\H %(ѲQ?D U'*Fl^w!at2^'9P|VDk:#uAS-+]˗= A2jzqmu9¦o reBRnܛs0- 7VHEw18n\Ƹ{D)f>׭FE.*[H=\WqK7%%It'%%B-\S~GUZa$]4e"A)oNb:3(uInnhg #$\B{lm/]*""r((4x4N(+0[9ayXԋ`PuZzHF .8ve}c[W1i ܾsժL׮ @zN*0;<'!c=Ha6۶u݋,WXiMcd hƀ)|9.ĥ4DNKbDAZMuǮpO,1G(,;c?梗sEUtWklhjj+n ;QLڜ=Ȭjfxh\IT=¡UV(ǁ7FS6[b[߸s F`~ d:4Rem.:0Ŗ}9G\xa(B)W"O<*] Ă#|7{ƿ2)w}[\WR+LsFveSeua!dMSV{TߙH8K(:-(MUl0فl"weĎ .kP}m7!(L2Ve+0M\e8zSE7?uʸ35`ٖ!;U|'ձh]trz]YcҜMqˆѕI!BY "@b OX&d"|-gZmźqmRWcjt1{6^jh`$bWBsNP>(T[kݛO=vgZfTv wPM`A'HD|/ tRNS2*:[~^IDATh=hQ! BbkڬAZ;%EVXQ,XX fNbM#06"`p ܇av3EUW]Zz-{sׂww>ܿŝk_C`FYnxw<0nv=KaifSL$ؽ{ %ǧcP|Ay tB_\Eym)B bacN2p7lHG A0NAh L`H&44 Eq'X1 #! VO0|2.!&\ Y.M# > pYXTxzAϐ#y#@ !8 <Yh#oڽ`8 {A/6 ی ~7vl$F!,x 1⁄'MH%"d$!8 ) J JuU5e3f.`"֩@ lqIM>J@rtL5HtqB⚐x, +¯caf.( +/}蹠$|E^pp `8qǟ(svGi)aF_6<_yNS{) ʭ[cpM%tEXtArtistTreer https://github.com/TreerلtEXtCopyright2020 TreerUIENDB`nether-3.6/textures/nether_book_diagram_circular.png000066400000000000000000000100711461347743200231210ustar00rootroot00000000000000PNG  IHDRa PLTECkrrtRNS@f pHYs  uIDAThލZMlyfމvLNZ$p" 1%jSi @-PIQQ}ȁ@%-"@l/0-R6[⍕4EEsHy73;CRho=3R8{aBZ#Q:*[9T#e vƮpV;!SUj-na"rUڠ>i^~@FMjԴ0jB@Nj[QL3݋H!Suo@T HM$"IS5R$ڧ-MO;ft&NH%2egd/ۅn3<W3IRcF{"z3|jL⺜1 _RȝBt5֘{ '!5ȩ-Fq ,p)Aŭ pPpJūg>d&@O'`&ܒB4aQsܗy)>WOqj*6gNѢDYS!Xi@\}y=eazp\a  BEck2ȳ1MuYIMa*Q;2`2C S鉘1UY Lo+DI-);EvegsZ0kuJO{4OȘ0ȀZ/.+Z`&,aYFE0.KUzEԡib* N`8(-#^P- Y}P %ːi`T"B^B{m2yA d-9Z4biO;iCK\`Ϳ99[fUSMt}ǘ)ʹĘ&EA%6X`}X1 5ݩS%n/ͳ;+ò´['N֛YIH@TAV~)o¼Va$/$4B,p6d4<A>8 44H36f4Zr=NM1l9Z>1h wlr2Sanz MIWHX}`dw'0헃\䬎$ZUi?Ld'G/2[kCɆY7N5-B͟Gi]$B̫nf!1.|I BJyjxOyO0,aDkhȦG'γ3]VsF'm#Ԝ/yŴăA wg*NgtSim ٖ0ꢄ_) o a `SSdRdPO0$N M* Éyi&٦/ T qdhign]ڪlNB:Y701oM54-hX#A:h[!o4ӘW,*05k<7>HfL ff51~fzv/AOp~ж)uhG_ivϼ ?JF `Jӟxgq<oL>́Cp%IcSkRq!b!ӌEǽe ǖɆ5o; (!F`Q{~IYD/͟3 Y<._\R.k1ƖD%`  h%2O9vRc?ݧOuѪޔbQ:;EZ1+|p3j~ "O*`lYǒ<[6\TAdU9ŒkjŬp} Sc#hSK}e-JlAkla eu E.!Lbz~0PlS6x#cvΙ)dxqH^#6"3li݇UP$h(}82G"-bO`شh': jy^ӉҶ߽b.j $- B*6Sdok_%H>47bqNY=y^2e\];BJ,r&'m? jfzVvPmc6v0<'D^НZT=Y5qR4#nMi둾! 2zښb1zc`^Yɕ) MO\p(/ī^ww`2z)fL%[qĮrTY轛젟|V5DBlv=-GJ)_=&mTs< W[^_`GZP{xI-o,;%&r_<+5Q2(Wԍ]Y}ّD怾-CW9 i8 e&#߾IQ<]:p[:)4x5~Gdüd@P-YcXdG%tEXtArtistTreer https://github.com/TreerلtEXtCopyright2020 TreerUIENDB`nether-3.6/textures/nether_book_diagram_platform.png000066400000000000000000000064401461347743200231460ustar00rootroot00000000000000PNG  IHDR~o PLTEe-goĻ5>tRNS@f pHYs   \IDATXíYKo i,D&pXD#'qf PD^XvEBf)FAݪh{!ְta!eqP.X(d7"aR Rg{{e[%$r9;9D  rxPSw@,(Y,BcS/rW>ȯ{C@hxQACBݭ5Õ ƢiwhIPF YX\Ǘi&}+,1X :|Π+Ly~  chغQ2:x#32h$dCoЧ@?JWRIP>N_(_%7Ļ,s!fFDn89^y LOE#QqU|~Pv.KaGx(k2qh?c&jFj۬2$ M< %XdK$ _9Sw .lDuũʌG >R׷7!UՎjKQ` P&RO]i(f'+Ԅw3)zPK}0&Sw7P88ú*~:E0Wp?$qGm"0Lyz QM'G۩;,;%Q5'g$d. 9i<`8Nnb ݂dTgzezC iAĥ e$䒄3;Y ST6Xߵ PQbOL)ꪀpn#`>A2\V{w[ E6p(y:%02a@oʴ\c*ؗs"֫D%K޹s0Tf˯H,/R.ByaA]ќc63O,zJ~~LXW+`E iV n;f5`6f-Cs,֐!1rٌI˚!y3aC8V*ĮZȣWЋ HE;:[ Rd`׆o$J',V/,8#ɺkXs{Ԑ.\aWuQܤy%א+%|̤j:6!\1W;klư ӲPBc͌Ulc?nuNb 4KY“܆VrF`Bl.I~gu8/V)94RNZ]kJ. WLX֖uzW ӕš/Y㥸`/SۮX6j> i+Hr*+fT%#FraA]ũCnaqA+=9A=ѰX4,g>Wn Sj-Ulbq4Ҋ1&/󝫧=㜕!E)|ku[{NI4Pur)i0[;͇1m))±eaC&眺׊զ4UiULE tÁUO~"Z4O0W1ۃU#ݪ=cX:awٙ=RZds6e~Sg]*џXM\1%K `g A-QlR *gr!0ӀRQ0q8 \JTBԥKa^t\):0.<+|i%!&'X iRy3ȓtB Žh*ѤpX@9;۷۶a=gwd+1>xyȑ)6e?ݷpӧޫ-)~h03/\)R9#^xoyy V)4W|n?},U;iS} ߛ'򅅗o`%{3ԏ_=Q%\ovܜâP !_p\.1(iyDY& {?|tA!=.} /G^w }'KAXwV%?jǔ~IՇҾ ^h~F!E&@_v7 5%tEXtArtistTreer https://github.com/TreerلtEXtCopyright2020 TreerUIENDB`nether-3.6/textures/nether_book_diagram_traditional.png000066400000000000000000000124021461347743200236270ustar00rootroot00000000000000PNG  IHDR1xr PLTEvY_tRNS@f pHYs  >IDAThލZ]l[GvK/IH#JBa[4nSi-T+ȊL;XKkmWX-xB1hg:,R숄yѽ;=3sg(UŏgΜ33?4g48Ju_P+ɛ@Wj`PTu[}mW)YJ=tJ**Pד$QUJ)㗑k4_rU9cXopCEm$Xr캊<(|=/K/]o(&~ D9(;<,2^^7Ub ~zig?)3^bk#^A䞱mmt!% Pb ΅*W-"w{FEHr-5*J" @or4'`\,%MN#R)5I Q&{ COqz!,|t2 :U_Ƥ%yLVr4OHrYMM3"r_  SHJ G! lAKt~TeaNܽܝpG`0Ga(\Ũzj{,92[H91ڒZd 2P4*ʳ_{ "yrVC%ti]Xu]1diM7]q)ɝ;הJ,Q̠]]Ac-4]˲wb4%#ڱx0L;z{4zEb WDU5t5R>"zuԛ/(ԛj =Gfp**U9wnssgUJ ˈnkJ6S*~+EcEynD7 TEof(_@Flq U-|^Z,!ztXnc~ID]ZqG,NG:J iEp頥 vӔCEʶt:` {^- w?lm qRa`$-%b$ Ċ%ʞư=?(1}A\ItLRԥn(ĮDA<aǃepzIp|?:NA15s1(Mp\P6(zkԙv |)CxLoI*9aַB%nFŖw<Nb9WFaV7)]qWN0ݒq"a0 vߋ<T+@R}h~0Ќu$:~^}쿾]R4xJò]̙+}V-Ln0D(ӫ>fjD[*88"@ ի.Y +4~Z6 { MW |U}Z^!XHf𰕽zL5b@dIQ f ~?,:QIAWGE崄ÑToN' *dh) m)S]xB\NO|R >xm-ݾ [42K rjT4YDLu2I`VTzށ4떇/ɫs(z> PˉC%Sy*)A9D$;wҖL8T8+o&/Jx%E*Ow.oOs 4&2Xy;y7L ?OTH]P,5}@k,9u[\sTj(rު1tԱZC@W 4  t:PB#;nypz@ξcM DqA ItnŊD* 5fަ?xPSg`9zBZnLGwݾ4g-PbRu/Ph1bIdRDH7n!-3Blr&c硡JjOk(J,K="%;LנT?&]n,k,12Ya%w]f(L`#?d h宗+N 9EVlZ_YSޒ Gd廙@pwqh),:,J?x+6.ಆ-TƆ &+S)k󑁟%t|adG\8U}{3W-b!cQ_?P6}1iZuI1AOGCE*3bvoԣkレp,YЩ~7u7o?:_G$zC%>#:=Xc\hq/]RE-tc 9Qp Hf_!N|ݵG.QO.[(HfO:K/xS6G}?qݞ&bwS |ᕒ . 5OYy$5wa^RN%W*9ȢaJh$d)<vD7|)&]O ':1cqK\/Ǧ ߽Tet\zBiO%T&/@R;-\zWbS)0dwJ:1iǪ$,).(T &$tjC I=yt7%m5[b4.R֘Mᕃ*ti)D{A1P[Ț;[EC,5y$wwγ 1 Hx䊻ؚdB;΢kjc S"9=2|4;Rr̖z V]k߯FTgu(u`Im(-z rbw&ձPOrE:?#㣴={I\VfpMtNJ 0L ߥq70K`Ga3YO IҞBxRQBVa!JǴta#k̳8O~-uJiEF=1OY2PiA7٧lZc@*nw!YL,mDes\53?+LLf`` hg kOG-M@e61liH>bMw &y\IJ,> tTab'|5Ϗa큇Y Rͣq.)L+88 Oc=A}ޏĵ{pXq6솢ZX [vƐnΙ ynfX6v@>ə>]`j]WXt&t=a?\o'~Qd&I4挴7my!)d_x}Y=˙#H?283 v-W⃄R f[PGxܾd X WYP[$6)*1fQ]5U͕dw M]ڬ/*"pK(c,BmzZeeCa#vʉ<v/sQf}4Aa1H\?: rS"70#l!>n_^ٗE7OeyG.5h %TͲ6\wDO9! 5O %8a |bYt.y6RFZR Iyכy&F2;bVq(GoԮg-/3*I*{wWɒ/%YFፗ8rHIl5>Zj%f=k|$ҷD45J bM؂bO^-u!Zwbe- mWXoܔ<ޗI x(֎xXuW{#أ;zAa~u9Pi|%tEXtArtistTreer https://github.com/TreerلtEXtCopyright2020 TreerUIENDB`nether-3.6/textures/nether_book_of_portals.png000066400000000000000000000010361461347743200220020ustar00rootroot00000000000000PNG  IHDR(-SgAMA|Q cHRMz%u0`:o_F3PLTE""ī""^&&}mgUŨrǛiEؼ8(tRNSv8bKGDH pHYs  tIME  +tIDATUQ )>"v]Fnx+H;ptC `p Ճ;լ7E0jH"R?0syJx~?_jQh3%tEXtdate:create2019-07-11T22:22:11+10:00=%tEXtdate:modify2019-07-11T22:22:11+10:00LN%tEXtArtistTreer https://github.com/TreerلtEXtCopyright2019 TreerNIENDB`nether-3.6/textures/nether_brick.png000066400000000000000000000010421461347743200177070ustar00rootroot00000000000000PNG  IHDRabKGD pHYs  tIME#f7IDAT8mr0?Flk OÅS 6 ʊF㞞yexi`]׻7@YdlG\.|gߚJaVԊ&R)̪>R" }v-sD3Nse@"@q1V+Rj%8F ׏U d 4Za;2fmY8?G YgE4 "o`:M,x=t>b T39Tmkl ЦF?Sbse;^u]f̪R3zOOpgkάkݥל;HvU)fd3YZThZ ul,r͙cVF3m}T hٌ Kw;ޖ<;^:~cJ,ȵ۷e_Ez=+%v#`1$IENDB`nether-3.6/textures/nether_brick_compressed.png000066400000000000000000000005261461347743200221410ustar00rootroot00000000000000PNG  IHDR(-SZPLTE+/36!!9$#:%$@%'A&(F)&K)(H+(L*)O,,J.+Q.-R/.W/0X/1Z12]45KJ(QK*RL+[O/fd;bf;hf=rpFnrF~{Q pRIDAT !D ՙ˒R$$H**;##?&&;$$5!!+6""03- pHYs  IDAToPEq" ͧ۹I!WYBJ$UYR`Z210""$" DT{E[G{DWq;97$uuC| W޻s8 eIENDB`nether-3.6/textures/nether_brick_deep.png000066400000000000000000000013751461347743200207150ustar00rootroot00000000000000PNG  IHDR TggAMA asRGB0PLTE+/0X23[%&G))M."#B3[Z-.SDEs78^?@o8IJv:;iKIDAT(?hQiS8BwJKz"I0HP$R*f0dzIEpC8*) CR"L!!k.}><k4>QEt/^=, NhTż% $I6c0dLO=ګieʂ8  '赿qb5 s岿,>Ž5pHҮMnH04UZeŁ@g0x(t8|2y5{'\$zh4D#YDIMwDq62H&2$㳄ZD G;5١%K>S@K?>RWذa*PoD U6 FEmB(Z x%}Ɉ.pI eY=Сpx`pODקKKs`{Qꂂ&v>\ܰaO\_..#^Bee*+stQUB`@K 窪46s`y05xXohMD7jjTf `f3 SyjwX@ICO>stHkL7Ry DmNl657ɤJ$wsD$q|ܓ%%,HY 6lve;9#mlQ]ݕUUvuVM0VS`PߤMT&#Q]9 &| 6Ù 4/bo$E`23jL1cSo^&z܍'B! AW3F@K~9^1BsXls/;LVP s,W웵&QU0Ӧ&(SdV}~Hh\0#.:ȕc#rv)]Бpl4*3'[竫rn-aK*osYrŻ`ه5_y̸6" 3RFܣf.KT3=z HDEC.C $ y fF#LC #F8:LvofztۆtYh0 Xr,ѣ&1~ӣ(-CBh,??˻Z_ef&epK"GZCKmn+xAШ:-鞱ea *lY`/E"h Ih`V ^~Z3\/FhIh _áe6D` G& A},\ӿ>I&5d55ϲɝ<ljY^E6O]ӫIx>Ql_F_ŀؘN*X}.'sI. ΔlM=z^(& ̃^R ܃-|ī#ABAt&?+#&jQ8`_8;ڒ thE|~;{oltF#Wk?N$ޮhsN.̵"G`+J)kco]=1.4"^:M-ɵkX $~ iVtkGFt ^!rX…L? 9-OoR%WB$~0fs=\^pmӫAH,^"l - |]{L |p, 0J}oP-j)aR˙י~n| p,LàA{0?}%tEXtArtistTreer https://github.com/TreerلtEXtCopyright2021 TreerOIENDB`nether-3.6/textures/nether_geode_glass.png000066400000000000000000000010701461347743200210720ustar00rootroot00000000000000PNG  IHDR Tg0PLTEQS We~'< |HU 9g``k' pKA_7(a'D$H$Jm]}+#OOZr?{tŖ%H3""(2p+)y sBk0UKD-x=ͪ"Bqr^4A$ƭ&jrz)5[}R/?  eC>v8j[l߹_ W>%tEXtArtistTreer https://github.com/TreerلtEXtCopyright2021 TreerOIENDB`nether-3.6/textures/nether_glowstone.png000066400000000000000000000015541461347743200206460ustar00rootroot00000000000000PNG  IHDRabKGDJIR.} pHYsodtIME--MCIDAT8UKoe%ةݸI&h㖨R@HQEY!)lڿ`Æ ?E$PTd&nC'ė022c{f>8#y)Y3LOgF.vpJ8Vb~y }dI,i2g!BsD,Ząy'{h{4J%۠OAA" u'}Ύd2Au/Y[{x@Bt,Bl6RR.oM_Bpc&&!z0݇z!}'[| a: i:B Àb&t&bn<1 | zZ*0Rh>4La vwwC2 A"[D)SI"dU uk&1e1B.'X,{si| >"bw4M1o]зuV /^ӳ/X^^an1{G/< Bh|N4chE>\$IENDB`nether-3.6/textures/nether_glowstone_deep.png000066400000000000000000000012321461347743200216340ustar00rootroot00000000000000PNG  IHDRh6 pHYsodLIDAT(M͒F$v]SϐG͇$UT&]E1>}閳q8j~A8F\vM;_y6*Ofc}t}̛tPO>e\Dj]MI/`5m̡Iv7ac}I0P  '(GI֡0a,Z[^Zae'C-]QF }Spdq(*RMєEPK4QMs<p[]KyqmlOI['? v C_~۵p`,ᯬ.2M76f|}@A8p3k\) j`,:w`"h6((}p¾#;mzS}ѵł靼$f;K'IcUW{yKѹͱ_~$b=)@>=<;:974\$ 0|5Z2"]7(P1$>*"]%`)r1s8"P4*r$ Nw-j+R$d.j5$.(&Y e#Z ^%[$e3$Y-!V-"a5(\3(N0'_l ` o(KY!l,_)[f S V K n"WG V]"d%MJ?T+"UND\ O Q I ]RHNVOBPc.$V)!B R)"Y0(/)(HM I G C < E 9 KES?3 EMIE[$GBU$N!=],%W+%G#K*%:%"? JQO&!T+&K&"V-(S-)-$#-%$97>.O)&B# G(%M,)=%#37D&$G*)@&%8"!:%%5""/&&0((-&& pHYs  DIDATx^m}|? jktCk+Sz{뒪Wn%Jӈi;i0ZZ 2l)Ѝ";+ Ao*:;nqޟ+Iq?qz(37)-.)fK/ǣIum I1Mo,'f&Z)-wf&i9fqZZs c7Ek.j*{qBq_YHԸbqEmѫ/:&ZUux}_2ų!q9glBn7%|2N䁤-o.x7f#9S._ŎHzfY_gtg;7friFt=7Ί?K*OKvi4z{>=hAЅOZW||}`iK fN.?@p}/8N)_ż_'fNU>=܌[L$͜s;SKK7W䌤3I-^˾ϗݎ?J}=>]ZyWܙ/v5ӯ{Oy֞vi\ScS.9w~YZzv۝Gcs :{IS>r_oY887|î ӓ;{|{UgݑslaJ&E/NPnGli`{} *M߬Zsr?. jI=:x)/g Vr_}4'}YǠp`z_>ϟϭұ3g&?4eݘ'ċ{> g%=OFt'+1эvUsDbfdtʐn/?gdjથ"zdv;Sf͚koHWwkϵK=8o?3i>Ƽ+'_듞|p缁G*YSGh?ףi}pʰoFhĨm[?q{{*ͺMcCC+Q}#ygE 5җ7Ucg웎4H= {{9U&Ky {e}.ߛԄnGbTgVlQ4%=|-ՠ ރ7DÇ~D| O11l]'"Zxqڢ/f> }(!- !jį}[e7330r<sElh!eCl[->C3AB>h}({W[\CŹiGV[R$䃫K^m./6 \, Б%1,i_et`RÇ1a90~z؂s $,Ù{^>[wwӎ/vڷx]|Uo7T0!n0ʙS>85#)ɇMiQ1eM\g Q=*|l45Y u_/;K><K~g\Ì [nߟ}N Stɇ,C3МV=.S‡2zf>.z9 f1IV_k. Mu 0q>:%)䃧`(|PO M$~Q_LćTAm:yHWdn@pX=\U 2B>HWlڻ [?8:>@A*||p+:+jD>Z}( d30S!~з%,Ivҷ# v{L}(}?KOl.0/W:<[7[y઀l|U!0YI⌉V/CAdAGCLBB >0̓>=5z;=:P|(p3Ҕ6Ax.ZґSuR.Yph|p ɇaKPN>/O惭|1ގ?wMbMⅨ*`ōo7_+npM휛Ii~Mx%\gͩil-O&<˽A͎Ą '!;W=ncCB=_oهaP{l>$^w?|S|Pm=Tٛnai36# oӎdO?^H[/ܕ|ΒznS?T6bɬ_$̇.KKFKóLyyއ)ÌԤSF4[& 0m{ϥ_#>dKw(~0qk/#P!+LƯAxߋ݅բԔ27lR yvusKSE'Vg,/!ܪ\_W!ȮbC"LE|W vWf S؇MRuKxN>8h;0>AP|x<%9B> i |uB|; /|7n"íbz_ރ4=9 >|(H'5.Ϟ1d 4Nۧ_8c4`IzC*VUًC>:qX՘0>8#&C䇤ڶoB>Q~1ˀ+C3 q#Bߏ=P?:ʰVqωNgn|H8l5mnG>hb&>G\c#`KvLļ_#*Cfcɇs!|p}/L̇Q*>@pލ䃯Uۓ34_K7wjMMjUsM2MceqӪVߙS \js1W1pP:7 tgCA(gͥOj>|"6.|d<7$U;WKru7o΁ CkA+W}ZᜣK}6L}皟  1Ӥ뾓wFtK>Ah)#|8Q"FNN6!V"PRn׮V(!ͅҴ>6rZqUX"b>Csin Ua) |/V#bi`G<9j# S-_H/ZL `>x7}ArX!Q)ANßGć8+?z{ %׿VRj mXrVUY~eæ:>$LH&C[F_C.ACl| ><,^/o1gJȇrƽÆZ#P\VXwի#T|@&#fM#V`A#V~,~PlCɇЧ3gC|UQ`B>UGQS֕Y& `:>޴wWgIlt gj,œ yeGYp96l'}p> 8 W|Pa||c+u#3⻕U&̇y灿\=6^u|r|3Z0|X|p6[$8QI|/{sno33샊S}UAzO1|{;+.>6jo>jށZ2w hמ%J]ž.>蝦|C obty2=h#ׄ3~.0A^lee5CAZK{5ޡa5 !~P o:A=K>2)aiw̨y秱A=.ICf[0;<0K>X_,e>hɇ1??b,q_fiZZGw*~kܽ ebN푲au|0ޢ0trz=P]g??+ɇ:ĸ–)MGۡBv9Z%O=B>lKɁ㇧} >ɇr_R/t={{6nᅌ| 0bfV79Y)V!V!~U/|H@|Yݪ᩶ >y_ V@0JQ|p(Z.)[xob\Mbi}|eḃrS1z%1uAFAIg>hB>;g1;4lɇO:}|)(>T#ȵk\a2=ן7,yEkAݥ!w2;~4~ /vz%>RQNeoD3ƜUo>.lϖIeGvi{jA?F4SW>|x~|ig>{Tr^=j p.AV[o RV@Y|o5KM>ecg?䃞|H䃬jpz=d‡JAG>B>C'A?X܁B~jJ^5G|@D4ckZ8G!] >?NG;{ N*1'lG:} uAC?9ZD| bCV7sԧas/{nM]{q݂zt҇N|ȔC"T0k^@ߋN5G~&aoA^4M#'hc-1C+TցZ#kUg5DMjMT|'Kl(:RH2~6=샖{ z2)8A=Qܡ{1+|+C=[ٴeMҌ)&Uo|灯.Ǎ| 8}𜿧V]UT*)8öI厘ߏbxQ0[c!tM!ukt#GJ?BI)C0>dIw|ts49Qa 4cWuɇO&㇃{L$ḋ0È3[%MO{{d5 { " >(4 D!ClqOlSF#'|:~*|PS=bE YW}aEE'|򋘄|q@=jVB7"[}6@徥"ۍ| `n/u-k?#?GLAN&އ5u/&TɇsxQ M4?CL Hb)ȇ+ɪxG*Fv^yhW^7Җჺf]Kxyr6|iC=⋝6`m*/5|/Po]V}_+[?Og_:ICPI>(C>< dvzv"'ÄOt|Zz+mmᴿҍ>|r@m*|c%ZKJ8ϟ0e./9afvUA7̷zR)p˥?&a*}/HPޅ~Hэ&rC^|]J6Qfe-S~`C!`o.hfk6V‡nwOP8W~{:^|9'B 8=Ú3 Y />N %P=n/fw>hj̇yANjID>.QR5F0>REYIe;C>r䃂yć\|Y/O>3ṫc=oB>P~itx޹)kw*Izk.6Hj\{X=B&#4UZsÒׅ浠$+겨q-ޡz+4NM3| cTtQ?,]=&fx%v %>2$!e!$fw1jįhWXKmv5"~0AGȭrg(~(}]O5LILDd惙|-0Gu?WIQoⅨbK>.i >@>,y;3p@!5⃾^沅T<L⇯EƢd:[}h)8r@)!jf|{?Imyy4aڐ*g>oEO>{sx;b~]%h-b><",?0X^!TA"Riߝٔ8keja |#>{p&|0Mȇ|65ؑJ6T?>֐!فMB' m|/4|x#*|ȔVG߇y9jΝ%^LFʪK~H\\2棶4R~Mb _Hѿ@MG>NWp~|,=b#: r$i"` P{RLJ>T-v>7kI|,ܚE>‡Y ? χ )@ s| p4FFQD K]\%2LAAI@P%jr6[]%Z 4̇&3?윫[\~!K>3?DתP4t9 [} ϧB0~B>M>Xv;XA_89dGR&V|ٚ?4*۸?tpj4G=;låjWć{(XQ@o |eI==:pjNlrC><>v<|<pz#t:{2es >^w 嗔O7=.c=ܝS~9\bKY{3;z7r e>d4p|p , ~+F&a u>xGQT">PX)N==⃿L_">f$rw|H\FZø/|&<|R|S%f!/oUT?Q&-6kH)^̵ZC./VYz>:&1n$C֖pP'Bz9!ÁFw2:y?ktqJڢ[{i>y?6m`6|$淟.|PWZ_|?N)è *>ȇY _(ԣs JG>C&79*>o7\F\@ >2Rgی=?4/3X7Nd7/aX ]Y#'um|sgFb?Aa>8yf.KyHӣÎVoD|60%4O7n.8Qᬐ_̾|@q1aa:Q@Xȇ2ޏڥ=p}[_c=gC|(0 @R;86|,̇CL"wd PjȇjuA>4ao$}q'5CkT|%>ղuO/RQl|XK˘ڦ@G|Ƞy1/E>|/ C3Jȇ]ȇXݡVCV !>(9[={ۇfkAҏO 7;+j&G|@|A~q |g|3)!;=`5q[;=p/7ZQJڦA6|Vdݐk h@{!2z#EAU i?˦}g5M,=y|a]YYf2 ןE>hh#?A70{cR^@Ն ?J?^6a]9[M2ol惐_ 'Ɯ|`>qY_0'w>j =ƁG?C`LIT/ڸT _wJ1A>_\P@rCCDZN>|œx |q;&)!~x3|(+7 )~ZOJ<|hklfS%+z2r5)CX |Hgɦw!><|x6|<0 )4Oy|HY{l}zoz0l&fG[oB2P O])V*asaŃ|b&*!NjAOʰQ9 '2U\J>d)*&xYDwS4>(CJ/6 j>zؠ |(,1R}|a"ؼd?.}h6Oop|ᛗӅy9BG=~'e䲍C#9N$¼}f+A}|09|wa^.栣>|Hiɹ|'F;_/<ēkꔘ[=P c>(<| p|lsJm"x(] %] ?ш;teC}PkbaEj|. h~V* J{\!PolN@ӡ 91|ȫ1v`&G!5AӖ=8YTYY` #|vB` PZ2hO>(kȇ;u_1az~TibG>"R31o}*1ێZlԊ:|f gC/q CyWA.1ⲕ#u@J!~`>=O>d7]zGE%C!J9CK*@MC橷<|@$`| ] 0o|`$Sk_X/cJBE >Uz{ǃy%3ߧy0N e *~y.iZPdObi>.a|ǼTz+-%}2B!_$2U)A$n·o+Ѕ|񥯢$L""V,~)|pUQsbE>nCmU;pV C>~J,z4/_<֥JJ惞 ;Ԃ/X}rs&r 0'0~qX[v]/*|(m|>oޞt+Z+_kU̇'?`'6V\{Ig"a>(~ЖȇGz1?g}` nC |8%I >Q~!Cele]>(~`>>V(~EćD28/pE|dB>%C^|HY(‡2">h9g!72Vԩ^&!>ȰBPaR z;F'CY#Z惌|SݨOaN|/m楩AvUB zr/|U+ =kCze|Tv)MܦVa26_5+u``qnXEc,~|x7z8Vy)gnY 3zG|P@l?Pewc?h4F-|>|zM, `e-A-`E*nŤi̇yPExEÙe"~4`!+Q8wH$AmC+ho|(|w#^PV)+ea*(q~ h1}{='1Gkg\wb'~-C[JE-'>T`q{E |ñzІ17'+ |Q})⇫&C%7/bT >lVD0 >%p7ۣm o>}!@?>6})a>|'gcv7ϦSJɇxbN/>, h)Tsa2Ç!~|X!˽ˇHX~P$Y;|C0sz{+"CCJ>ii X-{~(OG CvdAr N|>b<4d>M$T|'mJ*Q0ƀ@>,|0fm&$0OɧE|0J̋5F?|"y0JwquO滘S7 x>mW7gdVZ;՘l}(:9V6񘟼\5ȓ$:9}%惊[sˈ||E|Ca,aQqΏv&@Ax‡r54O+Cߜ*^COPoe 䤪> |0l>|8A_gsVr>a[^}r1Q!~#Za 3.6g7;A/"s**\nT\^VQV~g2|mDޑO> v \)};"C g?Sow6o`W*i5avNyZj%~P,e>4/ù>I##^!$_0_*G.U\aYÄ7߄oS ZF曔54/t) erϳ*+̳"_dJisE]E?0c~%䃌!_!wL/d!/ör!%ԿLED/C%?8%7-j\g/tZmvH5_2#~@Df[,G@>lS;_]/z0`<{_C*|8 gvk[w%Ke 14Q{bd/Wablùm aW[? ~7|H4aG}λ|(Pȇ8 52Pd12Ȕ/>զ%|Ws!sQV:|X$Ƽ6'(2_yP@BVA\!>__fڿ?I棨9[?DŽBy2?xi>1ݱ> hڋ C/|u}^sy/||fG,?M~=/w9!6+rÙIL? `?2(~ص;d1>,~d_f yUQR1ՊO >|?O7K?/*#C܄'x f45TT[?ȫf9[E$D:  Eu(|b+nà0/|>#ۑIٜYr+_>(B>`>dV >¤}b 2dy5]6Vf惾znbQm@}aR-x OA ?S|0dB:<_埍ICbG^_ƚ6+cl>Zp'w=>9T?@#y9'?$$?|2t@>,?ַh>۠q崢0F`&Na>x4N~V¼o^ @67oںP.}H_$݇~7q0//`1> ^d~.)ֳ[Q /пIJy|Aghq}>t0_}%akL{lq26%H>(qaaϕyŒ>Hi"?c^NQPC=MAc! :lޚJ"bhH{A)ba؇O>mΡ02t‡ZmZ[`<Ѕ`A `}Ͳ"1OȇyIZ;XW|@4of||}h!0>gC) TI7L 'ʱ_h9,} w tW=Gټ܌s~.0~h!d0ؓe ܏Z bc?kE󴨄{(|#䃂:̴P~AY&>\¾f䃵#[ɇ=*i 8w:bǟ? 4/}bM#ޱ~pYWؿ`䘯>y"a`4? 栿`:y)A>}1NֿMmSz*I_hԦ6=O񃫝1G9fc*^ ~@rb pe,??i?z`̇n$z*oG40󡠘 <%uaZLJ  >us&A=|eQރo2ߙ@GB~AJГw`e>="_(&A|Pi(E4tAZe*%.6ߧ}1ppTr A~㧲2>F> >A D> Ol>nX,~xCjm>ZAm/F[M.:PlᕖtY/$ faNN]>()}CD P{aF#po-~>}ݐ+=tCVv>e:ESހ}1މAq@`g,w{rw1Pq1螘_|@I܃r6=D|h1=zb?4|+:j/)( G)Rf~|0x/ .8Mn_G3OS=VKɇ-#n?7`?⾜0h޾[AW~>۔_2y.ڰN v߬?Vvy ,>,R RE߄^ԉ2 x!c^F|1QhWl}C>X$w7Y~T5}ػ>eSgU:￞=N;_[ IPIr#e0p/舰߭dܠ!-1U5,ܬe*kFOapAC"֯0C|(eFz6aQO}N+ݟ,?_!K!>3>LLu|> Ev[=¾fSK NJ}^+ZW)~ p!G`rV|.ZC |>N Zs._x~{bK6T@ss? GW (~(@?75itvce{p㾜mVMA7)`>}O>'ǡϳb󴗋y_9͖l1V(~JC_i<e΢9B9g>L=݇)7{fēN?gMk}3>܇`Y~3|^~".8 |P}Y|0cxEp?N_(m PF=" /Q l=|\X >t~ђ7~>DcTv_E|UNW>zdP0\!|3RW͐,~#t3k+B=?='u!hcE~u vֲ q_ǜÇ$vq0odjLjܗ}}nOܗ+l< g(~h‡ʛ/<%65GC()=1GC~)|a*C=y~Gш>8Ku}蚃||}RɕQJ0ɫ.asVzPRĹ=f]4_4OWp?|Hy9t YܭpdrK>خ`Z#ݟdM ,!Ej3A.YP"h\U</Ó!{!`=)̢*}%[p2 kP(c辜t7vZQ,O=~VZWf|YY60A)g}9 ~\>wi^x}Ziݗ3zkJ]Imtnp;#y>}/WO\|`iɇs%-2v_']M0ܯOsɸՙ DRlPk.9Et~d՚"?[YzT0ooT:~p/O&Ӳ>EAiç͇|p_.P;T9N>l]oNY5_O*Q٥12O o<_P|}-v ^5 ܧG o~fOFe~!UFOV%D5ȄE)t6yf>WW׶~j+tý^JO1?yHOʸ̐v3*pA?q_,ݯtErra/'r5ݟd/-'v;Rk_CbbއSჂ|p:f}-m;}ȇf>]M>G^#Y׃E U. z2?~ֱfŎA[a>mT˅Qts0%ܟ ēVvW3Lt>-ݯ}\C@7+oOEÑ_-~ qjg!rvCCŗ|$4>=v~ߩ?0dWܸo(pof(&WK~'@B`>oA~u _mVF|Hg^}{vR>+ϱ՘rCq:ݷ]=J|Oޯ^`X\jNNivW@_m a.("jēqea V8~Yj}Doj]~W?+ܯ"pݯ^|x3 ߯D_cZwݷov!3 ḟ}R/ "ܧ=l\O~XA‡WvU$~5ݷ|Hyݷ~uØ>?t st?\Ն}{IFSD)ܷw 0oD3I*>|}{a~upd>(҃$%tEXtArtistTreer https://github.com/TreerلtEXtCopyright2021 TreerOIENDB`nether-3.6/textures/nether_lava_source_animated.png000066400000000000000000000051361461347743200227720ustar00rootroot00000000000000PNG  IHDR DPLTE-9Daw%MVcmI}#18@]e+=Pd8*EL &8el"1O6&5 8 >y{tr(vqw{npqmgkheeb_r^`VZUWRNWTQLKNMFFGDB=??>6;46464410(+*+((%%&  ~}zwurpmjfc\YUQMIC?:51+ \dm pHYs   IDAThWuw%(f/Aa) ǒQ A3ܔ"r!:hbdKq^"Ѳx t㾱؍ml\l ?9'cA N5)D"aM򋴫u_|fjk`KA2ƋQiQ4./%q{o["6rYJ uC4-Qk޷au%3yW:HwYT~# oc!~>ݽƓFހ$&71PKm!៿mˠQf,ZUKyc̶>v?i2 $*S9wO$/Osʱ/<X%]P0ԮRT }7$E4mW*sKͫ Y|YGyvo~QGm ܘ iܠ{5ST?_J_k?= ڊC^3ŒSm6Ř%?]Z8 j,έKik^g^U*#NMꁪozVX83V]'2YCfw`m7誫ӌT}sѣQ7CyQ^ <C:M/|P:26R |ma~z=g ņNi[w騃÷pD)ZQC݂_(`lQel([÷V̧l+R6s:ZݦTKߙ6%쎌1opZ_I^乻?fddK4&>z7r]ͱ9&''G]VW7 ) =}z;K_z6UW^.JX&bwokXo6n6CuFEv1ޠO}ЕvAjkV{];-I(?~"ޠ1p4E$ 7аQxf AYxCb4 L>^~ o6{zŽ䄏W/;zbێ744[8~V"Wd̙׊7H+EFufvWoܭR{-2oNݪR{I" e3~urSEU-ęsoCc}xCA+6mǀ#'P;hm|n]_枣xUu6賲k`tפg&[J;>J˓](UŽ`{̷ű_ VSt.Oxgi>wV'vs)`1(Wn˺ԏ7 hKƒo)z8`黺zzjlj74)ng0靽 ֫ chl|iw *[":6>֧5h8&8z?lWUM{ D/'^`O z=A{ D/'^`O z= 'Ob/^ <@x{$I“ 'Ob/^ <@x{$I“p?>~} @p?>~} @܇=<߃^˓O']7]7zrG={jY$p?>~} @p?>~} @~p}O,}IENDB`nether-3.6/textures/nether_lightstaff.png000066400000000000000000000032051461347743200207530ustar00rootroot00000000000000PNG  IHDRRgAMA asRGB pHYs  KiTXtXML:com.adobe.xmp staff $PLTEGpL;""P**ɮoΑݰZ))a44ݝèi#tRNS@f5IDATc`. ˴91*(aH fe;]"w9%tEXtArtistTreer https://github.com/TreerلtEXtCopyright2021 TreerOIENDB`nether-3.6/textures/nether_nether_ingot.png000066400000000000000000000003131461347743200213020ustar00rootroot00000000000000PNG  IHDRR0PLTEU^b/di4in7jo9tz@w~CKPSX\cfjm8EtRNS@fIIDATc` G?1'20>+'2H{$g !%'d0= IENDB`nether-3.6/textures/nether_nether_lump.png000066400000000000000000000002441461347743200211420ustar00rootroot00000000000000PNG  IHDRRPLTEormcr2p>}JVm"tRNS@f=IDATc` @`װ`0#,45`6 5V2C f#c3H5IENDB`nether-3.6/textures/nether_particle.png000066400000000000000000000002251461347743200204220ustar00rootroot00000000000000PNG  IHDRnv pHYs  GIDAT]ͱ 0D(gHt%fXdC)]-A7EK;\NL2#d)Fd vwIENDB`nether-3.6/textures/nether_particle_anim1.png000066400000000000000000000004441461347743200215120ustar00rootroot00000000000000PNG  IHDR*i pHYs$$P$IDAT(Ͻ 0 @_:Da ud>SC _'$ǘsgbbc%`eC f"L@dpb 6}5}bTz>ݑ$Wr0 Mmc=Q/|'{kS| %tEXtArtistTreer https://github.com/TreerلtEXtCopyright2019 TreerNIENDB`nether-3.6/textures/nether_particle_anim2.png000066400000000000000000000004231461347743200215100ustar00rootroot00000000000000PNG  IHDR1x{ pHYs$$P$tIDAT(ϭ Cmv]=@nOB9Al lƅ_qstXyNKLg] Ey栰V( 4nM~bܺ$/Wsy2'P%tEXtArtistTreer https://github.com/TreerلtEXtCopyright2019 TreerNIENDB`nether-3.6/textures/nether_particle_anim3.png000066400000000000000000000006011461347743200215070ustar00rootroot00000000000000PNG  IHDRMR pHYs$$P$IDAT8˕ۑ0E."ӶwC&982}JoPHr!B%\Vũ}@ [Ad#Ix'%+Fous;^K/ e_K_dbZRɑG-W>FxC M,bT#Yn>9-+gac Ԫ}}a^+zҤֺ)DIPZܽ%tEXtArtistTreer https://github.com/TreerلtEXtCopyright2019 TreerNIENDB`nether-3.6/textures/nether_particle_anim4.png000066400000000000000000000004401461347743200215110ustar00rootroot00000000000000PNG  IHDR*N\gAMA asRGBPLTEGpL<tRNS?@JIDATו 0 V(iyAm5/8YFd*7/rY2Rl!n{5%tEXtArtistTreer https://github.com/TreerلtEXtCopyright2020 TreerUIENDB`nether-3.6/textures/nether_portal.png000066400000000000000000000027341461347743200201270ustar00rootroot00000000000000PNG  IHDR5 pHYs  *PLTEjM-tRNSk,uIDAT8EUr6]:ZQs9761@I=u ^ؾL];LΝh1}݉ƒ{⁖̈́h-3$ zcC\Mc f4" ţ_`CQ,$Dq~D EY t NBudy.`PzJKV`W:$Obj!DI*ODQɜfT\lƞlSޒigV")%bNՕ(敨[#xmIOL&J䧱imSjt.%5s3g*-[03Mm 2z%!Qx&QqS2qY2֓9|GMQ$JVtLKβ_b̼jXLBZ!63oƵ)`c?<ƭ9L\E5Bن_is|iž1)|,8Ss'[=.-ͤ^^̆\xZx85ﰒ_~y怂9T1Νk8YYo(8yᝯ,}+whg$~ujp^iQoKÑ+=/΢ti}mcN7&WgJ_̝-YbiXץc(}'WN-j w=pݳOʭZ֤.[2-qQ+R_(GF.0!uX-X?Vg"JR+D́#Хm ׍WO %jkgѼ2KZ$Zn~iOٶX7lZɹ~nVZ@7O;z,l ˛c xS\"[mJT;JiyNȔ-!D\BR˔|.ʡ68h)3&K 㼷XMCw}h=ykGШI gۣ?TTͪm~Ik^A׎4,{=|[xF4=#&"<CGec;BK k[ ^QH* 1$gudcH,8|x/`p5yA0o/ߝRx ũnʾ0H? AbHؽWFHpm(F,Fv?RBdK'ꀰvcE x(8RU2W+tEXtArtistExtex101 https://github.com/Extex101r$tEXtCopyright2020 Extex101IIENDB`nether-3.6/textures/nether_portal_alt.png000066400000000000000000000004321461347743200207600ustar00rootroot00000000000000PNG  IHDR@t?bKGD$ pHYs  IDATHU 0#L7ڏRh]/71`ܶoM,j. sy K\jVmШ4I7[ a CZG?q @OjX,6$ jow +U>HZK1~ AEYF\p{GՈ+54oIENDB`nether-3.6/textures/nether_portals_palette.png000066400000000000000000000015531461347743200220260ustar00rootroot00000000000000PNG  IHDRqPLTE7n pHYs  IDATc``gdfbeat>IENDB`nether-3.6/textures/nether_rack.png000066400000000000000000000011041461347743200175340ustar00rootroot00000000000000PNG  IHDRh6bKGD pHYs  d_tIME#֒IDAT(=RR1 \d_n~;@HϲՇT#hwOO#LQpcf۲9Zq-1>'շe!yҊpqU)"""V{7ڻy_cLd&#F V8Z֎RrV'w1K)۔J)]}D71TGHs^=f˲{N ٝjR] -KX[D"o "2yq׫h"< G 3I#wWI1lST?Z v)=^SE)Z##@$$A&%B((D))F)+G+,I,-J-.L.0M00O11O22P44S54S88X9:Z<=]=>^??b@@cAAeEDhFGlIIpMNrSSy.IDAT bB! Wؐ%93 0_GM8*=⎵*M釤g:Ǵ?kJe@ͪd٪ƒQHaAr>oRnYVPsnm 13}9y/XH'!tRvtr6r2F(RZxU̅4 IENDB`nether-3.6/textures/nether_sand.png000066400000000000000000000010461461347743200175460ustar00rootroot00000000000000PNG  IHDRh6bKGD pHYs+tIME# fUIDAT(M0 5ɊfB9#/B!x7 ʙ= _2I!F4-ĬD0gc]!ƺ,w)1w@t@RPQתʏ8dQ11X8uY5b]g"wn!HӉyֹ]P1J{n >^BJu~aVY;3*U({itZ ou }] b6FӤvbu.K(&cԞ8d  uNɤ{{/KKWP|V/yBdI,?:M>>ļ0߉RqYڼmBV)gTx;$60VU、EAZ4>KhD:'<4-vQ ?(9IENDB`nether-3.6/textures/nether_smoke_puff.png000066400000000000000000000003631461347743200207600ustar00rootroot00000000000000PNG  IHDR6!gAMA asRGB pHYs  0PLTE{{{pppnnnuuulllb;tRNSb80IDATcf3va0Nfce&p(,U J  d8IENDB`nether-3.6/textures/nether_tool_netheraxe.png000066400000000000000000000003111461347743200216330ustar00rootroot00000000000000PNG  IHDR(-S9PLTE16";%F-Q9lIEWEHYGJ\IL^KOaNSeSXjW_r^dwcl~kqpvu+tRNS@f>IDATc`xxP|B(|.~AA.dvn>V!.^vf](|V6t>3L`bbtFIENDB`nether-3.6/textures/nether_tool_netherpick.png000066400000000000000000000003371461347743200220140ustar00rootroot00000000000000PNG  IHDR(-S~tRNS@fQIDAT}A 7PkK{9LXvNV]YHuZ:o&QVb@;31uaOLj=:4"IENDB`nether-3.6/textures/nether_tool_nethershovel.png000066400000000000000000000003471461347743200223670ustar00rootroot00000000000000PNG  IHDR(-SEPLTEmɶ 0 Q9lIewd;%srG1nmqp6"EWEJ\J`AntRNS@fPIDATӥK0P"?lkg&Qcg As%PfL QZqC0NorϜsz1z>IENDB`nether-3.6/textures/nether_tool_nethersword.png000066400000000000000000000002651461347743200222240ustar00rootroot00000000000000PNG  IHDRR!PLTEnmgyf 0 `r`J\J9'7H7lIO5 wtRNS@fBIDATc`@9Dpu0XB PfT1LjA*%FP IENDB`nether-3.6/textures/nether_transparent.png000066400000000000000000000002001461347743200211510ustar00rootroot00000000000000PNG  IHDRĉbKGDJIR.} pHYs  tIME L IDATc````^*:IENDB`nether-3.6/tools.lua000066400000000000000000000334711461347743200145350ustar00rootroot00000000000000--[[ Copyright (C) 2020 lortas Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ]]-- local S = nether.get_translator minetest.register_tool("nether:pick_nether", { description = S("Nether Pickaxe\nWell suited for mining netherrack"), _doc_items_longdesc = S("Uniquely suited for mining netherrack, with minimal wear when doing so. Blunts quickly on other materials."), inventory_image = "nether_tool_netherpick.png", tool_capabilities = { full_punch_interval = 0.8, max_drop_level=3, groupcaps={ cracky = {times={[1]=1.90, [2]=0.9, [3]=0.3}, uses=35, maxlevel=2}, }, damage_groups = {fleshy=4}, }, sound = {breaks = "default_tool_breaks"}, groups = {pickaxe = 1}, after_use = function(itemstack, user, node, digparams) local wearDivisor = 1 local nodeDef = minetest.registered_nodes[node.name] if nodeDef ~= nil and nodeDef.groups ~= nil then -- The nether pick hardly wears out when mining netherrack local workable = nodeDef.groups.workable_with_nether_tools or 0 wearDivisor = 1 + (3 * workable) -- 10 for netherrack, 1 otherwise. Making it able to mine 350 netherrack nodes, instead of 35. end local wear = math.floor(digparams.wear / wearDivisor) itemstack:add_wear(wear) -- apply the adjusted wear as usual return itemstack end }) minetest.register_tool("nether:shovel_nether", { description = S("Nether Shovel"), inventory_image = "nether_tool_nethershovel.png", wield_image = "nether_tool_nethershovel.png^[transformR90", tool_capabilities = { full_punch_interval = 1.0, max_drop_level=3, groupcaps={ crumbly = {times={[1]=1.0, [2]=0.4, [3]=0.25}, uses=35, maxlevel=3}, }, damage_groups = {fleshy=4}, }, sound = {breaks = "default_tool_breaks"}, groups = {shovel = 1} }) minetest.register_tool("nether:axe_nether", { description = S("Nether Axe"), inventory_image = "nether_tool_netheraxe.png", tool_capabilities = { full_punch_interval = 0.8, max_drop_level=1, groupcaps={ choppy={times={[1]=1.9, [2]=0.7, [3]=0.4}, uses=35, maxlevel=3}, }, damage_groups = {fleshy=7}, }, sound = {breaks = "default_tool_breaks"}, groups = {axe = 1} }) minetest.register_tool("nether:sword_nether", { description = S("Nether Sword"), inventory_image = "nether_tool_nethersword.png", tool_capabilities = { full_punch_interval = 0.7, max_drop_level=1, groupcaps={ snappy={times={[1]=1.5, [2]=0.6, [3]=0.2}, uses=45, maxlevel=3}, }, damage_groups = {fleshy=10}, }, sound = {breaks = "default_tool_breaks"}, groups = {sword = 1} }) minetest.register_craftitem("nether:nether_ingot", { description = S("Nether Ingot"), inventory_image = "nether_nether_ingot.png" }) minetest.register_craftitem("nether:nether_lump", { description = S("Nether Lump"), inventory_image = "nether_nether_lump.png", }) minetest.register_craft({ type = "cooking", output = "nether:nether_ingot", recipe = "nether:nether_lump", cooktime = 30, }) minetest.register_craft({ output = "nether:nether_lump", recipe = { {"nether:brick_compressed","nether:brick_compressed","nether:brick_compressed"}, {"nether:brick_compressed","nether:brick_compressed","nether:brick_compressed"}, {"nether:brick_compressed","nether:brick_compressed","nether:brick_compressed"}, } }) minetest.register_craft({ output = "nether:pick_nether", recipe = { {"nether:nether_ingot","nether:nether_ingot","nether:nether_ingot"}, {"", "group:stick", ""}, {"", "group:stick", ""} } }) minetest.register_craft({ output = "nether:shovel_nether", recipe = { {"nether:nether_ingot"}, {"group:stick"}, {"group:stick"} } }) minetest.register_craft({ output = "nether:axe_nether", recipe = { {"nether:nether_ingot","nether:nether_ingot"}, {"nether:nether_ingot","group:stick"}, {"","group:stick"} } }) minetest.register_craft({ output = "nether:sword_nether", recipe = { {"nether:nether_ingot"}, {"nether:nether_ingot"}, {"group:stick"} } }) if minetest.get_modpath("toolranks") then local function add_toolranks(name) local nethertool_after_use = ItemStack(name):get_definition().after_use toolranks.add_tool(name) local toolranks_after_use = ItemStack(name):get_definition().after_use if nethertool_after_use == nil or nethertool_after_use == toolranks_after_use then return end minetest.override_item(name, { after_use = function(itemstack, user, node, digparams) -- combine nethertool_after_use and toolranks_after_use by allowing -- nethertool_after_use() to calculate the wear... local initial_wear = itemstack:get_wear() itemstack = nethertool_after_use(itemstack, user, node, digparams) local wear = itemstack:get_wear() - initial_wear itemstack:set_wear(initial_wear) -- restore/undo the wear -- ...and have toolranks_after_use() apply the wear. digparams.wear = wear return toolranks_after_use(itemstack, user, node, digparams) end }) end add_toolranks("nether:pick_nether") add_toolranks("nether:shovel_nether") add_toolranks("nether:axe_nether") add_toolranks("nether:sword_nether") end --===========================-- --== Nether Staff of Light ==-- --===========================-- nether.lightstaff_recipes = { ["nether:rack"] = "nether:glowstone", ["nether:brick"] = "nether:glowstone", ["nether:brick_cracked"] = "nether:glowstone", ["nether:brick_compressed"] = "nether:glowstone", ["stairs:slab_netherrack"] = "nether:glowstone", ["nether:rack_deep"] = "nether:glowstone_deep", ["nether:brick_deep"] = "nether:glowstone_deep", ["stairs:slab_netherrack_deep"] = "nether:glowstone_deep" } nether.lightstaff_range = 100 nether.lightstaff_velocity = 60 nether.lightstaff_gravity = 0 -- using 0 instead of 10 because projectile arcs look less magical - magic isn't affected by gravity ;) (but set this to 10 if you're making a crossbow etc.) nether.lightstaff_uses = 60 -- number of times the Eternal Lightstaff can be used before wearing out nether.lightstaff_duration = 40 -- lifespan of glowstone created by the termporay Lightstaff -- 'serverLag' is a rough amount to reduce the projected impact-time the server must wait before initiating the -- impact events (i.e. node changing to glowstone with explosion particle effect). -- In tests using https://github.com/jagt/clumsy to simulate network lag I've found this value to not noticeably -- matter. A large network lag is noticeable in the time between clicking fire and when the shooting-particleEffect -- begins, as well as the time between when the impact sound/particleEffect start and when the netherrack turns -- into glowstone. The synchronization that 'serverLag' adjusts seems to already tolerate network lag well enough (at -- least when lag is consistent, as I have not simulated random lag) local serverLag = 0.05 -- in seconds. Larger values makes impact events more premature/early. -- returns a pointed_thing, or nil if no solid node intersected the ray local function raycastForSolidNode(rayStartPos, rayEndPos) local raycast = minetest.raycast( rayStartPos, rayEndPos, false, -- objects - if false, only nodes will be returned. Default is `true` true -- liquids - if false, liquid nodes won't be returned. Default is `false` ) local next_pointed = raycast:next() while next_pointed do local under_node = minetest.get_node(next_pointed.under) local under_def = minetest.registered_nodes[under_node.name] if (under_def and not under_def.buildable_to) or not under_def then return next_pointed end next_pointed = raycast:next(next_pointed) end return nil end -- Turns a node into a light source -- `lightDuration` 0 is considered permanent, lightDuration is in seconds -- returns true if a node is transmogrified into a glowstone local function light_node(pos, playerName, lightDuration) local result = false if minetest.is_protected(pos, playerName) then minetest.record_protection_violation(pos, playerName) return false end local oldNode = minetest.get_node(pos) local litNodeName = nether.lightstaff_recipes[oldNode.name] if litNodeName ~= nil then result = nether.magicallyTransmogrify_node( pos, playerName, {name=litNodeName}, {name = "nether_rack_destroy", gain = 0.8}, lightDuration == 0 -- isPermanent ) if lightDuration > 0 then minetest.after(lightDuration, function() -- Restore the node to its original type. -- -- If the server crashes or shuts down before this is invoked, the node -- will remain in its transmogrified state. These could be cleaned up -- with an LBM, but I don't think that's necessary: if this functionality -- is only being used for the Nether Lightstaff then I don't think it -- matters if there's occasionally an extra glowstone left in the -- netherrack. nether.magicallyTransmogrify_node(pos, playerName) end ) end end return result end -- a lightDuration of 0 is considered permanent, lightDuration is in seconds -- returns true if a node is transmogrified into a glowstone local function lightstaff_on_use(user, boltColorString, lightDuration) if not user then return false end local playerName = user:get_player_name() local playerlookDir = user:get_look_dir() local playerPos = user:get_pos() local playerEyePos = vector.add(playerPos, {x = 0, y = 1.5, z = 0}) -- not always the cameraPos, e.g. 3rd person mode. local target = vector.add(playerEyePos, vector.multiply(playerlookDir, nether.lightstaff_range)) local targetHitPos = nil local targetNodePos = nil local target_pointed = raycastForSolidNode(playerEyePos, target) if target_pointed then targetNodePos = target_pointed.under targetHitPos = vector.divide(vector.add(target_pointed.under, target_pointed.above), 2) end local wieldOffset = {x= 0.5, y = -0.2, z= 0.8} local lookRotation = ({x = -user:get_look_vertical(), y = user:get_look_horizontal(), z = 0}) local wieldPos = vector.add(playerEyePos, vector.rotate(wieldOffset, lookRotation)) local aimPos = targetHitPos or target local distance = math.abs(vector.length(vector.subtract(aimPos, wieldPos))) local flightTime = distance / nether.lightstaff_velocity local dropDistance = nether.lightstaff_gravity * 0.5 * (flightTime * flightTime) aimPos.y = aimPos.y + dropDistance local boltDir = vector.normalize(vector.subtract(aimPos, wieldPos)) minetest.sound_play("nether_lightstaff", {to_player = playerName, gain = 0.8}, true) -- animate a "magic bolt" from wieldPos to aimPos local particleSpawnDef = { amount = 20, time = 0.4, minpos = vector.add(wieldPos, -0.13), maxpos = vector.add(wieldPos, 0.13), minvel = vector.multiply(boltDir, nether.lightstaff_velocity - 0.3), maxvel = vector.multiply(boltDir, nether.lightstaff_velocity + 0.3), minacc = {x=0, y=-nether.lightstaff_gravity, z=0}, maxacc = {x=0, y=-nether.lightstaff_gravity, z=0}, minexptime = 1, maxexptime = 2, minsize = 4, maxsize = 5, collisiondetection = true, collision_removal = true, texture = "nether_particle_anim3.png", animation = { type = "vertical_frames", aspect_w = 7, aspect_h = 7, length = 0.8 }, glow = 15 } minetest.add_particlespawner(particleSpawnDef) particleSpawnDef.texture = "nether_particle_anim3.png^[colorize:" .. boltColorString .. ":alpha" particleSpawnDef.amount = 12 particleSpawnDef.time = 0.2 particleSpawnDef.minsize = 6 particleSpawnDef.maxsize = 7 particleSpawnDef.minpos = vector.add(wieldPos, -0.35) particleSpawnDef.maxpos = vector.add(wieldPos, 0.35) minetest.add_particlespawner(particleSpawnDef) local result = false if targetNodePos then -- delay the impact until roughly when the particle effects will have reached the target minetest.after( math.max(0, (distance / nether.lightstaff_velocity) - serverLag), function() light_node(targetNodePos, playerName, lightDuration) end ) if lightDuration ~= 0 then -- we don't need to care whether the transmogrify will be successful result = true else -- check whether the transmogrify will be successful local targetNode = minetest.get_node(targetNodePos) result = nether.lightstaff_recipes[targetNode.name] ~= nil end end return result end -- Inspired by FaceDeer's torch crossbow and Xanthin's Staff of Light minetest.register_tool("nether:lightstaff", { description = S("Nether staff of Light\nTemporarily transforms the netherrack into glowstone"), inventory_image = "nether_lightstaff.png", wield_image = "nether_lightstaff.png", light_source = 11, -- used by wielded_light mod etc. stack_max = 1, on_use = function(itemstack, user, pointed_thing) lightstaff_on_use(user, "#F70", nether.lightstaff_duration) end }) minetest.register_tool("nether:lightstaff_eternal", { description = S("Nether staff of Eternal Light\nCreates glowstone from netherrack"), inventory_image = "nether_lightstaff.png^[colorize:#55F:90", wield_image = "nether_lightstaff.png^[colorize:#55F:90", light_source = 11, -- used by wielded_light mod etc. sound = {breaks = "default_tool_breaks"}, stack_max = 1, on_use = function(itemstack, user, pointed_thing) if lightstaff_on_use(user, "#23F", 0) then -- was "#8088FF" or "#13F" -- The staff of Eternal Light wears out, to limit how much -- a player can alter the nether with it. itemstack:add_wear(65535 / (nether.lightstaff_uses - 1)) end return itemstack end })