58 Commits

Author SHA1 Message Date
a6d1f55f0a Technic and Toolranks support (#73)
Co-authored-by: Treer <treer.git@gmail.com>
2024-04-28 18:53:46 +02:00
d16b530685 Call old on_place when not clicking on portal material (#74)
This is needed to work with telemosaic since telemosaic depends on `on_rightclick` being called on beacon node when wielding a mese crystal fragment.
2024-03-09 19:29:03 +01:00
ea677c5a1b Hide unknown nodes under the nether bug (#72)
Hides bug #51 - the node_stone of the biome appears in the whole chunk under the Nether, so make nether:native_mapgen look the same as default:stone, and drop whatever default:stone drops
2023-05-06 14:44:54 +02:00
21c7e2801d restore string key in template
903025e accidently dropped a .
2023-04-16 01:38:43 +10:00
3470b1e22e add some French translations
I Hope all of the formatting is the same. I did check it multiple times.
2023-04-16 01:38:43 +10:00
3e809a811b Merge pull request #65 from Treer/feature/fix-netherrack-slab-upstream
Update localization files
2023-04-15 12:51:51 +10:00
6c6a182366 Update localization files 2023-04-15 12:43:56 +10:00
9c1fa2c843 A fix for Netherrack slab being called Deep Netherrack slab (#63)
* fix netherrack slab being called Deep Netherrack
* Fix several lowercase block names

Several slabs and stairs were incorrectly named.
2023-04-15 12:34:20 +10:00
957fdc887b Add giant crystal geodes (#35) 2023-01-26 15:59:05 +01:00
7dc32f6bb8 fix the sky in the nether caverns when using climate_api (#61)
The mantle and geode skys were working correctly, but not the sky in the main nether area - it would always switch back to stars.
Issue was found by process of elimination, so I don't know why climate_api doesn't accept "nether:nether" as a unique weather name identifier, or how this issue wasn't noticed earlier (did something change?).

climate_api's api_doc.md file says the name "should be prefixed with the mod's name in a way that could look like ``mymod:awesome_weather``. This name should only be used once."

so perhaps names of the form "mymod:mymod" cause issues? (I don't plan on figuring this climate_api behavior out though)
2022-11-20 01:05:03 +11:00
a8a6bb00c5 fumaroles: don't extinguish fires outside of the nether (#60)
thanks
2022-11-14 00:55:49 +11:00
12ca98c25a Add gravel sounds to lava crust (#58) 2022-11-12 22:30:31 +11:00
a03375c5a4 Use Minetest_game fences API if it's available (#57)
Fixes #56, and adds netherbrick rails (for consistency with MTG) if the fences API is found.
Using the MTG fence system adds interop between different fence types, and support for the "Tall fences and walls" option.

I've left the nether's register_craft() call unchanged, it overrides the recipe that default.register_fence() registers, granting 6 fences per crafting instead of MTG's 4.
2022-09-11 14:51:23 +02:00
d3f79e961b Update locale templates 2022-02-13 14:14:03 +11:00
d8e6a6aca3 Treasures (#50)
* Add Netherrack slabs and walls

Also adds deep netherrack stairs, and deep nether brick stairs, slabs, inner stairs and outer stairs.
Adds 9 new nodes in total.

* Treasure: Nether pickaxe takes 10x less wear damage when mining netherrack

cracky-3 mining time of Nether pickaxe reduced from 0.4 to 0.3 to mine netherrack faster.
maxlevel of nether pickaxe dropped from 3 to 2 to increase wear damage with non-netherrack nodes.

* Treasure: Nether staff of light

Adds "Nether staff of Light" and "Nether staff of Eternal Light"
One is limited to 60 uses, and the other has unlimited uses but the glowstone it creates will only last for 40 seconds.
There are no crafting recipes as I hope these to eventually be treasure that can be found in the nether.

See the pull request (#50) for more discussion
2021-11-27 15:40:00 +11:00
247ca0844b Switch Deep-netherbrick to a texture with licence allowing adaption & redistribution (CC BY-SA 4.0)
To fix #52, while trying to capture some of the feel of the original texture.
2021-10-28 23:09:26 +11:00
52038017f3 Make ShiftExistingBiomes available via API (#40)
* bug fixes

Fixes wrong new_y_min when shift_existing_biomes() shifts a biome below the nether floor,
find_surface_anchorPos() no longer assumes y=0 will be outside the nether.
Nil-reference fixed when a mods tries to register a portal after mods are finished loading, but the portal shape+material was already registered by another mod.

* Make ShiftExistingBiomes available via API

makes the ShiftExistingBiomes function available to other mods via the nether global, since it's not a simple function and biomes would also need to be shifted if another mod wants to add a second nether layer.

* Allow layers to extend the depth of nether effects

Mods can set/lower nether.DEPTH_FLOOR_LAYERS when creating a layer under the nether. This allows multiple layer mods to know where their ceiling should start, and to be included in the effects which only happen in the nether.

* document nether API

More of a tentative interop guide than an API.
Use snake_case for API functions.
2021-07-18 16:44:37 +10:00
54613d673a Update README.md
add texture license info
2021-07-11 12:11:39 +10:00
023539c297 add deep nether brick (#47)
* add deep nether brick
* Update crafts.lua
2021-07-11 12:10:58 +10:00
b90afa0b86 fix nil bug in portal_api.lua
fixes a nil bug in portal_api.lua where nodedef.is_ground_content isn't available.
2021-07-08 10:14:24 +10:00
fb6b666895 Fix deprecation warnings (#44)
Co-authored-by: sys4 <bricassa@sys4.fr>
2021-04-18 16:30:05 +02:00
fb3c358f74 #43 Ethereal compatibility
Ethereal clears every biome when it starts to get rid of the default mapgen, which breaks the nether and can cause crashes.
Adding Ethereal as an optional dependency ensures the nether is loaded after Ethereal has already cleared the biomes.
2021-03-14 16:37:48 +11:00
ecce6e27c9 #42 make lava crust walkable
Fixes issue #42 - mobs not being able to walk on lava_crust
2021-03-14 16:37:48 +11:00
3f4b014298 #41 Allow nether to be in the sky
fixes issue #41
(Now that the Nether no longer extends all the way to -31000)
2021-03-14 16:37:48 +11:00
bafa4e8ea1 Improve dungeons
* clears netherrack from dungeon stairways
* Adds pillars or mezzanine floors to large rooms
* slightly more careful with window placement
* glowstone sometimes added behind in windows
2021-02-13 16:47:26 +11:00
b30272d2dd Move dungeon excavation functions to separate file 2021-02-13 16:47:26 +11:00
87f0ea7a51 Implement PR feedback 2021-02-13 13:00:41 +11:00
3008b167b2 climate_api support - add distance fog
Add more appropriate/atmosphere distance fog to the nether, via climate_api mod to avoid conflicting with other mods.
Any game or server with climate_api mod installed will be expecting climate_api to take control of sky values.
2021-02-13 13:00:41 +11:00
00099f2aa2 Fix #36 - better handling of biome definition tables
Add support for min_pos and max_pos in biome definition tables, and follow the same interpretation logic as Minetest, same defaults/fallbacks etc.
2021-02-06 00:25:27 +11:00
89a467698a minor fixes
Fixes issue #34 (get_node_group deprecated), and removes nether:lava_source from creative_inventory
2021-02-02 17:15:22 +11:00
979493ed64 Fix nil reference
The error below was reported, so handle the existence of a mod called "bucket" which doesn't have any liquids field:

Runtime error from mod 'nether' in callback on_mods_loaded(): /mods/environment/nether/nodes.lua:378: attempt to index field 'liquids' (a nil value)
stack traceback:
/mods/environment/nether/nodes.lua:378: in function /mods/environment/nether/nodes.lua:373
/minetest/builtin/game/register.lua:429: in function /minetest/builtin/game/register.lua:413
2021-01-26 19:49:10 +11:00
1fbab7451e Update README.md 2021-01-22 15:35:50 +11:00
4b508f8492 Update README.md 2021-01-15 00:29:23 +11:00
24845dce1e Update descriptions 2021-01-15 00:29:23 +11:00
00cda53a13 Add the Mantle
Adds a magma oceans region to the nether outside the existing nether caverns, which can be reached via tunnels.

Other misc changes:
* chatcomment nether_whereami, a debug aid for knowing which perlin-noise region you are in
* Nether ores no longer obtainable on the ceiling
* Move crafts into crafts.lua
* Add steam to lava cooling, and play bubbling lava upon death by lava
* Add cracked netherbrick
    a decorative node which can only be obtained from dungeons or structures
    I encourage someone to improve or replace the cracked netherbrick texture. For copyright purposes it's currently a derivative work (by me, 2020) from nether_brick.png, which is licensed under CC BY-SA 3.0 by PilzAdam, so it can fall under the "All other media" PilzAdam's credit in readme.md rather than need its own entry.
2021-01-15 00:29:23 +11:00
5a2a6c63f1 Add tools attributions 2021-01-04 18:05:48 +11:00
00bb132edb add "fire" optional dependency
fumaroles use flame nodes if available
2021-01-04 18:05:48 +11:00
c7776cc082 Add nether based tools to mod (#31)
* textures for new tool items

* axe, shovel, pick, sword based on nether

* run also tools.lua

* Textture for Compressed Netherbrick

* new node "Compressed Netherbrick"

* define how to craft "Compressed Netherbrick"

* change recipe for NetherLump and remove old node

* Delete nether_nether_compressed.png
2021-01-01 23:54:48 +11:00
09ca1efd62 Square smoke puffs
The round ones look nice and cartoony, but not the right art direction for Minetest Nether.
Making use of the existing default_item_smoke.png is also a close contender for smoke puffs, but it's not translucent and I think the roundness of it reduces how much the puffs form into plumes.
2020-12-28 20:38:55 +11:00
f3c6f2df23 Add fumaroles (Nether chimneys)
Or they can be used on the surface as cottage chimneys.
2020-12-28 20:38:55 +11:00
2fb93b8c74 Add Concealed crevice / Lava sinkhole 2020-12-28 20:38:55 +11:00
1268049706 Remove string.lower so item name is correctly translated 2020-09-28 00:07:03 +10:00
c70a669709 Add french translation 2020-09-28 00:07:03 +10:00
02d062b9c9 Handle large protected depths
Portals will now fail to ignite instead of defaulting to a fallback depth of (starty - 256) when no unprotected target can be found.
The search depth is also increased from (starty - 256) to (starty - 646)

This is to properly handle large protected areas that are sometimes set up at spawn - see issue #26
2020-08-02 12:19:30 +10:00
e326a94266 Add nether.debug() (#28) 2020-07-26 15:07:39 +10:00
5b3b56ebec Respect protected areas when spawning remote portals (#27)
* respect protected areas when spawning remote portals

* Use portal_shape to determine area to check for natural/protected

* Log warning on deprecated function call
2020-07-26 14:34:06 +10:00
8769593d6f Reduce lava in biomes-based mapgen (#25)
The biomes-based mapgen was creating the same amount of lava as the original nether mapgen, but it doesn't have the same issue of chunk emerge order sometimes causing lava in the overdraw regions to get removed. This adjustment will hopefully balance that a little.

Also makes glowstone stalactite a bit rarer.

Adjusting lava ore scarcity in the biomes-mapgen doesn't cause forwards or backwards compatibilty issues with existing maps, likewise with schematic rarety like glowstone stalactites, so can afford to fiddle and tune.
2020-07-12 12:12:00 +10:00
5cb9e5fb27 Merge pull request #24 from Treer/bugfix/OutOfMemory
Fix Out Of Memory issue
2020-06-18 00:47:03 +10:00
ab4a031c1c Fix Out Of Memory issue
Also another naming convention update - it's been get_3d_map_flat() sin Jan 2018.
2020-06-18 00:41:07 +10:00
aac3ea6719 Merge pull request #13 from Treer/feature/biome_mapgen
Switch mapgen to using biomes (still with backwards compatibility)
2020-06-08 21:05:16 +10:00
f4255f5d1f Add biomes-based implementation of the mapgen, and decorations
Prevents the c++/native mapgen from placing ores or decorations (when useBiomes is true).
The biomes-based implementation is faster, more deterministic, and keeps its dungeons, but requires MT 5.1 features to work.

Also
 * Glowstone & Netherrack stalactites
 * Include the new decorations in mapgen_nobiomes
 * Decorate dungeons - Add windows and glowstone "chandeliers" to some rooms
 * Configurable Nether floor and ceiling depths
2020-06-08 21:00:58 +10:00
94222d44e0 Preserve original mapgen as a "nobiomes" version
For v6 mapgen which doesn't support biomes, or MT versions prior to 5.1 which don't support the necessary biome features
2020-06-05 19:36:30 +10:00
86105b4eb8 fix lighting bug in mapgen
Removes hard black edges appearing at emerge boundaries.
2020-06-05 19:32:14 +10:00
0e26770830 Merge pull request #18 from Treer/feature/portals-api-facedir-support
Add facedir support to Portals API
2020-06-01 22:08:15 +10:00
e5fbc2486b Allow a colorfacedir color to be specified for a portal's frame node
A color could also be specified via param2 in a portal schematic.
2020-05-31 19:48:26 +10:00
608d692401 Fix logic bug in parsing of nether_realm_enabled
The second argument of minetest.settings:get_bool is the default value (for when the configuration value is not set). Remove the superfluous `or nether.NETHER_REALM_ENABLED` which rendered the config option useless, because it always forced the variable to true.
2020-05-31 09:40:23 +02:00
f7ebd78614 New/extra portal animation
(new animation by Extex101)
The original texture is now "nether_portal_alt.png", and used to provide a "nether:portal_alt" node, which is used/demoed by the Surface-portal as the original animation still has a "it's full of stars" charm coming out when used for lighter coloured portals.
2020-05-31 09:31:16 +02:00
e113db1478 Add facedir support to Portals API
Portal shematics include facedir information for when new portals are spawned using frame nodes that are facedir or colorfacedir
2020-03-04 00:31:33 +11:00
52 changed files with 4672 additions and 338 deletions

View File

@ -2,10 +2,12 @@ unused_args = false
allow_defined_top = true
globals = {
"bucket",
"nether"
}
read_globals = {
"climate_api",
"core",
"default",
"DIR_DELIM",
@ -23,9 +25,14 @@ read_globals = {
"PseudoRandom",
"stairs",
"stairsplus",
"string.split",
table = { fields = { "copy", "getn" } },
"technic",
"toolranks",
"vector",
"VoxelArea",
"VoxelManip",
"walls",
xpanes = { fields = { "register_pane" } },
}

View File

@ -1,9 +1,32 @@
Nether Mod for Minetest, with Portals API.
# 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.
See settingtypes.txt or go to "Settings"->"All Settings"->"Mods"->"nether"
in the game to view the options provided by this mod.
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:
@ -27,21 +50,35 @@ SOFTWARE.
### [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_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.
* `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_portal_ignition_failure.ogg`: Treer, 2019
* `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_rack.png`: Zeg9
* `nether_glowstone.png`: BlockMen
* `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.
All other media: Copyright © 2013 PilzAdam, licensed under CC BY-SA 3.0 by PilzAdam.

72
crafts.lua Normal file
View File

@ -0,0 +1,72 @@
--[[
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

View File

@ -1,7 +1,10 @@
stairs
default
moreblocks?
mesecons?
loot?
dungeon_loot?
stairs
doc_basics?
dungeon_loot?
fire?
loot?
mesecons?
moreblocks?
climate_api?
xpanes?

218
init.lua
View File

@ -19,6 +19,13 @@
]]--
-- 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")
@ -35,31 +42,111 @@ 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 = -5000 -- The y location of the Nether
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
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) or nether.NETHER_REALM_ENABLED
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] = '<nil>'
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
dofile(nether.path .. "/mapgen.lua")
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(
@ -83,15 +170,15 @@ This opens to a truly hellish place, though for small mercies the air there is s
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
return pos.y < nether.DEPTH_CEILING and pos.y > nether.DEPTH_FLOOR
end,
find_realm_anchorPos = function(surface_anchorPos)
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 - 1000 -- temp value so find_nearest_working_portal() returns nether portals
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 =
@ -100,13 +187,13 @@ The expedition parties have found no diamonds or gold, and after an experienced
if existing_portal_location ~= nil then
return existing_portal_location, existing_portal_orientation
else
local start_y = nether.DEPTH - math.random(500, 1500) -- Search starting altitude
destination_pos.y = nether.find_nether_ground_y(destination_pos.x, destination_pos.z, start_y)
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)
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.
@ -116,7 +203,7 @@ The expedition parties have found no diamonds or gold, and after an experienced
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 = 0 -- temp value so find_nearest_working_portal() doesn't return nether portals
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 =
@ -125,7 +212,7 @@ The expedition parties have found no diamonds or gold, and after an experienced
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")
destination_pos.y = nether.find_surface_target_y(destination_pos.x, destination_pos.z, "nether_portal", player_name)
return destination_pos
end
end,
@ -161,4 +248,103 @@ The expedition parties have found no diamonds or gold, and after an experienced
end
})
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
)

141
locale/nether.fr.tr Normal file
View File

@ -0,0 +1,141 @@
# 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 dobsidienne, qui peut être trouvée profondément sous terre, là où leau 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 sestimer heureux que lair 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 lexpédition nontr trouvé ni diamants ni or, et après quun groupe de recheche expérimenté nait pas réussi à retrouver la piste dun membre de lexpédition disparu, je nai dautre 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 lair à linté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 dautres mondes. Il peut parfois être trouvé dans des coffres de dongeons, cependant la copie de ce livre nest pas nécessaire puisque son contenu est inclus dans lencyclopé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 souvrent vers dautres 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 souvrent vers dautres 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 dautres mondes peuvent être ouvert en construisant un cadre de la bonne forme avec les bons blocs, puis en utilisant un objet pour lactiver. 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 sils sont construits profondément sous terre. Ils ont lair de préférer une direction étrange, avec le portail de sortie ne se reliant au portail dentrée que tant quils 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 jai 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 deau 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.

140
locale/template.txt Normal file
View File

@ -0,0 +1,140 @@
# 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.=

View File

@ -2,6 +2,12 @@
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
@ -22,14 +28,231 @@
-- Parameters
local NETHER_DEPTH = nether.DEPTH
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
local np_cave = {
mapgen.np_cave = {
offset = 0,
scale = 1,
spread = {x = 384, y = 128, z = 384}, -- squashed 3:1
@ -40,180 +263,253 @@ local np_cave = {
--flags = ""
}
local cavePointPerlin = nil
-- Stuff
mapgen.get_cave_point_perlin = function()
cavePointPerlin = cavePointPerlin or minetest.get_perlin(mapgen.np_cave)
return cavePointPerlin
end
local yblmax = NETHER_DEPTH - BLEND * 2
mapgen.get_cave_perlin_at = function(pos)
cavePointPerlin = cavePointPerlin or minetest.get_perlin(mapgen.np_cave)
return cavePointPerlin:get_3d(pos)
end
-- Mapgen
-- Initialize noise object, localise noise and data buffers
-- Buffers and objects we shouldn't recreate every on_generate
local nobj_cave = nil
local nbuf_cave = nil
local dbuf = nil
local nbuf_cave = {}
local dbuf = {}
-- Content ids
local c_air = minetest.get_content_id("air")
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 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 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)
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")
-- 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
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_DEPTH then
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 x1 = maxp.x
local y1 = maxp.y
local z1 = maxp.z
local x0 = minp.x
local y0 = minp.y
local z0 = minp.z
local vm, emin, emax = minetest.get_mapgen_object("voxelmanip")
local area = VoxelArea:new{MinEdge = emin, MaxEdge = emax}
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 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 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 ystride = x1 - x0 + 1
local zstride = ystride * ystride
local chulens = {x = ystride, y = ystride, z = ystride}
local minposxyz = {x = x0, y = y0, z = z0}
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(np_cave, chulens)
local nvals_cave = nobj_cave:get3dMap_flat(minposxyz, nbuf_cave)
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)
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
if y > yblmax then
tcave = TCAVE + ((y - yblmax) / BLEND) ^ 2
else
tcave = TCAVE
end
in_chunk_y = true
end
local dungeonRooms = mapgen.build_dungeon_room_list(data, area) -- function from mapgen_dungeons.lua
local abs_cave_noise, abs_cave_noise_adjusted
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
local contains_nether = false
local contains_mantle = false
local contains_ocean = false
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
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
-- 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
contains_nether = true
if in_chunk_yzx then -- In mapchunk
if nvals_cave[ni] > tcave then -- Only excavate cavern in mapchunk
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
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
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
ni = ni + 1 -- Only increment noise index in mapchunk
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)
vm:set_lighting({day = 0, night = 0})
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)
end
-- use knowledge of the nether mapgen algorithm to return a suitable ground level for placing a portal.
function nether.find_nether_ground_y(target_x, target_z, start_y)
local nobj_cave_point = minetest.get_perlin(np_cave)
-- 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
for y = start_y, start_y - 4096, -1 do
local nval_cave = nobj_cave_point:get3d({x = target_x, y = y, z = target_z})
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
local minp = {x = target_x - 1, y = y , z = target_z - 2}
local maxp = {x = target_x + 2, y = y + 4, z = target_z + 2}
if nether.volume_is_natural(minp, maxp) then
return y + 1
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)
nether.find_nether_ground_y(target_x, target_z, y - 16, player_name)
end
else -- Not enough space, reset air to zero
air = 0
@ -221,5 +517,7 @@ function nether.find_nether_ground_y(target_x, target_z, start_y)
end
end
return start_y -- Fallback
end
return math_max(start_y, NETHER_FLOOR + BLEND) -- Fallback
end
minetest.register_on_generated(on_generated)

592
mapgen_decorations.lua Normal file
View File

@ -0,0 +1,592 @@
--[[
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"
})

334
mapgen_dungeons.lua Normal file
View File

@ -0,0 +1,334 @@
--[[
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

221
mapgen_geodes.lua Normal file
View File

@ -0,0 +1,221 @@
--[[
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
end

512
mapgen_mantle.lua Normal file
View File

@ -0,0 +1,512 @@
--[[
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
}
)

241
mapgen_nobiomes.lua Normal file
View File

@ -0,0 +1,241 @@
--[[
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

View File

@ -1 +1,4 @@
name = 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

View File

@ -0,0 +1,40 @@
local 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

78
nether_api.txt Normal file
View File

@ -0,0 +1,78 @@
Modding/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.

949
nodes.lua

File diff suppressed because it is too large Load Diff

View File

@ -23,8 +23,9 @@
]]--
local DEBUG = false
local DEBUG_IGNORE_MODSTORAGE = false -- setting true prevents portals from knowing where other portals are, forcing find_realm_anchorpos() etc. to be executed every time
-- 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
@ -51,6 +52,11 @@ 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
@ -113,9 +119,28 @@ metadata).
]]
local __ = {name = "air", prob = 0}
local AA = {name = "air", prob = 255, force_place = true}
local OO = {name = "default:obsidian", prob = 255, force_place = true}
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
@ -285,25 +310,26 @@ nether.PortalShape_Traditional = {
AA,AA,AA,AA,
AA,AA,AA,AA,
AA,AA,AA,AA,
OO,OO,OO,OO,
OO,AA,AA,OO,
OO,AA,AA,OO,
OO,AA,AA,OO,
OO,OO,OO,OO,
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
@ -449,7 +475,7 @@ nether.PortalShape_Circular = {
__,__,AA,AA,AA,__,__,
__,__,__,__,__,__,__,
__,__,__,__,__,__,__,
__,__,__,__,__,__,__,
__,AA,AA,AA,AA,AA,__,
__,AA,AA,AA,AA,AA,__,
@ -457,7 +483,7 @@ nether.PortalShape_Circular = {
__,AA,AA,AA,AA,AA,__,
__,AA,AA,AA,AA,AA,__,
__,__,__,__,__,__,__,
__,__,__,__,__,__,__,
__,AA,AA,AA,AA,AA,__,
AA,AA,AA,AA,AA,AA,AA,
@ -465,15 +491,15 @@ nether.PortalShape_Circular = {
AA,AA,AA,AA,AA,AA,AA,
__,AA,AA,AA,AA,AA,__,
__,__,AA,AA,AA,__,__,
__,__,OO,OO,OO,__,__,
__,OO,AA,AA,AA,OO,__,
OO,AA,AA,AA,AA,AA,OO,
OO,AA,AA,AA,AA,AA,OO,
OO,AA,AA,AA,AA,AA,OO,
__,OO,AA,AA,AA,OO,__,
__,__,OO,OO,OO,__,__,
__,__,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,
@ -481,7 +507,7 @@ nether.PortalShape_Circular = {
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,__,
@ -497,7 +523,8 @@ nether.PortalShape_Circular = {
__,__,AA,AA,AA,__,__,
__,__,__,__,__,__,__,
__,__,__,__,__,__,__,
}
},
facedirNodes = facedirNodeList
}
} -- End of PortalShape_Circular class
@ -597,35 +624,36 @@ nether.PortalShape_Platform = {
size = {x = 5, y = 5, z = 5},
data = { -- note that data is upside down
__,__,__,__,__,
OO,OO,OO,OO,OO,
OU4,OW,OW,OW,OU3,
__,AA,AA,AA,__,
__,AA,AA,AA,__,
__,__,__,__,__,
__,OO,OO,OO,__,
OO,AA,AA,AA,OO,
__,OU4,OW,OU3,__,
ON,AA,AA,AA,OS,
AA,AA,AA,AA,AA,
AA,AA,AA,AA,AA,
__,AA,AA,AA,__,
__,OO,OO,OO,__,
OO,AA,AA,AA,OO,
__,ON,OD,OS,__,
ON,AA,AA,AA,OS,
AA,AA,AA,AA,AA,
AA,AA,AA,AA,AA,
__,AA,AA,AA,__,
__,OO,OO,OO,__,
OO,AA,AA,AA,OO,
__,OU,OE,OU2,__,
ON,AA,AA,AA,OS,
AA,AA,AA,AA,AA,
AA,AA,AA,AA,AA,
__,AA,AA,AA,__,
__,__,__,__,__,
OO,OO,OO,OO,OO,
OU,OE,OE,OE,OU2,
__,AA,AA,AA,__,
__,AA,AA,AA,__,
__,__,__,__,__,
}
},
facedirNodes = facedirNodeList
}
} -- End of PortalShape_Platform class
@ -640,8 +668,8 @@ nether.PortalShape_Platform = {
-- Portal implementation functions --
-- =============================== --
local debugf = nether.debug
local ignition_item_name
local S = nether.get_translator
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
@ -799,7 +827,7 @@ end
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
if DEBUG then minetest.chat_send_all("Adding/updating portal in mod_storage: " .. key) end
debugf("Adding/updating portal in mod_storage: " .. key)
mod_storage:set_string(
key,
minetest.serialize({orientation = orientation, active = ignited})
@ -812,7 +840,7 @@ end
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
if DEBUG then minetest.chat_send_all("Removing portal from mod_storage: " .. key) end
debugf("Removing portal from mod_storage: " .. key)
mod_storage:set_string(key, "")
end
end
@ -850,7 +878,7 @@ local function list_closest_portals(portal_definition, anchorPos, distance_limit
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 {}
if DEBUG then minetest.chat_send_all("found " .. found_name .. " listed at distance " .. distance .. " (within " .. distance_limit .. ") from dest " .. minetest.pos_to_string(anchorPos) .. ", found: " .. minetest.pos_to_string(found_anchorPos) .. " orientation " .. info.orientation) end
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
@ -902,14 +930,14 @@ end
function extinguish_portal(pos, node_name, frame_was_destroyed)
-- mesecons seems to invoke action_off() 6 times every time you place a block?
if DEBUG then minetest.chat_send_all("extinguish_portal" .. minetest.pos_to_string(pos) .. " " .. node_name) end
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
if DEBUG then minetest.chat_send_all(" no active portal found to extinguish") end
debugf(" no active portal found to extinguish")
return false
end
@ -961,7 +989,7 @@ function extinguish_portal(pos, node_name, frame_was_destroyed)
end
if target ~= nil then
if DEBUG then minetest.chat_send_all(" attempting to also extinguish target with wormholePos " .. minetest.pos_to_string(target)) end
debugf(" attempting to also extinguish target with wormholePos %s", target)
extinguish_portal(target, node_name)
end
@ -978,7 +1006,8 @@ 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)
if DEBUG then minetest.chat_send_all("set_portal_metadata(ignite=" .. tostring(ignite) .. ") at " .. minetest.pos_to_string(anchorPos) .. " orient " .. orientation .. ", setting to target " .. minetest.pos_to_string(destination_wormholePos)) end
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
@ -1006,7 +1035,7 @@ local function set_portal_metadata(portal_definition, anchorPos, orientation, de
if existing_p1 ~= "" then
local existing_p2 = meta:get_string("p2")
if existing_p1 ~= p1_string or existing_p2 ~= p2_string then
if DEBUG then minetest.chat_send_all("set_portal_metadata() found existing metadata from another portal: existing_p1 " .. existing_p1 .. ", existing_p2" .. existing_p2 .. ", p1 " .. p1_string .. ", p2 " .. p2_string .. ", will existinguish existing portal...") end
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)
@ -1070,7 +1099,7 @@ local function is_portal_at_anchorPos(portal_definition, anchorPos, orientation,
-- 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
if DEBUG then minetest.chat_send_all("Forced loading of 'ignore' node at " .. minetest.pos_to_string(check_pos) .. ", got " .. foundName) end
debugf("Forced loading of 'ignore' node at %s, got %s", check_pos, foundName)
if foundName ~= frame_node_name then
nodes_are_valid = false
@ -1137,8 +1166,29 @@ local function is_within_portal_frame(portal_definition, pos)
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,
@ -1159,7 +1209,7 @@ local function build_portal(portal_definition, anchorPos, orientation, destinati
function(pos) minetest.swap_node(pos, wormholeNode) end
)
if DEBUG then minetest.chat_send_all("Placed " .. portal_definition.name .. " portal schematic at " .. minetest.pos_to_string(portal_definition.shape.get_schematicPos_from_anchorPos(anchorPos, orientation)) .. ", orientation " .. orientation) 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)
@ -1173,13 +1223,13 @@ end
-- Make portals immortal for ~20 seconds after creation
local function remote_portal_checkup(elapsed, portal_definition, anchorPos, orientation, destination_wormholePos)
if DEBUG then minetest.chat_send_all("portal checkup at " .. elapsed .. " seconds") end
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
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
@ -1188,7 +1238,7 @@ local function remote_portal_checkup(elapsed, portal_definition, anchorPos, orie
-- 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)
if DEBUG then minetest.chat_send_all("!!! " .. message) end
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)
@ -1216,7 +1266,7 @@ end
-- specified if an existing portal was already found there.
local function locate_or_build_portal(portal_definition, suggested_wormholePos, suggested_orientation, destination_wormholePos)
if DEBUG then minetest.chat_send_all("locate_or_build_portal() called at wormholePos" .. minetest.pos_to_string(suggested_wormholePos) .. " with suggested orient " .. suggested_orientation .. ", targetted to " .. minetest.pos_to_string(destination_wormholePos)) end
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;
@ -1237,13 +1287,13 @@ local function locate_or_build_portal(portal_definition, suggested_wormholePos,
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.
if DEBUG then minetest.chat_send_all(" Build unnecessary: already a lit portal that links back here at " .. minetest.pos_to_string(found_anchorPos) .. ", orientation " .. result_orientation) end
debugf(" Build unnecessary: already a lit portal that links back here at %s, orientation %s", found_anchorPos, result_orientation)
else
if DEBUG then minetest.chat_send_all(" Build unnecessary: already a lit portal at " .. minetest.pos_to_string(found_anchorPos) .. ", orientation " .. result_orientation .. ", linking to " .. result_target_str .. ". Extinguishing...") end
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
if DEBUG then minetest.chat_send_all(" Build unnecessary: already an unlit portal at " .. minetest.pos_to_string(found_anchorPos) .. ", orientation " .. result_orientation) end
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)
@ -1260,11 +1310,12 @@ 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, ignition_node_name)
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
if DEBUG then minetest.chat_send_all("IGNITE the " .. ignition_node_name .. " at " .. minetest.pos_to_string(ignition_pos)) 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)
@ -1275,7 +1326,7 @@ local function ignite_portal(ignition_pos, ignition_node_name)
-- 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
if DEBUG then minetest.chat_send_all("No " .. portal_definition.name .. " portal frame found at " .. minetest.pos_to_string(ignition_pos)) end
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.
@ -1287,10 +1338,10 @@ local function ignite_portal(ignition_pos, ignition_node_name)
-- 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
if DEBUG then minetest.chat_send_all("Broken portal detected, allowing reignition/repair") end
debugf("Broken portal detected, allowing reignition/repair")
repair = true
else
if DEBUG then minetest.chat_send_all("This portal links to " .. meta:get_string("target") .. ". p1=" .. meta:get_string("p1") .. " p2=" .. meta:get_string("p2")) end
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
@ -1298,7 +1349,7 @@ local function ignite_portal(ignition_pos, ignition_node_name)
-- (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
if DEBUG then minetest.chat_send_all("Portal timer was not running: restarting the timer.") end
debugf("Portal timer was not running: restarting the timer.")
timer:start(1)
end
end
@ -1307,23 +1358,30 @@ local function ignite_portal(ignition_pos, ignition_node_name)
end
if continue == false then
if DEBUG then minetest.chat_send_all("Found portal frame. Looked at " .. minetest.pos_to_string(ignition_pos) .. ", found at " .. minetest.pos_to_string(anchorPos) .. " orientation " .. orientation) end
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)
destination_anchorPos, destination_orientation = portal_definition.find_surface_anchorPos(anchorPos, player_name or "")
else
destination_anchorPos, destination_orientation = portal_definition.find_realm_anchorPos(anchorPos)
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 DEBUG and destination_orientation == nil then minetest.chat_send_all("No destination_orientation given") end
if destination_orientation == nil then destination_orientation = orientation end
if destination_anchorPos == nil then
if DEBUG then minetest.chat_send_all("No portal destination available here!") 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)
if DEBUG then minetest.chat_send_all("Destination set to " .. minetest.pos_to_string(destination_anchorPos)) end
debugf("Destination set to %s", destination_anchorPos)
-- ignition/BURN_BABY_BURN
set_portal_metadata_and_ignite(portal_definition, anchorPos, orientation, destination_wormholePos)
@ -1362,7 +1420,7 @@ local function ensure_remote_portal_then_teleport(playerName, portal_definition,
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
if DEBUG then minetest.chat_send_all("the player already teleported from " .. minetest.pos_to_string(local_anchorPos) .. ", and is now standing in a different portal - " .. meta:get_string("p1")) end
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
@ -1370,7 +1428,7 @@ local function ensure_remote_portal_then_teleport(playerName, portal_definition,
if dest_wormhole_node == nil then
-- area not emerged yet, delay and retry
if DEBUG then minetest.chat_send_all("ensure_remote_portal_then_teleport() could not find anything yet at " .. minetest.pos_to_string(destination_wormholePos)) end
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)
@ -1387,9 +1445,9 @@ local function ensure_remote_portal_then_teleport(playerName, portal_definition,
local remoteMeta = minetest.get_meta(destination_wormholePos)
local remoteTarget = minetest.string_to_pos(remoteMeta:get_string("target"))
if remoteTarget == nil then
if DEBUG then minetest.chat_send_all("Failed to test whether target portal links back to this one") end
debugf("Failed to test whether target portal links back to this one")
elseif not vector.equals(remoteTarget, local_wormholePos) then
if DEBUG then minetest.chat_send_all("Target portal is already linked, extinguishing then relighting to point back at this one") end
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,
@ -1399,7 +1457,7 @@ local function ensure_remote_portal_then_teleport(playerName, portal_definition,
)
end
if DEBUG then minetest.chat_send_all("Teleporting player from wormholePos" .. minetest.pos_to_string(local_wormholePos) .. " to wormholePos" .. minetest.pos_to_string(destination_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
@ -1426,7 +1484,7 @@ local function ensure_remote_portal_then_teleport(playerName, portal_definition,
-- 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.
if DEBUG then minetest.chat_send_all("ensure_remote_portal_then_teleport() saw " .. dest_wormhole_node.name .. " at " .. minetest.pos_to_string(destination_wormholePos) .. " rather than a wormhole. Calling locate_or_build_portal()") end
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)
@ -1443,10 +1501,10 @@ local function ensure_remote_portal_then_teleport(playerName, portal_definition,
-- 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)
if DEBUG then minetest.chat_send_all("!ERROR! - " .. message) end
debugf("!ERROR! - " .. message)
else
destination_wormholePos = new_dest_wormholePos
if DEBUG then minetest.chat_send_all(" updating target to where remote portal was found - " .. minetest.pos_to_string(destination_wormholePos)) end
debugf(" updating target to where remote portal was found - %s", destination_wormholePos)
set_portal_metadata(
portal_definition,
@ -1722,7 +1780,7 @@ local function create_book_of_portals()
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.", string.lower(ignition_item_description))
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}
@ -1781,22 +1839,22 @@ function register_frame_node(frame_node_name)
extended_node_def.replaced_by_portalapi.mesecons = extended_node_def.mesecons
extended_node_def.mesecons = {effector = {
action_on = function (pos, node)
if DEBUG then minetest.chat_send_all("portal frame material: mesecons action ON") end
ignite_portal(pos, node.name)
debugf("portal frame material: mesecons action ON")
ignite_portal(pos, nil, node.name)
end,
action_off = function (pos, node)
if DEBUG then minetest.chat_send_all("portal frame material: mesecons action OFF") end
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)
if DEBUG then minetest.chat_send_all("portal frame material: destruct") end
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)
if DEBUG then minetest.chat_send_all("portal frame material: blast") end
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)
@ -1864,6 +1922,10 @@ function test_portaldef_is_valid(portal_definition)
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
@ -1888,9 +1950,9 @@ minetest.register_lbm({
local timer = minetest.get_node_timer(timerPos)
if timer ~= nil then
timer:start(1)
if DEBUG then minetest.chat_send_all("LBM started portal timer " .. minetest.pos_to_string(timerPos)) end
elseif DEBUG then
minetest.chat_send_all("get_node_timer" .. minetest.pos_to_string(timerPos) .. " returned null")
debugf("LBM started portal timer %s", timerPos)
else
debugf("get_node_timer%s returned null", timerPos)
end
end
end
@ -1916,7 +1978,7 @@ local wormhole_nodedef_default = {
type = "vertical_frames",
aspect_w = 16,
aspect_h = 16,
length = 0.5,
length = 0.9,
},
},
{
@ -1925,7 +1987,7 @@ local wormhole_nodedef_default = {
type = "vertical_frames",
aspect_w = 16,
aspect_h = 16,
length = 0.5,
length = 0.9,
},
},
},
@ -1945,7 +2007,8 @@ local wormhole_nodedef_default = {
a = 160, r = 128, g = 0, b = 80
},
sunlight_propagates = true,
use_texture_alpha = true,
use_texture_alpha = minetest.features.use_texture_alpha_string_modes
and "blend" or true,
walkable = false,
diggable = false,
pointable = false,
@ -1953,7 +2016,6 @@ local wormhole_nodedef_default = {
is_ground_content = false,
drop = "",
light_source = 5,
alpha = 192,
node_box = {
type = "fixed",
fixed = {
@ -2011,7 +2073,7 @@ function nether.register_portal(name, portaldef)
end
portaldef.name = name
portaldef.mod_name = minetest.get_current_modname()
portaldef.mod_name = minetest.get_current_modname() or "<mod name not recorded>"
-- 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
@ -2034,7 +2096,7 @@ function nether.register_portal(name, portaldef)
end
if portaldef.find_surface_anchorPos == nil then -- default to using find_surface_target_y()
portaldef.find_surface_anchorPos = function(pos)
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 =
@ -2042,7 +2104,7 @@ function nether.register_portal(name, portaldef)
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)
destination_pos.y = nether.find_surface_target_y(destination_pos.x, destination_pos.z, name, player_name)
return destination_pos
end
end
@ -2104,22 +2166,20 @@ function nether.unregister_portal(name)
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, _, pt)
local done = false
on_place = function(stack, placer, pt, ...)
if pt.under and nether.is_frame_node[minetest.get_node(pt.under).name] then
done = ignite_portal(pt.under)
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
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
return old_on_place(stack, placer, pt, ...)
end,
})
@ -2128,32 +2188,32 @@ 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(minp, maxp)
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 pos1 = {x = minp.x, y = minp.y, z = minp.z}
local pos2 = {x = maxp.x, y = maxp.y, z = maxp.z}
local emin, emax = vm:read_from_map(pos1, pos2)
local emin, emax = vm:read_from_map(minp, maxp)
local area = VoxelArea:new({MinEdge = emin, MaxEdge = emax})
local data = vm:get_data()
for z = pos1.z, pos2.z do
for y = pos1.y, pos2.y do
local vi = area:index(pos1.x, y, z)
for x = pos1.x, pos2.x do
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 DEBUG and id == nil then minetest.chat_send_all("nil block at index " .. vi) end
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 not nodedef.is_ground_content then
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
if DEBUG then minetest.chat_send_all("volume_is_natural() found unnatural node " .. name) end
debugf("volume_is_natural_and_unprotected() found unnatural node %s", name)
return false
end
end
@ -2163,13 +2223,73 @@ function nether.volume_is_natural(minp, maxp)
end
end
if DEBUG then minetest.chat_send_all("Volume is natural") 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.
function nether.find_surface_target_y(target_x, target_z, portal_name)
-- 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()")
@ -2199,24 +2319,35 @@ function nether.find_surface_target_y(target_x, target_z, portal_name)
end
end
for y = start_y, start_y - 256, -16 do
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
local minp = {x = target_x - 1, y = y - 1, z = target_z - 2}
local maxp = {x = target_x + 2, y = y + 3, z = target_z + 2}
if nether.volume_is_natural(minp, maxp) then
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
if DEBUG then minetest.chat_send_all("Volume_is_natural check failed, but a portal frame is here " .. minetest.pos_to_string(anchorPos) .. ", so this is still a good target y level") end
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 start_y - 256 -- Fallback
return nil -- Portal ignition failure. Possibly due to a large protected area.
end
@ -2239,7 +2370,7 @@ function nether.find_nearest_working_portal(portal_name, anchorPos, distance_lim
for _, dist in ipairs(dist_list) do
local portal_info = contenders[dist]
if DEBUG then minetest.chat_send_all("checking portal from mod_storage at " .. minetest.pos_to_string(portal_info.anchorPos) .. " orientation " .. portal_info.orientation) end
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
@ -2248,10 +2379,11 @@ function nether.find_nearest_working_portal(portal_name, anchorPos, distance_lim
if portalFound then
return portal_info.anchorPos, portal_info.orientation
else
if DEBUG then minetest.chat_send_all("Portal wasn't found, removing portal from mod_storage at " .. minetest.pos_to_string(portal_info.anchorPos) .. " orientation " .. portal_info.orientation) end
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
end

View File

@ -22,6 +22,9 @@ one kind of portal with the same frame material — such as obsidian — provide
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
------
@ -49,17 +52,24 @@ surface.
Helper functions
----------------
* `nether.volume_is_natural(minp, maxp)`: returns a boolean
* `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)`: returns a
suitable anchorPos
* `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
@ -115,6 +125,12 @@ Used by `nether.register_portal`.
-- 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:
@ -127,6 +143,9 @@ Used by `nether.register_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.
@ -199,7 +218,7 @@ Used by `nether.register_portal`.
-- Ideally implementations are fast, as this function can be used to
-- sift through a list of portals.
find_realm_anchorPos = function(surface_anchorPos),
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)
@ -208,8 +227,12 @@ Used by `nether.register_portal`.
-- 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),
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)
@ -222,17 +245,21 @@ Used by `nether.register_portal`.
-- 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, anochorPos, orientation),
on_run_wormhole = function(portalDef, anchorPos, orientation),
-- invoked once per second per portal
on_extinguish = function(portalDef, anochorPos, orientation),
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, anochorPos, orientation)
on_ignite = function(portalDef, anchorPos, orientation)
-- invoked when a player or mesecon ignites a portal
on_created = function(portalDef, anochorPos, orientation)
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.
}

View File

@ -85,7 +85,7 @@ This portal is different to the others, rather than acting akin to a doorway it
return pos.y > FLOATLAND_LEVEL - 200
end,
find_realm_anchorPos = function(surface_anchorPos)
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}
@ -106,7 +106,7 @@ end
-- Surface-travel portal, playable code example --
--==============================================--
-- These Moore Curve functions requred by surface_portal's find_surface_anchorPos() will
-- 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
@ -116,6 +116,7 @@ if minetest.settings:get_bool("nether_enable_portal_example_surfacetravel", ENAB
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.
@ -130,13 +131,13 @@ Due to such difficulties, we never learned what determines the direction and dis
return true
end,
find_realm_anchorPos = function(surface_anchorPos)
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)
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.
@ -191,7 +192,7 @@ Due to such difficulties, we never learned what determines the direction and dis
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")
destination_pos.y = nether.find_surface_target_y(destination_pos.x, destination_pos.z, "surface_portal", player_name)
return destination_pos
end

View File

@ -16,4 +16,10 @@ nether_realm_enabled (Enable Nether realm & portal) bool true
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_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 30000

BIN
sounds/nether_fumarole.ogg Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
textures/nether_basalt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 678 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 597 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 594 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 765 B

BIN
textures/nether_geode.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 568 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 666 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 B

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 B

394
tools.lua Normal file
View File

@ -0,0 +1,394 @@
--[[
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
})