Updated Cloudlands, and made compost bins work with hoppers.

master
NathanSalapat 2020-06-22 07:43:02 -05:00
parent 11a5200170
commit 51fbd54112
13 changed files with 982 additions and 154 deletions

View File

@ -6,31 +6,34 @@ local LOWLAND_BIOMES = false or -- If true then determine an island's bi
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.06 -- Chance of a viable island having a giant tree growing out of it
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 = false 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 = {"epic:stone", "mcl_core:stone", "default:stone"}
local NODENAMES_WATER = {"mapgen_water_source", "mcl_core:water_source", "default:water_source"}
local NODENAMES_ICE = {"mapgen_ice", "mcl_core:ice", "pedology:ice_white", "default:ice"}
local NODENAMES_GRAVEL = {"mapgen_gravel", "mcl_core:gravel", "default:gravel"}
local NODENAMES_GRASS = {"mapgen_dirt_with_grass", "mcl_core:dirt_with_grass", "default:dirt_with_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"} -- 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"} -- 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"} -- ethereal vines don't grow, so only select that if there's nothing else.
local NODENAMES_STONE = {"epic: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"}
local NODENAMES_TREELEAVES = {"mcl_core:leaves", "default:leaves", "mapgen_leaves"}
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
@ -48,7 +51,7 @@ local DEBUG_SKYTREES = false -- dev logging
-- notice problems requiring it.
local OVERDRAW = 0
local S = minetest.get_translator(minetest.get_current_modname())
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.
@ -171,7 +174,7 @@ local noise_surfaceMap
local noise_skyReef
local worldSeed
local nodeId_ignore = minetest.CONTENT_IGNORE
local nodeId_ignore = minetest.CONTENT_IGNORE
local nodeId_air
local nodeId_stone
local nodeId_grass
@ -182,6 +185,7 @@ 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
@ -241,27 +245,47 @@ if isMapgenV6 then
end
local interop = {}
-- returns the id of the first name in the list that resolves to a node id, or nodeId_ignore if not found
-- 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
if minetest.registered_nodes[nonAliasName] ~= nil then
result = minetest.get_content_id(nonAliasName)
end
--if DEBUG then minetest.log("info", contenderName .. " returned " .. result) 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 name in the list that resolves to a node id, or 'ignore' if not found
-- 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]
@ -316,6 +340,43 @@ interop.file_exists = function(filename)
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
==============================]]--
@ -405,6 +466,7 @@ if ENABLE_PORTALS and minetest.get_modpath("nether") ~= nil and minetest.global_
-- 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", {
@ -459,17 +521,18 @@ if ENABLE_PORTALS and minetest.get_modpath("nether") ~= nil and minetest.global_
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)
fastHash = (37 * fastHash) + ISLANDS_SEED
fastHash = (37 * fastHash) + 9354 -- to keep this probability distinct from reefs and atols
if (PORTAL_RARITY * 10000) < math_floor((math_abs(fastHash)) % 10000) then return false end
local portalPos = find_potential_portal_location_on_island(core)
@ -498,9 +561,9 @@ if ENABLE_PORTALS and minetest.get_modpath("nether") ~= nil and minetest.global_
)
end
end
nether.register_portal("cloudlands_portal", {
nether.register_portal("cloudlands_portal", {
shape = nether.PortalShape_Traditional,
frame_node_name = "cloudlands:ancient_portalstone",
wormhole_node_color = 2, -- 2 is blue
@ -521,10 +584,12 @@ if ENABLE_PORTALS and minetest.get_modpath("nether") ~= nil and minetest.global_
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)
cloudlands.init()
-- 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
-- I'm doing this based off height for speed, so it sometimes gets it wrong when the
-- Hallelujah mountains start reaching the ground.
local largestCoreType = cloudlands.coreTypes[1] -- the first island type is the biggest/thickest
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}))
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,
@ -551,13 +616,13 @@ if ENABLE_PORTALS and minetest.get_modpath("nether") ~= nil and minetest.global_
end,
find_surface_anchorPos = function(realm_anchorPos)
-- This function isn't needed since find_surface_target_y() will be used by default,
-- 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 surface portals.
-- a y_factor of 0 makes the search ignore the -100000 altitude of the portals (as
-- 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)
@ -612,6 +677,7 @@ if ENABLE_PORTALS and minetest.get_modpath("nether") ~= nil and minetest.global_
end
})
end
--[[==============================
@ -693,6 +759,11 @@ if not minetest.global_exists("SkyTrees") then -- If SkyTrees added into other m
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
@ -931,7 +1002,8 @@ if not minetest.global_exists("SkyTrees") then -- If SkyTrees added into other m
{
tiles = { heartwoodTexture },
description = S("Heart of the Tree"),
groups = {oddly_breakable_by_hand = 3},
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
@ -1167,7 +1239,7 @@ if not minetest.global_exists("SkyTrees") then -- If SkyTrees added into other m
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 == "ignore" then
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
@ -1701,7 +1773,7 @@ local function addDetail_skyReef(decoration_list, core, data, area, minp, maxp)
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 a atoll
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
@ -1915,48 +1987,60 @@ end
-- 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.
if minetest.get_modpath("default") then
-- the crack texture is probably available
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^[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"
}
)
end
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 eggTextureName = "default_jungleleaves.png" -- called this in default/Voxelgarden/MineClone2
if minetest.get_modpath("ethereal") ~= nil then eggTextureName = "ethereal_frost_leaves.png" end -- called "ethereal_frost_leaves.png" in ethereal
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 = {
-- [Ab]Use a leaf texture to avoid needing to include an egg texture and exposing that the mod contains secrets
eggTextureName.."^[colorize:#280040E0^[noalpha"
},
tiles = { eggTextureName },
description = S("Fossilized Egg"),
groups = {oddly_breakable_by_hand = 3, not_in_creative_inventory = 1},
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 = {
@ -1972,6 +2056,86 @@ if minetest.registered_nodes[nodeName_egg] == nil then
}
)
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)"
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"))
@ -2308,15 +2472,10 @@ local function addDetail_secrets(decoration_list, core, data, area, minp, maxp)
end
end
local stackName_writtenBook = "default:book_written"
if isMineCloneBookshelf then stackName_writtenBook = "mcl_books:written_book" end
local book_itemstack = ItemStack(stackName_writtenBook)
local book_data = {}
book_data.title = S("Weddell Outpost")
-- Instead of being a stand-alone line, the McNish line is tacked on the end of the
-- journey sentence because otherwise it gets truncated off by default:book_written
book_data.text = S([[The aerostat is lost.
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.
@ -2331,29 +2490,23 @@ 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)
---====---]], groundDesc),
S("Diary of Bert Shackleton") -- description
)
if isMineCloneBookshelf then book_data.text = book_data.title .. "\n\n" .. book_data.text end -- MineClone2 doesn't show the title
book_data.owner = S("Bert Shackleton")
book_data.author = book_data.owner
book_data.description = S("Diary of Bert Shackleton")
book_data.page = 1
book_data.page_max = 1
book_data.generation = 0
book_itemstack:get_meta():from_table({fields = book_data})
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
minetest.registered_nodes[nodeName_bookshelf].on_metadata_inventory_put(bookshelf_pos, "books", 1, book_itemstack, dummyPlayer)
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
minetest.registered_nodes[nodeName_bookshelf].on_metadata_inventory_put(bookshelf_pos, "books", 1, book_itemstack, dummyPlayer)
end
end
end
@ -2369,10 +2522,10 @@ look for us here. McNish is attempting to strengthen the gliders.
end
end
end
addIfFound({"mcl_tools:pick_iron", "default:pick_steel"}, 1)
addIfFound({"mcl_tools:pick_iron", "default:pick_steel", "main:ironpick"}, 1)
addIfFound({"binoculars:binoculars"}, 1)
addIfFound({"mcl_core:wood", "default:wood"}, 10)
addIfFound({"mcl_torches:torch", "default:torch"}, 3)
addIfFound(NODENAMES_WOOD, 10)
addIfFound({"mcl_torches:torch", "default:torch", "torch:torch"}, 3)
end
end
@ -2382,26 +2535,25 @@ look for us here. McNish is attempting to strengthen the gliders.
end
local function init_secrets()
nodeId_bed_top = interop.find_node_id({"beds:bed_top"})
nodeId_bed_bottom = interop.find_node_id({"beds:bed_bottom"})
nodeId_torch = interop.find_node_id({"mcl_torches:torch_wall", "default:torch_wall"})
nodeId_chest = interop.find_node_id({"chest", "mcl_chests:chest", "default:chest"})
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" }) -- "default:anvil" isn'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"}) -- "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"})
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
local nodeName_standinCobweb = MODNAME .. ":cobweb"
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))
else
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
@ -2592,7 +2744,7 @@ local function renderCores(cores, minp, maxp, blockseed)
local pondWallBuffer = core.type.pondWallBuffer
local pondBottom = nodeId_filler
local pondWater = nodeId_water
if radius > 18 and core.depth > 15 and nodeId_silt ~= nodeId_ignore then
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
@ -2648,13 +2800,12 @@ local function renderCores(cores, minp, maxp, blockseed)
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)
if addDetail_ancientPortal ~= nil then addDetail_ancientPortal(core) end
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 == "ignore" then minetest.set_node(decoration.pos, decoration.node) end
if nodeAtPos.name == "air" or nodeAtPos.name == nodeName_ignore then minetest.set_node(decoration.pos, decoration.node) end
end
local dustingInProgress = false
@ -2733,6 +2884,11 @@ local function renderCores(cores, minp, maxp, blockseed)
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

426
mods/cloudlands/i18n.py Normal file
View File

@ -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()

View File

@ -1,32 +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=
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 ---@=@=@=@=---@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=
Wisteria blossom=
ice=
rock=
# 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=

View File

@ -1,5 +1,5 @@
name = cloudlands
optional_depends = vines, schemlib, default, mcl_core, ethereal, biomeinfo, nether
optional_depends = nether, vines, biomeinfo, schemlib, default, mcl_core, xpanes, ethereal, main
description = """
Hallelujah Mountains for Minetest

Binary file not shown.

Before

Width:  |  Height:  |  Size: 370 KiB

View File

@ -1,3 +1,17 @@
-- modified by dcc to work with hoppers
--
-- NOTE: input hopper goes on SIDE not top... to allow punching of barrel - placing hopper to point correctly
-- may require screwdriver
--
-- output hopper goes below barrel
--
-- If testing - please note the abm's only fire every 30 seconds so it can take some time
--
compost = {}
compost.items = {}
compost.groups = {}
@ -55,6 +69,154 @@ compost.register_item("farming:wheat")
compost.register_group("plant")
compost.register_group("flower")
-- dcc
local wood_barrel_src_on_construct = function(pos)
local inv = minetest.get_meta(pos):get_inventory()
inv:set_size("src", 1)
end
local wood_barrel_dst_on_construct = function(pos)
local inv = minetest.get_meta(pos):get_inventory()
inv:set_size("dst", 1)
-- this is for wood barrel #3 which has 1 compost ready for taking
--
inv:set_stack('dst', 1, 'compost:compost')
local meta = minetest.get_meta(pos)
meta:set_string("formspec",
"size[8,5.3]" ..
default.gui_bg ..
default.gui_bg_img ..
default.gui_slots ..
"list[current_name;dst;3.5,0;1,1;]" ..
"list[current_player;main;0,1.15;8,1;]" ..
"list[current_player;main;0,2.38;8,3;8]" ..
"listring[current_name;main]" ..
"listring[current_player;main]" ..
default.get_hotbar_bg(0,1.15)
)
end
-- Only allow compostable items to be placed in input
-- allow_metadata_inventory_put is a pre-check prior to actual
-- putting...
--
local wood_barrel_allow_metadata_inventory_put = function(pos, listname, index, stack, player)
--print ("[compost] wood_barrel_allow_metadata_inventory_put ", listname)
if listname == "src" then
local stackname = stack:get_name()
--print ("[compost] wood_barrel_allow_metadata_inventory_put ", stackname )
if compost.can_compost(stackname) then
--print ("[compost] succeed wood_barrel_allow_metadata_inventory_put ", stackname)
return 1 -- take only 1 item
end
end
return 0
end
-- actual put
--
local wood_barrel_on_metadata_inventory_put = function(pos, listname, index, stack, player)
--print ("[compost] wood_barrel_on_metadata_inventory_put ")
if listname == "src" then
-- check again just in case
--
local stackname = stack:get_name()
if compost.can_compost(stackname) then
-- we received a compostable item - change to new node type
--
minetest.set_node(pos, {name = "compost:wood_barrel_1"})
return
end
end
end
-- Versions of callbacks for the inaccessible versions of the barrel
--
-- nope
local wood_barrelNope_allow_metadata_inventory_put = function(pos, listname, index, stack, player)
--print ("[compost] wood_barrelNope_allow_metadata_inventory_put ")
return 0
end
-- nope
local wood_barrelNope_on_metadata_inventory_put = function(pos, listname, index, stack, player)
--print ("[compost] wood_barrelNope_on_metadata_inventory_put ")
return
end
-- nope
local wood_barrelNope_allow_metadata_inventory_take = function(pos, listname, index, stack, player)
-- nope you can't take out here
return 0
end
-- nope
local wood_barrelNope_on_metadata_inventory_take = function(pos, listname, index, stack, player)
return
end
-- Versions of callbacks for when compost is ready....
-- nope
local wood_barrel_allow_metadata_inventory_take = function(pos, listname, index, stack, player)
-- YES you can take out here - we know it is 1 because we put it there with the constructor
return 1
end
-- nope
local wood_barrel_on_metadata_inventory_take = function(pos, listname, index, stack, player)
-- when emptied we revert to a plain wood barrel
local node = minetest.get_node (pos)
if node.name == "compost:wood_barrel_3" then
minetest.set_node(pos, {name = "compost:wood_barrel"})
return ItemStack("compost:compost")
end
return
end
minetest.register_node("compost:wood_barrel", {
description = "Wood Barrel",
tiles = {"default_wood.png"},
@ -71,6 +233,13 @@ minetest.register_node("compost:wood_barrel", {
is_ground_content = false,
groups = {choppy = 3},
sounds = default.node_sound_wood_defaults(),
on_construct = wood_barrel_src_on_construct, -- dcc
allow_metadata_inventory_put = wood_barrel_allow_metadata_inventory_put, -- dcc
on_metadata_inventory_put = wood_barrel_on_metadata_inventory_put, -- dcc
allow_metadata_inventory_take = wood_barrelNope_allow_metadata_inventory_take, -- dcc
on_metadata_inventory_take = wood_barrelNope_on_metadata_inventory_take, -- dcc
on_punch = function(pos, node, puncher, pointed_thing)
local wielded_item = puncher:get_wielded_item():get_name()
if compost.can_compost(wielded_item) then
@ -84,6 +253,11 @@ minetest.register_node("compost:wood_barrel", {
end
})
minetest.register_node("compost:wood_barrel_1", {
description = "Wood Barrel with compost",
tiles = {"default_wood.png^compost_compost_1.png", "default_wood.png"},
@ -99,6 +273,13 @@ minetest.register_node("compost:wood_barrel_1", {
},
paramtype = "light",
is_ground_content = false,
allow_metadata_inventory_put = wood_barrelNope_allow_metadata_inventory_put, -- dcc
on_metadata_inventory_put = wood_barrelNope_on_metadata_inventory_put, -- dcc
allow_metadata_inventory_take = wood_barrelNope_allow_metadata_inventory_take, -- dcc
on_metadata_inventory_take = wood_barrelNope_on_metadata_inventory_take, -- dcc
groups = {choppy = 3},
sounds = default.node_sound_wood_defaults(),
})
@ -118,6 +299,13 @@ minetest.register_node("compost:wood_barrel_2", {
},
paramtype = "light",
is_ground_content = false,
allow_metadata_inventory_put = wood_barrelNope_allow_metadata_inventory_put, -- dcc
on_metadata_inventory_put = wood_barrelNope_on_metadata_inventory_put, -- dcc
allow_metadata_inventory_take = wood_barrelNope_allow_metadata_inventory_take, -- dcc
on_metadata_inventory_take = wood_barrelNope_on_metadata_inventory_take, -- dcc
groups = {choppy = 3},
sounds = default.node_sound_wood_defaults(),
})
@ -138,6 +326,14 @@ minetest.register_node("compost:wood_barrel_3", {
paramtype = "light",
is_ground_content = false,
groups = {choppy = 3},
on_construct = wood_barrel_dst_on_construct, -- dcc
allow_metadata_inventory_put = wood_barrelNope_allow_metadata_inventory_put, -- dcc
on_metadata_inventory_put = wood_barrelNope_on_metadata_inventory_put, -- dcc
allow_metadata_inventory_take = wood_barrel_allow_metadata_inventory_take, -- dcc
on_metadata_inventory_take = wood_barrel_on_metadata_inventory_take, -- dcc
sounds = default.node_sound_wood_defaults(),
on_punch = function(pos, node, player, pointed_thing)
local p = {x = pos.x + math.random(0, 5)/5 - 0.5, y = pos.y+1, z = pos.z + math.random(0, 5)/5 - 0.5}
@ -195,7 +391,7 @@ minetest.register_craft({
})
if minetest.get_modpath ("tubelib") and tubelib then
print ("[compost] found tubelib")
--print ("[compost] found tubelib")
-- Thanks to @joe7575
tubelib.register_node ("compost:wood_barrel", {
@ -231,3 +427,23 @@ if minetest.get_modpath ("tubelib") and tubelib then
end,
})
end
-- dcc
if minetest.get_modpath ("hopper") and hopper then
--print ("[compost] found hopper")
hopper:add_container({
{"top", "compost:wood_barrel_3", "dst"}, -- take compost from above into hopper below
{"side", "compost:wood_barrel", "src"}, -- insert compostable items below to be composted from hopper at side
})
end

View File

@ -1,5 +1,5 @@
name = compost
depends = default
optional_depends = tubelib
optional_depends = tubelib, hopper
description = This mod allows you to compost grass, leaves, flowers.
author = cdqwertz

View File

@ -77,8 +77,6 @@ minetest.register_node('epic:stone_with_titanium', {
tiles = {'nether_rack.png^epic_titanium_ore.png'},
groups = {cracky = 1, stone = 1},
drop = 'epic:titanium_lump',
light_source = 14,
paramtype = 'light',
sounds = default.node_sound_stone_defaults(),
})

View File

@ -118,7 +118,7 @@ for i in ipairs(dye_table) do
})
minetest.register_node('furniture:curtain_short_right_'..name..'_1', {
description = 'Short '..desc..' Curtain Open',
description = 'Short Right '..desc..' Curtain Open',
drawtype = 'mesh',
mesh = 'furniture_curtain_short.obj',
tiles = {'furniture_curtain_short_right_1.png^[multiply:'..hex},
@ -141,7 +141,7 @@ for i in ipairs(dye_table) do
})
minetest.register_node('furniture:curtain_short_center_'..name..'_0', {
description = 'Short Middle'..desc..' Curtain Closed',
description = 'Short Middle '..desc..' Curtain Closed',
drawtype = 'mesh',
mesh = 'furniture_curtain_short.obj',
tiles = {'furniture_curtain_short_0.png^[multiply:'..hex},
@ -298,7 +298,7 @@ for i in ipairs(dye_table) do
})
minetest.register_node('furniture:curtain_tall_right_'..name..'_1', {
description = 'Tall '..desc..' Curtain Open',
description = 'Tall Right '..desc..' Curtain Open',
drawtype = 'mesh',
mesh = 'furniture_curtain_tall.obj',
tiles = {'furniture_curtain_tall_right_1.png^[multiply:'..hex},
@ -321,7 +321,7 @@ for i in ipairs(dye_table) do
})
minetest.register_node('furniture:curtain_tall_center_'..name..'_0', {
description = 'Tall Middle'..desc..' Curtain Closed',
description = 'Tall Middle '..desc..' Curtain Closed',
drawtype = 'mesh',
mesh = 'furniture_curtain_tall.obj',
tiles = {'furniture_curtain_tall_0.png^[multiply:'..hex},
@ -343,7 +343,7 @@ for i in ipairs(dye_table) do
})
minetest.register_node('furniture:curtain_tall_center_'..name..'_1', {
description = 'Tall Middle'..desc..' Curtain Open',
description = 'Tall Middle '..desc..' Curtain Open',
drawtype = 'mesh',
mesh = 'furniture_curtain_tall.obj',
tiles = {'furniture_curtain_tall_center_1.png^[multiply:'..hex},

View File

@ -1668,7 +1668,7 @@ local function create_book(item_name, inventory_description, inventory_image, ti
minetest.register_craftitem(item_name, {
description = inventory_description,
inventory_image = inventory_image,
groups = {book = 1},
groups = {book = 1, not_in_creative_inventory=1},
on_use = display_book,
_doc_items_hidden = true,
_doc_items_longdesc =
@ -2301,4 +2301,4 @@ function nether.find_nearest_working_portal(portal_name, anchorPos, distance_lim
end
end
return nil
end
end

View File

@ -1,6 +1,10 @@
local news = {
'6/22/20',
'Cloudland portals are working now.',
'',
'6/21/20',
'Small tweaks to hoppers. Updated Nether.',
'Added wide curtains. Hoppers work with compost bins thanks to Daniel1.',
'',
'6/20/20',
'Added hoppers. Small bugfixes.',

View File

@ -189,4 +189,31 @@ stations.dual_register_recipe('sewing', {
output = 'furniture:curtain_tall_'..colors[i]..'_1',
})
stations.dual_register_recipe('sewing', {
input = {
[('furniture:fabric_'..colors[i])] = 2,
[('furniture:thread_'..colors[i])] = 2,
['ropes:ropesegment'] = 1,
},
output = 'furniture:curtain_tall_left_'..colors[i]..'_1',
})
stations.dual_register_recipe('sewing', {
input = {
[('furniture:fabric_'..colors[i])] = 2,
[('furniture:thread_'..colors[i])] = 2,
['ropes:ropesegment'] = 1,
},
output = 'furniture:curtain_tall_right_'..colors[i]..'_1',
})
stations.dual_register_recipe('sewing', {
input = {
[('furniture:fabric_'..colors[i])] = 2,
[('furniture:thread_'..colors[i])] = 2,
['ropes:ropesegment'] = 1,
},
output = 'furniture:curtain_tall_center_'..colors[i]..'_1',
})
end