Skip to content

Commit

Permalink
Add everything
Browse files Browse the repository at this point in the history
  • Loading branch information
random-geek committed Mar 21, 2019
1 parent fe05f7a commit 9fea60e
Show file tree
Hide file tree
Showing 17 changed files with 766 additions and 2 deletions.
38 changes: 36 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,36 @@
# cg_plus
Crafting Guide Plus is a simple and intuitive crafting guide and auto-crafting mod for Minetest.
# Crafting Guide Plus

Crafting Guide Plus is a simple and intuitive crafting guide and auto-crafting mod for Minetest. CGP is compatible with
Minetest Game and any other games that use sfinv. It was built mostly from the ground up, with some inspiration from
jp's mod as well as Unified Inventory.

## Features:

- "Intelligent" auto-crafting, or rather, automatic craft staging. This feature can be disabled if it is not wanted.
- Group support, including group search and support for craft recipes requiring items in multiple groups.
- Shaped and shapeless crafting recipes.
- Fuel and cooking recipes, including fuel replacements and burning/cooking times.
- Digging and digging by chance (item drop) previews.

## Known issues:

- The auto-crafting algorithm is not *perfect*. For craft recipes requiring items in a group, only the item with the
greatest count from the player's inventory will be utilized.
- Items in multiple groups will not always display correctly in craft view.

## License

Code is licensed under the GNU LGPL v3.0. Images and other media are CC BY-SA 4.0 unless otherwise noted.

The following images are from Minetest Game, and their respective licenses apply:

```
cg_plus_icon_autocrafting.png Based on default_tool_stonepick.png
cg_plus_icon_clear.png From creative_clear_icon.png
cg_plus_icon_cooking.png From default_furnace_front_active.png
cg_plus_icon_digging.png From default_tool_stonepick.png
cg_plus_icon_fuel.png From default_furnace_fire_fg.png
cg_plus_icon_next.png From creative_next_icon.png
cg_plus_icon_prev.png From creative_prev_icon.png
cg_plus_icon_search.png From creative_search_icon.png
```
219 changes: 219 additions & 0 deletions api.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
-- TODO: aliases?

local get_drops = function(item, def)
local normalDrops = {}
local randomDrops = {}

if type(def.drop) == "table" then
-- Handle complex drops. This is the method used by Unified Inventory.
local maxStart = true
local itemsLeft = def.drop.max_items
local dropTables = def.drop.items or {}

local dStack, dName, dCount

for _, dropTable in ipairs(dropTables) do
if itemsLeft and itemsLeft <= 0 then break end

for _, dropItem in ipairs(dropTable.items) do
dStack = ItemStack(dropItem)
dName = dStack:get_name()
dCount = dStack:get_count()

if dCount > 0 and dName ~= item then
if #dropTable.items == 1 and dropTable.rarity == 1 and maxStart then
normalDrops[dName] = (normalDrops[dName] or 0) + dCount

if itemsLeft then
itemsLeft = itemsLeft - 1
if itemsLeft <= 0 then break end
end
else
if itemsLeft then maxStart = false end

randomDrops[dName] = (randomDrops[dName] or 0) + dCount
end
end
end
end
else
-- Handle simple, one-item drops.
local dStack = ItemStack(def.drop)

if not dStack:is_empty() and dStack:get_name() ~= item then
normalDrops[dStack:get_name()] = dStack:get_count()
end
end

return normalDrops, randomDrops
end

cg.build_item_list = function()
local startTime = minetest.get_us_time()
cg.items_all.list = {}

for item, def in pairs(minetest.registered_items) do
if def.description and def.description ~= "" and
minetest.get_item_group(item, "not_in_creative_inventory") == 0 then
table.insert(cg.items_all.list, item)
cg.crafts[item] = minetest.get_all_craft_recipes(item) or {}
end
end

local def, fuel, decremented

for _, item in ipairs(cg.items_all.list) do
def = minetest.registered_items[item]

fuel, decremented = minetest.get_craft_result({method = "fuel", width = 0, items = {ItemStack(item)}})

if fuel.time > 0 then
table.insert(cg.crafts[item], {
type = "fuel",
items = {item},
output = decremented.items[1]:to_string(),
time = fuel.time,
})
end

if def.drop then
local normalDrops, randomDrops = get_drops(item, def)

for dItem, dCount in pairs(normalDrops) do
if cg.crafts[dItem] then
table.insert(cg.crafts[dItem], {
type = "digging",
width = 0,
items = {item},
output = ItemStack({name = dItem, count = dCount}):to_string()
})
end
end

for dItem, dCount in pairs(randomDrops) do
if cg.crafts[dItem] then
table.insert(cg.crafts[dItem], {
type = "digging_chance",
width = 0,
items = {item},
output = ItemStack({name = dItem, count = dCount}):to_string()
})
end
end
end

for group, _ in pairs(def.groups) do
if not cg.group_stereotypes[group] then
cg.group_stereotypes[group] = item
end
end
end

table.sort(cg.items_all.list)
cg.items_all.num_pages = math.ceil(#cg.items_all.list / cg.PAGE_ITEMS)

minetest.log("info", string.format("[cg_plus] Finished building item list in %.3f s.",
(minetest.get_us_time() - startTime) / 1000000))
end

cg.filter_items = function(player, filter)
local playerName = player:get_player_name()

if not filter or filter == "" then
cg.items_filtered[playerName] = nil
return
end

cg.items_filtered[playerName] = {list = {}}

local groupFilter = string.sub(filter, 1, 6) == "group:" and filter:sub(7)

if groupFilter and cg.group_search then
-- Search by group
local groups = string.split(groupFilter, ",")
local isInGroups

for _, item in ipairs(cg.items_all.list) do
isInGroups = true

for idx = 1, math.min(#groups, cg.group_search_max) do
if minetest.get_item_group(item, groups[idx]) == 0 then
isInGroups = false
break
end
end

if isInGroups then
table.insert(cg.items_filtered[playerName].list, item)
end
end
else
-- Regular search
for _, item in ipairs(cg.items_all.list) do
if item:lower():find(filter, 1, true) or
minetest.registered_items[item].description:lower():find(filter, 1, true) then
table.insert(cg.items_filtered[playerName].list, item)
end
end
end

cg.items_filtered[playerName].num_pages = math.ceil(#cg.get_item_list(player).list / cg.PAGE_ITEMS)
end

cg.parse_craft = function(craft)
local type = craft.type
local template = cg.craft_types[type] or {}

if craft.width == 0 and template.alt_zero_width then
type = template.alt_zero_width
template = cg.craft_types[template.alt_zero_width] or {}
end

local newCraft = {
type = type,
items = {},
output = craft.output,
}

if template.get_infotext then
newCraft.infotext = template.get_infotext(craft) or ""
end

local width = math.max(craft.width or 0, 1)

if template.get_grid_size then
newCraft.grid_size = template.get_grid_size(craft)
else
newCraft.grid_size = {x = width, y = math.ceil(table.maxn(craft.items) / width)}
end

if template.inherit_width then
-- For shapeless recipes, there is no need to modify the item list.
newCraft.items = craft.items
else
-- The craft's width is not always the same as the grid size, so items need to be shifted around.
for idx, item in pairs(craft.items) do
newCraft.items[idx + (newCraft.grid_size.x - width) * math.floor((idx - 1) / width)] = item
end
end

return newCraft
end

cg.get_item_list = function(player)
return cg.items_filtered[player:get_player_name()] or cg.items_all
end

cg.register_craft_type = function(name, def)
cg.craft_types[name] = def
end

cg.register_group_stereotype = function(group, item)
cg.group_stereotypes[group] = item
end

minetest.register_on_mods_loaded(cg.build_item_list)

minetest.register_on_leaveplayer(function(player, timed_out)
cg.items_filtered[player:get_player_name()] = nil
end)
124 changes: 124 additions & 0 deletions autocrafting.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
local add_or_create = function(t, i, n)
t[i] = t[i] and t[i] + n or n
end

local get_group_item = function(invCache, groups)
local maxCount = 0
local maxItem
local isInGroups

for item, count in pairs(invCache) do
isInGroups = true

for _, group in ipairs(groups) do
if minetest.get_item_group(item, group) == 0 then
isInGroups = false
break
end
end

if isInGroups and count > maxCount then
maxItem = item
maxCount = count
end
end

return maxItem
end

cg.auto_get_craftable = function(player, craft)
local inv = player:get_inventory():get_list("main")
local invCache = {}

-- Create a cache of the inventory with itemName = count pairs. This speeds up searching for items.
for _, stack in ipairs(inv) do
if stack:get_count() > 0 then
add_or_create(invCache, stack:get_name(), stack:get_count())
end
end

local reqItems = {}
local reqGroups = {}

-- Find out how many of each item/group is required to craft one item.
for _, item in pairs(craft.items) do
if item:sub(1, 6) == "group:" then
add_or_create(reqGroups, item, 1)
else
add_or_create(reqItems, item, 1)
end
end

local gMaxItem

-- For each group, find the item in that group from the player's inventory with the largest count.
for group, count in pairs(reqGroups) do
gMaxItem = get_group_item(invCache, group:sub(7):split(","))

if gMaxItem then
add_or_create(reqItems, gMaxItem, count)
else
return 0
end
end

local craftable = 1000

for item, count in pairs(reqItems) do
if invCache[item] then
craftable = math.min(craftable, math.floor(invCache[item] / count))
else
return 0
end

-- We can't craft more than the stack_max of our ingredients.
if minetest.registered_items[item].stack_max then
craftable = math.min(craftable, minetest.registered_items[item].stack_max)
end
end

return craftable
end

cg.auto_craft = function(player, craft, num)
inv = player:get_inventory()

if craft.width > inv:get_width("craft") or table.maxn(craft.items) > inv:get_size("craft") then return end

local width = craft.width == 0 and inv:get_width("craft") or craft.width
local stack, invCache
local groupCache = {}

for idx, item in pairs(craft.items) do
-- Shift the indices so the items in the craft go to the right spots on the crafting grid.
idx = idx + (inv:get_width("craft") - width) * math.floor((idx - 1) / width)

if item:sub(1, 6) == "group:" then
-- Create an inventory cache.
if not invCache then
invCache = {}

for _, stack in ipairs(inv:get_list("main")) do
if stack:get_count() > 0 then
add_or_create(invCache, stack:get_name(), stack:get_count())
end
end
end

-- Get the most plentiful item in the group.
if not groupCache[item] then
groupCache[item] = get_group_item(invCache, item:sub(7):split(","))
end

-- Move the selected item.
if groupCache[item] then
stack = inv:remove_item("main", ItemStack({name = groupCache[item], count = num}))
inv:set_stack("craft", idx, stack)
end
else
-- Move the item.
stack = inv:remove_item("main", ItemStack({name = item, count = num}))
inv:set_stack("craft", idx, stack)
end
end
end
Loading

0 comments on commit 9fea60e

Please sign in to comment.