Compare commits
119 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
b51b831483 | ||
|
01b6f3c56d | ||
|
a6d1f55f0a | ||
|
d16b530685 | ||
|
ea677c5a1b | ||
|
21c7e2801d | ||
|
3470b1e22e | ||
|
3e809a811b | ||
|
6c6a182366 | ||
|
9c1fa2c843 | ||
|
957fdc887b | ||
|
7dc32f6bb8 | ||
|
a8a6bb00c5 | ||
|
12ca98c25a | ||
|
a03375c5a4 | ||
|
d3f79e961b | ||
|
d8e6a6aca3 | ||
|
247ca0844b | ||
|
52038017f3 | ||
|
54613d673a | ||
|
023539c297 | ||
|
b90afa0b86 | ||
|
fb6b666895 | ||
|
fb3c358f74 | ||
|
ecce6e27c9 | ||
|
3f4b014298 | ||
|
bafa4e8ea1 | ||
|
b30272d2dd | ||
|
87f0ea7a51 | ||
|
3008b167b2 | ||
|
00099f2aa2 | ||
|
89a467698a | ||
|
979493ed64 | ||
|
1fbab7451e | ||
|
4b508f8492 | ||
|
24845dce1e | ||
|
00cda53a13 | ||
|
5a2a6c63f1 | ||
|
00bb132edb | ||
|
c7776cc082 | ||
|
09ca1efd62 | ||
|
f3c6f2df23 | ||
|
2fb93b8c74 | ||
|
1268049706 | ||
|
c70a669709 | ||
|
02d062b9c9 | ||
|
e326a94266 | ||
|
5b3b56ebec | ||
|
8769593d6f | ||
|
5cb9e5fb27 | ||
|
ab4a031c1c | ||
|
aac3ea6719 | ||
|
f4255f5d1f | ||
|
94222d44e0 | ||
|
86105b4eb8 | ||
|
0e26770830 | ||
|
e5fbc2486b | ||
|
608d692401 | ||
|
f7ebd78614 | ||
|
e113db1478 | ||
|
7939161535 | ||
|
32e5d818e8 | ||
|
aa2bfe9472 | ||
|
4c3545f17a | ||
|
39d655b4c8 | ||
|
36d1f6f573 | ||
|
0618325710 | ||
|
8f29a35175 | ||
|
e3ac1ade79 | ||
|
363872c760 | ||
|
40913ee841 | ||
|
e765f5f504 | ||
|
58da96549a | ||
|
d0ce9dd633 | ||
|
1d19546345 | ||
|
3945467152 | ||
|
478684062d | ||
|
4ebdf7f25f | ||
|
4985e199b1 | ||
|
48cbd87b59 | ||
|
43f1f24d53 | ||
|
a971e0359e | ||
|
6752964c96 | ||
|
b9e85582f9 | ||
|
11a818212a | ||
|
58eb65c0c8 | ||
|
6e0cadb1ec | ||
|
9bfbb64e96 | ||
|
b8ec09f402 | ||
|
0f3f42e5c0 | ||
|
d5a551b4cf | ||
|
31cf6a9bc3 | ||
|
7cff6b8cc2 | ||
|
27a660b731 | ||
|
dd6c19d005 | ||
|
835ad9686d | ||
|
e2666146ca | ||
|
0c7a6e95c5 | ||
|
52b20925d5 | ||
|
6401052f92 | ||
|
c106b88e98 | ||
|
1a6d15def3 | ||
|
43b37e96b2 | ||
|
b6e2a3335a | ||
|
3126d067a1 | ||
|
83c9ad574f | ||
|
f38211e55d | ||
|
2113d332f8 | ||
|
9a4c7ed875 | ||
|
59275bb242 | ||
|
c7d83eceb7 | ||
|
edf961907e | ||
|
7a4d9ebf01 | ||
|
a085e194fa | ||
|
614a3f91a7 | ||
|
b49f4ce73d | ||
|
6551f5c120 | ||
|
b8dab52e41 | ||
|
bf145d4c11 |
16
.luacheckrc
|
@ -1,21 +1,37 @@
|
||||||
unused_args = false
|
unused_args = false
|
||||||
allow_defined_top = true
|
allow_defined_top = true
|
||||||
|
|
||||||
|
globals = {
|
||||||
|
"bucket",
|
||||||
|
"nether"
|
||||||
|
}
|
||||||
|
|
||||||
read_globals = {
|
read_globals = {
|
||||||
|
"climate_api",
|
||||||
"core",
|
"core",
|
||||||
"default",
|
"default",
|
||||||
"DIR_DELIM",
|
"DIR_DELIM",
|
||||||
"dump",
|
"dump",
|
||||||
|
"dungeon_loot",
|
||||||
"intllib",
|
"intllib",
|
||||||
"ItemStack",
|
"ItemStack",
|
||||||
|
"loot",
|
||||||
|
"doc",
|
||||||
|
"math.hypot",
|
||||||
|
"mesecon",
|
||||||
"minetest",
|
"minetest",
|
||||||
"nodeupdate",
|
"nodeupdate",
|
||||||
|
"PcgRandom",
|
||||||
"PseudoRandom",
|
"PseudoRandom",
|
||||||
"stairs",
|
"stairs",
|
||||||
"stairsplus",
|
"stairsplus",
|
||||||
|
"string.split",
|
||||||
table = { fields = { "copy", "getn" } },
|
table = { fields = { "copy", "getn" } },
|
||||||
|
"toolranks",
|
||||||
"vector",
|
"vector",
|
||||||
"VoxelArea",
|
"VoxelArea",
|
||||||
"VoxelManip",
|
"VoxelManip",
|
||||||
|
"walls",
|
||||||
|
xpanes = { fields = { "register_pane" } },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
71
README.md
|
@ -1,4 +1,33 @@
|
||||||
Nether Mod for Minetest
|
# Nether Mod for Minetest, with Portals API.
|
||||||
|
|
||||||
|
Allows Nether portals to be constructed, opening a gateway between the surface
|
||||||
|
realm and one of lava and netherrack, with rumors of a passageway through the
|
||||||
|
netherrack to a great magma ocean.
|
||||||
|
|
||||||
|
To view the options provided by this mod, see settingtypes.txt or
|
||||||
|
go to "Settings"->"All Settings"->"Mods"->"nether" in the game.
|
||||||
|
|
||||||
|
A Nether portal is built as a rectangular vertical frame of obsidian, 4 blocks
|
||||||
|
wide and 5 blocks high. Once constructed, a Mese crystal fragment can be
|
||||||
|
right-click/used on the frame to activate it.
|
||||||
|
|
||||||
|
|
||||||
|
## Modders and game designers
|
||||||
|
|
||||||
|
See portal_api.txt for how to create custom portals to your own realms.
|
||||||
|
|
||||||
|
Nether Portals can allow surface fast-travel.
|
||||||
|
|
||||||
|
This mod provides Nether basalts (natural, hewn, and chiseled) as nodes which
|
||||||
|
require a player to journey to the magma ocean to obtain, so these can be used
|
||||||
|
for gating progression through a game. For example, a portal to another realm
|
||||||
|
might need to be constructed from basalt, thus requiring a journey through
|
||||||
|
the nether first, or basalt might be a crafting ingredient required to reach
|
||||||
|
a particular branch of the tech-tree.
|
||||||
|
|
||||||
|
Netherbrick tools are provided (pick, shovel, axe, & sword), see tools.lua
|
||||||
|
The Nether pickaxe has a 10x bonus against wear when mining netherrack.
|
||||||
|
|
||||||
|
|
||||||
## License of source code:
|
## License of source code:
|
||||||
|
|
||||||
|
@ -17,15 +46,39 @@ WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||||
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
|
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
|
||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
|
|
||||||
## License of media (textures and sounds)
|
## License and attribution of media (textures and sounds)
|
||||||
|
|
||||||
Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)
|
### [Public Domain Dedication (CC0 1.0)](https://creativecommons.org/publicdomain/zero/1.0/)
|
||||||
http://creativecommons.org/licenses/by-sa/3.0/
|
|
||||||
|
|
||||||
## Authors of media files
|
* `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
|
||||||
|
|
||||||
Everything not listed in here:
|
### [Attribution 3.0 Unported (CC BY 3.0)](https://creativecommons.org/licenses/by/3.0/)
|
||||||
Copyright (C) 2013 PilzAdam
|
|
||||||
|
|
||||||
- nether_rack.png: Zeg9
|
* `nether_lightstaff.ogg` is "Fire Burst" by [SilverIllusionist](https://freesound.org/people/SilverIllusionist/), 2019
|
||||||
- nether_glowstone.png: BlockMen
|
* `nether_portal_ambient.ogg` & `nether_portal_ambient.0.ogg` are extractions from "Deep Cinematic Rumble Stereo" by [Patrick Lieberkind](http://www.lieberkindvisuals.dk), used under CC BY 3.0
|
||||||
|
* `nether_portal_extinguish.ogg` is an extraction from "Tight Laser Weapon Hit Scifi" by [damjancd](https://freesound.org/people/damjancd), used under CC BY 3.0
|
||||||
|
* `nether_portal_ignite.ogg` is a derivative of "Flame Ignition" by [hykenfreak](https://freesound.org/people/hykenfreak), used under CC BY 3.0. "Nether Portal ignite" is licensed under CC BY 3.0 by Treer.
|
||||||
|
|
||||||
|
### [Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)](https://creativecommons.org/licenses/by-sa/4.0/)
|
||||||
|
* `nether_basalt`* (files starting with "nether_basalt"): Treer, 2020
|
||||||
|
* `nether_book_`* (files starting with "nether_book"): Treer, 2019-2020
|
||||||
|
* `nether_brick_deep.png`: Treer, 2021
|
||||||
|
* `nether_fumarole.ogg`: Treer, 2020
|
||||||
|
* `nether_geode.png`: Treer, 2021
|
||||||
|
* `nether_geode_glass.png`: Treer, 2021
|
||||||
|
* `nether_lava_bubble`* (files starting with "nether_lava_bubble"): Treer, 2020
|
||||||
|
* `nether_lava_crust_animated.png`: Treer, 2019-2020
|
||||||
|
* `nether_lightstaff.png`: Treer, 2021
|
||||||
|
* `nether_particle_anim`* (files starting with "nether_particle_anim"): Treer, 2019
|
||||||
|
* `nether_portal_ignition_failure.ogg`: Treer, 2019
|
||||||
|
* `nether_smoke_puff.png`: Treer, 2020
|
||||||
|
|
||||||
|
### [Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)](http://creativecommons.org/licenses/by-sa/3.0/)
|
||||||
|
* `nether_glowstone`* (files starting with "nether_glowstone"): BlockMen
|
||||||
|
* `nether_nether_ingot.png` & `nether_nether_lump.png`: color adjusted versions from "[default](https://github.com/minetest/minetest_game/tree/master/mods/default)" mod, originally by Gambit
|
||||||
|
* `nether_portal.png`: [Extex101](https://github.com/Extex101), 2020
|
||||||
|
* `nether_rack`* (files starting with "nether_rack"): Zeg9
|
||||||
|
* `nether_tool_`* (files starting with "nether_tool_"): color adjusted versions from "[default](https://github.com/minetest/minetest_game/tree/master/mods/default)" mod, originals by BlockMen
|
||||||
|
|
||||||
|
All other media: Copyright © 2013 PilzAdam, licensed under CC BY-SA 3.0 by PilzAdam.
|
||||||
|
|
72
crafts.lua
Normal 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
|
|
@ -1,3 +1,10 @@
|
||||||
stairs
|
|
||||||
default
|
default
|
||||||
|
stairs
|
||||||
|
doc_basics?
|
||||||
|
dungeon_loot?
|
||||||
|
fire?
|
||||||
|
loot?
|
||||||
|
mesecons?
|
||||||
moreblocks?
|
moreblocks?
|
||||||
|
climate_api?
|
||||||
|
xpanes?
|
917
init.lua
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
|
|
||||||
Nether mod for minetest
|
Nether mod for minetest
|
||||||
|
@ -20,690 +19,330 @@
|
||||||
|
|
||||||
]]--
|
]]--
|
||||||
|
|
||||||
|
-- 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
|
||||||
|
|
||||||
-- Parameters
|
local S
|
||||||
|
if minetest.get_translator ~= nil then
|
||||||
|
S = minetest.get_translator("nether")
|
||||||
|
else
|
||||||
|
-- mock the translator function for MT 0.4
|
||||||
|
S = function(str, ...)
|
||||||
|
local args={...}
|
||||||
|
return str:gsub(
|
||||||
|
"@%d+",
|
||||||
|
function(match) return args[tonumber(match:sub(2))] end
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local NETHER_DEPTH = -5000
|
-- Global Nether namespace
|
||||||
local TCAVE = 0.6
|
nether = {}
|
||||||
local BLEND = 128
|
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)
|
||||||
-- 3D noise
|
nether.get_translator = S
|
||||||
|
-- nether.useBiomes allows other mods to know whether they can register ores etc. in the Nether.
|
||||||
local np_cave = {
|
-- See mapgen.lua for an explanation of why minetest.read_schematic is being checked
|
||||||
offset = 0,
|
nether.useBiomes = minetest.get_mapgen_setting("mg_name") ~= "v6" and minetest.read_schematic ~= nil
|
||||||
scale = 1,
|
nether.fogColor = { -- only used if climate_api is installed
|
||||||
spread = {x = 384, y = 128, z = 384}, -- squashed 3:1
|
netherCaverns = "#1D0504", -- Distance-fog colour for classic nether
|
||||||
seed = 59033,
|
mantle = "#070916", -- Distance-fog colour for the Mantle region
|
||||||
octaves = 5,
|
geodes = "#300530" -- Distance-fog colour for secondary region
|
||||||
persist = 0.7,
|
|
||||||
lacunarity = 2.0,
|
|
||||||
--flags = ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
-- Stuff
|
-- Settings
|
||||||
|
nether.DEPTH_CEILING = -5000 -- The y location of the Nether's celing
|
||||||
local yblmax = NETHER_DEPTH - BLEND * 2
|
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
|
||||||
|
|
||||||
|
|
||||||
-- Functions
|
-- Override default settings with values from the .conf file, if any are present.
|
||||||
|
nether.FASTTRAVEL_FACTOR = tonumber(minetest.settings:get("nether_fasttravel_factor") or nether.FASTTRAVEL_FACTOR)
|
||||||
|
nether.PORTAL_BOOK_LOOT_WEIGHTING = tonumber(minetest.settings:get("nether_portalBook_loot_weighting") or nether.PORTAL_BOOK_LOOT_WEIGHTING)
|
||||||
|
nether.NETHER_REALM_ENABLED = minetest.settings:get_bool("nether_realm_enabled", nether.NETHER_REALM_ENABLED)
|
||||||
|
nether.DEPTH_CEILING = tonumber(minetest.settings:get("nether_depth_ymax") or nether.DEPTH_CEILING)
|
||||||
|
nether.DEPTH_FLOOR = tonumber(minetest.settings:get("nether_depth_ymin") or nether.DEPTH_FLOOR)
|
||||||
|
|
||||||
local function build_portal(pos, target)
|
if nether.DEPTH_FLOOR + 1000 > nether.DEPTH_CEILING then
|
||||||
local p1 = {x = pos.x - 1, y = pos.y - 1, z = pos.z}
|
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)
|
||||||
local p2 = {x = p1.x + 3, y = p1.y + 4, z = p1.z}
|
|
||||||
|
|
||||||
local path = minetest.get_modpath("nether") .. "/schematics/nether_portal.mts"
|
|
||||||
minetest.place_schematic({x = p1.x, y = p1.y, z = p1.z - 2}, path, 0, nil, true)
|
|
||||||
|
|
||||||
for y = p1.y, p2.y do
|
|
||||||
for x = p1.x, p2.x do
|
|
||||||
local meta = minetest.get_meta({x = x, y = y, z = p1.z})
|
|
||||||
meta:set_string("p1", minetest.pos_to_string(p1))
|
|
||||||
meta:set_string("p2", minetest.pos_to_string(p2))
|
|
||||||
meta:set_string("target", minetest.pos_to_string(target))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
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
|
||||||
|
|
||||||
local function volume_is_natural(minp, maxp)
|
-- A debug-print function that understands vectors etc. and does not
|
||||||
local c_air = minetest.get_content_id("air")
|
-- evaluate when debugging is turned off.
|
||||||
local c_ignore = minetest.get_content_id("ignore")
|
-- 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 vm = minetest.get_voxel_manip()
|
local args = {...}
|
||||||
local pos1 = {x = minp.x, y = minp.y, z = minp.z}
|
local argCount = select("#", ...)
|
||||||
local pos2 = {x = maxp.x, y = maxp.y, z = maxp.z}
|
|
||||||
local emin, emax = vm:read_from_map(pos1, pos2)
|
|
||||||
local area = VoxelArea:new({MinEdge = emin, MaxEdge = emax})
|
|
||||||
local data = vm:get_data()
|
|
||||||
|
|
||||||
for z = pos1.z, pos2.z do
|
for i = 1, argCount do
|
||||||
for y = pos1.y, pos2.y do
|
local arg = args[i]
|
||||||
local vi = area:index(pos1.x, y, z)
|
if arg == nil then
|
||||||
for x = pos1.x, pos2.x do
|
-- convert nils to strings
|
||||||
local id = data[vi] -- Existing node
|
args[i] = '<nil>'
|
||||||
if id ~= c_air and id ~= c_ignore then -- These are natural
|
elseif type(arg) == "table" then
|
||||||
local name = minetest.get_name_from_content_id(id)
|
local tableCount = 0
|
||||||
if not minetest.registered_nodes[name].is_ground_content then
|
for _,_ in pairs(arg) do tableCount = tableCount + 1 end
|
||||||
return false
|
if tableCount == 3 and arg.x ~= nil and arg.y ~= nil and arg.z ~= nil then
|
||||||
end
|
-- convert vectors to strings
|
||||||
end
|
args[i] = minetest.pos_to_string(arg)
|
||||||
vi = vi + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function find_nether_target_y(target_x, target_z, start_y)
|
|
||||||
local nobj_cave_point = minetest.get_perlin(np_cave)
|
|
||||||
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})
|
|
||||||
|
|
||||||
if nval_cave > TCAVE then -- Cavern
|
|
||||||
air = air + 1
|
|
||||||
else -- Not cavern, check if 4 nodes of space above
|
|
||||||
if air >= 4 then
|
|
||||||
-- 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 volume_is_natural(minp, maxp) then
|
|
||||||
return y + 2
|
|
||||||
else -- Restart search a little lower
|
|
||||||
find_nether_target_y(target_x, target_z, y - 16)
|
|
||||||
end
|
|
||||||
else -- Not enough space, reset air to zero
|
|
||||||
air = 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return start_y -- Fallback
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function find_surface_target_y(target_x, target_z, start_y)
|
|
||||||
for y = start_y, start_y - 256, -16 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 volume_is_natural(minp, maxp) then
|
|
||||||
return y
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return start_y - 256 -- Fallback
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function move_check(p1, max, dir)
|
|
||||||
local p = {x = p1.x, y = p1.y, z = p1.z}
|
|
||||||
local d = math.abs(max - p1[dir]) / (max - p1[dir])
|
|
||||||
|
|
||||||
while p[dir] ~= max do
|
|
||||||
p[dir] = p[dir] + d
|
|
||||||
if minetest.get_node(p).name ~= "default:obsidian" then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function check_portal(p1, p2)
|
|
||||||
if p1.x ~= p2.x then
|
|
||||||
if not move_check(p1, p2.x, "x") then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
if not move_check(p2, p1.x, "x") then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
elseif p1.z ~= p2.z then
|
|
||||||
if not move_check(p1, p2.z, "z") then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
if not move_check(p2, p1.z, "z") then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
return false
|
-- convert tables to strings
|
||||||
end
|
-- (calling function can use dump() if a multi-line listing is desired)
|
||||||
|
args[i] = string.gsub(dump(arg, ""), "\n", " ")
|
||||||
if not move_check(p1, p2.y, "y") then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
if not move_check(p2, p1.y, "y") then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local function is_portal(pos)
|
|
||||||
for d = -3, 3 do
|
|
||||||
for y = -4, 4 do
|
|
||||||
local px = {x = pos.x + d, y = pos.y + y, z = pos.z}
|
|
||||||
local pz = {x = pos.x, y = pos.y + y, z = pos.z + d}
|
|
||||||
|
|
||||||
if check_portal(px, {x = px.x + 3, y = px.y + 4, z = px.z}) then
|
|
||||||
return px, {x = px.x + 3, y = px.y + 4, z = px.z}
|
|
||||||
end
|
|
||||||
if check_portal(pz, {x = pz.x, y = pz.y + 4, z = pz.z + 3}) then
|
|
||||||
return pz, {x = pz.x, y = pz.y + 4, z = pz.z + 3}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local composed_message = "nether: " .. string.format(message, unpack(args))
|
||||||
|
|
||||||
local function make_portal(pos)
|
if math.floor(DEBUG_FLAGS / 1) % 2 == 1 then print(composed_message) end
|
||||||
local p1, p2 = is_portal(pos)
|
if math.floor(DEBUG_FLAGS / 2) % 2 == 1 then minetest.chat_send_all(composed_message) end
|
||||||
if not p1 or not p2 then
|
if math.floor(DEBUG_FLAGS / 4) % 2 == 1 then minetest.log("info", composed_message) end
|
||||||
return false
|
end
|
||||||
|
if DEBUG_FLAGS == 0 then
|
||||||
|
-- do as little evaluation as possible
|
||||||
|
nether.debug = function() end
|
||||||
end
|
end
|
||||||
|
|
||||||
for d = 1, 2 do
|
|
||||||
for y = p1.y + 1, p2.y - 1 do
|
-- Load files
|
||||||
local p
|
dofile(nether.path .. "/portal_api.lua")
|
||||||
if p1.z == p2.z then
|
dofile(nether.path .. "/nodes.lua")
|
||||||
p = {x = p1.x + d, y = y, z = p1.z}
|
dofile(nether.path .. "/tools.lua")
|
||||||
|
dofile(nether.path .. "/crafts.lua")
|
||||||
|
if nether.NETHER_REALM_ENABLED then
|
||||||
|
if nether.useBiomes then
|
||||||
|
dofile(nether.path .. "/mapgen.lua")
|
||||||
else
|
else
|
||||||
p = {x = p1.x, y = y, z = p1.z + d}
|
dofile(nether.path .. "/mapgen_nobiomes.lua")
|
||||||
end
|
|
||||||
if minetest.get_node(p).name ~= "air" then
|
|
||||||
return false
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
dofile(nether.path .. "/portal_examples.lua")
|
||||||
|
|
||||||
local param2
|
|
||||||
if p1.z == p2.z then
|
|
||||||
param2 = 0
|
|
||||||
else
|
|
||||||
param2 = 1
|
|
||||||
end
|
|
||||||
|
|
||||||
local target = {x = p1.x, y = p1.y, z = p1.z}
|
|
||||||
target.x = target.x + 1
|
|
||||||
if target.y < NETHER_DEPTH then
|
|
||||||
target.y = find_surface_target_y(target.x, target.z, -16)
|
|
||||||
else
|
|
||||||
local start_y = NETHER_DEPTH - math.random(500, 1500) -- Search start
|
|
||||||
target.y = find_nether_target_y(target.x, target.z, start_y)
|
|
||||||
end
|
|
||||||
|
|
||||||
for d = 0, 3 do
|
|
||||||
for y = p1.y, p2.y do
|
|
||||||
local p
|
|
||||||
if param2 == 0 then
|
|
||||||
p = {x = p1.x + d, y = y, z = p1.z}
|
|
||||||
else
|
|
||||||
p = {x = p1.x, y = y, z = p1.z + d}
|
|
||||||
end
|
|
||||||
if minetest.get_node(p).name == "air" then
|
|
||||||
minetest.set_node(p, {name = "nether:portal", param2 = param2})
|
|
||||||
end
|
|
||||||
local meta = minetest.get_meta(p)
|
|
||||||
meta:set_string("p1", minetest.pos_to_string(p1))
|
|
||||||
meta:set_string("p2", minetest.pos_to_string(p2))
|
|
||||||
meta:set_string("target", minetest.pos_to_string(target))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- ABMs
|
-- Portals are ignited by right-clicking with a mese crystal fragment
|
||||||
|
nether.register_portal_ignition_item(
|
||||||
minetest.register_abm({
|
"default:mese_crystal_fragment",
|
||||||
nodenames = {"nether:portal"},
|
{name = "nether_portal_ignition_failure", gain = 0.3}
|
||||||
interval = 1,
|
|
||||||
chance = 2,
|
|
||||||
action = function(pos, node)
|
|
||||||
minetest.add_particlespawner({
|
|
||||||
32, --amount
|
|
||||||
4, --time
|
|
||||||
{x = pos.x - 0.25, y = pos.y - 0.25, z = pos.z - 0.25}, --minpos
|
|
||||||
{x = pos.x + 0.25, y = pos.y + 0.25, z = pos.z + 0.25}, --maxpos
|
|
||||||
{x = -0.8, y = -0.8, z = -0.8}, --minvel
|
|
||||||
{x = 0.8, y = 0.8, z = 0.8}, --maxvel
|
|
||||||
{x = 0, y = 0, z = 0}, --minacc
|
|
||||||
{x = 0, y = 0, z = 0}, --maxacc
|
|
||||||
0.5, --minexptime
|
|
||||||
1, --maxexptime
|
|
||||||
1, --minsize
|
|
||||||
2, --maxsize
|
|
||||||
false, --collisiondetection
|
|
||||||
"nether_particle.png" --texture
|
|
||||||
})
|
|
||||||
for _, obj in ipairs(minetest.get_objects_inside_radius(pos, 1)) do
|
|
||||||
if obj:is_player() then
|
|
||||||
local meta = minetest.get_meta(pos)
|
|
||||||
local target = minetest.string_to_pos(meta:get_string("target"))
|
|
||||||
if target then
|
|
||||||
-- force emerge of target area
|
|
||||||
minetest.get_voxel_manip():read_from_map(target, target)
|
|
||||||
if not minetest.get_node_or_nil(target) then
|
|
||||||
minetest.emerge_area(
|
|
||||||
vector.subtract(target, 4), vector.add(target, 4))
|
|
||||||
end
|
|
||||||
-- teleport the player
|
|
||||||
minetest.after(3, function(o, p, t)
|
|
||||||
local objpos = o:getpos()
|
|
||||||
objpos.y = objpos.y + 0.1 -- Fix some glitches at -8000
|
|
||||||
if minetest.get_node(objpos).name ~= "nether:portal" then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
o:setpos(t)
|
|
||||||
|
|
||||||
local function check_and_build_portal(pp, tt)
|
|
||||||
local n = minetest.get_node_or_nil(tt)
|
|
||||||
if n and n.name ~= "nether:portal" then
|
|
||||||
build_portal(tt, pp)
|
|
||||||
minetest.after(2, check_and_build_portal, pp, tt)
|
|
||||||
minetest.after(4, check_and_build_portal, pp, tt)
|
|
||||||
elseif not n then
|
|
||||||
minetest.after(1, check_and_build_portal, pp, tt)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
minetest.after(1, check_and_build_portal, p, t)
|
|
||||||
|
|
||||||
end, obj, pos, target)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
-- Nodes
|
|
||||||
|
|
||||||
minetest.register_node("nether:portal", {
|
|
||||||
description = "Nether Portal",
|
|
||||||
tiles = {
|
|
||||||
"nether_transparent.png",
|
|
||||||
"nether_transparent.png",
|
|
||||||
"nether_transparent.png",
|
|
||||||
"nether_transparent.png",
|
|
||||||
{
|
|
||||||
name = "nether_portal.png",
|
|
||||||
animation = {
|
|
||||||
type = "vertical_frames",
|
|
||||||
aspect_w = 16,
|
|
||||||
aspect_h = 16,
|
|
||||||
length = 0.5,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "nether_portal.png",
|
|
||||||
animation = {
|
|
||||||
type = "vertical_frames",
|
|
||||||
aspect_w = 16,
|
|
||||||
aspect_h = 16,
|
|
||||||
length = 0.5,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
drawtype = "nodebox",
|
|
||||||
paramtype = "light",
|
|
||||||
paramtype2 = "facedir",
|
|
||||||
sunlight_propagates = true,
|
|
||||||
use_texture_alpha = true,
|
|
||||||
walkable = false,
|
|
||||||
diggable = false,
|
|
||||||
pointable = false,
|
|
||||||
buildable_to = false,
|
|
||||||
is_ground_content = false,
|
|
||||||
drop = "",
|
|
||||||
light_source = 5,
|
|
||||||
post_effect_color = {a = 180, r = 128, g = 0, b = 128},
|
|
||||||
alpha = 192,
|
|
||||||
node_box = {
|
|
||||||
type = "fixed",
|
|
||||||
fixed = {
|
|
||||||
{-0.5, -0.5, -0.1, 0.5, 0.5, 0.1},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
groups = {not_in_creative_inventory = 1}
|
|
||||||
})
|
|
||||||
|
|
||||||
minetest.register_node(":default:obsidian", {
|
|
||||||
description = "Obsidian",
|
|
||||||
tiles = {"default_obsidian.png"},
|
|
||||||
is_ground_content = false,
|
|
||||||
sounds = default.node_sound_stone_defaults(),
|
|
||||||
groups = {cracky = 1, level = 2},
|
|
||||||
|
|
||||||
on_destruct = function(pos)
|
|
||||||
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 not p1 or not p2 then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
for x = p1.x, p2.x do
|
|
||||||
for y = p1.y, p2.y do
|
|
||||||
for z = p1.z, p2.z do
|
|
||||||
local nn = minetest.get_node({x = x, y = y, z = z}).name
|
|
||||||
if nn == "default:obsidian" or nn == "nether:portal" then
|
|
||||||
if nn == "nether:portal" then
|
|
||||||
minetest.remove_node({x = x, y = y, z = z})
|
|
||||||
end
|
|
||||||
local m = minetest.get_meta({x = x, y = y, z = z})
|
|
||||||
m:set_string("p1", "")
|
|
||||||
m:set_string("p2", "")
|
|
||||||
m:set_string("target", "")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
meta = minetest.get_meta(target)
|
|
||||||
if not meta then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
p1 = minetest.string_to_pos(meta:get_string("p1"))
|
|
||||||
p2 = minetest.string_to_pos(meta:get_string("p2"))
|
|
||||||
if not p1 or not p2 then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
for x = p1.x, p2.x do
|
|
||||||
for y = p1.y, p2.y do
|
|
||||||
for z = p1.z, p2.z do
|
|
||||||
local nn = minetest.get_node({x = x, y = y, z = z}).name
|
|
||||||
if nn == "default:obsidian" or nn == "nether:portal" then
|
|
||||||
if nn == "nether:portal" then
|
|
||||||
minetest.remove_node({x = x, y = y, z = z})
|
|
||||||
end
|
|
||||||
local m = minetest.get_meta({x = x, y = y, z = z})
|
|
||||||
m:set_string("p1", "")
|
|
||||||
m:set_string("p2", "")
|
|
||||||
m:set_string("target", "")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
|
|
||||||
minetest.register_node("nether:rack", {
|
|
||||||
description = "Netherrack",
|
|
||||||
tiles = {"nether_rack.png"},
|
|
||||||
is_ground_content = true,
|
|
||||||
groups = {cracky = 3, level = 2},
|
|
||||||
sounds = default.node_sound_stone_defaults(),
|
|
||||||
})
|
|
||||||
|
|
||||||
minetest.register_node("nether:sand", {
|
|
||||||
description = "Nethersand",
|
|
||||||
tiles = {"nether_sand.png"},
|
|
||||||
is_ground_content = true,
|
|
||||||
groups = {crumbly = 3, level = 2, falling_node = 1},
|
|
||||||
sounds = default.node_sound_gravel_defaults({
|
|
||||||
footstep = {name = "default_gravel_footstep", gain = 0.45},
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
|
|
||||||
minetest.register_node("nether:glowstone", {
|
|
||||||
description = "Glowstone",
|
|
||||||
tiles = {"nether_glowstone.png"},
|
|
||||||
is_ground_content = true,
|
|
||||||
light_source = 14,
|
|
||||||
paramtype = "light",
|
|
||||||
groups = {cracky = 3, oddly_breakable_by_hand = 3},
|
|
||||||
sounds = default.node_sound_glass_defaults(),
|
|
||||||
})
|
|
||||||
|
|
||||||
minetest.register_node("nether:brick", {
|
|
||||||
description = "Nether Brick",
|
|
||||||
tiles = {"nether_brick.png"},
|
|
||||||
is_ground_content = false,
|
|
||||||
groups = {cracky = 2, level = 2},
|
|
||||||
sounds = default.node_sound_stone_defaults(),
|
|
||||||
})
|
|
||||||
|
|
||||||
local fence_texture =
|
|
||||||
"default_fence_overlay.png^nether_brick.png^default_fence_overlay.png^[makealpha:255,126,126"
|
|
||||||
|
|
||||||
minetest.register_node("nether:fence_nether_brick", {
|
|
||||||
description = "Nether Brick Fence",
|
|
||||||
drawtype = "fencelike",
|
|
||||||
tiles = {"nether_brick.png"},
|
|
||||||
inventory_image = fence_texture,
|
|
||||||
wield_image = fence_texture,
|
|
||||||
paramtype = "light",
|
|
||||||
sunlight_propagates = true,
|
|
||||||
is_ground_content = false,
|
|
||||||
selection_box = {
|
|
||||||
type = "fixed",
|
|
||||||
fixed = {-1/7, -1/2, -1/7, 1/7, 1/2, 1/7},
|
|
||||||
},
|
|
||||||
groups = {cracky = 2, level = 2},
|
|
||||||
sounds = default.node_sound_stone_defaults(),
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
-- Register stair and slab
|
|
||||||
|
|
||||||
stairs.register_stair_and_slab(
|
|
||||||
"nether_brick",
|
|
||||||
"nether:brick",
|
|
||||||
{cracky = 2, level = 2},
|
|
||||||
{"nether_brick.png"},
|
|
||||||
"nether stair",
|
|
||||||
"nether slab",
|
|
||||||
default.node_sound_stone_defaults()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
-- StairsPlus
|
|
||||||
|
|
||||||
if minetest.get_modpath("moreblocks") then
|
if nether.NETHER_REALM_ENABLED then
|
||||||
stairsplus:register_all(
|
-- Use the Portal API to add a portal type which goes to the Nether
|
||||||
"nether", "brick", "nether:brick", {
|
-- See portal_api.txt for documentation
|
||||||
description = "Nether Brick",
|
nether.register_portal("nether_portal", {
|
||||||
groups = {cracky = 2, level = 2},
|
shape = nether.PortalShape_Traditional,
|
||||||
tiles = {"nether_brick.png"},
|
frame_node_name = "default:obsidian",
|
||||||
sounds = default.node_sound_stone_defaults(),
|
wormhole_node_color = 0, -- 0 is magenta
|
||||||
})
|
title = S("Nether Portal"),
|
||||||
end
|
book_of_portals_pagetext = S([[Construction requires 14 blocks of obsidian, which we found deep underground where water had solidified molten rock. The finished frame is four blocks wide, five blocks high, and stands vertically, like a doorway.
|
||||||
|
|
||||||
stairs.register_stair_and_slab("brick", "nether:brick",
|
This opens to a truly hellish place, though for small mercies the air there is still breathable. There is an intriguing dimensional mismatch happening between this realm and ours, as after opening the second portal into it we observed that 10 strides taken in the Nether appear to be an equivalent of @1 in the natural world.
|
||||||
{cracky=3, oddly_breakable_by_hand=1},
|
|
||||||
{"nether_brick.png"},
|
|
||||||
"nether stair",
|
|
||||||
"nether slab",
|
|
||||||
default.node_sound_stone_defaults())
|
|
||||||
|
|
||||||
|
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),
|
||||||
|
|
||||||
-- Craftitems
|
is_within_realm = function(pos) -- return true if pos is inside the Nether
|
||||||
|
return pos.y < nether.DEPTH_CEILING and pos.y > nether.DEPTH_FLOOR
|
||||||
minetest.override_item("default:mese_crystal_fragment", {
|
|
||||||
on_place = function(stack, _, pt)
|
|
||||||
if pt.under and minetest.get_node(pt.under).name == "default:obsidian" then
|
|
||||||
local done = make_portal(pt.under)
|
|
||||||
if done and not minetest.settings:get_bool("creative_mode") then
|
|
||||||
stack:take_item()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return stack
|
|
||||||
end,
|
end,
|
||||||
})
|
|
||||||
|
|
||||||
-- Crafting
|
find_realm_anchorPos = function(surface_anchorPos, player_name)
|
||||||
|
-- divide x and z by a factor of 8 to implement Nether fast-travel
|
||||||
|
local destination_pos = vector.divide(surface_anchorPos, nether.FASTTRAVEL_FACTOR)
|
||||||
|
destination_pos.x = math.floor(0.5 + destination_pos.x) -- round to int
|
||||||
|
destination_pos.z = math.floor(0.5 + destination_pos.z) -- round to int
|
||||||
|
destination_pos.y = nether.DEPTH_CEILING - 1 -- temp value so find_nearest_working_portal() returns nether portals
|
||||||
|
|
||||||
minetest.register_craft({
|
-- a y_factor of 0 makes the search ignore the altitude of the portals (as long as they are in the Nether)
|
||||||
output = "nether:brick 4",
|
local existing_portal_location, existing_portal_orientation =
|
||||||
recipe = {
|
nether.find_nearest_working_portal("nether_portal", destination_pos, 8, 0)
|
||||||
{"nether:rack", "nether:rack"},
|
|
||||||
{"nether:rack", "nether:rack"},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
minetest.register_craft({
|
if existing_portal_location ~= nil then
|
||||||
output = "nether:fence_nether_brick 6",
|
return existing_portal_location, existing_portal_orientation
|
||||||
recipe = {
|
|
||||||
{"nether:brick", "nether:brick", "nether:brick"},
|
|
||||||
{"nether:brick", "nether:brick", "nether:brick"},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
-- Mapgen
|
|
||||||
|
|
||||||
-- Initialize noise object, localise noise and data buffers
|
|
||||||
|
|
||||||
local nobj_cave = nil
|
|
||||||
local nbuf_cave = nil
|
|
||||||
local dbuf = nil
|
|
||||||
|
|
||||||
|
|
||||||
-- 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_DEPTH 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 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:get3dMap_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
|
|
||||||
if y > yblmax then
|
|
||||||
tcave = TCAVE + ((y - yblmax) / BLEND) ^ 2
|
|
||||||
else
|
else
|
||||||
tcave = TCAVE
|
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
|
||||||
in_chunk_y = true
|
end,
|
||||||
|
|
||||||
|
find_surface_anchorPos = function(realm_anchorPos, player_name)
|
||||||
|
-- A portal definition doesn't normally need to provide a find_surface_anchorPos() function,
|
||||||
|
-- since find_surface_target_y() will be used by default, but Nether portals also scale position
|
||||||
|
-- to create fast-travel.
|
||||||
|
-- Defining a custom function also means we can look for existing nearby portals.
|
||||||
|
|
||||||
|
-- Multiply x and z by a factor of 8 to implement Nether fast-travel
|
||||||
|
local destination_pos = vector.multiply(realm_anchorPos, nether.FASTTRAVEL_FACTOR)
|
||||||
|
destination_pos.x = math.min(30900, math.max(-30900, destination_pos.x)) -- clip to world boundary
|
||||||
|
destination_pos.z = math.min(30900, math.max(-30900, destination_pos.z)) -- clip to world boundary
|
||||||
|
destination_pos.y = nether.DEPTH_CEILING + 1 -- temp value so find_nearest_working_portal() doesn't return nether portals
|
||||||
|
|
||||||
|
-- a y_factor of 0 makes the search ignore the altitude of the portals (as long as they are outside the Nether)
|
||||||
|
local existing_portal_location, existing_portal_orientation =
|
||||||
|
nether.find_nearest_working_portal("nether_portal", destination_pos, 8 * nether.FASTTRAVEL_FACTOR, 0)
|
||||||
|
|
||||||
|
if existing_portal_location ~= nil then
|
||||||
|
return existing_portal_location, existing_portal_orientation
|
||||||
|
else
|
||||||
|
destination_pos.y = nether.find_surface_target_y(destination_pos.x, destination_pos.z, "nether_portal", player_name)
|
||||||
|
return destination_pos
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
on_ignite = function(portalDef, anchorPos, orientation)
|
||||||
|
|
||||||
|
-- make some sparks fly
|
||||||
|
local p1, p2 = portalDef.shape:get_p1_and_p2_from_anchorPos(anchorPos, orientation)
|
||||||
|
local pos = vector.divide(vector.add(p1, p2), 2)
|
||||||
|
|
||||||
|
local textureName = portalDef.particle_texture
|
||||||
|
if type(textureName) == "table" then textureName = textureName.name end
|
||||||
|
|
||||||
|
minetest.add_particlespawner({
|
||||||
|
amount = 110,
|
||||||
|
time = 0.1,
|
||||||
|
minpos = {x = pos.x - 0.5, y = pos.y - 1.2, z = pos.z - 0.5},
|
||||||
|
maxpos = {x = pos.x + 0.5, y = pos.y + 1.2, z = pos.z + 0.5},
|
||||||
|
minvel = {x = -5, y = -1, z = -5},
|
||||||
|
maxvel = {x = 5, y = 1, z = 5},
|
||||||
|
minacc = {x = 0, y = 0, z = 0},
|
||||||
|
maxacc = {x = 0, y = 0, z = 0},
|
||||||
|
minexptime = 0.1,
|
||||||
|
maxexptime = 0.5,
|
||||||
|
minsize = 0.2 * portalDef.particle_texture_scale,
|
||||||
|
maxsize = 0.8 * portalDef.particle_texture_scale,
|
||||||
|
collisiondetection = false,
|
||||||
|
texture = textureName .. "^[colorize:#F4F:alpha",
|
||||||
|
animation = portalDef.particle_texture_animation,
|
||||||
|
glow = 8
|
||||||
|
})
|
||||||
|
|
||||||
end
|
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
|
-- Set appropriate nether distance-fog if climate_api is available
|
||||||
-- Cave air, cave liquids and dungeons are overgenerated,
|
--
|
||||||
-- convert these throughout mapchunk plus shell
|
-- Delegating to a mod like climate_api means nether won't unexpectedly stomp on the sky of
|
||||||
if id == c_air or -- Air and liquids to air
|
-- any other mod.
|
||||||
id == c_lava_source or
|
-- Skylayer is another mod which can perform this role, and skylayer support could be added
|
||||||
id == c_lava_flowing or
|
-- here as well. However skylayer doesn't provide a position-based method of specifying sky
|
||||||
id == c_water_source or
|
-- colours out-of-the-box, so the nether mod will have to monitor when players enter and
|
||||||
id == c_water_flowing then
|
-- leave the nether.
|
||||||
data[vi] = c_air
|
if minetest.get_modpath("climate_api") and minetest.global_exists("climate_api") and climate_api.register_weather ~= nil then
|
||||||
-- 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
|
climate_api.register_influence(
|
||||||
if nvals_cave[ni] > tcave then -- Only excavate cavern in mapchunk
|
"nether_biome",
|
||||||
data[vi] = c_air
|
function(pos)
|
||||||
elseif id == c_mese then -- Mese block to lava
|
local result = "surface"
|
||||||
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
|
if pos.y <= nether.DEPTH_CEILING and pos.y >= nether.DEPTH_FLOOR then
|
||||||
end
|
result = "nether"
|
||||||
|
|
||||||
vi = vi + 1
|
-- 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
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
vm:set_data(data)
|
return result
|
||||||
vm:set_lighting({day = 0, night = 0})
|
end
|
||||||
vm:calc_lighting()
|
)
|
||||||
vm:update_liquids()
|
|
||||||
vm:write_to_map()
|
-- using sky type "plain" unfortunately means we don't get smooth fading transitions when
|
||||||
end)
|
-- 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
|
@ -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 d’obsidienne, qui peut être trouvée profondément sous terre, là où l’eau a solidifié de la roche fondue. Une fois terminé, le cadre fait quatre blocs de large, cinq blocs de haut, et se tient verticalement comme une porte.@n@nElle ouvre sur un lieu vraiment infernal, mais on peut s’estimer heureux que l’air y soit quand même respirable. Il y a un décalage dimensionnel intrigant entre ce monde et le nôtre, car après avoir ouvert un deuxième portail, nous avons observé que 10 pas effectués dans le Nether semblent être l’équivalent de @1 dans notre monde.@n@nLes membres de l’expédition n’ontr trouvé ni diamants ni or, et après qu’un groupe de recheche expérimenté n’ait pas réussi à retrouver la piste d’un membre de l’expédition disparu, je n’ai d’autre choix que de conclure que cet endroit est trop dangereux pour nous.
|
||||||
|
|
||||||
|
|
||||||
|
### init.lua ###
|
||||||
|
### nodes.lua ###
|
||||||
|
|
||||||
|
Nether Portal=Portail du Nether
|
||||||
|
|
||||||
|
### mapgen_mantle.lua ###
|
||||||
|
|
||||||
|
, @1m above lava-sea level=, @1m au-dessus du niveau de la mer de lave
|
||||||
|
, @1m below lava-sea level=, @1m en-dessous du niveau de la mer de lave
|
||||||
|
, approaching y boundary of Nether=, approchant de la limite y du Nether
|
||||||
|
@1@2@3@4=
|
||||||
|
Center/Mantle, but outside the caverns=Centre/Manteau, mais à l'extérieur des cavernes
|
||||||
|
Center/Mantle, inside cavern=Centre/Manteau, à l'intérieur d'une caverne
|
||||||
|
Describes which region of the nether the player is in=Indique dans quelle région du Nether se trouve le joueur
|
||||||
|
Negative nether=Nether négatif
|
||||||
|
Positive nether=Nether positif
|
||||||
|
Shell between negative nether and center region=Coquille entre le Nether négatif et la région centrale
|
||||||
|
Shell between positive nether and center region=Coquille entre le Nether positif et la région centrale
|
||||||
|
The Overworld=L'Overworld
|
||||||
|
Unknown player position=Position du joueur inconnue
|
||||||
|
[Perlin @1] =
|
||||||
|
|
||||||
|
### nodes.lua ###
|
||||||
|
|
||||||
|
A Deep Netherrack Wall=Un mur profond en Netherrack
|
||||||
|
A Netherrack Wall=Un mur en Netherrack
|
||||||
|
A finely finished block of solid Nether Basalt.=Un bloc fini de basalte du Nether solide.
|
||||||
|
A rough cut solid block of Nether Basalt.=Un bloc solide de basalte du Nether taillé à la hache.
|
||||||
|
A thin crust of cooled lava with liquid lava beneath=Une croûte fine de lave refroidie avec de la lave liquide en dessous
|
||||||
|
A vent in the earth emitting steam and gas=Une fissure dans la terre émettant de la vapeur et du gaz
|
||||||
|
Can be repurposed to provide puffs of smoke in a chimney=Peut être réutilisé pour produire des bouffées de fumée dans une cheminée
|
||||||
|
Chiselled Basalt=Basalte sculpté
|
||||||
|
|
||||||
|
Columns of dark basalt found only in magma oceans deep within the Nether.=Colonnes de basalte noir que l'on trouve uniquement dans les océans de magma profonds du Nether.
|
||||||
|
|
||||||
|
Compressed Netherbrick=Briques du Nether compressées
|
||||||
|
Cracked Nether Brick=Briques du Nether craquelées
|
||||||
|
Deep Glowstone=Pierre lumineuse profonde
|
||||||
|
Deep Nether Brick=Briques du Nether profondes
|
||||||
|
Deep Nether Slab=Dalle du Nether profonde
|
||||||
|
Deep Nether Stair=Escalier du Nether profond
|
||||||
|
Deep Netherrack=Netherrack profond
|
||||||
|
Deep Netherrack Slab=Dalle de Netherrack profonde
|
||||||
|
Deep Netherrack Stair=Escalier de Netherrack profond
|
||||||
|
Fumarolic Chimney=Cheminée fumarolique
|
||||||
|
Fumarolic Chimney Corner=Coin de cheminée fumarolique
|
||||||
|
Fumarolic Chimney Slab=Dalle de cheminée fumarolique
|
||||||
|
Glowstone=Pierre lumineuse
|
||||||
|
Hewn Basalt=Basalte taillé
|
||||||
|
Inner Deep Nether Stair=Escalier intérieur du Nether profond
|
||||||
|
Inner Nether Stair=Escalier intérieur du Nether
|
||||||
|
Lava Crust=Croûte de lave
|
||||||
|
|
||||||
|
Lava crust is strong enough to walk on, but still hot enough to inflict burns.=La croûte de lave est assez solide pour marcher dessus, mais encore assez chaude pour causer des brûlures
|
||||||
|
|
||||||
|
Nether Basalt=Basalte du Nether
|
||||||
|
Nether Beryl=Béryl du Nether
|
||||||
|
Nether Berylite=Bérylite du Nether
|
||||||
|
Nether Brick=Brique du Nether
|
||||||
|
Nether Brick Fence=Barrière en briques du Nether
|
||||||
|
Nether Brick Fence Rail=Rail de barrière en briques du Nether
|
||||||
|
Nether Crystal Pane=Panneau de cristal du Nether
|
||||||
|
Nether Slab=Dalle du Nether
|
||||||
|
Nether Stair=Escalier du Nether
|
||||||
|
|
||||||
|
Nether geode crystal, found lining the interior walls of Nether geodes=Cristal de géode du Nether, trouvé le long des parois intérieures des géodes du Nether
|
||||||
|
|
||||||
|
Nether geode crystal. A crystalline structure with faint glow found inside large Nether geodes=Cristal de géode du Nether. Une structure cristalline avec une faible lueur trouvée à l'intérieur des grandes géodes du Nether.
|
||||||
|
|
||||||
|
Netherrack=Roche du Nether
|
||||||
|
Netherrack from deep in the mantle=Roche du Nether provenant des profondeurs du manteau
|
||||||
|
Netherrack Slab=Dalle du Nether
|
||||||
|
Netherrack Stair=Escalier du Nether
|
||||||
|
Nethersand=Sable du Nether
|
||||||
|
Outer Deep Nether Stair=Escalier extérieur profond du Nether
|
||||||
|
Outer Nether Stair=Escalier extérieur du Nether
|
||||||
|
Portal=Portail
|
||||||
|
|
||||||
|
### portal_api.lua ###
|
||||||
|
|
||||||
|
@n@nThe key to opening such a doorway is to strike the frame with a @1, at which point the very air inside begins to crackle and glow.=@n@nLa méthode pour ouvrir une telle porte est de frapper son cadre avec un @1, jusqu’à ce que tout l’air à l’intérieur commence à crépiter et briller.
|
||||||
|
|
||||||
|
A definitive guide to Rifts and Portals=Un guide détaillé des failles et des portails
|
||||||
|
|
||||||
|
A guidebook for how to build portals to other realms. It can sometimes be found in dungeon chests, however a copy of this book is not needed as its contents are included in this Encyclopedia.=Un guide sur comment construire des portails vers d’autres mondes. Il peut parfois être trouvé dans des coffres de dongeons, cependant la copie de ce livre n’est pas nécessaire puisque son contenu est inclus dans l’encyclopédie.
|
||||||
|
|
||||||
|
Book of Portals=Livre des portails
|
||||||
|
Building Portals=Construire des portails
|
||||||
|
|
||||||
|
In all my travels, and time spent in the Great Libraries, I have encountered no shortage of legends surrounding preternatural doorways said to open into other worlds, yet only @1 can I confirm as being more than merely stories.=Après tous mes voyages, et le temps passé dans les Grandes Bibliothèques, je ne manque pas de légendes sur les portes surnaturelles qui, dit-on s’ouvrent vers d’autres mondes, mais seul @1 peut confirmer que ce sont plus que de simples histoires.
|
||||||
|
|
||||||
|
In all my travels, and time spent in the Great Libraries, I have encountered no shortage of legends surrounding preternatural doorways said to open into other worlds, yet only one can I confirm as being more than merely a story.=Après tous mes voyages, et le temps passé dans les Grandes Bibliothèques, je ne manque pas de légendes sur les portes surnaturelles qui, dit-on s’ouvrent vers d’autres mondes, mais seul une personne peut confirmer que ce sont plus que de simples histoires.
|
||||||
|
|
||||||
|
Mysterious forces prevented you from opening that portal. Please try another location=Des forces mystérieuses vous empêchent d'ouvrir ce portail. Veuillez essayer un autre emplacement.
|
||||||
|
|
||||||
|
Portal wormhole=Vortex du portail
|
||||||
|
|
||||||
|
Portals to other realms can be opened by building a frame in the right shape with the right blocks, then using an item to activate it. A local copy of the guidebook to portals is published below.@n---@n@n=Les portails vers d’autres mondes peuvent être ouvert en construisant un cadre de la bonne forme avec les bons blocs, puis en utilisant un objet pour l’activer. Une copie du guide des portails est ci-dessous.@n---@n@n
|
||||||
|
|
||||||
|
Refer: "Help" > "Basics" > "Building Portals"=Voir : Aide > Bases > Construire des portails
|
||||||
|
Untitled portal=Portail sans nom
|
||||||
|
We know almost nothing about this portal=Nous ne savons presque rien sur les portails
|
||||||
|
|
||||||
|
### portal_examples.lua ###
|
||||||
|
|
||||||
|
Floatlands Portal=Portail du monde flottant
|
||||||
|
|
||||||
|
Requiring 16 blocks of tin and constructed in a circular fashion, a finished frame is seven blocks wide, seven blocks high, and stands vertically like a doorway.@n@nThese travel a distance along the ground, and even when constructed deep underground will link back up to the surface. They appear to favor a strange direction, with the exit portal linking back only for as long as the portal stays open — attempting to reopen a portal from the exit doorway leads to a new destination along this favored direction. It has stymied our ability to study the behavior of these portals because without constructing dual portals and keeping both open it's hard to step through more than one and still be able to return home.@n@nDue to such difficulties, we never learned what determines the direction and distance where the matching twin portal will appear, and I have lost my friend and protégé. In cavalier youth and with little more than a rucksack, Coudreau has decided to follow the chain as far as it goes, and has not been seen since. Coudreau believes it works in epicycles, but I am not convinced. Still, I cling to the hope that one day the portal will open and Coudreau will step out from whichever place leads to this one, perhaps with an epic tale to tell.=Nécessite 16 blocs d’étain placés de manière circulaire, le cadre final fait sept blocs de large, sept blocs de haut, et se tient verticalement comme une porte.@n@nIls permettent de voyager une distance sous le sol et se relieent à la surface même s’ils sont construits profondément sous terre. Ils ont l’air de préférer une direction étrange, avec le portail de sortie ne se reliant au portail d’entrée que tant qu’ils restent tous deux ouverts – tenter de réouvrir le portail de sortie mènera à une nouvelle destination dans cette direction privilégiée.Cela a entravé notre capacité à étudier le comportement de ces portails, car sans la construction de doubles portails et en gardant les deux ouverts, il est difficile d'en traverser plus d'un et de pouvoir rentrer chez soi.@n@nEn raison de ces difficultés, nous n'avons jamais appris ce qui détermine la direction et la distance à laquelle le portail jumeau correspondant apparaîtra, et j’ai perdu mon ami et mon protégé. Dans sa jeunesse cavalière et avec à peine plus qu'un sac à dos, Coudreau a décidé de suivre la chaîne jusqu'au bout, et n'a pas été vu depuis. Coudreau croit qu'elle fonctionne sur les épicycles, mais je n'en suis pas convaincu. Je m'accroche néanmoins à l'espoir qu'un jour le portail s'ouvrira et que Coudreau sortira de l'endroit qui mène à celui-ci, peut-être avec un récit épique à raconter.
|
||||||
|
|
||||||
|
Requiring 21 blocks of ice, and constructed in the shape of a 3 × 3 platform with walls, or like a bowl. A finished platform is 2 blocks high, and 5 blocks wide at the widest in both directions.@n@nThis portal is different to the others, rather than acting akin to a doorway it appears to the eye more like a small pool of water which can be stepped into. Upon setting foot in the portal we found ourselves at a tremendous altitude.@1=Nécessite 21 blocs de glace placés pour former une plateforme de 3 × 3 avec des murs, ou comme un bol. La plateforme finale fait 2 blocs de haut, et 5 blocs de large à sa largeur maximale dans les deux directions.@n@nCe portail est différent des autres, au lieu de ressembler à une porte, il ressemble plus à un petit bassin d’eau dans lequel on peut entrer. En mettant les pieds dans le portail, nous nous sommes retrouvés à une altitude énorme.@1
|
||||||
|
|
||||||
|
Surface Portal=Portail de surface
|
||||||
|
|
||||||
|
There is a floating land of hills and forests up there, over the edges of which is a perilous drop all the way back down to sea level. We have not found how far these pristine lands extend. I have half a mind to retire there one day.=Il y a là un monde flottant remplis de collines et de forêts, sur les bords duquel se trouve une chute périlleuse jusqu'au niveau de la mer. Nous n'avons pas encore trouvé jusqu'où s'étendent ces terres vierges. J'ai à moitié envie de m'y retirer un jour.
|
||||||
|
|
||||||
|
|
||||||
|
### tools.lua ###
|
||||||
|
|
||||||
|
Nether Axe=Hache du Nether
|
||||||
|
Nether Ingot=Lingot du Nether
|
||||||
|
Nether Lump=Morceau du Nether
|
||||||
|
Nether Pickaxe@nWell suited for mining netherrack=Pioche du Nether@nBien adaptée pour miner la roche du Nether
|
||||||
|
Nether Shovel=Pelle du Nether
|
||||||
|
Nether Sword=Épée du Nether
|
||||||
|
|
||||||
|
Nether staff of Eternal Light@nCreates glowstone from netherrack=Bâton du Nether de lumière éternelle@nCrée des blocs de pierre lumineuse à partir de roche du Nether
|
||||||
|
|
||||||
|
Nether staff of Light@nTemporarily transforms the netherrack into glowstone=Bâton du Nether de lumière@nTransforme temporairement la roche du Nether en blocs de pierre lumineuse
|
||||||
|
|
||||||
|
Uniquely suited for mining netherrack, with minimal wear when doing so. Blunts quickly on other materials.=Convient parfaitement pour miner la roche du Nether avec une usure minimale. S'émousse rapidement sur les autres matériaux.
|
||||||
|
|
140
locale/template.txt
Normal 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.=
|
||||||
|
|
523
mapgen.lua
Normal file
|
@ -0,0 +1,523 @@
|
||||||
|
--[[
|
||||||
|
|
||||||
|
Nether mod for minetest
|
||||||
|
|
||||||
|
"mapgen.lua" is the modern biomes-based Nether mapgen, which
|
||||||
|
requires Minetest v5.1 or greater
|
||||||
|
"mapgen_nobiomes.lua" is the legacy version of the mapgen, only used
|
||||||
|
in older versions of Minetest or in v6 worlds.
|
||||||
|
|
||||||
|
|
||||||
|
Copyright (C) 2013 PilzAdam
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for
|
||||||
|
any purpose with or without fee is hereby granted, provided that the
|
||||||
|
above copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||||
|
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
|
||||||
|
BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
|
||||||
|
OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||||
|
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
]]--
|
||||||
|
|
||||||
|
|
||||||
|
-- Parameters
|
||||||
|
|
||||||
|
local NETHER_CEILING = nether.DEPTH_CEILING
|
||||||
|
local NETHER_FLOOR = nether.DEPTH_FLOOR
|
||||||
|
local TCAVE = 0.6
|
||||||
|
local BLEND = 128
|
||||||
|
|
||||||
|
-- parameters for central region
|
||||||
|
local REGION_BUFFER_THICKNESS = 0.2
|
||||||
|
local CENTER_REGION_LIMIT = TCAVE - REGION_BUFFER_THICKNESS -- Netherrack gives way to Deep-Netherrack here
|
||||||
|
local CENTER_CAVERN_LIMIT = CENTER_REGION_LIMIT - 0.1 -- Deep-Netherrack gives way to air here
|
||||||
|
local SURFACE_CRUST_LIMIT = CENTER_CAVERN_LIMIT * 0.25 -- Crusted-lava at the surface of the lava ocean gives way to liquid lava here
|
||||||
|
local CRUST_LIMIT = CENTER_CAVERN_LIMIT * 0.85 -- Crusted-lava under the surface of the lava ocean gives way to liquid lava here
|
||||||
|
local BASALT_COLUMN_UPPER_LIMIT = CENTER_CAVERN_LIMIT * 0.9 -- Basalt columns may appear between these upper and lower limits
|
||||||
|
local BASALT_COLUMN_LOWER_LIMIT = CENTER_CAVERN_LIMIT * 0.25 -- This value is close to SURFACE_CRUST_LIMIT so basalt columns give way to "flowing" lava rivers
|
||||||
|
|
||||||
|
|
||||||
|
-- Shared Nether mapgen namespace
|
||||||
|
-- For mapgen files to share functions and constants
|
||||||
|
local mapgen = nether.mapgen
|
||||||
|
|
||||||
|
mapgen.TCAVE = TCAVE -- const needed in mapgen_mantle.lua
|
||||||
|
mapgen.BLEND = BLEND -- const needed in mapgen_mantle.lua
|
||||||
|
mapgen.CENTER_REGION_LIMIT = CENTER_REGION_LIMIT -- const needed in mapgen_mantle.lua
|
||||||
|
mapgen.CENTER_CAVERN_LIMIT = CENTER_CAVERN_LIMIT -- const needed in mapgen_mantle.lua
|
||||||
|
mapgen.BASALT_COLUMN_UPPER_LIMIT = BASALT_COLUMN_UPPER_LIMIT -- const needed in mapgen_mantle.lua
|
||||||
|
mapgen.BASALT_COLUMN_LOWER_LIMIT = BASALT_COLUMN_LOWER_LIMIT -- const needed in mapgen_mantle.lua
|
||||||
|
|
||||||
|
mapgen.ore_ceiling = NETHER_CEILING - BLEND -- leave a solid 128 node cap of netherrack before introducing ores
|
||||||
|
mapgen.ore_floor = NETHER_FLOOR + BLEND
|
||||||
|
|
||||||
|
local debugf = nether.debug
|
||||||
|
|
||||||
|
if minetest.read_schematic == nil then
|
||||||
|
-- Using biomes to create the Nether requires the ability for biomes to set "node_cave_liquid = air".
|
||||||
|
-- This feature was introduced by paramat in b1b40fef1 on 2019-05-19, but we can't test for
|
||||||
|
-- it directly. However b2065756c was merged a few months later (in 2019-08-14) and it is easy
|
||||||
|
-- to directly test for - it adds minetest.read_schematic() - so we use this as a proxy-test
|
||||||
|
-- for whether the Minetest engine is recent enough to have implemented node_cave_liquid=air
|
||||||
|
error("This " .. nether.modname .. " mapgen requires Minetest v5.1 or greater, use mapgen_nobiomes.lua instead.", 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Load specialty helper functions
|
||||||
|
dofile(nether.path .. "/mapgen_dungeons.lua")
|
||||||
|
dofile(nether.path .. "/mapgen_mantle.lua")
|
||||||
|
dofile(nether.path .. "/mapgen_geodes.lua")
|
||||||
|
|
||||||
|
|
||||||
|
-- Misc math functions
|
||||||
|
|
||||||
|
-- avoid needing table lookups each time a common math function is invoked
|
||||||
|
local math_max, math_min, math_abs, math_floor = math.max, math.min, math.abs, math.floor
|
||||||
|
|
||||||
|
|
||||||
|
-- Inject nether_caverns biome
|
||||||
|
|
||||||
|
-- Move any existing biomes out of the y-range specified by 'floor_y' and 'ceiling_y'
|
||||||
|
mapgen.shift_existing_biomes = function(floor_y, ceiling_y)
|
||||||
|
-- https://forum.minetest.net/viewtopic.php?p=257522#p257522
|
||||||
|
-- Q: Is there a way to override an already-registered biome so I can get it out of the
|
||||||
|
-- way of my own underground biomes without disturbing the other biomes registered by
|
||||||
|
-- default?
|
||||||
|
-- A: No, all you can do is use a mod to clear all biomes then re-register the complete
|
||||||
|
-- set but with your changes. It has been described as hacky but this is actually the
|
||||||
|
-- official way to alter biomes, most mods and subgames would want to completely change
|
||||||
|
-- all biomes anyway.
|
||||||
|
-- To avoid the engine side of mapgen becoming overcomplex the approach is to require mods
|
||||||
|
-- to do slightly more complex stuff in Lua.
|
||||||
|
|
||||||
|
-- take a copy of all biomes, decorations, and ores. Regregistering a biome changes its ID, so
|
||||||
|
-- any decorations or ores using the 'biomes' field must afterwards be cleared and re-registered.
|
||||||
|
-- https://github.com/minetest/minetest/issues/9288
|
||||||
|
local registered_biomes_copy = {}
|
||||||
|
local registered_decorations_copy = {}
|
||||||
|
local registered_ores_copy = {}
|
||||||
|
|
||||||
|
for old_biome_key, old_biome_def in pairs(minetest.registered_biomes) do
|
||||||
|
registered_biomes_copy[old_biome_key] = old_biome_def
|
||||||
|
end
|
||||||
|
for old_decoration_key, old_decoration_def in pairs(minetest.registered_decorations) do
|
||||||
|
registered_decorations_copy[old_decoration_key] = old_decoration_def
|
||||||
|
end
|
||||||
|
for old_ore_key, old_ore_def in pairs(minetest.registered_ores) do
|
||||||
|
registered_ores_copy[old_ore_key] = old_ore_def
|
||||||
|
end
|
||||||
|
|
||||||
|
-- clear biomes, decorations, and ores
|
||||||
|
minetest.clear_registered_decorations()
|
||||||
|
minetest.clear_registered_ores()
|
||||||
|
minetest.clear_registered_biomes()
|
||||||
|
|
||||||
|
-- Restore biomes, adjusted to not overlap the Nether
|
||||||
|
for biome_key, new_biome_def in pairs(registered_biomes_copy) do
|
||||||
|
-- follow similar min_pos/max_pos processing logic as read_biome_def() in l_mapgen.cpp
|
||||||
|
local biome_y_max, biome_y_min = 31000, -31000
|
||||||
|
if type(new_biome_def.min_pos) == 'table' and type(new_biome_def.min_pos.y) == 'number' then biome_y_min = new_biome_def.min_pos.y end
|
||||||
|
if type(new_biome_def.max_pos) == 'table' and type(new_biome_def.max_pos.y) == 'number' then biome_y_max = new_biome_def.max_pos.y end
|
||||||
|
if type(new_biome_def.y_min) == 'number' then biome_y_min = new_biome_def.y_min end
|
||||||
|
if type(new_biome_def.y_max) == 'number' then biome_y_max = new_biome_def.y_max end
|
||||||
|
|
||||||
|
if biome_y_max > floor_y and biome_y_min < ceiling_y then
|
||||||
|
-- This biome occupies some or all of the depth of the Nether, shift/crop it.
|
||||||
|
local new_y_min, new_y_max
|
||||||
|
local spaceOccupiedAbove = biome_y_max - ceiling_y
|
||||||
|
local spaceOccupiedBelow = floor_y - biome_y_min
|
||||||
|
if spaceOccupiedAbove >= spaceOccupiedBelow or biome_y_min <= -30000 then
|
||||||
|
-- place the biome above the Nether
|
||||||
|
-- We also shift biomes which extend to the bottom of the map above the Nether, since they
|
||||||
|
-- likely only extend that deep as a catch-all, and probably have a role nearer the surface.
|
||||||
|
new_y_min = ceiling_y + 1
|
||||||
|
new_y_max = math_max(biome_y_max, ceiling_y + 2)
|
||||||
|
else
|
||||||
|
-- shift the biome to below the Nether
|
||||||
|
new_y_max = floor_y - 1
|
||||||
|
new_y_min = math_min(biome_y_min, floor_y - 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
debugf("Moving biome \"%s\" from %s..%s to %s..%s", new_biome_def.name, new_biome_def.y_min, new_biome_def.y_max, new_y_min, new_y_max)
|
||||||
|
|
||||||
|
if type(new_biome_def.min_pos) == 'table' and type(new_biome_def.min_pos.y) == 'number' then new_biome_def.min_pos.y = new_y_min end
|
||||||
|
if type(new_biome_def.max_pos) == 'table' and type(new_biome_def.max_pos.y) == 'number' then new_biome_def.max_pos.y = new_y_max end
|
||||||
|
new_biome_def.y_min = new_y_min -- Ensure the new heights are saved, even if original biome never specified one
|
||||||
|
new_biome_def.y_max = new_y_max
|
||||||
|
end
|
||||||
|
minetest.register_biome(new_biome_def)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Restore biome decorations
|
||||||
|
for decoration_key, new_decoration_def in pairs(registered_decorations_copy) do
|
||||||
|
minetest.register_decoration(new_decoration_def)
|
||||||
|
end
|
||||||
|
-- Restore biome ores
|
||||||
|
for ore_key, new_ore_def in pairs(registered_ores_copy) do
|
||||||
|
minetest.register_ore(new_ore_def)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Shift any overlapping biomes out of the way before we create the Nether biomes
|
||||||
|
mapgen.shift_existing_biomes(NETHER_FLOOR, NETHER_CEILING)
|
||||||
|
|
||||||
|
-- nether:native_mapgen is used to prevent ores and decorations being generated according
|
||||||
|
-- to landforms created by the native mapgen.
|
||||||
|
-- Ores and decorations can be registered against "nether:rack" instead, and the lua
|
||||||
|
-- on_generate() callback will carve the Nether with nether:rack before invoking
|
||||||
|
-- generate_decorations and generate_ores.
|
||||||
|
-- It is disguised as stone to hide any bug where it leaks out of the nether, such as
|
||||||
|
-- https://github.com/minetest/minetest/issues/13440 or if on_generated() somehow was aborted.
|
||||||
|
local stone_copy_def = table.copy(minetest.registered_nodes["default:stone"] or {})
|
||||||
|
stone_copy_def.drop = stone_copy_def.drop or "default:stone" -- probably already defined as cobblestone
|
||||||
|
minetest.register_node("nether:native_mapgen", stone_copy_def)
|
||||||
|
|
||||||
|
minetest.register_biome({
|
||||||
|
name = "nether_caverns",
|
||||||
|
node_stone = "nether:native_mapgen", -- nether:native_mapgen is used here to prevent the native mapgen from placing ores and decorations.
|
||||||
|
node_filler = "nether:native_mapgen", -- The lua on_generate will transform nether:native_mapgen into nether:rack then decorate and add ores.
|
||||||
|
node_dungeon = "nether:brick",
|
||||||
|
node_dungeon_alt = "nether:brick_cracked",
|
||||||
|
node_dungeon_stair = "stairs:stair_nether_brick",
|
||||||
|
-- Setting node_cave_liquid to "air" avoids the need to filter lava and water out of the mapchunk and
|
||||||
|
-- surrounding shell (overdraw nodes beyond the mapchunk).
|
||||||
|
-- This feature was introduced by paramat in b1b40fef1 on 2019-05-19, and this mapgen.lua file should only
|
||||||
|
-- be run if the Minetest version includes it. The earliest tag made after 2019-05-19 is 5.1.0 on 2019-10-13,
|
||||||
|
-- however we shouldn't test version numbers. minetest.read_schematic() was added by b2065756c and merged in
|
||||||
|
-- 2019-08-14 and is easy to test for, we don't use it but it should make a good proxy-test for whether the
|
||||||
|
-- Minetest version is recent enough to have implemented node_cave_liquid=air
|
||||||
|
node_cave_liquid = "air",
|
||||||
|
y_max = NETHER_CEILING,
|
||||||
|
y_min = NETHER_FLOOR,
|
||||||
|
vertical_blend = 0,
|
||||||
|
heat_point = 50,
|
||||||
|
humidity_point = 50,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
-- Ores and decorations
|
||||||
|
|
||||||
|
dofile(nether.path .. "/mapgen_decorations.lua")
|
||||||
|
|
||||||
|
minetest.register_ore({
|
||||||
|
ore_type = "scatter",
|
||||||
|
ore = "nether:glowstone",
|
||||||
|
wherein = "nether:rack",
|
||||||
|
clust_scarcity = 11 * 11 * 11,
|
||||||
|
clust_num_ores = 3,
|
||||||
|
clust_size = 2,
|
||||||
|
y_max = mapgen.ore_ceiling,
|
||||||
|
y_min = mapgen.ore_floor
|
||||||
|
})
|
||||||
|
|
||||||
|
minetest.register_ore({
|
||||||
|
ore_type = "scatter",
|
||||||
|
ore = "nether:lava_crust", -- crusted lava replaces scattered glowstone in the mantle
|
||||||
|
wherein = "nether:rack_deep",
|
||||||
|
clust_scarcity = 16 * 16 * 16,
|
||||||
|
clust_num_ores = 4,
|
||||||
|
clust_size = 2,
|
||||||
|
y_max = mapgen.ore_ceiling,
|
||||||
|
y_min = mapgen.ore_floor
|
||||||
|
})
|
||||||
|
|
||||||
|
minetest.register_ore({
|
||||||
|
ore_type = "scatter",
|
||||||
|
ore = "default:lava_source",
|
||||||
|
wherein = {"nether:rack", "nether:rack_deep"},
|
||||||
|
clust_scarcity = 36 * 36 * 36,
|
||||||
|
clust_num_ores = 4,
|
||||||
|
clust_size = 2,
|
||||||
|
y_max = mapgen.ore_ceiling,
|
||||||
|
y_min = mapgen.ore_floor
|
||||||
|
})
|
||||||
|
|
||||||
|
minetest.register_ore({
|
||||||
|
ore_type = "blob",
|
||||||
|
ore = "nether:sand",
|
||||||
|
wherein = "nether:rack",
|
||||||
|
clust_scarcity = 14 * 14 * 14,
|
||||||
|
clust_size = 8,
|
||||||
|
y_max = mapgen.ore_ceiling,
|
||||||
|
y_min = mapgen.ore_floor
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
-- Mapgen
|
||||||
|
|
||||||
|
-- 3D noise
|
||||||
|
|
||||||
|
mapgen.np_cave = {
|
||||||
|
offset = 0,
|
||||||
|
scale = 1,
|
||||||
|
spread = {x = 384, y = 128, z = 384}, -- squashed 3:1
|
||||||
|
seed = 59033,
|
||||||
|
octaves = 5,
|
||||||
|
persist = 0.7,
|
||||||
|
lacunarity = 2.0,
|
||||||
|
--flags = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
local cavePointPerlin = nil
|
||||||
|
|
||||||
|
mapgen.get_cave_point_perlin = function()
|
||||||
|
cavePointPerlin = cavePointPerlin or minetest.get_perlin(mapgen.np_cave)
|
||||||
|
return cavePointPerlin
|
||||||
|
end
|
||||||
|
|
||||||
|
mapgen.get_cave_perlin_at = function(pos)
|
||||||
|
cavePointPerlin = cavePointPerlin or minetest.get_perlin(mapgen.np_cave)
|
||||||
|
return cavePointPerlin:get_3d(pos)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Buffers and objects we shouldn't recreate every on_generate
|
||||||
|
|
||||||
|
local nobj_cave = nil
|
||||||
|
local nbuf_cave = {}
|
||||||
|
local dbuf = {}
|
||||||
|
|
||||||
|
|
||||||
|
-- Content ids
|
||||||
|
|
||||||
|
local c_air = minetest.get_content_id("air")
|
||||||
|
local c_netherrack = minetest.get_content_id("nether:rack")
|
||||||
|
local c_netherrack_deep = minetest.get_content_id("nether:rack_deep")
|
||||||
|
local c_crystaldark = minetest.get_content_id("nether:geode")
|
||||||
|
local c_lavasea_source = minetest.get_content_id("nether:lava_source") -- same as lava but with staggered animation to look better as an ocean
|
||||||
|
local c_lava_crust = minetest.get_content_id("nether:lava_crust")
|
||||||
|
local c_native_mapgen = minetest.get_content_id("nether:native_mapgen")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
local yblmin = NETHER_FLOOR + BLEND * 2
|
||||||
|
local yblmax = NETHER_CEILING - BLEND * 2
|
||||||
|
-- At both the top and bottom of the Nether, as set by NETHER_CEILING and NETHER_FLOOR,
|
||||||
|
-- there is a 128 deep cap of solid netherrack, followed by a 128-deep blending zone
|
||||||
|
-- where Nether caverns may start to appear.
|
||||||
|
-- The solid zones and blending zones are achieved by adjusting the np_cave noise to be
|
||||||
|
-- outside the range where caverns form, this function returns that adjustment.
|
||||||
|
--
|
||||||
|
-- Returns two values: the noise limit adjustment for nether caverns, and the
|
||||||
|
-- noise limit adjustment for the central region / mantle caverns
|
||||||
|
mapgen.get_mapgenblend_adjustments = function(y)
|
||||||
|
|
||||||
|
-- floorAndCeilingBlend will normally be 0, but shifts toward 1 in the
|
||||||
|
-- blending zone, and goes higher than 1 in the solid zone between the
|
||||||
|
-- blending zone and the end of the nether.
|
||||||
|
local floorAndCeilingBlend = 0
|
||||||
|
if y > yblmax then floorAndCeilingBlend = ((y - yblmax) / BLEND) ^ 2 end
|
||||||
|
if y < yblmin then floorAndCeilingBlend = ((yblmin - y) / BLEND) ^ 2 end
|
||||||
|
|
||||||
|
-- the nether caverns exist when np_cave noise is greater than TCAVE, so
|
||||||
|
-- to fade out the nether caverns, adjust TCAVE upward.
|
||||||
|
local tcave_adj = floorAndCeilingBlend
|
||||||
|
|
||||||
|
-- the central regions exists when np_cave noise is below CENTER_REGION_LIMIT,
|
||||||
|
-- so to fade out the mantle caverns adjust CENTER_REGION_LIMIT downward.
|
||||||
|
local centerRegionLimit_adj = -(CENTER_REGION_LIMIT * floorAndCeilingBlend)
|
||||||
|
|
||||||
|
return tcave_adj, centerRegionLimit_adj
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- On-generated function
|
||||||
|
|
||||||
|
local tunnelCandidate_count = 0
|
||||||
|
local tunnel_count = 0
|
||||||
|
local total_chunk_count = 0
|
||||||
|
local function on_generated(minp, maxp, seed)
|
||||||
|
|
||||||
|
if minp.y > NETHER_CEILING or maxp.y < NETHER_FLOOR then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local vm, emerge_min, emerge_max = minetest.get_mapgen_object("voxelmanip")
|
||||||
|
local area = VoxelArea:new{MinEdge=emerge_min, MaxEdge=emerge_max}
|
||||||
|
local data = vm:get_data(dbuf)
|
||||||
|
|
||||||
|
local x0, y0, z0 = minp.x, math_max(minp.y, NETHER_FLOOR), minp.z
|
||||||
|
local x1, y1, z1 = maxp.x, math_min(maxp.y, NETHER_CEILING), maxp.z
|
||||||
|
|
||||||
|
local yCaveStride = x1 - x0 + 1
|
||||||
|
local zCaveStride = yCaveStride * yCaveStride
|
||||||
|
local chulens = {x = yCaveStride, y = yCaveStride, z = yCaveStride}
|
||||||
|
|
||||||
|
nobj_cave = nobj_cave or minetest.get_perlin_map(mapgen.np_cave, chulens)
|
||||||
|
local nvals_cave = nobj_cave:get_3d_map_flat(minp, nbuf_cave)
|
||||||
|
|
||||||
|
local dungeonRooms = mapgen.build_dungeon_room_list(data, area) -- function from mapgen_dungeons.lua
|
||||||
|
local abs_cave_noise, abs_cave_noise_adjusted
|
||||||
|
|
||||||
|
local contains_nether = false
|
||||||
|
local contains_mantle = false
|
||||||
|
local contains_ocean = false
|
||||||
|
|
||||||
|
|
||||||
|
for y = y0, y1 do -- Y loop first to minimise tcave & lava-sea calculations
|
||||||
|
|
||||||
|
local sea_level, cavern_limit_distance = mapgen.find_nearest_lava_sealevel(y) -- function from mapgen_mantle.lua
|
||||||
|
local above_lavasea = y > sea_level
|
||||||
|
local below_lavasea = y < sea_level
|
||||||
|
|
||||||
|
local tcave_adj, centerRegionLimit_adj = mapgen.get_mapgenblend_adjustments(y)
|
||||||
|
local tcave = TCAVE + tcave_adj
|
||||||
|
local tmantle = CENTER_REGION_LIMIT + centerRegionLimit_adj -- cavern_noise_adj already contains central_region_limit_adj, so tmantle is only for comparisons when cavern_noise_adj hasn't been added to the noise value
|
||||||
|
|
||||||
|
-- cavern_noise_adj gets added to noise value instead of added to the limit np_noise
|
||||||
|
-- is compared against, so subtract centerRegionLimit_adj instead of adding
|
||||||
|
local cavern_noise_adj =
|
||||||
|
CENTER_REGION_LIMIT * (cavern_limit_distance * cavern_limit_distance * cavern_limit_distance) -
|
||||||
|
centerRegionLimit_adj
|
||||||
|
|
||||||
|
for z = z0, z1 do
|
||||||
|
local vi = area:index(x0, y, z) -- Initial voxelmanip index
|
||||||
|
local ni = (z - z0) * zCaveStride + (y - y0) * yCaveStride + 1
|
||||||
|
local noise2di = 1 + (z - z0) * yCaveStride
|
||||||
|
|
||||||
|
for x = x0, x1 do
|
||||||
|
|
||||||
|
local cave_noise = nvals_cave[ni]
|
||||||
|
|
||||||
|
if cave_noise > tcave then
|
||||||
|
-- Prime region
|
||||||
|
-- This was the only region in initial versions of the Nether mod.
|
||||||
|
-- It is the only region which portals from the surface will open into,
|
||||||
|
-- getting to any other regions in the Nether will require Shanks' Pony.
|
||||||
|
data[vi] = c_air
|
||||||
|
contains_nether = true
|
||||||
|
|
||||||
|
elseif -cave_noise > tcave then
|
||||||
|
-- Secondary/spare region
|
||||||
|
-- This secondary region is unused until someone decides to do something cool or novel with it.
|
||||||
|
-- Reaching here would require the player to first find and journey through the central region,
|
||||||
|
-- as it's always separated from the Prime region by the central region.
|
||||||
|
|
||||||
|
data[vi] = mapgen.getGeodeInteriorNodeId(x, y, z)-- function from mapgen_geodes.lua
|
||||||
|
|
||||||
|
-- Only set contains_nether to true here if you want tunnels created between the secondary region
|
||||||
|
-- and the central region.
|
||||||
|
contains_nether = true
|
||||||
|
else
|
||||||
|
-- netherrack walls and/or center region/mantle
|
||||||
|
abs_cave_noise = math_abs(cave_noise)
|
||||||
|
|
||||||
|
-- abs_cave_noise_adjusted makes the center region smaller as distance from the lava ocean
|
||||||
|
-- increases, we do this by pretending the abs_cave_noise value is higher.
|
||||||
|
abs_cave_noise_adjusted = abs_cave_noise + cavern_noise_adj
|
||||||
|
|
||||||
|
if abs_cave_noise_adjusted >= CENTER_CAVERN_LIMIT then
|
||||||
|
|
||||||
|
local id = data[vi] -- Check existing node to avoid removing dungeons
|
||||||
|
if id == c_air or id == c_native_mapgen then
|
||||||
|
if abs_cave_noise < tmantle then
|
||||||
|
data[vi] = c_netherrack_deep
|
||||||
|
else
|
||||||
|
-- the shell seperating the mantle from the rest of the nether...
|
||||||
|
if cave_noise > 0 then
|
||||||
|
data[vi] = c_netherrack -- excavate_dungeons() will mostly reverse this inside dungeons
|
||||||
|
else
|
||||||
|
data[vi] = c_crystaldark
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif above_lavasea then
|
||||||
|
data[vi] = c_air
|
||||||
|
contains_mantle = true
|
||||||
|
elseif abs_cave_noise_adjusted < SURFACE_CRUST_LIMIT or (below_lavasea and abs_cave_noise_adjusted < CRUST_LIMIT) then
|
||||||
|
data[vi] = c_lavasea_source
|
||||||
|
contains_ocean = true
|
||||||
|
else
|
||||||
|
data[vi] = c_lava_crust
|
||||||
|
contains_ocean = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ni = ni + 1
|
||||||
|
vi = vi + 1
|
||||||
|
noise2di = noise2di + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if contains_mantle or contains_ocean then
|
||||||
|
mapgen.add_basalt_columns(data, area, minp, maxp) -- function from mapgen_mantle.lua
|
||||||
|
end
|
||||||
|
|
||||||
|
if contains_nether and contains_mantle then
|
||||||
|
tunnelCandidate_count = tunnelCandidate_count + 1
|
||||||
|
local success = mapgen.excavate_tunnel_to_center_of_the_nether(data, area, nvals_cave, minp, maxp) -- function from mapgen_mantle.lua
|
||||||
|
if success then tunnel_count = tunnel_count + 1 end
|
||||||
|
end
|
||||||
|
total_chunk_count = total_chunk_count + 1
|
||||||
|
if total_chunk_count % 50 == 0 then
|
||||||
|
debugf(
|
||||||
|
"%s of %s chunks contain both nether and lava-sea (%s%%), %s chunks generated a pathway (%s%%)",
|
||||||
|
tunnelCandidate_count,
|
||||||
|
total_chunk_count,
|
||||||
|
math_floor(tunnelCandidate_count * 100 / total_chunk_count),
|
||||||
|
tunnel_count,
|
||||||
|
math_floor(tunnel_count * 100 / total_chunk_count)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- any air from the native mapgen has been replaced by netherrack, but we
|
||||||
|
-- don't want netherrack inside dungeons, so fill known dungeon rooms with air.
|
||||||
|
mapgen.excavate_dungeons(data, area, dungeonRooms) -- function from mapgen_dungeons.lua
|
||||||
|
mapgen.decorate_dungeons(data, area, dungeonRooms) -- function from mapgen_dungeons.lua
|
||||||
|
|
||||||
|
vm:set_data(data)
|
||||||
|
|
||||||
|
minetest.generate_ores(vm)
|
||||||
|
minetest.generate_decorations(vm)
|
||||||
|
|
||||||
|
vm:set_lighting({day = 0, night = 0}, minp, maxp)
|
||||||
|
vm:calc_lighting()
|
||||||
|
vm:update_liquids()
|
||||||
|
vm:write_to_map()
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- use knowledge of the nether mapgen algorithm to return a suitable ground level for placing a portal.
|
||||||
|
-- player_name is optional, allowing a player to spawn a remote portal in their own protected areas.
|
||||||
|
function nether.find_nether_ground_y(target_x, target_z, start_y, player_name)
|
||||||
|
local nobj_cave_point = mapgen.get_cave_point_perlin()
|
||||||
|
local air = 0 -- Consecutive air nodes found
|
||||||
|
|
||||||
|
local minp_schem, maxp_schem = nether.get_schematic_volume({x = target_x, y = 0, z = target_z}, nil, "nether_portal")
|
||||||
|
local minp = {x = minp_schem.x, y = 0, z = minp_schem.z}
|
||||||
|
local maxp = {x = maxp_schem.x, y = 0, z = maxp_schem.z}
|
||||||
|
|
||||||
|
for y = start_y, math_max(NETHER_FLOOR + BLEND, start_y - 4096), -1 do
|
||||||
|
local nval_cave = nobj_cave_point:get_3d({x = target_x, y = y, z = target_z})
|
||||||
|
|
||||||
|
if nval_cave > TCAVE then -- Cavern
|
||||||
|
air = air + 1
|
||||||
|
else -- Not cavern, check if 4 nodes of space above
|
||||||
|
if air >= 4 then
|
||||||
|
local portal_y = y + 1
|
||||||
|
-- Check volume for non-natural nodes
|
||||||
|
minp.y = minp_schem.y + portal_y
|
||||||
|
maxp.y = maxp_schem.y + portal_y
|
||||||
|
if nether.volume_is_natural_and_unprotected(minp, maxp, player_name) then
|
||||||
|
return portal_y
|
||||||
|
else -- Restart search a little lower
|
||||||
|
nether.find_nether_ground_y(target_x, target_z, y - 16, player_name)
|
||||||
|
end
|
||||||
|
else -- Not enough space, reset air to zero
|
||||||
|
air = 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return math_max(start_y, NETHER_FLOOR + BLEND) -- Fallback
|
||||||
|
end
|
||||||
|
|
||||||
|
minetest.register_on_generated(on_generated)
|
592
mapgen_decorations.lua
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
3
mod.conf
|
@ -1 +1,4 @@
|
||||||
name = nether
|
name = nether
|
||||||
|
description = Adds a deep underground realm with different mapgen that you can reach with obsidian portals.
|
||||||
|
depends = stairs, default
|
||||||
|
optional_depends = toolranks, moreblocks, mesecons, loot, dungeon_loot, doc_basics, fire, climate_api, ethereal, xpanes, walls
|
||||||
|
|
78
nether_api.txt
Normal 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.
|
2389
portal_api.lua
Normal file
265
portal_api.txt
Normal file
|
@ -0,0 +1,265 @@
|
||||||
|
Portal API Reference
|
||||||
|
====================
|
||||||
|
|
||||||
|
The portal system used to get to the Nether can be used to create portals
|
||||||
|
to other realms.
|
||||||
|
|
||||||
|
Pick a node type to have your portals built from, a shape in which the
|
||||||
|
portals must be built, and provide 3 functions for portals to find their
|
||||||
|
destination with:
|
||||||
|
* `find_realm_anchorPos(surface_anchorPos)`
|
||||||
|
* `find_surface_anchorPos(realm_anchorPos)`
|
||||||
|
* `is_within_realm(pos)`
|
||||||
|
|
||||||
|
Optionally decorate by choosing portal colors, particles, media etc.
|
||||||
|
|
||||||
|
See `init.lua` and `portal_examples.lua` for examples of 3 different portals.
|
||||||
|
|
||||||
|
Portal code is more efficient when each type of portal uses a different type
|
||||||
|
of node to build its frame out of - consider creating your own node for
|
||||||
|
players to build portals from. However it is possible to register more than
|
||||||
|
one kind of portal with the same frame material — such as obsidian — provided
|
||||||
|
the size of the PortalShape is distinct from any other type of portal that is
|
||||||
|
using the same node for its frame, and portal sizes remain small.
|
||||||
|
|
||||||
|
The Nether provides three variants of Nether basalt to ensure there are
|
||||||
|
alternatives to obsidian for other mods to use as portalstones.
|
||||||
|
|
||||||
|
|
||||||
|
Realms
|
||||||
|
------
|
||||||
|
|
||||||
|
This API uses the concept of a realm for each type of portal. If a portal is
|
||||||
|
outside its realm then it links to a portal inside the realm, if a portal is
|
||||||
|
inside its realm then it links to the outside.
|
||||||
|
|
||||||
|
You get to decide what constitutes your realm by implementing the function
|
||||||
|
`is_within_realm(position)`.
|
||||||
|
|
||||||
|
For example, the Nether realm is defined as existing at a certain depth and
|
||||||
|
anything above that depth is considered outside the Realm.
|
||||||
|
|
||||||
|
In contrast, the "Surface portal" - an example in portal_examples.lua, only
|
||||||
|
uses one realm. Its `is_within_realm()` function always returns true so that
|
||||||
|
any time a portal is opened it will use `find_surface_anchorPos()`.
|
||||||
|
|
||||||
|
Note that the name "find_surface_anchorPos" is a Nether-centric misnomer, as
|
||||||
|
different types of portals are free to use different definitions of a realm
|
||||||
|
such that leaving the realm might not be synonymous with travelling to the
|
||||||
|
surface.
|
||||||
|
|
||||||
|
|
||||||
|
Helper functions
|
||||||
|
----------------
|
||||||
|
|
||||||
|
* `nether.volume_is_natural_and_unprotected(minp, maxp, player_name)`: returns
|
||||||
|
a boolean.
|
||||||
|
* use this when determining where to spawn a portal, to avoid overwriting
|
||||||
|
player builds. It checks the area for any nodes that aren't ground or
|
||||||
|
trees.
|
||||||
|
Water will fail this test, unless it is unemerged.
|
||||||
|
* player_name is optional, providing it allows the player's own protected
|
||||||
|
areas to be treated as unprotected.
|
||||||
|
|
||||||
|
* `nether.find_surface_target_y(target_x, target_z, portal_name, player_name)`:
|
||||||
|
returns a suitable anchorPos
|
||||||
|
* Can be used when implementing custom find_surface_anchorPos() functions
|
||||||
|
* portal_name is optional, providing it allows existing portals on the
|
||||||
|
surface to be reused.
|
||||||
|
* player_name is optional, providing it prevents the exclusion of surface
|
||||||
|
target areas which are protected by the player.
|
||||||
|
* May return nil in extreme circumstances, such as the surface being
|
||||||
|
protected down to a great depth.
|
||||||
|
|
||||||
|
* `nether.find_nearest_working_portal(portal_name, anchorPos, distance_limit, y_factor)`: returns
|
||||||
|
(anchorPos, orientation), or nil if no portal was found within the
|
||||||
|
distance_limit.
|
||||||
|
* A y_factor of 0 means y does not affect the distance_limit, a y_factor
|
||||||
|
of 1 means y is included (the default if y_factor is nil), and a y_factor
|
||||||
|
of 2 would squash the search-sphere by a factor of 2 on the y-axis, etc.
|
||||||
|
* Only portals in the same realm as the anchorPos will be returned, even if
|
||||||
|
y_factor is 0.
|
||||||
|
* Pass a nil (or negative) distance_limit to indicate no distance limit
|
||||||
|
|
||||||
|
|
||||||
|
API functions
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Call these functions only at load time:
|
||||||
|
|
||||||
|
* `nether.register_portal(name, portal_definition)`
|
||||||
|
* Returns true on success. Can return false if the portal definition
|
||||||
|
clashes with a portal already registered by another mod, e.g. if the size
|
||||||
|
and frame node is not unique.
|
||||||
|
A false return value should be handled, you could:
|
||||||
|
* Fall back to using a secondary material for portals to be built with.
|
||||||
|
* Use error() to exit lua with a message explaining how two mods are
|
||||||
|
clashing and how it can be resolved.
|
||||||
|
* Continue without a portal (the reason will be logged for the user).
|
||||||
|
* `nether.register_portal_ignition_item(name, ignition_failure_sound)`
|
||||||
|
* ignition_failure_sound is optional, it plays any time an attempt to use
|
||||||
|
the item occurs if a portal is not ignited.
|
||||||
|
* `nether.register_wormhole_node(name, nodedef_overrides)`
|
||||||
|
* Can be used to register wormhole nodes with a different post_effect_color
|
||||||
|
from the "nether:portal" node. "Post effect color" is the tint the world
|
||||||
|
takes on when you are standing inside a portal. `post_effect_color` is the
|
||||||
|
only key/value that is needed in the nodedef_overrides table to achieve that,
|
||||||
|
but the function allows any nodedef key/value to be specified/overridden.
|
||||||
|
* After `register_wormhole_node()`, invoke `register_portal()` and include
|
||||||
|
`wormhole_node_name` in the portal_definition, assigning it the name of the
|
||||||
|
new wormhole node.
|
||||||
|
* `nether.unregister_portal(name)`
|
||||||
|
* Unregisters the portal from the engine, and deletes the entry with key
|
||||||
|
`name` from `nether.registered_portals` and associated internal tables.
|
||||||
|
* Returns true on success
|
||||||
|
* You will probably never need to call this, it exists only for completeness.
|
||||||
|
|
||||||
|
|
||||||
|
Portal definition
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
Used by `nether.register_portal`.
|
||||||
|
|
||||||
|
{
|
||||||
|
frame_node_name = "default:obsidian",
|
||||||
|
-- Required. For best results, have your portal constructed of a
|
||||||
|
-- material nobody else is using.
|
||||||
|
|
||||||
|
frame_node_color = 0,
|
||||||
|
-- Optional.
|
||||||
|
-- A value from 0 to 7. Only used if the frame node's paramtype2 is
|
||||||
|
-- "colorfacedir", in which case this color will be used when a remote
|
||||||
|
-- portal is created.
|
||||||
|
|
||||||
|
shape = nether.PortalShape_Traditional,
|
||||||
|
-- Optional.
|
||||||
|
-- Shapes available are:
|
||||||
|
-- nether.PortalShape_Traditional (default)
|
||||||
|
-- nether.PortalShape_Circular
|
||||||
|
-- nether.PortalShape_Platform
|
||||||
|
-- New shapes can be created, but are beyond the scope of this guide.
|
||||||
|
|
||||||
|
wormhole_node_name = "nether:portal",
|
||||||
|
-- Optional. Allows a custom wormhole node to be specified.
|
||||||
|
-- Useful if you want the portals to have a different post_effect_color
|
||||||
|
-- or texture.
|
||||||
|
-- The Nether mod provides:
|
||||||
|
-- "nether:portal" (default)
|
||||||
|
-- "nether:portal_alt"
|
||||||
|
|
||||||
|
wormhole_node_color = 0,
|
||||||
|
-- Optional. Defaults to 0/magenta.
|
||||||
|
-- A value from 0 to 7 corresponding to the color of pixels in
|
||||||
|
-- nether_portals_palette.png:
|
||||||
|
-- 0 traditional/magenta
|
||||||
|
-- 1 black
|
||||||
|
-- 2 blue
|
||||||
|
-- 3 green
|
||||||
|
-- 4 cyan
|
||||||
|
-- 5 red
|
||||||
|
-- 6 yellow
|
||||||
|
-- 7 white
|
||||||
|
|
||||||
|
particle_color = "#808",
|
||||||
|
-- Optional. Will default to a colour matching the wormhole_node_color
|
||||||
|
-- if not specified.
|
||||||
|
|
||||||
|
particle_texture = "image.png",
|
||||||
|
-- Optional. Hardware colouring (i.e. particle_color) is applied to
|
||||||
|
-- this texture, use particle_texture_colored instead if you want to
|
||||||
|
-- use the colors of the image.
|
||||||
|
-- Animation and particle scale may also be specified, e.g:
|
||||||
|
-- particle_texture = {
|
||||||
|
-- name = "nether_particle_anim1.png",
|
||||||
|
-- animation = {
|
||||||
|
-- type = "vertical_frames",
|
||||||
|
-- aspect_w = 7,
|
||||||
|
-- aspect_h = 7,
|
||||||
|
-- length = 1,
|
||||||
|
-- },
|
||||||
|
-- scale = 1.5
|
||||||
|
-- },
|
||||||
|
-- See lua_api.txt for Tile Animation definition
|
||||||
|
-- Some animated and non-animated textures are provided by this mod:
|
||||||
|
-- nether_particle.png (original)
|
||||||
|
-- nether_particle_anim1.png (stars)
|
||||||
|
-- nether_particle_anim2.png (bubbles)
|
||||||
|
-- nether_particle_anim3.png (sparks)
|
||||||
|
-- nether_particle_anim4.png (particles)
|
||||||
|
|
||||||
|
title = "Gateway to Moria",
|
||||||
|
-- Optional. Provides a title for the portal.
|
||||||
|
-- Used in the Book of Portals or Help modpack.
|
||||||
|
|
||||||
|
book_of_portals_pagetext = "Everything I need the player to know",
|
||||||
|
-- Optional. Provides the text for the portal in the Book of Portals
|
||||||
|
-- and Help modpack.
|
||||||
|
-- The Book of Portals is a book that can be found in chests, and
|
||||||
|
-- provides players with instructions on how to build and use the
|
||||||
|
-- portal, so be sure to mention the node type the frame must be built
|
||||||
|
-- from.
|
||||||
|
-- This can also provide flavortext or details about where the portal
|
||||||
|
-- will take the player.
|
||||||
|
|
||||||
|
sounds = {
|
||||||
|
ambient = <SimpleSoundSpec>,
|
||||||
|
-- if the ambient SimpleSoundSpec is a table it can also contain a
|
||||||
|
-- "length" int, which is the number of seconds to wait before
|
||||||
|
-- repeating the ambient sound. Default is 3.
|
||||||
|
ignite = <SimpleSoundSpec>,
|
||||||
|
extinguish = <SimpleSoundSpec>,
|
||||||
|
teleport = <SimpleSoundSpec>,
|
||||||
|
}
|
||||||
|
-- sounds is optional
|
||||||
|
|
||||||
|
within_realm = function(pos),
|
||||||
|
-- Required. Return true if a portal at pos is in the realm, rather
|
||||||
|
-- than the surface world.
|
||||||
|
-- Ideally implementations are fast, as this function can be used to
|
||||||
|
-- sift through a list of portals.
|
||||||
|
|
||||||
|
find_realm_anchorPos = function(surface_anchorPos, player_name),
|
||||||
|
-- Required. Return a position in the realm that a portal created at
|
||||||
|
-- surface_anchorPos will link to.
|
||||||
|
-- Return an anchorPos or (anchorPos, orientation)
|
||||||
|
-- If orientation is not specified then the orientation of the surface
|
||||||
|
-- portal will be used.
|
||||||
|
-- If the location of an existing portal is returned then include the
|
||||||
|
-- orientation, otherwise the existing portal could be overwritten by
|
||||||
|
-- a new one with the orientation of the surface portal.
|
||||||
|
-- Return nil, or a position with a nil y component, to prevent the
|
||||||
|
-- portal from igniting.
|
||||||
|
-- player_name may be "", e.g. if the portal was ignited by a mesecon,
|
||||||
|
-- and is provided for use with volume_is_natural_and_unprotected() etc.
|
||||||
|
|
||||||
|
find_surface_anchorPos = function(realm_anchorPos, player_name),
|
||||||
|
-- Optional. If you don't implement this then a position near the
|
||||||
|
-- surface will be picked.
|
||||||
|
-- Return an anchorPos or (anchorPos, orientation)
|
||||||
|
-- The name of this function is a Nether-centric misnomer. It should
|
||||||
|
-- return a position outside the realm, and different types of portals
|
||||||
|
-- are free to use different definitions of a realm such that leaving
|
||||||
|
-- the realm might not be synonymous with travelling to the surface.
|
||||||
|
-- If orientation is not specified then the orientation of the realm
|
||||||
|
-- portal will be used.
|
||||||
|
-- If the location of an existing portal is returned then include the
|
||||||
|
-- orientation, otherwise the existing portal could be overwritten by
|
||||||
|
-- a new one with the orientation of the realm portal.
|
||||||
|
-- Return nil, or a position with a nil y component, to prevent the
|
||||||
|
-- portal from igniting.
|
||||||
|
-- player_name may be "", e.g. if the portal was ignited by a mesecon,
|
||||||
|
-- and is provided for use with volume_is_natural_and_unprotected() etc.
|
||||||
|
|
||||||
|
on_run_wormhole = function(portalDef, anchorPos, orientation),
|
||||||
|
-- invoked once per second per portal
|
||||||
|
on_extinguish = function(portalDef, anchorPos, orientation),
|
||||||
|
-- invoked when a portal is extinguished, including when the portal
|
||||||
|
-- it connected to was extinguished.
|
||||||
|
on_player_teleported = function(portalDef, player, oldPos, newPos),
|
||||||
|
-- invoked immediately after a player is teleported
|
||||||
|
on_ignite = function(portalDef, anchorPos, orientation)
|
||||||
|
-- invoked when a player or mesecon ignites a portal
|
||||||
|
on_created = function(portalDef, anchorPos, orientation)
|
||||||
|
-- invoked when a portal creates a remote twin, this is usually when
|
||||||
|
-- a player travels through a portal for the first time.
|
||||||
|
}
|
320
portal_examples.lua
Normal file
|
@ -0,0 +1,320 @@
|
||||||
|
--[[
|
||||||
|
|
||||||
|
Nether mod portal examples for Minetest
|
||||||
|
|
||||||
|
These portal API examples work independently of the Nether realm
|
||||||
|
and Nether portal. To try these examples, enable them in:
|
||||||
|
Minetest -> Settings -> All settings -> Mods -> nether
|
||||||
|
Once enabled, details on how to build them can be found in dungeon
|
||||||
|
chests in the book of portals.
|
||||||
|
|
||||||
|
--
|
||||||
|
|
||||||
|
Copyright (C) 2020 Treer
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for
|
||||||
|
any purpose with or without fee is hereby granted, provided that the
|
||||||
|
above copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||||
|
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
|
||||||
|
BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
|
||||||
|
OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||||
|
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
]]--
|
||||||
|
local S = nether.get_translator
|
||||||
|
|
||||||
|
|
||||||
|
local ENABLE_PORTAL_EXAMPLE_FLOATLANDS = false
|
||||||
|
local ENABLE_PORTAL_EXAMPLE_SURFACETRAVEL = false
|
||||||
|
|
||||||
|
-- Sets how far a Surface Portal will travel, measured in cells along the Moore curve,
|
||||||
|
-- which are about 117 nodes square each. Larger numbers will generally mean further distance
|
||||||
|
-- as-the-crow-flies, but this will not always be true due to the how the Moore curve
|
||||||
|
-- frequently doubles back upon itself.
|
||||||
|
-- This doubling-back prevents the surface portal from taking players easily accross the
|
||||||
|
-- map - the curve is 262144 cells long!
|
||||||
|
local SURFACE_TRAVEL_DISTANCE = 26
|
||||||
|
|
||||||
|
|
||||||
|
--=================================================--
|
||||||
|
-- Portal to the Floatlands, playable code example --
|
||||||
|
--==================================================--
|
||||||
|
local FLOATLANDS_ENABLED
|
||||||
|
local FLOATLAND_LEVEL = 1280
|
||||||
|
|
||||||
|
if minetest.settings:get_bool("nether_enable_portal_example_floatlands", ENABLE_PORTAL_EXAMPLE_FLOATLANDS) or ENABLE_PORTAL_EXAMPLE_FLOATLANDS then
|
||||||
|
|
||||||
|
local floatlands_flavortext = ""
|
||||||
|
if minetest.get_mapgen_setting("mg_name") == "v7" then
|
||||||
|
local mgv7_spflags = minetest.get_mapgen_setting("mgv7_spflags")
|
||||||
|
FLOATLANDS_ENABLED = mgv7_spflags ~= nil and mgv7_spflags:find("floatlands") ~= nil and mgv7_spflags:find("nofloatlands") == nil
|
||||||
|
FLOATLAND_LEVEL = minetest.get_mapgen_setting("mgv7_floatland_level") or 1280
|
||||||
|
|
||||||
|
if FLOATLANDS_ENABLED then
|
||||||
|
floatlands_flavortext = "\n\n " .. S("There is a floating land of hills and forests up there, over the edges of which is a perilous drop all the way back down to sea level. We have not found how far these pristine lands extend. I have half a mind to retire there one day.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
nether.register_portal("floatlands_portal", {
|
||||||
|
shape = nether.PortalShape_Platform,
|
||||||
|
frame_node_name = "default:ice",
|
||||||
|
wormhole_node_color = 7, -- 7 is white
|
||||||
|
particle_texture = {
|
||||||
|
name = "nether_particle_anim1.png",
|
||||||
|
animation = {
|
||||||
|
type = "vertical_frames",
|
||||||
|
aspect_w = 7,
|
||||||
|
aspect_h = 7,
|
||||||
|
length = 1,
|
||||||
|
},
|
||||||
|
scale = 1.5
|
||||||
|
},
|
||||||
|
title = S("Floatlands Portal"),
|
||||||
|
book_of_portals_pagetext = S([[Requiring 21 blocks of ice, and constructed in the shape of a 3 × 3 platform with walls, or like a bowl. A finished platform is 2 blocks high, and 5 blocks wide at the widest in both directions.
|
||||||
|
|
||||||
|
This portal is different to the others, rather than acting akin to a doorway it appears to the eye more like a small pool of water which can be stepped into. Upon setting foot in the portal we found ourselves at a tremendous altitude.@1]],
|
||||||
|
floatlands_flavortext),
|
||||||
|
|
||||||
|
is_within_realm = function(pos) -- return true if pos is inside the Nether
|
||||||
|
return pos.y > FLOATLAND_LEVEL - 200
|
||||||
|
end,
|
||||||
|
|
||||||
|
find_realm_anchorPos = function(surface_anchorPos, player_name)
|
||||||
|
-- TODO: Once paramat finishes adjusting the floatlands, implement a surface algorithm that finds land
|
||||||
|
local destination_pos = {x = surface_anchorPos.x ,y = FLOATLAND_LEVEL + 2, z = surface_anchorPos.z}
|
||||||
|
|
||||||
|
-- a y_factor of 0 makes the search ignore the altitude of the portals (as long as they are in the Floatlands)
|
||||||
|
local existing_portal_location, existing_portal_orientation = nether.find_nearest_working_portal("floatlands_portal", destination_pos, 10, 0)
|
||||||
|
if existing_portal_location ~= nil then
|
||||||
|
return existing_portal_location, existing_portal_orientation
|
||||||
|
else
|
||||||
|
return destination_pos
|
||||||
|
end
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
--==============================================--
|
||||||
|
-- Surface-travel portal, playable code example --
|
||||||
|
--==============================================--
|
||||||
|
|
||||||
|
-- These Moore Curve functions required by surface_portal's find_surface_anchorPos() will
|
||||||
|
-- be assigned later in this file.
|
||||||
|
local get_moore_distance -- will be function get_moore_distance(cell_count, x, y): integer
|
||||||
|
local get_moore_coords -- will be function get_moore_coords(cell_count, distance): pos2d
|
||||||
|
|
||||||
|
if minetest.settings:get_bool("nether_enable_portal_example_surfacetravel", ENABLE_PORTAL_EXAMPLE_SURFACETRAVEL) or ENABLE_PORTAL_EXAMPLE_SURFACETRAVEL then
|
||||||
|
|
||||||
|
nether.register_portal("surface_portal", {
|
||||||
|
shape = nether.PortalShape_Circular,
|
||||||
|
frame_node_name = "default:tinblock",
|
||||||
|
wormhole_node_name = "nether:portal_alt",
|
||||||
|
wormhole_node_color = 4, -- 4 is cyan
|
||||||
|
title = S("Surface Portal"),
|
||||||
|
book_of_portals_pagetext = S([[Requiring 16 blocks of tin and constructed in a circular fashion, a finished frame is seven blocks wide, seven blocks high, and stands vertically like a doorway.
|
||||||
|
|
||||||
|
These travel a distance along the ground, and even when constructed deep underground will link back up to the surface. They appear to favor a strange direction, with the exit portal linking back only for as long as the portal stays open — attempting to reopen a portal from the exit doorway leads to a new destination along this favored direction. It has stymied our ability to study the behavior of these portals because without constructing dual portals and keeping both open it's hard to step through more than one and still be able to return home.
|
||||||
|
|
||||||
|
Due to such difficulties, we never learned what determines the direction and distance where the matching twin portal will appear, and I have lost my friend and protégé. In cavalier youth and with little more than a rucksack, Coudreau has decided to follow the chain as far as it goes, and has not been seen since. Coudreau believes it works in epicycles, but I am not convinced. Still, I cling to the hope that one day the portal will open and Coudreau will step out from whichever place leads to this one, perhaps with an epic tale to tell.]]),
|
||||||
|
|
||||||
|
is_within_realm = function(pos)
|
||||||
|
-- Always return true, because these portals always just take you around the surface
|
||||||
|
-- rather than taking you to a different realm
|
||||||
|
return true
|
||||||
|
end,
|
||||||
|
|
||||||
|
find_realm_anchorPos = function(surface_anchorPos, player_name)
|
||||||
|
-- This function isn't needed, since this type of portal always goes to the surface
|
||||||
|
minetest.log("error" , "find_realm_anchorPos called for surface portal")
|
||||||
|
return {x=0, y=0, z=0}
|
||||||
|
end,
|
||||||
|
|
||||||
|
find_surface_anchorPos = function(realm_anchorPos, player_name)
|
||||||
|
-- A portal definition doesn't normally need to provide a find_surface_anchorPos() function,
|
||||||
|
-- since find_surface_target_y() will be used by default, but these portals travel around the
|
||||||
|
-- surface (following a Moore curve) so will be calculating a different x and z to realm_anchorPos.
|
||||||
|
|
||||||
|
local cellCount = 512
|
||||||
|
local maxDistFromOrigin = 30000 -- the world edges are at X=30927, X=−30912, Z=30927 and Z=−30912
|
||||||
|
|
||||||
|
-- clip realm_anchorPos to maxDistFromOrigin, and move the origin so that all values are positive
|
||||||
|
local x = math.min(maxDistFromOrigin, math.max(-maxDistFromOrigin, realm_anchorPos.x)) + maxDistFromOrigin
|
||||||
|
local z = math.min(maxDistFromOrigin, math.max(-maxDistFromOrigin, realm_anchorPos.z)) + maxDistFromOrigin
|
||||||
|
|
||||||
|
local divisor = math.ceil(maxDistFromOrigin * 2 / cellCount)
|
||||||
|
local distance = get_moore_distance(cellCount, math.floor(x / divisor + 0.5), math.floor(z / divisor + 0.5))
|
||||||
|
local destination_distance = (distance + SURFACE_TRAVEL_DISTANCE) % (cellCount * cellCount)
|
||||||
|
local moore_pos = get_moore_coords(cellCount, destination_distance)
|
||||||
|
local target_x = moore_pos.x * divisor - maxDistFromOrigin
|
||||||
|
local target_z = moore_pos.y * divisor - maxDistFromOrigin
|
||||||
|
|
||||||
|
local search_radius = divisor / 2 - 5 -- any portal within this area will do
|
||||||
|
|
||||||
|
-- a y_factor of 0 makes the search ignore the altitude of the portals
|
||||||
|
local existing_portal_location, existing_portal_orientation =
|
||||||
|
nether.find_nearest_working_portal("surface_portal", {x = target_x, y = 0, z = target_z}, search_radius, 0)
|
||||||
|
|
||||||
|
if existing_portal_location ~= nil then
|
||||||
|
-- use the existing portal that was found near target_x, target_z
|
||||||
|
return existing_portal_location, existing_portal_orientation
|
||||||
|
else
|
||||||
|
-- find a good location for the new portal, or if that isn't possible then at
|
||||||
|
-- least adjust the coords a little so portals don't line up in a grid
|
||||||
|
local adj_x, adj_z = 0, 0
|
||||||
|
|
||||||
|
-- Deterministically look for a location in the cell where get_spawn_level() can give
|
||||||
|
-- us a surface height, since nether.find_surface_target_y() works *much* better when
|
||||||
|
-- it can use get_spawn_level()
|
||||||
|
local prng = PcgRandom( -- seed the prng so that all portals for these Moore Curve coords will use the same random location
|
||||||
|
moore_pos.x * 65732 +
|
||||||
|
moore_pos.y * 729 +
|
||||||
|
minetest.get_mapgen_setting("seed") * 3
|
||||||
|
)
|
||||||
|
|
||||||
|
local attemptLimit = 15 -- how many attempts we'll make at finding a good location
|
||||||
|
for attempt = 1, attemptLimit do
|
||||||
|
adj_x = math.floor(prng:rand_normal_dist(-search_radius, search_radius, 2) + 0.5)
|
||||||
|
adj_z = math.floor(prng:rand_normal_dist(-search_radius, search_radius, 2) + 0.5)
|
||||||
|
if minetest.get_spawn_level == nil or minetest.get_spawn_level(target_x + adj_x, target_z + adj_z) ~= nil then
|
||||||
|
-- Found a location which will be at ground level - unless a player has built there.
|
||||||
|
-- Or this is MT 0.4 which does not have get_spawn_level(), so there's no point looking
|
||||||
|
-- at any further further random locations.
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local destination_pos = {x = target_x + adj_x, y = 0, z = target_z + adj_z}
|
||||||
|
destination_pos.y = nether.find_surface_target_y(destination_pos.x, destination_pos.z, "surface_portal", player_name)
|
||||||
|
|
||||||
|
return destination_pos
|
||||||
|
end
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
--=========================================--
|
||||||
|
-- Hilbert curve and Moore curve functions --
|
||||||
|
--=========================================--
|
||||||
|
|
||||||
|
-- These are space-filling curves, used by the surface_portal example as a way to determine where
|
||||||
|
-- to place portals. https://en.wikipedia.org/wiki/Moore_curve
|
||||||
|
|
||||||
|
|
||||||
|
-- Flip a quadrant on a diagonal axis
|
||||||
|
-- cell_count is the number of cells across the square is split into, and must be a power of 2
|
||||||
|
-- if flip_twice is true then pos does not change (even numbers of flips cancel out)
|
||||||
|
-- if flip_direction is true then the position is flipped along the \ diagonal
|
||||||
|
-- if flip_direction is false then the position is flipped along the / diagonal
|
||||||
|
local function hilbert_flip(cell_count, pos, flip_direction, flip_twice)
|
||||||
|
if not flip_twice then
|
||||||
|
if flip_direction then
|
||||||
|
pos.x = (cell_count - 1) - pos.x;
|
||||||
|
pos.y = (cell_count - 1) - pos.y;
|
||||||
|
end
|
||||||
|
|
||||||
|
local temp_x = pos.x;
|
||||||
|
pos.x = pos.y;
|
||||||
|
pos.y = temp_x;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function test_bit(cell_count, value, flag)
|
||||||
|
local bit_value = cell_count / 2
|
||||||
|
while bit_value > flag and bit_value >= 1 do
|
||||||
|
if value >= bit_value then value = value - bit_value end
|
||||||
|
bit_value = bit_value / 2
|
||||||
|
end
|
||||||
|
return value >= bit_value
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Converts (x,y) to distance
|
||||||
|
-- starts at bottom left corner, i.e. (0, 0)
|
||||||
|
-- ends at bottom right corner, i.e. (cell_count - 1, 0)
|
||||||
|
local function get_hilbert_distance (cell_count, x, y)
|
||||||
|
local distance = 0
|
||||||
|
local pos = {x=x, y=y}
|
||||||
|
local rx, ry
|
||||||
|
|
||||||
|
local s = cell_count / 2
|
||||||
|
while s > 0 do
|
||||||
|
|
||||||
|
if test_bit(cell_count, pos.x, s) then rx = 1 else rx = 0 end
|
||||||
|
if test_bit(cell_count, pos.y, s) then ry = 1 else ry = 0 end
|
||||||
|
|
||||||
|
local rx_XOR_ry = rx
|
||||||
|
if ry == 1 then rx_XOR_ry = 1 - rx_XOR_ry end -- XOR'd ry against rx
|
||||||
|
|
||||||
|
distance = distance + s * s * (2 * rx + rx_XOR_ry)
|
||||||
|
hilbert_flip(cell_count, pos, rx > 0, ry > 0);
|
||||||
|
|
||||||
|
s = math.floor(s / 2)
|
||||||
|
end
|
||||||
|
return distance;
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Converts distance to (x,y)
|
||||||
|
local function get_hilbert_coords(cell_count, distance)
|
||||||
|
local pos = {x=0, y=0}
|
||||||
|
local rx, ry
|
||||||
|
|
||||||
|
local s = 1
|
||||||
|
while s < cell_count do
|
||||||
|
rx = math.floor(distance / 2) % 2
|
||||||
|
ry = distance % 2
|
||||||
|
if rx == 1 then ry = 1 - ry end -- XOR ry with rx
|
||||||
|
|
||||||
|
hilbert_flip(s, pos, rx > 0, ry > 0);
|
||||||
|
pos.x = pos.x + s * rx
|
||||||
|
pos.y = pos.y + s * ry
|
||||||
|
distance = math.floor(distance / 4)
|
||||||
|
|
||||||
|
s = s * 2
|
||||||
|
end
|
||||||
|
return pos
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Converts (x,y) to distance
|
||||||
|
-- A Moore curve is a variation of the Hilbert curve that has the start and
|
||||||
|
-- end next to each other.
|
||||||
|
-- Top middle point is the start/end location
|
||||||
|
get_moore_distance = function(cell_count, x, y)
|
||||||
|
|
||||||
|
local quadLength = cell_count / 2
|
||||||
|
local quadrant = 1 - math.floor(y / quadLength)
|
||||||
|
if math.floor(x / quadLength) == 1 then quadrant = 3 - quadrant end
|
||||||
|
local flipDirection = x < quadLength
|
||||||
|
|
||||||
|
local pos = {x = x % quadLength, y = y % quadLength}
|
||||||
|
hilbert_flip(quadLength, pos, flipDirection, false)
|
||||||
|
|
||||||
|
return (quadrant * quadLength * quadLength) + get_hilbert_distance(quadLength, pos.x, pos.y)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Converts distance to (x,y)
|
||||||
|
-- A Moore curve is a variation of the Hilbert curve that has the start and
|
||||||
|
-- end next to each other.
|
||||||
|
-- Top middle point is the start/end location
|
||||||
|
get_moore_coords = function(cell_count, distance)
|
||||||
|
local quadLength = cell_count / 2
|
||||||
|
local quadDistance = quadLength * quadLength
|
||||||
|
local quadrant = math.floor(distance / quadDistance)
|
||||||
|
local flipDirection = distance * 2 < cell_count * cell_count
|
||||||
|
local pos = get_hilbert_coords(quadLength, distance % quadDistance)
|
||||||
|
hilbert_flip(quadLength, pos, flipDirection, false)
|
||||||
|
|
||||||
|
if quadrant >= 2 then pos.x = pos.x + quadLength end
|
||||||
|
if quadrant % 3 == 0 then pos.y = pos.y + quadLength end
|
||||||
|
|
||||||
|
return pos
|
||||||
|
end
|
25
settingtypes.txt
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# Travelling a short distance in the Nether can correspond to a much further distance on the surface.
|
||||||
|
#
|
||||||
|
# A factor of 10 might be a better value for Minetest, since there's no sprint, but ex-Minecraft players will be mathing for 8.
|
||||||
|
nether_fasttravel_factor (Nether fast-travel factor) int 8
|
||||||
|
|
||||||
|
# The likelyhood of finding a Book containing all the portal plans in a dungeon chest.
|
||||||
|
# Set to 0 to disable, or 10 to have it extremely common.
|
||||||
|
#
|
||||||
|
# (This value will be treated as 0 when the Nether portal is the only type of portal available, or when the help modpack is installed)
|
||||||
|
nether_portalBook_loot_weighting (Likelyhood of finding Book of Portals in dungeon chests) int 9
|
||||||
|
|
||||||
|
# Turn off to disable the Nether and Nether portal
|
||||||
|
nether_realm_enabled (Enable Nether realm & portal) bool true
|
||||||
|
|
||||||
|
# Enables the Floatlands portal api code example
|
||||||
|
nether_enable_portal_example_floatlands (Enable example portal: Floatlands) bool false
|
||||||
|
|
||||||
|
# Enables the Surface-travel portal api code example
|
||||||
|
nether_enable_portal_example_surfacetravel (Enable example portal: Surface-travel) bool false
|
||||||
|
|
||||||
|
[Nether depth]
|
||||||
|
#The depth where the Nether begins / the Nether ceiling
|
||||||
|
nether_depth_ymax (Upper limit of Nether) int -5000 -30000 32767
|
||||||
|
#The lower limit of the Nether must be at least 1000 lower than the upper limit, and more than 3000 lower is recommended.
|
||||||
|
nether_depth_ymin (Lower limit of Nether) int -11000 -32768 30000
|
BIN
sounds/nether_book_open.ogg
Normal file
BIN
sounds/nether_fumarole.ogg
Normal file
BIN
sounds/nether_lava_bubble.0.ogg
Normal file
BIN
sounds/nether_lava_bubble.ogg
Normal file
BIN
sounds/nether_lightstaff.ogg
Normal file
BIN
sounds/nether_portal_ambient.0.ogg
Normal file
BIN
sounds/nether_portal_ambient.ogg
Normal file
BIN
sounds/nether_portal_extinguish.ogg
Normal file
BIN
sounds/nether_portal_ignite.ogg
Normal file
BIN
sounds/nether_portal_ignition_failure.ogg
Normal file
BIN
sounds/nether_portal_teleport.ogg
Normal file
BIN
sounds/nether_rack_destroy.ogg
Normal file
BIN
textures/nether_basalt.png
Normal file
After Width: | Height: | Size: 678 B |
BIN
textures/nether_basalt_chiselled_side.png
Normal file
After Width: | Height: | Size: 597 B |
BIN
textures/nether_basalt_chiselled_top.png
Normal file
After Width: | Height: | Size: 594 B |
BIN
textures/nether_basalt_hewn.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
textures/nether_basalt_side.png
Normal file
After Width: | Height: | Size: 518 B |
BIN
textures/nether_book_background.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
textures/nether_book_close.png
Normal file
After Width: | Height: | Size: 961 B |
BIN
textures/nether_book_diagram_circular.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
textures/nether_book_diagram_platform.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
textures/nether_book_diagram_traditional.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
textures/nether_book_of_portals.png
Normal file
After Width: | Height: | Size: 542 B |
BIN
textures/nether_brick_compressed.png
Normal file
After Width: | Height: | Size: 342 B |
BIN
textures/nether_brick_cracked.png
Normal file
After Width: | Height: | Size: 290 B |
BIN
textures/nether_brick_deep.png
Normal file
After Width: | Height: | Size: 765 B |
BIN
textures/nether_geode.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
textures/nether_geode_glass.png
Normal file
After Width: | Height: | Size: 568 B |
BIN
textures/nether_glowstone_deep.png
Normal file
After Width: | Height: | Size: 666 B |
BIN
textures/nether_lava_crust_animated.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
textures/nether_lava_source_animated.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
textures/nether_lightstaff.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
textures/nether_nether_ingot.png
Normal file
After Width: | Height: | Size: 203 B |
BIN
textures/nether_nether_lump.png
Normal file
After Width: | Height: | Size: 164 B |
Before Width: | Height: | Size: 226 B After Width: | Height: | Size: 149 B |
BIN
textures/nether_particle_anim1.png
Normal file
After Width: | Height: | Size: 292 B |
BIN
textures/nether_particle_anim2.png
Normal file
After Width: | Height: | Size: 275 B |
BIN
textures/nether_particle_anim3.png
Normal file
After Width: | Height: | Size: 385 B |
BIN
textures/nether_particle_anim4.png
Normal file
After Width: | Height: | Size: 288 B |
Before Width: | Height: | Size: 382 B After Width: | Height: | Size: 1.5 KiB |
BIN
textures/nether_portal_alt.png
Normal file
After Width: | Height: | Size: 282 B |
BIN
textures/nether_portals_palette.png
Normal file
After Width: | Height: | Size: 875 B |
BIN
textures/nether_rack_deep.png
Normal file
After Width: | Height: | Size: 353 B |
BIN
textures/nether_smoke_puff.png
Normal file
After Width: | Height: | Size: 243 B |
BIN
textures/nether_tool_netheraxe.png
Normal file
After Width: | Height: | Size: 201 B |
BIN
textures/nether_tool_netherpick.png
Normal file
After Width: | Height: | Size: 223 B |
BIN
textures/nether_tool_nethershovel.png
Normal file
After Width: | Height: | Size: 231 B |
BIN
textures/nether_tool_nethersword.png
Normal file
After Width: | Height: | Size: 181 B |
395
tools.lua
Normal file
|
@ -0,0 +1,395 @@
|
||||||
|
--[[
|
||||||
|
|
||||||
|
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) -- was "#8088FF" or "#13F"
|
||||||
|
and not minetest.is_creative_enabled(user) then
|
||||||
|
-- The staff of Eternal Light wears out, to limit how much
|
||||||
|
-- a player can alter the nether with it.
|
||||||
|
itemstack:add_wear_by_uses(nether.lightstaff_uses)
|
||||||
|
end
|
||||||
|
return itemstack
|
||||||
|
end
|
||||||
|
})
|