Add files via upload

master
Noodlemire 2020-07-08 19:35:36 -05:00 committed by GitHub
parent 22b246f6dc
commit b20d1a7110
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 844 additions and 2 deletions

View File

@ -1,2 +1,24 @@
# rnd
Inspired directly by Terraria's 1.4 update, this mod offers an "earned creative" mode to survival players. When active, you can use a specific amount of an item for research. Once an item is fully researched, it is permanently unlocked in the duplication menu, letting you create an infinite amount of that item.
-------------------------------------------------------------------------------------------------------------
Research N' Duplication
[rnd]
-------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------
About
-------------------------------------------------------------------------------------------------------------
This mod is inspired directly by the main feature of Terraria's "Journey Mode". It is a pseudo-creative mode where it is possible to create infinite resources, but you first have to earn that ability per item by obtaining a specific quantity of said item, and then using it for "research". This means that instead of having god-like powers from the start, you have to play the game in a survival-esque way, using mostly traditional methods, with duplication often only aiding in reducing the total amount of grinding, and in some cases, providing more of a reason to build big, intricate, and/or luxurious structures.
-------------------------------------------------------------------------------------------------------------
Dependencies and Support
-------------------------------------------------------------------------------------------------------------
No hard dependencies are necessary. There is support for sfinv, though, which lets the research and duplication menus appear as tabs in the regular survival inventory. Without it, those menus are accessed through the /research and /duplicate commands.
-------------------------------------------------------------------------------------------------------------
License
-------------------------------------------------------------------------------------------------------------
The LGPL v2.1 License is used with this mod. See https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html or LICENSE.txt for more details.
-------------------------------------------------------------------------------------------------------------
Installation
-------------------------------------------------------------------------------------------------------------
Download, unzip, and place within the usual minetest/current/mods folder, and it will behave in relation to the Minetest engine like any other mod.

237
duplication.lua Normal file
View File

@ -0,0 +1,237 @@
--[[
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 = {}
--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)
minetest.log("init trash")
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)
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.ceil(countComplete(pname) / 32))
--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 rnd.base_inv_formspec..
"button[1.5,4;1,1;frst;<<]"..
"button[2.5,4;1,1;prev;<]"..
"button[4.5,4;1,1;next;>]"..
"button[5.5,4;1,1;last;>>]"..
"label[3.7,4;Page]"..
"label["..tostring(3.9 - 0.05 * pageString:len())..",4.25;"..pageString.."]"..
"list[current_player;duplication;0,0;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.
function rnd.duplication.allow_player_inventory_action(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
return inventory_info.count or inventory_info.stack:get_count()
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 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.min(math.ceil(countComplete(pname) / 32), page + 1)
--Get the amount of pages to find the last page.
elseif fields["last"] then
page = math.ceil(countComplete(pname) / 32)
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 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 = "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, "Creative Mode players can't use this.")
else
minetest.show_formspec(name, "duplication", duplication_formspec(minetest.get_player_by_name(name)))
end
end,
})
--When sfinv is active, the duplication tab is defined here, using several previously defined functions.
if sfinv then
sfinv.register_page("duplication", {
title = "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

50
init.lua Normal file
View File

@ -0,0 +1,50 @@
--[[
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
--]]
--Mod-specific global variable
rnd = {}
--MP = Mod Path
rnd.mp = minetest.get_modpath(minetest.get_current_modname())..'/'
--If default is loaded, grab its hotbar background image
local hotbar_bg = ""
if default then
hotbar_bg = default.get_hotbar_bg(0,5.2)
end
--The size and player inventory menu, used for both the research and duplication menus
rnd.base_inv_formspec = "size[8,9.1]"..
"list[current_player;main;0,5.2;8,1;]"..
"list[current_player;main;0,6.35;8,3;8]"..
hotbar_bg
--A custom API file that I find a little more convenient than the usual mod storage API.
dofile(rnd.mp.."storage.lua")
--All of the functionality of the research menu
dofile(rnd.mp.."research.lua")
--All of the functionality of the duplication menu
dofile(rnd.mp.."duplication.lua")

3
mod.conf Normal file
View File

@ -0,0 +1,3 @@
name = rnd
description = Research N' Duplicate: Obtain and research enough of a specific item to unlock the ability to infinitely duplicate it. In other words, it's an "earned" creative mode.
optional_depends = creative, default, sfinv

376
research.lua Normal file
View File

@ -0,0 +1,376 @@
--[[
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.research = {}
--Every researchable item has a specific amount needed to be researched in order to unlock duplication.
--This table stores the research requirement of each, indexed by item name.
rnd.research.goals = {}
--Tracks research progress of each item of each player
rnd.research.progs = {}
--This variable is needed to prevent buggy behavior when players grab an item from the sfinv research tab,
--switch to a different tab, and place the item somewhere.
--Without it, the game erroneously displays the /research menu in this scenario.
local normal_research_menu_active = {}
--In research.txt, research requirements of entire groups can be defined.
--In case an item fits into numberous groups, the smallest research requirement takes precedent over larger research requirements.
--Note that if a multi-group item is named specifically in research.txt or fits into group:rnd_goal,
--that research requirement is used no matter how many group requirements are defined.
--The point of returning rnd.research.goals[item] is to confirm that anything was found at all for an if statement.
local function find_research_groups(item, research_specs)
for k, v in pairs(research_specs) do
if (not rnd.research.goals[item] or v < rnd.research.goals[item]) and k:sub(1, 6) == "group:" and minetest.get_item_group(item, k:sub(7)) > 0 then
rnd.research.goals[item] = v
end
end
return rnd.research.goals[item]
end
--Once all mods have been loaded, no more items can be registered, so this is when research requirements are calculated.
minetest.register_on_mods_loaded(function()
--Open research.txt in read-only mode
local research_txt = io.open(rnd.mp.."research.txt", "r")
--A list of each line in research.txt, indexed by item or group name and storing the associated research requirement of each
local research_specs = {}
--For each line in research.txt...
for line in research_txt:lines() do
--If the line isn't blank and isn't a comment as marked by a # at the start...
if line:len() > 0 and line:sub(1, 1) ~= '#' then
--Separate each line by the space in between the item/group name and research requirement number
local line_parts = string.split(line, ' ')
research_specs[line_parts[1]] = tonumber(line_parts[2])
end
end
--For each registered item...
for item, def in pairs(minetest.registered_items) do
--Skip over items marked as "not_in_creative_inventory" or "rnd_disabled", they aren't allowed to be researched.
if minetest.get_item_group(item, "not_in_creative_inventory") == 0 and minetest.get_item_group(item, "rnd_disabled") == 0 then
--If an item is named specifically in research.txt, that given requirement gets top priority.
if research_specs[item] then
rnd.research.goals[item] = research_specs[item]
--If a mod creator gives their item the "rnd_goal" group, it has priority over group-wide goals defined in research.txt.
elseif minetest.get_item_group(item, "rnd_goal") ~= 0 then
rnd.research.goals[item] = minetest.get_item_group(item, "rnd_goal")
--See the function above this one.
elseif find_research_groups(item, research_specs) then
--Nothing happens here because everything already happened in the function. This is only here so the default doesn't apply.
else
--By default, a full stack of items is needed to unlock duplication.
rnd.research.goals[item] = def.stack_max
end
end
end
--Once finished, the file can be closed.
io.close()
end)
--Whenever a player joins, progress data is either loaded from memory or created.
minetest.register_on_joinplayer(function(player)
local pname = player:get_player_name()
--If the current game/world doesn't use the sfinv mod, commands are used to view menus.
--This might not be intuitive, so players are reminded each time they join.
--When sfinv is used, this isn't necessary, as those menus are integrated to the survival inventory.
if not sfinv then
--Research mode is redundant if the player already has creative mode.
--This also saves on a bit of memory, since a progs table won't be created for creative players.
if minetest.settings:get_bool("creative_mode") or (creative and creative.is_enabled_for(pname)) then
minetest.chat_send_player(pname, "Warning: You are in creative mode. Research and Duplication is disabled in favor of the default creative inventory.")
return
else
minetest.chat_send_player(pname, "Research and Duplication is active. You can use /research and /duplicate to bring up their respective menus.")
end
end
--Short for "base", this is used to sort between saved research progress amounts.
local b = pname.."_"
--Checks if data has been created yet.
local hasData = rnd.storage.getBool(b.."hasData")
--Create a new research progress table for the newly joined player.
rnd.research.progs[pname] = {}
if hasData then
--If this is a returning player, fill the new table with saved data.
--Do this by checking the name of every researchable item, and seeing if data for "<player name>_<item name>" exists.
for item, _ in pairs(minetest.registered_items) do
if minetest.get_item_group(item, "not_in_creative_inventory") == 0 and minetest.get_item_group(item, "rnd_disabled") == 0 then
local itemdata = rnd.storage.getNum(b..item)
if itemdata then
rnd.research.progs[pname][item] = itemdata
end
end
end
else
rnd.storage.put(b.."hasData", true)
--Create a research inventory for that player.
player:get_inventory():set_size("research", 8)
--For sorting purposes, creation of the duplication inventory is moved to duplication.lua.
rnd.duplication.init(player)
end
end)
--When a player leaves, their progress data doesn't need to be loaded in progs[] anymore, since it's in mod storage.
minetest.register_on_leaveplayer(function(player)
rnd.research.progs[player:get_player_name()] = nil
end)
--Use this to check if a player's research of any particular item is complete.
function rnd.research.complete(pname, iname)
--If the given item can't be researched, automatically say no.
if not rnd.research.progs[pname] or not rnd.research.progs[pname][iname] or not rnd.research.goals[iname] then
return false
end
--Otherwise, research is complete has long as progress has reached or exceeded the goal.
--(Excess is normally prevented, but if a file is modified and a goal becomes lowered, it can still happen.)
return rnd.research.progs[pname][iname] >= rnd.research.goals[iname]
end
--This function is used to determine how much progress to add, based on the returned number.
function rnd.research.progress(pname, iname, num)
--If the item can't be researched, always return 0.
if not rnd.research.progs[pname] or not rnd.research.goals[iname] or rnd.research.complete(pname, iname) then
return 0
end
--Give whichever is smaller: the provided amount of items to use, or the amount needed to complete research.
return math.min(num, rnd.research.goals[iname] - (rnd.research.progs[pname][iname] or 0))
end
--This function creates the research menu.
local function research_formspec(player)
--Get the player's inventory to look through later.
local inv = player:get_inventory()
--Stored information to displayer later in the menu.
--"X" is the default goal to show that an item can't be researched.
local item = {name = "", count = 0, goal = "X"}
--For each item currently in the research inventory, search for its name.
--Only one kind of item is allowed in the research inventory at a time, so when one is found, the loop can be exited immediately.
for i = 1, inv:get_size("research") do
local stack = inv:get_stack("research", i)
if not stack:is_empty() then
item.name = stack:get_name()
break
end
end
--If a name was found, get information about current progress and its goal.
if item.name ~= "" then
item.count = rnd.research.progs[player:get_player_name()][item.name] or item.count
item.goal = rnd.research.goals[item.name] or item.goal
end
--The progress label's text is stored here because it is used more than once.
local progString = "("..item.count.."/"..item.goal..")"
--With all of the above information, the formspec can be formed here.
--Note that every time information changes, the menu is reformed.
return rnd.base_inv_formspec..
"label["..tostring(4 - 0.05 * item.name:len())..",1.75;"..item.name.."]"..
"label["..tostring(3.9 - 0.05 * progString:len())..",2;"..progString.."]"..
"button[3,2.5;2,1;research;Research]"..
"list[current_player;research;0,3.5;8,1;]"..
"listring[]"
end
--This function forces the research menu to only accept once item at a time.
--The extra slots are only here to make it possible to research several stacks at a time, for items with high requirements.
minetest.register_allow_player_inventory_action(function(player, action, inventory, inventory_info)
--Putting items directly into the research menu from nowhere isn't normally possible, so just block it.
if action == "put" and inventory_info.listname == "research" then
return 0
--If the player tries to move an item from an inventory to the research inventory...
elseif action == "move" and inventory_info.to_list == "research" then
--This overly long set of functions and variables is only to get the name of the item being moved.
local iname = inventory:get_stack(inventory_info.from_list, inventory_info.from_index):get_name()
--For each item in the research menu...
for i = 1, inventory:get_size(inventory_info.to_list) do
local otherItem = inventory:get_stack(inventory_info.to_list, i)
--Only keep going if the current slot is where the item is being moved, if the current slot is empty,
--or the item that is in this slot has the same name as the item being moved to the research inventory.
--Otherwise, prevent the movement.
if i ~= inventory_info.to_index and not otherItem:is_empty() and iname ~= otherItem:get_name() then
return 0
end
end
--If we got this far, we can let the itemstack into the research inventory.
return inventory_info.count
end
--For sorting purposes, the next part of the function is in duplication.lua.
return rnd.duplication.allow_player_inventory_action(player, action, inventory, inventory_info)
end)
--Whenever something is successfully moved to the research menu, reform it to adjust the research progress labels.
minetest.register_on_player_inventory_action(function(player, action, inventory, inventory_info)
if action == "move" and (inventory_info.to_list == "research" or inventory_info.from_list == "research") then
local pname = player:get_player_name()
--A different method must be used if the sfinv inventory is open, so the tabs still work after the reformation.
if sfinv and sfinv.get_or_create_context(player).page == "research" then
sfinv.set_player_inventory_formspec(player)
--Check first that the /research menu is open. If it is, update it.
elseif normal_research_menu_active[pname] then
minetest.show_formspec(pname, "research", research_formspec(player))
end
end
end)
--The functionality between the research menu's Research button.
--This function is defined like this because it is used in two different places, one for sfinv and one that is used for /research.
local function on_player_receive_fields_research(player, formname, fields, context)
local pname = player:get_player_name()
if formname == "research" and fields["research"] then
--Get the player's inventory to look through later.
local inv = player:get_inventory()
--Information stored about the item, for later research
local item = {name = "", count = 0}
--Look through each slot in the research inventory, to fill out the item table.
for i = 1, inv:get_size("research") do
local stack = inv:get_stack("research", i)
if not stack:is_empty() then
item.count = item.count + stack:get_count()
item.name = stack:get_name()
end
end
--If items were found...
if item.name ~= "" then
--Calculate the amount of progress to add.
local progress = rnd.research.progress(pname, item.name, item.count)
--Add the progress to the progs table and immediately save it in mod storage.
rnd.research.progs[pname][item.name] = (rnd.research.progs[pname][item.name] or 0) + progress
rnd.storage.put(pname.."_"..item.name, rnd.research.progs[pname][item.name])
--Reform the research inventory, using a method to preserve sfinv tabs if they exist.
--Its placement here, before the research inventory is cleared, is important.
--This makes the research label "lag", so if all of the items are removed by research,
--The player will still see the total research progress for that item, rather than the usual (0/X)
if sfinv and context then
sfinv.set_player_inventory_formspec(player)
else
minetest.show_formspec(pname, "research", research_formspec(player))
end
--For each research inventory slot...
--Note that from here on out, the progress variable is only used to determine how many items need to be removed.
for i = 1, inv:get_size("research") do
local stack = inv:get_stack("research", i)
--If the stack is larger than the amount of progress made, deplete that amount from the stack and leave everything else be.
if stack:get_count() > progress then
stack:set_count(stack:get_count() - progress)
inv:set_stack("research", i, stack)
break
else
--Otherwise, decrase progress for future reference in this loop and erase the stack
progress = progress - stack:get_count()
inv:set_stack("research", i, {})
--If progress is 0, because the remaining progress equalled this stack's size, this loop can be exited.
if progress == 0 then
break
end
end
end
end
end
if normal_research_menu_active[pname] and fields["quit"] then
--If the player just exited the /research menu, remember that it is no longer open.
normal_research_menu_active[pname] = false
end
end
--This applies the above function to /research.
minetest.register_on_player_receive_fields(on_player_receive_fields_research)
--For use when sfinv isn't active.
minetest.register_chatcommand("research", {
params = "",
description = "Open the research 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, "Creative Mode players can't use this.")
else
--Remember that the /research menu is being used right now.
normal_research_menu_active[name] = true
minetest.show_formspec(name, "research", research_formspec(minetest.get_player_by_name(name)))
end
end,
})
--When sfinv is active, the research tab is defined here, using several previously defined functions.
if sfinv then
sfinv.register_page("research", {
title = "Research",
get = function(self, player, context)
return sfinv.make_formspec(player, context, research_formspec(player))
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_research(player, context.page, fields, context)
--When this tab is exited, the player will see the home tab upon seeing the inventory next.
--This prevents /research 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

87
research.txt Normal file
View File

@ -0,0 +1,87 @@
#Use this file to specify research requirements for items, given specific names or groups.
#An item name will always take precedence over a group name.
#Group names may not always apply as you'd expect, if a modder added that item to the "rnd_goal" group in their code.
#Specific item names will override rnd_goal.
#Give research requirements to items by first writing that item's internal name, followed by a space and a number.
#For groups, write "group:" followed by the group's internal name. Then follow that with a space and a number.
#The number you write is the exact amount of items needed to research in order to unlock duplication.
#It's okay to write the names of items from any other mods, even mods that aren't loaded.
#It's important to note that if no goal is defined at all, items default to their given stack size.
#For most items, this means the research requirement is 99. For tools, however, the requirement is 1.
#Some items, like slabs and stairs, have requirements based on how many you can get per block.
#This is especially so for slabs, which can be made back into solid blocks easily.
group:slab 198
group:stair 149
#A number of crafted items have a goal of 1 for the sake of convenience, since you only make one at a time.
#Plus, if you're making these, you'll almost always have the materials researched already.
beds:bed 1
beds:fancy_bed 1
boats:boat 1
carts:cart 1
default:furnace 1
doors:door_wood 1
doors:door_steel 1
doors:door_glass 1
doors:door_obsidian_glass 1
doors:trapdoor 1
doors:trapdoor_steel 1
doors:gate_wood 1
doors:gate_acacia_wood 1
doors:gate_junglewood 1
doors:gate_pine_wood 1
doors:gate_aspen_wood 1
xpanes:door_steel_bar 1
#Empty buckets are the only buckets that are allowed to stack, which I find weird.
bucket:bucket_empty 1
#Some research requirements are based on how frequently you get items.
#Although there are massive differences between different ore rarities, obtaining them is still the same process,
#via digging through through stone or wandering caves and hoping for the best.
#Therefore, these require less research than very readily available materials, with rarer materials being more difficult to research due to their power.
default:stone_with_coal 27
default:coal_lump 27
default:stone_with_copper 27
default:copper_lump 27
default:copper_ingot 27
default:stone_with_tin 27
default:tin_lump 27
default:tin_ingot 27
default:bronze_ingot 27
default:stone_with_iron 27
default:iron_lump 27
default:steel_ingot 27
default:stone_with_gold 27
default:gold_lump 27
default:gold_ingot 27
default:stone_with_mese 27
default:mese_crystal 27
default:stone_with_diamond 27
default:diamond 27
#Solid ore blocks are made from 9 of that ore, and can be made back into 9 ore each. So, slab logic applies here.
default:coalblock 3
default:copperblock 3
default:tinblock 3
default:bronzeblock 3
default:steelblock 3
default:goldblock 3
default:meseblock 3
default:diamondblock 3
#In the same way, 9 pieces of ore equal one usuably-sized unit of ore.
default:mese_crystal_fragment 243
#More slab logic, even these crafting processes can't normally be reversed.
default:paper 33
default:book 11
#TNT Sticks need 9 because that's how many you need to make a block of TNT.
tnt:tnt_stick 9
tnt:tnt 1

67
storage.lua Normal file
View File

@ -0,0 +1,67 @@
--[[
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.storage = {}
--Grab rnd's mod-private storage
local store = minetest.get_mod_storage()
--A set function that automatically takes any kind of storable value
function rnd.storage.put(key, val)
local t = type(val)
if t == "string" then
store:set_string(key, val)
elseif t == "number" then
store:set_float(key, val)
elseif val == true then
store:set_int(key, 1)
elseif not val then
--Setting a value to "" will delete it.
store:set_string(key, "")
else
minetest.log("error", "Attempt to put val of type "..t.." into key "..key)
end
end
--Normally, get_string returns "" when there's no value. I find nil easier to check.
function rnd.storage.getStr(key)
if not store:contains(key) then return nil end
return store:get_string(key)
end
--Normally, get_float returns 0 when there's no value. I find nil easier to check.
--Also, "Num" is easier to remember, because the type() for numeral values in Lua is "number", rather than "float"
function rnd.storage.getNum(key)
if not store:contains(key) then return nil end
return store:get_float(key)
end
--Essentially only exists because it's memorable, alongside the rest of the "getType" functions
function rnd.storage.getBool(key)
return store:contains(key)
end
--A value that isn't given at all counts as "not val" in the if statements of put(), which is why this works.
function rnd.storage.del(key)
rnd.storage.put(key)
end