diff --git a/README.md b/README.md index 25d9128..eea50ca 100644 --- a/README.md +++ b/README.md @@ -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 ``` diff --git a/api.lua b/api.lua index 98d8b73..9039a69 100644 --- a/api.lua +++ b/api.lua @@ -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) diff --git a/autocrafting.lua b/autocrafting.lua index 0c13937..50b3faa 100644 --- a/autocrafting.lua +++ b/autocrafting.lua @@ -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) +) diff --git a/init.lua b/init.lua index ae502ac..4b3d2de 100644 --- a/init.lua +++ b/init.lua @@ -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") diff --git a/inventory.lua b/inventory.lua index 20cd8d7..baefa0e 100644 --- a/inventory.lua +++ b/inventory.lua @@ -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. + -- / ( - ) + local slotDist = 3.5 / (gridMax - 0.2) + + -- Determine upper-left corner of crafting squares + -- - ( - ) * slotDist + local xOffset = 6.375 - (craft.grid_size.x - 0.2) * slotDist + --
- + -- ( - ) * 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 - + 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 diff --git a/locale/template.txt b/locale/template.txt new file mode 100644 index 0000000..2783345 --- /dev/null +++ b/locale/template.txt @@ -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.= diff --git a/mod.conf b/mod.conf index eddb767..7e0b16f 100644 --- a/mod.conf +++ b/mod.conf @@ -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 diff --git a/textures/cg_plus_arrow_bottom.png b/textures/cg_plus_arrow_bottom.png new file mode 100644 index 0000000..05c3b61 Binary files /dev/null and b/textures/cg_plus_arrow_bottom.png differ diff --git a/textures/cg_plus_arrow_small.png b/textures/cg_plus_arrow_small.png deleted file mode 100644 index 5b8bca1..0000000 Binary files a/textures/cg_plus_arrow_small.png and /dev/null differ diff --git a/textures/cg_plus_icon_clear.png b/textures/cg_plus_icon_clear.png index 9244264..73d8cea 100644 Binary files a/textures/cg_plus_icon_clear.png and b/textures/cg_plus_icon_clear.png differ diff --git a/textures/cg_plus_icon_cooking.png b/textures/cg_plus_icon_cooking.png index 9938263..57af947 100644 Binary files a/textures/cg_plus_icon_cooking.png and b/textures/cg_plus_icon_cooking.png differ diff --git a/textures/cg_plus_icon_digging.png b/textures/cg_plus_icon_digging.png index f8c38b3..0529b4b 100644 Binary files a/textures/cg_plus_icon_digging.png and b/textures/cg_plus_icon_digging.png differ diff --git a/textures/cg_plus_icon_fuel.png b/textures/cg_plus_icon_fuel.png index 38fbe96..001a0e6 100644 Binary files a/textures/cg_plus_icon_fuel.png and b/textures/cg_plus_icon_fuel.png differ diff --git a/textures/cg_plus_icon_next.png b/textures/cg_plus_icon_next.png index 82cf3d3..e08a258 100644 Binary files a/textures/cg_plus_icon_next.png and b/textures/cg_plus_icon_next.png differ diff --git a/textures/cg_plus_icon_prev.png b/textures/cg_plus_icon_prev.png index b26cd15..bd06931 100644 Binary files a/textures/cg_plus_icon_prev.png and b/textures/cg_plus_icon_prev.png differ diff --git a/textures/cg_plus_icon_search.png b/textures/cg_plus_icon_search.png index aace804..53b9f76 100644 Binary files a/textures/cg_plus_icon_search.png and b/textures/cg_plus_icon_search.png differ