boxes = {} local modpath = minetest.get_modpath(minetest.get_current_modname()) -- Box allocation dofile(modpath .. "/valloc.lua") -- Handling the data that encodes boxes dofile(modpath .. "/data.lua") -- Import/export boxes to files dofile(modpath .. "/io.lua") -- Handle inventory-related callbacks (i.e. whether the itemstacks -- should be decreased when placing nodes) dofile(modpath .. "/inv.lua") -- Nodes that have a box-related effect dofile(modpath .. "/nodes.lua") -- Set the region from minp to maxp to air function boxes.cleanup(minp, maxp) -- Clear any leftover entities local center = vector.divide(vector.add(minp, maxp), 2) local radius = vector.length(vector.subtract(vector.add(maxp, 1), minp)) / 2 for _, obj in ipairs(minetest.get_objects_inside_radius(center, radius)) do if not obj:is_player() then local pos = obj:get_pos() if minp.x - 0.5 <= pos.x and pos.x <= maxp.x + 0.5 and minp.y - 0.5 <= pos.y and pos.y <= maxp.y + 0.5 and minp.z - 0.5 <= pos.z and pos.z <= maxp.z + 0.5 then obj:remove() end end end local vm = minetest.get_voxel_manip(minp, maxp) local emin, emax = vm:get_emerged_area() local va = VoxelArea:new{MinEdge=emin,MaxEdge=emax} local vmdata = vm:get_data() local param2 = vm:get_param2_data() local cid = minetest.get_content_id("air") for z = minp.z, maxp.z do for y = minp.y, maxp.y do local index = va:index(minp.x, y, z) for x = minp.x, maxp.x do vmdata[index] = cid param2[index] = 0 index = index + 1 end end end vm:set_data(vmdata) vm:set_param2_data(param2) vm:update_liquids() vm:write_to_map() vm:update_map() end function vector.min(a, b) return { x = math.min(a.x, b.x), y = math.min(a.y, b.y), z = math.min(a.z, b.z), } end function vector.max(a, b) return { x = math.max(a.x, b.x), y = math.max(a.y, b.y), z = math.max(a.z, b.z), } end boxes.players_in_boxes = {} local function open_door(player, pos1, pos2, bminp, bmaxp) local minp = vector.min(pos1, pos2) local maxp = vector.max(pos1, pos2) local drs = {} local i = 1 for x = minp.x, maxp.x do for y = minp.y, maxp.y do for z = minp.z, maxp.z do local door = doors.get({x = x, y = y, z = z}) if door then door:open() drs[i] = {x = x, y = y, z = z} i = i + 1 end end end end local od = boxes.players_in_boxes[player:get_player_name()].open_doors od[#od + 1] = {minp = bminp, maxp = bmaxp, doors = drs, respawn = {x = maxp.x + 1, y = minp.y, z = (minp.z + maxp.z) / 2}} end local function handle_stats(stat, name, new_score, num_total) if stat.player_best[name] then local old = stat.player_best[name] if new_score < old then stat.average_best = stat.average_best + (new_score - old) / num_total stat.player_best[name] = new_score stat.overall_best = math.min(stat.overall_best, new_score) end else stat.player_best[name] = new_score stat.average_first = (num_total * stat.average_first + new_score) / (num_total + 1) stat.average_best = (num_total * stat.average_best + new_score) / (num_total + 1) stat.ovarall_best = math.min(stat.overall_best, new_score) end end function boxes.player_success(player) local name = player:get_player_name() local box = boxes.players_in_boxes[name] local id = box.box_id local time_taken = minetest.get_gametime() - box.start_time local deaths = box.deaths local damage = box.damage local bmeta = db.box_get_meta(id) local k = bmeta.meta.num_completed_players handle_stats(bmeta.meta.time, name, time_taken, k) handle_stats(bmeta.meta.deaths, name, deaths, k) handle_stats(bmeta.meta.damage, name, damage, k) if not bmeta.meta.completed_players[name] then bmeta.meta.completed_players[name] = true bmeta.meta.num_completed_players = k + 1 end db.box_set_meta(id, bmeta) for _, p in ipairs(box.signs_to_update) do local node = minetest.get_node(p) if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].on_exit_update then minetest.registered_nodes[node.name].on_exit_update(p, player, { time_taken = {format = "time", data = time_taken}, damage_taken = {format = "text", data = tostring(damage)}, num_deaths = {format = "text", data = tostring(deaths)}, }) end end local sid = box.in_series if sid then local pmeta = db.player_get_meta(name) local index = pmeta.series_progress[sid] or 1 pmeta.series_progress[sid] = index + 1 db.player_set_meta(name, pmeta) end end function boxes.open_exit(player) local name = player:get_player_name() local box = boxes.players_in_boxes[name] local exds = box.exit_doors local n = #exds if n == 0 then return end local ex = exds[n] exds[n] = nil open_door(player, ex.door_pos, vector.add(ex.door_pos, {x = -1, y = 0, z = 1}), ex.box_minp, ex.box_maxp) end function boxes.increase_items(player) local name = player:get_player_name() if not boxes.players_in_boxes[name] then return end local exds = boxes.players_in_boxes[name].exit_doors local n = #exds if n == 0 then return end local ex = exds[n] ex.door_items = ex.door_items - 1 if ex.door_items == 0 then boxes.open_exit(player) end end -- Close doors behind players minetest.register_globalstep(function(dtime) for playername, info in pairs(boxes.players_in_boxes) do local i = 1 local player = minetest.get_player_by_name(playername) local pos = player:get_pos() local odoors = info.open_doors local n = #odoors while odoors[i] do local off = 0.7 if odoors[i].minp.x + off <= pos.x and pos.x <= odoors[i].maxp.x - off and odoors[i].minp.y <= pos.y and pos.y <= odoors[i].maxp.y and odoors[i].minp.z + off <= pos.z and pos.z <= odoors[i].maxp.z - off then for _, dpos in ipairs(odoors[i].doors) do local door = doors.get(dpos) if door then door:close() end end info.respawn = odoors[i].respawn odoors[i] = odoors[n] odoors[n] = nil n = n - 1 if not info.start_time then info.start_time = minetest.get_gametime() info.deaths = 0 info.damage = 0 music.start(player, info, "box") else -- We closed the last door: the player got out! boxes.player_success(player) minetest.chat_send_all(playername .. " completed box " .. info.box_id .. "!") music.start(player, info, "exit") end else i = i + 1 end end end end) -- Although this functions takes a list as an argument, -- several parts of the code rely on it having length exactly -- 3 and being entry lobby, box, exit function boxes.open_box(player, box_id_list) local name = player:get_player_name() if boxes.players_in_boxes[name] ~= nil or boxes.players_editing_boxes[name] ~= nil then return end local offset = {x = 0, y = 0, z = 0} local pmin = {x = 0, y = 0, z = 0} local pmax = {x = 0, y = 0, z = 0} local metas = {} local offsets = {} for i, box in ipairs(box_id_list) do local meta = db.box_get_meta(box).meta metas[i] = meta offset = vector.subtract(offset, meta.entry) pmin = vector.min(pmin, offset) pmax = vector.max(pmax, vector.add(offset, meta.size)) offsets[i] = offset offset = vector.add(offset, meta.exit) end for i, off in ipairs(offsets) do offsets[i] = vector.subtract(off, pmin) end pmax = vector.subtract(pmax, pmin) local size = math.max(pmax.x, pmax.z) local minp = boxes.valloc(size) local maxp = vector.add(minp, vector.subtract(pmax, 1)) local exit_doors = {} local n = #box_id_list for i = 1, n - 1 do local exd = vector.add(minp, vector.add(metas[n - i].exit, offsets[n - i])) exit_doors[i] = { door_items = metas[n - i].num_items, door_pos = exd, box_minp = vector.add(minp, offsets[n - i + 1]), box_maxp = vector.add(minp, vector.add(offsets[n - i + 1], vector.subtract(metas[n - i + 1].size, 1))), } end local spawn_pos = vector.add(minp, vector.add(metas[1].entry, offsets[1])) spawn_pos.y = spawn_pos.y - 0.5 local signs_to_update = {} for i, p in ipairs(metas[3].signs_to_update) do signs_to_update[i] = vector.add(p, vector.add(minp, offsets[3])) end boxes.players_in_boxes[name] = { box_id = box_id_list[2], minp = minp, maxp = maxp, exit_doors = exit_doors, exit = vector.add(minp, vector.add(metas[n].exit, offsets[n])), open_doors = {}, respawn = spawn_pos, signs_to_update = signs_to_update, } for i, box in ipairs(box_id_list) do boxes.load(vector.add(minp, offsets[i]), box, player) end player:set_physics_override({gravity = 0, jump = 0}) player:set_pos(spawn_pos) minetest.after(0.5, function() player:set_physics_override({gravity = 1, jump = 1}) end) player:set_look_horizontal(3 * math.pi / 2) player:set_look_vertical(0) players.give_box_inventory(player) skybox.set(player, metas[n - 1].skybox) boxes.open_exit(player) end function boxes.close_box(player) local name = player:get_player_name() if boxes.players_in_boxes[name] == nil then return end local bx = boxes.players_in_boxes[name] boxes.cleanup(bx.minp, bx.maxp) boxes.vfree(bx.minp) boxes.players_in_boxes[name] = nil end function boxes.next_series(player, sid) local name = player:get_player_name() local pmeta = db.player_get_meta(name) local index = pmeta.series_progress[sid] or 1 local bxs = db.series_get_boxes(sid) if not bxs[index] then players.return_to_lobby(player) return false else boxes.open_box(player, {0, bxs[index], 1}) boxes.players_in_boxes[name].in_series = sid return true end end boxes.players_editing_boxes = {} minetest.register_chatcommand("enter", { params = "", description = "Enter box with this id", -- privs = {server = true}, func = function(name, param) local player = minetest.get_player_by_name(name) if boxes.players_in_boxes[name] or boxes.players_editing_boxes[name] then minetest.chat_send_player(name, "You are already in a box!") return end local id = tonumber(param) if not id or id ~= math.floor(id) or id < 0 then minetest.chat_send_player(name, "The id you supplied is not a nonnegative interger.") return end local meta = db.box_get_meta(id) if not meta or meta.type ~= db.BOX_TYPE then minetest.chat_send_player(name, "The id you supplied does not correspond to any box.") return end boxes.open_box(player, {0, id, 1}) end, }) minetest.register_chatcommand("series_create", { params = "", description = "Create a new series", privs = {server = true}, func = function(name, param) if param == "" then minetest.chat_send_player(name, "Please privide a name for the series") return end local id = db.series_create(param) if id then minetest.chat_send_player(name, "Series successfully created with id " .. id) else minetest.chat_send_player(name, "Series failed to create") end end }) minetest.register_chatcommand("series_destroy", { params = "", description = "Destroy a series", privs = {server = true}, func = function(name, param) if param == "" then minetest.chat_send_player(name, "Please privide a series id to destroy") return end local id = tonumber(param) if not db.series_destroy(id) then minetest.chat_send_player(name, "Failed to destroy series " .. id) else minetest.chat_send_player(name, "Destroyed series " .. id) end end }) minetest.register_chatcommand("series_add", { params = " ", description = "Add a box a series", privs = {server = true}, func = function(name, param) local sid, bid = string.match(param, "^([^ ]+) +(.+)$") sid = tonumber(sid) bid = tonumber(bid) if not sid or not bid or not db.series_get_meta(sid) or not db.box_get_meta(bid) then minetest.chat_send_player(name, "Box or series doesn't exist.") return end db.series_add_at_end(sid, bid) minetest.chat_send_player(name, "Done") end }) minetest.register_chatcommand("series_list", { params = "", description = "List defined series", privs = {server = true}, func = function(name, param) if param ~= "" then -- list boxes in this series. local sid = tonumber(param) if not sid or not db.series_get_meta(sid) then minetest.chat_send_player(name, "Series doesn't exist.") return end for _, v in ipairs(db.series_get_boxes(sid)) do minetest.chat_send_player(name, v) end else -- list all series for k, v in ipairs(db.series_get_series()) do minetest.chat_send_player(name, v.id .. " - " .. v.name) end end end }) minetest.register_chatcommand("series_remove", { params = " ", description = "Remove a box from specified series", privs = {server = true}, func = function(name, param) local sid, bid = string.match(param, "^([^ ]+) +(.+)$") sid = tonumber(sid) bid = tonumber(bid) if not sid or not bid or not db.series_get_meta(sid) or not db.box_get_meta(bid) then minetest.chat_send_player(name, "Box or series doesn't exist.") return end db.series_delete_box(sid, bid) minetest.chat_send_player(name, "Done") end }) minetest.register_chatcommand("leave", { params = "", description = "Leave the current box", privs = {server = true}, func = function(name, param) if not boxes.players_in_boxes[name] then minetest.chat_send_player(name, "You are not in a box!") return end local player = minetest.get_player_by_name(name) boxes.close_box(player) players.return_to_lobby(player) end, }) minetest.register_chatcommand("open", { params = "", description = "Open the current exit.", privs = {server = true}, func = function(name, param) local box = boxes.players_in_boxes[name] if not box then minetest.chat_send_player(name, "You are not in a box!") return end if box.exit_doors[1] == nil then minetest.chat_send_player(name, "There are no more exits to open!") return end if box.open_doors[1] ~= nil then minetest.chat_send_player(name, "This box already has an open door!") return end local player = minetest.get_player_by_name(name) boxes.open_exit(player) end, }) -- This is only still needed if we want to change the size, the entry, or the -- exit of the lobbies; changing the contents can be done by /edite. local lobby_updates = {} minetest.register_chatcommand("update_lobby", { params = "entry|exit", description = "Set corresponding lobby. Use without parameter to start \ updating a lobby, then punch both corners of the lobby and the bottom node \ of the exit. In the case of the entry lobby, stand at its spawnpoint to run \ the command.", privs = {server = true}, func = function(name, param) if param ~= "entry" and param ~= "exit" and param ~= "" then return end if param == "" then lobby_updates[name] = {} elseif not lobby_updates[name] then minetest.chat_send_player(name, "Not all positions have been set.") return else local pos1 = lobby_updates[name].pos1 local pos2 = lobby_updates[name].pos2 local pos3 = lobby_updates[name].pos3 if not pos1 or not pos2 or not pos3 then minetest.chat_send_player(name, "Not all positions have been set.") return end local minp = vector.min(pos1, pos2) local maxp = vector.max(pos1, pos2) local data = boxes.save(minp, maxp) local p3 = vector.subtract(pos3, minp) if param == "exit" then local player = minetest.get_player_by_name(name) local exit = vector.subtract(vector.round(player:get_pos()), minp) -- Exit lobby is hardcoded as id 1 for now db.box_set_data(1, data) db.box_set_meta(1, { type = db.EXIT_TYPE, meta = { size = vector.add(vector.subtract(maxp, minp), 1), entry = p3, exit = exit, } }) else local player = minetest.get_player_by_name(name) local entry = vector.subtract(vector.round(player:get_pos()), minp) p3.x = p3.x + 1 -- Entry lobby is hardcoded as id 0 for now db.box_set_data(0, data) db.box_set_meta(0, { type = db.ENTRY_TYPE, meta = { size = vector.add(vector.subtract(maxp, minp), 1), entry = entry, exit = p3, } }) end minetest.chat_send_player(name, "Updated.") lobby_updates[name] = nil end end, }) minetest.register_on_punchnode(function(pos, node, puncher, pointed_thing) if not puncher then return end local name = puncher:get_player_name() if not lobby_updates[name] then return end if not lobby_updates[name].pos1 then lobby_updates[name].pos1 = pos minetest.chat_send_player(name, "Position 1 set to " .. dump(pos) .. ".") elseif not lobby_updates[name].pos2 then lobby_updates[name].pos2 = pos minetest.chat_send_player(name, "Position 2 set to " .. dump(pos) .. ".") elseif not lobby_updates[name].pos3 then lobby_updates[name].pos3 = pos minetest.chat_send_player(name, "Position 3 set to " .. dump(pos) .. ".") end end) local digits = {[0] = { true, true, true, true, false, true, true, false, true, true, false, true, true, true, true, }, {false, false, true, false, false, true, false, false, true, false, false, true, false, false, true, }, { true, true, true, false, false, true, true, true, true, true, false, false, true, true, true, }, { true, true, true, false, false, true, true, true, true, false, false, true, true, true, true, }, { true, false, true, true, false, true, true, true, true, false, false, true, false, false, true, }, { true, true, true, true, false, false, true, true, true, false, false, true, true, true, true, }, { true, true, true, true, false, false, true, true, true, true, false, true, true, true, true, }, { true, true, true, false, false, true, false, false, true, false, false, true, false, false, true, }, { true, true, true, true, false, true, true, true, true, true, false, true, true, true, true, }, { true, true, true, true, false, true, true, true, true, false, false, true, true, true, true, }, } function boxes.make_new(player, size) local minp = boxes.valloc(size + 2) local maxp = vector.add(minp, size + 1) -- Create the box local cid_air = minetest.get_content_id("air") local cid_wall = minetest.get_content_id("nodes:marbleb") local cid_digit = minetest.get_content_id("nodes:bronzeb") local cid_barrier = minetest.get_content_id("nodes:barrier") local vm = minetest.get_voxel_manip(minp, maxp) local emin, emax = vm:get_emerged_area() local va = VoxelArea:new{MinEdge=emin,MaxEdge=emax} local vmdata = vm:get_data() local param2 = vm:get_param2_data() -- Set to air for z = minp.z, maxp.z do for y = minp.y, maxp.y do local index = va:index(minp.x, y, z) for x = minp.x, maxp.x do vmdata[index] = cid_air param2[index] = 0 index = index + 1 end end end -- Add stone for walls and barrier at the top for z = minp.z, maxp.z do local index = va:index(minp.x, minp.y, z) local index2 = va:index(minp.x, maxp.y, z) for x = minp.x, maxp.x do vmdata[index] = cid_wall vmdata[index2] = cid_barrier index = index + 1 index2 = index2 + 1 end end for y = minp.y, maxp.y do local index = va:index(minp.x, y, minp.z) local index2 = va:index(minp.x, y, maxp.z) for x = minp.x, maxp.x do vmdata[index] = cid_wall vmdata[index2] = cid_wall index = index + 1 index2 = index2 + 1 end end local ystride = emax.x - emin.x + 1 for z = minp.z, maxp.z do local index = va:index(minp.x, minp.y, z) local index2 = va:index(maxp.x, minp.y, z) for y = minp.y, maxp.y do vmdata[index] = cid_wall vmdata[index2] = cid_wall index = index + ystride index2 = index2 + ystride end end local box_id = db.get_last_box_id() + 1 -- Write the box id local id_string = tostring(box_id) local id_sz = 4 * string.len(id_string) - 1 if size < 6 or size < id_sz + 2 then minetest.log("error", "boxes.make_new(" .. size .. "): box size too small") else local xoff = minp.x + math.floor((size + 2 - id_sz) / 2) local yoff = minp.y + math.floor((size + 2 - 5 + 1) / 2) local n = string.len(id_string) for i = 1, string.len(id_string) do for dx = 0, 2 do for dy = 0, 4 do if digits[string.byte(id_string, n - i + 1) - 48][3-dx+3*(4-dy)] then local index = va:index(xoff + dx, yoff + dy, minp.z) vmdata[index] = cid_digit end end end xoff = xoff + 4 end end vm:set_data(vmdata) vm:set_param2_data(param2) vm:update_liquids() vm:write_to_map() vm:update_map() local s2 = math.floor(size / 2) local entry = {x = 0, y = 1, z = s2 + 1} local exit = {x = size + 2, y = 1, z = s2 + 1} local sz = {x = size + 2, y = size + 2, z = size + 2} local meta = { type = db.BOX_TYPE, meta = { entry = entry, exit = exit, size = sz, num_items = 0, box_name = "(No name)", builder = player:get_player_name(), build_time = 0, skybox = 0, } } player:set_pos(vector.add(minp, {x = 1, y = 1, z = s2 + 1})) players.give_edit_inventory(player) db.box_set_meta(box_id, meta) db.box_set_data(box_id, boxes.save(minp, maxp)) boxes.players_editing_boxes[player:get_player_name()] = { box_id = box_id, minp = minp, maxp = maxp, num_items = 0, entry = vector.add(minp, entry), exit = vector.add(minp, exit), start_edit_time = minetest.get_gametime(), box_name = "", skybox = 0, } end minetest.register_privilege("create", "Can create new boxes") minetest.register_chatcommand("create", { params = "", description = "Start editing a new box.", privs = {create = true}, func = function(name, param) if boxes.players_editing_boxes[name] or boxes.players_in_boxes[name] then minetest.chat_send_player(name, "You are already editing a box!") return end local size = tonumber(param) if not size or size ~= math.floor(size) or size <= 0 then minetest.chat_send_player(name, "Please specify a size.") return end if size < 20 or size > 40 then minetest.chat_send_player(name, "Size is not in the allowed range [20, 40]") return end boxes.make_new(minetest.get_player_by_name(name), size) end, }) minetest.register_chatcommand("edit", { params = "", description = "DEPRECATED", privs = {server = true}, func = function(name, param) minetest.chat_send_player(name, "Did you mean /create or /edite?") end, }) minetest.register_chatcommand("edite", { params = "", description = "Edit an existing box.", privs = {create = true}, func = function(name, param) if boxes.players_editing_boxes[name] or boxes.players_in_boxes[name] then minetest.chat_send_player(name, "You are already editing a box!") return end local id = tonumber(param) if not id or id ~= math.floor(id) or id < 0 then minetest.chat_send_player(name, "The id you supplied is not a nonnegative integer.") return end local meta = db.box_get_meta(id) if not meta then minetest.chat_send_player(name, "The id you supplied is not in the database.") return end if meta.meta.builder ~= name and not minetest.check_player_privs(name, "server") then minetest.chat_send_player(name, "You are not allowed to edit this box.") return end local player = minetest.get_player_by_name(name) local minp = boxes.valloc(math.max(meta.meta.size.x, meta.meta.size.z)) local maxp = vector.add(minp, vector.subtract(meta.meta.size, 1)) boxes.players_editing_boxes[name] = { box_id = id, minp = minp, maxp = maxp, num_items = meta.meta.num_items, entry = vector.add(minp, meta.meta.entry), exit = vector.add(minp, meta.meta.exit), start_edit_time = minetest.get_gametime(), box_name = meta.meta.box_name, skybox = meta.meta.skybox, } boxes.load(minp, id, player) local spawnpoint = vector.add({x = 1, y = 0, z = 0}, vector.add(minp, meta.meta.entry)) player:set_pos(spawnpoint) players.give_edit_inventory(player) end, }) function boxes.save_edit(player, id) local name = player:get_player_name() local box = boxes.players_editing_boxes[name] if not box then return end local bmeta = db.box_get_meta(box.box_id) if id == nil then id = box.box_id end db.box_set_data(id, boxes.save(box.minp, box.maxp)) bmeta.meta.num_items = box.num_items bmeta.meta.entry = vector.subtract(box.entry, box.minp) bmeta.meta.exit = vector.subtract(box.exit, box.minp) bmeta.meta.box_name = box.box_name if box.box_name == "" then bmeta.meta.box_name = "(No name)" end bmeta.meta.skybox = box.skybox local t = minetest.get_gametime() if name == bmeta.meta.builder then -- Do not count admin edit times bmeta.meta.build_time = bmeta.meta.build_time + t - box.start_edit_time box.start_edit_time = t end db.box_set_meta(id, bmeta) minetest.log("action", name .. " saved box " .. id) minetest.chat_send_player(name, "Box " .. id .. " saved successfully") end function boxes.stop_edit(player) local name = player:get_player_name() local box = boxes.players_editing_boxes[name] if not box then return end boxes.cleanup(box.minp, box.maxp) boxes.vfree(box.minp) boxes.players_editing_boxes[name] = nil end minetest.register_chatcommand("setbuilder", { params = " ", description = "Set the builder of the box to .", privs = {server = true}, func = function(name, param) local box_id, nname = string.match(param, "^([^ ]+) +(.+)$") box_id = tonumber(box_id) local bmeta = db.box_get_meta(box_id) if not bmeta then minetest.chat_send_player(name, "Supplied box id does not exist!") return end if bmeta.type ~= db.BOX_TYPE then minetest.chat_send_player(name, "This id does not correspond to a box.") return end bmeta.meta.builder = nname db.box_set_meta(box_id, bmeta) minetest.chat_send_player(name, "Done.") end, }) minetest.register_chatcommand("save", { params = "[]", description = "Save the box you are currently editing. If id is supplied, a copy is" .. "instead saved to the box numbered id.", privs = {server = true}, func = function(name, param) if not boxes.players_editing_boxes[name] then minetest.chat_send_player(name, "You are not currently editing a box!") return end local box_id = nil if param ~= "" then local id = tonumber(param) if not id or id ~= math.floor(id) or id < 0 then minetest.chat_send_player(name, "The id you supplied is not a non-negative number.") return end box_id = id end boxes.save_edit(minetest.get_player_by_name(name), box_id) end, }) minetest.register_chatcommand("stopedit", { params = "", description = "Stop editing a box.", privs = {server = true}, func = function(name, param) if not boxes.players_editing_boxes[name] then minetest.chat_send_player(name, "You are not currently editing a box!") return end local player = minetest.get_player_by_name(name) boxes.save_edit(player) boxes.stop_edit(player) players.return_to_lobby(player) end, }) local function on_leaveplayer(player) minetest.log("action", player:get_player_name() .. " left the game") boxes.close_box(player) boxes.save_edit(player) boxes.stop_edit(player) end minetest.register_on_leaveplayer(on_leaveplayer) minetest.register_on_shutdown(function() for _, player in ipairs(minetest.get_connected_players()) do on_leaveplayer(player) end db.shutdown() end) minetest.register_on_respawnplayer(function(player) local name = player:get_player_name() if boxes.players_in_boxes[name] then local box = boxes.players_in_boxes[name] player:setpos(box.respawn) elseif boxes.players_editing_boxes[name] then local box = boxes.players_editing_boxes[name] player:setpos(vector.add(box.entry, {x = 1, y = 0, z = 0})) else players.return_to_lobby(player) end return true end) minetest.register_on_dieplayer(function(player) local name = player:get_player_name() local box = boxes.players_in_boxes[name] if box and box.deaths then box.deaths = box.deaths + 1 end end) minetest.register_on_player_hpchange(function(player, hp_change) local name = player:get_player_name() local box = boxes.players_in_boxes[name] if box and box.damage and hp_change < 0 then box.damage = box.damage - hp_change end end, false) function boxes.can_edit(player) local name = player:get_player_name() if boxes.players_editing_boxes[name] then return true end if boxes.players_in_boxes[name] then return false end return minetest.check_player_privs(name, "server") end local metadata_updates = {} minetest.register_on_punchnode(function(pos, node, puncher, pointed_thing) if not puncher then return end local name = puncher:get_player_name() if not metadata_updates[name] then return end if not boxes.players_editing_boxes[name] then return end local box = boxes.players_editing_boxes[name] local p = vector.subtract(pos, box.minp) local upd = metadata_updates[name] local key = upd.update_key local bmeta = db.box_get_meta(box.box_id) if upd.update_type == "set" then bmeta.meta[key] = p db.box_set_meta(box.box_id, bmeta) metadata_updates[name] = nil sfinv.set_player_inventory_formspec(puncher) elseif upd.update_type == "add" then bmeta.meta[key][#bmeta.meta[key] + 1] = p db.box_set_meta(box.box_id, bmeta) sfinv.set_player_inventory_formspec(puncher) end end) local meta_types = { [db.BOX_TYPE] = { {key_name = "builder", type = "text"}, }, [db.ENTRY_TYPE] = {}, [db.EXIT_TYPE] = { {key_name = "signs_to_update", type = "poslist"} } } sfinv.register_page("boxes:admin", { title = "Admin", is_in_nav = function(self, player, context) return minetest.check_player_privs(player, "server") and boxes.players_editing_boxes[player:get_player_name()] end, get = function(self, player, context) local name = player:get_player_name() local box = boxes.players_editing_boxes[name] local upd = metadata_updates[name] local bmeta = db.box_get_meta(box.box_id) local form = "" local y = 0 for i, setting in ipairs(meta_types[bmeta.type]) do local current_value = bmeta.meta[setting.key_name] if setting.type == "text" then form = form .. "field[0.3," .. (y + 0.8) .. ";5,1;" .. tostring(i) .. ";" .. setting.key_name .. ";" .. minetest.formspec_escape(current_value) .. "]" .. "field_close_on_enter[" .. tostring(i) .. ";false]" y = y + 1.2 elseif setting.type == "number" then form = form .. "field[0.3," .. (y + 0.8) .. ";5,1;" .. tostring(i) .. ";" .. setting.key_name .. ";" .. tostring(current_value) .. "]" .. "field_close_on_enter[" .. tostring(i) .. ";false]" y = y + 1.2 elseif setting.type == "pos" then local blab = "Edit" if upd and upd.update_key == setting.key_name then blab = "Cancel edit" end form = form .. "label[0," .. (y + 0.3) .. ";" .. minetest.formspec_escape(setting.key_name .. ": " .. minetest.pos_to_string(current_value)) .. "]" form = form .. "button[0," .. (y + 0.7) .. ";3,1;" .. tostring(i) .. ";" .. blab .. "]" y = y + 1.6 elseif setting.type == "poslist" then local blab = "Edit" if upd and upd.update_key == setting.key_name then blab = "Stop edit" end local cvs = "{" for i, pos in ipairs(current_value) do if i > 1 then cvs = cvs .. ", " end cvs = cvs .. minetest.pos_to_string(pos) end cvs = cvs .. "}" form = form .. "label[0," .. (y + 0.3) .. ";" .. minetest.formspec_escape(setting.key_name .. ": " .. cvs) .. "]" form = form .. "button[0," .. (y + 0.7) .. ";3,1;" .. tostring(i) .. ";" .. blab .. "]" y = y + 1.6 end end return sfinv.make_formspec(player, context, form, false) end, on_player_receive_fields = function(self, player, context, fields) if not minetest.check_player_privs(player, "server") then return end local update = false local name = player:get_player_name() local box = boxes.players_editing_boxes[name] local bmeta = db.box_get_meta(box.box_id) local settings = meta_types[bmeta.type] for index, value in pairs(fields) do local i = tonumber(index) if i ~= nil then local setting = settings[i] if setting.type == "number" then value = tonumber(value) end if value ~= nil and setting.type ~= "pos" and setting.type ~= "poslist" then bmeta.meta[setting.key_name] = value elseif setting.type == "pos" or setting.type == "poslist" then if metadata_updates[name] and metadata_updates[name].update_key == setting.key_name then metadata_updates[name] = nil else if setting.type == "poslist" then bmeta.meta[setting.key_name] = {} end metadata_updates[name] = { update_type = (setting.type == "pos" and "set" or "add"), update_key = setting.key_name } end update = true end end end db.box_set_meta(box.box_id, bmeta) if update then sfinv.set_page(player, "boxes:admin") end end, }) --[[ -- Handle box expositions -- These are boxes whose inside can be seen by the players before entering them -- Proof of concept for now, so only one such box. local box_expo_minp = {x = 5, y = 0, z = 5} local box_expo_size = {x = 22, y = 22, z = 22} minetest.register_chatcommand("expo", { params = "", description = "Select the chosen box for exposition", privs = {server = true}, func = function(name, param) local id = tonumber(param) if not id or id ~= math.floor(id) or id < 0 then minetest.chat_send_player(name, "The id you supplied is not a nonnegative integer.") return end local meta = db.box_get_meta(id) if not meta then minetest.chat_send_player(name, "The id you supplied is not in the database.") return end if meta.type ~= db.BOX_TYPE then minetest.chat_send_player(name, "The id you supplied does not correspond to a box.") return end local size = meta.meta.size if not vector.equals(size, box_expo_size) then minetest.chat_send_player(name, "The box you chose does not have the correct size.") return end boxes.load(box_expo_minp, id, nil) -- Open up a side local minp = vector.add(box_expo_minp, {x = 1, y = 1, z = size.z - 1}) local maxp = vector.add(box_expo_minp, {x = size.x - 2, y = size.y - 1, z = size.z - 1}) local cid_barrier = minetest.get_content_id("nodes:barrier") local vm = minetest.get_voxel_manip(minp, maxp) local emin, emax = vm:get_emerged_area() local va = VoxelArea:new{MinEdge=emin,MaxEdge=emax} local vmdata = vm:get_data() for y = minp.y, maxp.y do local index = va:index(minp.x, y, maxp.z) for x = minp.x, maxp.x do vmdata[index] = cid_barrier index = index + 1 end end vm:set_data(vmdata) vm:write_to_map() vm:update_map() end, }) ]]