This moves review into a separate permission, aside from "server". The benefit is that we can grant this to more people without the need to pass out "server" privs, which has a lot more powers than needeed. This doesn't grant noclip. This needs to be done separately. Scoring will be disabled for those with the review priv.
995 lines
29 KiB
Lua
995 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 limit = tonumber(player:get_attribute("box_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") 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")
|