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 # 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) ![screenshot](screenshot.png)
Crafting Guide Plus is a simple and intuitive crafting guide and auto-crafting mod for Minetest. CGP is compatible with 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 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: ## 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: 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_autocrafting.png Adapted from 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_cooking.png From default_furnace_front_active.png cg_plus_icon_digging.png From default_tool_stonepick.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_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
``` ```

128
api.lua
View File

@ -1,6 +1,6 @@
-- TODO: aliases? -- TODO: aliases?
local get_drops = function(item, def) local function get_drops(item, def)
local normalDrops = {} local normalDrops = {}
local randomDrops = {} local randomDrops = {}
@ -13,7 +13,9 @@ local get_drops = function(item, def)
local dStack, dName, dCount local dStack, dName, dCount
for _, dropTable in ipairs(dropTables) do 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 for _, dropItem in ipairs(dropTable.items) do
dStack = ItemStack(dropItem) dStack = ItemStack(dropItem)
@ -21,15 +23,20 @@ local get_drops = function(item, def)
dCount = dStack:get_count() dCount = dStack:get_count()
if dCount > 0 and dName ~= item then 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 normalDrops[dName] = (normalDrops[dName] or 0) + dCount
if itemsLeft then if itemsLeft then
itemsLeft = itemsLeft - 1 itemsLeft = itemsLeft - 1
if itemsLeft <= 0 then break end if itemsLeft <= 0 then
break
end
end end
else else
if itemsLeft then maxStart = false end if itemsLeft then
maxStart = false
end
randomDrops[dName] = (randomDrops[dName] or 0) + dCount randomDrops[dName] = (randomDrops[dName] or 0) + dCount
end end
@ -48,14 +55,16 @@ local get_drops = function(item, def)
return normalDrops, randomDrops return normalDrops, randomDrops
end end
cg.build_item_list = function() function cg.build_item_list()
local startTime = minetest.get_us_time() local startTime = minetest.get_us_time()
cg.items_all.list = {} cg.items_all.list = {}
for item, def in pairs(minetest.registered_items) do for item, def in pairs(minetest.registered_items) do
if def.description and def.description ~= "" and if def.description and def.description ~= ""
minetest.get_item_group(item, "not_in_creative_inventory") == 0 and and minetest.get_item_group(item,
minetest.get_item_group(item, "not_in_craft_guide") == 0 then "not_in_creative_inventory") == 0
and minetest.get_item_group(item,
"not_in_craft_guide") == 0 then
table.insert(cg.items_all.list, item) table.insert(cg.items_all.list, item)
cg.crafts[item] = minetest.get_all_craft_recipes(item) or {} cg.crafts[item] = minetest.get_all_craft_recipes(item) or {}
end end
@ -66,7 +75,11 @@ cg.build_item_list = function()
for _, item in ipairs(cg.items_all.list) do for _, item in ipairs(cg.items_all.list) do
def = minetest.registered_items[item] 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 if fuel.time > 0 then
table.insert(cg.crafts[item], { table.insert(cg.crafts[item], {
@ -83,22 +96,28 @@ cg.build_item_list = function()
for dItem, dCount in pairs(normalDrops) do for dItem, dCount in pairs(normalDrops) do
if cg.crafts[dItem] then if cg.crafts[dItem] then
table.insert(cg.crafts[dItem], { table.insert(cg.crafts[dItem], {
type = "digging", type = "digging",
width = 0, width = 0,
items = {item}, items = {item},
output = ItemStack({name = dItem, count = dCount}):to_string() output = ItemStack({
}) name = dItem,
count = dCount
}):to_string()
})
end end
end end
for dItem, dCount in pairs(randomDrops) do for dItem, dCount in pairs(randomDrops) do
if cg.crafts[dItem] then if cg.crafts[dItem] then
table.insert(cg.crafts[dItem], { table.insert(cg.crafts[dItem], {
type = "digging_chance", type = "digging_chance",
width = 0, width = 0,
items = {item}, items = {item},
output = ItemStack({name = dItem, count = dCount}):to_string() output = ItemStack({
}) name = dItem,
count = dCount
}):to_string()
})
end end
end end
end end
@ -113,23 +132,29 @@ cg.build_item_list = function()
table.sort(cg.items_all.list) table.sort(cg.items_all.list)
cg.items_all.num_pages = math.ceil(#cg.items_all.list / cg.PAGE_ITEMS) 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.log("info",
(minetest.get_us_time() - startTime) / 1000000)) string.format(
"[cg_plus] Finished building item list in %.3f s.",
(minetest.get_us_time() - startTime) / 1000000
)
)
end end
cg.filter_items = function(player, filter) function cg.filter_items(player, filter)
local playerName = player:get_player_name() local playerName = player:get_player_name()
local playerData = cg.player_data[playerName]
if not filter or filter == "" then if not filter or filter == "" then
cg.items_filtered[playerName] = nil playerData.items = nil
return return
end end
cg.items_filtered[playerName] = {list = {}} playerData.items = {list = {}}
filter = filter:lower()
local groupFilter = string.sub(filter, 1, 6) == "group:" and filter:sub(7) 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 -- Search by group
local groups = string.split(groupFilter, ",") local groups = string.split(groupFilter, ",")
local isInGroups local isInGroups
@ -137,7 +162,7 @@ cg.filter_items = function(player, filter)
for _, item in ipairs(cg.items_all.list) do for _, item in ipairs(cg.items_all.list) do
isInGroups = true 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 if minetest.get_item_group(item, groups[idx]) == 0 then
isInGroups = false isInGroups = false
break break
@ -145,23 +170,28 @@ cg.filter_items = function(player, filter)
end end
if isInGroups then if isInGroups then
table.insert(cg.items_filtered[playerName].list, item) table.insert(playerData.items.list, item)
end end
end end
else else
-- Regular search -- Regular search
local langCode = playerData.lang_code
for _, item in ipairs(cg.items_all.list) do for _, item in ipairs(cg.items_all.list) do
if item:lower():find(filter, 1, true) or if item:lower():find(filter, 1, true)
minetest.registered_items[item].description:lower():find(filter, 1, true) then or minetest.get_translated_string(langCode,
table.insert(cg.items_filtered[playerName].list, item) minetest.registered_items[item].description)
:lower():find(filter, 1, true) then
table.insert(playerData.items.list, item)
end end
end 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 end
cg.parse_craft = function(craft) function cg.parse_craft(craft)
local type = craft.type local type = craft.type
local template = cg.craft_types[type] or {} local template = cg.craft_types[type] or {}
@ -185,36 +215,50 @@ cg.parse_craft = function(craft)
if template.get_grid_size then if template.get_grid_size then
newCraft.grid_size = template.get_grid_size(craft) newCraft.grid_size = template.get_grid_size(craft)
else 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 end
if template.inherit_width then if template.inherit_width then
-- For shapeless recipes, there is no need to modify the item list. -- For shapeless recipes, there is no need to modify the item list.
newCraft.items = craft.items newCraft.items = craft.items
else 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 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
end end
return newCraft return newCraft
end end
cg.get_item_list = function(player) function cg.get_item_list(player)
return cg.items_filtered[player:get_player_name()] or cg.items_all return cg.player_data[player:get_player_name()].items or cg.items_all
end end
cg.register_craft_type = function(name, def) function cg.register_craft_type(name, def)
cg.craft_types[name] = def cg.craft_types[name] = def
end end
cg.register_group_stereotype = function(group, item) function cg.register_group_stereotype(group, item)
cg.group_stereotypes[group] = item cg.group_stereotypes[group] = item
end end
minetest.register_on_mods_loaded(cg.build_item_list) minetest.register_on_mods_loaded(cg.build_item_list)
minetest.register_on_leaveplayer(function(player, timed_out) minetest.register_on_joinplayer(function(player)
cg.items_filtered[player:get_player_name()] = nil 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) 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 t[i] = t[i] and t[i] + n or n
end end
local get_group_item = function(invCache, groups) local function get_group_item(invCache, groups)
local maxCount = 0 local maxCount = 0
local maxItem local maxItem
local isInGroups local isInGroups
@ -26,11 +26,12 @@ local get_group_item = function(invCache, groups)
return maxItem return maxItem
end end
cg.auto_get_craftable = function(player, craft) function cg.auto_get_craftable(player, craft)
local inv = player:get_inventory():get_list("main") local inv = player:get_inventory():get_list("main")
local invCache = {} 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 for _, stack in ipairs(inv) do
if stack:get_count() > 0 then 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())
@ -51,7 +52,8 @@ cg.auto_get_craftable = function(player, craft)
local gMaxItem 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 for group, count in pairs(reqGroups) do
gMaxItem = get_group_item(invCache, group:sub(7):split(",")) 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. -- We can't craft more than the stack_max of our ingredients.
if minetest.registered_items[item].stack_max then 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
end end
return craftable return craftable
end end
cg.auto_craft = function(player, craft, num) function cg.auto_craft(player, craft, num)
inv = player:get_inventory() local inv = player:get_inventory()
if not inv:is_empty("craft") then if not inv:is_empty("craft") then
-- Attempt to move items to the player's main inventory. -- 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. -- Check again, and return if not all items were moved.
if not inv:is_empty("craft") then 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 return
end end
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 invList = inv:get_list("main")
local width = craft.width == 0 and inv:get_width("craft") or craft.width 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 = {} local groupCache = {}
for idx, item in pairs(craft.items) do 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. -- Shift the indices so the items in the craft go to the right spots on
idx = idx + (inv:get_width("craft") - width) * math.floor((idx - 1) / width) -- the crafting grid.
idx = (idx + (inv:get_width("craft") - width) *
math.floor((idx - 1) / width))
if item:sub(1, 6) == "group:" then if item:sub(1, 6) == "group:" then
-- Create an inventory cache. -- Create an inventory cache.
@ -117,37 +126,43 @@ cg.auto_craft = function(player, craft, num)
for _, stack in ipairs(invList) do for _, stack in ipairs(invList) do
if stack:get_count() > 0 then 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 end
end end
-- Get the most plentiful item in the group. -- Get the most plentiful item in the group.
if not groupCache[item] then 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 end
-- Move the selected item. -- Move the selected item.
if groupCache[item] then 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) inv:set_stack("craft", idx, stack)
end end
else else
-- Move the item. -- 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) inv:set_stack("craft", idx, stack)
end end
end end
end end
minetest.register_on_player_inventory_action(function(player, action, inventory, inventory_info) minetest.register_on_player_inventory_action(
-- Hide the autocrafting menu when the player drops an item. function(player, action, inventory, inventory_info)
if cg.autocrafting and inventory_info.listname == "main" then -- Hide the autocrafting menu when the player drops an item.
local context = sfinv.get_or_create_context(player) if cg.AUTOCRAFTING and inventory_info.listname == "main" then
local context = sfinv.get_or_create_context(player)
if context.cg_auto_menu then if context.cg_auto_menu then
context.cg_auto_menu = false context.cg_auto_menu = false
sfinv.set_player_inventory_formspec(player) sfinv.set_player_inventory_formspec(player)
end
end end
end end
end) )

View File

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

View File

@ -1,69 +1,94 @@
local F = minetest.formspec_escape local F = minetest.formspec_escape
cg.update_filter = function(player, context, filter, force) function cg.update_filter(player, context, filter, force)
if not force and (filter or "") == context.cg_filter then return end filter = filter or ""
if not force and filter:lower() == (context.cg_filter or ""):lower() then
return
end
context.cg_page = 0 context.cg_page = 0
context.cg_filter = filter or "" context.cg_filter = filter
cg.filter_items(player, context.cg_filter) cg.filter_items(player, context.cg_filter)
end end
cg.update_selected_item = function(player, context, item, force) function cg.update_selected_item(player, context, item, force)
if not force and item == context.cg_selected_item then return end 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_selected_item = item
context.cg_auto_menu = false context.cg_auto_menu = false
end 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 if name and name ~= "" then
local groups, buttonText local groups, buttonText
if name:sub(1, 6) == "group:" then if name:sub(1, 6) == "group:" then
groups = name:sub(7):split(",") groups = name:sub(7):split(",")
buttonText = #groups > 1 and ("G " .. #groups) or "G" buttonText = #groups == 1 and "G" or ("G " .. #groups)
name = name:gsub(",", "/") name = name:gsub(",", "/")
end end
formspec[#formspec + 1] = string.format("item_image_button[%.2f,%.2f;%.2f,%.2f;%s;cgitem_%s;%s]", formspec[#formspec + 1] = string.format(
x, y, size, size, "item_image_button[%.2f,%.2f;%.2f,%.2f;%s;cgitem_%s;%s]",
groups and (cg.group_stereotypes[groups[1]] or "") or name, x, y, size, size,
name:match("^%S+"), -- Keep only the item name, not the quantity. groups and (cg.group_stereotypes[groups[1]] or "") or name,
buttonText or "" name:match("^%S+"), -- Keep only the item name, not the quantity.
) buttonText or ""
)
if groups then 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]", formspec[#formspec + 1] = string.format("tooltip[cgitem_%s;%s]",
name, name, tooltipText)
#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])))
)
end end
else else
size = size * 0.8 + 0.2 formspec[#formspec + 1] = string.format(
formspec[#formspec + 1] = string.format("image[%.2f,%.2f;%.2f,%.2f;gui_hb_bg.png]", x, y, size, size) "image[%.2f,%.2f;%.2f,%.2f;gui_hb_bg.png]",
x, y, size, size
)
end end
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) local itemList = cg.get_item_list(player)
context.cg_page = context.cg_page or 0 context.cg_page = context.cg_page or 0
formspec[#formspec + 1] = [[ -- Buttons
image_button[2.4,3.7;0.8,0.8;cg_plus_icon_search.png;cg_search;] formspec[#formspec + 1] =
image_button[3.1,3.7;0.8,0.8;cg_plus_icon_clear.png;cg_clear;] "real_coordinates[true]" ..
image_button[5.1,3.7;0.8,0.8;cg_plus_icon_prev.png;cg_prev;] "image_button[3.425,5.3;0.8,0.8;cg_plus_icon_search.png;cg_search;]" ..
image_button[7.1,3.7;0.8,0.8;cg_plus_icon_next.png;cg_next;] "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"))) -- Search box
formspec[#formspec + 1] = string.format(
formspec[#formspec + 1] = string.format("field[0.3,3.9;2.5,1;cg_filter;;%s]", F(context.cg_filter or "")) "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] = "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 startIdx = context.cg_page * cg.PAGE_ITEMS + 1
local item local item
@ -72,64 +97,86 @@ local make_item_grid = function(formspec, player, context)
item = itemList.list[startIdx + itemIdx] item = itemList.list[startIdx + itemIdx]
if item then if item then
formspec[#formspec + 1] = string.format("item_image_button[%.2f,%.2f;1,1;%s;cgitem_%s;]", formspec[#formspec + 1] = string.format(
itemIdx % cg.PAGE_WIDTH, "item_image_button[%.2f,%.2f;1,1;%s;cgitem_%s;]",
math.floor(itemIdx / cg.PAGE_WIDTH) + 0.5, (itemIdx % cg.PAGE_WIDTH) * 1.25 + 0.375,
item, item math.floor(itemIdx / cg.PAGE_WIDTH) * 1.25 + 0.375,
) item, item
)
end end
end end
end end
local make_craft_preview = function(formspec, player, context) local function make_craft_preview(formspec, player, context)
formspec[#formspec + 1] = [[ formspec[#formspec + 1] =
image_button[7.1,0.1;0.8,0.8;cg_plus_icon_prev.png;cg_craft_close;] "real_coordinates[true]" ..
image[0.1,0.1;0.8,0.8;gui_hb_bg.png] "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 local item = context.cg_selected_item
formspec[#formspec + 1] = string.format("item_image[0.1,0.1;0.8,0.8;%s]", item) -- Item image
formspec[#formspec + 1] = string.format("label[1,0;%s]", formspec[#formspec + 1] = string.format(
cg.crafts[item] and minetest.registered_items[item].description or item) "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] local crafts = cg.crafts[item]
if not crafts or #crafts == 0 then 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 return
end end
-- Previous/next craft buttons, page number
if #crafts > 1 then if #crafts > 1 then
formspec[#formspec + 1] = [[ formspec[#formspec + 1] =
image_button[1.85,3.7;0.8,0.8;cg_plus_icon_prev.png;cg_craft_prev;] "image_button[2.875,5.3;0.8,0.8;" ..
image_button[3.85,3.7;0.8,0.8;cg_plus_icon_next.png;cg_craft_next;] "cg_plus_icon_prev.png;cg_craft_prev;]" ..
]] "image_button[5.575,5.3;0.8,0.8;" ..
formspec[#formspec + 1] = string.format("label[2.75,3.8;%i / %i]", context.cg_craft_page + 1, #crafts) "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 end
local craft = cg.parse_craft(crafts[context.cg_craft_page + 1]) local craft = cg.parse_craft(crafts[context.cg_craft_page + 1])
local template = cg.craft_types[craft.type] or {} local template = cg.craft_types[craft.type] or {}
if cg.autocrafting and template.uses_crafting_grid then -- Auto-crafting buttons
formspec[#formspec + 1] = "image_button[0.1,3.7;0.8,0.8;cg_plus_icon_autocrafting.png;cg_auto_menu;]" if cg.AUTOCRAFTING and template.uses_crafting_grid then
formspec[#formspec + 1] = string.format("tooltip[cg_auto_menu;%s]", F(cg.S("Craft this recipe"))) 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 if context.cg_auto_menu then
local num = 1 local num = 1
local yPos = 3 local yPos = 4.3
while true do while true do
num = math.min(num, context.cg_auto_max) 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( formspec[#formspec + 1] = string.format(
"tooltip[cg_auto_%i;%s]", "button[0.375,%.2f;0.8,0.8;cg_auto_%i;%i]",
num, yPos, num, num
num == 1 and F(cg.S("Craft @1 item", num)) or F(cg.S("Craft @1 items", 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 if num < context.cg_auto_max then
num = num * 10 num = num * 10
yPos = yPos - 0.7 yPos = yPos - 1
else else
break break
end end
@ -137,127 +184,168 @@ local make_craft_preview = function(formspec, player, context)
end end
end end
formspec[#formspec + 1] = string.format("label[5,0.5;%s]", template.description or "") -- Craft type/info text
formspec[#formspec + 1] = string.format("label[5,1;%s]", craft.infotext or "") formspec[#formspec + 1] = string.format("label[6.7,1.8;%s]",
formspec[#formspec + 1] = string.format("image[4.75,1.5;1,1;%s]", template.description or "")
template.arrow_icon or "cg_plus_arrow.png") 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) -- Draw craft item grid, feat. maths.
local xOffset = 4.75 - craft.grid_size.x * slotSize
local yOffset = 2 - craft.grid_size.y * slotSize * 0.5 -- 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 for idx = 1, craft.grid_size.x * craft.grid_size.y do
make_item_button(formspec, make_item_button(
(idx - 1) % craft.grid_size.x * slotSize + xOffset, formspec,
math.floor((idx - 1) / craft.grid_size.y) * slotSize + yOffset, ((idx - 1) % craft.grid_size.x) * slotDist + xOffset,
slotSize, math.floor((idx - 1) / craft.grid_size.y) * slotDist + yOffset,
craft.items[idx] slotDist * 0.8, -- 1 - <padding amount>
) craft.items[idx]
)
end 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 end
sfinv.register_page("cg_plus:crafting_guide", { --[[
title = "Crafting Guide", sfinv registration
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]
]]}
if context.cg_selected_item then local function page_get(self, player, context)
make_craft_preview(formspec, player, context) local formspec = {}
else
make_item_grid(formspec, player, context)
end
return sfinv.make_formspec(player, context, table.concat(formspec), true) if context.cg_selected_item then
end, make_craft_preview(formspec, player, context)
else
make_item_grid(formspec, player, context)
end
on_player_receive_fields = function(self, player, context, fields) return sfinv.make_formspec(player, context, table.concat(formspec), true)
if fields.cg_craft_close then end
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]
if craft and cg.craft_types[craft.type] and cg.craft_types[craft.type].uses_crafting_grid then local function page_on_player_receive_fields(self, player, context, fields)
context.cg_auto_menu = true if fields.cg_craft_close then
context.cg_auto_max = cg.auto_get_craftable(player, craft) context.cg_selected_item = nil
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)
context.cg_auto_menu = false 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 name = cg_plus
description = An intuitive and full-featured craft guide and autocrafting mod which is compatible with sfinv. description = An intuitive and full-featured craft guide and autocrafting mod which is compatible with sfinv.
depends = 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