307 lines
7.8 KiB
Lua
307 lines
7.8 KiB
Lua
-- Crafting Mod - semi-realistic crafting in minetest
|
|
-- Copyright (C) 2018 rubenwardy <rw@rubenwardy.com>
|
|
--
|
|
-- 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
|
|
|
|
|
|
crafting = {
|
|
recipes = {},
|
|
recipes_by_id = {},
|
|
registered_on_crafts = {},
|
|
}
|
|
|
|
function crafting.register_type(name)
|
|
crafting.recipes[name] = {}
|
|
end
|
|
|
|
local recipe_counter = 0
|
|
function crafting.register_recipe(def)
|
|
assert(def.output, "Output needed in recipe definition")
|
|
assert(def.type, "Type needed in recipe definition")
|
|
assert(def.items, "Items needed in recipe definition")
|
|
|
|
def.level = def.level or 1
|
|
|
|
local tab = crafting.recipes[def.type]
|
|
assert(tab, "Unknown craft type " .. def.type)
|
|
|
|
recipe_counter = recipe_counter + 1
|
|
def.id = recipe_counter
|
|
crafting.recipes_by_id[recipe_counter] = def
|
|
tab[#tab + 1] = def
|
|
|
|
return def.id
|
|
end
|
|
|
|
local unlocked_cache = {}
|
|
function crafting.get_unlocked(name)
|
|
local player = minetest.get_player_by_name(name)
|
|
if not player then
|
|
minetest.log("warning", "Crafting doesn't support getting unlocks for offline players")
|
|
return {}
|
|
end
|
|
|
|
local retval = unlocked_cache[name]
|
|
if not retval then
|
|
retval = minetest.parse_json(
|
|
player:get_meta():get("crafting:unlocked") or "{}")
|
|
unlocked_cache[name] = retval
|
|
end
|
|
|
|
assert(retval)
|
|
|
|
return retval
|
|
end
|
|
|
|
if minetest then
|
|
minetest.register_on_leaveplayer(function(player)
|
|
unlocked_cache[player:get_player_name()] = nil
|
|
end)
|
|
end
|
|
|
|
local function write_json_dictionary(value)
|
|
if next(value) then
|
|
return minetest.write_json(value)
|
|
else
|
|
return "{}"
|
|
end
|
|
end
|
|
|
|
function crafting.lock_all(name)
|
|
local player = minetest.get_player_by_name(name)
|
|
if not player then
|
|
minetest.log("warning", "Crafting doesn't support setting unlocks for offline players")
|
|
return {}
|
|
end
|
|
|
|
local unlocked = crafting.get_unlocked(name)
|
|
|
|
for key, _ in pairs(unlocked) do
|
|
unlocked[key] = nil
|
|
end
|
|
|
|
unlocked_cache[name] = unlocked
|
|
|
|
player:get_meta():set_string("crafting:unlocked", write_json_dictionary(unlocked))
|
|
end
|
|
|
|
function crafting.unlock(name, output)
|
|
local player = minetest.get_player_by_name(name)
|
|
if not player then
|
|
minetest.log("warning", "Crafting doesn't support setting unlocks for offline players")
|
|
return {}
|
|
end
|
|
|
|
local unlocked = crafting.get_unlocked(name)
|
|
|
|
if type(output) == "table" then
|
|
for i=1, #output do
|
|
unlocked[output[i]] = true
|
|
minetest.chat_send_player(name, "You've unlocked " .. output[i])
|
|
end
|
|
else
|
|
unlocked[output] = true
|
|
minetest.chat_send_player(name, "You've unlocked " .. output)
|
|
end
|
|
|
|
unlocked_cache[name] = unlocked
|
|
player:get_meta():set_string("crafting:unlocked", write_json_dictionary(unlocked))
|
|
end
|
|
|
|
function crafting.get_recipe(id)
|
|
return crafting.recipes_by_id[id]
|
|
end
|
|
|
|
function crafting.get_all(type, level, item_hash, unlocked)
|
|
assert(crafting.recipes[type], "No such craft type!")
|
|
|
|
local results = {}
|
|
|
|
for _, recipe in pairs(crafting.recipes[type]) do
|
|
local craftable = true
|
|
|
|
if recipe.level <= level and (recipe.always_known or unlocked[recipe.output]) then
|
|
-- Check all ingredients are available
|
|
local items = {}
|
|
for _, item in pairs(recipe.items) do
|
|
item = ItemStack(item)
|
|
local needed_count = item:get_count()
|
|
|
|
local available_count = item_hash[item:get_name()] or 0
|
|
if available_count < needed_count then
|
|
craftable = false
|
|
end
|
|
|
|
items[#items + 1] = {
|
|
name = item:get_name(),
|
|
have = available_count,
|
|
need = needed_count,
|
|
}
|
|
end
|
|
|
|
results[#results + 1] = {
|
|
recipe = recipe,
|
|
items = items,
|
|
craftable = craftable,
|
|
}
|
|
end
|
|
end
|
|
|
|
return results
|
|
end
|
|
|
|
function crafting.set_item_hashes_from_list(inv, listname, item_hash)
|
|
for _, stack in pairs(inv:get_list(listname)) do
|
|
if not stack:is_empty() then
|
|
local itemname = stack:get_name()
|
|
item_hash[itemname] = (item_hash[itemname] or 0) + stack:get_count()
|
|
|
|
local def = minetest.registered_items[itemname]
|
|
if def and def.groups then
|
|
for groupname, _ in pairs(def.groups) do
|
|
local group = "group:" .. groupname
|
|
item_hash[group] = (item_hash[group] or 0) + stack:get_count()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function crafting.get_all_for_player(player, type, level)
|
|
local unlocked = crafting.get_unlocked(player:get_player_name())
|
|
|
|
-- Get items hashed
|
|
local item_hash = {}
|
|
crafting.set_item_hashes_from_list(player:get_inventory(), "main", item_hash)
|
|
|
|
return crafting.get_all(type, level, item_hash, unlocked)
|
|
end
|
|
|
|
function crafting.can_craft(name, type, level, recipe)
|
|
local unlocked = crafting.get_unlocked(name)
|
|
|
|
return recipe.type == type and recipe.level <= level and
|
|
(recipe.always_known or unlocked[recipe.output])
|
|
end
|
|
|
|
local function give_all_to_player(inv, list)
|
|
for _, item in pairs(list) do
|
|
inv:add_item("main", item)
|
|
end
|
|
end
|
|
|
|
function crafting.find_required_items(inv, listname, recipe)
|
|
local items = {}
|
|
for _, item in pairs(recipe.items) do
|
|
item = ItemStack(item)
|
|
|
|
local itemname = item:get_name()
|
|
if item:get_name():sub(1, 6) == "group:" then
|
|
local groupname = itemname:sub(7, #itemname)
|
|
local required = item:get_count()
|
|
|
|
-- Find stacks in group
|
|
for i = 1, inv:get_size(listname) do
|
|
local stack = inv:get_stack(listname, i)
|
|
|
|
-- Is it in group?
|
|
local def = minetest.registered_items[stack:get_name()]
|
|
if def and def.groups and def.groups[groupname] then
|
|
stack = ItemStack(stack)
|
|
if stack:get_count() > required then
|
|
stack:set_count(required)
|
|
end
|
|
items[#items + 1] = stack
|
|
|
|
required = required - stack:get_count()
|
|
|
|
if required == 0 then
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
if required > 0 then
|
|
return nil
|
|
end
|
|
else
|
|
if inv:contains_item(listname, item) then
|
|
items[#items + 1] = item
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
end
|
|
|
|
return items
|
|
end
|
|
|
|
function crafting.has_required_items(inv, listname, recipe)
|
|
return crafting.find_required_items(inv, listname, recipe) ~= nil
|
|
end
|
|
|
|
function crafting.register_on_craft(func)
|
|
table.insert(crafting.registered_on_crafts, func)
|
|
end
|
|
|
|
function crafting.perform_craft(name, inv, listname, outlistname, recipe)
|
|
local items = crafting.find_required_items(inv, listname, recipe)
|
|
if not items then
|
|
return false
|
|
end
|
|
|
|
-- Take items
|
|
local taken = {}
|
|
for _, item in pairs(items) do
|
|
item = ItemStack(item)
|
|
|
|
local took = inv:remove_item(listname, item)
|
|
taken[#taken + 1] = took
|
|
if took:get_count() ~= item:get_count() then
|
|
minetest.log("error", "Unexpected lack of items in inventory")
|
|
give_all_to_player(inv, taken)
|
|
return false
|
|
end
|
|
end
|
|
|
|
for i=1, #crafting.registered_on_crafts do
|
|
crafting.registered_on_crafts[i](name, recipe)
|
|
end
|
|
|
|
-- Add output
|
|
if inv:room_for_item("main", recipe.output) then
|
|
inv:add_item(outlistname, recipe.output)
|
|
else
|
|
local pos = minetest.get_player_by_name(name):get_pos()
|
|
minetest.chat_send_player(name, "No room in inventory!")
|
|
minetest.add_item(pos, recipe.output)
|
|
end
|
|
return true
|
|
end
|
|
|
|
local function to_hex(str)
|
|
return (str:gsub('.', function (c)
|
|
return string.format('%02X', string.byte(c))
|
|
end))
|
|
end
|
|
|
|
function crafting.calc_inventory_list_hash(inv, listname)
|
|
local str = ""
|
|
for _, stack in pairs(inv:get_list(listname)) do
|
|
str = str .. stack:get_name() .. stack:get_count()
|
|
end
|
|
return minetest.sha1(to_hex(str))
|
|
end
|