├── LICENSE.md ├── README.md ├── bridge.lua ├── crafts.lua ├── depends.txt ├── description.txt ├── doc.lua ├── extendingladder.lua ├── functions.lua ├── i18n.py ├── init.lua ├── locale ├── ropes.es.tr ├── ropes.ru.tr └── template.txt ├── loot.lua ├── mod.conf ├── ropeboxes.lua ├── ropeladder.lua ├── screenshot.png ├── settingtypes.txt ├── sounds ├── license.txt ├── ropes_creak.1.ogg ├── ropes_creak.2.ogg └── ropes_creak.3.ogg └── textures ├── ropes_1.png ├── ropes_2.png ├── ropes_3.png ├── ropes_4.png ├── ropes_5.png ├── ropes_item.png ├── ropes_ropebox_front_1.png ├── ropes_ropebox_front_2.png ├── ropes_ropebox_front_3.png ├── ropes_ropebox_front_4.png ├── ropes_ropebox_front_5.png ├── ropes_ropebox_side.png ├── ropes_ropeladder.png ├── ropes_ropeladder_bottom.png ├── ropes_ropeladder_overlay.png └── ropes_ropeladder_top.png /LICENSE.md: -------------------------------------------------------------------------------- 1 | ## MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ropes 2 | 3 | This mod adds "rope boxes", blocks that when placed in world will automatically lower a rope at 1 meter per second until it reaches a fixed maximum length. The basic rope box produces 50m of rope by default and there are up to eight additional rope box types that produce multiples of that rope length - 100m, 150m, and so forth up to 450m. The number of rope boxes and the length of a standard rope length can be configured via the settings menu. 4 | 5 | The rope stops lowering if it reaches an obstruction. Ropes can be cut using an axe or other choppy tool at any location and when they're cut the bottom half of the rope will disappear, dropping any climbers. The same happens to the entire rope if the rope box at the top of the rope is removed. Cutting the rope doesn't reduce the maximum length of rope the rope box will produce if it's removed and rebuilt again. Ropes are flammable. They respect protection settings - if the player that placed the rope box isn't permitted to build in an area then the rope descending from that box will treat it as an obstruction. 6 | 7 | Also included is a rope ladder that behaves similarly, though it only comes in one standard maximum length - 50m by default, again changeable in settings. 8 | 9 | This mod will also enhance default wood ladders and steel ladders to make them "extendable", capable of building upward independent of support to a setting-defined limit (defaulting to 5 nodes for wood and 15 nodes for steel ladders). This can be disabled if undesired. 10 | 11 | This mod retains optional backward compatibility with the crafting items from the vines mod (anything with group "vines" can be used to make rope boxes and rope ladders). Ropes can also be made from cotton, available via an optional dependency on the farming mod. 12 | 13 | In-game documentation is provided via an optional dependency on the doc mod. 14 | 15 | ## Interaction with other mods 16 | 17 | By default ropes and rope ladders only extend downward into "air" nodes. Other mods can modify this behaviour to add other nodes as valid targets for ropes to extend into using either of two mechanisms: either add `ropes_can_extend_into = 1` to the node definition's groups list or add a dependency on the ropes mod to your mod and then set `ropes.can_extend_into_nodes[target_node_name] = true`. There is also a configuration setting, `ropes_can_extend_into_airlike`, that will allow ropes to extend into any node with `drawtype = "airlike"` in its definition. Note that in cases where ropes extend into non-air nodes the rope will still be replaced with an "air" node when it's eventually destroyed. -------------------------------------------------------------------------------- /bridge.lua: -------------------------------------------------------------------------------- 1 | local S = ropes.S 2 | 3 | if ropes.bridges_enabled then 4 | 5 | local bridge_on_place = function(itemstack, placer, pointed_thing) 6 | -- Shall place item and return the leftover itemstack. 7 | -- The placer may be any ObjectRef or nil. 8 | -- default: minetest.item_place 9 | if placer == nil then 10 | return minetest.item_place(itemstack, placer, pointed_thing) 11 | end 12 | 13 | local above = pointed_thing.above 14 | local under = pointed_thing.under 15 | 16 | if above.x == under.x and above.z == under.z and above.y > under.y then 17 | -- we're aimed downward at a buildable node from above. 18 | -- determine the direction the placer lies relative to this node. 19 | local new_under = vector.new(under) 20 | local placer_pos = placer:get_pos() 21 | local diff_x = placer_pos.x - under.x 22 | local diff_z = placer_pos.z - under.z 23 | if math.abs(diff_x) > math.abs(diff_z) then 24 | -- placer is displaced along the X axis relative to the target 25 | if diff_x > 0 then 26 | new_under.x = under.x - 1 27 | else 28 | new_under.x = under.x + 1 29 | end 30 | else 31 | -- placer is displaced along the Z axis relative to the target 32 | if diff_z > 0 then 33 | new_under.z = under.z - 1 34 | else 35 | new_under.z = under.z + 1 36 | end 37 | end 38 | if minetest.registered_nodes[minetest.get_node(new_under).name].buildable_to then 39 | local new_pointed_thing = {type="node", under=new_under, above={x=new_under.x, y=new_under.y+1, z=new_under.z}} 40 | return minetest.item_place(itemstack, placer, new_pointed_thing) 41 | end 42 | end 43 | 44 | return minetest.item_place(itemstack, placer, pointed_thing) 45 | end 46 | 47 | minetest.register_node("ropes:wood_bridge", { 48 | description = S("Wooden Bridge"), 49 | _doc_items_longdesc = ropes.doc.wooden_bridge_longdesc, 50 | _doc_items_usagehelp = ropes.doc.wooden_bridge_usagehelp, 51 | tiles = { 52 | "default_wood.png", "default_wood.png", 53 | "default_wood.png^[transformR270", "default_wood.png^[transformR90", 54 | "default_wood.png^[transformR270", "default_wood.png^[transformR90", 55 | }, 56 | drawtype = "nodebox", 57 | paramtype = "light", 58 | paramtype2 = "facedir", 59 | is_ground_content = false, 60 | groups = {choppy = 2, flammable = 2, oddly_breakable_by_hand = 1, flow_through = 1, fence = 1, wall = 1}, 61 | sounds = default.node_sound_wood_defaults(), 62 | node_box = { 63 | type = "fixed", 64 | fixed = { 65 | {-0.5, 0.375, -0.5, 0.5, 0.5, 0.5}, -- Platform 66 | {-0.375, -0.5, -0.5, 0.375, -0.375, -0.4375}, -- x beam4 67 | {-0.375, -0.5, 0.4375, 0.375, -0.375, 0.5}, -- x beam3 68 | {0.375, -0.5, -0.4375, 0.5, -0.375, 0.4375}, -- z beam2 69 | {-0.5, -0.5, -0.4375, -0.375, -0.375, 0.4375}, -- z beam1 70 | {0.375, -0.5, -0.5, 0.5, 0.375, -0.4375}, -- upright4 71 | {0.375, -0.5, 0.4375, 0.5, 0.375, 0.5}, -- upright3 72 | {-0.5, -0.5, -0.5, -0.375, 0.375, -0.4375}, -- upright2 73 | {-0.5, -0.5, 0.4375, -0.375, 0.375, 0.5}, -- upright1 74 | } 75 | }, 76 | on_place = bridge_on_place, 77 | }) 78 | 79 | minetest.register_craft({ 80 | output = "ropes:wood_bridge 5", 81 | recipe = { 82 | {"group:stick", "stairs:slab_wood", "group:stick"}, 83 | {"group:stick", "", "group:stick"}, 84 | {"group:stick", "group:stick", "group:stick"}, 85 | } 86 | }) 87 | 88 | end 89 | -------------------------------------------------------------------------------- /crafts.lua: -------------------------------------------------------------------------------- 1 | local S = ropes.S 2 | 3 | if minetest.get_modpath("farming") then 4 | -- this doesn't work reliably due to side effects of https://github.com/minetest/minetest/issues/5518 5 | -- local old_def = minetest.registered_craftitems["farming:cotton"] 6 | -- if old_def then 7 | -- old_def.groups["thread"] = 1 8 | -- minetest.override_item("farming:cotton", { 9 | -- groups = old_def.groups 10 | -- }) 11 | -- end 12 | minetest.register_craft({ 13 | output = 'ropes:ropesegment', 14 | recipe = { 15 | {'farming:cotton','farming:cotton'}, 16 | {'farming:cotton','farming:cotton'}, 17 | {'farming:cotton','farming:cotton'}, 18 | } 19 | }) 20 | 21 | if farming.mod == "redo" or farming.mod == "undo" then 22 | minetest.register_craft({ 23 | output = 'ropes:ropesegment', 24 | recipe = { 25 | {'farming:hemp_rope'}, 26 | {'farming:hemp_rope'}, 27 | } 28 | }) 29 | end 30 | end 31 | 32 | if minetest.get_modpath("hemp") then 33 | minetest.register_craft({ 34 | output = 'ropes:ropesegment', 35 | recipe = { 36 | {'hemp:hemp_rope'}, 37 | {'hemp:hemp_rope'}, 38 | } 39 | }) 40 | end 41 | 42 | if minetest.get_modpath("cottages") then 43 | minetest.register_craft({ 44 | output = 'ropes:ropesegment', 45 | recipe = { 46 | {'cottages:rope'}, 47 | {'cottages:rope'}, 48 | } 49 | }) 50 | end 51 | 52 | if minetest.get_modpath("moreblocks") then 53 | minetest.register_craft({ 54 | output = 'ropes:ropesegment', 55 | recipe = { 56 | {'moreblocks:rope','moreblocks:rope'}, 57 | {'moreblocks:rope','moreblocks:rope'}, 58 | {'moreblocks:rope','moreblocks:rope'}, 59 | } 60 | }) 61 | end 62 | 63 | minetest.register_craft({ 64 | output = 'ropes:ropesegment', 65 | recipe = { 66 | {'group:thread','group:thread'}, 67 | {'group:thread','group:thread'}, 68 | {'group:thread','group:thread'}, 69 | } 70 | }) 71 | 72 | minetest.register_craftitem("ropes:ropesegment", { 73 | description = S("Rope Segment"), 74 | _doc_items_longdesc = ropes.doc.ropesegment_longdesc, 75 | _doc_items_usagehelp = ropes.doc.ropesegment_usage, 76 | groups = {vines = 1}, 77 | inventory_image = "ropes_item.png", 78 | }) 79 | 80 | local cotton_burn_time = 1 81 | ropes.wood_burn_time = minetest.get_craft_result({method="fuel", width=1, items={ItemStack("default:wood")}}).time 82 | ropes.rope_burn_time = cotton_burn_time * 6 83 | local stick_burn_time = minetest.get_craft_result({method="fuel", width=1, items={ItemStack("default:stick")}}).time 84 | ropes.ladder_burn_time = ropes.rope_burn_time * 2 + stick_burn_time * 3 85 | 86 | minetest.register_craft({ 87 | type = "fuel", 88 | recipe = "ropes:ropesegment", 89 | burntime = ropes.rope_burn_time, 90 | }) -------------------------------------------------------------------------------- /depends.txt: -------------------------------------------------------------------------------- 1 | default 2 | farming? 3 | vines? 4 | doc? 5 | loot? 6 | hemp? 7 | cottages? 8 | -------------------------------------------------------------------------------- /description.txt: -------------------------------------------------------------------------------- 1 | Adds rope boxes of various lengths and also rope ladders. -------------------------------------------------------------------------------- /doc.lua: -------------------------------------------------------------------------------- 1 | ropes.doc = {} 2 | 3 | if not minetest.get_modpath("doc") then 4 | return 5 | end 6 | 7 | local S = ropes.S 8 | 9 | ropes.doc.ropesegment_longdesc = S("Rope segments are bundles of fibre twisted into robust cables.") 10 | ropes.doc.ropesegment_usage = S("This craft item is useful for creating rope ladders, or for spooling on wooden spindles to hang and climb upon.") 11 | 12 | ropes.doc.ropeladder_longdesc = S("A hanging rope ladder that automatically extends downward.") 13 | ropes.doc.ropeladder_usage = S("After a rope ladder is placed on a vertical wall it will begin extending downward until it reaches its maximum length (@1 meters). If the rope ladder is removed all of the ladder below the point of removal will disappear. A rope ladder can be severed partway down using an axe or similar tool, and the ladder below the point where it is cut will collapse. No rope is actually lost in the process, though, and if the uppermost section of the ladder is removed and replaced the ladder will re-extend to the same maximum length as before.", ropes.ropeLadderLength) 14 | 15 | local rope_length_doc = S("Rope boxes have a certain amount of rope contained within them specified in the name of the node, and have a limit to how much rope they can support that depends on the material they're made of. The different lengths can be crafted by combining and splitting up rope boxes in the crafting grid. For example, you can craft a @1m rope box by putting a @2m rope box and a rope segment in the crafting grid, or a @3m rope box and two rope segments in the crafting grid. Two rope segments can be recovered by putting the @4m rope box in the crafting grid by itself.", ropes.ropeLength*3, ropes.ropeLength*2, ropes.ropeLength, ropes.ropeLength*3) .. "\n" 16 | 17 | if ropes.woodRopeBoxMaxMultiple == 1 then 18 | rope_length_doc = rope_length_doc .. "\n" .. S("Wood") .. " " .. S("rope boxes can hold @1m of rope.", ropes.ropeLength) 19 | elseif ropes.woodRopeBoxMaxMultiple > 1 then 20 | rope_length_doc = rope_length_doc .. "\n" .. S("Wood") .. " " .. S("rope boxes can hold rope lengths from @1m to @2m.", ropes.ropeLength, ropes.ropeLength*ropes.woodRopeBoxMaxMultiple) 21 | end 22 | 23 | if ropes.copperRopeBoxMaxMultiple == 1 then 24 | rope_length_doc = rope_length_doc .. "\n" .. S("Copper") .. " " .. S("rope boxes can hold @1m of rope.", ropes.ropeLength) 25 | elseif ropes.copperRopeBoxMaxMultiple > 1 then 26 | rope_length_doc = rope_length_doc .. "\n" .. S("Copper") .. " " .. S("rope boxes can hold rope lengths from @1m to @2m.", ropes.ropeLength, ropes.ropeLength*ropes.copperRopeBoxMaxMultiple) 27 | end 28 | 29 | if ropes.steelRopeBoxMaxMultiple == 1 then 30 | rope_length_doc = rope_length_doc .. "\n" .. S("Steel") .. " " .. S("rope boxes can hold @1m of rope.", ropes.ropeLength) 31 | elseif ropes.steelRopeBoxMaxMultiple > 1 then 32 | rope_length_doc = rope_length_doc .. "\n" .. S("Steel") .. " " .. S("rope boxes can hold rope lengths from @1m to @2m.", ropes.ropeLength, ropes.ropeLength*ropes.steelRopeBoxMaxMultiple) 33 | end 34 | 35 | ropes.doc.ropebox_longdesc = S("Ropes are hung by placing rope boxes, which automatically lower a rope of fixed length below them. They can be climbed and cut.") 36 | ropes.doc.ropebox_usage = rope_length_doc .. "\n\n" .. 37 | S("When a rope box is placed the rope will immediately begin lowering from it at one meter per second. The rope will only descend when its end is in the vicinity of an active player, suspending its journey when no players are nearby, so a long descent may require a player to climb down the rope as it goes. If you are near the bottom end of a rope that's extending you'll be automatically carried down with it. The rope will stop when it encounters and obstruction, but will resume lowering if the obstruction is removed.") .. "\n\n" .. 38 | S("A rope can be severed midway using an axe or other similar tool. The section of rope below the cut will collapse and disappear, potentially causing players who were hanging on to it to fall. The remaining rope will not resume descent on its own, but the rope box at the top of the rope \"remembers\" how long the rope was and if it is deconstructed and replaced it will still have the same maximum length of rope as before - no rope is permanently lost when a rope is severed like this.") 39 | 40 | if ropes.extending_ladder_enabled then 41 | ropes.doc.ladder_longdesc = S("A ladder for climbing. It can reach greater heights when placed against a supporting block.") 42 | ropes.doc.ladder_usagehelp = S("Right-clicking on a ladder with a stack of identical ladder items will automatically add new ladder segments to the top, provided it hasn't extended too far up beyond the last block behind it providing support.") 43 | end 44 | 45 | ropes.doc.wooden_bridge_longdesc = S("A wooden platform with support struts useful for bridging gaps.") 46 | ropes.doc.wooden_bridge_usagehelp = S("This behaves like most structural blocks except in one circumstance: when placed on top of a block with buildable space on the side facing away from you, this block will not be built on top but instead will extend out from that far side of the target block. This allows a platform to be easily built that juts out away from the location you're standing on.") 47 | 48 | doc.add_entry_alias("nodes", "ropes:ropeladder_top", "nodes", "ropes:ropeladder") 49 | doc.add_entry_alias("nodes", "ropes:ropeladder_top", "nodes", "ropes:ropeladder_bottom") 50 | doc.add_entry_alias("nodes", "ropes:ropeladder_top", "nodes", "ropes:ropeladder_falling") 51 | 52 | doc.add_entry_alias("nodes", "ropes:rope", "nodes", "ropes:rope_bottom") 53 | doc.add_entry_alias("nodes", "ropes:rope", "nodes", "ropes:rope_top") 54 | -------------------------------------------------------------------------------- /extendingladder.lua: -------------------------------------------------------------------------------- 1 | local S = ropes.S 2 | 3 | if ropes.extending_ladder_enabled then 4 | 5 | local wood_recipe = { 6 | {"group:stick", "", "group:stick"}, 7 | {"group:stick", "", "group:stick"}, 8 | {"group:stick", "group:stick", "group:stick"}, 9 | } 10 | local wood_name = S("Wooden Extendable Ladder") 11 | 12 | local steel_recipe = { 13 | {"default:steel_ingot", "", "default:steel_ingot"}, 14 | {"default:steel_ingot", "", "default:steel_ingot"}, 15 | {"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"}, 16 | } 17 | local steel_name = S("Steel Extendable Ladder") 18 | 19 | -- Overlay texture: by 1F616EMO, CC0 20 | local texture_overlay = "^ropes_ropeladder_overlay.png" 21 | 22 | if ropes.replace_default_ladders then 23 | 24 | minetest.unregister_item("default:ladder_wood") 25 | minetest.unregister_item("default:ladder_steel") 26 | minetest.clear_craft({output = "default:ladder_wood"}) 27 | minetest.clear_craft({output = "default:ladder_steel"}) 28 | 29 | local wallmounted_to_facedir = 30 | {[0] = 15, -- ceiling 31 | [1] = 13, -- floor 32 | [2] = 1, -- +X 33 | [3] = 3, -- -X 34 | [4] = 0, -- +Z 35 | [5] = 2, -- -Z 36 | } 37 | 38 | minetest.register_lbm({ 39 | label = "Switch from wallmounted default ladders to rope mod extending ladders", 40 | name = "ropes:wallmounted_ladder_to_facedir_ladder", 41 | nodenames = {"default:ladder_wood", "default:ladder_steel"}, 42 | run_at_every_load = false, 43 | action = function(pos, node) 44 | local new_node = {param2 = wallmounted_to_facedir[node.param2]} 45 | if (node.name == "default:ladder_wood") then 46 | new_node.name = "ropes:ladder_wood" 47 | else 48 | new_node.name = "ropes:ladder_steel" 49 | end 50 | minetest.set_node(pos, new_node) 51 | end, 52 | }) 53 | 54 | wood_recipe = { 55 | {"group:stick", "", "group:stick"}, 56 | {"group:stick", "group:stick", "group:stick"}, 57 | {"group:stick", "", "group:stick"}, 58 | } 59 | wood_name = S("Wooden Ladder") 60 | 61 | steel_recipe = { 62 | {'default:steel_ingot', '', 'default:steel_ingot'}, 63 | {'default:steel_ingot', 'default:steel_ingot', 'default:steel_ingot'}, 64 | {'default:steel_ingot', '', 'default:steel_ingot'}, 65 | } 66 | steel_name = S("Steel Ladder") 67 | 68 | texture_overlay = "" 69 | 70 | else 71 | -- Swap between normal and extendable ladders 72 | minetest.register_craft({ 73 | type = "shapeless", 74 | output = "ropes:ladder_wood", 75 | recipe = {"default:ladder_wood"}, 76 | }) 77 | 78 | minetest.register_craft({ 79 | type = "shapeless", 80 | output = "ropes:ladder_steel", 81 | recipe = {"default:ladder_steel"}, 82 | }) 83 | end 84 | 85 | minetest.register_craft({ 86 | output = "ropes:ladder_wood 5", 87 | recipe = wood_recipe, 88 | }) 89 | 90 | minetest.register_craft({ 91 | output = 'ropes:ladder_steel 15', 92 | recipe = steel_recipe, 93 | }) 94 | 95 | local ladder_extender = function(pos, node, clicker, itemstack, pointed_thing, ladder_node, standing_limit) 96 | -- on_rightclick can be called by other mods, make sure we have all the parameters we need 97 | if pointed_thing == nil or itemstack == nil then 98 | return itemstack 99 | end 100 | 101 | local clicked_stack = ItemStack(itemstack) 102 | 103 | -- true if we're pointing up at the ladder from below and there's a buildable space below it 104 | -- this check allows us to extend ladders downward 105 | local pointing_directly_below = 106 | pointed_thing.above.x == pos.x and 107 | pointed_thing.above.z == pos.z and 108 | pointed_thing.above.y == pos.y - 1 and 109 | minetest.registered_nodes[minetest.get_node(pointed_thing.above).name].buildable_to 110 | 111 | if clicked_stack:get_name() == ladder_node and not pointing_directly_below then 112 | local param2 = minetest.get_node(pos).param2 113 | local dir = minetest.facedir_to_dir(param2) 114 | local scan_limit = pos.y + 6 -- Only add ladder segments up to five nodes above the one clicked on 115 | pos.y = pos.y + 1 116 | while pos.y < scan_limit and minetest.get_node(pos).name == ladder_node do 117 | param2 = minetest.get_node(pos).param2 118 | pos.y = pos.y + 1 119 | end 120 | if pos.y < scan_limit and minetest.registered_nodes[minetest.get_node(pos).name].buildable_to then 121 | 122 | -- scan downward behind the ladder to find support 123 | local behind_pos = vector.add(pos, minetest.facedir_to_dir(param2)) 124 | local target_height = pos.y - standing_limit - 1 125 | while behind_pos.y > target_height and minetest.registered_nodes[minetest.get_node(behind_pos).name].buildable_to do 126 | behind_pos.y = behind_pos.y - 1 127 | end 128 | 129 | -- If there's enough support, build a new ladder segment 130 | if behind_pos.y > target_height then 131 | if minetest.is_protected(pos, clicker:get_player_name()) then 132 | minetest.record_protection_violation(pos, clicker:get_player_name()) 133 | else 134 | minetest.set_node(pos, {name=ladder_node, param2=param2}) 135 | if not minetest.settings:get_bool("creative_mode") then 136 | clicked_stack:take_item(1) 137 | end 138 | end 139 | end 140 | end 141 | elseif clicked_stack:get_definition().type == "node" then 142 | return minetest.item_place_node(itemstack, clicker, pointed_thing) 143 | end 144 | return clicked_stack 145 | end 146 | 147 | minetest.register_node("ropes:ladder_wood", { 148 | description = wood_name, 149 | _doc_items_longdesc = ropes.doc.ladder_longdesc, 150 | _doc_items_usagehelp = ropes.doc.ladder_usagehelp, 151 | tiles = {"default_wood.png","default_wood.png","default_wood.png^[transformR270","default_wood.png^[transformR270","default_ladder_wood.png"}, 152 | use_texture_alpha = "clip", 153 | inventory_image = "default_ladder_wood.png" .. texture_overlay, 154 | wield_image = "default_ladder_wood.png", 155 | paramtype = "light", 156 | paramtype2 = "facedir", 157 | sunlight_propagates = true, 158 | walkable = false, 159 | climbable = true, 160 | is_ground_content = false, 161 | drawtype = "nodebox", 162 | paramtype = "light", 163 | node_box = { 164 | type = "fixed", 165 | fixed = { 166 | {-0.375, -0.5, 0.375, -0.25, 0.5, 0.5}, -- Upright1 167 | {0.25, -0.5, 0.375, 0.375, 0.5, 0.5}, -- Upright2 168 | {-0.4375, 0.3125, 0.4375, 0.4375, 0.4375, 0.5}, -- Rung_4 169 | {-0.4375, -0.1875, 0.4375, 0.4375, -0.0625, 0.5}, -- Rung_2 170 | {-0.4375, -0.4375, 0.4375, 0.4375, -0.3125, 0.5}, -- Rung_1 171 | {-0.4375, 0.0625, 0.4375, 0.4375, 0.1875, 0.5}, -- Rung_3 172 | } 173 | }, 174 | groups = {choppy = 2, oddly_breakable_by_hand = 3, flammable = 2, flow_through = 1}, 175 | sounds = default.node_sound_wood_defaults(), 176 | on_rightclick = function(pos, node, clicker, itemstack, pointed_thing) 177 | return ladder_extender(pos, node, clicker, itemstack, pointed_thing, "ropes:ladder_wood", ropes.extending_wood_ladder_limit) 178 | end, 179 | }) 180 | 181 | minetest.register_node("ropes:ladder_steel", { 182 | description = steel_name, 183 | _doc_items_longdesc = ropes.doc.ladder_longdesc, 184 | _doc_items_usagehelp = ropes.doc.ladder_usagehelp, 185 | tiles = {"default_steel_block.png","default_steel_block.png","default_steel_block.png","default_steel_block.png","default_ladder_steel.png"}, 186 | use_texture_alpha = "clip", 187 | inventory_image = "default_ladder_steel.png" .. texture_overlay, 188 | wield_image = "default_ladder_steel.png", 189 | paramtype = "light", 190 | paramtype2 = "facedir", 191 | sunlight_propagates = true, 192 | walkable = false, 193 | climbable = true, 194 | is_ground_content = false, 195 | drawtype = "nodebox", 196 | node_box = { 197 | type = "fixed", 198 | fixed = { 199 | {-0.4375, -0.5, 0.3125, -0.25, 0.5, 0.5}, -- Upright1 200 | {0.25, -0.5, 0.3125, 0.4375, 0.5, 0.5}, -- Upright2 201 | {-0.25, 0.3125, 0.375, 0.25, 0.4375, 0.5}, -- Rung_4 202 | {-0.25, -0.1875, 0.375, 0.25, -0.0625, 0.5}, -- Rung_2 203 | {-0.25, -0.4375, 0.375, 0.25, -0.3125, 0.5}, -- Rung_1 204 | {-0.25, 0.0625, 0.375, 0.25, 0.1875, 0.5}, -- Rung_3 205 | } 206 | }, 207 | groups = {cracky = 2, flow_through = 1}, 208 | sounds = default.node_sound_metal_defaults(), 209 | on_rightclick = function(pos, node, clicker, itemstack, pointed_thing) 210 | return ladder_extender(pos, node, clicker, itemstack, pointed_thing, "ropes:ladder_steel", ropes.extending_steel_ladder_limit) 211 | end, 212 | }) 213 | 214 | else 215 | 216 | -- Table of possible wallmounted values 217 | local facedir_to_wallmounted = { 218 | 4, -- +Z 219 | 2, -- +X 220 | 5, -- -Z 221 | 3, -- -X 222 | 1, -- -Y 223 | 0, -- +Y 224 | } 225 | -- Mapping from facedir value to index in facedir_to_dir. 226 | local facedir_to_wallmounted_map = { 227 | [0]=1, 2, 3, 4, 228 | 5, 2, 6, 4, 229 | 6, 2, 5, 4, 230 | 1, 5, 3, 6, 231 | 1, 6, 3, 5, 232 | 1, 4, 3, 2, 233 | } 234 | 235 | minetest.register_lbm({ 236 | label = "Switch from ropes ladders to wallmounted default ladders", 237 | name = "ropes:facedir_ladder_to_wallmounted_ladder", 238 | nodenames = {"ropes:ladder_wood", "ropes:ladder_steel"}, 239 | run_at_every_load = false, 240 | action = function(pos, node) 241 | local new_node = {param2 = facedir_to_wallmounted[facedir_to_wallmounted_map[node.param2 % 32]]} 242 | if (node.name == "ropes:ladder_wood") then 243 | new_node.name = "default:ladder_wood" 244 | else 245 | new_node.name = "default:ladder_steel" 246 | end 247 | minetest.set_node(pos, new_node) 248 | end, 249 | }) 250 | 251 | end 252 | -------------------------------------------------------------------------------- /functions.lua: -------------------------------------------------------------------------------- 1 | ropes.can_place_rope_in_node = function(target_node_name) 2 | if ropes.can_extend_into_nodes[target_node_name] == true then 3 | return true 4 | end 5 | local target_def = minetest.registered_nodes[target_node_name] 6 | if target_def then 7 | if target_def.drawtype == "airlike" and ropes.can_extend_into_airlike then 8 | return true 9 | end 10 | if target_def.groups and target_def.groups.ropes_can_extend_into then 11 | return true 12 | end 13 | end 14 | return false 15 | end 16 | 17 | ropes.make_rope_on_timer = function(rope_node_name) 18 | return function(pos, elapsed) 19 | local currentend = minetest.get_node(pos) 20 | local currentmeta = minetest.get_meta(pos) 21 | local currentlength = currentmeta:get_int("length_remaining") 22 | local placer_name = currentmeta:get_string("placer") 23 | local newpos = {x=pos.x, y=pos.y-1, z=pos.z} 24 | local newnode = minetest.get_node(newpos) 25 | local oldnode = minetest.get_node(pos) 26 | if currentlength > 1 and (not minetest.is_protected(newpos, placer_name) 27 | or minetest.check_player_privs(placer_name, "protection_bypass")) then 28 | if ropes.can_place_rope_in_node(newnode.name) then 29 | minetest.add_node(newpos, {name=currentend.name, param2=oldnode.param2}) 30 | local newmeta = minetest.get_meta(newpos) 31 | newmeta:set_int("length_remaining", currentlength-1) 32 | newmeta:set_string("placer", placer_name) 33 | minetest.set_node(pos, {name=rope_node_name, param2=oldnode.param2}) 34 | ropes.move_players_down(pos, 1) 35 | else 36 | local timer = minetest.get_node_timer( pos ) 37 | timer:start( 1 ) 38 | end 39 | end 40 | end 41 | end 42 | 43 | local data = {} 44 | local c_air = minetest.get_content_id("air") 45 | 46 | ropes.destroy_rope = function(pos, nodes) 47 | local top = pos.y 48 | local bottom = pos.y-15 49 | local voxel_manip = minetest.get_voxel_manip() 50 | 51 | local finished = false 52 | local ids_to_destroy = {} 53 | for _, node in pairs(nodes) do 54 | if minetest.registered_nodes[node] then 55 | ids_to_destroy[minetest.get_content_id(node)] = true 56 | end 57 | end 58 | 59 | while not finished do 60 | local emin, emax = voxel_manip:read_from_map({x=pos.x, y=bottom, z=pos.z}, {x=pos.x, y=top, z=pos.z}) 61 | voxel_manip:get_data(data) 62 | local voxel_area = VoxelArea:new{MinEdge=emin, MaxEdge=emax} 63 | bottom = emin.y 64 | for y = top, bottom, -1 do 65 | local index = voxel_area:index(pos.x, y, pos.z) 66 | if ids_to_destroy[data[index]] then 67 | data[index] = c_air 68 | else 69 | finished = true 70 | break 71 | end 72 | end 73 | voxel_manip:set_data(data) 74 | voxel_manip:write_to_map() 75 | voxel_manip:update_map() 76 | top = bottom - 1 77 | bottom = bottom - 15 78 | end 79 | end 80 | 81 | 82 | ropes.hanging_after_destruct = function(pos, top_node, middle_node, bottom_node) 83 | local node = minetest.get_node(pos) 84 | if node.name == top_node or node.name == middle_node or node.name == bottom_node then 85 | return -- this was done by another ladder or rope node changing this one, don't react 86 | end 87 | 88 | pos.y = pos.y + 1 -- one up 89 | local node_above = minetest.get_node(pos) 90 | if node_above.name == middle_node then 91 | minetest.swap_node(pos, {name=bottom_node, param2=node_above.param2}) 92 | end 93 | 94 | pos.y = pos.y - 2 -- one down 95 | local node_below = minetest.get_node(pos) 96 | if node_below.name == middle_node then 97 | ropes.destroy_rope(pos, {middle_node, bottom_node}) 98 | elseif node_below.name == bottom_node then 99 | minetest.swap_node(pos, {name="air"}) 100 | end 101 | end 102 | 103 | ropes.move_players_down = function(pos, radius) 104 | local all_objects = minetest.get_objects_inside_radius({x=pos.x, y=pos.y+radius, z=pos.z}, radius) 105 | local players = {} 106 | local _,obj 107 | for _,obj in pairs(all_objects) do 108 | if obj:is_player() then 109 | local obj_pos = obj:get_pos() 110 | if math.abs(obj_pos.x-pos.x) < 0.5 and math.abs(obj_pos.z-pos.z) < 0.5 then 111 | obj:set_pos({x=obj_pos.x, y=obj_pos.y-1, z=obj_pos.z}, true) 112 | end 113 | end 114 | end 115 | end 116 | -------------------------------------------------------------------------------- /i18n.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Script to generate the template file and update the translation files. 5 | # Copy the script into the mod or modpack root folder and run it there. 6 | # 7 | # Copyright (C) 2019 Joachim Stolberg, 2020 FaceDeer, 2020 Louis Royer 8 | # LGPLv2.1+ 9 | 10 | from __future__ import print_function 11 | import os, fnmatch, re, shutil, errno 12 | from sys import argv as _argv 13 | 14 | # Running params 15 | params = {"recursive": False, 16 | "help": False, 17 | "mods": False, 18 | "verbose": False, 19 | "folders": [] 20 | } 21 | # Available CLI options 22 | options = {"recursive": ['--recursive', '-r'], 23 | "help": ['--help', '-h'], 24 | "mods": ['--installed-mods'], 25 | "verbose": ['--verbose', '-v'] 26 | } 27 | 28 | # Strings longer than this will have extra space added between 29 | # them in the translation files to make it easier to distinguish their 30 | # beginnings and endings at a glance 31 | doublespace_threshold = 60 32 | 33 | def set_params_folders(tab: list): 34 | '''Initialize params["folders"] from CLI arguments.''' 35 | # Discarding argument 0 (tool name) 36 | for param in tab[1:]: 37 | stop_param = False 38 | for option in options: 39 | if param in options[option]: 40 | stop_param = True 41 | break 42 | if not stop_param: 43 | params["folders"].append(os.path.abspath(param)) 44 | 45 | def set_params(tab: list): 46 | '''Initialize params from CLI arguments.''' 47 | for option in options: 48 | for option_name in options[option]: 49 | if option_name in tab: 50 | params[option] = True 51 | break 52 | 53 | def print_help(name): 54 | '''Prints some help message.''' 55 | print(f'''SYNOPSIS 56 | {name} [OPTIONS] [PATHS...] 57 | DESCRIPTION 58 | {', '.join(options["help"])} 59 | prints this help message 60 | {', '.join(options["recursive"])} 61 | run on all subfolders of paths given 62 | {', '.join(options["mods"])} 63 | run on locally installed modules 64 | {', '.join(options["verbose"])} 65 | add output information 66 | ''') 67 | 68 | 69 | def main(): 70 | '''Main function''' 71 | set_params(_argv) 72 | set_params_folders(_argv) 73 | if params["help"]: 74 | print_help(_argv[0]) 75 | elif params["recursive"] and params["mods"]: 76 | print("Option --installed-mods is incompatible with --recursive") 77 | else: 78 | # Add recursivity message 79 | print("Running ", end='') 80 | if params["recursive"]: 81 | print("recursively ", end='') 82 | # Running 83 | if params["mods"]: 84 | print(f"on all locally installed modules in {os.path.abspath('~/.minetest/mods/')}") 85 | run_all_subfolders("~/.minetest/mods") 86 | elif len(params["folders"]) >= 2: 87 | print("on folder list:", params["folders"]) 88 | for f in params["folders"]: 89 | if params["recursive"]: 90 | run_all_subfolders(f) 91 | else: 92 | update_folder(f) 93 | elif len(params["folders"]) == 1: 94 | print("on folder", params["folders"][0]) 95 | if params["recursive"]: 96 | run_all_subfolders(params["folders"][0]) 97 | else: 98 | update_folder(params["folders"][0]) 99 | else: 100 | print("on folder", os.path.abspath("./")) 101 | if params["recursive"]: 102 | run_all_subfolders(os.path.abspath("./")) 103 | else: 104 | update_folder(os.path.abspath("./")) 105 | 106 | #group 2 will be the string, groups 1 and 3 will be the delimiters (" or ') 107 | #See https://stackoverflow.com/questions/46967465/regex-match-text-in-either-single-or-double-quote 108 | pattern_lua = re.compile(r'[\.=^\t,{\(\s]N?S\(\s*(["\'])((?:\\\1|(?:(?!\1)).)*)(\1)[\s,\)]', re.DOTALL) 109 | pattern_lua_bracketed = re.compile(r'[\.=^\t,{\(\s]N?S\(\s*\[\[(.*?)\]\][\s,\)]', re.DOTALL) 110 | 111 | # Handles "concatenation" .. " of strings" 112 | pattern_concat = re.compile(r'["\'][\s]*\.\.[\s]*["\']', re.DOTALL) 113 | 114 | pattern_tr = re.compile(r'(.+?[^@])=(.*)') 115 | pattern_name = re.compile(r'^name[ ]*=[ ]*([^ \n]*)') 116 | pattern_tr_filename = re.compile(r'\.tr$') 117 | pattern_po_language_code = re.compile(r'(.*)\.po$') 118 | 119 | #attempt to read the mod's name from the mod.conf file. Returns None on failure 120 | def get_modname(folder): 121 | try: 122 | with open(os.path.join(folder, "mod.conf"), "r", encoding='utf-8') as mod_conf: 123 | for line in mod_conf: 124 | match = pattern_name.match(line) 125 | if match: 126 | return match.group(1) 127 | except FileNotFoundError: 128 | pass 129 | return None 130 | 131 | #If there are already .tr files in /locale, returns a list of their names 132 | def get_existing_tr_files(folder): 133 | out = [] 134 | for root, dirs, files in os.walk(os.path.join(folder, 'locale/')): 135 | for name in files: 136 | if pattern_tr_filename.search(name): 137 | out.append(name) 138 | return out 139 | 140 | # A series of search and replaces that massage a .po file's contents into 141 | # a .tr file's equivalent 142 | def process_po_file(text): 143 | # The first three items are for unused matches 144 | text = re.sub(r'#~ msgid "', "", text) 145 | text = re.sub(r'"\n#~ msgstr ""\n"', "=", text) 146 | text = re.sub(r'"\n#~ msgstr "', "=", text) 147 | # comment lines 148 | text = re.sub(r'#.*\n', "", text) 149 | # converting msg pairs into "=" pairs 150 | text = re.sub(r'msgid "', "", text) 151 | text = re.sub(r'"\nmsgstr ""\n"', "=", text) 152 | text = re.sub(r'"\nmsgstr "', "=", text) 153 | # various line breaks and escape codes 154 | text = re.sub(r'"\n"', "", text) 155 | text = re.sub(r'"\n', "\n", text) 156 | text = re.sub(r'\\"', '"', text) 157 | text = re.sub(r'\\n', '@n', text) 158 | # remove header text 159 | text = re.sub(r'=Project-Id-Version:.*\n', "", text) 160 | # remove double-spaced lines 161 | text = re.sub(r'\n\n', '\n', text) 162 | return text 163 | 164 | # Go through existing .po files and, if a .tr file for that language 165 | # *doesn't* exist, convert it and create it. 166 | # The .tr file that results will subsequently be reprocessed so 167 | # any "no longer used" strings will be preserved. 168 | # Note that "fuzzy" tags will be lost in this process. 169 | def process_po_files(folder, modname): 170 | for root, dirs, files in os.walk(os.path.join(folder, 'locale/')): 171 | for name in files: 172 | code_match = pattern_po_language_code.match(name) 173 | if code_match == None: 174 | continue 175 | language_code = code_match.group(1) 176 | tr_name = modname + "." + language_code + ".tr" 177 | tr_file = os.path.join(root, tr_name) 178 | if os.path.exists(tr_file): 179 | if params["verbose"]: 180 | print(f"{tr_name} already exists, ignoring {name}") 181 | continue 182 | fname = os.path.join(root, name) 183 | with open(fname, "r", encoding='utf-8') as po_file: 184 | if params["verbose"]: 185 | print(f"Importing translations from {name}") 186 | text = process_po_file(po_file.read()) 187 | with open(tr_file, "wt", encoding='utf-8') as tr_out: 188 | tr_out.write(text) 189 | 190 | # from https://stackoverflow.com/questions/600268/mkdir-p-functionality-in-python/600612#600612 191 | # Creates a directory if it doesn't exist, silently does 192 | # nothing if it already exists 193 | def mkdir_p(path): 194 | try: 195 | os.makedirs(path) 196 | except OSError as exc: # Python >2.5 197 | if exc.errno == errno.EEXIST and os.path.isdir(path): 198 | pass 199 | else: raise 200 | 201 | # Converts the template dictionary to a text to be written as a file 202 | # dKeyStrings is a dictionary of localized string to source file sets 203 | # dOld is a dictionary of existing translations and comments from 204 | # the previous version of this text 205 | def strings_to_text(dkeyStrings, dOld, mod_name): 206 | lOut = [f"# textdomain: {mod_name}\n"] 207 | 208 | dGroupedBySource = {} 209 | 210 | for key in dkeyStrings: 211 | sourceList = list(dkeyStrings[key]) 212 | sourceList.sort() 213 | sourceString = "\n".join(sourceList) 214 | listForSource = dGroupedBySource.get(sourceString, []) 215 | listForSource.append(key) 216 | dGroupedBySource[sourceString] = listForSource 217 | 218 | lSourceKeys = list(dGroupedBySource.keys()) 219 | lSourceKeys.sort() 220 | for source in lSourceKeys: 221 | localizedStrings = dGroupedBySource[source] 222 | localizedStrings.sort() 223 | lOut.append("") 224 | lOut.append(source) 225 | lOut.append("") 226 | for localizedString in localizedStrings: 227 | val = dOld.get(localizedString, {}) 228 | translation = val.get("translation", "") 229 | comment = val.get("comment") 230 | if len(localizedString) > doublespace_threshold and not lOut[-1] == "": 231 | lOut.append("") 232 | if comment != None: 233 | lOut.append(comment) 234 | lOut.append(f"{localizedString}={translation}") 235 | if len(localizedString) > doublespace_threshold: 236 | lOut.append("") 237 | 238 | 239 | unusedExist = False 240 | for key in dOld: 241 | if key not in dkeyStrings: 242 | val = dOld[key] 243 | translation = val.get("translation") 244 | comment = val.get("comment") 245 | # only keep an unused translation if there was translated 246 | # text or a comment associated with it 247 | if translation != None and (translation != "" or comment): 248 | if not unusedExist: 249 | unusedExist = True 250 | lOut.append("\n\n##### not used anymore #####\n") 251 | if len(key) > doublespace_threshold and not lOut[-1] == "": 252 | lOut.append("") 253 | if comment != None: 254 | lOut.append(comment) 255 | lOut.append(f"{key}={translation}") 256 | if len(key) > doublespace_threshold: 257 | lOut.append("") 258 | return "\n".join(lOut) + '\n' 259 | 260 | # Writes a template.txt file 261 | # dkeyStrings is the dictionary returned by generate_template 262 | def write_template(templ_file, dkeyStrings, mod_name): 263 | # read existing template file to preserve comments 264 | existing_template = import_tr_file(templ_file) 265 | 266 | text = strings_to_text(dkeyStrings, existing_template[0], mod_name) 267 | mkdir_p(os.path.dirname(templ_file)) 268 | with open(templ_file, "wt", encoding='utf-8') as template_file: 269 | template_file.write(text) 270 | 271 | 272 | # Gets all translatable strings from a lua file 273 | def read_lua_file_strings(lua_file): 274 | lOut = [] 275 | with open(lua_file, encoding='utf-8') as text_file: 276 | text = text_file.read() 277 | #TODO remove comments here 278 | 279 | text = re.sub(pattern_concat, "", text) 280 | 281 | strings = [] 282 | for s in pattern_lua.findall(text): 283 | strings.append(s[1]) 284 | for s in pattern_lua_bracketed.findall(text): 285 | strings.append(s) 286 | 287 | for s in strings: 288 | s = re.sub(r'"\.\.\s+"', "", s) 289 | s = re.sub("@[^@=0-9]", "@@", s) 290 | s = s.replace('\\"', '"') 291 | s = s.replace("\\'", "'") 292 | s = s.replace("\n", "@n") 293 | s = s.replace("\\n", "@n") 294 | s = s.replace("=", "@=") 295 | lOut.append(s) 296 | return lOut 297 | 298 | # Gets strings from an existing translation file 299 | # returns both a dictionary of translations 300 | # and the full original source text so that the new text 301 | # can be compared to it for changes. 302 | def import_tr_file(tr_file): 303 | dOut = {} 304 | text = None 305 | if os.path.exists(tr_file): 306 | with open(tr_file, "r", encoding='utf-8') as existing_file : 307 | # save the full text to allow for comparison 308 | # of the old version with the new output 309 | text = existing_file.read() 310 | existing_file.seek(0) 311 | # a running record of the current comment block 312 | # we're inside, to allow preceeding multi-line comments 313 | # to be retained for a translation line 314 | latest_comment_block = None 315 | for line in existing_file.readlines(): 316 | line = line.rstrip('\n') 317 | if line[:3] == "###": 318 | # Reset comment block if we hit a header 319 | latest_comment_block = None 320 | continue 321 | if line[:1] == "#": 322 | # Save the comment we're inside 323 | if not latest_comment_block: 324 | latest_comment_block = line 325 | else: 326 | latest_comment_block = latest_comment_block + "\n" + line 327 | continue 328 | match = pattern_tr.match(line) 329 | if match: 330 | # this line is a translated line 331 | outval = {} 332 | outval["translation"] = match.group(2) 333 | if latest_comment_block: 334 | # if there was a comment, record that. 335 | outval["comment"] = latest_comment_block 336 | latest_comment_block = None 337 | dOut[match.group(1)] = outval 338 | return (dOut, text) 339 | 340 | # Walks all lua files in the mod folder, collects translatable strings, 341 | # and writes it to a template.txt file 342 | # Returns a dictionary of localized strings to source file sets 343 | # that can be used with the strings_to_text function. 344 | def generate_template(folder, mod_name): 345 | dOut = {} 346 | for root, dirs, files in os.walk(folder): 347 | for name in files: 348 | if fnmatch.fnmatch(name, "*.lua"): 349 | fname = os.path.join(root, name) 350 | found = read_lua_file_strings(fname) 351 | if params["verbose"]: 352 | print(f"{fname}: {str(len(found))} translatable strings") 353 | 354 | for s in found: 355 | sources = dOut.get(s, set()) 356 | sources.add(f"### {os.path.basename(fname)} ###") 357 | dOut[s] = sources 358 | 359 | if len(dOut) == 0: 360 | return None 361 | templ_file = os.path.join(folder, "locale/template.txt") 362 | write_template(templ_file, dOut, mod_name) 363 | return dOut 364 | 365 | # Updates an existing .tr file, copying the old one to a ".old" file 366 | # if any changes have happened 367 | # dNew is the data used to generate the template, it has all the 368 | # currently-existing localized strings 369 | def update_tr_file(dNew, mod_name, tr_file): 370 | if params["verbose"]: 371 | print(f"updating {tr_file}") 372 | 373 | tr_import = import_tr_file(tr_file) 374 | dOld = tr_import[0] 375 | textOld = tr_import[1] 376 | 377 | textNew = strings_to_text(dNew, dOld, mod_name) 378 | 379 | if textOld and textOld != textNew: 380 | print(f"{tr_file} has changed.") 381 | shutil.copyfile(tr_file, f"{tr_file}.old") 382 | 383 | with open(tr_file, "w", encoding='utf-8') as new_tr_file: 384 | new_tr_file.write(textNew) 385 | 386 | # Updates translation files for the mod in the given folder 387 | def update_mod(folder): 388 | modname = get_modname(folder) 389 | if modname is not None: 390 | process_po_files(folder, modname) 391 | print(f"Updating translations for {modname}") 392 | data = generate_template(folder, modname) 393 | if data == None: 394 | print(f"No translatable strings found in {modname}") 395 | else: 396 | for tr_file in get_existing_tr_files(folder): 397 | update_tr_file(data, modname, os.path.join(folder, "locale/", tr_file)) 398 | else: 399 | print("Unable to find modname in folder " + folder) 400 | 401 | # Determines if the folder being pointed to is a mod or a mod pack 402 | # and then runs update_mod accordingly 403 | def update_folder(folder): 404 | is_modpack = os.path.exists(os.path.join(folder, "modpack.txt")) or os.path.exists(os.path.join(folder, "modpack.conf")) 405 | if is_modpack: 406 | subfolders = [f.path for f in os.scandir(folder) if f.is_dir()] 407 | for subfolder in subfolders: 408 | update_mod(subfolder + "/") 409 | else: 410 | update_mod(folder) 411 | print("Done.") 412 | 413 | def run_all_subfolders(folder): 414 | for modfolder in [f.path for f in os.scandir(folder) if f.is_dir()]: 415 | update_folder(modfolder + "/") 416 | 417 | 418 | main() 419 | -------------------------------------------------------------------------------- /init.lua: -------------------------------------------------------------------------------- 1 | ropes = { 2 | name = 'ropes', 3 | } 4 | 5 | -- internationalization boilerplate 6 | local modname = minetest.get_current_modname() 7 | local MP = minetest.get_modpath(modname) 8 | ropes.S = minetest.get_translator(modname) 9 | 10 | ropes.ropeLength = tonumber(minetest.settings:get("ropes_rope_length")) or 50 11 | ropes.woodRopeBoxMaxMultiple = tonumber(minetest.settings:get("ropes_wood_rope_box_max_multiple")) or 2 12 | ropes.copperRopeBoxMaxMultiple = tonumber(minetest.settings:get("ropes_copper_rope_box_max_multiple")) or 5 13 | ropes.steelRopeBoxMaxMultiple = tonumber(minetest.settings:get("ropes_steel_rope_box_max_multiple")) or 9 14 | ropes.create_all_definitions = minetest.settings:get_bool("ropes_create_all_definitions") 15 | 16 | ropes.ropeLadderLength = tonumber(minetest.settings:get("ropes_rope_ladder_length")) or 50 17 | 18 | ropes.extending_ladder_enabled = minetest.settings:get_bool("ropes_extending_ladder_enabled") 19 | if ropes.extending_ladder_enabled == nil then 20 | ropes.extending_ladder_enabled = true 21 | end 22 | ropes.replace_default_ladders = minetest.settings:get_bool("ropes_replace_default_ladders") 23 | 24 | ropes.extending_wood_ladder_limit = tonumber(minetest.settings:get("ropes_extending_wood_ladder_limit")) or 5 25 | ropes.extending_steel_ladder_limit = tonumber(minetest.settings:get("ropes_extending_steel_ladder_limit")) or 15 26 | 27 | ropes.bridges_enabled = minetest.settings:get_bool("ropes_bridges_enabled") 28 | if ropes.bridges_enabled == nil then 29 | ropes.bridges_enabled = true 30 | end 31 | 32 | ropes.can_extend_into_airlike = minetest.settings:get_bool("ropes_can_extend_into_airlike") 33 | ropes.can_extend_into_nodes = {["air"] = true} 34 | if minetest.get_modpath("nether") then 35 | ropes.can_extend_into_nodes["nether:fumes"] = true 36 | end 37 | 38 | dofile( MP .. "/doc.lua" ) 39 | dofile( MP .. "/functions.lua" ) 40 | dofile( MP .. "/crafts.lua" ) 41 | dofile( MP .. "/ropeboxes.lua" ) 42 | dofile( MP .. "/ropeladder.lua" ) 43 | dofile( MP .. "/extendingladder.lua" ) 44 | dofile( MP .. "/bridge.lua" ) 45 | dofile( MP .. "/loot.lua" ) 46 | 47 | for i=1,5 do 48 | minetest.register_alias(string.format("vines:%irope_block", i), string.format("ropes:%irope_block", i)) 49 | end 50 | minetest.register_alias("vines:rope", "ropes:rope") 51 | minetest.register_alias("vines:rope_bottom", "ropes:rope_bottom") 52 | minetest.register_alias("vines:rope_end", "ropes:rope_bottom") 53 | minetest.register_alias("vines:rope_top", "ropes:rope_top") 54 | minetest.register_alias("vines:ropeladder_top", "ropes:ropeladder_top") 55 | minetest.register_alias("vines:ropeladder", "ropes:ropeladder") 56 | minetest.register_alias("vines:ropeladder_bottom", "ropes:ropeladder_bottom") 57 | minetest.register_alias("vines:ropeladder_falling", "ropes:ropeladder_falling") 58 | minetest.register_alias("vines:rope_block", "ropes:steel5rope_block") 59 | for i=1,9 do 60 | minetest.register_alias(string.format("ropes:%irope_block", i), string.format("ropes:steel%irope_block", i)) 61 | end 62 | minetest.register_alias("castle:ropes", "ropes:rope") 63 | minetest.register_alias("castle:ropebox", "ropes:steel1rope_block") 64 | minetest.register_alias("castle:box_rope", "ropes:rope") 65 | 66 | print("[Ropes] Loaded!") 67 | -------------------------------------------------------------------------------- /locale/ropes.es.tr: -------------------------------------------------------------------------------- 1 | # textdomain: ropes 2 | 3 | 4 | ### bridge.lua ### 5 | 6 | #WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE 7 | Wooden Bridge=Puente de madera 8 | 9 | ### crafts.lua ### 10 | 11 | Rope Segment=Segmento de cuerda 12 | 13 | ### doc.lua ### 14 | 15 | A hanging rope ladder that automatically extends downward.=Una escalera de cuerda colgante que se extiende automáticamente hacia abajo. 16 | 17 | #WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE 18 | A ladder for climbing. It can reach greater heights when placed against a supporting block.=Una escalera para subir. Puede alcanzar mayores alturas cuando se coloca contra un bloque de soporte. 19 | 20 | A rope can be severed midway using an axe or other similar tool. The section of rope below the cut will collapse and disappear, potentially causing players who were hanging on to it to fall. The remaining rope will not resume descent on its own, but the rope box at the top of the rope "remembers" how long the rope was and if it is deconstructed and replaced it will still have the same maximum length of rope as before - no rope is permanently lost when a rope is severed like this.=Una cuerda puede ser cortada a mitad de camino usando un hacha u otra herramienta similar. La sección de la cuerda debajo del corte se colapsará y desaparecerá, lo que puede causar que los jugadores que estaban colgados de ella se caigan. El resto de la cuerda no volverá a descender por sí sola, pero la caja de la cuerda en la parte superior de la cuerda "recuerda" la longitud de la cuerda y si es deconstruida y reemplazada tendrá la misma longitud máxima de cuerda que antes - ninguna cuerda se pierde permanentemente cuando una cuerda es cortada de esta manera. 21 | 22 | #WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE 23 | A wooden platform with support struts useful for bridging gaps.=Una plataforma de madera con puntales de soporte útil para salvar huecos. 24 | 25 | After a rope ladder is placed on a vertical wall it will begin extending downward until it reaches its maximum length (@1 meters). If the rope ladder is removed all of the ladder below the point of removal will disappear. A rope ladder can be severed partway down using an axe or similar tool, and the ladder below the point where it is cut will collapse. No rope is actually lost in the process, though, and if the uppermost section of the ladder is removed and replaced the ladder will re-extend to the same maximum length as before.=Después de colocar una escalera de cuerda en una pared vertical, comenzará a extenderse hacia abajo hasta que alcance su longitud máxima (@1 metro). Si se retira la escalera de cuerda, desaparecerá toda la escalera por debajo del punto de extracción. Una escalera de cuerda puede ser cortada hasta la mitad usando un hacha o una herramienta similar, y la escalera por debajo del punto donde es cortada colapsará. Sin embargo, no se pierde ninguna cuerda en el proceso, y si la sección superior de la escalera se retira y se reemplaza, la escalera se volverá a extender a la misma longitud máxima que antes. 26 | 27 | #WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE 28 | Right-clicking on a ladder with a stack of identical ladder items will automatically add new ladder segments to the top, provided it hasn't extended too far up beyond the last block behind it providing support.=Al hacer clic con el botón derecho en una escalera con una pila de elementos de escalera idénticos, se agregarán automáticamente nuevos segmentos de escalera a la parte superior, siempre que no se haya extendido demasiado más allá del último bloque detrás de ella que brinda soporte. 29 | 30 | Rope boxes have a certain amount of rope contained within them specified in the name of the node, and have a limit to how much rope they can support that depends on the material they're made of. The different lengths can be crafted by combining and splitting up rope boxes in the crafting grid. For example, you can craft a @1m rope box by putting a @2m rope box and a rope segment in the crafting grid, or a @3m rope box and two rope segments in the crafting grid. Two rope segments can be recovered by putting the @4m rope box in the crafting grid by itself.=Las cajas de cuerdas tienen una cierta cantidad de cuerda contenida dentro de ellas especificada en el nombre del nodo, y tienen un límite en la cantidad de cuerda que pueden soportar que depende del material del que están hechas. Las diferentes longitudes se pueden realizar combinando y dividiendo las cajas de cuerda en la rejilla de elaboración. Por ejemplo, puedes crear una caja de cuerda de @1m poniendo una caja de cuerda de @2m y un segmento de cuerda en la rejilla de artesanía, o una caja de cuerda de @3m y dos segmentos de cuerda en la rejilla de artesanía. Se pueden recuperar dos segmentos de cable colocando solo la caja de cable de @4m en la rejilla de fabricación. 31 | 32 | Rope segments are bundles of fibre twisted into robust cables.=Los segmentos de cable son haces de fibras trenzadas en cables robustos. 33 | 34 | Ropes are hung by placing rope boxes, which automatically lower a rope of fixed length below them. They can be climbed and cut.=Las cuerdas se cuelgan colocando cajas de cuerda, que bajan automáticamente una cuerda de longitud fija por debajo de ellas. Se pueden escalar y cortar. 35 | 36 | #WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE 37 | This behaves like most structural blocks except in one circumstance: when placed on top of a block with buildable space on the side facing away from you, this block will not be built on top but instead will extend out from that far side of the target block. This allows a platform to be easily built that juts out away from the location you're standing on.=Esto se comporta como la mayoría de los bloques estructurales, excepto en una circunstancia: cuando se coloca encima de un bloque con un espacio edificable en el lado que mira hacia afuera, este bloque no se construirá en la parte superior, sino que se extenderá desde ese lado más alejado del bloque objetivo. . Esto permite construir fácilmente una plataforma que sobresale del lugar en el que se encuentra. 38 | 39 | This craft item is useful for creating rope ladders, or for spooling on wooden spindles to hang and climb upon.=Esta objeto es útil para crear escaleras de cuerda, o para enrollar en husillos de madera para colgar y trepar. 40 | 41 | When a rope box is placed the rope will immediately begin lowering from it at one meter per second. The rope will only descend when its end is in the vicinity of an active player, suspending its journey when no players are nearby, so a long descent may require a player to climb down the rope as it goes. If you are near the bottom end of a rope that's extending you'll be automatically carried down with it. The rope will stop when it encounters and obstruction, but will resume lowering if the obstruction is removed.=Cuando se coloca una caja de cuerda, la cuerda comienza a descender inmediatamente a un metro por segundo. La cuerda sólo descenderá cuando su final esté cerca de un jugador activo, suspendiendo su viaje cuando no haya jugadores cerca, por lo que un largo descenso puede requerir que el jugador baje por la cuerda a medida que avanza. Si estás cerca del extremo inferior de una cuerda que se está extendiendo, serás arrastrado automáticamente hacia abajo con ella. La cuerda se detendrá cuando se encuentre con una obstrucción, pero volverá a bajar si se retira la obstrucción. 42 | 43 | rope boxes can hold @1m of rope.=Las cajas de cuerdas pueden mantener @1m de cuerda. 44 | rope boxes can hold rope lengths from @1m to @2m.=Las cajas de cuerda pueden contener longitudes de cuerda de @1m a @2m. 45 | 46 | ### doc.lua ### 47 | ### ropeboxes.lua ### 48 | 49 | Copper=cobre 50 | Steel=acero 51 | Wood=madera 52 | 53 | ### extendingladder.lua ### 54 | 55 | #WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE 56 | Steel Extendable Ladder=Escalera extensible de acero 57 | #WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE 58 | Steel Ladder=Escalera de acero 59 | #WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE 60 | Wooden Extendable Ladder=Escalera extensible de madera 61 | #WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE 62 | Wooden Ladder=Escalera de madera 63 | 64 | ### ropeboxes.lua ### 65 | 66 | @1 Ropebox @2m=Caja de cuerda de @1 de @2m 67 | Rope=Cuerda 68 | 69 | ### ropeladder.lua ### 70 | 71 | Rope Ladder=Escalera de cuerda 72 | -------------------------------------------------------------------------------- /locale/ropes.ru.tr: -------------------------------------------------------------------------------- 1 | # textdomain: ropes 2 | 3 | ### bridge.lua ### 4 | 5 | #WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE 6 | Wooden Bridge=Деревянный мост 7 | 8 | ### crafts.lua ### 9 | 10 | Rope Segment=Сегмент каната 11 | 12 | ### doc.lua ### 13 | 14 | A hanging rope ladder that automatically extends downward.=Подвесная веревочная лестница, которая автоматически выдвигается вниз. 15 | 16 | #WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE 17 | A ladder for climbing. It can reach greater heights when placed against a supporting block.=Лестница для подъема. Она может достигать большей высоты, если ее приставить к опорному блоку. 18 | 19 | A rope can be severed midway using an axe or other similar tool. The section of rope below the cut will collapse and disappear, potentially causing players who were hanging on to it to fall. The remaining rope will not resume descent on its own, but the rope box at the top of the rope "remembers" how long the rope was and if it is deconstructed and replaced it will still have the same maximum length of rope as before - no rope is permanently lost when a rope is severed like this.=Веревку можно перерезать на полпути с помощью топора или другого похожего инструмента. Часть веревки в месте разреза разрушится и исчезнет, ​​что может привести к падению игроков, которые на ней висели. Оставшаяся веревка не возобновит спуск сама по себе, но коробка для веревок наверху «помнит», какой длины была веревка, и если ее разобрать и заменить, она все равно будет иметь ту же максимальную длину веревки, что и раньше — ни одна веревка не теряется навсегда, когда веревка перерезана таким образом. 20 | 21 | #WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE 22 | A wooden platform with support struts useful for bridging gaps.=Деревянная платформа с опорными стойками, используемая для перекрытия щелей. 23 | 24 | After a rope ladder is placed on a vertical wall it will begin extending downward until it reaches its maximum length (@1 meters). If the rope ladder is removed all of the ladder below the point of removal will disappear. A rope ladder can be severed partway down using an axe or similar tool, and the ladder below the point where it is cut will collapse. No rope is actually lost in the process, though, and if the uppermost section of the ladder is removed and replaced the ladder will re-extend to the same maximum length as before.=После того, как веревочная лестница будет размещена на вертикальной стене, она начнет удлиняться вниз, пока не достигнет своей максимальной длины (@1 метр). Если веревочную лестницу убрать, вся лестница ниже точки удаления исчезнет. Веревочную лестницу можно перерезать наполовину с помощью топора или подобного инструмента, и лестница ниже точки, где она перерезана, рухнет. Однако в этом процессе веревка фактически не теряется, и если снять и заменить самую верхнюю часть лестницы, лестница снова удлинится до той же максимальной длины, что и раньше. 25 | 26 | #WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE 27 | Right-clicking on a ladder with a stack of identical ladder items will automatically add new ladder segments to the top, provided it hasn't extended too far up beyond the last block behind it providing support.=Щелчок правой кнопкой мыши по лестнице со стопкой идентичных элементов лестницы автоматически добавит новые сегменты лестницы наверх, при условии, что она не выступает слишком далеко за пределы последнего блока позади нее, обеспечивающего поддержку. 28 | 29 | Rope boxes have a certain amount of rope contained within them specified in the name of the node, and have a limit to how much rope they can support that depends on the material they're made of. The different lengths can be crafted by combining and splitting up rope boxes in the crafting grid. For example, you can craft a @1m rope box by putting a @2m rope box and a rope segment in the crafting grid, or a @3m rope box and two rope segments in the crafting grid. Two rope segments can be recovered by putting the @4m rope box in the crafting grid by itself.=Ящики для веревок содержат определенное количество веревки, указанное в названии узла, и имеют ограничение на то, сколько веревки они могут поддерживать, в зависимости от материала, из которого они сделаны. Различные длины могут быть созданы путем объединения и разделения ящиков для веревок в сетке крафта. Например, вы можете создать ящик для веревки @1m, поместив ящик для веревки @2m и сегмент веревки в сетку крафта, или ящик для веревки @3m и два сегмента веревки в сетку крафта. Два сегмента веревки можно получить, поместив ящик для веревки @4m в сетку крафта отдельно. 30 | 31 | Rope segments are bundles of fibre twisted into robust cables.=Сегменты каната представляют собой пучки волокон, скрученные в прочные тросы. 32 | 33 | Ropes are hung by placing rope boxes, which automatically lower a rope of fixed length below them. They can be climbed and cut.=Канаты подвешиваются путем размещения ящиков для канатов, которые автоматически опускают под ними канат фиксированной длины. По ним можно лазить и резать. 34 | 35 | #WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE 36 | This behaves like most structural blocks except in one circumstance: when placed on top of a block with buildable space on the side facing away from you, this block will not be built on top but instead will extend out from that far side of the target block. This allows a platform to be easily built that juts out away from the location you're standing on.=Он ведет себя как большинство структурных блоков, за исключением одного обстоятельства: при размещении на блоке с пространством для строительства на стороне, обращенной от вас, этот блок не будет построен сверху, а вместо этого будет выступать из дальней стороны целевого блока. Это позволяет легко построить платформу, которая выступает из того места, где вы стоите. 37 | 38 | This craft item is useful for creating rope ladders, or for spooling on wooden spindles to hang and climb upon.=Этот предмет ручной работы пригодится для создания веревочных лестниц или для наматывания на деревянные стержни, чтобы можно было повесить веревку или залезть на нее. 39 | 40 | When a rope box is placed the rope will immediately begin lowering from it at one meter per second. The rope will only descend when its end is in the vicinity of an active player, suspending its journey when no players are nearby, so a long descent may require a player to climb down the rope as it goes. If you are near the bottom end of a rope that's extending you'll be automatically carried down with it. The rope will stop when it encounters and obstruction, but will resume lowering if the obstruction is removed.=Когда вы размещаете ящик с веревкой, веревка немедленно начнет опускаться с него со скоростью один метр в секунду. Веревка будет опускаться только тогда, когда ее конец находится в непосредственной близости от активного игрока, приостанавливая свое движение, когда поблизости нет игроков, поэтому для длительного спуска игроку может потребоваться спускаться по веревке по мере ее движения. Если вы находитесь около нижнего конца удлиняющейся веревки, вы автоматически будете унесены ею вниз. Веревка остановится, когда столкнется с препятствием, но возобновит спуск, если препятствие будет устранено. 41 | 42 | rope boxes can hold @1m of rope.=Ящики для веревок вмещают до 1 м веревки. 43 | rope boxes can hold rope lengths from @1m to @2m.=Ящики для веревок могут вмещать веревки длиной от @1 до @2 метров. 44 | 45 | ### doc.lua ### 46 | ### ropeboxes.lua ### 47 | 48 | Copper=Медный 49 | Steel=Стальной 50 | Wood=Древесный 51 | 52 | ### extendingladder.lua ### 53 | 54 | #WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE 55 | Steel Extendable Ladder=Стальная раздвижная лестница 56 | #WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE 57 | Steel Ladder=Стальная лестница 58 | #WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE 59 | Wooden Extendable Ladder=Деревянная раздвижная лестница 60 | #WARNING: AUTOTRANSLATED BY GOOGLE TRANSLATE 61 | Wooden Ladder=Деревянная лестница 62 | 63 | ### ropeboxes.lua ### 64 | 65 | @1 Ropebox @2m=@1 веревочный ящик @2м 66 | Rope=Веревка 67 | 68 | ### ropeladder.lua ### 69 | 70 | Rope Ladder=Веревочная лестница 71 | -------------------------------------------------------------------------------- /locale/template.txt: -------------------------------------------------------------------------------- 1 | # textdomain: ropes 2 | 3 | 4 | ### bridge.lua ### 5 | 6 | Wooden Bridge= 7 | 8 | ### crafts.lua ### 9 | 10 | Rope Segment= 11 | 12 | ### doc.lua ### 13 | 14 | A hanging rope ladder that automatically extends downward.= 15 | 16 | A ladder for climbing. It can reach greater heights when placed against a supporting block.= 17 | 18 | A rope can be severed midway using an axe or other similar tool. The section of rope below the cut will collapse and disappear, potentially causing players who were hanging on to it to fall. The remaining rope will not resume descent on its own, but the rope box at the top of the rope "remembers" how long the rope was and if it is deconstructed and replaced it will still have the same maximum length of rope as before - no rope is permanently lost when a rope is severed like this.= 19 | 20 | A wooden platform with support struts useful for bridging gaps.= 21 | 22 | After a rope ladder is placed on a vertical wall it will begin extending downward until it reaches its maximum length (@1 meters). If the rope ladder is removed all of the ladder below the point of removal will disappear. A rope ladder can be severed partway down using an axe or similar tool, and the ladder below the point where it is cut will collapse. No rope is actually lost in the process, though, and if the uppermost section of the ladder is removed and replaced the ladder will re-extend to the same maximum length as before.= 23 | 24 | Right-clicking on a ladder with a stack of identical ladder items will automatically add new ladder segments to the top, provided it hasn't extended too far up beyond the last block behind it providing support.= 25 | 26 | Rope boxes have a certain amount of rope contained within them specified in the name of the node, and have a limit to how much rope they can support that depends on the material they're made of. The different lengths can be crafted by combining and splitting up rope boxes in the crafting grid. For example, you can craft a @1m rope box by putting a @2m rope box and a rope segment in the crafting grid, or a @3m rope box and two rope segments in the crafting grid. Two rope segments can be recovered by putting the @4m rope box in the crafting grid by itself.= 27 | 28 | Rope segments are bundles of fibre twisted into robust cables.= 29 | 30 | Ropes are hung by placing rope boxes, which automatically lower a rope of fixed length below them. They can be climbed and cut.= 31 | 32 | This behaves like most structural blocks except in one circumstance: when placed on top of a block with buildable space on the side facing away from you, this block will not be built on top but instead will extend out from that far side of the target block. This allows a platform to be easily built that juts out away from the location you're standing on.= 33 | 34 | This craft item is useful for creating rope ladders, or for spooling on wooden spindles to hang and climb upon.= 35 | 36 | When a rope box is placed the rope will immediately begin lowering from it at one meter per second. The rope will only descend when its end is in the vicinity of an active player, suspending its journey when no players are nearby, so a long descent may require a player to climb down the rope as it goes. If you are near the bottom end of a rope that's extending you'll be automatically carried down with it. The rope will stop when it encounters and obstruction, but will resume lowering if the obstruction is removed.= 37 | 38 | rope boxes can hold @1m of rope.= 39 | rope boxes can hold rope lengths from @1m to @2m.= 40 | 41 | ### doc.lua ### 42 | ### ropeboxes.lua ### 43 | 44 | Copper= 45 | Steel= 46 | Wood= 47 | 48 | ### extendingladder.lua ### 49 | 50 | Steel Extendable Ladder= 51 | Steel Ladder= 52 | Wooden Extendable Ladder= 53 | Wooden Ladder= 54 | 55 | ### ropeboxes.lua ### 56 | 57 | @1 Ropebox @2m= 58 | Rope= 59 | 60 | ### ropeladder.lua ### 61 | 62 | Rope Ladder= 63 | -------------------------------------------------------------------------------- /loot.lua: -------------------------------------------------------------------------------- 1 | if not minetest.get_modpath("loot") then 2 | return 3 | end 4 | 5 | loot.register_loot({ 6 | weights = { generic = 300 }, 7 | payload = { 8 | stack = ItemStack("ropes:ropesegment"), 9 | min_size = 1, 10 | max_size = 50, 11 | }, 12 | }) 13 | 14 | if ropes.ropeLadderLength > 0 then 15 | loot.register_loot({ 16 | weights = { generic = 150 }, 17 | payload = { 18 | stack = ItemStack("ropes:ropeladder_top"), 19 | min_size = 1, 20 | max_size = 20, 21 | }, 22 | }) 23 | end 24 | 25 | if ropes.woodRopeBoxMaxMultiple > 0 then 26 | loot.register_loot({ 27 | weights = { generic = 100 }, 28 | payload = { 29 | stack = ItemStack("ropes:wood1rope_block"), 30 | min_size = 1, 31 | max_size = 20, 32 | }, 33 | }) 34 | end 35 | 36 | if ropes.copperRopeBoxMaxMultiple > 0 then 37 | loot.register_loot({ 38 | weights = { generic = 75 }, 39 | payload = { 40 | stack = ItemStack("ropes:copper1rope_block"), 41 | min_size = 1, 42 | max_size = 15, 43 | }, 44 | }) 45 | end 46 | 47 | if ropes.steelRopeBoxMaxMultiple > 0 then 48 | loot.register_loot({ 49 | weights = { generic = 50 }, 50 | payload = { 51 | stack = ItemStack("ropes:steel1rope_block"), 52 | min_size = 1, 53 | max_size = 10, 54 | }, 55 | }) 56 | end -------------------------------------------------------------------------------- /mod.conf: -------------------------------------------------------------------------------- 1 | name = ropes 2 | description = Adds rope boxes and ladders 3 | depends = default 4 | optional_depends = cottages, doc, farming, hemp, loot, vines 5 | -------------------------------------------------------------------------------- /ropeboxes.lua: -------------------------------------------------------------------------------- 1 | -- internationalization boilerplate 2 | local S = ropes.S 3 | 4 | local function rope_box_tiles(count, tint) 5 | return { 6 | string.format("ropes_ropebox_front_%i.png^[colorize:%s^ropes_ropebox_front_%i.png^ropes_%i.png", count, tint, count, count), 7 | string.format("ropes_ropebox_front_%i.png^[colorize:%s^ropes_ropebox_front_%i.png^ropes_%i.png", count, tint, count, count), 8 | string.format("ropes_ropebox_side.png^[colorize:%s^ropes_ropebox_side.png", tint), 9 | string.format("ropes_ropebox_side.png^[colorize:%s^ropes_ropebox_side.png", tint), 10 | string.format("ropes_ropebox_front_%i.png^[colorize:%s^ropes_ropebox_front_%i.png^ropes_%i.png", count, tint, count, count), 11 | string.format("ropes_ropebox_front_%i.png^[colorize:%s^ropes_ropebox_front_%i.png^ropes_%i.png", count, tint, count, count), 12 | } 13 | end 14 | 15 | local rope_box_data = { 16 | { 17 | node={ 18 | {-0.125, -0.125, -0.25, 0.125, 0.125, 0.25}, -- pulley 19 | {-0.125, -0.25, -0.125, 0.125, 0.25, 0.125}, -- pulley 20 | {-0.125, -0.1875, -0.1875, 0.125, 0.1875, 0.1875}, -- pulley_core 21 | {-0.1875, -0.5, -0.125, -0.125, 0.125, 0.125}, -- support 22 | {0.125, -0.5, -0.125, 0.1875, 0.125, 0.125}, -- support 23 | }, 24 | --selection = {-0.1875, -0.5, -0.25, 0.1875, 0.25, 0.25}, -- selection 25 | tiles = 1, 26 | }, 27 | { 28 | node={ 29 | {-0.1875, -0.125, -0.25, 0.1875, 0.125, 0.25}, -- pulley 30 | {-0.1875, -0.25, -0.125, 0.1875, 0.25, 0.125}, -- pulley 31 | {-0.1875, -0.1875, -0.1875, 0.1875, 0.1875, 0.1875}, -- pulley_core 32 | {-0.25, -0.5, -0.125, -0.1875, 0.125, 0.125}, -- support 33 | {0.1875, -0.5, -0.125, 0.25, 0.125, 0.125}, -- support 34 | }, 35 | --selection = {-0.1875, -0.5, -0.25, 0.1875, 0.25, 0.25}, -- selection 36 | tiles = 2, 37 | }, 38 | { 39 | node={ 40 | {-0.25, -0.125, -0.25, 0.25, 0.125, 0.25}, -- pulley 41 | {-0.25, -0.25, -0.125, 0.25, 0.25, 0.125}, -- pulley 42 | {-0.25, -0.1875, -0.1875, 0.25, 0.1875, 0.1875}, -- pulley_core 43 | {-0.3125, -0.5, -0.125, -0.25, 0.125, 0.125}, -- support 44 | {0.25, -0.5, -0.125, 0.3125, 0.125, 0.125}, -- support 45 | }, 46 | --selection = {-0.3125, -0.5, -0.25, 0.3125, 0.25, 0.25}, -- selection 47 | tiles = 3, 48 | }, 49 | { 50 | node={ 51 | {-0.3125, -0.125, -0.25, 0.3125, 0.125, 0.25}, -- pulley 52 | {-0.3125, -0.25, -0.125, 0.3125, 0.25, 0.125}, -- pulley 53 | {-0.3125, -0.1875, -0.1875, 0.3125, 0.1875, 0.1875}, -- pulley_core 54 | {-0.375, -0.5, -0.125, -0.3125, 0.125, 0.125}, -- support 55 | {0.3125, -0.5, -0.125, 0.375, 0.125, 0.125}, -- support 56 | }, 57 | --selection = {-0.375, -0.5, -0.25, 0.375, 0.25, 0.25}, -- selection 58 | tiles = 4, 59 | }, 60 | { 61 | node={ 62 | {-0.375, -0.125, -0.25, 0.375, 0.125, 0.25}, -- pulley 63 | {-0.375, -0.25, -0.125, 0.375, 0.25, 0.125}, -- pulley 64 | {-0.375, -0.1875, -0.1875, 0.375, 0.1875, 0.1875}, -- pulley_core 65 | {-0.4375, -0.5, -0.125, -0.375, 0.125, 0.125}, -- support 66 | {0.375, -0.5, -0.125, 0.4375, 0.125, 0.125}, -- support 67 | }, 68 | --selection = {-0.4375, -0.5, -0.25, 0.4375, 0.25, 0.25}, -- selection 69 | tiles = 5, 70 | }, 71 | { 72 | node={ 73 | {-0.1875, -0.1875, -0.3125, 0.1875, 0.1875, 0.3125}, -- pulley 74 | {-0.1875, -0.3125, -0.1875, 0.1875, 0.3125, 0.1875}, -- pulley 75 | {-0.1875, -0.25, -0.25, 0.1875, 0.25, 0.25}, -- pulley_core 76 | {-0.25, -0.5, -0.125, -0.1875, 0.125, 0.125}, -- support 77 | {0.1875, -0.5, -0.125, 0.25, 0.125, 0.125}, -- support 78 | }, 79 | --selection = {-0.1875, -0.5, -0.3125, 0.1875, 0.3125, 0.3125}, -- selection 80 | tiles = 2, 81 | }, 82 | { 83 | node={ 84 | {-0.25, -0.1875, -0.3125, 0.25, 0.1875, 0.3125}, -- pulley 85 | {-0.25, -0.3125, -0.1875, 0.25, 0.3125, 0.1875}, -- pulley 86 | {-0.25, -0.25, -0.25, 0.25, 0.25, 0.25}, -- pulley_core 87 | {-0.3125, -0.5, -0.125, -0.25, 0.125, 0.125}, -- support 88 | {0.25, -0.5, -0.125, 0.3125, 0.125, 0.125}, -- support 89 | }, 90 | --selection = {-0.3125, -0.5, -0.3125, 0.3125, 0.3125, 0.3125}, -- selection 91 | tiles = 3, 92 | }, 93 | { 94 | node={ 95 | {-0.3125, -0.1875, -0.3125, 0.3125, 0.1875, 0.3125}, -- pulley 96 | {-0.3125, -0.3125, -0.1875, 0.3125, 0.3125, 0.1875}, -- pulley 97 | {-0.3125, -0.25, -0.25, 0.3125, 0.25, 0.25}, -- pulley_core 98 | {-0.375, -0.5, -0.125, -0.3125, 0.125, 0.125}, -- support 99 | {0.3125, -0.5, -0.125, 0.375, 0.125, 0.125}, -- support 100 | }, 101 | --selection = {-0.375, -0.5, -0.3125, 0.375, 0.3125, 0.3125}, -- selection 102 | tiles = 4, 103 | }, 104 | { 105 | node={ 106 | {-0.375, -0.1875, -0.3125, 0.375, 0.1875, 0.3125}, -- pulley 107 | {-0.375, -0.3125, -0.1875, 0.375, 0.3125, 0.1875}, -- pulley 108 | {-0.375, -0.25, -0.25, 0.375, 0.25, 0.25}, -- pulley_core 109 | {-0.4375, -0.5, -0.125, -0.375, 0.125, 0.125}, -- support 110 | {0.375, -0.5, -0.125, 0.4375, 0.125, 0.125}, -- support 111 | }, 112 | --selection_bottom = {-0.4375, -0.5, -0.3125, 0.4375, 0.3125, 0.3125}, -- selection 113 | tiles = 5, 114 | } 115 | } 116 | 117 | local function register_rope_block(multiple, max_multiple, name_prefix, node_prefix, tint, base_material, flammable) 118 | local node_name = string.format("ropes:%s%irope_block", node_prefix, multiple) 119 | local rope_block_def = { 120 | description = S("@1 Ropebox @2m", name_prefix, ropes.ropeLength*multiple), 121 | _doc_items_create_entry = false, 122 | drawtype="nodebox", 123 | sunlight_propagates = true, 124 | paramtype = "light", 125 | paramtype2 = "wallmounted", 126 | walkable = false, 127 | climbable = true, 128 | tiles = rope_box_tiles(rope_box_data[multiple].tiles, tint), 129 | use_texture_alpha = "clip", 130 | is_ground_content = false, 131 | node_box = { 132 | type = "fixed", 133 | fixed = rope_box_data[multiple].node 134 | }, 135 | selection_box = {type="regular"}, 136 | collision_box = {type="regular"}, 137 | groups = {attached_node = 1, choppy=2, oddly_breakable_by_hand=1, rope_block = 1}, 138 | 139 | on_place = function(itemstack, placer, pointed_thing) 140 | if pointed_thing.type == "node" then 141 | local target_node = minetest.get_node(pointed_thing.under) 142 | local target_def = minetest.registered_nodes[target_node.name] 143 | if target_def.walkable == false then 144 | return itemstack 145 | end 146 | end 147 | return minetest.item_place(itemstack, placer, pointed_thing) 148 | end, 149 | 150 | after_place_node = function(pos, placer) 151 | local pos_below = {x=pos.x, y=pos.y-1, z=pos.z} 152 | local placer_name = placer:get_player_name() 153 | 154 | if minetest.is_protected(pos_below, placer_name) and not minetest.check_player_privs(placer, "protection_bypass") then 155 | return 156 | end 157 | 158 | local node_below = minetest.get_node(pos_below) 159 | if ropes.can_place_rope_in_node(node_below.name) then 160 | minetest.add_node(pos_below, {name="ropes:rope_bottom"}) 161 | local meta = minetest.get_meta(pos_below) 162 | meta:set_int("length_remaining", ropes.ropeLength*multiple) 163 | meta:set_string("placer", placer:get_player_name()) 164 | end 165 | end, 166 | 167 | after_destruct = function(pos) 168 | local pos_below = {x=pos.x, y=pos.y-1, z=pos.z} 169 | ropes.destroy_rope(pos_below, {'ropes:rope', 'ropes:rope_bottom'}) 170 | end 171 | } 172 | 173 | -- If this number is higher than permitted, we still want to register the block (in case 174 | -- some were already placed in-world) but we want to hide it from creative inventory 175 | -- and if someone digs it we want to disintegrate it into its component parts to prevent 176 | -- reuse. 177 | if multiple > max_multiple then 178 | rope_block_def.groups.not_in_creative_inventory = 1 179 | rope_block_def.drop = string.format("ropes:%s1rope_block %i", node_prefix, multiple) 180 | end 181 | 182 | if flammable then 183 | rope_block_def.groups.flammable = flammable 184 | 185 | minetest.register_craft({ 186 | type = "fuel", 187 | recipe = node_name, 188 | burntime = ropes.rope_burn_time * multiple + ropes.wood_burn_time, 189 | }) 190 | else 191 | core.register_craft({ 192 | type = "fuel", 193 | recipe = node_name, 194 | burntime = ropes.rope_burn_time * multiple, 195 | replacements = {{node_name, base_material}} 196 | }) 197 | end 198 | 199 | minetest.register_node(node_name, rope_block_def) 200 | 201 | if (multiple ~= 1) then 202 | -- Only register a recipe to craft this if it's within the permitted multiple range 203 | if multiple <= max_multiple then 204 | for i = 1, multiple-1 do 205 | local rec = {string.format("ropes:%s%irope_block", node_prefix, i)} 206 | for n = 1, multiple-i do 207 | table.insert(rec, "ropes:ropesegment") 208 | end 209 | minetest.register_craft({ 210 | output = node_name, 211 | type = "shapeless", 212 | recipe = rec 213 | }) 214 | end 215 | end 216 | 217 | -- Always allow players to disintegrate this into component parts, in case 218 | -- there were some in inventory and the setting was changed. 219 | minetest.register_craft({ 220 | output = "ropes:ropesegment", 221 | type = "shapeless", 222 | recipe = { 223 | node_name 224 | }, 225 | replacements = { 226 | {node_name, string.format('ropes:%s%irope_block', node_prefix, multiple-1)}, 227 | }, 228 | }) 229 | end 230 | 231 | if minetest.get_modpath("doc") then 232 | doc.add_entry_alias("nodes", "ropes:rope", "nodes", node_name) 233 | end 234 | end 235 | 236 | local rope_def = { 237 | description = S("Rope"), 238 | _doc_items_longdesc = ropes.doc.ropebox_longdesc, 239 | _doc_items_usagehelp = ropes.doc.ropebox_usage, 240 | walkable = false, 241 | climbable = true, 242 | sunlight_propagates = true, 243 | paramtype = "light", 244 | drop = "", 245 | tiles = { "ropes_3.png", "ropes_3.png", "ropes_3.png", "ropes_3.png", "ropes_5.png", "ropes_5.png" }, 246 | use_texture_alpha = "clip", 247 | is_ground_content = false, 248 | groups = {choppy=2, flammable=2, not_in_creative_inventory=1}, 249 | sounds = { 250 | footstep = {name = "ropes_creak", gain = 0.8, max_hear_distance = 6}, 251 | dig = "__group", 252 | dug = "__group", 253 | }, 254 | drawtype = "nodebox", 255 | node_box = { 256 | type = "connected", 257 | fixed = {-1/16, -1/2, -1/16, 1/16, 1/2, 1/16}, 258 | connect_top = {-1/16, 1/2, -1/16, 1/16, 3/4, 1/16} 259 | }, 260 | connects_to = {"group:rope_block"}, 261 | connect_sides = {"top"}, 262 | selection_box = { 263 | type = "fixed", 264 | fixed = {-1/8, -1/2, -1/8, 1/8, 1/2, 1/8}, 265 | }, 266 | after_destruct = function(pos) 267 | ropes.hanging_after_destruct(pos, "ropes:rope_top", "ropes:rope", "ropes:rope_bottom") 268 | end, 269 | } 270 | 271 | local rope_extension_timer = ropes.make_rope_on_timer("ropes:rope") 272 | 273 | local rope_bottom_def = { 274 | description = S("Rope"), 275 | _doc_items_create_entry = false, 276 | walkable = false, 277 | climbable = true, 278 | sunlight_propagates = true, 279 | paramtype = "light", 280 | drop = "", 281 | tiles = { "ropes_3.png", "ropes_3.png", "ropes_3.png", "ropes_3.png", "ropes_5.png", "ropes_5.png" }, 282 | use_texture_alpha = "clip", 283 | is_ground_content = false, 284 | drawtype = "nodebox", 285 | groups = {choppy=2, flammable=2, not_in_creative_inventory=1}, 286 | sounds = { 287 | footstep = {name = "ropes_creak", gain = 0.8, max_hear_distance = 6}, 288 | dig = "__group", 289 | dug = "__group", 290 | }, 291 | node_box = { 292 | type = "connected", 293 | fixed = { 294 | {-1/16, -3/8, -1/16, 1/16, 1/2, 1/16}, 295 | {-2/16, -5/16, -2/16, 2/16, -1/16, 2/16}, 296 | }, 297 | connect_top = {-1/16, 1/2, -1/16, 1/16, 3/4, 1/16} 298 | }, 299 | connects_to = {"group:rope_block"}, 300 | connect_sides = {"top"}, 301 | selection_box = { 302 | type = "fixed", 303 | fixed = {-1/8, -1/2, -1/8, 1/8, 1/2, 1/8}, 304 | }, 305 | 306 | on_construct = function( pos ) 307 | local timer = minetest.get_node_timer( pos ) 308 | timer:start( 1 ) 309 | end, 310 | 311 | on_timer = rope_extension_timer, 312 | 313 | after_destruct = function(pos) 314 | ropes.hanging_after_destruct(pos, "ropes:rope_top", "ropes:rope", "ropes:rope_bottom") 315 | end, 316 | } 317 | 318 | minetest.register_node("ropes:rope", rope_def) 319 | minetest.register_node("ropes:rope_bottom", rope_bottom_def) 320 | 321 | if ropes.woodRopeBoxMaxMultiple > 0 or ropes.create_all_definitions then 322 | if ropes.woodRopeBoxMaxMultiple > 0 then 323 | minetest.register_craft({ 324 | output = "ropes:wood1rope_block", 325 | recipe = { 326 | {'group:wood'}, 327 | {'group:vines'} 328 | } 329 | }) 330 | end 331 | for i = 1,9 do 332 | if ropes.woodRopeBoxMaxMultiple >= i or ropes.create_all_definitions then 333 | register_rope_block(i, ropes.woodRopeBoxMaxMultiple, S("Wood"), "wood", "#86683a", "group:wood", 2) 334 | end 335 | end 336 | end 337 | 338 | if ropes.copperRopeBoxMaxMultiple > 0 or ropes.create_all_definitions then 339 | if ropes.copperRopeBoxMaxMultiple > 0 then 340 | minetest.register_craft({ 341 | output = "ropes:copper1rope_block", 342 | recipe = { 343 | {'default:copper_ingot'}, 344 | {'group:vines'} 345 | } 346 | }) 347 | end 348 | for i = 1,9 do 349 | if ropes.copperRopeBoxMaxMultiple >= i or ropes.create_all_definitions then 350 | register_rope_block(i, ropes.copperRopeBoxMaxMultiple, S("Copper"), "copper", "#c88648", "default:copper_ingot") 351 | end 352 | end 353 | end 354 | 355 | if ropes.steelRopeBoxMaxMultiple > 0 or ropes.create_all_definitions then 356 | if ropes.steelRopeBoxMaxMultiple > 0 then 357 | minetest.register_craft({ 358 | output = "ropes:steel1rope_block", 359 | recipe = { 360 | {'default:steel_ingot'}, 361 | {'group:vines'} 362 | } 363 | }) 364 | end 365 | for i = 1,9 do 366 | if ropes.steelRopeBoxMaxMultiple >= i or ropes.create_all_definitions then 367 | register_rope_block(i, ropes.steelRopeBoxMaxMultiple, S("Steel"), "steel", "#ffffff", "default:steel_ingot") 368 | end 369 | end 370 | end 371 | -------------------------------------------------------------------------------- /ropeladder.lua: -------------------------------------------------------------------------------- 1 | if ropes.ropeLadderLength == 0 and not ropes.create_all_definitions then 2 | return 3 | end 4 | 5 | local S = ropes.S 6 | 7 | if ropes.ropeLadderLength > 0 then 8 | minetest.register_craft({ 9 | output = "ropes:ropeladder_top", 10 | recipe = { 11 | {'','group:stick',''}, 12 | {'group:vines','group:stick','group:vines'}, 13 | {'','group:stick',''}, 14 | } 15 | }) 16 | end 17 | 18 | minetest.register_craft({ 19 | type = "fuel", 20 | recipe = "ropes:ropeladder_top", 21 | burntime = ropes.ladder_burn_time, 22 | }) 23 | 24 | local rope_ladder_top_def = { 25 | description = S("Rope Ladder"), 26 | _doc_items_longdesc = ropes.doc.ropeladder_longdesc, 27 | _doc_items_usagehelp = ropes.doc.ropeladder_usage, 28 | drawtype = "signlike", 29 | tiles = {"default_ladder_wood.png^ropes_ropeladder_top.png"}, 30 | use_texture_alpha = "clip", 31 | is_ground_content = false, 32 | inventory_image = "default_ladder_wood.png^ropes_ropeladder_top.png", 33 | wield_image = "default_ladder_wood.png^ropes_ropeladder_top.png", 34 | paramtype = "light", 35 | paramtype2 = "wallmounted", 36 | walkable = false, 37 | climbable = true, 38 | sunlight_propagates = true, 39 | selection_box = { 40 | type = "wallmounted", 41 | --wall_top = = 42 | --wall_bottom = = 43 | --wall_side = = 44 | 45 | }, 46 | groups = { choppy=2, oddly_breakable_by_hand=1,flammable=2}, 47 | sounds = default.node_sound_wood_defaults(), 48 | 49 | on_place = function(itemstack, placer, pointed_thing) 50 | if pointed_thing.type == "node" then 51 | local target_node = minetest.get_node(pointed_thing.under) 52 | local target_def = minetest.registered_nodes[target_node.name] 53 | if target_def.walkable == false then 54 | return itemstack 55 | end 56 | end 57 | return minetest.item_place(itemstack, placer, pointed_thing) 58 | end, 59 | 60 | after_place_node = function(pos, placer) 61 | local pos_below = {x=pos.x, y=pos.y-1, z=pos.z} 62 | local node_below = minetest.get_node(pos_below) 63 | local this_node = minetest.get_node(pos) 64 | local placer_name = placer:get_player_name() 65 | -- param2 holds the facing direction of this node. If it's 0 or 1 the node is "flat" and we don't want the ladder to extend. 66 | if ropes.can_place_rope_in_node(node_below.name) and this_node.param2 > 1 67 | and (not minetest.is_protected(pos_below, placer_name) 68 | or minetest.check_player_privs(placer_name, "protection_bypass")) then 69 | minetest.add_node(pos_below, {name="ropes:ropeladder_bottom", param2=this_node.param2}) 70 | local meta = minetest.get_meta(pos_below) 71 | meta:set_int("length_remaining", ropes.ropeLadderLength) 72 | meta:set_string("placer", placer_name) 73 | end 74 | end, 75 | after_destruct = function(pos) 76 | local pos_below = {x=pos.x, y=pos.y-1, z=pos.z} 77 | ropes.destroy_rope(pos_below, {"ropes:ropeladder", "ropes:ropeladder_bottom", "ropes:ropeladder_falling"}) 78 | end, 79 | } 80 | 81 | if ropes.ropeLadderLength == 0 then 82 | rope_ladder_top_def.groups.not_in_creative_inventory = 1 83 | end 84 | 85 | minetest.register_node("ropes:ropeladder_top", rope_ladder_top_def) 86 | 87 | minetest.register_node("ropes:ropeladder", { 88 | description = S("Rope Ladder"), 89 | _doc_items_create_entry = false, 90 | drop = "", 91 | drawtype = "signlike", 92 | tiles = {"default_ladder_wood.png^ropes_ropeladder.png"}, 93 | use_texture_alpha = "clip", 94 | is_ground_content = false, 95 | inventory_image = "default_ladder_wood.png^ropes_ropeladder.png", 96 | wield_image = "default_ladder_wood.png^ropes_ropeladder.png", 97 | paramtype = "light", 98 | paramtype2 = "wallmounted", 99 | walkable = false, 100 | climbable = true, 101 | sunlight_propagates = true, 102 | selection_box = { 103 | type = "wallmounted", 104 | --wall_top = = 105 | --wall_bottom = = 106 | --wall_side = = 107 | }, 108 | groups = {choppy=2, flammable=2, not_in_creative_inventory=1}, 109 | sounds = default.node_sound_wood_defaults(), 110 | 111 | after_destruct = function(pos) 112 | ropes.hanging_after_destruct(pos, "ropes:ropeladder_falling", "ropes:ropeladder", "ropes:ropeladder_bottom") 113 | end, 114 | }) 115 | 116 | local ladder_extender = ropes.make_rope_on_timer("ropes:ropeladder") 117 | 118 | minetest.register_node("ropes:ropeladder_bottom", { 119 | description = S("Rope Ladder"), 120 | _doc_items_create_entry = false, 121 | drop = "", 122 | drawtype = "signlike", 123 | tiles = {"default_ladder_wood.png^ropes_ropeladder_bottom.png"}, 124 | use_texture_alpha = "clip", 125 | is_ground_content = false, 126 | inventory_image = "default_ladder_wood.png^ropes_ropeladder_bottom.png", 127 | wield_image = "default_ladder_wood.png^ropes_ropeladder_bottom.png", 128 | paramtype = "light", 129 | paramtype2 = "wallmounted", 130 | walkable = false, 131 | climbable = true, 132 | sunlight_propagates = true, 133 | selection_box = { 134 | type = "wallmounted", 135 | --wall_top = = 136 | --wall_bottom = = 137 | --wall_side = = 138 | 139 | }, 140 | groups = {choppy=2, flammable=2, not_in_creative_inventory=1}, 141 | sounds = default.node_sound_wood_defaults(), 142 | on_construct = function( pos ) 143 | local timer = minetest.get_node_timer( pos ) 144 | timer:start( 1 ) 145 | end, 146 | on_timer = ladder_extender, 147 | 148 | after_destruct = function(pos) 149 | ropes.hanging_after_destruct(pos, "ropes:ropeladder_falling", "ropes:ropeladder", "ropes:ropeladder_bottom") 150 | end, 151 | }) 152 | 153 | minetest.register_node("ropes:ropeladder_falling", { 154 | description = S("Rope Ladder"), 155 | _doc_items_create_entry = false, 156 | drop = "", 157 | drawtype = "signlike", 158 | tiles = {"default_ladder_wood.png^ropes_ropeladder.png"}, 159 | use_texture_alpha = "clip", 160 | is_ground_content = false, 161 | inventory_image = "default_ladder_wood.png^ropes_ropeladder.png", 162 | wield_image = "default_ladder_wood.png^ropes_ropeladder.png", 163 | paramtype = "light", 164 | paramtype2 = "wallmounted", 165 | walkable = false, 166 | climbable = true, 167 | sunlight_propagates = true, 168 | selection_box = { 169 | type = "wallmounted", 170 | --wall_top = = 171 | --wall_bottom = = 172 | --wall_side = = 173 | 174 | }, 175 | groups = {flammable=2, not_in_creative_inventory=1}, 176 | sounds = default.node_sound_wood_defaults(), 177 | on_construct = function( pos ) 178 | local timer = minetest.get_node_timer( pos ) 179 | timer:start( 1 ) 180 | end, 181 | on_timer = function( pos, elapsed ) 182 | local pos_below = {x=pos.x, y=pos.y-1, z=pos.z} 183 | local node_below = minetest.get_node(pos_below) 184 | 185 | if (node_below.name ~= "ignore") then 186 | ropes.destroy_rope(pos_below, {'ropes:ropeladder', 'ropes:ropeladder_bottom', 'ropes:ropeladder_falling'}) 187 | minetest.swap_node(pos, {name="air"}) 188 | else 189 | local timer = minetest.get_node_timer( pos ) 190 | timer:start( 1 ) 191 | end 192 | end 193 | }) 194 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/ropes/a06e94e4e22795a3fc33c1a1b5435be579942338/screenshot.png -------------------------------------------------------------------------------- /settingtypes.txt: -------------------------------------------------------------------------------- 1 | #The shortest rope will extend for this many meters. Longer ropes come in 2 | #multiples of this length. 3 | #Changing this value will not affect ropes that already exist in-world. 4 | ropes_rope_length (Rope length) int 50 1 30000 5 | 6 | #Rope ladders will extend this far at maximum. 7 | #Changing this value will not affect rope ladders that already exist in-world. 8 | #Setting this to 0 disables rope ladders. 9 | ropes_rope_ladder_length (Rope ladder length) int 50 0 30000 10 | 11 | #Sets the maximum length multiple wooden rope box permitted to be crafted. 12 | #So for example if the rope length is set to 50 and this is set to 2, 13 | #the longest possible wooden rope box a player can craft has 100 meters of rope. 14 | #Allowed values run from 0 to 9. 0 disables wood rope boxes. 15 | ropes_wood_rope_box_max_multiple (Maximum wood_rope box multiple) int 2 0 9 16 | 17 | #Sets the maximum length multiple copper rope box permitted to be crafted. 18 | #So for example if the rope length is set to 50 and this is set to 5, 19 | #the longest possible copper rope box a player can craft has 250 meters of rope. 20 | #Allowed values run from 0 to 9. 0 disables copper rope boxes. 21 | ropes_copper_rope_box_max_multiple (Maximum copper rope box multiple) int 5 0 9 22 | 23 | #Sets the maximum length multiple steel rope box permitted to be crafted. 24 | #So for example if the rope length is set to 50 and this is set to 9, 25 | #the longest possible steel rope box a player can craft has 450 meters of rope. 26 | #Allowed values run from 0 to 9. 0 disables steel rope boxes. 27 | ropes_steel_rope_box_max_multiple (Maximum steel rope box multiple) int 9 0 9 28 | 29 | #If this is set to true, then the mod will generate definitions for the rope boxes 30 | #that are otherwise not permitted by the settings above. These rope boxes 31 | #will not be craftable and will not be available in the creative inventory, 32 | #but they will continue to exist if they were placed "in world" and a player 33 | #can deconstruct them to retrieve their rope components. This setting is 34 | #intended for the situation where you have an established world and you want 35 | #to reduce the number of rope boxes available to players without turning 36 | #existing rope boxes into "unknown node"s. 37 | ropes_create_all_definitions (Create all rope box definitions) bool false 38 | 39 | #Extending ladders are capable of standing on their own, to a defined limit. 40 | #A ladder can extend to its unsupported limit before needing another node 41 | #behind it to provide a new point of support. Right-clicking on an existing 42 | #ladder with a stack of ladders will add new ladder segments to its top. 43 | ropes_extending_ladder_enabled (Enable extendable ladders) bool true 44 | 45 | #If extending ladders are enabled, this setting will cause them to replace 46 | #the default ladders entirely. 47 | ropes_replace_default_ladders (Replace default ladders with extendable ladders) bool false 48 | 49 | ropes_extending_wood_ladder_limit (Unsupported limit of wooden ladders) int 5 50 | ropes_extending_steel_ladder_limit (Unsupported limit of steel ladders) int 15 51 | 52 | #These nodes make it easier to build bridges by extending out away 53 | #from the player as they're placed 54 | ropes_bridges_enabled (Enable bridges) bool true 55 | 56 | #Allows ropes and rope ladders to extend into all "airlike" nodes. 57 | #Note that ropes will leave "air" nodes behind when destroyed. 58 | ropes_can_extend_into_airlike (Ropes can extend into all nodes with drawtype airlike) bool false -------------------------------------------------------------------------------- /sounds/license.txt: -------------------------------------------------------------------------------- 1 | ropes_creak.ogg - by jergonda from https://www.freesound.org/people/jergonda/sounds/254735/ under public domain via CC 0 -------------------------------------------------------------------------------- /sounds/ropes_creak.1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/ropes/a06e94e4e22795a3fc33c1a1b5435be579942338/sounds/ropes_creak.1.ogg -------------------------------------------------------------------------------- /sounds/ropes_creak.2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/ropes/a06e94e4e22795a3fc33c1a1b5435be579942338/sounds/ropes_creak.2.ogg -------------------------------------------------------------------------------- /sounds/ropes_creak.3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/ropes/a06e94e4e22795a3fc33c1a1b5435be579942338/sounds/ropes_creak.3.ogg -------------------------------------------------------------------------------- /textures/ropes_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/ropes/a06e94e4e22795a3fc33c1a1b5435be579942338/textures/ropes_1.png -------------------------------------------------------------------------------- /textures/ropes_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/ropes/a06e94e4e22795a3fc33c1a1b5435be579942338/textures/ropes_2.png -------------------------------------------------------------------------------- /textures/ropes_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/ropes/a06e94e4e22795a3fc33c1a1b5435be579942338/textures/ropes_3.png -------------------------------------------------------------------------------- /textures/ropes_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/ropes/a06e94e4e22795a3fc33c1a1b5435be579942338/textures/ropes_4.png -------------------------------------------------------------------------------- /textures/ropes_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/ropes/a06e94e4e22795a3fc33c1a1b5435be579942338/textures/ropes_5.png -------------------------------------------------------------------------------- /textures/ropes_item.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/ropes/a06e94e4e22795a3fc33c1a1b5435be579942338/textures/ropes_item.png -------------------------------------------------------------------------------- /textures/ropes_ropebox_front_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/ropes/a06e94e4e22795a3fc33c1a1b5435be579942338/textures/ropes_ropebox_front_1.png -------------------------------------------------------------------------------- /textures/ropes_ropebox_front_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/ropes/a06e94e4e22795a3fc33c1a1b5435be579942338/textures/ropes_ropebox_front_2.png -------------------------------------------------------------------------------- /textures/ropes_ropebox_front_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/ropes/a06e94e4e22795a3fc33c1a1b5435be579942338/textures/ropes_ropebox_front_3.png -------------------------------------------------------------------------------- /textures/ropes_ropebox_front_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/ropes/a06e94e4e22795a3fc33c1a1b5435be579942338/textures/ropes_ropebox_front_4.png -------------------------------------------------------------------------------- /textures/ropes_ropebox_front_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/ropes/a06e94e4e22795a3fc33c1a1b5435be579942338/textures/ropes_ropebox_front_5.png -------------------------------------------------------------------------------- /textures/ropes_ropebox_side.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/ropes/a06e94e4e22795a3fc33c1a1b5435be579942338/textures/ropes_ropebox_side.png -------------------------------------------------------------------------------- /textures/ropes_ropeladder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/ropes/a06e94e4e22795a3fc33c1a1b5435be579942338/textures/ropes_ropeladder.png -------------------------------------------------------------------------------- /textures/ropes_ropeladder_bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/ropes/a06e94e4e22795a3fc33c1a1b5435be579942338/textures/ropes_ropeladder_bottom.png -------------------------------------------------------------------------------- /textures/ropes_ropeladder_overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/ropes/a06e94e4e22795a3fc33c1a1b5435be579942338/textures/ropes_ropeladder_overlay.png -------------------------------------------------------------------------------- /textures/ropes_ropeladder_top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minetest-mods/ropes/a06e94e4e22795a3fc33c1a1b5435be579942338/textures/ropes_ropeladder_top.png --------------------------------------------------------------------------------