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