rnd/duplication.lua

270 lines
9.8 KiB
Lua

--[[
Research N' Duplication
Copyright (C) 2020 Noodlemire
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
--]]
--file-specific global storage
rnd.duplication = {}
--Stores each player's current selected duplication page, which is important only so that page selection works at all.
rnd.duplication.currentPage = {}
-- Translation support
local S = minetest.get_translator("rnd")
local F = minetest.formspec_escape
--Create both the player's duplication inventory, and a hidden trash inventory so that items in the survival inventory can be shift-click-dumped.
function rnd.duplication.init(player)
player:get_inventory():set_size("duplication", 32)
player:get_inventory():set_size("trash", 1)
end
--Count the amount of items that the player has completely researched, to determine how many pages there are.
local function countComplete(pname)
local count = 0
for iname, iprog in pairs(rnd.research.progs[pname]) do
if rnd.research.complete(pname, iname) then
count = count + 1
end
end
return count
end
--A version of the pairs() function that sorts keys alphabetically.
local function spairs(t)
--Collect the keys
local keys = {}
for k in pairs(t) do
keys[#keys + 1] = k
end
--Sort them
table.sort(keys)
--Return the iterator function
local i = 0
return function()
i = i + 1
if keys[i] then
return keys[i], t[keys[i]]
end
end
end
--This function creates the duplication menu.
local function duplication_formspec(player, page, unified)
local pname = player:get_player_name()
--Get the player's inventory to look through later.
local inv = player:get_inventory()
--Show either the provided page number, or default to the first page.
rnd.duplication.currentPage[pname] = page or 1
--An iterator for the upcoming loop
local i = 1
--The start point, to determine when to start adding items to the duplication inventory.
local itemI = (rnd.duplication.currentPage[pname] - 1) * 32 + 1
--Clear the duplication inventory beforehand.
inv:set_list("duplication", {})
--For each item that this player has researched so far...
for iname, iprog in spairs(rnd.research.progs[pname]) do
--If research for this item is complete...
if rnd.research.complete(pname, iname) then
--If the iterator has reached or passed the starting point...
if i >= itemI then
--Create a new itemstack based on the current item's name
local stack = ItemStack({name = iname})
--Set its count to this item's specific maximum stack size
stack:set_count(stack:get_stack_max())
--Finally, add it to the inventory at the appropriate slot.
inv:set_stack("duplication", (1 + i - itemI), stack)
end
--Iterate now
i = i + 1
--If the iterator has made enough progress to fill the entire page, this loop can be exited.
if i > itemI + 32 then
break
end
end
end
----The page number label's text is stored here because it is used more than once.
local pageString = tostring(rnd.duplication.currentPage[pname]).."/"..tostring(math.max(math.ceil(countComplete(pname) / 32), 1))
--Show a different inventory layout based on if the unified_inventory is open or not
local base_inv = rnd.base_inv_formspec
local btn_height = 4.05
if unified then
base_inv = rnd.base_unified_formspec
btn_height = 3.7
end
--With all of the above information, the formspec can be formed here.
--Note that every time information changes, the menu is reformed.
--Also, note the extra listring to trash, which enables deletion of items in the survival inventory via shift-clicking.
return base_inv..
"button[1.5,"..btn_height..";1,1;frst;<<]"..
"button[2.5,"..btn_height..";1,1;prev;<]"..
"button[4.5,"..btn_height..";1,1;next;>]"..
"button[5.5,"..btn_height..";1,1;last;>>]"..
"label[3.7,"..(btn_height+0.05)..";"..F(S("Page")).."]"..
"label["..tostring(3.9 - 0.05 * pageString:len())..","..(btn_height+0.3)..";"..pageString.."]"..
"list[current_player;duplication;0,-0.1;8,4;]"..
"listring[current_player;duplication]"..
"listring[current_player;main]"..
"listring[current_player;trash]"
end
--This function prevents players from placing anything inside the duplication inventory.
minetest.register_allow_player_inventory_action(function(player, action, inventory, inventory_info)
if (action == "put" and inventory_info.listname == "duplication") or (action == "move" and inventory_info.to_list == "duplication") then
return 0
end
end)
--This function refills the duplication inventory whenever something is removed, and empties the trash slot whenever it is filled.
minetest.register_on_player_inventory_action(function(player, action, inventory, inventory_info)
--Slightly different means are necessary when items are put in the survival inventory or thrown on the ground
--due to differences in inventory_info variables when the action is "take" or "move".
if action == "take" and inventory_info.listname == "duplication" then
inventory:set_stack("duplication", inventory_info.index, inventory_info.stack)
elseif action == "move" and inventory_info.from_list == "duplication" then
local stack = inventory:get_stack(inventory_info.to_list, inventory_info.to_index)
stack:set_count(stack:get_stack_max())
inventory:set_stack("duplication", inventory_info.from_index, stack)
end
if action == "move" and inventory_info.to_list == "trash" then
inventory:set_list("trash", {})
end
end)
--Defines functionality for the page change buttons.
--This function is defined like this because it is used in two different places, one for sfinv and one that is used for /duplicate.
local function on_player_receive_fields_duplication(player, formname, fields, context)
--If one of the four buttons were pressed...
if ((unified_inventory and formname == "") or formname == "duplication") and (fields["frst"] or fields["prev"] or fields["next"] or fields["last"]) then
local pname = player:get_player_name()
local page = rnd.duplication.currentPage[pname]
--The first page is always page 1
if fields["frst"] then
page = 1
--Decrease the page number. If it dips below 1, keep it at 1.
elseif fields["prev"] then
page = math.max(1, page - 1)
--Increase the page number. If it exceeds the last page, set it back to the last page.
elseif fields["next"] then
page = math.max(1, math.min(math.ceil(countComplete(pname) / 32), page + 1))
--Get the amount of pages to find the last page.
elseif fields["last"] then
page = math.max(math.ceil(countComplete(pname) / 32), 1)
end
--When a page changes, the duplication menu has to be reformed.
--A different method must be used if the sfinv inventory is open, so the tabs still work after the reformation.
if unified_inventory and formname == "" then
rnd.duplication.currentPage[pname] = page
unified_inventory.set_inventory_formspec(player, "duplication")
elseif sfinv and context then
rnd.duplication.currentPage[pname] = page
sfinv.set_player_inventory_formspec(player)
else
minetest.show_formspec(pname, "duplication", duplication_formspec(player, page))
end
end
end
--This applies the above function to /duplicate.
minetest.register_on_player_receive_fields(on_player_receive_fields_duplication)
--For use when sfinv isn't active.
minetest.register_chatcommand("duplicate", {
params = "",
description = S("Open the duplication menu."),
privs = {},
func = function(name, param)
--Block creative players from using it.
if minetest.settings:get_bool("creative_mode") or (creative and creative.is_enabled_for(name)) then
minetest.chat_send_player(name, S("Creative Mode players can't use this."))
else
minetest.show_formspec(name, "duplication", duplication_formspec(minetest.get_player_by_name(name)))
end
end,
})
--When unified_inventory is active, the duplication tab is defined here, using several previously defined functions.
if unified_inventory then
unified_inventory.register_page("duplication", {
get_formspec = function(player)
return {
formspec = duplication_formspec(player, rnd.duplication.currentPage[player:get_player_name()], true),
draw_inventory = false,
}
end
})
unified_inventory.register_button("duplication", {
type = "image",
image = "rnd_button_duplication_page.png",
tooltip = S("Duplicate"),
condition = function(player)
return not unified_inventory.is_creative(player)
end
})
--When sfinv is active, the duplication tab is defined here, using several previously defined functions.
elseif sfinv then
sfinv.register_page("duplication", {
title = S("Duplicate"),
get = function(self, player, context)
return sfinv.make_formspec(player, context, duplication_formspec(player, rnd.duplication.currentPage[player:get_player_name()]))
end,
is_in_nav = function(self, player, context)
return not (minetest.settings:get_bool("creative_mode") or (creative and creative.is_enabled_for(player)))
end,
on_player_receive_fields = function(self, player, context, fields)
on_player_receive_fields_duplication(player, context.page, fields, context)
--When this tab is exited, the player will see the home tab upon seeing the inventory next.
--This prevents /duplicate from being bugged if it's used after closing the research tab.
if fields["quit"] then
sfinv.set_page(player, sfinv.get_homepage_name(player))
end
end,
})
end