--[[ ITB (insidethebox) minetest game - Copyright (C) 2017-2018 sofar & nore This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ]]-- local S = minetest.get_translator("boxes") local FE = minetest.formspec_escape local callback = {} local function register_teleport(name, def) local function teleport_update_particles(pos, pname) local tm = math.random(10, 50) / 10 minetest.add_particlespawner({ amount = tm * 3, time = tm, minpos = {x = pos.x - 7/16, y = pos.y - 5/16, z = pos.z - 7/16}, maxpos = {x = pos.x + 7/16, y = pos.y - 5/16, z = pos.z + 7/16}, minvel = vector.new(-1, 2, -1), maxvel = vector.new(1, 5, 1), minacc = vector.new(0, -9.81, 0), maxacc = vector.new(0, -9.81, 0), --collisiondetection = true, --collision_removal = true, texture = def.tiles[1], playername = pname, }) minetest.get_node_timer(pos):start(tm) end def.drawtype = "nodebox" def.node_box = { type = "fixed", fixed = {{-7/16, -1/2, -7/16, 7/16, -7/16, 7/16}}, } def.paramtype = "light" def.groups = def.groups or {} def.sounds = def.sounds or sounds.metal def.on_construct = function(pos) teleport_update_particles(pos, nil) end def.on_timer = function(pos) teleport_update_particles(pos, nil) end def.after_box_construct = function(pos) teleport_update_particles(pos, nil) end -- on_teleport: return true to nudge -- player back off the teleport -- -- return true only when displaying a formspec or -- when the player is not teleported into a box local on_teleport = def.on_teleport def.on_walk_over = function(pos, node, player) if on_teleport(pos, node, player) then local dir = player:get_look_dir() dir.y = 0 player:set_pos(vector.subtract(pos, vector.normalize(dir))) end end minetest.register_node(name, def) end local function series_progress_reset(player, id) -- show a formspec to the user where they can explicitly reset -- series progress, and explain why they might not want to do so local name = player:get_player_name() --FIXME change this to a bunch of labels. fsc.show(name, "size[10,7]" .. -- TODO: Make translatable "textlist[0.5,0.5;8.7,4.5;restettext;You have already completed this series.,," .. "If you want\\, you can reset your progress for this series," .. "and start it again from the start.,," .. "Or\\, you can wait for more boxes to get added to this," .. "series later\\, and come back regularly to check if that," .. "has happened.;0;0]" .. "button[0.6,5.5;4.4,1.5;btn_cancel;".. FE(S("I'll come back later")).. "]" .. "button[5.0,5.5;4.4,1.5;btn_reset;".. FE(S("Reset progress")).. "]", id, callback.progress_reset ) end function callback.progress_reset(player, fields, context) local name = player:get_player_name() if not fields.btn_reset then return true end -- reset series progress local pmeta = db.player_get_meta(name) pmeta.series_progress[context] = nil db.player_set_meta(name, pmeta) minetest.chat_send_player(player:get_player_name(), S("Progress for series @1 was reset.", context)) return true end local function series_enter_choice(player, id, b_sel) -- Show a formspec allowing the user to choose what box they want to enter local name = player:get_player_name() local f = "size[6,9]" f = f .. "textlist[0.2,0.5;5.4,7.4;in_series;" local boxes = db.series_get_boxes(id) local player_id = db.player_get_id(name) local completed = db.player_get_series_boxes(player_id, id) local complete = {} for _, v in ipairs(completed) do complete[v] = true end for k, box_id in pairs(boxes) do local bmeta = db.box_get_meta(box_id) local bname = bmeta.meta.box_name or "[unnamed]" local color = "#c0c0c0" if complete[box_id] then color = "#00ff00" end f = f .. color f = f .. minetest.formspec_escape("[" .. box_id .. "] " .. bname) if k ~= #boxes then f = f .. "," end end f = f .. ";]" f = f .. "button[1.4,8.1;3.4,1;enter;"..FE(S("Play")).."]" fsc.show(name, f, {id = id, boxes = boxes, b_sel = b_sel}, callback.enter_choice) end function callback.enter_choice(player, fields, context) local name = player:get_player_name() if fields.in_series then local i = tonumber(string.match(fields.in_series, ":(%d+)", 1)) if i == nil then minetest.log("error", "callback.enter_choice: " .. dump(fields.in_series)) return end if i > #context.boxes or i < 1 then return true end series_enter_choice(player, context.id, i) return end if fields.enter then if not context.id or not context.b_sel then return true end local i = context.b_sel local id = context.id local pmeta = db.player_get_meta(name) pmeta.series_progress[id] = i db.player_set_meta(name, pmeta) boxes.next_series(player, id, true) return true end end register_teleport("boxes:enter_teleport", { description = S("Box enter teleporter"), tiles = {"diamond.png"}, on_teleport = function(pos, node, player) local meta = minetest.get_meta(pos) local id = meta:get_int("box") local is_series = meta:get_int("is_series") if is_series == 1 then local smeta = db.series_get_meta(id) if not smeta then return end if smeta.meta.type == db.SEQUENTIAL_TYPE then if not boxes.next_series(player, id, true) then series_progress_reset(player, id) return true end elseif smeta.meta.type == db.RANDOM_ACCESS_TYPE then series_enter_choice(player, id) return true end return elseif is_series == 2 then if not ranks or not ranks.categories then minetest.log("error", "ranks mod not found, can't use category teleport") return true end -- get the list of boxes for category (id) local catboxes = ranks.categories[tostring(id)] -- first, find first unplayed box in dynamic series local name = player:get_player_name() local player_id = db.player_get_id(name) local playerboxes = db.player_get_completed_boxes(player_id) for _, catbox in pairs(catboxes) do if not playerboxes[tonumber(catbox)] then local bmeta = db.box_get_meta(tonumber(catbox)) if bmeta.type ~= db.BOX_TYPE then return true end boxes.open_box(player, {0, tonumber(catbox), 1}) return end end -- fallback to a random one ? -- FIXME implement minetest.chat_send_player(name, S("You've completed every box in this category! Come back later to see if there are new boxes in it.") ) return true end local bmeta = db.box_get_meta(id) if bmeta.type ~= db.BOX_TYPE then return end boxes.open_box(player, {0, id, 1}) end, on_punch = function(pos, node, puncher, pointed_thing) if not puncher or not minetest.check_player_privs(puncher, "server") then return end local meta = minetest.get_meta(pos) if puncher:get_player_control().sneak then -- 0: a box portal -- 1: a numbered series portal -- 2: a category portal local is_series = meta:get_int("is_series") + 1 if is_series > 2 then is_series = 0 end meta:set_int("is_series", is_series) local smsg if is_series == 0 then smsg = S("Teleporter is a box portal.") elseif is_series == 1 then smsg = S("Teleporter is a numbered series portal.") else smsg = S("Teleporter is a category portal.") end minetest.chat_send_player(puncher:get_player_name(), smsg) return end local id = meta:get_int("box") local newid = id + 1 local is_series = meta:get_int("is_series") if is_series == 1 and not db.box_exists(newid) then newid = 0 end meta:set_int("box", newid) minetest.chat_send_player(puncher:get_player_name(), S("Destination set to @1.", newid)) end, on_rightclick = function(pos, node, puncher, pointed_thing) if not puncher or not minetest.check_player_privs(puncher, "server") then return end local meta = minetest.get_meta(pos) local id = meta:get_int("box") local newid = id - 1 local is_series = meta:get_int("is_series") if is_series == 1 and not db.box_exists(newid) then newid = db.get_last_box_id() end meta:set_int("box", newid) minetest.chat_send_player(puncher:get_player_name(), S("Destination set to @1.", newid)) end, }) register_teleport("boxes:exit_teleport", { description = S("Box exit teleporter"), tiles = {"diamond.png"}, on_teleport = function(pos, node, player) local name = player:get_player_name() if not boxes.players_in_boxes[name] then return end local bx = boxes.players_in_boxes[name] local stars = 0 for _, p in ipairs(bx.star_positions) do local star_node = minetest.get_node(p) local nodedef = minetest.registered_nodes[star_node.name] if nodedef and nodedef.groups and nodedef.groups.star and nodedef.groups.star > 0 then stars = stars + 1 end end local icons = {} local icon_ids = {} for _, p in ipairs(bx.category_positions) do local sign_node = minetest.get_node(p) local nodedef = minetest.registered_nodes[sign_node.name] if nodedef and nodedef.groups and nodedef.groups.icon and nodedef.groups.icon > 0 and nodedef.icon_name and nodedef.icon_name ~= "nil" then icons[#icons + 1] = nodedef.icon_name -- omit "nil" icon - nothing selected if nodedef.icon_id > 1 then icon_ids[#icon_ids + 1] = nodedef.icon_id end end end local ic = "" for i, iname in ipairs(icons) do if i > 1 then ic = ic .. ", " end ic = ic .. iname end -- record rating/icons if needed if stars > 0 then boxes.score(name, bx.box_id, "rating", {stars}) end if #icon_ids > 0 then boxes.score(name, bx.box_id, "category", icon_ids) end local sid = boxes.players_in_boxes[name].in_series boxes.close_box(player) if sid then boxes.next_series(player, sid) else players.return_to_lobby(player) end end, }) register_teleport("boxes:lobby_teleport", { description = S("Lobby teleporter"), tiles = {"blocks_tiles.png^[sheet:8x8:1,2"}, on_teleport = function(pos, node, player) local name = player:get_player_name() if not boxes.players_in_boxes[name] then return end boxes.close_box(player) players.return_to_lobby(player) end, }) local get_sizes = function(player) --FIXME link to scoring and limit box sizes for novice builders return {20, 25, 30, 35, 40} end local do_series_if = function(player, context) local name = player:get_player_name() assert(context) local f = "size[12,9]" if context.exc_sel then f = f .. "button[5.6,3;1,1;add;<<]" end if context.inc_sel then f = f .. "button[5.6,4;1,1;remove;>>]" end local showall = context.showall == "true" if showall then f = f .. "checkbox[6.5,1;showall;"..FE(S("Show all boxes"))..";true]" else f = f .. "checkbox[6.5,1;showall;"..FE(S("Show all boxes"))..";false]" end f = f .. "dropdown[0.5,1;5.45;series;" local series = db.series_get_series() local s_sel local i = 0 for k, v in pairs(series) do i = i + 1 f = f .. "{" .. v.id .. "} " .. minetest.formspec_escape(v.name) if k ~= #series then f = f .. "," end if context.series_id and context.series_id == v.id then s_sel = i end end if s_sel then f = f .. ";" .. s_sel .. "]" else f = f .. ";]" end local series_id = context.series_id if series_id then local smeta = db.series_get_meta(series_id) f = f .. "field[0.8,2.35;5.2,1;series_name;"..FE(S("Name"))..";" .. minetest.formspec_escape(smeta.name) .. "]" .. "field_close_on_enter[series_name;false]" f = f .. "dropdown[6.5,2;5.45;series_type;" .. ""..FE(S("Sequential"))..","..FE(S("Random access")).."" .. ";" .. (1 + smeta.meta.type) .. "]" f = f .. "textlist[0.5,3;5,6;list_included;" local boxes = db.series_get_boxes(series_id) context.inc = boxes for k, box_id in pairs(boxes) do local bmeta = db.box_get_meta(box_id) local bname = bmeta.meta.box_name or "[unnamed]" if bmeta.meta.status == db.STATUS_SUBMITTED then f = f .. "#FF8800" elseif bmeta.meta.status == db.STATUS_ACCEPTED then f = f .. "#00FF00" elseif bmeta.meta.builder ~= name then f = f .. "#FFFFAA" end f = f .. minetest.formspec_escape("[" .. box_id .. "] " .. bname) if k ~= #boxes then f = f .. "," end end f = f .. ";]" f = f .. "textlist[6.5,3;5,6;list_excluded;" local b_all if showall then -- list all boxes not in current series b_all = db.series_get_boxes_not_in(series_id) else -- list boxes not in any series (unreachable) b_all = db.series_get_boxes_not_in() end -- don't show non-accepted boxes here, at all local b = {} local b_size = 1 for k, box_id in ipairs(b_all) do local bmeta = db.box_get_meta(box_id) if bmeta.meta.status == db.STATUS_ACCEPTED then b[b_size] = box_id b_size = b_size + 1 end end context.exc = b for k, box_id in ipairs(b) do local bmeta = db.box_get_meta(box_id) if bmeta.meta.status == db.STATUS_SUBMITTED then f = f .. "#FF8800" elseif bmeta.meta.status == db.STATUS_ACCEPTED then f = f .. "#00FF00" elseif bmeta.meta.builder ~= name then f = f .. "#FFFFAA" end local bname = bmeta.meta.box_name or "[unnamed]" f = f .. minetest.formspec_escape("[" .. box_id .. "] " .. bname) if k ~= #b then f = f .. "," end end f = f .. ";]" end fsc.show(name, f, context, callback.series) end function callback.series(player, fields, context) local name = player:get_player_name() -- minimum required permissions if not minetest.check_player_privs(name, "server") then return true end if fields.showall then context.showall = fields.showall do_series_if(player, context) return end if fields.list_included then local i = tonumber(string.match(fields.list_included, ":(%d+)", 1)) context.inc_sel = context.inc[i] do_series_if(player, context) return end if fields.list_excluded then local i = tonumber(string.match(fields.list_excluded, ":(%d+)", 1)) context.exc_sel = context.exc[i] do_series_if(player, context) return end if fields.add then if context.exc_sel and context.series_id then db.series_add_at_end(context.series_id, context.exc_sel) minetest.log("action", name .. " added box " .. context.exc_sel .. " to series " .. context.series_id) minetest.chat_send_player(name, S("Added box @1 to series @2.", context.exc_sel, context.series_id)) context.exc_sel = nil end do_series_if(player, context) return end if fields.remove then if context.inc_sel and context.series_id then db.series_delete_box(context.series_id, context.inc_sel) minetest.log("action", name .. " removed box " .. context.inc_sel .. " from series " .. context.series_id) minetest.chat_send_player(name, S("Removed box @1 from series @2.", context.inc_sel, context.series_id)) context.inc_sel = nil end do_series_if(player, context) return end -- These three fields are dropdowns or text fields, and sent every time -- We need to handle every one of them if fields.series then context.series_id = tonumber(string.match(fields.series, "{(%d+)}", 1)) end if fields.series_type and not fields.key_enter_field then if context.series_id then local smeta = db.series_get_meta(context.series_id) local ntype = smeta.meta.type if fields.series_type == FE(S("Sequential")) then minetest.chat_send_player(name, S("Changing series @1 to type Sequential.", context.series_id)) minetest.log("action", name .. " changes series " .. context.series_id .. " to type Sequential") ntype = db.SEQUENTIAL_TYPE elseif fields.series_type == FE(S("Random access")) then minetest.chat_send_player(name, S("Changing series @1 to type Random.", context.series_id)) minetest.log("action", name .. " changes series " .. context.series_id .. " to type Random") ntype = db.RANDOM_ACCESS_TYPE end smeta.meta.type = ntype db.series_set_meta(context.series_id, smeta) end end if fields.series_name and fields.key_enter_field == "series_name" then if context.series_id then local smeta = db.series_get_meta(context.series_id) smeta.name = fields.series_name db.series_set_meta(context.series_id, smeta) minetest.chat_send_player(name, S("Renamed series @1 to \"@2\".", context.series_id, fields.series_name)) minetest.log("action", name .. " renamed series " .. context.series_id .. " to \"" .. fields.series_name .. "\"") end end if fields.series or fields.series_type or fields.series_name then do_series_if(player, context) return end return true end do_creator_if = function(player, context) local name = player:get_player_name() if not context.boxes or not context.sizes then context.boxes = boxes.get_player_boxes(name) context.sizes = get_sizes(name) end local counts = { accepted = 0, submitted = 0, editing = 0 } local f = "size[12, 8]" .. "field_close_on_enter[input;false]" .. "textlist[0.4,0.5;7.2,7.3;boxes;" --list of boxes owned, or all in case of admin for i, v in ipairs(context.boxes) do if i > 1 then f = f .. "," end local text = "" if v.status == db.STATUS_SUBMITTED then text = "#FF8800" counts.submitted = counts.submitted + 1 elseif v.status == db.STATUS_ACCEPTED then text = "#00FF00" counts.accepted = counts.accepted + 1 else if v.builder ~= name then text = "#FFFFAA" end counts.editing = counts.editing + 1 end text = text .. v.id .. " - " .. minetest.formspec_escape(v.name) if v.builder ~= name then text = text .. " (" .. minetest.formspec_escape(v.builder or "") .. ")" end f = f .. text end f = f .. "]" if minetest.check_player_privs(name, "server") then f = f .. "button[8.4,0;3.4,1;series;"..FE(S("Manage Series")).."]" end local limit = tonumber(player:get_attribute("box_create_limit") or "3") if (minetest.check_player_privs(name, "create") and counts.editing + counts.submitted <= limit) or minetest.check_player_privs(name, "server") then context.cancreate = true f = f .. "button[8.4,1;3.4,1;new;"..FE(S("Create new")).."]" elseif not context.limitreached then context.limitreached = true minetest.chat_send_player(name, S("You have too many unfinished boxes. Box creation will become available again once your boxes get accepted.")) end if context.box and context.box.status == db.STATUS_EDITING then f = f .. "button[8.4,3;3.4,1;submit;"..FE(S("Submit")).."]" elseif minetest.check_player_privs(name, "server") and context.box and context.box.status == db.STATUS_SUBMITTED then f = f .. "button[8.4,3;3.4,1;reject;"..FE(S("Reject")).."]" f = f .. "button[8.4,4;3.4,1;accept;"..FE(S("Accept")).."]" elseif context.box and context.box.status == db.STATUS_SUBMITTED then f = f .. "button[8.4,3;3.4,1;retract;"..FE(S("Retract")).."]" end if not context.box or context.box.status == db.STATUS_EDITING then f = f .. "button[8.4,7;3.4,1;edit;"..FE(S("Edit")).."]" elseif minetest.check_player_privs(name, "server") then f = f .. "button[8.4,7;3.4,1;edit;"..FE(S("Force edit")).."]" end if context.box then f = f .. "button[8.4,6;3.4,1;play;"..FE(S("Play")).."]" end fsc.show(name, f, context, callback.boxes_create) end local do_creator_size_if = function(player, context) local name = player:get_player_name() local f = "size[6.2, 6]" .. "field_close_on_enter[input;false]" .. "textlist[0.4,0.5;5.2,4.3;sizes;" for i, v in ipairs(context.sizes) do if i > 1 then f = f .. "," end f = f .. tostring(v) end f = f .. "]" .. "button[0.4,5;5.4,1;create;"..FE(S("Choose selected size")).."]" fsc.show(name, f, context, callback.boxes_create) end function callback.boxes_create(player, fields, context) local name = player:get_player_name() -- minimum required permissions if not minetest.check_player_privs(name, "create") and not minetest.check_player_privs(name, "server") then return true end local meta = nil -- makes reading the code below a bit easier local function box_set_status(id, status) if not meta then meta = db.box_get_meta(id) assert(meta.meta) end meta.meta.status = status db.box_set_meta(id, meta) end local function box_get_status(id) if not meta then meta = db.box_get_meta(id) assert(meta.meta) end return meta.meta.status end local is_admin = minetest.check_player_privs(name, "server") -- sadly a function, because meta may be nil local function is_builder(id) if not meta then meta = db.box_get_meta(id) assert(meta.meta) end return meta.meta.builder == name end if fields.boxes and fields.boxes ~= "INV" then local s, _ = fields.boxes:gsub(".*:(.*)", "%1") if not s then return true end local n = tonumber(s, 10) if n and context.boxes[n] then context.box = context.boxes[n] do_creator_if(player, context) return else return true end elseif fields.play and context.box then local id = context.box.id if is_admin or box_get_status(id) == db.STATUS_ACCEPTED or is_builder(id) then minetest.chat_send_player(name, S("Playing box @1", tostring(id))) minetest.registered_chatcommands["enter"].func(name, tostring(id)) end return true elseif fields.edit and context.box then local id = context.box.id if is_admin or (box_get_status(id) == db.STATUS_EDITING and is_builder(id)) then minetest.chat_send_player(name, S("Editing box @1", tostring(id))) minetest.registered_chatcommands["edite"].func(name, tostring(id)) end return true elseif fields.submit and context.box then local id = context.box.id if is_admin or (box_get_status(id) == db.STATUS_EDITING and is_builder(id)) then if not meta then meta = db.box_get_meta(id) end if meta.meta.builder == name and not meta.meta.tested then minetest.chat_send_player(name, S("Box @1 was not tested yet.\n" .. "You need to play through this box once before you are allowed to submit it.", tostring(id))) return true end minetest.chat_send_player(name, S("Submitting box @1", tostring(id))) announce.all(name .. " submits box " .. tostring(id)) announce.admins(name .. " submits box " .. tostring(id)) box_set_status(id, db.STATUS_SUBMITTED) end return true elseif fields.accept and context.box then local id = context.box.id if is_admin and box_get_status(id) == db.STATUS_SUBMITTED then minetest.chat_send_player(name, S("Accepting box @1", tostring(id))) announce.all(name .. " accepts box " .. tostring(id) .. "!") announce.admins(name .. " accepts box " .. tostring(id) .. "!") box_set_status(id, db.STATUS_ACCEPTED) -- grant perks to box builder local box_meta = db.box_get_meta(id) perks.grant(box_meta.meta.builder) end return true elseif fields.retract and context.box then local id = context.box.id if is_builder(id) and box_get_status(id) == db.STATUS_SUBMITTED then minetest.chat_send_player(name, S("Retracted box @1", tostring(id))) announce.all(name .. " retracts box " .. tostring(id)) announce.admins(name .. " retracts box " .. tostring(id)) box_set_status(id, db.STATUS_EDITING) end return true elseif fields.reject and context.box then local id = context.box.id if is_admin and box_get_status(id) == db.STATUS_SUBMITTED then minetest.chat_send_player(name, S("Rejecting box @1", tostring(id))) announce.all(name .. " rejects box " .. tostring(id)) announce.admins(name .. " rejects box " .. tostring(id)) box_set_status(id, db.STATUS_EDITING) end return true elseif fields.new and context.cancreate then do_creator_size_if(player, context) return elseif fields.series and is_admin then do_series_if(player, {}) return elseif fields.sizes then local s, _ = fields.sizes:gsub(".*:(.*)", "%1") context.size = context.sizes[tonumber(s, 10)] return elseif fields.create and context.size and context.cancreate then minetest.close_formspec(name, "") minetest.chat_send_player(name, S("Creating a new box with size @1", context.size)) minetest.log("action", name .. " creates a new box with size " .. context.size) boxes.make_new(player, context.size) return true end return true end register_teleport("boxes:creator_teleport", { description = S("Creator teleporter"), tiles = {"blocks_tiles.png^[sheet:8x8:2,2"}, on_teleport = function(pos, node, player) do_creator_if(player, {}) return true end, }) minetest.register_node("boxes:nexus", { description = S("Nexus"), drawtype = "mesh", mesh = "nexus.obj", paramtype = "light", paramtype2 = "facedir", light_source = 6, tiles = { { name = "nexus.png", animation = { type = "vertical_frames", aspect_w = 48, aspect_h = 16, length = 6.25, } }, }, selection_box = { type = "fixed", fixed = {-3/8, -3/8, -3/8, 3/8, 3/8, 3/8}, }, collision_box = { type = "fixed", fixed = {-3/8, -3/8, -3/8, 3/8, 3/8, 3/8}, }, group = {node = 0, nexus = 1}, node_placement_prediction = "", on_place = function(itemstack, placer, pointed_thing, param2) -- Can't be placed, will only trigger on_rightclick if defined if pointed_thing.type == "node" then local n = minetest.get_node(pointed_thing.under) local nn = n.name if minetest.registered_nodes[nn] and minetest.registered_nodes[nn].on_rightclick then return minetest.registered_nodes[nn].on_rightclick(pointed_thing.under, n, placer, itemstack, pointed_thing) or itemstack, false end end return itemstack end, on_trigger = function() end, on_untrigger = function() end, groups = {not_in_creative_inventory = 1}, }) minetest.register_node("boxes:nexus_large", { description = S("Mega nexus"), drawtype = "mesh", mesh = "nexus.obj", paramtype = "light", paramtype2 = "facedir", visual_scale = 4.0, wield_scale = { x=3.2, y=3.2, z=3.2 }, tiles = { { name = "nexus.png", animation = { type = "vertical_frames", aspect_w = 48, aspect_h = 16, length = 6.25, } }, }, selection_box = { type = "fixed", fixed = {-3/8, -3/8, -3/8, 3/8, 3/8, 3/8}, }, collision_box = { type = "fixed", fixed = {-3/8, -3/8, -3/8, 3/8, 3/8, 3/8}, }, group = {node = 0}, on_construct = function(pos) -- flip after 2 cycles minetest.get_node_timer(pos):start(12.5) end, on_timer = function(pos, node) -- make it self-rotate randomly minetest.swap_node(pos, {name = "boxes:nexus_large", param2 = math.random(24) - 1}) end, groups = {not_in_creative_inventory = 1}, }) minetest.register_craftitem("boxes:set_door", { description = S("Box door placement tool").."\n".. S("Place or move entry and exit doors and box number").."\n" .. S("Entry door must be on the west wall.").."\n" .. S("Exit door must be on the east wall.").."\n" .. S("Box number must be on the south wall."), inventory_image = "set_door_tool.png", on_place = function(itemstack, placer, pointed_thing) if pointed_thing.type ~= "node" or not placer then return end local name = placer:get_player_name() local pos = pointed_thing.under if not boxes.players_editing_boxes[name] then return end local box = boxes.players_editing_boxes[name] local pos1 local pos2 local dir local wallnode = {name = "nodes:marbleb"} if pos.x == box.minp.x then if pos.y <= box.minp.y or pos.y >= box.maxp.y - 2 or pos.z <= box.minp.z or pos.z >= box.maxp.z - 2 then return end pos1 = pos pos2 = {x = pos.x, y = pos.y, z = pos.z + 1} dir = 3 for y = box.entry.y, box.entry.y + 1 do for z = box.entry.z, box.entry.z + 1 do minetest.set_node({x = pos.x, y = y, z = z}, wallnode) end end box.entry = {x = pos.x, y = pos.y, z = pos.z} elseif pos.x == box.maxp.x then if pos.y <= box.minp.y or pos.y >= box.maxp.y - 2 or pos.z <= box.minp.z + 1 or pos.z >= box.maxp.z - 1 then return end pos1 = pos pos2 = {x = pos.x, y = pos.y, z = pos.z - 1} dir = 1 for y = box.exit.y, box.exit.y + 1 do for z = box.exit.z, box.exit.z + 1 do minetest.set_node({x = pos.x, y = y, z = z}, wallnode) end end box.exit = {x = pos.x + 1, y = pos.y, z = pos.z - 1} elseif pos.z == box.minp.z then pos.x = math.min(pos.x, box.maxp.x - ((string.len(box.box_id) * 4) - 1)) pos.y = math.min(pos.y, box.maxp.y - 5) boxes.move_digits(placer, pos) return else return end local door_name = "doors:door_steel" local state1 = 0 local state2 = 2 local meta1 = minetest.get_meta(pos1) local meta2 = minetest.get_meta(pos2) local above1 = vector.add(pos1, {x = 0, y = 1, z = 0}) local above2 = vector.add(pos2, {x = 0, y = 1, z = 0}) minetest.set_node(pos1, {name = door_name .. "_a", param2 = dir}) minetest.set_node(above1, {name = "doors:hidden", param2 = dir}) meta1:set_int("state", state1) meta1:set_string("doors_owner", "boxes:door owner") minetest.set_node(pos2, {name = door_name .. "_b", param2 = dir}) minetest.set_node(above2, {name = "doors:hidden", param2 = (dir + 3) % 4}) meta2:set_int("state", state2) meta2:set_string("doors_owner", "boxes:door owner") end, }) frame.register("boxes:set_door")