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") -- 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 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) if n == 1 and box.start_time then -- We opened the last door: the player got out! local id = box.box_id local time_taken = minetest.get_gametime() - box.start_time local bmeta = db.box_get_meta(id) if bmeta.meta.player_best_time[name] then local old_time = bmeta.meta.player_best_time[name] if time_taken < old_time then local k = bmeta.meta.num_completed_players bmeta.meta.average_best_time = bmeta.meta.average_best_time + (time_taken - old_time) / k bmeta.meta.player_best_time[name] = time_taken bmeta.meta.best_time = math.min(bmeta.meta.best_time, time_taken) end else bmeta.meta.player_best_time[name] = time_taken local k = bmeta.meta.num_completed_players bmeta.meta.num_completed_players = k + 1 bmeta.meta.average_first_time = (bmeta.meta.average_first_time * k + time_taken) / (k + 1) bmeta.meta.average_best_time = (bmeta.meta.average_best_time * k + time_taken) / (k + 1) bmeta.meta.best_time = math.min(bmeta.meta.best_time, time_taken) end db.box_set_meta(id, bmeta) end 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() 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 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])) 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, } for i, box in ipairs(box_id_list) do boxes.load(vector.add(minp, offsets[i]), box, player) end player:set_pos(spawn_pos) 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) else boxes.open_box(player, {0, bxs[index], 1}) boxes.players_in_boxes[name].in_series = sid end end boxes.players_editing_boxes = {} -- FIXME: get that from db each time, but how? local min_free_id = 0 while db.box_get_meta(min_free_id) ~= nil do min_free_id = min_free_id + 1 end 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] 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("new_series", { params = "", description = "Create a new series", privs = {server = true}, func = function(name, param) local id = 1 while db.series_get_meta(id) do id = id + 1 end db.series_set_meta(id, {name = param, meta = {}}) minetest.chat_send_player(name, "Series successfully created with id " .. id) 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_insert_box(sid, bid, #db.series_get_boxes(sid)) 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, }) 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.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 minetest.register_node(name, def) end register_teleport("boxes:enter_teleport", { description = "Enter teleport", tiles = {"diamond.png"}, on_walk_over = function(pos, node, player) local meta = minetest.get_meta(pos) local id = meta:get_int("box") if meta:get_int("is_series") == 1 then local smeta = db.series_get_meta(id) if not smeta then return end boxes.next_series(player, id) return 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 then return end local meta = minetest.get_meta(pos) if puncher:get_player_control().sneak then meta:set_int("is_series", 1 - meta:get_int("is_series")) return end local id = meta:get_int("box") local newid = id + 1 if db.box_get_meta(newid) == nil then newid = 0 end meta:set_int("box", newid) minetest.chat_send_player(puncher:get_player_name(), "Destination set to " .. newid) end, on_rightclick = function(pos, node, puncher, pointed_thing) if not puncher then return end local meta = minetest.get_meta(pos) local id = meta:get_int("box") local newid = id - 1 if db.box_get_meta(newid) == nil then newid = min_free_id - 1 end meta:set_int("box", newid) minetest.chat_send_player(puncher:get_player_name(), "Destination set to " .. newid) end, }) register_teleport("boxes:exit_teleport", { description = "Exit teleport", tiles = {"diamond.png"}, on_walk_over = function(pos, node, player) local name = player:get_player_name() if not boxes.players_in_boxes[name] then return end local sid = boxes.players_in_boxes[name].in_series boxes.close_box(player) 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) boxes.next_series(player, sid) return else players.return_to_lobby(player) end end, }) register_teleport("boxes:lobby_teleport", { description = "Lobby teleport", tiles = {"gold.png"}, on_walk_over = 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, }) minetest.register_chatcommand("open", { params = "", description = "Open the current exit.", 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 if boxes.players_in_boxes[name].exit_doors[1] == nil then minetest.chat_send_player(name, "There are no more exits to open!") return end local player = minetest.get_player_by_name(name) boxes.open_exit(player) end, }) minetest.register_node("boxes:nexus", { description = "Nexus", drawtype = "mesh", mesh = "nexus.obj", paramtype = "light", paramtype2 = "facedir", tiles = { { name = "nexus.png", animation = { type = "sheet_2d", frames_w = 5, frames_h = 10, frame_length = 0.125, } }, }, 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}, }) minetest.register_node("boxes:nexus_large", { description = "Nexus", drawtype = "mesh", mesh = "nexus.obj", paramtype = "light", paramtype2 = "facedir", visual_scale = 4.0, tiles = { { name = "nexus.png", animation = { type = "sheet_2d", frames_w = 5, frames_h = 10, frame_length = 0.125, } }, }, 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.set_node(pos, {name = "boxes:nexus_large", param2 = math.random(24) - 1}) end, }) minetest.register_node("boxes:pedestal", { description = "Nexus Pedestal", tiles = {"bricks_marble.png"}, drawtype = "nodebox", paramtype = "light", node_box = { type = "connected", fixed = {{-1/2, -1/2, -1/2, 1/2, 1/2, 1/2}}, connect_top = { {-1/2, 1/2, -1/2, -1/4, 3/4, -1/4}, {1/4, 1/2, -1/2, 1/2, 3/4, -1/4}, {-1/2, 1/2, 1/4, -1/4, 3/4, 1/2}, {1/4, 1/2, 1/4, 1/2, 3/4, 1/2}, }, }, connects_to = {"boxes:nexus"}, groups = {node = 1}, on_rightclick = function(pos, node, clicker, itemstack, pointed_thing) if itemstack:get_name() ~= "boxes:nexus" then return itemstack end itemstack:take_item() minetest.set_node({x = pos.x, y = pos.y + 1, z = pos.z}, {name = "boxes:nexus", param2 = math.random(24) - 1}) boxes.increase_items(clicker) return itemstack end, after_place_node = function(pos, placer, itemstack, pointed_thing) if not placer then return end local name = placer:get_player_name() local box = boxes.players_editing_boxes[name] if box then box.num_items = box.num_items + 1 end end, after_dig_node = function(pos, oldnode, oldmeta, digger) if not digger then return end local name = digger:get_player_name() local box = boxes.players_editing_boxes[name] if box then box.num_items = math.max(0, box.num_items - 1) end end, }) minetest.register_craftitem("boxes:set_door", { description = "Set door thing", inventory_image = "sorry_i_dont_have_one.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:marble"} 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.entry.y + 1 do for z = box.exit.z, box.entry.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} 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, }) -- 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:marble") local cid_digit = minetest.get_content_id("nodes:bronze") 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 -- Write the box id local id_string = tostring(min_free_id) local id_sz = 4 * string.len(id_string) - 1 if size < 6 or size < id_sz + 2 then print("WARNING: could not write box id: 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, completed_players = {}, num_completed_players = 0, average_first_time = 0, best_time = 1e20, } } player:set_pos(vector.add(minp, {x = 1, y = 1, z = s2 + 1})) db.box_set_meta(min_free_id, meta) db.box_set_data(min_free_id, boxes.save(minp, maxp)) boxes.players_editing_boxes[player:get_player_name()] = { box_id = min_free_id, minp = minp, maxp = maxp, num_items = 0, entry = vector.add(minp, entry), exit = vector.add(minp, exit), } min_free_id = min_free_id + 1 end minetest.register_chatcommand("edit", { params = "", description = "Start editing a new box.", privs = {server = true}, func = function(name, param) if boxes.players_editing_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 boxes.make_new(minetest.get_player_by_name(name), size) end, }) minetest.register_chatcommand("edite", { params = "", description = "Edit an existing box.", privs = {server = true}, func = function(name, param) if boxes.players_editing_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 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), } 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) 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 if id == nil then id = box.box_id end db.box_set_data(id, boxes.save(box.minp, box.maxp)) local bmeta = db.box_get_meta(id) 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) db.box_set_meta(id, bmeta) 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("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) print("leave", player:get_player_name()) 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] print(dump(box)) 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) --[[ -- 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, }) ]]