998 lines
29 KiB
Lua
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")
|