insidethebox/mods/boxes/nodes.lua
Auke Kok 6af528b74f Properly check for nil - tonumber will emit it.
This caused a few crashes on people using the creator pad.
2021-07-09 22:23:44 -07:00

998 lines
29 KiB
Lua

--[[
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 = {}
minetest.register_privilege("review", S("Player can review boxes"))
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, "review") 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, "review") then
f = f .. "button[8.4,0;3.4,1;series;"..FE(S("Manage Series")).."]"
end
local pmeta = player:get_meta()
local limit = tonumber(pmeta:get_string("boxxx_create_limit")) or 3
if (minetest.check_player_privs(name, "create") and counts.editing + counts.submitted <= limit) or
minetest.check_player_privs(name, "review") 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, "review") 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) and
(minetest.check_player_privs(name, "server") or
(context.box and context.box.builder == name)
) 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 can_review = minetest.check_player_privs(name, "review")
-- 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 can_review 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 can_review 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 can_review 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 can_review 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 can_review 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 can_review 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")