From 3b5c81cb3afcac8452dd8d875f77028d10da1b1a Mon Sep 17 00:00:00 2001 From: Jean-Patrick Guerrero Date: Sun, 20 Jun 2021 00:04:33 +0200 Subject: [PATCH 01/19] Fix item loss if placed past slot 36 --- init.lua | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/init.lua b/init.lua index 9376fcc..48ee6ab 100644 --- a/init.lua +++ b/init.lua @@ -2561,9 +2561,7 @@ local function get_inventory_fs(player, data, fs) (max_val * 4) / 12, max_val, 9.8, ctn_hgt, data.scrbar_inv)) fs(fmt("scroll_container[3.9,0.2;%f,%f;scrbar_inv;vertical]", ctn_len, ctn_hgt)) - - get_ctn_content(fs, data, player, yoffset, ctn_len, award_list, awards_unlocked, award_list_nb) - + get_ctn_content(fs, data, player, yoffset, ctn_len, award_list, awards_unlocked, award_list_nb) fs("scroll_container_end[]") local btn = { @@ -2969,7 +2967,7 @@ local function init_backpack(player) local data = pdata[name] local inv = player:get_inventory() - inv:set_size("main", INV_SIZE) + inv:set_size("main", data.bag_size and BAG_SIZES[data.bag_size] or INV_SIZE) data.bag = create_inventory(fmt("%s_backpack", name), { allow_put = function(_inv, listname, _, stack) @@ -3010,7 +3008,6 @@ local function init_backpack(player) if data.bag_size then data.bag:set_stack("main", 1, fmt("i3:bag_%s", data.bag_size)) - inv:set_size("main", BAG_SIZES[data.bag_size]) end end From f2eb377d9643effee300ae7648ca30ab84e380d6 Mon Sep 17 00:00:00 2001 From: Jean-Patrick Guerrero Date: Sun, 20 Jun 2021 02:06:55 +0200 Subject: [PATCH 02/19] Fix potential issues --- init.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/init.lua b/init.lua index 48ee6ab..43b3c1e 100644 --- a/init.lua +++ b/init.lua @@ -2511,7 +2511,7 @@ local function get_inventory_fs(player, data, fs) local textures = concat(t, ","):gsub("!", ",") --fs("style[player_model;bgcolor=black]") - fs("model", 0.2, 0.2, armor_skin and 4 or 3.4, armor_skin and ctn_hgt or 6.1, + fs("model", 0.2, 0.2, armor_skin and 4 or 3.4, ctn_hgt, "player_model", props.mesh, textures, "0,-150", "false", "false", fmt("%u,%u", anim.x, anim.y)) else @@ -3034,9 +3034,9 @@ end on_joinplayer(function(player) local name = player:get_player_name() - local info = get_player_info(name) + local info = get_player_info and get_player_info(name) - if get_formspec_version(info) < MIN_FORMSPEC_VERSION then + if not info or get_formspec_version(info) < MIN_FORMSPEC_VERSION then if __sfinv then sfinv.set_player_inventory_formspec = old_sfinv_fn sfinv.enabled = true @@ -3050,6 +3050,8 @@ on_joinplayer(function(player) end end + pdata[name] = nil + return outdated(name) end From c5dd2be56905facc22765dc9c2d43bb28c0538aa Mon Sep 17 00:00:00 2001 From: Jean-Patrick Guerrero Date: Sun, 20 Jun 2021 02:29:50 +0200 Subject: [PATCH 03/19] Re-enable player animation when MT 5.5 is out --- init.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/init.lua b/init.lua index 43b3c1e..dc9cd85 100644 --- a/init.lua +++ b/init.lua @@ -2355,6 +2355,7 @@ local function init_data(player, info) data.subcat = 1 data.scrbar_inv = 0 data.lang_code = get_lang_code(info) + data.fs_version = info.formspec_version after(0, set_fs, player) end @@ -2513,7 +2514,7 @@ local function get_inventory_fs(player, data, fs) --fs("style[player_model;bgcolor=black]") fs("model", 0.2, 0.2, armor_skin and 4 or 3.4, ctn_hgt, "player_model", props.mesh, textures, "0,-150", "false", "false", - fmt("%u,%u", anim.x, anim.y)) + fmt("%u,%u%s", anim.x, anim.y, data.fs_version >= 5 and ";30" or "")) else local size = 2.5 fs("image", 0.7, 0.2, size, size * props.visual_size.y, props.textures[1]) From b1fff8617b386c1649307f1758d0507774800e52 Mon Sep 17 00:00:00 2001 From: Jean-Patrick Guerrero Date: Fri, 25 Jun 2021 03:44:38 +0200 Subject: [PATCH 04/19] Add Item Compression --- README.md | 1 + compress.lua | 191 ++++++++++++++++++++++++++++++++++++ init.lua | 250 +++++++++++++++++++++++++++++++---------------- settingtypes.txt | 3 + 4 files changed, 360 insertions(+), 85 deletions(-) create mode 100644 compress.lua diff --git a/README.md b/README.md index d1dd3be..730dc8d 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ This mod requires **Minetest 5.4+** - Inventory Sorting (alphabetical + item stack compression) - Item Bookmarks - Waypoints + - Item List Compression **¹** *This mode is a Terraria-like system that shows recipes you can craft from items you ever had in your inventory. To enable it: `i3_progressive_mode = true` in `minetest.conf`.* diff --git a/compress.lua b/compress.lua new file mode 100644 index 0000000..4032ccd --- /dev/null +++ b/compress.lua @@ -0,0 +1,191 @@ +local fmt, insert = string.format, table.insert + +local wood_types = { + "acacia_wood", "aspen_wood", "junglewood", "pine_wood", +} + +local material_tools = { + "bronze", "diamond", "mese", "stone", "wood", +} + +local material_stairs = { + "acacia_wood", "aspen_wood", "brick", "bronzeblock", "cobble", "copperblock", + "desert_cobble", "desert_sandstone", "desert_sandstone_block", "desert_sandstone_brick", + "desert_stone", "desert_stone_block", "desert_stonebrick", + "glass", "goldblock", "ice", "junglewood", "mossycobble", "obsidian", + "obsidian_block", "obsidian_glass", "obsidianbrick", "pine_wood", + "sandstone", "sandstone_block", "sandstonebrick", + "silver_sandstone", "silver_sandstone_block", "silver_sandstone_brick", + "snowblock", "steelblock", "stone", "stone_block", "stonebrick", + "straw", "tinblock", +} + +local colors = { + "black", "blue", "brown", "cyan", "dark_green", "dark_grey", "green", + "grey", "magenta", "orange", "pink", "red", "violet", "yellow", +} + +local to_compress = { + ["bucket:bucket_empty"] = { + replace = "empty", + by = {"lava", "river_water", "water"} + }, + + ["default:wood"] = { + replace = "wood", + by = wood_types, + }, + + ["default:sapling"] = { + replace = "sapling", + by = { + "acacia_bush_sapling", + "acacia_sapling", + "aspen_sapling", + "blueberry_bush_sapling", + "bush_sapling", + "emergent_jungle_sapling", + "junglesapling", + "pine_bush_sapling", + "pine_sapling" + } + }, + + ["default:gold_lump"] = { + replace = "gold", + by = {"clay", "coal", "copper", "iron", "tin"} + }, + + ["default:leaves"] = { + replace = "leaves", + by = { + "acacia_bush_leaves", + "acacia_leaves", + "aspen_leaves", + "blueberry_bush_leaves", + "blueberry_bush_leaves_with_berries", + "bush_leaves", + "jungleleaves", + }, + }, + + ["default:stone_with_diamond"] = { + replace = "diamond", + by = {"coal", "copper", "gold", "iron", "mese", "tin"}, + }, + + ["default:fence_wood"] = { + replace = "wood", + by = wood_types, + }, + + ["default:fence_rail_wood"] = { + replace = "wood", + by = wood_types, + }, + + ["default:mese_post_light"] = { + replace = "mese_post_light", + by = { + "mese_post_light_acacia", + "mese_post_light_aspen_wood", + "mese_post_light_junglewood", + "mese_post_light_pine_wood", + } + }, + + ["doors:gate_wood_closed"] = { + replace = "wood", + by = wood_types, + }, + + ["doors:door_wood"] = { + replace = "wood", + by = {"glass", "obsidian_glass", "steel"} + }, + + ["flowers:geranium"] = { + replace = "geranium", + by = { + "chrysanthemum_green", + "dandelion_white", + "dandelion_yellow", + "rose", + "tulip", + "tulip_black", + "viola", + } + }, + + ["wool:white"] = { + replace = "white", + by = colors + }, + + ["dye:white"] = { + replace = "white", + by = colors + }, + + ["default:axe_steel"] = { + replace = "steel", + by = material_tools + }, + + ["default:pick_steel"] = { + replace = "steel", + by = material_tools + }, + + ["default:shovel_steel"] = { + replace = "steel", + by = material_tools + }, + + ["default:sword_steel"] = { + replace = "steel", + by = material_tools + }, + + ["stairs:slab_wood"] = { + replace = "wood", + by = material_stairs + }, + + ["stairs:stair_wood"] = { + replace = "wood", + by = material_stairs + }, + + ["stairs:stair_inner_wood"] = { + replace = "wood", + by = material_stairs + }, + + ["stairs:stair_outer_wood"] = { + replace = "wood", + by = material_stairs + }, +} + +local compressed = {} + +for k, v in pairs(to_compress) do + compressed[k] = compressed[k] or {} + + for _, str in ipairs(v.by) do + local a, b = k:match("(.*):(.*)") + local it = fmt("%s:%s", a, b:gsub(v.replace, str)) + insert(compressed[k], it) + end +end + +local _compressed = {} + +for _, v in pairs(compressed) do +for _, v2 in ipairs(v) do + _compressed[v2] = true +end +end + +return compressed, _compressed diff --git a/init.lua b/init.lua index dc9cd85..8d34992 100644 --- a/init.lua +++ b/init.lua @@ -1,5 +1,6 @@ i3 = {} +local modpath = minetest.get_modpath "i3" local storage = core.get_mod_storage() local slz, dslz = core.serialize, core.deserialize local pdata = dslz(storage:get_string "pdata") or {} @@ -14,8 +15,10 @@ local replacements = {fuel = {}} local toolrepair local tabs = {} +local compress_groups, compressed = loadfile(modpath .. "/compress.lua")() local progressive_mode = core.settings:get_bool "i3_progressive_mode" +local item_compression = core.settings:get_bool "i3_item_compression" local damage_enabled = core.settings:get_bool "enable_damage" local __3darmor, __skinsdb, __awards @@ -25,6 +28,10 @@ local __unified_inventory, old_unified_inventory_fn local http = core.request_http_api() local singleplayer = core.is_singleplayer() +local log = core.log +local after = core.after +local clr = core.colorize + local reg_items = core.registered_items local reg_nodes = core.registered_nodes local reg_craftitems = core.registered_craftitems @@ -32,29 +39,9 @@ local reg_tools = core.registered_tools local reg_entities = core.registered_entities local reg_aliases = core.registered_aliases -local log = core.log -local after = core.after -local clr = core.colorize -local parse_json = core.parse_json -local write_json = core.write_json - -local get_inv = core.get_inventory -local chat_send = core.chat_send_player -local show_formspec = core.show_formspec -local pos_to_string = core.pos_to_string local check_privs = core.check_player_privs -local globalstep = core.register_globalstep -local on_shutdown = core.register_on_shutdown -local get_players = core.get_connected_players -local get_craft_result = core.get_craft_result local translate = minetest.get_translated_string -local on_joinplayer = core.register_on_joinplayer -local get_all_recipes = core.get_all_craft_recipes -local on_leaveplayer = core.register_on_leaveplayer -local on_mods_loaded = core.register_on_mods_loaded -local get_player_info = core.get_player_information local create_inventory = core.create_detached_inventory -local on_receive_fields = core.register_on_player_receive_fields local ESC = core.formspec_escape local S = core.get_translator "i3" @@ -233,7 +220,7 @@ local function outdated(name) PNG.book, "Your Minetest client is outdated.\nGet the latest version on minetest.net to use i3") - show_formspec(name, "i3", fs) + core.show_formspec(name, "i3", fs) end local old_is_creative_enabled = core.is_creative_enabled @@ -318,7 +305,7 @@ local function err(str) end local function msg(name, str) - return chat_send(name, sprintf("[i3] %s", str)) + return core.chat_send_player(name, sprintf("[i3] %s", str)) end local function is_num(x) @@ -500,7 +487,7 @@ function i3.register_craft(def) http.fetch({url = def.url}, function(result) if result.succeeded then - local t = parse_json(result.data) + local t = core.parse_json(result.data) if is_table(t) then return i3.register_craft(t) end @@ -651,6 +638,14 @@ function i3.get_search_filters() return search_filters end +local function compression_active() + return item_compression and not next(recipe_filters) +end + +local function compressible(item) + return compression_active() and compress_groups[item] +end + local function weird_desc(str) return not true_str(str) or find(str, "\n") or not find(str, "%u") end @@ -734,7 +729,7 @@ local function get_filtered_items(player, data) end local function get_burntime(item) - return get_craft_result{method = "fuel", items = {item}}.time + return core.get_craft_result{method = "fuel", items = {item}}.time end local function cache_fuel(item) @@ -755,6 +750,9 @@ local function show_item(def) end local function search(data) + data.alt_items = nil + data.expand = "" + local filter = data.filter if searches[filter] then @@ -923,7 +921,7 @@ local function cache_drops(name, drop) end local function cache_recipes(item) - local recipes = get_all_recipes(item) + local recipes = core.get_all_craft_recipes(item) if replacements[item] then local _recipes = {} @@ -1241,36 +1239,82 @@ local function select_item(player, name, data, _f) end end - if not item then - return - elseif sub(item, 1, 1) == "_" then - item = sub(item, 2) - elseif sub(item, 1, 6) == "group|" then - item = match(item, "([%w:_]+)$") + if not item then return end + + if compressible(item) then + local idx + + for i = 1, #data.items do + local it = data.items[i] + if it == item then + idx = i + break + end + end + + if data.expand ~= "" then + data.alt_items = nil + + if item == data.expand then + data.expand = nil + return + end + end + + if idx and item ~= data.expand then + data.alt_items = copy(data.items) + data.expand = item + + if compress_groups[item] then + local items = copy(compress_groups[item]) + insert(items, fmt("_%s", item)) + + sort(items, function(a, b) + if a:sub(1, 1) == "_" then + a = a:sub(2) + end + + return a < b + end) + + local i = 1 + + for _, v in ipairs(items) do + insert(data.alt_items, idx + i, v) + i = i + 1 + end + end + end + else + if sub(item, 1, 1) == "_" then + item = sub(item, 2) + elseif sub(item, 1, 6) == "group|" then + item = match(item, "([%w:_]+)$") + end + + item = reg_aliases[item] or item + if not reg_items[item] then return end + + if core.is_creative_enabled(name) then + local stack = ItemStack(item) + local stackmax = stack:get_stack_max() + stack = fmt("%s %s", item, stackmax) + return get_stack(player, stack, clr("#ff0", fmt("%u x %s", stackmax, get_desc(item)))) + end + + if item == data.query_item then return end + local recipes, usages = get_recipes(player, item) + + data.query_item = item + data.recipes = recipes + data.usages = usages + data.rnum = 1 + data.unum = 1 + data.scrbar_rcp = 1 + data.scrbar_usg = 1 + data.export_rcp = nil + data.export_usg = nil end - - item = reg_aliases[item] or item - if not reg_items[item] then return end - - if core.is_creative_enabled(name) then - local stack = ItemStack(item) - local stackmax = stack:get_stack_max() - stack = fmt("%s %s", item, stackmax) - return get_stack(player, stack, clr("#ff0", fmt("%u x %s", stackmax, get_desc(item)))) - end - - if item == data.query_item then return end - local recipes, usages = get_recipes(player, item) - - data.query_item = item - data.recipes = recipes - data.usages = usages - data.rnum = 1 - data.unum = 1 - data.scrbar_rcp = 1 - data.scrbar_usg = 1 - data.export_rcp = nil - data.export_usg = nil end local function repairable(tool) @@ -1421,7 +1465,11 @@ local function get_output_fs(fs, data, rcp, is_recipe, shapeless, right, btn_siz fs(fmt("list[detached:i3_output_%s;main;%f,%f;1,1;]", rcp_usg, X + 0.11, Y)) fs("button", X + 0.11, Y, ITEM_BTN_SIZE, ITEM_BTN_SIZE, _name, "") - local inv = get_inv {type = "detached", name = fmt("i3_output_%s", rcp_usg)} + local inv = core.get_inventory { + type = "detached", + name = fmt("i3_output_%s", rcp_usg) + } + inv:set_stack("main", 1, item) pos = {x = X + 0.11, y = Y} else @@ -1789,6 +1837,20 @@ local function get_rcp_extra(player, fs, data, panel, is_recipe, is_usage) end local function get_items_fs(fs, data, extend) + if compression_active() then + local new = {} + + for i = 1, #data.items do + local item = data.items[i] + if not compressed[item] then + new[#new + 1] = item + end + end + + data.items = new + end + + local items = data.alt_items or data.items local rows = 8 local lines = extend and 12 or 9 local ipp = rows * lines @@ -1799,17 +1861,17 @@ local function get_items_fs(fs, data, extend) fmt("field[%f,0.2;2.95,0.6;filter;;%s]", data.inv_width + 0.35, ESC(data.filter)), "field_close_on_enter[filter;false]") - fs("image_button", data.inv_width + 3.35, 0.35, 0.3, 0.3, "", "cancel", "") + fs("image_button", data.inv_width + 3.35, 0.35, 0.3, 0.3, "", "cancel", "") fs("image_button", data.inv_width + 3.85, 0.32, 0.35, 0.35, "", "search", "") - fs("image_button", data.inv_width + 5.27, 0.3, 0.35, 0.35, "", "prev_page", "") - fs("image_button", data.inv_width + 7.45, 0.3, 0.35, 0.35, "", "next_page", "") + fs("image_button", data.inv_width + 5.27, 0.3, 0.35, 0.35, "", "prev_page", "") + fs("image_button", data.inv_width + 7.45, 0.3, 0.35, 0.35, "", "next_page", "") - data.pagemax = max(1, ceil(#data.items / ipp)) + data.pagemax = max(1, ceil(#items / ipp)) fs("button", data.inv_width + 5.6, 0.14, 1.88, 0.7, "pagenum", fmt("%s / %u", clr("#ff0", data.pagenum), data.pagemax)) - if #data.items == 0 then + if #items == 0 then local lbl = ES"No item to show" if next(recipe_filters) and #init_items > 0 and data.filter == "" then @@ -1817,21 +1879,33 @@ local function get_items_fs(fs, data, extend) end fs("button", data.inv_width + 0.1, 3, 8, 1, "no_item", lbl) - end + else + local first_item = (data.pagenum - 1) * ipp - local first_item = (data.pagenum - 1) * ipp + for i = first_item, first_item + ipp - 1 do + local item = items[i + 1] + if not item then break end - for i = first_item, first_item + ipp - 1 do - local item = data.items[i + 1] - if not item then break end + local _compressed = item:sub(1, 1) == "_" + local name = _compressed and item:sub(2) or item - local X = i % rows - X = X - (X * 0.045) + data.inv_width + 0.28 + local X = i % rows + X = X - (X * 0.045) + data.inv_width + 0.28 - local Y = round((i % ipp - X) / rows + 1, 0) - Y = Y - (Y * (extend and 0.085 or 0.035)) + 0.95 + local Y = round((i % ipp - X) / rows + 1, 0) + Y = Y - (Y * (extend and 0.085 or 0.035)) + 0.95 - fs[#fs + 1] = fmt("item_image_button", X, Y, size, size, item, item, "") + fs[#fs + 1] = fmt("item_image_button", X, Y, size, size, name, item, "") + + if compressible(item) then + local expand = data.expand == name + + fs(fmt("tooltip[%s;%s]", item, expand and ES"Click to hide" or ES"Click to expand")) + fs("style_type[label;font=bold;font_size=20]") + fs("label", X + 0.65, Y + 0.7, expand and "-" or "+") + fs("style_type[label;font=normal;font_size=16]") + end + end end end @@ -2008,7 +2082,7 @@ local function get_waypoint_fs(fs, data, player, yextra, ctn_len) fs("tooltip", 0, y, ctn_len - 2.5, 0.65, fmt("Name: %s\nPosition:%s", clr("#ff0", v.name), - pos_to_string(v.pos, 0):sub(2,-2):gsub("(%-*%d+)", clr("#ff0", " %1")))) + core.pos_to_string(v.pos, 0):sub(2,-2):gsub("(%-*%d+)", clr("#ff0", " %1")))) local del = fmt("waypoint_%u_delete", i) fs(fmt("style[%s;fgimg=%s;fgimg_hovered=%s;content_offset=0]", del, PNG.trash, PNG.trash_hover)) @@ -2362,6 +2436,7 @@ end local function reset_data(data) data.filter = "" + data.expand = "" data.pagenum = 1 data.rnum = 1 data.unum = 1 @@ -2372,6 +2447,7 @@ local function reset_data(data) data.usages = nil data.export_rcp = nil data.export_usg = nil + data.alt_items = nil data.items = data.items_raw end @@ -2937,14 +3013,14 @@ local function get_init_items() usages = usages_cache, } - http.fetch_async{ + http.fetch_async { url = i3.export_url, - post_data = write_json(post_data), + post_data = core.write_json(post_data), } end end -on_mods_loaded(function() +core.register_on_mods_loaded(function() get_init_items() __sfinv = rawget(_G, "sfinv") @@ -3033,9 +3109,9 @@ local function init_waypoints(player) end end -on_joinplayer(function(player) +core.register_on_joinplayer(function(player) local name = player:get_player_name() - local info = get_player_info and get_player_info(name) + local info = core.get_player_information and core.get_player_information(name) if not info or get_formspec_version(info) < MIN_FORMSPEC_VERSION then if __sfinv then @@ -3106,12 +3182,12 @@ local function save_data(player_name) storage:set_string("pdata", slz(_pdata)) end -on_leaveplayer(function(player) +core.register_on_leaveplayer(function(player) local name = player:get_player_name() save_data(name) end) -on_shutdown(save_data) +core.register_on_shutdown(save_data) local function routine() save_data() @@ -3120,7 +3196,7 @@ end after(SAVE_INTERVAL, routine) -on_receive_fields(function(player, formname, fields) +core.register_on_player_receive_fields(function(player, formname, fields) if formname ~= "" then return false end @@ -3165,6 +3241,7 @@ if progressive_mode then if is_group(item) then local groups = extract_groups(item) + for i = 1, inv_items_size do local def = reg_items[inv_items[i]] @@ -3231,6 +3308,7 @@ if progressive_mode then for i = 1, #stacks do local stack = stacks[i] + if not stack:is_empty() then local name = stack:get_name() if reg_items[name] then @@ -3321,7 +3399,8 @@ if progressive_mode then -- Workaround. Need an engine call to detect when the contents of -- the player inventory changed, instead. local function poll_new_items() - local players = get_players() + local players = core.get_connected_players() + for i = 1, #players do local player = players[i] local name = player:get_player_name() @@ -3351,8 +3430,9 @@ if progressive_mode then poll_new_items() - globalstep(function() - local players = get_players() + core.register_globalstep(function() + local players = core.get_connected_players() + for i = 1, #players do local player = players[i] local name = player:get_player_name() @@ -3366,7 +3446,7 @@ if progressive_mode then i3.add_recipe_filter("Default progressive filter", progressive_filter) - on_joinplayer(function(player) + core.register_on_joinplayer(function(player) local name = player:get_player_name() local data = pdata[name] @@ -3412,5 +3492,5 @@ for size, rcp in pairs(bag_recipes) do core.register_craft {type = "fuel", recipe = bagname, burntime = 3} end ---dofile(core.get_modpath("i3") .. "/test_tabs.lua") ---dofile(core.get_modpath("i3") .. "/test_custom_recipes.lua") +--dofile(modpath .. "/test_tabs.lua") +--dofile(modpath .. "/test_custom_recipes.lua") diff --git a/settingtypes.txt b/settingtypes.txt index a97f54a..42bc83e 100644 --- a/settingtypes.txt +++ b/settingtypes.txt @@ -1,2 +1,5 @@ # The progressive mode shows recipes you can craft from items you ever had in your inventory. i3_progressive_mode (Learn crafting recipes progressively) bool false + +# Regroup the items of the same type in the item list. +i3_item_compression (Regroup items of the same type) bool true From c1bd944465b404ecb50dc25810843eac4d95c6db Mon Sep 17 00:00:00 2001 From: Jean-Patrick Guerrero Date: Fri, 25 Jun 2021 04:21:23 +0200 Subject: [PATCH 05/19] Split some code to own files --- API.md | 11 +- compress.lua => etc/compress.lua | 0 etc/groups.lua | 62 ++++++ etc/model_aliases.lua | 13 ++ etc/styles.lua | 114 ++++++++++ .../test_custom_recipes.lua | 0 .../test_online_recipe.json | 0 test_tabs.lua => etc/test_tabs.lua | 0 init.lua | 200 +----------------- 9 files changed, 201 insertions(+), 199 deletions(-) rename compress.lua => etc/compress.lua (100%) create mode 100644 etc/groups.lua create mode 100644 etc/model_aliases.lua create mode 100644 etc/styles.lua rename test_custom_recipes.lua => etc/test_custom_recipes.lua (100%) rename test_online_recipe.json => etc/test_online_recipe.json (100%) rename test_tabs.lua => etc/test_tabs.lua (100%) diff --git a/API.md b/API.md index 8a54d9f..4bf8448 100644 --- a/API.md +++ b/API.md @@ -141,7 +141,7 @@ Recipes can be registered from a given URL containing a JSON file (HTTP support ```Lua i3.register_craft({ - url = "https://raw.githubusercontent.com/minetest-mods/i3/main/test_online_recipe.json" + url = "https://raw.githubusercontent.com/minetest-mods/i3/main/etc/test_online_recipe.json" }) ``` @@ -237,15 +237,6 @@ Returns a map of search filters, indexed by name. ### Miscellaneous -#### `i3.group_stereotypes` - -This is the table indexing the item groups by stereotypes. -You can add a stereotype like so: - -```Lua -i3.group_stereotypes.radioactive = "mod:item" -``` - #### `i3.export_url` If set, the mod will export all the cached recipes and usages in a JSON format diff --git a/compress.lua b/etc/compress.lua similarity index 100% rename from compress.lua rename to etc/compress.lua diff --git a/etc/groups.lua b/etc/groups.lua new file mode 100644 index 0000000..ea126e9 --- /dev/null +++ b/etc/groups.lua @@ -0,0 +1,62 @@ +local S = core.get_translator "i3" + +local group_stereotypes = { + dye = "dye:white", + wool = "wool:white", + wood = "default:wood", + tree = "default:tree", + sand = "default:sand", + glass = "default:glass", + stick = "default:stick", + stone = "default:stone", + leaves = "default:leaves", + coal = "default:coal_lump", + vessel = "vessels:glass_bottle", + flower = "flowers:dandelion_yellow", + water_bucket = "bucket:bucket_water", + mesecon_conductor_craftable = "mesecons:wire_00000000_off", +} + +local group_names = { + dye = S"Any dye", + coal = S"Any coal", + sand = S"Any sand", + tree = S"Any tree", + wool = S"Any wool", + glass = S"Any glass", + stick = S"Any stick", + stone = S"Any stone", + carpet = S"Any carpet", + flower = S"Any flower", + leaves = S"Any leaves", + vessel = S"Any vessel", + wood = S"Any wood planks", + mushroom = S"Any mushroom", + + ["color_red,flower"] = S"Any red flower", + ["color_blue,flower"] = S"Any blue flower", + ["color_black,flower"] = S"Any black flower", + ["color_white,flower"] = S"Any white flower", + ["color_green,flower"] = S"Any green flower", + ["color_orange,flower"] = S"Any orange flower", + ["color_yellow,flower"] = S"Any yellow flower", + ["color_violet,flower"] = S"Any violet flower", + + ["color_red,dye"] = S"Any red dye", + ["color_blue,dye"] = S"Any blue dye", + ["color_grey,dye"] = S"Any grey dye", + ["color_pink,dye"] = S"Any pink dye", + ["color_cyan,dye"] = S"Any cyan dye", + ["color_black,dye"] = S"Any black dye", + ["color_white,dye"] = S"Any white dye", + ["color_brown,dye"] = S"Any brown dye", + ["color_green,dye"] = S"Any green dye", + ["color_orange,dye"] = S"Any orange dye", + ["color_yellow,dye"] = S"Any yellow dye", + ["color_violet,dye"] = S"Any violet dye", + ["color_magenta,dye"] = S"Any magenta dye", + ["color_dark_grey,dye"] = S"Any dark grey dye", + ["color_dark_green,dye"] = S"Any dark green dye", +} + +return group_stereotypes, group_names diff --git a/etc/model_aliases.lua b/etc/model_aliases.lua new file mode 100644 index 0000000..30083e6 --- /dev/null +++ b/etc/model_aliases.lua @@ -0,0 +1,13 @@ +local model_alias = { + ["boats:boat"] = {name = "boats:boat", drawtype = "entity"}, + ["carts:cart"] = {name = "carts:cart", drawtype = "entity", frames = "0,0"}, + ["default:chest"] = {name = "default:chest_open"}, + ["default:chest_locked"] = {name = "default:chest_locked_open"}, + ["doors:door_wood"] = {name = "doors:door_wood_a"}, + ["doors:door_glass"] = {name = "doors:door_glass_a"}, + ["doors:door_obsidian_glass"] = {name = "doors:door_obsidian_glass_a"}, + ["doors:door_steel"] = {name = "doors:door_steel_a"}, + ["xpanes:door_steel_bar"] = {name = "xpanes:door_steel_bar_a"}, +} + +return model_alias diff --git a/etc/styles.lua b/etc/styles.lua new file mode 100644 index 0000000..052a27e --- /dev/null +++ b/etc/styles.lua @@ -0,0 +1,114 @@ +local fmt = string.format + +local PNG = { + bg = "i3_bg.png", + bg_full = "i3_bg_full.png", + bar = "i3_bar.png", + hotbar = "i3_hotbar.png", + search = "i3_search.png", + heart = "i3_heart.png", + heart_half = "i3_heart_half.png", + heart_grey = "i3_heart_grey.png", + prev = "i3_next.png^\\[transformFX", + next = "i3_next.png", + arrow = "i3_arrow.png", + trash = "i3_trash.png", + sort_az = "i3_sort_az.png", + sort_za = "i3_sort_za.png", + compress = "i3_compress.png", + fire = "i3_fire.png", + fire_anim = "i3_fire_anim.png", + book = "i3_book.png", + sign = "i3_sign.png", + cancel = "i3_cancel.png", + export = "i3_export.png", + slot = "i3_slot.png", + tab = "i3_tab.png", + tab_top = "i3_tab.png^\\[transformFY", + furnace_anim = "i3_furnace_anim.png", + bag = "i3_bag.png", + armor = "i3_armor.png", + awards = "i3_award.png", + skins = "i3_skin.png", + waypoints = "i3_waypoint.png", + teleport = "i3_teleport.png", + add = "i3_add.png", + refresh = "i3_refresh.png", + visible = "i3_visible.png^\\[brighten", + nonvisible = "i3_non_visible.png", + exit = "i3_exit.png", + + cancel_hover = "i3_cancel.png^\\[brighten", + search_hover = "i3_search.png^\\[brighten", + export_hover = "i3_export.png^\\[brighten", + trash_hover = "i3_trash.png^\\[brighten^\\[colorize:#f00:100", + compress_hover = "i3_compress.png^\\[brighten", + sort_az_hover = "i3_sort_az.png^\\[brighten", + sort_za_hover = "i3_sort_za.png^\\[brighten", + prev_hover = "i3_next_hover.png^\\[transformFX", + next_hover = "i3_next_hover.png", + tab_hover = "i3_tab_hover.png", + tab_hover_top = "i3_tab_hover.png^\\[transformFY", + bag_hover = "i3_bag_hover.png", + armor_hover = "i3_armor_hover.png", + awards_hover = "i3_award_hover.png", + skins_hover = "i3_skin_hover.png", + waypoints_hover = "i3_waypoint_hover.png", + teleport_hover = "i3_teleport.png^\\[brighten", + add_hover = "i3_add.png^\\[brighten", + refresh_hover = "i3_refresh.png^\\[brighten", + exit_hover = "i3_exit.png^\\[brighten", +} + +local styles = fmt([[ + style_type[field;border=false;bgcolor=transparent] + style_type[label,field;font_size=16] + style_type[button;border=false;content_offset=0] + style_type[image_button,item_image_button;border=false;sound=i3_click] + style_type[item_image_button;bgimg_hovered=%s] + + style[pagenum,no_item,no_rcp;font=bold;font_size=18] + style[exit;fgimg=%s;fgimg_hovered=%s;content_offset=0] + style[cancel;fgimg=%s;fgimg_hovered=%s;content_offset=0] + style[search;fgimg=%s;fgimg_hovered=%s;content_offset=0] + style[prev_page;fgimg=%s;fgimg_hovered=%s] + style[next_page;fgimg=%s;fgimg_hovered=%s] + style[prev_recipe;fgimg=%s;fgimg_hovered=%s] + style[next_recipe;fgimg=%s;fgimg_hovered=%s] + style[prev_usage;fgimg=%s;fgimg_hovered=%s] + style[next_usage;fgimg=%s;fgimg_hovered=%s] + style[waypoint_add;fgimg=%s;fgimg_hovered=%s;content_offset=0] + style[btn_bag,btn_armor,btn_skins;font=bold;font_size=18;content_offset=0;sound=i3_click] + style[craft_rcp,craft_usg;noclip=true;font_size=16;sound=i3_craft; + bgimg=i3_btn9.png;bgimg_hovered=i3_btn9_hovered.png; + bgimg_pressed=i3_btn9_pressed.png;bgimg_middle=4,6] +]], +PNG.slot, +PNG.exit, PNG.exit_hover, +PNG.cancel, PNG.cancel_hover, +PNG.search, PNG.search_hover, +PNG.prev, PNG.prev_hover, +PNG.next, PNG.next_hover, +PNG.prev, PNG.prev_hover, +PNG.next, PNG.next_hover, +PNG.prev, PNG.prev_hover, +PNG.next, PNG.next_hover, +PNG.add, PNG.add_hover) + +local fs_elements = { + label = "label[%f,%f;%s]", + box = "box[%f,%f;%f,%f;%s]", + image = "image[%f,%f;%f,%f;%s]", + tooltip = "tooltip[%f,%f;%f,%f;%s]", + button = "button[%f,%f;%f,%f;%s;%s]", + item_image = "item_image[%f,%f;%f,%f;%s]", + hypertext = "hypertext[%f,%f;%f,%f;%s;%s]", + bg9 = "background9[%f,%f;%f,%f;%s;false;%u]", + scrollbar = "scrollbar[%f,%f;%f,%f;%s;%s;%u]", + model = "model[%f,%f;%f,%f;%s;%s;%s;%s;%s;%s;%s]", + image_button = "image_button[%f,%f;%f,%f;%s;%s;%s]", + animated_image = "animated_image[%f,%f;%f,%f;;%s;%u;%u]", + item_image_button = "item_image_button[%f,%f;%f,%f;%s;%s;%s]", +} + +return PNG, styles, fs_elements diff --git a/test_custom_recipes.lua b/etc/test_custom_recipes.lua similarity index 100% rename from test_custom_recipes.lua rename to etc/test_custom_recipes.lua diff --git a/test_online_recipe.json b/etc/test_online_recipe.json similarity index 100% rename from test_online_recipe.json rename to etc/test_online_recipe.json diff --git a/test_tabs.lua b/etc/test_tabs.lua similarity index 100% rename from test_tabs.lua rename to etc/test_tabs.lua diff --git a/init.lua b/init.lua index 8d34992..67b1455 100644 --- a/init.lua +++ b/init.lua @@ -1,6 +1,6 @@ i3 = {} -local modpath = minetest.get_modpath "i3" +local modpath = core.get_modpath "i3" local storage = core.get_mod_storage() local slz, dslz = core.serialize, core.deserialize local pdata = dslz(storage:get_string "pdata") or {} @@ -15,7 +15,11 @@ local replacements = {fuel = {}} local toolrepair local tabs = {} -local compress_groups, compressed = loadfile(modpath .. "/compress.lua")() + +local model_aliases = loadfile(modpath .. "/etc/model_aliases.lua")() +local PNG, styles, fs_elements = loadfile(modpath .. "/etc/styles.lua")() +local compress_groups, compressed = loadfile(modpath .. "/etc/compress.lua")() +local group_stereotypes, group_names = loadfile(modpath .. "/etc/groups.lua")() local progressive_mode = core.settings:get_bool "i3_progressive_mode" local item_compression = core.settings:get_bool "i3_item_compression" @@ -40,7 +44,7 @@ local reg_entities = core.registered_entities local reg_aliases = core.registered_aliases local check_privs = core.check_player_privs -local translate = minetest.get_translated_string +local translate = core.get_translated_string local create_inventory = core.create_detached_inventory local ESC = core.formspec_escape @@ -96,117 +100,6 @@ local SUBCAT = { "waypoints", } -local PNG = { - bg = "i3_bg.png", - bg_full = "i3_bg_full.png", - bar = "i3_bar.png", - hotbar = "i3_hotbar.png", - search = "i3_search.png", - heart = "i3_heart.png", - heart_half = "i3_heart_half.png", - heart_grey = "i3_heart_grey.png", - prev = "i3_next.png^\\[transformFX", - next = "i3_next.png", - arrow = "i3_arrow.png", - trash = "i3_trash.png", - sort_az = "i3_sort_az.png", - sort_za = "i3_sort_za.png", - compress = "i3_compress.png", - fire = "i3_fire.png", - fire_anim = "i3_fire_anim.png", - book = "i3_book.png", - sign = "i3_sign.png", - cancel = "i3_cancel.png", - export = "i3_export.png", - slot = "i3_slot.png", - tab = "i3_tab.png", - tab_top = "i3_tab.png^\\[transformFY", - furnace_anim = "i3_furnace_anim.png", - bag = "i3_bag.png", - armor = "i3_armor.png", - awards = "i3_award.png", - skins = "i3_skin.png", - waypoints = "i3_waypoint.png", - teleport = "i3_teleport.png", - add = "i3_add.png", - refresh = "i3_refresh.png", - visible = "i3_visible.png^\\[brighten", - nonvisible = "i3_non_visible.png", - exit = "i3_exit.png", - - cancel_hover = "i3_cancel.png^\\[brighten", - search_hover = "i3_search.png^\\[brighten", - export_hover = "i3_export.png^\\[brighten", - trash_hover = "i3_trash.png^\\[brighten^\\[colorize:#f00:100", - compress_hover = "i3_compress.png^\\[brighten", - sort_az_hover = "i3_sort_az.png^\\[brighten", - sort_za_hover = "i3_sort_za.png^\\[brighten", - prev_hover = "i3_next_hover.png^\\[transformFX", - next_hover = "i3_next_hover.png", - tab_hover = "i3_tab_hover.png", - tab_hover_top = "i3_tab_hover.png^\\[transformFY", - bag_hover = "i3_bag_hover.png", - armor_hover = "i3_armor_hover.png", - awards_hover = "i3_award_hover.png", - skins_hover = "i3_skin_hover.png", - waypoints_hover = "i3_waypoint_hover.png", - teleport_hover = "i3_teleport.png^\\[brighten", - add_hover = "i3_add.png^\\[brighten", - refresh_hover = "i3_refresh.png^\\[brighten", - exit_hover = "i3_exit.png^\\[brighten", -} - -local fs_elements = { - label = "label[%f,%f;%s]", - box = "box[%f,%f;%f,%f;%s]", - image = "image[%f,%f;%f,%f;%s]", - tooltip = "tooltip[%f,%f;%f,%f;%s]", - button = "button[%f,%f;%f,%f;%s;%s]", - item_image = "item_image[%f,%f;%f,%f;%s]", - hypertext = "hypertext[%f,%f;%f,%f;%s;%s]", - bg9 = "background9[%f,%f;%f,%f;%s;false;%u]", - scrollbar = "scrollbar[%f,%f;%f,%f;%s;%s;%u]", - model = "model[%f,%f;%f,%f;%s;%s;%s;%s;%s;%s;%s]", - image_button = "image_button[%f,%f;%f,%f;%s;%s;%s]", - animated_image = "animated_image[%f,%f;%f,%f;;%s;%u;%u]", - item_image_button = "item_image_button[%f,%f;%f,%f;%s;%s;%s]", -} - -local styles = sprintf([[ - style_type[field;border=false;bgcolor=transparent] - style_type[label,field;font_size=16] - style_type[button;border=false;content_offset=0] - style_type[image_button,item_image_button;border=false;sound=i3_click] - style_type[item_image_button;bgimg_hovered=%s] - - style[pagenum,no_item,no_rcp;font=bold;font_size=18] - style[exit;fgimg=%s;fgimg_hovered=%s;content_offset=0] - style[cancel;fgimg=%s;fgimg_hovered=%s;content_offset=0] - style[search;fgimg=%s;fgimg_hovered=%s;content_offset=0] - style[prev_page;fgimg=%s;fgimg_hovered=%s] - style[next_page;fgimg=%s;fgimg_hovered=%s] - style[prev_recipe;fgimg=%s;fgimg_hovered=%s] - style[next_recipe;fgimg=%s;fgimg_hovered=%s] - style[prev_usage;fgimg=%s;fgimg_hovered=%s] - style[next_usage;fgimg=%s;fgimg_hovered=%s] - style[waypoint_add;fgimg=%s;fgimg_hovered=%s;content_offset=0] - style[btn_bag,btn_armor,btn_skins;font=bold;font_size=18;content_offset=0;sound=i3_click] - style[craft_rcp,craft_usg;noclip=true;font_size=16;sound=i3_craft; - bgimg=i3_btn9.png;bgimg_hovered=i3_btn9_hovered.png; - bgimg_pressed=i3_btn9_pressed.png;bgimg_middle=4,6] -]], -PNG.slot, -PNG.exit, PNG.exit_hover, -PNG.cancel, PNG.cancel_hover, -PNG.search, PNG.search_hover, -PNG.prev, PNG.prev_hover, -PNG.next, PNG.next_hover, -PNG.prev, PNG.prev_hover, -PNG.next, PNG.next_hover, -PNG.prev, PNG.prev_hover, -PNG.next, PNG.next_hover, -PNG.add, PNG.add_hover) - local function get_lang_code(info) return info and info.lang_code end @@ -229,77 +122,6 @@ function core.is_creative_enabled(name) return check_privs(name, {creative = true}) or old_is_creative_enabled(name) end -i3.group_stereotypes = { - dye = "dye:white", - wool = "wool:white", - wood = "default:wood", - tree = "default:tree", - sand = "default:sand", - glass = "default:glass", - stick = "default:stick", - stone = "default:stone", - leaves = "default:leaves", - coal = "default:coal_lump", - vessel = "vessels:glass_bottle", - flower = "flowers:dandelion_yellow", - water_bucket = "bucket:bucket_water", - mesecon_conductor_craftable = "mesecons:wire_00000000_off", -} - -local group_names = { - dye = S"Any dye", - coal = S"Any coal", - sand = S"Any sand", - tree = S"Any tree", - wool = S"Any wool", - glass = S"Any glass", - stick = S"Any stick", - stone = S"Any stone", - carpet = S"Any carpet", - flower = S"Any flower", - leaves = S"Any leaves", - vessel = S"Any vessel", - wood = S"Any wood planks", - mushroom = S"Any mushroom", - - ["color_red,flower"] = S"Any red flower", - ["color_blue,flower"] = S"Any blue flower", - ["color_black,flower"] = S"Any black flower", - ["color_white,flower"] = S"Any white flower", - ["color_green,flower"] = S"Any green flower", - ["color_orange,flower"] = S"Any orange flower", - ["color_yellow,flower"] = S"Any yellow flower", - ["color_violet,flower"] = S"Any violet flower", - - ["color_red,dye"] = S"Any red dye", - ["color_blue,dye"] = S"Any blue dye", - ["color_grey,dye"] = S"Any grey dye", - ["color_pink,dye"] = S"Any pink dye", - ["color_cyan,dye"] = S"Any cyan dye", - ["color_black,dye"] = S"Any black dye", - ["color_white,dye"] = S"Any white dye", - ["color_brown,dye"] = S"Any brown dye", - ["color_green,dye"] = S"Any green dye", - ["color_orange,dye"] = S"Any orange dye", - ["color_yellow,dye"] = S"Any yellow dye", - ["color_violet,dye"] = S"Any violet dye", - ["color_magenta,dye"] = S"Any magenta dye", - ["color_dark_grey,dye"] = S"Any dark grey dye", - ["color_dark_green,dye"] = S"Any dark green dye", -} - -i3.model_alias = { - ["boats:boat"] = {name = "boats:boat", drawtype = "entity"}, - ["carts:cart"] = {name = "carts:cart", drawtype = "entity", frames = "0,0"}, - ["default:chest"] = {name = "default:chest_open"}, - ["default:chest_locked"] = {name = "default:chest_locked_open"}, - ["doors:door_wood"] = {name = "doors:door_wood_a"}, - ["doors:door_glass"] = {name = "doors:door_glass_a"}, - ["doors:door_obsidian_glass"] = {name = "doors:door_obsidian_glass_a"}, - ["doors:door_steel"] = {name = "doors:door_steel_a"}, - ["xpanes:door_steel_bar"] = {name = "xpanes:door_steel_bar_a"}, -} - local function err(str) return log("error", str) end @@ -976,7 +798,7 @@ end local function groups_to_items(groups, get_all) if not get_all and #groups == 1 then local group = groups[1] - local stereotype = i3.group_stereotypes[group] + local stereotype = group_stereotypes[group] local def = reg_items[stereotype] if def and show_item(def) then @@ -1756,7 +1578,7 @@ local function get_header(fs, data) fs("style_type[label;font=normal;font_size=16]") local def = reg_items[data.query_item] - local model_alias = i3.model_alias[data.query_item] + local model_alias = model_aliases[data.query_item] if def.drawtype == "mesh" or model_alias then get_model_fs(fs, data, def, model_alias) @@ -3492,5 +3314,5 @@ for size, rcp in pairs(bag_recipes) do core.register_craft {type = "fuel", recipe = bagname, burntime = 3} end ---dofile(modpath .. "/test_tabs.lua") ---dofile(modpath .. "/test_custom_recipes.lua") +--dofile(modpath .. "/etc/test_tabs.lua") +--dofile(modpath .. "/etc/test_custom_recipes.lua") From 00042c77e1011e3c7b973df418c51361e2219d54 Mon Sep 17 00:00:00 2001 From: Jean-Patrick Guerrero Date: Fri, 25 Jun 2021 04:46:12 +0200 Subject: [PATCH 06/19] Add small check --- init.lua | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/init.lua b/init.lua index 67b1455..6c973b2 100644 --- a/init.lua +++ b/init.lua @@ -163,7 +163,7 @@ local function fmt(elem, ...) end local function clean_name(item) - if sub(item, 1, 1) == ":" or sub(item, 1, 1) == " " then + if sub(item, 1, 1) == ":" or sub(item, 1, 1) == " " or sub(item, 1, 1) == "_" then item = sub(item, 2) end @@ -801,12 +801,13 @@ local function groups_to_items(groups, get_all) local stereotype = group_stereotypes[group] local def = reg_items[stereotype] - if def and show_item(def) then + if show_item(def) then return stereotype end end local names = {} + for name, def in pairs(reg_items) do if show_item(def) and item_has_groups(def.groups, groups) then if get_all then @@ -1102,8 +1103,10 @@ local function select_item(player, name, data, _f) local i = 1 for _, v in ipairs(items) do - insert(data.alt_items, idx + i, v) - i = i + 1 + if show_item(reg_items[clean_name(v)]) then + insert(data.alt_items, idx + i, v) + i = i + 1 + end end end end From c3f050b737880e20a52a23b6f0eba54698c261b2 Mon Sep 17 00:00:00 2001 From: Jean-Patrick Guerrero Date: Fri, 25 Jun 2021 17:20:15 +0200 Subject: [PATCH 07/19] Some fixes --- etc/compress.lua | 42 +++++++++++++++++++++++++++++++++++++++--- init.lua | 14 +++++++------- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/etc/compress.lua b/etc/compress.lua index 4032ccd..df30580 100644 --- a/etc/compress.lua +++ b/etc/compress.lua @@ -31,11 +31,31 @@ local to_compress = { by = {"lava", "river_water", "water"} }, + ["butterflies:butterfly_white"] = { + replace = "white", + by = {"red", "violet"} + }, + ["default:wood"] = { replace = "wood", by = wood_types, }, + ["default:tree"] = { + replace = "tree", + by = {"acacia_tree", "aspen_tree", "jungletree", "pine_tree"}, + }, + + ["default:grass_1"] = { + replace = "grass_1", + by = {"dry_grass_1", "junglegrass", "marram_grass_1", "fern_1"}, + }, + + ["carts:rail"] = { + replace = "rail", + by = {"brakerail", "powerrail"}, + }, + ["default:sapling"] = { replace = "sapling", by = { @@ -56,6 +76,11 @@ local to_compress = { by = {"clay", "coal", "copper", "iron", "tin"} }, + ["default:gold_ingot"] = { + replace = "gold_ingot", + by = {"bronze_ingot", "copper_ingot", "steel_ingot", "tin_ingot", "clay_brick"} + }, + ["default:leaves"] = { replace = "leaves", by = { @@ -66,6 +91,8 @@ local to_compress = { "blueberry_bush_leaves_with_berries", "bush_leaves", "jungleleaves", + "pine_bush_needles", + "pine_needles", }, }, @@ -147,6 +174,11 @@ local to_compress = { by = material_tools }, + ["farming:hoe_steel"] = { + replace = "steel", + by = {"wood", "stone"} + }, + ["stairs:slab_wood"] = { replace = "wood", by = material_stairs @@ -166,6 +198,11 @@ local to_compress = { replace = "wood", by = material_stairs }, + + ["walls:cobble"] = { + replace = "cobble", + by = {"desertcobble", "mossycobble"} + }, } local compressed = {} @@ -173,9 +210,8 @@ local compressed = {} for k, v in pairs(to_compress) do compressed[k] = compressed[k] or {} - for _, str in ipairs(v.by) do - local a, b = k:match("(.*):(.*)") - local it = fmt("%s:%s", a, b:gsub(v.replace, str)) + for _, str in ipairs(v.by) do + local it = k:gsub(v.replace, str) insert(compressed[k], it) end end diff --git a/init.lua b/init.lua index 6c973b2..a1f5eb6 100644 --- a/init.lua +++ b/init.lua @@ -460,12 +460,12 @@ function i3.get_search_filters() return search_filters end -local function compression_active() - return item_compression and not next(recipe_filters) +local function compression_active(data) + return item_compression and not next(recipe_filters) and data.filter == "" end -local function compressible(item) - return compression_active() and compress_groups[item] +local function compressible(item, data) + return compression_active(data) and compress_groups[item] end local function weird_desc(str) @@ -1064,7 +1064,7 @@ local function select_item(player, name, data, _f) if not item then return end - if compressible(item) then + if compressible(item, data) then local idx for i = 1, #data.items do @@ -1662,7 +1662,7 @@ local function get_rcp_extra(player, fs, data, panel, is_recipe, is_usage) end local function get_items_fs(fs, data, extend) - if compression_active() then + if compression_active(data) then local new = {} for i = 1, #data.items do @@ -1722,7 +1722,7 @@ local function get_items_fs(fs, data, extend) fs[#fs + 1] = fmt("item_image_button", X, Y, size, size, name, item, "") - if compressible(item) then + if compressible(item, data) then local expand = data.expand == name fs(fmt("tooltip[%s;%s]", item, expand and ES"Click to hide" or ES"Click to expand")) From a1b2d03a715f0a93e6fa22199418ae0df588f523 Mon Sep 17 00:00:00 2001 From: Jean-Patrick Guerrero Date: Sat, 26 Jun 2021 03:10:05 +0200 Subject: [PATCH 08/19] Move some files --- API.md | 2 +- init.lua | 4 ++-- {etc => tests}/test_custom_recipes.lua | 0 {etc => tests}/test_online_recipe.json | 0 {etc => tests}/test_tabs.lua | 0 5 files changed, 3 insertions(+), 3 deletions(-) rename {etc => tests}/test_custom_recipes.lua (100%) rename {etc => tests}/test_online_recipe.json (100%) rename {etc => tests}/test_tabs.lua (100%) diff --git a/API.md b/API.md index 4bf8448..e3c0b49 100644 --- a/API.md +++ b/API.md @@ -141,7 +141,7 @@ Recipes can be registered from a given URL containing a JSON file (HTTP support ```Lua i3.register_craft({ - url = "https://raw.githubusercontent.com/minetest-mods/i3/main/etc/test_online_recipe.json" + url = "https://raw.githubusercontent.com/minetest-mods/i3/main/tests/test_online_recipe.json" }) ``` diff --git a/init.lua b/init.lua index a1f5eb6..ba29987 100644 --- a/init.lua +++ b/init.lua @@ -3317,5 +3317,5 @@ for size, rcp in pairs(bag_recipes) do core.register_craft {type = "fuel", recipe = bagname, burntime = 3} end ---dofile(modpath .. "/etc/test_tabs.lua") ---dofile(modpath .. "/etc/test_custom_recipes.lua") +--dofile(modpath .. "/tests/test_tabs.lua") +--dofile(modpath .. "/tests/test_custom_recipes.lua") diff --git a/etc/test_custom_recipes.lua b/tests/test_custom_recipes.lua similarity index 100% rename from etc/test_custom_recipes.lua rename to tests/test_custom_recipes.lua diff --git a/etc/test_online_recipe.json b/tests/test_online_recipe.json similarity index 100% rename from etc/test_online_recipe.json rename to tests/test_online_recipe.json diff --git a/etc/test_tabs.lua b/tests/test_tabs.lua similarity index 100% rename from etc/test_tabs.lua rename to tests/test_tabs.lua From b24411b189dcd25f344ae419ebb02448f5aeddfd Mon Sep 17 00:00:00 2001 From: Jean-Patrick Guerrero Date: Sat, 26 Jun 2021 04:40:57 +0200 Subject: [PATCH 09/19] Add tabs to item list --- etc/styles.lua | 2 + init.lua | 88 +++++++++++++++++++++----------- textures/i3_tab_small.png | Bin 0 -> 5721 bytes textures/i3_tab_small_hover.png | Bin 0 -> 16963 bytes 4 files changed, 61 insertions(+), 29 deletions(-) create mode 100644 textures/i3_tab_small.png create mode 100644 textures/i3_tab_small_hover.png diff --git a/etc/styles.lua b/etc/styles.lua index 052a27e..da91599 100644 --- a/etc/styles.lua +++ b/etc/styles.lua @@ -24,6 +24,7 @@ local PNG = { export = "i3_export.png", slot = "i3_slot.png", tab = "i3_tab.png", + tab_small = "i3_tab_small.png", tab_top = "i3_tab.png^\\[transformFY", furnace_anim = "i3_furnace_anim.png", bag = "i3_bag.png", @@ -48,6 +49,7 @@ local PNG = { prev_hover = "i3_next_hover.png^\\[transformFX", next_hover = "i3_next_hover.png", tab_hover = "i3_tab_hover.png", + tab_small_hover = "i3_tab_small_hover.png", tab_hover_top = "i3_tab_hover.png^\\[transformFY", bag_hover = "i3_bag_hover.png", armor_hover = "i3_armor_hover.png", diff --git a/init.lua b/init.lua index ba29987..587bfeb 100644 --- a/init.lua +++ b/init.lua @@ -7,7 +7,6 @@ local pdata = dslz(storage:get_string "pdata") or {} -- Caches local init_items = {} -local searches = {} local recipes_cache = {} local usages_cache = {} local fuel_cache = {} @@ -571,17 +570,15 @@ local function show_item(def) def.description and def.description ~= "" end -local function search(data) +local function reset_compression(data) data.alt_items = nil data.expand = "" +end + +local function search(data) + reset_compression(data) local filter = data.filter - - if searches[filter] then - data.items = searches[filter] - return - end - local opt = "^(.-)%+([%w_]+)=([%w_,]+)" local search_filter = next(search_filters) and match(filter, opt) local filters = {} @@ -629,16 +626,36 @@ local function search(data) end end - if not next(recipe_filters) then - -- Cache the results only if searched 2 times - if searches[filter] == nil then - searches[filter] = false - else - searches[filter] = filtered_list + data.items = filtered_list +end + +local function sort_by_category(data) + reset_compression(data) + local items = copy(data.items_raw) + + if data.filter ~= "" then + search(data) + items = copy(data.items) + end + + local new = {} + + for i = 1, #items do + local item = items[i] + local to_add = true + + if data.current_itab == 2 then + to_add = reg_nodes[item] + elseif data.current_itab == 3 then + to_add = reg_craftitems[item] or reg_tools[item] + end + + if to_add then + new[#new + 1] = item end end - data.items = filtered_list + data.items = new end local function get_item_usages(item, recipe, added) @@ -1661,7 +1678,7 @@ local function get_rcp_extra(player, fs, data, panel, is_recipe, is_usage) fs("container_end[]") end -local function get_items_fs(fs, data, extend) +local function get_items_fs(fs, data, full_height) if compression_active(data) then local new = {} @@ -1676,8 +1693,7 @@ local function get_items_fs(fs, data, extend) end local items = data.alt_items or data.items - local rows = 8 - local lines = extend and 12 or 9 + local rows, lines = 8, 12 local ipp = rows * lines local size = 0.85 @@ -1718,7 +1734,7 @@ local function get_items_fs(fs, data, extend) X = X - (X * 0.045) + data.inv_width + 0.28 local Y = round((i % ipp - X) / rows + 1, 0) - Y = Y - (Y * (extend and 0.085 or 0.035)) + 0.95 + Y = Y - (Y * 0.085) + 0.95 fs[#fs + 1] = fmt("item_image_button", X, Y, size, size, name, item, "") @@ -1732,6 +1748,22 @@ local function get_items_fs(fs, data, extend) end end end + + local _tabs = {"All", "Nodes", "Items"} + local tab_len, tab_hgh = 1.8, 0.5 + + for i, title in ipairs(_tabs) do + local selected = i == data.current_itab + + fs(fmt([[style_type[image_button;fgimg=%s;fgimg_hovered=%s;noclip=true; + font_size=16;textcolor=%s;content_offset=0;sound=i3_tab] ]], + selected and PNG.tab_small_hover or PNG.tab_small, + PNG.tab_small_hover, selected and "#fff" or "#ddd")) + + fs("style_type[image_button:hovered;textcolor=#fff]") + fs("image_button", (data.inv_width - 0.65) + (i * (tab_len + 0.1)), + full_height, tab_len, tab_hgh, "", fmt("itab_%u", i), title) + end end local function get_favs(fs, data) @@ -1753,21 +1785,15 @@ end local function get_panels(player, data, fs, full_height) local _title = {name = "title", height = 1.4} local _favs = {name = "favs", height = 2.23} - local _items = {name = "items", height = 9.69} + local _items = {name = "items", height = full_height} local _recipes = {name = "recipes", rcp = data.recipes, height = 4.045} local _usages = {name = "usages", rcp = data.usages, height = 4.045} - local panels, extend + local panels if data.query_item then panels = {_title, _recipes, _usages, _favs} else - panels = {_items, _favs} - - if #data.favs == 0 then - extend = true - remove(panels, 2) - _items.height = full_height - end + panels = {_items} end for idx = 1, #panels do @@ -1787,7 +1813,7 @@ local function get_panels(player, data, fs, full_height) if is_recipe or is_usage then get_rcp_extra(player, fs, data, panel, is_recipe, is_usage) elseif panel.name == "items" then - get_items_fs(fs, data, extend) + get_items_fs(fs, data, full_height) elseif panel.name == "title" then get_header(fs, data) elseif panel.name == "favs" then @@ -2251,6 +2277,7 @@ local function init_data(player, info) data.favs = {} data.export_counts = {} data.current_tab = 1 + data.current_itab = 1 data.subcat = 1 data.scrbar_inv = 0 data.lang_code = get_lang_code(info) @@ -3035,6 +3062,9 @@ core.register_on_player_receive_fields(function(player, formname, fields) local tabname = sub(f, 5) set_tab(player, tabname) break + elseif sub(f, 1, 5) == "itab_" then + data.current_itab = tonum(f:sub(-1)) + sort_by_category(data) end end diff --git a/textures/i3_tab_small.png b/textures/i3_tab_small.png new file mode 100644 index 0000000000000000000000000000000000000000..2a2fc0b479c28a86ce03d3b3d22b29a6fc7af017 GIT binary patch literal 5721 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 literal 0 HcmV?d00001 diff --git a/textures/i3_tab_small_hover.png b/textures/i3_tab_small_hover.png new file mode 100644 index 0000000000000000000000000000000000000000..102c027d80573f9df5e1711ea889d9e6a431509f GIT binary patch literal 16963 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