Better icons, try to improve code

master
random-geek 2020-10-05 15:28:27 -07:00
parent fc53035ec7
commit 677e3d81ed
16 changed files with 436 additions and 269 deletions

View File

@ -1,10 +1,15 @@
# Crafting Guide Plus
[![ContentDB](https://content.minetest.net/packages/random_geek/cg_plus/shields/downloads/)](https://content.minetest.net/packages/random_geek/cg_plus/)
![screenshot](screenshot.png)
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.
jp's Crafting Guide, Unified Inventory, and Minetest Game's builtin crafting guide.
Note: If `mtg_craftguide` is present, CGP will override its page in the inventory. You may want to remove
`mtg_craftguide` entirely for optimization purposes.
## Features:
@ -27,12 +32,8 @@ Code is licensed under the GNU LGPL v3.0. Images and other media are CC BY-SA 4.
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
cg_plus_icon_autocrafting.png Adapted from default_tool_stonepick.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
```

128
api.lua
View File

@ -1,6 +1,6 @@
-- TODO: aliases?
local get_drops = function(item, def)
local function get_drops(item, def)
local normalDrops = {}
local randomDrops = {}
@ -13,7 +13,9 @@ local get_drops = function(item, def)
local dStack, dName, dCount
for _, dropTable in ipairs(dropTables) do
if itemsLeft and itemsLeft <= 0 then break end
if itemsLeft and itemsLeft <= 0 then
break
end
for _, dropItem in ipairs(dropTable.items) do
dStack = ItemStack(dropItem)
@ -21,15 +23,20 @@ local get_drops = function(item, def)
dCount = dStack:get_count()
if dCount > 0 and dName ~= item then
if #dropTable.items == 1 and dropTable.rarity == 1 and maxStart 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
if itemsLeft <= 0 then
break
end
end
else
if itemsLeft then maxStart = false end
if itemsLeft then
maxStart = false
end
randomDrops[dName] = (randomDrops[dName] or 0) + dCount
end
@ -48,14 +55,16 @@ local get_drops = function(item, def)
return normalDrops, randomDrops
end
cg.build_item_list = function()
function cg.build_item_list()
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 and
minetest.get_item_group(item, "not_in_craft_guide") == 0 then
if def.description and def.description ~= ""
and minetest.get_item_group(item,
"not_in_creative_inventory") == 0
and minetest.get_item_group(item,
"not_in_craft_guide") == 0 then
table.insert(cg.items_all.list, item)
cg.crafts[item] = minetest.get_all_craft_recipes(item) or {}
end
@ -66,7 +75,11 @@ cg.build_item_list = function()
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)}})
fuel, decremented = minetest.get_craft_result({
method = "fuel",
width = 0,
items = {ItemStack(item)}
})
if fuel.time > 0 then
table.insert(cg.crafts[item], {
@ -83,22 +96,28 @@ cg.build_item_list = function()
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()
})
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()
})
type = "digging_chance",
width = 0,
items = {item},
output = ItemStack({
name = dItem,
count = dCount
}):to_string()
})
end
end
end
@ -113,23 +132,29 @@ cg.build_item_list = function()
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))
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)
function cg.filter_items(player, filter)
local playerName = player:get_player_name()
local playerData = cg.player_data[playerName]
if not filter or filter == "" then
cg.items_filtered[playerName] = nil
playerData.items = nil
return
end
cg.items_filtered[playerName] = {list = {}}
playerData.items = {list = {}}
filter = filter:lower()
local groupFilter = string.sub(filter, 1, 6) == "group:" and filter:sub(7)
if groupFilter and cg.group_search then
if groupFilter and cg.GROUP_SEARCH then
-- Search by group
local groups = string.split(groupFilter, ",")
local isInGroups
@ -137,7 +162,7 @@ cg.filter_items = function(player, filter)
for _, item in ipairs(cg.items_all.list) do
isInGroups = true
for idx = 1, math.min(#groups, cg.group_search_max) do
for idx = 1, math.min(#groups, cg.GROUP_SEARCH_MAX) do
if minetest.get_item_group(item, groups[idx]) == 0 then
isInGroups = false
break
@ -145,23 +170,28 @@ cg.filter_items = function(player, filter)
end
if isInGroups then
table.insert(cg.items_filtered[playerName].list, item)
table.insert(playerData.items.list, item)
end
end
else
-- Regular search
local langCode = playerData.lang_code
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)
if item:lower():find(filter, 1, true)
or minetest.get_translated_string(langCode,
minetest.registered_items[item].description)
:lower():find(filter, 1, true) then
table.insert(playerData.items.list, item)
end
end
end
cg.items_filtered[playerName].num_pages = math.ceil(#cg.get_item_list(player).list / cg.PAGE_ITEMS)
playerData.items.num_pages =
math.ceil(#cg.get_item_list(player).list / cg.PAGE_ITEMS)
end
cg.parse_craft = function(craft)
function cg.parse_craft(craft)
local type = craft.type
local template = cg.craft_types[type] or {}
@ -185,36 +215,50 @@ cg.parse_craft = function(craft)
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)}
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.
-- 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
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
function cg.get_item_list(player)
return cg.player_data[player:get_player_name()].items or cg.items_all
end
cg.register_craft_type = function(name, def)
function cg.register_craft_type(name, def)
cg.craft_types[name] = def
end
cg.register_group_stereotype = function(group, item)
function cg.register_group_stereotype(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
minetest.register_on_joinplayer(function(player)
local playerName = player:get_player_name()
local langCode = minetest.get_player_information(playerName).lang_code
cg.player_data[playerName] = {
lang_code = langCode
}
end)
minetest.register_on_leaveplayer(function(player, timed_out)
cg.player_data[player:get_player_name()] = nil
end)

View File

@ -1,8 +1,8 @@
local add_or_create = function(t, i, n)
local function add_or_create(t, i, n)
t[i] = t[i] and t[i] + n or n
end
local get_group_item = function(invCache, groups)
local function get_group_item(invCache, groups)
local maxCount = 0
local maxItem
local isInGroups
@ -26,11 +26,12 @@ local get_group_item = function(invCache, groups)
return maxItem
end
cg.auto_get_craftable = function(player, craft)
function cg.auto_get_craftable(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.
-- 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())
@ -51,7 +52,8 @@ cg.auto_get_craftable = function(player, craft)
local gMaxItem
-- For each group, find the item in that group from the player's inventory with the largest count.
-- 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(","))
@ -73,15 +75,16 @@ cg.auto_get_craftable = function(player, craft)
-- 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)
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()
function cg.auto_craft(player, craft, num)
local inv = player:get_inventory()
if not inv:is_empty("craft") then
-- Attempt to move items to the player's main inventory.
@ -94,12 +97,16 @@ cg.auto_craft = function(player, craft, num)
-- Check again, and return if not all items were moved.
if not inv:is_empty("craft") then
minetest.chat_send_player(player:get_player_name(), cg.S("Item could not be crafted!"))
minetest.chat_send_player(player:get_player_name(),
cg.S("Item could not be crafted!"))
return
end
end
if craft.width > inv:get_width("craft") or table.maxn(craft.items) > inv:get_size("craft") then return end
if craft.width > inv:get_width("craft")
or table.maxn(craft.items) > inv:get_size("craft") then
return
end
local invList = inv:get_list("main")
local width = craft.width == 0 and inv:get_width("craft") or craft.width
@ -107,8 +114,10 @@ cg.auto_craft = function(player, craft, num)
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)
-- 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.
@ -117,37 +126,43 @@ cg.auto_craft = function(player, craft, num)
for _, stack in ipairs(invList) do
if stack:get_count() > 0 then
add_or_create(invCache, stack:get_name(), stack:get_count())
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(","))
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}))
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}))
stack = inv:remove_item("main",
ItemStack({name = item, count = num}))
inv:set_stack("craft", idx, stack)
end
end
end
minetest.register_on_player_inventory_action(function(player, action, inventory, inventory_info)
-- Hide the autocrafting menu when the player drops an item.
if cg.autocrafting and inventory_info.listname == "main" then
local context = sfinv.get_or_create_context(player)
minetest.register_on_player_inventory_action(
function(player, action, inventory, inventory_info)
-- Hide the autocrafting menu when the player drops an item.
if cg.AUTOCRAFTING and inventory_info.listname == "main" then
local context = sfinv.get_or_create_context(player)
if context.cg_auto_menu then
context.cg_auto_menu = false
sfinv.set_player_inventory_formspec(player)
if context.cg_auto_menu then
context.cg_auto_menu = false
sfinv.set_player_inventory_formspec(player)
end
end
end
end)
)

View File

@ -1,8 +1,8 @@
cg = {
PAGE_WIDTH = 8,
PAGE_ITEMS = 24,
PAGE_ITEMS = 32,
items_all = {},
items_filtered = {},
player_data = {},
crafts = {},
craft_types = {},
group_stereotypes = {},
@ -10,18 +10,17 @@ cg = {
local settings = minetest.settings
cg.autocrafting = settings:get_bool("cg_plus_autocrafting", true)
cg.group_search = settings:get_bool("cg_plus_group_search", true)
cg.group_search_max = tonumber(settings:get("cg_plus_group_search_max")) or 5
cg.AUTOCRAFTING = settings:get_bool("cg_plus_autocrafting", true)
cg.GROUP_SEARCH = settings:get_bool("cg_plus_group_search", true)
cg.GROUP_SEARCH_MAX = tonumber(settings:get("cg_plus_group_search_max")) or 5
cg.S = minetest.get_translator("cg_plus")
local F = minetest.formspec_escape
local path = minetest.get_modpath("cg_plus")
dofile(path .. "/api.lua")
if cg.autocrafting then
if cg.AUTOCRAFTING then
dofile(path .. "/autocrafting.lua")
end
@ -42,7 +41,7 @@ cg.register_craft_type("normal", {
else
return {x = sideLen, y = sideLen}
end
end,
end
})
cg.register_craft_type("shapeless", {
@ -59,58 +58,61 @@ cg.register_craft_type("shapeless", {
local sideLen = math.ceil(math.sqrt(numItems))
return {x = sideLen, y = sideLen}
end
end,
end
})
cg.register_craft_type("cooking", {
description = F(cg.S("Cooking")),
inherit_width = true,
arrow_icon = "cg_plus_arrow_small.png^cg_plus_icon_cooking.png",
arrow_icon = "cg_plus_arrow_bottom.png^cg_plus_icon_cooking.png",
get_grid_size = function(craft)
return {x = 1, y = 1}
end,
get_infotext = function(craft)
return minetest.colorize("#FFFF00", F(cg.S("Time: @1 s", craft.width or 0)))
end,
return minetest.colorize("#FFFF00",
F(cg.S("Time: @1 s", craft.width or 0)))
end
})
cg.register_craft_type("fuel", {
description = F(cg.S("Fuel")),
inherit_width = true,
arrow_icon = "cg_plus_arrow_small.png^cg_plus_icon_fuel.png",
arrow_icon = "cg_plus_arrow_bottom.png^cg_plus_icon_fuel.png",
get_grid_size = function(craft)
return {x = 1, y = 1}
end,
get_infotext = function(craft)
return minetest.colorize("#FFFF00", F(cg.S("Time: @1 s", craft.time or 0)))
end,
return minetest.colorize("#FFFF00",
F(cg.S("Time: @1 s", craft.time or 0)))
end
})
cg.register_craft_type("digging", {
description = F(cg.S("Digging")),
inherit_width = true,
arrow_icon = "cg_plus_arrow_small.png^cg_plus_icon_digging.png",
arrow_icon = "cg_plus_arrow_bottom.png^cg_plus_icon_digging.png",
get_grid_size = function(craft)
return {x = 1, y = 1}
end,
end
})
cg.register_craft_type("digging_chance", {
description = F(cg.S("Digging@n(by chance)")),
inherit_width = true,
arrow_icon = "cg_plus_arrow_small.png^cg_plus_icon_digging.png",
arrow_icon = "cg_plus_arrow_bottom.png^cg_plus_icon_digging.png",
get_grid_size = function(craft)
return {x = 1, y = 1}
end,
end
})
cg.register_group_stereotype("mesecon_conductor_craftable", "mesecons:wire_00000000_off")
cg.register_group_stereotype("mesecon_conductor_craftable",
"mesecons:wire_00000000_off")
if minetest.get_modpath("default") then
cg.register_group_stereotype("stone", "default:stone")

View File

@ -1,69 +1,94 @@
local F = minetest.formspec_escape
cg.update_filter = function(player, context, filter, force)
if not force and (filter or "") == context.cg_filter then return end
function cg.update_filter(player, context, filter, force)
filter = filter or ""
if not force and filter:lower() == (context.cg_filter or ""):lower() then
return
end
context.cg_page = 0
context.cg_filter = filter or ""
context.cg_filter = filter
cg.filter_items(player, context.cg_filter)
end
cg.update_selected_item = function(player, context, item, force)
if not force and item == context.cg_selected_item then return end
function cg.update_selected_item(player, context, item, force)
if not force and item == context.cg_selected_item then
return
end
if item then context.cg_craft_page = 0 end
if item then
context.cg_craft_page = 0
end
context.cg_selected_item = item
context.cg_auto_menu = false
end
local make_item_button = function(formspec, x, y, size, name)
local function make_item_button(formspec, x, y, size, name)
if name and name ~= "" then
local groups, buttonText
if name:sub(1, 6) == "group:" then
groups = name:sub(7):split(",")
buttonText = #groups > 1 and ("G " .. #groups) or "G"
buttonText = #groups == 1 and "G" or ("G " .. #groups)
name = name:gsub(",", "/")
end
formspec[#formspec + 1] = string.format("item_image_button[%.2f,%.2f;%.2f,%.2f;%s;cgitem_%s;%s]",
x, y, size, size,
groups and (cg.group_stereotypes[groups[1]] or "") or name,
name:match("^%S+"), -- Keep only the item name, not the quantity.
buttonText or ""
)
formspec[#formspec + 1] = string.format(
"item_image_button[%.2f,%.2f;%.2f,%.2f;%s;cgitem_%s;%s]",
x, y, size, size,
groups and (cg.group_stereotypes[groups[1]] or "") or name,
name:match("^%S+"), -- Keep only the item name, not the quantity.
buttonText or ""
)
if groups then
local tooltipText
if #groups == 1 then
tooltipText = F(cg.S(
"Any item in group: @1",
minetest.colorize("#72FF63", groups[1])
))
else
tooltipText = F(cg.S(
"Any item in groups: @1",
minetest.colorize("#72FF63", table.concat(groups, ", "))
))
end
formspec[#formspec + 1] = string.format("tooltip[cgitem_%s;%s]",
name,
#groups > 1 and
F(cg.S("Any item in groups: @1", minetest.colorize("#72FF63", table.concat(groups, ", ")))) or
F(cg.S("Any item in group: @1", minetest.colorize("#72FF63", groups[1])))
)
name, tooltipText)
end
else
size = size * 0.8 + 0.2
formspec[#formspec + 1] = string.format("image[%.2f,%.2f;%.2f,%.2f;gui_hb_bg.png]", x, y, size, size)
formspec[#formspec + 1] = string.format(
"image[%.2f,%.2f;%.2f,%.2f;gui_hb_bg.png]",
x, y, size, size
)
end
end
local make_item_grid = function(formspec, player, context)
local function make_item_grid(formspec, player, context)
local itemList = cg.get_item_list(player)
context.cg_page = context.cg_page or 0
formspec[#formspec + 1] = [[
image_button[2.4,3.7;0.8,0.8;cg_plus_icon_search.png;cg_search;]
image_button[3.1,3.7;0.8,0.8;cg_plus_icon_clear.png;cg_clear;]
image_button[5.1,3.7;0.8,0.8;cg_plus_icon_prev.png;cg_prev;]
image_button[7.1,3.7;0.8,0.8;cg_plus_icon_next.png;cg_next;]
]]
-- Buttons
formspec[#formspec + 1] =
"real_coordinates[true]" ..
"image_button[3.425,5.3;0.8,0.8;cg_plus_icon_search.png;cg_search;]" ..
"image_button[4.325,5.3;0.8,0.8;cg_plus_icon_clear.png;cg_clear;]" ..
"image_button[6.625,5.3;0.8,0.8;cg_plus_icon_prev.png;cg_prev;]" ..
"image_button[9.325,5.3;0.8,0.8;cg_plus_icon_next.png;cg_next;]"
formspec[#formspec + 1] = string.format("label[0,0;%s]", F(cg.S("Crafting Guide")))
formspec[#formspec + 1] = string.format("field[0.3,3.9;2.5,1;cg_filter;;%s]", F(context.cg_filter or ""))
-- Search box
formspec[#formspec + 1] = string.format(
"field[0.375,5.3;2.95,0.8;cg_filter;;%s]",
F(context.cg_filter or "")
)
formspec[#formspec + 1] = "field_close_on_enter[cg_filter;false]"
formspec[#formspec + 1] = string.format("label[6,3.8;%i / %i]", context.cg_page + 1, itemList.num_pages)
-- Page number
formspec[#formspec + 1] = string.format("label[7.75,5.7;%i / %i]",
context.cg_page + 1, itemList.num_pages)
local startIdx = context.cg_page * cg.PAGE_ITEMS + 1
local item
@ -72,64 +97,86 @@ local make_item_grid = function(formspec, player, context)
item = itemList.list[startIdx + itemIdx]
if item then
formspec[#formspec + 1] = string.format("item_image_button[%.2f,%.2f;1,1;%s;cgitem_%s;]",
itemIdx % cg.PAGE_WIDTH,
math.floor(itemIdx / cg.PAGE_WIDTH) + 0.5,
item, item
)
formspec[#formspec + 1] = string.format(
"item_image_button[%.2f,%.2f;1,1;%s;cgitem_%s;]",
(itemIdx % cg.PAGE_WIDTH) * 1.25 + 0.375,
math.floor(itemIdx / cg.PAGE_WIDTH) * 1.25 + 0.375,
item, item
)
end
end
end
local make_craft_preview = function(formspec, player, context)
formspec[#formspec + 1] = [[
image_button[7.1,0.1;0.8,0.8;cg_plus_icon_prev.png;cg_craft_close;]
image[0.1,0.1;0.8,0.8;gui_hb_bg.png]
]]
local function make_craft_preview(formspec, player, context)
formspec[#formspec + 1] =
"real_coordinates[true]" ..
"image_button[9.325,0.375;0.8,0.8;" ..
"cg_plus_icon_clear.png;cg_craft_close;]" ..
"image[0.375,0.375;0.8,0.8;gui_hb_bg.png]"
local item = context.cg_selected_item
formspec[#formspec + 1] = string.format("item_image[0.1,0.1;0.8,0.8;%s]", item)
formspec[#formspec + 1] = string.format("label[1,0;%s]",
cg.crafts[item] and minetest.registered_items[item].description or item)
-- Item image
formspec[#formspec + 1] = string.format(
"item_image[0.375,0.375;0.8,0.8;%s]",
item
)
-- Item name
formspec[#formspec + 1] = string.format(
"label[1.5,0.6;%s]",
cg.crafts[item] and minetest.registered_items[item].description or item
)
-- No recipes label, if applicable
local crafts = cg.crafts[item]
if not crafts or #crafts == 0 then
formspec[#formspec + 1] = string.format("label[1,0.5;%s]", F(cg.S("There are no recipes for this item.")))
formspec[#formspec + 1] = string.format("label[2.875,3.2;%s]",
F(cg.S("There are no recipes for this item.")))
return
end
-- Previous/next craft buttons, page number
if #crafts > 1 then
formspec[#formspec + 1] = [[
image_button[1.85,3.7;0.8,0.8;cg_plus_icon_prev.png;cg_craft_prev;]
image_button[3.85,3.7;0.8,0.8;cg_plus_icon_next.png;cg_craft_next;]
]]
formspec[#formspec + 1] = string.format("label[2.75,3.8;%i / %i]", context.cg_craft_page + 1, #crafts)
formspec[#formspec + 1] =
"image_button[2.875,5.3;0.8,0.8;" ..
"cg_plus_icon_prev.png;cg_craft_prev;]" ..
"image_button[5.575,5.3;0.8,0.8;" ..
"cg_plus_icon_next.png;cg_craft_next;]"
formspec[#formspec + 1] = string.format("label[4,5.7;%i / %i]",
context.cg_craft_page + 1, #crafts)
end
local craft = cg.parse_craft(crafts[context.cg_craft_page + 1])
local template = cg.craft_types[craft.type] or {}
if cg.autocrafting and template.uses_crafting_grid then
formspec[#formspec + 1] = "image_button[0.1,3.7;0.8,0.8;cg_plus_icon_autocrafting.png;cg_auto_menu;]"
formspec[#formspec + 1] = string.format("tooltip[cg_auto_menu;%s]", F(cg.S("Craft this recipe")))
-- Auto-crafting buttons
if cg.AUTOCRAFTING and template.uses_crafting_grid then
formspec[#formspec + 1] = "image_button[0.375,5.3;0.8,0.8;" ..
"cg_plus_icon_autocrafting.png;cg_auto_menu;]"
formspec[#formspec + 1] = string.format("tooltip[cg_auto_menu;%s]",
F(cg.S("Craft this recipe")))
if context.cg_auto_menu then
local num = 1
local yPos = 3
local yPos = 4.3
while true do
num = math.min(num, context.cg_auto_max)
formspec[#formspec + 1] = string.format("button[0.1,%.2f;0.8,0.8;cg_auto_%i;%i]", yPos, num, num)
formspec[#formspec + 1] = string.format(
"tooltip[cg_auto_%i;%s]",
num,
num == 1 and F(cg.S("Craft @1 item", num)) or F(cg.S("Craft @1 items", num))
)
"button[0.375,%.2f;0.8,0.8;cg_auto_%i;%i]",
yPos, num, num
)
formspec[#formspec + 1] = string.format(
"tooltip[cg_auto_%i;%s]",
num,
num == 1
and F(cg.S("Craft @1 item", num))
or F(cg.S("Craft @1 items", num))
)
if num < context.cg_auto_max then
num = num * 10
yPos = yPos - 0.7
yPos = yPos - 1
else
break
end
@ -137,127 +184,168 @@ local make_craft_preview = function(formspec, player, context)
end
end
formspec[#formspec + 1] = string.format("label[5,0.5;%s]", template.description or "")
formspec[#formspec + 1] = string.format("label[5,1;%s]", craft.infotext or "")
formspec[#formspec + 1] = string.format("image[4.75,1.5;1,1;%s]",
template.arrow_icon or "cg_plus_arrow.png")
-- Craft type/info text
formspec[#formspec + 1] = string.format("label[6.7,1.8;%s]",
template.description or "")
formspec[#formspec + 1] = string.format("label[6.7,2.4;%s]",
craft.infotext or "")
local slotSize = math.min(3 / math.max(craft.grid_size.x, craft.grid_size.y), 1)
local xOffset = 4.75 - craft.grid_size.x * slotSize
local yOffset = 2 - craft.grid_size.y * slotSize * 0.5
-- Draw craft item grid, feat. maths.
-- Determine max number of grid slots that could fit on one side.
-- Squares shouldn't take up more than a third of the grid area.
local gridMax = math.max(math.max(craft.grid_size.x, craft.grid_size.y), 3)
-- Determine distance between crafting grid slots.
-- <grid area side length> / (<num slots per side> - <extra padding>)
local slotDist = 3.5 / (gridMax - 0.2)
-- Determine upper-left corner of crafting squares
-- <right x of grid area> - (<grid width> - <extra padding>) * slotDist
local xOffset = 6.375 - (craft.grid_size.x - 0.2) * slotDist
-- <center y of grid area> -
-- (<grid height> - <extra padding>) * slotDist * 0.5
local yOffset = 3.2 - (craft.grid_size.y - 0.2) * slotDist * 0.5
for idx = 1, craft.grid_size.x * craft.grid_size.y do
make_item_button(formspec,
(idx - 1) % craft.grid_size.x * slotSize + xOffset,
math.floor((idx - 1) / craft.grid_size.y) * slotSize + yOffset,
slotSize,
craft.items[idx]
)
make_item_button(
formspec,
((idx - 1) % craft.grid_size.x) * slotDist + xOffset,
math.floor((idx - 1) / craft.grid_size.y) * slotDist + yOffset,
slotDist * 0.8, -- 1 - <padding amount>
craft.items[idx]
)
end
make_item_button(formspec, 5.75, 1.5, 1, craft.output)
-- Craft arrow
formspec[#formspec + 1] = string.format("image[6.625,2.7;1,1;%s]",
template.arrow_icon or "cg_plus_arrow.png")
-- Craft output
make_item_button(formspec, 7.875, 2.7, 1, craft.output)
end
sfinv.register_page("cg_plus:crafting_guide", {
title = "Crafting Guide",
get = function(self, player, context)
local formspec = {[[
image[0,4.75;1,1;gui_hb_bg.png]
image[1,4.75;1,1;gui_hb_bg.png]
image[2,4.75;1,1;gui_hb_bg.png]
image[3,4.75;1,1;gui_hb_bg.png]
image[4,4.75;1,1;gui_hb_bg.png]
image[5,4.75;1,1;gui_hb_bg.png]
image[6,4.75;1,1;gui_hb_bg.png]
image[7,4.75;1,1;gui_hb_bg.png]
]]}
--[[
sfinv registration
]]
if context.cg_selected_item then
make_craft_preview(formspec, player, context)
else
make_item_grid(formspec, player, context)
end
local function page_get(self, player, context)
local formspec = {}
return sfinv.make_formspec(player, context, table.concat(formspec), true)
end,
if context.cg_selected_item then
make_craft_preview(formspec, player, context)
else
make_item_grid(formspec, player, context)
end
on_player_receive_fields = function(self, player, context, fields)
if fields.cg_craft_close then
context.cg_selected_item = nil
context.cg_auto_menu = false
elseif fields.cg_prev and context.cg_page then
context.cg_page = context.cg_page - 1
elseif fields.cg_next and context.cg_page then
context.cg_page = context.cg_page + 1
elseif fields.cg_craft_prev and context.cg_craft_page then
context.cg_craft_page = context.cg_craft_page - 1
context.cg_auto_menu = false
elseif fields.cg_craft_next and context.cg_craft_page then
context.cg_craft_page = context.cg_craft_page + 1
context.cg_auto_menu = false
elseif fields.cg_search or fields.key_enter_field == "cg_filter" then
cg.update_filter(player, context, fields.cg_filter)
elseif fields.cg_clear then
cg.update_filter(player, context, "", true)
elseif fields.cg_auto_menu and cg.autocrafting then
if not context.cg_auto_menu then
-- Make sure the craft is valid, in case the client is sending fake formspec fields.
local crafts = cg.crafts[context.cg_selected_item] or {}
local craft = crafts[context.cg_craft_page + 1]
return sfinv.make_formspec(player, context, table.concat(formspec), true)
end
if craft and cg.craft_types[craft.type] and cg.craft_types[craft.type].uses_crafting_grid then
context.cg_auto_menu = true
context.cg_auto_max = cg.auto_get_craftable(player, craft)
end
else
context.cg_auto_menu = false
end
else
for field, _ in pairs(fields) do
if field:sub(1, 7) == "cgitem_" then
local item = string.sub(field, 8)
if item:sub(1, 6) == "group:" then
if cg.group_search then
cg.update_filter(player, context, item:gsub("/", ","))
cg.update_selected_item(player, context, nil)
elseif cg.group_stereotypes[item:sub(7)] then
cg.update_selected_item(player, context, cg.group_stereotypes[item:sub(7)])
end
else
cg.update_selected_item(player, context, item)
end
break
elseif field:sub(1, 8) == "cg_auto_" and context.cg_auto_menu then
-- No need to sanity check, we already did that when showing the autocrafting menu.
local num = tonumber(field:sub(9))
if num > 0 and num <= context.cg_auto_max then
cg.auto_craft(player, cg.crafts[context.cg_selected_item][context.cg_craft_page + 1], num)
sfinv.set_page(player, "sfinv:crafting")
end
context.cg_auto_menu = false
break
end
end
end
-- Wrap around when the player presses the next button on the last page, or the previous button on the first.
if context.cg_page then
context.cg_page = context.cg_page % math.max(cg.get_item_list(player).num_pages, 1)
end
if context.cg_craft_page then
context.cg_craft_page = context.cg_craft_page % math.max(#(cg.crafts[context.cg_selected_item] or {}), 1)
end
-- Update the formspec.
sfinv.set_player_inventory_formspec(player, context)
end,
on_leave = function(self, player, context)
local function page_on_player_receive_fields(self, player, context, fields)
if fields.cg_craft_close then
context.cg_selected_item = nil
context.cg_auto_menu = false
end,
})
elseif fields.cg_prev and context.cg_page then
context.cg_page = context.cg_page - 1
elseif fields.cg_next and context.cg_page then
context.cg_page = context.cg_page + 1
elseif fields.cg_craft_prev and context.cg_craft_page then
context.cg_craft_page = context.cg_craft_page - 1
context.cg_auto_menu = false
elseif fields.cg_craft_next and context.cg_craft_page then
context.cg_craft_page = context.cg_craft_page + 1
context.cg_auto_menu = false
elseif fields.cg_search or fields.key_enter_field == "cg_filter" then
cg.update_filter(player, context, fields.cg_filter)
elseif fields.cg_clear then
cg.update_filter(player, context, "", true)
elseif fields.cg_auto_menu and cg.AUTOCRAFTING then
if not context.cg_auto_menu then
-- Make sure the craft is valid, in case the client is sending
-- fake formspec fields.
local crafts = cg.crafts[context.cg_selected_item] or {}
local craft = crafts[context.cg_craft_page + 1]
if craft and cg.craft_types[craft.type]
and cg.craft_types[craft.type].uses_crafting_grid then
context.cg_auto_menu = true
context.cg_auto_max = cg.auto_get_craftable(player, craft)
end
else
context.cg_auto_menu = false
end
else
for field, _ in pairs(fields) do
if field:sub(1, 7) == "cgitem_" then
local item = string.sub(field, 8)
if item:sub(1, 6) == "group:" then
if cg.GROUP_SEARCH then
cg.update_filter(player, context, item)
cg.update_selected_item(player, context, nil)
elseif cg.group_stereotypes[item:sub(7)] then
cg.update_selected_item(player, context,
cg.group_stereotypes[item:sub(7)])
end
else
cg.update_selected_item(player, context, item)
end
break
elseif field:sub(1, 8) == "cg_auto_"
and context.cg_auto_menu then
-- No need to sanity check, we already did that when
-- showing the autocrafting menu.
local num = tonumber(field:sub(9))
if num > 0 and num <= context.cg_auto_max then
cg.auto_craft(
player,
cg.crafts[context.cg_selected_item]
[context.cg_craft_page + 1],
num
)
sfinv.set_page(player, "sfinv:crafting")
end
context.cg_auto_menu = false
break
end
end
end
-- Wrap around when the player presses the next button on the last
-- page, or the previous button on the first.
if context.cg_page then
context.cg_page = context.cg_page %
math.max(cg.get_item_list(player).num_pages, 1)
end
if context.cg_craft_page then
context.cg_craft_page = context.cg_craft_page %
math.max(#(cg.crafts[context.cg_selected_item] or {}), 1)
end
-- Update the formspec.
sfinv.set_player_inventory_formspec(player, context)
end
local function page_on_leave(self, player, context)
context.cg_auto_menu = false
end
if sfinv.pages["mtg_craftguide:craftguide"] ~= nil then
-- Override MTG's crafting guide
sfinv.override_page("mtg_craftguide:craftguide", {
title = F(cg.S("Crafting Guide")),
get = page_get,
on_player_receive_fields = page_on_player_receive_fields,
on_leave = page_on_leave
})
else
sfinv.register_page("cg_plus:crafting_guide", {
title = F(cg.S("Crafting Guide")),
get = page_get,
on_player_receive_fields = page_on_player_receive_fields,
on_leave = page_on_leave
})
end

16
locale/template.txt Normal file
View File

@ -0,0 +1,16 @@
# textdomain:cg_plus
Crafting=
Mixing=
Cooking=
Fuel=
Time: @1 s=
Digging=
Digging@n(by chance)=
Crafting Guide=
Item could not be crafted!=
Any item in group: @1=
Any item in groups: @1=
Craft @1 item=
Craft @1 items=
There are no recipes for this item.=

View File

@ -1,3 +1,4 @@
name = cg_plus
description = An intuitive and full-featured craft guide and autocrafting mod which is compatible with sfinv.
depends = sfinv
optional_depends = mtg_craftguide

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 708 B

After

Width:  |  Height:  |  Size: 132 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 772 B

After

Width:  |  Height:  |  Size: 772 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 223 B

After

Width:  |  Height:  |  Size: 225 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 581 B

After

Width:  |  Height:  |  Size: 581 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 727 B

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 728 B

After

Width:  |  Height:  |  Size: 132 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 167 B