diff --git a/mods/cloudlands/license.txt b/mods/cloudlands/LICENSE.txt similarity index 100% rename from mods/cloudlands/license.txt rename to mods/cloudlands/LICENSE.txt diff --git a/mods/cloudlands/cloudlands.lua b/mods/cloudlands/cloudlands.lua index 0009ed9..8c2d66c 100644 --- a/mods/cloudlands/cloudlands.lua +++ b/mods/cloudlands/cloudlands.lua @@ -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 diff --git a/mods/cloudlands/i18n.py b/mods/cloudlands/i18n.py new file mode 100644 index 0000000..5e87937 --- /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/locale/template.txt b/mods/cloudlands/locale/template.txt index b9ed2d6..1b6df0b 100644 --- a/mods/cloudlands/locale/template.txt +++ b/mods/cloudlands/locale/template.txt @@ -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= diff --git a/mods/cloudlands/mod.conf b/mods/cloudlands/mod.conf index 4ea7c81..1ca75c9 100644 --- a/mods/cloudlands/mod.conf +++ b/mods/cloudlands/mod.conf @@ -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 diff --git a/mods/cloudlands/screenshot.png b/mods/cloudlands/screenshot.png deleted file mode 100644 index 8f90ae7..0000000 Binary files a/mods/cloudlands/screenshot.png and /dev/null differ diff --git a/mods/compost/init.lua b/mods/compost/init.lua index f9456cd..0eb1db3 100644 --- a/mods/compost/init.lua +++ b/mods/compost/init.lua @@ -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 + + + + + + + + diff --git a/mods/compost/mod.conf b/mods/compost/mod.conf index d83e024..2e03800 100644 --- a/mods/compost/mod.conf +++ b/mods/compost/mod.conf @@ -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 diff --git a/mods/epic/nodes.lua b/mods/epic/nodes.lua index 7034461..a6f032a 100644 --- a/mods/epic/nodes.lua +++ b/mods/epic/nodes.lua @@ -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(), }) diff --git a/mods/furniture/decor.lua b/mods/furniture/decor.lua index 16b2925..1d69ca7 100644 --- a/mods/furniture/decor.lua +++ b/mods/furniture/decor.lua @@ -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}, diff --git a/mods/nether/portal_api.lua b/mods/nether/portal_api.lua index 9b5df54..961ca1f 100644 --- a/mods/nether/portal_api.lua +++ b/mods/nether/portal_api.lua @@ -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 \ No newline at end of file +end diff --git a/mods/spawn/news.lua b/mods/spawn/news.lua index 8ffc8b1..7e6bb8e 100644 --- a/mods/spawn/news.lua +++ b/mods/spawn/news.lua @@ -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.', diff --git a/mods/stations/recipes_sewing.lua b/mods/stations/recipes_sewing.lua index 543a3a2..98ad2cf 100644 --- a/mods/stations/recipes_sewing.lua +++ b/mods/stations/recipes_sewing.lua @@ -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