--[[ Tower Crane Mod =============== Copyright (C) 2017-2020 Joachim Stolberg LGPLv2.1+ See LICENSE.txt for more information Nodes Meta data +--------+ | | - last_known_pos as "(x,y,z)" | switch | - last_used | | - running +--------+ +--------+ | | - owner | base | - width | | - height +--------+ - dir as "(0,0,1)" ]]-- -- for lazy programmers local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end local S2P = minetest.string_to_pos -- crane minimum size local MIN_SIZE = 8 towercrane = {} towercrane.S = minetest.get_translator("towercrane") local S = towercrane.S local MP = minetest.get_modpath("towercrane") dofile(MP.."/config.lua") dofile(MP.."/control.lua") ------------------------------------------------------------------------------- -- Helper functions ------------------------------------------------------------------------------- local function chat(owner, text) if owner ~= nil then minetest.chat_send_player(owner, "[Tower Crane] "..text) end end local function formspec(height, width) local text = "" if height and width then text = height..","..width end return "size[5,4]".. "label[0,0;"..S("Construction area size").."]" .. "field[1,1.5;3,1;size;height,width;"..text.."]" .. "button_exit[1,2;2,1;exit;"..S("Build").."]" end local function get_node_lvm(pos) local node = minetest.get_node_or_nil(pos) if node then return node end local vm = minetest.get_voxel_manip() local MinEdge, MaxEdge = vm:read_from_map(pos, pos) local data = vm:get_data() local param2_data = vm:get_param2_data() local area = VoxelArea:new({MinEdge = MinEdge, MaxEdge = MaxEdge}) local idx = area:indexp(pos) node = { name = minetest.get_name_from_content_id(data[idx]), param2 = param2_data[idx] } return node end local function turnright(dir) local facedir = minetest.dir_to_facedir(dir) return minetest.facedir_to_dir((facedir + 1) % 4) end local function turnleft(dir) local facedir = minetest.dir_to_facedir(dir) return minetest.facedir_to_dir((facedir + 3) % 4) end -- pos is the base position local function is_crane_running(pos) local switch_pos = {x=pos.x, y=pos.y+1, z=pos.z} return towercrane.is_crane_running(switch_pos) end local function get_crane_data(pos) local meta = minetest.get_meta(pos) local dir = S2P(meta:get_string("dir")) local owner = meta:get_string("owner") local height = meta:get_int("height") local width = meta:get_int("width") if dir and height > 0 and width > 0 and owner ~= "" then return {dir = dir, height = height, width = width, owner = owner} end end -- generic function for contruction and removement local function crane_body_plan(pos, dir, height, width, clbk, tArg) pos.y = pos.y + 1 clbk(pos, "towercrane:mast_ctrl_off", tArg) for _ = 1,height+1 do pos.y = pos.y + 1 clbk(pos, "towercrane:mast", tArg) end pos.y = pos.y - 2 pos.x = pos.x - dir.x pos.z = pos.z - dir.z clbk(pos, "towercrane:arm2", tArg) pos.x = pos.x - dir.x pos.z = pos.z - dir.z clbk(pos, "towercrane:arm", tArg) pos.x = pos.x - dir.x pos.z = pos.z - dir.z clbk(pos, "towercrane:balance", tArg) pos.x = pos.x + 3 * dir.x pos.z = pos.z + 3 * dir.z for i = 1,width do pos.x = pos.x + dir.x pos.z = pos.z + dir.z if i % 2 == 0 then clbk(pos, "towercrane:arm2", tArg) else clbk(pos, "towercrane:arm", tArg) end end end -- Check space and protection for the crane local function check_space(pos, dir, height, width, owner) local check = function(pos, node_name, tArg) if minetest.get_node(pos).name ~= "air" then tArg.res = false elseif minetest.is_protected(pos, tArg.owner) then tArg.res = false end end local tArg = {res = true, owner = owner} crane_body_plan(table.copy(pos), dir, height, width, check, tArg) return tArg.res end local function construct_crane(pos, dir, height, width) local add = function(pos, node_name, tArg) minetest.add_node(pos, { name = node_name, param2 = minetest.dir_to_facedir(tArg.dir)}) end local tArg = {dir = dir} crane_body_plan(table.copy(pos), dir, height, width, add, tArg) end local function remove_crane(pos, dir, height, width) local remove = function(pos, node_name, tArg) local node = get_node_lvm(pos) if node.name == node_name or node.name == "towercrane:mast_ctrl_on" then minetest.remove_node(pos) end end crane_body_plan(table.copy(pos), dir, height, width, remove, {}) end -- pos is the base position local function is_my_crane(pos, player) if minetest.check_player_privs(player, "server") then return true end -- check protection local player_name = player and player:get_player_name() or "" if minetest.is_protected(pos, player_name) then return false end -- check owner local meta = minetest.get_meta(pos) if not meta or player_name ~= meta:get_string("owner") then return false end return true end -- Check user input (height, width) local function check_input(fields) local size = string.split(fields.size, ",") if #size == 2 then local height = tonumber(size[1]) local width = tonumber(size[2]) if height ~= nil and width ~= nil then height = math.max(height, MIN_SIZE) height = math.min(height, towercrane.max_height) width = math.max(width, MIN_SIZE) width = math.min(width, towercrane.max_width) return height, width end end return 0, 0 end -- pos is the base position function towercrane.get_crane_down(pos) local data = get_crane_data(pos) if data then remove_crane(pos, data.dir, data.height, data.width) local meta = minetest.get_meta(pos) meta:set_string("formspec", formspec(data.height, data.width)) end end local function build_crane_up(pos, owner, height, width) if height > 0 and width > 0 then local meta = minetest.get_meta(pos) local dir = S2P(meta:get_string("dir")) if dir then if check_space(pos, dir, height, width, owner) then construct_crane(pos, dir, height, width) meta:set_int("height", height) meta:set_int("width", width) meta:set_string("infotext", S("Owner")..": "..owner.. ", "..S("Crane size")..": "..height..","..width) meta:set_string("formspec", formspec(height, width)) else chat(owner, S("Area is protected or too less space for the crane!")) end end else chat(owner, S("Invalid input!")) end end ------------------------------------------------------------------------------- -- Nodes ------------------------------------------------------------------------------- minetest.register_node("towercrane:base", { description = S("Tower Crane Base"), inventory_image = "towercrane_invent.png", tiles = { "towercrane_base_top.png", "towercrane_base.png", "towercrane_base.png", "towercrane_base.png", "towercrane_base.png", "towercrane_base.png", }, paramtype = "light", paramtype2 = "facedir", sunlight_propagates = true, sounds = default.node_sound_metal_defaults(), is_ground_content = false, groups = {cracky=2}, -- set meta data (form for crane height and width, dir of the arm) after_place_node = function(pos, placer) local meta = minetest.get_meta(pos) local owner = placer:get_player_name() meta:set_string("owner", owner) meta:set_string("formspec", formspec()) local fdir = minetest.dir_to_facedir(placer:get_look_dir(), false) local dir = minetest.facedir_to_dir(fdir) meta:set_string("dir", P2S(dir)) end, -- evaluate user input (height, width), -- destroy old crane and build a new one with -- the given size on_receive_fields = function(pos, formname, fields, player) if fields.size == nil then return end if is_crane_running(pos) then return end if not is_my_crane(pos, player) then return end -- destroy old crane towercrane.get_crane_down(pos) -- evaluate user input and build new local height, width = check_input(fields) build_crane_up(pos, player:get_player_name(), height, width) end, can_dig = function(pos, player) if minetest.check_player_privs(player, "server") then return true end if is_crane_running(pos) then return false end if not is_my_crane(pos, player) then return false end return true end, on_destruct = function(pos) towercrane.get_crane_down(pos) end, }) minetest.register_node("towercrane:balance", { description = S("Tower Crane Balance"), tiles = { "towercrane_base.png", "towercrane_base.png", "towercrane_base.png", "towercrane_base.png", "towercrane_base.png", "towercrane_base.png", }, paramtype = "light", paramtype2 = "facedir", sunlight_propagates = true, is_ground_content = false, groups = {crumbly=0, not_in_creative_inventory=1}, }) minetest.register_node("towercrane:mast", { description = S("Tower Crane Mast"), drawtype = "glasslike_framed", tiles = { "towercrane_mast.png", "towercrane_mast.png", "towercrane_mast.png", "towercrane_mast.png", "towercrane_mast.png", "towercrane_mast.png", }, paramtype = "light", paramtype2 = "facedir", sunlight_propagates = true, is_ground_content = false, groups = {crumbly=0, not_in_creative_inventory=1}, }) minetest.register_node("towercrane:arm", { description = S("Tower Crane Arm"), drawtype = "glasslike_framed", tiles = { "towercrane_arm.png", "towercrane_arm.png", "towercrane_arm.png", "towercrane_arm.png", "towercrane_arm.png", "towercrane_arm.png", }, paramtype = "light", paramtype2 = "facedir", sunlight_propagates = true, is_ground_content = false, groups = {crumbly=0, not_in_creative_inventory=1}, }) minetest.register_node("towercrane:arm2", { description = S("Tower Crane Arm2"), drawtype = "glasslike_framed", tiles = { "towercrane_arm2.png", "towercrane_arm2.png", "towercrane_arm2.png", "towercrane_arm2.png", "towercrane_arm2.png", "towercrane_arm2.png", }, paramtype = "light", paramtype2 = "facedir", sunlight_propagates = true, is_ground_content = false, groups = {crumbly=0, not_in_creative_inventory=1}, }) if towercrane.recipe then minetest.register_craft({ output = "towercrane:base", recipe = { {"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"}, {"default:steel_ingot", "", ""}, {"default:steel_ingot", "dye:yellow", ""} } }) end ------------------------------------------------------------------------------- -- export ------------------------------------------------------------------------------- towercrane.turnright = turnright towercrane.turnleft = turnleft towercrane.is_my_crane = is_my_crane towercrane.get_crane_data = get_crane_data