diff --git a/mods/caverealms/biomes.lua b/mods/caverealms/biomes.lua index 6c432c74..5a914a8f 100644 --- a/mods/caverealms/biomes.lua +++ b/mods/caverealms/biomes.lua @@ -17,8 +17,8 @@ minetest.register_decoration({ place_on = "default:stone", fill_ratio = 10, biomes = asuna.features.cave.moss, - y_max = 0, - y_min = -31000, + y_max = -4096, + y_min = -10000, place_offset_y = -1, decoration = moss_cave_surface, flags = "all_floors,force_placement", @@ -32,8 +32,8 @@ minetest.register_decoration({ num_spawn_by = 8, fill_ratio = 0.000085, biomes = asuna.features.cave.moss, - y_max = -16, - y_min = -31000, + y_max = -4096, + y_min = -10000, place_offset_y = -1, schematic = spath .. "emerald_stalagmite.mts", flags = "all_floors,place_center_x,place_center_z", @@ -47,8 +47,8 @@ minetest.register_decoration({ num_spawn_by = 8, fill_ratio = 0.000085, biomes = asuna.features.cave.moss, - y_max = -16, - y_min = -31000, + y_max = -4096, + y_min = -10000, place_offset_y = -1, schematic = spath .. "emerald_stalactite.mts", flags = "all_ceilings,place_center_x,place_center_z", @@ -62,8 +62,8 @@ minetest.register_decoration({ num_spawn_by = 8, fill_ratio = 0.000085, biomes = asuna.features.cave.moss, - y_max = -16, - y_min = -31000, + y_max = -4096, + y_min = -10000, place_offset_y = -1, schematic = spath .. "sapphire_stalagmite.mts", flags = "all_floors,place_center_x,place_center_z", @@ -77,8 +77,8 @@ minetest.register_decoration({ num_spawn_by = 8, fill_ratio = 0.000085, biomes = asuna.features.cave.moss, - y_max = -16, - y_min = -31000, + y_max = -4096, + y_min = -10000, place_offset_y = -1, schematic = spath .. "sapphire_stalactite.mts", flags = "all_ceilings,place_center_x,place_center_z", @@ -90,8 +90,8 @@ minetest.register_decoration({ place_on = moss_cave_surface, fill_ratio = 0.00625, biomes = asuna.features.cave.moss, - y_max = 0, - y_min = -31000, + y_max = -4096, + y_min = -10000, decoration = "caverealms:glow_gem", flags = "all_floors", }) @@ -102,8 +102,8 @@ minetest.register_decoration({ place_on = "default:stone", fill_ratio = 0.0175, biomes = asuna.features.cave.moss, - y_max = 0, - y_min = -31000, + y_max = -4096, + y_min = -10000, height = 2, height_max = 4, decoration = "caverealms:glow_worm", @@ -122,8 +122,8 @@ minetest.register_decoration({ place_on = "default:stone", fill_ratio = 10, biomes = asuna.features.cave.fungal, - y_max = 0, - y_min = -31000, + y_max = -4096, + y_min = -10000, place_offset_y = -1, decoration = fungal_cave_surface, flags = "all_floors,force_placement", @@ -137,8 +137,8 @@ minetest.register_decoration({ num_spawn_by = 8, fill_ratio = 0.000085, biomes = asuna.features.cave.fungal, - y_max = -16, - y_min = -31000, + y_max = -4096, + y_min = -10000, place_offset_y = -1, schematic = spath .. "emerald_stalagmite.mts", flags = "all_floors,place_center_x,place_center_z", @@ -152,8 +152,8 @@ minetest.register_decoration({ num_spawn_by = 8, fill_ratio = 0.000085, biomes = asuna.features.cave.fungal, - y_max = -16, - y_min = -31000, + y_max = -4096, + y_min = -10000, place_offset_y = -1, schematic = spath .. "emerald_stalactite.mts", flags = "all_ceilings,place_center_x,place_center_z", @@ -167,8 +167,8 @@ minetest.register_decoration({ num_spawn_by = 8, fill_ratio = 0.000085, biomes = asuna.features.cave.fungal, - y_max = -16, - y_min = -31000, + y_max = -4096, + y_min = -10000, place_offset_y = -1, schematic = spath .. "amethyst_stalagmite.mts", flags = "all_floors,place_center_x,place_center_z", @@ -182,8 +182,8 @@ minetest.register_decoration({ num_spawn_by = 8, fill_ratio = 0.000085, biomes = asuna.features.cave.fungal, - y_max = -16, - y_min = -31000, + y_max = -4096, + y_min = -10000, place_offset_y = -1, schematic = spath .. "amethyst_stalactite.mts", flags = "all_ceilings,place_center_x,place_center_z", @@ -195,8 +195,8 @@ minetest.register_decoration({ place_on = fungal_cave_surface, fill_ratio = 0.0005, biomes = asuna.features.cave.fungal, - y_max = -16, - y_min = -31000, + y_max = -4096, + y_min = -10000, schematic = spath .. "shroom.mts", flags = "all_floors,place_center_x,place_center_z", }) @@ -207,8 +207,8 @@ minetest.register_decoration({ place_on = fungal_cave_surface, fill_ratio = 0.025, biomes = asuna.features.cave.fungal, - y_max = 0, - y_min = -31000, + y_max = -4096, + y_min = -10000, decoration = "caverealms:fungus", flags = "all_floors", }) @@ -219,8 +219,8 @@ minetest.register_decoration({ place_on = fungal_cave_surface, fill_ratio = 0.025, biomes = asuna.features.cave.fungal, - y_max = 0, - y_min = -31000, + y_max = -4096, + y_min = -10000, decoration = "caverealms:mycena", flags = "all_floors", }) @@ -231,8 +231,8 @@ minetest.register_decoration({ place_on = "default:stone", fill_ratio = 0.0175, biomes = asuna.features.cave.fungal, - y_max = 0, - y_min = -31000, + y_max = -4096, + y_min = -10000, height = 2, height_max = 4, decoration = "caverealms:glow_worm_green", @@ -253,8 +253,8 @@ minetest.register_decoration({ lacunarity = 0.69, }, biomes = asuna.features.cave.fungal, - y_max = 0, - y_min = -31000, + y_max = -4096, + y_min = -10000, place_offset_y = -1, height_max = 2, decoration = "ethereal:mushroom", @@ -267,8 +267,8 @@ minetest.register_decoration({ place_on = fungal_cave_surface, fill_ratio = 0.00095, biomes = asuna.features.cave.fungal, - y_max = 0, - y_min = -31000, + y_max = -4096, + y_min = -10000, decoration = { "flowers:mushroom_red", "flowers:mushroom_brown", @@ -294,8 +294,8 @@ minetest.register_decoration({ place_on = "default:stone", fill_ratio = 10, biomes = asuna.features.cave.coal, - y_max = 0, - y_min = -31000, + y_max = -4096, + y_min = -10000, place_offset_y = -1, decoration = desert_cave_surface, flags = "all_floors,force_placement", @@ -307,8 +307,8 @@ minetest.register_decoration({ place_on = desert_cave_surface, fill_ratio = 10, biomes = asuna.features.cave.coal, - y_max = 0, - y_min = -31000, + y_max = -4096, + y_min = -10000, place_offset_y = -2, decoration = "default:stone", flags = "all_floors,force_placement", @@ -326,8 +326,8 @@ minetest.register_decoration({ num_spawn_by = 8, fill_ratio = 0.000085, biomes = asuna.features.cave.coal, - y_max = -16, - y_min = -31000, + y_max = -4096, + y_min = -10000, place_offset_y = -1, schematic = spath .. "ruby_stalagmite.mts", flags = "all_floors,place_center_x,place_center_z", @@ -341,8 +341,8 @@ minetest.register_decoration({ num_spawn_by = 8, fill_ratio = 0.000085, biomes = asuna.features.cave.coal, - y_max = -16, - y_min = -31000, + y_max = -4096, + y_min = -10000, place_offset_y = -1, schematic = spath .. "ruby_stalactite.mts", flags = "all_ceilings,place_center_x,place_center_z", @@ -360,8 +360,8 @@ minetest.register_decoration({ num_spawn_by = 8, fill_ratio = 0.000085, biomes = asuna.features.cave.coal, - y_max = -16, - y_min = -31000, + y_max = -4096, + y_min = -10000, place_offset_y = -1, schematic = spath .. "amethyst_stalagmite.mts", flags = "all_floors,place_center_x,place_center_z", @@ -375,8 +375,8 @@ minetest.register_decoration({ num_spawn_by = 8, fill_ratio = 0.000085, biomes = asuna.features.cave.coal, - y_max = -16, - y_min = -31000, + y_max = -4096, + y_min = -10000, place_offset_y = -1, schematic = spath .. "amethyst_stalactite.mts", flags = "all_ceilings,place_center_x,place_center_z", @@ -388,8 +388,8 @@ minetest.register_decoration({ place_on = desert_cave_surface, fill_ratio = 0.1, biomes = asuna.features.cave.coal, - y_max = 0, - y_min = -31000, + y_max = -4096, + y_min = -10000, place_offset_y = -1, decoration = "caverealms:coal_dust", flags = "all_floors,force_placement", @@ -401,8 +401,8 @@ minetest.register_decoration({ place_on = desert_cave_surface, fill_ratio = 0.005, biomes = asuna.features.cave.coal, - y_max = 0, - y_min = -31000, + y_max = -4096, + y_min = -10000, place_offset_y = -1, decoration = "default:coalblock", flags = "all_floors,force_placement", @@ -414,8 +414,8 @@ minetest.register_decoration({ place_on = desert_cave_surface, fill_ratio = 0.01, biomes = asuna.features.cave.coal, - y_max = 0, - y_min = -31000, + y_max = -4096, + y_min = -10000, decoration = "caverealms:constant_flame", flags = "all_floors", }) @@ -432,8 +432,8 @@ minetest.register_decoration({ num_spawn_by = 8, fill_ratio = 0.001, biomes = asuna.features.cave.coal, - y_max = 0, - y_min = -31000, + y_max = -4096, + y_min = -10000, decoration = "default:cactus", flags = "all_floors", }) @@ -446,8 +446,8 @@ minetest.register_decoration({ place_on = "default:stone", fill_ratio = 10, biomes = asuna.features.cave.fire, - y_max = 0, - y_min = -31000, + y_max = -4096, + y_min = -10000, place_offset_y = -1, decoration = "caverealms:hot_cobble", flags = "all_floors,force_placement", @@ -467,8 +467,8 @@ minetest.register_decoration({ lacunarity = 0.7, }, biomes = asuna.features.cave.fire, - y_max = 0, - y_min = -31000, + y_max = -4096, + y_min = -10000, place_offset_y = -1, decoration = "default:obsidian", flags = "all_floors,force_placement", @@ -480,8 +480,8 @@ minetest.register_decoration({ place_on = "default:stone", fill_ratio = 10, biomes = asuna.features.cave.fire, - y_max = 0, - y_min = -31000, + y_max = -4096, + y_min = -10000, place_offset_y = -1, decoration = "caverealms:hot_cobble", flags = "all_ceilings,force_placement", @@ -493,8 +493,8 @@ minetest.register_decoration({ place_on = "caverealms:hot_cobble", fill_ratio = 0.085, biomes = asuna.features.cave.fire, - y_max = 0, - y_min = -31000, + y_max = -4096, + y_min = -10000, decoration = "caverealms:constant_flame", flags = "all_floors", }) @@ -516,8 +516,8 @@ abdecor.register_advanced_decoration("caverealms_fire_cave_lava",{ }, sidelen = 80, fill_ratio = 0.065, - y_max = -8, - y_min = -31000, + y_max = -4096, + y_min = -10000, flags = "all_floors", }, fn = function(mapgen) @@ -567,8 +567,8 @@ minetest.register_decoration({ place_on = "default:stone", fill_ratio = 10, biomes = asuna.features.cave.dry, - y_max = 0, - y_min = -31000, + y_max = -4096, + y_min = -10000, place_offset_y = -1, decoration = dry_cave_surface, flags = "all_floors,force_placement", @@ -582,8 +582,8 @@ minetest.register_decoration({ num_spawn_by = 8, fill_ratio = 0.000085, biomes = asuna.features.cave.dry, - y_max = -16, - y_min = -31000, + y_max = -4096, + y_min = -10000, place_offset_y = -1, schematic = spath .. "ruby_stalagmite.mts", flags = "all_floors,place_center_x,place_center_z", @@ -597,8 +597,8 @@ minetest.register_decoration({ num_spawn_by = 8, fill_ratio = 0.000085, biomes = asuna.features.cave.dry, - y_max = -16, - y_min = -31000, + y_max = -4096, + y_min = -10000, place_offset_y = -1, schematic = spath .. "ruby_stalactite.mts", flags = "all_ceilings,place_center_x,place_center_z", @@ -612,8 +612,8 @@ minetest.register_decoration({ num_spawn_by = 8, fill_ratio = 0.000085, biomes = asuna.features.cave.dry, - y_max = -16, - y_min = -31000, + y_max = -4096, + y_min = -10000, place_offset_y = -1, schematic = spath .. "amethyst_stalagmite.mts", flags = "all_floors,place_center_x,place_center_z", @@ -627,8 +627,8 @@ minetest.register_decoration({ num_spawn_by = 8, fill_ratio = 0.000085, biomes = asuna.features.cave.dry, - y_max = -16, - y_min = -31000, + y_max = -4096, + y_min = -10000, place_offset_y = -1, schematic = spath .. "amethyst_stalactite.mts", flags = "all_ceilings,place_center_x,place_center_z", @@ -640,8 +640,8 @@ minetest.register_decoration({ place_on = dry_cave_surface, fill_ratio = 0.01, biomes = asuna.features.cave.dry, - y_max = 0, - y_min = -31000, + y_max = -4096, + y_min = -10000, decoration = "caverealms:spike", flags = "all_floors", }) @@ -652,8 +652,8 @@ minetest.register_decoration({ place_on = "default:stone", fill_ratio = 0.01, biomes = asuna.features.cave.dry, - y_max = 0, - y_min = -31000, + y_max = -4096, + y_min = -10000, height = 2, height_max = 3, decoration = "caverealms:fire_vine", diff --git a/mods/caverealms/config.lua b/mods/caverealms/config.lua index 5a5f9cc8..73f092c9 100644 --- a/mods/caverealms/config.lua +++ b/mods/caverealms/config.lua @@ -20,8 +20,8 @@ local function setting(stype, name, default) end --generation settings -setting("number", "ymin", -33000) --bottom realm limit -setting("number", "ymax", -1500) --top realm limit +setting("number", "ymin", -10000) --bottom realm limit +setting("number", "ymax", -4096) --top realm limit setting("number", "tcave", 0.75) --cave threshold --decoration chances @@ -30,7 +30,7 @@ setting("number", "stalcha", 0.003) --chance of stalactites setting("number", "h_lag", 8) --max height for stalagmites setting("number", "h_lac", 8) --...stalactites -setting("number", "crystal", 0.0002) --chance of glow crystal formations +setting("number", "crystal", 0.0002) --chance of glow crystal formations setting("number", "h_cry", 8) --max height of glow crystals setting("number", "h_clac", 8) --max height of glow crystal stalactites @@ -43,8 +43,8 @@ setting("number", "icicha", 0.035) --chance of icicles setting("number", "flacha", 0.04) --chance of constant flames --realm limits for Dungeon Masters' Lair -setting("number", "dm_top", -14000) --upper limit -setting("number", "dm_bot", -16000) --lower limit +setting("number", "dm_top", -8000) --upper limit +setting("number", "dm_bot", -10000) --lower limit --should DMs spawn in DM Lair? setting("bool", "dm_spawn", true) diff --git a/mods/cloudlands/.luacheckrc b/mods/cloudlands/.luacheckrc new file mode 100644 index 00000000..d37fbe85 --- /dev/null +++ b/mods/cloudlands/.luacheckrc @@ -0,0 +1,28 @@ +unused_args = false +allow_defined_top = true + +globals = { + "cloudlands" +} + +read_globals = { + "core", + "default", + "biomeinfo", + "schemlib", + "nether", + "DIR_DELIM", + "intllib", + "ItemStack", + "math.hypot", + "minetest", + "PcgRandom", + "PseudoRandom", + table = { fields = { "copy", "getn" } }, + "vector", + "file_exists", + "dump", + "VoxelArea", + "VoxelManip", +} + diff --git a/mods/cloudlands/LICENSE.txt b/mods/cloudlands/LICENSE.txt new file mode 100644 index 00000000..99282871 --- /dev/null +++ b/mods/cloudlands/LICENSE.txt @@ -0,0 +1,26 @@ +Cloudlands mapgen +~~~~~~~~~~~~~~~~~ + + Copyright © 2018 Treer + + A more permissive license may be available by request. + + For simplicity this Minetest mod is licensed with the same + license as Minetest - "GNU LGPL" version 2.1 or later. + + +GNU LGPL information +-------------------- + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You can receive a copy of the GNU Lesser General Public License from + http://www.gnu.org/ \ No newline at end of file diff --git a/mods/cloudlands/README.md b/mods/cloudlands/README.md new file mode 100644 index 00000000..f5663320 --- /dev/null +++ b/mods/cloudlands/README.md @@ -0,0 +1,56 @@ +

+ +

+ +# Hallelujah Mountains for Minetest + + + +[![License](https://img.shields.io/badge/license-LGPLv2.1%2B-blue.svg)](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html) + +AKA Cloudlands — giant rocks floating suspended in magnetic eddies. Now with giant trees + +This can be run with or without a normal terrain mapgen, allowing pure skylands or an exotic addition to the landscape. + +Many Configurable settings, including the altitude islands appear, and the ability to restrict the areas they spawn. + +It's a single file with no hard dependencies for easy integration into modpacks or games, e.g. as an exotic biome. + +Hallelujah Mountains [maps](https://i.imgur.com/2SkoAyB.png) can be generated by [Amidstest](https://forum.minetest.net/viewtopic.php?t=19869), provided you know the seed of the world. The islands are not homogenous — there will be areas without any, while smaller islands tend to follow eddy fields, often emanating out from much larger and rarer islands. + +**Mod dependencies:** None. + +**Minetest version:** v5.0+ + +**Recommended mods:** +* [Vines](https://forum.minetest.net/viewtopic.php?t=2344), or something that contains vines, such as plantlife_modpack or MineClone2. These will grow off the sides of the islands, climate permitting, allowing you to climb, and looking picturesque! +* [Nether](https://forum.minetest.net/viewtopic.php?t=5790) enables players to build portals to Hallelujah Mountains, allowing secret entrances into floating kingdoms, or the island altitude to be configured way up high while still having a means to get there. Stones to build these portals can only be found in the Nether. +* Extra ways to explore + * [Bridger](https://forum.minetest.net/viewtopic.php?t=18243) or [Bridges](https://forum.minetest.net/viewtopic.php?t=3488) if you want to build nice wooden bridges between islands + * [Airboat](https://github.com/paramat/airboat) to sail the high clouds on a small airship + * [Flying carpet](https://forum.minetest.net/viewtopic.php?t=12263) + * [Jetpack, wings, etc.](https://forum.minetest.net/viewtopic.php?t=11257) + * [Hang-glider / Elytra](https://forum.minetest.net/viewtopic.php?t=21425) + * Teleporter mods, balloon mods, etc. + +**Ores tip:** If you decide ores like iron and diamond should be obtainable from island stone, and are playing Minetest Game, you can go into Settings->Advanced Settings->Mods->cloudlands, set the "Altitude of Islands" to be 1300 (or higher), and Enable "Generate ores inside islands". This works because Minetest Game also generates the deep-underground ores in stone above 1025 to ensure they can be found in mgV7 Floatlands. Ores can be added in other games or at other altitudes by adding adjusted copies of the game's calls to minetest.register_ore(). + +**Video:** [Minetest Mod Review: Cloudlands](http://www.nathansalapat.com/minetest/cloudlands) + +**Forum:** [link](https://forum.minetest.net/viewtopic.php?t=20257) + +**Screenshots:** + +[![default](https://i.imgur.com/jwHN6Sd.jpg)](http://panoramas.minetest.land/IslandMoonlight.html) + +![default](https://i.imgur.com/PUjjaIK.jpg) + +![default](https://i.imgur.com/xn9UING.jpg) + +![default](https://i.imgur.com/SeYMfZv.jpg) + +![default](https://i.imgur.com/wBLEFWn.jpg) + +![default](https://i.imgur.com/A6vDDGc.jpg) + + diff --git a/mods/cloudlands/bower.json b/mods/cloudlands/bower.json new file mode 100644 index 00000000..1d1c97f0 --- /dev/null +++ b/mods/cloudlands/bower.json @@ -0,0 +1,21 @@ +{ + "name": "cloudlands", + "description": "Hallelujah Mountains for Minetest (runs with or without a normal terrain mapgen)", + "keywords": [ + "cloudlands", "Hallelujah Mountains", "floatlands", "skylands", "islands", "mapgen", "fantasy", "fly" + ], + "homepage": "https://github.com/Treer/cloudlands", + "forum": "https://forum.minetest.net/viewtopic.php?t=20257", + "screenshots": [ + "https://i.imgur.com/A6vDDGc.jpg", + "https://i.imgur.com/PUjjaIK.jpg", + "https://i.imgur.com/Zty57dy.jpg", + "https://i.imgur.com/wBLEFWn.jpg", + "https://i.imgur.com/xn9UING.jpg", + "https://i.imgur.com/rCtczAK.jpg" + ], + "authors": [ + "Treer" + ], + "license": "LGPL-2.1-or-later" +} diff --git a/mods/cloudlands/cloudlands.lua b/mods/cloudlands/cloudlands.lua new file mode 100644 index 00000000..1d59c4b5 --- /dev/null +++ b/mods/cloudlands/cloudlands.lua @@ -0,0 +1,2967 @@ +local ALTITUDE = 1500 -- average altitude of islands +local ALTITUDE_AMPLITUDE = 1200 -- rough island altitude variance (plus or minus) +local GENERATE_ORES = true -- set to true for island core stone to contain patches of dirt and sand etc. +local LOWLAND_BIOMES = false or -- If true then determine an island's biome using the biome at altitude "LOWLAND_BIOME_ALTITUDE" + minetest.get_modpath("ethereal") ~= nil -- Ethereal has an alpine biome above altitude 40, so default to lowland biomes +local LOWLAND_BIOME_ALTITUDE = 10 -- Higher than beaches, lower than mountains (See LOWLAND_BIOMES) +local VINE_COVERAGE = 0.3 -- set to 0 to turn off vines +local REEF_RARITY = 0.015 -- Chance of a viable island having a reef or atoll +local TREE_RARITY = 0.08 -- Chance of a viable island having a giant tree growing out of it +local PORTAL_RARITY = 0.04 -- Chance of a viable island having some ancient portalstone on it (If portals API available and ENABLE_PORTALS is true) +local BIOLUMINESCENCE = true or -- Allow giant trees variants which have glowing parts + minetest.get_modpath("glowtest") ~= nil or + minetest.get_modpath("ethereal") ~= nil or + minetest.get_modpath("glow") ~= nil or + minetest.get_modpath("nsspf") ~= nil or + minetest.get_modpath("nightscape") ~= nil or + minetest.get_modpath("moonflower") ~= nil -- a world using any of these mods is OK with bioluminescence +local ENABLE_PORTALS = true -- Whether to allow players to build portals to islands. Portals require the Nether mod. +local EDDYFIELD_SIZE = 1 -- size of the "eddy field-lines" that smaller islands follow +local ISLANDS_SEED = 1000 -- You only need to change this if you want to try different island layouts without changing the map seed + +-- Some lists of known node aliases (any nodes which can't be found won't be used). +local NODENAMES_STONE = {"mapgen_stone", "mcl_core:stone", "default:stone", "main:stone"} +local NODENAMES_WATER = {"mapgen_water_source", "mcl_core:water_source", "default:water_source", "main:water"} +local NODENAMES_ICE = {"mapgen_ice", "mcl_core:ice", "pedology:ice_white", "default:ice", "main:ice"} +local NODENAMES_GRAVEL = {"mapgen_gravel", "mcl_core:gravel", "default:gravel", "main:gravel"} +local NODENAMES_GRASS = {"mapgen_dirt_with_grass", "mcl_core:dirt_with_grass", "default:dirt_with_grass", "main:grass"} -- currently only used with games that don't register biomes, e.g. Hades Revisted +local NODENAMES_DIRT = {"mapgen_dirt", "mcl_core:dirt", "default:dirt", "main:dirt"} -- currently only used with games that don't register biomes, e.g. Hades Revisted +local NODENAMES_SILT = {"mapgen_silt", "default:silt", "aotearoa:silt", "darkage:silt", "mapgen_sand", "mcl_core:sand", "default:sand", "main:sand"} -- silt isn't a thing yet, but perhaps one day it will be. Use sand for the bottom of ponds in the meantime. +local NODENAMES_VINES = {"mcl_core:vine", "vines:side_end", "ethereal:vine", "main:vine"} -- ethereal vines don't grow, so only select that if there's nothing else. +local NODENAMES_HANGINGVINE = {"vines:vine_end"} +local NODENAMES_HANGINGROOT = {"vines:root_end"} +local NODENAMES_TREEWOOD = {"mcl_core:tree", "default:tree", "mapgen_tree", "main:tree"} +local NODENAMES_TREELEAVES = {"mcl_core:leaves", "default:leaves", "mapgen_leaves", "main:leaves"} +local NODENAMES_FRAMEGLASS = {"xpanes:obsidian_pane_flat", "xpanes:pane_flat", "default:glass", "xpanes:pane_natural_flat", "mcl_core:glass", "walls:window"} +local NODENAMES_WOOD = {"default:wood", "mcl_core:wood", "main:wood"} + +local MODNAME = minetest.get_current_modname() +local VINES_REQUIRED_HUMIDITY = 49 +local VINES_REQUIRED_TEMPERATURE = 40 +local ICE_REQUIRED_TEMPERATURE = 8 + +local DEBUG = false -- dev logging +local DEBUG_GEOMETRIC = false -- turn off noise from island shapes +local DEBUG_SKYTREES = false -- dev logging + +-- OVERDRAW can be set to 1 to cause a y overdraw of one node above the chunk, to avoid creating a dirt "surface" +-- at the top of the chunk that trees mistakenly grow on when the chunk is decorated. +-- However, it looks like that tree problem has been solved by either engine or biome updates, and overdraw causes +-- it's own issues (e.g. nodeId_top not getting set correctly), so I'm leaving overdraw off (i.e. zero) until I +-- notice problems requiring it. +local OVERDRAW = 0 + +local S = minetest.get_translator(MODNAME) + +cloudlands = {} -- API functions can be accessed via this global: + -- cloudlands.get_island_details(minp, maxp) -- returns an array of island-information-tables, y is ignored. + -- cloudlands.find_nearest_island(x, z, search_radius) -- returns a single island-information-table, or nil + -- cloudlands.get_height_at(x, z, [island-information-tables]) -- returns (y, isWater), or nil if no island here + +cloudlands.coreTypes = { + { + territorySize = 200, + coresPerTerritory = 3, + radiusMax = 96, + depthMax = 50, + thicknessMax = 8, + frequency = 0.1, + pondWallBuffer = 0.03, + requiresNexus = true, + exclusive = false + }, + { + territorySize = 60, + coresPerTerritory = 1, + radiusMax = 40, + depthMax = 40, + thicknessMax = 4, + frequency = 0.1, + pondWallBuffer = 0.06, + requiresNexus = false, + exclusive = true + }, + { + territorySize = 30, + coresPerTerritory = 3, + radiusMax = 16, -- I feel this and depthMax should be bigger, say 18, and territorySize increased to 34 to match, but I can't change it any more or existing worlds will mismatch along previously emerged chunk boundaries + depthMax = 16, + thicknessMax = 2, + frequency = 0.1, + pondWallBuffer = 0.11, -- larger values will make ponds smaller and further from island edges, so it should be as low as you can get it without the ponds leaking over the edge. A small leak-prone island is at (3160, -2360) on seed 1 + requiresNexus = false, + exclusive = true + } +} + +if minetest.get_biome_data == nil then error(MODNAME .. " requires Minetest v5.0 or greater", 0) end + +local function fromSettings(settings_name, default_value) + local result + if type(default_value) == "number" then + result = tonumber(minetest.settings:get(settings_name) or default_value) + elseif type(default_value) == "boolean" then + result = minetest.settings:get_bool(settings_name, default_value) + end + return result +end +-- override any settings with user-specified values before these values are needed +ALTITUDE = fromSettings(MODNAME .. "_altitude", ALTITUDE) +ALTITUDE_AMPLITUDE = fromSettings(MODNAME .. "_altitude_amplitude", ALTITUDE_AMPLITUDE) +GENERATE_ORES = fromSettings(MODNAME .. "_generate_ores", GENERATE_ORES) +VINE_COVERAGE = fromSettings(MODNAME .. "_vine_coverage", VINE_COVERAGE * 100) / 100 +LOWLAND_BIOMES = fromSettings(MODNAME .. "_use_lowland_biomes", LOWLAND_BIOMES) +TREE_RARITY = fromSettings(MODNAME .. "_giant_tree_rarety", TREE_RARITY * 100) / 100 +BIOLUMINESCENCE = fromSettings(MODNAME .. "_bioluminescence", BIOLUMINESCENCE) +ENABLE_PORTALS = fromSettings(MODNAME .. "_enable_portals", ENABLE_PORTALS) + +local noiseparams_eddyField = { + offset = -1, + scale = 2, + spread = {x = 350 * EDDYFIELD_SIZE, y = 350 * EDDYFIELD_SIZE, z= 350 * EDDYFIELD_SIZE}, + seed = ISLANDS_SEED, --WARNING! minetest.get_perlin() will add the server map's seed to this value + octaves = 2, + persistence = 0.7, + lacunarity = 2.0, +} +local noiseparams_heightMap = { + offset = 0, + scale = ALTITUDE_AMPLITUDE, + spread = {x = 160, y = 160, z= 160}, + seed = ISLANDS_SEED, --WARNING! minetest.get_perlin() will add the server map's seed to this value + octaves = 3, + persistence = 0.5, + lacunarity = 2.0, +} +local DENSITY_OFFSET = 0.7 +local noiseparams_density = { + offset = DENSITY_OFFSET, + scale = .3, + spread = {x = 25, y = 25, z= 25}, + seed = 1000, --WARNING! minetest.get_perlin() will add the server map's seed to this value + octaves = 4, + persistence = 0.5, + lacunarity = 2.0, +} +local SURFACEMAP_OFFSET = 0.5 +local noiseparams_surfaceMap = { + offset = SURFACEMAP_OFFSET, + scale = .5, + spread = {x = 40, y = 40, z= 40}, + seed = ISLANDS_SEED, --WARNING! minetest.get_perlin() will add the server map's seed to this value + octaves = 4, + persistence = 0.5, + lacunarity = 2.0, +} +local noiseparams_skyReef = { + offset = .3, + scale = .9, + spread = {x = 3, y = 3, z= 3}, + seed = 1000, + octaves = 2, + persistence = 0.5, + lacunarity = 2.0, +} + +local noiseAngle = -15 --degrees to rotate eddyField noise, so that the vertical and horizontal tendencies are off-axis +local ROTATE_COS = math.cos(math.rad(noiseAngle)) +local ROTATE_SIN = math.sin(math.rad(noiseAngle)) + +local noise_eddyField +local noise_heightMap +local noise_density +local noise_surfaceMap +local noise_skyReef + +local worldSeed +local nodeId_ignore = minetest.CONTENT_IGNORE +local nodeId_air +local nodeId_stone +local nodeId_grass +local nodeId_dirt +local nodeId_water +local nodeId_ice +local nodeId_silt +local nodeId_gravel +local nodeId_vine +local nodeName_vine +local nodeName_ignore = minetest.get_name_from_content_id(nodeId_ignore) + +local REQUIRED_DENSITY = 0.4 + +local randomNumbers = {} -- array of 0-255 random numbers with values between 0 and 1 (inclusive) +local data = {} -- reuse the massive VoxelManip memory buffers instead of creating on every on_generate() +local biomes = {} + +-- optional region specified in settings to restrict islands too +local region_restrictions = false +local region_min_x, region_min_z, region_max_x, region_max_z = -32000, -32000, 32000, 32000 + +-- optional biomes specified in settings to restrict islands too +local limit_to_biomes = nil +local limit_to_biomes_altitude = nil + +--[[============================== + Math functions + ==============================]]-- + +-- avoid having to perform table lookups each time a common math function is invoked +local math_min, math_max, math_floor, math_sqrt, math_cos, math_sin, math_abs, math_pow, PI = math.min, math.max, math.floor, math.sqrt, math.cos, math.sin, math.abs, math.pow, math.pi + +local function clip(value, minValue, maxValue) + if value <= minValue then + return minValue + elseif value >= maxValue then + return maxValue + else + return value + end +end + +local function round(value) + return math_floor(0.5 + value) +end + +--[[============================== + Interop functions + ==============================]]-- + +local get_heat, get_humidity = minetest.get_heat, minetest.get_humidity + +local biomeinfoAvailable = minetest.get_modpath("biomeinfo") ~= nil and minetest.global_exists("biomeinfo") +local isMapgenV6 = minetest.get_mapgen_setting("mg_name") == "v6" +if isMapgenV6 then + if not biomeinfoAvailable then + -- The biomeinfo mod by Wuzzy can be found at https://repo.or.cz/minetest_biomeinfo.git + minetest.log("warning", MODNAME .. " detected mapgen v6: Full mapgen v6 support requires adding the biomeinfo mod.") + else + get_heat = function(pos) + return biomeinfo.get_v6_heat(pos) * 100 + end + get_humidity = function(pos) + return biomeinfo.get_v6_humidity(pos) * 100 + end + end +end + +local interop = {} +-- returns the id of the first nodename in the list that resolves to a node id, or nodeId_ignore if not found +interop.find_node_id = function (node_contender_names) + local result = nodeId_ignore + for _,contenderName in ipairs(node_contender_names) do + + local nonAliasName = minetest.registered_aliases[contenderName] or contenderName + if minetest.registered_nodes[nonAliasName] ~= nil then + result = minetest.get_content_id(nonAliasName) + end + + --if DEBUG then minetest.log("info", contenderName .. " returned " .. result .. " (" .. minetest.get_name_from_content_id(result) .. ")") end + if result ~= nodeId_ignore then return result end + end + return result +end + +-- returns the name of the first nodename in the list that resolves to a node id, or 'ignore' if not found +interop.find_node_name = function (node_contender_names) + return minetest.get_name_from_content_id(interop.find_node_id(node_contender_names)) +end + +interop.get_first_element_in_table = function(tbl) + for k,v in pairs(tbl) do return v end + return nil +end + +-- returns the top-texture name of the first nodename in the list that's a registered node, or nil if not found +interop.find_node_texture = function (node_contender_names) + local result = nil + local nodeName = minetest.get_name_from_content_id(interop.find_node_id(node_contender_names)) + if nodeName ~= nil then + local node = minetest.registered_nodes[nodeName] + if node ~= nil then + result = node.tiles + if type(result) == "table" then result = result["name"] or interop.get_first_element_in_table(result) end -- incase it's not a string + if type(result) == "table" then result = result["name"] or interop.get_first_element_in_table(result) end -- incase multiple tile definitions + end + end + return result +end + +-- returns the node name of the clone node. +interop.register_clone = function(node_name, clone_name) + local node = minetest.registered_nodes[node_name] + if node == nil then + minetest.log("error", "cannot clone " .. node_name) + return nil + else + if clone_name == nil then clone_name = MODNAME .. ":" .. string.gsub(node.name, ":", "_") end + if minetest.registered_nodes[clone_name] == nil then + if DEBUG then minetest.log("info", "attempting to register: " .. clone_name) end + local clone = {} + for key, value in pairs(node) do clone[key] = value end + clone.name = clone_name + minetest.register_node(clone_name, clone) + --minetest.log("info", clone_name .. " id: " .. minetest.get_content_id(clone_name)) + --minetest.log("info", clone_name .. ": " .. dump(minetest.registered_nodes[clone_name])) + end + return clone_name + end +end + +-- converts "modname:nodename" into (modname, nodename), if no colon is found then modname is nil +interop.split_nodename = function(nodeName) + local result_modname = nil + local result_nodename = nodeName + + local pos = nodeName:find(':') + if pos ~= nil then + result_modname = nodeName:sub(0, pos - 1) + result_nodename = nodeName:sub(pos + 1) + end + return result_modname, result_nodename +end + +-- returns a unique id for the biome, normally this is numeric but with mapgen v6 it can be a string name. +interop.get_biome_key = function(pos) + if isMapgenV6 and biomeinfoAvailable then + return biomeinfo.get_v6_biome(pos) + else + return minetest.get_biome_data(pos).biome + end +end + +-- returns true if filename is a file that exists. +interop.file_exists = function(filename) + local f = io.open(filename, "r") + if f == nil then + return false + else + f:close() + return true + end +end + +-- returns a written book item (technically an item stack), or nil if no books mod available +interop.write_book = function(title, author, text, description) + + local stackName_writtenBook + if minetest.get_modpath("mcl_books") then + stackName_writtenBook = "mcl_books:written_book" + text = title .. "\n\n" .. text -- MineClone2 books doen't show a title (or author) + + elseif minetest.get_modpath("book") ~= nil then + stackName_writtenBook = "book:book_written" + text = "\n\n" .. text -- Crafter books put the text immediately under the title + + elseif minetest.get_modpath("default") ~= nil then + stackName_writtenBook = "default:book_written" + + else + return nil + end + + local book_itemstack = ItemStack(stackName_writtenBook) + local book_data = {} + book_data.title = title + book_data.text = text + book_data.owner = author + book_data.author = author + book_data.description = description + book_data.page = 1 + book_data.page_max = 1 + book_data.generation = 0 + book_data["book.book_title"] = title -- Crafter book title + book_data["book.book_text"] = text -- Crafter book text + + book_itemstack:get_meta():from_table({fields = book_data}) + + return book_itemstack +end + +--[[============================== + Portals + ==============================]]-- + +local addDetail_ancientPortal = nil + +if ENABLE_PORTALS and minetest.get_modpath("nether") ~= nil and minetest.global_exists("nether") and nether.register_portal ~= nil then + -- The Portals API is available + -- Register a player-buildable portal to Hallelujah Mountains. + + + -- returns a position on the island which is suitable for a portal to be placed, or nil if none can be found + -- player_name is optional, allowing a player to spawn a remote portal in their own protected areas. + local function find_potential_portal_location_on_island(island_info, player_name) + + local result = nil + + if island_info ~= nil then + local searchRadius = island_info.radius * 0.6 -- islands normally don't reach their full radius, and lets not put portals too near the edge + local coreList = cloudlands.get_island_details( + {x = island_info.x - searchRadius, z = island_info.z - searchRadius}, + {x = island_info.x + searchRadius, z = island_info.z + searchRadius} + ) + + -- Deterministically sample the island for a low location that isn't water. + -- Seed the prng so this function always returns the same coords for the island + local prng = PcgRandom(island_info.x * 65732 + island_info.z * 729 + minetest.get_mapgen_setting("seed") * 3) + local positions = {} + + for attempt = 1, 15 do -- how many attempts we'll make at finding a good location + local angle = (prng:next(0, 10000) / 10000) * 2 * PI + local distance = math_sqrt(prng:next(0, 10000) / 10000) * searchRadius + if attempt == 1 then distance = 0 end -- Always sample the middle of the island, as it's the safest fallback location + local x = round(island_info.x + math_cos(angle) * distance) + local z = round(island_info.z + math_sin(angle) * distance) + local y, isWater = cloudlands.get_height_at(x, z, coreList) + if y ~= nil then + local weight = 0 + if not isWater then weight = weight + 1 end -- avoid putting portals in ponds + if y >= island_info.y + ALTITUDE then weight = weight + 2 end -- avoid putting portals down the sides of eroded cliffs + positions[#positions + 1] = {x = x, y = y + 1, z = z, weight = weight} + end + end + + -- Order the locations by how good they are + local compareFn = function(pos_a, pos_b) + if pos_a.weight > pos_b.weight then return true end + if pos_a.weight == pos_b.weight and pos_a.y < pos_b.y then return true end -- I can't justify why I think lower positions are better. I'm imagining portals nested in valleys rather than on ridges. + return false + end + table.sort(positions, compareFn) + + -- nether.volume_is_natural() was deprecated in favor of nether.volume_is_natural_and_unprotected() + local volume_is_natural_and_unprotected = nether.volume_is_natural_and_unprotected or nether.volume_is_natural + + -- Now the locations are sorted by how good they are, find the first/best that doesn't + -- grief a player build. + -- Ancient Portalstone has is_ground_content set to true, so we won't have to worry about old/broken + -- portal frames interfering with the results of nether.volume_is_natural_and_unprotected() + for _, position in ipairs(positions) do + -- Unfortunately, at this point we don't know the orientation of the portal, so use worst case + local minp = {x = position.x - 2, y = position.y, z = position.z - 2} + local maxp = {x = position.x + 3, y = position.y + 4, z = position.z + 3} + if volume_is_natural_and_unprotected(minp, maxp, player_name) then + result = position + break + end + end + end + + return result + end + + + -- returns nil if no suitable location could be found, otherwise returns (portal_pos, island_info) + -- player_name is optional, allowing a player to spawn a remote portal in their own protected areas. + local function find_nearest_island_location_for_portal(surface_x, surface_z, player_name) + + local result = nil + + local island = cloudlands.find_nearest_island(surface_x, surface_z, 75) + if island == nil then island = cloudlands.find_nearest_island(surface_x, surface_z, 150) end + if island == nil then island = cloudlands.find_nearest_island(surface_x, surface_z, 400) end + + if island ~= nil then + result = find_potential_portal_location_on_island(island, player_name) + end + + return result, island + end + + -- Ideally the Nether mod will provide a block obtainable by exploring the Nether which is + -- earmarked for mods like this one to use for portals, but until this happens I'll create + -- our own tempory placeholder "portalstone". + -- The Portals API is currently provided by nether, which depends on default, so we can assume default textures are available + local portalstone_end = "default_furnace_top.png^(default_ice.png^[opacity:120)^[multiply:#668" -- this gonna look bad with non-default texturepacks, hopefully Nether mod will provide a real block + local portalstone_side = "[combine:16x16:0,0=default_furnace_top.png:4,0=default_furnace_top.png:8,0=default_furnace_top.png:12,0=default_furnace_top.png:^(default_ice.png^[opacity:120)^[multiply:#668" + minetest.register_node("cloudlands:ancient_portalstone", { + description = S("Ancient Portalstone"), + tiles = {portalstone_end, portalstone_end, portalstone_side, portalstone_side, portalstone_side, portalstone_side}, + paramtype2 = "facedir", + sounds = default.node_sound_stone_defaults(), + groups = {cracky = 1, level = 2}, + on_blast = function() --[[blast proof]] end + }) + + minetest.register_ore({ + ore_type = "scatter", + ore = "cloudlands:ancient_portalstone", + wherein = "nether:rack", + clust_scarcity = 32 * 32 * 32, + clust_num_ores = 6, + clust_size = 3, + y_max = nether.DEPTH_CEILING or nether.DEPTH, + y_min = nether.DEPTH_FLOOR or -32000, + }) + + local _ = {name = "air", prob = 0} + local A = {name = "air", prob = 255, force_place = true} + local PU = {name = "cloudlands:ancient_portalstone", param2 = 0, prob = 255, force_place = true} + local PW = {name = "cloudlands:ancient_portalstone", param2 = 12, prob = 255, force_place = true} + local PN = {name = "cloudlands:ancient_portalstone", param2 = 4, prob = 255, force_place = true} + minetest.register_decoration({ + name = "Ancient broken portal", + deco_type = "schematic", + place_on = "nether:rack", + sidelen = 80, + fill_ratio = 0.00018, + biomes = {"nether_caverns"}, + y_max = nether.DEPTH_CEILING or nether.DEPTH, + y_min = nether.DEPTH_FLOOR or -32000, + schematic = { + size = {x = 4, y = 4, z = 1}, + data = { + PN, A, PW, PN, + PU, A, A, PU, + A, _, _, PU, + _, _, _, PU + }, + yslice_prob = { + {ypos = 3, prob = 92}, + {ypos = 1, prob = 30}, + } + }, + place_offset_y = 1, + flags = "force_placement,all_floors", + rotation = "random" + }) + + -- this uses place_schematic() without minetest.after(), so should be called after vm:write_to_map() + addDetail_ancientPortal = function(core) + + if (core.radius < 8 or PORTAL_RARITY == 0) then return false end -- avoid portals hanging off the side of small islands + + local fastHash = 3 + fastHash = (37 * fastHash) + 9354 -- to keep this probability distinct from reefs and atols + fastHash = (37 * fastHash) + ISLANDS_SEED + fastHash = (37 * fastHash) + core.x + fastHash = (37 * fastHash) + core.z + fastHash = (37 * fastHash) + math_floor(core.radius) + fastHash = (37 * fastHash) + math_floor(core.depth) + if (PORTAL_RARITY * 10000) < math_floor((math_abs(fastHash)) % 10000) then return false end + + local portalPos = find_potential_portal_location_on_island(core, nil) + + if portalPos ~= nil then + local orientation = (fastHash % 2) * 90 + portalPos.y = portalPos.y - ((core.x + core.z) % 3) -- partially bury some ancient portals + + minetest.place_schematic( + portalPos, + { + size = {x = 4, y = 5, z = 1}, + data = { + PN, PW, PW, PN, + PU, _, _, PU, + PU, _, _, PU, + PU, _, _, PU, + PN, PW, PW, PN + }, + }, + orientation, + { -- node replacements + ["default:obsidian"] = "cloudlands:ancient_portalstone", + }, + true + ) + end + end + + + nether.register_portal("cloudlands_portal", { + shape = nether.PortalShape_Traditional, + frame_node_name = "cloudlands:ancient_portalstone", + wormhole_node_color = 2, -- 2 is blue + particle_color = "#77F", + particle_texture = { + name = "nether_particle_anim1.png", + animation = { + type = "vertical_frames", + aspect_w = 7, + aspect_h = 7, + length = 1, + }, + scale = 1.5 + }, + title = S("Hallelujah Mountains Portal"), + book_of_portals_pagetext = + S("Construction requires 14 blocks of ancient portalstone. We have no knowledge of how portalstones were created, the means to craft them are likely lost to time, so our only source has been to scavenge the Nether for the remnants of ancient broken portals. A finished frame is four blocks wide, five blocks high, and stands vertically, like a doorway.") .. "\n\n" .. + S("The only portal we managed to scavenge enough portalstone to build took us to a land of floating islands. There were hills and forests and even water up there, but the edges are a perilous drop — a depth of which we cannot even begin to plumb."), + + is_within_realm = function(pos) + -- return true if pos is in the cloudlands + -- I'm doing this based off height for speed, so it sometimes gets it wrong when the + -- Hallelujah mountains start reaching the ground. + if noise_heightMap == nil then cloudlands.init() end + local largestCoreType = cloudlands.coreTypes[1] -- the first island type is the biggest/thickest + local island_bottom = ALTITUDE - (largestCoreType.depthMax * 0.66) + round(noise_heightMap:get2d({x = pos.x, y = pos.z})) + + return pos.y > math_max(40, island_bottom) + end, + + find_realm_anchorPos = function(surface_anchorPos, player_name) + -- Find the nearest island and obtain a suitable surface position on it + local destination_pos, island = find_nearest_island_location_for_portal(surface_anchorPos.x, surface_anchorPos.z, player_name) + + if island ~= nil then + -- Allow any existing or player-positioned portal on the island to be linked to + -- first before resorting to the island's default portal position + local existing_portal_location, existing_portal_orientation = nether.find_nearest_working_portal( + "cloudlands_portal", + {x = island.x, y = 100000, z = island.z}, -- Using 100000 for y to ensure the position is in the cloudlands realm and so find_nearest_working_portal() will only returns island portals. + island.radius * 0.9, -- Islands normally don't reach their full radius. Ensure this distance limit encompasses any location find_nearest_island_location_for_portal() can return. + 0 -- a y_factor of 0 makes the search ignore the altitude of the portals (as long as they are in the Cloudlands realm) + ) + if existing_portal_location ~= nil then + return existing_portal_location, existing_portal_orientation + end + end + + return destination_pos + end, + + find_surface_anchorPos = function(realm_anchorPos) + -- This function isn't needed since find_surface_target_y() will be used by default, + -- but by implementing it I can look for any existing nearby portals before falling + -- back to find_surface_target_y. + + -- Using -100000 for y to ensure the position is outside the cloudlands realm and so + -- find_nearest_working_portal() will only returns ground portals. + -- a y_factor of 0 makes the search ignore the -100000 altitude of the portals (as + -- long as they are outside the cloudlands realm) + local existing_portal_location, existing_portal_orientation = + nether.find_nearest_working_portal("cloudlands_portal", {x = realm_anchorPos.x, y = -100000, z = realm_anchorPos.z}, 150, 0) + + if existing_portal_location ~= nil then + return existing_portal_location, existing_portal_orientation + else + local y = nether.find_surface_target_y(realm_anchorPos.x, realm_anchorPos.z, "cloudlands_portal") + return {x = realm_anchorPos.x, y = y, z = realm_anchorPos.z} + end + end, + + on_ignite = function(portalDef, anchorPos, orientation) + -- make some sparks fly on ignition + 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 + + local velocity + if orientation == 0 then + velocity = {x = 0, y = 0, z = 7} + else + velocity = {x = 7, y = 0, z = 0} + end + + local particleSpawnerDef = { + amount = 180, + time = 0.15, + minpos = {x = pos.x - 1, y = pos.y - 1.5, z = pos.z - 1}, + maxpos = {x = pos.x + 1, y = pos.y + 1.5, z = pos.z + 1}, + minvel = velocity, + maxvel = velocity, + minacc = {x = 0, y = 0, z = 0}, + maxacc = {x = 0, y = 0, z = 0}, + minexptime = 0.1, + maxexptime = 0.5, + minsize = 0.3 * portalDef.particle_texture_scale, + maxsize = 0.8 * portalDef.particle_texture_scale, + collisiondetection = false, + texture = textureName .. "^[colorize:#99F:alpha", + animation = portalDef.particle_texture_animation, + glow = 8 + } + + minetest.add_particlespawner(particleSpawnerDef) + + velocity = vector.multiply(velocity, -1) + particleSpawnerDef.minvel, particleSpawnerDef.maxvel = velocity, velocity + minetest.add_particlespawner(particleSpawnerDef) + end + + }) +end + +--[[============================== + SkyTrees + ==============================]]-- + +-- If splitting SkyTrees into a seperate mod, perhaps schemlib would be of help - https://forum.minetest.net/viewtopic.php?t=18084 + + +if not minetest.global_exists("SkyTrees") then -- If SkyTrees added into other mods, this may have already been defined + + local TREE1_FILE = 'cloudlands_tree1.mts' + local TREE2_FILE = 'cloudlands_tree2.mts' + local BARK_SUFFIX = '_bark' + local GLOW_SUFFIX = '_glow' + + SkyTrees = { + -- Order the trees in this schematicInfo array from the largest island requirements to smallest + -- The data in each schematicInfo must exactly match what's in the .mts file or things will break + schematicInfo = { + { + filename = TREE1_FILE, + size = {x = 81, y = 106, z = 111}, + center = {x = 37, y = 11, z = 73}, + requiredIslandDepth = 20, + requiredIslandRadius = 40, + nodesWithConstructor = { + {x=35, y=69, z=1}, {x=61, y=51, z=2}, {x=36, y=68, z=2}, {x=68, y=48, z=3}, {x=61, y=50, z=4}, {x=71, y=50, z=5}, {x=58, y=52, z=5}, {x=65, y=50, z=9}, {x=72, y=53, z=11}, {x=41, y=67, z=12}, {x=63, y=48, z=13}, {x=69, y=52, z=13}, {x=33, y=66, z=14}, {x=39, y=68, z=15}, {x=72, y=68, z=15}, {x=40, y=67, z=16}, {x=39, y=66, z=17}, {x=68, y=45, z=19}, {x=69, y=44, z=20}, {x=72, y=55, z=20}, {x=66, y=56, z=20}, {x=58, y=66, z=20}, {x=71, y=58, z=21}, {x=68, y=45, z=22}, {x=70, y=51, z=22}, {x=73, y=55, z=22}, {x=36, y=62, z=22}, {x=70, y=67, z=22}, {x=21, y=65, z=23}, {x=22, y=66, z=23}, {x=53, y=66, z=23}, {x=70, y=68, z=23}, {x=73, y=54, z=24}, {x=75, y=57, z=24}, {x=37, y=63, z=24}, {x=7, y=68, z=24}, {x=69, y=56, z=25}, {x=34, y=58, z=25}, {x=66, y=62, z=25}, {x=64, y=66, z=25}, {x=6, y=67, z=25}, {x=3, y=68, z=25}, {x=68, y=56, z=26}, {x=65, y=57, z=26}, {x=61, y=63, z=26}, {x=31, y=59, z=27}, {x=48, y=62, z=27}, {x=50, y=63, z=27}, {x=78, y=65, z=27}, {x=78, y=52, z=28}, {x=68, y=57, z=28}, {x=76, y=57, z=28}, {x=31, y=60, z=28}, {x=15, y=63, z=28}, {x=16, y=63, z=28}, {x=66, y=64, z=28}, {x=60, y=65, z=28}, {x=61, y=76, z=28}, {x=63, y=76, z=28}, {x=69, y=59, z=29}, {x=51, y=65, z=29}, {x=72, y=57, z=30}, {x=20, y=60, z=30}, {x=21, y=61, z=30}, {x=49, y=65, z=30}, {x=52, y=53, z=31}, {x=72, y=57, z=31}, {x=36, y=58, z=31}, {x=63, y=60, z=31}, {x=54, y=63, z=31}, {x=45, y=65, z=31}, {x=79, y=66, z=31}, {x=62, y=70, z=31}, {x=55, y=103, z=31}, {x=52, y=53, z=32}, {x=68, y=60, z=32}, {x=19, y=61, z=32}, {x=53, y=63, z=32}, {x=37, y=64, z=32}, {x=21, y=65, z=32}, {x=56, y=65, z=32}, {x=59, y=71, z=32}, {x=35, y=74, z=32}, {x=23, y=75, z=32}, {x=35, y=58, z=33}, {x=62, y=60, z=33}, {x=18, y=63, z=33}, {x=73, y=67, z=33}, {x=37, y=74, z=33}, {x=65, y=75, z=33}, {x=38, y=2, z=34}, {x=67, y=52, z=34}, {x=71, y=60, z=34}, {x=25, y=63, z=34}, {x=19, y=64, z=34}, {x=32, y=66, z=34}, {x=66, y=72, z=34}, {x=41, y=81, z=34}, {x=45, y=93, z=34}, {x=54, y=99, z=34}, {x=38, y=5, z=35}, {x=68, y=48, z=35}, {x=69, y=51, z=35}, {x=48, y=53, z=35}, {x=37, y=57, z=35}, {x=77, y=58, z=35}, {x=32, y=60, z=35}, {x=20, y=61, z=35}, {x=27, y=61, z=35}, {x=33, y=65, z=35}, {x=58, y=65, z=35}, {x=58, y=72, z=35}, {x=60, y=73, z=35}, {x=30, y=74, z=35}, {x=41, y=74, z=35}, {x=41, y=87, z=35}, {x=22, y=58, z=36}, {x=64, y=58, z=36}, {x=39, y=70, z=36}, {x=36, y=77, z=36}, {x=44, y=83, z=36}, {x=40, y=86, z=36}, {x=35, y=56, z=37}, {x=65, y=59, z=37}, {x=66, y=62, z=37}, {x=62, y=67, z=37}, {x=39, y=68, z=37}, {x=40, y=86, z=37}, {x=53, y=88, z=37}, {x=43, y=97, z=37}, {x=52, y=99, z=37}, {x=37, y=3, z=38}, {x=35, y=55, z=38}, {x=38, y=56, z=38}, {x=25, y=57, z=38}, {x=65, y=57, z=38}, {x=71, y=61, z=38}, {x=33, y=65, z=38}, {x=61, y=65, z=38}, {x=50, y=66, z=38}, {x=38, y=68, z=38}, {x=46, y=97, z=38}, {x=44, y=100, z=38}, {x=51, y=102, z=38}, {x=29, y=42, z=39}, {x=27, y=43, z=39}, {x=70, y=48, z=39}, {x=72, y=52, z=39}, {x=23, y=57, z=39}, {x=26, y=57, z=39}, {x=28, y=58, z=39}, {x=55, y=58, z=39}, {x=73, y=59, z=39}, {x=65, y=65, z=39}, {x=41, y=68, z=39}, {x=42, y=81, z=39}, {x=55, y=88, z=39}, {x=43, y=91, z=39}, {x=45, y=100, z=39}, {x=23, y=57, z=40}, {x=29, y=57, z=40}, {x=76, y=58, z=40}, {x=73, y=59, z=40}, {x=78, y=59, z=40}, {x=31, y=60, z=40}, {x=64, y=64, z=40}, {x=41, y=67, z=40}, {x=42, y=75, z=40}, {x=37, y=78, z=40}, {x=42, y=92, z=40}, {x=51, y=101, z=40}, {x=48, y=105, z=40}, {x=75, y=59, z=41}, {x=55, y=63, z=41}, {x=35, y=68, z=41}, {x=35, y=69, z=41}, {x=35, y=71, z=41}, {x=34, y=42, z=42}, {x=29, y=55, z=42}, {x=50, y=61, z=42}, {x=34, y=65, z=42}, {x=57, y=88, z=42}, {x=48, y=89, z=42}, {x=49, y=89, z=42}, {x=27, y=22, z=43}, {x=26, y=28, z=43}, {x=31, y=46, z=43}, {x=66, y=52, z=43}, {x=49, y=57, z=43}, {x=56, y=57, z=43}, {x=41, y=69, z=43}, {x=36, y=52, z=44}, {x=63, y=54, z=44}, {x=51, y=55, z=44}, {x=57, y=56, z=44}, {x=69, y=57, z=44}, {x=64, y=65, z=44}, {x=55, y=90, z=44}, {x=30, y=42, z=45}, {x=31, y=52, z=45}, {x=51, y=54, z=45}, {x=24, y=57, z=45}, {x=70, y=62, z=45}, {x=39, y=69, z=45}, {x=35, y=80, z=45}, {x=29, y=81, z=45}, {x=44, y=85, z=45}, {x=41, y=86, z=45}, {x=33, y=9, z=46}, {x=28, y=44, z=46}, {x=50, y=54, z=46}, {x=47, y=55, z=46}, {x=45, y=56, z=46}, {x=45, y=58, z=46}, {x=47, y=58, z=46}, {x=30, y=63, z=46}, {x=27, y=81, z=46}, {x=28, y=81, z=46}, {x=40, y=86, z=46}, {x=29, y=16, z=47}, {x=32, y=10, z=48}, {x=66, y=49, z=48}, {x=29, y=52, z=48}, {x=53, y=54, z=48}, {x=55, y=54, z=48}, {x=61, y=58, z=48}, {x=59, y=61, z=48}, {x=50, y=63, z=48}, {x=26, y=82, z=48}, {x=43, y=85, z=48}, {x=48, y=86, z=48}, {x=31, y=19, z=49}, {x=30, y=46, z=49}, {x=63, y=51, z=49}, {x=41, y=53, z=49}, {x=31, y=60, z=49}, {x=67, y=1, z=50}, {x=37, y=8, z=50}, {x=40, y=30, z=50}, {x=43, y=57, z=50}, {x=59, y=57, z=50}, {x=60, y=57, z=50}, {x=29, y=61, z=50}, {x=34, y=63, z=50}, {x=49, y=65, z=50}, {x=65, y=3, z=51}, {x=45, y=29, z=51}, {x=41, y=58, z=51}, {x=42, y=60, z=51}, {x=46, y=64, z=51}, {x=47, y=67, z=51}, {x=52, y=68, z=51}, {x=69, y=51, z=52}, {x=53, y=55, z=52}, {x=45, y=62, z=52}, {x=64, y=2, z=53}, {x=3, y=3, z=53}, {x=10, y=6, z=53}, {x=31, y=14, z=53}, {x=37, y=35, z=53}, {x=43, y=48, z=53}, {x=71, y=50, z=53}, {x=52, y=54, z=53}, {x=43, y=57, z=53}, {x=55, y=57, z=53}, {x=52, y=67, z=53}, {x=48, y=72, z=53}, {x=5, y=1, z=54}, {x=9, y=4, z=54}, {x=62, y=4, z=54}, {x=33, y=8, z=54}, {x=42, y=29, z=54}, {x=42, y=32, z=54}, {x=43, y=34, z=54}, {x=41, y=39, z=54}, {x=41, y=57, z=54}, {x=34, y=61, z=54}, {x=58, y=2, z=55}, {x=59, y=3, z=55}, {x=38, y=7, z=55}, {x=40, y=12, z=55}, {x=38, y=39, z=55}, {x=33, y=46, z=55}, {x=28, y=54, z=55}, {x=29, y=55, z=55}, {x=30, y=57, z=55}, {x=54, y=58, z=55}, {x=52, y=63, z=55}, {x=37, y=7, z=56}, {x=55, y=8, z=56}, {x=33, y=45, z=56}, {x=58, y=0, z=57}, {x=9, y=5, z=57}, {x=34, y=7, z=57}, {x=54, y=8, z=57}, {x=17, y=9, z=57}, {x=32, y=12, z=57}, {x=37, y=39, z=57}, {x=41, y=45, z=57}, {x=31, y=46, z=57}, {x=49, y=50, z=57}, {x=50, y=56, z=57}, {x=46, y=59, z=57}, {x=48, y=66, z=57}, {x=51, y=67, z=57}, {x=15, y=3, z=58}, {x=8, y=10, z=58}, {x=41, y=11, z=58}, {x=40, y=13, z=58}, {x=42, y=45, z=58}, {x=50, y=51, z=58}, {x=20, y=5, z=59}, {x=19, y=7, z=59}, {x=22, y=8, z=59}, {x=23, y=9, z=59}, {x=40, y=13, z=59}, {x=33, y=14, z=59}, {x=42, y=41, z=59}, {x=20, y=6, z=60}, {x=9, y=8, z=60}, {x=46, y=8, z=60}, {x=34, y=39, z=60}, {x=30, y=52, z=60}, {x=43, y=57, z=60}, {x=18, y=5, z=61}, {x=11, y=10, z=61}, {x=36, y=36, z=61}, {x=47, y=55, z=61}, {x=38, y=56, z=61}, {x=61, y=59, z=61}, {x=56, y=60, z=61}, {x=36, y=6, z=62}, {x=55, y=7, z=62}, {x=26, y=10, z=62}, {x=29, y=13, z=62}, {x=46, y=13, z=62}, {x=57, y=60, z=62}, {x=18, y=7, z=63}, {x=30, y=11, z=63}, {x=53, y=13, z=63}, {x=45, y=14, z=63}, {x=36, y=32, z=63}, {x=46, y=41, z=63}, {x=29, y=43, z=63}, {x=29, y=44, z=63}, {x=29, y=46, z=63}, {x=29, y=50, z=63}, {x=30, y=52, z=63}, {x=46, y=54, z=63}, {x=19, y=6, z=64}, {x=54, y=8, z=64}, {x=16, y=11, z=64}, {x=42, y=16, z=64}, {x=36, y=25, z=64}, {x=37, y=27, z=64}, {x=36, y=28, z=64}, {x=37, y=29, z=64}, {x=40, y=33, z=64}, {x=30, y=36, z=64}, {x=43, y=39, z=64}, {x=62, y=61, z=64}, {x=21, y=6, z=65}, {x=24, y=6, z=65}, {x=53, y=10, z=65}, {x=52, y=12, z=65}, {x=27, y=17, z=65}, {x=39, y=17, z=65}, {x=29, y=19, z=65}, {x=32, y=22, z=65}, {x=28, y=42, z=65}, {x=60, y=61, z=65}, {x=24, y=6, z=66}, {x=26, y=6, z=66}, {x=19, y=12, z=66}, {x=28, y=20, z=66}, {x=31, y=26, z=66}, {x=39, y=55, z=66}, {x=42, y=6, z=67}, {x=24, y=7, z=67}, {x=20, y=14, z=67}, {x=41, y=21, z=67}, {x=28, y=22, z=67}, {x=29, y=46, z=67}, + {x=34, y=52, z=67}, {x=45, y=17, z=68}, {x=42, y=25, z=68}, {x=28, y=43, z=68}, {x=46, y=44, z=68}, {x=29, y=7, z=69}, {x=49, y=12, z=69}, {x=29, y=43, z=69}, {x=48, y=9, z=70}, {x=45, y=17, z=70}, {x=36, y=9, z=71}, {x=47, y=10, z=71}, {x=25, y=11, z=71}, {x=45, y=17, z=71}, {x=42, y=46, z=71}, {x=34, y=47, z=71}, {x=35, y=48, z=71}, {x=45, y=10, z=72}, {x=25, y=12, z=72}, {x=45, y=35, z=72}, {x=45, y=43, z=72}, {x=36, y=52, z=72}, {x=39, y=55, z=72}, {x=26, y=19, z=73}, {x=27, y=21, z=73}, {x=26, y=27, z=73}, {x=26, y=29, z=73}, {x=43, y=31, z=73}, {x=28, y=36, z=73}, {x=42, y=41, z=73}, {x=34, y=46, z=73}, {x=39, y=59, z=73}, {x=24, y=9, z=74}, {x=48, y=9, z=74}, {x=35, y=48, z=74}, {x=35, y=51, z=74}, {x=42, y=53, z=74}, {x=33, y=57, z=74}, {x=30, y=60, z=74}, {x=47, y=8, z=75}, {x=22, y=12, z=75}, {x=45, y=18, z=75}, {x=27, y=30, z=75}, {x=45, y=33, z=75}, {x=36, y=49, z=75}, {x=36, y=1, z=76}, {x=45, y=7, z=76}, {x=21, y=14, z=76}, {x=44, y=23, z=76}, {x=29, y=35, z=76}, {x=38, y=40, z=76}, {x=39, y=42, z=76}, {x=33, y=58, z=76}, {x=34, y=1, z=77}, {x=21, y=7, z=77}, {x=18, y=11, z=77}, {x=26, y=23, z=77}, {x=43, y=25, z=77}, {x=41, y=32, z=77}, {x=36, y=41, z=77}, {x=39, y=47, z=77}, {x=35, y=56, z=77}, {x=35, y=1, z=78}, {x=26, y=3, z=78}, {x=34, y=3, z=78}, {x=18, y=9, z=78}, {x=27, y=23, z=78}, {x=51, y=33, z=78}, {x=41, y=37, z=78}, {x=36, y=1, z=79}, {x=25, y=2, z=79}, {x=18, y=8, z=79}, {x=15, y=10, z=79}, {x=14, y=11, z=79}, {x=27, y=23, z=79}, {x=28, y=25, z=79}, {x=45, y=32, z=79}, {x=33, y=34, z=79}, {x=34, y=34, z=79}, {x=37, y=55, z=79}, {x=40, y=62, z=79}, {x=27, y=0, z=80}, {x=31, y=18, z=80}, {x=30, y=26, z=80}, {x=34, y=61, z=80}, {x=20, y=7, z=81}, {x=51, y=7, z=81}, {x=25, y=8, z=81}, {x=53, y=8, z=81}, {x=42, y=10, z=81}, {x=56, y=12, z=81}, {x=21, y=15, z=81}, {x=37, y=28, z=81}, {x=36, y=29, z=81}, {x=37, y=29, z=81}, {x=44, y=35, z=81}, {x=22, y=7, z=82}, {x=26, y=8, z=82}, {x=29, y=8, z=82}, {x=44, y=9, z=82}, {x=42, y=10, z=82}, {x=32, y=13, z=82}, {x=13, y=14, z=82}, {x=29, y=22, z=82}, {x=31, y=25, z=82}, {x=35, y=27, z=82}, {x=27, y=60, z=82}, {x=41, y=64, z=82}, {x=20, y=8, z=83}, {x=57, y=8, z=83}, {x=24, y=9, z=83}, {x=58, y=9, z=83}, {x=36, y=22, z=83}, {x=32, y=24, z=83}, {x=47, y=8, z=84}, {x=56, y=8, z=84}, {x=59, y=11, z=84}, {x=45, y=13, z=84}, {x=58, y=13, z=84}, {x=17, y=14, z=84}, {x=23, y=14, z=84}, {x=56, y=14, z=84}, {x=29, y=19, z=84}, {x=36, y=19, z=84}, {x=27, y=59, z=84}, {x=35, y=6, z=85}, {x=9, y=8, z=85}, {x=41, y=11, z=85}, {x=50, y=13, z=85}, {x=33, y=58, z=85}, {x=34, y=58, z=85}, {x=33, y=7, z=86}, {x=18, y=10, z=86}, {x=9, y=12, z=86}, {x=41, y=12, z=87}, {x=41, y=60, z=87}, {x=9, y=2, z=88}, {x=7, y=5, z=88}, {x=5, y=10, z=88}, {x=41, y=11, z=88}, {x=62, y=11, z=88}, {x=42, y=68, z=88}, {x=37, y=6, z=89}, {x=66, y=8, z=89}, {x=9, y=10, z=89}, {x=19, y=10, z=89}, {x=58, y=12, z=89}, {x=45, y=62, z=89}, {x=7, y=5, z=90}, {x=67, y=5, z=90}, {x=7, y=9, z=90}, {x=31, y=11, z=90}, {x=62, y=11, z=90}, {x=1, y=2, z=91}, {x=5, y=5, z=91}, {x=69, y=5, z=91}, {x=62, y=8, z=91}, {x=58, y=9, z=91}, {x=63, y=10, z=91}, {x=35, y=7, z=92}, {x=62, y=9, z=92}, {x=33, y=13, z=92}, {x=36, y=62, z=92}, {x=37, y=3, z=93}, {x=37, y=6, z=93}, {x=64, y=6, z=93}, {x=32, y=10, z=93}, {x=34, y=14, z=93}, {x=39, y=57, z=93}, {x=41, y=67, z=93}, {x=33, y=9, z=94}, {x=38, y=57, z=94}, {x=41, y=69, z=94}, {x=40, y=1, z=95}, {x=34, y=7, z=97}, {x=33, y=9, z=97}, {x=33, y=10, z=102}, {x=33, y=7, z=105}, {x=35, y=9, z=107} + } + }, + { + filename = TREE2_FILE, + size = {x = 62, y = 65, z = 65}, + center = {x = 30, y = 12, z = 36}, + requiredIslandDepth = 16, + requiredIslandRadius = 24, + nodesWithConstructor = { {x=35, y=53, z=1}, {x=33, y=59, z=1}, {x=32, y=58, z=3}, {x=31, y=57, z=5}, {x=40, y=58, z=6}, {x=29, y=57, z=7}, {x=39, y=51, z=8}, {x=52, y=53, z=8}, {x=32, y=53, z=9}, {x=25, y=58, z=9}, {x=51, y=51, z=10}, {x=47, y=50, z=11}, {x=50, y=55, z=11}, {x=28, y=57, z=11}, {x=26, y=39, z=12}, {x=30, y=39, z=12}, {x=24, y=40, z=12}, {x=53, y=52, z=12}, {x=29, y=57, z=12}, {x=43, y=59, z=12}, {x=26, y=39, z=13}, {x=36, y=48, z=13}, {x=27, y=39, z=14}, {x=39, y=48, z=14}, {x=33, y=50, z=14}, {x=43, y=50, z=14}, {x=24, y=59, z=14}, {x=41, y=49, z=15}, {x=33, y=12, z=16}, {x=36, y=46, z=16}, {x=50, y=51, z=16}, {x=46, y=57, z=16}, {x=36, y=45, z=17}, {x=27, y=46, z=17}, {x=22, y=48, z=17}, {x=45, y=50, z=17}, {x=31, y=38, z=18}, {x=32, y=38, z=18}, {x=39, y=46, z=18}, {x=51, y=51, z=18}, {x=31, y=11, z=19}, {x=32, y=38, z=19}, {x=39, y=41, z=19}, {x=45, y=57, z=19}, {x=29, y=58, z=19}, {x=28, y=60, z=20}, {x=38, y=40, z=21}, {x=30, y=58, z=21}, {x=31, y=13, z=22}, {x=20, y=41, z=22}, {x=22, y=43, z=22}, {x=20, y=48, z=22}, {x=22, y=39, z=23}, {x=49, y=50, z=23}, {x=52, y=52, z=23}, {x=53, y=53, z=23}, {x=32, y=55, z=23}, {x=36, y=59, z=23}, {x=31, y=60, z=23}, {x=25, y=46, z=24}, {x=40, y=56, z=24}, {x=34, y=58, z=24}, {x=38, y=58, z=24}, {x=32, y=39, z=25}, {x=40, y=46, z=25}, {x=39, y=55, z=25}, {x=36, y=45, z=26}, {x=12, y=7, z=28}, {x=34, y=33, z=28}, {x=31, y=36, z=28}, {x=37, y=41, z=28}, {x=14, y=60, z=28}, {x=19, y=13, z=29}, {x=12, y=43, z=29}, {x=8, y=45, z=29}, {x=31, y=46, z=29}, {x=39, y=47, z=29}, {x=13, y=60, z=29}, {x=22, y=63, z=29}, {x=51, y=9, z=30}, {x=32, y=39, z=30}, {x=33, y=40, z=30}, {x=34, y=44, z=30}, {x=22, y=1, z=31}, {x=24, y=2, z=31}, {x=20, y=7, z=31}, {x=51, y=9, z=31}, {x=16, y=12, z=31}, {x=34, y=27, z=31}, {x=22, y=43, z=31}, {x=27, y=44, z=31}, {x=23, y=51, z=31}, {x=42, y=58, z=31}, {x=9, y=60, z=31}, {x=22, y=5, z=32}, {x=22, y=6, z=32}, {x=50, y=10, z=32}, {x=53, y=11, z=32}, {x=41, y=15, z=32}, {x=43, y=15, z=32}, {x=31, y=21, z=32}, {x=31, y=28, z=32}, {x=12, y=42, z=32}, {x=15, y=42, z=32}, {x=13, y=48, z=32}, {x=37, y=49, z=32}, {x=18, y=59, z=32}, {x=52, y=9, z=33}, {x=40, y=10, z=33}, {x=43, y=10, z=33}, {x=22, y=11, z=33}, {x=27, y=11, z=33}, {x=50, y=11, z=33}, {x=22, y=15, z=33}, {x=36, y=29, z=33}, {x=33, y=37, z=33}, {x=9, y=42, z=33}, {x=14, y=42, z=33}, {x=18, y=43, z=33}, {x=23, y=43, z=33}, {x=33, y=49, z=33}, {x=43, y=53, z=33}, {x=54, y=53, z=33}, {x=31, y=55, z=33}, {x=23, y=58, z=33}, {x=43, y=10, z=34}, {x=44, y=10, z=34}, {x=32, y=12, z=34}, {x=46, y=13, z=34}, {x=28, y=29, z=34}, {x=20, y=42, z=34}, {x=39, y=50, z=34}, {x=51, y=52, z=34}, {x=54, y=52, z=34}, {x=35, y=55, z=34}, {x=51, y=56, z=34}, {x=35, y=5, z=35}, {x=34, y=8, z=35}, {x=33, y=10, z=35}, {x=49, y=10, z=35}, {x=43, y=14, z=35}, {x=36, y=35, z=35}, {x=30, y=47, z=35}, {x=9, y=48, z=35}, {x=39, y=51, z=35}, {x=56, y=52, z=35}, {x=40, y=56, z=35}, {x=13, y=59, z=35}, {x=26, y=62, z=35}, {x=28, y=13, z=36}, {x=38, y=17, z=36}, {x=38, y=20, z=36}, {x=27, y=26, z=36}, {x=38, y=35, z=36}, {x=24, y=39, z=36}, {x=6, y=43, z=36}, {x=13, y=57, z=36}, {x=48, y=7, z=37}, {x=33, y=8, z=37}, {x=50, y=9, z=37}, {x=36, y=11, z=37}, {x=27, y=20, z=37}, {x=27, y=22, z=37}, {x=38, y=24, z=37}, {x=33, y=34, z=37}, {x=9, y=42, z=37}, {x=14, y=42, z=37}, {x=25, y=42, z=37}, {x=53, y=50, z=37}, {x=33, y=53, z=37}, {x=54, y=59, z=37}, {x=28, y=21, z=38}, {x=39, y=34, z=38}, {x=24, y=35, z=38}, {x=8, y=43, z=38}, {x=6, y=47, z=38}, {x=48, y=51, z=38}, {x=61, y=53, z=38}, {x=26, y=57, z=38}, {x=27, y=57, z=38}, {x=32, y=59, z=38}, {x=29, y=62, z=38}, {x=38, y=62, z=38}, {x=33, y=7, z=39}, {x=34, y=9, z=39}, {x=28, y=23, z=39}, {x=34, y=37, z=39}, {x=19, y=42, z=39}, {x=55, y=50, z=39}, {x=47, y=51, z=39}, {x=11, y=54, z=39}, {x=9, y=60, z=39}, {x=33, y=61, z=39}, {x=33, y=4, z=40}, {x=30, y=11, z=40}, {x=39, y=13, z=40}, {x=36, y=23, z=40}, {x=22, y=38, z=40}, {x=54, y=49, z=40}, {x=53, y=50, z=40}, {x=23, y=54, z=40}, {x=28, y=57, z=40}, {x=29, y=57, z=40}, {x=31, y=29, z=41}, {x=27, y=34, z=41}, {x=30, y=37, z=41}, {x=42, y=38, z=41}, {x=12, y=42, z=41}, {x=15, y=42, z=41}, {x=44, y=44, z=41}, {x=28, y=57, z=41}, {x=55, y=57, z=41}, {x=9, y=59, z=41}, {x=30, y=10, z=42}, {x=26, y=15, z=42}, {x=31, y=15, z=42}, {x=34, y=17, z=42}, {x=28, y=36, z=42}, {x=38, y=44, z=42}, {x=42, y=44, z=42}, {x=46, y=44, z=42}, {x=32, y=47, z=42}, {x=52, y=47, z=42}, {x=39, y=55, z=42}, {x=54, y=56, z=42}, {x=34, y=59, z=42}, {x=40, y=11, z=43}, {x=30, y=14, z=43}, {x=28, y=16, z=43}, {x=34, y=31, z=43}, {x=11, y=43, z=43}, {x=14, y=43, z=43}, {x=28, y=47, z=43}, {x=57, y=50, z=43}, {x=61, y=54, z=43}, {x=30, y=58, z=43}, {x=34, y=59, z=43}, {x=7, y=61, z=43}, {x=41, y=10, z=44}, {x=29, y=15, z=44}, {x=36, y=39, z=44}, {x=6, y=43, z=44}, {x=30, y=47, z=44}, {x=57, y=50, z=44}, {x=38, y=10, z=45}, {x=42, y=10, z=45}, {x=11, y=43, z=45}, {x=14, y=43, z=45}, {x=46, y=44, z=45}, {x=32, y=45, z=45}, {x=55, y=45, z=45}, {x=3, y=48, z=45}, {x=31, y=57, z=45}, {x=41, y=3, z=46}, {x=40, y=7, z=46}, {x=28, y=11, z=46}, {x=23, y=13, z=46}, {x=19, y=43, z=46}, {x=24, y=9, z=47}, {x=39, y=9, z=47}, {x=43, y=12, z=47}, {x=5, y=43, z=47}, {x=42, y=43, z=47}, {x=46, y=43, z=47}, {x=24, y=47, z=47}, {x=60, y=52, z=47}, {x=24, y=54, z=47}, {x=37, y=57, z=47}, {x=11, y=60, z=47}, {x=27, y=9, z=48}, {x=27, y=11, z=48}, {x=22, y=14, z=48}, {x=15, y=44, z=48}, {x=51, y=45, z=48}, {x=23, y=49, z=48}, {x=59, y=53, z=48}, {x=9, y=56, z=48}, {x=33, y=59, z=48}, {x=41, y=14, z=49}, {x=8, y=43, z=49}, {x=10, y=43, z=49}, {x=39, y=43, z=49}, {x=34, y=44, z=49}, {x=47, y=44, z=49}, {x=48, y=44, z=49}, {x=24, y=51, z=49}, {x=10, y=55, z=49}, {x=32, y=59, z=49}, {x=20, y=61, z=49}, {x=11, y=63, z=49}, {x=25, y=8, z=50}, {x=22, y=10, z=50}, {x=42, y=14, z=50}, {x=10, y=43, z=50}, {x=43, y=43, z=50}, {x=61, y=46, z=50}, {x=39, y=54, z=50}, {x=24, y=12, z=51}, {x=50, y=44, z=51}, {x=52, y=45, z=51}, {x=54, y=45, z=51}, {x=2, y=46, z=51}, {x=8, y=51, z=51}, {x=7, y=52, z=51}, {x=37, y=58, z=51}, {x=22, y=50, z=52}, {x=25, y=55, z=52}, {x=39, y=58, z=52}, {x=20, y=7, z=53}, {x=40, y=43, z=53}, {x=58, y=45, z=53}, {x=60, y=50, z=53}, {x=22, y=55, z=53}, {x=28, y=56, z=53}, {x=50, y=62, z=53}, {x=54, y=45, z=54}, {x=61, y=46, z=54}, {x=30, y=47, z=54}, {x=30, y=49, z=54}, {x=53, y=53, z=54}, {x=18, y=55, z=54}, {x=51, y=56, z=54}, {x=46, y=62, z=54}, {x=21, y=56, z=55}, {x=24, y=56, z=55}, {x=38, y=61, z=55}, {x=19, y=49, z=56}, {x=46, y=52, z=56}, {x=47, y=53, z=56}, {x=59, y=47, z=57}, {x=26, y=57, z=57}, {x=45, y=43, z=58}, {x=15, y=50, z=58}, {x=11, y=51, z=58}, {x=50, y=44, z=59}, {x=53, y=47, z=59}, {x=43, y=49, z=59}, {x=18, y=50, z=59}, {x=18, y=51, z=60}, {x=38, y=45, z=61}, {x=50, y=47, z=61}, {x=41, y=48, z=61} }, + } + }, + MODNAME = minetest.get_current_modname() -- don't hardcode incase it's copied into other mods + } + + -- Must be called during mod load time, as it uses minetest.register_node() + -- (add an optional dependency for any mod where the tree & leaf textures might be + -- sourced from, to ensure they are loaded before this is called) + SkyTrees.init = function() + + SkyTrees.minimumIslandRadius = 100000 + SkyTrees.minimumIslandDepth = 100000 + SkyTrees.maximumYOffset = 0 + SkyTrees.maximumHeight = 0 + + SkyTrees.nodeName_sideVines = interop.find_node_name(NODENAMES_VINES) + SkyTrees.nodeName_hangingVine = interop.find_node_name(NODENAMES_HANGINGVINE) + SkyTrees.nodeName_hangingRoot = interop.find_node_name(NODENAMES_HANGINGROOT) + + for i,tree in pairs(SkyTrees.schematicInfo) do + local fullFilename = minetest.get_modpath(SkyTrees.MODNAME) .. DIR_DELIM .. tree.filename + + if not interop.file_exists(fullFilename) then + -- remove the schematic from the list + SkyTrees.schematicInfo[i] = nil + else + SkyTrees.minimumIslandRadius = math_min(SkyTrees.minimumIslandRadius, tree.requiredIslandRadius) + SkyTrees.minimumIslandDepth = math_min(SkyTrees.minimumIslandDepth, tree.requiredIslandDepth) + SkyTrees.maximumYOffset = math_max(SkyTrees.maximumYOffset, tree.center.y) + SkyTrees.maximumHeight = math_max(SkyTrees.maximumHeight, tree.size.y) + + tree.theme = {} + SkyTrees.schematicInfo[tree.filename] = tree -- so schematicInfo of trees can be indexed by name + end + end + + local function generate_woodTypes(nodeName_templateWood, overlay, barkoverlay, nodesuffix, description, dropsTemplateWood) + + local trunkNode = minetest.registered_nodes[nodeName_templateWood] + local newTrunkNode = {} + for key, value in pairs(trunkNode) do newTrunkNode[key] = value end + newTrunkNode.name = SkyTrees.MODNAME .. ":" .. nodesuffix + newTrunkNode.description = description + if newTrunkNode.paramtype2 == nil then newTrunkNode.paramtype2 = "facedir" end + if newTrunkNode.on_dig ~= nil and minetest.get_modpath("main") then + newTrunkNode.on_dig = nil -- Crafter has special trunk auto-digging logic that doesn't make sense for giant trees + end + + if dropsTemplateWood then + newTrunkNode.drop = nodeName_templateWood + if newTrunkNode.groups == nil then newTrunkNode.groups = {} end + newTrunkNode.groups.not_in_creative_inventory = 1 + else + newTrunkNode.drop = nil + end + + local tiles = trunkNode.tiles + if type(tiles) == "table" then + newTrunkNode.tiles = {} + for key, value in pairs(tiles) do newTrunkNode.tiles[key] = value .. overlay end + else + newTrunkNode.tiles = tiles .. overlay + end + + local newBarkNode = {} + for key, value in pairs(newTrunkNode) do newBarkNode[key] = value end + newBarkNode.name = newBarkNode.name .. BARK_SUFFIX + newBarkNode.description = S("Bark of @1", newBarkNode.description) + -- .drop: leave the bark nodes dropping the trunk wood + + tiles = trunkNode.tiles + if type(tiles) == "table" then + newBarkNode.tiles = { tiles[#tiles] .. barkoverlay } + end + + --minetest.log("info", newTrunkNode.name .. ": " .. dump(newTrunkNode)) + minetest.register_node(newTrunkNode.name, newTrunkNode) + minetest.register_node(newBarkNode.name, newBarkNode) + return newTrunkNode.name + end + + local function generate_leafTypes(nodeName_templateLeaf, overlay, nodesuffix, description, dropsTemplateLeaf, glowVariantBrightness) + + local leafNode = minetest.registered_nodes[nodeName_templateLeaf] + local newLeafNode = {} + for key, value in pairs(leafNode) do newLeafNode[key] = value end + newLeafNode.name = SkyTrees.MODNAME .. ":" .. nodesuffix + newLeafNode.description = description + newLeafNode.sunlight_propagates = true -- soo many leaves they otherwise blot out the sun. + if dropsTemplateLeaf then + newLeafNode.drop = nodeName_templateLeaf + if newLeafNode.groups == nil then newLeafNode.groups = {} end + newLeafNode.groups.not_in_creative_inventory = 1 + else + newLeafNode.drop = nil + end + + local tiles = leafNode.tiles + if type(tiles) == "table" then + newLeafNode.tiles = {} + for key, value in pairs(tiles) do newLeafNode.tiles[key] = value .. overlay end + else + newLeafNode.tiles = tiles .. overlay + end + + minetest.register_node(newLeafNode.name, newLeafNode) + + if glowVariantBrightness ~= nil and glowVariantBrightness > 0 and BIOLUMINESCENCE then + local glowingLeafNode = {} + for key, value in pairs(newLeafNode) do glowingLeafNode[key] = value end + glowingLeafNode.name = newLeafNode.name .. GLOW_SUFFIX + glowingLeafNode.description = S("Glowing @1", description) + glowingLeafNode.light_source = glowVariantBrightness + minetest.register_node(glowingLeafNode.name, glowingLeafNode) + end + + return newLeafNode.name + end + + local templateWood = interop.find_node_name(NODENAMES_TREEWOOD) + if templateWood == 'ignore' then + SkyTrees.disabled = "Could not find any tree nodes" + return + end + local normalwood = generate_woodTypes(templateWood, "", "", "tree", S("Giant tree"), true) + local darkwood = generate_woodTypes(templateWood, "^[colorize:black:205", "^[colorize:black:205", "darkwood", S("Giant Ziricote"), false) + local deadwood = generate_woodTypes(templateWood, "^[colorize:#EFE6B9:110", "^[colorize:#E8D0A0:110", "deadbleachedwood", S("Dead bleached wood"), false) -- make use of the bark blocks to introduce some color variance in the tree + + + local templateLeaf = interop.find_node_name(NODENAMES_TREELEAVES) + if templateLeaf == 'ignore' then + SkyTrees.disabled = "Could not find any treeleaf nodes" + return + end + local greenleaf1 = generate_leafTypes(templateLeaf, "", "leaves", S("Leaves of a giant tree"), true) -- drops templateLeaf because these look close enough to the original leaves that we won't clutter the game & creative-menu with tiny visual variants that other recipes/parts of the game won't know about + local greenleaf2 = generate_leafTypes(templateLeaf, "^[colorize:#00FF00:16", "leaves2", S("Leaves of a giant tree"), false) + local greenleaf3 = generate_leafTypes(templateLeaf, "^[colorize:#90FF60:28", "leaves3", S("Leaves of a giant tree"), false) + + local whiteblossom1 = generate_leafTypes(templateLeaf, "^[colorize:#fffdfd:alpha", "blossom_white1", S("Blossom"), false) + local whiteblossom2 = generate_leafTypes(templateLeaf, "^[colorize:#fff0f0:alpha", "blossom_white2", S("Blossom"), false) + local pinkblossom = generate_leafTypes(templateLeaf, "^[colorize:#FFE3E8:alpha", "blossom_whitepink", S("Blossom"), false, 5) + + local sakurablossom1 = generate_leafTypes(templateLeaf, "^[colorize:#ea327c:alpha", "blossom_red", S("Sakura blossom"), false, 5) + local sakurablossom2 = generate_leafTypes(templateLeaf, "^[colorize:#ffc3dd:alpha", "blossom_pink", S("Sakura blossom"), false) + + local wisteriaBlossom1 = generate_leafTypes(templateLeaf, "^[colorize:#8087ec:alpha", "blossom_wisteria1", S("Wisteria blossom"), false) + local wisteriaBlossom2 = generate_leafTypes(templateLeaf, "^[colorize:#ccc9ff:alpha", "blossom_wisteria2", S("Wisteria blossom"), false, 7) + + + local tree = SkyTrees.schematicInfo[TREE1_FILE] + if tree ~= nil then + + tree.defaultThemeName = "Green foliage" + tree.theme[tree.defaultThemeName] = { + relativeProbability = 5, + trunk = normalwood, + leaves1 = greenleaf1, + leaves2 = greenleaf2, + leaves_special = greenleaf3, + vineflags = { leaves = true, hanging_leaves = true }, + + init = function(self, position) + -- if it's hot and humid then add vines + local viney = get_heat(position) >= VINES_REQUIRED_TEMPERATURE and get_humidity(position) >= VINES_REQUIRED_HUMIDITY + + if viney then + local flagSeed = position.x * 3 + position.z + ISLANDS_SEED + self.vineflags.hanging_leaves = (flagSeed % 10) <= 3 or (flagSeed % 10) >= 8 + self.vineflags.leaves = (flagSeed % 10) <= 5 + self.vineflags.bark = (flagSeed % 10) <= 2 + self.vineflags.hanging_bark = (flagSeed % 10) <= 1 + end + end + } + + tree.theme["Haunted"] = { + relativeProbability = 2, + trunk = darkwood, + vineflags = { hanging_roots = true }, + hasHeart = false, + hasSoil = false, + + init = function(self, position) + -- 60% of these trees are a hanging roots variant + self.vineflags.hanging_roots = (position.x * 3 + position.y + position.z + ISLANDS_SEED) % 10 < 60 + end + } + + tree.theme["Dead"] = { + relativeProbability = 0, -- 0 because this theme will be chosen based on location, rather than chance. + trunk = deadwood, + hasHeart = false + } + + tree.theme["Sakura"] = { + relativeProbability = 2, + trunk = darkwood, + leaves1 = sakurablossom2, + leaves2 = whiteblossom2, + leaves_special = sakurablossom1, + + init = function(self, position) + -- 40% of these trees are a glowing variant + self.glowing = (position.x * 3 + position.z + ISLANDS_SEED) % 10 <= 3 and BIOLUMINESCENCE + self.leaves_special = sakurablossom1 + if self.glowing then self.leaves_special = sakurablossom1 .. GLOW_SUFFIX end + end + } + + end + + tree = SkyTrees.schematicInfo[TREE2_FILE] + if tree ~= nil then + + -- copy the green leaves theme from tree1 + tree.defaultThemeName = "Green foliage" + tree.theme[tree.defaultThemeName] = SkyTrees.schematicInfo[TREE1_FILE].theme["Green foliage"] + + tree.theme["Wisteria"] = { + relativeProbability = 2.5, + trunk = normalwood, + leaves1 = greenleaf1, + leaves2 = wisteriaBlossom1, + leaves_special = wisteriaBlossom2, + vineflags = { leaves = true, hanging_leaves = true, hanging_bark = true }, + + init = function(self, position) + -- 40% of these trees are a glowing variant + self.glowing = (position.x * 3 + position.z + ISLANDS_SEED) % 10 <= 3 and BIOLUMINESCENCE + self.leaves_special = wisteriaBlossom2 + if self.glowing then self.leaves_special = wisteriaBlossom2 .. GLOW_SUFFIX end + + -- if it's hot and humid then allow vines on the trunk as well + self.vineflags.bark = get_heat(position) >= VINES_REQUIRED_TEMPERATURE and get_humidity(position) >= VINES_REQUIRED_HUMIDITY + end + } + + tree.theme["Blossom"] = { + relativeProbability = 1.5, + trunk = normalwood, + leaves1 = whiteblossom1, + leaves2 = whiteblossom2, + leaves_special = normalwood..BARK_SUFFIX, + + init = function(self, position) + -- 30% of these trees are a glowing variant + self.glowing = (position.x * 3 + position.z + ISLANDS_SEED) % 10 <= 2 and BIOLUMINESCENCE + self.leaves_special = normalwood..BARK_SUFFIX + if self.glowing then self.leaves_special = pinkblossom .. GLOW_SUFFIX end + end + } + + end + + -- fill in any omitted fields in the themes with default values + for _,treeInfo in pairs(SkyTrees.schematicInfo) do + for _,theme in pairs(treeInfo.theme) do + if theme.bark == nil then theme.bark = theme.trunk .. BARK_SUFFIX end + if theme.leaves1 == nil then theme.leaves1 = 'ignore' end + if theme.leaves2 == nil then theme.leaves2 = 'ignore' end + if theme.leaves_special == nil then theme.leaves_special = theme.leaves1 end + + if theme.vineflags == nil then theme.vineflags = {} end + if theme.relativeProbability == nil then theme.relativeProbability = 1.0 end + if theme.glowing == nil then theme.glowing = false end + if theme.hasSoil == nil then theme.hasSoil = true end + if theme.hasHeart == nil then theme.hasHeart = true end + end + end + + -- The heart of the Tree + -- The difference between a living tree and and a haunted/darkened husk + -- + -- Ideally trees would slowly fizzlefade to/from the Haunted theme depending on + -- whether a player steals or restores the heart, meaning a house hollowed out inside + -- a living tree would need the heart to still be kept inside it, perhaps on its + -- own pedestal (unless wanting an Addam's Family treehouse). + local heartwoodTexture = minetest.registered_nodes[templateWood].tiles + if type(heartwoodTexture) == "table" then heartwoodTexture = heartwoodTexture[1] end + local heartwoodGlow = minetest.LIGHT_MAX -- plants can grow under the heart of the Tree + if not BIOLUMINESCENCE then heartwoodGlow = 0 end -- :( + minetest.register_node( + SkyTrees.MODNAME .. ":HeartWood", + { + tiles = { heartwoodTexture }, + description = S("Heart of the Tree"), + groups = {oddly_breakable_by_hand = 3, handy = 1}, + _mcl_hardness = 0.4, + drawtype = "nodebox", + paramtype = "light", + light_source = heartwoodGlow, -- plants can grow under the heart of the Tree + node_box = { + type = "fixed", + fixed = { + --[[ Original heart + {-0.38, -0.38, -0.38, 0.38, 0.38, 0.38}, + {0.15, 0.15, 0.15, 0.5, 0.5, 0.5}, + {-0.5, 0.15, 0.15, -0.15, 0.5, 0.5}, + {-0.5, 0.15, -0.5, -0.15, 0.5, -0.15}, + {0.15, 0.15, -0.5, 0.5, 0.5, -0.15}, + {0.15, -0.5, -0.5, 0.5, -0.15, -0.15}, + {-0.5, -0.5, -0.5, -0.15, -0.15, -0.15}, + {-0.5, -0.5, 0.15, -0.15, -0.15, 0.5}, + {0.15, -0.5, 0.15, 0.5, -0.15, 0.5} + ]] + + {-0.38, -0.38, -0.38, 0.38, 0.38, 0.38}, + {-0.5, -0.2, -0.2, 0.5, 0.2, 0.2}, + {-0.2, -0.5, -0.2, 0.2, 0.5, 0.2}, + {-0.2, -0.2, -0.5, 0.2, 0.2, 0.5} + } + } + } + ) + end + + -- this is hack to work around how place_schematic() never invalidates its cache + -- a unique schematic filename is generated for each unique theme + SkyTrees.getMalleatedFilename = function(schematicInfo, themeName) + + -- create a unique id for the theme + local theme = schematicInfo.theme[themeName] + local flags = 0 + if theme.glowing then flags = flags + 1 end + if theme.vineflags.leaves then flags = flags + 2 end + if theme.vineflags.hanging_leaves then flags = flags + 4 end + if theme.vineflags.bark then flags = flags + 8 end + if theme.vineflags.hanging_bark then flags = flags + 16 end + if theme.vineflags.hanging_roots then flags = flags + 32 end + if theme.hasSoil then flags = flags + 64 end + if theme.hasHeart then flags = flags + 128 end + + local uniqueId = themeName .. flags + + if schematicInfo.malleatedFilenames == nil then schematicInfo.malleatedFilenames = {} end + + if schematicInfo.malleatedFilenames[uniqueId] == nil then + + local malleationCount = 0 + for _ in pairs(schematicInfo.malleatedFilenames) do malleationCount = malleationCount + 1 end + + local malleatedFilename = minetest.get_modpath(SkyTrees.MODNAME) .. DIR_DELIM + for i = 1, malleationCount do + malleatedFilename = malleatedFilename .. '.' .. DIR_DELIM -- should work on both Linux and Windows + end + malleatedFilename = malleatedFilename .. schematicInfo.filename + schematicInfo.malleatedFilenames[uniqueId] = malleatedFilename + end + + --minetest.log("info", "Malleated file name for " .. uniqueId .. " is " .. schematicInfo.malleatedFilenames[uniqueId]) + return schematicInfo.malleatedFilenames[uniqueId] + end + + + -- Returns true if a tree in this location would be dead + -- (checks for desert) + SkyTrees.isDead = function(position) + local heat = get_heat(position) + local humidity = get_humidity(position) + + if humidity <= 10 or (humidity <= 20 and heat >= 80) then + return true + end + + local biomeId = interop.get_biome_key(position) + local biome = biomes[biomeId] + if biome ~= nil and biome.node_top ~= nil then + local modname, nodename = interop.split_nodename(biome.node_top) + if string.find(nodename, "sand") or string.find(nodename, "desert") then + return true + end + end + end + + + -- Returns the name of a suitable theme + -- Picks a theme from the schematicInfo automatically, based on the themes' relativeProbability, and location. + SkyTrees.selectTheme = function(position, schematicInfo, choiceSeed) + + local deadThemeName = "Dead" + + if schematicInfo.theme[deadThemeName] ~= nil then + -- Tree is dead and bleached in desert biomes + if SkyTrees.isDead(position) then + return deadThemeName + end + end + + if choiceSeed == nil then choiceSeed = 0 end + -- Use a known PRNG implementation + local prng = PcgRandom( + position.x * 65732 + + position.z * 729 + + schematicInfo.size.x * 3 + + choiceSeed + ) + + local sumProbabilities = 0 + for _,theme in pairs(schematicInfo.theme) do + sumProbabilities = sumProbabilities + theme.relativeProbability + end + + local selection = prng:next(0, sumProbabilities * 1000) / 1000 + if DEBUG_SKYTREES then minetest.log("info", "Skytrees x: "..position.x.." y: ".. position.y .. " sumProbabilities: " .. sumProbabilities .. ", selection: " .. selection) end + + sumProbabilities = 0 + for themeName,theme in pairs(schematicInfo.theme) do + if selection <= sumProbabilities + theme.relativeProbability then + return themeName + else + sumProbabilities = sumProbabilities + theme.relativeProbability + end + end + + error(SkyTrees.MODNAME .. " - SkyTrees.selectTheme failed to find a theme", 0) + return schematicInfo.defaultThemeName + end + + + -- position is a vector {x, y, z} + -- rotation must be either 0, 90, 180, or 270 + -- schematicInfo must be one of the items in SkyTrees.schematicInfo[] + -- topsoil [optional] is the biome's "node_top" - the ground node of the region. + SkyTrees.placeTree = function(position, rotation, schematicInfo, themeName, topsoil) + + if SkyTrees.disabled ~= nil then + error(SkyTrees.MODNAME .. " - SkyTrees are disabled: " .. SkyTrees.disabled, 0) + return + end + + -- returns a new position vector, rotated around (0, 0) to match the schematic rotation (provided the schematic_size is correct!) + local function rotatePositon(position, schematic_size, rotation) + local result = vector.new(position) + if rotation == 90 then + result.x = position.z + result.z = schematic_size.x - position.x - 1 + elseif rotation == 180 then + result.x = schematic_size.x - position.x - 1 + result.z = schematic_size.z - position.z - 1 + elseif rotation == 270 then + result.x = schematic_size.z - position.z - 1 + result.z = position.x + end + return result + end + + local rotatedCenter = rotatePositon(schematicInfo.center, schematicInfo.size, rotation) + local treePos = vector.subtract(position, rotatedCenter) + + if themeName == nil then themeName = SkyTrees.selectTheme(position, schematicInfo) end + local theme = schematicInfo.theme[themeName] + if theme == nil then error(MODNAME .. ' called SkyTrees.placeTree("' .. schematicInfo.filename .. '") with invalid theme: ' .. themeName, 0) end + if theme.init ~= nil then theme.init(theme, position) end + + if theme.hasSoil then + if topsoil == nil then + topsoil = 'ignore' + if minetest.get_biome_data == nil then error(SkyTrees.MODNAME .. " requires Minetest v5.0 or greater, or to have minor modifications to support v0.4.x", 0) end + local treeBiome = biomes[interop.get_biome_key(position)] + if treeBiome ~= nil and treeBiome.node_top ~= nil then topsoil = treeBiome.node_top end + end + else + topsoil = 'ignore' + end + + local nodeName_heart = SkyTrees.MODNAME .. ":HeartWood" + if not theme.hasHeart then nodeName_heart = 'ignore' end + + -- theme.init() may have changed the vineflags, so update the replacement node names + if theme.vineflags.hanging_leaves == true and SkyTrees.nodeName_hangingVine == 'ignore' then theme.vineflags.leaves = true end -- if there are no hanging vines then substitute side_vines + if theme.vineflags.leaves == true then theme.leaf_vines = SkyTrees.nodeName_sideVines else theme.leaf_vines = 'ignore' end + if theme.vineflags.bark == true then theme.bark_vines = SkyTrees.nodeName_sideVines else theme.bark_vines = 'ignore' end + if theme.vineflags.hanging_leaves == true then theme.hanging_leaf_vines = SkyTrees.nodeName_hangingVine else theme.hanging_leaf_vines = 'ignore' end + if theme.vineflags.hanging_bark == true then theme.hanging_bark_vines = SkyTrees.nodeName_hangingVine else theme.hanging_bark_vines = 'ignore' end + if theme.vineflags.hanging_roots == true and SkyTrees.nodeName_hangingRoot ~= 'ignore' then theme.hanging_bark_vines = SkyTrees.nodeName_hangingRoot end + + local replacements = { + ['treebark\r\n\r\n~~~ Cloudlands_tree mts by Dr.Frankenstone: Amateur Arborist ~~~\r\n\r\n'] = theme.bark, -- because this node name is always replaced, it can double as space for a text header in the file. + ['default:tree'] = theme.trunk, + ['default:leaves'] = theme.leaves1, + ['leaves_alt'] = theme.leaves2, + ['leaves_special'] = theme.leaves_special, + ['leaf_vines'] = theme.leaf_vines, + ['bark_vines'] = theme.bark_vines, + ['hanging_leaf_vines'] = theme.hanging_leaf_vines, + ['hanging_bark_vines'] = theme.hanging_bark_vines, + ['default:dirt'] = topsoil, + ['heart'] = nodeName_heart + } + + if minetest.global_exists("schemlib") then + -- Use schemlib instead minetest.place_schematic(), to avoid bugs in place_schematic() + + local filename = minetest.get_modpath(SkyTrees.MODNAME) .. DIR_DELIM .. schematicInfo.filename + local plan_obj = schemlib.plan.new() + plan_obj:read_from_schem_file(filename, replacements) + plan_obj.data.ground_y = -1 -- prevent read_from_schem_file() from automatically adjusting the height when it encounters dirt in the schematic (SkyTrees sometimes have dirt up in their nooks) + plan_obj.data.facedir = round(rotation / 90) + rotatedCenter = plan_obj:get_world_pos(vector.add(vector.multiply(schematicInfo.center, -1), -1), position) -- this function performs the rotation I require, even if it's named/intended for something else. + plan_obj.data.anchor_pos = rotatedCenter + + if DEBUG_SKYTREES then minetest.log("info", "building tree at " .. dump(position) .. "rotated to " .. dump(treePos) .. "rotatedCenter " .. dump(rotatedCenter) .. ", " .. schematicInfo.filename) end + plan_obj:set_status("build") + + else -- fall back on minetest.place_schematic() + + local malleatedFilename = SkyTrees.getMalleatedFilename(schematicInfo, themeName) + + if DEBUG_SKYTREES then minetest.log("info", "placing tree at " .. dump(position) .. "rotated to " .. dump(treePos) .. "rotatedCenter " .. dump(rotatedCenter) .. ", " .. schematicInfo.filename) end + + -- Defering minetest.place_schematic() until after the lua emerge seems to reduce the likelyhood of + -- having it draw the tree with pieces missing. + minetest.after( + 0.1, + function(treePos, malleatedFilename, rotation, replacements, schematicInfo) + + minetest.place_schematic(treePos, malleatedFilename, rotation, replacements, true) + + -- minetest.place_schematic() doesn't invoke node constructors, so use set_node() for any nodes requiring construction + for i, schematicCoords in pairs(schematicInfo.nodesWithConstructor) do + if rotation ~= 0 then schematicCoords = rotatePositon(schematicCoords, schematicInfo.size, rotation) end + local nodePos = vector.add(treePos, schematicCoords) + local nodeToConstruct = minetest.get_node(nodePos) + if nodeToConstruct.name == "air" or nodeToConstruct.name == nodeName_ignore then + --this is now normal - e.g. if vines are set to 'ignore' then the nodeToConstruct won't be there. + --minetest.log("error", "nodesWithConstructor["..i.."] does not match schematic " .. schematicInfo.filename .. " at " .. nodePos.x..","..nodePos.y..","..nodePos.z.." rotation "..rotation) + else + minetest.set_node(nodePos, nodeToConstruct) + end + end + + end, + treePos, malleatedFilename, rotation, replacements, schematicInfo + ) + + end + end + +end + +SkyTrees.init() + + +--[[============================== + Initialization and Mapgen + ==============================]]-- + +local function init_mapgen() + -- invoke get_perlin() here, since it can't be invoked before the environment + -- is created because it uses the world's seed value. + noise_eddyField = minetest.get_perlin(noiseparams_eddyField) + noise_heightMap = minetest.get_perlin(noiseparams_heightMap) + noise_density = minetest.get_perlin(noiseparams_density) + noise_surfaceMap = minetest.get_perlin(noiseparams_surfaceMap) + noise_skyReef = minetest.get_perlin(noiseparams_skyReef) + + local prng = PcgRandom(122456 + ISLANDS_SEED) + for i = 0,255 do randomNumbers[i] = prng:next(0, 0x10000) / 0x10000 end + + if isMapgenV6 then + biomes["Normal"] = {node_top="mapgen_dirt_with_grass", node_filler="mapgen_dirt", node_stone="mapgen_stone"} + biomes["Desert"] = {node_top="mapgen_desert_sand", node_filler="mapgen_desert_sand", node_stone="mapgen_desert_stone"} + biomes["Jungle"] = {node_top="mapgen_dirt_with_grass", node_filler="mapgen_dirt", node_stone="mapgen_stone"} + biomes["Tundra"] = {node_top="mapgen_dirt_with_snow", node_filler="mapgen_dirt", node_stone="mapgen_stone"} + biomes["Taiga"] = {node_top="mapgen_dirt_with_snow", node_filler="mapgen_dirt", node_stone="mapgen_stone"} + else + for k,v in pairs(minetest.registered_biomes) do + biomes[minetest.get_biome_id(k)] = v + end + end + if DEBUG then minetest.log("info", "registered biomes: " .. dump(biomes)) end + + nodeId_air = minetest.get_content_id("air") + + nodeId_stone = interop.find_node_id(NODENAMES_STONE) + nodeId_grass = interop.find_node_id(NODENAMES_GRASS) + nodeId_dirt = interop.find_node_id(NODENAMES_DIRT) + nodeId_water = interop.find_node_id(NODENAMES_WATER) + nodeId_ice = interop.find_node_id(NODENAMES_ICE) + nodeId_silt = interop.find_node_id(NODENAMES_SILT) + nodeId_gravel = interop.find_node_id(NODENAMES_GRAVEL) + nodeId_vine = interop.find_node_id(NODENAMES_VINES) + nodeName_vine = minetest.get_name_from_content_id(nodeId_vine) + + local regionRectStr = minetest.settings:get(MODNAME .. "_limit_rect") + if type(regionRectStr) == "string" then + local minXStr, minZStr, maxXStr, maxZStr = string.match(regionRectStr, '(-?[%d%.]+)[,%s]+(-?[%d%.]+)[,%s]+(-?[%d%.]+)[,%s]+(-?[%d%.]+)') + if minXStr ~= nil then + local minX, minZ, maxX, maxZ = tonumber(minXStr), tonumber(minZStr), tonumber(maxXStr), tonumber(maxZStr) + if minX ~= nil and maxX ~= nil and minX < maxX then + region_min_x, region_max_x = minX, maxX + end + if minZ ~= nil and maxZ ~= nil and minZ < maxZ then + region_min_z, region_max_z = minZ, maxZ + end + end + end + + local limitToBiomesStr = minetest.settings:get(MODNAME .. "_limit_biome") + if type(limitToBiomesStr) == "string" and string.len(limitToBiomesStr) > 0 then + limit_to_biomes = limitToBiomesStr:lower() + end + limit_to_biomes_altitude = tonumber(minetest.settings:get(MODNAME .. "_limit_biome_altitude")) + + region_restrictions = + region_min_x > -32000 or region_min_z > -32000 + or region_max_x < 32000 or region_max_z < 32000 + or limit_to_biomes ~= nil +end + +-- Updates coreList to include all cores of type coreType within the given bounds +local function addCores(coreList, coreType, x1, z1, x2, z2) + + -- this function is used by the API functions, so may be invoked without our on_generated + -- being called + cloudlands.init() + + for z = math_floor(z1 / coreType.territorySize), math_floor(z2 / coreType.territorySize) do + for x = math_floor(x1 / coreType.territorySize), math_floor(x2 / coreType.territorySize) do + + -- Use a known PRNG implementation, to make life easier for Amidstest + local prng = PcgRandom( + x * 8973896 + + z * 7467838 + + worldSeed + 8438 + ISLANDS_SEED + ) + + local coresInTerritory = {} + + for i = 1, coreType.coresPerTerritory do + local coreX = x * coreType.territorySize + prng:next(0, coreType.territorySize - 1) + local coreZ = z * coreType.territorySize + prng:next(0, coreType.territorySize - 1) + + -- there's strong vertical and horizontal tendency in 2-octave noise, + -- so rotate it a little to avoid it lining up with the world axis. + local noiseX = ROTATE_COS * coreX - ROTATE_SIN * coreZ + local noiseZ = ROTATE_SIN * coreX + ROTATE_COS * coreZ + local eddyField = noise_eddyField:get2d({x = noiseX, y = noiseZ}) + + if (math_abs(eddyField) < coreType.frequency) then + + local nexusConditionMet = not coreType.requiresNexus + if not nexusConditionMet then + -- A 'nexus' is a made up name for a place where the eddyField is flat. + -- There are often many 'field lines' leading out from a nexus. + -- Like a saddle in the perlin noise the height "coreType.frequency" + local eddyField_orthA = noise_eddyField:get2d({x = noiseX + 2, y = noiseZ}) + local eddyField_orthB = noise_eddyField:get2d({x = noiseX, y = noiseZ + 2}) + if math_abs(eddyField - eddyField_orthA) + math_abs(eddyField - eddyField_orthB) < 0.02 then + nexusConditionMet = true + end + end + + if nexusConditionMet then + local radius = (coreType.radiusMax + prng:next(0, coreType.radiusMax) * 2) / 3 -- give a 33%/66% weighting split between max-radius and random + local depth = (coreType.depthMax + prng:next(0, coreType.depthMax) * 2) / 2 -- ERROR!! fix this bug! should be dividing by 3. But should not change worldgen now, so adjust depthMax of islands so nothing changes when bug is fixed? + local thickness = prng:next(0, coreType.thicknessMax) + + + if coreX >= x1 and coreX < x2 and coreZ >= z1 and coreZ < z2 then + + local spaceConditionMet = not coreType.exclusive + if not spaceConditionMet then + -- see if any other cores occupy this space, and if so then + -- either deny the core, or raise it + spaceConditionMet = true + local minDistSquared = radius * radius * .7 + + for _,core in ipairs(coreList) do + if core.type.radiusMax == coreType.radiusMax then + -- We've reached the cores of the current type. We can't exclude based on all + -- cores of the same type as we can't be sure neighboring territories will have been generated. + break + end + if (core.x - coreX)*(core.x - coreX) + (core.z - coreZ)*(core.z - coreZ) <= minDistSquared + core.radius * core.radius then + spaceConditionMet = false + break + end + end + if spaceConditionMet then + for _,core in ipairs(coresInTerritory) do + -- We can assume all cores of the current type are being generated in this territory, + -- so we can exclude the core if it overlaps one already in this territory. + if (core.x - coreX)*(core.x - coreX) + (core.z - coreZ)*(core.z - coreZ) <= minDistSquared + core.radius * core.radius then + spaceConditionMet = false + break + end + end + end + end + + if spaceConditionMet then + -- all conditions met, we've located a new island core + --minetest.log("Adding core "..x..","..y..","..z..","..radius) + local y = round(noise_heightMap:get2d({x = coreX, y = coreZ})) + local newCore = { + x = coreX, + y = y, + z = coreZ, + radius = radius, + thickness = thickness, + depth = depth, + type = coreType, + } + coreList[#coreList + 1] = newCore + coresInTerritory[#coreList + 1] = newCore + end + + else + -- We didn't test coreX,coreZ against x1,z1,x2,z2 immediately and save all + -- that extra work, as that would break the determinism of the prng calls. + -- i.e. if the area was approached from a different direction then a + -- territory might end up with a different list of cores. + -- TODO: filter earlier but advance prng? + end + end + end + end + end + end +end + + +-- removes any islands that fall outside region restrictions specified in the options +local function removeUnwantedIslands(coreList) + + local testBiome = limit_to_biomes ~= nil + local get_biome_name = nil + if testBiome then + -- minetest.get_biome_name() was added in March 2018, we'll ignore the + -- limit_to_biomes option on versions of Minetest that predate this + get_biome_name = minetest.get_biome_name + testBiome = get_biome_name ~= nil + if get_biome_name == nil then + minetest.log("warning", MODNAME .. " ignoring " .. MODNAME .. "_limit_biome option as Minetest API version too early to support get_biome_name()") + limit_to_biomes = nil + end + end + + for i = #coreList, 1, -1 do + local core = coreList[i] + local coreX = core.x + local coreZ = core.z + + if coreX < region_min_x or coreX > region_max_x or coreZ < region_min_z or coreZ > region_max_z then + table.remove(coreList, i) + + elseif testBiome then + local biomeAltitude + if (limit_to_biomes_altitude == nil) then biomeAltitude = ALTITUDE + core.y else biomeAltitude = limit_to_biomes_altitude end + + local biomeName = get_biome_name(minetest.get_biome_data({x = coreX, y = biomeAltitude, z = coreZ}).biome) + if not string.match(limit_to_biomes, biomeName:lower()) then + table.remove(coreList, i) + end + end + end +end + + +-- gets an array of all cores which may intersect the (minp, maxp) area +-- y is ignored +cloudlands.get_island_details = function(minp, maxp) + local result = {} + + for _,coreType in pairs(cloudlands.coreTypes) do + addCores( + result, + coreType, + minp.x - coreType.radiusMax, + minp.z - coreType.radiusMax, + maxp.x + coreType.radiusMax, + maxp.z + coreType.radiusMax + ) + end + + -- remove islands only after cores have all generated to avoid the restriction + -- settings from rearranging islands. + if region_restrictions then removeUnwantedIslands(result) end + + return result +end + + +cloudlands.find_nearest_island = function(x, z, search_radius) + + local coreList = {} + for _,coreType in pairs(cloudlands.coreTypes) do + addCores( + coreList, + coreType, + x - (search_radius + coreType.radiusMax), + z - (search_radius + coreType.radiusMax), + x + (search_radius + coreType.radiusMax), + z + (search_radius + coreType.radiusMax) + ) + end + -- remove islands only after cores have all generated to avoid the restriction + -- settings from rearranging islands. + if region_restrictions then removeUnwantedIslands(coreList) end + + local result = nil + for _,core in ipairs(coreList) do + local distance = math.hypot(core.x - x, core.z - z) + if distance >= core.radius then + core.distance = 1 + distance - core.radius + else + -- distance is fractional + core.distance = distance / (core.radius + 1) + end + + if result == nil or core.distance < result.distance then result = core end + end + + return result +end + + +-- coreList can be left as null, but if you wish to sample many heights in a small area +-- then use cloudlands.get_island_details() to get the coreList for that area and save +-- having to recalculate it during each call to get_height_at(). +cloudlands.get_height_at = function(x, z, coreList) + + local result, isWater = nil, false + + if coreList == nil then + local pos = {x = x, z = z} + coreList = cloudlands.get_island_details(pos, pos) + end + + for _,core in ipairs(coreList) do + + -- duplicates the code from renderCores() to find surface height + -- See the renderCores() version for explanatory comments + local horz_easing + local distanceSquared = (x - core.x)*(x - core.x) + (z - core.z)*(z - core.z) + local radiusSquared = core.radius * core.radius + + local noise_weighting = 1 + local shapeType = math_floor(core.depth + core.radius + core.x) % 5 + if shapeType < 2 then -- convex, see renderCores() implementatin for comments + horz_easing = 1 - distanceSquared / radiusSquared + elseif shapeType == 2 then -- conical, see renderCores() implementatin for comments + horz_easing = 1 - math_sqrt(distanceSquared) / core.radius + else -- concave, see renderCores() implementatin for comments + local radiusRoot = math_sqrt(core.radius) + local squared = 1 - distanceSquared / radiusSquared + local distance = math_sqrt(distanceSquared) + local distance_normalized = distance / core.radius + local root = 1 - math_sqrt(distance) / radiusRoot + horz_easing = math_min(1, 0.8*distance_normalized*squared + 1.2*(1-distance_normalized)*root) + noise_weighting = 0.63 + end + if core.radius + core.depth > 80 then noise_weighting = 0.6 end + if core.radius + core.depth > 120 then noise_weighting = 0.35 end + + local surfaceNoise = noise_surfaceMap:get2d({x = x, y = z}) + if DEBUG_GEOMETRIC then surfaceNoise = SURFACEMAP_OFFSET end + local coreTop = ALTITUDE + core.y + local surfaceHeight = coreTop + round(surfaceNoise * 3 * (core.thickness + 1) * horz_easing) + + if result == nil or math_max(coreTop, surfaceHeight) > result then + + local coreBottom = math_floor(coreTop - (core.thickness + core.depth)) + local yBottom = coreBottom + if result ~= nil then yBottom = math_max(yBottom, result + 1) end + + for y = math_max(coreTop, surfaceHeight), yBottom, -1 do + local vert_easing = math_min(1, (y - coreBottom) / core.depth) + + local densityNoise = noise_density:get3d({x = x, y = y - coreTop, z = z}) + densityNoise = noise_weighting * densityNoise + (1 - noise_weighting) * DENSITY_OFFSET + if DEBUG_GEOMETRIC then densityNoise = DENSITY_OFFSET end + + if densityNoise * ((horz_easing + vert_easing) / 2) >= REQUIRED_DENSITY then + result = y + isWater = surfaceNoise < 0 + break + + --[[abandoned because do we need to calc the bottom of ponds? It also needs the outer code refactored to work + if not isWater then + -- we've found the land height + break + else + -- find the pond bottom, since the water level is already given by (ALTITUDE + island.y) + local surfaceDensity = densityNoise * ((horz_easing + 1) / 2) + local onTheEdge = math_sqrt(distanceSquared) + 1 >= core.radius + if onTheEdge or surfaceDensity > (REQUIRED_DENSITY + core.type.pondWallBuffer) then + break + end + end]] + end + end + end + end + + return result, isWater +end + + +local function setCoreBiomeData(core) + local pos = {x = core.x, y = ALTITUDE + core.y, z = core.z} + if LOWLAND_BIOMES then pos.y = LOWLAND_BIOME_ALTITUDE end + core.biomeId = interop.get_biome_key(pos) + core.biome = biomes[core.biomeId] + core.temperature = get_heat(pos) + core.humidity = get_humidity(pos) + + if core.temperature == nil then core.temperature = 50 end + if core.humidity == nil then core.humidity = 50 end + + if core.biome == nil then + -- Some games don't use the biome list, so come up with some fallbacks + core.biome = {} + core.biome.node_top = minetest.get_name_from_content_id(nodeId_grass) + core.biome.node_filler = minetest.get_name_from_content_id(nodeId_dirt) + end + +end + +local function addDetail_vines(decoration_list, core, data, area, minp, maxp) + + if VINE_COVERAGE > 0 and nodeId_vine ~= nodeId_ignore then + + local y = ALTITUDE + core.y + if y >= minp.y and y <= maxp.y then + -- if core.biome is nil then renderCores() never rendered it, which means it + -- doesn't instersect this draw region. + if core.biome ~= nil and core.humidity >= VINES_REQUIRED_HUMIDITY and core.temperature >= VINES_REQUIRED_TEMPERATURE then + + local nodeId_top + local nodeId_filler + local nodeId_stoneBase + local nodeId_dust + if core.biome.node_top == nil then nodeId_top = nodeId_stone else nodeId_top = minetest.get_content_id(core.biome.node_top) end + if core.biome.node_filler == nil then nodeId_filler = nodeId_stone else nodeId_filler = minetest.get_content_id(core.biome.node_filler) end + if core.biome.node_stone == nil then nodeId_stoneBase = nodeId_stone else nodeId_stoneBase = minetest.get_content_id(core.biome.node_stone) end + if core.biome.node_dust == nil then nodeId_dust = nodeId_stone else nodeId_dust = minetest.get_content_id(core.biome.node_dust) end + + local function isIsland(nodeId) + return (nodeId == nodeId_filler or nodeId == nodeId_top + or nodeId == nodeId_stoneBase or nodeId == nodeId_dust + or nodeId == nodeId_silt or nodeId == nodeId_water) + end + + local function findHighestNodeFace(y, solidIndex, emptyIndex) + -- return the highest y value (or maxp.y) where solidIndex is part of an island + -- and emptyIndex is not + local yOffset = 1 + while y + yOffset <= maxp.y and isIsland(data[solidIndex + yOffset * area.ystride]) and not isIsland(data[emptyIndex + yOffset * area.ystride]) do + yOffset = yOffset + 1 + end + return y + yOffset - 1 + end + + local radius = round(core.radius) + local xCropped = math_min(maxp.x, math_max(minp.x, core.x)) + local zStart = math_max(minp.z, core.z - radius) + local vi = area:index(xCropped, y, zStart) + + for z = 0, math_min(maxp.z, core.z + radius) - zStart do + local searchIndex = vi + z * area.zstride + if isIsland(data[searchIndex]) then + + -- add vines to east face + if randomNumbers[(zStart + z + y) % 256] <= VINE_COVERAGE then + for x = xCropped + 1, maxp.x do + if not isIsland(data[searchIndex + 1]) then + local yhighest = findHighestNodeFace(y, searchIndex, searchIndex + 1) + decoration_list[#decoration_list + 1] = {pos={x=x, y=yhighest, z= zStart + z}, node={name = nodeName_vine, param2 = 3}} + break + end + searchIndex = searchIndex + 1 + end + end + -- add vines to west face + if randomNumbers[(zStart + z + y + 128) % 256] <= VINE_COVERAGE then + searchIndex = vi + z * area.zstride + for x = xCropped - 1, minp.x, -1 do + if not isIsland(data[searchIndex - 1]) then + local yhighest = findHighestNodeFace(y, searchIndex, searchIndex - 1) + decoration_list[#decoration_list + 1] = {pos={x=x, y=yhighest, z= zStart + z}, node={name = nodeName_vine, param2 = 2}} + break + end + searchIndex = searchIndex - 1 + end + end + end + end + + local zCropped = math_min(maxp.z, math_max(minp.z, core.z)) + local xStart = math_max(minp.x, core.x - radius) + local zstride = area.zstride + vi = area:index(xStart, y, zCropped) + + for x = 0, math_min(maxp.x, core.x + radius) - xStart do + local searchIndex = vi + x + if isIsland(data[searchIndex]) then + + -- add vines to north face (make it like moss - grows better on the north side) + if randomNumbers[(xStart + x + y) % 256] <= (VINE_COVERAGE * 1.2) then + for z = zCropped + 1, maxp.z do + if not isIsland(data[searchIndex + zstride]) then + local yhighest = findHighestNodeFace(y, searchIndex, searchIndex + zstride) + decoration_list[#decoration_list + 1] = {pos={x=xStart + x, y=yhighest, z=z}, node={name = nodeName_vine, param2 = 5}} + break + end + searchIndex = searchIndex + zstride + end + end + -- add vines to south face (make it like moss - grows better on the north side) + if randomNumbers[(xStart + x + y + 128) % 256] <= (VINE_COVERAGE * 0.8) then + searchIndex = vi + x + for z = zCropped - 1, minp.z, -1 do + if not isIsland(data[searchIndex - zstride]) then + local yhighest = findHighestNodeFace(y, searchIndex, searchIndex - zstride) + decoration_list[#decoration_list + 1] = {pos={x=xStart + x, y=yhighest, z=z}, node={name = nodeName_vine, param2 = 4}} + break + end + searchIndex = searchIndex - zstride + end + end + end + end + + end + end + end +end + + +-- A rare formation of rocks circling or crowning an island +-- returns true if voxels were changed +local function addDetail_skyReef(decoration_list, core, data, area, minp, maxp) + + local coreTop = ALTITUDE + core.y + local overdrawTop = maxp.y + OVERDRAW + local reefAltitude = math_floor(coreTop - 1 - core.thickness / 2) + local reefMaxHeight = 12 + local reefMaxUnderhang = 4 + + if (maxp.y < reefAltitude - reefMaxUnderhang) or (minp.y > reefAltitude + reefMaxHeight) then + --no reef here + return false + end + + local isReef = core.radius < core.type.radiusMax * 0.4 -- a reef can't extend beyond radiusMax, so needs a small island + local isAtoll = core.radius > core.type.radiusMax * 0.8 + if not (isReef or isAtoll) then return false end + + local fastHash = 3 + fastHash = (37 * fastHash) + core.x + fastHash = (37 * fastHash) + core.z + fastHash = (37 * fastHash) + math_floor(core.radius) + fastHash = (37 * fastHash) + math_floor(core.depth) + if ISLANDS_SEED ~= 1000 then fastHash = (37 * fastHash) + ISLANDS_SEED end + local rarityAdj = 1 + if core.type.requiresNexus and isAtoll then rarityAdj = 4 end -- humongous islands are very rare, and look good as an atoll + if (REEF_RARITY * rarityAdj * 1000) < math_floor((math_abs(fastHash)) % 1000) then return false end + + local coreX = core.x --save doing a table lookup in the loop + local coreZ = core.z --save doing a table lookup in the loop + + -- Use a known PRNG implementation + local prng = PcgRandom( + coreX * 8973896 + + coreZ * 7467838 + + worldSeed + 32564 + ) + + local reefUnderhang + local reefOuterRadius = math_floor(core.type.radiusMax) + local reefInnerRadius = prng:next(core.type.radiusMax * 0.5, core.type.radiusMax * 0.7) + local reefWidth = reefOuterRadius - reefInnerRadius + local noiseOffset = 0 + + if isReef then + reefMaxHeight = round((core.thickness + 4) / 2) + reefUnderhang = round(reefMaxHeight / 2) + noiseOffset = -0.1 + end + if isAtoll then + -- a crown attached to the island + reefOuterRadius = math_floor(core.radius * 0.8) + reefWidth = math_max(4, math_floor(core.radius * 0.15)) + reefInnerRadius = reefOuterRadius - reefWidth + reefUnderhang = 0 + if maxp.y < reefAltitude - reefUnderhang then return end -- no atoll here + end + + local reefHalfWidth = reefWidth / 2 + local reefMiddleRadius = (reefInnerRadius + reefOuterRadius) / 2 + local reefOuterRadiusSquared = reefOuterRadius * reefOuterRadius + local reefInnerRadiusSquared = reefInnerRadius * reefInnerRadius + local reefMiddleRadiusSquared = reefMiddleRadius * reefMiddleRadius + local reefHalfWidthSquared = reefHalfWidth * reefHalfWidth + + -- get the biome details for this core + local nodeId_first + local nodeId_second + local nodeId_top + local nodeId_filler + if core.biome == nil then setCoreBiomeData(core) end -- We can't assume the core biome has already been resolved, core might not have been big enough to enter the draw region + if core.biome.node_top == nil then nodeId_top = nodeId_stone else nodeId_top = minetest.get_content_id(core.biome.node_top) end + if core.biome.node_filler == nil then nodeId_filler = nodeId_stone else nodeId_filler = minetest.get_content_id(core.biome.node_filler) end + if core.biome.node_dust ~= nil then + nodeId_first = minetest.get_content_id(core.biome.node_dust) + nodeId_second = nodeId_top + else + nodeId_first = nodeId_top + nodeId_second = nodeId_filler + end + + local zStart = round(math_max(core.z - reefOuterRadius, minp.z)) + local zStop = round(math_min(core.z + reefOuterRadius, maxp.z)) + local xStart = round(math_max(core.x - reefOuterRadius, minp.x)) + local xStop = round(math_min(core.x + reefOuterRadius, maxp.x)) + local yCenter = math_min(math_max(reefAltitude, minp.y), maxp.y) + local pos = {} + + local dataBufferIndex = area:index(xStart, yCenter, zStart) + local vi = -1 + for z = zStart, zStop do + local zDistSquared = (z - coreZ) * (z - coreZ) + pos.y = z + for x = xStart, xStop do + local distanceSquared = (x - coreX) * (x - coreX) + zDistSquared + if distanceSquared < reefOuterRadiusSquared and distanceSquared > reefInnerRadiusSquared then + pos.x = x + local offsetEase = math_abs(distanceSquared - reefMiddleRadiusSquared) / reefHalfWidthSquared + local fineNoise = noise_skyReef:get2d(pos) + local reefNoise = (noiseOffset* offsetEase) + fineNoise + 0.2 * noise_surfaceMap:get2d(pos) + + if (reefNoise > 0) then + local distance = math_sqrt(distanceSquared) + local ease = 1 - math_abs(distance - reefMiddleRadius) / reefHalfWidth + local yStart = math_max(math_floor(reefAltitude - ease * fineNoise * reefUnderhang), minp.y) + local yStop = math_min(math_floor(reefAltitude + ease * reefNoise * reefMaxHeight), overdrawTop) + + for y = yStart, yStop do + vi = dataBufferIndex + (y - yCenter) * area.ystride + if data[vi] == nodeId_air then + if y == yStop then + data[vi] = nodeId_first + elseif y == yStop - 1 then + data[vi] = nodeId_second + else + data[vi] = nodeId_filler + end + end + end + end + end + dataBufferIndex = dataBufferIndex + 1 + end + dataBufferIndex = dataBufferIndex + area.zstride - (xStop - xStart + 1) + end + + return vi >= 0 +end + +-- A rarely occuring giant tree growing from the center of the island +-- returns true if tree was added +local function addDetail_skyTree(decoration_list, core, minp, maxp) + + if (core.radius < SkyTrees.minimumIslandRadius) or (core.depth < SkyTrees.minimumIslandDepth) then + --no tree here + return false + end + + local coreTop = ALTITUDE + core.y + local treeAltitude = math_floor(coreTop + core.thickness) + + if (maxp.y < treeAltitude - SkyTrees.maximumYOffset) or (minp.y > treeAltitude + SkyTrees.maximumHeight) then + --no tree here + return false + elseif SkyTrees.disabled ~= nil then + -- can't find nodes/textures in this game that are needed to build trees + return false + end + + local coreX = core.x --save doing a table lookups + local coreZ = core.z --save doing a table lookups + + local fastHash = 3 + fastHash = (37 * fastHash) + coreX + fastHash = (37 * fastHash) + coreZ + fastHash = (37 * fastHash) + math_floor(core.radius) + fastHash = (37 * fastHash) + math_floor(core.depth) + fastHash = (37 * fastHash) + ISLANDS_SEED + fastHash = (37 * fastHash) + 76276 -- to keep this probability distinct from reefs and atols + if (TREE_RARITY * 1000) < math_floor((math_abs(fastHash)) % 1000) then return false end + + -- choose a tree that will fit on the island + local tree + + local skipLargeTree = (fastHash % 10) < 3 -- to allow small trees a chance to spawn on large islands + if skipLargeTree then + if SkyTrees.isDead({x = coreX, y = treeAltitude, z = coreZ}) then + -- small tree currently doesn't have a dead theme, so don't skip the large tree + skipLargeTree = false + end + end + + + for i, treeType in pairs(SkyTrees.schematicInfo) do + if i == 1 and skipLargeTree then + -- 'continue', to allow small trees a chance to spawn on large islands + elseif (core.radius >= treeType.requiredIslandRadius) and (core.depth >= treeType.requiredIslandDepth) then + tree = treeType + break + end + end + + local maxOffsetFromCenter = core.radius - (tree.requiredIslandRadius - 4) -- 4 is an arbitrary number, to allow trees to get closer to the edge + + -- Use a known PRNG implementation + local prng = PcgRandom( + coreX * 8973896 + + coreZ * 7467838 + + worldSeed + 43786 + ) + + local treeAngle = 90 * prng:next(0, 3) + local treePos = { + x = coreX + math_floor((prng:next(-maxOffsetFromCenter, maxOffsetFromCenter) + prng:next(-maxOffsetFromCenter, maxOffsetFromCenter)) / 2), + y = treeAltitude, + z = coreZ + math_floor((prng:next(-maxOffsetFromCenter, maxOffsetFromCenter) + prng:next(-maxOffsetFromCenter, maxOffsetFromCenter)) / 2) + } + + if minetest.global_exists("schemlib") then + -- This check is skipped when not using schemlib, because while redrawing the tree multiple times - every time a chunk it + -- touches gets emitted - might be slower, it helps work around the bugs in minetest.place_schematic() where large schematics + -- are spawned incompletely. + -- The bug in question: https://forum.minetest.net/viewtopic.php?f=6&t=22136 + -- (it isn't an issue if schemlib is used) + if (maxp.y < treePos.y) or (minp.y > treePos.y) or (maxp.x < treePos.x) or (minp.x > treePos.x) or (maxp.z < treePos.z) or (minp.z > treePos.z) then + -- Now that we know the exact position of the tree, we know it's spawn point is not in this chunk. + -- In the interests of only drawing trees once, we only invoke placeTree when the chunk containing treePos is emitted. + return false + end + end + + if tree.theme["Dead"] == nil then + if SkyTrees.isDead(treePos) then + -- Trees in this location should be dead, but this tree doesn't have a dead theme, so don't put a tree here + return false + end + end + + if core.biome == nil then setCoreBiomeData(core) end -- We shouldn't assume the core biome has already been resolved, it might be below the emerged chunk and unrendered + + if core.biome.node_top == nil then + -- solid stone isn't fertile enough for giant trees, and there's a solid stone biome in MT-Game: tundra_highland + return false + end + + if DEBUG_SKYTREES then minetest.log("info", "core x: "..coreX.." y: ".. coreZ .. " treePos: " .. treePos.x .. ", y: " .. treePos.y) end + + SkyTrees.placeTree(treePos, treeAngle, tree, nil, core.biome.node_top) + return true +end + + +------------------------------------------------------------------------------ +-- Secrets section +------------------------------------------------------------------------------ + +-- We might not need this stand-in cobweb, but unless we go overboard on listing many +-- optional dependencies we won't know whether there's a proper cobweb available to +-- use until after it's too late to register this one. +local nodeName_standinCobweb = MODNAME .. ":cobweb" +minetest.register_node( + nodeName_standinCobweb, + { + tiles = { + -- [Ab]Use the crack texture to avoid needing to include a cobweb texture + -- crack_anylength.png is required by the engine, so all games will have it. + "crack_anylength.png^[verticalframe:5:4^[brighten" + }, + description = S("Cobweb"), + groups = {snappy = 3, liquid = 3, flammable = 3, not_in_creative_inventory = 1}, + drawtype = "plantlike", + walkable = false, + liquid_viscosity = 8, + liquidtype = "source", + liquid_alternative_flowing = nodeName_standinCobweb, + liquid_alternative_source = nodeName_standinCobweb, + liquid_renewable = false, + liquid_range = 0, + sunlight_propagates = true, + paramtype = "light" + } +) + + +local nodeName_egg = "secret:fossilized_egg" +local eggTextureBaseName = interop.find_node_texture({"default:jungleleaves", "mcl_core:jungleleaves", "ethereal:frost_leaves", "main:leaves"}) + +-- [Ab]Use a leaf texture. Originally this was to avoid needing to include an egg texture (extra files) and +-- exposing that the mod contains secrets, however both those reasons are obsolete and the mod could have textures +-- added in future +local eggTextureName = eggTextureBaseName.."^[colorize:#280040E0^[noalpha" + +-- Since "secret:fossilized_egg" doesn't use this mod's name for the prefix, we can't assume +-- another mod isn't also using/providing it +if minetest.registered_nodes[nodeName_egg] == nil then + + local fossilSounds = nil + local nodeName_stone = interop.find_node_name(NODENAMES_STONE) + if nodeName_stone ~= nodeName_ignore then fossilSounds = minetest.registered_nodes[nodeName_stone].sounds end + + minetest.register_node( + ":"..nodeName_egg, + { + tiles = { eggTextureName }, + description = S("Fossilized Egg"), + groups = { + oddly_breakable_by_hand = 3, -- MTG + handy = 1, -- MCL + stone = 1, -- Crafter needs to know the material in order to be breakable by hand + not_in_creative_inventory = 1 + }, + _mcl_hardness = 0.4, + sounds = fossilSounds, + drawtype = "nodebox", + paramtype = "light", + node_box = { + type = "fixed", + fixed = { + {-0.066666, -0.5, -0.066666, 0.066666, 0.5, 0.066666}, -- column1 + {-0.133333, -0.476667, -0.133333, 0.133333, 0.42, 0.133333}, -- column2 + {-0.2, -0.435, -0.2, 0.2, 0.31, 0.2 }, -- column3 + {-0.2, -0.36, -0.28, 0.2, 0.16667, 0.28 }, -- side1 + {-0.28, -0.36, -0.2, 0.28, 0.16667, 0.2 } -- side2 + } + } + } + ) +end + +-- Allow the player to craft their egg into an egg in a display case +local nodeName_eggDisplay = nodeName_egg .. "_display" +local nodeName_frameGlass = interop.find_node_name(NODENAMES_FRAMEGLASS) +local woodTexture = interop.find_node_texture(NODENAMES_WOOD) +local frameTexture = nil +if woodTexture ~= nil then + -- perhaps it's time for cloudlands to contain textures. + frameTexture = "([combine:16x16:0,0="..woodTexture.."\\^[colorize\\:black\\:170:1,1="..woodTexture.."\\^[colorize\\:#0F0\\:255\\^[resize\\:14x14^[makealpha:0,255,0)" + --frameTexture = "([combine:16x16:0,0="..woodTexture.."\\^[resize\\:16x16\\^[colorize\\:black\\:170:1,1="..woodTexture.."\\^[colorize\\:#0F0\\:255\\^[resize\\:14x14^[makealpha:0,255,0)" + --frameTexture = "(("..woodTexture.."^[colorize:black:170)^([combine:16x16:1,1="..woodTexture.."\\^[resize\\:14x14\\^[colorize\\:#0F0\\:255))" +end + +-- Since "secret:fossilized_egg_display" doesn't use this mod's name as the prefix, we shouldn't +-- assume another mod isn't also using/providing it. +if frameTexture ~= nil and nodeName_frameGlass ~= nodeName_ignore and minetest.registered_nodes[nodeName_eggDisplay] == nil then + minetest.register_node( + ":"..nodeName_eggDisplay, + { + tiles = { eggTextureName .. "^" .. frameTexture }, + description = S("Fossil Display"), + groups = { + oddly_breakable_by_hand = 3, + glass = 1, -- Crafter needs to know the material in order to be breakable by hand + not_in_creative_inventory = 1}, + _mcl_hardness = 0.2, + drop = "", + sounds = minetest.registered_nodes[nodeName_frameGlass].sounds, + drawtype = "nodebox", + paramtype = "light", + node_box = { + type = "fixed", + fixed = { + {-0.066666, -0.5, -0.066666, 0.066666, 0.4375, 0.066666}, -- column1 + {-0.133333, -0.5, -0.133333, 0.133333, 0.375, 0.133333}, -- column2 + {-0.2, -0.4375, -0.2, 0.2, 0.285, 0.2 }, -- column3 + {-0.2, -0.36, -0.28, 0.2, 0.14, 0.28 }, -- side1 + {-0.28, -0.36, -0.2, 0.28, 0.14, 0.2 }, -- side2 + + -- corner frame (courtesy of NodeBox Editor Abuse mod) + {-0.4375, 0.4375, 0.4375, 0.4375, 0.5, 0.5}, + {-0.4375, -0.5, 0.4375, 0.4375, -0.4375, 0.5}, + {-0.5, -0.5, 0.4375, -0.4375, 0.5, 0.5}, + {0.4375, -0.5, 0.4375, 0.5, 0.5, 0.5}, + {-0.5, 0.4375, -0.4375, -0.4375, 0.5, 0.4375}, + {-0.5, -0.5, -0.4375, -0.4375, -0.4375, 0.4375}, + {0.4375, 0.4375, -0.4375, 0.5, 0.5, 0.4375}, + {0.4375, -0.5, -0.4375, 0.5, -0.4375, 0.4375}, + {-0.5, 0.4375, -0.5, 0.5, 0.5, -0.4375}, + {-0.5, -0.5, -0.5, 0.5, -0.4375, -0.4375}, + {0.4375, -0.4375, -0.5, 0.5, 0.4375, -0.4375}, + {-0.5, -0.4375, -0.5, -0.4375, 0.4375, -0.4375} + } + }, + after_destruct = function(pos,node) + minetest.set_node(pos, {name = nodeName_egg, param2 = node.param2}) + end, + } + ) + + if minetest.get_modpath("xpanes") ~= nil then + minetest.register_craft({ + output = nodeName_eggDisplay, + recipe = { + {"group:stick", "group:pane", "group:stick"}, + {"group:pane", nodeName_egg, "group:pane"}, + {"group:stick", "group:pane", "group:stick"} + } + }) + else + -- Game doesn't have glass panes, so just use glass + minetest.register_craft({ + output = nodeName_eggDisplay, + recipe = { + {"group:stick", nodeName_frameGlass, "group:stick"}, + {nodeName_frameGlass, nodeName_egg, nodeName_frameGlass}, + {"group:stick", nodeName_frameGlass, "group:stick"} + } + }) + end +end + +local nodeId_egg = minetest.get_content_id(nodeName_egg) +local nodeId_airStandIn = minetest.get_content_id(interop.register_clone("air")) + +-- defer assigning the following until all mods are loaded +local nodeId_bed_top +local nodeId_bed_bottom +local nodeId_torch +local nodeId_chest +local nodeId_bookshelf +local nodeId_junk +local nodeId_anvil +local nodeId_workbench +local nodeId_cobweb +local nodeName_bookshelf +local isMineCloneBookshelf + +local function addDetail_secrets(decoration_list, core, data, area, minp, maxp) + + -- if core.biome is nil then renderCores() never rendered it, which means it + -- doesn't instersect this draw region. + if core.biome ~= nil and core.radius > 18 and core.depth > 20 and core.radius + core.depth > 60 then + + local territoryX = math_floor(core.x / core.type.territorySize) + local territoryZ = math_floor(core.z / core.type.territorySize) + local isPolarOutpost = (core.temperature <= 5) and (core.x % 3 == 0) and noise_surfaceMap:get2d({x = core.x, y = core.z - 8}) >= 0 --make sure steps aren't under a pond + local isAncientBurrow = core.humidity >= 60 and core.temperature >= 50 + + -- only allow a checkerboard pattern of territories to help keep the secrets + -- spread out, rather than bunching up too much with climate + if ((territoryX + territoryZ) % 2 == 0) and (isPolarOutpost or isAncientBurrow) then + + local burrowRadius = 7 + local burrowHeight = 5 + local burrowDepth = 12 + local burrowFloor = ALTITUDE + core.y - burrowDepth + local radiusSquared = burrowRadius * burrowRadius + + local function carve(originp, destp, pattern, height, floorId, floorDistance) + + local direction = vector.direction(originp, destp) + local vineSearchDirection = {} + if direction.x > 0 then vineSearchDirection.x = -1 else vineSearchDirection.x = 1 end + if direction.z > 0 then vineSearchDirection.z = -1 else vineSearchDirection.z = 1 end + + local vinePlacements = {} + local function placeVine(vi, pos, only_place_on_nodeId) + if data[vi] == nodeId_air then + local faces = {} + local facing + + local function vineCanGrowOnIt(node_id) + return node_id ~= nodeId_air and node_id ~= nodeId_airStandIn and (node_id == only_place_on_nodeId or only_place_on_nodeId == nil) + end + if vineCanGrowOnIt(data[vi + vineSearchDirection.x]) and pos.x + vineSearchDirection.x >= minp.x and pos.x + vineSearchDirection.x <= maxp.x then + if vineSearchDirection.x > 0 then facing = 2 else facing = 3 end + faces[#faces + 1] = {solid_vi = vi + vineSearchDirection.x, facing = facing} + end + if vineCanGrowOnIt(data[vi + vineSearchDirection.z * area.zstride]) and pos.z + vineSearchDirection.z >= minp.z and pos.z + vineSearchDirection.z <= maxp.z then + if vineSearchDirection.z > 0 then facing = 4 else facing = 5 end + faces[#faces + 1] = {solid_vi = vi + vineSearchDirection.z * area.zstride, facing = facing} + end + + local faceInfo = nil + if #faces == 1 then + faceInfo = faces[1] + elseif #faces == 2 then + local ratio = math.abs(direction.x) / (math.abs(direction.x) + math.abs(direction.z)) + if randomNumbers[(pos.x + pos.y + pos.z) % 256] <= ratio then faceInfo = faces[1] else faceInfo = faces[2] end + end + if faceInfo ~= nil + and (only_place_on_nodeId == nil or only_place_on_nodeId == data[faceInfo.solid_vi]) + and (data[faceInfo.solid_vi] ~= nodeId_airStandIn) then + -- find the highest y value (or maxp.y) where solid_vi is solid + -- and vi is not + local solid_vi = faceInfo.solid_vi + local yOffset = 1 + while (pos.y + yOffset <= maxp.y + 1) + and (data[solid_vi + yOffset * area.ystride] ~= nodeId_air) + and (data[vi + yOffset * area.ystride] == nodeId_air) + and (only_place_on_nodeId == nil or only_place_on_nodeId == data[solid_vi + yOffset * area.ystride]) do + yOffset = yOffset + 1 + end + + -- defer final vine placement until all nodes have been carved + vinePlacements[#vinePlacements + 1] = function(decoration_list) + -- retest that the vine is still going in air and still attached to a solid node + local solidNode = data[solid_vi + (yOffset - 1) * area.ystride] + if solidNode ~= nodeId_airStandIn and solidNode ~= nodeId_air and data[vi] == nodeId_air then + decoration_list[#decoration_list + 1] = {pos={x=pos.x, y=pos.y + yOffset - 1, z=pos.z}, node={name = nodeName_vine, param2 = faceInfo.facing}} + end + end + end + end + end + + local stampedIndexes = {} + local function stamp(pos, pattern, height, node_id, isAir_callback) + local callbackClosures = {} + local index = -1 + for y = pos.y, pos.y + height - 1 do + if y >= minp.y and y <= maxp.y then + if index == -1 then index = area:index(pos.x, y, pos.z) else index = index + area.ystride end + for _,voxel in ipairs(pattern) do + local x = pos.x + voxel.x + local z = pos.z + voxel.z + if x >= minp.x and x <= maxp.x and z >= minp.z and z <= maxp.z then + local vi = index + voxel.x + voxel.z * area.zstride + if data[vi] == nodeId_air then + if isAir_callback ~= nil then + callbackClosures[#callbackClosures + 1] = function() isAir_callback(pos, vi, x, y, z) end + end + else + data[vi] = node_id + stampedIndexes[#stampedIndexes + 1] = vi + end + end + end + end + end + for _,callback in ipairs(callbackClosures) do callback() end + end + + local function excavate(pos, add_floor, add_vines, add_cobwebs) + + local function onAirNode(stampPos, node_vi, node_x, node_y, node_z) + if node_y > stampPos.y and node_y + 1 <= maxp.y then + -- place vines above the entrance, for concealment + placeVine(node_vi + area.ystride, {x=node_x, y=node_y + 1, z=node_z}) + else + -- place vines on the floor - perhaps explorers can climb to the burrow + placeVine(node_vi, {x=node_x, y=node_y, z=node_z}, floorId) + end + end + + local onAirNodeCallback = onAirNode + local fill = nodeId_airStandIn + if not add_vines or nodeId_vine == nodeId_ignore then onAirNodeCallback = nil end + if add_cobwebs and nodeId_cobweb ~= nodeId_ignore then fill = nodeId_cobweb end + + stamp(pos, pattern, height, fill, onAirNodeCallback) + if add_floor and floorId ~= nil then + stamp({x=pos.x, y=pos.y - 1, z=pos.z}, pattern, 1, floorId, onAirNodeCallback) + end + end + + local addVines = core.humidity >= VINES_REQUIRED_HUMIDITY and core.temperature >= VINES_REQUIRED_TEMPERATURE + if floorDistance == nil then floorDistance = 0 end + local distance = round(vector.distance(originp, destp)) + local step = vector.divide(vector.subtract(destp, originp), distance) + + local pos = vector.new(originp) + local newPos = vector.new(originp) + + excavate(originp, 0 >= floorDistance, false) + for i = 1, distance do + newPos.x = newPos.x + step.x + if round(newPos.x) ~= pos.x then + pos.x = round(newPos.x) + excavate(pos, i >= floorDistance, addVines, i <= floorDistance - 1 and i >= floorDistance - 2) + end + newPos.y = newPos.y + step.y + if round(newPos.y) ~= pos.y then + pos.y = round(newPos.y) + excavate(pos, i >= floorDistance, addVines, i <= floorDistance - 1 and i >= floorDistance - 2) + end + newPos.z = newPos.z + step.z + if round(newPos.z) ~= pos.z then + pos.z = round(newPos.z) + excavate(pos, i >= floorDistance, addVines, i <= floorDistance - 1 and i >= floorDistance - 2) + end + end + + -- We only place vines after entire burrow entrance has been carved, to avoid placing + -- vines on blocks which will later be removed. + for _,vineFunction in ipairs(vinePlacements) do vineFunction(decoration_list) end + + -- Replace airStandIn with real air. + -- This two-pass process was neccessary because the vine placing algorithm used + -- the presense of air to determine if a rock was facing outside and should have a vine. + -- Single-pass solutions result in vines inside the tunnel (where I'd rather overgrowth spawned) + for _,stampedIndex in ipairs(stampedIndexes) do + if data[stampedIndex] == nodeId_airStandIn then + data[stampedIndex] = nodeId_air + end + end + + end + + local function placeNode(x, y, z, node_id) + if (x >= minp.x and x <= maxp.x and z >= minp.z and z <= maxp.z and y >= minp.y and y <= maxp.y) then + data[area:index(x, y, z)] = node_id + end + end + + local function posInBounds(pos) + return pos.x >= minp.x and pos.x <= maxp.x and pos.z >= minp.z and pos.z <= maxp.z and pos.y >= minp.y and pos.y <= maxp.y + end + + local zStart = math_max(core.z - burrowRadius, minp.z) + local xStart = math_max(core.x - burrowRadius, minp.x) + local xStop = math_min(core.x + burrowRadius, maxp.x) + local yStart = math_max(burrowFloor, minp.y) + + -- dig burrow + local dataBufferIndex = area:index(xStart, yStart, zStart) + for z = zStart, math_min(core.z + burrowRadius, maxp.z) do + for x = xStart, xStop do + local distanceSquared = (x - core.x)*(x - core.x) + (z - core.z)*(z - core.z) + if distanceSquared < radiusSquared then + local horz_easing = 1 - distanceSquared / radiusSquared + for y = math_max(minp.y, burrowFloor + math_floor(1.4 - horz_easing)), math_min(maxp.y, burrowFloor + 1 + math_min(burrowHeight - 1, math_floor(0.8 + burrowHeight * horz_easing))) do + data[dataBufferIndex + (y - yStart) * area.ystride] = nodeId_air + end + end + dataBufferIndex = dataBufferIndex + 1 + end + dataBufferIndex = dataBufferIndex + area.zstride - (xStop - xStart + 1) + end + + local floorId + if core.biome.node_top == nil then floorId = nil else floorId = minetest.get_content_id(core.biome.node_top) end + + if isAncientBurrow then + -- island overlaps can only happen at territory edges when a coreType has exclusive=true, so + -- angle the burrow entrance toward the center of the terrority to avoid any overlapping islands. + local territoryCenter = vector.new( + core.type.territorySize * math.floor(core.x / core.type.territorySize) + math.floor(0.5 + core.type.territorySize / 2), + burrowFloor, + core.type.territorySize * math.floor(core.z / core.type.territorySize) + math.floor(0.5 + core.type.territorySize / 2) + ) + local burrowStart = vector.new(core.x, burrowFloor, core.z) + local direction = vector.direction(burrowStart, territoryCenter) + local directionOffsetZ = 4 + if direction.z < 0 then directionOffsetZ = -directionOffsetZ end + burrowStart.z = burrowStart.z + directionOffsetZ -- start the burrow enterance off-center + burrowStart.x = burrowStart.x + 2 -- start the burrow enterance off-center + direction = vector.direction(burrowStart, territoryCenter) + if vector.length(direction) == 0 then direction = vector.direction({x=0, y=0, z=0}, {x=2, y=0, z=1}) end + + local path = vector.add(vector.multiply(direction, core.radius), {x=0, y=-4,z=0}) + local floorStartingFrom = 4 + math.floor(0.5 + core.radius * 0.3) + + -- carve burrow entrance + local pattern = {{x=0,z=0}, {x=-1,z=0}, {x=1,z=0}, {x=0,z=-1}, {x=0,z=1}} + carve(burrowStart, vector.add(burrowStart, path), pattern, 2, floorId, floorStartingFrom) + + -- place egg in burrow + local eggX = core.x + local eggZ = core.z - directionOffsetZ * 0.75 -- move the egg away from where the burrow entrance is carved + placeNode(eggX, burrowFloor, eggZ, nodeId_egg) + if nodeId_gravel ~= nodeId_ignore then placeNode(eggX, burrowFloor - 1, eggZ, nodeId_gravel) end + if nodeId_cobweb ~= nodeId_ignore then + placeNode(core.x - 6, burrowFloor + 3, core.z - 1, nodeId_cobweb) + placeNode(core.x + 4, burrowFloor + 4, core.z + 3, nodeId_cobweb) + placeNode(core.x + 6, burrowFloor + 1, core.z - 3, nodeId_cobweb) + end + + else + -- Only attempt this if it can contain beds and a place to store the diary. + if (nodeId_bookshelf ~= nodeId_ignore or nodeId_chest ~= nodeId_ignore) and nodeId_bed_top ~= nodeId_ignore and nodeId_bed_bottom ~= nodeId_ignore then + + -- carve stairs to the surface + local stairsStart = vector.new(core.x - 3, burrowFloor, core.z - 7) + local stairsbottom = vector.add(stairsStart, {x=0,y=0,z=1}) + local stairsMiddle1 = vector.add(stairsStart, {x=8,y=8,z=0}) + local stairsMiddle2 = vector.add(stairsMiddle1, {x=0,y=0,z=-1}) + local stairsEnd = vector.add(stairsMiddle2, {x=-20,y=20,z=0}) + + carve(stairsEnd, stairsMiddle2, {{x=0,z=0}}, 3, floorId, 0) + carve(stairsMiddle1, stairsStart, {{x=0,z=0}}, 2, floorId, 0) + local pattern = {{x=0,z=0}, {x=1,z=0}, {x=0,z=2}, {x=0,z=1}, {x=1,z=1}} + carve(stairsbottom, stairsbottom, pattern, 2, floorId, 0) + + -- fill the outpost + placeNode(core.x + 2, burrowFloor, core.z + 5, nodeId_bed_top) + placeNode(core.x + 2, burrowFloor, core.z + 4, nodeId_bed_bottom) + + placeNode(core.x + 2, burrowFloor, core.z + 2, nodeId_bed_top) + placeNode(core.x + 2, burrowFloor, core.z + 1, nodeId_bed_bottom) + + placeNode(core.x + 4, burrowFloor, core.z + 2, nodeId_bed_top) + placeNode(core.x + 4, burrowFloor, core.z + 1, nodeId_bed_bottom) + + if (nodeId_torch ~= nodeId_ignore) then + decoration_list[#decoration_list + 1] = { + pos={x=core.x, y=burrowFloor + 2, z=core.z + 6}, + node={name = minetest.get_name_from_content_id(nodeId_torch), param2 = 4} + } + end + if nodeId_junk ~= nodeId_ignore then placeNode(core.x - 4, burrowFloor + 1, core.z + 5, nodeId_junk) end + if nodeId_anvil ~= nodeId_ignore then placeNode(core.x - 6, burrowFloor + 1, core.z, nodeId_anvil) end + if nodeId_workbench ~= nodeId_ignore then placeNode(core.x - 5, burrowFloor, core.z + 2, nodeId_workbench) end + if nodeId_cobweb ~= nodeId_ignore then placeNode(core.x + 4, burrowFloor + 4, core.z - 3, nodeId_cobweb) end + + local bookshelf_pos + local invBookshelf = nil + local invChest = nil + if nodeId_chest ~= nodeId_ignore then + local pos = {x = core.x - 3, y = burrowFloor + 1, z = core.z + 6} + + local nodeName_chest = minetest.get_name_from_content_id(nodeId_chest) + local nodeNameAtPos = minetest.get_node(pos).name + -- falls back on the nodeNameAtPos:find("chest") check to avoid a race-condition where if the + -- chest is opened while nearby areas are being generated, the opened chest may be replaced with + -- a new empty closed one. + if nodeNameAtPos ~= nodeName_chest and not nodeNameAtPos:find("chest") then minetest.set_node(pos, {name = nodeName_chest}) end + + if posInBounds(pos) then + data[area:index(pos.x, pos.y, pos.z)] = nodeId_chest + invChest = minetest.get_inventory({type = "node", pos = pos}) + end + end + if nodeId_bookshelf ~= nodeId_ignore then + local pos = {x = core.x - 2, y = burrowFloor + 1, z = core.z + 6} + bookshelf_pos = pos + + if minetest.get_node(pos).name ~= nodeName_bookshelf then minetest.set_node(pos, {name = nodeName_bookshelf}) end + + if posInBounds(pos) then + data[area:index(pos.x, pos.y, pos.z)] = nodeId_bookshelf + if not isMineCloneBookshelf then -- mineclone bookshelves are decorational (like Minecraft) and don't contain anything + invBookshelf = minetest.get_inventory({type = "node", pos = pos}) + end + end + end + + if invBookshelf ~= nil or invChest ~= nil then + -- create diary + local groundDesc = S("rock") + if core.biome.node_filler ~= nil then + local earthNames = string.lower(core.biome.node_filler) .. string.lower(core.biome.node_top) + if string.match(earthNames, "ice") or string.match(earthNames, "snow") or string.match(earthNames, "frozen") then + groundDesc = S("ice") + end + end + + local book_itemstack = interop.write_book( + S("Weddell Outpost, November 21"), -- title + S("Bert Shackleton"), -- owner/author + S([[The aerostat is lost. + +However, salvage attempts throughout the night managed to +save most provisions before it finally broke apart and fell. + + ---====--- + +This island is highly exposed and the weather did not treat +the tents well. We have enlarged a sheltered crag in the @1, +but it is laborous work and the condition of some of the party +is becoming cause for concern. + +Quite a journey is now required, we cannot stay - nobody will +look for us here. McNish is attempting to strengthen the gliders. + + ---====---]], groundDesc), + S("Diary of Bert Shackleton") -- description + ) + + if book_itemstack ~= nil then + if invBookshelf == nil then + -- mineclone bookshelves are decorational like Minecraft, put the book in the chest instead + -- (also testing for nil invBookshelf because it can happen. Weird race condition??) + if invChest ~= nil then invChest:add_item("main", book_itemstack) end + else + -- add the book to the bookshelf and manually trigger update_bookshelf() so its + -- name will reflect the new contents. + invBookshelf:add_item("books", book_itemstack) + local dummyPlayer = {} + dummyPlayer.get_player_name = function() return "server" end + dummyPlayer.is_fake_player = true + dummyPlayer.is_player = function() return false end -- it's unclear whether this should return false for fake players + + minetest.registered_nodes[nodeName_bookshelf].on_metadata_inventory_put(bookshelf_pos, "books", 1, book_itemstack, dummyPlayer) + end + end + end + + if invChest ~= nil then + -- leave some junk from the expedition in the chest + local stack + local function addIfFound(item_aliases, amount) + for _,name in ipairs(item_aliases) do + if minetest.registered_items[name] ~= nil then + stack = ItemStack(name .. " " .. amount) + invChest:add_item("main", stack) + break + end + end + end + addIfFound({"mcl_tools:pick_iron", "default:pick_steel", "main:ironpick"}, 1) + addIfFound({"binoculars:binoculars"}, 1) + addIfFound(NODENAMES_WOOD, 10) + addIfFound({"mcl_torches:torch", "default:torch", "torch:torch"}, 3) + end + + end + end + end + end +end + +local function init_secrets() + nodeId_bed_top = interop.find_node_id({"beds:bed_top", "bed:bed_front"}) + nodeId_bed_bottom = interop.find_node_id({"beds:bed_bottom", "bed:bed_back"}) + nodeId_torch = interop.find_node_id({"mcl_torches:torch_wall", "default:torch_wall", "torch:wall"}) + nodeId_chest = interop.find_node_id({"chest", "mcl_chests:chest", "default:chest", "utility:chest"}) + nodeId_junk = interop.find_node_id({"xdecor:barrel", "cottages:barrel", "homedecor:copper_pans", "vessels:steel_bottle", "mcl_flowerpots:flower_pot"}) + nodeId_anvil = interop.find_node_id({"castle:anvil", "cottages:anvil", "mcl_anvils:anvil", "default:anvil", "main:anvil" }) -- "default:anvil" and "main:anvil" aren't a thing, but perhaps one day. + nodeId_workbench = interop.find_node_id({"homedecor:table", "xdecor:workbench", "mcl_crafting_table:crafting_table", "default:table", "random_buildings:bench", "craftingtable:craftingtable"}) -- "default:table" isn't a thing, but perhaps one day. + nodeId_cobweb = interop.find_node_id({"mcl_core:cobweb", "xdecor:cobweb", "homedecor:cobweb_plantlike", "default:cobweb", "main:cobweb"}) + + local mineCloneBookshelfName = "mcl_books:bookshelf" + nodeId_bookshelf = interop.find_node_id({mineCloneBookshelfName, "default:bookshelf"}) + nodeName_bookshelf = minetest.get_name_from_content_id(nodeId_bookshelf) + isMineCloneBookshelf = nodeName_bookshelf == mineCloneBookshelfName + + if nodeId_cobweb ~= nodeId_ignore then + -- This game has proper cobwebs, replace any cobwebs this mod may have generated + -- previously (when a cobweb mod wasn't included) with the proper cobwebs. + minetest.register_alias(nodeName_standinCobweb, minetest.get_name_from_content_id(nodeId_cobweb)) + elseif minetest.registered_nodes[nodeName_standinCobweb] ~= nil then + -- use a stand-in cobweb created by this mod + nodeId_cobweb = minetest.get_content_id(nodeName_standinCobweb) + end +end +------------------------------------------------------------------------------ +-- End of secrets section +------------------------------------------------------------------------------ + + +local function renderCores(cores, minp, maxp, blockseed) + + local voxelsWereManipulated = false + + local vm, emerge_min, emerge_max = minetest.get_mapgen_object("voxelmanip") + vm:get_data(data) -- put all nodes except the ground surface in this array + local area = VoxelArea:new{MinEdge=emerge_min, MaxEdge=emerge_max} + local overdrawTop = maxp.y + OVERDRAW + + local currentBiomeId = -1 + local nodeId_dust + local nodeId_top + local nodeId_filler + local nodeId_stoneBase + local nodeId_pondBottom + local depth_top + local depth_filler + local fillerFallsWithGravity + local floodableDepth + + for z = minp.z, maxp.z do + + local dataBufferIndex = area:index(minp.x, minp.y, z) + for x = minp.x, maxp.x do + for _,core in pairs(cores) do + local coreTop = ALTITUDE + core.y + + local distanceSquared = (x - core.x)*(x - core.x) + (z - core.z)*(z - core.z) + local radius = core.radius + local radiusSquared = radius * radius + + if distanceSquared <= radiusSquared then + + -- get the biome details for this core + if core.biome == nil then setCoreBiomeData(core) end + if currentBiomeId ~= core.biomeId then + if core.biome.node_top == nil then nodeId_top = nodeId_stone else nodeId_top = minetest.get_content_id(core.biome.node_top) end + if core.biome.node_filler == nil then nodeId_filler = nodeId_stone else nodeId_filler = minetest.get_content_id(core.biome.node_filler) end + if core.biome.node_stone == nil then nodeId_stoneBase = nodeId_stone else nodeId_stoneBase = minetest.get_content_id(core.biome.node_stone) end + if core.biome.node_dust == nil then nodeId_dust = nodeId_ignore else nodeId_dust = minetest.get_content_id(core.biome.node_dust) end + if core.biome.node_riverbed == nil then nodeId_pondBottom = nodeId_silt else nodeId_pondBottom = minetest.get_content_id(core.biome.node_riverbed) end + + if core.biome.depth_top == nil then depth_top = 1 else depth_top = core.biome.depth_top end + if core.biome.depth_filler == nil then depth_filler = 3 else depth_filler = core.biome.depth_filler end + fillerFallsWithGravity = core.biome.node_filler ~= nil and minetest.registered_items[core.biome.node_filler].groups.falling_node == 1 + + --[[Commented out as unnecessary, as a supporting node will be added, but uncommenting + this will make the strata transition less noisey. + if fillerFallsWithGravity then + -- the filler node is affected by gravity and can fall if unsupported, so keep that layer thinner than + -- core.thickness when possible. + --depth_filler = math_min(depth_filler, math_max(1, core.thickness - 1)) + end--]] + + floodableDepth = 0 + if nodeId_top ~= nodeId_stone and minetest.registered_items[core.biome.node_top].floodable then + -- nodeId_top is a node that water floods through, so we can't have ponds appearing at this depth + floodableDepth = depth_top + end + + currentBiomeId = core.biomeId + end + + -- decide on a shape + local horz_easing + local noise_weighting = 1 + local shapeType = math_floor(core.depth + radius + core.x) % 5 + if shapeType < 2 then + -- convex + -- squared easing function, e = 1 - x² + horz_easing = 1 - distanceSquared / radiusSquared + elseif shapeType == 2 then + -- conical + -- linear easing function, e = 1 - x + horz_easing = 1 - math_sqrt(distanceSquared) / radius + else + -- concave + -- root easing function blended/scaled with square easing function, + -- x = normalised distance from center of core + -- a = 1 - x² + -- b = 1 - √x + -- e = 0.8*a*x + 1.2*b*(1 - x) + + local radiusRoot = core.radiusRoot + if radiusRoot == nil then + radiusRoot = math_sqrt(radius) + core.radiusRoot = radiusRoot + end + + local squared = 1 - distanceSquared / radiusSquared + local distance = math_sqrt(distanceSquared) + local distance_normalized = distance / radius + local root = 1 - math_sqrt(distance) / radiusRoot + horz_easing = math_min(1, 0.8*distance_normalized*squared + 1.2*(1-distance_normalized)*root) + + -- this seems to be a more delicate shape that gets wiped out by the + -- density noise, so lower that + noise_weighting = 0.63 + end + if radius + core.depth > 80 then + -- larger islands shapes have a slower easing transition, which leaves large areas + -- dominated by the density noise, so reduce the density noise when the island is large. + -- (the numbers here are arbitrary) + if radius + core.depth > 120 then + noise_weighting = 0.35 + else + noise_weighting = math_min(0.6, noise_weighting) + end + end + + local surfaceNoise = noise_surfaceMap:get2d({x = x, y = z}) + if DEBUG_GEOMETRIC then surfaceNoise = SURFACEMAP_OFFSET end + local surface = round(surfaceNoise * 3 * (core.thickness + 1) * horz_easing) -- if you change this formular then update maxSufaceRise in on_generated() + local coreBottom = math_floor(coreTop - (core.thickness + core.depth)) + local noisyDepthOfFiller = depth_filler + if noisyDepthOfFiller >= 3 then noisyDepthOfFiller = noisyDepthOfFiller + math_floor(randomNumbers[(x + z) % 256] * 3) - 1 end + + local yBottom = math_max(minp.y, coreBottom - 4) -- the -4 is for rare instances when density noise pushes the bottom of the island deeper + local yBottomIndex = dataBufferIndex + area.ystride * (yBottom - minp.y) -- equivalent to yBottomIndex = area:index(x, yBottom, z) + local topBlockIndex = -1 + local bottomBlockIndex = -1 + local vi = yBottomIndex + local densityNoise = nil + + for y = yBottom, math_min(overdrawTop, coreTop + surface) do + local vert_easing = math_min(1, (y - coreBottom) / core.depth) + + -- If you change the densityNoise calculation, remember to similarly update the copy of this calculation in the pond code + densityNoise = noise_density:get3d({x = x, y = y - coreTop, z = z}) -- TODO: Optimize this!! + densityNoise = noise_weighting * densityNoise + (1 - noise_weighting) * DENSITY_OFFSET + + if DEBUG_GEOMETRIC then densityNoise = DENSITY_OFFSET end + + if densityNoise * ((horz_easing + vert_easing) / 2) >= REQUIRED_DENSITY then + if vi > topBlockIndex then topBlockIndex = vi end + if bottomBlockIndex < 0 and y > minp.y then bottomBlockIndex = vi end -- if y==minp.y then we don't know for sure this is the lowest block + + if y > coreTop + surface - depth_top and data[vi] == nodeId_air then + data[vi] = nodeId_top + elseif y >= coreTop + surface - (depth_top + noisyDepthOfFiller) then + data[vi] = nodeId_filler + else + data[vi] = nodeId_stoneBase + end + end + vi = vi + area.ystride + end + + -- ensure nodeId_top blocks also cover the rounded sides of islands (which may be lower + -- than the flat top), then dust the top surface. + if topBlockIndex >= 0 then + voxelsWereManipulated = true + + -- we either have the highest block, or overdrawTop - but we don't want to set overdrawTop nodes to nodeId_top + -- (we will err on the side of caution when we can't distinguish the top of a island's side from overdrawTop) + if overdrawTop >= coreTop + surface or vi > topBlockIndex + area.ystride then + if topBlockIndex > yBottomIndex and data[topBlockIndex - area.ystride] ~= nodeId_air and data[topBlockIndex + area.ystride] == nodeId_air then + -- We only set a block to nodeId_top if there's a block under it "holding it up" as + -- it's better to leave 1-deep noise as stone/whatever. + data[topBlockIndex] = nodeId_top + end + if nodeId_dust ~= nodeId_ignore and data[topBlockIndex + area.ystride] == nodeId_air then + -- Delay writing dust to the data buffer until after decoration so avoid preventing tree growth etc + if core.dustLocations == nil then core.dustLocations = {} end + core.dustLocations[#core.dustLocations + 1] = topBlockIndex + area.ystride + end + end + + if fillerFallsWithGravity and bottomBlockIndex >= 0 and data[bottomBlockIndex] == nodeId_filler then + -- the bottom node is affected by gravity and can fall if unsupported, put some support in + data[bottomBlockIndex] = nodeId_stoneBase + end + end + + -- add ponds of water, trying to make sure they're not on an edge. + -- (the only time a pond needs to be rendered when densityNoise is nil (i.e. when there was no land at this x, z), + -- is when the pond is at minp.y - i.e. the reason no land was rendered is it was below minp.y) + if surfaceNoise < 0 and (densityNoise ~= nil or (coreTop + surface < minp.y and coreTop >= minp.y)) and nodeId_water ~= nodeId_ignore then + local pondWallBuffer = core.type.pondWallBuffer + local pondBottom = nodeId_filler + local pondWater = nodeId_water + if radius > 18 and core.depth > 15 and nodeId_pondBottom ~= nodeId_ignore then + -- only give ponds a sandbed when islands are large enough for it not to stick out the side or bottom + pondBottom = nodeId_pondBottom + end + if core.temperature <= ICE_REQUIRED_TEMPERATURE and nodeId_ice ~= nodeId_ignore then pondWater = nodeId_ice end + + if densityNoise == nil then + -- Rare edge case. If the pond is at minp.y, then no land has been rendered, so + -- densityNoise hasn't been calculated. Calculate it now. + densityNoise = noise_density:get3d({x = x, y = minp.y, z = z}) + densityNoise = noise_weighting * densityNoise + (1 - noise_weighting) * DENSITY_OFFSET + if DEBUG_GEOMETRIC then densityNoise = DENSITY_OFFSET end + end + + local surfaceDensity = densityNoise * ((horz_easing + 1) / 2) + local onTheEdge = math_sqrt(distanceSquared) + 1 >= radius + for y = math_max(minp.y, coreTop + surface), math_min(overdrawTop, coreTop - floodableDepth) do + if surfaceDensity > REQUIRED_DENSITY then + vi = dataBufferIndex + area.ystride * (y - minp.y) -- this is the same as vi = area:index(x, y, z) + + if surfaceDensity > (REQUIRED_DENSITY + pondWallBuffer) and not onTheEdge then + data[vi] = pondWater + if y > minp.y then data[vi - area.ystride] = pondBottom end + --remove any dust above ponds + if core.dustLocations ~= nil and core.dustLocations[#core.dustLocations] == vi + area.ystride then core.dustLocations[#core.dustLocations] = nil end + else + -- make sure there are some walls to keep the water in + if y == coreTop then + data[vi] = nodeId_top -- to let isIsland() know not to put vines here (only seems to be an issue when pond is 2 deep or more) + else + data[vi] = nodeId_filler + end + end + end + end + end + + end + end + dataBufferIndex = dataBufferIndex + 1 + end + end + + local decorations = {} + for _,core in ipairs(cores) do + addDetail_vines(decorations, core, data, area, minp, maxp) + voxelsWereManipulated = addDetail_skyReef(decorations, core, data, area, minp, maxp) or voxelsWereManipulated + addDetail_secrets(decorations, core, data, area, minp, maxp) + end + + if voxelsWereManipulated then + + vm:set_data(data) + if GENERATE_ORES then minetest.generate_ores(vm) end + minetest.generate_decorations(vm) + + for _,core in ipairs(cores) do + addDetail_skyTree(decorations, core, minp, maxp) + end + for _,decoration in ipairs(decorations) do + local nodeAtPos = minetest.get_node(decoration.pos) + if nodeAtPos.name == "air" or nodeAtPos.name == nodeName_ignore then minetest.set_node(decoration.pos, decoration.node) end + end + + local dustingInProgress = false + for _,core in ipairs(cores) do + if core.dustLocations ~= nil then + if not dustingInProgress then + vm:get_data(data) + dustingInProgress = true + end + + nodeId_dust = minetest.get_content_id(core.biome.node_dust) + for _, location in ipairs(core.dustLocations) do + if data[location] == nodeId_air and data[location - area.ystride] ~= nodeId_air then + data[location] = nodeId_dust + end + end + end + end + if dustingInProgress then vm:set_data(data) end + + + -- Lighting is a problem. Two problems really... + -- + -- Problem 1: + -- We can't use the usual lua mapgen lighting trick of flags="nolight" e.g.: + -- minetest.set_mapgen_params({mgname = "singlenode", flags = "nolight"}) + -- (https://forum.minetest.net/viewtopic.php?t=19836) + -- + -- because the mod is designed to run with other mapgens. So we must set the light + -- values to zero at islands before calling calc_lighting() to propegate lighting + -- down from above. + -- + -- This causes lighting bugs if we zero the whole emerge_min-emerge_max area because + -- it leaves hard black at the edges of the emerged area (calc_lighting must assume + -- a value of zero for light outside the region, and be blending that in) + -- + -- But we can't simply zero only the minp-maxp area instead, because then calc_lighting + -- reads the daylight values out of the overdraw area and blends those in, cutting + -- up shadows with lines of daylight along chunk boundaries. + -- + -- The correct solution is to zero and calculate the whole emerge_min-emerge_max area, + -- but only write the calculated lighting information from minp-maxp back into the map, + -- however the API doesn't appear to provide a fast way to do that. + -- + -- Workaround: zero an area that extends into the overdraw region, but keeps a gap around + -- the edges to preserve and allow the real light values to propegate in. Then when + -- calc_lighting is called it will have daylight (or existing values) at the emerge boundary + -- but not near the chunk boundary. calc_lighting is able to take the edge lighting into + -- account instead of assuming zero. It's not a perfect solution, but allows shading without + -- glaringly obvious lighting artifacts, and the minor ill effects should only affect the + -- islands and be corrected any time lighting is updated. + -- + -- + -- Problem 2: + -- We don't want islands to blacken the landscape below them in shadow. + -- + -- Workaround 1: Instead of zeroing the lighting before propegating from above, set it + -- to 2, so that shadows are never pitch black. Shadows will still go back to pitch black + -- though if lighting gets recalculated, e.g. player places a torch then removes it. + -- + -- Workaround 2: set the bottom of the chunk to full daylight, ensuring that full + -- daylight is what propegates down below islands. This has the problem of causing a + -- bright horizontal band of light where islands approach a chunk floor or ceiling, + -- but Hallelujah Mountains already had that issue due to having propagate_shadow + -- turned off when calling calc_lighting. This workaround has the same drawback, but + -- does a much better job of preventing undesired shadows. + + local shadowGap = 1 + local brightMin = {x = emerge_min.x + shadowGap, y = minp.y , z = emerge_min.z + shadowGap} + local brightMax = {x = emerge_max.x - shadowGap, y = minp.y + 1, z = emerge_max.z - shadowGap} + local darkMin = {x = emerge_min.x + shadowGap, y = minp.y + 1, z = emerge_min.z + shadowGap} + local darkMax = {x = emerge_max.x - shadowGap, y = maxp.y , z = emerge_max.z - shadowGap} + + vm:set_lighting({day=2, night=0}, darkMin, darkMax) + vm:calc_lighting() + vm:set_lighting({day=15, night=0}, brightMin, brightMax) + + vm:write_to_map() -- seems to be unnecessary when other mods that use vm are running + + for _,core in ipairs(cores) do + -- place any schematics which should be placed after the landscape + if addDetail_ancientPortal ~= nil then addDetail_ancientPortal(core) end + end + end +end + + +cloudlands.init = function() + if noise_eddyField == nil then + init_mapgen() + init_secrets() + end + if noise_eddyField == nil then + -- See comment in init_mapgen() about when this can be called + minetest.log("warning", "cloudlands.init() unable to init - was probably invoked before the the environment was created") + end +end + +local function on_generated(minp, maxp, blockseed) + + local memUsageT0 + local osClockT0 = os.clock() + if DEBUG then memUsageT0 = collectgarbage("count") end + + local largestCoreType = cloudlands.coreTypes[1] -- the first island type is the biggest/thickest + local maxCoreThickness = largestCoreType.thicknessMax + local maxCoreDepth = largestCoreType.radiusMax * 3 / 2 -- todo: not sure why this is radius based and not maxDepth based?? + local maxSufaceRise = 3 * (maxCoreThickness + 1) + + if minp.y > ALTITUDE + (ALTITUDE_AMPLITUDE + maxSufaceRise + 10) or -- the 10 is an arbitrary number because sometimes the noise values exceed their normal range. + maxp.y < ALTITUDE - (ALTITUDE_AMPLITUDE + maxCoreThickness + maxCoreDepth + 10) then + -- Hallelujah Mountains don't generate here + return + end + + local cores = cloudlands.get_island_details(minp, maxp) + + if DEBUG then + minetest.log("info", "Cores for on_generated(): " .. #cores) + for _,core in pairs(cores) do + minetest.log("core ("..core.x..","..core.y..","..core.z..") r"..core.radius) + end + end + + if #cores > 0 then + -- voxelmanip has mem-leaking issues, avoid creating one if we're not going to need it + renderCores(cores, minp, maxp, blockseed) + + if DEBUG then + minetest.log( + "info", + MODNAME .. " took " + .. round((os.clock() - osClockT0) * 1000) + .. "ms for " .. #cores .. " cores. Uncollected memory delta: " + .. round(collectgarbage("count") - memUsageT0) .. " KB" + ) + end + end +end + + +minetest.register_on_generated(on_generated) + +minetest.register_on_mapgen_init( + -- invoked after mods initially run but before the environment is created, while the mapgen is being initialized + function(mgparams) + worldSeed = mgparams.seed + --if DEBUG then minetest.set_mapgen_params({mgname = "singlenode"--[[, flags = "nolight"]]}) end + end +) \ No newline at end of file diff --git a/mods/cloudlands/cloudlands_tree1.mts b/mods/cloudlands/cloudlands_tree1.mts new file mode 100644 index 00000000..345cd5d3 Binary files /dev/null and b/mods/cloudlands/cloudlands_tree1.mts differ diff --git a/mods/cloudlands/cloudlands_tree2.mts b/mods/cloudlands/cloudlands_tree2.mts new file mode 100644 index 00000000..6af3b128 Binary files /dev/null and b/mods/cloudlands/cloudlands_tree2.mts differ diff --git a/mods/cloudlands/i18n.py b/mods/cloudlands/i18n.py new file mode 100644 index 00000000..5e879378 --- /dev/null +++ b/mods/cloudlands/i18n.py @@ -0,0 +1,426 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Script to generate the template file and update the translation files. +# Copy the script into the mod or modpack root folder and run it there. +# +# Copyright (C) 2019 Joachim Stolberg, 2020 FaceDeer, 2020 Louis Royer +# LGPLv2.1+ +# +# See https://github.com/minetest-tools/update_translations for +# potential future updates to this script. + +from __future__ import print_function +import os, fnmatch, re, shutil, errno +from sys import argv as _argv + +# Running params +params = {"recursive": False, + "help": False, + "mods": False, + "verbose": False, + "folders": [], + "no-old-file": False +} +# Available CLI options +options = {"recursive": ['--recursive', '-r'], + "help": ['--help', '-h'], + "mods": ['--installed-mods'], + "verbose": ['--verbose', '-v'], + "no-old-file": ['--no-old-file'] +} + +# Strings longer than this will have extra space added between +# them in the translation files to make it easier to distinguish their +# beginnings and endings at a glance +doublespace_threshold = 60 + +def set_params_folders(tab: list): + '''Initialize params["folders"] from CLI arguments.''' + # Discarding argument 0 (tool name) + for param in tab[1:]: + stop_param = False + for option in options: + if param in options[option]: + stop_param = True + break + if not stop_param: + params["folders"].append(os.path.abspath(param)) + +def set_params(tab: list): + '''Initialize params from CLI arguments.''' + for option in options: + for option_name in options[option]: + if option_name in tab: + params[option] = True + break + +def print_help(name): + '''Prints some help message.''' + print(f'''SYNOPSIS + {name} [OPTIONS] [PATHS...] +DESCRIPTION + {', '.join(options["help"])} + prints this help message + {', '.join(options["recursive"])} + run on all subfolders of paths given + {', '.join(options["mods"])} + run on locally installed modules + {', '.join(options["no-old-file"])} + do not create *.old files + {', '.join(options["verbose"])} + add output information +''') + + +def main(): + '''Main function''' + set_params(_argv) + set_params_folders(_argv) + if params["help"]: + print_help(_argv[0]) + elif params["recursive"] and params["mods"]: + print("Option --installed-mods is incompatible with --recursive") + else: + # Add recursivity message + print("Running ", end='') + if params["recursive"]: + print("recursively ", end='') + # Running + if params["mods"]: + print(f"on all locally installed modules in {os.path.abspath('~/.minetest/mods/')}") + run_all_subfolders("~/.minetest/mods") + elif len(params["folders"]) >= 2: + print("on folder list:", params["folders"]) + for f in params["folders"]: + if params["recursive"]: + run_all_subfolders(f) + else: + update_folder(f) + elif len(params["folders"]) == 1: + print("on folder", params["folders"][0]) + if params["recursive"]: + run_all_subfolders(params["folders"][0]) + else: + update_folder(params["folders"][0]) + else: + print("on folder", os.path.abspath("./")) + if params["recursive"]: + run_all_subfolders(os.path.abspath("./")) + else: + update_folder(os.path.abspath("./")) + +#group 2 will be the string, groups 1 and 3 will be the delimiters (" or ') +#See https://stackoverflow.com/questions/46967465/regex-match-text-in-either-single-or-double-quote +pattern_lua = re.compile(r'[\.=^\t,{\(\s]N?S\(\s*(["\'])((?:\\\1|(?:(?!\1)).)*)(\1)[\s,\)]', re.DOTALL) +pattern_lua_bracketed = re.compile(r'[\.=^\t,{\(\s]N?S\(\s*\[\[(.*?)\]\][\s,\)]', re.DOTALL) + +# Handles "concatenation" .. " of strings" +pattern_concat = re.compile(r'["\'][\s]*\.\.[\s]*["\']', re.DOTALL) + +pattern_tr = re.compile(r'(.+?[^@])=(.*)') +pattern_name = re.compile(r'^name[ ]*=[ ]*([^ \n]*)') +pattern_tr_filename = re.compile(r'\.tr$') +pattern_po_language_code = re.compile(r'(.*)\.po$') + +#attempt to read the mod's name from the mod.conf file. Returns None on failure +def get_modname(folder): + try: + with open(os.path.join(folder, "mod.conf"), "r", encoding='utf-8') as mod_conf: + for line in mod_conf: + match = pattern_name.match(line) + if match: + return match.group(1) + except FileNotFoundError: + pass + return None + +#If there are already .tr files in /locale, returns a list of their names +def get_existing_tr_files(folder): + out = [] + for root, dirs, files in os.walk(os.path.join(folder, 'locale/')): + for name in files: + if pattern_tr_filename.search(name): + out.append(name) + return out + +# A series of search and replaces that massage a .po file's contents into +# a .tr file's equivalent +def process_po_file(text): + # The first three items are for unused matches + text = re.sub(r'#~ msgid "', "", text) + text = re.sub(r'"\n#~ msgstr ""\n"', "=", text) + text = re.sub(r'"\n#~ msgstr "', "=", text) + # comment lines + text = re.sub(r'#.*\n', "", text) + # converting msg pairs into "=" pairs + text = re.sub(r'msgid "', "", text) + text = re.sub(r'"\nmsgstr ""\n"', "=", text) + text = re.sub(r'"\nmsgstr "', "=", text) + # various line breaks and escape codes + text = re.sub(r'"\n"', "", text) + text = re.sub(r'"\n', "\n", text) + text = re.sub(r'\\"', '"', text) + text = re.sub(r'\\n', '@n', text) + # remove header text + text = re.sub(r'=Project-Id-Version:.*\n', "", text) + # remove double-spaced lines + text = re.sub(r'\n\n', '\n', text) + return text + +# Go through existing .po files and, if a .tr file for that language +# *doesn't* exist, convert it and create it. +# The .tr file that results will subsequently be reprocessed so +# any "no longer used" strings will be preserved. +# Note that "fuzzy" tags will be lost in this process. +def process_po_files(folder, modname): + for root, dirs, files in os.walk(os.path.join(folder, 'locale/')): + for name in files: + code_match = pattern_po_language_code.match(name) + if code_match == None: + continue + language_code = code_match.group(1) + tr_name = modname + "." + language_code + ".tr" + tr_file = os.path.join(root, tr_name) + if os.path.exists(tr_file): + if params["verbose"]: + print(f"{tr_name} already exists, ignoring {name}") + continue + fname = os.path.join(root, name) + with open(fname, "r", encoding='utf-8') as po_file: + if params["verbose"]: + print(f"Importing translations from {name}") + text = process_po_file(po_file.read()) + with open(tr_file, "wt", encoding='utf-8') as tr_out: + tr_out.write(text) + +# from https://stackoverflow.com/questions/600268/mkdir-p-functionality-in-python/600612#600612 +# Creates a directory if it doesn't exist, silently does +# nothing if it already exists +def mkdir_p(path): + try: + os.makedirs(path) + except OSError as exc: # Python >2.5 + if exc.errno == errno.EEXIST and os.path.isdir(path): + pass + else: raise + +# Converts the template dictionary to a text to be written as a file +# dKeyStrings is a dictionary of localized string to source file sets +# dOld is a dictionary of existing translations and comments from +# the previous version of this text +def strings_to_text(dkeyStrings, dOld, mod_name): + lOut = [f"# textdomain: {mod_name}\n"] + + dGroupedBySource = {} + + for key in dkeyStrings: + sourceList = list(dkeyStrings[key]) + sourceList.sort() + sourceString = "\n".join(sourceList) + listForSource = dGroupedBySource.get(sourceString, []) + listForSource.append(key) + dGroupedBySource[sourceString] = listForSource + + lSourceKeys = list(dGroupedBySource.keys()) + lSourceKeys.sort() + for source in lSourceKeys: + localizedStrings = dGroupedBySource[source] + localizedStrings.sort() + lOut.append("") + lOut.append(source) + lOut.append("") + for localizedString in localizedStrings: + val = dOld.get(localizedString, {}) + translation = val.get("translation", "") + comment = val.get("comment") + if len(localizedString) > doublespace_threshold and not lOut[-1] == "": + lOut.append("") + if comment != None: + lOut.append(comment) + lOut.append(f"{localizedString}={translation}") + if len(localizedString) > doublespace_threshold: + lOut.append("") + + + unusedExist = False + for key in dOld: + if key not in dkeyStrings: + val = dOld[key] + translation = val.get("translation") + comment = val.get("comment") + # only keep an unused translation if there was translated + # text or a comment associated with it + if translation != None and (translation != "" or comment): + if not unusedExist: + unusedExist = True + lOut.append("\n\n##### not used anymore #####\n") + if len(key) > doublespace_threshold and not lOut[-1] == "": + lOut.append("") + if comment != None: + lOut.append(comment) + lOut.append(f"{key}={translation}") + if len(key) > doublespace_threshold: + lOut.append("") + return "\n".join(lOut) + '\n' + +# Writes a template.txt file +# dkeyStrings is the dictionary returned by generate_template +def write_template(templ_file, dkeyStrings, mod_name): + # read existing template file to preserve comments + existing_template = import_tr_file(templ_file) + + text = strings_to_text(dkeyStrings, existing_template[0], mod_name) + mkdir_p(os.path.dirname(templ_file)) + with open(templ_file, "wt", encoding='utf-8') as template_file: + template_file.write(text) + + +# Gets all translatable strings from a lua file +def read_lua_file_strings(lua_file): + lOut = [] + with open(lua_file, encoding='utf-8') as text_file: + text = text_file.read() + #TODO remove comments here + + text = re.sub(pattern_concat, "", text) + + strings = [] + for s in pattern_lua.findall(text): + strings.append(s[1]) + for s in pattern_lua_bracketed.findall(text): + strings.append(s) + + for s in strings: + s = re.sub(r'"\.\.\s+"', "", s) + s = re.sub("@[^@=0-9]", "@@", s) + s = s.replace('\\"', '"') + s = s.replace("\\'", "'") + s = s.replace("\n", "@n") + s = s.replace("\\n", "@n") + s = s.replace("=", "@=") + lOut.append(s) + return lOut + +# Gets strings from an existing translation file +# returns both a dictionary of translations +# and the full original source text so that the new text +# can be compared to it for changes. +def import_tr_file(tr_file): + dOut = {} + text = None + if os.path.exists(tr_file): + with open(tr_file, "r", encoding='utf-8') as existing_file : + # save the full text to allow for comparison + # of the old version with the new output + text = existing_file.read() + existing_file.seek(0) + # a running record of the current comment block + # we're inside, to allow preceeding multi-line comments + # to be retained for a translation line + latest_comment_block = None + for line in existing_file.readlines(): + line = line.rstrip('\n') + if line[:3] == "###": + # Reset comment block if we hit a header + latest_comment_block = None + continue + if line[:1] == "#": + # Save the comment we're inside + if not latest_comment_block: + latest_comment_block = line + else: + latest_comment_block = latest_comment_block + "\n" + line + continue + match = pattern_tr.match(line) + if match: + # this line is a translated line + outval = {} + outval["translation"] = match.group(2) + if latest_comment_block: + # if there was a comment, record that. + outval["comment"] = latest_comment_block + latest_comment_block = None + dOut[match.group(1)] = outval + return (dOut, text) + +# Walks all lua files in the mod folder, collects translatable strings, +# and writes it to a template.txt file +# Returns a dictionary of localized strings to source file sets +# that can be used with the strings_to_text function. +def generate_template(folder, mod_name): + dOut = {} + for root, dirs, files in os.walk(folder): + for name in files: + if fnmatch.fnmatch(name, "*.lua"): + fname = os.path.join(root, name) + found = read_lua_file_strings(fname) + if params["verbose"]: + print(f"{fname}: {str(len(found))} translatable strings") + + for s in found: + sources = dOut.get(s, set()) + sources.add(f"### {os.path.basename(fname)} ###") + dOut[s] = sources + + if len(dOut) == 0: + return None + templ_file = os.path.join(folder, "locale/template.txt") + write_template(templ_file, dOut, mod_name) + return dOut + +# Updates an existing .tr file, copying the old one to a ".old" file +# if any changes have happened +# dNew is the data used to generate the template, it has all the +# currently-existing localized strings +def update_tr_file(dNew, mod_name, tr_file): + if params["verbose"]: + print(f"updating {tr_file}") + + tr_import = import_tr_file(tr_file) + dOld = tr_import[0] + textOld = tr_import[1] + + textNew = strings_to_text(dNew, dOld, mod_name) + + if textOld and textOld != textNew: + print(f"{tr_file} has changed.") + if not params["no-old-file"]: + shutil.copyfile(tr_file, f"{tr_file}.old") + + with open(tr_file, "w", encoding='utf-8') as new_tr_file: + new_tr_file.write(textNew) + +# Updates translation files for the mod in the given folder +def update_mod(folder): + modname = get_modname(folder) + if modname is not None: + process_po_files(folder, modname) + print(f"Updating translations for {modname}") + data = generate_template(folder, modname) + if data == None: + print(f"No translatable strings found in {modname}") + else: + for tr_file in get_existing_tr_files(folder): + update_tr_file(data, modname, os.path.join(folder, "locale/", tr_file)) + else: + print("Unable to find modname in folder " + folder) + +# Determines if the folder being pointed to is a mod or a mod pack +# and then runs update_mod accordingly +def update_folder(folder): + is_modpack = os.path.exists(os.path.join(folder, "modpack.txt")) or os.path.exists(os.path.join(folder, "modpack.conf")) + if is_modpack: + subfolders = [f.path for f in os.scandir(folder) if f.is_dir()] + for subfolder in subfolders: + update_mod(subfolder + "/") + else: + update_mod(folder) + print("Done.") + +def run_all_subfolders(folder): + for modfolder in [f.path for f in os.scandir(folder) if f.is_dir()]: + update_folder(modfolder + "/") + + +main() diff --git a/mods/cloudlands/init.lua b/mods/cloudlands/init.lua new file mode 100644 index 00000000..b7a65b1d --- /dev/null +++ b/mods/cloudlands/init.lua @@ -0,0 +1,3 @@ +local path = minetest.get_modpath(minetest.get_current_modname()) + +dofile(path.."/cloudlands.lua") diff --git a/mods/cloudlands/locale/template.txt b/mods/cloudlands/locale/template.txt new file mode 100644 index 00000000..1b6df0b3 --- /dev/null +++ b/mods/cloudlands/locale/template.txt @@ -0,0 +1,33 @@ +# textdomain: cloudlands + + +### cloudlands.lua ### + +Ancient Portalstone= +Bark of @1= +Bert Shackleton= +Blossom= +Cobweb= + +Construction requires 14 blocks of ancient portalstone. We have no knowledge of how portalstones were created, the means to craft them are likely lost to time, so our only source has been to scavenge the Nether for the remnants of ancient broken portals. A finished frame is four blocks wide, five blocks high, and stands vertically, like a doorway.= + +Dead bleached wood= +Diary of Bert Shackleton= +Fossil Display= +Fossilized Egg= +Giant Ziricote= +Giant tree= +Glowing @1= +Hallelujah Mountains Portal= +Heart of the Tree= +Leaves of a giant tree= +Sakura blossom= + +The aerostat is lost.@n@nHowever, salvage attempts throughout the night managed to@nsave most provisions before it finally broke apart and fell.@n@n ---@=@=@=@=---@n@nThis island is highly exposed and the weather did not treat@nthe tents well. We have enlarged a sheltered crag in the @1,@nbut it is laborous work and the condition of some of the party@nis becoming cause for concern.@n@nQuite a journey is now required, we cannot stay - nobody will@nlook for us here. McNish is attempting to strengthen the gliders.@n@n ---@=@=@=@=---= + +The only portal we managed to scavenge enough portalstone to build took us to a land of floating islands. There were hills and forests and even water up there, but the edges are a perilous drop — a depth of which we cannot even begin to plumb.= + +Weddell Outpost, November 21= +Wisteria blossom= +ice= +rock= diff --git a/mods/cloudlands/mod.conf b/mods/cloudlands/mod.conf new file mode 100644 index 00000000..f90dc170 --- /dev/null +++ b/mods/cloudlands/mod.conf @@ -0,0 +1,16 @@ +name = cloudlands +optional_depends = nether, vines, biomeinfo, schemlib, default, mcl_core, xpanes, ethereal, main +min_minetest_version = 5.0 +description = """ +Hallelujah Mountains for Minetest + +Giant rocks floating suspended in magnetic eddies. + +This can be run with or without a normal terrain mapgen, allowing pure skylands or an exotic addition to the landscape. + +Configurable settings include the altitude islands appear, and the ability to restrict the area they spawn. If you only ever see snow-covered islands then enable the "Use lowland biomes" option. +""" + +release = 18355 +author = DrFrankenstone +title = Hallelujah Mountains (AKA Cloudlands) diff --git a/mods/cloudlands/screenshot.png b/mods/cloudlands/screenshot.png new file mode 100644 index 00000000..8f90ae7b Binary files /dev/null and b/mods/cloudlands/screenshot.png differ diff --git a/mods/cloudlands/settingtypes.txt b/mods/cloudlands/settingtypes.txt new file mode 100644 index 00000000..70be6443 --- /dev/null +++ b/mods/cloudlands/settingtypes.txt @@ -0,0 +1,70 @@ +# If true then islands will use biomes from altitude 10 - higher than beaches +# and lower than mountains. This avoids a problem with some mods which have +# alpine biomes, causing all islands to be alpine/snowy due to their altitude, +# however plant-life may still reflect the actual altitude of the islands. +# +# Turn this off to have each island's altitude affect its biome. +cloudlands_use_lowland_biomes (Use lowland biomes) bool false + +# The average altitude islands are found at +# +# In Minetest game, ores like iron and diamond will be generated if the +# altitude of islands is set above 1200 and "Generate ores inside islands" +# is Enabled. +cloudlands_altitude (Altitude of islands) int 1500 + +# The rough variance of altitude in islands (plus or minus this value) +cloudlands_altitude_amplitude (Altitude variance) int 1200 + +# Generates patches of sand and dirt inside island stone cores. +cloudlands_generate_ores (Generate ores inside islands - patches of dirt, sand etc.) bool true + +# Set this to 0 to disable vines. +# +# A percentage value between 0 and 100 which controls the likelyhood of each +# node on an island edge having a vine growing down it. +# This option is ignored unless played with a game or mod which provides vines, +# such as plantlife_modpack, vines, or MineClone2. +# This setting does not alter the temperature or humidity determining whether +# vines will be present on an island. +cloudlands_vine_coverage (Vine coverage %) int 30 0 100 + +# If you wish to have large areas of the map without islands then specify +# here the area where islands may spawn. +# +# The format is: +#   minimum_x minimum_z maximum_x maximum_z +cloudlands_limit_rect (Limit island locations within rectange) string -32000 -32000 32000 32000 + +# Leave blank to allow islands to appear in any biome +# Otherwise list biomes by name, with a space between each one. +# +# Example: +#   cold_desert sandstone_desert +cloudlands_limit_biome (Limit island locations within listed biomes) string + +# Only used when "Limit island locations within listed biomes" is set. +# This specifies the altitude of biomes which determine whether islands can +# spawn, allowing islands to have their own biome system at island altitude +# while still having locations limited by the biomes at ground level. +# +#   Set to about 10 to use ground level biomes (10 is higher than beaches) +#   Leave blank to use the islands' altitudes. +cloudlands_limit_biome_altitude (Altitude for listed biomes) string + +# Set this to 0 to disable giant trees. +# +# A percentage value between 0 and 100 which controls the spread of the +# rare and gigantic trees which can sometimes be found growing on islands. +cloudlands_giant_tree_rarety (Giant tree rarety %) int 8 0 100 + +# Set true to allow glowing rarer variants of the rare and gigantic trees +# which can sometimes be found growing on islands. +# +# This option is ignored if tree rarety is set to 0 +cloudlands_bioluminescence (Allow glowing trees) bool true + +# Set true to allow players to build portals to islands. +# +# Portals require the Nether mod. +cloudlands_enable_portals (Enable portals to the islands) bool true diff --git a/mods/ethereal/biomes.lua b/mods/ethereal/biomes.lua index 9a679c96..3fa0e30c 100644 --- a/mods/ethereal/biomes.lua +++ b/mods/ethereal/biomes.lua @@ -55,7 +55,7 @@ register_biome(1, { name = "grassland_under", node_cave_liquid = {"default:water_source", "default:lava_source"}, heat_point = old and 45 or 50, humidity_point = old and 65 or 35, - y_min = -31000, y_max = -256}) + y_min = -2048, y_max = -256}) -- desert @@ -84,7 +84,7 @@ register_biome(ethereal.desert, { register_biome(ethereal.desert, { name = "desert_under", heat_point = old and 35 or 92, humidity_point = old and 20 or 16, - y_min = -31000, y_max = -256, + y_min = -2048, y_max = -256, node_cave_liquid = {"default:water_source", "default:lava_source"}}) -- bamboo @@ -172,7 +172,7 @@ register_biome(ethereal.snowy, { register_biome(ethereal.snowy, { name = "coniferous_forest_under", heat_point = old and 10 or 45, humidity_point = old and 40 or 70, - y_min = -31000, y_max = -256, + y_min = -2048, y_max = -256, node_cave_liquid = {"default:water_source", "default:lava_source"}}) -- taiga @@ -197,7 +197,7 @@ register_biome(ethereal.alpine, { register_biome(ethereal.alpine, { name = "taiga_under", heat_point = old and 10 or 25, humidity_point = old and 40 or 70, - y_min = -31000, y_max = -256, + y_min = -2048, y_max = -256, node_cave_liquid = {"default:water_source", "default:lava_source"}}) -- frost @@ -235,14 +235,14 @@ register_biome(ethereal.grassy, { register_biome(ethereal.grassy, { name = "deciduous_forest_ocean", heat_point = old and 13 or 60, humidity_point = old and 40 or 68, - y_min = -31000, y_max = 3, + y_min = -2048, y_max = 3, node_top = "default:sand", depth_top = 2, node_filler = "default:gravel", depth_filler = 1}) register_biome(ethereal.grassy, { name = "deciduous_forest_under", heat_point = old and 13 or 60, humidity_point = old and 40 or 68, - y_min = -31000, y_max = -256, + y_min = -2048, y_max = -256, node_cave_liquid = {"default:water_source", "default:lava_source"}}) -- caves @@ -280,7 +280,7 @@ register_biome(ethereal.grayness, { register_biome(ethereal.grayness, { name = "grayness_under", heat_point = 15, humidity_point = old and 25 or 30, - y_min = -31000, y_max = -23, + y_min = -2048, y_max = -23, node_cave_liquid = {"default:water_source", "default:lava_source"}}) -- grassy two @@ -350,7 +350,7 @@ register_biome(ethereal.junglee, { register_biome(ethereal.junglee, { name = "rainforest_under", heat_point = old and 30 or 86, humidity_point = old and 60 or 65, - y_min = -31000, y_max = -256, + y_min = -2048, y_max = -256, node_cave_liquid = {"default:water_source", "default:lava_source"}}) -- swamp @@ -444,7 +444,7 @@ register_biome(ethereal.sandstone, { register_biome(ethereal.sandstone, { name = "sandstone_desert_under", heat_point = old and 50 or 60, humidity_point = old and 20 or 0, - y_min = -31000, y_max = -256, + y_min = -2048, y_max = -256, node_cave_liquid = {"default:water_source", "default:lava_source"}}) -- plains @@ -485,7 +485,7 @@ register_biome(ethereal.savanna, { register_biome(ethereal.savanna, { name = "savanna_under", heat_point = old and 55 or 89, humidity_point = old and 25 or 42, - y_min = -31000, y_max = -256, + y_min = -2048, y_max = -256, node_cave_liquid = {"default:water_source", "default:lava_source"}}) -- fiery @@ -514,7 +514,7 @@ register_biome(ethereal.fiery, { register_biome(ethereal.fiery, { name = "fiery_under", heat_point = old and 75 or 80, humidity_point = 10, - y_min = -31000, y_max = -256, + y_min = -2048, y_max = -256, node_cave_liquid = {"default:lava_source"}}) -- glacier @@ -545,7 +545,7 @@ register_biome(ethereal.glacier, { register_biome(ethereal.glacier, { name = "glacier_under", heat_point = 0, humidity_point = old and 50 or 73, - y_max = -256, y_min = -31000, + y_max = -256, y_min = -2048, node_cave_liquid = {"default:water_source", "default:lava_source"}, node_dungeon = "default:cobble", node_dungeon_alt = "default:mossycobble", @@ -585,7 +585,7 @@ register_biome(ethereal.tundra, { register_biome(ethereal.tundra, { name = "tundra_under", - heat_point = 0, humidity_point = 40, y_max = -256, y_min = -31000, + heat_point = 0, humidity_point = 40, y_max = -256, y_min = -2048, node_cave_liquid = {"default:water_source", "default:lava_source"}}) -- only register when using new mapgen @@ -612,7 +612,7 @@ if not old then register_biome(ethereal.cold_desert, { name = "cold_desert_under", node_cave_liquid = {"default:water_source", "default:lava_source"}, - heat_point = 20, humidity_point = 85, y_min = -31000, y_max = -256}) + heat_point = 20, humidity_point = 85, y_min = -2048, y_max = -256}) -- snowy grassland (inbetween frost and taiga/jumble) diff --git a/mods/ethereal/decor.lua b/mods/ethereal/decor.lua index 1a6f0d7c..50b1e651 100644 --- a/mods/ethereal/decor.lua +++ b/mods/ethereal/decor.lua @@ -582,9 +582,9 @@ local function add_illumishroom(low, high, nodename) decoration = nodename}) end -add_illumishroom(-1000, -50, "ethereal:illumishroom") -add_illumishroom(-2000, -1000, "ethereal:illumishroom2") -add_illumishroom(-3000, -2000, "ethereal:illumishroom3") +add_illumishroom(-2000, -50, "ethereal:illumishroom") +add_illumishroom(-4000, -2000, "ethereal:illumishroom2") +add_illumishroom(-6000, -4000, "ethereal:illumishroom3") --= Register Biome Decoration Using Plants Mega Pack Lite if Xanadu found diff --git a/mods/everness/api.lua b/mods/everness/api.lua index 01ce8849..a746603d 100644 --- a/mods/everness/api.lua +++ b/mods/everness/api.lua @@ -82,7 +82,7 @@ Everness = { everness_coral_forest_under = { enabled = minetest.settings:get_bool('everness_coral_forest_under', true), y_max = tonumber(minetest.settings:get('everness_coral_forest_under_y_max')) or 0, - y_min = tonumber(minetest.settings:get('everness_coral_forest_under_y_min')) or -31000, + y_min = tonumber(minetest.settings:get('everness_coral_forest_under_y_min')) or -2048, }, everness_frosted_icesheet = { enabled = minetest.settings:get_bool('everness_frosted_icesheet', true), @@ -97,7 +97,7 @@ Everness = { everness_frosted_icesheet_under = { enabled = minetest.settings:get_bool('everness_frosted_icesheet_under', true), y_max = tonumber(minetest.settings:get('everness_frosted_icesheet_under_y_max')) or 0, - y_min = tonumber(minetest.settings:get('everness_frosted_icesheet_under_y_min')) or -31000, + y_min = tonumber(minetest.settings:get('everness_frosted_icesheet_under_y_min')) or -2048, }, everness_cursed_lands = { enabled = minetest.settings:get_bool('everness_cursed_lands', true), @@ -127,7 +127,7 @@ Everness = { everness_cursed_lands_under = { enabled = minetest.settings:get_bool('everness_cursed_lands_under', true), y_max = tonumber(minetest.settings:get('everness_cursed_lands_under_y_max')) or 0, - y_min = tonumber(minetest.settings:get('everness_cursed_lands_under_y_min')) or -31000, + y_min = tonumber(minetest.settings:get('everness_cursed_lands_under_y_min')) or -2048, }, everness_crystal_forest = { enabled = minetest.settings:get_bool('everness_crystal_forest', true), @@ -157,7 +157,7 @@ Everness = { everness_crystal_forest_under = { enabled = minetest.settings:get_bool('everness_crystal_forest_under', true), y_max = tonumber(minetest.settings:get('everness_crystal_forest_under_y_max')) or 0, - y_min = tonumber(minetest.settings:get('everness_crystal_forest_under_y_min')) or -31000, + y_min = tonumber(minetest.settings:get('everness_crystal_forest_under_y_min')) or -2048, }, everness_bamboo_forest = { enabled = minetest.settings:get_bool('everness_bamboo_forest', true), @@ -167,7 +167,7 @@ Everness = { everness_bamboo_forest_under = { enabled = minetest.settings:get_bool('everness_bamboo_forest_under', true), y_max = tonumber(minetest.settings:get('everness_bamboo_forest_under_y_max')) or 0, - y_min = tonumber(minetest.settings:get('everness_bamboo_forest_under_y_min')) or -31000, + y_min = tonumber(minetest.settings:get('everness_bamboo_forest_under_y_min')) or -2048, }, everness_forsaken_desert = { enabled = minetest.settings:get_bool('everness_forsaken_desert', true), @@ -182,7 +182,7 @@ Everness = { everness_forsaken_desert_under = { enabled = minetest.settings:get_bool('everness_forsaken_desert_under', true), y_max = tonumber(minetest.settings:get('everness_forsaken_desert_under_y_max')) or 0, - y_min = tonumber(minetest.settings:get('everness_forsaken_desert_under_y_min')) or -31000, + y_min = tonumber(minetest.settings:get('everness_forsaken_desert_under_y_min')) or -2048, }, everness_baobab_savanna = { enabled = minetest.settings:get_bool('everness_baobab_savanna', false), @@ -207,7 +207,7 @@ Everness = { everness_forsaken_tundra_under = { enabled = minetest.settings:get_bool('everness_forsaken_tundra_under', true), y_max = tonumber(minetest.settings:get('everness_forsaken_tundra_under_y_max')) or 0, - y_min = tonumber(minetest.settings:get('everness_forsaken_tundra_under_y_min')) or -31000, + y_min = tonumber(minetest.settings:get('everness_forsaken_tundra_under_y_min')) or -2048, }, everness_mineral_waters = { enabled = minetest.settings:get_bool('everness_mineral_waters', true), @@ -217,7 +217,7 @@ Everness = { everness_mineral_waters_under = { enabled = minetest.settings:get_bool('everness_mineral_waters_under', true), y_max = tonumber(minetest.settings:get('everness_mineral_waters_under_y_max')) or 0, - y_min = tonumber(minetest.settings:get('everness_mineral_waters_under_y_min')) or -31000, + y_min = tonumber(minetest.settings:get('everness_mineral_waters_under_y_min')) or -2048, }, }, features = { diff --git a/mods/everness/settingtypes.txt b/mods/everness/settingtypes.txt index c5cc0206..b1e41aab 100644 --- a/mods/everness/settingtypes.txt +++ b/mods/everness/settingtypes.txt @@ -58,7 +58,7 @@ everness_coral_forest_under (Coral Forest Under) bool true everness_coral_forest_under_y_max (y max) int -256 # Lower limit for biome. -everness_coral_forest_under_y_min (y min) int -31000 +everness_coral_forest_under_y_min (y min) int -2048 [*Frosted Icesheet] @@ -94,7 +94,7 @@ everness_frosted_icesheet_under (Frosted Icesheet Under) bool true everness_frosted_icesheet_under_y_max (y max) int -256 # Lower limit for biome. -everness_frosted_icesheet_under_y_min (y min) int -31000 +everness_frosted_icesheet_under_y_min (y min) int -2048 [*Cursed Lands] @@ -166,7 +166,7 @@ everness_cursed_lands_under (Cursed Lands Under) bool true everness_cursed_lands_under_y_max (y max) int -256 # Lower limit for biome. -everness_cursed_lands_under_y_min (y min) int -31000 +everness_cursed_lands_under_y_min (y min) int -2048 [*Crystal Forest] @@ -238,7 +238,7 @@ everness_crystal_forest_under (Crystal Forest Under) bool true everness_crystal_forest_under_y_max (y max) int -256 # Lower limit for biome. -everness_crystal_forest_under_y_min (y min) int -31000 +everness_crystal_forest_under_y_min (y min) int -2048 [*Bamboo Forest] @@ -262,7 +262,7 @@ everness_bamboo_forest_under (Bamboo Forest Under) bool true everness_bamboo_forest_under_y_max (y max) int -256 # Lower limit for biome. -everness_bamboo_forest_under_y_min (y min) int -31000 +everness_bamboo_forest_under_y_min (y min) int -2048 [*Forsaken Desert] @@ -298,7 +298,7 @@ everness_forsaken_desert_under (Forsaken Desert Under) bool true everness_forsaken_desert_under_y_max (y max) int -256 # Lower limit for biome. -everness_forsaken_desert_under_y_min (y min) int -31000 +everness_forsaken_desert_under_y_min (y min) int -2048 [*Baobab Savanna] @@ -358,7 +358,7 @@ everness_forsaken_tundra_under (Forsaken Tundra Under) bool true everness_forsaken_tundra_under_y_max (y max) int -256 # Lower limit for biome. -everness_forsaken_tundra_under_y_min (y min) int -31000 +everness_forsaken_tundra_under_y_min (y min) int -2048 [*Mineral Waters] @@ -382,7 +382,7 @@ everness_mineral_waters_under (Mineral Waters) bool false everness_mineral_waters_under_y_max (y max) int -256 # Lower limit for biome. -everness_mineral_waters_under_y_min (y min) int -31000 +everness_mineral_waters_under_y_min (y min) int -2048 [Features] diff --git a/mods/nether/nether/init.lua b/mods/nether/nether/init.lua index 57e7acb6..fd3bf51b 100644 --- a/mods/nether/nether/init.lua +++ b/mods/nether/nether/init.lua @@ -20,7 +20,7 @@ end --== EDITABLE OPTIONS ==-- -- Depth of the nether -local nether_middle = -20000 +local nether_middle = -30000 -- forest bottom perlin multiplication local f_bottom_scale = 4 diff --git a/mods/nether/nether/settings.lua b/mods/nether/nether/settings.lua index f0f97db4..adc328e5 100644 --- a/mods/nether/nether/settings.lua +++ b/mods/nether/nether/settings.lua @@ -1,5 +1,5 @@ local default_settings = { - trap_players = true, + trap_players = false, log_to_chat = false, log_level = 2, overworld_help = true, diff --git a/mods/nether/nether/settingtypes.txt b/mods/nether/nether/settingtypes.txt index 39d8510a..c46f44d5 100644 --- a/mods/nether/nether/settingtypes.txt +++ b/mods/nether/nether/settingtypes.txt @@ -6,7 +6,7 @@ # hellish effort to go back to their home in the overworld. # It is recommended to disable this setting in creative mode or if damage is # disabled. -nether.trap_players (Trap players) bool true +nether.trap_players (Trap players) bool false # If enabled, show log messages in the chat and not only in debug.txt nether.log_to_chat (Log messages to chat) bool false diff --git a/mods/other_worlds/README.md b/mods/other_worlds/README.md new file mode 100644 index 00000000..b0dd16e6 --- /dev/null +++ b/mods/other_worlds/README.md @@ -0,0 +1,34 @@ +Other Worlds +=== + +Minetest mod which adds asteroid layers. + +Each layer contains: + +1. Asteroids composed of unique materials. +2. Decoration (or lack of) for the asteroid surfaces, including plants and glowing crystals. +3. A skybox for the layer. + +Default layers are Space and Red Sky, but anyone familiar with editing mods should be able to easily adjust these or create different/ additional layers. + + +Settings +-------- + +Settings for this mod can be adjusted in Minetests Settings tab -> All Settings + +See the comments in this file for how to adjust: + +1. The minimum and maximum height of each layer. +2. Whether map-generation is currently active for individual layers. +3. Whether player gravity changes for each layer (off by default). +4. Whether crafting recipes are enabled. +5. Set the chance of finding ores in asteroids (default is 27 [0 to 100]). + +Note: It is advised to turn off map-generation after you have generated the required number of asteroids. + + +Licenses and Attribution +----------------------- + +Components of this mod use multiple licenses and were the work of several individuals. Please see license.txt for the full list. diff --git a/mods/other_worlds/asteroid_layer_helpers.lua b/mods/other_worlds/asteroid_layer_helpers.lua new file mode 100644 index 00000000..18de257d --- /dev/null +++ b/mods/other_worlds/asteroid_layer_helpers.lua @@ -0,0 +1,311 @@ + +-- submodule + +otherworlds.asteroids = {} + +-- Approximate realm limits + +local XMIN = -33000 +local XMAX = 33000 +local ZMIN = -33000 +local ZMAX = 33000 + +local ASCOT = 1.0 -- Large asteroid / comet nucleus noise threshold +local SASCOT = 1.0 -- Small asteroid / comet nucleus noise threshold +local STOT = 0.125 -- Asteroid stone threshold +local COBT = 0.05 -- Asteroid cobble threshold +local GRAT = 0.02 -- Asteroid gravel threshold +local ICET = 0.05 -- Comet ice threshold +local ATMOT = -0.2 -- Comet atmosphere threshold +local FISTS = 0.01 -- Fissure noise threshold at surface. Controls size of fissures and amount / size of fissure entrances +local FISEXP = 0.3 -- Fissure expansion rate under surface +local ORECHA = otherworlds.settings.ore_chance.value -- Ore 1/x chance per stone node +local CPCHU = 0 -- Maximum craters per chunk +local CRMIN = 5 -- Crater radius minimum, radius includes dust and obsidian layers +local CRRAN = 8 -- Crater radius range + +local random = math.random +local floor = math.floor +local abs = math.abs + + +-- Note: for fewer large objects: increase the 'spread' numbers in 'np_large' noise parameters. For fewer small objects do the same in 'np_small'. Then tune size with 'ASCOT' + +local np_large = { -- 3D Perlin noise 1 for large structures + offset = 0, + scale = 1, + spread = {x = 256, y = 128, z = 256}, + seed = -83928935, + octaves = 5, + persist = 0.6} + +local np_fissure = { -- 3D Perlin noise 3 for fissures + offset = 0, + scale = 1, + spread = {x = 64, y = 64, z = 64}, + seed = -188881, + octaves = 4, + persist = 0.5} + +local np_small = { -- 3D Perlin noise 4 for small structures + offset = 0, + scale = 1, + spread = {x = 128, y = 64, z = 128}, + seed = 1000760700090, + octaves = 4, + persist = 0.6} + +local np_ores = { -- 3D Perlin noise 5 for ore selection + offset = 0, + scale = 1, + spread = {x = 128, y = 128, z = 128}, + seed = -70242, + octaves = 1, + persist = 0.5} + +local np_latmos = { -- 3D Perlin noise 6 for comet atmosphere + offset = 0, + scale = 1, + spread = {x = 256, y = 128, z = 256}, + seed = -83928935, + octaves = 3, + persist = 0.6} + +local np_satmos = { -- 3D Perlin noise 7 for small comet atmosphere + offset = 0, + scale = 1, + spread = {x = 128, y = 64, z = 128}, + seed = 1000760700090, + octaves = 2, + persist = 0.6} + +-- On dignode function. Atmosphere flows into a dug hole. + +core.register_on_dignode(function(pos, oldnode, digger) + + if core.find_node_near(pos, 1, {"asteroid:atmos"}) + and core.get_node(pos).name == "air" then + core.set_node(pos, {name = "asteroid:atmos"}) + end +end) + +-- Generate on_generated function based on parameters + +function otherworlds.asteroids.create_on_generated(YMIN, YMAX, content_ids) + + local c_air = content_ids.c_air + local c_stone = content_ids.c_stone + local c_cobble = content_ids.c_cobble + local c_gravel = content_ids.c_gravel + local c_dust = content_ids.c_dust + local c_ironore = content_ids.c_ironore + local c_copperore = content_ids.c_copperore + local c_goldore = content_ids.c_goldore + local c_diamondore = content_ids.c_diamondore + local c_meseore = content_ids.c_meseore + local c_waterice = content_ids.c_waterice + local c_atmos = content_ids.c_atmos + local c_snowblock = content_ids.c_snowblock + local c_obsidian = content_ids.c_obsidian + + -- return the function closed over the upvalues we want + return function(minp, maxp, seed) + + if minp.x < XMIN or maxp.x > XMAX + or minp.y < YMIN or maxp.y > YMAX + or minp.z < ZMIN or maxp.z > ZMAX then + return + end + + local x0, y0, z0 = minp.x, minp.y, minp.z + local x1, y1, z1 = maxp.x, maxp.y, maxp.z + +-- local t1 = os.clock() +--print ("[asteroid] chunk ("..x0.." "..y0.." "..z0..")") + + local sidelen = x1 - x0 + 1 -- chunk side length + local chulens = {x = sidelen, y = sidelen, z = sidelen} + local minpos = {x = x0, y = y0, z = z0} + + local vm, emin, emax = core.get_mapgen_object("voxelmanip") + local area = VoxelArea:new{MinEdge = emin, MaxEdge = emax} + local data = vm:get_data() + + local nvals1 = core.get_perlin_map(np_large, chulens):get_3d_map_flat(minpos) + local nvals3 = core.get_perlin_map(np_fissure, chulens):get_3d_map_flat(minpos) + local nvals4 = core.get_perlin_map(np_small, chulens):get_3d_map_flat(minpos) + local nvals5 = core.get_perlin_map(np_ores, chulens):get_3d_map_flat(minpos) + local nvals6 = core.get_perlin_map(np_latmos, chulens):get_3d_map_flat(minpos) + local nvals7 = core.get_perlin_map(np_satmos, chulens):get_3d_map_flat(minpos) + + local ni = 1 + local noise1abs, noise4abs, comet, noise1dep, noise4dep, vi + + for z = z0, z1 do -- for each vertical plane do + for y = y0, y1 do -- for each horizontal row do + + vi = area:index(x0, y, z) -- LVM index for first node in x row + + for x = x0, x1 do -- for each node do + + noise1abs = abs(nvals1[ni]) + noise4abs = abs(nvals4[ni]) + comet = false + + -- comet biome + if nvals6[ni] < -(ASCOT + ATMOT) + or (nvals7[ni] < -(SASCOT + ATMOT) and nvals1[ni] < ASCOT) then + comet = true + end + + -- if below surface + if noise1abs > ASCOT or noise4abs > SASCOT then + + -- noise1dep zero at surface, positive beneath + noise1dep = noise1abs - ASCOT + + -- if no fissure + if abs(nvals3[ni]) > FISTS + noise1dep * FISEXP then + + -- noise4dep zero at surface, positive beneath + noise4dep = noise4abs - SASCOT + + if not comet + or (comet and (noise1dep > random() + ICET + or noise4dep > random() + ICET)) then + + -- asteroid or asteroid materials in comet + if noise1dep >= STOT or noise4dep >= STOT then + + -- stone/ores + if random(ORECHA) == 1 then + + if nvals5[ni] > 0.6 then + data[vi] = c_goldore + elseif nvals5[ni] < -0.6 then + data[vi] = c_diamondore + elseif nvals5[ni] > 0.2 then + data[vi] = c_meseore + elseif nvals5[ni] < -0.2 then + data[vi] = c_copperore + else + data[vi] = c_ironore + end + else + data[vi] = c_stone + end + + elseif noise1dep >= COBT or noise4dep >= COBT then + data[vi] = c_cobble + elseif noise1dep >= GRAT or noise4dep >= GRAT then + data[vi] = c_gravel + else + data[vi] = c_dust + end + else -- comet materials + if noise1dep >= ICET or noise4dep >= ICET then + data[vi] = c_waterice + else + data[vi] = c_snowblock + end + end + + elseif comet then -- fissures, if comet then add atmosphere + data[vi] = c_atmos + end + + elseif comet then -- if comet atmosphere then + data[vi] = c_atmos + end + + ni = ni + 1 + vi = vi + 1 + end + end + end + + local cr, cx, cz, comet, surfy, vi, nr, nodeid + + -- craters + for ci = 1, CPCHU do -- iterate + + -- exponential radius + cr = CRMIN + floor(random() ^ 2 * CRRAN) + cx = random(minp.x + cr, maxp.x - cr) -- centre x + cz = random(minp.z + cr, maxp.z - cr) -- centre z + comet = false + surfy = false + + for y = y1, y0 + cr, -1 do + + vi = area:index(cx, y, cz) -- LVM index for node + nodeid = data[vi] + + if nodeid == c_dust or nodeid == c_gravel or nodeid == c_cobble then + surfy = y + break + + elseif nodename == c_snowblock or nodename == c_waterice then + comet = true + surfy = y + break + end + end + + -- if surface found and 8 node space above impact node then + if surfy and y1 - surfy > 8 then + + for x = cx - cr, cx + cr do -- for each plane do + for z = cz - cr, cz + cr do -- for each column do + for y = surfy - cr, surfy + cr do -- for each node do + + -- LVM index for node + vi = area:index(x, y, z) + nr = ((x - cx) ^ 2 + (y - surfy) ^ 2 + (z - cz) ^ 2) ^ 0.5 + + if nr <= cr - 2 then + + if comet then + data[vi] = c_atmos + else + data[vi] = c_air + end + + elseif nr <= cr - 1 then + + nodeid = data[vi] + + if nodeid == c_gravel or nodeid == c_cobble + or nodeid == c_stone or nodeid == c_diamondore + or nodeid == c_goldore or nodeid == c_meseore + or nodeid == c_copperore or nodeid == c_ironore then + data[vi] = c_dust + end + + elseif nr <= cr then + + nodeid = data[vi] + + if nodeid == c_cobble or nodeid == c_stone then + data[vi] = c_obsidian -- obsidian buried under dust + end + end + end + end + end + end + end + + vm:set_data(data) + vm:set_lighting({day = 0, night = 0}) + vm:calc_lighting() + vm:write_to_map(data) + + data = nil ; collectgarbage("collect") -- clear mem +--[[ + local chugent = math.ceil((os.clock() - t1) * 1000) + print ("[asteroid] time "..chugent.." ms / used mem:" + .. collectgarbage("count") / 1024 .. " MiB") +]]-- + end +end diff --git a/mods/other_worlds/crafting.lua b/mods/other_worlds/crafting.lua new file mode 100644 index 00000000..e8cf1720 --- /dev/null +++ b/mods/other_worlds/crafting.lua @@ -0,0 +1,47 @@ + +-- register craft recipes when enabled + +if otherworlds.settings.crafting.enable then + + core.register_craft({ + output = "asteroid:cobble", + recipe = {{"asteroid:stone"}} + }) + + core.register_craft({ + output = "asteroid:gravel", + recipe = {{"asteroid:cobble"}} + }) + + core.register_craft({ + output = "asteroid:dust", + recipe = {{"asteroid:gravel"}} + }) + + core.register_craft({ + type = "cooking", + output = "asteroid:stone", + recipe = "asteroid:cobble" + }) + + core.register_craft({ + output = "asteroid:redcobble", + recipe = {{"asteroid:redstone"}} + }) + + core.register_craft({ + output = "asteroid:redgravel", + recipe = {{"asteroid:redcobble"}} + }) + + core.register_craft({ + output = "asteroid:reddust", + recipe = {{"asteroid:redgravel"}} + }) + + core.register_craft({ + type = "cooking", + output = "asteroid:redstone", + recipe = "asteroid:redcobble" + }) +end diff --git a/mods/other_worlds/init.lua b/mods/other_worlds/init.lua new file mode 100644 index 00000000..ab73027a --- /dev/null +++ b/mods/other_worlds/init.lua @@ -0,0 +1,26 @@ + +-- global, mod path and load mod sections + +otherworlds = {} + +local modpath = core.get_modpath("other_worlds") .. "/" + +dofile(modpath .. "settings.lua") +dofile(modpath .. "nodes.lua") +dofile(modpath .. "crafting.lua") +dofile(modpath .. "skybox.lua") + +-- required helpers for mapgen options below + +dofile(modpath .. "asteroid_layer_helpers.lua") + +if otherworlds.settings.space_asteroids.enable then + dofile(modpath .. "space_asteroids.lua") +end + +if otherworlds.settings.redsky_asteroids.enable then + dofile(modpath .. "redsky_asteroids.lua") +end + + +print("[MOD] Other Worlds loaded") diff --git a/mods/other_worlds/license.txt b/mods/other_worlds/license.txt new file mode 100644 index 00000000..038aa9b8 --- /dev/null +++ b/mods/other_worlds/license.txt @@ -0,0 +1,38 @@ + + +Original asteroids code: +License: MIT (https://opensource.org/licenses/MIT) +By paramat (available from https://github.com/paramat/asteroid) + +Original asteroid_ textures: +License: CC BY-SA 3.0 (https://creativecommons.org/licenses/by/3.0/) +Attribution: paramat + +Crystal models: +License: MIT (https://opensource.org/licenses/MIT) +By Electra Gizen + +Skybox texture: +License: CC BY-SA 3.0 (https://creativecommons.org/licenses/by/3.0/) +Attribution: Ulukai (available from http://opengameart.org/content/ulukais-space-skyboxes) + +--- + +Recoloured textures from various mods: +Grass textures based on default mod in minetest_game (WTFPL). +Redgrass texture based on junglegrass texture in default mod in minetest_game (assumed to be CC BY-SA 3.0). +Red versions of asteroid textures (CC BY-SA 3.0). + +--- + +Textures created for this mod: +CC BY-SA 4.0 (https://creativecommons.org/licenses/by-sa/4.0/) +Attribution: Shara RedCat + +All original code and edits to code for this mod: +License: MIT (https://opensource.org/licenses/MIT) +By Shara RedCat, tenplus1 and Mewmon + +--- + +Thanks also to DonBatman for help with the initial idea, and to the players of Red Cat Creative who gave ideas and helped in testing. \ No newline at end of file diff --git a/mods/other_worlds/mod.conf b/mods/other_worlds/mod.conf new file mode 100644 index 00000000..b1e117f2 --- /dev/null +++ b/mods/other_worlds/mod.conf @@ -0,0 +1,8 @@ +name = other_worlds +description = Adds asteroid layers and height-based skybox switches to create space environments. +depends = default +optional_depends = pova, nether +min_minetest_version = 5.0 +release = 30955 +author = TenPlus1 +title = Other Worlds diff --git a/mods/other_worlds/models/crystal_shape01.obj b/mods/other_worlds/models/crystal_shape01.obj new file mode 100644 index 00000000..06140046 --- /dev/null +++ b/mods/other_worlds/models/crystal_shape01.obj @@ -0,0 +1,228 @@ +# Blender v2.77 (sub 0) OBJ File: '' +# www.blender.org +mtllib crystal_shape01.mtl +o Crystal_Taya1.001 +v 0.019427 0.022291 -0.013603 +v 0.012823 0.031159 -0.009449 +v 0.013985 0.030045 -0.009115 +v 0.008443 0.038346 -0.005912 +v 0.000000 0.050687 0.000000 +v -0.015208 0.018939 -0.021719 +v 0.020909 0.009672 -0.020909 +v 0.021719 0.018939 -0.015208 +v -0.016684 -0.038612 -0.014735 +v -0.017468 -0.029657 -0.017468 +v -0.015357 -0.037747 -0.016760 +v -0.015748 -0.040752 -0.016497 +v -0.015913 -0.047431 -0.015913 +v -0.017109 -0.033753 0.015516 +v -0.018018 -0.023370 0.018018 +v -0.018512 -0.017718 0.018512 +v -0.016177 -0.044411 0.016177 +v -0.015748 -0.049313 0.015748 +v -0.015748 -0.049313 -0.015748 +v -0.019675 -0.004427 0.016381 +v -0.020909 0.009672 -0.020909 +v -0.020741 0.007752 0.020741 +v -0.021719 0.018939 0.015208 +v -0.020909 0.009672 0.020909 +v -0.018512 -0.003302 0.019773 +v 0.020560 0.005685 0.020560 +v 0.015208 0.018939 0.021719 +v 0.017174 -0.008451 0.019323 +v 0.018163 -0.021714 0.018163 +v -0.016177 -0.030600 0.017385 +v 0.015748 -0.049313 0.015748 +v 0.020909 0.009672 0.020909 +v 0.019672 -0.004460 0.017730 +v 0.020488 0.004867 -0.020488 +v 0.019075 -0.011285 -0.016746 +v 0.016094 -0.045364 -0.016094 +v 0.015748 -0.049313 -0.015748 +v 0.017187 -0.010187 -0.019171 +vt 0.0000 0.0000 +vt -0.0148 0.1172 +vt -0.0221 0.1025 +vt 0.0000 0.0000 +vt -0.0100 0.0907 +vt -0.0085 -0.0141 +vt -0.0000 0.0000 +vt -0.0729 -0.1433 +vt 0.1875 -0.3687 +vt -0.1099 -0.2268 +vt -0.1696 -0.4763 +vt -0.1677 -0.3298 +vt -0.1875 -0.3687 +vt 0.0165 -0.0925 +vt -0.0000 0.0000 +vt -0.0076 -0.0836 +vt -0.0199 -0.0214 +vt -0.0000 0.0000 +vt -0.0242 0.0087 +vt -0.0140 -0.0882 +vt -0.0200 -0.0214 +vt -0.3048 0.1074 +vt -0.0023 0.1562 +vt -0.3322 0.1973 +vt 0.0227 0.2604 +vt 0.0276 0.3172 +vt 0.0043 0.0492 +vt -0.3166 0.0189 +vt 0.0000 0.0000 +vt -0.3150 0.0000 +vt 0.0063 0.4506 +vt -0.3666 0.5921 +vt 0.0499 0.5728 +vt -0.0054 0.6851 +vt 0.0516 0.5921 +vt -0.0003 -0.1132 +vt -0.0000 0.0000 +vt -0.0360 -0.1247 +vt -0.0119 -0.1442 +vt -0.0000 0.0000 +vt -0.0359 -0.0113 +vt -0.3426 0.4619 +vt 0.0481 0.5521 +vt -0.3649 0.5728 +vt -0.0054 0.6851 +vt -0.3666 0.5921 +vt 0.0143 0.4102 +vt -0.3426 0.3172 +vt 0.0241 0.2770 +vt -0.3377 0.2604 +vt -0.3192 0.1878 +vt 0.0000 0.0000 +vt -0.3192 0.0492 +vt -0.3150 0.0000 +vt 0.0516 0.5921 +vt 0.0093 -0.0743 +vt -0.0000 0.0000 +vt -0.0102 -0.1067 +vt -0.0104 -0.1382 +vt -0.0000 0.0000 +vt -0.0208 -0.0316 +vt 0.0000 0.0000 +vt 0.0149 0.1726 +vt -0.0147 0.1327 +vt 0.0000 0.0000 +vt -0.0166 0.1044 +vt -0.0280 -0.0411 +vt -0.3348 0.4502 +vt 0.0474 0.5439 +vt -0.3631 0.5521 +vt -0.0054 0.6851 +vt -0.3666 0.5921 +vt 0.0100 0.3817 +vt -0.3391 0.2770 +vt 0.0035 0.0396 +vt -0.3150 0.0000 +vt 0.0000 0.0000 +vt 0.0516 0.5921 +vt 0.0000 0.0000 +vt 0.0168 0.3529 +vt -0.0139 0.3419 +vt 0.0000 0.0000 +vt -0.0126 0.1542 +vt -0.0306 -0.0113 +vt 0.0000 0.0859 +vt -0.0039 0.1161 +vt -0.3184 0.0396 +vt 0.0172 0.1973 +vt -0.3293 0.3928 +vt 0.0516 0.5921 +vt -0.3624 0.5439 +vt -0.0054 0.6851 +vt -0.3666 0.5921 +vt 0.0016 0.0189 +vt -0.3150 0.0000 +vt 0.0000 0.0000 +vt -0.1696 -0.4763 +vt 0.1875 -0.3687 +vt -0.1875 -0.3687 +vt -0.1696 -0.4763 +vt 0.1875 -0.3687 +vt -0.1875 -0.3687 +vt 0.0729 -0.1433 +vt -0.1875 -0.3687 +vt 0.1141 -0.2397 +vt -0.1696 -0.4763 +vt 0.1677 -0.3298 +vt 0.1875 -0.3687 +vt 0.1575 0.1575 +vt -0.1575 0.1575 +vt 0.1575 -0.1575 +vt -0.1575 -0.1575 +usemtl wire_191191191.001 +s 1 +f 1/1 2/2 3/3 +f 2/4 4/5 3/6 +f 5/7 4/8 6/9 +f 6/9 4/8 2/10 +f 6/9 2/10 7/11 +f 7/11 2/10 1/12 +f 7/11 1/12 8/13 +f 9/14 10/15 11/16 +f 12/17 9/18 11/19 +f 13/20 9/18 12/21 +f 9/22 14/23 10/24 +f 10/24 14/23 15/25 +f 10/24 15/25 16/26 +f 14/23 9/22 17/27 +f 17/27 9/22 13/28 +f 17/27 13/28 18/29 +f 18/29 13/28 19/30 +f 16/26 20/31 10/24 +f 10/24 20/31 21/32 +f 21/32 20/31 22/33 +f 21/32 22/33 23/34 +f 23/34 22/33 24/35 +f 25/36 22/37 20/38 +f 16/39 25/40 20/41 +f 25/42 26/43 22/44 +f 22/44 26/43 27/45 +f 22/44 27/45 24/46 +f 26/43 25/42 28/47 +f 28/47 25/42 16/48 +f 28/47 16/48 29/49 +f 29/49 16/48 15/50 +f 29/49 15/50 30/51 +f 29/49 30/51 31/52 +f 31/52 30/51 17/53 +f 31/52 17/53 18/54 +f 26/43 32/55 27/45 +f 30/56 15/57 14/58 +f 17/59 30/60 14/61 +f 29/62 33/63 28/64 +f 33/65 26/66 28/67 +f 33/68 34/69 26/70 +f 26/70 34/69 8/71 +f 26/70 8/71 32/72 +f 34/69 33/68 35/73 +f 35/73 33/68 29/74 +f 35/73 29/74 36/75 +f 36/75 29/74 31/76 +f 36/75 31/76 37/77 +f 34/69 7/78 8/71 +f 36/79 38/80 35/81 +f 38/82 34/83 35/84 +f 12/85 11/86 36/87 +f 36/87 11/86 10/88 +f 36/87 10/88 38/89 +f 38/89 10/88 21/90 +f 38/89 21/90 34/91 +f 34/91 21/90 6/92 +f 34/91 6/92 7/93 +f 12/85 36/87 13/94 +f 13/94 36/87 37/95 +f 13/94 37/95 19/96 +f 21/97 23/98 6/99 +f 6/99 23/98 5/7 +f 24/100 27/101 23/102 +f 23/102 27/101 5/7 +f 4/103 27/104 3/105 +f 3/105 27/104 32/106 +f 3/105 32/106 1/107 +f 1/107 32/106 8/108 +f 4/103 5/7 27/104 +f 31/109 18/110 37/111 +f 37/111 18/110 19/112 diff --git a/mods/other_worlds/models/crystal_shape02.obj b/mods/other_worlds/models/crystal_shape02.obj new file mode 100644 index 00000000..0b92b901 --- /dev/null +++ b/mods/other_worlds/models/crystal_shape02.obj @@ -0,0 +1,198 @@ +# Blender v2.77 (sub 0) OBJ File: '' +# www.blender.org +mtllib crystal_shape02.mtl +o Crystal_Taya2.000 +v 0.006739 0.025244 0.012075 +v 0.001956 0.043504 0.004196 +v 0.004129 0.024144 0.013098 +v 0.011672 0.002214 0.025031 +v 0.000000 0.051819 0.000000 +v 0.026389 -0.000477 -0.012306 +v 0.023148 -0.023541 0.023148 +v 0.012306 -0.000477 0.026389 +v 0.023266 -0.013100 -0.018731 +v 0.024664 -0.012754 -0.016476 +v 0.023359 -0.022037 -0.022441 +v -0.006315 0.024980 -0.013543 +v -0.008197 0.010863 -0.020143 +v 0.023148 -0.023541 -0.023148 +v -0.011933 0.001106 -0.025590 +v -0.012306 -0.000477 -0.026389 +v -0.010407 0.010810 -0.018614 +v -0.018415 0.015325 0.008587 +v -0.009799 0.031187 0.003172 +v -0.001187 0.049467 0.000554 +v -0.023148 -0.023541 -0.023148 +v -0.026389 -0.000477 0.012306 +v -0.009284 0.030199 0.005679 +v -0.023148 -0.023541 0.023148 +v -0.019685 -0.048181 -0.019685 +v 0.019685 -0.048181 -0.019685 +v 0.019685 -0.048181 0.019685 +v -0.019685 -0.048181 0.019685 +vt 0.0323 -0.2020 +vt -0.0000 0.0000 +vt 0.0048 -0.2141 +vt 0.0306 -0.2671 +vt -0.0000 0.0000 +vt -0.0273 -0.0128 +vt 0.0000 0.0000 +vt -0.0327 -0.0894 +vt 0.2059 -0.5620 +vt -0.0904 -0.2856 +vt -0.1383 -0.8099 +vt -0.1953 -0.5331 +vt -0.2059 -0.5620 +vt 0.0710 -0.1265 +vt -0.0000 0.0000 +vt 0.0445 -0.1230 +vt 0.0304 -0.0919 +vt -0.0000 0.0000 +vt -0.0265 0.0036 +vt -0.2059 -0.5620 +vt 0.1057 -0.2884 +vt -0.1546 -0.6977 +vt 0.1459 -0.4402 +vt -0.1383 -0.8099 +vt 0.1997 -0.5450 +vt 0.2059 -0.5620 +vt -0.1428 -0.7937 +vt 0.0000 0.0000 +vt 0.0280 0.1172 +vt 0.0012 0.1178 +vt 0.0000 0.0000 +vt -0.0042 0.1559 +vt -0.0269 0.0006 +vt -0.1393 -0.4407 +vt 0.1437 -0.3922 +vt -0.1057 -0.2884 +vt 0.0633 -0.2217 +vt 0.0093 -0.0253 +vt -0.1383 -0.8099 +vt -0.1997 -0.5450 +vt -0.2059 -0.5620 +vt 0.2059 -0.5620 +vt 0.0000 0.0000 +vt 0.0137 0.1764 +vt -0.0111 0.1881 +vt 0.0000 0.0000 +vt -0.0163 0.2146 +vt -0.0251 0.0110 +vt 0.0327 -0.0894 +vt -0.0678 -0.2324 +vt 0.0836 -0.2974 +vt -0.1437 -0.3922 +vt -0.1383 -0.8099 +vt -0.2059 -0.5620 +vt -0.0093 -0.0253 +vt 0.2059 -0.5620 +vt 0.1953 -0.5331 +vt -0.0738 0.4817 +vt -0.4283 0.2488 +vt 0.0346 0.2488 +vt 0.0000 0.0000 +vt -0.3937 0.0000 +vt -0.0738 0.4817 +vt -0.4283 0.2488 +vt -0.0321 0.3577 +vt 0.0276 0.2640 +vt 0.0346 0.2488 +vt 0.0000 0.0000 +vt -0.3937 0.0000 +vt -0.0738 0.4817 +vt -0.4283 0.2488 +vt 0.0346 0.2488 +vt 0.0000 0.0000 +vt -0.3937 0.0000 +vt -0.0738 0.4817 +vt -0.4283 0.2488 +vt 0.0346 0.2488 +vt 0.0000 0.0000 +vt -0.3937 0.0000 +vt 0.1969 0.1969 +vt -0.1969 0.1969 +vt 0.1969 -0.1969 +vt -0.1969 -0.1969 +vn 0.1681 0.4273 0.8883 +vn 0.1213 0.5063 0.8538 +vn 0.8744 0.3663 0.3183 +vn 0.8744 0.3663 0.3182 +vn 0.8745 0.3664 0.3178 +vn 0.8439 0.0624 -0.5328 +vn 0.8105 0.2318 -0.5380 +vn 0.3182 0.3663 -0.8744 +vn 0.3183 0.3663 -0.8744 +vn 0.3181 0.3663 -0.8744 +vn -0.4802 0.5605 -0.6747 +vn -0.5239 0.4171 -0.7427 +vn -0.8744 0.3663 -0.3182 +vn -0.8744 0.3663 -0.3183 +vn -0.8744 0.3663 -0.3181 +vn -0.7587 0.5377 0.3678 +vn -0.8295 0.4401 0.3438 +vn -0.3182 0.3663 0.8744 +vn -0.3183 0.3663 0.8744 +vn -0.3181 0.3663 0.8744 +vn -0.3180 0.3664 0.8744 +vn -0.3181 0.3664 0.8744 +vn 0.0000 -0.1392 -0.9903 +vn 0.9903 -0.1391 0.0000 +vn 0.9903 -0.1392 0.0000 +vn 0.9903 -0.1390 -0.0000 +vn 0.0000 -0.1392 0.9903 +vn -0.9903 -0.1392 -0.0000 +vn 0.0000 -1.0000 -0.0000 +usemtl wire_191191191.000 +s 1 +f 1/1/1 2/2/1 3/3/1 +f 4/4/2 1/5/2 3/6/2 +f 5/7/3 2/8/3 6/9/4 +f 6/9/4 2/8/3 1/10/4 +f 6/9/4 1/10/4 7/11/4 +f 7/11/4 1/10/4 4/12/4 +f 7/11/4 4/12/4 8/13/5 +f 9/14/6 6/15/6 10/16/6 +f 11/17/7 9/18/7 10/19/7 +f 5/7/8 6/20/8 12/21/8 +f 12/21/8 6/20/8 9/22/8 +f 12/21/8 9/22/8 13/23/8 +f 13/23/8 9/22/8 14/24/9 +f 13/23/8 14/24/9 15/25/8 +f 15/25/8 14/24/9 16/26/8 +f 9/22/8 11/27/10 14/24/9 +f 15/28/11 17/29/11 13/30/11 +f 17/31/12 12/32/12 13/33/12 +f 17/34/13 18/35/13 12/36/14 +f 12/36/14 18/35/13 19/37/14 +f 12/36/14 19/37/14 20/38/13 +f 18/35/13 17/34/13 21/39/13 +f 21/39/13 17/34/13 15/40/13 +f 21/39/13 15/40/13 16/41/15 +f 21/39/13 22/42/13 18/35/13 +f 20/38/13 5/7/13 12/36/14 +f 18/43/16 23/44/16 19/45/16 +f 23/46/17 20/47/17 19/48/17 +f 2/49/18 23/50/19 3/51/19 +f 3/51/19 23/50/19 18/52/18 +f 3/51/19 18/52/18 24/53/18 +f 24/53/18 18/52/18 22/54/20 +f 5/7/21 20/55/22 2/49/18 +f 2/49/18 20/55/22 23/50/19 +f 8/56/18 4/57/18 24/53/18 +f 24/53/18 4/57/18 3/51/19 +f 16/58/23 14/59/23 21/60/23 +f 21/60/23 14/59/23 25/61/23 +f 25/61/23 14/59/23 26/62/23 +f 6/63/24 7/64/25 10/65/25 +f 10/65/25 7/64/25 11/66/26 +f 11/66/26 7/64/25 14/67/24 +f 14/67/24 7/64/25 26/68/25 +f 26/68/25 7/64/25 27/69/25 +f 8/70/27 24/71/27 7/72/27 +f 7/72/27 24/71/27 27/73/27 +f 27/73/27 24/71/27 28/74/27 +f 22/75/28 21/76/28 24/77/28 +f 24/77/28 21/76/28 28/78/28 +f 28/78/28 21/76/28 25/79/28 +f 27/80/29 28/81/29 26/82/29 +f 26/82/29 28/81/29 25/83/29 diff --git a/mods/other_worlds/nodes.lua b/mods/other_worlds/nodes.lua new file mode 100644 index 00000000..b4820c31 --- /dev/null +++ b/mods/other_worlds/nodes.lua @@ -0,0 +1,311 @@ + +-- Asteroid nodes + +core.register_node(":asteroid:stone", { + description = "Asteroid Stone", + tiles = {"default_stone.png"}, + is_ground_content = false, + drop = 'asteroid:cobble', + groups = {cracky = 3, stone = 1, not_in_creative_inventory = 1}, + sounds = default.node_sound_stone_defaults() +}) + +core.register_node(":asteroid:redstone", { + description = "Asteroid Stone", + tiles = {"asteroid_redstone.png"}, + is_ground_content = false, + drop = 'asteroid:redcobble', + groups = {cracky = 3, stone = 1}, + sounds = default.node_sound_stone_defaults() +}) + +core.register_node(":asteroid:cobble", { + description = "Asteroid Cobble", + tiles = {"asteroid_cobble.png"}, + is_ground_content = false, + groups = {cracky = 3, stone = 2}, + sounds = default.node_sound_stone_defaults() +}) + +core.register_node(":asteroid:redcobble", { + description = "Asteroid Cobble", + tiles = {"asteroid_redcobble.png"}, + is_ground_content = false, + groups = {cracky = 3, stone = 2}, + sounds = default.node_sound_stone_defaults() +}) + +core.register_node(":asteroid:gravel", { + description = "Asteroid Gravel", + tiles = {"asteroid_gravel.png"}, + is_ground_content = false, + groups = {crumbly = 2}, + sounds = default.node_sound_dirt_defaults({ + footstep = {name = "default_gravel_footstep", gain = 0.2} + }) +}) + +core.register_node(":asteroid:redgravel", { + description = "Asteroid Gravel", + tiles = {"asteroid_redgravel.png"}, + is_ground_content = false, + groups = {crumbly = 2}, + sounds = default.node_sound_dirt_defaults({ + footstep = {name = "default_gravel_footstep", gain = 0.2} + }) +}) + +core.register_node(":asteroid:dust", { + description = "Asteroid Dust", + tiles = {"asteroid_dust.png"}, + is_ground_content = false, + groups = {crumbly = 3}, + sounds = default.node_sound_dirt_defaults({ + footstep = {name = "default_gravel_footstep", gain = 0.1} + }) +}) + +core.register_node(":asteroid:reddust", { + description = "Asteroid Dust", + tiles = {"asteroid_reddust.png"}, + is_ground_content = false, + groups = {crumbly = 3}, + sounds = default.node_sound_dirt_defaults({ + footstep = {name = "default_gravel_footstep", gain = 0.1} + }) +}) + +core.register_node(":asteroid:ironore", { + description = "Asteroid Iron Ore", + tiles = {"asteroid_redstone.png^default_mineral_iron.png"}, + is_ground_content = false, + groups = {cracky = 2}, + drop = "default:iron_lump", + sounds = default.node_sound_stone_defaults() +}) + +core.register_node(":asteroid:copperore", { + description = "Asteroid Copper Ore", + tiles = {"asteroid_redstone.png^default_mineral_copper.png"}, + is_ground_content = false, + groups = {cracky = 2}, + drop = "default:copper_lump", + sounds = default.node_sound_stone_defaults() +}) + +core.register_node(":asteroid:goldore", { + description = "Asteroid Gold Ore", + tiles = {"asteroid_redstone.png^default_mineral_gold.png"}, + is_ground_content = false, + groups = {cracky = 2}, + drop = "default:gold_lump", + sounds = default.node_sound_stone_defaults() +}) + +core.register_node(":asteroid:diamondore", { + description = "Asteroid Diamond Ore", + tiles = {"asteroid_redstone.png^default_mineral_diamond.png"}, + is_ground_content = false, + groups = {cracky = 1}, + drop = "default:diamond", + sounds = default.node_sound_stone_defaults() +}) + +core.register_node(":asteroid:meseore", { + description = "Asteroid Mese Ore", + tiles = {"asteroid_redstone.png^default_mineral_mese.png"}, + is_ground_content = false, + groups = {cracky = 1}, + drop = "default:mese_crystal", + sounds = default.node_sound_stone_defaults() +}) + +core.register_node(":asteroid:atmos", { + description = "Comet Atmosphere", + drawtype = "glasslike", + tiles = {"asteroid_atmos.png"}, + paramtype = "light", + sunlight_propagates = true, + walkable = false, + pointable = false, + diggable = false, + buildable_to = true, + is_ground_content = false, + use_texture_alpha = "blend", + post_effect_color = {a = 31, r = 241, g = 248, b = 255}, + groups = {not_in_creative_inventory = 1}, + drop = {} +}) + +-- Redsky plant nodes + +core.register_node(":mars:redgrass", { + description = "Red Grass", + drawtype = "plantlike", + waving = 1, + visual_scale = 1.3, + tiles = {"mars_redgrass.png"}, + inventory_image = "mars_redgrass.png", + wield_image = "mars_redgrass.png", + paramtype = "light", + sunlight_propagates = true, + walkable = false, + buildable_to = true, + groups = {snappy = 3, flora = 1, attached_node = 1}, + sounds = default.node_sound_leaves_defaults(), + selection_box = { + type = "fixed", + fixed = {-0.5, -0.5, -0.5, 0.5, -5/16, 0.5} + } +}) + +core.register_node(":mars:redweed", { + description = "Red Weed", + drawtype = "plantlike", + waving = 1, + visual_scale = 1.3, + tiles = {"mars_redweed.png"}, + inventory_image = "mars_redweed.png", + wield_image = "mars_redweed.png", + paramtype = "light", + sunlight_propagates = true, + walkable = false, + buildable_to = true, + groups = {snappy = 3, flora = 1, attached_node = 1}, + sounds = default.node_sound_leaves_defaults(), + selection_box = { + type = "fixed", + fixed = {-0.5, -0.5, -0.5, 0.5, -5/16, 0.5} + } +}) + +core.register_node(":mars:moss", { + description = "Martian Moss", + drawtype = "nodebox", + tiles = {"mars_moss.png"}, + inventory_image = "mars_moss.png", + wield_image = "mars_moss.png", + paramtype = "light", + paramtype2 = "facedir", + use_texture_alpha = "clip", + sunlight_propagates = true, + walkable = false, + node_box = { + type = "fixed", + fixed = {-1/2, -1/2, -1/2, 1/2, -15/32, 1/2}, + }, + selection_box = { + type = "fixed", + fixed = {-1/2, -1/2, -1/2, 1/2, -15/32, 1/2}, + }, + groups = {snappy = 3, flora = 1, attached_node = 1}, + sounds = default.node_sound_leaves_defaults() +}) + +-- mars grass + +core.register_node(":mars:grass_1", { + description = "Martian Grass", + drawtype = "plantlike", + waving = 1, + tiles = {"mars_grass_1.png"}, + inventory_image = "mars_grass_3.png", + wield_image = "mars_grass_3.png", + paramtype = "light", + sunlight_propagates = true, + walkable = false, + buildable_to = true, + groups = {snappy = 3, flora = 1, attached_node = 1}, + sounds = default.node_sound_leaves_defaults(), + selection_box = { + type = "fixed", + fixed = {-0.5, -0.5, -0.5, 0.5, -5/16, 0.5} + }, + + on_place = function(itemstack, placer, pointed_thing) + + -- place a random grass node + local stack = ItemStack("mars:grass_" .. math.random(5)) + local ret = core.item_place(stack, placer, pointed_thing) + + return ItemStack("mars:grass_1 " + .. itemstack:get_count() - (1 - ret:get_count())) + end +}) + +for i = 2, 5 do + + core.register_node(":mars:grass_" .. i, { + description = "Martian Grass", + drawtype = "plantlike", + waving = 1, + tiles = {"mars_grass_" .. i .. ".png"}, + inventory_image = "mars_grass_" .. i .. ".png", + wield_image = "mars_grass_" .. i .. ".png", + paramtype = "light", + sunlight_propagates = true, + walkable = false, + buildable_to = true, + drop = "mars:grass_1", + groups = { + snappy = 3, flora = 1, attached_node = 1, + not_in_creative_inventory = 1 + }, + sounds = default.node_sound_leaves_defaults(), + selection_box = { + type = "fixed", + fixed = {-0.5, -0.5, -0.5, 0.5, -5/16, 0.5} + } + }) +end + +-- Crystals + +local sbox = { + type = "fixed", + fixed = {-5/16, -8/16, -6/16, 5/16, -1/32, 5/16}} + +local crystal_list = { + {"ghost_crystal", "ghost_crystal.png"}, + {"red_crystal", "red_crystal.png"}, + {"rose_quartz", "rose_quartz.png"}} + +for i = 1, #crystal_list do -- in ipairs(crystal_list) do + + local name = crystal_list[i][1] + local texture = crystal_list[i][2] + + core.register_node(":crystals:" .. name .. "_1", { + description = "Glowing Crystal", + drawtype = "mesh", + mesh = "crystal_shape01.obj", + tiles = {"crystals_" .. texture}, + wield_scale = {x = 7, y = 7, z = 7}, + paramtype = "light", + paramtype2 = "facedir", + selection_box = sbox, + walkable = false, + light_source = 10, + use_texture_alpha = "blend", + visual_scale = 10, + groups = {snappy = 2, choppy = 2, oddly_breakable_by_hand = 3}, + sounds = default.node_sound_glass_defaults() + }) + + core.register_node(":crystals:" .. name .. "_2", { + description = "Glowing Crystal", + drawtype = "mesh", + mesh = "crystal_shape02.obj", + tiles = {"crystals_" .. texture}, + wield_scale = {x = 7, y = 7, z = 7}, + paramtype = "light", + paramtype2 = "facedir", + selection_box = sbox, + walkable = false, + light_source = 10, + use_texture_alpha = "blend", + visual_scale = 10, + groups = {snappy = 2, choppy = 2, oddly_breakable_by_hand = 3}, + sounds = default.node_sound_glass_defaults() + }) +end diff --git a/mods/other_worlds/redsky_asteroids.lua b/mods/other_worlds/redsky_asteroids.lua new file mode 100644 index 00000000..9da627c1 --- /dev/null +++ b/mods/other_worlds/redsky_asteroids.lua @@ -0,0 +1,72 @@ + +-- Approximate realm limits + +local YMIN = otherworlds.settings.redsky_asteroids.YMIN or 6000 +local YMAX = otherworlds.settings.redsky_asteroids.YMAX or 7000 + +-- Register on_generated function for this layer + +core.register_on_generated( + otherworlds.asteroids.create_on_generated(YMIN, YMAX, { + + c_air = core.get_content_id("air"), + c_obsidian = core.get_content_id("default:obsidian"), + c_stone = core.get_content_id("asteroid:redstone"), + c_cobble = core.get_content_id("air"), + c_gravel = core.get_content_id("asteroid:redgravel"), + c_dust = core.get_content_id("asteroid:reddust"), + c_ironore = core.get_content_id("asteroid:ironore"), + c_copperore = core.get_content_id("asteroid:copperore"), + c_goldore = core.get_content_id("asteroid:goldore"), + c_diamondore = core.get_content_id("asteroid:diamondore"), + c_meseore = core.get_content_id("asteroid:meseore"), + c_waterice = core.get_content_id("default:ice"), + c_atmos = core.get_content_id("asteroid:atmos"), + c_snowblock = core.get_content_id("default:snowblock") +})) + +-- Deco code for grass and crystal + +local TOPDECO = 500 -- how often deco appears on top of asteroid cobble + +local grass = { + "mars:grass_1", "mars:grass_2", "mars:grass_3", "mars:grass_4", "mars:grass_5"} + +local flower = {"mars:moss", "mars:redweed", "mars:redgrass"} + +local crystal = { + "crystals:ghost_crystal_1", "crystals:ghost_crystal_2", + "crystals:red_crystal_1", "crystals:red_crystal_2", + "crystals:rose_quartz_1", "crystals:rose_quartz_2"} + +local random = math.random + +-- Add surface decoration + +core.register_on_generated(function(minp, maxp) + + if minp.y < YMIN or maxp.y > YMAX then return end + + local bpos, ran + local coal = core.find_nodes_in_area_under_air(minp, maxp, {"asteroid:redgravel"}) + + for n = 1, #coal do + + bpos = {x = coal[n].x, y = coal[n].y + 1, z = coal[n].z} + + ran = random(TOPDECO) + + if ran < 100 then -- grass + + core.swap_node(bpos, {name = grass[random(#grass)]}) + + elseif ran >= 180 and ran <= 200 then -- other plants + + core.swap_node(bpos, {name = flower[random(#flower)]}) + + elseif ran == TOPDECO then -- crystals + + core.swap_node(bpos, {name = crystal[random(#crystal)]}) + end + end +end) diff --git a/mods/other_worlds/screenshot.png b/mods/other_worlds/screenshot.png new file mode 100644 index 00000000..82975368 Binary files /dev/null and b/mods/other_worlds/screenshot.png differ diff --git a/mods/other_worlds/settings.lua b/mods/other_worlds/settings.lua new file mode 100644 index 00000000..bc35f8bc --- /dev/null +++ b/mods/other_worlds/settings.lua @@ -0,0 +1,45 @@ + +otherworlds.settings = {} + +-- general + +otherworlds.settings.crafting = { + -- set to false to remove crafting recipes + enable = core.settings:get_bool("otherworlds.crafting", true) +} + +-- space_asteroids + +otherworlds.settings.space_asteroids = { + -- set to false to prevent space mapgen + enable = core.settings:get_bool("otherworlds.space", true), + -- minimum height of space layer + YMIN = tonumber(core.settings:get("otherworlds.space.ymin") or 20000), + -- maximum height for space layer + YMAX = tonumber(core.settings:get("otherworlds.space.ymax") or 25000) +} + +-- redsky_asteroids + +otherworlds.settings.redsky_asteroids = { + -- set to false to prevent redsky mapgen + enable = core.settings:get_bool("otherworlds.redsky", true), + -- minimum height of redsky layer + YMIN = tonumber(core.settings:get("otherworlds.redsky.ymin") or 25000), + -- maximum height for redsky layer + YMAX = tonumber(core.settings:get("otherworlds.redsky.ymax") or 30000) +} + +-- gravity + +otherworlds.settings.gravity = { + -- set to true to enable gravity + enable = core.settings:get_bool("otherworlds.gravity", false) +} + +-- increase or decrease change of ores appearing in asteroids + +otherworlds.settings.ore_chance = { + -- default ore chance is multiplied by following value + value = tonumber(core.settings:get("otherworlds.ore_chance") or 27) +} diff --git a/mods/other_worlds/settingtypes.txt b/mods/other_worlds/settingtypes.txt new file mode 100644 index 00000000..2766a90e --- /dev/null +++ b/mods/other_worlds/settingtypes.txt @@ -0,0 +1,13 @@ +otherworlds.crafting (Enable crafting recipes) bool true + +otherworlds.space (Enable Space asteroids) bool true +otherworlds.space.ymin (Space asteroid min height) int 20000 +otherworlds.space.ymax (Space asteroid max height) int 25000 + +otherworlds.redsky (Enable Redsky asteroids) bool true +otherworlds.redsky.ymin (Redsky asteroid min height) int 25000 +otherworlds.redsky.ymax (Redsky asteroid max height) int 31000 + +otherworlds.gravity (Enable gravity) bool false + +otherworlds.ore_chance (Chance of finding ores) int 27 diff --git a/mods/other_worlds/skybox.lua b/mods/other_worlds/skybox.lua new file mode 100644 index 00000000..7778d941 --- /dev/null +++ b/mods/other_worlds/skybox.lua @@ -0,0 +1,220 @@ + +-- Heights for skyboxes + +local underground_low = -31000 +local underground_high = -50 +local space_low = 5000 +local space_high = 5999 +local redsky_low = 6000 +local redsky_high = 6999 +local nether_low = -32000 +local nether_high = -31000 + +-- Nether check + +local mod_nether = core.get_modpath("nether") + +if mod_nether then + + nether_low = nether.DEPTH_FLOOR or -32000 + nether_high = nether.DEPTH_CEILING or -31000 + underground_low = nether_high + + if core.get_modpath("climate_api") then + mod_nether = nil -- remove nether skybox for climate_api version + end +end + +-- Holds name of skybox showing for each player + +local player_list = {} + +-- Outerspace skybox + +local spaceskybox = { + "sky_pos_z.png", + "sky_neg_z.png^[transformR180", + "sky_neg_y.png^[transformR270", + "sky_pos_y.png^[transformR270", + "sky_pos_x.png^[transformR270", + "sky_neg_x.png^[transformR90"} + +-- Redsky skybox + +local redskybox = { + "sky_pos_z.png^[colorize:#99000050", + "sky_neg_z.png^[transformR180^[colorize:#99000050", + "sky_neg_y.png^[transformR270^[colorize:#99000050", + "sky_pos_y.png^[transformR270^[colorize:#99000050", + "sky_pos_x.png^[transformR270^[colorize:#99000050", + "sky_neg_x.png^[transformR90^[colorize:#99000050"} + +-- Darkest space skybox + +local darkskybox = { + "sky_pos_z.png^[colorize:#00005070", + "sky_neg_z.png^[transformR180^[colorize:#00005070", + "sky_neg_y.png^[transformR270^[colorize:#00005070", + "sky_pos_y.png^[transformR270^[colorize:#00005070", + "sky_pos_x.png^[transformR270^[colorize:#00005070", + "sky_neg_x.png^[transformR90^[colorize:#00005070"} + +-- check for active pova mod + +local mod_pova = core.get_modpath("pova") + +-- gravity helper function + +local function set_gravity(player, grav) + + if mod_pova then + pova.add_override(player:get_player_name(), "default", {gravity = grav}) + else + player:set_physics_override({gravity = grav}) + end +end + +-- globalstep function runs every 2 seconds to show appropriate skybox + +local timer, timer2 = 0, 0 + +core.register_globalstep(function(dtime) + + timer = timer + dtime ; if timer < 2 then return end ; timer = 0 + timer2 = timer2 + 2 + + local name, pos, current + + for _, player in pairs(core.get_connected_players()) do + + name = player:get_player_name() + pos = player:get_pos() + current = player_list[name] or "" + + -- this just adds nether background outwith climate_api mod + + if mod_nether and pos.y >= nether_low and pos.y <= nether_high + and (current ~= "nether" or (current == "nether" and timer2 > 6)) then + + timer2 = 0 -- reset nether layer timer (every 10 seconds) + + local base_col = current ~= "nether" and "#1D0504" + local ps, cn = core.find_nodes_in_area( + {x = pos.x - 6, y = pos.y - 6, z = pos.z - 6}, + {x = pos.x + 6, y = pos.y + 6, z = pos.z + 6}, + {"nether:rack", "nether:rack_deep", "nether:geode", "nether:geodelite"}) + + -- easy find nether layer via quick node count + + if (cn["nether:rack"] or 0) > 100 then + base_col = "#1D0504" + elseif (cn["nether:rack_deep"] or 0) > 100 then + base_col = "#070916" + elseif (cn["nether:geode"] or 0) + (cn["nether:geodelite"] or 0)> 100 then + base_col = "#300530" + end + + if base_col then + player:set_sky({type = "plain", base_color = base_col, clouds = false}) + end + + player:set_moon({visible = false}) + player:set_stars({visible = false}) + player:set_sun({visible = false, sunrise_visible = false}) + + player_list[name] = "nether" + + if otherworlds.settings.gravity.enable then + set_gravity(player, 1.05) + end + + -- Underground (above Nether limit) + + elseif pos.y >= underground_low and pos.y <= underground_high + and current ~= "underground" then + + player:set_sky({type = "plain", clouds = false, base_color = "#101010"}) + player:set_moon({visible = false}) + player:set_stars({visible = false}) + player:set_sun({visible = false, sunrise_visible = false}) + + player_list[name] = "underground" + + if otherworlds.settings.gravity.enable then + set_gravity(player, 1.0) + end + + -- Earth + + elseif pos.y > underground_high and pos.y < space_low + and current ~= "earth" then + + player:set_sky({type = "regular", clouds = true}) + player:set_moon({visible = true}) + player:set_stars({visible = true}) + player:set_sun({visible = true, scale = 1.0, sunrise_visible = true}) + + player_list[name] = "earth" + + if otherworlds.settings.gravity.enable then + set_gravity(player, 1.0) + end + + -- Outerspace + + elseif pos.y >= space_low and pos.y <= space_high + and current ~= "space" then + + player:set_sky({type = "skybox", textures = spaceskybox, clouds = false, + base_color = "#000000"}) + player:set_moon({visible = false}) + player:set_stars({visible = false}) + player:set_sun({visible = true, scale = 1.0, sunrise_visible = false}) + + player_list[name] = "space" + + if otherworlds.settings.gravity.enable then + set_gravity(player, 0.4) + end + + -- Redsky + + elseif pos.y >= redsky_low and pos.y <= redsky_high + and current ~= "redsky" then + + player:set_sky({type = "skybox", textures = redskybox, clouds = false, + base_color = "#000000"}) + player:set_moon({visible = false}) + player:set_stars({visible = false}) + player:set_sun({visible = true, scale = 0.5, sunrise_visible = false}) + + player_list[name] = "redsky" + + if otherworlds.settings.gravity.enable then + set_gravity(player, 0.2) + end + + -- Everything else above (the blackness) + + elseif pos.y > redsky_high and current ~= "blackness" then + + player:set_sky({type = "skybox", textures = darkskybox, clouds = false, + base_color = "#000000"}) + player:set_moon({visible = false}) + player:set_stars({visible = true}) + player:set_sun({visible = true, scale = 0.1, sunrise_visible = false}) + + player_list[name] = "blackness" + + if otherworlds.settings.gravity.enable then + set_gravity(player, 0.1) + end + end + end +end) + +-- remove player from list when they leave + +core.register_on_leaveplayer(function(player) + player_list[player:get_player_name()] = nil +end) diff --git a/mods/other_worlds/space_asteroids.lua b/mods/other_worlds/space_asteroids.lua new file mode 100644 index 00000000..0318fb80 --- /dev/null +++ b/mods/other_worlds/space_asteroids.lua @@ -0,0 +1,26 @@ + +-- Approximate realm limits + +local YMIN = otherworlds.settings.space_asteroids.YMIN or 5000 +local YMAX = otherworlds.settings.space_asteroids.YMAX or 6000 + +-- Register on_generated function for this layer + +core.register_on_generated( + otherworlds.asteroids.create_on_generated(YMIN, YMAX, { + + c_air = core.get_content_id("air"), + c_obsidian = core.get_content_id("default:obsidian"), + c_stone = core.get_content_id("asteroid:stone"), + c_cobble = core.get_content_id("asteroid:cobble"), + c_gravel = core.get_content_id("asteroid:gravel"), + c_dust = core.get_content_id("asteroid:dust"), + c_ironore = core.get_content_id("default:stone_with_iron"), + c_copperore = core.get_content_id("default:stone_with_copper"), + c_goldore = core.get_content_id("default:stone_with_gold"), + c_diamondore = core.get_content_id("default:stone_with_diamond"), + c_meseore = core.get_content_id("default:stone_with_mese"), + c_waterice = core.get_content_id("default:ice"), + c_atmos = core.get_content_id("asteroid:atmos"), + c_snowblock = core.get_content_id("default:snowblock") +})) diff --git a/mods/other_worlds/textures/asteroid_atmos.png b/mods/other_worlds/textures/asteroid_atmos.png new file mode 100644 index 00000000..7f521b02 Binary files /dev/null and b/mods/other_worlds/textures/asteroid_atmos.png differ diff --git a/mods/other_worlds/textures/asteroid_cobble.png b/mods/other_worlds/textures/asteroid_cobble.png new file mode 100644 index 00000000..567f559a Binary files /dev/null and b/mods/other_worlds/textures/asteroid_cobble.png differ diff --git a/mods/other_worlds/textures/asteroid_dust.png b/mods/other_worlds/textures/asteroid_dust.png new file mode 100644 index 00000000..0ad0c712 Binary files /dev/null and b/mods/other_worlds/textures/asteroid_dust.png differ diff --git a/mods/other_worlds/textures/asteroid_gravel.png b/mods/other_worlds/textures/asteroid_gravel.png new file mode 100644 index 00000000..5fe791e8 Binary files /dev/null and b/mods/other_worlds/textures/asteroid_gravel.png differ diff --git a/mods/other_worlds/textures/asteroid_redcobble.png b/mods/other_worlds/textures/asteroid_redcobble.png new file mode 100644 index 00000000..85aa4616 Binary files /dev/null and b/mods/other_worlds/textures/asteroid_redcobble.png differ diff --git a/mods/other_worlds/textures/asteroid_reddust.png b/mods/other_worlds/textures/asteroid_reddust.png new file mode 100644 index 00000000..85aa44fd Binary files /dev/null and b/mods/other_worlds/textures/asteroid_reddust.png differ diff --git a/mods/other_worlds/textures/asteroid_redgravel.png b/mods/other_worlds/textures/asteroid_redgravel.png new file mode 100644 index 00000000..56b45b92 Binary files /dev/null and b/mods/other_worlds/textures/asteroid_redgravel.png differ diff --git a/mods/other_worlds/textures/asteroid_redstone.png b/mods/other_worlds/textures/asteroid_redstone.png new file mode 100644 index 00000000..4b024f6e Binary files /dev/null and b/mods/other_worlds/textures/asteroid_redstone.png differ diff --git a/mods/other_worlds/textures/crystals_ghost_crystal.png b/mods/other_worlds/textures/crystals_ghost_crystal.png new file mode 100644 index 00000000..40dc6c2a Binary files /dev/null and b/mods/other_worlds/textures/crystals_ghost_crystal.png differ diff --git a/mods/other_worlds/textures/crystals_red_crystal.png b/mods/other_worlds/textures/crystals_red_crystal.png new file mode 100644 index 00000000..afd60461 Binary files /dev/null and b/mods/other_worlds/textures/crystals_red_crystal.png differ diff --git a/mods/other_worlds/textures/crystals_rose_quartz.png b/mods/other_worlds/textures/crystals_rose_quartz.png new file mode 100644 index 00000000..951488f6 Binary files /dev/null and b/mods/other_worlds/textures/crystals_rose_quartz.png differ diff --git a/mods/other_worlds/textures/mars_grass_1.png b/mods/other_worlds/textures/mars_grass_1.png new file mode 100644 index 00000000..4613df6f Binary files /dev/null and b/mods/other_worlds/textures/mars_grass_1.png differ diff --git a/mods/other_worlds/textures/mars_grass_2.png b/mods/other_worlds/textures/mars_grass_2.png new file mode 100644 index 00000000..2906085b Binary files /dev/null and b/mods/other_worlds/textures/mars_grass_2.png differ diff --git a/mods/other_worlds/textures/mars_grass_3.png b/mods/other_worlds/textures/mars_grass_3.png new file mode 100644 index 00000000..83aa3c07 Binary files /dev/null and b/mods/other_worlds/textures/mars_grass_3.png differ diff --git a/mods/other_worlds/textures/mars_grass_4.png b/mods/other_worlds/textures/mars_grass_4.png new file mode 100644 index 00000000..1ed4e83e Binary files /dev/null and b/mods/other_worlds/textures/mars_grass_4.png differ diff --git a/mods/other_worlds/textures/mars_grass_5.png b/mods/other_worlds/textures/mars_grass_5.png new file mode 100644 index 00000000..16125e85 Binary files /dev/null and b/mods/other_worlds/textures/mars_grass_5.png differ diff --git a/mods/other_worlds/textures/mars_moss.png b/mods/other_worlds/textures/mars_moss.png new file mode 100644 index 00000000..8ca0fa77 Binary files /dev/null and b/mods/other_worlds/textures/mars_moss.png differ diff --git a/mods/other_worlds/textures/mars_redgrass.png b/mods/other_worlds/textures/mars_redgrass.png new file mode 100644 index 00000000..1d8106e8 Binary files /dev/null and b/mods/other_worlds/textures/mars_redgrass.png differ diff --git a/mods/other_worlds/textures/mars_redweed.png b/mods/other_worlds/textures/mars_redweed.png new file mode 100644 index 00000000..c144bad4 Binary files /dev/null and b/mods/other_worlds/textures/mars_redweed.png differ diff --git a/mods/other_worlds/textures/sky_neg_x.png b/mods/other_worlds/textures/sky_neg_x.png new file mode 100644 index 00000000..399cee46 Binary files /dev/null and b/mods/other_worlds/textures/sky_neg_x.png differ diff --git a/mods/other_worlds/textures/sky_neg_y.png b/mods/other_worlds/textures/sky_neg_y.png new file mode 100644 index 00000000..c02dcfb6 Binary files /dev/null and b/mods/other_worlds/textures/sky_neg_y.png differ diff --git a/mods/other_worlds/textures/sky_neg_z.png b/mods/other_worlds/textures/sky_neg_z.png new file mode 100644 index 00000000..82958909 Binary files /dev/null and b/mods/other_worlds/textures/sky_neg_z.png differ diff --git a/mods/other_worlds/textures/sky_pos_x.png b/mods/other_worlds/textures/sky_pos_x.png new file mode 100644 index 00000000..c18e71be Binary files /dev/null and b/mods/other_worlds/textures/sky_pos_x.png differ diff --git a/mods/other_worlds/textures/sky_pos_y.png b/mods/other_worlds/textures/sky_pos_y.png new file mode 100644 index 00000000..883e46d4 Binary files /dev/null and b/mods/other_worlds/textures/sky_pos_y.png differ diff --git a/mods/other_worlds/textures/sky_pos_z.png b/mods/other_worlds/textures/sky_pos_z.png new file mode 100644 index 00000000..f7f38d14 Binary files /dev/null and b/mods/other_worlds/textures/sky_pos_z.png differ