diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..a140226 --- /dev/null +++ b/init.lua @@ -0,0 +1,33 @@ + +local S = minetest.get_translator("quickstack") +local MP = minetest.get_modpath("quickstack") + +local function get_settings(player) + local str = player:get_meta():get("quickstack_settings") + if not str then + return {} + end + str = minetest.parse_json(str) + return str or {} +end + +local function save_settings(player, settings) + local str = minetest.write_json(settings) + player:get_meta():set_string("quickstack_settings", str) +end + +local quickstack = loadfile(MP.."/quickstack.lua")(get_settings) + +loadfile(MP.."/ui.lua")(quickstack, get_settings, save_settings) + +minetest.register_chatcommand("qs", { + description = S("Quick stack to nearby chests"), + func = function(name) + local player = minetest.get_player_by_name(name) + if not player then + return false + end + quickstack(player) + return true + end +}) diff --git a/mod.conf b/mod.conf new file mode 100644 index 0000000..95ca8e6 --- /dev/null +++ b/mod.conf @@ -0,0 +1,4 @@ +name = quickstack +description = Adds Terraria's quick stack feature to Unified Inventory +depends = unified_inventory +min_minetest_version = 5.5.0 diff --git a/quickstack.lua b/quickstack.lua new file mode 100644 index 0000000..9be5a23 --- /dev/null +++ b/quickstack.lua @@ -0,0 +1,123 @@ + +local get_settings = ... +local chest_nodenames, cooldowns = {}, {} + +local function can_quickstack(settings, index, stack) + if stack:is_empty() or settings["qs_locked_"..index] or + settings.qs_lock_tools and minetest.registered_tools[stack:get_name()] then + return false + end + return true +end + +local function find_nearby_chests(player) + local pos = vector.round(player:get_pos()) + local pos1 = vector.subtract(pos, vector.new(4, 3, 4)) + local pos2 = vector.add(pos, vector.new(4, 3, 4)) + return minetest.find_nodes_in_area(pos1, pos2, chest_nodenames, true) +end + +local function stack_to_chest(settings, pos, player, items, allow_put) + local inv = minetest.get_meta(pos):get_inventory() + if inv:get_size("main") == 0 or inv:is_empty("main") then + return + end + local items_added = false + for _,stack in pairs(items) do + if inv:contains_item("main", stack:peek_item(1), settings.qs_exact_match) then + local count = stack:get_count() + if allow_put then + count = allow_put(pos, "main", nil, stack, player) + end + if count and count > 0 then + local to_add = stack:take_item(count) + local leftover = inv:add_item("main", to_add) + if leftover:get_count() < count then + items_added = true + end + stack:add_item(leftover) + end + end + end + return items_added +end + +local function quickstack(player) + if not player then + return + end + local inv = player:get_inventory() + if inv:is_empty("main") then + return + end + local player_name = player:get_player_name() + local now = os.time() + if cooldowns[player_name] and now < cooldowns[player_name] then + return + end + cooldowns[player_name] = now + 3 + local settings = get_settings(player) + local items = {} + for i, stack in pairs(inv:get_list("main")) do + if can_quickstack(settings, i, stack) then + items[i] = stack + end + end + if next(items) == nil then + return + end + local chests = find_nearby_chests(player) + if next(chests) == nil then + return + end + local items_added = false + for name, positions in pairs(chests) do + local def = minetest.registered_nodes[name] + if def then + local allow_put = def.allow_metadata_inventory_put + for _,pos in pairs(positions) do + if stack_to_chest(settings, pos, player, items, allow_put) then + items_added = true + end + end + end + end + if items_added then + for i, stack in pairs(items) do + inv:set_stack("main", i, stack) + end + minetest.sound_play("quickstack_pop", {to_player = player_name, gain = 0.1}) + end +end + +local function setting_to_table(setting) + local t = {} + local str = minetest.settings:get(setting) + if not str then + return t + end + for _,name in pairs(str:split(",")) do + name = name:trim() + if minetest.registered_nodes[name] then + t[name] = true + end + end + return t +end + +minetest.register_on_mods_loaded(function() + local exclude_nodes = setting_to_table("quickstack_exclude_nodes") + local include_nodes = setting_to_table("quickstack_include_nodes") + for name, def in pairs(minetest.registered_nodes) do + if not exclude_nodes[name] then + if name:find("chest") or def.description and def.description:find("[Cc]hest") then + include_nodes[name] = true + end + end + end + for name in pairs(include_nodes) do + table.insert(chest_nodenames, name) + end +end) + +return quickstack diff --git a/sounds/quickstack_pop.ogg b/sounds/quickstack_pop.ogg new file mode 100644 index 0000000..74b6265 Binary files /dev/null and b/sounds/quickstack_pop.ogg differ diff --git a/textures/quicksatck_locked.png b/textures/quicksatck_locked.png new file mode 100644 index 0000000..6ae9f09 Binary files /dev/null and b/textures/quicksatck_locked.png differ diff --git a/textures/quicksatck_unlocked.png b/textures/quicksatck_unlocked.png new file mode 100644 index 0000000..4ab795f Binary files /dev/null and b/textures/quicksatck_unlocked.png differ diff --git a/textures/quickstack_button.png b/textures/quickstack_button.png new file mode 100644 index 0000000..3372f7b Binary files /dev/null and b/textures/quickstack_button.png differ diff --git a/textures/quickstack_lock_icon.png b/textures/quickstack_lock_icon.png new file mode 100644 index 0000000..e11a930 Binary files /dev/null and b/textures/quickstack_lock_icon.png differ diff --git a/textures/quickstack_lock_overlay.png b/textures/quickstack_lock_overlay.png new file mode 100644 index 0000000..bfb3fa6 Binary files /dev/null and b/textures/quickstack_lock_overlay.png differ diff --git a/textures/quickstack_settings.png b/textures/quickstack_settings.png new file mode 100644 index 0000000..feba085 Binary files /dev/null and b/textures/quickstack_settings.png differ diff --git a/ui.lua b/ui.lua new file mode 100644 index 0000000..c418ff6 --- /dev/null +++ b/ui.lua @@ -0,0 +1,115 @@ + +local S = minetest.get_translator("quickstack") +local FS = function(...) + return minetest.formspec_escape(S(...)) +end +local floor, format, insert, concat = math.floor, string.format, table.insert, table.concat +local quickstack, get_settings, save_settings = ... +local ui = unified_inventory + +ui.register_button("quickstack", { + type = "image", + image = "quickstack_button.png", + tooltip = S("Quick stack to nearby chests"), + action = quickstack, +}) + +ui.register_button("quickstack_settings", { + type = "image", + image = "quickstack_settings.png", + tooltip = S("Quick stack settings"), +}) + +local function get_inventory_overlay(player, style) + local settings = get_settings(player) + if not settings.qs_show_locked then + return "" + end + local overlay = {} + for i=1, 32 do + local x, y = (i-1)%8, floor((i-1)/8) + if settings["qs_locked_"..i] then + insert(overlay, format("image[%f,%f;1.25,1.25;quickstack_lock_overlay.png]", + style.std_inv_x + x*1.25, style.std_inv_y + y*1.25)) + end + end + return concat(overlay) +end + +ui.register_page("quickstack_settings", {get_formspec = function(player, style) + local settings = get_settings(player) + local offset = style.is_lite_mode and 0.7 or 1.0 + local formspec = { + style.standard_inv_bg, + format("label[%f,%f;%s]", style.form_header_x, style.form_header_y, FS("Quick stack settings")), + format("tooltip[%f,%f;6.7,3.3;%s]", + style.std_inv_x + 0.1, offset, FS("Locked inventory slots are not quick stacked")), + format("image_button[%f,%f;0.75,0.75;ui_locked.png;qs_lock_all;]tooltip[qs_lock_all;%s]", + style.std_inv_x + 7.35, offset, FS("Lock all inventory slots")), + format("image_button[%f,%f;0.75,0.75;ui_unlocked.png;qs_unlock_all;]tooltip[qs_unlock_all;%s]", + style.std_inv_x + 8.2, offset, FS("Unlock all inventory slots")), + } + for i=1, 32 do + local x, y = (i-1)%8, floor((i-1)/8) + if settings["qs_locked_"..i] then + insert(formspec, format("image_button[%f,%f;0.75,0.75;quickstack_lock_icon.png;%s;]", + style.std_inv_x + 0.1 + x*0.85, offset + y*0.85, "qs_unlock_"..i)) + else + insert(formspec, format("button[%f,%f;0.75,0.75;%s;]", + style.std_inv_x + 0.1 + x*0.85, offset + y*0.85, "qs_lock_"..i)) + end + end + local checkboxes = { + {"qs_lock_tools", FS("Lock tools"), FS("Tools are not quick stacked")}, + {"qs_exact_match", FS("Exact match"), FS("Only items with matching metadata are quick stacked")}, + {"qs_show_locked", FS("Show locked"), FS("Locked slots are shown in the inventory")}, + } + for i, box in ipairs(checkboxes) do + local checked = settings[box[1]] and "true" or "false" + insert(formspec, format("checkbox[%f,%f;%s;%s;%s]tooltip[%s;%s]", + style.std_inv_x + 7.35, offset + 1.2 + (i-1)*0.6, box[1], box[2], checked, box[1], box[3])) + end + return {formspec = concat(formspec)} +end}) + +minetest.register_on_player_receive_fields(function(player, formname, fields) + if not player or not fields or formname ~= "" then + return + end + local settings = get_settings(player) + local update_formspec = false + for i=1, 32 do + if (fields.qs_lock_all or fields["qs_lock_"..i]) and not settings["qs_locked_"..i] then + settings["qs_locked_"..i] = true + update_formspec = true + elseif (fields.qs_unlock_all or fields["qs_unlock_"..i]) and settings["qs_locked_"..i] then + settings["qs_locked_"..i] = nil + update_formspec = true + end + end + for _,box in pairs({"qs_lock_tools", "qs_exact_match", "qs_show_locked"}) do + if fields[box] then + settings[box] = fields[box] == "true" and true or nil + update_formspec = true + end + end + if update_formspec then + save_settings(player, settings) + minetest.sound_play("ui_click", {to_player=player:get_player_name(), gain = 0.1}) + ui.set_inventory_formspec(player, "quickstack_settings") + end +end) + +minetest.register_on_mods_loaded(function() + for _,page in pairs(ui.pages) do + local old_get_formspec = page.get_formspec + page.get_formspec = function(player, style) + local formspec = old_get_formspec(player, style) + if formspec.draw_inventory ~= false then + local overlay = get_inventory_overlay(player, style) + formspec.formspec = formspec.formspec..overlay + end + return formspec + end + end +end)