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") local function bytes_to_string(bytes) local s = {} for i = 1, #bytes do s[i] = string.char(bytes[i]) end return table.concat(s) end -- 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 boxes.import_box(id, box_data) local data_index = 1 local function read_u8() local c = string.byte(box_data, data_index) data_index = data_index + 1 return c end local function read_u16() local a = read_u8() local b = read_u8() return 256 * a + b end local function read_pos() local x = read_u16() local y = read_u16() local z = read_u16() return {x = x, y = y, z = z} end local version = read_u8() assert (version == 1) local type = read_u8() local size = read_pos() local entry = read_pos() local exit = read_pos() db.box_set_data(id, string.sub(box_data, data_index)) db.box_set_meta(id, { type = type, meta = { size = size, entry = entry, exit = exit, } }) end function boxes.export_box(id) local data_index = 1 local data = {} local function write_u8(x) data[data_index] = x data_index = data_index + 1 end local function write_u16(x) write_u8(math.floor(x / 256)) write_u8(x % 256) end local function write_pos(p) write_u16(p.x) write_u16(p.y) write_u16(p.z) end local meta = db.box_get_meta(id) write_u8(1) -- version write_u8(meta.type) write_pos(meta.meta.size) write_pos(meta.meta.entry) write_pos(meta.meta.exit) return bytes_to_string(data) .. db.box_get_data(id) 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 local players_in_boxes = {} local function read_file(filename) local file = io.open(filename, "r") if file ~= nil then local file_content = file:read("*all") io.close(file) return file_content end return "" end local function write_file(filename, data) local file, err = io.open(filename, "w") if file then file:write(data) io.close(file) else error(err) end end if db.box_get_meta(0) == nil then local box_data = read_file(modpath .. "/entry.box") boxes.import_box(0, box_data) end if db.box_get_meta(1) == nil then local box_data = read_file(modpath .. "/exit.box") boxes.import_box(1, box_data) end if db.box_get_meta(2) == nil then local box_data = read_file(modpath .. "/box.box") boxes.import_box(2, box_data) end -- local worldpath = minetest.get_worldpath() -- write_file(worldpath .. "/entry.box", boxes.export_box(0)) -- write_file(worldpath .. "/exit.box", boxes.export_box(1)) -- write_file(worldpath .. "/box.box", boxes.export_box(2)) local function pop_node(pos) local node = minetest.get_node(pos) local meta = minetest.get_meta(pos) local r = {pos = pos, node = node, meta = meta:to_table()} minetest.remove_node(pos) return r end local function unpop_node(data) minetest.set_node(data.pos, data.node) local meta = minetest.get_meta(data.pos) meta:from_table(data.meta) end 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 = players_in_boxes[player:get_player_name()].open_doors od[#od + 1] = {minp = bminp, maxp = bmaxp, doors = drs} end function boxes.open_exit(player) local name = player:get_player_name() local exds = players_in_boxes[name].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 players_in_boxes[name] then return end local exds = 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(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 _, data in ipairs(odoors[i].nodes) do -- unpop_node(data) --end for _, pos in ipairs(odoors[i].doors) do local door = doors.get(pos) if door then door:close() end end odoors[i] = odoors[n] odoors[n] = nil n = n - 1 else i = i + 1 end end end end) function boxes.open_box(player, box_id_list) local name = player:get_player_name() if 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 players_in_boxes[name] = { minp = minp, maxp = maxp, exit_doors = exit_doors, exit = vector.add(minp, vector.add(metas[n].exit, offsets[n])), open_doors = {}, } for i, box in ipairs(box_id_list) do boxes.load(vector.add(minp, offsets[i]), box, player) end local spawn_pos = vector.add(minp, vector.add(metas[1].entry, offsets[1])) player:set_pos(spawn_pos) boxes.open_exit(player) end function boxes.close_box(player) local name = player:get_player_name() if players_in_boxes[name] == nil then return end local bx = players_in_boxes[name] boxes.cleanup(bx.minp, bx.maxp) boxes.vfree(bx.minp) 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}) players_in_boxes[name].in_series = sid end end local 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 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, }) local function teleport_update_particles(pos, name) 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 = "diamond.png", playername = name, }) minetest.get_node_timer(pos):start(tm) end minetest.register_node("boxes:enter_teleport", { description = "Enter teleport", tiles = {"diamond.png"}, drawtype = "nodebox", node_box = { type = "fixed", fixed = {{-7/16, -1/2, -7/16, 7/16, -7/16, 7/16}}, }, paramtype = "light", groups = {}, 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, on_construct = function(pos) teleport_update_particles(pos, nil) end, on_timer = function(pos) teleport_update_particles(pos, nil) 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 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_node("boxes:exit_teleport", { description = "Exit teleport", tiles = {"diamond.png"}, drawtype = "nodebox", node_box = { type = "fixed", fixed = {{-7/16, -1/2, -7/16, 7/16, -7/16, 7/16}}, }, paramtype = "light", groups = {}, on_walk_over = function(pos, node, player) local name = player:get_player_name() if not players_in_boxes[name] then return end local sid = 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, on_construct = function(pos) teleport_update_particles(pos, nil) end, on_timer = function(pos) teleport_update_particles(pos, nil) end, after_box_construct = function(pos) teleport_update_particles(pos, nil) end, }) minetest.register_chatcommand("open", { params = "", description = "Open the current exit.", privs = {server = true}, func = function(name, param) if not players_in_boxes[name] then minetest.chat_send_player(name, "You are not in a box!") return end if 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 = 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 = 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 players_editing_boxes[name] then return end local box = 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_stone = minetest.get_content_id("nodes:stone") local cid_barrier = minetest.get_content_id("nodes:barrier") local cid_marble = minetest.get_content_id("nodes:marble") 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_stone 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_stone vmdata[index2] = cid_stone 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_stone vmdata[index2] = cid_stone 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_marble 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, } } 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)) players_editing_boxes[player:get_player_name()] = { box_id = min_free_id, minp = minp, maxp = maxp, num_items = 0, entry = vector.new(entry), exit = vector.new(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 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 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)) players_editing_boxes[name] = { box_id = id, minp = minp, maxp = maxp, num_items = meta.meta.num_items, entry = vector.new(meta.meta.entry), exit = vector.new(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 = 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 = players_editing_boxes[name] if not box then return end boxes.cleanup(box.minp, box.maxp) boxes.vfree(box.minp) 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 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 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) --[[ -- 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, }) ]]