-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
fe05f7a
commit 9fea60e
Showing
17 changed files
with
766 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.